2

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?

Yang Bo
  • 3,586
  • 3
  • 22
  • 35
  • This is spec-compliant behavior; I will try to find a duplicate. See [ES5 15.1.2.1.1 Direct Call to Eval](http://www.ecma-international.org/ecma-262/5.1/#sec-15.1.2.1.1) and step 1 of [10.4.2 Entering Eval Code](http://www.ecma-international.org/ecma-262/5.1/#sec-10.4.2) – apsillers Jul 16 '15 at 16:25

1 Answers1

2

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.

apsillers
  • 112,806
  • 17
  • 235
  • 239
  • But why the same code works on Google Chrome's JavaScript console, while it does not work on Node.js? – Yang Bo Jul 16 '15 at 17:57
  • 1
    @user955091 It's because `i` is ***not*** a locally-scoped variable in Chrome. Node probably wraps your code in a `(function() { ... })()`. If you wrap it in Google Chrome, you'll see the final call also generates an error. – apsillers Jul 16 '15 at 18:14
  • What if `eval` is referenced by a local variable of the same name? – Melab Nov 29 '21 at 05:54
  • @Melab That will also work. A local variable also uses an "environment record as its base value" same as a global variable: the global environment record is simply an environment record that has no parent record. You could even use `with({ eval: global.eval }) { eval(...); }` since variables created inside `with` scopes also use environment records. – apsillers Nov 29 '21 at 12:43