From c7bd0fd48a9f5c02f53a4162dba9c3f2c2eac47f Mon Sep 17 00:00:00 2001 From: Hojun-Cho Date: Sat, 8 Aug 2026 14:32:51 +0900 Subject: [PATCH] test: port the module-reset adjacency pin to ww --- Makefile | 2 +- test/wcc/989_modresetadj_run.c | 207 --------------------------------- test/xmod/modreset_test.ww | 82 +++++++++++++ 3 files changed, 83 insertions(+), 208 deletions(-) delete mode 100644 test/wcc/989_modresetadj_run.c create mode 100644 test/xmod/modreset_test.ww diff --git a/Makefile b/Makefile index 1b48299b..87a87f56 100644 --- a/Makefile +++ b/Makefile @@ -374,7 +374,7 @@ SEP_WW_TARGETS = $(SEP_WW_TESTS:%=wwtest/%) # they run under test-compiler. XMOD_WW_TESTS = test/xmod/collide_test.ww test/xmod/m1_test.ww \ test/xmod/label_test.ww test/xmod/typecheck_test.ww \ - test/xmod/enumcap_test.ww + test/xmod/enumcap_test.ww test/xmod/modreset_test.ww XMOD_WW_TARGETS = $(XMOD_WW_TESTS:%=wwtest/%) BOOTSTRAP_WRAPPER_SOURCES = test/wcc/950_selfcheck.c \ test/wcc/991_w6a_ww.c \ diff --git a/test/wcc/989_modresetadj_run.c b/test/wcc/989_modresetadj_run.c deleted file mode 100644 index 9b097669..00000000 --- a/test/wcc/989_modresetadj_run.c +++ /dev/null @@ -1,207 +0,0 @@ -/* - * 989_modresetadj_run — BUG-A (#9) regression pin: the lexer must NOT - * leave a stale TK_MODPATH pending past a `//ww:module-reset`. - * - * THE BUG: `//ww:module

` and `//ww:module-reset` are both recognized - * inside skipws() in SOURCE ORDER, each setting a sticky lexer flag; - * lexnext() drains them in a FIXED order (modreset first, modpath second). - * When the two land in the SAME skipws run with no real token between them - * — i.e. `//ww:module e` directly followed by `//ww:module-reset`, which the - * sep driver emits for an empty / export-less inlined module body — the - * modreset drained first, then the STALE TK_MODPATH=e surfaced PAST the - * reset boundary, re-binding pathmod=e. The root `package main` was then - * validated against import path "e" → "package main does not match import - * path e" → HARD REJECT (cmd/wcc/parse.c:1437, both stages). - * - * THE FIX (cmd/wcc/lex.c + lib/ww/syntax/lex.ww, skipws): a reset - * recognized in source clears any modpath set earlier in the same skipws - * run. In the NORMAL non-empty boundary (reset THEN the next module's path) - * the clear is a no-op — the path is set after the reset and legitimately - * survives (the 990-997 byte-id gates cover that case). - * - * WHY a direct-frontend pin and not a `ww build` dir-fixture: the driver - * materializes an empty module's interface (`.wwi`) BEFORE composing the - * root unit, and wwi_emit defaults a decl-less module's package line to the - * literal "main" (task #11, a SEPARATE bug) — so a natural empty-module dir - * import never produces the directive ADJACENCY this bug needs; it trips #11 - * first. The pin therefore feeds the composed unit (with the adjacency) - * straight to w6c / w6c_ww, immune to #11. - * - * Three legs (the teeth): (1) w6c accepts the adjacency unit (pre-fix it - * REJECTS — the BUG-A teeth); (2) w6c_ww accepts it (rule-10 twin); - * (3) the two `.s` are byte-identical (rule-10 stage symmetry) and the - * assembled+linked program runs to its return value (the emitted code is - * valid, not just parse-clean). - * - * Every intermediate is `-o`-redirected to /tmp and never lands - * next to a source. Models 989_declns_sep + 994_w6c_ww conventions; 989 - * prefix per the sep-gate precedent (the 7xx range is exhausted). - */ -#include -#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 * -absbin(void) -{ - const char *b = getenv("BIN"); - if (!b) b = "out/bin"; - if (b[0] == '/') return b; - static char buf[2048]; - char cwd[1024]; - if (getcwd(cwd, sizeof cwd) == NULL) return NULL; - snprintf(buf, sizeof buf, "%s/%s", cwd, b); - return buf; -} - -static int -slurp(const char *path, char **outbuf, size_t *outlen) -{ - FILE *f = fopen(path, "rb"); - if (!f) return -1; - fseek(f, 0, SEEK_END); - long n = ftell(f); - fseek(f, 0, SEEK_SET); - if (n < 0) { fclose(f); return -1; } - char *b = malloc((size_t)n + 1); - if (!b) { fclose(f); return -1; } - if (fread(b, 1, (size_t)n, f) != (size_t)n) { free(b); fclose(f); return -1; } - b[n] = '\0'; - fclose(f); - *outbuf = b; - *outlen = (size_t)n; - return 0; -} - -static int -files_eq(const char *a, const char *b) -{ - char *ba = NULL, *bb = NULL; - size_t na = 0, nb = 0; - if (slurp(a, &ba, &na) < 0 || slurp(b, &bb, &nb) < 0) { - free(ba); free(bb); - return -1; - } - int eq = (na == nb && memcmp(ba, bb, na) == 0); - free(ba); free(bb); - return eq ? 0 : 1; -} - -static int -write_file(const char *path, const char *content) -{ - FILE *f = fopen(path, "wb"); - if (!f) return -1; - fputs(content, f); - fclose(f); - return 0; -} - -/* - * The composed sep-unit carrying the bug's trigger: `//ww:module e` - * (opens module e, sets the pending modpath) immediately followed — with - * no real token between — by `//ww:module-reset` (closes the empty body). - * Both directives land in ONE skipws run. The primary body then declares - * `package main` against the reset boundary; pre-fix the stale modpath=e - * survived and the clause was checked against "e". RET is distinctive so - * the run leg proves main actually executed. - */ -#define RET 42 -static const char UNIT[] = - "//ww:module e\n" - "//ww:module-reset\n" - "package main;\n" - "import e;\n" - "export fn main() i32 = { return 42; };\n"; - -int -main(void) -{ - const char *bin = absbin(); - if (!bin) return 1; - int pid = (int)getpid(); - - char unit[64], scs[64], sww[64], obj[64], prog[64], cmd[4096]; - snprintf(unit, sizeof unit, "/tmp/mradj_%d.unit.ww", pid); - snprintf(scs, sizeof scs, "/tmp/mradj_%d_cs.s", pid); - snprintf(sww, sizeof sww, "/tmp/mradj_%d_ww.s", pid); - snprintf(obj, sizeof obj, "/tmp/mradj_%d.o", pid); - snprintf(prog, sizeof prog, "/tmp/mradj_%d.bin", pid); - - int fail = 0; - - if (write_file(unit, UNIT) != 0) { - fprintf(stderr, "modresetadj: cannot write %s\n", unit); - return 1; - } - - /* Leg 1 — cstage w6c accepts the adjacency (pre-fix: REJECT). */ - snprintf(cmd, sizeof cmd, - "%s/w6c -c -o %s %s 2>/dev/null", bin, scs, unit); - if (runwait(cmd) != 0) { - fprintf(stderr, - "modresetadj FAIL: w6c rejected the adjacency unit (BUG-A)\n"); - fail++; - } - - /* Leg 2 — wwstage w6c_ww accepts it (rule-10 twin). */ - snprintf(cmd, sizeof cmd, - "%s/w6c_ww -c -o %s %s 2>/dev/null", bin, sww, unit); - if (runwait(cmd) != 0) { - fprintf(stderr, - "modresetadj FAIL: w6c_ww rejected the adjacency unit (BUG-A)\n"); - fail++; - } - - /* Leg 3a — the two .s are byte-identical (rule-10 stage symmetry). */ - if (!fail && files_eq(scs, sww) != 0) { - fprintf(stderr, - "modresetadj FAIL: w6c vs w6c_ww .s differ (rule 10)\n"); - fail++; - } - - /* Leg 3b — the emitted code assembles, links, and runs to RET. */ - if (!fail) { - snprintf(cmd, sizeof cmd, - "%s/w6a -o %s %s 2>/dev/null", bin, obj, scs); - if (runwait(cmd) != 0) { - fprintf(stderr, "modresetadj FAIL: w6a errored\n"); - fail++; - } - } - if (!fail) { - snprintf(cmd, sizeof cmd, - "%s/w6l -o %s %s %s/../lib/libwwrt.a 2>/dev/null", - bin, prog, obj, bin); - if (runwait(cmd) != 0) { - fprintf(stderr, "modresetadj FAIL: w6l errored\n"); - fail++; - } - } - if (!fail) { - int got = runwait(prog); - if (got != RET) { - fprintf(stderr, - "modresetadj FAIL: program exit=%d want=%d\n", got, RET); - fail++; - } - } - - unlink(unit); unlink(scs); unlink(sww); unlink(obj); unlink(prog); - - if (fail) return 1; - printf("modresetadj: w6c/w6c_ww accept the //ww:module + //ww:module-reset " - "adjacency, byte-identical .s, program runs to %d (#9)\n", RET); - return 0; -} diff --git a/test/xmod/modreset_test.ww b/test/xmod/modreset_test.ww new file mode 100644 index 00000000..a8a5b5b0 --- /dev/null +++ b/test/xmod/modreset_test.ww @@ -0,0 +1,82 @@ +package modreset_test; + +// BUG-A (#9) regression pin: the lexer must not leave a stale +// TK_MODPATH pending past a `//ww:module-reset`. Port of the retired +// native carrier test/wcc/989_modresetadj_run.c; every assertion +// preserved. +// +// The composed sep-unit puts `//ww:module e` and `//ww:module-reset` +// in ONE skipws run with no real token between (the shape the sep +// driver emits for an empty/export-less inlined module body); pre-fix +// the stale modpath=e survived the reset and `package main` was +// rejected against import path "e". The unit is fed STRAIGHT to +// w6c/w6c_ww: the natural empty-module dir route trips the separate +// #11 wwi_emit bug first, and the driver cannot locate the phantom +// package e (which is also why the corpus cannot host it -- the +// blanket data byte-id gate drives every non-error fixture through +// `ww build`). +// +// Legs: (1) w6c accepts the adjacency (pre-fix REJECT -- the teeth); +// (2) w6c_ww accepts it (rule-10 twin); (3a) the two .s are +// byte-identical; (3b) assemble+link+run to exit 42 (the emitted code +// is valid, not just parse-clean). + +import os; +import os.exec; +import strings; +import testenv; +import time; + +fn fail(why: str) void = { + let m: str = strings.concat("modreset FAIL: ", why, "\n"); + os.write(2, m.ptr, m.len: u64); + assert(false); +}; + +fn tmo() time.duration = { + return (240i64 * (time.second: i64)): time.duration; +}; + +// -1 encodes an abnormal (non-EXIT) termination, never a valid code. +fn runcode(dir: str, name: str, argv: []str) i32 = { + let co: testenv.commandout; + testenv.runcommand(dir, dir, name, argv, tmo(), &co); + if (co.termination != exec.termination.EXIT) { return -1; }; + return co.code; +}; + +@test fn modresetadj() void = { + let td: str = testenv.fresh(); + let unit: str = strings.concat(td, "/mradj.unit.ww"); + testenv.writefile(unit, strings.concat( + "//ww:module e\n", + "//ww:module-reset\n", + "package main;\n", + "import e;\n", + "export fn main() i32 = { return 42; };\n")); + let scs: str = strings.concat(td, "/cs.s"); + let sww: str = strings.concat(td, "/ww.s"); + let cav: []str = [testenv.driver("w6c"), "-c", "-o", scs, unit]; + if (runcode(td, "w6c", cav) != 0) { + fail("w6c rejected the adjacency unit (BUG-A stale modpath)"); + }; + let wav: []str = [testenv.driver("w6c_ww"), "-c", "-o", sww, unit]; + if (runcode(td, "w6c_ww", wav) != 0) { + fail("w6c_ww rejected the adjacency unit (BUG-A)"); + }; + if (!testenv.same(testenv.readfile(scs), testenv.readfile(sww))) { + fail("w6c vs w6c_ww .s differ (rule 10)"); + }; + let obj: str = strings.concat(td, "/mradj.o"); + let aav: []str = [testenv.driver("w6a"), "-o", obj, scs]; + if (runcode(td, "w6a", aav) != 0) { fail("w6a errored"); }; + let prog: str = strings.concat(td, "/mradj.bin"); + let lav: []str = [testenv.driver("w6l"), "-o", prog, obj, + strings.concat(testenv.repo(), "/out/lib/libwwrt.a")]; + if (runcode(td, "w6l", lav) != 0) { fail("w6l errored"); }; + let rav: []str = [prog]; + if (runcode(td, "run", rav) != 42) { + fail("program exit != 42 (main did not run to its return)"); + }; + testenv.clean(td); +};