An empty `[]` carries no element type; ww gets it only from a let annotation (the #45 retype). Both stages used to silently default the element to u8, and in value-form positions (return / call-arg) the lowering miscompiled — malloc(8) ignoring n, a 16B *u8|nomem where a 24B slice was expected (#5). Now every empty alloc that isn't a let-annotated binding fails to infer with a loud error, aligning ww DOWN to harec (ref/harec/src/check.c:1801-1802). Mechanism: clet / checkletassign flags the single alloc call node that a `let x: []T =` rescues (save/restore around the init walk); the alloc branch errors on any empty alloc that isn't that node. The #45 wide-T retype path is kept. wwstage needs an extra not-yet-stamped guard because resolvewalk re-types value nodes context-free after checkletassign. Tests: negative cstage-driver 729 (table-driven: bare-let, return, call-arg, assignment) + positive @test in attest_pass.ww exercising the u8 and the wide-i32 (#45) paths at runtime. Both stages reject symmetrically; byte-id verified on []u8 and []i32.
199 lines
6.0 KiB
C
199 lines
6.0 KiB
C
/*
|
|
* 729_empty_alloc_infer — check: an untyped empty `alloc([], n)` must
|
|
* loudly fail to infer its slice element type instead of silently
|
|
* defaulting to []u8 (task #3 / B', subsumes #5).
|
|
*
|
|
* Pre-fix: cmd/wcc/check.c's alloc-slice branch pinned the element type
|
|
* to u8 with no context. `let b = alloc([], n)!` (no annotation) became
|
|
* []u8, and the value-form lowerings `return alloc([], n)!` /
|
|
* `f(alloc([], n))` SILENTLY MISCOMPILED (malloc(8) ignoring n; a 16B
|
|
* *u8|nomem where a 24B slice was expected) — that was bug #5.
|
|
*
|
|
* Post-fix: ww aligns DOWN to harec, which refuses to guess —
|
|
* "Cannot infer array type from context" (ref/harec/src/check.c:1801).
|
|
* The ONLY context that supplies the element type is the let annotation
|
|
* (the #45 retype), so an annotated alloc of ANY element type still
|
|
* compiles; every other empty alloc errors at check time.
|
|
*
|
|
* Cstage-driver negative test, same shape as 712_redecl. The wwstage
|
|
* twin (selfhost/cmd/wcc/check.ww, exprtype alloc-slice branch +
|
|
* checkletassign context-flag) rejects symmetrically; it is validated by
|
|
* the 990-997 byte-id gates rebuilding the *_ww tools from check.ww. The
|
|
* positive @test (annotated []u8 / []i32 alloc) rides attest_pass.ww
|
|
* (910/997, dual-stage).
|
|
*
|
|
* row | kind | what it pins
|
|
* ---------------------+-------------+------------------------------
|
|
* neg_bare_let | build fails | no-annotation u8 default gone
|
|
* neg_return | build fails | #5 value-form return
|
|
* neg_call_arg | build fails | #5 value-form call-arg
|
|
* neg_assign | build fails | re-bind has no annotation hint
|
|
* pos_annotated_u8 | exit=16 | let []u8 still infers
|
|
* pos_annotated_wide | exit=5 | let []i32 (#45 retype) infers
|
|
*/
|
|
#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;
|
|
}
|
|
|
|
/*
|
|
* kind == 0: negative — build must fail (any nonzero exit).
|
|
* kind == 1: positive — build must succeed AND binary exits with `want`.
|
|
*/
|
|
struct row { const char *label; int kind; const char *src; int want; };
|
|
|
|
static const struct row rows[] = {
|
|
/* neg: bare let, no annotation — was a silent []u8 default. */
|
|
{ "neg_bare_let", 0,
|
|
"fn main() i32 = {\n"
|
|
" let b = alloc([], 8u64)!;\n"
|
|
" return b.len: i32;\n"
|
|
"};\n",
|
|
0 },
|
|
|
|
/* neg: value-form return — #5 silent miscompile. */
|
|
{ "neg_return", 0,
|
|
"fn mk() []u8 = { return alloc([], 8u64)!; };\n"
|
|
"fn main() i32 = { let b: []u8 = mk(); return b.len: i32; };\n",
|
|
0 },
|
|
|
|
/* neg: value-form call-arg — #5 silent miscompile. */
|
|
{ "neg_call_arg", 0,
|
|
"fn g(x: []u8) i32 = { return x.len: i32; };\n"
|
|
"fn main() i32 = { return g(alloc([], 8u64)!); };\n",
|
|
0 },
|
|
|
|
/* neg: assignment target supplies NO context (only the let
|
|
* annotation does; #45). A re-bind `x = alloc([], n)` must error
|
|
* too — top-down hint threading to assign is A', out of scope. */
|
|
{ "neg_assign", 0,
|
|
"fn main() i32 = {\n"
|
|
" let x: []u8 = alloc([], 4u64)!;\n"
|
|
" x = alloc([], 8u64)!;\n"
|
|
" return x.len: i32;\n"
|
|
"};\n",
|
|
0 },
|
|
|
|
/* pos: let annotation supplies the element type (u8). alloc([], n)
|
|
* is Hare's len=0 / cap=n empty slice, so write into the cap-backed
|
|
* memory and read back — also proves the u8 element stride. */
|
|
{ "pos_annotated_u8", 1,
|
|
"import rt;\n"
|
|
"fn main() i32 = {\n"
|
|
" let b: []u8 = alloc([], 8u64)!;\n"
|
|
" b[0] = 7u8;\n"
|
|
" b[3] = 9u8;\n"
|
|
" return (b[0]: i32) + (b[3]: i32);\n"
|
|
"};\n",
|
|
16 },
|
|
|
|
/* pos: let annotation with a wide element — the #45 retype. The i32
|
|
* stride (4B) must drive indexing, not the u8 default. */
|
|
{ "pos_annotated_wide", 1,
|
|
"import rt;\n"
|
|
"fn main() i32 = {\n"
|
|
" let w: []i32 = alloc([], 4u64)!;\n"
|
|
" w[2] = 5i32;\n"
|
|
" return w[2];\n"
|
|
"};\n",
|
|
5 },
|
|
};
|
|
|
|
static int
|
|
run_row(const char *driver, const struct row *r, int i)
|
|
{
|
|
char src[128], tmpdir[128], cmd[2048];
|
|
snprintf(src, sizeof src, "/tmp/wcealloc_%d_%d.ww", getpid(), i);
|
|
snprintf(tmpdir, sizeof tmpdir, "/tmp/wcealloc_%d_d_%d", getpid(), i);
|
|
|
|
FILE *f = fopen(src, "wb");
|
|
if (!f) return -1;
|
|
fputs(r->src, f);
|
|
fclose(f);
|
|
|
|
mkdir(tmpdir, 0755);
|
|
|
|
const char *base = strrchr(src, '/');
|
|
base = base ? base + 1 : src;
|
|
char outbin[256];
|
|
snprintf(outbin, sizeof outbin, "%s/%s", tmpdir, base);
|
|
char *dot = strrchr(outbin, '.');
|
|
if (dot && strcmp(dot, ".ww") == 0) *dot = '\0';
|
|
|
|
char combined[256];
|
|
snprintf(combined, sizeof combined, "/tmp/wcealloc_%d_%d.combined.ww",
|
|
getpid(), i);
|
|
|
|
if (r->kind == 0) {
|
|
/* Negative — build must fail. */
|
|
snprintf(cmd, sizeof cmd,
|
|
"cd %s && %s build %s >/dev/null 2>&1", tmpdir, driver, src);
|
|
int rc = runwait(cmd);
|
|
if (rc == 0) {
|
|
fprintf(stderr,
|
|
"ealloc[%s]: build unexpectedly succeeded\n",
|
|
r->label);
|
|
unlink(outbin);
|
|
}
|
|
unlink(src);
|
|
unlink(combined);
|
|
rmdir(tmpdir);
|
|
return rc == 0 ? -1 : 0;
|
|
}
|
|
|
|
/* Positive — build (with rt linked via `ww run`) then check exit. */
|
|
snprintf(cmd, sizeof cmd, "%s run %s >/dev/null 2>&1", driver, src);
|
|
int got = runwait(cmd);
|
|
unlink(src);
|
|
unlink(outbin);
|
|
unlink(combined);
|
|
rmdir(tmpdir);
|
|
if (got != r->want) {
|
|
fprintf(stderr, "ealloc[%s]: exit=%d want=%d\n",
|
|
r->label, got, r->want);
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
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);
|
|
|
|
int n = (int)(sizeof rows / sizeof rows[0]);
|
|
int fail = 0;
|
|
for (int i = 0; i < n; i++) {
|
|
if (run_row(cdrv, &rows[i], i) != 0) fail++;
|
|
}
|
|
|
|
if (fail) {
|
|
fprintf(stderr, "ealloc: %d/%d row(s) failed\n", fail, n);
|
|
return 1;
|
|
}
|
|
printf("ealloc: %d/%d ok\n", n, n);
|
|
return 0;
|
|
}
|