From 2dd6b8a250a2e932643f3eb9d3dc2c4bbb1f7cf8 Mon Sep 17 00:00:00 2001 From: Hojun-Cho Date: Wed, 12 Aug 2026 22:09:55 +0900 Subject: [PATCH] build: close package variant identity gaps --- cmd/wcc/check.c | 4 +++- cmd/ww/main.c | 25 ++++++++++++++++++++++++- selfhost/cmd/wcc/check.ww | 4 +++- selfhost/cmd/ww/main.ww | 26 +++++++++++++++++++++++++- test/byteid/wwi_test.ww | 5 ++--- 5 files changed, 57 insertions(+), 7 deletions(-) diff --git a/cmd/wcc/check.c b/cmd/wcc/check.c index 3270df0d..a18f2272 100644 --- a/cmd/wcc/check.c +++ b/cmd/wcc/check.c @@ -3060,7 +3060,9 @@ lookup_visible_type(Checker *c, const char *name) for (Scope *p = c->cur; p; p = p->parent) { for (Sym *b = p->first; b; b = b->next) if (b->kind == SK_TYPE && strcmp(b->name, name) == 0 - && direct_module_visible(c, b->mod)) + && direct_module_visible(c, b->mod) + && (b->decl == NULL || !b->decl->imported + || b->decl->export)) return b; } return NULL; diff --git a/cmd/ww/main.c b/cmd/ww/main.c index dea8ed13..b1fa0b33 100644 --- a/cmd/ww/main.c +++ b/cmd/ww/main.c @@ -1182,6 +1182,13 @@ sep_add_generated_main(struct sepgraph *g, struct sepproduct *product, fprintf(stderr, "ww: generated test-main identity is too long\n"); return -1; } + for (int i = 0; i < g->n; i++) { + if (strcmp(g->pkg[i].path, p->path) != 0) continue; + fprintf(stderr, + "ww: generated test-main package identity collides with source import %s\n", + p->path); + return -1; + } snprintf(p->entry, sizeof p->entry, "%s", variantentry); snprintf(p->name, sizeof p->name, "main"); p->variant = SEP_VARIANT_TEST_MAIN; @@ -1361,6 +1368,21 @@ sep_topo_visit(struct sepgraph *g, int pi, int *order, int *no, * with an unrelated normal action in the command-global universe, but never * in one product. A single link closure must remain an unambiguous package * namespace. */ +static int +sep_internal_replaces_production(const struct sepgraph *g, int a, int b) +{ + const struct seppkg *internal = &g->pkg[a]; + const struct seppkg *production = &g->pkg[b]; + if (internal->variant != SEP_VARIANT_SAME_TEST) { + internal = &g->pkg[b]; + production = &g->pkg[a]; + } + return internal->variant == SEP_VARIANT_SAME_TEST + && production->variant == SEP_VARIANT_PRODUCTION + && production->role != SEP_ROLE_TEST_SUPPORT + && strcmp(internal->canon, production->canon) == 0; +} + static int sep_validate_module_closure(struct sepgraph *g, const int *order, int n, int include_root) @@ -1372,7 +1394,8 @@ sep_validate_module_closure(struct sepgraph *g, const int *order, int n, for (int j = i + 1; j < n; j++) { int b = order[j]; if (!include_root && g->pkg[b].root) continue; - if (strcmp(g->pkg[a].path, g->pkg[b].path) == 0) { + if (strcmp(g->pkg[a].path, g->pkg[b].path) == 0 + && !sep_internal_replaces_production(g, a, b)) { fprintf(stderr, "ww: product closure contains multiple packages named %s\n", g->pkg[a].path); diff --git a/selfhost/cmd/wcc/check.ww b/selfhost/cmd/wcc/check.ww index 163c29c4..a54b683f 100644 --- a/selfhost/cmd/wcc/check.ww +++ b/selfhost/cmd/wcc/check.ww @@ -312,7 +312,9 @@ fn lookupvisibletype(c: *checker, name: str) *syntax.sym = { for (b != nil) { if (b.skind == syntax.skind.SK_TYPE && syntax.streq(b.name, name) - && directmodvisible(c, b.mod)) { return b; }; + && directmodvisible(c, b.mod) + && (b.decl == nil || b.decl.imported == 0 + || b.decl.exported != 0)) { return b; }; b = b.snext; }; p = p.parent; diff --git a/selfhost/cmd/ww/main.ww b/selfhost/cmd/ww/main.ww index 925a3b89..2270a0a3 100644 --- a/selfhost/cmd/ww/main.ww +++ b/selfhost/cmd/ww/main.ww @@ -1397,6 +1397,15 @@ fn sepaddgeneratedmain(g: *sepgraph, product: *sepproduct, ordinal: i32, if (variant < 0 || variant >= g.n) { return -1; }; let p: *seppkg = &g.pkg[g.n]; p.path = generatedmainpath(ordinal); + let pathi: i32 = 0; + for (pathi < g.n) { + if (cstreq(g.pkg[pathi].path, p.path)) { + cerr("ww: generated test-main package identity collides with source import "); + cerr(pathstr(p.path)); cerr("\n"); + return -1; + }; + pathi += 1; + }; p.entry = g.pkg[variant].entry; p.artifact = productartifact(ordinal, SEP_VARIANT_TEST_MAIN); p.name = arenadupcstr("main\0".ptr, 4u64); @@ -1613,6 +1622,20 @@ fn septopovisit(g: *sepgraph, pi: i32, order: []i32, no: *i32, return 0; }; +fn sepinternalreplacesproduction(g: *sepgraph, a: i32, b: i32) bool = { + let internal: i32 = a; + let production: i32 = b; + if (g.pkg[internal].variant != SEP_VARIANT_SAME_TEST) { + internal = b; + production = a; + }; + return g.pkg[internal].variant == SEP_VARIANT_SAME_TEST + && g.pkg[production].variant == SEP_VARIANT_PRODUCTION + && g.pkg[production].role != SEP_ROLE_TEST_SUPPORT + && os.samefile(pathstr(g.pkg[internal].entry), + pathstr(g.pkg[production].entry)); +}; + fn sepvalidatemoduleclosure(g: *sepgraph, order: []i32, n: i32, includeroot: bool) i32 = { let i: i32 = 0; @@ -1624,7 +1647,8 @@ fn sepvalidatemoduleclosure(g: *sepgraph, order: []i32, n: i32, for (j < n) { let b: i32 = order[j]; if ((includeroot || !g.pkg[b].root) - && cstreq(g.pkg[a].path, g.pkg[b].path)) { + && cstreq(g.pkg[a].path, g.pkg[b].path) + && !sepinternalreplacesproduction(g, a, b)) { cerr("ww: product closure contains multiple packages named "); cerr(pathstr(g.pkg[a].path)); cerr("\n"); return -1; diff --git a/test/byteid/wwi_test.ww b/test/byteid/wwi_test.ww index 77923a36..09b590c1 100644 --- a/test/byteid/wwi_test.ww +++ b/test/byteid/wwi_test.ww @@ -302,7 +302,7 @@ fn m2positive(pkg: str) void = { }; testenv.writefile(strings.concat(td, "/consumer.ww"), strings.concat( "package consumer;\nimport leaktest;\n", - "fn forbidden(s: leaktest.secret) void = {};\n")); + "fn forbidden(s: secret) void = {};\n")); let ccav: []str = [testenv.driver("w6c"), "-c", "--import", "leaktest", "cs.wwi", "-o", "cs.s", "consumer.ww"]; let wcav: []str = [testenv.driver("w6c_ww"), "-c", "--import", @@ -314,8 +314,7 @@ fn m2positive(pkg: str) void = { if (cco.termination != exec.termination.EXIT || cco.code == 0 || wco.termination != exec.termination.EXIT || wco.code == 0 || !testenv.same(cco.stderr, wco.stderr) - || !testenv.has(cco.stderr, - "package 'leaktest' has no exported declaration 'secret'")) { + || !testenv.has(cco.stderr, "unknown type 'secret'")) { fail("private-closure", "private nominal became source-visible"); }; testenv.clean(td);