sentry_backtrace/
utils.rs1use 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
48pub 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
95pub 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}