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; } // ========== inkscape-0004: LayerManager::_rebuild() std::find O(L² × D) ========== /** Defective: std::find(layers, curr) per ancestor per layer — O(L² × D) */ static long layerManagerRebuildDefective(int numLayers, int depth) { long ops = 0; for (int l = 0; l < numLayers; l++) { for (int d = 0; d < depth; d++) { // std::find on vector: O(L) per ancestor check for (int k = 0; k < numLayers; k++) { ops++; } } } return ops; } /** Fixed: unordered_set built once; O(1) per ancestor check — O(L × D) */ static long layerManagerRebuildFixed(int numLayers, int depth) { long ops = 0; // Build hash set: O(L) ops += numLayers; // Per layer, per ancestor: O(1) lookup for (int l = 0; l < numLayers; l++) { ops += depth; } return ops; } static boolean testLayerManagerRebuild() { int L = 200; // layers int D = 5; // nesting depth long defect = layerManagerRebuildDefective(L, D); long fixed = layerManagerRebuildFixed(L, D); double ratio = (double) defect / fixed; System.out.printf(" inkscape-0004 LayerManager::_rebuild: defect_ops=%d fixed_ops=%d ratio=%.1fx%n", defect, fixed, ratio); return ratio > 20.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(); boolean p4 = testLayerManagerRebuild(); 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"); System.out.printf("inkscape-0004 LayerManager::_rebuild: %s%n", p4 ? "PASS" : "FAIL"); if (!p1 || !p2 || !p3 || !p4) { System.exit(1); } System.out.println("\nAll 4 tests PASS"); } }