Perfection Kills

by kangax

Exploring Javascript by example

Global eval. What are the options?

December 15th, 2010

David Flanagan recently wrote about global eval() in Javascript, proposing a simple one-liner like this:


  var geval = this.execScript || eval;

Even though it looked beautifully simple, it wasn’t the best cross-browser solution I could think of. Moreover, it’s not something I would recommend. Pondering a bit more on this subject, I realized that there are actually quite a few ways to evaluate code in global scope in Javascript. Some of those ways — e.g. indirect eval — are also not understood very well, and their implications are not immediately visible. David pointed out to me — in one of the comments — that “indirect eval” is not a well-known idiom except in close circles of implementors. And he was right. There was in fact tons of confusion on the subject of global eval, all over the web.

In hope of making the situation clearer, I’d like to go over “global eval” options, taking a look at how and why they work. I’ll also explain some of the problems with the above-mentioned one-line solution.

How eval works

Before going further, let’s define “global eval” as a way to evaluate code in global scope. Simple as that.

The reason for all this fuss about evaluating code in global scope is because global, built-in eval function evaluates code in the scope of a caller:


  var x = 'outer';
  (function() {
    var x = 'inner';
    eval('x'); // "inner"
  })();

This behavior is described very clearly in both — ES3 and ES5:

[...]
The scope chain is initialised to contain the same objects, in the same order, as the calling context’s scope chain. This includes objects added to the calling context’s scope chain by with statements and catch clauses.
[...]

10.2.2 Eval Code [ES3]

In ES5, things are a bit more complicated interesting. The behavior of eval depends on 2 things — whether it is a direct or indirect call and whether invocation happens in strict mode. Don’t worry if you don’t know what “indirect call” is — we’ll take a look at it very soon. When talking about non-strict, direct eval call (i.e. good old eval(...) invocation) the outcome is exactly the same as in ES3 — code is executed in the scope of a caller.

[...]
b. Set the LexicalEnvironment to the same value as the LexicalEnvironment of the calling execution context.
[...]

10.4.2 Entering Eval Code [ES5]

Eval’ing in global scope

So native eval doesn’t allow to execute code globally. What to do? Let’s first take a look at some of the options, then try to distill them into a cross-browser solution.

Indirect eval call. Theory.

I mentioned “indirect eval call” before, when explaining behavior of eval in ES5. The reason indirect eval is of interest is because in ES5 it actually does execute code globally. Bingo! But what does it even mean — “indirect eval call”. Indirect eval call is simply any eval call that’s not a direct eval call. And direct eval call is defined as:

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 builtin function defined in 15.1.2.1.

15.1.2.1.1 Direct Call to Eval [ES5]

That might sound a bit cryptic, but it’s actually very simple. What specification is trying to tell is that eval('1+1') is a direct call, but (1,eval)('1+1') is not. If we were to dissect first expression — eval('1+1') — syntactically, we would see that it’s really just a CallExpression, which consists of a MemberExpression (eval) followed by Arguments ('(1+1)'), and in which MemberExpression consists of “eval” Identifier:


    eval             ( '1+1' )
  |______|
  Identifier

  |______|          |________|
  MemberExpression  Arguments

  |__________________________|
  CallExpression

This is, more or less, a signature of a direct call. ES3 has a slightly simpler definition, and the one that’s pretty much identical to the graphic above:

“[…] any way other than a direct call (that is, other than by the explicit use of its name as an Identifier which is the MemberExpression in a CallExpression) […]”

15.1.2.1 eval(x) [ES3]

So eval('1+1') is a direct eval call, but (1,eval)(‘1+1’) is not. Since the latter one is not a direct call, it is therefore an indirect eval call. Let’s take a look at it in more detail:


  (     1        ,         eval  )        ( '1+1' )
     |____|   |_____|    |_____|
     Literal  Operator   Identifier

     |_________________________|
     Expression

  |______________________________|
  PrimaryExpression

  |______________________________|        |________|
  MemberExpression                        Arguments

  |________________________________________________|
  CallExpression

In the above example, the part before Arguments (invocation parens) clearly doesn’t consist of just “eval” identifier. There’s a whole other expression, consisting of comma operator, numeric literal, and only then “eval” identifier. 1,eval — based on how comma operator works — still evaluates to a standard, built-in eval function, but overall expression is no longer a direct call. It is therefore an indirect eval call.

Indirect eval call examples

If you’re still not quite able to recognize indirect eval calls, these are some of the examples:


(1, eval)('...')
(eval, eval)('...')
(1 ? eval : 0)('...')
(__ = eval)('...')
var e = eval; e('...')
(function(e) { e('...') })(eval)
(function(e) { return e })(eval)('...')
(function() { arguments[0]('...') })(eval)
this.eval('...')
this['eval']('...')
[eval][0]('...')
eval.call(this, '...')
eval('eval')('...')

According to ES5, all of these are indirect calls and should execute code in global scope. I’d love to go over each one of them in more detail, but that would be too much for this post.

Note the var e = eval; e('...') on 5th line.

You can see that this is essentially what David’s solution does — var geval = window.execScript || eval. When geval is executed, geval identifier resolves to a global, built-in eval function, but overall expression does not have “eval” as an Identifier in CallExpression. Instead, there’s “geval” identifier, making this an indirect call and evaluating code globally.

But we’ll get back to that one-liner later, and now let’s take a look at one last, but not least important point about indirect eval call.

Have you noticed how ES5 definition says that eval from CallExpression should evaluate to standard, built-in function? It means that taken out of context, eval('1+1') is not necessarily a direct call. Only when eval actually references standard, built-in function (wasn’t overwritten or shadowed), a call can be considered direct.


  eval = (function(eval) {
    return function(expr) {
      return eval(expr);
    };
  })(eval);

  eval('1+1'); // It looks like a direct call, but really is an indirect one.
               // It's because `eval` resolves to a custom function, rather than standard, built-in one

Now that we looked at a list of indirect calls, how about few examples of direct ones?


  eval('...')
  (eval)('...')
  (((eval)))('...')
  (function() { return eval('...') })()
  eval('eval("...")')
  (function(eval) { return eval('...'); })(eval)
  with({ eval: eval }) eval('...')
  with(window) eval('...')

Looks pretty straightforward, doesn’t it?

But wait a second. Why is it that (eval)('...') and (((eval)))('...') are considered direct calls? Certainly, they don’t adhere to the signature we established earlier — Identifier “eval” within MemberExpression within CallExpression. What’s going on here? Shouldn’t the parenthesis around eval make it an indirect call?

The answer to this tricky question is in the first paragraph of ES5 direct call definition — the fact that “eval” in CallExpression should be a Reference, not a value. During program execution, eval in eval('1+1') expression is nothing but a Reference, and needs to be evaluated to a value. Once it’s evaluated, the value is (most likely) a standard, built-in function object. What happens in previously dissected (1,eval)('1+1') indirect call, is that (1,eval) expression evaluates to a value, not a Reference. Since it doesn’t evaluate to a Reference, it can not be considered a direct eval call.

But what about (eval)('1+1')?

The reason (eval) is considered a direct eval call is because (eval) expression still evaluates to a Reference, not a value. Ditto for ((eval)), (((eval))), and so on. This happens because Grouping Operator — “(” and “)” — does not evaluate its expression. If a Reference is passed to a Grouping operator — “(” and “)” — it still evaluates to a Reference, not a value.


  eval(); // <-- expression to the left of invocation parens — "eval" — evaluates to a Reference
  (eval)(); // <-- expression to the left of invocation parens — "(eval)" — evaluates to a Reference
  (((eval)))(); // <-- expression to the left of invocation parens — "(((eval)))" — evaluates to a Reference
  (1,eval)(); // <-- expression to the left of invocation parens — "(1, eval)" — evaluates to a value
  (eval = eval)(); // <-- expression to the left of invocation parens — "(eval = eval)" — evaluates to a value

Speaking in terms of ECMAScript, this is because both — comma operator (in (1, eval) example) and = operator (in (eval = eval) example) perform GetValue on its operands. As a result, (1, eval) and (eval = eval) evaluate to a value, whereas eval and (eval) — to a Reference.

Hopefully, it is now clear why (eval)('...') and (function(eval){ return eval('...') })(eval) are direct eval calls, but (1, eval)('...') and this.eval('...') aren’t.

Indirect eval call. In practice.

With all the theory of indirect eval behind us, let’s consider practical implications. We already know that ES5 specifies such calls to evaluate code in global scope. There are only 2 things left to consider — ES3 and reality. The fun part starts with ES3, which allows indirect eval calls to throw an error. Not only does ES3 allow for an error, it says nothing about evaluating code in global scope. And what about reality?

It turns out that quite a lot of environments nowadays more or less follow ES5 behavior, evaluating code in global scope. But unfortunately, not all of them.

IE<=8 executes code in the scope of a caller, as if indirect call was direct one. Some versions of Konqueror (~4.3) and Safari <=3.2 do the same thing as IE, evaluating code in the scope of a caller. Older Opera (~9.27) goes even further and actually does throw error, as permitted by a spec. All of this makes indirect eval call (on its own) not a good candidate for a cross-browser “global eval” solution. Not for another few years (when Opera <=9.27, Safari <=3.2 (which is probably still present in earlier iPhones), and other “misbehaving” browsers “disappear”).

Curiously, as of now, even the newest browsers deviate from ES5. Chrome 9, for example, considers this an indirect call — (function(eval){ return eval('x'); })(eval) — which it really isn’t; eval is a reference, it’s part of a MemberExpression within a CallExpression, and evaluates to a standard, built-in function.

Opera 11 and Firefox 4 — both browsers are still in beta for these versions — consider the following expression to be a direct call, even though it doesn’t satisfy all requirements of such (eval doesn’t resolve to a standard, built-in function in this case):


  eval = (function(eval) {
    return function(s) {
      return eval(s)
    };
  })(eval);

  eval('...');

Full ES5 conformance is clearly yet to come (meanwhile, take a look at my simple test suite).

window.execScript

Fortunately for us, there’s a proprietary window.execScript function present in IE. It’s defined to “execute the specified script in the provided language” and allows to execute Javascript code in global scope. It’s not very surprising that Chrome has this function as well; most likely for compatibility reasons. As of today, window.execScript is still present in Chrome 9.

The way window.execScript differs from indirect eval call is that it doesn’t return a value. Actually, it does return value in Chrome, but that value is always null.


  window.execScript('1+1'); // null in Chrome, undefined in IE
  (1,eval)('1+1'); // 2

window.eval

Another, very popular way to execute code in global scope is using window.eval. What’s interesting about it, is that window.eval('...') is often believed to be somehow special when it comes to executing code globally. It’s as if eval called as a property of window object always executes in global scope. The truth is that window.eval('...') is nothing but a… indirect eval call, and is not much different than (1,eval)(‘…’), this.eval('...'), or (eval = eval)('...') when it comes to evaluation.

Not understanding what’s going on, some people unfortunately come up with rather monstrous solutions like eval.call(window, '...'), window.eval.call(window, '...') and even scarier combinations. All of those are, once again, nothing but indirect eval calls. The reason code is executed in global scope is not because eval is called in context of window, but because it’s invoked as an indirect call. eval might as well be a property of foo object, and would still work identically:


  var foo = {
    eval: eval
  };

  foo.eval('...'); // behaviorally identical to `window.eval('...')`
                   // both are indirect calls and so evaluate code in global scope

More confusion can be seen on StackOverflow, where eval is labeled “magic”, and is claimed to somehow lose its magic when aliased — all the symptoms of direct vs. indirect calls.

eval context in webkit

It’s worth mentioning that in some of the WebKit -based browsers — including latest Safari (5) and Chrome (9) — eval throws error when called from within certain context (a.k.a. “this” object). And by “certain context” I mean anything that’s not a window/global object (or at least it seems that way). The error is EvalError: The “this” object passed to eval must be the global object from which eval originated. This is probably done for security reasons.


  window.eval('1+1'); // works
  eval.call(window, '1+1'); // works
  eval.call(null, '1+1'); // works, since eval is invoked with "this" object referencing global object

  eval.call({ }, '1+1'); // EvalError (wrong "this" object)
  [eval][0]('1+1'); // EvalError (wrong "this" object)
  with({ eval: eval }) eval('1+1'); // EvalError (wrong "this" object)

Since all of those calls are indirect ones, such error is allowed by ES3 (see, for example, EvalError — 15.11.6.1)

new Function

Sometimes I see suggestions to use new Function (or just Function) to evaluate code globally:


  Function('return 1+1')(); // 2

This is somewhat misleading.

The code executed from within function created by Function constructor doesn’t really execute in global scope. However, it doesn’t execute in local scope either, which is what probably leads to confusion. Function constructor creates a function whose scope chain consists of nothing but a global scope (preceded with function’s own Activation Object, of course). Any code contained in a function created via Function constructor evaluates in a scope of that function, not in a global scope. However, it’s almost as if code executes globally, since global object is the very next object in the scope chain.

What are the implications of this difference?

Well, for once, any function/variable declarations are performed using created function’s Activation Object as Variable Object. In other words, they are declared local to created function, rather than in a global scope:


  function globalEval(expression) {
    return Function(expression)();
  }

  var x = 'outer';
  (function() {
    var x = 'inner';
    globalEval('alert(x)'); // alerts "outer"
  })();

  // but!

  globalEval('var foo = 1');
  typeof foo; // "undefined" (`foo` was declared within function created by `Function`, not in the global scope)

Another interesting peculiarity of evaluating code via Function('...')() is identifiers leakage. An example of this is "arguments" identifier which resolves to an arguments object, when evaluating code via Function('...')():


  eval('alert(arguments)'); // ReferenceError
  Function('alert(arguments)')(); // alerts representation of an `arguments` object

Function -executed code is a good solution if you wish to avoid executing code locally, but it won’t execute it globally either.

setTimeout

Non-standard (but finally codified in currently draft HTML5) setTimeout is another way to execute code globally — when given code as a string, rather than as a function. The problem with setTimeout, of course, is that it’s asynchronous and that — similarly to window.execScript — it doesn’t return the result of evaluation.

Unfortunately, looking through HTML5 specs, I couldn’t find any mention of the fact that setTimeout should evaluate code in global scope. Hopefully, I missed it; it should certainly be specified.

Script insertion

Finally, in browser environment, there’s always an option of injecting a script element into a document. Contents of that script element are then parsed and executed as a Program in global scope:


  var el = document.createElement('script');
  el.appendChild(document.createTextNode('1+1'));
  document.body.appendChild(el);

As with window.execScript and setTimeout -based solutions, this one doesn’t allow to get meaningful return value (unless some kind of workaround is applied). Script insertion is used by current jQuery (1.4.4) in jQuery.globalEval. It fails to provide a return value, but works in more cross-browser manner than “indirect eval + window.execScript” combination. For example, it works in Safari <=3.2 and in older Opera, in which indirect eval calls fail.

The problem with window.execScript || eval

Let’s get back to this very appealing one-liner, taking a look at some of its problems. Although it seems great in theory, its 2 main downsides can be summarized as:

  • Indirect eval is not sufficiently cross-browser; lack of feature-testing
  • Non-standard execScript is preferred to standard eval

As you’ve seen before, indirect eval is still quirky across some of the existing browsers. There are also environments where it hasn’t even been tested in — mobile browsers, non-standard platforms, older, rare or unknown browsers, etc. Relying on the fact that indirect eval executes code globally — just because that’s what happens in some of the newer, popular browsers — is rather careless. A much saner strategy is to actually feature test indirect eval call behavior before attempting to use it.

Another problem of one-liner is that window.execScript is tested for existence first, and only when it doesn’t exist, eval is chosen as a fallback. One of the rules of interoperability is that standard features should be preferred over non-standard ones. For example, since window.execScript exists in Chrome, one-liner will end up “using” non-standard (and less-capable) window.execScript rather than standard eval.

Finally, the assumption that if window.execScript doesn’t exist, then indirect eval must be executing code globally (which is what one-liner does) strikes me as rather unsafe.

There are more similarly non-optimal soutions online, where folks seem to be taking trial-and-error approach, not realizing why things work the way they do. Rakesh Pai is bedazzled by the unexplainable difference between eval and window.eval invocations:

I don’t know what Firefox is doing in case 2, and for some reason Safari Nightlies seem to be following it. Maybe it’s just beyond my understanding, but case 2 is not supposed to be different from case 1. Why does case 2 operate in global scope? If window.eval is different from eval, case 3 shouldn’t all have given errors. Someone please help me understand that $hit.

The fact that browsers do not fully conform to ES5 makes things even worse. For example, with(window) eval('...') is considered an indirect call in Chrome 9, even though as per ES5 it is very much a direct one — the “eval” Identifier is within MemberExpression of CallExpression; it’s a Reference, and it resolves to a standard, built-in eval function.

Feature testing -based approach

Creating a more robust version of global eval is not hard.

The idea is similar — use indirect eval, with a fall back on window.execScript. The important difference, however, is that indirect eval is feature-tested and is only used when it’s safe to do so. One potential problem here is that window.execScript is not tested, but is instead assumed to execute code globally. If you find this assumption unsafe, it should be trivial to add feature-test for window.execScript as well.


var globalEval = (function() {

  var isIndirectEvalGlobal = (function(original, Object) {
    try {
      // Does `Object` resolve to a local variable, or to a global, built-in `Object`,
      // reference to which we passed as a first argument?
      return (1,eval)('Object') === original;
    }
    catch(err) {
      // if indirect eval errors out (as allowed per ES3), then just bail out with `false`
      return false;
    }
  })(Object, 123);

  if (isIndirectEvalGlobal) {

    // if indirect eval executes code globally, use it
    return function(expression) {
      return (1,eval)(expression);
    };
  }
  else if (typeof window.execScript !== 'undefined') {

    // if `window.execScript exists`, use it
    return function(expression) {
      return window.execScript(expression);
    };
  }

  // otherwise, globalEval is `undefined` since nothing is returned
})();

The benefit of this kind of implementation is that it enables graceful degradation. You can easily find out if globalEval is available in a current environment (since globalEval would be undefined when none of the 2 solutions are available):


  if (globalEval) {
    /* code can be executed globally */
  }
  else {
    /* use fallback scenario */
  }

Note that — as with any indirect eval / window.execScript approach — globalEval will differ in its ability to return a value, depending on which method was chosen.

Global eval in libraries

So what do some of the popular Javascript libraries do? Which global eval solution do they chose?

As we’ve seen earlier, jQuery sacrifices return value for a more extensive browser support, using script insertion technique in its jQuery.globalEval method.

Prototype.js doesn’t even try to evaluate scripts globally — it uses a good old, direct eval invocation. Instead, Prototype.js warns about ramification of local execution in the documentation.

Dojo choses the infamous window.eval variation, but does something really messed up — it uses indirect eval if eval exists on a global object (which should be true in all ES3-compliant browsers), and falls back on regular eval invocation. This kind of fall back obviously results in code executed locally, which should also happen silently. Dojo’s eval behavior will vary wildly depending on the environment, and has a possibility of uncaught error. On a related, humorous note, I was just as stunned by comments around dojo.eval as David Mark in this message.

Mootools uses window.execScript when present, falling back on script insertion (similar to jQuery). jQuery’s script insertion, however, is more robust than Mootools, as they actually test if it results in evaluation of code.

Fuse.js choses a very elaborate and careful strategy. It tests if indirect eval works as expected (catching an error if any), uses it if it does; if indirect eval is defunct, Fuse falls back on script injection. Unlike jQuery or Mootools, it actually tries to return a value of a code executed via script injection. It does so by wrapping contents of code with eval. An interesting side effect of executing code globally via eval is that function and variable declarations create deletable bindings (which is most likely an insignificant detail).

Summary

So what can we learn from all this?

Hopefully, you now have a true understanding of why eval sometimes executes code in global scope. That window.eval(...) is not very special and is just another form of indirect eval. That behavior of indirect eval is specified differently in ES3 and ES5. That behavior of indirect eval in actual implementations is still often sporadic — partly due to transitioning from ES3 to ES5, partly because of uncaught bugs. You’ve seen that blindly relying on indirect eval is unsafe. That implementations of “global eval” relying on indirect eval should employ feature testing, as with so many other things in cross-browser scripting. Finally, that alternative techniques exist and are used by libraries, such as script injection.

Categories: ECMA-262, ES5, eval, strict-mode

Comments (25)

  1. Gravatar

    David Flanagan said on Dec 15, 2010 @ 12:41

    A very thorough discussion. Thanks!

    You’re right that my one-liner isn’t suitable for production use. It is tweetable, however :-)

    Only two things I’d add to this:

    1) jQuery’s version of global eval via script injection is a little more complicated since it has a special branch for IE where the code to be evaluated is set on the text property of the script element rather than being inserted as you show. I’m not sure which versions of IE this is required for.

    2) Many of your indirect eval examples will produce syntax errors in ES5 strict mode, where ‘eval’ is effectively a reserved word.

  2. Gravatar

    npup said on Dec 15, 2010 @ 12:45

    Really nice writeup, thanks. The line with I’d love to go over each one of them in more detail (the indirect-call variants) even made me giggle..

  3. Gravatar

    Sergey said on Dec 15, 2010 @ 12:45

    Realy interesting article! But I have one question, you wrote: “The reason code is executed in global scope is not because eval is called in context of window, but because it’s invoked as an indirect call”, but why it’s indirect call, this is a Reference (or I’m wrong?) and ReferenceName is eval (Before this article I realy think that code evaluated in global scope because it was invoked in window context).

  4. Gravatar

    AllenWB said on Dec 15, 2010 @ 13:07

    I don’t think the wrapper function you are returning in

      if (isIndirectEvalGlobal) {
    
        // if indirect eval executes code globally, use it
        return function(expression) {
          return (1,eval)(expression);
        };
      }
    

    is strictly necessary.
    Because the return value is being assigned to a variable named globalEval and you know indirect eval is supported, you know that any call through globalEval must be an indirect eval. Of course, the wrapper could be bullet proofing against the possibility that the result is assigned to a variable named eval. If that is the case you probably should explicitly mention it.

  5. Gravatar

    Peter van der Zee said on Dec 15, 2010 @ 13:19

    I hope that by now we’ve learned not to use eval at all. Well, I kinda hope we have :p

  6. Gravatar

    Asen Bozhilov said on Dec 15, 2010 @ 13:22

    Pretty seems as discussion at twitter, 1 or 2 weeks ago. There I suggested `Function’ constructor and my suggestions weren’t be to evaluate code globally. They were to prevent injection in local scope of third parties.

    I still do not see any reasons to allow users to inject code globally or locally in my code. It allows for disaster results after their injection. Even locally injection are safer than globally. Do you see any reasons to make API as `jQuery.globalEval’? I would like to see production usage of indirect `eval’ which is the most appropriate approach.

    BTW you are probably interesting in strict mode indirect eval.

    (function () {
        'use strict';
        (1, eval)('var x = 10;');
    })();
    
    print(x); //Should be 10
    
  7. Gravatar

    Andy E said on Dec 15, 2010 @ 13:38

    Another great article, I thoroughly enjoyed the read. As you pointed out yourself, the resulting globalEval function differs from browser to browser in the way that it may or may not return a result depending on whether or not indirect eval works. I actually had to work around this issue a couple of months ago, I was building a web console in Internet Explorer and needed that return value. The way I had to do it was something like the following:

    function globalEval(text) {
        var ret;
        // Properly escape \, " and ' in the input, normalize \r\n to an escaped \n
        text = text.replace(/["'\\]/g, "\\$&").replace(/\r\n/g, "\\n");
    
        // You have to use eval() because not every expression can be used with an assignment operator
        window.execScript("globalEval.____lastInputResult____ = eval('" + text + "');} }");
    
        // Store the result and delete the property
        ret = globalEval.____lastInputResult____;
        delete globalEval.____lastInputResult____;
    
        return ret;
    }

    Yes, it’s a mess, yes there’s 2 evals and a double-escaping regex, but that’s about the closest you can actually get with Internet Explorer and I didn’t really have to worry about it much because it’s for a web console where the developer tools aren’t normally available.

  8. Gravatar

    Andy E said on Dec 15, 2010 @ 13:41

    Actually, it sounds similar to how you described the way Fuse.js works — I might have to take a look at that code :-)

  9. Gravatar

    Asen Bozhilov said on Dec 15, 2010 @ 14:07

    To be a really globalEval you should filter "use strict"; directive.
    globalEval('"use strict";'); //no more global eval
    e.g.

    (1,eval)(expression.replace(/^\s*(?:"use strict"|'use strict'/, ''));

  10. Gravatar

    Tom Robinson said on Dec 15, 2010 @ 16:40

    Any reason to do

    return function(expression) {
    return (1,eval)(expression);
    };

    instead of just:

    return eval;

    Isn’t the latter an indirect eval, but without the extra function call?

  11. Gravatar

    Tom Trenka said on Dec 16, 2010 @ 9:13

    On the dojo.eval…IIRC (and I’m tapping into *long* memories here) the reason for it is….drumroll please…Internet Explorer 6 and iframes (not mutually exclusive). We have a *lot* of enterprise-types that insist on IE6 support (we’re trying to get away from it) but more importantly we also have that dijit.Editor, which (iirc also) is not something that any of the other libraries have, and we need to support it.

    Believe me when I say that we’re not happy about it either =)

  12. Gravatar

    kangax (article author) said on Dec 16, 2010 @ 11:08

    @DavidFlanagan

    No luck for me tweeting that 20-smth-liner :) Well, “script injection” -based solution is pretty short, if not for IE workaround (what else is new…). jQuery does feature test code evaluation by script injection, unlike Mootools (I mentioned it in “global eval in libraries” overview). IIRC, IE up until 9 doesn’t allow to append text node to a script element, therefore “text” assignment is used.

    Thanks for bringing up strict mode restrictions. You’re right that “eval” is effectively a reserved word in strict; technically, of course, it’s not a reserved word but since using it as an identifier in variable/function declaration (as well as in other places that result in bindings creation — formal parameters, identifier in catch, etc.) is a SyntaxError, it’s practically unusable.

    However, if we look closer, you’ll be surprised that out of 32 examples in a test suite, only 3 would fail in strict mode: (eval = eval)('...'), (function(eval){ return eval('...'); })(eval), and eval=(function(eval){return function(s){return eval(s)}})(eval); eval("...") — all cases of reassignment (and one — local shadowing).

    I’m not sure what the committee plans are regarding indirect eval calls. Perhaps those will all be a SyntaxError in harmony. I can even imagine why that would make sense, since overall direction seems to be to disallow access to global scope.

  13. Gravatar

    kangax (article author) said on Dec 16, 2010 @ 14:05

    @AllenWB, @Tom Robinson

    Good point on assigning eval to globalEval instead of using intermediate function.

    The reason I used (1,eval)('...') is because that’s how the test went. It was a direct inference — test certain functionality, then use that functionality in a resulting method. Assuming that invoking eval with a different name will evaluate code globally just because (1,eval)('...') evaluates code globally is already a weaker inference. After all, there are implementations which are inconsistent with indirect eval calls; In Opera 11, for example, (0 || eval)('...') is a direct call, but (1,eval)('...') isn’t. If we tested (1,eval)('...'), then assumed that (0, eval)('...') works, the result wouldn’t be as expected.

    The test can be changed to invoke eval with a different name — assign “eval” to “geval”, then invoke “geval” testing if code is executed globally.

  14. Gravatar

    Kevin Hakanson said on Dec 20, 2010 @ 12:02

    In reading through your globalEval implementation, I fear that var isIndirectEvalGlobal = (function(original, Object) { could be broken when minifiers like Google Closure Compiler start optimizing. I compiled the code with the Simple optimization and got:

    var globalEval=function(){if(function(a){try{return(0,eval)("Object")===a}catch(b){return false}}(Object,123))return function(a){return(0,eval)(a)};else if(typeof window.execScript!=="undefined")return function(a){return window.execScript(a)}}();

    It looks like second named parameter Object was completely removed because it wasn’t used. I only post this in case someone copies your code, which has been tested as-is, but may break if “optimized”.

  15. Gravatar

    kangax (article author) said on Dec 20, 2010 @ 13:27

    @Kevin

    Thanks for bringing this up. They actually mention it in the docs, that “Compilation with SIMPLE_OPTIMIZATIONS always preserves the functionality of syntactically valid JavaScript, provided that the code does not access local variables using string names (by using eval() statements, for example). (emphasis mine)

    That’s why:

    (function(x) { eval("x") })(123);
    
    // compiles to
    
    (function(){eval("x")})(123);
    

    ..which definitely changes program behavior.

    It would certainly be better if Closure Compiler at least issued a warning (or even better — avoid certain optimizations when eval is present in a scope).

  16. Gravatar

    Jeff Walden said on Dec 25, 2010 @ 22:33

    I don’t believe you’re right about Opera and Firefox implementing eval wrong in the one instance you mention.

      eval = (function(eval) {
        return function(s) {
          return eval(s)
        };
      })(eval);
    
      eval('...');
    

    You say they “consider the following expression to be a direct call, even though it doesn’t satisfy all requirements of such (eval doesn’t resolve to a standard, built-in function in this case)”. The top-level eval('...') has the syntax for a direct call, so if eval there were the original eval function it’d be a direct eval. But of course it’s not — it’s the function(s) { } returned by the parenthesized function. So its directness is irrelevant.

    What matters is the directness not of the top-level call, but of the nested call performed by the top-level call: return eval(s). Because the original eval was passed into the parenthesized closure, the eval in this expression is the original eval. This eval is syntactically direct, and because it’s the original eval this expression must therefore be a direct eval. So Opera and Firefox get this right (and reasoning from your not mentioning them, seemingly Chrome and IE get it wrong, although I can’t easily test either right now to be sure).

  17. Gravatar

    kangax (article author) said on Dec 25, 2010 @ 22:53

    @Jeff Walden
    Thanks for catching this. I actually had thoughts like that — that it’s an inner eval invocation that should matter here, and that result merely propagates to an outer function — but for some reason discarded them :)

    Yes, Chrome does consider this an indirect call; executes code in global scope. Can’t check IE at the moment.

    It’s nice to see FF4 being so conforming. I’ll move that test to the “direct calls” section.

  18. Gravatar

    Will Alexander said on Jan 3, 2011 @ 11:03

    Very helpful post!

    FF <=3.6 executes all dynamic scripts serially so using Script Insertion has two drawbacks. The code:
    1 – Never executes synchronously.
    2 – Doesn't execute until any outstanding remote scripts have finished.

    An example of this can be seen here

    While I cannot figure out how to solve #1, at least in FF =3.6, you can workaround #2 by setting the async attribute to true and assigning the code to the src attribute as a data url. http://jsfiddle.net/serverherder/dTgm5/1/embedded/result/

    IIRC, HTML 5 requires synchronous execution and this is the behavior of FF 4.

  19. Gravatar

    santhosh said on Mar 20, 2011 @ 8:59

    Interesting Article.

Trackbacks

  1. Perfection kills » Unnecessarily comprehensive look into a rather insignificant issue of global objects creation said:

    [...] The rationale for this was, supposedly, to increase security by limiting access to global object. There is, however, a workaround and it involves indirect eval call (which I explained in the past): [...]

  2. The JavaScript Comma Operator | JavaScript, JavaScript said:

    [...] @kangax describes here, we can use the comma operator to fashion an indirect call to eval which will force it to execute [...]

  3. JavaScript F.A.Q | Dan Tyan — web developer blog said:

    [...] Global eval. What are the options? [...]

  4. Global `eval` with a result in Internet Explorer « What the Head Said said:

    [...] the result to display in the console window. Looking around the web didn’t help. As usual, kangax had an article and even a solution for providing a somewhat cross-browser global eval. However for Internet [...]

  5. Vjeux » Intercept and alter <script> include said:

    [...] I'm using window.eval in order to eval from the global scope. var ast = Reflect.parse(request.responseText); var ast = transform(ast) [...]

  6. eval/window.eval/window.execScript之区别及用法及注意点 | jser.in - 专注js技术领域 said:

    [...] 另感谢js罗浮宫2群,银川-bird给出了扩展资料,其中也证明了这点,需要的亲并且E文无障碍的话可以狠狠点击这里。 [...]

Leave a Comment

Please, don't forget to escape your input (<, > and &). Wrap code sections with <pre>

Allowed tags: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>