package unit; import java.util.*; /** * BulletTest — Java analogs of three Bullet Physics CWE-407 defects. * * Defect 1 (bullet-0001): btGhostObject — findLinearSearch on m_overlappingObjects * in addOverlappingObjectInternal / removeOverlappingObjectInternal. * Called per broadphase pair per simulation step. * Slow: ArrayList.contains / indexOf — O(N) per call. * Fast: HashMap with index — O(1) per call. * * Defect 2 (bullet-0002): btCollisionObject::checkCollideWithOverride — * findLinearSearch on m_objectsWithoutCollisionCheck. * Called inside needsCollision() for every pair in processAllOverlappingPairs. * Slow: ArrayList.contains — O(E) per pair. * Fast: HashSet.contains — O(1) per pair. * * Defect 3 (bullet-0003): btSortedOverlappingPairCache::findPair / * removeOverlappingPair — findLinearSearch on m_overlappingPairArray. * Called during broadphase pair removal phase. * Slow: ArrayList.indexOf — O(P) per removal. * Fast: HashMap — O(1) per removal. */ public class BulletTest { static void bench(String label, Runnable slow, Runnable fast, long sOps, long fOps) { // warmup slow.run(); fast.run(); long t0 = System.nanoTime(); slow.run(); long sMs = (System.nanoTime() - t0) / 1_000_000; long t1 = System.nanoTime(); fast.run(); long fMs = (System.nanoTime() - t1) / 1_000_000; double ratio = fOps > 0 ? (double) sOps / fOps : 0; System.out.printf(" %-56s slow:%5dms (%,d ops) fast:%5dms (%,d ops) speedup:%.0fx%n", label, sMs, sOps, fMs, fOps, ratio); } // ----------------------------------------------------------------------- // bullet-0001: btGhostObject overlapping-object add/remove per broadphase step // ----------------------------------------------------------------------- static void benchGhostOverlapping(int P, int steps) { // P objects start overlapping a ghost, then all leave — simulated over steps // SLOW: ArrayList membership check — O(P) per add and remove Runnable slow = () -> { List overlapping = new ArrayList<>(P); Object[] bodies = new Object[P]; for (int i = 0; i < P; i++) bodies[i] = new Object(); for (int s = 0; s < steps; s++) { // add phase (each new overlap checks if already present) overlapping.clear(); for (int i = 0; i < P; i++) { if (!overlapping.contains(bodies[i])) // O(P) overlapping.add(bodies[i]); } // remove phase for (int i = 0; i < P; i++) { int idx = overlapping.indexOf(bodies[i]); // O(P) if (idx >= 0) { overlapping.set(idx, overlapping.get(overlapping.size() - 1)); overlapping.remove(overlapping.size() - 1); } } } }; // FAST: HashMap index — O(1) per add and remove Runnable fast = () -> { List overlapping = new ArrayList<>(P); Map index = new HashMap<>(P * 2); Object[] bodies = new Object[P]; for (int i = 0; i < P; i++) bodies[i] = new Object(); for (int s = 0; s < steps; s++) { // add phase overlapping.clear(); index.clear(); for (int i = 0; i < P; i++) { if (!index.containsKey(bodies[i])) { // O(1) index.put(bodies[i], overlapping.size()); overlapping.add(bodies[i]); } } // remove phase for (int i = P - 1; i >= 0; i--) { Integer pos = index.remove(bodies[i]); // O(1) if (pos != null) { int last = overlapping.size() - 1; if (pos != last) { Object moved = overlapping.get(last); overlapping.set(pos, moved); index.put(moved, pos); } overlapping.remove(last); } } } }; long sOps = (long) steps * P * P; // P adds × O(P) + P removes × O(P) long fOps = (long) steps * P; bench(String.format("bullet-0001 btGhostObject overlapping P=%d steps=%d", P, steps), slow, fast, sOps, fOps); } // ----------------------------------------------------------------------- // bullet-0002: btCollisionObject::checkCollideWithOverride per pair per step // ----------------------------------------------------------------------- static void benchCheckCollideWith(int M, int E, int steps) { // M total pairs, E exclusions per object Object[] colliders = new Object[E + 1]; for (int i = 0; i <= E; i++) colliders[i] = new Object(); // SLOW: ArrayList.contains — O(E) per pair Runnable slow = () -> { List exclusions = new ArrayList<>(Arrays.asList(colliders).subList(1, E + 1)); long dummy = 0; for (int s = 0; s < steps; s++) { for (int p = 0; p < M; p++) { Object candidate = colliders[p % (E + 1)]; if (!exclusions.contains(candidate)) // O(E) dummy++; } } if (dummy < 0) System.out.println("never"); }; // FAST: HashSet.contains — O(1) per pair Runnable fast = () -> { Set exclusions = new HashSet<>(Arrays.asList(colliders).subList(1, E + 1)); long dummy = 0; for (int s = 0; s < steps; s++) { for (int p = 0; p < M; p++) { Object candidate = colliders[p % (E + 1)]; if (!exclusions.contains(candidate)) // O(1) dummy++; } } if (dummy < 0) System.out.println("never"); }; long sOps = (long) steps * M * E; long fOps = (long) steps * M; bench(String.format("bullet-0002 checkCollideWith M=%d E=%d steps=%d", M, E, steps), slow, fast, sOps, fOps); } // ----------------------------------------------------------------------- // bullet-0003: btSortedOverlappingPairCache::removeOverlappingPair — O(P) per remove // ----------------------------------------------------------------------- static void benchSortedPairCacheRemove(int P) { // P pairs, remove all of them (broadphase pair removal phase) Integer[] pairs = new Integer[P]; for (int i = 0; i < P; i++) pairs[i] = i; // SLOW: ArrayList.indexOf + remove — O(P) per removal Runnable slow = () -> { List pairArray = new ArrayList<>(Arrays.asList(pairs)); for (int i = 0; i < P; i++) { int idx = pairArray.indexOf(pairs[i]); // O(P) if (idx >= 0) { pairArray.set(idx, pairArray.get(pairArray.size() - 1)); pairArray.remove(pairArray.size() - 1); } } }; // FAST: HashMap — O(1) per removal Runnable fast = () -> { List pairArray = new ArrayList<>(Arrays.asList(pairs)); Map pairIndex = new HashMap<>(P * 2); for (int i = 0; i < P; i++) pairIndex.put(pairs[i], i); for (int i = 0; i < P; i++) { Integer pos = pairIndex.remove(pairs[i]); // O(1) if (pos != null) { int last = pairArray.size() - 1; if (pos != last) { Integer moved = pairArray.get(last); pairArray.set(pos, moved); pairIndex.put(moved, pos); } pairArray.remove(last); } } }; long sOps = (long) P * P / 2; // average scan P/2 × P removals long fOps = P; bench(String.format("bullet-0003 btSortedPairCache removeOverlappingPair P=%d", P), slow, fast, sOps, fOps); } public static void main(String[] args) { System.out.println("Bullet Physics CWE-407 defect benchmarks"); System.out.println("=".repeat(100)); System.out.println("\n[bullet-0001] btGhostObject::addOverlappingObjectInternal/removeOverlappingObjectInternal"); benchGhostOverlapping(50, 100); benchGhostOverlapping(200, 50); benchGhostOverlapping(500, 20); System.out.println("\n[bullet-0002] btCollisionObject::checkCollideWithOverride per pair per step"); benchCheckCollideWith(500, 10, 100); benchCheckCollideWith(1_000, 20, 50); benchCheckCollideWith(2_000, 50, 20); System.out.println("\n[bullet-0003] btSortedOverlappingPairCache::removeOverlappingPair"); benchSortedPairCacheRemove(500); benchSortedPairCacheRemove(2_000); benchSortedPairCacheRemove(10_000); } }