# UNDF: UNDF-2026-000001135 # UNDF: UNDF-2026-XXXXXXXXX --- a/src/cpu/MSXCPUInterface.hh +++ b/src/cpu/MSXCPUInterface.hh @@ -260,6 +260,11 @@ public: using BreakPoints = std::vector; [[nodiscard]] static BreakPoints& getBreakPoints() { return breakPoints; } + // CWE-407 fix: address index for O(1) breakpoint lookup per PC. + // Maps fixed PC address -> list of BreakPoint IDs at that address. + // Dynamic breakpoints (address == nullopt) are not indexed here. + using BreakIndex = std::unordered_map>; + void setCondition(DebugCondition cond); void removeCondition(const DebugCondition& cond); void removeCondition(unsigned id); @@ -460,6 +465,7 @@ private: static inline BreakPoints breakPoints; // unsorted + static inline BreakIndex breakIndex; // CWE-407: PC -> [bp-id,...] index WatchPoints watchPoints; static inline Conditions conditions; --- a/src/cpu/MSXCPUInterface.cc +++ b/src/cpu/MSXCPUInterface.cc @@ -869,6 +869,10 @@ void MSXCPUInterface::insertBreakPoint(BreakPoint bp) { cliComm.update(CliComm::UpdateType::DEBUG_UPDT, bp.getIdStr(), "add"); + // CWE-407: maintain address index for O(1) per-PC lookup. + if (auto addr = bp.getAddress()) { + breakIndex[*addr].push_back(bp.getId()); + } breakPoints.push_back(std::move(bp)); } @@ -876,6 +880,14 @@ void MSXCPUInterface::removeBreakPoint(const BreakPoint& bp) { cliComm.update(CliComm::UpdateType::DEBUG_UPDT, bp.getIdStr(), "remove"); + // CWE-407: remove from address index. + if (auto addr = bp.getAddress()) { + auto& ids = breakIndex[*addr]; + ids.erase(std::ranges::find(ids, bp.getId())); + if (ids.empty()) breakIndex.erase(*addr); + } breakPoints.erase(find_unguarded(breakPoints, &bp, [](const BreakPoint& i) { return &i; })); } @@ -883,7 +895,7 @@ void MSXCPUInterface::removeBreakPoint(unsigned id) { if (auto it = std::ranges::find(breakPoints, id, &BreakPoint::getId); it != breakPoints.end()) { cliComm.update(CliComm::UpdateType::DEBUG_UPDT, it->getIdStr(), "remove"); + if (auto addr = it->getAddress()) { + auto& ids = breakIndex[*addr]; + ids.erase(std::ranges::find(ids, id)); + if (ids.empty()) breakIndex.erase(*addr); + } breakPoints.erase(it); } } @@ -892,12 +906,30 @@ bool MSXCPUInterface::checkBreakPoints(unsigned pc) { // create copy for the case that breakpoint/condition removes itself // - avoids iterating over a changing collection - // CWE-407 DEFECT: O(B) scan over ALL breakpoints per Z80 instruction. - // With B breakpoints active, every instruction pays the full scan cost - // regardless of PC value. Fix: use breakIndex for O(k) lookup where - // k = breakpoints at this exact address (almost always 0 or 1). std::vector bpCopy; - for (const auto& bp : breakPoints) { - if (bp.isEnabled() && bp.getAddress() == pc) bpCopy.push_back(bp); + // CWE-407 FIX: O(k) lookup via address index instead of O(B) full scan. + // k = breakpoints set at exactly this PC, usually 0. + if (auto it = breakIndex.find(uint16_t(pc)); it != breakIndex.end()) { + for (unsigned bpId : it->second) { + if (auto bpIt = std::ranges::find(breakPoints, bpId, &BreakPoint::getId); + bpIt != breakPoints.end() && bpIt->isEnabled()) { + bpCopy.push_back(*bpIt); + } + } } + // Dynamic breakpoints (address == nullopt) still need full scan, but + // these are uncommon; typical usage has zero dynamic breakpoints. + for (const auto& bp : breakPoints) { + if (bp.isEnabled() && !bp.getAddress()) bpCopy.push_back(bp); + } std::vector condCopy; for (const auto& cond : conditions) { if (cond.isEnabled()) condCopy.push_back(cond);