Skip to content

Commit

Permalink
Create temp vars in eval [improve]
Browse files Browse the repository at this point in the history
  • Loading branch information
overlookmotel committed Nov 12, 2023
1 parent 25ac398 commit 281b1e9
Showing 1 changed file with 42 additions and 21 deletions.
63 changes: 42 additions & 21 deletions lib/init/eval.js
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ function evalIndirect(code, tracker, filename, blockIdCounter, externalPrefixNum
// Compile code with no external scope.
// Code returned is for a function which takes arguments `(livepack_tracker, livepack_getScopeId)`.
const {code: fnCode, shouldThrow, internalPrefixNum} = compile(
code, filename, blockIdCounter, externalPrefixNum, true, undefined, false
code, filename, blockIdCounter, externalPrefixNum, true, undefined, [], false
);

// If prefix num inside `eval` is different from outside, create new tracker
Expand Down Expand Up @@ -118,12 +118,13 @@ function evalDirect(args, filename, blockIdCounter, externalPrefixNum, evalDirec
currentThisBlock: undefined,
currentSuperBlock: undefined
};
const tempVars = [];
for (const [blockId, blockName, scopeId, ...varDefs] of scopeDefs) {
const block = createBlockWithId(blockId, blockName, true, state);
block.scopeIdVarNode = t.numericLiteral(scopeId);
state.currentBlock = block;

for (const [varName, isConst, isSilentConst, argNames] of varDefs) {
for (const [varName, isConst, isSilentConst, argNames, tempVarValue] of varDefs) {
if (varName === 'this') {
createThisBinding(block);
state.currentThisBlock = block;
Expand All @@ -137,29 +138,33 @@ function evalDirect(args, filename, blockIdCounter, externalPrefixNum, evalDirec
// 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
// for internal vars
createBindingWithoutNameCheck(
const binding = createBindingWithoutNameCheck(
block, varName, {isConst: !!isConst, isSilentConst: !!isSilentConst, argNames}
);

if (tempVarValue) tempVars.push({value: tempVarValue, binding});
}
}
}

// Compile to executable code with tracking code inserted.
// If var names prefix inside code has to be different from outside,
// code is wrapped in a function which injects tracker and getScopeId functions:
// code is wrapped in a function which injects tracker and getScopeId functions, and temp vars:
// `() => foo` -> `(livepack1_tracker, livepack1_getScopeId) => () => foo`
const {
code: codeInstrumented, shouldThrow, internalPrefixNum
} = compile(code, filename, blockIdCounter, externalPrefixNum, false, state, isStrict);
code: codeInstrumented, shouldThrow, internalPrefixNum, tempVars: tempVarsUsed
} = compile(code, filename, blockIdCounter, externalPrefixNum, false, state, tempVars, isStrict);

// Call `eval()` with amended code
let res = execEvalCode(execEvalSingleArg, codeInstrumented, shouldThrow, code, evalDirectLocal);

// If was wrapped in a function, create new tracker and inject tracker and `getScopeId` into function
if (internalPrefixNum !== externalPrefixNum) {
const tracker = createTracker(filename, blockIdCounter, internalPrefixNum);
res = res(tracker, getScopeId);
}
// If was wrapped in a function, create new tracker and inject tracker and `getScopeId`
// and/or any temp vars into function
const params = internalPrefixNum !== externalPrefixNum
? [createTracker(filename, blockIdCounter, internalPrefixNum), getScopeId]
: [];
if (tempVarsUsed.length > 0) params.push(...tempVars.map(tempVar => tempVar.value));
if (params.length > 0) res = res(...params);

return res;
}
Expand Down Expand Up @@ -207,14 +212,18 @@ function execEvalCode(exec, arg, shouldThrowSyntaxError, code, fn) {
* @param {number} externalPrefixNum - Internal vars prefix num outside `eval()`
* @param {boolean} isIndirectEval - `true` if is indirect eval
* @param {Object} [state] - State to initialize instrumentation state (only if direct `eval()` call)
* @param {Array<Object>} tempVars - Array of temp vars to be injected
* @param {boolean} isStrict - `true` if environment outside `eval()` is strict mode
* @returns {Object} - Object with properties:
* {string} .code - Instrumented code (or input code if parsing failed)
* {boolean} .shouldThrow - `true` if could not parse code, so calling `eval()` with this code
* should throw syntax error
* {number} .internalPrefixNum - Internal vars prefix num inside `eval()`
* {Array<Object>} .tempVars - Array of temp vars to be injected
*/
function compile(code, filename, blockIdCounter, externalPrefixNum, isIndirectEval, state, isStrict) {
function compile(
code, filename, blockIdCounter, externalPrefixNum, isIndirectEval, state, tempVars, isStrict
) {
// Parse code.
// If parsing fails, swallow error. Expression will be passed to `eval()`
// which should throw - this maintains native errors.
Expand All @@ -224,7 +233,7 @@ function compile(code, filename, blockIdCounter, externalPrefixNum, isIndirectEv
code, filename, false, false, !isIndirectEval, false, isStrict, false, undefined
).ast;
} catch (err) {
return {code, shouldThrow: true, internalPrefixNum: externalPrefixNum};
return {code, shouldThrow: true, internalPrefixNum: externalPrefixNum, tempVars};
}

// Instrument code.
Expand All @@ -249,10 +258,25 @@ function compile(code, filename, blockIdCounter, externalPrefixNum, isIndirectEv
// `123` => `(livepack_tracker, livepack_getScopeId) => eval('123')`.
// Wrapping in a 2nd `eval()` is required to ensure it returns its value.
const internalPrefixNum = state.internalVarsPrefixNum;
if (isIndirectEval || internalPrefixNum !== externalPrefixNum) {
ast = wrapInFunction(ast, internalPrefixNum);
const params = (isIndirectEval || internalPrefixNum !== externalPrefixNum)
? [
internalVarNode(TRACKER_VAR_NAME_BODY, internalPrefixNum),
internalVarNode(GET_SCOPE_ID_VAR_NAME_BODY, internalPrefixNum)
]
: [];

// Filter out any temp vars which aren't used in `eval`-ed code
if (tempVars.length > 0) {
tempVars = tempVars.filter((tempVar) => {
const {varNode} = tempVar.binding;
if (!varNode) return false;
params.push(varNode);
return true;
});
}

if (params.length > 0) ast = wrapInFunction(ast, params);

// Return instrumented code
code = generate(ast, {retainLines: true, compact: true}).code;

Expand All @@ -266,17 +290,14 @@ function compile(code, filename, blockIdCounter, externalPrefixNum, isIndirectEv
/* eslint-enable no-console */
}

return {code, shouldThrow: false, internalPrefixNum};
return {code, shouldThrow: false, internalPrefixNum, tempVars};
}

function wrapInFunction(ast, internalPrefixNum) {
function wrapInFunction(ast, params) {
return t.program([
t.expressionStatement(
t.arrowFunctionExpression(
[
internalVarNode(TRACKER_VAR_NAME_BODY, internalPrefixNum),
internalVarNode(GET_SCOPE_ID_VAR_NAME_BODY, internalPrefixNum)
],
params,
t.callExpression(
t.identifier('eval'), [
t.stringLiteral(generate(ast, {retainLines: true, compact: true}).code)
Expand Down

0 comments on commit 281b1e9

Please sign in to comment.