java-topology/defects/inkscape/unit/InkscapeTest.java
russell@unturf.com 89de6df1d4 gimp/inkscape: 2 new CWE-407 defects + all 5 MOADs scanned
gimp-0003: xcf_save_layer_props layer_sets O(L×S×I) MEDIUM 166.7x
  - xcf_save_layer_props() called per layer rebuilds+scans each named
    layer set item list on every XCF save
  - Fix: pre-build GHashTable per set before layer loop

inkscape-0004: LayerManager::_rebuild() std::find O(L²×D) HIGH 166.7x
  - Per layer, per ancestor: std::find on full layers vector
  - Runs on every document load, every layer add/remove, every undo/redo
  - Fix: unordered_set built once at start of _rebuild()

MOADs 0002/0003/0004/0005: CLEAN for both GIMP and Inkscape.
All 3/4 unit tests PASS respectively.
2026-03-31 20:55:50 -04:00

261 lines
9.5 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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<Integer> getLinkedRecursiveDefective(Map<Integer, List<Integer>> graph, int start) {
List<Integer> objects = new ArrayList<>();
getLinkedRecursiveHelper(graph, start, objects);
return objects;
}
static void getLinkedRecursiveHelper(Map<Integer, List<Integer>> graph, int node, List<Integer> objects) {
List<Integer> 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<Integer> getLinkedRecursiveFixed(Map<Integer, List<Integer>> graph, int start) {
List<Integer> objects = new ArrayList<>();
Set<Integer> seen = new HashSet<>();
getLinkedRecursiveFixedHelper(graph, start, objects, seen);
return objects;
}
static void getLinkedRecursiveFixedHelper(Map<Integer, List<Integer>> graph, int node,
List<Integer> objects, Set<Integer> seen) {
List<Integer> 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<Integer, List<Integer>> graph = new HashMap<>();
for (int i = 0; i < N; i++) {
List<Integer> 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<Integer> selected, List<Integer> 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<Integer> selected, List<Integer> allSiblings) {
Set<Integer> 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<Integer> selected = new ArrayList<>();
for (int i = 0; i < S; i++) selected.add(i * 2);
List<Integer> 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<Integer> getAllItemsDefective(List<Integer> allChildren, List<Integer> exclude) {
List<Integer> 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<Integer> getAllItemsFixed(List<Integer> allChildren, List<Integer> exclude) {
Set<Integer> excludeSet = new HashSet<>(exclude);
List<Integer> 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<Integer> allChildren = new ArrayList<>();
for (int i = 0; i < C; i++) allChildren.add(i);
List<Integer> 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");
}
}