From b2cdd8ee719961b2f037f20bf0f88fad94ca73b4 Mon Sep 17 00:00:00 2001 From: Gabriel Schneider Date: Mon, 6 Jul 2026 22:00:39 -0300 Subject: fix: every external-scanner grammar crashed pardes on parse (rust/cpp/python/...; e.g. opening agave cpi.rs or any tracy .cpp), Debug AND ReleaseSafe. Root cause: clang -fsanitize=function (in zig's default C UBSan set) traps at the runtime's indirect call of the scanner because grammars declare external_scanner_create() with EMPTY PARENS — a K&R non-prototype whose type hash differs from the void*(*)(void) pointer type. The ud1 trap lands on a bogus inlined line (stack.c:746), which cost the diagnosis a detour through rr (its gdbserver dies replaying past the task exit — core dump + coredumpctl worked; ud1 0x6(%eax) = SanitizerHandler kind 6 = function_type_mismatch; scanner-less c/zig grammars never crashed). Fix per review direction: -fno-sanitize=function on the grammar TUs in build.zig — uninstrumented callees make the runtime's call-site checks skip; the rest of UBSan stays live. Second half: fatal signals (SIGILL/SEGV/BUS/FPE) never run defers and bypassed the panic hook, leaving the terminal raw after a crash — root.debug.handleSegfault override now runs vaxis.recover() before std.debug.defaultHandleSegfault, verified in a raw pty (kill -ILL $PPID: rmcup + mouse resets precede the trace). Verified: rust/cpp/python opens work with real highlighting (snapstyle: keywords/strings/comments colored), agave cpi.js 2.7k-line open fine, suite 30/30, ReleaseSafe build opens rust identically. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.zig | 13 +++++++++++-- next-steps.txt | 1 + src/main.zig | 10 ++++++++++ 3 files changed, 22 insertions(+), 2 deletions(-) diff --git a/build.zig b/build.zig index beeba648..29188d48 100644 --- a/build.zig +++ b/build.zig @@ -163,8 +163,17 @@ pub fn build(b: *std.Build) void { .linkage = .static, }); if (libc_file) |f| lib.setLibCFile(f); - lib.root_module.addCSourceFile(.{ .file = dep.path(g.src ++ "/parser.c") }); - if (g.scanner) lib.root_module.addCSourceFile(.{ .file = dep.path(g.src ++ "/scanner.c") }); + // grammars declare external_scanner_create() with EMPTY PARENS + // (a K&R non-prototype, not (void)): under clang's + // -fsanitize=function the callee's type hash differs from the + // runtime's void*(*)(void) call through the pointer, so the + // first scanner call of ANY parse traps (function_type_mismatch, + // an ud1 blamed on a random inlined line). Uninstrumented + // callees make the runtime's call-site checks skip; the rest + // of UBSan stays live for the grammar code. + const ts_cflags = [_][]const u8{"-fno-sanitize=function"}; + lib.root_module.addCSourceFile(.{ .file = dep.path(g.src ++ "/parser.c"), .flags = &ts_cflags }); + if (g.scanner) lib.root_module.addCSourceFile(.{ .file = dep.path(g.src ++ "/scanner.c"), .flags = &ts_cflags }); lib.root_module.addIncludePath(dep.path(g.src)); root_mod.linkLibrary(lib); if (is_emscripten) wasm_libs.append(b.allocator, lib.getEmittedBin()) catch @panic("OOM"); diff --git a/next-steps.txt b/next-steps.txt index ef7b06a2..686f21b8 100644 --- a/next-steps.txt +++ b/next-steps.txt @@ -17,3 +17,4 @@ - initial layout: one pane, not three - focusing a file at a line also moves the modal cursor to that line (not just the scroll) - let's implement file searching, pardes philosophy is that what you do and the texts you read are sacred, so we don't want to implement file search with popups or information that will just dissapear. How I want you to do this is to: when I search something with / (in a text file, leave tty for later), we'll call grep -n on the current file with the search text. so far very simple, the neat thing is when I press n/N pardes will use the semantic tty access we have to select grep's result next line up or down and LOOK it (note we aren't introducing a new search concept or anything, just hooking modal editing, with semantic tty info from ghostty, and look semantics from acme). +- crash: illegal instruction in ts_parser_parse opening agave cpi.rs with full treesitter (0.26 zig bindings + 0.27 C runtime linked together); terminal left broken after non-panic crashes (SIGILL/SIGSEGV need recover in the signal path) diff --git a/src/main.zig b/src/main.zig index 56d04669..5275ea28 100644 --- a/src/main.zig +++ b/src/main.zig @@ -28,6 +28,16 @@ pub const panic = if (is_emscripten) std.debug.FullPanic(std.debug.defaultPanic) } }.call); +// Fatal signals (SIGSEGV/SIGILL/SIGBUS/SIGFPE) bypass the panic handler and +// no defer/errdefer ever runs — hook std.debug's segfault path the same way +// so the terminal is restored before the trace prints. +pub const debug = if (is_emscripten) struct {} else struct { + pub fn handleSegfault(addr: ?usize, name: []const u8, opt_ctx: anytype) noreturn { + @import("vaxis").recover(); + return std.debug.defaultHandleSegfault(addr, name, opt_ctx); + } +}; + const help_text = \\Usage: pardes [options] \\ -- cgit v1.3