Skip to content

Commit

Permalink
Allow freezing individual external vars (prep for with () support)
Browse files Browse the repository at this point in the history
  • Loading branch information
overlookmotel committed Nov 23, 2023
1 parent 5e7d7a9 commit 1aae468
Show file tree
Hide file tree
Showing 5 changed files with 36 additions and 19 deletions.
21 changes: 13 additions & 8 deletions lib/instrument/visitors/eval.js
Original file line number Diff line number Diff line change
Expand Up @@ -165,14 +165,19 @@ function instrumentEvalCall(callNode, block, fn, isStrict, canUseSuper, superIsP
// If var is external to function, record function as using this var.
// Ignore `new.target` and `super` as they're not possible to recreate.
// https://github.com/overlookmotel/livepack/issues/448
if (externalVars && varName !== 'new.target' && varName !== 'super') {
activateBinding(binding, varName);
const externalVar = getOrCreateExternalVar(externalVars, block, varName, binding);
externalVar.isReadFrom = true;
if (!isConst) externalVar.isAssignedTo = true;
if (!externalVar.isFrozenName) {
externalVar.isFrozenName = true;
externalVar.trails.length = 0;
if (externalVars) {
if (varName === 'new.target' || varName === 'super') {
const externalVar = externalVars.get(block)?.[varName];
if (externalVar) externalVar.isFrozenName = true;
} else {
activateBinding(binding, varName);
const externalVar = getOrCreateExternalVar(externalVars, block, varName, binding);
externalVar.isReadFrom = true;
if (!isConst) externalVar.isAssignedTo = true;
if (!externalVar.isFrozenName) {
externalVar.isFrozenName = true;
externalVar.trails.length = 0;
}
}
}
}
Expand Down
1 change: 1 addition & 0 deletions lib/instrument/visitors/function.js
Original file line number Diff line number Diff line change
Expand Up @@ -762,6 +762,7 @@ function createFunctionInfoFunction(fn, state) {
return {
isReadFrom: varProps.isReadFrom || undefined,
isAssignedTo: varProps.isAssignedTo || undefined,
isFrozenName: varProps.isFrozenName || undefined,
isFrozenInternalName: varProps.binding.isFrozenName || undefined,
trails: varProps.trails
};
Expand Down
10 changes: 6 additions & 4 deletions lib/serialize/blocks.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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;
Expand Down Expand Up @@ -637,7 +639,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) {
Expand Down Expand Up @@ -665,7 +667,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.
Expand Down
9 changes: 5 additions & 4 deletions lib/serialize/functions.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
}

Expand Down
14 changes: 11 additions & 3 deletions lib/serialize/parseFunction.js
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,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
};
})
});
}
Expand Down Expand Up @@ -867,14 +871,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
Expand Down

0 comments on commit 1aae468

Please sign in to comment.