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, include_hash: bool) -> std::borrow::Cow<str>149 fn demangle_line(line: &str, include_hash: bool) -> std::borrow::Cow<str> {
150     let mut line = std::borrow::Cow::Borrowed(line);
151     let mut head = 0;
152     loop {
153         // Move to the next potential match
154         head = match (line[head..].find("_ZN"), line[head..].find("_R")) {
155             (Some(idx), None) | (None, Some(idx)) => head + idx,
156             (Some(idx1), Some(idx2)) => head + idx1.min(idx2),
157             (None, None) => {
158                 // No more matches, we can return our line.
159                 return line;
160             }
161         };
162         // Find the non-matching character.
163         //
164         // If we do not find a character, then until the end of the line is the
165         // thing to demangle.
166         let match_end = line[head..]
167             .find(|ch: char| !(ch == '$' || ch == '.' || ch == '_' || ch.is_ascii_alphanumeric()))
168             .map(|idx| head + idx)
169             .unwrap_or(line.len());
170 
171         let mangled = &line[head..match_end];
172         if let Ok(demangled) = try_demangle(mangled) {
173             let demangled = if include_hash {
174                 format!("{}", demangled)
175             } else {
176                 format!("{:#}", demangled)
177             };
178             line.to_mut().replace_range(head..match_end, &demangled);
179             // Start again after the replacement.
180             head = head + demangled.len();
181         } else {
182             // Skip over the full symbol. We don't try to find a partial Rust symbol in the wider
183             // matched text today.
184             head = head + mangled.len();
185         }
186     }
187 }
188 
189 /// Process a stream of data from `input` into the provided `output`, demangling any symbols found
190 /// within.
191 ///
192 /// This currently is implemented by buffering each line of input in memory, but that may be
193 /// changed in the future. Symbols never cross line boundaries so this is just an implementation
194 /// detail.
195 #[cfg(feature = "std")]
196 #[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<()>197 pub fn demangle_stream<R: std::io::BufRead, W: std::io::Write>(
198     input: &mut R,
199     output: &mut W,
200     include_hash: bool,
201 ) -> std::io::Result<()> {
202     let mut buf = std::string::String::new();
203     // We read in lines to reduce the memory usage at any time.
204     //
205     // demangle_line is also more efficient with relatively small buffers as it will copy around
206     // trailing data during demangling. In the future we might directly stream to the output but at
207     // least right now that seems to be less efficient.
208     while input.read_line(&mut buf)? > 0 {
209         let demangled_line = demangle_line(&buf, include_hash);
210         output.write_all(demangled_line.as_bytes())?;
211         buf.clear();
212     }
213     Ok(())
214 }
215 
216 /// Error returned from the `try_demangle` function below when demangling fails.
217 #[derive(Debug, Clone)]
218 pub struct TryDemangleError {
219     _priv: (),
220 }
221 
222 /// The same as `demangle`, except return an `Err` if the string does not appear
223 /// to be a Rust symbol, rather than "demangling" the given string as a no-op.
224 ///
225 /// ```
226 /// extern crate rustc_demangle;
227 ///
228 /// let not_a_rust_symbol = "la la la";
229 ///
230 /// // The `try_demangle` function will reject strings which are not Rust symbols.
231 /// assert!(rustc_demangle::try_demangle(not_a_rust_symbol).is_err());
232 ///
233 /// // While `demangle` will just pass the non-symbol through as a no-op.
234 /// assert_eq!(rustc_demangle::demangle(not_a_rust_symbol).as_str(), not_a_rust_symbol);
235 /// ```
try_demangle(s: &str) -> Result<Demangle, TryDemangleError>236 pub fn try_demangle(s: &str) -> Result<Demangle, TryDemangleError> {
237     let sym = demangle(s);
238     if sym.style.is_some() {
239         Ok(sym)
240     } else {
241         Err(TryDemangleError { _priv: () })
242     }
243 }
244 
245 impl<'a> Demangle<'a> {
246     /// Returns the underlying string that's being demangled.
as_str(&self) -> &'a str247     pub fn as_str(&self) -> &'a str {
248         self.original
249     }
250 }
251 
is_symbol_like(s: &str) -> bool252 fn is_symbol_like(s: &str) -> bool {
253     s.chars().all(|c| {
254         // Once `char::is_ascii_punctuation` and `char::is_ascii_alphanumeric`
255         // have been stable for long enough, use those instead for clarity
256         is_ascii_alphanumeric(c) || is_ascii_punctuation(c)
257     })
258 }
259 
260 // Copied from the documentation of `char::is_ascii_alphanumeric`
is_ascii_alphanumeric(c: char) -> bool261 fn is_ascii_alphanumeric(c: char) -> bool {
262     match c {
263         '\u{0041}'..='\u{005A}' | '\u{0061}'..='\u{007A}' | '\u{0030}'..='\u{0039}' => true,
264         _ => false,
265     }
266 }
267 
268 // Copied from the documentation of `char::is_ascii_punctuation`
is_ascii_punctuation(c: char) -> bool269 fn is_ascii_punctuation(c: char) -> bool {
270     match c {
271         '\u{0021}'..='\u{002F}'
272         | '\u{003A}'..='\u{0040}'
273         | '\u{005B}'..='\u{0060}'
274         | '\u{007B}'..='\u{007E}' => true,
275         _ => false,
276     }
277 }
278 
279 impl<'a> fmt::Display for DemangleStyle<'a> {
fmt(&self, f: &mut fmt::Formatter) -> fmt::Result280     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
281         match *self {
282             DemangleStyle::Legacy(ref d) => fmt::Display::fmt(d, f),
283             DemangleStyle::V0(ref d) => fmt::Display::fmt(d, f),
284         }
285     }
286 }
287 
288 // Maximum size of the symbol that we'll print.
289 const MAX_SIZE: usize = 1_000_000;
290 
291 #[derive(Copy, Clone, Debug)]
292 struct SizeLimitExhausted;
293 
294 struct SizeLimitedFmtAdapter<F> {
295     remaining: Result<usize, SizeLimitExhausted>,
296     inner: F,
297 }
298 
299 impl<F: fmt::Write> fmt::Write for SizeLimitedFmtAdapter<F> {
write_str(&mut self, s: &str) -> fmt::Result300     fn write_str(&mut self, s: &str) -> fmt::Result {
301         self.remaining = self
302             .remaining
303             .and_then(|r| r.checked_sub(s.len()).ok_or(SizeLimitExhausted));
304 
305         match self.remaining {
306             Ok(_) => self.inner.write_str(s),
307             Err(SizeLimitExhausted) => Err(fmt::Error),
308         }
309     }
310 }
311 
312 impl<'a> fmt::Display for Demangle<'a> {
fmt(&self, f: &mut fmt::Formatter) -> fmt::Result313     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
314         match self.style {
315             None => f.write_str(self.original)?,
316             Some(ref d) => {
317                 let alternate = f.alternate();
318                 let mut size_limited_fmt = SizeLimitedFmtAdapter {
319                     remaining: Ok(MAX_SIZE),
320                     inner: &mut *f,
321                 };
322                 let fmt_result = if alternate {
323                     write!(size_limited_fmt, "{:#}", d)
324                 } else {
325                     write!(size_limited_fmt, "{}", d)
326                 };
327                 let size_limit_result = size_limited_fmt.remaining.map(|_| ());
328 
329                 // Translate a `fmt::Error` generated by `SizeLimitedFmtAdapter`
330                 // into an error message, instead of propagating it upwards
331                 // (which could cause panicking from inside e.g. `std::io::print`).
332                 match (fmt_result, size_limit_result) {
333                     (Err(_), Err(SizeLimitExhausted)) => f.write_str("{size limit reached}")?,
334 
335                     _ => {
336                         fmt_result?;
337                         size_limit_result
338                             .expect("`fmt::Error` from `SizeLimitedFmtAdapter` was discarded");
339                     }
340                 }
341             }
342         }
343         f.write_str(self.suffix)
344     }
345 }
346 
347 impl<'a> fmt::Debug for Demangle<'a> {
fmt(&self, f: &mut fmt::Formatter) -> fmt::Result348     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
349         fmt::Display::fmt(self, f)
350     }
351 }
352 
353 #[cfg(test)]
354 mod tests {
355     use std::prelude::v1::*;
356 
357     macro_rules! t {
358         ($a:expr, $b:expr) => {
359             assert!(ok($a, $b))
360         };
361     }
362 
363     macro_rules! t_err {
364         ($a:expr) => {
365             assert!(ok_err($a))
366         };
367     }
368 
369     macro_rules! t_nohash {
370         ($a:expr, $b:expr) => {{
371             assert_eq!(format!("{:#}", super::demangle($a)), $b);
372         }};
373     }
374 
ok(sym: &str, expected: &str) -> bool375     fn ok(sym: &str, expected: &str) -> bool {
376         match super::try_demangle(sym) {
377             Ok(s) => {
378                 if s.to_string() == expected {
379                     true
380                 } else {
381                     println!("\n{}\n!=\n{}\n", s, expected);
382                     false
383                 }
384             }
385             Err(_) => {
386                 println!("error demangling");
387                 false
388             }
389         }
390     }
391 
ok_err(sym: &str) -> bool392     fn ok_err(sym: &str) -> bool {
393         match super::try_demangle(sym) {
394             Ok(_) => {
395                 println!("succeeded in demangling");
396                 false
397             }
398             Err(_) => super::demangle(sym).to_string() == sym,
399         }
400     }
401 
402     #[test]
demangle()403     fn demangle() {
404         t_err!("test");
405         t!("_ZN4testE", "test");
406         t_err!("_ZN4test");
407         t!("_ZN4test1a2bcE", "test::a::bc");
408     }
409 
410     #[test]
demangle_dollars()411     fn demangle_dollars() {
412         t!("_ZN4$RP$E", ")");
413         t!("_ZN8$RF$testE", "&test");
414         t!("_ZN8$BP$test4foobE", "*test::foob");
415         t!("_ZN9$u20$test4foobE", " test::foob");
416         t!("_ZN35Bar$LT$$u5b$u32$u3b$$u20$4$u5d$$GT$E", "Bar<[u32; 4]>");
417     }
418 
419     #[test]
demangle_many_dollars()420     fn demangle_many_dollars() {
421         t!("_ZN13test$u20$test4foobE", "test test::foob");
422         t!("_ZN12test$BP$test4foobE", "test*test::foob");
423     }
424 
425     #[test]
demangle_osx()426     fn demangle_osx() {
427         t!(
428             "__ZN5alloc9allocator6Layout9for_value17h02a996811f781011E",
429             "alloc::allocator::Layout::for_value::h02a996811f781011"
430         );
431         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");
432         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");
433     }
434 
435     #[test]
demangle_windows()436     fn demangle_windows() {
437         t!("ZN4testE", "test");
438         t!("ZN13test$u20$test4foobE", "test test::foob");
439         t!("ZN12test$RF$test4foobE", "test&test::foob");
440     }
441 
442     #[test]
demangle_elements_beginning_with_underscore()443     fn demangle_elements_beginning_with_underscore() {
444         t!("_ZN13_$LT$test$GT$E", "<test>");
445         t!("_ZN28_$u7b$$u7b$closure$u7d$$u7d$E", "{{closure}}");
446         t!("_ZN15__STATIC_FMTSTRE", "__STATIC_FMTSTR");
447     }
448 
449     #[test]
demangle_trait_impls()450     fn demangle_trait_impls() {
451         t!(
452             "_ZN71_$LT$Test$u20$$u2b$$u20$$u27$static$u20$as$u20$foo..Bar$LT$Test$GT$$GT$3barE",
453             "<Test + 'static as foo::Bar<Test>>::bar"
454         );
455     }
456 
457     #[test]
demangle_without_hash()458     fn demangle_without_hash() {
459         let s = "_ZN3foo17h05af221e174051e9E";
460         t!(s, "foo::h05af221e174051e9");
461         t_nohash!(s, "foo");
462     }
463 
464     #[test]
demangle_without_hash_edgecases()465     fn demangle_without_hash_edgecases() {
466         // One element, no hash.
467         t_nohash!("_ZN3fooE", "foo");
468         // Two elements, no hash.
469         t_nohash!("_ZN3foo3barE", "foo::bar");
470         // Longer-than-normal hash.
471         t_nohash!("_ZN3foo20h05af221e174051e9abcE", "foo");
472         // Shorter-than-normal hash.
473         t_nohash!("_ZN3foo5h05afE", "foo");
474         // Valid hash, but not at the end.
475         t_nohash!("_ZN17h05af221e174051e93fooE", "h05af221e174051e9::foo");
476         // Not a valid hash, missing the 'h'.
477         t_nohash!("_ZN3foo16ffaf221e174051e9E", "foo::ffaf221e174051e9");
478         // Not a valid hash, has a non-hex-digit.
479         t_nohash!("_ZN3foo17hg5af221e174051e9E", "foo::hg5af221e174051e9");
480     }
481 
482     #[test]
demangle_thinlto()483     fn demangle_thinlto() {
484         // One element, no hash.
485         t!("_ZN3fooE.llvm.9D1C9369", "foo");
486         t!("_ZN3fooE.llvm.9D1C9369@@16", "foo");
487         t_nohash!(
488             "_ZN9backtrace3foo17hbb467fcdaea5d79bE.llvm.A5310EB9",
489             "backtrace::foo"
490         );
491     }
492 
493     #[test]
demangle_llvm_ir_branch_labels()494     fn demangle_llvm_ir_branch_labels() {
495         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");
496         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");
497     }
498 
499     #[test]
demangle_ignores_suffix_that_doesnt_look_like_a_symbol()500     fn demangle_ignores_suffix_that_doesnt_look_like_a_symbol() {
501         t_err!("_ZN3fooE.llvm moocow");
502     }
503 
504     #[test]
dont_panic()505     fn dont_panic() {
506         super::demangle("_ZN2222222222222222222222EE").to_string();
507         super::demangle("_ZN5*70527e27.ll34csaғE").to_string();
508         super::demangle("_ZN5*70527a54.ll34_$b.1E").to_string();
509         super::demangle(
510             "\
511              _ZN5~saäb4e\n\
512              2734cOsbE\n\
513              5usage20h)3\0\0\0\0\0\0\07e2734cOsbE\
514              ",
515         )
516         .to_string();
517     }
518 
519     #[test]
invalid_no_chop()520     fn invalid_no_chop() {
521         t_err!("_ZNfooE");
522     }
523 
524     #[test]
handle_assoc_types()525     fn handle_assoc_types() {
526         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");
527     }
528 
529     #[test]
handle_bang()530     fn handle_bang() {
531         t!(
532             "_ZN88_$LT$core..result..Result$LT$$u21$$C$$u20$E$GT$$u20$as$u20$std..process..Termination$GT$6report17hfc41d0da4a40b3e8E",
533             "<core::result::Result<!, E> as std::process::Termination>::report::hfc41d0da4a40b3e8"
534         );
535     }
536 
537     #[test]
limit_recursion()538     fn limit_recursion() {
539         assert_contains!(
540             super::demangle("_RNvB_1a").to_string(),
541             "{recursion limit reached}"
542         );
543         assert_contains!(
544             super::demangle("_RMC0RB2_").to_string(),
545             "{recursion limit reached}"
546         );
547     }
548 
549     #[test]
limit_output()550     fn limit_output() {
551         assert_ends_with!(
552             super::demangle("RYFG_FGyyEvRYFF_EvRYFFEvERLB_B_B_ERLRjB_B_B_").to_string(),
553             "{size limit reached}"
554         );
555         // NOTE(eddyb) somewhat reduced version of the above, effectively
556         // `<for<...> fn()>` with a larger number of lifetimes in `...`.
557         assert_ends_with!(
558             super::demangle("_RMC0FGZZZ_Eu").to_string(),
559             "{size limit reached}"
560         );
561     }
562 
563     #[test]
564     #[cfg(feature = "std")]
find_multiple()565     fn find_multiple() {
566         assert_eq!(
567             super::demangle_line("_ZN3fooE.llvm moocow _ZN3fooE.llvm", false),
568             "foo.llvm moocow foo.llvm"
569         );
570     }
571 
572     #[test]
573     #[cfg(feature = "std")]
interleaved_new_legacy()574     fn interleaved_new_legacy() {
575         assert_eq!(
576             super::demangle_line("_ZN3fooE.llvm moocow _RNvMNtNtNtNtCs8a2262Dv4r_3mio3sys4unix8selector5epollNtB2_8Selector6select _ZN3fooE.llvm", false),
577             "foo.llvm moocow <mio::sys::unix::selector::epoll::Selector>::select foo.llvm"
578         );
579     }
580 }
581