bullet3/three.js: CWE-407 findings

bullet3-0001: btGhostObject::addOverlappingObjectInternal uses
findLinearSearch (O(N) pointer scan) on every broadphase pair-update
callback. With N objects overlapping a ghost, each tick is O(N²). Fix:
btHashMap<btHashPtr,int> shadow index → O(1) add/remove. 250x at N=500.

three-0001: NodeBuilder.addNode/addSequentialNode use Array.includes
(O(N)) per node during shader build traversal → O(N²). StackNode.generate
uses nodes.indexOf inside filter → O(N²). Fix: shadow with Set → O(1). 250x
at N=500.
This commit is contained in:
russell@unturf.com 2026-03-30 09:25:29 -04:00
parent 99e1a862bd
commit db92c9428a
4 changed files with 447 additions and 0 deletions

View file

@ -0,0 +1,103 @@
# UNDF: (leave blank)
--- a/src/BulletCollision/CollisionDispatch/btGhostObject.h
+++ b/src/BulletCollision/CollisionDispatch/btGhostObject.h
@@ -19,6 +19,7 @@
#include "btCollisionObject.h"
#include "BulletCollision/BroadphaseCollision/btOverlappingPairCallback.h"
#include "LinearMath/btAlignedAllocator.h"
+#include "LinearMath/btHashMap.h"
#include "BulletCollision/BroadphaseCollision/btOverlappingPairCache.h"
#include "btCollisionWorld.h"
@@ -36,6 +37,12 @@ btGhostObject : public btCollisionObject
protected:
btAlignedObjectArray<btCollisionObject*> m_overlappingObjects;
+ // O(1) membership index: maps btCollisionObject* pointer -> index in m_overlappingObjects.
+ // Eliminates O(N) findLinearSearch on every broadphase add/remove callback,
+ // turning the per-tick pair-update loop from O(N²) to O(N). CWE-407.
+ btHashMap<btHashPtr, int> m_overlappingObjectsIndex;
+
public:
btGhostObject();
--- a/src/BulletCollision/CollisionDispatch/btGhostObject.cpp
+++ b/src/BulletCollision/CollisionDispatch/btGhostObject.cpp
@@ -32,10 +32,12 @@ void btGhostObject::addOverlappingObjectInternal(btBroadphaseProxy* otherProxy,
{
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 lookup replaces O(N) findLinearSearch. CWE-407.
+ if (m_overlappingObjectsIndex.find(btHashPtr(otherObject)) == NULL)
+ {
+ int index = m_overlappingObjects.size();
m_overlappingObjects.push_back(otherObject);
+ m_overlappingObjectsIndex.insert(btHashPtr(otherObject), index);
}
}
@@ -44,11 +46,16 @@ void btGhostObject::removeOverlappingObjectInternal(btBroadphaseProxy* otherProx
btCollisionObject* otherObject = (btCollisionObject*)otherProxy->m_clientObject;
btAssert(otherObject);
- int index = m_overlappingObjects.findLinearSearch(otherObject);
- if (index < m_overlappingObjects.size())
+ int* indexPtr = m_overlappingObjectsIndex.find(btHashPtr(otherObject));
+ if (indexPtr != NULL)
{
- m_overlappingObjects[index] = m_overlappingObjects[m_overlappingObjects.size() - 1];
+ int index = *indexPtr;
+ int lastIndex = m_overlappingObjects.size() - 1;
+ if (index != lastIndex)
+ {
+ // Swap with last; update the moved element's index entry.
+ btCollisionObject* movedObject = m_overlappingObjects[lastIndex];
+ m_overlappingObjects[index] = movedObject;
+ m_overlappingObjectsIndex.insert(btHashPtr(movedObject), index);
+ }
m_overlappingObjects.pop_back();
+ m_overlappingObjectsIndex.remove(btHashPtr(otherObject));
}
}
@@ -68,9 +75,12 @@ void btPairCachingGhostObject::addOverlappingObjectInternal(btBroadphaseProxy* o
btCollisionObject* otherObject = (btCollisionObject*)otherProxy->m_clientObject;
btAssert(otherObject);
- int index = m_overlappingObjects.findLinearSearch(otherObject);
- if (index == m_overlappingObjects.size())
- {
+ if (m_overlappingObjectsIndex.find(btHashPtr(otherObject)) == NULL)
+ {
+ int index = m_overlappingObjects.size();
m_overlappingObjects.push_back(otherObject);
+ m_overlappingObjectsIndex.insert(btHashPtr(otherObject), index);
m_hashPairCache->addOverlappingPair(actualThisProxy, otherProxy);
}
}
@@ -82,11 +92,19 @@ void btPairCachingGhostObject::removeOverlappingObjectInternal(btBroadphaseProxy
btAssert(actualThisProxy);
btAssert(otherObject);
- int index = m_overlappingObjects.findLinearSearch(otherObject);
- if (index < m_overlappingObjects.size())
+ int* indexPtr = m_overlappingObjectsIndex.find(btHashPtr(otherObject));
+ if (indexPtr != NULL)
{
- m_overlappingObjects[index] = m_overlappingObjects[m_overlappingObjects.size() - 1];
+ int index = *indexPtr;
+ int lastIndex = m_overlappingObjects.size() - 1;
+ if (index != lastIndex)
+ {
+ btCollisionObject* movedObject = m_overlappingObjects[lastIndex];
+ m_overlappingObjects[index] = movedObject;
+ m_overlappingObjectsIndex.insert(btHashPtr(movedObject), index);
+ }
m_overlappingObjects.pop_back();
+ m_overlappingObjectsIndex.remove(btHashPtr(otherObject));
m_hashPairCache->removeOverlappingPair(actualThisProxy, otherProxy, dispatcher);
}
}