From f31edda06e1fae0e4ad92414ff54a871135c6ff8 Mon Sep 17 00:00:00 2001 From: Luke Street Date: Wed, 13 Mar 2024 10:43:40 -0600 Subject: [PATCH] Support MW extensions & double underscore fixes Enable MW extensions (`__int128`, `__vec2x32float__`) with `--mw-extensions`. Disabled by default since these extensions use `1` and `2` as type indicators, conflicting with other demangling schemes (template args, type names). Includes extra logic to handle cases where a function's class or template arguments contain a type with a double underscore. Fixes #2 --- Cargo.toml | 2 +- src/lib.rs | 94 +++++++++++++++++++++++++++++++++++++++++++++++------ src/main.rs | 10 ++++-- 3 files changed, 92 insertions(+), 14 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 782e58a..5d3e37b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "cwdemangle" -version = "0.1.7" +version = "0.2.0" edition = "2018" authors = ["Luke Street "] license = "MIT OR Apache-2.0" diff --git a/src/lib.rs b/src/lib.rs index 7c72b45..d43b02b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,10 +3,14 @@ use std::str::FromStr; pub struct DemangleOptions { /// Replace `(void)` function parameters with `()` pub omit_empty_parameters: bool, + /// Enable Metrowerks extension types (`__int128`, `__vec2x32float__`, etc.) + /// Disabled by default since they conflict with template argument literals + /// and can't always be demangled correctly. + pub mw_extensions: bool, } impl Default for DemangleOptions { - fn default() -> Self { DemangleOptions { omit_empty_parameters: true } } + fn default() -> Self { DemangleOptions { omit_empty_parameters: true, mw_extensions: false } } } fn parse_qualifiers(mut str: &str) -> (String, String, &str) { @@ -84,11 +88,6 @@ fn demangle_template_args<'a>( fn demangle_name<'a>(str: &'a str, options: &DemangleOptions) -> Option<(String, String, &'a str)> { let (size, rest) = parse_digits(str)?; - // hack for template argument constants - if rest.is_empty() || rest.starts_with(',') { - let out = format!("{size}"); - return Some((out.clone(), out, rest)); - } if rest.len() < size { return None; } @@ -137,7 +136,34 @@ fn demangle_arg<'a>( let (mut pre, mut post, rest) = parse_qualifiers(str); result += pre.as_str(); str = rest; - if str.starts_with('Q') || str.starts_with(|c: char| c.is_ascii_digit()) { + // Disambiguate arguments starting with a number + if str.starts_with(|c: char| c.is_ascii_digit()) { + let (num, rest) = parse_digits(str)?; + // If the number is followed by a comma or the end of the string, it's a template argument + if rest.is_empty() || rest.starts_with(',') { + // ...or a Metrowerks extension type + if options.mw_extensions { + if let Some(t) = match num { + 1 => Some("__int128"), + 2 => Some("__vec2x32float__"), + _ => None, + } { + result += t; + return Some((result, post, rest)); + } + } + result += &format!("{num}"); + result += post.as_str(); + return Some((result, String::new(), rest)); + } + // Otherwise, it's (probably) the size of a type + let (_, qualified, rest) = demangle_name(str, options)?; + result += qualified.as_str(); + result += post.as_str(); + return Some((result, String::new(), rest)); + } + // Handle qualified names + if str.starts_with('Q') { let (_, qualified, rest) = demangle_qualified_name(str, options)?; result += qualified.as_str(); result += post.as_str(); @@ -207,6 +233,8 @@ fn demangle_arg<'a>( 'w' => "wchar_t", 'v' => "void", 'e' => "...", + '1' if options.mw_extensions => "__int128", + '2' if options.mw_extensions => "__vec2x32float__", '_' => return Some((result, String::new(), rest)), _ => return None, }); @@ -330,7 +358,7 @@ pub fn demangle(mut str: &str, options: &DemangleOptions) -> Option { str = &str[2..]; } { - let mut idx = str.find("__")?; + let mut idx = find_split(str, special, options)?; // Handle any trailing underscores in the function name while str.chars().nth(idx + 2) == Some('_') { idx += 1; @@ -418,6 +446,31 @@ pub fn demangle(mut str: &str, options: &DemangleOptions) -> Option { Some(fn_name) } +/// Finds the first double underscore in the string, excluding any that are part of a +/// template argument list or operator name. +fn find_split(s: &str, special: bool, options: &DemangleOptions) -> Option { + let mut start = 0; + if special && s.starts_with("op") { + let (_, _, rest) = demangle_arg(&s[2..], options)?; + start = s.len() - rest.len(); + } + let mut depth = 0; + let bytes = s.as_bytes(); + for i in start..s.len() { + match bytes[i] { + b'<' => depth += 1, + b'>' => depth -= 1, + b'_' => { + if bytes.get(i + 1).cloned() == Some(b'_') && depth == 0 { + return Some(i); + } + } + _ => {} + } + } + None +} + #[cfg(test)] mod tests { use super::*; @@ -704,19 +757,40 @@ mod tests { demangle("__ct__Q37JGadget27TLinkList<10JUTConsole,-24>8iteratorFQ37JGadget13TNodeLinkList8iterator", &options), Some("JGadget::TLinkList::iterator::iterator(JGadget::TNodeLinkList::iterator)".to_string()) ); + assert_eq!( + demangle("do_assign>8iterator>>__Q23std36__cdeque>FQ23std126__convert_iterator>8iterator>Q23std126__convert_iterator>8iterator>Q23std20forward_iterator_tag", &options), + Some("std::__cdeque>::do_assign>::iterator>>(std::__convert_iterator>::iterator>, std::__convert_iterator>::iterator>, std::forward_iterator_tag)".to_string()) + ); + assert_eq!( + demangle("__opPCQ23std15__locale_imp<1>__Q23std80_RefCountedPtr,Q23std32_Single>>CFv", &options), + Some("std::_RefCountedPtr, std::_Single>>::operator const std::__locale_imp<1>*() const".to_string()) + ); + assert_eq!( + demangle("__partition_const_ref>>>__3stdFPP12CSpaceObjectPP12CSpaceObjectRCQ23std74unary_negate>>", &options), + Some("std::__partition_const_ref>>>(CSpaceObject**, CSpaceObject**, const std::unary_negate>>&)".to_string()) + ); } #[test] fn test_demangle_options() { - let options = DemangleOptions { omit_empty_parameters: true }; + let options = DemangleOptions { omit_empty_parameters: true, mw_extensions: false }; assert_eq!( demangle("__dt__26__partial_array_destructorFv", &options), Some("__partial_array_destructor::~__partial_array_destructor()".to_string()) ); - let options = DemangleOptions { omit_empty_parameters: false }; + let options = DemangleOptions { omit_empty_parameters: false, mw_extensions: false }; assert_eq!( demangle("__dt__26__partial_array_destructorFv", &options), Some("__partial_array_destructor::~__partial_array_destructor(void)".to_string()) ); + let options = DemangleOptions { omit_empty_parameters: true, mw_extensions: true }; + assert_eq!( + demangle("__opPCQ23std15__locale_imp<1>__Q23std80_RefCountedPtr,Q23std32_Single>>CFv", &options), + Some("std::_RefCountedPtr, std::_Single>>::operator const std::__locale_imp<__int128>*() const".to_string()) + ); + assert_eq!( + demangle("fn<3,PV2>__FPC2", &options), + Some("fn<3, volatile __vec2x32float__*>(const __vec2x32float__*)".to_string()) + ); } } diff --git a/src/main.rs b/src/main.rs index 0971c8d..ff0430f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -14,13 +14,17 @@ struct Args { /// disable replacing `(void)` with `()` #[argh(switch)] keep_void: bool, + /// enable Metrowerks extensions + #[argh(switch)] + mw_extensions: bool, } fn main() -> Result<(), &'static str> { let args: Args = from_env(); - return if let Some(symbol) = - demangle(args.symbol.as_str(), &DemangleOptions { omit_empty_parameters: !args.keep_void }) - { + return if let Some(symbol) = demangle(args.symbol.as_str(), &DemangleOptions { + omit_empty_parameters: !args.keep_void, + mw_extensions: args.mw_extensions, + }) { println!("{symbol}"); Ok(()) } else {