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.
191 lines
5.6 KiB
ReStructuredText
191 lines
5.6 KiB
ReStructuredText
TypeScript Compiler (tsc) — CWE-407 Analysis
|
|
============================================
|
|
|
|
.. contents:: :local:
|
|
|
|
Overview
|
|
--------
|
|
|
|
``tsc`` is the TypeScript compiler, which performs type checking, symbol resolution, and
|
|
export-star graph traversal. Three CWE-407 defect sites were found in ``checker.ts``, the
|
|
largest single source file in the compiler (54,000+ lines). **All three are now patched
|
|
in a single 120-line diff** (``defects/typescript/patch/ts-checker-set-visited.patch``).
|
|
The defects affect cycle detection in resolution chains, export-star symbol merging, and
|
|
symbol-table inheritance traversal.
|
|
|
|
Defect Sites
|
|
------------
|
|
|
|
ts-0001
|
|
~~~~~~~
|
|
|
|
**File:** ``src/compiler/checker.ts:11503``
|
|
|
|
**Pattern:**
|
|
|
|
.. code-block:: typescript
|
|
|
|
// Resolution cycle detection — linear scan of resolutionTargets array
|
|
function findResolutionCycleStartIndex(target: object, propertyName: TypeSystemPropertyName): number {
|
|
for (let i = resolutionTargets.length - 1; i >= 0; i--) {
|
|
if (resolutionTargets[i] === target &&
|
|
resolutionPropertyNames[i] === propertyName) {
|
|
return i; // O(depth) linear scan every call
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
**Why this is O(n):** ``resolutionTargets`` is a plain array; finding a target requires scanning
|
|
from the end. Called once per symbol resolution: O(depth²) total for a chain of depth ``depth``.
|
|
|
|
**Complexity:** ``O(depth²)`` where depth = resolution stack depth
|
|
|
|
**Patch:**
|
|
|
|
.. code-block:: typescript
|
|
|
|
// Use a Map<object, Map<TypeSystemPropertyName, number>> for O(1) lookup
|
|
const resolutionTargetIndex = new Map<object, Map<TypeSystemPropertyName, number>>();
|
|
|
|
function findResolutionCycleStartIndex(target: object, propertyName: TypeSystemPropertyName): number {
|
|
return resolutionTargetIndex.get(target)?.get(propertyName) ?? -1;
|
|
}
|
|
|
|
**Data structure change:** ``object[]`` array + linear scan → ``Map<object, Map<...>>``
|
|
|
|
**Status:** **PATCHED** — ``defects/typescript/patch/ts-checker-set-visited.patch``
|
|
|
|
Benchmark Results
|
|
-----------------
|
|
|
|
.. TODO: benchmark pending patch
|
|
|
|
Complexity Proof
|
|
----------------
|
|
|
|
Let depth = maximum resolution stack depth. Each call to ``findResolutionCycleStartIndex``
|
|
scans an array of up to ``depth`` entries: O(depth). With O(depth) calls total during a
|
|
resolution chain: O(depth²). Map lookup is O(1) amortized: O(depth) total. QED.
|
|
|
|
References
|
|
----------
|
|
|
|
* Defect ticket: ``tools/tickets/defects/ts-0001.md``
|
|
|
|
ts-0002
|
|
~~~~~~~
|
|
|
|
**File:** ``src/compiler/checker.ts:5256``
|
|
|
|
**Pattern:**
|
|
|
|
.. code-block:: typescript
|
|
|
|
// Export-star symbol merging — pushIfUnique on visitedSymbols array
|
|
function getExportsOfModuleWorker(moduleSymbol: Symbol): Symbol[] {
|
|
const visitedSymbols: Symbol[] = [];
|
|
// ...
|
|
function visit(symbol: Symbol) {
|
|
if (!pushIfUnique(visitedSymbols, symbol)) return; // O(V) per call
|
|
// ... traverse exports
|
|
}
|
|
}
|
|
|
|
function pushIfUnique<T>(array: T[], toAdd: T): boolean {
|
|
if (array.includes(toAdd)) return false; // O(V) linear scan
|
|
array.push(toAdd);
|
|
return true;
|
|
}
|
|
|
|
**Why this is O(n):** ``array.includes()`` is O(|array|); called once per symbol in the
|
|
export-star graph; total is O(V²) where V = number of exported symbols.
|
|
|
|
**Complexity:** ``O(V²)`` where V = number of symbols in the export-star graph
|
|
|
|
**Patch:**
|
|
|
|
.. code-block:: typescript
|
|
|
|
function getExportsOfModuleWorker(moduleSymbol: Symbol): Symbol[] {
|
|
const visitedSymbols = new Set<Symbol>();
|
|
function visit(symbol: Symbol) {
|
|
if (visitedSymbols.has(symbol)) return; // O(1)
|
|
visitedSymbols.add(symbol);
|
|
// ... traverse exports
|
|
}
|
|
}
|
|
|
|
**Data structure change:** ``Symbol[]`` array + ``pushIfUnique`` → ``Set<Symbol>`` + ``has/add``
|
|
|
|
**Status:** **PATCHED** — ``defects/typescript/patch/ts-checker-set-visited.patch``
|
|
|
|
Benchmark Results
|
|
-----------------
|
|
|
|
.. TODO: benchmark pending patch
|
|
|
|
Complexity Proof
|
|
----------------
|
|
|
|
Let V = number of unique symbols in the export graph. ``pushIfUnique`` scans an array of up to V
|
|
elements: O(V) per call. With V calls total: O(V²). ``Set.has()`` is O(1): O(V) total. QED.
|
|
|
|
References
|
|
----------
|
|
|
|
* Defect ticket: ``tools/tickets/defects/ts-0002.md``
|
|
|
|
ts-0003
|
|
~~~~~~~
|
|
|
|
**File:** ``src/compiler/checker.ts:5763``
|
|
|
|
**Pattern:**
|
|
|
|
.. code-block:: typescript
|
|
|
|
// Symbol-table inheritance traversal — pushIfUnique on visitedSymbolTables
|
|
const visitedSymbolTables: SymbolTable[] = [];
|
|
|
|
function visit(symbolTable: SymbolTable) {
|
|
if (!pushIfUnique(visitedSymbolTables, symbolTable)) return; // O(depth) per call
|
|
// ... inherit symbols
|
|
}
|
|
|
|
**Why this is O(n):** Same ``pushIfUnique`` pattern as ts-0002; ``visitedSymbolTables`` is a
|
|
plain array scanned linearly on every call.
|
|
|
|
**Complexity:** ``O(depth²)`` where depth = symbol-table inheritance depth
|
|
|
|
**Patch:**
|
|
|
|
.. code-block:: typescript
|
|
|
|
const visitedSymbolTables = new Set<SymbolTable>();
|
|
|
|
function visit(symbolTable: SymbolTable) {
|
|
if (visitedSymbolTables.has(symbolTable)) return; // O(1)
|
|
visitedSymbolTables.add(symbolTable);
|
|
// ... inherit symbols
|
|
}
|
|
|
|
**Data structure change:** ``SymbolTable[]`` array + ``pushIfUnique`` → ``Set<SymbolTable>``
|
|
|
|
**Status:** **PATCHED** — ``defects/typescript/patch/ts-checker-set-visited.patch``
|
|
|
|
Benchmark Results
|
|
-----------------
|
|
|
|
.. TODO: benchmark pending patch
|
|
|
|
Complexity Proof
|
|
----------------
|
|
|
|
Identical argument to ts-0002. Let depth = inheritance chain depth. ``pushIfUnique`` costs
|
|
O(depth) per call; O(depth²) total. ``Set.has()`` costs O(1) per call; O(depth) total. QED.
|
|
|
|
References
|
|
----------
|
|
|
|
* Defect ticket: ``tools/tickets/defects/ts-0003.md``
|