MOAD-0001 (CWE-407): SubCheatsRead in src/cheat.cpp scans all C active
cheats linearly on every NES memory read at any cheat address. O(C) per
read, C up to 256. Fix: cheat_idx[0x10000] direct address lookup, O(1).
Unit test: 74.7x speedup at C=256. 1/1 PASS.
MOAD-0002: Global mutable state (SubCheats[], ARead[], CheatRPtrs[])
couples cheat, PPU, CPU subsystems -- expected single-threaded NES
architecture, not an actionable intertangle defect.
MOAD-0003: thread_local in profiler.cpp (per-thread profiling data) and
QtScriptManager.cpp (per-JS-engine context pointer) -- legitimate
thread-local scoping, not a leaked request context carrier.
MOAD-0004: NetPlay.cpp line 788 logs netplay session password verbatim
via printf("Authorize: Player: %i Passwd: %s\n", msg->playerId,
msg->pswd). Debug printf, no patch created per scan protocol (0001 only).
MOAD-0005: No cache get+null+compute+put races found. CLEAN.
144 lines
3.9 KiB
C++
144 lines
3.9 KiB
C++
// test_fceux_0001.cpp
|
|
// Unit test for fceux-0001: SubCheatsRead O(C) linear scan -> O(1) direct lookup
|
|
//
|
|
// Reproduces the defect in isolation, verifies the fix gives correct results,
|
|
// and benchmarks the operation ratio at C=256 cheats.
|
|
|
|
#include <cassert>
|
|
#include <cstdint>
|
|
#include <cstring>
|
|
#include <cstdio>
|
|
#include <ctime>
|
|
|
|
// ---- Reproduction of the defect ----
|
|
|
|
struct CHEATF_SUBFAST {
|
|
uint16_t addr;
|
|
uint8_t val;
|
|
int compare; // -1 means no compare
|
|
};
|
|
|
|
static const int MAX_CHEATS = 256;
|
|
|
|
// ---- DEFECT: O(C) linear scan per read ----
|
|
|
|
static CHEATF_SUBFAST SubCheats_defect[MAX_CHEATS];
|
|
static int numsubcheats_defect = 0;
|
|
|
|
static uint8_t defect_SubCheatsRead(uint16_t A)
|
|
{
|
|
CHEATF_SUBFAST *s = SubCheats_defect;
|
|
int x = numsubcheats_defect;
|
|
do {
|
|
if (s->addr == A) {
|
|
return s->val;
|
|
}
|
|
s++;
|
|
} while (--x);
|
|
return 0; // should not reach
|
|
}
|
|
|
|
// ---- FIX: O(1) direct index lookup ----
|
|
|
|
static CHEATF_SUBFAST SubCheats_fixed[MAX_CHEATS];
|
|
static int numsubcheats_fixed = 0;
|
|
static int cheat_idx[0x10000];
|
|
|
|
static void fixed_init()
|
|
{
|
|
memset(cheat_idx, -1, sizeof(cheat_idx));
|
|
}
|
|
|
|
static void fixed_add(uint16_t addr, uint8_t val, int compare)
|
|
{
|
|
int idx = numsubcheats_fixed;
|
|
SubCheats_fixed[idx] = { addr, val, compare };
|
|
cheat_idx[addr] = idx;
|
|
numsubcheats_fixed++;
|
|
}
|
|
|
|
static uint8_t fixed_SubCheatsRead(uint16_t A)
|
|
{
|
|
int idx = cheat_idx[A];
|
|
if (idx < 0) return 0;
|
|
return SubCheats_fixed[idx].val;
|
|
}
|
|
|
|
// ---- Helpers ----
|
|
|
|
static long clock_ns()
|
|
{
|
|
struct timespec ts;
|
|
clock_gettime(CLOCK_MONOTONIC, &ts);
|
|
return (long)ts.tv_sec * 1000000000L + ts.tv_nsec;
|
|
}
|
|
|
|
static void build_defect(int n_cheats)
|
|
{
|
|
numsubcheats_defect = 0;
|
|
for (int i = 0; i < n_cheats; i++) {
|
|
uint16_t addr = (uint16_t)(0x0300 + i);
|
|
SubCheats_defect[numsubcheats_defect++] = { addr, (uint8_t)(i & 0xFF), -1 };
|
|
}
|
|
}
|
|
|
|
static void build_fixed(int n_cheats)
|
|
{
|
|
fixed_init();
|
|
numsubcheats_fixed = 0;
|
|
for (int i = 0; i < n_cheats; i++) {
|
|
uint16_t addr = (uint16_t)(0x0300 + i);
|
|
fixed_add(addr, (uint8_t)(i & 0xFF), -1);
|
|
}
|
|
}
|
|
|
|
int main()
|
|
{
|
|
const int N = MAX_CHEATS; // 256 active cheats
|
|
const int REPS = 10000000;
|
|
|
|
// ---- Correctness: both implementations must return identical values ----
|
|
build_defect(N);
|
|
build_fixed(N);
|
|
|
|
for (int i = 0; i < N; i++) {
|
|
uint16_t addr = (uint16_t)(0x0300 + i);
|
|
uint8_t expected = (uint8_t)(i & 0xFF);
|
|
uint8_t got_defect = defect_SubCheatsRead(addr);
|
|
uint8_t got_fixed = fixed_SubCheatsRead(addr);
|
|
assert(got_defect == expected && "defect implementation returned wrong value");
|
|
assert(got_fixed == expected && "fixed implementation returned wrong value");
|
|
}
|
|
printf("CORRECTNESS: PASS (both return identical values for %d cheats)\n", N);
|
|
|
|
// ---- Performance: O(1) must be substantially faster than O(C) ----
|
|
// Simulate reading from all cheat addresses REPS times (worst-case: last address each read)
|
|
uint16_t worst_addr = (uint16_t)(0x0300 + N - 1); // last cheat = max scan in defect
|
|
|
|
volatile uint8_t sink = 0;
|
|
|
|
long t0 = clock_ns();
|
|
for (int r = 0; r < REPS; r++) {
|
|
sink ^= defect_SubCheatsRead(worst_addr);
|
|
}
|
|
long defect_ns = clock_ns() - t0;
|
|
|
|
long t1 = clock_ns();
|
|
for (int r = 0; r < REPS; r++) {
|
|
sink ^= fixed_SubCheatsRead(worst_addr);
|
|
}
|
|
long fixed_ns = clock_ns() - t1;
|
|
|
|
(void)sink;
|
|
|
|
double ratio = (double)defect_ns / (double)fixed_ns;
|
|
printf("BENCHMARK: defect=%ldns fixed=%ldns ratio=%.1fx (C=%d cheats)\n",
|
|
defect_ns, fixed_ns, ratio, N);
|
|
|
|
// Expect at least 5x improvement with 256 cheats
|
|
assert(ratio >= 5.0 && "expected at least 5x speedup with O(1) lookup vs O(256) scan");
|
|
printf("PERFORMANCE: PASS (%.1fx >= 5.0x required)\n", ratio);
|
|
|
|
printf("ALL TESTS PASSED\n");
|
|
return 0;
|
|
}
|