From 5faf3d4cfa5af8a5aa5625461b632431d818ff81 Mon Sep 17 00:00:00 2001 From: overlookmotel Date: Fri, 1 Sep 2023 20:16:20 +0100 Subject: [PATCH] Instrument: Create function info functions at end of 2nd pass [refactor] --- lib/instrument/modify.js | 11 ++++-- lib/instrument/visitors/class.js | 9 ++--- lib/instrument/visitors/function.js | 61 ++++++++++++----------------- 3 files changed, 37 insertions(+), 44 deletions(-) diff --git a/lib/instrument/modify.js b/lib/instrument/modify.js index e2e9bb6a..1a85e507 100644 --- a/lib/instrument/modify.js +++ b/lib/instrument/modify.js @@ -12,7 +12,9 @@ const {join: pathJoin, parse: pathParse} = require('path'), // Imports const Program = require('./visitors/program.js'), - {escapeFilename, hoistSloppyFunctionDeclarations} = require('./visitors/function.js'), + { + escapeFilename, hoistSloppyFunctionDeclarations, createFunctionInfoFunction + } = require('./visitors/function.js'), { createAndEnterBlock, createBindingWithoutNameCheck, createThisBinding, createArgumentsBinding, createNewTargetBinding @@ -84,7 +86,7 @@ function modifyAst(ast, filename, isCommonJs, isStrict, sources, evalState) { internalVarsPrefixNum: 0, trackerVarNode: undefined, getScopeIdVarNode: undefined, - functionInfoNodes: [], + functions: [], fileContainsFunctionsOrEval: false, secondPass: (fn, ...params) => secondPassQueue.push({fn, params}) }; @@ -266,8 +268,9 @@ function insertImportStatement(programNode, state) { * @returns {undefined} */ function insertFunctionInfoFunctions(programNode, isEvalCode, sources, state) { - const {functionInfoNodes} = state; - if (functionInfoNodes.length === 0) return; + if (state.functions.length === 0) return; + + const functionInfoNodes = state.functions.map(fn => createFunctionInfoFunction(fn, state)); // Insert function to get sources functionInfoNodes.push( diff --git a/lib/instrument/visitors/class.js b/lib/instrument/visitors/class.js index 3d11e5ac..2948c101 100644 --- a/lib/instrument/visitors/class.js +++ b/lib/instrument/visitors/class.js @@ -259,7 +259,7 @@ function visitClass(classNode, parent, key, className, state) { } // Serialize class AST - const astJson = serializeClassAst(classNode, constructorNode, constructorIndex); + fn.astJson = serializeClassAst(classNode, constructorNode, constructorIndex); // If constructor or a prototype property contains `eval()`, make class name internal to class. // Achieved by inserting an extra block between `super` block and constructor params @@ -288,7 +288,7 @@ function visitClass(classNode, parent, key, className, state) { state.secondPass( instrumentClass, classNode, fn, parent, key, constructorNode, constructorParamsBlock, constructorBodyBlock, - superBlock, astJson, state + superBlock, state ); } @@ -424,13 +424,12 @@ function serializeClassAst(classNode, constructorNode, constructorIndex) { * @param {Object} constructorParamsBlock - Constructor params block object * @param {Object} constructorBodyBlock - Constructor body block object * @param {Object} superBlock - `super` target block object - * @param {string} astJson - JSON-stringified class AST * @param {Object} state - State object * @returns {undefined} */ function instrumentClass( classNode, fn, parent, key, constructorNode, constructorParamsBlock, constructorBodyBlock, - superBlock, astJson, state + superBlock, state ) { // If class has no constructor, create one for tracker code to be inserted in if (!constructorNode) { @@ -454,7 +453,7 @@ function instrumentClass( // Add tracker code + block vars to constructor instrumentFunctionOrClassConstructor( - constructorNode, fn, constructorParamsBlock, constructorBodyBlock, astJson, state + constructorNode, fn, constructorParamsBlock, constructorBodyBlock, state ); // Insert tracking comment diff --git a/lib/instrument/visitors/function.js b/lib/instrument/visitors/function.js index 3e025db9..9de9cf99 100644 --- a/lib/instrument/visitors/function.js +++ b/lib/instrument/visitors/function.js @@ -20,7 +20,8 @@ module.exports = { withStrictModeState, hoistSloppyFunctionDeclarations, instrumentFunctionOrClassConstructor, - insertTrackerComment + insertTrackerComment, + createFunctionInfoFunction }; // Modules @@ -228,7 +229,7 @@ function visitFunctionOrMethod( } // Serialize AST - const astJson = serializeFunctionAst(fnNode, hasBodyBlock, isStrict, isEnteringStrict); + fn.astJson = serializeFunctionAst(fnNode, hasBodyBlock, isStrict, isEnteringStrict); // Exit function state.currentBlock = paramsBlock.parent; @@ -239,9 +240,7 @@ function visitFunctionOrMethod( parent[key] = null; // Queue instrumentation of function in 2nd pass - state.secondPass( - instrumentFunction, fnNode, fn, parent, key, astJson, paramsBlock, bodyBlock, state - ); + state.secondPass(instrumentFunction, fnNode, fn, parent, key, paramsBlock, bodyBlock, state); return fn; } @@ -427,12 +426,14 @@ function createFunction(id, node, isStrict, state) { const fn = { id, node, + astJson: undefined, isStrict, parent: parentFunction, children: [], trail: parentFunction ? [...state.trail] : undefined, internalVars: new Map(), // Keyed by binding externalVars: new Map(), // Keyed by block + scopes: undefined, globalVarNames: new Set(), amendments: [], superIsProto: false, @@ -446,6 +447,7 @@ function createFunction(id, node, isStrict, state) { if (parentFunction) parentFunction.children.push(fn); + state.functions.push(fn); state.fileContainsFunctionsOrEval = true; return fn; @@ -586,17 +588,16 @@ function hoistSloppyFunctionDeclaration(declaration) { * @param {Object} fn - Function object * @param {Object|Array} parent - Parent AST node/container * @param {string|number} key - Node's key on parent AST node/container - * @param {string} astJson - JSON-stringified function AST * @param {Object} paramsBlock - Function's params block object * @param {Object} [bodyBlock] - Function's body block object (if it has one, arrow functions may not) * @param {Object} state - State object * @returns {undefined} */ -function instrumentFunction(node, fn, parent, key, astJson, paramsBlock, bodyBlock, state) { +function instrumentFunction(node, fn, parent, key, paramsBlock, bodyBlock, state) { // Restore to original place in AST parent[key] = node; - instrumentFunctionOrClassConstructor(node, fn, paramsBlock, bodyBlock, astJson, state); + instrumentFunctionOrClassConstructor(node, fn, paramsBlock, bodyBlock, state); if (node.type === 'ArrowFunctionExpression') insertArrowFunctionTrackerComment(fn, node, state); } @@ -611,15 +612,15 @@ function instrumentFunction(node, fn, parent, key, astJson, paramsBlock, bodyBlo * @param {Object} fn - Function object * @param {Object} paramsBlock - Function's params block object * @param {Object} bodyBlock - Function's body block object - * @param {string} astJson - JSON-stringified function/class AST * @param {Object} state - State object * @returns {undefined} */ -function instrumentFunctionOrClassConstructor(node, fn, paramsBlock, bodyBlock, astJson, state) { - // NB: `bodyBlock` here may actually be params block if is an arrow function with no body. +function instrumentFunctionOrClassConstructor(node, fn, paramsBlock, bodyBlock, state) { + // NB `bodyBlock` here may actually be params block if is an arrow function with no body. // Sort scopes in ascending order of block ID. const scopes = [...fn.externalVars].map(([block, vars]) => ({block, vars})) .sort((scope1, scope2) => (scope1.block.id > scope2.block.id ? 1 : -1)); + fn.scopes = scopes; // Add external vars to parent function where external to that function too const parentFunction = fn.parent; @@ -634,13 +635,9 @@ function instrumentFunctionOrClassConstructor(node, fn, paramsBlock, bodyBlock, } } - // Create function info function - const fnInfoVarNode = createFnInfoVarNode(fn.id, state); - createFunctionInfoFunction(fn, scopes, astJson, fnInfoVarNode, state); - // Insert tracker call and block vars (`livepack_scopeId` and `livepack_temp`) into function. // If function has complex params, insert into params. Otherwise, into function body. - const trackerNode = createTrackerCall(scopes, fnInfoVarNode, state); + const trackerNode = createTrackerCall(fn.id, scopes, state); const {firstComplexParamIndex} = fn; if (firstComplexParamIndex === undefined) { @@ -654,15 +651,15 @@ function instrumentFunctionOrClassConstructor(node, fn, paramsBlock, bodyBlock, /** * Create tracker call. + * @param {number} fnId - Function ID * @param {Array} scopes - Array of scope objects in ascending order of block ID - * @param {Object} fnInfoVarNode - Function info function identifier AST node * @param {Object} state - State object * @returns {undefined} */ -function createTrackerCall(scopes, fnInfoVarNode, state) { +function createTrackerCall(fnId, scopes, state) { // `livepack_tracker(livepack_getFnInfo_3, () => [[livepack_scopeId_2, x]]);` return t.callExpression(state.trackerVarNode, [ - fnInfoVarNode, + createFnInfoVarNode(fnId, state), t.arrowFunctionExpression([], t.arrayExpression( scopes.map(scope => t.arrayExpression([ scope.block.varsBlock.scopeIdVarNode, @@ -719,15 +716,11 @@ function insertTrackerComment(fnId, fnType, commentHolderNode, commentType, stat /** * Create function info function containing function AST, details of internal and external vars etc. - * Add to `state.functionInfoNodes`. It will be inserted into file at end of 2nd pass. * @param {Object} fn - Function object - * @param {Array} scopes - Array of scope objects in ascending order of block ID - * @param {string} astJson - JSON-stringified function AST - * @param {Object} fnInfoVarNode - Function info function identifier AST node * @param {Object} state - State object - * @returns {undefined} + * @returns {Object} - AST node for function info function */ -function createFunctionInfoFunction(fn, scopes, astJson, fnInfoVarNode, state) { +function createFunctionInfoFunction(fn, state) { // Compile internal vars const internalVars = Object.create(null); for (const {varName, trails} of fn.internalVars.values()) { @@ -739,7 +732,7 @@ function createFunctionInfoFunction(fn, scopes, astJson, fnInfoVarNode, state) { const {children} = fn; let argNames; let json = JSON.stringify({ - scopes: scopes.map(({block, vars}) => ({ + scopes: fn.scopes.map(({block, vars}) => ({ blockId: block.id, blockName: block.name, vars: mapValues(vars, (varProps, varName) => { @@ -769,17 +762,15 @@ function createFunctionInfoFunction(fn, scopes, astJson, fnInfoVarNode, state) { }); // Add AST JSON to JSON - json = `${json.slice(0, -1)},"ast":${astJson}}`; + json = `${json.slice(0, -1)},"ast":${fn.astJson}}`; // Create function returning JSON string and references to function info function for child fns. // Make output as single-quoted string for shorter output (not escaping every `"`). - state.functionInfoNodes.push( - t.functionDeclaration(fnInfoVarNode, [], t.blockStatement([ - t.returnStatement(t.arrayExpression([ - stringLiteralWithSingleQuotes(json), - t.arrayExpression(children.map(childFn => createFnInfoVarNode(childFn.id, state))), - state.getSourcesNode - ])) + return t.functionDeclaration(createFnInfoVarNode(fn.id, state), [], t.blockStatement([ + t.returnStatement(t.arrayExpression([ + stringLiteralWithSingleQuotes(json), + t.arrayExpression(children.map(childFn => createFnInfoVarNode(childFn.id, state))), + state.getSourcesNode ])) - ); + ])); }