and it knows how to determine if an object one of those edges points to is a cycle collectable native object by the normal means.
Right, this is the part that I could not figure out where this is happening, if anywhere...
But if I interpret the issue right, it is that we return early from
Ah, because the weakptr thing is not really holding a ref to us, so the only ref is our self-ref while the JS object is alive, and we take that early return?
keep wrappedJS object alive (it doesn't get unlinked)
Meaning that us not running that code keeps it alive during the CC, in the sense that it has a ref that the CC doesn't know about so the CC considers it live?
Yes, this seems right.
(Why proxy code doesn't recreate the (preserved) wrapper?)
Recreate in what sense? One failure mode here is this sequence of events:
- Things get unlinked, wrapper (reflector) that has expandos gets unpreserved.
- The object is brought back to life.
- Someone reads an expando property from it.
- The someone hands the object over to C++, nothing is holding on to the reflector anymore.
- The reflector is GCed.
- The "someone" grabs the object from C++ again (with new reflector), the expando is gone.
For non-proxy objects (for which we don't even have an assert right now), where would we preserve the wrapper in all this? I don't see a place to do it.
For proxies, we could re-preserve the wrapper during step 3, if we discover we have an expando object but are not preserved. But even that would fail if step 3 happens from an IC (see GetPropIRGenerator::tryAttachDOMProxyExpando) because that basically reimplements the relevant codepath bit in jitcode except obviously without the assert in question and without the ability to preserve wrappers.
and it's holding alive its XPCWrappedJS, which has a cyclic reference back to the JS object
The JS object does not have an edge to the XPCWrappedJS from the CC's point of view. The XPCWrappedJS has a self-edge, but it (and the edge from the XPCWrappedJS to the JS object and in fact all of the XPCWrappedJS outgoing edges) are treated specially when the XPCWrappedJS refcount is 1; that's the first link in comment 6. So in fact if the XPCWrappedJS refcount is 1 it does not end up in a cycle because it has no outgoing edges as far as the CC is concerned. Then if the JS object is GCed, we go ahead and kill the XPCWrappedJS in JSObject2WrappedJSMap::UpdateWeakPointersAfterGC.