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
155 lines
5.7 KiB
Java
155 lines
5.7 KiB
Java
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);
|
|
}
|
|
}
|
|
}
|