Late shadowing of globals, esp. 'undefined'?
This sounds like an oversight on my part.
Please file a bug and I'll fix it.
However, that won't do any thing for host defined global properties
On 10/28/14, 12:23 PM, Allen Wirfs-Brock wrote:
However, that won't do any thing for host defined global properties
Hmm.
That's actually a problem. The following host defined (by DOM) global properties need to be non-configurable and not shadowable when evaluating script in the global scope: "window", "document", "location", "top". Allowing those to be shadowed or otherwise overridden leads to security bugs.
They could be specified as being handled in the same manner as the ES builtins.
Also I'll check if it's feasible to make it a runtime error for a global let to shadow a non configurable property of the global object.
On 10/28/2014 09:10 AM, Andreas Rossberg wrote:
If so, how do we fix this? Allowing shadowing after the fact is pretty bad, since it will probably make all accesses to builtin globals slower in ES6. But it is particularly bad for 'undefined', where the ability to rebind would break various assumptions and optimisations based on its immutability.
This seems avoidable to me, even with the current spec language. Implementations would simply need to track syntactically-global accesses (accesses that are global only at runtime, due to dynamic scoping via with, eval, and similar are already slow, so effects on their perf seem ignorable). Then, if a new script's compilation would introduce a shadowing lexical declaration, invalidate the existing global-access code, such that when it next runs it takes account of the shadowing declaration. (Or appears to have that effect.) What am I missing?
Of course, we could make it an early error to let declare ‘undefined’ and friends at the top level of a script or module.
I suppose we could extend that to include all built-in globals. Could really do it for host defined globals.
On Wed, Oct 29, 2014 at 1:53 AM, Allen Wirfs-Brock <allen at wirfs-brock.com> wrote:
I suppose we could extend that to include all built-in globals. Could really do it for host defined globals.
-
I thought the idea was that "host defined" would be a thing of the past. Do we want differences in capabilities between globals defined through JavaScript libraries and the browser?
-
While document, navigator, etc. are non-configurable and whatnot, there's a bunch of other "host defined" globals that have different characteristics. Just study Window's IDL in HTML.
On 28 October 2014 17:38, Allen Wirfs-Brock <allen at wirfs-brock.com> wrote:
Also I'll check if it's feasible to make it a runtime error for a global let to shadow a non configurable property of the global object.
Is there a reason why we cannot make it an error for any present property? Maybe that would even allow us to get rid of the VarNames list?
On 28 October 2014 23:22, Jeff Walden <jwalden+es at mit.edu> wrote:
This seems avoidable to me, even with the current spec language. Implementations would simply need to track syntactically-global accesses (accesses that are global only at runtime, due to dynamic scoping via with, eval, and similar are already slow, so effects on their perf seem ignorable). Then, if a new script's compilation would introduce a shadowing lexical declaration, invalidate the existing global-access code, such that when it next runs it takes account of the shadowing declaration. (Or appears to have that effect.) What am I missing?
It's possible, but introducing code dependencies would be rather heavy-weight machinery for supporting a "feature" that nobody wants. If lexical declaration semantics required deopts then I would consider that a serious design fail.
Andreas Rossberg wrote:
Is there a reason why we cannot make it an error for any present property? Maybe that would even allow us to get rid of the VarNames list?
+1
On Oct 29, 2014, at 6:54 AM, Anne van Kesteren <annevk at annevk.nl> wrote:
- I thought the idea was that "host defined" would be a thing of the past. Do we want differences in capabilities between globals defined through JavaScript libraries and the browser?
The issues is really about the difference between globals that are explicitly declared in ES using var (or function) and properties of the global object that have been directly created via direct property access. Historically, ES defined globals and platform defined globals were treated as pre-existing properties.
ES since forever have made special provisions about when shadowing of such properties are allowed. Both global var and function declarations reused any global object existing property for the declared name, except that function declarations refused to change the value of non-configurable readonly properties.
Note that global object properties that are not declared bindings can be created by ES code, too.
If browsers use the same semantic mechanisms a libraries (or the implementation level equivalent) then they will behave identically.
- While document, navigator, etc. are non-configurable and whatnot, there's a bunch of other "host defined" globals that have different characteristics. Just study Window's IDL in HTML.
Yes, has always been the case and ES has allowed them to be over-written by explicit var and function declarations, except as noted above.
What we are really talking about here is whether let-style declarations should be able to over-write such properties.
On Oct 29, 2014, at 6:59 AM, Andreas Rossberg <rossberg at google.com> wrote:
Is there a reason why we cannot make it an error for any present property? Maybe that would even allow us to get rid of the VarNames list?
Perhaps, there is a legacy that requires that var/function declarations can over-write global object properties. There is no particular reason that we have to allow this for let-like declarations.
On 10/29/14, 2:55 PM, Allen Wirfs-Brock wrote:
ES since forever have made special provisions about when shadowing of such properties are allowed. Both global var and function declarations reused any global object existing property for the declared name, except that function declarations refused to change the value of non-configurable readonly properties.
"var undefined = 5" doesn't change the value of undefined evaluated in global scope, right?
If browsers use the same semantic mechanisms a libraries (or the implementation level equivalent) then they will behave identically.
What browsers are doing here is defining non-configurable accessor (or readonly value, for some browsers) properties on the global. As far as I can tell, "var" and "function" can't mess with such properties today.
Don't forget the implications for the future. If declaring certain names in top level scopes is an error if they are also present in the global or host environment, it means that ES7+ can never define new names on the global (or HTML6+ can never define new names in the host environment) without risking hard errors in existing code.
On Oct 29, 2014, at 4:03 PM, Boris Zbarsky <bzbarsky at mit.edu> wrote:
"var undefined = 5" doesn't change the value of undefined evaluated in global scope, right?
Prior to the ES5 spec. it actually did, as undefined was writable.
What browsers are doing here is defining non-configurable accessor (or readonly value, for some browsers) properties on the global. As far as I can tell, "var" and "function" can't mess with such properties today.
It’s a bit more complicated than that. See ecmascript#78 which is a bug against ES5.1 is addressed in the ES6 spec. Also see the reference firefox bug. I suspect it will jog you memory.
On 10/29/14, 5:44 PM, Allen Wirfs-Brock wrote:
Prior to the ES5 spec. it actually did, as undefined was writable.
Right, but that was a change in the behavior of undefined, not or var.
It’s a bit more complicated than that. See ecmascript#78 which is a bug against ES5.1 is addressed in the ES6 spec. Also see the reference firefox bug. I suspect it will jog you memory.
Sure. The particular cases I'm worried about here ("window", "location", etc) are own props, not props on the proto chain. Trying to make stuff that's up on the proto chain not shadowable is impossible anyway, right? You can always defineProperty on the receiver involved and shadow that way.
On Oct 29, 2014, at 4:13 PM, C. Scott Ananian <ecmascript at cscott.net> wrote:
Don't forget the implications for the future. If declaring certain names in top level scopes is an error if they are also present in the global or host environment, it means that ES7+ can never define new names on the global (or HTML6+ can never define new names in the host environment) without risking hard errors in existing code. --scott
A good point, even though we are only talking about let/const/class declarations. We couldn’t want the possibility that some body has a global let declaration for ‘Foo’ to prevent us (or a host such as the browser) from ever defining a new built-in global named ‘Foo’. (although, I suspect there is an argument to be made that because we now have modules that all future global candidates should instead be exports from built-in modules).
This brings me around to the position that let-like global instantiation should reject let-shadowing of non-configurable property global properties such as ‘undefined’ and ‘window’.
The VarNames list is actually support a different case. The general case is that we down’t allow the same name to be explicitly declared using both a ‘var’ and a ‘let’ (etc.) declaration in any scope, including the global scope. However, we only apply that rule to explicit global var/function declarations. Undeclared global properties are “in the global scope” but are not considered to be var declarations.
On 29 October 2014 22:56, Allen Wirfs-Brock <allen at wirfs-brock.com> wrote:
A good point, even though we are only talking about let/const/class declarations. We couldn’t want the possibility that some body has a global let declaration for ‘Foo’ to prevent us (or a host such as the browser) from ever defining a new built-in global named ‘Foo’. (although, I suspect there is an argument to be made that because we now have modules that all future global candidates should instead be exports from built-in modules).
This brings me around to the position that let-like global instantiation should reject let-shadowing of non-configurable property global properties such as ‘undefined’ and ‘window’.
All good points, I agree.
The VarNames list is actually support a different case. The general case is that we down’t allow the same name to be explicitly declared using both a ‘var’ and a ‘let’ (etc.) declaration in any scope, including the global scope. However, we only apply that rule to explicit global var/function declarations. Undeclared global properties are “in the global scope” but are not considered to be var declarations.
OTOH, var declarations usually result in non-configurable properties anyway, so would already be covered by the above, once we have it. The only exception are var declarations from sloppy eval, but I suppose one could make an argument that it is fine to be able to shadow those.
Andreas Rossberg wrote:
All good points, I agree.
Agreed.
OTOH, var declarations usually result in non-configurable properties anyway, so would already be covered by the above, once we have it. The only exception are var declarations from sloppy eval, but I suppose one could make an argument that it is fine to be able to shadow those.
Right, if there's any sense in that old sloppy-eval-makes-configurable-var from ES1, the sense extends to this corner case.
While polishing some remaining corners of lexical scoping support in V8, we stumbled over the following.
ES5 has the invariant that any identifier not found on the scope chain has to be a property of the global object (except in the presence of 'with' or sloppy direct eval), or be a reference error. In particular, this guarantees that the compiler usually knows that 'undefined' is bound to the undefined value, and similarly for other frozen properties of the global object.
In ES6, this no longer seems to be the case, due to the mutable toplevel lexical scope. At least I wasn't able to find language in the draft that would make the following example illegal:
Is that correct?
(I would have expected the VarNames list of the initial global environment to contain all global names from Sec. 18, but that doesn't seem to be the case.)
If so, how do we fix this? Allowing shadowing after the fact is pretty bad, since it will probably make all accesses to builtin globals slower in ES6. But it is particularly bad for 'undefined', where the ability to rebind would break various assumptions and optimisations based on its immutability.