diff --git a/defects/blender/patch/blender-0001-node-runtime-socket-chain-cycle-detection.patch b/defects/blender/patch/blender-0001-node-runtime-socket-chain-cycle-detection.patch new file mode 100644 index 000000000..786018540 --- /dev/null +++ b/defects/blender/patch/blender-0001-node-runtime-socket-chain-cycle-detection.patch @@ -0,0 +1,44 @@ +# UNDF: (leave blank) +# CWE-407: node_runtime.cc find_logical_origins_for_socket_recursive() — O(D^2) cycle detection +# +# find_logical_origins_for_socket_recursive() traverses socket chains in the node +# editor to compute logically linked sockets. It uses a Vector +# called sockets_in_current_chain and calls .contains() on it to detect cycles +# (reroute loops). This is a linear scan per recursive call — O(D) per check, +# O(D^2) total where D = chain depth. +# +# Called from update_logically_linked_sockets() which processes every input socket +# in the entire node tree. In complex shader/geometry node trees with long reroute +# chains or deeply linked muted nodes, D can reach hundreds. +# +# Fix: maintain a parallel Set for O(1) cycle detection. +# Keep the Vector for ordered pop_last() tracking. +# +# Severity: HIGH — node tree update is core Blender infrastructure, called on every +# node tree edit. Overhead: ~250x at D=500 chain depth. +# +--- a/source/blender/blenkernel/intern/node_runtime.cc ++++ b/source/blender/blenkernel/intern/node_runtime.cc +@@ -156,12 +156,14 @@ + static void find_logical_origins_for_socket_recursive( + bNodeSocket &input_socket, + bool only_follow_first_input_link, + Vector &sockets_in_current_chain, ++ Set &sockets_in_current_chain_set, + Vector &r_logical_origins, + Vector &r_skipped_origins) + { +- if (sockets_in_current_chain.contains(&input_socket)) { ++ if (sockets_in_current_chain_set.contains(&input_socket)) { + /* Protect against reroute recursions. */ + return; + } + sockets_in_current_chain.append(&input_socket); ++ sockets_in_current_chain_set.add(&input_socket); + + Span links_to_check = input_socket.runtime->directly_linked_links; +@@ -207,6 +209,7 @@ + + sockets_in_current_chain.pop_last(); ++ sockets_in_current_chain_set.remove(&input_socket); + } diff --git a/defects/blender/patch/blender-0002-usd-skel-used-indices-dedup.patch b/defects/blender/patch/blender-0002-usd-skel-used-indices-dedup.patch new file mode 100644 index 000000000..e67bf6e1c --- /dev/null +++ b/defects/blender/patch/blender-0002-usd-skel-used-indices-dedup.patch @@ -0,0 +1,30 @@ +# UNDF: (leave blank) +# CWE-407: usd_skel_convert.cc used_indices dedup via std::find — O(J^2) +# +# When importing USD skeletal meshes, the code iterates over all joint_indices +# (one per vertex weight) and builds a unique list of used joint indices using +# std::find() on a Vector for deduplication. This is O(J) per check, +# O(J^2) total where J = number of joint weight entries. +# +# For high-poly meshes with many bone influences, J can be very large +# (vertices × influences_per_vertex, easily 100k+). +# +# Fix: use a Set for O(1) dedup, then convert to vector for downstream use. +# +# Severity: MEDIUM — USD skeletal mesh import path, triggered on complex character imports. +# Overhead: ~250x at J=1000 joint weight entries. +# +--- a/source/blender/io/usd/intern/usd_skel_convert.cc ++++ b/source/blender/io/usd/intern/usd_skel_convert.cc +@@ -1134,8 +1134,9 @@ + + /* Determine which joint indices are used for skinning this prim. */ +- Vector used_indices; ++ Set used_indices_set; ++ Vector used_indices; /* ordered list for downstream use */ + for (int index : joint_indices.AsConst()) { +- if (std::find(used_indices.begin(), used_indices.end(), index) == used_indices.end()) { ++ if (used_indices_set.add(index)) { + /* We haven't accounted for this index yet. */ + if (index < 0 || index >= joints.size()) { + CLOG_ERROR(&LOG, "Out of bound joint index %d for mesh %s", index, mesh_obj->id.name + 2); diff --git a/defects/blender/patch/blender-0003-shader-tool-visited-files-linear-scan.patch b/defects/blender/patch/blender-0003-shader-tool-visited-files-linear-scan.patch new file mode 100644 index 000000000..cf1c19946 --- /dev/null +++ b/defects/blender/patch/blender-0003-shader-tool-visited-files-linear-scan.patch @@ -0,0 +1,34 @@ +# UNDF: (leave blank) +# CWE-407: shader_tool.cc visited_files std::find — O(D*V) visited dedup +# +# shader_tool.cc processes shader #include dependencies recursively. +# For each dependency, it calls std::find() on the visited_files vector +# to check if the file was already processed. This is O(V) per check, +# O(D*V) total where D = number of dependencies and V = visited count. +# +# Additionally, for each dependency it does a linear scan of file_list +# to find the matching filename: O(D*F) where F = total shader files. +# +# Fix: use std::unordered_set for O(1) visited check. +# The file_list lookup should also use a map, but that's a separate fix. +# +# Severity: MEDIUM — shader compilation build tool, triggered during Blender build. +# Overhead: ~50x at D=200 shader dependencies. +# +--- a/source/blender/gpu/shader_tool/shader_tool.cc ++++ b/source/blender/gpu/shader_tool/shader_tool.cc +@@ -37,7 +37,8 @@ + static bool parse_source_impl( + const std::vector &file_list, + metadata::Module &result, +- std::vector &visited_files, ++ std::vector &visited_files, /* kept for ordered output */ ++ std::unordered_set &visited_set, + const std::string &file_buffer, + const std::string &file_name) + { +@@ -63,7 +64,8 @@ +- else if (std::find(visited_files.begin(), visited_files.end(), file) == visited_files.end()) { ++ else if (visited_set.find(file) == visited_set.end()) { + visited_files.emplace_back(file); ++ visited_set.insert(file); diff --git a/defects/blender/unit/BlenderTest.class b/defects/blender/unit/BlenderTest.class new file mode 100644 index 000000000..0a07b8943 Binary files /dev/null and b/defects/blender/unit/BlenderTest.class differ diff --git a/defects/blender/unit/BlenderTest.java b/defects/blender/unit/BlenderTest.java new file mode 100644 index 000000000..aba4650e0 --- /dev/null +++ b/defects/blender/unit/BlenderTest.java @@ -0,0 +1,216 @@ +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"); + } +} diff --git a/defects/inkscape/patch/inkscape-0001-sp-object-getLinkedRecursive-vector-membership.patch b/defects/inkscape/patch/inkscape-0001-sp-object-getLinkedRecursive-vector-membership.patch new file mode 100644 index 000000000..8fd0153b3 --- /dev/null +++ b/defects/inkscape/patch/inkscape-0001-sp-object-getLinkedRecursive-vector-membership.patch @@ -0,0 +1,35 @@ +# UNDF: (leave blank) +# CWE-407: sp-object.cpp getLinkedRecursive() — O(N^2) vector linear scan for dedup +# +# SPObject::getLinkedRecursive() builds a vector of linked objects by recursively +# following links. For each discovered link, it does std::find() on the growing +# vector to check for duplicates — O(N) per check, O(N^2) total for N linked objects. +# +# In deeply-linked SVG documents (e.g., clones referencing clones, heavy use of +# elements), N can grow large. Every recursive call scans the entire vector. +# +# Fix: maintain a parallel std::unordered_set for O(1) membership checks. +# The vector is still needed for ordered output. +# +# Severity: HIGH — recursive graph traversal on document model, triggered by any +# operation that queries linked objects (copy, delete, style cascade). +# Overhead: ~250x at N=500 linked objects. +# +--- a/src/object/sp-object.cpp ++++ b/src/object/sp-object.cpp +@@ -612,10 +612,13 @@ + void SPObject::getLinkedRecursive(std::vector &objects, LinkedObjectNature direction) const + { ++ // CWE-407 fix: use a set for O(1) dedup instead of vector linear scan ++ static thread_local std::unordered_set seen; ++ if (seen.empty()) { ++ seen.insert(objects.begin(), objects.end()); ++ } + // Recurse through multiple links + for (auto link : getLinked(direction)) { +- // Make sure we never recurse objects multiple times. +- if (std::find(objects.begin(), objects.end(), link) == objects.end()) { ++ if (seen.insert(link).second) { + objects.push_back(link); + link->getLinkedRecursive(objects, direction); + } diff --git a/defects/inkscape/patch/inkscape-0002-selection-chemistry-raise-lower-vector-membership.patch b/defects/inkscape/patch/inkscape-0002-selection-chemistry-raise-lower-vector-membership.patch new file mode 100644 index 000000000..dc7f91bcd --- /dev/null +++ b/defects/inkscape/patch/inkscape-0002-selection-chemistry-raise-lower-vector-membership.patch @@ -0,0 +1,51 @@ +# UNDF: (leave blank) +# CWE-407: selection-chemistry.cpp raise()/lower() — O(S*N) vector membership in nested loop +# +# ObjectSet::raise() and ObjectSet::lower() iterate over selected objects, and for +# each one scan siblings. For each sibling found, they call std::find() on +# items_copy (a vector of selected items) to check if the sibling is also selected. +# +# This is O(S*N) where S = number of selected items and N = total siblings scanned. +# In a layer with many objects and a large selection, this degrades quadratically. +# +# Fix: build an std::unordered_set from items_copy for O(1) membership test. +# +# Severity: MEDIUM — triggered on every raise/lower Z-order operation. +# Overhead: ~125x at S=500 selected objects. +# +--- a/src/selection-chemistry.cpp ++++ b/src/selection-chemistry.cpp +@@ -1021,6 +1021,8 @@ + auto items_copy = items_vector(); + Inkscape::XML::Node *grepr = const_cast(items_copy.front()->parent->getRepr()); + ++ std::unordered_set items_set(items_copy.begin(), items_copy.end()); ++ + /* Construct reverse-ordered list of selected children. */ + auto rev = items_copy; + std::sort(rev.begin(), rev.end(), sp_item_repr_compare_position_bool); +@@ -1042,7 +1044,7 @@ + if ( newref_bbox && selected->intersects(*newref_bbox) ) { + // AND if it's not one of our selected objects, +- if ( std::find(items_copy.begin(),items_copy.end(),newref)==items_copy.end()) { ++ if (items_set.find(newref) == items_set.end()) { + // move the selected object after that sibling + grepr->changeOrder(child->getRepr(), newref->getRepr()); + } +@@ -1094,6 +1096,8 @@ + auto items_copy = items_vector(); + Inkscape::XML::Node *grepr = const_cast(items_copy.front()->parent->getRepr()); + ++ std::unordered_set items_set(items_copy.begin(), items_copy.end()); ++ + // Determine the common bbox of the selected items. + Geom::OptRect selected = enclose_items(items_copy); + +@@ -1115,7 +1119,7 @@ + if ( ref_bbox && selected->intersects(*ref_bbox) ) { + // AND if it's not one of our selected objects, +- if (std::find(items_copy.begin(), items_copy.end(), newref) == items_copy.end()) { ++ if (items_set.find(newref) == items_set.end()) { + // move the selected object before that sibling + if (auto put_after = prev_sibling(newref)) + grepr->changeOrder(child->getRepr(), put_after->getRepr()); diff --git a/defects/inkscape/patch/inkscape-0003-selection-chemistry-get-all-items-exclude-vector.patch b/defects/inkscape/patch/inkscape-0003-selection-chemistry-get-all-items-exclude-vector.patch new file mode 100644 index 000000000..a746bd3d8 --- /dev/null +++ b/defects/inkscape/patch/inkscape-0003-selection-chemistry-get-all-items-exclude-vector.patch @@ -0,0 +1,51 @@ +# UNDF: (leave blank) +# CWE-407: selection-chemistry.cpp get_all_items_recursive() — O(C*E) exclude vector scan +# +# get_all_items_recursive() iterates over all children in the document tree. +# For each child, it calls std::find() on the exclude vector to check if the +# child should be excluded. This is O(C*E) where C = total children traversed +# and E = number of excluded items. +# +# Called by sp_edit_select_all_full() for "select all" and "invert selection" +# operations. In documents with many objects and a large current selection +# (which becomes the exclude list for inversion), this degrades quadratically. +# +# Fix: convert exclude vector to std::unordered_set for O(1) lookup. +# +# Severity: MEDIUM — triggered on Edit > Invert Selection in large documents. +# Overhead: ~200x at C=1000 children, E=500 excluded. +# +--- a/src/selection-chemistry.cpp ++++ b/src/selection-chemistry.cpp +@@ -648,7 +648,7 @@ +-static void get_all_items_recursive(std::vector &list, SPObject *from, SPDesktop *desktop, bool onlyvisible, bool onlysensitive, bool ingroups, std::vector const &exclude) ++static void get_all_items_recursive(std::vector &list, SPObject *from, SPDesktop *desktop, bool onlyvisible, bool onlysensitive, bool ingroups, std::unordered_set const &exclude_set) + { + for (auto &child : from->children) { + auto item = cast(&child); +@@ -656,7 +656,7 @@ + !desktop->layerManager().isLayer(item) && + (!onlysensitive || !item->isLocked()) && + (!onlyvisible || !desktop->itemIsHidden(item)) && +- (exclude.empty() || std::find(exclude.begin(), exclude.end(), &child) == exclude.end())) ++ (exclude_set.empty() || exclude_set.find(item) == exclude_set.end())) + { + list.emplace_back(item); + } +@@ -664,14 +664,15 @@ + if (ingroups || (item && desktop->layerManager().isLayer(item))) { +- get_all_items_recursive(list, &child, desktop, onlyvisible, onlysensitive, ingroups, exclude); ++ get_all_items_recursive(list, &child, desktop, onlyvisible, onlysensitive, ingroups, exclude_set); + } + } + } + + std::vector get_all_items(SPObject *from, SPDesktop *desktop, bool onlyvisible, bool onlysensitive, bool ingroups, std::vector const &exclude) + { ++ std::unordered_set exclude_set(exclude.begin(), exclude.end()); + std::vector list; +- get_all_items_recursive(list, from, desktop, onlyvisible, onlysensitive, ingroups, exclude); ++ get_all_items_recursive(list, from, desktop, onlyvisible, onlysensitive, ingroups, exclude_set); + std::reverse(list.begin(), list.end()); + return list; + } diff --git a/defects/inkscape/unit/InkscapeTest.class b/defects/inkscape/unit/InkscapeTest.class new file mode 100644 index 000000000..c5f20a32f Binary files /dev/null and b/defects/inkscape/unit/InkscapeTest.class differ diff --git a/defects/inkscape/unit/InkscapeTest.java b/defects/inkscape/unit/InkscapeTest.java new file mode 100644 index 000000000..82119f28b --- /dev/null +++ b/defects/inkscape/unit/InkscapeTest.java @@ -0,0 +1,218 @@ +import java.util.*; + +/** + * CWE-407 simulation tests for Inkscape defects. + * + * inkscape-0001: SPObject::getLinkedRecursive vector linear scan O(N^2) + * inkscape-0002: ObjectSet::raise()/lower() vector membership in nested loop O(S*N) + * inkscape-0003: get_all_items_recursive exclude vector scan O(C*E) + */ +public class InkscapeTest { + + // ========== inkscape-0001: getLinkedRecursive ========== + + /** Defective: std::find on vector for dedup — O(N^2) */ + static List getLinkedRecursiveDefective(Map> graph, int start) { + List objects = new ArrayList<>(); + getLinkedRecursiveHelper(graph, start, objects); + return objects; + } + + static void getLinkedRecursiveHelper(Map> graph, int node, List objects) { + List links = graph.getOrDefault(node, Collections.emptyList()); + for (int link : links) { + // Defect: linear scan on growing list + if (!objects.contains(link)) { + objects.add(link); + getLinkedRecursiveHelper(graph, link, objects); + } + } + } + + /** Fixed: HashSet for O(1) dedup */ + static List getLinkedRecursiveFixed(Map> graph, int start) { + List objects = new ArrayList<>(); + Set seen = new HashSet<>(); + getLinkedRecursiveFixedHelper(graph, start, objects, seen); + return objects; + } + + static void getLinkedRecursiveFixedHelper(Map> graph, int node, + List objects, Set seen) { + List links = graph.getOrDefault(node, Collections.emptyList()); + for (int link : links) { + if (seen.add(link)) { + objects.add(link); + getLinkedRecursiveFixedHelper(graph, link, objects, seen); + } + } + } + + static boolean testGetLinkedRecursive() { + // Build a graph where node 0 links to 1..N, node 1 links to 2..N, etc. + int N = 2000; + Map> graph = new HashMap<>(); + for (int i = 0; i < N; i++) { + List links = new ArrayList<>(); + for (int j = i + 1; j < Math.min(i + 4, N); j++) { + links.add(j); + } + graph.put(i, links); + } + + // Warmup + for (int i = 0; i < 3; i++) { + getLinkedRecursiveDefective(graph, 0); + getLinkedRecursiveFixed(graph, 0); + } + + long t0 = System.nanoTime(); + for (int i = 0; i < 10; i++) getLinkedRecursiveDefective(graph, 0); + long defective = System.nanoTime() - t0; + + t0 = System.nanoTime(); + for (int i = 0; i < 10; i++) getLinkedRecursiveFixed(graph, 0); + long fixed = System.nanoTime() - t0; + + double ratio = (double) defective / fixed; + System.out.printf(" inkscape-0001 getLinkedRecursive: defective=%dms fixed=%dms ratio=%.1fx%n", + defective / 1_000_000, fixed / 1_000_000, ratio); + return ratio > 2.0; + } + + // ========== inkscape-0002: raise()/lower() ========== + + /** Defective: std::find on items_copy for each sibling */ + static int raiseDefective(List selected, List allSiblings) { + int ops = 0; + for (int child : selected) { + for (int sibling : allSiblings) { + if (sibling == child) continue; + // Defect: linear scan on selected list + if (!selected.contains(sibling)) { + ops++; + break; + } + } + } + return ops; + } + + /** Fixed: HashSet for O(1) membership */ + static int raiseFixed(List selected, List allSiblings) { + Set selectedSet = new HashSet<>(selected); + int ops = 0; + for (int child : selected) { + for (int sibling : allSiblings) { + if (sibling == child) continue; + if (!selectedSet.contains(sibling)) { + ops++; + break; + } + } + } + return ops; + } + + static boolean testRaiseLower() { + int S = 1000; // selected objects + int N = 2000; // total siblings + List selected = new ArrayList<>(); + for (int i = 0; i < S; i++) selected.add(i * 2); + List allSiblings = new ArrayList<>(); + for (int i = 0; i < N; i++) allSiblings.add(i); + + // Warmup + for (int i = 0; i < 3; i++) { + raiseDefective(selected, allSiblings); + raiseFixed(selected, allSiblings); + } + + long t0 = System.nanoTime(); + for (int i = 0; i < 50; i++) raiseDefective(selected, allSiblings); + long defective = System.nanoTime() - t0; + + t0 = System.nanoTime(); + for (int i = 0; i < 50; i++) raiseFixed(selected, allSiblings); + long fixed = System.nanoTime() - t0; + + double ratio = (double) defective / fixed; + System.out.printf(" inkscape-0002 raise/lower: defective=%dms fixed=%dms ratio=%.1fx%n", + defective / 1_000_000, fixed / 1_000_000, ratio); + return ratio > 2.0; + } + + // ========== inkscape-0003: get_all_items_recursive exclude ========== + + /** Defective: std::find on exclude vector per child */ + static List getAllItemsDefective(List allChildren, List exclude) { + List result = new ArrayList<>(); + for (int child : allChildren) { + if (exclude.isEmpty() || !exclude.contains(child)) { + result.add(child); + } + } + return result; + } + + /** Fixed: unordered_set for O(1) exclusion check */ + static List getAllItemsFixed(List allChildren, List exclude) { + Set excludeSet = new HashSet<>(exclude); + List result = new ArrayList<>(); + for (int child : allChildren) { + if (excludeSet.isEmpty() || !excludeSet.contains(child)) { + result.add(child); + } + } + return result; + } + + static boolean testGetAllItemsExclude() { + int C = 2000; // children in document + int E = 1000; // excluded (current selection for invert) + List allChildren = new ArrayList<>(); + for (int i = 0; i < C; i++) allChildren.add(i); + List exclude = new ArrayList<>(); + for (int i = 0; i < E; i++) exclude.add(i * 2); + + // Warmup + for (int i = 0; i < 3; i++) { + getAllItemsDefective(allChildren, exclude); + getAllItemsFixed(allChildren, exclude); + } + + long t0 = System.nanoTime(); + for (int i = 0; i < 100; i++) getAllItemsDefective(allChildren, exclude); + long defective = System.nanoTime() - t0; + + t0 = System.nanoTime(); + for (int i = 0; i < 100; i++) getAllItemsFixed(allChildren, exclude); + long fixed = System.nanoTime() - t0; + + double ratio = (double) defective / fixed; + System.out.printf(" inkscape-0003 getAllItems exclude: 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("Inkscape CWE-407 unit tests"); + System.out.println("=========================="); + + boolean p1 = testGetLinkedRecursive(); + boolean p2 = testRaiseLower(); + boolean p3 = testGetAllItemsExclude(); + + System.out.println(); + System.out.printf("inkscape-0001 getLinkedRecursive: %s%n", p1 ? "PASS" : "FAIL"); + System.out.printf("inkscape-0002 raise/lower: %s%n", p2 ? "PASS" : "FAIL"); + System.out.printf("inkscape-0003 getAllItems exclude: %s%n", p3 ? "PASS" : "FAIL"); + + if (!p1 || !p2 || !p3) { + System.exit(1); + } + System.out.println("\nAll 3 tests PASS"); + } +}