Every surviving carrier now owns its artifacts: checked mkdir/mkdtemp/ fopen acquisition, one all-exit cleanup funnel per carrier, ENOENT- tolerant checked unlinks, exact-path deletion (rm -rf only for an owned pid-keyed dir or a .sepwork beneath one), and cleanup failure fails a passing carrier without overwriting its diagnostic. In the same pass the carriers adapt to the driver contract this branch lands: --sep and WW_PKGCACHE are gone, -S and the /tmp/ww_run_<pid> scratch contract are asserted, and rows whose runtime or reject coverage moved to test/wcc/data fixtures or test/lang @test owners are trimmed to the byte/artifact/diagnostic observations only they can make. Repair and adaptation ride together because most files interleave both in the same hunks; splitting would manufacture intermediate carrier states that never existed and cannot run against either driver.
324 lines
11 KiB
C
324 lines
11 KiB
C
/*
|
|
* 989_seproot_export_run — sep-build of a ROOT unit whose `export fn`
|
|
* references an unexported LOCAL type (#69, BUG-1).
|
|
*
|
|
* Root cause it guards: the separate-compilation producer compiled EVERY package,
|
|
* INCLUDING the root build-target, with the `.wwi`-producer `-I` flag.
|
|
* `-I` triggers wwi_emit -> check_exported_type, which rejects an exported
|
|
* declaration that references an unexported type. A terminal binary's root
|
|
* legitimately has such a decl (`export fn use(a: *t)` over an unexported
|
|
* local `type t`) — fine, because the root is never imported, so its `.wwi`
|
|
* is never consumed. Historically, the separately compiled root was also
|
|
* passed `-I`, so it rejected and blocked every real tool build.
|
|
* Fix: for pi==root the producer invokes `w6c -c -o` WITHOUT `-I`.
|
|
*
|
|
* Graph (smallest reproducing shape): root -> { c (one real dep package) }.
|
|
* The root exports `use(a: *t)` over the unexported local `type t`.
|
|
*
|
|
* Asserts (direct builds compile every package):
|
|
* 1. Build + run, BOTH stages -> exit EXPECT_EXIT. Pre-fix the root w6c
|
|
* pass exited 1 ("exported declaration references unexported type"),
|
|
* so the build never produced a binary.
|
|
* 2. cs==ww (rule 10): per-package `.s`/`.wwi`/`.unit.ww` for {c,__root}
|
|
* AND the final binary are byte-identical between the two drivers.
|
|
* 3. NON-VACUITY (forced -I reject): re-run the EXACT pre-fix root
|
|
* invocation on the produced `__root.unit.ww` — `w6c -c -I <wwi> -o`
|
|
* MUST exit non-zero (the export-check fires on this pattern), while
|
|
* `w6c -c -o` (the post-fix root invocation) MUST exit 0. Demonstrated
|
|
* for BOTH compilers (w6c, w6c_ww). This is the bug, isolated.
|
|
* 4. The root `.s` is produced and defines the exported fn symbol.
|
|
*
|
|
* All intermediates are `-o`-redirected to /tmp for isolation. Models
|
|
* 989_sepdotpath_run.c conventions; 989 prefix per the sep-gate precedent.
|
|
*/
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
#include <sys/wait.h>
|
|
#include <sys/stat.h>
|
|
#include <errno.h>
|
|
|
|
#define EXPECT_EXIT 37
|
|
|
|
static int
|
|
runwait(const char *cmd)
|
|
{
|
|
int rc = system(cmd);
|
|
if (rc == -1) return -1;
|
|
if (WIFEXITED(rc)) return WEXITSTATUS(rc);
|
|
return 1;
|
|
}
|
|
|
|
static const char *
|
|
absbin(void)
|
|
{
|
|
const char *b = getenv("BIN");
|
|
if (!b) b = "out/bin";
|
|
if (b[0] == '/') return b;
|
|
static char buf[2048];
|
|
char cwd[1024];
|
|
if (getcwd(cwd, sizeof cwd) == NULL) return NULL;
|
|
snprintf(buf, sizeof buf, "%s/%s", cwd, b);
|
|
return buf;
|
|
}
|
|
|
|
static int
|
|
slurp(const char *path, char **outbuf, size_t *outlen)
|
|
{
|
|
FILE *f = fopen(path, "rb");
|
|
if (!f) return -1;
|
|
fseek(f, 0, SEEK_END);
|
|
long n = ftell(f);
|
|
fseek(f, 0, SEEK_SET);
|
|
if (n < 0) { fclose(f); return -1; }
|
|
char *b = malloc((size_t)n + 1);
|
|
if (!b) { fclose(f); return -1; }
|
|
if (fread(b, 1, (size_t)n, f) != (size_t)n) { free(b); fclose(f); return -1; }
|
|
b[n] = '\0';
|
|
fclose(f);
|
|
*outbuf = b;
|
|
*outlen = (size_t)n;
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
files_eq(const char *a, const char *b)
|
|
{
|
|
char *ba = NULL, *bb = NULL;
|
|
size_t na = 0, nb = 0;
|
|
if (slurp(a, &ba, &na) < 0 || slurp(b, &bb, &nb) < 0) {
|
|
free(ba); free(bb);
|
|
return -1;
|
|
}
|
|
int eq = (na == nb && memcmp(ba, bb, na) == 0);
|
|
free(ba); free(bb);
|
|
return eq ? 0 : 1;
|
|
}
|
|
|
|
/* 0 if `needle` occurs in the file at `path`, 1 if absent, -1 on read err. */
|
|
static int
|
|
file_contains(const char *path, const char *needle)
|
|
{
|
|
char *b = NULL;
|
|
size_t n = 0;
|
|
if (slurp(path, &b, &n) < 0) return -1;
|
|
int found = (strstr(b, needle) != NULL);
|
|
free(b);
|
|
return found ? 0 : 1;
|
|
}
|
|
|
|
static int
|
|
write_file(const char *path, const char *body)
|
|
{
|
|
FILE *f = fopen(path, "wb");
|
|
if (!f) return -1;
|
|
fputs(body, f);
|
|
fclose(f);
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
main(void)
|
|
{
|
|
const char *bin = absbin();
|
|
if (!bin) return 1;
|
|
char td[64], cmd[8192], p[1024];
|
|
int fail = 0, have_lib = 0, have_c = 0;
|
|
|
|
snprintf(td, sizeof td, "/tmp/wwseproot_%d", getpid());
|
|
if (mkdir(td, 0755) != 0) {
|
|
fprintf(stderr, "seproot FAIL: cannot acquire %s\n", td);
|
|
return 1;
|
|
}
|
|
|
|
/* lib/c — one real dep package the root imports. */
|
|
snprintf(p, sizeof p, "%s/lib", td);
|
|
if (mkdir(p, 0755) != 0) {
|
|
fprintf(stderr, "seproot FAIL: cannot create %s\n", p);
|
|
fail++; goto out;
|
|
}
|
|
have_lib = 1;
|
|
snprintf(p, sizeof p, "%s/lib/c", td);
|
|
if (mkdir(p, 0755) != 0) {
|
|
fprintf(stderr, "seproot FAIL: cannot create %s\n", p);
|
|
fail++; goto out;
|
|
}
|
|
have_c = 1;
|
|
snprintf(p, sizeof p, "%s/lib/c/mod.ww", td);
|
|
if (write_file(p, "package c;\nexport fn cval() i32 = { return 5; };\n"))
|
|
{ fail++; goto out; }
|
|
|
|
/* Root: `export fn use(a: *t)` over the UNEXPORTED local `type t` —
|
|
* exactly the pattern `-I`'s check_exported_type rejected. */
|
|
char rootww[1024];
|
|
snprintf(rootww, sizeof rootww, "%s/root.ww", td);
|
|
if (write_file(rootww,
|
|
"package main;\n"
|
|
"import c;\n"
|
|
"type t = struct { v: i32 };\n"
|
|
"export fn use(a: *t) i32 = { return a.v; };\n"
|
|
"fn main() i32 = {\n"
|
|
"\tlet x: t = t { v = 42 };\n"
|
|
"\treturn use(&x) - c.cval();\n"
|
|
"};\n"))
|
|
{ fail++; goto out; }
|
|
|
|
struct { const char *drv, *tag; char prog[1024]; }
|
|
stg[] = { { "ww", "cs", {0} }, { "ww_ww", "ww", {0} } };
|
|
|
|
for (int s = 0; s < 2; s++) {
|
|
snprintf(stg[s].prog, sizeof stg[s].prog, "%s/prog.%s", td, stg[s].tag);
|
|
/* Each direct build compiles every package, so the
|
|
* `.s`/`.unit.ww` this gate inspects are always produced. */
|
|
snprintf(cmd, sizeof cmd,
|
|
"timeout 240 %s/%s build "
|
|
"-I %s/lib -o %s %s >/dev/null 2>&1",
|
|
bin, stg[s].drv, td, stg[s].prog, rootww);
|
|
if (runwait(cmd) != 0) {
|
|
fprintf(stderr, "seproot FAIL: %s build (pre-fix: -I on root "
|
|
"rejects export-fn-over-unexported-type)\n", stg[s].drv);
|
|
fail++;
|
|
continue;
|
|
}
|
|
int rc = runwait(stg[s].prog);
|
|
if (rc != EXPECT_EXIT) {
|
|
fprintf(stderr, "seproot FAIL: %s prog exit=%d expected %d\n",
|
|
stg[s].drv, rc, EXPECT_EXIT);
|
|
fail++;
|
|
}
|
|
}
|
|
|
|
/* cs==ww (rule 10): per-package .s/.unit.ww + final binary. The root's
|
|
* `.wwi` is intentionally NOT produced (the fix), so it is excluded
|
|
* here and asserted ABSENT below; the dep `c` still emits a `.wwi`. */
|
|
const char *pkgs[] = { "c", "__root" };
|
|
for (int i = 0; i < 2; i++) {
|
|
const char *suf[] = { ".s", ".wwi", ".unit.ww" };
|
|
for (int k = 0; k < 3; k++) {
|
|
if (strcmp(pkgs[i], "__root") == 0 && strcmp(suf[k], ".wwi") == 0)
|
|
continue;
|
|
char a[1024], b[1024];
|
|
snprintf(a, sizeof a, "%s/prog.%s.sepwork/%s%s",
|
|
td, stg[0].tag, pkgs[i], suf[k]);
|
|
snprintf(b, sizeof b, "%s/prog.%s.sepwork/%s%s",
|
|
td, stg[1].tag, pkgs[i], suf[k]);
|
|
if (files_eq(a, b) != 0) {
|
|
fprintf(stderr, "seproot FAIL: cs!=ww for %s%s (rule 10)\n",
|
|
pkgs[i], suf[k]);
|
|
fail++;
|
|
}
|
|
}
|
|
}
|
|
if (files_eq(stg[0].prog, stg[1].prog) != 0) {
|
|
fprintf(stderr, "seproot FAIL: cs exe != ww exe (rule 10)\n");
|
|
fail++;
|
|
}
|
|
|
|
/* NON-VACUITY: the fix omits `-I` for the root, so the root's `.wwi`
|
|
* (the producer flag's only effect) must NOT exist — BOTH stages. */
|
|
for (int s = 0; s < 2; s++) {
|
|
char rw[1024];
|
|
snprintf(rw, sizeof rw, "%s/prog.%s.sepwork/__root.wwi", td, stg[s].tag);
|
|
if (access(rw, 0) == 0) {
|
|
fprintf(stderr, "seproot FAIL: %s produced __root.wwi (-I still "
|
|
"passed for the root)\n", stg[s].drv);
|
|
fail++;
|
|
}
|
|
}
|
|
|
|
/* NON-VACUITY: replay the EXACT pre-fix root invocation on the produced
|
|
* `__root.unit.ww`. With `-I` (the .wwi producer) the export-check fires
|
|
* and w6c exits non-zero; without `-I` (the post-fix invocation) it
|
|
* exits 0. Proven for BOTH compilers. */
|
|
{
|
|
char unit[1024], wwi[1024], asmf[1024];
|
|
snprintf(unit, sizeof unit, "%s/prog.cs.sepwork/__root.unit.ww", td);
|
|
struct { const char *comp; } cc[] = { { "w6c" }, { "w6c_ww" } };
|
|
for (int j = 0; j < 2; j++) {
|
|
snprintf(wwi, sizeof wwi, "%s/nv.%s.wwi", td, cc[j].comp);
|
|
snprintf(asmf, sizeof asmf, "%s/nv.%s.s", td, cc[j].comp);
|
|
snprintf(cmd, sizeof cmd,
|
|
"%s/%s -c -I %s -o %s %s >/dev/null 2>&1",
|
|
bin, cc[j].comp, wwi, asmf, unit);
|
|
if (runwait(cmd) == 0) {
|
|
fprintf(stderr, "seproot FAIL: %s -c -I accepted the root "
|
|
"export-over-unexported-type (bug not reproduced -> "
|
|
"vacuous gate)\n", cc[j].comp);
|
|
fail++;
|
|
}
|
|
snprintf(cmd, sizeof cmd,
|
|
"%s/%s -c -o %s %s >/dev/null 2>&1",
|
|
bin, cc[j].comp, asmf, unit);
|
|
if (runwait(cmd) != 0) {
|
|
fprintf(stderr, "seproot FAIL: %s -c -o (no -I) rejected the "
|
|
"root unit (post-fix invocation must succeed)\n",
|
|
cc[j].comp);
|
|
fail++;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* The root `.s` is produced and defines the exported fn. */
|
|
{
|
|
char rs[1024];
|
|
snprintf(rs, sizeof rs, "%s/prog.cs.sepwork/__root.s", td);
|
|
if (file_contains(rs, "use") != 0) {
|
|
fprintf(stderr, "seproot FAIL: __root.s lacks the exported `use` "
|
|
"symbol (root compile produced no code)\n");
|
|
fail++;
|
|
}
|
|
}
|
|
|
|
out:
|
|
snprintf(cmd, sizeof cmd,
|
|
"rm -rf -- '%s/prog.cs.sepwork' '%s/prog.ww.sepwork'", td, td);
|
|
if (runwait(cmd) != 0) {
|
|
fprintf(stderr, "seproot FAIL: cannot clean .sepwork trees\n");
|
|
fail++;
|
|
}
|
|
{
|
|
char path[1200];
|
|
snprintf(path, sizeof path, "%s/root.ww", td);
|
|
if (unlink(path) != 0 && errno != ENOENT) { fprintf(stderr, "seproot FAIL: cannot remove %s\n", path); fail++; }
|
|
snprintf(path, sizeof path, "%s/prog.cs", td);
|
|
if (unlink(path) != 0 && errno != ENOENT) { fprintf(stderr, "seproot FAIL: cannot remove %s\n", path); fail++; }
|
|
snprintf(path, sizeof path, "%s/prog.ww", td);
|
|
if (unlink(path) != 0 && errno != ENOENT) { fprintf(stderr, "seproot FAIL: cannot remove %s\n", path); fail++; }
|
|
snprintf(path, sizeof path, "%s/nv.w6c.wwi", td);
|
|
if (unlink(path) != 0 && errno != ENOENT) { fprintf(stderr, "seproot FAIL: cannot remove %s\n", path); fail++; }
|
|
snprintf(path, sizeof path, "%s/nv.w6c.s", td);
|
|
if (unlink(path) != 0 && errno != ENOENT) { fprintf(stderr, "seproot FAIL: cannot remove %s\n", path); fail++; }
|
|
snprintf(path, sizeof path, "%s/nv.w6c_ww.wwi", td);
|
|
if (unlink(path) != 0 && errno != ENOENT) { fprintf(stderr, "seproot FAIL: cannot remove %s\n", path); fail++; }
|
|
snprintf(path, sizeof path, "%s/nv.w6c_ww.s", td);
|
|
if (unlink(path) != 0 && errno != ENOENT) { fprintf(stderr, "seproot FAIL: cannot remove %s\n", path); fail++; }
|
|
}
|
|
if (have_c) {
|
|
snprintf(p, sizeof p, "%s/lib/c", td);
|
|
{
|
|
char path[1200];
|
|
snprintf(path, sizeof path, "%s/mod.ww", p);
|
|
if (unlink(path) != 0 && errno != ENOENT) { fprintf(stderr, "seproot FAIL: cannot remove %s\n", path); fail++; }
|
|
}
|
|
if (rmdir(p) != 0 && errno != ENOENT) { fprintf(stderr, "seproot FAIL: cannot remove %s\n", p); fail++; }
|
|
}
|
|
if (have_lib) {
|
|
snprintf(p, sizeof p, "%s/lib", td);
|
|
if (rmdir(p) != 0 && errno != ENOENT) { fprintf(stderr, "seproot FAIL: cannot remove %s\n", p); fail++; }
|
|
}
|
|
if (rmdir(td) != 0 && errno != ENOENT) {
|
|
fprintf(stderr, "seproot FAIL: cannot remove %s\n", td);
|
|
fail++;
|
|
}
|
|
if (fail) {
|
|
fprintf(stderr, "seproot: %d check(s) failed\n", fail);
|
|
return 1;
|
|
}
|
|
printf("seproot: root `export fn use(a:*t)` over unexported `type t` "
|
|
"via build_one_sep — build+run (exit %d) + cs==ww per-pkg + final "
|
|
"binary + forced -I rejects (bug) while -o accepts (both compilers)\n",
|
|
EXPECT_EXIT);
|
|
return 0;
|
|
}
|