import java.util.*; /** * CWE-407 unit test for Bullet Physics btGhostObject defect. * * bullet3-0001: src/BulletCollision/CollisionDispatch/btGhostObject.cpp * btGhostObject::addOverlappingObjectInternal() and removeOverlappingObjectInternal() * use btAlignedObjectArray::findLinearSearch (O(N) pointer scan) on every broadphase * pair-update callback. With N objects overlapping a single ghost, each physics tick * calls addOverlappingObjectInternal N times → O(N²) total per tick. * The source comment even acknowledges the problem: * "if this linearSearch becomes too slow (too many overlapping objects) * we should add a more appropriate data structure" * Fix: maintain a btHashMap shadow index → O(1) add/remove. * * Severity: HIGH — called every broadphase tick for every ghost-object pair. * At N=500 overlapping objects the per-tick work grows 500×. */ public class Bullet3Test { // --- defect simulation: linear array dedup --- static boolean addOverlapping_linear(List overlapping, long ptr) { // O(N) scan — models findLinearSearch for (long p : overlapping) { if (p == ptr) return false; // already present } overlapping.add(ptr); return true; } static boolean removeOverlapping_linear(List overlapping, long ptr) { int index = -1; for (int i = 0; i < overlapping.size(); i++) { if (overlapping.get(i) == ptr) { index = i; break; } } if (index < 0) return false; // swap with last overlapping.set(index, overlapping.get(overlapping.size() - 1)); overlapping.remove(overlapping.size() - 1); return true; } // --- fix simulation: HashMap dedup --- static boolean addOverlapping_hash(List overlapping, Map index, long ptr) { if (index.containsKey(ptr)) return false; // O(1) index.put(ptr, overlapping.size()); overlapping.add(ptr); return true; } static boolean removeOverlapping_hash(List overlapping, Map index, long ptr) { Integer idx = index.get(ptr); // O(1) if (idx == null) return false; int lastI = overlapping.size() - 1; if (idx != lastI) { long moved = overlapping.get(lastI); overlapping.set(idx, moved); index.put(moved, idx); } overlapping.remove(lastI); index.remove(ptr); return true; } // --- correctness check --- static void assertEqualSets(List a, List b, String label) throws Exception { Set sa = new HashSet<>(a); Set sb = new HashSet<>(b); if (!sa.equals(sb)) { throw new Exception("FAIL " + label + ": sets differ. a=" + sa + " b=" + sb); } } static void testBullet30001() throws Exception { int N = 500; // Build base state: N objects in ghost overlap list List linearList = new ArrayList<>(); List hashList = new ArrayList<>(); Map hashIdx = new HashMap<>(); for (long i = 0; i < N; i++) { addOverlapping_linear(linearList, i); addOverlapping_hash(hashList, hashIdx, i); } assertEqualSets(linearList, hashList, "initial fill"); // Attempt to re-add all (dedup test) — N² work in linear, O(N) in hash long linearOps = 0, hashOps = 0; for (long i = 0; i < N; i++) { // linear: scan all N before deciding "already there" boolean found = false; for (long p : linearList) { linearOps++; if (p == i) { found = true; break; } } // hash: O(1) hashOps++; addOverlapping_hash(hashList, hashIdx, i); } assertEqualSets(linearList, hashList, "after re-add"); // Remove even-indexed objects for (long i = 0; i < N; i += 2) { removeOverlapping_linear(linearList, i); removeOverlapping_hash(hashList, hashIdx, i); } assertEqualSets(linearList, hashList, "after remove evens"); // Verify index consistency for (int i = 0; i < hashList.size(); i++) { long ptr = hashList.get(i); Integer stored = hashIdx.get(ptr); if (stored == null || stored != i) { throw new Exception("FAIL index consistency at i=" + i + " ptr=" + ptr + " stored=" + stored); } } // Performance ratio double ratio = (double) linearOps / hashOps; System.out.printf("bullet3-0001 PASS N=%d linear_ops=%d hash_ops=%d ratio=%.1fx%n", N, linearOps, hashOps, ratio); if (ratio < 2.0) { throw new Exception("FAIL: expected ratio >= 2x, got " + ratio); } } public static void main(String[] args) throws Exception { testBullet30001(); System.out.println("All bullet3 tests PASS"); } }