java-topology/whitepaper/outreach/clementine.md
russell@unturf.com 7e7ec2c3d3 feat: add 10 outreach docs (20 defects) for 2-patch projects
amarok, arrow, audacity, cargo, clementine, composer, dask,
deluge, dosbox-x, dragonfly. All CWE-407.
2026-04-14 13:50:33 -04:00

4.5 KiB
Raw Blame History

Clementine — CWE-407 Disclosure Brief

2026-04-13 · Patch available — awaiting upstream merge

Finding

Two O(n²) defects in Clementine's library scanner and network remote song sender. Both patched. Patches ready for upstream review. The first defect site contains a // TODO: Make this faster comment acknowledging the performance problem.

The Defects

clementine-0001 (PATCHED — HIGH): src/library/librarywatcher.cpp:358

// In ScanSubdirectory() — fires during every library scan:
// TODO: Make this faster
Song matching_song;
if (FindSongByPath(songs_in_db, file, &matching_song)) { ... }
// FindSongByPath is O(S) linear scan per file

// Also at line 437:
if (!files_on_disk.contains(song.url().toLocalFile())) { ... }
// QStringList::contains() is O(F) per song

Two O(N²) patterns in the same function: FindSongByPath linearly scans songs_in_db for every file on disk — O(F×S). Then files_on_disk.contains() linearly scans the QStringList for every song in the database — O(S×F). Both fire during every library scan.

clementine-0002 (PATCHED — MEDIUM): src/networkremote/songsender.cpp:321

// In SendAlbum() — fires when remote client requests album download:
for (Song s : album) {
    DownloadItem item(s, album.indexOf(s) + 1, album.size());  // O(N) per song
    download_queue_.append(item);
}

QList::indexOf(s) is O(N) per song in three methods: SendAlbum(), SendPlaylist(), and SendUrls(). SendPlaylist() also calls requested_ids.contains(s.id()) with O(R) per song. Total cost across all three: O(N²).

Complexity Proof

clementine-0001: At F=S=500 (typical music directory):

  • Defective: 500 × 500 = 250,000 comparisons (FindSongByPath) + 500 × 500 = 250,000 (contains)
  • Fixed: 500 × O(1) = 500 lookups each (QHash + QSet)
  • ~250× op reduction per library scan.

clementine-0002: At N=500 songs:

  • Defective: 500 × 250 (avg) = 125,000 comparisons per send operation
  • Fixed: 500 × O(1) = 500 (integer counter replaces indexOf)
  • ~250× op reduction per remote download.

Impact

Clementine is a popular cross-platform music player forked from Amarok, with a large user base on Linux, macOS, and Windows. clementine-0001 fires on every library scan — initial imports, directory watches, and manual rescans all trigger ScanSubdirectory(). Users with large music collections (thousands of files per directory) experience slow scans. The source code itself acknowledges the problem with a TODO comment.

clementine-0002 fires when Clementine's Android remote client requests album, playlist, or URL downloads. Large playlists trigger quadratic indexOf lookups for every song sent.

The Fix

clementine-0001: Build QHash<QString, Song> for path lookup and QSet<QString> for file-on-disk membership:

// Before
if (FindSongByPath(songs_in_db, file, &matching_song)) { ... }
if (!files_on_disk.contains(song.url().toLocalFile())) { ... }

// After
// CWE-407 fix: QHash for O(1) song-by-path lookup (was O(S) linear scan per file).
QHash<QString, Song> songs_by_path;
for (const Song& song : songs_in_db) songs_by_path.insert(song.url().toLocalFile(), song);
auto it = songs_by_path.find(file);

// CWE-407 fix: QSet for O(1) contains instead of O(F) QStringList scan.
QSet<QString> files_on_disk_set;
if (!files_on_disk_set.contains(song.url().toLocalFile())) { ... }

clementine-0002: Replace indexOf with integer counter; convert requested_ids to QSet:

// Before
DownloadItem item(s, album.indexOf(s) + 1, album.size());

// After
// CWE-407 fix: counter replaces O(N) indexOf.
int pos = 0;
for (const Song& s : album) {
    DownloadItem item(s, ++pos, album.size());
}

Patch

Fix available: defects/clementine/patch/clementine-0001-librarywatcher-scan-quadratic.patch and defects/clementine/patch/clementine-0002-songsender-indexof-quadratic.patch

Two-file patch across librarywatcher.cpp and songsender.cpp.

clementine-0001: ~250× speedup at F=S=500. clementine-0002: ~250× speedup at N=500.

What We Ask

A patch is ready for review.

  1. Confirm receipt and assign an issue reference (clementine-player/Clementine).
  2. Assess severity — clementine-0001 fires on every library scan and the source code acknowledges the performance problem with a TODO comment.
  3. Coordinate a disclosure date — we are targeting 90 days from first contact.
  4. We will credit the Clementine team in the public disclosure. Preferred acknowledgment format welcome.

Contact: see cover email. This brief is confidential until coordinated disclosure.