-
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
Contain eval()
in a cage in instrumentation
#485
Comments
Actually, there is one case I'm not sure how to handle. function f() {
eval('eval("var x = 123")');
return x;
} The inner Argh! Maybe could solve by adding a // Instrumented
function f() {
const livepack_dynamicVars = Object.create(null);
with (livepack_dynamicVars) {
livepack_tracker.evalDirect(
'eval("var x = 123")', ..., livepack_dynamicVars
);
return x;
}
} Any NB |
Yes, actually above idea might be quite good. Source (original example): const x = 123;
function f(y) {
return eval('() => [x, y += 100, this, arguments]');
}
module.exports = f(); Instrumented: const x = 123;
function f(y) {
const livepack_dynamicVars = Object.create(null),
livepack_dynamicVarsEvalOnly = Object.create(null);
with (livepack_dynamicVars) {
return livepack_tracker.evalDirect(
[ '() => [x, y += 100, this, arguments]' ],
eval,
{
module: [ () => module, v => module = v ],
exports: [ () => exports, v => exports = v ],
require: [ () => require, v => require = v ],
x: () => x,
f: [ () => f, v => f = v ],
y: [ () => y, v => y = v ],
this: () => this,
arguments: [ () => arguments, v => arguments = v ],
'new.target': () => new.target
},
livepack_dynamicVars,
livepack_dynamicVarsEvalOnly
);
}
}
module.exports = f(); There are no direct
This could even handle vars created with names which clash with Livepack's internal vars in outer scope e.g. Would also integrate nicely with support for |
In addition to |
Function declarations also behave the same as (0, eval)('function a() {}');
assert(typeof global.a === 'function');
(0, eval)('{ function b() {} }');
assert(typeof global.b === 'function');
(() => {
eval('function c() {}');
assert(typeof c === 'function');
assert(typeof global.c === 'undefined');
eval('{ function d() {} }');
assert(typeof d === 'function');
assert(typeof global.d === 'undefined');
})(); |
For indirect eval, need to wrap eval-ed code in a function to inject Livepack's internal vars and then execute the eval-ed code within a direct If eval-ed code is: (livepack_tracker, livepack_getScopeId, livepack_temp1, livepack_temp2) => {
with (livepack_temp1) {
return eval(`
livepack_temp2('var x');
x = 123;
456
`);
}
} Function would be called with: const nativeEval = global.eval; // Before it's shimmed
const withObj = {
__proto__: null,
get eval() {
delete this.eval;
Object.freeze(this);
return nativeEval;
}
};
f(tracker, getScopeId, withObj, nativeEval); The
|
Just realised, the "cage" approach will prevent |
There are many problems and annoying edge cases with
eval()
. Solving them all in output code is very tricky (#278), but should at least be possible to simplify handling it in instrumentation.I made a good attempt to solve it in #137 (comment) but the implementation has become tortuously complex.
To prevent
eval()
's effects leaking outside of it, and requiring workarounds all over the instrumentation code, it'd be better to try to contain its effects by running it in a global scope "cage" and injecting all external variables into that environment.e.g. input:
Instrumented:
evalDirect()
would instrument the theeval
-ed code as:evalDirect()
would return an object with.eval
and.exec
properties..eval()
is called first to create any bindings in outer scope due tovar
statements or sloppy function declarations in theeval
-ed code (these can be determined statically)..exec()
is called 2nd._get
and_set
.(0, eval)(code)
)._get
and_set
use the 2rd argument toevalDirect()
to execute get/set operations in the surrounding context._get
and_set
use the 3rd + 4th arguments if the code insideeval
itself gets or sets an external vareval
._get
and_set
can remove themselves from stack traces where there's an error.Previous attempts to solve the problems of
eval()
have aimed to make minimal changes to theeval
-ed code and use cunning wheezes to inject vars withwith (...) {}
, and other ploys to injectthis
,arguments
,new.target
andsuper
.The above suggestion takes a different approach. The changes to the
eval
-ed code are very invasive. However, as both the code inside and outside theeval()
is statically analysed, we can know which variables theeval
code accesses, so it's possible to translate it in full.e.g.:
_set()
can be passed a third argument for whether the position where_set()
is called is strict or sloppy mode, so it can throw an error or fail silently accordingly if the assignment cannot succeed.var
statements in sloppy mode create bindings in the outer function. This can be determined statically and executed.delete x
can also be handled.Despite the invasiveness, I actually think it's simpler overall than other attempted solutions, because all the actions are confined to within
eval()
itself.The text was updated successfully, but these errors were encountered: