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 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 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 — {termId, scale} // Linear scan on every add() // ----------------------------------------------------------------------- static long defectiveLinearSumBuild(int[] termIds, int[] scales) { ArrayList 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 (termId -> scale) // O(1) amortised per add() // ----------------------------------------------------------------------- static long fixedLinearSumBuild(int[] termIds, int[] scales) { HashMap 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."); } }