Skip to main content

oopsie_core/
marker.rs

1//! Thread-local trace markers.
2//!
3//! A marker records the raw stack — one `ip` per physical frame, never
4//! symbolicated — at a chosen point. Captures on the same thread embed a
5//! snapshot of it, and rendering hides the trace's common suffix with the
6//! marker: an exact bottom boundary instead of symbol-name heuristics.
7
8use std::cell::Cell;
9use std::sync::Arc;
10
11/// A recorded stack: one ip per physical frame, top → bottom.
12#[doc(hidden)]
13#[derive(Debug, Clone)]
14pub struct TraceMarker {
15    pub(crate) ips: Arc<[usize]>,
16}
17
18/// A stack frame exposing its instruction pointer, the value markers compare on.
19pub trait FrameLike {
20    /// The frame's instruction pointer.
21    fn ip(&self) -> usize;
22}
23
24impl FrameLike for backtrace::BacktraceFrame {
25    #[inline]
26    fn ip(&self) -> usize {
27        self.ip() as usize
28    }
29}
30
31impl FrameLike for backtrace::Frame {
32    #[inline]
33    fn ip(&self) -> usize {
34        self.ip() as usize
35    }
36}
37
38impl TraceMarker {
39    /// Number of trailing `trace` frames hidden by this marker: the longest
40    /// common `ip` suffix.
41    pub(crate) fn cut_len(&self, trace: &[impl FrameLike]) -> usize {
42        let m = &self.ips;
43        let mut k = 0;
44        while k < trace.len()
45            && k < m.len()
46            && trace[trace.len() - 1 - k].ip() == m[m.len() - 1 - k]
47        {
48            k += 1;
49        }
50        k
51    }
52}
53
54thread_local! {
55    static MARKER: Cell<Option<TraceMarker>> = const { Cell::new(None) };
56}
57
58fn capture_marker() -> TraceMarker {
59    let mut ips = Vec::with_capacity(32);
60    backtrace::trace(|frame| {
61        ips.push(FrameLike::ip(frame));
62        true
63    });
64    TraceMarker { ips: ips.into() }
65}
66
67/// Snapshot the current thread's marker, if any. All slot accessors degrade
68/// to a no-op once the thread's TLS is being torn down — capture inside a
69/// dying thread's panic hook must never double-panic.
70pub fn current() -> Option<TraceMarker> {
71    MARKER
72        .try_with(|slot| {
73            let cur = slot.take();
74            let snapshot = cur.clone();
75            slot.set(cur);
76            snapshot
77        })
78        .ok()
79        .flatten()
80}
81
82/// Install a marker captured at the caller, returning the previous marker
83/// for [`restore_marker`].
84#[doc(hidden)]
85#[must_use]
86pub fn set_marker() -> Option<TraceMarker> {
87    let data = capture_marker();
88    MARKER
89        .try_with(|slot| {
90            let prev = slot.take();
91            slot.set(Some(data));
92            prev
93        })
94        .ok()
95        .flatten()
96}
97
98#[doc(hidden)]
99pub fn restore_marker(prev: Option<TraceMarker>) {
100    let _ = MARKER.try_with(|slot| slot.set(prev));
101}
102
103/// Marks the start of meaningful backtraces on the current thread: frames
104/// below this call are hidden from rendered error and panic traces. The
105/// function containing the call stays visible. A later call replaces the
106/// marker; other threads are unaffected.
107/// `RUST_BACKTRACE=full` renders traces unfiltered, ignoring markers.
108/// Hiding is applied by the `fancy` feature's renderers; without that
109/// feature the marker is recorded but has no effect.
110#[macro_export]
111macro_rules! start_marker {
112    () => {{
113        let _ = $crate::__private::set_marker();
114    }};
115}
116
117#[cfg(test)]
118mod tests {
119    use super::*;
120
121    impl FrameLike for usize {
122        fn ip(&self) -> usize {
123            *self
124        }
125    }
126
127    fn ips(values: &[usize]) -> Arc<[usize]> {
128        values.into()
129    }
130
131    #[test]
132    fn capture_records_frames() {
133        let marker = capture_marker();
134        assert!(!marker.ips.is_empty());
135    }
136
137    #[test]
138    fn set_marker_is_latest_wins() {
139        let _ = set_marker();
140        let first = current().expect("marker set");
141        let _ = set_marker();
142        let second = current().expect("marker set");
143        assert!(!Arc::ptr_eq(&first.ips, &second.ips));
144    }
145
146    #[test]
147    fn current_returns_clone_and_leaves_slot_intact() {
148        let _ = set_marker();
149        let a = current().expect("marker set");
150        let b = current().expect("still set");
151        assert!(Arc::ptr_eq(&a.ips, &b.ips));
152    }
153
154    #[test]
155    fn set_marker_returns_previous_and_restore_reinstates_it() {
156        let _ = set_marker();
157        let user_marker = current().expect("marker set");
158        let prev = set_marker();
159        let replacement = current().expect("replacement marker set");
160        assert!(!Arc::ptr_eq(&user_marker.ips, &replacement.ips));
161        restore_marker(prev);
162        let restored = current().expect("previous marker restored");
163        assert!(Arc::ptr_eq(&user_marker.ips, &restored.ips));
164    }
165
166    #[test]
167    fn cut_len_exact_suffix() {
168        let marker = TraceMarker {
169            ips: ips(&[900, 10, 20, 30]),
170        };
171        let trace = ips(&[800, 11, 20, 30]);
172        assert_eq!(marker.cut_len(&trace), 2);
173    }
174
175    #[test]
176    fn cut_len_disjoint_stacks_is_zero() {
177        let marker = TraceMarker {
178            ips: ips(&[10, 20]),
179        };
180        assert_eq!(marker.cut_len(&ips(&[77, 88])), 0);
181    }
182
183    #[test]
184    fn cut_len_empty_marker_is_zero() {
185        let marker = TraceMarker { ips: ips(&[]) };
186        assert_eq!(marker.cut_len(&ips(&[1])), 0);
187    }
188
189    #[test]
190    fn cut_len_empty_trace_is_zero() {
191        let marker = TraceMarker {
192            ips: ips(&[10, 20]),
193        };
194        assert_eq!(marker.cut_len(&ips(&[])), 0);
195    }
196
197    #[test]
198    fn cut_len_trace_fully_contained_is_capped() {
199        let marker = TraceMarker {
200            ips: ips(&[10, 20, 30]),
201        };
202        let trace = ips(&[20, 30]);
203        assert_eq!(marker.cut_len(&trace), 2);
204    }
205}