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]
32extern crate std;
33
34// HACK(eddyb) helper macros for tests.
35#[cfg(test)]
36macro_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)]
48macro_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
60mod legacy;
61mod v0;
62
63use core::fmt::{self, Write as _};
64
65/// Representation of a demangled symbol name.
66pub struct Demangle<'a> {
67 style: Option<DemangleStyle<'a>>,
68 original: &'a str,
69 suffix: &'a str,
70}
71
72enum 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/// ```
92pub 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")]
149fn 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")))]
199pub 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)]
219pub 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/// ```
237pub fn try_demangle(s: &str) -> Result<Demangle, TryDemangleError> {
238 let sym: Demangle<'_> = demangle(s);
239 if sym.style.is_some() {
240 Ok(sym)
241 } else {
242 Err(TryDemangleError { _priv: () })
243 }
244}
245
246impl<'a> Demangle<'a> {
247 /// Returns the underlying string that's being demangled.
248 pub fn as_str(&self) -> &'a str {
249 self.original
250 }
251}
252
253fn is_symbol_like(s: &str) -> bool {
254 s.chars().all(|c: char| {
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`
262fn 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`
270fn 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
280impl<'a> fmt::Display for DemangleStyle<'a> {
281 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
282 match *self {
283 DemangleStyle::Legacy(ref d: &Demangle<'_>) => fmt::Display::fmt(d, f),
284 DemangleStyle::V0(ref d: &Demangle<'_>) => fmt::Display::fmt(d, f),
285 }
286 }
287}
288
289// Maximum size of the symbol that we'll print.
290const MAX_SIZE: usize = 1_000_000;
291
292#[derive(Copy, Clone, Debug)]
293struct SizeLimitExhausted;
294
295struct SizeLimitedFmtAdapter<F> {
296 remaining: Result<usize, SizeLimitExhausted>,
297 inner: F,
298}
299
300impl<F: fmt::Write> fmt::Write for SizeLimitedFmtAdapter<F> {
301 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
313impl<'a> fmt::Display for Demangle<'a> {
314 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
348impl<'a> fmt::Debug for Demangle<'a> {
349 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
350 fmt::Display::fmt(self, f)
351 }
352}
353
354#[cfg(test)]
355mod 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
376 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
393 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]
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]
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]
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]
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]
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]
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]
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]
459 fn demangle_without_hash() {
460 let s = "_ZN3foo17h05af221e174051e9E";
461 t!(s, "foo::h05af221e174051e9");
462 t_nohash!(s, "foo");
463 }
464
465 #[test]
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]
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]
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]
501 fn demangle_ignores_suffix_that_doesnt_look_like_a_symbol() {
502 t_err!("_ZN3fooE.llvm moocow");
503 }
504
505 #[test]
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]
521 fn invalid_no_chop() {
522 t_err!("_ZNfooE");
523 }
524
525 #[test]
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]
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]
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]
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")]
565 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")]
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")]
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