java-topology/defects/wesnoth-0003/test/wesnoth-0003-test.cpp
russell@unturf.com 9fac7766ba wesnoth: 3 CWE-407 defects, MOAD 0002-0005 CLEAN
wesnoth-0001: A* pathfinding std::find on pq vector for decrease-key
  O(V*Q) per relaxation, fix: lazy deletion. HIGH, 1279x at N=5000.
wesnoth-0002: server ip_log_ deque linear scan on login/logoff
  O(N) per event with N up to 500. MEDIUM, 437x at L=2000.
wesnoth-0003: combine_special_notes O(N^2) vector dedup
  utils::contains on vector per note insertion. MEDIUM, 499x at N=1000.

MOAD-0002 (Intertangle): singletons deeply embedded, not actionable.
MOAD-0003 (Leaked Context): thread_local for debug/call-stack only.
MOAD-0004 (Logged Secret): passwords never logged verbatim.
MOAD-0005 (Thundering Herd): single-threaded game + coroutine server.

6/6 unit tests PASS.
2026-03-31 12:14:20 -04:00

141 lines
4.4 KiB
C++

// wesnoth-0003-test.cpp
// Unit test: combine_special_notes O(N^2) dedup (CWE-407)
//
// DEFECT: In types.cpp, combine_special_notes calls append_special_note for
// every note from direct notes, abilities, attack specials, damage types,
// and movement type. Each call does utils::contains(notes, new_note) which
// is std::find on a vector, making the total cost O(N^2) where N is the
// total number of note insertions.
//
// FIX: Use a std::set<std::string> to track seen notes for O(log N) dedup
// (or unordered_set for O(1)). This reduces total cost to O(N log N).
//
// BUILD: g++ -std=c++17 -O2 -o wesnoth-0003-test wesnoth-0003-test.cpp && ./wesnoth-0003-test
#include <vector>
#include <set>
#include <string>
#include <algorithm>
#include <cstdio>
#include <cassert>
// Simulate the defect: O(N) contains check per insertion
long long defective_combine(const std::vector<std::string>& input) {
std::vector<std::string> notes;
long long ops = 0;
for (const auto& note : input) {
if (note.empty()) continue;
// Linear scan for dedup
bool found = false;
for (const auto& existing : notes) {
ops++;
if (existing == note) { found = true; break; }
}
if (!found) {
notes.push_back(note);
}
}
return ops;
}
// Fixed: set-based dedup
long long fixed_combine(const std::vector<std::string>& input) {
std::vector<std::string> notes;
std::set<std::string> seen;
long long ops = 0;
for (const auto& note : input) {
if (note.empty()) continue;
ops++; // set insertion/lookup
if (seen.insert(note).second) {
notes.push_back(note);
}
}
return ops;
}
int main() {
printf("wesnoth-0003-test: combine_special_notes O(N^2) dedup (CWE-407)\n\n");
// Test 1: Correctness
{
printf("Test 1: correctness\n");
std::vector<std::string> input = {
"Poison attack", "First strike", "Poison attack",
"Regenerates", "First strike", "Skirmisher",
"Regenerates", "Marksman", ""
};
// Defective approach
std::vector<std::string> defect_result;
for (const auto& note : input) {
if (note.empty()) continue;
bool found = false;
for (const auto& e : defect_result) {
if (e == note) { found = true; break; }
}
if (!found) defect_result.push_back(note);
}
// Fixed approach
std::vector<std::string> fixed_result;
std::set<std::string> seen;
for (const auto& note : input) {
if (note.empty()) continue;
if (seen.insert(note).second) {
fixed_result.push_back(note);
}
}
assert(defect_result == fixed_result);
printf(" PASS (same output: %zu unique notes)\n", defect_result.size());
}
// Test 2: Performance with many notes (simulating unit with many abilities)
{
const int N = 500;
printf("\nTest 2: performance with N=%d notes (50%% duplicates)\n", N);
std::vector<std::string> input;
for (int i = 0; i < N; i++) {
input.push_back("special_note_" + std::to_string(i % (N / 2)));
}
long long defect_ops = defective_combine(input);
long long fixed_ops = fixed_combine(input);
double ratio = (double)defect_ops / (double)fixed_ops;
printf(" defective ops: %lld\n", defect_ops);
printf(" fixed ops: %lld\n", fixed_ops);
printf(" ratio: %.1fx\n", ratio);
assert(ratio > 50.0);
printf(" PASS (ratio > 50x)\n");
}
// Test 3: All unique notes (worst case for defective)
{
const int N = 1000;
printf("\nTest 3: worst case, N=%d all unique notes\n", N);
std::vector<std::string> input;
for (int i = 0; i < N; i++) {
input.push_back("unique_note_" + std::to_string(i));
}
long long defect_ops = defective_combine(input);
long long fixed_ops = fixed_combine(input);
double ratio = (double)defect_ops / (double)fixed_ops;
printf(" defective ops: %lld\n", defect_ops);
printf(" fixed ops: %lld\n", fixed_ops);
printf(" ratio: %.1fx\n", ratio);
assert(ratio > 100.0);
printf(" PASS (ratio > 100x)\n");
}
printf("\nAll tests PASSED.\n");
return 0;
}