archives

« Bugzilla Issues Index

#78 — global declarations and preexisting inherited global properties


From https://mail.mozilla.org/pipermail/es5-discuss/2011-January/003882.html

In an previous thread (https://mail.mozilla.org/pipermail/es5-discuss/2010-July/003606.html), issues related to processing global function declarations whose name is the same as an already defined property of the global object were discussed. The result was a change to the ES5 Declaration Binding Instantiations section 10.5 steps 5.e-5.f that have been incorporated into the ES 5.1 spec.

Those changes were motivated mostly by issues related to what happens if the preexisting property is not configurable or if it is a setter. In general, we tried to preserve ES3 semantics except where it directly clashed with new ES5 features.

One issue we didn't explicitly deal with in the previous discussion was the handling for preexisting global properties that are inherited properties rather than own properties of the global objects. That is the topic of this post. (Also see https://bugzilla.mozilla.org/show_bug.cgi?id=624364 )

More concretely, how should ECMAScript deal with the following situation in an implementation where the global object inherits from Object.prototype:

<script type="text/javascript">
Object.defineProperty(Object.prototype,"a", {writable: false, configurable: false});
Object.defineProperty(Object.prototype,"b", {writable: false, configurable: false});
Object.defineProperty(Object.prototype,"c", {writable: true, configurable: false});
</script>
<script type="text/javascript">
function a() {}
</script>
<script type="text/javascript">
var b="foo";
</script>
<script type="text/javascript">
var c="bar";
</script>

Note that multiple script blocks are shown to indicate that they are sequentially processed as independent ECMAScript Programs. According to the ES5.1 spec. The function declaration will throw a TypeError because the the inherited property "a" is not writable. The declaration of b does not create a own property of the global object (because the a inherited property already exists) and its initialization assignment will silently do nothing (default action for a [[Put]] to an inherited non-writable property. The declaration of c also does not directly create an own property of the global object; however, the initialization assignment does create such an own property whose [[Configurable]] attribute is always true even though var declaration (outside of evals) create properties whose [[Configurable]] attribute is false.

The root of the problem is that the Object Environment Records treat inherited properties as defined bindings and the 10.5 algorithm does not create new bindings when one does already exist. This is arguably consistent with ES3 which does not distinguishes between own and inherited properties in its variable instantiation spec. However, in ES3 it wasn't an important distinction as without user configurable attributes it was hard to observe the difference.

Before describing, the specification fix it's probably a good idea to define the actual desire behavior:

1) "variable" accesses that bind to inherited properties of the global object should return the current value of the inherited property.
2) "variable" assignments to inherited properties of the global object should be equivalent to a [[Put]] to the global object. Whether or not a own property is created depends upon the [[Writable]] attribute of the inherited property and the extensible internal property of the global object.

The ES5.1 spec. correctly defines the above two behaviors

3) global function and var declarations always create own properties of the global object. If an inherited property of the same name already exists it is over-ridden with an own property.
4) The declaration instantiation rules relating to pre-existing bindings are only consider own properties of the global object. Inherited properties of the global object have no effect upon the processing of function and var declarations.

The ES5.1 spec. does not correctly define behaviors 3&4.

The fixes:

Function declarations are handled by step 5 of 10.5. The ES5.1 spec. is:

5. For each FunctionDeclaration f in code, in source text order do
a. Let fn be the Identifier in FunctionDeclaration f.
b. Let fo be the result of instantiating FunctionDeclaration f as described in Clause 13.
c. Let funcAlreadyDeclared be the result of calling env’s HasBinding concrete method passing fn as the argument.
d. If funcAlreadyDeclared is false, call env’s CreateMutableBinding concrete method passing fn and configurableBindings as the arguments.
e. Else if env is the environment record component of the global environment then
i. Let go be the global object.
ii. Let existingProp be the resulting of calling the [[GetProperty]] internal method of go with argument fn.
iii. If existingProp .[[Configurable]] is true, then
1. Call the [[DefineOwnProperty]] internal method of go, passing fn, Property Descriptor {[[Value]]: undefined, [[Writable]]: true, [[Enumerable]]: true , [[Configurable]]: configurableBindings }, and true as arguments.
iv. Else if IsAccessorDescriptor(existingProp) or existingProp does not have attribute values {[[Writable]]: true, [[Enumerable]]: true}, then
1. Throw a TypeError exception.
f. Call env’s SetMutableBinding concrete method passing fn, fo, and strict as the arguments.

The correction for the ES5.1 spec. is:

5. For each FunctionDeclaration f in code, in source text order do
a. Let fn be the Identifier in FunctionDeclaration f.
b. Let fo be the result of instantiating FunctionDeclaration f as described in Clause 13.
c. Let funcAlreadyDeclared be the result of calling env’s HasBinding concrete method passing fn as the argument.
d. If funcAlreadyDeclared is false, call env’s CreateMutableBinding concrete method passing fn and configurableBindings as the arguments.
e. Else if env is the environment record component of the global environment then
i. Let go be the global object.
ii. Let existingProp be the resulting of calling the [[GetOwnProperty]] internal method of go with argument fn.
iii. If existingProp is undefined or existingProp .[[Configurable]] is true, then
1. Call the [[DefineOwnProperty]] internal method of go, passing fn, Property Descriptor {[[Value]]: undefined, [[Writable]]: true, [[Enumerable]]: true , [[Configurable]]: configurableBindings }, and true as arguments.
iv. Else if IsAccessorDescriptor(existingProp) or existingProp does not have attribute values {[[Writable]]: true, [[Enumerable]]: true}, then
1. Throw a TypeError exception.
f. Call env’s SetMutableBinding concrete method passing fn, fo, and strict as the arguments.

The changes are in lines e.ii and e.iii

Larger changes are needed to handle var declaration in step 8 because special case processing for global declaration similar to what is is step 5 needs to be added. The corrected ES5.1 spec. adds step 8.d and its sub-steps:

8. For each VariableDeclaration and VariableDeclarationNoIn d in code, in source text order do
a. Let dn be the Identifier in d.
b. Let varAlreadyDeclared be the result of calling env’s HasBinding concrete method passing dn as the argument.
c. If varAlreadyDeclared is false, then
i. Call env’s CreateMutableBinding concrete method passing dn and configurableBindings as the arguments.
ii. Call env’s SetMutableBinding concrete method passing dn, undefined, and strict as the arguments.
d. else if env is the environment record component of the global environment then
i. Let go be the global object.
ii. Let existingProp be the resulting of calling the [[GetOwnProperty]] internal method of go with argument fn.
iii. If existingProp is undefined, then
1. Call the [[DefineOwnProperty]] internal method of go, passing dn, Property Descriptor {[[Value]]: undefined, [[Writable]]: true, [[Enumerable]]: true , [[Configurable]]: configurableBindings }, and true as arguments.

An alternative approach to fixes these issues would involve changes to Object Environment Record and possibly the common interface shared by all environment records. Those changes would have broader impact than the above. More work will probably have to be done in the Harmony context where we will be dealing with a wide variety of declarative forms.


set IN_PROGRESS to indicated this should go into ES5.1 Errata.


fix verified at March 2012 meeting.


In the proposed fix, can't all the steps 5.e.i-iv now be collapsed to just:

5.e. Call the [[DefineOwnProperty]] internal method of the global object,
passing fn, Property Descriptor {[[Value]]: undefined,
[[Writable]]: true, [[Enumerable]]: true,
[[Configurable]]: configurableBindings}, and true as arguments.

As far as I can see, all the additional error cases treated by the other steps are already handled by [[DefineOwnProperty]].


As another simplification, one might also consider removing all the special case logic for the global object here and folding it into the spec of CreateMutableBinding, SetMutableBinding, & friends on object environment records (10.2.1.2). Moreover, given that the global environment is the only object environment that can ever be used as a variable environment, there is no need to even distinguish its behaviour.


Ah, scratch the first part of my previous comment. The one additional case the text allows is the existing property being non-configurable, but writable and enumerable already. In that case, it falls back to Set instead of Define. (Not sure I like that special case of a special case, but I suppose it's motivated by some compatibility issue?)

The second half still stands, though.


(In reply to comment #3)
>
> As another simplification, one might also consider removing all the special
> case logic for the global object here and folding it into the spec of
> CreateMutableBinding, SetMutableBinding, & friends on object environment
> records (10.2.1.2). Moreover, given that the global environment is the only
> object environment that can ever be used as a variable environment, there is no
> need to even distinguish its behaviour.

Yes, this was always a possibility. However, since this was initially developed as a "correction" to the ES5 spec. (that needed further correction in ES5.1) I tried to localize the "patch" to a single place.

For ES6 I might do something less hackish, but so far I'm waiting for some consensus on the other global scope issues.


See http://lists.w3.org/Archives/Public/public-webapps/2012JulSep/0392.html for how the fix for this erratum, which SpiderMonkey (and other engines, but their DOM bindings mitigated the effects) implemented ahead of schedule, blew back.

/be


Bulk resolving ES5.1 errata issues as a sampling suggests these are all fixed. If this is in error, please open a new issue on GitHub.