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
178 lines
8.1 KiB
Java
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");
|
|
}
|
|
}
|