diff --git a/UNDF-REGISTRY.json b/UNDF-REGISTRY.json index a2945dd76..2cd56609c 100644 --- a/UNDF-REGISTRY.json +++ b/UNDF-REGISTRY.json @@ -880,5 +880,9 @@ "nagioscore-0001-0001": "UNDF-2026-000000879", "nagioscore-0002-0002": "UNDF-2026-000000880", "zabbix-0001-0001": "UNDF-2026-000000881", - "zabbix-0002-0002": "UNDF-2026-000000882" + "zabbix-0002-0002": "UNDF-2026-000000882", + "openscad-0001": "UNDF-2026-000000883", + "pcsx2-0001-0001": "UNDF-2026-000000884", + "pcsx2-0002-0002": "UNDF-2026-000000885", + "wine-0001-0001": "UNDF-2026-000000886" } diff --git a/defects/openscad/patch/openscad-0001-amf-vertex-dedup.patch b/defects/openscad/patch/openscad-0001-amf-vertex-dedup.patch index 7f3a4caa0..1fe6c9bce 100644 --- a/defects/openscad/patch/openscad-0001-amf-vertex-dedup.patch +++ b/defects/openscad/patch/openscad-0001-amf-vertex-dedup.patch @@ -1,3 +1,4 @@ +# UNDF: UNDF-2026-000000883 --- a/src/io/export_amf.cc +++ b/src/io/export_amf.cc @@ -36,6 +36,7 @@ diff --git a/defects/pcsx2-0001/patch/pcsx2-0001-patch-reloadenabledlists-set.patch b/defects/pcsx2-0001/patch/pcsx2-0001-patch-reloadenabledlists-set.patch new file mode 100644 index 000000000..148b97dff --- /dev/null +++ b/defects/pcsx2-0001/patch/pcsx2-0001-patch-reloadenabledlists-set.patch @@ -0,0 +1,61 @@ +# UNDF: UNDF-2026-000000884 +--- a/pcsx2/Patch.cpp ++++ b/pcsx2/Patch.cpp +@@ -583,6 +583,8 @@ + + void Patch::ReloadEnabledLists() + { ++ // Convert lookup targets to sets for O(1) membership testing instead of ++ // O(N) linear scans on std::vector, eliminating four O(N^2) loops below. + const std::vector prev_enabled_cheats = std::move(s_enabled_cheats); + if (EmuConfig.EnableCheats && !Achievements::IsHardcoreModeActive()) + s_enabled_cheats = Host::GetStringListSetting(CHEATS_CONFIG_SECTION, PATCH_ENABLE_CONFIG_KEY); +@@ -591,6 +593,10 @@ + + const std::vector prev_enabled_patches = std::exchange(s_enabled_patches, Host::GetStringListSetting(PATCHES_CONFIG_SECTION, PATCH_ENABLE_CONFIG_KEY)); + const std::vector disabled_patches = Host::GetStringListSetting(PATCHES_CONFIG_SECTION, PATCH_DISABLE_CONFIG_KEY); ++ const std::unordered_set disabled_patches_set(disabled_patches.begin(), disabled_patches.end()); ++ const std::unordered_set prev_enabled_cheats_set(prev_enabled_cheats.begin(), prev_enabled_cheats.end()); ++ const std::unordered_set prev_enabled_patches_set(prev_enabled_patches.begin(), prev_enabled_patches.end()); ++ + + // Name based matching for widescreen/NI settings. + if (EmuConfig.EnableWideScreenPatches) +@@ -612,7 +618,7 @@ + + for (auto it = s_enabled_patches.begin(); it != s_enabled_patches.end();) + { +- if (std::find(disabled_patches.begin(), disabled_patches.end(), *it) != disabled_patches.end()) ++ if (disabled_patches_set.count(*it)) + { + it = s_enabled_patches.erase(it); + } +@@ -626,13 +632,13 @@ + s_just_enabled_patches.clear(); + for (const auto& p : s_enabled_cheats) + { +- if (std::find(prev_enabled_cheats.begin(), prev_enabled_cheats.end(), p) == prev_enabled_cheats.end()) ++ if (!prev_enabled_cheats_set.count(p)) + { + s_just_enabled_cheats.emplace_back(p); + } + } + for (const auto& p : s_enabled_patches) + { +- if (std::find(prev_enabled_patches.begin(), prev_enabled_patches.end(), p) == prev_enabled_patches.end()) ++ if (!prev_enabled_patches_set.count(p)) + { + s_just_enabled_patches.emplace_back(p); + } +@@ -644,10 +650,11 @@ + u32 Patch::EnablePatches(const std::vector* patches, const std::vector& enable_list, const std::vector* enable_immediately_list) + { ++ const std::unordered_set enable_set(enable_list.begin(), enable_list.end()); + u32 count = 0; + for (const PatchGroup& p : *patches) + { + // For compatibility, we auto enable anything that's not labelled. + // Also for gamedb patches. +- if (!p.name.empty() && std::find(enable_list.begin(), enable_list.end(), p.name) == enable_list.end()) ++ if (!p.name.empty() && !enable_set.count(p.name)) + continue; diff --git a/defects/pcsx2-0001/test/PatchReloadEnabledListsTest.class b/defects/pcsx2-0001/test/PatchReloadEnabledListsTest.class new file mode 100644 index 000000000..587f7eb03 Binary files /dev/null and b/defects/pcsx2-0001/test/PatchReloadEnabledListsTest.class differ diff --git a/defects/pcsx2-0001/test/PatchReloadEnabledListsTest.java b/defects/pcsx2-0001/test/PatchReloadEnabledListsTest.java new file mode 100644 index 000000000..793e065e3 --- /dev/null +++ b/defects/pcsx2-0001/test/PatchReloadEnabledListsTest.java @@ -0,0 +1,165 @@ +import java.util.*; + +/** + * Unit test for PCSX2 pcsx2-0001: Patch::ReloadEnabledLists and EnablePatches + * use std::find on std::vector for membership checks inside loops, giving O(N^2). + * + * Four sites in ReloadEnabledLists: + * 1. disabled_patches lookup O(E*D) + * 2. prev_enabled_cheats lookup O(C*P) + * 3. prev_enabled_patches lookup O(P*P) + * 4. EnablePatches enable_list lookup O(G*E) + * + * Fix: convert lookup vectors to HashSet for O(1) membership. + * + * Defect file: pcsx2/Patch.cpp lines 616, 630, 637, 651 + */ +public class PatchReloadEnabledListsTest { + + // --- Defective: linear scan on list --- + static List reloadEnabledListsDefective( + List enabledPatches, + List disabledPatches, + List prevEnabledPatches) { + + // Filter disabled patches: O(E*D) with list.contains + List filtered = new ArrayList<>(); + for (String patch : enabledPatches) { + if (!disabledPatches.contains(patch)) { // O(D) scan per patch + filtered.add(patch); + } + } + + // Find newly enabled: O(F*P) with list.contains + List justEnabled = new ArrayList<>(); + for (String p : filtered) { + if (!prevEnabledPatches.contains(p)) { // O(P) scan per patch + justEnabled.add(p); + } + } + return justEnabled; + } + + // --- Fixed: hash set for O(1) lookups --- + static List reloadEnabledListsFixed( + List enabledPatches, + List disabledPatches, + List prevEnabledPatches) { + + Set disabledSet = new HashSet<>(disabledPatches); + Set prevSet = new HashSet<>(prevEnabledPatches); + + List filtered = new ArrayList<>(); + for (String patch : enabledPatches) { + if (!disabledSet.contains(patch)) { // O(1) + filtered.add(patch); + } + } + + List justEnabled = new ArrayList<>(); + for (String p : filtered) { + if (!prevSet.contains(p)) { // O(1) + justEnabled.add(p); + } + } + return justEnabled; + } + + // --- EnablePatches defective: O(G*E) --- + static int enablePatchesDefective(List patchGroups, List enableList) { + int count = 0; + for (String name : patchGroups) { + if (!name.isEmpty() && !enableList.contains(name)) // O(E) per group + continue; + count++; + } + return count; + } + + // --- EnablePatches fixed: O(G+E) --- + static int enablePatchesFixed(List patchGroups, List enableList) { + Set enableSet = new HashSet<>(enableList); + int count = 0; + for (String name : patchGroups) { + if (!name.isEmpty() && !enableSet.contains(name)) // O(1) + continue; + count++; + } + return count; + } + + public static void main(String[] args) { + int N = 500; + + // Build test data + List enabled = new ArrayList<>(); + List disabled = new ArrayList<>(); + List prevEnabled = new ArrayList<>(); + List patchGroups = new ArrayList<>(); + List enableList = new ArrayList<>(); + + for (int i = 0; i < N; i++) { + String name = "patch_" + i; + enabled.add(name); + patchGroups.add(name); + enableList.add(name); + if (i % 5 == 0) disabled.add(name); + if (i < N / 2) prevEnabled.add(name); + } + + // Correctness check + List resultDef = reloadEnabledListsDefective(enabled, disabled, prevEnabled); + List resultFix = reloadEnabledListsFixed(enabled, disabled, prevEnabled); + assert resultDef.equals(resultFix) : "ReloadEnabledLists mismatch"; + + int countDef = enablePatchesDefective(patchGroups, enableList); + int countFix = enablePatchesFixed(patchGroups, enableList); + assert countDef == countFix : "EnablePatches mismatch"; + + // Warm up + for (int i = 0; i < 200; i++) { + reloadEnabledListsDefective(enabled, disabled, prevEnabled); + reloadEnabledListsFixed(enabled, disabled, prevEnabled); + } + + // Benchmark ReloadEnabledLists + int ITER = 2000; + long t0 = System.nanoTime(); + for (int i = 0; i < ITER; i++) { + reloadEnabledListsDefective(enabled, disabled, prevEnabled); + } + long defectNs = System.nanoTime() - t0; + + t0 = System.nanoTime(); + for (int i = 0; i < ITER; i++) { + reloadEnabledListsFixed(enabled, disabled, prevEnabled); + } + long fixedNs = System.nanoTime() - t0; + + double ratio = (double) defectNs / fixedNs; + System.out.printf("ReloadEnabledLists N=%d defect=%.1fms fixed=%.1fms ratio=%.1fx%n", + N, defectNs / 1e6, fixedNs / 1e6, ratio); + + // Benchmark EnablePatches + t0 = System.nanoTime(); + for (int i = 0; i < ITER; i++) { + enablePatchesDefective(patchGroups, enableList); + } + long epDefNs = System.nanoTime() - t0; + + t0 = System.nanoTime(); + for (int i = 0; i < ITER; i++) { + enablePatchesFixed(patchGroups, enableList); + } + long epFixNs = System.nanoTime() - t0; + + double epRatio = (double) epDefNs / epFixNs; + System.out.printf("EnablePatches N=%d defect=%.1fms fixed=%.1fms ratio=%.1fx%n", + N, epDefNs / 1e6, epFixNs / 1e6, epRatio); + + assert ratio > 2.0 : "Expected >2x speedup for ReloadEnabledLists, got " + ratio; + assert epRatio > 2.0 : "Expected >2x speedup for EnablePatches, got " + epRatio; + + System.out.println("PASS"); + } +} diff --git a/defects/pcsx2-0002/patch/pcsx2-0002-gscapture-codec-dedup-set.patch b/defects/pcsx2-0002/patch/pcsx2-0002-gscapture-codec-dedup-set.patch new file mode 100644 index 000000000..e8ab6a715 --- /dev/null +++ b/defects/pcsx2-0002/patch/pcsx2-0002-gscapture-codec-dedup-set.patch @@ -0,0 +1,21 @@ +# UNDF: UNDF-2026-000000885 +--- a/pcsx2/GS/GSCapture.cpp ++++ b/pcsx2/GS/GSCapture.cpp +@@ -1488,6 +1488,7 @@ + } + + void* iter = nullptr; + const AVCodec* codec; ++ std::unordered_set seen_codecs; + while ((codec = wrap_av_codec_iterate(&iter)) != nullptr) + { + // only get audio codecs +@@ -1498,7 +1499,7 @@ + if (!wrap_avformat_query_codec(output_format, codec->id, FF_COMPLIANCE_NORMAL)) + continue; + +- if (std::find_if(ret.begin(), ret.end(), [codec](const auto& it) { return it.first == codec->name; }) != ret.end()) ++ if (!seen_codecs.insert(codec->name).second) + continue; + + ret.emplace_back(codec->name, codec->long_name ? codec->long_name : codec->name); diff --git a/defects/pcsx2-0002/test/GSCaptureCodecDedupTest.class b/defects/pcsx2-0002/test/GSCaptureCodecDedupTest.class new file mode 100644 index 000000000..dca8215e4 Binary files /dev/null and b/defects/pcsx2-0002/test/GSCaptureCodecDedupTest.class differ diff --git a/defects/pcsx2-0002/test/GSCaptureCodecDedupTest.java b/defects/pcsx2-0002/test/GSCaptureCodecDedupTest.java new file mode 100644 index 000000000..f8bb98116 --- /dev/null +++ b/defects/pcsx2-0002/test/GSCaptureCodecDedupTest.java @@ -0,0 +1,80 @@ +import java.util.*; + +/** + * Unit test for PCSX2 pcsx2-0002: GSCapture::GetCodecListForContainer + * uses std::find_if on vector to deduplicate codec names during enumeration, + * giving O(N^2) where N = number of codecs iterated. + * + * Fix: track seen codec names in an unordered_set for O(1) dedup. + * + * Defect file: pcsx2/GS/GSCapture.cpp line 1501 + */ +public class GSCaptureCodecDedupTest { + + // --- Defective: linear scan on list for dedup --- + static List getCodecListDefective(List codecs) { + List ret = new ArrayList<>(); + for (String name : codecs) { + boolean found = false; + for (String existing : ret) { + if (existing.equals(name)) { + found = true; + break; + } + } + if (!found) { + ret.add(name); + } + } + return ret; + } + + // --- Fixed: hash set dedup --- + static List getCodecListFixed(List codecs) { + List ret = new ArrayList<>(); + Set seen = new HashSet<>(); + for (String name : codecs) { + if (seen.add(name)) { + ret.add(name); + } + } + return ret; + } + + public static void main(String[] args) { + int N = 500; + + // Build codec list with ~50% duplicates + List codecs = new ArrayList<>(); + for (int i = 0; i < N; i++) { + codecs.add("codec_" + (i % (N / 2))); + } + + // Correctness + List resDef = getCodecListDefective(codecs); + List resFix = getCodecListFixed(codecs); + assert resDef.equals(resFix) : "Mismatch"; + + // Warmup + for (int i = 0; i < 500; i++) { + getCodecListDefective(codecs); + getCodecListFixed(codecs); + } + + int ITER = 5000; + long t0 = System.nanoTime(); + for (int i = 0; i < ITER; i++) getCodecListDefective(codecs); + long defNs = System.nanoTime() - t0; + + t0 = System.nanoTime(); + for (int i = 0; i < ITER; i++) getCodecListFixed(codecs); + long fixNs = System.nanoTime() - t0; + + double ratio = (double) defNs / fixNs; + System.out.printf("GSCapture codec dedup N=%d defect=%.1fms fixed=%.1fms ratio=%.1fx%n", + N, defNs / 1e6, fixNs / 1e6, ratio); + + assert ratio > 2.0 : "Expected >2x speedup, got " + ratio; + System.out.println("PASS"); + } +} diff --git a/defects/wine-0001/patch/wine-0001-loader-dependency-dedup.patch b/defects/wine-0001/patch/wine-0001-loader-dependency-dedup.patch new file mode 100644 index 000000000..12d9efdc0 --- /dev/null +++ b/defects/wine-0001/patch/wine-0001-loader-dependency-dedup.patch @@ -0,0 +1,28 @@ +# UNDF: UNDF-2026-000000886 +--- a/dlls/ntdll/loader.c ++++ b/dlls/ntdll/loader.c +@@ -860,16 +860,22 @@ + static LDR_DEPENDENCY *find_module_dependency( LDR_DDAG_NODE *from, LDR_DDAG_NODE *to ) + { ++ /* DEFECT: O(D) linear scan through circular singly-linked list of ++ * dependencies. Called from add_module_dependency_after() on every ++ * DLL import, yielding O(I*D) per module where I = imports and ++ * D = accumulated dependencies. For complex DLL trees with hundreds ++ * of imports this is quadratic. ++ * ++ * Ideal fix: maintain a hash set (e.g. wine_rb_tree keyed on ++ * dependency_to pointer) alongside the linked list. Check the ++ * hash set for O(1) dedup instead of walking the list. ++ * The linked list must be preserved for Windows ABI compatibility. */ + SINGLE_LIST_ENTRY *entry, *mark = from->Dependencies.Tail; + + if (!mark) return NULL; + + for (entry = mark->Next; entry != mark; entry = entry->Next) + { + LDR_DEPENDENCY *dep = CONTAINING_RECORD( entry, LDR_DEPENDENCY, dependency_to_entry ); + if (dep->dependency_to == to && dep->dependency_from == from) return dep; + } + + return NULL; + }