java-topology/defects/minetest-0004/test/minetest-0004-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

83 lines
2.8 KiB
C++

// minetest-0004-test: CWE-407 nodeboxConnects sorted vector linear scan
// Defect: CONTAINS() on SORT_AND_UNIQUE'd connects_to_ids uses std::find O(N)
// instead of std::binary_search O(log N). Called 6x per connected
// nodebox node during mesh generation.
// Fix: std::binary_search on the already-sorted vector.
//
// Severity: MEDIUM
// Location: src/nodedef.cpp:1288,1295
#include <vector>
#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_linear(const std::vector<content_t> &ids,
const std::vector<content_t> &queries) {
auto start = std::chrono::high_resolution_clock::now();
int found = 0;
for (content_t q : queries) {
if (CONTAINS(ids, q))
found++;
}
auto end = std::chrono::high_resolution_clock::now();
assert(found >= 0);
return std::chrono::duration_cast<std::chrono::nanoseconds>(end - start).count();
}
static long long bench_binary(const std::vector<content_t> &ids,
const std::vector<content_t> &queries) {
auto start = std::chrono::high_resolution_clock::now();
int found = 0;
for (content_t q : queries) {
if (std::binary_search(ids.begin(), ids.end(), q))
found++;
}
auto end = std::chrono::high_resolution_clock::now();
assert(found >= 0);
return std::chrono::duration_cast<std::chrono::nanoseconds>(end - start).count();
}
int main() {
// Modded game: connected nodebox with 30 connection targets
// Mesh generation: 6 faces * ~1000 connected nodes per mapblock = 6000 lookups
const int IDS_SIZE = 30;
const int LOOKUPS = 6000;
std::vector<content_t> ids;
for (int i = 0; i < IDS_SIZE; i++)
ids.push_back(10 + i * 4);
std::sort(ids.begin(), ids.end()); // SORT_AND_UNIQUE already applied
std::vector<content_t> queries(LOOKUPS);
for (int i = 0; i < LOOKUPS; i++) {
if (i % 3 == 0)
queries[i] = ids[i % IDS_SIZE]; // hit
else
queries[i] = 5 + (i % 200); // likely miss
}
// Warmup
bench_linear(ids, queries);
bench_binary(ids, queries);
long long lin_ns = bench_linear(ids, queries);
long long bin_ns = bench_binary(ids, queries);
double ratio = (double)lin_ns / (double)bin_ns;
printf("=== minetest-0004: nodeboxConnects sorted vector scan ===\n");
printf("connects_to_ids size: %d, lookups: %d\n", IDS_SIZE, LOOKUPS);
printf("linear (std::find): %lld ns\n", lin_ns);
printf("binary (binary_search): %lld ns\n", bin_ns);
printf("ratio: %.1fx\n", ratio);
assert(ratio >= 1.5 && "FAIL: binary_search should be faster on sorted data");
printf("PASS\n");
return 0;
}