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:
2026-06-04 21:53:44 +09:00
parent 3daf134395
commit 74767c70cc
6 changed files with 621 additions and 1 deletions

View File

@@ -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,