A slice VALUE stored through a whole-deref lhs `*p = v` dropped len+cap:
the `*p = v` arm kind-gated its 3-word {ptr,len,cap} stash+store on str
ONLY, so a slice fell to the 1-word fldstoreop default (ptr only). The
deref READ is 3-word, so the reader got garbage len/cap -- correctness,
not perf. str IS []u8 since #1, so the str machinery applies verbatim;
widen the gate str -> str||slice (kind-OR, not a sz==24 test). This is
the project #75 str-only-gate one level down (deref-store).
cstage cmd/w6c/cgen.c:3792/3800 (two gates); wwstage cgenexpr.ww `*p=v`
twin detects N_TSLICE syntactically (mirror str). Both stages dropped
identically, so cs==ww + 990-997 + byte-id are all gate-blind here --
only a store->read roundtrip catches it. New 944_deref_slice_store_run
asserts the {ptr,len,cap} survives a poisoned dst, via the direct local
and the field-deref read; str-deref + (*p).field controls guard the
untouched arms. Verified fail-before (1-word ptr store) / pass-after
(8/8), byte-identical asm both stages.
Out-of-gate, deferred to #80: the wwstage syntactic detection is
alias-BLIND -- a slice-alias `*Foo` (Foo=[]T) or non-ident deref-store
stays 1-word, the SAME retained divergence str already carries (cstage's
resolved-type vt fires in both). #80 unifies detection by aligning the
wwstage UP, not gating cstage down. Separately surfaced (filed apart,
not touched here): the whole-deref READ-into-let `let v = *p` drops
len+cap for a slice while the str form is 3-word -- the read-side twin
of this store hole.
216 lines
7.4 KiB
C
216 lines
7.4 KiB
C
/*
|
|
* 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 <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[] = {
|
|
/* 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;
|
|
}
|