Skip to main content

ntex_error/
bt.rs

1//! Backtrace
2#![allow(warnings)]
3use std::collections::HashMap;
4use std::hash::{BuildHasher, Hasher};
5use std::panic::Location;
6use std::{cell::RefCell, fmt, fmt::Write, os, path, ptr, sync::Arc, sync::LazyLock};
7
8use backtrace::{BacktraceFmt, BacktraceFrame, BytesOrWideString, Frame};
9
10thread_local! {
11    static FRAMES: RefCell<HashMap<usize, Arc<BacktraceFrame>>> = RefCell::new(HashMap::default());
12    static REPRS: RefCell<HashMap<u64, Arc<str>>> = RefCell::new(HashMap::default());
13    static DEFAULT: Arc<str> = Arc::from("Unresolved backtrace");
14}
15
16static mut START: Option<(&'static str, u32)> = None;
17static mut START_ALT: Option<(&'static str, u32)> = None;
18
19/// Sets the source location where rendered backtraces are cut off.
20///
21/// The frame matching `file` (a path suffix) and `line` (`0` matches any line),
22/// and all of its callers, are omitted from the rendered backtrace.
23/// Must be called once during initialization, before any backtrace is resolved.
24pub fn set_backtrace_start(file: &'static str, line: u32) {
25    unsafe {
26        START = Some((file, line));
27    }
28}
29
30#[doc(hidden)]
31pub fn set_backtrace_start_alt(file: &'static str, line: u32) {
32    unsafe {
33        START_ALT = Some((file, line));
34    }
35}
36
37#[derive(Clone)]
38/// Representation of a backtrace.
39///
40/// This structure can be used to capture a backtrace at various
41/// points in a program and later used to inspect what the backtrace
42/// was at that time.
43pub struct Backtrace(Arc<BacktraceRaw>);
44
45#[derive(Debug)]
46/// Backtrace resolver.
47///
48/// Symbol resolution may require filesystem access and can be blocking.
49/// In asynchronous contexts, this work should be offloaded to a thread
50/// pool.
51///
52/// **Note:** Once resolution is complete, control must return to the
53/// originating thread to ensure caching is performed correctly.
54pub struct BacktraceResolver {
55    bt: Arc<BacktraceRaw>,
56    repr: Option<Arc<str>>,
57    resolved: bool,
58    frames: HashMap<usize, Arc<BacktraceFrame>>,
59}
60
61#[derive(Debug)]
62/// Representation of a backtrace.
63pub struct BacktraceRaw {
64    id: u64,
65    frames: [Option<Frame>; 80],
66    location: &'static str,
67}
68
69impl BacktraceRaw {
70    /// Create new backtrace
71    pub fn new(location: &'static Location<'static>) -> Self {
72        Self::with_filename(location.file())
73    }
74
75    #[track_caller]
76    /// Create new backtrace with current location
77    pub fn with_current() -> Self {
78        Self::new(Location::caller())
79    }
80
81    /// Create new backtrace with filename location
82    pub fn with_filename(location: &'static str) -> Self {
83        Self::capture(location, |cb| backtrace::trace(cb))
84    }
85
86    #[doc(hidden)]
87    /// Create new backtrace without acquiring the global backtrace lock.
88    ///
89    /// Does not allocate or lock, so it can be used from a signal handler
90    /// (subject to the platform unwinder being signal-safe).
91    ///
92    /// # Safety
93    ///
94    /// Same requirements as [`backtrace::trace_unsynchronized`].
95    pub unsafe fn with_filename_unsynchronized(location: &'static str) -> Self {
96        Self::capture(location, |cb| unsafe {
97            backtrace::trace_unsynchronized(cb);
98        })
99    }
100
101    fn capture<F>(location: &'static str, trace: F) -> Self
102    where
103        F: FnOnce(&mut dyn FnMut(&Frame) -> bool),
104    {
105        let mut st = foldhash::fast::FixedState::default().build_hasher();
106        let mut idx = 0;
107        let mut frames: [Option<Frame>; 80] = [const { None }; 80];
108
109        trace(&mut |frm| {
110            let ip = frm.ip();
111            st.write_usize(ip as usize);
112            frames[idx] = Some(frm.clone());
113            idx += 1;
114            idx < 80
115        });
116        let id = st.finish();
117
118        BacktraceRaw {
119            id,
120            frames,
121            location,
122        }
123    }
124}
125
126impl From<BacktraceRaw> for Backtrace {
127    fn from(bt: BacktraceRaw) -> Backtrace {
128        Backtrace(Arc::new(bt))
129    }
130}
131
132impl Backtrace {
133    /// Create new backtrace
134    pub fn new(location: &'static Location<'static>) -> Self {
135        Self(Arc::new(BacktraceRaw::new(location)))
136    }
137
138    #[track_caller]
139    /// Create new backtrace with current location
140    pub fn with_current() -> Self {
141        Self(Arc::new(BacktraceRaw::new(Location::caller())))
142    }
143
144    /// Create new backtrace with filename location
145    pub fn with_filename(location: &'static str) -> Self {
146        Self(Arc::new(BacktraceRaw::with_filename(location)))
147    }
148
149    /// Returns the rendered backtrace, if it has been resolved on the current thread.
150    pub fn repr(&self) -> Option<Arc<str>> {
151        REPRS.with(|r| r.borrow_mut().get(&self.0.id).cloned())
152    }
153
154    /// Returns `true` if this backtrace has been resolved (or resolution
155    /// has started) on the current thread.
156    pub fn is_resolved(&self) -> bool {
157        REPRS.with(|r| r.borrow_mut().contains_key(&self.0.id))
158    }
159
160    /// Resolves the backtrace symbols on the current thread and caches the result.
161    #[must_use]
162    pub fn resolve(self) -> Self {
163        self.resolver().resolve();
164        self
165    }
166
167    /// Returns a resolver that can resolve symbols outside the current thread.
168    ///
169    /// See [`BacktraceResolver`] for details.
170    pub fn resolver(&self) -> BacktraceResolver {
171        REPRS.with(|r| {
172            let mut reprs = r.borrow_mut();
173            if let Some(repr) = reprs.get(&self.0.id) {
174                BacktraceResolver {
175                    repr: None,
176                    resolved: true,
177                    bt: self.0.clone(),
178                    frames: HashMap::default(),
179                }
180            } else {
181                DEFAULT.with(|s| {
182                    reprs.insert(self.0.id, s.clone());
183                });
184
185                let mut frames = HashMap::default();
186
187                FRAMES.with(|c| {
188                    let mut cache = c.borrow();
189
190                    for frm in &self.0.frames {
191                        if let Some(frm) = frm {
192                            let ip = frm.ip() as usize;
193                            if let Some(frame) = cache.get(&ip) {
194                                frames.insert(ip, frame.clone());
195                            }
196                        }
197                    }
198                });
199
200                BacktraceResolver {
201                    frames,
202                    resolved: false,
203                    repr: None,
204                    bt: self.0.clone(),
205                }
206            }
207        })
208    }
209}
210
211impl BacktraceResolver {
212    /// Resolves backtrace symbols and renders the representation.
213    ///
214    /// The result is cached when the resolver is dropped.
215    #[allow(clippy::return_self_not_must_use)]
216    pub fn resolve(mut self) -> Self {
217        if self.resolved {
218            return self;
219        }
220
221        for frm in &self.bt.frames {
222            if let Some(frm) = frm {
223                let ip = frm.ip() as usize;
224                if self.frames.contains_key(&ip) {
225                    continue;
226                }
227
228                let mut f = BacktraceFrame::from(frm.clone());
229                f.resolve();
230                self.frames.insert(ip, Arc::new(f));
231            }
232        }
233
234        let mut idx = 0;
235        let mut frames: [Option<&BacktraceFrame>; 80] = [None; 80];
236        for frm in &self.bt.frames {
237            if let Some(frm) = frm {
238                let ip = frm.ip() as usize;
239                frames[idx] = Some(self.frames[&ip].as_ref());
240                idx += 1;
241            }
242        }
243
244        find_loc(self.bt.location, &mut frames);
245
246        #[allow(static_mut_refs)]
247        {
248            if let Some(start) = unsafe { START } {
249                find_loc_start(start, &mut frames);
250            }
251            if let Some(start) = unsafe { START_ALT } {
252                find_loc_start(start, &mut frames);
253            }
254            PATHS2.with(|paths| {
255                for s in paths {
256                    find_loc_start((s.as_str(), 0), &mut frames);
257                }
258            });
259        }
260
261        let mut idx = 0;
262        for frm in &mut frames {
263            if frm.is_some() {
264                if idx > 10 {
265                    *frm = None;
266                } else {
267                    idx += 1;
268                }
269            }
270        }
271
272        let bt = Bt(&frames[..]);
273        let mut buf = String::new();
274        let _ = write!(&mut buf, "\n{bt:?}");
275        self.repr = Some(Arc::from(buf));
276
277        self
278    }
279}
280
281impl Drop for BacktraceResolver {
282    fn drop(&mut self) {
283        if !self.resolved {
284            if let Some(repr) = self.repr.take() {
285                REPRS.with(|r| {
286                    r.borrow_mut().insert(self.bt.id, repr);
287                });
288            }
289
290            FRAMES.with(|c| {
291                let mut cache = c.borrow_mut();
292
293                for (ip, frm) in &self.frames {
294                    let ip = frm.ip() as usize;
295                    if !cache.contains_key(&ip) {
296                        cache.insert(ip, frm.clone());
297                    }
298                }
299            });
300        }
301    }
302}
303
304fn find_loc(loc: &str, frames: &mut [Option<&BacktraceFrame>]) {
305    let mut idx = 0;
306
307    'outter: for (i, frm) in frames.iter().enumerate() {
308        if let Some(f) = frm {
309            for sym in f.symbols() {
310                if let Some(fname) = sym.filename()
311                    && fname.ends_with(loc)
312                {
313                    idx = i;
314                    break 'outter;
315                }
316            }
317        } else {
318            break;
319        }
320    }
321
322    for f in frames.iter_mut().take(idx) {
323        *f = None;
324    }
325
326    PATHS.with(|paths| {
327        'outter: for frm in &mut frames[idx..] {
328            if let Some(f) = frm {
329                for sym in f.symbols() {
330                    if let Some(fname) = sym.filename() {
331                        for p in paths {
332                            if fname.ends_with(p) {
333                                *frm = None;
334                                continue 'outter;
335                            }
336                        }
337                    }
338                }
339            }
340        }
341    });
342}
343
344thread_local! {
345    static PATHS: Vec<String> = {
346        let mut paths = Vec::new();
347        for item in [
348            &["src", "ctx.rs"][..],
349            &["src", "map_err.rs"][..],
350            &["src", "and_then.rs"][..],
351            &["src", "fn_service.rs"][..],
352            &["src", "pipeline.rs"][..],
353            &["src", "net", "factory.rs"][..],
354            &["src", "future", "future.rs"][..],
355            &["src", "net", "service.rs"][..],
356            &["src", "boxed.rs"][..],
357            &["src", "error.rs"][..],
358            &["src", "wrk.rs"][..],
359            &["src", "future.rs"][..],
360            &["std", "src", "thread", "local.rs"][..],
361        ] {
362            paths.push(item.iter().collect::<path::PathBuf>().to_string_lossy().into_owned());
363        }
364        paths
365    };
366
367    static PATHS2: Vec<String> = {
368        let mut paths = Vec::new();
369        for item in [
370            &["src", "driver.rs"][..],
371            &["src", "rt_compio.rs"][..],
372            &["core", "src", "panic", "unwind_safe.rs"][..],
373            &["src", "runtime", "task", "core.rs"][..]
374        ] {
375            paths.push(item.iter().collect::<path::PathBuf>().to_string_lossy().into_owned());
376        }
377        paths
378    }
379}
380
381fn find_loc_start(loc: (&str, u32), frames: &mut [Option<&BacktraceFrame>]) {
382    let mut idx = 0;
383    while idx < frames.len() {
384        if let Some(frm) = &frames[idx] {
385            for sym in frm.symbols() {
386                if let Some(fname) = sym.filename()
387                    && let Some(lineno) = sym.lineno()
388                    && fname.ends_with(loc.0)
389                    && (loc.1 == 0 || lineno == loc.1)
390                {
391                    for f in frames.iter_mut().skip(idx) {
392                        if f.is_some() {
393                            *f = None;
394                        }
395                    }
396                    return;
397                }
398            }
399        }
400        idx += 1;
401    }
402}
403
404struct Bt<'a>(&'a [Option<&'a BacktraceFrame>]);
405
406impl fmt::Debug for Bt<'_> {
407    fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
408        let cwd = std::env::current_dir();
409        let mut print_path = move |fmt: &mut fmt::Formatter<'_>, path: BytesOrWideString<'_>| {
410            let path = crate::utils::module_path_fs(path.to_str_lossy().as_ref());
411            fmt::Display::fmt(&path, fmt)
412        };
413
414        let mut f = BacktraceFmt::new(fmt, backtrace::PrintFmt::Short, &mut print_path);
415        f.add_context()?;
416        for frm in self.0.iter().flatten() {
417            f.frame().backtrace_frame(frm)?;
418        }
419        f.finish()?;
420        Ok(())
421    }
422}
423
424impl fmt::Debug for Backtrace {
425    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
426        if let Some(repr) = self.repr() {
427            fmt::Display::fmt(repr.as_ref(), f)
428        } else {
429            Ok(())
430        }
431    }
432}
433
434impl fmt::Display for Backtrace {
435    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
436        if let Some(repr) = self.repr() {
437            fmt::Display::fmt(repr.as_ref(), f)
438        } else {
439            Ok(())
440        }
441    }
442}