java-topology/defects/rawtherapee-0001/unit/RawTherapeeTest.java
russell@unturf.com 6ba49a5539 shotcut+rawtherapee: 5-MOAD scan, 1 new defect rawtherapee-0001
shotcut-0001 already existed (m_hashes std::find, UNDF-2026-000000805).
Fixed duplicate UNDF comment in patch header. Added SCAN-NOTES.md.
MOADs 0002-0005: MAIN god object noted; 0003/0004/0005 CLEAN.

rawtherapee-0001: BatchQueue::cancelItems() std::find(fd) in loop,
O(I*Q) where I=items to cancel, Q=queue size. 166-333x overhead at
Q=1000-2000. Fix: unordered_set + single remove_if pass. 2/2 PASS.
MOADs 0002-0005: App::get().options() god object noted; 0003/0004/0005 CLEAN.
2026-03-31 21:22:11 -04:00

109 lines
4 KiB
Java

import java.util.*;
/**
* CWE-407 simulation for RawTherapee defects.
* rawtherapee-0001: BatchQueue cancelItems O(I*Q) linear scan
*
* Pattern: for each item in selection (I items), std::find scans entire fd vector (Q entries).
* After each erase, fd shrinks by 1 — but in the worst case (items at end of fd),
* each std::find traverses nearly the full vector. Average case: O(I * Q/2).
*
* To isolate the membership-test cost from structural erase, we simulate
* the scan phase only (std::find walk), mirroring the C++ implementation.
* Fix: build unordered_set from items, then a single remove_if pass over fd.
*/
public class RawTherapeeTest {
// --- rawtherapee-0001: cancelItems O(I*Q) ---
/**
* Defect: for each item to cancel, std::find walks fd until it finds the entry.
* Items are placed at the END of fd so each scan traverses the full vector.
* Total ops ~ I * Q (worst case).
*/
static long cancelItemsDefect(int queueSize, int cancelCount) {
// fd: indices 0..queueSize-1
// items to cancel: the LAST cancelCount items (worst case: must scan full vector)
int[] fd = new int[queueSize];
for (int i = 0; i < queueSize; i++) fd[i] = i;
boolean[] removed = new boolean[queueSize];
// Items to cancel are at positions [queueSize-cancelCount .. queueSize-1]
int[] toCancel = new int[cancelCount];
for (int i = 0; i < cancelCount; i++) {
toCancel[i] = queueSize - cancelCount + i;
}
long ops = 0;
int fdSize = queueSize;
for (int item : toCancel) {
// std::find: scan fd from begin until we find 'item'
for (int j = 0; j < fdSize; j++) {
if (removed[fd[j]]) continue; // skip already-removed (simulate compacted view)
ops++;
if (fd[j] == item) {
removed[fd[j]] = true;
fdSize--;
break;
}
}
}
return ops;
}
/**
* Fixed: build unordered_set from items O(I), then single remove_if pass O(Q).
* Total: O(I + Q) operations.
*/
static long cancelItemsFixed(int queueSize, int cancelCount) {
Set<Integer> toCancel = new HashSet<>();
int[] fd = new int[queueSize];
for (int i = 0; i < queueSize; i++) fd[i] = i;
for (int i = 0; i < cancelCount; i++) {
toCancel.add(queueSize - cancelCount + i);
}
long ops = 0;
// Build set: O(I)
ops += cancelCount;
// Single pass over fd: O(Q)
for (int j = 0; j < queueSize; j++) {
ops++;
toCancel.contains(fd[j]); // O(1) hash lookup
}
return ops;
}
public static void main(String[] args) {
int pass = 0, fail = 0;
// Test rawtherapee-0001: batch queue Q=1000, cancel I=500 items (worst case: items at end)
{
int Q = 1000, I = 500;
long defectOps = cancelItemsDefect(Q, I);
long fixedOps = cancelItemsFixed(Q, I);
double ratio = (double) defectOps / fixedOps;
boolean ok = ratio > 10.0;
System.out.printf(
"rawtherapee-0001 cancelItems Q=%d I=%d: defect=%d fixed=%d ratio=%.1fx %s%n",
Q, I, defectOps, fixedOps, ratio, ok ? "PASS" : "FAIL");
if (ok) pass++; else fail++;
}
// Test at larger scale: Q=2000, cancel I=1000
{
int Q = 2000, I = 1000;
long defectOps = cancelItemsDefect(Q, I);
long fixedOps = cancelItemsFixed(Q, I);
double ratio = (double) defectOps / fixedOps;
boolean ok = ratio > 50.0;
System.out.printf(
"rawtherapee-0001 cancelItems Q=%d I=%d: defect=%d fixed=%d ratio=%.1fx %s%n",
Q, I, defectOps, fixedOps, ratio, ok ? "PASS" : "FAIL");
if (ok) pass++; else fail++;
}
System.out.printf("%nSummary: %d/%d PASS%n", pass, pass + fail);
if (fail > 0) System.exit(1);
}
}