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.
209 lines
7.6 KiB
Java
209 lines
7.6 KiB
Java
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");
|
||
}
|
||
}
|