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