/* * 944_deref_slice_store_run — runtime coverage for project #79: a `[]T` * (slice-typed) VALUE stored through a WHOLE-deref lhs `*p = sliceval` must * move the full 24B {ptr,len,cap} header (ref/hare/rt/ensure.ha:4-8), not * just {ptr}. The `*p = v` deref-store arm was kind-gated on str ONLY; a * slice fell to the 1-word fldstoreop default and silently DROPPED len+cap. * str IS []u8 since Phase 2 (#1), so the str 3-word stash+store machinery * applies to slices verbatim — the fix widens the gate from `str` to * `str || slice` (kind-OR, never a sz==24 test, which would also catch * >16B structs). This is the project #75 str-only-gate one level down * (deref-store). * * SYMMETRIC across both stages (cstage cmd/w6c/cgen.c:3792/3800 + wwstage * cgenexpr.ww `*p=v` twin both dropped identically), so byte-id + cs==ww + * 990-997 are all gate-BLIND here — only a store->read ROUNDTRIP catches it. * * Rows (each store paired with a read mirror; cap!=len in every row so a * dropped len OR cap is caught): * A d_slice_direct — `*pp = p`, read back via the DIRECT local `dst`. * dst is POISONED first with a different slice q (len=4,cap=5) via a * PROVEN 3-word store (`dst = q`, the whole-local slice assign, NOT the * site under test); a 1-word `*pp=p` leaves q's cap=5, the fix lands 8. * B d_slice_thru — same store, read back THROUGH the pointer via the * field-deref read `(*pp).cap`/`(*pp).len` (already 3-word), confirming * the stored header survives a pointer-side read. * C control str-deref — `*pp = "hello"` over a poisoned `d="xy"`; the str * arm we widen AROUND, must still land len=5 (regression guard). * D control (*p).field — `(*pb).s = p` into a struct{s:[]u8} field; a * SEPARATE branch (explicit-deref N_DOT), already 3-word, untouched. * * The whole-deref READ-into-let `let v = *pp` is deliberately NOT used: for * a slice it drops len+cap (ptr-only) while the str form is 3-word — a * SEPARATE read-side gate hole, filed apart from this store fold (project * #81, the read-twin of #79). The element-index read `(*pp)[i]` is likewise * avoided (index-through-deref-field diverges cs vs ww — also separate, * project #82); ptr is covered by row A's dst[0]. * * Verified fail-before (cstage store emits 1-word ptr-only, row A reads the * poison cap=5 -> exit 1) / pass-after (3-word store, exit 0) on BOTH the * cstage `ww` and wwstage `ww_ww` drivers, byte-identical asm. * NNN<950, self-contained (/tmp, no imports), so rule-14's selfhost-sibling * race does not apply (mirrors the 941/942/943 precedent). */ #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[] = { /* A — d_slice_direct: poison dst via the proven 3-word `dst = q`, * then `*pp = p` and read the direct local. p:{cap=8,len=2,[0]=104}, * q:{cap=5,len=4}. A dropped store keeps q's cap=5. */ { "d_slice_direct", "export fn main() i32 = {\n" " let hb: [8]u8; hb[0] = 104u8;\n" " let p: []u8; p.ptr = &hb[0]; p.len = 2; p.cap = 8;\n" " let qb: [8]u8;\n" " let q: []u8; q.ptr = &qb[0]; q.len = 4; q.cap = 5;\n" " let dst: []u8 = q;\n" " let pp: *[]u8 = &dst;\n" " *pp = p;\n" " if (dst.cap: i32 != 8) { return 1; };\n" " if (dst.len: i32 != 2) { return 2; };\n" " if (dst[0] != 104u8) { return 3; };\n" " return 0;\n" "};\n", 0 }, /* B — d_slice_thru: same store, read back THROUGH the pointer via the * 3-word field-deref read `(*pp).cap`/`.len`. */ { "d_slice_thru", "export fn main() i32 = {\n" " let hb: [8]u8; hb[0] = 104u8;\n" " let p: []u8; p.ptr = &hb[0]; p.len = 2; p.cap = 8;\n" " let qb: [8]u8;\n" " let q: []u8; q.ptr = &qb[0]; q.len = 4; q.cap = 5;\n" " let dst: []u8 = q;\n" " let pp: *[]u8 = &dst;\n" " *pp = p;\n" " if ((*pp).cap: i32 != 8) { return 1; };\n" " if ((*pp).len: i32 != 2) { return 2; };\n" " return 0;\n" "};\n", 0 }, /* C — control str-deref: the str arm the fix widens around. Poison * d="xy" then `*pp="hello"`; must still land len=5. */ { "d_str_control", "export fn main() i32 = {\n" " let d: str = \"xy\";\n" " let pp: *str = &d;\n" " *pp = \"hello\";\n" " if (d.len: i32 != 5) { return 1; };\n" " return 0;\n" "};\n", 0 }, /* D — control (*p).field: explicit-deref field store, a separate * already-3-word branch. `(*pb).s = p` into a struct{s:[]u8}. */ { "d_field_control", "type box = struct { s: []u8 };\n" "export fn main() i32 = {\n" " let hb: [8]u8; hb[0] = 104u8;\n" " let p: []u8; p.ptr = &hb[0]; p.len = 2; p.cap = 8;\n" " let b: box;\n" " let pb: *box = &b;\n" " (*pb).s = p;\n" " if (b.s.cap: i32 != 8) { return 1; };\n" " if (b.s.len: i32 != 2) { return 2; };\n" " if (b.s[0] != 104u8) { return 3; };\n" " return 0;\n" "};\n", 0 }, }; static int run_driver(const char *driver, const struct row *r, int i) { char src[96], tmpdir[96], cmd[1024]; snprintf(src, sizeof src, "/tmp/derefslicestore_%d_%d.ww", getpid(), i); snprintf(tmpdir, sizeof tmpdir, "/tmp/derefslicestore_%d_d_%d", getpid(), i); FILE *f = fopen(src, "wb"); if (!f) return -1; fputs(r->src, f); fclose(f); mkdir(tmpdir, 0755); snprintf(cmd, sizeof cmd, "cd %s && %s build %s", tmpdir, driver, src); if (runwait(cmd) != 0) { fprintf(stderr, "row[%s]: build via %s failed\n", r->label, driver); unlink(src); rmdir(tmpdir); return -1; } const char *base = strrchr(src, '/'); base = base ? base + 1 : src; char outbin[160]; snprintf(outbin, sizeof outbin, "%s/%s", tmpdir, base); char *dot = strrchr(outbin, '.'); if (dot && strcmp(dot, ".ww") == 0) *dot = '\0'; int got = runwait(outbin); unlink(src); unlink(outbin); rmdir(tmpdir); return got; } 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]; snprintf(cdrv, sizeof cdrv, "%s/ww", bin); char wdrv[640]; snprintf(wdrv, sizeof wdrv, "%s/ww_ww", bin); struct { const char *name; const char *path; int gated_on_existence; } drivers[] = { { "cstage", cdrv, 0 }, { "wwstage", wdrv, 1 }, { NULL, NULL, 0 }, }; int n = (int)(sizeof rows / sizeof rows[0]); int total = 0, fail = 0; for (int d = 0; drivers[d].name; d++) { if (drivers[d].gated_on_existence && access(drivers[d].path, X_OK) != 0) { fprintf(stderr, "deref_slice_store_run: skip %s (no %s)\n", drivers[d].name, drivers[d].path); continue; } for (int i = 0; i < n; i++) { int got = run_driver(drivers[d].path, &rows[i], i); total++; if (got != rows[i].want) { fprintf(stderr, "deref_slice_store_run[%s][%s]: exit=%d want=%d\n", drivers[d].name, rows[i].label, got, rows[i].want); fail++; } } } if (fail) { fprintf(stderr, "deref_slice_store_run: %d/%d fixtures failed\n", fail, total); return 1; } printf("deref_slice_store_run: %d/%d ok\n", total, total); return 0; }