java-topology/defects/openxcom-0001/test/test_reachable_lookup.cpp
russell@unturf.com 47aa94a654 openxcom: 2 CWE-407 defects, MOAD 0002-0005 CLEAN
openxcom-0001: AIModule _reachable/_reachableWithAttack std::vector<int>
  with std::find() inside AI loops (setupAmbush, setupEscape,
  selectPointNearTarget, findFirePoint). O(N*R) per alien turn where
  N = nodes checked, R = reachable tiles (~500 on typical map).
  Fix: std::unordered_set<int> for O(1) lookup. MEDIUM-HIGH, 7.6x.

openxcom-0002: SavedGame::isResearched linear scan of _discovered vector
  O(D) per call, called O(R*4) times from getAvailableResearchProjects
  per base. Also unlocked vector with std::find O(R*U).
  Fix: parallel unordered_set<string> for O(1) lookup. MEDIUM, 4.5x.

MOAD-0002 (Intertangle): CLEAN, typical game state architecture
MOAD-0003 (Leaked Context): CLEAN, single-threaded game
MOAD-0004 (Logged Secret): CLEAN, no credentials
MOAD-0005 (Thundering Herd): CLEAN, no concurrent caching
2026-03-31 12:56:27 -04:00

106 lines
3.9 KiB
C++

// Unit test for openxcom-0001: AIModule _reachable/_reachableWithAttack
// vector std::find O(N*R) -> unordered_set O(1) lookup
//
// Defect: AIModule stores reachable tile indices in std::vector<int> and
// uses std::find() for membership checks inside loops over map nodes
// (setupAmbush, setupEscape, selectPointNearTarget, findFirePoint).
// On a 50x50x4 map with hundreds of reachable tiles, each AI turn
// performs O(N*R) linear scans where N = nodes checked and R = reachable tiles.
//
// Fix: Replace std::vector<int> with std::unordered_set<int> for O(1) lookup.
#include <vector>
#include <unordered_set>
#include <algorithm>
#include <chrono>
#include <iostream>
#include <cassert>
#include <cstdlib>
// Simulate our defective pattern: vector + std::find in loop
int benchmarkVector(const std::vector<int>& reachable, const std::vector<int>& queries) {
int found = 0;
for (size_t i = 0; i < queries.size(); ++i) {
if (std::find(reachable.begin(), reachable.end(), queries[i]) != reachable.end()) {
++found;
}
}
return found;
}
// Simulate our fixed pattern: unordered_set + count in loop
int benchmarkSet(const std::unordered_set<int>& reachable, const std::vector<int>& queries) {
int found = 0;
for (size_t i = 0; i < queries.size(); ++i) {
if (reachable.count(queries[i]) != 0) {
++found;
}
}
return found;
}
int main() {
// Typical battlescape: 50x50x4 = 10000 tiles, ~500 reachable
const int R = 500; // reachable tiles
const int N = 200; // nodes/positions checked per AI cycle (setupAmbush + findFirePoint + selectPointNearTarget)
const int UNITS = 20; // alien units per battle
const int ITERATIONS = UNITS; // each unit runs AI per turn
// Build reachable tile indices
std::vector<int> reachableVec;
reachableVec.reserve(R);
for (int i = 0; i < R; ++i) {
reachableVec.push_back(i * 20); // spread across map
}
std::unordered_set<int> reachableSet(reachableVec.begin(), reachableVec.end());
// Build query positions (mix of reachable and unreachable)
std::vector<int> queries;
queries.reserve(N);
for (int i = 0; i < N; ++i) {
queries.push_back(i * 10); // ~50% will hit
}
// Correctness check
int vecResult = benchmarkVector(reachableVec, queries);
int setResult = benchmarkSet(reachableSet, queries);
assert(vecResult == setResult);
std::cout << "Correctness: PASS (both found " << vecResult << " matches)" << std::endl;
// Benchmark: vector (defective)
auto t0 = std::chrono::high_resolution_clock::now();
int dummy1 = 0;
for (int iter = 0; iter < ITERATIONS; ++iter) {
dummy1 += benchmarkVector(reachableVec, queries);
}
auto t1 = std::chrono::high_resolution_clock::now();
// Benchmark: unordered_set (fixed)
auto t2 = std::chrono::high_resolution_clock::now();
int dummy2 = 0;
for (int iter = 0; iter < ITERATIONS; ++iter) {
dummy2 += benchmarkSet(reachableSet, queries);
}
auto t3 = std::chrono::high_resolution_clock::now();
double vecUs = std::chrono::duration_cast<std::chrono::microseconds>(t1 - t0).count();
double setUs = std::chrono::duration_cast<std::chrono::microseconds>(t3 - t2).count();
double ratio = vecUs / (setUs > 0 ? setUs : 1);
std::cout << "Vector (defective): " << vecUs << " us" << std::endl;
std::cout << "Set (fixed): " << setUs << " us" << std::endl;
std::cout << "Ratio: " << ratio << "x" << std::endl;
std::cout << "Parameters: R=" << R << " tiles reachable, N=" << N << " queries, "
<< UNITS << " units per turn" << std::endl;
// Verify significant speedup
assert(ratio > 2.0);
std::cout << "Performance: PASS (ratio > 2x)" << std::endl;
// Prevent optimizer from removing work
assert(dummy1 == dummy2);
std::cout << "ALL TESTS PASSED" << std::endl;
return 0;
}