wave15: sm-0003/0004 + threejs-0006 + varnish-0002 + mongodb-0008 — 533/240
This commit is contained in:
parent
7146714143
commit
31850d5ef6
13 changed files with 1893 additions and 9 deletions
|
|
@ -0,0 +1,88 @@
|
|||
# threejs-0006: EventDispatcher.addEventListener() O(N²) via indexOf dedup on every add
|
||||
|
||||
**File:** `src/core/EventDispatcher.js`
|
||||
**Lines:** 43 (`addEventListener` indexOf dedup), 64 (`hasEventListener` indexOf)
|
||||
**Severity:** MEDIUM
|
||||
**CWE:** CWE-407 (Inefficient Algorithmic Complexity)
|
||||
|
||||
## Description
|
||||
|
||||
`EventDispatcher.addEventListener(type, listener)` checks for duplicate
|
||||
listeners using a linear array scan:
|
||||
|
||||
```js
|
||||
addEventListener( type, listener ) {
|
||||
...
|
||||
if ( listeners[ type ] === undefined ) {
|
||||
listeners[ type ] = [];
|
||||
}
|
||||
|
||||
if ( listeners[ type ].indexOf( listener ) === - 1 ) { // O(N) scan — CWE-407
|
||||
listeners[ type ].push( listener );
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
`listeners[type]` is a plain `Array`. `Array.prototype.indexOf()` performs a
|
||||
linear scan over all existing listeners for that event type.
|
||||
|
||||
When `addEventListener` is called N times for the same event type (e.g. adding
|
||||
N unique disposal/resize handlers in a scene setup loop, or attaching per-object
|
||||
listeners in a particle system), the total dedup cost is:
|
||||
|
||||
```
|
||||
O(0) + O(1) + O(2) + ... + O(N-1) = O(N²/2)
|
||||
```
|
||||
|
||||
`hasEventListener(type, listener)` has the same O(N) scan. If called in a
|
||||
render loop that validates listeners exist before dispatching, it compounds the
|
||||
problem.
|
||||
|
||||
Three.js objects — `Material`, `BufferGeometry`, `Texture`, `Object3D` — all
|
||||
extend `EventDispatcher`. A scene with M materials each gaining N listeners
|
||||
during load would incur O(M × N²) total work.
|
||||
|
||||
## Fix
|
||||
|
||||
Replace the per-type `Array` with a `Map<type, Set<listener>>`:
|
||||
|
||||
```js
|
||||
addEventListener( type, listener ) {
|
||||
if ( this._listenerSets === undefined ) this._listenerSets = new Map();
|
||||
|
||||
let listenerSet = this._listenerSets.get( type );
|
||||
if ( listenerSet === undefined ) {
|
||||
listenerSet = new Set();
|
||||
this._listenerSets.set( type, listenerSet );
|
||||
}
|
||||
|
||||
if ( ! listenerSet.has( listener ) ) { // O(1) — fixed
|
||||
listenerSet.add( listener );
|
||||
}
|
||||
}
|
||||
|
||||
hasEventListener( type, listener ) {
|
||||
if ( this._listenerSets === undefined ) return false;
|
||||
const listenerSet = this._listenerSets.get( type );
|
||||
return listenerSet !== undefined && listenerSet.has( listener ); // O(1)
|
||||
}
|
||||
```
|
||||
|
||||
`dispatchEvent` iterates the set (same O(N) iteration, no change in
|
||||
semantics since insertion order is preserved by `Set`).
|
||||
|
||||
## Complexity
|
||||
|
||||
| Path | Before | After |
|
||||
|------|--------|-------|
|
||||
| `addEventListener()` per call | O(N) | O(1) |
|
||||
| Total for N unique listeners, same type | O(N²) | O(N) |
|
||||
| `hasEventListener()` per call | O(N) | O(1) |
|
||||
|
||||
**Speedup:** ~N/2 × at N=500: ~250×
|
||||
|
||||
## References
|
||||
|
||||
- `Material`, `BufferGeometry`, `Texture`, `Object3D` all extend `EventDispatcher`
|
||||
- `WebGLRenderer` calls `material.addEventListener('dispose', ...)` — guarded but pattern propagates
|
||||
- threejs-0001..0005: previous CWE-407 defects in Three.js
|
||||
Loading…
Add table
Add a link
Reference in a new issue