53 lines
1.8 KiB
Diff
53 lines
1.8 KiB
Diff
# 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<Node>}
|
|
*/
|
|
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<Node> 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 ) {
|