1 | use core::char; |
2 | use core::fmt; |
3 | |
4 | /// Representation of a demangled symbol name. |
5 | pub struct Demangle<'a> { |
6 | inner: &'a str, |
7 | /// The number of ::-separated elements in the original name. |
8 | elements: usize, |
9 | } |
10 | |
11 | /// De-mangles a Rust symbol into a more readable version |
12 | /// |
13 | /// All Rust symbols by default are mangled as they contain characters that |
14 | /// cannot be represented in all object files. The mangling mechanism is similar |
15 | /// to C++'s, but Rust has a few specifics to handle items like lifetimes in |
16 | /// symbols. |
17 | /// |
18 | /// This function will take a **mangled** symbol and return a value. When printed, |
19 | /// the de-mangled version will be written. If the symbol does not look like |
20 | /// a mangled symbol, the original value will be written instead. |
21 | /// |
22 | /// # Examples |
23 | /// |
24 | /// ``` |
25 | /// use rustc_demangle::demangle; |
26 | /// |
27 | /// assert_eq!(demangle("_ZN4testE" ).to_string(), "test" ); |
28 | /// assert_eq!(demangle("_ZN3foo3barE" ).to_string(), "foo::bar" ); |
29 | /// assert_eq!(demangle("foo" ).to_string(), "foo" ); |
30 | /// ``` |
31 | |
32 | // All Rust symbols are in theory lists of "::"-separated identifiers. Some |
33 | // assemblers, however, can't handle these characters in symbol names. To get |
34 | // around this, we use C++-style mangling. The mangling method is: |
35 | // |
36 | // 1. Prefix the symbol with "_ZN" |
37 | // 2. For each element of the path, emit the length plus the element |
38 | // 3. End the path with "E" |
39 | // |
40 | // For example, "_ZN4testE" => "test" and "_ZN3foo3barE" => "foo::bar". |
41 | // |
42 | // We're the ones printing our backtraces, so we can't rely on anything else to |
43 | // demangle our symbols. It's *much* nicer to look at demangled symbols, so |
44 | // this function is implemented to give us nice pretty output. |
45 | // |
46 | // Note that this demangler isn't quite as fancy as it could be. We have lots |
47 | // of other information in our symbols like hashes, version, type information, |
48 | // etc. Additionally, this doesn't handle glue symbols at all. |
49 | pub fn demangle(s: &str) -> Result<(Demangle, &str), ()> { |
50 | // First validate the symbol. If it doesn't look like anything we're |
51 | // expecting, we just print it literally. Note that we must handle non-Rust |
52 | // symbols because we could have any function in the backtrace. |
53 | let inner = if s.starts_with("_ZN" ) { |
54 | &s[3..] |
55 | } else if s.starts_with("ZN" ) { |
56 | // On Windows, dbghelp strips leading underscores, so we accept "ZN...E" |
57 | // form too. |
58 | &s[2..] |
59 | } else if s.starts_with("__ZN" ) { |
60 | // On OSX, symbols are prefixed with an extra _ |
61 | &s[4..] |
62 | } else { |
63 | return Err(()); |
64 | }; |
65 | |
66 | // only work with ascii text |
67 | if inner.bytes().any(|c| c & 0x80 != 0) { |
68 | return Err(()); |
69 | } |
70 | |
71 | let mut elements = 0; |
72 | let mut chars = inner.chars(); |
73 | let mut c = chars.next().ok_or(())?; |
74 | while c != 'E' { |
75 | // Decode an identifier element's length. |
76 | if !c.is_digit(10) { |
77 | return Err(()); |
78 | } |
79 | let mut len = 0usize; |
80 | while let Some(d) = c.to_digit(10) { |
81 | len = len |
82 | .checked_mul(10) |
83 | .and_then(|len| len.checked_add(d as usize)) |
84 | .ok_or(())?; |
85 | c = chars.next().ok_or(())?; |
86 | } |
87 | |
88 | // `c` already contains the first character of this identifier, skip it and |
89 | // all the other characters of this identifier, to reach the next element. |
90 | for _ in 0..len { |
91 | c = chars.next().ok_or(())?; |
92 | } |
93 | |
94 | elements += 1; |
95 | } |
96 | |
97 | Ok((Demangle { inner, elements }, chars.as_str())) |
98 | } |
99 | |
100 | // Rust hashes are hex digits with an `h` prepended. |
101 | fn is_rust_hash(s: &str) -> bool { |
102 | s.starts_with('h' ) && s[1..].chars().all(|c| c.is_digit(16)) |
103 | } |
104 | |
105 | impl<'a> fmt::Display for Demangle<'a> { |
106 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
107 | // Alright, let's do this. |
108 | let mut inner = self.inner; |
109 | for element in 0..self.elements { |
110 | let mut rest = inner; |
111 | while rest.chars().next().unwrap().is_digit(10) { |
112 | rest = &rest[1..]; |
113 | } |
114 | let i: usize = inner[..(inner.len() - rest.len())].parse().unwrap(); |
115 | inner = &rest[i..]; |
116 | rest = &rest[..i]; |
117 | // Skip printing the hash if alternate formatting |
118 | // was requested. |
119 | if f.alternate() && element + 1 == self.elements && is_rust_hash(&rest) { |
120 | break; |
121 | } |
122 | if element != 0 { |
123 | f.write_str("::" )?; |
124 | } |
125 | if rest.starts_with("_$" ) { |
126 | rest = &rest[1..]; |
127 | } |
128 | loop { |
129 | if rest.starts_with('.' ) { |
130 | if let Some('.' ) = rest[1..].chars().next() { |
131 | f.write_str("::" )?; |
132 | rest = &rest[2..]; |
133 | } else { |
134 | f.write_str("." )?; |
135 | rest = &rest[1..]; |
136 | } |
137 | } else if rest.starts_with('$' ) { |
138 | let (escape, after_escape) = if let Some(end) = rest[1..].find('$' ) { |
139 | (&rest[1..=end], &rest[end + 2..]) |
140 | } else { |
141 | break; |
142 | }; |
143 | |
144 | // see src/librustc_codegen_utils/symbol_names/legacy.rs for these mappings |
145 | let unescaped = match escape { |
146 | "SP" => "@" , |
147 | "BP" => "*" , |
148 | "RF" => "&" , |
149 | "LT" => "<" , |
150 | "GT" => ">" , |
151 | "LP" => "(" , |
152 | "RP" => ")" , |
153 | "C" => "," , |
154 | |
155 | _ => { |
156 | if escape.starts_with('u' ) { |
157 | let digits = &escape[1..]; |
158 | let all_lower_hex = digits.chars().all(|c| match c { |
159 | '0' ..='9' | 'a' ..='f' => true, |
160 | _ => false, |
161 | }); |
162 | let c = u32::from_str_radix(digits, 16) |
163 | .ok() |
164 | .and_then(char::from_u32); |
165 | if let (true, Some(c)) = (all_lower_hex, c) { |
166 | // FIXME(eddyb) do we need to filter out control codepoints? |
167 | if !c.is_control() { |
168 | c.fmt(f)?; |
169 | rest = after_escape; |
170 | continue; |
171 | } |
172 | } |
173 | } |
174 | break; |
175 | } |
176 | }; |
177 | f.write_str(unescaped)?; |
178 | rest = after_escape; |
179 | } else if let Some(i) = rest.find(|c| c == '$' || c == '.' ) { |
180 | f.write_str(&rest[..i])?; |
181 | rest = &rest[i..]; |
182 | } else { |
183 | break; |
184 | } |
185 | } |
186 | f.write_str(rest)?; |
187 | } |
188 | |
189 | Ok(()) |
190 | } |
191 | } |
192 | |
193 | #[cfg (test)] |
194 | mod tests { |
195 | use std::prelude::v1::*; |
196 | |
197 | macro_rules! t { |
198 | ($a:expr, $b:expr) => { |
199 | assert!(ok($a, $b)) |
200 | }; |
201 | } |
202 | |
203 | macro_rules! t_err { |
204 | ($a:expr) => { |
205 | assert!(ok_err($a)) |
206 | }; |
207 | } |
208 | |
209 | macro_rules! t_nohash { |
210 | ($a:expr, $b:expr) => {{ |
211 | assert_eq!(format!("{:#}" , ::demangle($a)), $b); |
212 | }}; |
213 | } |
214 | |
215 | fn ok(sym: &str, expected: &str) -> bool { |
216 | match ::try_demangle(sym) { |
217 | Ok(s) => { |
218 | if s.to_string() == expected { |
219 | true |
220 | } else { |
221 | println!(" \n{} \n!= \n{} \n" , s, expected); |
222 | false |
223 | } |
224 | } |
225 | Err(_) => { |
226 | println!("error demangling" ); |
227 | false |
228 | } |
229 | } |
230 | } |
231 | |
232 | fn ok_err(sym: &str) -> bool { |
233 | match ::try_demangle(sym) { |
234 | Ok(_) => { |
235 | println!("succeeded in demangling" ); |
236 | false |
237 | } |
238 | Err(_) => ::demangle(sym).to_string() == sym, |
239 | } |
240 | } |
241 | |
242 | #[test] |
243 | fn demangle() { |
244 | t_err!("test" ); |
245 | t!("_ZN4testE" , "test" ); |
246 | t_err!("_ZN4test" ); |
247 | t!("_ZN4test1a2bcE" , "test::a::bc" ); |
248 | } |
249 | |
250 | #[test] |
251 | fn demangle_dollars() { |
252 | t!("_ZN4$RP$E" , ")" ); |
253 | t!("_ZN8$RF$testE" , "&test" ); |
254 | t!("_ZN8$BP$test4foobE" , "*test::foob" ); |
255 | t!("_ZN9$u20$test4foobE" , " test::foob" ); |
256 | t!("_ZN35Bar$LT$$u5b$u32$u3b$$u20$4$u5d$$GT$E" , "Bar<[u32; 4]>" ); |
257 | } |
258 | |
259 | #[test] |
260 | fn demangle_many_dollars() { |
261 | t!("_ZN13test$u20$test4foobE" , "test test::foob" ); |
262 | t!("_ZN12test$BP$test4foobE" , "test*test::foob" ); |
263 | } |
264 | |
265 | #[test] |
266 | fn demangle_osx() { |
267 | t!( |
268 | "__ZN5alloc9allocator6Layout9for_value17h02a996811f781011E" , |
269 | "alloc::allocator::Layout::for_value::h02a996811f781011" |
270 | ); |
271 | 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" ); |
272 | 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" ); |
273 | } |
274 | |
275 | #[test] |
276 | fn demangle_windows() { |
277 | t!("ZN4testE" , "test" ); |
278 | t!("ZN13test$u20$test4foobE" , "test test::foob" ); |
279 | t!("ZN12test$RF$test4foobE" , "test&test::foob" ); |
280 | } |
281 | |
282 | #[test] |
283 | fn demangle_elements_beginning_with_underscore() { |
284 | t!("_ZN13_$LT$test$GT$E" , "<test>" ); |
285 | t!("_ZN28_$u7b$$u7b$closure$u7d$$u7d$E" , "{{closure}}" ); |
286 | t!("_ZN15__STATIC_FMTSTRE" , "__STATIC_FMTSTR" ); |
287 | } |
288 | |
289 | #[test] |
290 | fn demangle_trait_impls() { |
291 | t!( |
292 | "_ZN71_$LT$Test$u20$$u2b$$u20$$u27$static$u20$as$u20$foo..Bar$LT$Test$GT$$GT$3barE" , |
293 | "<Test + 'static as foo::Bar<Test>>::bar" |
294 | ); |
295 | } |
296 | |
297 | #[test] |
298 | fn demangle_without_hash() { |
299 | let s = "_ZN3foo17h05af221e174051e9E" ; |
300 | t!(s, "foo::h05af221e174051e9" ); |
301 | t_nohash!(s, "foo" ); |
302 | } |
303 | |
304 | #[test] |
305 | fn demangle_without_hash_edgecases() { |
306 | // One element, no hash. |
307 | t_nohash!("_ZN3fooE" , "foo" ); |
308 | // Two elements, no hash. |
309 | t_nohash!("_ZN3foo3barE" , "foo::bar" ); |
310 | // Longer-than-normal hash. |
311 | t_nohash!("_ZN3foo20h05af221e174051e9abcE" , "foo" ); |
312 | // Shorter-than-normal hash. |
313 | t_nohash!("_ZN3foo5h05afE" , "foo" ); |
314 | // Valid hash, but not at the end. |
315 | t_nohash!("_ZN17h05af221e174051e93fooE" , "h05af221e174051e9::foo" ); |
316 | // Not a valid hash, missing the 'h'. |
317 | t_nohash!("_ZN3foo16ffaf221e174051e9E" , "foo::ffaf221e174051e9" ); |
318 | // Not a valid hash, has a non-hex-digit. |
319 | t_nohash!("_ZN3foo17hg5af221e174051e9E" , "foo::hg5af221e174051e9" ); |
320 | } |
321 | |
322 | #[test] |
323 | fn demangle_thinlto() { |
324 | // One element, no hash. |
325 | t!("_ZN3fooE.llvm.9D1C9369" , "foo" ); |
326 | t!("_ZN3fooE.llvm.9D1C9369@@16" , "foo" ); |
327 | t_nohash!( |
328 | "_ZN9backtrace3foo17hbb467fcdaea5d79bE.llvm.A5310EB9" , |
329 | "backtrace::foo" |
330 | ); |
331 | } |
332 | |
333 | #[test] |
334 | fn demangle_llvm_ir_branch_labels() { |
335 | 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" ); |
336 | 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" ); |
337 | } |
338 | |
339 | #[test] |
340 | fn demangle_ignores_suffix_that_doesnt_look_like_a_symbol() { |
341 | t_err!("_ZN3fooE.llvm moocow" ); |
342 | } |
343 | |
344 | #[test] |
345 | fn dont_panic() { |
346 | ::demangle("_ZN2222222222222222222222EE" ).to_string(); |
347 | ::demangle("_ZN5*70527e27.ll34csaғE" ).to_string(); |
348 | ::demangle("_ZN5*70527a54.ll34_$b.1E" ).to_string(); |
349 | ::demangle( |
350 | "\ |
351 | _ZN5~saäb4e \n\ |
352 | 2734cOsbE \n\ |
353 | 5usage20h)3 \0\0\0\0\0\0\07e2734cOsbE\ |
354 | " , |
355 | ) |
356 | .to_string(); |
357 | } |
358 | |
359 | #[test] |
360 | fn invalid_no_chop() { |
361 | t_err!("_ZNfooE" ); |
362 | } |
363 | |
364 | #[test] |
365 | fn handle_assoc_types() { |
366 | 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" ); |
367 | } |
368 | |
369 | #[test] |
370 | fn handle_bang() { |
371 | t!( |
372 | "_ZN88_$LT$core..result..Result$LT$$u21$$C$$u20$E$GT$$u20$as$u20$std..process..Termination$GT$6report17hfc41d0da4a40b3e8E" , |
373 | "<core::result::Result<!, E> as std::process::Termination>::report::hfc41d0da4a40b3e8" |
374 | ); |
375 | } |
376 | |
377 | #[test] |
378 | fn demangle_utf8_idents() { |
379 | t_nohash!( |
380 | "_ZN11utf8_idents157_$u10e1$$u10d0$$u10ed$$u10db$$u10d4$$u10da$$u10d0$$u10d3$_$u10d2$$u10d4$$u10db$$u10e0$$u10d8$$u10d4$$u10da$$u10d8$_$u10e1$$u10d0$$u10d3$$u10d8$$u10da$$u10d8$17h21634fd5714000aaE" , |
381 | "utf8_idents::საჭმელად_გემრიელი_სადილი" |
382 | ); |
383 | } |
384 | |
385 | #[test] |
386 | fn demangle_issue_60925() { |
387 | t_nohash!( |
388 | "_ZN11issue_609253foo37Foo$LT$issue_60925..llv$u6d$..Foo$GT$3foo17h059a991a004536adE" , |
389 | "issue_60925::foo::Foo<issue_60925::llvm::Foo>::foo" |
390 | ); |
391 | } |
392 | } |
393 | |