whitepaper: 352/169 — wave4 MEDIUM (hadoop/hbase/nova/neutron/openstack) + fix odl-0002 dup

This commit is contained in:
russell@unturf.com 2026-03-27 15:33:17 -04:00
parent 9934133dcf
commit 835ae73b0f
82 changed files with 5931 additions and 6 deletions

View file

@ -0,0 +1,41 @@
# cmake-0002 — GetDirectoriesWithBacktraces: O(n²) std::find inside loop
**Severity:** MEDIUM
**File:** `Source/cmComputeLinkInformation.cxx`
**Lines:** 465–472
**CWE:** CWE-407 (Algorithmic Complexity — Quadratic)
## Description
`cmComputeLinkInformation::GetDirectoriesWithBacktraces()` iterates over
`orderedDirectories` (size N) and for each entry calls `std::find` over
`targetLinkDirectories` (size M) to recover the backtrace annotation.
Total cost: O(N × M).
In a large project with many link directories and many targets, both N and M
grow with the number of libraries, making this O(n²) on the link directory
count.
```cpp
// Source/cmComputeLinkInformation.cxx:465-472
for (std::string const& dir : orderedDirectories) {
auto result = std::find(targetLinkDirectories.begin(), // O(M) scan
targetLinkDirectories.end(), dir);
...
}
```
## Fix
Build a `std::unordered_map<string, BT<string>>` from `targetLinkDirectories`
before the loop, reducing each lookup to O(1) amortised.
**Patch:** `patch/cmake-0002-getdirectories-unordered-map.patch`
**Unit test:** `unit/GetDirectoriesAlgorithm.java`
## Complexity
| | Time |
|---|---|
| Before | O(N × M) |
| After | O(N + M) |

View file

@ -0,0 +1,38 @@
# cmake-0003 — AddRuntimeDLL: O(n) std::find per DLL in hot loop
**Severity:** MEDIUM
**File:** `Source/cmComputeLinkInformation.cxx`
**Lines:** 1352–1355
**CWE:** CWE-407 (Algorithmic Complexity — Quadratic)
## Description
`AddRuntimeDLL()` guards insertion with a linear scan over `RuntimeDLLs`:
```cpp
// Source/cmComputeLinkInformation.cxx:1352-1355
if (std::find(this->RuntimeDLLs.begin(), this->RuntimeDLLs.end(), tgt) ==
this->RuntimeDLLs.end()) {
this->RuntimeDLLs.emplace_back(tgt);
}
```
`AddRuntimeDLL` is called from `AddItem()` and `AddSharedDepItem()`, both of
which are invoked for every link entry in the `Compute()` main loop over
`linkEntries`. On a Windows/DLL project with D shared-library dependencies,
the total cost is O(D²) pointer comparisons.
## Fix
Add a parallel `std::unordered_set<cmGeneratorTarget const*> RuntimeDLLsSet`
member. Replace the `std::find` guard with a set insertion check.
**Patch:** `patch/cmake-0003-addruntimedll-unordered-set.patch`
**Unit test:** `unit/RuntimeDllAlgorithm.java`
## Complexity
| | Time |
|---|---|
| Before | O(D²) |
| After | O(D) amortised |

View file

@ -0,0 +1,52 @@
# cmake-0004 — AddSource: O(n²) std::find_if per call in Unity build loop
**Severity:** HIGH
**File:** `Source/cmTarget.cxx`
**Lines:** 1428–1444
**CWE:** CWE-407 (Algorithmic Complexity — Quadratic)
## Description
`cmTarget::AddSource()` performs a linear `std::find_if` over the entire
`Sources.Entries` vector on every call:
```cpp
// Source/cmTarget.cxx:1432-1434
auto const& sources = this->impl->Sources.Entries;
if (std::find_if(sources.begin(), sources.end(),
TargetPropertyEntryFinder(sfl)) == sources.end()) {
```
`AddSource` is called in a `for` loop over `unity_files` in
`cmLocalGenerator.cxx:3353`:
```cpp
for (UnitySource const& file : unity_files) {
target->AddSource(file.Path, true); // O(n) scan each iteration
```
For a Unity build with S source files, constructing the unity file list
costs O(S²) in total. Unity builds are precisely the scenario where S is
large (hundreds to thousands of files per target).
## Fix
Maintain a parallel `std::unordered_set<std::string> SourcePathsSet` in
`cmTargetInternals`. On each `AddSource` call, attempt a set insertion
first; if already present, skip the `find_if` and `WriteDirect` entirely.
**Patch:** `patch/cmake-0004-addsource-unordered-set.patch`
**Unit test:** `unit/AddSourceAlgorithm.java`
## Complexity
| | Time |
|---|---|
| Before | O(S²) |
| After | O(S) amortised |
## Speedup estimate
At S=1000 sources: ~500× fewer comparisons on the hot path.
Measured HIGH: Unity builds with thousands of sources are a documented
CMake use-case (Qt, Chromium, large game engines).

View file

@ -0,0 +1,30 @@
diff --git a/Source/cmComputeLinkInformation.cxx b/Source/cmComputeLinkInformation.cxx
index abc1234..def5678 100644
--- a/Source/cmComputeLinkInformation.cxx
+++ b/Source/cmComputeLinkInformation.cxx
@@ -456,14 +456,19 @@ std::vector<BT<std::string>>
cmComputeLinkInformation::GetDirectoriesWithBacktraces()
{
std::vector<BT<std::string>> directoriesWithBacktraces;
std::vector<BT<std::string>> targetLinkDirectories =
this->Target->GetLinkDirectories(this->Config, this->LinkLanguage);
+ // Build an index from directory string → BT entry for O(1) lookup.
+ std::unordered_map<std::string, BT<std::string>> dirIndex;
+ dirIndex.reserve(targetLinkDirectories.size());
+ for (auto& bt : targetLinkDirectories)
+ dirIndex.emplace(bt.Value, std::move(bt));
+
std::vector<std::string> const& orderedDirectories = this->GetDirectories();
for (std::string const& dir : orderedDirectories) {
- auto result = std::find(targetLinkDirectories.begin(),
- targetLinkDirectories.end(), dir);
- if (result != targetLinkDirectories.end()) {
- directoriesWithBacktraces.emplace_back(std::move(*result));
+ auto result = dirIndex.find(dir);
+ if (result != dirIndex.end()) {
+ directoriesWithBacktraces.emplace_back(std::move(result->second));
} else {
directoriesWithBacktraces.emplace_back(dir);
}

View file

@ -0,0 +1,31 @@
diff --git a/Source/cmComputeLinkInformation.h b/Source/cmComputeLinkInformation.h
index abc1234..def5678 100644
--- a/Source/cmComputeLinkInformation.h
+++ b/Source/cmComputeLinkInformation.h
@@ -1,6 +1,7 @@
#pragma once
#include <map>
+#include <unordered_set>
#include <vector>
// ... (other includes unchanged)
@@ -180,7 +181,9 @@ private:
// DLL dependencies to copy at runtime (Windows/DLL platforms only).
// Populated by AddRuntimeDLL(), queried by GetRuntimeDLLs().
std::vector<cmGeneratorTarget const*> RuntimeDLLs;
+ // Shadow set for O(1) membership check in AddRuntimeDLL.
+ std::unordered_set<cmGeneratorTarget const*> RuntimeDLLsSet;
diff --git a/Source/cmComputeLinkInformation.cxx b/Source/cmComputeLinkInformation.cxx
index abc1234..def5678 100644
--- a/Source/cmComputeLinkInformation.cxx
+++ b/Source/cmComputeLinkInformation.cxx
@@ -1350,8 +1350,9 @@ void cmComputeLinkInformation::AddRuntimeDLL(cmGeneratorTarget const* tgt)
{
- if (std::find(this->RuntimeDLLs.begin(), this->RuntimeDLLs.end(), tgt) ==
- this->RuntimeDLLs.end()) {
+ if (this->RuntimeDLLsSet.insert(tgt).second) {
this->RuntimeDLLs.emplace_back(tgt);
}
}

View file

@ -0,0 +1,28 @@
diff --git a/Source/cmTarget.cxx b/Source/cmTarget.cxx
index abc1234..def5678 100644
--- a/Source/cmTarget.cxx
+++ b/Source/cmTarget.cxx
@@ -1428,11 +1428,19 @@ cmSourceFile* cmTarget::AddSource(std::string const& src, bool before)
{
cmSourceFileLocation sfl(this->impl->Makefile, src,
cmSourceFileLocationKind::Known);
+ // Fast path: check a parallel unordered_set of source-path strings.
+ // The full TargetPropertyEntryFinder scan is O(n) and is called from loops
+ // (e.g. Unity build file iteration), producing O(n^2) overall.
+ // An exact-string set lookup is O(1) amortized and covers the common case
+ // where src is a canonical resolved path.
+ if (!this->impl->SourcePathsSet.insert(src).second) {
+ // Already present — skip the expensive find_if and WriteDirect.
+ if (cmGeneratorExpression::Find(src) != std::string::npos)
+ return nullptr;
+ return this->impl->Makefile->GetOrCreateSource(
+ src, false, cmSourceFileLocationKind::Known);
+ }
auto const& sources = this->impl->Sources.Entries;
if (std::find_if(sources.begin(), sources.end(),
TargetPropertyEntryFinder(sfl)) == sources.end()) {
this->impl->Sources.WriteDirect(
this->impl.get(), {}, cmValue(src),
before ? UsageRequirementProperty::Action::Prepend
: UsageRequirementProperty::Action::Append);
}

View file

@ -0,0 +1,89 @@
package unit;
import java.util.*;
/**
* CWE-407 unit test: cmake-0004
* AddSource — O(n²) std::find_if per call in Unity build loop vs O(n) set guard.
*
* Simulates cmTarget::AddSource called in loop over unity_files:
* slow: scan Sources.Entries vector (find_if) for each add → O(n²)
* fast: use HashSet shadow, skip scan if already present → O(n)
*/
public class AddSourceAlgorithm {
// Slow: O(n^2) — find_if scan per AddSource call (mirrors TargetPropertyEntryFinder)
static long slowAddSources(List<String> unityFiles) {
long ops = 0;
List<String> sourcesEntries = new ArrayList<>();
for (String src : unityFiles) {
boolean found = false;
for (String existing : sourcesEntries) { // O(n) scan
ops++;
if (existing.equals(src)) {
found = true;
break;
}
}
if (!found) {
sourcesEntries.add(src);
}
}
return ops;
}
// Fast: HashSet shadow for O(1) check, O(n) total
static long fastAddSources(List<String> unityFiles) {
long ops = 0;
Set<String> sourcePathsSet = new HashSet<>();
List<String> sourcesEntries = new ArrayList<>();
for (String src : unityFiles) {
ops++;
if (sourcePathsSet.add(src)) {
sourcesEntries.add(src);
}
}
return ops;
}
public static void main(String[] args) {
int[] sizes = {200, 500, 1000, 2000};
int passed = 0, total = 0;
for (int S : sizes) {
// S unity source files, 20% duplicates (re-added after PCH generation)
List<String> unityFiles = new ArrayList<>(S + S / 5);
for (int i = 0; i < S; i++) {
unityFiles.add("/src/unity_" + i + ".cpp");
}
for (int i = 0; i < S / 5; i++) {
unityFiles.add("/src/unity_" + i + ".cpp"); // duplicate
}
long slowOps = slowAddSources(unityFiles);
long fastOps = fastAddSources(unityFiles);
double ratio = (double) slowOps / fastOps;
total++;
System.out.printf("S=%4d slow=%8d fast=%6d ratio=%.1fx%n",
S, slowOps, fastOps, ratio);
// At S=2000: slow is O(S^2/2) ~ 2M, fast is O(S) ~ 2000 → >100x
assert ratio > 20.0 : "Expected ratio > 20 at S=" + S + ", got " + ratio;
passed++;
}
// Correctness: same unique list preserved
List<String> input = Arrays.asList("a.cpp", "b.cpp", "a.cpp", "c.cpp");
List<String> slowOut = new ArrayList<>();
for (String s : input) {
if (!slowOut.contains(s)) slowOut.add(s);
}
Set<String> fastOut = new LinkedHashSet<>();
fastOut.addAll(input);
assert new ArrayList<>(fastOut).equals(slowOut) :
"Correctness: " + fastOut + " vs " + slowOut;
passed++; total++;
System.out.printf("%d/%d PASS%n", passed, total);
}
}

View file

@ -0,0 +1,89 @@
package unit;
import java.util.*;
/**
* CWE-407 unit test: cmake-0002
* GetDirectoriesWithBacktraces — O(n*m) std::find inside loop vs O(n+m) map lookup.
*
* Simulates CMake's GetDirectoriesWithBacktraces():
* slow: for each orderedDir, scan targetLinkDirs linearly → O(n*m)
* fast: build HashMap from targetLinkDirs, then lookup → O(n+m)
*/
public class GetDirectoriesAlgorithm {
// Slow: O(n*m) — mirrors std::find inside for loop
static long slowGetDirectories(List<String> orderedDirs, List<String> targetLinkDirs) {
long ops = 0;
List<String> result = new ArrayList<>();
for (String dir : orderedDirs) {
boolean found = false;
for (String t : targetLinkDirs) { // O(m) scan per element
ops++;
if (t.equals(dir)) {
result.add(t + "#BT");
found = true;
break;
}
}
if (!found) result.add(dir);
}
return ops;
}
// Fast: O(n+m) — build map then lookup
static long fastGetDirectories(List<String> orderedDirs, List<String> targetLinkDirs) {
long ops = 0;
Map<String, String> dirIndex = new HashMap<>(targetLinkDirs.size() * 2);
for (String t : targetLinkDirs) {
ops++;
dirIndex.put(t, t + "#BT");
}
List<String> result = new ArrayList<>();
for (String dir : orderedDirs) {
ops++;
String bt = dirIndex.get(dir);
result.add(bt != null ? bt : dir);
}
return ops;
}
public static void main(String[] args) {
int[] sizes = {100, 500, 1000};
int passed = 0, total = 0;
for (int N : sizes) {
// Build N ordered dirs, half of which are in targetLinkDirs
List<String> orderedDirs = new ArrayList<>(N);
List<String> targetLinkDirs = new ArrayList<>(N);
for (int i = 0; i < N; i++) {
orderedDirs.add("/usr/lib/dir" + i);
}
for (int i = 0; i < N; i += 2) {
targetLinkDirs.add("/usr/lib/dir" + i); // every other dir has BT
}
long slowOps = slowGetDirectories(orderedDirs, targetLinkDirs);
long fastOps = fastGetDirectories(orderedDirs, targetLinkDirs);
double ratio = (double) slowOps / fastOps;
total++;
System.out.printf("N=%4d slow=%8d fast=%6d ratio=%.1fx%n",
N, slowOps, fastOps, ratio);
// At N=1000: slow ~ N*M/2 = 250000, fast ~ 3N/2 = 1500 → >100x
assert ratio > 5.0 : "Expected slow/fast ratio > 5, got " + ratio;
passed++;
}
// Correctness: both produce same result
List<String> dirs = Arrays.asList("/a", "/b", "/c");
List<String> targets = Arrays.asList("/b");
// Just verify no crash and ops make sense
long s = slowGetDirectories(dirs, targets);
long f = fastGetDirectories(dirs, targets);
assert s > 0 && f > 0;
passed++; total++;
System.out.printf("%d/%d PASS%n", passed, total);
}
}

View file

@ -0,0 +1,86 @@
package unit;
import java.util.*;
/**
* CWE-407 unit test: cmake-0003
* AddRuntimeDLL — O(n²) std::find before emplace_back vs O(n) set insertion.
*
* Simulates CMake's AddRuntimeDLL():
* slow: scan vector for membership before push → O(n) per call, O(n²) total
* fast: use HashSet for membership check → O(1) per call, O(n) total
*/
public class RuntimeDllAlgorithm {
// Slow: mirrors std::find(begin, end, tgt) == end before emplace_back
static long slowAddRuntimeDlls(List<Integer> dllIds) {
long ops = 0;
List<Integer> runtimeDlls = new ArrayList<>();
for (int id : dllIds) {
boolean found = false;
for (int existing : runtimeDlls) { // O(n) scan
ops++;
if (existing == id) {
found = true;
break;
}
}
if (!found) runtimeDlls.add(id);
}
return ops;
}
// Fast: unordered_set insertion returns bool (inserted = new)
static long fastAddRuntimeDlls(List<Integer> dllIds) {
long ops = 0;
Set<Integer> runtimeDllsSet = new HashSet<>();
List<Integer> runtimeDlls = new ArrayList<>();
for (int id : dllIds) {
ops++;
if (runtimeDllsSet.add(id)) {
runtimeDlls.add(id);
}
}
return ops;
}
public static void main(String[] args) {
int[] sizes = {100, 500, 1000};
int passed = 0, total = 0;
for (int N : sizes) {
// D unique DLLs, each referenced twice (common: transitive deps)
List<Integer> dllIds = new ArrayList<>(N * 2);
for (int i = 0; i < N; i++) dllIds.add(i);
for (int i = 0; i < N; i++) dllIds.add(i); // duplicates
long slowOps = slowAddRuntimeDlls(dllIds);
long fastOps = fastAddRuntimeDlls(dllIds);
double ratio = (double) slowOps / fastOps;
total++;
System.out.printf("D=%4d slow=%8d fast=%6d ratio=%.1fx%n",
N, slowOps, fastOps, ratio);
// At D=1000: slow accumulates O(N²/2)=500k ops, fast=2N=2000
assert ratio > 10.0 : "Expected ratio > 10, got " + ratio;
passed++;
}
// Correctness: same unique set produced
List<Integer> input = Arrays.asList(1, 2, 1, 3, 2, 4);
List<Integer> slowResult = new ArrayList<>();
Set<Integer> seen = new HashSet<>();
for (int id : input) {
boolean found = false;
for (int x : slowResult) if (x == id) { found = true; break; }
if (!found) slowResult.add(id);
}
Set<Integer> fastResult = new HashSet<>();
for (int id : input) fastResult.add(id);
assert new HashSet<>(slowResult).equals(fastResult) :
"Correctness check failed: " + slowResult + " vs " + fastResult;
passed++; total++;
System.out.printf("%d/%d PASS%n", passed, total);
}
}