From 2b4727b7c0b45ddc5a7507d4bc3bd390a28ddd1d Mon Sep 17 00:00:00 2001 From: Glauber Costa Date: Mon, 26 Apr 2021 10:11:13 -0400 Subject: [PATCH] implement From for BacktraceFrame (#420) * implement From for BacktraceFrame There are situations where we can only capture raw `Frame` (example: capturing from a signal handler), but it could still be that we can process them later and are not fully nostd in the environment. With a conversion from Frame to BacktraceFrame, this is trivial. But without it, we can't really use the pretty printers as they are reliant on BacktraceFrame, not Frame. Fixes: #419 * add test * fixes for miri Every backtrace that miri generates - conversion or no conversion, has totally different addresses. They all resolve to the same thing, though, so test with that. * fix another broken test `cargo test --no-default-features --features std` fails because frames are not resolved to symbols in that case. We'll just remove the assert, rather than disabling the whole test on that case. At least the code paths are stressed. --- src/capture.rs | 65 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) diff --git a/src/capture.rs b/src/capture.rs index 9bd6ce907..713667dea 100644 --- a/src/capture.rs +++ b/src/capture.rs @@ -249,6 +249,15 @@ impl From> for Backtrace { } } +impl From for BacktraceFrame { + fn from(frame: crate::Frame) -> BacktraceFrame { + BacktraceFrame { + frame: Frame::Raw(frame), + symbols: None, + } + } +} + impl Into> for Backtrace { fn into(self) -> Vec { self.frames @@ -518,3 +527,59 @@ mod serde_impls { } } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_frame_conversion() { + // captures an original backtrace, and makes sure that the manual conversion + // to frames yields the same results. + let mut bt = Backtrace::new(); + bt.resolve(); + let original_frames = bt.frames(); + + let mut frames = vec![]; + crate::trace(|frame| { + let converted = BacktraceFrame::from(frame.clone()); + frames.push(converted); + true + }); + + let mut manual = Backtrace::from(frames); + manual.resolve(); + let frames = manual.frames(); + + // the first frames can be different because we call from slightly different places, + // and the `trace` version has an extra capture. But because of inlining the number of + // frames that differ may be different between release and debug versions. Plus who knows + // what the compiler will do in the future. So we just take 4 frames from the end and make + // sure they match + // + // For miri, every backtrace that is taken yields different addresses and different + // instruction pointers. That is irrespective of conversion and happens even if + // Backtrace::new() is invoked in a row. They all resolve to the same names, though, so to + // make sure this works on miri we resolve the symbols and compare the results. It is okay + // if some addresses don't have symbols, but if we scan enough frames at least some will do + for (converted, og) in frames + .iter() + .rev() + .take(4) + .zip(original_frames.iter().rev().take(4)) + { + let converted_symbols = converted.symbols(); + let og_symbols = og.symbols(); + + assert_eq!(og_symbols.len(), converted_symbols.len()); + for (os, cs) in og_symbols.iter().zip(converted_symbols.iter()) { + assert_eq!( + os.name().map(|x| x.as_bytes()), + cs.name().map(|x| x.as_bytes()) + ); + assert_eq!(os.filename(), cs.filename()); + assert_eq!(os.lineno(), cs.lineno()); + } + } + } +}