use std::collections::{hash_map::DefaultHasher, HashMap, HashSet}; use std::fmt; use std::hash::{Hash, Hasher}; use super::{Backtrace, Symbol, SymbolTrace, Trace}; /// An adjacency list representation of an execution tree. /// /// This tree provides a convenient intermediate representation for formatting /// [`Trace`] as a tree. pub(super) struct Tree { /// The roots of the trees. /// /// There should only be one root, but the code is robust to multiple roots. roots: HashSet, /// The adjacency list of symbols in the execution tree(s). edges: HashMap>, } impl Tree { /// Constructs a [`Tree`] from [`Trace`] pub(super) fn from_trace(trace: Trace) -> Self { let mut roots: HashSet = HashSet::default(); let mut edges: HashMap> = HashMap::default(); for trace in trace.backtraces { let trace = to_symboltrace(trace); if let Some(first) = trace.first() { roots.insert(first.to_owned()); } let mut trace = trace.into_iter().peekable(); while let Some(frame) = trace.next() { let subframes = edges.entry(frame).or_default(); if let Some(subframe) = trace.peek() { subframes.insert(subframe.clone()); } } } Tree { roots, edges } } /// Produces the sub-symbols of a given symbol. fn consequences(&self, frame: &Symbol) -> Option> { Some(self.edges.get(frame)?.iter()) } /// Format this [`Tree`] as a textual tree. fn display( &self, f: &mut W, root: &Symbol, is_last: bool, prefix: &str, ) -> fmt::Result { let root_fmt = format!("{}", root); let current; let next; if is_last { current = format!("{prefix}└╼\u{a0}{root_fmt}"); next = format!("{}\u{a0}\u{a0}\u{a0}", prefix); } else { current = format!("{prefix}├╼\u{a0}{root_fmt}"); next = format!("{}│\u{a0}\u{a0}", prefix); } write!(f, "{}", { let mut current = current.chars(); current.next().unwrap(); current.next().unwrap(); ¤t.as_str() })?; if let Some(consequences) = self.consequences(root) { let len = consequences.len(); for (i, consequence) in consequences.enumerate() { let is_last = i == len - 1; writeln!(f)?; self.display(f, consequence, is_last, &next)?; } } Ok(()) } } impl fmt::Display for Tree { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { for root in &self.roots { self.display(f, root, true, " ")?; } Ok(()) } } /// Resolve a sequence of [`backtrace::BacktraceFrame`]s into a sequence of /// [`Symbol`]s. fn to_symboltrace(backtrace: Backtrace) -> SymbolTrace { // Resolve the backtrace frames to symbols. let backtrace: Backtrace = { let mut backtrace = backtrace::Backtrace::from(backtrace); backtrace.resolve(); backtrace.into() }; // Accumulate the symbols in descending order into `symboltrace`. let mut symboltrace: SymbolTrace = vec![]; let mut state = DefaultHasher::new(); for frame in backtrace.into_iter().rev() { for symbol in frame.symbols().iter().rev() { let symbol = Symbol { symbol: symbol.clone(), parent_hash: state.finish(), }; symbol.hash(&mut state); symboltrace.push(symbol); } } symboltrace }