diff --git a/Cargo.toml b/Cargo.toml index 241bb3a7487..4f2a533377f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -43,7 +43,10 @@ panic = "abort" # This leads to better optimizations and smaller binaries (and i # split-debuginfo = "unpacked" # faster debug builds on mac # opt-level = 1 # Make debug builds run faster -panic = "abort" # This leads to better optimizations and smaller binaries (and is the default in Wasm anyways). +# panic = "abort" leads to better optimizations and smaller binaries (and is the default in Wasm anyways), +# but it also means backtraces don't work with the `backtrace` library (https://github.com/rust-lang/backtrace-rs/issues/397). +# egui has a feature where if you hold down all modifiers keys on your keyboard and hover any UI widget, +# you will see the backtrace to that widget, and we don't want to break that feature in dev builds. [profile.dev.package."*"] # Optimize all dependencies even in debug builds (does not affect workspace packages): diff --git a/crates/egui/src/callstack.rs b/crates/egui/src/callstack.rs index aa6f44cb08d..6b1959b0b79 100644 --- a/crates/egui/src/callstack.rs +++ b/crates/egui/src/callstack.rs @@ -10,6 +10,7 @@ struct Frame { /// /// In particular: slips everything before `egui::Context::run`, /// and skipping all frames in the `egui::` namespace. +#[inline(never)] pub fn capture() -> String { let mut frames = vec![]; let mut depth = 0; @@ -28,7 +29,7 @@ pub fn capture() -> String { let name = symbol .name() - .map(|name| name.to_string()) + .map(|name| clean_symbol_name(name.to_string())) .unwrap_or_default(); frames.push(Frame { @@ -44,12 +45,13 @@ pub fn capture() -> String { }); if frames.is_empty() { - return Default::default(); + return + "Failed to capture a backtrace. A common cause of this is compiling with panic=\"abort\" (https://github.com/rust-lang/backtrace-rs/issues/397)".to_owned(); } // Inclusive: let mut min_depth = 0; - let mut max_depth = frames.len() - 1; + let mut max_depth = usize::MAX; for frame in &frames { if frame.name.starts_with("egui::callstack::capture") { @@ -60,14 +62,23 @@ pub fn capture() -> String { } } + /// Is this the name of some sort of useful entry point? + fn is_start_name(name: &str) -> bool { + name == "main" + || name == "_main" + || name.starts_with("eframe::run_native") + || name.starts_with("egui::context::Context::run") + } + + let mut has_kept_any_start_names = false; + + frames.reverse(); // main on top, i.e. chronological order. Same as Python. + // Remove frames that are uninteresting: frames.retain(|frame| { - // Keep some special frames to give the user a sense of chronology: - if frame.name == "main" - || frame.name == "_main" - || frame.name.starts_with("egui::context::Context::run") - || frame.name.starts_with("eframe::run_native") - { + // Keep the first "start" frame we can detect (e.g. `main`) to give the user a sense of chronology: + if is_start_name(&frame.name) && !has_kept_any_start_names { + has_kept_any_start_names = true; return true; } @@ -96,8 +107,6 @@ pub fn capture() -> String { true }); - frames.reverse(); // main on top, i.e. chronological order. Same as Python. - let mut deepest_depth = 0; let mut widest_file_line = 0; for frame in &frames { @@ -135,6 +144,28 @@ pub fn capture() -> String { formatted } +fn clean_symbol_name(mut s: String) -> String { + // We get a hex suffix (at least on macOS) which is quite unhelpful, + // e.g. `my_crate::my_function::h3bedd97b1e03baa5`. + // Let's strip that. + if let Some(h) = s.rfind("::h") { + let hex = &s[h + 3..]; + if hex.len() == 16 && hex.chars().all(|c| c.is_ascii_hexdigit()) { + s.truncate(h); + } + } + + s +} + +#[test] +fn test_clean_symbol_name() { + assert_eq!( + clean_symbol_name("my_crate::my_function::h3bedd97b1e03baa5".to_owned()), + "my_crate::my_function" + ); +} + /// Shorten a path to a Rust source file from a callstack. /// /// Example input: