@@ -32,6 +32,7 @@ use std::{
ffi::OsStr,
fs::OpenOptions,
io::Write as _,
+ ops::Not,
os::unix::prelude::OsStrExt,
panic,
path::{Path, PathBuf},
@@ -371,13 +372,12 @@ struct Panic {
#[serde(skip_serializing_if = "Option::is_none")]
location_data: Option<LocationData>,
backtrace: Vec<String>,
- // TODO
- // stripped_backtrace: String,
release_channel: String,
os_name: String,
os_version: Option<String>,
architecture: String,
panicked_on: u128,
+ identifying_backtrace: Option<Vec<String>>,
}
#[derive(Serialize)]
@@ -396,18 +396,73 @@ fn init_panic_hook(app: &App) {
let app_version = ZED_APP_VERSION
.or_else(|| platform.app_version().ok())
.map_or("dev".to_string(), |v| v.to_string());
- let backtrace = Backtrace::new();
let thread = thread::current();
let thread = thread.name().unwrap_or("<unnamed>");
- let payload = match info.payload().downcast_ref::<&'static str>() {
- Some(s) => *s,
- None => match info.payload().downcast_ref::<String>() {
- Some(s) => &**s,
- None => "Box<Any>",
- },
- };
+ let payload = info.payload();
+ let payload = None
+ .or_else(|| payload.downcast_ref::<&str>().map(|s| s.to_string()))
+ .or_else(|| payload.downcast_ref::<String>().map(|s| s.clone()))
+ .unwrap_or_else(|| "Box<Any>".to_string());
+
+ let backtrace = Backtrace::new();
+ let backtrace = backtrace
+ .frames()
+ .iter()
+ .filter_map(|frame| {
+ let symbol = frame.symbols().first()?;
+ let path = symbol.filename()?;
+ Some((path, symbol.lineno(), format!("{:#}", symbol.name()?)))
+ })
+ .collect::<Vec<_>>();
+
+ let this_file_path = Path::new(file!());
+
+ // Find the first frame in the backtrace for this panic hook itself. Exclude
+ // that frame and all frames before it.
+ let mut start_frame_ix = 0;
+ let mut codebase_root_path = None;
+ for (ix, (path, _, _)) in backtrace.iter().enumerate() {
+ if path.ends_with(this_file_path) {
+ start_frame_ix = ix + 1;
+ codebase_root_path = path.ancestors().nth(this_file_path.components().count());
+ break;
+ }
+ }
+
+ // Exclude any subsequent frames inside of rust's panic handling system.
+ while let Some((path, _, _)) = backtrace.get(start_frame_ix) {
+ if path.starts_with("/rustc") {
+ start_frame_ix += 1;
+ } else {
+ break;
+ }
+ }
+
+ // Build two backtraces:
+ // * one for display, which includes symbol names for all frames, and files
+ // and line numbers for symbols in this codebase
+ // * one for identification and de-duplication, which only includes symbol
+ // names for symbols in this codebase.
+ let mut display_backtrace = Vec::new();
+ let mut identifying_backtrace = Vec::new();
+ for (path, line, symbol) in &backtrace[start_frame_ix..] {
+ display_backtrace.push(symbol.clone());
+
+ if let Some(codebase_root_path) = &codebase_root_path {
+ if let Ok(suffix) = path.strip_prefix(&codebase_root_path) {
+ identifying_backtrace.push(symbol.clone());
+
+ let display_path = suffix.to_string_lossy();
+ if let Some(line) = line {
+ display_backtrace.push(format!(" {display_path}:{line}"));
+ } else {
+ display_backtrace.push(format!(" {display_path}"));
+ }
+ }
+ }
+ }
let panic_data = Panic {
thread: thread.into(),
@@ -416,11 +471,6 @@ fn init_panic_hook(app: &App) {
file: location.file().into(),
line: location.line(),
}),
- backtrace: format!("{:?}", backtrace)
- .split("\n")
- .map(|line| line.to_string())
- .collect(),
- // modified_backtrace: None,
release_channel: RELEASE_CHANNEL.dev_name().into(),
os_name: platform.os_name().into(),
os_version: platform
@@ -432,6 +482,11 @@ fn init_panic_hook(app: &App) {
.duration_since(UNIX_EPOCH)
.unwrap()
.as_millis(),
+ backtrace: display_backtrace,
+ identifying_backtrace: identifying_backtrace
+ .is_empty()
+ .not()
+ .then_some(identifying_backtrace),
};
if let Some(panic_data_json) = serde_json::to_string_pretty(&panic_data).log_err() {