mirror of
https://github.com/oven-sh/bun
synced 2026-02-02 15:08:46 +00:00
1896 lines
75 KiB
Zig
1896 lines
75 KiB
Zig
//! This file contains Bun's crash handler. In debug builds, we are able to
|
|
//! 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.
|
|
//!
|
|
//! 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
|
|
//!
|
|
//! 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 VLQ = SourceMap.VLQ;
|
|
const windows = std.os.windows;
|
|
const Output = bun.Output;
|
|
const Global = bun.Global;
|
|
const Features = bun.Analytics.Features;
|
|
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 enable = true;
|
|
|
|
/// Overridable with BUN_CRASH_REPORT_URL environment variable.
|
|
const default_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.
|
|
/// The counter is incremented/decremented atomically.
|
|
var panicking = std.atomic.Value(u8).init(0);
|
|
|
|
// Locked to avoid interleaving panic messages from multiple threads.
|
|
// TODO: I don't think it's safe to lock/unlock a mutex inside a signal handler.
|
|
var panic_mutex = bun.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;
|
|
|
|
threadlocal var inside_native_plugin: ?[*:0]const u8 = null;
|
|
threadlocal var unsupported_uv_function: ?[*:0]const u8 = null;
|
|
|
|
/// This can be set by various parts of the codebase to indicate a broader
|
|
/// action being taken. It is printed when a crash happens, which can help
|
|
/// narrow down what the bug is. Example: "Crashed while parsing /path/to/file.js"
|
|
///
|
|
/// Some of these are enabled in release builds, which may encourage users to
|
|
/// attach the affected files to crash report. Others, which may have low crash
|
|
/// rate or only crash due to assertion failures, are debug-only. See `Action`.
|
|
pub threadlocal var current_action: ?Action = null;
|
|
|
|
var before_crash_handlers: std.ArrayListUnmanaged(struct { *anyopaque, *const OnBeforeCrash }) = .{};
|
|
|
|
var before_crash_handlers_mutex: bun.Mutex = .{};
|
|
|
|
const CPUFeatures = @import("./bun.js/bindings/CPUFeatures.zig");
|
|
|
|
/// 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,
|
|
|
|
/// "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,
|
|
|
|
/// 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(reason: CrashReason, comptime _: []const u8, _: std.fmt.FormatOptions, writer: anytype) !void {
|
|
switch (reason) {
|
|
.panic => |message| try writer.print("{s}", .{message}),
|
|
.@"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"),
|
|
.zig_error => |err| try writer.print("error.{s}", .{@errorName(err)}),
|
|
.out_of_memory => try writer.writeAll("Bun ran out of memory"),
|
|
}
|
|
}
|
|
};
|
|
|
|
pub const Action = union(enum) {
|
|
parse: []const u8,
|
|
visit: []const u8,
|
|
print: []const u8,
|
|
|
|
/// bun.bundle_v2.LinkerContext.generateCompileResultForJSChunk
|
|
bundle_generate_chunk: if (bun.Environment.isDebug) struct {
|
|
context: *const anyopaque, // unfortunate dependency loop workaround
|
|
chunk: *const bun.bundle_v2.Chunk,
|
|
part_range: *const bun.bundle_v2.PartRange,
|
|
|
|
pub fn linkerContext(data: *const @This()) *const bun.bundle_v2.LinkerContext {
|
|
return @ptrCast(@alignCast(data.context));
|
|
}
|
|
} else void,
|
|
|
|
resolver: if (bun.Environment.isDebug) struct {
|
|
source_dir: []const u8,
|
|
import_path: []const u8,
|
|
kind: bun.ImportKind,
|
|
} else void,
|
|
|
|
dlopen: []const u8,
|
|
|
|
pub fn format(act: Action, comptime _: []const u8, _: std.fmt.FormatOptions, writer: anytype) !void {
|
|
switch (act) {
|
|
.parse => |path| try writer.print("parsing {s}", .{path}),
|
|
.visit => |path| try writer.print("visiting {s}", .{path}),
|
|
.print => |path| try writer.print("printing {s}", .{path}),
|
|
.bundle_generate_chunk => |data| if (bun.Environment.isDebug) {
|
|
try writer.print(
|
|
\\generating bundler chunk
|
|
\\ chunk entry point: {?s}
|
|
\\ source: {?s}
|
|
\\ part range: {d}..{d}
|
|
,
|
|
.{
|
|
if (data.part_range.source_index.isValid()) data.linkerContext().parse_graph.input_files
|
|
.items(.source)[data.chunk.entry_point.source_index]
|
|
.path.text else null,
|
|
if (data.part_range.source_index.isValid()) data.linkerContext().parse_graph.input_files
|
|
.items(.source)[data.part_range.source_index.get()]
|
|
.path.text else null,
|
|
data.part_range.part_index_begin,
|
|
data.part_range.part_index_end,
|
|
},
|
|
);
|
|
},
|
|
.resolver => |res| if (bun.Environment.isDebug) {
|
|
try writer.print("resolving {s} from {s} ({s})", .{
|
|
res.import_path,
|
|
res.source_dir,
|
|
res.kind.label(),
|
|
});
|
|
},
|
|
.dlopen => |path| try writer.print("while loading native module: {s}", .{path}),
|
|
}
|
|
}
|
|
};
|
|
|
|
/// This function is invoked when a crash happens. 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 {
|
|
@branchHint(.cold);
|
|
|
|
if (bun.Environment.isDebug)
|
|
bun.Output.disableScopedDebugWriter();
|
|
|
|
var trace_str_buf = std.BoundedArray(u8, 1024){};
|
|
|
|
nosuspend switch (panic_stage) {
|
|
0 => {
|
|
bun.maybeHandlePanicDuringProcessReload();
|
|
|
|
panic_stage = 1;
|
|
_ = panicking.fetchAdd(1, .seq_cst);
|
|
|
|
if (before_crash_handlers_mutex.tryLock()) {
|
|
for (before_crash_handlers.items) |item| {
|
|
const ptr, const cb = item;
|
|
cb(ptr);
|
|
}
|
|
}
|
|
|
|
{
|
|
panic_mutex.lock();
|
|
defer panic_mutex.unlock();
|
|
|
|
// Use an raw unbuffered writer to stderr to avoid losing information on
|
|
// panic in a panic. There is also a possibility that `Output` related code
|
|
// is not configured correctly, so that would also mask the message.
|
|
//
|
|
// Output.errorWriter() is not used here because it may not be configured
|
|
// if the program crashes immediately at startup.
|
|
const writer = std.io.getStdErr().writer();
|
|
|
|
// 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;
|
|
}
|
|
}
|
|
// Act like release build when explicitly enabling reporting
|
|
if (isReportingEnabled()) break :check_flag false;
|
|
|
|
break :check_flag true;
|
|
};
|
|
|
|
if (!has_printed_message) {
|
|
Output.flush();
|
|
Output.Source.Stdio.restore();
|
|
|
|
writer.writeAll("=" ** 60 ++ "\n") catch std.posix.abort();
|
|
printMetadata(writer) catch std.posix.abort();
|
|
|
|
if (inside_native_plugin) |name| {
|
|
const native_plugin_name = name;
|
|
const fmt =
|
|
\\
|
|
\\Bun has encountered a crash while running the <red><d>"{s}"<r> native plugin.
|
|
\\
|
|
\\This indicates either a bug in the native plugin or in Bun.
|
|
\\
|
|
;
|
|
writer.print(Output.prettyFmt(fmt, true), .{native_plugin_name}) catch std.posix.abort();
|
|
} else if (bun.analytics.Features.unsupported_uv_function > 0) {
|
|
const name = unsupported_uv_function orelse "<unknown>";
|
|
const fmt =
|
|
\\Bun encountered a crash when running a NAPI module that tried to call
|
|
\\the <red>{s}<r> libuv function.
|
|
\\
|
|
\\Bun is actively working on supporting all libuv functions for POSIX
|
|
\\systems, please see this issue to track our progress:
|
|
\\
|
|
\\<cyan>https://github.com/oven-sh/bun/issues/18546<r>
|
|
\\
|
|
\\
|
|
;
|
|
writer.print(Output.prettyFmt(fmt, true), .{name}) catch std.posix.abort();
|
|
has_printed_message = true;
|
|
}
|
|
} else {
|
|
if (Output.enable_ansi_colors) {
|
|
writer.writeAll(Output.prettyFmt("<red>", true)) catch std.posix.abort();
|
|
}
|
|
writer.writeAll("oh no") catch std.posix.abort();
|
|
if (Output.enable_ansi_colors) {
|
|
writer.writeAll(Output.prettyFmt("<r><d>: multiple threads are crashing<r>\n", true)) catch std.posix.abort();
|
|
} else {
|
|
writer.writeAll(Output.prettyFmt(": multiple threads are crashing\n", true)) catch std.posix.abort();
|
|
}
|
|
}
|
|
|
|
if (reason != .out_of_memory or debug_trace) {
|
|
if (Output.enable_ansi_colors) {
|
|
writer.writeAll(Output.prettyFmt("<red>", true)) catch std.posix.abort();
|
|
}
|
|
|
|
writer.writeAll("panic") catch std.posix.abort();
|
|
|
|
if (Output.enable_ansi_colors) {
|
|
writer.writeAll(Output.prettyFmt("<r><d>", true)) catch std.posix.abort();
|
|
}
|
|
|
|
if (bun.CLI.Cli.is_main_thread) {
|
|
writer.writeAll("(main thread)") catch std.posix.abort();
|
|
} else switch (bun.Environment.os) {
|
|
.windows => {
|
|
var name: std.os.windows.PWSTR = undefined;
|
|
const result = bun.windows.GetThreadDescription(bun.windows.GetCurrentThread(), &name);
|
|
if (std.os.windows.HRESULT_CODE(result) == .SUCCESS and name[0] != 0) {
|
|
writer.print("({})", .{bun.fmt.utf16(bun.span(name))}) catch std.posix.abort();
|
|
} else {
|
|
writer.print("(thread {d})", .{bun.windows.GetCurrentThreadId()}) catch std.posix.abort();
|
|
}
|
|
},
|
|
.mac, .linux => {},
|
|
else => @compileError("TODO"),
|
|
}
|
|
|
|
writer.writeAll(": ") catch std.posix.abort();
|
|
if (Output.enable_ansi_colors) {
|
|
writer.writeAll(Output.prettyFmt("<r>", true)) catch std.posix.abort();
|
|
}
|
|
writer.print("{}\n", .{reason}) catch std.posix.abort();
|
|
}
|
|
|
|
if (current_action) |action| {
|
|
writer.print("Crashed while {}\n", .{action}) catch std.posix.abort();
|
|
}
|
|
|
|
var addr_buf: [10]usize = undefined;
|
|
var trace_buf: std.builtin.StackTrace = undefined;
|
|
|
|
// If a trace was not provided, compute one now
|
|
const trace = @as(?*std.builtin.StackTrace, if (error_return_trace) |ert|
|
|
if (ert.index > 0)
|
|
ert
|
|
else
|
|
null
|
|
else
|
|
null) 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;
|
|
};
|
|
|
|
if (debug_trace) {
|
|
has_printed_message = true;
|
|
|
|
dumpStackTrace(trace.*);
|
|
|
|
trace_str_buf.writer().print("{}", .{TraceString{
|
|
.trace = trace,
|
|
.reason = reason,
|
|
.action = .view_trace,
|
|
}}) catch std.posix.abort();
|
|
} else {
|
|
if (!has_printed_message) {
|
|
has_printed_message = true;
|
|
writer.writeAll("oh no") catch std.posix.abort();
|
|
if (Output.enable_ansi_colors) {
|
|
writer.writeAll(Output.prettyFmt("<r><d>:<r> ", true)) catch std.posix.abort();
|
|
} else {
|
|
writer.writeAll(Output.prettyFmt(": ", true)) catch std.posix.abort();
|
|
}
|
|
if (inside_native_plugin) |name| {
|
|
const native_plugin_name = name;
|
|
writer.print(Output.prettyFmt(
|
|
\\Bun has encountered a crash while running the <red><d>"{s}"<r> native plugin.
|
|
\\
|
|
\\To send a redacted crash report to Bun's team,
|
|
\\please file a GitHub issue using the link below:
|
|
\\
|
|
\\
|
|
, true), .{native_plugin_name}) catch std.posix.abort();
|
|
} else if (bun.analytics.Features.unsupported_uv_function > 0) {
|
|
const name = unsupported_uv_function orelse "<unknown>";
|
|
const fmt =
|
|
\\Bun encountered a crash when running a NAPI module that tried to call
|
|
\\the <red>{s}<r> libuv function.
|
|
\\
|
|
\\Bun is actively working on supporting all libuv functions for POSIX
|
|
\\systems, please see this issue to track our progress:
|
|
\\
|
|
\\<cyan>https://github.com/oven-sh/bun/issues/18546<r>
|
|
\\
|
|
\\
|
|
;
|
|
writer.print(Output.prettyFmt(fmt, true), .{name}) catch std.posix.abort();
|
|
} else if (reason == .out_of_memory) {
|
|
writer.writeAll(
|
|
\\Bun has run out of memory.
|
|
\\
|
|
\\To send a redacted crash report to Bun's team,
|
|
\\please file a GitHub issue using the link below:
|
|
\\
|
|
\\
|
|
) catch std.posix.abort();
|
|
} else {
|
|
writer.writeAll(
|
|
\\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:
|
|
\\
|
|
\\
|
|
) catch std.posix.abort();
|
|
}
|
|
}
|
|
|
|
if (Output.enable_ansi_colors) {
|
|
writer.print(Output.prettyFmt("<cyan>", true), .{}) catch std.posix.abort();
|
|
}
|
|
|
|
writer.writeAll(" ") catch std.posix.abort();
|
|
|
|
trace_str_buf.writer().print("{}", .{TraceString{
|
|
.trace = trace,
|
|
.reason = reason,
|
|
.action = .open_issue,
|
|
}}) catch std.posix.abort();
|
|
|
|
writer.writeAll(trace_str_buf.slice()) catch std.posix.abort();
|
|
|
|
writer.writeAll("\n") catch std.posix.abort();
|
|
}
|
|
|
|
if (Output.enable_ansi_colors) {
|
|
writer.writeAll(Output.prettyFmt("<r>\n", true)) catch std.posix.abort();
|
|
} else {
|
|
writer.writeAll("\n") catch std.posix.abort();
|
|
}
|
|
}
|
|
|
|
// Be aware that this function only lets one thread return from it.
|
|
// This is important so that we do not try to run the following reload logic twice.
|
|
waitForOtherThreadToFinishPanicking();
|
|
|
|
report(trace_str_buf.slice());
|
|
|
|
// At this point, the crash handler has performed it's job. Reset the segfault handler
|
|
// so that a crash will actually crash. We need this because we want the process to
|
|
// exit with a signal, and allow tools to be able to gather core dumps.
|
|
//
|
|
// This is done so late (in comparison to the Zig Standard Library's panic handler)
|
|
// because if multiple threads segfault (more often the case on Windows), we don't
|
|
// want another thread to interrupt the crashing of the first one.
|
|
resetSegfaultHandler();
|
|
|
|
if (bun.auto_reload_on_crash and
|
|
// Do not reload if the panic arose FROM the reload function.
|
|
!bun.isProcessReloadInProgressOnAnotherThread())
|
|
{
|
|
// attempt to prevent a double panic
|
|
bun.auto_reload_on_crash = false;
|
|
|
|
Output.prettyErrorln("<d>--- Bun is auto-restarting due to crash <d>[time: <b>{d}<r><d>] ---<r>", .{
|
|
@max(std.time.milliTimestamp(), 0),
|
|
});
|
|
Output.flush();
|
|
|
|
comptime bun.assert(void == @TypeOf(bun.reloadProcess(bun.default_allocator, false, true)));
|
|
bun.reloadProcess(bun.default_allocator, false, true);
|
|
}
|
|
},
|
|
inline 1, 2 => |t| {
|
|
if (t == 1) {
|
|
panic_stage = 2;
|
|
|
|
resetSegfaultHandler();
|
|
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("\npanic: {s}\n", .{reason}) catch std.posix.abort();
|
|
stderr.print("panicked during a panic. Aborting.\n", .{}) catch std.posix.abort();
|
|
},
|
|
3 => {
|
|
// Panicked while printing "Panicked during a panic."
|
|
panic_stage = 4;
|
|
},
|
|
else => {
|
|
// Panicked or otherwise looped into the panic handler while trying to exit.
|
|
std.posix.abort();
|
|
},
|
|
};
|
|
|
|
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 {
|
|
var show_trace = bun.Environment.isDebug;
|
|
|
|
switch (err) {
|
|
error.OutOfMemory => bun.outOfMemory(),
|
|
|
|
error.InvalidArgument,
|
|
error.@"Invalid Bunfig",
|
|
error.InstallFailed,
|
|
=> if (!show_trace) Global.exit(1),
|
|
|
|
error.SyntaxError => {
|
|
Output.err("SyntaxError", "An error occurred while parsing code", .{});
|
|
},
|
|
|
|
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.",
|
|
.{},
|
|
);
|
|
},
|
|
|
|
error.SystemFdQuotaExceeded => {
|
|
if (comptime bun.Environment.isPosix) {
|
|
const limit = if (std.posix.getrlimit(.NOFILE)) |limit| limit.cur else |_| null;
|
|
if (comptime bun.Environment.isMac) {
|
|
Output.prettyError(
|
|
\\<r><red>error<r>: Your computer ran out of file descriptors <d>(<red>SystemFdQuotaExceeded<r><d>)<r>
|
|
\\
|
|
\\<d>Current limit: {d}<r>
|
|
\\
|
|
\\To fix this, try running:
|
|
\\
|
|
\\ <cyan>sudo launchctl limit maxfiles 2147483646<r>
|
|
\\ <cyan>ulimit -n 2147483646<r>
|
|
\\
|
|
\\That will only work until you reboot.
|
|
\\
|
|
,
|
|
.{
|
|
bun.fmt.nullableFallback(limit, "<unknown>"),
|
|
},
|
|
);
|
|
} else {
|
|
Output.prettyError(
|
|
\\
|
|
\\<r><red>error<r>: Your computer ran out of file descriptors <d>(<red>SystemFdQuotaExceeded<r><d>)<r>
|
|
\\
|
|
\\<d>Current limit: {d}<r>
|
|
\\
|
|
\\To fix this, try running:
|
|
\\
|
|
\\ <cyan>sudo echo -e "\nfs.file-max=2147483646\n" >> /etc/sysctl.conf<r>
|
|
\\ <cyan>sudo sysctl -p<r>
|
|
\\ <cyan>ulimit -n 2147483646<r>
|
|
\\
|
|
,
|
|
.{
|
|
bun.fmt.nullableFallback(limit, "<unknown>"),
|
|
},
|
|
);
|
|
|
|
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:
|
|
\\
|
|
\\ <cyan>{s} soft nofile 2147483646<r>
|
|
\\ <cyan>{s} hard nofile 2147483646<r>
|
|
\\
|
|
,
|
|
.{ user, user },
|
|
);
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
Output.prettyError(
|
|
\\<r><red>error<r>: Your computer ran out of file descriptors <d>(<red>SystemFdQuotaExceeded<r><d>)<r>
|
|
,
|
|
.{},
|
|
);
|
|
}
|
|
},
|
|
|
|
error.ProcessFdQuotaExceeded => {
|
|
if (comptime bun.Environment.isPosix) {
|
|
const limit = if (std.posix.getrlimit(.NOFILE)) |limit| limit.cur else |_| null;
|
|
if (comptime bun.Environment.isMac) {
|
|
Output.prettyError(
|
|
\\
|
|
\\<r><red>error<r>: bun ran out of file descriptors <d>(<red>ProcessFdQuotaExceeded<r><d>)<r>
|
|
\\
|
|
\\<d>Current limit: {d}<r>
|
|
\\
|
|
\\To fix this, try running:
|
|
\\
|
|
\\ <cyan>ulimit -n 2147483646<r>
|
|
\\
|
|
\\You may also need to run:
|
|
\\
|
|
\\ <cyan>sudo launchctl limit maxfiles 2147483646<r>
|
|
\\
|
|
,
|
|
.{
|
|
bun.fmt.nullableFallback(limit, "<unknown>"),
|
|
},
|
|
);
|
|
} else {
|
|
Output.prettyError(
|
|
\\
|
|
\\<r><red>error<r>: bun ran out of file descriptors <d>(<red>ProcessFdQuotaExceeded<r><d>)<r>
|
|
\\
|
|
\\<d>Current limit: {d}<r>
|
|
\\
|
|
\\To fix this, try running:
|
|
\\
|
|
\\ <cyan>ulimit -n 2147483646<r>
|
|
\\
|
|
\\That will only work for the current shell. To fix this for the entire system, run:
|
|
\\
|
|
\\ <cyan>sudo echo -e "\nfs.file-max=2147483646\n" >> /etc/sysctl.conf<r>
|
|
\\ <cyan>sudo sysctl -p<r>
|
|
\\
|
|
,
|
|
.{
|
|
bun.fmt.nullableFallback(limit, "<unknown>"),
|
|
},
|
|
);
|
|
|
|
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:
|
|
\\
|
|
\\ <cyan>{s} soft nofile 2147483646<r>
|
|
\\ <cyan>{s} hard nofile 2147483646<r>
|
|
\\
|
|
,
|
|
.{ user, user },
|
|
);
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
Output.prettyErrorln(
|
|
\\<r><red>error<r>: bun ran out of file descriptors <d>(<red>ProcessFdQuotaExceeded<r><d>)<r>
|
|
,
|
|
.{},
|
|
);
|
|
}
|
|
},
|
|
|
|
// The usage of `unreachable` in Zig's std.posix may cause the file descriptor problem to show up as other errors
|
|
error.NotOpenForReading, error.Unexpected => {
|
|
if (comptime bun.Environment.isPosix) {
|
|
const limit = std.posix.getrlimit(.NOFILE) catch std.mem.zeroes(std.posix.rlimit);
|
|
|
|
if (limit.cur > 0 and limit.cur < (8192 * 2)) {
|
|
Output.prettyError(
|
|
\\
|
|
\\<r><red>error<r>: An unknown error occurred, possibly due to low max file descriptors <d>(<red>Unexpected<r><d>)<r>
|
|
\\
|
|
\\<d>Current limit: {d}<r>
|
|
\\
|
|
\\To fix this, try running:
|
|
\\
|
|
\\ <cyan>ulimit -n 2147483646<r>
|
|
\\
|
|
,
|
|
.{
|
|
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:
|
|
\\
|
|
\\ <cyan>{s} soft nofile 2147483646<r>
|
|
\\ <cyan>{s} hard nofile 2147483646<r>
|
|
\\
|
|
,
|
|
.{
|
|
user,
|
|
user,
|
|
},
|
|
);
|
|
}
|
|
}
|
|
} else if (bun.Environment.isMac) {
|
|
Output.prettyError(
|
|
\\
|
|
\\If that still doesn't work, you may need to run:
|
|
\\
|
|
\\ <cyan>sudo launchctl limit maxfiles 2147483646<r>
|
|
\\
|
|
,
|
|
.{},
|
|
);
|
|
}
|
|
} else {
|
|
Output.errGeneric(
|
|
"An unknown error occurred <d>(<red>{s}<r><d>)<r>",
|
|
.{@errorName(err)},
|
|
);
|
|
show_trace = true;
|
|
}
|
|
} else {
|
|
Output.errGeneric(
|
|
\\An unknown error occurred <d>(<red>{s}<r><d>)<r>
|
|
,
|
|
.{@errorName(err)},
|
|
);
|
|
show_trace = true;
|
|
}
|
|
},
|
|
|
|
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.errGeneric(
|
|
"Bun could not find a package.json file to install from",
|
|
.{},
|
|
);
|
|
Output.note("Run \"bun init\" to initialize a project", .{});
|
|
},
|
|
|
|
else => {
|
|
Output.errGeneric(
|
|
if (bun.Environment.isDebug)
|
|
"'main' returned <red>error.{s}<r>"
|
|
else
|
|
"An internal error occurred (<red>{s}<r>)",
|
|
.{@errorName(err)},
|
|
);
|
|
show_trace = true;
|
|
},
|
|
}
|
|
|
|
if (show_trace) {
|
|
verbose_error_trace = show_trace;
|
|
handleErrorReturnTraceExtra(err, error_return_trace, true);
|
|
}
|
|
|
|
Global.exit(1);
|
|
}
|
|
|
|
pub fn panicImpl(msg: []const u8, error_return_trace: ?*std.builtin.StackTrace, begin_addr: ?usize) noreturn {
|
|
@branchHint(.cold);
|
|
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 {
|
|
std.debug.panicImpl(error_return_trace, begin_addr, msg);
|
|
}
|
|
|
|
pub const panic = if (enable) panicImpl else panicBuiltin;
|
|
|
|
pub fn reportBaseUrl() []const u8 {
|
|
const static = struct {
|
|
var base_url: ?[]const u8 = null;
|
|
};
|
|
return static.base_url orelse {
|
|
const computed = computed: {
|
|
if (bun.getenvZ("BUN_CRASH_REPORT_URL")) |url| {
|
|
break :computed bun.strings.withoutTrailingSlash(url);
|
|
}
|
|
break :computed default_report_base_url;
|
|
};
|
|
static.base_url = computed;
|
|
return computed;
|
|
};
|
|
}
|
|
|
|
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 {s}v{s} {s} {s}{s}\n",
|
|
.{
|
|
if (bun.Environment.isDebug) "Debug " else if (bun.Environment.is_canary) "Canary " else "",
|
|
Global.package_json_version_with_sha,
|
|
bun.Environment.os.displayString(),
|
|
arch_display_string,
|
|
if (bun.Environment.baseline) " (baseline)" else "",
|
|
},
|
|
);
|
|
|
|
fn handleSegfaultPosix(sig: i32, info: *const std.posix.siginfo_t, _: ?*const anyopaque) callconv(.C) noreturn {
|
|
const addr = switch (bun.Environment.os) {
|
|
.linux => @intFromPtr(info.fields.sigfault.addr),
|
|
.mac => @intFromPtr(info.addr),
|
|
else => @compileError(unreachable),
|
|
};
|
|
|
|
crashHandler(
|
|
switch (sig) {
|
|
std.posix.SIG.SEGV => .{ .segmentation_fault = addr },
|
|
std.posix.SIG.ILL => .{ .illegal_instruction = addr },
|
|
std.posix.SIG.BUS => .{ .bus_error = addr },
|
|
std.posix.SIG.FPE => .{ .floating_point_error = addr },
|
|
|
|
// we do not register this handler for other signals
|
|
else => unreachable,
|
|
},
|
|
null,
|
|
@returnAddress(),
|
|
);
|
|
}
|
|
|
|
var did_register_sigaltstack = false;
|
|
var sigaltstack: [512 * 1024]u8 = undefined;
|
|
|
|
pub fn updatePosixSegfaultHandler(act: ?*std.posix.Sigaction) !void {
|
|
if (act) |act_| {
|
|
if (!did_register_sigaltstack) {
|
|
var stack: std.c.stack_t = .{
|
|
.flags = 0,
|
|
.size = sigaltstack.len,
|
|
.sp = &sigaltstack,
|
|
};
|
|
|
|
if (std.c.sigaltstack(&stack, null) == 0) {
|
|
act_.flags |= std.posix.SA.ONSTACK;
|
|
did_register_sigaltstack = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
std.posix.sigaction(std.posix.SIG.SEGV, act, null);
|
|
std.posix.sigaction(std.posix.SIG.ILL, act, null);
|
|
std.posix.sigaction(std.posix.SIG.BUS, act, null);
|
|
std.posix.sigaction(std.posix.SIG.FPE, act, null);
|
|
}
|
|
|
|
var windows_segfault_handle: ?windows.HANDLE = null;
|
|
|
|
pub fn resetOnPosix() void {
|
|
var act = std.posix.Sigaction{
|
|
.handler = .{ .sigaction = handleSegfaultPosix },
|
|
.mask = std.posix.empty_sigset,
|
|
.flags = (std.posix.SA.SIGINFO | std.posix.SA.RESTART | std.posix.SA.RESETHAND),
|
|
};
|
|
updatePosixSegfaultHandler(&act) catch {};
|
|
}
|
|
|
|
pub fn init() void {
|
|
if (!enable) return;
|
|
switch (bun.Environment.os) {
|
|
.windows => {
|
|
windows_segfault_handle = windows.kernel32.AddVectoredExceptionHandler(0, handleSegfaultWindows);
|
|
},
|
|
.mac, .linux => {
|
|
resetOnPosix();
|
|
},
|
|
else => @compileError("TODO"),
|
|
}
|
|
}
|
|
|
|
pub fn resetSegfaultHandler() void {
|
|
if (!enable) return;
|
|
|
|
if (bun.Environment.os == .windows) {
|
|
if (windows_segfault_handle) |handle| {
|
|
const rc = windows.kernel32.RemoveVectoredExceptionHandler(handle);
|
|
windows_segfault_handle = null;
|
|
bun.assert(rc != 0);
|
|
}
|
|
return;
|
|
}
|
|
|
|
var act = std.posix.Sigaction{
|
|
.handler = .{ .handler = std.posix.SIG.DFL },
|
|
.mask = std.posix.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 {
|
|
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 = {} },
|
|
|
|
// exception used for thread naming
|
|
// https://learn.microsoft.com/en-us/previous-versions/visualstudio/visual-studio-2017/debugger/how-to-set-a-thread-name-in-native-code?view=vs-2017#set-a-thread-name-by-throwing-an-exception
|
|
// related commit
|
|
// https://github.com/go-delve/delve/pull/1384
|
|
bun.windows.MS_VC_EXCEPTION => return bun.windows.EXCEPTION_CONTINUE_EXECUTION,
|
|
|
|
else => return windows.EXCEPTION_CONTINUE_SEARCH,
|
|
},
|
|
null,
|
|
@intFromPtr(info.ExceptionRecord.ExceptionAddress),
|
|
);
|
|
}
|
|
|
|
extern "c" fn gnu_get_libc_version() ?[*:0]const u8;
|
|
|
|
pub fn printMetadata(writer: anytype) !void {
|
|
if (Output.enable_ansi_colors) {
|
|
try writer.writeAll(Output.prettyFmt("<r><d>", true));
|
|
}
|
|
|
|
var is_ancient_cpu = false;
|
|
|
|
try writer.writeAll(metadata_version_line);
|
|
{
|
|
const platform = bun.Analytics.GenerateHeader.GeneratePlatform.forOS();
|
|
const cpu_features = CPUFeatures.get();
|
|
if (bun.Environment.isLinux and !bun.Environment.isMusl) {
|
|
const version = gnu_get_libc_version() orelse "";
|
|
const kernel_version = bun.Analytics.GenerateHeader.GeneratePlatform.kernelVersion();
|
|
if (platform.os == .wsl) {
|
|
try writer.print("WSL Kernel v{d}.{d}.{d} | glibc v{s}\n", .{ kernel_version.major, kernel_version.minor, kernel_version.patch, bun.sliceTo(version, 0) });
|
|
} else {
|
|
try writer.print("Linux Kernel v{d}.{d}.{d} | glibc v{s}\n", .{ kernel_version.major, kernel_version.minor, kernel_version.patch, bun.sliceTo(version, 0) });
|
|
}
|
|
} else if (bun.Environment.isLinux and bun.Environment.isMusl) {
|
|
const kernel_version = bun.Analytics.GenerateHeader.GeneratePlatform.kernelVersion();
|
|
try writer.print("Linux Kernel v{d}.{d}.{d} | musl\n", .{ kernel_version.major, kernel_version.minor, kernel_version.patch });
|
|
} else if (bun.Environment.isMac) {
|
|
try writer.print("macOS v{s}\n", .{platform.version});
|
|
} else if (bun.Environment.isWindows) {
|
|
try writer.print("Windows v{s}\n", .{std.zig.system.windows.detectRuntimeVersion()});
|
|
}
|
|
|
|
if (bun.Environment.isX64) {
|
|
is_ancient_cpu = !cpu_features.hasAnyAVX();
|
|
}
|
|
|
|
if (!cpu_features.isEmpty()) {
|
|
try writer.print("CPU: {}\n", .{cpu_features});
|
|
}
|
|
|
|
try writer.print("Args: ", .{});
|
|
var arg_chars_left: usize = if (bun.Environment.isDebug) 4096 else 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()});
|
|
|
|
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\n", .{
|
|
elapsed_msecs,
|
|
user_msecs,
|
|
system_msecs,
|
|
});
|
|
try writer.print("RSS: {:<3.2} | Peak: {:<3.2} | Commit: {:<3.2} | Faults: {d}\n", .{
|
|
std.fmt.fmtIntSizeDec(current_rss),
|
|
std.fmt.fmtIntSizeDec(peak_rss),
|
|
std.fmt.fmtIntSizeDec(current_commit),
|
|
page_faults,
|
|
});
|
|
}
|
|
|
|
if (Output.enable_ansi_colors) {
|
|
try writer.writeAll(Output.prettyFmt("<r>", true));
|
|
}
|
|
try writer.writeAll("\n");
|
|
|
|
if (comptime bun.Environment.isX64) {
|
|
if (is_ancient_cpu) {
|
|
try writer.writeAll("CPU lacks AVX support. Please consider upgrading to a newer CPU.\n");
|
|
}
|
|
}
|
|
}
|
|
|
|
fn waitForOtherThreadToFinishPanicking() void {
|
|
if (panicking.fetchSub(1, .seq_cst) != 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) bun.Futex.waitForever(&futex, 0);
|
|
comptime unreachable;
|
|
}
|
|
}
|
|
|
|
/// This is to be called by any thread that is attempting to exit the process.
|
|
/// If another thread is panicking, this will sleep this thread forever, under
|
|
/// the assumption that the crash handler will terminate the program.
|
|
///
|
|
/// There have been situations in the past where a bundler thread starts
|
|
/// panicking, but the main thread ends up marking a test as passing and then
|
|
/// exiting with code zero before the crash handler can finish the crash.
|
|
pub fn sleepForeverIfAnotherThreadIsCrashing() void {
|
|
if (panicking.load(.acquire) > 0) {
|
|
// Sleep forever without hammering the CPU
|
|
var futex = std.atomic.Value(u32).init(0);
|
|
while (true) bun.Futex.waitForever(&futex, 0);
|
|
comptime unreachable;
|
|
}
|
|
}
|
|
|
|
/// Each platform is encoded as 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 ""));
|
|
};
|
|
|
|
/// Note to the decoder on how to process this string. This ensures backwards
|
|
/// compatibility with older versions of the tracestring.
|
|
///
|
|
/// '1' - original. uses 7 char hash with VLQ encoded stack-frames
|
|
/// '2' - same as '1' but this build is known to be a canary build
|
|
const version_char = if (bun.Environment.is_canary)
|
|
"2"
|
|
else
|
|
"1";
|
|
|
|
const git_sha = if (bun.Environment.git_sha.len > 0) bun.Environment.git_sha[0..7] else "unknown";
|
|
|
|
const StackLine = struct {
|
|
address: i32,
|
|
// null -> from bun.exe
|
|
object: ?[]const u8,
|
|
|
|
/// `null` implies the trace is not known.
|
|
pub fn fromAddress(addr: usize, name_bytes: []u8) ?StackLine {
|
|
return switch (bun.Environment.os) {
|
|
.windows => {
|
|
const module = bun.windows.getModuleHandleFromAddress(addr) orelse
|
|
return null;
|
|
|
|
const base_address = @intFromPtr(module);
|
|
|
|
var temp: [512]u16 = undefined;
|
|
const name = bun.windows.getModuleNameW(module, &temp) orelse
|
|
return null;
|
|
|
|
const image_path = bun.windows.exePathW();
|
|
|
|
return .{
|
|
// To remap this, `pdb-addr2line --exe bun.pdb 0x123456`
|
|
.address = @intCast(addr - base_address),
|
|
|
|
.object = if (!std.mem.eql(u16, name, image_path)) name: {
|
|
const basename = name[if (std.mem.lastIndexOfAny(u16, name, &[_]u16{ '\\', '/' })) |i|
|
|
// skip the last slash
|
|
i + 1
|
|
else
|
|
0..];
|
|
break :name bun.strings.convertUTF16toUTF8InBuffer(name_bytes, basename) catch null;
|
|
} else null,
|
|
};
|
|
},
|
|
.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;
|
|
|
|
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: 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 null;
|
|
}
|
|
|
|
// 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 .{
|
|
.object = null,
|
|
.address = @intCast(image_relative_address),
|
|
};
|
|
} else {
|
|
// these libraries are not interesting, mark as unknown
|
|
return null;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
},
|
|
else => {},
|
|
};
|
|
}
|
|
|
|
return null;
|
|
},
|
|
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 {
|
|
// Input
|
|
address: usize,
|
|
i: usize = 0,
|
|
// Output
|
|
result: ?StackLine = null,
|
|
} = .{ .address = addr -| 1 };
|
|
const CtxTy = @TypeOf(ctx);
|
|
|
|
std.posix.dl_iterate_phdr(&ctx, error{Found}, struct {
|
|
fn callback(info: *std.posix.dl_phdr_info, _: usize, context: *CtxTy) !void {
|
|
defer context.i += 1;
|
|
|
|
if (context.address < info.addr) return;
|
|
const phdrs = info.phdr[0..info.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.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.name, 0) orelse "";
|
|
context.result = .{
|
|
.address = @intCast(context.address - info.addr),
|
|
.object = null,
|
|
};
|
|
return error.Found;
|
|
}
|
|
}
|
|
}
|
|
}.callback) catch {};
|
|
|
|
return ctx.result;
|
|
},
|
|
};
|
|
}
|
|
|
|
pub fn writeEncoded(self: ?StackLine, writer: anytype) !void {
|
|
const known = self orelse {
|
|
try writer.writeAll("_");
|
|
return;
|
|
};
|
|
|
|
if (known.object) |object| {
|
|
try VLQ.encode(1).writeTo(writer);
|
|
try VLQ.encode(@intCast(object.len)).writeTo(writer);
|
|
try writer.writeAll(object);
|
|
}
|
|
|
|
try VLQ.encode(known.address).writeTo(writer);
|
|
}
|
|
|
|
pub fn format(line: StackLine, comptime _: []const u8, _: std.fmt.FormatOptions, writer: anytype) !void {
|
|
try writer.print("0x{x}{s}{s}", .{
|
|
if (bun.Environment.isMac) @as(u64, line.address) + 0x100000000 else line.address,
|
|
if (line.object != null) " @ " else "",
|
|
line.object orelse "",
|
|
});
|
|
}
|
|
|
|
pub fn writeDecoded(self: ?StackLine, writer: anytype) !void {
|
|
const known = self orelse {
|
|
try writer.print("???", .{});
|
|
return;
|
|
};
|
|
try known.format("", .{}, writer);
|
|
}
|
|
};
|
|
|
|
const TraceString = struct {
|
|
trace: *const std.builtin.StackTrace,
|
|
reason: CrashReason,
|
|
action: TraceString.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 {
|
|
encodeTraceString(self, writer) catch return;
|
|
}
|
|
};
|
|
|
|
fn encodeTraceString(opts: TraceString, writer: anytype) !void {
|
|
try writer.writeAll(reportBaseUrl());
|
|
try writer.writeAll(
|
|
"/" ++
|
|
bun.Environment.version_string ++
|
|
"/" ++
|
|
.{@intFromEnum(Platform.current)},
|
|
);
|
|
try writer.writeByte(if (bun.CLI.Cli.cmd) |cmd| cmd.char() else '_');
|
|
|
|
try writer.writeAll(version_char ++ git_sha);
|
|
|
|
const packed_features = bun.analytics.packedFeatures();
|
|
try writeU64AsTwoVLQs(writer, @bitCast(packed_features));
|
|
|
|
var name_bytes: [1024]u8 = undefined;
|
|
|
|
for (opts.trace.instruction_addresses[0..opts.trace.index]) |addr| {
|
|
const line = StackLine.fromAddress(addr, &name_bytes);
|
|
try StackLine.writeEncoded(line, writer);
|
|
}
|
|
|
|
try writer.writeAll(VLQ.zero.slice());
|
|
|
|
// The following switch must be kept in sync with `bun.report`'s decoder implementation.
|
|
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);
|
|
|
|
try writer.writeAll(std.mem.trimRight(u8, b64_bytes[0..b64_len], "="));
|
|
},
|
|
|
|
.@"unreachable" => try writer.writeByte('1'),
|
|
|
|
.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'),
|
|
|
|
.zig_error => |err| {
|
|
try writer.writeByte('8');
|
|
try writer.writeAll(@errorName(err));
|
|
},
|
|
|
|
.out_of_memory => try writer.writeByte('9'),
|
|
}
|
|
|
|
if (opts.action == .view_trace) {
|
|
try writer.writeAll("/view");
|
|
}
|
|
}
|
|
|
|
fn writeU64AsTwoVLQs(writer: anytype, addr: usize) !void {
|
|
const first = VLQ.encode(@bitCast(@as(u32, @intCast((addr & 0xFFFFFFFF00000000) >> 32))));
|
|
const second = VLQ.encode(@bitCast(@as(u32, @intCast(addr & 0xFFFFFFFF))));
|
|
try first.writeTo(writer);
|
|
try second.writeTo(writer);
|
|
}
|
|
|
|
fn isReportingEnabled() bool {
|
|
// If trying to test the crash handler backend, implicitly enable reporting
|
|
if (bun.getenvZ("BUN_CRASH_REPORT_URL")) |value| {
|
|
return value.len > 0;
|
|
}
|
|
|
|
// Environment variable to specifically enable or disable reporting
|
|
if (bun.getenvZ("BUN_ENABLE_CRASH_REPORTING")) |value| {
|
|
if (value.len > 0) {
|
|
if (bun.strings.eqlComptime(value, "1")) {
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Debug builds shouldn't report to the default url by default
|
|
if (bun.Environment.isDebug)
|
|
return false;
|
|
|
|
// Honor DO_NOT_TRACK
|
|
if (!bun.analytics.isEnabled())
|
|
return false;
|
|
|
|
if (bun.Environment.is_canary)
|
|
return true;
|
|
|
|
// Change in v1.1.10: enable crash reporter auto upload on macOS and Windows.
|
|
if (bun.Environment.isMac or bun.Environment.isWindows) {
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/// Bun automatically reports crashes on Windows and macOS
|
|
///
|
|
/// These URLs contain no source code or personally-identifiable
|
|
/// information (PII). The stackframes point to Bun's open-source native code
|
|
/// (not user code), and are safe to share publicly and with the Bun team.
|
|
fn report(url: []const u8) void {
|
|
if (!isReportingEnabled()) return;
|
|
|
|
switch (bun.Environment.os) {
|
|
.windows => {
|
|
var process: std.os.windows.PROCESS_INFORMATION = undefined;
|
|
var startup_info = std.os.windows.STARTUPINFOW{
|
|
.cb = @sizeOf(std.os.windows.STARTUPINFOW),
|
|
.lpReserved = null,
|
|
.lpDesktop = null,
|
|
.lpTitle = null,
|
|
.dwX = 0,
|
|
.dwY = 0,
|
|
.dwXSize = 0,
|
|
.dwYSize = 0,
|
|
.dwXCountChars = 0,
|
|
.dwYCountChars = 0,
|
|
.dwFillAttribute = 0,
|
|
.dwFlags = 0,
|
|
.wShowWindow = 0,
|
|
.cbReserved2 = 0,
|
|
.lpReserved2 = null,
|
|
.hStdInput = null,
|
|
.hStdOutput = null,
|
|
.hStdError = null,
|
|
// .hStdInput = bun.win32.STDIN_FD.cast(),
|
|
// .hStdOutput = bun.win32.STDOUT_FD.cast(),
|
|
// .hStdError = bun.win32.STDERR_FD.cast(),
|
|
};
|
|
var cmd_line = std.BoundedArray(u16, 4096){};
|
|
cmd_line.appendSliceAssumeCapacity(std.unicode.utf8ToUtf16LeStringLiteral("powershell -ExecutionPolicy Bypass -Command \"try{Invoke-RestMethod -Uri '"));
|
|
{
|
|
const encoded = bun.strings.convertUTF8toUTF16InBuffer(cmd_line.unusedCapacitySlice(), url);
|
|
cmd_line.len += @intCast(encoded.len);
|
|
}
|
|
cmd_line.appendSlice(std.unicode.utf8ToUtf16LeStringLiteral("/ack'|out-null}catch{}\"")) catch return;
|
|
cmd_line.append(0) catch return;
|
|
const cmd_line_slice = cmd_line.buffer[0 .. cmd_line.len - 1 :0];
|
|
const spawn_result = std.os.windows.kernel32.CreateProcessW(
|
|
null,
|
|
cmd_line_slice,
|
|
null,
|
|
null,
|
|
1, // true
|
|
0,
|
|
null,
|
|
null,
|
|
&startup_info,
|
|
&process,
|
|
);
|
|
|
|
// we don't care what happens with the process
|
|
_ = spawn_result;
|
|
},
|
|
.mac, .linux => {
|
|
var buf: bun.PathBuffer = undefined;
|
|
var buf2: bun.PathBuffer = undefined;
|
|
const curl = bun.which(
|
|
&buf,
|
|
bun.getenvZ("PATH") orelse return,
|
|
bun.getcwd(&buf2) catch return,
|
|
"curl",
|
|
) orelse return;
|
|
var cmd_line = std.BoundedArray(u8, 4096){};
|
|
cmd_line.appendSlice(url) catch return;
|
|
cmd_line.appendSlice("/ack") catch return;
|
|
cmd_line.append(0) catch return;
|
|
|
|
var argv = [_:null]?[*:0]const u8{
|
|
curl,
|
|
"-fsSL",
|
|
cmd_line.buffer[0..cmd_line.len :0],
|
|
};
|
|
const result = std.c.fork();
|
|
switch (result) {
|
|
// success and failure cases: ignore the result
|
|
else => return,
|
|
// child
|
|
0 => {
|
|
inline for (0..2) |i| {
|
|
_ = std.c.close(i);
|
|
}
|
|
_ = std.c.execve(argv[0].?, &argv, std.c.environ);
|
|
std.c.exit(0);
|
|
},
|
|
}
|
|
},
|
|
else => @compileError("Not implemented"),
|
|
}
|
|
}
|
|
|
|
/// 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 => {
|
|
// This exit code is what Node.js uses when it calls
|
|
// abort. This is relied on by their Node-API tests.
|
|
bun.C.quick_exit(134);
|
|
},
|
|
else => {
|
|
// Install default handler so that the tkill below will terminate.
|
|
const sigact = std.posix.Sigaction{ .handler = .{ .handler = std.posix.SIG.DFL }, .mask = std.posix.empty_sigset, .flags = 0 };
|
|
inline for (.{
|
|
std.posix.SIG.SEGV,
|
|
std.posix.SIG.ILL,
|
|
std.posix.SIG.BUS,
|
|
std.posix.SIG.ABRT,
|
|
std.posix.SIG.FPE,
|
|
std.posix.SIG.HUP,
|
|
std.posix.SIG.TERM,
|
|
}) |sig| {
|
|
std.posix.sigaction(sig, &sigact, null);
|
|
}
|
|
|
|
@trap();
|
|
},
|
|
}
|
|
}
|
|
|
|
pub var verbose_error_trace = false;
|
|
|
|
noinline fn coldHandleErrorReturnTrace(err_int_workaround_for_zig_ccall_bug: std.meta.Int(.unsigned, @bitSizeOf(anyerror)), trace: *std.builtin.StackTrace, comptime is_root: bool) void {
|
|
@branchHint(.cold);
|
|
const err = @errorFromInt(err_int_workaround_for_zig_ccall_bug);
|
|
|
|
// 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) {
|
|
if (is_root) {
|
|
if (verbose_error_trace) {
|
|
Output.note("Release build will not have this trace by default:", .{});
|
|
}
|
|
} else {
|
|
Output.note(
|
|
"caught error.{s}:",
|
|
.{@errorName(err)},
|
|
);
|
|
}
|
|
Output.flush();
|
|
dumpStackTrace(trace.*);
|
|
} else {
|
|
const ts = TraceString{
|
|
.trace = trace,
|
|
.reason = .{ .zig_error = err },
|
|
.action = .view_trace,
|
|
};
|
|
if (is_root) {
|
|
Output.prettyErrorln(
|
|
\\
|
|
\\To send a redacted crash report to Bun's team,
|
|
\\please file a GitHub issue using the link below:
|
|
\\
|
|
\\ <cyan>{}<r>
|
|
\\
|
|
,
|
|
.{ts},
|
|
);
|
|
} else {
|
|
Output.prettyErrorln(
|
|
"<cyan>trace<r>: error.{s}: <d>{}<r>",
|
|
.{ @errorName(err), ts },
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
inline 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 and !is_root) return;
|
|
|
|
if (maybe_trace) |trace| {
|
|
coldHandleErrorReturnTrace(@intFromError(err), trace, is_root);
|
|
}
|
|
}
|
|
|
|
/// 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);
|
|
}
|
|
|
|
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 {
|
|
Output.flush();
|
|
const stderr = std.io.getStdErr().writer();
|
|
if (!bun.Environment.isDebug) {
|
|
// debug symbols aren't available, lets print a tracestring
|
|
stderr.print("View Debug Trace: {}\n", .{TraceString{
|
|
.action = .view_trace,
|
|
.reason = .{ .zig_error = error.DumpStackTrace },
|
|
.trace = &trace,
|
|
}}) catch {};
|
|
return;
|
|
}
|
|
|
|
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_info, std.io.tty.detectConfig(std.io.getStdErr())) catch |err| {
|
|
stderr.print("Unable to dump stack trace: {s}\nFallback trace:\n", .{@errorName(err)}) catch return;
|
|
break :attempt_dump;
|
|
};
|
|
return;
|
|
},
|
|
.linux => {
|
|
// Linux doesnt seem to be able to decode it's own debug info.
|
|
// TODO(@paperclover): see if zig 0.14 fixes this
|
|
},
|
|
else => {
|
|
stdDumpStackTrace(trace);
|
|
return;
|
|
},
|
|
}
|
|
|
|
var arena = bun.ArenaAllocator.init(bun.default_allocator);
|
|
defer arena.deinit();
|
|
var sfa = std.heap.stackFallback(16384, arena.allocator());
|
|
const alloc = sfa.get();
|
|
|
|
var argv = std.ArrayList([]const u8).init(alloc);
|
|
|
|
const program = switch (bun.Environment.os) {
|
|
.windows => "pdb-addr2line",
|
|
else => "llvm-symbolizer",
|
|
};
|
|
argv.append(program) catch return;
|
|
|
|
argv.append("--exe") catch return;
|
|
argv.append(
|
|
switch (bun.Environment.os) {
|
|
.windows => brk: {
|
|
const image_path = bun.strings.toUTF8Alloc(alloc, bun.windows.exePathW()) catch return;
|
|
break :brk std.mem.concat(alloc, u8, &.{
|
|
image_path[0 .. image_path.len - 3],
|
|
"pdb",
|
|
}) catch return;
|
|
},
|
|
else => bun.selfExePath() catch return,
|
|
},
|
|
) catch return;
|
|
|
|
var name_bytes: [1024]u8 = undefined;
|
|
for (trace.instruction_addresses[0..trace.index]) |addr| {
|
|
const line = StackLine.fromAddress(addr, &name_bytes) orelse
|
|
continue;
|
|
argv.append(std.fmt.allocPrint(alloc, "0x{X}", .{line.address}) catch return) catch return;
|
|
}
|
|
|
|
// std.process is used here because bun.spawnSync with libuv does not work within
|
|
// the crash handler.
|
|
const proc = std.process.Child.run(.{
|
|
.allocator = alloc,
|
|
.argv = argv.items,
|
|
}) catch {
|
|
stderr.print("Failed to invoke command: {s}\n", .{bun.fmt.fmtSlice(argv.items, " ")}) catch return;
|
|
return;
|
|
};
|
|
if (proc.term != .Exited or proc.term.Exited != 0) {
|
|
stderr.print("Failed to invoke command: {s}\n", .{bun.fmt.fmtSlice(argv.items, " ")}) catch return;
|
|
}
|
|
defer alloc.free(proc.stderr);
|
|
defer alloc.free(proc.stdout);
|
|
stderr.writeAll(proc.stdout) catch return;
|
|
stderr.writeAll(proc.stderr) catch return;
|
|
}
|
|
|
|
pub fn dumpCurrentStackTrace(first_address: ?usize) void {
|
|
var addrs: [32]usize = undefined;
|
|
var stack: std.builtin.StackTrace = .{ .index = 0, .instruction_addresses = &addrs };
|
|
std.debug.captureStackTrace(first_address orelse @returnAddress(), &stack);
|
|
dumpStackTrace(stack);
|
|
}
|
|
|
|
/// A variant of `std.builtin.StackTrace` that stores its data within itself
|
|
/// instead of being a pointer. This allows storing captured stack traces
|
|
/// for later printing.
|
|
pub const StoredTrace = struct {
|
|
data: [31]usize,
|
|
index: usize,
|
|
|
|
pub const empty: StoredTrace = .{
|
|
.data = .{0} ** 31,
|
|
.index = 0,
|
|
};
|
|
|
|
pub fn trace(stored: *StoredTrace) std.builtin.StackTrace {
|
|
return .{
|
|
.index = stored.index,
|
|
.instruction_addresses = &stored.data,
|
|
};
|
|
}
|
|
|
|
pub fn capture(begin: ?usize) StoredTrace {
|
|
var stored: StoredTrace = StoredTrace.empty;
|
|
var frame = stored.trace();
|
|
std.debug.captureStackTrace(begin orelse @returnAddress(), &frame);
|
|
stored.index = frame.index;
|
|
for (frame.instruction_addresses[0..frame.index], 0..) |addr, i| {
|
|
if (addr == 0) {
|
|
stored.index = i;
|
|
break;
|
|
}
|
|
}
|
|
return stored;
|
|
}
|
|
|
|
pub fn from(stack_trace: ?*std.builtin.StackTrace) StoredTrace {
|
|
if (stack_trace) |stack| {
|
|
var data: [31]usize = undefined;
|
|
@memset(&data, 0);
|
|
const items = @min(stack.instruction_addresses.len, 31);
|
|
@memcpy(data[0..items], stack.instruction_addresses[0..items]);
|
|
return .{
|
|
.data = data,
|
|
.index = @min(items, stack.index),
|
|
};
|
|
} else {
|
|
return empty;
|
|
}
|
|
}
|
|
};
|
|
|
|
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 },
|
|
.{ "getFeaturesAsVLQ", jsGetFeaturesAsVLQ },
|
|
.{ "getFeatureData", jsGetFeatureData },
|
|
|
|
.{ "segfault", jsSegfault },
|
|
.{ "panic", jsPanic },
|
|
.{ "rootError", jsRootError },
|
|
.{ "outOfMemory", jsOutOfMemory },
|
|
.{ "raiseIgnoringPanicHandler", jsRaiseIgnoringPanicHandler },
|
|
}) |tuple| {
|
|
const name = JSC.ZigString.static(tuple[0]);
|
|
obj.put(global, name, JSC.createCallback(global, name, 1, tuple[1]));
|
|
}
|
|
return obj;
|
|
}
|
|
|
|
pub fn jsGetMachOImageZeroOffset(_: *bun.JSC.JSGlobalObject, _: *bun.JSC.CallFrame) bun.JSError!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 JSValue.jsNumber(base_address - vmaddr_slide);
|
|
}
|
|
|
|
pub fn jsSegfault(_: *JSC.JSGlobalObject, _: *JSC.CallFrame) bun.JSError!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) bun.JSError!JSC.JSValue {
|
|
bun.crash_handler.panicImpl("invoked crashByPanic() handler", null, null);
|
|
}
|
|
|
|
pub fn jsRootError(_: *JSC.JSGlobalObject, _: *JSC.CallFrame) bun.JSError!JSC.JSValue {
|
|
bun.crash_handler.handleRootError(error.Test, null);
|
|
}
|
|
|
|
pub fn jsOutOfMemory(_: *JSC.JSGlobalObject, _: *JSC.CallFrame) bun.JSError!JSC.JSValue {
|
|
bun.outOfMemory();
|
|
}
|
|
|
|
pub fn jsRaiseIgnoringPanicHandler(_: *JSC.JSGlobalObject, _: *JSC.CallFrame) bun.JSError!JSC.JSValue {
|
|
bun.Global.raiseIgnoringPanicHandler(.SIGSEGV);
|
|
}
|
|
|
|
pub fn jsGetFeaturesAsVLQ(global: *JSC.JSGlobalObject, _: *JSC.CallFrame) bun.JSError!JSC.JSValue {
|
|
const bits = bun.Analytics.packedFeatures();
|
|
var buf = std.BoundedArray(u8, 16){};
|
|
writeU64AsTwoVLQs(buf.writer(), @bitCast(bits)) catch {
|
|
// there is definitely enough space in the bounded array
|
|
unreachable;
|
|
};
|
|
var str = bun.String.createLatin1(buf.slice());
|
|
return str.transferToJS(global);
|
|
}
|
|
|
|
pub fn jsGetFeatureData(global: *JSC.JSGlobalObject, _: *JSC.CallFrame) bun.JSError!JSC.JSValue {
|
|
const obj = JSValue.createEmptyObject(global, 5);
|
|
const list = bun.Analytics.packed_features_list;
|
|
const array = JSValue.createEmptyArray(global, list.len);
|
|
for (list, 0..) |feature, i| {
|
|
array.putIndex(global, @intCast(i), bun.String.static(feature).toJS(global));
|
|
}
|
|
obj.put(global, JSC.ZigString.static("features"), array);
|
|
obj.put(global, JSC.ZigString.static("version"), bun.String.init(Global.package_json_version).toJS(global));
|
|
obj.put(global, JSC.ZigString.static("is_canary"), JSC.JSValue.jsBoolean(bun.Environment.is_canary));
|
|
|
|
// This is the source of truth for the git sha.
|
|
// Not the github ref or the git tag.
|
|
obj.put(global, JSC.ZigString.static("revision"), bun.String.init(bun.Environment.git_sha).toJS(global));
|
|
|
|
obj.put(global, JSC.ZigString.static("generated_at"), JSValue.jsNumberFromInt64(@max(std.time.milliTimestamp(), 0)));
|
|
return obj;
|
|
}
|
|
};
|
|
|
|
const OnBeforeCrash = fn (opaque_ptr: *anyopaque) void;
|
|
|
|
/// For large codebases such as bun.bake.DevServer, it may be helpful
|
|
/// to dump a large amount of state to a file to aid debugging a crash.
|
|
///
|
|
/// Pre-crash handlers are likely, but not guaranteed to call. Errors are ignored.
|
|
pub fn appendPreCrashHandler(comptime T: type, ptr: *T, comptime handler: fn (*T) anyerror!void) !void {
|
|
const wrap = struct {
|
|
fn onCrash(opaque_ptr: *anyopaque) void {
|
|
handler(@ptrCast(@alignCast(opaque_ptr))) catch |err| {
|
|
bun.handleErrorReturnTrace(err, @errorReturnTrace());
|
|
};
|
|
}
|
|
};
|
|
|
|
before_crash_handlers_mutex.lock();
|
|
defer before_crash_handlers_mutex.unlock();
|
|
try before_crash_handlers.append(bun.default_allocator, .{ ptr, wrap.onCrash });
|
|
}
|
|
|
|
pub fn removePreCrashHandler(ptr: *anyopaque) void {
|
|
before_crash_handlers_mutex.lock();
|
|
defer before_crash_handlers_mutex.unlock();
|
|
const index = for (before_crash_handlers.items, 0..) |item, i| {
|
|
if (item.@"0" == ptr) break i;
|
|
} else return;
|
|
_ = before_crash_handlers.orderedRemove(index);
|
|
}
|
|
|
|
pub fn isPanicking() bool {
|
|
return panicking.load(.monotonic) > 0;
|
|
}
|
|
|
|
export fn CrashHandler__setInsideNativePlugin(name: ?[*:0]const u8) callconv(.C) void {
|
|
inside_native_plugin = name;
|
|
}
|
|
|
|
export fn CrashHandler__unsupportedUVFunction(name: ?[*:0]const u8) callconv(.C) void {
|
|
bun.analytics.Features.unsupported_uv_function += 1;
|
|
unsupported_uv_function = name;
|
|
std.debug.panic("unsupported uv function: {s}", .{name.?});
|
|
}
|
|
|
|
export fn Bun__crashHandler(message_ptr: [*]u8, message_len: usize) noreturn {
|
|
crashHandler(.{ .panic = message_ptr[0..message_len] }, null, @returnAddress());
|
|
}
|
|
|
|
export fn CrashHandler__setDlOpenAction(action: ?[*:0]const u8) void {
|
|
if (action) |str| {
|
|
bun.debugAssert(current_action == null);
|
|
current_action = .{ .dlopen = bun.sliceTo(str, 0) };
|
|
} else {
|
|
bun.debugAssert(current_action != null and current_action.? == .dlopen);
|
|
current_action = null;
|
|
}
|
|
}
|
|
|
|
pub fn fixDeadCodeElimination() void {
|
|
std.mem.doNotOptimizeAway(&CrashHandler__unsupportedUVFunction);
|
|
}
|
|
comptime {
|
|
_ = &Bun__crashHandler;
|
|
if (!bun.Environment.isWindows) {
|
|
std.mem.doNotOptimizeAway(&CrashHandler__unsupportedUVFunction);
|
|
// @export(&unsupportUVFunction, .{ .name = "CrashHandler__unsupportedUVFunction", .linkage = .strong });
|
|
}
|
|
}
|