/* * 957_assert_builtin_run — stderr + byte-id net for #58: the * `assert(cond[, msg])` / `abort([msg])` builtins (the harec * EXPR_ASSERT family) in BOTH stages. * * Root: wwstage had NO builtin intercept for assert/abort — the checker * left the call untyped (asserttyped gate 4 deliberately skipped it) * and cgcall fell through to the regular call path, emitting * `CALL assert(SB)` for a symbol that exists nowhere → link-fail. * cstage lowers both inline via rt_abort (checker tags the callee * ty_err at cmd/wcc/check.c:1536-1572, cgen keys on the tag at * cmd/w6c/cgen.c:6618-6663). The fix mirrors that tag-then-lower pair * into check.ww exprtype N_CALL + cgenexpr.ww cgcall. * * Shared runtime and reject contracts live in r957_* fixtures. This carrier * retains runtime stderr for aborting rows and w6c vs w6c_ww `.s` identity. * The latter fails if the stages diverge. On * pre-fix master every assert/abort row diverges (wwstage emits * the bogus CALL). * * Shadow rows pin the gate's other half: a user fn named assert/abort * (same-module or cross-module, the #45 shape) suppresses the builtin * and routes the regular call path in BOTH stages. The cross-module * row pins the CURRENT flat-scope semantics — if the #45 root (filed * as task #14: cross-module bare resolution of a foreign decl) is * later ruled a reject, its fixture should change polarity with it. * * The migrated reject fixtures retain the checker diagnostics and the * alias-cond rule-10 down-alignment. */ #include #include #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 long slurp(const char *path, char *buf, long cap) { FILE *f = fopen(path, "rb"); if (!f) return -1; long n = (long)fread(buf, 1, cap - 1, f); fclose(f); if (n < 0) n = 0; buf[n] = '\0'; return n; } struct row { const char *label; const char *src; int want_exit; const char *want_msg; /* run stderr must contain this; NULL = must be empty */ int observe_output; }; static const struct row rows[] = { /* Passing asserts (bare and with msg) fall through; exit 42. */ { "pass", "package main;\n" "export fn main() i32 = {\n" "\tassert(1 + 1 == 2);\n" "\tassert(2 > 1, \"never fires\");\n" "\treturn 42;\n" "};\n", 42, NULL, 0 }, /* Failing assert without msg: rt_abort(NULL, 0), exit 1, silent. */ { "fail_nomsg", "package main;\n" "export fn main() i32 = {\n" "\tassert(1 == 2);\n" "\treturn 0;\n" "};\n", 1, NULL, 1 }, /* Failing assert with msg: rt_abort(ptr, len) writes it to stderr. */ { "fail_msg", "package main;\n" "export fn main() i32 = {\n" "\tassert(false, \"assert fired\\n\");\n" "\treturn 0;\n" "};\n", 1, "assert fired", 1 }, /* abort with msg: unconditional rt_abort, exit 1, msg on stderr. */ { "abort_msg", "package main;\n" "export fn main() i32 = {\n" "\tabort(\"boom\\n\");\n" "\treturn 0;\n" "};\n", 1, "boom", 1 }, /* bare abort(): rt_abort(NULL, 0), exit 1, silent. */ { "abort_bare", "package main;\n" "export fn main() i32 = {\n" "\tabort();\n" "\treturn 0;\n" "};\n", 1, NULL, 1 }, /* assert inside an IMPORTED module's fn (single-file multi-package * form, like 953) — the regex fold-5 consumer shape. */ { "imported_mod", "package m;\n" "export fn f() i32 = {\n" "\tassert(3 > 2, \"m.f invariant\");\n" "\treturn 42;\n" "};\n" "package main;\n" "import m;\n" "export fn main() i32 = { return m.f(); };\n", 42, NULL, 0 }, /* SHADOW control, same-module: a user fn `assert` suppresses the * builtin; the call routes the regular path (no-op fn), so the * false cond does NOT abort. Exit 7 proves the user fn ran. */ { "shadow_samemod", "package main;\n" "let g: i32 = 0;\n" "fn assert(b: bool) void = { g = 7; };\n" "export fn main() i32 = {\n" "\tassert(false);\n" "\treturn g;\n" "};\n", 7, NULL, 0 }, /* SHADOW control, cross-module (#45 shape, task #14): a foreign * exported fn `assert` in the flat scope also suppresses the * builtin; bare `assert(false, ...)` binds it and does NOT abort. */ { "shadow_xmod", "package m;\n" "export fn assert(b: bool, msg: str) void = { };\n" "package main;\n" "import m;\n" "export fn main() i32 = {\n" "\tassert(false, \"routed to m.assert\");\n" "\treturn 42;\n" "};\n", 42, NULL, 0 }, { NULL, NULL, 0, NULL, 0 } }; static int slurp_eq(const char *a, const char *b) { FILE *fa = fopen(a, "rb"); FILE *fb = fopen(b, "rb"); if (!fa || !fb) { if (fa) fclose(fa); if (fb) fclose(fb); return -1; } int rc = 0; for (;;) { int ca = fgetc(fa); int cb = fgetc(fb); if (ca != cb) { rc = -1; break; } if (ca == EOF) break; } fclose(fa); fclose(fb); return rc; } 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 w6c[1100], w6c_ww[1100]; snprintf(w6c, sizeof w6c, "%s/w6c", bin); snprintf(w6c_ww, sizeof w6c_ww, "%s/w6c_ww", bin); if (access(w6c_ww, X_OK) != 0) { fprintf(stderr, "assert_builtin: w6c_ww missing — cannot run " "the cs==ww byte-id gate (the whole point of #58)\n"); return 1; } char errbuf[4096]; int n = 0, fail = 0; for (int i = 0; rows[i].src; i++, n++) { char tmpdir[64] = "/tmp/wwasrt_XXXXXX"; if (mkdtemp(tmpdir) == NULL) { perror("assert_builtin: mkdtemp"); fail++; continue; } char src[128], errf[128], outbin[128], sepwork[160]; char cs_s[128], ws_s[128], rmcmd[192]; snprintf(src, sizeof src, "%s/wwasrt_%d_%d.ww", tmpdir, getpid(), i); snprintf(errf, sizeof errf, "%s/wwasrt_%d_%d.err", tmpdir, getpid(), i); snprintf(outbin, sizeof outbin, "%s/wwasrt_%d_%d", tmpdir, getpid(), i); snprintf(sepwork, sizeof sepwork, "%s.sepwork", outbin); snprintf(cs_s, sizeof cs_s, "%s/wwasrt_%d_%d_cs.s", tmpdir, getpid(), i); snprintf(ws_s, sizeof ws_s, "%s/wwasrt_%d_%d_ww.s", tmpdir, getpid(), i); snprintf(rmcmd, sizeof rmcmd, "rm -rf %s", sepwork); FILE *f = fopen(src, "wb"); if (f == NULL) { fail++; goto cleanup; } fputs(rows[i].src, f); fclose(f); char cmd[2048]; if (rows[i].observe_output) { snprintf(cmd, sizeof cmd, "%s/ww build -o %s %s " ">/dev/null 2>&1", bin, outbin, src); if (runwait(cmd) != 0) { fprintf(stderr, "row[%s]: cstage build failed\n", rows[i].label); fail++; goto cleanup; } snprintf(cmd, sizeof cmd, "%s >/dev/null 2>%s", outbin, errf); int got = runwait(cmd); if (got != rows[i].want_exit) { fprintf(stderr, "row[%s]: cstage exit %d, want %d\n", rows[i].label, got, rows[i].want_exit); fail++; } long elen = slurp(errf, errbuf, sizeof errbuf); if (rows[i].want_msg) { if (elen < 0 || strstr(errbuf, rows[i].want_msg) == NULL) { fprintf(stderr, "row[%s]: run stderr missing " "\"%s\"\n", rows[i].label, rows[i].want_msg); fail++; } } else if (elen != 0) { fprintf(stderr, "row[%s]: run stderr not empty " "(rt_abort no-msg must be (NULL, 0))\n", rows[i].label); fail++; } } /* cs==ww byte-id gate. */ snprintf(cmd, sizeof cmd, "%s -o %s %s 2>/dev/null", w6c, cs_s, src); if (runwait(cmd) != 0) { fprintf(stderr, "row[%s]: w6c failed\n", rows[i].label); fail++; goto cleanup; } snprintf(cmd, sizeof cmd, "%s -o %s %s 2>/dev/null", w6c_ww, ws_s, src); if (runwait(cmd) != 0) { fprintf(stderr, "row[%s]: w6c_ww failed\n", rows[i].label); fail++; goto cleanup; } if (slurp_eq(cs_s, ws_s) != 0) { fprintf(stderr, "row[%s]: cstage/wwstage .s DIFFER (rule-10 " "byte-id violation)\n", rows[i].label); fail++; } cleanup: { int cleanbad = 0; if (unlink(src) != 0 && errno != ENOENT) cleanbad = 1; if (unlink(errf) != 0 && errno != ENOENT) cleanbad = 1; if (unlink(outbin) != 0 && errno != ENOENT) cleanbad = 1; if (unlink(cs_s) != 0 && errno != ENOENT) cleanbad = 1; if (unlink(ws_s) != 0 && errno != ENOENT) cleanbad = 1; if (runwait(rmcmd) != 0) cleanbad = 1; if (rmdir(tmpdir) != 0) cleanbad = 1; if (cleanbad) { fprintf(stderr, "row[%s]: temporary cleanup failed\n", rows[i].label); fail++; } } } if (fail) { fprintf(stderr, "%d/%d assert-builtin tests failed\n", fail, n); return 1; } printf("assert_builtin: %d/%d ok (stderr + cs==ww byte-id)\n", n, n); return 0; }