/* * 949_nullable_try_run — the `?` / `!` try operators on a nullable * `(*T | void)` operand must discriminate POINTER-vs-NULL, not * tag-vs-success-index (report item #15, F4). * * Gate-blind hazard: for a nullable operand AX holds the POINTER and * successtag()=0, so pre-fix wwstage cgtryprop/cgtryunw emitted * `CMPQ $0,AX; JE ok` — treating NULL as success and a valid pointer * as the error, exactly inverted. A valid pointer was early-propagated * / aborted; a null pointer fell through to a deref (SEGV). cstage * already carried the nullable arm (cmd/w6c/cgen.c N_TRYPROP/N_TRYUNW) * and ww's own cgtypetest carries the matching nullable fold; the fix * mirrors that arm into cgtryprop/cgtryunw. 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. tryprop_valid — `p?` on a VALID pointer must unwrap and continue * (mutate through it). Pre-fix wwstage early-propagated → 50. * 2. tryprop_null — `p?` on NULL must propagate (caller sees void). * Pre-fix wwstage treated null as success → null deref SEGV. * 3. tryunw_valid — `p!` on a VALID pointer must unwrap. Pre-fix * wwstage aborted exit(1). * 4. tryunw_null — `p!` on NULL must abort exit(1). Pre-fix wwstage * treated null as success → null deref SEGV. */ #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[] = { { "tryprop_valid", "package main;\n" "fn g(p: (*int|void)) (*int|void) = { let q = p?; *q = 7; return q; };\n" "export fn main() i32 = {\n" " let x: int = 1;\n" " let r = g(&x);\n" " if (x != 7) { return 50; };\n" " return 0;\n" "};\n", 0 }, { "tryprop_null", "package main;\n" "fn g(p: (*int|void)) (*int|void) = { let q = p?; *q = 99; return q; };\n" "export fn main() i32 = {\n" " let r = g(void);\n" " match (r) {\n" " case let q: *int => return 60;\n" " case void => return 7;\n" " };\n" "};\n", 7 }, { "tryunw_valid", "package main;\n" "fn h(p: (*int|void)) int = { let q = p!; return *q; };\n" "export fn main() i32 = {\n" " let x: int = 42;\n" " return h(&x): i32;\n" "};\n", 42 }, { "tryunw_null", "package main;\n" "fn h(p: (*int|void)) int = { let q = p!; return *q; };\n" "export fn main() i32 = {\n" " let r = h(void);\n" " return 50;\n" "};\n", 1 }, }; 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/ntry.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_try_run: %d/%d checks failed\n", fail, total); return 1; } printf("nullable_try_run: %d/%d ok\n", total, total); return 0; }