-
Notifications
You must be signed in to change notification settings - Fork 1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Instrumentation does not correctly distinguish direct and indirect eval
in some obscure cases
#137
Comments
Tricky case where a local const x = 123;
module.exports = (eval => (
() => {
'use strict';
return eval(x);
}
))(eval); In this case, substituting |
Here's an implementation which solves both this and #51: // lib/init/eval.js
const nativeEval = eval;
const evalShim = {
eval(str) {
return nativeEval(preval(str));
}
}.eval;
function shimGlobal() {
global.eval = evalShim;
}
shimGlobal();
function preval(str, scopes, filename) {
return str; // Actually would do babel-transform on string
}
module.exports = {
shim(val) {
return val === nativeEval ? evalShim : val;
},
unshim(val) {
return val === evalShim ? nativeEval : val;
},
shimGlobal,
exec(localEval, exec, args, scopes, filename) {
// If `eval` where it's used is not global eval, execute it as a normal function
const isShimmedEval = localEval === evalShim;
if ( !isShimmedEval && localEval !== nativeEval ) return localEval(...args);
// It's direct eval - process the evaluated expression before executing `eval()`
let str = preval( args[0], scopes, filename );
if (isShimmedEval) {
// `eval` where it's being executed refers to global, which is shimmed.
// Temporarily restore `global.eval`.
global.eval = nativeEval;
str = `livepack_eval.shimGlobal();${str}`;
}
try {
return exec(str);
} catch (err) {
// Fallback in case eval fails with syntax error and `livepack_eval.shimGlobal()`
// inside eval expression is not executed
if (isShimmedEval) shimGlobal();
throw err;
}
}
}; Then this input: // src/index.js
const Object = 123;
const fn1 = ( eval => () => {
'use strict';
const indirectEval = eval;
return [
eval('Object'),
indirectEval('Object')
];
} )(eval);
const fn2 = (() => {
const { eval } = { eval: global.eval };
return () => {
'use strict';
return [
eval('Object'),
(0, eval)('Object')
];
};
})();
const fn3 = () => [
eval('Object'),
(0, eval)('Object')
];
module.exports = [ fn1, fn2, fn3 ]; is transformed by Babel plugin to: const livepack_eval = require('/path/to/app/node_modules/livepack/lib/init/eval.js'),
livepack_filename = '/path/to/app/src/index.js';
const livepack_scopeId_1 = 1;
const Object = 123;
const fn1 = ( (eval) => {
eval = livepack_eval.unshim(eval);
return () => {
'use strict';
const indirectEval = livepack_eval.shim(eval);
return [
livepack_eval.exec(
eval,
str => eval(str),
['Object'],
[ [ 1, livepack_scopeId_1, , [ 'Object', 'module', 'exports' ] ] ],
livepack_filename
),
indirectEval('Object')
];
};
} )(eval);
const fn2 = ( () => {
const {eval: livepack_temp_1} = {eval: global.eval};
const eval = livepack_eval.unshim(livepack_temp_1);
return () => {
'use strict';
return [
livepack_eval.exec(
eval,
str => eval(str),
['Object'],
[ [ 1, livepack_scopeId_1, , [ 'Object', 'module', 'exports' ] ] ],
livepack_filename
),
( 0, livepack_eval.shim(eval) )('Object')
];
};
} )();
const fn3 = () => [
livepack_eval.exec(
eval,
str => eval(str),
['Object'],
[ [ 1, livepack_scopeId_1, , [ 'Object', 'module', 'exports' ] ] ],
livepack_filename
),
(0, eval)('Object')
];
module.exports = [ fn1, fn2, fn3 ]; Notes:
Tricky part in function serialization is restoring the original object destructuring from The above implementation does not deal with passing Livepack's internal vars into scope of eval-ed expression. The intent of passing |
Prepending e.g. if evaluated string is Evaluating this string with It's not as simple as checking if string begins with Solution: Insert NB Another tempting solution is to prepend |
Once this is implemented, enable the tests in: Line 823 in 55bf630
|
eval
in some obscure caseseval
in some obscure cases
The above would not work if user code prevents modification of It could be shimmed with a getter/setter instead: let globalEval = eval,
shimmedEval;
Object.defineProperty(global, 'eval', {
get() { return shimmedEval || globalEval; },
set(v) { globalEval = v; }
});
|
Solution in comment above can be simplified a bit: Rather than shimming and unshimming local vars called
The example above would be instrumented as: const Object = 123;
const fn1 = ( livepack_localEval => () => {
'use strict';
const indirectEval = livepack_localEval;
return [
livepack_tracker.evalDirect(
'Object', livepack_localEval, livepack_temp1 => eval(livepack_temp1), [ /* ... */ ]
),
indirectEval('Object')
];
} )(eval);
const fn2 = (() => {
const { eval: livepack_localEval } = { eval: global.eval };
return () => {
'use strict';
return [
livepack_tracker.evalDirect(
'Object', livepack_localEval, livepack_temp2 => eval(livepack_temp2), [ /* ... */ ]
),
(0, livepack_localEval)('Object')
];
};
})();
const fn3 = () => [
livepack_tracker.evalDirect(
'Object', eval, livepack_temp3 => eval(livepack_temp3), [ /* ... */ ]
),
(0, eval)('Object')
];
module.exports = [ fn1, fn2, fn3 ]; Handling references to local
|
Here's a solution which also solves #51 and #457 (comment). Direct (livepack_temp1 = livepack_tracker.evalDirect(eval, [x], [ /* scopes */ ]))[0]
? eval(livepack_temp1[1])
: livepack_temp1[1] Set-up code: const nativeEval = eval;
const evalShim = {
eval(code) {
if (typeof code === 'string') code = instrumentCode(code);
return nativeEval(code);
}
}.eval;
let useNative = false;
Object.defineProperty( global, 'eval', {
get() {
if (!useNative) return evalShim;
useNative = false;
return nativeEval;
}
} );
livepack_tracker.evalDirect = ( localEval, values, scopes ) => {
if ( localEval !== evalShim || typeof values[0] !== 'string' ) {
return [ false, localEval(...values) ];
}
useNative = true;
return [ true, instrumentCode(values[0], scopes) ];
};
This also maintains the original order of operations - The example above would be instrumented as: const Object = 123;
const fn1 = ( livepack_evalLocal => () => {
'use strict';
let livepack_temp1, livepack_temp2;
const indirectEval = livepack_evalLocal;
return [
(livepack_temp1 = livepack_tracker.evalDirect(
livepack_evalLocal, ['Object'], [ /* scopes */ ]
))[0] ? eval(livepack_temp1[1]) : livepack_temp1[1]
(livepack_temp2 = livepack_tracker.evalDirect(
indirectEval, ['Object'], [ /* scopes */ ]
))[0] ? eval(livepack_temp2[1]) : livepack_temp2[1]
];
} )(eval);
const fn2 = (() => {
const { eval: livepack_evalLocal } = { eval: global.eval };
return () => {
'use strict';
let livepack_temp3;
return [
(livepack_temp3 = livepack_tracker.evalDirect(
livepack_evalLocal, ['Object'], [ /* scopes */ ]
))[0] ? eval(livepack_temp3[1]) : livepack_temp3[1],
(0, livepack_evalLocal)('Object')
];
};
})();
const fn3 = () => {
let livepack_temp4;
return [
(livepack_temp4 = livepack_tracker.evalDirect(
eval, ['Object'], [ /* scopes */ ]
))[0] ? eval(livepack_temp4[1]) : livepack_temp4[1],
(0, eval)('Object')
];
};
module.exports = [ fn1, fn2, fn3 ]; |
Mostly implemented now on instrument-eval branch. Remaining work:
|
Implementation on instrument-eval branch has become impractically convoluted.
The difficulty is this can be interfered with by a local var in source called const {eval} = global;
eval('foo'); The solution I've implemented is that all local vars called This produces a lot of complications, because function declarations called #485 might be a better solution. Or could try creating a setter for the local var so its value can be "switched" to native That would be tricky where the local var is a
|
In both these cases, Babel plugin will incorrectly interpret
eval()
as indirect, aseval
where it's used is a reference to a local variable.NB Both cases are only possible in sloppy mode. It is not legal in strict mode to define a variable called
eval
.More background here: http://perfectionkills.com/global-eval-what-are-the-options/
Only way to get it correct in these cases is to check if
eval
var is global eval at time of execution.Could also be condensed to:
livepack_evalExec
implementation:Low priority. These cases are pretty obscure.
The text was updated successfully, but these errors were encountered: