commit a89695e3122b7a118a91d8828d350e2ffc18409c
parent 58a41780a77ed786a88361881b40d8059cc29a94
Author: Ryan Sepassi <rsepassi@gmail.com>
Date: Thu, 4 Jun 2026 08:43:13 -0700
Drain driver TODO backlog
Diffstat:
10 files changed, 607 insertions(+), 719 deletions(-)
diff --git a/doc/plan/README.md b/doc/plan/README.md
@@ -18,4 +18,4 @@ shrinks to whatever remains open.
| [IMAGE_INSPECT.md](IMAGE_INSPECT.md) | Extending object inspection to executables and shared libraries. | [../OBJ.md](../OBJ.md) |
| [BUILD.md](BUILD.md) | A new content-addressed build coordinator (Bazel/Nix-style incremental builds layered on the CAS) — storage state machine, caching algorithm, recipe protocol. Distinct from `../BUILD.md` (kit's own Makefile build). | — (new subsystem) |
| [BUILD_COMMANDS.md](BUILD_COMMANDS.md) | The kit-native `build-exe`/`build-lib`/`build-obj` verbs that replace `compile`: polyglot, in-memory compile+link with `--group` flag scoping and full link-flag control. Distinct from `BUILD.md` (the CAS coordinator). | [../DRIVER.md](../DRIVER.md) |
-| [TODO.md](TODO.md) | A catalog of known bugs, code smells, and test-infra failures to address — driver memory leaks, a `-Wl` rpath overflow, a test-infra prerequisite gap, and build-commands follow-ups. Not a roadmap; a backlog to drain. | — |
+| [TODO.md](TODO.md) | Open deferred fixes and code smells only. Completed items are removed instead of checked off. Not a roadmap; a current backlog. | — |
diff --git a/doc/plan/TODO.md b/doc/plan/TODO.md
@@ -1,80 +1,8 @@
# kit — deferred fixes & code-smell backlog
-A catalog of known bugs, code smells, and test-infra failures to address. Each
-entry names the symptom, the location, why it matters, and a suggested fix.
+No open items.
-## Memory / correctness (driver)
+This file is an open-backlog catalog, not a completion ledger. When an item is
+fixed, remove it instead of checking it off or keeping a closed entry.
-### 1. `cc` leaks one `KitObjBuilder` per source on the link path
-- **Where:** `driver/cmd/cc.c` `cc_run_link_exe` (the `out:` cleanup frees only
- the `objs` array, never `kit_obj_builder_free(objs[i])`).
-- **Why:** A link session only *borrows* builders added via
- `kit_link_session_add_obj` — `src/link/link.c:39,449` ("borrowed; caller still
- owns"), and `kit_link_session_free` frees only its own `publish_objs` pointer
- array, not the builders. The compiler does not free them either (the
- `cc_run_check` path frees them explicitly, which establishes that ownership
- stays with the caller). Every `kit cc a.c b.c -o exe` leaks one builder
- (sections/symbols/relocs/atoms buffers) per source. The comment above
- `cc_run_link_exe` ("The compiler owns the per-source KitObjBuilders for the
- lifetime of the link") is wrong and masks this.
-- **Fix:** Free `objs[i]` after the link completes (the session is already freed
- by then), and correct the comment.
-
-### 2. `cc` leaks `rpath_slices` on every `-rpath` link
-- **Where:** `driver/cmd/cc.c` `cc_run_link_exe` (`rpath_slices` is
- `driver_alloc_zeroed`'d, with no matching `driver_free` anywhere in the
- function).
-- **Why:** Any `kit cc … -Wl,-rpath=DIR` / `-rpath` leaks
- `nrpaths * sizeof(KitSlice)`.
-- **Fix:** Free `rpath_slices` in `cc_run_link_exe`'s cleanup.
-
-### 3. Fixed-bound `rpaths` array can overflow on a comma-packed `-Wl,` token
-- **Where:** `driver/cmd/cc.c` `cc_record_wl` and `driver/cmd/build.c`
- `build_record_wl`; in both, `o->rpaths` is sized `argc + 16`.
-- **Why:** A single argv element `-Wl,-rpath=a,-rpath=b,…` counts as one toward
- `argc` but is split on commas, pushing one entry per `-rpath=` via
- `o->rpaths[o->nrpaths++]`. With more than `argc + 16` rpath entries in one
- token, the write runs past the heap allocation (OOB write). Exotic — real
- builds use separate `-Wl,-rpath=` tokens — but a genuine overflow.
-- **Fix:** Bounds-check `nrpaths` against the allocation, size `rpaths` from a
- worst-case comma count, or grow it dynamically.
-
-## Test infrastructure
-
-### 4. `make test-rt-runtime` fails standalone (missing helper prerequisite)
-- **Symptom:** `make test-rt-runtime` aborts with
- `link-exe-runner missing at build/test/link-exe-runner` (Error 2). The target
- references `build/test/link-exe-runner` but does not build it as a
- prerequisite, so a clean standalone invocation cannot succeed.
-- **Fix:** Declare the `link-exe-runner` build as a prerequisite of
- `test-rt-runtime` (or document the prior target that must run first).
-
-### 5. Driver leaks are invisible on macOS
-- **Why:** The driver env heap is libc-backed (no arena bulk-free at
- `driver_env_fini`), and `ASAN_OPTIONS` (`mk/flags.mk`) sets
- `halt_on_error`/`abort_on_error` but not `detect_leaks`. macOS ASan ships no
- LeakSanitizer, so process-lifetime leaks (#1, #2) never trip locally — they
- surface only under Linux ASan+LSan.
-- **Fix:** Add a Linux LSan lane (CI or periodic) so driver leaks are caught,
- then keep it green by scrubbing #1/#2.
-
-## Code smells / factoring
-
-### 6. `cc` and `build` duplicate ~400 lines of link orchestration
-- **Where:** `driver/cmd/build.c` re-implements `cc.c`'s hosted-profile
- application, runtime-archive insertion, `-Wl,` parsing, `--build-id` parsing,
- PE-subsystem matching, and Windows lib-dir handling.
-- **Why it matters:** A fix to `-Wl` parsing or hosted wiring (e.g. #2, #3) has
- to land in two places. The hosted/runtime/link-flag orchestration should be
- factored into a shared `driver/lib` unit the way `link_engine` already shares
- the session step.
-
-## build-commands follow-ups
-
-- **`build-exe -shared` is a silent no-op.** `-dynamic` is the exe default
- (fine), but `-shared` (gcc spelling for "make a shared library") silently
- builds an executable. Emit a diagnostic instead.
-- **Diagnostic: `-x`/`-X` at the end of a `--group` flag section eats the `--`.**
- `kit build-obj --group -x -- a.c` reports `unsupported -x language: --` instead
- of a clearer "missing argument / `--` before sources". Safe (no state
- corruption), just a confusing message.
+Add new deferred fixes below as they are discovered.
diff --git a/driver/cmd/build.c b/driver/cmd/build.c
@@ -12,6 +12,7 @@
#include "hosted.h"
#include "lib_resolve.h"
#include "link_engine.h"
+#include "link_flags.h"
#include "runtime.h"
/* `kit build-exe` / `build-lib` / `build-obj` — the kit-native build verbs.
@@ -140,11 +141,10 @@ typedef struct BuildOptions {
int opt_level;
int debug_info;
int dynamic; /* -dynamic / -shared */
+ int shared_requested; /* -shared spelling, for build-exe diagnostics */
int shared; /* computed: kind==lib && dynamic */
int static_link; /* -static */
int pie; /* -pie */
- int gc_sections; /* -Wl,--gc-sections */
- int strip_debug; /* -Wl,-S / -Wl,--strip-debug */
int function_sections; /* -ffunction-sections */
int data_sections; /* -fdata-sections */
uint8_t default_visibility; /* KitSymVis */
@@ -155,18 +155,8 @@ typedef struct BuildOptions {
KitTargetSpec target;
DriverTargetFeatures target_features;
- /* Link knobs (build-exe / shared build-lib). */
- const char* entry;
- const char* linker_script;
- uint16_t pe_subsystem;
- const char* soname;
- const char** rpaths;
- uint32_t nrpaths;
- int new_dtags;
- const char* interp_path;
- uint8_t build_id_mode;
- uint8_t* build_id_bytes;
- uint32_t build_id_len;
+ /* Link-session options and owned -Wl state. */
+ DriverLinkFlags link;
uint64_t epoch;
/* Hosted libc / sysroot state. */
@@ -229,80 +219,6 @@ static int build_parse_u64(const char* s, uint64_t* out) {
return 0;
}
-static char* build_dup_span(DriverEnv* env, const char* s, size_t n) {
- char* buf = driver_alloc(env, n + 1u);
- if (!buf) return NULL;
- driver_memcpy(buf, s, n);
- buf[n] = '\0';
- return buf;
-}
-
-static int build_parse_hex_bytes(DriverEnv* env, const char* s,
- uint8_t** out_bytes, uint32_t* out_len) {
- size_t n = driver_strlen(s);
- uint8_t* bs;
- size_t i;
- if (n == 0 || (n & 1u)) return 1;
- bs = driver_alloc(env, n / 2u);
- if (!bs) return 1;
- for (i = 0; i < n; i += 2) {
- unsigned hi, lo;
- char c1 = s[i], c2 = s[i + 1];
- if (c1 >= '0' && c1 <= '9')
- hi = (unsigned)(c1 - '0');
- else if (c1 >= 'a' && c1 <= 'f')
- hi = (unsigned)(c1 - 'a' + 10);
- else if (c1 >= 'A' && c1 <= 'F')
- hi = (unsigned)(c1 - 'A' + 10);
- else {
- driver_free(env, bs, n / 2u);
- return 1;
- }
- if (c2 >= '0' && c2 <= '9')
- lo = (unsigned)(c2 - '0');
- else if (c2 >= 'a' && c2 <= 'f')
- lo = (unsigned)(c2 - 'a' + 10);
- else if (c2 >= 'A' && c2 <= 'F')
- lo = (unsigned)(c2 - 'A' + 10);
- else {
- driver_free(env, bs, n / 2u);
- return 1;
- }
- bs[i / 2u] = (uint8_t)((hi << 4) | lo);
- }
- *out_bytes = bs;
- *out_len = (uint32_t)(n / 2u);
- return 0;
-}
-
-static int build_record_build_id(BuildOptions* o, const char* val) {
- if (driver_streq(val, "none")) {
- o->build_id_mode = KIT_BUILDID_NONE;
- return 0;
- }
- if (driver_streq(val, "sha256")) {
- o->build_id_mode = KIT_BUILDID_SHA256;
- return 0;
- }
- if (driver_streq(val, "uuid")) {
- o->build_id_mode = KIT_BUILDID_UUID;
- return 0;
- }
- if (driver_strneq(val, "0x", 2)) {
- if (build_parse_hex_bytes(o->env, val + 2, &o->build_id_bytes,
- &o->build_id_len) != 0) {
- driver_errf(o->tool,
- "--build-id=0x... requires an even-length hex string");
- return 1;
- }
- o->build_id_mode = KIT_BUILDID_USER;
- return 0;
- }
- driver_errf(o->tool, "unknown --build-id value: %.*s",
- KIT_SLICE_ARG(kit_slice_cstr(val)));
- return 1;
-}
-
static int build_record_mcmodel(BuildOptions* o, const char* val) {
if (driver_streq(val, "small") || driver_streq(val, "medlow")) {
o->target.code_model = KIT_CM_SMALL;
@@ -321,157 +237,6 @@ static int build_record_mcmodel(BuildOptions* o, const char* val) {
return 1;
}
-static int build_subsystem_eq(const char* val, size_t n, const char* want) {
- size_t i;
- for (i = 0; want[i]; ++i) {
- char a, b;
- if (i >= n) return 0;
- a = val[i];
- b = want[i];
- if (a >= 'a' && a <= 'z') a = (char)(a - 'a' + 'A');
- if (b >= 'a' && b <= 'z') b = (char)(b - 'a' + 'A');
- if (a != b) return 0;
- }
- return i == n || val[i] == ',';
-}
-
-static int build_record_pe_subsystem(BuildOptions* o, const char* val,
- size_t n) {
- if (build_subsystem_eq(val, n, "CONSOLE") || build_subsystem_eq(val, n, "CUI")) {
- o->pe_subsystem = KIT_PE_SUBSYSTEM_WINDOWS_CUI;
- return 0;
- }
- if (build_subsystem_eq(val, n, "WINDOWS") || build_subsystem_eq(val, n, "GUI")) {
- o->pe_subsystem = KIT_PE_SUBSYSTEM_WINDOWS_GUI;
- return 0;
- }
- driver_errf(o->tool, "unsupported subsystem: %.*s", (int)n, val);
- return 1;
-}
-
-/* Parse one -Wl,X[,Y...] linker pass-through (subset shared with `cc`). */
-static int build_record_wl(BuildOptions* o, const char* arg) {
- const char* p = arg;
- int expect_rpath = 0, expect_soname = 0, expect_interp = 0, expect_sub = 0;
- while (*p) {
- const char* tok = p;
- size_t n = 0;
- while (p[n] && p[n] != ',') ++n;
- p = tok + n + (tok[n] == ',' ? 1 : 0);
-
- if (expect_rpath || expect_soname || expect_interp || expect_sub) {
- char* buf = build_dup_span(o->env, tok, n);
- if (!buf) {
- driver_errf(o->tool, "out of memory");
- return 1;
- }
- if (expect_rpath) o->rpaths[o->nrpaths++] = buf;
- if (expect_soname) o->soname = buf;
- if (expect_interp) o->interp_path = buf;
- if (expect_sub) {
- int rc = build_record_pe_subsystem(o, buf, driver_strlen(buf));
- driver_free(o->env, buf, n + 1u);
- if (rc != 0) return 1;
- }
- expect_rpath = expect_soname = expect_interp = expect_sub = 0;
- continue;
- }
- if (n >= 8 && driver_strneq(tok, "-soname=", 8)) {
- o->soname = build_dup_span(o->env, tok + 8, n - 8);
- if (!o->soname) {
- driver_errf(o->tool, "out of memory");
- return 1;
- }
- continue;
- }
- if (n == 7 && driver_strneq(tok, "-soname", 7)) {
- expect_soname = 1;
- continue;
- }
- if (n >= 7 && driver_strneq(tok, "-rpath=", 7)) {
- char* buf = build_dup_span(o->env, tok + 7, n - 7);
- if (!buf) {
- driver_errf(o->tool, "out of memory");
- return 1;
- }
- o->rpaths[o->nrpaths++] = buf;
- continue;
- }
- if (n == 6 && driver_strneq(tok, "-rpath", 6)) {
- expect_rpath = 1;
- continue;
- }
- if (n >= 16 && driver_strneq(tok, "-dynamic-linker=", 16)) {
- o->interp_path = build_dup_span(o->env, tok + 16, n - 16);
- if (!o->interp_path) {
- driver_errf(o->tool, "out of memory");
- return 1;
- }
- continue;
- }
- if (n == 15 && driver_strneq(tok, "-dynamic-linker", 15)) {
- expect_interp = 1;
- continue;
- }
- if (n == 18 && driver_strneq(tok, "--enable-new-dtags", 18)) {
- o->new_dtags = 1;
- continue;
- }
- if (n == 19 && driver_strneq(tok, "--disable-new-dtags", 19)) {
- o->new_dtags = 0;
- continue;
- }
- if (n == 13 && driver_strneq(tok, "--gc-sections", 13)) {
- o->gc_sections = 1;
- continue;
- }
- if (n == 16 && driver_strneq(tok, "--no-gc-sections", 16)) {
- o->gc_sections = 0;
- continue;
- }
- if ((n == 2 && driver_strneq(tok, "-S", 2)) ||
- (n == 13 && driver_strneq(tok, "--strip-debug", 13))) {
- o->strip_debug = 1;
- continue;
- }
- if (n >= 11 && driver_strneq(tok, "--build-id=", 11)) {
- char* buf = build_dup_span(o->env, tok + 11, n - 11);
- int rc;
- if (!buf) {
- driver_errf(o->tool, "out of memory");
- return 1;
- }
- rc = build_record_build_id(o, buf);
- driver_free(o->env, buf, n - 11 + 1u);
- if (rc != 0) return 1;
- continue;
- }
- if (n == 10 && driver_strneq(tok, "--build-id", 10)) {
- o->build_id_mode = KIT_BUILDID_SHA256;
- continue;
- }
- if (n >= 12 && driver_strneq(tok, "--subsystem=", 12)) {
- if (build_record_pe_subsystem(o, tok + 12, n - 12) != 0) return 1;
- continue;
- }
- if (n == 11 && driver_strneq(tok, "--subsystem", 11)) {
- expect_sub = 1;
- continue;
- }
- if (n >= 11 && driver_strneq(tok, "/SUBSYSTEM:", 11)) {
- if (build_record_pe_subsystem(o, tok + 11, n - 11) != 0) return 1;
- continue;
- }
- driver_errf(o->tool, "unsupported -Wl, token: %.*s", (int)n, tok);
- return 1;
- }
- if (expect_rpath || expect_soname || expect_interp || expect_sub) {
- driver_errf(o->tool, "-Wl option requires another comma argument");
- return 1;
- }
- return 0;
-}
-
static int build_lang_from_name(const char* name, KitLanguage* out) {
if (driver_streq(name, "c")) {
*out = KIT_LANG_C;
@@ -517,18 +282,16 @@ static int build_alloc(BuildOptions* o, int argc) {
o->pending_libs =
driver_alloc_zeroed(o->env, bound * sizeof(*o->pending_libs));
o->link_items = driver_alloc_zeroed(o->env, bound * sizeof(*o->link_items));
- o->rpaths = driver_alloc_zeroed(o->env, bound * sizeof(*o->rpaths));
if (!o->sources || !o->groups || !o->object_files || !o->archives ||
- !o->dsos || !o->lib_search_paths || !o->pending_libs || !o->link_items ||
- !o->rpaths) {
+ !o->dsos || !o->lib_search_paths || !o->pending_libs || !o->link_items) {
driver_errf(o->tool, "out of memory");
return 1;
}
- o->new_dtags = 1;
o->cur_link_mode = KIT_LM_DEFAULT;
/* groups[0] is the global / bare baseline. */
o->ngroups = 1;
- if (build_group_cf_init(o, &o->groups[0]) != 0 ||
+ if (driver_link_flags_init(&o->link, o->env, o->tool, (uint32_t)bound) != 0 ||
+ build_group_cf_init(o, &o->groups[0]) != 0 ||
driver_target_features_init(&o->target_features, o->env, argc) != 0) {
driver_errf(o->tool, "out of memory");
return 1;
@@ -554,11 +317,10 @@ static void build_release(BuildOptions* o) {
if (g->m_def) driver_free(o->env, g->m_def, g->m_def_cap * sizeof(*g->m_def));
if (g->m_und) driver_free(o->env, g->m_und, g->m_nund * sizeof(*g->m_und));
}
- if (o->build_id_bytes)
- driver_free(o->env, o->build_id_bytes, o->build_id_len);
if (o->owned_sysroot_lib_dir)
driver_free(o->env, o->owned_sysroot_lib_dir, o->owned_sysroot_lib_dir_size);
driver_hosted_plan_fini(o->env, &o->hosted);
+ driver_link_flags_fini(&o->link);
driver_target_features_fini(&o->target_features, o->env);
if (o->sources) driver_free(o->env, o->sources, bound * sizeof(*o->sources));
if (o->groups) driver_free(o->env, o->groups, bound * sizeof(*o->groups));
@@ -573,7 +335,6 @@ static void build_release(BuildOptions* o) {
driver_free(o->env, o->pending_libs, bound * sizeof(*o->pending_libs));
if (o->link_items)
driver_free(o->env, o->link_items, bound * sizeof(*o->link_items));
- if (o->rpaths) driver_free(o->env, o->rpaths, bound * sizeof(*o->rpaths));
}
/* ===================================================================== */
@@ -735,6 +496,10 @@ static int build_try_scopable(BuildOptions* o, BuildGroup* g, int argc,
driver_errf(o->tool, "-x requires an argument");
return -1;
}
+ if (driver_streq(argv[*i], "--")) {
+ driver_errf(o->tool, "-x requires an argument before `--`");
+ return -1;
+ }
if (build_lang_from_name(argv[*i], &lang) != 0) {
driver_errf(o->tool, "unsupported -x language: %.*s",
KIT_SLICE_ARG(kit_slice_cstr(argv[*i])));
@@ -756,6 +521,11 @@ static int build_try_scopable(BuildOptions* o, BuildGroup* g, int argc,
KIT_SLICE_ARG(kit_slice_cstr(a)));
return -1;
}
+ if (driver_streq(argv[*i], "--")) {
+ driver_errf(o->tool, "%.*s requires a following flag before `--`",
+ KIT_SLICE_ARG(kit_slice_cstr(a)));
+ return -1;
+ }
f = &g->fe[g->nfe++];
f->lang = (uint8_t)lang;
f->tok = argv[*i];
@@ -1003,8 +773,8 @@ static int build_parse(int argc, char** argv, BuildOptions* o) {
continue;
}
if (driver_streq(a, "-shared")) {
- /* Hidden alias for -dynamic on build-lib; rejected elsewhere. */
o->dynamic = 1;
+ o->shared_requested = 1;
continue;
}
if (driver_streq(a, "-pie")) {
@@ -1052,7 +822,7 @@ static int build_parse(int argc, char** argv, BuildOptions* o) {
driver_errf(o->tool, "-e requires an argument");
return 1;
}
- o->entry = argv[i];
+ o->link.entry = argv[i];
continue;
}
if (driver_streq(a, "-T")) {
@@ -1060,11 +830,11 @@ static int build_parse(int argc, char** argv, BuildOptions* o) {
driver_errf(o->tool, "-T requires an argument");
return 1;
}
- o->linker_script = argv[i];
+ o->link.linker_script = argv[i];
continue;
}
if (driver_strneq(a, "-Wl,", 4)) {
- if (build_record_wl(o, a + 4) != 0) return 1;
+ if (driver_link_flags_record_wl(&o->link, a + 4) != 0) return 1;
continue;
}
if (driver_streq(a, "-Xlinker")) {
@@ -1072,19 +842,19 @@ static int build_parse(int argc, char** argv, BuildOptions* o) {
driver_errf(o->tool, "-Xlinker requires an argument");
return 1;
}
- if (build_record_wl(o, argv[i]) != 0) return 1;
+ if (driver_link_flags_record_wl(&o->link, argv[i]) != 0) return 1;
continue;
}
if (driver_strneq(a, "--build-id=", 11)) {
- if (build_record_build_id(o, a + 11) != 0) return 1;
+ if (driver_link_flags_record_build_id(&o->link, a + 11) != 0) return 1;
continue;
}
if (driver_streq(a, "-mwindows")) {
- o->pe_subsystem = KIT_PE_SUBSYSTEM_WINDOWS_GUI;
+ o->link.pe_subsystem = KIT_PE_SUBSYSTEM_WINDOWS_GUI;
continue;
}
if (driver_streq(a, "-mconsole")) {
- o->pe_subsystem = KIT_PE_SUBSYSTEM_WINDOWS_CUI;
+ o->link.pe_subsystem = KIT_PE_SUBSYSTEM_WINDOWS_CUI;
continue;
}
if (driver_strneq(a, "-mcmodel=", 9)) {
@@ -1269,8 +1039,8 @@ static int build_apply_hosted_profile(BuildOptions* o) {
if (o->no_startfiles) break;
if (build_append_hosted_input(o, &o->hosted.final[i], 0, 0) != 0) return 1;
}
- if (!o->interp_path && o->hosted.interp_path)
- o->interp_path = o->hosted.interp_path;
+ if (!o->link.interp_path && o->hosted.interp_path)
+ o->link.interp_path = o->hosted.interp_path;
return 0;
}
@@ -1567,54 +1337,6 @@ static int build_open_output(const KitContext* ctx, DriverEnv* env,
return 0;
}
-/* ===================================================================== */
-/* run paths */
-/* ===================================================================== */
-
-/* Build a KitLinkSessionOptions from the parsed link knobs. */
-static int build_fill_link_opts(BuildOptions* o, DriverEnv* env,
- KitLinkScript* script, uint8_t output_kind,
- KitLinkSessionOptions* lopts,
- KitSlice** rpath_slices_out) {
- KitSlice* rpath_slices = NULL;
- uint32_t i;
- if (o->nrpaths) {
- rpath_slices = driver_alloc_zeroed(env, o->nrpaths * sizeof(*rpath_slices));
- if (!rpath_slices) {
- driver_errf(o->tool, "out of memory");
- return 1;
- }
- for (i = 0; i < o->nrpaths; ++i)
- rpath_slices[i] = kit_slice_cstr(o->rpaths[i]);
- }
- memset(lopts, 0, sizeof(*lopts));
- lopts->output_kind = output_kind;
- lopts->entry = kit_slice_cstr(o->entry);
- lopts->linker_script = script;
- lopts->build_id_mode = o->build_id_mode;
- lopts->build_id_bytes = o->build_id_bytes;
- lopts->build_id_len = o->build_id_len;
- lopts->gc_sections = o->gc_sections ? true : false;
- lopts->strip_debug = o->strip_debug ? true : false;
- lopts->pie = driver_link_pie(o->target, o->pie, o->shared,
- output_kind == KIT_LINK_OUTPUT_RELOCATABLE)
- ? true
- : false;
- lopts->pe_subsystem = o->pe_subsystem;
- lopts->interp_path = kit_slice_cstr(o->interp_path);
- lopts->soname = kit_slice_cstr(o->soname);
- if (o->new_dtags) {
- lopts->runpaths = rpath_slices;
- lopts->nrunpaths = o->nrpaths;
- } else {
- lopts->rpaths = rpath_slices;
- lopts->nrpaths = o->nrpaths;
- }
- lopts->allow_undefined = 1;
- *rpath_slices_out = rpath_slices;
- return 0;
-}
-
/* Compile every source to an in-memory builder; objs[] is caller-owned. */
static int build_compile_all(BuildOptions* o, KitCompiler* compiler,
const KitContext* ctx, const KitCodeOptions* code,
@@ -1699,10 +1421,10 @@ static int build_run_link(BuildOptions* o, KitCompiler* compiler,
goto out;
dso_names[i] = kit_slice_cstr(o->dsos[i].path);
}
- if (o->linker_script) {
+ if (o->link.linker_script) {
KitSlice dummy;
- if (driver_load_bytes(io, o->tool, o->linker_script, &script_lf, &dummy) !=
- 0)
+ if (driver_load_bytes(io, o->tool, o->link.linker_script, &script_lf,
+ &dummy) != 0)
goto out;
}
@@ -1756,8 +1478,10 @@ static int build_run_link(BuildOptions* o, KitCompiler* compiler,
KitLinkSessionOptions lopts;
DriverLinkInputs li;
KitStatus st;
- if (build_fill_link_opts(o, env, script, output_kind, &lopts,
- &rpath_slices) != 0)
+ if (driver_link_flags_fill_options(
+ &o->link, o->target, o->pie, o->shared,
+ output_kind == KIT_LINK_OUTPUT_RELOCATABLE, output_kind, script,
+ &lopts, &rpath_slices) != 0)
goto out;
memset(&li, 0, sizeof(li));
li.objs = objs;
@@ -1787,8 +1511,7 @@ out:
}
}
if (script) kit_link_script_free(ctx, script);
- if (rpath_slices)
- driver_free(env, rpath_slices, o->nrpaths * sizeof(*rpath_slices));
+ driver_link_flags_free_rpath_slices(&o->link, rpath_slices);
driver_release_bytes(io, &script_lf);
if (arch_lf)
for (i = 0; i < o->narchives; ++i) driver_release_bytes(io, &arch_lf[i]);
@@ -1851,7 +1574,7 @@ static int build_run_relocatable(BuildOptions* o, KitCompiler* compiler,
memset(&lopts, 0, sizeof(lopts));
lopts.output_kind = KIT_LINK_OUTPUT_RELOCATABLE;
lopts.allow_undefined = 1;
- lopts.strip_debug = o->strip_debug ? true : false;
+ lopts.strip_debug = o->link.strip_debug ? true : false;
memset(&li, 0, sizeof(li));
li.objs = objs;
li.nobjs = o->nsources;
@@ -1991,11 +1714,15 @@ static int build_validate(BuildOptions* o) {
return 1;
}
- /* -dynamic/-shared selects a shared library for build-lib; it is a no-op for
- * build-exe (already dynamically linked by default) and an error for
- * build-obj (caught in its branch below). */
+ /* -dynamic selects the default executable link mode for build-exe and the
+ * not-yet-supported shared-library mode for build-lib. -shared is the GCC
+ * spelling for producing a shared library, so build-exe rejects it. */
if (o->kind == BUILD_OUT_EXE) {
+ if (o->shared_requested) {
+ driver_errf(o->tool, "-shared is not valid for build-exe");
+ return 1;
+ }
if (o->emit != BUILD_EMIT_OBJ || o->syntax_only) {
driver_errf(o->tool, "--emit/-S/-fsyntax-only are build-obj options");
return 1;
diff --git a/driver/cmd/cc.c b/driver/cmd/cc.c
@@ -14,6 +14,7 @@
#include "hosted.h"
#include "lib_resolve.h"
#include "link_engine.h"
+#include "link_flags.h"
#include "runtime.h"
/* `kit cc` — C compiler driver. With -c produces a single object;
@@ -133,9 +134,6 @@ typedef struct CcOptions {
int output_path_set;
char* owned_output_path;
size_t owned_output_path_size;
- const char* entry; /* -e */
- const char* linker_script; /* -T path */
- uint16_t pe_subsystem; /* KitPeSubsystem */
const char* sysroot; /* --sysroot / -isysroot */
int freestanding; /* -ffreestanding (suppresses sysroot headers) */
uint8_t default_visibility; /* KitSymVis; -fvisibility=... */
@@ -151,12 +149,6 @@ typedef struct CcOptions {
char** owned_path_map_olds;
size_t* owned_path_map_old_sizes;
- /* Build-id. mode is a KitBuildIdMode value; bytes/len are owned and
- * non-NULL only when mode == KIT_BUILDID_USER. */
- uint8_t build_id_mode;
- uint8_t* build_id_bytes;
- uint32_t build_id_len;
-
/* Reproducibility: SOURCE_DATE_EPOCH parsed at end-of-parse. */
uint64_t epoch;
@@ -203,23 +195,15 @@ typedef struct CcOptions {
const char** dep_targets; /* -MT/-MQ */
uint32_t ndep_targets;
- /* Shared-library link state — reachable via -shared and -Wl,... pass-
- * throughs. cc forwards these to kit_link_shared in the same way GCC
- * forwards -Wl,-soname=... to ld. rpaths/runpaths alias argv. */
- int shared; /* -shared */
- const char* soname; /* -Wl,-soname=NAME */
- const char** rpaths; /* -Wl,-rpath=DIR (repeatable) */
- uint32_t nrpaths;
- int new_dtags; /* 1=DT_RUNPATH (default), 0=DT_RPATH */
+ /* Link-session options and owned -Wl state. */
+ DriverLinkFlags link;
+ int shared; /* -shared */
int static_link;
int pie;
- int gc_sections;
- const char* interp_path;
int no_stdlib;
int no_defaultlibs;
int no_startfiles;
int wants_hosted_libc;
- int strip_debug;
DriverHostedPlan hosted;
} CcOptions;
@@ -307,17 +291,16 @@ static int cc_alloc_arrays(CcOptions* o, int argc) {
driver_alloc_zeroed(o->env, bound * sizeof(*o->owned_path_map_olds));
o->owned_path_map_old_sizes =
driver_alloc_zeroed(o->env, bound * sizeof(*o->owned_path_map_old_sizes));
- o->rpaths = driver_alloc_zeroed(o->env, bound * sizeof(*o->rpaths));
if (!o->source_files || !o->source_langs || !o->source_memory ||
!o->object_files || !o->archives || !o->dsos || !o->lib_search_paths ||
!o->pending_libs || !o->link_items || !o->dep_targets || !o->path_map ||
- !o->owned_path_map_olds || !o->owned_path_map_old_sizes || !o->rpaths) {
+ !o->owned_path_map_olds || !o->owned_path_map_old_sizes) {
driver_errf(CC_TOOL, "out of memory");
return 1;
}
- o->new_dtags = 1;
o->cur_link_mode = KIT_LM_DEFAULT;
- if (driver_cflags_init(&o->cf, o->env,
+ if (driver_link_flags_init(&o->link, o->env, CC_TOOL, (uint32_t)bound) != 0 ||
+ driver_cflags_init(&o->cf, o->env,
(int)(bound + DRIVER_HOSTED_MAX_DEFINES +
DRIVER_HOSTED_MAX_INCLUDES)) != 0 ||
driver_target_features_init(&o->target_features, o->env, argc) != 0) {
@@ -348,14 +331,13 @@ static void cc_options_release(CcOptions* o) {
}
}
if (o->stdin_buf) driver_free(o->env, o->stdin_buf, o->stdin_size);
- if (o->build_id_bytes)
- driver_free(o->env, o->build_id_bytes, o->build_id_len);
if (o->owned_output_path)
driver_free(o->env, o->owned_output_path, o->owned_output_path_size);
if (o->owned_sysroot_lib_dir)
driver_free(o->env, o->owned_sysroot_lib_dir,
o->owned_sysroot_lib_dir_size);
driver_hosted_plan_fini(o->env, &o->hosted);
+ driver_link_flags_fini(&o->link);
driver_target_features_fini(&o->target_features, o->env);
driver_cflags_fini(&o->cf, o->env);
driver_free(o->env, o->source_files, bound * sizeof(*o->source_files));
@@ -374,190 +356,10 @@ static void cc_options_release(CcOptions* o) {
bound * sizeof(*o->owned_path_map_olds));
driver_free(o->env, o->owned_path_map_old_sizes,
bound * sizeof(*o->owned_path_map_old_sizes));
- driver_free(o->env, o->rpaths, bound * sizeof(*o->rpaths));
-}
-
-static char* cc_dup_span(DriverEnv* env, const char* s, size_t n) {
- char* buf = driver_alloc(env, n + 1u);
- if (!buf) return NULL;
- driver_memcpy(buf, s, n);
- buf[n] = '\0';
- return buf;
}
-static int cc_record_build_id(CcOptions* o, const char* val);
static int cc_apply_hosted_profile(CcOptions* o);
-static int cc_subsystem_value_eq(const char* val, size_t n, const char* want) {
- size_t i;
- for (i = 0; want[i]; ++i) {
- char a;
- char b;
- if (i >= n) return 0;
- a = val[i];
- b = want[i];
- if (a >= 'a' && a <= 'z') a = (char)(a - 'a' + 'A');
- if (b >= 'a' && b <= 'z') b = (char)(b - 'a' + 'A');
- if (a != b) return 0;
- }
- return i == n || val[i] == ',';
-}
-
-static int cc_record_pe_subsystem(CcOptions* o, const char* val, size_t n) {
- if (cc_subsystem_value_eq(val, n, "CONSOLE") ||
- cc_subsystem_value_eq(val, n, "CUI")) {
- o->pe_subsystem = KIT_PE_SUBSYSTEM_WINDOWS_CUI;
- return 0;
- }
- if (cc_subsystem_value_eq(val, n, "WINDOWS") ||
- cc_subsystem_value_eq(val, n, "GUI")) {
- o->pe_subsystem = KIT_PE_SUBSYSTEM_WINDOWS_GUI;
- return 0;
- }
- driver_errf(CC_TOOL, "unsupported subsystem: %.*s", (int)n, val);
- return 1;
-}
-
-/* Parse a single GCC-style -Wl,X[,Y...] pass-through argument. */
-static int cc_record_wl(CcOptions* o, const char* arg) {
- const char* p = arg;
- int expect_rpath = 0;
- int expect_soname = 0;
- int expect_interp = 0;
- int expect_subsystem = 0;
- while (*p) {
- const char* tok = p;
- size_t n = 0;
- while (p[n] && p[n] != ',') ++n;
- p = tok + n + (tok[n] == ',' ? 1 : 0);
-
- if (expect_rpath || expect_soname || expect_interp || expect_subsystem) {
- char* buf = cc_dup_span(o->env, tok, n);
- if (!buf) {
- driver_errf(CC_TOOL, "out of memory");
- return 1;
- }
- if (expect_rpath) o->rpaths[o->nrpaths++] = buf;
- if (expect_soname) o->soname = buf;
- if (expect_interp) o->interp_path = buf;
- if (expect_subsystem) {
- int rc = cc_record_pe_subsystem(o, buf, driver_strlen(buf));
- driver_free(o->env, buf, n + 1u);
- if (rc != 0) return 1;
- }
- expect_rpath = expect_soname = expect_interp = expect_subsystem = 0;
- continue;
- }
-
- if (n >= 8 && driver_strneq(tok, "-soname=", 8)) {
- char* buf;
- size_t bufsz = n - 8 + 1;
- buf = driver_alloc(o->env, bufsz);
- if (!buf) {
- driver_errf(CC_TOOL, "out of memory");
- return 1;
- }
- driver_memcpy(buf, tok + 8, n - 8);
- buf[n - 8] = '\0';
- o->soname = buf;
- continue;
- }
- if (n == 7 && driver_strneq(tok, "-soname", 7)) {
- expect_soname = 1;
- continue;
- }
- if (n >= 7 && driver_strneq(tok, "-rpath=", 7)) {
- char* buf;
- size_t bufsz = n - 7 + 1;
- buf = driver_alloc(o->env, bufsz);
- if (!buf) {
- driver_errf(CC_TOOL, "out of memory");
- return 1;
- }
- driver_memcpy(buf, tok + 7, n - 7);
- buf[n - 7] = '\0';
- o->rpaths[o->nrpaths++] = buf;
- continue;
- }
- if (n == 6 && driver_strneq(tok, "-rpath", 6)) {
- expect_rpath = 1;
- continue;
- }
- if (n >= 16 && driver_strneq(tok, "-dynamic-linker=", 16)) {
- o->interp_path = cc_dup_span(o->env, tok + 16, n - 16);
- if (!o->interp_path) {
- driver_errf(CC_TOOL, "out of memory");
- return 1;
- }
- continue;
- }
- if (n == 15 && driver_strneq(tok, "-dynamic-linker", 15)) {
- expect_interp = 1;
- continue;
- }
- if (n == 18 && driver_strneq(tok, "--enable-new-dtags", 18)) {
- o->new_dtags = 1;
- continue;
- }
- if (n == 19 && driver_strneq(tok, "--disable-new-dtags", 19)) {
- o->new_dtags = 0;
- continue;
- }
- if (n == 13 && driver_strneq(tok, "--gc-sections", 13)) {
- o->gc_sections = 1;
- continue;
- }
- if (n == 16 && driver_strneq(tok, "--no-gc-sections", 16)) {
- o->gc_sections = 0;
- continue;
- }
- if (n == 2 && driver_strneq(tok, "-S", 2)) {
- o->strip_debug = 1;
- continue;
- }
- if (n == 13 && driver_strneq(tok, "--strip-debug", 13)) {
- o->strip_debug = 1;
- continue;
- }
- if (n >= 11 && driver_strneq(tok, "--build-id=", 11)) {
- char* buf = cc_dup_span(o->env, tok + 11, n - 11);
- int rc;
- if (!buf) {
- driver_errf(CC_TOOL, "out of memory");
- return 1;
- }
- rc = cc_record_build_id(o, buf);
- driver_free(o->env, buf, n - 11 + 1u);
- if (rc != 0) return 1;
- continue;
- }
- if (n == 10 && driver_strneq(tok, "--build-id", 10)) {
- o->build_id_mode = KIT_BUILDID_SHA256;
- continue;
- }
- if (n >= 12 && driver_strneq(tok, "--subsystem=", 12)) {
- if (cc_record_pe_subsystem(o, tok + 12, n - 12) != 0) return 1;
- continue;
- }
- if (n == 11 && driver_strneq(tok, "--subsystem", 11)) {
- expect_subsystem = 1;
- continue;
- }
- if (n >= 11 && driver_strneq(tok, "/SUBSYSTEM:", 11)) {
- if (cc_record_pe_subsystem(o, tok + 11, n - 11) != 0) return 1;
- continue;
- }
-
- driver_errf(CC_TOOL, "unsupported -Wl, token: %.*s", (int)n, tok);
- return 1;
- }
- if (expect_rpath || expect_soname || expect_interp || expect_subsystem) {
- driver_errf(CC_TOOL, "-Wl option requires another comma argument");
- return 1;
- }
- return 0;
-}
-
static int cc_is_c_source(const char* s) {
return driver_has_suffix(s, ".c") || driver_has_suffix(s, ".S") ||
driver_has_suffix(s, ".s") || driver_has_suffix(s, ".toy") ||
@@ -657,44 +459,6 @@ static int cc_parse_u64(const char* s, uint64_t* out) {
return 0;
}
-static int cc_parse_hex_bytes(DriverEnv* env, const char* s,
- uint8_t** out_bytes, uint32_t* out_len) {
- size_t n = driver_strlen(s);
- uint8_t* bs;
- size_t i;
- if (n == 0 || (n & 1u)) return 1;
- bs = driver_alloc(env, n / 2u);
- if (!bs) return 1;
- for (i = 0; i < n; i += 2) {
- unsigned hi, lo;
- char c1 = s[i], c2 = s[i + 1];
- if (c1 >= '0' && c1 <= '9')
- hi = (unsigned)(c1 - '0');
- else if (c1 >= 'a' && c1 <= 'f')
- hi = (unsigned)(c1 - 'a' + 10);
- else if (c1 >= 'A' && c1 <= 'F')
- hi = (unsigned)(c1 - 'A' + 10);
- else {
- driver_free(env, bs, n / 2u);
- return 1;
- }
- if (c2 >= '0' && c2 <= '9')
- lo = (unsigned)(c2 - '0');
- else if (c2 >= 'a' && c2 <= 'f')
- lo = (unsigned)(c2 - 'a' + 10);
- else if (c2 >= 'A' && c2 <= 'F')
- lo = (unsigned)(c2 - 'A' + 10);
- else {
- driver_free(env, bs, n / 2u);
- return 1;
- }
- bs[i / 2u] = (uint8_t)((hi << 4) | lo);
- }
- *out_bytes = bs;
- *out_len = (uint32_t)(n / 2u);
- return 0;
-}
-
static int cc_record_path_map(CcOptions* o, const char* arg) {
const char* eq = driver_strchr(arg, '=');
KitPathPrefixMap* m = &o->path_map[o->npath_map];
@@ -721,34 +485,6 @@ static int cc_record_path_map(CcOptions* o, const char* arg) {
return 0;
}
-static int cc_record_build_id(CcOptions* o, const char* val) {
- if (driver_streq(val, "none")) {
- o->build_id_mode = KIT_BUILDID_NONE;
- return 0;
- }
- if (driver_streq(val, "sha256")) {
- o->build_id_mode = KIT_BUILDID_SHA256;
- return 0;
- }
- if (driver_streq(val, "uuid")) {
- o->build_id_mode = KIT_BUILDID_UUID;
- return 0;
- }
- if (driver_strneq(val, "0x", 2)) {
- if (cc_parse_hex_bytes(o->env, val + 2, &o->build_id_bytes,
- &o->build_id_len) != 0) {
- driver_errf(CC_TOOL,
- "--build-id=0x... requires an even-length hex string");
- return 1;
- }
- o->build_id_mode = KIT_BUILDID_USER;
- return 0;
- }
- driver_errf(CC_TOOL, "unknown --build-id value: %.*s",
- KIT_SLICE_ARG(kit_slice_cstr(val)));
- return 1;
-}
-
static int cc_record_mcmodel(CcOptions* o, const char* val) {
if (driver_streq(val, "small")) {
o->target.code_model = KIT_CM_SMALL;
@@ -1077,8 +813,8 @@ static int cc_apply_hosted_profile(CcOptions* o) {
if (o->no_startfiles) break;
if (cc_append_hosted_input(o, &o->hosted.final[i], 0, 0) != 0) return 1;
}
- if (!o->interp_path && o->hosted.interp_path)
- o->interp_path = o->hosted.interp_path;
+ if (!o->link.interp_path && o->hosted.interp_path)
+ o->link.interp_path = o->hosted.interp_path;
return 0;
}
@@ -1492,15 +1228,15 @@ static int cc_parse(int argc, char** argv, CcOptions* o) {
continue;
}
if (driver_streq(a, "-mwindows")) {
- o->pe_subsystem = KIT_PE_SUBSYSTEM_WINDOWS_GUI;
+ o->link.pe_subsystem = KIT_PE_SUBSYSTEM_WINDOWS_GUI;
continue;
}
if (driver_streq(a, "-mconsole")) {
- o->pe_subsystem = KIT_PE_SUBSYSTEM_WINDOWS_CUI;
+ o->link.pe_subsystem = KIT_PE_SUBSYSTEM_WINDOWS_CUI;
continue;
}
if (driver_strneq(a, "-Wl,", 4)) {
- if (cc_record_wl(o, a + 4) != 0) return 1;
+ if (driver_link_flags_record_wl(&o->link, a + 4) != 0) return 1;
continue;
}
@@ -1515,7 +1251,7 @@ static int cc_parse(int argc, char** argv, CcOptions* o) {
if (tr > 0) continue;
}
if (driver_strneq(a, "--build-id=", 11)) {
- if (cc_record_build_id(o, a + 11) != 0) return 1;
+ if (driver_link_flags_record_build_id(&o->link, a + 11) != 0) return 1;
continue;
}
if (driver_strneq(a, "-ffile-prefix-map=", 18)) {
@@ -1551,7 +1287,7 @@ static int cc_parse(int argc, char** argv, CcOptions* o) {
driver_errf(CC_TOOL, "-e requires an argument");
return 1;
}
- o->entry = argv[i];
+ o->link.entry = argv[i];
continue;
}
if (driver_streq(a, "-T")) {
@@ -1559,7 +1295,7 @@ static int cc_parse(int argc, char** argv, CcOptions* o) {
driver_errf(CC_TOOL, "-T requires an argument");
return 1;
}
- o->linker_script = argv[i];
+ o->link.linker_script = argv[i];
continue;
}
if (driver_streq(a, "-target")) {
@@ -1602,7 +1338,7 @@ static int cc_parse(int argc, char** argv, CcOptions* o) {
driver_errf(CC_TOOL, "-Xlinker requires an argument");
return 1;
}
- if (cc_record_wl(o, argv[i]) != 0) return 1;
+ if (driver_link_flags_record_wl(&o->link, argv[i]) != 0) return 1;
continue;
}
if (driver_streq(a, "-x")) {
@@ -1774,7 +1510,7 @@ static int cc_parse(int argc, char** argv, CcOptions* o) {
return 1;
}
}
- if (!o->shared && o->soname) {
+ if (!o->shared && o->link.soname) {
driver_errf(CC_TOOL, "-Wl,-soname requires -shared");
return 1;
}
@@ -2540,10 +2276,10 @@ out:
return rc;
}
-/* exe path: compile every C source via a single KitCompiler, load
- * .o/.a/script inputs, and call kit_link_exe or kit_link_shared. The
- * compiler owns the per-source KitObjBuilders for the lifetime of the
- * link. */
+/* exe/shared path: compile every source via a single KitCompiler, load
+ * .o/.a/script inputs, and link. The link session borrows the per-source
+ * KitObjBuilders; this function keeps ownership and frees them after the
+ * session is done. */
static int cc_run_link_exe(DriverEnv* env, const CcOptions* o,
const KitPreprocessOptions* pp) {
KitContext ctx = driver_env_to_context(env);
@@ -2565,6 +2301,7 @@ static int cc_run_link_exe(DriverEnv* env, const CcOptions* o,
KitLinkInputOrder* order = NULL;
KitObjBuilder** objs = NULL;
KitLinkScript* script = NULL;
+ KitSlice* rpath_slices = NULL;
KitCCompileOptions copts;
uint32_t nsrc = o->nsource_files + o->nsource_memory;
uint32_t i;
@@ -2651,10 +2388,10 @@ static int cc_run_link_exe(DriverEnv* env, const CcOptions* o,
dso_names[i] = kit_slice_cstr(o->dsos[i].path);
}
- if (o->linker_script) {
+ if (o->link.linker_script) {
KitSlice dummy;
- if (driver_load_bytes(io, CC_TOOL, o->linker_script, &script_lf, &dummy) !=
- 0)
+ if (driver_load_bytes(io, CC_TOOL, o->link.linker_script, &script_lf,
+ &dummy) != 0)
goto out;
}
@@ -2697,39 +2434,11 @@ static int cc_run_link_exe(DriverEnv* env, const CcOptions* o,
{
KitLinkSessionOptions lopts;
KitStatus st;
- KitSlice* rpath_slices = NULL;
- if (o->nrpaths) {
- rpath_slices =
- driver_alloc_zeroed(env, o->nrpaths * sizeof(*rpath_slices));
- if (!rpath_slices) {
- driver_errf(CC_TOOL, "out of memory");
- goto out;
- }
- for (i = 0; i < o->nrpaths; ++i)
- rpath_slices[i] = kit_slice_cstr(o->rpaths[i]);
- }
- memset(&lopts, 0, sizeof(lopts));
- lopts.output_kind =
- o->shared ? KIT_LINK_OUTPUT_SHARED : KIT_LINK_OUTPUT_EXE;
- lopts.entry = kit_slice_cstr(o->entry);
- lopts.linker_script = script;
- lopts.build_id_mode = o->build_id_mode;
- lopts.build_id_bytes = o->build_id_bytes;
- lopts.build_id_len = o->build_id_len;
- lopts.gc_sections = o->gc_sections;
- lopts.strip_debug = o->strip_debug;
- lopts.pie = driver_link_pie(o->target, o->pie, o->shared, /*reloc=*/0);
- lopts.pe_subsystem = o->pe_subsystem;
- lopts.interp_path = kit_slice_cstr(o->interp_path);
- lopts.soname = kit_slice_cstr(o->soname);
- if (o->new_dtags) {
- lopts.runpaths = rpath_slices;
- lopts.nrunpaths = o->nrpaths;
- } else {
- lopts.rpaths = rpath_slices;
- lopts.nrpaths = o->nrpaths;
- }
- lopts.allow_undefined = 1;
+ if (driver_link_flags_fill_options(
+ &o->link, o->target, o->pie, o->shared, /*relocatable=*/0,
+ o->shared ? KIT_LINK_OUTPUT_SHARED : KIT_LINK_OUTPUT_EXE, script,
+ &lopts, &rpath_slices) != 0)
+ goto out;
/* Translate the command-line link order into the engine's public
* KitLinkInputOrder list. The dead-simple fallback for o->nlink_items == 0
@@ -2803,6 +2512,7 @@ out:
}
}
if (script) kit_link_script_free(&ctx, script);
+ driver_link_flags_free_rpath_slices(&o->link, rpath_slices);
if (compiler) driver_compiler_free(compiler);
kit_target_free(target);
driver_release_bytes(io, &script_lf);
@@ -2831,7 +2541,10 @@ out:
if (src_lf) driver_free(env, src_lf, o->nsource_files * sizeof(*src_lf));
if (src_bytes)
driver_free(env, src_bytes, o->nsource_files * sizeof(*src_bytes));
- if (objs) driver_free(env, objs, nsrc * sizeof(*objs));
+ if (objs) {
+ for (i = 0; i < nsrc; ++i) kit_obj_builder_free(objs[i]);
+ driver_free(env, objs, nsrc * sizeof(*objs));
+ }
return rc;
}
diff --git a/driver/lib/link_engine.h b/driver/lib/link_engine.h
@@ -13,7 +13,7 @@
* `order` drives the add sequence: each entry's (kind, index) selects one
* element of the parallel arrays below, so link order matches the command
* line exactly.
- * KIT_LINK_INPUT_OBJ -> objs[index] (in-memory builder; consumed)
+ * KIT_LINK_INPUT_OBJ -> objs[index] (in-memory builder; borrowed)
* KIT_LINK_INPUT_OBJ_BYTES -> obj_bytes[index], labelled obj_names[index]
* KIT_LINK_INPUT_ARCHIVE -> archives[index]
* KIT_LINK_INPUT_DSO -> dso_bytes[index], labelled dso_names[index]
@@ -36,7 +36,9 @@ typedef struct DriverLinkInputs {
/* Open a session with `lopts`, add every input named by `in->order`, and emit
* to `out`. Returns the resulting KitStatus (KIT_OK on success). The session is
- * always freed before return; `out` is neither opened nor closed here. */
+ * always freed before return; `out` is neither opened nor closed here.
+ * In-memory builders are borrowed for the duration of the session; callers
+ * retain ownership and must free them after this returns. */
KitStatus driver_link_engine_emit(KitCompiler* compiler,
const KitLinkSessionOptions* lopts,
const DriverLinkInputs* in, KitWriter* out);
diff --git a/driver/lib/link_flags.c b/driver/lib/link_flags.c
@@ -0,0 +1,441 @@
+#include "link_flags.h"
+
+#include <string.h>
+
+static int lf_tok_eq(const char* tok, size_t n, const char* lit) {
+ size_t ln = driver_strlen(lit);
+ return n == ln && driver_strneq(tok, lit, ln);
+}
+
+static int lf_tok_prefix(const char* tok, size_t n, const char* lit) {
+ size_t ln = driver_strlen(lit);
+ return n >= ln && driver_strneq(tok, lit, ln);
+}
+
+static int lf_grow_rpaths(DriverLinkFlags* lf) {
+ uint32_t old_cap = lf->cap_rpaths;
+ uint32_t new_cap = old_cap ? old_cap * 2u : 8u;
+ const char** nr;
+ if (new_cap <= old_cap) return 1;
+ nr = (const char**)driver_alloc_zeroed(lf->env,
+ (size_t)new_cap * sizeof(*nr));
+ if (!nr) return 1;
+ if (lf->rpaths) {
+ driver_memcpy(nr, lf->rpaths, (size_t)lf->nrpaths * sizeof(*lf->rpaths));
+ driver_free(lf->env, lf->rpaths, (size_t)old_cap * sizeof(*lf->rpaths));
+ }
+ lf->rpaths = nr;
+ lf->cap_rpaths = new_cap;
+ return 0;
+}
+
+static int lf_grow_owned(DriverLinkFlags* lf) {
+ uint32_t old_cap = lf->cap_owned_strings;
+ uint32_t new_cap = old_cap ? old_cap * 2u : 8u;
+ char** ns;
+ size_t* nsz;
+ if (new_cap <= old_cap) return 1;
+ ns = (char**)driver_alloc_zeroed(lf->env, (size_t)new_cap * sizeof(*ns));
+ nsz = (size_t*)driver_alloc_zeroed(lf->env, (size_t)new_cap * sizeof(*nsz));
+ if (!ns || !nsz) {
+ if (ns) driver_free(lf->env, ns, (size_t)new_cap * sizeof(*ns));
+ if (nsz) driver_free(lf->env, nsz, (size_t)new_cap * sizeof(*nsz));
+ return 1;
+ }
+ if (lf->owned_strings) {
+ driver_memcpy(ns, lf->owned_strings,
+ (size_t)lf->nowned_strings * sizeof(*lf->owned_strings));
+ driver_free(lf->env, lf->owned_strings,
+ (size_t)old_cap * sizeof(*lf->owned_strings));
+ }
+ if (lf->owned_string_sizes) {
+ driver_memcpy(nsz, lf->owned_string_sizes,
+ (size_t)lf->nowned_strings *
+ sizeof(*lf->owned_string_sizes));
+ driver_free(lf->env, lf->owned_string_sizes,
+ (size_t)old_cap * sizeof(*lf->owned_string_sizes));
+ }
+ lf->owned_strings = ns;
+ lf->owned_string_sizes = nsz;
+ lf->cap_owned_strings = new_cap;
+ return 0;
+}
+
+static int lf_take_owned(DriverLinkFlags* lf, char* s, size_t n) {
+ if (lf->nowned_strings >= lf->cap_owned_strings && lf_grow_owned(lf) != 0)
+ return 1;
+ lf->owned_strings[lf->nowned_strings] = s;
+ lf->owned_string_sizes[lf->nowned_strings] = n;
+ lf->nowned_strings++;
+ return 0;
+}
+
+static char* lf_dup_owned(DriverLinkFlags* lf, const char* s, size_t n) {
+ size_t bytes = n + 1u;
+ char* buf;
+ if (bytes == 0) return NULL;
+ buf = (char*)driver_alloc(lf->env, bytes);
+ if (!buf) return NULL;
+ driver_memcpy(buf, s, n);
+ buf[n] = '\0';
+ if (lf_take_owned(lf, buf, bytes) != 0) {
+ driver_free(lf->env, buf, bytes);
+ return NULL;
+ }
+ return buf;
+}
+
+static void lf_free_build_id(DriverLinkFlags* lf) {
+ if (lf->build_id_bytes) {
+ driver_free(lf->env, lf->build_id_bytes, lf->build_id_size);
+ lf->build_id_bytes = NULL;
+ lf->build_id_len = 0;
+ lf->build_id_size = 0;
+ }
+}
+
+static int lf_hex_val(char c, unsigned* out) {
+ if (c >= '0' && c <= '9') {
+ *out = (unsigned)(c - '0');
+ return 0;
+ }
+ if (c >= 'a' && c <= 'f') {
+ *out = (unsigned)(c - 'a' + 10);
+ return 0;
+ }
+ if (c >= 'A' && c <= 'F') {
+ *out = (unsigned)(c - 'A' + 10);
+ return 0;
+ }
+ return 1;
+}
+
+static int lf_parse_hex_bytes(DriverLinkFlags* lf, const char* s, size_t n,
+ uint8_t** out_bytes, uint32_t* out_len,
+ size_t* out_size) {
+ uint8_t* bs;
+ size_t i;
+ if (n == 0 || (n & 1u) || n / 2u > UINT32_MAX) return 1;
+ bs = (uint8_t*)driver_alloc(lf->env, n / 2u);
+ if (!bs) return 1;
+ for (i = 0; i < n; i += 2u) {
+ unsigned hi, lo;
+ if (lf_hex_val(s[i], &hi) != 0 || lf_hex_val(s[i + 1u], &lo) != 0) {
+ driver_free(lf->env, bs, n / 2u);
+ return 1;
+ }
+ bs[i / 2u] = (uint8_t)((hi << 4) | lo);
+ }
+ *out_bytes = bs;
+ *out_len = (uint32_t)(n / 2u);
+ *out_size = n / 2u;
+ return 0;
+}
+
+static int lf_record_build_id_span(DriverLinkFlags* lf, const char* val,
+ size_t n) {
+ if (n == 4 && driver_strneq(val, "none", 4)) {
+ lf_free_build_id(lf);
+ lf->build_id_mode = KIT_BUILDID_NONE;
+ return 0;
+ }
+ if (n == 6 && driver_strneq(val, "sha256", 6)) {
+ lf_free_build_id(lf);
+ lf->build_id_mode = KIT_BUILDID_SHA256;
+ return 0;
+ }
+ if (n == 4 && driver_strneq(val, "uuid", 4)) {
+ lf_free_build_id(lf);
+ lf->build_id_mode = KIT_BUILDID_UUID;
+ return 0;
+ }
+ if (n >= 2 && driver_strneq(val, "0x", 2)) {
+ uint8_t* bytes = NULL;
+ uint32_t len = 0;
+ size_t size = 0;
+ if (lf_parse_hex_bytes(lf, val + 2, n - 2u, &bytes, &len, &size) != 0) {
+ driver_errf(lf->tool,
+ "--build-id=0x... requires an even-length hex string");
+ return 1;
+ }
+ lf_free_build_id(lf);
+ lf->build_id_bytes = bytes;
+ lf->build_id_len = len;
+ lf->build_id_size = size;
+ lf->build_id_mode = KIT_BUILDID_USER;
+ return 0;
+ }
+ driver_errf(lf->tool, "unknown --build-id value: %.*s", (int)n, val);
+ return 1;
+}
+
+static int lf_record_rpath(DriverLinkFlags* lf, const char* s, size_t n) {
+ char* buf;
+ if (lf->nrpaths >= lf->cap_rpaths && lf_grow_rpaths(lf) != 0) {
+ driver_errf(lf->tool, "out of memory");
+ return 1;
+ }
+ buf = lf_dup_owned(lf, s, n);
+ if (!buf) {
+ driver_errf(lf->tool, "out of memory");
+ return 1;
+ }
+ lf->rpaths[lf->nrpaths++] = buf;
+ return 0;
+}
+
+static int lf_record_soname(DriverLinkFlags* lf, const char* s, size_t n) {
+ char* buf = lf_dup_owned(lf, s, n);
+ if (!buf) {
+ driver_errf(lf->tool, "out of memory");
+ return 1;
+ }
+ lf->soname = buf;
+ return 0;
+}
+
+static int lf_record_interp(DriverLinkFlags* lf, const char* s, size_t n) {
+ char* buf = lf_dup_owned(lf, s, n);
+ if (!buf) {
+ driver_errf(lf->tool, "out of memory");
+ return 1;
+ }
+ lf->interp_path = buf;
+ return 0;
+}
+
+static int lf_subsystem_eq(const char* val, size_t n, const char* want) {
+ size_t i;
+ for (i = 0; want[i]; ++i) {
+ char a;
+ char b;
+ if (i >= n) return 0;
+ a = val[i];
+ b = want[i];
+ if (a >= 'a' && a <= 'z') a = (char)(a - 'a' + 'A');
+ if (b >= 'a' && b <= 'z') b = (char)(b - 'a' + 'A');
+ if (a != b) return 0;
+ }
+ return i == n || val[i] == ',';
+}
+
+int driver_link_flags_init(DriverLinkFlags* lf, DriverEnv* env,
+ const char* tool, uint32_t initial_cap) {
+ uint32_t cap = initial_cap ? initial_cap : 8u;
+ memset(lf, 0, sizeof(*lf));
+ lf->env = env;
+ lf->tool = tool;
+ lf->new_dtags = 1;
+ lf->cap_rpaths = cap;
+ lf->cap_owned_strings = cap;
+ lf->rpaths =
+ (const char**)driver_alloc_zeroed(env, (size_t)cap * sizeof(*lf->rpaths));
+ lf->owned_strings =
+ (char**)driver_alloc_zeroed(env, (size_t)cap * sizeof(*lf->owned_strings));
+ lf->owned_string_sizes = (size_t*)driver_alloc_zeroed(
+ env, (size_t)cap * sizeof(*lf->owned_string_sizes));
+ if (!lf->rpaths || !lf->owned_strings || !lf->owned_string_sizes) {
+ driver_link_flags_fini(lf);
+ return 1;
+ }
+ return 0;
+}
+
+void driver_link_flags_fini(DriverLinkFlags* lf) {
+ uint32_t i;
+ if (!lf || !lf->env) return;
+ for (i = 0; i < lf->nowned_strings; ++i) {
+ if (lf->owned_strings[i])
+ driver_free(lf->env, lf->owned_strings[i], lf->owned_string_sizes[i]);
+ }
+ lf_free_build_id(lf);
+ if (lf->rpaths)
+ driver_free(lf->env, lf->rpaths,
+ (size_t)lf->cap_rpaths * sizeof(*lf->rpaths));
+ if (lf->owned_strings)
+ driver_free(lf->env, lf->owned_strings,
+ (size_t)lf->cap_owned_strings * sizeof(*lf->owned_strings));
+ if (lf->owned_string_sizes)
+ driver_free(lf->env, lf->owned_string_sizes,
+ (size_t)lf->cap_owned_strings *
+ sizeof(*lf->owned_string_sizes));
+ memset(lf, 0, sizeof(*lf));
+}
+
+int driver_link_flags_record_build_id(DriverLinkFlags* lf, const char* val) {
+ return lf_record_build_id_span(lf, val, driver_strlen(val));
+}
+
+int driver_link_flags_record_pe_subsystem(DriverLinkFlags* lf,
+ const char* val, size_t n) {
+ if (lf_subsystem_eq(val, n, "CONSOLE") ||
+ lf_subsystem_eq(val, n, "CUI")) {
+ lf->pe_subsystem = KIT_PE_SUBSYSTEM_WINDOWS_CUI;
+ return 0;
+ }
+ if (lf_subsystem_eq(val, n, "WINDOWS") ||
+ lf_subsystem_eq(val, n, "GUI")) {
+ lf->pe_subsystem = KIT_PE_SUBSYSTEM_WINDOWS_GUI;
+ return 0;
+ }
+ driver_errf(lf->tool, "unsupported subsystem: %.*s", (int)n, val);
+ return 1;
+}
+
+int driver_link_flags_record_wl(DriverLinkFlags* lf, const char* arg) {
+ const char* p = arg;
+ int expect_rpath = 0;
+ int expect_soname = 0;
+ int expect_interp = 0;
+ int expect_subsystem = 0;
+ while (*p) {
+ const char* tok = p;
+ size_t n = 0;
+ while (p[n] && p[n] != ',') ++n;
+ p = tok + n + (tok[n] == ',' ? 1u : 0u);
+
+ if (expect_rpath || expect_soname || expect_interp || expect_subsystem) {
+ int rc = 0;
+ if (expect_rpath)
+ rc = lf_record_rpath(lf, tok, n);
+ else if (expect_soname)
+ rc = lf_record_soname(lf, tok, n);
+ else if (expect_interp)
+ rc = lf_record_interp(lf, tok, n);
+ else if (expect_subsystem)
+ rc = driver_link_flags_record_pe_subsystem(lf, tok, n);
+ if (rc != 0) return 1;
+ expect_rpath = expect_soname = expect_interp = expect_subsystem = 0;
+ continue;
+ }
+
+ if (lf_tok_prefix(tok, n, "-soname=")) {
+ if (lf_record_soname(lf, tok + 8, n - 8u) != 0) return 1;
+ continue;
+ }
+ if (lf_tok_eq(tok, n, "-soname")) {
+ expect_soname = 1;
+ continue;
+ }
+ if (lf_tok_prefix(tok, n, "-rpath=")) {
+ if (lf_record_rpath(lf, tok + 7, n - 7u) != 0) return 1;
+ continue;
+ }
+ if (lf_tok_eq(tok, n, "-rpath")) {
+ expect_rpath = 1;
+ continue;
+ }
+ if (lf_tok_prefix(tok, n, "-dynamic-linker=")) {
+ if (lf_record_interp(lf, tok + 16, n - 16u) != 0) return 1;
+ continue;
+ }
+ if (lf_tok_eq(tok, n, "-dynamic-linker")) {
+ expect_interp = 1;
+ continue;
+ }
+ if (lf_tok_eq(tok, n, "--enable-new-dtags")) {
+ lf->new_dtags = 1;
+ continue;
+ }
+ if (lf_tok_eq(tok, n, "--disable-new-dtags")) {
+ lf->new_dtags = 0;
+ continue;
+ }
+ if (lf_tok_eq(tok, n, "--gc-sections")) {
+ lf->gc_sections = 1;
+ continue;
+ }
+ if (lf_tok_eq(tok, n, "--no-gc-sections")) {
+ lf->gc_sections = 0;
+ continue;
+ }
+ if (lf_tok_eq(tok, n, "-S") || lf_tok_eq(tok, n, "--strip-debug")) {
+ lf->strip_debug = 1;
+ continue;
+ }
+ if (lf_tok_prefix(tok, n, "--build-id=")) {
+ if (lf_record_build_id_span(lf, tok + 11, n - 11u) != 0) return 1;
+ continue;
+ }
+ if (lf_tok_eq(tok, n, "--build-id")) {
+ lf_free_build_id(lf);
+ lf->build_id_mode = KIT_BUILDID_SHA256;
+ continue;
+ }
+ if (lf_tok_prefix(tok, n, "--subsystem=")) {
+ if (driver_link_flags_record_pe_subsystem(lf, tok + 12, n - 12u) != 0)
+ return 1;
+ continue;
+ }
+ if (lf_tok_eq(tok, n, "--subsystem")) {
+ expect_subsystem = 1;
+ continue;
+ }
+ if (lf_tok_prefix(tok, n, "/SUBSYSTEM:")) {
+ if (driver_link_flags_record_pe_subsystem(lf, tok + 11, n - 11u) != 0)
+ return 1;
+ continue;
+ }
+
+ driver_errf(lf->tool, "unsupported -Wl, token: %.*s", (int)n, tok);
+ return 1;
+ }
+ if (expect_rpath || expect_soname || expect_interp || expect_subsystem) {
+ driver_errf(lf->tool, "-Wl option requires another comma argument");
+ return 1;
+ }
+ return 0;
+}
+
+int driver_link_flags_fill_options(const DriverLinkFlags* lf,
+ KitTargetSpec target, int explicit_pie,
+ int shared, int relocatable,
+ uint8_t output_kind,
+ const KitLinkScript* script,
+ KitLinkSessionOptions* lopts,
+ KitSlice** rpath_slices_out) {
+ KitSlice* rpath_slices = NULL;
+ uint32_t i;
+ *rpath_slices_out = NULL;
+ if (lf->nrpaths) {
+ rpath_slices = (KitSlice*)driver_alloc_zeroed(
+ lf->env, (size_t)lf->nrpaths * sizeof(*rpath_slices));
+ if (!rpath_slices) {
+ driver_errf(lf->tool, "out of memory");
+ return 1;
+ }
+ for (i = 0; i < lf->nrpaths; ++i)
+ rpath_slices[i] = kit_slice_cstr(lf->rpaths[i]);
+ }
+ memset(lopts, 0, sizeof(*lopts));
+ lopts->output_kind = output_kind;
+ lopts->entry = kit_slice_cstr(lf->entry);
+ lopts->linker_script = script;
+ lopts->build_id_mode = lf->build_id_mode;
+ lopts->build_id_bytes = lf->build_id_bytes;
+ lopts->build_id_len = lf->build_id_len;
+ lopts->gc_sections = lf->gc_sections != 0;
+ lopts->strip_debug = lf->strip_debug != 0;
+ lopts->pie =
+ driver_link_pie(target, explicit_pie, shared, relocatable) != 0;
+ lopts->pe_subsystem = lf->pe_subsystem;
+ lopts->interp_path = kit_slice_cstr(lf->interp_path);
+ lopts->soname = kit_slice_cstr(lf->soname);
+ if (lf->new_dtags) {
+ lopts->runpaths = rpath_slices;
+ lopts->nrunpaths = lf->nrpaths;
+ } else {
+ lopts->rpaths = rpath_slices;
+ lopts->nrpaths = lf->nrpaths;
+ }
+ lopts->allow_undefined = 1;
+ *rpath_slices_out = rpath_slices;
+ return 0;
+}
+
+void driver_link_flags_free_rpath_slices(const DriverLinkFlags* lf,
+ KitSlice* rpath_slices) {
+ if (rpath_slices)
+ driver_free(lf->env, rpath_slices,
+ (size_t)lf->nrpaths * sizeof(*rpath_slices));
+}
diff --git a/driver/lib/link_flags.h b/driver/lib/link_flags.h
@@ -0,0 +1,56 @@
+#ifndef KIT_DRIVER_LINK_FLAGS_H
+#define KIT_DRIVER_LINK_FLAGS_H
+
+#include <kit/link.h>
+#include <stddef.h>
+#include <stdint.h>
+
+#include "driver.h"
+
+typedef struct DriverLinkFlags {
+ DriverEnv* env;
+ const char* tool;
+
+ const char* entry;
+ const char* linker_script;
+ uint16_t pe_subsystem; /* KitPeSubsystem */
+ const char* soname;
+ const char* interp_path;
+ const char** rpaths;
+ uint32_t nrpaths;
+ uint32_t cap_rpaths;
+ int new_dtags; /* 1=DT_RUNPATH (default), 0=DT_RPATH */
+ int gc_sections;
+ int strip_debug;
+
+ uint8_t build_id_mode; /* KitBuildIdMode */
+ uint8_t* build_id_bytes;
+ uint32_t build_id_len;
+ size_t build_id_size;
+
+ char** owned_strings;
+ size_t* owned_string_sizes;
+ uint32_t nowned_strings;
+ uint32_t cap_owned_strings;
+} DriverLinkFlags;
+
+int driver_link_flags_init(DriverLinkFlags* lf, DriverEnv* env,
+ const char* tool, uint32_t initial_cap);
+void driver_link_flags_fini(DriverLinkFlags* lf);
+
+int driver_link_flags_record_wl(DriverLinkFlags* lf, const char* arg);
+int driver_link_flags_record_build_id(DriverLinkFlags* lf, const char* val);
+int driver_link_flags_record_pe_subsystem(DriverLinkFlags* lf,
+ const char* val, size_t n);
+
+int driver_link_flags_fill_options(const DriverLinkFlags* lf,
+ KitTargetSpec target, int explicit_pie,
+ int shared, int relocatable,
+ uint8_t output_kind,
+ const KitLinkScript* script,
+ KitLinkSessionOptions* lopts,
+ KitSlice** rpath_slices_out);
+void driver_link_flags_free_rpath_slices(const DriverLinkFlags* lf,
+ KitSlice* rpath_slices);
+
+#endif
diff --git a/mk/driver_srcs.mk b/mk/driver_srcs.mk
@@ -64,6 +64,7 @@ DRIVER_SRCS += $(call need-any,CC CHECK BUILD_EXE BUILD_LIB BUILD_OBJ LD RUN,dri
DRIVER_SRCS += $(call need-any,CC CHECK BUILD_EXE BUILD_LIB BUILD_OBJ RUN,driver/lib/hosted.c)
DRIVER_SRCS += $(call need-any,CC CHECK BUILD_EXE BUILD_LIB BUILD_OBJ LD,driver/lib/runtime.c)
DRIVER_SRCS += $(call need-any,CC CHECK BUILD_EXE BUILD_LIB BUILD_OBJ,driver/lib/link_engine.c)
+DRIVER_SRCS += $(call need-any,CC CHECK BUILD_EXE BUILD_LIB BUILD_OBJ,driver/lib/link_flags.c)
DRIVER_SRCS += $(call need-any,BUILD_EXE BUILD_LIB BUILD_OBJ,driver/lib/archive_engine.c)
DRIVER_SRCS += $(call need-any,AR RANLIB STRIP DBG RUN BUILD_EXE BUILD_LIB BUILD_OBJ,driver/lib/inputs.c)
DRIVER_SRCS += $(call need-any,RUN,driver/lib/wasm_run.c)
diff --git a/mk/test.mk b/mk/test.mk
@@ -67,6 +67,7 @@ TEST_TARGETS = \
test-driver-cas \
test-driver-cc \
test-driver-build \
+ test-driver-lsan \
test-driver-objcopy \
test-driver-objdump \
test-driver-pkg \
@@ -201,6 +202,15 @@ test-driver-cc: bin
test-driver-build: bin
@KIT=$(abspath $(BIN)) sh test/buildcmds/run.sh
+ifeq ($(HOST_OS),linux)
+test-driver-lsan:
+ @$(MAKE) test-driver RELEASE=0 BUILD_DIR=build/lsan \
+ ASAN_OPTIONS=detect_leaks=1:halt_on_error=1:abort_on_error=1
+else
+test-driver-lsan:
+ @printf '%s\n' 'SKIP test-driver-lsan: LeakSanitizer requires HOST_OS=linux'
+endif
+
# test-cbackend: --emit=c C-source backend, driven through three
# frontends — parse-runner (C), toy-runner (toy), wasm-runner (wat/wasm).
# Each invokes its existing runner with paths=C so a single corpus per
@@ -546,6 +556,7 @@ TEST_RT_DEP = $(_TEST_RT_$(KIT_TEST_ARCH))
# reference. KIT_RT_RUNTIME_ARCHES mirrors the default in test/rt/run.sh.
KIT_RT_RUNTIME_ARCHES ?= aa64 x64 rv64
RT_RUNTIME_DEPS := $(foreach a,$(KIT_RT_RUNTIME_ARCHES),$(_TEST_RT_$(a)))
+LINK_EXE_RUNNER = build/test/link-exe-runner
test-rt-runtime: bin $(RT_RUNTIME_DEPS) $(LINK_EXE_RUNNER)
@bash test/rt/run.sh
@@ -566,7 +577,6 @@ COFF_IMPORT_SMOKE_BIN = build/test/pe-import-smoke
COFF_IMPORT_MINGW_BIN = build/test/pe-import-mingw
COFF_DSO_FORWARDER_BIN = build/test/pe-dso-forwarder
COFF_MIXED_ARCHIVE_BIN = build/test/pe-mixed-archive
-LINK_EXE_RUNNER = build/test/link-exe-runner
JIT_RUNNER = build/test/jit-runner
PARSE_RUNNER = build/test/parse-runner
ASM_RUNNER = build/test/asm-runner
diff --git a/test/buildcmds/run.sh b/test/buildcmds/run.sh
@@ -126,6 +126,11 @@ run_fail bo-neg-ir-needs-opt "$KIT" build-obj --emit=ir prog.toy -o x.ir
run_fail bo-neg-o-multi-asm "$KIT" build-obj -S -Iinc hello.c std.c -o both.s
# A per-output flag is not allowed inside a --group.
run_fail bo-neg-global-in-group "$KIT" build-obj -Iinc --group -O2 -- hello.c
+# A missing -x argument before a --group source separator should not treat `--`
+# as a language name.
+run_fail bo-neg-group-x-missing-arg "$KIT" build-obj --group -x -- hello.c
+contains bo-neg-group-x-missing-arg-diag "$work/bo-neg-group-x-missing-arg.err" \
+ "requires an argument"
# -dynamic is a build-lib concept.
run_fail bo-neg-dynamic "$KIT" build-obj -dynamic prog.toy
# unknown flag.
@@ -155,6 +160,11 @@ run_ok be-link "$KIT" build-exe -lc -Iinc hello.c helper.c -o app
is_executable be-link-exec app
run_ok be-run ./app
+# build-exe should reject the shared-library spelling instead of ignoring it.
+run_fail be-neg-shared "$KIT" build-exe -shared -lc -Iinc hello.c helper.c \
+ -o shared-nope
+contains be-neg-shared-diag "$work/be-neg-shared.err" "shared"
+
# Default output name a.out when -o is omitted.
run_ok be-default-name "$KIT" build-exe -lc -Iinc hello.c helper.c
assert_file_exists be-default-name-file a.out