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.
This commit is contained in:
russell@unturf.com 2026-03-26 17:11:57 -04:00
commit 0a580b313d
70422 changed files with 17213626 additions and 0 deletions

View file

@ -0,0 +1,120 @@
diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts
index 0567712..5509a04 100644
--- a/src/compiler/checker.ts
+++ b/src/compiler/checker.ts
@@ -2348,6 +2348,8 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
var resolutionTargets: TypeSystemEntity[] = [];
var resolutionResults: boolean[] = [];
var resolutionPropertyNames: TypeSystemPropertyName[] = [];
+ // CWE-407 fix: O(1) lookup for findResolutionCycleStartIndex (was O(depth) scan)
+ var resolutionTargetsSet = new Map<TypeSystemEntity, Map<TypeSystemPropertyName, number>>();
var resolutionStart = 0;
var inVarianceComputation = false;
@@ -5227,7 +5229,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
}
function getExportsOfModuleWorker(moduleSymbol: Symbol) {
- const visitedSymbols: Symbol[] = [];
+ const visitedSymbols = new Set<Symbol>(); // CWE-407 fix: was Symbol[] (O(n) pushIfUnique)
let typeOnlyExportStarMap: Map<__String, ExportDeclaration & { readonly isTypeOnly: true; readonly moduleSpecifier: Expression; }> | undefined;
const nonTypeOnlyNames = new Set<__String>();
@@ -5253,9 +5255,10 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
// again with 'export *' will override the type-onlyness of its exports.
symbol.exports.forEach((_, name) => nonTypeOnlyNames.add(name));
}
- if (!(symbol && symbol.exports && pushIfUnique(visitedSymbols, symbol))) {
- return;
- }
+ // CWE-407 fix: O(1) Set.has replaces O(n) pushIfUnique on array
+ if (!(symbol && symbol.exports)) return;
+ if (visitedSymbols.has(symbol)) return;
+ visitedSymbols.add(symbol);
const symbols = new Map(symbol.exports);
// All export * declarations are collected in an __export symbol by the binder
@@ -5734,7 +5737,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
return rightMeaning === SymbolFlags.Value ? SymbolFlags.Value : SymbolFlags.Namespace;
}
- function getAccessibleSymbolChain(symbol: Symbol | undefined, enclosingDeclaration: Node | undefined, meaning: SymbolFlags, useOnlyExternalAliasing: boolean, visitedSymbolTablesMap = new Map<SymbolId, SymbolTable[]>()): Symbol[] | undefined {
+ function getAccessibleSymbolChain(symbol: Symbol | undefined, enclosingDeclaration: Node | undefined, meaning: SymbolFlags, useOnlyExternalAliasing: boolean, visitedSymbolTablesMap = new Map<SymbolId, Set<SymbolTable>>()): Symbol[] | undefined {
if (!(symbol && !isPropertyOrMethodDeclarationSymbol(symbol))) {
return undefined;
}
@@ -5750,7 +5753,8 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
const id = getSymbolId(symbol);
let visitedSymbolTables = visitedSymbolTablesMap.get(id);
if (!visitedSymbolTables) {
- visitedSymbolTablesMap.set(id, visitedSymbolTables = []);
+ // CWE-407 fix: Set<SymbolTable> for O(1) has/add/delete (was SymbolTable[] + pushIfUnique)
+ visitedSymbolTablesMap.set(id, visitedSymbolTables = new Set());
}
const result = forEachSymbolTableInScope(enclosingDeclaration, getAccessibleSymbolChainFromSymbolTable);
cache.set(key, result);
@@ -5760,12 +5764,13 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
* @param {ignoreQualification} boolean Set when a symbol is being looked for through the exports of another symbol (meaning we have a route to qualify it already)
*/
function getAccessibleSymbolChainFromSymbolTable(symbols: SymbolTable, ignoreQualification?: boolean, isLocalNameLookup?: boolean): Symbol[] | undefined {
- if (!pushIfUnique(visitedSymbolTables!, symbols)) {
+ // CWE-407 fix: O(1) Set.has/add/delete replaces O(n) pushIfUnique + pop on array
+ if (visitedSymbolTables!.has(symbols)) {
return undefined;
}
-
+ visitedSymbolTables!.add(symbols);
const result = trySymbolTable(symbols, ignoreQualification, isLocalNameLookup);
- visitedSymbolTables!.pop();
+ visitedSymbolTables!.delete(symbols);
return result;
}
@@ -11497,19 +11502,27 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
resolutionTargets.push(target);
resolutionResults.push(/*items*/ true);
resolutionPropertyNames.push(propertyName);
+ // CWE-407 fix: record index for O(1) cycle detection
+ let propMap = resolutionTargetsSet.get(target);
+ if (!propMap) resolutionTargetsSet.set(target, propMap = new Map());
+ propMap.set(propertyName, resolutionTargets.length - 1);
return true;
}
function findResolutionCycleStartIndex(target: TypeSystemEntity, propertyName: TypeSystemPropertyName): number {
- for (let i = resolutionTargets.length - 1; i >= resolutionStart; i--) {
+ // CWE-407 fix: O(1) map lookup replaces O(depth) linear scan for membership test
+ const propMap = resolutionTargetsSet.get(target);
+ const idx = propMap?.get(propertyName);
+ if (idx === undefined || idx < resolutionStart) {
+ return -1;
+ }
+ // Verify no resolved entry sits above idx that would invalidate the cycle
+ for (let i = resolutionTargets.length - 1; i > idx; i--) {
if (resolutionTargetHasProperty(resolutionTargets[i], resolutionPropertyNames[i])) {
return -1;
}
- if (resolutionTargets[i] === target && resolutionPropertyNames[i] === propertyName) {
- return i;
- }
}
- return -1;
+ return idx;
}
function resolutionTargetHasProperty(target: TypeSystemEntity, propertyName: TypeSystemPropertyName): boolean {
@@ -11541,8 +11554,12 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
* be true if no circularities were detected, or false if a circularity was found.
*/
function popTypeResolution(): boolean {
- resolutionTargets.pop();
- resolutionPropertyNames.pop();
+ const target = resolutionTargets.pop()!;
+ const propertyName = resolutionPropertyNames.pop()!;
+ // CWE-407 fix: keep map in sync
+ const propMap = resolutionTargetsSet.get(target)!;
+ propMap.delete(propertyName);
+ if (propMap.size === 0) resolutionTargetsSet.delete(target);
return resolutionResults.pop()!;
}