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
150 lines
5.2 KiB
Java
150 lines
5.2 KiB
Java
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");
|
||
}
|
||
}
|