java-topology/defects/blender/unit/BlenderTest.java
russell@unturf.com bdfda13c7e inkscape/blender: CWE-407 scan — 6 defects across 2 creative tool targets
Inkscape (3 defects):
- inkscape-0001: SPObject::getLinkedRecursive vector dedup O(N^2) HIGH
- inkscape-0002: ObjectSet::raise()/lower() vector membership O(S*N) MEDIUM
- inkscape-0003: get_all_items_recursive exclude scan O(C*E) MEDIUM

Blender (3 defects):
- blender-0001: node_runtime socket chain cycle detection Vector.contains O(D^2) HIGH
- blender-0002: USD skel import used_indices dedup std::find O(J^2) MEDIUM
- blender-0003: shader_tool visited_files std::find O(D*V) MEDIUM

6/6 unit tests PASS.
2026-03-30 11:13:03 -04:00

216 lines
7.7 KiB
Java

import java.util.*;
/**
* CWE-407 simulation tests for Blender defects.
*
* blender-0001: node_runtime.cc socket chain cycle detection Vector.contains() O(D^2)
* blender-0002: usd_skel_convert.cc used_indices dedup std::find O(J^2)
* blender-0003: shader_tool.cc visited_files std::find O(D*V)
*/
public class BlenderTest {
// ========== blender-0001: socket chain cycle detection ==========
/** Simulates find_logical_origins_for_socket_recursive with Vector.contains cycle check */
static int traceSocketChainDefective(Map<Integer, List<Integer>> links, int start) {
List<Integer> chain = new ArrayList<>();
return traceRecursiveDefective(links, start, chain);
}
static int traceRecursiveDefective(Map<Integer, List<Integer>> links, int socket, List<Integer> chain) {
// Defect: linear scan for cycle detection
if (chain.contains(socket)) {
return 0; // cycle detected
}
chain.add(socket);
int count = 1;
for (int linked : links.getOrDefault(socket, Collections.emptyList())) {
count += traceRecursiveDefective(links, linked, chain);
}
chain.remove(chain.size() - 1);
return count;
}
/** Fixed: HashSet for O(1) cycle detection */
static int traceSocketChainFixed(Map<Integer, List<Integer>> links, int start) {
List<Integer> chain = new ArrayList<>();
Set<Integer> chainSet = new HashSet<>();
return traceRecursiveFixed(links, start, chain, chainSet);
}
static int traceRecursiveFixed(Map<Integer, List<Integer>> links, int socket,
List<Integer> chain, Set<Integer> chainSet) {
if (chainSet.contains(socket)) {
return 0;
}
chain.add(socket);
chainSet.add(socket);
int count = 1;
for (int linked : links.getOrDefault(socket, Collections.emptyList())) {
count += traceRecursiveFixed(links, linked, chain, chainSet);
}
chain.remove(chain.size() - 1);
chainSet.remove(socket);
return count;
}
static boolean testSocketChainCycleDetection() {
// Build a long reroute chain: 0 -> 1 -> 2 -> ... -> D-1
int D = 2000;
Map<Integer, List<Integer>> links = new HashMap<>();
for (int i = 0; i < D - 1; i++) {
links.put(i, List.of(i + 1));
}
links.put(D - 1, Collections.emptyList());
// Warmup
for (int i = 0; i < 3; i++) {
traceSocketChainDefective(links, 0);
traceSocketChainFixed(links, 0);
}
long t0 = System.nanoTime();
for (int i = 0; i < 5; i++) traceSocketChainDefective(links, 0);
long defective = System.nanoTime() - t0;
t0 = System.nanoTime();
for (int i = 0; i < 5; i++) traceSocketChainFixed(links, 0);
long fixed = System.nanoTime() - t0;
double ratio = (double) defective / fixed;
System.out.printf(" blender-0001 socket chain cycle: defective=%dms fixed=%dms ratio=%.1fx%n",
defective / 1_000_000, fixed / 1_000_000, ratio);
return ratio > 2.0;
}
// ========== blender-0002: USD skel used_indices dedup ==========
/** Defective: std::find on vector for dedup */
static List<Integer> collectUsedIndicesDefective(int[] jointIndices) {
List<Integer> usedIndices = new ArrayList<>();
for (int index : jointIndices) {
if (!usedIndices.contains(index)) {
usedIndices.add(index);
}
}
return usedIndices;
}
/** Fixed: Set for O(1) dedup */
static List<Integer> collectUsedIndicesFixed(int[] jointIndices) {
Set<Integer> seen = new HashSet<>();
List<Integer> usedIndices = new ArrayList<>();
for (int index : jointIndices) {
if (seen.add(index)) {
usedIndices.add(index);
}
}
return usedIndices;
}
static boolean testUsedIndicesDedup() {
// Simulate a high-poly mesh with many joint weight entries
int J = 10000;
int numJoints = 200;
Random rng = new Random(42);
int[] jointIndices = new int[J];
for (int i = 0; i < J; i++) {
jointIndices[i] = rng.nextInt(numJoints);
}
// Warmup
for (int i = 0; i < 3; i++) {
collectUsedIndicesDefective(jointIndices);
collectUsedIndicesFixed(jointIndices);
}
long t0 = System.nanoTime();
for (int i = 0; i < 100; i++) collectUsedIndicesDefective(jointIndices);
long defective = System.nanoTime() - t0;
t0 = System.nanoTime();
for (int i = 0; i < 100; i++) collectUsedIndicesFixed(jointIndices);
long fixed = System.nanoTime() - t0;
double ratio = (double) defective / fixed;
System.out.printf(" blender-0002 USD skel dedup: defective=%dms fixed=%dms ratio=%.1fx%n",
defective / 1_000_000, fixed / 1_000_000, ratio);
return ratio > 2.0;
}
// ========== blender-0003: shader_tool visited_files ==========
/** Defective: std::find on visited vector — isolate visited membership */
static int processShaderDepsDefective(List<String> resolvedFiles) {
List<String> visited = new ArrayList<>();
int processed = 0;
for (String file : resolvedFiles) {
// Defect: linear scan of visited list
if (!visited.contains(file)) {
visited.add(file);
processed++;
}
}
return processed;
}
/** Fixed: HashSet for visited check */
static int processShaderDepsFixed(List<String> resolvedFiles) {
Set<String> visitedSet = new HashSet<>();
int processed = 0;
for (String file : resolvedFiles) {
if (visitedSet.add(file)) {
processed++;
}
}
return processed;
}
static boolean testShaderToolVisited() {
int D = 5000; // dependencies (many unique files to grow visited list)
List<String> resolvedFiles = new ArrayList<>();
Random rng = new Random(42);
// Many unique files so visited list grows large
for (int i = 0; i < D; i++) resolvedFiles.add("shader_" + rng.nextInt(D) + ".glsl");
// Warmup
for (int i = 0; i < 3; i++) {
processShaderDepsDefective(resolvedFiles);
processShaderDepsFixed(resolvedFiles);
}
long t0 = System.nanoTime();
for (int i = 0; i < 20; i++) processShaderDepsDefective(resolvedFiles);
long defective = System.nanoTime() - t0;
t0 = System.nanoTime();
for (int i = 0; i < 20; i++) processShaderDepsFixed(resolvedFiles);
long fixed = System.nanoTime() - t0;
double ratio = (double) defective / fixed;
System.out.printf(" blender-0003 shader visited: defective=%dms fixed=%dms ratio=%.1fx%n",
defective / 1_000_000, fixed / 1_000_000, ratio);
return ratio > 2.0;
}
// ========== Main ==========
public static void main(String[] args) {
System.out.println("Blender CWE-407 unit tests");
System.out.println("=========================");
boolean p1 = testSocketChainCycleDetection();
boolean p2 = testUsedIndicesDedup();
boolean p3 = testShaderToolVisited();
System.out.println();
System.out.printf("blender-0001 socket chain cycle: %s%n", p1 ? "PASS" : "FAIL");
System.out.printf("blender-0002 USD skel dedup: %s%n", p2 ? "PASS" : "FAIL");
System.out.printf("blender-0003 shader visited: %s%n", p3 ? "PASS" : "FAIL");
if (!p1 || !p2 || !p3) {
System.exit(1);
}
System.out.println("\nAll 3 tests PASS");
}
}