game engines/web frameworks: 27 CWE-407 defects + 3 CLEAN; 194 sites, 78 ecosystems

This commit is contained in:
russell@unturf.com 2026-03-27 14:14:49 -04:00
parent 547a9f5738
commit 4d3fcc8e73
76 changed files with 6216 additions and 17 deletions

View file

@ -0,0 +1,325 @@
package unit;
import java.util.*;
/**
* LibGDXTest libgdx-0001..0004
*
* Proves CWE-407 in libGDX:
* libgdx-0001: Model.loadNode() nested for-loop string-ID scan O(parts × meshes + parts × materials)
* libgdx-0002: ModelBuilder.rebuildReferences() Array.contains() in node-part loop O(parts × materials)
* libgdx-0003: ModelInstance.invalidate() Array.contains() in node-part loop O(parts × materials)
* libgdx-0004: Kerning.readSubtable2() IntArray.contains() in GPOS coverage loop O(coverage × classes × K)
*
* Run: javac -d . LibGDXTest.java && java -ea unit.LibGDXTest
*/
public class LibGDXTest {
static void bench(String label, Runnable slow, Runnable fast, long sOps, long fOps) {
slow.run(); fast.run(); // warmup
long t0 = System.nanoTime(); slow.run(); long sMs = (System.nanoTime() - t0) / 1_000_000;
long t1 = System.nanoTime(); fast.run(); long fMs = (System.nanoTime() - t1) / 1_000_000;
double r = fOps > 0 ? (double) sOps / fOps : 0;
System.out.printf(" %-52s slow:%4dms (%,d ops) fast:%4dms (%,d ops) speedup:%.0fx%n",
label, sMs, sOps, fMs, fOps, r);
}
// libgdx-0001: Model.loadNode nested for-loop string scan
/**
* SLOW: simulates Model.loadNode() for each node-part, scan all meshParts
* (by string ID) and all materials (by string ID).
* O(parts × (meshCount + materialCount))
*/
static long loadNodeSlow(int nodePartCount, int meshCount, int materialCount) {
// Build arrays of String IDs
String[] meshIds = new String[meshCount];
String[] materialIds = new String[materialCount];
for (int i = 0; i < meshCount; i++) meshIds[i] = "mesh_" + i;
for (int i = 0; i < materialCount; i++) materialIds[i] = "mat_" + i;
long ops = 0;
// Simulate loading nodePartCount node-parts, each referencing the last mesh/material
String targetMesh = meshIds[meshCount - 1]; // worst case: always last
String targetMat = materialIds[materialCount - 1];
for (int p = 0; p < nodePartCount; p++) {
// for (MeshPart part : meshParts) { if id.equals(...) ... } O(M)
for (int m = 0; m < meshCount; m++) {
ops++;
if (meshIds[m].equals(targetMesh)) break;
}
// for (Material mat : materials) { if id.equals(...) ... } O(T)
for (int t = 0; t < materialCount; t++) {
ops++;
if (materialIds[t].equals(targetMat)) break;
}
}
return ops;
}
/**
* FAST: simulates fixed Model.loadNode() build HashMap<String, index> once
* before the node loop, use O(1) get() per node-part.
*/
static long loadNodeFast(int nodePartCount, int meshCount, int materialCount) {
String[] meshIds = new String[meshCount];
String[] materialIds = new String[materialCount];
for (int i = 0; i < meshCount; i++) meshIds[i] = "mesh_" + i;
for (int i = 0; i < materialCount; i++) materialIds[i] = "mat_" + i;
// Build lookup maps once O(M + T)
Map<String, Integer> meshById = new HashMap<>(meshCount * 2);
for (int i = 0; i < meshCount; i++) meshById.put(meshIds[i], i);
Map<String, Integer> matById = new HashMap<>(materialCount * 2);
for (int i = 0; i < materialCount; i++) matById.put(materialIds[i], i);
String targetMesh = meshIds[meshCount - 1];
String targetMat = materialIds[materialCount - 1];
long ops = 0;
for (int p = 0; p < nodePartCount; p++) {
ops++; meshById.get(targetMesh); // O(1)
ops++; matById.get(targetMat); // O(1)
}
return ops;
}
// libgdx-0002: ModelBuilder.rebuildReferences Array.contains
/**
* SLOW: simulates rebuildReferences() for each node-part, call Array.contains()
* (linear scan by identity) to check if material/meshPart/mesh is already added.
* O(parts × (materials + meshParts + meshes))
*/
static long rebuildRefsSlow(int nodePartCount, int materialCount) {
List<Object> materials = new ArrayList<>();
List<Object> meshParts = new ArrayList<>();
List<Object> meshes = new ArrayList<>();
// Pre-create unique objects
Object[] matObjs = new Object[materialCount];
Object[] partObjs = new Object[materialCount];
Object[] meshObjs = new Object[materialCount];
for (int i = 0; i < materialCount; i++) {
matObjs[i] = new Object();
partObjs[i] = new Object();
meshObjs[i] = new Object();
}
long ops = 0;
// Each node-part references materials/parts in round-robin (causes dedup checks)
for (int p = 0; p < nodePartCount; p++) {
Object mat = matObjs[p % materialCount];
Object part = partObjs[p % materialCount];
Object mesh = meshObjs[p % materialCount];
// Array.contains linear scan by identity O(M)
boolean hasMat = false;
for (Object m : materials) { ops++; if (m == mat) { hasMat = true; break; } }
if (!hasMat) materials.add(mat);
boolean hasPart = false;
for (Object pp : meshParts) { ops++; if (pp == part) { hasPart = true; break; } }
if (!hasPart) {
meshParts.add(part);
boolean hasMesh = false;
for (Object msh : meshes) { ops++; if (msh == mesh) { hasMesh = true; break; } }
if (!hasMesh) meshes.add(mesh);
}
}
return ops;
}
/**
* FAST: simulates fixed rebuildReferences() IdentityHashMap for O(1) dedup.
*/
static long rebuildRefsFast(int nodePartCount, int materialCount) {
IdentityHashMap<Object, Boolean> matSeen = new IdentityHashMap<>();
IdentityHashMap<Object, Boolean> partSeen = new IdentityHashMap<>();
IdentityHashMap<Object, Boolean> meshSeen = new IdentityHashMap<>();
List<Object> materials = new ArrayList<>();
List<Object> meshParts = new ArrayList<>();
List<Object> meshes = new ArrayList<>();
Object[] matObjs = new Object[materialCount];
Object[] partObjs = new Object[materialCount];
Object[] meshObjs = new Object[materialCount];
for (int i = 0; i < materialCount; i++) {
matObjs[i] = new Object();
partObjs[i] = new Object();
meshObjs[i] = new Object();
}
long ops = 0;
for (int p = 0; p < nodePartCount; p++) {
Object mat = matObjs[p % materialCount];
Object part = partObjs[p % materialCount];
Object mesh = meshObjs[p % materialCount];
ops++;
if (matSeen.put(mat, Boolean.TRUE) == null) materials.add(mat); // O(1)
ops++;
if (partSeen.put(part, Boolean.TRUE) == null) {
meshParts.add(part);
ops++;
if (meshSeen.put(mesh, Boolean.TRUE) == null) meshes.add(mesh); // O(1)
}
}
return ops;
}
// libgdx-0003: ModelInstance.invalidate Array.contains
/**
* SLOW: simulates ModelInstance.invalidate() for each node-part, call
* materials.contains(part.material, identity=true) O(T) linear scan.
*/
static long invalidateSlow(int nodePartCount, int materialCount) {
List<Object> materials = new ArrayList<>();
Object[] matObjs = new Object[materialCount];
for (int i = 0; i < materialCount; i++) matObjs[i] = new Object();
long ops = 0;
for (int p = 0; p < nodePartCount; p++) {
Object mat = matObjs[p % materialCount];
boolean found = false;
for (Object m : materials) { ops++; if (m == mat) { found = true; break; } }
if (!found) materials.add(mat);
}
return ops;
}
/**
* FAST: simulates fixed invalidate() IdentityHashMap dedup, O(1) per part.
*/
static long invalidateFast(int nodePartCount, int materialCount) {
IdentityHashMap<Object, Boolean> seen = new IdentityHashMap<>();
List<Object> materials = new ArrayList<>();
Object[] matObjs = new Object[materialCount];
for (int i = 0; i < materialCount; i++) matObjs[i] = new Object();
long ops = 0;
for (int p = 0; p < nodePartCount; p++) {
Object mat = matObjs[p % materialCount];
ops++;
if (seen.put(mat, Boolean.TRUE) == null) materials.add(mat); // O(1)
}
return ops;
}
// libgdx-0004: Kerning GPOS IntArray.contains
/**
* SLOW: simulates GPOS coverage loop for each covered glyph, scan all class
* definitions with IntArray.contains() (linear scan) to find class membership.
* O(coverageLen × class1Count × avg_glyphs_per_class)
*/
static long kerningGposSlow(int coverageLen, int class1Count, int glyphsPerClass) {
// Build class arrays: class c contains glyphs [c*glyphsPerClass .. (c+1)*glyphsPerClass)
int[][] glyphsByClass = new int[class1Count][];
for (int c = 1; c < class1Count; c++) {
glyphsByClass[c] = new int[glyphsPerClass];
for (int k = 0; k < glyphsPerClass; k++)
glyphsByClass[c][k] = c * glyphsPerClass + k;
}
glyphsByClass[0] = new int[0]; // class 0 = unclassified
long ops = 0;
// Coverage glyphs: worst case, each one is at the end of the last class (or unclassified)
for (int i = 0; i < coverageLen; i++) {
int glyph = (class1Count - 1) * glyphsPerClass + (i % glyphsPerClass); // last class
boolean found = false;
for (int j = 1; j < class1Count && !found; j++) {
// IntArray.contains linear scan
for (int k = 0; k < glyphsByClass[j].length; k++) {
ops++;
if (glyphsByClass[j][k] == glyph) { found = true; break; }
}
}
}
return ops;
}
/**
* FAST: simulates fixed GPOS handler build IntIntMap (glyphclass) once,
* then O(1) lookup per covered glyph.
*/
static long kerningGposFast(int coverageLen, int class1Count, int glyphsPerClass) {
int[][] glyphsByClass = new int[class1Count][];
for (int c = 1; c < class1Count; c++) {
glyphsByClass[c] = new int[glyphsPerClass];
for (int k = 0; k < glyphsPerClass; k++)
glyphsByClass[c][k] = c * glyphsPerClass + k;
}
// Build reverse map once O(class1Count × glyphsPerClass)
Map<Integer, Integer> glyphToClass = new HashMap<>((class1Count * glyphsPerClass) * 2);
for (int c = 1; c < class1Count; c++)
for (int k = 0; k < glyphsByClass[c].length; k++)
glyphToClass.put(glyphsByClass[c][k], c);
long ops = 0;
for (int i = 0; i < coverageLen; i++) {
int glyph = (class1Count - 1) * glyphsPerClass + (i % glyphsPerClass);
ops++;
glyphToClass.containsKey(glyph); // O(1)
}
return ops;
}
public static void main(String[] args) {
System.out.println("=== UNIT libgdx-0001..0004: LibGDX CWE-407 ===");
// libgdx-0001: Model.loadNode
System.out.println();
System.out.println(" libgdx-0001: Model.loadNode nested string-ID scan");
final int NP1 = 500, M1 = 100, T1 = 50;
long s0 = loadNodeSlow(NP1, M1, T1);
long f0 = loadNodeFast(NP1, M1, T1);
bench(String.format("libgdx-0001 parts=%d meshes=%d mats=%d", NP1, M1, T1),
() -> loadNodeSlow(NP1, M1, T1), () -> loadNodeFast(NP1, M1, T1), s0, f0);
final int NP2 = 2000, M2 = 200, T2 = 100;
long s1 = loadNodeSlow(NP2, M2, T2);
long f1 = loadNodeFast(NP2, M2, T2);
bench(String.format("libgdx-0001 parts=%d meshes=%d mats=%d", NP2, M2, T2),
() -> loadNodeSlow(NP2, M2, T2), () -> loadNodeFast(NP2, M2, T2), s1, f1);
// libgdx-0002: ModelBuilder.rebuildReferences
System.out.println();
System.out.println(" libgdx-0002: ModelBuilder.rebuildReferences Array.contains");
final int RNP = 1000, RM = 50;
long s2 = rebuildRefsSlow(RNP, RM);
long f2 = rebuildRefsFast(RNP, RM);
bench(String.format("libgdx-0002 parts=%d materials=%d", RNP, RM),
() -> rebuildRefsSlow(RNP, RM), () -> rebuildRefsFast(RNP, RM), s2, f2);
// libgdx-0003: ModelInstance.invalidate
System.out.println();
System.out.println(" libgdx-0003: ModelInstance.invalidate Array.contains");
final int INP = 1000, IM = 50;
long s3 = invalidateSlow(INP, IM);
long f3 = invalidateFast(INP, IM);
bench(String.format("libgdx-0003 parts=%d materials=%d", INP, IM),
() -> invalidateSlow(INP, IM), () -> invalidateFast(INP, IM), s3, f3);
// libgdx-0004: Kerning GPOS
System.out.println();
System.out.println(" libgdx-0004: Kerning GPOS IntArray.contains");
final int KC = 1000, KN = 100, KG = 20;
long s4 = kerningGposSlow(KC, KN, KG);
long f4 = kerningGposFast(KC, KN, KG);
bench(String.format("libgdx-0004 coverage=%d classes=%d glyphs/class=%d", KC, KN, KG),
() -> kerningGposSlow(KC, KN, KG), () -> kerningGposFast(KC, KN, KG), s4, f4);
System.out.println();
int pass = 0;
assert s0 > f0 * 5 : "libgdx-0001 (small) expected >5x speedup"; pass++;
assert s1 > f1 * 5 : "libgdx-0001 (large) expected >5x speedup"; pass++;
assert s2 > f2 * 5 : "libgdx-0002 expected >5x speedup"; pass++;
assert s3 > f3 * 5 : "libgdx-0003 expected >5x speedup"; pass++;
assert s4 > f4 * 5 : "libgdx-0004 expected >5x speedup"; pass++;
System.out.printf("%d/5 PASS%n", pass);
}
}