global.eval is not able to visit variables in the

2019-03-02 21:43发布

问题:

I have a JavaScript file, e.js

var global = Function('return this')();

var i = 1;

console.log(eval("100-1"));
console.log(eval("i"));

console.log(global.eval("100-1"));
console.log(global.eval("i"));

When I execute it by V8:

$ node e.js
99
1
99

undefined:1
i
^
ReferenceError: i is not defined
    at eval (eval at <anonymous> (/private/tmp/xxxx/e.js:8:20), <anonymous>:1:1)
    at eval (native)
    at Object.<anonymous> (/private/tmp/xxxx/e.js:8:20)
    at Module._compile (module.js:456:26)
    at Object.Module._extensions..js (module.js:474:10)
    at Module.load (module.js:356:32)
    at Function.Module._load (module.js:312:12)
    at Function.Module.runMain (module.js:497:10)
    at startup (node.js:119:16)
    at node.js:902:3

As a result, global.eval works for math operator, but it is unable to visit the variable i, while eval works for both cases.

Is this behavior a limitation of V8? Or is it the expected behavior according to ECMAScript standard?

回答1:

Yes, this is spec-compliant behavior. ES5 15.1.2.1.1, Direct Call to Eval, says that one requirement for a call to eval to be "direct" is that the reference to eval "has an environment record as its base value." This means it cannot be a reference done by property access (in which case the owning object would the base value); it must be a "bare" function.

This distinction is critical to step 1 of 10.4.2, Entering Eval Code:

  1. If there is no calling context or if the eval code is not being evaluated by a direct call (15.1.2.1.1) to the eval function then,
    • a. Initialise the execution context as if it was a global execution context using the eval code as C as described in 10.4.1.1.

Thus, an indirect call to eval is given a global variable environment, not the local variable environment. Only direct calls get access to the local environment.

This is done for practical implementation reasons, because eval can signal to garbage collectors a need to avoid cleaning up any variables. For example, here's a case without eval:

function foo() {
    var a = 5, b = 6, c = 7;
    return function() { return a; }
}
var func = foo();
alert(func());

The function returned by foo might access a after foo terminates, but we can be sure b and c will never be accessed ever again after foo terminates. b and c can be safely garbage collected, while a remains uncollected.

Now a case with eval:

function foo() {
    var a = 5, b = 6, c = 7;
    return function(exp) { return eval(exp); }
}
var func = foo();
alert(func("b"));

It's impossible to generally decide if the eval expression exp will refer to a given variable, so the garbage collector must never collect any of the variables so they are still available for use by the returned function.

In order to decide that eval is in use, the parser must be able to reliably recognize a call to eval. If the eval is presented in an indirect way, like global["e"+"va"+"l!"[0]], the spec says that that evaled code doesn't get access to any local variables, thereby avoiding the garbage collection problem.