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}