[10] [JSC] StringAt should respect arrayMode in CSE
Rated High because the diff fixes an unsound CSE on
String.prototype.at: the GCSE key omittedarrayMode, allowing the optimizer to merge an in-bounds (returnsString) computation with an OOB (returnsundefined) computation — a type confusion at JIT-IR level reachable from web JS.
StringAt fell through to StringCharAt's def(PureValue(node)) clause. charAt is type-stable (always returns a string, empty on OOB), but at returns either a string or undefined depending on bounds.
Source/JavaScriptCore/dfg/DFGClobberize.h
Patch Details
StringAt gets its own case calling def(PureValue(node, node->arrayMode().asWord())), folding ArrayMode into the CSE key so nodes with different array modes are no longer merged.
CSE key omits a mode discriminator that controls the operation's return type, allowing the optimizer to merge two computations with incompatible output-type contracts.
Background
clobberize() produces read/write/def summaries that drive CSE for pure nodes. PureValue is the CSE key for side-effect-free nodes; by default it hashes (opcode, child edges), but an extra discriminator can be folded in via PureValue(node, word). ArrayMode is the DFG's per-node specialisation of an indexed operation, derived from per-bytecode array profiles. String.prototype.at(i) returns the code unit string at i if in range and undefined otherwise — distinct from charAt, which returns the empty string for OOB.
Analysis
Two s.at(i) calls in the same function can be specialised differently — one as String in-bounds, another as Generic/OOB-observing. With the shared PureValue(node) key omitting ArrayMode, CSE happily merged a mode-A StringAt with a mode-B StringAt using the same string/index inputs.
The surviving node's specialised lowering (and the type abstract-interpreted by downstream nodes) was then applied to a usage expecting the other mode's return-type contract — a String-vs-Undefined type confusion. The regression test warms up opt("hello", 1) 20000 times (always in-bounds), then calls opt("hello", 100) 200 times (always OOB) under --jitPolicyScale=0.1. A value typed as String by JIT tracking but runtime-tagged as Undefined is the standard DFG type-confusion precursor.
This vulnerability weakens DFG's type-system invariant inside the WebContent process: two CSE-merged nodes are supposed to share output type, and they didn't.
Audit directions
def(PureValue(node))calls inDFGClobberize.hwhere the node has anarrayMode(),arithMode(),typedArrayType(), or similar variant selector. Grepdef(PureValue(node))and cross-reference each opcode againstDFGNode.h.- Pairs of related opcodes sharing a fallthrough case.
StringAt/StringCharAt,GetByVal/GetByValMegamorphic, atomics families. Check whether grouped nodes share output-type contracts across all modes. .at-style methods. Review DFG intrinsics forArray.prototype.at,TypedArray.prototype.at,String.prototype.atand confirm CSE keys distinguish in-bounds vs OOB return types.- AbstractInterpreter type predictions assuming CSE-merged nodes share an output type. Audit
DFGAbstractInterpreterInlines.hforStringAtand any node whose forecast depends onarrayMode().