package unit; import java.util.ArrayList; import java.util.LinkedHashSet; import java.util.List; /** * Unit test for CWE-407 tomcat-0001: * ReplicationValve.registerReplicationSession() uses ArrayList.contains() * for cross-context session deduplication — O(n²) for n registrations. * * Run: java -ea -cp . unit.TomcatReplicationValveTest */ public class TomcatReplicationValveTest { // ----------------------------------------------------------------------- // Models — stand-ins for DeltaSession (identity equality, no Tomcat deps) // ----------------------------------------------------------------------- static final class Session { final String id; Session(String id) { this.id = id; } @Override public String toString() { return "Session(" + id + ")"; } } /** Defective: ArrayList-based cross-context session registry (Tomcat original). */ static final class DefectiveRegistry { private final List sessions = new ArrayList<>(); long containsProbes = 0; public void register(Session s) { // Simulate ArrayList.contains() probe count containsProbes += sessions.size(); if (!sessions.contains(s)) { sessions.add(s); } } public List getSessions() { return sessions; } } /** Fixed: LinkedHashSet-based registry — O(1) dedup, no contains() guard. */ static final class FixedRegistry { private final LinkedHashSet sessions = new LinkedHashSet<>(); long containsProbes = 0; public void register(Session s) { containsProbes += 1; // O(1) hash probe sessions.add(s); // Set.add() is idempotent } public LinkedHashSet getSessions() { return sessions; } } // ----------------------------------------------------------------------- // Test 1 — correctness: same unique sessions survive dedup // ----------------------------------------------------------------------- static void test1_correctness() { int N = 20; Session[] allSessions = new Session[N]; for (int i = 0; i < N; i++) allSessions[i] = new Session("s" + i); DefectiveRegistry def = new DefectiveRegistry(); FixedRegistry fix = new FixedRegistry(); // Register each session twice (duplicate registrations) for (Session s : allSessions) { def.register(s); fix.register(s); } for (Session s : allSessions) { def.register(s); fix.register(s); } assert def.getSessions().size() == N : "tomcat-0001 correctness: defective size=" + def.getSessions().size() + " expected=" + N; assert fix.getSessions().size() == N : "tomcat-0001 correctness: fixed size=" + fix.getSessions().size() + " expected=" + N; // Same sessions in same order List defList = def.getSessions(); List fixList = new ArrayList<>(fix.getSessions()); assert defList.equals(fixList) : "tomcat-0001 correctness: session lists differ"; System.out.println("PASS test1_correctness: " + N + " unique sessions after 2x registration each"); } // ----------------------------------------------------------------------- // Test 2 — complexity: O(n²) probes vs O(n) probes // ----------------------------------------------------------------------- static void test2_complexity_ratio() { // Small: N=50, Large: N=500 — unique sessions each time (no early exit) int small = 50; int large = 500; DefectiveRegistry defSmall = new DefectiveRegistry(); DefectiveRegistry defLarge = new DefectiveRegistry(); FixedRegistry fixSmall = new FixedRegistry(); FixedRegistry fixLarge = new FixedRegistry(); // Register N unique sessions (all new — worst case for defective: no early miss) for (int i = 0; i < small; i++) { Session s = new Session("s" + i); defSmall.register(s); fixSmall.register(s); } for (int i = 0; i < large; i++) { Session s = new Session("s" + i); defLarge.register(s); fixLarge.register(s); } double defRatio = (double) defLarge.containsProbes / Math.max(defSmall.containsProbes, 1); double fixRatio = (double) fixLarge.containsProbes / Math.max(fixSmall.containsProbes, 1); System.out.printf(" defective probes: small=%d large=%d ratio=%.1fx%n", defSmall.containsProbes, defLarge.containsProbes, defRatio); System.out.printf(" fixed probes: small=%d large=%d ratio=%.1fx%n", fixSmall.containsProbes, fixLarge.containsProbes, fixRatio); // 10x input → ~100x probes (quadratic) assert defRatio > 50.0 : "tomcat-0001 complexity: defective ratio should be >50x at 10x scale, got " + defRatio; // 10x input → ~10x probes (linear) assert fixRatio < 20.0 : "tomcat-0001 complexity: fixed ratio should be <20x at 10x scale, got " + fixRatio; assert defRatio > fixRatio * 3 : "tomcat-0001 complexity: defective should grow much faster, def=" + defRatio + " fix=" + fixRatio; System.out.printf("PASS test2_complexity_ratio: defective=%.0fx fixed=%.0fx%n", defRatio, fixRatio); } // ----------------------------------------------------------------------- // Test 3 — absolute counts at N=200 // ----------------------------------------------------------------------- static void test3_absolute_counts() { int N = 200; DefectiveRegistry def = new DefectiveRegistry(); FixedRegistry fix = new FixedRegistry(); for (int i = 0; i < N; i++) { Session s = new Session("s" + i); def.register(s); fix.register(s); } // Defective: probes = 0+1+2+...+(N-1) = N*(N-1)/2 long expectedDefMin = (long) N * (N - 1) / 2; assert def.containsProbes >= expectedDefMin : "tomcat-0001 counts: defective probes=" + def.containsProbes + " expected>=" + expectedDefMin; // Fixed: exactly N probes (one per registration) assert fix.containsProbes == N : "tomcat-0001 counts: fixed probes=" + fix.containsProbes + " expected=" + N; long speedup = def.containsProbes / fix.containsProbes; System.out.printf("PASS test3_absolute_counts: defective=%d fixed=%d speedup=%dx%n", def.containsProbes, fix.containsProbes, speedup); } // ----------------------------------------------------------------------- // Test 4 — duplicate registration preserves single entry // ----------------------------------------------------------------------- static void test4_duplicate_single_entry() { Session s = new Session("shared"); FixedRegistry fix = new FixedRegistry(); for (int i = 0; i < 50; i++) fix.register(s); assert fix.getSessions().size() == 1 : "tomcat-0001 dedup: expected 1 entry after 50 identical registrations, got " + fix.getSessions().size(); assert fix.getSessions().contains(s) : "tomcat-0001 dedup: session not found after registration"; System.out.println("PASS test4_duplicate_single_entry: 50 registrations → 1 entry"); } // ----------------------------------------------------------------------- // main // ----------------------------------------------------------------------- public static void main(String[] args) { System.out.println("=== TomcatReplicationValveTest — CWE-407 tomcat-0001 ==="); int passed = 0; int failed = 0; Runnable[] tests = { TomcatReplicationValveTest::test1_correctness, TomcatReplicationValveTest::test2_complexity_ratio, TomcatReplicationValveTest::test3_absolute_counts, TomcatReplicationValveTest::test4_duplicate_single_entry, }; for (Runnable test : tests) { try { test.run(); passed++; } catch (AssertionError e) { System.out.println("FAIL: " + e.getMessage()); failed++; } } System.out.println("---"); System.out.println("Results: " + passed + " passed, " + failed + " failed"); if (failed > 0) System.exit(1); } }