wcc/check: #12+#106 reject overlong array-literal in return/call-arg/alias positions (wwstage)

An overlong array literal (more initializers than the declared length) is
invalid -- cstage loud-rejects it everywhere -- but wwstage silently
accepted (and truncated) it in several positions; #9 wired only the decl
position. This folds the remaining three (one class: checkarrlitfits
over-fill coverage), all wwstage-only reject-align:

- #12a return   fn f() [2]int = [1,2,3]            -- silently accepted.
- #12b call-arg g([1,2,3])                         -- louded only late via cgen #271.
- #106 alias    type A=[2]int; let g: A = [1,2,3]  -- silently truncated;
  checkarrlitfits bailed on the N_TNAME alias without chasing.

Four inserts in check.ww: an alias-chase (resolvealias) at the top of
checkarrlitfits (makes all callers alias-aware), the over-fill check wired
into checkretassign (hoisted above the isassignable short-circuit) and
desugarcallargs, and the alias-let-global guard made alias-aware. cstage
unchanged (w6c md5 unchanged); reject-only, so no asm moves -- 990-997 8/8,
no lib byte-id pin flips. A 5th position (tuple-element overlong) is a
separate pre-existing hole, filed (#20). test/wcc/828 table-driven.
This commit is contained in:
2026-06-09 00:17:10 +09:00
parent 5dd239d01e
commit c10fffae16
5 changed files with 435 additions and 3 deletions

View File

@@ -4226,6 +4226,14 @@ fn errnotassign(c: *checker, dst: *node, src: *node, where: str) void = {
fn checkarrlitfits(c: *checker, arrtn: *node, rhs: *node) void = {
if (arrtn == nil) { return; };
if (rhs == nil) { return; };
// #106: chase a TY_NAMED alias (`type A=[2]int`) to the underlying
// [N]T before the kind gate — else an alias declared type bails
// here and the over-fill (#9) never fires (silent DATA-truncate).
// Idempotent on a non-alias (resolvealias returns the node), so a
// direct N_TARRAY is untouched. Makes EVERY caller (decl/let/def/
// struct/return/call-arg) alias-aware by construction.
arrtn = resolvealias(c, unwrapbang(arrtn));
if (arrtn == 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
@@ -4464,6 +4472,14 @@ fn desugarcallargs(c: *checker, n: *node) void = {
};
}; };
}; };
// #12: overlong array-lit CALL-ARG — `g([1,2,3])`.
// Reject at CHECK time (clean over-fill msg) instead
// of falling to cgen #271's late aggregate-arg loud.
// param.lhs is the declared param type (alias-aware
// via the resolvealias chase in checkarrlitfits).
if (a.kind == nkind.N_ARRLIT) {
checkarrlitfits(c, param.lhs, a);
};
// #31/#33: bare array-literal arg has no backing
// — loud-reject (supported only at a `let`).
if (!rejectarrlitborrow(c, param.lhs, a)) {
@@ -4672,7 +4688,16 @@ fn checkletassign(c: *checker, n: *node) void = {
// (#146 merged). Mirrors cstage check.c arrlit_init_fits. Scoped
// to the array path; scalar-init range-check is a separate
// language-wide gap (#148).
if (n.lhs.kind == nkind.N_TARRAY && n.rhs.kind == nkind.N_ARRLIT) {
// #106: an alias-of-array lhs (`type A=[2]int; let g: A = […]`) arrives
// as N_TNAME, so the bare N_TARRAY gate skipped it and the over-fill
// never fired (silent DATA-truncate). The let path is the one caller
// that pre-gates on the declared node's kind (def/return/call-arg pass
// it straight to the alias-aware checkarrlitfits); resolve the alias
// here too so an alias-of-array routes through the same over-fill. A
// direct N_TARRAY is unchanged (resolvealias is idempotent), and the
// early return matches the direct-array branch's pre-existing return.
let llhs: *node = resolvealias(c, unwrapbang(n.lhs));
if (llhs != nil && llhs.kind == nkind.N_TARRAY && n.rhs.kind == nkind.N_ARRLIT) {
checkarrlitfits(c, n.lhs, n.rhs);
return;
};
@@ -4744,6 +4769,16 @@ fn checkretassign(c: *checker, n: *node) void = {
return;
};
if (c.fnret == nil) { return; };
// #12: overlong array-lit RETURN — `fn f() [2]int = { return [1,2,3]; }`.
// #9 wired the over-fill at DECL only; the return position was lenient.
// Fire BEFORE the isassignable/conf guards below — a direct [N]T return
// type drives isassignable to conf=false (array-length leniency), which
// would short-circuit the check (the alias path set conf=true, so neg3
// caught it but the direct neg0 slipped). Alias-aware via the
// resolvealias chase at the top of checkarrlitfits.
if (n.lhs.kind == nkind.N_ARRLIT) {
checkarrlitfits(c, c.fnret, n.lhs);
};
let src: *node = exprtype(c, n.lhs, nil);
if (src == nil) { return; };
let conf: bool = false;