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
This commit is contained in:
12
examples/1188-diagnostics-run-no-main.sx
Normal file
12
examples/1188-diagnostics-run-no-main.sx
Normal file
@@ -0,0 +1,12 @@
|
||||
// `sx run` on a program with no `main` must emit a clean diagnostic and exit
|
||||
// non-zero — never call into a garbage JIT address and segfault. A pre-JIT
|
||||
// entry-point check in main.zig (plus a defensive `main_addr == 0` backstop in
|
||||
// target.zig's runJITFromObject) replaces the old silent garbage-pointer call.
|
||||
//
|
||||
// Regression (issue 0137).
|
||||
#import "modules/std.sx";
|
||||
|
||||
// Intentionally no `main` — only a helper.
|
||||
greet :: () {
|
||||
print("unreachable\n");
|
||||
}
|
||||
1
examples/expected/1188-diagnostics-run-no-main.exit
Normal file
1
examples/expected/1188-diagnostics-run-no-main.exit
Normal file
@@ -0,0 +1 @@
|
||||
1
|
||||
1
examples/expected/1188-diagnostics-run-no-main.stderr
Normal file
1
examples/expected/1188-diagnostics-run-no-main.stderr
Normal file
@@ -0,0 +1 @@
|
||||
error: no 'main' function found — 'sx run' requires a top-level 'main' entry point
|
||||
1
examples/expected/1188-diagnostics-run-no-main.stdout
Normal file
1
examples/expected/1188-diagnostics-run-no-main.stdout
Normal file
@@ -0,0 +1 @@
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
# 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**
|
||||
|
||||
39
src/main.zig
39
src/main.zig
@@ -240,6 +240,18 @@ pub fn main(init: std.process.Init) !void {
|
||||
// Cache check — use .o files (precompiled object, skip IR compilation in JIT)
|
||||
// Disable caching for files with top-level #run (side effects lost on cache hit)
|
||||
const root = comp.resolved_root orelse comp.root orelse return;
|
||||
|
||||
// Pre-JIT entry-point check. The ORC `main` lookup in
|
||||
// runJITFromObject does NOT reliably report "no main" — it has been
|
||||
// observed reporting success while leaving the address at garbage
|
||||
// (0x0 or a small non-zero value), which then gets called and
|
||||
// segfaults (issue 0137). Reject programs with no `main` here, before
|
||||
// any codegen/JIT, with a clean diagnostic + non-zero exit.
|
||||
if (!hasMainEntry(root)) {
|
||||
std.debug.print("error: no 'main' function found — 'sx run' requires a top-level 'main' entry point\n", .{});
|
||||
std.process.exit(1);
|
||||
}
|
||||
|
||||
const use_cache = enable_cache and !hasTopLevelRun(root);
|
||||
const key = computeCacheKey(source, &comp.import_sources, target_config);
|
||||
const cache_obj = cachePath(allocator, key, "o") catch std.process.exit(1);
|
||||
@@ -914,6 +926,33 @@ fn hasTopLevelRun(root: *const sx.ast.Node) bool {
|
||||
return false;
|
||||
}
|
||||
|
||||
/// Does the program declare a `main` entry point? The JIT (and an AOT
|
||||
/// binary) entry symbol is a flat function named `main`; this scans the
|
||||
/// resolved AST for a `fn_decl` named "main", recursing into namespace
|
||||
/// decls so a `main` brought in behind an aliased import is still found.
|
||||
/// Used as a pre-JIT guard (issue 0137): the ORC `main` lookup does not
|
||||
/// reliably surface "no main", so we reject the no-main program here with
|
||||
/// a clean diagnostic instead of calling a garbage function pointer.
|
||||
fn hasMainEntry(root: *const sx.ast.Node) bool {
|
||||
const walker = struct {
|
||||
fn walk(decls: []const *sx.ast.Node) bool {
|
||||
for (decls) |d| {
|
||||
switch (d.data) {
|
||||
.fn_decl => |fd| {
|
||||
if (std.mem.eql(u8, fd.name, "main")) return true;
|
||||
},
|
||||
.namespace_decl => |ns| {
|
||||
if (walk(ns.decls)) return true;
|
||||
},
|
||||
else => {},
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
};
|
||||
return walker.walk(root.data.root.decls);
|
||||
}
|
||||
|
||||
fn extractLibraries(allocator: std.mem.Allocator, root: *const sx.ast.Node) ![]const []const u8 {
|
||||
var libs = std.ArrayList([]const u8).empty;
|
||||
var seen = std.StringHashMap(void).init(allocator);
|
||||
|
||||
@@ -383,6 +383,17 @@ pub fn runJITFromObject(obj_buf: c.LLVMMemoryBufferRef, priority_dylibs: []const
|
||||
return error.CompileError;
|
||||
}
|
||||
|
||||
// Defensive backstop (issue 0137): ORC has been observed reporting
|
||||
// success from the `main` lookup while leaving `main_addr` at 0 — calling
|
||||
// @ptrFromInt(0) then segfaults. The real fix is the pre-JIT entry-point
|
||||
// check in main.zig (which also catches the observed NON-zero garbage
|
||||
// address case); this guard is a last line of defense so a null entry can
|
||||
// never be called regardless of how we got here.
|
||||
if (main_addr == 0) {
|
||||
std.debug.print("error: no 'main' function found in JIT module\n", .{});
|
||||
return error.CompileError;
|
||||
}
|
||||
|
||||
// Cast to function pointer and call. The exit code is main's integer
|
||||
// return truncated to u8 — matching the OS truncation an AOT binary's
|
||||
// exit status already gets, so JIT and AOT agree (e.g. 1105 -> 81,
|
||||
|
||||
Reference in New Issue
Block a user