Files
ww/test/wcc/752_modparam_callee.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

299 lines
9.7 KiB
C

/*
* 752_modparam_callee — sentinel for task #16 (sub-bug of #4d):
* wwstage's callee_variadic_param helper (selfhost/cmd/wcc/
* cgenutil.ww) took an N_DOT callee but routed both arms through
* bare-leaf fnparamslookup. Post-#4d that walk is same-module-first
* then head-walk fallback — for a cross-module N_DOT call from a
* caller whose c.curmod doesn't match either side, the head-walk
* returns whichever module's same-leaf fn sits at the head of
* c.fnrets. When that head-side fn has a divergent variadic-vs-
* non-variadic shape the callee_variadic_param result fires the
* gather machinery against the wrong-module shape: arg-prep
* builds a slice for `xs: T...` while the CALL still targets the
* non-variadic module-qualified label. Label correct, ABI wrong.
*
* Surfaced by attempting strings.contains' Hare-shaped
* `(needles: (str | rune)...)` graduation while bytes.contains
* stays at `(needle: (u8 | []u8))` — caller's
* `bytes.contains(b, n)` site picked strings.contains' variadic
* params for arg-prep.
*
* Cstage carries no sister bug: cmd/w6c/cgen.c reads the callee
* fn-type from the typed `n->lhs->type` (TY_FN sig) and walks
* `callee_params->variadic` directly, module-aware via the typed
* AST. Mirror of #4d / #28 / #31 / #34: cstage sidesteps every
* bare-leaf table.
*
* Fix: route the N_DOT arm through fnparamslookupmod(c, leaf,
* callee.lhs.str). The N_IDENT arm stays on bare fnparamslookup
* (already same-module-first post-#4d).
*
* row | what it pins
* ---------------------------------------|-------------------------
* cross_module_same_leaf_variadic_vs_scalar
* | primary wedge. A.foo
* | variadic, B.foo scalar
* | same leaf. Caller in
* | a third package calls
* | B.foo with a tagged
* | arg; pre-fix wwstage
* | preps the call site as
* | variadic gather and
* | mis-marshals — rc=11.
* | Post-fix both stages
* | rc=0.
* bare_leaf_no_collision | control: variadic fn
* | declared in only one
* | module, called via
* | bare leaf (N_IDENT)
* | from the same module.
* | Confirms the N_IDENT
* | arm still graduates
* | through bare
* | fnparamslookup
* | (post-#4d).
*
* The standalone same_module_same_leaf runtime control now lives in
* test/wcc/data/r75_modparam_same_module_same_leaf/case.ww. This wrapper
* retains only the two package-layout rows that require separately named
* imported source files and therefore are not expressible by case.ww.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.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;
}
struct row {
const char *label;
int want_exit;
int n_files;
const char *files[8]; /* {path, src, path, src, ...} */
const char *entry; /* file to `ww build` (relative) */
};
static const struct row rows[] = {
/* Primary wedge. alpha.foo is variadic (i32...), beta.foo is
* non-variadic with a tagged-union param. Caller in `main`
* imports both and calls beta.foo with a tagged (u8|[]u8). The
* combined-source layout puts `beta` after `alpha` so beta.foo
* sits at the head of c.fnrets among non-main fns; with
* c.curmod="main" the bare-leaf walk falls through same-module
* to head-walk and picks beta.foo here — but reverse the import
* order or any other ordering perturbation flips it to alpha.foo.
* Either fnparamslookup result is wrong half the time; the
* N_DOT arm must route through fnparamslookupmod to be stable.
* Pre-#16 the head-pick happens to be the variadic side and
* arg-prep builds an `i32...` gather slice while CALL targets
* beta.foo's scalar ABI. */
{ "cross_module_same_leaf_variadic_vs_scalar", 0, 3,
{ "alpha/alpha.ww",
"package alpha;\n"
"export fn foo(a: i32, xs: i32...) i32 = {\n"
"\treturn a + xs.len;\n"
"};\n",
"beta/beta.ww",
"package beta;\n"
"export fn foo(a: i32, n: (u8 | []u8)) i32 = {\n"
"\tmatch (n) {\n"
"\tcase let c: u8 => return a + (c: i32);\n"
"\tcase let s: []u8 => return a + s.len;\n"
"\t};\n"
"\treturn 0;\n"
"};\n",
"main.ww",
"package main;\n"
"import alpha;\n"
"import beta;\n"
"export fn main() i32 = {\n"
"\tlet n: (u8 | []u8) = 7u8;\n"
"\tlet r: i32 = beta.foo(10, n);\n"
"\tif (r != 17) { return 11; };\n"
"\treturn 0;\n"
"};\n" },
"main.ww" },
/* Control. A variadic fn exists in only one imported module;
* the caller calls it via N_DOT. No same-leaf collision, so
* fnparamslookupmod's same-module hint and the head-walk
* fallback agree. Pre+post fix both stages rc=0. */
{ "bare_leaf_no_collision", 0, 2,
{ "alpha/alpha.ww",
"package alpha;\n"
"export fn sum(a: i32, xs: i32...) i32 = {\n"
"\tlet t: i32 = a;\n"
"\tlet i: i32 = 0;\n"
"\tfor (i < xs.len) { t += xs[i]; i += 1; };\n"
"\treturn t;\n"
"};\n",
"main.ww",
"package main;\n"
"import alpha;\n"
"export fn main() i32 = {\n"
"\tlet r: i32 = alpha.sum(1, 2, 3, 4);\n"
"\tif (r != 10) { return 40; };\n"
"\treturn 0;\n"
"};\n" },
"main.ww" },
};
static int
write_one(const char *dir, const char *rel, const char *src,
int *parent_owned)
{
char path[512];
snprintf(path, sizeof path, "%s/%s", dir, rel);
char *slash = strchr(path + strlen(dir) + 1, '/');
if (slash) {
*slash = '\0';
if (mkdir(path, 0755) != 0) return -1;
*parent_owned = 1;
*slash = '/';
}
FILE *f = fopen(path, "wb");
if (!f) return -1;
int rc = fputs(src, f) == EOF ? -1 : 0;
if (fclose(f) != 0) rc = -1;
return rc;
}
static int
cleanup_row(const char *tmpdir, const struct row *r,
const int *parent_owned)
{
char path[512], cmd[640], entry[512];
int rc = 0;
for (int k = 0; k < r->n_files; k++) {
snprintf(path, sizeof path, "%s/%s", tmpdir, r->files[2 * k]);
char *slash = strchr(path + strlen(tmpdir) + 1, '/');
if (slash && !parent_owned[k]) continue;
if (unlink(path) != 0 && errno != ENOENT) rc = -1;
}
snprintf(entry, sizeof entry, "%s/%s", tmpdir, r->entry);
char *dot = strrchr(entry, '.');
if (dot && strcmp(dot, ".ww") == 0) *dot = '\0';
if (unlink(entry) != 0 && errno != ENOENT) rc = -1;
snprintf(cmd, sizeof cmd, "rm -rf %s.sepwork", entry);
if (runwait(cmd) != 0) rc = -1;
for (int k = 0; k < r->n_files; k++) {
snprintf(path, sizeof path, "%s/%s", tmpdir, r->files[2 * k]);
char *slash = strchr(path + strlen(tmpdir) + 1, '/');
if (slash && parent_owned[k]) {
*slash = '\0';
if (rmdir(path) != 0 && errno != ENOENT) rc = -1;
}
}
if (rmdir(tmpdir) != 0) rc = -1;
return rc;
}
static int
run_row(const char *driver, const struct row *r, int idx, const char *tag)
{
(void)idx;
char tmpdir[] = "/tmp/modparam_XXXXXX";
if (mkdtemp(tmpdir) == NULL) {
fprintf(stderr, "modparam_callee[%s][%s]: mkdtemp failed\n",
tag, r->label);
return 1;
}
int fail = 0;
int parent_owned[4] = { 0 };
for (int k = 0; k < r->n_files; k++) {
if (write_one(tmpdir, r->files[2 * k],
r->files[2 * k + 1], &parent_owned[k]) != 0) {
fprintf(stderr,
"modparam_callee[%s][%s]: write %s failed\n",
tag, r->label, r->files[2 * k]);
fail++;
goto cleanup;
}
}
char cmd[2048];
snprintf(cmd, sizeof cmd,
"cd %s && %s build %s >/dev/null 2>&1", tmpdir, driver, r->entry);
if (runwait(cmd) != 0) {
fprintf(stderr,
"modparam_callee[%s][%s]: build failed\n",
tag, r->label);
fail++;
goto cleanup;
}
char entry_bin[512];
snprintf(entry_bin, sizeof entry_bin, "%s/%s", tmpdir, r->entry);
char *dot = strrchr(entry_bin, '.');
if (dot && strcmp(dot, ".ww") == 0) *dot = '\0';
int got = runwait(entry_bin);
if (got != r->want_exit) {
fprintf(stderr,
"modparam_callee[%s][%s]: rc=%d want=%d\n",
tag, r->label, got, r->want_exit);
fail++;
}
cleanup:
if (cleanup_row(tmpdir, r, parent_owned) != 0) {
fprintf(stderr, "modparam_callee[%s][%s]: cleanup failed\n",
tag, r->label);
if (fail == 0) fail++;
}
return fail;
}
int
main(void)
{
const char *bin = getenv("BIN");
if (!bin) bin = "out/bin";
char absbin[512];
if (bin[0] != '/') {
char cwd[256];
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin);
bin = absbin;
}
char cdrv[640], wdrv[640];
snprintf(cdrv, sizeof cdrv, "%s/ww", bin);
snprintf(wdrv, sizeof wdrv, "%s/ww_ww", bin);
int have_ww = (access(wdrv, X_OK) == 0);
int n = (int)(sizeof rows / sizeof rows[0]);
int total = 0, fail = 0;
for (int i = 0; i < n; i++) {
total++;
fail += run_row(cdrv, &rows[i], i, "cs");
if (have_ww) {
total++;
fail += run_row(wdrv, &rows[i], i, "ws");
}
}
if (fail) {
fprintf(stderr,
"modparam_callee: %d/%d fixtures failed\n",
fail, total);
return 1;
}
printf("modparam_callee: %d/%d ok\n", total, total);
return 0;
}