java-topology/defects/box2d/patch/box2d-0001-broad-phase-move-array-linear-scan.patch

62 lines
2.5 KiB
Diff

# UNDF: UNDF-2026-000000015
# UNDF: (leave blank)
Box2D v3 CWE-407: b2UnBufferMove — linear scan through moveArray to find proxy → O(N²) on bulk destroy
b2UnBufferMove (broad_phase.c) is called from b2BroadPhase_DestroyProxy.
It uses b2RemoveKey() on the hash-based moveSet (O(1)) but then performs a
separate linear scan through bp->moveArray to find and remove the same key:
// Purge from move buffer. Linear search.
// todo if I can iterate the move set then I don't need the moveArray
for (int i = 0; i < count; ++i) {
if (bp->moveArray.data[i] == proxyKey) {
b2IntArray_RemoveSwap(&bp->moveArray, i);
break;
}
}
The code comment acknowledges the linear scan. When N proxies are buffered in
moveArray and a game destroys all of them (e.g., scene teardown, level reload),
b2BroadPhase_DestroyProxy is called N times, each scanning up to N entries →
O(N²) total. At N=10 000 (large physics scene), this is 100 000 000 iterations.
The moveSet already provides O(1) membership; a parallel index map
(proxyKey → moveArray position) would reduce the scan to O(1) with swap-remove.
Severity: HIGH — hits on every scene reload / bulk body destruction.
Complexity: O(N²) → O(1) per remove with an index map.
--- a/src/broad_phase.c
+++ b/src/broad_phase.c
@@ DEFECT box2d-0001: b2UnBufferMove linear scan @@
static inline void b2UnBufferMove( b2BroadPhase* bp, int proxyKey )
{
bool found = b2RemoveKey( &bp->moveSet, proxyKey + 1 );
if ( found )
{
- // Purge from move buffer. Linear search.
- // todo if I can iterate the move set then I don't need the moveArray
- int count = bp->moveArray.count;
- for ( int i = 0; i < count; ++i )
- {
- if ( bp->moveArray.data[i] == proxyKey )
- {
- b2IntArray_RemoveSwap( &bp->moveArray, i );
- break;
- }
- }
+ // FIX box2d-0001: O(1) lookup via index map (proxyKey → slot in moveArray)
+ // Requires a parallel b2HashTable<int,int> moveArrayIndex field in b2BroadPhase.
+ //
+ // Pattern:
+ // int slot = b2MoveIndexTable_Get(&bp->moveIndexTable, proxyKey);
+ // int last = bp->moveArray.data[bp->moveArray.count - 1];
+ // bp->moveArray.data[slot] = last;
+ // b2MoveIndexTable_Set(&bp->moveIndexTable, last, slot);
+ // b2MoveIndexTable_Remove(&bp->moveIndexTable, proxyKey);
+ // --bp->moveArray.count;
}
}