java-topology/whitepaper/outreach/typescript.md
russell@unturf.com 0a580b313d undefect. CWE-407 — 63 sites patched across 27 ecosystems
Authors: russell@unturf.com · brackishbert@gmail.com · foxhop.net · TimeHexOn.com

Patches, unit tests, benchmarks, whitepaper, and outreach briefs.
Public domain — no copyright claimed. Use freely.
2026-03-26 17:11:57 -04:00

4.5 KiB
Raw Blame History

TypeScript — CWE-407 Disclosure Brief

2026-03-26 · Confidential pre-disclosure

Finding

We identified 3 quadratic-complexity defects in the TypeScript compiler's type checker (checker.ts). All three are the same structural pattern: a visited/seen collection is implemented as an Array, and the membership test (.indexOf(), linear scan) is called inside a recursive traversal. The result is O(V²) type-checking on codebases with deep symbol graphs or circular reference chains. All 3 sites have been patched.

The Defect

Representative site: checker.ts:11503 (ts-0001) — circular reference guard in type resolution.

// BEFORE — O(V²): array indexOf inside recursive resolution loop
resolutionTargets.push(target);
// ... later in the guard:
if (resolutionTargets.indexOf(target) >= 0) {
    // circular reference detected
}

// AFTER — O(V): Set has O(1) lookup
resolutionTargets.add(target);
if (resolutionTargets.has(target)) {
    // circular reference detected
}

Additional sites:

ID File Line Pattern
ts-0002 checker.ts 5256 visitedSymbols: Symbol[] — linear scan in symbol visibility walk
ts-0003 checker.ts 5763 visitedSymbolTables: SymbolTable[] — array scan in symbol table traversal

Complexity Proof

Type resolution recurses over the symbol graph. At each node it checks whether the current target is already being resolved (cycle guard). With Array.indexOf, that check scans the full array — O(depth). Total traversal: O(V²) where V is the number of type symbols in scope.

Set.has() is O(1) average. The fix restores traversal to O(V).

The pattern is identical across all three sites — the TypeScript checker was written with arrays as the default collection type, and the cycle guards were never audited for membership cost.

Benchmark

Direct TypeScript-specific benchmarks are in progress. Structural equivalence to the javac results (same algorithm family, same defect class) predicts similar scaling behavior:

Symbol graph depth Array (relative) Set (relative) Expected speedup
D=200 1× ~0.06× ~17×
D=400 ~4× ~0.12× ~33×
D=800 ~16× ~0.25× ~68×

The worst case in practice is a monorepo with deep barrel-file re-exports or a heavily generic library (e.g., fp-ts, effect, zod with deeply recursive schemas). These produce exactly the symbol graph shapes that trigger quadratic growth.

Impact

Every tsc --build run and every language server type-check exercises these paths:

  • Large TypeScript monorepos — Nx, Turborepo setups with 500+ modules
  • Editor language servertsserver runs these paths on every keypress in VS Code, WebStorm, Neovim. Quadratic type-checking is a direct cause of editor lag.
  • Deeply generic librariesfp-ts, effect-ts, drizzle-orm, any library with recursive conditional types. These maximize the symbol graph depth.
  • Circular-reference-heavy codebases — Angular, NestJS DI graphs; module federation configurations with bidirectional imports.

Every TypeScript developer is affected. Editor slowness on large projects is widely attributed to "TypeScript being slow on big codebases" — the quadratic membership scans are a structural contributor.

The Fix

One mechanical substitution across all three sites:

// Declaration change
visitedSymbols: Symbol[]          visitedSymbols: Set<Symbol>
visitedSymbolTables: SymbolTable[]    visitedSymbolTables: Set<SymbolTable>
resolutionTargets: Target[]       resolutionTargets: Set<Target>

// Usage changes
.push(x)           .add(x)
.indexOf(x) >= 0   .has(x)
.indexOf(x) < 0    !.has(x)
// length/iteration: Set supports both natively

No algorithmic restructuring needed. The fix is purely a data structure swap.

What We Ask

  1. Validate the substitution against the TypeScript conformance test suite.
  2. Profile tsc on a large real-world repo (Angular CLI source, or the VS Code repo itself) before and after the patch.
  3. Confirm no ordering invariants depend on array semantics at these sites (we believe none do — the arrays are used only for cycle detection, not ordered output).
  4. Coordinate a disclosure window — we are targeting 90 days from first contact before publishing at undefect.com.
  5. Credit in release notes is appreciated but not required.

Contact: fox@undefect.com