java-topology/defects/darktable/unit/DarktableTest.java
russell@unturf.com 7d135aa6a1 blender+darktable: 3 new defects, MOADs 0002-0005 scan complete
blender-0004: MOAD-0001 CWE-407 anim_channels_edit.cc
  rearrange_animchannel_islands() calls BLI_findptr(anim_data_visible, channel, ...)
  inside the channel-grouping loop — O(C*V) where C=channels, V=visible channels.
  In a complex rig: C=V=1000, 1,000,000 pointer comparisons per reorder.
  Fix: build blender::Set<void*> from anim_data_visible before loop, O(1) lookup.
  Measured: 250x op-count ratio at C=V=1000.

darktable-0004: MOAD-0004 CWE-312 pwstorage backends
  backend_kwallet.c and backend_apple_keychain.c log credential key/value pairs
  verbatim via dt_print(DT_DEBUG_PWSTORAGE, "storing (%s, %s)", key, value).
  `value` for Piwigo export is JSON including plaintext password.
  Triggered by `darktable -d pwstorage` or `-d all` (common debugging mode).
  Fix: replace value argument with "[REDACTED]" in all four dt_print calls.

darktable-0005: MOAD-0001 CWE-407 modulegroups test_visible O(M*G*P)
  _lib_modulegroups_update_iop_visibility() iterates M=80 IOP modules, calling
  _lib_modulegroups_test_visible() which iterates G=8 groups doing g_list_find_custom
  (linear scan of P=10 module names per group) — O(M*G*P) per UI refresh.
  Called on every search keystroke, module toggle, and group switch.
  Fix: precompute GHashTable of all visible module names; test_visible = O(1).
  Measured: 37.8x op-count ratio at M=80, G=8, P=10.

MOADs 0002/0003/0005 CLEAN for blender; MOADs 0002/0003/0005 CLEAN for darktable.
All 4 blender + 5 darktable unit tests PASS.
2026-03-31 20:49:15 -04:00

281 lines
11 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 / CWE-312 simulation tests for darktable defects.
*
* darktable-0001: map_locations image diff via g_list_find O(N*M)
* darktable-0002: _tag_add_tags_to_list dedup via g_list_find O(T*L)
* darktable-0003: map view clustering sel_imgs g_list_find inside O(I*J) loop
* darktable-0004: pwstorage backends log credentials verbatim (CWE-312)
* darktable-0005: modulegroups test_visible O(M*G*P) per UI refresh
*/
public class DarktableTest {
// ---------------------------------------------------------------
// darktable-0001: map_locations symmetric diff O(N*M) vs O(N+M)
// ---------------------------------------------------------------
/** Defective: g_list_find inside loop — O(N*M) */
static int mapLocationDiffDefective(List<Integer> oldImgs, List<Integer> newImgs) {
int ops = 0;
// detach old not in new
for (int id : oldImgs) {
for (int nid : newImgs) { ops++; if (nid == id) break; }
}
// attach new not in old
for (int id : newImgs) {
for (int oid : oldImgs) { ops++; if (oid == id) break; }
}
return ops;
}
/** Fixed: GHashTable for O(1) lookup — O(N+M) */
static int mapLocationDiffFixed(List<Integer> oldImgs, List<Integer> newImgs) {
int ops = 0;
Set<Integer> newSet = new HashSet<>(newImgs); ops += newImgs.size();
for (int id : oldImgs) { newSet.contains(id); ops++; }
Set<Integer> oldSet = new HashSet<>(oldImgs); ops += oldImgs.size();
for (int id : newImgs) { oldSet.contains(id); ops++; }
return ops;
}
static void testMapLocationDiff() {
int N = 1000;
List<Integer> oldImgs = new ArrayList<>();
List<Integer> newImgs = new ArrayList<>();
for (int i = 0; i < N; i++) { oldImgs.add(i); newImgs.add(i + N / 2); }
int defectOps = mapLocationDiffDefective(oldImgs, newImgs);
int fixedOps = mapLocationDiffFixed(oldImgs, newImgs);
double ratio = (double) defectOps / fixedOps;
System.out.printf("darktable-0001 map_locations diff: defect=%d fixed=%d ratio=%.1fx%n",
defectOps, fixedOps, ratio);
assert ratio > 10 : "Expected >10x ratio, got " + ratio;
System.out.println(" PASS");
}
// ---------------------------------------------------------------
// darktable-0002: tag dedup via g_list_find O(T*L) ≈ O(N²)
// ---------------------------------------------------------------
/** Defective: linear scan dedup */
static int tagAddToListDefective(List<Integer> existingTags, List<Integer> newTags) {
int ops = 0;
List<Integer> list = new ArrayList<>(existingTags);
for (int tag : newTags) {
boolean found = false;
for (int t : list) { ops++; if (t == tag) { found = true; break; } }
if (!found) list.add(tag);
}
return ops;
}
/** Fixed: HashSet companion */
static int tagAddToListFixed(List<Integer> existingTags, List<Integer> newTags) {
int ops = 0;
Set<Integer> seen = new HashSet<>(existingTags); ops += existingTags.size();
for (int tag : newTags) {
ops++;
if (!seen.contains(tag)) { seen.add(tag); }
}
return ops;
}
static void testTagAddToList() {
int N = 500;
List<Integer> existing = new ArrayList<>();
List<Integer> toAdd = new ArrayList<>();
// all unique — worst case for dedup
for (int i = 0; i < N; i++) existing.add(i);
for (int i = N; i < 2 * N; i++) toAdd.add(i);
int defectOps = tagAddToListDefective(existing, toAdd);
int fixedOps = tagAddToListFixed(existing, toAdd);
double ratio = (double) defectOps / fixedOps;
System.out.printf("darktable-0002 tag dedup: defect=%d fixed=%d ratio=%.1fx%n",
defectOps, fixedOps, ratio);
assert ratio > 50 : "Expected >50x ratio, got " + ratio;
System.out.println(" PASS");
}
// ---------------------------------------------------------------
// darktable-0003: map view sel_imgs inside O(I*J) clustering loop
// ---------------------------------------------------------------
/** Defective: g_list_find(sel_imgs, imgid) inside nested I×J loop — O(I²×S) */
static long mapViewClusterDefective(int imgCount, List<Integer> selImgs, int[] clusterIds) {
long ops = 0;
for (int i = 0; i < imgCount; i++) {
int group = clusterIds[i];
for (int j = 0; j < imgCount; j++) {
if (clusterIds[j] == group) {
// linear scan of sel_imgs
for (int s : selImgs) { ops++; if (s == j) break; }
}
}
}
return ops;
}
/** Fixed: HashSet for O(1) selection check — O(I²+S) */
static long mapViewClusterFixed(int imgCount, List<Integer> selImgs, int[] clusterIds) {
long ops = 0;
Set<Integer> selSet = new HashSet<>(selImgs); ops += selImgs.size();
for (int i = 0; i < imgCount; i++) {
int group = clusterIds[i];
for (int j = 0; j < imgCount; j++) {
if (clusterIds[j] == group) {
selSet.contains(j); ops++;
}
}
}
return ops;
}
static void testMapViewCluster() {
int I = 500;
int S = 200;
List<Integer> selImgs = new ArrayList<>();
for (int i = 0; i < S; i++) selImgs.add(i);
// All in one cluster — worst case
int[] clusterIds = new int[I];
Arrays.fill(clusterIds, 1);
long defectOps = mapViewClusterDefective(I, selImgs, clusterIds);
long fixedOps = mapViewClusterFixed(I, selImgs, clusterIds);
double ratio = (double) defectOps / fixedOps;
System.out.printf("darktable-0003 map view cluster sel: defect=%d fixed=%d ratio=%.1fx%n",
defectOps, fixedOps, ratio);
assert ratio > 10 : "Expected >10x ratio, got " + ratio;
System.out.println(" PASS");
}
// ---------------------------------------------------------------
// darktable-0004: pwstorage credential logged verbatim (CWE-312)
// ---------------------------------------------------------------
/**
* Defective: credential value logged verbatim.
* Simulates dt_print(DT_DEBUG_PWSTORAGE, "storing (%s, %s)", key, value)
* where value = {"server":"...","username":"...","password":"s3cr3t"}.
*/
static String logCredentialDefective(String key, String credentialJson) {
// Mirrors: dt_print(DT_DEBUG_PWSTORAGE, "storing (%s, %s)", key, value)
return String.format("[pwstorage_kwallet_set] storing (%s, %s)", key, credentialJson);
}
/**
* Fixed: only log the key (service name), never the value (which contains password).
* Mirrors: dt_print(DT_DEBUG_PWSTORAGE, "storing (%s, [REDACTED])", key)
*/
static String logCredentialFixed(String key, String credentialJson) {
return String.format("[pwstorage_kwallet_set] storing (%s, [REDACTED])", key);
}
static void testPwstorageCredentialLog() {
String key = "example.com";
String credJson = "{\"server\":\"example.com\",\"username\":\"alice\",\"password\":\"s3cr3t\"}";
String defectLog = logCredentialDefective(key, credJson);
String fixedLog = logCredentialFixed(key, credJson);
boolean defectExposesPassword = defectLog.contains("s3cr3t");
boolean fixedExposesPassword = fixedLog.contains("s3cr3t");
System.out.printf("darktable-0004 pwstorage CWE-312: defect_exposes=%b fixed_exposes=%b%n",
defectExposesPassword, fixedExposesPassword);
assert defectExposesPassword : "Defective log should expose password";
assert !fixedExposesPassword : "Fixed log must NOT expose password";
assert fixedLog.contains("[REDACTED]") : "Fixed log must contain [REDACTED]";
System.out.println(" PASS");
}
// ---------------------------------------------------------------
// darktable-0005: modulegroups test_visible O(M*G*P) vs O(M)
// ---------------------------------------------------------------
/**
* Defective: for each of M modules, iterate G groups, each doing linear scan of P items.
* Mirrors _lib_modulegroups_test_visible called inside _lib_modulegroups_update_iop_visibility.
*/
static int modulegroupsTestVisibleDefective(List<String> iopModules,
List<List<String>> groups) {
int ops = 0;
for (String module : iopModules) {
// _lib_modulegroups_test_visible inner loop
for (List<String> group : groups) {
for (String gm : group) {
ops++;
if (gm.equals(module)) break;
}
}
}
return ops;
}
/**
* Fixed: precompute a Set of all visible module names across groups.
* test_visible = O(1) per module.
*/
static int modulegroupsTestVisibleFixed(List<String> iopModules,
List<List<String>> groups) {
int ops = 0;
// Rebuild visible set once (done when groups change)
Set<String> visibleSet = new HashSet<>();
for (List<String> group : groups) {
for (String gm : group) {
visibleSet.add(gm);
ops++; // set build cost
}
}
// Per-update: O(M) hash lookups
for (String module : iopModules) {
visibleSet.contains(module);
ops++;
}
return ops;
}
static void testModulegroupsTestVisible() {
// 80 iop modules, 8 groups of 10 modules each
int M = 80;
int G = 8;
int P = 10;
List<String> iopModules = new ArrayList<>();
for (int i = 0; i < M; i++) iopModules.add("module_" + i);
List<List<String>> groups = new ArrayList<>();
for (int g = 0; g < G; g++) {
List<String> group = new ArrayList<>();
for (int p = 0; p < P; p++) {
// Distribute modules evenly across groups
group.add("module_" + (g * P + p));
}
groups.add(group);
}
int defectOps = modulegroupsTestVisibleDefective(iopModules, groups);
int fixedOps = modulegroupsTestVisibleFixed(iopModules, groups);
double ratio = (double) defectOps / fixedOps;
System.out.printf("darktable-0005 modulegroups test_visible: defect=%d fixed=%d ratio=%.1fx%n",
defectOps, fixedOps, ratio);
assert ratio > 3.0 : "Expected >3x ratio, got " + ratio;
System.out.println(" PASS");
}
// ---------------------------------------------------------------
public static void main(String[] args) {
testMapLocationDiff();
testTagAddToList();
testMapViewCluster();
testPwstorageCredentialLog();
testModulegroupsTestVisible();
System.out.println("\nAll 5 darktable tests PASSED.");
}
}