diff --git a/src/legacy.rs b/src/legacy.rs index d55f3a1..aad2fd7 100644 --- a/src/legacy.rs +++ b/src/legacy.rs @@ -1,8 +1,10 @@ +use crate::Config; use core::char; use core::fmt; +use core::fmt::Display; /// Representation of a demangled symbol name. -pub struct Demangle<'a> { +pub(crate) struct Demangle<'a> { inner: &'a str, /// The number of ::-separated elements in the original name. elements: usize, @@ -46,7 +48,7 @@ pub struct Demangle<'a> { // Note that this demangler isn't quite as fancy as it could be. We have lots // of other information in our symbols like hashes, version, type information, // etc. Additionally, this doesn't handle glue symbols at all. -pub fn demangle(s: &str) -> Result<(Demangle, &str), ()> { +pub(crate) fn demangle(s: &str) -> Result<(Demangle, &str), ()> { // First validate the symbol. If it doesn't look like anything we're // expecting, we just print it literally. Note that we must handle non-Rust // symbols because we could have any function in the backtrace. @@ -102,8 +104,8 @@ fn is_rust_hash(s: &str) -> bool { s.starts_with('h') && s[1..].chars().all(|c| c.is_digit(16)) } -impl<'a> fmt::Display for Demangle<'a> { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { +impl<'a> Demangle<'a> { + pub(crate) fn fmt_with_config(&self, f: &mut fmt::Formatter, config: &Config) -> fmt::Result { // Alright, let's do this. let mut inner = self.inner; for element in 0..self.elements { @@ -114,9 +116,8 @@ impl<'a> fmt::Display for Demangle<'a> { let i: usize = inner[..(inner.len() - rest.len())].parse().unwrap(); inner = &rest[i..]; rest = &rest[..i]; - // Skip printing the hash if alternate formatting - // was requested. - if f.alternate() && element + 1 == self.elements && is_rust_hash(&rest) { + // Skip printing the hash if requested. + if !config.with_hash && element + 1 == self.elements && is_rust_hash(&rest) { break; } if element != 0 { @@ -208,7 +209,13 @@ mod tests { macro_rules! t_nohash { ($a:expr, $b:expr) => {{ - assert_eq!(format!("{:#}", ::demangle($a)), $b); + assert_eq!( + format!( + "{}", + crate::demangle_with_config($a, crate::Config::new().with_hash(false)) + ), + $b + ); }}; } diff --git a/src/lib.rs b/src/lib.rs index 2b8684e..589f212 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -12,7 +12,7 @@ //! # Examples //! //! ``` -//! use rustc_demangle::demangle; +//! use rustc_demangle::{demangle, Config, demangle_with_config}; //! //! assert_eq!(demangle("_ZN4testE").to_string(), "test"); //! assert_eq!(demangle("_ZN3foo3barE").to_string(), "foo::bar"); @@ -21,6 +21,8 @@ //! assert_eq!(format!("{}", demangle("_ZN3foo17h05af221e174051e9E")), "foo::h05af221e174051e9"); //! // Without hash //! assert_eq!(format!("{:#}", demangle("_ZN3foo17h05af221e174051e9E")), "foo"); +//! // Without hash by clearing Config::with_hash +//! assert_eq!(format!("{}", demangle_with_config("_ZN3foo17h05af221e174051e9E", Config::new().with_hash(false))), "foo"); //! ``` #![no_std] @@ -35,9 +37,36 @@ mod v0; use core::fmt; +/// demangle configuration +#[derive(Clone, Debug)] +pub struct Config { + with_hash: bool, + // extend with more config options in the future +} + +impl Default for Config { + fn default() -> Self { + Self { with_hash: true } + } +} + +impl Config { + /// create the default demangle configuration + pub fn new() -> Self { + Self::default() + } + /// set if disambiguating hashes should be displayed. This is the default. + /// + /// using `format!("{:#}", ...)` is the alternate method of disabling `with_hash`. + pub fn with_hash(self, with_hash: bool) -> Self { + Self { with_hash, ..self } + } +} + /// Representation of a demangled symbol name. pub struct Demangle<'a> { style: Option>, + config: Config, original: &'a str, suffix: &'a str, } @@ -62,7 +91,26 @@ enum DemangleStyle<'a> { /// assert_eq!(demangle("_ZN3foo3barE").to_string(), "foo::bar"); /// assert_eq!(demangle("foo").to_string(), "foo"); /// ``` -pub fn demangle(mut s: &str) -> Demangle { +pub fn demangle(s: &str) -> Demangle { + demangle_with_config(s, Config::new()) +} + +/// De-mangles a Rust symbol into a more readable version +/// +/// This function will take a **mangled** symbol and return a value. When printed, +/// the de-mangled version will be written. If the symbol does not look like +/// a mangled symbol, the original value will be written instead. +/// +/// # Examples +/// +/// ``` +/// use rustc_demangle::demangle; +/// +/// assert_eq!(demangle("_ZN4testE").to_string(), "test"); +/// assert_eq!(demangle("_ZN3foo3barE").to_string(), "foo::bar"); +/// assert_eq!(demangle("foo").to_string(), "foo"); +/// ``` +pub fn demangle_with_config(mut s: &str, config: Config) -> Demangle { // During ThinLTO LLVM may import and rename internal symbols, so strip out // those endings first as they're one of the last manglings applied to symbol // names. @@ -108,6 +156,7 @@ pub fn demangle(mut s: &str) -> Demangle { Demangle { style, + config, original: s, suffix, } @@ -134,7 +183,25 @@ pub struct TryDemangleError { /// assert_eq!(rustc_demangle::demangle(not_a_rust_symbol).as_str(), not_a_rust_symbol); /// ``` pub fn try_demangle(s: &str) -> Result { - let sym = demangle(s); + try_demangle_with_config(s, Config::new()) +} + +/// The same as `demangle`, except return an `Err` if the string does not appear +/// to be a Rust symbol, rather than "demangling" the given string as a no-op. +/// +/// ``` +/// extern crate rustc_demangle; +/// +/// let not_a_rust_symbol = "la la la"; +/// +/// // The `try_demangle` function will reject strings which are not Rust symbols. +/// assert!(rustc_demangle::try_demangle(not_a_rust_symbol).is_err()); +/// +/// // While `demangle` will just pass the non-symbol through as a no-op. +/// assert_eq!(rustc_demangle::demangle(not_a_rust_symbol).as_str(), not_a_rust_symbol); +/// ``` +pub fn try_demangle_with_config(s: &str, config: Config) -> Result { + let sym = demangle_with_config(s, config); if sym.style.is_some() { Ok(sym) } else { @@ -147,6 +214,10 @@ impl<'a> Demangle<'a> { pub fn as_str(&self) -> &'a str { self.original } + /// Set the demangling configuration + pub fn with_config(self, config: Config) -> Self { + Self { config, ..self } + } } fn is_symbol_like(s: &str) -> bool { @@ -178,10 +249,14 @@ fn is_ascii_punctuation(c: char) -> bool { impl<'a> fmt::Display for Demangle<'a> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let mut config = self.config.clone(); + if f.alternate() { + config = config.with_hash(false); + } match self.style { None => f.write_str(self.original)?, - Some(DemangleStyle::Legacy(ref d)) => fmt::Display::fmt(d, f)?, - Some(DemangleStyle::V0(ref d)) => fmt::Display::fmt(d, f)?, + Some(DemangleStyle::Legacy(ref d)) => d.fmt_with_config(f, &config)?, + Some(DemangleStyle::V0(ref d)) => d.fmt_with_config(f, &config)?, } f.write_str(self.suffix) } @@ -209,12 +284,24 @@ mod tests { }; } - macro_rules! t_nohash { + macro_rules! t_nohash_alt { ($a:expr, $b:expr) => {{ assert_eq!(format!("{:#}", super::demangle($a)), $b); }}; } + macro_rules! t_nohash { + ($a:expr, $b:expr) => {{ + assert_eq!( + format!( + "{}", + super::demangle_with_config($a, super::Config::new().with_hash(false)) + ), + $b + ); + }}; + } + fn ok(sym: &str, expected: &str) -> bool { match super::try_demangle(sym) { Ok(s) => { @@ -304,6 +391,13 @@ mod tests { t_nohash!(s, "foo"); } + #[test] + fn demangle_without_hash_alt() { + let s = "_ZN3foo17h05af221e174051e9E"; + t!(s, "foo::h05af221e174051e9"); + t_nohash_alt!(s, "foo"); + } + #[test] fn demangle_without_hash_edgecases() { // One element, no hash. diff --git a/src/v0.rs b/src/v0.rs index c941acd..5bfa08f 100644 --- a/src/v0.rs +++ b/src/v0.rs @@ -1,9 +1,10 @@ +use crate::Config; use core::char; use core::fmt; use core::fmt::Display; /// Representation of a demangled symbol name. -pub struct Demangle<'a> { +pub(crate) struct Demangle<'a> { inner: &'a str, } @@ -12,7 +13,7 @@ pub struct Demangle<'a> { /// This function will take a **mangled** symbol and return a value. When printed, /// the de-mangled version will be written. If the symbol does not look like /// a mangled symbol, the original value will be written instead. -pub fn demangle(s: &str) -> Result<(Demangle, &str), Invalid> { +pub(crate) fn demangle(s: &str) -> Result<(Demangle, &str), Invalid> { // First validate the symbol. If it doesn't look like anything we're // expecting, we just print it literally. Note that we must handle non-Rust // symbols because we could have any function in the backtrace. @@ -56,14 +57,15 @@ pub fn demangle(s: &str) -> Result<(Demangle, &str), Invalid> { Ok((Demangle { inner }, &parser.sym[parser.next..])) } -impl<'s> Display for Demangle<'s> { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { +impl<'s> Demangle<'s> { + pub(crate) fn fmt_with_config(&self, f: &mut fmt::Formatter, config: &Config) -> fmt::Result { let mut printer = Printer { parser: Ok(Parser { sym: self.inner, next: 0, }), out: f, + config, bound_lifetime_depth: 0, }; printer.print_path(true) @@ -71,7 +73,7 @@ impl<'s> Display for Demangle<'s> { } #[derive(PartialEq, Eq)] -pub struct Invalid; +pub(crate) struct Invalid; struct Ident<'s> { /// ASCII part of the identifier. @@ -205,8 +207,8 @@ impl<'s> Ident<'s> { } } -impl<'s> Display for Ident<'s> { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { +impl<'s> Ident<'s> { + fn fmt_with_config(&self, f: &mut fmt::Formatter, _config: &Config) -> fmt::Result { self.try_small_punycode_decode(|chars| { for &c in chars { c.fmt(f)?; @@ -550,6 +552,7 @@ impl<'s> Parser<'s> { struct Printer<'a, 'b: 'a, 's> { parser: Result, Invalid>, out: &'a mut fmt::Formatter<'b>, + config: &'a Config, bound_lifetime_depth: u32, } @@ -594,6 +597,7 @@ impl<'a, 'b, 's> Printer<'a, 'b, 's> { Printer { parser: self.parser_mut().and_then(|p| p.backref()), out: self.out, + config: self.config, bound_lifetime_depth: self.bound_lifetime_depth, } } @@ -676,8 +680,8 @@ impl<'a, 'b, 's> Printer<'a, 'b, 's> { let dis = parse!(self, disambiguator); let name = parse!(self, ident); - name.fmt(self.out)?; - if !self.out.alternate() { + name.fmt_with_config(self.out, self.config)?; + if self.config.with_hash { self.out.write_str("[")?; fmt::LowerHex::fmt(&dis, self.out)?; self.out.write_str("]")?; @@ -702,7 +706,7 @@ impl<'a, 'b, 's> Printer<'a, 'b, 's> { } if !name.ascii.is_empty() || !name.punycode.is_empty() { self.out.write_str(":")?; - name.fmt(self.out)?; + name.fmt_with_config(self.out, self.config)?; } self.out.write_str("#")?; dis.fmt(self.out)?; @@ -713,7 +717,7 @@ impl<'a, 'b, 's> Printer<'a, 'b, 's> { None => { if !name.ascii.is_empty() || !name.punycode.is_empty() { self.out.write_str("::")?; - name.fmt(self.out)?; + name.fmt_with_config(self.out, self.config)?; } } } @@ -918,7 +922,7 @@ impl<'a, 'b, 's> Printer<'a, 'b, 's> { } let name = parse!(self, ident); - name.fmt(self.out)?; + name.fmt_with_config(self.out, self.config)?; self.out.write_str(" = ")?; self.print_type()?; } @@ -949,7 +953,7 @@ impl<'a, 'b, 's> Printer<'a, 'b, 's> { self.print_const_uint()?; } - if !self.out.alternate() { + if self.config.with_hash { self.out.write_str(": ")?; self.out.write_str(ty)?; } @@ -978,7 +982,13 @@ impl<'a, 'b, 's> Printer<'a, 'b, 's> { mod tests { macro_rules! t_nohash { ($a:expr, $b:expr) => {{ - assert_eq!(format!("{:#}", ::demangle($a)), $b); + assert_eq!( + format!( + "{}", + crate::demangle_with_config($a, crate::Config::new().with_hash(false)) + ), + $b + ); }}; } macro_rules! t_nohash_type {