kit

kit
git clone https://git.ryansepassi.com/git/kit.git
Log | Files | Refs | README

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:
Mdoc/plan/CLEANUP-2026-06-09.md | 31+++++++++++++++++++++----------
Mdriver/cmd/build.c | 54+++++++-----------------------------------------------
Mdriver/cmd/cc.c | 130+++++--------------------------------------------------------------------------
Mdriver/cmd/dbg.c | 55++++++++++++++++++++++++++++++++-----------------------
Mdriver/lib/link_inputs.c | 136+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mdriver/lib/link_inputs.h | 28++++++++++++++++++++++++++++
Msrc/api/package.c | 23++++-------------------
Msrc/dist/kpkg.c | 5++++-
Msrc/dist/manifest.c | 25++++++++++---------------
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;