Skip to content

Commit

Permalink
Support super in arrow functions in eval() [fix]
Browse files Browse the repository at this point in the history
Fixes #308.
  • Loading branch information
overlookmotel committed Nov 14, 2023
1 parent 18a43d8 commit 02a0f66
Show file tree
Hide file tree
Showing 2 changed files with 22 additions and 12 deletions.
13 changes: 8 additions & 5 deletions lib/init/eval.js
Original file line number Diff line number Diff line change
Expand Up @@ -100,9 +100,11 @@ function evalIndirect(code, tracker, filename, blockIdCounter, externalPrefixNum
* @returns {*} - Result of `eval()` call
*/
function evalDirect(args, filename, blockIdCounter, externalPrefixNum, evalDirectLocal) {
const callArgs = args.slice(0, -5),
const callArgs = args.slice(0, -6),
code = callArgs[0],
[possibleEval, execEvalSingleArg, execEvalSpread, scopeDefs, isStrict] = args.slice(-5);
[
possibleEval, execEvalSingleArg, execEvalSpread, scopeDefs, isStrict, superIsProto
] = args.slice(-6);

// If var `eval` where `eval()` is called is not global `eval`,
// call `eval()` with original args unaltered
Expand All @@ -116,7 +118,8 @@ function evalDirect(args, filename, blockIdCounter, externalPrefixNum, evalDirec
const state = {
currentBlock: undefined,
currentThisBlock: undefined,
currentSuperBlock: undefined
currentSuperBlock: undefined,
currentSuperIsProto: false
};
const tempVars = [];
let allowNewTarget = false;
Expand All @@ -134,9 +137,9 @@ function evalDirect(args, filename, blockIdCounter, externalPrefixNum, evalDirec
allowNewTarget = true;
} else if (varName === 'super') {
// Don't create binding - `super` binding is created lazily
// TODO: Also need to set `superIsProto` and create a new temp var for `super` target
// (the external var could be shadowed inside `eval()` if prefix num is changing)
state.currentSuperBlock = block;
state.currentSuperIsProto = superIsProto;
tempVars.push({value: tempVarValue, block, varName});
} else {
// Whether var is function is not relevant because it will always be in external scope
// of the functions it's being recorded on, and value of `isFunction` only has any effect
Expand Down
21 changes: 14 additions & 7 deletions lib/instrument/visitors/eval.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ function visitEval(node, parent, key, state) {
// Queue instrumentation on 2nd pass
state.secondPass(
instrumentEval, node, isEvalCall, parent, key, state.currentBlock, fn,
state.isStrict, canUseSuper, state
state.isStrict, canUseSuper, state.currentSuperIsProto, state
);
}

Expand All @@ -66,16 +66,19 @@ function visitEval(node, parent, key, state) {
* @param {Object} [fn] - Function object for function `eval` is in (`undefined` if not in a function)
* @param {boolean} isStrict - `true` if is strict mode
* @param {boolean} canUseSuper - `true` if `super()` can be used in `eval()`
* @param {boolean} superIsProto - `true` if `super` refers to class prototype
* @param {Object} state - State object
* @returns {undefined}
*/
function instrumentEval(node, isEvalCall, parent, key, block, fn, isStrict, canUseSuper, state) {
function instrumentEval(
node, isEvalCall, parent, key, block, fn, isStrict, canUseSuper, superIsProto, state
) {
// Check `eval` is global
if (!isGlobalEval(block)) return;

// Handle either `eval()` call or `eval` identifier
if (isEvalCall) {
instrumentEvalCall(parent, block, fn, isStrict, canUseSuper, state);
instrumentEvalCall(parent, block, fn, isStrict, canUseSuper, superIsProto, state);
} else {
instrumentEvalIdentifier(node, parent, key, state);
}
Expand All @@ -93,10 +96,11 @@ function instrumentEval(node, isEvalCall, parent, key, block, fn, isStrict, canU
* @param {Object} [fn] - Function object for function `eval` is in (`undefined` if not in a function)
* @param {boolean} isStrict - `true` if is strict mode
* @param {boolean} canUseSuper - `true` if `super()` can be used in `eval()`
* @param {boolean} superIsProto - `true` if `super` refers to class prototype
* @param {Object} state - State object
* @returns {undefined}
*/
function instrumentEvalCall(callNode, block, fn, isStrict, canUseSuper, state) {
function instrumentEvalCall(callNode, block, fn, isStrict, canUseSuper, superIsProto, state) {
// If no arguments, leave as is
const argNodes = callNode.arguments;
if (argNodes.length === 0) return;
Expand Down Expand Up @@ -138,6 +142,10 @@ function instrumentEvalCall(callNode, block, fn, isStrict, canUseSuper, state) {
while (varDefNodes.length !== 3) varDefNodes.push(null);
varDefNodes.push(t.arrayExpression(binding.argNames.map(argName => t.stringLiteral(argName))));
}
if (varName === 'super') {
while (varDefNodes.length !== 4) varDefNodes.push(null);
varDefNodes.push(binding.varNode);
}
varDefsNodes.push(t.arrayExpression(varDefNodes));

// If var is external to function, record function as using this var.
Expand Down Expand Up @@ -186,7 +194,8 @@ function instrumentEvalCall(callNode, block, fn, isStrict, canUseSuper, state) {
[tempVarNode], t.callExpression(t.identifier('eval'), [t.spreadElement(tempVarNode)])
),
t.arrayExpression(scopeNodes.reverse()),
t.booleanLiteral(isStrict)
t.booleanLiteral(isStrict),
t.booleanLiteral(canUseSuper && superIsProto)
);
}

Expand Down Expand Up @@ -254,8 +263,6 @@ function activateSuperIfIsUsable(fn, state) {
setSuperIsProtoOnFunctions(superBlock, fn, state);
}

// TODO: Also need to pass `superIsProto` and `superVarNode` to `evalDirect()`
// for it to set inside `eval()`
activateSuperBinding(superBlock, state);
return true;
}

0 comments on commit 02a0f66

Please sign in to comment.