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> links, int start) { List chain = new ArrayList<>(); return traceRecursiveDefective(links, start, chain); } static int traceRecursiveDefective(Map> links, int socket, List 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> links, int start) { List chain = new ArrayList<>(); Set chainSet = new HashSet<>(); return traceRecursiveFixed(links, start, chain, chainSet); } static int traceRecursiveFixed(Map> links, int socket, List chain, Set 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> 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 collectUsedIndicesDefective(int[] jointIndices) { List usedIndices = new ArrayList<>(); for (int index : jointIndices) { if (!usedIndices.contains(index)) { usedIndices.add(index); } } return usedIndices; } /** Fixed: Set for O(1) dedup */ static List collectUsedIndicesFixed(int[] jointIndices) { Set seen = new HashSet<>(); List 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 resolvedFiles) { List 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 resolvedFiles) { Set 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 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"); } }