[JSC] Incorrect side-effect modeling for Spread(SetObjectUse)
Set spread was proven side-effect-free by a comment, not by a check
Component: JSC DFG and FTL JIT | fb8bfea
Source/JavaScriptCore/dfg/DFGAbstractInterpreterInlines.h
The DFG abstract interpreter tracks structure proofs — compile-time guarantees that an object's hidden class has not changed — so it can elide redundant CheckStructure nodes and safely fold subsequent property accesses like GetByOffset. That optimization is only sound if no side-effecting code runs between the proof and its use. Spread(SetObjectUse) was assumed side-effect-free because JSC normally spreads Sets by iterating internal storage directly, bypassing the JS iterator protocol. The patch replaces that blanket assumption with a structure-subset test: the fold is now permitted only when the operand's proven structure is a subset of the global object's original Set structure, which guarantees no own Symbol.iterator and is watchpoint-protected; otherwise the node clobbers the world and the checks are re-emitted.
Significance
Since 313031@main, Set spreads can invoke a user-defined Symbol.iterator when the fast internal-storage path is unavailable, meaning arbitrary JavaScript — which can reshape objects — can now execute mid-spread. A JIT-compiled function could read a stale property slot after a CheckStructure was wrongly folded away, a type-confusion-shaped read reachable from ordinary script.
Audit directions
This is a textbook stale-structure-proof pattern, and the forward-facing hunt is for its siblings. Narrow: enumerate the other useKind-gated "no side effects" branches in executeEffects and, for each, check whether the fast-path predicate that gates them (the analogue of canDoFastSpread) can fall back to user-observable JS. Wider: the general shape is a fast-path bypass check whose failure branch reaches iterators, valueOf, toString, or proxies without the abstract interpreter being updated to match — audit every DFG node whose lowering has both an internal-storage path and a generic-protocol path. Also worth checking whether the new canFold condition's watchpoint protection on Set.prototype[Symbol.iterator] can be invalidated in ways the structure-subset check does not cover, such as realm/global-object mismatches or concurrent structure transitions during OSR. Match tell in a diff: a didFoldClobberWorld() call whose justification is a comment rather than a runtime or watchpoint-backed predicate.