/* * 949_nullable_assert_run — the `e as T` type assertion on a nullable * `(*T | void)` operand must discriminate POINTER-vs-NULL, not compare * the pointer value against a variant tag index (report item #17, F4). * * Gate-blind hazard: for a nullable operand the slot word IS the * pointer (no tag word), so pre-fix wwstage cgtypeassert emitted * `CMPQ $want,AX; JE ok` against the POINTER and then unwrapped a frame * word past the 8B slot — a valid pointer aborted, a void value * silently PASSED, and the unwrap read garbage. cstage already carried * the nullable arm (cmd/w6c/cgen.c N_TYPEASSERT) and ww's own * cgtypetest carries the matching nullable fold; the fix mirrors that * arm into cgtypeassert. wwstage-only (cstage correct) → after the fix * cs.s == ww.s, so the byte-id row witnesses rule-10 convergence. * * Two assertions per row: * - RUNTIME: build with `ww` (cstage) and `ww_ww` (wwstage), run, * compare exit code. This is what was wrong pre-fix. * - ASM BYTE-ID: compile the same source through `w6c` and `w6c_ww` * and require byte-identical .s (rule 10). * * Rows: * 1. asrt_ptr_ok — valid ptr `as *int` must unwrap and deref. * Pre-fix wwstage aborted exit(1) (CMPQ against the pointer). * 2. asrt_void_fail — void value `as *int` must abort exit(1). * Pre-fix wwstage silently PASSED (null == ptr-tag 0). * 3. asrt_void_target_fail — valid ptr `as void` must abort exit(1) * (non-null != the void variant). Exercises the void polarity. * 4. asrt_void_target_ok — void value `as void` must succeed. * Exercises the void polarity on the matching side. */ #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; } struct row { const char *label; const char *src; int want; }; static const struct row rows[] = { { "asrt_ptr_ok", "package main;\n" "export fn main() i32 = {\n" " let x: int = 42;\n" " let p: (*int|void) = &x;\n" " let q: *int = p as *int;\n" " return (*q): i32;\n" "};\n", 42 }, { "asrt_void_fail", "package main;\n" "export fn main() i32 = {\n" " let p: (*int|void) = void;\n" " let q = p as *int;\n" " return 50;\n" "};\n", 1 }, { "asrt_void_target_fail", "package main;\n" "export fn main() i32 = {\n" " let x: int = 5;\n" " let p: (*int|void) = &x;\n" " let z = p as void;\n" " return 7;\n" "};\n", 1 }, { "asrt_void_target_ok", "package main;\n" "export fn main() i32 = {\n" " let p: (*int|void) = void;\n" " let z = p as void;\n" " return 7;\n" "};\n", 7 }, }; static int write_src(const char *dir, const char *base, const struct row *r, char *out, size_t outsz) { snprintf(out, outsz, "%s/%s.ww", dir, base); FILE *f = fopen(out, "wb"); if (!f) return -1; fputs(r->src, f); fclose(f); return 0; } static int build_run(const char *driver, const char *src, const char *workdir) { char cmd[8192]; snprintf(cmd, sizeof cmd, "cd %s && %s build %s > /dev/null 2>&1", workdir, driver, src); if (runwait(cmd) != 0) return -1; const char *base = strrchr(src, '/'); base = base ? base + 1 : src; char outbin[1024]; snprintf(outbin, sizeof outbin, "%s/%s", workdir, base); char *dot = strrchr(outbin, '.'); if (dot && strcmp(dot, ".ww") == 0) *dot = '\0'; return runwait(outbin); } static int files_equal(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 ca, cb, eq = 1; do { ca = fgetc(fa); cb = fgetc(fb); if (ca != cb) { eq = 0; break; } } while (ca != EOF); fclose(fa); fclose(fb); return eq; } int main(void) { const char *bin = getenv("BIN"); if (!bin) bin = "out/bin"; char absbin[512]; if (bin[0] != '/') { char cwd[256]; if (getcwd(cwd, sizeof cwd) == NULL) return 1; snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin); bin = absbin; } char cdrv[640], wdrv[640], cw6[640], ww6[640]; snprintf(cdrv, sizeof cdrv, "%s/ww", bin); snprintf(wdrv, sizeof wdrv, "%s/ww_ww", bin); snprintf(cw6, sizeof cw6, "%s/w6c", bin); snprintf(ww6, sizeof ww6, "%s/w6c_ww", bin); int have_ww = (access(wdrv, X_OK) == 0); int have_w6cww = (access(ww6, X_OK) == 0); int n = (int)(sizeof rows / sizeof rows[0]); int total = 0, fail = 0; for (int i = 0; i < n; i++) { const struct row *r = &rows[i]; char dir[] = "/tmp/nasr.XXXXXX"; if (mkdtemp(dir) == NULL) { fprintf(stderr, "row[%s]: mkdtemp failed\n", r->label); fail++; total++; continue; } char src[1024]; if (write_src(dir, "p", r, src, sizeof src) != 0) { fprintf(stderr, "row[%s]: write src failed\n", r->label); fail++; total++; goto cleanup; } if (have_w6cww) { char css[1024], wss[1024], cmd[8192]; snprintf(css, sizeof css, "%s/cs.s", dir); snprintf(wss, sizeof wss, "%s/ww.s", dir); snprintf(cmd, sizeof cmd, "%s -o %s %s > /dev/null 2>&1", cw6, css, src); int rc1 = runwait(cmd); snprintf(cmd, sizeof cmd, "%s -o %s %s > /dev/null 2>&1", ww6, wss, src); int rc2 = runwait(cmd); total++; if (rc1 != 0 || rc2 != 0) { fprintf(stderr, "row[%s]: w6c/w6c_ww emit failed (%d/%d)\n", r->label, rc1, rc2); fail++; } else if (files_equal(css, wss) != 1) { fprintf(stderr, "row[%s]: cs.s != ww.s (rule-10 break)\n", r->label); fail++; } } { char wk[1024]; snprintf(wk, sizeof wk, "%s/cs", dir); mkdir(wk, 0755); int got = build_run(cdrv, src, wk); total++; if (got != r->want) { fprintf(stderr, "row[%s][cstage]: exit=%d want=%d\n", r->label, got, r->want); fail++; } } if (have_ww) { char wk[1024]; snprintf(wk, sizeof wk, "%s/ww", dir); mkdir(wk, 0755); int got = build_run(wdrv, src, wk); total++; if (got != r->want) { fprintf(stderr, "row[%s][wwstage]: exit=%d want=%d\n", r->label, got, r->want); fail++; } } cleanup: { char rm[1100]; snprintf(rm, sizeof rm, "rm -rf %s", dir); runwait(rm); } } if (fail) { fprintf(stderr, "nullable_assert_run: %d/%d checks failed\n", fail, total); return 1; } printf("nullable_assert_run: %d/%d ok\n", total, total); return 0; }