From fa34944bda3644b626945cf6bbeb70806af8bee2 Mon Sep 17 00:00:00 2001 From: dave caruso Date: Wed, 10 Apr 2024 22:10:47 -0700 Subject: [PATCH 01/26] feat: tracestrings on Windows --- CMakeLists.txt | 4 +- src/analytics/analytics_thread.zig | 13 +- src/bun.js/api/BunObject.zig | 38 ++- src/bun.js/bindings/wtf-bindings.cpp | 32 --- src/bun.zig | 3 + src/cli.zig | 2 + src/cli/install.ps1 | 2 +- src/deps/backtrace.zig | 0 src/deps/crash_reporter_linux.zig | 12 - src/main.zig | 6 +- src/report.zig | 41 +--- src/sourcemap/sourcemap.zig | 4 +- src/trace_string.zig | 342 +++++++++++++++++++++++++++ src/windows.zig | 37 +++ src/zlib.zig | 2 +- 15 files changed, 446 insertions(+), 92 deletions(-) delete mode 100644 src/deps/backtrace.zig delete mode 100644 src/deps/crash_reporter_linux.zig create mode 100644 src/trace_string.zig diff --git a/CMakeLists.txt b/CMakeLists.txt index eaf7b30e710cb9..d28f75c56737c1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1060,8 +1060,8 @@ elseif(CMAKE_BUILD_TYPE STREQUAL "Release") list(APPEND LTO_LINK_FLAG "/LTCG") endif() - target_compile_options(${bun} PUBLIC /O2 ${LTO_FLAG} /DEBUG /Z7) - target_link_options(${bun} PUBLIC ${LTO_LINK_FLAG} /DEBUG) + target_compile_options(${bun} PUBLIC /O2 ${LTO_FLAG} /DEBUG:FULL) + target_link_options(${bun} PUBLIC ${LTO_LINK_FLAG} /DEBUG:FULL) endif() endif() diff --git a/src/analytics/analytics_thread.zig b/src/analytics/analytics_thread.zig index c4ecd60897d318..be752f7b0ad074 100644 --- a/src/analytics/analytics_thread.zig +++ b/src/analytics/analytics_thread.zig @@ -72,6 +72,7 @@ pub const Features = struct { pub fn formatter() Formatter { return Formatter{}; } + pub const Formatter = struct { pub fn format(_: Formatter, comptime _: []const u8, _: std.fmt.FormatOptions, writer: anytype) !void { const fields = comptime brk: { @@ -91,9 +92,14 @@ pub const Features = struct { break :brk buffer[0..count]; }; + var is_first_feature = true; inline for (fields) |field| { const count = @field(Features, field); if (count > 0) { + if (is_first_feature) { + try writer.writeAll("Features: "); + is_first_feature = false; + } try writer.writeAll(field); if (count > 1) { try writer.print("({d}) ", .{count}); @@ -102,10 +108,13 @@ pub const Features = struct { } } } + if (!is_first_feature) { + try writer.writeAll("\n"); + } var builtins = builtin_modules.iterator(); if (builtins.next()) |first| { - try writer.writeAll("\nBuiltins: \""); + try writer.writeAll("Builtins: \""); try writer.writeAll(@tagName(first)); try writer.writeAll("\" "); @@ -115,8 +124,6 @@ pub const Features = struct { try writer.writeAll("\" "); } - try writer.writeAll("\n"); - } else { try writer.writeAll("\n"); } } diff --git a/src/bun.js/api/BunObject.zig b/src/bun.js/api/BunObject.zig index 5bf82aeecb0ef9..a18a707bed01eb 100644 --- a/src/bun.js/api/BunObject.zig +++ b/src/bun.js/api/BunObject.zig @@ -3325,7 +3325,11 @@ const UnsafeObject = struct { const object = JSValue.createEmptyObject(globalThis, 3); const fields = comptime .{ .gcAggressionLevel = &gcAggressionLevel, - .segfault = &__debug__doSegfault, + .crashBySegfault = &crashBySegfault, + .crashByPanic = &crashByPanic, + .crashByUnreachable = &crashByUnreachable, + .crashBySafetyCheck = &crashBySafetyCheck, + .crashByCallGlobalError = &crashByCallGlobalError, .arrayBufferToString = &arrayBufferToString, .mimallocDump = &dump_mimalloc, }; @@ -3357,11 +3361,33 @@ const UnsafeObject = struct { return ret; } - // For testing the segfault handler - pub fn __debug__doSegfault( - _: *JSC.JSGlobalObject, - _: *JSC.CallFrame, - ) callconv(.C) JSC.JSValue { + pub fn crashBySegfault(_: *JSC.JSGlobalObject, _: *JSC.CallFrame) callconv(.C) JSC.JSValue { + @setRuntimeSafety(false); + const ptr: [*]align(1) u64 = @ptrFromInt(0xDEADBEEF); + ptr[0] = 0xDEADBEEF; + std.mem.doNotOptimizeAway(&ptr); + return .undefined; + } + + pub fn crashByPanic(_: *JSC.JSGlobalObject, _: *JSC.CallFrame) callconv(.C) JSC.JSValue { + std.debug.panic("invoked crashByPanic() handler", .{}); + } + + pub fn crashByUnreachable(_: *JSC.JSGlobalObject, _: *JSC.CallFrame) callconv(.C) JSC.JSValue { + @setRuntimeSafety(true); + unreachable; + } + + pub fn crashBySafetyCheck(_: *JSC.JSGlobalObject, _: *JSC.CallFrame) callconv(.C) JSC.JSValue { + @setRuntimeSafety(true); + const Y = enum(u8) { a }; + var a: u8 = 2; + const x: Y = @enumFromInt((&a).*); + _ = x; + return .undefined; + } + + pub fn crashByCallGlobalError(_: *JSC.JSGlobalObject, _: *JSC.CallFrame) callconv(.C) JSC.JSValue { const Reporter = @import("../../report.zig"); Reporter.globalError(error.SegfaultTest, null); } diff --git a/src/bun.js/bindings/wtf-bindings.cpp b/src/bun.js/bindings/wtf-bindings.cpp index cf049c709fbd93..712179eda71a14 100644 --- a/src/bun.js/bindings/wtf-bindings.cpp +++ b/src/bun.js/bindings/wtf-bindings.cpp @@ -178,38 +178,6 @@ extern "C" void WTF__copyLCharsFromUCharSource(LChar* destination, const UChar* WTF::StringImpl::copyCharacters(destination, source, length); } -extern "C" void Bun__crashReportWrite(void* ctx, const char* message, size_t length); -extern "C" void Bun__crashReportDumpStackTrace(void* ctx) -{ - static constexpr int framesToShow = 32; - static constexpr int framesToSkip = 2; - void* stack[framesToShow + framesToSkip]; - int frames = framesToShow + framesToSkip; - WTFGetBacktrace(stack, &frames); - int size = frames - framesToSkip; - bool isFirst = true; - for (int frameNumber = 0; frameNumber < size; ++frameNumber) { - auto demangled = WTF::StackTraceSymbolResolver::demangle(stack[frameNumber]); - - StringPrintStream out; - if (isFirst) { - isFirst = false; - if (demangled) - out.printf("\n%-3d %p %s", frameNumber, stack[frameNumber], demangled->demangledName() ? demangled->demangledName() : demangled->mangledName()); - else - out.printf("\n%-3d %p", frameNumber, stack[frameNumber]); - } else { - if (demangled) - out.printf("%-3d ??? %s", frameNumber, demangled->demangledName() ? demangled->demangledName() : demangled->mangledName()); - else - out.printf("%-3d ???", frameNumber); - } - - auto str = out.toCString(); - Bun__crashReportWrite(ctx, str.data(), str.length()); - } -} - // For whatever reason // Doing this in C++/C is 2x faster than doing it in Zig. // However, it's still slower than it should be. diff --git a/src/bun.zig b/src/bun.zig index 67a1c53eb65632..1e2d199a9c7ccc 100644 --- a/src/bun.zig +++ b/src/bun.zig @@ -697,6 +697,7 @@ pub const BoringSSL = @import("./boringssl.zig"); pub const LOLHTML = @import("./deps/lol-html.zig"); pub const clap = @import("./deps/zig-clap/clap.zig"); pub const analytics = @import("./analytics.zig"); +pub const zlib = @import("./zlib.zig"); pub var start_time: i128 = 0; @@ -3044,3 +3045,5 @@ pub fn SliceIterator(comptime T: type) type { } pub const Futex = @import("./futex.zig"); + +pub const crash_report = @import("panic_v2.zig"); diff --git a/src/cli.zig b/src/cli.zig index c62ea916d6a84e..18c3daffafc8b3 100644 --- a/src/cli.zig +++ b/src/cli.zig @@ -49,6 +49,7 @@ pub const Cli = struct { pub var log_: logger.Log = undefined; pub fn startTransform(_: std.mem.Allocator, _: Api.TransformOptions, _: *logger.Log) anyerror!void {} pub fn start(allocator: std.mem.Allocator, comptime MainPanicHandler: type) void { + is_main_thread = true; start_time = std.time.nanoTimestamp(); log_ = logger.Log.init(allocator); @@ -68,6 +69,7 @@ pub const Cli = struct { } pub var cmd: ?Command.Tag = null; + pub threadlocal var is_main_thread: bool = false; }; const LoaderMatcher = strings.ExactSizeMatcher(4); diff --git a/src/cli/install.ps1 b/src/cli/install.ps1 index 10530be324b550..44a06dcd9ebde0 100644 --- a/src/cli/install.ps1 +++ b/src/cli/install.ps1 @@ -277,7 +277,7 @@ function Install-Bun { New-ItemProperty -Path $RegistryKey -Name "DisplayName" -Value "Bun" -PropertyType String -Force | Out-Null New-ItemProperty -Path $RegistryKey -Name "InstallLocation" -Value "${BunRoot}" -PropertyType String -Force | Out-Null New-ItemProperty -Path $RegistryKey -Name "DisplayIcon" -Value $BunBin\bun.exe -PropertyType String -Force | Out-Null - New-ItemProperty -Path $RegistryKey -Name "UninstallString" -Value "powershell -c `"& `'$BunRoot\uninstall.ps1`' -PauseOnError`"" -PropertyType String -Force | Out-Null + New-ItemProperty -Path $RegistryKey -Name "UninstallString" -Value "powershell -c `"& `'$BunRoot\uninstall.ps1`' -PauseOnError`" -ExecutionPolicy Bypass" -PropertyType String -Force | Out-Null } catch { if ($rootKey -ne $null) { Remove-Item -Path $RegistryKey -Force diff --git a/src/deps/backtrace.zig b/src/deps/backtrace.zig deleted file mode 100644 index e69de29bb2d1d6..00000000000000 diff --git a/src/deps/crash_reporter_linux.zig b/src/deps/crash_reporter_linux.zig deleted file mode 100644 index 62dd31616d3bb2..00000000000000 --- a/src/deps/crash_reporter_linux.zig +++ /dev/null @@ -1,12 +0,0 @@ -const std = @import("std"); - -pub fn start(_: anytype) bool { - std.debug.attachSegfaultHandler(); - return true; -} - -pub fn generate() void {} - -pub fn crashReportPath(_: *[1024]u8) []const u8 { - return ""; -} diff --git a/src/main.zig b/src/main.zig index c2b6093580c64b..c259a1d55ff943 100644 --- a/src/main.zig +++ b/src/main.zig @@ -9,16 +9,14 @@ const Environment = bun.Environment; const panic_handler = @import("./panic_handler.zig"); const MainPanicHandler = panic_handler.NewPanicHandler(std.builtin.default_panic); +pub const panic = @import("panic_v2.zig").panic; + pub const io_mode = .blocking; comptime { std.debug.assert(builtin.target.cpu.arch.endian() == .little); } -pub fn panic(msg: []const u8, trace: ?*std.builtin.StackTrace, addr: ?usize) noreturn { - MainPanicHandler.handle_panic(msg, trace, addr); -} - const CrashReporter = @import("./crash_reporter.zig"); extern fn bun_warn_avx_missing(url: [*:0]const u8) void; pub extern "C" var _environ: ?*anyopaque; diff --git a/src/report.zig b/src/report.zig index 70e80702da7eda..357dba783c7591 100644 --- a/src/report.zig +++ b/src/report.zig @@ -239,9 +239,14 @@ pub fn fatal(err_: ?anyerror, msg_: ?string) void { crash_report_writer.flush(); + var addrs: [32]usize = undefined; + var trace = std.builtin.StackTrace{ + .index = 0, + .instruction_addresses = &addrs, + }; + std.debug.captureStackTrace(@returnAddress(), &trace); + // It only is a real crash report if it's not coming from Zig - std.mem.doNotOptimizeAway(&Bun__crashReportWrite); - Bun__crashReportDumpStackTrace(&crash_report_writer); crash_report_writer.flush(); @@ -250,7 +255,6 @@ pub fn fatal(err_: ?anyerror, msg_: ?string) void { if (!had_printed_fatal) { if (Environment.isWindows) { - // TODO(@paperdave) change this to the original one once bun windows is stable crash_report_writer.print("\nSearch GitHub issues https://bun.sh/issues or join in #windows channel in https://bun.sh/discord\n\n", .{}); } else { crash_report_writer.print("\nSearch GitHub issues https://bun.sh/issues or ask for #help in https://bun.sh/discord\n\n", .{}); @@ -262,25 +266,6 @@ pub fn fatal(err_: ?anyerror, msg_: ?string) void { var globalError_ranOnce = false; var error_return_trace: ?*std.builtin.StackTrace = null; -export fn Bun__crashReportWrite(ctx: *CrashReportWriter, bytes_ptr: [*]const u8, len: usize) void { - if (!Environment.isWindows) { - if (error_return_trace) |trace| { - if (len > 0) { - ctx.print("{s}\n{}", .{ bytes_ptr[0..len], trace }); - } else { - ctx.print("{}\n", .{trace}); - } - return; - } - } - - if (len > 0) { - ctx.print("{s}\n", .{bytes_ptr[0..len]}); - } -} - -extern "C" fn Bun__crashReportDumpStackTrace(ctx: *anyopaque) void; - pub noinline fn handleCrash(signal: i32, addr: usize) void { const had_printed_fatal = has_printed_fatal; if (has_printed_fatal) return; @@ -307,12 +292,12 @@ pub noinline fn handleCrash(signal: i32, addr: usize) void { error_return_trace = @errorReturnTrace(); } - if (!Environment.isWindows) { - if (comptime !@import("root").bun.JSC.is_bindgen) { - std.mem.doNotOptimizeAway(&Bun__crashReportWrite); - Bun__crashReportDumpStackTrace(&crash_report_writer); - } - } + // if (!Environment.isWindows) { + // if (comptime !@import("root").bun.JSC.is_bindgen) { + // std.mem.doNotOptimizeAway(&Bun__crashReportWrite); + // Bun__crashReportDumpStackTrace(&crash_report_writer); + // } + // } if (!had_printed_fatal) { crash_report_writer.print("\nAsk for #help in https://bun.sh/discord or go to https://bun.sh/issues\n\n", .{}); diff --git a/src/sourcemap/sourcemap.zig b/src/sourcemap/sourcemap.zig index d366e8af905739..e2d086c635c327 100644 --- a/src/sourcemap/sourcemap.zig +++ b/src/sourcemap/sourcemap.zig @@ -736,9 +736,7 @@ test "decodeVLQ" { // V V // 101011 // -pub fn encodeVLQ( - value: i32, -) VLQ { +pub fn encodeVLQ(value: i32) VLQ { var len: u4 = 0; var bytes: [vlq_max_in_bytes]u8 = undefined; diff --git a/src/trace_string.zig b/src/trace_string.zig new file mode 100644 index 00000000000000..425f7fa87ca856 --- /dev/null +++ b/src/trace_string.zig @@ -0,0 +1,342 @@ +//! TODO: document this +//! +//! This file is heavily inspired off of std.debug.panicImpl, but instead of +//! dumping addresses in release mode, it finds the offsets of these functions +//! and encodes them into a short string that can be compactly shared to the +//! GitHub issue tracker. This allows the binary to not contain any debug +//! symbols (makes it a smaller download), makes traces more concise in the +//! console, and still helps us debug crashes easily. +//! +//! Trace strings can be remapped using a remote service, which has a copy +//! of every version since this new panic handler was implemented (v1.1.4). +//! +//! TODO: instructions to fetch that +//! +const std = @import("std"); +const builtin = @import("builtin"); + +const mimalloc = @import("allocators/mimalloc.zig"); +const bun = @import("root").bun; + +const SourceMap = @import("./sourcemap/sourcemap.zig"); + +const report_base_url = "https://bun.report/"; + +var has_printed_message = false; + +/// Non-zero whenever the program triggered a panic. +/// The counter is incremented/decremented atomically. +var panicking = std.atomic.Value(u8).init(0); + +// Locked to avoid interleaving panic messages from multiple threads. +var panic_mutex = std.Thread.Mutex{}; + +/// Counts how many times the panic handler is invoked by this thread. +/// This is used to catch and handle panics triggered by the panic handler. +threadlocal var panic_stage: usize = 0; + +pub fn panic(msg: []const u8, maybe_trace: ?*std.builtin.StackTrace, begin_addr: ?usize) noreturn { + @setCold(true); + + nosuspend switch (panic_stage) { + 0 => { + panic_stage = 1; + _ = panicking.fetchAdd(1, .SeqCst); + + { + panic_mutex.lock(); + defer panic_mutex.unlock(); + + bun.Output.flush(); + + const writer = bun.Output.errorWriter(); + + if (!has_printed_message) { + has_printed_message = true; + writer.writeAll("=" ** 60 ++ "\n") catch std.os.abort(); + bun.Output.err("oh no", + \\Bun has crashed. This indicates a bug in Bun, and + \\should be reported as a GitHub issue. + \\ + \\ + , .{}); + bun.Output.flush(); + printMetadata(writer) catch std.os.abort(); + } + + if (bun.Output.enable_ansi_colors) { + writer.writeAll(bun.Output.prettyFmt("", true)) catch std.os.abort(); + } + + writer.writeAll("panic") catch std.os.abort(); + switch (bun.Environment.os) { + .windows => { + var name: std.os.windows.PWSTR = undefined; + const result = bun.windows.GetThreadDescription(std.os.windows.kernel32.GetCurrentThread(), &name); + if (std.os.windows.HRESULT_CODE(result) == .SUCCESS and name[0] != 0) { + writer.print("({})", .{bun.fmt.utf16(bun.span(name))}) catch std.os.abort(); + } else { + writer.print("(thread {d})", .{std.os.windows.kernel32.GetCurrentThreadId()}) catch std.os.abort(); + } + }, + else => @compileError("TODO"), + } + + writer.writeAll(": ") catch std.os.abort(); + writer.writeAll(msg) catch std.os.abort(); + + if (bun.Output.enable_ansi_colors) { + writer.writeAll(bun.Output.prettyFmt("\n", true)) catch std.os.abort(); + } else { + writer.writeAll("\n") catch std.os.abort(); + } + + var addr_buf: [32]usize = undefined; + var trace_buf: std.builtin.StackTrace = undefined; + + // If a trace was not provided, compute one now + const trace = (maybe_trace orelse compute_now: { + trace_buf = std.builtin.StackTrace{ + .index = 0, + .instruction_addresses = &addr_buf, + }; + std.debug.captureStackTrace(begin_addr orelse @returnAddress(), &trace_buf); + break :compute_now &trace_buf; + }); + + writer.writeAll("Please report this panic as a GitHub issue using this link:\n") catch std.os.abort(); + if (bun.Output.enable_ansi_colors) { + writer.print(bun.Output.prettyFmt("", true), .{}) catch std.os.abort(); + } + + encode(trace, msg, writer) catch std.os.abort(); + + if (bun.Output.enable_ansi_colors) { + writer.writeAll(bun.Output.prettyFmt("\n", true)) catch std.os.abort(); + } else { + writer.writeAll("\n") catch std.os.abort(); + } + + bun.Output.flush(); + } + + waitForOtherThreadToFinishPanicking(); + }, + 1 => { + // A panic happened while trying to print a previous panic message, + // we're still holding the mutex but that's fine as we're going to + // call abort() + const stderr = std.io.getStdErr().writer(); + stderr.print("panic: {s}\n", .{msg}) catch std.os.abort(); + stderr.print("panicked during a panic. Aborting.\n", .{}) catch std.os.abort(); + }, + else => { + // Panicked while printing "Panicked during a panic." + }, + }; + + switch (bun.Environment.os) { + .windows => { + std.os.abort(); + }, + else => { + // Cause a segfault to make sure a core dump is generated if such is enabled + const act = std.os.Sigaction{ + .handler = .{ .sigaction = @ptrCast(@alignCast(std.os.SIG.DFL)) }, + .mask = std.os.empty_sigset, + .flags = 0, + }; + std.os.sigaction(std.os.SIG.SEGV, &act, null) catch std.os.abort(); + }, + } +} + +const arch_display_string = if (bun.Environment.isAarch64) + if (bun.Environment.isMac) "Silicon" else "arm64" +else + "x64"; + +const metadata_version_line = std.fmt.comptimePrint( + "Bun v{s} {s} {s}{s}\n", + .{ + bun.Global.package_json_version_with_sha, + bun.Environment.os.displayString(), + arch_display_string, + if (bun.Environment.baseline) " (baseline)" else "", + }, +); + +pub fn printMetadata(writer: anytype) !void { + try writer.writeAll(metadata_version_line); + try writer.print("Cmd: {s}\n", .{if (bun.CLI.Cli.cmd) |cmd| @tagName(cmd) else "unknown"}); + try writer.print("{}", .{bun.Analytics.Features.formatter()}); + + if (bun.use_mimalloc) { + var elapsed_msecs: usize = 0; + var user_msecs: usize = 0; + var system_msecs: usize = 0; + var current_rss: usize = 0; + var peak_rss: usize = 0; + var current_commit: usize = 0; + var peak_commit: usize = 0; + var page_faults: usize = 0; + mimalloc.mi_process_info( + &elapsed_msecs, + &user_msecs, + &system_msecs, + ¤t_rss, + &peak_rss, + ¤t_commit, + &peak_commit, + &page_faults, + ); + try writer.print("Elapsed: {d}ms | User: {d}ms | Sys: {d}ms\nRSS: {:<3.2} | Peak: {:<3.2} | Commit: {:<3.2} | Faults: {d}\n", .{ + elapsed_msecs, + user_msecs, + system_msecs, + std.fmt.fmtIntSizeDec(current_rss), + std.fmt.fmtIntSizeDec(peak_rss), + std.fmt.fmtIntSizeDec(current_commit), + page_faults, + }); + } + + try writer.writeAll("\n"); +} + +fn waitForOtherThreadToFinishPanicking() void { + if (panicking.fetchSub(1, .SeqCst) != 1) { + // Another thread is panicking, wait for the last one to finish + // and call abort() + if (builtin.single_threaded) unreachable; + + // Sleep forever without hammering the CPU + var futex = std.atomic.Value(u32).init(0); + while (true) std.Thread.Futex.wait(&futex, 0); + comptime unreachable; + } +} + +/// Each platform is encoded is a single character. It is placed right after the +/// slash after the version, so someone just reading the trace string can tell +/// what platform it came from. L, M, and W are for Linux, macOS, and Windows, +/// with capital letters indicating aarch64, lowercase indicating x86_64. +/// +/// eg: 'https://bun.report/1.1.3/we04c... +// ^ this tells you it is windows x86_64 +/// +/// Baseline gets a weirder encoding of a mix of b and e. +const Platform = enum(u8) { + linux_x86_64 = 'l', + linux_x86_64_baseline = 'B', + linux_aarch64 = 'L', + + macos_x86_64_baseline = 'b', + macos_x86_64 = 'm', + macos_aarch64 = 'M', + + windows_x86_64 = 'w', + windows_x86_64_baseline = 'e', + + const current = @field(Platform, @tagName(bun.Environment.os) ++ + "_" ++ @tagName(builtin.target.cpu.arch)) ++ + (if (bun.Environment.isBaseline) "_baseline" else ""); +}; + +const header = std.fmt.comptimePrint( + "{s}/{c}{s}1", + .{ + bun.Environment.version_string, + @intFromEnum(Platform.current), + if (bun.Environment.git_sha.len > 0) bun.Environment.git_sha[0..7] else "unknown", + }, +); + +const EncodeOptions = struct { + trace: *std.builtin.StackTrace, + msg: ?[]const u8, + include_features: bool, + action: Action, + + const Action = enum { + /// Open a pre-filled GitHub issue with the expanded trace + open_issue, + /// View the trace with nothing else + view_trace, + }; +}; + +fn encode(opts: EncodeOptions, writer: anytype) !void { + try writer.writeAll(report_base_url ++ header); + + const image_path = bun.windows.exePathW(); + + for (opts.trace.instruction_addresses[0..opts.trace.index]) |addr| { + const module = bun.windows.getModuleHandleFromAddress(addr) orelse { + try writer.writeAll("_"); + continue; + }; + const base_address = @intFromPtr(module); + var name_bytes: [512]u16 = undefined; + var name_bytes_utf8: [1024]u8 = undefined; + const name = bun.windows.getModuleNameW(module, &name_bytes) orelse { + try writer.writeAll("_"); + continue; + }; + if (!std.mem.eql(u16, name, image_path)) { + try writer.writeAll(comptime one_vlq: { + const vlq = SourceMap.encodeVLQ(1); + break :one_vlq vlq.bytes[0..vlq.len]; + }); + + const utf8_name = try bun.strings.convertUTF16toUTF8InBuffer(&name_bytes_utf8, name); + const vlq_name = SourceMap.encodeVLQ(@intCast(utf8_name.len)); + try writer.writeAll(vlq_name.bytes[0..vlq_name.len]); + try writer.writeAll(utf8_name); + } + + const offset = addr - base_address; + const vlq = SourceMap.encodeVLQ(@intCast(offset)); + try writer.writeAll(vlq.bytes[0..vlq.len]); + } + + try writer.writeAll(comptime zero_vlq: { + const vlq = SourceMap.encodeVLQ(0); + break :zero_vlq vlq.bytes[0..vlq.len]; + }); + + if (opts.msg) |message| { + var compressed_bytes: [2048]u8 = undefined; + var len: usize = compressed_bytes.len; + const ret: bun.zlib.ReturnCode = @enumFromInt(bun.zlib.compress2(&compressed_bytes, &len, message.ptr, message.len, 9)); + const compressed = switch (ret) { + .Ok => compressed_bytes[0..len], + // Insufficient memory. + .MemError => return error.OutOfMemory, + // The buffer dest was not large enough to hold the compressed data. + .BufError => return error.NoSpaceLeft, + + // The level was not Z_DEFAULT_LEVEL, or was not between 0 and 9. + // This is technically possible but impossible because we pass 9. + .StreamError => return error.Unexpected, + else => return error.Unexpected, + }; + + var b64_bytes: [2048]u8 = undefined; + if (bun.base64.encodeLen(compressed) > b64_bytes.len) { + return error.NoSpaceLeft; + } + const b64_len = bun.base64.encode(&b64_bytes, compressed); + + try writer.writeAll(b64_bytes[0..b64_len]); + } + + if (opts.include_features) { + try writer.writeAll("_"); + // TODO + } + + if (opts.action == .view_trace) { + try writer.writeAll("/view"); + } +} diff --git a/src/windows.zig b/src/windows.zig index 06009f9da0117b..7f9831734cb7a9 100644 --- a/src/windows.zig +++ b/src/windows.zig @@ -77,6 +77,7 @@ pub const PathBuffer = if (Environment.isWindows) bun.PathBuffer else void; pub const WPathBuffer = if (Environment.isWindows) bun.WPathBuffer else void; pub const HANDLE = win32.HANDLE; +pub const HMODULE = win32.HMODULE; /// https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-getfileinformationbyhandle pub extern "kernel32" fn GetFileInformationByHandle( @@ -3361,3 +3362,39 @@ pub fn GetFinalPathNameByHandle( bun.sys.syslog("GetFinalPathNameByHandle({*p})", .{hFile}); return std.os.windows.GetFinalPathNameByHandle(hFile, fmt, out_buffer); } + +extern "kernel32" fn GetModuleHandleExW( + dwFlags: u32, // [in] + lpModuleName: ?*anyopaque, // [in, optional] + phModule: *HMODULE, // [out] +) BOOL; + +extern "kernel32" fn GetModuleFileNameW( + hModule: HMODULE, // [in] + lpFilename: LPWSTR, // [out] + nSize: DWORD, // [in] +) BOOL; + +const GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS = 0x00000004; + +pub fn getModuleHandleFromAddress(addr: usize) ?HMODULE { + var module: HMODULE = undefined; + const rc = GetModuleHandleExW( + GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS, + @ptrFromInt(addr), + &module, + ); + // If the function succeeds, the return value is nonzero. + return if (rc != 0) module else null; +} + +pub fn getModuleNameW(module: HMODULE, buf: []u16) ?[]const u16 { + const rc = GetModuleFileNameW(module, @ptrCast(buf.ptr), @intCast(buf.len)); + if (rc == 0) return null; + return buf[0..@intCast(rc)]; +} + +pub extern "kernel32" fn GetThreadDescription( + thread: ?*anyopaque, // [in] + *PWSTR, // [out] +) std.os.windows.HRESULT; diff --git a/src/zlib.zig b/src/zlib.zig index 1be58557598e8b..4a871ab72a3dea 100644 --- a/src/zlib.zig +++ b/src/zlib.zig @@ -93,7 +93,7 @@ const z_streamp = @import("zlib-internal").z_streamp; const DataType = @import("zlib-internal").DataType; const FlushValue = @import("zlib-internal").FlushValue; -const ReturnCode = @import("zlib-internal").ReturnCode; +pub const ReturnCode = @import("zlib-internal").ReturnCode; // ZEXTERN int ZEXPORT inflateInit OF((z_streamp strm)); From 79af8d2730d6f4821d667a5631ae23e00e71ad19 Mon Sep 17 00:00:00 2001 From: dave caruso Date: Wed, 10 Apr 2024 05:03:54 -0700 Subject: [PATCH 02/26] bwaa --- src/StandaloneModuleGraph.zig | 6 +- src/__global.zig | 32 +- src/analytics.zig | 1 - src/bun.js/api/BunObject.zig | 3 +- src/bun.js/api/bun/process.zig | 2 +- src/bun.js/bindings/exports.zig | 2 +- src/bun.js/node/types.zig | 6 +- src/bun.zig | 18 +- src/bunfig.zig | 2 +- src/cli.zig | 24 +- src/cli/install_completions_command.zig | 6 +- src/cli/run_command.zig | 10 +- src/cli/upgrade_command.zig | 8 +- src/deps/zig-clap/clap/args.zig | 2 +- src/main.zig | 17 +- ...sh_reporter.zig => old_crash_reporter.zig} | 1 + src/old_panic_handler.zig | 58 ++ src/panic_handler.zig | 529 ++++++++++++++++-- src/report.zig | 2 +- src/sha.zig | 2 +- src/sourcemap/sourcemap.zig | 4 + src/trace_string.zig | 342 ----------- 22 files changed, 608 insertions(+), 469 deletions(-) delete mode 100644 src/analytics.zig rename src/{crash_reporter.zig => old_crash_reporter.zig} (97%) create mode 100644 src/old_panic_handler.zig delete mode 100644 src/trace_string.zig diff --git a/src/StandaloneModuleGraph.zig b/src/StandaloneModuleGraph.zig index 8e59529c5e8b02..90d55726450d08 100644 --- a/src/StandaloneModuleGraph.zig +++ b/src/StandaloneModuleGraph.zig @@ -694,7 +694,7 @@ pub const StandaloneModuleGraph = struct { fn openSelf() std.fs.OpenSelfExeError!bun.FileDescriptor { if (!Environment.isWindows) { - const argv = bun.argv(); + const argv = bun.argv; if (argv.len > 0) { if (isBuiltInExe(u8, argv[0])) { return error.FileNotFound; @@ -707,14 +707,14 @@ pub const StandaloneModuleGraph = struct { if (std.fs.openFileAbsoluteZ("/proc/self/exe", .{})) |easymode| { return bun.toFD(easymode.handle); } else |_| { - if (bun.argv().len > 0) { + if (bun.argv.len > 0) { // The user doesn't have /proc/ mounted, so now we just guess and hope for the best. var whichbuf: [bun.MAX_PATH_BYTES]u8 = undefined; if (bun.which( &whichbuf, bun.getenvZ("PATH") orelse return error.FileNotFound, "", - bun.argv()[0], + bun.argv[0], )) |path| { return bun.toFD((try std.fs.cwd().openFileZ(path, .{})).handle); } diff --git a/src/__global.zig b/src/__global.zig index c4c4bcadbcbfa3..b293b77a215c2a 100644 --- a/src/__global.zig +++ b/src/__global.zig @@ -64,13 +64,13 @@ pub inline fn getStartTime() i128 { return @import("root").bun.start_time; } -pub fn setThreadName(name: StringTypes.stringZ) void { +pub fn setThreadName(name: [:0]const u8) void { if (Environment.isLinux) { - _ = std.os.prctl(.SET_NAME, .{@intFromPtr(name.ptr)}) catch 0; + _ = std.os.prctl(.SET_NAME, .{@intFromPtr(name.ptr)}) catch {}; } else if (Environment.isMac) { _ = std.c.pthread_setname_np(name); } else if (Environment.isWindows) { - // _ = std.os.SetThreadDescription(std.os.GetCurrentThread(), name); + _ = std.os.SetThreadDescription(std.os.GetCurrentThread(), name); } } @@ -105,7 +105,7 @@ pub fn raiseIgnoringPanicHandler(sig: anytype) noreturn { } Output.flush(); - @import("./crash_reporter.zig").on_error = null; + if (!Environment.isWindows) { if (sig >= 1 and sig != std.os.SIG.STOP and sig != std.os.SIG.KILL) { const act = std.os.Sigaction{ @@ -119,9 +119,7 @@ pub fn raiseIgnoringPanicHandler(sig: anytype) noreturn { Output.Source.Stdio.restore(); - // TODO(@paperdave): report a bug that this intcast shouldnt be needed. signals are i32 not u32 - // after that is fixed we can make this function take i32 - _ = std.c.raise(@intCast(sig)); + _ = std.c.raise(sig); std.c.abort(); } @@ -165,28 +163,10 @@ pub fn panic(comptime fmt: string, args: anytype) noreturn { // std.debug.assert but happens at runtime pub fn invariant(condition: bool, comptime fmt: string, args: anytype) void { if (!condition) { - _invariant(fmt, args); + bun.Output.panic(fmt, args); } } -inline fn _invariant(comptime fmt: string, args: anytype) noreturn { - @setCold(true); - - if (comptime Environment.isWasm) { - Output.printErrorln(fmt, args); - Output.flush(); - @panic(fmt); - } else { - Output.prettyErrorln(fmt, args); - Global.exit(1); - } -} - -pub fn notimpl() noreturn { - @setCold(true); - Global.panic("Not implemented yet!!!!!", .{}); -} - // Make sure we always print any leftover pub fn crash() noreturn { @setCold(true); diff --git a/src/analytics.zig b/src/analytics.zig deleted file mode 100644 index 68b2e08f53b39e..00000000000000 --- a/src/analytics.zig +++ /dev/null @@ -1 +0,0 @@ -pub usingnamespace @import("./analytics/analytics_thread.zig"); diff --git a/src/bun.js/api/BunObject.zig b/src/bun.js/api/BunObject.zig index a18a707bed01eb..9a39726cfde75b 100644 --- a/src/bun.js/api/BunObject.zig +++ b/src/bun.js/api/BunObject.zig @@ -3370,7 +3370,8 @@ const UnsafeObject = struct { } pub fn crashByPanic(_: *JSC.JSGlobalObject, _: *JSC.CallFrame) callconv(.C) JSC.JSValue { - std.debug.panic("invoked crashByPanic() handler", .{}); + // std.debug.panic("invoked crashByPanic() handler", .{}); + bun.panic_handler.panicImpl("invoked crashByPanic() handler", null, null); } pub fn crashByUnreachable(_: *JSC.JSGlobalObject, _: *JSC.CallFrame) callconv(.C) JSC.JSValue { diff --git a/src/bun.js/api/bun/process.zig b/src/bun.js/api/bun/process.zig index 9cdddf25dd81be..d398e5f46a33cf 100644 --- a/src/bun.js/api/bun/process.zig +++ b/src/bun.js/api/bun/process.zig @@ -1059,7 +1059,7 @@ pub const PosixSpawnResult = struct { } fn pidfdFlagsForLinux() u32 { - const kernel = @import("../../../analytics.zig").GenerateHeader.GeneratePlatform.kernelVersion(); + const kernel = bun.analytics.GenerateHeader.GeneratePlatform.kernelVersion(); // pidfd_nonblock only supported in 5.10+ return if (kernel.orderWithoutTag(.{ .major = 5, .minor = 10, .patch = 0 }).compare(.gte)) diff --git a/src/bun.js/bindings/exports.zig b/src/bun.js/bindings/exports.zig index 99ee9ce599529e..2fd652de6dc488 100644 --- a/src/bun.js/bindings/exports.zig +++ b/src/bun.js/bindings/exports.zig @@ -25,7 +25,7 @@ const Exception = JSC.Exception; const JSModuleLoader = JSC.JSModuleLoader; const Microtask = JSC.Microtask; -const Backtrace = @import("../../crash_reporter.zig"); +const Backtrace = @import("../../old_crash_reporter.zig"); const JSPrinter = bun.js_printer; const JSLexer = bun.js_lexer; const typeBaseName = @import("../../meta.zig").typeBaseName; diff --git a/src/bun.js/node/types.zig b/src/bun.js/node/types.zig index 4dd667ed9e73f8..72ea0e81e00d79 100644 --- a/src/bun.js/node/types.zig +++ b/src/bun.js/node/types.zig @@ -4848,7 +4848,7 @@ pub const Path = struct { pub const Process = struct { pub fn getArgv0(globalObject: *JSC.JSGlobalObject) callconv(.C) JSC.JSValue { - return JSC.ZigString.fromUTF8(bun.argv()[0]).toValueGC(globalObject); + return JSC.ZigString.fromUTF8(bun.argv[0]).toValueGC(globalObject); } pub fn getExecPath(globalObject: *JSC.JSGlobalObject) callconv(.C) JSC.JSValue { @@ -4879,13 +4879,13 @@ pub const Process = struct { JSC.ZigString, // argv omits "bun" because it could be "bun run" or "bun" and it's kind of ambiguous // argv also omits the script name - bun.argv().len -| 1, + bun.argv.len -| 1, ) catch bun.outOfMemory(); defer allocator.free(args); var used: usize = 0; const offset = 1; - for (bun.argv()[@min(bun.argv().len, offset)..]) |arg| { + for (bun.argv[@min(bun.argv.len, offset)..]) |arg| { if (arg.len == 0) continue; diff --git a/src/bun.zig b/src/bun.zig index 1e2d199a9c7ccc..4559a129949fd8 100644 --- a/src/bun.zig +++ b/src/bun.zig @@ -696,7 +696,7 @@ pub const uws = @import("./deps/uws.zig"); pub const BoringSSL = @import("./boringssl.zig"); pub const LOLHTML = @import("./deps/lol-html.zig"); pub const clap = @import("./deps/zig-clap/clap.zig"); -pub const analytics = @import("./analytics.zig"); +pub const analytics = @import("./analytics/analytics_thread.zig"); pub const zlib = @import("./zlib.zig"); pub var start_time: i128 = 0; @@ -1597,8 +1597,8 @@ pub fn reloadProcess( } } const PosixSpawn = posix.spawn; - const dupe_argv = allocator.allocSentinel(?[*:0]const u8, bun.argv().len, null) catch unreachable; - for (bun.argv(), dupe_argv) |src, *dest| { + const dupe_argv = allocator.allocSentinel(?[*:0]const u8, bun.argv.len, null) catch unreachable; + for (bun.argv, dupe_argv) |src, *dest| { dest.* = (allocator.dupeZ(u8, src) catch unreachable).ptr; } @@ -2059,14 +2059,10 @@ const WindowsStat = extern struct { pub const Stat = if (Environment.isWindows) windows.libuv.uv_stat_t else std.os.Stat; -var _argv: [][:0]const u8 = &[_][:0]const u8{}; - -pub inline fn argv() [][:0]const u8 { - return _argv; -} +pub var argv: [][:0]const u8 = &[_][:0]const u8{}; pub fn initArgv(allocator: std.mem.Allocator) !void { - _argv = try std.process.argsAlloc(allocator); + argv = try std.process.argsAlloc(allocator); } pub const posix = struct { @@ -2989,7 +2985,7 @@ pub inline fn markPosixOnly() if (Environment.isPosix) void else noreturn { pub fn linuxKernelVersion() Semver.Version { if (comptime !Environment.isLinux) @compileError("linuxKernelVersion() is only available on Linux"); - return @import("./analytics.zig").GenerateHeader.GeneratePlatform.kernelVersion(); + return analytics.GenerateHeader.GeneratePlatform.kernelVersion(); } pub fn selfExePath() ![:0]u8 { @@ -3046,4 +3042,4 @@ pub fn SliceIterator(comptime T: type) type { pub const Futex = @import("./futex.zig"); -pub const crash_report = @import("panic_v2.zig"); +pub const panic_handler = @import("panic_handler.zig"); diff --git a/src/bunfig.zig b/src/bunfig.zig index c55361b33e1891..9431bd11b76de6 100644 --- a/src/bunfig.zig +++ b/src/bunfig.zig @@ -25,7 +25,7 @@ pub const MacroImportReplacementMap = bun.StringArrayHashMap(string); pub const MacroMap = bun.StringArrayHashMapUnmanaged(MacroImportReplacementMap); pub const BundlePackageOverride = bun.StringArrayHashMapUnmanaged(options.BundleOverride); const LoaderMap = bun.StringArrayHashMapUnmanaged(options.Loader); -const Analytics = @import("./analytics.zig"); +const Analytics = bun.Analytics; const JSONParser = bun.JSON; const Command = @import("cli.zig").Command; const TOML = @import("./toml/toml_parser.zig").TOML; diff --git a/src/cli.zig b/src/cli.zig index 18c3daffafc8b3..29c61a6f2f0087 100644 --- a/src/cli.zig +++ b/src/cli.zig @@ -48,15 +48,15 @@ pub const Cli = struct { var wait_group: sync.WaitGroup = undefined; pub var log_: logger.Log = undefined; pub fn startTransform(_: std.mem.Allocator, _: Api.TransformOptions, _: *logger.Log) anyerror!void {} - pub fn start(allocator: std.mem.Allocator, comptime MainPanicHandler: type) void { + pub fn start(allocator: std.mem.Allocator) void { is_main_thread = true; start_time = std.time.nanoTimestamp(); log_ = logger.Log.init(allocator); var log = &log_; - var panicker = MainPanicHandler.init(log); - MainPanicHandler.Singleton = &panicker; + // var panicker = MainPanicHandler.init(log); + // MainPanicHandler.Singleton = &panicker; Command.start(allocator, log) catch |err| { log.printForLogLevel(Output.errorWriter()) catch {}; @@ -1054,7 +1054,7 @@ pub const HelpCommand = struct { pub const ReservedCommand = struct { pub fn exec(_: std.mem.Allocator) !void { @setCold(true); - const command_name = bun.argv()[1]; + const command_name = bun.argv[1]; Output.prettyError( \\Uh-oh. bun {s} is a subcommand reserved for future use by Bun. \\ @@ -1240,7 +1240,7 @@ pub const Command = struct { } pub fn which() Tag { - var args_iter = ArgsIterator{ .buf = bun.argv() }; + var args_iter = ArgsIterator{ .buf = bun.argv }; const argv0 = args_iter.next() orelse return .HelpCommand; @@ -1383,8 +1383,8 @@ pub const Command = struct { }; ctx.args.target = Api.Target.bun; - if (bun.argv().len > 1) { - ctx.passthrough = bun.argv()[1..]; + if (bun.argv.len > 1) { + ctx.passthrough = bun.argv[1..]; } else { ctx.passthrough = &[_]string{}; } @@ -1397,7 +1397,7 @@ pub const Command = struct { return; } - debug("argv: [{s}]", .{bun.fmt.fmtSlice(bun.argv(), ", ")}); + debug("argv: [{s}]", .{bun.fmt.fmtSlice(bun.argv, ", ")}); const tag = which(); @@ -1405,7 +1405,7 @@ pub const Command = struct { .DiscordCommand => return try DiscordCommand.exec(allocator), .HelpCommand => return try HelpCommand.exec(allocator), .ReservedCommand => return try ReservedCommand.exec(allocator), - .InitCommand => return try InitCommand.exec(allocator, bun.argv()), + .InitCommand => return try InitCommand.exec(allocator, bun.argv), .BuildCommand => { if (comptime bun.fast_debug_build_mode and bun.fast_debug_build_cmd != .BuildCommand) unreachable; const ctx = try Command.Context.create(allocator, log, .BuildCommand); @@ -1441,7 +1441,7 @@ pub const Command = struct { if (comptime bun.fast_debug_build_mode and bun.fast_debug_build_cmd != .BunxCommand) unreachable; const ctx = try Command.Context.create(allocator, log, .BunxCommand); - try BunxCommand.exec(ctx, bun.argv()[if (is_bunx_exe) 0 else 1..]); + try BunxCommand.exec(ctx, bun.argv[if (is_bunx_exe) 0 else 1..]); return; }, .ReplCommand => { @@ -1449,7 +1449,7 @@ pub const Command = struct { if (comptime bun.fast_debug_build_mode and bun.fast_debug_build_cmd != .BunxCommand) unreachable; var ctx = try Command.Context.create(allocator, log, .BunxCommand); ctx.debug.run_in_bun = true; // force the same version of bun used. fixes bun-debug for example - var args = bun.argv()[0..]; + var args = bun.argv[0..]; args[1] = "bun-repl"; try BunxCommand.exec(ctx, args); return; @@ -1755,7 +1755,7 @@ pub const Command = struct { // KEYWORDS: open file argv argv0 if (ctx.args.entry_points.len == 1) { if (strings.eqlComptime(extension, ".lockb")) { - for (bun.argv()) |arg| { + for (bun.argv) |arg| { if (strings.eqlComptime(arg, "--hash")) { try PackageManagerCommand.printHash(ctx, ctx.args.entry_points[0]); return; diff --git a/src/cli/install_completions_command.zig b/src/cli/install_completions_command.zig index 3611ad32a21dda..711b61d464ecf8 100644 --- a/src/cli/install_completions_command.zig +++ b/src/cli/install_completions_command.zig @@ -221,10 +221,10 @@ pub const InstallCompletionsCommand = struct { var completions_dir: string = ""; var output_dir: std.fs.Dir = found: { - for (bun.argv(), 0..) |arg, i| { + for (bun.argv, 0..) |arg, i| { if (strings.eqlComptime(arg, "completions")) { - if (bun.argv().len > i + 1) { - const input = bun.argv()[i + 1]; + if (bun.argv.len > i + 1) { + const input = bun.argv[i + 1]; if (!std.fs.path.isAbsolute(input)) { completions_dir = resolve_path.joinAbs( diff --git a/src/cli/run_command.zig b/src/cli/run_command.zig index 6d0b302909b795..552227420b04fd 100644 --- a/src/cli/run_command.zig +++ b/src/cli/run_command.zig @@ -716,9 +716,9 @@ pub const RunCommand = struct { // if we are already an absolute path, use that // if the user started the application via a shebang, it's likely that the path is absolute already - if (bun.argv()[0][0] == '/') { - optional_bun_path.* = bun.argv()[0]; - argv0 = bun.argv()[0]; + if (bun.argv[0][0] == '/') { + optional_bun_path.* = bun.argv[0]; + argv0 = bun.argv[0]; } else if (optional_bun_path.len == 0) { // otherwise, ask the OS for the absolute path const self = try bun.selfExePath(); @@ -729,7 +729,7 @@ pub const RunCommand = struct { } if (optional_bun_path.len == 0) { - argv0 = bun.argv()[0]; + argv0 = bun.argv[0]; } if (Environment.isDebug) { @@ -1391,7 +1391,7 @@ pub const RunCommand = struct { shebang = std.mem.trim(u8, shebang, " \r\n\t"); if (strings.hasPrefixComptime(shebang, "#!")) { - const first_arg: string = if (bun.argv().len > 0) bun.argv()[0] else ""; + const first_arg: string = if (bun.argv.len > 0) bun.argv[0] else ""; const filename = std.fs.path.basename(first_arg); // are we attempting to run the script with bun? if (!strings.contains(shebang, filename)) { diff --git a/src/cli/upgrade_command.zig b/src/cli/upgrade_command.zig index f1f0b838c4bd0c..9eae932e801d29 100644 --- a/src/cli/upgrade_command.zig +++ b/src/cli/upgrade_command.zig @@ -409,7 +409,7 @@ pub const UpgradeCommand = struct { pub fn exec(ctx: Command.Context) !void { @setCold(true); - const args = bun.argv(); + const args = bun.argv; if (args.len > 2) { for (args[2..]) |arg| { if (!strings.contains(arg, "--")) { @@ -454,14 +454,14 @@ pub const UpgradeCommand = struct { const use_canary = brk: { const default_use_canary = Environment.is_canary; - if (default_use_canary and strings.containsAny(bun.argv(), "--stable")) + if (default_use_canary and strings.containsAny(bun.argv, "--stable")) break :brk false; break :brk strings.eqlComptime(env_loader.map.get("BUN_CANARY") orelse "0", "1") or - strings.containsAny(bun.argv(), "--canary") or default_use_canary; + strings.containsAny(bun.argv, "--canary") or default_use_canary; }; - const use_profile = strings.containsAny(bun.argv(), "--profile"); + const use_profile = strings.containsAny(bun.argv, "--profile"); const version: Version = if (!use_canary) v: { var refresher = std.Progress{}; diff --git a/src/deps/zig-clap/clap/args.zig b/src/deps/zig-clap/clap/args.zig index b7dd99d761788c..3104440b27cd8d 100644 --- a/src/deps/zig-clap/clap/args.zig +++ b/src/deps/zig-clap/clap/args.zig @@ -61,7 +61,7 @@ pub const OsIterator = struct { var res = OsIterator{ .arena = @import("root").bun.ArenaAllocator.init(allocator), .exe_arg = undefined, - .remain = bun.argv(), + .remain = bun.argv, }; res.exe_arg = res.next(); return res; diff --git a/src/main.zig b/src/main.zig index c259a1d55ff943..64d092eff2b66c 100644 --- a/src/main.zig +++ b/src/main.zig @@ -6,10 +6,11 @@ const bun = @import("root").bun; const Output = bun.Output; const Environment = bun.Environment; -const panic_handler = @import("./panic_handler.zig"); -const MainPanicHandler = panic_handler.NewPanicHandler(std.builtin.default_panic); +pub const panic = bun.panic_handler.panic; -pub const panic = @import("panic_v2.zig").panic; +pub const std_options = struct { + pub const enable_segfault_handler = !bun.panic_handler.enabled; +}; pub const io_mode = .blocking; @@ -17,12 +18,13 @@ comptime { std.debug.assert(builtin.target.cpu.arch.endian() == .little); } -const CrashReporter = @import("./crash_reporter.zig"); extern fn bun_warn_avx_missing(url: [*:0]const u8) void; pub extern "C" var _environ: ?*anyopaque; pub extern "C" var environ: ?*anyopaque; pub fn main() void { + bun.panic_handler.init(); + // This should appear before we make any calls at all to libuv. // So it's safest to put it very early in the main function. if (Environment.isWindows) { @@ -36,19 +38,16 @@ pub fn main() void { _environ = @ptrCast(std.os.environ.ptr); } + bun.start_time = std.time.nanoTimestamp(); bun.initArgv(bun.default_allocator) catch |err| { Output.panic("Failed to initialize argv: {s}\n", .{@errorName(err)}); }; - if (Environment.isRelease and Environment.isPosix) - CrashReporter.start() catch unreachable; - - bun.start_time = std.time.nanoTimestamp(); Output.Source.Stdio.init(); defer Output.flush(); if (Environment.isX64 and Environment.enableSIMD and Environment.isPosix) { bun_warn_avx_missing(@import("./cli/upgrade_command.zig").Version.Bun__githubBaselineURL.ptr); } - bun.CLI.Cli.start(bun.default_allocator, MainPanicHandler); + bun.CLI.Cli.start(bun.default_allocator); } diff --git a/src/crash_reporter.zig b/src/old_crash_reporter.zig similarity index 97% rename from src/crash_reporter.zig rename to src/old_crash_reporter.zig index f31ed81b650e4d..64356c089cabea 100644 --- a/src/crash_reporter.zig +++ b/src/old_crash_reporter.zig @@ -1,3 +1,4 @@ +// TODO(@paperdave): merge all functionality into panic_handler.zig const std = @import("std"); fn setup_sigactions(act: ?*const os.Sigaction) !void { diff --git a/src/old_panic_handler.zig b/src/old_panic_handler.zig new file mode 100644 index 00000000000000..95d5eb37f9c630 --- /dev/null +++ b/src/old_panic_handler.zig @@ -0,0 +1,58 @@ +// TODO(@paperdave): merge all functionality into panic_handler.zig +const std = @import("std"); +const bun = @import("root").bun; +const string = bun.string; +const Output = bun.Output; +const Global = bun.Global; +const Environment = bun.Environment; +const strings = bun.strings; +const MutableString = bun.MutableString; +const stringZ = bun.stringZ; +const default_allocator = bun.default_allocator; +const C = bun.C; +const CLI = @import("./cli.zig").Cli; +const Features = @import("./analytics/analytics_thread.zig").Features; +const HTTP = @import("root").bun.http.AsyncHTTP; +const Report = @import("./report.zig"); + +pub fn NewPanicHandler(comptime panic_func: fn ([]const u8, ?*std.builtin.StackTrace, ?usize) noreturn) type { + return struct { + panic_count: usize = 0, + skip_next_panic: bool = false, + log: *bun.logger.Log, + + pub var Singleton: ?*Handler = null; + const Handler = @This(); + + pub fn init(log: *bun.logger.Log) Handler { + return Handler{ + .log = log, + }; + } + pub inline fn handle_panic(msg: []const u8, error_return_type: ?*std.builtin.StackTrace, addr: ?usize) noreturn { + + // This exists to ensure we flush all buffered output before panicking. + Output.flush(); + + bun.maybeHandlePanicDuringProcessReload(); + + Report.fatal(null, msg); + + Output.disableBuffering(); + + Output.Source.Stdio.restore(); + + if (bun.auto_reload_on_crash) { + // attempt to prevent a double panic + bun.auto_reload_on_crash = false; + + Output.prettyErrorln("--- Bun is auto-restarting due to crash [time: {d}] ---", .{@max(std.time.milliTimestamp(), 0)}); + Output.flush(); + bun.reloadProcess(bun.default_allocator, false); + } + + // We want to always inline the panic handler so it doesn't show up in the stacktrace. + @call(bun.callmod_inline, panic_func, .{ msg, error_return_type, addr }); + } + }; +} diff --git a/src/panic_handler.zig b/src/panic_handler.zig index ce80d92ebf500a..488d06653d00d3 100644 --- a/src/panic_handler.zig +++ b/src/panic_handler.zig @@ -1,57 +1,500 @@ +//! This file contains Bun's panic handler. In debug builds, we are able to +//! print backtraces that are mapped to source code. In a release mode, we do +//! not have that information in the binary. Bun's solution to this is called +//! a "trace string", a compressed and url-safe encoding of a captured +//! backtrace. Version 1 tracestrings contain the following information: +//! +//! - What version and commit of Bun captured the backtrace. +//! - The platform the backtrace was captured on. +//! - The list of addresses ready to be remapped. +//! - If panicking, the message that was panicked with. +//! - List of feature-flags that were marked. +//! +//! These can be demangled using Bun's remapping API, which has cached +//! versions of all debug symbols for all versions of Bun. Hosting this keeps +//! users from having to download symbols, which can be very large. +//! +//! +//! Source code: https://github.com/oven-sh/bun-report const std = @import("std"); const bun = @import("root").bun; -const string = bun.string; -const Output = bun.Output; -const Global = bun.Global; -const Environment = bun.Environment; -const strings = bun.strings; -const MutableString = bun.MutableString; -const stringZ = bun.stringZ; -const default_allocator = bun.default_allocator; -const C = bun.C; -const CLI = @import("./cli.zig").Cli; -const Features = @import("./analytics/analytics_thread.zig").Features; -const HTTP = @import("root").bun.http.AsyncHTTP; -const Report = @import("./report.zig"); - -pub fn NewPanicHandler(comptime panic_func: fn ([]const u8, ?*std.builtin.StackTrace, ?usize) noreturn) type { - return struct { - panic_count: usize = 0, - skip_next_panic: bool = false, - log: *bun.logger.Log, - - pub var Singleton: ?*Handler = null; - const Handler = @This(); - - pub fn init(log: *bun.logger.Log) Handler { - return Handler{ - .log = log, +const builtin = @import("builtin"); +const mimalloc = @import("allocators/mimalloc.zig"); +const SourceMap = @import("./sourcemap/sourcemap.zig"); +const Features = bun.Analytics.Features; + +/// Set this to false if you want to disable all uses of this panic handler. +/// This is useful for testing as a crash in here will not 'panicked during a panic'. +pub const enabled = false; + +const report_base_url = "https://bun.report/"; + +var has_printed_message = false; + +/// Non-zero whenever the program triggered a panic. +/// The counter is incremented/decremented atomically. +var panicking = std.atomic.Value(u8).init(0); + +// Locked to avoid interleaving panic messages from multiple threads. +var panic_mutex = std.Thread.Mutex{}; + +/// Counts how many times the panic handler is invoked by this thread. +/// This is used to catch and handle panics triggered by the panic handler. +threadlocal var panic_stage: usize = 0; + +pub fn panicImpl(msg: []const u8, error_return_trace: ?*std.builtin.StackTrace, begin_addr: ?usize) noreturn { + @setCold(true); + + // std.os.abort()'s non libc version will not clear signal handlers. + comptime std.debug.assert(builtin.link_libc); + + nosuspend switch (panic_stage) { + 0 => { + panic_stage = 1; + _ = panicking.fetchAdd(1, .SeqCst); + + { + panic_mutex.lock(); + defer panic_mutex.unlock(); + + bun.Output.flush(); + + const writer = bun.Output.errorWriter(); + + if (!has_printed_message) { + has_printed_message = true; + writer.writeAll("=" ** 60 ++ "\n") catch std.os.abort(); + bun.Output.err("oh no", + \\Bun has crashed. This indicates a bug in Bun, and + \\should be reported as a GitHub issue. + \\ + \\ + , .{}); + bun.Output.flush(); + printMetadata(writer) catch std.os.abort(); + } + + if (bun.Output.enable_ansi_colors) { + writer.writeAll(bun.Output.prettyFmt("", true)) catch std.os.abort(); + } + + writer.writeAll("panic") catch std.os.abort(); + if (bun.CLI.Cli.is_main_thread) { + writer.writeAll("(main thread)") catch std.os.abort(); + } else switch (bun.Environment.os) { + .windows => { + var name: std.os.windows.PWSTR = undefined; + const result = bun.windows.GetThreadDescription(std.os.windows.kernel32.GetCurrentThread(), &name); + if (std.os.windows.HRESULT_CODE(result) == .SUCCESS and name[0] != 0) { + writer.print("({})", .{bun.fmt.utf16(bun.span(name))}) catch std.os.abort(); + } else { + writer.print("(thread {d})", .{std.os.windows.kernel32.GetCurrentThreadId()}) catch std.os.abort(); + } + }, + .mac, .linux => {}, + else => @compileError("TODO"), + } + + writer.writeAll(": ") catch std.os.abort(); + writer.writeAll(msg) catch std.os.abort(); + + if (bun.Output.enable_ansi_colors) { + writer.writeAll(bun.Output.prettyFmt("\n", true)) catch std.os.abort(); + } else { + writer.writeAll("\n") catch std.os.abort(); + } + + var addr_buf: [32]usize = undefined; + var trace_buf: std.builtin.StackTrace = undefined; + + // If a trace was not provided, compute one now + const trace = (error_return_trace orelse get_backtrace: { + trace_buf = std.builtin.StackTrace{ + .index = 0, + .instruction_addresses = &addr_buf, + }; + std.debug.captureStackTrace(begin_addr orelse @returnAddress(), &trace_buf); + break :get_backtrace &trace_buf; + }); + + writer.writeAll("Please report this panic as a GitHub issue using this link:\n") catch std.os.abort(); + if (bun.Output.enable_ansi_colors) { + writer.print(bun.Output.prettyFmt("", true), .{}) catch std.os.abort(); + } + + encodeTraceString( + .{ + .trace = trace, + .msg = msg, + .include_features = true, + .action = .open_issue, + }, + writer, + ) catch std.os.abort(); + + if (bun.Output.enable_ansi_colors) { + writer.writeAll(bun.Output.prettyFmt("\n", true)) catch std.os.abort(); + } else { + writer.writeAll("\n") catch std.os.abort(); + } + + bun.Output.flush(); + } + + waitForOtherThreadToFinishPanicking(); + }, + inline 1, 2 => |t| { + if (t == 1) { + panic_stage = 2; + bun.Output.flush(); + } + panic_stage = 3; + + // A panic happened while trying to print a previous panic message, + // we're still holding the mutex but that's fine as we're going to + // call abort() + const stderr = std.io.getStdErr().writer(); + stderr.print("panic: {s}\n", .{msg}) catch std.os.abort(); + stderr.print("panicked during a panic. Aborting.\n", .{}) catch std.os.abort(); + }, + 3 => { + // Panicked while printing "Panicked during a panic." + }, + else => { + // Panicked or otherwise looped into the panic handler while trying to exit. + std.os.abort(); + }, + }; + + switch (bun.Environment.os) { + .windows => { + std.os.abort(); + }, + else => { + // Parts of this is copied from std.os.abort (linux non libc path) and WTFCrash + // Cause a segfault to make sure a core dump is generated if such is enabled + + // Only one thread may proceed to the rest of abort(). + const global = struct { + var abort_entered: bool = false; }; + while (@cmpxchgWeak(bool, &global.abort_entered, false, true, .SeqCst, .SeqCst)) |_| {} + + // Install default handler so that the tkill below will terminate. + const sigact = std.os.Sigaction{ .handler = .{ .handler = std.os.SIG.DFL }, .mask = std.os.empty_sigset, .flags = 0 }; + inline for (.{ + std.os.SIG.SEGV, + std.os.SIG.ILL, + std.os.SIG.BUS, + std.os.SIG.ABRT, + std.os.SIG.FPE, + std.os.SIG.HUP, + std.os.SIG.TERM, + }) |sig| { + std.os.sigaction(sig, &sigact, null) catch {}; + } + + @as(*allowzero volatile u8, @ptrFromInt(0xDEADBEEF)).* = 0; + std.os.raise(std.os.SIG.SEGV) catch {}; + @as(*allowzero volatile u8, @ptrFromInt(0)).* = 0; + std.os.exit(127); + }, + } +} + +fn panicBuiltin(msg: []const u8, error_return_trace: ?*std.builtin.StackTrace, begin_addr: ?usize) noreturn { + std.debug.panicImpl(error_return_trace, begin_addr, msg); +} + +pub const panic = if (enabled) panicImpl else panicBuiltin; + +const arch_display_string = if (bun.Environment.isAarch64) + if (bun.Environment.isMac) "Silicon" else "arm64" +else + "x64"; + +const metadata_version_line = std.fmt.comptimePrint( + "Bun v{s} {s} {s}{s}\n", + .{ + bun.Global.package_json_version_with_sha, + bun.Environment.os.displayString(), + arch_display_string, + if (bun.Environment.baseline) " (baseline)" else "", + }, +); + +pub fn init() void { + if (!enabled) return; + // TODO: +} + +pub fn printMetadata(writer: anytype) !void { + try writer.writeAll(metadata_version_line); + try writer.print("Args: ", .{}); + for (bun.argv, 0..) |arg, i| { + if (i != 0) try writer.writeAll(", "); + try bun.fmt.quotedWriter(writer, arg); + } + try writer.print("{}", .{bun.Analytics.Features.formatter()}); + + if (bun.use_mimalloc) { + var elapsed_msecs: usize = 0; + var user_msecs: usize = 0; + var system_msecs: usize = 0; + var current_rss: usize = 0; + var peak_rss: usize = 0; + var current_commit: usize = 0; + var peak_commit: usize = 0; + var page_faults: usize = 0; + mimalloc.mi_process_info( + &elapsed_msecs, + &user_msecs, + &system_msecs, + ¤t_rss, + &peak_rss, + ¤t_commit, + &peak_commit, + &page_faults, + ); + try writer.print("Elapsed: {d}ms | User: {d}ms | Sys: {d}ms\nRSS: {:<3.2} | Peak: {:<3.2} | Commit: {:<3.2} | Faults: {d}\n", .{ + elapsed_msecs, + user_msecs, + system_msecs, + std.fmt.fmtIntSizeDec(current_rss), + std.fmt.fmtIntSizeDec(peak_rss), + std.fmt.fmtIntSizeDec(current_commit), + page_faults, + }); + } + + try writer.writeAll("\n"); +} + +fn waitForOtherThreadToFinishPanicking() void { + if (panicking.fetchSub(1, .SeqCst) != 1) { + // Another thread is panicking, wait for the last one to finish + // and call abort() + if (builtin.single_threaded) unreachable; + + // Sleep forever without hammering the CPU + var futex = std.atomic.Value(u32).init(0); + while (true) std.Thread.Futex.wait(&futex, 0); + comptime unreachable; + } +} + +/// Each platform is encoded is a single character. It is placed right after the +/// slash after the version, so someone just reading the trace string can tell +/// what platform it came from. L, M, and W are for Linux, macOS, and Windows, +/// with capital letters indicating aarch64, lowercase indicating x86_64. +/// +/// eg: 'https://bun.report/1.1.3/we04c... +// ^ this tells you it is windows x86_64 +/// +/// Baseline gets a weirder encoding of a mix of b and e. +const Platform = enum(u8) { + linux_x86_64 = 'l', + linux_x86_64_baseline = 'B', + linux_aarch64 = 'L', + + mac_x86_64_baseline = 'b', + mac_x86_64 = 'm', + mac_aarch64 = 'M', + + windows_x86_64 = 'w', + windows_x86_64_baseline = 'e', + + const current = @field(Platform, @tagName(bun.Environment.os) ++ + "_" ++ @tagName(builtin.target.cpu.arch) ++ + (if (bun.Environment.baseline) "_baseline" else "")); +}; + +const tracestr_header = std.fmt.comptimePrint( + "{s}/{c}{s}1", + .{ + bun.Environment.version_string, + @intFromEnum(Platform.current), + if (bun.Environment.git_sha.len > 0) bun.Environment.git_sha[0..7] else "unknown", + }, +); + +const EncodeOptions = struct { + trace: *std.builtin.StackTrace, + msg: ?[]const u8, + include_features: bool, + action: Action, + + const Action = enum { + /// Open a pre-filled GitHub issue with the expanded trace + open_issue, + /// View the trace with nothing else + view_trace, + }; +}; + +const Address = union(enum) { + unknown, + known: struct { + address: i32, + // null -> from bun.exe + object: ?[]const u8, + }, + javascript, + + pub fn writeEncoded(self: Address, writer: anytype) !void { + switch (self) { + .unknown => try writer.writeAll("_"), + .known => |known| { + if (known.object) |object| { + try SourceMap.encodeVLQ(1).writeTo(writer); + try SourceMap.encodeVLQ(@intCast(object.len)).writeTo(writer); + try writer.writeAll(object); + } + try SourceMap.encodeVLQ(known.address).writeTo(writer); + }, + .javascript => { + try writer.writeAll("="); + }, } - pub inline fn handle_panic(msg: []const u8, error_return_type: ?*std.builtin.StackTrace, addr: ?usize) noreturn { + } - // This exists to ensure we flush all buffered output before panicking. - Output.flush(); + pub fn format(self: Address, comptime _: []const u8, _: std.fmt.FormatOptions, writer: anytype) !void { + switch (self) { + .unknown => try writer.print("unknown address", .{}), + .known => |known| try writer.print("0x{x} @ {s}", .{ known.address, known.object orelse "bun" }), + .javascript => try writer.print("javascript address", .{}), + } + } +}; - bun.maybeHandlePanicDuringProcessReload(); +fn encodeTraceString(opts: EncodeOptions, writer: anytype) !void { + try writer.writeAll(report_base_url ++ tracestr_header); - Report.fatal(null, msg); + const image_path = if (bun.Environment.isWindows) bun.windows.exePathW() else null; - Output.disableBuffering(); + var name_bytes: [512]u16 = undefined; + var name_bytes_utf8: [1024]u8 = undefined; - Output.Source.Stdio.restore(); + for (opts.trace.instruction_addresses[0..opts.trace.index]) |addr| { + const address: Address = switch (bun.Environment.os) { + .windows => addr: { + const module = bun.windows.getModuleHandleFromAddress(addr) orelse { + // TODO: try to figure out of this is a JS stack frame + break :addr .{ .unknown = {} }; + }; - if (bun.auto_reload_on_crash) { - // attempt to prevent a double panic - bun.auto_reload_on_crash = false; + const base_address = @intFromPtr(module); + const name = bun.windows.getModuleNameW(module, &name_bytes) orelse + break :addr .{ .unknown = {} }; - Output.prettyErrorln("--- Bun is auto-restarting due to crash [time: {d}] ---", .{@max(std.time.milliTimestamp(), 0)}); - Output.flush(); - bun.reloadProcess(bun.default_allocator, false); - } + break :addr .{ + .mapped = .{ + // To remap this, `pdb-addr2line --exe bun.pdb 0x123456` + .address = addr - base_address, + + .object = if (!std.mem.eql(u16, name, image_path)) name: { + const basename = name[std.mem.lastIndexOfAny(u16, name, "\\/") orelse 0 ..]; + break :name bun.strings.convertUTF8toUTF16InBuffer(&name_bytes_utf8, basename); + } else null, + }, + }; + }, + .mac => addr: { + const address = if (addr == 0) 0 else addr - 1; + + const image_count = std.c._dyld_image_count(); + + var i: u32 = 0; + while (i < image_count) : (i += 1) { + const header = std.c._dyld_get_image_header(i) orelse continue; + const base_address = @intFromPtr(header); + if (address < base_address) continue; + // This 'slide' is the ASLR offset. Subtract from `address` to get a stable address + const vmaddr_slide = std.c._dyld_get_image_vmaddr_slide(i); + + var it = std.macho.LoadCommandIterator{ + .ncmds = header.ncmds, + .buffer = @alignCast(@as( + [*]u8, + @ptrFromInt(@intFromPtr(header) + @sizeOf(std.macho.mach_header_64)), + )[0..header.sizeofcmds]), + }; + + while (it.next()) |cmd| switch (cmd.cmd()) { + .SEGMENT_64 => { + const segment_cmd = cmd.cast(std.macho.segment_command_64).?; + if (!bun.strings.eqlComptime(segment_cmd.segName(), "__TEXT")) continue; + + const original_address = address - vmaddr_slide; + const seg_start = segment_cmd.vmaddr; + const seg_end = seg_start + segment_cmd.vmsize; + if (original_address >= seg_start and original_address < seg_end) { + // Subtract ASLR value for stable address + const stable_address: isize = @intCast(address - vmaddr_slide); + // Subtract base address for compactness + // To remap this, `llvm-symbolizer --obj bun-with-symbols --relative-address 0x123456` + const relative_address: i32 = @intCast(stable_address - @as(isize, @intCast(base_address))); + + const object = if (i == 0) + null // zero is the main binary + else + std.fs.path.basename(bun.sliceTo(std.c._dyld_get_image_name(i), 0)); + + break :addr .{ .known = .{ + .object = object, + .address = relative_address, + } }; + } + }, + else => {}, + }; + } - // We want to always inline the panic handler so it doesn't show up in the stacktrace. - @call(bun.callmod_inline, panic_func, .{ msg, error_return_type, addr }); + break :addr .{ .unknown = {} }; + }, + else => @compileError("TODO"), + }; + + try address.writeEncoded(writer); + } + + try writer.writeAll(comptime zero_vlq: { + const vlq = SourceMap.encodeVLQ(0); + break :zero_vlq vlq.bytes[0..vlq.len]; + }); + + if (opts.msg) |message| { + var compressed_bytes: [2048]u8 = undefined; + var len: usize = compressed_bytes.len; + const ret: bun.zlib.ReturnCode = @enumFromInt(bun.zlib.compress2(&compressed_bytes, &len, message.ptr, message.len, 9)); + const compressed = switch (ret) { + .Ok => compressed_bytes[0..len], + // Insufficient memory. + .MemError => return error.OutOfMemory, + // The buffer dest was not large enough to hold the compressed data. + .BufError => return error.NoSpaceLeft, + + // The level was not Z_DEFAULT_LEVEL, or was not between 0 and 9. + // This is technically possible but impossible because we pass 9. + .StreamError => return error.Unexpected, + else => return error.Unexpected, + }; + + var b64_bytes: [2048]u8 = undefined; + if (bun.base64.encodeLen(compressed) > b64_bytes.len) { + return error.NoSpaceLeft; } - }; + const b64_len = bun.base64.encode(&b64_bytes, compressed); + + try writer.writeAll(b64_bytes[0..b64_len]); + } + + if (opts.include_features) { + // try writer.writeAll("_"); + // TODO + } + + if (opts.action == .view_trace) { + try writer.writeAll("/view"); + } } diff --git a/src/report.zig b/src/report.zig index 357dba783c7591..9d2a4878d12d7e 100644 --- a/src/report.zig +++ b/src/report.zig @@ -14,7 +14,7 @@ const CLI = @import("./cli.zig").Cli; const Features = @import("./analytics/analytics_thread.zig").Features; const Platform = @import("./analytics/analytics_thread.zig").GenerateHeader.GeneratePlatform; const HTTP = @import("root").bun.http.AsyncHTTP; -const CrashReporter = @import("./crash_reporter.zig"); +const CrashReporter = @import("./old_crash_reporter.zig"); const Report = @This(); diff --git a/src/sha.zig b/src/sha.zig index c8a1a095eae53d..d456f0e9cf7799 100644 --- a/src/sha.zig +++ b/src/sha.zig @@ -199,7 +199,7 @@ const labels = [_][]const u8{ "Blake3", }; pub fn main() anyerror!void { - var file = try std.fs.cwd().openFileZ(bun.argv()[bun.argv().len - 1], .{}); + var file = try std.fs.cwd().openFileZ(bun.argv[bun.argv.len - 1], .{}); const bytes = try file.readToEndAlloc(std.heap.c_allocator, std.math.maxInt(usize)); const engine = BoringSSL.ENGINE_new().?; diff --git a/src/sourcemap/sourcemap.zig b/src/sourcemap/sourcemap.zig index e2d086c635c327..cbe61f4cb784be 100644 --- a/src/sourcemap/sourcemap.zig +++ b/src/sourcemap/sourcemap.zig @@ -678,6 +678,10 @@ pub const VLQ = struct { // I believe the actual number is 7 bytes long, however we can add an extra byte to be more cautious bytes: [vlq_max_in_bytes]u8, len: u4 = 0, + + pub fn writeTo(self: VLQ, writer: anytype) !void { + try writer.writeAll(self.bytes[0..self.len]); + } }; pub fn encodeVLQWithLookupTable( diff --git a/src/trace_string.zig b/src/trace_string.zig deleted file mode 100644 index 425f7fa87ca856..00000000000000 --- a/src/trace_string.zig +++ /dev/null @@ -1,342 +0,0 @@ -//! TODO: document this -//! -//! This file is heavily inspired off of std.debug.panicImpl, but instead of -//! dumping addresses in release mode, it finds the offsets of these functions -//! and encodes them into a short string that can be compactly shared to the -//! GitHub issue tracker. This allows the binary to not contain any debug -//! symbols (makes it a smaller download), makes traces more concise in the -//! console, and still helps us debug crashes easily. -//! -//! Trace strings can be remapped using a remote service, which has a copy -//! of every version since this new panic handler was implemented (v1.1.4). -//! -//! TODO: instructions to fetch that -//! -const std = @import("std"); -const builtin = @import("builtin"); - -const mimalloc = @import("allocators/mimalloc.zig"); -const bun = @import("root").bun; - -const SourceMap = @import("./sourcemap/sourcemap.zig"); - -const report_base_url = "https://bun.report/"; - -var has_printed_message = false; - -/// Non-zero whenever the program triggered a panic. -/// The counter is incremented/decremented atomically. -var panicking = std.atomic.Value(u8).init(0); - -// Locked to avoid interleaving panic messages from multiple threads. -var panic_mutex = std.Thread.Mutex{}; - -/// Counts how many times the panic handler is invoked by this thread. -/// This is used to catch and handle panics triggered by the panic handler. -threadlocal var panic_stage: usize = 0; - -pub fn panic(msg: []const u8, maybe_trace: ?*std.builtin.StackTrace, begin_addr: ?usize) noreturn { - @setCold(true); - - nosuspend switch (panic_stage) { - 0 => { - panic_stage = 1; - _ = panicking.fetchAdd(1, .SeqCst); - - { - panic_mutex.lock(); - defer panic_mutex.unlock(); - - bun.Output.flush(); - - const writer = bun.Output.errorWriter(); - - if (!has_printed_message) { - has_printed_message = true; - writer.writeAll("=" ** 60 ++ "\n") catch std.os.abort(); - bun.Output.err("oh no", - \\Bun has crashed. This indicates a bug in Bun, and - \\should be reported as a GitHub issue. - \\ - \\ - , .{}); - bun.Output.flush(); - printMetadata(writer) catch std.os.abort(); - } - - if (bun.Output.enable_ansi_colors) { - writer.writeAll(bun.Output.prettyFmt("", true)) catch std.os.abort(); - } - - writer.writeAll("panic") catch std.os.abort(); - switch (bun.Environment.os) { - .windows => { - var name: std.os.windows.PWSTR = undefined; - const result = bun.windows.GetThreadDescription(std.os.windows.kernel32.GetCurrentThread(), &name); - if (std.os.windows.HRESULT_CODE(result) == .SUCCESS and name[0] != 0) { - writer.print("({})", .{bun.fmt.utf16(bun.span(name))}) catch std.os.abort(); - } else { - writer.print("(thread {d})", .{std.os.windows.kernel32.GetCurrentThreadId()}) catch std.os.abort(); - } - }, - else => @compileError("TODO"), - } - - writer.writeAll(": ") catch std.os.abort(); - writer.writeAll(msg) catch std.os.abort(); - - if (bun.Output.enable_ansi_colors) { - writer.writeAll(bun.Output.prettyFmt("\n", true)) catch std.os.abort(); - } else { - writer.writeAll("\n") catch std.os.abort(); - } - - var addr_buf: [32]usize = undefined; - var trace_buf: std.builtin.StackTrace = undefined; - - // If a trace was not provided, compute one now - const trace = (maybe_trace orelse compute_now: { - trace_buf = std.builtin.StackTrace{ - .index = 0, - .instruction_addresses = &addr_buf, - }; - std.debug.captureStackTrace(begin_addr orelse @returnAddress(), &trace_buf); - break :compute_now &trace_buf; - }); - - writer.writeAll("Please report this panic as a GitHub issue using this link:\n") catch std.os.abort(); - if (bun.Output.enable_ansi_colors) { - writer.print(bun.Output.prettyFmt("", true), .{}) catch std.os.abort(); - } - - encode(trace, msg, writer) catch std.os.abort(); - - if (bun.Output.enable_ansi_colors) { - writer.writeAll(bun.Output.prettyFmt("\n", true)) catch std.os.abort(); - } else { - writer.writeAll("\n") catch std.os.abort(); - } - - bun.Output.flush(); - } - - waitForOtherThreadToFinishPanicking(); - }, - 1 => { - // A panic happened while trying to print a previous panic message, - // we're still holding the mutex but that's fine as we're going to - // call abort() - const stderr = std.io.getStdErr().writer(); - stderr.print("panic: {s}\n", .{msg}) catch std.os.abort(); - stderr.print("panicked during a panic. Aborting.\n", .{}) catch std.os.abort(); - }, - else => { - // Panicked while printing "Panicked during a panic." - }, - }; - - switch (bun.Environment.os) { - .windows => { - std.os.abort(); - }, - else => { - // Cause a segfault to make sure a core dump is generated if such is enabled - const act = std.os.Sigaction{ - .handler = .{ .sigaction = @ptrCast(@alignCast(std.os.SIG.DFL)) }, - .mask = std.os.empty_sigset, - .flags = 0, - }; - std.os.sigaction(std.os.SIG.SEGV, &act, null) catch std.os.abort(); - }, - } -} - -const arch_display_string = if (bun.Environment.isAarch64) - if (bun.Environment.isMac) "Silicon" else "arm64" -else - "x64"; - -const metadata_version_line = std.fmt.comptimePrint( - "Bun v{s} {s} {s}{s}\n", - .{ - bun.Global.package_json_version_with_sha, - bun.Environment.os.displayString(), - arch_display_string, - if (bun.Environment.baseline) " (baseline)" else "", - }, -); - -pub fn printMetadata(writer: anytype) !void { - try writer.writeAll(metadata_version_line); - try writer.print("Cmd: {s}\n", .{if (bun.CLI.Cli.cmd) |cmd| @tagName(cmd) else "unknown"}); - try writer.print("{}", .{bun.Analytics.Features.formatter()}); - - if (bun.use_mimalloc) { - var elapsed_msecs: usize = 0; - var user_msecs: usize = 0; - var system_msecs: usize = 0; - var current_rss: usize = 0; - var peak_rss: usize = 0; - var current_commit: usize = 0; - var peak_commit: usize = 0; - var page_faults: usize = 0; - mimalloc.mi_process_info( - &elapsed_msecs, - &user_msecs, - &system_msecs, - ¤t_rss, - &peak_rss, - ¤t_commit, - &peak_commit, - &page_faults, - ); - try writer.print("Elapsed: {d}ms | User: {d}ms | Sys: {d}ms\nRSS: {:<3.2} | Peak: {:<3.2} | Commit: {:<3.2} | Faults: {d}\n", .{ - elapsed_msecs, - user_msecs, - system_msecs, - std.fmt.fmtIntSizeDec(current_rss), - std.fmt.fmtIntSizeDec(peak_rss), - std.fmt.fmtIntSizeDec(current_commit), - page_faults, - }); - } - - try writer.writeAll("\n"); -} - -fn waitForOtherThreadToFinishPanicking() void { - if (panicking.fetchSub(1, .SeqCst) != 1) { - // Another thread is panicking, wait for the last one to finish - // and call abort() - if (builtin.single_threaded) unreachable; - - // Sleep forever without hammering the CPU - var futex = std.atomic.Value(u32).init(0); - while (true) std.Thread.Futex.wait(&futex, 0); - comptime unreachable; - } -} - -/// Each platform is encoded is a single character. It is placed right after the -/// slash after the version, so someone just reading the trace string can tell -/// what platform it came from. L, M, and W are for Linux, macOS, and Windows, -/// with capital letters indicating aarch64, lowercase indicating x86_64. -/// -/// eg: 'https://bun.report/1.1.3/we04c... -// ^ this tells you it is windows x86_64 -/// -/// Baseline gets a weirder encoding of a mix of b and e. -const Platform = enum(u8) { - linux_x86_64 = 'l', - linux_x86_64_baseline = 'B', - linux_aarch64 = 'L', - - macos_x86_64_baseline = 'b', - macos_x86_64 = 'm', - macos_aarch64 = 'M', - - windows_x86_64 = 'w', - windows_x86_64_baseline = 'e', - - const current = @field(Platform, @tagName(bun.Environment.os) ++ - "_" ++ @tagName(builtin.target.cpu.arch)) ++ - (if (bun.Environment.isBaseline) "_baseline" else ""); -}; - -const header = std.fmt.comptimePrint( - "{s}/{c}{s}1", - .{ - bun.Environment.version_string, - @intFromEnum(Platform.current), - if (bun.Environment.git_sha.len > 0) bun.Environment.git_sha[0..7] else "unknown", - }, -); - -const EncodeOptions = struct { - trace: *std.builtin.StackTrace, - msg: ?[]const u8, - include_features: bool, - action: Action, - - const Action = enum { - /// Open a pre-filled GitHub issue with the expanded trace - open_issue, - /// View the trace with nothing else - view_trace, - }; -}; - -fn encode(opts: EncodeOptions, writer: anytype) !void { - try writer.writeAll(report_base_url ++ header); - - const image_path = bun.windows.exePathW(); - - for (opts.trace.instruction_addresses[0..opts.trace.index]) |addr| { - const module = bun.windows.getModuleHandleFromAddress(addr) orelse { - try writer.writeAll("_"); - continue; - }; - const base_address = @intFromPtr(module); - var name_bytes: [512]u16 = undefined; - var name_bytes_utf8: [1024]u8 = undefined; - const name = bun.windows.getModuleNameW(module, &name_bytes) orelse { - try writer.writeAll("_"); - continue; - }; - if (!std.mem.eql(u16, name, image_path)) { - try writer.writeAll(comptime one_vlq: { - const vlq = SourceMap.encodeVLQ(1); - break :one_vlq vlq.bytes[0..vlq.len]; - }); - - const utf8_name = try bun.strings.convertUTF16toUTF8InBuffer(&name_bytes_utf8, name); - const vlq_name = SourceMap.encodeVLQ(@intCast(utf8_name.len)); - try writer.writeAll(vlq_name.bytes[0..vlq_name.len]); - try writer.writeAll(utf8_name); - } - - const offset = addr - base_address; - const vlq = SourceMap.encodeVLQ(@intCast(offset)); - try writer.writeAll(vlq.bytes[0..vlq.len]); - } - - try writer.writeAll(comptime zero_vlq: { - const vlq = SourceMap.encodeVLQ(0); - break :zero_vlq vlq.bytes[0..vlq.len]; - }); - - if (opts.msg) |message| { - var compressed_bytes: [2048]u8 = undefined; - var len: usize = compressed_bytes.len; - const ret: bun.zlib.ReturnCode = @enumFromInt(bun.zlib.compress2(&compressed_bytes, &len, message.ptr, message.len, 9)); - const compressed = switch (ret) { - .Ok => compressed_bytes[0..len], - // Insufficient memory. - .MemError => return error.OutOfMemory, - // The buffer dest was not large enough to hold the compressed data. - .BufError => return error.NoSpaceLeft, - - // The level was not Z_DEFAULT_LEVEL, or was not between 0 and 9. - // This is technically possible but impossible because we pass 9. - .StreamError => return error.Unexpected, - else => return error.Unexpected, - }; - - var b64_bytes: [2048]u8 = undefined; - if (bun.base64.encodeLen(compressed) > b64_bytes.len) { - return error.NoSpaceLeft; - } - const b64_len = bun.base64.encode(&b64_bytes, compressed); - - try writer.writeAll(b64_bytes[0..b64_len]); - } - - if (opts.include_features) { - try writer.writeAll("_"); - // TODO - } - - if (opts.action == .view_trace) { - try writer.writeAll("/view"); - } -} From 2a633fa2de6728596e04c9582228e65aeac552e8 Mon Sep 17 00:00:00 2001 From: dave caruso Date: Wed, 10 Apr 2024 06:47:55 -0700 Subject: [PATCH 03/26] some work --- src/panic_handler.zig | 339 +++++++++++++++++++++++++++++++----------- 1 file changed, 254 insertions(+), 85 deletions(-) diff --git a/src/panic_handler.zig b/src/panic_handler.zig index 488d06653d00d3..80b7088a826272 100644 --- a/src/panic_handler.zig +++ b/src/panic_handler.zig @@ -1,4 +1,4 @@ -//! This file contains Bun's panic handler. In debug builds, we are able to +//! This file contains Bun's crash handler. In debug builds, we are able to //! print backtraces that are mapped to source code. In a release mode, we do //! not have that information in the binary. Bun's solution to this is called //! a "trace string", a compressed and url-safe encoding of a captured @@ -21,11 +21,12 @@ const bun = @import("root").bun; const builtin = @import("builtin"); const mimalloc = @import("allocators/mimalloc.zig"); const SourceMap = @import("./sourcemap/sourcemap.zig"); +const windows = std.os.windows; const Features = bun.Analytics.Features; /// Set this to false if you want to disable all uses of this panic handler. /// This is useful for testing as a crash in here will not 'panicked during a panic'. -pub const enabled = false; +pub const enabled = true; const report_base_url = "https://bun.report/"; @@ -42,11 +43,46 @@ var panic_mutex = std.Thread.Mutex{}; /// This is used to catch and handle panics triggered by the panic handler. threadlocal var panic_stage: usize = 0; -pub fn panicImpl(msg: []const u8, error_return_trace: ?*std.builtin.StackTrace, begin_addr: ?usize) noreturn { +pub const CrashReason = union(enum) { + panic: []const u8, + // "reached unreachable code" + @"unreachable", + + segmentation_fault: usize, + illegal_instruction: usize, + /// Posix-only + bus_error: usize, + /// Posix-only + floating_point_error: usize, + /// Windows-only + datatype_misalignment, + /// Windows-only + stack_overflow, + + pub fn format(self: CrashReason, comptime _: []const u8, _: std.fmt.FormatOptions, writer: anytype) !void { + switch (self) { + .panic => try writer.print("{s}", .{self.panic}), + .@"unreachable" => try writer.writeAll("reached unreachable code"), + .segmentation_fault => |addr| try writer.print("Segmentation fault at address 0x{x}", .{addr}), + .illegal_instruction => |addr| try writer.print("Illegal instruction at address 0x{x}", .{addr}), + .bus_error => |addr| try writer.print("Bus error at address 0x{x}", .{addr}), + .floating_point_error => |addr| try writer.print("Floating point error at address 0x{x}", .{addr}), + .datatype_misalignment => try writer.writeAll("Unaligned memory access"), + .stack_overflow => try writer.writeAll("Stack overflow"), + } + } +}; + +pub fn crashHandler( + reason: CrashReason, + error_return_trace: ?*std.builtin.StackTrace, + begin_addr: ?usize, +) noreturn { @setCold(true); - // std.os.abort()'s non libc version will not clear signal handlers. - comptime std.debug.assert(builtin.link_libc); + // If a segfault happens while panicking, we want it to actually segfault, not trigger + // the handler. + resetSegfaultHandler(); nosuspend switch (panic_stage) { 0 => { @@ -95,8 +131,7 @@ pub fn panicImpl(msg: []const u8, error_return_trace: ?*std.builtin.StackTrace, else => @compileError("TODO"), } - writer.writeAll(": ") catch std.os.abort(); - writer.writeAll(msg) catch std.os.abort(); + writer.print(": {}", .{reason}) catch std.os.abort(); if (bun.Output.enable_ansi_colors) { writer.writeAll(bun.Output.prettyFmt("\n", true)) catch std.os.abort(); @@ -125,7 +160,7 @@ pub fn panicImpl(msg: []const u8, error_return_trace: ?*std.builtin.StackTrace, encodeTraceString( .{ .trace = trace, - .msg = msg, + .reason = reason, .include_features = true, .action = .open_issue, }, @@ -154,7 +189,7 @@ pub fn panicImpl(msg: []const u8, error_return_trace: ?*std.builtin.StackTrace, // we're still holding the mutex but that's fine as we're going to // call abort() const stderr = std.io.getStdErr().writer(); - stderr.print("panic: {s}\n", .{msg}) catch std.os.abort(); + stderr.print("panic: {s}\n", .{reason}) catch std.os.abort(); stderr.print("panicked during a panic. Aborting.\n", .{}) catch std.os.abort(); }, 3 => { @@ -166,40 +201,19 @@ pub fn panicImpl(msg: []const u8, error_return_trace: ?*std.builtin.StackTrace, }, }; - switch (bun.Environment.os) { - .windows => { - std.os.abort(); - }, - else => { - // Parts of this is copied from std.os.abort (linux non libc path) and WTFCrash - // Cause a segfault to make sure a core dump is generated if such is enabled - - // Only one thread may proceed to the rest of abort(). - const global = struct { - var abort_entered: bool = false; - }; - while (@cmpxchgWeak(bool, &global.abort_entered, false, true, .SeqCst, .SeqCst)) |_| {} - - // Install default handler so that the tkill below will terminate. - const sigact = std.os.Sigaction{ .handler = .{ .handler = std.os.SIG.DFL }, .mask = std.os.empty_sigset, .flags = 0 }; - inline for (.{ - std.os.SIG.SEGV, - std.os.SIG.ILL, - std.os.SIG.BUS, - std.os.SIG.ABRT, - std.os.SIG.FPE, - std.os.SIG.HUP, - std.os.SIG.TERM, - }) |sig| { - std.os.sigaction(sig, &sigact, null) catch {}; - } + crash(); +} - @as(*allowzero volatile u8, @ptrFromInt(0xDEADBEEF)).* = 0; - std.os.raise(std.os.SIG.SEGV) catch {}; - @as(*allowzero volatile u8, @ptrFromInt(0)).* = 0; - std.os.exit(127); - }, - } +pub fn panicImpl(msg: []const u8, error_return_trace: ?*std.builtin.StackTrace, begin_addr: ?usize) noreturn { + @setCold(true); + crashHandler( + if (bun.strings.eqlComptime(msg, "reached unreachable code")) + .{ .@"unreachable" = {} } + else + .{ .panic = msg }, + error_return_trace, + begin_addr orelse @returnAddress(), + ); } fn panicBuiltin(msg: []const u8, error_return_trace: ?*std.builtin.StackTrace, begin_addr: ?usize) noreturn { @@ -223,9 +237,89 @@ const metadata_version_line = std.fmt.comptimePrint( }, ); +fn handleSegfaultPosix(sig: i32, info: *const std.os.siginfo_t, _: ?*const anyopaque) callconv(.C) noreturn { + resetSegfaultHandler(); + + const addr = switch (bun.Environment.os) { + .linux => @intFromPtr(info.fields.sigfault.addr), + .mac => @intFromPtr(info.addr), + else => unreachable, + }; + + crashHandler( + switch (sig) { + std.os.SIG.SEGV => .{ .segmentation_fault = addr }, + std.os.SIG.ILL => .{ .illegal_instruction = addr }, + std.os.SIG.BUS => .{ .bus_error = addr }, + std.os.SIG.FPE => .{ .floating_point_error = addr }, + + // we do not register this handler for other signals + else => unreachable, + }, + null, + @returnAddress(), + ); +} + +pub fn updatePosixSegfaultHandler(act: ?*const std.os.Sigaction) !void { + try std.os.sigaction(std.os.SIG.SEGV, act, null); + try std.os.sigaction(std.os.SIG.ILL, act, null); + try std.os.sigaction(std.os.SIG.BUS, act, null); + try std.os.sigaction(std.os.SIG.FPE, act, null); +} + +var windows_segfault_handle: ?windows.HANDLE = null; + pub fn init() void { if (!enabled) return; - // TODO: + switch (bun.Environment.os) { + .windows => { + windows_segfault_handle = windows.kernel32.AddVectoredExceptionHandler(0, handleSegfaultWindows); + }, + .mac, .linux => { + const act = std.os.Sigaction{ + .handler = .{ .sigaction = handleSegfaultPosix }, + .mask = std.os.empty_sigset, + .flags = (std.os.SA.SIGINFO | std.os.SA.RESTART | std.os.SA.RESETHAND), + }; + updatePosixSegfaultHandler(&act) catch {}; + }, + else => @compileError("TODO"), + } +} + +pub fn resetSegfaultHandler() void { + if (bun.Environment.os == .windows) { + if (windows_segfault_handle) |handle| { + std.debug.assert( + windows.kernel32.RemoveVectoredExceptionHandler(handle) != 0, + ); + windows_segfault_handle = null; + } + return; + } + var act = std.os.Sigaction{ + .handler = .{ .handler = std.os.SIG.DFL }, + .mask = std.os.empty_sigset, + .flags = 0, + }; + // To avoid a double-panic, do nothing if an error happens here. + updatePosixSegfaultHandler(&act) catch {}; +} + +pub fn handleSegfaultWindows(info: windows.EXCEPTION_POINTERS) callconv(windows.WINAPI) c_long { + resetSegfaultHandler(); + crashHandler( + switch (info.ExceptionRecord.ExceptionCode) { + windows.EXCEPTION_DATATYPE_MISALIGNMENT => .{ .datatype_misalignment = {} }, + windows.EXCEPTION_ACCESS_VIOLATION => .{ .segmentation_fault = info.ExceptionRecord.ExceptionInformation[1] }, + windows.EXCEPTION_ILLEGAL_INSTRUCTION => .{ .illegal_instruction = info.ContextRecord.getRegs().ip }, + windows.EXCEPTION_STACK_OVERFLOW => .{ .stack_overflow = {} }, + else => return windows.EXCEPTION_CONTINUE_SEARCH, + }, + null, + @intFromPtr(info.ExceptionRecord.ExceptionAddress), + ); } pub fn printMetadata(writer: anytype) !void { @@ -235,7 +329,7 @@ pub fn printMetadata(writer: anytype) !void { if (i != 0) try writer.writeAll(", "); try bun.fmt.quotedWriter(writer, arg); } - try writer.print("{}", .{bun.Analytics.Features.formatter()}); + try writer.print("\n{}", .{bun.Analytics.Features.formatter()}); if (bun.use_mimalloc) { var elapsed_msecs: usize = 0; @@ -309,29 +403,18 @@ const Platform = enum(u8) { (if (bun.Environment.baseline) "_baseline" else "")); }; +const tracestr_version: u8 = '1'; + const tracestr_header = std.fmt.comptimePrint( - "{s}/{c}{s}1", + "{s}/{c}{s}{c}", .{ bun.Environment.version_string, @intFromEnum(Platform.current), if (bun.Environment.git_sha.len > 0) bun.Environment.git_sha[0..7] else "unknown", + tracestr_version, }, ); -const EncodeOptions = struct { - trace: *std.builtin.StackTrace, - msg: ?[]const u8, - include_features: bool, - action: Action, - - const Action = enum { - /// Open a pre-filled GitHub issue with the expanded trace - open_issue, - /// View the trace with nothing else - view_trace, - }; -}; - const Address = union(enum) { unknown, known: struct { @@ -367,6 +450,20 @@ const Address = union(enum) { } }; +const EncodeOptions = struct { + trace: *std.builtin.StackTrace, + reason: CrashReason, + include_features: bool, + action: Action, + + const Action = enum { + /// Open a pre-filled GitHub issue with the expanded trace + open_issue, + /// View the trace with nothing else + view_trace, + }; +}; + fn encodeTraceString(opts: EncodeOptions, writer: anytype) !void { try writer.writeAll(report_base_url ++ tracestr_header); @@ -400,6 +497,8 @@ fn encodeTraceString(opts: EncodeOptions, writer: anytype) !void { }; }, .mac => addr: { + // This code is slightly modified from std.debug.DebugInfo.lookupModuleNameDyld + // https://github.com/ziglang/zig/blob/215de3ee67f75e2405c177b262cb5c1cd8c8e343/lib/std/debug.zig#L1783 const address = if (addr == 0) 0 else addr - 1; const image_count = std.c._dyld_image_count(); @@ -463,38 +562,108 @@ fn encodeTraceString(opts: EncodeOptions, writer: anytype) !void { break :zero_vlq vlq.bytes[0..vlq.len]; }); - if (opts.msg) |message| { - var compressed_bytes: [2048]u8 = undefined; - var len: usize = compressed_bytes.len; - const ret: bun.zlib.ReturnCode = @enumFromInt(bun.zlib.compress2(&compressed_bytes, &len, message.ptr, message.len, 9)); - const compressed = switch (ret) { - .Ok => compressed_bytes[0..len], - // Insufficient memory. - .MemError => return error.OutOfMemory, - // The buffer dest was not large enough to hold the compressed data. - .BufError => return error.NoSpaceLeft, - - // The level was not Z_DEFAULT_LEVEL, or was not between 0 and 9. - // This is technically possible but impossible because we pass 9. - .StreamError => return error.Unexpected, - else => return error.Unexpected, - }; + switch (opts.reason) { + .panic => |message| { + try writer.writeByte('0'); + + var compressed_bytes: [2048]u8 = undefined; + var len: usize = compressed_bytes.len; + const ret: bun.zlib.ReturnCode = @enumFromInt(bun.zlib.compress2(&compressed_bytes, &len, message.ptr, message.len, 9)); + const compressed = switch (ret) { + .Ok => compressed_bytes[0..len], + // Insufficient memory. + .MemError => return error.OutOfMemory, + // The buffer dest was not large enough to hold the compressed data. + .BufError => return error.NoSpaceLeft, + + // The level was not Z_DEFAULT_LEVEL, or was not between 0 and 9. + // This is technically possible but impossible because we pass 9. + .StreamError => return error.Unexpected, + else => return error.Unexpected, + }; - var b64_bytes: [2048]u8 = undefined; - if (bun.base64.encodeLen(compressed) > b64_bytes.len) { - return error.NoSpaceLeft; - } - const b64_len = bun.base64.encode(&b64_bytes, compressed); + var b64_bytes: [2048]u8 = undefined; + if (bun.base64.encodeLen(compressed) > b64_bytes.len) { + return error.NoSpaceLeft; + } + const b64_len = bun.base64.encode(&b64_bytes, compressed); - try writer.writeAll(b64_bytes[0..b64_len]); - } + try writer.writeAll(std.mem.trimRight(u8, b64_bytes[0..b64_len], "=")); + }, + .@"unreachable" => try writer.writeByte('1'), - if (opts.include_features) { - // try writer.writeAll("_"); - // TODO + .segmentation_fault => |addr| { + try writer.writeByte('2'); + try writeU64AsTwoVLQs(writer, addr); + }, + .illegal_instruction => |addr| { + try writer.writeByte('3'); + try writeU64AsTwoVLQs(writer, addr); + }, + .bus_error => |addr| { + try writer.writeByte('4'); + try writeU64AsTwoVLQs(writer, addr); + }, + .floating_point_error => |addr| { + try writer.writeByte('5'); + try writeU64AsTwoVLQs(writer, addr); + }, + + .datatype_misalignment => try writer.writeByte('6'), + .stack_overflow => try writer.writeByte('7'), } + // TODO: not sure if worth adding since URLs are already quite lengthy + // if (opts.include_features) { + // try writer.writeAll("_"); + // TODO + // } + if (opts.action == .view_trace) { try writer.writeAll("/view"); } } + +fn writeU64AsTwoVLQs(writer: anytype, addr: usize) !void { + const first = SourceMap.encodeVLQ(@intCast((addr & 0xFFFFFFFF00000000) >> 32)); + const second = SourceMap.encodeVLQ(@intCast(addr & 0xFFFFFFFF)); + try first.writeTo(writer); + try second.writeTo(writer); +} + +fn crash() noreturn { + switch (bun.Environment.os) { + .windows => { + std.os.abort(); + }, + else => { + // Parts of this is copied from std.os.abort (linux non libc path) and WTFCrash + // Cause a segfault to make sure a core dump is generated if such is enabled + + // Only one thread may proceed to the rest of abort(). + const global = struct { + var abort_entered: bool = false; + }; + while (@cmpxchgWeak(bool, &global.abort_entered, false, true, .SeqCst, .SeqCst)) |_| {} + + // Install default handler so that the tkill below will terminate. + const sigact = std.os.Sigaction{ .handler = .{ .handler = std.os.SIG.DFL }, .mask = std.os.empty_sigset, .flags = 0 }; + inline for (.{ + std.os.SIG.SEGV, + std.os.SIG.ILL, + std.os.SIG.BUS, + std.os.SIG.ABRT, + std.os.SIG.FPE, + std.os.SIG.HUP, + std.os.SIG.TERM, + }) |sig| { + std.os.sigaction(sig, &sigact, null) catch {}; + } + + @as(*allowzero volatile u8, @ptrFromInt(0xDEADBEEF)).* = 0; + std.os.raise(std.os.SIG.SEGV) catch {}; + @as(*allowzero volatile u8, @ptrFromInt(0)).* = 0; + std.os.exit(127); + }, + } +} From 3b33c49b927aceab7a7e8d6947c5f7df0a5fd889 Mon Sep 17 00:00:00 2001 From: dave caruso Date: Wed, 10 Apr 2024 06:48:20 -0700 Subject: [PATCH 04/26] linux things --- src/panic_handler.zig | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/panic_handler.zig b/src/panic_handler.zig index 80b7088a826272..e7cda515361ae0 100644 --- a/src/panic_handler.zig +++ b/src/panic_handler.zig @@ -6,7 +6,7 @@ //! //! - What version and commit of Bun captured the backtrace. //! - The platform the backtrace was captured on. -//! - The list of addresses ready to be remapped. +//! - The list of addresses with ASLR removed, ready to be remapped. //! - If panicking, the message that was panicked with. //! - List of feature-flags that were marked. //! @@ -534,6 +534,8 @@ fn encodeTraceString(opts: EncodeOptions, writer: anytype) !void { // To remap this, `llvm-symbolizer --obj bun-with-symbols --relative-address 0x123456` const relative_address: i32 = @intCast(stable_address - @as(isize, @intCast(base_address))); + if (relative_address < 0) break; + const object = if (i == 0) null // zero is the main binary else @@ -551,7 +553,9 @@ fn encodeTraceString(opts: EncodeOptions, writer: anytype) !void { break :addr .{ .unknown = {} }; }, - else => @compileError("TODO"), + else => { + // + } }; try address.writeEncoded(writer); From 0b6b86540be3e421cde90790559a8b15f582da47 Mon Sep 17 00:00:00 2001 From: dave caruso Date: Wed, 10 Apr 2024 07:07:23 -0700 Subject: [PATCH 05/26] linux things --- src/panic_handler.zig | 40 ++++++++++++++++++++++++++++++++++++---- 1 file changed, 36 insertions(+), 4 deletions(-) diff --git a/src/panic_handler.zig b/src/panic_handler.zig index e7cda515361ae0..d5e047abd4a0bc 100644 --- a/src/panic_handler.zig +++ b/src/panic_handler.zig @@ -553,9 +553,41 @@ fn encodeTraceString(opts: EncodeOptions, writer: anytype) !void { break :addr .{ .unknown = {} }; }, - else => { - // - } + else => addr: { + // This code is slightly modified from std.debug.DebugInfo.lookupModuleDl + // https://github.com/ziglang/zig/blob/215de3ee67f75e2405c177b262cb5c1cd8c8e343/lib/std/debug.zig#L2024 + var ctx: struct { + // Input + address: usize, + i: usize = 0, + // Output + result: Address = .{ .unknown = {} }, + } = .{ .address = addr -| 1 }; + const CtxTy = @TypeOf(ctx); + + std.os.dl_iterate_phdr(&ctx, error{Found}, struct { + fn callback(info: *std.os.dl_phdr_info, _: usize, context: *CtxTy) !void { + defer context.i += 1; + if (context.address < info.dlpi_addr) return; + const phdrs = info.dlpi_phdr[0..info.dlpi_phnum]; + for (phdrs) |*phdr| { + if (phdr.p_type != std.elf.PT_LOAD) continue; + + // Overflowing addition is used to handle the case of VSDOs + // having a p_vaddr = 0xffffffffff700000 + const seg_start = info.dlpi_addr +% phdr.p_vaddr; + const seg_end = seg_start + phdr.p_memsz; + if (context.address >= seg_start and context.address < seg_end) { + const name = bun.sliceTo(info.dlpi_name, 0) orelse ""; + std.debug.print("\nhi {d}, {s}, base = 0x{x}, ptr = 0x{x}", .{ context.i, name, info.dlpi_addr, context.address }); + return error.Found; + } + } + } + }.callback) catch {}; + + break :addr ctx.result; + }, }; try address.writeEncoded(writer); @@ -667,7 +699,7 @@ fn crash() noreturn { @as(*allowzero volatile u8, @ptrFromInt(0xDEADBEEF)).* = 0; std.os.raise(std.os.SIG.SEGV) catch {}; @as(*allowzero volatile u8, @ptrFromInt(0)).* = 0; - std.os.exit(127); + std.c._exit(127); }, } } From a9912a591818a832bcf61f15dbb8b3b1c4b68af7 Mon Sep 17 00:00:00 2001 From: dave caruso Date: Thu, 11 Apr 2024 22:37:43 -0700 Subject: [PATCH 06/26] more work on the crash handler --- build.zig | 2 +- packages/bun-internal-test/src/banned.json | 3 +- src/bun.js/api/BunObject.zig | 9 +- src/bun.js/bindings/bindings.zig | 4 +- src/bun.js/bindings/exports.zig | 2 - src/bun.js/javascript.zig | 8 +- src/bun.zig | 47 +- src/bundler.zig | 1 - src/bundler/bundle_v2.zig | 2 - src/cli.zig | 24 +- src/cli/run_command.zig | 34 +- src/{panic_handler.zig => crash_handler.zig} | 479 ++++++++++++-- src/fmt.zig | 19 + src/http.zig | 7 +- src/install/install.zig | 14 +- src/install/lockfile.zig | 9 +- src/install/migration.zig | 6 +- src/main.zig | 7 +- src/main_wasm.zig | 5 +- src/old_crash_reporter.zig | 66 -- src/old_panic_handler.zig | 58 -- src/report.zig | 633 ------------------- 22 files changed, 553 insertions(+), 886 deletions(-) rename src/{panic_handler.zig => crash_handler.zig} (56%) delete mode 100644 src/old_crash_reporter.zig delete mode 100644 src/old_panic_handler.zig delete mode 100644 src/report.zig diff --git a/build.zig b/build.zig index 5a3eb7868ad86a..3db9b5a54253ee 100644 --- a/build.zig +++ b/build.zig @@ -146,7 +146,7 @@ const fs = std.fs; pub fn build(b: *Build) !void { build_(b) catch |err| { if (@errorReturnTrace()) |trace| { - std.debug.dumpStackTrace(trace.*); + (std.debug).dumpStackTrace(trace.*); } return err; diff --git a/packages/bun-internal-test/src/banned.json b/packages/bun-internal-test/src/banned.json index 2689404af4eb87..4e09fb463c3cb3 100644 --- a/packages/bun-internal-test/src/banned.json +++ b/packages/bun-internal-test/src/banned.json @@ -1,3 +1,4 @@ { - "std.debug.assert": "Use bun.assert instead" + "std.debug.assert": "Use bun.assert instead", + "std.debug.dumpStackTrace": "Use bun.handleErrorReturnTrace instead" } diff --git a/src/bun.js/api/BunObject.zig b/src/bun.js/api/BunObject.zig index 2d53cf7255108d..159f3255274c6a 100644 --- a/src/bun.js/api/BunObject.zig +++ b/src/bun.js/api/BunObject.zig @@ -3371,7 +3371,7 @@ const UnsafeObject = struct { pub fn crashByPanic(_: *JSC.JSGlobalObject, _: *JSC.CallFrame) callconv(.C) JSC.JSValue { // std.debug.panic("invoked crashByPanic() handler", .{}); - bun.panic_handler.panicImpl("invoked crashByPanic() handler", null, null); + bun.crash_handler.panicImpl("invoked crashByPanic() handler", null, null); } pub fn crashByUnreachable(_: *JSC.JSGlobalObject, _: *JSC.CallFrame) callconv(.C) JSC.JSValue { @@ -3389,8 +3389,11 @@ const UnsafeObject = struct { } pub fn crashByCallGlobalError(_: *JSC.JSGlobalObject, _: *JSC.CallFrame) callconv(.C) JSC.JSValue { - const Reporter = @import("../../report.zig"); - Reporter.globalError(error.SegfaultTest, null); + bun.crash_handler.handleRootError(error.Test, null); + } + + pub fn crashOutOfMemory(_: *JSC.JSGlobalObject, _: *JSC.CallFrame) callconv(.C) JSC.JSValue { + bun.outOfMemory(); } pub fn arrayBufferToString( diff --git a/src/bun.js/bindings/bindings.zig b/src/bun.js/bindings/bindings.zig index 2793faff49aa1a..a00235d193da26 100644 --- a/src/bun.js/bindings/bindings.zig +++ b/src/bun.js/bindings/bindings.zig @@ -1897,8 +1897,8 @@ pub fn NewGlobalObject(comptime Type: type) type { } Output.flush(); - const Reporter = @import("../../report.zig"); - Reporter.fatal(null, "A C++ exception occurred"); + + @panic("A C++ exception occurred"); } }; } diff --git a/src/bun.js/bindings/exports.zig b/src/bun.js/bindings/exports.zig index 6b87878ab9dd95..4b40aff2f4f156 100644 --- a/src/bun.js/bindings/exports.zig +++ b/src/bun.js/bindings/exports.zig @@ -25,7 +25,6 @@ const Exception = JSC.Exception; const JSModuleLoader = JSC.JSModuleLoader; const Microtask = JSC.Microtask; -const Backtrace = @import("../../old_crash_reporter.zig"); const JSPrinter = bun.js_printer; const JSLexer = bun.js_lexer; const typeBaseName = @import("../../meta.zig").typeBaseName; @@ -49,7 +48,6 @@ pub const ZigGlobalObject = extern struct { worker_ptr: ?*anyopaque, ) *JSGlobalObject { const global = shim.cppFn("create", .{ console, context_id, mini_mode, eval_mode, worker_ptr }); - Backtrace.reloadHandlers() catch unreachable; return global; } diff --git a/src/bun.js/javascript.zig b/src/bun.js/javascript.zig index c1b2ca7f5a6430..87db8f77ac1bd6 100644 --- a/src/bun.js/javascript.zig +++ b/src/bun.js/javascript.zig @@ -850,7 +850,11 @@ pub const VirtualMachine = struct { Output.debug("Reloading...", .{}); if (this.hot_reload == .watch) { Output.flush(); - bun.reloadProcess(bun.default_allocator, !strings.eqlComptime(this.bundler.env.get("BUN_CONFIG_NO_CLEAR_TERMINAL_ON_RELOAD") orelse "0", "true")); + bun.reloadProcess( + bun.default_allocator, + !strings.eqlComptime(this.bundler.env.get("BUN_CONFIG_NO_CLEAR_TERMINAL_ON_RELOAD") orelse "0", "true"), + false, + ); } if (!strings.eqlComptime(this.bundler.env.get("BUN_CONFIG_NO_CLEAR_TERMINAL_ON_RELOAD") orelse "0", "true")) { @@ -3462,7 +3466,7 @@ pub fn NewHotReloader(comptime Ctx: type, comptime EventLoopType: type, comptime if (comptime Ctx == ImportWatcher) { this.reloader.ctx.rareData().closeAllListenSocketsForWatchMode(); } - bun.reloadProcess(bun.default_allocator, clear_screen); + bun.reloadProcess(bun.default_allocator, clear_screen, false); unreachable; } diff --git a/src/bun.zig b/src/bun.zig index 4371e4697a032f..6b62226227a0fa 100644 --- a/src/bun.zig +++ b/src/bun.zig @@ -1534,7 +1534,7 @@ pub const failing_allocator = std.mem.Allocator{ .ptr = undefined, .vtable = &.{ var __reload_in_progress__ = std.atomic.Value(bool).init(false); threadlocal var __reload_in_progress__on_current_thread = false; -fn isProcessReloadInProgressOnAnotherThread() bool { +pub fn isProcessReloadInProgressOnAnotherThread() bool { @fence(.Acquire); return __reload_in_progress__.load(.Monotonic) and !__reload_in_progress__on_current_thread; } @@ -1562,18 +1562,26 @@ pub noinline fn maybeHandlePanicDuringProcessReload() void { } } -/// Reload Bun's process +/// Reload Bun's process. This clones envp, argv, and gets the current +/// executable path. /// -/// This clones envp, argv, and gets the current executable path +/// On posix, this overwrites the current process with the new process using +/// `execve`. On Windows, we dont have this API, instead relying on a dummy +/// parent process that we can signal via a special exit code. /// -/// Overwrites the current process with the new process -/// -/// Must be able to allocate memory. malloc is not signal safe, but it's +/// Must be able to allocate memory. `malloc` is not signal safe, but it's /// best-effort. Not much we can do if it fails. +/// +/// Note that this function is called during the crash handler, in which it is +/// passed true to `may_return`. If failure occurs, one line of standard error +/// is printed and then this returns void. If `may_return == false`, then a +/// panic will occur on failure. The crash handler will not schedule two reloads +/// at once. pub fn reloadProcess( allocator: std.mem.Allocator, clear_terminal: bool, -) noreturn { + comptime may_return: bool, +) if (may_return) void else noreturn { __reload_in_progress__.store(true, .Monotonic); __reload_in_progress__on_current_thread = true; @@ -1582,6 +1590,7 @@ pub fn reloadProcess( Output.disableBuffering(); Output.resetTerminalAll(); } + Output.Source.Stdio.restore(); const bun = @This(); @@ -1591,11 +1600,20 @@ pub fn reloadProcess( const rc = bun.windows.TerminateProcess(@ptrFromInt(std.math.maxInt(usize)), win32.watcher_reload_exit); if (rc == 0) { const err = bun.windows.GetLastError(); + if (may_return) { + Output.errGeneric("Failed to reload process: {s}", .{@tagName(err)}); + return; + } Output.panic("Error while reloading process: {s}", .{@tagName(err)}); } else { + if (may_return) { + Output.errGeneric("Failed to reload process", .{}); + return; + } Output.panic("Unexpected error while reloading process\n", .{}); } } + const PosixSpawn = posix.spawn; const dupe_argv = allocator.allocSentinel(?[*:0]const u8, bun.argv.len, null) catch unreachable; for (bun.argv, dupe_argv) |src, *dest| { @@ -1642,9 +1660,17 @@ pub fn reloadProcess( ) catch unreachable; switch (PosixSpawn.spawnZ(exec_path, actions, attrs, @as([*:null]?[*:0]const u8, @ptrCast(newargv)), @as([*:null]?[*:0]const u8, @ptrCast(envp)))) { .err => |err| { + if (may_return) { + Output.errGeneric("Failed to reload process: {s}", .{@tagName(err.getErrno())}); + return; + } Output.panic("Unexpected error while reloading: {d} {s}", .{ err.errno, @tagName(err.getErrno()) }); }, .result => |_| { + if (may_return) { + Output.errGeneric("Failed to reload process", .{}); + return; + } Output.panic("Unexpected error while reloading: posix_spawn returned a result", .{}); }, } @@ -1659,6 +1685,10 @@ pub fn reloadProcess( newargv, envp, ); + if (may_return) { + Output.errGeneric("Failed to reload process: {s}", .{@errorName(err)}); + return; + } Output.panic("Unexpected error while reloading: {s}", .{@errorName(err)}); } else { @compileError("unsupported platform for reloadProcess"); @@ -3067,7 +3097,8 @@ pub fn SliceIterator(comptime T: type) type { pub const Futex = @import("./futex.zig"); -pub const panic_handler = @import("panic_handler.zig"); +pub const crash_handler = @import("crash_handler.zig"); +pub const handleErrorReturnTrace = crash_handler.handleErrorReturnTrace; noinline fn assertionFailure() noreturn { if (@inComptime()) { diff --git a/src/bundler.zig b/src/bundler.zig index 8383c92d7fe815..6dff52b00084ea 100644 --- a/src/bundler.zig +++ b/src/bundler.zig @@ -46,7 +46,6 @@ const NodeFallbackModules = @import("./node_fallbacks.zig"); const CacheEntry = @import("./cache.zig").FsCacheEntry; const Analytics = @import("./analytics/analytics_thread.zig"); const URL = @import("./url.zig").URL; -const Report = @import("./report.zig"); const Linker = linker.Linker; const Resolver = _resolver.Resolver; const TOML = @import("./toml/toml_parser.zig").TOML; diff --git a/src/bundler/bundle_v2.zig b/src/bundler/bundle_v2.zig index 44b394bfcc0cea..e3fb47ef3b5180 100644 --- a/src/bundler/bundle_v2.zig +++ b/src/bundler/bundle_v2.zig @@ -73,7 +73,6 @@ const DebugOptions = @import("../cli.zig").Command.DebugOptions; const ThreadPoolLib = @import("../thread_pool.zig"); const ThreadlocalArena = @import("../mimalloc_arena.zig").Arena; const BabyList = @import("../baby_list.zig").BabyList; -const panicky = @import("../panic_handler.zig"); const Fs = @import("../fs.zig"); const schema = @import("../api/schema.zig"); const Api = schema.Api; @@ -97,7 +96,6 @@ const NodeFallbackModules = @import("../node_fallbacks.zig"); const CacheEntry = @import("../cache.zig").Fs.Entry; const Analytics = @import("../analytics/analytics_thread.zig"); const URL = @import("../url.zig").URL; -const Report = @import("../report.zig"); const Linker = linker.Linker; const Resolver = _resolver.Resolver; const TOML = @import("../toml/toml_parser.zig").TOML; diff --git a/src/cli.zig b/src/cli.zig index 970e3b3e5f9ac7..89c3dae408b126 100644 --- a/src/cli.zig +++ b/src/cli.zig @@ -20,6 +20,7 @@ const js_printer = bun.js_printer; const js_ast = bun.JSAst; const linker = @import("linker.zig"); const RegularExpression = bun.RegularExpression; +const builtin = @import("builtin"); const debug = Output.scoped(.CLI, true); @@ -41,7 +42,6 @@ const Router = @import("./router.zig"); const MacroMap = @import("./resolver/package_json.zig").MacroMap; const TestCommand = @import("./cli/test_command.zig").TestCommand; -const Reporter = @import("./report.zig"); pub var start_time: i128 = undefined; const Bunfig = @import("./bunfig.zig").Bunfig; @@ -61,11 +61,7 @@ pub const Cli = struct { Command.start(allocator, log) catch |err| { log.printForLogLevel(Output.errorWriter()) catch {}; - if (@errorReturnTrace()) |trace| { - std.debug.dumpStackTrace(trace.*); - } - - Reporter.globalError(err, null); + bun.crash_handler.handleRootError(err, @errorReturnTrace()); }; } @@ -153,7 +149,10 @@ pub const Arguments = struct { clap.parseParam("-c, --config ? Specify path to Bun config file. Default $cwd/bunfig.toml") catch unreachable, clap.parseParam("-h, --help Display this menu and exit") catch unreachable, clap.parseParam("...") catch unreachable, - }; + } ++ if (builtin.have_error_return_tracing) [_]ParamType{ + // This will print more error return traces, as a debug aid + clap.parseParam("--verbose-error-trace") catch unreachable, + } else [_]ParamType{}; const transpiler_params_ = [_]ParamType{ clap.parseParam("--main-fields ... Main fields to lookup in package.json. Defaults to --target dependent") catch unreachable, @@ -411,6 +410,12 @@ pub const Arguments = struct { } } + if (builtin.have_error_return_tracing) { + if (args.flag("--verbose-error-trace")) { + bun.crash_handler.verbose_error_trace = true; + } + } + var cwd: []u8 = undefined; if (args.option("--cwd")) |cwd_| { cwd = brk: { @@ -1964,6 +1969,8 @@ pub const Command = struct { ctx.*, absolute_script_path.?, ) catch |err| { + bun.handleErrorReturnTrace(err, @errorReturnTrace()); + if (Output.enable_ansi_colors) { ctx.log.printForLogLevelWithEnableAnsiColors(Output.errorWriter(), true) catch {}; } else { @@ -1974,9 +1981,6 @@ pub const Command = struct { std.fs.path.basename(file_path), @errorName(err), }); - if (@errorReturnTrace()) |trace| { - std.debug.dumpStackTrace(trace.*); - } Global.exit(1); }; return true; diff --git a/src/cli/run_command.zig b/src/cli/run_command.zig index e2d0ff06929864..24e6075a5f7bb8 100644 --- a/src/cli/run_command.zig +++ b/src/cli/run_command.zig @@ -487,9 +487,6 @@ pub const RunCommand = struct { fn runBinaryGenericError(executable: []const u8, silent: bool, err: bun.sys.Error) noreturn { if (!silent) { Output.prettyErrorln("error: Failed to run \"{s}\" due to:\n{}", .{ basenameOrBun(executable), err.withPath(executable) }); - if (@errorReturnTrace()) |trace| { - std.debug.dumpStackTrace(trace.*); - } } Global.exit(1); @@ -533,6 +530,8 @@ pub const RunCommand = struct { .loop = JSC.EventLoopHandle.init(JSC.MiniEventLoop.initGlobal(env)), } else {}, }) catch |err| { + bun.handleErrorReturnTrace(err, @errorReturnTrace()); + // an error occurred before the process was spawned print_error: { if (!silent) { @@ -557,9 +556,6 @@ pub const RunCommand = struct { } Output.prettyErrorln("error: Failed to run \"{s}\" due to {s}", .{ basenameOrBun(executable), @errorName(err) }); - if (@errorReturnTrace()) |trace| { - std.debug.dumpStackTrace(trace.*); - } } } Global.exit(1); @@ -600,9 +596,6 @@ pub const RunCommand = struct { basenameOrBun(executable), exit_code.signal.name() orelse "unknown", }); - if (@errorReturnTrace()) |trace| { - std.debug.dumpStackTrace(trace.*); - } } Output.flush(); @@ -640,10 +633,6 @@ pub const RunCommand = struct { code, }); } - - if (@errorReturnTrace()) |trace| { - std.debug.dumpStackTrace(trace.*); - } } } @@ -1320,15 +1309,14 @@ pub const RunCommand = struct { (script_name_to_search.len == 2 and @as(u16, @bitCast(script_name_to_search[0..2].*)) == @as(u16, @bitCast([_]u8{ '.', '/' })))) { Run.boot(ctx, ".") catch |err| { + bun.handleErrorReturnTrace(err, @errorReturnTrace()); + ctx.log.printForLogLevel(Output.errorWriter()) catch {}; Output.prettyErrorln("error: Failed to run {s} due to error {s}", .{ script_name_to_search, @errorName(err), }); - if (@errorReturnTrace()) |trace| { - std.debug.dumpStackTrace(trace.*); - } Global.exit(1); }; return true; @@ -1416,15 +1404,14 @@ pub const RunCommand = struct { Global.configureAllocator(.{ .long_running = true }); const out_path = ctx.allocator.dupe(u8, file_path) catch unreachable; Run.boot(ctx, out_path) catch |err| { + bun.handleErrorReturnTrace(err, @errorReturnTrace()); + ctx.log.printForLogLevel(Output.errorWriter()) catch {}; Output.prettyErrorln("error: Failed to run {s} due to error {s}", .{ std.fs.path.basename(file_path), @errorName(err), }); - if (@errorReturnTrace()) |trace| { - std.debug.dumpStackTrace(trace.*); - } Global.exit(1); }; @@ -1518,15 +1505,14 @@ pub const RunCommand = struct { (script_name_to_search.len > 2 and script_name_to_search[0] == '.' and script_name_to_search[1] == '/')) { Run.boot(ctx, ctx.allocator.dupe(u8, script_name_to_search) catch unreachable) catch |err| { + bun.handleErrorReturnTrace(err, @errorReturnTrace()); + ctx.log.printForLogLevel(Output.errorWriter()) catch {}; Output.prettyErrorln("error: Failed to run {s} due to error {s}", .{ std.fs.path.basename(script_name_to_search), @errorName(err), }); - if (@errorReturnTrace()) |trace| { - std.debug.dumpStackTrace(trace.*); - } Global.exit(1); }; } @@ -1553,9 +1539,7 @@ pub const RunCommand = struct { std.fs.path.basename(script_name_to_search), @errorName(err), }); - if (@errorReturnTrace()) |trace| { - std.debug.dumpStackTrace(trace.*); - } + bun.handleErrorReturnTrace(err, @errorReturnTrace()); Global.exit(1); }; return true; diff --git a/src/panic_handler.zig b/src/crash_handler.zig similarity index 56% rename from src/panic_handler.zig rename to src/crash_handler.zig index 80b7088a826272..6d632695e4814d 100644 --- a/src/panic_handler.zig +++ b/src/crash_handler.zig @@ -14,15 +14,21 @@ //! versions of all debug symbols for all versions of Bun. Hosting this keeps //! users from having to download symbols, which can be very large. //! +//! The remapper is open source: https://github.com/oven-sh/bun-report //! -//! Source code: https://github.com/oven-sh/bun-report +//! A lot of this handler is based on the Zig Standard Library implementation +//! for std.debug.panicImpl and their code for gathering backtraces. const std = @import("std"); const bun = @import("root").bun; const builtin = @import("builtin"); const mimalloc = @import("allocators/mimalloc.zig"); const SourceMap = @import("./sourcemap/sourcemap.zig"); const windows = std.os.windows; +const Output = bun.Output; +const Global = bun.Global; const Features = bun.Analytics.Features; +const debug = std.debug; +const dumpStackTrace = debug.dumpStackTrace; /// Set this to false if you want to disable all uses of this panic handler. /// This is useful for testing as a crash in here will not 'panicked during a panic'. @@ -30,6 +36,8 @@ pub const enabled = true; const report_base_url = "https://bun.report/"; +/// Only print the `Bun has crashed` message once. Once this is true, control +/// flow is not returned to the main application. var has_printed_message = false; /// Non-zero whenever the program triggered a panic. @@ -43,13 +51,17 @@ var panic_mutex = std.Thread.Mutex{}; /// This is used to catch and handle panics triggered by the panic handler. threadlocal var panic_stage: usize = 0; +/// This structure and formatter must be kept in sync with `bun-report`'s decoder. pub const CrashReason = union(enum) { + /// From @panic() panic: []const u8, - // "reached unreachable code" + + /// "reached unreachable code" @"unreachable", segmentation_fault: usize, illegal_instruction: usize, + /// Posix-only bus_error: usize, /// Posix-only @@ -59,6 +71,9 @@ pub const CrashReason = union(enum) { /// Windows-only stack_overflow, + /// Either `main` returned an error, or somewhere else in the code a trace string is printed. + zig_error: anyerror, + pub fn format(self: CrashReason, comptime _: []const u8, _: std.fmt.FormatOptions, writer: anytype) !void { switch (self) { .panic => try writer.print("{s}", .{self.panic}), @@ -69,12 +84,15 @@ pub const CrashReason = union(enum) { .floating_point_error => |addr| try writer.print("Floating point error at address 0x{x}", .{addr}), .datatype_misalignment => try writer.writeAll("Unaligned memory access"), .stack_overflow => try writer.writeAll("Stack overflow"), + .zig_error => |err| try writer.print("error.{s}", .{@errorName(err)}), } } }; +/// This function is invoked when a crash happpens. A crash is classified in `CrashReason`. pub fn crashHandler( reason: CrashReason, + // TODO: if both of these are specified, what is supposed to happen? error_return_trace: ?*std.builtin.StackTrace, begin_addr: ?usize, ) noreturn { @@ -86,6 +104,8 @@ pub fn crashHandler( nosuspend switch (panic_stage) { 0 => { + bun.maybeHandlePanicDuringProcessReload(); + panic_stage = 1; _ = panicking.fetchAdd(1, .SeqCst); @@ -93,25 +113,46 @@ pub fn crashHandler( panic_mutex.lock(); defer panic_mutex.unlock(); - bun.Output.flush(); - - const writer = bun.Output.errorWriter(); + const writer = Output.errorWriter(); + + // The format of the panic trace is slightly different in debug + // builds Mainly, we demangle the backtrace immediately instead + // of using a trace string. + // + // To make the release-mode behavior easier to demo, debug mode + // checks for this CLI flag. + const debug_trace = bun.Environment.isDebug and check_flag: { + for (bun.argv) |arg| { + if (bun.strings.eqlComptime(arg, "--debug-crash-handler-use-trace-string")) { + break :check_flag false; + } + } + break :check_flag true; + }; if (!has_printed_message) { has_printed_message = true; + + Output.flush(); + Output.Source.Stdio.restore(); + writer.writeAll("=" ** 60 ++ "\n") catch std.os.abort(); - bun.Output.err("oh no", - \\Bun has crashed. This indicates a bug in Bun, and - \\should be reported as a GitHub issue. - \\ - \\ - , .{}); - bun.Output.flush(); + + // Omit this blurb in debug builds because it is noise + if (!debug_trace) { + Output.err("oh no", + \\Bun has crashed. This indicates a bug in Bun, and + \\should be reported as a GitHub issue. + \\ + \\ + , .{}); + } + Output.flush(); printMetadata(writer) catch std.os.abort(); } - if (bun.Output.enable_ansi_colors) { - writer.writeAll(bun.Output.prettyFmt("", true)) catch std.os.abort(); + if (Output.enable_ansi_colors) { + writer.writeAll(Output.prettyFmt("", true)) catch std.os.abort(); } writer.writeAll("panic") catch std.os.abort(); @@ -133,8 +174,8 @@ pub fn crashHandler( writer.print(": {}", .{reason}) catch std.os.abort(); - if (bun.Output.enable_ansi_colors) { - writer.writeAll(bun.Output.prettyFmt("\n", true)) catch std.os.abort(); + if (Output.enable_ansi_colors) { + writer.writeAll(Output.prettyFmt("\n", true)) catch std.os.abort(); } else { writer.writeAll("\n") catch std.os.abort(); } @@ -143,45 +184,67 @@ pub fn crashHandler( var trace_buf: std.builtin.StackTrace = undefined; // If a trace was not provided, compute one now - const trace = (error_return_trace orelse get_backtrace: { + const trace = error_return_trace orelse get_backtrace: { trace_buf = std.builtin.StackTrace{ .index = 0, .instruction_addresses = &addr_buf, }; std.debug.captureStackTrace(begin_addr orelse @returnAddress(), &trace_buf); break :get_backtrace &trace_buf; - }); + }; - writer.writeAll("Please report this panic as a GitHub issue using this link:\n") catch std.os.abort(); - if (bun.Output.enable_ansi_colors) { - writer.print(bun.Output.prettyFmt("", true), .{}) catch std.os.abort(); + if (debug_trace) { + // TODO: On Windows, there are sometimes issues remapping information here: + dumpStackTrace(trace.*); + } else { + writer.writeAll("Please report this panic as a GitHub issue using this link:\n") catch std.os.abort(); + if (Output.enable_ansi_colors) { + writer.print(Output.prettyFmt("", true), .{}) catch std.os.abort(); + } } encodeTraceString( .{ .trace = trace, .reason = reason, - .include_features = true, .action = .open_issue, }, writer, ) catch std.os.abort(); - if (bun.Output.enable_ansi_colors) { - writer.writeAll(bun.Output.prettyFmt("\n", true)) catch std.os.abort(); + if (Output.enable_ansi_colors) { + writer.writeAll(Output.prettyFmt("\n", true)) catch std.os.abort(); } else { writer.writeAll("\n") catch std.os.abort(); } - bun.Output.flush(); + Output.flush(); } + // Be aware that this function only lets one thread return from it. + // This is important sot hat we do not try to run the reload logic twice. waitForOtherThreadToFinishPanicking(); + + if (bun.auto_reload_on_crash and + // Do not reload if the panic arised FROM the reload function. + !bun.isProcessReloadInProgressOnAnotherThread()) + { + // attempt to prevent a double panic + bun.auto_reload_on_crash = false; + + Output.prettyErrorln("--- Bun is auto-restarting due to crash [time: {d}] ---", .{ + @max(std.time.milliTimestamp(), 0), + }); + Output.flush(); + + // It is important to be aware that this function *can* panic. + bun.reloadProcess(bun.default_allocator, false, true); + } }, inline 1, 2 => |t| { if (t == 1) { panic_stage = 2; - bun.Output.flush(); + Output.flush(); } panic_stage = 3; @@ -204,6 +267,292 @@ pub fn crashHandler( crash(); } +/// This is called when `main` returns a Zig error. +/// We don't want to treat it as a crash under certain error codes. +pub fn handleRootError(err: anyerror, error_return_trace: ?*std.builtin.StackTrace) noreturn { + if (bun.Environment.isDebug) { + if (bun.verbose_error_trace) { + if (error_return_trace) |trace| { + Output.errGeneric("CLI returned {s}", .{@errorName(err)}); + std.debug.dumpStackTrace(trace.*); + Global.exit(1); + } else { + Output.debugWarn("error return trace not available for the following error", .{}); + } + } + } + + const always_show_trace = bun.Environment.isDebug; + + switch (err) { + error.OutOfMemory => bun.outOfMemory(), + + error.InvalidArgument, + error.@"Invalid Bunfig", + => if (!always_show_trace) Global.exit(1), + + error.SyntaxError => { + Output.err("SyntaxError", "An error occurred while parsing code", .{}); + if (!always_show_trace) Global.exit(1); + }, + + error.CurrentWorkingDirectoryUnlinked => { + Output.errGeneric( + "The current working directory was deleted, so that command didn't work. Please cd into a different directory and try again.", + .{}, + ); + if (!always_show_trace) Global.exit(1); + }, + + error.SystemFdQuotaExceeded => { + if (comptime bun.Environment.isPosix) { + const limit = if (std.os.getrlimit(.NOFILE)) |limit| limit.cur else |_| null; + if (comptime bun.Environment.isMac) { + Output.prettyError( + \\error: Your computer ran out of file descriptors (SystemFdQuotaExceeded) + \\ + \\Current limit: {d} + \\ + \\To fix this, try running: + \\ + \\ sudo launchctl limit maxfiles 2147483646 + \\ ulimit -n 2147483646 + \\ + \\That will only work until you reboot. + \\ + , + .{ + bun.fmt.nullableFallback(limit, ""), + }, + ); + } else { + Output.prettyError( + \\ + \\error: Your computer ran out of file descriptors (SystemFdQuotaExceeded) + \\ + \\Current limit: {d} + \\ + \\To fix this, try running: + \\ + \\ sudo echo -e "\nfs.file-max=2147483646\n" >> /etc/sysctl.conf + \\ sudo sysctl -p + \\ ulimit -n 2147483646 + \\ + , + .{ + bun.fmt.nullableFallback(limit, ""), + }, + ); + + if (bun.getenvZ("USER")) |user| { + if (user.len > 0) { + Output.prettyError( + \\ + \\If that still doesn't work, you may need to add these lines to /etc/security/limits.conf: + \\ + \\ {s} soft nofile 2147483646 + \\ {s} hard nofile 2147483646 + \\ + , + .{ user, user }, + ); + } + } + } + } else { + Output.prettyError( + \\error: Your computer ran out of file descriptors (SystemFdQuotaExceeded) + , + .{}, + ); + } + + if (!always_show_trace) Global.exit(1); + }, + + error.ProcessFdQuotaExceeded => { + if (comptime bun.Environment.isPosix) { + const limit = if (std.os.getrlimit(.NOFILE)) |limit| limit.cur else |_| null; + if (comptime bun.Environment.isMac) { + Output.prettyError( + \\ + \\error: bun ran out of file descriptors (ProcessFdQuotaExceeded) + \\ + \\Current limit: {d} + \\ + \\To fix this, try running: + \\ + \\ ulimit -n 2147483646 + \\ + \\You may also need to run: + \\ + \\ sudo launchctl limit maxfiles 2147483646 + \\ + , + .{ + bun.fmt.nullableFallback(limit, ""), + }, + ); + } else { + Output.prettyError( + \\ + \\error: bun ran out of file descriptors (ProcessFdQuotaExceeded) + \\ + \\Current limit: {d} + \\ + \\To fix this, try running: + \\ + \\ ulimit -n 2147483646 + \\ + \\That will only work for the current shell. To fix this for the entire system, run: + \\ + \\ sudo echo -e "\nfs.file-max=2147483646\n" >> /etc/sysctl.conf + \\ sudo sysctl -p + \\ + , + .{ + bun.fmt.nullableFallback(limit, ""), + }, + ); + + if (bun.getenvZ("USER")) |user| { + if (user.len > 0) { + Output.prettyError( + \\ + \\If that still doesn't work, you may need to add these lines to /etc/security/limits.conf: + \\ + \\ {s} soft nofile 2147483646 + \\ {s} hard nofile 2147483646 + \\ + , + .{ user, user }, + ); + } + } + } + } else { + Output.prettyErrorln( + \\error: bun ran out of file descriptors (ProcessFdQuotaExceeded) + , + .{}, + ); + } + + if (!always_show_trace) Global.exit(1); + }, + + // The usage of `unreachable` in Zig's std.os may cause the file descriptor problem to show up as other errors + error.NotOpenForReading, error.Unexpected => { + if (comptime bun.Environment.isPosix) { + const limit = std.os.getrlimit(.NOFILE) catch std.mem.zeroes(std.os.rlimit); + + if (limit.cur > 0 and limit.cur < (8192 * 2)) { + Output.prettyError( + \\ + \\error: An unknown error ocurred, possibly due to low max file descriptors (Unexpected) + \\ + \\Current limit: {d} + \\ + \\To fix this, try running: + \\ + \\ ulimit -n 2147483646 + \\ + , + .{ + limit.cur, + }, + ); + + if (bun.Environment.isLinux) { + if (bun.getenvZ("USER")) |user| { + if (user.len > 0) { + Output.prettyError( + \\ + \\If that still doesn't work, you may need to add these lines to /etc/security/limits.conf: + \\ + \\ {s} soft nofile 2147483646 + \\ {s} hard nofile 2147483646 + \\ + , + .{ + user, + user, + }, + ); + } + } + } else if (bun.Environment.isMac) { + Output.prettyError( + \\ + \\If that still doesn't work, you may need to run: + \\ + \\ sudo launchctl limit maxfiles 2147483646 + \\ + , + .{}, + ); + } + } else { + Output.errGeneric( + "An unknown error ocurred ({s})", + .{@errorName(err)}, + ); + } + } else { + Output.errGeneric( + \\An unknown error ocurred ({s}) + , + .{@errorName(err)}, + ); + } + if (!always_show_trace) Global.exit(1); + }, + + error.ENOENT, error.FileNotFound => { + Output.err( + "ENOENT", + "Bun could not find a file, and the code that produces this error is missing a better error.", + .{}, + ); + }, + + error.MissingPackageJSON => { + Output.err( + "MissingPackageJSON", + "Bun could not find a package.json file.", + .{}, + ); + if (!always_show_trace) Global.exit(1); + }, + + else => { + Output.errGeneric( + if (bun.Environment.isDebug) + "'main' returned error.{s}" + else + "An internal error ocurred ({s})", + .{@errorName(err)}, + ); + }, + } + + if (error_return_trace) |trace| { + if (bun.Environment.isDebug) { + std.debug.dumpStackTrace(trace.*); + } + + // TODO: enable release-mode error return traces + // TODO: write better message here + Output.note("error trace string: {}", .{TraceString{ + .trace = trace, + .reason = .{ .zig_error = err }, + .action = .view_trace, + }}); + } + + Global.exit(1); +} + pub fn panicImpl(msg: []const u8, error_return_trace: ?*std.builtin.StackTrace, begin_addr: ?usize) noreturn { @setCold(true); crashHandler( @@ -230,7 +579,7 @@ else const metadata_version_line = std.fmt.comptimePrint( "Bun v{s} {s} {s}{s}\n", .{ - bun.Global.package_json_version_with_sha, + Global.package_json_version_with_sha, bun.Environment.os.displayString(), arch_display_string, if (bun.Environment.baseline) " (baseline)" else "", @@ -291,14 +640,14 @@ pub fn init() void { pub fn resetSegfaultHandler() void { if (bun.Environment.os == .windows) { if (windows_segfault_handle) |handle| { - std.debug.assert( - windows.kernel32.RemoveVectoredExceptionHandler(handle) != 0, - ); + const rc = windows.kernel32.RemoveVectoredExceptionHandler(handle); windows_segfault_handle = null; + bun.assert(rc != 0); } return; } - var act = std.os.Sigaction{ + + const act = std.os.Sigaction{ .handler = .{ .handler = std.os.SIG.DFL }, .mask = std.os.empty_sigset, .flags = 0, @@ -450,10 +799,9 @@ const Address = union(enum) { } }; -const EncodeOptions = struct { +const TraceString = struct { trace: *std.builtin.StackTrace, reason: CrashReason, - include_features: bool, action: Action, const Action = enum { @@ -462,9 +810,13 @@ const EncodeOptions = struct { /// View the trace with nothing else view_trace, }; + + pub fn format(self: TraceString, comptime _: []const u8, _: std.fmt.FormatOptions, writer: anytype) !void { + try encodeTraceString(self, writer); + } }; -fn encodeTraceString(opts: EncodeOptions, writer: anytype) !void { +fn encodeTraceString(opts: TraceString, writer: anytype) !void { try writer.writeAll(report_base_url ++ tracestr_header); const image_path = if (bun.Environment.isWindows) bun.windows.exePathW() else null; @@ -562,6 +914,7 @@ fn encodeTraceString(opts: EncodeOptions, writer: anytype) !void { break :zero_vlq vlq.bytes[0..vlq.len]; }); + // The following switch must be kept in sync with `bun-report`'s decoder. switch (opts.reason) { .panic => |message| { try writer.writeByte('0'); @@ -590,6 +943,7 @@ fn encodeTraceString(opts: EncodeOptions, writer: anytype) !void { try writer.writeAll(std.mem.trimRight(u8, b64_bytes[0..b64_len], "=")); }, + .@"unreachable" => try writer.writeByte('1'), .segmentation_fault => |addr| { @@ -611,13 +965,12 @@ fn encodeTraceString(opts: EncodeOptions, writer: anytype) !void { .datatype_misalignment => try writer.writeByte('6'), .stack_overflow => try writer.writeByte('7'), - } - // TODO: not sure if worth adding since URLs are already quite lengthy - // if (opts.include_features) { - // try writer.writeAll("_"); - // TODO - // } + .zig_error => |err| { + try writer.writeByte('8'); + try writer.writeAll(@errorName(err)); + }, + } if (opts.action == .view_trace) { try writer.writeAll("/view"); @@ -667,3 +1020,49 @@ fn crash() noreturn { }, } } + +pub var verbose_error_trace = false; + +/// In many places we catch errors, the trace for them is absorbed and only a +/// single line (the error name) is printed. When this is set, we will print +/// trace strings for those errors (or full stacks in debug builds). +/// +/// This can be enabled by passing `--verbose-error-trace` to the CLI. +/// In release builds with error return tracing enabled, this is also exposed. +/// You can test if this feature is available by checking `bun --help` for the flag. +pub inline fn handleErrorReturnTrace(err: anyerror, maybe_trace: ?*std.builtin.StackTrace) void { + if (!builtin.have_error_return_tracing) return; + if (!verbose_error_trace) return; + + if (maybe_trace) |trace| { + + // The format of the panic trace is slightly different in debug + // builds Mainly, we demangle the backtrace immediately instead + // of using a trace string. + // + // To make the release-mode behavior easier to demo, debug mode + // checks for this CLI flag. + const is_debug = bun.Environment.isDebug and check_flag: { + for (bun.argv) |arg| { + if (bun.strings.eqlComptime(arg, "--debug-crash-handler-use-trace-string")) { + break :check_flag false; + } + } + break :check_flag true; + }; + + if (is_debug) { + Output.note("caught error.{s}:", .{@errorName(err)}); + std.debug.dumpStackTrace(trace.*); + } else { + Output.prettyErrorln("trace: error.{s}: {}", .{ + @errorName(err), + TraceString{ + .trace = trace, + .reason = .{ .cli_returned_error = err }, + .action = .view_trace, + }, + }); + } + } +} diff --git a/src/fmt.zig b/src/fmt.zig index 259729247c4f88..2c6a63cf57c2fb 100644 --- a/src/fmt.zig +++ b/src/fmt.zig @@ -1304,3 +1304,22 @@ pub const FormatDouble = struct { try writer.writeAll(slice); } }; + +pub fn nullableFallback(value: anytype, null_fallback: []const u8) NullableFallback(@TypeOf(value)) { + return .{ .value = value, .null_fallback = null_fallback }; +} + +pub fn NullableFallback(comptime T: type) type { + return struct { + value: T, + null_fallback: []const u8, + + pub fn format(self: @This(), comptime template: []const u8, opts: fmt.FormatOptions, writer: anytype) !void { + if (self.value) |value| { + try std.fmt.formatType(value, template, opts, writer, 4); + } else { + try writer.writeAll(self.null_fallback); + } + } + }; +} diff --git a/src/http.zig b/src/http.zig index 0fa3b00bb7e756..419f75da27c4b5 100644 --- a/src/http.zig +++ b/src/http.zig @@ -2254,11 +2254,8 @@ fn start_(this: *HTTPClient, comptime is_ssl: bool) void { } var socket = http_thread.connect(this, is_ssl) catch |err| { - if (Environment.isDebug) { - if (@errorReturnTrace()) |trace| { - std.debug.dumpStackTrace(trace.*); - } - } + bun.handleErrorReturnTrace(err, @errorReturnTrace()); + this.fail(err); return; }; diff --git a/src/install/install.zig b/src/install/install.zig index 65dc60cc14d66f..cec541a16ddfb8 100644 --- a/src/install/install.zig +++ b/src/install/install.zig @@ -673,11 +673,8 @@ const Task = struct { manifest.network.callback.package_manifest.loaded_manifest, manager, ) catch |err| { - if (comptime Environment.isDebug) { - if (@errorReturnTrace()) |trace| { - std.debug.dumpStackTrace(trace.*); - } - } + bun.handleErrorReturnTrace(err, @errorReturnTrace()); + this.err = err; this.status = Status.fail; this.data = .{ .package_manifest = .{} }; @@ -1725,11 +1722,8 @@ pub const PackageInstall = struct { state.to_copy_buf2, if (Environment.isWindows) &state.buf2 else void{}, ) catch |err| { - if (comptime Environment.isDebug) { - if (@errorReturnTrace()) |trace| { - std.debug.dumpStackTrace(trace.*); - } - } + bun.handleErrorReturnTrace(err, @errorReturnTrace()); + if (comptime Environment.isWindows) { if (err == error.FailedToCopyFile) { return Result.fail(err, .copying_files); diff --git a/src/install/lockfile.zig b/src/install/lockfile.zig index e794f35d046854..2facff883cec43 100644 --- a/src/install/lockfile.zig +++ b/src/install/lockfile.zig @@ -1662,15 +1662,12 @@ pub fn saveToDisk(this: *Lockfile, filename: stringZ) void { } file.closeAndMoveTo(tmpname, filename) catch |err| { + bun.handleErrorReturnTrace(err, @errorReturnTrace()); + // note: file is already closed here. _ = bun.sys.unlink(tmpname); - if (comptime Environment.allow_assert) { - if (@errorReturnTrace()) |trace| { - std.debug.dumpStackTrace(trace.*); - } - } - Output.err(err, "failed to replace old lockfile with new lockfile on disk", .{}); + Output.err(err, "Failed to replace old lockfile with new lockfile on disk", .{}); Global.crash(); }; } diff --git a/src/install/migration.zig b/src/install/migration.zig index ca206ad1210971..163084e880a45d 100644 --- a/src/install/migration.zig +++ b/src/install/migration.zig @@ -60,12 +60,10 @@ pub fn detectAndLoadOtherLockfile(this: *Lockfile, allocator: Allocator, log: *l Global.exit(1); } if (Environment.allow_assert) { - const maybe_trace = @errorReturnTrace(); + bun.handleErrorReturnTrace(err, @errorReturnTrace()); + Output.prettyErrorln("Error: {s}", .{@errorName(err)}); log.printForLogLevel(Output.errorWriter()) catch {}; - if (maybe_trace) |trace| { - std.debug.dumpStackTrace(trace.*); - } Output.prettyErrorln("Invalid NPM package-lock.json\nIn a release build, this would ignore and do a fresh install.\nAborting", .{}); Global.exit(1); } diff --git a/src/main.zig b/src/main.zig index ae9289fb871fe3..7af6b5bdd8504d 100644 --- a/src/main.zig +++ b/src/main.zig @@ -6,10 +6,9 @@ const bun = @import("root").bun; const Output = bun.Output; const Environment = bun.Environment; -pub const panic = bun.panic_handler.panic; - +pub const panic = bun.crash_handler.panic; pub const std_options = struct { - pub const enable_segfault_handler = !bun.panic_handler.enabled; + pub const enable_segfault_handler = !bun.crash_handler.enabled; }; pub const io_mode = .blocking; @@ -23,7 +22,7 @@ pub extern "C" var _environ: ?*anyopaque; pub extern "C" var environ: ?*anyopaque; pub fn main() void { - bun.panic_handler.init(); + bun.crash_handler.init(); // This should appear before we make any calls at all to libuv. // So it's safest to put it very early in the main function. diff --git a/src/main_wasm.zig b/src/main_wasm.zig index adf57dee438b41..a02da7755b57d8 100644 --- a/src/main_wasm.zig +++ b/src/main_wasm.zig @@ -479,11 +479,10 @@ export fn getTests(opts_array: u64) u64 { parser.options.features.top_level_await = true; parser.analyze(&anaylzer, @ptrCast(&TestAnalyzer.visitParts)) catch |err| { + bun.handleErrorReturnTrace(err, @errorReturnTrace()); + Output.print("Error: {s}\n", .{@errorName(err)}); - if (@errorReturnTrace()) |trace| { - Output.print("{}\n", .{trace}); - } log_.printForLogLevel(Output.writer()) catch unreachable; return 0; }; diff --git a/src/old_crash_reporter.zig b/src/old_crash_reporter.zig deleted file mode 100644 index 2a0dc0721821a0..00000000000000 --- a/src/old_crash_reporter.zig +++ /dev/null @@ -1,66 +0,0 @@ -// TODO(@paperdave): merge all functionality into panic_handler.zig -const std = @import("std"); -const bun = @import("root").bun; - -fn setup_sigactions(act: ?*const os.Sigaction) !void { - try os.sigaction(os.SIG.ABRT, act, null); - try os.sigaction(os.SIG.BUS, act, null); - try os.sigaction(os.SIG.FPE, act, null); - try os.sigaction(os.SIG.ILL, act, null); - try os.sigaction(os.SIG.SEGV, act, null); - try os.sigaction(os.SIG.TRAP, act, null); -} - -const builtin = @import("builtin"); -const ErrorCallback = *const fn (sig: i32, addr: usize) void; -pub var on_error: ?ErrorCallback = null; -noinline fn sigaction_handler(sig: i32, info: *const std.os.siginfo_t, _: ?*const anyopaque) callconv(.C) void { - // Prevent recursive calls - setup_sigactions(null) catch unreachable; - - const addr = switch (builtin.target.os.tag) { - .linux => @intFromPtr(info.fields.sigfault.addr), - .macos, .freebsd => @intFromPtr(info.addr), - .netbsd => @intFromPtr(info.info.reason.fault.addr), - .openbsd => @intFromPtr(info.data.fault.addr), - .solaris => @intFromPtr(info.reason.fault.addr), - else => @compileError("unreachable"), - }; - if (on_error) |handle| handle(sig, addr); -} - -noinline fn sigpipe_handler(_: i32, _: *const std.os.siginfo_t, _: ?*const anyopaque) callconv(.C) void { - bun.Output.debug("SIGPIPE received\n", .{}); -} - -pub fn reloadHandlers() !void { - if (comptime bun.Environment.isWindows) { - return bun.todo(@src(), {}); - } - try os.sigaction(os.SIG.PIPE, null, null); - try setup_sigactions(null); - - var act = os.Sigaction{ - .handler = .{ .sigaction = sigaction_handler }, - .mask = os.empty_sigset, - .flags = (os.SA.SIGINFO | os.SA.RESTART | os.SA.RESETHAND), - }; - - try setup_sigactions(&act); - bun.spawn.WaiterThread.reloadHandlers(); - bun_ignore_sigpipe(); -} -const os = std.os; -pub fn start() !void { - var act = os.Sigaction{ - .handler = .{ .sigaction = sigaction_handler }, - .mask = os.empty_sigset, - .flags = (os.SA.SIGINFO | os.SA.RESTART | os.SA.RESETHAND), - }; - - try setup_sigactions(&act); - bun_ignore_sigpipe(); - bun.spawn.WaiterThread.reloadHandlers(); -} - -extern fn bun_ignore_sigpipe() void; diff --git a/src/old_panic_handler.zig b/src/old_panic_handler.zig deleted file mode 100644 index 95d5eb37f9c630..00000000000000 --- a/src/old_panic_handler.zig +++ /dev/null @@ -1,58 +0,0 @@ -// TODO(@paperdave): merge all functionality into panic_handler.zig -const std = @import("std"); -const bun = @import("root").bun; -const string = bun.string; -const Output = bun.Output; -const Global = bun.Global; -const Environment = bun.Environment; -const strings = bun.strings; -const MutableString = bun.MutableString; -const stringZ = bun.stringZ; -const default_allocator = bun.default_allocator; -const C = bun.C; -const CLI = @import("./cli.zig").Cli; -const Features = @import("./analytics/analytics_thread.zig").Features; -const HTTP = @import("root").bun.http.AsyncHTTP; -const Report = @import("./report.zig"); - -pub fn NewPanicHandler(comptime panic_func: fn ([]const u8, ?*std.builtin.StackTrace, ?usize) noreturn) type { - return struct { - panic_count: usize = 0, - skip_next_panic: bool = false, - log: *bun.logger.Log, - - pub var Singleton: ?*Handler = null; - const Handler = @This(); - - pub fn init(log: *bun.logger.Log) Handler { - return Handler{ - .log = log, - }; - } - pub inline fn handle_panic(msg: []const u8, error_return_type: ?*std.builtin.StackTrace, addr: ?usize) noreturn { - - // This exists to ensure we flush all buffered output before panicking. - Output.flush(); - - bun.maybeHandlePanicDuringProcessReload(); - - Report.fatal(null, msg); - - Output.disableBuffering(); - - Output.Source.Stdio.restore(); - - if (bun.auto_reload_on_crash) { - // attempt to prevent a double panic - bun.auto_reload_on_crash = false; - - Output.prettyErrorln("--- Bun is auto-restarting due to crash [time: {d}] ---", .{@max(std.time.milliTimestamp(), 0)}); - Output.flush(); - bun.reloadProcess(bun.default_allocator, false); - } - - // We want to always inline the panic handler so it doesn't show up in the stacktrace. - @call(bun.callmod_inline, panic_func, .{ msg, error_return_type, addr }); - } - }; -} diff --git a/src/report.zig b/src/report.zig deleted file mode 100644 index bc07e05297ba5c..00000000000000 --- a/src/report.zig +++ /dev/null @@ -1,633 +0,0 @@ -const std = @import("std"); -const logger = bun.logger; -const bun = @import("root").bun; -const string = bun.string; -const Output = bun.Output; -const Global = bun.Global; -const Environment = bun.Environment; -const strings = bun.strings; -const MutableString = bun.MutableString; -const stringZ = bun.stringZ; -const default_allocator = bun.default_allocator; -const C = bun.C; -const CLI = @import("./cli.zig").Cli; -const Features = @import("./analytics/analytics_thread.zig").Features; -const Platform = @import("./analytics/analytics_thread.zig").GenerateHeader.GeneratePlatform; -const HTTP = @import("root").bun.http.AsyncHTTP; -const CrashReporter = @import("./old_crash_reporter.zig"); - -const Report = @This(); - -var crash_report_writer: CrashReportWriter = CrashReportWriter{ .file = null }; -const CrashWritable = if (Environment.isWasm) std.io.fixedBufferStream([2048]u8) else std.fs.File.Writer; -var crash_reporter_path: [1024]u8 = undefined; -pub const CrashReportWriter = struct { - file: ?std.io.BufferedWriter(4096, CrashWritable) = null, - file_path: []const u8 = "", - - pub fn printFrame(_: ?*anyopaque, frame: CrashReporter.StackFrame) void { - const function_name = if (frame.function_name.len > 0) frame.function_name else "[function ?]"; - const filename = if (frame.filename.len > 0) frame.function_name else "[file ?]"; - const fmt = bun.fmt.hexIntUpper(frame.pc); - crash_report_writer.print("[0x{any}] - {s} {s}:{d}\n", .{ fmt, function_name, filename, frame.line_number }); - } - - pub fn dump() void { - if (comptime !Environment.isWasm) - CrashReporter.print(); - } - - pub fn done() void { - if (comptime !Environment.isWasm) - CrashReporter.print(); - } - - pub fn print(this: *CrashReportWriter, comptime fmt: string, args: anytype) void { - Output.prettyError(fmt, args); - if (this.file) |*file| { - var writer = file.writer(); - writer.print(comptime Output.prettyFmt(fmt, false), args) catch {}; - } - } - - pub fn flush(this: *CrashReportWriter) void { - if (this.file) |*file| { - file.flush() catch {}; - } - Output.flush(); - } - - pub fn generateFile(this: *CrashReportWriter) void { - if (this.file != null) return; - - var base_dir: []const u8 = "."; - if (bun.getenvZ("BUN_INSTALL")) |install_dir| { - base_dir = strings.withoutTrailingSlash(install_dir); - } else if (bun.getenvZ(bun.DotEnv.home_env)) |home_dir| { - base_dir = strings.withoutTrailingSlash(home_dir); - } - const file_path = std.fmt.bufPrintZ( - &crash_reporter_path, - "{s}/.bun-crash/v{s}-{d}.crash", - .{ base_dir, Global.package_json_version, @as(u64, @intCast(@max(std.time.milliTimestamp(), 0))) }, - ) catch return; - - if (bun.path.nextDirname(file_path)) |dirname| { - _ = bun.sys.mkdirA(dirname, 0); - } - - const call = bun.sys.openA(file_path, std.os.O.CREAT | std.os.O.TRUNC, 0).unwrap() catch return; - var file = call.asFile(); - this.file = std.io.bufferedWriter( - file.writer(), - ); - this.file_path = bun.asByteSlice(file_path); - } - - pub fn printPath(this: *CrashReportWriter) void { - var display_path = this.file_path; - - if (this.file_path.len > 0) { - var tilda = false; - if (bun.getenvZ(bun.DotEnv.home_env)) |home_dir| { - if (strings.hasPrefix(display_path, home_dir)) { - display_path = display_path[home_dir.len..]; - tilda = true; - } - } - - if (tilda) { - Output.prettyError("\nCrash report saved to:\n ~{s}\n", .{display_path}); - } else { - Output.prettyError("\nCrash report saved to:\n {s}\n", .{display_path}); - } - } - } -}; - -pub fn printMetadata() void { - @setCold(true); - - if (comptime !Environment.isWindows) { - // TODO(@paperdave): report files do not work on windows, and report files in general are buggy - crash_report_writer.generateFile(); - } - - const cmd_label: string = if (CLI.cmd) |tag| @tagName(tag) else "Unknown"; - - const platform = comptime Environment.os.displayString(); - const arch = comptime if (Environment.isAarch64) - if (Environment.isMac) "Silicon" else "arm64" - else - "x64"; - - const analytics_platform = Platform.forOS(); - - const maybe_baseline = if (Environment.baseline) " (baseline)" else ""; - - crash_report_writer.print( - \\ - \\----- bun meta ----- - ++ "\nBun v" ++ Global.package_json_version_with_sha ++ " " ++ platform ++ " " ++ arch ++ maybe_baseline ++ " {s}\n" ++ - \\{s}: {} - , .{ - analytics_platform.version, - cmd_label, - Features.formatter(), - }); - - const http_count = HTTP.active_requests_count.raw; - if (http_count > 0) - crash_report_writer.print( - \\HTTP: {d} - \\ - , .{http_count}); - - if (comptime bun.use_mimalloc) { - var elapsed_msecs: usize = 0; - var user_msecs: usize = 0; - var system_msecs: usize = 0; - var current_rss: usize = 0; - var peak_rss: usize = 0; - var current_commit: usize = 0; - var peak_commit: usize = 0; - var page_faults: usize = 0; - const mimalloc = @import("allocators/mimalloc.zig"); - mimalloc.mi_process_info( - &elapsed_msecs, - &user_msecs, - &system_msecs, - ¤t_rss, - &peak_rss, - ¤t_commit, - &peak_commit, - &page_faults, - ); - crash_report_writer.print("Elapsed: {d}ms | User: {d}ms | Sys: {d}ms\nRSS: {:<3.2} | Peak: {:<3.2} | Commit: {:<3.2} | Faults: {d}\n", .{ - elapsed_msecs, - user_msecs, - system_msecs, - std.fmt.fmtIntSizeDec(current_rss), - std.fmt.fmtIntSizeDec(peak_rss), - std.fmt.fmtIntSizeDec(current_commit), - page_faults, - }); - } - - crash_report_writer.print("----- bun meta -----\n", .{}); -} -var has_printed_fatal = false; -var has_printed_crash = false; -pub fn fatal(err_: ?anyerror, msg_: ?string) void { - const had_printed_fatal = has_printed_fatal; - if (!has_printed_fatal) { - has_printed_fatal = true; - if (comptime !Environment.isWindows) { - crash_report_writer.generateFile(); - } - - if (err_) |err| { - if (Output.isEmojiEnabled()) { - crash_report_writer.print( - "\nerror: {s}\n", - .{@errorName(err)}, - ); - } else { - crash_report_writer.print( - "\nerror: {s}\n\n", - .{@errorName(err)}, - ); - } - } - - if (msg_) |msg| { - const msg_ptr = @intFromPtr(msg.ptr); - if (msg_ptr > 0) { - const len = @max(@min(msg.len, 1024), 0); - - if (len > 0) { - if (Output.isEmojiEnabled()) { - crash_report_writer.print( - "\nuh-oh: {s}\n", - .{msg[0..len]}, - ); - } else { - crash_report_writer.print( - "\nPanic: {s}\n\n", - .{msg[0..len]}, - ); - } - } - } - } - - if (err_ == null) { - if (Output.isEmojiEnabled()) { - if (msg_ == null and err_ == null) { - crash_report_writer.print("", .{}); - } else { - crash_report_writer.print("", .{}); - } - crash_report_writer.print("bun will crash now 😭😭😭\n", .{}); - } else { - crash_report_writer.print("bun has crashed :'(\n", .{}); - } - } - crash_report_writer.flush(); - - printMetadata(); - - crash_report_writer.flush(); - - var addrs: [32]usize = undefined; - var trace = std.builtin.StackTrace{ - .index = 0, - .instruction_addresses = &addrs, - }; - std.debug.captureStackTrace(@returnAddress(), &trace); - - // It only is a real crash report if it's not coming from Zig - - crash_report_writer.flush(); - - crash_report_writer.printPath(); - } - - if (!had_printed_fatal) { - if (Environment.isWindows) { - crash_report_writer.print("\nSearch GitHub issues https://bun.sh/issues or join in #windows channel in https://bun.sh/discord\n\n", .{}); - } else { - crash_report_writer.print("\nSearch GitHub issues https://bun.sh/issues or ask for #help in https://bun.sh/discord\n\n", .{}); - } - crash_report_writer.flush(); - } -} - -var globalError_ranOnce = false; -var error_return_trace: ?*std.builtin.StackTrace = null; - -pub noinline fn handleCrash(signal: i32, addr: usize) void { - const had_printed_fatal = has_printed_fatal; - if (has_printed_fatal) return; - has_printed_fatal = true; - - if (comptime !Environment.isWindows) { - // TODO(@paperdave): report files do not work on windows, and report files in general are buggy - crash_report_writer.generateFile(); - } - - const name = switch (signal) { - std.os.SIG.SEGV => error.SegmentationFault, - std.os.SIG.ILL => error.InstructionError, - std.os.SIG.BUS => error.BusError, - else => error.Crash, - }; - - crash_report_writer.print( - "\n{s} at 0x{any}\n\n", - .{ @errorName(name), bun.fmt.hexIntUpper(addr) }, - ); - printMetadata(); - if (comptime Environment.isDebug and !Environment.isWindows) { - error_return_trace = @errorReturnTrace(); - } - - if (!had_printed_fatal) { - crash_report_writer.print("\nAsk for #help in https://bun.sh/discord or go to https://bun.sh/issues\n\n", .{}); - } - crash_report_writer.flush(); - - if (crash_report_writer.file) |file| { - // don't handle return codes here - _ = std.os.system.close(file.unbuffered_writer.context.handle); - } - - crash_report_writer.file = null; - if (!Environment.isWindows) { - if (error_return_trace) |trace| { - std.debug.dumpStackTrace(trace.*); - } - } - Global.runExitCallbacks(); - std.c._exit(128 + @as(u8, @truncate(@as(u8, @intCast(@max(signal, 0)))))); -} - -pub noinline fn globalError(err: anyerror, trace_: @TypeOf(@errorReturnTrace())) noreturn { - @setCold(true); - - bun.maybeHandlePanicDuringProcessReload(); - - error_return_trace = trace_; - - if (@atomicRmw(bool, &globalError_ranOnce, .Xchg, true, .Monotonic)) { - Global.exit(1); - } - - switch (err) { - // error.BrokenPipe => { - // if (comptime Environment.isNative) { - // // if stdout/stderr was closed, we don't need to print anything - // std.c._exit(bun.JSC.getGlobalExitCodeForPipeFailure()); - // } - // }, - error.SyntaxError => { - Output.prettyError( - "\nSyntaxError: An error occurred while parsing code", - .{}, - ); - Global.exit(1); - }, - error.OutOfMemory => { - Output.prettyError( - "\nOutOfMemory: There might be an infinite loop somewhere", - .{}, - ); - printMetadata(); - Global.exit(1); - }, - error.CurrentWorkingDirectoryUnlinked => { - Output.prettyError( - "\nerror: The current working directory was deleted, so that command didn't work. Please cd into a different directory and try again.", - .{}, - ); - Global.exit(1); - }, - error.InvalidArgument => { - Global.exit(1); - }, - error.SystemFdQuotaExceeded => { - if (comptime Environment.isPosix) { - const limit = std.os.getrlimit(.NOFILE) catch std.mem.zeroes(std.os.rlimit); - if (comptime Environment.isMac) { - Output.prettyError( - \\ - \\error: Your computer ran out of file descriptors (SystemFdQuotaExceeded) - \\ - \\Current limit: {d} - \\ - \\To fix this, try running: - \\ - \\ sudo launchctl limit maxfiles 2147483646 - \\ ulimit -n 2147483646 - \\ - \\That will only work until you reboot. - \\ - , - .{ - limit.cur, - }, - ); - } else { - Output.prettyError( - \\ - \\error: Your computer ran out of file descriptors (SystemFdQuotaExceeded) - \\ - \\Current limit: {d} - \\ - \\To fix this, try running: - \\ - \\ sudo echo -e "\nfs.file-max=2147483646\n" >> /etc/sysctl.conf - \\ sudo sysctl -p - \\ ulimit -n 2147483646 - \\ - , - .{ - limit.cur, - }, - ); - - if (bun.getenvZ("USER")) |user| { - if (user.len > 0) { - Output.prettyError( - \\ - \\If that still doesn't work, you may need to add these lines to /etc/security/limits.conf: - \\ - \\ {s} soft nofile 2147483646 - \\ {s} hard nofile 2147483646 - \\ - , - .{ user, user }, - ); - } - } - } - } else { - Output.prettyError( - \\error: Your computer ran out of file descriptors (SystemFdQuotaExceeded) - , - .{}, - ); - } - - Global.exit(1); - }, - error.@"Invalid Bunfig" => { - Global.exit(1); - }, - error.ProcessFdQuotaExceeded => { - if (comptime Environment.isPosix) { - const limit = std.os.getrlimit(.NOFILE) catch std.mem.zeroes(std.os.rlimit); - if (comptime Environment.isMac) { - Output.prettyError( - \\ - \\error: bun ran out of file descriptors (ProcessFdQuotaExceeded) - \\ - \\Current limit: {d} - \\ - \\To fix this, try running: - \\ - \\ ulimit -n 2147483646 - \\ - \\You may also need to run: - \\ - \\ sudo launchctl limit maxfiles 2147483646 - \\ - , - .{ - limit.cur, - }, - ); - } else { - Output.prettyError( - \\ - \\error: bun ran out of file descriptors (ProcessFdQuotaExceeded) - \\ - \\Current limit: {d} - \\ - \\To fix this, try running: - \\ - \\ ulimit -n 2147483646 - \\ - \\That will only work for the current shell. To fix this for the entire system, run: - \\ - \\ sudo echo -e "\nfs.file-max=2147483646\n" >> /etc/sysctl.conf - \\ sudo sysctl -p - \\ - , - .{ - limit.cur, - }, - ); - - if (bun.getenvZ("USER")) |user| { - if (user.len > 0) { - Output.prettyError( - \\ - \\If that still doesn't work, you may need to add these lines to /etc/security/limits.conf: - \\ - \\ {s} soft nofile 2147483646 - \\ {s} hard nofile 2147483646 - \\ - , - .{ user, user }, - ); - } - } - } - } else { - Output.prettyErrorln( - \\error: bun ran out of file descriptors (ProcessFdQuotaExceeded) - , - .{}, - ); - } - - Global.exit(1); - }, - // The usage of `unreachable` in Zig's std.os may cause the file descriptor problem to show up as other errors - error.NotOpenForReading, error.Unexpected => { - if (!Environment.isWindows) { - if (trace_) |trace| { - print_stacktrace: { - const debug_info = std.debug.getSelfDebugInfo() catch break :print_stacktrace; - Output.disableBuffering(); - std.debug.writeStackTrace(trace.*, Output.errorWriter(), default_allocator, debug_info, std.io.tty.detectConfig(std.io.getStdErr())) catch break :print_stacktrace; - } - } - } - - if (comptime Environment.isPosix) { - const limit = std.os.getrlimit(.NOFILE) catch std.mem.zeroes(std.os.rlimit); - - if (limit.cur > 0 and limit.cur < (8192 * 2)) { - Output.prettyError( - \\ - \\error: An unknown error ocurred, possibly due to low max file descriptors (Unexpected) - \\ - \\Current limit: {d} - \\ - \\To fix this, try running: - \\ - \\ ulimit -n 2147483646 - \\ - , - .{ - limit.cur, - }, - ); - - if (Environment.isLinux) { - if (bun.getenvZ("USER")) |user| { - if (user.len > 0) { - Output.prettyError( - \\ - \\If that still doesn't work, you may need to add these lines to /etc/security/limits.conf: - \\ - \\ {s} soft nofile 2147483646 - \\ {s} hard nofile 2147483646 - \\ - , - .{ - user, - user, - }, - ); - } - } - } else if (Environment.isMac) { - Output.prettyError( - \\ - \\If that still doesn't work, you may need to run: - \\ - \\ sudo launchctl limit maxfiles 2147483646 - \\ - , - .{}, - ); - } - } else { - Output.prettyError( - \\error: An unknown error ocurred (Unexpected) - , - .{}, - ); - } - - Global.exit(1); - } - }, - error.ENOENT, error.FileNotFound => { - Output.prettyError( - "\nerror: FileNotFound\nBun could not find a file, and the code that produces this error is missing a better error.\n", - .{}, - ); - Output.flush(); - - Report.printMetadata(); - - Output.flush(); - - if (!Environment.isWindows) { - if (trace_) |trace| { - print_stacktrace: { - const debug_info = std.debug.getSelfDebugInfo() catch break :print_stacktrace; - Output.disableBuffering(); - std.debug.writeStackTrace(trace.*, Output.errorWriter(), default_allocator, debug_info, std.io.tty.detectConfig(std.io.getStdErr())) catch break :print_stacktrace; - } - } - } - - Global.exit(1); - }, - error.MissingPackageJSON => { - Output.prettyError( - "\nerror: MissingPackageJSON\nBun could not find a package.json file.\n", - .{}, - ); - if (!Environment.isWindows) { - if (trace_) |trace| { - print_stacktrace: { - const debug_info = std.debug.getSelfDebugInfo() catch break :print_stacktrace; - Output.disableBuffering(); - std.debug.writeStackTrace(trace.*, Output.errorWriter(), default_allocator, debug_info, std.io.tty.detectConfig(std.io.getStdErr())) catch break :print_stacktrace; - } - } - } - - Global.exit(1); - }, - else => {}, - } - - Report.fatal(err, null); - if (!Environment.isWindows) { - if (trace_) |trace| { - print_stacktrace: { - const debug_info = std.debug.getSelfDebugInfo() catch break :print_stacktrace; - Output.disableBuffering(); - std.debug.writeStackTrace(trace.*, Output.errorWriter(), default_allocator, debug_info, std.io.tty.detectConfig(std.io.getStdErr())) catch break :print_stacktrace; - } - } - } - - if (bun.auto_reload_on_crash) { - // attempt to prevent a double panic - bun.auto_reload_on_crash = false; - - Output.prettyErrorln("--- Bun is auto-restarting due to crash [time: {d}] ---", .{@max(std.time.milliTimestamp(), 0)}); - Output.flush(); - bun.reloadProcess(bun.default_allocator, false); - } - - Global.exit(1); -} From 455ff79f40dd10539e37de0bd67200c8641fa573 Mon Sep 17 00:00:00 2001 From: dave caruso Date: Thu, 11 Apr 2024 22:44:26 -0700 Subject: [PATCH 07/26] okay --- src/cli/run_command.zig | 3 --- src/crash_handler.zig | 49 ++++++++++++++++++++++------------------- src/install/install.zig | 12 ++-------- 3 files changed, 28 insertions(+), 36 deletions(-) diff --git a/src/cli/run_command.zig b/src/cli/run_command.zig index 24e6075a5f7bb8..d65918ff4abd35 100644 --- a/src/cli/run_command.zig +++ b/src/cli/run_command.zig @@ -579,9 +579,6 @@ pub const RunCommand = struct { basenameOrBun(executable), signal.name() orelse "unknown", }); - if (@errorReturnTrace()) |trace| { - std.debug.dumpStackTrace(trace.*); - } } Output.flush(); diff --git a/src/crash_handler.zig b/src/crash_handler.zig index 3a8f312b4eb541..c21b3c7c4d4030 100644 --- a/src/crash_handler.zig +++ b/src/crash_handler.zig @@ -270,15 +270,12 @@ pub fn crashHandler( /// This is called when `main` returns a Zig error. /// We don't want to treat it as a crash under certain error codes. pub fn handleRootError(err: anyerror, error_return_trace: ?*std.builtin.StackTrace) noreturn { - if (bun.Environment.isDebug) { - if (bun.verbose_error_trace) { - if (error_return_trace) |trace| { - Output.errGeneric("CLI returned {s}", .{@errorName(err)}); - std.debug.dumpStackTrace(trace.*); - Global.exit(1); - } else { - Output.debugWarn("error return trace not available for the following error", .{}); - } + if (verbose_error_trace) { + if (error_return_trace) |trace| { + handleErrorReturnTraceExtra(err, trace, true); + Global.exit(1); + } else { + Output.debugWarn("error return trace not available for the following error", .{}); } } @@ -538,7 +535,7 @@ pub fn handleRootError(err: anyerror, error_return_trace: ?*std.builtin.StackTra if (error_return_trace) |trace| { if (bun.Environment.isDebug) { - std.debug.dumpStackTrace(trace.*); + dumpStackTrace(trace.*); } // TODO: enable release-mode error return traces @@ -1059,19 +1056,11 @@ fn crash() noreturn { pub var verbose_error_trace = false; -/// In many places we catch errors, the trace for them is absorbed and only a -/// single line (the error name) is printed. When this is set, we will print -/// trace strings for those errors (or full stacks in debug builds). -/// -/// This can be enabled by passing `--verbose-error-trace` to the CLI. -/// In release builds with error return tracing enabled, this is also exposed. -/// You can test if this feature is available by checking `bun --help` for the flag. -pub inline fn handleErrorReturnTrace(err: anyerror, maybe_trace: ?*std.builtin.StackTrace) void { +fn handleErrorReturnTraceExtra(err: anyerror, maybe_trace: ?*std.builtin.StackTrace, comptime is_root: bool) void { if (!builtin.have_error_return_tracing) return; if (!verbose_error_trace) return; if (maybe_trace) |trace| { - // The format of the panic trace is slightly different in debug // builds Mainly, we demangle the backtrace immediately instead // of using a trace string. @@ -1088,17 +1077,31 @@ pub inline fn handleErrorReturnTrace(err: anyerror, maybe_trace: ?*std.builtin.S }; if (is_debug) { - Output.note("caught error.{s}:", .{@errorName(err)}); - std.debug.dumpStackTrace(trace.*); + Output.note(if (is_root) "CLI returned error.{s}" else "caught error.{s}:", .{@errorName(err)}); + dumpStackTrace(trace.*); } else { - Output.prettyErrorln("trace: error.{s}: {}", .{ + Output.prettyErrorln(if (is_root) + "trace: Bun returned with error.{s}: {}" + else + "trace: error.{s}: {}", .{ @errorName(err), TraceString{ .trace = trace, - .reason = .{ .cli_returned_error = err }, + .reason = .{ .zig_error = err }, .action = .view_trace, }, }); } } } + +/// In many places we catch errors, the trace for them is absorbed and only a +/// single line (the error name) is printed. When this is set, we will print +/// trace strings for those errors (or full stacks in debug builds). +/// +/// This can be enabled by passing `--verbose-error-trace` to the CLI. +/// In release builds with error return tracing enabled, this is also exposed. +/// You can test if this feature is available by checking `bun --help` for the flag. +pub inline fn handleErrorReturnTrace(err: anyerror, maybe_trace: ?*std.builtin.StackTrace) void { + handleErrorReturnTraceExtra(err, maybe_trace, false); +} diff --git a/src/install/install.zig b/src/install/install.zig index cec541a16ddfb8..5bf335966be25c 100644 --- a/src/install/install.zig +++ b/src/install/install.zig @@ -707,11 +707,7 @@ const Task = struct { const result = this.request.extract.tarball.run( bytes, ) catch |err| { - if (comptime Environment.isDebug) { - if (@errorReturnTrace()) |trace| { - std.debug.dumpStackTrace(trace.*); - } - } + bun.handleErrorReturnTrace(err, @errorReturnTrace()); this.err = err; this.status = Status.fail; @@ -787,11 +783,7 @@ const Task = struct { manager.allocator, &this.request.local_tarball.tarball, ) catch |err| { - if (comptime Environment.isDebug) { - if (@errorReturnTrace()) |trace| { - std.debug.dumpStackTrace(trace.*); - } - } + bun.handleErrorReturnTrace(err, @errorReturnTrace()); this.err = err; this.status = Status.fail; From 3e5c9273e85370a4bd25256b07428dfe2e856966 Mon Sep 17 00:00:00 2001 From: dave caruso Date: Thu, 11 Apr 2024 23:01:11 -0700 Subject: [PATCH 08/26] adgadsgbcxcv --- src/crash_handler.zig | 94 +++++++++++++++++++++---------------------- 1 file changed, 46 insertions(+), 48 deletions(-) diff --git a/src/crash_handler.zig b/src/crash_handler.zig index c21b3c7c4d4030..8af51adcea76d2 100644 --- a/src/crash_handler.zig +++ b/src/crash_handler.zig @@ -270,27 +270,17 @@ pub fn crashHandler( /// This is called when `main` returns a Zig error. /// We don't want to treat it as a crash under certain error codes. pub fn handleRootError(err: anyerror, error_return_trace: ?*std.builtin.StackTrace) noreturn { - if (verbose_error_trace) { - if (error_return_trace) |trace| { - handleErrorReturnTraceExtra(err, trace, true); - Global.exit(1); - } else { - Output.debugWarn("error return trace not available for the following error", .{}); - } - } - - const always_show_trace = bun.Environment.isDebug; + var show_trace = bun.Environment.isDebug; switch (err) { error.OutOfMemory => bun.outOfMemory(), error.InvalidArgument, error.@"Invalid Bunfig", - => if (!always_show_trace) Global.exit(1), + => if (!show_trace) Global.exit(1), error.SyntaxError => { Output.err("SyntaxError", "An error occurred while parsing code", .{}); - if (!always_show_trace) Global.exit(1); }, error.CurrentWorkingDirectoryUnlinked => { @@ -298,7 +288,6 @@ pub fn handleRootError(err: anyerror, error_return_trace: ?*std.builtin.StackTra "The current working directory was deleted, so that command didn't work. Please cd into a different directory and try again.", .{}, ); - if (!always_show_trace) Global.exit(1); }, error.SystemFdQuotaExceeded => { @@ -363,8 +352,6 @@ pub fn handleRootError(err: anyerror, error_return_trace: ?*std.builtin.StackTra .{}, ); } - - if (!always_show_trace) Global.exit(1); }, error.ProcessFdQuotaExceeded => { @@ -434,8 +421,6 @@ pub fn handleRootError(err: anyerror, error_return_trace: ?*std.builtin.StackTra .{}, ); } - - if (!always_show_trace) Global.exit(1); }, // The usage of `unreachable` in Zig's std.os may cause the file descriptor problem to show up as other errors @@ -494,6 +479,7 @@ pub fn handleRootError(err: anyerror, error_return_trace: ?*std.builtin.StackTra "An unknown error ocurred ({s})", .{@errorName(err)}, ); + show_trace = true; } } else { Output.errGeneric( @@ -501,8 +487,8 @@ pub fn handleRootError(err: anyerror, error_return_trace: ?*std.builtin.StackTra , .{@errorName(err)}, ); + show_trace = true; } - if (!always_show_trace) Global.exit(1); }, error.ENOENT, error.FileNotFound => { @@ -519,7 +505,6 @@ pub fn handleRootError(err: anyerror, error_return_trace: ?*std.builtin.StackTra "Bun could not find a package.json file.", .{}, ); - if (!always_show_trace) Global.exit(1); }, else => { @@ -530,21 +515,12 @@ pub fn handleRootError(err: anyerror, error_return_trace: ?*std.builtin.StackTra "An internal error ocurred ({s})", .{@errorName(err)}, ); + show_trace = true; }, } - if (error_return_trace) |trace| { - if (bun.Environment.isDebug) { - dumpStackTrace(trace.*); - } - - // TODO: enable release-mode error return traces - // TODO: write better message here - Output.note("error trace string: {}", .{TraceString{ - .trace = trace, - .reason = .{ .zig_error = err }, - .action = .view_trace, - }}); + if (show_trace) { + handleErrorReturnTraceExtra(err, error_return_trace, true); } Global.exit(1); @@ -670,10 +646,18 @@ pub fn handleSegfaultWindows(info: windows.EXCEPTION_POINTERS) callconv(windows. pub fn printMetadata(writer: anytype) !void { try writer.writeAll(metadata_version_line); - try writer.print("Args: ", .{}); - for (bun.argv, 0..) |arg, i| { - if (i != 0) try writer.writeAll(", "); - try bun.fmt.quotedWriter(writer, arg); + { + try writer.print("Args: ", .{}); + var arg_chars_left: usize = 196; + for (bun.argv, 0..) |arg, i| { + if (i != 0) try writer.writeAll(", "); + try bun.fmt.quotedWriter(writer, arg[0..@min(arg.len, arg_chars_left)]); + arg_chars_left -|= arg.len; + if (arg_chars_left == 0) { + try writer.writeAll("..."); + break; + } + } } try writer.print("\n{}", .{bun.Analytics.Features.formatter()}); @@ -1017,6 +1001,8 @@ fn writeU64AsTwoVLQs(writer: anytype, addr: usize) !void { try second.writeTo(writer); } +/// Crash. Make sure segfault handlers are off so that this doesnt trigger the crash handler. +/// This causes a segfault on posix systems to try to get a core dump. fn crash() noreturn { switch (bun.Environment.os) { .windows => { @@ -1058,7 +1044,7 @@ pub var verbose_error_trace = false; fn handleErrorReturnTraceExtra(err: anyerror, maybe_trace: ?*std.builtin.StackTrace, comptime is_root: bool) void { if (!builtin.have_error_return_tracing) return; - if (!verbose_error_trace) return; + if (!verbose_error_trace and !is_root) return; if (maybe_trace) |trace| { // The format of the panic trace is slightly different in debug @@ -1077,20 +1063,32 @@ fn handleErrorReturnTraceExtra(err: anyerror, maybe_trace: ?*std.builtin.StackTr }; if (is_debug) { - Output.note(if (is_root) "CLI returned error.{s}" else "caught error.{s}:", .{@errorName(err)}); + Output.note( + if (is_root) + "CLI returned error.{s}" + else + "caught error.{s}:", + .{@errorName(err)}, + ); + Output.flush(); dumpStackTrace(trace.*); } else { - Output.prettyErrorln(if (is_root) - "trace: Bun returned with error.{s}: {}" - else - "trace: error.{s}: {}", .{ - @errorName(err), - TraceString{ - .trace = trace, - .reason = .{ .zig_error = err }, - .action = .view_trace, - }, - }); + const ts = TraceString{ + .trace = trace, + .reason = .{ .zig_error = err }, + .action = .view_trace, + }; + if (is_root) { + Output.prettyErrorln( + "trace string: {}", + .{ @errorName(err), ts }, + ); + } else { + Output.prettyErrorln( + "trace: error.{s}: {}", + .{ @errorName(err), ts }, + ); + } } } } From f906a08fbe29c0b38d8ace6027593d400e5bcd3e Mon Sep 17 00:00:00 2001 From: dave caruso Date: Thu, 11 Apr 2024 23:20:38 -0700 Subject: [PATCH 09/26] ya --- src/crash_handler.zig | 45 +++++++++++++++++++++++++++++++++---------- 1 file changed, 35 insertions(+), 10 deletions(-) diff --git a/src/crash_handler.zig b/src/crash_handler.zig index 8af51adcea76d2..5c267f81c67b96 100644 --- a/src/crash_handler.zig +++ b/src/crash_handler.zig @@ -28,7 +28,6 @@ const Output = bun.Output; const Global = bun.Global; const Features = bun.Analytics.Features; const debug = std.debug; -const dumpStackTrace = debug.dumpStackTrace; /// Set this to false if you want to disable all uses of this panic handler. /// This is useful for testing as a crash in here will not 'panicked during a panic'. @@ -252,7 +251,7 @@ pub fn crashHandler( // we're still holding the mutex but that's fine as we're going to // call abort() const stderr = std.io.getStdErr().writer(); - stderr.print("panic: {s}\n", .{reason}) catch std.os.abort(); + stderr.print("\npanic: {s}\n", .{reason}) catch std.os.abort(); stderr.print("panicked during a panic. Aborting.\n", .{}) catch std.os.abort(); }, 3 => { @@ -1063,13 +1062,23 @@ fn handleErrorReturnTraceExtra(err: anyerror, maybe_trace: ?*std.builtin.StackTr }; if (is_debug) { - Output.note( - if (is_root) - "CLI returned error.{s}" - else + if (is_root) { + Output.note( + "'main' returned error.{s}.{s}", + .{ + @errorName(err), + if (verbose_error_trace) + "" + else + " (release build will not have this trace by default)", + }, + ); + } else { + Output.note( "caught error.{s}:", - .{@errorName(err)}, - ); + .{@errorName(err)}, + ); + } Output.flush(); dumpStackTrace(trace.*); } else { @@ -1080,8 +1089,15 @@ fn handleErrorReturnTraceExtra(err: anyerror, maybe_trace: ?*std.builtin.StackTr }; if (is_root) { Output.prettyErrorln( - "trace string: {}", - .{ @errorName(err), ts }, + \\ + \\The trace for the above error has been captured as a URL, + \\which will direct you to fill out a GitHub issue for Bun. + \\This trace only includes functions in Bun, and contains none + \\of your code data: + \\{} + \\ + , + .{ts}, ); } else { Output.prettyErrorln( @@ -1103,3 +1119,12 @@ fn handleErrorReturnTraceExtra(err: anyerror, maybe_trace: ?*std.builtin.StackTr pub inline fn handleErrorReturnTrace(err: anyerror, maybe_trace: ?*std.builtin.StackTrace) void { handleErrorReturnTraceExtra(err, maybe_trace, false); } + +const stdDumpStackTrace = debug.dumpStackTrace; + +pub fn dumpStackTrace(trace: std.builtin.StackTrace) void { + if (bun.Environment.isWindows) { + // TODO: Zig's dump trace for windows is not fully reliable. + } + stdDumpStackTrace(trace); +} From 3e53dbb5397553ae6162266e5543e4b29880bc52 Mon Sep 17 00:00:00 2001 From: dave caruso Date: Fri, 12 Apr 2024 01:05:51 -0700 Subject: [PATCH 10/26] dsafds --- packages/bun-internal-test/src/banned.json | 2 +- src/crash_handler.zig | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/bun-internal-test/src/banned.json b/packages/bun-internal-test/src/banned.json index 4e09fb463c3cb3..279772e34e9d48 100644 --- a/packages/bun-internal-test/src/banned.json +++ b/packages/bun-internal-test/src/banned.json @@ -1,4 +1,4 @@ { "std.debug.assert": "Use bun.assert instead", - "std.debug.dumpStackTrace": "Use bun.handleErrorReturnTrace instead" + "std.debug.dumpStackTrace": "Use bun.handleErrorReturnTrace or bun.crash_handler.dumpStackTrace instead" } diff --git a/src/crash_handler.zig b/src/crash_handler.zig index 5c267f81c67b96..24ea18cfbfae76 100644 --- a/src/crash_handler.zig +++ b/src/crash_handler.zig @@ -96,6 +96,7 @@ pub fn crashHandler( begin_addr: ?usize, ) noreturn { @setCold(true); + std.crypto.random // If a segfault happens while panicking, we want it to actually segfault, not trigger // the handler. From fb77fee51403aec4ac62967dbcf2d52785be1889 Mon Sep 17 00:00:00 2001 From: dave caruso Date: Fri, 12 Apr 2024 02:42:52 -0700 Subject: [PATCH 11/26] a --- .github/ISSUE_TEMPLATE/6-crash-report.yml | 22 ++++++++++++++++++++++ src/crash_handler.zig | 1 - 2 files changed, 22 insertions(+), 1 deletion(-) create mode 100644 .github/ISSUE_TEMPLATE/6-crash-report.yml diff --git a/.github/ISSUE_TEMPLATE/6-crash-report.yml b/.github/ISSUE_TEMPLATE/6-crash-report.yml new file mode 100644 index 00000000000000..09779e0b31b059 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/6-crash-report.yml @@ -0,0 +1,22 @@ +name: Crash +description: CRASH +labels: [bug, crash] +body: + - type: markdown + attributes: + value: | + Thank you for submitting a crash report. It helps make Bun better. + someone should write better text in here please + + - type: textarea + id: remapped_trace + attributes: + label: Stack Trace + description: auto fill + validations: + required: true + + - type: textarea + attributes: + label: fsdfasdfasdfa + description: If this is a reproducible bug, please provide a code snippet or list of steps that can reproduce it. diff --git a/src/crash_handler.zig b/src/crash_handler.zig index 24ea18cfbfae76..5c267f81c67b96 100644 --- a/src/crash_handler.zig +++ b/src/crash_handler.zig @@ -96,7 +96,6 @@ pub fn crashHandler( begin_addr: ?usize, ) noreturn { @setCold(true); - std.crypto.random // If a segfault happens while panicking, we want it to actually segfault, not trigger // the handler. From f9d1ee6fa96b52592a654d056fe52d56453142ee Mon Sep 17 00:00:00 2001 From: dave caruso Date: Fri, 12 Apr 2024 23:03:51 -0700 Subject: [PATCH 12/26] wuh --- CMakeLists.txt | 2 + src/crash_handler.zig | 225 +++++++++++++++++---------------- src/js/internal-for-testing.ts | 6 + test/cli/crash-handler.test.ts | 8 ++ 4 files changed, 133 insertions(+), 108 deletions(-) create mode 100644 test/cli/crash-handler.test.ts diff --git a/CMakeLists.txt b/CMakeLists.txt index d28f75c56737c1..cf9b4a9a49f9c2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -555,6 +555,7 @@ else() add_compile_definitions("BUN_DEBUG=1") set(ASSERT_ENABLED "1") endif() + message(STATUS "Using WebKit from ${WEBKIT_DIR}") else() if(NOT EXISTS "${WEBKIT_DIR}/lib/${libWTF}.${STATIC_LIB_EXT}" OR NOT EXISTS "${WEBKIT_DIR}/lib/${libJavaScriptCore}.${STATIC_LIB_EXT}") @@ -762,6 +763,7 @@ if(NOT NO_CODEGEN) "${BUN_SRC}/js/node/*.ts" "${BUN_SRC}/js/thirdparty/*.js" "${BUN_SRC}/js/thirdparty/*.ts" + "${BUN_SRC}/js/internal-for-testing.ts" ) file(GLOB CODEGEN_FILES ${CONFIGURE_DEPENDS} "${BUN_CODEGEN_SRC}/*.ts") diff --git a/src/crash_handler.zig b/src/crash_handler.zig index 5c267f81c67b96..0ca162afe6c543 100644 --- a/src/crash_handler.zig +++ b/src/crash_handler.zig @@ -1,20 +1,19 @@ //! This file contains Bun's crash handler. In debug builds, we are able to -//! print backtraces that are mapped to source code. In a release mode, we do -//! not have that information in the binary. Bun's solution to this is called -//! a "trace string", a compressed and url-safe encoding of a captured -//! backtrace. Version 1 tracestrings contain the following information: +//! print backtraces that are mapped to source code. In release builds, we do +//! not have debug symbols in the binary. Bun's solution to this is called +//! a "trace string", a url with compressed encoding of the captured +//! backtrace. Version 1 trace strings contain the following information: //! //! - What version and commit of Bun captured the backtrace. //! - The platform the backtrace was captured on. //! - The list of addresses with ASLR removed, ready to be remapped. //! - If panicking, the message that was panicked with. -//! - List of feature-flags that were marked. //! //! These can be demangled using Bun's remapping API, which has cached //! versions of all debug symbols for all versions of Bun. Hosting this keeps //! users from having to download symbols, which can be very large. //! -//! The remapper is open source: https://github.com/oven-sh/bun-report +//! The remapper is open source: https://github.com/oven-sh/bun.report //! //! A lot of this handler is based on the Zig Standard Library implementation //! for std.debug.panicImpl and their code for gathering backtraces. @@ -50,7 +49,7 @@ var panic_mutex = std.Thread.Mutex{}; /// This is used to catch and handle panics triggered by the panic handler. threadlocal var panic_stage: usize = 0; -/// This structure and formatter must be kept in sync with `bun-report`'s decoder. +/// This structure and formatter must be kept in sync with `bun.report`'s decoder implementation. pub const CrashReason = union(enum) { /// From @panic() panic: []const u8, @@ -193,7 +192,6 @@ pub fn crashHandler( }; if (debug_trace) { - // TODO: On Windows, there are sometimes issues remapping information here: dumpStackTrace(trace.*); } else { writer.writeAll("Please report this panic as a GitHub issue using this link:\n") catch std.os.abort(); @@ -221,7 +219,7 @@ pub fn crashHandler( } // Be aware that this function only lets one thread return from it. - // This is important sot hat we do not try to run the reload logic twice. + // This is important so that we do not try to run the following reload logic twice. waitForOtherThreadToFinishPanicking(); if (bun.auto_reload_on_crash and @@ -253,6 +251,8 @@ pub fn crashHandler( const stderr = std.io.getStdErr().writer(); stderr.print("\npanic: {s}\n", .{reason}) catch std.os.abort(); stderr.print("panicked during a panic. Aborting.\n", .{}) catch std.os.abort(); + + std.debug.dumpCurrentStackTrace(@returnAddress()); }, 3 => { // Panicked while printing "Panicked during a panic." @@ -499,11 +499,11 @@ pub fn handleRootError(err: anyerror, error_return_trace: ?*std.builtin.StackTra }, error.MissingPackageJSON => { - Output.err( - "MissingPackageJSON", - "Bun could not find a package.json file.", + Output.errGeneric( + "Bun could not find a package.json file to install from", .{}, ); + Output.note("Run \"bun init\" to initialize a project", .{}); }, else => { @@ -744,7 +744,7 @@ const tracestr_header = std.fmt.comptimePrint( }, ); -const Address = union(enum) { +const StackLine = union(enum) { unknown, known: struct { address: i32, @@ -753,82 +753,35 @@ const Address = union(enum) { }, javascript, - pub fn writeEncoded(self: Address, writer: anytype) !void { - switch (self) { - .unknown => try writer.writeAll("_"), - .known => |known| { - if (known.object) |object| { - try SourceMap.encodeVLQ(1).writeTo(writer); - try SourceMap.encodeVLQ(@intCast(object.len)).writeTo(writer); - try writer.writeAll(object); - } - try SourceMap.encodeVLQ(known.address).writeTo(writer); - }, - .javascript => { - try writer.writeAll("="); - }, - } - } - - pub fn format(self: Address, comptime _: []const u8, _: std.fmt.FormatOptions, writer: anytype) !void { - switch (self) { - .unknown => try writer.print("unknown address", .{}), - .known => |known| try writer.print("0x{x} @ {s}", .{ known.address, known.object orelse "bun" }), - .javascript => try writer.print("javascript address", .{}), - } - } -}; - -const TraceString = struct { - trace: *std.builtin.StackTrace, - reason: CrashReason, - action: Action, - - const Action = enum { - /// Open a pre-filled GitHub issue with the expanded trace - open_issue, - /// View the trace with nothing else - view_trace, - }; - - pub fn format(self: TraceString, comptime _: []const u8, _: std.fmt.FormatOptions, writer: anytype) !void { - try encodeTraceString(self, writer); - } -}; - -fn encodeTraceString(opts: TraceString, writer: anytype) !void { - try writer.writeAll(report_base_url ++ tracestr_header); - - const image_path = if (bun.Environment.isWindows) bun.windows.exePathW() else null; - - var name_bytes: [512]u16 = undefined; - var name_bytes_utf8: [1024]u8 = undefined; - - for (opts.trace.instruction_addresses[0..opts.trace.index]) |addr| { - const address: Address = switch (bun.Environment.os) { - .windows => addr: { + pub fn fromAddress(addr: usize, name_bytes: []const u8) StackLine { + return switch (bun.Environment.os) { + .windows => { const module = bun.windows.getModuleHandleFromAddress(addr) orelse { // TODO: try to figure out of this is a JS stack frame - break :addr .{ .unknown = {} }; + return .{ .unknown = {} }; }; const base_address = @intFromPtr(module); - const name = bun.windows.getModuleNameW(module, &name_bytes) orelse - break :addr .{ .unknown = {} }; - break :addr .{ + var temp: [512]u16 = undefined; + const name = bun.windows.getModuleNameW(module, &temp) orelse + return .{ .unknown = {} }; + + const image_path = bun.windows.exePathW(); + + return .{ .mapped = .{ // To remap this, `pdb-addr2line --exe bun.pdb 0x123456` .address = addr - base_address, .object = if (!std.mem.eql(u16, name, image_path)) name: { const basename = name[std.mem.lastIndexOfAny(u16, name, "\\/") orelse 0 ..]; - break :name bun.strings.convertUTF8toUTF16InBuffer(&name_bytes_utf8, basename); + break :name bun.strings.convertUTF8toUTF16InBuffer(&name_bytes, basename); } else null, }, }; }, - .mac => addr: { + .mac => { // This code is slightly modified from std.debug.DebugInfo.lookupModuleNameDyld // https://github.com/ziglang/zig/blob/215de3ee67f75e2405c177b262cb5c1cd8c8e343/lib/std/debug.zig#L1783 const address = if (addr == 0) 0 else addr - 1; @@ -861,31 +814,37 @@ fn encodeTraceString(opts: TraceString, writer: anytype) !void { const seg_end = seg_start + segment_cmd.vmsize; if (original_address >= seg_start and original_address < seg_end) { // Subtract ASLR value for stable address - const stable_address: isize = @intCast(address - vmaddr_slide); - // Subtract base address for compactness - // To remap this, `llvm-symbolizer --obj bun-with-symbols --relative-address 0x123456` - const relative_address: i32 = @intCast(stable_address - @as(isize, @intCast(base_address))); - - if (relative_address < 0) break; - - const object = if (i == 0) - null // zero is the main binary - else - std.fs.path.basename(bun.sliceTo(std.c._dyld_get_image_name(i), 0)); - - break :addr .{ .known = .{ - .object = object, - .address = relative_address, - } }; + const stable_address: usize = @intCast(address - vmaddr_slide); + + if (i == 0) { + const image_relative_address = stable_address - seg_start; + if (image_relative_address > std.math.maxInt(i32)) { + return .{ .unknown = {} }; + } + + // To remap this, you have to add the offset (which is going to be 0x100000000), + // and then you can run it through `llvm-symbolizer --obj bun-with-symbols 0x123456` + // The reason we are subtracting this known offset is mostly just so that we can + // fit it within a signed 32-bit integer. The VLQs will be shorter too. + return .{ .known = .{ + .object = null, + .address = @intCast(image_relative_address), + } }; + } else { + // these libraries are not interesting, mark as unknown + return .{ .unknown = {} }; + } + + return .{ .unknown = {} }; } }, else => {}, }; } - break :addr .{ .unknown = {} }; + return .{ .unknown = {} }; }, - else => addr: { + else => { // This code is slightly modified from std.debug.DebugInfo.lookupModuleDl // https://github.com/ziglang/zig/blob/215de3ee67f75e2405c177b262cb5c1cd8c8e343/lib/std/debug.zig#L2024 var ctx: struct { @@ -893,7 +852,7 @@ fn encodeTraceString(opts: TraceString, writer: anytype) !void { address: usize, i: usize = 0, // Output - result: Address = .{ .unknown = {} }, + result: StackLine = .{ .unknown = {} }, } = .{ .address = addr -| 1 }; const CtxTy = @TypeOf(ctx); @@ -918,11 +877,62 @@ fn encodeTraceString(opts: TraceString, writer: anytype) !void { } }.callback) catch {}; - break :addr ctx.result; + return ctx.result; }, }; + } - try address.writeEncoded(writer); + pub fn writeEncoded(self: StackLine, writer: anytype) !void { + switch (self) { + .unknown => try writer.writeAll("_"), + .known => |known| { + if (known.object) |object| { + try SourceMap.encodeVLQ(1).writeTo(writer); + try SourceMap.encodeVLQ(@intCast(object.len)).writeTo(writer); + try writer.writeAll(object); + } + try SourceMap.encodeVLQ(known.address).writeTo(writer); + }, + .javascript => { + try writer.writeAll("="); + }, + } + } + + pub fn format(self: StackLine, comptime _: []const u8, _: std.fmt.FormatOptions, writer: anytype) !void { + switch (self) { + .unknown => try writer.print("unknown address", .{}), + .known => |known| try writer.print("0x{x} @ {s}", .{ known.address, known.object orelse "bun" }), + .javascript => try writer.print("javascript address", .{}), + } + } +}; + +const TraceString = struct { + trace: *std.builtin.StackTrace, + reason: CrashReason, + action: Action, + + const Action = enum { + /// Open a pre-filled GitHub issue with the expanded trace + open_issue, + /// View the trace with nothing else + view_trace, + }; + + pub fn format(self: TraceString, comptime _: []const u8, _: std.fmt.FormatOptions, writer: anytype) !void { + try encodeTraceString(self, writer); + } +}; + +fn encodeTraceString(opts: TraceString, writer: anytype) !void { + try writer.writeAll(report_base_url ++ tracestr_header); + + var name_bytes: [1024]u8 = undefined; + + for (opts.trace.instruction_addresses[0..opts.trace.index]) |addr| { + const line = StackLine.fromAddress(addr, &name_bytes); + try line.writeEncoded(writer); } try writer.writeAll(comptime zero_vlq: { @@ -930,7 +940,7 @@ fn encodeTraceString(opts: TraceString, writer: anytype) !void { break :zero_vlq vlq.bytes[0..vlq.len]; }); - // The following switch must be kept in sync with `bun-report`'s decoder. + // The following switch must be kept in sync with `bun.report`'s decoder implementation. switch (opts.reason) { .panic => |message| { try writer.writeByte('0'); @@ -995,7 +1005,7 @@ fn encodeTraceString(opts: TraceString, writer: anytype) !void { fn writeU64AsTwoVLQs(writer: anytype, addr: usize) !void { const first = SourceMap.encodeVLQ(@intCast((addr & 0xFFFFFFFF00000000) >> 32)); - const second = SourceMap.encodeVLQ(@intCast(addr & 0xFFFFFFFF)); + const second = SourceMap.encodeVLQ(@bitCast(@as(u32, @intCast(addr & 0xFFFFFFFF)))); try first.writeTo(writer); try second.writeTo(writer); } @@ -1008,14 +1018,6 @@ fn crash() noreturn { std.os.abort(); }, else => { - // Parts of this is copied from std.os.abort (linux non libc path) and WTFCrash - // Cause a segfault to make sure a core dump is generated if such is enabled - - // Only one thread may proceed to the rest of abort(). - const global = struct { - var abort_entered: bool = false; - }; - while (@cmpxchgWeak(bool, &global.abort_entered, false, true, .SeqCst, .SeqCst)) |_| {} // Install default handler so that the tkill below will terminate. const sigact = std.os.Sigaction{ .handler = .{ .handler = std.os.SIG.DFL }, .mask = std.os.empty_sigset, .flags = 0 }; @@ -1031,10 +1033,7 @@ fn crash() noreturn { std.os.sigaction(sig, &sigact, null) catch {}; } - @as(*allowzero volatile u8, @ptrFromInt(0xDEADBEEF)).* = 0; - std.os.raise(std.os.SIG.SEGV) catch {}; - @as(*allowzero volatile u8, @ptrFromInt(0)).* = 0; - std.c._exit(127); + @trap(); }, } } @@ -1128,3 +1127,13 @@ pub fn dumpStackTrace(trace: std.builtin.StackTrace) void { } stdDumpStackTrace(trace); } + +pub fn jsGetMachOImageZeroOffset(_: *bun.JSC.JSGlobalObject, _: *bun.JSC.CallFrame) bun.JSC.JSValue { + if (!bun.Environment.isMac) return .undefined; + + const header = std.c._dyld_get_image_header(0) orelse return .undefined; + const base_address = @intFromPtr(header); + const vmaddr_slide = std.c._dyld_get_image_vmaddr_slide(0); + + return bun.JSC.JSValue.jsNumber(base_address - vmaddr_slide); +} diff --git a/src/js/internal-for-testing.ts b/src/js/internal-for-testing.ts index ffbf7e7b4375c9..f0024d8c86617f 100644 --- a/src/js/internal-for-testing.ts +++ b/src/js/internal-for-testing.ts @@ -21,3 +21,9 @@ export const shellInternals = { lex: $newZigFunction("shell.zig", "TestingAPIs.shellLex", 1), parse: $newZigFunction("shell.zig", "TestingAPIs.shellParse", 1), }; + +export const getMachOImageZeroOffset = $newZigFunction( + "crash_handler.zig", + "jsGetMachOImageZeroOffset", + 0, +) as () => number; diff --git a/test/cli/crash-handler.test.ts b/test/cli/crash-handler.test.ts new file mode 100644 index 00000000000000..394602d7794fa2 --- /dev/null +++ b/test/cli/crash-handler.test.ts @@ -0,0 +1,8 @@ +import { getMachOImageZeroOffset } from "bun:internal-for-testing"; +import { test, expect } from "bun:test"; + +test.if(process.platform === "darwin")("macOS has the assumed image offset", () => { + // If this fails, then https://bun.report will be incorrect and the stack + // trace remappings will stop working. + expect(getMachOImageZeroOffset()).toBe(0x100000000); +}); From cbd5103a5bf5c77daa7d10fa8c39c88f47199843 Mon Sep 17 00:00:00 2001 From: dave caruso Date: Sat, 13 Apr 2024 03:11:36 -0700 Subject: [PATCH 13/26] a --- CMakeLists.txt | 1 - build.zig | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index cf9b4a9a49f9c2..01d83d2a943070 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1109,7 +1109,6 @@ else() -fvisibility=hidden -fvisibility-inlines-hidden -fno-rtti - -fno-omit-frame-pointer ) string(APPEND CMAKE_CXX_FLAGS " -std=c++2a") endif() diff --git a/build.zig b/build.zig index 3db9b5a54253ee..17a80e983cb9b9 100644 --- a/build.zig +++ b/build.zig @@ -394,7 +394,7 @@ pub fn build_(b: *Build) !void { obj.linkLibC(); obj.dll_export_fns = true; obj.strip = false; - obj.omit_frame_pointer = optimize != .Debug; + obj.omit_frame_pointer = false; obj.subsystem = .Console; // Disable stack probing on x86 so we don't need to include compiler_rt From 2d1c83078706b4b1e2284b9a92d1d2f673c167ed Mon Sep 17 00:00:00 2001 From: dave caruso Date: Sat, 13 Apr 2024 07:11:04 -0700 Subject: [PATCH 14/26] bru h --- CMakeLists.txt | 4 +- src/bun.js/api/BunObject.zig | 40 ---------- src/crash_handler.zig | 100 ++++++++++++++++++------- src/js/internal-for-testing.ts | 12 +-- src/main.zig | 2 +- test/cli/crash-handler.test.ts | 8 -- test/cli/run/fixture-crash.js | 8 ++ test/cli/run/run-crash-handler.test.ts | 19 +++++ 8 files changed, 110 insertions(+), 83 deletions(-) delete mode 100644 test/cli/crash-handler.test.ts create mode 100644 test/cli/run/fixture-crash.js create mode 100644 test/cli/run/run-crash-handler.test.ts diff --git a/CMakeLists.txt b/CMakeLists.txt index 01d83d2a943070..d7784fc0f13a80 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -738,7 +738,7 @@ if(NOT NO_CODEGEN) OUTPUT ${BUN_IDENTIFIER_CACHE_OUT} MAIN_DEPENDENCY "${BUN_SRC}/js_lexer/identifier_data.zig" DEPENDS "${BUN_SRC}/js_lexer/identifier_cache.zig" - COMMAND ${ZIG_COMPILER} run "${BUN_SRC}/js_lexer/identifier_data.zig" + COMMAND ${ZIG_COMPILER} run "--zig-lib-dir" "${ZIG_LIB_DIR}" "${BUN_SRC}/js_lexer/identifier_data.zig" VERBATIM COMMENT "Building Identifier Cache" ) @@ -1015,7 +1015,7 @@ endif() # --- clang and linker flags --- if(CMAKE_BUILD_TYPE STREQUAL "Debug") if(NOT WIN32) - target_compile_options(${bun} PUBLIC -g3 -O0 -gdwarf-4 + target_compile_options(${bun} PUBLIC -O0 -g -g3 -ggdb -gdwarf-4 -Werror=return-type -Werror=return-stack-address -Werror=implicit-function-declaration diff --git a/src/bun.js/api/BunObject.zig b/src/bun.js/api/BunObject.zig index 159f3255274c6a..c92e9ac3aa06e5 100644 --- a/src/bun.js/api/BunObject.zig +++ b/src/bun.js/api/BunObject.zig @@ -3325,11 +3325,6 @@ const UnsafeObject = struct { const object = JSValue.createEmptyObject(globalThis, 3); const fields = comptime .{ .gcAggressionLevel = &gcAggressionLevel, - .crashBySegfault = &crashBySegfault, - .crashByPanic = &crashByPanic, - .crashByUnreachable = &crashByUnreachable, - .crashBySafetyCheck = &crashBySafetyCheck, - .crashByCallGlobalError = &crashByCallGlobalError, .arrayBufferToString = &arrayBufferToString, .mimallocDump = &dump_mimalloc, }; @@ -3361,41 +3356,6 @@ const UnsafeObject = struct { return ret; } - pub fn crashBySegfault(_: *JSC.JSGlobalObject, _: *JSC.CallFrame) callconv(.C) JSC.JSValue { - @setRuntimeSafety(false); - const ptr: [*]align(1) u64 = @ptrFromInt(0xDEADBEEF); - ptr[0] = 0xDEADBEEF; - std.mem.doNotOptimizeAway(&ptr); - return .undefined; - } - - pub fn crashByPanic(_: *JSC.JSGlobalObject, _: *JSC.CallFrame) callconv(.C) JSC.JSValue { - // std.debug.panic("invoked crashByPanic() handler", .{}); - bun.crash_handler.panicImpl("invoked crashByPanic() handler", null, null); - } - - pub fn crashByUnreachable(_: *JSC.JSGlobalObject, _: *JSC.CallFrame) callconv(.C) JSC.JSValue { - @setRuntimeSafety(true); - unreachable; - } - - pub fn crashBySafetyCheck(_: *JSC.JSGlobalObject, _: *JSC.CallFrame) callconv(.C) JSC.JSValue { - @setRuntimeSafety(true); - const Y = enum(u8) { a }; - var a: u8 = 2; - const x: Y = @enumFromInt((&a).*); - _ = x; - return .undefined; - } - - pub fn crashByCallGlobalError(_: *JSC.JSGlobalObject, _: *JSC.CallFrame) callconv(.C) JSC.JSValue { - bun.crash_handler.handleRootError(error.Test, null); - } - - pub fn crashOutOfMemory(_: *JSC.JSGlobalObject, _: *JSC.CallFrame) callconv(.C) JSC.JSValue { - bun.outOfMemory(); - } - pub fn arrayBufferToString( globalThis: *JSC.JSGlobalObject, callframe: *JSC.CallFrame, diff --git a/src/crash_handler.zig b/src/crash_handler.zig index 0ca162afe6c543..99579d0fe72cc1 100644 --- a/src/crash_handler.zig +++ b/src/crash_handler.zig @@ -30,7 +30,7 @@ const debug = std.debug; /// Set this to false if you want to disable all uses of this panic handler. /// This is useful for testing as a crash in here will not 'panicked during a panic'. -pub const enabled = true; +pub const enable = true; const report_base_url = "https://bun.report/"; @@ -194,27 +194,29 @@ pub fn crashHandler( if (debug_trace) { dumpStackTrace(trace.*); } else { - writer.writeAll("Please report this panic as a GitHub issue using this link:\n") catch std.os.abort(); + writer.writeAll("tracestr:\n") catch std.os.abort(); + if (Output.enable_ansi_colors) { writer.print(Output.prettyFmt("", true), .{}) catch std.os.abort(); } + encodeTraceString( + .{ + .trace = trace, + .reason = reason, + .action = .open_issue, + }, + writer, + ) catch std.os.abort(); } - encodeTraceString( - .{ - .trace = trace, - .reason = reason, - .action = .open_issue, - }, - writer, - ) catch std.os.abort(); - if (Output.enable_ansi_colors) { writer.writeAll(Output.prettyFmt("\n", true)) catch std.os.abort(); } else { writer.writeAll("\n") catch std.os.abort(); } + writer.print("\nSearch GitHub issues https://bun.sh/issues or ask for #help in https://bun.sh/discord\n\n", .{}) catch std.os.abort(); + Output.flush(); } @@ -234,7 +236,7 @@ pub fn crashHandler( }); Output.flush(); - // It is important to be aware that this function *can* panic. + comptime std.debug.assert(void == @TypeOf(bun.reloadProcess(bun.default_allocator, false, true))); bun.reloadProcess(bun.default_allocator, false, true); } }, @@ -251,8 +253,6 @@ pub fn crashHandler( const stderr = std.io.getStdErr().writer(); stderr.print("\npanic: {s}\n", .{reason}) catch std.os.abort(); stderr.print("panicked during a panic. Aborting.\n", .{}) catch std.os.abort(); - - std.debug.dumpCurrentStackTrace(@returnAddress()); }, 3 => { // Panicked while printing "Panicked during a panic." @@ -541,7 +541,7 @@ fn panicBuiltin(msg: []const u8, error_return_trace: ?*std.builtin.StackTrace, b std.debug.panicImpl(error_return_trace, begin_addr, msg); } -pub const panic = if (enabled) panicImpl else panicBuiltin; +pub const panic = if (enable) panicImpl else panicBuiltin; const arch_display_string = if (bun.Environment.isAarch64) if (bun.Environment.isMac) "Silicon" else "arm64" @@ -592,7 +592,7 @@ pub fn updatePosixSegfaultHandler(act: ?*const std.os.Sigaction) !void { var windows_segfault_handle: ?windows.HANDLE = null; pub fn init() void { - if (!enabled) return; + if (!enable) return; switch (bun.Environment.os) { .windows => { windows_segfault_handle = windows.kernel32.AddVectoredExceptionHandler(0, handleSegfaultWindows); @@ -869,8 +869,10 @@ const StackLine = union(enum) { const seg_start = info.dlpi_addr +% phdr.p_vaddr; const seg_end = seg_start + phdr.p_memsz; if (context.address >= seg_start and context.address < seg_end) { - const name = bun.sliceTo(info.dlpi_name, 0) orelse ""; - std.debug.print("\nhi {d}, {s}, base = 0x{x}, ptr = 0x{x}", .{ context.i, name, info.dlpi_addr, context.address }); + // const name = bun.sliceTo(info.dlpi_name, 0) orelse ""; + { + @compileError("TODO"); + } return error.Found; } } @@ -901,8 +903,12 @@ const StackLine = union(enum) { pub fn format(self: StackLine, comptime _: []const u8, _: std.fmt.FormatOptions, writer: anytype) !void { switch (self) { - .unknown => try writer.print("unknown address", .{}), - .known => |known| try writer.print("0x{x} @ {s}", .{ known.address, known.object orelse "bun" }), + .unknown => try writer.print("???", .{}), + .known => |known| try writer.print("0x{x}{s}{s}", .{ + if (bun.Environment.isMac) @as(u64, known.address) + 0x100000000 else known.address, + if (known.object != null) " @ " else "", + known.object orelse "bun", + }), .javascript => try writer.print("javascript address", .{}), } } @@ -1128,12 +1134,52 @@ pub fn dumpStackTrace(trace: std.builtin.StackTrace) void { stdDumpStackTrace(trace); } -pub fn jsGetMachOImageZeroOffset(_: *bun.JSC.JSGlobalObject, _: *bun.JSC.CallFrame) bun.JSC.JSValue { - if (!bun.Environment.isMac) return .undefined; +pub const js_bindings = struct { + const JSC = bun.JSC; + const JSValue = JSC.JSValue; + + pub fn generate(global: *JSC.JSGlobalObject) JSC.JSValue { + const obj = JSC.JSValue.createEmptyObject(global, 3); + inline for (.{ + .{ "getMachOImageZeroOffset", jsGetMachOImageZeroOffset }, + .{ "segfault", jsSegfault }, + .{ "panic", jsPanic }, + .{ "rootError", jsRootError }, + .{ "outOfMemory", jsOutOfMemory }, + }) |tuple| { + const name = JSC.ZigString.static(tuple[0]); + obj.put(global, name, JSC.createCallback(global, name, 1, tuple[1])); + } + return obj; + } - const header = std.c._dyld_get_image_header(0) orelse return .undefined; - const base_address = @intFromPtr(header); - const vmaddr_slide = std.c._dyld_get_image_vmaddr_slide(0); + pub fn jsGetMachOImageZeroOffset(_: *bun.JSC.JSGlobalObject, _: *bun.JSC.CallFrame) callconv(.C) bun.JSC.JSValue { + if (!bun.Environment.isMac) return .undefined; - return bun.JSC.JSValue.jsNumber(base_address - vmaddr_slide); -} + const header = std.c._dyld_get_image_header(0) orelse return .undefined; + const base_address = @intFromPtr(header); + const vmaddr_slide = std.c._dyld_get_image_vmaddr_slide(0); + + return bun.JSC.JSValue.jsNumber(base_address - vmaddr_slide); + } + + pub fn jsSegfault(_: *JSC.JSGlobalObject, _: *JSC.CallFrame) callconv(.C) JSC.JSValue { + @setRuntimeSafety(false); + const ptr: [*]align(1) u64 = @ptrFromInt(0xDEADBEEF); + ptr[0] = 0xDEADBEEF; + std.mem.doNotOptimizeAway(&ptr); + return .undefined; + } + + pub fn jsPanic(_: *JSC.JSGlobalObject, _: *JSC.CallFrame) callconv(.C) JSC.JSValue { + bun.crash_handler.panicImpl("invoked crashByPanic() handler", null, null); + } + + pub fn jsRootError(_: *JSC.JSGlobalObject, _: *JSC.CallFrame) callconv(.C) JSC.JSValue { + bun.crash_handler.handleRootError(error.Test, null); + } + + pub fn jsOutOfMemory(_: *JSC.JSGlobalObject, _: *JSC.CallFrame) callconv(.C) JSC.JSValue { + bun.outOfMemory(); + } +}; diff --git a/src/js/internal-for-testing.ts b/src/js/internal-for-testing.ts index f0024d8c86617f..bce65a31916604 100644 --- a/src/js/internal-for-testing.ts +++ b/src/js/internal-for-testing.ts @@ -22,8 +22,10 @@ export const shellInternals = { parse: $newZigFunction("shell.zig", "TestingAPIs.shellParse", 1), }; -export const getMachOImageZeroOffset = $newZigFunction( - "crash_handler.zig", - "jsGetMachOImageZeroOffset", - 0, -) as () => number; +export const crash_handler = $zig("crash_handler.zig", "js_bindings.generate") as { + getMachOImageZeroOffset: () => number; + segfault: () => void; + panic: () => void; + rootError: () => void; + outOfMemory: () => void; +}; diff --git a/src/main.zig b/src/main.zig index 7af6b5bdd8504d..5f06509a2dcbcc 100644 --- a/src/main.zig +++ b/src/main.zig @@ -8,7 +8,7 @@ const Environment = bun.Environment; pub const panic = bun.crash_handler.panic; pub const std_options = struct { - pub const enable_segfault_handler = !bun.crash_handler.enabled; + pub const enable_segfault_handler = !bun.crash_handler.enable; }; pub const io_mode = .blocking; diff --git a/test/cli/crash-handler.test.ts b/test/cli/crash-handler.test.ts deleted file mode 100644 index 394602d7794fa2..00000000000000 --- a/test/cli/crash-handler.test.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { getMachOImageZeroOffset } from "bun:internal-for-testing"; -import { test, expect } from "bun:test"; - -test.if(process.platform === "darwin")("macOS has the assumed image offset", () => { - // If this fails, then https://bun.report will be incorrect and the stack - // trace remappings will stop working. - expect(getMachOImageZeroOffset()).toBe(0x100000000); -}); diff --git a/test/cli/run/fixture-crash.js b/test/cli/run/fixture-crash.js new file mode 100644 index 00000000000000..4d43da3d493a65 --- /dev/null +++ b/test/cli/run/fixture-crash.js @@ -0,0 +1,8 @@ +import { crash_handler } from "bun:internal-for-testing"; + +const approach = process.argv[2]; +if (approach in crash_handler) { + crash_handler[approach](); +} else { + console.error("usage: bun fixture-crash.js "); +} diff --git a/test/cli/run/run-crash-handler.test.ts b/test/cli/run/run-crash-handler.test.ts new file mode 100644 index 00000000000000..bb7de7a724a366 --- /dev/null +++ b/test/cli/run/run-crash-handler.test.ts @@ -0,0 +1,19 @@ +import { crash_handler } from "bun:internal-for-testing"; +import { test, expect } from "bun:test"; +import { bunExe, bunEnv } from "harness"; +import path from "path"; +const { getMachOImageZeroOffset } = crash_handler; + +test.if(process.platform === "darwin")("macOS has the assumed image offset", () => { + // If this fails, then https://bun.report will be incorrect and the stack + // trace remappings will stop working. + expect(getMachOImageZeroOffset()).toBe(0x100000000); +}); + +test("a panic dumps a trace string", async () => { + const result = Bun.spawnSync([bunExe(), path.join(import.meta.dir, "fixture-crash.js"), "panic"], { + env: { + ...bunEnv, + }, + }); +}); From a5c9979ddd3adc6304890b5363076473cea55557 Mon Sep 17 00:00:00 2001 From: dave caruso Date: Tue, 16 Apr 2024 14:16:28 -0700 Subject: [PATCH 15/26] yay --- CMakeLists.txt | 1 + src/{__global.zig => Global.zig} | 6 +++++- src/bun.zig | 2 +- src/crash_handler.zig | 16 +++++++--------- src/deps/zig | 2 +- src/string_immutable.zig | 14 ++++++++++++++ 6 files changed, 29 insertions(+), 12 deletions(-) rename src/{__global.zig => Global.zig} (94%) diff --git a/CMakeLists.txt b/CMakeLists.txt index d7784fc0f13a80..a064eb6acef51c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1109,6 +1109,7 @@ else() -fvisibility=hidden -fvisibility-inlines-hidden -fno-rtti + -fno-omit-frame-pointer ) string(APPEND CMAKE_CXX_FLAGS " -std=c++2a") endif() diff --git a/src/__global.zig b/src/Global.zig similarity index 94% rename from src/__global.zig rename to src/Global.zig index a7f4d1ff68ad17..a6d7aff63cde9e 100644 --- a/src/__global.zig +++ b/src/Global.zig @@ -64,13 +64,17 @@ pub inline fn getStartTime() i128 { return bun.start_time; } +extern "kernel32" fn SetThreadDescription(thread: std.os.windows.HANDLE, name: [*:0]const u16) callconv(std.os.windows.WINAPI) std.os.windows.HRESULT; + pub fn setThreadName(name: [:0]const u8) void { if (Environment.isLinux) { _ = std.os.prctl(.SET_NAME, .{@intFromPtr(name.ptr)}) catch {}; } else if (Environment.isMac) { _ = std.c.pthread_setname_np(name); } else if (Environment.isWindows) { - _ = std.os.SetThreadDescription(std.os.GetCurrentThread(), name); + var name_buf: [1024]u16 = undefined; + const name_wide = bun.strings.convertUTF8toUTF16InBufferZ(&name_buf, name); + _ = SetThreadDescription(std.os.windows.kernel32.GetCurrentThread(), name_wide); } } diff --git a/src/bun.zig b/src/bun.zig index 6b62226227a0fa..8ffe5b59c98380 100644 --- a/src/bun.zig +++ b/src/bun.zig @@ -53,7 +53,7 @@ pub const shell = struct { }; pub const Output = @import("./output.zig"); -pub const Global = @import("./__global.zig"); +pub const Global = @import("./global.zig"); // make this non-pub after https://github.com/ziglang/zig/issues/18462 is resolved pub const FileDescriptorInt = if (Environment.isBrowser) diff --git a/src/crash_handler.zig b/src/crash_handler.zig index 99579d0fe72cc1..ce1b87ba98429d 100644 --- a/src/crash_handler.zig +++ b/src/crash_handler.zig @@ -199,6 +199,7 @@ pub fn crashHandler( if (Output.enable_ansi_colors) { writer.print(Output.prettyFmt("", true), .{}) catch std.os.abort(); } + encodeTraceString( .{ .trace = trace, @@ -559,8 +560,6 @@ const metadata_version_line = std.fmt.comptimePrint( ); fn handleSegfaultPosix(sig: i32, info: *const std.os.siginfo_t, _: ?*const anyopaque) callconv(.C) noreturn { - resetSegfaultHandler(); - const addr = switch (bun.Environment.os) { .linux => @intFromPtr(info.fields.sigfault.addr), .mac => @intFromPtr(info.addr), @@ -628,8 +627,7 @@ pub fn resetSegfaultHandler() void { updatePosixSegfaultHandler(&act) catch {}; } -pub fn handleSegfaultWindows(info: windows.EXCEPTION_POINTERS) callconv(windows.WINAPI) c_long { - resetSegfaultHandler(); +pub fn handleSegfaultWindows(info: *windows.EXCEPTION_POINTERS) callconv(windows.WINAPI) c_long { crashHandler( switch (info.ExceptionRecord.ExceptionCode) { windows.EXCEPTION_DATATYPE_MISALIGNMENT => .{ .datatype_misalignment = {} }, @@ -753,7 +751,7 @@ const StackLine = union(enum) { }, javascript, - pub fn fromAddress(addr: usize, name_bytes: []const u8) StackLine { + pub fn fromAddress(addr: usize, name_bytes: []u8) StackLine { return switch (bun.Environment.os) { .windows => { const module = bun.windows.getModuleHandleFromAddress(addr) orelse { @@ -770,13 +768,13 @@ const StackLine = union(enum) { const image_path = bun.windows.exePathW(); return .{ - .mapped = .{ + .known = .{ // To remap this, `pdb-addr2line --exe bun.pdb 0x123456` - .address = addr - base_address, + .address = @intCast(addr - base_address), .object = if (!std.mem.eql(u16, name, image_path)) name: { - const basename = name[std.mem.lastIndexOfAny(u16, name, "\\/") orelse 0 ..]; - break :name bun.strings.convertUTF8toUTF16InBuffer(&name_bytes, basename); + const basename = name[std.mem.lastIndexOfAny(u16, name, &[_]u16{ '\\', '/' }) orelse 0 ..]; + break :name bun.strings.convertUTF16toUTF8InBuffer(name_bytes, basename) catch null; } else null, }, }; diff --git a/src/deps/zig b/src/deps/zig index 593a407f121a28..7fe33d94eaeb1a 160000 --- a/src/deps/zig +++ b/src/deps/zig @@ -1 +1 @@ -Subproject commit 593a407f121a2870e9c645da33c11db5e4331920 +Subproject commit 7fe33d94eaeb1af7705e9c5f43a3b243aa895436 diff --git a/src/string_immutable.zig b/src/string_immutable.zig index aee4513b461dcd..9ff5d75985097f 100644 --- a/src/string_immutable.zig +++ b/src/string_immutable.zig @@ -5402,6 +5402,20 @@ pub fn convertUTF8toUTF16InBuffer( return buf[0..result]; } +pub fn convertUTF8toUTF16InBufferZ( + buf: []u16, + input: []const u8, +) [:0]u16 { + // TODO: see convertUTF8toUTF16InBuffer + if (input.len == 0) { + buf[0] = 0; + return buf[0..0 :0]; + } + const result = bun.simdutf.convert.utf8.to.utf16.le(input, buf); + buf[result] = 0; + return buf[0..result :0]; +} + pub fn convertUTF16toUTF8InBuffer( buf: []u8, input: []const u16, From 1b48b1d1256a595179097ff2fd12bb77f139e1c5 Mon Sep 17 00:00:00 2001 From: dave caruso Date: Mon, 15 Apr 2024 14:50:25 -0700 Subject: [PATCH 16/26] window --- src/crash_handler.zig | 1 - 1 file changed, 1 deletion(-) diff --git a/src/crash_handler.zig b/src/crash_handler.zig index ce1b87ba98429d..8f39fe4cf3d782 100644 --- a/src/crash_handler.zig +++ b/src/crash_handler.zig @@ -1022,7 +1022,6 @@ fn crash() noreturn { std.os.abort(); }, else => { - // Install default handler so that the tkill below will terminate. const sigact = std.os.Sigaction{ .handler = .{ .handler = std.os.SIG.DFL }, .mask = std.os.empty_sigset, .flags = 0 }; inline for (.{ From 2f666c6aa0b8d040a69b91f39b813e6f6c20ed67 Mon Sep 17 00:00:00 2001 From: dave caruso Date: Mon, 15 Apr 2024 16:21:42 -0700 Subject: [PATCH 17/26] ok --- src/crash_handler.zig | 59 +++++++++++++++++++++++++------------------ src/deps/zig | 2 +- 2 files changed, 36 insertions(+), 25 deletions(-) diff --git a/src/crash_handler.zig b/src/crash_handler.zig index 8f39fe4cf3d782..b728a2f45659b3 100644 --- a/src/crash_handler.zig +++ b/src/crash_handler.zig @@ -119,7 +119,7 @@ pub fn crashHandler( // // To make the release-mode behavior easier to demo, debug mode // checks for this CLI flag. - const debug_trace = bun.Environment.isDebug and check_flag: { + const debug_trace = check_flag: { for (bun.argv) |arg| { if (bun.strings.eqlComptime(arg, "--debug-crash-handler-use-trace-string")) { break :check_flag false; @@ -129,24 +129,15 @@ pub fn crashHandler( }; if (!has_printed_message) { - has_printed_message = true; - Output.flush(); Output.Source.Stdio.restore(); writer.writeAll("=" ** 60 ++ "\n") catch std.os.abort(); + printMetadata(writer) catch std.os.abort(); - // Omit this blurb in debug builds because it is noise - if (!debug_trace) { - Output.err("oh no", - \\Bun has crashed. This indicates a bug in Bun, and - \\should be reported as a GitHub issue. - \\ - \\ - , .{}); - } Output.flush(); - printMetadata(writer) catch std.os.abort(); + } else { + Output.err("oh no", "multiple threads are crashing", .{}); } if (Output.enable_ansi_colors) { @@ -154,6 +145,11 @@ pub fn crashHandler( } writer.writeAll("panic") catch std.os.abort(); + + if (Output.enable_ansi_colors) { + writer.writeAll(Output.prettyFmt("", true)) catch std.os.abort(); + } + if (bun.CLI.Cli.is_main_thread) { writer.writeAll("(main thread)") catch std.os.abort(); } else switch (bun.Environment.os) { @@ -170,15 +166,10 @@ pub fn crashHandler( else => @compileError("TODO"), } - writer.print(": {}", .{reason}) catch std.os.abort(); - - if (Output.enable_ansi_colors) { - writer.writeAll(Output.prettyFmt("\n", true)) catch std.os.abort(); - } else { - writer.writeAll("\n") catch std.os.abort(); - } + Output.prettyErrorln(": {}", .{reason}); + Output.flush(); - var addr_buf: [32]usize = undefined; + var addr_buf: [10]usize = undefined; var trace_buf: std.builtin.StackTrace = undefined; // If a trace was not provided, compute one now @@ -194,7 +185,20 @@ pub fn crashHandler( if (debug_trace) { dumpStackTrace(trace.*); } else { - writer.writeAll("tracestr:\n") catch std.os.abort(); + if (!has_printed_message) { + has_printed_message = true; + Output.err("oh no", + \\Bun has crashed. This indicates a bug in Bun, not your code. + \\ + \\The following URL will redirect to create a new GitHub issue + \\from the stack trace of this crash. The report does not + \\include any of your own code or data, and you will be able + \\to edit the issue text before submitting it: + \\ + \\ + , .{}); + Output.flush(); + } if (Output.enable_ansi_colors) { writer.print(Output.prettyFmt("", true), .{}) catch std.os.abort(); @@ -216,7 +220,7 @@ pub fn crashHandler( writer.writeAll("\n") catch std.os.abort(); } - writer.print("\nSearch GitHub issues https://bun.sh/issues or ask for #help in https://bun.sh/discord\n\n", .{}) catch std.os.abort(); + // writer.print("\nSearch GitHub issues https://bun.sh/issues or ask for #help in https://bun.sh/discord\n\n", .{}) catch std.os.abort(); Output.flush(); } @@ -642,6 +646,10 @@ pub fn handleSegfaultWindows(info: *windows.EXCEPTION_POINTERS) callconv(windows } pub fn printMetadata(writer: anytype) !void { + if (Output.enable_ansi_colors) { + try writer.writeAll(Output.prettyFmt("", true)); + } + try writer.writeAll(metadata_version_line); { try writer.print("Args: ", .{}); @@ -688,6 +696,9 @@ pub fn printMetadata(writer: anytype) !void { }); } + if (Output.enable_ansi_colors) { + try writer.writeAll(Output.prettyFmt("", true)); + } try writer.writeAll("\n"); } @@ -1054,7 +1065,7 @@ fn handleErrorReturnTraceExtra(err: anyerror, maybe_trace: ?*std.builtin.StackTr // // To make the release-mode behavior easier to demo, debug mode // checks for this CLI flag. - const is_debug = bun.Environment.isDebug and check_flag: { + const is_debug = check_flag: { for (bun.argv) |arg| { if (bun.strings.eqlComptime(arg, "--debug-crash-handler-use-trace-string")) { break :check_flag false; diff --git a/src/deps/zig b/src/deps/zig index 7fe33d94eaeb1a..593a407f121a28 160000 --- a/src/deps/zig +++ b/src/deps/zig @@ -1 +1 @@ -Subproject commit 7fe33d94eaeb1af7705e9c5f43a3b243aa895436 +Subproject commit 593a407f121a2870e9c645da33c11db5e4331920 From a3c06e02f63a5818d8f95eef4feb49d007b98fcd Mon Sep 17 00:00:00 2001 From: dave caruso Date: Tue, 16 Apr 2024 16:53:56 -0700 Subject: [PATCH 18/26] alright --- src/bun.zig | 2 +- src/crash_handler.zig | 63 ++++++++++++++++++++++++++++++++--------- src/install/install.zig | 1 + 3 files changed, 52 insertions(+), 14 deletions(-) diff --git a/src/bun.zig b/src/bun.zig index b903810ad43f78..bd2c83b1c04dda 100644 --- a/src/bun.zig +++ b/src/bun.zig @@ -54,7 +54,7 @@ pub const shell = struct { }; pub const Output = @import("./output.zig"); -pub const Global = @import("./global.zig"); +pub const Global = @import("./Global.zig"); // make this non-pub after https://github.com/ziglang/zig/issues/18462 is resolved pub const FileDescriptorInt = if (Environment.isBrowser) diff --git a/src/crash_handler.zig b/src/crash_handler.zig index b728a2f45659b3..6e73e093b8421d 100644 --- a/src/crash_handler.zig +++ b/src/crash_handler.zig @@ -190,10 +190,8 @@ pub fn crashHandler( Output.err("oh no", \\Bun has crashed. This indicates a bug in Bun, not your code. \\ - \\The following URL will redirect to create a new GitHub issue - \\from the stack trace of this crash. The report does not - \\include any of your own code or data, and you will be able - \\to edit the issue text before submitting it: + \\To send a redacted crash report to Bun's team, + \\please file a GitHub issue using the link below: \\ \\ , .{}); @@ -204,6 +202,8 @@ pub fn crashHandler( writer.print(Output.prettyFmt("", true), .{}) catch std.os.abort(); } + writer.writeAll(" ") catch std.os.abort(); + encodeTraceString( .{ .trace = trace, @@ -212,6 +212,8 @@ pub fn crashHandler( }, writer, ) catch std.os.abort(); + + writer.writeAll(" ") catch std.os.abort(); } if (Output.enable_ansi_colors) { @@ -220,8 +222,6 @@ pub fn crashHandler( writer.writeAll("\n") catch std.os.abort(); } - // writer.print("\nSearch GitHub issues https://bun.sh/issues or ask for #help in https://bun.sh/discord\n\n", .{}) catch std.os.abort(); - Output.flush(); } @@ -868,6 +868,7 @@ const StackLine = union(enum) { std.os.dl_iterate_phdr(&ctx, error{Found}, struct { fn callback(info: *std.os.dl_phdr_info, _: usize, context: *CtxTy) !void { defer context.i += 1; + if (context.address < info.dlpi_addr) return; const phdrs = info.dlpi_phdr[0..info.dlpi_phnum]; for (phdrs) |*phdr| { @@ -879,9 +880,12 @@ const StackLine = union(enum) { const seg_end = seg_start + phdr.p_memsz; if (context.address >= seg_start and context.address < seg_end) { // const name = bun.sliceTo(info.dlpi_name, 0) orelse ""; - { - @compileError("TODO"); - } + context.result = .{ + .known = .{ + .address = @intCast(context.address - info.dlpi_addr), + .object = null, + }, + }; return error.Found; } } @@ -916,7 +920,7 @@ const StackLine = union(enum) { .known => |known| try writer.print("0x{x}{s}{s}", .{ if (bun.Environment.isMac) @as(u64, known.address) + 0x100000000 else known.address, if (known.object != null) " @ " else "", - known.object orelse "bun", + known.object orelse "", }), .javascript => try writer.print("javascript address", .{}), } @@ -1135,11 +1139,44 @@ pub inline fn handleErrorReturnTrace(err: anyerror, maybe_trace: ?*std.builtin.S const stdDumpStackTrace = debug.dumpStackTrace; +/// Version of the standard library dumpStackTrace that has some fallbacks for +/// cases where such logic fails to run. pub fn dumpStackTrace(trace: std.builtin.StackTrace) void { - if (bun.Environment.isWindows) { - // TODO: Zig's dump trace for windows is not fully reliable. + const stderr = std.io.getStdErr().writer(); + switch (bun.Environment.os) { + .windows => attempt_dump: { + // Windows has issues with opening the PDB file sometimes. + const debug_info = debug.getSelfDebugInfo() catch |err| { + stderr.print("Unable to dump stack trace: Unable to open debug info: {s}\n", .{@errorName(err)}) catch return; + break :attempt_dump; + }; + debug.writeStackTrace(trace, stderr, debug.getDebugInfoAllocator(), debug_info, std.io.tty.detectConfig(std.io.getStdErr())) catch |err| { + stderr.print("Unable to dump stack trace: {s}\n", .{@errorName(err)}) catch return; + break :attempt_dump; + }; + return; + }, + .linux => { + // Linux doesnt seem to be able to decode it's own debug info. + // TODO(@paperdave): see if zig 0.12 fixes this + }, + else => { + stdDumpStackTrace(trace); + return; + }, } - stdDumpStackTrace(trace); + // TODO: It would be reasonable, but hacky, to spawn LLVM-symbolizer here in + // order to get the demangled stack traces. + var name_bytes: [1024]u8 = undefined; + for (trace.instruction_addresses[0..trace.index]) |addr| { + const line = StackLine.fromAddress(addr, &name_bytes); + stderr.print("- {}\n", .{line}) catch break; + } + const program = switch (bun.Environment.os) { + .windows => "pdb-addr2line", + else => "llvm-symbolizer", + }; + stderr.print("note: Use " ++ program ++ " to demangle the above trace.\n", .{}) catch return; } pub const js_bindings = struct { diff --git a/src/install/install.zig b/src/install/install.zig index 23bf985a7058ab..670ac0511aac0b 100644 --- a/src/install/install.zig +++ b/src/install/install.zig @@ -66,6 +66,7 @@ pub const alignment_bytes_to_repeat_buffer = [_]u8{0} ** 144; const JSAst = bun.JSAst; pub fn initializeStore() void { + {@panic("lol");} if (initialized_store) { JSAst.Expr.Data.Store.reset(); JSAst.Stmt.Data.Store.reset(); From b730add2a600d0a04785a5951b797a567e265c97 Mon Sep 17 00:00:00 2001 From: dave caruso Date: Tue, 16 Apr 2024 17:19:49 -0700 Subject: [PATCH 19/26] oops --- src/crash_handler.zig | 2 +- src/install/install.zig | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/crash_handler.zig b/src/crash_handler.zig index 6e73e093b8421d..33e87e922122f8 100644 --- a/src/crash_handler.zig +++ b/src/crash_handler.zig @@ -213,7 +213,7 @@ pub fn crashHandler( writer, ) catch std.os.abort(); - writer.writeAll(" ") catch std.os.abort(); + writer.writeAll("\n") catch std.os.abort(); } if (Output.enable_ansi_colors) { diff --git a/src/install/install.zig b/src/install/install.zig index 670ac0511aac0b..23bf985a7058ab 100644 --- a/src/install/install.zig +++ b/src/install/install.zig @@ -66,7 +66,6 @@ pub const alignment_bytes_to_repeat_buffer = [_]u8{0} ** 144; const JSAst = bun.JSAst; pub fn initializeStore() void { - {@panic("lol");} if (initialized_store) { JSAst.Expr.Data.Store.reset(); JSAst.Stmt.Data.Store.reset(); From eb43700584a46f14128cc239a5b73f49eb19abab Mon Sep 17 00:00:00 2001 From: dave caruso Date: Tue, 16 Apr 2024 19:25:22 -0700 Subject: [PATCH 20/26] yeah --- .github/ISSUE_TEMPLATE/6-crash-report.yml | 4 +--- src/bun.js/module_loader.zig | 7 ------- src/crash_handler.zig | 13 ++++++------- 3 files changed, 7 insertions(+), 17 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/6-crash-report.yml b/.github/ISSUE_TEMPLATE/6-crash-report.yml index 09779e0b31b059..ab63fc36112164 100644 --- a/.github/ISSUE_TEMPLATE/6-crash-report.yml +++ b/.github/ISSUE_TEMPLATE/6-crash-report.yml @@ -6,17 +6,15 @@ body: attributes: value: | Thank you for submitting a crash report. It helps make Bun better. - someone should write better text in here please - type: textarea id: remapped_trace attributes: label: Stack Trace - description: auto fill validations: required: true - type: textarea attributes: - label: fsdfasdfasdfa + label: Any extra info? description: If this is a reproducible bug, please provide a code snippet or list of steps that can reproduce it. diff --git a/src/bun.js/module_loader.zig b/src/bun.js/module_loader.zig index 9f87dcb193a833..e0699471918841 100644 --- a/src/bun.js/module_loader.zig +++ b/src/bun.js/module_loader.zig @@ -2354,13 +2354,6 @@ pub const ModuleLoader = struct { if (!Environment.isDebug) { if (!is_allowed_to_use_internal_testing_apis) return null; - const is_outside_our_ci = brk: { - const repo = jsc_vm.bundler.env.get("GITHUB_REPOSITORY") orelse break :brk true; - break :brk !strings.endsWithComptime(repo, "/bun"); - }; - if (is_outside_our_ci) { - return null; - } } return jsSyntheticModule(.InternalForTesting, specifier); diff --git a/src/crash_handler.zig b/src/crash_handler.zig index 33e87e922122f8..0ecdecefed3226 100644 --- a/src/crash_handler.zig +++ b/src/crash_handler.zig @@ -119,7 +119,7 @@ pub fn crashHandler( // // To make the release-mode behavior easier to demo, debug mode // checks for this CLI flag. - const debug_trace = check_flag: { + const debug_trace = bun.Environment.isDebug or check_flag: { for (bun.argv) |arg| { if (bun.strings.eqlComptime(arg, "--debug-crash-handler-use-trace-string")) { break :check_flag false; @@ -1069,7 +1069,7 @@ fn handleErrorReturnTraceExtra(err: anyerror, maybe_trace: ?*std.builtin.StackTr // // To make the release-mode behavior easier to demo, debug mode // checks for this CLI flag. - const is_debug = check_flag: { + const is_debug = bun.Environment.isDebug or check_flag: { for (bun.argv) |arg| { if (bun.strings.eqlComptime(arg, "--debug-crash-handler-use-trace-string")) { break :check_flag false; @@ -1107,11 +1107,10 @@ fn handleErrorReturnTraceExtra(err: anyerror, maybe_trace: ?*std.builtin.StackTr if (is_root) { Output.prettyErrorln( \\ - \\The trace for the above error has been captured as a URL, - \\which will direct you to fill out a GitHub issue for Bun. - \\This trace only includes functions in Bun, and contains none - \\of your code data: - \\{} + \\To send a redacted crash report to Bun's team, + \\please file a GitHub issue using the link below: + \\ + \\ {} \\ , .{ts}, From cab8ebe39019110112d9ad5b7202724ffa4b68fd Mon Sep 17 00:00:00 2001 From: dave caruso Date: Tue, 16 Apr 2024 19:57:38 -0700 Subject: [PATCH 21/26] a --- test/cli/run/fixture-crash.js | 9 ++++++++- test/cli/run/run-crash-handler.test.ts | 7 +++++++ test/harness.ts | 2 ++ 3 files changed, 17 insertions(+), 1 deletion(-) diff --git a/test/cli/run/fixture-crash.js b/test/cli/run/fixture-crash.js index 4d43da3d493a65..105f5e39c54aad 100644 --- a/test/cli/run/fixture-crash.js +++ b/test/cli/run/fixture-crash.js @@ -1,4 +1,11 @@ -import { crash_handler } from "bun:internal-for-testing"; +let crash_handler; +try{ + crash_handler = require('bun:internal-for-testing'); +} catch { + console.error('This version of bun does not have internal-for-testing exposed'); + console.error('BUN_GARBAGE_COLLECTOR_LEVEL=0 BUN_FEATURE_FLAG_INTERNALS_FOR_TESTING=1 bun') + process.exit(1); +} const approach = process.argv[2]; if (approach in crash_handler) { diff --git a/test/cli/run/run-crash-handler.test.ts b/test/cli/run/run-crash-handler.test.ts index bb7de7a724a366..807c436fa548dd 100644 --- a/test/cli/run/run-crash-handler.test.ts +++ b/test/cli/run/run-crash-handler.test.ts @@ -16,4 +16,11 @@ test("a panic dumps a trace string", async () => { ...bunEnv, }, }); + + try { + expect(result.stderr.toString('utf-8')).toInclude('https://bun.report/'); + } catch (e) { + console.log(result.stderr.toString('utf-8')); + throw e; + } }); diff --git a/test/harness.ts b/test/harness.ts index 8acf65266344cf..a9d6276509785e 100644 --- a/test/harness.ts +++ b/test/harness.ts @@ -20,6 +20,8 @@ export const bunEnv: NodeJS.ProcessEnv = { TZ: "Etc/UTC", CI: "1", BUN_RUNTIME_TRANSPILER_CACHE_PATH: "0", + BUN_FEATURE_FLAG_INTERNAL_FOR_TESTING: '1', + BUN_GARBAGE_COLLECTOR_LEVEL: process.env.BUN_GARBAGE_COLLECTOR_LEVEL || '0', }; if (isWindows) { From fe2055a928fdace1edd3c08ea4e02aa6a4889916 Mon Sep 17 00:00:00 2001 From: dave caruso Date: Tue, 16 Apr 2024 20:53:22 -0700 Subject: [PATCH 22/26] a --- src/crash_handler.zig | 4 ++-- test/cli/run/fixture-crash.js | 8 ++++---- test/cli/run/run-crash-handler.test.ts | 15 +++++++++------ 3 files changed, 15 insertions(+), 12 deletions(-) diff --git a/src/crash_handler.zig b/src/crash_handler.zig index 0ecdecefed3226..b38e13a6e4008f 100644 --- a/src/crash_handler.zig +++ b/src/crash_handler.zig @@ -119,7 +119,7 @@ pub fn crashHandler( // // To make the release-mode behavior easier to demo, debug mode // checks for this CLI flag. - const debug_trace = bun.Environment.isDebug or check_flag: { + const debug_trace = bun.Environment.isDebug and check_flag: { for (bun.argv) |arg| { if (bun.strings.eqlComptime(arg, "--debug-crash-handler-use-trace-string")) { break :check_flag false; @@ -1069,7 +1069,7 @@ fn handleErrorReturnTraceExtra(err: anyerror, maybe_trace: ?*std.builtin.StackTr // // To make the release-mode behavior easier to demo, debug mode // checks for this CLI flag. - const is_debug = bun.Environment.isDebug or check_flag: { + const is_debug = bun.Environment.isDebug and check_flag: { for (bun.argv) |arg| { if (bun.strings.eqlComptime(arg, "--debug-crash-handler-use-trace-string")) { break :check_flag false; diff --git a/test/cli/run/fixture-crash.js b/test/cli/run/fixture-crash.js index 105f5e39c54aad..9a56452ac7022e 100644 --- a/test/cli/run/fixture-crash.js +++ b/test/cli/run/fixture-crash.js @@ -1,9 +1,9 @@ let crash_handler; -try{ - crash_handler = require('bun:internal-for-testing'); +try { + crash_handler = require("bun:internal-for-testing").crash_handler; } catch { - console.error('This version of bun does not have internal-for-testing exposed'); - console.error('BUN_GARBAGE_COLLECTOR_LEVEL=0 BUN_FEATURE_FLAG_INTERNALS_FOR_TESTING=1 bun') + console.error("This version of bun does not have internal-for-testing exposed"); + console.error("BUN_GARBAGE_COLLECTOR_LEVEL=0 BUN_FEATURE_FLAG_INTERNALS_FOR_TESTING=1 bun"); process.exit(1); } diff --git a/test/cli/run/run-crash-handler.test.ts b/test/cli/run/run-crash-handler.test.ts index 807c436fa548dd..7800bf3a2fcb3e 100644 --- a/test/cli/run/run-crash-handler.test.ts +++ b/test/cli/run/run-crash-handler.test.ts @@ -11,16 +11,19 @@ test.if(process.platform === "darwin")("macOS has the assumed image offset", () }); test("a panic dumps a trace string", async () => { - const result = Bun.spawnSync([bunExe(), path.join(import.meta.dir, "fixture-crash.js"), "panic"], { - env: { - ...bunEnv, + const result = Bun.spawnSync( + [bunExe(), path.join(import.meta.dir, "fixture-crash.js"), "panic", "--debug-crash-handler-use-trace-string"], + { + env: { + ...bunEnv, + }, }, - }); + ); try { - expect(result.stderr.toString('utf-8')).toInclude('https://bun.report/'); + expect(result.stderr.toString("utf-8")).toInclude("https://bun.report/"); } catch (e) { - console.log(result.stderr.toString('utf-8')); + console.log(result.stderr.toString("utf-8")); throw e; } }); From 739dbd6287cf691a6281ebaeb026544664bedd69 Mon Sep 17 00:00:00 2001 From: dave caruso Date: Wed, 17 Apr 2024 20:53:30 -0700 Subject: [PATCH 23/26] fix on window --- src/crash_handler.zig | 4 +++- src/deps/zig | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/crash_handler.zig b/src/crash_handler.zig index 0ecdecefed3226..16d16790d9c76e 100644 --- a/src/crash_handler.zig +++ b/src/crash_handler.zig @@ -1149,7 +1149,9 @@ pub fn dumpStackTrace(trace: std.builtin.StackTrace) void { stderr.print("Unable to dump stack trace: Unable to open debug info: {s}\n", .{@errorName(err)}) catch return; break :attempt_dump; }; - debug.writeStackTrace(trace, stderr, debug.getDebugInfoAllocator(), debug_info, std.io.tty.detectConfig(std.io.getStdErr())) catch |err| { + var arena = bun.ArenaAllocator.init(bun.default_allocator); + defer arena.deinit(); + debug.writeStackTrace(trace, stderr, arena.allocator(), debug_info, std.io.tty.detectConfig(std.io.getStdErr())) catch |err| { stderr.print("Unable to dump stack trace: {s}\n", .{@errorName(err)}) catch return; break :attempt_dump; }; diff --git a/src/deps/zig b/src/deps/zig index 593a407f121a28..7fe33d94eaeb1a 160000 --- a/src/deps/zig +++ b/src/deps/zig @@ -1 +1 @@ -Subproject commit 593a407f121a2870e9c645da33c11db5e4331920 +Subproject commit 7fe33d94eaeb1af7705e9c5f43a3b243aa895436 From cc5aa4d7081e368e0708d34d5cbc838352c506b4 Mon Sep 17 00:00:00 2001 From: dave caruso Date: Wed, 17 Apr 2024 10:46:08 -0700 Subject: [PATCH 24/26] OOM handling --- src/bun.zig | 6 +-- src/crash_handler.zig | 87 ++++++++++++++++++++++++++----------------- src/deps/zig | 2 +- 3 files changed, 56 insertions(+), 39 deletions(-) diff --git a/src/bun.zig b/src/bun.zig index bd2c83b1c04dda..78f4ea2d3e8c41 100644 --- a/src/bun.zig +++ b/src/bun.zig @@ -2729,9 +2729,7 @@ pub const Dirname = struct { pub noinline fn outOfMemory() noreturn { @setCold(true); - - // TODO: In the future, we should print jsc + mimalloc heap statistics - @panic("Bun ran out of memory!"); + crash_handler.crashHandler(.out_of_memory, null, @returnAddress()); } pub const is_heap_breakdown_enabled = Environment.allow_assert and Environment.isMac; @@ -3107,7 +3105,7 @@ noinline fn assertionFailure() noreturn { } @setCold(true); - Output.panic("Internal assertion failure. This is a bug in Bun.", .{}); + Output.panic("Internal assertion failure", .{}); } pub inline fn debugAssert(cheap_value_only_plz: bool) void { diff --git a/src/crash_handler.zig b/src/crash_handler.zig index dcda41ac0e74e6..8f126e032c9543 100644 --- a/src/crash_handler.zig +++ b/src/crash_handler.zig @@ -72,17 +72,20 @@ pub const CrashReason = union(enum) { /// Either `main` returned an error, or somewhere else in the code a trace string is printed. zig_error: anyerror, + out_of_memory, + pub fn format(self: CrashReason, comptime _: []const u8, _: std.fmt.FormatOptions, writer: anytype) !void { switch (self) { .panic => try writer.print("{s}", .{self.panic}), .@"unreachable" => try writer.writeAll("reached unreachable code"), - .segmentation_fault => |addr| try writer.print("Segmentation fault at address 0x{x}", .{addr}), - .illegal_instruction => |addr| try writer.print("Illegal instruction at address 0x{x}", .{addr}), - .bus_error => |addr| try writer.print("Bus error at address 0x{x}", .{addr}), - .floating_point_error => |addr| try writer.print("Floating point error at address 0x{x}", .{addr}), + .segmentation_fault => |addr| try writer.print("Segmentation fault at address 0x{X}", .{addr}), + .illegal_instruction => |addr| try writer.print("Illegal instruction at address 0x{X}", .{addr}), + .bus_error => |addr| try writer.print("Bus error at address 0x{X}", .{addr}), + .floating_point_error => |addr| try writer.print("Floating point error at address 0x{X}", .{addr}), .datatype_misalignment => try writer.writeAll("Unaligned memory access"), .stack_overflow => try writer.writeAll("Stack overflow"), .zig_error => |err| try writer.print("error.{s}", .{@errorName(err)}), + .out_of_memory => try writer.writeAll("Bun ran out of memory"), } } }; @@ -140,33 +143,36 @@ pub fn crashHandler( Output.err("oh no", "multiple threads are crashing", .{}); } - if (Output.enable_ansi_colors) { - writer.writeAll(Output.prettyFmt("", true)) catch std.os.abort(); - } + if (reason != .out_of_memory or debug_trace) { + if (Output.enable_ansi_colors) { + writer.writeAll(Output.prettyFmt("", true)) catch std.os.abort(); + } - writer.writeAll("panic") catch std.os.abort(); + writer.writeAll("panic") catch std.os.abort(); - if (Output.enable_ansi_colors) { - writer.writeAll(Output.prettyFmt("", true)) catch std.os.abort(); - } + if (Output.enable_ansi_colors) { + writer.writeAll(Output.prettyFmt("", true)) catch std.os.abort(); + } - if (bun.CLI.Cli.is_main_thread) { - writer.writeAll("(main thread)") catch std.os.abort(); - } else switch (bun.Environment.os) { - .windows => { - var name: std.os.windows.PWSTR = undefined; - const result = bun.windows.GetThreadDescription(std.os.windows.kernel32.GetCurrentThread(), &name); - if (std.os.windows.HRESULT_CODE(result) == .SUCCESS and name[0] != 0) { - writer.print("({})", .{bun.fmt.utf16(bun.span(name))}) catch std.os.abort(); - } else { - writer.print("(thread {d})", .{std.os.windows.kernel32.GetCurrentThreadId()}) catch std.os.abort(); - } - }, - .mac, .linux => {}, - else => @compileError("TODO"), + if (bun.CLI.Cli.is_main_thread) { + writer.writeAll("(main thread)") catch std.os.abort(); + } else switch (bun.Environment.os) { + .windows => { + var name: std.os.windows.PWSTR = undefined; + const result = bun.windows.GetThreadDescription(std.os.windows.kernel32.GetCurrentThread(), &name); + if (std.os.windows.HRESULT_CODE(result) == .SUCCESS and name[0] != 0) { + writer.print("({})", .{bun.fmt.utf16(bun.span(name))}) catch std.os.abort(); + } else { + writer.print("(thread {d})", .{std.os.windows.kernel32.GetCurrentThreadId()}) catch std.os.abort(); + } + }, + .mac, .linux => {}, + else => @compileError("TODO"), + } + + Output.prettyErrorln(": {}", .{reason}); } - Output.prettyErrorln(": {}", .{reason}); Output.flush(); var addr_buf: [10]usize = undefined; @@ -187,14 +193,25 @@ pub fn crashHandler( } else { if (!has_printed_message) { has_printed_message = true; - Output.err("oh no", - \\Bun has crashed. This indicates a bug in Bun, not your code. - \\ - \\To send a redacted crash report to Bun's team, - \\please file a GitHub issue using the link below: - \\ - \\ - , .{}); + if (reason == .out_of_memory) { + Output.err("oh no", + \\Bun has ran out of memory. + \\ + \\To send a redacted crash report to Bun's team, + \\please file a GitHub issue using the link below: + \\ + \\ + , .{}); + } else { + Output.err("oh no", + \\Bun has crashed. This indicates a bug in Bun, not your code. + \\ + \\To send a redacted crash report to Bun's team, + \\please file a GitHub issue using the link below: + \\ + \\ + , .{}); + } Output.flush(); } @@ -1015,6 +1032,8 @@ fn encodeTraceString(opts: TraceString, writer: anytype) !void { try writer.writeByte('8'); try writer.writeAll(@errorName(err)); }, + + .out_of_memory => try writer.writeByte('9'), } if (opts.action == .view_trace) { diff --git a/src/deps/zig b/src/deps/zig index 7fe33d94eaeb1a..593a407f121a28 160000 --- a/src/deps/zig +++ b/src/deps/zig @@ -1 +1 @@ -Subproject commit 7fe33d94eaeb1af7705e9c5f43a3b243aa895436 +Subproject commit 593a407f121a2870e9c645da33c11db5e4331920 From cc4fe81ebee474ffa849f1ee2d152b18bb903112 Mon Sep 17 00:00:00 2001 From: dave caruso Date: Wed, 17 Apr 2024 14:00:13 -0700 Subject: [PATCH 25/26] hi --- src/bun.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bun.zig b/src/bun.zig index 78f4ea2d3e8c41..5448c0ff36090d 100644 --- a/src/bun.zig +++ b/src/bun.zig @@ -1598,7 +1598,7 @@ pub fn reloadProcess( if (comptime Environment.isWindows) { // on windows we assume that we have a parent process that is monitoring us and will restart us if we exit with a magic exit code // see becomeWatcherManager - const rc = bun.windows.TerminateProcess(@ptrFromInt(std.math.maxInt(usize)), win32.watcher_reload_exit); + const rc = bun.windows.TerminateProcess(bun.windows.GetCurrentProcess(), win32.watcher_reload_exit); if (rc == 0) { const err = bun.windows.GetLastError(); if (may_return) { From 907b9009742a8f8a127073edafe732df83c62c71 Mon Sep 17 00:00:00 2001 From: dave caruso Date: Wed, 17 Apr 2024 15:30:47 -0700 Subject: [PATCH 26/26] review comments --- .github/ISSUE_TEMPLATE/6-crash-report.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE/6-crash-report.yml b/.github/ISSUE_TEMPLATE/6-crash-report.yml index ab63fc36112164..ccd53f3c3ba660 100644 --- a/.github/ISSUE_TEMPLATE/6-crash-report.yml +++ b/.github/ISSUE_TEMPLATE/6-crash-report.yml @@ -1,5 +1,5 @@ name: Crash -description: CRASH +description: Crash report labels: [bug, crash] body: - type: markdown