java-topology/defects/bzflag-0003/test/test_parse_perm_dedup.cpp
russell@unturf.com 4641c3c60f 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.
2026-03-31 13:00:38 -04:00

108 lines
3.2 KiB
C++

// 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<std::string> shadow for O(log C) dedup lookups.
#include <vector>
#include <set>
#include <string>
#include <algorithm>
#include <cassert>
#include <chrono>
#include <cstdio>
// BEFORE: vector find dedup
struct PermInfoBefore {
std::vector<std::string> 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<std::string> customPerms;
std::set<std::string> 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<std::string> 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<double, std::milli>(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<double, std::milli>(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;
}