1#![warn(missing_docs)]
35
36use std::borrow::Cow;
37#[cfg(feature = "swift")]
38use std::ffi::{CStr, CString};
39#[cfg(feature = "swift")]
40use std::os::raw::{c_char, c_int};
41
42use symbolic_common::{Language, Name, NameMangling};
43
44#[cfg(feature = "swift")]
45const SYMBOLIC_SWIFT_FEATURE_RETURN_TYPE: c_int = 0x1;
46#[cfg(feature = "swift")]
47const SYMBOLIC_SWIFT_FEATURE_PARAMETERS: c_int = 0x2;
48
49#[cfg(feature = "swift")]
50extern "C" {
51 fn symbolic_demangle_swift(
52 sym: *const c_char,
53 buf: *mut c_char,
54 buf_len: usize,
55 features: c_int,
56 ) -> c_int;
57
58 fn symbolic_demangle_is_swift_symbol(sym: *const c_char) -> c_int;
59}
60
61#[derive(Clone, Copy, Debug)]
89pub struct DemangleOptions {
90 return_type: bool,
91 parameters: bool,
92}
93
94impl DemangleOptions {
95 pub const fn complete() -> Self {
97 Self {
98 return_type: true,
99 parameters: true,
100 }
101 }
102
103 pub const fn name_only() -> Self {
105 Self {
106 return_type: false,
107 parameters: false,
108 }
109 }
110
111 pub const fn return_type(mut self, return_type: bool) -> Self {
113 self.return_type = return_type;
114 self
115 }
116
117 pub const fn parameters(mut self, parameters: bool) -> Self {
119 self.parameters = parameters;
120 self
121 }
122}
123
124fn is_maybe_objc(ident: &str) -> bool {
125 (ident.starts_with("-[") || ident.starts_with("+[")) && ident.ends_with(']')
126}
127
128fn is_maybe_cpp(ident: &str) -> bool {
129 ident.starts_with("_Z")
130 || ident.starts_with("__Z")
131 || ident.starts_with("___Z")
132 || ident.starts_with("____Z")
133}
134
135fn is_maybe_msvc(ident: &str) -> bool {
136 ident.starts_with('?') || ident.starts_with("@?")
137}
138
139fn is_maybe_md5(ident: &str) -> bool {
142 if ident.len() != 36 {
143 return false;
144 }
145
146 ident.starts_with("??@")
147 && ident.ends_with('@')
148 && ident[3..35].chars().all(|c| c.is_ascii_hexdigit())
149}
150
151#[cfg(feature = "swift")]
152fn is_maybe_swift(ident: &str) -> bool {
153 CString::new(ident)
154 .map(|cstr| unsafe { symbolic_demangle_is_swift_symbol(cstr.as_ptr()) != 0 })
155 .unwrap_or(false)
156}
157
158#[cfg(not(feature = "swift"))]
159fn is_maybe_swift(_ident: &str) -> bool {
160 false
161}
162
163#[cfg(feature = "msvc")]
164fn try_demangle_msvc(ident: &str, opts: DemangleOptions) -> Option<String> {
165 use msvc_demangler::DemangleFlags as MsvcFlags;
166
167 let mut flags = MsvcFlags::COMPLETE
169 | MsvcFlags::SPACE_AFTER_COMMA
170 | MsvcFlags::HUG_TYPE
171 | MsvcFlags::NO_MS_KEYWORDS
172 | MsvcFlags::NO_CLASS_TYPE;
173 if !opts.return_type {
174 flags |= MsvcFlags::NO_FUNCTION_RETURNS;
175 }
176 if !opts.parameters {
177 flags |= MsvcFlags::NAME_ONLY;
179 }
180
181 msvc_demangler::demangle(ident, flags).ok()
182}
183
184#[cfg(not(feature = "msvc"))]
185fn try_demangle_msvc(_ident: &str, _opts: DemangleOptions) -> Option<String> {
186 None
187}
188
189fn strip_hash_suffix(ident: &str) -> &str {
192 let len = ident.len();
193 if len >= 33 {
194 let mut char_iter = ident.char_indices();
195 while let Some((pos, c)) = char_iter.next_back() {
196 if (len - pos) == 33 && c == '$' {
197 return &ident[..pos];
201 } else if (len - pos) > 33 || !c.is_ascii_hexdigit() {
202 return ident;
205 }
206 }
207 }
208 ident
209}
210
211struct BoundedString {
212 str: String,
213 bound: usize,
214}
215
216impl BoundedString {
217 fn new(bound: usize) -> Self {
218 Self {
219 str: String::new(),
220 bound,
221 }
222 }
223
224 pub fn into_inner(self) -> String {
225 self.str
226 }
227}
228
229impl std::fmt::Write for BoundedString {
230 fn write_str(&mut self, s: &str) -> std::fmt::Result {
231 if self.str.len().saturating_add(s.len()) > self.bound {
232 return Err(std::fmt::Error);
233 }
234 self.str.write_str(s)
235 }
236}
237
238fn try_demangle_cpp(ident: &str, opts: DemangleOptions) -> Option<String> {
239 if is_maybe_msvc(ident) {
240 return try_demangle_msvc(ident, opts);
241 }
242
243 if !is_maybe_cpp(ident) {
247 return None;
248 }
249
250 #[cfg(feature = "cpp")]
251 {
252 use cpp_demangle::{DemangleOptions as CppOptions, ParseOptions, Symbol as CppSymbol};
253
254 let stripped = strip_hash_suffix(ident);
255
256 let parse_options = ParseOptions::default().recursion_limit(160); let symbol = match CppSymbol::new_with_options(stripped, &parse_options) {
258 Ok(symbol) => symbol,
259 Err(_) => return None,
260 };
261
262 let mut cpp_options = CppOptions::new().recursion_limit(192); if !opts.parameters {
264 cpp_options = cpp_options.no_params();
265 }
266 if !opts.return_type {
267 cpp_options = cpp_options.no_return_type();
268 }
269
270 let mut buf = BoundedString::new(4096);
273
274 symbol
275 .structured_demangle(&mut buf, &cpp_options)
276 .ok()
277 .map(|_| buf.into_inner())
278 }
279 #[cfg(not(feature = "cpp"))]
280 {
281 None
282 }
283}
284
285#[cfg(feature = "rust")]
286fn try_demangle_rust(ident: &str, _opts: DemangleOptions) -> Option<String> {
287 match rustc_demangle::try_demangle(ident) {
288 Ok(demangled) => Some(format!("{demangled:#}")),
289 Err(_) => None,
290 }
291}
292
293#[cfg(not(feature = "rust"))]
294fn try_demangle_rust(_ident: &str, _opts: DemangleOptions) -> Option<String> {
295 None
296}
297
298#[cfg(feature = "swift")]
299fn try_demangle_swift(ident: &str, opts: DemangleOptions) -> Option<String> {
300 let mut buf = vec![0; 4096];
301 let sym = match CString::new(ident) {
302 Ok(sym) => sym,
303 Err(_) => return None,
304 };
305
306 let mut features = 0;
307 if opts.return_type {
308 features |= SYMBOLIC_SWIFT_FEATURE_RETURN_TYPE;
309 }
310 if opts.parameters {
311 features |= SYMBOLIC_SWIFT_FEATURE_PARAMETERS;
312 }
313
314 unsafe {
315 match symbolic_demangle_swift(sym.as_ptr(), buf.as_mut_ptr(), buf.len(), features) {
316 0 => None,
317 _ => Some(CStr::from_ptr(buf.as_ptr()).to_string_lossy().to_string()),
318 }
319 }
320}
321
322#[cfg(not(feature = "swift"))]
323fn try_demangle_swift(_ident: &str, _opts: DemangleOptions) -> Option<String> {
324 None
325}
326
327fn demangle_objc(ident: &str, _opts: DemangleOptions) -> String {
328 ident.to_string()
329}
330
331fn try_demangle_objcpp(ident: &str, opts: DemangleOptions) -> Option<String> {
332 if is_maybe_objc(ident) {
333 Some(demangle_objc(ident, opts))
334 } else if is_maybe_cpp(ident) {
335 try_demangle_cpp(ident, opts)
336 } else {
337 None
338 }
339}
340
341pub trait Demangle {
347 fn detect_language(&self) -> Language;
365
366 fn demangle(&self, opts: DemangleOptions) -> Option<String>;
391
392 fn try_demangle(&self, opts: DemangleOptions) -> Cow<'_, str>;
417}
418
419impl Demangle for Name<'_> {
420 fn detect_language(&self) -> Language {
421 if self.language() != Language::Unknown {
422 return self.language();
423 }
424
425 if is_maybe_objc(self.as_str()) {
426 return Language::ObjC;
427 }
428
429 #[cfg(feature = "rust")]
430 {
431 if rustc_demangle::try_demangle(self.as_str()).is_ok() {
432 return Language::Rust;
433 }
434 }
435
436 if is_maybe_cpp(self.as_str()) || is_maybe_msvc(self.as_str()) {
437 return Language::Cpp;
438 }
439
440 if is_maybe_swift(self.as_str()) {
441 return Language::Swift;
442 }
443
444 Language::Unknown
445 }
446
447 fn demangle(&self, opts: DemangleOptions) -> Option<String> {
448 if matches!(self.mangling(), NameMangling::Unmangled) || is_maybe_md5(self.as_str()) {
449 return Some(self.to_string());
450 }
451
452 match self.detect_language() {
453 Language::ObjC => Some(demangle_objc(self.as_str(), opts)),
454 Language::ObjCpp => try_demangle_objcpp(self.as_str(), opts),
455 Language::Rust => try_demangle_rust(self.as_str(), opts),
456 Language::Cpp => try_demangle_cpp(self.as_str(), opts),
457 Language::Swift => try_demangle_swift(self.as_str(), opts),
458 _ => None,
459 }
460 }
461
462 fn try_demangle(&self, opts: DemangleOptions) -> Cow<'_, str> {
463 if matches!(self.mangling(), NameMangling::Unmangled) {
464 return Cow::Borrowed(self.as_str());
465 }
466 match self.demangle(opts) {
467 Some(demangled) => Cow::Owned(demangled),
468 None => Cow::Borrowed(self.as_str()),
469 }
470 }
471}
472
473pub fn demangle(ident: &str) -> Cow<'_, str> {
487 match Name::from(ident).demangle(DemangleOptions::complete()) {
488 Some(demangled) => Cow::Owned(demangled),
489 None => Cow::Borrowed(ident),
490 }
491}
492
493#[cfg(test)]
494mod test {
495 use super::*;
496
497 #[test]
498 fn simple_md5() {
499 let md5_mangled = "??@8ba8d245c9eca390356129098dbe9f73@";
500 assert_eq!(
501 Name::from(md5_mangled)
502 .demangle(DemangleOptions::name_only())
503 .unwrap(),
504 md5_mangled
505 );
506 }
507
508 #[test]
509 fn test_strip_hash_suffix() {
510 assert_eq!(
511 strip_hash_suffix("hello$0123456789abcdef0123456789abcdef"),
512 "hello"
513 );
514 assert_eq!(
515 strip_hash_suffix("hello_0123456789abcdef0123456789abcdef"),
516 "hello_0123456789abcdef0123456789abcdef",
517 );
518 assert_eq!(
519 strip_hash_suffix("hello\u{1000}0123456789abcdef0123456789abcdef"),
520 "hello\u{1000}0123456789abcdef0123456789abcdef"
521 );
522 assert_eq!(
523 strip_hash_suffix("hello$0123456789abcdef0123456789abcdxx"),
524 "hello$0123456789abcdef0123456789abcdxx"
525 );
526 assert_eq!(
527 strip_hash_suffix("hello$\u{1000}0123456789abcdef0123456789abcde"),
528 "hello$\u{1000}0123456789abcdef0123456789abcde"
529 );
530 }
531}