From 5529e3dc14322672bbc78e2c455677808d003162 Mon Sep 17 00:00:00 2001 From: Sebastian Jeong Date: Sun, 16 Aug 2026 22:23:41 +0900 Subject: [PATCH] test: prove --no-checks removes the bounds check The bounds-no-checks cases emitted and built but never ran, so they only proved that --no-checks produces compilable C -- not that it removes the check, which is the entire point of the flag. A run case could not simply be appended. BOUNDS.FE returns the out-of-bounds element directly, so with checks removed its exit code is whatever sits past the array on the stack and there is no correct status to assert. Asserting on the generated C instead does not work either: emit_c.c defines fe_trap_bounds unconditionally and --no-checks only suppresses the call sites. Add NOCHK.FE, which reads one element past a [2]i32 and returns x - x. That is 0 for whatever garbage the unchecked read produced, so the same source has a defined outcome both ways: compiled with checks it must trap, compiled with --no-checks it must run to completion and exit 0. Register both halves and drop the two BOUNDS-N cases they supersede. Verified in DOSBox-X: 155 passed, including m3-nochk-trap failing as expected and m3-nochk-off-run succeeding. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01BScg8CF1sAAM2zVHAu5zvW --- fec/tests/m3/nochk.fe | 7 +++++++ src/ferrolang_vm/registry.py | 16 ++++++++++++---- 2 files changed, 19 insertions(+), 4 deletions(-) create mode 100644 fec/tests/m3/nochk.fe diff --git a/fec/tests/m3/nochk.fe b/fec/tests/m3/nochk.fe new file mode 100644 index 0000000..e8e62f3 --- /dev/null +++ b/fec/tests/m3/nochk.fe @@ -0,0 +1,7 @@ +unit m3_no_checks; + +fn main() -> i32 { + let a: [2]i32 = [1, 2]; + let x: i32 = a[2]; + return x - x; +} diff --git a/src/ferrolang_vm/registry.py b/src/ferrolang_vm/registry.py index 5478bbc..74d129a 100644 --- a/src/ferrolang_vm/registry.py +++ b/src/ferrolang_vm/registry.py @@ -146,10 +146,18 @@ CASES: list[Case] = [ # These two must trap at runtime: the bounds check is the feature under test. *_triple(3, "bounds", M3, run_suffix="trap", run_ok=False), *_triple(3, "slcbound", M3, run_suffix="trap", run_ok=False), - _case(3, "bounds-no-checks-emit", - _emit(_fe(M3, "bounds"), f"{M3}\\BOUNDS-N.C", flags=("--no-checks",))), - _case(3, "bounds-no-checks-build", - _wcl(f"{M3}\\BOUNDS-N.EXE", f"{M3}\\BOUNDS-N.C")), + # --no-checks is proved by a differential on one source. NOCHK.FE reads one + # element past a [2]i32 and returns x - x, which is 0 whatever garbage the + # unchecked read produced: compiled with checks it must trap, compiled with + # --no-checks it must run to completion. BOUNDS.FE cannot serve as the + # unchecked half because it returns the out-of-bounds value directly, so its + # exit code would be whatever happens to sit past the array on the stack. + *_triple(3, "nochk", M3, run_suffix="trap", run_ok=False), + _case(3, "nochk-off-emit", + _emit(_fe(M3, "nochk"), f"{M3}\\NOCHK-N.C", flags=("--no-checks",))), + _case(3, "nochk-off-build", + _wcl(f"{M3}\\NOCHK-N.EXE", f"{M3}\\NOCHK-N.C")), + _case(3, "nochk-off-run", f"{M3}\\NOCHK-N.EXE"), *_rejects(3, M3, ("badfld", "badmat", "badarr", "badcycle", "badstr", "badchar", "badfield", "badindex"), suffix="reject"),