sentry_backtrace/
utils.rs

1use std::{borrow::Cow, sync::LazyLock};
2
3use regex::{Captures, Regex};
4
5static HASH_FUNC_RE: LazyLock<Regex> = LazyLock::new(|| {
6    Regex::new(
7        r#"(?x)
8        ^(.*)::h[a-f0-9]{16}$
9    "#,
10    )
11    .unwrap()
12});
13
14static CRATE_HASH_RE: LazyLock<Regex> = LazyLock::new(|| {
15    Regex::new(
16        r"(?x)
17        \b(\[[a-f0-9]{16}\])
18    ",
19    )
20    .unwrap()
21});
22
23static CRATE_RE: LazyLock<Regex> = LazyLock::new(|| {
24    Regex::new(
25        r"(?x)
26        ^
27        (?:_?<)?           # trait impl syntax
28        (?:\w+\ as \ )?    # anonymous implementor
29        ([a-zA-Z0-9_]+?)   # crate name
30        (?:\.\.|::|\[)     # crate delimiter (.. or :: or [)
31    ",
32    )
33    .unwrap()
34});
35
36static COMMON_RUST_SYMBOL_ESCAPES_RE: LazyLock<Regex> = LazyLock::new(|| {
37    Regex::new(
38        r"(?x)
39        \$
40            (SP|BP|RF|LT|GT|LP|RP|C|
41                u7e|u20|u27|u5b|u5d|u7b|u7d|u3b|u2b|u22)
42        \$
43    ",
44    )
45    .unwrap()
46});
47
48/// Tries to parse the rust crate from a function name.
49pub fn parse_crate_name(func_name: &str) -> Option<String> {
50    CRATE_RE
51        .captures(func_name)
52        .and_then(|caps| caps.get(1))
53        .map(|cr| cr.as_str().into())
54}
55
56pub fn filename(s: &str) -> &str {
57    s.rsplit(&['/', '\\'][..]).next().unwrap()
58}
59
60pub fn strip_symbol(s: &str) -> Cow<str> {
61    let stripped_trailing_hash = HASH_FUNC_RE
62        .captures(s)
63        .map(|c| c.get(1).unwrap().as_str())
64        .unwrap_or(s);
65
66    CRATE_HASH_RE.replace_all(stripped_trailing_hash, "")
67}
68
69pub fn demangle_symbol(s: &str) -> String {
70    COMMON_RUST_SYMBOL_ESCAPES_RE
71        .replace_all(s, |caps: &Captures<'_>| match &caps[1] {
72            "SP" => "@",
73            "BP" => "*",
74            "RF" => "&",
75            "LT" => "<",
76            "GT" => ">",
77            "LP" => "(",
78            "RP" => ")",
79            "C" => ",",
80            "u7e" => "~",
81            "u20" => " ",
82            "u27" => "'",
83            "u5b" => "[",
84            "u5d" => "]",
85            "u7b" => "{",
86            "u7d" => "}",
87            "u3b" => ";",
88            "u2b" => "+",
89            "u22" => "\"",
90            _ => unreachable!(),
91        })
92        .to_string()
93}
94
95/// Checks whether the function name starts with the given pattern.
96///
97/// In trait implementations, the original type name is wrapped in "_< ... >" and colons are
98/// replaced with dots. This function accounts for differences while checking.
99/// The `<F as ` pattern is a special case that can often be observed in frames involving futures-rs traits.
100pub fn function_starts_with(mut func_name: &str, mut pattern: &str) -> bool {
101    if pattern.starts_with('<') {
102        while pattern.starts_with('<') {
103            pattern = &pattern[1..];
104
105            if func_name.starts_with("<F as ") {
106                func_name = &func_name[6..];
107            } else if func_name.starts_with('<') {
108                func_name = &func_name[1..];
109            } else if func_name.starts_with("_<") {
110                func_name = &func_name[2..];
111            } else {
112                return false;
113            }
114        }
115    } else {
116        func_name = func_name
117            .trim_start_matches("<F as ")
118            .trim_start_matches('<')
119            .trim_start_matches("_<");
120    }
121
122    if !func_name.is_char_boundary(pattern.len()) {
123        return false;
124    }
125
126    func_name
127        .chars()
128        .zip(pattern.chars())
129        .all(|(f, p)| f == p || f == '.' && p == ':')
130}
131
132#[cfg(test)]
133mod tests {
134    use super::*;
135
136    #[test]
137    fn test_function_starts_with() {
138        assert!(function_starts_with(
139            "futures::task_impl::std::set",
140            "futures::"
141        ));
142
143        assert!(!function_starts_with(
144            "futures::task_impl::std::set",
145            "tokio::"
146        ));
147    }
148
149    #[test]
150    fn test_function_starts_with_impl() {
151        assert!(function_starts_with(
152            "_<futures..task_impl..Spawn<T>>::enter::_{{closure}}",
153            "futures::"
154        ));
155
156        assert!(!function_starts_with(
157            "_<futures..task_impl..Spawn<T>>::enter::_{{closure}}",
158            "tokio::"
159        ));
160    }
161
162    #[test]
163    fn test_function_starts_with_newimpl() {
164        assert!(function_starts_with(
165            "<futures::task_impl::Spawn<T>>::enter::{{closure}}",
166            "futures::"
167        ));
168
169        assert!(!function_starts_with(
170            "<futures::task_impl::Spawn<T>>::enter::{{closure}}",
171            "tokio::"
172        ));
173    }
174
175    #[test]
176    fn test_function_starts_with_impl_pattern() {
177        assert!(function_starts_with(
178            "_<futures..task_impl..Spawn<T>>::enter::_{{closure}}",
179            "<futures::"
180        ));
181
182        assert!(function_starts_with(
183            "<futures::task_impl::Spawn<T>>::enter::{{closure}}",
184            "<futures::"
185        ));
186
187        assert!(!function_starts_with(
188            "futures::task_impl::std::set",
189            "<futures::"
190        ));
191    }
192
193    #[test]
194    fn test_parse_crate_name() {
195        assert_eq!(
196            parse_crate_name("futures::task_impl::std::set"),
197            Some("futures".into())
198        );
199    }
200
201    #[test]
202    fn test_parse_crate_name_impl() {
203        assert_eq!(
204            parse_crate_name("_<futures..task_impl..Spawn<T>>::enter::_{{closure}}"),
205            Some("futures".into())
206        );
207    }
208
209    #[test]
210    fn test_parse_crate_name_anonymous_impl() {
211        assert_eq!(
212            parse_crate_name("_<F as alloc..boxed..FnBox<A>>::call_box"),
213            Some("alloc".into())
214        );
215    }
216
217    #[test]
218    fn strip_crate_hash() {
219        assert_eq!(
220            &strip_symbol("std::panic::catch_unwind::hd044952603e5f56c"),
221            "std::panic::catch_unwind"
222        );
223        assert_eq!(
224            &strip_symbol("std[550525b9dd91a68e]::rt::lang_start::<()>"),
225            "std::rt::lang_start::<()>"
226        );
227        assert_eq!(
228            &strip_symbol("<fn() as core[bb3d6b31f0e973c8]::ops::function::FnOnce<()>>::call_once"),
229            "<fn() as core::ops::function::FnOnce<()>>::call_once"
230        );
231        assert_eq!(&strip_symbol("<std[550525b9dd91a68e]::thread::local::LocalKey<(arc_swap[1d34a79be67db79e]::ArcSwapAny<alloc[bc7f897b574022f6]::sync::Arc<sentry_core[1d5336878cce1456]::hub::Hub>>, core[bb3d6b31f0e973c8]::cell::Cell<bool>)>>::with::<<sentry_core[1d5336878cce1456]::hub::Hub>::with<<sentry_core[1d5336878cce1456]::hub::Hub>::with_active<sentry_core[1d5336878cce1456]::api::with_integration<sentry_panic[c87c9124ff32f50e]::PanicIntegration, sentry_panic[c87c9124ff32f50e]::panic_handler::{closure#0}, ()>::{closure#0}, ()>::{closure#0}, ()>::{closure#0}, ()>"), "<std::thread::local::LocalKey<(arc_swap::ArcSwapAny<alloc::sync::Arc<sentry_core::hub::Hub>>, core::cell::Cell<bool>)>>::with::<<sentry_core::hub::Hub>::with<<sentry_core::hub::Hub>::with_active<sentry_core::api::with_integration<sentry_panic::PanicIntegration, sentry_panic::panic_handler::{closure#0}, ()>::{closure#0}, ()>::{closure#0}, ()>::{closure#0}, ()>");
232    }
233
234    #[test]
235    fn test_parse_crate_name_none() {
236        assert_eq!(parse_crate_name("main"), None);
237    }
238
239    #[test]
240    fn test_parse_crate_name_newstyle() {
241        assert_eq!(
242            parse_crate_name("<failure::error::Error as core::convert::From<F>>::from"),
243            Some("failure".into())
244        );
245    }
246
247    #[test]
248    fn test_parse_crate_name_hash() {
249        assert_eq!(
250            parse_crate_name("backtrace[856cf81bbf211f65]::backtrace::libunwind::trace"),
251            Some("backtrace".into())
252        );
253        assert_eq!(
254            parse_crate_name("<backtrace[856cf81bbf211f65]::capture::Backtrace>::new"),
255            Some("backtrace".into())
256        );
257    }
258}