Skip to content

Commit

Permalink
implement From<Frame> for BacktraceFrame (#420)
Browse files Browse the repository at this point in the history
* implement From<Frame> 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.
  • Loading branch information
Glauber Costa committed Apr 26, 2021
1 parent 221483e commit 2b4727b
Showing 1 changed file with 65 additions and 0 deletions.
65 changes: 65 additions & 0 deletions src/capture.rs
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,15 @@ impl From<Vec<BacktraceFrame>> for Backtrace {
}
}

impl From<crate::Frame> for BacktraceFrame {
fn from(frame: crate::Frame) -> BacktraceFrame {
BacktraceFrame {
frame: Frame::Raw(frame),
symbols: None,
}
}
}

impl Into<Vec<BacktraceFrame>> for Backtrace {
fn into(self) -> Vec<BacktraceFrame> {
self.frames
Expand Down Expand Up @@ -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());
}
}
}
}

0 comments on commit 2b4727b

Please sign in to comment.