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) * blender-0004: anim_channels_edit.cc rearrange_animchannel_islands BLI_findptr O(C*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; } // ========== blender-0004: anim channel rearrange island BLI_findptr ========== /** * Simulates rearrange_animchannel_islands: for each channel in list, * check membership in anim_data_visible. * Defective: linear scan (BLI_findptr = List.contains). */ static int rearrangeIslandsDefective(List channelList, List visibleChannels) { int ops = 0; for (int channel : channelList) { // BLI_findptr: O(V) linear scan for (int vis : visibleChannels) { ops++; if (vis == channel) break; } } return ops; } /** Fixed: Set<> from anim_data_visible for O(1) lookup per channel. */ static int rearrangeIslandsFixed(List channelList, List visibleChannels) { int ops = 0; Set visibleSet = new HashSet<>(visibleChannels); ops += visibleChannels.size(); // one-time build for (int channel : channelList) { visibleSet.contains(channel); // O(1) ops++; } return ops; } static boolean testAnimChannelRearrangeIslands() { // 100 objects * 10 NLA tracks = 1000 channels; all visible int C = 1000; int V = 1000; List channelList = new ArrayList<>(); List visibleChannels = new ArrayList<>(); for (int i = 0; i < C; i++) channelList.add(i); for (int i = 0; i < V; i++) visibleChannels.add(i); // all visible, worst case int defectOps = rearrangeIslandsDefective(channelList, visibleChannels); int fixedOps = rearrangeIslandsFixed(channelList, visibleChannels); double ratio = (double) defectOps / fixedOps; System.out.printf(" blender-0004 anim island rearrange: defect=%d fixed=%d ratio=%.1fx%n", defectOps, fixedOps, ratio); return ratio > 10.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(); boolean p4 = testAnimChannelRearrangeIslands(); 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"); System.out.printf("blender-0004 anim island rearrange: %s%n", p4 ? "PASS" : "FAIL"); if (!p1 || !p2 || !p3 || !p4) { System.exit(1); } System.out.println("\nAll 4 tests PASS"); } }