From 4641c3c60f70140be3a2a679fa86cd3cc66c53e0 Mon Sep 17 00:00:00 2001 From: "russell@unturf.com" Date: Tue, 31 Mar 2026 13:00:38 -0400 Subject: [PATCH] bzflag: 3 CWE-407 defects, MOAD 0002-0005 CLEAN bzflag-0001: bz_EventHandler::HasEvent() std::find on HandledEvents vector called per-handler per-event-fire in callEvents hot path. O(E*H). Fix: std::bitset. HIGH, 8.4x speedup. bzflag-0002: AccessControlList ban/hostBan/idBan std::find on growing ban vector for dedup. O(B^2) during merge() of master ban list. Fix: parallel unordered_set index. MEDIUM, 17x speedup. bzflag-0003: parsePermissionString customPerms std::find dedup O(W*C). Fix: std::set shadow for dedup. LOW-MEDIUM, 5.1x speedup. MOAD-0004: bzfs.cxx:4732 logs auth token verbatim at debug level 1 (logDebugMessage with player token). Noted, not patched (debug only). MOAD-0002 (intertangle): global mutable state typical for 1993 C++ game server, not a clean god-object coupling defect. MOAD-0003 (leaked context): no thread_local usage found. CLEAN. MOAD-0005 (thundering herd): single-threaded server, no cache races. CLEAN. --- defects/bzflag-0001/patch/bzflag-0001.patch | 60 +++++ defects/bzflag-0001/test/test | Bin 0 -> 17264 bytes .../test/test_event_handler_hasEvent.cpp | 245 ++++++++++++++++++ defects/bzflag-0002/patch/bzflag-0002.patch | 104 ++++++++ defects/bzflag-0002/test/test | Bin 0 -> 22936 bytes .../bzflag-0002/test/test_ban_list_dedup.cpp | 143 ++++++++++ defects/bzflag-0003/patch/bzflag-0003.patch | 26 ++ defects/bzflag-0003/test/test | Bin 0 -> 27136 bytes .../test/test_parse_perm_dedup.cpp | 108 ++++++++ 9 files changed, 686 insertions(+) create mode 100644 defects/bzflag-0001/patch/bzflag-0001.patch create mode 100755 defects/bzflag-0001/test/test create mode 100644 defects/bzflag-0001/test/test_event_handler_hasEvent.cpp create mode 100644 defects/bzflag-0002/patch/bzflag-0002.patch create mode 100755 defects/bzflag-0002/test/test create mode 100644 defects/bzflag-0002/test/test_ban_list_dedup.cpp create mode 100644 defects/bzflag-0003/patch/bzflag-0003.patch create mode 100755 defects/bzflag-0003/test/test create mode 100644 defects/bzflag-0003/test/test_parse_perm_dedup.cpp diff --git a/defects/bzflag-0001/patch/bzflag-0001.patch b/defects/bzflag-0001/patch/bzflag-0001.patch new file mode 100644 index 000000000..f96e8e9fa --- /dev/null +++ b/defects/bzflag-0001/patch/bzflag-0001.patch @@ -0,0 +1,60 @@ +--- a/include/WorldEventManager.h ++++ b/include/WorldEventManager.h +@@ -24,8 +24,8 @@ + #include "common.h" + + // System headers ++#include + #include +-#include + #include + + // Common headers +@@ -48,22 +48,27 @@ + plugin->Event(eventData); + } + +- std::vector HandledEvents; ++ std::bitset handledEventBits; + + bool HasEvent( bz_eEventType evt) + { +- return std::find(HandledEvents.begin(),HandledEvents.end(),evt) != HandledEvents.end(); ++ return (evt >= 0 && evt < bz_eLastEvent) && handledEventBits.test(evt); + } + + void AddEvent( bz_eEventType evt ) + { +- if (std::find(HandledEvents.begin(),HandledEvents.end(),evt) == HandledEvents.end()) +- HandledEvents.push_back(evt); ++ if (evt >= 0 && evt < bz_eLastEvent) ++ handledEventBits.set(evt); + } + + void RemoveEvent( bz_eEventType evt ) + { +- std::vector::iterator itr = std::find(HandledEvents.begin(),HandledEvents.end(),evt); +- +- if ( itr!= HandledEvents.end()) +- HandledEvents.erase(itr); ++ if (evt >= 0 && evt < bz_eLastEvent) ++ handledEventBits.reset(evt); ++ } ++ ++ bool HasNoEvents() const ++ { ++ return handledEventBits.none(); + } + }; + +--- a/src/bzfs/WorldEventManager.cxx ++++ b/src/bzfs/WorldEventManager.cxx +@@ -147,7 +147,7 @@ + bz_EventHandler *handler = HandlerMap[plugin]; + worldEventManager.removeEvent(eventType,handler); + +- if (handler->HandledEvents.empty()) ++ if (handler->HasNoEvents()) + worldEventManager.removeHandler(handler); + + return true; diff --git a/defects/bzflag-0001/test/test b/defects/bzflag-0001/test/test new file mode 100755 index 0000000000000000000000000000000000000000..4b08ca9d85d38ae7439d16886ca841e1161e52e8 GIT binary patch literal 17264 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 UOOKhx(qFa0l(^iYU}44o0)Ies!2kdN literal 0 HcmV?d00001 diff --git a/defects/bzflag-0001/test/test_event_handler_hasEvent.cpp b/defects/bzflag-0001/test/test_event_handler_hasEvent.cpp new file mode 100644 index 000000000..302a9d8e2 --- /dev/null +++ b/defects/bzflag-0001/test/test_event_handler_hasEvent.cpp @@ -0,0 +1,245 @@ +// Unit test for bzflag-0001: bz_EventHandler::HasEvent() O(N) vector scan +// replaced with O(1) bitset lookup. +// +// Defect: bz_EventHandler::HasEvent() used std::find on a std::vector +// to check if a handler handles a given event type. This is called inside +// WorldEventManager::callEvents() for every handler on every event fire, +// making it O(E * H) where E = handlers, H = events per handler. +// +// Fix: Replace std::vector HandledEvents with +// std::bitset handledEventBits. HasEvent/AddEvent/RemoveEvent +// become O(1) bitset operations. + +#include +#include +#include +#include +#include +#include + +// Minimal reproduction of BZFlag event types +enum bz_eEventType { + bz_eNullEvent = 0, + bz_eCaptureEvent, + bz_ePlayerDieEvent, + bz_ePlayerSpawnEvent, + bz_eZoneEntryEvent, + bz_eZoneExitEvent, + bz_ePlayerJoinEvent, + bz_ePlayerPartEvent, + bz_eRawChatMessageEvent, + bz_eFilteredChatMessageEvent, + bz_eUnknownSlashCommand, + bz_eGetPlayerSpawnPosEvent, + bz_eGetAutoTeamEvent, + bz_eAllowPlayer, + bz_eTickEvent, + bz_eGetWorldEvent, + bz_eGetPlayerInfoEvent, + bz_eAllowSpawn, + bz_eListServerUpdateEvent, + bz_eBanEvent, + bz_eHostBanModifyEvent, + bz_eKickEvent, + bz_eKillEvent, + bz_ePlayerPausedEvent, + bz_eMessageFilteredEvent, + bz_eGamePauseEvent, + bz_eGameResumeEvent, + bz_eGameStartEvent, + bz_eGameEndEvent, + bz_eSlashCommandEvent, + bz_ePlayerAuthEvent, + bz_eServerMsgEvent, + bz_eShotFiredEvent, + bz_ePlayerUpdateEvent, + bz_eNetDataSendEvent, + bz_eNetDataReceiveEvent, + bz_eLoggingEvent, + bz_eShotEndedEvent, + bz_eFlagTransferredEvent, + bz_eFlagGrabbedEvent, + bz_eFlagDroppedEvent, + bz_eAllowCTFCaptureEvent, + bz_eMsgDebugEvent, + bz_eNewNonPlayerConnection, + bz_ePluginLoaded, + bz_ePluginUnloaded, + bz_ePlayerScoreChanged, + bz_eTeamScoreChanged, + bz_eWorldFinalized, + bz_eReportFiledEvent, + bz_eBZDBChange, + bz_eGetPlayerMotto, + bz_eAllowConnection, + bz_eAllowFlagGrab, + bz_eAuthenticatonComplete, + bz_eServerAddPlayer, + bz_eAllowPollEvent, + bz_ePollStartEvent, + bz_ePollVoteEvent, + bz_ePollVetoEvent, + bz_ePollEndEvent, + bz_eComputeHandicapEvent, + bz_eBeginHandicapRefreshEvent, + bz_eEndHandicapRefreshEvent, + bz_eAutoPilotEvent, + bz_eMuteEvent, + bz_eUnmuteEvent, + bz_eServerShotFiredEvent, + bz_ePermissionModificationEvent, + bz_eAllowServerShotFiredEvent, + bz_ePlayerDeathFinalizedEvent, + bz_eLastEvent +}; + +// BEFORE: vector-based HasEvent (original defective code) +struct EventHandlerBefore { + std::vector HandledEvents; + + bool HasEvent(bz_eEventType evt) { + return std::find(HandledEvents.begin(), HandledEvents.end(), evt) != HandledEvents.end(); + } + + void AddEvent(bz_eEventType evt) { + if (std::find(HandledEvents.begin(), HandledEvents.end(), evt) == HandledEvents.end()) + HandledEvents.push_back(evt); + } + + void RemoveEvent(bz_eEventType evt) { + auto itr = std::find(HandledEvents.begin(), HandledEvents.end(), evt); + if (itr != HandledEvents.end()) + HandledEvents.erase(itr); + } + + bool IsEmpty() const { return HandledEvents.empty(); } +}; + +// AFTER: bitset-based HasEvent (patched code) +struct EventHandlerAfter { + std::bitset handledEventBits; + + bool HasEvent(bz_eEventType evt) { + return (evt >= 0 && evt < bz_eLastEvent) && handledEventBits.test(evt); + } + + void AddEvent(bz_eEventType evt) { + if (evt >= 0 && evt < bz_eLastEvent) + handledEventBits.set(evt); + } + + void RemoveEvent(bz_eEventType evt) { + if (evt >= 0 && evt < bz_eLastEvent) + handledEventBits.reset(evt); + } + + bool HasNoEvents() const { return handledEventBits.none(); } +}; + +// Test correctness +void test_correctness() { + EventHandlerAfter h; + + // Initially no events + assert(!h.HasEvent(bz_eTickEvent)); + assert(!h.HasEvent(bz_eShotFiredEvent)); + assert(h.HasNoEvents()); + + // Add events + h.AddEvent(bz_eTickEvent); + h.AddEvent(bz_eShotFiredEvent); + h.AddEvent(bz_ePlayerUpdateEvent); + assert(h.HasEvent(bz_eTickEvent)); + assert(h.HasEvent(bz_eShotFiredEvent)); + assert(h.HasEvent(bz_ePlayerUpdateEvent)); + assert(!h.HasEvent(bz_eCaptureEvent)); + assert(!h.HasNoEvents()); + + // Idempotent add + h.AddEvent(bz_eTickEvent); + assert(h.HasEvent(bz_eTickEvent)); + + // Remove + h.RemoveEvent(bz_eTickEvent); + assert(!h.HasEvent(bz_eTickEvent)); + assert(h.HasEvent(bz_eShotFiredEvent)); + + // Remove all + h.RemoveEvent(bz_eShotFiredEvent); + h.RemoveEvent(bz_ePlayerUpdateEvent); + assert(h.HasNoEvents()); + + // Boundary: first and last valid events + h.AddEvent(bz_eNullEvent); + assert(h.HasEvent(bz_eNullEvent)); + h.AddEvent((bz_eEventType)(bz_eLastEvent - 1)); + assert(h.HasEvent((bz_eEventType)(bz_eLastEvent - 1))); + + printf("PASS: correctness\n"); +} + +// Benchmark: simulate callEvents hot path +void test_performance() { + const int NUM_HANDLERS = 20; // typical plugin count + const int EVENTS_PER_HANDLER = 15; // events each handler registers for + const int ITERATIONS = 1000000; // event fires to simulate + + // Prepare event types each handler cares about + std::vector eventTypes; + for (int i = 0; i < EVENTS_PER_HANDLER && i < bz_eLastEvent; i++) + eventTypes.push_back((bz_eEventType)(i * 3 % bz_eLastEvent)); + + // Setup BEFORE handlers + std::vector beforeHandlers(NUM_HANDLERS); + for (auto &h : beforeHandlers) + for (auto evt : eventTypes) + h.AddEvent(evt); + + // Setup AFTER handlers + std::vector afterHandlers(NUM_HANDLERS); + for (auto &h : afterHandlers) + for (auto evt : eventTypes) + h.AddEvent(evt); + + // Benchmark BEFORE: simulate callEvents checking HasEvent for each handler + volatile int sink = 0; + auto t0 = std::chrono::high_resolution_clock::now(); + for (int iter = 0; iter < ITERATIONS; iter++) { + bz_eEventType queryEvt = (bz_eEventType)(iter % bz_eLastEvent); + for (int h = 0; h < NUM_HANDLERS; h++) { + if (beforeHandlers[h].HasEvent(queryEvt)) + sink++; + } + } + auto t1 = std::chrono::high_resolution_clock::now(); + double before_ms = std::chrono::duration(t1 - t0).count(); + + // Benchmark AFTER: same with bitset + volatile int sink2 = 0; + auto t2 = std::chrono::high_resolution_clock::now(); + for (int iter = 0; iter < ITERATIONS; iter++) { + bz_eEventType queryEvt = (bz_eEventType)(iter % bz_eLastEvent); + for (int h = 0; h < NUM_HANDLERS; h++) { + if (afterHandlers[h].HasEvent(queryEvt)) + sink2++; + } + } + auto t3 = std::chrono::high_resolution_clock::now(); + double after_ms = std::chrono::duration(t3 - t2).count(); + + double ratio = before_ms / after_ms; + printf("BEFORE: %.1f ms\n", before_ms); + printf("AFTER: %.1f ms\n", after_ms); + printf("Ratio: %.1fx speedup\n", ratio); + + // Patched version must be faster + assert(ratio > 2.0 && "Expected at least 2x speedup from bitset vs vector find"); + printf("PASS: performance (%.1fx)\n", ratio); +} + +int main() { + test_correctness(); + test_performance(); + printf("ALL TESTS PASSED\n"); + return 0; +} diff --git a/defects/bzflag-0002/patch/bzflag-0002.patch b/defects/bzflag-0002/patch/bzflag-0002.patch new file mode 100644 index 000000000..643c9da2e --- /dev/null +++ b/defects/bzflag-0002/patch/bzflag-0002.patch @@ -0,0 +1,104 @@ +--- a/src/bzfs/AccessControlList.h ++++ b/src/bzfs/AccessControlList.h +@@ -12,6 +12,7 @@ + + // System headers + #include ++#include + #include + #include + +@@ -173,6 +174,15 @@ + /* FIXME the AccessControlList assumes that 255 is a wildcard. it "should" + * include a cidr mask with each address. it's still useful as is, though + * see wildcard conversion occurs in convert(). ++ * ++ * PERF NOTE: ban(), hostBan(), and idBan() each use std::find() on our ++ * vector to detect duplicates before inserting. When called from merge() ++ * which processes every entry in a master ban list, this makes ban list ++ * loading O(B^2) where B = number of bans. For large master ban lists ++ * (hundreds to thousands of entries from community servers) this is ++ * significant. Fix: maintain a parallel unordered_set index keyed on ++ * the ban identity (IP+CIDR for IP bans, hostpat for host bans, idpat ++ * for ID bans) to achieve O(1) duplicate detection. + */ + + /** This class handles the lists of bans and hostbans. It has functions for +@@ -296,6 +306,15 @@ + typedef std::vector idBanList_t; + idBanList_t idBanList; + ++ // O(1) duplicate detection indexes. ++ // Key for IP bans: (addr.s_addr, cidr) packed as uint64. ++ // Key for host bans: hostpat string. ++ // Key for ID bans: idpat string. ++ std::unordered_set banIndex; ++ std::unordered_set hostBanIndex; ++ std::unordered_set idBanIndex; ++ ++ static uint64_t banKey(in_addr addr, unsigned char cidr) { return ((uint64_t)addr.s_addr << 8) | cidr; } + std::string banFile; + + private: +--- a/src/bzfs/AccessControlList.cxx ++++ b/src/bzfs/AccessControlList.cxx +@@ -41,9 +41,12 @@ + { + BanInfo toban(ipAddr, bannedBy, period, cidr, fromMaster); + if (reason) toban.reason = reason; +- banList_t::iterator oldit = std::find(banList.begin(), banList.end(), toban); +- if (oldit != banList.end()) // IP already in list? -> replace +- *oldit = toban; ++ uint64_t key = banKey(ipAddr, cidr); ++ if (banIndex.count(key)) { ++ // IP already in list -> replace ++ banList_t::iterator oldit = std::find(banList.begin(), banList.end(), toban); ++ if (oldit != banList.end()) ++ *oldit = toban; ++ } + else ++ { ++ banIndex.insert(key); + banList.push_back(toban); ++ } + } + + +@@ -93,9 +96,12 @@ + { + HostBanInfo toban(hostpat, bannedBy, period,fromMaster); + if (reason) toban.reason = reason; +- hostBanList_t::iterator oldit = std::find(hostBanList.begin(), hostBanList.end(), toban); +- if (oldit != hostBanList.end()) +- *oldit = toban; ++ if (hostBanIndex.count(hostpat)) { ++ hostBanList_t::iterator oldit = std::find(hostBanList.begin(), hostBanList.end(), toban); ++ if (oldit != hostBanList.end()) ++ *oldit = toban; ++ } + else ++ { ++ hostBanIndex.insert(hostpat); + hostBanList.push_back(toban); ++ } + } + + +@@ -106,9 +112,12 @@ + { + IdBanInfo toban(idpat, bannedBy, period, fromMaster); + if (reason) toban.reason = reason; +- idBanList_t::iterator oldit = std::find(idBanList.begin(), idBanList.end(), toban); +- if (oldit != idBanList.end()) +- *oldit = toban; ++ if (idBanIndex.count(idpat)) { ++ idBanList_t::iterator oldit = std::find(idBanList.begin(), idBanList.end(), toban); ++ if (oldit != idBanList.end()) ++ *oldit = toban; ++ } + else ++ { ++ idBanIndex.insert(idpat); + idBanList.push_back(toban); ++ } + } diff --git a/defects/bzflag-0002/test/test b/defects/bzflag-0002/test/test new file mode 100755 index 0000000000000000000000000000000000000000..4a50eb64ddf00131aae87b8af9c575f7b9f62bbb GIT binary patch literal 22936 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 literal 0 HcmV?d00001 diff --git a/defects/bzflag-0002/test/test_ban_list_dedup.cpp b/defects/bzflag-0002/test/test_ban_list_dedup.cpp new file mode 100644 index 000000000..847153fd1 --- /dev/null +++ b/defects/bzflag-0002/test/test_ban_list_dedup.cpp @@ -0,0 +1,143 @@ +// Unit test for bzflag-0002: AccessControlList ban/hostBan/idBan O(B^2) dedup +// +// Defect: ban(), hostBan(), and idBan() each use std::find() on a +// std::vector to detect duplicate bans before inserting. When called from +// merge() processing a master ban list, this makes ban list loading O(B^2). +// +// Fix: Maintain a parallel std::unordered_set index for O(1) duplicate +// detection. Only fall through to std::find for replacement when our +// index confirms a duplicate exists. + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// Minimal reproduction of BZFlag ban structures +struct in_addr_sim { + uint32_t s_addr; +}; + +struct BanInfo { + in_addr_sim addr; + unsigned char cidr; + std::string bannedBy; + std::string reason; + + BanInfo(in_addr_sim a, unsigned char c) : addr(a), cidr(c) {} + + bool operator==(const BanInfo &rhs) const { + return addr.s_addr == rhs.addr.s_addr && cidr == rhs.cidr; + } +}; + +// BEFORE: original O(B^2) pattern +struct BanListBefore { + std::vector banList; + + void ban(in_addr_sim addr, unsigned char cidr) { + BanInfo toban(addr, cidr); + auto oldit = std::find(banList.begin(), banList.end(), toban); + if (oldit != banList.end()) + *oldit = toban; + else + banList.push_back(toban); + } +}; + +// AFTER: O(1) dedup with unordered_set index +struct BanListAfter { + std::vector banList; + std::unordered_set banIndex; + + static uint64_t banKey(in_addr_sim addr, unsigned char cidr) { + return ((uint64_t)addr.s_addr << 8) | cidr; + } + + void ban(in_addr_sim addr, unsigned char cidr) { + BanInfo toban(addr, cidr); + uint64_t key = banKey(addr, cidr); + if (banIndex.count(key)) { + auto oldit = std::find(banList.begin(), banList.end(), toban); + if (oldit != banList.end()) + *oldit = toban; + } else { + banIndex.insert(key); + banList.push_back(toban); + } + } +}; + +void test_correctness() { + BanListAfter acl; + + // Add unique bans + for (int i = 0; i < 100; i++) { + in_addr_sim a; + a.s_addr = (uint32_t)i; + acl.ban(a, 32); + } + assert(acl.banList.size() == 100); + + // Add duplicate: should replace, not add + in_addr_sim dup; + dup.s_addr = 50; + acl.ban(dup, 32); + assert(acl.banList.size() == 100); + + // Different CIDR = different ban + dup.s_addr = 50; + acl.ban(dup, 24); + assert(acl.banList.size() == 101); + + printf("PASS: correctness\n"); +} + +void test_performance() { + const int NUM_BANS = 5000; + + // Benchmark BEFORE + BanListBefore before; + auto t0 = std::chrono::high_resolution_clock::now(); + for (int i = 0; i < NUM_BANS; i++) { + in_addr_sim a; + a.s_addr = (uint32_t)i; + before.ban(a, 32); + } + auto t1 = std::chrono::high_resolution_clock::now(); + double before_ms = std::chrono::duration(t1 - t0).count(); + + // Benchmark AFTER + BanListAfter after; + auto t2 = std::chrono::high_resolution_clock::now(); + for (int i = 0; i < NUM_BANS; i++) { + in_addr_sim a; + a.s_addr = (uint32_t)i; + after.ban(a, 32); + } + auto t3 = std::chrono::high_resolution_clock::now(); + double after_ms = std::chrono::duration(t3 - t2).count(); + + assert(before.banList.size() == (size_t)NUM_BANS); + assert(after.banList.size() == (size_t)NUM_BANS); + + double ratio = before_ms / after_ms; + printf("BEFORE: %.1f ms (%d bans)\n", before_ms, NUM_BANS); + printf("AFTER: %.1f ms (%d bans)\n", after_ms, NUM_BANS); + printf("Ratio: %.1fx speedup\n", ratio); + + assert(ratio > 5.0 && "Expected at least 5x speedup from hash index"); + printf("PASS: performance (%.1fx)\n", ratio); +} + +int main() { + test_correctness(); + test_performance(); + printf("ALL TESTS PASSED\n"); + return 0; +} diff --git a/defects/bzflag-0003/patch/bzflag-0003.patch b/defects/bzflag-0003/patch/bzflag-0003.patch new file mode 100644 index 000000000..a7ba89910 --- /dev/null +++ b/defects/bzflag-0003/patch/bzflag-0003.patch @@ -0,0 +1,26 @@ +--- a/src/bzfs/Permissions.cxx ++++ b/src/bzfs/Permissions.cxx +@@ -536,6 +536,7 @@ + // return value is only needed for groupdb parsing, not for userdb. + bool parsePermissionString(const std::string &permissionString, PlayerAccessInfo &info) + { ++ std::set customPermsSeen; + if (permissionString.length() < 1) + return false; + +@@ -649,10 +650,14 @@ + { + // Easy access + std::vector& c = info.customPerms; ++ // Populate seen set on first custom perm encounter ++ if (customPermsSeen.empty() && !c.empty()) ++ customPermsSeen.insert(c.begin(), c.end()); + + // Only store the custom permission if it doesn't exist, in order to prevent duplicates +- if (std::find(c.begin(), c.end(), word) == c.end()) ++ if (customPermsSeen.find(word) == customPermsSeen.end()) + { ++ customPermsSeen.insert(word); + c.push_back(word); + } + } diff --git a/defects/bzflag-0003/test/test b/defects/bzflag-0003/test/test new file mode 100755 index 0000000000000000000000000000000000000000..e6e38942fa9e90717c90a61ec9abbbd49f11366c GIT binary patch literal 27136 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 literal 0 HcmV?d00001 diff --git a/defects/bzflag-0003/test/test_parse_perm_dedup.cpp b/defects/bzflag-0003/test/test_parse_perm_dedup.cpp new file mode 100644 index 000000000..605882adf --- /dev/null +++ b/defects/bzflag-0003/test/test_parse_perm_dedup.cpp @@ -0,0 +1,108 @@ +// Unit test for bzflag-0003: parsePermissionString customPerms dedup O(W*C) +// +// Defect: parsePermissionString uses std::find on customPerms vector to +// check for duplicates when adding custom permissions. When a group file +// has many custom permissions, this is O(W*C) per parse call. +// +// Fix: Use a std::set shadow for O(log C) dedup lookups. + +#include +#include +#include +#include +#include +#include +#include + +// BEFORE: vector find dedup +struct PermInfoBefore { + std::vector customPerms; + + void addCustomPerm(const std::string &word) { + if (std::find(customPerms.begin(), customPerms.end(), word) == customPerms.end()) + customPerms.push_back(word); + } +}; + +// AFTER: set-backed dedup +struct PermInfoAfter { + std::vector customPerms; + std::set customPermsSeen; + + void addCustomPerm(const std::string &word) { + if (customPermsSeen.find(word) == customPermsSeen.end()) { + customPermsSeen.insert(word); + customPerms.push_back(word); + } + } +}; + +void test_correctness() { + PermInfoAfter info; + + info.addCustomPerm("CUSTOM_KICK"); + info.addCustomPerm("CUSTOM_BAN"); + info.addCustomPerm("CUSTOM_MUTE"); + assert(info.customPerms.size() == 3); + + // Duplicate should not be added + info.addCustomPerm("CUSTOM_KICK"); + assert(info.customPerms.size() == 3); + + // Order preserved + assert(info.customPerms[0] == "CUSTOM_KICK"); + assert(info.customPerms[1] == "CUSTOM_BAN"); + assert(info.customPerms[2] == "CUSTOM_MUTE"); + + printf("PASS: correctness\n"); +} + +void test_performance() { + const int NUM_PERMS = 2000; + + // Generate unique perm names + std::vector perms; + for (int i = 0; i < NUM_PERMS; i++) + perms.push_back("CUSTOM_PERM_" + std::to_string(i)); + + // Benchmark BEFORE + PermInfoBefore before; + auto t0 = std::chrono::high_resolution_clock::now(); + for (int round = 0; round < 10; round++) { + before.customPerms.clear(); + for (auto &p : perms) + before.addCustomPerm(p); + } + auto t1 = std::chrono::high_resolution_clock::now(); + double before_ms = std::chrono::duration(t1 - t0).count(); + + // Benchmark AFTER + PermInfoAfter after; + auto t2 = std::chrono::high_resolution_clock::now(); + for (int round = 0; round < 10; round++) { + after.customPerms.clear(); + after.customPermsSeen.clear(); + for (auto &p : perms) + after.addCustomPerm(p); + } + auto t3 = std::chrono::high_resolution_clock::now(); + double after_ms = std::chrono::duration(t3 - t2).count(); + + assert(before.customPerms.size() == (size_t)NUM_PERMS); + assert(after.customPerms.size() == (size_t)NUM_PERMS); + + double ratio = before_ms / after_ms; + printf("BEFORE: %.1f ms (%d perms)\n", before_ms, NUM_PERMS); + printf("AFTER: %.1f ms (%d perms)\n", after_ms, NUM_PERMS); + printf("Ratio: %.1fx speedup\n", ratio); + + assert(ratio > 3.0 && "Expected at least 3x speedup from set dedup"); + printf("PASS: performance (%.1fx)\n", ratio); +} + +int main() { + test_correctness(); + test_performance(); + printf("ALL TESTS PASSED\n"); + return 0; +}