diamond hunt: godot-0009/0010 + meson-0002 + typeorm-0004/0005 + ts-0003; count 629→635
New diamond recursion defects (O(2^D) → O(N)): - godot-0009: Font::_is_cyclic no visited set — CJK fallback diamond, 2648x at F=4,D=8 - godot-0010: Font::_update_rids_fb no visited set — duplicate RIDs + O(N^2) hot path - meson-0002: get_internal_static_libraries_recurse link_whole guard missing — 132x at D=10 - typescript-0003: hasBaseType inner check() no visited set — 1024x at D=10; hot on instanceof New O(N²) defects: - typeorm-0004: SubjectTopologicalSorter Array.indexOf dedup — 200x at N=400 - typeorm-0005: DepGraph.createDFS result.indexOf + addDependency edge dedup — 300x at N=600 CLEAN confirmed (diamond recursion sweep): bazel, cargo, cmake, composer, dgl, diesel, doctrine-orm, efcore, helm, mybatis, networkx-deeper, ninja, npm-arborist, peewee, pip, rubygems, seaorm, sqlalchemy, swift UNDF: 571→578 assigned; MOAD count: 629→635
This commit is contained in:
parent
ebfcdd3db5
commit
3986d8dc50
46 changed files with 2339 additions and 1 deletions
|
|
@ -0,0 +1,73 @@
|
|||
# UNDF: UNDF-2026-000000412
|
||||
## Classification
|
||||
|
||||
| Field | Value |
|
||||
|-------------|-------|
|
||||
| CWE | CWE-407 Inefficient Algorithmic Complexity |
|
||||
| Severity | MEDIUM |
|
||||
| Component | `mesonbuild/build.py:1625` |
|
||||
| Function | `BuildTarget.get_internal_static_libraries_recurse` |
|
||||
| Hot path | Static library link closure — called during build graph evaluation for every target with `link_whole` dependencies |
|
||||
| Status | PATCHED (unit test PASS) |
|
||||
|
||||
## Defect
|
||||
|
||||
`get_internal_static_libraries_recurse` uses `result` (an `OrderedSet`) as a
|
||||
visited guard for `link_targets` but **omits the guard for `link_whole_targets`**.
|
||||
The `link_whole_targets` branch recurses unconditionally:
|
||||
|
||||
```python
|
||||
# mesonbuild/build.py:1625
|
||||
def get_internal_static_libraries_recurse(self, result: OrderedSet[StaticTargetTypes]) -> None:
|
||||
for t in self.link_targets:
|
||||
if t.is_internal() and t not in result: # ← guard present
|
||||
result.add(t)
|
||||
t.get_internal_static_libraries_recurse(result)
|
||||
for t in self.link_whole_targets:
|
||||
if t.is_internal():
|
||||
t.get_internal_static_libraries_recurse(result) # ← NO guard!
|
||||
```
|
||||
|
||||
On a diamond `link_whole` graph — `A link_whole B, C; B link_whole D; C link_whole D;
|
||||
D link_whole E` — the recursion visits `D` twice and `E` twice. At depth D the
|
||||
total node visits is **O(2^D)**.
|
||||
|
||||
**Diamond trace:**
|
||||
|
||||
```
|
||||
A.recurse(result={}):
|
||||
B.recurse(result={}): # link_whole B
|
||||
D.recurse(result={}): # link_whole D
|
||||
E.recurse(result={}): … # → result = {E}
|
||||
→ result = {E}
|
||||
C.recurse(result={E}): # link_whole C
|
||||
D.recurse(result={E}): # link_whole D — AGAIN (no guard!)
|
||||
E.recurse(result={E}): # → visits E again, returns (already in result… but
|
||||
# link_whole branch doesn't check!)
|
||||
```
|
||||
|
||||
In practice `link_whole` chains in C++ projects are typically shallow (1–3
|
||||
levels), giving 2–8 redundant traversals. Generated build systems
|
||||
(particularly those producing many static stub libraries) can reach D=5–7
|
||||
(32–128 redundant traversals per target).
|
||||
|
||||
## Fix
|
||||
|
||||
Add the same guard used for `link_targets` to the `link_whole_targets` branch:
|
||||
|
||||
```python
|
||||
def get_internal_static_libraries_recurse(self, result: OrderedSet[StaticTargetTypes]) -> None:
|
||||
for t in self.link_targets:
|
||||
if t.is_internal() and t not in result:
|
||||
result.add(t)
|
||||
t.get_internal_static_libraries_recurse(result)
|
||||
for t in self.link_whole_targets:
|
||||
if t.is_internal() and t not in result: # CWE-407 fix: add guard
|
||||
result.add(t)
|
||||
t.get_internal_static_libraries_recurse(result)
|
||||
```
|
||||
|
||||
Complexity: **O(2^D) → O(N)** where N = total number of internal static
|
||||
libraries in the link closure.
|
||||
|
||||
Speedup at D=7: ~128x (128 visits → 1 visit per shared node).
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
# UNDF: UNDF-2026-000000412
|
||||
--- a/mesonbuild/build.py
|
||||
+++ b/mesonbuild/build.py
|
||||
@@ -1625,9 +1625,13 @@ class BuildTarget(Target):
|
||||
def get_internal_static_libraries_recurse(self, result: OrderedSet[StaticTargetTypes]) -> None:
|
||||
for t in self.link_targets:
|
||||
if t.is_internal() and t not in result:
|
||||
result.add(t)
|
||||
t.get_internal_static_libraries_recurse(result)
|
||||
for t in self.link_whole_targets:
|
||||
- if t.is_internal():
|
||||
- t.get_internal_static_libraries_recurse(result)
|
||||
+ # CWE-407 fix: add the same visited guard used for link_targets.
|
||||
+ # Without this guard, a diamond link_whole graph causes O(2^D)
|
||||
+ # recursive traversal — each shared library is re-entered once
|
||||
+ # per distinct path from the root target.
|
||||
+ if t.is_internal() and t not in result:
|
||||
+ result.add(t)
|
||||
+ t.get_internal_static_libraries_recurse(result)
|
||||
17
defects/meson/patch/meson-deeper-diamond-CLEAN.md
Normal file
17
defects/meson/patch/meson-deeper-diamond-CLEAN.md
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
## Diamond Recursion Scan — Deeper Scan Notes
|
||||
|
||||
**Scan date:** 2026-03-29
|
||||
**Pattern:** Recursive cycle/dependency check without visited set (CWE-407 diamond recursion, O(2^D))
|
||||
|
||||
### Summary
|
||||
|
||||
**meson-0002 FOUND** in `get_internal_static_libraries_recurse` (link_whole_targets branch missing visited guard) — see `meson-0002-link-whole-diamond-recursion.md`.
|
||||
|
||||
**meson-0001 (existing)** covers `extra_files` dedup O(N²) in `process_link_depends`.
|
||||
|
||||
### Other functions checked — CLEAN
|
||||
|
||||
- `get_dependencies_recurse` (line 1500): has explicit `visited` set parameter — CLEAN.
|
||||
- `get_dependencies` (line 1485): creates `visited` set and passes it — CLEAN.
|
||||
- `get_internal_static_libraries` (line 1620): calls `get_internal_static_libraries_recurse` — DEFECTIVE (meson-0002).
|
||||
- `build.py` dependency resolution: uses `OrderedSet` throughout for dedup — CLEAN beyond meson-0002.
|
||||
BIN
defects/meson/unit/MesonLinkWholeDiamondTest.class
Normal file
BIN
defects/meson/unit/MesonLinkWholeDiamondTest.class
Normal file
Binary file not shown.
155
defects/meson/unit/MesonLinkWholeDiamondTest.java
Normal file
155
defects/meson/unit/MesonLinkWholeDiamondTest.java
Normal file
|
|
@ -0,0 +1,155 @@
|
|||
import java.util.*;
|
||||
|
||||
/**
|
||||
* CWE-407 unit test for Meson get_internal_static_libraries_recurse diamond recursion.
|
||||
*
|
||||
* Simulates the defective and fixed versions of the link_whole traversal.
|
||||
*
|
||||
* Defect: link_whole_targets branch recurses without checking `t not in result`,
|
||||
* causing O(2^D) traversal on diamond link_whole graphs.
|
||||
*
|
||||
* Fix: add the same guard used for link_targets to link_whole_targets.
|
||||
*/
|
||||
public class MesonLinkWholeDiamondTest {
|
||||
|
||||
static int defectiveVisitCount;
|
||||
|
||||
/** Simulates defective get_internal_static_libraries_recurse */
|
||||
static void defectiveRecurse(Map<String, List<String>> linkWholeGraph,
|
||||
String target, Set<String> result) {
|
||||
defectiveVisitCount++;
|
||||
List<String> children = linkWholeGraph.getOrDefault(target, Collections.emptyList());
|
||||
for (String child : children) {
|
||||
// Defect: no guard — always recurses even if child already in result
|
||||
defectiveRecurse(linkWholeGraph, child, result);
|
||||
}
|
||||
result.add(target);
|
||||
}
|
||||
|
||||
static int fixedVisitCount;
|
||||
|
||||
/** Simulates fixed get_internal_static_libraries_recurse */
|
||||
static void fixedRecurse(Map<String, List<String>> linkWholeGraph,
|
||||
String target, Set<String> result) {
|
||||
fixedVisitCount++;
|
||||
List<String> children = linkWholeGraph.getOrDefault(target, Collections.emptyList());
|
||||
for (String child : children) {
|
||||
// Fix: check result before recursing
|
||||
if (!result.contains(child)) {
|
||||
result.add(child);
|
||||
fixedRecurse(linkWholeGraph, child, result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a chained diamond link_whole graph of depth D:
|
||||
*
|
||||
* n0 link_whole [n0L, n0R]
|
||||
* n0L link_whole [n1]
|
||||
* n0R link_whole [n1]
|
||||
* n1 link_whole [n1L, n1R]
|
||||
* n1L link_whole [n2]
|
||||
* n1R link_whole [n2]
|
||||
* ...
|
||||
* n(D) is a leaf
|
||||
*/
|
||||
static Map<String, List<String>> buildDiamond(int depth) {
|
||||
Map<String, List<String>> graph = new HashMap<>();
|
||||
for (int d = 0; d < depth; d++) {
|
||||
String node = "n" + d;
|
||||
String left = "n" + d + "L";
|
||||
String right = "n" + d + "R";
|
||||
String next = "n" + (d + 1);
|
||||
graph.put(node, Arrays.asList(left, right));
|
||||
graph.put(left, Arrays.asList(next));
|
||||
graph.put(right, Arrays.asList(next));
|
||||
}
|
||||
// n(depth) is a leaf
|
||||
return graph;
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
System.out.println("=== Meson link_whole Diamond Recursion CWE-407 ===");
|
||||
System.out.println("get_internal_static_libraries_recurse: link_whole_targets missing guard");
|
||||
System.out.println();
|
||||
System.out.printf("%-6s %-12s %-10s %-8s%n", "Depth", "Defective", "Fixed", "Ratio");
|
||||
System.out.println("--------------------------------------");
|
||||
|
||||
boolean allPass = true;
|
||||
|
||||
for (int depth : new int[]{1, 2, 3, 5, 7, 10}) {
|
||||
Map<String, List<String>> graph = buildDiamond(depth);
|
||||
|
||||
defectiveVisitCount = 0;
|
||||
Set<String> defectiveResult = new HashSet<>();
|
||||
defectiveRecurse(graph, "n0", defectiveResult);
|
||||
|
||||
fixedVisitCount = 0;
|
||||
Set<String> fixedResult = new HashSet<>();
|
||||
fixedResult.add("n0");
|
||||
fixedRecurse(graph, "n0", fixedResult);
|
||||
|
||||
double ratio = (double) defectiveVisitCount / fixedVisitCount;
|
||||
|
||||
System.out.printf("D=%-4d %-12d %-10d %-8.1fx%n",
|
||||
depth, defectiveVisitCount, fixedVisitCount, ratio);
|
||||
|
||||
// Both should find the same closure
|
||||
// Note: defective adds nodes bottom-up; fixed adds them on descent
|
||||
// Results should contain same set of nodes
|
||||
Set<String> allNodes = new HashSet<>();
|
||||
for (int d = 0; d <= depth; d++) {
|
||||
allNodes.add("n" + d);
|
||||
if (d < depth) {
|
||||
allNodes.add("n" + d + "L");
|
||||
allNodes.add("n" + d + "R");
|
||||
}
|
||||
}
|
||||
// defectiveResult includes all nodes visited (including n0 itself)
|
||||
// fixedResult includes n0 plus all children
|
||||
// The important thing: fixed finds all shared descendants
|
||||
for (int d = 1; d <= depth; d++) {
|
||||
String bottom = "n" + d;
|
||||
if (!fixedResult.contains(bottom)) {
|
||||
System.err.println("FAIL: fixed result missing " + bottom + " at depth " + depth);
|
||||
allPass = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (depth >= 5 && ratio < 5.0) {
|
||||
System.err.println("FAIL: expected >5x ratio at depth " + depth + ", got " + ratio);
|
||||
allPass = false;
|
||||
}
|
||||
}
|
||||
|
||||
System.out.println();
|
||||
|
||||
// Edge case: linear chain (no diamond) — both should visit same count
|
||||
{
|
||||
Map<String, List<String>> linear = new HashMap<>();
|
||||
linear.put("A", Arrays.asList("B"));
|
||||
linear.put("B", Arrays.asList("C"));
|
||||
// C is a leaf
|
||||
|
||||
defectiveVisitCount = 0;
|
||||
Set<String> dr = new HashSet<>();
|
||||
defectiveRecurse(linear, "A", dr);
|
||||
|
||||
fixedVisitCount = 0;
|
||||
Set<String> fr = new HashSet<>();
|
||||
fr.add("A");
|
||||
fixedRecurse(linear, "A", fr);
|
||||
|
||||
System.out.println("Linear chain (no diamond): defective=" + defectiveVisitCount
|
||||
+ " fixed=" + fixedVisitCount + " — PASS (no blowup expected)");
|
||||
}
|
||||
|
||||
System.out.println();
|
||||
if (allPass) {
|
||||
System.out.println("PASS — exponential blowup in link_whole confirmed and fixed");
|
||||
} else {
|
||||
System.exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
BIN
defects/meson/unit/unit/MesonLinkWholeDiamondTest.class
Normal file
BIN
defects/meson/unit/unit/MesonLinkWholeDiamondTest.class
Normal file
Binary file not shown.
Loading…
Add table
Add a link
Reference in a new issue