DFG Spread(SetObjectUse) side-effect modeling
Source/JavaScriptCore/dfg/DFGAbstractInterpreterInlines.h
The DFG abstract interpreter assigns abstract states to values during JIT compilation, including "structure proofs" that let it eliminate CheckStructure guards. didFoldClobberWorld() signals an operation is side-effect-free and proofs survive; clobberWorld() invalidates all structure proofs. When 313031@main allowed Set spreads to invoke user-defined Symbol.iterator functions via operationSpreadSet (which can run arbitrary JS), the unconditional didFoldClobberWorld() became unsound: a user iterator executing during [...set] can redefine properties on other live objects.
This patch gates the fold on whether the operand is proven to carry the original Set structure. If yes, the fast path (no iterator) preserves proofs; otherwise clobberWorld() fires.
Significance
Incorrect side-effect attribution caused CheckStructure nodes to be folded away, allowing GetByOffset to read stale property slots after an iterator-induced structure transition — producing a type confusion primitive in JIT-compiled code.
Warmup: compiler infers set has originalSetStructure
CheckStructure(o, S_double)
Spread(set, SetObjectUse) → AI: didFoldClobberWorld() // proof for o survives
GetByOffset(o, slot 0, Double) // CheckStructure folded away
Exploit:
iterator: Object.defineProperty(obj, 'a', {get(){return 1}})
→ obj transitions S_double → S_accessor
GetByOffset reads slot 0 as Double on S_accessor → type confusion
Audit directions
- Bottom-structured operands hitting the fast path. The fold uses
isSubsetOfagainst a singleton set. If the abstract value's structure set is empty (Bottom, nominally unreachable),isSubsetOfvacuously returns true, settingcanFold=true. Verify whether a Bottom-structuredSetObjectUsecan reach this code. - Prototype-change watchpoints. The fast-path safety assumes mutations to
Set.prototype[Symbol.iterator]are covered by watchpoints installed at compile time. Audit whether those watchpoints are actually emitted forSetand cover all invalidation paths (Reflect.definePropertyon the prototype, a Proxy intercepting the lookup). - Other collection UseKinds with new slow paths. Grep for
didFoldClobberWorld()calls onMapObjectUse,WeakSetObjectUse,WeakMapObjectUseand confirm none have analogous user-iterator slow paths introduced after their AI modeling was written. - Concurrent JIT consistency. The test pins
--useConcurrentJIT=0; confirm the concurrent compiler's copy of this logic was updated in lockstep and cannot observe acanFolddecision based on a concurrently-mutating structure.