w6c+selfhost+lib: zero-init multi-word no-rhs lets

`let x: T;` for str/slice/tuple/struct/tagged previously left the slot
holding stack garbage — only 8B-primitive slots were zeroed. This bit
`expectbindname` in lib/ww/parse: `let empty: str; *into = empty;` was
copying stack bytes (often a recently-vacated str descriptor) into the
caller's `id`, so wwstage emitted `_` discard nodes carrying random
text instead of "". Both stages now zero the full slot on no-rhs lets;
`[N]T` arrays keep the per-index-write contract.

Also tightens the two known buggy sites: parse.ww `expectbindname`
writes `*into = ""` directly, expr.ww `_` primary returns the bare
newnode (amalloc already zeroes).
This commit is contained in:
2026-05-13 12:46:02 +09:00
parent b6cf68f2b8
commit 956a20701b
7 changed files with 155 additions and 43 deletions

View File

@@ -68,14 +68,12 @@ fn parseprimary(p: *parser) *node = {
return newnode(p.a, nkind.N_VOIDLIT, pf, pl, pc);
};
if (p.curkind == tkind.TK_UNDER) {
// Bare `_` — valid only as a discard lvalue. Emit an nkind.N_IDENT
// with empty str; the checker rejects it outside lvalue
// Bare `_` — valid only as a discard lvalue. Emit an N_IDENT
// with empty str (newnode zeroes the node, so str.len is
// already 0); the checker rejects it outside lvalue
// positions.
advance(p);
let n: *node = newnode(p.a, nkind.N_IDENT, pf, pl, pc);
let empty: str;
n.str = empty;
return n;
return newnode(p.a, nkind.N_IDENT, pf, pl, pc);
};
if (p.curkind == tkind.TK_LBRACK) {
// Array literal `[a, b, c]` or `[v, w...]` (repeat suffix).

View File

@@ -92,8 +92,7 @@ fn expectident(p: *parser, into: *str) bool = {
// scope_define for the binding.
fn expectbindname(p: *parser, into: *str) bool = {
if (p.curkind == tkind.TK_UNDER) {
let empty: str;
*into = empty;
*into = "";
advance(p);
return true;
};