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