game engines/web frameworks: 27 CWE-407 defects + 3 CLEAN; 194 sites, 78 ecosystems

This commit is contained in:
russell@unturf.com 2026-03-27 14:14:49 -04:00
parent 547a9f5738
commit 4d3fcc8e73
76 changed files with 6216 additions and 17 deletions

View file

@ -0,0 +1,180 @@
package unit;
import java.util.*;
/**
* Box2D CWE-407 benchmark: b2UnBufferMove linear scan vs O(1) index map.
*
* Defect: src/broad_phase.c b2UnBufferMove() lines 77-87
* Linear scan of moveArray to find the index for removal after
* O(1) hash-set removal. With N shapes all buffered for movement,
* destroying all bodies triggers N × N/2 comparisons on average.
*
* Fix: Maintain a parallel int[] indexMap[proxyKey] array position.
* On RemoveSwap, update the displaced element's entry. O(1) removal.
*
* ticket: docs/tickets/box2d-0001-broad-phase-unbuffer-move-linear-scan.md
*/
public class Box2DTest {
// -----------------------------------------------------------------------
// SLOW: mirrors b2UnBufferMove hash set O(1) + linear array scan O(n)
// -----------------------------------------------------------------------
static class SlowMoveBuffer {
Set<Integer> moveSet = new HashSet<>();
List<Integer> moveArray = new ArrayList<>();
void bufferMove(int proxyKey) {
if (moveSet.add(proxyKey)) {
moveArray.add(proxyKey);
}
}
/** Linear search — exact mirror of box2d broad_phase.c lines 77-87. */
void unBufferMove(int proxyKey) {
if (moveSet.remove(proxyKey)) {
// "Purge from move buffer. Linear search."
for (int i = 0; i < moveArray.size(); i++) {
if (moveArray.get(i) == proxyKey) {
// RemoveSwap: replace with last, shrink
int last = moveArray.size() - 1;
moveArray.set(i, moveArray.get(last));
moveArray.remove(last);
break;
}
}
}
}
}
// -----------------------------------------------------------------------
// FAST: O(1) removal via index map (the proposed fix)
// -----------------------------------------------------------------------
static class FastMoveBuffer {
Set<Integer> moveSet = new HashSet<>();
int[] moveArray = new int[4096];
int count = 0;
Map<Integer, Integer> indexMap = new HashMap<>(); // proxyKey array index
void bufferMove(int proxyKey) {
if (moveSet.add(proxyKey)) {
if (count == moveArray.length) {
moveArray = Arrays.copyOf(moveArray, count * 2);
}
indexMap.put(proxyKey, count);
moveArray[count++] = proxyKey;
}
}
void unBufferMove(int proxyKey) {
if (moveSet.remove(proxyKey)) {
int idx = indexMap.remove(proxyKey);
int last = count - 1;
if (idx != last) {
int displaced = moveArray[last];
moveArray[idx] = displaced;
indexMap.put(displaced, idx);
}
count--;
}
}
}
// -----------------------------------------------------------------------
// Bench harness
// -----------------------------------------------------------------------
static void bench(String label, Runnable slow, Runnable fast, long sOps, long fOps) {
// Warmup
slow.run(); fast.run();
// Slow timing
long t0 = System.nanoTime(); slow.run(); long sMs = (System.nanoTime() - t0) / 1_000_000;
// Fast timing
long t1 = System.nanoTime(); fast.run(); long fMs = (System.nanoTime() - t1) / 1_000_000;
double r = fOps > 0 ? (double) sOps / fOps : 0;
System.out.printf(" %-52s slow:%4dms (%,d ops) fast:%4dms (%,d ops) speedup:%.0fx%n",
label, sMs, sOps, fMs, fOps, r);
}
// -----------------------------------------------------------------------
// Scenarios
// -----------------------------------------------------------------------
/** N shapes all buffered, then all destroyed (body-destroy sweep). */
static Runnable slowDestroyAll(int n) {
return () -> {
SlowMoveBuffer buf = new SlowMoveBuffer();
for (int i = 0; i < n; i++) buf.bufferMove(i);
for (int i = 0; i < n; i++) buf.unBufferMove(i);
};
}
static Runnable fastDestroyAll(int n) {
return () -> {
FastMoveBuffer buf = new FastMoveBuffer();
for (int i = 0; i < n; i++) buf.bufferMove(i);
for (int i = 0; i < n; i++) buf.unBufferMove(i);
};
}
/** Interleaved add/remove (shape filter update pattern): N pairs. */
static Runnable slowInterleaved(int n) {
return () -> {
SlowMoveBuffer buf = new SlowMoveBuffer();
for (int i = 0; i < n; i++) {
buf.bufferMove(i);
buf.unBufferMove(i);
}
};
}
static Runnable fastInterleaved(int n) {
return () -> {
FastMoveBuffer buf = new FastMoveBuffer();
for (int i = 0; i < n; i++) {
buf.bufferMove(i);
buf.unBufferMove(i);
}
};
}
/** Fill half, then remove all (ContactManager pattern: some shapes static). */
static Runnable slowHalfFill(int n) {
return () -> {
SlowMoveBuffer buf = new SlowMoveBuffer();
for (int i = 0; i < n; i++) buf.bufferMove(i);
// remove only the dynamic half
for (int i = 0; i < n / 2; i++) buf.unBufferMove(i);
};
}
static Runnable fastHalfFill(int n) {
return () -> {
FastMoveBuffer buf = new FastMoveBuffer();
for (int i = 0; i < n; i++) buf.bufferMove(i);
for (int i = 0; i < n / 2; i++) buf.unBufferMove(i);
};
}
public static void main(String[] args) {
System.out.println("Box2D CWE-407: b2UnBufferMove linear scan vs O(1) index map");
System.out.println(" defect: src/broad_phase.c lines 77-87");
System.out.println();
int N = 800;
long ops = (long) N * N / 2; // approx comparisons in slow path
bench(String.format("destroy-all N=%d (body-destroy sweep)", N),
slowDestroyAll(N), fastDestroyAll(N), ops, N);
bench(String.format("interleaved N=%d (shape-filter update)", N),
slowInterleaved(N), fastInterleaved(N), ops, N);
bench(String.format("half-fill N=%d (mixed static/dynamic)", N),
slowHalfFill(N), fastHalfFill(N), ops, N);
System.out.println();
System.out.println("Fix: maintain HashMap<proxyKey, arrayIndex> alongside moveArray.");
System.out.println(" On RemoveSwap, update displaced element's index entry.");
System.out.println(" All operations O(1). See patch box2d-0001-broad-phase-index-map.patch");
}
}