java-topology/defects/spidermonkey/unit/SpiderMonkeyLinearSumTest.java
russell@unturf.com db29a08762 undefect. CWE-407 — 92 sites, 42 ecosystems
B&W print-friendly diagrams + tinkerpop-0001 + wave-3 proof sections.
Squash of 94 local commits onto remote master.
2026-03-26 19:48:18 -04:00

248 lines
9.9 KiB
Java

package unit;
import java.util.ArrayList;
import java.util.HashMap;
/**
* SpiderMonkeyLinearSumTest
*
* Models the CWE-407 defect in LinearSum::add(MDefinition* term, int32_t scale):
*
* Defective: Vector<LinearTerm, 2, JitAllocPolicy> searched by linear pointer
* scan on every add() call. At T distinct terms and N total add()
* calls, cost is O(N * T).
*
* Fixed: HashMap<MDefinition*, int32_t, ...> with lookupOrAdd(), O(1)
* amortised per call regardless of T.
*
* "Term" is modelled as an Integer ID. Comparison counts are instrumented
* explicitly — not wall-clock timing.
*/
public class SpiderMonkeyLinearSumTest {
// -----------------------------------------------------------------------
// Instrumented defective LinearSum: ArrayList<int[]> — {termId, scale}
// Linear scan on every add()
// -----------------------------------------------------------------------
static long defectiveLinearSumBuild(int[] termIds, int[] scales) {
ArrayList<int[]> terms = new ArrayList<>(); // {termId, scale}
long comparisons = 0;
for (int i = 0; i < termIds.length; i++) {
int termId = termIds[i];
int scale = scales[i];
boolean found = false;
// O(T) linear scan — the defect
for (int[] entry : terms) {
comparisons++;
if (entry[0] == termId) {
entry[1] += scale;
if (entry[1] == 0) {
terms.remove(entry);
}
found = true;
break;
}
}
if (!found) {
terms.add(new int[]{termId, scale});
}
}
return comparisons;
}
// -----------------------------------------------------------------------
// Instrumented fixed LinearSum: HashMap<Integer,Integer> (termId -> scale)
// O(1) amortised per add()
// -----------------------------------------------------------------------
static long fixedLinearSumBuild(int[] termIds, int[] scales) {
HashMap<Integer, Integer> terms = new HashMap<>();
long lookups = 0;
for (int i = 0; i < termIds.length; i++) {
int termId = termIds[i];
int scale = scales[i];
lookups++; // one O(1) hash lookup per call
Integer existing = terms.get(termId);
if (existing != null) {
int newScale = existing + scale;
if (newScale == 0) {
terms.remove(termId);
} else {
terms.put(termId, newScale);
}
} else {
terms.put(termId, scale);
}
}
return lookups;
}
// -----------------------------------------------------------------------
// Helper: build add() call sequence — N calls over T distinct term IDs,
// cycling through IDs so each term is visited multiple times.
// -----------------------------------------------------------------------
static int[][] makeAddCalls(int N, int T) {
int[] termIds = new int[N];
int[] scales = new int[N];
for (int i = 0; i < N; i++) {
termIds[i] = i % T;
scales[i] = 1;
}
return new int[][]{termIds, scales};
}
// -----------------------------------------------------------------------
// Test 1 — T=20 terms, N=100 add() calls: defect comparisons > fixed lookups
// -----------------------------------------------------------------------
static void test1_basicRatio() {
int T = 20, N = 100;
int[][] calls = makeAddCalls(N, T);
long defectOps = defectiveLinearSumBuild(calls[0], calls[1]);
long fixedOps = fixedLinearSumBuild(calls[0], calls[1]);
System.out.printf("test1: T=%d N=%d defect=%d fixed=%d%n",
T, N, defectOps, fixedOps);
assert defectOps > fixedOps
: "defect must do more work than fix: " + defectOps + " vs " + fixedOps;
}
// -----------------------------------------------------------------------
// Test 2 — Ratio > 10x at T=20, N=100
// -----------------------------------------------------------------------
static void test2_ratioExceedsTenX() {
int T = 20, N = 100;
int[][] calls = makeAddCalls(N, T);
long defectOps = defectiveLinearSumBuild(calls[0], calls[1]);
long fixedOps = fixedLinearSumBuild(calls[0], calls[1]);
double ratio = (double) defectOps / Math.max(1, fixedOps);
System.out.printf("test2: T=%d N=%d defect=%d fixed=%d ratio=%.1fx%n",
T, N, defectOps, fixedOps, ratio);
assert ratio > 10.0
: "expected ratio > 10x, got " + ratio;
}
// -----------------------------------------------------------------------
// Test 3 — All N add() calls use the same term (T=1, N=200)
// Defect: each call scans list of length 1 → N-1 comparisons (hitting first).
// Actually on first call list is empty (0 comparisons), subsequent hits find it.
// But scale accumulates; zero-cancellation only if scale wraps.
// We use non-zero final scale; list stays length 1 throughout.
// -----------------------------------------------------------------------
static void test3_singleTermRepeat() {
int N = 200;
int[] termIds = new int[N];
int[] scales = new int[N];
for (int i = 0; i < N; i++) { termIds[i] = 7; scales[i] = 1; }
long defectOps = defectiveLinearSumBuild(termIds, scales);
long fixedOps = fixedLinearSumBuild(termIds, scales);
// Defect: 0 comparisons for first call (list empty), 1 comparison each for calls 2..N.
// Total: N-1 comparisons.
System.out.printf("test3: T=1 N=%d defect=%d (expect %d) fixed=%d%n",
N, defectOps, N - 1, fixedOps);
assert defectOps == N - 1
: "expected " + (N-1) + " defect comparisons for T=1, got " + defectOps;
assert fixedOps == N
: "expected " + N + " fixed lookups for T=1, got " + fixedOps;
}
// -----------------------------------------------------------------------
// Test 4 — Scale cancellation: add term with +1 then -1 repeatedly.
// Fixed must handle zero-scale removal correctly.
// Defect still scans; the removal path is exercised on both sides.
// -----------------------------------------------------------------------
static void test4_scaleCancellation() {
int T = 10;
int N = 40; // 20 pairs of (+1,-1) over T terms
int[] termIds = new int[N];
int[] scales = new int[N];
for (int i = 0; i < N; i++) {
termIds[i] = i % T;
scales[i] = (i / T % 2 == 0) ? 1 : -1;
}
long defectOps = defectiveLinearSumBuild(termIds, scales);
long fixedOps = fixedLinearSumBuild(termIds, scales);
System.out.printf("test4: T=%d N=%d cancellation defect=%d fixed=%d%n",
T, N, defectOps, fixedOps);
// Both must complete (no assertion error = correct semantics)
// Defect should still be >= fixed
assert defectOps >= fixedOps
: "defect should not be cheaper than fix: " + defectOps + " vs " + fixedOps;
}
// -----------------------------------------------------------------------
// Test 5 — Scaling: doubling T roughly doubles defect per add() call,
// while fixed remains O(1) per call.
// -----------------------------------------------------------------------
static void test5_linearVsConstantScaling() {
int N = 200;
int T1 = 10;
int T2 = 20; // double T
int[][] calls1 = makeAddCalls(N, T1);
int[][] calls2 = makeAddCalls(N, T2);
long d1 = defectiveLinearSumBuild(calls1[0], calls1[1]);
long d2 = defectiveLinearSumBuild(calls2[0], calls2[1]);
long f1 = fixedLinearSumBuild(calls1[0], calls1[1]);
long f2 = fixedLinearSumBuild(calls2[0], calls2[1]);
double defectGrowth = (double) d2 / Math.max(1, d1);
double fixedGrowth = (double) f2 / Math.max(1, f1);
System.out.printf("test5: N=%d T1=%d→T2=%d defect_growth=%.2fx fixed_growth=%.2fx%n",
N, T1, T2, defectGrowth, fixedGrowth);
// Defect average scan length grows with T → more than 2x when T doubles
// (for N >> T, each call hits an average-length list growing with T)
// We need at least that doubling T increases defect more than it increases fixed.
assert defectGrowth > fixedGrowth
: "defect growth should exceed fixed growth when T doubles";
// Fixed lookups should remain essentially constant (N lookups regardless of T)
assert fixedGrowth <= 1.1
: "fixed lookups should not grow meaningfully with T, got " + fixedGrowth;
}
// -----------------------------------------------------------------------
// Main
// -----------------------------------------------------------------------
public static void main(String[] args) {
System.out.println("=== SpiderMonkeyLinearSumTest ===");
System.out.println("Modelling CWE-407: LinearSum::add() linear scan vs HashMap O(1)");
System.out.println();
test1_basicRatio();
System.out.println(" PASS test1_basicRatio");
test2_ratioExceedsTenX();
System.out.println(" PASS test2_ratioExceedsTenX");
test3_singleTermRepeat();
System.out.println(" PASS test3_singleTermRepeat");
test4_scaleCancellation();
System.out.println(" PASS test4_scaleCancellation");
test5_linearVsConstantScaling();
System.out.println(" PASS test5_linearVsConstantScaling");
System.out.println();
System.out.println("All 5 tests PASSED.");
}
}