java-topology/defects/minetest-0002/test/minetest-0002-test.cpp
russell@unturf.com a86a765544 minetest (Luanti): 5 CWE-407 defects, all 5 MOADs scanned
minetest-0001: mg_ore.cpp c_wherein vector CONTAINS in voxel inner loop O(V*W) HIGH 3.3x
minetest-0002: mg_decoration.cpp c_place_on/c_spawnby vector CONTAINS O(S*P) MEDIUM 1.8x
minetest-0003: l_env.cpp find_node_near/find_nodes_in_area filter CONTAINS O(V*F) MEDIUM 1.6x
minetest-0004: nodedef.cpp nodeboxConnects sorted vector linear scan O(N) MEDIUM 2.1x
minetest-0005: blockmodifier.cpp ABM neighbor check sorted vector O(N) LOW-MEDIUM 1.5x

MOAD-0002: g_settings global singleton (architectural, not patchable)
MOAD-0003: thread_local log streams (properly scoped, not leaked context)
MOAD-0004: CLEAN (no credential logging found)
MOAD-0005: CLEAN (no unsynchronized cache patterns found)
2026-03-31 10:10:03 -04:00

92 lines
3.2 KiB
C++

// minetest-0002-test: CWE-407 decoration c_place_on/c_spawnby linear scan
// Defect: CONTAINS(c_place_on, content) and CONTAINS(c_spawnby, content)
// use std::find O(N) per decoration placement candidate.
// Fix: std::unordered_set<content_t> for O(1) lookup.
//
// Severity: MEDIUM
// Location: src/mapgen/mg_decoration.cpp, canPlaceDecoration()
// Pattern: CONTAINS(c_place_on, ...) and CONTAINS(c_spawnby, ...)
#include <vector>
#include <unordered_set>
#include <algorithm>
#include <chrono>
#include <cassert>
#include <cstdio>
#include <cstdint>
using content_t = uint16_t;
#define CONTAINS(c, v) (std::find((c).begin(), (c).end(), (v)) != (c).end())
static long long bench_vector(const std::vector<content_t> &place_on,
const std::vector<content_t> &nodes, int reps) {
auto start = std::chrono::high_resolution_clock::now();
int matches = 0;
for (int r = 0; r < reps; r++) {
for (content_t c : nodes) {
if (CONTAINS(place_on, c))
matches++;
}
}
auto end = std::chrono::high_resolution_clock::now();
assert(matches >= 0);
return std::chrono::duration_cast<std::chrono::nanoseconds>(end - start).count();
}
static long long bench_set(const std::unordered_set<content_t> &place_on,
const std::vector<content_t> &nodes, int reps) {
auto start = std::chrono::high_resolution_clock::now();
int matches = 0;
for (int r = 0; r < reps; r++) {
for (content_t c : nodes) {
if (place_on.count(c) > 0)
matches++;
}
}
auto end = std::chrono::high_resolution_clock::now();
assert(matches >= 0);
return std::chrono::duration_cast<std::chrono::nanoseconds>(end - start).count();
}
int main() {
// Modded game: 50 place_on node types (group:soil etc.),
// 6400 surface nodes per mapchunk (80x80), 50 repetitions (50 decos)
const int PLACE_ON_SIZE = 50;
const int SURFACE_NODES = 6400;
const int REPS = 50;
std::vector<content_t> place_on_vec;
std::unordered_set<content_t> place_on_set;
for (int i = 0; i < PLACE_ON_SIZE; i++) {
content_t id = 50 + i * 5;
place_on_vec.push_back(id);
place_on_set.insert(id);
}
std::vector<content_t> surface(SURFACE_NODES);
for (int i = 0; i < SURFACE_NODES; i++) {
if (i % 5 == 0)
surface[i] = place_on_vec[i % PLACE_ON_SIZE];
else
surface[i] = 1 + (i % 30);
}
bench_vector(place_on_vec, surface, 1);
bench_set(place_on_set, surface, 1);
long long vec_ns = bench_vector(place_on_vec, surface, REPS);
long long set_ns = bench_set(place_on_set, surface, REPS);
double ratio = (double)vec_ns / (double)set_ns;
printf("=== minetest-0002: decoration c_place_on membership ===\n");
printf("place_on size: %d, surface nodes: %d\n", PLACE_ON_SIZE, SURFACE_NODES);
printf("vector (std::find): %lld ns\n", vec_ns);
printf("unordered_set (count): %lld ns\n", set_ns);
printf("ratio: %.1fx\n", ratio);
fflush(stdout);
assert(ratio >= 1.5 && "FAIL: unordered_set should be at least 1.5x faster");
printf("PASS\n");
return 0;
}