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:
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