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

@@ -522,6 +522,7 @@ def SEP_ROLE_NORMAL: i32 = 0;
def SEP_ROLE_TEST_SUPPORT: i32 = 1;
def SEP_ROLE_GENERATED_MAIN: i32 = 2;
def SEP_TEST_SUPPORT_MODULE: str = "__wwtest";
def SEP_LOAD_INTERNAL: i32 = -3;
def SEP_INITIAL_CAP: i32 = 8;
def SEP_COUNT_MAX: i32 = 2147483647;
@@ -1345,6 +1346,115 @@ fn sepjoinpath(dir: *u8, name: *u8) *u8 = {
return sepjoinpathlit(dir, pathstr(name));
};
fn sepphysicaljoin(dir: *u8, name: str) *u8 = {
if (cstrlen(dir) == 1u64 && dir[0u64] == '/': u8) {
return sepappendlit(dir, name);
};
return sepjoinpathlit(dir, name);
};
fn seppendingpath(target: str, rest: *u8, requiredir: bool) *u8 = {
let restlen: u64 = cstrlen(rest);
let need: u64 = 0u64;
if (!sepaddbytes(&need, target.len: u64)
|| (target.len != 0 && (restlen != 0 || requiredir)
&& !sepaddbytes(&need, 1u64))
|| !sepaddbytes(&need, restlen)
|| !sepaddbytes(&need, 1u64)) { return nil; };
let out: []u8;
if (!sepmakebytes(need, &out)) { return nil; };
let off: u64 = strinto(out.ptr, 0u64, target);
if (target.len != 0 && (restlen != 0 || requiredir)) {
off = byteinto(out.ptr, off, '/': u8);
};
off = cstrinto(out.ptr, off, rest);
cstrseal(out.ptr, off);
return out.ptr;
};
fn sepphysicalparent(path: *u8) *u8 = {
let n: u64 = cstrlen(path);
for (n > 1u64 && path[n - 1u64] == '/': u8) { n -= 1u64; };
for (n > 1u64 && path[n - 1u64] != '/': u8) { n -= 1u64; };
if (n > 1u64) { n -= 1u64; };
return sepdupcstr(path, n);
};
fn sepcanonicalfile(path: *u8) *u8 = {
if (path[0u64] == 0u8) { return nil; };
let pending: *u8 = sepdupcstr(path, cstrlen(path));
if (pending == nil) { return nil; };
let resolved: *u8 = nil;
if (path[0u64] == '/': u8) {
resolved = sepdupcstr("/".ptr, 1u64);
} else {
let cwd: []u8;
if (!sepmakebytes(os.PATH_MAX: u64, &cwd)) { return nil; };
let n: i64 = os.getcwd(cwd.ptr, cwd.len: u64);
if (n <= 1i64 || n > cwd.len: i64) { return nil; };
resolved = sepdupcstr(cwd.ptr, (n - 1i64): u64);
};
if (resolved == nil) { return nil; };
let links: i32 = 0;
for (true) {
let total: u64 = cstrlen(pending);
let start: u64 = 0u64;
for (start < total && pending[start] == '/': u8) { start += 1u64; };
if (start == total) { return resolved; };
let end: u64 = start;
for (end < total && pending[end] != '/': u8) { end += 1u64; };
let followed: bool = end < total;
let rest: u64 = end;
for (rest < total && pending[rest] == '/': u8) { rest += 1u64; };
let requiredir: bool = followed && rest == total;
let name: str;
name.ptr = pending + start;
name.len = (end - start): i32;
if (name.len == 1 && name[0] == '.': u8) {
pending = pending + rest;
continue;
};
if (name.len == 2 && name[0] == '.': u8 && name[1] == '.': u8) {
resolved = sepphysicalparent(resolved);
if (resolved == nil) { return nil; };
pending = pending + rest;
continue;
};
let candidate: *u8 = sepphysicaljoin(resolved, name);
if (candidate == nil) { return nil; };
let fi: os.filestat;
match (os.lstat(&fi, pathstr(candidate))) {
case void => void;
case let e: os.oserror => return nil;
};
let typ: u32 = (fi.mode: u32) & 61440u32;
if (typ == os.mode.LINK: u32) {
if (links == 40) { return nil; };
links += 1;
let target: []u8;
if (!sepmakebytes(os.PATH_MAX: u64, &target)) { return nil; };
let n: i64 = os.readlink(pathstr(candidate), target.ptr,
os.PATH_MAX: u64);
if (n < 0i64 || n >= os.PATH_MAX: i64) { return nil; };
let ni: i32 = n: i32;
target[ni] = 0u8;
let targetname: str;
targetname.ptr = target.ptr;
targetname.len = ni;
if (ni > 0 && target[0] == '/': u8) {
resolved = sepdupcstr("/".ptr, 1u64);
if (resolved == nil) { return nil; };
};
pending = seppendingpath(targetname, pending + rest, requiredir);
if (pending == nil) { return nil; };
continue;
};
if (followed && typ != os.mode.DIR: u32) { return nil; };
resolved = candidate;
pending = pending + rest;
};
};
fn sepdirectoryvariant(variant: i32) bool = {
return variant == SEP_VARIANT_PRODUCTION
|| variant == SEP_VARIANT_SAME_TEST
@@ -1422,6 +1532,106 @@ fn sepforbiddencommandimport(g: *sepgraph, importer: i32, dep: i32) bool = {
&& cstreq(from.canon, to.canon));
};
fn sepinternalparentcount(path: *u8, parents: *u64) bool = {
let total: u64 = cstrlen(path);
let p: u64 = 0u64;
let components: u64 = 0u64;
let final: u64 = 0u64;
let found: bool = false;
for (p < total) {
for (p < total && path[p] == '.': u8) { p += 1u64; };
if (p == total) { break; };
let end: u64 = p;
for (end < total && path[end] != '.': u8) { end += 1u64; };
if (end - p == "internal".len: u64
&& bytecmp(path + p, end - p, "internal".ptr,
"internal".len: u64) == 0) {
final = components;
found = true;
};
components += 1u64;
p = end;
};
if (!found) { return false; };
*parents = components - final;
return true;
};
fn seprawimporterdir(p: *seppkg) *u8 = {
let total: u64 = cstrlen(p.canon);
let slash: u64 = total;
let i: u64 = 0u64;
for (i < total) {
if (p.canon[i] == '/': u8) { slash = i; };
i += 1u64;
};
if (slash == total) { return nil; };
if (slash == 0u64) { slash = 1u64; };
return sepdupcstr(p.canon, slash);
};
fn sepcanonicalinternalowner(path: *u8, parents: u64) *u8 = {
let boundary: u64 = cstrlen(path);
for (boundary > 1u64 && path[boundary - 1u64] == '/': u8) {
boundary -= 1u64;
};
let pi: u64 = 0u64;
for (pi < parents) {
for (boundary > 0u64 && path[boundary - 1u64] != '/': u8) {
boundary -= 1u64;
};
for (boundary > 1u64 && path[boundary - 1u64] == '/': u8) {
boundary -= 1u64;
};
pi += 1u64;
};
let lexical: *u8 = nil;
if (boundary == 0u64) {
lexical = sepdupcstr(".".ptr, 1u64);
} else {
lexical = sepdupcstr(path, boundary);
};
if (lexical == nil) { return nil; };
return canonicaldir(pathstr(lexical));
};
fn sepinternalimportallowed(from: *seppkg, targetpath: *u8,
targetentry: *u8) i32 = {
let parents: u64 = 0u64;
if (!sepinternalparentcount(targetpath, &parents)) { return 1; };
let importer: *u8 = from.canon;
if (from.isdir == 0) {
importer = seprawimporterdir(from);
if (importer == nil) {
if (!sepfatalallocation) {
cerr("ww: cannot canonicalize package ");
cerr(pathstr(from.entry)); cerr("\n");
};
return -1;
};
};
let owner: *u8 = sepcanonicalinternalowner(targetentry, parents);
if (owner == nil) {
if (!sepfatalallocation) {
cerr("ww: cannot canonicalize package ");
cerr(pathstr(targetentry)); cerr("\n");
};
return -1;
};
let boundary: u64 = cstrlen(owner);
let n: u64 = cstrlen(importer);
if (n == boundary
&& bytecmp(importer, boundary, owner, boundary) == 0) {
return 1;
};
if (boundary == 1u64 && owner[0u64] == '/': u8
&& importer[0u64] == '/': u8) { return 1; };
if (n > boundary
&& bytecmp(importer, boundary, owner, boundary) == 0
&& importer[boundary] == '/': u8) { return 1; };
return 0;
};
fn sepcommandcompilermarker(g: *sepgraph, pi: i32) bool = {
return sepcommanddeclaredname(&g.pkg[pi]) && !g.pkg[pi].linkentry;
};
@@ -1494,12 +1704,14 @@ fn sepfindoraddvariant(g: *sepgraph, path: *u8, entry: *u8,
let canon: *u8 = nil;
if (isdir != 0) {
canon = canonicaldir(pathstr(entry));
if (canon == nil) {
if (sepfatalallocation) { return -1; };
cerr("ww: cannot canonicalize package ");
cerr(pathstr(entry)); cerr("\n");
return -1;
};
} else {
canon = sepcanonicalfile(entry);
};
if (canon == nil) {
if (sepfatalallocation) { return -1; };
cerr("ww: cannot canonicalize package ");
cerr(pathstr(entry)); cerr("\n");
return -1;
};
let incoming: *u8 = nil;
if (isdir != 0 && path[0u64] != 0u8) {
@@ -2112,19 +2324,19 @@ fn sepscanfile(g: *sepgraph, pi: i32, file: *u8, searchpath: *u8,
};
let externalproduction: bool = false;
let ipath: *u8 = nil;
let visibilityentry: *u8 = nil;
if (g.pkg[pi].variant == SEP_VARIANT_EXTERNAL
&& g.pkg[pi].importbase != nil
&& cstrlen(g.pkg[pi].importbase) == idn
&& bytecmp(g.pkg[pi].importbase, idn, idp, idn) == 0) {
ipath = g.pkg[pi].canon;
visibilityentry = g.pkg[pi].entry;
};
if (ipath == nil) {
ipath = locateimport(searchpath, idp, idn);
visibilityentry = ipath;
};
if (ipath != nil) {
if (!sepbindadd(bindings, 'D': u8, u.usepath, ipath)) {
return -1;
};
let self: bool = os.samefile(pathstr(ipath),
pathstr(g.pkg[pi].entry));
if (self && (sepexternalname(&g.pkg[pi], idp, idn, true)
@@ -2150,6 +2362,18 @@ fn sepscanfile(g: *sepgraph, pi: i32, file: *u8, searchpath: *u8,
// action. Discovery role and product artifact never create another.
let di: i32 = sepfindoradd(g, nm.ptr, ipath, 1);
if (di < 0) { return -1; };
let allowed: i32 = sepinternalimportallowed(&g.pkg[pi],
g.pkg[di].path, visibilityentry);
if (allowed < 0) { return -1; };
if (allowed == 0) {
cerrpos(u.file, u.line, u.col);
cerr(": error: use of internal package ");
cerr(pathstr(g.pkg[di].path)); cerr(" not allowed\n");
return SEP_LOAD_INTERNAL;
};
if (!sepbindadd(bindings, 'D': u8, u.usepath, ipath)) {
return -1;
};
if (!sepadddep(g, pi, di)) { return -1; };
} else {
let lstart: u64 = 0u64;
@@ -2510,7 +2734,8 @@ fn seploadpkg(g: *sepgraph, pi: i32, context: i32) i32 = {
nframe -= 1;
continue;
};
if (seppreparepkgcontext(g, f.pkg, f.context) < 0) {
let prepared: i32 = seppreparepkgcontext(g, f.pkg, f.context);
if (prepared < 0) {
let fi: i32 = 0;
for (fi < nframe) {
g.pkg[frames[fi].pkg].failed = true;
@@ -2519,7 +2744,7 @@ fn seploadpkg(g: *sepgraph, pi: i32, context: i32) i32 = {
if (sepfatalallocation) {
return sepfinishloadframes(frames, -2);
};
return sepfinishloadframes(frames, -1);
return sepfinishloadframes(frames, prepared);
};
f.nextdep = 0;
};
@@ -3771,6 +3996,7 @@ fn buildonesepimpl(selfdir: *u8, src: *u8, entryisdir: i32,
let loadresult: i32 = seploadpkg(g, root,
products[producti].context);
if (loadresult == -2) { return 1; };
if (loadresult == SEP_LOAD_INTERNAL) { return 1; };
if (loadresult < 0) {
g.pkg[root].failed = true;
producti += 1;
@@ -3793,6 +4019,7 @@ fn buildonesepimpl(selfdir: *u8, src: *u8, entryisdir: i32,
let loadresult: i32 = seploadpkg(g, support,
products[producti].context);
if (loadresult == -2) { return 1; };
if (loadresult == SEP_LOAD_INTERNAL) { return 1; };
if (loadresult < 0) {
g.pkg[variant].failed = true;
};