You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
The binding for var x is therefore created in the arrow function's scope, not global scope.
Possible solutions
The simple solution for #457 is not available here. Indirect eval is executed in global scope so only avenue for passing in Livepack's internal vars is wrapping in function (current solution which doesn't work) or via properties on global.
1. Global var
1.1. Static global var
At define a property on global as a function which returns livepack_tracker and livepack_getScopeId. That function can be a closure and the livepack_tracker instance for the file can be provided to the closure just before eval code is executed.
Property can have an obscure name like $$__livepack_getEvalVars so it's unlikely to clash with a property used by user code.
Property can be defined in livepack/init, so before any user code runs (in case user calls Object.freeze(global)).
Property key could also be a global symbol global[Symbol.for('$$__livepack_getEvalVars')] which makes it even more obscure.
This is pretty simple to implement, but has disadvantages:
Property name could (in unlikely case) clash with a var used by user code.
Property is visible to user via Object.getOwnPropertyDescriptors(global)
1.2. Dynamic global var
Prior to running eval code, define a getter property on global which removes itself as soon as it's accessed, so it's invisible to all user code.
A safe name for that property can be chosen after parsing the code to be executed, avoiding any vars used in it, and also avoiding any existing properties on global.
This solves the problems of above solution. Disadvantage:
Will not work if user code has called Object.preventExtensions(global).
1.3. Proxy on global
global itself cannot be converted into a proxy, but its prototype can.
injectTempGlobal() would be called just before executing the eval code. It creates a global var which disappears as soon as it's accessed. resetTempGlobal() can be used to delete the global var manually (if eval() call fails with a syntax error).
Object.setPrototypeOf, Reflect.setPrototypeOf and Object.prototype.__proto__ setter could also be shimmed to defeat user setting the prototype of global.
This approach will address all above problems, but is very complicated.
It still could be defeated by user if they use vm module to get access to an unshimmed Object.setPrototypeOf, but that'd be pretty bizarre.
Putting a Proxy in the prototype chain of global could be bad for performance, and this setup code needs to be run before any user code executes, regardless of whether that code uses eval() anywhere or not. It could be a high price to pay to handle an uncommon case.
#137 (comment) has the beginnings of a solution, I think.
global.eval is going to be a getter which dynamically alters what it returns. This mechanism could be re-used to smuggle livepack_tracker etc into the eval-ed code as a property of global.eval e.g. global.eval.internalVars.
Problem
In original code, assertion passes. In instrumented code, it fails.
Cause
This problem is similar to #457 (direct
eval()
).Cause is that Livepack's instrumentation wraps the
eval
code in an arrow function to inject Livepack's internal vars.The binding for
var x
is therefore created in the arrow function's scope, not global scope.Possible solutions
The simple solution for #457 is not available here. Indirect
eval
is executed in global scope so only avenue for passing in Livepack's internal vars is wrapping in function (current solution which doesn't work) or via properties onglobal
.1. Global var
1.1. Static global var
At define a property on
global
as a function which returnslivepack_tracker
andlivepack_getScopeId
. That function can be a closure and thelivepack_tracker
instance for the file can be provided to the closure just beforeeval
code is executed.Property can have an obscure name like
$$__livepack_getEvalVars
so it's unlikely to clash with a property used by user code.Property can be defined in
livepack/init
, so before any user code runs (in case user callsObject.freeze(global)
).Property key could also be a global symbol
global[Symbol.for('$$__livepack_getEvalVars')]
which makes it even more obscure.This is pretty simple to implement, but has disadvantages:
Object.getOwnPropertyDescriptors(global)
1.2. Dynamic global var
Prior to running
eval
code, define a getter property onglobal
which removes itself as soon as it's accessed, so it's invisible to all user code.A safe name for that property can be chosen after parsing the code to be executed, avoiding any vars used in it, and also avoiding any existing properties on
global
.This solves the problems of above solution. Disadvantage:
Object.preventExtensions(global)
.1.3. Proxy on global
global
itself cannot be converted into a proxy, but its prototype can.injectTempGlobal()
would be called just before executing theeval
code. It creates a global var which disappears as soon as it's accessed.resetTempGlobal()
can be used to delete the global var manually (ifeval()
call fails with a syntax error).Object.setPrototypeOf
,Reflect.setPrototypeOf
andObject.prototype.__proto__
setter could also be shimmed to defeat user setting the prototype ofglobal
.This approach will address all above problems, but is very complicated.
It still could be defeated by user if they use
vm
module to get access to an unshimmedObject.setPrototypeOf
, but that'd be pretty bizarre.Putting a Proxy in the prototype chain of
global
could be bad for performance, and this setup code needs to be run before any user code executes, regardless of whether that code useseval()
anywhere or not. It could be a high price to pay to handle an uncommon case.2. Use VM
Use
vm
module.Run eval code with:
Something of this sort should work, but:
3. Transform eval-ed code
Transform the eval-ed code so that it can be wrapped in a function but still maintain correct behavior.
Top-level
var
and function declarations can be identified during instrumentation and moved outside of the wrapper function.var x = 1; var y = 2;
would become:function f() {}
would become:It gets much more complicated with functions in nested blocks which are hoisted e.g.
if (q) { function f() {} }
.Assuming can get the implementation right, this has advantages:
However, implementation could be quite fiendish to handle all the edge cases with hoisted function declarations.
The text was updated successfully, but these errors were encountered: