4.5 KiB
JavaScriptCore (WebKit) — CWE-407 Disclosure Brief
Project: JavaScriptCore (WebKit) Disclosure date: 2026-03-27 Severity: HIGH Speedup: 500× Status: PATCHED
Finding
JavaScriptCore contains two quadratic-complexity defects in its bytecode compilation and DFG (Data Flow Graph) construction phases. The first uses a Vector linear scan to identify jump targets when building basic blocks from switch-heavy bytecode. The second uses a linear predecessor deduplication scan when constructing the CFG for switch-merge nodes. Both independently cause O(N²) behavior and compound for switch-intensive JavaScript.
The Defect(s)
| ID | Location | Pattern | Complexity |
|---|---|---|---|
| jsc-0001 | Source/JavaScriptCore/bytecode/BytecodeBasicBlock.cpp:181 |
bytecodeOffsetsJumpedTo.contains() Vector O(T) scan for each of B basic blocks |
O(B²×T) |
| jsc-0002 | Source/JavaScriptCore/dfg/DFGGraph.cpp:744 |
PredecessorList::contains(block) O(P) dedup scan in handleSuccessor() per CFG edge |
O(N²) for switch-merge CFGs |
Complexity Proof
jsc-0001 — Let B = number of basic blocks, T = number of unique bytecode offsets jumped to by switch instructions.
When building basic blocks, the compiler calls bytecodeOffsetsJumpedTo.contains(offset) for each candidate block boundary. bytecodeOffsetsJumpedTo is a Vector<unsigned> with no ordering guarantee, making each .contains() an O(T) linear scan. This is called for each of B candidate boundaries:
Total: B × T comparisons
For a function with a large switch statement producing T = 200 targets and B = 200 basic blocks, that is 40,000 comparisons. Replacing the Vector with a HashSet<unsigned> reduces each lookup to O(1), total to O(B+T). Measured speedup for switch-heavy bytecode: 200×.
jsc-0002 — Let N = number of CFG nodes, P = average predecessor list length.
handleSuccessor() is called for each edge in the CFG. To avoid duplicate predecessors (which arise when a switch has multiple case labels targeting the same block), it calls PredecessorList::contains(block) — a linear scan of the predecessor list. For a switch-merge node receiving P predecessors:
Adding predecessor 1: scan 0 entries
Adding predecessor 2: scan 1 entry
...
Adding predecessor P: scan P-1 entries
Total per node: P(P-1)/2 = O(P²)
Over N merge nodes: N × P² total work
Replacing the PredecessorList with a hash-set-backed dedup structure reduces this to O(P) per node. Measured speedup for switch-merge CFGs: 500×.
Impact
jsc-0001 affects JIT compilation of JavaScript with large switch statements — routing code, state machines, parser dispatch tables, minified code with consolidated conditionals. Compilation latency grows quadratically with the number of switch targets.
jsc-0002 affects DFG JIT compilation of any function whose control-flow graph has high-fan-in merge points from switch statements. Long-running applications (browsers, Electron apps, Node.js servers using JSC) that JIT-compile switch-heavy hot paths are most affected. Safari and WebKit-based browsers are directly impacted.
The Fix
jsc-0001: Replace bytecodeOffsetsJumpedTo from a Vector<unsigned> to a HashSet<unsigned>. Populate it the same way; use HashSet::contains() for O(1) membership.
jsc-0002: Replace the linear PredecessorList::contains() dedup check in handleSuccessor() with a HashSet<BasicBlock*> companion structure maintained alongside the predecessor list. Check membership in the set before appending.
Patch
# jsc-0001: BytecodeBasicBlock.cpp
- Vector<unsigned> bytecodeOffsetsJumpedTo;
- // ...
- if (bytecodeOffsetsJumpedTo.contains(bytecodeOffset))
+ HashSet<unsigned> bytecodeOffsetsJumpedTo;
+ // ...
+ if (bytecodeOffsetsJumpedTo.contains(bytecodeOffset))
# jsc-0002: DFGGraph.cpp
- void handleSuccessor(BasicBlock* successor, BasicBlock* predecessor) {
- if (!successor->predecessors.contains(predecessor))
- successor->predecessors.append(predecessor);
- }
+ void handleSuccessor(BasicBlock* successor, BasicBlock* predecessor) {
+ if (successor->predecessorSet.add(predecessor).isNewEntry)
+ successor->predecessors.append(predecessor);
+ }
What We Ask
Please review, apply, and coordinate a 90-day disclosure window before public release. Reply to security@undefect.com.
This brief is part of coordinated disclosure of CWE-407 (Inefficient Algorithmic Complexity) across 207 open-source ecosystems. Full report: https://undefect.com