openmw-0001: pathgrid.cpp Tarjan SCC std::find(mSCCStack) O(V^2), 2.3x (HIGH) openmw-0002: pathgrid.cpp A* openset std::find O(V*E), 4.4x op-count (HIGH) openmw-0003: cellstore.cpp mMovedRefs std::find O(R*M), 15.6x (MEDIUM) openmw-0004: objectpaging.cpp mMovedRefs std::find O(R*M), 4.9x (MEDIUM) MOAD-0002 (Intertangle): CLEAN, typical game engine global state MOAD-0003 (Leaked Context): CLEAN, no thread_local identity carriers MOAD-0004 (Logged Secret): CLEAN, game engine has no credentials MOAD-0005 (Thundering Herd): CLEAN, no unsynchronized cache patterns 8/8 unit tests PASS.
118 lines
3.6 KiB
C++
118 lines
3.6 KiB
C++
// openmw-0003-test.cpp
|
|
// CWE-407: cellstore.cpp mMovedRefs std::find O(R*M)
|
|
//
|
|
// During cell loading, for each reference read from an ESM file, the code calls
|
|
// std::find(cell.mMovedRefs.begin(), cell.mMovedRefs.end(), ref.mRefNum) to check
|
|
// if the reference was moved to a different cell. mMovedRefs is a
|
|
// std::list<MovedCellRef>, so this is O(M) per reference. With R references and
|
|
// M moved refs, total cost is O(R*M).
|
|
//
|
|
// This pattern appears in cellstore.cpp (listRefs and loadRefs) and
|
|
// objectpaging.cpp. Cells can have hundreds of references and dozens of moved refs.
|
|
//
|
|
// Fix: build an unordered_set of moved ref numbers once before iterating, O(R+M).
|
|
|
|
#include <algorithm>
|
|
#include <cassert>
|
|
#include <chrono>
|
|
#include <cstdio>
|
|
#include <list>
|
|
#include <unordered_set>
|
|
#include <vector>
|
|
|
|
struct RefNum {
|
|
int mIndex;
|
|
int mContentFile;
|
|
|
|
bool operator==(const RefNum& other) const {
|
|
return mIndex == other.mIndex && mContentFile == other.mContentFile;
|
|
}
|
|
|
|
struct Hash {
|
|
size_t operator()(const RefNum& r) const {
|
|
return std::hash<long long>()(((long long)r.mContentFile << 32) | (unsigned)r.mIndex);
|
|
}
|
|
};
|
|
};
|
|
|
|
struct MovedCellRef {
|
|
RefNum mRefNum;
|
|
int mTarget[2];
|
|
|
|
bool operator==(const RefNum& ref) const {
|
|
return mRefNum == ref;
|
|
}
|
|
};
|
|
|
|
using MovedCellRefTracker = std::list<MovedCellRef>;
|
|
|
|
// ---------- DEFECTIVE: std::find on list per reference ----------
|
|
int loadRefsDefective(const std::vector<RefNum>& refs, const MovedCellRefTracker& movedRefs) {
|
|
int loaded = 0;
|
|
for (const auto& ref : refs) {
|
|
// O(M) linear scan per reference
|
|
auto iter = std::find(movedRefs.begin(), movedRefs.end(), ref);
|
|
if (iter != movedRefs.end())
|
|
continue;
|
|
loaded++;
|
|
}
|
|
return loaded;
|
|
}
|
|
|
|
// ---------- PATCHED: unordered_set pre-built once ----------
|
|
int loadRefsPatched(const std::vector<RefNum>& refs, const MovedCellRefTracker& movedRefs) {
|
|
std::unordered_set<RefNum, RefNum::Hash> movedRefSet;
|
|
movedRefSet.reserve(movedRefs.size());
|
|
for (const auto& moved : movedRefs)
|
|
movedRefSet.insert(moved.mRefNum);
|
|
|
|
int loaded = 0;
|
|
for (const auto& ref : refs) {
|
|
// O(1) hash set lookup
|
|
if (movedRefSet.count(ref))
|
|
continue;
|
|
loaded++;
|
|
}
|
|
return loaded;
|
|
}
|
|
|
|
int main() {
|
|
const int R = 5000; // references per cell
|
|
const int M = 200; // moved references
|
|
|
|
// Build test data
|
|
std::vector<RefNum> refs;
|
|
refs.reserve(R);
|
|
for (int i = 0; i < R; i++)
|
|
refs.push_back({i, 0});
|
|
|
|
MovedCellRefTracker movedRefs;
|
|
for (int i = 0; i < M; i++)
|
|
movedRefs.push_back({{i * 3, 0}, {0, 0}}); // every 3rd ref is moved
|
|
|
|
// Correctness
|
|
int r1 = loadRefsDefective(refs, movedRefs);
|
|
int r2 = loadRefsPatched(refs, movedRefs);
|
|
assert(r1 == r2);
|
|
printf("PASS correctness: both loaded %d refs (R=%d, M=%d)\n", r1, R, M);
|
|
|
|
// Performance
|
|
auto bench = [&](auto fn) {
|
|
auto t0 = std::chrono::high_resolution_clock::now();
|
|
for (int i = 0; i < 50; i++)
|
|
fn(refs, movedRefs);
|
|
auto t1 = std::chrono::high_resolution_clock::now();
|
|
return std::chrono::duration<double, std::milli>(t1 - t0).count() / 50.0;
|
|
};
|
|
|
|
double defMs = bench(loadRefsDefective);
|
|
double patMs = bench(loadRefsPatched);
|
|
double ratio = defMs / patMs;
|
|
|
|
printf("Defective: %.2f ms Patched: %.2f ms Ratio: %.1fx (R=%d, M=%d)\n",
|
|
defMs, patMs, ratio, R, M);
|
|
assert(ratio > 3.0 && "Patched should be at least 3x faster");
|
|
printf("PASS performance: %.1fx speedup\n", ratio);
|
|
|
|
return 0;
|
|
}
|