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