164 lines
6.7 KiB
Java
164 lines
6.7 KiB
Java
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<unsigned>.Contains() O(n)
|
|
* fired on every UI multi-select change
|
|
*
|
|
* dry-0002: Object::UnsubscribeFromAllEventsExcept() — PODVector<StringHash>.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<Integer> selections = new ArrayList<>();
|
|
for (int i = 0; i < selectionSize; i++) selections.add(i);
|
|
|
|
List<Integer> 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<Integer> 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<Integer> selections = new ArrayList<>();
|
|
Set<Integer> selSet = new HashSet<>();
|
|
for (int i = 0; i < selectionSize; i++) { selections.add(i); selSet.add(i); }
|
|
|
|
List<Integer> indices = new ArrayList<>();
|
|
for (int i = selectionSize / 2; i < selectionSize / 2 + newSize; i++) indices.add(i);
|
|
Set<Integer> idxSet = new HashSet<>(indices);
|
|
|
|
long ops = 0;
|
|
|
|
// Loop 1: remove stale — O(1) per item
|
|
Iterator<Integer> 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<Integer> eventHandlers = new ArrayList<>();
|
|
for (int i = 0; i < handlers; i++) eventHandlers.add(i);
|
|
|
|
List<Integer> exc = new ArrayList<>();
|
|
for (int i = 0; i < exceptions; i++) exc.add(i); // keep first 'exceptions' handlers
|
|
|
|
long ops = 0;
|
|
Iterator<Integer> 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<Integer> eventHandlers = new ArrayList<>();
|
|
for (int i = 0; i < handlers; i++) eventHandlers.add(i);
|
|
|
|
List<Integer> exc = new ArrayList<>();
|
|
for (int i = 0; i < exceptions; i++) exc.add(i);
|
|
|
|
// FIX: build HashSet once O(m)
|
|
Set<Integer> excSet = new HashSet<>(exc);
|
|
|
|
long ops = 0;
|
|
Iterator<Integer> 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");
|
|
}
|
|
}
|