Devtools go into infinite loop after creating cyclic prototype chain and examining a specific object in it
Categories
(DevTools :: Console, defect, P2)
Tracking
(Not tracked)
People
(Reporter: bzbarsky, Assigned: jimb)
References
Details
STEPS TO REPRODUCE:
- Load any web page.
- Evaluate:
in the console.Location.prototype.__proto__ = { __proto__: location}
EXPECTED RESULTS: Get a cyclic prototype chain, but no infinite loop.
ACTUAL RESULTS: Infinite loop in devtools code. The tab no longer responds to clicks, can't be navigated via the URL bar, etc. The infinite loop is not even detectable by the slow script dialog, afaict (though maybe that's because it's system code and I have that turned off in my profile; I don't recall for sure).
I tried to experiment a bit more by putting that line in a web page (in a <script> tag) and then evaluating some things in the console. When I evaluate Location.prototype.__proto__
I get InternalError: too much recursion
. When I evaluate Location.prototype
I get the infinite loop.
There are proposals to make this particular cyclic prototype chain impossible (see https://github.com/heycam/webidl/pull/606), but I'm not sure whether this can happen in some other cases where cyclic prototype chains would still be allowed (any case that has a non-ordinary [[GetPrototypeOf]]).
Jim, do you have any idea who could look into this?
Assignee | ||
Comment 3•6 years ago
|
||
I believe that SpiderMonkey in general assumes that prototype chains are acyclic. There seems to be code to prevent the creation of such changes:
/*
* ES6 9.1.2 step 6 forbids generating cyclical prototype chains. But we
* have to do this comparison on the observable WindowProxy, not on the
* possibly-Window object we're setting the proto on.
*/
RootedObject objMaybeWindowProxy(cx, ToWindowProxyIfWindow(obj));
RootedObject obj2(cx, proto);
while (obj2) {
MOZ_ASSERT(!IsWindow(obj2));
if (obj2 == objMaybeWindowProxy) {
return result.fail(JSMSG_CANT_SET_PROTO_CYCLE);
}
bool isOrdinary;
if (!GetPrototypeIfOrdinary(cx, obj2, &isOrdinary, &obj2)) {
return false;
}
if (!isOrdinary) {
break;
}
}
So the first question is, why isn't this triggering?
Reporter | ||
Comment 4•6 years ago
|
||
So the first question is, why isn't this triggering?
ES6 9.1.2 is the ordinary [[SetPrototypeOf]]
internal method. It does cycle prevention in https://tc39.github.io/ecma262/#sec-ordinarysetprototypeof step 8.c, but only when all the objects involved have the ordinary [[GetPrototypeOf]]
method.
Which means there is no cycle protection once objects have a non-ordinary [[GetPrototypeOf]], per spec. That includes the "location" object in Gecko at the moment (which also per spec has a non-ordinary [[GetPrototypeOf]], but also various other objects (WindowProxy, all Proxy instances, etc, etc). That's the break
in the !isOrdinary
case.
I haven't tried writing a similar testcase with a Proxy instance; maybe devtools knows to not trust those in situations like this...
There are spec proposals to add the cycle-checking for "location" somehow; that's where the testcase came from. There are also proposals to make Location.prototype
an immutable prototype object in the sense of https://tc39.github.io/ecma262/#sec-immutable-prototype-exotic-objects which would also fix this specific testcase.
Assignee | ||
Comment 5•6 years ago
|
||
The loop is in that perennial favorite, _findSafeGetterValues in devtools/server/actors/object.js.
Assignee | ||
Comment 6•6 years ago
|
||
... and other places as well
Comment hidden (off-topic) |
Comment 8•2 years ago
|
||
Sorry, there was a problem with the detection of inactive users. I'm reverting the change.
Updated•2 years ago
|
Description
•