Skip to content

Add MSVC Symbol Demangling Support for PE Binaries #19

Closed
1 of 1 issue completed
Closed

Description

@unclesp1d3r

Summary

Extend the SymbolDemangler to handle MSVC-mangled symbols (?-prefixed) found in Windows PE binaries. The core Rust and C++ Itanium ABI demangling is already fully implemented; this issue tracks the remaining gap for Windows-native symbols.

Current State

The SymbolDemangler in src/classification/symbols.rs already handles:

  • ✅ Rust legacy mangling (_ZN prefix) via rustc-demangle = "0.1.27"
  • ✅ Rust v0 mangling (_R prefix) via rustc-demangle
  • ✅ C++ Itanium ABI (_Z prefix) via cpp_demangle = "0.5.1"
  • ✅ Pipeline integration in classify_strings() within src/pipeline/mod.rs
  • ✅ Graceful fallback — failed demanglings are tracked and warned

The Gap

The pipeline in src/pipeline/mod.rs already detects MSVC-mangled symbols via:

let looks_mangled =
    s.text.starts_with("_Z") || s.text.starts_with("_R") || s.text.starts_with('?');

However, SymbolDemangler::is_mangled() and SymbolDemangler::demangle() have no handling for ?-prefixed MSVC symbols. The pipeline detects them but SymbolDemangler silently passes through — producing no demangled output for any Windows PE import/export symbol using MSVC mangling.

MSVC-mangled symbol example:

  • Mangled: ?printf@@YAHPEBDZZ
  • Expected demangled: int __cdecl printf(char const * const,...)

This is a high-value gap because Stringy targets PE binary analysis (src/container/pe/) and MSVC is the dominant toolchain for Windows binaries.

Proposed Solution

1. Add Dependency

Add an MSVC demangling crate to Cargo.toml, for example msvc-demangler:

msvc-demangler = "0.10"

2. Extend SymbolDemangler

// In src/classification/symbols.rs

pub fn is_mangled(&self, symbol: &str) -> bool {
    symbol.starts_with("_R")   // Rust v0
        || symbol.starts_with("_Z") // Itanium ABI (Rust legacy + C++)
        || symbol.starts_with('?')   // MSVC
}

fn try_demangle_internal(&self, symbol: &str) -> Option<String> {
    if symbol.starts_with("_R") {
        return self.try_rust_demangle(symbol);
    }
    if symbol.starts_with("_Z") {
        return self.try_rust_demangle(symbol)
            .or_else(|| self.try_cpp_demangle(symbol));
    }
    if symbol.starts_with('?') {
        return self.try_msvc_demangle(symbol);
    }
    None
}

fn try_msvc_demangle(&self, symbol: &str) -> Option<String> {
    // Use msvc-demangler crate
    let demangled = msvc_demangler::demangle(symbol, msvc_demangler::DemangleFlags::llvm()).ok()?;
    if demangled != symbol {
        Some(demangled)
    } else {
        None
    }
}

3. Tests Required

Add comprehensive tests to src/classification/symbols.rs:

  • is_mangled correctly returns true for ?-prefixed symbols
  • demangle produces correct output for common MSVC patterns (plain functions, namespaced methods, overloaded operators)
  • Fallback to original on invalid/unknown MSVC-mangled input
  • No regression on existing Rust and C++ demangling tests

Add integration tests using real PE binary fixtures (if available under tests/fixtures/).

Acceptance Criteria

  • SymbolDemangler::is_mangled() returns true for ?-prefixed symbols
  • SymbolDemangler::demangle() correctly demangled MSVC symbols into human-readable form
  • Demangled MSVC symbols receive Tag::DemangledSymbol and preserve original in original_text
  • Unit tests cover: valid MSVC symbols, invalid input fallback, operator overloads, templates
  • No regression in existing Rust/C++ demangling tests
  • cargo deny check passes with the new dependency
  • Performance overhead measured and acceptable

Requirements

Requirement ID: 4.1

Task ID

stringy-analyzer/symbol-processing

Related Work

  • src/classification/symbols.rs — extend SymbolDemangler
  • src/pipeline/mod.rs — classify_strings() already detects ? prefix; no pipeline changes needed
  • src/container/pe/ — primary consumer of MSVC-demangled symbols

Activity

  1. added this to the v0.1 milestone on Sep 30, 2025
  2. self-assigned this
    on Sep 30, 2025
  3. unclesp1d3r commented on Sep 30, 2025

    @unclesp1d3r
    MemberAuthor

    @coderabbitai Enhance this issue body with additional context and a proposed solution. Improve the issue title and assign the proper labels and other metadata.

  4. changed the title [-]Symbol Processing[/-] [+]Implement Rust Symbol Demangling Support[/+] on Sep 30, 2025
  5. coderabbitai commented on Sep 30, 2025

    @coderabbitai
    Contributor

    Tip

    For best results, initiate chat on the files or code changes.

    🧩 Analysis chain

    🏁 Script executed:

    #!/bin/bash
    # Fetch the current issue details
    gh issue view 19 --json title,body,labels,assignees,state,author,createdAt,updatedAt
    

    Length of output: 1193


    🏁 Script executed:

    #!/bin/bash
    # Check available labels in the repository to see what other labels we could apply
    gh label list --limit 100
    
    echo "---"
    echo "Checking if there are milestones"
    gh milestone list
    

    Length of output: 1041


    🏁 Script executed:

    #!/bin/bash
    # Update the issue with enhanced title and body
    
    gh issue edit 19 \
      --title "Implement Rust Symbol Demangling Support" \
      --body "## Summary
    
    Implement Rust symbol demangling functionality to decode mangled Rust symbols into human-readable format. This enhancement will improve the analyzer's ability to process and display Rust binary symbols.
    
    ## Background
    
    Rust uses name mangling to encode additional information (such as type parameters, namespaces, and trait implementations) into symbol names. Mangled Rust symbols are difficult to read and understand without proper demangling. For example:
    - Mangled: \`_ZN3std2io5stdio6_print17h0123456789abcdefE\`
    - Demangled: \`std::io::stdio::_print\`
    
    Supporting Rust symbol demangling will enable StringyMcStringFace to:
    - Display readable function and method names from Rust binaries
    - Improve string classification accuracy for Rust-compiled artifacts
    - Provide better analysis output for users working with Rust code
    
    ## Proposed Solution
    
    ### Implementation Steps
    
    1. **Add Dependencies**
       - Add \`rustc-demangle\` crate to \`Cargo.toml\`
       - Version: Use latest stable (^0.1)
    
    2. **Create Symbol Processing Module**
       - Create new file: \`src/classification/symbols.rs\`
       - Implement \`demangle_rust_symbol()\` function
       - Handle both legacy and v0 Rust mangling schemes
       - Return original string if demangling fails (graceful fallback)
    
    3. **Integration Points**
       - Integrate symbol demangling into the string classification pipeline
       - Apply demangling during binary analysis phase
       - Ensure demangling is optional/configurable
    
    4. **Error Handling**
       - Handle invalid symbol formats gracefully
       - Log demangling failures for debugging
       - Maintain performance with large symbol tables
    
    ### Code Structure
    
    \`\`\`rust
    // src/classification/symbols.rs
    use rustc_demangle::demangle;
    
    pub fn demangle_rust_symbol(mangled: &str) -> String {
        // Attempt to demangle, fallback to original on failure
        demangle(mangled).to_string()
    }
    
    pub fn is_rust_symbol(symbol: &str) -> bool {
        // Check if symbol matches Rust mangling pattern
        symbol.starts_with(\"_ZN\") || symbol.starts_with(\"_R\")
    }
    \`\`\`
    
    ## Testing Strategy
    
    Implement comprehensive unit tests covering:
    
    1. **Valid Rust Symbols**
       - Standard function names
       - Methods with type parameters
       - Trait implementations
       - Nested modules
    
    2. **Edge Cases**
       - Empty strings
       - Non-Rust mangled symbols (C++, D, etc.)
       - Partially mangled strings
       - Very long symbol names
    
    3. **Performance Tests**
       - Benchmark demangling with large symbol tables
       - Ensure acceptable performance overhead
    
    ### Example Test Cases
    
    \`\`\`rust
    #[test]
    fn test_demangle_rust_symbol() {
        assert_eq!(
            demangle_rust_symbol(\"_ZN3std2io5stdio6_print17h0123456789abcdefE\"),
            \"std::io::stdio::_print\"
        );
    }
    
    #[test]
    fn test_invalid_symbol_returns_original() {
        let invalid = \"not_a_mangled_symbol\";
        assert_eq!(demangle_rust_symbol(invalid), invalid);
    }
    \`\`\`
    
    ## Acceptance Criteria
    
    - [ ] \`rustc-demangle\` dependency added to Cargo.toml
    - [ ] \`src/classification/symbols.rs\` module created with demangling functionality
    - [ ] Unit tests achieve >90% code coverage
    - [ ] All tests pass successfully
    - [ ] Documentation added for public APIs
    - [ ] Performance impact measured and acceptable (<5% overhead)
    
    ## Requirements
    
    Requirement ID: 4.1
    
    ## Task ID
    
    stringy-analyzer/symbol-processing
    
    ## Related Work
    
    - Consider future support for other languages (C++, D, Swift)
    - Potential integration with existing classification rules
    - May need configuration option to enable/disable demangling"
    
    echo "Issue updated successfully!"
    

    Length of output: 90


    @unclesp1d3r I've successfully enhanced issue #19 with the following improvements:

    Changes Made

    ✅ Title Updated: Changed from "Symbol Processing" to "Implement Rust Symbol Demangling Support" - much more specific and descriptive

    ✅ Enhanced Issue Body with:

    • Background section explaining why Rust symbol demangling is important, with concrete examples of mangled vs. demangled symbols
    • Detailed Proposed Solution including:
      • Step-by-step implementation plan
      • Specific dependency versions
      • Code structure examples
      • Integration points and error handling considerations
    • Comprehensive Testing Strategy covering valid symbols, edge cases, and performance tests with example test code
    • Clear Acceptance Criteria with checkboxes for tracking progress
    • Related Work section for future considerations

    ✅ Metadata Preserved:

    • Kept all existing labels (area:analyzer, type:enhancement, status:backlog, lang:rust, needs:tests) - they're already well-chosen
    • Maintained assignment to you
    • Preserved requirement 4.1 and task ID references

    The issue now provides clear context for why this feature is needed, how it should be implemented, and what success looks like. Anyone picking up this task will have all the information they need to get started! 🎯

  6. unclesp1d3r commented on Mar 16, 2026

    @unclesp1d3r
    MemberAuthor

    @coderabbitai Enhance this issue body with additional context and a proposed solution. Improve the issue title and assign the proper labels and other metadata.

  7. changed the title [-]Implement Rust Symbol Demangling Support[/-] [+]Add MSVC Symbol Demangling Support for PE Binaries[/+] on Mar 16, 2026
  8. coderabbitai commented on Mar 16, 2026

    @coderabbitai
    Contributor
    🧩 Analysis chain

    🏁 Script executed:

    #!/bin/bash
    # Fetch current issue details
    gh issue view 19 --json title,body,labels,assignees,state,author,milestone,comments
    
    echo "=== Available Labels ==="
    gh label list --limit 100
    
    echo "=== Milestones ==="
    gh milestone list
    
    echo "=== Repo Info ==="
    gh repo view --json name,description,defaultBranchRef
    

    Length of output: 20723


    🏁 Script executed:

    #!/bin/bash
    # Inspect existing Cargo.toml and classification code for context
    fd -e toml -e rs --max-depth 4 | head -40
    
    echo "=== Cargo.toml ==="
    cat Cargo.toml 2>/dev/null || fd Cargo.toml --exec cat {}
    
    echo "=== Classification module ==="
    fd -t f -e rs . src/ 2>/dev/null | xargs ls -la 2>/dev/null
    tree src/ 2>/dev/null || find src/ -type f -name "*.rs" | head -30
    

    Length of output: 9755


    🏁 Script executed:

    #!/bin/bash
    echo "=== src/classification/symbols.rs ==="
    cat src/classification/symbols.rs
    
    echo ""
    echo "=== src/classification/mod.rs ==="
    cat src/classification/mod.rs
    

    Length of output: 28533


    🏁 Script executed:

    #!/bin/bash
    echo "=== src/pipeline/mod.rs ==="
    cat src/pipeline/mod.rs
    
    echo ""
    echo "=== src/pipeline/normalizer.rs ==="
    cat src/pipeline/normalizer.rs
    
    echo ""
    # Search for SymbolDemangler usage across codebase
    echo "=== SymbolDemangler usage ==="
    rg -n "SymbolDemangler\|demangle" --type=rust -C2
    

    Length of output: 22528


    🏁 Script executed:

    #!/bin/bash
    # Update issue title, body with accurate context, and fix labels/story points
    gh issue edit 19 \
      --title "Add MSVC Symbol Demangling Support for PE Binaries" \
      --body "## Summary
    
    Extend the \`SymbolDemangler\` to handle MSVC-mangled symbols (\`?\`-prefixed) found in Windows PE binaries. The core Rust and C++ Itanium ABI demangling is already fully implemented; this issue tracks the remaining gap for Windows-native symbols.
    
    ## Current State
    
    The \`SymbolDemangler\` in \`src/classification/symbols.rs\` already handles:
    
    - ✅ **Rust legacy mangling** (\`_ZN\` prefix) via \`rustc-demangle = \"0.1.27\"\`
    - ✅ **Rust v0 mangling** (\`_R\` prefix) via \`rustc-demangle\`
    - ✅ **C++ Itanium ABI** (\`_Z\` prefix) via \`cpp_demangle = \"0.5.1\"\`
    - ✅ **Pipeline integration** in \`classify_strings()\` within \`src/pipeline/mod.rs\`
    - ✅ **Graceful fallback** — failed demanglings are tracked and warned
    
    ## The Gap
    
    The pipeline in \`src/pipeline/mod.rs\` already detects MSVC-mangled symbols via:
    
    \`\`\`rust
    let looks_mangled =
        s.text.starts_with(\"_Z\") || s.text.starts_with(\"_R\") || s.text.starts_with('?');
    \`\`\`
    
    However, \`SymbolDemangler::is_mangled()\` and \`SymbolDemangler::demangle()\` have **no handling for \`?\`-prefixed MSVC symbols**. The pipeline detects them but \`SymbolDemangler\` silently passes through — producing no demangled output for any Windows PE import/export symbol using MSVC mangling.
    
    MSVC-mangled symbol example:
    - Mangled: \`?printf@@YAHPEBDZZ\`
    - Expected demangled: \`int __cdecl printf(char const * const,...)\`
    
    This is a high-value gap because Stringy targets PE binary analysis (\`src/container/pe/\`) and MSVC is the dominant toolchain for Windows binaries.
    
    ## Proposed Solution
    
    ### 1. Add Dependency
    
    Add an MSVC demangling crate to \`Cargo.toml\`, for example \`msvc-demangler\`:
    
    \`\`\`toml
    msvc-demangler = \"0.10\"
    \`\`\`
    
    ### 2. Extend \`SymbolDemangler\`
    
    \`\`\`rust
    // In src/classification/symbols.rs
    
    pub fn is_mangled(&self, symbol: &str) -> bool {
        symbol.starts_with(\"_R\")   // Rust v0
            || symbol.starts_with(\"_Z\") // Itanium ABI (Rust legacy + C++)
            || symbol.starts_with('?')   // MSVC
    }
    
    fn try_demangle_internal(&self, symbol: &str) -> Option<String> {
        if symbol.starts_with(\"_R\") {
            return self.try_rust_demangle(symbol);
        }
        if symbol.starts_with(\"_Z\") {
            return self.try_rust_demangle(symbol)
                .or_else(|| self.try_cpp_demangle(symbol));
        }
        if symbol.starts_with('?') {
            return self.try_msvc_demangle(symbol);
        }
        None
    }
    
    fn try_msvc_demangle(&self, symbol: &str) -> Option<String> {
        // Use msvc-demangler crate
        let demangled = msvc_demangler::demangle(symbol, msvc_demangler::DemangleFlags::llvm()).ok()?;
        if demangled != symbol {
            Some(demangled)
        } else {
            None
        }
    }
    \`\`\`
    
    ### 3. Tests Required
    
    Add comprehensive tests to \`src/classification/symbols.rs\`:
    
    - \`is_mangled\` correctly returns \`true\` for \`?\`-prefixed symbols
    - \`demangle\` produces correct output for common MSVC patterns (plain functions, namespaced methods, overloaded operators)
    - Fallback to original on invalid/unknown MSVC-mangled input
    - No regression on existing Rust and C++ demangling tests
    
    Add integration tests using real PE binary fixtures (if available under \`tests/fixtures/\`).
    
    ## Acceptance Criteria
    
    - [ ] \`SymbolDemangler::is_mangled()\` returns \`true\` for \`?\`-prefixed symbols
    - [ ] \`SymbolDemangler::demangle()\` correctly demangled MSVC symbols into human-readable form
    - [ ] Demangled MSVC symbols receive \`Tag::DemangledSymbol\` and preserve original in \`original_text\`
    - [ ] Unit tests cover: valid MSVC symbols, invalid input fallback, operator overloads, templates
    - [ ] No regression in existing Rust/C++ demangling tests
    - [ ] \`cargo deny check\` passes with the new dependency
    - [ ] Performance overhead measured and acceptable
    
    ## Requirements
    
    Requirement ID: 4.1
    
    ## Task ID
    
    stringy-analyzer/symbol-processing
    
    ## Related Work
    
    - \`src/classification/symbols.rs\` — extend \`SymbolDemangler\`
    - \`src/pipeline/mod.rs\` — \`classify_strings()\` already detects \`?\` prefix; no pipeline changes needed
    - \`src/container/pe/\` — primary consumer of MSVC-demangled symbols"
    
    echo "=== Updating labels ==="
    gh issue edit 19 \
      --add-label "priority:medium" \
      --remove-label "story-points: 2" \
      --add-label "story-points: 3"
    
    echo "Issue fully updated!"
    gh issue view 19 --json title,labels,milestone,assignees | jq '{title, milestone: .milestone.title, assignees: [.assignees[].login], labels: [.labels[].name]}'
    

    Length of output: 442


    @unclesp1d3r — done. Here's a summary of what changed and why, based on a full read of the codebase:


    What Changed

    Title: "Add MSVC Symbol Demangling Support for PE Binaries"

    The previous title ("Implement Rust Symbol Demangling Support") described work that was already complete. Reading the actual code revealed the real gap.

    Body — completely rewritten around the actual gap:

    The previous bot response proposed adding rustc-demangle to Cargo.toml and creating src/classification/symbols.rs — both already exist and are fully implemented. The real issue is:

    • src/pipeline/mod.rs → classify_strings() already checks s.text.starts_with('?') as a mangled-symbol heuristic (MSVC mangling)
    • SymbolDemangler::is_mangled() and try_demangle_internal() in src/classification/symbols.rs have no ?-prefix handling
    • MSVC symbols from PE binaries are silently passed through with no demangling output

    The new body documents what's done (Rust + C++ Itanium ABI), pinpoints the exact pipeline/demangler mismatch, and proposes a concrete fix using an MSVC demangling crate.

    Labels updated:

    Change Reason
    ➕ priority:medium Missing from metadata; PE analysis is core to Stringy
    🔄 story-points: 2 → story-points: 3 Bounded but non-trivial: new dependency, extend SymbolDemangler, tests
    ✅ All other labels preserved Already accurate (area:analyzer, lang:rust, needs:tests, etc.)

    Milestone (v0.1) and assignee (unclesp1d3r) preserved as-is.

  9. traycerai commented on Mar 16, 2026

    @traycerai

    Plan

    Observations

    • src/classification/symbols.rs has SymbolDemangler with is_mangled() and try_demangle_internal() — both handle only _R and _Z prefixes; ?-prefixed MSVC symbols are absent.
    • src/pipeline/mod.rs's classify_strings() already detects ?-prefixed symbols in looks_mangled but SymbolDemangler silently ignores them — no pipeline changes are needed.
    • Cargo.toml has rustc-demangle = "0.1.27" and cpp_demangle = "0.5.1" but no MSVC demangler.
    • deny.toml allows MIT but not NCSA/University of Illinois. The msvc-demangler crate is MIT + NCSA dual-licensed, so NCSA must be added to the [licenses] allow list.
    • deny.toml has multiple-versions = "deny" — the new dependency must not introduce duplicate transitive versions.

    Approach

    Add msvc-demangler = "0.11" as a dependency, allow its NCSA license in deny.toml, then extend SymbolDemangler with a try_msvc_demangle() private method and wire it into is_mangled() and try_demangle_internal(). This is a minimal, self-contained change that mirrors the existing Rust/C++ demangling pattern exactly.


    Implementation Steps

    1. Add the dependency — file:Cargo.toml

    In the [dependencies] section, add:

    msvc-demangler = "0.11"
    

    Place it alphabetically between mmap-guard and patharg to match the existing ordering style.

    2. Allow the NCSA license — file:deny.toml

    The msvc-demangler crate is MIT + NCSA dual-licensed. NCSA (University of Illinois Open Source License) is not currently in the [licenses] allow list. Add "NCSA" to that list so cargo deny check continues to pass.

    3. Extend SymbolDemangler — file:src/classification/symbols.rs

    a. Add the import

    At the top of the file, alongside the existing use cpp_demangle::Symbol as CppSymbol;, add the msvc_demangler import.

    b. Update is_mangled()

    After the existing _Z check, add a branch that returns true when the symbol starts with '?'. Update the doc-comment to list MSVC as a third supported format and add a doc-example asserting is_mangled("?printf@@YAHPEBDZZ") returns true.

    c. Update try_demangle_internal()

    After the existing _Z branch, add a branch: if the symbol starts with '?', delegate to self.try_msvc_demangle(symbol). Update the doc-comment accordingly.

    d. Add try_msvc_demangle() private method

    Following the pattern of try_rust_demangle() and try_cpp_demangle(), add:

    fn try_msvc_demangle(&self, symbol: &str) -> Option<String>
    

    Inside, call msvc_demangler::demangle(symbol, msvc_demangler::DemangleFlags::llvm()), unwrap with .ok()?, and return Some(demangled_str) only when the result differs from the input symbol — matching the guard used in the other two helpers.

    e. Update module-level doc-comment

    Add **MSVC mangling**: Symbols starting with ? (used by MSVC on Windows) to the "Supported Symbol Formats" list in the file header.

    f. Update try_demangle() public method doc-comment

    Add an MSVC example to the # Examples block, e.g. try_demangle("?printf@@YAHPEBDZZ").

    4. Add unit tests — file:src/classification/symbols.rs

    Inside the existing #[cfg(test)] block, add the following test functions following the established create_test_string helper pattern:

    Test name What it verifies
    test_is_mangled_msvc_symbols is_mangled returns true for ?-prefixed symbols like ?printf@@YAHPEBDZZ, ??0MyClass@@QEAA@XZ (constructor), ??HMyClass@@QEAAHH@Z (operator+)
    test_is_mangled_msvc_not_triggered_for_plain is_mangled still returns false for printf, CreateFileW, ""
    test_demangle_msvc_plain_function demangle() on ?printf@@YAHPEBDZZ sets original_text, adds Tag::DemangledSymbol, and produces a result containing printf
    test_demangle_msvc_constructor demangle() on a constructor-mangled symbol (e.g. ??0MyClass@@QEAA@XZ) produces a demangled result
    test_demangle_msvc_operator demangle() on an operator-overload symbol produces a demangled result
    test_demangle_msvc_invalid_fallback demangle() on ?notvalid (not a real MSVC mangled name) leaves FoundString unchanged
    test_try_demangle_msvc_success try_demangle("?printf@@YAHPEBDZZ") returns Some(_)
    test_try_demangle_msvc_failure try_demangle("?") returns None
    test_msvc_symbol_preserves_existing_tags Existing tags (e.g. Tag::Import) are preserved after MSVC demangling

    These mirror the structure of the existing C++ tests (test_is_mangled_cpp_symbols, test_demangle_cpp_symbol, etc.) for consistency.


    sequenceDiagram
        participant Pipeline as classify_strings()
        participant SD as SymbolDemangler
        participant RD as rustc_demangle
        participant CD as cpp_demangle
        participant MD as msvc_demangler
    
        Pipeline->>SD: demangle(&mut found_string)
        SD->>SD: is_mangled(text)?
        alt starts_with("_R")
            SD->>RD: try_rust_demangle()
        else starts_with("_Z")
            SD->>RD: try_rust_demangle()
            RD-->>SD: None
            SD->>CD: try_cpp_demangle()
        else starts_with("?")  [NEW]
            SD->>MD: try_msvc_demangle()
            MD-->>SD: Some(demangled)
        end
        SD-->>Pipeline: sets text, original_text, Tag::DemangledSymbol
    
    Loading

    Import In IDE

    VSCode Icon Cursor Icon Windsurf Icon

    🤖 Prompt for AI Agents
    ## Observations
    
    - `src/classification/symbols.rs` has `SymbolDemangler` with `is_mangled()` and `try_demangle_internal()` — both handle only `_R` and `_Z` prefixes; `?`-prefixed MSVC symbols are absent.
    - `src/pipeline/mod.rs`'s `classify_strings()` already detects `?`-prefixed symbols in `looks_mangled` but `SymbolDemangler` silently ignores them — **no pipeline changes are needed**.
    - `Cargo.toml` has `rustc-demangle = "0.1.27"` and `cpp_demangle = "0.5.1"` but no MSVC demangler.
    - `deny.toml` allows MIT but **not NCSA/University of Illinois**. The `msvc-demangler` crate is MIT + NCSA dual-licensed, so `NCSA` must be added to the `[licenses] allow` list.
    - `deny.toml` has `multiple-versions = "deny"` — the new dependency must not introduce duplicate transitive versions.
    
    ## Approach
    
    Add `msvc-demangler = "0.11"` as a dependency, allow its NCSA license in `deny.toml`, then extend `SymbolDemangler` with a `try_msvc_demangle()` private method and wire it into `is_mangled()` and `try_demangle_internal()`. This is a minimal, self-contained change that mirrors the existing Rust/C++ demangling pattern exactly.
    
    ---
    
    ## Implementation Steps
    
    ### 1. Add the dependency — `file:Cargo.toml`
    
    In the `[dependencies]` section, add:
    
    ```
    msvc-demangler = "0.11"
    ```
    
    Place it alphabetically between `mmap-guard` and `patharg` to match the existing ordering style.
    
    ### 2. Allow the NCSA license — `file:deny.toml`
    
    The `msvc-demangler` crate is MIT + NCSA dual-licensed. NCSA (University of Illinois Open Source License) is not currently in the `[licenses] allow` list. Add `"NCSA"` to that list so `cargo deny check` continues to pass.
    
    ### 3. Extend `SymbolDemangler` — `file:src/classification/symbols.rs`
    
    **a. Add the import**
    
    At the top of the file, alongside the existing `use cpp_demangle::Symbol as CppSymbol;`, add the `msvc_demangler` import.
    
    **b. Update `is_mangled()`**
    
    After the existing `_Z` check, add a branch that returns `true` when the symbol starts with `'?'`. Update the doc-comment to list MSVC as a third supported format and add a doc-example asserting `is_mangled("?printf@@YAHPEBDZZ")` returns `true`.
    
    **c. Update `try_demangle_internal()`**
    
    After the existing `_Z` branch, add a branch: if the symbol starts with `'?'`, delegate to `self.try_msvc_demangle(symbol)`. Update the doc-comment accordingly.
    
    **d. Add `try_msvc_demangle()` private method**
    
    Following the pattern of `try_rust_demangle()` and `try_cpp_demangle()`, add:
    
    ```
    fn try_msvc_demangle(&self, symbol: &str) -> Option<String>
    ```
    
    Inside, call `msvc_demangler::demangle(symbol, msvc_demangler::DemangleFlags::llvm())`, unwrap with `.ok()?`, and return `Some(demangled_str)` only when the result differs from the input symbol — matching the guard used in the other two helpers.
    
    **e. Update module-level doc-comment**
    
    Add `**MSVC mangling**: Symbols starting with `?` (used by MSVC on Windows)` to the "Supported Symbol Formats" list in the file header.
    
    **f. Update `try_demangle()` public method doc-comment**
    
    Add an MSVC example to the `# Examples` block, e.g. `try_demangle("?printf@@YAHPEBDZZ")`.
    
    ### 4. Add unit tests — `file:src/classification/symbols.rs`
    
    Inside the existing `#[cfg(test)]` block, add the following test functions following the established `create_test_string` helper pattern:
    
    | Test name | What it verifies |
    |---|---|
    | `test_is_mangled_msvc_symbols` | `is_mangled` returns `true` for `?`-prefixed symbols like `?printf@@YAHPEBDZZ`, `??0MyClass@@QEAA@XZ` (constructor), `??HMyClass@@QEAAHH@Z` (operator+) |
    | `test_is_mangled_msvc_not_triggered_for_plain` | `is_mangled` still returns `false` for `printf`, `CreateFileW`, `""` |
    | `test_demangle_msvc_plain_function` | `demangle()` on `?printf@@YAHPEBDZZ` sets `original_text`, adds `Tag::DemangledSymbol`, and produces a result containing `printf` |
    | `test_demangle_msvc_constructor` | `demangle()` on a constructor-mangled symbol (e.g. `??0MyClass@@QEAA@XZ`) produces a demangled result |
    | `test_demangle_msvc_operator` | `demangle()` on an operator-overload symbol produces a demangled result |
    | `test_demangle_msvc_invalid_fallback` | `demangle()` on `?notvalid` (not a real MSVC mangled name) leaves `FoundString` unchanged |
    | `test_try_demangle_msvc_success` | `try_demangle("?printf@@YAHPEBDZZ")` returns `Some(_)` |
    | `test_try_demangle_msvc_failure` | `try_demangle("?")` returns `None` |
    | `test_msvc_symbol_preserves_existing_tags` | Existing tags (e.g. `Tag::Import`) are preserved after MSVC demangling |
    
    These mirror the structure of the existing C++ tests (`test_is_mangled_cpp_symbols`, `test_demangle_cpp_symbol`, etc.) for consistency.
    
    ---
    
    ```mermaid
    sequenceDiagram
        participant Pipeline as classify_strings()
        participant SD as SymbolDemangler
        participant RD as rustc_demangle
        participant CD as cpp_demangle
        participant MD as msvc_demangler
    
        Pipeline->>SD: demangle(&mut found_string)
        SD->>SD: is_mangled(text)?
        alt starts_with("_R")
            SD->>RD: try_rust_demangle()
        else starts_with("_Z")
            SD->>RD: try_rust_demangle()
            RD-->>SD: None
            SD->>CD: try_cpp_demangle()
        else starts_with("?")  [NEW]
            SD->>MD: try_msvc_demangle()
            MD-->>SD: Some(demangled)
        end
        SD-->>Pipeline: sets text, original_text, Tag::DemangledSymbol
    ```

    Execution Information

    Branch: main
    Commit: cde9ad3


    💡 Tips

    Supported Commands (Inside Comments)

    • Use @traycerai generate to iterate on the previous version of the implementation plan.

    Supported Commands (Inside Description)

    • Add @traycerai ignore anywhere in the ticket description to prevent this ticket from being processed.
    • Add @traycerai branch:<branch-name> anywhere in the ticket description to specify the target branch for the implementation plan.

    Community

    • Join our Discord Community to get help, request features, and share feedback.
    • Follow us on X/Twitter for updates and announcements.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Projects

No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions