← All issues

[5] DFG ValueRepReduction type confusion via MultiGetByOffset

Severity: High | Component: JSC DFG JIT — ValueRepReductionPhase | 460528e

Rated High because the observable effect is a type confusion where a non-numeric JSValue's bit pattern is reinterpreted as an unboxed double in JIT-compiled code, reachable via crafted polymorphic property accesses — the missing per-case validation for MultiGetByOffset constant cases is directly visible in the diff with confidence 0.92.

ValueRepReduction for doubles needs to eagerly convert constants in MultiGetByOffset cases to doubles. This patch escapes MultiGetByOffset constants that cannot be converted to doubles purely.

Source/JavaScriptCore/dfg/DFGValueRepReductionPhase.cpp

case MultiGetByOffset:
case GetByOffset: {
- if (node->child1().useKind() == RealNumberUse || node->child1().useKind() == NumberUse) {
- if (node->child1()->origin.exitOK)
- candidates.add(node->child1().node());
+ if (node->child1().useKind() != RealNumberUse && node->child1().useKind() != NumberUse)
break;
- }
+ if (!node->child1()->origin.exitOK)
+ break;
+ if (node->child1()->op() == MultiGetByOffset) {
+ bool isCandidate = true;
+ MultiGetByOffsetData& data = node->child1()->multiGetByOffsetData();
+ for (unsigned i = 0; i < data.cases.size(); ++i) {
+ GetByOffsetMethod& method = data.cases[i].method();
+ if (method.kind() == GetByOffsetMethod::Constant && !method.constant()->value().toNumberFromPrimitive()) {
+ isCandidate = false;
+ break;
+ }
+ }
+ if (!isCandidate)
+ break;
+ }
+ candidates.add(node->child1().node());
break;
}

The fix adds a validation loop for MultiGetByOffset nodes before marking them as double-conversion candidates. The new code iterates through all MultiGetByOffsetData cases and checks whether each Constant-kind case's value can be purely converted to double via toNumberFromPrimitive(). If any constant case fails this conversion, the node is rejected. Previously, MultiGetByOffset was treated identically to GetByOffset — added as a candidate with no per-case constant validation.

Missing per-case constant-convertibility validation before committing a polymorphic property load to unboxed double representation in DFG.

The DFG ValueRepReductionPhase runs on SSA-form DFG IR and identifies nodes whose output can be converted from boxed JSValue to an unboxed machine representation (double, int52, int32). This avoids boxing/unboxing overhead on hot paths. MultiGetByOffset is a DFG node that represents a polymorphic inline cache — a property access profiled to hit multiple object structures. Each case in a MultiGetByOffset specifies how to load the property: either from an object slot at a fixed offset or as an inline constant (GetByOffsetMethod::Constant). toNumberFromPrimitive() attempts pure numeric conversion of a JSValue — it returns a valid double for numbers and numeric strings but fails for values like Symbols, BigInts, or non-coercible objects.

  JS property access         DFG IR                    ValueRepReduction
  ─────────────────     ──────────────────        ──────────────────────────
  obj.x                 MultiGetByOffset           Is every case safely
  (profiled: 3           ├─ case S1: offset 16     convertible to double?
   structures)           ├─ case S2: Constant(42)    42 → 42.0   ✓
                         └─ case S3: Constant(sym)   Symbol → ✗  REJECT

Before the fix, convertValueRepsToUnboxed<DoubleRepUse>() treated MultiGetByOffset identically to GetByOffset when deciding whether a node could be converted from boxed JSValue to unboxed double. MultiGetByOffset dispatches across multiple structure cases, some of which may return inline constants. When the phase marks such a node as a double candidate, the JIT later eagerly converts the node's output to an unboxed double. If one of the constant cases holds a value that cannot be purely converted to double (e.g., a Symbol or BigInt), the eager double conversion would produce incorrect bit-pattern reinterpretation — the raw JSValue encoding would be treated as a double.

The bug arose because MultiGetByOffset was grouped with GetByOffset in a shared case statement despite having fundamentally different semantics — MultiGetByOffset carries inline constant cases that GetByOffset does not.

🔒

Explores how polymorphic property access interacts with value representation narrowing and what type confusion primitive this could yield

Subscribe to read more

🔒

Multiple audit patterns identified across DFG optimization phases, with concrete search targets for variant discovery

Subscribe to read more