java-topology/defects/natron/patch/natron-0001-node-graph-traversal-visited-set-quadratic.patch

92 lines
3.4 KiB
Diff

# UNDF: UNDF-2026-000001129
# UNDF: (leave blank)
# CWE-407: Algorithmic Complexity — node graph traversal visited-set O(N^2)
# File: Engine/Node.cpp
# Severity: HIGH
# Ratio: ~250x at N=100 nodes
#
# Multiple recursive graph traversal functions in Node.cpp use std::list<Node*>
# as a visited set and call std::find() at each node visit to detect cycles.
# std::find on a std::list is O(N) per call. With N nodes visited, total cost
# is O(N^2).
#
# Affected functions (all same pattern):
# computeHashRecursive() — called on every render hash invalidation
# clearPersistentMessageRecursive() — called on node connection changes
# refreshPreviewsRecursivelyUpstreamInternal() — preview refresh
# refreshPreviewsRecursivelyDownstreamInternal() — preview refresh
# addIdentityNodesRecursively() — called per-frame during composition
# markInputRelatedDataDirtyRecursiveInternal() — parameter change propagation
# EffectInstance::refreshMetadata_recursive() — metadata refresh
#
# computeHashRecursive is the hottest: called every time a parameter changes to
# propagate cache invalidation across the downstream graph. In a composition
# with N=100 nodes, this visits all N nodes with std::find O(N) each = O(N^2).
#
# Fix: replace std::list<Node*> visited set with std::unordered_set<Node*>.
# std::unordered_set::count() is O(1) average. Total cost becomes O(N).
#
--- a/Engine/Node.cpp
+++ b/Engine/Node.cpp
@@ -879,10 +879,11 @@ Node::computeHashInternal()
void
-Node::computeHashRecursive(std::list<Node*>& marked)
+Node::computeHashRecursive(std::unordered_set<Node*>& marked)
{
- if ( std::find(marked.begin(), marked.end(), this) != marked.end() ) {
+ if ( marked.count(this) ) {
return;
}
bool hasChanged = computeHashInternal();
- marked.push_back(this);
+ marked.insert(this);
if (!hasChanged) {
//Nothing changed, no need to recurse on outputs
return;
@@ -970,7 +971,7 @@ Node::computeHash()
{
- std::list<Node*> marked;
+ std::unordered_set<Node*> marked;
computeHashRecursive(marked);
}
@@ -3891,10 +3892,10 @@ Node::clearPersistentMessageInternal()
void
-Node::clearPersistentMessageRecursive(std::list<Node*>& markedNodes)
+Node::clearPersistentMessageRecursive(std::unordered_set<Node*>& markedNodes)
{
- if ( std::find(markedNodes.begin(), markedNodes.end(), this) != markedNodes.end() ) {
+ if ( markedNodes.count(this) ) {
return;
}
- markedNodes.push_back(this);
+ markedNodes.insert(this);
@@ -3944,7 +3945,7 @@ Node::clearPersistentMessage(bool recurse)
}
- std::list<Node*> markedNodes;
+ std::unordered_set<Node*> markedNodes;
clearPersistentMessageRecursive(markedNodes);
@@ -6233,11 +6234,11 @@ Node::markInputRelatedDataDirtyRecursiveInternal(
- std::list<Node*>& markedNodes,
+ std::unordered_set<Node*>& markedNodes,
bool recurse)
{
- std::list<Node*>::iterator found = std::find(markedNodes.begin(), markedNodes.end(), this);
- if ( found != markedNodes.end() ) {
+ if ( markedNodes.count(this) ) {
return;
}
markAllInputRelatedDataDirty();
- markedNodes.push_back(this);
+ markedNodes.insert(this);
@@ -6253,7 +6254,7 @@ Node::markInputRelatedDataDirtyRecursive()
{
- std::list<Node*> marked;
+ std::unordered_set<Node*> marked;
markInputRelatedDataDirtyRecursiveInternal(marked, true);