Assigning to eval-introduced local bindings outside strict mode, and an ES5 spec bug

Jeff Walden jwalden+es at MIT.EDU
Wed Nov 24 19:31:29 PST 2010


In answering some questions about how SpiderMonkey implements assignment in the face of crazy concerns like those raised in the "Assigning to globals in strict mode" thread, I wrote this example to demonstrate the precise semantics specified and implemented:

   var x = "global";
   function fun(s) { eval(s); return function(s) { return eval(s); }; }
   var closure = fun("var x = 'local'; x = (delete x, 'overwrite');");
   assert(x === "global");
   assert(closure("x") === "overwrite");

When I went to verify our behavior/implementation conforms to ES5, I discovered that ES5 asserts this situation to be impossible!

eval calls CreateMutableBinding for x.  The binding is configurable because it is introduced by eval code.  The assignment to x starts by evaluating x to a Reference rx whose base is the lexical environment for the call to fun and whose name is "x".  Next we evaluate the right-hand side, along the way calling DeleteBinding for x and successfully removing that binding because it was configurable.  The assignment algorithm completes by calling PutValue(rx, "overwrite").  Step 5 of PutValue calls SetMutableBinding on the lexical environment.  Step 2 of SetMutableBinding asserts that a binding for x already exists -- but it doesn't because we deleted it!

ES5 can't assert the binding exists.  SpiderMonkey, when it assigns to a deleted binding, appears to reintroduce a configurable, mutable binding:

   [jwalden at find-waldo-now src]$ dbg/js
   js> var x = "global";
   js> function foo(s) { eval(s); return function(s) { return eval(s); }; }
   js> var closure = foo("var x = 'local'; " +
                         "x = (delete x, 'overwrite'); " +
                         "print(x); " +
                         "delete x; " +
                         "print(x); " +
                         "eval('var x = \"local2\";'); " +
                         "print(x);")
   overwrite
   global
   local2

Thus I *think* the right change (awful as it is) is to replace step 2 with:

2. If envRec does not have a binding for N, call CreateMutableBinding(N, true).

and perhaps add a note to the end of the algorithm explaining how this can occur.  But I could well be missing something here -- please poke holes in my suggestion.  :-)

Jeff


More information about the es5-discuss mailing list