diff --git a/defects/desmume-0001/patch/desmume-0001.patch b/defects/desmume-0001/patch/desmume-0001.patch new file mode 100644 index 000000000..0d63ba384 --- /dev/null +++ b/defects/desmume-0001/patch/desmume-0001.patch @@ -0,0 +1,95 @@ +# UNDF: UNDF-2026-000001058 +--- a/desmume/src/NDSSystem.cpp ++++ b/desmume/src/NDSSystem.cpp +@@ -1,5 +1,6 @@ + #include + #include ++#include + + // ... + +@@ -1955,6 +1955,14 @@ template + static /*donotinline*/ std::pair armInnerLoop( + const u64 nds_timer_base, const s32 s32next, s32 arm9, s32 arm7) + { ++ // DEFECT (removed): O(B) linear scan of breakPoints vector on every CPU instruction. ++ // With B breakpoints set, the NDS_ARM9 and NDS_ARM7 loops each scan the full vector ++ // per instruction, giving O(B * I) per second where I is the instruction rate ++ // (~66 MHz ARM9 + ~33 MHz ARM7). Fix: replace std::vector *breakPoints with ++ // std::unordered_set *breakPoints (O(1) lookup). ++ // ++ // In armcpu.h: change std::vector *breakPoints; ++ // to std::unordered_set *breakPoints; ++ // ++ // In armcpu.cpp armcpu_init(): change armcpu->breakPoints = new std::vector; ++ // to armcpu->breakPoints = new std::unordered_set; ++ // ++ // In disView.cpp: change push_back -> insert; erase(begin()+i) -> erase(value). + + s32 timer = minarmtime(arm9,arm7); + while(timer < s32next && !sequencer.reschedule && execute) + { + // breakpoint handling + #if defined(HOST_WINDOWS) && !defined(TARGET_INTERFACE) +- const std::vector *breakpointList9 = NDS_ARM9.breakPoints; +- for (int i = 0; i < breakpointList9->size(); ++i) { +- if (NDS_ARM9.instruct_adr == (*breakpointList9)[i] && !NDS_ARM9.debugStep) { ++ // PATCHED: O(1) hash-set lookup instead of O(B) linear scan. ++ if (NDS_ARM9.breakPoints->count(NDS_ARM9.instruct_adr) && !NDS_ARM9.debugStep) { + emu_paused = true; + paused = true; + execute = false; + // update debug display + PostMessageA(DisViewWnd[0], WM_COMMAND, IDC_DISASMSEEK, NDS_ARM9.instruct_adr); + InvalidateRect(DisViewWnd[0], NULL, FALSE); + return std::make_pair(arm9, arm7); +- } + } +- const std::vector *breakpointList7 = NDS_ARM7.breakPoints; +- for (int i = 0; i < breakpointList7->size(); ++i) { +- if (NDS_ARM7.instruct_adr == (*breakpointList7)[i] && !NDS_ARM7.debugStep) { ++ if (NDS_ARM7.breakPoints->count(NDS_ARM7.instruct_adr) && !NDS_ARM7.debugStep) { + emu_paused = true; + paused = true; + execute = false; + // update debug display + PostMessageA(DisViewWnd[1], WM_COMMAND, IDC_DISASMSEEK, NDS_ARM7.instruct_adr); + InvalidateRect(DisViewWnd[1], NULL, FALSE); + return std::make_pair(arm9, arm7); +- } + } + #endif //HOST_WINDOWS + +--- a/desmume/src/armcpu.h ++++ b/desmume/src/armcpu.h +@@ -313,7 +313,8 @@ typedef struct armcpu_t { + // ... +- std::vector *breakPoints; ++ // PATCHED: unordered_set gives O(1) lookup vs O(B) linear scan in armInnerLoop. ++ std::unordered_set *breakPoints; + // ... + +--- a/desmume/src/armcpu.cpp ++++ b/desmume/src/armcpu.cpp +@@ -149,7 +149,7 @@ void armcpu_init(armcpu_t *armcpu, u32 adr) +- armcpu->breakPoints = new std::vector; ++ armcpu->breakPoints = new std::unordered_set; + +--- a/desmume/src/frontend/windows/disView.cpp ++++ b/desmume/src/frontend/windows/disView.cpp +@@ -550,7 +550,7 @@ LRESULT CALLBACK DisViewWndProc(...) +- NDS_ARM7.breakPoints->push_back(adr); ++ NDS_ARM7.breakPoints->insert(adr); + ... +- if (DisView7->break_pos < NDS_ARM7.breakPoints->size()) { +- NDS_ARM7.breakPoints->erase(NDS_ARM7.breakPoints->begin() + DisView7->break_pos); ++ if (NDS_ARM7.breakPoints->count(adr)) { ++ NDS_ARM7.breakPoints->erase(adr); +@@ -883,7 +883,7 @@ LRESULT CALLBACK DisViewWndProc(...) +- NDS_ARM9.breakPoints->push_back(adr); ++ NDS_ARM9.breakPoints->insert(adr); + ... +- if (DisView9->break_pos < NDS_ARM9.breakPoints->size()) { +- NDS_ARM9.breakPoints->erase(NDS_ARM9.breakPoints->begin() + DisView9->break_pos); ++ if (NDS_ARM9.breakPoints->count(adr)) { ++ NDS_ARM9.breakPoints->erase(adr); diff --git a/defects/desmume-0001/test/desmume-0001-test.cpp b/defects/desmume-0001/test/desmume-0001-test.cpp new file mode 100644 index 000000000..d81dad942 --- /dev/null +++ b/defects/desmume-0001/test/desmume-0001-test.cpp @@ -0,0 +1,103 @@ +// desmume-0001-test.cpp +// CWE-407: armInnerLoop breakpoint scan O(B) per CPU instruction in NDSSystem.cpp +// +// The armInnerLoop in NDSSystem.cpp (Windows build) scans the full breakPoints +// std::vector on every ARM9 and ARM7 instruction. With B breakpoints, this +// is O(B) per instruction. The NDS ARM9 runs at ~66 MHz and ARM7 at ~33 MHz, +// so even a small number of breakpoints creates significant overhead during +// debugging sessions. +// +// Fix: replace std::vector *breakPoints with std::unordered_set +// for O(1) lookup. +// +// Complexity: O(B * I) defective vs O(I) patched where I = instruction count. +// Speedup at B=32 breakpoints over 10M instructions: ~32x. + +#include +#include +#include +#include +#include +#include +#include + +using u32 = uint32_t; + +// ---------- DEFECTIVE: std::vector linear scan ---------- +namespace Defective { + static bool checkBreakpoint(const std::vector& bpList, u32 addr) + { + for (size_t i = 0; i < bpList.size(); ++i) + { + if (addr == bpList[i]) + return true; + } + return false; + } +} + +// ---------- PATCHED: std::unordered_set O(1) lookup ---------- +namespace Patched { + static bool checkBreakpoint(const std::unordered_set& bpSet, u32 addr) + { + return bpSet.count(addr) != 0; + } +} + +int main() +{ + // Use B = 32 breakpoints (typical debug session ceiling). + const int B = 32; + // Simulate I = 10M instructions. + const int I = 10'000'000; + + // Populate breakpoints; last one is at a high address to maximize scan length. + std::vector bpVec; + std::unordered_set bpSet; + for (int i = 0; i < B; i++) + { + u32 addr = 0x02000000 + static_cast(i) * 0x1000; + bpVec.push_back(addr); + bpSet.insert(addr); + } + // One matching address (last breakpoint) and many non-matching addresses. + u32 hitAddr = bpVec.back(); + u32 missAddr = 0x01000000; // not in either collection + + // Correctness check. + assert(Defective::checkBreakpoint(bpVec, hitAddr) == true); + assert(Defective::checkBreakpoint(bpVec, missAddr) == false); + assert(Patched::checkBreakpoint(bpSet, hitAddr) == true); + assert(Patched::checkBreakpoint(bpSet, missAddr) == false); + printf("PASS correctness: hit/miss agree for B=%d\n", B); + + // Performance: simulate I instruction checks (mostly misses, occasional hits). + volatile bool sink = false; + + auto t0 = std::chrono::high_resolution_clock::now(); + for (int i = 0; i < I; i++) + { + u32 addr = (i % 100 == 0) ? hitAddr : missAddr; + sink ^= Defective::checkBreakpoint(bpVec, addr); + } + auto t1 = std::chrono::high_resolution_clock::now(); + double ms_defect = std::chrono::duration(t1 - t0).count(); + + auto t2 = std::chrono::high_resolution_clock::now(); + for (int i = 0; i < I; i++) + { + u32 addr = (i % 100 == 0) ? hitAddr : missAddr; + sink ^= Patched::checkBreakpoint(bpSet, addr); + } + auto t3 = std::chrono::high_resolution_clock::now(); + double ms_patch = std::chrono::duration(t3 - t2).count(); + + double ratio = ms_defect / ms_patch; + printf("Defective: %.2f ms Patched: %.2f ms Ratio: %.1fx (B=%d, I=%d)\n", + ms_defect, ms_patch, ratio, B, I); + + assert(ratio > 2.0 && "Patched should be at least 2x faster"); + printf("PASS performance: %.1fx speedup\n", ratio); + (void)sink; + return 0; +} diff --git a/defects/desmume-0001/test/test b/defects/desmume-0001/test/test new file mode 100755 index 000000000..b60178980 Binary files /dev/null and b/defects/desmume-0001/test/test differ