Skip to content

Commit

Permalink
Print our own callstack on panics (#1622)
Browse files Browse the repository at this point in the history
  • Loading branch information
emilk authored Mar 21, 2023
1 parent 88ed85b commit b5a96ca
Showing 1 changed file with 70 additions and 24 deletions.
94 changes: 70 additions & 24 deletions crates/rerun/src/crash_handler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,41 @@ 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::PanicInfo<'_>| {
// This prints the callstack etc
(*previous_panic_hook)(panic_info);
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())
});

// `panic_info.message` is unstable, so this is the recommended way of getting
// the panic message out. We need both the `&str` and `String` variants.
let msg = panic_info_message(panic_info);

if let Some(msg) = &msg {
// Print our own panic message.
// Our formatting is nicer than `std` since we shorten the file paths (for privacy reasons).
// This also makes it easier for users to copy-paste the callstack into an issue
// without having any sensitive data in it.

let thread = std::thread::current();
let thread_name = thread
.name()
.map_or_else(|| format!("{:?}", thread.id()), |name| name.to_owned());

let file_line_suffix = if let Some(file_line) = &file_line {
format!(", {file_line}")
} else {
String::new()
};

eprintln!(
"\nthread '{thread_name}' panicked at '{msg}'{file_line_suffix}\n\n{callstack}"
);
} else {
// This prints the panic message and callstack:
(*previous_panic_hook)(panic_info);
}

eprintln!(
"\n\
Expand All @@ -35,26 +68,21 @@ fn install_panic_hook(_build_info: BuildInfo) {
{
if let Ok(analytics) = re_analytics::Analytics::new(std::time::Duration::from_millis(1))
{
let callstack = callstack_from("panicking::panic_fmt\n");
let mut event = re_analytics::Event::append("crash-panic")
.with_build_info(&_build_info)
.with_prop("callstack", callstack);

let include_panic_message = false; // Don't include it, because it can contain sensitive information (`panic!("Couldn't read {file_path}")`)
let include_panic_message = false; // Don't include it, because it can contain sensitive information (`panic!("Couldn't read {sensitive_file_path}")`)
if include_panic_message {
// `panic_info.message` is unstable, so this is the recommended way of getting
// the panic message out. We need both the `&str` and `String` variants.
if let Some(msg) = panic_info.payload().downcast_ref::<&str>() {
event = event.with_prop("message", *msg);
} else if let Some(msg) = panic_info.payload().downcast_ref::<String>() {
event = event.with_prop("message", msg.clone());
if let Some(msg) = msg {
event = event.with_prop("message", msg);
}
}

if let Some(location) = panic_info.location() {
let file =
anonymize_source_file_path(&std::path::PathBuf::from(location.file()));
event = event.with_prop("file_line", format!("{file}:{}", location.line()));
if let Some(file_line) = file_line {
event = event.with_prop("file_line", file_line);
}

analytics.record(event);
Expand All @@ -65,6 +93,17 @@ fn install_panic_hook(_build_info: BuildInfo) {
}));
}

fn panic_info_message(panic_info: &std::panic::PanicInfo<'_>) -> Option<String> {
#[allow(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"))]
#[allow(unsafe_code)]
Expand Down Expand Up @@ -183,21 +222,28 @@ fn callstack_from(start_pattern: &str) -> String {
let mut stack = stack.as_str();

// Trim the top (closest to the panic handler) to cut out some noise:
if let Some(start_offset) = stack.find(start_pattern) {
stack = &stack[start_offset + start_pattern.len()..];
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..];
}

// Trim the bottom to cut out code that sets up the callstack:
if let Some(end_offset) = stack.find("std::sys_common::backtrace::__rust_begin_short_backtrace")
{
stack = &stack[..end_offset];
}

// Trim the bottom even more to exclude any user code that potentially used `rerun`
// as a library to show a viewer. In these cases there may be sensitive user code
// that called `rerun::run`, and we do not want to include it:
if let Some(end_offset) = stack.find("run_native_app") {
stack = &stack[..end_offset];
let end_patterns = [
"std::sys_common::backtrace::__rust_begin_short_backtrace",
// Trim the bottom even more to exclude any user code that potentially used `rerun`
// as a library to show a viewer. In these cases there may be sensitive user code
// that called `rerun::run`, and we do not want to include it:
"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()
Expand Down

1 comment on commit b5a96ca

@github-actions
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rust Benchmark

Benchmark suite Current: b5a96ca Previous: 88ed85b Ratio
datastore/insert/batch/rects/insert 552400 ns/iter (± 1947) 563160 ns/iter (± 2138) 0.98
datastore/latest_at/batch/rects/query 1874 ns/iter (± 6) 1856 ns/iter (± 12) 1.01
datastore/latest_at/missing_components/primary 286 ns/iter (± 0) 287 ns/iter (± 0) 1.00
datastore/latest_at/missing_components/secondaries 437 ns/iter (± 1) 438 ns/iter (± 0) 1.00
datastore/range/batch/rects/query 150300 ns/iter (± 1008) 152060 ns/iter (± 244) 0.99
mono_points_arrow/generate_message_bundles 46732018 ns/iter (± 880640) 44047159 ns/iter (± 1146601) 1.06
mono_points_arrow/generate_messages 127244143 ns/iter (± 1198914) 127181212 ns/iter (± 1117984) 1.00
mono_points_arrow/encode_log_msg 157276826 ns/iter (± 1213622) 152657222 ns/iter (± 625123) 1.03
mono_points_arrow/encode_total 334440333 ns/iter (± 2582457) 325220569 ns/iter (± 1491494) 1.03
mono_points_arrow/decode_log_msg 180601091 ns/iter (± 884084) 176540354 ns/iter (± 943559) 1.02
mono_points_arrow/decode_message_bundles 65806249 ns/iter (± 918492) 63719471 ns/iter (± 990914) 1.03
mono_points_arrow/decode_total 242750667 ns/iter (± 1488225) 238810524 ns/iter (± 1721475) 1.02
batch_points_arrow/generate_message_bundles 329979 ns/iter (± 842) 327361 ns/iter (± 2664) 1.01
batch_points_arrow/generate_messages 6625 ns/iter (± 18) 6487 ns/iter (± 25) 1.02
batch_points_arrow/encode_log_msg 360175 ns/iter (± 1599) 358830 ns/iter (± 909) 1.00
batch_points_arrow/encode_total 713009 ns/iter (± 5189) 711636 ns/iter (± 1921) 1.00
batch_points_arrow/decode_log_msg 349611 ns/iter (± 1545) 345913 ns/iter (± 281) 1.01
batch_points_arrow/decode_message_bundles 2125 ns/iter (± 12) 2057 ns/iter (± 1) 1.03
batch_points_arrow/decode_total 355540 ns/iter (± 1291) 346088 ns/iter (± 413) 1.03
arrow_mono_points/insert 6104864157 ns/iter (± 13769441) 6041063174 ns/iter (± 19036450) 1.01
arrow_mono_points/query 1822825 ns/iter (± 28013) 1754415 ns/iter (± 8025) 1.04
arrow_batch_points/insert 2633287 ns/iter (± 52044) 2631848 ns/iter (± 6798) 1.00
arrow_batch_points/query 16200 ns/iter (± 78) 16155 ns/iter (± 24) 1.00
arrow_batch_vecs/insert 42329 ns/iter (± 109) 42681 ns/iter (± 161) 0.99
arrow_batch_vecs/query 389601 ns/iter (± 620) 388648 ns/iter (± 225) 1.00
tuid/Tuid::random 34 ns/iter (± 0) 34 ns/iter (± 0) 1

This comment was automatically generated by workflow using github-action-benchmark.

Please sign in to comment.