Skip to content

Commit 5941ee9

Browse files
committed
Allow formatting the demangled symbol name without the trailing hash.
This changeset allows for formatting the symbol name without the trailing hash. The hash is useful for Rust's linking situation, but not particularly useful to humans, and possibly harmful in cases where we want to use function names for aggregation (like crash reporting). Since the existing code is just using the `Display` trait for string conversion, I made it support the "alternate" format modifier. I don't know if there's a more sensible way to handle this. I could instead implement a separate method on the type and have the `Display` implementation call that, if you prefer.
1 parent 38e866b commit 5941ee9

File tree

1 file changed

+59
-7
lines changed

1 file changed

+59
-7
lines changed

src/lib.rs

Lines changed: 59 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,10 @@
55
//! symbol name. The demangled representation will be the same as the original
66
//! if it doesn't look like a mangled symbol name.
77
//!
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+
//!
812
//! # Examples
913
//!
1014
//! ```
@@ -13,6 +17,10 @@
1317
//! assert_eq!(demangle("_ZN4testE").to_string(), "test");
1418
//! assert_eq!(demangle("_ZN3foo3barE").to_string(), "foo::bar");
1519
//! 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");
1624
//! ```
1725
1826
#![no_std]
@@ -29,6 +37,8 @@ pub struct Demangle<'a> {
2937
original: &'a str,
3038
inner: &'a str,
3139
valid: bool,
40+
/// The number of ::-separated elements in the original name.
41+
elements: usize,
3242
}
3343

3444
/// De-mangles a Rust symbol into a more readable version
@@ -86,6 +96,7 @@ pub fn demangle(s: &str) -> Demangle {
8696
valid = false;
8797
}
8898

99+
let mut elements = 0;
89100
if valid {
90101
let mut chars = inner.chars();
91102
while valid {
@@ -102,13 +113,16 @@ pub fn demangle(s: &str) -> Demangle {
102113
break;
103114
} else if chars.by_ref().take(i - 1).count() != i - 1 {
104115
valid = false;
116+
} else {
117+
elements += 1;
105118
}
106119
}
107120
}
108121

109122
Demangle {
110123
inner: inner,
111124
valid: valid,
125+
elements: elements,
112126
original: s,
113127
}
114128
}
@@ -120,6 +134,11 @@ impl<'a> Demangle<'a> {
120134
}
121135
}
122136

137+
// Rust hashes are hex digits with an `h` prepended.
138+
fn is_rust_hash(s: &str) -> bool {
139+
s.starts_with('h') && s[1..].chars().all(|c| c.is_digit(16))
140+
}
141+
123142
impl<'a> fmt::Display for Demangle<'a> {
124143
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
125144
// Alright, let's do this.
@@ -128,20 +147,22 @@ impl<'a> fmt::Display for Demangle<'a> {
128147
}
129148

130149
let mut inner = self.inner;
131-
let mut first = true;
132-
while !inner.is_empty() {
133-
if !first {
134-
try!(f.write_str("::"));
135-
} else {
136-
first = false;
137-
}
150+
for element in 0..self.elements {
138151
let mut rest = inner;
139152
while rest.chars().next().unwrap().is_digit(10) {
140153
rest = &rest[1..];
141154
}
142155
let i: usize = inner[..(inner.len() - rest.len())].parse().unwrap();
143156
inner = &rest[i..];
144157
rest = &rest[..i];
158+
// Skip printing the hash if alternate formatting
159+
// was requested.
160+
if f.alternate() && element+1 == self.elements && is_rust_hash(&rest) {
161+
break;
162+
}
163+
if element != 0 {
164+
try!(f.write_str("::"));
165+
}
145166
if rest.starts_with("_$") {
146167
rest = &rest[1..];
147168
}
@@ -224,6 +245,12 @@ mod tests {
224245
})
225246
}
226247

248+
macro_rules! t_nohash {
249+
($a:expr, $b:expr) => ({
250+
assert_eq!(format!("{:#}", super::demangle($a)), $b);
251+
})
252+
}
253+
227254
#[test]
228255
fn demangle() {
229256
t!("test", "test");
@@ -266,4 +293,29 @@ mod tests {
266293
t!("_ZN71_$LT$Test$u20$$u2b$$u20$$u27$static$u20$as$u20$foo..Bar$LT$Test$GT$$GT$3barE",
267294
"<Test + 'static as foo::Bar<Test>>::bar");
268295
}
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+
}
269321
}

0 commit comments

Comments
 (0)