ES1-3 eval-created function bindings can be used to destroy caller bindings
ES3.1 changes the rules to use CreateMutableBinding when entering the eval execution context only if x is not already bound, and in any case then to call SetMutableBinding (10.2.1.2.3 in the feb09 draft). SetMutableBinding calls [[ThrowingPut]]. Strict mode adds value in its own way but let's assume it's not used in the following.
[[ThrowingPut]] would not be called in the example given. x has a mutable binding in a Declarative Environment Record so SetMutableBinding simply records the value without involving [[ThrowingPut]]. However, the issue Brendan raises would occur in scenarios where x resolved to a binding in an Object Environment Record (ie, the global environment or a property of a with bound object). The same outcome would occur if this global code was executed: var x = a * a; eval("function x() { return 'ha ha!'; }"); alert(x);
Brendan actually gets around to saying this but I found it a bit buried in the message so I wanted to re-emphasize the point that this issue applied only to globals and with bound names.
I'm not yet convinced that this really is a significant ES3 to ES3.1 compatibility issues. The potential incompatibilities all seem to involve either side effects of getter/setter properties or explicit attribute manipulation. Since neither of these are a part of ES3 it would have to be occurring in existing code using implementation depended means (language extensions or implementation magic). Code that depended upon such implementation characteristics would not be portable among ES3 implementations and hence I don't think there should be expectations that it would be portable to an conforming ES 3.1 implementation. That said, I do think we want to be respectful of the existing implementations and not unnecessarily break code that depends non-standard features in existing ES3 implementations.
I don't see that the specified behavior would be a problem for new code, written specifically to use ES 3.1 features including accessor properties and attribute manipulation. I believe that the current ES3.1 draft is complete and consistent in what will happen when those features are used in conjunction binding instantiation in Object Environment Records. New code that uses 3.1 features should expect to get 3.1 semantics.
I do think that I could probably fix the concern with some rewriting of 10.6 (Binding Instantiation) and the Environment Record interface/implementation to ensure that functions get a "fresh" property in the binding objects. However, I'm reluctant to mess with already "debugged code", ie the specification, at this point unless this is more than a hypothetical problem.
Can anybody identify any real scenarios based upon existing implementations and existing code where this change in semantics is actually going to cause a forward compatibility problem?
On Feb 13, 2009, at 9:44 AM, Allen Wirfs-Brock wrote:
I'm not yet convinced that this really is a significant ES3 to ES3.1
compatibility issues.
Not sure either, why I wrote "I can't prove this is going to burn
programmers in the future, but I suspect it will."
I don't see that the specified behavior would be a problem for new
code, written specifically to use ES 3.1 features including accessor
properties and attribute manipulation. I believe that the current
ES3.1 draft is complete and consistent in what will happen when
those features are used in conjunction binding instantiation in
Object Environment Records. New code that uses 3.1 features should
expect to get 3.1 semantics.
That may not help if we make it unnecessarily hard to compose old and
new code in the global object. It's an issue for existing code, and if
you expect new code to use Object.getOwnPropertyDescriptor before
declaring a function, just in case there's an accessor of the same
name, I will bet you no one will write such code in the future, even
if you staple ES3.1's spec to their foreheads.
I do think that I could probably fix the concern with some rewriting
of 10.6 (Binding Instantiation) and the Environment Record interface/ implementation to ensure that functions get a "fresh" property in
the binding objects. However, I'm reluctant to mess with already
"debugged code", ie the specification, at this point unless this is
more than a hypothetical problem.
That's fine. I'm not asking for a change, just discussion. We should
implement and test.
Can anybody identify any real scenarios based upon existing
implementations and existing code where this change in semantics is
actually going to cause a forward compatibility problem?
Hate to say it, but you buried the important part too, for anyone not
digging below the fold. But this topic isn't for short attention
spans. ;-)
Laying a trap with defineSetter and defineGetter on the global
object, to take over a function (wrap it to spy on its params), would
have to depend on a failure to judge trustworthiness. But this happens
(perl.com's ad supplier let its domain lapse, and pr0n.com grabbed it
and hijacked location). The ES1-3 function declaration super-power of
blowing away any pre-existing property provided some protection, but
really, if the enemy is within the gates, you're dead.
So the more plausible threat, and a lesser one, is defending in a non-
hostile case against other code that was loaded previously. Even
native global bindings might be configurable and writable, for
backward compatibility (undefined comes to mind, along with many DOM
level 0 additions over the years). If these are all data properties,
no problem. If any is an accessor, then it's hostile to existing or
future code that wants to name a function with the same identifier.
Thanks for the reply, it helps to talk this stuff through (I don't
have as much time to think about it as I'd like).
ES3 10.1.3 says:
"For each FunctionDeclaration in the code, in source text order,
create a property of the variable object whose name is the Identifier
in the FunctionDeclaration, whose value is the result returned by
creating a Function object as described in section 13, and whose
attributes are determined by the type of code. If the variable object
already has a property with this name, replace its value and
attributes. Semantically, this step must follow the creation of
FormalParameterList properties."
But in eval code this is a botch, since it requires
function foo(a, s) { var x = a * a; eval(s); return x; } alert(foo(2, "function x() { return 'ha ha!'; }"));
to alert the function x's toString results.
Still, the spec is clear enough, and Firefox 3 (not 2 or earlier),
Safari, and Opera all agree (I can't test IE atm).
ES3.1 changes the rules to use CreateMutableBinding when entering the
eval execution context only if x is not already bound, and in any case
then to call SetMutableBinding (10.2.1.2.3 in the feb09 draft).
SetMutableBinding calls [[ThrowingPut]]. Strict mode adds value in its
own way but let's assume it's not used in the following.
The change in spec between 3 and 3.1 does not affect the outcome for
the example above, but changing the attributes of x before the eval
call, or giving it a setter somehow (the native implementation might
use some kind of internal setter, depending on how it handles eval and
function activations), would make a big difference: the old spec says
to blow away any pre-existing x including its attributes, getter, and
setter.
The new 3.1 language would respect the attributes and call the setter.
And 3.1 Object.defineProperty, etc., lets users define setters on
objects they can reference.
Since the activation object is censored, the issue becomes function
foo in global code, not var or function x in function code. Code on
the web today can count on a function declaration being processed from
global code (and possibly from eval from global code, depending on the
implementation) so that any pre-existing writable binding of the same
name is destroyed. This gives some integrity that otherwise can't be
recovered.
With ES3.1, programmers will have to use
Object.getOwnPropertyDescriptor(this, 'foo') and check first to avoid
spoofing and luring attacks via setters. If an unwanted foo pre- exists, the code can work around the name conflict or try to delete
that foo.
A pre-existing non-writable (const) property is not a threat (except
of the trivial DOS kind), and indeed Firefox throws an error given
const foo = 42; (in its own script tag or earlier in the same one as
function foo shown above) and then function foo(){...}. Silent failure
to update foo would be inferior, but better than recreating foo and
violating the integrity of const.
The problem is setters (and getters) on the global object combined
with the change to function binding, from re-creation to conditional
creation followed by assignment. I can't prove this is going to burn
programmers in the future, but I suspect it will. Anway, it is a big
enough change from ES3 and implementation behavior -- for global code
at least -- that I wanted to bring it up for discussion here.