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