From 647ebedfee8533eb215fbfe9a4dad11cf646fd6a Mon Sep 17 00:00:00 2001 From: overlookmotel Date: Mon, 11 Dec 2023 02:03:50 +0000 Subject: [PATCH] Allow freezing individual external vars [refactor] --- lib/instrument/visitors/function.js | 1 + lib/serialize/blocks.js | 14 ++++++++------ lib/serialize/functions.js | 9 +++++---- lib/serialize/parseFunction.js | 14 +++++++++++--- 4 files changed, 25 insertions(+), 13 deletions(-) diff --git a/lib/instrument/visitors/function.js b/lib/instrument/visitors/function.js index 8541e773..f05c7b7c 100644 --- a/lib/instrument/visitors/function.js +++ b/lib/instrument/visitors/function.js @@ -757,6 +757,7 @@ function createFunctionInfoFunction(fn, state) { { isReadFrom: varProps.isReadFrom || undefined, isAssignedTo: varProps.isAssignedTo || undefined, + isFrozenName: varProps.isFrozenName || undefined, isFrozenInternalName: varProps.binding.isFrozenName || undefined, trails: varProps.trails } diff --git a/lib/serialize/blocks.js b/lib/serialize/blocks.js index e95003a6..6b180e77 100644 --- a/lib/serialize/blocks.js +++ b/lib/serialize/blocks.js @@ -62,9 +62,11 @@ module.exports = { const {containsEval} = block, paramsByName = Object.create(null); let frozenNamesIsCloned = false; - const params = [...block.params.keys()].map((name) => { + const params = [...block.params].map(([name, {isFrozenName}]) => { + if (containsEval) isFrozenName = true; const param = { name, + isFrozenName, // Identifier nodes referring to this param localVarNodes: [], // If param always contains another function defined in this block, @@ -83,7 +85,7 @@ module.exports = { paramsByName[name] = param; // `super` and `new.target` are not actually frozen - if (containsEval && !['super', 'new.target'].includes(name)) { + if (isFrozenName && !['super', 'new.target'].includes(name)) { if (!frozenNamesIsCloned) { frozenNames = new Set(frozenNames); frozenNamesIsCloned = true; @@ -638,7 +640,7 @@ module.exports = { if (paramName === 'new.target') { // `new.target` is always renamed as it can't be provided for `eval()` anyway - newName = transformVarName('newTarget', containsEval); + newName = transformVarName('newTarget', param.isFrozenName); // Convert `MetaProperty` nodes into `Identifier`s with new name for (const varNode of param.localVarNodes) { @@ -650,10 +652,10 @@ module.exports = { // Rename injection node renameInjectionVarNode(); - } else if (!containsEval || paramName === 'super') { + } else if (!param.isFrozenName || paramName === 'super') { // Param can be renamed. // NB: `super` param is always renamed. - newName = transformVarName(paramName, containsEval); + newName = transformVarName(paramName, param.isFrozenName); if (newName !== paramName) { // Rename all nodes for (const varNode of param.localVarNodes) { @@ -666,7 +668,7 @@ module.exports = { } else { // Frozen var name (potentially used in `eval()`) // eslint-disable-next-line no-lonely-if - if (paramName === 'this' || (paramName === 'arguments' && paramsByName.this)) { + if (paramName === 'this' || (paramName === 'arguments' && paramsByName.this?.isFrozenName)) { // `this` or `arguments` captured from function. // `arguments` is only injected with a function wrapper if `this` is frozen too. // Otherwise, can just make `arguments` a normal param. diff --git a/lib/serialize/functions.js b/lib/serialize/functions.js index a0396328..4bb2dae2 100644 --- a/lib/serialize/functions.js +++ b/lib/serialize/functions.js @@ -150,12 +150,13 @@ module.exports = { } // Add var names to block - for (const [varName, {isAssignedTo}] of Object.entries(vars)) { + for (const [varName, {isAssignedTo, isFrozenName}] of Object.entries(vars)) { const param = params.get(varName); if (!param) { - params.set(varName, {isMutable: isAssignedTo || argNames?.has(varName)}); - } else if (isAssignedTo) { - param.isMutable = true; + params.set(varName, {isMutable: isAssignedTo || argNames?.has(varName), isFrozenName}); + } else { + if (isAssignedTo) param.isMutable = true; + if (isFrozenName) param.isFrozenName = true; } } diff --git a/lib/serialize/parseFunction.js b/lib/serialize/parseFunction.js index 771efbb5..636f10df 100644 --- a/lib/serialize/parseFunction.js +++ b/lib/serialize/parseFunction.js @@ -84,7 +84,11 @@ module.exports = function parseFunction( blockName, vars: mapValues(vars, (varProps, varName) => { externalVars[varName] = []; - return {isReadFrom: !!varProps.isReadFrom, isAssignedTo: !!varProps.isAssignedTo}; + return { + isReadFrom: !!varProps.isReadFrom, + isAssignedTo: !!varProps.isAssignedTo, + isFrozenName: !!varProps.isFrozenName + }; }) }); } @@ -747,14 +751,18 @@ function resolveFunctionInfo( const {blockId} = scope; if (blockId < fnId) { // External var - for (const [varName, {isReadFrom, isAssignedTo, trails}] of Object.entries(scope.vars)) { + for ( + const [varName, {isReadFrom, isAssignedTo, isFrozenName, trails}] + of Object.entries(scope.vars) + ) { if (isNestedFunction) { const scopeDefVar = scopeDefs.get(blockId).vars[varName]; if (isReadFrom) scopeDefVar.isReadFrom = true; if (isAssignedTo) scopeDefVar.isAssignedTo = true; + if (isFrozenName) scopeDefVar.isFrozenName = true; } - externalVars[varName].push(...trailsToNodes(fnNode, trails, varName)); + if (!isFrozenName) externalVars[varName].push(...trailsToNodes(fnNode, trails, varName)); } } else { // Var which is external to current function, but internal to function being serialized