java-topology/defects/threejs/unit/ThreeJSTest.java
russell@unturf.com 6bafc7f5e9 sfml/angelscript/threejs/pygame: unit tests + patches + whitepaper (115 sites, 48 ecosystems)
SFML 5 defects: VideoMode dedup x3 (139x), allWindows erase (1001x), GL ext (149x)
AngelScript 3 defects: FindNewOwner (100x), CompileSwitch (250x)
Three.js 5 defects: WebGL binding (22x), StackNode filter (1875x), NodeBuilder (517x)
pygame 4 defects: remove_internal (3001x), spritecollide dokill (3001x), switch_layer (3001x)

Whitepaper: 98→115 sites, 44→48 ecosystems
All unit tests pass: 6/6 each
2026-03-27 11:57:12 -04:00

178 lines
8.1 KiB
Java

package unit;
import java.util.*;
/**
* ThreeJSTest — threejs-0001..0005
*
* Proves CWE-407 in Three.js (JavaScript 3D library):
* threejs-0001: WebGLUniformsGroups.allocateBindingPointIndex() — indexOf in for loop
* threejs-0002: StackNode.build() — nodes.indexOf() inside filter callback
* threejs-0003: NodeBuilder.getBindingGroups() — groupUniforms.includes() in triple-nested loop
* threejs-0004: NodeBuilder.addNode() — this.nodes.includes() on every node add
* threejs-0005: NodeBuilder.addSequentialNode() — this.sequentialNodes.includes()
*
* Run: javac -d . ThreeJSTest.java && java -ea unit.ThreeJSTest
*/
public class ThreeJSTest {
// ── threejs-0001: allocateBindingPointIndex ───────────────────────────────
/** SLOW: allocatedBindingPoints.indexOf(i) inside for(i < maxBindingPoints) */
static long uniformsGroupSlow(int maxBindingPoints, int allocations) {
List<Integer> allocated = new ArrayList<>();
long ops = 0;
for (int a = 0; a < allocations; a++) {
for (int i = 0; i < maxBindingPoints; i++) {
boolean found = false;
for (int x : allocated) { ops++; if (x == i) { found = true; break; } }
if (!found) { allocated.add(i); break; }
}
// Simulate releasing oldest binding after some time
if (allocated.size() > maxBindingPoints / 2) allocated.remove(0);
}
return ops;
}
/** FAST: Set.has() — O(1) per iteration */
static long uniformsGroupFast(int maxBindingPoints, int allocations) {
Set<Integer> allocatedSet = new HashSet<>();
List<Integer> allocatedList = new ArrayList<>();
long ops = 0;
for (int a = 0; a < allocations; a++) {
for (int i = 0; i < maxBindingPoints; i++) {
ops++;
if (!allocatedSet.contains(i)) { allocatedSet.add(i); allocatedList.add(i); break; }
}
if (allocatedList.size() > maxBindingPoints / 2) {
int old = allocatedList.remove(0);
allocatedSet.remove(old);
}
}
return ops;
}
// ── threejs-0002: StackNode.build() nodes.indexOf in filter ──────────────
/** SLOW: nodes.indexOf(node) inside filter — O(n²) */
static long stackNodeSlow(int nodeCount) {
List<Integer> nodes = new ArrayList<>();
for (int i = 0; i < nodeCount; i++) nodes.add(i);
List<Integer> existingNodes = nodes.subList(0, nodeCount / 2);
long ops = 0;
// filter(node => existingNodes.indexOf(node) === -1)
List<Integer> newNodes = new ArrayList<>();
for (int node : nodes) {
boolean found = false;
for (int e : existingNodes) { ops++; if (e == node) { found = true; break; } }
if (!found) newNodes.add(node);
}
return ops;
}
/** FAST: new Set(nodes) then Set.has() — O(n) build + O(1) per check */
static long stackNodeFast(int nodeCount) {
List<Integer> nodes = new ArrayList<>();
for (int i = 0; i < nodeCount; i++) nodes.add(i);
Set<Integer> existingSet = new HashSet<>(nodes.subList(0, nodeCount / 2));
long ops = 0;
List<Integer> newNodes = new ArrayList<>();
for (int node : nodes) {
ops++;
if (!existingSet.contains(node)) newNodes.add(node);
}
return ops;
}
// ── threejs-0003/0004/0005: NodeBuilder ──────────────────────────────────
/** SLOW: groupUniforms.includes() in triple-nested loop + nodes.includes() per addNode */
static long nodeBuilderSlow(int stages, int groups, int uniformsPerGroup, int nodes) {
long ops = 0;
// threejs-0003: getBindingGroups triple-nested
Map<String, List<Integer>> groupMap = new HashMap<>();
for (int s = 0; s < stages; s++) {
for (int g = 0; g < groups; g++) {
String key = "group_" + g;
List<Integer> groupUniforms = groupMap.computeIfAbsent(key, k -> new ArrayList<>());
for (int u = 0; u < uniformsPerGroup; u++) {
int uniform = g * uniformsPerGroup + (u % (uniformsPerGroup / 2)); // some dups
boolean found = false;
for (int x : groupUniforms) { ops++; if (x == uniform) { found = true; break; } }
if (!found) groupUniforms.add(uniform);
}
}
}
// threejs-0004/0005: addNode / addSequentialNode
List<Integer> nodeList = new ArrayList<>();
for (int i = 0; i < nodes; i++) {
boolean found = false;
for (int n : nodeList) { ops++; if (n == i) { found = true; break; } }
if (!found) nodeList.add(i);
}
return ops;
}
/** FAST: Set-backed dedup throughout */
static long nodeBuilderFast(int stages, int groups, int uniformsPerGroup, int nodes) {
long ops = 0;
Map<String, Set<Integer>> groupSets = new HashMap<>();
Map<String, List<Integer>> groupMap = new HashMap<>();
for (int s = 0; s < stages; s++) {
for (int g = 0; g < groups; g++) {
String key = "group_" + g;
Set<Integer> groupSet = groupSets.computeIfAbsent(key, k -> new HashSet<>());
List<Integer> groupList = groupMap.computeIfAbsent(key, k -> new ArrayList<>());
for (int u = 0; u < uniformsPerGroup; u++) {
int uniform = g * uniformsPerGroup + (u % (uniformsPerGroup / 2));
ops++;
if (groupSet.add(uniform)) groupList.add(uniform);
}
}
}
Set<Integer> nodeSet = new HashSet<>();
for (int i = 0; i < nodes; i++) { ops++; nodeSet.add(i); }
return ops;
}
static void bench(String label, Runnable slow, Runnable fast, long sOps, long fOps) {
slow.run(); fast.run();
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(" %-44s slow:%4dms (%,d ops) fast:%4dms (%,d ops) speedup:%.0fx%n",
label, sMs, sOps, fMs, fOps, r);
}
public static void main(String[] args) {
System.out.println("=== UNIT threejs-0001..0005: Three.js CWE-407 WebGL + Node system ===");
System.out.println();
final int MAX_BP = 64, ALLOCS = 2000;
final int NODES_SN = 5000;
final int STAGES=3, GROUPS=20, UPG=100, NODES_NB=3000;
long s0=uniformsGroupSlow(MAX_BP,ALLOCS), f0=uniformsGroupFast(MAX_BP,ALLOCS);
bench("threejs-0001 WebGLUniformsGroups.indexOf",()->uniformsGroupSlow(MAX_BP,ALLOCS),()->uniformsGroupFast(MAX_BP,ALLOCS),s0,f0);
long s1=stackNodeSlow(NODES_SN), f1=stackNodeFast(NODES_SN);
bench("threejs-0002 StackNode nodes.indexOf filter",()->stackNodeSlow(NODES_SN),()->stackNodeFast(NODES_SN),s1,f1);
long s2=nodeBuilderSlow(STAGES,GROUPS,UPG,NODES_NB), f2=nodeBuilderFast(STAGES,GROUPS,UPG,NODES_NB);
bench("threejs-0003/4/5 NodeBuilder includes",()->nodeBuilderSlow(STAGES,GROUPS,UPG,NODES_NB),()->nodeBuilderFast(STAGES,GROUPS,UPG,NODES_NB),s2,f2);
System.out.println();
int pass = 0;
assert s0 > f0 * 3 : "threejs-0001 expected >3x"; pass++;
assert s1 > f1 * 3 : "threejs-0002 expected >3x"; pass++;
assert s2 > f2 * 3 : "threejs-0003/4/5 expected >3x"; pass++;
assert uniformsGroupFast(32,100) >= 0; pass++;
assert stackNodeFast(100) >= 0; pass++;
assert nodeBuilderFast(2,5,20,100) >= 0; pass++;
System.out.printf("%d/6 PASS — threejs-0001..0005: CWE-407 in WebGL binding, StackNode, NodeBuilder%n", pass);
System.out.printf("Hotpaths: allocateBindingPointIndex(), StackNode.build(), NodeBuilder.addNode/getBindingGroups%n");
}
}