netpanzer-0001: UnitInterface::removeUnit std::find on PlayerUnitList vector O(D*U) during mass destruction. Fix: unordered_map index + swap-and-pop O(1) removal. MEDIUM, 3.7x at U=2000 D=1000. netpanzer-0002: UnitBucketArray::getUnitBucketIndex scans all buckets O(B*U) per misplaced unit in sortBucketArray fallback path. Fix: unordered_map<UnitID, bucket_index> for O(1) lookup. HIGH, 9.4x at B=200 U=2000 M=500. MOAD 0003 (ThreadLocal): CLEAN, no thread_local patterns MOAD 0004 (Logged Secret): CLEAN, passwords not logged verbatim MOAD 0005 (Thundering Herd): CLEAN, PathCache is single-threaded
157 lines
4.8 KiB
C++
157 lines
4.8 KiB
C++
// Unit test for netpanzer-0001: UnitInterface::removeUnit std::find O(U) -> O(1) swap-and-pop
|
|
// CWE-407: Algorithmic Complexity — list membership inside removal loop
|
|
//
|
|
// Defect: removeUnit() calls std::find(plist.begin(), plist.end(), unit) to locate
|
|
// a unit in our per-player vector before erasing it. std::find is O(U) where U is
|
|
// our player's unit count. When multiple units are destroyed in a single frame
|
|
// (mass battle), updateUnitStatus calls removeUnit for each dead unit, yielding
|
|
// O(D*U) total where D is dead units per frame.
|
|
//
|
|
// Fix: maintain a parallel std::unordered_map<UnitBase*, size_t> tracking each
|
|
// unit's index in our player vector. Removal becomes O(1) via swap-with-back
|
|
// plus pop_back, eliminating our linear scan entirely.
|
|
|
|
#include <algorithm>
|
|
#include <cassert>
|
|
#include <chrono>
|
|
#include <cstdio>
|
|
#include <cstdlib>
|
|
#include <iostream>
|
|
#include <unordered_map>
|
|
#include <vector>
|
|
|
|
// Simulate our UnitBase* as opaque pointers
|
|
struct FakeUnit {
|
|
int id;
|
|
};
|
|
|
|
// ========== DEFECTIVE VERSION: std::find + erase ==========
|
|
struct DefectivePlayerList {
|
|
std::vector<FakeUnit*> units;
|
|
|
|
void addUnit(FakeUnit* u) {
|
|
units.push_back(u);
|
|
}
|
|
|
|
void removeUnit(FakeUnit* u) {
|
|
auto it = std::find(units.begin(), units.end(), u);
|
|
if (it != units.end()) {
|
|
units.erase(it); // O(U) find + O(U) shift
|
|
}
|
|
}
|
|
};
|
|
|
|
// ========== FIXED VERSION: index map + swap-and-pop ==========
|
|
struct FixedPlayerList {
|
|
std::vector<FakeUnit*> units;
|
|
std::unordered_map<FakeUnit*, size_t> indexMap;
|
|
|
|
void addUnit(FakeUnit* u) {
|
|
indexMap[u] = units.size();
|
|
units.push_back(u);
|
|
}
|
|
|
|
void removeUnit(FakeUnit* u) {
|
|
auto it = indexMap.find(u);
|
|
if (it != indexMap.end()) {
|
|
size_t idx = it->second;
|
|
if (idx < units.size() - 1) {
|
|
FakeUnit* back = units.back();
|
|
units[idx] = back;
|
|
indexMap[back] = idx;
|
|
}
|
|
units.pop_back();
|
|
indexMap.erase(it);
|
|
}
|
|
}
|
|
};
|
|
|
|
static long long now_ns() {
|
|
return std::chrono::high_resolution_clock::now().time_since_epoch().count();
|
|
}
|
|
|
|
int main() {
|
|
// Test correctness first
|
|
{
|
|
FixedPlayerList fixed;
|
|
std::vector<FakeUnit> pool(100);
|
|
for (int i = 0; i < 100; i++) {
|
|
pool[i].id = i;
|
|
fixed.addUnit(&pool[i]);
|
|
}
|
|
assert(fixed.units.size() == 100);
|
|
|
|
// Remove every other unit
|
|
for (int i = 0; i < 100; i += 2) {
|
|
fixed.removeUnit(&pool[i]);
|
|
}
|
|
assert(fixed.units.size() == 50);
|
|
|
|
// Verify all remaining units are odd-indexed
|
|
for (size_t i = 0; i < fixed.units.size(); i++) {
|
|
assert(fixed.units[i]->id % 2 == 1);
|
|
// Verify index map is consistent
|
|
assert(fixed.indexMap[fixed.units[i]] == i);
|
|
}
|
|
|
|
// Remove all remaining
|
|
std::vector<FakeUnit*> remaining(fixed.units.begin(), fixed.units.end());
|
|
for (auto* u : remaining) {
|
|
fixed.removeUnit(u);
|
|
}
|
|
assert(fixed.units.size() == 0);
|
|
assert(fixed.indexMap.size() == 0);
|
|
}
|
|
printf("PASS correctness\n");
|
|
|
|
// Benchmark: simulate mass destruction (many removals from large list)
|
|
const int U = 2000; // units per player
|
|
const int D = 1000; // units destroyed per frame
|
|
|
|
std::vector<FakeUnit> units(U);
|
|
for (int i = 0; i < U; i++) units[i].id = i;
|
|
|
|
// Build removal order (first D units)
|
|
std::vector<int> removeOrder(D);
|
|
for (int i = 0; i < D; i++) removeOrder[i] = i;
|
|
|
|
const int TRIALS = 200;
|
|
|
|
// Benchmark defective
|
|
long long defective_ns = 0;
|
|
for (int t = 0; t < TRIALS; t++) {
|
|
DefectivePlayerList defective;
|
|
for (int i = 0; i < U; i++) defective.addUnit(&units[i]);
|
|
|
|
long long start = now_ns();
|
|
for (int i = 0; i < D; i++) {
|
|
defective.removeUnit(&units[removeOrder[i]]);
|
|
}
|
|
defective_ns += now_ns() - start;
|
|
}
|
|
|
|
// Benchmark fixed
|
|
long long fixed_ns = 0;
|
|
for (int t = 0; t < TRIALS; t++) {
|
|
FixedPlayerList fixed;
|
|
for (int i = 0; i < U; i++) fixed.addUnit(&units[i]);
|
|
|
|
long long start = now_ns();
|
|
for (int i = 0; i < D; i++) {
|
|
fixed.removeUnit(&units[removeOrder[i]]);
|
|
}
|
|
fixed_ns += now_ns() - start;
|
|
}
|
|
|
|
double ratio = (double)defective_ns / (double)fixed_ns;
|
|
printf("Defective: %lld ns total (%d trials)\n", defective_ns, TRIALS);
|
|
printf("Fixed: %lld ns total (%d trials)\n", fixed_ns, TRIALS);
|
|
printf("Ratio: %.1fx speedup\n", ratio);
|
|
printf("U=%d units, D=%d destroyed per frame\n", U, D);
|
|
|
|
// At U=500, D=250, we expect significant speedup
|
|
assert(ratio > 2.0 && "Fixed version should be at least 2x faster");
|
|
printf("PASS performance (%.1fx)\n", ratio);
|
|
|
|
return 0;
|
|
}
|