java-topology/defects/ardour/unit/ArdourTest.java
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

125 lines
4.7 KiB
Java

import java.util.*;
/**
* CWE-407 simulation tests for Ardour defects.
*
* ardour-0001: PluginManager blacklist/rescan PluginInfoList O(I * N)
*
* PluginManager::blacklist() and ::rescan_plugin() in plugin_manager.cc
* iterate over I entries from the scan log for the path being processed and
* call std::find on pil (the master PluginInfoList with N entries) for each.
* std::find on std::list<PluginInfoPtr> is O(N) per call, giving O(I * N) total.
*
* In a studio with 500 VST3 plugins, blacklisting a bundle with I=10 entries
* costs 5000 pointer comparisons instead of 510 (O(N+I) for the fixed version).
*
* Fix: build an unordered_set of the I entries to remove, then do one
* remove_if pass over pil — O(N + I) total.
*
* Both blacklist() and rescan_plugin() share the same pattern and both
* need the same fix.
*/
public class ArdourTest {
// ---------------------------------------------------------------
// ardour-0001: PluginInfoList std::find inside scan-log loop
// ---------------------------------------------------------------
/**
* Simulate defective removal: for each of I entries to remove,
* std::find scans all N entries in pil = O(I * N).
*
* @param pil total number of plugins in master PluginInfoList
* @param toRemove number of plugins to remove from this scan log entry
* @return total comparison operations
*/
static long blacklistDefective(int pil, int toRemove) {
long ops = 0;
// Simulate worst case: each to-remove entry is found at end of pil
for (int i = 0; i < toRemove; i++) {
// std::find scans from begin() to the found element (worst case: N)
for (int k = 0; k < pil; k++) {
ops++;
}
pil--; // list shrinks after erase
}
return ops;
}
/**
* Simulate fixed removal: build unordered_set of I entries (O(I)),
* then single remove_if pass over pil (O(N)) = O(N + I) total.
*
* @param pil total number of plugins in master PluginInfoList
* @param toRemove number of plugins to remove from this scan log entry
* @return total comparison operations
*/
static long blacklistFixed(int pil, int toRemove) {
long ops = 0;
// Build unordered_set: O(I) inserts
ops += toRemove;
// remove_if pass: O(N) hash lookups (O(1) each)
ops += pil;
return ops;
}
static void testBlacklistSmallBundle() {
// Typical: 500 VST3 plugins, blacklisting a bundle with 5 variants
int N = 500;
int I = 5;
long defectOps = blacklistDefective(N, I);
long fixedOps = blacklistFixed(N, I);
double ratio = (double) defectOps / fixedOps;
System.out.printf("ardour-0001 blacklist small bundle (N=%d plugins, I=%d entries):%n", N, I);
System.out.printf(" defect_ops=%d fixed_ops=%d ratio=%.1fx%n",
defectOps, fixedOps, ratio);
assert ratio > 2.0 : "Expected overhead, got " + ratio;
System.out.println(" PASS");
}
static void testBlacklistLargeScan() {
// Stress case: 1000 plugins, rescan of bundle with 20 entries
int N = 1000;
int I = 20;
long defectOps = blacklistDefective(N, I);
long fixedOps = blacklistFixed(N, I);
double ratio = (double) defectOps / fixedOps;
System.out.printf("ardour-0001 blacklist large scan (N=%d plugins, I=%d entries):%n", N, I);
System.out.printf(" defect_ops=%d fixed_ops=%d ratio=%.1fx%n",
defectOps, fixedOps, ratio);
assert ratio > 5.0 : "Expected significant overhead, got " + ratio;
System.out.println(" PASS");
}
static void testRescanPlugin() {
// rescan_plugin() same pattern as blacklist()
// 800 LADSPA plugins, rescan with I=15 entries
int N = 800;
int I = 15;
long defectOps = blacklistDefective(N, I);
long fixedOps = blacklistFixed(N, I);
double ratio = (double) defectOps / fixedOps;
System.out.printf("ardour-0001 rescan_plugin (N=%d plugins, I=%d entries):%n", N, I);
System.out.printf(" defect_ops=%d fixed_ops=%d ratio=%.1fx%n",
defectOps, fixedOps, ratio);
assert ratio > 5.0 : "Expected significant overhead, got " + ratio;
System.out.println(" PASS");
}
// ---------------------------------------------------------------
// Main
// ---------------------------------------------------------------
public static void main(String[] args) {
testBlacklistSmallBundle();
testBlacklistLargeScan();
testRescanPlugin();
System.out.println("\nAll Ardour CWE-407 tests PASS");
}
}