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
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
|
@ -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));
|
||||
}
|
||||
|
|
@ -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())
|
||||
220
defects/bullet/unit/BulletTest.java
Normal file
220
defects/bullet/unit/BulletTest.java
Normal 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);
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue