java-topology/whitepaper/outreach/openxcom-0002.md
russell@unturf.com 652608142a feat: close outreach doc gap — 276 docs (batches 11-16)
All projects with patches now have outreach docs. 276 new docs covering
CWE-407, CWE-312, CWE-362 across C, C++, Java, Python, Go, Rust, C#,
PHP, Ruby, JavaScript, Dart, Erlang, R, and more.

Outreach gap: 276 -> 0.
2026-04-15 13:57:42 -04:00

2.8 KiB
Raw Blame History

OpenXcom — CWE-407 Disclosure Brief (openxcom-0002)

2026-04-13 · Patch available — awaiting upstream merge

Finding

Two O(N²) patterns in SavedGame: (1) isResearched() scans the entire _discovered vector for every research name check, and (2) getAvailableResearchProjects() builds an unlocked vector with O(N) membership tests.

The Defect

openxcom-0002 (PATCHED — HIGH): src/Savegame/SavedGame.cpp

// isResearched — O(D) per call, called in many hot loops:
bool SavedGame::isResearched(const std::string &research, ...) const {
    for (auto i = _discovered.begin(); i != _discovered.end(); ++i) {
        if ((*i)->getName() == research)
            return true;
    }
    return false;
}
// getAvailableResearchProjects — O(U) per candidate:
std::vector<const RuleResearch *> unlocked;
// ... populated by iterating _discovered ...
if (std::find(unlocked.begin(), unlocked.end(), research) != unlocked.end())
    ...

isResearched() is called from manufacturing checks, research availability, inventory screens, and save/load. With D discovered technologies, each call costs O(D). getAvailableResearchProjects() compounds this across all candidate research topics.

Complexity Proof

At D=200 discovered technologies:

  • isResearched(): 200 string comparisons per call × hundreds of calls per UI refresh = O(D²) total
  • getAvailableResearchProjects(): D × U unlocked entries ≈ 40,000 pointer comparisons
  • Fixed: O(1) hash lookup per call
  • ~200× op reduction per isResearched call.

Impact

OpenXcom late-game saves with extensive research trees trigger isResearched() from dozens of call sites. The Ufopedia, manufacturing, and research screens all block while scanning the discovered list. Mods like OpenXcom Extended and The X-Com Files expand the research tree to 500+ entries, compounding the quadratic cost.

The Fix

Add a parallel unordered_set<string> _discoveredNames maintained at all three push_back sites:

// Before
for (auto i = _discovered.begin(); i != _discovered.end(); ++i)
    if ((*i)->getName() == research) return true;
return false;

// After
return _discoveredNames.count(research) != 0;

Patch

Fix available: defects/openxcom-0002/patch/openxcom-0002.patch

Two-file patch across SavedGame.h and SavedGame.cpp.

What We Ask

A patch is ready for review.

  1. Confirm receipt and assign a GitHub issue reference (OpenXcom/OpenXcom).
  2. Assess severity — fires across all UI screens checking research status, compounds with mod size.
  3. Coordinate a disclosure date — we target 90 days from first contact.
  4. We will credit the OpenXcom team in the public disclosure. Preferred acknowledgment format welcome.

Contact: see cover email. This brief is confidential until coordinated disclosure.