Coverage Report

Created: 2025-11-16 06:37

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/rust/registry/src/index.crates.io-1949cf8c6b5b557f/symbolic-demangle-12.13.4/src/lib.rs
Line
Count
Source
1
//! Demangling support for various languages and compilers.
2
//!
3
//! Currently supported languages are:
4
//!
5
//! - C++ (GCC-style compilers and MSVC) (`features = ["cpp", "msvc"]`)
6
//! - Rust (both `legacy` and `v0`) (`features = ["rust"]`)
7
//! - Swift (up to Swift 5.3) (`features = ["swift"]`)
8
//! - ObjC (only symbol detection)
9
//!
10
//! As the demangling schemes for the languages are different, the supported demangling features are
11
//! inconsistent. For example, argument types were not encoded in legacy Rust mangling and thus not
12
//! available in demangled names.
13
//! The demangling results should not be considered stable, and may change over time as more
14
//! demangling features are added.
15
//!
16
//! This module is part of the `symbolic` crate and can be enabled via the `demangle` feature.
17
//!
18
//! # Examples
19
//!
20
//! ```rust
21
//! # #[cfg(feature = "rust")] {
22
//! use symbolic_common::{Language, Name};
23
//! use symbolic_demangle::{Demangle, DemangleOptions};
24
//!
25
//! let name = Name::from("__ZN3std2io4Read11read_to_end17hb85a0f6802e14499E");
26
//! assert_eq!(name.detect_language(), Language::Rust);
27
//! assert_eq!(
28
//!     name.try_demangle(DemangleOptions::complete()),
29
//!     "std::io::Read::read_to_end"
30
//! );
31
//! # }
32
//! ```
33
34
#![warn(missing_docs)]
35
36
use std::borrow::Cow;
37
#[cfg(feature = "swift")]
38
use std::ffi::{CStr, CString};
39
#[cfg(feature = "swift")]
40
use std::os::raw::{c_char, c_int};
41
42
use symbolic_common::{Language, Name, NameMangling};
43
44
#[cfg(feature = "swift")]
45
const SYMBOLIC_SWIFT_FEATURE_RETURN_TYPE: c_int = 0x1;
46
#[cfg(feature = "swift")]
47
const SYMBOLIC_SWIFT_FEATURE_PARAMETERS: c_int = 0x2;
48
49
#[cfg(feature = "swift")]
50
extern "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
/// Options for [`Demangle::demangle`].
62
///
63
/// One can chose from complete, or name-only demangling, and toggle specific demangling features
64
/// explicitly.
65
///
66
/// The resulting output depends very much on the language of the mangled [`Name`], and may change
67
/// over time as more fine grained demangling options and features are added. Not all options are
68
/// fully supported by each language, and not every feature is mutually exclusive on all languages.
69
///
70
/// # Examples
71
///
72
/// ```
73
/// # #[cfg(feature = "swift")] {
74
/// use symbolic_common::{Name, NameMangling, Language};
75
/// use symbolic_demangle::{Demangle, DemangleOptions};
76
///
77
/// let symbol = Name::new("$s8mangling12GenericUnionO3FooyACyxGSicAEmlF", NameMangling::Mangled, Language::Swift);
78
///
79
/// let simple = symbol.demangle(DemangleOptions::name_only()).unwrap();
80
/// assert_eq!(&simple, "GenericUnion.Foo<A>");
81
///
82
/// let full = symbol.demangle(DemangleOptions::complete()).unwrap();
83
/// assert_eq!(&full, "mangling.GenericUnion.Foo<A>(mangling.GenericUnion<A>.Type) -> (Swift.Int) -> mangling.GenericUnion<A>");
84
/// # }
85
/// ```
86
///
87
/// [`Demangle::demangle`]: trait.Demangle.html#tymethod.demangle
88
#[derive(Clone, Copy, Debug)]
89
pub struct DemangleOptions {
90
    return_type: bool,
91
    parameters: bool,
92
}
93
94
impl DemangleOptions {
95
    /// DemangleOptions that output a complete verbose demangling.
96
0
    pub const fn complete() -> Self {
97
0
        Self {
98
0
            return_type: true,
99
0
            parameters: true,
100
0
        }
101
0
    }
102
103
    /// DemangleOptions that output the most simple (likely name-only) demangling.
104
0
    pub const fn name_only() -> Self {
105
0
        Self {
106
0
            return_type: false,
107
0
            parameters: false,
108
0
        }
109
0
    }
110
111
    /// Determines whether a functions return type should be demangled.
112
0
    pub const fn return_type(mut self, return_type: bool) -> Self {
113
0
        self.return_type = return_type;
114
0
        self
115
0
    }
116
117
    /// Determines whether function argument types should be demangled.
118
0
    pub const fn parameters(mut self, parameters: bool) -> Self {
119
0
        self.parameters = parameters;
120
0
        self
121
0
    }
122
}
123
124
0
fn is_maybe_objc(ident: &str) -> bool {
125
0
    (ident.starts_with("-[") || ident.starts_with("+[")) && ident.ends_with(']')
126
0
}
127
128
0
fn is_maybe_cpp(ident: &str) -> bool {
129
0
    ident.starts_with("_Z")
130
0
        || ident.starts_with("__Z")
131
0
        || ident.starts_with("___Z")
132
0
        || ident.starts_with("____Z")
133
0
}
134
135
0
fn is_maybe_msvc(ident: &str) -> bool {
136
0
    ident.starts_with('?') || ident.starts_with("@?")
137
0
}
138
139
/// An MD5 mangled name consists of the prefix "??@", 32 hex digits,
140
/// and the suffix "@".
141
0
fn is_maybe_md5(ident: &str) -> bool {
142
0
    if ident.len() != 36 {
143
0
        return false;
144
0
    }
145
146
0
    ident.starts_with("??@")
147
0
        && ident.ends_with('@')
148
0
        && ident[3..35].chars().all(|c| c.is_ascii_hexdigit())
149
0
}
150
151
#[cfg(feature = "swift")]
152
fn 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"))]
159
0
fn is_maybe_swift(_ident: &str) -> bool {
160
0
    false
161
0
}
162
163
#[cfg(feature = "msvc")]
164
fn try_demangle_msvc(ident: &str, opts: DemangleOptions) -> Option<String> {
165
    use msvc_demangler::DemangleFlags as MsvcFlags;
166
167
    // the flags are bitflags
168
    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
        // a `NO_ARGUMENTS` flag is there in the code, but commented out
178
        flags |= MsvcFlags::NAME_ONLY;
179
    }
180
181
    msvc_demangler::demangle(ident, flags).ok()
182
}
183
184
#[cfg(not(feature = "msvc"))]
185
0
fn try_demangle_msvc(_ident: &str, _opts: DemangleOptions) -> Option<String> {
186
0
    None
187
0
}
188
189
/// Removes a suffix consisting of $ followed by 32 hex digits, if there is one,
190
/// otherwise returns its input.
191
0
fn strip_hash_suffix(ident: &str) -> &str {
192
0
    let len = ident.len();
193
0
    if len >= 33 {
194
0
        let mut char_iter = ident.char_indices();
195
0
        while let Some((pos, c)) = char_iter.next_back() {
196
0
            if (len - pos) == 33 && c == '$' {
197
                // If we have not yet returned we have a valid suffix to strip.  This is
198
                // safe because we know the current pos is on the start of the '$' char
199
                // boundary.
200
0
                return &ident[..pos];
201
0
            } else if (len - pos) > 33 || !c.is_ascii_hexdigit() {
202
                // If pos is more than 33 bytes from the end a multibyte char made us skip
203
                // pos 33, multibyte chars are not hexdigit or $ so nothing to strip.
204
0
                return ident;
205
0
            }
206
        }
207
0
    }
208
0
    ident
209
0
}
210
211
struct BoundedString {
212
    str: String,
213
    bound: usize,
214
}
215
216
impl BoundedString {
217
0
    fn new(bound: usize) -> Self {
218
0
        Self {
219
0
            str: String::new(),
220
0
            bound,
221
0
        }
222
0
    }
223
224
0
    pub fn into_inner(self) -> String {
225
0
        self.str
226
0
    }
227
}
228
229
impl std::fmt::Write for BoundedString {
230
0
    fn write_str(&mut self, s: &str) -> std::fmt::Result {
231
0
        if self.str.len().saturating_add(s.len()) > self.bound {
232
0
            return Err(std::fmt::Error);
233
0
        }
234
0
        self.str.write_str(s)
235
0
    }
236
}
237
238
0
fn try_demangle_cpp(ident: &str, opts: DemangleOptions) -> Option<String> {
239
0
    if is_maybe_msvc(ident) {
240
0
        return try_demangle_msvc(ident, opts);
241
0
    }
242
243
    // C++ *symbols* will always start with a `_Z` prefix, but `cpp_demangle` is a bit more lenient
244
    // and will also demangle bare types, turning `a` into `signed char` for example. So lets be
245
    // a bit stricter and make sure we always have a `_Z` prefix.
246
0
    if !is_maybe_cpp(ident) {
247
0
        return None;
248
0
    }
249
250
    #[cfg(feature = "cpp")]
251
    {
252
        use cpp_demangle::{DemangleOptions as CppOptions, ParseOptions, Symbol as CppSymbol};
253
254
0
        let stripped = strip_hash_suffix(ident);
255
256
0
        let parse_options = ParseOptions::default().recursion_limit(160); // default is 96
257
0
        let symbol = match CppSymbol::new_with_options(stripped, &parse_options) {
258
0
            Ok(symbol) => symbol,
259
0
            Err(_) => return None,
260
        };
261
262
0
        let mut cpp_options = CppOptions::new().recursion_limit(192); // default is 128
263
0
        if !opts.parameters {
264
0
            cpp_options = cpp_options.no_params();
265
0
        }
266
0
        if !opts.return_type {
267
0
            cpp_options = cpp_options.no_return_type();
268
0
        }
269
270
        // Bound the maximum output string, as a huge number of substitutions could potentially
271
        // lead to a "Billion laughs attack".
272
0
        let mut buf = BoundedString::new(4096);
273
274
0
        symbol
275
0
            .structured_demangle(&mut buf, &cpp_options)
276
0
            .ok()
277
0
            .map(|_| buf.into_inner())
278
    }
279
    #[cfg(not(feature = "cpp"))]
280
    {
281
        None
282
    }
283
0
}
284
285
#[cfg(feature = "rust")]
286
0
fn try_demangle_rust(ident: &str, _opts: DemangleOptions) -> Option<String> {
287
0
    match rustc_demangle::try_demangle(ident) {
288
0
        Ok(demangled) => Some(format!("{demangled:#}")),
289
0
        Err(_) => None,
290
    }
291
0
}
292
293
#[cfg(not(feature = "rust"))]
294
fn try_demangle_rust(_ident: &str, _opts: DemangleOptions) -> Option<String> {
295
    None
296
}
297
298
#[cfg(feature = "swift")]
299
fn 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"))]
323
0
fn try_demangle_swift(_ident: &str, _opts: DemangleOptions) -> Option<String> {
324
0
    None
325
0
}
326
327
0
fn demangle_objc(ident: &str, _opts: DemangleOptions) -> String {
328
0
    ident.to_string()
329
0
}
330
331
0
fn try_demangle_objcpp(ident: &str, opts: DemangleOptions) -> Option<String> {
332
0
    if is_maybe_objc(ident) {
333
0
        Some(demangle_objc(ident, opts))
334
0
    } else if is_maybe_cpp(ident) {
335
0
        try_demangle_cpp(ident, opts)
336
    } else {
337
0
        None
338
    }
339
0
}
340
341
/// An extension trait on `Name` for demangling names.
342
///
343
/// See the [module level documentation] for a list of supported languages.
344
///
345
/// [module level documentation]: index.html
346
pub trait Demangle {
347
    /// Infers the language of a mangled name.
348
    ///
349
    /// In case the symbol is not mangled or its language is unknown, the return value will be
350
    /// `Language::Unknown`. If the language of the symbol was specified explicitly, this is
351
    /// returned instead. For a list of supported languages, see the [module level documentation].
352
    ///
353
    /// # Examples
354
    ///
355
    /// ```
356
    /// use symbolic_common::{Language, Name};
357
    /// use symbolic_demangle::{Demangle, DemangleOptions};
358
    ///
359
    /// assert_eq!(Name::from("_ZN3foo3barEv").detect_language(), Language::Cpp);
360
    /// assert_eq!(Name::from("unknown").detect_language(), Language::Unknown);
361
    /// ```
362
    ///
363
    /// [module level documentation]: index.html
364
    fn detect_language(&self) -> Language;
365
366
    /// Demangles the name with the given options.
367
    ///
368
    /// Returns `None` in one of the following cases:
369
    ///  1. The language cannot be detected.
370
    ///  2. The language is not supported.
371
    ///  3. Demangling of the name failed.
372
    ///
373
    /// # Examples
374
    ///
375
    /// ```
376
    /// # #[cfg(feature = "cpp")] {
377
    /// use symbolic_common::Name;
378
    /// use symbolic_demangle::{Demangle, DemangleOptions};
379
    ///
380
    /// assert_eq!(
381
    ///     Name::from("_ZN3foo3barEv").demangle(DemangleOptions::name_only()),
382
    ///     Some("foo::bar".to_string())
383
    /// );
384
    /// assert_eq!(
385
    ///     Name::from("unknown").demangle(DemangleOptions::name_only()),
386
    ///     None
387
    /// );
388
    /// # }
389
    /// ```
390
    fn demangle(&self, opts: DemangleOptions) -> Option<String>;
391
392
    /// Tries to demangle the name and falls back to the original name.
393
    ///
394
    /// Similar to [`demangle`], except that it returns a borrowed instance of the original name if
395
    /// the name cannot be demangled.
396
    ///
397
    /// # Examples
398
    ///
399
    /// ```
400
    /// # #[cfg(feature = "cpp")] {
401
    /// use symbolic_common::Name;
402
    /// use symbolic_demangle::{Demangle, DemangleOptions};
403
    ///
404
    /// assert_eq!(
405
    ///     Name::from("_ZN3foo3barEv").try_demangle(DemangleOptions::name_only()),
406
    ///     "foo::bar"
407
    /// );
408
    /// assert_eq!(
409
    ///     Name::from("unknown").try_demangle(DemangleOptions::name_only()),
410
    ///     "unknown"
411
    /// );
412
    /// # }
413
    /// ```
414
    ///
415
    /// [`demangle`]: trait.Demangle.html#tymethod.demangle
416
    fn try_demangle(&self, opts: DemangleOptions) -> Cow<'_, str>;
417
}
418
419
impl Demangle for Name<'_> {
420
0
    fn detect_language(&self) -> Language {
421
0
        if self.language() != Language::Unknown {
422
0
            return self.language();
423
0
        }
424
425
0
        if is_maybe_objc(self.as_str()) {
426
0
            return Language::ObjC;
427
0
        }
428
429
        #[cfg(feature = "rust")]
430
        {
431
0
            if rustc_demangle::try_demangle(self.as_str()).is_ok() {
432
0
                return Language::Rust;
433
0
            }
434
        }
435
436
0
        if is_maybe_cpp(self.as_str()) || is_maybe_msvc(self.as_str()) {
437
0
            return Language::Cpp;
438
0
        }
439
440
0
        if is_maybe_swift(self.as_str()) {
441
0
            return Language::Swift;
442
0
        }
443
444
0
        Language::Unknown
445
0
    }
446
447
0
    fn demangle(&self, opts: DemangleOptions) -> Option<String> {
448
0
        if matches!(self.mangling(), NameMangling::Unmangled) || is_maybe_md5(self.as_str()) {
449
0
            return Some(self.to_string());
450
0
        }
451
452
0
        match self.detect_language() {
453
0
            Language::ObjC => Some(demangle_objc(self.as_str(), opts)),
454
0
            Language::ObjCpp => try_demangle_objcpp(self.as_str(), opts),
455
0
            Language::Rust => try_demangle_rust(self.as_str(), opts),
456
0
            Language::Cpp => try_demangle_cpp(self.as_str(), opts),
457
0
            Language::Swift => try_demangle_swift(self.as_str(), opts),
458
0
            _ => None,
459
        }
460
0
    }
461
462
0
    fn try_demangle(&self, opts: DemangleOptions) -> Cow<'_, str> {
463
0
        if matches!(self.mangling(), NameMangling::Unmangled) {
464
0
            return Cow::Borrowed(self.as_str());
465
0
        }
466
0
        match self.demangle(opts) {
467
0
            Some(demangled) => Cow::Owned(demangled),
468
0
            None => Cow::Borrowed(self.as_str()),
469
        }
470
0
    }
471
}
472
473
/// Demangles an identifier and falls back to the original symbol.
474
///
475
/// This is a shortcut for [`Demangle::try_demangle`] with complete demangling.
476
///
477
/// # Examples
478
///
479
/// ```
480
/// # #[cfg(feature = "cpp")] {
481
/// assert_eq!(symbolic_demangle::demangle("_ZN3foo3barEv"), "foo::bar()");
482
/// # }
483
/// ```
484
///
485
/// [`Demangle::try_demangle`]: trait.Demangle.html#tymethod.try_demangle
486
0
pub fn demangle(ident: &str) -> Cow<'_, str> {
487
0
    match Name::from(ident).demangle(DemangleOptions::complete()) {
488
0
        Some(demangled) => Cow::Owned(demangled),
489
0
        None => Cow::Borrowed(ident),
490
    }
491
0
}
492
493
#[cfg(test)]
494
mod 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
}