From 2ce94a194a560ae38d9f9991e1433de3db1df4d8 Mon Sep 17 00:00:00 2001 From: Hojun-Cho Date: Sat, 13 Jun 2026 04:40:58 +0900 Subject: [PATCH] wwdump: -c/-r gate on parse errors MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit wwdump's -c/-r modes emitted output from a garbage parse silently. Gate on the parse-error count first (the w6c main gate, main.ww:162); wwstage-only — the C wwdump has no -c/-r modes. --- Makefile | 8 ++ selfhost/cmd/wwdump/main.combined.ww | 11 +++ selfhost/cmd/wwdump/main.ww | 11 +++ test/wcc/989_wwdumpgate_run.c | 139 +++++++++++++++++++++++++++ 4 files changed, 169 insertions(+) create mode 100644 test/wcc/989_wwdumpgate_run.c diff --git a/Makefile b/Makefile index 6877dc32..c67c65ff 100644 --- a/Makefile +++ b/Makefile @@ -263,6 +263,7 @@ TESTS = $(BIN)/test_smoke $(BIN)/test_lex $(BIN)/test_parse $(BIN)/test_check \ $(BIN)/test_defercap_run \ $(BIN)/test_loopcap_run \ $(BIN)/test_enumcap_run \ + $(BIN)/test_wwdumpgate_run \ $(BIN)/test_ampfncollide_run \ $(BIN)/test_trycallcollide_run \ $(BIN)/test_gunsigned_run \ @@ -829,6 +830,13 @@ $(BIN)/test_enumcap_run: test/wcc/989_enumcap_run.c \ $(LIB)/libwwrt.a | $(BIN) $(CC) $(CFLAGS) -o $@ $< +# 989_wwdumpgate_run (#52, F15 c4): wwdump_ww's -c/-r arms gate on parse-stage +# errors instead of silently emitting asm / a resolve report on a broken AST. +# Runs wwdump + wwdump_ww directly (rule-10). See the test header. +$(BIN)/test_wwdumpgate_run: test/wcc/989_wwdumpgate_run.c \ + $(BIN)/wwdump $(BIN)/wwdump_ww | $(BIN) + $(CC) $(CFLAGS) -o $@ $< + # 989_ampfncollide_run (#4, c2): `&fn` synthesis (unoptype TK_AMP + # assignableaddrfn) prefers the current module's fn when a same-leaf fn is # declared in a later module. Builds/rejects on BOTH driver twins (rule-10). diff --git a/selfhost/cmd/wwdump/main.combined.ww b/selfhost/cmd/wwdump/main.combined.ww index 10e1654a..c0bd0a35 100644 --- a/selfhost/cmd/wwdump/main.combined.ww +++ b/selfhost/cmd/wwdump/main.combined.ww @@ -44685,6 +44685,11 @@ export fn main(argc: i32, argv: **u8) i32 = { let ps: parser; parserinit(&ps, &l); let f: *node = parsefile(&ps); + // #52: gate the resolve report on parse-stage errors. Without + // this a parse-errored decl is silently dropped from the AST + // and the report is printed with rc=0. Mirrors w6c main.ww:162 + // / cmd/w6c/main.c. + if (l.errs > 0 || ps.errs > 0) { return 1; }; let tc: tctx; typesinit(&tc); let ck: checker; @@ -44708,6 +44713,12 @@ export fn main(argc: i32, argv: **u8) i32 = { let ps: parser; parserinit(&ps, &l); let f: *node = parsefile(&ps); + // #52: gate cgen on parse-stage errors BEFORE check/cgen. + // A parse-errored decl is silently dropped from the AST; the + // remaining file would otherwise emit asm with rc=0 (silent + // miscompile, and the 994 byte-identity probe ships wrong asm + // silently). Mirrors w6c main.ww:162 / cmd/w6c/main.c. + if (l.errs > 0 || ps.errs > 0) { return 1; }; // #50: mirror w6c — run check before cgen so AST mutations // from #42 (size/align/offset fold) and audit §1.8 (node.type_ // population) land before cgen walks. Without this, wwdump -c diff --git a/selfhost/cmd/wwdump/main.ww b/selfhost/cmd/wwdump/main.ww index 4c8a1972..77b04271 100644 --- a/selfhost/cmd/wwdump/main.ww +++ b/selfhost/cmd/wwdump/main.ww @@ -135,6 +135,11 @@ export fn main(argc: i32, argv: **u8) i32 = { let ps: parser; parserinit(&ps, &l); let f: *node = parsefile(&ps); + // #52: gate the resolve report on parse-stage errors. Without + // this a parse-errored decl is silently dropped from the AST + // and the report is printed with rc=0. Mirrors w6c main.ww:162 + // / cmd/w6c/main.c. + if (l.errs > 0 || ps.errs > 0) { return 1; }; let tc: tctx; typesinit(&tc); let ck: checker; @@ -158,6 +163,12 @@ export fn main(argc: i32, argv: **u8) i32 = { let ps: parser; parserinit(&ps, &l); let f: *node = parsefile(&ps); + // #52: gate cgen on parse-stage errors BEFORE check/cgen. + // A parse-errored decl is silently dropped from the AST; the + // remaining file would otherwise emit asm with rc=0 (silent + // miscompile, and the 994 byte-identity probe ships wrong asm + // silently). Mirrors w6c main.ww:162 / cmd/w6c/main.c. + if (l.errs > 0 || ps.errs > 0) { return 1; }; // #50: mirror w6c — run check before cgen so AST mutations // from #42 (size/align/offset fold) and audit §1.8 (node.type_ // population) land before cgen walks. Without this, wwdump -c diff --git a/test/wcc/989_wwdumpgate_run.c b/test/wcc/989_wwdumpgate_run.c new file mode 100644 index 00000000..9a072a6d --- /dev/null +++ b/test/wcc/989_wwdumpgate_run.c @@ -0,0 +1,139 @@ +/* + * 989_wwdumpgate_run (#52, F15 c4) — wwdump_ww's `-c` and `-r` arms must gate + * on parse-stage errors instead of silently emitting on a broken AST. + * + * THE BUG (wwstage wwdump_ww only, cat-A silent wrong output under rc=0): the + * `-c` codegen arm ran checkfile+cgfile with NO parse-error gate — ps.errs + * was never read — so a parse-errored decl was silently dropped from the AST + * and the rest of the file compiled to asm with exit 0 (and `-c` IS the 994 + * byte-identity probe, so the gate tool itself could ship wrong asm silently). + * The `-r` resolve report arm was likewise ungated. THE FIX: add the + * `l.errs>0 || ps.errs>0` gate after parsefile in both arms, mirroring the + * w6c compiler gate (selfhost/cmd/w6c/main.ww:162 / cmd/w6c/main.c). + * + * WWSTAGE-ONLY: the C wwdump (cstage) implements only -t/-a, not -c/-r — the + * codegen/resolve dump arms are a wwstage-wwdump_ww feature, so there is no + * cstage -c/-r twin to diff against; the gate reference is the w6c compiler. + * + * row | mode | input | result + * -------+------+---------------+---------------------------------- + * bad_c | -c | parse error | rc != 0, zero asm bytes + * bad_r | -r | parse error | rc != 0 + * ok_c | -c | valid file | rc == 0, asm emitted (control) + * + * bad_c / bad_r were RED pre-c4 (rc=0 with truncated asm / a resolve report). + * ok_c pins the healthy emission path unperturbed. + */ +#include +#include +#include +#include + +static int +runwait(const char *cmd) +{ + int rc = system(cmd); + if (rc == -1) return -1; + if (WIFEXITED(rc)) return WEXITSTATUS(rc); + return -1; +} + +static const char *BAD = + "package main;\n\n" + "export fn main(argc: i32, argv: **u8) i32 = {\n\treturn 0;\n};\n\n" + "fn broken( {\n"; +static const char *OK = + "package main;\nexport fn main() i32 = { return 0; };\n"; + +/* Run ` ` capturing asm on stdout; returns rc, sets + * *asmbytes to the stdout byte count when non-NULL. */ +static int +run_dump(const char *tool, const char *mode, const char *src, long *asmbytes) +{ + char outp[128], cmd[1024]; + snprintf(outp, sizeof outp, "/tmp/wwdg_%d_out", getpid()); + snprintf(cmd, sizeof cmd, "%s %s %s > %s 2>/dev/null", + tool, mode, src, outp); + int rc = runwait(cmd); + if (asmbytes) { + FILE *f = fopen(outp, "rb"); + long n = 0; + if (f) { fseek(f, 0, SEEK_END); n = ftell(f); fclose(f); } + *asmbytes = n; + } + unlink(outp); + return rc; +} + +static int +write_file(const char *path, const char *body) +{ + FILE *f = fopen(path, "wb"); + if (!f) return -1; + fputs(body, f); + fclose(f); + return 0; +} + +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 wtool[1024]; + snprintf(wtool, sizeof wtool, "%s/wwdump_ww", bin); + if (access(wtool, X_OK) != 0) { + /* wwdump_ww is the unit under test; absence is a wiring fault. */ + fprintf(stderr, "wwdumpgate: missing %s\n", wtool); + return 1; + } + + char badp[64], okp[64]; + snprintf(badp, sizeof badp, "/tmp/wwdg_bad_%d.ww", getpid()); + snprintf(okp, sizeof okp, "/tmp/wwdg_ok_%d.ww", getpid()); + if (write_file(badp, BAD) || write_file(okp, OK)) return 1; + + int fail = 0, total = 0; + long ab; + + /* bad_c: loud, zero asm */ + total++; + if (run_dump(wtool, "-c", badp, &ab) == 0 || ab != 0) { + fprintf(stderr, "wwdumpgate[bad_c]: emitted asm with rc=0 on " + "parse-errored input (#52); asm=%ld\n", ab); + fail++; + } + + /* bad_r: loud */ + total++; + if (run_dump(wtool, "-r", badp, NULL) == 0) { + fprintf(stderr, "wwdumpgate[bad_r]: resolve report with rc=0 on " + "parse-errored input (#52)\n"); + fail++; + } + + /* ok_c control: rc=0 with asm */ + total++; + if (run_dump(wtool, "-c", okp, &ab) != 0 || ab <= 0) { + fprintf(stderr, "wwdumpgate[ok_c]: healthy file rejected/no asm " + "(asm=%ld)\n", ab); + fail++; + } + + unlink(badp); unlink(okp); + + if (fail) { + fprintf(stderr, "wwdumpgate_run: %d/%d check(s) failed\n", fail, total); + return 1; + } + printf("wwdumpgate_run: %d/%d ok\n", total, total); + return 0; +}