commit 8e44a1d43cfe18ffe958c88434c62ee2fa6cef34
parent 57dce832a3603adb73f196313e0859e19946bac2
Author: Ryan Sepassi <rsepassi@gmail.com>
Date: Thu, 16 Jul 2026 10:12:33 -0700
driver: make binary input handling transactional
Diffstat:
| M | driver/cmd/ld.c | | | 49 | ++++++++++++++++++++++++++++++++++++++++--------- |
| M | driver/cmd/size.c | | | 110 | ++++++++++++++++++++++++++++++++++++++++++++++++++++--------------------------- |
| A | test/diagnostics/run.sh | | | 91 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
3 files changed, 204 insertions(+), 46 deletions(-)
diff --git a/driver/cmd/ld.c b/driver/cmd/ld.c
@@ -721,8 +721,7 @@ static int ld_set_target_triple(LdOptions* o, const char* triple) {
return 1;
}
if (driver_target_from_triple(triple, &t) != 0) {
- driver_errf(LD_TOOL, "unrecognized target triple: %.*s",
- KIT_SLICE_ARG(kit_slice_cstr(triple)));
+ driver_err_unknown_target(LD_TOOL, triple);
return 1;
}
o->windows_ucrt_target = strstr(triple, "windows-gnullvm") != NULL;
@@ -3115,6 +3114,30 @@ static int ld_run_link(LdOptions* o) {
KitTargetOptions topts;
memset(&topts, 0, sizeof topts);
topts.spec = o->target;
+ /* A linker target auto-detected from RISC-V ELF inputs already carries
+ * the psABI in spec.float_abi. kit_target_new normally derives that
+ * field from -march/-mabi, so reproduce the detected profile here rather
+ * than letting the rv32 default (ilp32 soft-float) overwrite ilp32f/d.
+ * The ISA matters for rv32 hardware ABIs because its conservative default
+ * deliberately omits F/D; rv64's default already includes both. */
+ if (o->target.arch == KIT_ARCH_RV32) {
+ if (o->target.float_abi == KIT_FLOAT_ABI_SINGLE) {
+ topts.isa = kit_slice_cstr("rv32imafc_zicsr_zifencei");
+ topts.abi = kit_slice_cstr("ilp32f");
+ } else if (o->target.float_abi == KIT_FLOAT_ABI_DOUBLE) {
+ topts.isa = kit_slice_cstr("rv32imafdc_zicsr_zifencei");
+ topts.abi = kit_slice_cstr("ilp32d");
+ } else if (o->target.float_abi == KIT_FLOAT_ABI_SOFT) {
+ topts.abi = kit_slice_cstr("ilp32");
+ }
+ } else if (o->target.arch == KIT_ARCH_RV64) {
+ if (o->target.float_abi == KIT_FLOAT_ABI_SINGLE)
+ topts.abi = kit_slice_cstr("lp64f");
+ else if (o->target.float_abi == KIT_FLOAT_ABI_DOUBLE)
+ topts.abi = kit_slice_cstr("lp64d");
+ else if (o->target.float_abi == KIT_FLOAT_ABI_SOFT)
+ topts.abi = kit_slice_cstr("lp64");
+ }
if (kit_target_new(&ctx, &topts, &target) != KIT_OK ||
driver_compiler_new(target, &ctx, &compiler) != KIT_OK) {
driver_errf(LD_TOOL, "failed to initialize compiler");
@@ -3131,12 +3154,6 @@ static int ld_run_link(LdOptions* o) {
}
}
- if (io->open_writer(io->user, o->output_path, &writer) != KIT_OK) {
- driver_errf(LD_TOOL, "failed to open output: %.*s",
- KIT_SLICE_ARG(kit_slice_cstr(o->output_path)));
- goto out;
- }
-
/* Translate the recorded link-item order into the engine's KitLinkInputOrder
* list. ld records no compiled-source items, so the source args are NULL. */
if (o->inputs.nlink_items) {
@@ -3213,6 +3230,17 @@ static int ld_run_link(LdOptions* o) {
break;
}
}
+ /* Parse and validate the complete input set before an output writer is
+ * opened. Apart from preserving the primary named-input diagnostic, this
+ * means a malformed object/archive/DSO or a link-resolution failure cannot
+ * create even a temporary output artifact. */
+ if (st == KIT_OK) st = kit_link_session_resolve(link);
+ if (st == KIT_OK &&
+ io->open_writer(io->user, o->output_path, &writer) != KIT_OK) {
+ driver_errf(LD_TOOL, "failed to open output: %.*s",
+ KIT_SLICE_ARG(kit_slice_cstr(o->output_path)));
+ st = KIT_IO;
+ }
if (st == KIT_OK) st = kit_link_session_emit(link, writer);
if (st == KIT_OK) st = ld_write_link_report(o, io, link, o->lf.map_path, 0);
if (st == KIT_OK)
@@ -3224,7 +3252,10 @@ static int ld_run_link(LdOptions* o) {
}
out:
- if (writer) kit_writer_close(writer);
+ if (writer) {
+ if (rc != 0) driver_writer_abort(writer);
+ kit_writer_close(writer);
+ }
kit_link_session_free(link);
/* Match compiler/linker drivers: successful link outputs get executable
* file modes while still respecting the process umask. Done after closing
diff --git a/driver/cmd/size.c b/driver/cmd/size.c
@@ -63,6 +63,12 @@ static void size_print_berkeley_header(const SizeOpts* opts) {
"data", w, "bss", "dec", "hex");
}
+static void size_begin_output(const SizeOpts* opts, int* begun) {
+ if (*begun) return;
+ if (opts->fmt == SIZE_FMT_BERKELEY) size_print_berkeley_header(opts);
+ *begun = 1;
+}
+
static void size_print_berkeley_sums(const KitObjSizeTotals* a, const char* name,
const SizeOpts* opts) {
int w = size_width(opts->radix);
@@ -100,33 +106,39 @@ static void size_print_sysv(KitObjFile* of, const char* name,
static int size_process_file(const KitContext* ctx, const KitSlice* input,
const char* path, const SizeOpts* opts,
- KitObjSizeTotals* total_out, int* any_out) {
+ KitObjSizeTotals* total_out, int* any_out,
+ int* output_begun) {
KitBinFmt fmt = kit_detect_fmt(input->data, input->len);
if (fmt == KIT_BIN_AR) {
KitArIter* it = NULL;
+ int failed = 0;
+ int valid_members = 0;
if (kit_ar_iter_new(ctx, input, &it) != KIT_OK) {
driver_errf(SIZE_TOOL, "%s: not a recognized archive", path);
return 1;
}
for (;;) {
KitArMember m;
- if (kit_ar_iter_next(it, &m) != KIT_ITER_ITEM) break;
+ KitIterResult ir = kit_ar_iter_next(it, &m);
+ if (ir == KIT_ITER_END) break;
+ if (ir == KIT_ITER_ERROR) {
+ driver_errf(SIZE_TOOL, "%s: corrupt archive", path);
+ failed = 1;
+ break;
+ }
KitObjFile* of = NULL;
KitSlice mb;
+ char nmbuf[512];
mb.data = m.data;
mb.len = m.size;
- if (kit_obj_open(ctx, KIT_SLICE_NULL, &mb, &of) == KIT_OK) {
+ snprintf(nmbuf, sizeof nmbuf, "%s(%.*s)", path, (int)m.name.len,
+ m.name.s);
+ if (kit_obj_open(ctx, kit_slice_cstr(nmbuf), &mb, &of) == KIT_OK) {
KitObjSizeTotals a = size_compute_obj(of, opts);
+ size_begin_output(opts, output_begun);
if (opts->fmt == SIZE_FMT_BERKELEY) {
- char nmbuf[512];
- snprintf(nmbuf, sizeof nmbuf, "%.*s(%.*s)",
- path ? (int)strlen(path) : 0, path ? path : "",
- (int)m.name.len, m.name.s);
size_print_berkeley_sums(&a, nmbuf, opts);
} else {
- char nmbuf[512];
- snprintf(nmbuf, sizeof nmbuf, "%s(%.*s)", path, (int)m.name.len,
- m.name.s);
size_print_sysv(of, nmbuf, opts);
}
total_out->text += a.text;
@@ -134,10 +146,19 @@ static int size_process_file(const KitContext* ctx, const KitSlice* input,
total_out->bss += a.bss;
total_out->total += a.total;
*any_out = 1;
+ ++valid_members;
kit_obj_free(of);
+ } else {
+ driver_errf(SIZE_TOOL, "%s: not a recognized object file", nmbuf);
+ failed = 1;
}
}
kit_ar_iter_free(it);
+ if (!valid_members && !failed) {
+ driver_errf(SIZE_TOOL, "%s: archive contains no object members", path);
+ failed = 1;
+ }
+ return failed;
} else {
KitObjFile* of = NULL;
if (kit_obj_open(ctx, kit_slice_cstr(path), input, &of) != KIT_OK) {
@@ -146,6 +167,7 @@ static int size_process_file(const KitContext* ctx, const KitSlice* input,
}
{
KitObjSizeTotals a = size_compute_obj(of, opts);
+ size_begin_output(opts, output_begun);
if (opts->fmt == SIZE_FMT_BERKELEY) {
size_print_berkeley_sums(&a, path, opts);
} else {
@@ -204,8 +226,9 @@ int driver_size(int argc, char** argv) {
SizeOpts opts;
KitObjSizeTotals totals;
int i, rc = 1, any_input = 0, any_output = 0;
+ int output_begun = 0, had_error = 0, options_done = 0;
- if (argc < 2 || driver_argv_wants_help(argc, argv, 1)) {
+ if (argc < 2) {
driver_help_size();
return 0;
}
@@ -218,35 +241,48 @@ int driver_size(int argc, char** argv) {
for (i = 1; i < argc; ++i) {
const char* a = argv[i];
- if (driver_streq(a, "-A") || driver_streq(a, "--format=sysv")) {
+ if (!options_done && driver_streq(a, "--")) {
+ options_done = 1;
+ continue;
+ }
+ if (!options_done &&
+ (driver_streq(a, "-h") || driver_streq(a, "--help"))) {
+ driver_help_size();
+ rc = 0;
+ goto done;
+ }
+ if (!options_done &&
+ (driver_streq(a, "-A") || driver_streq(a, "--format=sysv"))) {
opts.fmt = SIZE_FMT_SYSV;
continue;
}
- if (driver_streq(a, "-B") || driver_streq(a, "--format=berkeley")) {
+ if (!options_done &&
+ (driver_streq(a, "-B") || driver_streq(a, "--format=berkeley"))) {
opts.fmt = SIZE_FMT_BERKELEY;
continue;
}
- if (driver_streq(a, "-d")) {
+ if (!options_done && driver_streq(a, "-d")) {
opts.radix = SIZE_RAD_DEC;
continue;
}
- if (driver_streq(a, "-o")) {
+ if (!options_done && driver_streq(a, "-o")) {
opts.radix = SIZE_RAD_OCT;
continue;
}
- if (driver_streq(a, "-x")) {
+ if (!options_done && driver_streq(a, "-x")) {
opts.radix = SIZE_RAD_HEX;
continue;
}
- if (driver_streq(a, "--common")) {
+ if (!options_done && driver_streq(a, "--common")) {
opts.common = 1;
continue;
}
- if (driver_streq(a, "-t") || driver_streq(a, "--totals")) {
+ if (!options_done &&
+ (driver_streq(a, "-t") || driver_streq(a, "--totals"))) {
opts.totals = 1;
continue;
}
- if (a[0] == '-') {
+ if (!options_done && a[0] == '-') {
driver_errf(SIZE_TOOL, "unknown option: %s", a);
rc = 2;
goto done;
@@ -260,15 +296,19 @@ int driver_size(int argc, char** argv) {
goto done;
}
- if (opts.fmt == SIZE_FMT_BERKELEY) size_print_berkeley_header(&opts);
-
+ options_done = 0;
for (i = 1; i < argc; ++i) {
const char* a = argv[i];
- if (driver_streq(a, "-A") || driver_streq(a, "--format=sysv") ||
- driver_streq(a, "-B") || driver_streq(a, "--format=berkeley") ||
- driver_streq(a, "-d") || driver_streq(a, "-o") ||
- driver_streq(a, "-x") || driver_streq(a, "--common") ||
- driver_streq(a, "-t") || driver_streq(a, "--totals")) {
+ if (!options_done && driver_streq(a, "--")) {
+ options_done = 1;
+ continue;
+ }
+ if (!options_done &&
+ (driver_streq(a, "-A") || driver_streq(a, "--format=sysv") ||
+ driver_streq(a, "-B") || driver_streq(a, "--format=berkeley") ||
+ driver_streq(a, "-d") || driver_streq(a, "-o") ||
+ driver_streq(a, "-x") || driver_streq(a, "--common") ||
+ driver_streq(a, "-t") || driver_streq(a, "--totals"))) {
continue;
}
{
@@ -276,22 +316,18 @@ int driver_size(int argc, char** argv) {
DriverLoad ld = {0};
KitSlice input;
if (driver_load_bytes(&env.file_io, SIZE_TOOL, path, &ld, &input) != 0) {
- rc = 1;
- goto done;
- }
- if (size_process_file(&ctx, &input, path, &opts, &totals, &any_output) !=
- 0) {
- driver_release_bytes(&env.file_io, &ld);
- rc = 1;
- goto done;
+ had_error = 1;
+ continue;
}
+ if (size_process_file(&ctx, &input, path, &opts, &totals, &any_output,
+ &output_begun) != 0)
+ had_error = 1;
driver_release_bytes(&env.file_io, &ld);
}
}
if (!any_output) {
- driver_errf(SIZE_TOOL, "no input files");
- rc = 2;
+ rc = 1;
goto done;
}
@@ -307,7 +343,7 @@ int driver_size(int argc, char** argv) {
dec_buf, hex_buf);
}
- rc = 0;
+ rc = had_error ? 1 : 0;
done:
driver_env_fini(&env);
return rc;
diff --git a/test/diagnostics/run.sh b/test/diagnostics/run.sh
@@ -0,0 +1,91 @@
+#!/bin/sh
+# Regression checks for primary-input diagnostics and transactional output in
+# ld/strip/size (KIT-P2-003).
+
+set -u
+
+script_dir=$(CDPATH= cd -- "$(dirname "$0")" && pwd)
+repo_root=$(CDPATH= cd -- "$script_dir/../.." && pwd)
+KIT=${KIT:-$repo_root/build/kit}
+KIT_KIT_DIR=$repo_root/test/lib
+. "$repo_root/test/lib/kit_sh_kit.sh"
+
+kit_require_kit driver-diagnostics
+kit_report_init
+work=$(mktemp -d "${TMPDIR:-/tmp}/kit-diagnostics-test.XXXXXX")
+trap 'rm -rf "$work"' EXIT
+
+expect_one() {
+ label=$1
+ got=$2
+ if [ "$got" -eq 1 ]; then
+ ok "$label"
+ else
+ printf 'got exit %s, want 1\n' "$got" >"$work/$label.diag"
+ not_ok "$label" "$work/$label.diag"
+ fi
+}
+
+cat >"$work/valid.c" <<'EOF'
+int diagnostic_fixture(void) { return 1; }
+EOF
+"$KIT" cc -ffreestanding -c "$work/valid.c" -o "$work/valid.o" \
+ >"$work/compile.stdout" 2>"$work/compile.stderr"
+
+# A total size failure has no stdout. A mixed request retains the valid row but
+# still returns operational failure for the missing input.
+"$KIT" size "$work/missing.o" >"$work/size-missing.stdout" \
+ 2>"$work/size-missing.stderr"
+expect_one size-missing-status "$?"
+if [ ! -s "$work/size-missing.stdout" ]; then
+ ok size-missing-stdout-empty
+else
+ not_ok size-missing-stdout-empty "$work/size-missing.stdout"
+fi
+contains size-missing-path "$work/size-missing.stderr" "$work/missing.o"
+
+"$KIT" size "$work/valid.o" "$work/missing.o" \
+ >"$work/size-mixed.stdout" 2>"$work/size-mixed.stderr"
+expect_one size-mixed-status "$?"
+contains size-mixed-valid-row "$work/size-mixed.stdout" "$work/valid.o"
+contains size-mixed-bad-path "$work/size-mixed.stderr" "$work/missing.o"
+
+# Invalid strip input names the primary input and never publishes its output.
+printf 'not an object\n' >"$work/invalid.o"
+"$KIT" strip -o "$work/stripped.o" "$work/invalid.o" \
+ >"$work/strip.stdout" 2>"$work/strip.stderr"
+expect_one strip-invalid-status "$?"
+contains strip-invalid-path "$work/strip.stderr" "$work/invalid.o"
+if [ ! -e "$work/stripped.o" ]; then
+ ok strip-invalid-output-absent
+else
+ not_ok strip-invalid-output-absent
+fi
+
+# A recognized-but-truncated member is diagnosed as archive(member).
+head -c 8 "$work/valid.o" >"$work/bad.o"
+"$KIT" ar rcs "$work/bad.a" "$work/bad.o" \
+ >"$work/ar.stdout" 2>"$work/ar.stderr"
+"$KIT" strip -o "$work/bad-stripped.a" "$work/bad.a" \
+ >"$work/strip-archive.stdout" 2>"$work/strip-archive.stderr"
+expect_one strip-archive-status "$?"
+contains strip-archive-member "$work/strip-archive.stderr" \
+ "$work/bad.a(bad.o)"
+if [ ! -e "$work/bad-stripped.a" ]; then
+ ok strip-archive-output-absent
+else
+ not_ok strip-archive-output-absent
+fi
+
+# ld resolves/validates before publication. An existing destination remains
+# byte-identical on invalid input rather than becoming empty or partial.
+cp "$work/invalid.o" "$work/ld-before.o"
+cp "$work/ld-before.o" "$work/ld-out.o"
+"$KIT" ld -r -o "$work/ld-out.o" "$work/invalid.o" \
+ >"$work/ld.stdout" 2>"$work/ld.stderr"
+expect_one ld-invalid-status "$?"
+contains ld-invalid-path "$work/ld.stderr" "$work/invalid.o"
+same_file ld-invalid-output-unchanged "$work/ld-before.o" "$work/ld-out.o"
+
+kit_summary driver-diagnostics
+kit_exit