build: separate package identity from storage paths

This commit is contained in:
2026-08-13 05:37:56 +09:00
parent 5f0f544157
commit c3df0afeb0
11 changed files with 1167 additions and 505 deletions

View File

@@ -55,6 +55,10 @@ WWTEST_BIN = $(BIN)/wwtest
WWTEST_SRC = cmd/wwtest/wwtest.ww \
internal/wwpackage/package.ww lib/os/exec/exec.ww \
lib/ascii/ascii.ww lib/bytes/bytes.ww \
lib/crypto/math/math.ww lib/crypto/sha256/sha256.ww \
lib/endian/big.ww lib/endian/little.ww lib/endian/network.ww \
lib/errors/errors.ww lib/hash/hash.ww \
lib/io/empty.ww lib/io/io.ww lib/io/stream.ww lib/io/types.ww \
lib/encoding/utf8/utf8.ww lib/math/floats.ww lib/math/math.ww \
lib/os/os.ww lib/rt/malloc.ww \
lib/strconv/decimal.ww lib/strconv/ftos.ww lib/strconv/ftos_data.ww \
@@ -99,6 +103,10 @@ PACKAGE_TEST_BIN = $(BIN)/test_package
PACKAGE_TEST_SRC = test/package/package_test.ww lib/os/exec/exec.ww \
lib/test/run.ww lib/fnmatch/fnmatch.ww \
lib/ascii/ascii.ww lib/bytes/bytes.ww \
lib/crypto/math/math.ww lib/crypto/sha256/sha256.ww \
lib/endian/big.ww lib/endian/little.ww lib/endian/network.ww \
lib/errors/errors.ww lib/hash/hash.ww \
lib/io/empty.ww lib/io/io.ww lib/io/stream.ww lib/io/types.ww \
lib/encoding/utf8/utf8.ww lib/os/os.ww lib/rt/malloc.ww \
lib/strings/strings.ww lib/temp/temp.ww lib/time/time.ww \
lib/types/types.ww
@@ -256,6 +264,10 @@ $(BIN)/w6l_ww: selfhost/cmd/w6l/main.ww selfhost/cmd/w6l/sym.ww \
$(BIN)/ww_ww: selfhost/cmd/ww/main.ww lib/os/exec/exec.ww \
lib/os/os.ww lib/rt/malloc.ww \
lib/time/time.ww lib/types/types.ww \
lib/crypto/math/math.ww lib/crypto/sha256/sha256.ww \
lib/endian/big.ww lib/endian/little.ww lib/endian/network.ww \
lib/errors/errors.ww lib/hash/hash.ww \
lib/io/empty.ww lib/io/io.ww lib/io/stream.ww lib/io/types.ww \
lib/strings/strings.ww lib/bytes/bytes.ww lib/encoding/utf8/utf8.ww \
lib/ww/syntax/lex.ww lib/ww/syntax/tok.ww lib/ww/syntax/ast.ww \
lib/ww/syntax/parse.ww lib/ww/syntax/expr.ww \

View File

@@ -50,16 +50,20 @@ a_intern(Asm *a, const char *name)
return s;
}
/* line iterator: returns the next line as a NUL-terminated buffer in
* line/llen pointers, advances pos. Returns 0 on EOF.
*/
static int
nextline(Asm *a, char **line, size_t *llen, char *buf, size_t bufsz)
nextline(Asm *a, char **line, size_t *llen)
{
if (a->pos >= a->srclen) return 0;
size_t n = 0;
while (a->pos < a->srclen && a->src[a->pos] != '\n' && n + 1 < bufsz)
buf[n++] = a->src[a->pos++];
u64 start = a->pos;
while (a->pos < a->srclen && a->src[a->pos] != '\n') a->pos++;
size_t n = (size_t)(a->pos - start);
char *buf = malloc(n + 1);
if (buf == NULL) {
err(a, "out of memory");
a->pos = a->srclen;
return 0;
}
memcpy(buf, a->src + start, n);
buf[n] = '\0';
if (a->pos < a->srclen && a->src[a->pos] == '\n') a->pos++;
*line = buf;
@@ -206,10 +210,13 @@ parse_operand(Asm *a, const char *s, Aoperand *out)
}
if (a_isidstart((unsigned char)*s)) {
char buf[256] = {0};
int n = 0;
while (a_isidcont((unsigned char)*s) && n < 255) buf[n++] = *s++;
buf[n] = 0;
const char *start = s;
while (a_isidcont((unsigned char)*s)) s++;
size_t n = (size_t)(s - start);
char *name = malloc(n + 1);
if (name == NULL) { err(a, "out of memory"); return -1; }
memcpy(name, start, n);
name[n] = 0;
/* Optional `+disp` between the ident and `(SB)`. Used by
* DATAR to address bytes inside an existing .data slot
@@ -227,12 +234,16 @@ parse_operand(Asm *a, const char *s, Aoperand *out)
int rn = 0;
s++;
while (*s && *s != ')' && rn < 7) rbuf[rn++] = *s++;
if (*s != ')') { err(a, "missing ')'"); return -1; }
if (*s != ')') {
err(a, "missing ')'");
free(name);
return -1;
}
s++;
int r = reg_lookup(rbuf);
if (r == D_PSB) {
out->type = D_EXTERN;
out->sym = strdup(buf);
out->sym = name;
out->offset = sym_disp;
return 0;
}
@@ -241,16 +252,18 @@ parse_operand(Asm *a, const char *s, Aoperand *out)
* reg_lookup and silently discarded the ident and
* +disp (rule 7). */
err(a, "unsupported name(reg) operand");
free(name);
return -1;
}
int r = reg_lookup(buf);
int r = reg_lookup(name);
if (r != 0) {
out->type = r;
free(name);
return 0;
}
out->type = D_BRANCH;
out->sym = strdup(buf);
out->sym = name;
return 0;
}
@@ -261,17 +274,14 @@ parse_operand(Asm *a, const char *s, Aoperand *out)
int
a_parse(Asm *a)
{
/* Big enough for a DATAW emitting a [256]u32 table (1024 bytes
* → ~4100 chars of `\xNN` escapes plus directive boilerplate).
* Selfhost w6a allocates per-line; this is the cstage equivalent. */
static char buf[32768];
char *line;
size_t len;
const char *pending_label = NULL;
while (nextline(a, &line, &len, buf, sizeof buf)) {
while (nextline(a, &line, &len)) {
const char *p = skipws(line);
if (*p == '\0' || (*p == '/' && p[1] == '/')) {
free(line);
a->line++;
continue;
}
@@ -282,6 +292,11 @@ a_parse(Asm *a)
if (*q == ':') {
size_t nl = q - p;
char *name = malloc(nl + 1);
if (name == NULL) {
err(a, "out of memory");
free(line);
return a->errs;
}
memcpy(name, p, nl);
name[nl] = '\0';
/* If a label is already pending we'd lose it
@@ -297,6 +312,7 @@ a_parse(Asm *a)
a->tail = prg;
}
pending_label = name;
free(line);
a->line++;
continue;
}
@@ -311,6 +327,7 @@ a_parse(Asm *a)
int op = opcode_lookup(mnem);
if (op == 0) {
err(a, "unknown opcode");
free(line);
a->line++;
continue;
}
@@ -322,14 +339,20 @@ a_parse(Asm *a)
pending_label = NULL;
while (*m == ' ' || *m == '\t') m++;
const char *rest = m;
char *rest = (char *)m;
if (op == A_TEXT) {
char nbuf[256] = {0};
int nn = 0;
while (*m && *m != ',' && nn < 255) nbuf[nn++] = *m++;
const char *start = m;
while (*m && *m != ',') m++;
size_t nn = (size_t)(m - start);
char *name = malloc(nn + 1);
if (name == NULL) {
err(a, "out of memory"); free(line); return a->errs;
}
memcpy(name, start, nn);
name[nn] = 0;
prg->to.type = D_EXTERN;
prg->to.sym = strdup(nbuf);
prg->to.sym = name;
if (*m == ',') {
m++;
while (*m == ' ' || *m == '$') m++;
@@ -339,11 +362,17 @@ a_parse(Asm *a)
} else if (op == A_DATA || op == A_DATAW) {
/* DATA name(SB),"escaped bytes" — read-only in .text
* DATAW name(SB),"escaped bytes" — writable in .data */
char nbuf[256] = {0};
int nn = 0;
while (*m && *m != '(' && nn < 255) nbuf[nn++] = *m++;
const char *start = m;
while (*m && *m != '(') m++;
size_t nn = (size_t)(m - start);
char *name = malloc(nn + 1);
if (name == NULL) {
err(a, "out of memory"); free(line); return a->errs;
}
memcpy(name, start, nn);
name[nn] = 0;
prg->to.type = D_EXTERN;
prg->to.sym = strdup(nbuf);
prg->to.sym = name;
if (*m == '(') {
while (*m && *m != ')') m++;
if (*m == ')') m++;
@@ -357,6 +386,9 @@ a_parse(Asm *a)
m++;
size_t cap = 32, len = 0;
u8 *buf = malloc(cap);
if (buf == NULL) {
err(a, "out of memory"); free(line); return a->errs;
}
while (*m && *m != '"') {
int c = (unsigned char)*m++;
if (c == '\\' && *m) {
@@ -381,7 +413,12 @@ a_parse(Asm *a)
}
if (len + 1 > cap) {
cap *= 2;
buf = realloc(buf, cap);
u8 *next = realloc(buf, cap);
if (next == NULL) {
free(buf); err(a, "out of memory");
free(line); return a->errs;
}
buf = next;
}
buf[len++] = (u8)c;
}
@@ -389,27 +426,30 @@ a_parse(Asm *a)
prg->nbytes = len;
}
} else {
const char *comma = NULL;
for (const char *q = rest; *q; q++)
int operand_bad = 0;
char *comma = NULL;
for (char *q = rest; *q; q++)
if (*q == ',' && comma == NULL) comma = q;
if (comma) {
char op1[256], op2[256];
size_t l1 = comma - rest;
if (l1 >= sizeof op1) l1 = sizeof op1 - 1;
memcpy(op1, rest, l1); op1[l1] = '\0';
size_t l2 = strlen(comma + 1);
if (l2 >= sizeof op2) l2 = sizeof op2 - 1;
memcpy(op2, comma + 1, l2); op2[l2] = '\0';
parse_operand(a, op1, &prg->from);
parse_operand(a, op2, &prg->to);
*comma = '\0';
if (parse_operand(a, rest, &prg->from) < 0
|| parse_operand(a, comma + 1, &prg->to) < 0)
operand_bad = 1;
} else if (*rest) {
parse_operand(a, rest, &prg->to);
if (parse_operand(a, rest, &prg->to) < 0)
operand_bad = 1;
}
if (operand_bad) {
free(line);
a->line++;
continue;
}
}
if (a->head == NULL) a->head = prg;
else a->tail->link = prg;
a->tail = prg;
free(line);
a->line++;
}
return a->errs;

View File

@@ -22,6 +22,18 @@ slurp(const char *path, char **outbuf, u64 *outlen)
return 0;
}
static int
export_owner_matches(const char *buf, u64 len, const char *path)
{
static const char prefix[] = "//ww:module ";
size_t pn = strlen(path);
size_t need = sizeof prefix - 1 + pn + 1;
return len >= need
&& memcmp(buf, prefix, sizeof prefix - 1) == 0
&& memcmp(buf + sizeof prefix - 1, path, pn) == 0
&& buf[need - 1] == '\n';
}
struct importin {
const char *path;
const char *file;
@@ -164,6 +176,13 @@ main(int argc, char **argv)
imports[i].path, imports[i].file);
return 1;
}
if (!export_owner_matches(imports[i].buf, imports[i].len,
imports[i].path)) {
fprintf(stderr,
"w6c: import %s: export owner mismatch in %s\n",
imports[i].path, imports[i].file);
return 1;
}
int bad = 0;
Node *f = parseinput(a, imports[i].file, imports[i].buf,
imports[i].len, imports[i].path, testsupport, 0, &bad);

View File

@@ -864,6 +864,8 @@ wwi_emit(Checker *c, FILE *of, Node *file)
const char *dot = strrchr(file->module, '.');
pkg = dot ? dot + 1 : file->module;
}
if (file->module != NULL && file->module[0] != '\0')
fprintf(of, "//ww:module %s\n", file->module);
fprintf(of, "package %s;\n", pkg);
/* imports — primary N_USE, byte-sorted by import path. */

View File

@@ -82,9 +82,8 @@ resolve_typename(Checker *c, Node *n)
* same name as the module itself (e.g. `random.random`). */
const char *dot = strrchr(nm, '.');
if (dot) {
char head[128] = {0};
size_t hl = (size_t)(dot - nm);
if (hl < sizeof head) memcpy(head, nm, hl);
char *head = astrndup(c->a, nm, hl);
Sym *m = scope_lookup(c->cur, head);
if (m && (m->kind == SK_USE || m->use_alias)) {
/* M1 #22: map the qualifier alias to its dotted

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +1,7 @@
package wwpackage;
import crypto.sha256;
import hash;
import os;
import os.exec;
import strconv;
@@ -269,9 +271,9 @@ fn pkgkeepfile(name: str) bool = {
// One explicit package directory; with recurse (the DIR/... form) every
// subdirectory whose name does not begin with '.' or '_' is descended as
// well, Go's ./... convention. lstat is used for the root and each
// candidate so a symlink cannot be used to cross the boundary (including
// DT_UNKNOWN files); a symlinked subdirectory is skipped, not followed.
// well, Go's ./... convention. An explicitly selected root may be a symlink;
// canonical planning coalesces it with the target directory. Recursive child
// symlinks remain skipped and symlink source files remain rejected.
fn pkgdiscoverdir(path: str, st: *pkgdiscover, recurse: bool) void = {
let rootstat: os.filestat;
match (os.lstat(&rootstat, path)) {
@@ -283,10 +285,15 @@ fn pkgdiscoverdir(path: str, st: *pkgdiscover, recurse: bool) void = {
};
};
if (pkgmodeis(rootstat.mode, os.mode.LINK)) {
pkgfailpath(path, "symlink traversal is not allowed");
match (os.stat(&rootstat, path)) {
case void => void;
case let e: os.oserror => {
pkgfailpath(path, "cannot stat package directory");
st.errors += 1;
return;
};
};
};
if (!pkgmodeis(rootstat.mode, os.mode.DIR)) {
pkgfailpath(path, "expected one package directory");
st.errors += 1;
@@ -540,7 +547,25 @@ fn pkgworkescape(s: str) str = {
};
fn pkgworkkey(dir: str) str = {
return strings.concat("d_", pkgworkescape(dir));
let escaped: str = strings.concat("d_", pkgworkescape(dir));
if (escaped.len <= 255) { return escaped; };
let state: sha256.state = sha256.sha256();
let h: *hash.hash = (&state): *hash.hash;
hash.write(h, strings.toutf8("ww-request-workdir-v1:"));
hash.write(h, strings.toutf8(dir));
let digest: [32]u8;
hash.sum(h, digest[0:32]);
let out: []u8 = alloc([], 65u64)!;
let digits: str = "0123456789abcdef";
let i: i32 = 0;
for (i < 32) {
let high: i32 = (digest[i] / 16u8): i32;
let low: i32 = (digest[i] % 16u8): i32;
append(out, digits[high]);
append(out, digits[low]);
i += 1;
};
return strings.concat("d_", strings.frombytes(out));
};
fn pkgsetplanpaths(p: *pkgplan, groups: []pkggroup, root: str, index: i32,

View File

@@ -629,7 +629,9 @@ export fn parsefile(p: *parser) *node = {
|| strings.compare(name, "main_test") == 0);
if (strings.compare(name, last) != 0 && !testsupport
&& !commandpackage) {
errmsg(p, "package does not match import path");
errmsg(p, strings.concat(strings.concat(strings.concat(
"package ", name),
" does not match import path "), active));
};
} else {
p.curmod = name;

View File

@@ -60,6 +60,25 @@ fn slurp(path: *u8) (*u8, u64) = {
return buf.ptr, nz;
};
fn exportownermatches(buf: *u8, n: u64, path: *u8) bool = {
let prefix: str = "//ww:module ";
let pn: u64 = cstrlen(path);
let poff: u64 = prefix.len: u64;
let need: u64 = poff + pn + 1u64;
if (n < need) { return false; };
let i: u64 = 0u64;
for (i < poff) {
if (buf[i] != prefix.ptr[i]) { return false; };
i += 1u64;
};
i = 0u64;
for (i < pn) {
if (buf[poff + i] != path[i]) { return false; };
i += 1u64;
};
return buf[need - 1u64] == 10u8;
};
export fn main(argc: i32, argv: **u8) i32 = {
let src: *u8 = nil;
let out: *u8 = nil;
@@ -204,6 +223,17 @@ export fn main(argc: i32, argv: **u8) i32 = {
os.write(2, nl.ptr, nl.len: u64);
return 1;
};
if (!exportownermatches(ibuf, ilen, importpaths[importi])) {
let pre: str = "w6c: import ";
let mid: str = ": export owner mismatch in ";
let nl: str = "\n";
os.write(2, pre.ptr, pre.len: u64);
os.write(2, importpaths[importi], cstrlen(importpaths[importi]));
os.write(2, mid.ptr, mid.len: u64);
os.write(2, importfiles[importi], cstrlen(importfiles[importi]));
os.write(2, nl.ptr, nl.len: u64);
return 1;
};
let il: lex;
lexinit(&il, strings.dup(pathstr(importfiles[importi])), ibuf, ilen);
let ips: parser;

View File

@@ -1017,6 +1017,9 @@ fn wwiemit(c: *checker, file: *syntax.node, path: str) i32 = {
pkg = file.nmod;
};
};
if (file.nmod.len > 0) {
wputs(fd, "//ww:module "); wputs(fd, file.nmod); wputs(fd, "\n");
};
wputs(fd, "package ");
wputs(fd, pkg);
wputs(fd, ";\n");

View File

@@ -13,6 +13,8 @@
package main;
import crypto.sha256;
import hash;
import os;
import os.exec;
import rt;
@@ -137,8 +139,9 @@ fn selfdirinto(dst: *u8, dstsz: u64, argv0: *u8) void = {
};
fn joinpath(dir: *u8, name: *u8) *u8 = {
let buf: []u8 = alloc([], (os.PATH_MAX: u64))!;
buf.len = os.PATH_MAX;
let need: u64 = cstrlen(dir) + 1u64 + cstrlen(name) + 1u64;
let buf: []u8 = alloc([], need)!;
buf.len = need: i32;
let off: u64 = cstrinto(buf.ptr, 0u64, dir);
off = byteinto(buf.ptr, off, 47u8);
off = cstrinto(buf.ptr, off, name);
@@ -147,8 +150,9 @@ fn joinpath(dir: *u8, name: *u8) *u8 = {
};
fn joinpathlit(dir: *u8, name: str) *u8 = {
let buf: []u8 = alloc([], (os.PATH_MAX: u64))!;
buf.len = os.PATH_MAX;
let need: u64 = cstrlen(dir) + 1u64 + name.len: u64 + 1u64;
let buf: []u8 = alloc([], need)!;
buf.len = need: i32;
let off: u64 = cstrinto(buf.ptr, 0u64, dir);
off = byteinto(buf.ptr, off, 47u8);
off = strinto(buf.ptr, off, name);
@@ -495,7 +499,6 @@ def SEP_ROLE_GENERATED_MAIN: i32 = 2;
def SEP_TEST_SUPPORT_MODULE: str = "__wwtest";
def SEP_MAXPRODUCT: i32 = 256;
def SEP_MAXCONTEXT: i32 = 257;
def SEP_ARTIFACT_MAX: i32 = 1024;
// Classify a selected directory entry: 1 production, 2 test, 0 skipped,
// -1 @test outside *_test.ww, -2 non-regular source.
@@ -558,11 +561,6 @@ fn dirpackagename(path: *u8) *u8 = {
cerr(": error: invalid or missing package clause\n");
return nil;
};
if (imports.nmod.len >= 256) {
cerrpos(imports.file, imports.line, imports.col);
cerr(": error: package name is too long\n");
return nil;
};
return arenadupcstr(imports.nmod.ptr, imports.nmod.len: u64);
};
@@ -759,8 +757,9 @@ fn makestem(stem: *u8, src: *u8) void = {
};
fn appendlit(stem: *u8, suffix: str) *u8 = {
let buf: []u8 = alloc([], (os.PATH_MAX: u64))!;
buf.len = os.PATH_MAX;
let need: u64 = cstrlen(stem) + suffix.len: u64 + 1u64;
let buf: []u8 = alloc([], need)!;
buf.len = need: i32;
let off: u64 = cstrinto(buf.ptr, 0u64, stem);
off = strinto(buf.ptr, off, suffix);
cstrseal(buf.ptr, off);
@@ -804,6 +803,8 @@ type seppkg = struct {
entry: *u8, // resolved package dir (or file, file root), NUL-term
canon: *u8, // canonical location; never package identity
artifact: *u8, // stable non-importable variant artifact key
storage: *u8, // internal storage basename; never package identity
storagehashed: bool,
name: *u8, // validated declared name; directory packages only
testpackage: *u8,
sources: **u8, // owned, byte-sorted selected paths; dirs only
@@ -930,10 +931,7 @@ fn sepcommandcompilermarker(g: *sepgraph, pi: i32) bool = {
// identity. The compiler path is derived from that base and the semantic
// variant; root/product/artifact state never participates.
fn sepbindimportbase(g: *sepgraph, pi: i32, base: *u8) i32 = {
if (base == nil || base[0u64] == 0u8 || cstrlen(base) >= 256u64) {
cerr("ww: package path is too long (limit 255 bytes)\n");
return -1;
};
if (base == nil || base[0u64] == 0u8) { return -1; };
if (!reservedimportpath(base) && !sepimportbasevalid(base)) {
cerr("ww: invalid package path "); cerr(pathstr(base)); cerr("\n");
return -1;
@@ -992,10 +990,6 @@ fn sepbindimportbase(g: *sepgraph, pi: i32, base: *u8) i32 = {
fn sepfindoraddvariant(g: *sepgraph, path: *u8, entry: *u8,
isdir: i32, variant: i32, testpackage: *u8, role: i32,
artifact: *u8, root: bool) i32 = {
if (cstrlen(path) >= 256u64) {
cerr("ww: package path is too long (limit 255 bytes)\n");
return -1;
};
let canon: *u8 = nil;
if (isdir != 0) {
canon = canonicaldir(pathstr(entry));
@@ -1004,10 +998,6 @@ fn sepfindoraddvariant(g: *sepgraph, path: *u8, entry: *u8,
cerr(pathstr(entry)); cerr("\n");
return -1;
};
if (cstrlen(canon) >= 1024u64) {
cerr("ww: canonical package path is too long\n");
return -1;
};
};
let incoming: *u8 = nil;
if (isdir != 0 && path[0u64] != 0u8) {
@@ -1094,6 +1084,8 @@ fn sepfindoraddvariant(g: *sepgraph, path: *u8, entry: *u8,
g.pkg[g.n].entry = arenadupcstr(entry, elen);
g.pkg[g.n].canon = canon;
g.pkg[g.n].artifact = nil;
g.pkg[g.n].storage = nil;
g.pkg[g.n].storagehashed = false;
g.pkg[g.n].name = nil;
g.pkg[g.n].testpackage = nil;
if (testpackage != nil) {
@@ -1186,17 +1178,12 @@ fn sepgraphfree(g: *sepgraph) void = {
fn sepcontextfor(g: *sepgraph, root: *u8, incs: *u8,
toolsrcdir: *u8) i32 = {
let cap: u64 = (os.PATH_MAX: u64) * 2u64;
let need: u64 = cstrlen(root) + 1u64 + cstrlen(toolsrcdir) + 1u64;
if (incs != nil && incs[0u64] != 0u8) {
need += cstrlen(incs) + 1u64;
};
if (need > cap) {
cerr("ww: package import search path is too long\n");
return -1;
};
let search: []u8 = alloc([], cap)!;
search.len = cap: i32;
let search: []u8 = alloc([], need)!;
search.len = need: i32;
let off: u64 = cstrinto(search.ptr, 0u64, root);
off = byteinto(search.ptr, off, 58u8);
if (incs != nil && incs[0u64] != 0u8) {
@@ -1221,59 +1208,142 @@ fn sepcontextfor(g: *sepgraph, root: *u8, incs: *u8,
return result;
};
// Build one artifact path. Test roots use distinct names even though both
// compiler units reset to the bare executable namespace.
def SEP_NAME_MAX: u64 = 255u64;
fn sepstoragedigest(p: *seppkg) *u8 = {
let state: sha256.state = sha256.sha256();
let h: *hash.hash = (&state): *hash.hash;
hash.write(h, strings.toutf8("ww-package-storage-v2:"));
let tag: [4]u8;
tag[0] = ('0': i32 + p.variant): u8;
tag[1] = ':': u8;
tag[2] = ('0': i32 + p.role): u8;
tag[3] = ':': u8;
hash.write(h, tag[0:4]);
hash.write(h, strings.toutf8(pathstr(p.path)));
let zero: [1]u8;
zero[0] = 0u8;
hash.write(h, zero[0:1]);
hash.write(h, strings.toutf8(pathstr(p.canon)));
let digest: [32]u8;
hash.sum(h, digest[0:32]);
let need: u64 = "__wwpkg.v".len: u64 + 1u64 + ".r".len: u64
+ 1u64 + ".h".len: u64 + 64u64 + 1u64;
let out: []u8 = alloc([], need)!;
out.len = need: i32;
let off: u64 = strinto(out.ptr, 0u64, "__wwpkg.v");
off = byteinto(out.ptr, off, tag[0]);
off = strinto(out.ptr, off, ".r");
off = byteinto(out.ptr, off, tag[2]);
off = strinto(out.ptr, off, ".h");
let hex: str = "0123456789abcdef";
let i: i32 = 0;
for (i < 32) {
let high: i32 = (digest[i] / 16u8): i32;
let low: i32 = (digest[i] % 16u8): i32;
off = byteinto(out.ptr, off, hex[high]);
off = byteinto(out.ptr, off, hex[low]);
i += 1;
};
cstrseal(out.ptr, off);
return out.ptr;
};
fn seplegacyartifact(p: *seppkg) *u8 = {
if (p.artifact != nil && p.artifact[0u64] != 0u8) { return p.artifact; };
if (p.path != nil && p.path[0u64] != 0u8) { return p.path; };
return "__root\0".ptr;
};
fn sepvalidatestoragepath(p: *seppkg, scratch: *u8) i32 = {
let need: u64 = cstrlen(scratch) + 1u64 + cstrlen(p.storage)
+ ".unit.new".len: u64 + 1u64;
if (cstrlen(p.storage) + ".unit.new".len: u64 > SEP_NAME_MAX
|| need > os.PATH_MAX: u64) {
cerr("ww: package artifact path is too long\n");
return -1;
};
return 0;
};
fn sepassignstorage(p: *seppkg, scratch: *u8) i32 = {
let base: *u8 = seplegacyartifact(p);
let need: u64 = cstrlen(scratch) + 1u64 + cstrlen(base)
+ ".unit.new".len: u64 + 1u64;
if (cstrlen(base) + ".unit.new".len: u64 <= SEP_NAME_MAX
&& need <= os.PATH_MAX: u64) {
p.storage = arenadupcstr(base, cstrlen(base));
p.storagehashed = false;
} else {
p.storage = sepstoragedigest(p);
p.storagehashed = true;
};
return sepvalidatestoragepath(p, scratch);
};
fn sepfname(g: *sepgraph, pi: i32, scratch: *u8, suffix: str) *u8 = {
let buf: []u8 = alloc([], SEP_ARTIFACT_MAX: u64)!;
buf.len = SEP_ARTIFACT_MAX;
let need: u64 = cstrlen(scratch) + 1u64
+ cstrlen(g.pkg[pi].storage) + suffix.len: u64 + 1u64;
let buf: []u8 = alloc([], need)!;
buf.len = need: i32;
let off: u64 = cstrinto(buf.ptr, 0u64, scratch);
off = byteinto(buf.ptr, off, 47u8); // '/'
if (g.pkg[pi].artifact != nil) {
off = cstrinto(buf.ptr, off, g.pkg[pi].artifact);
} else { if (g.pkg[pi].path[0u64] != 0u8) {
off = cstrinto(buf.ptr, off, g.pkg[pi].path);
} else {
off = strinto(buf.ptr, off, "__root");
}; };
off = cstrinto(buf.ptr, off, g.pkg[pi].storage);
off = strinto(buf.ptr, off, suffix);
cstrseal(buf.ptr, off);
return buf.ptr;
};
// sepfname uses SEP_ARTIFACT_MAX storage. Validate the longest suffix once
// before opening files so two action identities can never alias by truncation.
fn sepvalidateartifactpaths(g: *sepgraph, scratch: *u8) i32 = {
let i: i32 = 0;
for (i < g.n) {
if (g.pkg[i].failed || !g.pkg[i].loaded) { i += 1; continue; };
let base: *u8 = g.pkg[i].artifact;
if (base == nil) {
base = g.pkg[i].path;
if (base[0u64] == 0u8) { base = "__root\0".ptr; };
};
let need: u64 = cstrlen(scratch) + 1u64 + cstrlen(base)
+ ".unit.new".len: u64 + 1u64;
if (need > SEP_ARTIFACT_MAX: u64) {
cerr("ww: package artifact path is too long\n");
return -1;
if (g.pkg[i].storage == nil
&& sepassignstorage(&g.pkg[i], scratch) < 0) { return -1; };
i += 1;
};
let changed: bool = true;
for (changed) {
changed = false;
i = 0;
for (i < g.n && !changed) {
if (g.pkg[i].failed || !g.pkg[i].loaded) { i += 1; continue; };
let j: i32 = i + 1;
for (j < g.n) {
if (g.pkg[j].failed || !g.pkg[j].loaded) { j += 1; continue; };
let other: *u8 = g.pkg[j].artifact;
if (other == nil) {
other = g.pkg[j].path;
if (other[0u64] == 0u8) { other = "__root\0".ptr; };
if (g.pkg[j].failed || !g.pkg[j].loaded
|| !cstreq(g.pkg[i].storage, g.pkg[j].storage)) {
j += 1; continue;
};
if (cstreq(base, other)) {
cerr("ww: package actions share artifact identity ");
cerr(pathstr(base)); cerr("\n");
if (!g.pkg[i].storagehashed || !g.pkg[j].storagehashed) {
if (!g.pkg[i].storagehashed) {
g.pkg[i].storage = sepstoragedigest(&g.pkg[i]);
g.pkg[i].storagehashed = true;
if (sepvalidatestoragepath(&g.pkg[i], scratch) < 0) {
return -1;
};
};
if (!g.pkg[j].storagehashed) {
g.pkg[j].storage = sepstoragedigest(&g.pkg[j]);
g.pkg[j].storagehashed = true;
if (sepvalidatestoragepath(&g.pkg[j], scratch) < 0) {
return -1;
};
};
changed = true;
j = g.n;
} else {
cerr("ww: package storage collision for ");
cerr(pathstr(g.pkg[i].path)); cerr(" in ");
cerr(pathstr(g.pkg[i].canon)); cerr(" and ");
cerr(pathstr(g.pkg[j].path)); cerr(" in ");
cerr(pathstr(g.pkg[j].canon)); cerr(" at ");
cerr(pathstr(g.pkg[i].storage)); cerr("\n");
return -1;
};
j += 1;
};
i += 1;
};
};
return 0;
};
@@ -1384,11 +1454,6 @@ fn sepscanfile(g: *sepgraph, pi: i32, file: *u8, searchpath: *u8,
&& (ownedsource != 0 || g.pkg[pi].name == nil)) {
let declared: *u8 = imports.nmod.ptr;
let declaredn: u64 = imports.nmod.len: u64;
if (declaredn >= 256u64) {
cerrpos(imports.file, imports.line, imports.col);
cerr(": error: package name is too long\n");
return -1;
};
if (g.pkg[pi].name == nil) {
g.pkg[pi].name = arenadupcstr(declared, declaredn);
} else { if (bytecmp(g.pkg[pi].name, cstrlen(g.pkg[pi].name),
@@ -1465,11 +1530,6 @@ fn sepscanfile(g: *sepgraph, pi: i32, file: *u8, searchpath: *u8,
cerr(u.usepath); cerr(" is reserved\n");
return -1;
};
if (idn >= 256u64) {
cerrpos(u.file, u.line, u.col);
cerr(": error: import path is too long (limit 255 bytes)\n");
return -1;
};
let externalproduction: bool = false;
let ipath: *u8 = nil;
if (g.pkg[pi].variant == SEP_VARIANT_EXTERNAL
@@ -1638,6 +1698,8 @@ fn sepaddgeneratedmain(g: *sepgraph, product: *sepproduct, ordinal: i32,
let variantartifact: *u8 = g.pkg[variant].artifact;
if (variantartifact == nil) { variantartifact = g.pkg[variant].path; };
p.artifact = appendlit(variantartifact, "-main");
p.storage = nil;
p.storagehashed = false;
p.name = arenadupcstr("main\0".ptr, 4u64);
p.testpackage = nil;
p.sources = nil;
@@ -1888,10 +1950,7 @@ fn sepreverseimportbase(g: *sepgraph, p: *seppkg, context: i32,
if (rel != nil) {
let converted: i32 = sepimportpathfromrelative(rel,
out, outsz);
if (converted < 0) {
cerr("ww: package path is too long (limit 255 bytes)\n");
return -1;
};
if (converted < 0) { return -1; };
if (converted > 0 && reservedimportpath(out)) {
converted = 0;
};
@@ -1981,12 +2040,14 @@ fn sepfinalizedirectoryidentities(g: *sepgraph) i32 = {
let ci: i32 = 0;
for (ci < g.ncontext) {
if (p.contextstate[ci] == 2u8) {
let candidate: [256]u8;
let candidatesz: u64 = cstrlen(p.canon) + 1u64;
let candidate: []u8 = alloc([], candidatesz)!;
candidate.len = candidatesz: i32;
let found: i32 = sepreverseimportbase(g, p, ci,
&candidate[0], 256u64);
candidate.ptr, candidatesz);
if (found < 0) { return -1; };
if (found > 0 && sepbindimportbase(g, pi,
&candidate[0]) < 0) { return -1; };
candidate.ptr) < 0) { return -1; };
};
ci += 1;
};
@@ -2014,10 +2075,7 @@ fn sepfinalizedirectoryidentities(g: *sepgraph) i32 = {
};
if (base == nil) {
base = seplocalimportbase(p);
if (base == nil || cstrlen(base) >= 256u64) {
cerr("ww: local package identity is too long (limit 255 bytes)\n");
return -1;
};
if (base == nil) { return -1; };
};
if (sepbindimportbase(g, pi, base) < 0) { return -1; };
};
@@ -2353,6 +2411,70 @@ fn filesizenonzero(path: *u8) bool = {
return ok;
};
fn sepvalidateunitowner(g: *sepgraph, pi: i32, scratch: *u8) i32 = {
let unit: *u8 = sepfname(g, pi, scratch, ".unit.ww");
let fi: os.filestat;
match (os.lstat(&fi, pathstr(unit))) {
case let e: os.oserror => {
if ((e: i64) == -2i64) { return 0; };
cerr("ww: package storage owner mismatch at ");
cerr(pathstr(g.pkg[pi].storage)); cerr(" for ");
if (g.pkg[pi].path[0u64] == 0u8) { cerr("(root)"); }
else { cerr(pathstr(g.pkg[pi].path)); };
cerr("\n");
return -1;
};
case void => void;
};
let typ: u32 = (fi.mode: u32) & 61440u32;
if (typ != os.mode.REG: u32) {
cerr("ww: package storage owner mismatch at ");
cerr(pathstr(g.pkg[pi].storage)); cerr(" for ");
if (g.pkg[pi].path[0u64] == 0u8) { cerr("(root)"); }
else { cerr(pathstr(g.pkg[pi].path)); };
cerr("\n");
return -1;
};
let buf: *u8;
let n: u64;
buf, n = slurp(unit);
let prefix: str = "//ww:module-reset";
let pathn: u64 = cstrlen(g.pkg[pi].path);
let want: u64 = prefix.len: u64 + 1u64;
if (pathn > 0u64) { want += 1u64 + pathn; };
let matches: bool = buf != nil && n >= want
&& bytecmp(buf, prefix.len: u64, prefix.ptr,
prefix.len: u64) == 0;
if (matches) {
let off: u64 = prefix.len: u64;
if (pathn == 0u64) {
matches = buf[off] == ('\n': u8);
} else {
matches = buf[off] == (' ': u8)
&& bytecmp(buf + off + 1u64, pathn,
g.pkg[pi].path, pathn) == 0
&& buf[off + 1u64 + pathn] == ('\n': u8);
};
};
if (matches) { return 1; };
cerr("ww: package storage owner mismatch at ");
cerr(pathstr(g.pkg[pi].storage)); cerr(" for ");
if (pathn == 0u64) { cerr("(root)"); }
else { cerr(pathstr(g.pkg[pi].path)); };
cerr("\n");
return -1;
};
fn sepvalidateworkdirowners(g: *sepgraph, scratch: *u8) i32 = {
let i: i32 = 0;
for (i < g.n) {
if (!g.pkg[i].failed && g.pkg[i].loaded
&& sepvalidateunitowner(g, i, scratch) < 0) { return -1; };
i += 1;
};
return 0;
};
// Byte equality of two files; absence or IO error is inequality.
fn fileequal(a: *u8, b: *u8) bool = {
let fa: i32 = os.open(pathstr(a), os.flag.RDONLY, 0i32);
@@ -2423,14 +2545,14 @@ fn copyfileatomic(src: *u8, dst: *u8) i32 = {
fn workdirstamptext(istest: i32, emitasm: i32) str = {
if (istest != 0) {
if (emitasm != 0) {
return "ww workdir fmt 9 mode test asm 1\n";
return "ww workdir fmt 11 mode test asm 1\n";
};
return "ww workdir fmt 9 mode test asm 0\n";
return "ww workdir fmt 11 mode test asm 0\n";
};
if (emitasm != 0) {
return "ww workdir fmt 8 mode build asm 1\n";
return "ww workdir fmt 10 mode build asm 1\n";
};
return "ww workdir fmt 8 mode build asm 0\n";
return "ww workdir fmt 10 mode build asm 0\n";
};
fn stampmatches(path: *u8, want: str) bool = {
@@ -2595,20 +2717,18 @@ fn buildonesepimpl(selfdir: *u8, src: *u8, entryisdir: i32,
};
};
let stem: []u8 = alloc([], (os.PATH_MAX: u64))!;
stem.len = os.PATH_MAX;
let stem: *u8 = nil;
if (entryisdir != 0) {
let dlen: u64 = cstrlen(srcd.ptr);
let bo: u64 = basenameoff(srcd.ptr, dlen);
let off: u64 = cstrinto(stem.ptr, 0u64, srcd.ptr);
stem[off] = 47u8; off += 1u64; // '/'
let i: u64 = bo;
for (i < dlen) { stem[off] = srcd[i]; off += 1u64; i += 1u64; };
cstrseal(stem.ptr, off);
stem = joinpath(srcd.ptr, srcd.ptr + bo);
} else {
makestem(stem.ptr, src);
let stembuf: []u8 = alloc([], cstrlen(src) + 1u64)!;
stembuf.len = (cstrlen(src) + 1u64): i32;
makestem(stembuf.ptr, src);
stem = stembuf.ptr;
};
let effstem: *u8 = stem.ptr;
let effstem: *u8 = stem;
if (objstem != nil) { effstem = objstem; };
let warm: bool = false;
if (workdir != nil) {
@@ -2675,7 +2795,6 @@ fn buildonesepimpl(selfdir: *u8, src: *u8, entryisdir: i32,
if (!fileequal(toola, a6)) { staleall = true; };
};
};
if (staleall && invalidateworkdirunits(scratch) != 0) { return 1; };
};
let rtpaths: []*u8 = alloc([], 2u64)!;
@@ -2882,6 +3001,8 @@ fn buildonesepimpl(selfdir: *u8, src: *u8, entryisdir: i32,
};
};
if (sepvalidateartifactpaths(g, scratch) < 0) { return 1; };
if (warm && sepvalidateworkdirowners(g, scratch) < 0) { return 1; };
if (warm && staleall && invalidateworkdirunits(scratch) != 0) { return 1; };
let rootpackage: bool = packageonly != 0;
if (rootpackage && !g.pkg[products[0].root].failed
&& cstreqlit(g.pkg[products[0].root].name, "main")) {
@@ -3534,8 +3655,14 @@ fn dobuild(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = {
let workdir: *u8 = nil; // -w persistent package-artifact workdir
let emitasm: i32 = 0;
let packageonly: i32 = 0;
let incs: []u8 = alloc([], (os.PATH_MAX: u64) * 2u64)!;
incs.len = ((os.PATH_MAX: u64) * 2u64): i32;
let inccap: u64 = 1u64;
let capi: i32 = start;
for (capi < argc) {
inccap += cstrlen(argv[capi]) + 1u64;
capi += 1;
};
let incs: []u8 = alloc([], inccap)!;
incs.len = inccap: i32;
let incoff: u64 = 0u64;
cstrseal(incs.ptr, 0u64);
@@ -3739,8 +3866,14 @@ fn makedrivertmp(buf: *u8, prefix: str) void = {
fn dorun(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = {
let src: *u8 = nil;
let passstart: i32 = -1; // first argv idx to pass through to program
let incs: []u8 = alloc([], (os.PATH_MAX: u64) * 2u64)!;
incs.len = ((os.PATH_MAX: u64) * 2u64): i32;
let inccap: u64 = 1u64;
let capi: i32 = start;
for (capi < argc) {
inccap += cstrlen(argv[capi]) + 1u64;
capi += 1;
};
let incs: []u8 = alloc([], inccap)!;
incs.len = inccap: i32;
let incoff: u64 = 0u64;
cstrseal(incs.ptr, 0u64);
@@ -4038,8 +4171,14 @@ fn dotest(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = {
// to the test binary as argv[1] (single-file/module only; dir-mode
// rejects). cstage twin: do_test `pattern`.
let patarg: *u8 = nil;
let incs: []u8 = alloc([], (os.PATH_MAX: u64) * 2u64)!;
incs.len = ((os.PATH_MAX: u64) * 2u64): i32;
let inccap: u64 = 1u64;
let capi: i32 = start;
for (capi < argc) {
inccap += cstrlen(argv[capi]) + 1u64;
capi += 1;
};
let incs: []u8 = alloc([], inccap)!;
incs.len = inccap: i32;
let incoff: u64 = 0u64;
cstrseal(incs.ptr, 0u64);
@@ -4093,7 +4232,7 @@ fn dotest(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = {
if ((!cstreqlit(kind, "production")
&& !cstreqlit(kind, "same")
&& !cstreqlit(kind, "external"))
|| pn == 0u64 || pn >= 256u64
|| pn == 0u64
|| dir[0u64] == 0u8 || output[0u64] == 0u8
|| status[0u64] == 0u8
|| (cstreqlit(kind, "external")