wcc/check+wcc_ww/check: reject overlong array literal — frame-smash class (#71)
An array literal with more elements than the declared [N] passed the per-element accept-if-fits checks in both stages and cgen then stored every element at its natural offset, writing past the slot: local frames smashed silently (the repeat form [1,2,3...] into [2]int wrote at the saved BP), module DATA corrupted neighbours. All four declaration contexts (local let, module let, def, struct-field literal) funnel through one choke point per stage — arrlit_init_fits (check.c) / checkarrlitfits (check.ww) — which now pre-counts the literal (skipping the ... marker) and rejects count > N naming both counts. cstage clet's blanket has_arr_repeat bypass is narrowed to non-array declared targets: repeat literals into arrays now run the same overlong + #130 range checks wwstage's checkletassign always ran (the bypass let [2]u8 = [999...] dodge the range check cstage-only). checkarrlitfits also recurses into NESTED array-literal elements (declared elem node N_TARRAY): cstage catches the nested shape through its typed-literal assignability net, which wwstage's untyped elements have no analog of — [2][2]int = [[1,2,3],[4,5]] at module scope silently emitted corrupted DATA (1,2,4,5) and the struct-field twin likewise. Recursion through the one choke point closes any depth; a named-alias element type still bypasses — task #16. alen==0/nil-length stays exempt ([0]/[_] sentinel conflation and un-inferred [_] in def/struct-field — task #11); a non-INTLIT length child (def-named [N]) is exempt in wwstage — task #13; under-long literals keep their current accept (Hare rejects — task #10); wwstage's overlong accept at assign/call-arg/return position (cstage already rejects) is task #12; exact-fit bare-int nested cs-reject/ ww-accept divergence is pre-existing — task #17.
This commit is contained in:
12
Makefile
12
Makefile
@@ -394,6 +394,7 @@ TESTS = $(BIN)/test_smoke $(BIN)/test_lex $(BIN)/test_parse $(BIN)/test_check \
|
||||
$(BIN)/test_append_wide_elem \
|
||||
$(BIN)/test_delete_elem \
|
||||
$(BIN)/test_insert_elem \
|
||||
$(BIN)/test_arrlit_overlong \
|
||||
$(BIN)/test_placeaddr_store \
|
||||
$(BIN)/test_tryprop_multisuccess \
|
||||
$(BIN)/test_append_place \
|
||||
@@ -1039,6 +1040,17 @@ $(BIN)/test_insert_elem: test/wcc/807_insert_elem.c \
|
||||
# cgplaceaddr resolver — runtime rows across widths/signedness/str +
|
||||
# compound ops + loud-tail reject rows w/ exact diagnostic text + cs==ww
|
||||
# asm byte-id.
|
||||
# #71: overlong array literal (more elements than the declared [N]) is
|
||||
# a loud checker reject in BOTH stages — local/module/def/struct-field +
|
||||
# the repeat-marker form (which used to clobber the saved BP) — plus
|
||||
# accept guards (exact-N, repeat-fill, [_] inference) w/ runtime + asm
|
||||
# byte-id on both drivers.
|
||||
$(BIN)/test_arrlit_overlong: test/wcc/808_arrlit_overlong.c \
|
||||
$(BIN)/ww $(BIN)/w6c $(BIN)/w6c_ww $(BIN)/w6a $(BIN)/w6l \
|
||||
$(BIN)/ww_ww \
|
||||
$(LIB)/libwwrt.a | $(BIN)
|
||||
$(CC) $(CFLAGS) -o $@ $<
|
||||
|
||||
$(BIN)/test_placeaddr_store: test/wcc/805_placeaddr_store.c \
|
||||
$(BIN)/ww $(BIN)/w6c $(BIN)/w6c_ww $(BIN)/w6a $(BIN)/w6l \
|
||||
$(BIN)/ww_ww \
|
||||
|
||||
@@ -411,6 +411,27 @@ arrlit_init_fits(Checker *c, Type *dt, Node *rhs)
|
||||
&& strcmp(e->str, "...") == 0)
|
||||
continue;
|
||||
count++;
|
||||
}
|
||||
/* #71: more elements than the declared [N] passed every per-element
|
||||
* check below and then smashed the frame at cgen (each element is
|
||||
* stored at its natural offset — the overflow clobbered neighbours
|
||||
* and even the saved BP). Reject loud before the element walk.
|
||||
* alen==0 stays exempt: 0 doubles as the [_] infer sentinel ([0]
|
||||
* vs [_] conflation, and [_] in def/struct-field never infers —
|
||||
* task #11), and the let paths patch the real length in before
|
||||
* reaching here. Under-long (count < N, no `...`) stays accepted
|
||||
* as before; Hare rejects it — task #10. */
|
||||
if (u->kind == TY_ARRAY && u->alen != SIZE_UNDEFINED && u->alen > 0
|
||||
&& count > u->alen) {
|
||||
err(c, rhs->pos, "array literal has %llu elements "
|
||||
"but declared array holds %llu",
|
||||
(unsigned long long)count, (unsigned long long)u->alen);
|
||||
return 0;
|
||||
}
|
||||
for (Node *e = rhs->list; e; e = e->next) {
|
||||
if (e->kind == N_FIELD && e->str
|
||||
&& strcmp(e->str, "...") == 0)
|
||||
continue;
|
||||
Node *ev = e;
|
||||
while (ev && ev->kind == N_CAST) ev = ev->lhs;
|
||||
u64 v;
|
||||
@@ -2086,7 +2107,15 @@ clet(Checker *c, Node *n)
|
||||
* "flexible" length — the last value fills the remaining slots.
|
||||
* The literal's type carries the explicit-element count, which
|
||||
* may not match the declared length. Trust the declared type
|
||||
* when the marker is present. */
|
||||
* when the marker is present.
|
||||
*
|
||||
* #71: NOT when the declared type is an array — there the repeat
|
||||
* only relaxes the length upward (explicit count <= N, fill the
|
||||
* rest); arrlit_init_fits skips the marker and runs the overlong
|
||||
* reject + per-element range checks. The blanket bypass let
|
||||
* `[2]int = [1,2,3...]` write past the slot (saved-BP clobber)
|
||||
* and `[2]u8 = [999...]` skip the #130 range check — wwstage's
|
||||
* checkletassign already runs both unconditionally. */
|
||||
int has_arr_repeat = 0;
|
||||
if (n->rhs && n->rhs->kind == N_ARRLIT) {
|
||||
for (Node *e = n->rhs->list; e; e = e->next)
|
||||
@@ -2096,6 +2125,12 @@ clet(Checker *c, Node *n)
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (has_arr_repeat && declared) {
|
||||
Type *du = (declared->kind == TY_NAMED) ? declared->under
|
||||
: declared;
|
||||
if (du && du->kind == TY_ARRAY)
|
||||
has_arr_repeat = 0;
|
||||
}
|
||||
/* #45: alloc([], n) defers element type to the let-init context
|
||||
* (Hare-style). cexpr's alloc-slice branch synthesizes
|
||||
* ([]u8 | nomem) with no LHS context; when the let declares []T,
|
||||
|
||||
@@ -14325,6 +14325,39 @@ fn checkarrlitfits(c: *checker, arrtn: *node, rhs: *node) void = {
|
||||
if (rhs == nil) { return; };
|
||||
if (arrtn.kind != nkind.N_TARRAY) { return; };
|
||||
if (rhs.kind != nkind.N_ARRLIT) { return; };
|
||||
// #71: more elements than the declared [N] passed every per-element
|
||||
// check below and then smashed the frame at cgen (each element is
|
||||
// stored at its natural offset — the overflow clobbered neighbours
|
||||
// and even the saved BP). Reject loud before the element walk.
|
||||
// A nil or zero length child stays exempt: nil is the un-inferred
|
||||
// [_] sentinel in def/struct-field contexts and 0 doubles as both
|
||||
// [0] and the cstage [_] sentinel (conflation: task #11); the let
|
||||
// paths stamp the real length before reaching here. A non-INTLIT
|
||||
// length child (def-named [N]) is also exempt — task #13.
|
||||
// Under-long (count < N, no `...`) stays accepted as before; Hare
|
||||
// rejects it — task #10.
|
||||
if (arrtn.rhs != nil && arrtn.rhs.kind == nkind.N_INTLIT
|
||||
&& arrtn.rhs.uval > 0u64) {
|
||||
let cnt: u64 = 0u64;
|
||||
let ce: *node = rhs.list;
|
||||
for (ce != nil) {
|
||||
let cskip: bool = false;
|
||||
if (ce.kind == nkind.N_FIELD) {
|
||||
if (streq(ce.str, "...")) { cskip = true; };
|
||||
};
|
||||
if (!cskip) { cnt += 1u64; };
|
||||
ce = ce.next;
|
||||
};
|
||||
if (cnt > arrtn.rhs.uval) {
|
||||
cerr("array literal has ");
|
||||
cerr(strconv.u64tos(cnt, strconv.base.DEC));
|
||||
cerr(" elements but declared array holds ");
|
||||
cerr(strconv.u64tos(arrtn.rhs.uval, strconv.base.DEC));
|
||||
cerr("\n");
|
||||
c.errs += 1;
|
||||
return;
|
||||
};
|
||||
};
|
||||
let elemtn: *node = arrtn.lhs;
|
||||
let at: *tinfo = tinfofornode(c, arrtn);
|
||||
let et: *tinfo = nil;
|
||||
@@ -14339,6 +14372,21 @@ fn checkarrlitfits(c: *checker, arrtn: *node, rhs: *node) void = {
|
||||
if (!skip) {
|
||||
let ev: *node = e;
|
||||
for (ev != nil && ev.kind == nkind.N_CAST) { ev = ev.lhs; };
|
||||
// #71: a NESTED array-literal element must run the same
|
||||
// count check against the inner [N] — cstage catches the
|
||||
// nested shape through its typed-literal assignability net
|
||||
// (the literal's stamped [2][3]T fails type_assignable),
|
||||
// which wwstage's untyped elements have no analog of; the
|
||||
// silent accept emitted corrupted DATA / smashed frames.
|
||||
// Recursion through the one choke point closes any depth.
|
||||
// A named-alias element type ([2]row) still bypasses — the
|
||||
// elemtn node is N_IDENT, not N_TARRAY — task #16.
|
||||
if (elemtn != nil && elemtn.kind == nkind.N_TARRAY
|
||||
&& ev != nil && ev.kind == nkind.N_ARRLIT) {
|
||||
checkarrlitfits(c, elemtn, ev);
|
||||
e = e.next;
|
||||
continue;
|
||||
};
|
||||
let v: u64 = 0u64;
|
||||
let folded: bool = false;
|
||||
if (et != nil) {
|
||||
|
||||
@@ -4008,6 +4008,39 @@ fn checkarrlitfits(c: *checker, arrtn: *node, rhs: *node) void = {
|
||||
if (rhs == nil) { return; };
|
||||
if (arrtn.kind != nkind.N_TARRAY) { return; };
|
||||
if (rhs.kind != nkind.N_ARRLIT) { return; };
|
||||
// #71: more elements than the declared [N] passed every per-element
|
||||
// check below and then smashed the frame at cgen (each element is
|
||||
// stored at its natural offset — the overflow clobbered neighbours
|
||||
// and even the saved BP). Reject loud before the element walk.
|
||||
// A nil or zero length child stays exempt: nil is the un-inferred
|
||||
// [_] sentinel in def/struct-field contexts and 0 doubles as both
|
||||
// [0] and the cstage [_] sentinel (conflation: task #11); the let
|
||||
// paths stamp the real length before reaching here. A non-INTLIT
|
||||
// length child (def-named [N]) is also exempt — task #13.
|
||||
// Under-long (count < N, no `...`) stays accepted as before; Hare
|
||||
// rejects it — task #10.
|
||||
if (arrtn.rhs != nil && arrtn.rhs.kind == nkind.N_INTLIT
|
||||
&& arrtn.rhs.uval > 0u64) {
|
||||
let cnt: u64 = 0u64;
|
||||
let ce: *node = rhs.list;
|
||||
for (ce != nil) {
|
||||
let cskip: bool = false;
|
||||
if (ce.kind == nkind.N_FIELD) {
|
||||
if (streq(ce.str, "...")) { cskip = true; };
|
||||
};
|
||||
if (!cskip) { cnt += 1u64; };
|
||||
ce = ce.next;
|
||||
};
|
||||
if (cnt > arrtn.rhs.uval) {
|
||||
cerr("array literal has ");
|
||||
cerr(strconv.u64tos(cnt, strconv.base.DEC));
|
||||
cerr(" elements but declared array holds ");
|
||||
cerr(strconv.u64tos(arrtn.rhs.uval, strconv.base.DEC));
|
||||
cerr("\n");
|
||||
c.errs += 1;
|
||||
return;
|
||||
};
|
||||
};
|
||||
let elemtn: *node = arrtn.lhs;
|
||||
let at: *tinfo = tinfofornode(c, arrtn);
|
||||
let et: *tinfo = nil;
|
||||
@@ -4022,6 +4055,21 @@ fn checkarrlitfits(c: *checker, arrtn: *node, rhs: *node) void = {
|
||||
if (!skip) {
|
||||
let ev: *node = e;
|
||||
for (ev != nil && ev.kind == nkind.N_CAST) { ev = ev.lhs; };
|
||||
// #71: a NESTED array-literal element must run the same
|
||||
// count check against the inner [N] — cstage catches the
|
||||
// nested shape through its typed-literal assignability net
|
||||
// (the literal's stamped [2][3]T fails type_assignable),
|
||||
// which wwstage's untyped elements have no analog of; the
|
||||
// silent accept emitted corrupted DATA / smashed frames.
|
||||
// Recursion through the one choke point closes any depth.
|
||||
// A named-alias element type ([2]row) still bypasses — the
|
||||
// elemtn node is N_IDENT, not N_TARRAY — task #16.
|
||||
if (elemtn != nil && elemtn.kind == nkind.N_TARRAY
|
||||
&& ev != nil && ev.kind == nkind.N_ARRLIT) {
|
||||
checkarrlitfits(c, elemtn, ev);
|
||||
e = e.next;
|
||||
continue;
|
||||
};
|
||||
let v: u64 = 0u64;
|
||||
let folded: bool = false;
|
||||
if (et != nil) {
|
||||
|
||||
@@ -14325,6 +14325,39 @@ fn checkarrlitfits(c: *checker, arrtn: *node, rhs: *node) void = {
|
||||
if (rhs == nil) { return; };
|
||||
if (arrtn.kind != nkind.N_TARRAY) { return; };
|
||||
if (rhs.kind != nkind.N_ARRLIT) { return; };
|
||||
// #71: more elements than the declared [N] passed every per-element
|
||||
// check below and then smashed the frame at cgen (each element is
|
||||
// stored at its natural offset — the overflow clobbered neighbours
|
||||
// and even the saved BP). Reject loud before the element walk.
|
||||
// A nil or zero length child stays exempt: nil is the un-inferred
|
||||
// [_] sentinel in def/struct-field contexts and 0 doubles as both
|
||||
// [0] and the cstage [_] sentinel (conflation: task #11); the let
|
||||
// paths stamp the real length before reaching here. A non-INTLIT
|
||||
// length child (def-named [N]) is also exempt — task #13.
|
||||
// Under-long (count < N, no `...`) stays accepted as before; Hare
|
||||
// rejects it — task #10.
|
||||
if (arrtn.rhs != nil && arrtn.rhs.kind == nkind.N_INTLIT
|
||||
&& arrtn.rhs.uval > 0u64) {
|
||||
let cnt: u64 = 0u64;
|
||||
let ce: *node = rhs.list;
|
||||
for (ce != nil) {
|
||||
let cskip: bool = false;
|
||||
if (ce.kind == nkind.N_FIELD) {
|
||||
if (streq(ce.str, "...")) { cskip = true; };
|
||||
};
|
||||
if (!cskip) { cnt += 1u64; };
|
||||
ce = ce.next;
|
||||
};
|
||||
if (cnt > arrtn.rhs.uval) {
|
||||
cerr("array literal has ");
|
||||
cerr(strconv.u64tos(cnt, strconv.base.DEC));
|
||||
cerr(" elements but declared array holds ");
|
||||
cerr(strconv.u64tos(arrtn.rhs.uval, strconv.base.DEC));
|
||||
cerr("\n");
|
||||
c.errs += 1;
|
||||
return;
|
||||
};
|
||||
};
|
||||
let elemtn: *node = arrtn.lhs;
|
||||
let at: *tinfo = tinfofornode(c, arrtn);
|
||||
let et: *tinfo = nil;
|
||||
@@ -14339,6 +14372,21 @@ fn checkarrlitfits(c: *checker, arrtn: *node, rhs: *node) void = {
|
||||
if (!skip) {
|
||||
let ev: *node = e;
|
||||
for (ev != nil && ev.kind == nkind.N_CAST) { ev = ev.lhs; };
|
||||
// #71: a NESTED array-literal element must run the same
|
||||
// count check against the inner [N] — cstage catches the
|
||||
// nested shape through its typed-literal assignability net
|
||||
// (the literal's stamped [2][3]T fails type_assignable),
|
||||
// which wwstage's untyped elements have no analog of; the
|
||||
// silent accept emitted corrupted DATA / smashed frames.
|
||||
// Recursion through the one choke point closes any depth.
|
||||
// A named-alias element type ([2]row) still bypasses — the
|
||||
// elemtn node is N_IDENT, not N_TARRAY — task #16.
|
||||
if (elemtn != nil && elemtn.kind == nkind.N_TARRAY
|
||||
&& ev != nil && ev.kind == nkind.N_ARRLIT) {
|
||||
checkarrlitfits(c, elemtn, ev);
|
||||
e = e.next;
|
||||
continue;
|
||||
};
|
||||
let v: u64 = 0u64;
|
||||
let folded: bool = false;
|
||||
if (et != nil) {
|
||||
|
||||
429
test/wcc/808_arrlit_overlong.c
Normal file
429
test/wcc/808_arrlit_overlong.c
Normal file
@@ -0,0 +1,429 @@
|
||||
/*
|
||||
* 808_arrlit_overlong — an array literal with MORE elements than the
|
||||
* declared [N] must be a loud checker reject in BOTH stages (#71).
|
||||
*
|
||||
* The bug: `let a: [2]u8 = [1u8, 2u8, 3u8];` was silently accepted by
|
||||
* both checkers — type_assignable fails on the length mismatch, but
|
||||
* arrlit_init_fits (#130 accept-if-fits) only range-checked the
|
||||
* elements and never compared the literal's count against the declared
|
||||
* length. cgen then stored every element at its natural offset, writing
|
||||
* past the slot: stack-frame smash for locals (the `[1,2,3...]`-into-
|
||||
* `[2]int` repeat form clobbered the saved BP outright), silent
|
||||
* neighbour corruption for module-level DATA. The repeat-marker form
|
||||
* was worse on cstage: clet's has_arr_repeat bypass skipped ALL checks
|
||||
* for any `...` literal, so `[2]u8 = [999...]` also dodged the #130
|
||||
* range check that wwstage already enforced.
|
||||
*
|
||||
* The fix (BOTH stages, one choke point each): the shared accept-if-
|
||||
* fits helper (cmd/wcc/check.c arrlit_init_fits; selfhost/cmd/wcc/
|
||||
* check.ww checkarrlitfits) pre-counts the literal's elements (skipping
|
||||
* the `...` marker) and rejects count > N naming both counts. All four
|
||||
* declaration contexts (local let / module let / def / struct-field
|
||||
* literal) funnel through that helper. cstage clet's repeat bypass is
|
||||
* narrowed to non-array targets so repeat literals into arrays run the
|
||||
* same checks wwstage always ran.
|
||||
*
|
||||
* Out of scope, probed + filed separately: UNDER-long literals (no
|
||||
* `...`) stay accepted in both stages (Hare rejects); `[0]`/`[_]`
|
||||
* alen==0 sentinel conflation; wwstage assign/call-arg overlong
|
||||
* acceptance (cstage already rejects those positions).
|
||||
*
|
||||
* accept rows | want
|
||||
* ----------------------------------+------
|
||||
* exact_local [2]int = [1,2] | runs, 0
|
||||
* exact_module module-level [2] | runs, 0
|
||||
* exact_def def [3] = [1,2,3] | runs, 0
|
||||
* exact_field struct f=[2 elems] | runs, 0
|
||||
* repeat_fill [4]int = [9...] | runs, 0
|
||||
* repeat_partial [4]int = [1,2...] | runs, 0
|
||||
* infer_len [_]int = [1,2,3] | runs, 0
|
||||
*
|
||||
* reject rows (build must FAIL on both drivers, and stderr must name
|
||||
* both counts — per-stage expected substring: cstage catches the
|
||||
* NESTED rows through its typed-literal assignability net instead of
|
||||
* the choke-point diag, so those carry a different cstage substring)
|
||||
* ----------------------------------------------
|
||||
* overlong local / module / def(5-vs-3) / struct-field
|
||||
* overlong repeat `[2]int = [1,2,3...]`
|
||||
* overlong narrow `[2]u8 = [1u8,2u8,3u8]`
|
||||
* nested module `[2][2]int = [[1,2,3],[4,5]]` (inner overlong —
|
||||
* pre-fix wwstage emitted corrupted DATA: 1,2,4,5)
|
||||
* nested field struct{f:[2][2]int} inner overlong
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/wait.h>
|
||||
|
||||
static int
|
||||
runwait(const char *cmd)
|
||||
{
|
||||
int rc = system(cmd);
|
||||
if (rc == -1) return -1;
|
||||
if (WIFEXITED(rc)) return WEXITSTATUS(rc);
|
||||
return -1;
|
||||
}
|
||||
|
||||
struct row { const char *label; const char *src; int want; };
|
||||
|
||||
static const struct row rows[] = {
|
||||
{ "exact_local",
|
||||
"package main;\n"
|
||||
"export fn main() i32 = {\n"
|
||||
"\tlet a: [2]int = [1, 2];\n"
|
||||
"\treturn a[1]: i32 - 2;\n"
|
||||
"};\n",
|
||||
0 },
|
||||
|
||||
{ "exact_module",
|
||||
"package main;\n"
|
||||
"let g: [2]int = [4, 5];\n"
|
||||
"export fn main() i32 = {\n"
|
||||
"\treturn g[1]: i32 - 5;\n"
|
||||
"};\n",
|
||||
0 },
|
||||
|
||||
{ "exact_def",
|
||||
"package main;\n"
|
||||
"def TAB: [3]int = [1, 2, 3];\n"
|
||||
"export fn main() i32 = {\n"
|
||||
"\treturn TAB[2]: i32 - 3;\n"
|
||||
"};\n",
|
||||
0 },
|
||||
|
||||
{ "exact_field",
|
||||
"package main;\n"
|
||||
"type s = struct { f: [2]int, g: int };\n"
|
||||
"export fn main() i32 = {\n"
|
||||
"\tlet v: s = s{ f = [6, 7], g = 8 };\n"
|
||||
"\treturn v.f[1]: i32 + v.g: i32 - 15;\n"
|
||||
"};\n",
|
||||
0 },
|
||||
|
||||
{ "repeat_fill",
|
||||
"package main;\n"
|
||||
"export fn main() i32 = {\n"
|
||||
"\tlet a: [4]int = [9...];\n"
|
||||
"\treturn a[3]: i32 - 9;\n"
|
||||
"};\n",
|
||||
0 },
|
||||
|
||||
{ "repeat_partial",
|
||||
"package main;\n"
|
||||
"export fn main() i32 = {\n"
|
||||
"\tlet a: [4]int = [1, 2...];\n"
|
||||
"\treturn a[3]: i32 - 2;\n"
|
||||
"};\n",
|
||||
0 },
|
||||
|
||||
{ "infer_len",
|
||||
"package main;\n"
|
||||
"export fn main() i32 = {\n"
|
||||
"\tlet a: [_]int = [10, 20, 30];\n"
|
||||
"\treturn a.len: i32 - 3;\n"
|
||||
"};\n",
|
||||
0 },
|
||||
};
|
||||
|
||||
/* Overlong literals — both stages must FAIL the build (the old accept
|
||||
* stored every element at its natural offset: frame smash) AND the
|
||||
* diagnostic must carry the expected substring (the choke-point reject
|
||||
* names BOTH counts; the nested rows reach cstage's pre-existing
|
||||
* assignability net instead, hence per-stage substrings). */
|
||||
struct negrow {
|
||||
const char *label;
|
||||
const char *src;
|
||||
const char *diag_c; /* expected stderr substring, cstage */
|
||||
const char *diag_w; /* expected stderr substring, wwstage */
|
||||
};
|
||||
|
||||
#define OVERLONG_3V2 "array literal has 3 elements but declared array holds 2"
|
||||
|
||||
static const struct negrow neg[] = {
|
||||
{ "overlong_local",
|
||||
"package main;\n"
|
||||
"export fn main() i32 = {\n"
|
||||
"\tlet a: [2]int = [1, 2, 3];\n"
|
||||
"\treturn 0;\n"
|
||||
"};\n",
|
||||
OVERLONG_3V2, OVERLONG_3V2 },
|
||||
|
||||
{ "overlong_module",
|
||||
"package main;\n"
|
||||
"let g: [2]int = [1, 2, 3];\n"
|
||||
"export fn main() i32 = { return 0; };\n",
|
||||
OVERLONG_3V2, OVERLONG_3V2 },
|
||||
|
||||
/* counts deliberately differ from the other rows so a diag that
|
||||
* hardcodes 3/2 instead of naming the real counts trips here */
|
||||
{ "overlong_def",
|
||||
"package main;\n"
|
||||
"def TAB: [3]int = [1, 2, 3, 4, 5];\n"
|
||||
"export fn main() i32 = { return 0; };\n",
|
||||
"array literal has 5 elements but declared array holds 3",
|
||||
"array literal has 5 elements but declared array holds 3" },
|
||||
|
||||
{ "overlong_field",
|
||||
"package main;\n"
|
||||
"type s = struct { f: [2]int };\n"
|
||||
"export fn main() i32 = {\n"
|
||||
"\tlet v: s = s{ f = [1, 2, 3] };\n"
|
||||
"\treturn 0;\n"
|
||||
"};\n",
|
||||
OVERLONG_3V2, OVERLONG_3V2 },
|
||||
|
||||
/* repeat marker with too many explicit elements — the worst
|
||||
* pre-fix case (wrote at the saved BP) */
|
||||
{ "overlong_repeat",
|
||||
"package main;\n"
|
||||
"export fn main() i32 = {\n"
|
||||
"\tlet a: [2]int = [1, 2, 3...];\n"
|
||||
"\treturn 0;\n"
|
||||
"};\n",
|
||||
OVERLONG_3V2, OVERLONG_3V2 },
|
||||
|
||||
/* narrow element width (sub-8B store path) */
|
||||
{ "overlong_narrow",
|
||||
"package main;\n"
|
||||
"export fn main() i32 = {\n"
|
||||
"\tlet a: [2]u8 = [1u8, 2u8, 3u8];\n"
|
||||
"\treturn 0;\n"
|
||||
"};\n",
|
||||
OVERLONG_3V2, OVERLONG_3V2 },
|
||||
|
||||
/* nested inner overlong, module DATA — pre-fix wwstage silently
|
||||
* emitted 1,2,4,5; cstage rejects via assignability, wwstage via
|
||||
* the recursive choke point */
|
||||
{ "nested_module",
|
||||
"package main;\n"
|
||||
"let g: [2][2]int = [[1, 2, 3], [4, 5]];\n"
|
||||
"export fn main() i32 = { return 0; };\n",
|
||||
"not assignable", OVERLONG_3V2 },
|
||||
|
||||
{ "nested_field",
|
||||
"package main;\n"
|
||||
"type s = struct { f: [2][2]int };\n"
|
||||
"export fn main() i32 = {\n"
|
||||
"\tlet v: s = s{ f = [[1, 2, 3], [4, 5]] };\n"
|
||||
"\treturn 0;\n"
|
||||
"};\n",
|
||||
"not assignable", OVERLONG_3V2 },
|
||||
};
|
||||
|
||||
static int
|
||||
run_driver(const char *driver, const struct row *r, int i)
|
||||
{
|
||||
char src[64], tmpdir[64], cmd[1024];
|
||||
snprintf(src, sizeof src, "/tmp/aol_%d_%d.ww", getpid(), i);
|
||||
snprintf(tmpdir, sizeof tmpdir, "/tmp/aol_%d_d_%d", getpid(), i);
|
||||
|
||||
FILE *f = fopen(src, "wb");
|
||||
if (!f) return -1;
|
||||
fputs(r->src, f);
|
||||
fclose(f);
|
||||
|
||||
mkdir(tmpdir, 0755);
|
||||
snprintf(cmd, sizeof cmd, "cd %s && %s build %s 2>/dev/null",
|
||||
tmpdir, driver, src);
|
||||
if (runwait(cmd) != 0) {
|
||||
fprintf(stderr, "row[%s]: build via %s failed\n",
|
||||
r->label, driver);
|
||||
unlink(src); rmdir(tmpdir);
|
||||
return -1;
|
||||
}
|
||||
|
||||
const char *base = strrchr(src, '/');
|
||||
base = base ? base + 1 : src;
|
||||
char outbin[128];
|
||||
snprintf(outbin, sizeof outbin, "%s/%s", tmpdir, base);
|
||||
char *dot = strrchr(outbin, '.');
|
||||
if (dot && strcmp(dot, ".ww") == 0) *dot = '\0';
|
||||
int got = runwait(outbin);
|
||||
|
||||
unlink(src); unlink(outbin); rmdir(tmpdir);
|
||||
return got;
|
||||
}
|
||||
|
||||
/* build_should_fail — an overlong literal must error on `driver` AND
|
||||
* the diagnostic must contain `diag`; returns 0 when the build
|
||||
* correctly FAILS with the expected text, non-zero otherwise. */
|
||||
static int
|
||||
build_should_fail(const char *dname, const char *driver,
|
||||
const struct negrow *r, const char *diag, int i)
|
||||
{
|
||||
char s[64], tmpdir[64], errf[64], cmd[1024];
|
||||
snprintf(s, sizeof s, "/tmp/aoln_%d_%d.ww", getpid(), i);
|
||||
snprintf(tmpdir, sizeof tmpdir, "/tmp/aoln_%d_d_%d", getpid(), i);
|
||||
snprintf(errf, sizeof errf, "/tmp/aoln_%d_%d.err", getpid(), i);
|
||||
|
||||
FILE *f = fopen(s, "wb");
|
||||
if (!f) return -1;
|
||||
fputs(r->src, f);
|
||||
fclose(f);
|
||||
|
||||
mkdir(tmpdir, 0755);
|
||||
snprintf(cmd, sizeof cmd, "cd %s && %s build %s 2>%s",
|
||||
tmpdir, driver, s, errf);
|
||||
int rc = runwait(cmd);
|
||||
unlink(s);
|
||||
/* clean any emitted binary */
|
||||
const char *base = strrchr(s, '/');
|
||||
base = base ? base + 1 : s;
|
||||
char outbin[128];
|
||||
snprintf(outbin, sizeof outbin, "%s/%s", tmpdir, base);
|
||||
char *dot = strrchr(outbin, '.');
|
||||
if (dot && strcmp(dot, ".ww") == 0) *dot = '\0';
|
||||
unlink(outbin);
|
||||
rmdir(tmpdir);
|
||||
|
||||
int bad = 0;
|
||||
if (rc == 0) {
|
||||
fprintf(stderr, "arrlit_overlong[%s][%s]: built ok, "
|
||||
"expected a loud error\n", dname, r->label);
|
||||
bad = 1;
|
||||
} else {
|
||||
char ebuf[4096];
|
||||
size_t n = 0;
|
||||
FILE *ef = fopen(errf, "rb");
|
||||
if (ef) {
|
||||
n = fread(ebuf, 1, sizeof ebuf - 1, ef);
|
||||
fclose(ef);
|
||||
}
|
||||
ebuf[n] = '\0';
|
||||
if (strstr(ebuf, diag) == NULL) {
|
||||
fprintf(stderr, "arrlit_overlong[%s][%s]: rejected "
|
||||
"but diagnostic lacks \"%s\"; got: %s\n",
|
||||
dname, r->label, diag, ebuf);
|
||||
bad = 1;
|
||||
}
|
||||
}
|
||||
unlink(errf);
|
||||
return bad;
|
||||
}
|
||||
|
||||
/* asm_byte_identical — w6c vs w6c_ww .s for the same source must match. */
|
||||
static int
|
||||
asm_byte_identical(const char *bin, const struct row *r, int i)
|
||||
{
|
||||
char src[64], cs[64], ws[64], cmd[1024];
|
||||
snprintf(src, sizeof src, "/tmp/aol_asm_%d_%d.ww", getpid(), i);
|
||||
snprintf(cs, sizeof cs, "/tmp/aol_asm_%d_%d_c.s", getpid(), i);
|
||||
snprintf(ws, sizeof ws, "/tmp/aol_asm_%d_%d_w.s", getpid(), i);
|
||||
|
||||
FILE *f = fopen(src, "wb");
|
||||
if (!f) return -1;
|
||||
fputs(r->src, f);
|
||||
fclose(f);
|
||||
|
||||
snprintf(cmd, sizeof cmd, "%s/w6c -o %s %s 2>/dev/null", bin, cs, src);
|
||||
if (runwait(cmd) != 0) {
|
||||
fprintf(stderr, "row[%s]: w6c errored\n", r->label);
|
||||
unlink(src);
|
||||
return -1;
|
||||
}
|
||||
snprintf(cmd, sizeof cmd, "%s/w6c_ww -o %s %s 2>/dev/null",
|
||||
bin, ws, src);
|
||||
if (runwait(cmd) != 0) {
|
||||
fprintf(stderr, "row[%s]: w6c_ww errored\n", r->label);
|
||||
unlink(src); unlink(cs);
|
||||
return -1;
|
||||
}
|
||||
|
||||
FILE *fc = fopen(cs, "rb");
|
||||
FILE *fw = fopen(ws, "rb");
|
||||
int rc = 0;
|
||||
if (!fc || !fw) {
|
||||
rc = -1;
|
||||
} else {
|
||||
for (;;) {
|
||||
int a = fgetc(fc);
|
||||
int b = fgetc(fw);
|
||||
if (a != b) { rc = -1; break; }
|
||||
if (a == EOF) break;
|
||||
}
|
||||
}
|
||||
if (fc) fclose(fc);
|
||||
if (fw) fclose(fw);
|
||||
if (rc != 0)
|
||||
fprintf(stderr, "row[%s]: cstage vs wwstage asm differs\n",
|
||||
r->label);
|
||||
unlink(src); unlink(cs); unlink(ws);
|
||||
return rc;
|
||||
}
|
||||
|
||||
int
|
||||
main(void)
|
||||
{
|
||||
const char *bin = getenv("BIN");
|
||||
if (!bin) bin = "out/bin";
|
||||
char absbin[1024];
|
||||
if (bin[0] != '/') {
|
||||
char cwd[1024];
|
||||
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
|
||||
snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin);
|
||||
bin = absbin;
|
||||
}
|
||||
|
||||
char cdrv[1024];
|
||||
snprintf(cdrv, sizeof cdrv, "%s/ww", bin);
|
||||
char wdrv[1024];
|
||||
snprintf(wdrv, sizeof wdrv, "%s/ww_ww", bin);
|
||||
|
||||
struct { const char *name; const char *path; int gated_on_existence; }
|
||||
drivers[] = {
|
||||
{ "cstage", cdrv, 0 },
|
||||
{ "wwstage", wdrv, 1 },
|
||||
{ NULL, NULL, 0 },
|
||||
};
|
||||
|
||||
int n = (int)(sizeof rows / sizeof rows[0]);
|
||||
int nn = (int)(sizeof neg / sizeof neg[0]);
|
||||
int total = 0, fail = 0;
|
||||
|
||||
for (int d = 0; drivers[d].name; d++) {
|
||||
if (drivers[d].gated_on_existence
|
||||
&& access(drivers[d].path, X_OK) != 0) {
|
||||
fprintf(stderr, "arrlit_overlong: skip %s (no %s)\n",
|
||||
drivers[d].name, drivers[d].path);
|
||||
continue;
|
||||
}
|
||||
for (int i = 0; i < n; i++) {
|
||||
int got = run_driver(drivers[d].path, &rows[i], i);
|
||||
total++;
|
||||
if (got != rows[i].want) {
|
||||
fprintf(stderr,
|
||||
"arrlit_overlong[%s][%s]: exit=%d want=%d\n",
|
||||
drivers[d].name, rows[i].label,
|
||||
got, rows[i].want);
|
||||
fail++;
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < nn; i++) {
|
||||
total++;
|
||||
const char *diag =
|
||||
strcmp(drivers[d].name, "cstage") == 0
|
||||
? neg[i].diag_c : neg[i].diag_w;
|
||||
if (build_should_fail(drivers[d].name,
|
||||
drivers[d].path, &neg[i], diag, 100 + i) != 0)
|
||||
fail++;
|
||||
}
|
||||
}
|
||||
|
||||
if (access(wdrv, X_OK) == 0) {
|
||||
for (int i = 0; i < n; i++) {
|
||||
total++;
|
||||
if (asm_byte_identical(bin, &rows[i], i) != 0)
|
||||
fail++;
|
||||
}
|
||||
}
|
||||
|
||||
if (fail) {
|
||||
fprintf(stderr,
|
||||
"arrlit_overlong: %d/%d fixtures failed\n", fail, total);
|
||||
return 1;
|
||||
}
|
||||
printf("arrlit_overlong: %d/%d ok\n", total, total);
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user