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
49pub 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
96pub 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}