From dbc058c15549cbb5fb6d3ea6eff9ca05238bf8da Mon Sep 17 00:00:00 2001 From: "russell@unturf.com" Date: Tue, 31 Mar 2026 12:59:12 -0400 Subject: [PATCH] naev: 2 CWE-407 defects, MOAD 0002-0005 CLEAN naev-0001: map.c Dijkstra/A* pathfinding uses linked-list open/closed sets with O(V) A_in() membership test and O(V) A_lowest() extract-min per iteration, making full pathfinding O(V^2 + E*V). Fix: array-indexed visited flags for O(1) membership, sorted-insert open list for O(1) extract-min. 102.5x at V=500 (Naev has 538 star systems). HIGH. naev-0002: tech.c tech_addGroupItemPrice() dedup scans growing output array linearly per item O(I*N) when building outfit/ship/commodity lists from tech groups. Fix: hash set for O(1) amortized dedup. 333x at N=1000. MEDIUM. MOAD-0002 (Intertangle): global stacks are standard C game engine pattern, subsystems largely independent. CLEAN. MOAD-0003 (Leaked Context): single thread_local in Rust RNG only. CLEAN. MOAD-0004 (Logged Secret): no credentials in single-player game. CLEAN. MOAD-0005 (Thundering Herd): single-threaded gameplay logic. CLEAN. 2/2 PASS, 2 defects. --- .../freeorion-0001/patch/freeorion-0001.patch | 56 +++ defects/freeorion-0001/test/test | Bin 0 -> 27944 bytes .../test/test_freeorion_0001.cpp | 102 ++++++ .../freeorion-0002/patch/freeorion-0002.patch | 29 ++ defects/freeorion-0002/test/test | Bin 0 -> 28864 bytes .../test/test_freeorion_0002.cpp | 148 ++++++++ .../freeorion-0003/patch/freeorion-0003.patch | 61 +++ defects/freeorion-0003/test/test | Bin 0 -> 23608 bytes .../test/test_freeorion_0003.cpp | 98 +++++ defects/naev-0001/patch/naev-0001.patch | 263 +++++++++++++ defects/naev-0001/test/test_naev_0001 | Bin 0 -> 16360 bytes defects/naev-0001/test/test_naev_0001.c | 346 ++++++++++++++++++ defects/naev-0002/patch/naev-0002.patch | 27 ++ defects/naev-0002/test/test_naev_0002 | Bin 0 -> 16464 bytes defects/naev-0002/test/test_naev_0002.c | 179 +++++++++ 15 files changed, 1309 insertions(+) create mode 100644 defects/freeorion-0001/patch/freeorion-0001.patch create mode 100755 defects/freeorion-0001/test/test create mode 100644 defects/freeorion-0001/test/test_freeorion_0001.cpp create mode 100644 defects/freeorion-0002/patch/freeorion-0002.patch create mode 100755 defects/freeorion-0002/test/test create mode 100644 defects/freeorion-0002/test/test_freeorion_0002.cpp create mode 100644 defects/freeorion-0003/patch/freeorion-0003.patch create mode 100755 defects/freeorion-0003/test/test create mode 100644 defects/freeorion-0003/test/test_freeorion_0003.cpp create mode 100644 defects/naev-0001/patch/naev-0001.patch create mode 100755 defects/naev-0001/test/test_naev_0001 create mode 100644 defects/naev-0001/test/test_naev_0001.c create mode 100644 defects/naev-0002/patch/naev-0002.patch create mode 100755 defects/naev-0002/test/test_naev_0002 create mode 100644 defects/naev-0002/test/test_naev_0002.c diff --git a/defects/freeorion-0001/patch/freeorion-0001.patch b/defects/freeorion-0001/patch/freeorion-0001.patch new file mode 100644 index 000000000..3fc793b0c --- /dev/null +++ b/defects/freeorion-0001/patch/freeorion-0001.patch @@ -0,0 +1,56 @@ +--- a/Empire/Empire.cpp ++++ b/Empire/Empire.cpp +@@ -2263,6 +2263,14 @@ std::vector Empire::CheckResearchProgress( + const std::vector>& costs_times) + { + SanitizeResearchQueue(m_research_queue); ++ ++ // Build hash map for O(1) lookup of tech cost/time by name. ++ // Without this, each lookup is a linear scan of costs_times, making ++ // both loops below O(Q*T) and O(T*T) respectively. ++ boost::unordered_flat_map> costs_times_map; ++ costs_times_map.reserve(costs_times.size()); ++ for (const auto& [name, cost, time] : costs_times) ++ costs_times_map.emplace(name, std::pair{cost, time}); + + float spent_rp = 0.0f; + const float total_rp_available = m_research_pool.TotalAvailable(); +@@ -2274,15 +2282,13 @@ std::vector Empire::CheckResearchProgress( + // process items on queue + std::vector to_erase_from_queue_and_grant_next_turn; + for (auto& elem : m_research_queue | range_filter(has_allocated_rp)) { +- const auto is_tech = [tech_name{elem.name}](const std::tuple& ct) +- { return std::get<0>(ct) == tech_name; }; +- +- const auto ct_it = range_find_if(costs_times, is_tech); +- if (ct_it == costs_times.end()) { ++ const auto ct_it = costs_times_map.find(elem.name); ++ if (ct_it == costs_times_map.end()) { + ErrorLogger() << "Missing tech " << elem.name << " cost time in CheckResearchProgress!"; + continue; + } +- const float tech_cost = static_cast(std::get<1>(*ct_it)); ++ const float tech_cost = static_cast(ct_it->second.first); + + float& progress = m_research_progress[elem.name]; + progress += elem.allocated_rp / std::max(EPSILON, tech_cost); +@@ -2328,16 +2334,13 @@ std::vector Empire::CheckResearchProgress( + if (progress_fraction >= 1.0) + continue; + +- const auto is_tech = [tech_name{tech_name}](const std::tuple& ct) +- { return std::get<0>(ct) == tech_name; }; +- +- const auto ct_it = range_find_if(costs_times, is_tech); +- if (ct_it == costs_times.end()) { ++ const auto ct_it = costs_times_map.find(tech_name); ++ if (ct_it == costs_times_map.end()) { + ErrorLogger() << "Missing tech " << tech_name << " cost time in CheckResearchProgress!"; + continue; + } + +- const double remaining_cost = std::get<1>(*ct_it) * (1.0 - progress_fraction); ++ const double remaining_cost = ct_it->second.first * (1.0 - progress_fraction); + costs_to_complete_available_unpaused_techs.emplace_back(remaining_cost, tech_name); + } + std::stable_sort(costs_to_complete_available_unpaused_techs.begin(), diff --git a/defects/freeorion-0001/test/test b/defects/freeorion-0001/test/test new file mode 100755 index 0000000000000000000000000000000000000000..0b16a02812426770b5a6b9829a7d4bac62e03f32 GIT binary patch literal 27944 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 literal 0 HcmV?d00001 diff --git a/defects/freeorion-0001/test/test_freeorion_0001.cpp b/defects/freeorion-0001/test/test_freeorion_0001.cpp new file mode 100644 index 000000000..1c0bb7110 --- /dev/null +++ b/defects/freeorion-0001/test/test_freeorion_0001.cpp @@ -0,0 +1,102 @@ +// Unit test for freeorion-0001: CheckResearchProgress costs_times linear scan O(Q*T + T*T) +// Demonstrates that building an unordered_map for O(1) lookup eliminates quadratic scaling. + +#include +#include +#include +#include +#include +#include +#include +#include + +// ----------------------------------------------------------------------- +// DEFECT (original): linear scan of costs_times vector per tech lookup +// ----------------------------------------------------------------------- +static double __attribute__((noinline)) lookup_linear( + const std::vector>& costs_times, + const std::vector& tech_names) +{ + volatile double sum = 0.0; + for (const auto& tech_name : tech_names) { + for (const auto& [name, cost, time] : costs_times) { + if (name == tech_name) { + sum = sum + cost; + break; + } + } + } + return sum; +} + +// ----------------------------------------------------------------------- +// FIX (patched): build hash map once, O(1) lookup per tech +// ----------------------------------------------------------------------- +static double __attribute__((noinline)) lookup_hashmap( + const std::vector>& costs_times, + const std::vector& tech_names) +{ + std::unordered_map> costs_map; + costs_map.reserve(costs_times.size()); + for (const auto& [name, cost, time] : costs_times) + costs_map.emplace(name, std::pair{cost, time}); + + volatile double sum = 0.0; + for (const auto& tech_name : tech_names) { + auto it = costs_map.find(tech_name); + if (it != costs_map.end()) + sum = sum + it->second.first; + } + return sum; +} + +int main() { + // Simulate a tech tree with T techs + constexpr int T = 2000; + + std::vector tech_names; + tech_names.reserve(T); + std::vector> costs_times; + costs_times.reserve(T); + + for (int i = 0; i < T; ++i) { + tech_names.push_back("TECH_" + std::to_string(i)); + } + // Build costs_times with string_views into tech_names + for (int i = 0; i < T; ++i) { + costs_times.emplace_back(std::string_view(tech_names[i]), static_cast(i + 1), 1); + } + + // Correctness check + double linear_sum = lookup_linear(costs_times, tech_names); + double hashmap_sum = lookup_hashmap(costs_times, tech_names); + assert(linear_sum == hashmap_sum); + + // Benchmark: linear (defect) + auto t0 = std::chrono::high_resolution_clock::now(); + constexpr int ITERS = 20; + for (int r = 0; r < ITERS; ++r) + lookup_linear(costs_times, tech_names); + auto t1 = std::chrono::high_resolution_clock::now(); + + // Benchmark: hashmap (fix) + for (int r = 0; r < ITERS; ++r) + lookup_hashmap(costs_times, tech_names); + auto t2 = std::chrono::high_resolution_clock::now(); + + auto linear_us = std::chrono::duration_cast(t1 - t0).count(); + auto hashmap_us = std::chrono::duration_cast(t2 - t1).count(); + + double ratio = (hashmap_us > 0) ? static_cast(linear_us) / hashmap_us : 999.0; + + std::cout << "T=" << T << " techs, " << ITERS << " iterations" << std::endl; + std::cout << "linear (defect): " << linear_us << " us" << std::endl; + std::cout << "hashmap (fix): " << hashmap_us << " us" << std::endl; + std::cout << "ratio: " << ratio << "x" << std::endl; + + // At T=2000, expect at least 10x speedup + assert(ratio > 5.0 && "Expected significant speedup from hashmap lookup"); + + std::cout << "PASS" << std::endl; + return 0; +} diff --git a/defects/freeorion-0002/patch/freeorion-0002.patch b/defects/freeorion-0002/patch/freeorion-0002.patch new file mode 100644 index 000000000..18dd677a1 --- /dev/null +++ b/defects/freeorion-0002/patch/freeorion-0002.patch @@ -0,0 +1,29 @@ +--- a/universe/Tech.cpp ++++ b/universe/Tech.cpp +@@ -593,6 +593,7 @@ std::string TechManager::FindFirstDependencyCycle() const { + + std::vector stack; + stack.reserve(m_techs.size()); ++ std::unordered_set stack_set; + stack.push_back(&tech); + while (!stack.empty()) { + // Examine the tech on top of the stack. If the tech has no prerequisite techs, or if all +@@ -608,8 +609,8 @@ std::string TechManager::FindFirstDependencyCycle() const { + + // since this is not a checked prereq, see if it is already in the stack somewhere; + // if it is, we have a cycle +- const auto stack_duplicate_it = std::find(stack.rbegin(), stack.rend(), prereq_tech); +- if (stack_duplicate_it == stack.rend()) { ++ if (!stack_set.contains(prereq_tech)) { + // OK! no cycle, move to next prereq ++ stack_set.insert(prereq_tech); + stack.push_back(prereq_tech); + continue; + } +@@ -634,6 +635,7 @@ std::string TechManager::FindFirstDependencyCycle() const { + if (starting_stack_size == stack.size()) { + stack.pop_back(); ++ stack_set.erase(current_tech); + checked_techs.insert(current_tech); + } + } diff --git a/defects/freeorion-0002/test/test b/defects/freeorion-0002/test/test new file mode 100755 index 0000000000000000000000000000000000000000..1860387ae5f2c3e0d84f2f297fc1f7b7f2490083 GIT binary patch literal 28864 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 +#include +#include +#include +#include +#include +#include + +using TechTree = std::unordered_map>; + +// ----------------------------------------------------------------------- +// DEFECT: std::find on vector stack for cycle detection. +// For each prereq of each tech, scan the entire stack vector. +// ----------------------------------------------------------------------- +static int __attribute__((noinline)) scan_ops_linear(const TechTree& tree, int num_techs) { + volatile int scan_ops = 0; + std::unordered_set checked; + + for (int t = num_techs - 1; t >= 0; --t) { + if (checked.count(t)) continue; + + std::vector stack; + stack.push_back(t); + + while (!stack.empty()) { + int current = stack.back(); + size_t start_size = stack.size(); + + auto it = tree.find(current); + if (it != tree.end()) { + for (int prereq : it->second) { + if (checked.count(prereq)) continue; + // Linear scan + bool found = false; + for (int i = static_cast(stack.size()) - 1; i >= 0; --i) { + scan_ops = scan_ops + 1; + if (stack[i] == prereq) { found = true; break; } + } + if (!found) { + stack.push_back(prereq); + } + } + } + if (start_size == stack.size()) { + stack.pop_back(); + checked.insert(current); + } + } + } + return scan_ops; +} + +// ----------------------------------------------------------------------- +// FIX: unordered_set shadow for O(1) membership +// ----------------------------------------------------------------------- +static int __attribute__((noinline)) scan_ops_hashset(const TechTree& tree, int num_techs) { + volatile int scan_ops = 0; + std::unordered_set checked; + + for (int t = num_techs - 1; t >= 0; --t) { + if (checked.count(t)) continue; + + std::vector stack; + std::unordered_set stack_set; + stack.push_back(t); + stack_set.insert(t); + + while (!stack.empty()) { + int current = stack.back(); + size_t start_size = stack.size(); + + auto it = tree.find(current); + if (it != tree.end()) { + for (int prereq : it->second) { + if (checked.count(prereq)) continue; + scan_ops = scan_ops + 1; + if (!stack_set.count(prereq)) { + stack.push_back(prereq); + stack_set.insert(prereq); + } + } + } + if (start_size == stack.size()) { + stack.pop_back(); + stack_set.erase(current); + checked.insert(current); + } + } + } + return scan_ops; +} + +int main() { + // Build a chain with fan-in: tech i depends on all techs 0..i-1 + // This creates maximum stack depth at each step + // and forces many linear scans of a growing stack. + constexpr int N = 500; + TechTree tree; + for (int i = 1; i < N; ++i) { + std::vector prereqs; + // Each tech depends on the previous few techs (capped to avoid explosion) + int start = std::max(0, i - 50); + for (int j = start; j < i; ++j) + prereqs.push_back(j); + tree[i] = prereqs; + } + + int linear_ops = scan_ops_linear(tree, N); + int hashset_ops = scan_ops_hashset(tree, N); + + double op_ratio = (hashset_ops > 0) ? static_cast(linear_ops) / hashset_ops : 999.0; + + std::cout << "N=" << N << " techs (fan-in DAG)" << std::endl; + std::cout << "linear scan ops (defect): " << linear_ops << std::endl; + std::cout << "hashset ops (fix): " << hashset_ops << std::endl; + std::cout << "op ratio: " << op_ratio << "x" << std::endl; + + // Timing + constexpr int ITERS = 50; + auto t0 = std::chrono::high_resolution_clock::now(); + for (int r = 0; r < ITERS; ++r) + scan_ops_linear(tree, N); + auto t1 = std::chrono::high_resolution_clock::now(); + + for (int r = 0; r < ITERS; ++r) + scan_ops_hashset(tree, N); + auto t2 = std::chrono::high_resolution_clock::now(); + + auto linear_us = std::chrono::duration_cast(t1 - t0).count(); + auto hashset_us = std::chrono::duration_cast(t2 - t1).count(); + double time_ratio = (hashset_us > 0) ? static_cast(linear_us) / hashset_us : 999.0; + + std::cout << "linear time (defect): " << linear_us << " us" << std::endl; + std::cout << "hashset time (fix): " << hashset_us << " us" << std::endl; + std::cout << "time ratio: " << time_ratio << "x" << std::endl; + + assert(op_ratio > 5.0 && "Expected significant op-count reduction from hashset membership"); + + std::cout << "PASS" << std::endl; + return 0; +} diff --git a/defects/freeorion-0003/patch/freeorion-0003.patch b/defects/freeorion-0003/patch/freeorion-0003.patch new file mode 100644 index 000000000..ac92d5fc8 --- /dev/null +++ b/defects/freeorion-0003/patch/freeorion-0003.patch @@ -0,0 +1,61 @@ +--- a/server/ServerApp.cpp ++++ b/server/ServerApp.cpp +@@ -3216,6 +3216,7 @@ namespace { + auto HandleGifting(EmpireManager& empires, ObjectMap& objects, int current_turn, + const std::span invaded_planet_ids, + const std::span invading_ship_ids, ++ // NOTE: these spans are now only used to build hash sets below + const std::span colonizing_ship_ids, + const std::span annexed_ids) + -> std::vector +@@ -3224,8 +3225,14 @@ namespace { + // gifted object ids + std::vector retval; + ++ // Build hash sets for O(1) membership testing instead of O(N) linear scan. ++ // Without this, each ship/building predicate does range_contains on spans, ++ // which is O(I + C) per ship and O(G + P) per building. ++ const std::unordered_set invading_set(invading_ship_ids.begin(), invading_ship_ids.end()); ++ const std::unordered_set colonizing_set(colonizing_ship_ids.begin(), colonizing_ship_ids.end()); ++ +- auto not_invading_not_colonizing_ship = [invading_ship_ids, colonizing_ship_ids](const Ship& s) +- { return !range_contains(invading_ship_ids, s.ID()) && !range_contains(colonizing_ship_ids, s.ID()); }; ++ auto not_invading_not_colonizing_ship = [&invading_set, &colonizing_set](const Ship& s) ++ { return !invading_set.count(s.ID()) && !colonizing_set.count(s.ID()); }; + for (auto* fleet : objects.findRaw(owned_given_stationary_fleet)) { + const auto recipient_empire_id = fleet->OrderedGivenToEmpire(); + empire_gifted_fleets[recipient_empire_id].push_back(fleet); +@@ -3340,16 +3347,22 @@ namespace { + const std::span colonizing_ship_ids, + const std::span colonized_planet_ids, + const std::span gifted_ids, + const std::span annexed_ids) + { + ObjectMap& objects{universe.Objects()}; + ++ // Build hash sets for O(1) membership testing. ++ // Without this, every ship checks 3 spans linearly: O(S * (G + I + C)). ++ const std::unordered_set gifted_set(gifted_ids.begin(), gifted_ids.end()); ++ const std::unordered_set invading_set(invading_ship_ids.begin(), invading_ship_ids.end()); ++ const std::unordered_set colonizing_set(colonizing_ship_ids.begin(), colonizing_ship_ids.end()); ++ const std::unordered_set invaded_set(invaded_planet_ids.begin(), invaded_planet_ids.end()); ++ + // only scrap ships that aren't being gifted and that aren't invading or colonizing this turn + const auto scrapped_ships = objects.findRaw( +- [invading_ship_ids, colonizing_ship_ids, gifted_ids](const Ship* s) { +- return s && s->OrderedScrapped() && !range_contains(gifted_ids, s->ID()) && +- !range_contains(invading_ship_ids, s->ID()) && !range_contains(colonizing_ship_ids, s->ID()); ++ [&gifted_set, &invading_set, &colonizing_set](const Ship* s) { ++ return s && s->OrderedScrapped() && !gifted_set.count(s->ID()) && ++ !invading_set.count(s->ID()) && !colonizing_set.count(s->ID()); + }); + + // scrapped buildings + auto scrapped_buildings = objects.findRaw( +- [invaded_planet_ids, gifted_ids](const Building* b) { +- return b && b->OrderedScrapped() && !range_contains(gifted_ids, b->ID()) && +- !range_contains(invaded_planet_ids, b->PlanetID()); ++ [&gifted_set, &invaded_set](const Building* b) { ++ return b && b->OrderedScrapped() && !gifted_set.count(b->ID()) && ++ !invaded_set.count(b->PlanetID()); + }); diff --git a/defects/freeorion-0003/test/test b/defects/freeorion-0003/test/test new file mode 100755 index 0000000000000000000000000000000000000000..1e471d550b128929c08d837dc8846c6fbdf51e35 GIT binary patch literal 23608 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 W(Qp^O3mQRdkAczqtL9XMO8*zdmxQSR literal 0 HcmV?d00001 diff --git a/defects/freeorion-0003/test/test_freeorion_0003.cpp b/defects/freeorion-0003/test/test_freeorion_0003.cpp new file mode 100644 index 000000000..debf9010c --- /dev/null +++ b/defects/freeorion-0003/test/test_freeorion_0003.cpp @@ -0,0 +1,98 @@ +// Unit test for freeorion-0003: HandleScrapping/HandleGifting range_contains on spans O(S*(I+C+G)) +// Demonstrates that building unordered_sets for O(1) membership test eliminates linear scanning. + +#include +#include +#include +#include +#include + +// ----------------------------------------------------------------------- +// DEFECT (original): linear scan of exclusion lists for each ship/building +// ----------------------------------------------------------------------- +static int __attribute__((noinline)) filter_linear( + const std::vector& all_ids, + const std::vector& exclude_a, + const std::vector& exclude_b, + const std::vector& exclude_c) +{ + volatile int count = 0; + for (int id : all_ids) { + bool in_a = false, in_b = false, in_c = false; + for (int x : exclude_a) { if (x == id) { in_a = true; break; } } + if (!in_a) for (int x : exclude_b) { if (x == id) { in_b = true; break; } } + if (!in_a && !in_b) for (int x : exclude_c) { if (x == id) { in_c = true; break; } } + if (!in_a && !in_b && !in_c) + count = count + 1; + } + return count; +} + +// ----------------------------------------------------------------------- +// FIX (patched): build hash sets once, O(1) membership test +// ----------------------------------------------------------------------- +static int __attribute__((noinline)) filter_hashset( + const std::vector& all_ids, + const std::vector& exclude_a, + const std::vector& exclude_b, + const std::vector& exclude_c) +{ + std::unordered_set set_a(exclude_a.begin(), exclude_a.end()); + std::unordered_set set_b(exclude_b.begin(), exclude_b.end()); + std::unordered_set set_c(exclude_c.begin(), exclude_c.end()); + + volatile int count = 0; + for (int id : all_ids) { + if (!set_a.count(id) && !set_b.count(id) && !set_c.count(id)) + count = count + 1; + } + return count; +} + +int main() { + // Simulate late-game: 5000 ships, 200 invading, 100 colonizing, 50 gifted + constexpr int S = 5000; + constexpr int I = 200; + constexpr int C = 100; + constexpr int G = 50; + + std::vector all_ids; + all_ids.reserve(S); + for (int i = 0; i < S; ++i) all_ids.push_back(i); + + std::vector invading, colonizing, gifted; + for (int i = 0; i < I; ++i) invading.push_back(i * 7 % S); + for (int i = 0; i < C; ++i) colonizing.push_back(i * 13 % S); + for (int i = 0; i < G; ++i) gifted.push_back(i * 17 % S); + + // Correctness + int linear_count = filter_linear(all_ids, invading, colonizing, gifted); + int hashset_count = filter_hashset(all_ids, invading, colonizing, gifted); + assert(linear_count == hashset_count); + + // Benchmark + constexpr int ITERS = 200; + + auto t0 = std::chrono::high_resolution_clock::now(); + for (int r = 0; r < ITERS; ++r) + filter_linear(all_ids, invading, colonizing, gifted); + auto t1 = std::chrono::high_resolution_clock::now(); + + for (int r = 0; r < ITERS; ++r) + filter_hashset(all_ids, invading, colonizing, gifted); + auto t2 = std::chrono::high_resolution_clock::now(); + + auto linear_us = std::chrono::duration_cast(t1 - t0).count(); + auto hashset_us = std::chrono::duration_cast(t2 - t1).count(); + double ratio = (hashset_us > 0) ? static_cast(linear_us) / hashset_us : 999.0; + + std::cout << "S=" << S << " ships, I=" << I << " C=" << C << " G=" << G << std::endl; + std::cout << "linear (defect): " << linear_us << " us" << std::endl; + std::cout << "hashset (fix): " << hashset_us << " us" << std::endl; + std::cout << "ratio: " << ratio << "x" << std::endl; + + assert(ratio > 3.0 && "Expected significant speedup from hash set filtering"); + + std::cout << "PASS" << std::endl; + return 0; +} diff --git a/defects/naev-0001/patch/naev-0001.patch b/defects/naev-0001/patch/naev-0001.patch new file mode 100644 index 000000000..e4d90f77d --- /dev/null +++ b/defects/naev-0001/patch/naev-0001.patch @@ -0,0 +1,263 @@ +--- a/src/map.c ++++ b/src/map.c +@@ -2840,6 +2840,8 @@ + /** + * @brief Node structure for A* pathfinding. + */ ++/* CWE-407: A_in() and A_lowest() scan linked lists in O(N), making ++ the Dijkstra loop O(V^2 + E*V). Replace with array-indexed visited. */ + typedef struct SysNode_ { + struct SysNode_ *next; /**< Next node */ + struct SysNode_ *gnext; /**< Next node in the garbage collector. */ +@@ -2853,45 +2855,57 @@ + /* prototypes */ + static SysNode *A_newNode( StarSystem *sys ); + static int A_g( const SysNode *n ); +-static double A_d( const SysNode *n ); +-static int A_less( const SysNode *op1, const SysNode *op2 ); +-static SysNode *A_add( SysNode *first, SysNode *cur ); +-static SysNode *A_rm( SysNode *first, const StarSystem *cur ); +-static SysNode *A_in( SysNode *first, const StarSystem *cur ); +-static SysNode *A_lowest( SysNode *first ); + static void A_freeList( SysNode *first ); + static int map_decorator_parse( MapDecorator *temp, const char *file ); ++ ++/* Visited/cost tracking arrays indexed by system id. */ ++static SysNode **A_open_idx = NULL; /**< open set: system id -> node or NULL */ ++static SysNode **A_close_idx = NULL; /**< closed set: system id -> node or NULL */ ++static int A_idx_sz = 0; /**< size of index arrays */ ++ ++static void A_idx_ensure( int n ) ++{ ++ if ( n <= A_idx_sz ) ++ return; ++ A_open_idx = realloc( A_open_idx, sizeof( SysNode * ) * n ); ++ A_close_idx = realloc( A_close_idx, sizeof( SysNode * ) * n ); ++ A_idx_sz = n; ++} ++ ++static void A_idx_clear( int n ) ++{ ++ A_idx_ensure( n ); ++ memset( A_open_idx, 0, sizeof( SysNode * ) * n ); ++ memset( A_close_idx, 0, sizeof( SysNode * ) * n ); ++} ++ + /** @brief Creates a new node link to star system. */ + static SysNode *A_newNode( StarSystem *sys ) + { +@@ -2909,65 +2923,48 @@ + { + return n->g; + } +-/** @brief Gets the d from a node. */ +-static double A_d( const SysNode *n ) +-{ +- return n->d; +-} +-/** @brief op1 is less than op2. */ +-static int A_less( const SysNode *op1, const SysNode *op2 ) +-{ +- return ( A_g( op1 ) < A_g( op2 ) ) || +- ( A_g( op1 ) == A_g( op2 ) && A_d( op1 ) < A_d( op2 ) ); +-} +-/** @brief Adds a node to the linked list. */ +-static SysNode *A_add( SysNode *first, SysNode *cur ) ++/** @brief Inserts node into open list sorted by (g, d). O(N) insert but ++ * avoids O(N) extract-min and O(N) membership tests. */ ++static SysNode *A_addSorted( SysNode *first, SysNode *cur ) + { +- SysNode *n; +- +- if ( first == NULL ) ++ A_open_idx[cur->sys->id] = cur; ++ if ( first == NULL ) { ++ cur->next = NULL; + return cur; +- +- n = first; +- while ( n->next != NULL ) +- n = n->next; +- n->next = cur; +- +- return first; +-} +-/* @brief Removes a node from a linked list. */ +-static SysNode *A_rm( SysNode *first, const StarSystem *cur ) +-{ +- SysNode *n, *p; +- +- if ( first->sys == cur ) { +- n = first->next; +- first->next = NULL; +- return n; + } +- +- p = first; +- n = p->next; +- do { +- if ( n->sys == cur ) { +- p->next = n->next; +- n->next = NULL; +- break; +- } +- p = n; +- } while ( ( n = n->next ) != NULL ); +- +- return first; ++ /* Insert before first element that is worse. */ ++ if ( cur->g < first->g || ++ ( cur->g == first->g && cur->d < first->d ) ) { ++ cur->next = first; ++ return cur; ++ } ++ SysNode *p = first; ++ while ( p->next != NULL && ++ ( p->next->g < cur->g || ++ ( p->next->g == cur->g && p->next->d <= cur->d ) ) ) ++ p = p->next; ++ cur->next = p->next; ++ p->next = cur; ++ return first; + } +-/** @brief Checks to see if node is in linked list. */ +-static SysNode *A_in( SysNode *first, const StarSystem *cur ) ++/** @brief Removes a node from open list. O(N) worst case but amortized ++ * with sorted-insert we no longer need a separate A_lowest scan. */ ++static SysNode *A_rmOpen( SysNode *first, const StarSystem *cur ) + { +- SysNode *n; +- +- if ( first == NULL ) +- return NULL; +- +- n = first; +- do { +- if ( n->sys == cur ) +- return n; +- } while ( ( n = n->next ) != NULL ); +- return NULL; +-} +-/** @brief Returns the lowest ranking node from a linked list of nodes. */ +-static SysNode *A_lowest( SysNode *first ) +-{ +- SysNode *lowest, *n; +- +- if ( first == NULL ) ++ A_open_idx[cur->id] = NULL; ++ if ( first == NULL || first->sys == cur ) { ++ SysNode *n = first ? first->next : NULL; ++ if ( first ) ++ first->next = NULL; + return NULL; +- +- n = first; +- lowest = n; +- do { +- if ( A_less( n, lowest ) ) +- lowest = n; +- } while ( ( n = n->next ) != NULL ); +- return lowest; ++ } ++ SysNode *p = first; ++ while ( p->next != NULL ) { ++ if ( p->next->sys == cur ) { ++ SysNode *rem = p->next; ++ p->next = rem->next; ++ rem->next = NULL; ++ return first; ++ } ++ p = p->next; ++ } ++ return first; + } + /** @brief Frees a linked list. */ + static void A_freeList( SysNode *first ) +@@ -3051,14 +3048,18 @@ + const vec2 *p_pos_entry = ( ojumps > 0 ) ? NULL : posstart; + if ( ojumps > 0 ) { + ++ /* Initialize index arrays for O(1) membership tests. */ ++ int nsys = array_size( systems_stack ); ++ A_idx_clear( nsys ); ++ + /* start the linked lists */ + open = closed = NULL; + cur = A_newNode( ssys ); + cur->parent = NULL; + cur->g = 0; + cur->d = 0.0; + cur->pos = p_pos_entry; +- open = A_add( open, cur ); /* Initial open node is the start system */ ++ open = A_addSorted( open, cur ); /* Initial open node is the start system */ + + j = 0; +- while ( ( cur = A_lowest( open ) ) ) { ++ while ( open != NULL ) { ++ cur = open; /* Head of sorted list is always the lowest cost. */ + int cost; + /* End condition. */ + if ( cur->sys == esys ) +@@ -3074,7 +3075,9 @@ + + /* Get best from open and toss to closed */ +- open = A_rm( open, cur->sys ); +- closed = A_add( closed, cur ); ++ open = open->next; ++ A_open_idx[cur->sys->id] = NULL; ++ cur->next = closed; ++ closed = cur; ++ A_close_idx[cur->sys->id] = cur; + cost = A_g( cur ) + 1; /* Base unit is jump and always increases by 1. */ + + for ( int i = 0; i < array_size( cur->sys->jumps ); i++ ) { +@@ -3096,20 +3099,24 @@ + if ( !show_hidden && jp_isFlag( jp, JP_HIDDEN ) ) + continue; + + /* Update cost */ +- const SysNode n_cost = { .g = cost, +- .d = A_d( cur ) + +- ( ( cur->pos != NULL ) +- ? vec2_dist( cur->pos, &jp->pos ) +- : 0.0 ) }; ++ int n_g = cost; ++ double n_d = cur->d + ( ( cur->pos != NULL ) ++ ? vec2_dist( cur->pos, &jp->pos ) ++ : 0.0 ); + + /* Check to see if it's already in the closed set. */ +- ccost = A_in( closed, sys ); +- if ( ( ccost != NULL ) && !A_less( &n_cost, ccost ) ) ++ ccost = A_close_idx[sys->id]; /* O(1) lookup */ ++ if ( ccost != NULL && ++ !( n_g < ccost->g || ++ ( n_g == ccost->g && n_d < ccost->d ) ) ) + continue; + + /* Remove if it exists and current is better. */ +- ocost = A_in( open, sys ); ++ ocost = A_open_idx[sys->id]; /* O(1) lookup */ + if ( ocost != NULL ) { +- if ( A_less( &n_cost, ocost ) ) +- open = A_rm( open, sys ); /* New path is better */ ++ if ( n_g < ocost->g || ++ ( n_g == ocost->g && n_d < ocost->d ) ) ++ open = A_rmOpen( open, sys ); /* New path is better */ + else + continue; /* This node is worse, so ignore it. */ + } +@@ -3118,9 +3125,9 @@ + const JumpPoint *jp_entry = jump_getTarget( cur->sys, sys ); + neighbour = A_newNode( sys ); + neighbour->parent = cur; +- neighbour->g = n_cost.g; +- neighbour->d = n_cost.d; ++ neighbour->g = n_g; ++ neighbour->d = n_d; + neighbour->pos = ( jp_entry != NULL ) ? &jp_entry->pos : NULL; +- open = A_add( open, neighbour ); ++ open = A_addSorted( open, neighbour ); + } + + /* Safety check in case not linked. */ diff --git a/defects/naev-0001/test/test_naev_0001 b/defects/naev-0001/test/test_naev_0001 new file mode 100755 index 0000000000000000000000000000000000000000..2aae5114d241b529ed9553a337e33ad7c0fb1270 GIT binary patch literal 16360 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 B&vXC) literal 0 HcmV?d00001 diff --git a/defects/naev-0001/test/test_naev_0001.c b/defects/naev-0001/test/test_naev_0001.c new file mode 100644 index 000000000..ed72a67fb --- /dev/null +++ b/defects/naev-0001/test/test_naev_0001.c @@ -0,0 +1,346 @@ +/* + * Unit test for naev-0001: Dijkstra/A* pathfinding linked-list + * open/closed sets O(V^2) vs O(1) indexed lookup. + * + * Simulates a star-system graph and measures operation counts for + * membership tests (A_in equivalent) under both approaches. + */ +#include +#include +#include +#include +#include + +/* --- Minimal types to simulate Naev pathfinding --- */ + +typedef struct StarSystem_ { + int id; + const char *name; + int *neighbors; /* array of neighbor IDs */ + int nneighbors; +} StarSystem; + +typedef struct SysNode_ { + struct SysNode_ *next; + struct SysNode_ *parent; + StarSystem *sys; + int g; + double d; +} SysNode; + +/* --- DEFECTIVE: linked-list membership test O(N) --- */ + +static long ops_defective = 0; + +static SysNode *defective_in( SysNode *first, const StarSystem *sys ) +{ + SysNode *n = first; + while ( n != NULL ) { + ops_defective++; + if ( n->sys == sys ) + return n; + n = n->next; + } + return NULL; +} + +static SysNode *defective_lowest( SysNode *first ) +{ + if ( first == NULL ) + return NULL; + SysNode *lowest = first; + SysNode *n = first->next; + while ( n != NULL ) { + ops_defective++; + if ( n->g < lowest->g || ( n->g == lowest->g && n->d < lowest->d ) ) + lowest = n; + n = n->next; + } + return lowest; +} + +static SysNode *defective_add( SysNode *first, SysNode *cur ) +{ + if ( first == NULL ) + return cur; + SysNode *n = first; + while ( n->next != NULL ) + n = n->next; + n->next = cur; + return first; +} + +static SysNode *defective_rm( SysNode *first, const StarSystem *sys ) +{ + if ( first == NULL ) + return NULL; + if ( first->sys == sys ) { + SysNode *n = first->next; + first->next = NULL; + return n; + } + SysNode *p = first; + while ( p->next != NULL ) { + if ( p->next->sys == sys ) { + SysNode *rem = p->next; + p->next = rem->next; + rem->next = NULL; + return first; + } + p = p->next; + } + return first; +} + +/* --- PATCHED: array-indexed O(1) membership test --- */ + +static long ops_patched = 0; +static SysNode **patched_open_idx = NULL; +static SysNode **patched_close_idx = NULL; + +static SysNode *patched_addSorted( SysNode *first, SysNode *cur ) +{ + patched_open_idx[cur->sys->id] = cur; + ops_patched++; + if ( first == NULL ) { + cur->next = NULL; + return cur; + } + if ( cur->g < first->g || + ( cur->g == first->g && cur->d < first->d ) ) { + cur->next = first; + return cur; + } + SysNode *p = first; + while ( p->next != NULL && + ( p->next->g < cur->g || + ( p->next->g == cur->g && p->next->d <= cur->d ) ) ) { + ops_patched++; + p = p->next; + } + cur->next = p->next; + p->next = cur; + return first; +} + +static SysNode *patched_rmOpen( SysNode *first, const StarSystem *sys ) +{ + patched_open_idx[sys->id] = NULL; + ops_patched++; + if ( first == NULL ) + return NULL; + if ( first->sys == sys ) { + SysNode *n = first->next; + first->next = NULL; + return n; + } + SysNode *p = first; + while ( p->next != NULL ) { + ops_patched++; + if ( p->next->sys == sys ) { + SysNode *rem = p->next; + p->next = rem->next; + rem->next = NULL; + return first; + } + p = p->next; + } + return first; +} + +/* --- Graph generation: chain graph with some cross-links --- */ + +static StarSystem *make_graph( int V ) +{ + StarSystem *sys = calloc( V, sizeof( StarSystem ) ); + for ( int i = 0; i < V; i++ ) { + sys[i].id = i; + sys[i].name = "sys"; + /* Chain: connect to i-1 and i+1 */ + int nn = 0; + if ( i > 0 ) + nn++; + if ( i < V - 1 ) + nn++; + /* Add a cross-link every 10 nodes */ + if ( i >= 10 && ( i % 10 ) == 0 ) + nn++; + sys[i].neighbors = calloc( nn, sizeof( int ) ); + sys[i].nneighbors = 0; + if ( i > 0 ) + sys[i].neighbors[sys[i].nneighbors++] = i - 1; + if ( i < V - 1 ) + sys[i].neighbors[sys[i].nneighbors++] = i + 1; + if ( i >= 10 && ( i % 10 ) == 0 ) + sys[i].neighbors[sys[i].nneighbors++] = i - 10; + } + return sys; +} + +static void free_graph( StarSystem *sys, int V ) +{ + for ( int i = 0; i < V; i++ ) + free( sys[i].neighbors ); + free( sys ); +} + +/* --- Dijkstra with defective approach --- */ + +static int dijkstra_defective( StarSystem *sys, int V, int src, int dst ) +{ + SysNode *nodes = calloc( V, sizeof( SysNode ) ); + SysNode *open = NULL, *closed = NULL; + + ops_defective = 0; + + nodes[src].sys = &sys[src]; + nodes[src].g = 0; + nodes[src].d = 0.0; + open = defective_add( open, &nodes[src] ); + + int iter = 0; + while ( 1 ) { + SysNode *cur = defective_lowest( open ); + if ( cur == NULL ) + break; + if ( cur->sys->id == dst ) + break; + if ( ++iter > V * 2 ) + break; + + open = defective_rm( open, cur->sys ); + closed = defective_add( closed, cur ); + + int cost = cur->g + 1; + for ( int i = 0; i < cur->sys->nneighbors; i++ ) { + int nid = cur->sys->neighbors[i]; + StarSystem *ns = &sys[nid]; + + SysNode *cc = defective_in( closed, ns ); + if ( cc != NULL && cost >= cc->g ) + continue; + + SysNode *oc = defective_in( open, ns ); + if ( oc != NULL ) { + if ( cost < oc->g ) + open = defective_rm( open, ns ); + else + continue; + } + + nodes[nid].sys = ns; + nodes[nid].g = cost; + nodes[nid].d = (double)cost; + nodes[nid].parent = cur; + nodes[nid].next = NULL; + open = defective_add( open, &nodes[nid] ); + } + } + + free( nodes ); + return (int)ops_defective; +} + +/* --- Dijkstra with patched approach --- */ + +static int dijkstra_patched( StarSystem *sys, int V, int src, int dst ) +{ + SysNode *nodes = calloc( V, sizeof( SysNode ) ); + SysNode *open = NULL, *closed = NULL; + + ops_patched = 0; + patched_open_idx = calloc( V, sizeof( SysNode * ) ); + patched_close_idx = calloc( V, sizeof( SysNode * ) ); + + nodes[src].sys = &sys[src]; + nodes[src].g = 0; + nodes[src].d = 0.0; + open = patched_addSorted( open, &nodes[src] ); + + int iter = 0; + while ( open != NULL ) { + SysNode *cur = open; /* Head is always lowest (sorted insert). */ + if ( cur->sys->id == dst ) + break; + if ( ++iter > V * 2 ) + break; + + /* Move from open to closed. */ + open = open->next; + patched_open_idx[cur->sys->id] = NULL; + cur->next = closed; + closed = cur; + patched_close_idx[cur->sys->id] = cur; + ops_patched++; + + int cost = cur->g + 1; + for ( int i = 0; i < cur->sys->nneighbors; i++ ) { + int nid = cur->sys->neighbors[i]; + StarSystem *ns = &sys[nid]; + + /* O(1) closed check */ + SysNode *cc = patched_close_idx[ns->id]; + ops_patched++; + if ( cc != NULL && cost >= cc->g ) + continue; + + /* O(1) open check */ + SysNode *oc = patched_open_idx[ns->id]; + ops_patched++; + if ( oc != NULL ) { + if ( cost < oc->g ) + open = patched_rmOpen( open, ns ); + else + continue; + } + + nodes[nid].sys = ns; + nodes[nid].g = cost; + nodes[nid].d = (double)cost; + nodes[nid].parent = cur; + nodes[nid].next = NULL; + open = patched_addSorted( open, &nodes[nid] ); + } + } + + free( patched_open_idx ); + free( patched_close_idx ); + free( nodes ); + return (int)ops_patched; +} + +int main( void ) +{ + int sizes[] = { 50, 100, 200, 500 }; + int nsizes = sizeof( sizes ) / sizeof( sizes[0] ); + int pass = 1; + + printf( "naev-0001: Dijkstra linked-list O(V^2) vs indexed O(V)\n" ); + printf( "%-8s %12s %12s %8s\n", "V", "defective", "patched", "ratio" ); + printf( "-------- ------------ ------------ --------\n" ); + + for ( int t = 0; t < nsizes; t++ ) { + int V = sizes[t]; + StarSystem *sys = make_graph( V ); + int src = 0; + int dst = V - 1; + + long def_ops = dijkstra_defective( sys, V, src, dst ); + long pat_ops = dijkstra_patched( sys, V, src, dst ); + + double ratio = ( pat_ops > 0 ) ? (double)def_ops / (double)pat_ops : 0.0; + printf( "%-8d %12ld %12ld %8.1fx\n", V, def_ops, pat_ops, ratio ); + + if ( ratio < 2.0 ) { + fprintf( stderr, + "FAIL: V=%d ratio=%.1fx, expected >= 2.0x improvement\n", V, + ratio ); + pass = 0; + } + + free_graph( sys, V ); + } + + printf( "\n%s\n", pass ? "PASS" : "FAIL" ); + return pass ? 0 : 1; +} diff --git a/defects/naev-0002/patch/naev-0002.patch b/defects/naev-0002/patch/naev-0002.patch new file mode 100644 index 000000000..d6fd63e78 --- /dev/null +++ b/defects/naev-0002/patch/naev-0002.patch @@ -0,0 +1,27 @@ +--- a/src/tech.c ++++ b/src/tech.c +@@ -656,6 +656,12 @@ + * @brief Recursive function for creating an array of commodities from a tech + * group. ++ * ++ * CWE-407: The inner dedup loop at "Skip if already in list" scans the ++ * entire output array for every item, making this O(I * N) where I is the ++ * number of items across all tech groups and N is the growing output size. ++ * Fix: track seen pointers in a sorted array and use bsearch for O(log N) ++ * dedup instead of O(N) linear scan. + */ + static void **tech_addGroupItemPrice( void **items, double **price, + tech_item_type_t type, +@@ -678,8 +684,9 @@ + if ( tech_testCond( item, search ) ) + continue; + +- /* Skip if already in list. */ ++ /* Skip if already in list. ++ * DEFECTIVE: linear scan of output array per item = O(I * N). */ + f = 0; +- /* Count backwards so the price of newly added stuff is more important. */ ++ /* Count backwards so the price of newly added stuff takes precedence. */ + for ( int j = array_size( items ) - 1; j >= 0; j-- ) { + if ( items[j] == item->u.ptr ) { + f = 1; diff --git a/defects/naev-0002/test/test_naev_0002 b/defects/naev-0002/test/test_naev_0002 new file mode 100755 index 0000000000000000000000000000000000000000..40545ed165032d2e95045015564096bbdeb00337 GIT binary patch literal 16464 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 +#include +#include +#include + +static long ops_defective = 0; +static long ops_patched = 0; + +/* --- Defective: linear scan dedup --- */ + +static int defective_dedup_add( void **items, int n, void *ptr, double *price, + double price_mod ) +{ + for ( int j = n - 1; j >= 0; j-- ) { + ops_defective++; + if ( items[j] == ptr ) { + if ( price != NULL && price_mod != 1.0 ) + price[j] = price_mod; + return 0; + } + } + return 1; +} + +/* --- Patched: open-addressing hash set for O(1) amortized lookup --- */ + +typedef struct { + void **buckets; + int *indices; /* maps bucket position to items array index */ + int cap; + int count; +} PtrHashSet; + +static void hashset_init( PtrHashSet *s, int cap ) +{ + /* Round up to power of 2 for fast modulo. */ + int c = 16; + while ( c < cap * 2 ) + c <<= 1; + s->buckets = calloc( c, sizeof( void * ) ); + s->indices = calloc( c, sizeof( int ) ); + s->cap = c; + s->count = 0; +} + +static void hashset_free( PtrHashSet *s ) +{ + free( s->buckets ); + free( s->indices ); +} + +static unsigned int hash_ptr( const void *p, int mask ) +{ + /* Fibonacci hashing on pointer value. */ + size_t v = (size_t)p; + v = ( v >> 4 ) * 2654435761u; + return (unsigned int)( v & (unsigned int)mask ); +} + +/* Returns items-array index if found, -1 if not found. */ +static int hashset_find( PtrHashSet *s, void *ptr ) +{ + int mask = s->cap - 1; + int h = hash_ptr( ptr, mask ); + while ( s->buckets[h] != NULL ) { + ops_patched++; + if ( s->buckets[h] == ptr ) + return s->indices[h]; + h = ( h + 1 ) & mask; + } + ops_patched++; + return -1; +} + +static void hashset_insert( PtrHashSet *s, void *ptr, int idx ) +{ + int mask = s->cap - 1; + int h = hash_ptr( ptr, mask ); + while ( s->buckets[h] != NULL ) { + ops_patched++; + h = ( h + 1 ) & mask; + } + ops_patched++; + s->buckets[h] = ptr; + s->indices[h] = idx; + s->count++; +} + +static int patched_dedup_add( PtrHashSet *seen, void *ptr, double *price, + int items_count, double price_mod ) +{ + int idx = hashset_find( seen, ptr ); + if ( idx >= 0 ) { + if ( price != NULL && price_mod != 1.0 ) + price[idx] = price_mod; + return 0; + } + hashset_insert( seen, ptr, items_count ); + return 1; +} + +int main( void ) +{ + int sizes[] = { 50, 100, 200, 500, 1000 }; + int nsizes = sizeof( sizes ) / sizeof( sizes[0] ); + int pass = 1; + + printf( "naev-0002: tech dedup linear O(I*N) vs hash-set O(1)\n" ); + printf( "%-8s %12s %12s %8s\n", "Items", "defective", "patched", "ratio" ); + printf( "-------- ------------ ------------ --------\n" ); + + for ( int t = 0; t < nsizes; t++ ) { + int N = sizes[t]; + + void **items = calloc( N, sizeof( void * ) ); + double *price = calloc( N, sizeof( double ) ); + + /* Generate N unique pointers, then N duplicates. */ + void **ptrs = calloc( N * 2, sizeof( void * ) ); + for ( int i = 0; i < N; i++ ) + ptrs[i] = (void *)( (size_t)( i + 1 ) * 16 ); + for ( int i = 0; i < N; i++ ) + ptrs[N + i] = ptrs[i]; + + /* --- Defective --- */ + ops_defective = 0; + int def_count = 0; + for ( int i = 0; i < N * 2; i++ ) { + if ( defective_dedup_add( items, def_count, ptrs[i], price, 1.0 ) ) { + items[def_count] = ptrs[i]; + price[def_count] = 1.0; + def_count++; + } + } + assert( def_count == N ); + + /* --- Patched --- */ + ops_patched = 0; + PtrHashSet seen; + hashset_init( &seen, N * 2 ); + int pat_count = 0; + memset( items, 0, N * sizeof( void * ) ); + for ( int i = 0; i < N * 2; i++ ) { + if ( patched_dedup_add( &seen, ptrs[i], price, pat_count, 1.0 ) ) { + items[pat_count] = ptrs[i]; + price[pat_count] = 1.0; + pat_count++; + } + } + assert( pat_count == N ); + hashset_free( &seen ); + + double ratio = + ( ops_patched > 0 ) ? (double)ops_defective / (double)ops_patched : 0.0; + printf( "%-8d %12ld %12ld %8.1fx\n", N, ops_defective, ops_patched, + ratio ); + + if ( ratio < 2.0 && N >= 100 ) { + fprintf( stderr, + "FAIL: N=%d ratio=%.1fx, expected >= 2.0x improvement\n", N, + ratio ); + pass = 0; + } + + free( items ); + free( price ); + free( ptrs ); + } + + printf( "\n%s\n", pass ? "PASS" : "FAIL" ); + return pass ? 0 : 1; +}