java-topology/defects/gimp/unit/GimpTest.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

209 lines
7.6 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 GIMP defects.
*
* gimp-0001: layer stack dedup — O(S² × L²) via nested g_list_find
* gimp-0002: remove_from_layer_stack — O(C × S × L) nested g_list_remove
*/
public class GimpTest {
// ---------------------------------------------------------------
// gimp-0001: layer stack dedup O(S² × L²)
// ---------------------------------------------------------------
/** Simulate GList-based layer stack comparison (defective) */
static long layerStackCmpDefective(List<Object> layers1, List<Object> layers2) {
long ops = 0;
// g_list_length — O(L) each
ops += layers1.size(); // simulated traversal
ops += layers2.size();
if (layers1.size() != layers2.size())
return ops;
// g_list_find in loop — O(L²)
for (Object item : layers1) {
for (Object item2 : layers2) {
ops++;
if (item == item2) break;
}
}
return ops;
}
/** Simulate GHashTable-based comparison (fixed) */
static long layerStackCmpFixed(List<Object> layers1, List<Object> layers2) {
long ops = 0;
ops += layers1.size();
ops += layers2.size();
if (layers1.size() != layers2.size())
return ops;
// Build hash set from layers2: O(L)
Set<Object> set = new HashSet<>(layers2);
ops += layers2.size();
// Membership test: O(L)
for (Object item : layers1) {
ops++;
if (!set.contains(item)) break;
}
return ops;
}
/** Simulate rec_remove_layer_stack_dups — defective O(S² × cmp) */
static long recRemoveDupsDefective(List<List<Object>> stack) {
long ops = 0;
for (int i = 0; i < stack.size(); i++) {
// g_slist_find_custom scans remaining entries
for (int j = i + 1; j < stack.size(); j++) {
ops += layerStackCmpDefective(stack.get(i), stack.get(j));
}
}
return ops;
}
/** Simulate fixed dedup — O(S × L) using hash */
static long recRemoveDupsFixed(List<List<Object>> stack) {
long ops = 0;
Set<Set<Object>> seen = new HashSet<>();
for (List<Object> entry : stack) {
Set<Object> entrySet = new HashSet<>(entry);
ops += entry.size(); // building set
ops++; // hash lookup
seen.add(entrySet);
}
return ops;
}
static void testLayerStackDedup() {
int S = 50; // stack entries (undo states)
int L = 10; // layers per selection
// Build stack with unique selections (worst case)
List<List<Object>> stack = new ArrayList<>();
for (int i = 0; i < S; i++) {
List<Object> selection = new ArrayList<>();
for (int j = 0; j < L; j++) {
selection.add(new Object()); // unique objects
}
stack.add(selection);
}
long defectOps = recRemoveDupsDefective(stack);
long fixedOps = recRemoveDupsFixed(stack);
double ratio = (double) defectOps / fixedOps;
System.out.printf("gimp-0001 layer stack dedup:%n");
System.out.printf(" S=%d L=%d defect_ops=%d fixed_ops=%d ratio=%.1fx%n",
S, L, defectOps, fixedOps, ratio);
assert ratio > 20.0 : "Expected significant overhead, got " + ratio;
System.out.println(" PASS");
}
// ---------------------------------------------------------------
// gimp-0002: remove_from_layer_stack O(C × S × L)
// ---------------------------------------------------------------
/** Simulate defective removal: g_list_remove per child per stack entry */
static long removeFromStackDefective(int children, int stackSize, int layersPerEntry) {
long ops = 0;
// For each child (plus the layer itself = children+1)
for (int c = 0; c < children + 1; c++) {
// For each stack entry
for (int s = 0; s < stackSize; s++) {
// g_list_remove scans the list: O(L)
for (int l = 0; l < layersPerEntry; l++) {
ops++;
}
}
}
return ops;
}
/** Simulate fixed removal: hash set + single pass per stack entry */
static long removeFromStackFixed(int children, int stackSize, int layersPerEntry) {
long ops = 0;
// Build hash set of all layers to remove: O(C)
ops += children + 1;
// Single pass over each stack entry: O(S × L)
for (int s = 0; s < stackSize; s++) {
for (int l = 0; l < layersPerEntry; l++) {
ops++; // hash lookup per layer
}
}
return ops;
}
static void testRemoveFromLayerStack() {
int C = 20; // children in group layer
int S = 50; // undo stack entries
int L = 20; // layers per stack entry
long defectOps = removeFromStackDefective(C, S, L);
long fixedOps = removeFromStackFixed(C, S, L);
double ratio = (double) defectOps / fixedOps;
System.out.printf("gimp-0002 remove_from_layer_stack:%n");
System.out.printf(" C=%d S=%d L=%d defect_ops=%d fixed_ops=%d ratio=%.1fx%n",
C, S, L, defectOps, fixedOps, ratio);
assert ratio > 10.0 : "Expected significant overhead, got " + ratio;
System.out.println(" PASS");
}
// ---------------------------------------------------------------
// gimp-0003: xcf_save_layer_props layer_sets O(L × S × I)
// ---------------------------------------------------------------
/** Defective: for each layer, for each set, build item list + g_list_find O(I) */
static long xcfSaveLayerSetsDefective(int numLayers, int numSets, int itemsPerSet) {
long ops = 0;
for (int l = 0; l < numLayers; l++) {
for (int s = 0; s < numSets; s++) {
// gimp_item_list_get_items copies: O(I)
ops += itemsPerSet;
// g_list_find: O(I) scan
ops += itemsPerSet;
}
}
return ops;
}
/** Fixed: pre-build hash sets once per set, then O(1) per layer per set */
static long xcfSaveLayerSetsFixed(int numLayers, int numSets, int itemsPerSet) {
long ops = 0;
// Pre-build hash sets: O(S * I) once
for (int s = 0; s < numSets; s++) {
ops += itemsPerSet; // build hash set
}
// Per layer: O(S) hash lookups
for (int l = 0; l < numLayers; l++) {
ops += numSets; // O(1) per set
}
return ops;
}
static void testXcfSaveLayerSets() {
int L = 500; // layers
int S = 10; // named layer sets
int I = 100; // items per set
long defectOps = xcfSaveLayerSetsDefective(L, S, I);
long fixedOps = xcfSaveLayerSetsFixed(L, S, I);
double ratio = (double) defectOps / fixedOps;
System.out.printf("gimp-0003 xcf_save_layer_props layer_sets:%n");
System.out.printf(" L=%d S=%d I=%d defect_ops=%d fixed_ops=%d ratio=%.1fx%n",
L, S, I, defectOps, fixedOps, ratio);
assert ratio > 50.0 : "Expected significant overhead, got " + ratio;
System.out.println(" PASS");
}
// ---------------------------------------------------------------
// Main
// ---------------------------------------------------------------
public static void main(String[] args) {
testLayerStackDedup();
testRemoveFromLayerStack();
testXcfSaveLayerSets();
System.out.println("\nAll GIMP CWE-407 tests PASS");
}
}