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
332
defects/godot/unit/GodotFontCyclicTest.java
Normal file
332
defects/godot/unit/GodotFontCyclicTest.java
Normal file
|
|
@ -0,0 +1,332 @@
|
|||
package unit;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* GodotFontCyclicTest — godot-0009 / godot-0010
|
||||
*
|
||||
* Standalone Java proof of the CWE-407 diamond-recursion patterns in Godot's
|
||||
* font fallback graph traversal.
|
||||
*
|
||||
* Two defects:
|
||||
* godot-0009: Font::_is_cyclic() — no visited set, O(F^D) exponential
|
||||
* re-traversal of shared fallback nodes in a diamond graph;
|
||||
* fix: HashSet<Font*> visited passed through recursion → O(N).
|
||||
*
|
||||
* godot-0010: Font::_update_rids_fb() — no visited set, O(N^2) re-traversal
|
||||
* of shared fonts; rids[] accumulates duplicate RIDs; called on
|
||||
* every text render op; fix: HashSet<Font*> visited → O(N),
|
||||
* each font RID collected exactly once.
|
||||
*
|
||||
* Run: javac -d . GodotFontCyclicTest.java && java -ea unit.GodotFontCyclicTest
|
||||
*/
|
||||
public class GodotFontCyclicTest {
|
||||
|
||||
// ── Minimal Font model ───────────────────────────────────────────────────
|
||||
|
||||
static class Font {
|
||||
final String name;
|
||||
final List<Font> fallbacks = new ArrayList<>();
|
||||
final int rid; // simulated RID (renderer handle)
|
||||
|
||||
Font(String name, int rid) {
|
||||
this.name = name;
|
||||
this.rid = rid;
|
||||
}
|
||||
|
||||
void addFallback(Font f) { fallbacks.add(f); }
|
||||
|
||||
@Override public String toString() { return name; }
|
||||
}
|
||||
|
||||
// ── godot-0009: _is_cyclic without visited set ───────────────────────────
|
||||
|
||||
/**
|
||||
* SLOW: Simulates Font::_is_cyclic without a visited set (the defect).
|
||||
* Counts nodes visited. Returns {isCyclic, nodesVisited}.
|
||||
*/
|
||||
static long[] isCyclicSlow(Font thisFont, Font p_f, int depth, int maxDepth) {
|
||||
if (depth > maxDepth) return new long[]{1, 1};
|
||||
if (p_f == null) return new long[]{0, 1};
|
||||
if (p_f == thisFont) return new long[]{1, 1};
|
||||
|
||||
long visits = 1;
|
||||
for (Font f : p_f.fallbacks) {
|
||||
long[] result = isCyclicSlow(thisFont, f, depth + 1, maxDepth);
|
||||
visits += result[1];
|
||||
if (result[0] == 1) return new long[]{1, visits};
|
||||
}
|
||||
return new long[]{0, visits};
|
||||
}
|
||||
|
||||
/**
|
||||
* FAST: Simulates Font::_is_cyclic_internal with a visited set (the fix).
|
||||
* Returns {isCyclic, nodesVisited}.
|
||||
*/
|
||||
static long[] isCyclicFast(Font thisFont, Font p_f, int depth, int maxDepth,
|
||||
Set<Font> visited) {
|
||||
if (depth > maxDepth) return new long[]{1, 1};
|
||||
if (p_f == null) return new long[]{0, 1};
|
||||
if (p_f == thisFont) return new long[]{1, 1};
|
||||
if (visited.contains(p_f)) return new long[]{0, 0}; // skip — already proven safe
|
||||
|
||||
visited.add(p_f);
|
||||
long visits = 1;
|
||||
for (Font f : p_f.fallbacks) {
|
||||
long[] result = isCyclicFast(thisFont, f, depth + 1, maxDepth, visited);
|
||||
visits += result[1];
|
||||
if (result[0] == 1) return new long[]{1, visits};
|
||||
}
|
||||
return new long[]{0, visits};
|
||||
}
|
||||
|
||||
/**
|
||||
* Simulate set_fallbacks(p_fallbacks) for a diamond graph.
|
||||
* thisFont wants to add F new fallbacks; each new fallback shares D shared fonts
|
||||
* in a diamond topology below it.
|
||||
*
|
||||
* Diamond structure (depth=3, fanout=2):
|
||||
* new[0] new[1] ... new[F-1]
|
||||
* / \ / \
|
||||
* mid0 mid1 ... (fanout fonts at depth 1)
|
||||
* \ /
|
||||
* leaf0 leaf1 ... (shared leafs — same Font objects)
|
||||
*
|
||||
* With no visited set, each leaf is visited 2^(depth-1) times per call.
|
||||
*/
|
||||
static long setFallbacksOpsSlowDiamond(int F, int fanout, int depth) {
|
||||
// Build diamond: fanout^0 nodes at top, fanout^1 mid, ...
|
||||
// All paths converge to `fanout^(depth-1)` shared leaf nodes.
|
||||
|
||||
// Create shared leaves
|
||||
int numLeaves = fanout;
|
||||
Font[] leaves = new Font[numLeaves];
|
||||
for (int i = 0; i < numLeaves; i++) {
|
||||
leaves[i] = new Font("leaf" + i, 100 + i);
|
||||
}
|
||||
|
||||
// Build a diamond of given depth over the leaves
|
||||
Font[] currentLevel = leaves;
|
||||
for (int d = 1; d < depth; d++) {
|
||||
Font[] nextLevel = new Font[currentLevel.length]; // same width (shared convergence)
|
||||
for (int i = 0; i < nextLevel.length; i++) {
|
||||
nextLevel[i] = new Font("mid_d" + d + "_" + i, 200 + d * 10 + i);
|
||||
for (Font leaf : currentLevel) {
|
||||
nextLevel[i].addFallback(leaf); // all mids point to all leaves → diamond
|
||||
}
|
||||
}
|
||||
currentLevel = nextLevel;
|
||||
}
|
||||
Font[] topLevel = currentLevel;
|
||||
|
||||
// Create F new fonts, each pointing to all top-level nodes
|
||||
long totalOps = 0;
|
||||
Font thisFont = new Font("this", 0);
|
||||
for (int f = 0; f < F; f++) {
|
||||
Font newFb = new Font("newFb" + f, 300 + f);
|
||||
for (Font top : topLevel) {
|
||||
newFb.addFallback(top);
|
||||
}
|
||||
// Simulate set_fallbacks calling _is_cyclic(newFb, 0) — no visited set
|
||||
long[] result = isCyclicSlow(thisFont, newFb, 0, 64);
|
||||
totalOps += result[1];
|
||||
}
|
||||
return totalOps;
|
||||
}
|
||||
|
||||
static long setFallbacksOpsFastDiamond(int F, int fanout, int depth) {
|
||||
int numLeaves = fanout;
|
||||
Font[] leaves = new Font[numLeaves];
|
||||
for (int i = 0; i < numLeaves; i++) {
|
||||
leaves[i] = new Font("leaf" + i, 100 + i);
|
||||
}
|
||||
Font[] currentLevel = leaves;
|
||||
for (int d = 1; d < depth; d++) {
|
||||
Font[] nextLevel = new Font[currentLevel.length];
|
||||
for (int i = 0; i < nextLevel.length; i++) {
|
||||
nextLevel[i] = new Font("mid_d" + d + "_" + i, 200 + d * 10 + i);
|
||||
for (Font leaf : currentLevel) {
|
||||
nextLevel[i].addFallback(leaf);
|
||||
}
|
||||
}
|
||||
currentLevel = nextLevel;
|
||||
}
|
||||
Font[] topLevel = currentLevel;
|
||||
|
||||
long totalOps = 0;
|
||||
Font thisFont = new Font("this", 0);
|
||||
for (int f = 0; f < F; f++) {
|
||||
Font newFb = new Font("newFb" + f, 300 + f);
|
||||
for (Font top : topLevel) {
|
||||
newFb.addFallback(top);
|
||||
}
|
||||
// Fix: per-call visited set — shared across the entire _is_cyclic traversal
|
||||
Set<Font> visited = new HashSet<>();
|
||||
long[] result = isCyclicFast(thisFont, newFb, 0, 64, visited);
|
||||
totalOps += result[1];
|
||||
}
|
||||
return totalOps;
|
||||
}
|
||||
|
||||
// ── godot-0010: _update_rids_fb without visited set ─────────────────────
|
||||
|
||||
/**
|
||||
* SLOW: Simulates _update_rids_fb without visited set.
|
||||
* Returns list of collected RIDs (may contain duplicates).
|
||||
*/
|
||||
static List<Integer> updateRidsSlow(Font p_f, int depth, int maxDepth) {
|
||||
List<Integer> rids = new ArrayList<>();
|
||||
updateRidsFbSlow(p_f, depth, maxDepth, rids);
|
||||
return rids;
|
||||
}
|
||||
|
||||
static void updateRidsFbSlow(Font p_f, int depth, int maxDepth, List<Integer> rids) {
|
||||
if (depth > maxDepth || p_f == null) return;
|
||||
if (p_f.rid > 0) rids.add(p_f.rid);
|
||||
for (Font fb : p_f.fallbacks) {
|
||||
updateRidsFbSlow(fb, depth + 1, maxDepth, rids); // ← no visited guard
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* FAST: Simulates _update_rids_fb with visited set (the fix).
|
||||
* Returns list of collected RIDs (each font at most once).
|
||||
*/
|
||||
static List<Integer> updateRidsFast(Font p_f, int depth, int maxDepth) {
|
||||
List<Integer> rids = new ArrayList<>();
|
||||
Set<Font> visited = new HashSet<>();
|
||||
updateRidsFbFast(p_f, depth, maxDepth, rids, visited);
|
||||
return rids;
|
||||
}
|
||||
|
||||
static void updateRidsFbFast(Font p_f, int depth, int maxDepth,
|
||||
List<Integer> rids, Set<Font> visited) {
|
||||
if (depth > maxDepth || p_f == null) return;
|
||||
if (visited.contains(p_f)) return; // skip — already collected
|
||||
visited.add(p_f);
|
||||
if (p_f.rid > 0) rids.add(p_f.rid);
|
||||
for (Font fb : p_f.fallbacks) {
|
||||
updateRidsFbFast(fb, depth + 1, maxDepth, rids, visited);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a diamond font graph and count operations and RID list size.
|
||||
* Diamond: A → {B, C}, B → D, C → D (D is shared CJK fallback font).
|
||||
*/
|
||||
static Font buildDiamondGraph(int sharedFonts) {
|
||||
Font root = new Font("root", 1);
|
||||
Font[] shared = new Font[sharedFonts];
|
||||
for (int i = 0; i < sharedFonts; i++) {
|
||||
shared[i] = new Font("cjk" + i, 10 + i);
|
||||
}
|
||||
// Create N "language" fonts, each referencing ALL shared CJK fonts
|
||||
int numLang = 4;
|
||||
for (int l = 0; l < numLang; l++) {
|
||||
Font lang = new Font("lang" + l, 50 + l);
|
||||
for (Font s : shared) lang.addFallback(s);
|
||||
root.addFallback(lang);
|
||||
}
|
||||
return root;
|
||||
}
|
||||
|
||||
static void bench(String label, long slowOps, long fastOps) {
|
||||
double ratio = fastOps > 0 ? (double) slowOps / fastOps : 999;
|
||||
System.out.printf(" %-48s slow: %,8d ops fast: %,8d ops ratio: %.0fx%n",
|
||||
label, slowOps, fastOps, ratio);
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
System.out.println("=== UNIT godot-0009 / godot-0010: Godot Font Fallback CWE-407 ===");
|
||||
System.out.println();
|
||||
|
||||
// ── godot-0009: _is_cyclic diamond test ─────────────────────────────
|
||||
System.out.println("godot-0009: Font::_is_cyclic — diamond fallback graph");
|
||||
|
||||
// Diamond parameters
|
||||
int F = 5; // number of new fallbacks to validate
|
||||
int fanout = 3; // fonts at each level
|
||||
int depth = 6; // depth of the diamond graph
|
||||
|
||||
long slowOps0009 = setFallbacksOpsSlowDiamond(F, fanout, depth);
|
||||
long fastOps0009 = setFallbacksOpsFastDiamond(F, fanout, depth);
|
||||
|
||||
bench(String.format("isCyclic(F=%d,fanout=%d,depth=%d)", F, fanout, depth),
|
||||
slowOps0009, fastOps0009);
|
||||
|
||||
// Deeper diamond
|
||||
int F2 = 4; int fanout2 = 4; int depth2 = 8;
|
||||
long slowOps0009b = setFallbacksOpsSlowDiamond(F2, fanout2, depth2);
|
||||
long fastOps0009b = setFallbacksOpsFastDiamond(F2, fanout2, depth2);
|
||||
bench(String.format("isCyclic(F=%d,fanout=%d,depth=%d)", F2, fanout2, depth2),
|
||||
slowOps0009b, fastOps0009b);
|
||||
|
||||
System.out.println();
|
||||
|
||||
// Assertions
|
||||
assert slowOps0009 > fastOps0009 * 10 :
|
||||
"godot-0009: expected slow >> fast; slow=" + slowOps0009 + " fast=" + fastOps0009;
|
||||
assert slowOps0009b > fastOps0009b * 100 :
|
||||
"godot-0009 deep: expected slow >> fast; slow=" + slowOps0009b + " fast=" + fastOps0009b;
|
||||
|
||||
// ── godot-0010: _update_rids_fb diamond test ─────────────────────────
|
||||
System.out.println("godot-0010: Font::_update_rids_fb — duplicate RIDs in diamond graph");
|
||||
|
||||
int sharedFonts = 8; // 8 shared CJK fallback fonts
|
||||
Font root = buildDiamondGraph(sharedFonts);
|
||||
|
||||
List<Integer> slowRids = updateRidsSlow(root, 0, 64);
|
||||
List<Integer> fastRids = updateRidsFast(root, 0, 64);
|
||||
|
||||
System.out.printf(" %-48s slow rids.size(): %d fast rids.size(): %d duplicates eliminated: %d%n",
|
||||
String.format("updateRids(sharedFonts=%d, langFonts=4)", sharedFonts),
|
||||
slowRids.size(), fastRids.size(), slowRids.size() - fastRids.size());
|
||||
|
||||
// Verify fast has no duplicates
|
||||
Set<Integer> fastRidSet = new HashSet<>(fastRids);
|
||||
assert fastRidSet.size() == fastRids.size() :
|
||||
"godot-0010: fast path must have no duplicate RIDs";
|
||||
|
||||
// Verify slow has duplicates
|
||||
Set<Integer> slowRidSet = new HashSet<>(slowRids);
|
||||
assert slowRids.size() > slowRidSet.size() :
|
||||
"godot-0010: slow path must produce duplicate RIDs (diamond graph)";
|
||||
|
||||
// Count ops (traversal steps)
|
||||
long[] slowResult = new long[]{0};
|
||||
long[] fastResult = new long[]{0};
|
||||
countOpsUpdateRidsSlow(root, 0, 64, slowResult);
|
||||
Set<Font> vis = new HashSet<>();
|
||||
countOpsUpdateRidsFast(root, 0, 64, fastResult, vis);
|
||||
|
||||
bench(String.format("updateRids(sharedFonts=%d, langFonts=4)", sharedFonts),
|
||||
slowResult[0], fastResult[0]);
|
||||
|
||||
assert slowResult[0] > fastResult[0] * 2 :
|
||||
"godot-0010: expected slow >> fast; slow=" + slowResult[0] + " fast=" + fastResult[0];
|
||||
|
||||
System.out.println();
|
||||
System.out.println("ALL ASSERTIONS PASS");
|
||||
System.out.println();
|
||||
System.out.println("Summary:");
|
||||
System.out.println(" godot-0009: Font::_is_cyclic O(F^D) → O(N) — visited set eliminates");
|
||||
System.out.println(" exponential re-traversal of shared fallback fonts");
|
||||
System.out.println(" godot-0010: Font::_update_rids_fb — no visited set causes duplicate");
|
||||
System.out.println(" RIDs and O(N^2) traversal; fix collects each font once");
|
||||
}
|
||||
|
||||
static void countOpsUpdateRidsSlow(Font p_f, int depth, int maxDepth, long[] ops) {
|
||||
if (depth > maxDepth || p_f == null) return;
|
||||
ops[0]++;
|
||||
for (Font fb : p_f.fallbacks) countOpsUpdateRidsSlow(fb, depth + 1, maxDepth, ops);
|
||||
}
|
||||
|
||||
static void countOpsUpdateRidsFast(Font p_f, int depth, int maxDepth,
|
||||
long[] ops, Set<Font> visited) {
|
||||
if (depth > maxDepth || p_f == null) return;
|
||||
if (visited.contains(p_f)) return;
|
||||
visited.add(p_f);
|
||||
ops[0]++;
|
||||
for (Font fb : p_f.fallbacks) countOpsUpdateRidsFast(fb, depth + 1, maxDepth, ops, visited);
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue