/* * 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 ` '' shadows * imported module ''` 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 #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 int run_neg(const char *driver, const char *fixdir, const char *tag) { char src[64]; snprintf(src, sizeof src, "neg_%s.ww", tag); char cmd[2048]; /* cd into the fixture dir so the driver's source-dir-first * import search resolves `use shadowmod;`. */ snprintf(cmd, sizeof cmd, "cd %s && %s build %s >/dev/null 2>&1", fixdir, driver, src); int rc = runwait(cmd); 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 cmd[2048]; snprintf(cmd, sizeof cmd, "cd %s && %s build pos_rename.ww >/dev/null 2>&1", fixdir, driver); if (runwait(cmd) != 0) { 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/pos_rename", fixdir); int got = runwait(bin); unlink(bin); 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 cmd[2048]; snprintf(cmd, sizeof cmd, "cd %s && %s build pos_crossmod.ww >/dev/null 2>&1", fixdir, driver); if (runwait(cmd) != 0) { 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/pos_crossmod", fixdir); int got = runwait(bin); unlink(bin); 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; }