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
2.8 KiB
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:
# 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:
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).