wwdump: -c/-r gate on parse errors

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.
This commit is contained in:
2026-06-13 04:40:58 +09:00
parent 4dc8459a90
commit 2ce94a194a
4 changed files with 169 additions and 0 deletions

View File

@@ -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).

View File

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

View File

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

View File

@@ -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 <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
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 `<tool> <mode> <src>` 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;
}