Skip to content

Commit

Permalink
Improve backtraces when hovering widgets with modifiers pressed (emil…
Browse files Browse the repository at this point in the history
…k#4696)

A useful debug-feature in egui is pressing down all modifiers keys and
hovering any widget to see its backtrace (only in `dev` builds).
Unfortunately this is incompatible with `panic="abort"`, something I
just now discovered.

So I removed `panic="abort"` from `Cargo.toml` for `dev` builds. If the
backtrace returns empty-handed, I also suggests this as a fix to the
user. Finally, I cleaned up the backtraces a bit, making them slightly
shorter and more readable.
  • Loading branch information
emilk authored and hacknus committed Oct 30, 2024
1 parent dce230a commit ec5dbcf
Show file tree
Hide file tree
Showing 2 changed files with 46 additions and 12 deletions.
5 changes: 4 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down
53 changes: 42 additions & 11 deletions crates/egui/src/callstack.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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 {
Expand All @@ -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") {
Expand All @@ -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;
}

Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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:
Expand Down

0 comments on commit ec5dbcf

Please sign in to comment.