Global Object property resolution is observable with for-in
Categories
(Core :: JavaScript Engine: JIT, defect, P3)
Tracking
()
Tracking | Status | |
---|---|---|
firefox68 | --- | fix-optional |
People
(Reporter: gkw, Unassigned)
References
Details
(Keywords: testcase)
Edit: See updated testcase in comment 7.
undefined;
__proto__[2
.v] = 2;
count = 0;
for (let x in this) {
if (count ===246) {
print("count is: " + count);
}
count++;
}
$ ./js-dbg-64-dm-linux-x86_64-fd1adb9941e9 --fuzzing-safe --no-threads --baseline-eager --no-ion testcase.js
count is: 246
$
$ ./js-dbg-64-dm-linux-x86_64-fd1adb9941e9 --fuzzing-safe --no-threads --no-baseline --no-ion testcase.js
$
Tested this on m-c rev fd1adb9941e9.
My configure flags are:
AR=ar sh ./configure --enable-debug --enable-more-deterministic --with-ccache --enable-gczeal --enable-debug-symbols --disable-tests --disable-cranelift
python3 -u -m funfuzz.js.compile_shell -b "--enable-debug --enable-more-deterministic" -r fd1adb9941e9
I'm setting this as s-s because this came up not long after I made some tweaks (with :decoder's assistance) in lieu of the pwn2own bugs this year, just to be safe.
Running bisection soon...
Comment hidden (obsolete) |
Comment 3•5 years ago
|
||
It is unlikely the regressor. I rather think that this patch changed the number of global objects.
:gkw, what happens if you let it print the count after the for loop instead?
Reporter | ||
Comment 4•5 years ago
|
||
Actually the count printing isn't needed. It's just that it dumps too much stdout, hence why I put it there. I can start another bisection for a testcase without the count stuff:
undefined;
__proto__[2
.v] = 2;
for (let x in this) {
print("FOO");
}
Reporter | ||
Comment 5•5 years ago
|
||
autobisectjs shows this is probably related to the following changeset:
The first bad revision is:
changeset: https://hg.mozilla.org/mozilla-central/rev/51f0acbee7c9
user: Jan de Mooij
date: Wed Jun 14 10:39:07 2017 +0200
summary: Bug 1370608 part 2 - Add JS_NewEnumerateStandardClasses and use it in js/src. r=evilpie
Jan, is bug 1370608 a likely regressor?
Comment 6•5 years ago
|
||
Could this be a conceptual problem in JS_NewEnumerateStandardClasses
, because it doesn't add non-enumerable properties to the visited
set, which means prototype properties with the same name are iterated over in for-in
?
Comment 7•5 years ago
|
||
It looks like that is indeed what is going on.
Here's a cleaned-up testcase:
this.__proto__[undefined] = 3;
for (let x in this) {
if (x === 'undefined') {
print("WRONG");
}
}
|undefined| is defined on the global object as an unenumerable property: https://tc39.github.io/ecma262/#sec-undefined. If we define "undefined" as an enumerable property on the prototype of the global object, we see it in a for-in. This is incorrect according to the spec (https://tc39.github.io/ecma262/#sec-enumerate-object-properties):
Enumerating the properties of the target object includes enumerating properties of its prototype, and the prototype of the prototype, and so on, recursively; but a property of a prototype is not processed if it has the same name as a property that has already been processed by the iterator's next method. The values of [[Enumerable]] attributes are not considered when determining if a property of a prototype object has already been processed.
It looks like we have the same bug with this.proto["Infinity"] and this.proto["NaN"].
Comment 8•5 years ago
|
||
One thing to note in the testcase of Comment 7 is that 'undefined' will trigger the property resolution and skew behaviour. If you use 'void 0' instead, the test even fails on the interpreter (--no-baseline too). Baseline uses short-circuiting in [1] which is why the behaviour appears to be JIT differential.
Comment 9•5 years ago
|
||
Yeah, the newEnumerate Class hook needs a way to return non-enumerable properties. Probably a struct {jsid id, bool enumerable} or two Vectors.
Updated•5 years ago
|
Comment 10•5 years ago
|
||
We already have the enumerableOnly flag. I wonder if the simplest fix is to just call newEnumerate twice for this for-in case.
Comment 11•5 years ago
|
||
(In reply to Ted Campbell [:tcampbell] from comment #10)
We already have the enumerableOnly flag. I wonder if the simplest fix is to just call newEnumerate twice for this for-in case.
Problem is that the global has a ton of lazy properties (in the browser) and this path is somewhat hot, so calling it twice with enumerableOnly true and false just to fill the |visited| set would be unfortunate...
Comment 12•5 years ago
|
||
Let's wait for bug 1534967 to land first to avoid conflicts.
Comment hidden (obsolete) |
Comment 14•5 years ago
|
||
Test in comment 7 still fails with --baseline-eager.
Reporter | ||
Comment 15•5 years ago
|
||
Oops, you're right.
Comment 16•5 years ago
|
||
Clearing the NI. It's a bug we should fix, but it's also pretty obscure and non-trivial to fix so it's not exactly high priority.
Updated•5 years ago
|
Reporter | ||
Comment 17•5 years ago
|
||
(In reply to Jan de Mooij [:jandem] from comment #16)
Clearing the NI. It's a bug we should fix, but it's also pretty obscure and non-trivial to fix so it's not exactly high priority.
May I suggest to bump up this priority? It's not easy to ignore this particular issue involving __proto__
, I found bug 1548063 separately and then had to sieve out a bunch of this issue's dupes to find these other testcases as they all have __proto__
.
Comment 18•5 years ago
|
||
Raising priority to P1, based on Gary's comment.
Jan, can you investigate since you seems to have fixed all other bugs of this category lately.
Updated•4 years ago
|
Comment 20•2 years ago
|
||
I spent some time looking at bug 1764563 before noticing that anba had duped it to this bug.
One thing I noticed was that the bug didn't happen in that case if we had ever resolved the undefined
property on the global (using JS_ResolveStandardClass. In that case, the normal enumeration code sees the unenumerable undefined
property on the global, and adds it to visited
but not props
. When we enumerate Object.prototype
, undefined
is already in visited
, so we filter it out and get the right answer.
The problem happens when, instead of calling GetNameOperation
, we use the optimization in baseline or WarpBuilder to optimize the lookup of undefined/NaN/infinity. (Ted pointed this out in comment 8.)
Is it the case that this only affects undefined
/ NaN
/ Infinity
? How bad would it be to make them normal properties on the global instead of resolving them lazily? Would that fix the problem?
Updated•2 years ago
|
Comment 21•11 months ago
|
||
I took a look at this because of my work in Bug 1833484 which blessed the optimization we had in Baseline right into the parser; so now this isn't a differential fuzzing issue, this is just a straight up resolve hook issue.
While fixing this for NaN
, undefined
and Infinity
is straightforward enough (define these values on the global early as all globals should have them), fixing this for globalThis
is harder. See the comment on GlobalObject::maybeResolveGlobalThis
for some details.
Updated•11 months ago
|
Updated•11 months ago
|
Updated•5 months ago
|
Description
•