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,264 @@
package unit;
import java.util.*;
/**
* Unit test for solc-0001: CallGraphCycleFinder CWE-407.
*
* Defect: CallGraphCycleFinder.visit() uses std::find on currentPath (a vector)
* to detect whether a function is already on the DFS stack. That is an O(D)
* membership test performed once per edge visited. With F functions each calling
* D others, total comparisons are O(F × D²).
*
* File: libyul/optimiser/CallGraphGenerator.cpp:49
* Symbol: CallGraphCycleFinder::visit — `std::find(currentPath.begin(), currentPath.end(), _function)`
*
* Fix: Carry a parallel std::set<FunctionHandle> (currentPathSet) alongside
* the currentPath vector. The set gives O(log D) membership, effectively
* O(1) compared to O(D) at practical depths.
*
* Modeled here in Java:
* - YulString ≡ String (function name)
* - currentPath ≡ List<String>
* - currentPathSet ≡ Set<String>
* - comparisons counted at the membership-test site
*
* Expected at depth=32, functions=32:
* defective ≈ F × D×(D+1)/2 = 32 × 528 = 16896
* fixed ≈ F × D = 32 × 32 = 1024
* ratio ≈ 16.5×
*/
public class SolcCallGraphCycleTest {
// ── Defective: linear scan of currentPath for cycle detection ─────────────
static class DefectiveCycleFinder {
final Map<String, List<String>> callGraph;
final Set<String> containedInCycle = new HashSet<>();
final Set<String> visited = new HashSet<>();
final List<String> currentPath = new ArrayList<>();
long comparisons = 0;
DefectiveCycleFinder(Map<String, List<String>> callGraph) {
this.callGraph = callGraph;
}
void visit(String function) {
if (visited.contains(function)) return;
// O(D) linear scan — the defect
int cycleStart = -1;
for (int i = 0; i < currentPath.size(); i++) {
comparisons++;
if (currentPath.get(i).equals(function)) {
cycleStart = i;
break;
}
}
if (cycleStart >= 0) {
for (int i = cycleStart; i < currentPath.size(); i++)
containedInCycle.add(currentPath.get(i));
} else {
currentPath.add(function);
List<String> callees = callGraph.getOrDefault(function, Collections.emptyList());
for (String child : callees)
visit(child);
currentPath.remove(currentPath.size() - 1);
visited.add(function);
}
}
}
// ── Fixed: parallel HashSet for O(1) membership test ─────────────────────
static class FixedCycleFinder {
final Map<String, List<String>> callGraph;
final Set<String> containedInCycle = new HashSet<>();
final Set<String> visited = new HashSet<>();
final List<String> currentPath = new ArrayList<>();
final Set<String> currentPathSet = new HashSet<>();
long comparisons = 0;
FixedCycleFinder(Map<String, List<String>> callGraph) {
this.callGraph = callGraph;
}
void visit(String function) {
if (visited.contains(function)) return;
// O(1) hash lookup — the fix
comparisons++;
if (currentPathSet.contains(function)) {
// still need to walk currentPath to collect cycle members
boolean inCycle = false;
for (String f : currentPath) {
if (f.equals(function)) inCycle = true;
if (inCycle) containedInCycle.add(f);
}
} else {
currentPath.add(function);
currentPathSet.add(function);
List<String> callees = callGraph.getOrDefault(function, Collections.emptyList());
for (String child : callees)
visit(child);
currentPath.remove(currentPath.size() - 1);
currentPathSet.remove(function);
visited.add(function);
}
}
}
// ── Graph builders ────────────────────────────────────────────────────────
/**
* Build a linear chain: f0→f1→f2→…→f(D-1), no cycles.
* Each function is visited once; the path grows to depth D.
* Defective finder scans 0+1+2+…+(D-1) = D*(D-1)/2 times per function.
* With F independent chains total defective comparisons ≈ F * D*(D-1)/2.
*/
public static Map<String, List<String>> chainGraph(int F, int D) {
Map<String, List<String>> g = new LinkedHashMap<>();
for (int f = 0; f < F; f++) {
String prefix = "fn" + f + "_";
for (int d = 0; d < D - 1; d++) {
g.computeIfAbsent(prefix + d, k -> new ArrayList<>())
.add(prefix + (d + 1));
}
g.computeIfAbsent(prefix + (D - 1), k -> new ArrayList<>());
}
return g;
}
/**
* Build a star graph: root calls all F leaf functions.
* Path depth is 2 for each leaf. Membership test on each leaf sees
* currentPath = [root] → 1 comparison (defective) vs O(1) (fixed).
* This isolates the per-visit cost pattern.
*/
static Map<String, List<String>> starGraph(int F) {
Map<String, List<String>> g = new LinkedHashMap<>();
List<String> children = new ArrayList<>();
for (int i = 0; i < F; i++) {
String leaf = "leaf_" + i;
children.add(leaf);
g.put(leaf, Collections.emptyList());
}
g.put("root", children);
return g;
}
// ── Simulation helpers ────────────────────────────────────────────────────
public static long simulateDefective(Map<String, List<String>> g) {
DefectiveCycleFinder finder = new DefectiveCycleFinder(g);
for (String fn : g.keySet()) finder.visit(fn);
return finder.comparisons;
}
public static long simulateFixed(Map<String, List<String>> g) {
FixedCycleFinder finder = new FixedCycleFinder(g);
for (String fn : g.keySet()) finder.visit(fn);
return finder.comparisons;
}
// ── Tests ─────────────────────────────────────────────────────────────────
static void testCorrectnessNoCycle() {
Map<String, List<String>> g = chainGraph(4, 5);
DefectiveCycleFinder def = new DefectiveCycleFinder(g);
FixedCycleFinder fix = new FixedCycleFinder(g);
for (String fn : g.keySet()) { def.visit(fn); fix.visit(fn); }
assert def.containedInCycle.equals(fix.containedInCycle)
: "cycle sets differ (no-cycle graph)";
assert def.containedInCycle.isEmpty()
: "expected no cycles in chain graph";
System.out.println("PASS testCorrectnessNoCycle");
}
static void testCorrectnessCycleDetection() {
// f0 → f1 → f2 → f0 (cycle), f3 standalone
Map<String, List<String>> g = new LinkedHashMap<>();
g.put("f0", Arrays.asList("f1"));
g.put("f1", Arrays.asList("f2"));
g.put("f2", Arrays.asList("f0"));
g.put("f3", Collections.emptyList());
DefectiveCycleFinder def = new DefectiveCycleFinder(g);
FixedCycleFinder fix = new FixedCycleFinder(g);
for (String fn : g.keySet()) { def.visit(fn); fix.visit(fn); }
assert def.containedInCycle.containsAll(Arrays.asList("f0", "f1", "f2"))
: "defective missed cycle members: " + def.containedInCycle;
assert fix.containedInCycle.containsAll(Arrays.asList("f0", "f1", "f2"))
: "fixed missed cycle members: " + fix.containedInCycle;
assert !def.containedInCycle.contains("f3")
: "defective falsely included f3";
assert !fix.containedInCycle.contains("f3")
: "fixed falsely included f3";
assert def.containedInCycle.equals(fix.containedInCycle)
: "cycle sets differ between defective and fixed";
System.out.println("PASS testCorrectnessCycleDetection");
}
static void testDefectiveGrowsQuadratically() {
// Comparison count in chain graph grows quadratically with depth D
long prev = -1;
for (int D : new int[]{8, 16, 32}) {
long c = simulateDefective(chainGraph(1, D));
if (prev > 0) {
double ratio = (double) c / prev;
assert ratio > 2.5
: "defective should grow >2.5x when D doubles; got " + ratio + " at D=" + D;
}
prev = c;
}
System.out.println("PASS testDefectiveGrowsQuadratically");
}
static void testFixedGrowsLinearly() {
long prev = -1;
for (int D : new int[]{8, 16, 32}) {
long c = simulateFixed(chainGraph(1, D));
if (prev > 0) {
double ratio = (double) c / prev;
assert ratio < 2.3
: "fixed should grow ~2x when D doubles; got " + ratio + " at D=" + D;
}
prev = c;
}
System.out.println("PASS testFixedGrowsLinearly");
}
static void testRatioAtScale() {
// F=32 independent chains of depth D=32
// defective: each of the 32 chains accumulates 0+1+…+31 = 496 comparisons → 32*496 = 15872
// fixed: each chain accumulates 32 comparisons → 32*32 = 1024
// The spec says ≈16896 (uses D*(D+1)/2) and 1024; ratio ≈16.5×
int F = 32, D = 32;
Map<String, List<String>> g = chainGraph(F, D);
long defComp = simulateDefective(g);
long fixComp = simulateFixed(g);
double ratio = (double) defComp / fixComp;
assert defComp >= 15000 && defComp <= 18000
: "defective comparisons out of expected range: " + defComp;
assert fixComp == (long) F * D
: "fixed comparisons should be F*D=" + (F * D) + "; got " + fixComp;
assert ratio > 10.0
: "ratio should be >10x at F=32,D=32; got " + ratio;
System.out.printf(
"PASS testRatioAtScale (defective=%d, fixed=%d, ratio=%.1fx)%n",
defComp, fixComp, ratio);
}
public static void main(String[] args) {
testCorrectnessNoCycle();
testCorrectnessCycleDetection();
testDefectiveGrowsQuadratically();
testFixedGrowsLinearly();
testRatioAtScale();
System.out.println("All solc-0001 tests passed.");
}
}