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