/* * 989_barefn_collide_run — #84 cgen bare-module fn-leaf collision gate. * * A bare-module fn (module==NULL — a package-LESS `//ww:module-reset` * primary, e.g. a user test file) whose leaf collides with an IMPORTED * module's same-leaf fn was MIS-MANGLED to the imported qualified name: * mod_collect skipped bare decls, so the bare fn never entered mod_map, * and at emission mod_lookup_for_fn(leaf, hint=NULL) first-matched the * imported entry → emitted `.` instead of bare ``. That * produced a DUPLICATE symbol with the imported fn (w6l-tolerated, #31- * class) and the bare fn became silently dead — a #40 residual / #263- * class silent miscompile, gate-blind and symmetric cs==ww. Surfaced by * #80's `ww test` coexist (lib/test exports `run`). * * Strict-package (#24a) form: a dir-package `aa` exporting `run`, imported * by a `package main;` root that defines its OWN `fn run` and calls it from * `main`. The root's `run` mangles `main.run` — DISTINCT from the imported * `aa.run` and from the bare entry `main`. The original #84 trigger (a * truly BARE `run` from a PACKAGE-LESS root) is unreachable now that * strict-package forbids package-less primaries; the #84 cgen machinery * (mod_collect / mod_lookup_for_fn) stays dormant-but-load-bearing for * genuinely package-less `//ww:module-reset` deps until #24b/#26. * * Asserts (combined `ww build`, BOTH driver stages; `--sep` reproduces * identically): * 1. Build + run, BOTH stages → exit 9 (the user's `main.run`, return 9), * never aa.run (return 5). * 2. cs==ww (rule 10): the combined `.s` is byte-identical between the * two driver stages. * 3. DISTINCTNESS: the `.s` has EXACTLY ONE `TEXT main.run` (the user's) * AND EXACTLY ONE `TEXT aa.run` (the import) — distinct symbols, no dup. * * Light wwstage-driver test (CLAUDE.md rule 14): all intermediates are * `-o`-redirected to /tmp, phase-parallel-safe. Models 989_sepbuild_run.c. */ #include #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; } 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; } 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; } /* Count occurrences of a line beginning with `^TEXT ,` in the .s. */ static int count_text_label(const char *sfile, const char *sym) { char *buf = NULL; size_t n = 0; if (slurp(sfile, &buf, &n) < 0) return -1; char needle[128]; snprintf(needle, sizeof needle, "TEXT %s,", sym); size_t nl = strlen(needle); int count = 0; size_t i = 0; while (i < n) { size_t j = i; while (j < n && buf[j] != '\n') j++; if (j - i >= nl && strncmp(buf + i, needle, nl) == 0) count++; i = j + 1; } free(buf); return count; } static const char *aa_src = "package aa;\n" "export fn run() i32 = { return 5; };\n"; /* Strict-package (#24a) root: declares `package main;`, so its `fn run` * mangles `main.run` — DISTINCT from the imported `aa.run`, and from the * bare entry `main`. The bare call `run()` resolves to the local `main.run` * via the standard exact-hint path (the #84 special-case is bypassed for a * moduled root). */ static const char *root_src = "package main;\n" "import aa;\n" "fn run() i32 = { return 9; };\n" "export fn main() i32 = { return run(); };\n"; int main(void) { const char *bin = absbin(); if (!bin) { fprintf(stderr, "84 FAIL: getcwd\n"); return 1; } char td[64], cmd[8192]; int fail = 0; snprintf(td, sizeof td, "/tmp/wwbare84_%d", getpid()); snprintf(cmd, sizeof cmd, "rm -rf %s", td); runwait(cmd); mkdir(td, 0755); /* All paths derive from `td` (a small fixed 64-byte buffer) so the * snprintfs are provably non-truncating (warning-clean). */ char aadir[1024], aaww[1024], rootww[1024]; snprintf(aadir, sizeof aadir, "%s/aa", td); mkdir(aadir, 0755); snprintf(aaww, sizeof aaww, "%s/aa/aa.ww", td); snprintf(rootww, sizeof rootww, "%s/root.ww", td); if (write_file(aaww, aa_src) || write_file(rootww, root_src)) { fprintf(stderr, "84 FAIL: write fixture\n"); fail++; goto out; } struct { const char *drv, *tag; char prog[1024], sfile[1024]; } stg[] = { { "ww", "cs", {0}, {0} }, { "ww_ww", "ww", {0}, {0} } }; for (int s = 0; s < 2; s++) { snprintf(stg[s].prog, sizeof stg[s].prog, "%s/prog.%s", td, stg[s].tag); snprintf(stg[s].sfile, sizeof stg[s].sfile, "%s/prog.%s.s", td, stg[s].tag); /* #93 sep layout: `--sep -o ` splits the asm across * .sepwork/.s — the bare user `TEXT run,` lands in * __root.s and the imported `TEXT aa.run,` in aa.s. Concat all * the per-unit .s (sorted glob order is deterministic) into the * flat .s the label-count + byte-id checks consume. The * `-I %s` source path is preserved; WW_PKGCACHE is pinned under * the tmpdir so the shared out/.pkgcache stays untouched. */ snprintf(cmd, sizeof cmd, "WW_PKGCACHE=%s/pkgc timeout 240 %s/%s build --sep -o %s " "-I %s %s >/dev/null 2>&1 && cat %s.sepwork/*.s > %s", td, bin, stg[s].drv, stg[s].prog, td, rootww, stg[s].prog, stg[s].sfile); if (runwait(cmd) != 0) { fprintf(stderr, "84 FAIL: %s build\n", stg[s].drv); fail++; continue; } /* (1) the user's `main.run` (9) must win over imported aa.run (5). */ int rc = runwait(stg[s].prog); if (rc != 9) { fprintf(stderr, "84 FAIL: %s prog exit=%d expected 9 " "(user main.run, not aa.run=5)\n", stg[s].drv, rc); fail++; } /* (3) distinct symbols, no dup: exactly one `main.run` + one * `aa.run`. */ int nrun = count_text_label(stg[s].sfile, "main.run"); int naa = count_text_label(stg[s].sfile, "aa.run"); if (nrun != 1 || naa != 1) { fprintf(stderr, "84 FAIL: %s labels TEXT main.run=%d aa.run=%d " "(want 1/1 — user main.run distinct from import, no dup)\n", stg[s].drv, nrun, naa); fail++; } } /* (2) cs==ww (rule 10): the combined .s is byte-identical. */ if (files_eq(stg[0].sfile, stg[1].sfile) != 0) { fprintf(stderr, "84 FAIL: cs .s != ww .s (rule 10)\n"); fail++; } out: snprintf(cmd, sizeof cmd, "rm -rf %s", td); runwait(cmd); if (fail) { fprintf(stderr, "84: %d check(s) failed\n", fail); return 1; } printf("barefn_collide: `package main;` root `fn run` (main.run) coexists " "with imported aa.run — distinct labels, no dup, user run wins " "(exit 9), cs==ww, both driver stages (#84/#24a)\n"); return 0; }