wave15: sm-0003/0004 + threejs-0006 + varnish-0002 + mongodb-0008 — 533/240

This commit is contained in:
russell@unturf.com 2026-03-27 19:22:37 -04:00
parent 7146714143
commit 31850d5ef6
13 changed files with 1893 additions and 9 deletions

View file

@ -0,0 +1,275 @@
package unit;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
/**
* ThreeJSObject3DTest threejs-0006
*
* Models the CWE-407 defect in Three.js EventDispatcher.addEventListener():
*
* Defective: listeners[type] is a plain Array.
* addEventListener() calls listeners[type].indexOf(listener) before push.
* When N unique listeners are added for the same type:
* O(0) + O(1) + ... + O(N-1) = O(N²)
*
* Fixed: listeners[type] is a Set (Map<type, Set<listener>>).
* addEventListener() calls listenerSet.has(listener) O(1).
* Total for N additions: O(N).
*
* "Listener" is modelled as an Integer ID. Comparison counts are instrumented
* explicitly not wall-clock timing.
*
* Run: javac -d . ThreeJSObject3DTest.java && java -ea unit.ThreeJSObject3DTest
*/
public class ThreeJSObject3DTest {
// -----------------------------------------------------------------------
// Defective EventDispatcher: listeners[type] is ArrayList
// indexOf(listener) called before every push O(N) per add
// -----------------------------------------------------------------------
static class DefectiveEventDispatcher {
private final Map<String, ArrayList<Integer>> listeners = new HashMap<>();
long comparisons = 0;
/**
* Models: if (listeners[type].indexOf(listener) === -1) listeners[type].push(listener)
*/
void addEventListener(String type, int listener) {
listeners.computeIfAbsent(type, k -> new ArrayList<>());
ArrayList<Integer> arr = listeners.get(type);
// O(N) indexOf scan the defect
for (int existing : arr) {
comparisons++;
if (existing == listener) return; // already present
}
arr.add(listener);
}
/**
* Models: listeners[type] !== undefined && listeners[type].indexOf(listener) !== -1
*/
boolean hasEventListener(String type, int listener) {
ArrayList<Integer> arr = listeners.get(type);
if (arr == null) return false;
for (int existing : arr) {
comparisons++;
if (existing == listener) return true;
}
return false;
}
int listenerCount(String type) {
ArrayList<Integer> arr = listeners.get(type);
return arr == null ? 0 : arr.size();
}
}
// -----------------------------------------------------------------------
// Fixed EventDispatcher: listeners[type] is Set
// has(listener) is O(1) amortised
// -----------------------------------------------------------------------
static class FixedEventDispatcher {
private final Map<String, Set<Integer>> listenerSets = new HashMap<>();
long lookups = 0;
void addEventListener(String type, int listener) {
listenerSets.computeIfAbsent(type, k -> new HashSet<>());
Set<Integer> set = listenerSets.get(type);
lookups++; // one O(1) hash probe
set.add(listener);
}
boolean hasEventListener(String type, int listener) {
Set<Integer> set = listenerSets.get(type);
if (set == null) return false;
lookups++;
return set.contains(listener);
}
int listenerCount(String type) {
Set<Integer> set = listenerSets.get(type);
return set == null ? 0 : set.size();
}
}
// -----------------------------------------------------------------------
// Test 1 correctness: both dispatchers agree on listener count and has()
// -----------------------------------------------------------------------
static void test1_correctness() {
DefectiveEventDispatcher slow = new DefectiveEventDispatcher();
FixedEventDispatcher fast = new FixedEventDispatcher();
// Add 10 unique listeners for "dispose", with 3 duplicates
int[] listeners = {1, 2, 3, 4, 5, 1, 6, 7, 2, 8, 9, 10, 5};
for (int l : listeners) {
slow.addEventListener("dispose", l);
fast.addEventListener("dispose", l);
}
int slowCount = slow.listenerCount("dispose");
int fastCount = fast.listenerCount("dispose");
assert slowCount == 10 : "defective: expected 10 unique listeners, got " + slowCount;
assert fastCount == 10 : "fixed: expected 10 unique listeners, got " + fastCount;
// Both should agree on hasEventListener
for (int l : new int[]{1, 5, 10, 99}) {
boolean inSlow = slow.hasEventListener("dispose", l);
boolean inFast = fast.hasEventListener("dispose", l);
assert inSlow == inFast
: "disagreement on listener " + l + ": slow=" + inSlow + " fast=" + inFast;
}
System.out.printf("test1: 10 unique listeners (13 adds with 3 dupes), both agree — CORRECT%n");
}
// -----------------------------------------------------------------------
// Test 2 ratio >= 5x at N=100 unique listeners, same event type
// -----------------------------------------------------------------------
static void test2_ratioAt100() {
int N = 100;
DefectiveEventDispatcher slow = new DefectiveEventDispatcher();
FixedEventDispatcher fast = new FixedEventDispatcher();
for (int i = 0; i < N; i++) {
slow.addEventListener("resize", i);
fast.addEventListener("resize", i);
}
double ratio = (double) slow.comparisons / Math.max(1, fast.lookups);
System.out.printf("test2: N=%d defect=%d fixed=%d ratio=%.1fx%n",
N, slow.comparisons, fast.lookups, ratio);
assert ratio >= 5.0 : "expected ratio >= 5x at N=100, got " + ratio;
}
// -----------------------------------------------------------------------
// Test 3 ratio >= 50x at N=500 unique listeners, same event type
// -----------------------------------------------------------------------
static void test3_ratioAt500() {
int N = 500;
DefectiveEventDispatcher slow = new DefectiveEventDispatcher();
FixedEventDispatcher fast = new FixedEventDispatcher();
for (int i = 0; i < N; i++) {
slow.addEventListener("update", i);
fast.addEventListener("update", i);
}
double ratio = (double) slow.comparisons / Math.max(1, fast.lookups);
System.out.printf("test3: N=%d defect=%d fixed=%d ratio=%.1fx%n",
N, slow.comparisons, fast.lookups, ratio);
assert ratio >= 50.0 : "expected ratio >= 50x at N=500, got " + ratio;
}
// -----------------------------------------------------------------------
// Test 4 quadratic growth: doubling N should roughly quadruple defect ops
// -----------------------------------------------------------------------
static void test4_quadraticGrowth() {
int N1 = 200, N2 = 400;
DefectiveEventDispatcher slow1 = new DefectiveEventDispatcher();
DefectiveEventDispatcher slow2 = new DefectiveEventDispatcher();
FixedEventDispatcher fast1 = new FixedEventDispatcher();
FixedEventDispatcher fast2 = new FixedEventDispatcher();
for (int i = 0; i < N1; i++) { slow1.addEventListener("change", i); fast1.addEventListener("change", i); }
for (int i = 0; i < N2; i++) { slow2.addEventListener("change", i); fast2.addEventListener("change", i); }
double defectGrowth = (double) slow2.comparisons / Math.max(1, slow1.comparisons);
double fixedGrowth = (double) fast2.lookups / Math.max(1, fast1.lookups);
System.out.printf("test4: N=%d→%d defect %d→%d (%.2fx) fixed %d→%d (%.2fx)%n",
N1, N2,
slow1.comparisons, slow2.comparisons, defectGrowth,
fast1.lookups, fast2.lookups, fixedGrowth);
// Defect should grow quadratically (~4x when N doubles)
assert defectGrowth >= 3.5
: "expected defect ~4x when N doubles, got " + defectGrowth;
// Fixed should grow linearly (~2x when N doubles)
assert fixedGrowth >= 1.5 && fixedGrowth <= 2.5
: "fixed should grow ~2x when N doubles, got " + fixedGrowth;
}
// -----------------------------------------------------------------------
// Test 5 duplicate suppression: adding same listener M times should not
// grow the listener array, and total comparison cost is O(M×current_size)
// -----------------------------------------------------------------------
static void test5_duplicateSuppression() {
int N = 20; // unique listeners
int M = 50; // times each listener is re-added
DefectiveEventDispatcher slow = new DefectiveEventDispatcher();
FixedEventDispatcher fast = new FixedEventDispatcher();
// Add N unique listeners first
for (int i = 0; i < N; i++) {
slow.addEventListener("click", i);
fast.addEventListener("click", i);
}
assert slow.listenerCount("click") == N;
assert fast.listenerCount("click") == N;
// Re-add all N listeners M times (simulating sloppy register-on-each-render)
for (int r = 0; r < M; r++) {
for (int i = 0; i < N; i++) {
slow.addEventListener("click", i);
fast.addEventListener("click", i);
}
}
// Size should remain N
assert slow.listenerCount("click") == N
: "defective size wrong after re-adds: " + slow.listenerCount("click");
assert fast.listenerCount("click") == N
: "fixed size wrong after re-adds: " + fast.listenerCount("click");
double ratio = (double) slow.comparisons / Math.max(1, fast.lookups);
System.out.printf("test5: N=%d M=%d re-adds defect=%d fixed=%d ratio=%.1fx — size both=%d%n",
N, M, slow.comparisons, fast.lookups, ratio, N);
assert ratio >= 5.0 : "expected ratio >= 5x for re-add pattern, got " + ratio;
}
// -----------------------------------------------------------------------
// Main
// -----------------------------------------------------------------------
public static void main(String[] args) {
System.out.println("=== ThreeJSObject3DTest ===");
System.out.println("Modelling CWE-407 threejs-0006: EventDispatcher.addEventListener() indexOf O(N²)");
System.out.println("Location: src/core/EventDispatcher.js addEventListener() + hasEventListener()");
System.out.println();
test1_correctness();
System.out.println(" PASS test1_correctness");
test2_ratioAt100();
System.out.println(" PASS test2_ratioAt100");
test3_ratioAt500();
System.out.println(" PASS test3_ratioAt500");
test4_quadraticGrowth();
System.out.println(" PASS test4_quadraticGrowth");
test5_duplicateSuppression();
System.out.println(" PASS test5_duplicateSuppression");
System.out.println();
System.out.println("5/5 PASS");
}
}