# 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 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; } }