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 ids, LinkedList 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 ids, LinkedList queue) { int ops = 0; Set 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 queueBefore = new LinkedList<>(); LinkedList 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 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> groupMap, int sourceRow) { int ops = 0; int groupRow = -1; for (Map.Entry> 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 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> groupMap = new LinkedHashMap<>(); Map reverseMap = new HashMap<>(); int sourceRow = 0; for (int g = 0; g < G; g++) { List 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"); } }