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

163 lines
5.4 KiB
C

/*
* 989_fnptrcollide_run — F7-c7 (#14): nodefnptr must be type-keyed, not
* name-keyed, so a value ident whose LEAF collides with a fn name is not
* mis-folded into the fn's TEXT reloc.
*
* THE BUG (cat-A silent miscompile, gate-blind): nodefnptr
* (selfhost/cmd/wcc/cgen.ww) decided whether `&x` (in a static-init / DATAR
* fold context) was the address-of a top-level fn by NAME — `fnretlookup(
* c, x.str) != nil`. So `&slot` for a data global `slot: i64` that shares a
* leaf with a fn `slot` (e.g. an imported `bar.slot`) matched the fn and
* folded into a DATAR reloc to the fn's TEXT symbol instead of the data
* symbol. cstage is type-keyed (node_fnptr_sym: type_chase_named(opnd->
* type)->kind == TY_FN, cmd/w6c/cgen.c:15542) — `slot`'s stamped type is
* i64, not TY_FN, so it does NOT fold the reloc and the build errors loud.
* Pre-fix wwstage silently produced a binary (the cat-A divergence: cs
* loud-fails, ww builds). THE FIX: read the stamped operand type
* (opnd.type_ chased == TY_FN), aligning wwstage UP — a non-fn operand can
* never fold to a fn reloc, closing the leaf-name collision by construction.
*
* This shape is gate-blind (the corpus never collides a data-global leaf
* with a fn name on the &-fold path); only this row catches it. The
* conversion's neutrality on the corpus's REAL &fn static-inits (the
* #117/#119 reloc machinery) is proven separately by the c7 self-compile
* byte-id bind (B1/B2 zero-move) — see /tmp/implf7_result.txt.
*
* Assertion: the construction must FAIL TO BUILD on BOTH stages (cstage
* already rejects; wwstage now rejects too — rule-10 align-up). Pre-fix
* wwstage BUILT it (rc=0) — the row was RED on the pre-c7 binary.
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/wait.h>
static int
runwait(const char *cmd)
{
int rc = system(cmd);
if (rc == -1) return -1;
if (WIFEXITED(rc)) return WEXITSTATUS(rc);
return -1;
}
/* build_must_fail — write a bar/ module (fn `slot`) and a main.ww that
* declares a data global `slot: i64` plus `let fp: *i64 = &slot`, then
* `drv build -I bar main.ww`. Returns 0 when the build correctly FAILS
* (the leaf-name collision no longer diverts &slot into the fn reloc),
* non-zero when it wrongly built. */
static int
build_must_fail(const char *drv, int tag, int *cleanup_failed)
{
int pid = getpid();
char dir[96], bard[160], p[224], cmd[1024];
int rc = -1, setupfail = 1, cleanfail = 0, bard_owned = 0;
*cleanup_failed = 0;
snprintf(dir, sizeof dir, "/tmp/fnpc_%d_%d", pid, tag);
snprintf(bard, sizeof bard, "%s/bar", dir);
if (mkdir(dir, 0755) != 0) return -2;
if (mkdir(bard, 0755) != 0) goto cleanup;
bard_owned = 1;
snprintf(p, sizeof p, "%s/bar.ww", bard);
FILE *f = fopen(p, "wb");
if (!f) goto cleanup;
fputs("package bar;\n"
"export fn slot() i64 = { return 99; };\n", f);
if (fclose(f) != 0) goto cleanup;
snprintf(p, sizeof p, "%s/main.ww", dir);
f = fopen(p, "wb");
if (!f) goto cleanup;
fputs("package main;\n"
"import bar;\n"
"let slot: i64 = 7;\n"
"let fp: *i64 = &slot;\n"
"export fn main() int = { return (*fp): int; };\n", f);
if (fclose(f) != 0) goto cleanup;
snprintf(cmd, sizeof cmd,
"cd %s && %s build -I bar main.ww >/dev/null 2>&1", dir, drv);
rc = runwait(cmd);
setupfail = 0;
cleanup:
snprintf(p, sizeof p, "%s/main.sepwork", dir);
snprintf(cmd, sizeof cmd, "rm -rf %s", p);
if (runwait(cmd) != 0) cleanfail = 1;
snprintf(p, sizeof p, "%s/main", dir);
if (unlink(p) != 0 && access(p, F_OK) == 0) cleanfail = 1;
snprintf(p, sizeof p, "%s/main.ww", dir);
if (unlink(p) != 0 && access(p, F_OK) == 0) cleanfail = 1;
if (bard_owned) {
snprintf(p, sizeof p, "%s/bar.ww", bard);
if (unlink(p) != 0 && access(p, F_OK) == 0) cleanfail = 1;
if (rmdir(bard) != 0) cleanfail = 1;
}
if (rmdir(dir) != 0) cleanfail = 1;
*cleanup_failed = cleanfail;
if (setupfail) return -2;
return rc == 0 ? -1 : 0; /* build must NOT succeed */
}
int
main(void)
{
const char *bin = getenv("BIN");
if (!bin) bin = "out/bin";
char absbin[1024];
if (bin[0] != '/') {
char cwd[1024];
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin);
bin = absbin;
}
char cdrv[1024], wdrv[1024];
snprintf(cdrv, sizeof cdrv, "%s/ww", bin);
snprintf(wdrv, sizeof wdrv, "%s/ww_ww", bin);
int total = 0, fail = 0;
total++;
int cleanfail = 0;
int got = build_must_fail(cdrv, 1, &cleanfail);
if (got != 0) {
fprintf(stderr, "fnptrcollide_run[cstage]: %s\n",
got == -2 ? "setup failed" :
"built ok, expected the leaf-name collision to be rejected");
fail++;
}
if (cleanfail) {
fprintf(stderr, "fnptrcollide_run[cstage]: temporary cleanup failed\n");
fail++;
}
if (access(wdrv, X_OK) == 0) { /* wwstage gated */
total++;
got = build_must_fail(wdrv, 2, &cleanfail);
if (got != 0) {
fprintf(stderr, "fnptrcollide_run[wwstage]: %s\n",
got == -2 ? "setup failed" :
"built ok, expected reject (#14 — &slot mis-folded "
"to the fn TEXT reloc by the name-keyed nodefnptr)");
fail++;
}
if (cleanfail) {
fprintf(stderr, "fnptrcollide_run[wwstage]: temporary cleanup failed\n");
fail++;
}
} else {
fprintf(stderr, "fnptrcollide_run: skip wwstage (no %s)\n", wdrv);
}
if (fail) {
fprintf(stderr, "fnptrcollide_run: %d/%d checks failed\n",
fail, total);
return 1;
}
printf("fnptrcollide_run: %d/%d ok\n", total, total);
return 0;
}