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 hits; public final int probeCount; public Result(List 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 objects, double lo, double hi) { List 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 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 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 buildScene(int n, double maxPos) { List 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; } }