llvm-native-core-ext 0.1.0

Extended modules for llvm-native-core: analysis passes, transforms, codegen extras, bitcode, linker, JIT, utilities. Part of the llvm-native workspace (https://crates.io/crates/llvm-native).
//! C++ Name Demangler — decodes mangled C++ symbol names.
//! Clean-room behavioral reconstruction from the Itanium C++ ABI
//! mangling specification and the Microsoft Visual C++ name decoration
//! documentation.
//!
//! Supports:
//! - Itanium C++ ABI mangling (used by GCC, Clang on Linux/macOS)
//! - MSVC name decoration (used by MSVC on Windows)
//! - Mangling detection heuristics
//!
//! @llvm_behavior: LLVM's `llvm-cxxfilt` is a drop-in replacement for
//! `c++filt` from GNU binutils. It decodes mangled C++ names into
//! human-readable form. The Itanium ABI mangling scheme uses a prefix
//! `_Z` followed by an encoded name; MSVC uses `?` prefix.

use std::collections::HashMap;

// ============================================================================
// CXXNameDemangler
// ============================================================================

/// CXXNameDemangler — decodes mangled C++ symbol names into human-readable
/// form.
pub struct CXXNameDemangler;

impl CXXNameDemangler {
    // ========================================================================
    // Main API
    // ========================================================================

    /// Demangle a C++ name, auto-detecting the mangling scheme.
    ///
    /// Returns `None` if the name doesn't appear to be mangled or if
    /// demangling fails.
    pub fn demangle(name: &str) -> Option<String> {
        if !Self::is_mangled(name) {
            return None;
        }

        if name.starts_with('?') {
            Self::demangle_msvc(name)
        } else if name.starts_with("_Z") || name.starts_with("__Z") {
            Self::demangle_itanium(name)
        } else {
            None
        }
    }

    // ========================================================================
    // Itanium C++ ABI Demangling
    // ========================================================================

    /// Demangle an Itanium-ABI mangled name (prefix `_Z`).
    ///
    /// ## Itanium mangling grammar (subset)
    ///
    /// ```text
    /// <mangled-name> ::= _Z <encoding>
    /// <encoding>     ::= <name> <bare-function-type>
    /// <name>         ::= <nested-name>
    ///                 |  <unscoped-name>
    /// <unscoped-name>::= <unqualified-name>
    /// <nested-name>  ::= N <name> E
    /// <unqualified-name> ::= <number> <identifier>
    /// <bare-function-type> ::= <type>*
    /// ```
    pub fn demangle_itanium(name: &str) -> Option<String> {
        let input = if name.starts_with("__Z") {
            &name[1..] // Strip one leading underscore.
        } else {
            name
        };

        if !input.starts_with("_Z") {
            return None;
        }

        let rest = &input[2..];
        if rest.is_empty() {
            return None;
        }

        let mut pos = 0usize;
        let bytes = rest.as_bytes();

        // Parse the <name> part.
        let demangled_name = Self::parse_itanium_name(bytes, &mut pos)?;

        // If there's more, it's the function type signature.
        let has_params = pos < bytes.len();

        Some(if has_params {
            format!("{}()", demangled_name)
        } else {
            demangled_name
        })
    }

    /// Parse an Itanium <name>, returning the demangled string.
    fn parse_itanium_name(bytes: &[u8], pos: &mut usize) -> Option<String> {
        if *pos >= bytes.len() {
            return None;
        }

        match bytes[*pos] {
            b'N' => {
                // Nested name: N <prefix> <name> E
                *pos += 1;
                let mut parts = Vec::new();

                while *pos < bytes.len() && bytes[*pos] != b'E' {
                    let part = Self::parse_itanium_name(bytes, pos)?;
                    parts.push(part);
                }

                if *pos < bytes.len() && bytes[*pos] == b'E' {
                    *pos += 1;
                }

                Some(parts.join("::"))
            }
            b'0'..=b'9' => {
                // Unqualified name: <length> <identifier>
                let mut len = 0usize;
                while *pos < bytes.len() && bytes[*pos].is_ascii_digit() {
                    len = len * 10 + (bytes[*pos] - b'0') as usize;
                    *pos += 1;
                }

                if *pos + len > bytes.len() {
                    return None;
                }

                let identifier = std::str::from_utf8(&bytes[*pos..*pos + len]).ok()?;
                *pos += len;
                Some(identifier.to_string())
            }
            b'S' => {
                // Substitution or special name.
                *pos += 1;
                if *pos < bytes.len() && bytes[*pos].is_ascii_lowercase() {
                    let short = bytes[*pos] as char;
                    *pos += 1;
                    Some(Self::itanium_substitution(short))
                } else {
                    Some("std".to_string())
                }
            }
            _ => None,
        }
    }

    /// Look up common Itanium substitutions.
    fn itanium_substitution(code: char) -> String {
        match code {
            's' => "std".to_string(),
            'a' => "std::allocator".to_string(),
            'b' => "std::basic_string".to_string(),
            't' => "std::char_traits".to_string(),
            'i' => "std::istream".to_string(),
            'o' => "std::ostream".to_string(),
            'd' => "std::iostream".to_string(),
            'c' => "std::complex".to_string(),
            _ => format!("<substitution {}>", code),
        }
    }

    // ========================================================================
    // MSVC Demangling
    // ========================================================================

    /// Demangle an MSVC-decorated name (prefix `?`).
    ///
    /// ## MSVC decoration grammar (subset)
    ///
    /// ```text
    /// <decorated-name> ::= ? <name> <name-mangling>
    /// <name>           ::= <identifier> @
    /// <name-mangling>  ::= <namespace>* <operator>? <type-encoding>
    /// ```
    pub fn demangle_msvc(name: &str) -> Option<String> {
        if !name.starts_with('?') {
            return None;
        }

        let rest = &name[1..];
        let parts: Vec<&str> = rest.split('@').collect();

        if parts.is_empty() {
            return None;
        }

        let mut demangled = Vec::new();

        for (i, part) in parts.iter().enumerate() {
            if part.is_empty() {
                continue;
            }

            // Last part or parts after certain markers are type encodings.
            if i == parts.len() - 1 && part.len() <= 4 {
                // Type encoding suffix — skip.
                break;
            }

            // Identifier or namespace.
            if !part
                .chars()
                .all(|c| c.is_uppercase() || c == '_' || c == '$')
            {
                demangled.push(part.to_string());
            }
        }

        if demangled.is_empty() {
            return None;
        }

        Some(demangled.join("::"))
    }

    // ========================================================================
    // Mangling Detection
    // ========================================================================

    /// Return `true` if the name appears to be a mangled C++ symbol.
    ///
    /// Heuristics:
    /// - Starts with `_Z` (Itanium C++ ABI)
    /// - Starts with `?` (MSVC)
    /// - Contains `@@` (MSVC import thunk)
    /// - Starts with `__Z` (macOS alternate prefix)
    pub fn is_mangled(name: &str) -> bool {
        if name.is_empty() {
            return false;
        }

        // Itanium ABI: _Z prefix
        if name.starts_with("_Z") {
            return name.len() > 2;
        }

        // macOS alternate prefix
        if name.starts_with("__Z") {
            return name.len() > 3;
        }

        // MSVC: ? prefix
        if name.starts_with('?') {
            return name.len() > 1 && name.contains('@');
        }

        // MSVC import thunk
        if name.contains("@@") {
            return true;
        }

        // Rust legacy mangling: _ZN prefix
        if name.starts_with("_ZN") {
            return true;
        }

        false
    }
}

// ============================================================================
// Tests
// ============================================================================

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_is_mangled_itanium() {
        assert!(CXXNameDemangler::is_mangled("_Z3foov"));
        assert!(CXXNameDemangler::is_mangled("_ZN3foo3barEv"));
        assert!(!CXXNameDemangler::is_mangled(""));
        assert!(!CXXNameDemangler::is_mangled("main"));
        assert!(!CXXNameDemangler::is_mangled("_Z")); // too short
    }

    #[test]
    fn test_is_mangled_msvc() {
        assert!(CXXNameDemangler::is_mangled("?foo@@YAXXZ"));
        assert!(!CXXNameDemangler::is_mangled("?"));
    }

    #[test]
    fn test_demangle_itanium_simple() {
        // _Z3foov → foo()
        let result = CXXNameDemangler::demangle_itanium("_Z3foov");
        assert_eq!(result, Some("foo()".to_string()));
    }

    #[test]
    fn test_demangle_itanium_nested() {
        // _ZN3foo3barEv → foo::bar()
        let result = CXXNameDemangler::demangle_itanium("_ZN3foo3barEv");
        assert_eq!(result, Some("foo::bar()".to_string()));
    }

    #[test]
    fn test_demangle_itanium_stdstring() {
        // _ZNSs4sizeEv → std::basic_string::size()
        let result = CXXNameDemangler::demangle_itanium("_ZNSs4sizeEv");
        assert!(result.is_some());
    }

    #[test]
    fn test_demangle_msvc_simple() {
        let result = CXXNameDemangler::demangle_msvc("?foo@@YAXXZ");
        assert!(result.is_some());
        assert!(result.unwrap().contains("foo"));
    }

    #[test]
    fn test_demangle_auto_detect() {
        // Auto-detect should route to the right demangler.
        let itanium = CXXNameDemangler::demangle("_Z3foov");
        assert!(itanium.is_some());

        let msvc = CXXNameDemangler::demangle("?foo@@YAXXZ");
        assert!(msvc.is_some());

        let not_mangled = CXXNameDemangler::demangle("main");
        assert!(not_mangled.is_none());
    }

    #[test]
    fn test_demangle_non_mangled() {
        assert_eq!(CXXNameDemangler::demangle("printf"), None);
        assert_eq!(CXXNameDemangler::demangle("malloc"), None);
        assert_eq!(CXXNameDemangler::demangle(""), None);
    }
}