Reputation: 459
is there any way I can execute eval() on a specific scope (but NOT global)?
for example, the following code doesn't work (a is undefined on the second statement) because they are on different scope:
eval(var a = 1);
eval(alert(a));
If possible, I would like to create a scope on the fly. for example (the syntax is definitely wrong, but just to illustrate the idea)
var scope1;
var scope2;
with scope1{
eval(var a = 1); eval(alert(a)); // this will alert 1
}
with scope2{
eval(var a = 1); eval(a++); eval(alert(a)); // this will alert 2
}
with scope1{
eval(a += 2); eval(alert(a)); // this will alert 3 because a is already defined in scope1
}
Any idea on how to achieve something like this? Thanks!
Upvotes: 39
Views: 33899
Reputation: 1
var a; eval('a = 1'); eval('alert(a)');
interesting, you must understand: "eval" is run inside an anonymous function, therefore the "var" declares "a" as a variable that is unreachable. If you delete the "var", then you must declare "a" in the local calling function. If "var a" is defined in the calling function then the local variable is used, otherwise "a" is undefined. However if eval is called from a class function then an undeclared variable is assigned to "window".
function A() { eval('a=1'); eval('alert(a)'); }
A() ; // (output is undefined)
new A(); // (output window.a = 1)
function B() { var a; eval('a=1'); eval('alert(a)'); }
B() // (local variable, output 1)
function C { window.eval('a=1'); eval('alert(a)'); }
C() ; // (output window.a = 1)
new C(); // (output window.a = 1)
The value of "this" of eval is "window" if called from a orphan function. However if called from a class function the "this" is the class's "this".
A = test(); alert(window.a);
B = new test(); alert(B.a);
function test() { eval(this.a = 1); }
The following overrides the "this" to "window" in a call function. However the call, alter and bind functions of eval work, but they do not change the "this" value;
C = new wtest(); alert(window.a);
function wtest() { window.eval(this.a = 2) }
Upvotes: -1
Reputation: 1215
be careful; it seems some instances will still get access to outside variables. the code below should fail, but if you run it, you can see it works
let exp = 'a+b';
let a = 1;
let b = 2;
function scopeEval(scope, script) {
return Function('"use strict";return (' + script + ')').bind(scope)();
}
console.log(scopeEval({}, exp));
Upvotes: 1
Reputation: 2660
script evaluation that provides script-scope, app-level-scope and app-level-scripts-this-object. it is safe and not leaking to app
it also provides a console proxy to redirect console output, it is alive in script-scope, so the script call console.log
would go to the proxy.
// app level scope, to share additional function and variable that can be used without `this` keyword.
const scope = {};
// app level this obj for all scripts, use this to reference it;
// so we could share data between scripts.
const thisObj = {};
/**
* execute scripts
* @param thisObj 'this' is an app level obj to share data between scripts
* @param scriptsScope app level scope to share some global predefined functions.
* @param localScope we can setup a 'console' proxy is in this scope.
* @param script code to execute.
*/
const scopedEval = function scopedEval(thisObj, scriptsScope, localScope, script) {
const context = { ...scriptsScope, ...localScope };
// create new Function with keys from context as parameters, 'script' is the last parameter.
const evaluator = Function.apply(null, [...Object.keys(context), 'script',
`"use strict";
try{${script}}
catch (e) {
console.error(e);
}`]);
// call the function with values from context and 'script' as arguments.
evaluator.apply(thisObj, [...Object.values(context), script]);
}
/**
* create a proxy for console. that will also write to the consoleElement
* @param consoleElement the ui element to show the console logs, i.e. a div.
* @returns {Proxy}
*/
const consoleProxy = consoleElement => new Proxy(console, {
get: function (target, propKey) {
const originalMethod = target[propKey];
return function (...args) {
// get time with milliseconds
const now = new Date();
const time = now.getHours().toString().padStart(2,'0') + ':' + now.getMinutes().toString().padStart(2,'0') + ':' + now.getSeconds().toString().padStart(2,'0') + '.' + now.getMilliseconds().toString().padStart(3,'0');
// text to show
const text = document.createTextNode(`${time}: ${args.join(' ')}\n`);
const span = document.createElement('span');
span.appendChild(text);
if (propKey === 'error') {
span.style.color = 'red';
} else if (propKey === 'warn') {
span.style.color = 'orange';
} else if (propKey === 'info') {
span.style.color = 'blue';
} else if (propKey === 'debug') {
span.style.color = 'gray';
}
consoleElement.appendChild(span);
// original console logs, if you need
//originalMethod.apply(target, args);
}
}
});
const codes = document.querySelector('.codes');
const script1 = codes.querySelector('.script1').textContent;
const script2 = codes.querySelector('.script2').textContent;
const scriptConsole1 = codes.querySelector('.script-console1');
const scriptConsole2 = codes.querySelector('.script-console2');
scopedEval(thisObj, scope, { console: consoleProxy(scriptConsole1) }, script1);
scopedEval(thisObj, scope, { console: consoleProxy(scriptConsole2) }, script2);
.code {
background: lightgray;
}
<div class="codes">
<div>
<pre class="code">
<code class="script1">
console.log('hello');
this.a = 1
// error
b++
</code>
</pre>
<pre class="script-console1">
</pre>
<div>
<div>
<pre class="code">
<code class="script2">
this.a++
console.log(this.a);
</code>
</pre>
<pre class="script-console2">
</pre>
</div>
<div>
Upvotes: -2
Reputation: 592
I'm not sure how much this adds, but I thought I'd post my version of the Function
constructor based solution with modern syntactic sugar and what I think is a good solution to avoid polluting the scope with the internal one containing the evaluated text. (By sacrificing the this
context, on which properties can be delete
d even inside use strict;
code)
class ScopedEval {
/** @param {Record<string,unknown>} scope */
constructor(scope) {
this.scope = scope;
}
eval(__script) {
return new Function(...Object.keys(this.scope),`
return eval(
'"use strict";delete this.__script;'
+ this.__script
);
`.replace(/[\n\t]| +/g,'')
).bind({__script})(...Object.values(this.scope));
}
}
Personally I prefer being able to separate when I add or adjust the scope and when I eval some code, so this could be used like:
const context = {
hi: 12,
x: () => 'this is x',
get xGet() {
return 'this is the xGet getter'
}
};
const x = new ScopedEval(context)
console.log(x.eval('"hi = " + hi'));
console.log(x.eval(`
let k = x();
"x() returns " + k
`));
console.log(x.eval('xGet'));
x.scope.someId = 42;
console.log(x.eval('(() => someId)()'))
Upvotes: 1
Reputation: 6508
Function
const evaluate = (context, expr) =>
Function(Object.keys(context).join(','), `return ${expr}`)
(...Object.values(context));
Usage
const result = evaluate({a: 1, b: 2, c: {d: 3}}, "a+b+c.d"); // 6
Example
const evaluate = (context, expr) => Function(Object.keys(context).join(','), `return ${expr}`)(...Object.values(context));
const result = evaluate({a: 1, b: 2, c: {d: 3}}, "a+b+c.d");
console.log('result is: ', result);
Upvotes: -1
Reputation: 321
The approach here was to allow a context object to parameterize the evaluation of the expression.
First a function is created using the Function() constructor that accepts every key of the context as well as the expression to evaluate; the body returns the evaluated expression. Then that function is called with all of the values of the context and the expression to evaluate.
function scopedEval(context, expr) {
const evaluator = Function.apply(null, [...Object.keys(context), 'expr', "return eval('expr = undefined;' + expr)"]);
return evaluator.apply(null, [...Object.values(context), expr]);
}
// Usage
const context = {a: 1, b: 2, c: {d: 3}};
scopedEval(context, "a+b+c.d"); // 6
By using Function.prototype.apply the number of arguments and the names don't need to be known beforehand. Because the arguments are scoped to the evaluator
function they are directly accessible from the expression (instead of requiring this.a
).
Upvotes: 5
Reputation: 475
This is the simplest way I found to do that, but it doesn't use eval.
function execInContext(code, context)
{
return Function(...Object.keys(context), 'return '+ code (...Object.values(context));
}
Here we create a Function
object. The Function
constructor accepts an array of parameters, the last one is the code to be executed by the function and all others are argument names for the function. What we're doing is creating a function that has arguments with the same names as the fields in the context
object and then calling this function with the values of the fields in context
. So if you call
execInContext('myVar', {myVar: 'hi!'});
it's the same as
((myVar) => { return myVar; })('hi!');
and the result will be hi!
Upvotes: -1
Reputation: 22304
This worked for me the best:
const scopedEval = (scope, script) => Function(`"use strict"; ${script}`).bind(scope)();
Usage:
scopedEval({a:1,b:2},"return this.a+this.b")
Upvotes: 22
Reputation: 6691
The poor man's method:
If your scope is not too dynamic, just a couple of static and read-only declarations, simply put it in a string and concatenate with the string what you wanna execute like this:
const scopeAll = `
const myFunc = (a, b) => a + b + s;
`
const scope1 = `
${scopeAll}
const s = 'c';
`
const scope2 = `
${scopeAll}
const s = 'd';
`
const myStringToExecute = `
myFunc('a', 'b')
`
console.log(eval(scope1 + myStringToExecute));
console.log(eval(scope2 + myStringToExecute));
Upvotes: 0
Reputation: 518
Simple as pie.
// Courtesy of Hypersoft-Systems: U.-S.-A.
function scopeEval(scope, script) {
return Function('"use strict";return (' + script + ')').bind(scope)();
}
scopeEval(document, 'alert(this)');
Upvotes: 10
Reputation: 975
Here is a 20-line or so JS class that implements an extensible context using eval in a lexical scope:
// Scope class
// aScope.eval(str) -- eval a string within the scope
// aScope.newNames(name...) - adds vars to the scope
function Scope() {
"use strict";
this.names = [];
this.eval = function(s) {
return eval(s);
};
}
Scope.prototype.newNames = function() {
"use strict";
var names = [].slice.call(arguments);
var newNames = names.filter((x)=> !this.names.includes(x));
if (newNames.length) {
var i, len;
var totalNames = newNames.concat(this.names);
var code = "(function() {\n";
for (i = 0, len = newNames.length; i < len; i++) {
code += 'var ' + newNames[i] + ' = null;\n';
}
code += 'return function(str) {return eval(str)};\n})()';
this.eval = this.eval(code);
this.names = totalNames;
}
}
// LOGGING FOR EXAMPLE RUN
function log(s, eval, expr) {
s = '<span class="remark">' + String(s);
if (expr) {
s += ':\n<b>' + expr + '</b> --> ';
}
s += '</span>';
if (expr) {
try {
s += '<span class="result">' + JSON.stringify(eval(expr)) + '</span>';
} catch (err) {
s += '<span class="error">' + err.message + '</span>';
}
}
document.body.innerHTML += s + '\n\n';
}
document.body.innerHTML = '';
// EXAMPLE RUN
var scope = new Scope();
log("Evaluating a var statement doesn't change the scope but newNames does (should return undefined)", scope.eval, 'var x = 4')
log("X in the scope object should raise 'x not defined' error", scope.eval, 'x');
log("X in the global scope should raise 'x not defined' error", eval, 'x');
log("Adding X and Y to the scope object");
scope.newNames('x', 'y');
log("Assigning x and y", scope.eval, 'x = 3; y = 4');
log("X in the global scope should still raise 'x not defined' error", eval, 'x');
log("X + Y in the scope object should be 7", scope.eval, 'x + y');
log("X + Y in the global scope should raise 'x not defined' error", eval, 'x + y');
.remark {
font-style: italic;
}
.result, .error {
font-weight: bold;
}
.error {
color: red;
}
<body style='white-space: pre'></body>
Upvotes: 3
Reputation: 498
Create the variables you want to exist in your scope as local variables in a function. Then, from that function, return a locally-defined function that has a single argument and calls eval
on it. That instance of eval
will use the scope of its containing function, which is nested inside the scope of your top level function. Each invocation of the top level function creates a new scope with a new instance of the eval function. To keep everything dynamic, you can even use a call to eval
in the top level function to declare the variables that will be local to that scope.
Example code:
function makeEvalContext (declarations)
{
eval(declarations);
return function (str) { eval(str); }
}
eval1 = makeEvalContext ("var x;");
eval2 = makeEvalContext ("var x;");
eval1("x = 'first context';");
eval2("x = 'second context';");
eval1("window.alert(x);");
eval2("window.alert(x);");
https://jsfiddle.net/zgs73ret/
Upvotes: 15
Reputation: 112827
You can look into the vm-browserify project, which would be used in conjunction with browserify.
It works by creating <iframe>
s, and eval
ing the code in that <iframe>
. The code is actually pretty simple, so you could adapt the basic idea for your own purposes if you don't want to use the library itself.
Upvotes: 3
Reputation: 119837
you can use the "use strict" to contain the eval'ed code within the eval itself.
Second,
eval
of strict mode code does not introduce new variables into the surrounding scope. In normal codeeval("var x;")
introduces a variablex
into the surrounding function or the global scope. This means that, in general, in a function containing a call toeval
every name not referring to an argument or local variable must be mapped to a particular definition at runtime (because thateval
might have introduced a new variable that would hide the outer variable). In strict modeeval
creates variables only for the code being evaluated, so eval can't affect whether a name refers to an outer variable or some local variable
var x = 17; //a local variable
var evalX = eval("'use strict'; var x = 42; x"); //eval an x internally
assert(x === 17); //x is still 17 here
assert(evalX === 42); //evalX takes 42 from eval'ed x
If a function is declared with "use strict", everything in it will be executed in strict mode. the following will do the same as above:
function foo(){
"use strict";
var x = 17;
var evalX = eval("var x = 42; x");
assert(x === 17);
assert(evalX === 42);
}
Upvotes: 23