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
73 lines
2.8 KiB
Markdown
73 lines
2.8 KiB
Markdown
# 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).
|