java-topology/defects/strawberry/unit/StrawberryTest.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

150 lines
5.2 KiB
Java
Raw Permalink 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 Strawberry Music Player defects.
*
* strawberry-0001: CollectionWatcher files_on_disk QStringList O(S×F + F²)
* strawberry-0002: ScrobblerCache::Flush QList.contains/removeAll O(F×C)
*/
public class StrawberryTest {
// ========== strawberry-0001: CollectionWatcher files_on_disk ==========
/** BEFORE: QStringList.contains() in deleted-songs loop — O(S×F) */
static int deletedSongsDetection_before(List<String> songsInDb, List<String> filesOnDisk) {
int ops = 0;
List<String> deletedSongs = new ArrayList<>();
for (String songFile : songsInDb) {
// QStringList::contains is O(F)
boolean found = false;
for (String diskFile : filesOnDisk) {
ops++;
if (diskFile.equals(songFile)) { found = true; break; }
}
if (!found) {
deletedSongs.add(songFile);
}
}
return ops;
}
/** AFTER: QSet<QString> for O(1) lookup — O(S) */
static int deletedSongsDetection_after(List<String> songsInDb, Set<String> filesOnDisk) {
int ops = 0;
List<String> deletedSongs = new ArrayList<>();
for (String songFile : songsInDb) {
ops++; // O(1) hash lookup
if (!filesOnDisk.contains(songFile)) {
deletedSongs.add(songFile);
}
}
return ops;
}
static boolean test_strawberry_0001() {
int F = 5000; // files on disk
int S = 5000; // songs in DB (most overlap)
List<String> filesOnDisk = new ArrayList<>();
Set<String> filesOnDiskSet = new HashSet<>();
for (int i = 0; i < F; i++) {
String path = "/music/track_" + i + ".mp3";
filesOnDisk.add(path);
filesOnDiskSet.add(path);
}
// Songs in DB: 90% overlap, 10% deleted
List<String> songsInDb = new ArrayList<>();
for (int i = 0; i < (int)(S * 0.9); i++) {
songsInDb.add("/music/track_" + i + ".mp3");
}
for (int i = 0; i < (int)(S * 0.1); i++) {
songsInDb.add("/music/deleted_" + i + ".mp3");
}
int opsBefore = deletedSongsDetection_before(songsInDb, filesOnDisk);
int opsAfter = deletedSongsDetection_after(songsInDb, filesOnDiskSet);
double ratio = (double) opsBefore / opsAfter;
System.out.printf(" strawberry-0001 files_on_disk: before=%d after=%d ratio=%.1fx%n",
opsBefore, opsAfter, ratio);
return ratio > 5.0;
}
// ========== strawberry-0002: ScrobblerCache::Flush ==========
/** BEFORE: QList.contains + removeAll per item — O(F×C) */
static int scrobblerFlush_before(List<Integer> cache, List<Integer> toFlush) {
int ops = 0;
List<Integer> cacheCopy = new ArrayList<>(cache);
for (int item : toFlush) {
// contains is O(C)
boolean found = false;
for (int i = 0; i < cacheCopy.size(); i++) {
ops++;
if (cacheCopy.get(i) == item) { found = true; break; }
}
if (found) {
// removeAll is O(C)
Iterator<Integer> it = cacheCopy.iterator();
while (it.hasNext()) {
ops++;
if (it.next() == item) it.remove();
}
}
}
return ops;
}
/** AFTER: build set + single-pass filter — O(F + C) */
static int scrobblerFlush_after(List<Integer> cache, List<Integer> toFlush) {
int ops = 0;
Set<Integer> toRemove = new HashSet<>(toFlush);
ops += toFlush.size(); // building the set
List<Integer> cacheCopy = new ArrayList<>(cache);
Iterator<Integer> it = cacheCopy.iterator();
while (it.hasNext()) {
ops++;
if (toRemove.contains(it.next())) it.remove();
}
return ops;
}
static boolean test_strawberry_0002() {
int C = 1000; // cache size
int F = 500; // items to flush (half the cache)
List<Integer> cache = new ArrayList<>();
for (int i = 0; i < C; i++) cache.add(i);
List<Integer> toFlush = new ArrayList<>();
for (int i = 0; i < F; i++) toFlush.add(i * 2); // flush even-numbered items
int opsBefore = scrobblerFlush_before(cache, toFlush);
int opsAfter = scrobblerFlush_after(cache, toFlush);
double ratio = (double) opsBefore / opsAfter;
System.out.printf(" strawberry-0002 scrobbler flush: before=%d after=%d ratio=%.1fx%n",
opsBefore, opsAfter, ratio);
return ratio > 5.0;
}
// ========== Main ==========
public static void main(String[] args) {
System.out.println("StrawberryTest: CWE-407 simulation");
boolean pass1 = test_strawberry_0001();
boolean pass2 = test_strawberry_0002();
System.out.println();
System.out.printf(" strawberry-0001: %s%n", pass1 ? "PASS" : "FAIL");
System.out.printf(" strawberry-0002: %s%n", pass2 ? "PASS" : "FAIL");
if (!pass1 || !pass2) {
System.out.println("FAILED");
System.exit(1);
}
System.out.println("ALL PASS");
}
}