Files
ww/test/wcc/832_tuple_elem_overlong.c
Hojun-Cho 785fe342fa wcc/check: #24 reject composite-element tuple (array/struct/tuple), declared+inferred, both stages
A tuple whose element chases to TY_ARRAY/STRUCT/TUPLE (>8B) silently
miscompiled both stages: t.0[i] read segfaulted and construction dropped
the payload into the 8B slot. Reject the type at resolution (DISP-B);
faithful inline layout deferred to #60. cstage resolve_type N_TTUPLE
(declared) + N_TUPLE expr (inferred literal, was a cstage-only silent
miscompile + cs!=ww asymmetry); wwstage tinfofornode covers both.
test/wcc/832 + 941 migrated.
2026-06-09 17:29:11 +09:00

315 lines
10 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
* 832_tuple_elem_overlong — an OVERLONG array literal in a TUPLE ELEMENT
* position is INVALID ww; BOTH stages must LOUDLY REJECT at check time
* (#20, the #12 + #106 follow-up; rob spec .ai/rob-20-spec.md). #12/#106
* wired checkarrlitfits for a DIRECT array lhs (+ alias) at the decl /
* return / call-arg positions, but the tuple-element position was never
* wired — `let t: ([2]int, i32) = ([1,2,3], 5)` over-fills the [2]int slot
* with 3 initialisers.
*
* WWSTAGE-ONLY fix — cstage already rejects (tuple element-wise
* type_assignable counts elements: `[3]int` vs `[2]int`). Pre-fix wwstage
* divergence (the mutation-sanity target):
* - tuple_arr_over `let t:([2]int,i32)=([1,2,3],5)` : ww silently ACCEPTED
* - tuple_nested_arr `let t:([2][3]int,i32)=([...x3],5)` : the outer [2]
* slot over-filled by 3 sub-arrays (checkarrlitfits
* nested-array recursion under the tuple walk)
*
* The diagnostic TEXT may differ between stages ("over-fill" vs "not
* assignable") — byte-id-blind (stderr is not asm). Both REJECT and emit no
* asm; selfhost has no overlong tuple-elements, so 990-997 byte-id is
* untouched. Do NOT chase message parity.
*
* #24 (DISP-B broad reject, rob spec .ai/rob-24-spec.md): a tuple whose
* ELEMENT is a composite (array / struct / nested-tuple >8B) cannot ride the
* 8B cursor slot (#60 layout) — it silently DROPS on construction and SEGVs
* on the t.N[i] read. Both stages now REJECT such a type at N_TTUPLE
* resolution (kind ∈ {TY_ARRAY, TY_STRUCT, TY_TUPLE} after TY_NAMED chase),
* converting two silent miscompiles into one loud checker error. This FLIPS
* the former tuple_arr_exact / tuple_nested_exact positive controls to the
* neg table (their types are now outlawed) and adds slice/str/tagged-element
* positive controls proving DISP-B does NOT over-reject the inline-header
* kinds. Full inline support deferred to task #60 / DISP-A.
*
* neg row | shape | gate
* -------------------+----------------------------------------------+--------
* tuple_arr_over | let t:([2]int,i32)=([1,2,3],5) | b. FAIL
* tuple_nested_arr | let t:([2][3]int,i32)=([[..],[..],[..]],5) | b. FAIL
* tuple_in_tuple | let t:([2]int,([2]int,i32))=([..],([1,2,3],.))| #26 FAIL
* tuple_return_over | fn()([2]int,i32){return([1,2,3],5)} | #25 FAIL
* tuple_return_nested| fn()([2]int,([2]int,i32)){return(..,([..3],.))| #25 FAIL
* tuple_arr_exact | let t:([2]int,i32)=([1,2],5) | #24 FAIL
* tuple_nested_exact | let t:(i32,([2]int,i32))=(9,([3,4],7)) | #24 FAIL
* tuple_struct_elem | type P=struct{x:int}; let t:(P,i32)=(P{x=1},5)| #24 FAIL
*
* pos row | shape | want
* -------------------+----------------------------------------------+------
* tuple_scalar | let t:(i32,i32)=(1,2); t.1 | 2
* tuple_slice_elem | let t:([]u8,i32)=(a,5); t.1 | 5
* tuple_str_elem | let t:(str,i32)=("hi",7); t.1 | 7
* tuple_tagged_elem | let t:((void|size),i32)=(3,9); t.1 | 9
*/
#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 scalar-only tuple — all elements ride the 8B slot. */
{ "tuple_scalar",
"package main;\n"
"export fn main() i32 = {\n"
"\tlet t: (i32, i32) = (1, 2);\n"
"\treturn t.1;\n"
"};\n",
2 },
/* #24 positive control — a SLICE element is DISP-B-allowed (its 24B
* header rides the cursor). Must NOT be over-rejected. Readout is the
* scalar t.1 (=5). */
{ "tuple_slice_elem",
"package main;\n"
"export fn main() i32 = {\n"
"\tlet hb: [8]u8;\n"
"\tlet a: []u8; a.ptr = &hb[0]; a.len = 3; a.cap = 8;\n"
"\tlet t: ([]u8, i32) = (a, 5);\n"
"\treturn t.1;\n"
"};\n",
5 },
/* #24 positive control — a STR element is DISP-B-allowed (24B header).
* Readout is the scalar t.1 (=7). */
{ "tuple_str_elem",
"package main;\n"
"export fn main() i32 = {\n"
"\tlet t: (str, i32) = (\"hi\", 7);\n"
"\treturn t.1;\n"
"};\n",
7 },
/* #24 positive control — a TAGGED-UNION element is DISP-B-allowed (its
* tag+payload box rides the slot). Readout is the scalar t.1 (=9). */
{ "tuple_tagged_elem",
"package main;\n"
"export fn main() i32 = {\n"
"\tlet t: ((void | size), i32) = (3, 9);\n"
"\treturn t.1;\n"
"};\n",
9 },
};
/* An overlong array literal in a tuple element — both stages must FAIL the
* build (loud checker diagnostic, not silent accept). */
static const char *neg[] = {
/* tuple_arr_over (the #20 repro) — [2]int slot gets 3 inits. */
"package main;\n"
"export fn main() i32 = {\n"
"\tlet t: ([2]int, i32) = ([1, 2, 3], 5);\n"
"\treturn t.0[1]: i32;\n"
"};\n",
/* tuple_nested_arr — outer [2] slot over-filled by 3 sub-arrays
* (checkarrlitfits recursion under the tuple walk). */
"package main;\n"
"export fn main() i32 = {\n"
"\tlet t: ([2][3]int, i32) = "
"([[1, 2, 3], [4, 5, 6], [7, 8, 9]], 5);\n"
"\treturn t.0[0][0]: i32;\n"
"};\n",
/* tuple_in_tuple (#26) — nested tuple element; inner [2]int over-
* filled by 3. The walk must RECURSE the nested tuple (let pos). */
"package main;\n"
"export fn main() i32 = {\n"
"\tlet t: ([2]int, ([2]int, i32)) = ([1, 2], ([3, 4, 5], 6));\n"
"\treturn t.0[0]: i32;\n"
"};\n",
/* tuple_return_over (#25) — overlong [2]int in a tuple RETURN. */
"package main;\n"
"fn f() ([2]int, i32) = {\n"
"\treturn ([1, 2, 3], 5);\n"
"};\n"
"export fn main() i32 = {\n"
"\tlet t = f();\n"
"\treturn t.1;\n"
"};\n",
/* tuple_return_nested (#25 path × #26 recursion) — nested tuple in a
* RETURN, inner [2]int over-filled. */
"package main;\n"
"fn f() ([2]int, ([2]int, i32)) = {\n"
"\treturn ([1, 2], ([3, 4, 5], 6));\n"
"};\n"
"export fn main() i32 = {\n"
"\tlet t = f();\n"
"\treturn t.0[0]: i32;\n"
"};\n",
/* tuple_arr_exact (#24) — was a GREEN positive control; the DISP-B
* broad reject now OUTLAWS an ARRAY tuple element (silent-drop on
* construction + segv on t.0[i] read). MIGRATED to the neg table. */
"package main;\n"
"export fn main() i32 = {\n"
"\tlet t: ([2]int, i32) = ([1, 2], 5);\n"
"\treturn t.1;\n"
"};\n",
/* tuple_nested_exact (#24) — was a GREEN positive control; a NESTED
* TUPLE element is now outlawed by DISP-B. MIGRATED to the neg table. */
"package main;\n"
"export fn main() i32 = {\n"
"\tlet t: (i32, ([2]int, i32)) = (9, ([3, 4], 7));\n"
"\treturn t.0;\n"
"};\n",
/* tuple_struct_elem (#24) — a STRUCT tuple element is now a checker
* loud (was a cgen "unsupported field-read shape" loud). */
"package main;\n"
"type P = struct { x: int };\n"
"export fn main() i32 = {\n"
"\tlet t: (P, i32) = (P { x = 1 }, 5);\n"
"\treturn t.1;\n"
"};\n",
};
static int
run_driver(const char *driver, const struct row *r, int i)
{
char src[64], tmpdir[64], cmd[1024];
snprintf(src, sizeof src, "/tmp/teo_%d_%d.ww", getpid(), i);
snprintf(tmpdir, sizeof tmpdir, "/tmp/teo_%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 2>/dev/null",
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[128];
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;
}
/* build_should_fail — an overlong tuple-element array must error on
* `driver`; returns 0 when the build correctly FAILS, non-zero when it
* wrongly succeeded. */
static int
build_should_fail(const char *driver, const char *src, int i)
{
char s[64], tmpdir[64], cmd[1024];
snprintf(s, sizeof s, "/tmp/teon_%d_%d.ww", getpid(), i);
snprintf(tmpdir, sizeof tmpdir, "/tmp/teon_%d_d_%d", getpid(), i);
FILE *f = fopen(s, "wb");
if (!f) return -1;
fputs(src, f);
fclose(f);
mkdir(tmpdir, 0755);
snprintf(cmd, sizeof cmd, "cd %s && %s build %s 2>/dev/null",
tmpdir, driver, s);
int rc = runwait(cmd);
unlink(s);
const char *base = strrchr(s, '/');
base = base ? base + 1 : s;
char outbin[128];
snprintf(outbin, sizeof outbin, "%s/%s", tmpdir, base);
char *dot = strrchr(outbin, '.');
if (dot && strcmp(dot, ".ww") == 0) *dot = '\0';
unlink(outbin);
rmdir(tmpdir);
return rc == 0 ? -1 : 0; /* build must NOT succeed */
}
int
main(void)
{
const char *bin = getenv("BIN");
if (!bin) bin = "out/bin";
char absbin[1024];
if (bin[0] != '/') {
char cwd[1024];
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin);
bin = absbin;
}
char cdrv[1024];
snprintf(cdrv, sizeof cdrv, "%s/ww", bin);
char wdrv[1024];
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 nn = (int)(sizeof neg / sizeof neg[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, "tuple_elem_overlong: 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,
"tuple_elem_overlong[%s][%s]: exit=%d want=%d\n",
drivers[d].name, rows[i].label,
got, rows[i].want);
fail++;
}
}
for (int i = 0; i < nn; i++) {
total++;
if (build_should_fail(drivers[d].path, neg[i],
100 + i) != 0) {
fprintf(stderr,
"tuple_elem_overlong[%s][neg%d]: built ok, "
"expected a loud error\n",
drivers[d].name, i);
fail++;
}
}
}
if (fail) {
fprintf(stderr,
"tuple_elem_overlong: %d/%d fixtures failed\n", fail, total);
return 1;
}
printf("tuple_elem_overlong: %d/%d ok\n", total, total);
return 0;
}