Files
sx/issues/0137-jit-run-no-main-segfault.md
agra 6ed29621ad fix: diagnose missing 'main' instead of segfaulting on 'sx run' (issue 0137)
A program with no 'main' reached the JIT entry-point call with a garbage
address (ORC reports lookup success but leaves main_addr degenerate), then
called it -> SIGSEGV. Add a pre-JIT entry-point check in main.zig that emits
'error: no main function found' and exits non-zero before codegen, plus a
defensive main_addr==0 guard in target.zig runJITFromObject as a backstop.

Regression: examples/1188-diagnostics-run-no-main.sx
2026-06-21 09:10:30 +03:00

77 lines
3.5 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 0137 — `sx run` on a program with no `main` segfaults (JIT entry lookup unguarded)
> **RESOLVED.** A pre-JIT entry-point check in `main.zig` now emits a clean
> `error: no 'main' function found …` diagnostic and exits non-zero before any
> codegen/JIT, so a no-main program never reaches the garbage-pointer call. A
> defensive `main_addr == 0` guard in `target.zig`'s `runJITFromObject` (ORC
> reports lookup success but leaves the address degenerate) remains as a
> backstop. Regression test: `examples/1188-diagnostics-run-no-main.sx`.
## Symptom
`sx run <file>` on a program that defines no `main` function **crashes**
(SIGSEGV/abort, "Segmentation fault at address 0x60") instead of emitting a clean
diagnostic like `error: no 'main' function found`.
- **Observed:** process crash, exit 134 (abort) / 139 (SIGSEGV); no diagnostic.
- **Expected:** a normal compile-style error ("no `main` entry point") and a
clean non-zero exit, the same way any other missing-entry condition reports.
Independent of inline assembly — surfaced while writing an ASM-stream probe that
omitted `main`, but reproduces with an ordinary, asm-free program (see below).
## Reproduction
A file with only an (uncalled) function and no `main`:
```sx
foo :: (n: u64) -> u64 { return n + 1; }
```
```sh
sx run that.sx
# => "Segmentation fault at address 0x60", exit 134
# expected: "error: no 'main' function found" (or similar), clean non-zero exit
```
## Root cause (suspected)
`src/target.zig` JIT-run path, ~lines 256273. After the ORC lookup:
```zig
var main_addr: c.LLVMOrcExecutorAddress = 0;
err = c.LLVMOrcLLJITLookup(jit, &main_addr, "main");
if (err != null) { /* prints "JIT lookup error" and returns error.CompileError */ }
// no guard for main_addr == 0 here:
const main_fn: *const fn () callconv(.c) i32 = @ptrFromInt(main_addr);
const result = main_fn(); // <- calls a null/garbage pointer when no main
```
When the module has no `main` symbol, the lookup leaves `main_addr` at `0` (or
ORC returns a degenerate success), so `@ptrFromInt(main_addr)` + `main_fn()`
calls into null → the crash. There is no `main_addr == 0` check.
## Investigation prompt (paste into a fresh session)
> `sx run` on a program with no `main` segfaults instead of diagnosing. The JIT
> run path in `src/target.zig` (~lines 256273) looks up `"main"` via
> `LLVMOrcLLJITLookup`, then unconditionally casts `main_addr` to a function
> pointer and calls it. When the program defines no `main`, `main_addr` is `0`
> (or the lookup degenerately "succeeds"), so the call dereferences null and
> crashes.
>
> Fix: after the lookup's `err` check, add `if (main_addr == 0) { … }` that emits
> a clean user-facing error ("no `main` function found" / "program has no entry
> point") and returns `error.CompileError` (matching the existing
> `JIT lookup error` style), BEFORE the `@ptrFromInt` + call. Consider whether a
> pre-JIT check (the module/program already knows whether a `main` decl exists —
> e.g. emit_llvm.zig:631 already null-checks `LLVMGetNamedFunction(.., "main")`)
> is the better choke point so the diagnostic carries a source span rather than a
> bare message. Either is acceptable; the hard requirement is *no crash*.
>
> Verification: `printf 'foo :: (n: u64) -> u64 { return n + 1; }\n' > /tmp/x.sx
> && sx run /tmp/x.sx` — expect a clean error message + non-zero exit, NOT a
> segfault. Add a pinned repro under `issues/` (or an `examples/11xx-diagnostics-*`
> once the message is settled) asserting the diagnostic on stderr + the exit code.