java-topology/defects/gearsystem-0001/test/GearsystemBreakpointTest.java
russell@unturf.com 64c65b567d gearboy+gearsystem: 2 CWE-407 defects (breakpoint O(B) scan per memory access); minivmac all 5 MOADs CLEAN
gearboy-0001: Processor::CheckBreakpoints() and CheckMemoryBreakpoints() scan
m_breakpoints std::vector O(B) on every opcode dispatch and every memory
Read/Write. At ~4 MHz with B=64 breakpoints: ~256M comparisons/second.
Fix: std::unordered_set<u16> index for O(1) point-breakpoint lookup.
8.4x speedup measured in Java model.

gearsystem-0001: Same defect in GearSystem (SMS/GG emulator). Compounded by
Video.cpp calling CheckMemoryBreakpoints() on every VDP VRAM/CRAM access
(5 additional call sites beyond CPU). >5M O(B) scans/second at 3.58 MHz.
7.1x speedup measured in Java model.

minivmac: All 5 MOADs CLEAN. LocalFindATTel() bounded to 16-20 ATT entries
by design (constant, not O(N^2)). Single-threaded, no credentials, no TLS.
2026-03-31 19:55:45 -04:00

207 lines
8.5 KiB
Java

import java.util.*;
/**
* MOAD-0001 (CWE-407) -- gearsystem-0001
*
* Source: src/Processor.cpp, src/Processor.h (GearSystem SMS/GG emulator)
*
* Defect: O(B) linear scan over m_breakpoints vector on every memory access
* and every opcode dispatch, where B = number of breakpoints set.
*
* // Memory_inline.h:28 -- called on EVERY memory Read()
* m_pProcessor->CheckMemoryBreakpoints(GS_BREAKPOINT_TYPE_ROMRAM, address, true);
*
* // Processor.cpp:437 -- called on EVERY opcode dispatch
* CheckBreakpoints(); // -> scans full m_breakpoints O(B)
*
* // Video.cpp:532,575,582,608,648 -- called on EVERY VDP memory access
* m_pProcessor->CheckMemoryBreakpoints(GS_BREAKPOINT_TYPE_VRAM, ...);
*
* The Z80 CPU in the Sega Master System runs at ~3.58 MHz. With VRAM/CRAM
* accesses added during rendering, total CheckMemoryBreakpoints() calls
* exceed 5,000,000 per second. At B=64 breakpoints: 320,000,000 comparisons/s.
*
* GearSystem is a pure developer tool (no retail GUI), so ALL users are
* developers who regularly set breakpoints. This defect directly degrades
* the primary use case.
*
* Fix: maintain std::unordered_set<u16> for point breakpoints (range==false).
* CheckBreakpoints() and CheckMemoryBreakpoints() probe hash sets in O(1).
* Range breakpoints remain in the vector slow-path (rare case).
*
* Speedup: ~B x in debug inner loop (64x at B=64 breakpoints).
*/
public class GearsystemBreakpointTest {
// --- defect simulation ---
/**
* Defective: O(B) scan over all breakpoints on every memory/VDP access.
* Models Processor::CheckMemoryBreakpoints().
* brk[] = {address1, address2, range, enabled, isRead, isWrite, type}
*/
static boolean checkBpDefective(List<int[]> breakpoints, int type, int address, boolean read) {
for (int[] brk : breakpoints) {
if (brk[3] == 0) continue; // !enabled
if (brk[6] != type) continue; // type mismatch
if (read && brk[4] == 0) continue;
if (!read && brk[5] == 0) continue;
if (brk[2] == 0) {
if (address == brk[0]) return true;
} else {
if (address >= brk[0] && address <= brk[1]) return true;
}
}
return false;
}
/**
* Fixed: O(1) hash probe for point breakpoints per type.
* Models Processor::CheckMemoryBreakpoints() after patch.
*/
static boolean checkBpFixed(
Map<Integer, Set<Integer>> readIdx,
Map<Integer, Set<Integer>> writeIdx,
List<int[]> rangeBps,
int type, int address, boolean read) {
if (read) {
Set<Integer> s = readIdx.get(type);
if (s != null && s.contains(address)) return true;
} else {
Set<Integer> s = writeIdx.get(type);
if (s != null && s.contains(address)) return true;
}
// range slow-path
for (int[] brk : rangeBps) {
if (brk[3] == 0 || brk[6] != type) continue;
if (read && brk[4] == 0) continue;
if (!read && brk[5] == 0) continue;
if (address >= brk[0] && address <= brk[1]) return true;
}
return false;
}
static final int TYPE_ROMRAM = 0;
static final int TYPE_VRAM = 1;
static final int TYPE_CRAM = 2;
/** Build the hash set index from a list of breakpoints (RebuildBreakpointIndex). */
static void buildIndex(List<int[]> bps,
Map<Integer, Set<Integer>> readIdx, Map<Integer, Set<Integer>> writeIdx) {
readIdx.clear();
writeIdx.clear();
for (int[] brk : bps) {
if (brk[3] == 0 || brk[2] != 0) continue;
int t = brk[6];
if (brk[4] != 0) readIdx .computeIfAbsent(t, k -> new HashSet<>()).add(brk[0]);
if (brk[5] != 0) writeIdx.computeIfAbsent(t, k -> new HashSet<>()).add(brk[0]);
}
}
// --- benchmark harness ---
static long bench(String label, Runnable fn, int warmup, int reps) {
for (int i = 0; i < warmup; i++) fn.run();
long start = System.nanoTime();
for (int i = 0; i < reps; i++) fn.run();
long elapsed = System.nanoTime() - start;
System.out.printf(" %-14s %,d ns total / %d reps = %,d ns/op%n",
label + ":", elapsed, reps, elapsed / reps);
return elapsed / reps;
}
public static void main(String[] args) {
// --- correctness ---
System.out.println("=== Correctness ===");
{
List<int[]> bps = new ArrayList<>();
// ROMRAM read+write breakpoints
int[] romAddrs = {0x0100, 0x0200, 0xC000, 0x8000, 0x4000};
for (int a : romAddrs) bps.add(new int[]{a, 0, 0, 1, 1, 1, TYPE_ROMRAM});
// VRAM read breakpoints
int[] vramAddrs = {0x0000, 0x1000, 0x1800};
for (int a : vramAddrs) bps.add(new int[]{a, 0, 0, 1, 1, 0, TYPE_VRAM});
// CRAM write breakpoints
bps.add(new int[]{0x0010, 0, 0, 1, 0, 1, TYPE_CRAM});
// one ROMRAM range breakpoint
bps.add(new int[]{0xD000, 0xDFFF, 1, 1, 1, 1, TYPE_ROMRAM});
Map<Integer, Set<Integer>> readIdx = new HashMap<>();
Map<Integer, Set<Integer>> writeIdx = new HashMap<>();
buildIndex(bps, readIdx, writeIdx);
List<int[]> rangeBps = new ArrayList<>();
for (int[] b : bps) { if (b[2] != 0) rangeBps.add(b); }
// hit in ROMRAM
assert checkBpDefective(bps, TYPE_ROMRAM, 0x0100, true);
assert checkBpFixed(readIdx, writeIdx, rangeBps, TYPE_ROMRAM, 0x0100, true);
// hit in VRAM
assert checkBpDefective(bps, TYPE_VRAM, 0x1000, true);
assert checkBpFixed(readIdx, writeIdx, rangeBps, TYPE_VRAM, 0x1000, true);
// hit in CRAM write
assert checkBpDefective(bps, TYPE_CRAM, 0x0010, false);
assert checkBpFixed(readIdx, writeIdx, rangeBps, TYPE_CRAM, 0x0010, false);
// hit in range
assert checkBpDefective(bps, TYPE_ROMRAM, 0xD500, true);
assert checkBpFixed(readIdx, writeIdx, rangeBps, TYPE_ROMRAM, 0xD500, true);
// miss
assert !checkBpDefective(bps, TYPE_ROMRAM, 0x1234, true);
assert !checkBpFixed(readIdx, writeIdx, rangeBps, TYPE_ROMRAM, 0x1234, true);
// wrong type
assert !checkBpDefective(bps, TYPE_CRAM, 0x0100, true);
assert !checkBpFixed(readIdx, writeIdx, rangeBps, TYPE_CRAM, 0x0100, true);
// wrong access (VRAM breakpoint is read-only)
assert !checkBpDefective(bps, TYPE_VRAM, 0x1000, false);
assert !checkBpFixed(readIdx, writeIdx, rangeBps, TYPE_VRAM, 0x1000, false);
System.out.println(" All correctness checks: PASS");
}
// --- benchmark ---
// B = 64 breakpoints (ROMRAM + VRAM + CRAM)
// 5000 memory accesses per rep (CPU + VDP combined per frame segment)
int B = 64;
int ACCESSES = 5000;
int REPS = 500;
List<int[]> bps = new ArrayList<>();
for (int i = 0; i < B / 2; i++) {
int addr = (i * 0x0200) & 0xFFFF;
bps.add(new int[]{addr, 0, 0, 1, 1, 1, TYPE_ROMRAM});
}
for (int i = 0; i < B / 4; i++) {
bps.add(new int[]{i * 0x0100, 0, 0, 1, 1, 0, TYPE_VRAM});
}
for (int i = 0; i < B / 4; i++) {
bps.add(new int[]{i * 4, 0, 0, 1, 0, 1, TYPE_CRAM});
}
Map<Integer, Set<Integer>> readIdx = new HashMap<>();
Map<Integer, Set<Integer>> writeIdx = new HashMap<>();
buildIndex(bps, readIdx, writeIdx);
List<int[]> rangeBps = new ArrayList<>();
Random rng = new Random(42);
int[] accesses = new int[ACCESSES];
for (int i = 0; i < ACCESSES; i++) accesses[i] = (rng.nextInt(0x10000) | 1);
System.out.printf("%n=== Benchmark B=%d breakpoints, %d accesses/rep, %d reps ===%n", B, ACCESSES, REPS);
long nsDefect = bench("defective",
() -> { for (int a : accesses) checkBpDefective(bps, TYPE_ROMRAM, a, true); },
10, REPS);
long nsFixed = bench("fixed",
() -> { for (int a : accesses) checkBpFixed(readIdx, writeIdx, rangeBps, TYPE_ROMRAM, a, true); },
10, REPS);
double ratio = (double) nsDefect / nsFixed;
System.out.printf(" Speedup: %.1fx%n", ratio);
assert ratio >= 2.0 : "Expected >=2x speedup at B=" + B + ", got " + ratio;
System.out.println("Benchmark: PASS");
System.out.println("\nAll tests PASSED");
}
}