Files
ww/test/wcc/989_syntaxexport_run.c
Hojun-Cho a95a7a316b test/wcc: carrier ownership repair and driver-contract adaptation
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.
2026-08-07 23:21:04 +09:00

307 lines
10 KiB
C

/*
* 989_syntaxexport_run — BUG-A residual at the surviving syntax->wcc
* package boundary (#72), post the #74 frontend consolidation.
*
* Root cause it guards: `lib/ww/syntax/` exports the fns wcc calls, and
* those fns' signatures name frontend types (tok, tinfo, tctx, ...). When
* the separate-compilation producer emits syntax's `.wwi`,
* check_exported_type recurses every exported decl — and, for an exported
* struct, its FIELDS too (the
* harec STORAGE_STRUCT precedent, wwi.c wwi_check_type N_TSTRUCT). Any
* referenced type that is itself unexported aborts the producer with
* "exported declaration references unexported type". Pre-fix, syntax's
* surface left 16 such types unexported, so syntax's `.wwi` never produced
* -> every wcc sep-build was blocked. Fix: `export` those 16 type decls
* (10 fn-signature-direct + 6 surfaced by the struct-field recursion:
* tykind/tfield/tparam/ttupleelem via tinfo, tctx via typesinit, and
* tinfocacheent via tctx). Exporting a TYPE emits no code -> the bug is
* byte-id-neutral; this gate proves the PRODUCER residual is closed.
*
* The consumer here uses QUALIFIED refs (`syntax.tokname`, `syntax.tkind`)
* to isolate the producer fix from the separate unqualified-ref question
* (#75). It happens to link + run end-to-end, which also shows the
* qualified-ref path resolves across the sep boundary.
*
* Asserts (direct builds compile every package):
* 1. Build + run, BOTH stages -> exit EXPECT_EXIT (`syntax.tokname(
* TK_INT)` == "int", len 3). Pre-fix the syntax producer exited 1, so
* the build never produced a binary.
* 2. syntax's `.wwi` is produced and carries the 16 `export type` decls.
* 3. cs==ww (rule 10): syntax's `.wwi`/`.s`/`.unit.ww` AND the final
* binary are byte-identical between `ww` and `ww_ww`.
* 4. NON-VACUITY (the bug, isolated, BOTH compilers): replay the producer
* `w6c -c -I` on the produced `syntax.unit.ww` -> exits 0 (exports
* present). On a copy with `export ` stripped from the type decls ->
* MUST exit non-zero (the export-check fires). Proven for w6c + w6c_ww.
*
* All intermediates are `-o`-redirected to /tmp; it only reads
* the in-tree `lib/ww/syntax`. Models 989_seproot_export_run.c.
*/
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/stat.h>
#define EXPECT_EXIT 3
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;
if (fputs(body, f) == EOF) { fclose(f); return -1; }
return fclose(f);
}
int
main(void)
{
const char *bin = absbin();
if (!bin) return 1;
char root[1024];
if (getcwd(root, sizeof root) == NULL) return 1;
char td[] = "/tmp/wwsynexp_XXXXXX";
char cmd[8192];
int fail = 0, cleanup_fail = 0;
if (mkdtemp(td) == NULL) return 1;
/* Root: QUALIFIED refs into the real syntax package — `syntax.tokname`
* over `syntax.tkind`. The producer must serialize syntax's `.wwi`
* (exported fns over frontend types) WITHOUT the unexported-type abort. */
char rootww[1024];
snprintf(rootww, sizeof rootww, "%s/root.ww", td);
if (write_file(rootww,
"package main;\n"
"import syntax;\n"
"fn main() i32 = {\n"
"\tlet s: str = syntax.tokname(syntax.tkind.TK_INT);\n"
"\treturn s.len: i32;\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);
/* Direct builds produce the `.wwi`/`.s`/`.unit.ww` artifacts this
* gate inspects. */
snprintf(cmd, sizeof cmd,
"timeout 240 %s/%s build "
"-I %s/lib/ww -o %s %s >%s/build.%s.log 2>&1",
bin, stg[s].drv, root, stg[s].prog, rootww,
td, stg[s].tag);
if (runwait(cmd) != 0) {
fprintf(stderr, "syntaxexport FAIL: %s build (pre-fix: syntax "
"producer rejects export-fn-over-unexported-type)\n", stg[s].drv);
fail++;
continue;
}
/* the producer abort must not appear in the build log. */
char log[1024], wwi[1024];
snprintf(log, sizeof log, "%s/build.%s.log", td, stg[s].tag);
if (file_contains(log, "unexported type") == 0) {
fprintf(stderr, "syntaxexport FAIL: %s build log shows "
"'unexported type' (producer residual not closed)\n", stg[s].drv);
fail++;
}
snprintf(wwi, sizeof wwi, "%s/prog.%s.sepwork/syntax.wwi", td, stg[s].tag);
if (access(wwi, 0) != 0) {
fprintf(stderr, "syntaxexport FAIL: %s produced no syntax.wwi\n",
stg[s].drv);
fail++;
}
int rc = runwait(stg[s].prog);
if (rc != EXPECT_EXIT) {
fprintf(stderr, "syntaxexport FAIL: %s prog exit=%d expected %d\n",
stg[s].drv, rc, EXPECT_EXIT);
fail++;
}
}
/* syntax's `.wwi` carries the now-exported frontend types — spot-check
* a representative set spanning each surfacing path (fn-direct + the
* struct-field and tctx-field cascades). */
{
char wwi[1024];
snprintf(wwi, sizeof wwi, "%s/prog.cs.sepwork/syntax.wwi", td);
const char *want[] = {
"export type tok ", "export type tkind ", "export type node ",
"export type nkind ", "export type tinfo ", "export type tfield ",
"export type tctx ", "export type tinfocacheent ",
};
for (int i = 0; i < (int)(sizeof want / sizeof want[0]); i++)
if (file_contains(wwi, want[i]) != 0) {
fprintf(stderr, "syntaxexport FAIL: syntax.wwi lacks '%s'\n",
want[i]);
fail++;
}
}
/* cs==ww (rule 10): syntax's per-package artefacts + the final binary. */
{
const char *suf[] = { ".wwi", ".s", ".unit.ww" };
for (int k = 0; k < 3; k++) {
char a[1024], b[1024];
snprintf(a, sizeof a, "%s/prog.cs.sepwork/syntax%s", td, suf[k]);
snprintf(b, sizeof b, "%s/prog.ww.sepwork/syntax%s", td, suf[k]);
if (files_eq(a, b) != 0) {
fprintf(stderr, "syntaxexport FAIL: cs!=ww for syntax%s "
"(rule 10)\n", suf[k]);
fail++;
}
}
if (files_eq(stg[0].prog, stg[1].prog) != 0) {
fprintf(stderr, "syntaxexport FAIL: cs exe != ww exe (rule 10)\n");
fail++;
}
}
/* NON-VACUITY (the bug, isolated): replay the producer on the produced
* `syntax.unit.ww`. With the exports present `w6c -c -I` exits 0; with
* `export ` stripped from the type decls it MUST exit non-zero (the
* export-check fires). Proven for BOTH compilers. */
{
char unit[1024], ne[1024];
snprintf(unit, sizeof unit, "%s/prog.cs.sepwork/syntax.unit.ww", td);
snprintf(ne, sizeof ne, "%s/syntax.noexport.ww", td);
snprintf(cmd, sizeof cmd,
"sed 's/export type /type /g' %s > %s", unit, ne);
if (runwait(cmd) != 0) { fail++; goto out; }
struct { const char *comp; } cc[] = { { "w6c" }, { "w6c_ww" } };
for (int j = 0; j < 2; j++) {
char wwi[1024], asmf[1024];
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, "syntaxexport FAIL: %s -c -I rejected the "
"EXPORTED unit (post-fix producer must succeed)\n",
cc[j].comp);
fail++;
}
snprintf(cmd, sizeof cmd, "%s/%s -c -I %s -o %s %s >/dev/null 2>&1",
bin, cc[j].comp, wwi, asmf, ne);
if (runwait(cmd) == 0) {
fprintf(stderr, "syntaxexport FAIL: %s -c -I accepted the "
"UNEXPORTED-type unit (bug not reproduced -> vacuous "
"gate)\n", cc[j].comp);
fail++;
}
}
}
out:
for (int s = 0; s < 2; s++) {
const char *tag = s == 0 ? "cs" : "ww";
char path[1200];
snprintf(path, sizeof path, "%s/prog.%s.sepwork", td, tag);
snprintf(cmd, sizeof cmd, "rm -rf -- '%s'", path);
if (runwait(cmd) != 0) cleanup_fail = 1;
snprintf(path, sizeof path, "%s/prog.%s", td, tag);
if (unlink(path) != 0 && errno != ENOENT) cleanup_fail = 1;
snprintf(path, sizeof path, "%s/build.%s.log", td, tag);
if (unlink(path) != 0 && errno != ENOENT) cleanup_fail = 1;
}
{
const char *files[] = {
"root.ww", "syntax.noexport.ww",
"nv.w6c.wwi", "nv.w6c.s",
"nv.w6c_ww.wwi", "nv.w6c_ww.s",
};
for (size_t i = 0; i < sizeof files / sizeof files[0]; i++) {
char path[1200];
snprintf(path, sizeof path, "%s/%s", td, files[i]);
if (unlink(path) != 0 && errno != ENOENT) cleanup_fail = 1;
}
}
if (rmdir(td) != 0) cleanup_fail = 1;
if (cleanup_fail) {
fprintf(stderr, "syntaxexport FAIL: cleanup incomplete under %s\n", td);
fail++;
}
if (fail) {
fprintf(stderr, "syntaxexport: %d check(s) failed\n", fail);
return 1;
}
printf("syntaxexport: syntax->wcc boundary sep-PRODUCES clean (16 export "
"type decls) + qualified-ref consumer build+run (exit %d) + cs==ww "
"syntax.wwi/.s/.unit.ww/binary + non-vacuity (export strip rejects, "
"both compilers)\n", EXPECT_EXIT);
return 0;
}