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.
139 lines
5.2 KiB
Java
139 lines
5.2 KiB
Java
package support;
|
|
|
|
import java.util.ArrayList;
|
|
import java.util.Arrays;
|
|
import java.util.List;
|
|
|
|
/**
|
|
* MOAD-0007: CWE-407 — A Flatland Defect.
|
|
*
|
|
* Defect: spatial data with inherent geometric structure queried by O(N) linear
|
|
* scan. Every spatial query — raycasting, collision detection, nearest-neighbor
|
|
* — visits all N objects, ignoring the structure that could prune to O(log N).
|
|
*
|
|
* Confirmed instances: three.js (flat mesh array), OGRE3D, Panda3D, unsandbox.
|
|
* Each object added to the scene makes every subsequent query more expensive.
|
|
*
|
|
* Fix: organize objects into a spatial index (1D sorted array shown here; BVH
|
|
* or k-d tree in production). Queries skip entire subtrees via binary search.
|
|
*
|
|
* This specimen models a 1-D spatial query: N objects occupy positions along an
|
|
* axis; a query asks for all objects within a range [lo, hi]. The defective
|
|
* implementation visits all N objects; the fixed implementation binary-searches
|
|
* to the first object in range and scans only the matching interval.
|
|
*
|
|
* Scanner detects: for-each / for-i loop over a list of spatial objects inside
|
|
* a query/intersect/raycast/collision method, with no early-exit or index prune.
|
|
*/
|
|
public class Moad0007Algorithm {
|
|
|
|
/** A spatial object with a 1-D position (x-axis). */
|
|
public static final class SpatialObject {
|
|
public final String id;
|
|
public final double position;
|
|
|
|
public SpatialObject(String id, double position) {
|
|
this.id = id;
|
|
this.position = position;
|
|
}
|
|
|
|
@Override public String toString() {
|
|
return id + "@" + position;
|
|
}
|
|
}
|
|
|
|
/** Result of a spatial range query: matching objects and probe count. */
|
|
public static final class Result {
|
|
public final List<SpatialObject> hits;
|
|
public final int probeCount;
|
|
|
|
public Result(List<SpatialObject> hits, int probeCount) {
|
|
this.hits = hits;
|
|
this.probeCount = probeCount;
|
|
}
|
|
}
|
|
|
|
// ── Defective: linear scan O(N) ───────────────────────────────────────────
|
|
|
|
/**
|
|
* Range query using a flat list: visits every object regardless of position.
|
|
* DEFECT: probeCount == N for all queries, even when zero objects match.
|
|
*
|
|
* @param objects unsorted flat list of spatial objects
|
|
* @param lo range lower bound (inclusive)
|
|
* @param hi range upper bound (inclusive)
|
|
*/
|
|
public static Result queryDefective(List<SpatialObject> objects, double lo, double hi) {
|
|
List<SpatialObject> hits = new ArrayList<>();
|
|
int probes = 0;
|
|
for (SpatialObject obj : objects) { // DEFECT: visit all N
|
|
probes++;
|
|
if (obj.position >= lo && obj.position <= hi) {
|
|
hits.add(obj);
|
|
}
|
|
}
|
|
return new Result(hits, probes);
|
|
}
|
|
|
|
// ── Fixed: binary search O(log N + k) ────────────────────────────────────
|
|
|
|
/**
|
|
* Builds a sorted spatial index from a flat list.
|
|
* Call once per scene; query many times.
|
|
*
|
|
* @param objects objects in any order
|
|
* @return new array sorted by position ascending
|
|
*/
|
|
public static SpatialObject[] buildIndex(List<SpatialObject> objects) {
|
|
SpatialObject[] arr = objects.toArray(new SpatialObject[0]);
|
|
Arrays.sort(arr, (a, b) -> Double.compare(a.position, b.position));
|
|
return arr;
|
|
}
|
|
|
|
/**
|
|
* Range query using sorted index: binary-searches to first object in [lo, hi],
|
|
* then scans forward until position exceeds hi.
|
|
*
|
|
* FIX: probeCount == O(log N + k) where k is the number of hits.
|
|
* For a query that returns zero hits, probeCount is at most O(log N).
|
|
*
|
|
* @param index sorted array produced by buildIndex()
|
|
* @param lo range lower bound (inclusive)
|
|
* @param hi range upper bound (inclusive)
|
|
*/
|
|
public static Result queryFixed(SpatialObject[] index, double lo, double hi) {
|
|
List<SpatialObject> hits = new ArrayList<>();
|
|
int probes = 0;
|
|
|
|
// Binary search for first position >= lo
|
|
int left = 0, right = index.length;
|
|
while (left < right) {
|
|
probes++;
|
|
int mid = (left + right) >>> 1;
|
|
if (index[mid].position < lo) {
|
|
left = mid + 1;
|
|
} else {
|
|
right = mid;
|
|
}
|
|
}
|
|
|
|
// Linear scan from 'left' until position > hi
|
|
for (int i = left; i < index.length; i++) {
|
|
probes++;
|
|
if (index[i].position > hi) break; // FIX: early exit — skip tail
|
|
hits.add(index[i]);
|
|
}
|
|
|
|
return new Result(hits, probes);
|
|
}
|
|
|
|
/** Build a list of N objects uniformly spaced in [0, maxPos]. */
|
|
public static List<SpatialObject> buildScene(int n, double maxPos) {
|
|
List<SpatialObject> objects = new ArrayList<>(n);
|
|
for (int i = 0; i < n; i++) {
|
|
double pos = (n == 1) ? 0 : maxPos * i / (n - 1);
|
|
objects.add(new SpatialObject("obj-" + i, pos));
|
|
}
|
|
return objects;
|
|
}
|
|
}
|