ww: enforce internal package visibility

This commit is contained in:
2026-08-13 15:37:51 +09:00
parent af0f56dfe5
commit 9a5dbac84f
4 changed files with 378 additions and 25 deletions

View File

@@ -373,6 +373,7 @@ source_has_test_decl(const char *path)
#define SEP_ROLE_TEST_SUPPORT 1
#define SEP_ROLE_GENERATED_MAIN 2
#define SEP_TEST_SUPPORT_MODULE "__wwtest"
#define SEP_LOAD_INTERNAL -3
/* Package-graph storage grows geometrically. Counts remain signed ints
* because they are stable action/context indices throughout the existing
@@ -994,6 +995,84 @@ sep_forbidden_command_import(const struct sepgraph *g, int importer, int dep)
&& strcmp(from->canon, to->canon) == 0);
}
static int
sep_internal_parent_count(const char *path, size_t *parents)
{
const char *p = path;
size_t components = 0;
size_t final = 0;
int found = 0;
while (*p != '\0') {
while (*p == '.') p++;
if (*p == '\0') break;
const char *end = strchr(p, '.');
size_t n = end != NULL ? (size_t)(end - p) : strlen(p);
if (n == sizeof "internal" - 1
&& memcmp(p, "internal", n) == 0) {
final = components;
found = 1;
}
components++;
if (end == NULL) break;
p = end + 1;
}
if (!found) return 0;
*parents = components - final;
return 1;
}
static int
sep_internal_import_allowed(const struct seppkg *from,
const char *target_path, const char *target_entry)
{
size_t parents;
if (!sep_internal_parent_count(target_path, &parents)) return 1;
const char *importer = from->canon;
char *owned = NULL;
if (!from->is_dir) {
const char *slash = strrchr(from->canon, '/');
if (slash == NULL) return -1;
size_t n = slash == from->canon ? 1 : (size_t)(slash - from->canon);
owned = strndup(from->canon, n);
if (owned == NULL) return sep_fail_nomem();
importer = owned;
}
size_t boundary = strlen(target_entry);
while (boundary > 1 && target_entry[boundary - 1] == '/') boundary--;
for (size_t i = 0; i < parents; i++) {
while (boundary > 0 && target_entry[boundary - 1] != '/') boundary--;
while (boundary > 1 && target_entry[boundary - 1] == '/') boundary--;
}
char *lexical = boundary == 0
? strdup(".") : strndup(target_entry, boundary);
if (lexical == NULL) {
free(owned);
return sep_fail_nomem();
}
errno = 0;
char *owner = realpath(lexical, NULL);
free(lexical);
if (owner == NULL) {
if (errno == ENOMEM)
sep_fail_nomem();
else
fprintf(stderr, "ww: cannot canonicalize package %s\n",
target_entry);
free(owned);
return -1;
}
size_t n = strlen(importer);
boundary = strlen(owner);
int allowed = (n == boundary
&& memcmp(importer, owner, boundary) == 0)
|| (boundary == 1 && owner[0] == '/' && importer[0] == '/')
|| (n > boundary && memcmp(importer, owner, boundary) == 0
&& importer[boundary] == '/');
free(owner);
free(owned);
return allowed;
}
static int
sep_command_compiler_marker(const struct sepgraph *g, int pi)
{
@@ -1718,6 +1797,8 @@ sep_scan_file(struct sepgraph *g, int pi, const char *file,
else
located = locate_import(searchpath, path_form, ipath,
sizeof ipath);
const char *visibility_entry = bound != NULL
? g->pkg[pi].entry : ipath;
if (!located) {
const char *dot = strrchr(name, '.');
const char *leaf = dot ? dot + 1 : name;
@@ -1751,13 +1832,7 @@ sep_scan_file(struct sepgraph *g, int pi, const char *file,
rc = -1;
break;
}
if (sep_binding_add(bindings, 'D', name, canon) < 0) {
free(canon);
rc = -1;
break;
}
int self = strcmp(canon, g->pkg[pi].canon) == 0;
free(canon);
if (self
&& (sep_external_production_name(&g->pkg[pi], name, 1)
|| (g->pkg[pi].variant == SEP_VARIANT_EXTERNAL
@@ -1768,6 +1843,7 @@ sep_scan_file(struct sepgraph *g, int pi, const char *file,
? g->pkg[pi].path : g->pkg[pi].canon;
errorf(u->pos, "self-import: package '%s' cannot import itself",
owner[0] ? owner : "(root)");
free(canon);
rc = -1;
break;
}
@@ -1775,7 +1851,23 @@ sep_scan_file(struct sepgraph *g, int pi, const char *file,
* canonical production action for this directory. Discovery role and
* the external product's artifact name never create another action. */
int di = sep_find_or_add(g, name, ipath, 1);
if (di < 0) { rc = -1; break; }
if (di < 0) { free(canon); rc = -1; break; }
int allowed = sep_internal_import_allowed(&g->pkg[pi],
g->pkg[di].path, visibility_entry);
if (allowed < 0) { free(canon); rc = -1; break; }
if (!allowed) {
errorf(u->pos, "use of internal package %s not allowed",
g->pkg[di].path);
free(canon);
rc = SEP_LOAD_INTERNAL;
break;
}
if (sep_binding_add(bindings, 'D', name, canon) < 0) {
free(canon);
rc = -1;
break;
}
free(canon);
if (sep_add_dep(g, pi, di) < 0) { rc = -1; break; }
}
}
@@ -1989,7 +2081,7 @@ sep_prepare_pkg_context(struct sepgraph *g, int pi, int context)
if (rc < 0) {
g->pkg[pi].context_state[context] = 2;
g->pkg[pi].failed = 1;
return -1;
return rc;
}
for (int i = 1; i < g->pkg[pi].ndeps; i++) {
int v = g->pkg[pi].deps[i];
@@ -2026,6 +2118,7 @@ sep_load_pkg(struct sepgraph *g, int pi, int context)
context = g->support_context;
struct seploadframe *frames = NULL;
int nframe = 0, framecap = 0;
int result = -1;
if (sep_reserve((void **)&frames, &framecap, 1,
sizeof *frames) < 0)
return -2;
@@ -2045,8 +2138,11 @@ sep_load_pkg(struct sepgraph *g, int pi, int context)
nframe--;
continue;
}
if (sep_prepare_pkg_context(g, f->pkg, f->context) < 0)
int prepared = sep_prepare_pkg_context(g, f->pkg, f->context);
if (prepared < 0) {
result = prepared;
goto failed;
}
f->next_dep = 0;
}
if (f->pending_dep >= 0) {
@@ -2094,7 +2190,7 @@ failed:
for (int i = 0; i < nframe; i++)
g->pkg[frames[i].pkg].failed = 1;
free(frames);
return sep_fatal_allocation ? -2 : -1;
return sep_fatal_allocation ? -2 : result;
}
static int
@@ -3016,6 +3112,7 @@ build_one_sep_impl(const char *src, int entry_is_dir,
}
int lr = sep_load_pkg(g, root, products[i].context);
if (lr == -2) return 1;
if (lr == SEP_LOAD_INTERNAL) return 1;
if (lr < 0) {
g->pkg[root].failed = 1;
continue;
@@ -3034,6 +3131,7 @@ build_one_sep_impl(const char *src, int entry_is_dir,
if (support >= 0 && support != variant) {
int lr = sep_load_pkg(g, support, products[i].context);
if (lr == -2) return 1;
if (lr == SEP_LOAD_INTERNAL) return 1;
if (lr < 0) g->pkg[variant].failed = 1;
}
}