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,63 @@
--- a/src/BulletCollision/CollisionDispatch/btGhostObject.h
+++ b/src/BulletCollision/CollisionDispatch/btGhostObject.h
@@ -25,6 +25,7 @@ subject to the following restrictions:
#include "BulletCollision/BroadphaseCollision/btOverlappingPairCallback.h"
#include "BulletCollision/CollisionDispatch/btCollisionObject.h"
#include "BulletCollision/BroadphaseCollision/btOverlappingPairCache.h"
+#include "LinearMath/btHashMap.h"
class btCollisionShape;
class btConvexShape;
@@ -42,6 +43,8 @@ class btGhostObject : public btCollisionObject
protected:
btAlignedObjectArray<btCollisionObject*> m_overlappingObjects;
+ /// O(1) membership index for m_overlappingObjects — eliminates findLinearSearch
+ btHashMap<btHashPtr, int> m_overlappingIndex;
--- a/src/BulletCollision/CollisionDispatch/btGhostObject.cpp
+++ b/src/BulletCollision/CollisionDispatch/btGhostObject.cpp
@@ -32,27 +32,35 @@ void btGhostObject::addOverlappingObjectInternal(btBroadphaseProxy* otherProxy,
btBroadphaseProxy* thisProxy)
{
btCollisionObject* otherObject = (btCollisionObject*)otherProxy->m_clientObject;
btAssert(otherObject);
- ///if this linearSearch becomes too slow (too many overlapping objects) we should add a more appropriate data structure
- int index = m_overlappingObjects.findLinearSearch(otherObject);
- if (index == m_overlappingObjects.size())
- {
- //not found
+ // O(1) hash membership check — replaces O(N) findLinearSearch
+ if (!m_overlappingIndex.find(btHashPtr(otherObject)))
+ {
m_overlappingObjects.push_back(otherObject);
+ m_overlappingIndex.insert(btHashPtr(otherObject),
+ m_overlappingObjects.size() - 1);
}
}
void btGhostObject::removeOverlappingObjectInternal(btBroadphaseProxy* otherProxy,
btDispatcher* dispatcher,
btBroadphaseProxy* thisProxy)
{
btCollisionObject* otherObject = (btCollisionObject*)otherProxy->m_clientObject;
btAssert(otherObject);
- int index = m_overlappingObjects.findLinearSearch(otherObject);
- if (index < m_overlappingObjects.size())
- {
- m_overlappingObjects[index] = m_overlappingObjects[m_overlappingObjects.size() - 1];
- m_overlappingObjects.pop_back();
+ int* idxPtr = m_overlappingIndex.find(btHashPtr(otherObject));
+ if (idxPtr)
+ {
+ int index = *idxPtr;
+ int last = m_overlappingObjects.size() - 1;
+ if (index != last)
+ {
+ // swap with last and fix up the moved element's index
+ m_overlappingObjects[index] = m_overlappingObjects[last];
+ m_overlappingIndex.insert(btHashPtr(m_overlappingObjects[index]), index);
+ }
+ m_overlappingObjects.pop_back();
+ m_overlappingIndex.remove(btHashPtr(otherObject));
}
}

View file

@ -0,0 +1,52 @@
--- a/src/BulletCollision/CollisionDispatch/btCollisionObject.h
+++ b/src/BulletCollision/CollisionDispatch/btCollisionObject.h
@@ -23,6 +23,7 @@ subject to the following restrictions:
#include "LinearMath/btTransform.h"
#include "LinearMath/btMotionState.h"
+#include "LinearMath/btHashMap.h"
#include "LinearMath/btAlignedObjectArray.h"
@@ -115,7 +115,8 @@ class btCollisionObject
{
private:
- btAlignedObjectArray<const btCollisionObject*> m_objectsWithoutCollisionCheck;
+ btAlignedObjectArray<const btCollisionObject*> m_objectsWithoutCollisionCheck; // for serialization
+ btHashMap<btHashPtr, bool> m_ignoreSet; // O(1) membership index
public:
@@ -235,7 +237,7 @@ public:
void setIgnoreCollisionCheck(const btCollisionObject* co, bool ignoreCollisionCheck)
{
if (ignoreCollisionCheck)
{
- //int index = m_objectsWithoutCollisionCheck.findLinearSearch(co);
- //if (index == m_objectsWithoutCollisionCheck.size())
- //{
+ if (!m_ignoreSet.find(btHashPtr(co)))
+ {
m_objectsWithoutCollisionCheck.push_back(co);
- //}
+ m_ignoreSet.insert(btHashPtr(co), true);
+ }
}
else
{
m_objectsWithoutCollisionCheck.remove(co);
+ m_ignoreSet.remove(btHashPtr(co));
}
m_checkCollideWith = m_objectsWithoutCollisionCheck.size() > 0;
}
@@ -266,10 +268,8 @@ public:
virtual bool checkCollideWithOverride(const btCollisionObject* co) const
{
- int index = m_objectsWithoutCollisionCheck.findLinearSearch(co); // O(N)
- if (index < m_objectsWithoutCollisionCheck.size())
- {
- return false;
- }
- return true;
+ // O(1) hash lookup — replaces O(N) findLinearSearch
+ return !m_ignoreSet.find(btHashPtr(co));
}

View file

@ -0,0 +1,43 @@
--- a/src/BulletCollision/BroadphaseCollision/btOverlappingPairCache.cpp
+++ b/src/BulletCollision/BroadphaseCollision/btOverlappingPairCache.cpp
@@ -444,13 +444,11 @@ void* btSortedOverlappingPairCache::removeOverlappingPair(btBroadphaseProxy* pro
if (!hasDeferredRemoval())
{
btBroadphasePair findPair(*proxy0, *proxy1);
- int findIndex = m_overlappingPairArray.findLinearSearch(findPair); // O(N)
- if (findIndex < m_overlappingPairArray.size())
+ // NOTE: btSortedOverlappingPairCache is the slow path.
+ // Callers should prefer btHashedOverlappingPairCache which provides O(1) here.
+ // If this cache must be used, add a btHashMap<key, int> index as done for
+ // btHashedOverlappingPairCache (see lines 100-260 of this file).
+ int findIndex = m_overlappingPairArray.findLinearSearch(findPair);
+ if (findIndex < m_overlappingPairArray.size())
{
btBroadphasePair& pair = m_overlappingPairArray[findIndex];
void* userData = pair.m_internalInfo1;
@@ -484,9 +482,8 @@ btBroadphasePair* btSortedOverlappingPairCache::findPair(btBroadphaseProxy* prox
if (!needsBroadphaseCollision(proxy0, proxy1))
return 0;
btBroadphasePair tmpPair(*proxy0, *proxy1);
- ///this findPair becomes really slow. Either sort the list to speedup the query,
- ///or use a different solution. It is mainly used for Removing overlapping pairs.
- ///we could keep a linked list in each proxy, and store pair in one of the proxies
- int findIndex = m_overlappingPairArray.findLinearSearch(tmpPair); // O(N)
+ // O(N) linear scan — migrate to btHashedOverlappingPairCache for O(1).
+ int findIndex = m_overlappingPairArray.findLinearSearch(tmpPair);
if (findIndex < m_overlappingPairArray.size())
{
--- a/src/BulletDynamics/Dynamics/btDiscreteDynamicsWorld.cpp
+++ b/src/BulletDynamics/Dynamics/btDiscreteDynamicsWorld.cpp
@@ -90,7 +90,9 @@ btDiscreteDynamicsWorld::btDiscreteDynamicsWorld(...)
- // Default pair cache: use btHashedOverlappingPairCache (O(1)) not btSortedOverlappingPairCache (O(N))
- // Existing code already uses btHashedOverlappingPairCache via btDbvtBroadphase.
- // btSortedOverlappingPairCache should not be used for dynamic worlds.
+ // Ensure O(1) pair operations: btDbvtBroadphase creates btHashedOverlappingPairCache.
+ // If using btAxisSweep3, pass btHashedOverlappingPairCache explicitly:
+ // new btAxisSweep3(min, max, maxHandles, new btHashedOverlappingPairCache())

View file

@ -0,0 +1,220 @@
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<key, index> — 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<Object> 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<Object, Integer> index — O(1) per add and remove
Runnable fast = () -> {
List<Object> overlapping = new ArrayList<>(P);
Map<Object, Integer> 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<Object> 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<Object> 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<Integer> 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<key, index> — O(1) per removal
Runnable fast = () -> {
List<Integer> pairArray = new ArrayList<>(Arrays.asList(pairs));
Map<Integer, Integer> 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);
}
}