// 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 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 #include #include #include #include #include #include #include // Simulate our UnitBase* as opaque pointers struct FakeUnit { int id; }; // ========== DEFECTIVE VERSION: std::find + erase ========== struct DefectivePlayerList { std::vector 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 units; std::unordered_map 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 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 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 units(U); for (int i = 0; i < U; i++) units[i].id = i; // Build removal order (first D units) std::vector 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; }