game engines/web frameworks: 27 CWE-407 defects + 3 CLEAN; 194 sites, 78 ecosystems
This commit is contained in:
parent
547a9f5738
commit
4d3fcc8e73
76 changed files with 6216 additions and 17 deletions
180
defects/box2d/unit/Box2DTest.java
Normal file
180
defects/box2d/unit/Box2DTest.java
Normal 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");
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue