pub mod sigint;
#[cfg(not(target_os = "windows"))]
use parking_lot::Mutex;
use re_build_info::BuildInfo;
#[cfg(not(target_os = "windows"))]
static BUILD_INFO: Mutex<Option<BuildInfo>> = Mutex::new(None);
#[allow(clippy::allow_attributes, clippy::needless_pass_by_value)]
pub fn install_crash_handlers(build_info: BuildInfo) {
install_panic_hook(build_info.clone());
#[cfg(not(target_arch = "wasm32"))]
#[cfg(not(target_os = "windows"))]
install_signal_handler(build_info);
}
fn install_panic_hook(_build_info: BuildInfo) {
let previous_panic_hook = std::panic::take_hook();
std::panic::set_hook(Box::new(
move |panic_info: &std::panic::PanicHookInfo<'_>| {
let callstack = callstack_from(&["panicking::panic_fmt\n"]);
let file_line = panic_info.location().map(|location| {
let file = anonymize_source_file_path(&std::path::PathBuf::from(location.file()));
format!("{file}:{}", location.line())
});
let msg = panic_info_message(panic_info);
if let Some(msg) = &msg {
let thread = std::thread::current();
let thread_name = thread
.name()
.map_or_else(|| format!("{:?}", thread.id()), |name| name.to_owned());
eprintln!("\nthread '{thread_name}' panicked at '{msg}'");
if let Some(file_line) = &file_line {
eprintln!("{file_line}");
}
eprintln!("stack backtrace:\n{callstack}");
} else {
(*previous_panic_hook)(panic_info);
}
econtext::print_econtext();
eprintln!(
"\n\
Troubleshooting Rerun: https://www.rerun.io/docs/overview/installing-rerun/troubleshooting \n\
Report bugs: https://github.com/rerun-io/rerun/issues"
);
#[cfg(feature = "analytics")]
re_analytics::record_and_flush_blocking(|| re_analytics::event::CrashPanic {
build_info: _build_info.clone(),
callstack,
message: None,
file_line,
});
#[expect(clippy::exit)]
std::process::exit(102);
},
));
}
fn panic_info_message(panic_info: &std::panic::PanicHookInfo<'_>) -> Option<String> {
#[expect(clippy::manual_map)]
if let Some(msg) = panic_info.payload().downcast_ref::<&str>() {
Some((*msg).to_owned())
} else if let Some(msg) = panic_info.payload().downcast_ref::<String>() {
Some(msg.clone())
} else {
None
}
}
#[cfg(not(target_arch = "wasm32"))]
#[cfg(not(target_os = "windows"))]
#[expect(unsafe_code)]
#[expect(clippy::fn_to_numeric_cast_any)]
fn install_signal_handler(build_info: BuildInfo) {
*BUILD_INFO.lock() = Some(build_info);
for signum in [
libc::SIGABRT,
libc::SIGBUS,
libc::SIGFPE,
libc::SIGILL,
libc::SIGSEGV,
] {
unsafe {
libc::signal(
signum,
signal_handler as *const fn(libc::c_int) as libc::size_t,
);
}
}
unsafe extern "C" fn signal_handler(signal_number: libc::c_int) {
fn print_problem_and_links(signal_name: &str) {
write_to_stderr("Rerun caught a signal: ");
write_to_stderr(signal_name);
write_to_stderr("\n");
write_to_stderr(
"Troubleshooting Rerun: https://www.rerun.io/docs/overview/installing-rerun/troubleshooting \n",
);
write_to_stderr("Report bugs: https://github.com/rerun-io/rerun/issues \n");
write_to_stderr("\n");
}
let signal_name = match signal_number {
libc::SIGABRT => "SIGABRT",
libc::SIGBUS => "SIGBUS",
libc::SIGFPE => "SIGFPE",
libc::SIGILL => "SIGILL",
libc::SIGINT => "SIGINT",
libc::SIGSEGV => "SIGSEGV",
libc::SIGTERM => "SIGTERM",
_ => "UNKNOWN SIGNAL",
};
write_to_stderr("\n");
print_problem_and_links(signal_name);
let callstack = callstack();
write_to_stderr(&callstack);
write_to_stderr("\n");
econtext::print_econtext();
write_to_stderr("\n");
print_problem_and_links(signal_name);
#[cfg(feature = "analytics")]
if let Some(build_info) = BUILD_INFO.lock().clone() {
re_analytics::record_and_flush_blocking(|| re_analytics::event::CrashSignal {
build_info,
signal: signal_name.to_owned(),
callstack,
});
}
unsafe {
libc::signal(signal_number, libc::SIG_DFL);
libc::raise(signal_number);
}
}
fn write_to_stderr(text: &str) {
unsafe {
libc::write(libc::STDERR_FILENO, text.as_ptr().cast(), text.len());
}
}
fn callstack() -> String {
callstack_from(&["install_signal_handler::signal_handler\n"])
}
}
pub fn callstack_from(start_patterns: &[&str]) -> String {
let backtrace = backtrace::Backtrace::new();
let stack = backtrace_to_string(&backtrace);
let mut stack = stack.as_str();
let start_patterns = start_patterns
.iter()
.chain(std::iter::once(&"callstack_from"));
for start_pattern in start_patterns {
if let Some(offset) = stack.find(start_pattern) {
let prev_newline = stack[..offset].rfind('\n').map_or(0, |newline| newline + 1);
stack = &stack[prev_newline..];
}
}
let end_patterns = [
"std::sys_common::backtrace::__rust_begin_short_backtrace",
"run_native_app",
];
for end_pattern in end_patterns {
if let Some(offset) = stack.find(end_pattern) {
if let Some(start_of_line) = stack[..offset].rfind('\n') {
stack = &stack[..start_of_line];
} else {
stack = &stack[..offset];
}
}
}
stack.into()
}
fn backtrace_to_string(backtrace: &backtrace::Backtrace) -> String {
struct AnonymizedBacktrace<'a>(&'a backtrace::Backtrace);
impl std::fmt::Display for AnonymizedBacktrace<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
format_backtrace(self.0, f)
}
}
AnonymizedBacktrace(backtrace).to_string()
}
fn format_backtrace(
backtrace: &backtrace::Backtrace,
fmt: &mut std::fmt::Formatter<'_>,
) -> std::fmt::Result {
let mut print_path = |fmt: &mut std::fmt::Formatter<'_>,
path: backtrace::BytesOrWideString<'_>| {
let path = path.into_path_buf();
let anoymized = anonymize_source_file_path(&path);
std::fmt::Display::fmt(&anoymized, fmt)
};
let style = if fmt.alternate() {
backtrace::PrintFmt::Full
} else {
backtrace::PrintFmt::Short
};
let mut f = backtrace::BacktraceFmt::new(fmt, style, &mut print_path);
f.add_context()?;
for frame in backtrace.frames() {
f.frame().backtrace_frame(frame)?;
}
f.finish()?;
Ok(())
}
fn anonymize_source_file_path(path: &std::path::Path) -> String {
use itertools::Itertools as _;
let components = path.iter().map(|path| path.to_string_lossy()).collect_vec();
if let Some((src_rev_idx, _)) = components.iter().rev().find_position(|&c| c == "src") {
let src_idx = components.len() - src_rev_idx - 1;
let first_index = src_idx.saturating_sub(1);
components.iter().skip(first_index).format("/").to_string()
} else {
components
.last()
.map(|filename| filename.to_string())
.unwrap_or_default()
}
}
#[test]
fn test_anonymize_path() {
for (before, after) in [
(
"/Users/emilk/.cargo/registry/src/github.com-1ecc6299db9ec823/tokio-1.24.1/src/runtime/runtime.rs",
"tokio-1.24.1/src/runtime/runtime.rs",
),
("crates/rerun/src/main.rs", "rerun/src/main.rs"),
(
"/rustc/d5a82bbd26e1ad8b7401f6a718a9c57c96905483/library/core/src/ops/function.rs",
"core/src/ops/function.rs",
),
("/weird/path/file.rs", "file.rs"),
] {
use std::str::FromStr as _;
let before = std::path::PathBuf::from_str(before).unwrap();
assert_eq!(anonymize_source_file_path(&before), after);
}
}