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<bz_eLastEvent>. 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.
This commit is contained in:
parent
dbc058c155
commit
4641c3c60f
9 changed files with 686 additions and 0 deletions
245
defects/bzflag-0001/test/test_event_handler_hasEvent.cpp
Normal file
245
defects/bzflag-0001/test/test_event_handler_hasEvent.cpp
Normal file
|
|
@ -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<bz_eEventType>
|
||||
// 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<bz_eEventType> HandledEvents with
|
||||
// std::bitset<bz_eLastEvent> handledEventBits. HasEvent/AddEvent/RemoveEvent
|
||||
// become O(1) bitset operations.
|
||||
|
||||
#include <bitset>
|
||||
#include <vector>
|
||||
#include <algorithm>
|
||||
#include <cassert>
|
||||
#include <chrono>
|
||||
#include <cstdio>
|
||||
|
||||
// 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<bz_eEventType> 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<bz_eLastEvent> 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<bz_eEventType> 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<EventHandlerBefore> beforeHandlers(NUM_HANDLERS);
|
||||
for (auto &h : beforeHandlers)
|
||||
for (auto evt : eventTypes)
|
||||
h.AddEvent(evt);
|
||||
|
||||
// Setup AFTER handlers
|
||||
std::vector<EventHandlerAfter> 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<double, std::milli>(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<double, std::milli>(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;
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue