java-topology/defects/solc/unit/SolcAssemblyRjumpTest.java
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

217 lines
8.4 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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.");
}
}