aboutsummaryrefslogtreecommitdiff
path: root/vendor/backtrace-ext/src/lib.rs
diff options
context:
space:
mode:
authorValentin Popov <valentin@popov.link>2024-01-08 00:21:28 +0300
committerValentin Popov <valentin@popov.link>2024-01-08 00:21:28 +0300
commit1b6a04ca5504955c571d1c97504fb45ea0befee4 (patch)
tree7579f518b23313e8a9748a88ab6173d5e030b227 /vendor/backtrace-ext/src/lib.rs
parent5ecd8cf2cba827454317368b68571df0d13d7842 (diff)
downloadfparkan-1b6a04ca5504955c571d1c97504fb45ea0befee4.tar.xz
fparkan-1b6a04ca5504955c571d1c97504fb45ea0befee4.zip
Initial vendor packages
Signed-off-by: Valentin Popov <valentin@popov.link>
Diffstat (limited to 'vendor/backtrace-ext/src/lib.rs')
-rw-r--r--vendor/backtrace-ext/src/lib.rs277
1 files changed, 277 insertions, 0 deletions
diff --git a/vendor/backtrace-ext/src/lib.rs b/vendor/backtrace-ext/src/lib.rs
new file mode 100644
index 0000000..b04c59d
--- /dev/null
+++ b/vendor/backtrace-ext/src/lib.rs
@@ -0,0 +1,277 @@
+//! Minor conveniences on top of the backtrace crate
+//!
+//! See [`short_frames_strict`][] for details.
+use backtrace::*;
+use std::ops::Range;
+
+#[cfg(test)]
+mod test;
+
+/// Gets an iterator over the frames that are part of Rust's "short backtrace" range.
+/// If no such range is found, the full stack is yielded.
+///
+/// Rust generally tries to include special frames on the stack called `rust_end_short_backtrace`
+/// and `rust_begin_short_backtrace` which delimit the "real" stackframes from "gunk" stackframes
+/// like setting up main and invoking the panic runtime. This yields all the "real" frames between
+/// those two (which theoretically can be nothing with enough optimization, although that's unlikely
+/// for any non-trivial program).
+///
+/// If only one of the special frames is present we will only clamp one side of the stack
+/// (similar to `a..` or `..a`). If the special frames are in the wrong order we will discard
+/// them and produce the full stack. If multiple versions of a special frame are found
+/// (I've seen it in the wild), we will pick the "innermost" ones, producing the smallest
+/// possible backtrace (and excluding all special frames from the output).
+///
+/// Each element of the iterator includes a Range which you should use to slice
+/// the frame's `symbols()` array. This handles the theoretical situation where "real" frames
+/// got inlined together with the special marker frames. I want to believe this can't happen
+/// but you can never trust backtraces to be reasonable! We will never yield a Frame to you
+/// with an empty Range.
+///
+/// Note that some "gunk" frames may still be found within the short backtrace, as there is still some
+/// platform-specific and optimization-specific glue around the edges because compilers are
+/// complicated and nothing's perfect. This can include:
+///
+/// * `core::ops::function::FnOnce::call_once`
+/// * `std::panicking::begin_panic_handler`
+/// * `core::panicking::panic_fmt`
+/// * `rust_begin_unwind`
+///
+/// In the future we may introduce a non-strict short_frames which heuristically filters
+/// those frames out too. Until then, the strict approach is safe.
+///
+/// # Example
+///
+/// Here's an example simple "short backtrace" implementation.
+/// Note the use of `sub_frames` for the inner loop to restrict `symbols`!
+///
+/// This example is based off of code found in `miette` (Apache-2.0), which itself
+/// copied the logic from `human-panic` (MIT/Apache-2.0).
+///
+/// FIXME: it would be nice if this example consulted `RUST_BACKTRACE=full`,
+/// and maybe other vars used by rust's builtin panic handler..?
+///
+/// ```
+/// fn backtrace() -> String {
+/// use std::fmt::Write;
+/// if let Ok(var) = std::env::var("RUST_BACKTRACE") {
+/// if !var.is_empty() && var != "0" {
+/// const HEX_WIDTH: usize = std::mem::size_of::<usize>() + 2;
+/// // Padding for next lines after frame's address
+/// const NEXT_SYMBOL_PADDING: usize = HEX_WIDTH + 6;
+/// let mut backtrace = String::new();
+/// let trace = backtrace::Backtrace::new();
+/// let frames = backtrace_ext::short_frames_strict(&trace).enumerate();
+/// for (idx, (frame, subframes)) in frames {
+/// let ip = frame.ip();
+/// let _ = write!(backtrace, "\n{:4}: {:2$?}", idx, ip, HEX_WIDTH);
+///
+/// let symbols = frame.symbols();
+/// if symbols.is_empty() {
+/// let _ = write!(backtrace, " - <unresolved>");
+/// continue;
+/// }
+///
+/// for (idx, symbol) in symbols[subframes].iter().enumerate() {
+/// // Print symbols from this address,
+/// // if there are several addresses
+/// // we need to put it on next line
+/// if idx != 0 {
+/// let _ = write!(backtrace, "\n{:1$}", "", NEXT_SYMBOL_PADDING);
+/// }
+///
+/// if let Some(name) = symbol.name() {
+/// let _ = write!(backtrace, " - {}", name);
+/// } else {
+/// let _ = write!(backtrace, " - <unknown>");
+/// }
+///
+/// // See if there is debug information with file name and line
+/// if let (Some(file), Some(line)) = (symbol.filename(), symbol.lineno()) {
+/// let _ = write!(
+/// backtrace,
+/// "\n{:3$}at {}:{}",
+/// "",
+/// file.display(),
+/// line,
+/// NEXT_SYMBOL_PADDING
+/// );
+/// }
+/// }
+/// }
+/// return backtrace;
+/// }
+/// }
+/// "".into()
+/// }
+/// ```
+pub fn short_frames_strict(
+ backtrace: &Backtrace,
+) -> impl Iterator<Item = (&BacktraceFrame, Range<usize>)> {
+ short_frames_strict_impl(backtrace)
+}
+
+pub(crate) fn short_frames_strict_impl<B: Backtraceish>(
+ backtrace: &B,
+) -> impl Iterator<Item = (&B::Frame, Range<usize>)> {
+ // Search for the special frames
+ let mut short_start = None;
+ let mut short_end = None;
+ let frames = backtrace.frames();
+ for (frame_idx, frame) in frames.iter().enumerate() {
+ let symbols = frame.symbols();
+ for (subframe_idx, frame) in symbols.iter().enumerate() {
+ if let Some(name) = frame.name_str() {
+ // Yes these ARE backwards, and that's intentional! We want to print the frames from
+ // "newest to oldest" (show what panicked first), and that's the order that Backtrace
+ // gives us, but these magic labels view the stack in the opposite order. So we just
+ // swap it once here and forget about that weirdness.
+ //
+ // Note that due to platform/optimization wobblyness you can end up with multiple frames
+ // that contain these names in sequence. If that happens we just want to pick the two
+ // that are closest together. For the start that means just using the last one we found,
+ // and for the end that means taking the first one we find.
+ if name.contains("rust_end_short_backtrace") {
+ short_start = Some((frame_idx, subframe_idx));
+ }
+ if name.contains("rust_begin_short_backtrace") && short_end.is_none() {
+ short_end = Some((frame_idx, subframe_idx));
+ }
+ }
+ }
+ }
+
+ // Check if these are in the right order, if they aren't, discard them
+ // This also handles the mega-cursed case of "someone made a symbol with both names
+ // so actually they're the exact same subframe".
+ if let (Some(start), Some(end)) = (short_start, short_end) {
+ if start >= end {
+ short_start = None;
+ short_end = None;
+ }
+ }
+
+ // By default we want to produce a full stack trace and now we'll try to clamp it.
+ let mut first_frame = 0usize;
+ let mut first_subframe = 0usize;
+ // NOTE: this is INCLUSIVE
+ let mut last_frame = frames.len().saturating_sub(1);
+ // NOTE: this is EXCLUSIVE
+ let mut last_subframe_excl = backtrace
+ .frames()
+ .last()
+ .map(|frame| frame.symbols().len())
+ .unwrap_or(0);
+
+ // This code tries to be really paranoid about boundary conditions although in practice
+ // most of them are impossible because there's always going to be gunk on either side
+ // of the short backtrace to smooth out the boundaries, and panic_fmt is basically
+ // impossible to optimize out. Still, don't trust backtracers!!!
+ //
+ // This library has a fuckton of tests to try to catch all the little corner cases here.
+
+ // If we found the start bound...
+ if let Some((idx, sub_idx)) = short_start {
+ if frames[idx].symbols().len() == sub_idx + 1 {
+ // If it was the last subframe of this frame, we want to just
+ // use the whole next frame! It's ok if this takes us to `first_frame = len`,
+ // that will be properly handled as an empty output
+ first_frame = idx + 1;
+ first_subframe = 0;
+ } else {
+ // Otherwise use this frame, and all the subframes after it
+ first_frame = idx;
+ first_subframe = sub_idx + 1;
+ }
+ }
+
+ // If we found the end bound...
+ if let Some((idx, sub_idx)) = short_end {
+ if sub_idx == 0 {
+ // If it was the first subframe of this frame, we want to just
+ // use the whole previous frame!
+ if idx == 0 {
+ // If we were *also* on the first frame, set subframe_excl
+ // to 0, indicating an empty output
+ last_frame = 0;
+ last_subframe_excl = 0;
+ } else {
+ last_frame = idx - 1;
+ last_subframe_excl = frames[last_frame].symbols().len();
+ }
+ } else {
+ // Otherwise use this frame (no need subframe math, exclusive bound!)
+ last_frame = idx;
+ last_subframe_excl = sub_idx;
+ }
+ }
+
+ // If the two subframes managed to perfectly line up with eachother, just
+ // throw everything out and yield an empty range. We don't need to fix any
+ // other values at this point as they won't be used for anything with an
+ // empty iterator
+ let final_frames = {
+ let start = (first_frame, first_subframe);
+ let end = (last_frame, last_subframe_excl);
+ if start == end {
+ &frames[0..0]
+ } else {
+ &frames[first_frame..=last_frame]
+ }
+ };
+
+ // Get the index of the last frame when starting from the first frame
+ let adjusted_last_frame = last_frame.saturating_sub(first_frame);
+
+ // finally do the iteration
+ final_frames.iter().enumerate().map(move |(idx, frame)| {
+ // Default to all subframes being yielded
+ let mut sub_start = 0;
+ let mut sub_end_excl = frame.symbols().len();
+ // If we're on first frame, apply its subframe clamp
+ if idx == 0 {
+ sub_start = first_subframe;
+ }
+ // If we're on the last frame, apply its subframe clamp
+ if idx == adjusted_last_frame {
+ sub_end_excl = last_subframe_excl;
+ }
+ (frame, sub_start..sub_end_excl)
+ })
+}
+
+pub(crate) trait Backtraceish {
+ type Frame: Frameish;
+ fn frames(&self) -> &[Self::Frame];
+}
+
+pub(crate) trait Frameish {
+ type Symbol: Symbolish;
+ fn symbols(&self) -> &[Self::Symbol];
+}
+
+pub(crate) trait Symbolish {
+ fn name_str(&self) -> Option<&str>;
+}
+
+impl Backtraceish for Backtrace {
+ type Frame = BacktraceFrame;
+ fn frames(&self) -> &[Self::Frame] {
+ self.frames()
+ }
+}
+
+impl Frameish for BacktraceFrame {
+ type Symbol = BacktraceSymbol;
+ fn symbols(&self) -> &[Self::Symbol] {
+ self.symbols()
+ }
+}
+
+impl Symbolish for BacktraceSymbol {
+ // We need to shortcut SymbolName here because
+ // HRTB isn't in our msrv
+ fn name_str(&self) -> Option<&str> {
+ self.name().and_then(|n| n.as_str())
+ }
+}