Files
ww/test/wcc/989_depmain_sep.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

290 lines
8.9 KiB
C

/*
* 989_depmain_sep — #99 E3 flip-blocker: an IMPORTED dir-package's non-
* exported `fn main` must mangle on its import path under `ww build`,
* not emit a bare `TEXT main` that collides with the root unit's entry.
*
* The bare-`main` carve-out (cgen mod_collect + cgfn label, both stages)
* keys on `leaf=="main" && imported==0`. Each package is its own w6c unit,
* composed with a path-carrying `//ww:module-reset <path>`
* (#57) that mangles decls but leaves imported==0 — so a dep unit's `fn
* main` was imported==0 too, the carve-out fired, and it emitted a bare
* `TEXT main` → `w6l: duplicate symbol main` against the root's real main.
*
* The fix adds a NON-ROOT gate `&& !sep_isdep` to the carve-out. sep_isdep
* is set from `wwiout != NULL`: the producer passes -I (the .wwi output) to
* DEP units ONLY — the root/link-entry unit's .wwi is stripped (#69) — so
* `wwiout==NULL` is the exact "this is the root/link-entry unit" oracle. A
* dep's main now mangles on its path; only the root's stays bare.
*
* Fixtures (test/wcc/data/depmain99/, srcd-relative dir-package resolution):
* main.ww package main, import aa.bb — root; calls bb.run() → 7.
* aa/bb/bb.ww package bb, `fn main`→7 + `export fn run` calls it — the
* DOTTED-path imported dep whose main must mangle aa.bb.main.
* main_cc.ww package main, import cc — root; calls cc.run() → 9.
* cc/cc.ww package cc, `fn main`→9 + `export fn run` — the SINGLE-
* component imported dep whose main must mangle cc.main.
*
* Asserts (both driver stages, direct builds):
* 1. SEP build+link+run exit == the dep's main's return — pre-fix the
* bare-main collision makes w6l reject. This is the non-vacuity teeth
* (verified: revert `&& !sep_isdep` → `w6l: duplicate symbol main`).
* 2. MANGLED — the dep's .s shows `TEXT <path>.main`; exactly ONE bare
* `TEXT main,` across the whole sep build (the root's __root.s).
* 3. cs==ww (rule 10) — per-package .s/.wwi byte-identical across the
* cstage `ww` and wwstage `ww_ww` sep-drivers.
*
* Every intermediate is `-o`-redirected to /tmp for isolation. Models 989_coloimport_sep
* conventions; 989 prefix per the sep-gate precedent.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <dirent.h>
static int
runwait(const char *cmd)
{
int rc = system(cmd);
if (rc == -1) return -1;
if (WIFEXITED(rc)) return WEXITSTATUS(rc);
return -2;
}
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;
}
/* cs==ww over a sep build's per-package output: every .s/.wwi the cstage
* driver produced in `csdir` must be byte-identical to the wwstage driver's
* same-named file in `wwdir`. Returns the count of mismatches. */
static int
cmp_sepwork(const char *csdir, const char *wwdir, const char *label)
{
DIR *d = opendir(csdir);
if (!d) {
fprintf(stderr, "depmain FAIL: %s — no cs sepwork %s\n", label, csdir);
return 1;
}
int bad = 0, seen = 0;
struct dirent *ent;
while ((ent = readdir(d)) != NULL) {
const char *nm = ent->d_name;
size_t nl = strlen(nm);
int is_s = (nl > 2 && strcmp(nm + nl - 2, ".s") == 0);
int is_wwi = (nl > 4 && strcmp(nm + nl - 4, ".wwi") == 0);
if (!is_s && !is_wwi) continue;
seen++;
char a[2048], b[2048];
snprintf(a, sizeof a, "%s/%s", csdir, nm);
snprintf(b, sizeof b, "%s/%s", wwdir, nm);
if (files_eq(a, b) != 0) {
fprintf(stderr, "depmain FAIL: %s — cs!=ww for %s (rule 10)\n",
label, nm);
bad++;
}
}
closedir(d);
if (seen == 0) {
fprintf(stderr, "depmain FAIL: %s — no .s/.wwi in %s\n", label, csdir);
bad++;
}
return bad;
}
/* count_bare_main — number of bare `TEXT main,` labels across every .s in
* `dir`. Exactly 1 is correct (the root entry); the bug produces 2 (the
* dep's mis-bared main collides). */
static int
count_bare_main(const char *dir)
{
DIR *d = opendir(dir);
if (!d) return -1;
int n = 0;
struct dirent *ent;
while ((ent = readdir(d)) != NULL) {
const char *nm = ent->d_name;
size_t nl = strlen(nm);
if (!(nl > 2 && strcmp(nm + nl - 2, ".s") == 0)) continue;
char p[2048];
snprintf(p, sizeof p, "%s/%s", dir, nm);
char *b = NULL;
size_t bn = 0;
if (slurp(p, &b, &bn) < 0) continue;
/* a bare label is `TEXT main,` at column 0 of any line. */
for (char *s = b; (s = strstr(s, "TEXT main,")) != NULL; s++)
if (s == b || s[-1] == '\n') n++;
free(b);
}
closedir(d);
return n;
}
/* file_has — 0 if `needle` occurs in `path`, 1 if absent, -1 on read err. */
static int
file_has(const char *path, const char *needle)
{
char *b = NULL;
size_t bn = 0;
if (slurp(path, &b, &bn) < 0) return -1;
int found = (strstr(b, needle) != NULL);
free(b);
return found ? 0 : 1;
}
struct tcase {
const char *label;
const char *entry; /* root build target */
int exit; /* the dep main's return, == the program exit */
const char *deps; /* the dep's .s basename in .sepwork */
const char *mangled; /* the mangled TEXT label the dep must emit */
};
static struct tcase cases[] = {
/* dotted-path import: dep main mangles on the full path. */
{ "aabb", "test/wcc/data/depmain99/main.ww", 7,
"aa.bb.s", "TEXT aa.bb.main," },
/* single-component import: dep main mangles on the leaf package. */
{ "cc", "test/wcc/data/depmain99/main_cc.ww", 9,
"cc.s", "TEXT cc.main," },
{ NULL, NULL, 0, NULL, NULL },
};
int
main(void)
{
const char *bin = absbin();
if (!bin) return 1;
char td[64], cmd[8192];
int fail = 0;
snprintf(td, sizeof td, "/tmp/wwdepmain_%d", getpid());
if (mkdir(td, 0755) != 0) {
perror(td);
return 1;
}
for (int i = 0; cases[i].label; i++) {
struct tcase *t = &cases[i];
struct { const char *drv, *tag; int rc; }
stg[] = { { "ww", "cs", -1 }, { "ww_ww", "ww", -1 } };
/* 1. SEP build+link+run exit == the dep main's return, both stages. */
for (int s = 0; s < 2; s++) {
char prog[1024];
snprintf(prog, sizeof prog, "%s/%s.%s.bin", td, t->label, stg[s].tag);
snprintf(cmd, sizeof cmd,
"%s/%s build -o %s %s "
">/dev/null 2>&1 && %s",
bin, stg[s].drv, prog, t->entry, prog);
stg[s].rc = runwait(cmd);
if (stg[s].rc != t->exit) {
fprintf(stderr, "depmain FAIL: %s sep %s exit=%d (expected %d)\n",
t->label, stg[s].tag, stg[s].rc, t->exit);
fail++;
}
}
char csdir[1024], wwdir[1024];
snprintf(csdir, sizeof csdir, "%s/%s.cs.bin.sepwork", td, t->label);
snprintf(wwdir, sizeof wwdir, "%s/%s.ww.bin.sepwork", td, t->label);
/* 2. MANGLED: the dep's .s emits `TEXT <path>.main`; exactly one bare
* `TEXT main,` across the whole sep build (the root's __root.s). */
char deps[2048];
snprintf(deps, sizeof deps, "%s/%s", csdir, t->deps);
if (file_has(deps, t->mangled) != 0) {
fprintf(stderr, "depmain FAIL: %s — %s lacks `%s` (dep main not "
"mangled)\n", t->label, deps, t->mangled);
fail++;
}
int nbare = count_bare_main(csdir);
if (nbare != 1) {
fprintf(stderr, "depmain FAIL: %s — %d bare `TEXT main,` in %s "
"(expected 1, the root entry)\n", t->label, nbare, csdir);
fail++;
}
/* 3. cs==ww (rule 10): per-package .s/.wwi byte-identical. */
fail += cmp_sepwork(csdir, wwdir, t->label);
}
for (int i = 0; cases[i].label; i++) {
const char *tags[] = { "cs", "ww" };
for (int s = 0; s < 2; s++) {
char prog[1024], work[1100];
snprintf(prog, sizeof prog, "%s/%s.%s.bin", td,
cases[i].label, tags[s]);
if (unlink(prog) != 0 && errno != ENOENT) {
perror(prog);
fail++;
}
snprintf(work, sizeof work, "%s.sepwork", prog);
snprintf(cmd, sizeof cmd, "rm -rf %s", work);
if (runwait(cmd) != 0) {
fprintf(stderr, "depmain: cleanup failed: %s\n", work);
fail++;
}
}
}
if (rmdir(td) != 0) {
perror(td);
fail++;
}
if (fail) {
fprintf(stderr, "depmain: %d check(s) failed\n", fail);
return 1;
}
printf("depmain: imported dir-package `fn main` mangles on its import path "
"under `ww build` (only the root entry stays bare) on both driver "
"stages + cs==ww per-pkg .s/.wwi\n");
return 0;
}