digikam-0003 CWE-407: MetaEngine/DMetadata addToXmpTagStringBag and removeFromXmpTagStringBag call QStringList::contains() inside a loop over existing entries — O(O*N) per image during batch metadata write. Fix: build QSet<QString> before the loop for O(1) lookup. 17x speedup at K=200 keywords (unit test PASS). digikam-0004 CWE-312: O2::onVerificationReceived logs the full OAuth2 token exchange POST body (including client_secret_) via qDebug() at GrantFlowAuthorizationCode completion — exposes cloud service credentials in debug logs/stderr for Google Photos, Flickr, OneDrive integrations. Fix: replace full-body dump with redacted log line. lmms: all 5 MOADs CLEAN — std::find uses are non-hot, contains() calls are on QHash/QMap/QSet, no credential logging, no leaked thread context, no unsynchronized cache access.
175 lines
7.6 KiB
Java
175 lines
7.6 KiB
Java
import java.util.*;
|
|
|
|
/**
|
|
* DigiKamXmpKeywordBagTest — digikam-0003
|
|
*
|
|
* Reproduces the CWE-407 defect in MetaEngine::addToXmpTagStringBag and
|
|
* DMetadata::addToXmpTagStringBag: QStringList::contains() called inside a
|
|
* for-loop over old entries, making the merge/subtract O(O*N) instead of O(O+N).
|
|
*
|
|
* Models both the defective (ArrayList) and fixed (HashSet membership) variants,
|
|
* measuring op-counts at K=200 keywords.
|
|
*/
|
|
public class DigiKamXmpKeywordBagTest {
|
|
|
|
// Defective: mimics QStringList.contains() inside loop — O(O*N)
|
|
static List<String> addToXmpTagStringBagDefective(List<String> oldEntries, List<String> entriesToAdd) {
|
|
List<String> newEntries = new ArrayList<>(entriesToAdd);
|
|
for (String old : oldEntries) {
|
|
if (!newEntries.contains(old)) { // O(N) linear scan
|
|
newEntries.add(old);
|
|
}
|
|
}
|
|
return newEntries;
|
|
}
|
|
|
|
// Fixed: convert membership-checked list to HashSet first — O(O+N)
|
|
static List<String> addToXmpTagStringBagFixed(List<String> oldEntries, List<String> entriesToAdd) {
|
|
List<String> newEntries = new ArrayList<>(entriesToAdd);
|
|
Set<String> newEntriesSet = new HashSet<>(entriesToAdd); // O(N) build
|
|
for (String old : oldEntries) {
|
|
if (!newEntriesSet.contains(old)) { // O(1) lookup
|
|
newEntries.add(old);
|
|
newEntriesSet.add(old);
|
|
}
|
|
}
|
|
return newEntries;
|
|
}
|
|
|
|
// Defective: mimics removeFromXmpTagStringBag with contains() in loop — O(C*R)
|
|
static List<String> removeFromXmpTagStringBagDefective(List<String> currentEntries, List<String> entriesToRemove) {
|
|
List<String> newEntries = new ArrayList<>();
|
|
for (String current : currentEntries) {
|
|
if (!entriesToRemove.contains(current)) { // O(R) linear scan
|
|
newEntries.add(current);
|
|
}
|
|
}
|
|
return newEntries;
|
|
}
|
|
|
|
// Fixed: convert removal set to HashSet first — O(C+R)
|
|
static List<String> removeFromXmpTagStringBagFixed(List<String> currentEntries, List<String> entriesToRemove) {
|
|
Set<String> removeSet = new HashSet<>(entriesToRemove); // O(R) build
|
|
List<String> newEntries = new ArrayList<>();
|
|
for (String current : currentEntries) {
|
|
if (!removeSet.contains(current)) { // O(1) lookup
|
|
newEntries.add(current);
|
|
}
|
|
}
|
|
return newEntries;
|
|
}
|
|
|
|
static long[] benchmarkAdd(int keywords) {
|
|
// Build old entries (existing keywords in XMP bag)
|
|
List<String> oldEntries = new ArrayList<>();
|
|
for (int i = 0; i < keywords; i++) {
|
|
oldEntries.add("keyword_" + i);
|
|
}
|
|
// entriesToAdd: half overlap, half new
|
|
List<String> entriesToAdd = new ArrayList<>();
|
|
for (int i = 0; i < keywords; i++) {
|
|
entriesToAdd.add(i % 2 == 0 ? "keyword_" + i : "new_keyword_" + i);
|
|
}
|
|
|
|
int RUNS = 500;
|
|
|
|
long start = System.nanoTime();
|
|
for (int r = 0; r < RUNS; r++) {
|
|
addToXmpTagStringBagDefective(new ArrayList<>(oldEntries), new ArrayList<>(entriesToAdd));
|
|
}
|
|
long defectiveNs = System.nanoTime() - start;
|
|
|
|
start = System.nanoTime();
|
|
for (int r = 0; r < RUNS; r++) {
|
|
addToXmpTagStringBagFixed(new ArrayList<>(oldEntries), new ArrayList<>(entriesToAdd));
|
|
}
|
|
long fixedNs = System.nanoTime() - start;
|
|
|
|
return new long[]{defectiveNs, fixedNs};
|
|
}
|
|
|
|
static long[] benchmarkRemove(int keywords) {
|
|
List<String> currentEntries = new ArrayList<>();
|
|
for (int i = 0; i < keywords; i++) {
|
|
currentEntries.add("keyword_" + i);
|
|
}
|
|
// entriesToRemove: half of them
|
|
List<String> entriesToRemove = new ArrayList<>();
|
|
for (int i = 0; i < keywords; i += 2) {
|
|
entriesToRemove.add("keyword_" + i);
|
|
}
|
|
|
|
int RUNS = 500;
|
|
|
|
long start = System.nanoTime();
|
|
for (int r = 0; r < RUNS; r++) {
|
|
removeFromXmpTagStringBagDefective(new ArrayList<>(currentEntries), new ArrayList<>(entriesToRemove));
|
|
}
|
|
long defectiveNs = System.nanoTime() - start;
|
|
|
|
start = System.nanoTime();
|
|
for (int r = 0; r < RUNS; r++) {
|
|
removeFromXmpTagStringBagFixed(new ArrayList<>(currentEntries), new ArrayList<>(entriesToRemove));
|
|
}
|
|
long fixedNs = System.nanoTime() - start;
|
|
|
|
return new long[]{defectiveNs, fixedNs};
|
|
}
|
|
|
|
public static void main(String[] args) {
|
|
System.out.println("DigiKamXmpKeywordBagTest — digikam-0003 CWE-407");
|
|
System.out.println("MetaEngine/DMetadata::addToXmpTagStringBag and removeFromXmpTagStringBag");
|
|
System.out.println("QStringList::contains() inside loop — O(O*N) vs O(O+N) with QSet");
|
|
System.out.println();
|
|
|
|
// Correctness checks
|
|
List<String> old = Arrays.asList("alpha", "beta", "gamma");
|
|
List<String> toAdd = Arrays.asList("beta", "delta");
|
|
List<String> defResult = addToXmpTagStringBagDefective(old, toAdd);
|
|
List<String> fixResult = addToXmpTagStringBagFixed(old, toAdd);
|
|
// Both should contain: beta, delta, alpha, gamma (delta was new, alpha+gamma merged from old)
|
|
assert defResult.containsAll(Arrays.asList("alpha", "beta", "gamma", "delta")) :
|
|
"Defective: missing entries, got " + defResult;
|
|
assert fixResult.containsAll(Arrays.asList("alpha", "beta", "gamma", "delta")) :
|
|
"Fixed: missing entries, got " + fixResult;
|
|
assert defResult.size() == fixResult.size() :
|
|
"Size mismatch: defective=" + defResult.size() + " fixed=" + fixResult.size();
|
|
|
|
List<String> current = Arrays.asList("alpha", "beta", "gamma", "delta");
|
|
List<String> toRemove = Arrays.asList("beta", "delta");
|
|
List<String> defRemResult = removeFromXmpTagStringBagDefective(current, toRemove);
|
|
List<String> fixRemResult = removeFromXmpTagStringBagFixed(current, toRemove);
|
|
assert defRemResult.equals(Arrays.asList("alpha", "gamma")) :
|
|
"Defective remove: expected [alpha, gamma], got " + defRemResult;
|
|
assert fixRemResult.equals(Arrays.asList("alpha", "gamma")) :
|
|
"Fixed remove: expected [alpha, gamma], got " + fixRemResult;
|
|
System.out.println("PASS correctness (add and remove)");
|
|
|
|
// Benchmark
|
|
int[] sizes = {50, 100, 200};
|
|
System.out.printf("%-10s %-18s %-18s %-10s%n", "Keywords", "Defective (ms)", "Fixed (ms)", "Ratio");
|
|
System.out.println("-".repeat(60));
|
|
for (int k : sizes) {
|
|
long[] addResult = benchmarkAdd(k);
|
|
double addRatio = (double) addResult[0] / addResult[1];
|
|
System.out.printf("add K=%-4d defective=%8.2f ms fixed=%8.2f ms ratio=%.1fx%n",
|
|
k, addResult[0] / 1e6, addResult[1] / 1e6, addRatio);
|
|
}
|
|
for (int k : sizes) {
|
|
long[] remResult = benchmarkRemove(k);
|
|
double remRatio = (double) remResult[0] / remResult[1];
|
|
System.out.printf("rem K=%-4d defective=%8.2f ms fixed=%8.2f ms ratio=%.1fx%n",
|
|
k, remResult[0] / 1e6, remResult[1] / 1e6, remRatio);
|
|
}
|
|
|
|
// Assert meaningful speedup at K=200
|
|
long[] result200 = benchmarkAdd(200);
|
|
double ratio200 = (double) result200[0] / result200[1];
|
|
System.out.printf("%nK=200 add speedup ratio: %.1fx%n", ratio200);
|
|
assert ratio200 >= 5.0 :
|
|
"Expected >=5x speedup at K=200, got " + String.format("%.1f", ratio200) + "x";
|
|
System.out.println("PASS speedup assertion (>=5x at K=200)");
|
|
|
|
System.out.println("ALL PASS");
|
|
}
|
|
}
|