java-topology/defects/amarok/unit/AmarokTest.java
russell@unturf.com 0f9e84d300 amarok + strawberry: 4 CWE-407 defects — playlist queue O(N×Q), grouping proxy O(G×S), collection watcher O(S×F), scrobbler cache O(F×C)
amarok-0001: TrackNavigator::queueIds QQueue.contains in loop MEDIUM 1000x
amarok-0002: QtGroupingProxy::mapFromSource QList.contains/indexOf in nested iteration MEDIUM 2426x
strawberry-0001: CollectionWatcher::ScanSubdirectory QStringList files_on_disk contains+removeAll HIGH 2525x
strawberry-0002: ScrobblerCache::Flush QList.contains/removeAll in loop MEDIUM 334x
2026-03-30 14:40:10 -04:00

147 lines
4.8 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 Amarok defects.
*
* amarok-0001: TrackNavigator::queueIds QQueue.contains() O(N×Q)
* amarok-0002: QtGroupingProxy mapFromSource QList.contains/indexOf O(G×S)
*/
public class AmarokTest {
// ========== amarok-0001: TrackNavigator queueIds ==========
/** BEFORE: QQueue.contains() is linear scan — O(N×Q) */
static int queueIds_before(List<Long> ids, LinkedList<Long> queue) {
int ops = 0;
for (long id : ids) {
// QQueue::contains() scans the entire queue — O(Q)
boolean found = false;
for (long q : queue) {
ops++;
if (q == id) { found = true; break; }
}
if (!found)
queue.add(id);
}
return ops;
}
/** AFTER: QSet mirror for O(1) membership — O(N) */
static int queueIds_after(List<Long> ids, LinkedList<Long> queue) {
int ops = 0;
Set<Long> queueSet = new HashSet<>(queue);
for (long id : ids) {
ops++;
if (!queueSet.contains(id)) {
queue.add(id);
queueSet.add(id);
}
}
return ops;
}
static boolean test_amarok_0001() {
int Q = 1000; // existing queue size
int N = 1000; // new IDs to add (half duplicates)
// Pre-fill queue with IDs 0..Q-1
LinkedList<Long> queueBefore = new LinkedList<>();
LinkedList<Long> queueAfter = new LinkedList<>();
for (long i = 0; i < Q; i++) {
queueBefore.add(i);
queueAfter.add(i);
}
// Queue IDs: 500..1499 (half already in queue, half new)
List<Long> newIds = new ArrayList<>();
for (long i = Q / 2; i < Q / 2 + N; i++)
newIds.add(i);
int opsBefore = queueIds_before(newIds, new LinkedList<>(queueBefore));
int opsAfter = queueIds_after(newIds, new LinkedList<>(queueAfter));
double ratio = (double) opsBefore / opsAfter;
System.out.printf(" amarok-0001 queueIds: before=%d after=%d ratio=%.1fx%n",
opsBefore, opsAfter, ratio);
return ratio > 5.0;
}
// ========== amarok-0002: QtGroupingProxy mapFromSource ==========
/** BEFORE: iterate all groups, QList.contains() per group — O(G×S) */
static int mapFromSource_before(Map<Integer, List<Integer>> groupMap, int sourceRow) {
int ops = 0;
int groupRow = -1;
for (Map.Entry<Integer, List<Integer>> entry : groupMap.entrySet()) {
for (int row : entry.getValue()) {
ops++;
if (row == sourceRow) {
groupRow = entry.getKey();
break;
}
}
if (groupRow != -1) break;
}
return ops;
}
/** AFTER: reverse map O(1) lookup */
static int mapFromSource_after(Map<Integer, Integer> reverseMap, int sourceRow) {
int ops = 1; // single hash lookup
int groupRow = reverseMap.getOrDefault(sourceRow, -1);
return ops;
}
static boolean test_amarok_0002() {
int G = 50; // number of groups
int S = 100; // tracks per group
int LOOKUPS = 200;
// Build group map
Map<Integer, List<Integer>> groupMap = new LinkedHashMap<>();
Map<Integer, Integer> reverseMap = new HashMap<>();
int sourceRow = 0;
for (int g = 0; g < G; g++) {
List<Integer> rows = new ArrayList<>();
for (int s = 0; s < S; s++) {
rows.add(sourceRow);
reverseMap.put(sourceRow, g);
sourceRow++;
}
groupMap.put(g, rows);
}
// Lookup rows spread across groups (worst-case: last group)
long totalBefore = 0, totalAfter = 0;
Random rng = new Random(42);
for (int i = 0; i < LOOKUPS; i++) {
int row = rng.nextInt(G * S);
totalBefore += mapFromSource_before(groupMap, row);
totalAfter += mapFromSource_after(reverseMap, row);
}
double ratio = (double) totalBefore / totalAfter;
System.out.printf(" amarok-0002 mapFromSource: before=%d after=%d ratio=%.1fx%n",
totalBefore, totalAfter, ratio);
return ratio > 5.0;
}
// ========== Main ==========
public static void main(String[] args) {
System.out.println("AmarokTest: CWE-407 simulation");
boolean pass1 = test_amarok_0001();
boolean pass2 = test_amarok_0002();
System.out.println();
System.out.printf(" amarok-0001: %s%n", pass1 ? "PASS" : "FAIL");
System.out.printf(" amarok-0002: %s%n", pass2 ? "PASS" : "FAIL");
if (!pass1 || !pass2) {
System.out.println("FAILED");
System.exit(1);
}
System.out.println("ALL PASS");
}
}