Skip to main content

scoped_trace/
lib.rs

1//! Capture scoped backtraces.
2//!
3//! Use [`Trace::root`] to define the upper unwinding bound of an execution
4//! trace, and [`Trace::leaf`] to define its lower bounds (the points at which
5//! backtraces are collected). The resulting traces are trees, since a single
6//! invocation of [`Trace::root`] may have multiple sub-invocations of
7//! [`Trace::leaf`].
8//!
9//! [`Trace::root`]: crate::Trace::root
10//! [`Trace::leaf`]: crate::Trace::leaf
11//!
12//! For example, running this program:
13//! ```
14//! use scoped_trace::Trace;
15//!
16//! fn main() {
17//!     let (_, trace) = Trace::root(|| foo());
18//!     println!("{trace}");
19//! }
20//!
21//! fn foo() {
22//!     bar();
23//!     baz();
24//! }
25//!
26//! fn bar() {
27//!     Trace::leaf();
28//! }
29//!
30//! fn baz() {
31//!     Trace::leaf();
32//! }
33//! ```
34//! ...will produce an output like:
35//! ```text
36//! ╼ inlining::main::{{closure}} at example.rs:4:38
37//!   ├╼ inlining::foo at example.rs:9:5
38//!   │  └╼ inlining::bar at example.rs:14:5
39//!   └╼ inlining::foo at example.rs:10:5
40//!      └╼ inlining::baz at example.rs:18:5
41//! ```
42
43use backtrace::BacktraceFrame;
44use std::{cell::Cell, ffi::c_void, fmt, ptr};
45
46mod symbol;
47mod tree;
48
49use symbol::Symbol;
50use tree::Tree;
51
52type Backtrace = Vec<BacktraceFrame>;
53type SymbolTrace = Vec<Symbol>;
54
55/// An execution trace.
56///
57/// This trace is constructed by calling [`Trace::root`] with a closure, and
58/// includes all execution Trace of that closure that end in an invocation of
59/// [`Trace::leaf`].
60#[derive(Clone)]
61pub struct Trace {
62    // The linear backtraces that comprise this trace. These linear traces can
63    // be re-knitted into a tree.
64    backtraces: Vec<Backtrace>,
65}
66
67/// The ambiant backtracing context.
68struct Context {
69    /// The address of [`Trace::root`] establishes an upper unwinding bound on
70    /// the backtraces in `Trace`.
71    root_addr: *const c_void,
72    /// The collection of backtraces collected beneath the invocation of
73    /// [`Trace::root`].
74    trace: Trace,
75}
76
77impl Trace {
78    /// Invokes `f`, returning both its result and the collection of backtraces
79    /// captured at each sub-invocation of [`Trace::leaf`].
80    pub fn root<F, R>(f: F) -> (R, Trace)
81    where
82        F: FnOnce() -> R,
83    {
84        // initialize the current backtracing context with an empty `Context`.
85        Context::with_current(|cell| Self::root_inner(f, cell))
86    }
87
88    // This function is marked `#[inline(never)]` to ensure that it gets a distinct
89    // `Frame` in the backtrace, above which we do not need to unwind.
90    #[inline(never)]
91    fn root_inner<F, R>(f: F, cell: &Cell<Option<Context>>) -> (R, Trace)
92    where
93        F: FnOnce() -> R,
94    {
95        cell.set(Some(Context::new::<F, R>()));
96
97        // if `f()` panics, reset the ambiant context to `None`.
98        let _deferred = defer(|| {
99            cell.set(None);
100        });
101
102        let result = f();
103
104        // take the resulting `Trace` and return it.
105        let context = cell.take().unwrap();
106
107        (result, context.trace)
108    }
109
110    /// If this is a sub-invocation of [`Trace::root`], capture a backtrace.
111    ///
112    /// The captured backtrace will be returned by [`Trace::root`].
113    ///
114    /// Invoking this function does nothing when it is not a sub-invocation
115    /// [`Trace::root`].
116    // This function is marked `#[inline(never)]` to ensure that it gets a distinct `Frame` in the
117    // backtrace, below which frames should not be included in the backtrace (since they reflect the
118    // internal implementation details of this crate).
119    #[inline(never)]
120    pub fn leaf() {
121        Context::with_current(|context_cell| {
122            if let Some(mut context) = context_cell.take() {
123                let mut frames = vec![];
124                let mut above_leaf = false;
125                backtrace::trace(|frame| {
126                    let below_root = !ptr::eq(frame.symbol_address(), context.root_addr);
127
128                    // only capture frames above `Trace::leaf()` and below
129                    // `Trace::root_inner()`.
130                    if above_leaf && below_root {
131                        frames.push(frame.to_owned().into());
132                    }
133
134                    if ptr::eq(frame.symbol_address(), Self::leaf as *const _) {
135                        above_leaf = true;
136                    }
137
138                    // only continue unwinding if we're below `Trace::root`
139                    below_root
140                });
141                context.trace.backtraces.push(frames);
142                context_cell.set(Some(context));
143            }
144        });
145    }
146}
147
148impl fmt::Display for Trace {
149    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
150        Tree::from_trace(self.clone()).fmt(f)
151    }
152}
153
154impl Context {
155    /// Constructs a new, empty ambient backtracing context.
156    fn new<F, R>() -> Self
157    where
158        F: FnOnce() -> R,
159    {
160        // the address of this function is used to establish an upper unwinding bound
161        let root_addr = Trace::root_inner::<F, R> as *const c_void;
162
163        Self {
164            root_addr,
165            trace: Trace { backtraces: vec![] },
166        }
167    }
168
169    /// Manipulate the current active backtracing context.
170    fn with_current<F, R>(f: F) -> R
171    where
172        F: FnOnce(&Cell<Option<Context>>) -> R,
173    {
174        thread_local! {
175            /// The current ambiant backtracing context, if any.
176            #[allow(clippy::declare_interior_mutable_const)]
177            static CURRENT_CONTEXT: Cell<Option<Context>> = const { Cell::new(None) };
178        }
179
180        CURRENT_CONTEXT.with(f)
181    }
182}
183
184fn defer<F: FnOnce() -> R, R>(f: F) -> impl Drop {
185    use std::mem::ManuallyDrop;
186
187    struct Defer<F: FnOnce() -> R, R>(ManuallyDrop<F>);
188
189    impl<F: FnOnce() -> R, R> Drop for Defer<F, R> {
190        #[inline(always)]
191        fn drop(&mut self) {
192            unsafe {
193                ManuallyDrop::take(&mut self.0)();
194            }
195        }
196    }
197
198    Defer(ManuallyDrop::new(f))
199}