# UNDF: UNDF-2026-000000752 # UNDF: (leave blank) --- a/src/nodes/core/NodeBuilder.js +++ b/src/nodes/core/NodeBuilder.js @@ -137,6 +137,13 @@ class NodeBuilder { * @type {Array} */ this.nodes = []; + + // O(1) membership guard for this.nodes and this.sequentialNodes. + // this.nodes.includes(node) is O(N) and is called once per node during + // the shader build traversal, making addNode() O(N²) for graphs with N + // nodes. A Set reduces each membership test to O(1), giving O(N) + // total build cost. CWE-407. + this._nodesSet = new Set(); + this._sequentialNodesSet = new Set(); /** * A list of all nodes the builder is processing in sequential order. @@ -762,8 +762,8 @@ class NodeBuilder { addNode( node ) { - if ( this.nodes.includes( node ) === false ) { + if ( this._nodesSet.has( node ) === false ) { this.nodes.push( node ); + this._nodesSet.add( node ); this.setHashNode( node, node.getHash( this ) ); @@ -785,8 +785,8 @@ class NodeBuilder { if ( updateBeforeType !== NodeUpdateType.NONE || updateAfterType !== NodeUpdateType.NONE ) { - if ( this.sequentialNodes.includes( node ) === false ) { + if ( this._sequentialNodesSet.has( node ) === false ) { this.sequentialNodes.push( node ); + this._sequentialNodesSet.add( node ); } --- a/src/nodes/core/StackNode.js +++ b/src/nodes/core/StackNode.js @@ -382,7 +382,9 @@ class StackNode extends Node { this._currentNode = null; - const newNodes = this.nodes.filter( ( node ) => nodes.indexOf( node ) === - 1 ); + // nodes.indexOf(node) is O(N) inside filter → O(N²) overall. + // Use a Set for O(N) total. CWE-407. + const nodesSnapshot = new Set( nodes ); + const newNodes = this.nodes.filter( ( node ) => nodesSnapshot.has( node ) === false ); for ( const node of newNodes ) {