Files
ww/test/wcc/832_tuple_elem_overlong.c

275 lines
9.0 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.
*
* 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
*
* pos row | shape | want
* -------------------+----------------------------------------------+------
* tuple_arr_exact | let t:([2]int,i32)=([1,2],5); t.1 | 5
* tuple_scalar | let t:(i32,i32)=(1,2); t.1 | 2
* tuple_nested_exact | let t:(i32,([2]int,i32))=(9,([3,4],7)); t.0 | 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[] = {
/* exact-length array element in a tuple still ACCEPTS, builds + runs.
* Readout is the scalar t.1 (=5), NOT t.0[1]: indexing an array
* element THROUGH a tuple is a separate pre-existing cgen read bug
* that segfaults on BOTH stages (w6c byte-identical, so not a #20
* regression — filed). The point of this control is that the #20
* over-fill walk does NOT over-reject the valid exact-length tuple-
* with-array-element: the build must succeed and the program run. */
{ "tuple_arr_exact",
"package main;\n"
"export fn main() i32 = {\n"
"\tlet t: ([2]int, i32) = ([1, 2], 5);\n"
"\treturn t.1;\n"
"};\n",
5 },
/* a scalar-only tuple has no array element — the #20 walk no-ops. */
{ "tuple_scalar",
"package main;\n"
"export fn main() i32 = {\n"
"\tlet t: (i32, i32) = (1, 2);\n"
"\treturn t.1;\n"
"};\n",
2 },
/* #26 positive control — a VALID nested tuple with an exact-length
* inner [2]int must NOT be over-rejected by the new recursion arm.
* Readout is the top-level scalar t.0 (=9), NOT a leaf through the
* inner tuple / array element (those hit pre-existing cgen read bugs
* that miscompile on BOTH stages — filed, byte-id-blind, see the
* tuple_arr_exact note). The point here is that checktuplearrfits
* recurses the inner ([2]int,i32), runs the count check, and lets the
* exact-length build proceed: build succeeds + program runs. */
{ "tuple_nested_exact",
"package main;\n"
"export fn main() i32 = {\n"
"\tlet t: (i32, ([2]int, i32)) = (9, ([3, 4], 7));\n"
"\treturn t.0;\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",
};
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;
}