diff --git a/defects/openttd-0001/patch/openttd-0001.patch b/defects/openttd-0001/patch/openttd-0001.patch new file mode 100644 index 000000000..14bcfea94 --- /dev/null +++ b/defects/openttd-0001/patch/openttd-0001.patch @@ -0,0 +1,29 @@ +--- a/src/economy.cpp ++++ b/src/economy.cpp +@@ -83,7 +83,7 @@ + return (int32_t)((int64_t)a * (int64_t)b >> shift); + } + +-typedef std::vector SmallIndustryList; ++typedef std::unordered_set SmallIndustryList; + + /** + * Score info, values used for computing the detailed performance rating. +@@ -1015,7 +1015,7 @@ + } + + /** The industries we've currently brought cargo to. */ +-static SmallIndustryList _cargo_delivery_destinations; ++static std::unordered_set _cargo_delivery_destinations; + + /** + * Transfer goods from station to industry. +@@ -1054,7 +1054,7 @@ + if (ind->exclusive_supplier != INVALID_OWNER && ind->exclusive_supplier != st->owner) continue; + + /* Insert the industry into _cargo_delivery_destinations, if not yet contained */ +- include(_cargo_delivery_destinations, ind); ++ _cargo_delivery_destinations.insert(ind); + + uint amount = std::min(num_pieces, 0xFFFFu - it->waiting); + it->waiting += amount; diff --git a/defects/openttd-0001/test/openttd-0001-test.cpp b/defects/openttd-0001/test/openttd-0001-test.cpp new file mode 100644 index 000000000..fcc32750f --- /dev/null +++ b/defects/openttd-0001/test/openttd-0001-test.cpp @@ -0,0 +1,124 @@ +// openttd-0001-test.cpp +// CWE-407: _cargo_delivery_destinations uses include() with O(N) linear scan +// on a std::vector inside the per-vehicle cargo delivery loop. +// Every loading tick, every vehicle delivering cargo calls include() which +// does std::ranges::find (O(N)) before inserting. With I unique industry +// destinations, total cost is O(I^2) per station per tick. +// +// Fix: Replace std::vector with std::unordered_set for O(1) insert+lookup. +// +// Defect location: src/economy.cpp, DeliverGoodsToIndustry(), +// _cargo_delivery_destinations vector with include() + +#include +#include +#include +#include +#include +#include + +// Simulate the include() pattern from container_func.hpp +template +inline bool include_vec(Container &container, typename Container::const_reference &item) +{ + const bool is_member = std::find(container.begin(), container.end(), item) != container.end(); + if (!is_member) container.emplace_back(item); + return is_member; +} + +struct FakeIndustry { + int id; +}; + +// Simulate the defective pattern: vector + include() for dedup +static long long bench_vector(int num_industries, int deliveries_per_industry) { + std::vector industries(num_industries); + for (int i = 0; i < num_industries; i++) { + industries[i] = new FakeIndustry{i}; + } + + std::vector destinations; + auto start = std::chrono::high_resolution_clock::now(); + + // Simulate deliveries: each industry gets delivered to multiple times + for (int d = 0; d < deliveries_per_industry; d++) { + for (int i = 0; i < num_industries; i++) { + include_vec(destinations, industries[i]); + } + } + + auto end = std::chrono::high_resolution_clock::now(); + long long ns = std::chrono::duration_cast(end - start).count(); + + // Verify dedup worked + assert((int)destinations.size() == num_industries); + + for (auto* p : industries) delete p; + return ns; +} + +// Simulate the fixed pattern: unordered_set for O(1) dedup +static long long bench_set(int num_industries, int deliveries_per_industry) { + std::vector industries(num_industries); + for (int i = 0; i < num_industries; i++) { + industries[i] = new FakeIndustry{i}; + } + + std::unordered_set destinations; + auto start = std::chrono::high_resolution_clock::now(); + + for (int d = 0; d < deliveries_per_industry; d++) { + for (int i = 0; i < num_industries; i++) { + destinations.insert(industries[i]); + } + } + + auto end = std::chrono::high_resolution_clock::now(); + long long ns = std::chrono::duration_cast(end - start).count(); + + assert((int)destinations.size() == num_industries); + + for (auto* p : industries) delete p; + return ns; +} + +int main() { + const int N = 500; // unique industry destinations + const int D = 10; // deliveries per industry per cycle + const int WARMUP = 3; + const int TRIALS = 5; + + printf("openttd-0001: _cargo_delivery_destinations include() O(N^2) -> unordered_set O(N)\n"); + printf("N=%d unique industries, D=%d deliveries per industry\n\n", N, D); + + // Warmup + for (int i = 0; i < WARMUP; i++) { + bench_vector(N, D); + bench_set(N, D); + } + + long long vec_total = 0, set_total = 0; + for (int t = 0; t < TRIALS; t++) { + long long vt = bench_vector(N, D); + long long st = bench_set(N, D); + vec_total += vt; + set_total += st; + printf(" Trial %d: vector=%lld ns, set=%lld ns, ratio=%.1fx\n", + t + 1, vt, st, (double)vt / st); + } + + double avg_vec = (double)vec_total / TRIALS; + double avg_set = (double)set_total / TRIALS; + double ratio = avg_vec / avg_set; + + printf("\nAverage: vector=%.0f ns, set=%.0f ns, ratio=%.1fx\n", avg_vec, avg_set, ratio); + + // PASS criteria: unordered_set must be at least 5x faster + if (ratio >= 5.0) { + printf("PASS: %.1fx speedup confirms O(N^2) -> O(N) fix\n", ratio); + return 0; + } else { + printf("FAIL: only %.1fx speedup (expected >= 5x)\n", ratio); + return 1; + } +} diff --git a/defects/openttd-0002/patch/openttd-0002.patch b/defects/openttd-0002/patch/openttd-0002.patch new file mode 100644 index 000000000..266b675b4 --- /dev/null +++ b/defects/openttd-0002/patch/openttd-0002.patch @@ -0,0 +1,58 @@ +--- a/src/rail_cmd.cpp ++++ b/src/rail_cmd.cpp +@@ -41,7 +41,7 @@ + #include "safeguards.h" + +-typedef std::vector TrainList; ++typedef std::unordered_set TrainList; + + /** + * Convert rail type for an area. +@@ -1625,7 +1625,7 @@ + MarkTileDirtyByTile(tile); + /* update power of train on this tile */ + for (Vehicle *v : VehiclesOnTile(tile)) { +- if (v->type == VEH_TRAIN) include(affected_trains, Train::From(v)->First()); ++ if (v->type == VEH_TRAIN) affected_trains.insert(Train::From(v)->First()); + } + } + } +@@ -1705,10 +1705,10 @@ + SetRailType(endtile, totype); + + for (Vehicle *v : VehiclesOnTile(tile)) { +- if (v->type == VEH_TRAIN) include(affected_trains, Train::From(v)->First()); ++ if (v->type == VEH_TRAIN) affected_trains.insert(Train::From(v)->First()); + } + for (Vehicle *v : VehiclesOnTile(endtile)) { +- if (v->type == VEH_TRAIN) include(affected_trains, Train::From(v)->First()); ++ if (v->type == VEH_TRAIN) affected_trains.insert(Train::From(v)->First()); + } + +--- a/src/road_cmd.cpp ++++ b/src/road_cmd.cpp +@@ -49,7 +49,7 @@ + #include "safeguards.h" + +-typedef std::vector RoadVehicleList; ++typedef std::unordered_set RoadVehicleList; + + /** + * Convert road type for an area. +@@ -2574,7 +2574,7 @@ + /* update power of train on this tile */ + for (Vehicle *v : VehiclesOnTile(tile)) { +- if (v->type == VEH_ROAD) include(affected_rvs, RoadVehicle::From(v)->First()); ++ if (v->type == VEH_ROAD) affected_rvs.insert(RoadVehicle::From(v)->First()); + } + +@@ -2633,10 +2633,10 @@ + + for (Vehicle *v : VehiclesOnTile(tile)) { +- if (v->type == VEH_ROAD) include(affected_rvs, RoadVehicle::From(v)->First()); ++ if (v->type == VEH_ROAD) affected_rvs.insert(RoadVehicle::From(v)->First()); + } + for (Vehicle *v : VehiclesOnTile(endtile)) { +- if (v->type == VEH_ROAD) include(affected_rvs, RoadVehicle::From(v)->First()); ++ if (v->type == VEH_ROAD) affected_rvs.insert(RoadVehicle::From(v)->First()); + } diff --git a/defects/openttd-0002/test/openttd-0002-test.cpp b/defects/openttd-0002/test/openttd-0002-test.cpp new file mode 100644 index 000000000..5c9004dca --- /dev/null +++ b/defects/openttd-0002/test/openttd-0002-test.cpp @@ -0,0 +1,128 @@ +// openttd-0002-test.cpp +// CWE-407: affected_trains/affected_rvs vectors in CmdConvertRail/CmdConvertRoad +// use include() with O(N) linear scan for dedup during area track conversion. +// When converting a large area with T tiles and V unique vehicles, each tile +// calls include() which scans the full vector. Total cost: O(T * V). +// +// Fix: Replace std::vector/std::vector with +// std::unordered_set for O(1) insert+dedup. +// +// Defect locations: +// src/rail_cmd.cpp: CmdConvertRail(), affected_trains TrainList +// src/road_cmd.cpp: CmdConvertRoad(), affected_rvs RoadVehicleList + +#include +#include +#include +#include +#include +#include + +// Simulate include() from container_func.hpp +template +inline bool include_vec(Container &container, typename Container::const_reference &item) +{ + const bool is_member = std::find(container.begin(), container.end(), item) != container.end(); + if (!is_member) container.emplace_back(item); + return is_member; +} + +struct FakeTrain { + int id; +}; + +// Defective: vector + include() for dedup across tiles +static long long bench_vector(int num_tiles, int vehicles_per_tile, int unique_vehicles) { + // Create unique vehicles + std::vector all_vehicles(unique_vehicles); + for (int i = 0; i < unique_vehicles; i++) { + all_vehicles[i] = new FakeTrain{i}; + } + + std::vector affected; + auto start = std::chrono::high_resolution_clock::now(); + + // Simulate iterating tiles, each tile has some vehicles + for (int t = 0; t < num_tiles; t++) { + for (int v = 0; v < vehicles_per_tile; v++) { + // Vehicle index wraps around unique vehicles + FakeTrain* train = all_vehicles[(t * vehicles_per_tile + v) % unique_vehicles]; + include_vec(affected, train); + } + } + + auto end = std::chrono::high_resolution_clock::now(); + long long ns = std::chrono::duration_cast(end - start).count(); + + assert((int)affected.size() == unique_vehicles); + + for (auto* p : all_vehicles) delete p; + return ns; +} + +// Fixed: unordered_set for O(1) dedup +static long long bench_set(int num_tiles, int vehicles_per_tile, int unique_vehicles) { + std::vector all_vehicles(unique_vehicles); + for (int i = 0; i < unique_vehicles; i++) { + all_vehicles[i] = new FakeTrain{i}; + } + + std::unordered_set affected; + auto start = std::chrono::high_resolution_clock::now(); + + for (int t = 0; t < num_tiles; t++) { + for (int v = 0; v < vehicles_per_tile; v++) { + FakeTrain* train = all_vehicles[(t * vehicles_per_tile + v) % unique_vehicles]; + affected.insert(train); + } + } + + auto end = std::chrono::high_resolution_clock::now(); + long long ns = std::chrono::duration_cast(end - start).count(); + + assert((int)affected.size() == unique_vehicles); + + for (auto* p : all_vehicles) delete p; + return ns; +} + +int main() { + // Simulate converting a 50x50 tile area with 3 vehicles per tile, 500 unique + const int TILES = 2500; + const int VEH_PER_TILE = 3; + const int UNIQUE = 500; + const int WARMUP = 3; + const int TRIALS = 5; + + printf("openttd-0002: affected_trains/affected_rvs include() O(T*V) -> unordered_set O(T)\n"); + printf("T=%d tiles, V=%d veh/tile, U=%d unique vehicles\n\n", TILES, VEH_PER_TILE, UNIQUE); + + for (int i = 0; i < WARMUP; i++) { + bench_vector(TILES, VEH_PER_TILE, UNIQUE); + bench_set(TILES, VEH_PER_TILE, UNIQUE); + } + + long long vec_total = 0, set_total = 0; + for (int t = 0; t < TRIALS; t++) { + long long vt = bench_vector(TILES, VEH_PER_TILE, UNIQUE); + long long st = bench_set(TILES, VEH_PER_TILE, UNIQUE); + vec_total += vt; + set_total += st; + printf(" Trial %d: vector=%lld ns, set=%lld ns, ratio=%.1fx\n", + t + 1, vt, st, (double)vt / st); + } + + double avg_vec = (double)vec_total / TRIALS; + double avg_set = (double)set_total / TRIALS; + double ratio = avg_vec / avg_set; + + printf("\nAverage: vector=%.0f ns, set=%.0f ns, ratio=%.1fx\n", avg_vec, avg_set, ratio); + + if (ratio >= 5.0) { + printf("PASS: %.1fx speedup confirms O(T*V) -> O(T) fix\n", ratio); + return 0; + } else { + printf("FAIL: only %.1fx speedup (expected >= 5x)\n", ratio); + return 1; + } +}