Indirect eval call in strict mode

I understand about how eval() works in non-strict contexts, however the case of using eval() in strict mode has completely befuddled me. When eval() is called directly in the global scope, variables are kept inside the new eval() scope:

'use strict';
eval('var a = 1;');
console.log(a); // ReferenceError: a is not defined

However, if I perform an indirect call to eval() in the global scope (should be the same thing, right?), it acts as though it is not in strict mode (if you don't believe me, see this JSFiddle):

'use strict';
(0, eval)('var a = 1;'); // indirect call to eval
console.log(a); // 1???

For an explanation of what (0, eval) does: see Why does google main page use (0, obj.func)(args) syntax?.

At least according to my understanding of how eval() is supposed to work in strict mode, it is meant to (no matter whether eval() is called directly or indirectly) create a new scope for variables defined in the eval() call, however this doesn't seem to be the case here. (ECMA-262 spec 5th ed 10.4.2)

This is the case in all major browsers (including Internet Explorer 10, Chrome 30 and Firefox 24) so I don't think it's a bug. Aren't they both meant to do the same thing, and if not, why is this the case?

Note: yes, I know the "dangers" of using eval() - I simply want to understand the logic behind this :)


Solution 1:

tl;dr

The second (0, eval)('var a = 1;'); case is in fact not a direct call.

You can see this more prevalently in:

(function(){ "use strict"
    var x = eval;
    x("var y = 10"); // look at me all indirect
    window.y;// 10
    eval("var y = 11");
    window.y;// still 10, direct call in strict mode gets a new context
})();

The issue can be seen in:

If the eval code is strict code, then (me: fix context)

But strict eval code is defined as:

Eval code is strict eval code if it begins with a Directive Prologue that contains a Use Strict Directive or if the call to eval is a direct call.

Since the call is not direct, the eval code is not strict eval code - and the execution is on global scope.


First of all great question.

"Eval Code" is more general than direct or indirect call to eval.

Let's check the exact specification for the eval function

15.1.2.1 eval (x)

When the eval function is called with one argument x, the following steps are taken:

  1. If Type(x) is not String, return x.

  2. Let prog be the ECMAScript code that is the result of parsing x as a Program. If the parse fails, throw a SyntaxError exception (but see also clause 16).

  3. Let evalCtx be the result of establishing a new execution context (10.4.2) for the eval code prog.

  4. Let result be the result of evaluating the program prog.

  5. Exit the running execution context evalCtx, restoring the previous execution context. ...

So, let's explore what 10.4.2 tells us, you cited that - in specific let's look at the first clause:

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 ... Initialise the execution context as if it was a global execution context

So what is a direct call?

A direct call to the eval function is one that is expressed as a CallExpression that meets the following two conditions:

The Reference that is the result of evaluating the MemberExpression in the CallExpression has an environment record as its base value and its reference name is "eval".

The result of calling the abstract operation GetValue with that Reference as the argument is the standard built-in function defined in 15.1.2.1.

So, what's the MemberExpression in both cases?

In eval('var a = 1;'); indeed the result of evaluating it has a reference name eval and calling GetValue resolution on it returns the built in function.

In (0, eval)('var a = 1;'); the result of evaluating the member expression does not have a reference name eval. (It does resolve to the built in function on GetValue though).

What are reference names anyway?

Section 8.7 in the spec tells us:

A Reference is a resolved name binding. A Reference consists of three components, the base value, the referenced name and the Boolean valued strict reference flag. The base value is either undefined, an Object, a Boolean, a String, a Number, or an environment record (10.2.1). A base value of undefined indicates that the reference could not be resolved to a binding. The referenced name is a String.

This requires us to look into the GetReferencedName:

GetReferencedName(V). Returns the referenced name component of the reference V.

So, while the expression (0,eval) === eval is true, when evaluating the function, this is actually an indirect call because of naming.

May I offer the Function constructor instead :)?