diff --git a/Makefile b/Makefile index 9d7da2cc..1b7a4013 100644 --- a/Makefile +++ b/Makefile @@ -412,6 +412,15 @@ MISC_WW_TESTS = test/misc/arrglob_test.ww test/misc/packedwwi_test.ww \ test/misc/stampdiag_test.ww test/misc/qualast_test.ww \ test/misc/abortmsg_test.ww MISC_WW_TARGETS = $(MISC_WW_TESTS:%=wwtest/%) + +# Ww-native driver/tool observers: single-file ww tests under +# test/tool/ on the test/testenv helper package, porting the residual +# driver/tool observer C carriers (cstage-only reject sets, wwdump_ww +# diagnostic gates, driver-CLI parity, @symbol FFI asm needles, .wwi +# wide-rune round-trips, the c6 sep soak). Compiler/driver gates like +# test/sep: they run under test-compiler. +TOOL_WW_TESTS = test/tool/rejects_test.ww +TOOL_WW_TARGETS = $(TOOL_WW_TESTS:%=wwtest/%) BOOTSTRAP_WRAPPER_SOURCES = test/wcc/950_selfcheck.c \ test/wcc/991_w6a_ww.c \ test/wcc/992_w6l_ww.c test/wcc/993_ww_ww.c \ @@ -498,7 +507,7 @@ test-compiler-smoke: $(WWFIXTURE_BIN) $(BIN)/w6c_ww $(BIN)/w6a_ww \ # ww-native sep observers that replaced the 989_sep* carriers. test-compiler: $(WWFIXTURE_BIN) $(WRAPPER_TOOLS) $(COMPILER_WRAPPER_BINS) \ $(SEP_WW_TARGETS) $(XMOD_WW_TARGETS) $(ASM_WW_TARGETS) \ - $(OBJECT_WW_TARGETS) $(MISC_WW_TARGETS) + $(OBJECT_WW_TARGETS) $(MISC_WW_TARGETS) $(TOOL_WW_TARGETS) @$(CURDIR)/$(WWFIXTURE_BIN) -j $(JOBS) @set -e; for t in $(COMPILER_WRAPPER_BINS); do \ echo "compiler artifact $$t"; BIN=$(CURDIR)/$(BIN) $(CURDIR)/$$t; \ @@ -585,6 +594,14 @@ $(OBJECT_WW_TARGETS): wwtest/%: $(BIN)/ww $(BIN)/w6c $(BIN)/w6a \ $(MISC_WW_TARGETS): wwtest/%: $(BIN)/ww $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ $(LIB)/libwwrt.a $(BIN)/ww_ww $(BIN)/w6c_ww $(BIN)/w6a_ww \ $(BIN)/w6l_ww $(BIN)/wwdump $(BIN)/wwdump_ww + +# The tool observers drive both driver stages, both bare frontend/ +# assembler/linker triples, and the wwstage checker/dumper. wwdump_ww, +# never cstage wwdump: the -c/-r arms and the asserttyped diagnostics +# under observation are wwstage-only (wwstage-warn-audit rule). +$(TOOL_WW_TARGETS): wwtest/%: $(BIN)/ww $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ + $(LIB)/libwwrt.a $(BIN)/ww_ww $(BIN)/w6c_ww $(BIN)/w6a_ww \ + $(BIN)/w6l_ww $(BIN)/wwdump_ww @echo "ww test $*" @mkdir -p $(WWBUILD)/wwtest/$(subst /,_,$*) @WW_TEST_REPO=$(CURDIR) $(BIN)/ww test \ diff --git a/test/tool/rejects_test.ww b/test/tool/rejects_test.ww new file mode 100644 index 00000000..2a72c6aa --- /dev/null +++ b/test/tool/rejects_test.ww @@ -0,0 +1,244 @@ +package rejects_test; + +// Cstage-only compile-reject observers on the `ww` driver. Ports of +// the retired native carriers test/wcc/708_param_shadow_mod.c, +// 712_redecl.c and 961_opaque_guards.c; every assertion preserved. +// +// ASYMMETRIC polarity, all three families: the reject lives only in +// cstage check.c. Wwstage's check.ww is a single-pass resolve walk +// with no per-block scoping (#11) and no #108(b) require_sized +// construction/binding guards, so it ACCEPTS these programs — the +// both-stage //ww:error fixture contract cannot carry them +// (residual-carrier-audit.json, 708/712/961 entries). Each row +// asserts the CSTAGE reject only; when wwstage gains the rule (#11 +// per-block scoping; the #108(b) guards), the rows graduate to +// //ww:error fixtures and this observer shrinks. The paramshadow neg +// rows additionally require the sibling-module fixture tree, so they +// stay here until a single-file multi-package repro is validated. +// +// paramshadow (#19/#16) — value names and module names are disjoint: +// a param/let/mlet/for-range/match-case binding named `shadowmod` in +// a file importing module shadowmod is rejected at the decl site; the +// renamed binding builds+runs 42 (no over-trigger); a param named +// like a module a SIBLING module imports does not trip (src_imports +// filters by the binding's own module — build+run exit 2). The +// carrier's neg_selfimp leg is DROPPED here: +// test/wcc/data/r948_selfimport/case.ww owns the identical claim +// (`//ww:error "self-import"`, both frontends). +// +// redecl (#32) — same-scope let/mlet/param/top-let redeclarations +// reject at every check.c site whose scope_define NULL return was +// once silently ignored (last-write-wins miscompile class). +// +// opaque guards — opaque by value (local/param/return/struct +// field/array elem/tuple/tagged member) and indexing []opaque reject. +// The both-stage size/align rejects and the *opaque / []opaque +// accepts are owned by the r961_* fixtures and test/lang. +// +// Dropped C machinery, not assertions: the per-path unlink/rmdir +// accounting (testenv.clean asserts the removal). + +import os; +import os.exec; +import strings; +import testenv; +import time; + +fn fail(label: str, why: str) void = { + let m: str = strings.concat("rejects FAIL: ", label, " -- ", why, + "\n"); + os.write(2, m.ptr, m.len: u64); + assert(false); +}; + +fn tmo() time.duration = { + return (240i64 * (time.second: i64)): time.duration; +}; + +// -1 encodes an abnormal (non-EXIT) termination, never a valid code. +fn runcode(dir: str, root: str, name: str, argv: []str) i32 = { + let co: testenv.commandout; + testenv.runcommand(dir, root, name, argv, tmo(), &co); + if (co.termination != exec.termination.EXIT) { return -1; }; + return co.code; +}; + +fn fixdir() str = { + return strings.concat(testenv.repo(), "/test/wcc/data/paramshadowmod"); +}; + +@test fn paramshadow_neg() void = { + let tags: []str = ["param", "let", "mlet", "forrange_single", + "forrange_tuple", "mcase"]; + let i: i32 = 0; + for (i < tags.len) { + let td: str = testenv.fresh(); + // cwd is the fixture dir so the driver's source-dir-first import + // search resolves `import shadowmod;`; -o keeps the binary and + // its sepwork out of the tracked tree. + let av: []str = [testenv.driver("ww"), "build", "-o", + strings.concat(td, "/out"), + strings.concat("neg_", tags[i], ".ww")]; + if (runcode(fixdir(), td, strings.concat("neg_", tags[i]), av) + == 0) { + fail(strings.concat("neg_", tags[i]), + "build unexpectedly succeeded -- shadow rule did not fire"); + }; + testenv.clean(td); + i += 1; + }; +}; + +fn paramshadow_pos(src: str, label: str, want: i32, why: str) void = { + let td: str = testenv.fresh(); + let out: str = strings.concat(td, "/out"); + let av: []str = [testenv.driver("ww"), "build", "-o", out, src]; + if (runcode(fixdir(), td, strings.concat("build_", label), av) != 0) { + fail(label, why); + }; + let rav: []str = [out]; + if (runcode(td, td, strings.concat("run_", label), rav) != want) { + fail(label, "built binary exit != expected"); + }; + testenv.clean(td); +}; + +@test fn paramshadow_pos_rename() void = { + paramshadow_pos("pos_rename.ww", "pos_rename", 42, + "build failed -- rule over-triggered after the rename"); +}; + +@test fn paramshadow_pos_crossmod() void = { + paramshadow_pos("pos_crossmod.ww", "pos_crossmod", 2, strings.concat( + "build failed -- shadow rule over-triggered on a ", + "cross-module param")); +}; + +// One `ww build` per inline source; the build must exit nonzero. +fn rejectrows(family: str, labels: []str, srcs: []str) void = { + let i: i32 = 0; + for (i < labels.len) { + let td: str = testenv.fresh(); + let src: str = strings.concat(td, "/src.ww"); + testenv.writefile(src, srcs[i]); + let av: []str = [testenv.driver("ww"), "build", "-o", + strings.concat(td, "/out"), src]; + if (runcode(td, td, labels[i], av) == 0) { + fail(strings.concat(family, ".", labels[i]), + "build unexpectedly succeeded"); + }; + testenv.clean(td); + i += 1; + }; +}; + +// The check.c sites named per row are the scope_define NULL returns +// the #32 fix turned into errors. +@test fn redecl() void = { + let labels: []str = ["neg_let_same_block", "neg_mlet_same_block", + "neg_mlet_tuple_dup", "neg_forrange_tuple_dup", "neg_param_dup", + "neg_toplet_dup"]; + let srcs: []str = [ + // site 1443 -- block-body let dup + strings.concat( + "package main;\n", + "fn main() i32 = {\n", + " let a: i32 = 1;\n", + " let a: i32 = 2;\n", + " return a;\n", + "};\n"), + // site 1562 -- mlet shadows earlier same-block let + strings.concat( + "package main;\n", + "fn pair() (i32, i32) = { return (10, 20); };\n", + "fn main() i32 = {\n", + " let a: i32 = 1;\n", + " let (a, b) = pair();\n", + " return a + b;\n", + "};\n"), + // site 1562 -- mlet pattern lists the same name twice + strings.concat( + "package main;\n", + "fn pair() (i32, i32) = { return (10, 20); };\n", + "fn main() i32 = {\n", + " let (a, a) = pair();\n", + " return a;\n", + "};\n"), + // site 1505 -- forrange tuple-pattern lists same name twice + strings.concat( + "package main;\n", + "fn main() i32 = {\n", + " let xs: [1](i32, i32) = [(10i32, 20i32)];\n", + " for (let (a, a) .. xs) {\n", + " return a;\n", + " };\n", + " return -1;\n", + "};\n"), + // site 1914 -- two params with the same name + strings.concat( + "package main;\n", + "fn f(a: i32, a: i32) i32 = { return a; };\n", + "fn main() i32 = { return f(1, 2); };\n"), + // site 1880 -- top-level let dup + strings.concat( + "package main;\n", + "let x: i32 = 1;\n", + "let x: i32 = 2;\n", + "fn main() i32 = { return x; };\n")]; + rejectrows("redecl", labels, srcs); +}; + +@test fn opaque_guards() void = { + let labels: []str = ["neg_bare_local", "neg_param", "neg_return", + "neg_struct_field", "neg_array_elem", "neg_slice_index", + "neg_tuple_member", "neg_tagged_variant"]; + let srcs: []str = [ + strings.concat( + "package main;\n", + "export fn main() i32 = {\n", + "\tlet x: opaque;\n", + "\treturn 0;\n", + "};\n"), + strings.concat( + "package main;\n", + "fn f(x: opaque) i32 = { return 0; };\n", + "export fn main() i32 = { return 0; };\n"), + // a declaration isolates the return-type guard: a body returning + // 0 would add an unrelated untyped_int-to-opaque rejection + strings.concat( + "package main;\n", + "fn f() opaque;\n", + "export fn main() i32 = { return 0; };\n"), + strings.concat( + "package main;\n", + "type S = struct { x: opaque };\n", + "export fn main() i32 = { return 0; };\n"), + strings.concat( + "package main;\n", + "export fn main() i32 = {\n", + "\tlet a: [4]opaque;\n", + "\treturn 0;\n", + "};\n"), + // the cast keeps this row on the slice-index guard rather than + // creating a bare opaque local + strings.concat( + "package main;\n", + "export fn main() i32 = {\n", + "\tlet s: []opaque;\n", + "\tlet v: i32 = s[0]: i32;\n", + "\treturn v;\n", + "};\n"), + strings.concat( + "package main;\n", + "export fn main() i32 = {\n", + "\tlet t: (opaque, i32);\n", + "\treturn 0;\n", + "};\n"), + strings.concat( + "package main;\n", + "export fn main() i32 = {\n", + "\tlet x: (opaque | i32);\n", + "\treturn 0;\n", + "};\n")]; + rejectrows("opaque", labels, srcs); +}; diff --git a/test/wcc/708_param_shadow_mod.c b/test/wcc/708_param_shadow_mod.c deleted file mode 100644 index 416bf73f..00000000 --- a/test/wcc/708_param_shadow_mod.c +++ /dev/null @@ -1,360 +0,0 @@ -/* - * 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 -#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 td[128]; - snprintf(td, sizeof td, "/tmp/psm_neg_%d_%s", getpid(), tag); - if (mkdir(td, 0755) != 0) { - fprintf(stderr, "param_shadow_mod[neg_%s]: mkdir %s: %s\n", - tag, td, strerror(errno)); - return 1; - } - char out[160], sepdir[192], rmcmd[224]; - snprintf(out, sizeof out, "%s/out", td); - snprintf(sepdir, sizeof sepdir, "%s.sepwork", out); - snprintf(rmcmd, sizeof rmcmd, "rm -rf %s", sepdir); - 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 caller-owned out.sepwork out of the tracked fixture tree. */ - snprintf(cmd, sizeof cmd, - "cd %s && %s build -o %s %s >/dev/null 2>&1", - fixdir, driver, out, src); - int rc = runwait(cmd); - int failed = 0; - if (rc == 0) { - fprintf(stderr, - "param_shadow_mod[neg_%s]: build unexpectedly succeeded " - "— shadow rule did not fire\n", tag); - failed = 1; - } - - { - int cleanup_failed = 0; - if (runwait(rmcmd) != 0) { - fprintf(stderr, "param_shadow_mod[neg_%s]: remove %s failed\n", - tag, sepdir); - cleanup_failed = 1; - } - if (unlink(out) != 0 && errno != ENOENT) { - fprintf(stderr, "param_shadow_mod[neg_%s]: unlink %s: %s\n", - tag, out, strerror(errno)); - cleanup_failed = 1; - } - if (rmdir(td) != 0) { - fprintf(stderr, "param_shadow_mod[neg_%s]: rmdir %s: %s\n", - tag, td, strerror(errno)); - cleanup_failed = 1; - } - if (!failed && cleanup_failed) failed = 1; - } - return failed; -} - -static int -run_pos(const char *driver, const char *fixdir) -{ - char td[128]; - snprintf(td, sizeof td, "/tmp/psm_pos_%d", getpid()); - if (mkdir(td, 0755) != 0) { - fprintf(stderr, "param_shadow_mod[pos_rename]: mkdir %s: %s\n", - td, strerror(errno)); - return 1; - } - char out[160], sepdir[192], rmcmd[224]; - snprintf(out, sizeof out, "%s/out", td); - snprintf(sepdir, sizeof sepdir, "%s.sepwork", out); - snprintf(rmcmd, sizeof rmcmd, "rm -rf %s", sepdir); - char cmd[2048]; - /* Keep cd for sibling-import resolution; -o moves the binary and - * caller-owned out.sepwork out of the tracked fixture tree. */ - snprintf(cmd, sizeof cmd, - "cd %s && %s build -o %s pos_rename.ww >/dev/null 2>&1", - fixdir, driver, out); - int failed = 0, got; - if (runwait(cmd) != 0) { - fprintf(stderr, - "param_shadow_mod[pos_rename]: build failed — rule " - "over-triggered on the rename\n"); - failed = 1; - goto cleanup; - } - got = runwait(out); - if (got != 42) { - fprintf(stderr, - "param_shadow_mod[pos_rename]: exit=%d want=42\n", got); - failed = 1; - } - -cleanup: - { - int cleanup_failed = 0; - if (runwait(rmcmd) != 0) { - fprintf(stderr, "param_shadow_mod[pos_rename]: remove %s failed\n", - sepdir); - cleanup_failed = 1; - } - if (unlink(out) != 0 && errno != ENOENT) { - fprintf(stderr, "param_shadow_mod[pos_rename]: unlink %s: %s\n", - out, strerror(errno)); - cleanup_failed = 1; - } - if (rmdir(td) != 0) { - fprintf(stderr, "param_shadow_mod[pos_rename]: rmdir %s: %s\n", - td, strerror(errno)); - cleanup_failed = 1; - } - if (!failed && cleanup_failed) failed = 1; - } - return failed; -} - -/* - * 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 td[128]; - snprintf(td, sizeof td, "/tmp/psm_selfimp_%d_%s", getpid(), tag); - if (mkdir(td, 0755) != 0) { - fprintf(stderr, "param_shadow_mod[neg_selfimp-%s]: mkdir %s: %s\n", - tag, td, strerror(errno)); - return 1; - } - char errp[160], cmd[2048]; - snprintf(errp, sizeof errp, "%s/stderr", td); - snprintf(cmd, sizeof cmd, - "cd %s && %s selfimp/selfimptest.ww -o /dev/null 2>%s", - fixdir, comp, errp); - int rc = runwait(cmd); - int failed = 0, found = 0; - FILE *f; - if (rc == 0) { - fprintf(stderr, "param_shadow_mod[neg_selfimp-%s]: accepted " - "self-import (expected reject)\n", tag); - failed = 1; - goto cleanup; - } - f = fopen(errp, "rb"); - if (f) { - char buf[4096]; - size_t n = fread(buf, 1, sizeof buf - 1, f); - if (!ferror(f)) { - buf[n] = '\0'; - found = strstr(buf, "self-import") != NULL; - } - if (fclose(f) != 0) found = 0; - } - if (!found) { - fprintf(stderr, "param_shadow_mod[neg_selfimp-%s]: rejected " - "but no 'self-import' on stderr\n", tag); - failed = 1; - } - -cleanup: - { - int cleanup_failed = 0; - if (unlink(errp) != 0 && errno != ENOENT) { - fprintf(stderr, - "param_shadow_mod[neg_selfimp-%s]: unlink %s: %s\n", - tag, errp, strerror(errno)); - cleanup_failed = 1; - } - if (rmdir(td) != 0) { - fprintf(stderr, - "param_shadow_mod[neg_selfimp-%s]: rmdir %s: %s\n", - tag, td, strerror(errno)); - cleanup_failed = 1; - } - if (!failed && cleanup_failed) failed = 1; - } - return failed; -} - -/* - * 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()); - if (mkdir(td, 0755) != 0) { - fprintf(stderr, "param_shadow_mod[pos_crossmod]: mkdir %s: %s\n", - td, strerror(errno)); - return 1; - } - char out[160], sepdir[192], rmcmd[224]; - snprintf(out, sizeof out, "%s/out", td); - snprintf(sepdir, sizeof sepdir, "%s.sepwork", out); - snprintf(rmcmd, sizeof rmcmd, "rm -rf %s", sepdir); - char cmd[2048]; - /* Keep cd for sibling-import resolution; -o moves the binary and - * caller-owned out.sepwork out of the tracked fixture tree. */ - snprintf(cmd, sizeof cmd, - "cd %s && %s build -o %s pos_crossmod.ww >/dev/null 2>&1", - fixdir, driver, out); - int failed = 0, got; - if (runwait(cmd) != 0) { - fprintf(stderr, - "param_shadow_mod[pos_crossmod]: build failed — shadow " - "rule over-triggered on a cross-module param\n"); - failed = 1; - goto cleanup; - } - got = runwait(out); - if (got != 2) { - fprintf(stderr, - "param_shadow_mod[pos_crossmod]: exit=%d want=2\n", got); - failed = 1; - } - -cleanup: - { - int cleanup_failed = 0; - if (runwait(rmcmd) != 0) { - fprintf(stderr, "param_shadow_mod[pos_crossmod]: remove %s failed\n", - sepdir); - cleanup_failed = 1; - } - if (unlink(out) != 0 && errno != ENOENT) { - fprintf(stderr, "param_shadow_mod[pos_crossmod]: unlink %s: %s\n", - out, strerror(errno)); - cleanup_failed = 1; - } - if (rmdir(td) != 0) { - fprintf(stderr, "param_shadow_mod[pos_crossmod]: rmdir %s: %s\n", - td, strerror(errno)); - cleanup_failed = 1; - } - if (!failed && cleanup_failed) failed = 1; - } - return failed; -} - -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; -} diff --git a/test/wcc/712_redecl.c b/test/wcc/712_redecl.c deleted file mode 100644 index 685c1c6e..00000000 --- a/test/wcc/712_redecl.c +++ /dev/null @@ -1,219 +0,0 @@ -/* - * 712_redecl — check: refuse same-scope let/mlet/param/top-let - * redeclarations (task #32). - * - * Pre-fix: cmd/wcc/check.c silently dropped the duplicate insert when - * scope_define returned NULL. `let a: i32 = 1; let a: i32 = 2;` in - * one block compiled cleanly and the second store last-write-wins; - * `fn f(a: i32, a: i32)` accepted both params, body resolved to the - * second. Surfaced post-#27 (which dropped the localoff name-dedup - * shim that had been masking the issue at codegen) — see commit - * 1292f98's follow-up note and worker-27's filed task. - * - * Post-fix: cstage check.c errors at every site where scope_define / - * scope_define_in_module's NULL return was previously ignored: - * - * site | message - * ---------------------------+-------------------------------------- - * block-body N_LET | let '%s' redeclared in same scope - * N_MLET (`let (a,b)=…`) | let '%s' redeclared in same scope - * N_FORRANGE tuple-binding | binding '%s' redeclared in same scope - * N_FNDECL param | param '%s' redeclared - * top-level N_LET | duplicate let %s - * - * N_MCASE (case-binding) and N_FORRANGE single-binding already lived - * in fresh per-arm / per-loop scopes with at most one bind, so they - * weren't part of the bug class. Top-level N_TYPEDECL / N_DEF / - * N_FNDECL already errored on NULL ("duplicate %s"). - * - * Cstage-only. Wwstage's check.ww is a single-pass resolve walk with - * no per-block scoping (line 1015 comment), exercised only by - * wwdump_ww as a diagnostic; adding the guard there today would - * false-positive on legal cross-block shadow. Tracked under #11 - * (wwstage checkfile pass with per-block scoping). Matches the - * test/wcc/708 + test/wcc/696 cstage-only neg-case precedent. - * - * row | gate | what it pins - * --------------------------+----------------+-------------------- - * neg_let_same_block | build fails | site 1443 - * neg_mlet_same_block | build fails | site 1562 - * neg_mlet_tuple_dup | build fails | site 1562 (dup-in) - * neg_forrange_tuple_dup | build fails | site 1505 - * neg_param_dup | build fails | site 1914 - * neg_toplet_dup | build fails | site 1880 - * The four legal-shadow positive rows are owned by the directive fixtures - * under test/wcc/data/r71_redecl_*. This residual wrapper intentionally owns - * only the six C-stage-only rejects below: wwstage lacks per-block scoping, - * so these cannot be represented by the symmetric //ww:error contract. - */ -#include -#include -#include -#include -#include -#include -#include -#include "wwtestpkg.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; const char *src; }; - -static const struct row rows[] = { - /* neg: site 1443 — block-body let dup. */ - { "neg_let_same_block", - "fn main() i32 = {\n" - " let a: i32 = 1;\n" - " let a: i32 = 2;\n" - " return a;\n" - "};\n" }, - - /* neg: site 1562 — mlet shadows earlier same-block let. */ - { "neg_mlet_same_block", - "fn pair() (i32, i32) = { return (10, 20); };\n" - "fn main() i32 = {\n" - " let a: i32 = 1;\n" - " let (a, b) = pair();\n" - " return a + b;\n" - "};\n" }, - - /* neg: site 1562 — mlet pattern lists the same name twice. */ - { "neg_mlet_tuple_dup", - "fn pair() (i32, i32) = { return (10, 20); };\n" - "fn main() i32 = {\n" - " let (a, a) = pair();\n" - " return a;\n" - "};\n" }, - - /* neg: site 1505 — forrange tuple-pattern lists same name twice. */ - { "neg_forrange_tuple_dup", - "fn main() i32 = {\n" - " let xs: [1](i32, i32) = [(10i32, 20i32)];\n" - " for (let (a, a) .. xs) {\n" - " return a;\n" - " };\n" - " return -1;\n" - "};\n" }, - - /* neg: site 1914 — two params with the same name. */ - { "neg_param_dup", - "fn f(a: i32, a: i32) i32 = { return a; };\n" - "fn main() i32 = { return f(1, 2); };\n" }, - - /* neg: site 1880 — top-level let dup. */ - { "neg_toplet_dup", - "let x: i32 = 1;\n" - "let x: i32 = 2;\n" - "fn main() i32 = { return x; };\n" }, -}; - -static int -run_row(const char *driver, const struct row *r, int i) -{ - char src[128], tmpdir[128], outbin[128], sepdir[160], rmcmd[192]; - char cmd[2048]; - snprintf(tmpdir, sizeof tmpdir, "/tmp/wcredecl_%d_d_%d", getpid(), i); - if (mkdir(tmpdir, 0755) != 0) { - fprintf(stderr, "redecl[%s]: mkdir %s: %s\n", - r->label, tmpdir, strerror(errno)); - return -1; - } - snprintf(src, sizeof src, "%s/wcredecl_%d_%d.ww", tmpdir, getpid(), i); - snprintf(outbin, sizeof outbin, "%s/wcredecl_%d_%d", tmpdir, getpid(), i); - snprintf(sepdir, sizeof sepdir, "%s.sepwork", outbin); - snprintf(rmcmd, sizeof rmcmd, "rm -rf %s", sepdir); - int failed = 0, rc; - - FILE *f = fopen(src, "wb"); - if (!f) { - fprintf(stderr, "redecl[%s]: fopen %s: %s\n", - r->label, src, strerror(errno)); - failed = 1; - goto cleanup; - } - wwtest_fputs(r->src, f); - if (ferror(f)) { - fprintf(stderr, "redecl[%s]: write %s failed\n", r->label, src); - failed = 1; - } - if (fclose(f) != 0) { - fprintf(stderr, "redecl[%s]: close %s: %s\n", - r->label, src, strerror(errno)); - failed = 1; - } - if (failed) goto cleanup; - - snprintf(cmd, sizeof cmd, "%s build -o %s %s >/dev/null 2>&1", - driver, outbin, src); - rc = runwait(cmd); - - if (rc == 0) { - fprintf(stderr, "redecl[%s]: build unexpectedly succeeded\n", - r->label); - failed = 1; - } - -cleanup: - { - int cleanup_failed = 0; - if (runwait(rmcmd) != 0) { - fprintf(stderr, "redecl[%s]: remove %s failed\n", - r->label, sepdir); - cleanup_failed = 1; - } - if (unlink(src) != 0 && errno != ENOENT) { - fprintf(stderr, "redecl[%s]: unlink %s: %s\n", - r->label, src, strerror(errno)); - cleanup_failed = 1; - } - if (unlink(outbin) != 0 && errno != ENOENT) { - fprintf(stderr, "redecl[%s]: unlink %s: %s\n", - r->label, outbin, strerror(errno)); - cleanup_failed = 1; - } - if (rmdir(tmpdir) != 0) { - fprintf(stderr, "redecl[%s]: rmdir %s: %s\n", - r->label, tmpdir, strerror(errno)); - cleanup_failed = 1; - } - if (!failed && cleanup_failed) failed = 1; - } - return failed ? -1 : 0; -} - -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]; - snprintf(cdrv, sizeof cdrv, "%s/ww", bin); - - int n = (int)(sizeof rows / sizeof rows[0]); - int fail = 0; - for (int i = 0; i < n; i++) { - if (run_row(cdrv, &rows[i], i) != 0) fail++; - } - - if (fail) { - fprintf(stderr, "redecl: %d/%d row(s) failed\n", fail, n); - return 1; - } - printf("redecl: %d/%d ok\n", n, n); - return 0; -} diff --git a/test/wcc/961_opaque_guards.c b/test/wcc/961_opaque_guards.c deleted file mode 100644 index c7b278e3..00000000 --- a/test/wcc/961_opaque_guards.c +++ /dev/null @@ -1,167 +0,0 @@ -/* - * 961_opaque_guards — C-stage-only guards for the unsized opaque type. - * - * opaque is legal behind indirection (*opaque is 8 bytes and []opaque is a - * 24-byte header), but it cannot appear by value or be indexed through a - * slice. The WW frontend currently accepts the eight programs below because - * it does not validate those construction and binding sites. That polarity - * cannot be represented by the both-stage fixture protocol, so this carrier - * requires each program to fail with the C-stage driver. - * - * Both-stage size/align rejections, including nested aggregates, are owned by - * the six r961_neg_* fixtures. The sized pointer and slice controls are - * owned by r961_pos_ptr_opaque and r961_pos_slice_opaque. - */ -#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; -} - -struct row { const char *label; const char *src; }; - -static const struct row rows[] = { - { "neg_bare_local", - "package main;\n" - "export fn main() i32 = {\n" - "\tlet x: opaque;\n" - "\treturn 0;\n" - "};\n" }, - - { "neg_param", - "package main;\n" - "fn f(x: opaque) i32 = { return 0; };\n" - "export fn main() i32 = { return 0; };\n" }, - - /* - * A declaration isolates the return-type guard. A function body returning - * 0 would add an unrelated untyped_int-to-opaque rejection. - */ - { "neg_return", - "package main;\n" - "fn f() opaque;\n" - "export fn main() i32 = { return 0; };\n" }, - - { "neg_struct_field", - "package main;\n" - "type S = struct { x: opaque };\n" - "export fn main() i32 = { return 0; };\n" }, - - { "neg_array_elem", - "package main;\n" - "export fn main() i32 = {\n" - "\tlet a: [4]opaque;\n" - "\treturn 0;\n" - "};\n" }, - - /* - * The cast keeps this row on the slice-index guard rather than creating - * a bare opaque local. - */ - { "neg_slice_index", - "package main;\n" - "export fn main() i32 = {\n" - "\tlet s: []opaque;\n" - "\tlet v: i32 = s[0]: i32;\n" - "\treturn v;\n" - "};\n" }, - - { "neg_tuple_member", - "package main;\n" - "export fn main() i32 = {\n" - "\tlet t: (opaque, i32);\n" - "\treturn 0;\n" - "};\n" }, - - { "neg_tagged_variant", - "package main;\n" - "export fn main() i32 = {\n" - "\tlet x: (opaque | i32);\n" - "\treturn 0;\n" - "};\n" }, -}; - -static int -run_row(const char *driver, const struct row *r, int i) -{ - char tmpdir[64] = "/tmp/wcopg_XXXXXX"; - if (mkdtemp(tmpdir) == NULL) { - perror("opaque-guard: mkdtemp"); - return -1; - } - char src[128], outbin[128], sepwork[160], rmcmd[192], cmd[2048]; - snprintf(src, sizeof src, "%s/wcopg_%d_%d.ww", tmpdir, getpid(), i); - snprintf(outbin, sizeof outbin, "%s/wcopg_%d_%d", tmpdir, getpid(), i); - snprintf(sepwork, sizeof sepwork, "%s.sepwork", outbin); - snprintf(rmcmd, sizeof rmcmd, "rm -rf %s", sepwork); - int result = -1; - - FILE *f = fopen(src, "wb"); - if (!f) goto cleanup; - fputs(r->src, f); - fclose(f); - - snprintf(cmd, sizeof cmd, "%s build -o %s %s >/dev/null 2>&1", - driver, outbin, src); - int rc = runwait(cmd); - int bad = (rc == 0); - if (bad) { - fprintf(stderr, "opaque-guard[%s]: build unexpectedly succeeded\n", - r->label); - } - result = bad ? -1 : 0; - -cleanup: { - int cleanbad = 0; - if (unlink(src) != 0 && errno != ENOENT) cleanbad = 1; - if (unlink(outbin) != 0 && errno != ENOENT) cleanbad = 1; - if (runwait(rmcmd) != 0) cleanbad = 1; - if (rmdir(tmpdir) != 0) cleanbad = 1; - if (cleanbad) { - fprintf(stderr, "opaque-guard[%s]: temporary cleanup failed\n", - r->label); - result = -1; - } - } - return result; -} - -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]; - snprintf(cdrv, sizeof cdrv, "%s/ww", bin); - - int n = (int)(sizeof rows / sizeof rows[0]); - int fail = 0; - for (int i = 0; i < n; i++) { - if (run_row(cdrv, &rows[i], i) != 0) fail++; - } - - if (fail) { - fprintf(stderr, "opaque-guards: %d/%d row(s) failed\n", fail, n); - return 1; - } - printf("opaque-guards: %d/%d ok\n", n, n); - return 0; -}