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
147 lines
4.8 KiB
Java
147 lines
4.8 KiB
Java
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");
|
||
}
|
||
}
|