wcc/ww: cgtryprop/cgtryunw gain the nullable arm
Try-propagation on a nullable value fell to the tagged path and compared the pointer to a tag — the null check came out inverted. Mirror ww's own cgtypetest nullable fold (cgenexpr.ww:652) and the cstage twin (cgen.c:10366/10504). Review item #15.
This commit is contained in:
246
test/wcc/949_nullable_try_run.c
Normal file
246
test/wcc/949_nullable_try_run.c
Normal file
@@ -0,0 +1,246 @@
|
||||
/*
|
||||
* 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 <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/stat.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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
Reference in New Issue
Block a user