selfhost+test: gate wwstage w6c cgen on parser errors (#17)

Wwstage w6c_ww silently exited 0 on parse errors (stderr noise
only). The driver ran cgen on the broken AST then only checked
l.errs; ps.errs was never read, so callers downstream
(`ww build`, make rules) saw no signal and proceeded with junk
asm. Cstage cmd/w6c/main.c gates on `l.errs || p.errs` before
cgen; mirror that, hoisting the check above cgfile so the broken
AST never reaches codegen.

New test 742_parse_error pins the contract on both binaries:
writes a known-bad fragment to a pid-scoped /tmp file, runs cstage
w6c unconditionally and wwstage w6c_ww if available, asserts both
exit non-zero. Pre-fix wwstage exited 0 with junk asm; post-fix
exits 1 with parse: messages preserved on stderr.

116/116 ok. ww2 == ww3 == ww4 byte-id holds (no behavior change
for valid input).
This commit is contained in:
2026-05-18 20:55:37 +09:00
parent d78956e5df
commit c40edaa7f9
4 changed files with 109 additions and 4 deletions

View File

@@ -20949,12 +20949,14 @@ export fn main(argc: i32, argv: **u8) i32 = {
let ps: parser;
parserinit(&ps, ar, &l);
let f: *node = parsefile(&ps);
// Gate cgen on parse-stage errors. Mirrors cmd/w6c/main.c's
// `if (l.errs || p.errs) return 1;` — broken AST otherwise reaches
// cgen and emits junk asm with a zero exit (silent miscompile).
if (l.errs > 0 || ps.errs > 0) { return 1; };
let cg: cgen;
cgeninit(&cg, ar);
cgfile(&cg, f);
if (l.errs > 0) { return 1; };
return 0;
};

View File

@@ -142,11 +142,13 @@ export fn main(argc: i32, argv: **u8) i32 = {
let ps: parser;
parserinit(&ps, ar, &l);
let f: *node = parsefile(&ps);
// Gate cgen on parse-stage errors. Mirrors cmd/w6c/main.c's
// `if (l.errs || p.errs) return 1;` — broken AST otherwise reaches
// cgen and emits junk asm with a zero exit (silent miscompile).
if (l.errs > 0 || ps.errs > 0) { return 1; };
let cg: cgen;
cgeninit(&cg, ar);
cgfile(&cg, f);
if (l.errs > 0) { return 1; };
return 0;
};