commit c717fb5b4469200bea55ac296bb38372c7d33218
parent 78317497c377709d6bffd9f97385ff3923766c33
Author: Ryan Sepassi <rsepassi@gmail.com>
Date: Tue, 9 Jun 2026 20:56:49 -0700
cleanup wave B: driver/dist consolidation (use the shared seam)
Adversarial review backlog, Wave B (doc/plan/CLEANUP-2026-06-09.md). Route
duplicated glue through the abstraction that already exists.
E.1 + T1.9/E.2 — driver link pipeline: cc_run_link_exe (~333 lines) and
build_run_link (~240) were near-identical. Factored the order-translate and
runtime-archive insertion into driver/lib/link_inputs.c:
- driver_link_inputs_build_order(): the DriverLinkKind -> KitLinkInputOrder
translation (SOURCE/SOURCE_MEMORY/OBJECT/ARCHIVE/DSO/LIB).
- driver_link_inputs_insert_runtime_archives(): the target-aware rt insert.
T1.9 payload: build-exe previously did a single end-insert and MISSED the
Windows two-position fix (libucrt.a _setjmp WEAK_EXTERNAL + late __chkstk_ms);
both cc and build-exe now route through the shared two-position helper, so
`kit build-exe -target ...-windows` no longer hits the 0xC0000139/chkstk
failures the cc workaround documents.
cc.c -130, build.c -54, link_inputs.c +136. Reconciled one drift: cc now sets
KitLinkArchiveInput.name for explicit .a inputs (diagnostic label) like build.
E.4 — dbg_jit_language_for_tag dropped its hardcoded tag->language map and
<dbg-jit.EXT> literals; resolves via kit_language_for_name + dbg_jit_default_name
(the P3 frontend-identity seam this file already used elsewhere). All
c/toy/asm/s/wasm/wat aliases + name.ext behavior preserved.
T1.7 — manifest.c and kpkg.c KV-line scanners now reject embedded NUL before
the strlen/%s field handling (tree.c already did); a NUL can no longer silently
truncate a value in an untrusted .kpkg/CAS parse.
T1.8 — unified the path-traversal validator: dist_manifest_path_valid and
pkg_locator_safe (src/api/package.c, byte-identical) now route through the
canonical dist_tree_path_valid (which has the strict \n/\r rejection); deleted
the two duplicate copies.
Deferred (documented in the plan): E.3 (env dedup, needs a Windows cross-build),
E.5 (flag/basename dedup, belongs in the driver util layer), F.4 diag-wrapper
(shared home is src/core/diag.{c,h}).
Verified: make lib bin + test-dist test-driver-cc test-driver-build
test-driver-ar test-link test-smoke-x64 test-smoke-rv64 test-toy test-cg-api
test-isa test-asm test-parse test-pp test-driver-tools — all green.
Diffstat:
9 files changed, 250 insertions(+), 237 deletions(-)
diff --git a/doc/plan/CLEANUP-2026-06-09.md b/doc/plan/CLEANUP-2026-06-09.md
@@ -64,14 +64,14 @@ Dormant or narrow today, but real defects.
the one-shot stays patched and resurfaces as a spurious `bp_id==0` stop misread as
step-completion. *Fix:* record each one-shot `bp_id`, clear on every non-self-completion
return path (one arm/resume/cleanup helper).
-- [ ] **T1.7 `.kpkg`/CAS parsers drop NUL rejection** — `src/dist/{tree,manifest,kpkg}.c`.
+- [x] **T1.7 `.kpkg`/CAS parsers drop NUL rejection** — `src/dist/{tree,manifest,kpkg}.c`.
KV-line scanner triplicated; only `tree.c` rejects embedded NUL, so a NUL silently
truncates a value in the others (via `strlen`/`%s`). *Fix:* shared `dist_kv_next_line`
iterator, or at minimum add NUL rejection to `manifest.c`/`kpkg.c`.
-- [ ] **T1.8 Path-traversal validator drift** — `src/dist/tree.c:39-54` + manifest.
+- [x] **T1.8 Path-traversal validator drift** — `src/dist/tree.c:39-54` + manifest.
`dist_tree_path_valid` and `pkg_locator_safe` byte-identical; `dist_manifest_path_valid`
omits the `\n`/`\r` rejection. *Fix:* all three share one core.
-- [ ] **T1.9 Windows runtime-insert fix only in `cc`** — `driver/cmd/cc.c:2455-2502` vs
+- [x] **T1.9 Windows runtime-insert fix only in `cc`** — `driver/cmd/cc.c:2455-2502` vs
`build.c:1963`. The two-position rt-archive insert (`_setjmp` WEAK_EXTERNAL + late
`__chkstk_ms`) lives only in `cc`; `build-exe` does the single end-insert and is liable
to the `0xC0000139`/chkstk failures the `cc` comment documents. *Fix:* extract
@@ -150,20 +150,20 @@ confirmed finding.
- [ ] **D.5** C-target data reloc width hardcoded — covered by **T1.3**.
### E. Driver/tool glue duplicated despite `lib/` seam (med→low, 9)
-- [ ] **E.1** Link pipeline (alloc/load/order-translate/emit/cleanup) copy-pasted between
+- [x] **E.1** Link pipeline (alloc/load/order-translate/emit/cleanup) copy-pasted between
`cc_run_link_exe` (`cc.c:2013-2346`, 333 lines) and `build_run_link` (240 lines). *Fix:*
`driver_link_inputs_build_order` + shared load/fill/emit/release helper in `link_inputs.c`.
-- [ ] **E.2** Windows two-position rt insert only in `cc` — covered by **T1.9** (lands via
+- [x] **E.2** Windows two-position rt insert only in `cc` — covered by **T1.9** (lands via
the E.1 shared helper).
-- [ ] **E.3** OS-neutral env code (stdio writer + thunks, `env→{context,jit_host,dbg_host}`
+- [~] **E.3** OS-neutral env code (stdio writer + thunks, `env→{context,jit_host,dbg_host}`
trio, dir-handle structs + read/close, `read_stdin` grow/shrink) byte-identical between
`driver/env/posix.c` and `windows.c`. *Fix:* move to `driver/env/common.c` + a shared
header; per-host keeps only a read-chunk callback.
-- [ ] **E.4** `dbg_jit_language_for_tag` hardcodes the tag→language map + `<dbg-jit.EXT>`
+- [x] **E.4** `dbg_jit_language_for_tag` hardcodes the tag→language map + `<dbg-jit.EXT>`
literals (`driver/cmd/dbg.c:1957-1981`), re-encoding frontend identity the P3 refactor
routed through `kit_language_for_name`. *Fix:* resolve via `kit_language_for_name` +
`dbg_jit_default_name`; delete the literal table.
-- [ ] **E.5** basename-stem+extension synthesis triplicated; codegen/link flag-parse blocks
+- [~] **E.5** basename-stem+extension synthesis triplicated; codegen/link flag-parse blocks
byte-identical across cc/build; `link_action` predicate computed 3 inconsistent ways.
*Fix:* shared driver helpers.
@@ -176,7 +176,7 @@ confirmed finding.
`strtab_add` 4× in the ELF linker. *Fix:* use the shared builder.
- [ ] **F.3** LEB128 encoders forked in `debug_emit`; `dw_skip_die_attrs` loop inlined 6×.
*Fix:* one encoder + one skip helper.
-- [ ] **F.4** path-join triplicated (2 copies in one dist file); `ctx->diag` varargs wrapper
+- [~] **F.4** path-join triplicated (2 copies in one dist file); `ctx->diag` varargs wrapper
copied across 4 API files; `run.c` reimplements `driver_parse_u64`/`driver_record_mcmodel`.
*Fix:* use the shared helpers.
- [ ] **F.5** TLS storage emission byte-identical between `define_tls_elf`/`define_tls_macho`
@@ -287,7 +287,18 @@ build + targeted tests + commit. Baseline captured in `build/baseline_summary.tx
T1.11, T1.12). Low risk, maximally parallel (disjoint files). Re-enabling
`-Werror=unused-function` (Q1) is the leverage move.
- **Wave B — Driver/dist consolidation** (E.1-E.5, T1.7, T1.8, T1.9, F.4). Disjoint from src
- backends.
+ backends. **LANDED:** E.1 (cc/build link pipeline → shared `driver_link_inputs_build_order`
+ + `driver_link_inputs_insert_runtime_archives`), T1.9/E.2 (build-exe now gets the Windows
+ two-position rt insert), E.4 (dbg lang-tag via `kit_language_for_name`/`dbg_jit_default_name`),
+ T1.7 (NUL rejection in manifest/kpkg KV scanners), T1.8 (`dist_manifest_path_valid` +
+ `pkg_locator_safe` routed through canonical `dist_tree_path_valid`). Also added the
+ **`test-dist`** target + read-side adversarial CAS coverage (untrusted-tree traversal /
+ absolute / NUL rejection).
+ **DEFERRED:** E.3 (OS-neutral env dedup posix.c↔windows.c→common.c) — needs a Windows
+ cross-build to verify `windows.c`, so split to its own step. E.5 (basename-stem + flag-parse
+ dedup) — the natural home is the driver util layer, not link_inputs.c; flag-parse blocks
+ aren't byte-identical (different option structs). F.4 diag-wrapper (the identical `*_diagf`
+ in core.c/cas.c/compress.c/package.c) — shared home is `src/core/diag.{c,h}`; do in Wave D.
- **Wave C — Cross-cutting backend extractions** (A.1, B.1-B.5, C.1-C.5, H.1). The "right
abstraction" core; agents partitioned by disjoint file sets; arch `native.c` files done
after Wave A.
diff --git a/driver/cmd/build.c b/driver/cmd/build.c
@@ -1270,49 +1270,10 @@ static int build_run_link(BuildOptions* o, KitCompiler* compiler,
if (build_open_output(ctx, env, o->tool, o->output_path, &out_w) != 0)
goto out;
- /* Translate the recorded link order into KitLinkInputOrder. */
- for (i = 0; i < o->inputs.nlink_items; ++i) {
- const DriverLinkItem* item = &o->inputs.link_items[i];
- KitLinkInputOrder* ord;
- switch ((DriverLinkKind)item->kind) {
- case DRIVER_LINK_SOURCE:
- if (!source_order_keep[item->index]) continue;
- ord = &order[norder++];
- ord->kind = KIT_LINK_INPUT_OBJ;
- ord->index = source_obj_index[item->index];
- break;
- case DRIVER_LINK_SOURCE_MEMORY:
- /* cc-only (in-memory stdin source); build never records it. */
- break;
- case DRIVER_LINK_OBJECT:
- ord = &order[norder++];
- ord->kind = KIT_LINK_INPUT_OBJ_BYTES;
- ord->index = item->index;
- break;
- case DRIVER_LINK_ARCHIVE:
- ord = &order[norder++];
- ord->kind = KIT_LINK_INPUT_ARCHIVE;
- ord->index = item->index;
- break;
- case DRIVER_LINK_DSO:
- ord = &order[norder++];
- ord->kind = KIT_LINK_INPUT_DSO;
- ord->index = item->index;
- break;
- case DRIVER_LINK_LIB: {
- const DriverPendingLib* pl = &o->inputs.pending_libs[item->index];
- ord = &order[norder++];
- if (pl->resolved_kind == DRIVER_LINK_DSO) {
- ord->kind = KIT_LINK_INPUT_DSO;
- ord->index = pl->resolved_index;
- } else {
- ord->kind = KIT_LINK_INPUT_ARCHIVE;
- ord->index = pl->resolved_index;
- }
- break;
- }
- }
- }
+ /* Translate the recorded link order into KitLinkInputOrder. (build never
+ * records SOURCE_MEMORY items, so nsource_files is immaterial here.) */
+ norder = driver_link_inputs_build_order(&o->inputs, source_obj_index,
+ source_order_keep, o->nsources, order);
{
KitLinkSessionOptions lopts;
@@ -1953,16 +1914,15 @@ static int build_main(int argc, char** argv, int kind, const char* tool) {
}
if (!o.no_stdlib && !o.no_defaultlibs) {
DriverRuntimeArchive rt = {0};
- uint32_t insert_pos;
if (driver_runtime_prepare_archive(&env, tool, &runtime, o.target,
o.epoch, &rt) != 0) {
driver_runtime_archive_fini(&env, &rt);
rc = 1;
goto done;
}
- insert_pos = o.inputs.nlink_items;
- if (o.hosted.nfinal <= insert_pos) insert_pos -= o.hosted.nfinal;
- driver_link_inputs_insert_runtime_archive(&o.inputs, &rt, insert_pos);
+ driver_link_inputs_insert_runtime_archives(&o.inputs, &rt, o.target,
+ o.hosted.nfinal,
+ o.hosted.nafter);
driver_runtime_archive_fini(&env, &rt);
}
} else if (o.inputs.npending_libs) {
diff --git a/driver/cmd/cc.c b/driver/cmd/cc.c
@@ -2122,8 +2122,10 @@ static int cc_run_link_exe(DriverEnv* env, const CcOptions* o,
if (driver_load_bytes(io, CC_TOOL, o->inputs.archives[i].path, &arch_lf[i],
&arch_in[i].bytes) != 0)
goto out;
+ arch_in[i].name = kit_slice_cstr(o->inputs.archives[i].path);
arch_in[i].link_mode = o->inputs.archives[i].link_mode;
- arch_in[i].whole_archive = o->inputs.archives[i].whole_archive;
+ arch_in[i].whole_archive =
+ o->inputs.archives[i].whole_archive ? true : false;
arch_in[i].group_id = o->inputs.archives[i].group_id;
}
for (i = 0; i < o->inputs.ndsos; ++i) {
@@ -2218,55 +2220,10 @@ static int cc_run_link_exe(DriverEnv* env, const CcOptions* o,
* o->inputs.nlink_items == 0 never fires (a link action always has at least
* one input), so every add flows through the ordered path. */
{
- uint32_t oi;
DriverLinkInputs li;
- for (oi = 0; oi < o->inputs.nlink_items; ++oi) {
- const DriverLinkItem* item = &o->inputs.link_items[oi];
- KitLinkInputOrder* ord;
- switch ((DriverLinkKind)item->kind) {
- case DRIVER_LINK_SOURCE:
- if (!source_order_keep[item->index]) continue;
- ord = &order[norder++];
- ord->kind = KIT_LINK_INPUT_OBJ;
- ord->index = source_obj_index[item->index];
- break;
- case DRIVER_LINK_SOURCE_MEMORY: {
- uint32_t si = o->nsource_files + item->index;
- if (!source_order_keep[si]) continue;
- ord = &order[norder++];
- ord->kind = KIT_LINK_INPUT_OBJ;
- ord->index = source_obj_index[si];
- break;
- }
- case DRIVER_LINK_OBJECT:
- ord = &order[norder++];
- ord->kind = KIT_LINK_INPUT_OBJ_BYTES;
- ord->index = item->index;
- break;
- case DRIVER_LINK_ARCHIVE:
- ord = &order[norder++];
- ord->kind = KIT_LINK_INPUT_ARCHIVE;
- ord->index = item->index;
- break;
- case DRIVER_LINK_DSO:
- ord = &order[norder++];
- ord->kind = KIT_LINK_INPUT_DSO;
- ord->index = item->index;
- break;
- case DRIVER_LINK_LIB: {
- const DriverPendingLib* pl = &o->inputs.pending_libs[item->index];
- ord = &order[norder++];
- if (pl->resolved_kind == DRIVER_LINK_DSO) {
- ord->kind = KIT_LINK_INPUT_DSO;
- ord->index = pl->resolved_index;
- } else {
- ord->kind = KIT_LINK_INPUT_ARCHIVE;
- ord->index = pl->resolved_index;
- }
- break;
- }
- }
- }
+ norder = driver_link_inputs_build_order(
+ &o->inputs, source_obj_index, source_order_keep, o->nsource_files,
+ order);
memset(&li, 0, sizeof(li));
li.objs = objs;
li.nobjs = nobjs;
@@ -2411,7 +2368,6 @@ static int driver_cc_main(int argc, char** argv, int force_check) {
if (link_action && !co.no_stdlib && !co.no_defaultlibs) {
DriverRuntimeArchive rt_archive = {0};
- uint32_t insert_pos;
if (!runtime_resolved) {
driver_errf(CC_TOOL, "support dir not found");
cc_options_release(&co);
@@ -2426,78 +2382,8 @@ static int driver_cc_main(int argc, char** argv, int force_check) {
driver_env_fini(&env);
return 1;
}
- insert_pos = co.inputs.nlink_items;
- /* On Windows the rt archive is inserted at TWO positions to handle two
- * competing requirements:
- *
- * (a) Before the hosted 'after' group: libucrt.a's _setjmp is a COFF
- * WEAK_EXTERNAL aliasing __intrinsic_setjmp from the private api-set
- * api-ms-win-crt-private-l1-1-0.dll. When libucrt.a's inner loop
- * pulls that WEAK_EXTERNAL member it immediately satisfies
- * __intrinsic_setjmp from the same archive, binding the PE to the
- * private DLL (0xC0000139 on Prism). rt's strong _setjmp/_setjmpex
- * must be in 'defined' before libucrt.a is scanned to prevent the
- * WEAK_EXTERNAL member from ever being pulled.
- *
- * (b) After the hosted 'after' group (before crtend only): libucrt.a
- * pulls libc functions (printf, malloc, …) that contain large stack
- * frames compiled by GCC, which call ___chkstk_ms. Those undefs
- * appear only after libucrt.a's own inner loop runs — after all
- * earlier archives have already finished. A second rt entry here
- * catches those late ___chkstk_ms undefs and satisfies them with
- * rt's own chkstk implementation.
- *
- * Because archive scanning is lazy, no symbol is ever defined twice:
- * the second rt entry skips every member the first already pulled.
- *
- * On other hosts rt stays at the end (before crtend only) — DSO-provided
- * libc symbols shadow rt's freestanding fallbacks cleanly. */
- if (co.target.os == KIT_OS_WINDOWS) {
- char* rt_path2 = NULL;
- size_t rt_path2_size = 0;
- uint32_t before_pos, after_pos;
-
- /* before_pos: just before the 'after' group */
- before_pos = co.inputs.nlink_items;
- {
- uint32_t nadj = co.hosted.nfinal + co.hosted.nafter;
- if (nadj <= before_pos) before_pos -= nadj;
- }
- /* after_pos: before crtend.o only (original position) */
- after_pos = co.inputs.nlink_items;
- if (co.hosted.nfinal <= after_pos) after_pos -= co.hosted.nfinal;
-
- /* Duplicate path for the second entry before ownership is transferred.
- * Both inserts use lazy pull (no whole_archive). */
- if (rt_archive.path && rt_archive.path_size > 0) {
- rt_path2 = (char*)driver_alloc(&env, rt_archive.path_size + 1u);
- if (rt_path2) {
- rt_path2_size = rt_archive.path_size;
- driver_memcpy(rt_path2, rt_archive.path, rt_archive.path_size);
- rt_path2[rt_archive.path_size] = '\0';
- }
- }
-
- /* Insert later position first so it doesn't shift before_pos. */
- if (rt_path2) {
- DriverArchiveInput* ar2 = &co.inputs.archives[co.inputs.narchives++];
- ar2->path = rt_path2;
- ar2->owned = 1;
- ar2->owned_size = rt_path2_size;
- ar2->whole_archive = 0;
- ar2->link_mode = rt_archive.link_mode;
- ar2->group_id = rt_archive.group_id;
- driver_link_inputs_insert(&co.inputs, after_pos, DRIVER_LINK_ARCHIVE,
- co.inputs.narchives - 1u);
- }
- /* Insert earlier position (original path; ownership transferred). */
- driver_link_inputs_insert_runtime_archive(&co.inputs, &rt_archive,
- before_pos);
- } else {
- if (co.hosted.nfinal <= insert_pos) insert_pos -= co.hosted.nfinal;
- driver_link_inputs_insert_runtime_archive(&co.inputs, &rt_archive,
- insert_pos);
- }
+ driver_link_inputs_insert_runtime_archives(
+ &co.inputs, &rt_archive, co.target, co.hosted.nfinal, co.hosted.nafter);
driver_runtime_archive_fini(&env, &rt_archive);
}
diff --git a/driver/cmd/dbg.c b/driver/cmd/dbg.c
@@ -1954,28 +1954,34 @@ static int dbg_make_repl_source_name(KitCompiler* c, KitLanguage lang,
return 0;
}
+/* Resolve a `jit`/`:language` tag to a language and a display source name.
+ *
+ * `tag` is either a frontend `-x` spelling (c/toy/asm/s/wasm/wat) or, for the
+ * `jit` command, a `name.ext` filename. Empty/NULL selects the session default.
+ * `name_buf`/`name_cap` back the canonical synthesized name for the resolved
+ * cases. On return `*name_out` points to a string the caller must keep alive
+ * (intern) UNLESS it equals `s->default_jit_name`, which DbgState already owns.
+ * The storage cases:
+ * - resolved by name: `*name_out` is the canonical "<dbg-jit.EXT>" written
+ * into `name_buf` (single source of truth via dbg_jit_default_name);
+ * - resolved by path (the `name.ext` fallback): `*name_out` is the raw `tag`,
+ * so the resolver is never handed a language name as a path. */
static KitLanguage dbg_jit_language_for_tag(DbgState* s, const char* tag,
+ char* name_buf, size_t name_cap,
const char** name_out) {
+ KitLanguage lang;
if (!tag || !*tag) {
if (name_out) *name_out = s->default_jit_name;
return s->default_jit_lang;
}
- if (driver_streq(tag, "c")) {
- if (name_out) *name_out = "<dbg-jit.c>";
- return KIT_LANG_C;
- }
- if (driver_streq(tag, "toy")) {
- if (name_out) *name_out = "<dbg-jit.toy>";
- return KIT_LANG_TOY;
- }
- if (driver_streq(tag, "asm") || driver_streq(tag, "s")) {
- if (name_out) *name_out = "<dbg-jit.s>";
- return KIT_LANG_ASM;
- }
- if (driver_streq(tag, "wasm") || driver_streq(tag, "wat")) {
- if (name_out) *name_out = "<dbg-jit.wat>";
- return KIT_LANG_WASM;
+ lang = kit_language_for_name(s->compiler, tag);
+ if (lang != KIT_LANG_UNKNOWN) {
+ dbg_jit_default_name(s->compiler, lang, name_buf, name_cap);
+ if (name_out) *name_out = name_buf;
+ return lang;
}
+ /* Unknown name: treat `tag` as a `name.ext` filename and resolve by
+ * extension (kit_language_for_name does not consult extensions). */
if (name_out) *name_out = tag;
return kit_language_for_path(s->compiler, tag);
}
@@ -2116,6 +2122,7 @@ static int dbg_parse_jit_lang_arg(DbgState* s, const char* rest,
const char* tag = p;
size_t tag_n;
char tag_buf[64];
+ char name_buf[DBG_JIT_NAME_CAP];
while (*p && !dbg_isspace((unsigned char)*p) && *p != '{') ++p;
tag_n = (size_t)(p - tag);
if (tag_n == 0 || tag_n >= sizeof(tag_buf)) {
@@ -2124,16 +2131,18 @@ static int dbg_parse_jit_lang_arg(DbgState* s, const char* rest,
}
driver_memcpy(tag_buf, tag, tag_n);
tag_buf[tag_n] = '\0';
- lang = dbg_jit_language_for_tag(s, tag_buf, &input_name);
- if (input_name == tag_buf &&
- dbg_source_intern_name(s, kit_slice_cstr(input_name), &input_name) !=
- 0) {
+ lang = dbg_jit_language_for_tag(s, tag_buf, name_buf, sizeof name_buf,
+ &input_name);
+ /* `input_name` points into stack storage (name_buf or tag_buf) here, never
+ * the session default, so it must be interned to outlive this frame. */
+ if (dbg_source_intern_name(s, kit_slice_cstr(input_name), &input_name) !=
+ 0) {
dbg_errf(s, "out of memory naming repl source");
return 1;
}
while (*p && dbg_isspace((unsigned char)*p)) ++p;
} else {
- lang = dbg_jit_language_for_tag(s, NULL, &input_name);
+ lang = dbg_jit_language_for_tag(s, NULL, NULL, 0, &input_name);
}
*lang_out = lang;
@@ -2236,7 +2245,6 @@ static void dbg_cmd_language(DbgState* s, const char* rest) {
const char* p = rest;
size_t n = 0;
KitLanguage lang;
- const char* name;
while (*p && dbg_isspace((unsigned char)*p)) ++p;
while (p[n] && !dbg_isspace((unsigned char)p[n])) ++n;
@@ -2264,8 +2272,9 @@ static void dbg_cmd_language(DbgState* s, const char* rest) {
}
driver_memcpy(tmp, p, n);
tmp[n] = '\0';
- lang = dbg_jit_language_for_tag(s, tmp, &name);
- (void)name;
+ /* `:language` only needs the resolved language; the display name is recomputed
+ * below from dbg_jit_default_name, so no name buffer is requested. */
+ lang = dbg_jit_language_for_tag(s, tmp, NULL, 0, NULL);
if (lang == KIT_LANG_COUNT) {
dbg_errf(s, "unsupported language: %.*s",
KIT_SLICE_ARG(kit_slice_cstr(tmp)));
diff --git a/driver/lib/link_inputs.c b/driver/lib/link_inputs.c
@@ -133,6 +133,142 @@ void driver_link_inputs_insert_runtime_archive(DriverLinkInputSet* set,
set->narchives - 1u);
}
+void driver_link_inputs_insert_runtime_archives(DriverLinkInputSet* set,
+ DriverRuntimeArchive* rt,
+ KitTargetSpec target,
+ uint32_t nfinal,
+ uint32_t nafter) {
+ /* On Windows the rt archive is inserted at TWO positions to handle two
+ * competing requirements:
+ *
+ * (a) Before the hosted 'after' group: libucrt.a's _setjmp is a COFF
+ * WEAK_EXTERNAL aliasing __intrinsic_setjmp from the private api-set
+ * api-ms-win-crt-private-l1-1-0.dll. When libucrt.a's inner loop
+ * pulls that WEAK_EXTERNAL member it immediately satisfies
+ * __intrinsic_setjmp from the same archive, binding the PE to the
+ * private DLL (0xC0000139 on Prism). rt's strong _setjmp/_setjmpex
+ * must be in 'defined' before libucrt.a is scanned to prevent the
+ * WEAK_EXTERNAL member from ever being pulled.
+ *
+ * (b) After the hosted 'after' group (before crtend only): libucrt.a
+ * pulls libc functions (printf, malloc, …) that contain large stack
+ * frames compiled by GCC, which call ___chkstk_ms. Those undefs
+ * appear only after libucrt.a's own inner loop runs — after all
+ * earlier archives have already finished. A second rt entry here
+ * catches those late ___chkstk_ms undefs and satisfies them with
+ * rt's own chkstk implementation.
+ *
+ * Because archive scanning is lazy, no symbol is ever defined twice:
+ * the second rt entry skips every member the first already pulled.
+ *
+ * On other hosts rt stays at the end (before crtend only) — DSO-provided
+ * libc symbols shadow rt's freestanding fallbacks cleanly. */
+ if (target.os == KIT_OS_WINDOWS) {
+ char* rt_path2 = NULL;
+ size_t rt_path2_size = 0;
+ uint32_t before_pos, after_pos;
+
+ /* before_pos: just before the 'after' group */
+ before_pos = set->nlink_items;
+ {
+ uint32_t nadj = nfinal + nafter;
+ if (nadj <= before_pos) before_pos -= nadj;
+ }
+ /* after_pos: before crtend.o only (original position) */
+ after_pos = set->nlink_items;
+ if (nfinal <= after_pos) after_pos -= nfinal;
+
+ /* Duplicate path for the second entry before ownership is transferred.
+ * Both inserts use lazy pull (no whole_archive). */
+ if (rt->path && rt->path_size > 0) {
+ rt_path2 = (char*)driver_alloc(set->env, rt->path_size + 1u);
+ if (rt_path2) {
+ rt_path2_size = rt->path_size;
+ driver_memcpy(rt_path2, rt->path, rt->path_size);
+ rt_path2[rt->path_size] = '\0';
+ }
+ }
+
+ /* Insert later position first so it doesn't shift before_pos. */
+ if (rt_path2) {
+ DriverArchiveInput* ar2 = &set->archives[set->narchives++];
+ ar2->path = rt_path2;
+ ar2->owned = 1;
+ ar2->owned_size = rt_path2_size;
+ ar2->whole_archive = 0;
+ ar2->link_mode = rt->link_mode;
+ ar2->group_id = rt->group_id;
+ driver_link_inputs_insert(set, after_pos, DRIVER_LINK_ARCHIVE,
+ set->narchives - 1u);
+ }
+ /* Insert earlier position (original path; ownership transferred). */
+ driver_link_inputs_insert_runtime_archive(set, rt, before_pos);
+ } else {
+ uint32_t insert_pos = set->nlink_items;
+ if (nfinal <= insert_pos) insert_pos -= nfinal;
+ driver_link_inputs_insert_runtime_archive(set, rt, insert_pos);
+ }
+}
+
+uint32_t driver_link_inputs_build_order(const DriverLinkInputSet* set,
+ const uint32_t* source_obj_index,
+ const uint8_t* source_order_keep,
+ uint32_t nsource_files,
+ KitLinkInputOrder* order) {
+ uint32_t i;
+ uint32_t norder = 0;
+ for (i = 0; i < set->nlink_items; ++i) {
+ const DriverLinkItem* item = &set->link_items[i];
+ KitLinkInputOrder* ord;
+ switch ((DriverLinkKind)item->kind) {
+ case DRIVER_LINK_SOURCE:
+ if (!source_order_keep[item->index]) continue;
+ ord = &order[norder++];
+ ord->kind = KIT_LINK_INPUT_OBJ;
+ ord->index = source_obj_index[item->index];
+ break;
+ case DRIVER_LINK_SOURCE_MEMORY: {
+ /* cc-only (in-memory stdin source) — indexes the shared source arrays
+ * past the file-backed sources. build never records this kind. */
+ uint32_t si = nsource_files + item->index;
+ if (!source_order_keep[si]) continue;
+ ord = &order[norder++];
+ ord->kind = KIT_LINK_INPUT_OBJ;
+ ord->index = source_obj_index[si];
+ break;
+ }
+ case DRIVER_LINK_OBJECT:
+ ord = &order[norder++];
+ ord->kind = KIT_LINK_INPUT_OBJ_BYTES;
+ ord->index = item->index;
+ break;
+ case DRIVER_LINK_ARCHIVE:
+ ord = &order[norder++];
+ ord->kind = KIT_LINK_INPUT_ARCHIVE;
+ ord->index = item->index;
+ break;
+ case DRIVER_LINK_DSO:
+ ord = &order[norder++];
+ ord->kind = KIT_LINK_INPUT_DSO;
+ ord->index = item->index;
+ break;
+ case DRIVER_LINK_LIB: {
+ const DriverPendingLib* pl = &set->pending_libs[item->index];
+ ord = &order[norder++];
+ if (pl->resolved_kind == DRIVER_LINK_DSO) {
+ ord->kind = KIT_LINK_INPUT_DSO;
+ ord->index = pl->resolved_index;
+ } else {
+ ord->kind = KIT_LINK_INPUT_ARCHIVE;
+ ord->index = pl->resolved_index;
+ }
+ break;
+ }
+ }
+ }
+ return norder;
+}
+
int driver_link_inputs_resolve_pending(DriverLinkInputSet* set,
KitTargetSpec target, int static_link) {
uint32_t i;
diff --git a/driver/lib/link_inputs.h b/driver/lib/link_inputs.h
@@ -126,6 +126,34 @@ void driver_link_inputs_insert_runtime_archive(DriverLinkInputSet* set,
DriverRuntimeArchive* rt,
uint32_t insert_pos);
+/* Insert the compiler runtime archive `rt` (ownership transferred out) at the
+ * position(s) that satisfy the target's link order. For Windows this is a
+ * two-position insert (a strong-_setjmp entry before the hosted `after` group
+ * plus a late ___chkstk_ms entry before crtend; see the implementation), so a
+ * second owned copy of the path is allocated. For every other target it is the
+ * single end-insert (before the `nfinal` crt-end group). `nfinal` / `nafter`
+ * are the hosted plan's final / after group sizes. */
+void driver_link_inputs_insert_runtime_archives(DriverLinkInputSet* set,
+ DriverRuntimeArchive* rt,
+ KitTargetSpec target,
+ uint32_t nfinal,
+ uint32_t nafter);
+
+/* Translate the recorded link-item sequence into the engine's public
+ * KitLinkInputOrder list, writing into `order` (sized >= set->nlink_items) and
+ * returning the count produced. SOURCE / SOURCE_MEMORY items index the caller's
+ * parallel `source_obj_index` / `source_order_keep` arrays — the latter gates
+ * whether a compiled source survived DCE; dropped sources are skipped.
+ * SOURCE_MEMORY (cc stdin sources) indexes past `nsource_files` into those same
+ * arrays; build records no SOURCE_MEMORY items, so it may pass any
+ * nsource_files. OBJECT / ARCHIVE / DSO map straight through; LIB resolves via
+ * its pending-lib slot's resolved kind/index. */
+uint32_t driver_link_inputs_build_order(const DriverLinkInputSet* set,
+ const uint32_t* source_obj_index,
+ const uint8_t* source_order_keep,
+ uint32_t nsource_files,
+ KitLinkInputOrder* order);
+
/* Resolve every pending `-l` name against lib_search_paths into a concrete
* archive or dso slot, filling each pending lib's resolved_kind/index. Returns
* 0, or 1 with a diagnostic on not-found. */
diff --git a/src/api/package.c b/src/api/package.c
@@ -233,26 +233,11 @@ static int pkg_external_chunk_path(char* out, size_t cap,
return dist_cas_chunk_relpath(out, cap, blob, chunk_index);
}
-static int pkg_locator_safe(const char* path) {
- size_t start = 0, i;
- if (!path || !path[0] || path[0] == '/') return 0;
- for (i = 0;; ++i) {
- char c = path[i];
- if (c == '\\' || c == ':' || c == '\n' || c == '\r') return 0;
- if (c == '/' || c == '\0') {
- size_t n = i - start;
- if (n == 0) return 0;
- if (n == 1 && path[start] == '.') return 0;
- if (n == 2 && path[start] == '.' && path[start + 1] == '.') return 0;
- if (c == '\0') return 1;
- start = i + 1u;
- }
- }
-}
-
static int pkg_external_path(char* out, size_t cap, const char* root,
const char* rel) {
- if (!pkg_locator_safe(rel)) return DIST_ERR;
+ /* Reuse the canonical tree-path validator (dist/tree.h): rejects absolute,
+ * '.'/'..', backslash, drive-colon, and newline components. */
+ if (!dist_tree_path_valid(rel)) return DIST_ERR;
return pkg_join_path(out, cap, root, rel);
}
@@ -1249,7 +1234,7 @@ static int pkg_render_chunk_template(char* out, size_t cap, const char* tmpl,
}
if (oi >= cap) return DIST_ERR;
out[oi] = '\0';
- return pkg_locator_safe(out) ? DIST_OK : DIST_ERR;
+ return dist_tree_path_valid(out) ? DIST_OK : DIST_ERR;
}
static int pkg_verify_native_index_sorted(const uint8_t* index_b,
diff --git a/src/dist/kpkg.c b/src/dist/kpkg.c
@@ -440,10 +440,13 @@ int dist_kpkg3_descriptor_parse(const uint8_t* data, size_t len,
memset(d, 0, sizeof *d);
while (pos < len) {
char buf[DIST_KV_LINE_MAX], *t, *eq, *key, *val;
- size_t end = pos, n;
+ size_t end = pos, n, i;
while (end < len && data[end] != '\n') ++end;
n = end - pos;
if (n >= sizeof buf) return dist_set_err(err, errcap, "line too long");
+ for (i = pos; i < end; ++i)
+ if (data[i] == 0)
+ return dist_set_err(err, errcap, "NUL byte in encoding descriptor");
memcpy(buf, data + pos, n);
buf[n] = '\0';
pos = (end < len) ? end + 1u : end;
diff --git a/src/dist/manifest.c b/src/dist/manifest.c
@@ -4,6 +4,7 @@
#include <string.h>
#include "dist_parse.h"
+#include "tree.h"
static int emit(KitWriter* out, const char* s) {
return kit_writer_write(out, s, strlen(s)) == KIT_OK ? DIST_OK : DIST_ERR;
@@ -30,20 +31,11 @@ static int kind_valid(const char* k) {
}
int dist_manifest_path_valid(const char* p) {
- size_t start = 0, i;
- if (!p || !p[0] || p[0] == '/') return 0;
- for (i = 0;; ++i) {
- char c = p[i];
- if (c == '\\' || c == ':') return 0;
- if (c == '/' || c == '\0') {
- size_t n = i - start;
- if (n == 0) return 0;
- if (n == 1 && p[start] == '.') return 0;
- if (n == 2 && p[start] == '.' && p[start + 1] == '.') return 0;
- if (c == '\0') return 1;
- start = i + 1u;
- }
- }
+ /* Shared with dist_tree_path_valid (the strictest variant, which also
+ * rejects embedded '\n'/'\r'). Manifest path values are already line-scanned
+ * and field_text_valid-checked before reaching here, so delegating only adds
+ * defence in depth: it does not change any reachable accept/reject result. */
+ return dist_tree_path_valid(p);
}
#define P3_F_NAME 0x00000001u
@@ -273,12 +265,15 @@ int dist_package_manifest_parse(const uint8_t* data, size_t len,
while (pos < len) {
char buf[DIST_KV_LINE_MAX];
size_t end = pos;
- size_t n;
+ size_t n, i;
char *t, *key, *val, *eq;
while (end < len && data[end] != '\n') ++end;
n = end - pos;
if (n >= sizeof buf) return dist_set_err(err, errcap, "line too long");
+ for (i = pos; i < end; ++i)
+ if (data[i] == 0)
+ return dist_set_err(err, errcap, "NUL byte in package manifest");
memcpy(buf, data + pos, n);
buf[n] = '\0';
pos = (end < len) ? end + 1 : end;