Closed
Bug 383558
Opened 18 years ago
Closed 18 years ago
Javascript "with" block obscures any "name" variables when the context is a Function object
Categories
(Core :: JavaScript Engine, defect)
Tracking
()
VERIFIED
INVALID
People
(Reporter: kris.kowal, Unassigned)
References
()
Details
User-Agent: Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.8.0.11) Gecko/20070327 Ubuntu/dapper-security Firefox/1.5.0.11
Build Identifier: Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.8.0.11) Gecko/20070327 Ubuntu/dapper-security Firefox/1.5.0.11
If a Javascript author uses a Function object as the argument of a with block, inside that block, evaluating the variable "name" will always return an empty string, regardless of the attributes of the Function object and any variables declared in the block.
Reproducible: Always
Steps to Reproduce:
1. Browse to javascript:with(function () {}) {var name = 10; alert(name === "")}
Actual Results:
alert(true)
Expected Results:
alert(false)
1. install Firefox with Firefox
2. browse to http://cixar.com/javascript/test.html?experiment/withFunction.js
3. observe that the last assertion fails
4. view the unit test source at https://cixar.com/tracs/javascript/browser/experiment/withFunction.js
Updated•18 years ago
|
Assignee: nobody → general
Component: General → JavaScript Engine
Product: Firefox → Core
QA Contact: general → general
Comment 1•18 years ago
|
||
Invalid:
1. functions have a "name" property. For anonymous functions, it's the empty string.
js> uneval((function(){}).name)
""
js> uneval((function a(){}).name)
"a"
2. "var" always declares variables at the function scope, while "with" creates a weird dynamic scope thing. That's what the "with" keyword *does*.
js> with({a:2}) { print(a); }
2
Status: UNCONFIRMED → RESOLVED
Closed: 18 years ago
Resolution: --- → INVALID
Comment 2•18 years ago
|
||
Do note that the name property isn't specified by ECMA-262 (it's a Mozilla JS extension), so you might not see this in other browsers. It's merely one hazard of the scary world of JavaScript's |with|.
Status: RESOLVED → VERIFIED
| Reporter | ||
Comment 3•18 years ago
|
||
@Jesse
That's not the problem. The problem is that you cannot override the name property in the scope of that block:
with (function () {}) {
var name = 10; /* ignored */
assert(name == 10); /* fails */
assert(name == ""); /* still? why? */
}
No matter how deeply you push closures onto the stack, inside this with context, the variables with the name "name" are unusable.
This behavior should work in ECMA.
Status: VERIFIED → UNCONFIRMED
Resolution: INVALID → ---
| Reporter | ||
Comment 4•18 years ago
|
||
Whoa whoa whoa.... Sorry. Operative words: "var", and "Function scope". I'll reread that part of ECMA, but I suspect that it might word that the var assignment clause as topmost scope, rather than Function scope, whether or not the variable is declared on the top. Will look again.
| Reporter | ||
Comment 5•18 years ago
|
||
Consider:
javascript:(function () {var a = 10; with ({}) { var a = 20; alert(a) } })();
var a = 10;
with ({}) {
var a = 20;
assert(a == 20); /* passes */
}
Page 63 of the spec: http://www.ecma-international.org/publications/files/ECMA-ST/Ecma-262.pdf, just says "evaluate identifier as described in 11.1.2", which implies to me that the variable identifier should be evaluated in the topmost scope, not the function scope. In any case, that's the behavior that simpletons like me would expect. I understand this behavior as independent of whether the variable needs to be added to the function scope first.
Looking deeper, on page 62, "A variable with an Initialiser is assigned the value of its Assignment Expression when the variable is is executed, not when the variable is created." That leaves ambiguous whether the identifier expression is evaluated in the topmost scope or the function scope, but leans toward execution time.
Your call, but for now you've given me a workaround that should work in any browser:
with (function () {}) {
(function () {
var name = 10;
assert(name == 10); /* works fine */
})();
}
I'll need this to work the kinks out of my module loader and evalJail system.
Thanks.
Comment 6•18 years ago
|
||
(In reply to comment #5)
> var a = 10;
> with ({}) {
> var a = 20;
> assert(a == 20); /* passes */
> }
This works because the second variable declaration is ignored (it has no work to do) and the assignment is still processed as per 12.2.
> Page 63 of the spec:
> http://www.ecma-international.org/publications/files/ECMA-ST/Ecma-262.pdf, just
> says "evaluate identifier as described in 11.1.2", which implies to me that the
> variable identifier should be evaluated in the topmost scope, not the function
> scope.
Ah, but read the Description on page 62 (the beginning of section 12.2):
If the variable statement occurs inside a FunctionDeclaration, the
variables are defined with function-local scope in that function, as
described in s10.1.3.
> In any case, that's the behavior that simpletons like me would expect.
> I understand this behavior as independent of whether the variable needs to be
> added to the function scope first.
This was the reason for the advent of 'let'. For more information, see http://developer.mozilla.org/en/docs/New_in_JavaScript_1.7#Block_scope_with_let .
> Your call, but for now you've given me a workaround that should work in any
> browser:
This is indeed the property workaround. let would be your other alternative, though as you'll no doubt note, is not supported across all browsers.
Status: UNCONFIRMED → RESOLVED
Closed: 18 years ago → 18 years ago
Resolution: --- → INVALID
Comment 7•18 years ago
|
||
(In reply to comment #6)
> This is indeed the property workaround. let would be your other alternative,
Of course, this was supposed to be "proper."
| Reporter | ||
Comment 8•18 years ago
|
||
Consider: javascript:(function () {var a = 10; with ({'a': 20}) {var a = 30; alert(a)}; alert(a)})()
(function () {
var a = 10;
with ({'a': 20}) {
var a = 30;
alert(a);
}
alert(a);
})()
Assuming that assignment is bound to a variable identifier evaluated in function scope, I would expect:
alert(30)
alert(30)
However, I get:
alert(30);
alert(10);
Which signifies to me that in this case the innermost "a" declaration is not ignored per spec.
I'm left to assume that the current Javascript is not consistent with your interpretation of the ECMA specification, nor my original interpretation of the spec, but rather a strange hybrid.
Comment 9•18 years ago
|
||
alerts showing 30 and 10 are the correct behavior. Please re-read Blake's first paragraph in comment 6, don't let the rest of the comment mislead. The var a = 30 in the with does not cause the assignment a = 30 to mutate the local var a = 10.
Instead the var in var a = 30 in the with body has no effect because there is already a property named 'a' in the variable object (see ECMA-262 10.1.3, "Variable Instantiation", specifically "If there is already a property of the variable object with the name of a declared variable, the value of the property and its attributes are not changed").
Then after entering the function's execution context, when control flow reaches the var a = 30; statement, it is evaluated according to 12.2, which evaluates the identifier according to 11.1.2 as you already noted in comment 5. That looks for 'a' in the scope chain, and finds it in the head object pushed by |with|.
Note that ECMA-262 Edition 3 Chapter 16 "Errors" allows implementations to add properties, such as function objects' name properties. So the problem here is the |with| statement, which should be avoided.
/be
/be
Status: RESOLVED → VERIFIED
| Reporter | ||
Comment 10•18 years ago
|
||
Brendan:
> Please re-read Blake's first
> paragraph in comment 6, don't let the rest of the comment mislead. The var a =
> 30 in the with does not cause the assignment a = 30 to mutate the local var a =
> 10.
Blake:
> This works because the second variable declaration is ignored (it has no work
> to do) and the assignment is still processed as per 12.2.
"and the assignment is still processed". The question is whether the identifier is evaluated at the top of the scope chain (per 12.2 page 63) or the topmost function scope (per 12.2 page 62). I would beg this to be an ambiguity of the specification, and separately an inconsistency in treatment in Firefox. I would expect these two code fragments to provide the same result:
javascript:(function () {var name = 10; with (function () {}) {var name = 30; alert(name)}; alert(name)})()
javascript:(function () {var name = 10; with ({'name': ''}) {var name = 30; alert(name)}; alert(name)})()
or, formatted:
var name = 10;
with (function () {}) {
var name = 30;
alert(name);
}
alert(name);
versus:
var name = 10;
with ({'name': ''}) {
var name = 30;
alert(name);
}
alert(name);
and their results are respectively:
alert(""); alert(10);
alert(30); alert(10);
The important difference is the bottom left alert. With an object as its argument instead of a function, with appears to push a new scope. The first code fragment seems to indicate that the "var" is ignored, as Brendan suggests, and the second code fragment seems to indicate that Objects, unlike Functions, push a new block scope and the assignment applies to it, which is all around incorrect.
Meanwhile, and completely irrelevant, Safari has a different read of the spec, even bearing in mind that annonymous functions don't have a name member:
alert(30); alert(30);
alert(""); alert(30);
Ignore the difference in the first alerts. The second alerts reveal that the Safari folks think that the inner declaration and assignment apply to the function block scope version of "name".
Comment 11•18 years ago
|
||
(In reply to comment #10)
> Blake:
> > This works because the second variable declaration is ignored (it has no work
> > to do) and the assignment is still processed as per 12.2.
>
> "and the assignment is still processed". The question is whether the
> identifier is evaluated at the top of the scope chain (per 12.2 page 63) or the
> topmost function scope (per 12.2 page 62).
What spec are you looking at? E262-3.pdf from http://www.mozilla.org/js/language is the final Edition 3 that went to ISO. The one at http://www.ecma-international.org/publications/files/ecma-st/ECMA-262.pdf is older (December 1999) and has different page numbers. I'll bug Ecma about this.
> I would beg this to be an ambiguity of the specification,
Sorry, no: the spec is unambiguous. See 11.1.2:
11.1.2 Identifier Reference
An Identifier is evaluated using the scoping rules stated in section 10.1.4. The result of evaluating an Identifier is always a value of type Reference.
10.1.4 Scope Chain and Identifier Resolution
Every execution context has associated with it a scope chain. A scope chain is a list of objects that are searched when evaluating an Identifier. When control enters an execution context, a scope chain is created and populated with an initial set of objects, depending on the type of code. During execution within an execution context, the scope chain of the execution context is affected only by with statements (section 12.10) and catch clauses (section 12.14).
During execution, the syntactic production PrimaryExpression : Identifier is evaluated using the following algorithm:
1. Get the next object in the scope chain. If there isn't one, go to step 5.
2. Call the [[HasProperty]] method of Result(1), passing the Identifier as the property.
3. If Result(2) is true, return a value of type Reference whose base object is Result(1) and whose property name is the Identifier.
4. Go to step 1.
5. Return a value of type Reference whose base object is null and whose property name is the Identifier.
The result of evaluating an identifier is always a value of type Reference with its member name component equal to the identifier string.
---- end citations ----
Since the identifier is evaluated inside the |with| body, the scope chain starts with the object named by the |with|'s head.
> and separately an inconsistency in treatment in Firefox.
Compared to what other browser? Please compare apples to apples: If you mean mean because Firefox has a name property of function objects, that has nothing to do with any of these spec sections. See comment 9, final paragraph.
> I would expect these two code fragments to provide the same result:
[snip]
> var name = 10;
> with (function () {}) {
> var name = 30;
> alert(name);
> }
> alert(name);
>
> versus:
>
> var name = 10;
> with ({'name': ''}) {
> var name = 30;
> alert(name);
> }
> alert(name);
>
> and their results are respectively:
>
> alert(""); alert(10);
> alert(30); alert(10);
>
> The important difference is the bottom left alert. With an object as its
> argument instead of a function, with appears to push a new scope. The first
> code fragment seems to indicate that the "var" is ignored, as Brendan suggests,
> and the second code fragment seems to indicate that Objects, unlike Functions,
> push a new block scope and the assignment applies to it, which is all around
> incorrect.
This is confused on several counts:
1. with always pushes an object on the front (head) of the scope chain, period. It doesn't matter whether the object is a function or not.
2. The second var is always ineffective at *creating a new property in the variable object on entering an execution context*, since that happened already due to the first var. So the second var is always "ignored" as far as effects on the variable object go.
3. The scope chain resolution of the identifier 'name' works per the ECMA spec.
4. You get "" from the alert for the function case because that function is anonymous, so its name property contains the empty string. Since the property is read-only, your attempt to set it to 30 silently fails (per ECMA-262) and the alert prints the original and only value, "".
> Meanwhile, and completely irrelevant, Safari has a different read of the spec,
> even bearing in mind that annonymous functions don't have a name member:
>
> alert(30); alert(30);
> alert(""); alert(30);
>
> Ignore the difference in the first alerts. The second alerts reveal that the
> Safari folks think that the inner declaration and assignment apply to the
> function block scope version of "name".
There is no "function block scope version of 'name'". Safari, or rather JavaScriptCore, appears to have an overt bug here. Please feel free to cc: me if you report it to their public trac or bugzilla (whichever they're using these days) at webkit.org
/be
| Reporter | ||
Comment 12•18 years ago
|
||
Brendan in comment
> 2. The second var is always ineffective at *creating a new property in the
> variable object on entering an execution context*, since that happened already
> due to the first var. So the second var is always "ignored" as far as effects
> on the variable object go.
Then why:
var name = 10;
with ({'name': ''}) {
var name = 30; /* is supposedly ignored */
alert(name); /* alerts 30, so clearly the previous line WAS NOT ignored */
}
When:
var name = 10;
with (function () {}) {
var name = 30; /* is supposedly ignored */
alert(name); /* alerts "", so apparently the previous line WAS ignored this time */
}
I gather, from reviewing this conversation and finding that my initial interpretation of the specification regarding var declarations and initializations was incorrect, that the latter behavior is correct, and the former is a bug.
So, my meaning is that there is an internal inconsistency between Firefox and Firefox. Very much Apples and Apples. Well, I suppose Oranges in Oranges since the JavascriptCore Webkit bug is more of Apples and Apples. Har har.
Let me know if I should just shut up on this one. I exaggerate its importance. I've enjoyed the conversation in any case.
Comment 13•18 years ago
|
||
(In reply to comment #12)
> Brendan in comment
> > 2. The second var is always ineffective at *creating a new property in the
> > variable object on entering an execution context*, since that happened already
> > due to the first var. So the second var is always "ignored" as far as effects
> > on the variable object go.
>
> Then why:
>
> var name = 10;
> with ({'name': ''}) {
> var name = 30; /* is supposedly ignored */
> alert(name); /* alerts 30, so clearly the previous line WAS NOT ignored */
> }
You are misreading my "ignored" -- the |var| is ignored as far as its effect on the variable object -- the name = 30 is *not* ignored, as I have said several times in this bug, citing spec sections explicitly. Please re-read them.
> When:
>
> var name = 10;
> with (function () {}) {
> var name = 30; /* is supposedly ignored */
> alert(name); /* alerts "", so apparently the previous line WAS ignored this
> time */
> }
Did you read what I just wrote in my previous comment about function objects' name properties being *read-only*?
/be
Comment 14•18 years ago
|
||
See ECMA-262 8.6.2.2 and .3:
8.6.2.2 [[Put]] (P, V)
When the [[Put]] method of O is called with property P and value V, the following steps are taken:
1. Call the [[CanPut]] method of O with name P.
2. If Result(1) is false, return.
3. If O doesn’t have a property with name P, go to step 6.
4. Set the value of the property to V. The attributes of the property are not changed.
5. Return.
6. Create a property with name P, set its value to V and give it empty attributes.
7. Return.
Note, however, that if O is an Array object, it has a more elaborate [[Put]] method (section 15.4.5.1).
8.6.2.3 [[CanPut]] (P)
The [[CanPut]] method is used only by the [[Put]] method.
When the [[CanPut]] method of O is called with property P, the following steps are taken:
1. If O doesn’t have a property with name P, go to step 4.
2. If the property has the ReadOnly attribute, return false.
3. Return true.
4. If the [[Prototype]] of O is null, return true.
5. Call the [[CanPut]] method of [[Prototype]] of O with property name P.
6. Return Result(5).
which say that ReadOnly properties cannot be mutated, and trying to set them seems to succeed but has no effect. This is a botch from Edition 1, but it's set in stone. So again, just applying the unambiguous spec, the |var| in the |with| is ineffective in creating a new property (because one was already created for the first |var| in the program) and the |name = 30| part of the var statement tries to mutate the ReadOnly 'name' property of the anonymous function, failing silently to change it from its "" value.
Clear now?
/be
| Reporter | ||
Comment 15•18 years ago
|
||
Ah, aye. Missed that bit about read-only causing the silent failure. I filed a radar internally against Safari a couple months ago; I will reference this conversation and let you know if the issue sees the light of day. I'll also blog the closure workaround. Thanks for your time.
You need to log in
before you can comment on or make changes to this bug.
Description
•