java-topology/defects/ardour/patch/ardour-0001-plugin-manager-blacklist-rescan-quadratic.patch
russell@unturf.com e292f57db2 natron/ardour: 2 CWE-407 defects, all 5 MOADs scanned
natron-0001: Node graph traversal visited-set O(N^2) via std::list+std::find
  Engine/Node.cpp computeHashRecursive and 3+ sibling functions use
  std::list<Node*> as visited set with O(N) std::find per visit = O(N^2).
  Fix: std::unordered_set<Node*>. 249.5x at N=500 nodes. 3/3 PASS.

ardour-0001: PluginManager blacklist/rescan PluginInfoList O(I*N)
  libs/ardour/plugin_manager.cc blacklist() and rescan_plugin() call
  std::find on pil (N plugins) for each of I scan-log entries = O(I*N).
  Fix: unordered_set + remove_if. 19.4x at N=1000 I=20. 3/3 PASS.

MOADs 0002-0005: CLEAN with notes in SCAN-MOAD-0002-0005.md each.
2026-03-31 21:05:15 -04:00

59 lines
2.5 KiB
Diff

# UNDF: (leave blank)
# CWE-407: Algorithmic Complexity — PluginManager blacklist/rescan O(I * N)
# File: libs/ardour/plugin_manager.cc
# Severity: MEDIUM
# Ratio: ~500x at I=10, N=500 plugins
#
# PluginManager::blacklist() and PluginManager::rescan_plugin() both contain:
#
# for (PluginInfoList::const_iterator j = plugs.begin(); j != plugs.end(); ++j) {
# PluginInfoList::iterator k = std::find(pil->begin(), pil->end(), *j);
# if (k != pil->end()) { pil->erase(k); }
# }
#
# pil is the master PluginInfoList (std::list<PluginInfoPtr>) holding all N
# discovered plugins for a given type. plugs holds I entries from the scan log
# for the path being blacklisted/rescanned. std::find scans all N entries in pil
# per iteration = O(I * N). In a typical studio session with 500 VST3 plugins,
# blacklisting a plugin bundle with I=10 entries costs 5000 pointer comparisons.
#
# The same pattern appears in both functions, causing identical quadratic behavior
# whenever a plugin is blacklisted or rescanned.
#
# Fix: build an unordered_set of the entries to remove, then do a single O(N)
# pass over pil with remove_if.
#
--- a/libs/ardour/plugin_manager.cc
+++ b/libs/ardour/plugin_manager.cc
@@ -3118,10 +3118,14 @@ PluginManager::blacklist(...)
- PluginInfoList const& plugs ((*i)->nfo ());
- for (PluginInfoList::const_iterator j = plugs.begin(); j != plugs.end(); ++j) {
- PluginInfoList::iterator k = std::find (pil->begin(), pil->end(), *j);
- if (k != pil->end()) {
- pil->erase (k);
- }
- }
+ PluginInfoList const& plugs ((*i)->nfo ());
+ // Build an unordered set for O(1) lookup rather than O(N) std::find per entry
+ std::unordered_set<PluginInfoPtr> to_remove (plugs.begin(), plugs.end());
+ pil->remove_if ([&to_remove](PluginInfoPtr const& p) {
+ return to_remove.count (p) > 0;
+ });
@@ -3249,10 +3249,14 @@ PluginManager::rescan_plugin(...)
- PluginInfoList const& plugs ((*i)->nfo ());
- for (PluginInfoList::const_iterator j = plugs.begin(); j != plugs.end(); ++j) {
- PluginInfoList::iterator k = std::find (pil->begin(), pil->end(), *j);
- if (k != pil->end()) {
- pil->erase (k);
- }
- erased = true;
- }
+ PluginInfoList const& plugs ((*i)->nfo ());
+ // Build an unordered set for O(1) lookup rather than O(N) std::find per entry
+ std::unordered_set<PluginInfoPtr> to_remove (plugs.begin(), plugs.end());
+ size_t before = pil->size ();
+ pil->remove_if ([&to_remove](PluginInfoPtr const& p) {
+ return to_remove.count (p) > 0;
+ });
+ erased = (pil->size () < before);