java-topology/defects/meson/patch/meson-0002-link-whole-diamond-recursion.md
russell@unturf.com 3986d8dc50 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
2026-03-29 16:52:04 -04:00

73 lines
2.8 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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 (13
levels), giving 28 redundant traversals. Generated build systems
(particularly those producing many static stub libraries) can reach D=57
(32128 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).