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.
141 lines
4.4 KiB
C++
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;
|
|
}
|