diff --git a/lib/instrument/blocks.js b/lib/instrument/blocks.js index 8633d50a..62252c0f 100644 --- a/lib/instrument/blocks.js +++ b/lib/instrument/blocks.js @@ -17,6 +17,7 @@ module.exports = { createArgumentsBinding, createNewTargetBinding, getOrCreateExternalVar, + createInternalVar, activateBlock, activateBinding, createBlockTempVar @@ -135,6 +136,7 @@ function createBindingWithoutNameCheck(block, varName, props) { isSilentConst: !!props.isSilentConst, isVar: !!props.isVar, isFunction: !!props.isFunction, + isBehindWith: false, argNames: props.argNames }; } @@ -208,6 +210,21 @@ function getOrCreateExternalVar(externalVars, block, varName, bindingOrExternalV return externalVar; } +/** + * Create an internal var in a function. + * @param {Object} fn - Function object + * @param {string} varName - Var name + * @param {Object} binding - Binding object + * @param {Array} [trail] - Trail for where binding appears (optional) + * @returns {undefined} + */ +function createInternalVar(fn, varName, binding, trail) { + const {internalVars} = fn; + let internalVar = internalVars[varName]; + if (!internalVar) internalVar = internalVars[varName] = {binding, trails: []}; + if (trail) internalVar.trails.push(trail); +} + /** * Activate block. * Called when block contains a binding which is referenced within a function. diff --git a/lib/instrument/visitors/assignee.js b/lib/instrument/visitors/assignee.js index 2c6877fa..79ee1c81 100644 --- a/lib/instrument/visitors/assignee.js +++ b/lib/instrument/visitors/assignee.js @@ -25,9 +25,8 @@ const assert = require('simple-invariant'); const Expression = require('./expression.js'), {IdentifierAssignOnly, IdentifierReadAndAssign} = require('./identifier.js'), MemberExpression = require('./memberExpression.js'), - {createBinding} = require('../blocks.js'), - {visitKey, visitKeyContainer, visitKeyContainerWithEmptyMembers} = require('../visit.js'), - {createArrayOrPush} = require('../../shared/functions.js'); + {createBinding, createInternalVar} = require('../blocks.js'), + {visitKey, visitKeyContainer, visitKeyContainerWithEmptyMembers} = require('../visit.js'); // Exports @@ -157,11 +156,11 @@ function visitConstOrLetIdentifier(node, isConst, state) { const varName = node.name, block = state.currentBlock; assertNoCommonJsVarsClash(block, varName, state); - createBinding(block, varName, {isConst}, state); + const binding = createBinding(block, varName, {isConst}, state); // Record as internal var const fn = state.currentFunction; - if (fn) createArrayOrPush(fn.internalVars, varName, [...state.trail]); + if (fn) createInternalVar(fn, varName, binding, [...state.trail]); } /** @@ -183,24 +182,9 @@ function IdentifierVar(node, state) { return; } - // Leave until later to add to `internalVars` in case binding is redeclared later - // as a function declaration. Vars defined as functions must not be - // added to `internalVars` to avoid their names being mangled. + // Record as internal var const fn = state.currentFunction; - if (fn) state.secondPass(addVarIdentifierToInternalVars, node, binding, varName, fn, [...state.trail]); -} - -/** - * Add identifier to parent function's internal vars. - * @param {Object} node - Identifier AST node (not needed but passed for debug reasons) - * @param {Object} binding - Binding object - * @param {string} varName - Variable name - * @param {Object} fn - Function object - * @param {Array} trail - Trail - * @returns {undefined} - */ -function addVarIdentifierToInternalVars(node, binding, varName, fn, trail) { - if (!binding.isFunction) createArrayOrPush(fn.internalVars, varName, trail); + if (fn) createInternalVar(fn, varName, binding, [...state.trail]); } /** diff --git a/lib/instrument/visitors/function.js b/lib/instrument/visitors/function.js index 5f5b56df..b8e89362 100644 --- a/lib/instrument/visitors/function.js +++ b/lib/instrument/visitors/function.js @@ -716,6 +716,14 @@ function insertTrackerComment(fnId, fnType, commentHolderNode, commentType, stat * @returns {undefined} */ function createFunctionInfoFunction(fn, scopes, astJson, fnInfoVarNode, state) { + // Remove internal vars for functions, + // and remove trails for vars which are accessed from within `with ()` + const internalVars = Object.fromEntries( + Object.entries(fn.internalVars) + .filter(([, {binding}]) => !binding?.isFunction) + .map(([varName, {binding, trails}]) => [varName, binding?.isBehindWith ? [] : trails]) + ); + // Create JSON function info string const {children} = fn; let argNames; @@ -738,7 +746,7 @@ function createFunctionInfoFunction(fn, scopes, astJson, fnInfoVarNode, state) { containsEval: fn.containsEval || undefined, containsImport: fn.containsImport || undefined, argNames, - internalVars: fn.internalVars, + internalVars, globalVarNames: fn.globalVarNames.size !== 0 ? [...fn.globalVarNames] : undefined, amendments: fn.amendments.length !== 0 ? fn.amendments.map(({type, blockId, trail}) => [type, blockId, ...trail]).reverse() diff --git a/lib/instrument/visitors/identifier.js b/lib/instrument/visitors/identifier.js index 4645e32c..fd3fc1b5 100644 --- a/lib/instrument/visitors/identifier.js +++ b/lib/instrument/visitors/identifier.js @@ -16,9 +16,10 @@ module.exports = { // Imports const visitEval = require('./eval.js'), - {getOrCreateExternalVar, activateBlock, activateBinding, createBlockTempVar} = require('../blocks.js'), + { + getOrCreateExternalVar, activateBlock, activateBinding, createBlockTempVar, createInternalVar + } = require('../blocks.js'), {checkInternalVarNameClash} = require('../internalVars.js'), - {createArrayOrPush} = require('../../shared/functions.js'), { CONST_VIOLATION_CONST, CONST_VIOLATION_FUNCTION_THROWING, CONST_VIOLATION_FUNCTION_SILENT } = require('../../shared/constants.js'); @@ -83,7 +84,7 @@ function ThisExpression(node, state) { if (block.id < fn.id) { recordExternalVar(binding, block, 'this', fn, [...state.trail], true, false, false, state); } else if (fn.hasSuperClass) { - createArrayOrPush(fn.internalVars, 'this', [...state.trail]); + createInternalVar(fn, 'this', binding, [...state.trail]); } } @@ -192,6 +193,9 @@ function resolveIdentifier( return; } + // Flag binding as behind `with` if it is + if (isBehindWith) binding.isBehindWith = true; + // Record if internal var if (block.id >= fn.id) { if (isWithBinding) { @@ -202,8 +206,9 @@ function resolveIdentifier( return; } - // TODO: Freeze var name if behind `with () {}` - if (!binding.isFunction && !binding.argNames) createArrayOrPush(fn.internalVars, varName, trail); + if (!binding.isFunction && !binding.argNames) { + createInternalVar(fn, varName, binding, [...state.trail]); + } return; } diff --git a/lib/instrument/visitors/super.js b/lib/instrument/visitors/super.js index 260ee71d..6f4b9b52 100644 --- a/lib/instrument/visitors/super.js +++ b/lib/instrument/visitors/super.js @@ -19,7 +19,8 @@ module.exports = { // Imports const { - createBindingWithoutNameCheck, getOrCreateExternalVar, activateBlock, createBlockTempVar + createBindingWithoutNameCheck, getOrCreateExternalVar, activateBlock, createBlockTempVar, + createInternalVar } = require('../blocks.js'), {getProp} = require('../../shared/functions.js'), {SUPER_CALL, SUPER_EXPRESSION} = require('../../shared/constants.js'); @@ -146,8 +147,7 @@ function activateSuperBinding(superBlock, state) { * @returns {undefined} */ function createInternalVarForThis(fn) { - const {internalVars} = fn; - if (!internalVars.this) internalVars.this = []; + createInternalVar(fn, 'this', null, null); } /**