const std = @import("std"); const logger = @import("bun").logger; const bun = @import("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("bun").HTTP.AsyncHTTP; const CrashReporter = @import("./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 = std.mem.trimRight(u8, install_dir, std.fs.path.sep_str); } else if (bun.getenvZ("HOME")) |home_dir| { base_dir = std.mem.trimRight(u8, home_dir, std.fs.path.sep_str); } const file_path = std.fmt.bufPrintZ( &crash_reporter_path, "{s}/.bun-crash/v{s}-{d}.crash", .{ base_dir, Global.package_json_version, @intCast(u64, @max(std.time.milliTimestamp(), 0)) }, ) catch return; std.fs.cwd().makeDir(std.fs.path.dirname(std.mem.span(file_path)).?) catch {}; var file = std.fs.cwd().createFileZ(file_path, .{ .truncate = true }) catch return; this.file = std.io.bufferedWriter( file.writer(), ); this.file_path = std.mem.span(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("HOME")) |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); crash_report_writer.generateFile(); const cmd_label: string = if (CLI.cmd) |tag| @tagName(tag) else "Unknown"; const platform = if (Environment.isMac) "macOS" else "Linux"; const arch = if (Environment.isAarch64) if (Environment.isMac) "Silicon" else "arm64" else "x64"; var analytics_platform = Platform.forOS(); crash_report_writer.print( \\ \\----- bun meta ----- ++ "\nBun v" ++ Global.package_json_version_with_sha ++ " " ++ platform ++ " " ++ arch ++ " {s}\n" ++ \\{s}: {} \\ , .{ analytics_platform.version, cmd_label, Features.formatter(), }); const http_count = HTTP.active_requests_count.loadUnchecked(); 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; 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 = @ptrToInt(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(); // It only is a real crash report if it's not coming from Zig if (comptime !@import("bun").JSC.is_bindgen) { std.mem.doNotOptimizeAway(&Bun__crashReportWrite); Bun__crashReportDumpStackTrace(&crash_report_writer); } crash_report_writer.flush(); crash_report_writer.printPath(); } if (!had_printed_fatal) { 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; export fn Bun__crashReportWrite(ctx: *CrashReportWriter, bytes_ptr: [*]const u8, len: usize) void { 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; has_printed_fatal = true; 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 !@import("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", .{}); } 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 (comptime Environment.isDebug) { if (@errorReturnTrace()) |stack| { std.debug.dumpStackTrace(stack.*); } } std.c._exit(128 + @truncate(u8, @intCast(u8, @max(signal, 0)))); } pub noinline fn globalError(err: anyerror) noreturn { @setCold(true); if (@atomicRmw(bool, &globalError_ranOnce, .Xchg, true, .Monotonic)) { Global.exit(1); } switch (err) { 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.BundleFailed => { Output.prettyError( "\nBundleFailed", .{}, ); Global.exit(1); }, error.InvalidArgument, error.InstallFailed => { Global.exit(1); }, error.SystemFdQuotaExceeded => { 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 }, ); } } } Global.exit(1); }, error.@"Invalid Bunfig" => { Global.exit(1); }, error.ProcessFdQuotaExceeded => { 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 }, ); } } } 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 => { const limit = std.os.getrlimit(.NOFILE) catch std.mem.zeroes(std.os.rlimit); if (limit.cur > 0 and limit.cur < (8096 * 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 \\ , .{}, ); } Global.exit(1); } }, 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(); print_stacktrace: { var debug_info = std.debug.getSelfDebugInfo() catch break :print_stacktrace; var trace = @errorReturnTrace() orelse break :print_stacktrace; Output.disableBuffering(); std.debug.writeStackTrace(trace.*, Output.errorWriter(), default_allocator, debug_info, std.debug.detectTTYConfig(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", .{}, ); Global.exit(1); }, error.MissingValue => { Global.exit(1); }, else => {}, } Report.fatal(err, null); print_stacktrace: { var debug_info = std.debug.getSelfDebugInfo() catch break :print_stacktrace; var trace = @errorReturnTrace() orelse break :print_stacktrace; Output.disableBuffering(); std.debug.writeStackTrace(trace.*, Output.errorWriter(), default_allocator, debug_info, std.debug.detectTTYConfig(std.io.getStdErr())) catch break :print_stacktrace; } Global.exit(1); }