commit fbd324f8653a7f3a4ffb2e5fdaf4b0248e7d2e57
parent 894503f54469181fc620f67445d7fdec5e33a26e
Author: Ryan Sepassi <rsepassi@gmail.com>
Date: Mon, 25 May 2026 14:13:09 -0700
Fix parse harness public API boundaries
Diffstat:
5 files changed, 83 insertions(+), 82 deletions(-)
diff --git a/test/elf/cfree-roundtrip.c b/test/elf/cfree-roundtrip.c
@@ -1,18 +1,14 @@
-/* cfree-roundtrip: read an ELF object via libcfree's read_elf, then re-emit
- * via emit_elf. Used by test/elf/run.sh as the structural roundtrip oracle.
+/* cfree-roundtrip: read an ELF object via libcfree's public object reader,
+ * then re-emit via the public object builder API. Used by test/elf/run.sh as
+ * the structural roundtrip oracle.
*
* Usage: cfree-roundtrip <in.o> <out.o>
*
- * Behavior: cfree_detect_target on the input bytes selects the Compiler
- * target; read_elf parses into an ObjBuilder; emit_elf writes the
- * canonical re-emit to out.o. Diagnostics go to stderr via the libc heap
- * + stderr diag sink. compiler_panic exits the process with status 2 and
- * the diagnostic text on stderr — this is the path the negative tests
- * (test/elf/bad/) exercise.
- *
- * Mixes public (<cfree.h>) and internal (src/obj/obj.h, src/core/core.h)
- * headers — this is a test binary, not a libcfree consumer, so seeing the
- * internal surface is fine. */
+ * Behavior: cfree_obj_open detects the input target and parses into a
+ * CfreeObjFile; cfree_obj_file_builder exposes the already-finalized builder;
+ * cfree_obj_builder_emit writes the canonical re-emit to out.o. Diagnostics go
+ * to stderr via the libc heap + stderr diag sink. Malformed recognized object
+ * files exit with status 2, matching the negative-test contract. */
#include <cfree/core.h>
#include <cfree/object.h>
@@ -24,9 +20,6 @@
#include <sys/stat.h>
#include <unistd.h>
-#include "core/core.h"
-#include "obj/obj.h"
-
/* ---- env vtables (libc-backed) ---- */
static void* heap_alloc(CfreeHeap* h, size_t n, size_t a) {
@@ -128,7 +121,8 @@ int main(int argc, char** argv) {
}
CfreeTarget target;
- if (cfree_detect_target(in_data, in_len, &target) != CFREE_OK) {
+ if (cfree_detect_target(in_data, in_len, &target) != CFREE_OK ||
+ target.obj != CFREE_OBJ_ELF) {
fprintf(stderr, "error: %s: not a recognized object file\n", in_path);
free(in_data);
return 1;
@@ -140,34 +134,39 @@ int main(int argc, char** argv) {
ctx.diag = &g_diag;
ctx.now = -1;
- CfreeCompiler* c = NULL;
- if (cfree_compiler_new(target, &ctx, &c) != CFREE_OK || !c) {
- fprintf(stderr, "error: cfree_compiler_new failed\n");
+ CfreeObjFile* obj = NULL;
+ CfreeSlice name = cfree_slice_cstr(in_path);
+ CfreeSlice input = {.data = in_data, .len = in_len};
+ CfreeStatus st = cfree_obj_open(&ctx, name, &input, &obj);
+ if (st != CFREE_OK || !obj) {
free(in_data);
- return 1;
+ return 2;
}
- /* read_elf and emit_elf are called inside their own panic boundary.
- * compiler_panic longjmps to c->panic, so we install setjmp here. */
- if (setjmp(((Compiler*)c)->panic)) {
- compiler_run_cleanups((Compiler*)c);
- cfree_compiler_free(c);
+ CfreeObjBuilder* ob = cfree_obj_file_builder(obj);
+ if (!ob) {
+ fprintf(stderr, "error: cfree_obj_file_builder failed\n");
+ cfree_obj_free(obj);
free(in_data);
- return 2;
+ return 1;
}
- ObjBuilder* ob = read_elf((Compiler*)c, in_path, in_data, in_len);
-
CfreeWriter* w = NULL;
if (cfree_writer_mem(&g_heap, &w) != CFREE_OK || !w) {
fprintf(stderr, "error: cfree_writer_mem failed\n");
- obj_free(ob);
- cfree_compiler_free(c);
+ cfree_obj_free(obj);
free(in_data);
return 1;
}
- emit_elf((Compiler*)c, ob, w);
+ st = cfree_obj_builder_emit(ob, w);
+ if (st != CFREE_OK) {
+ fprintf(stderr, "error: cfree_obj_builder_emit failed\n");
+ cfree_writer_close(w);
+ cfree_obj_free(obj);
+ free(in_data);
+ return 1;
+ }
size_t out_len = 0;
const uint8_t* out_data = cfree_writer_mem_bytes(w, &out_len);
@@ -176,8 +175,7 @@ int main(int argc, char** argv) {
if (rc != 0) fprintf(stderr, "error: cannot write %s\n", out_path);
cfree_writer_close(w);
- obj_free(ob);
- cfree_compiler_free(c);
+ cfree_obj_free(obj);
free(in_data);
return rc == 0 ? 0 : 1;
}
diff --git a/test/link/harness/jit_runner.c b/test/link/harness/jit_runner.c
@@ -378,6 +378,19 @@ static void jit_tls_ctx_destroy(void* user, void* ctx_v) {
}
static CfreeJitTls g_jit_tls = {jit_tls_ctx_new, jit_tls_ctx_destroy, NULL};
+#if defined(__aarch64__) || defined(__arm64__)
+__attribute__((noinline, no_sanitize("address", "undefined")))
+static int call_with_aarch64_tls(int (*fn)(void), void* tls_block) {
+ void* old_tp;
+ int result;
+ __asm__ volatile("mrs %0, tpidr_el0" : "=r"(old_tp) :: "memory");
+ __asm__ volatile("msr tpidr_el0, %0" ::"r"(tls_block) : "memory");
+ result = fn();
+ __asm__ volatile("msr tpidr_el0, %0" ::"r"(old_tp) : "memory");
+ return result;
+}
+#endif
+
#if defined(__x86_64__) && defined(__linux__)
static long x64_arch_prctl_raw(long code, unsigned long addr) {
register long rax __asm__("rax") = 158; /* SYS_arch_prctl */
@@ -641,13 +654,10 @@ int main(int argc, char** argv) {
free(instance);
} else if (fn) {
#if defined(__aarch64__) || defined(__arm64__)
- /* msr + blr in immediate succession; the compiler must not
- * insert anything between. `volatile` and the "memory"
- * clobber bracket the load of fn so the call uses the
- * post-msr register state. */
- __asm__ volatile("msr tpidr_el0, %0" ::"r"(tls_block) : "memory");
-#endif
+ result = call_with_aarch64_tls(fn, tls_block);
+#else
result = fn();
+#endif
#if defined(__x86_64__) && defined(__linux__)
if (restore_fs) (void)x64_arch_prctl_raw(0x1002, old_fs);
#endif
diff --git a/test/parse/harness/parse_runner.c b/test/parse/harness/parse_runner.c
@@ -464,6 +464,17 @@ static int mode_emit_impl(const char* src_path, const char* out_path,
* lookups fail and the block stays zeroed — harmless. */
#if defined(__aarch64__) || defined(__arm64__)
static char g_tls_block[8192] __attribute__((aligned(16)));
+
+__attribute__((noinline, no_sanitize("address", "undefined")))
+static int call_with_aarch64_tls(int (*fn)(void), void* tls_block) {
+ void* old_tp;
+ int result;
+ __asm__ volatile("mrs %0, tpidr_el0" : "=r"(old_tp) :: "memory");
+ __asm__ volatile("msr tpidr_el0, %0" ::"r"(tls_block) : "memory");
+ result = fn();
+ __asm__ volatile("msr tpidr_el0, %0" ::"r"(old_tp) : "memory");
+ return result;
+}
#endif
static int mode_jit(const char* src_path) {
@@ -552,9 +563,10 @@ static int mode_jit(const char* src_path) {
if (fn) {
#if defined(__aarch64__) || defined(__arm64__)
- __asm__ volatile("msr tpidr_el0, %0" ::"r"(g_tls_block) : "memory");
-#endif
+ result = call_with_aarch64_tls(fn, g_tls_block);
+#else
result = fn();
+#endif
} else {
result = 1;
}
diff --git a/test/parse/run.sh b/test/parse/run.sh
@@ -83,7 +83,6 @@ fi
CLANG_TARGET="--target=$CLANG_TRIPLE"
CC="${CC:-cc}"
-HARNESS_CFLAGS="-std=c11 -Wall -Wextra -I$ROOT/include -I$ROOT/test"
ALLOW_SKIP="${CFREE_TEST_ALLOW_SKIP:-0}"
FILTER="${1:-${CFREE_TEST_FILTER:-}}"
@@ -268,9 +267,9 @@ EXEC_TARGET_MOUNT_ROOT="$BUILD_DIR"
# shellcheck source=../lib/exec_target.sh
source "$ROOT/test/lib/exec_target.sh"
-# ---- build harness binaries ------------------------------------------------
+# ---- harness binaries ------------------------------------------------------
-printf 'Building harness...\n'
+printf 'Checking harness...\n'
if [ ! -f "$LIB_AR" ]; then
printf ' FATAL: %s not found — run "make lib" first\n' "$LIB_AR" >&2
@@ -278,57 +277,40 @@ if [ ! -f "$LIB_AR" ]; then
fi
# parse-runner
-if $CC $HARNESS_CFLAGS \
- "$TEST_DIR/harness/parse_runner.c" \
- "$LIB_AR" -o "$PARSE_RUNNER" 2>"$BUILD_DIR/parse-runner.err"; then
- printf ' %s parse-runner\n' "$(color_grn built)"
+if [ -x "$PARSE_RUNNER" ]; then
+ printf ' %s parse-runner\n' "$(color_grn found)"
else
- printf ' %s parse-runner (see %s)\n' \
- "$(color_red FATAL)" "$BUILD_DIR/parse-runner.err" >&2
+ printf ' %s parse-runner missing — run "make test-parse"\n' \
+ "$(color_red FATAL)" >&2
exit 1
fi
# cfree-roundtrip — for path R.
-if [ ! -x "$ROUNDTRIP_BIN" ]; then
- if $CC -I"$ROOT/include" -I"$ROOT/src" "$ROOT/test/elf/cfree-roundtrip.c" "$LIB_AR" \
- -o "$ROUNDTRIP_BIN" 2>"$BUILD_DIR/cfree-roundtrip.err"; then
- have_roundtrip=1
- printf ' %s cfree-roundtrip\n' "$(color_grn built)"
- else
- printf ' %s cfree-roundtrip (see %s)\n' \
- "$(color_yel warn)" "$BUILD_DIR/cfree-roundtrip.err" >&2
- fi
-else
+if [ -x "$ROUNDTRIP_BIN" ]; then
have_roundtrip=1
+ printf ' %s cfree-roundtrip\n' "$(color_grn found)"
+else
+ printf ' %s cfree-roundtrip missing — path R will skip\n' \
+ "$(color_yel warn)" >&2
fi
# link-exe-runner — for path E.
-if [ ! -x "$LINK_EXE_RUNNER" ]; then
- if $CC -I"$ROOT/include" -I"$ROOT/test" "$LINK_TEST_DIR/harness/link_exe_runner.c" \
- "$LIB_AR" -o "$LINK_EXE_RUNNER" 2>"$BUILD_DIR/link-exe-runner.err"; then
- have_exe_runner=1
- printf ' %s link-exe-runner\n' "$(color_grn built)"
- else
- printf ' %s link-exe-runner (see %s)\n' \
- "$(color_yel warn)" "$BUILD_DIR/link-exe-runner.err" >&2
- fi
-else
+if [ -x "$LINK_EXE_RUNNER" ]; then
have_exe_runner=1
+ printf ' %s link-exe-runner\n' "$(color_grn found)"
+else
+ printf ' %s link-exe-runner missing — path E will skip\n' \
+ "$(color_yel warn)" >&2
fi
# jit-runner — for path J. Only when host arch matches the cross-target.
if [ $is_native_target -eq 1 ]; then
- if [ ! -x "$JIT_RUNNER" ]; then
- if $CC -I"$ROOT/include" -I"$ROOT/test" "$LINK_TEST_DIR/harness/jit_runner.c" \
- "$LIB_AR" -o "$JIT_RUNNER" 2>"$BUILD_DIR/jit-runner.err"; then
- have_jit_runner=1
- printf ' %s jit-runner\n' "$(color_grn built)"
- else
- printf ' %s jit-runner (see %s)\n' \
- "$(color_yel warn)" "$BUILD_DIR/jit-runner.err" >&2
- fi
- else
+ if [ -x "$JIT_RUNNER" ]; then
have_jit_runner=1
+ printf ' %s jit-runner\n' "$(color_grn found)"
+ else
+ printf ' %s jit-runner missing — path J will skip\n' \
+ "$(color_yel warn)" >&2
fi
fi
diff --git a/test/test.mk b/test/test.mk
@@ -269,10 +269,9 @@ JIT_RUNNER = build/test/jit-runner
PARSE_RUNNER = build/test/parse-runner
WASM_TOOL = build/test/wasm-tool
-# cfree-roundtrip needs `-Isrc` for the internal obj.h surface it inspects.
$(ROUNDTRIP_BIN): test/elf/cfree-roundtrip.c $(LIB_AR)
@mkdir -p $(dir $@)
- $(CC) $(HARNESS_CFLAGS) -Isrc test/elf/cfree-roundtrip.c $(LIB_AR) -o $@
+ $(CC) $(HARNESS_CFLAGS) test/elf/cfree-roundtrip.c $(LIB_AR) -o $@
# Mach-O peer of cfree-roundtrip — read_macho + emit_macho. Used by
# test-link's path R when CFREE_TEST_OBJ=macho.