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,217 @@
package unit;
import java.util.*;
/**
* Unit test for solc-0002: Assembly calculateMaxStackHeight RJUMP target CWE-407.
*
* Defect: calculateMaxStackHeight() (EOF CFG builder) resolves each RJUMP/CRJUMP
* target via std::find(items.begin(), items.end(), item.tag()) — an O(N) scan
* over all assembly items per jump. With J jumps over N items, total cost is
* O(J × N).
*
* File: libevmasm/Assembly.cpp:1077
* Symbol: calculateMaxStackHeight — `std::find(items.begin(), items.end(), item.tag())`
*
* Fix: Build an unordered_map<u256, size_t> (tagIndex) once before the
* worklist loop, mapping each Tag item's data value to its index.
* Each RJUMP resolution becomes O(1). Total cost O(J + N).
*
* Modeled here in Java:
* - AssemblyItem ≡ Item record (type + label)
* - tag lookup ≡ linear scan of items[] vs HashMap<Integer,Integer>
* - comparisons counted at each scan step
*
* Expected at J=N=100:
* defective ≈ J × N = 10000
* fixed ≈ J + N = 200 (index build + lookups)
* ratio > 10×
*/
public class SolcAssemblyRjumpTest {
static final int TYPE_TAG = 0;
static final int TYPE_RJUMP = 1;
static final int TYPE_COND_RJUMP = 2;
static final int TYPE_OTHER = 3;
// ── Defective: std::find linear scan per jump ─────────────────────────────
static class DefectiveStackCalc {
final int[] itemTypes; // TYPE_* per item
final int[] itemLabels; // label for Tag and Jump items (−1 if not applicable)
long comparisons = 0;
DefectiveStackCalc(int[] itemTypes, int[] itemLabels) {
this.itemTypes = itemTypes;
this.itemLabels = itemLabels;
}
/** Returns index of the Tag item whose label equals targetLabel, −1 if not found. */
int findTag(int targetLabel) {
for (int i = 0; i < itemTypes.length; i++) {
comparisons++;
if (itemTypes[i] == TYPE_TAG && itemLabels[i] == targetLabel)
return i;
}
return -1;
}
/** Process all jumps, resolving targets via linear scan. */
void resolveAllJumps() {
for (int idx = 0; idx < itemTypes.length; idx++) {
int type = itemTypes[idx];
if (type == TYPE_RJUMP || type == TYPE_COND_RJUMP) {
int target = itemLabels[idx];
int pos = findTag(target);
assert pos >= 0 : "Tag not found for label " + target;
}
}
}
}
// ── Fixed: pre-built HashMap index, O(1) lookup ───────────────────────────
static class FixedStackCalc {
final int[] itemTypes;
final int[] itemLabels;
final Map<Integer, Integer> tagIndex;
long comparisons = 0;
FixedStackCalc(int[] itemTypes, int[] itemLabels) {
this.itemTypes = itemTypes;
this.itemLabels = itemLabels;
// Build index once: O(N)
tagIndex = new HashMap<>();
for (int i = 0; i < itemTypes.length; i++) {
if (itemTypes[i] == TYPE_TAG) {
comparisons++; // count the index-build work
tagIndex.put(itemLabels[i], i);
}
}
}
void resolveAllJumps() {
for (int idx = 0; idx < itemTypes.length; idx++) {
int type = itemTypes[idx];
if (type == TYPE_RJUMP || type == TYPE_COND_RJUMP) {
int target = itemLabels[idx];
comparisons++; // O(1) hash lookup
Integer pos = tagIndex.get(target);
assert pos != null : "Tag not found for label " + target;
}
}
}
}
// ── Graph builders ────────────────────────────────────────────────────────
/**
* Build an items array with N tags interleaved with J jumps.
* Layout: [TAG_0, OTHER, OTHER, RJUMP→TAG_0, TAG_1, OTHER, RJUMP→TAG_1, …]
* Ensures every jump target exists as a Tag item.
*/
static int[][] buildItems(int N, int J) {
// Each tag gets an index slot; jumps are interspersed.
// Simple layout: slots 0..N-1 are Tags, slots N..N+J-1 are RJUMPs
// targeting tag (slot % N).
int total = N + J;
int[] types = new int[total];
int[] labels = new int[total];
for (int i = 0; i < N; i++) {
types[i] = TYPE_TAG;
labels[i] = i; // label == index for simplicity
}
for (int j = 0; j < J; j++) {
types[N + j] = TYPE_RJUMP;
labels[N + j] = j % N; // jump to tag j%N
}
return new int[][]{ types, labels };
}
// ── Simulation helpers ────────────────────────────────────────────────────
public static long simulateDefective(int N, int J) {
int[][] items = buildItems(N, J);
DefectiveStackCalc calc = new DefectiveStackCalc(items[0], items[1]);
calc.resolveAllJumps();
return calc.comparisons;
}
public static long simulateFixed(int N, int J) {
int[][] items = buildItems(N, J);
FixedStackCalc calc = new FixedStackCalc(items[0], items[1]);
calc.resolveAllJumps();
return calc.comparisons;
}
// ── Tests ─────────────────────────────────────────────────────────────────
static void testCorrectnessMatch() {
// Both must resolve same targets without assertion failure
int N = 10, J = 10;
int[][] items = buildItems(N, J);
DefectiveStackCalc def = new DefectiveStackCalc(items[0], items[1]);
FixedStackCalc fix = new FixedStackCalc(items[0], items[1]);
// just verify they don't throw
def.resolveAllJumps();
fix.resolveAllJumps();
System.out.println("PASS testCorrectnessMatch");
}
static void testDefectiveGrowsQuadratically() {
long prev = -1;
for (int S : new int[]{10, 20, 40}) {
long c = simulateDefective(S, S);
if (prev > 0) {
double ratio = (double) c / prev;
assert ratio > 3.0
: "defective should grow >3x when N=J doubled; got " + ratio + " at N=J=" + S;
}
prev = c;
}
System.out.println("PASS testDefectiveGrowsQuadratically");
}
static void testFixedGrowsLinearly() {
long prev = -1;
for (int S : new int[]{10, 20, 40}) {
long c = simulateFixed(S, S);
if (prev > 0) {
double ratio = (double) c / prev;
assert ratio < 2.5
: "fixed should grow ~2x when N=J doubled; got " + ratio + " at N=J=" + S;
}
prev = c;
}
System.out.println("PASS testFixedGrowsLinearly");
}
static void testRatioAtScale() {
int N = 100, J = 100;
long defComp = simulateDefective(N, J);
long fixComp = simulateFixed(N, J);
double ratio = (double) defComp / fixComp;
// defective: each of J=100 jumps scans up to N+J=200 items before finding tag.
// Tags are at positions 0..99, jumps at 100..199; worst-case each jump scans
// past all 200 items. In our layout tags are first so average scan is ~N/2.
// Minimum expected: J * 1 = 100 comparisons. We assert > 10x fixed.
assert ratio > 10.0
: "ratio should be >10x at N=J=100; got " + ratio +
" (defective=" + defComp + ", fixed=" + fixComp + ")";
System.out.printf(
"PASS testRatioAtScale (defective=%d, fixed=%d, ratio=%.1fx)%n",
defComp, fixComp, ratio);
}
public static void main(String[] args) {
testCorrectnessMatch();
testDefectiveGrowsQuadratically();
testFixedGrowsLinearly();
testRatioAtScale();
System.out.println("All solc-0002 tests passed.");
}
}