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 songsInDb, List filesOnDisk) { int ops = 0; List 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 for O(1) lookup — O(S) */ static int deletedSongsDetection_after(List songsInDb, Set filesOnDisk) { int ops = 0; List 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 filesOnDisk = new ArrayList<>(); Set 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 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 cache, List toFlush) { int ops = 0; List 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 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 cache, List toFlush) { int ops = 0; Set toRemove = new HashSet<>(toFlush); ops += toFlush.size(); // building the set List cacheCopy = new ArrayList<>(cache); Iterator 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 cache = new ArrayList<>(); for (int i = 0; i < C; i++) cache.add(i); List 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"); } }