From 00903b986bb5ebe1020a696f3cd933ede80eb8e9 Mon Sep 17 00:00:00 2001 From: Hojun-Cho Date: Thu, 13 Aug 2026 08:08:49 +0900 Subject: [PATCH] ww: grow package universes dynamically --- cmd/ww/main.c | 727 +++++++++++++---- internal/wwpackage/package.ww | 362 ++++++++- selfhost/cmd/ww/main.ww | 1420 ++++++++++++++++++++++++++------- 3 files changed, 2016 insertions(+), 493 deletions(-) diff --git a/cmd/ww/main.c b/cmd/ww/main.c index cf39f98a..acb9a946 100644 --- a/cmd/ww/main.c +++ b/cmd/ww/main.c @@ -40,6 +40,7 @@ static const char *usage = static char *self_dir; static const char *self_path; static char *sep_sprintf(const char *, ...); +static int sep_reserve(void **, int *, int, size_t); static const char * envpath(const char *name) @@ -165,6 +166,10 @@ struct ImportSet { int n, cap; }; +static int sep_fatal_allocation; +static int sep_fail_size(void); +static int sep_fail_nomem(void); + #define SEP_LOCAL_IMPORT_PREFIX "__wwlocal" static int @@ -175,14 +180,17 @@ import_seen(struct ImportSet *s, const char *path) return 0; } -static void +static int import_add(struct ImportSet *s, const char *path) { - if (s->n + 1 > s->cap) { - s->cap = s->cap ? s->cap * 2 : 8; - s->paths = realloc(s->paths, s->cap * sizeof *s->paths); - } - s->paths[s->n++] = strdup(path); + if (s->n == INT_MAX) return sep_fail_size(); + if (sep_reserve((void **)&s->paths, &s->cap, s->n + 1, + sizeof *s->paths) < 0) + return -1; + char *copy = strdup(path); + if (copy == NULL) return sep_fail_nomem(); + s->paths[s->n++] = copy; + return 0; } /* `encoding.utf8` → `encoding/utf8`. Mirrors Hare hare(1)'s @@ -365,8 +373,57 @@ 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_MAXPRODUCT 256 -#define SEP_MAXCONTEXT (SEP_MAXPRODUCT + 1) + +/* Package-graph storage grows geometrically. Counts remain signed ints + * because they are stable action/context indices throughout the existing + * command model; checked reserve rejects an unrepresentable count or byte + * size before publishing a partial vector or invoking a tool. */ +#define SEP_INITIAL_CAP 8 + +/* Package discovery is deliberately single-threaded. Remember allocation + * and representability failures across its helper stack so one failed root + * cannot be mistaken for an ordinary package diagnostic while a sibling + * proceeds to compiler or linker invocation. */ +static int +sep_fail_size(void) +{ + sep_fatal_allocation = 1; + fprintf(stderr, "ww: package graph is too large\n"); + return -1; +} + +static int +sep_fail_nomem(void) +{ + sep_fatal_allocation = 1; + fprintf(stderr, "ww: out of memory\n"); + return -1; +} + +static int +sep_reserve(void **buf, int *cap, int need, size_t elemsz) +{ + if (need < 0 || elemsz == 0) return sep_fail_size(); + if (need <= *cap) return 0; + int ncap = *cap > 0 ? *cap : SEP_INITIAL_CAP; + while (ncap < need) { + if (ncap > INT_MAX / 2) { + ncap = INT_MAX; + break; + } + ncap *= 2; + } + if (ncap < need || (size_t)ncap > (size_t)-1 / elemsz) + return sep_fail_size(); + int oldcap = *cap; + void *next = realloc(*buf, (size_t)ncap * elemsz); + if (next == NULL) return sep_fail_nomem(); + memset((char *)next + (size_t)oldcap * elemsz, 0, + (size_t)(ncap - oldcap) * elemsz); + *buf = next; + *cap = ncap; + return 0; +} /* Test-file package classification uses the compiler's imports-only parser. * The coordinator chooses variants, but the command owns which real source @@ -400,6 +457,7 @@ source_package_name(const char *path, char **out) } *out = strdup(imports->module); if (*out == NULL) { + sep_fail_nomem(); freearena(a); free(buf); return -1; @@ -412,15 +470,11 @@ source_package_name(const char *path, char **out) static int source_list_add(char ***list, int *n, int *cap, const char *path) { - if (*n + 1 > *cap) { - int ncap = *cap ? *cap * 2 : 8; - char **next = realloc(*list, (size_t)ncap * sizeof *next); - if (next == NULL) return -1; - *list = next; - *cap = ncap; - } + if (*n == INT_MAX) return sep_fail_size(); + if (sep_reserve((void **)list, cap, *n + 1, sizeof **list) < 0) + return -1; char *copy = strdup(path); - if (copy == NULL) return -1; + if (copy == NULL) return sep_fail_nomem(); (*list)[(*n)++] = copy; return 0; } @@ -521,9 +575,24 @@ enumerate_dir_ww(const char *dirpath, int variant, const char *test_package, closedir(d); if (nprod > 1) qsort(prod, (size_t)nprod, sizeof *prod, strs_cmp); if (ntests > 1) qsort(tests, (size_t)ntests, sizeof *tests, strs_cmp); + if (nprod > INT_MAX - ntests) { + sep_fail_size(); + source_list_free(prod, nprod); + source_list_free(tests, ntests); + *out_files = NULL; + return -2; + } int total = nprod + ntests; + if ((size_t)total > (size_t)-1 / sizeof *prod) { + sep_fail_size(); + source_list_free(prod, nprod); + source_list_free(tests, ntests); + *out_files = NULL; + return -2; + } char **all = total ? malloc((size_t)total * sizeof *all) : NULL; if (total && all == NULL) { + sep_fail_nomem(); source_list_free(prod, nprod); source_list_free(tests, ntests); *out_files = NULL; @@ -554,8 +623,6 @@ enumerate_dir_ww(const char *dirpath, int variant, const char *test_package, * a `.wwi` relocates the public foreign type/const facts required by its own * API. Full transitive reachability remains a linker concern. */ -#define SEP_MAXPKG 256 - struct seppkg { char *path; /* complete compiler/import identity */ char *import_base; /* complete canonical ordinary import identity */ @@ -579,10 +646,12 @@ struct seppkg { int loaded; /* directory membership/name loaded exactly once */ int export_changed; /* staged export differs from committed export */ int emit_context; /* first verified resolution context */ - unsigned char context_state[SEP_MAXCONTEXT]; /* 0 new, 1 active, 2 checked */ + unsigned char *context_state; /* 0 new, 1 active, 2 checked */ + int context_cap; struct ImportSet bindings; /* first context's canonical import bindings */ - int deps[SEP_MAXPKG]; /* direct-dep indices into sepgraph.pkg */ + int *deps; /* stable direct-dep indices into sepgraph.pkg */ int ndeps; + int depcap; int color; /* tri-color DFS: 0 white, 1 gray, 2 black */ }; @@ -592,10 +661,12 @@ struct sepcontext { }; struct sepgraph { - struct seppkg pkg[SEP_MAXPKG]; + struct seppkg *pkg; int n; - struct sepcontext context[SEP_MAXCONTEXT]; + int pkgcap; + struct sepcontext *context; int ncontext; + int contextcap; int support_context; int identity_failed; /* command-global canonical identity collision */ }; @@ -611,8 +682,57 @@ struct sepproduct { int context; int root; int variant_root; /* production-plus-test or external test package */ + int support; /* direct generated-main support action, or -1 */ }; +static void sep_pkg_free_fields(struct seppkg *); + +static int +sep_reserve_packages(struct sepgraph *g, int need) +{ + return sep_reserve((void **)&g->pkg, &g->pkgcap, need, + sizeof *g->pkg); +} + +static int +sep_reserve_contexts(struct sepgraph *g, int need) +{ + return sep_reserve((void **)&g->context, &g->contextcap, need, + sizeof *g->context); +} + +static unsigned char +sep_context_state(const struct seppkg *p, int context) +{ + if (context < 0 || context >= p->context_cap) return 0; + return p->context_state[context]; +} + +static int +sep_set_context_state(struct seppkg *p, int context, unsigned char state) +{ + if (context < 0 || context == INT_MAX) return sep_fail_size(); + if (sep_reserve((void **)&p->context_state, &p->context_cap, + context + 1, sizeof *p->context_state) < 0) + return -1; + p->context_state[context] = state; + return 0; +} + +static int +sep_add_dep(struct sepgraph *g, int pi, int dep) +{ + struct seppkg *p = &g->pkg[pi]; + for (int i = 0; i < p->ndeps; i++) + if (p->deps[i] == dep) return 0; + if (p->ndeps == INT_MAX) return sep_fail_size(); + if (sep_reserve((void **)&p->deps, &p->depcap, p->ndeps + 1, + sizeof *p->deps) < 0) + return -1; + p->deps[p->ndeps++] = dep; + return 0; +} + #define SEP_MAXLFLAGS 32 #define SEP_ARTIFACT_MAX PATH_MAX struct seplinkflags { @@ -632,12 +752,15 @@ sep_sprintf(const char *fmt, ...) va_end(cp); if (n < 0) { va_end(ap); + sep_fail_size(); return NULL; } char *s = malloc((size_t)n + 1); + if (s == NULL) sep_fail_nomem(); if (s != NULL && vsnprintf(s, (size_t)n + 1, fmt, ap) != n) { free(s); s = NULL; + sep_fail_size(); } va_end(ap); return s; @@ -771,7 +894,10 @@ sep_storage_digest(const struct seppkg *p) sep_sha256_write(&s, p->canon, strlen(p->canon)); sep_sha256_sum(&s, sum); char *out = malloc(80); - if (out == NULL) return NULL; + if (out == NULL) { + sep_fail_nomem(); + return NULL; + } int n = snprintf(out, 16, "__wwpkg.v%d.r%d.h", p->variant, p->role); if (n < 0 || n >= 16) { free(out); return NULL; } for (int i = 0; i < 32; i++) { @@ -795,7 +921,9 @@ sep_variant_path(int variant, const char *base) { if (variant == SEP_VARIANT_EXTERNAL) return sep_sprintf("%s_test", base); - return strdup(base); + char *path = strdup(base); + if (path == NULL) sep_fail_nomem(); + return path; } static void @@ -925,7 +1053,11 @@ sep_bind_import_base(struct sepgraph *g, int pi, const char *base) } } p->import_base = strdup(base); - if (p->import_base == NULL) { free(path); return -1; } + if (p->import_base == NULL) { + sep_fail_nomem(); + free(path); + return -1; + } free(p->path); p->path = path; return 0; @@ -938,6 +1070,7 @@ sep_find_or_add_variant(struct sepgraph *g, const char *path, { char *canon = realpath(entry, NULL); if (canon == NULL) { + if (errno == ENOMEM) return sep_fail_nomem(); fprintf(stderr, "ww: cannot canonicalize package %s\n", entry); return -1; } @@ -1024,9 +1157,13 @@ sep_find_or_add_variant(struct sepgraph *g, const char *path, return i; } } - if (g->n >= SEP_MAXPKG) { - fprintf(stderr, "ww: too many packages (limit %d)\n", - SEP_MAXPKG); + if (g->n == INT_MAX) { + sep_fail_size(); + free(incoming_path); + free(canon); + return -1; + } + if (sep_reserve_packages(g, g->n + 1) < 0) { free(incoming_path); free(canon); return -1; @@ -1039,6 +1176,7 @@ sep_find_or_add_variant(struct sepgraph *g, const char *path, p->canon = canon; free(incoming_path); if (p->path == NULL || p->entry == NULL) { + sep_fail_nomem(); free(p->path); free(p->entry); free(p->canon); g->n--; return -1; @@ -1050,7 +1188,12 @@ sep_find_or_add_variant(struct sepgraph *g, const char *path, p->emit_context = -1; if (test_package != NULL) { p->test_package = strdup(test_package); - if (p->test_package == NULL) { g->n--; return -1; } + if (p->test_package == NULL) { + sep_fail_nomem(); + sep_pkg_free_fields(p); + g->n--; + return -1; + } } if (is_dir) { const char *inherited = base; @@ -1066,11 +1209,20 @@ sep_find_or_add_variant(struct sepgraph *g, const char *path, } if (inherited[0] != '\0' && sep_bind_import_base(g, ni, inherited) < 0) { + sep_pkg_free_fields(p); g->n--; return -1; } } else { - if (artifact != NULL) p->artifact = strdup(artifact); + if (artifact != NULL) { + p->artifact = strdup(artifact); + if (p->artifact == NULL) { + sep_fail_nomem(); + sep_pkg_free_fields(p); + g->n--; + return -1; + } + } } return ni; } @@ -1091,32 +1243,39 @@ sep_find_or_add_role(struct sepgraph *g, const char *path, const char *entry, SEP_VARIANT_PRODUCTION, NULL, role, artifact, 0); } +static void +sep_pkg_free_fields(struct seppkg *p) +{ + for (int j = 0; j < p->nsources; j++) free(p->sources[j]); + free(p->sources); + for (int j = 0; j < p->bindings.n; j++) free(p->bindings.paths[j]); + free(p->bindings.paths); + free(p->context_state); + free(p->deps); + free(p->path); + free(p->import_base); + free(p->entry); + free(p->canon); + free(p->artifact); + free(p->storage); + free(p->name); + free(p->test_package); + memset(p, 0, sizeof *p); +} + /* Release the one package-owned directory-membership list. Every graph exit * funnels through this function; regular-file nodes own no source list. */ static void sep_graph_free(struct sepgraph *g) { if (g == NULL) return; - for (int i = 0; i < g->n; i++) { - for (int j = 0; j < g->pkg[i].nsources; j++) - free(g->pkg[i].sources[j]); - free(g->pkg[i].sources); - for (int j = 0; j < g->pkg[i].bindings.n; j++) - free(g->pkg[i].bindings.paths[j]); - free(g->pkg[i].bindings.paths); - free(g->pkg[i].path); - free(g->pkg[i].import_base); - free(g->pkg[i].entry); - free(g->pkg[i].canon); - free(g->pkg[i].artifact); - free(g->pkg[i].storage); - free(g->pkg[i].name); - free(g->pkg[i].test_package); - } + for (int i = 0; i < g->n; i++) sep_pkg_free_fields(&g->pkg[i]); for (int i = 0; i < g->ncontext; i++) { free(g->context[i].root); free(g->context[i].searchpath); } + free(g->context); + free(g->pkg); free(g); } @@ -1140,15 +1299,23 @@ sep_context_for(struct sepgraph *g, const char *root, free(searchpath); return i; } - if (g->ncontext >= SEP_MAXCONTEXT) { - fprintf(stderr, "ww: too many package import contexts\n"); + if (g->ncontext == INT_MAX) { + sep_fail_size(); + free(searchpath); + return -1; + } + if (sep_reserve_contexts(g, g->ncontext + 1) < 0) { free(searchpath); return -1; } struct sepcontext *c = &g->context[g->ncontext]; c->root = strdup(root); c->searchpath = searchpath; - if (c->root == NULL) { free(searchpath); return -1; } + if (c->root == NULL) { + sep_fail_nomem(); + free(searchpath); + return -1; + } return g->ncontext++; } @@ -1188,6 +1355,7 @@ sep_assign_storage(struct seppkg *p, const char *scratch) if (strlen(base) + strlen(".unit.new") <= SEP_NAME_MAX && need <= SEP_ARTIFACT_MAX) { p->storage = strdup(base); + if (p->storage == NULL) sep_fail_nomem(); p->storage_hashed = 0; } else { p->storage = sep_storage_digest(p); @@ -1315,7 +1483,11 @@ sep_slurp(const char *path, char **out, u64 *len) return -1; } char *buf = malloc((size_t)n + 1); - if (buf == NULL) { fclose(f); return -1; } + if (buf == NULL) { + sep_fail_nomem(); + fclose(f); + return -1; + } if (fread(buf, 1, (size_t)n, f) != (size_t)n) { free(buf); fclose(f); @@ -1370,8 +1542,12 @@ sep_binding_add(struct ImportSet *bindings, char kind, const char *name, const char *target) { size_t nn = strlen(name), tn = target ? strlen(target) : 0; + if (tn > (size_t)-1 - 4 || nn > (size_t)-1 - 4 - tn) { + sep_fail_size(); + return -1; + } char *binding = malloc(nn + tn + 4); - if (binding == NULL) return -1; + if (binding == NULL) return sep_fail_nomem(); binding[0] = kind; binding[1] = ':'; memcpy(binding + 2, name, nn); @@ -1382,13 +1558,15 @@ sep_binding_add(struct ImportSet *bindings, char kind, const char *name, free(binding); return 0; } - if (bindings->n == bindings->cap) { - int cap = bindings->cap ? bindings->cap * 2 : 8; - char **paths = realloc(bindings->paths, - (size_t)cap * sizeof *paths); - if (paths == NULL) { free(binding); return -1; } - bindings->paths = paths; - bindings->cap = cap; + if (bindings->n == INT_MAX) { + sep_fail_size(); + free(binding); + return -1; + } + if (sep_reserve((void **)&bindings->paths, &bindings->cap, + bindings->n + 1, sizeof *bindings->paths) < 0) { + free(binding); + return -1; } bindings->paths[bindings->n++] = binding; return 0; @@ -1402,10 +1580,10 @@ sep_binding_add(struct ImportSet *bindings, char kind, const char *name, static int sep_scan_file(struct sepgraph *g, int pi, const char *file, const char *searchpath, struct ImportSet *filevisit, - struct ImportSet *bindings, int owned_source) + struct ImportSet *bindings, int owned_source) { if (import_seen(filevisit, file)) return 0; - import_add(filevisit, file); + if (import_add(filevisit, file) < 0) return -1; char *buf; u64 len; if (sep_slurp(file, &buf, &len) < 0) { @@ -1438,6 +1616,7 @@ sep_scan_file(struct sepgraph *g, int pi, const char *file, if (pkg->name == NULL) { pkg->name = strdup(declared); if (pkg->name == NULL) { + sep_fail_nomem(); freearena(a); free(buf); return -1; @@ -1467,9 +1646,24 @@ sep_scan_file(struct sepgraph *g, int pi, const char *file, int nuse = 0; for (Node *u = imports->list; u; u = u->next) - if (u->kind == N_USE) nuse++; + if (u->kind == N_USE) { + if (nuse == INT_MAX) { + sep_fail_size(); + freearena(a); + free(buf); + return -1; + } + nuse++; + } + if ((size_t)nuse > (size_t)-1 / sizeof(Node *)) { + sep_fail_size(); + freearena(a); + free(buf); + return -1; + } Node **uses = nuse ? malloc((size_t)nuse * sizeof *uses) : NULL; if (nuse && uses == NULL) { + sep_fail_nomem(); freearena(a); free(buf); return -1; @@ -1565,13 +1759,7 @@ sep_scan_file(struct sepgraph *g, int pi, const char *file, * 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; } - int seen = 0; - for (int k = 0; k < g->pkg[pi].ndeps; k++) - if (g->pkg[pi].deps[k] == di) { seen = 1; break; } - if (!seen) { - if (g->pkg[pi].ndeps >= SEP_MAXPKG) { rc = -1; break; } - g->pkg[pi].deps[g->pkg[pi].ndeps++] = di; - } + if (sep_add_dep(g, pi, di) < 0) { rc = -1; break; } } } free(uses); @@ -1624,6 +1812,7 @@ sep_add_generated_main(struct sepgraph *g, struct sepproduct *product, char *canon = sep_sprintf("%s#%s-test-main", g->pkg[variant].canon, kind); char *entry = strdup(g->pkg[variant].entry); + if (entry == NULL) sep_fail_nomem(); if (path == NULL || artifact == NULL || canon == NULL || entry == NULL) { free(path); free(artifact); free(canon); free(entry); return -1; @@ -1650,8 +1839,12 @@ sep_add_generated_main(struct sepgraph *g, struct sepproduct *product, free(path); free(artifact); free(canon); free(entry); return -1; } - if (g->n >= SEP_MAXPKG) { - fprintf(stderr, "ww: too many packages (limit %d)\n", SEP_MAXPKG); + if (g->n == INT_MAX) { + sep_fail_size(); + free(path); free(artifact); free(canon); free(entry); + return -1; + } + if (sep_reserve_packages(g, g->n + 1) < 0) { free(path); free(artifact); free(canon); free(entry); return -1; } @@ -1662,7 +1855,10 @@ sep_add_generated_main(struct sepgraph *g, struct sepproduct *product, p->canon = canon; p->entry = entry; p->name = strdup("main"); - if (p->name == NULL) return -1; + if (p->name == NULL) { + sep_fail_nomem(); + goto fail; + } p->variant = SEP_VARIANT_TEST_MAIN; p->role = SEP_ROLE_GENERATED_MAIN; p->root = 1; @@ -1670,10 +1866,11 @@ sep_add_generated_main(struct sepgraph *g, struct sepproduct *product, p->generated_main = 1; p->loaded = 1; p->emit_context = product->context; - p->context_state[product->context] = 2; - p->deps[p->ndeps++] = variant; - if (support >= 0 && support != variant) - p->deps[p->ndeps++] = support; + if (sep_set_context_state(p, product->context, 2) < 0 + || sep_add_dep(g, g->n, variant) < 0 + || (support >= 0 && support != variant + && sep_add_dep(g, g->n, support) < 0)) + goto fail; for (int i = 1; i < p->ndeps; i++) { int v = p->deps[i]; int j = i; @@ -1684,24 +1881,26 @@ sep_add_generated_main(struct sepgraph *g, struct sepproduct *product, p->deps[j] = v; } return g->n++; +fail: + free(p->path); + free(p->artifact); + free(p->canon); + free(p->entry); + free(p->name); + free(p->context_state); + free(p->deps); + memset(p, 0, sizeof *p); + return -1; } -/* Load one canonical package under one selected-root resolution context. - * Source membership is owned once, but a shared package's imports are checked - * under every context that reaches it. The first canonical binding set owns - * file-body composition; every later set must be identical. */ +/* Load one action's owned sources and direct bindings under one context. + * Dependency descent is iterative below so a valid deep graph consumes the + * growable frame vector rather than the process call stack. */ static int -sep_load_pkg(struct sepgraph *g, int pi, int context) +sep_prepare_pkg_context(struct sepgraph *g, int pi, int context) { - if (context < 0 || context >= g->ncontext) return -1; - if (g->pkg[pi].test_support - && g->support_context >= 0) - context = g->support_context; - if (g->pkg[pi].context_state[context] == 2) - return g->pkg[pi].failed ? -1 : 0; - if (g->pkg[pi].context_state[context] == 1) - return 0; - g->pkg[pi].context_state[context] = 1; + if (sep_set_context_state(&g->pkg[pi], context, 1) < 0) + return -1; const char *searchpath = g->context[context].searchpath; struct ImportSet fv = {0}, bindings = {0}; int rc = 0; @@ -1787,23 +1986,100 @@ sep_load_pkg(struct sepgraph *g, int pi, int context) /* Mark before recursion so a source cycle terminates here; topo emits the * stable cycle diagnostic after all direct bindings are known. */ g->pkg[pi].context_state[context] = 2; - for (int k = 0; k < g->pkg[pi].ndeps; k++) { - int dep = g->pkg[pi].deps[k]; - if (sep_load_pkg(g, dep, context) < 0) { - g->pkg[pi].failed = 1; - return -1; - } - if (dep != pi && sep_forbidden_command_import(g, pi, dep)) { - fprintf(stderr, - "ww: package %s is a program, not an importable package\n", - g->pkg[dep].path[0] ? g->pkg[dep].path : g->pkg[dep].canon); - g->pkg[pi].failed = 1; - return -1; - } - } return 0; } +struct seploadframe { + int pkg; + int context; + int next_dep; + int pending_dep; +}; + +/* Load one canonical package under one selected-root resolution context. + * Source membership is owned once, but a shared package's imports are checked + * under every context that reaches it. The first canonical binding set owns + * file-body composition; every later set must be identical. */ +static int +sep_load_pkg(struct sepgraph *g, int pi, int context) +{ + if (pi < 0 || pi >= g->n || context < 0 || context >= g->ncontext) + return -1; + if (g->pkg[pi].test_support && g->support_context >= 0) + context = g->support_context; + struct seploadframe *frames = NULL; + int nframe = 0, framecap = 0; + if (sep_reserve((void **)&frames, &framecap, 1, + sizeof *frames) < 0) + return -2; + frames[nframe++] = (struct seploadframe){ pi, context, -1, -1 }; + + while (nframe > 0) { + struct seploadframe *f = &frames[nframe - 1]; + if (f->next_dep < 0) { + unsigned char state = sep_context_state(&g->pkg[f->pkg], + f->context); + if (state == 2) { + if (g->pkg[f->pkg].failed) goto failed; + nframe--; + continue; + } + if (state == 1) { + nframe--; + continue; + } + if (sep_prepare_pkg_context(g, f->pkg, f->context) < 0) + goto failed; + f->next_dep = 0; + } + if (f->pending_dep >= 0) { + int dep = f->pending_dep; + f->pending_dep = -1; + if (dep != f->pkg + && sep_forbidden_command_import(g, f->pkg, dep)) { + fprintf(stderr, + "ww: package %s is a program, not an importable package\n", + g->pkg[dep].path[0] ? g->pkg[dep].path + : g->pkg[dep].canon); + goto failed; + } + } + if (f->next_dep >= g->pkg[f->pkg].ndeps) { + nframe--; + continue; + } + int dep = g->pkg[f->pkg].deps[f->next_dep++]; + f->pending_dep = dep; + int child_context = f->context; + if (g->pkg[dep].test_support && g->support_context >= 0) + child_context = g->support_context; + if (nframe == INT_MAX) { + sep_fail_size(); + for (int i = 0; i < nframe; i++) + g->pkg[frames[i].pkg].failed = 1; + free(frames); + return -2; + } + if (sep_reserve((void **)&frames, &framecap, nframe + 1, + sizeof *frames) < 0) { + for (int i = 0; i < nframe; i++) + g->pkg[frames[i].pkg].failed = 1; + free(frames); + return -2; + } + frames[nframe++] = (struct seploadframe){ + dep, child_context, -1, -1 }; + } + free(frames); + return 0; + +failed: + for (int i = 0; i < nframe; i++) + g->pkg[frames[i].pkg].failed = 1; + free(frames); + return sep_fatal_allocation ? -2 : -1; +} + static int sep_import_component(const char *s, size_t n) { @@ -1851,7 +2127,7 @@ sep_reverse_import_base(const struct sepgraph *g, const struct seppkg *pkg, const char *e = strchr(p, ':'); size_t n = e ? (size_t)(e - p) : strlen(p); char *root = malloc(n + 1); - if (root == NULL) return -1; + if (root == NULL) return sep_fail_nomem(); memcpy(root, p, n); root[n] = '\0'; char *canon = n == 0 ? NULL : realpath(root, NULL); @@ -1903,7 +2179,10 @@ sep_ordinary_declared_name(const struct seppkg *p) n -= 5; } char *out = malloc(n + 1); - if (out == NULL) return NULL; + if (out == NULL) { + sep_fail_nomem(); + return NULL; + } memcpy(out, p->name, n); out[n] = '\0'; return out; @@ -1916,10 +2195,21 @@ sep_local_import_base(const struct seppkg *p) { char *leaf = sep_ordinary_declared_name(p); if (leaf == NULL) return NULL; - size_t outsz = strlen(SEP_LOCAL_IMPORT_PREFIX) + 3 - + 4 * strlen(p->canon) + strlen(leaf) + 1; + size_t prefix = strlen(SEP_LOCAL_IMPORT_PREFIX); + size_t canon_len = strlen(p->canon), leaf_len = strlen(leaf); + if (leaf_len > (size_t)-1 - prefix - 4 + || canon_len > ((size_t)-1 - prefix - 4 - leaf_len) / 4) { + sep_fail_size(); + free(leaf); + return NULL; + } + size_t outsz = prefix + 3 + 4 * canon_len + leaf_len + 1; char *out = malloc(outsz); - if (out == NULL) { free(leaf); return NULL; } + if (out == NULL) { + sep_fail_nomem(); + free(leaf); + return NULL; + } size_t off = 0; int n = snprintf(out, outsz, "%s.p", SEP_LOCAL_IMPORT_PREFIX); if (n < 0 || (size_t)n >= outsz) { free(leaf); free(out); return NULL; } @@ -1944,7 +2234,7 @@ sep_local_import_base(const struct seppkg *p) out[off++] = hex[c & 15]; } } - size_t ln = strlen(leaf); + size_t ln = leaf_len; if (off + 1 + ln + 1 > outsz) { free(leaf); free(out); return NULL; } out[off++] = '.'; memcpy(out + off, leaf, ln + 1); @@ -1966,10 +2256,10 @@ sep_finalize_directory_identities(struct sepgraph *g) || p->import_base != NULL) continue; for (int ci = 0; ci < g->ncontext; ci++) { - if (p->context_state[ci] != 2) continue; + if (sep_context_state(p, ci) != 2) continue; size_t n = strlen(p->canon) + 1; char *candidate = malloc(n); - if (candidate == NULL) return -1; + if (candidate == NULL) return sep_fail_nomem(); int found = sep_reverse_import_base(g, p, ci, candidate, n); if (found < 0) { free(candidate); return -1; } if (found > 0 && sep_bind_import_base(g, pi, candidate) < 0) { @@ -2035,35 +2325,71 @@ sep_finalize_directory_identities(struct sepgraph *g) return 0; } -/* DFS post-order over the dep DAG → reverse-topo (deps before importer), - * cite Hare gather (deps.ha:123). Tri-color: a gray back-edge is a loud - * dep-cycle reject naming the chain (Hare deps.ha:243); `stack[0..depth)` - * is the live DFS path, so the cycle runs from pi's first occurrence on - * it to the top, closing back on pi. */ +struct septopoframe { + int pkg; + int next_dep; +}; + +/* Iterative DFS post-order over the dep DAG → reverse-topo (deps before + * importer). Tri-color and `stack[0..nframe)` retain the exact live path and + * cycle diagnostic without tying valid graph depth to the C call stack. */ static int sep_topo_visit(struct sepgraph *g, int pi, int *order, int *no, int *stack, int depth) { + (void)depth; if (g->pkg[pi].color == 2) return 0; if (g->pkg[pi].color == 1) { - int j = 0; - while (j < depth && stack[j] != pi) j++; - fprintf(stderr, "ww: dependency cycle: "); - for (int s = j; s < depth; s++) - fprintf(stderr, "%s -> ", g->pkg[stack[s]].path[0] - ? g->pkg[stack[s]].path : "(root)"); - fprintf(stderr, "%s\n", + fprintf(stderr, "ww: dependency cycle: %s\n", g->pkg[pi].path[0] ? g->pkg[pi].path : "(root)"); return -1; } + struct septopoframe *frames = NULL; + int nframe = 0, framecap = 0; + if (sep_reserve((void **)&frames, &framecap, 1, + sizeof *frames) < 0) + return -2; g->pkg[pi].color = 1; - stack[depth] = pi; - for (int k = 0; k < g->pkg[pi].ndeps; k++) - if (sep_topo_visit(g, g->pkg[pi].deps[k], order, no, - stack, depth + 1) < 0) - return -1; - g->pkg[pi].color = 2; - order[(*no)++] = pi; + stack[0] = pi; + frames[nframe++] = (struct septopoframe){ pi, 0 }; + while (nframe > 0) { + struct septopoframe *f = &frames[nframe - 1]; + if (f->next_dep < g->pkg[f->pkg].ndeps) { + int dep = g->pkg[f->pkg].deps[f->next_dep++]; + if (g->pkg[dep].color == 2) continue; + if (g->pkg[dep].color == 1) { + int j = 0; + while (j < nframe && stack[j] != dep) j++; + fprintf(stderr, "ww: dependency cycle: "); + for (int s = j; s < nframe; s++) + fprintf(stderr, "%s -> ", + g->pkg[stack[s]].path[0] + ? g->pkg[stack[s]].path : "(root)"); + fprintf(stderr, "%s\n", g->pkg[dep].path[0] + ? g->pkg[dep].path : "(root)"); + free(frames); + return -1; + } + if (nframe == INT_MAX) { + sep_fail_size(); + free(frames); + return -2; + } + if (sep_reserve((void **)&frames, &framecap, nframe + 1, + sizeof *frames) < 0) { + free(frames); + return -2; + } + g->pkg[dep].color = 1; + stack[nframe] = dep; + frames[nframe++] = (struct septopoframe){ dep, 0 }; + continue; + } + g->pkg[f->pkg].color = 2; + order[(*no)++] = f->pkg; + nframe--; + } + free(frames); return 0; } @@ -2394,7 +2720,8 @@ build_one_sep_impl(const char *src, int entry_is_dir, const char *workdir, char *scratchout, size_t scratchoutsz, struct sepgraph **graphout) { - if (nproducts < 1 || nproducts > SEP_MAXPRODUCT) return 1; + sep_fatal_allocation = 0; + if (nproducts < 1) return 1; for (int i = 0; i < nproducts; i++) if (products[i].status != NULL && unlink(products[i].status) != 0 && errno != ENOENT) @@ -2515,11 +2842,13 @@ build_one_sep_impl(const char *src, int entry_is_dir, } struct sepgraph *g = calloc(1, sizeof *g); - if (g == NULL) return 1; + if (g == NULL) { + fprintf(stderr, "ww: out of memory\n"); + return 1; + } g->support_context = -1; if (graphout) *graphout = g; - int support_for[SEP_MAXPRODUCT]; - for (int i = 0; i < nproducts; i++) support_for[i] = -1; + for (int i = 0; i < nproducts; i++) products[i].support = -1; for (int i = 0; i < nproducts; i++) { const char *entry = products[i].dir != NULL ? products[i].dir : src; @@ -2603,7 +2932,7 @@ build_one_sep_impl(const char *src, int entry_is_dir, if (root_is_support && strcmp(test_support_module, "test") == 0 && products[i].variant != SEP_VARIANT_EXTERNAL) { - support_for[i] = root; + products[i].support = root; continue; } int ti; @@ -2616,7 +2945,7 @@ build_one_sep_impl(const char *src, int entry_is_dir, tdir); if (ti < 0) return 1; g->pkg[ti].test_support = 1; - support_for[i] = ti; + products[i].support = ti; } free(tc); } @@ -2627,17 +2956,15 @@ build_one_sep_impl(const char *src, int entry_is_dir, * exception: keep compiler-owned test-main synthesis in that action. * Its support export is still an exact direct input. */ if (is_test && !entry_is_dir) { - int support = support_for[i]; - if (support >= 0 && support != root) { - int seen = 0; - for (int k = 0; k < g->pkg[root].ndeps; k++) - if (g->pkg[root].deps[k] == support) seen = 1; - if (!seen) - g->pkg[root].deps[g->pkg[root].ndeps++] = support; - } + int support = products[i].support; + if (support >= 0 && support != root + && sep_add_dep(g, root, support) < 0) + return 1; g->pkg[root].link_entry = 1; } - if (sep_load_pkg(g, root, products[i].context) < 0) { + int lr = sep_load_pkg(g, root, products[i].context); + if (lr == -2) return 1; + if (lr < 0) { g->pkg[root].failed = 1; continue; } @@ -2651,10 +2978,12 @@ build_one_sep_impl(const char *src, int entry_is_dir, if (is_test && entry_is_dir) { for (int i = 0; i < nproducts; i++) { int variant = products[i].variant_root; - int support = support_for[i]; - if (support >= 0 && support != variant - && sep_load_pkg(g, support, products[i].context) < 0) - g->pkg[variant].failed = 1; + int support = products[i].support; + if (support >= 0 && support != variant) { + int lr = sep_load_pkg(g, support, products[i].context); + if (lr == -2) return 1; + if (lr < 0) g->pkg[variant].failed = 1; + } } } if (g->identity_failed) return 1; @@ -2662,7 +2991,7 @@ build_one_sep_impl(const char *src, int entry_is_dir, if (is_test && entry_is_dir) { for (int i = 0; i < nproducts; i++) { int variant = products[i].variant_root; - int support = support_for[i]; + int support = products[i].support; if (g->pkg[variant].failed || (support >= 0 && g->pkg[support].failed)) { products[i].root = variant; @@ -2690,6 +3019,7 @@ build_one_sep_impl(const char *src, int entry_is_dir, int *stack = calloc((size_t)g->n, sizeof *stack); int norder = 0; if (order == NULL || stack == NULL) { + fprintf(stderr, "ww: out of memory\n"); free(stack); free(order); return 1; } /* Diagnose cycles per product before constructing the shared union. A @@ -2699,7 +3029,11 @@ build_one_sep_impl(const char *src, int entry_is_dir, if (g->pkg[root].failed) continue; for (int pi = 0; pi < g->n; pi++) g->pkg[pi].color = 0; int ignored = 0; - if (sep_topo_visit(g, root, order, &ignored, stack, 0) < 0 + int tr = sep_topo_visit(g, root, order, &ignored, stack, 0); + if (tr == -2) { + free(stack); free(order); return 1; + } + if (tr < 0 || sep_validate_module_closure(g, order, ignored, 1) < 0) g->pkg[root].failed = 1; } @@ -2769,7 +3103,13 @@ build_one_sep_impl(const char *src, int entry_is_dir, } continue; } - size_t cargvcap = (size_t)(12 + 3 * g->pkg[pi].ndeps); + size_t cargvcap = 12; + if ((size_t)g->pkg[pi].ndeps > ((size_t)-1 - cargvcap) / 3) { + fprintf(stderr, "ww: package graph is too large\n"); + free(order); + return 1; + } + cargvcap += 3 * (size_t)g->pkg[pi].ndeps; char **cargv = calloc(cargvcap, sizeof *cargv); char (*importfiles)[SEP_ARTIFACT_MAX] = NULL; if (g->pkg[pi].ndeps > 0) @@ -2958,18 +3298,34 @@ build_one_sep_impl(const char *src, int entry_is_dir, int *linkorder = calloc((size_t)g->n, sizeof *linkorder); int *linkstack = calloc((size_t)g->n, sizeof *linkstack); int nlink = 0; - if (linkorder == NULL || linkstack == NULL - || sep_topo_visit(g, root, linkorder, &nlink, + if (linkorder == NULL || linkstack == NULL) { + fprintf(stderr, "ww: out of memory\n"); + free(linkstack); free(linkorder); return 1; + } + if (sep_topo_visit(g, root, linkorder, &nlink, linkstack, 0) < 0) { free(linkstack); free(linkorder); return 1; } free(linkstack); - size_t largvcap = (size_t)(3 + nlink + nrt - + 2 * nlibdirs + 2 * nlibs + 1); + size_t largvcap = 4; + if ((size_t)nlink > (size_t)-1 - largvcap + || (size_t)nrt > (size_t)-1 - largvcap - (size_t)nlink + || (size_t)nlibdirs > ((size_t)-1 - largvcap + - (size_t)nlink - (size_t)nrt) / 2 + || (size_t)nlibs > ((size_t)-1 - largvcap + - (size_t)nlink - (size_t)nrt + - 2 * (size_t)nlibdirs) / 2) { + fprintf(stderr, "ww: package graph is too large\n"); + free(linkorder); + return 1; + } + largvcap += (size_t)nlink + (size_t)nrt + + 2 * (size_t)nlibdirs + 2 * (size_t)nlibs; char **largv = calloc(largvcap, sizeof *largv); char (*linkpaths)[SEP_ARTIFACT_MAX] = calloc((size_t)nlink, sizeof *linkpaths); if (largv == NULL || linkpaths == NULL) { + fprintf(stderr, "ww: out of memory\n"); free(linkpaths); free(largv); free(linkorder); return 1; } @@ -3046,6 +3402,7 @@ build_one_sep(const char *src, int entry_is_dir, const char *root_identity, .variant = root_variant, .root = -1, .variant_root = -1, + .support = -1, }; if (!package_only && !entry_is_dir) product.artifact = "__root"; @@ -3203,6 +3560,28 @@ include_append(const char *cmd, char *incs, size_t incsz, const char *dir) return 0; } +static int +include_buffer_for_args(int argc, char **argv, char **out, size_t *outsz) +{ + size_t n = 1; + for (int i = 0; i < argc; i++) { + size_t an = strlen(argv[i]); + if (n == (size_t)-1 || an > (size_t)-1 - n - 1) { + fprintf(stderr, "ww: package graph is too large\n"); + return -1; + } + n += an + 1; + } + char *p = calloc(n, 1); + if (p == NULL) { + fprintf(stderr, "ww: out of memory\n"); + return -1; + } + *out = p; + *outsz = n; + return 0; +} + /* Returns the index past the last arg consumed for positionals (so callers * can pick up trailing args), or -1 if a flag is missing its argument * (diagnostic already emitted). `cmd` names the subcommand for the @@ -3460,12 +3839,11 @@ static int do_test(int argc, char **argv) { const char *src = NULL; - struct sepproduct products[SEP_MAXPRODUCT]; - int nproducts = 0; - size_t incsz = 1; - for (int i = 0; i < argc; i++) incsz += strlen(argv[i]) + 1; - char incs[incsz]; - memset(incs, 0, sizeof incs); + struct sepproduct *products = NULL; + int nproducts = 0, productcap = 0; + size_t incsz = 0; + char *incs = NULL; + if (include_buffer_for_args(argc, argv, &incs, &incsz) < 0) return 1; /* -c (Go's `go test -c`) builds the test binary without running it. * -S + -o stops after the lib/test-inclusive package `.s` * outputs are emitted. Both routes use build_one_sep's is_test bundle @@ -3505,7 +3883,7 @@ do_test(int argc, char **argv) } dir = argv[++i]; } - if (include_append("test", incs, sizeof incs, dir) < 0) + if (include_append("test", incs, incsz, dir) < 0) return 2; } else if (strcmp(argv[i], "-c") == 0) { compileonly = 1; @@ -3519,7 +3897,7 @@ do_test(int argc, char **argv) } request_identity = argv[++i]; } else if (strcmp(argv[i], "--ww-package-test") == 0) { - if (i + 5 >= argc || nproducts >= SEP_MAXPRODUCT) { + if (i + 5 >= argc) { fprintf(stderr, "ww test: --ww-package-test needs kind, package, directory, output, and status\n"); return 2; @@ -3549,6 +3927,13 @@ do_test(int argc, char **argv) "ww test: invalid --ww-package-test variant\n"); return 2; } + if (nproducts == INT_MAX) { + sep_fail_size(); + return 1; + } + if (sep_reserve((void **)&products, &productcap, + nproducts + 1, sizeof *products) < 0) + return 1; products[nproducts].dir = dir; products[nproducts].out = output; products[nproducts].test_package = name; @@ -3557,6 +3942,7 @@ do_test(int argc, char **argv) products[nproducts].variant = variant; products[nproducts].root = -1; products[nproducts].variant_root = -1; + products[nproducts].support = -1; nproducts++; } else if (strcmp(argv[i], "-S") == 0) { emit_asm = 1; @@ -3709,9 +4095,11 @@ do_test(int argc, char **argv) "ww test: package-test products need -c\n"); return 2; } - return build_package_tests(resolved, request_identity, - incs, workdir, - products, nproducts); + int r = build_package_tests(resolved, request_identity, + incs, workdir, products, nproducts); + free(products); + free(incs); + return r; } return exec_package_tests(argc, argv, src, resolved, request_identity != NULL ? request_identity : target, 0); @@ -3877,8 +4265,11 @@ do_test(int argc, char **argv) "ww test: package-test products need -c\n"); return 2; } - return build_package_tests(target, request_identity, incs, workdir, + int r = build_package_tests(target, request_identity, incs, workdir, products, nproducts); + free(products); + free(incs); + return r; } return exec_package_tests(argc, argv, src, NULL, request_identity, src == NULL); diff --git a/internal/wwpackage/package.ww b/internal/wwpackage/package.ww index 07193cd1..ec384f1f 100644 --- a/internal/wwpackage/package.ww +++ b/internal/wwpackage/package.ww @@ -35,6 +35,7 @@ type pkggroup = struct { runout: str, runerr: str, state: i32, + runstartfailed: bool, runres: exec.result, }; @@ -52,6 +53,44 @@ type pkgplan = struct { buildres: exec.result, }; +def PKG_COUNT_MAX: i32 = 2147483647; +def PKG_INITIAL_CAP: i32 = 8; + +fn pkgallocstrs(cap: i32) ([]str | nomem) = { + let value: []str = alloc([], cap: u64)?; + return value; +}; + +fn pkgallocbytes(cap: i32) ([]u8 | nomem) = { + let value: []u8 = alloc([], cap: u64)?; + return value; +}; + +fn pkgallocsources(cap: i32) ([]pkgsource | nomem) = { + let value: []pkgsource = alloc([], cap: u64)?; + return value; +}; + +fn pkgallocfolders(cap: i32) ([]pkgfolder | nomem) = { + let value: []pkgfolder = alloc([], cap: u64)?; + return value; +}; + +fn pkgallocgroups(cap: i32) ([]pkggroup | nomem) = { + let value: []pkggroup = alloc([], cap: u64)?; + return value; +}; + +fn pkgallocplans(cap: i32) ([]pkgplan | nomem) = { + let value: []pkgplan = alloc([], cap: u64)?; + return value; +}; + +fn pkgallocprocesses(cap: i32) ([]exec.process | nomem) = { + let value: []exec.process = alloc([], cap: u64)?; + return value; +}; + // Product and directory-plan process states for the bounded coordinator. def PKGQUEUED: i32 = 0; def PKGBUILDING: i32 = 1; @@ -63,13 +102,88 @@ def pkgpoll: time.duration = 1000000i64: time.duration; type pkgdiscover = struct { paths: []str, errors: i32, + fatal: bool, +}; + +fn pkggrowcap(current: i32, need: i32) i32 = { + if (need < 0) { return -1; }; + if (need <= current) { return current; }; + let cap: i32 = current; + if (cap == 0) { cap = PKG_INITIAL_CAP; }; + for (cap < need) { + if (cap > PKG_COUNT_MAX / 2) { + cap = PKG_COUNT_MAX; + break; + }; + cap *= 2; + }; + if (cap < need) { return -1; }; + return cap; +}; + +fn pkgappenddiscovered(st: *pkgdiscover, path: str) bool = { + if (st.paths.len == PKG_COUNT_MAX) { + pkgputln(os.STDERR_FILENO, + "wwtest package: package graph is too large"); + st.errors += 1; + st.fatal = true; + return false; + }; + let need: i32 = st.paths.len + 1; + if (need > st.paths.cap) { + let cap: i32 = pkggrowcap(st.paths.cap, need); + if (cap < 0) { + pkgputln(os.STDERR_FILENO, + "wwtest package: package graph is too large"); + st.errors += 1; + st.fatal = true; + return false; + }; + let allocation: ([]str | nomem) = pkgallocstrs(cap); + let next: []str; + match (allocation) { + case let value: []str => next = value; + case nomem => { + pkgputln(os.STDERR_FILENO, + "wwtest package: out of memory"); + st.errors += 1; + st.fatal = true; + return false; + }; + }; + let n: i32 = st.paths.len; + next.len = cap; + let i: i32 = 0; + for (i < n) { next[i] = st.paths[i]; i += 1; }; + next.len = n; + if (st.paths.ptr != nil) { + os.free(st.paths.ptr: *void, + (st.paths.cap: u64) * (size(str): u64)); + }; + st.paths = next; + }; + append(st.paths, path); + return true; }; // Preserve the caller's toolchain environment while pinning the locale and // temporary directory used by the current build plan or test product. -fn toolenv(tmpdir: str) []str = { +fn toolenv(tmpdir: str, out: *[]str) bool = { let inherited: []str = os.getenvs(); - let env: []str = alloc([], (inherited.len + 2): u64)!; + if (inherited.len > PKG_COUNT_MAX - 2) { + pkgputln(os.STDERR_FILENO, + "wwtest package: package graph is too large"); + return false; + }; + let allocation: ([]str | nomem) = pkgallocstrs(inherited.len + 2); + let env: []str; + match (allocation) { + case let value: []str => env = value; + case nomem => { + pkgputln(os.STDERR_FILENO, "wwtest package: out of memory"); + return false; + }; + }; let i: i32 = 0; for (i < inherited.len) { if (!strings.hasprefix(inherited[i], "TMPDIR=") @@ -80,7 +194,8 @@ fn toolenv(tmpdir: str) []str = { }; append(env, "LC_ALL=C"); append(env, strings.concat("TMPDIR=", tmpdir)); - return env; + *out = env; + return true; }; fn pkgwrite(fd: i32, s: str) bool = { @@ -125,8 +240,24 @@ fn pkgread(path: str, out: *str) bool = { case let v: i64 => n = v; case let e: os.oserror => { os.close(fd); return false; }; }; - if (n < 0i64) { os.close(fd); return false; }; - let b: []u8 = alloc([], (n + 1i64): u64)!; + if (n < 0i64 || n >= PKG_COUNT_MAX: i64) { + os.close(fd); + if (n >= PKG_COUNT_MAX: i64) { + pkgputln(os.STDERR_FILENO, + "wwtest package: package graph is too large"); + }; + return false; + }; + let allocation: ([]u8 | nomem) = pkgallocbytes((n + 1i64): i32); + let b: []u8; + match (allocation) { + case let value: []u8 => b = value; + case nomem => { + os.close(fd); + pkgputln(os.STDERR_FILENO, "wwtest package: out of memory"); + return false; + }; + }; b.len = (n + 1i64): i32; let rr: (i64 | os.oserror) = os.readall(fd, b.ptr, n: u64); os.close(fd); @@ -275,6 +406,7 @@ fn pkgkeepfile(name: str) bool = { // 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 = { + if (st.fatal) { return; }; let rootstat: os.filestat; match (os.lstat(&rootstat, path)) { case void => void; @@ -305,7 +437,18 @@ fn pkgdiscoverdir(path: str, st: *pkgdiscover, recurse: bool) void = { st.errors += 1; return; }; - let buf: []u8 = alloc([], 8192u64)!; + let bufallocation: ([]u8 | nomem) = pkgallocbytes(8192); + let buf: []u8; + match (bufallocation) { + case let value: []u8 => buf = value; + case nomem => { + os.close(fd); + pkgputln(os.STDERR_FILENO, "wwtest package: out of memory"); + st.errors += 1; + st.fatal = true; + return; + }; + }; buf.len = 8192; let n: i64 = os.getdents64(fd, buf.ptr, 8192u64); for (n > 0i64) { @@ -335,7 +478,10 @@ fn pkgdiscoverdir(path: str, st: *pkgdiscover, recurse: bool) void = { "symlink source is not allowed"); st.errors += 1; } else if (pkgmodeis(fi.mode, os.mode.REG)) { - append(st.paths, child); + if (!pkgappenddiscovered(st, child)) { + os.close(fd); + return; + }; }; }; case let e: os.oserror => { @@ -351,6 +497,7 @@ fn pkgdiscoverdir(path: str, st: *pkgdiscover, recurse: bool) void = { case void => { if (pkgmodeis(fi.mode, os.mode.DIR)) { pkgdiscoverdir(child, st, true); + if (st.fatal) { os.close(fd); return; }; }; }; case let e: os.oserror => { @@ -484,7 +631,12 @@ fn pkgremoveall(path: str) bool = { if (!pkgmodeis(opened.mode, os.mode.DIR) || opened.inode != st.inode) { os.close(fd); return false; }; let ok: bool = true; - let buf: []u8 = alloc([], 8192u64)!; + let allocation: ([]u8 | nomem) = pkgallocbytes(8192); + let buf: []u8; + match (allocation) { + case let value: []u8 => buf = value; + case nomem => { os.close(fd); return false; }; + }; buf.len = 8192; let n: i64 = os.getdents64(fd, buf.ptr, 8192u64); for (n > 0i64) { @@ -643,9 +795,28 @@ fn pkgreportcommand(kind: str, g: *pkggroup, r: *exec.result) void = { }; fn pkgstartbuild(p: *pkgplan, groups: []pkggroup, builder: str, includes: []str, - h: *exec.process) void = { - let ba: []str = alloc([], - (12 + (p.end - p.start) * 6 + includes.len * 2): u64)!; + h: *exec.process) bool = { + let nproducts: i32 = p.end - p.start; + let capacity: i32 = 12; + if (nproducts < 0 || nproducts > (PKG_COUNT_MAX - capacity) / 6) { + pkgputln(os.STDERR_FILENO, "wwtest package: package graph is too large"); + return false; + }; + capacity += nproducts * 6; + if (includes.len > (PKG_COUNT_MAX - capacity) / 2) { + pkgputln(os.STDERR_FILENO, "wwtest package: package graph is too large"); + return false; + }; + capacity += includes.len * 2; + let allocation: ([]str | nomem) = pkgallocstrs(capacity); + let ba: []str; + match (allocation) { + case let value: []str => ba = value; + case nomem => { + pkgputln(os.STDERR_FILENO, "wwtest package: out of memory"); + return false; + }; + }; append(ba, builder); append(ba, "test"); append(ba, "-c"); @@ -682,10 +853,12 @@ fn pkgstartbuild(p: *pkgplan, groups: []pkggroup, builder: str, includes: []str, append(ba, p.workdir); }; append(ba, p.dir); + let env: []str; + if (!toolenv(p.root, &env)) { return false; }; let bcmd: exec.command; bcmd.path = builder; bcmd.argv = ba; - bcmd.env = toolenv(p.root); + bcmd.env = env; bcmd.dir = ""; bcmd.stdoutpath = p.buildout; bcmd.stderrpath = p.builderr; @@ -693,21 +866,37 @@ fn pkgstartbuild(p: *pkgplan, groups: []pkggroup, builder: str, includes: []str, bcmd.deadline.nsec = 0i64; bcmd.grace = 0i64: time.duration; exec.start(h, &bcmd); + return true; }; fn pkgstartrun(g: *pkggroup, filters: []str, timeoutarg: str, - list: bool, h: *exec.process) void = { - let ra: []str = alloc([], (filters.len + 4): u64)!; + list: bool, h: *exec.process) bool = { + if (filters.len > PKG_COUNT_MAX - 4) { + pkgputln(os.STDERR_FILENO, + "wwtest package: package graph is too large"); + return false; + }; + let allocation: ([]str | nomem) = pkgallocstrs(filters.len + 4); + let ra: []str; + match (allocation) { + case let value: []str => ra = value; + case nomem => { + pkgputln(os.STDERR_FILENO, "wwtest package: out of memory"); + return false; + }; + }; append(ra, g.bin); append(ra, strings.concat("-package=", g.pkg)); if (list) { append(ra, "-list"); }; if (timeoutarg.len != 0) { append(ra, timeoutarg); }; let i: i32 = 0; for (i < filters.len) { append(ra, filters[i]); i += 1; }; + let env: []str; + if (!toolenv(g.root, &env)) { return false; }; let rcmd: exec.command; rcmd.path = g.bin; rcmd.argv = ra; - rcmd.env = toolenv(g.root); + rcmd.env = env; rcmd.dir = ""; rcmd.stdoutpath = g.runout; rcmd.stderrpath = g.runerr; @@ -715,6 +904,7 @@ fn pkgstartrun(g: *pkggroup, filters: []str, timeoutarg: str, rcmd.deadline.nsec = 0i64; rcmd.grace = 0i64: time.duration; exec.start(h, &rcmd); + return true; }; fn pkgproductbuilt(g: *pkggroup) bool = { @@ -735,6 +925,7 @@ fn pkgemitgroup(g: *pkggroup, compileonly: bool) bool = { pkgputln(os.STDOUT_FILENO, g.bin); return true; }; + if (g.runstartfailed) { return false; }; let runstdout: str; let runstderr: str; if (!pkgread(g.runout, &runstdout) || !pkgread(g.runerr, &runstderr)) { @@ -791,9 +982,39 @@ export fn packagecommand(args: []str) int = { let list: bool = false; let jobs: i32 = 1; let afterdash: bool = false; - let roots: []str = alloc([], 2u64)!; - let filters: []str = alloc([], (args.len + 1): u64)!; - let includes: []str = alloc([], (args.len + 1): u64)!; + if (args.len == PKG_COUNT_MAX) { + pkgputln(os.STDERR_FILENO, + "wwtest package: package graph is too large"); + return 1; + }; + let argcapacity: i32 = args.len + 1; + let rootallocation: ([]str | nomem) = pkgallocstrs(argcapacity); + let roots: []str; + match (rootallocation) { + case let value: []str => roots = value; + case nomem => { + pkgputln(os.STDERR_FILENO, "wwtest package: out of memory"); + return 1; + }; + }; + let filterallocation: ([]str | nomem) = pkgallocstrs(argcapacity); + let filters: []str; + match (filterallocation) { + case let value: []str => filters = value; + case nomem => { + pkgputln(os.STDERR_FILENO, "wwtest package: out of memory"); + return 1; + }; + }; + let includeallocation: ([]str | nomem) = pkgallocstrs(argcapacity); + let includes: []str; + match (includeallocation) { + case let value: []str => includes = value; + case nomem => { + pkgputln(os.STDERR_FILENO, "wwtest package: out of memory"); + return 1; + }; + }; let timeoutarg: str = ""; let outname: str = ""; let workroot: str = ""; @@ -925,9 +1146,10 @@ export fn packagecommand(args: []str) int = { }; let ds: pkgdiscover; - let discoveredpaths: []str = alloc([], 64u64)!; - ds.paths = discoveredpaths; + let emptypaths: []str; + ds.paths = emptypaths; ds.errors = 0; + ds.fatal = false; pkgdiscoverdir(discoverroot, &ds, recurse); if (ds.errors != 0) { return 1; }; pkgsort(ds.paths); @@ -941,7 +1163,16 @@ export fn packagecommand(args: []str) int = { return 1; }; - let srcs: []pkgsource = alloc([], ds.paths.len: u64)!; + let sourceallocation: ([]pkgsource | nomem) = + pkgallocsources(ds.paths.len); + let srcs: []pkgsource; + match (sourceallocation) { + case let value: []pkgsource => srcs = value; + case nomem => { + pkgputln(os.STDERR_FILENO, "wwtest package: out of memory"); + return 1; + }; + }; i = 0; for (i < ds.paths.len) { let body: str; @@ -959,7 +1190,16 @@ export fn packagecommand(args: []str) int = { i += 1; }; - let folders: []pkgfolder = alloc([], srcs.len: u64)!; + let folderallocation: ([]pkgfolder | nomem) = + pkgallocfolders(srcs.len); + let folders: []pkgfolder; + match (folderallocation) { + case let value: []pkgfolder => folders = value; + case nomem => { + pkgputln(os.STDERR_FILENO, "wwtest package: out of memory"); + return 1; + }; + }; i = 0; for (i < srcs.len) { let f: pkgfolder; @@ -977,7 +1217,16 @@ export fn packagecommand(args: []str) int = { i = f.end; }; - let groups: []pkggroup = alloc([], srcs.len: u64)!; + let groupallocation: ([]pkggroup | nomem) = + pkgallocgroups(srcs.len); + let groups: []pkggroup; + match (groupallocation) { + case let value: []pkggroup => groups = value; + case nomem => { + pkgputln(os.STDERR_FILENO, "wwtest package: out of memory"); + return 1; + }; + }; i = 0; for (i < folders.len) { let f: pkgfolder = folders[i]; @@ -1050,7 +1299,15 @@ export fn packagecommand(args: []str) int = { "wwtest package: cannot use -o with multiple packages"); return 2; }; - let plans: []pkgplan = alloc([], 1u64)!; + let planallocation: ([]pkgplan | nomem) = pkgallocplans(1); + let plans: []pkgplan; + match (planallocation) { + case let value: []pkgplan => plans = value; + case nomem => { + pkgputln(os.STDERR_FILENO, "wwtest package: out of memory"); + return 1; + }; + }; let plan: pkgplan; plan.dir = discoverroot; plan.sourceroot = canonicalroot; @@ -1081,19 +1338,48 @@ export fn packagecommand(args: []str) int = { // Build the complete request in one command-owned package universe. Once // the union build completes, successful products run under the same global // -j bound. Emission stays in byte-sorted group order below. - let handles: []exec.process = alloc([], plans.len: u64)!; + let handleallocation: ([]exec.process | nomem) = + pkgallocprocesses(plans.len); + let handles: []exec.process; + match (handleallocation) { + case let value: []exec.process => handles = value; + case nomem => { + pkgputln(os.STDERR_FILENO, "wwtest package: out of memory"); + if (!pkgremoveall(tmproot)) { + pkgput(os.STDERR_FILENO, + "wwtest package: cleanup failed; retained "); + pkgputln(os.STDERR_FILENO, tmproot); + }; + return 1; + }; + }; i = 0; for (i < plans.len) { let h: exec.process; append(handles, h); i += 1; }; - let runhandles: []exec.process = alloc([], groups.len: u64)!; + let runhandleallocation: ([]exec.process | nomem) = + pkgallocprocesses(groups.len); + let runhandles: []exec.process; + match (runhandleallocation) { + case let value: []exec.process => runhandles = value; + case nomem => { + pkgputln(os.STDERR_FILENO, "wwtest package: out of memory"); + if (!pkgremoveall(tmproot)) { + pkgput(os.STDERR_FILENO, + "wwtest package: cleanup failed; retained "); + pkgputln(os.STDERR_FILENO, tmproot); + }; + return 1; + }; + }; i = 0; for (i < groups.len) { let h: exec.process; append(runhandles, h); groups[i].state = PKGQUEUED; + groups[i].runstartfailed = false; i += 1; }; let planlaunched: i32 = 0; @@ -1118,10 +1404,15 @@ export fn packagecommand(args: []str) int = { g.state = PKGDONE; productcompleted += 1; } else { - pkgstartrun(g, filters, timeoutarg, list, - &runhandles[gi]); - g.state = PKGRUNNING; - active += 1; + if (!pkgstartrun(g, filters, timeoutarg, list, + &runhandles[gi])) { + g.runstartfailed = true; + g.state = PKGDONE; + productcompleted += 1; + } else { + g.state = PKGRUNNING; + active += 1; + }; }; filling = true; break; @@ -1130,8 +1421,15 @@ export fn packagecommand(args: []str) int = { }; }; if (!filling && planlaunched < plans.len) { - pkgstartbuild(&plans[planlaunched], groups, builder, includes, - &handles[planlaunched]); + if (!pkgstartbuild(&plans[planlaunched], groups, builder, + includes, &handles[planlaunched])) { + if (!pkgremoveall(tmproot)) { + pkgput(os.STDERR_FILENO, + "wwtest package: cleanup failed; retained "); + pkgputln(os.STDERR_FILENO, tmproot); + }; + return 1; + }; plans[planlaunched].state = PKGBUILDING; active += 1; planlaunched += 1; diff --git a/selfhost/cmd/ww/main.ww b/selfhost/cmd/ww/main.ww index e73b5281..c69299ff 100644 --- a/selfhost/cmd/ww/main.ww +++ b/selfhost/cmd/ww/main.ww @@ -29,6 +29,7 @@ def CMD_MAX: u64 = 8192u64; def SEP_LOCAL_IMPORT_PREFIX: str = "__wwlocal"; let selfpath: *u8; +let sepfatalallocation: bool; // Tool-local (NOT a lib wrapper): messages are built from many // fragments and we route through os.write to avoid libc stdio. @@ -39,6 +40,18 @@ fn cerr(m: str) void = { os.write(2, m.ptr, m.len: u64); }; +fn sepfailsize() bool = { + sepfatalallocation = true; + cerr("ww: package graph is too large\n"); + return false; +}; + +fn sepfailnomem() bool = { + sepfatalallocation = true; + cerr("ww: out of memory\n"); + return false; +}; + fn cerrnum(v: i32) void = { let digits: [16]u8; let n: i32 = 0; @@ -188,13 +201,16 @@ fn reservedimportpath(p: *u8) bool = { // restoring cwd gives WWstage the same symlink-resolved absolute directory // spelling that Cstage obtains from realpath, without adding a library API. fn canonicaldir(path: str) *u8 = { - let before: []u8 = alloc([], os.PATH_MAX: u64)!; - before.len = os.PATH_MAX; + let before: []u8; + if (!sepmakebytes(os.PATH_MAX: u64, &before)) { return nil; }; let bn: i64 = os.getcwd(before.ptr, before.len: u64); if (bn <= 1i64 || bn > before.len: i64) { return nil; }; if (os.chdir(path) != 0) { return nil; }; - let after: []u8 = alloc([], os.PATH_MAX: u64)!; - after.len = os.PATH_MAX; + let after: []u8; + if (!sepmakebytes(os.PATH_MAX: u64, &after)) { + os.chdir(pathstr(before.ptr)); + return nil; + }; let an: i64 = os.getcwd(after.ptr, after.len: u64); let restored: i32 = os.chdir(pathstr(before.ptr)); if (restored != 0) { @@ -202,13 +218,13 @@ fn canonicaldir(path: str) *u8 = { return nil; }; if (an <= 1i64 || an > after.len: i64) { return nil; }; - return arenadupcstr(after.ptr, (an - 1i64): u64); + return sepdupcstr(after.ptr, (an - 1i64): u64); }; fn envpath(name: str) *u8 = { match (os.getenv(name)) { case let p: str => { - if (p.len != 0) { return owncstr(p); }; + if (p.len != 0) { return sepdupcstr(p.ptr, p.len: u64); }; }; case void => void; }; @@ -218,7 +234,7 @@ fn envpath(name: str) *u8 = { fn toolpath(selfdir: *u8, envvar: str, name: str) *u8 = { let p: *u8 = envpath(envvar); if (p != nil) { return p; }; - return joinpathlit(selfdir, name); + return sepjoinpathlit(selfdir, name); }; // execpackagetests — replace the driver with the native WW package @@ -307,9 +323,18 @@ fn visitseen(c: *expctx, path: str) bool = { return false; }; -fn visitadd(c: *expctx, path: str) void = { - let n: *strnode = alloc(strnode{s=path, snext=c.visit})!; - c.visit = n; +fn visitalloc(c: *expctx, path: str) (*strnode | nomem) = { + let n: *strnode = alloc(strnode{s=path, snext=c.visit})?; + return n; +}; + +fn visitadd(c: *expctx, path: str) bool = { + let allocation: (*strnode | nomem) = visitalloc(c, path); + match (allocation) { + case let n: *strnode => { c.visit = n; return true; }; + case nomem => { sepfailnomem(); return false; }; + }; + return false; }; // Translate dots in an `import` name to slashes for path lookup. @@ -497,8 +522,8 @@ 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_MAXPRODUCT: i32 = 256; -def SEP_MAXCONTEXT: i32 = 257; +def SEP_INITIAL_CAP: i32 = 8; +def SEP_COUNT_MAX: i32 = 2147483647; // Classify a selected directory entry: 1 production, 2 test, 0 skipped, // -1 @test outside *_test.ww, -2 non-regular source. @@ -561,7 +586,7 @@ fn dirpackagename(path: *u8) *u8 = { cerr(": error: invalid or missing package clause\n"); return nil; }; - return arenadupcstr(imports.nmod.ptr, imports.nmod.len: u64); + return sepdupcstr(imports.nmod.ptr, imports.nmod.len: u64); }; // Rule-10 byte-id requires cstage and wwstage sort the same way; @@ -593,13 +618,20 @@ fn enumeratedir(dirpath: *u8, variant: i32, // cmd/ww/main.c:209). The old fixed 256-name cap silently dropped every // eligible file past it, diverging the package unit from cstage on a // module dir with >256 sources. - let cap: i32 = 8; - let names: []*u8 = alloc([], cap: u64)!; - let nlens: []u64 = alloc([], cap: u64)!; - let kinds: []i32 = alloc([], cap: u64)!; + let names: []*u8 = []; + let nlens: []u64 = []; + let kinds: []i32 = []; let n: i32 = 0; - let buf: []u8 = alloc([], 8192u64)!; - buf.len = 8192; + if (!sepreservesources(&names, &nlens, &kinds, n, + SEP_INITIAL_CAP)) { + os.close(fd); + return nil: **u8, -2; + }; + let buf: []u8; + if (!sepmakebytes(8192u64, &buf)) { + os.close(fd); + return nil: **u8, -2; + }; let r: i64 = os.getdents64(fd, buf.ptr, 8192u64); for (r > 0i64) { let off: u64 = 0u64; @@ -635,22 +667,15 @@ fn enumeratedir(dirpath: *u8, variant: i32, continue; }; }; - if (n >= cap) { - let ncap: i32 = cap * 2; - let nn: []*u8 = alloc([], ncap: u64)!; - let nl2: []u64 = alloc([], ncap: u64)!; - let nk: []i32 = alloc([], ncap: u64)!; - let k: i32 = 0; - for (k < n) { - nn[k] = names[k]; - nl2[k] = nlens[k]; - nk[k] = kinds[k]; - k += 1; - }; - names = nn; - nlens = nl2; - kinds = nk; - cap = ncap; + if (n == SEP_COUNT_MAX) { + sepfailsize(); + os.close(fd); + return nil: **u8, -2; + }; + if (!sepreservesources(&names, &nlens, &kinds, n, + n + 1)) { + os.close(fd); + return nil: **u8, -2; }; names[n] = full; nlens[n] = cstrlen(full); @@ -698,20 +723,28 @@ fn enumeratedir(dirpath: *u8, variant: i32, i += 1; }; if (n == 0) { - os.free(names.ptr: *void, (cap: u64) * (size(*u8): u64)); - os.free(nlens.ptr: *void, (cap: u64) * (size(u64): u64)); - os.free(kinds.ptr: *void, (cap: u64) * (size(i32): u64)); + os.free(names.ptr: *void, (names.cap: u64) * (size(*u8): u64)); + os.free(nlens.ptr: *void, (nlens.cap: u64) * (size(u64): u64)); + os.free(kinds.ptr: *void, (kinds.cap: u64) * (size(i32): u64)); return nil: **u8, 0; }; - let exact: []*u8 = alloc([], n: u64)!; + let exactallocation: ([]*u8 | nomem) = sepallocptrs(n); + let exact: []*u8; + match (exactallocation) { + case let value: []*u8 => exact = value; + case nomem => { + sepfailnomem(); + return nil: **u8, -2; + }; + }; exact.len = n; let k: i32 = 0; for (k < n) { exact[k] = names[k]; k += 1; }; // rt_free is currently a no-op, but keep the concrete owner/release // shape correct for the driver's allocations. - os.free(names.ptr: *void, (cap: u64) * (size(*u8): u64)); - os.free(nlens.ptr: *void, (cap: u64) * (size(u64): u64)); - os.free(kinds.ptr: *void, (cap: u64) * (size(i32): u64)); + os.free(names.ptr: *void, (names.cap: u64) * (size(*u8): u64)); + os.free(nlens.ptr: *void, (nlens.cap: u64) * (size(u64): u64)); + os.free(kinds.ptr: *void, (kinds.cap: u64) * (size(i32): u64)); return exact.ptr, n; }; @@ -725,7 +758,21 @@ fn slurp(pathcs: *u8) (*u8, u64) = { case let e: os.oserror => { os.close(fd); return nil, 0u64; }; }; let nu: u64 = n: u64; - let buf: []u8 = alloc([], nu + 1u64)!; + if (nu >= SEP_COUNT_MAX: u64) { + sepfailsize(); + os.close(fd); + return nil, 0u64; + }; + let allocation: ([]u8 | nomem) = sepallocbytes((nu + 1u64): i32); + let buf: []u8; + match (allocation) { + case let value: []u8 => buf = value; + case nomem => { + sepfailnomem(); + os.close(fd); + return nil, 0u64; + }; + }; buf.len = (nu + 1u64): i32; let rr: (i64 | os.oserror) = os.readall(fd, buf.ptr, nu); let closed: i32 = os.close(fd); @@ -789,8 +836,6 @@ type lflags = struct { // qualified symbol identity; transitive exports remain outside the compile // action. Unit composition is byte-identical to the cstage driver. -def SEP_MAXPKG: i32 = 256; - type sepbind = struct { kind: u8, name: str, @@ -820,9 +865,9 @@ type seppkg = struct { loaded: bool, exportchanged: bool, emitcontext: i32, - contextstate: []u8, + contextstate: []u8, // zero-extended lazily for reached contexts bindings: []sepbind, - deps: []i32, // direct-dep indices into sepgraph.pkg + deps: []i32, // stable direct-dep indices into sepgraph.pkg ndeps: i32, color: i32, // tri-color DFS: 0 white, 1 gray, 2 black }; @@ -833,9 +878,9 @@ type sepcontext = struct { }; type sepgraph = struct { - pkg: []seppkg, // alloc'd SEP_MAXPKG + pkg: []seppkg, // len is allocated capacity; n is action count n: i32, - context: []sepcontext, + context: []sepcontext, // len is allocated capacity ncontext: i32, supportcontext: i32, identityfailed: bool, @@ -852,6 +897,423 @@ type sepproduct = struct { context: i32, root: i32, variantroot: i32, + support: i32, +}; + +fn sepgrowcap(current: i32, need: i32) i32 = { + if (need < 0) { + sepfailsize(); + return -1; + }; + if (need <= current) { return current; }; + let cap: i32 = current; + if (cap == 0) { cap = SEP_INITIAL_CAP; }; + for (cap < need) { + if (cap > SEP_COUNT_MAX / 2) { + cap = SEP_COUNT_MAX; + break; + }; + cap *= 2; + }; + if (cap < need) { + sepfailsize(); + return -1; + }; + return cap; +}; + +fn sepallocpackages(cap: i32) ([]seppkg | nomem) = { + let value: []seppkg = alloc([], cap: u64)?; + return value; +}; + +fn sepalloccontexts(cap: i32) ([]sepcontext | nomem) = { + let value: []sepcontext = alloc([], cap: u64)?; + return value; +}; + +fn sepallocints(cap: i32) ([]i32 | nomem) = { + let value: []i32 = alloc([], cap: u64)?; + return value; +}; + +fn sepallocu64s(cap: i32) ([]u64 | nomem) = { + let value: []u64 = alloc([], cap: u64)?; + return value; +}; + +fn sepallocnodeptrs(cap: i32) ([]*syntax.node | nomem) = { + let value: []*syntax.node = alloc([], cap: u64)?; + return value; +}; + +fn sepallocbinds(cap: i32) ([]sepbind | nomem) = { + let value: []sepbind = alloc([], cap: u64)?; + return value; +}; + +fn sepdupstr(s: str) (str | nomem) = { + let out: str; + out.ptr = nil; + out.len = 0; + out.cap = 0; + if (s.len == 0) { return out; }; + let allocation: ([]u8 | nomem) = sepallocbytes(s.len); + let bytes: []u8; + match (allocation) { + case let value: []u8 => bytes = value; + case let e: nomem => return e; + }; + bytes.len = s.len; + let i: i32 = 0; + for (i < s.len) { bytes[i] = s[i]; i += 1; }; + out.ptr = bytes.ptr; + out.len = bytes.len; + out.cap = bytes.cap; + return out; +}; + +fn sepallocbytes(cap: i32) ([]u8 | nomem) = { + let value: []u8 = alloc([], cap: u64)?; + return value; +}; + +fn sepallocproducts(cap: i32) ([]sepproduct | nomem) = { + let value: []sepproduct = alloc([], cap: u64)?; + return value; +}; + +fn sepallocstrs(cap: i32) ([]str | nomem) = { + let value: []str = alloc([], cap: u64)?; + return value; +}; + +fn sepallocptrs(cap: i32) ([]*u8 | nomem) = { + let value: []*u8 = alloc([], cap: u64)?; + return value; +}; + +fn sepmakeints(count: i32, out: *[]i32) bool = { + if (count < 0) { + sepfailsize(); + return false; + }; + let allocation: ([]i32 | nomem) = sepallocints(count); + match (allocation) { + case let value: []i32 => { + value.len = count; + *out = value; + return true; + }; + case nomem => { sepfailnomem(); return false; }; + }; + return false; +}; + +fn sepreservepackages(g: *sepgraph, need: i32) bool = { + if (need <= g.pkg.len) { return true; }; + let cap: i32 = sepgrowcap(g.pkg.len, need); + if (cap < 0) { return false; }; + let allocation: ([]seppkg | nomem) = sepallocpackages(cap); + let next: []seppkg; + match (allocation) { + case let v: []seppkg => next = v; + case nomem => { sepfailnomem(); return false; }; + }; + next.len = cap; + let i: i32 = 0; + for (i < g.n) { next[i] = g.pkg[i]; i += 1; }; + if (g.pkg.ptr != nil) { + os.free(g.pkg.ptr: *void, + (g.pkg.cap: u64) * (size(seppkg): u64)); + }; + g.pkg = next; + return true; +}; + +fn sepreservecontexts(g: *sepgraph, need: i32) bool = { + if (need <= g.context.len) { return true; }; + let cap: i32 = sepgrowcap(g.context.len, need); + if (cap < 0) { return false; }; + let allocation: ([]sepcontext | nomem) = sepalloccontexts(cap); + let next: []sepcontext; + match (allocation) { + case let v: []sepcontext => next = v; + case nomem => { sepfailnomem(); return false; }; + }; + next.len = cap; + let i: i32 = 0; + for (i < g.ncontext) { next[i] = g.context[i]; i += 1; }; + if (g.context.ptr != nil) { + os.free(g.context.ptr: *void, + (g.context.cap: u64) * (size(sepcontext): u64)); + }; + g.context = next; + return true; +}; + +fn sepreservedeps(p: *seppkg, need: i32) bool = { + if (need <= p.deps.len) { return true; }; + let cap: i32 = sepgrowcap(p.deps.len, need); + if (cap < 0) { return false; }; + let allocation: ([]i32 | nomem) = sepallocints(cap); + let next: []i32; + match (allocation) { + case let v: []i32 => next = v; + case nomem => { sepfailnomem(); return false; }; + }; + next.len = cap; + let i: i32 = 0; + for (i < p.ndeps) { next[i] = p.deps[i]; i += 1; }; + if (p.deps.ptr != nil) { + os.free(p.deps.ptr: *void, + (p.deps.cap: u64) * (size(i32): u64)); + }; + p.deps = next; + return true; +}; + +fn sepcontextstate(p: *seppkg, context: i32) u8 = { + if (context < 0 || context >= p.contextstate.len) { return 0u8; }; + return p.contextstate[context]; +}; + +fn sepsetcontextstate(p: *seppkg, context: i32, state: u8) bool = { + if (context < 0 || context == SEP_COUNT_MAX) { + sepfailsize(); + return false; + }; + let need: i32 = context + 1; + if (need > p.contextstate.len) { + let cap: i32 = sepgrowcap(p.contextstate.len, need); + if (cap < 0) { return false; }; + let allocation: ([]u8 | nomem) = sepallocbytes(cap); + let next: []u8; + match (allocation) { + case let v: []u8 => next = v; + case nomem => { sepfailnomem(); return false; }; + }; + next.len = cap; + let i: i32 = 0; + for (i < p.contextstate.len) { + next[i] = p.contextstate[i]; + i += 1; + }; + for (i < cap) { next[i] = 0u8; i += 1; }; + if (p.contextstate.ptr != nil) { + os.free(p.contextstate.ptr: *void, + (p.contextstate.cap: u64) * (size(u8): u64)); + }; + p.contextstate = next; + }; + p.contextstate[context] = state; + return true; +}; + +fn sepadddep(g: *sepgraph, pi: i32, dep: i32) bool = { + let i: i32 = 0; + for (i < g.pkg[pi].ndeps) { + if (g.pkg[pi].deps[i] == dep) { return true; }; + i += 1; + }; + if (g.pkg[pi].ndeps == SEP_COUNT_MAX) { + sepfailsize(); + return false; + }; + if (!sepreservedeps(&g.pkg[pi], g.pkg[pi].ndeps + 1)) { + return false; + }; + g.pkg[pi].deps[g.pkg[pi].ndeps] = dep; + g.pkg[pi].ndeps += 1; + return true; +}; + +fn sepreserveproducts(products: *[]sepproduct, need: i32) bool = { + if (need <= products.cap) { return true; }; + let cap: i32 = sepgrowcap(products.cap, need); + if (cap < 0) { return false; }; + let allocation: ([]sepproduct | nomem) = sepallocproducts(cap); + let next: []sepproduct; + match (allocation) { + case let v: []sepproduct => next = v; + case nomem => { sepfailnomem(); return false; }; + }; + let n: i32 = products.len; + next.len = cap; + let i: i32 = 0; + for (i < n) { next[i] = (*products)[i]; i += 1; }; + next.len = n; + if (products.ptr != nil) { + os.free(products.ptr: *void, + (products.cap: u64) * (size(sepproduct): u64)); + }; + *products = next; + return true; +}; + +fn sepreservesources(names: *[]*u8, nlens: *[]u64, kinds: *[]i32, + used: i32, need: i32) bool = { + if (need <= names.len) { return true; }; + let cap: i32 = sepgrowcap(names.len, need); + if (cap < 0) { return false; }; + let namesallocation: ([]*u8 | nomem) = sepallocptrs(cap); + let nextnames: []*u8; + match (namesallocation) { + case let value: []*u8 => nextnames = value; + case nomem => { sepfailnomem(); return false; }; + }; + let lensallocation: ([]u64 | nomem) = sepallocu64s(cap); + let nextlens: []u64; + match (lensallocation) { + case let value: []u64 => nextlens = value; + case nomem => { + os.free(nextnames.ptr: *void, + (nextnames.cap: u64) * (size(*u8): u64)); + sepfailnomem(); return false; + }; + }; + let kindsallocation: ([]i32 | nomem) = sepallocints(cap); + let nextkinds: []i32; + match (kindsallocation) { + case let value: []i32 => nextkinds = value; + case nomem => { + os.free(nextnames.ptr: *void, + (nextnames.cap: u64) * (size(*u8): u64)); + os.free(nextlens.ptr: *void, + (nextlens.cap: u64) * (size(u64): u64)); + sepfailnomem(); return false; + }; + }; + nextnames.len = cap; + nextlens.len = cap; + nextkinds.len = cap; + let i: i32 = 0; + for (i < used) { + nextnames[i] = (*names)[i]; + nextlens[i] = (*nlens)[i]; + nextkinds[i] = (*kinds)[i]; + i += 1; + }; + if (names.ptr != nil) { + os.free(names.ptr: *void, + (names.cap: u64) * (size(*u8): u64)); + }; + if (nlens.ptr != nil) { + os.free(nlens.ptr: *void, + (nlens.cap: u64) * (size(u64): u64)); + }; + if (kinds.ptr != nil) { + os.free(kinds.ptr: *void, + (kinds.cap: u64) * (size(i32): u64)); + }; + *names = nextnames; + *nlens = nextlens; + *kinds = nextkinds; + return true; +}; + +fn sepreservebinds(bindings: *[]sepbind, need: i32) bool = { + if (need <= bindings.cap) { return true; }; + let cap: i32 = sepgrowcap(bindings.cap, need); + if (cap < 0) { return false; }; + let allocation: ([]sepbind | nomem) = sepallocbinds(cap); + let next: []sepbind; + match (allocation) { + case let value: []sepbind => next = value; + case nomem => { sepfailnomem(); return false; }; + }; + let n: i32 = bindings.len; + next.len = cap; + let i: i32 = 0; + for (i < n) { next[i] = (*bindings)[i]; i += 1; }; + next.len = n; + if (bindings.ptr != nil) { + os.free(bindings.ptr: *void, + (bindings.cap: u64) * (size(sepbind): u64)); + }; + *bindings = next; + return true; +}; + +fn sepaddbytes(total: *u64, add: u64) bool = { + if (*total > SEP_COUNT_MAX: u64 + || add > (SEP_COUNT_MAX: u64) - *total) { + sepfailsize(); + return false; + }; + *total += add; + return true; +}; + +fn sepmuladdbytes(total: *u64, count: u64, factor: u64) bool = { + if (factor != 0u64 && count > (SEP_COUNT_MAX: u64) / factor) { + sepfailsize(); + return false; + }; + return sepaddbytes(total, count * factor); +}; + +fn sepmakebytes(count: u64, out: *[]u8) bool = { + let total: u64 = 0u64; + if (!sepaddbytes(&total, count)) { return false; }; + let allocation: ([]u8 | nomem) = sepallocbytes(total: i32); + match (allocation) { + case let value: []u8 => { + value.len = total: i32; + *out = value; + return true; + }; + case nomem => { sepfailnomem(); return false; }; + }; + return false; +}; + +// Package-semantic strings use the same checked signed-count storage rule as +// graph vectors. These helpers never publish a partial owner and mark their +// failure command-fatal so a sibling product cannot proceed to a tool. +fn sepdupcstr(src: *u8, n: u64) *u8 = { + let need: u64 = 0u64; + if (!sepaddbytes(&need, n) || !sepaddbytes(&need, 1u64)) { + return nil; + }; + let out: []u8; + if (!sepmakebytes(need, &out)) { return nil; }; + let i: u64 = 0u64; + for (i < n) { out[i] = src[i]; i += 1u64; }; + out[n] = 0u8; + return out.ptr; +}; + +fn sepappendlit(stem: *u8, suffix: str) *u8 = { + let need: u64 = 0u64; + if (!sepaddbytes(&need, cstrlen(stem)) + || !sepaddbytes(&need, suffix.len: u64) + || !sepaddbytes(&need, 1u64)) { return nil; }; + let out: []u8; + if (!sepmakebytes(need, &out)) { return nil; }; + let off: u64 = cstrinto(out.ptr, 0u64, stem); + off = strinto(out.ptr, off, suffix); + cstrseal(out.ptr, off); + return out.ptr; +}; + +fn sepjoinpathlit(dir: *u8, name: str) *u8 = { + let need: u64 = 0u64; + if (!sepaddbytes(&need, cstrlen(dir)) + || !sepaddbytes(&need, 1u64) + || !sepaddbytes(&need, name.len: u64) + || !sepaddbytes(&need, 1u64)) { return nil; }; + let out: []u8; + if (!sepmakebytes(need, &out)) { return nil; }; + let off: u64 = cstrinto(out.ptr, 0u64, dir); + off = byteinto(out.ptr, off, '/': u8); + off = strinto(out.ptr, off, name); + cstrseal(out.ptr, off); + return out.ptr; +}; + +fn sepjoinpath(dir: *u8, name: *u8) *u8 = { + return sepjoinpathlit(dir, pathstr(name)); }; fn sepdirectoryvariant(variant: i32) bool = { @@ -861,8 +1323,8 @@ fn sepdirectoryvariant(variant: i32) bool = { }; fn sepvariantpath(variant: i32, base: *u8) *u8 = { - if (variant == SEP_VARIANT_EXTERNAL) { return appendlit(base, "_test"); }; - return arenadupcstr(base, cstrlen(base)); + if (variant == SEP_VARIANT_EXTERNAL) { return sepappendlit(base, "_test"); }; + return sepdupcstr(base, cstrlen(base)); }; fn sepdiagpathlocations(path: *u8, a: *u8, b: *u8) void = { @@ -944,6 +1406,7 @@ fn sepbindimportbase(g: *sepgraph, pi: i32, base: *u8) i32 = { return -1; }; let candidate: *u8 = sepvariantpath(p.variant, base); + if (candidate == nil) { return -1; }; let i: i32 = 0; for (i < g.n) { if (i != pi && g.pkg[i].isdir != 0 && !g.pkg[i].generatedmain) { @@ -982,7 +1445,8 @@ fn sepbindimportbase(g: *sepgraph, pi: i32, base: *u8) i32 = { }; i += 1; }; - p.importbase = arenadupcstr(base, cstrlen(base)); + p.importbase = sepdupcstr(base, cstrlen(base)); + if (p.importbase == nil) { return -1; }; p.path = candidate; return 0; }; @@ -994,6 +1458,7 @@ fn sepfindoraddvariant(g: *sepgraph, path: *u8, entry: *u8, 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; @@ -1002,6 +1467,7 @@ fn sepfindoraddvariant(g: *sepgraph, path: *u8, entry: *u8, let incoming: *u8 = nil; if (isdir != 0 && path[0u64] != 0u8) { incoming = sepvariantpath(variant, path); + if (incoming == nil) { return -1; }; }; let i: i32 = 0; for (i < g.n) { @@ -1073,15 +1539,16 @@ fn sepfindoraddvariant(g: *sepgraph, path: *u8, entry: *u8, }; i += 1; }; - if (g.n >= SEP_MAXPKG) { - cerr("ww: too many packages\n"); + if (g.n == SEP_COUNT_MAX) { sepfailsize(); return -1; }; + if (!sepreservepackages(g, g.n + 1)) { return -1; }; let plen: u64 = cstrlen(path); let elen: u64 = cstrlen(entry); - g.pkg[g.n].path = arenadupcstr(path, plen); + g.pkg[g.n].path = sepdupcstr(path, plen); g.pkg[g.n].importbase = nil; - g.pkg[g.n].entry = arenadupcstr(entry, elen); + g.pkg[g.n].entry = sepdupcstr(entry, elen); + if (g.pkg[g.n].path == nil || g.pkg[g.n].entry == nil) { return -1; }; g.pkg[g.n].canon = canon; g.pkg[g.n].artifact = nil; g.pkg[g.n].storage = nil; @@ -1089,8 +1556,9 @@ fn sepfindoraddvariant(g: *sepgraph, path: *u8, entry: *u8, g.pkg[g.n].name = nil; g.pkg[g.n].testpackage = nil; if (testpackage != nil) { - g.pkg[g.n].testpackage = arenadupcstr(testpackage, + g.pkg[g.n].testpackage = sepdupcstr(testpackage, cstrlen(testpackage)); + if (g.pkg[g.n].testpackage == nil) { return -1; }; }; g.pkg[g.n].sources = nil; g.pkg[g.n].nsources = 0; @@ -1105,14 +1573,12 @@ fn sepfindoraddvariant(g: *sepgraph, path: *u8, entry: *u8, g.pkg[g.n].loaded = false; g.pkg[g.n].exportchanged = false; g.pkg[g.n].emitcontext = -1; - let cslot: []u8 = alloc([], SEP_MAXCONTEXT: u64)!; - cslot.len = SEP_MAXCONTEXT; - g.pkg[g.n].contextstate = cslot; + let emptystate: []u8; + g.pkg[g.n].contextstate = emptystate; let emptybindings: []sepbind; g.pkg[g.n].bindings = emptybindings; - let dslot: []i32 = alloc([], SEP_MAXPKG: u64)!; - dslot.len = SEP_MAXPKG; - g.pkg[g.n].deps = dslot; + let emptydeps: []i32; + g.pkg[g.n].deps = emptydeps; g.pkg[g.n].ndeps = 0; g.pkg[g.n].color = 0; if (isdir != 0) { @@ -1172,18 +1638,60 @@ fn sepgraphfree(g: *sepgraph) void = { if (g.pkg[i].name != nil) { os.free(g.pkg[i].name: *void, cstrlen(g.pkg[i].name) + 1u64); }; + if (g.pkg[i].contextstate.ptr != nil) { + os.free(g.pkg[i].contextstate.ptr: *void, + (g.pkg[i].contextstate.cap: u64) * (size(u8): u64)); + }; + if (g.pkg[i].deps.ptr != nil) { + os.free(g.pkg[i].deps.ptr: *void, + (g.pkg[i].deps.cap: u64) * (size(i32): u64)); + }; + let bi: i32 = 0; + for (bi < g.pkg[i].bindings.len) { + if (g.pkg[i].bindings[bi].name.ptr != nil) { + os.free(g.pkg[i].bindings[bi].name.ptr: *void, + g.pkg[i].bindings[bi].name.cap: u64); + }; + bi += 1; + }; + if (g.pkg[i].bindings.ptr != nil) { + os.free(g.pkg[i].bindings.ptr: *void, + (g.pkg[i].bindings.cap: u64) * (size(sepbind): u64)); + }; i += 1; }; + if (g.pkg.ptr != nil) { + os.free(g.pkg.ptr: *void, + (g.pkg.cap: u64) * (size(seppkg): u64)); + }; + i = 0; + for (i < g.ncontext) { + if (g.context[i].searchpath != nil) { + os.free(g.context[i].searchpath: *void, + cstrlen(g.context[i].searchpath) + 1u64); + }; + i += 1; + }; + if (g.context.ptr != nil) { + os.free(g.context.ptr: *void, + (g.context.cap: u64) * (size(sepcontext): u64)); + }; + os.free(g: *void, size(sepgraph): u64); }; fn sepcontextfor(g: *sepgraph, root: *u8, incs: *u8, toolsrcdir: *u8) i32 = { - let need: u64 = cstrlen(root) + 1u64 + cstrlen(toolsrcdir) + 1u64; + let need: u64 = 0u64; + if (!sepaddbytes(&need, cstrlen(root)) + || !sepaddbytes(&need, 1u64) + || !sepaddbytes(&need, cstrlen(toolsrcdir)) + || !sepaddbytes(&need, 1u64)) { return -1; }; if (incs != nil && incs[0u64] != 0u8) { - need += cstrlen(incs) + 1u64; + if (!sepaddbytes(&need, cstrlen(incs)) + || !sepaddbytes(&need, 1u64)) { return -1; }; }; - let search: []u8 = alloc([], need)!; - search.len = need: i32; + let search: []u8; + if (!sepmakebytes(need, &search)) { return -1; }; let off: u64 = cstrinto(search.ptr, 0u64, root); off = byteinto(search.ptr, off, 58u8); if (incs != nil && incs[0u64] != 0u8) { @@ -1194,11 +1702,15 @@ fn sepcontextfor(g: *sepgraph, root: *u8, incs: *u8, cstrseal(search.ptr, off); let i: i32 = 0; for (i < g.ncontext) { - if (cstreq(g.context[i].searchpath, search.ptr)) { return i; }; + if (cstreq(g.context[i].searchpath, search.ptr)) { + os.free(search.ptr: *void, + (search.cap: u64) * (size(u8): u64)); + return i; + }; i += 1; }; - if (g.ncontext >= SEP_MAXCONTEXT) { - cerr("ww: too many package import contexts\n"); + if (g.ncontext == SEP_COUNT_MAX) { sepfailsize(); return -1; }; + if (!sepreservecontexts(g, g.ncontext + 1)) { return -1; }; g.context[g.ncontext].root = root; @@ -1229,8 +1741,8 @@ fn sepstoragedigest(p: *seppkg) *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 out: []u8; + if (!sepmakebytes(need, &out)) { return nil; }; let off: u64 = strinto(out.ptr, 0u64, "__wwpkg.v"); off = byteinto(out.ptr, off, tag[0]); off = strinto(out.ptr, off, ".r"); @@ -1272,20 +1784,25 @@ fn sepassignstorage(p: *seppkg, scratch: *u8) i32 = { + ".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.storage = sepdupcstr(base, cstrlen(base)); p.storagehashed = false; } else { p.storage = sepstoragedigest(p); p.storagehashed = true; }; + if (p.storage == nil) { return -1; }; return sepvalidatestoragepath(p, scratch); }; fn sepfname(g: *sepgraph, pi: i32, scratch: *u8, suffix: str) *u8 = { - 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 need: u64 = 0u64; + if (!sepaddbytes(&need, cstrlen(scratch)) + || !sepaddbytes(&need, 1u64) + || !sepaddbytes(&need, cstrlen(g.pkg[pi].storage)) + || !sepaddbytes(&need, suffix.len: u64) + || !sepaddbytes(&need, 1u64)) { return nil; }; + let buf: []u8; + if (!sepmakebytes(need, &buf)) { return nil; }; let off: u64 = cstrinto(buf.ptr, 0u64, scratch); off = byteinto(buf.ptr, off, 47u8); // '/' off = cstrinto(buf.ptr, off, g.pkg[pi].storage); @@ -1374,24 +1891,35 @@ fn sepexternalname(pkg: *seppkg, path: *u8, n: u64, }; fn sepbindadd(bindings: *[]sepbind, kind: u8, name: str, - target: *u8) void = { + target: *u8) bool = { let i: i32 = 0; for (i < len(*bindings)) { let b: sepbind = (*bindings)[i]; if (b.kind == kind && syntax.streq(b.name, name)) { - if (target == nil && b.target == nil) { return; }; + if (target == nil && b.target == nil) { return true; }; if (target != nil && b.target != nil && os.samefile(pathstr(target), pathstr(b.target))) { - return; + return true; }; }; i += 1; }; + if (bindings.len == SEP_COUNT_MAX) { sepfailsize(); return false; }; + if (!sepreservebinds(bindings, bindings.len + 1)) { + return false; + }; + let copiedallocation: (str | nomem) = sepdupstr(name); + let copied: str; + match (copiedallocation) { + case let value: str => copied = value; + case nomem => { sepfailnomem(); return false; }; + }; append(*bindings, sepbind { kind = kind, - name = strings.dup(name), + name = copied, target = target, }); + return true; }; fn sepbindsame(a: []sepbind, b: []sepbind) bool = { @@ -1429,9 +1957,14 @@ fn sepscanfile(g: *sepgraph, pi: i32, file: *u8, searchpath: *u8, let fview: str; fview.ptr = file; fview.len = cstrlen(file): i32; - let fdup: str = strings.dup(fview); + let fdupres: (str | nomem) = sepdupstr(fview); + let fdup: str; + match (fdupres) { + case let value: str => fdup = value; + case nomem => { sepfailnomem(); return -1; }; + }; if (visitseen(fv, fdup)) { return 0; }; - visitadd(fv, fdup); + if (!visitadd(fv, fdup)) { return -1; }; let bufp: *u8; let blen: u64; bufp, blen = slurp(file); @@ -1455,7 +1988,8 @@ fn sepscanfile(g: *sepgraph, pi: i32, file: *u8, searchpath: *u8, let declared: *u8 = imports.nmod.ptr; let declaredn: u64 = imports.nmod.len: u64; if (g.pkg[pi].name == nil) { - g.pkg[pi].name = arenadupcstr(declared, declaredn); + g.pkg[pi].name = sepdupcstr(declared, declaredn); + if (g.pkg[pi].name == nil) { return -1; }; } else { if (bytecmp(g.pkg[pi].name, cstrlen(g.pkg[pi].name), declared, declaredn) != 0) { cerrpos(imports.file, imports.line, imports.col); @@ -1484,13 +2018,22 @@ fn sepscanfile(g: *sepgraph, pi: i32, file: *u8, searchpath: *u8, let nuse: i32 = 0; let u: *syntax.node = imports.list; for (u != nil) { - if (u.kind == syntax.nkind.N_USE) { nuse += 1; }; + if (u.kind == syntax.nkind.N_USE) { + if (nuse == SEP_COUNT_MAX) { + sepfailsize(); + return -1; + }; + nuse += 1; + }; u = u.next; }; let uses: []*syntax.node = []; if (nuse > 0) { - let allocated: []*syntax.node = alloc([], nuse: u64)!; - uses = allocated; + let allocation: ([]*syntax.node | nomem) = sepallocnodeptrs(nuse); + match (allocation) { + case let value: []*syntax.node => uses = value; + case nomem => { sepfailnomem(); return -1; }; + }; uses.len = nuse; }; let ui: i32 = 0; @@ -1542,7 +2085,9 @@ fn sepscanfile(g: *sepgraph, pi: i32, file: *u8, searchpath: *u8, ipath = locateimport(searchpath, idp, idn); }; if (ipath != nil) { - sepbindadd(bindings, 'D': u8, u.usepath, ipath); + 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) @@ -1559,7 +2104,8 @@ fn sepscanfile(g: *sepgraph, pi: i32, file: *u8, searchpath: *u8, cerr("' cannot import itself\n"); return -1; }; - let nm: []u8 = alloc([], idn + 1u64)!; + let nm: []u8; + if (!sepmakebytes(idn + 1u64, &nm)) { return -1; }; let k: u64 = 0u64; for (k < idn) { nm[k] = idp[k]; k += 1u64; }; nm[idn] = 0u8; @@ -1567,17 +2113,7 @@ 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 seen: bool = false; - let m: i32 = 0; - for (m < g.pkg[pi].ndeps) { - if (g.pkg[pi].deps[m] == di) { seen = true; }; - m += 1; - }; - if (!seen) { - if (g.pkg[pi].ndeps >= SEP_MAXPKG) { return -1; }; - g.pkg[pi].deps[g.pkg[pi].ndeps] = di; - g.pkg[pi].ndeps += 1; - }; + if (!sepadddep(g, pi, di)) { return -1; }; } else { let lstart: u64 = 0u64; let lk: u64 = 0u64; @@ -1603,7 +2139,9 @@ fn sepscanfile(g: *sepgraph, pi: i32, file: *u8, searchpath: *u8, cerr("\n"); return -1; } else { - sepbindadd(bindings, 'I': u8, u.usepath, nil); + if (!sepbindadd(bindings, 'I': u8, u.usepath, nil)) { + return -1; + }; }; }; }; @@ -1634,10 +2172,15 @@ fn generatedmainkind(variant: i32) str = { }; fn generatedmainpath(pkgpath: *u8, kind: str) *u8 = { - let need: u64 = "__wwtestmain.".len: u64 + cstrlen(pkgpath) - + 1u64 + kind.len: u64 + ".main".len: u64 + 1u64; - let buf: []u8 = alloc([], need)!; - buf.len = need: i32; + let need: u64 = 0u64; + if (!sepaddbytes(&need, "__wwtestmain.".len: u64) + || !sepaddbytes(&need, cstrlen(pkgpath)) + || !sepaddbytes(&need, 1u64) + || !sepaddbytes(&need, kind.len: u64) + || !sepaddbytes(&need, ".main".len: u64) + || !sepaddbytes(&need, 1u64)) { return nil; }; + let buf: []u8; + if (!sepmakebytes(need, &buf)) { return nil; }; let off: u64 = strinto(buf.ptr, 0u64, "__wwtestmain."); off = cstrinto(buf.ptr, off, pkgpath); off = byteinto(buf.ptr, off, '.': u8); @@ -1656,6 +2199,7 @@ fn sepaddgeneratedmain(g: *sepgraph, product: *sepproduct, ordinal: i32, if (variant < 0 || variant >= g.n) { return -1; }; let kind: str = generatedmainkind(g.pkg[variant].variant); let mainpath: *u8 = generatedmainpath(g.pkg[variant].path, kind); + if (mainpath == nil) { return -1; }; let pathi: i32 = 0; for (pathi < g.n) { if (cstreq(g.pkg[pathi].path, mainpath)) { @@ -1684,23 +2228,28 @@ fn sepaddgeneratedmain(g: *sepgraph, product: *sepproduct, ordinal: i32, }; pathi += 1; }; - if (g.n >= SEP_MAXPKG) { - cerr("ww: too many packages\n"); + if (g.n == SEP_COUNT_MAX) { sepfailsize(); return -1; }; + if (!sepreservepackages(g, g.n + 1)) { return -1; }; let p: *seppkg = &g.pkg[g.n]; p.path = mainpath; p.importbase = nil; p.entry = g.pkg[variant].entry; - let canonkind: *u8 = appendlit(g.pkg[variant].canon, "#"); - canonkind = appendlit(canonkind, kind); - p.canon = appendlit(canonkind, "-test-main"); + let canonkind: *u8 = sepappendlit(g.pkg[variant].canon, "#"); + if (canonkind == nil) { return -1; }; + canonkind = sepappendlit(canonkind, kind); + if (canonkind == nil) { return -1; }; + p.canon = sepappendlit(canonkind, "-test-main"); + if (p.canon == nil) { return -1; }; let variantartifact: *u8 = g.pkg[variant].artifact; if (variantartifact == nil) { variantartifact = g.pkg[variant].path; }; - p.artifact = appendlit(variantartifact, "-main"); + p.artifact = sepappendlit(variantartifact, "-main"); + if (p.artifact == nil) { return -1; }; p.storage = nil; p.storagehashed = false; - p.name = arenadupcstr("main\0".ptr, 4u64); + p.name = sepdupcstr("main\0".ptr, 4u64); + if (p.name == nil) { return -1; }; p.testpackage = nil; p.sources = nil; p.nsources = 0; @@ -1715,21 +2264,17 @@ fn sepaddgeneratedmain(g: *sepgraph, product: *sepproduct, ordinal: i32, p.loaded = true; p.exportchanged = false; p.emitcontext = product.context; - let cslot: []u8 = alloc([], SEP_MAXCONTEXT: u64)!; - cslot.len = SEP_MAXCONTEXT; - p.contextstate = cslot; - p.contextstate[product.context] = 2u8; + let emptystate: []u8; + p.contextstate = emptystate; let emptybindings: []sepbind; p.bindings = emptybindings; - let dslot: []i32 = alloc([], SEP_MAXPKG: u64)!; - dslot.len = SEP_MAXPKG; - p.deps = dslot; - p.ndeps = 1; - p.deps[0] = variant; - if (support >= 0 && support != variant) { - p.deps[p.ndeps] = support; - p.ndeps += 1; - }; + let emptydeps: []i32; + p.deps = emptydeps; + p.ndeps = 0; + if (!sepsetcontextstate(p, product.context, 2u8) + || !sepadddep(g, g.n, variant) + || (support >= 0 && support != variant + && !sepadddep(g, g.n, support))) { return -1; }; let i: i32 = 1; for (i < p.ndeps) { let v: i32 = p.deps[i]; @@ -1747,19 +2292,10 @@ fn sepaddgeneratedmain(g: *sepgraph, product: *sepproduct, ordinal: i32, return r; }; -// Load pi once: a directory node takes ownership of its sorted production -// paths, then every selected-root context verifies the same canonical import -// bindings before the package is compiled once. -fn seploadpkg(g: *sepgraph, pi: i32, context: i32) i32 = { - if (g.pkg[pi].testsupport - && g.supportcontext >= 0) { context = g.supportcontext; }; - if (context < 0 || context >= g.ncontext) { return -1; }; - if (g.pkg[pi].contextstate[context] == 2u8) { - if (g.pkg[pi].failed) { return -1; }; - return 0; - }; - if (g.pkg[pi].contextstate[context] == 1u8) { return 0; }; - g.pkg[pi].contextstate[context] = 1u8; +// Load one action's owned sources and direct bindings. Dependency descent is +// iterative below so a valid deep graph is not limited by the native stack. +fn seppreparepkgcontext(g: *sepgraph, pi: i32, context: i32) i32 = { + if (!sepsetcontextstate(&g.pkg[pi], context, 1u8)) { return -1; }; let searchpath: *u8 = g.context[context].searchpath; let fv: expctx; fv.out = -1; @@ -1855,27 +2391,155 @@ fn seploadpkg(g: *sepgraph, pi: i32, context: i32) i32 = { si += 1; }; g.pkg[pi].contextstate[context] = 2u8; - let k: i32 = 0; - for (k < g.pkg[pi].ndeps) { - let dep: i32 = g.pkg[pi].deps[k]; - if (seploadpkg(g, dep, context) < 0) { - g.pkg[pi].failed = true; - return -1; - }; - if (dep != pi && sepforbiddencommandimport(g, pi, dep)) { - cerr("ww: package "); - if (g.pkg[dep].path[0u64] != 0u8) { - cerr(pathstr(g.pkg[dep].path)); - } else { cerr(pathstr(g.pkg[dep].canon)); }; - cerr(" is a program, not an importable package\n"); - g.pkg[pi].failed = true; - return -1; - }; - k += 1; - }; return 0; }; +type seploadframe = struct { + pkg: i32, + context: i32, + nextdep: i32, + pendingdep: i32, +}; + +fn sepallocloadframes(cap: i32) ([]seploadframe | nomem) = { + let value: []seploadframe = alloc([], cap: u64)?; + return value; +}; + +fn sepreserveloadframes(frames: *[]seploadframe, used: i32, + need: i32) bool = { + if (need <= frames.len) { return true; }; + let cap: i32 = sepgrowcap(frames.len, need); + if (cap < 0) { return false; }; + let allocation: ([]seploadframe | nomem) = sepallocloadframes(cap); + let next: []seploadframe; + match (allocation) { + case let v: []seploadframe => next = v; + case nomem => { sepfailnomem(); return false; }; + }; + next.len = cap; + let i: i32 = 0; + for (i < used) { next[i] = (*frames)[i]; i += 1; }; + if (frames.ptr != nil) { + os.free(frames.ptr: *void, + (frames.cap: u64) * (size(seploadframe): u64)); + }; + *frames = next; + return true; +}; + +fn sepfinishloadframes(frames: []seploadframe, result: i32) i32 = { + if (frames.ptr != nil) { + os.free(frames.ptr: *void, + (frames.cap: u64) * (size(seploadframe): u64)); + }; + return result; +}; + +// Load pi once: a directory node takes ownership of its sorted production +// paths, then every selected-root context verifies the same canonical import +// bindings before the package is compiled once. +fn seploadpkg(g: *sepgraph, pi: i32, context: i32) i32 = { + if (pi < 0 || pi >= g.n || context < 0 || context >= g.ncontext) { + return -1; + }; + if (g.pkg[pi].testsupport + && g.supportcontext >= 0) { context = g.supportcontext; }; + let frames: []seploadframe; + let nframe: i32 = 0; + if (!sepreserveloadframes(&frames, nframe, 1)) { return -2; }; + frames[0].pkg = pi; + frames[0].context = context; + frames[0].nextdep = -1; + frames[0].pendingdep = -1; + nframe = 1; + for (nframe > 0) { + let f: *seploadframe = &frames[nframe - 1]; + if (f.nextdep < 0) { + let state: u8 = sepcontextstate(&g.pkg[f.pkg], f.context); + if (state == 2u8) { + if (g.pkg[f.pkg].failed) { + let fi: i32 = 0; + for (fi < nframe) { + g.pkg[frames[fi].pkg].failed = true; + fi += 1; + }; + return sepfinishloadframes(frames, -1); + }; + nframe -= 1; + continue; + }; + if (state == 1u8) { + nframe -= 1; + continue; + }; + if (seppreparepkgcontext(g, f.pkg, f.context) < 0) { + let fi: i32 = 0; + for (fi < nframe) { + g.pkg[frames[fi].pkg].failed = true; + fi += 1; + }; + if (sepfatalallocation) { + return sepfinishloadframes(frames, -2); + }; + return sepfinishloadframes(frames, -1); + }; + f.nextdep = 0; + }; + if (f.pendingdep >= 0) { + let dep: i32 = f.pendingdep; + f.pendingdep = -1; + if (dep != f.pkg && sepforbiddencommandimport(g, f.pkg, dep)) { + cerr("ww: package "); + if (g.pkg[dep].path[0u64] != 0u8) { + cerr(pathstr(g.pkg[dep].path)); + } else { cerr(pathstr(g.pkg[dep].canon)); }; + cerr(" is a program, not an importable package\n"); + let fi: i32 = 0; + for (fi < nframe) { + g.pkg[frames[fi].pkg].failed = true; + fi += 1; + }; + return sepfinishloadframes(frames, -1); + }; + }; + if (f.nextdep >= g.pkg[f.pkg].ndeps) { + nframe -= 1; + continue; + }; + let dep: i32 = g.pkg[f.pkg].deps[f.nextdep]; + f.nextdep += 1; + f.pendingdep = dep; + let childcontext: i32 = f.context; + if (g.pkg[dep].testsupport && g.supportcontext >= 0) { + childcontext = g.supportcontext; + }; + if (nframe == SEP_COUNT_MAX) { + sepfailsize(); + let fi: i32 = 0; + for (fi < nframe) { + g.pkg[frames[fi].pkg].failed = true; + fi += 1; + }; + return sepfinishloadframes(frames, -2); + }; + if (!sepreserveloadframes(&frames, nframe, nframe + 1)) { + let fi: i32 = 0; + for (fi < nframe) { + g.pkg[frames[fi].pkg].failed = true; + fi += 1; + }; + return sepfinishloadframes(frames, -2); + }; + frames[nframe].pkg = dep; + frames[nframe].context = childcontext; + frames[nframe].nextdep = -1; + frames[nframe].pendingdep = -1; + nframe += 1; + }; + return sepfinishloadframes(frames, 0); +}; + fn sepimportcomponent(s: *u8, n: u64) bool = { if (n == 0u64) { return false; }; let first: u8 = s[0u64]; @@ -1983,7 +2647,7 @@ fn sepordinarydeclaredname(p: *seppkg) *u8 = { }; n -= 5u64; }; - return arenadupcstr(p.name, n); + return sepdupcstr(p.name, n); }; // The reserved local namespace is reversible, so filesystem identity never @@ -1991,10 +2655,15 @@ fn sepordinarydeclaredname(p: *seppkg) *u8 = { fn seplocalimportbase(p: *seppkg) *u8 = { let leaf: *u8 = sepordinarydeclaredname(p); if (leaf == nil) { return nil; }; - let need: u64 = SEP_LOCAL_IMPORT_PREFIX.len: u64 + 2u64 - + cstrlen(p.canon) * 4u64 + 1u64 + cstrlen(leaf) + 1u64; - let out: []u8 = alloc([], need)!; - out.len = need: i32; + let need: u64 = 0u64; + if (!sepaddbytes(&need, SEP_LOCAL_IMPORT_PREFIX.len: u64) + || !sepaddbytes(&need, 2u64) + || !sepmuladdbytes(&need, cstrlen(p.canon), 4u64) + || !sepaddbytes(&need, 1u64) + || !sepaddbytes(&need, cstrlen(leaf)) + || !sepaddbytes(&need, 1u64)) { return nil; }; + let out: []u8; + if (!sepmakebytes(need, &out)) { return nil; }; let off: u64 = strinto(out.ptr, 0u64, SEP_LOCAL_IMPORT_PREFIX); off = byteinto(out.ptr, off, '.': u8); off = byteinto(out.ptr, off, 'p': u8); @@ -2039,10 +2708,12 @@ fn sepfinalizedirectoryidentities(g: *sepgraph) i32 = { && p.role != SEP_ROLE_TEST_SUPPORT && p.importbase == nil) { let ci: i32 = 0; for (ci < g.ncontext) { - if (p.contextstate[ci] == 2u8) { + if (sepcontextstate(p, ci) == 2u8) { let candidatesz: u64 = cstrlen(p.canon) + 1u64; - let candidate: []u8 = alloc([], candidatesz)!; - candidate.len = candidatesz: i32; + let candidate: []u8; + if (!sepmakebytes(candidatesz, &candidate)) { + return -1; + }; let found: i32 = sepreverseimportbase(g, p, ci, candidate.ptr, candidatesz); if (found < 0) { return -1; }; @@ -2104,10 +2775,13 @@ fn sepfinalizedirectoryidentities(g: *sepgraph) i32 = { }; p.artifact = nil; if (p.variant == SEP_VARIANT_SAME_TEST) { - p.artifact = appendlit(p.path, "-internal-test"); + p.artifact = sepappendlit(p.path, "-internal-test"); } else { if (p.variant == SEP_VARIANT_EXTERNAL) { - p.artifact = appendlit(p.path, "-external-test"); + p.artifact = sepappendlit(p.path, "-external-test"); }; }; + if ((p.variant == SEP_VARIANT_SAME_TEST + || p.variant == SEP_VARIANT_EXTERNAL) + && p.artifact == nil) { return -1; }; }; pi += 1; }; @@ -2118,41 +2792,106 @@ fn sepcyclenode(p: *u8) void = { if (p[0] == 0u8) { cerr("(root)"); } else { cerr(pathstr(p)); }; }; -// DFS post-order over the dep DAG → reverse-topo (deps before importer). -// Tri-color: a gray back-edge is a loud dep-cycle reject naming the chain -// (Hare deps.ha:243); stack[0..depth) is the live DFS path, so the cycle -// runs from pi's first occurrence on it to the top, closing on pi. -// Cite Hare gather (deps.ha:123). +type septopoframe = struct { + pkg: i32, + nextdep: i32, +}; + +fn sepalloctopoframes(cap: i32) ([]septopoframe | nomem) = { + let value: []septopoframe = alloc([], cap: u64)?; + return value; +}; + +fn sepreservetopoframes(frames: *[]septopoframe, used: i32, + need: i32) bool = { + if (need <= frames.len) { return true; }; + let cap: i32 = sepgrowcap(frames.len, need); + if (cap < 0) { return false; }; + let allocation: ([]septopoframe | nomem) = sepalloctopoframes(cap); + let next: []septopoframe; + match (allocation) { + case let v: []septopoframe => next = v; + case nomem => { sepfailnomem(); return false; }; + }; + next.len = cap; + let i: i32 = 0; + for (i < used) { next[i] = (*frames)[i]; i += 1; }; + if (frames.ptr != nil) { + os.free(frames.ptr: *void, + (frames.cap: u64) * (size(septopoframe): u64)); + }; + *frames = next; + return true; +}; + +fn sepfinishtopoframes(frames: []septopoframe, result: i32) i32 = { + if (frames.ptr != nil) { + os.free(frames.ptr: *void, + (frames.cap: u64) * (size(septopoframe): u64)); + }; + return result; +}; + +// Iterative DFS post-order over the dep DAG → reverse-topo (deps before +// importer). Tri-color and stack[0..nframe) retain the exact live path and +// deterministic cycle diagnostic without consuming one native frame/action. fn septopovisit(g: *sepgraph, pi: i32, order: []i32, no: *i32, stack: []i32, depth: i32) i32 = { if (g.pkg[pi].color == 2) { return 0; }; if (g.pkg[pi].color == 1) { - let j: i32 = 0; - for (j < depth && stack[j] != pi) { j += 1; }; cerr("ww: dependency cycle: "); - let s: i32 = j; - for (s < depth) { - sepcyclenode(g.pkg[stack[s]].path); - cerr(" -> "); - s += 1; - }; sepcyclenode(g.pkg[pi].path); cerr("\n"); return -1; }; + let frames: []septopoframe; + let nframe: i32 = 0; + if (!sepreservetopoframes(&frames, nframe, 1)) { return -2; }; g.pkg[pi].color = 1; - stack[depth] = pi; - let k: i32 = 0; - for (k < g.pkg[pi].ndeps) { - if (septopovisit(g, g.pkg[pi].deps[k], order, no, stack, depth + 1) < 0) { - return -1; + stack[0] = pi; + frames[0].pkg = pi; + frames[0].nextdep = 0; + nframe = 1; + for (nframe > 0) { + let f: *septopoframe = &frames[nframe - 1]; + if (f.nextdep < g.pkg[f.pkg].ndeps) { + let dep: i32 = g.pkg[f.pkg].deps[f.nextdep]; + f.nextdep += 1; + if (g.pkg[dep].color == 2) { continue; }; + if (g.pkg[dep].color == 1) { + let j: i32 = 0; + for (j < nframe && stack[j] != dep) { j += 1; }; + cerr("ww: dependency cycle: "); + let s: i32 = j; + for (s < nframe) { + sepcyclenode(g.pkg[stack[s]].path); + cerr(" -> "); + s += 1; + }; + sepcyclenode(g.pkg[dep].path); + cerr("\n"); + return sepfinishtopoframes(frames, -1); + }; + if (nframe == SEP_COUNT_MAX) { + sepfailsize(); + return sepfinishtopoframes(frames, -2); + }; + if (!sepreservetopoframes(&frames, nframe, nframe + 1)) { + return sepfinishtopoframes(frames, -2); + }; + g.pkg[dep].color = 1; + stack[nframe] = dep; + frames[nframe].pkg = dep; + frames[nframe].nextdep = 0; + nframe += 1; + continue; }; - k += 1; + g.pkg[f.pkg].color = 2; + order[*no] = f.pkg; + *no += 1; + nframe -= 1; }; - g.pkg[pi].color = 2; - order[*no] = pi; - *no += 1; - return 0; + return sepfinishtopoframes(frames, 0); }; fn sepinternalreplacesproduction(g: *sepgraph, a: i32, b: i32) bool = { @@ -2304,8 +3043,13 @@ fn archiveo(objpath: *u8, apath: *u8) i32 = { if ((objn & 1u64) != 0u64) { pad = 1u64; }; // ar(5) fixes the archive magic at 8 bytes and each serialized // member header at 60 bytes. - let total: u64 = 8u64 + 60u64 + objn + pad; - let outs: []u8 = alloc([], total)!; + let total: u64 = 0u64; + if (!sepaddbytes(&total, 8u64) || !sepaddbytes(&total, 60u64) + || !sepaddbytes(&total, objn) || !sepaddbytes(&total, pad)) { + return -1; + }; + let outs: []u8; + if (!sepmakebytes(total, &outs)) { return -1; }; let out: *u8 = outs.ptr; // 60-byte member header at offset 8, ASCII space-filled, fields @@ -2413,6 +3157,7 @@ fn filesizenonzero(path: *u8) bool = { fn sepvalidateunitowner(g: *sepgraph, pi: i32, scratch: *u8) i32 = { let unit: *u8 = sepfname(g, pi, scratch, ".unit.ww"); + if (unit == nil) { return -1; }; let fi: os.filestat; match (os.lstat(&fi, pathstr(unit))) { case let e: os.oserror => { @@ -2481,10 +3226,14 @@ fn fileequal(a: *u8, b: *u8) bool = { if (fa < 0) { return false; }; let fb: i32 = os.open(pathstr(b), os.flag.RDONLY, 0i32); if (fb < 0) { os.close(fa); return false; }; - let bufa: []u8 = alloc([], 65536u64)!; - bufa.len = 65536; - let bufb: []u8 = alloc([], 65536u64)!; - bufb.len = 65536; + let bufa: []u8; + if (!sepmakebytes(65536u64, &bufa)) { + os.close(fa); os.close(fb); return false; + }; + let bufb: []u8; + if (!sepmakebytes(65536u64, &bufb)) { + os.close(fa); os.close(fb); return false; + }; let eq: bool = true; let done: bool = false; for (!done) { @@ -2510,14 +3259,17 @@ fn fileequal(a: *u8, b: *u8) bool = { // Replace dst with src's bytes via temp + rename, so a torn write can // never masquerade as a committed tool copy. fn copyfileatomic(src: *u8, dst: *u8) i32 = { - let tmpp: *u8 = appendlit(dst, ".new"); + let tmpp: *u8 = sepappendlit(dst, ".new"); + if (tmpp == nil) { return -1; }; let in: i32 = os.open(pathstr(src), os.flag.RDONLY, 0i32); if (in < 0) { return -1; }; let out: i32 = os.open(pathstr(tmpp), os.flag.WRONLY | os.flag.CREATE | os.flag.TRUNC, 420i32); // 0o644 if (out < 0) { os.close(in); return -1; }; - let buf: []u8 = alloc([], 65536u64)!; - buf.len = 65536; + let buf: []u8; + if (!sepmakebytes(65536u64, &buf)) { + os.close(in); os.close(out); return -1; + }; let bad: bool = false; let done: bool = false; for (!done) { @@ -2558,8 +3310,8 @@ fn workdirstamptext(istest: i32, emitasm: i32) str = { fn stampmatches(path: *u8, want: str) bool = { let fd: i32 = os.open(pathstr(path), os.flag.RDONLY, 0i32); if (fd < 0) { return false; }; - let buf: []u8 = alloc([], 128u64)!; - buf.len = 128; + let buf: []u8; + if (!sepmakebytes(128u64, &buf)) { os.close(fd); return false; }; let n: i64 = os.read(fd, buf.ptr, 127u64); os.close(fd); if (n < 0) { return false; }; @@ -2574,7 +3326,8 @@ fn stampmatches(path: *u8, want: str) bool = { }; fn writestampatomic(path: *u8, want: str) i32 = { - let tmpp: *u8 = appendlit(path, ".new"); + let tmpp: *u8 = sepappendlit(path, ".new"); + if (tmpp == nil) { return -1; }; let fd: i32 = os.open(pathstr(tmpp), os.flag.WRONLY | os.flag.CREATE | os.flag.TRUNC, 420i32); // 0o644 if (fd < 0) { return -1; }; @@ -2592,7 +3345,8 @@ fn writestampatomic(path: *u8, want: str) i32 = { // product from a caller-owned binary left by an earlier invocation. fn recordproductstatus(path: *u8) i32 = { if (path == nil) { return 0; }; - let tmpp: *u8 = appendlit(path, ".new"); + let tmpp: *u8 = sepappendlit(path, ".new"); + if (tmpp == nil) { return -1; }; let fd: i32 = os.open(pathstr(tmpp), os.flag.WRONLY | os.flag.CREATE | os.flag.TRUNC, 420i32); if (fd < 0) { return -1; }; @@ -2614,8 +3368,8 @@ fn recordproductstatus(path: *u8) i32 = { fn invalidateworkdirunits(scratch: *u8) i32 = { let fd: i32 = os.open(pathstr(scratch), os.flag.RDONLY, 0i32); if (fd < 0) { return -1; }; - let buf: []u8 = alloc([], 8192u64)!; - buf.len = 8192; + let buf: []u8; + if (!sepmakebytes(8192u64, &buf)) { os.close(fd); return -1; }; let rc: i32 = 0; let r: i64 = os.getdents64(fd, buf.ptr, 8192u64); for (r > 0i64 && rc == 0) { @@ -2661,7 +3415,8 @@ fn buildonesepimpl(selfdir: *u8, src: *u8, entryisdir: i32, products: *sepproduct, nproducts: i32, emitasm: i32, workdir: *u8, scratchout: **u8, graphout: **sepgraph) i32 = { - if (nproducts < 1 || nproducts > SEP_MAXPRODUCT) { return 1; }; + sepfatalallocation = false; + if (nproducts < 1) { return 1; }; let statusi: i32 = 0; for (statusi < nproducts) { if (products[statusi].status != nil) { @@ -2673,11 +3428,16 @@ fn buildonesepimpl(selfdir: *u8, src: *u8, entryisdir: i32, let c6: *u8 = toolpath(selfdir, "WW_W6C", "w6c_ww"); let a6: *u8 = toolpath(selfdir, "WW_W6A", "w6a_ww"); let l6: *u8 = toolpath(selfdir, "WW_W6L", "w6l_ww"); + if (c6 == nil || a6 == nil || l6 == nil) { return 1; }; let libdir: *u8 = envpath("WW_LIB"); - if (libdir == nil) { libdir = joinpathlit(selfdir, "../lib"); }; + if (sepfatalallocation) { return 1; }; + if (libdir == nil) { libdir = sepjoinpathlit(selfdir, "../lib"); }; + if (libdir == nil) { return 1; }; let toolsrcdir: *u8 = envpath("WW_SRCLIB"); + if (sepfatalallocation) { return 1; }; if (toolsrcdir == nil) { - let candidate: *u8 = joinpathlit(selfdir, "../../lib"); + let candidate: *u8 = sepjoinpathlit(selfdir, "../../lib"); + if (candidate == nil) { return 1; }; if (os.access(pathstr(candidate), 0i32) == 0) { toolsrcdir = candidate; } else { if (os.access("lib", 0i32) == 0) { @@ -2687,8 +3447,8 @@ fn buildonesepimpl(selfdir: *u8, src: *u8, entryisdir: i32, }; }; }; - let srcd: []u8 = alloc([], (os.PATH_MAX: u64))!; - srcd.len = os.PATH_MAX; + let srcd: []u8; + if (!sepmakebytes(os.PATH_MAX: u64, &srcd)) { return 1; }; if (entryisdir != 0) { let slen: u64 = cstrlen(src); let k: u64 = 0u64; @@ -2721,13 +3481,17 @@ fn buildonesepimpl(selfdir: *u8, src: *u8, entryisdir: i32, if (entryisdir != 0) { let dlen: u64 = cstrlen(srcd.ptr); let bo: u64 = basenameoff(srcd.ptr, dlen); - stem = joinpath(srcd.ptr, srcd.ptr + bo); + stem = sepjoinpath(srcd.ptr, srcd.ptr + bo); } else { - let stembuf: []u8 = alloc([], cstrlen(src) + 1u64)!; - stembuf.len = (cstrlen(src) + 1u64): i32; + let stembuf: []u8; + let stemneed: u64 = 0u64; + if (!sepaddbytes(&stemneed, cstrlen(src)) + || !sepaddbytes(&stemneed, 1u64) + || !sepmakebytes(stemneed, &stembuf)) { return 1; }; makestem(stembuf.ptr, src); stem = stembuf.ptr; }; + if (stem == nil) { return 1; }; let effstem: *u8 = stem; if (objstem != nil) { effstem = objstem; }; let warm: bool = false; @@ -2755,7 +3519,8 @@ fn buildonesepimpl(selfdir: *u8, src: *u8, entryisdir: i32, // cleans it. scratch = workdir; } else { - scratch = appendlit(effstem, ".sepwork"); + scratch = sepappendlit(effstem, ".sepwork"); + if (scratch == nil) { return 1; }; if (os.mkdir(pathstr(scratch), 493i32) != 0) { cerr("ww: cannot create scratch\n"); return 1; @@ -2777,10 +3542,13 @@ fn buildonesepimpl(selfdir: *u8, src: *u8, entryisdir: i32, cerrpath("ww: cannot read driver identity ", selfpath, "\n"); return 1; }; - toolw = joinpathlit(scratch, ".wwtool.ww"); - toolc = joinpathlit(scratch, ".wwtool.w6c"); - toola = joinpathlit(scratch, ".wwtool.w6a"); - stampf = joinpathlit(scratch, ".wwtool.stamp"); + toolw = sepjoinpathlit(scratch, ".wwtool.ww"); + toolc = sepjoinpathlit(scratch, ".wwtool.w6c"); + toola = sepjoinpathlit(scratch, ".wwtool.w6a"); + stampf = sepjoinpathlit(scratch, ".wwtool.stamp"); + if (toolw == nil || toolc == nil || toola == nil || stampf == nil) { + return 1; + }; stampwant = workdirstamptext(istest, emitasm); stampok = stampmatches(stampf, stampwant); staleall = !stampok; @@ -2796,41 +3564,53 @@ fn buildonesepimpl(selfdir: *u8, src: *u8, entryisdir: i32, }; }; }; + if (sepfatalallocation) { return 1; }; - let rtpaths: []*u8 = alloc([], 2u64)!; + let rtallocation: ([]*u8 | nomem) = sepallocptrs(2); + let rtpaths: []*u8; + match (rtallocation) { + case let value: []*u8 => rtpaths = value; + case nomem => { sepfailnomem(); return 1; }; + }; rtpaths.len = 2; let nrt: i32 = 1; let havearchive: bool = false; if (cstrlen(libdir) + 1u64 + "libwwrt.a".len: u64 + 1u64 <= os.PATH_MAX: u64) { - rtpaths[0] = joinpathlit(libdir, "libwwrt.a"); + rtpaths[0] = sepjoinpathlit(libdir, "libwwrt.a"); + if (rtpaths[0] == nil) { return 1; }; if (os.access(pathstr(rtpaths[0]), 0i32) == 0) { havearchive = true; }; }; if (!havearchive) { nrt = 2; - rtpaths[0] = joinpathlit(selfdir, "../obj/rt/start.o"); - rtpaths[1] = joinpathlit(selfdir, "../obj/rt/syscall.o"); + rtpaths[0] = sepjoinpathlit(selfdir, "../obj/rt/start.o"); + rtpaths[1] = sepjoinpathlit(selfdir, "../obj/rt/syscall.o"); + if (rtpaths[0] == nil || rtpaths[1] == nil) { return 1; }; }; - let pkgslot: []seppkg = alloc([], SEP_MAXPKG: u64)!; - pkgslot.len = SEP_MAXPKG; - let contextslot: []sepcontext = alloc([], SEP_MAXCONTEXT: u64)!; - contextslot.len = SEP_MAXCONTEXT; - let g: *sepgraph = alloc(sepgraph{ + let pkgslot: []seppkg; + let contextslot: []sepcontext; + let graphallocation: (*sepgraph | nomem) = alloc(sepgraph{ pkg = pkgslot, n = 0, context = contextslot, ncontext = 0, supportcontext = -1, identityfailed = false, - })!; + }); + let g: *sepgraph; + match (graphallocation) { + case let value: *sepgraph => g = value; + case nomem => { sepfailnomem(); return 1; }; + }; if (graphout != nil) { *graphout = g; }; - let supportfor: []i32 = alloc([], nproducts: u64)!; - supportfor.len = nproducts; let producti: i32 = 0; - for (producti < nproducts) { supportfor[producti] = -1; producti += 1; }; + for (producti < nproducts) { + products[producti].support = -1; + producti += 1; + }; producti = 0; for (producti < nproducts) { let entry: *u8 = src; @@ -2914,7 +3694,7 @@ fn buildonesepimpl(selfdir: *u8, src: *u8, entryisdir: i32, if (rootissupport && syntax.streq(testsupportmodule, "test") && products[producti].variant != SEP_VARIANT_EXTERNAL) { - supportfor[producti] = root; + products[producti].support = root; producti += 1; continue; }; @@ -2928,7 +3708,7 @@ fn buildonesepimpl(selfdir: *u8, src: *u8, entryisdir: i32, }; if (ti < 0) { return 1; }; g.pkg[ti].testsupport = true; - supportfor[producti] = ti; + products[producti].support = ti; producti += 1; }; }; @@ -2939,22 +3719,15 @@ fn buildonesepimpl(selfdir: *u8, src: *u8, entryisdir: i32, // Raw single-file tests retain the explicit fixture exception: test-main // synthesis stays in that action and support remains a direct export. if (istest != 0 && entryisdir == 0) { - let support: i32 = supportfor[producti]; - if (support >= 0 && support != root) { - let seen: bool = false; - let sk: i32 = 0; - for (sk < g.pkg[root].ndeps) { - if (g.pkg[root].deps[sk] == support) { seen = true; }; - sk += 1; - }; - if (!seen) { - g.pkg[root].deps[g.pkg[root].ndeps] = support; - g.pkg[root].ndeps += 1; - }; - }; + let support: i32 = products[producti].support; + if (support >= 0 && support != root + && !sepadddep(g, root, support)) { return 1; }; g.pkg[root].linkentry = true; }; - if (seploadpkg(g, root, products[producti].context) < 0) { + let loadresult: i32 = seploadpkg(g, root, + products[producti].context); + if (loadresult == -2) { return 1; }; + if (loadresult < 0) { g.pkg[root].failed = true; producti += 1; continue; @@ -2971,9 +3744,12 @@ fn buildonesepimpl(selfdir: *u8, src: *u8, entryisdir: i32, producti = 0; for (producti < nproducts) { let variant: i32 = products[producti].variantroot; - let support: i32 = supportfor[producti]; + let support: i32 = products[producti].support; if (support >= 0 && support != variant) { - if (seploadpkg(g, support, products[producti].context) < 0) { + let loadresult: i32 = seploadpkg(g, support, + products[producti].context); + if (loadresult == -2) { return 1; }; + if (loadresult < 0) { g.pkg[variant].failed = true; }; }; @@ -2986,7 +3762,7 @@ fn buildonesepimpl(selfdir: *u8, src: *u8, entryisdir: i32, producti = 0; for (producti < nproducts) { let variant: i32 = products[producti].variantroot; - let support: i32 = supportfor[producti]; + let support: i32 = products[producti].support; if (g.pkg[variant].failed || (support >= 0 && g.pkg[support].failed)) { products[producti].root = variant; @@ -3011,10 +3787,11 @@ fn buildonesepimpl(selfdir: *u8, src: *u8, entryisdir: i32, }; let ci: i32 = 0; - let order: []i32 = alloc([], g.n: u64)!; - order.len = g.n; - let stack: []i32 = alloc([], g.n: u64)!; - stack.len = g.n; + let order: []i32; + let stack: []i32; + if (!sepmakeints(g.n, &order) || !sepmakeints(g.n, &stack)) { + return 1; + }; let norder: i32 = 0; // Diagnose cycles per product before constructing the shared union. A // variant-local cycle must not suppress an independent sibling root. @@ -3025,8 +3802,10 @@ fn buildonesepimpl(selfdir: *u8, src: *u8, entryisdir: i32, ci = 0; for (ci < g.n) { g.pkg[ci].color = 0; ci += 1; }; let ignored: i32 = 0; - if (septopovisit(g, root, order, - &ignored, stack, 0) < 0 + let topores: i32 = septopovisit(g, root, order, + &ignored, stack, 0); + if (topores == -2) { return 1; }; + if (topores < 0 || sepvalidatemoduleclosure(g, order, ignored, true) < 0) { g.pkg[root].failed = true; }; @@ -3075,6 +3854,9 @@ fn buildonesepimpl(selfdir: *u8, src: *u8, entryisdir: i32, let asmnew: *u8 = sepfname(g, pi, scratch, ".s.new"); let objnew: *u8 = sepfname(g, pi, scratch, ".o.new"); let anew: *u8 = sepfname(g, pi, scratch, ".a.new"); + if (unitf == nil || wwi == nil || asmf == nil || objf == nil + || apath == nil || unitnew == nil || wwinew == nil + || asmnew == nil || objnew == nil || anew == nil) { return 1; }; // Warm mode compiles from staged `.new` paths and commits by // rename; classic mode keeps its exact in-place paths. let cu: *u8 = unitf; @@ -3116,6 +3898,7 @@ fn buildonesepimpl(selfdir: *u8, src: *u8, entryisdir: i32, }; }; }; + if (sepfatalallocation) { return 1; }; if (fresh) { if (os.remove(pathstr(unitnew)) != 0) { cerrpath("ww: cannot remove ", unitnew, "\n"); @@ -3134,16 +3917,25 @@ fn buildonesepimpl(selfdir: *u8, src: *u8, entryisdir: i32, let commandpkg: bool = sepcommandcompilermarker(g, pi); let entry: bool = g.pkg[pi].linkentry; let supportpkg: bool = g.pkg[pi].testsupport; - let alen: u64 = 8u64; - if (gent) { alen += 4u64; } + let alen: i32 = 8; + if (gent) { alen += 4; } else { - if (testpkg) { alen += 1u64; }; - if (commandpkg) { alen += 1u64; }; - if (entry) { alen += 1u64; }; - if (supportpkg) { alen += 2u64; }; + if (testpkg) { alen += 1; }; + if (commandpkg) { alen += 1; }; + if (entry) { alen += 1; }; + if (supportpkg) { alen += 2; }; + }; + if (g.pkg[pi].ndeps > (SEP_COUNT_MAX - alen) / 3) { + sepfailsize(); + return 1; + }; + alen += g.pkg[pi].ndeps * 3; + let allocation: ([]str | nomem) = sepallocstrs(alen); + let argv: []str; + match (allocation) { + case let value: []str => argv = value; + case nomem => { sepfailnomem(); return 1; }; }; - alen += (g.pkg[pi].ndeps: u64) * 3u64; - let argv: []str = alloc([], alen)!; append(argv, "w6c"); if (gent) { append(argv, "-T"); @@ -3165,7 +3957,9 @@ fn buildonesepimpl(selfdir: *u8, src: *u8, entryisdir: i32, let dj: i32 = g.pkg[pi].deps[importk]; append(argv, "--import"); append(argv, pathstr(g.pkg[dj].path)); - append(argv, pathstr(sepfname(g, dj, scratch, ".wwi"))); + let depinterface: *u8 = sepfname(g, dj, scratch, ".wwi"); + if (depinterface == nil) { return 1; }; + append(argv, pathstr(depinterface)); importk += 1; }; append(argv, "-I"); @@ -3197,8 +3991,14 @@ fn buildonesepimpl(selfdir: *u8, src: *u8, entryisdir: i32, if (!warm || !fileequal(wwinew, wwi)) { g.pkg[pi].exportchanged = true; }; + if (sepfatalallocation) { return 1; }; if (emitasm == 0) { - let argv: []str = alloc([], 4u64)!; + let argallocation: ([]str | nomem) = sepallocstrs(4); + let argv: []str; + match (argallocation) { + case let value: []str => argv = value; + case nomem => { sepfailnomem(); return 1; }; + }; append(argv, "w6a"); append(argv, "-o"); append(argv, pathstr(co)); @@ -3285,20 +4085,26 @@ fn buildonesepimpl(selfdir: *u8, src: *u8, entryisdir: i32, // successful units remain safe to vouch for when a sibling root fails; // a killed pass retains the old identity and invalidates again next time. if (warm) { - if (!fileequal(toolw, selfpath)) { + let sametool: bool = fileequal(toolw, selfpath); + if (sepfatalallocation) { return 1; }; + if (!sametool) { if (copyfileatomic(selfpath, toolw) != 0) { cerrpath("ww: cannot record ", toolw, "\n"); return 1; }; }; - if (!fileequal(toolc, c6)) { + sametool = fileequal(toolc, c6); + if (sepfatalallocation) { return 1; }; + if (!sametool) { if (copyfileatomic(c6, toolc) != 0) { cerrpath("ww: cannot record ", toolc, "\n"); return 1; }; }; if (emitasm == 0) { - if (!fileequal(toola, a6)) { + sametool = fileequal(toola, a6); + if (sepfatalallocation) { return 1; }; + if (!sametool) { if (copyfileatomic(a6, toola) != 0) { cerrpath("ww: cannot record ", toola, "\n"); return 1; @@ -3318,7 +4124,8 @@ fn buildonesepimpl(selfdir: *u8, src: *u8, entryisdir: i32, if (g.pkg[root].failed) { return 1; }; let archive: *u8 = sepfname(g, root, scratch, ".a"); let iface: *u8 = sepfname(g, root, scratch, ".wwi"); - let outiface: *u8 = appendlit(out, ".wwi"); + let outiface: *u8 = sepappendlit(out, ".wwi"); + if (archive == nil || iface == nil || outiface == nil) { return 1; }; if (copyfileatomic(archive, out) != 0 || copyfileatomic(iface, outiface) != 0) { cerrpath("ww: cannot write package artifact ", out, "\n"); @@ -3350,16 +4157,37 @@ fn buildonesepimpl(selfdir: *u8, src: *u8, entryisdir: i32, }; ci = 0; for (ci < g.n) { g.pkg[ci].color = 0; ci += 1; }; - let linkorder: []i32 = alloc([], g.n: u64)!; - linkorder.len = g.n; - let linkstack: []i32 = alloc([], g.n: u64)!; - linkstack.len = g.n; + let linkorder: []i32; + let linkstack: []i32; + if (!sepmakeints(g.n, &linkorder) + || !sepmakeints(g.n, &linkstack)) { return 1; }; let nlink: i32 = 0; if (septopovisit(g, root, linkorder, &nlink, linkstack, 0) < 0) { return 1; }; - // argv: 3 fixed + closure + runtime inputs + joined flags + nil. - let total: i32 = 3 + nlink + nrt + nldirs + nllibs + 1; - let largv: []*u8 = alloc([], total: u64)!; + // argv: 3 fixed + closure + runtime inputs + flag/value pairs + nil. + let total: i32 = 4; + if (nlink > SEP_COUNT_MAX - total) { + sepfailsize(); return 1; + }; + total += nlink; + if (nrt > SEP_COUNT_MAX - total) { + sepfailsize(); return 1; + }; + total += nrt; + if (nldirs > (SEP_COUNT_MAX - total) / 2) { + sepfailsize(); return 1; + }; + total += nldirs * 2; + if (nllibs > (SEP_COUNT_MAX - total) / 2) { + sepfailsize(); return 1; + }; + total += nllibs * 2; + let largvallocation: ([]*u8 | nomem) = sepallocptrs(total); + let largv: []*u8; + match (largvallocation) { + case let value: []*u8 => largv = value; + case nomem => { sepfailnomem(); return 1; }; + }; largv.len = total; largv[0] = "w6l\0".ptr; largv[1] = "-o\0".ptr; @@ -3379,6 +4207,7 @@ fn buildonesepimpl(selfdir: *u8, src: *u8, entryisdir: i32, continue; }; largv[pos] = sepfname(g, pi, scratch, ".a"); + if (largv[pos] == nil) { return 1; }; pos += 1; li -= 1; }; @@ -3390,30 +4219,27 @@ fn buildonesepimpl(selfdir: *u8, src: *u8, entryisdir: i32, }; let k: i32 = 0; for (k < nldirs) { - let n: u64 = cstrlen(ldirs[k]); - let flag: []u8 = alloc([], n + 3u64)!; - flag.len = (n + 3u64): i32; - flag[0] = 45u8; flag[1] = 76u8; - bytecpy(flag.ptr + 2u64, ldirs[k], n); - flag[n + 2u64] = 0u8; - largv[pos] = flag.ptr; + largv[pos] = "-L\0".ptr; + pos += 1; + largv[pos] = ldirs[k]; pos += 1; k += 1; }; k = 0; for (k < nllibs) { - let n: u64 = cstrlen(llibs[k]); - let flag: []u8 = alloc([], n + 3u64)!; - flag.len = (n + 3u64): i32; - flag[0] = 45u8; flag[1] = 108u8; - bytecpy(flag.ptr + 2u64, llibs[k], n); - flag[n + 2u64] = 0u8; - largv[pos] = flag.ptr; + largv[pos] = "-l\0".ptr; + pos += 1; + largv[pos] = llibs[k]; pos += 1; k += 1; }; largv[pos] = nil; - let linkargs: []str = alloc([], pos: u64)!; + let linkallocation: ([]str | nomem) = sepallocstrs(pos); + let linkargs: []str; + match (linkallocation) { + case let value: []str => linkargs = value; + case nomem => { sepfailnomem(); return 1; }; + }; let ai: i32 = 0; for (ai < pos) { append(linkargs, pathstr(largv[ai])); @@ -3470,6 +4296,7 @@ fn buildonesep(selfdir: *u8, src: *u8, entryisdir: i32, product.variant = rootvariant; product.root = -1; product.variantroot = -1; + product.support = -1; let r: i32 = buildonesepimpl(selfdir, src, entryisdir, out, objstem, incs, lf, packageonly, istest, &product, 1, emitasm, workdir, &scratch, &g); @@ -4190,7 +5017,7 @@ fn dotest(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = { let outstem: *u8 = nil; let workdir: *u8 = nil; let requestidentity: *u8 = nil; - let products: []sepproduct = alloc([], SEP_MAXPRODUCT: u64)!; + let products: []sepproduct; let packageopts: bool = false; let afterdash: bool = false; let i: i32 = start; @@ -4213,7 +5040,7 @@ fn dotest(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = { continue; }; if (cstreqlit(p, "--ww-package-test")) { - if (i + 5 >= argc || products.len >= SEP_MAXPRODUCT) { + if (i + 5 >= argc) { cerr("ww test: --ww-package-test needs kind, package, directory, output, and status\n"); return 2; }; @@ -4252,6 +5079,13 @@ fn dotest(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = { product.variant = variant; product.root = -1; product.variantroot = -1; + product.support = -1; + if (products.len == SEP_COUNT_MAX) { + sepfailsize(); + return 1; + }; + if (!sepreserveproducts(&products, + products.len + 1)) { return 1; }; append(products, product); i += 6; continue;