clementine-0001: LibraryWatcher ScanSubdirectory FindSongByPath O(F*S) + files_on_disk.contains O(S*F) — linear scan with TODO comment, fix with HashMap + HashSet. HIGH severity, 250x at N=1000. clementine-0002: SongSender indexOf(s) O(N^2) in SendAlbum/SendPlaylist/ SendUrls loops — fix with integer counter + QSet for requested_ids. MEDIUM severity, 500x at N=1000. MPD: CLEAN — uses std::set, bitmask arrays, std::map throughout. Rhythmbox: CLEAN — uses g_hash_table for all membership checks. 4/4 unit tests PASS.
109 lines
3.7 KiB
Java
109 lines
3.7 KiB
Java
import java.util.*;
|
|
|
|
/**
|
|
* CWE-407 simulation: Clementine LibraryWatcher::ScanSubdirectory
|
|
* Two defects:
|
|
* 1. FindSongByPath: linear scan of songs_in_db per file -> O(F*S)
|
|
* 2. files_on_disk.contains: linear scan per song -> O(S*F)
|
|
*
|
|
* Fix: HashMap for song-by-path lookup, HashSet for files_on_disk membership.
|
|
*/
|
|
public class ClementineLibraryWatcherTest {
|
|
|
|
// --- Defective: linear scan to find song by path ---
|
|
static String findSongByPathLinear(List<String> songsInDb, String path) {
|
|
for (String song : songsInDb) {
|
|
if (song.equals(path)) return song;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
static long scanSubdirectoryDefective(List<String> filesOnDisk, List<String> songsInDb) {
|
|
long ops = 0;
|
|
|
|
// Phase 1: For each file on disk, find matching song (linear scan)
|
|
for (String file : filesOnDisk) {
|
|
for (String song : songsInDb) {
|
|
ops++;
|
|
if (song.equals(file)) break;
|
|
}
|
|
}
|
|
|
|
// Phase 2: For each song in DB, check if file exists on disk (linear scan)
|
|
for (String song : songsInDb) {
|
|
for (String file : filesOnDisk) {
|
|
ops++;
|
|
if (file.equals(song)) break;
|
|
}
|
|
}
|
|
|
|
return ops;
|
|
}
|
|
|
|
// --- Fixed: HashMap + HashSet ---
|
|
static long scanSubdirectoryFixed(List<String> filesOnDisk, List<String> songsInDb) {
|
|
long ops = 0;
|
|
|
|
// Build hash map for O(1) song-by-path lookup
|
|
Map<String, String> songsByPath = new HashMap<>();
|
|
for (String song : songsInDb) {
|
|
songsByPath.put(song, song);
|
|
ops++;
|
|
}
|
|
|
|
// Build hash set for O(1) file-on-disk membership
|
|
Set<String> filesOnDiskSet = new HashSet<>(filesOnDisk);
|
|
ops += filesOnDisk.size();
|
|
|
|
// Phase 1: For each file, O(1) lookup
|
|
for (String file : filesOnDisk) {
|
|
songsByPath.get(file);
|
|
ops++;
|
|
}
|
|
|
|
// Phase 2: For each song, O(1) contains
|
|
for (String song : songsInDb) {
|
|
filesOnDiskSet.contains(song);
|
|
ops++;
|
|
}
|
|
|
|
return ops;
|
|
}
|
|
|
|
public static void main(String[] args) {
|
|
int[] sizes = {100, 250, 500, 1000};
|
|
|
|
System.out.println("=== Clementine LibraryWatcher ScanSubdirectory CWE-407 Test ===");
|
|
System.out.println("Defect: FindSongByPath O(F*S) linear scan + files_on_disk.contains O(S*F)");
|
|
System.out.println();
|
|
System.out.printf("%-8s %-15s %-15s %-10s %-6s%n",
|
|
"N", "Defective ops", "Fixed ops", "Ratio", "PASS");
|
|
|
|
boolean allPass = true;
|
|
for (int n : sizes) {
|
|
// Create matching file lists (worst case: all files exist in DB)
|
|
List<String> filesOnDisk = new ArrayList<>();
|
|
List<String> songsInDb = new ArrayList<>();
|
|
for (int i = 0; i < n; i++) {
|
|
String path = "/music/artist/album/track" + String.format("%04d", i) + ".mp3";
|
|
filesOnDisk.add(path);
|
|
songsInDb.add(path);
|
|
}
|
|
// Shuffle DB order to avoid best-case
|
|
Collections.shuffle(songsInDb, new Random(42));
|
|
|
|
long defectiveOps = scanSubdirectoryDefective(filesOnDisk, songsInDb);
|
|
long fixedOps = scanSubdirectoryFixed(filesOnDisk, songsInDb);
|
|
double ratio = (double) defectiveOps / fixedOps;
|
|
boolean pass = ratio > 5.0;
|
|
allPass &= pass;
|
|
|
|
System.out.printf("%-8d %-15d %-15d %-10.1fx %-6s%n",
|
|
n, defectiveOps, fixedOps, ratio, pass ? "PASS" : "FAIL");
|
|
}
|
|
|
|
System.out.println();
|
|
System.out.println("Overall: " + (allPass ? "PASS" : "FAIL"));
|
|
System.exit(allPass ? 0 : 1);
|
|
}
|
|
}
|