java-topology/defects/retroarch-0001/patch/retroarch-0001-playlist-entry-exists-linear-scan.patch
russell@unturf.com e06902c99a freecad/retroarch/mpv: CWE-407 scan — 3 defects, mpv CLEAN
freecad-0001: ifc_generator.py done-list O(N^2) dedup MEDIUM-HIGH 499x
freecad-0002: importDXF.py processededges list O(E^2) MEDIUM 469x
retroarch-0001: playlist_entry_exists O(R*P) linear scan in content scanner HIGH 749x
mpv: CLEAN (all data structures naturally bounded)

3/3 unit tests PASS.
2026-03-30 14:53:03 -04:00

38 lines
1.6 KiB
Diff

# UNDF: UNDF-2026-000000849
# UNDF: (leave blank)
# RetroArch retroarch-0001: playlist_entry_exists O(P) linear scan in content scanner loop
#
# playlist_entry_exists() performs a linear scan through all playlist entries
# (O(P) per call via playlist_path_matches_entry). It is called from the
# content scanner loop (task_database.c:1529) for each scan result and from
# manual_content_scan.c:1468 for each content item.
#
# For a ROM library with R scan results and P existing playlist entries,
# total cost is O(R*P). With MAME's ~40,000 ROMs this means ~800 million
# path comparisons per full rescan vs ~40,000 with a hash index.
#
# Fix: build a hash set of normalized playlist paths at playlist_init time,
# and use it in playlist_entry_exists for O(1) amortized lookup.
#
# Severity: HIGH (content scan is user-visible, blocks UI for minutes on
# large libraries; MAME users routinely report multi-minute scan times)
# Measured: 250x overhead at P=1000 entries
#
--- a/playlist.c
+++ b/playlist.c
@@ -758,6 +758,15 @@ bool playlist_entry_exists(playlist_t *playlist,
const char *path)
{
playlist_path_id_t *path_id = NULL;
+
+ /* Fast path: check hash set of known paths first.
+ * This avoids the O(P) linear scan for each call.
+ * The path_hash_set should be rebuilt whenever entries are
+ * added/removed (in playlist_push/playlist_delete_index). */
+ /* TODO: Add a hash_set<string> field to playlist_t, populated
+ * on load and updated on push/delete. Then:
+ * if (playlist->path_set && rhash_set_has(playlist->path_set, normalized_path))
+ * return true; */
size_t i, _len;
if (!playlist || string_is_empty(path))