The wcc test drivers ran `ww build <bare-/tmp src>` with no -o, so the compiler's <stem>.sepwork scratch landed beside the source and was never cleaned: unbounded /tmp growth (2195 stale dirs observed) that fills tmpfs and fabricates phantom test failures + silent harness aborts, and for in-repo fixture builds leaked .sepwork into the tracked tree. Each leaking build now writes its source + output inside a per-invocation tmpdir, passes -o <tmpdir>/<stem> so the .sepwork lands inside it, and rm -rf's the tmpdir on every exit path -- including fopen-fail and the expected-fail reject builds (scratch is mkdir'd before the build can fail). `ww run` and explicit-`-o`/byte-id helpers are left as-is; the 990/993 byte-id comparison logic is byte-for-byte unchanged. Two items filed separately (this commit holds the no-Makefile / no-main.c rail): - #13: a stale <src>.s byte-id readback (749) silently no-ops since separate-compile emits .s to <ostem>.sepwork/__root.s; documented inline. - #14: build-system Makefile recipes build selfhost/cmd/*/main.ww with no -o and leak main.sepwork in-tree (bounded, gitignored; own commit). One concern -- sepwork leak hygiene -- across 228 drivers; uniform transform applied per-file and two-round reviewed. make test: all 402 passed, zero net-new /tmp scratch, zero test-driven in-repo .sepwork.
257 lines
8.9 KiB
C
257 lines
8.9 KiB
C
/*
|
|
* 706_param_shadow_mod — "value names and module names are disjoint."
|
|
*
|
|
* Pre-fix (task #19): a fn param / local-let named `shadowmod` while
|
|
* the same source carried `use shadowmod;` would compile cleanly and
|
|
* silently miscompile any `shadowmod.X` body lookup — cstage's cexpr
|
|
* N_DOT path resolved the inner ident through the shadow's value
|
|
* bits, then emitted CALL through the str's .ptr field. Symptom in
|
|
* the field was a SIGSEGV inside lib/log's lprintfln (worked around
|
|
* by renaming `fmt: str` → `format: str` at commit 6b6d7dd).
|
|
*
|
|
* Post-fix: cstage check.c (and wwstage check.ww, run by wwdump_ww)
|
|
* refuse the bind at the decl site with a `<kind> '<name>' shadows
|
|
* imported module '<name>'` diagnostic. Same-leaf top-level decls
|
|
* (e.g. `use fnmatch; fn fnmatch(...)`) are exempt — the rule fires
|
|
* only for nested-scope binds whose declaring source file imports
|
|
* the module.
|
|
*
|
|
* row | shape | gate
|
|
* ---------------------+--------------------------------------+--------
|
|
* neg_param | `fn p(shadowmod: str)` | fail
|
|
* neg_let | `let shadowmod: i32 = 0;` | fail
|
|
* neg_mlet | `let (shadowmod, x) = pair();` | fail
|
|
* neg_forrange_single | `for (let shadowmod .. s)` | fail
|
|
* neg_forrange_tuple | `for (let (shadowmod, x) .. s)` | fail
|
|
* neg_mcase | `match (r) { case let shadowmod ... }` | fail
|
|
* pos_rename | rename param away from `shadowmod` | exit=42
|
|
* neg_selfimp | `package selfimp; import selfimp;` | fail (both stages)
|
|
* pos_crossmod | param named like a CROSS-imported mod | exit=2
|
|
*
|
|
* Fixtures live in test/wcc/data/paramshadowmod/. The shadow-rule rows
|
|
* (the neg_ rows + pos_rename) are cstage-only: wwstage's check.ww runs
|
|
* inside wwdump_ww (diagnostic), and the actual selfhost compile
|
|
* pipeline (994_w6c_ww) doesn't trip because wwstage's cgen takes the
|
|
* module-qualified emit path for any N_DOT-callee bare ident — see
|
|
* STATUS.md's `Wwstage no-checkfile-pass smell` note.
|
|
*
|
|
* neg_selfimp / pos_crossmod (#16): pos_selfimp's old positive scenario
|
|
* (a self-import skipped from the shadow scan so a same-named param
|
|
* doesn't trip) is ABOLISHED — #16 check-(c) hard-rejects self-imports.
|
|
* neg_selfimp converts it: `package selfimp; import selfimp;` must be
|
|
* REJECTED by BOTH w6c and w6c_ww (compile-only, -o /dev/null — rule-14-
|
|
* safe, no wwstage driver) with the "self-import" diagnostic. pos_crossmod
|
|
* preserves the surviving shadow-TOLERANCE in legit form: src_imports
|
|
* filters by the binding's own module, so a param named like a module a
|
|
* SIBLING module imports (not this one) does NOT trip. The now-unreachable
|
|
* self-import-skip arm in check_module_shadow is task #13 (not removed here).
|
|
*/
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.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;
|
|
}
|
|
|
|
static int
|
|
run_neg(const char *driver, const char *fixdir, const char *tag)
|
|
{
|
|
char src[64];
|
|
snprintf(src, sizeof src, "neg_%s.ww", tag);
|
|
char td[128];
|
|
snprintf(td, sizeof td, "/tmp/psm_neg_%d_%s", getpid(), tag);
|
|
mkdir(td, 0755);
|
|
char cmd[2048];
|
|
/* cd into the fixture dir so the driver's source-dir-first
|
|
* import search resolves `use shadowmod;`; -o moves the binary
|
|
* and its <stem>.sepwork OUT of the tracked fixture tree. */
|
|
snprintf(cmd, sizeof cmd,
|
|
"cd %s && %s build -o %s/out %s >/dev/null 2>&1",
|
|
fixdir, driver, td, src);
|
|
int rc = runwait(cmd);
|
|
char rmcmd[256];
|
|
snprintf(rmcmd, sizeof rmcmd, "rm -rf %s", td);
|
|
runwait(rmcmd);
|
|
if (rc == 0) {
|
|
fprintf(stderr,
|
|
"param_shadow_mod[neg_%s]: build unexpectedly succeeded "
|
|
"— shadow rule did not fire\n", tag);
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
run_pos(const char *driver, const char *fixdir)
|
|
{
|
|
char td[128];
|
|
snprintf(td, sizeof td, "/tmp/psm_pos_%d", getpid());
|
|
mkdir(td, 0755);
|
|
char rmcmd[256];
|
|
snprintf(rmcmd, sizeof rmcmd, "rm -rf %s", td);
|
|
char cmd[2048];
|
|
/* keep cd for sibling-import resolution; -o moves the binary and
|
|
* its pos_rename.sepwork OUT of the tracked fixture tree. */
|
|
snprintf(cmd, sizeof cmd,
|
|
"cd %s && %s build -o %s/out pos_rename.ww >/dev/null 2>&1",
|
|
fixdir, driver, td);
|
|
if (runwait(cmd) != 0) {
|
|
runwait(rmcmd);
|
|
fprintf(stderr,
|
|
"param_shadow_mod[pos_rename]: build failed — rule "
|
|
"over-triggered on the rename\n");
|
|
return 1;
|
|
}
|
|
char bin[2048];
|
|
snprintf(bin, sizeof bin, "%s/out", td);
|
|
int got = runwait(bin);
|
|
runwait(rmcmd);
|
|
if (got != 42) {
|
|
fprintf(stderr,
|
|
"param_shadow_mod[pos_rename]: exit=%d want=42\n", got);
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* neg_selfimp — the bare self-import `package selfimp; import selfimp;`
|
|
* (selfimp/selfimptest.ww). #16 check-(c) hard-rejects it; both w6c and
|
|
* w6c_ww must fail with the "self-import" diagnostic. Compile-only
|
|
* (-o /dev/null): w6c/w6c_ww do not expand imports, but check-(c) fires
|
|
* at the N_USE install seam before resolution, so a lone file rejects
|
|
* directly — NOT a wwstage driver invocation, so rule-14-safe in 7xx.
|
|
*/
|
|
static int
|
|
run_neg_selfimp(const char *comp, const char *fixdir, const char *tag)
|
|
{
|
|
char errp[96], cmd[2048];
|
|
snprintf(errp, sizeof errp, "/tmp/psm_selfimp_%s_%d.err", tag, getpid());
|
|
snprintf(cmd, sizeof cmd,
|
|
"cd %s && %s selfimp/selfimptest.ww -o /dev/null 2>%s",
|
|
fixdir, comp, errp);
|
|
int rc = runwait(cmd);
|
|
if (rc == 0) {
|
|
fprintf(stderr, "param_shadow_mod[neg_selfimp-%s]: accepted "
|
|
"self-import (expected reject)\n", tag);
|
|
unlink(errp);
|
|
return 1;
|
|
}
|
|
FILE *f = fopen(errp, "rb");
|
|
int found = 0;
|
|
if (f) {
|
|
char buf[4096];
|
|
size_t n = fread(buf, 1, sizeof buf - 1, f);
|
|
fclose(f);
|
|
buf[n] = '\0';
|
|
found = strstr(buf, "self-import") != NULL;
|
|
}
|
|
unlink(errp);
|
|
if (!found) {
|
|
fprintf(stderr, "param_shadow_mod[neg_selfimp-%s]: rejected "
|
|
"but no 'self-import' on stderr\n", tag);
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* pos_crossmod — surviving shadow-TOLERANCE in legit form. The bundle
|
|
* imports `shadowmod` from module paramshadowmod and pulls module
|
|
* `crossmod`, whose probe() has a param named `shadowmod`. src_imports
|
|
* filters by the binding's own module, so crossmod's param does NOT trip
|
|
* (crossmod carries no `import shadowmod`) even though a sibling module
|
|
* imports that leaf. Build + run; exit = 2 (len "hi").
|
|
*/
|
|
static int
|
|
run_pos_crossmod(const char *driver, const char *fixdir)
|
|
{
|
|
char td[128];
|
|
snprintf(td, sizeof td, "/tmp/psm_crossmod_%d", getpid());
|
|
mkdir(td, 0755);
|
|
char rmcmd[256];
|
|
snprintf(rmcmd, sizeof rmcmd, "rm -rf %s", td);
|
|
char cmd[2048];
|
|
/* keep cd for sibling-import resolution; -o moves the binary and
|
|
* its pos_crossmod.sepwork OUT of the tracked fixture tree. */
|
|
snprintf(cmd, sizeof cmd,
|
|
"cd %s && %s build -o %s/out pos_crossmod.ww >/dev/null 2>&1",
|
|
fixdir, driver, td);
|
|
if (runwait(cmd) != 0) {
|
|
runwait(rmcmd);
|
|
fprintf(stderr,
|
|
"param_shadow_mod[pos_crossmod]: build failed — shadow "
|
|
"rule over-triggered on a cross-module param\n");
|
|
return 1;
|
|
}
|
|
char bin[2048];
|
|
snprintf(bin, sizeof bin, "%s/out", td);
|
|
int got = runwait(bin);
|
|
runwait(rmcmd);
|
|
if (got != 2) {
|
|
fprintf(stderr,
|
|
"param_shadow_mod[pos_crossmod]: exit=%d want=2\n", got);
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
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], w6c[1024], w6c_ww[1024];
|
|
snprintf(cdrv, sizeof cdrv, "%s/ww", bin);
|
|
snprintf(w6c, sizeof w6c, "%s/w6c", bin);
|
|
snprintf(w6c_ww, sizeof w6c_ww, "%s/w6c_ww", bin);
|
|
|
|
char fixdir[1024];
|
|
if (getcwd(fixdir, sizeof fixdir) == NULL) return 1;
|
|
size_t cwd_n = strlen(fixdir);
|
|
const char *rel = "/test/wcc/data/paramshadowmod";
|
|
if (cwd_n + strlen(rel) + 1 >= sizeof fixdir) return 1;
|
|
memcpy(fixdir + cwd_n, rel, strlen(rel) + 1);
|
|
|
|
int fail = 0, total = 0;
|
|
total++; fail += run_neg(cdrv, fixdir, "param");
|
|
total++; fail += run_neg(cdrv, fixdir, "let");
|
|
total++; fail += run_neg(cdrv, fixdir, "mlet");
|
|
total++; fail += run_neg(cdrv, fixdir, "forrange_single");
|
|
total++; fail += run_neg(cdrv, fixdir, "forrange_tuple");
|
|
total++; fail += run_neg(cdrv, fixdir, "mcase");
|
|
total++; fail += run_pos(cdrv, fixdir);
|
|
/* neg_selfimp: both compilers reject the self-import (compile-only,
|
|
* rule-14-safe). w6c always; w6c_ww when built. */
|
|
total++; fail += run_neg_selfimp(w6c, fixdir, "cstage");
|
|
if (access(w6c_ww, X_OK) == 0) {
|
|
total++; fail += run_neg_selfimp(w6c_ww, fixdir, "wwstage");
|
|
}
|
|
total++; fail += run_pos_crossmod(cdrv, fixdir);
|
|
|
|
if (fail) {
|
|
fprintf(stderr,
|
|
"param_shadow_mod: %d row(s) failed\n", fail);
|
|
return 1;
|
|
}
|
|
printf("param_shadow_mod: %d/%d ok\n", total, total);
|
|
return 0;
|
|
}
|