/* * 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 #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; } /* * 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 cmd[2048]; if (r->kind == 0) { /* Negative — build must fail. Source + outbin live inside a * per-row tmpdir so the compiler's .sepwork (mkdir'd * before the build fails) lands there and is rm -rf'd, not * leaked next to a bare-/tmp source. */ char tmpdir[128], src[160], outbin[160], rmcmd[192]; snprintf(tmpdir, sizeof tmpdir, "/tmp/wcealloc_%d_d_%d", getpid(), i); mkdir(tmpdir, 0755); snprintf(src, sizeof src, "%s/wcealloc_%d_%d.ww", tmpdir, getpid(), i); snprintf(outbin, sizeof outbin, "%s/wcealloc_%d_%d", tmpdir, getpid(), i); snprintf(rmcmd, sizeof rmcmd, "rm -rf %s", tmpdir); FILE *f = fopen(src, "wb"); if (!f) { runwait(rmcmd); return -1; } fputs(r->src, f); fclose(f); snprintf(cmd, sizeof cmd, "%s build -o %s %s >/dev/null 2>&1", driver, outbin, src); int rc = runwait(cmd); if (rc == 0) fprintf(stderr, "ealloc[%s]: build unexpectedly succeeded\n", r->label); runwait(rmcmd); return rc == 0 ? -1 : 0; } /* Positive — build (with rt linked via `ww run`) then check exit. * `ww run` self-cleans its scratch (/tmp/ww_run_.sepwork), so the * bare-/tmp source leaks nothing; left unwrapped. */ char src[128], combined[160]; snprintf(src, sizeof src, "/tmp/wcealloc_%d_%d.ww", getpid(), i); snprintf(combined, sizeof combined, "/tmp/wcealloc_%d_%d.combined.ww", getpid(), i); FILE *f = fopen(src, "wb"); if (!f) return -1; fputs(r->src, f); fclose(f); snprintf(cmd, sizeof cmd, "%s run %s >/dev/null 2>&1", driver, src); int got = runwait(cmd); unlink(src); unlink(combined); 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; }