sentry_backtrace/
utils.rs

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