package unit; import java.util.*; /** * DryEngineTest — dry-0001 / dry-0002 (Dry game engine, Urho3D fork) * * Standalone Java proof of CWE-407 patterns in Dry/Urho3D: * * dry-0001: ListView::SetSelections() — PODVector.Contains() O(n) * fired on every UI multi-select change * * dry-0002: Object::UnsubscribeFromAllEventsExcept() — PODVector.Contains() O(m) * per handler, O(n*m) total, fired on object cleanup * * Run: javac -d . DryEngineTest.java && java -ea unit.DryEngineTest */ public class DryEngineTest { // ── dry-0001: ListView::SetSelections ──────────────────────────────────── /** SLOW: Contains on PODVector — O(n) per element in each of two loops */ static long listViewSlow(int selectionSize, int newSize) { List selections = new ArrayList<>(); for (int i = 0; i < selectionSize; i++) selections.add(i); List indices = new ArrayList<>(); for (int i = selectionSize / 2; i < selectionSize / 2 + newSize; i++) indices.add(i); long ops = 0; // Loop 1: remove stale — indices.Contains(index) O(n) per item Iterator it = selections.iterator(); while (it.hasNext()) { int index = it.next(); boolean found = false; for (int idx : indices) { ops++; if (idx == index) { found = true; break; } } if (!found) it.remove(); } // Loop 2: add new — selections.Contains(index) O(n) per item for (int index : indices) { boolean dup = false; for (int s : selections) { ops++; if (s == index) { dup = true; break; } } if (!dup) selections.add(index); } return ops; } /** FAST: HashSet shadow — O(1) per element in both loops */ static long listViewFast(int selectionSize, int newSize) { List selections = new ArrayList<>(); Set selSet = new HashSet<>(); for (int i = 0; i < selectionSize; i++) { selections.add(i); selSet.add(i); } List indices = new ArrayList<>(); for (int i = selectionSize / 2; i < selectionSize / 2 + newSize; i++) indices.add(i); Set idxSet = new HashSet<>(indices); long ops = 0; // Loop 1: remove stale — O(1) per item Iterator it = selections.iterator(); while (it.hasNext()) { int index = it.next(); ops++; if (!idxSet.contains(index)) { it.remove(); selSet.remove(index); } } // Loop 2: add new — O(1) per item for (int index : indices) { ops++; if (!selSet.contains(index)) { selections.add(index); selSet.add(index); } } return ops; } // ── dry-0002: Object::UnsubscribeFromAllEventsExcept ───────────────────── /** SLOW: exceptions.Contains() — O(m) per handler — O(n*m) total */ static long unsubSlow(int handlers, int exceptions) { List eventHandlers = new ArrayList<>(); for (int i = 0; i < handlers; i++) eventHandlers.add(i); List exc = new ArrayList<>(); for (int i = 0; i < exceptions; i++) exc.add(i); // keep first 'exceptions' handlers long ops = 0; Iterator it = eventHandlers.iterator(); while (it.hasNext()) { int eventType = it.next(); // exceptions.Contains(handler->GetEventType()) — O(m) linear scan boolean keep = false; for (int e : exc) { ops++; if (e == eventType) { keep = true; break; } } if (!keep) it.remove(); } return ops; } /** FAST: HashSet built once — O(m) setup, O(1) per handler — O(n+m) total */ static long unsubFast(int handlers, int exceptions) { List eventHandlers = new ArrayList<>(); for (int i = 0; i < handlers; i++) eventHandlers.add(i); List exc = new ArrayList<>(); for (int i = 0; i < exceptions; i++) exc.add(i); // FIX: build HashSet once O(m) Set excSet = new HashSet<>(exc); long ops = 0; Iterator it = eventHandlers.iterator(); while (it.hasNext()) { int eventType = it.next(); ops++; // O(1) hash lookup if (!excSet.contains(eventType)) it.remove(); } return ops; } // ── Harness ────────────────────────────────────────────────────────────── static void bench(String label, Runnable slowFn, Runnable fastFn, long slowOps, long fastOps) { slowFn.run(); fastFn.run(); // warm up long t0 = System.nanoTime(); slowFn.run(); long slowMs = (System.nanoTime() - t0) / 1_000_000; long t1 = System.nanoTime(); fastFn.run(); long fastMs = (System.nanoTime() - t1) / 1_000_000; double ratio = fastOps > 0 ? (double) slowOps / fastOps : 0; System.out.printf(" %-40s slow: %4dms (%,d ops) fast: %4dms (%,d ops) ops-speedup: %.0fx%n", label, slowMs, slowOps, fastMs, fastOps, ratio); } public static void main(String[] args) { System.out.println("=== UNIT dry-0001/0002: Dry/Urho3D CWE-407 UI + event system ==="); System.out.println(); final int SEL = 2000; // current selections (large multi-select list) final int NEW = 1500; // new selection set final int HDLRS = 500; // event handlers per object final int EXCEP = 50; // exceptions list size long s0 = listViewSlow(SEL, NEW); long f0 = listViewFast(SEL, NEW); bench("dry-0001 ListView::SetSelections", () -> listViewSlow(SEL, NEW), () -> listViewFast(SEL, NEW), s0, f0); long s1 = unsubSlow(HDLRS, EXCEP); long f1 = unsubFast(HDLRS, EXCEP); bench("dry-0002 Object::UnsubscribeFromAllEventsExcept", () -> unsubSlow(HDLRS, EXCEP), () -> unsubFast(HDLRS, EXCEP), s1, f1); System.out.println(); int pass = 0; assert s0 > f0 * 5 : "dry-0001 expected >5x op reduction"; pass++; assert s1 > f1 * 5 : "dry-0002 expected >5x op reduction"; pass++; assert listViewFast(100, 80) >= 0 : "dry-0001 fast broken"; pass++; assert unsubFast(100, 20) >= 0 : "dry-0002 fast broken"; pass++; System.out.printf("%d/4 PASS — dry-0001/0002 confirmed: CWE-407 in ListView + event system%n", pass); System.out.printf("Defect hotpaths: ListView::SetSelections(), Object::UnsubscribeFromAllEventsExcept()%n"); } }