aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar zinc <zinc@downsta.ir> 2026-10-14 21:15:13 +0330
committerGravatar zinc <zinc@downsta.ir> 2026-10-14 21:15:13 +0330
commit3405cb8a34b8706ee2de3f110aeced3b7de2a890 (patch)
tree45fca828bd71ae55df4e60aa7d89b0cf09f4f291
parentdetach doc comments on internal bounds helpers (genome size); todo: reuse cap... (diff)
zy: a runtime trap exits 2 (rt.trap: index out of range, frame overflow, out of heap, bad pointer, use after free); compile errors stay 1; spec 8.1.3; exitstatus.sh checks exit 2
-rw-r--r--docs/SPEC.md4
-rw-r--r--src/runtime/rt.zc13
-rw-r--r--tests/exitstatus.sh8
3 files changed, 15 insertions, 10 deletions
diff --git a/docs/SPEC.md b/docs/SPEC.md
index 4936856..32afdfb 100644
--- a/docs/SPEC.md
+++ b/docs/SPEC.md
@@ -481,7 +481,7 @@ pn((int)(p + 1) - (int)p); // 1: p + 1 moves one word
3.13.3 `push(a, x)` appends and grows; `pop(a)` removes and returns the last element; `resize(a, n)` sets the length, zero-filling new elements and dropping the rest (freeing owned elements). `push` and `pop` on aggregate element types are errors (push takes scalar or reference elements; use resize for aggregates). `push`, `pop`, `resize` on a `fixed` array: error: a fixed array can't grow or shrink.
-3.13.4 indexing (outside unbounded and o3 code, 4.15): `a[i]` with `i < 0` or `i >= len(a)` prints `zy: index I out of range (length N)` on the output and stops the program (`rt.halt`, exit status 0). `pop` on an empty array reports the same with index -1. a constant index known to be out of range also produces a compile-time warning: `zc: line N: warning: out-of-bounds access: index 2 is outside length 2 (storage declared or allocated on line N)`, and the program still runs.
+3.13.4 indexing (outside unbounded and o3 code, 4.15): `a[i]` with `i < 0` or `i >= len(a)` prints `zy: index I out of range (length N)` on the output and stops the program (a runtime trap, exit status 2, 8.1.3). `pop` on an empty array reports the same with index -1. a constant index known to be out of range also produces a compile-time warning: `zc: line N: warning: out-of-bounds access: index 2 is outside length 2 (storage declared or allocated on line N)`, and the program still runs.
```
int[?] a = {1, 2};
push(a, 3); a.push(4);
@@ -1046,7 +1046,7 @@ changes to the operator and keyword tables take effect for the text read after t
8.1.2 after a unit compiles with no errors, the runtime calls `std.libinit()` if the unit includes `std`, then `main()` if a function `main` is defined (no arguments; it works in a namespaced file too), then stops the machine. a unit with no `main` runs nothing. a unit with errors does not run.
-8.1.3 the value `main` returns is discarded. the process exit status of `zy` is 0 in all cases, including compile errors and `rt.halt()`.
+8.1.3 the value `main` returns is discarded. the process exit status of `zy` (and `zym`) is 1 after compile errors, 2 after a runtime trap (an index out of range, a stack or frame overflow, out of heap memory, a bad pointer or use after free under memdebug), and 0 otherwise, including `rt.halt()`.
8.1.4 compile errors are printed on standard output (not standard error), the same stream as the program's output.
diff --git a/src/runtime/rt.zc b/src/runtime/rt.zc
index d6dcfb6..ad18f85 100644
--- a/src/runtime/rt.zc
+++ b/src/runtime/rt.zc
@@ -169,6 +169,9 @@ int get_c() { return asm { , }; }
int eof() { return asm { ? }; }
// stop the machine now. nothing after it runs, and the exit status is 0.
int halt() { asm { q } return 0; }
+// stop after a runtime trap (a bounds error, an overflow, out of memory, a bad
+// pointer) with exit status 2. a compile error exits 1, a normal halt 0.
+int trap() { syscall(7, 2, 0, 0); return 0; } // 7 is SYS_EXIT, defined further down
// write a zero-terminated string, one character at a time. it is the same as
// put_c on each byte of s, so a capture sees the text too.
int put_s(char* s) {
@@ -686,7 +689,7 @@ int scan_ptr(int p, int n) {
int frame_overflow() {
put_s("zy: frame stack overflow\n");
trace_at(1);
- halt();
+ trap();
return 0;
}
// choose the minimum size of subsequent frame segments, in words.
@@ -731,7 +734,7 @@ int oom() {
outmode = 0; outgrow = 0;
put_s("zc: out of heap memory\n");
trace_at(1);
- halt();
+ trap();
return 0;
}
// n words, untyped and not zeroed. the type word keeps -(n + 1), so fatten
@@ -1103,7 +1106,7 @@ int oob(int i, int n) {
put_n(n);
put_s(")\n");
trace_at(1);
- halt();
+ trap();
return 0;
}
// element i of a growable array h. indexing with [] does the same, with the bounds
@@ -1417,11 +1420,11 @@ int checkptr(int w) {
if (!(dbg & 1) || !w) return 0;
if (w < HEAP_LO || w >= hstop) return 0;
p = obj_base(w);
- if (!p) { put_s("zy: bad heap pointer\n"); trace_at(1); halt(); return 0; }
+ if (!p) { put_s("zy: bad heap pointer\n"); trace_at(1); trap(); return 0; }
if (!(p[-4] & 1)) {
put_s("zy: use after free from line "); put_n(site_line_of(p)); put_c(10);
trace_at(1);
- halt();
+ trap();
}
return 0;
}
diff --git a/tests/exitstatus.sh b/tests/exitstatus.sh
index d48ad1f..064aa1a 100644
--- a/tests/exitstatus.sh
+++ b/tests/exitstatus.sh
@@ -1,8 +1,9 @@
#!/bin/bash
# exit status of zy: a program that fails to compile (syntax, type or analyzer
# error, or too many errors) exits 1; a good program exits 0; a runtime trap
-# keeps its status (0 today). usage: tests/exitstatus.sh [VM]. prints one line
-# per case and exits 1 when any case is wrong.
+# (bounds, overflow, out of memory, bad pointer) exits 2. usage:
+# tests/exitstatus.sh [VM]. prints one line per case and exits 1 when any
+# case is wrong.
vm=${1:-build/bin/zy}
T=$(mktemp -d); trap 'rm -rf "$T"' EXIT
fail=0
@@ -17,5 +18,6 @@ want 1 "an undefined name" <<< $'namespace;\ninclude rt;\nfn main() { rt.put_n(n
want 1 "a type error" < tests/typeerrors/arg.zc
want 1 "an analyzer error" < tests/errors/alias_field.zc
want 1 "twenty errors" < <(printf 'namespace;\ninclude rt;\nfn main() {\n'; for i in $(seq 25); do echo " int a$i = ;"; done; echo ' return 0; }')
-want 0 "a runtime trap keeps its status" < tests/programs/bounds_heap.zc
+want 2 "a bounds trap" < tests/programs/bounds_heap.zc
+want 0 "an explicit halt" <<< $'namespace;\ninclude rt;\nfn main() { rt.put_n(1); rt.halt(); return 3; }'
exit $fail