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