aboutsummaryrefslogtreecommitdiff
path: root/tests
diff options
context:
space:
mode:
authorGravatar zinc <zinc@downsta.ir> 2026-10-14 16:25:05 +0330
committerGravatar zinc <zinc@downsta.ir> 2026-10-14 16:25:05 +0330
commit8a7092df7b7fc8a5eb4f02ef14588246bd111daf (patch)
tree0d9d28ca93ccbddb1f99cb02b88ed466e0ade481 /tests
parenthistory: 43-stdlib notes point forward to zc0 no longer reserving u, m and d (diff)
parentmerge bounds checking (diff)
Merge branch 'main' into work/xref
Diffstat (limited to 'tests')
-rw-r--r--tests/conformance/3-expressions/dyn_array_const_index_warning.zc2
-rw-r--r--tests/conformance/3-expressions/dyn_array_index_negative.zc2
-rw-r--r--tests/conformance/3-expressions/dyn_array_index_out_of_range.zc2
-rw-r--r--tests/conformance/3-expressions/dyn_array_pop_empty.zc2
-rw-r--r--tests/parity.known3
-rw-r--r--tests/programs/bounds_fixed.zc17
-rw-r--r--tests/programs/bounds_heap.zc16
-rw-r--r--tests/programs/bounds_hi.zc3
-rw-r--r--tests/programs/boundsrules.zc24
-rw-r--r--tests/programs/o3_bounds.zc3
-rw-r--r--tests/programs/o3call.zc2
-rw-r--r--tests/programs/zc0unbounded.zc16
-rwxr-xr-xtests/test.sh11
13 files changed, 93 insertions, 10 deletions
diff --git a/tests/conformance/3-expressions/dyn_array_const_index_warning.zc b/tests/conformance/3-expressions/dyn_array_const_index_warning.zc
index 36890d7..9639128 100644
--- a/tests/conformance/3-expressions/dyn_array_const_index_warning.zc
+++ b/tests/conformance/3-expressions/dyn_array_const_index_warning.zc
@@ -1,4 +1,4 @@
-// expect: zc: line 5: warning: out-of-bounds access: index 2 is outside length 2 (storage declared or allocated on line 4)/zy: index 2 out of range (length 2)/
+// expect: zc: line 5: warning: out-of-bounds access: index 2 is outside length 2 (storage declared or allocated on line 4)/zy: index 2 out of range (length 2)/ at <stdin>:5 in main/
include rt;
fn main() {
int[?] a = {1, 2};
diff --git a/tests/conformance/3-expressions/dyn_array_index_negative.zc b/tests/conformance/3-expressions/dyn_array_index_negative.zc
index c87e786..023951e 100644
--- a/tests/conformance/3-expressions/dyn_array_index_negative.zc
+++ b/tests/conformance/3-expressions/dyn_array_index_negative.zc
@@ -1,4 +1,4 @@
-// expect: zy: index -1 out of range (length 2)/
+// expect: zy: index -1 out of range (length 2)/ at <stdin>:7 in main/
include rt;
void pn(int x) { rt.put_n(x); rt.put_c(10); }
fn main() {
diff --git a/tests/conformance/3-expressions/dyn_array_index_out_of_range.zc b/tests/conformance/3-expressions/dyn_array_index_out_of_range.zc
index eff8061..e613b70 100644
--- a/tests/conformance/3-expressions/dyn_array_index_out_of_range.zc
+++ b/tests/conformance/3-expressions/dyn_array_index_out_of_range.zc
@@ -1,4 +1,4 @@
-// expect: 2/zy: index 2 out of range (length 2)/
+// expect: 2/zy: index 2 out of range (length 2)/ at <stdin>:8 in main/
include rt;
void pn(int x) { rt.put_n(x); rt.put_c(10); }
fn main() {
diff --git a/tests/conformance/3-expressions/dyn_array_pop_empty.zc b/tests/conformance/3-expressions/dyn_array_pop_empty.zc
index 6f5fdd0..ce08fca 100644
--- a/tests/conformance/3-expressions/dyn_array_pop_empty.zc
+++ b/tests/conformance/3-expressions/dyn_array_pop_empty.zc
@@ -1,4 +1,4 @@
-// expect: zy: index -1 out of range (length 0)/
+// expect: zy: index -1 out of range (length 0)/ at <stdin>:6 in main/
include rt;
void pn(int x) { rt.put_n(x); rt.put_c(10); }
fn main() {
diff --git a/tests/parity.known b/tests/parity.known
index 0a73eb3..6892499 100644
--- a/tests/parity.known
+++ b/tests/parity.known
@@ -13,3 +13,6 @@ tests/programs/uaf_in.zc # memdebug on: only the self-hosted compiler emits the
examples/lists.zc # ownership: the block's owner frees at its end, zc0 never frees
# the next program reads a local it never sets when stdin is empty, so it prints stack garbage
tests/programs/bench_strings.zc # reads the repeat count from stdin; with none, acc is uninitialized
+# zc0 reads unbounded and ignores it, and emits no index checks at all
+tests/programs/bounds_fixed.zc # bounds checks: only the self-hosted compiler checks a fixed array index
+tests/programs/bounds_heap.zc # bounds checks: only the self-hosted compiler checks a pointer into a heap block
diff --git a/tests/programs/bounds_fixed.zc b/tests/programs/bounds_fixed.zc
new file mode 100644
index 0000000..0b3f476
--- /dev/null
+++ b/tests/programs/bounds_fixed.zc
@@ -0,0 +1,17 @@
+namespace;
+include rt;
+
+// a fixed array T[n] with a variable index is checked against n. a loop whose
+// test is the constant n proves the index and drops the check; a constant
+// index is checked when it is compiled.
+int main() {
+ int[6] t; int i, s = 0, k = 6;
+ for (i = 0; i < 6; i++) t[i] = i * i;
+ for (i = 0; i < 6; i++) s = s + t[i];
+ rt.put_n(s); rt.put_c(10);
+ i = 2; rt.put_n(t[i] + t[5]); rt.put_c(10);
+ i = 0;
+ while (i <= k) { s = t[i]; i++; }
+ rt.put_s("not reached\n");
+ return 0;
+}
diff --git a/tests/programs/bounds_heap.zc b/tests/programs/bounds_heap.zc
new file mode 100644
index 0000000..d0bd318
--- /dev/null
+++ b/tests/programs/bounds_heap.zc
@@ -0,0 +1,16 @@
+namespace;
+include rt;
+
+// a pointer to the start of a live heap block is checked against the block's
+// payload, which a buddy block rounds up: rt.new(4) holds 11 words. a pointer
+// into the middle of a block, or to memory that is not a heap block, is not
+// checked. a char is a word, so a char* counts words.
+int main() {
+ int* p = rt.new(4); int* q; char* c = rt.new(2); int i = 10;
+ p[i] = 3; rt.put_n(p[i]); rt.put_c(10);
+ q = p + 1; i = 10; rt.put_n(q[i - 1] - 3); rt.put_c(10);
+ i = 2; c[i] = 'x'; rt.put_c(c[i]); rt.put_c(10);
+ i = 11;
+ rt.put_n(p[i]); rt.put_c(10);
+ return 0;
+}
diff --git a/tests/programs/bounds_hi.zc b/tests/programs/bounds_hi.zc
index 49639c5..5475764 100644
--- a/tests/programs/bounds_hi.zc
+++ b/tests/programs/bounds_hi.zc
@@ -1,7 +1,8 @@
namespace;
include rt;
-// the index is not the induction variable. i + 1 walks off the end.
+// the index is not the induction variable. i + 1 walks off the end, but o3
+// implies unbounded: nothing checks it, and the write lands in the block's slack
o3 fn main() {
fixed int[?] f = rt.new(fixed int[?], 4);
int n = len(f), i;
diff --git a/tests/programs/boundsrules.zc b/tests/programs/boundsrules.zc
new file mode 100644
index 0000000..1ae6a56
--- /dev/null
+++ b/tests/programs/boundsrules.zc
@@ -0,0 +1,24 @@
+namespace;
+include rt;
+
+// unbounded turns the checks off: as a function prefix (in any order with
+// unchecked), as a block, for one statement. o3 implies it. the last read is
+// checked and stops the program.
+int[8] pad;
+int[4] a;
+
+unbounded int pre(int i) { return a[i] * 0 + 1; }
+unchecked unbounded int both(int i) { return a[i] * 0 + 1; }
+o3 int fast(int i) { return a[i] * 0 + 1; }
+int blk(int i) { int r = 0; unbounded { r = a[i] * 0 + 1; } return r; }
+int one(int i) { int r = 0; unbounded r = a[i] * 0 + 1; return r; }
+int checked(int i) { return a[i]; }
+
+int main() {
+ int i = 5;
+ a[3] = 7;
+ rt.put_n(pre(i) + both(i) + fast(i) + blk(i) + one(i)); rt.put_c(10);
+ rt.put_n(checked(3)); rt.put_c(10);
+ rt.put_n(checked(i)); rt.put_c(10);
+ return 0;
+}
diff --git a/tests/programs/o3_bounds.zc b/tests/programs/o3_bounds.zc
index eaf92eb..cd65548 100644
--- a/tests/programs/o3_bounds.zc
+++ b/tests/programs/o3_bounds.zc
@@ -1,5 +1,6 @@
namespace;
include rt;
-// an index outside a T[?] inside o3 code: caught by the inline check
+// an index outside a T[?] inside o3 code: o3 implies unbounded, so nothing checks it
+// (the write lands in the slack of the buddy block) and the program ends normally
fn main() { int[?] a = rt.new(int[?], 5); o3 { int i = 7; a[i] = 1; } rt.free(d*a); return 0; }
diff --git a/tests/programs/o3call.zc b/tests/programs/o3call.zc
index d2292ad..3ad977b 100644
--- a/tests/programs/o3call.zc
+++ b/tests/programs/o3call.zc
@@ -19,7 +19,7 @@ unchecked o3 int nest(int n) {
if (n == 0) return 0;
return n + nest(n - 1);
}
-unchecked fn main() {
+unchecked unbounded fn main() { // reads p[-1], the type word, on purpose
node* n = leaf(4);
int* p = n;
rt.put_n(p[-1] < 0); rt.put_c(' ');
diff --git a/tests/programs/zc0unbounded.zc b/tests/programs/zc0unbounded.zc
new file mode 100644
index 0000000..67c3ae0
--- /dev/null
+++ b/tests/programs/zc0unbounded.zc
@@ -0,0 +1,16 @@
+namespace;
+include rt;
+
+// unbounded in all three places zc0 accepts it (and ignores): the unit, a
+// function prefix and a statement. both compilers print the same.
+unbounded;
+int[4] a;
+unbounded int get(int i) { return a[i]; }
+int main() {
+ int i = 2, r = 0;
+ a[2] = 40;
+ unbounded r = get(i) + 2;
+ unbounded { r = r + a[i - 2]; }
+ rt.put_n(r); rt.put_c(10);
+ return 0;
+}
diff --git a/tests/test.sh b/tests/test.sh
index a4e58e8..7a80c3c 100755
--- a/tests/test.sh
+++ b/tests/test.sh
@@ -220,10 +220,14 @@ ABC" "$l o3 lifts byte loads and stores"
chk "$($vm < tests/programs/o3licm.zc)" "3184200 7950 315 50 260 -56" "$l o3 hoists loop invariants and shares values between blocks"
chk "$($vm < tests/programs/o3deep.zc)" "30000" "$l o3 recursion thirty thousand deep"
chk "$($vm < tests/programs/o3deep_over.zc 2>&1)" "100000" "$l o3 recursion grows past the original stack"
- chk "$($vm < tests/programs/o3_bounds.zc 2>&1 | head -1)" "zy: index 7 out of range (length 5)" "$l o3 bounds check"
+ chk "$($vm < tests/programs/o3_bounds.zc 2>&1; echo "rc=$?")" "rc=0" "$l o3 implies unbounded: no check on a T[?] index"
chk "$($vm < tests/programs/bounds.zc)" "55 55 8" "$l a proved +1 loop skips the bounds check"
chk "$($vm < tests/programs/bounds_lo.zc 2>&1 | head -1)" "zy: index -1 out of range (length 4)" "$l a negative loop index is still checked"
- chk "$($vm < tests/programs/bounds_hi.zc 2>&1 | head -1)" "zy: index 4 out of range (length 4)" "$l an index that is not the induction variable is still checked"
+ chk "$($vm < tests/programs/bounds_hi.zc 2>&1; echo "rc=$?")" "rc=0" "$l o3 implies unbounded: an index that is not the induction variable is not checked"
+ chk "$($vm < tests/programs/boundsrules.zc 2>&1 | head -3)" $'5\n7\nzy: index 5 out of range (length 4)' "$l unbounded as prefix, block and statement, o3 implies it; checked code reports"
+ chk "$($vm < tests/programs/bounds_fixed.zc 2>&1 | head -3)" $'55\n29\nzy: index 6 out of range (length 6)' "$l a fixed array with a variable index is checked against its length"
+ chk "$($vm < tests/programs/bounds_heap.zc 2>&1 | head -4)" $'3\n0\nx\nzy: index 11 out of range (length 11)' "$l a pointer to a heap block is checked against its payload"
+ chk "$($vm < tests/programs/zc0unbounded.zc)" "42" "$l unbounded in the unit, a prefix and a statement"
local r; r=$(bash tests/runerr.sh "$vm" tests/errors | tail -1)
chk "$r" "rejected: $(ls tests/errors/*.zc | wc -l | tr -d ' ') passed, 0 failed" "$l analyzer rejects every bad program"
r=$(bash tests/runerr.sh "$vm" tests/typeerrors | tail -1)
@@ -357,7 +361,7 @@ s_runonly() {
runonly() { # $1 label, $2 source. the program is already in the image, so it reads no source.
# the map is sized to the program: src/tools/automap.sh lays it out for the image's own length
local a b d=$T/run-$1 input=$2
- if [ "$1" = gc ]; then input=$T/collector-progress.zc; sed 's/f >= 990/f > 0/' "$2" > "$input"; fi
+ if [ "$1" = gc ]; then input=$T/collector-progress.zc; sed 's/f >= 950/f > 0/' "$2" > "$input"; fi
GENMAP=$T/genmap bash src/tools/automap.sh run $d src/runtime/run.zc "$input" >/dev/null || { chk 0 1 "run $1 builds"; return; }
cp src/micro/micro.c src/micro/micro_port.h src/micro/port_linux.c src/micro/port_stub.c src/core/fops.h src/core/bops.h src/core/stack.h src/core/genome.h $d/
$CC -pthread -DZY_THREADS -O2 -w -fwrapv $d/micro.c $d/port_linux.c -o $T/zymr || { chk 0 1 "run $1 links"; return; }
@@ -370,6 +374,7 @@ s_runonly() {
runonly wide tests/programs/wide.zc
runonly selfpatch examples/selfpatch.zc
runonly gc tests/programs/gc.zc
+ runonly zc0unbounded tests/programs/zc0unbounded.zc # zc0 accepts unbounded in all three places and ignores it
runonly voidmain tests/programs/void_main.zc
runonly trace tests/programs/trace_run.zc # the image carries line tables: a fault and rt.trace() print the same backtrace as the resident compiler's
runonly float tests/programs/float.zc