Each MOAD now has a synthetic defective specimen and fixed specimen proven from first principles across three test tiers: Unit (tests/unit/Moad000X*.java): - Correctness: defective and fixed produce identical functional output - Defect behavior: defective specimen exhibits the defect (measurable) - Fix behavior: fixed specimen eliminates the defect Integration (tests/integration/AllMoadsIntegrationTest.java): - All 9 MOADs proven at medium scale (N=500-2000) - MOAD-0001: O(N^2) vs O(N) list scan at N=1000 - MOAD-0002: 500 sessions trample each other (defective) vs coexist (fixed) - MOAD-0003: 250 anonymous requests leak auth identity (defective) vs zero (fixed) - MOAD-0004: 3000 credential exposures across 1000 requests (defective) vs zero (fixed) - MOAD-0005: 500 computes for 500 concurrent misses vs exactly 1 - MOAD-0006: all 500 passwords extractable from DB (defective) vs unextractable (fixed) - MOAD-0007: N=2000 spatial objects, defective visits all 2000 vs O(log N + k) - MOAD-0009: 990 wasted firings for 1000 ticks / 10 events vs zero waste - MOAD-0011: 10240 NFA steps vs 13 steps on N=12 adversarial input (788x) Functional (tests/functional/AllMoadsFunctionalTest.java): - MOAD-0005: real-thread contention proves herd (defective >1 compute, fixed exactly 1) - MOAD-0007: N=50000 spatial objects, 50M defective probes vs 516K fixed (97x speedup) - MOAD-0009: 10000 ticks / 10 events, 9990 wasted firings vs zero (1000x ratio) - MOAD-0011: N=16 adversarial, 163840 defective steps vs 17 fixed (9638x ratio) Support algorithms (tests/support/Moad000X*.java): - Moad0002Algorithm: shared mutable global state (DefectiveAudioSystem / FixedAudioSystem + Context) - Moad0003Algorithm: ThreadLocal not cleared (handleDefective / handleFixed with finally) - Moad0004Algorithm: HTTP headers logged verbatim (logDefective / logFixed with CREDENTIAL_HEADERS denylist) - Moad0005Algorithm: get+null+compute+put (DefectiveCache HashMap / FixedCache ConcurrentHashMap.computeIfAbsent) - Moad0006Algorithm: Base64 password storage (DefectiveCredentialStore / FixedCredentialStore SHA-256+salt) - Moad0007Algorithm: linear spatial scan (queryDefective list / queryFixed sorted array + binary search) - Moad0009Algorithm: timer-driven polling (runDefectiveScheduler / runFixedEventDriven) - Moad0011Algorithm: PCRE nested quantifiers (matchDefective backtracking NFA / matchFixed linear NFA) Makefile: added unit-moad-0002 through unit-moad-0011 targets, integration-all-moads, functional-all-moads. integration and functional targets now depend on all-MOADs variants.
183 lines
8.2 KiB
Java
183 lines
8.2 KiB
Java
package unit;
|
|
|
|
import support.Moad0005Algorithm;
|
|
import support.Moad0005Algorithm.DefectiveCache;
|
|
import support.Moad0005Algorithm.FixedCache;
|
|
|
|
/**
|
|
* Unit tests for MOAD-0005: CWE-362 — A Thundering Herd.
|
|
*
|
|
* Proves from first principles:
|
|
* 1. Defective: N callers all seeing a null cache entry all invoke
|
|
* expensiveCompute() — N computes instead of 1. This is the herd.
|
|
* 2. Fixed: ConcurrentHashMap.computeIfAbsent() ensures exactly 1 compute
|
|
* regardless of how many callers miss simultaneously.
|
|
* 3. Both return the same (correct) value for any key.
|
|
* 4. Sequential access on a warm cache: both call compute exactly once.
|
|
*
|
|
* The single-threaded simulation models the race window: all callers read
|
|
* before any caller's put() completes. Integration tests use real threads.
|
|
*
|
|
* No build tool required. Compile and run:
|
|
*
|
|
* cd tests
|
|
* java -m jdk.compiler/com.sun.tools.javac.Main -cp . \
|
|
* support/Moad0005Algorithm.java unit/Moad0005UnitTest.java
|
|
* java -cp . unit.Moad0005UnitTest
|
|
*/
|
|
public class Moad0005UnitTest {
|
|
|
|
private static int passed = 0;
|
|
private static int failed = 0;
|
|
|
|
public static void main(String[] args) {
|
|
System.out.println("=== Moad0005UnitTest (A Thundering Herd) ===\n");
|
|
|
|
System.out.println("-- Correctness: both caches return correct value --");
|
|
testDefectiveReturnsCorrectValue();
|
|
testFixedReturnsCorrectValue();
|
|
testBothReturnSameValue();
|
|
|
|
System.out.println("\n-- Defect: N concurrent misses produce N computes --");
|
|
testDefectiveConcurrentMissN10();
|
|
testDefectiveConcurrentMissN50();
|
|
testDefectiveComputeCountScalesWithCallers();
|
|
|
|
System.out.println("\n-- Fix: N concurrent misses produce exactly 1 compute --");
|
|
testFixedConcurrentMissN10();
|
|
testFixedConcurrentMissN50();
|
|
testFixedComputeCountAlwaysOne();
|
|
|
|
System.out.println("\n-- Warm cache: both compute exactly once sequentially --");
|
|
testDefectiveWarmCacheNoRecompute();
|
|
testFixedWarmCacheNoRecompute();
|
|
|
|
System.out.printf("\n%d passed, %d failed%n", passed, failed);
|
|
if (failed > 0) System.exit(1);
|
|
}
|
|
|
|
// ── Correctness ───────────────────────────────────────────────────────────
|
|
|
|
static void testDefectiveReturnsCorrectValue() {
|
|
Moad0005Algorithm.resetCounter();
|
|
DefectiveCache cache = new DefectiveCache();
|
|
String val = cache.getOrCompute("foo");
|
|
assertEqual("defective: returns 'value-for-foo'", "value-for-foo", val);
|
|
}
|
|
|
|
static void testFixedReturnsCorrectValue() {
|
|
Moad0005Algorithm.resetCounter();
|
|
FixedCache cache = new FixedCache();
|
|
String val = cache.getOrCompute("foo");
|
|
assertEqual("fixed: returns 'value-for-foo'", "value-for-foo", val);
|
|
}
|
|
|
|
static void testBothReturnSameValue() {
|
|
Moad0005Algorithm.resetCounter();
|
|
DefectiveCache def = new DefectiveCache();
|
|
FixedCache fix = new FixedCache();
|
|
String dVal = def.getOrCompute("bar");
|
|
Moad0005Algorithm.resetCounter();
|
|
String fVal = fix.getOrCompute("bar");
|
|
assertEqual("both: return same value for 'bar'", dVal, fVal);
|
|
}
|
|
|
|
// ── Defect ────────────────────────────────────────────────────────────────
|
|
|
|
static void testDefectiveConcurrentMissN10() {
|
|
int computes = Moad0005Algorithm.simulateDefectiveConcurrentMiss(10, "key");
|
|
// DEFECT: all 10 callers see null simultaneously → 10 computes
|
|
assertEqual("defective: 10 concurrent misses cause 10 computes (herd)", 10, computes);
|
|
}
|
|
|
|
static void testDefectiveConcurrentMissN50() {
|
|
int computes = Moad0005Algorithm.simulateDefectiveConcurrentMiss(50, "key");
|
|
// DEFECT: 50 concurrent misses → 50 computes
|
|
assertEqual("defective: 50 concurrent misses cause 50 computes (herd)", 50, computes);
|
|
}
|
|
|
|
static void testDefectiveComputeCountScalesWithCallers() {
|
|
int c10 = Moad0005Algorithm.simulateDefectiveConcurrentMiss(10, "key");
|
|
int c20 = Moad0005Algorithm.simulateDefectiveConcurrentMiss(20, "key");
|
|
// DEFECT: compute count scales linearly with caller count (N wasted computes)
|
|
assertTrue("defective: doubling callers doubles computes",
|
|
c20 == 2 * c10, "c10=" + c10 + " c20=" + c20);
|
|
}
|
|
|
|
// ── Fix ───────────────────────────────────────────────────────────────────
|
|
|
|
static void testFixedConcurrentMissN10() {
|
|
int computes = Moad0005Algorithm.simulateFixedConcurrentMiss(10, "key");
|
|
// FIX: computeIfAbsent — exactly 1 compute regardless of caller count
|
|
assertEqual("fixed: 10 concurrent misses cause exactly 1 compute", 1, computes);
|
|
}
|
|
|
|
static void testFixedConcurrentMissN50() {
|
|
int computes = Moad0005Algorithm.simulateFixedConcurrentMiss(50, "key");
|
|
assertEqual("fixed: 50 concurrent misses cause exactly 1 compute", 1, computes);
|
|
}
|
|
|
|
static void testFixedComputeCountAlwaysOne() {
|
|
int c10 = Moad0005Algorithm.simulateFixedConcurrentMiss(10, "key");
|
|
int c100 = Moad0005Algorithm.simulateFixedConcurrentMiss(100, "key");
|
|
// FIX: compute count does not scale — always 1
|
|
assertEqual("fixed: 10 callers — 1 compute", 1, c10);
|
|
assertEqual("fixed: 100 callers — 1 compute", 1, c100);
|
|
}
|
|
|
|
// ── Warm cache ────────────────────────────────────────────────────────────
|
|
|
|
static void testDefectiveWarmCacheNoRecompute() {
|
|
Moad0005Algorithm.resetCounter();
|
|
DefectiveCache cache = new DefectiveCache();
|
|
cache.getOrCompute("warm"); // cold miss — 1 compute
|
|
int afterFirst = Moad0005Algorithm.COMPUTE_CALLS.get();
|
|
cache.getOrCompute("warm"); // warm hit — 0 compute
|
|
int afterSecond = Moad0005Algorithm.COMPUTE_CALLS.get();
|
|
assertEqual("defective: first call computes once", 1, afterFirst);
|
|
assertEqual("defective: second sequential call hits cache", 1, afterSecond);
|
|
}
|
|
|
|
static void testFixedWarmCacheNoRecompute() {
|
|
Moad0005Algorithm.resetCounter();
|
|
FixedCache cache = new FixedCache();
|
|
cache.getOrCompute("warm"); // cold miss — 1 compute
|
|
int afterFirst = Moad0005Algorithm.COMPUTE_CALLS.get();
|
|
cache.getOrCompute("warm"); // warm hit — 0 compute
|
|
int afterSecond = Moad0005Algorithm.COMPUTE_CALLS.get();
|
|
assertEqual("fixed: first call computes once", 1, afterFirst);
|
|
assertEqual("fixed: second sequential call hits cache", 1, afterSecond);
|
|
}
|
|
|
|
// ── Helpers ───────────────────────────────────────────────────────────────
|
|
|
|
static void assertEqual(String label, int expected, int actual) {
|
|
if (expected == actual) {
|
|
System.out.printf(" PASS: %s%n", label);
|
|
passed++;
|
|
} else {
|
|
System.out.printf(" FAIL: %s — expected %d, got %d%n", label, expected, actual);
|
|
failed++;
|
|
}
|
|
}
|
|
|
|
static void assertEqual(String label, String expected, String actual) {
|
|
if (expected == null ? actual == null : expected.equals(actual)) {
|
|
System.out.printf(" PASS: %s%n", label);
|
|
passed++;
|
|
} else {
|
|
System.out.printf(" FAIL: %s — expected '%s', got '%s'%n", label, expected, actual);
|
|
failed++;
|
|
}
|
|
}
|
|
|
|
static void assertTrue(String label, boolean condition, String detail) {
|
|
if (condition) {
|
|
System.out.printf(" PASS: %s%n", label);
|
|
passed++;
|
|
} else {
|
|
System.out.printf(" FAIL: %s — %s%n", label, detail);
|
|
failed++;
|
|
}
|
|
}
|
|
}
|