1 //! Demangle Rust compiler symbol names.
2 //!
3 //! This crate provides a `demangle` function which will return a `Demangle`
4 //! sentinel value that can be used to learn about the demangled version of a
5 //! symbol name. The demangled representation will be the same as the original
6 //! if it doesn't look like a mangled symbol name.
7 //!
8 //! `Demangle` can be formatted with the `Display` trait. The alternate
9 //! modifier (`#`) can be used to format the symbol name without the
10 //! trailing hash value.
11 //!
12 //! # Examples
13 //!
14 //! ```
15 //! use rustc_demangle::demangle;
16 //!
17 //! assert_eq!(demangle("_ZN4testE").to_string(), "test");
18 //! assert_eq!(demangle("_ZN3foo3barE").to_string(), "foo::bar");
19 //! assert_eq!(demangle("foo").to_string(), "foo");
20 //! // With hash
21 //! assert_eq!(format!("{}", demangle("_ZN3foo17h05af221e174051e9E")), "foo::h05af221e174051e9");
22 //! // Without hash
23 //! assert_eq!(format!("{:#}", demangle("_ZN3foo17h05af221e174051e9E")), "foo");
24 //! ```
25 
26 #![no_std]
27 #![deny(missing_docs)]
28 #![cfg_attr(docsrs, feature(doc_cfg))]
29 
30 #[cfg(any(test, feature = "std"))]
31 #[macro_use]
32 extern crate std;
33 
34 // HACK(eddyb) helper macros for tests.
35 #[cfg(test)]
36 macro_rules! assert_contains {
37     ($s:expr, $needle:expr) => {{
38         let (s, needle) = ($s, $needle);
39         assert!(
40             s.contains(needle),
41             "{:?} should've contained {:?}",
42             s,
43             needle
44         );
45     }};
46 }
47 #[cfg(test)]
48 macro_rules! assert_ends_with {
49     ($s:expr, $suffix:expr) => {{
50         let (s, suffix) = ($s, $suffix);
51         assert!(
52             s.ends_with(suffix),
53             "{:?} should've ended in {:?}",
54             s,
55             suffix
56         );
57     }};
58 }
59 
60 mod legacy;
61 mod v0;
62 
63 use core::fmt::{self, Write as _};
64 
65 /// Representation of a demangled symbol name.
66 pub struct Demangle<'a> {
67     style: Option<DemangleStyle<'a>>,
68     original: &'a str,
69     suffix: &'a str,
70 }
71 
72 enum DemangleStyle<'a> {
73     Legacy(legacy::Demangle<'a>),
74     V0(v0::Demangle<'a>),
75 }
76 
77 /// De-mangles a Rust symbol into a more readable version
78 ///
79 /// This function will take a **mangled** symbol and return a value. When printed,
80 /// the de-mangled version will be written. If the symbol does not look like
81 /// a mangled symbol, the original value will be written instead.
82 ///
83 /// # Examples
84 ///
85 /// ```
86 /// use rustc_demangle::demangle;
87 ///
88 /// assert_eq!(demangle("_ZN4testE").to_string(), "test");
89 /// assert_eq!(demangle("_ZN3foo3barE").to_string(), "foo::bar");
90 /// assert_eq!(demangle("foo").to_string(), "foo");
91 /// ```
demangle(mut s: &str) -> Demangle92 pub fn demangle(mut s: &str) -> Demangle {
93     // During ThinLTO LLVM may import and rename internal symbols, so strip out
94     // those endings first as they're one of the last manglings applied to symbol
95     // names.
96     let llvm = ".llvm.";
97     if let Some(i) = s.find(llvm) {
98         let candidate = &s[i + llvm.len()..];
99         let all_hex = candidate.chars().all(|c| match c {
100             'A'..='F' | '0'..='9' | '@' => true,
101             _ => false,
102         });
103 
104         if all_hex {
105             s = &s[..i];
106         }
107     }
108 
109     let mut suffix = "";
110     let mut style = match legacy::demangle(s) {
111         Ok((d, s)) => {
112             suffix = s;
113             Some(DemangleStyle::Legacy(d))
114         }
115         Err(()) => match v0::demangle(s) {
116             Ok((d, s)) => {
117                 suffix = s;
118                 Some(DemangleStyle::V0(d))
119             }
120             // FIXME(eddyb) would it make sense to treat an unknown-validity
121             // symbol (e.g. one that errored with `RecursedTooDeep`) as
122             // v0-mangled, and have the error show up in the demangling?
123             // (that error already gets past this initial check, and therefore
124             // will show up in the demangling, if hidden behind a backref)
125             Err(v0::ParseError::Invalid) | Err(v0::ParseError::RecursedTooDeep) => None,
126         },
127     };
128 
129     // Output like LLVM IR adds extra period-delimited words. See if
130     // we are in that case and save the trailing words if so.
131     if !suffix.is_empty() {
132         if suffix.starts_with('.') && is_symbol_like(suffix) {
133             // Keep the suffix.
134         } else {
135             // Reset the suffix and invalidate the demangling.
136             suffix = "";
137             style = None;
138         }
139     }
140 
141     Demangle {
142         style,
143         original: s,
144         suffix,
145     }
146 }
147 
148 #[cfg(feature = "std")]
demangle_line( line: &str, output: &mut impl std::io::Write, include_hash: bool, ) -> std::io::Result<()>149 fn demangle_line(
150     line: &str,
151     output: &mut impl std::io::Write,
152     include_hash: bool,
153 ) -> std::io::Result<()> {
154     let mut head = 0;
155     while head < line.len() {
156         // Move to the next potential match
157         let next_head = match (line[head..].find("_ZN"), line[head..].find("_R")) {
158             (Some(idx), None) | (None, Some(idx)) => head + idx,
159             (Some(idx1), Some(idx2)) => head + idx1.min(idx2),
160             (None, None) => {
161                 // No more matches...
162                 line.len()
163             }
164         };
165         output.write_all(line[head..next_head].as_bytes())?;
166         head = next_head;
167         // Find the non-matching character.
168         //
169         // If we do not find a character, then until the end of the line is the
170         // thing to demangle.
171         let match_end = line[head..]
172             .find(|ch: char| !(ch == '$' || ch == '.' || ch == '_' || ch.is_ascii_alphanumeric()))
173             .map(|idx| head + idx)
174             .unwrap_or(line.len());
175 
176         let mangled = &line[head..match_end];
177         head = head + mangled.len();
178         if let Ok(demangled) = try_demangle(mangled) {
179             if include_hash {
180                 write!(output, "{}", demangled)?;
181             } else {
182                 write!(output, "{:#}", demangled)?;
183             }
184         } else {
185             output.write_all(mangled.as_bytes())?;
186         }
187     }
188     Ok(())
189 }
190 
191 /// Process a stream of data from `input` into the provided `output`, demangling any symbols found
192 /// within.
193 ///
194 /// Note that the underlying implementation will perform many relatively small writes to the
195 /// output. If the output is expensive to write to (e.g., requires syscalls), consider using
196 /// `std::io::BufWriter`.
197 #[cfg(feature = "std")]
198 #[cfg_attr(docsrs, doc(cfg(feature = "std")))]
demangle_stream<R: std::io::BufRead, W: std::io::Write>( input: &mut R, output: &mut W, include_hash: bool, ) -> std::io::Result<()>199 pub fn demangle_stream<R: std::io::BufRead, W: std::io::Write>(
200     input: &mut R,
201     output: &mut W,
202     include_hash: bool,
203 ) -> std::io::Result<()> {
204     let mut buf = std::string::String::new();
205     // We read in lines to reduce the memory usage at any time.
206     //
207     // demangle_line is also more efficient with relatively small buffers as it will copy around
208     // trailing data during demangling. In the future we might directly stream to the output but at
209     // least right now that seems to be less efficient.
210     while input.read_line(&mut buf)? > 0 {
211         demangle_line(&buf, output, include_hash)?;
212         buf.clear();
213     }
214     Ok(())
215 }
216 
217 /// Error returned from the `try_demangle` function below when demangling fails.
218 #[derive(Debug, Clone)]
219 pub struct TryDemangleError {
220     _priv: (),
221 }
222 
223 /// The same as `demangle`, except return an `Err` if the string does not appear
224 /// to be a Rust symbol, rather than "demangling" the given string as a no-op.
225 ///
226 /// ```
227 /// extern crate rustc_demangle;
228 ///
229 /// let not_a_rust_symbol = "la la la";
230 ///
231 /// // The `try_demangle` function will reject strings which are not Rust symbols.
232 /// assert!(rustc_demangle::try_demangle(not_a_rust_symbol).is_err());
233 ///
234 /// // While `demangle` will just pass the non-symbol through as a no-op.
235 /// assert_eq!(rustc_demangle::demangle(not_a_rust_symbol).as_str(), not_a_rust_symbol);
236 /// ```
try_demangle(s: &str) -> Result<Demangle, TryDemangleError>237 pub fn try_demangle(s: &str) -> Result<Demangle, TryDemangleError> {
238     let sym = demangle(s);
239     if sym.style.is_some() {
240         Ok(sym)
241     } else {
242         Err(TryDemangleError { _priv: () })
243     }
244 }
245 
246 impl<'a> Demangle<'a> {
247     /// Returns the underlying string that's being demangled.
as_str(&self) -> &'a str248     pub fn as_str(&self) -> &'a str {
249         self.original
250     }
251 }
252 
is_symbol_like(s: &str) -> bool253 fn is_symbol_like(s: &str) -> bool {
254     s.chars().all(|c| {
255         // Once `char::is_ascii_punctuation` and `char::is_ascii_alphanumeric`
256         // have been stable for long enough, use those instead for clarity
257         is_ascii_alphanumeric(c) || is_ascii_punctuation(c)
258     })
259 }
260 
261 // Copied from the documentation of `char::is_ascii_alphanumeric`
is_ascii_alphanumeric(c: char) -> bool262 fn is_ascii_alphanumeric(c: char) -> bool {
263     match c {
264         '\u{0041}'..='\u{005A}' | '\u{0061}'..='\u{007A}' | '\u{0030}'..='\u{0039}' => true,
265         _ => false,
266     }
267 }
268 
269 // Copied from the documentation of `char::is_ascii_punctuation`
is_ascii_punctuation(c: char) -> bool270 fn is_ascii_punctuation(c: char) -> bool {
271     match c {
272         '\u{0021}'..='\u{002F}'
273         | '\u{003A}'..='\u{0040}'
274         | '\u{005B}'..='\u{0060}'
275         | '\u{007B}'..='\u{007E}' => true,
276         _ => false,
277     }
278 }
279 
280 impl<'a> fmt::Display for DemangleStyle<'a> {
fmt(&self, f: &mut fmt::Formatter) -> fmt::Result281     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
282         match *self {
283             DemangleStyle::Legacy(ref d) => fmt::Display::fmt(d, f),
284             DemangleStyle::V0(ref d) => fmt::Display::fmt(d, f),
285         }
286     }
287 }
288 
289 // Maximum size of the symbol that we'll print.
290 const MAX_SIZE: usize = 1_000_000;
291 
292 #[derive(Copy, Clone, Debug)]
293 struct SizeLimitExhausted;
294 
295 struct SizeLimitedFmtAdapter<F> {
296     remaining: Result<usize, SizeLimitExhausted>,
297     inner: F,
298 }
299 
300 impl<F: fmt::Write> fmt::Write for SizeLimitedFmtAdapter<F> {
write_str(&mut self, s: &str) -> fmt::Result301     fn write_str(&mut self, s: &str) -> fmt::Result {
302         self.remaining = self
303             .remaining
304             .and_then(|r| r.checked_sub(s.len()).ok_or(SizeLimitExhausted));
305 
306         match self.remaining {
307             Ok(_) => self.inner.write_str(s),
308             Err(SizeLimitExhausted) => Err(fmt::Error),
309         }
310     }
311 }
312 
313 impl<'a> fmt::Display for Demangle<'a> {
fmt(&self, f: &mut fmt::Formatter) -> fmt::Result314     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
315         match self.style {
316             None => f.write_str(self.original)?,
317             Some(ref d) => {
318                 let alternate = f.alternate();
319                 let mut size_limited_fmt = SizeLimitedFmtAdapter {
320                     remaining: Ok(MAX_SIZE),
321                     inner: &mut *f,
322                 };
323                 let fmt_result = if alternate {
324                     write!(size_limited_fmt, "{:#}", d)
325                 } else {
326                     write!(size_limited_fmt, "{}", d)
327                 };
328                 let size_limit_result = size_limited_fmt.remaining.map(|_| ());
329 
330                 // Translate a `fmt::Error` generated by `SizeLimitedFmtAdapter`
331                 // into an error message, instead of propagating it upwards
332                 // (which could cause panicking from inside e.g. `std::io::print`).
333                 match (fmt_result, size_limit_result) {
334                     (Err(_), Err(SizeLimitExhausted)) => f.write_str("{size limit reached}")?,
335 
336                     _ => {
337                         fmt_result?;
338                         size_limit_result
339                             .expect("`fmt::Error` from `SizeLimitedFmtAdapter` was discarded");
340                     }
341                 }
342             }
343         }
344         f.write_str(self.suffix)
345     }
346 }
347 
348 impl<'a> fmt::Debug for Demangle<'a> {
fmt(&self, f: &mut fmt::Formatter) -> fmt::Result349     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
350         fmt::Display::fmt(self, f)
351     }
352 }
353 
354 #[cfg(test)]
355 mod tests {
356     use std::prelude::v1::*;
357 
358     macro_rules! t {
359         ($a:expr, $b:expr) => {
360             assert!(ok($a, $b))
361         };
362     }
363 
364     macro_rules! t_err {
365         ($a:expr) => {
366             assert!(ok_err($a))
367         };
368     }
369 
370     macro_rules! t_nohash {
371         ($a:expr, $b:expr) => {{
372             assert_eq!(format!("{:#}", super::demangle($a)), $b);
373         }};
374     }
375 
ok(sym: &str, expected: &str) -> bool376     fn ok(sym: &str, expected: &str) -> bool {
377         match super::try_demangle(sym) {
378             Ok(s) => {
379                 if s.to_string() == expected {
380                     true
381                 } else {
382                     println!("\n{}\n!=\n{}\n", s, expected);
383                     false
384                 }
385             }
386             Err(_) => {
387                 println!("error demangling");
388                 false
389             }
390         }
391     }
392 
ok_err(sym: &str) -> bool393     fn ok_err(sym: &str) -> bool {
394         match super::try_demangle(sym) {
395             Ok(_) => {
396                 println!("succeeded in demangling");
397                 false
398             }
399             Err(_) => super::demangle(sym).to_string() == sym,
400         }
401     }
402 
403     #[test]
demangle()404     fn demangle() {
405         t_err!("test");
406         t!("_ZN4testE", "test");
407         t_err!("_ZN4test");
408         t!("_ZN4test1a2bcE", "test::a::bc");
409     }
410 
411     #[test]
demangle_dollars()412     fn demangle_dollars() {
413         t!("_ZN4$RP$E", ")");
414         t!("_ZN8$RF$testE", "&test");
415         t!("_ZN8$BP$test4foobE", "*test::foob");
416         t!("_ZN9$u20$test4foobE", " test::foob");
417         t!("_ZN35Bar$LT$$u5b$u32$u3b$$u20$4$u5d$$GT$E", "Bar<[u32; 4]>");
418     }
419 
420     #[test]
demangle_many_dollars()421     fn demangle_many_dollars() {
422         t!("_ZN13test$u20$test4foobE", "test test::foob");
423         t!("_ZN12test$BP$test4foobE", "test*test::foob");
424     }
425 
426     #[test]
demangle_osx()427     fn demangle_osx() {
428         t!(
429             "__ZN5alloc9allocator6Layout9for_value17h02a996811f781011E",
430             "alloc::allocator::Layout::for_value::h02a996811f781011"
431         );
432         t!("__ZN38_$LT$core..option..Option$LT$T$GT$$GT$6unwrap18_MSG_FILE_LINE_COL17haf7cb8d5824ee659E", "<core::option::Option<T>>::unwrap::_MSG_FILE_LINE_COL::haf7cb8d5824ee659");
433         t!("__ZN4core5slice89_$LT$impl$u20$core..iter..traits..IntoIterator$u20$for$u20$$RF$$u27$a$u20$$u5b$T$u5d$$GT$9into_iter17h450e234d27262170E", "core::slice::<impl core::iter::traits::IntoIterator for &'a [T]>::into_iter::h450e234d27262170");
434     }
435 
436     #[test]
demangle_windows()437     fn demangle_windows() {
438         t!("ZN4testE", "test");
439         t!("ZN13test$u20$test4foobE", "test test::foob");
440         t!("ZN12test$RF$test4foobE", "test&test::foob");
441     }
442 
443     #[test]
demangle_elements_beginning_with_underscore()444     fn demangle_elements_beginning_with_underscore() {
445         t!("_ZN13_$LT$test$GT$E", "<test>");
446         t!("_ZN28_$u7b$$u7b$closure$u7d$$u7d$E", "{{closure}}");
447         t!("_ZN15__STATIC_FMTSTRE", "__STATIC_FMTSTR");
448     }
449 
450     #[test]
demangle_trait_impls()451     fn demangle_trait_impls() {
452         t!(
453             "_ZN71_$LT$Test$u20$$u2b$$u20$$u27$static$u20$as$u20$foo..Bar$LT$Test$GT$$GT$3barE",
454             "<Test + 'static as foo::Bar<Test>>::bar"
455         );
456     }
457 
458     #[test]
demangle_without_hash()459     fn demangle_without_hash() {
460         let s = "_ZN3foo17h05af221e174051e9E";
461         t!(s, "foo::h05af221e174051e9");
462         t_nohash!(s, "foo");
463     }
464 
465     #[test]
demangle_without_hash_edgecases()466     fn demangle_without_hash_edgecases() {
467         // One element, no hash.
468         t_nohash!("_ZN3fooE", "foo");
469         // Two elements, no hash.
470         t_nohash!("_ZN3foo3barE", "foo::bar");
471         // Longer-than-normal hash.
472         t_nohash!("_ZN3foo20h05af221e174051e9abcE", "foo");
473         // Shorter-than-normal hash.
474         t_nohash!("_ZN3foo5h05afE", "foo");
475         // Valid hash, but not at the end.
476         t_nohash!("_ZN17h05af221e174051e93fooE", "h05af221e174051e9::foo");
477         // Not a valid hash, missing the 'h'.
478         t_nohash!("_ZN3foo16ffaf221e174051e9E", "foo::ffaf221e174051e9");
479         // Not a valid hash, has a non-hex-digit.
480         t_nohash!("_ZN3foo17hg5af221e174051e9E", "foo::hg5af221e174051e9");
481     }
482 
483     #[test]
demangle_thinlto()484     fn demangle_thinlto() {
485         // One element, no hash.
486         t!("_ZN3fooE.llvm.9D1C9369", "foo");
487         t!("_ZN3fooE.llvm.9D1C9369@@16", "foo");
488         t_nohash!(
489             "_ZN9backtrace3foo17hbb467fcdaea5d79bE.llvm.A5310EB9",
490             "backtrace::foo"
491         );
492     }
493 
494     #[test]
demangle_llvm_ir_branch_labels()495     fn demangle_llvm_ir_branch_labels() {
496         t!("_ZN4core5slice77_$LT$impl$u20$core..ops..index..IndexMut$LT$I$GT$$u20$for$u20$$u5b$T$u5d$$GT$9index_mut17haf9727c2edfbc47bE.exit.i.i", "core::slice::<impl core::ops::index::IndexMut<I> for [T]>::index_mut::haf9727c2edfbc47b.exit.i.i");
497         t_nohash!("_ZN4core5slice77_$LT$impl$u20$core..ops..index..IndexMut$LT$I$GT$$u20$for$u20$$u5b$T$u5d$$GT$9index_mut17haf9727c2edfbc47bE.exit.i.i", "core::slice::<impl core::ops::index::IndexMut<I> for [T]>::index_mut.exit.i.i");
498     }
499 
500     #[test]
demangle_ignores_suffix_that_doesnt_look_like_a_symbol()501     fn demangle_ignores_suffix_that_doesnt_look_like_a_symbol() {
502         t_err!("_ZN3fooE.llvm moocow");
503     }
504 
505     #[test]
dont_panic()506     fn dont_panic() {
507         super::demangle("_ZN2222222222222222222222EE").to_string();
508         super::demangle("_ZN5*70527e27.ll34csaғE").to_string();
509         super::demangle("_ZN5*70527a54.ll34_$b.1E").to_string();
510         super::demangle(
511             "\
512              _ZN5~saäb4e\n\
513              2734cOsbE\n\
514              5usage20h)3\0\0\0\0\0\0\07e2734cOsbE\
515              ",
516         )
517         .to_string();
518     }
519 
520     #[test]
invalid_no_chop()521     fn invalid_no_chop() {
522         t_err!("_ZNfooE");
523     }
524 
525     #[test]
handle_assoc_types()526     fn handle_assoc_types() {
527         t!("_ZN151_$LT$alloc..boxed..Box$LT$alloc..boxed..FnBox$LT$A$C$$u20$Output$u3d$R$GT$$u20$$u2b$$u20$$u27$a$GT$$u20$as$u20$core..ops..function..FnOnce$LT$A$GT$$GT$9call_once17h69e8f44b3723e1caE", "<alloc::boxed::Box<alloc::boxed::FnBox<A, Output=R> + 'a> as core::ops::function::FnOnce<A>>::call_once::h69e8f44b3723e1ca");
528     }
529 
530     #[test]
handle_bang()531     fn handle_bang() {
532         t!(
533             "_ZN88_$LT$core..result..Result$LT$$u21$$C$$u20$E$GT$$u20$as$u20$std..process..Termination$GT$6report17hfc41d0da4a40b3e8E",
534             "<core::result::Result<!, E> as std::process::Termination>::report::hfc41d0da4a40b3e8"
535         );
536     }
537 
538     #[test]
limit_recursion()539     fn limit_recursion() {
540         assert_contains!(
541             super::demangle("_RNvB_1a").to_string(),
542             "{recursion limit reached}"
543         );
544         assert_contains!(
545             super::demangle("_RMC0RB2_").to_string(),
546             "{recursion limit reached}"
547         );
548     }
549 
550     #[test]
limit_output()551     fn limit_output() {
552         assert_ends_with!(
553             super::demangle("RYFG_FGyyEvRYFF_EvRYFFEvERLB_B_B_ERLRjB_B_B_").to_string(),
554             "{size limit reached}"
555         );
556         // NOTE(eddyb) somewhat reduced version of the above, effectively
557         // `<for<...> fn()>` with a larger number of lifetimes in `...`.
558         assert_ends_with!(
559             super::demangle("_RMC0FGZZZ_Eu").to_string(),
560             "{size limit reached}"
561         );
562     }
563 
564     #[cfg(feature = "std")]
demangle_str(input: &str) -> String565     fn demangle_str(input: &str) -> String {
566         let mut output = Vec::new();
567         super::demangle_line(input, &mut output, false);
568         String::from_utf8(output).unwrap()
569     }
570 
571     #[test]
572     #[cfg(feature = "std")]
find_multiple()573     fn find_multiple() {
574         assert_eq!(
575             demangle_str("_ZN3fooE.llvm moocow _ZN3fooE.llvm"),
576             "foo.llvm moocow foo.llvm"
577         );
578     }
579 
580     #[test]
581     #[cfg(feature = "std")]
interleaved_new_legacy()582     fn interleaved_new_legacy() {
583         assert_eq!(
584             demangle_str("_ZN3fooE.llvm moocow _RNvMNtNtNtNtCs8a2262Dv4r_3mio3sys4unix8selector5epollNtB2_8Selector6select _ZN3fooE.llvm"),
585             "foo.llvm moocow <mio::sys::unix::selector::epoll::Selector>::select foo.llvm"
586         );
587     }
588 }
589