java-topology/defects/gimp/unit/GimpTest.java
russell@unturf.com 9b60b61e9c imagemagick/gimp: CWE-407 findings
imagemagick-0001: UHDR coder GetImageListLength() O(N) in for-loop = O(N²) MEDIUM
imagemagick-0002: SyncImageList nested scene-dedup O(N²) MEDIUM
gimp-0001: layer_stack_cmp g_list_find in loop O(S²×L²) MEDIUM
gimp-0002: remove_from_layer_stack nested g_list_remove O(C×S×L) LOW-MEDIUM

4 defects total, 4/4 unit tests PASS
2026-03-30 11:21:24 -04:00

160 lines
5.7 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");
}
// ---------------------------------------------------------------
// Main
// ---------------------------------------------------------------
public static void main(String[] args) {
testLayerStackDedup();
testRemoveFromLayerStack();
System.out.println("\nAll GIMP CWE-407 tests PASS");
}
}