From bf8ff0e4026c444760af3571181e84c4971bd0c3 Mon Sep 17 00:00:00 2001 From: r0ny123 <49360849+r0ny123@users.noreply.github.com> Date: Sat, 15 Aug 2026 02:29:16 +0530 Subject: [PATCH 01/23] fix(labels): report demangled Rust names with the spacing rustc uses Demangled names were passed through a port of Ghidra's CondensedString before being stored, so ::fmt reached the report as ::fmt and A, B became A,B. Ghidra needs that because a Ghidra symbol name cannot contain spaces; SMDA has no such constraint, and already stores names with spaces from another source -- 39 of the 399 names recovered from a PDB on a rust-lld x64 image contain them, because MSVC proc records spell them that way. The condensation was applying one spelling rule to names from a symbol table and another to names from a PDB, in the same report. Removing it exposed a printer bug it had been masking: a function pointer's ABI ran into its fn, giving unsafe extern "C"fn(*mut u8). The closing quote was emitted without the trailing space, and comparisons against the reference implementation could not see it because the condensation deleted that space from both sides. One existing test asserted the wrong spelling and is corrected. Measured against rustc-demangle 0.1.28 on 147 real symbols read out of a rust-lld x64 image: 93 of 147 matched byte for byte before, 146 after dropping the condensation, and 147 after the ABI fix. Cross-checked against the 18 mangled symbols in the reference implementation's own test corpus: 8 match, 9 raise and so keep the name the binary gave them, and 1 differs -- a punycode identifier this port renders as a placeholder, which is pre-existing and left alone here. --- .../labelprovider/RustSymbolProvider.py | 5 --- .../labelprovider/rust_demangler/rust_v0.py | 2 +- .../labelprovider/rust_demangler/utils.py | 41 ------------------- tests/testRustSymbolProvider.py | 37 ++++++++++++----- 4 files changed, 28 insertions(+), 57 deletions(-) delete mode 100644 src/smda/common/labelprovider/rust_demangler/utils.py diff --git a/src/smda/common/labelprovider/RustSymbolProvider.py b/src/smda/common/labelprovider/RustSymbolProvider.py index 7d02a06b..4e6efeff 100644 --- a/src/smda/common/labelprovider/RustSymbolProvider.py +++ b/src/smda/common/labelprovider/RustSymbolProvider.py @@ -11,7 +11,6 @@ from .ElfSymbolProvider import is_defined_elf_symbol from .import_parsers import resolve_pe_base_addr from .rust_demangler import demangle -from .rust_demangler.utils import remove_bad_spaces from .RustSymbolEvidence import RUST_DEMANGLE_ERRORS, is_rust_language_evidence LOGGER = logging.getLogger(__name__) @@ -169,7 +168,6 @@ def _update_macho(self, lief_binary, binary_info): try: demangled = demangle(raw_name) if demangled: - demangled = remove_bad_spaces(demangled) self._func_symbols[adjusted] = demangled except _DEMANGLE_ERRORS as exc: LOGGER.debug("Failed to demangle Rust symbol %s: %s", raw_name, exc) @@ -194,7 +192,6 @@ def _update_pe(self, lief_binary, base_addr=None): if self._is_rust_symbol(raw_name): demangled = demangle(raw_name) if demangled: - demangled = remove_bad_spaces(demangled) self._func_symbols[active_base + function.address] = demangled except _DEMANGLE_ERRORS as exc: LOGGER.debug("Failed to demangle Rust symbol %s: %s", raw_name, exc) @@ -218,7 +215,6 @@ def _update_pe(self, lief_binary, base_addr=None): if self._is_rust_symbol(raw_name): demangled = demangle(raw_name) if demangled: - demangled = remove_bad_spaces(demangled) function_offset = active_base + symbol.section.virtual_address + symbol.value if function_offset not in self._func_symbols: self._func_symbols[function_offset] = demangled @@ -239,7 +235,6 @@ def _parse_lief_symbols(self, symbols): try: demangled = demangle(raw_name) if demangled: - demangled = remove_bad_spaces(demangled) function_symbols[symbol.value] = demangled except _DEMANGLE_ERRORS as exc: LOGGER.debug("Failed to demangle Rust symbol %s: %s", raw_name, exc) diff --git a/src/smda/common/labelprovider/rust_demangler/rust_v0.py b/src/smda/common/labelprovider/rust_demangler/rust_v0.py index 25a22637..5dcb889c 100644 --- a/src/smda/common/labelprovider/rust_demangler/rust_v0.py +++ b/src/smda/common/labelprovider/rust_demangler/rust_v0.py @@ -559,7 +559,7 @@ def f1(): if abi: self.out += 'extern "' self.out += "-".join(abi.split("_")) - self.out += '"' + self.out += '" ' self.out += "fn(" self.print_sep_list("print_type", ", ") diff --git a/src/smda/common/labelprovider/rust_demangler/utils.py b/src/smda/common/labelprovider/rust_demangler/utils.py deleted file mode 100644 index 42066fa3..00000000 --- a/src/smda/common/labelprovider/rust_demangler/utils.py +++ /dev/null @@ -1,41 +0,0 @@ -def remove_bad_spaces(text): - """ - Removes spaces that are not separating distinct objects, particularly - inside templates and parameter lists. - Based on Ghidra's CondensedString logic. - """ - if not text: - return text - - depth = 0 - condensed_parts = [] - - # Simple state machine to track depth of <...> and (...) - # and remove spaces if depth > 0, unless they separate alphanumerics - - for i, char in enumerate(text): - if char == "<" or char == "(": - depth += 1 - condensed_parts.append(char) - elif (char == ">" or char == ")") and depth > 0: - depth -= 1 - condensed_parts.append(char) - elif depth > 0 and char == " ": - # Look ahead - next_char = text[i + 1] if i + 1 < len(text) else "\0" - last_char = text[i - 1] if i - 1 >= 0 else "\0" - - if last_char.isalnum() and next_char.isalnum(): - # Keep space as underscore if it separates words inside template? - # Ghidra says: "separate words with a value so they don't run together; drop the other spaces" - # But typically Rust types don't have spaces inside unless it's `where T: ...`? - # Actually Ghidra converts it to underscore if surrounded by chars. - # Example: `Foo < Bar >` -> `Foo`. `Foo < Bar Baz >` -> `Foo`. - condensed_parts.append("_") - else: - # Remove space - pass - else: - condensed_parts.append(char) - - return "".join(condensed_parts) diff --git a/tests/testRustSymbolProvider.py b/tests/testRustSymbolProvider.py index d9dffdef..fc755920 100644 --- a/tests/testRustSymbolProvider.py +++ b/tests/testRustSymbolProvider.py @@ -16,7 +16,6 @@ UnableTov0Demangle, V0Demangler, ) -from smda.common.labelprovider.rust_demangler.utils import remove_bad_spaces from smda.common.labelprovider.RustSymbolEvidence import is_rust_language_evidence from smda.common.labelprovider.RustSymbolProvider import RustSymbolProvider @@ -122,7 +121,7 @@ def test_v0_empty_const_hex_nibbles_raise_demangler_error(self): def test_v0_non_c_abi_fn_type_demangles(self): # the skip-pass abi validation was inverted, rejecting every valid # non-C abi (e.g. extern "system") fn-type symbol - self.assertEqual(demangle("_RIC1aFK6systemuEuE"), 'a::') + self.assertEqual(demangle("_RIC1aFK6systemuEuE"), 'a::') def test_legacy_strict_hash(self): """Test that hash segments are properly handled in legacy symbols.""" @@ -554,15 +553,33 @@ def test_pe_symbol_provider_returns_raw_rust_names(self): self.assertEqual(results[0x403000], "ExportedFunc") -class TestUtilityFunctions(unittest.TestCase): - """Tests for utility functions.""" +class TestDemangledSpacing(unittest.TestCase): + """Demangled names reach the report spelled the way rustc spells them.""" - def test_space_cleanup(self): - """Test remove_bad_spaces utility function.""" - # Inner spaces removed - self.assertEqual(remove_bad_spaces("Vec< T >"), "Vec") - # Separating space becomes underscore - self.assertEqual(remove_bad_spaces("Foo< Bar Baz >"), "Foo") + # a real symbol from a rust-lld/MSVC x64 image + TRAIT_IMPL = "_RNvXs5_NtNtCslFVcyoAu48q_3std2io5errorNtB5_5ErrorNtNtCs55qC6OcLGgs_4core3fmt7Display3fmt" + + def test_a_trait_impl_keeps_the_as_separator(self): + self.assertEqual(demangle(self.TRAIT_IMPL), "::fmt") + + def test_a_generic_argument_list_keeps_its_separating_space(self): + self.assertEqual(demangle("_RIC1aKh4_Kh4_E"), "a::<4, 4>") + + def test_a_function_pointer_abi_is_separated_from_its_fn(self): + # real symbol; the space after the ABI string used to be missing + name = "_RNvMs3_NtCs8oYkXk2gzQW_5alloc7raw_vecINtB5_6RawVecTOhFUKCBN_EuENtNtCslFVcyoAu48q_3std5alloc6SystemE8grow_oneB13_" + self.assertIn('unsafe extern "C" fn(*mut u8)', demangle(name)) + + def test_the_provider_stores_the_name_the_demangler_produced(self): + provider = RustSymbolProvider(None) + mock_binary = MockLiefBinary([], exported_functions=[MockExport(self.TRAIT_IMPL, 0x1000)]) + mock_binary.imagebase = 0x140000000 + mock_binary.sections = [MockSection(0x20000000, 0x1000)] + + with mock.patch("lief.PE.Binary", MockLiefBinary): + provider._update_pe(mock_binary, base_addr=0x400000) + + self.assertEqual(provider.getSymbol(0x401000), demangle(self.TRAIT_IMPL)) class TestRustV0ConstBackrefs(unittest.TestCase): From b46b178e88a2cc406838dbb41ce0085fd4ff03d9 Mon Sep 17 00:00:00 2001 From: r0ny123 <49360849+r0ny123@users.noreply.github.com> Date: Sat, 15 Aug 2026 02:36:13 +0530 Subject: [PATCH 02/23] docs: attribute the vendored third-party code src/smda/common/labelprovider/rust_demangler/ is vendored third-party code and carries no attribution. The commit that introduced it says so directly -- it vendors the rust_demangler package by Team bi0s, MIT licensed, with robustness behaviour reimplemented from Ghidra, which is Apache-2.0. Neither is recorded: LICENSE covers only this project's own BSD 2-Clause, there is no NOTICE, and the package has no header. The MIT licence asks that its copyright and permission notice travel with copies and substantial portions, and this code is redistributed in the wheel published to PyPI. Add a NOTICE carrying the MIT copyright line and permission text and a pointer to Apache-2.0 for the Ghidra-derived parts, a docstring on the vendored package pointing at it, and a line in the README credits. The in-code comments this attribution was drawn from named "Ghidra's rust-demangle.c", and no such file exists: Ghidra's Rust demanglers are Java, at Ghidra/Features/Rust/src/main/java/ghidra/app/plugin/core/ analysis/rust/demangler/RustDemanglerV0.java and RustDemanglerLegacy.java. Name those instead, in the NOTICE and in the two comments that were the source of the error, and record that Ghidra's own V0 demangler is a port of the rustc-demangle crate, which is the real upstream of the behaviour. The NOTICE lists every vendored component rather than only the one that prompted it, so Tarjan.py and DominatorTree.py -- both already credited in the README paragraph the new line joins -- appear alongside it. No pyproject change is needed: setuptools' default license-file glob already matches NOTICE*, and a build confirms it reaches both the wheel (dist-info/licenses/NOTICE) and the sdist. --- NOTICE | 64 +++++++++++++++++++ README.md | 1 + .../labelprovider/rust_demangler/__init__.py | 4 ++ .../rust_demangler/rust_legacy.py | 2 +- .../labelprovider/rust_demangler/rust_v0.py | 2 +- 5 files changed, 71 insertions(+), 2 deletions(-) create mode 100644 NOTICE diff --git a/NOTICE b/NOTICE new file mode 100644 index 00000000..c781b75a --- /dev/null +++ b/NOTICE @@ -0,0 +1,64 @@ +SMDA +Copyright (c) 2018-2020, Daniel Plohmann and Steffen Enders + +This product is licensed under the BSD 2-Clause License; see LICENSE. + +It includes third-party code, listed below with the notices its license +requires. + +================================================================================ +rust_demangler +-------------------------------------------------------------------------------- +src/smda/common/labelprovider/rust_demangler/ is derived from the rust_demangler +package by Team bi0s (https://github.com/teambi0s/rust_demangler), used under the +MIT License and modified for use here. + +MIT License + +Copyright (c) 2021 Team bi0s + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +================================================================================ +Ghidra +-------------------------------------------------------------------------------- +Parts of the same directory reimplement behaviour from Ghidra's Rust demanglers +(https://github.com/NationalSecurityAgency/ghidra), specifically +Ghidra/Features/Rust/src/main/java/ghidra/app/plugin/core/analysis/rust/demangler/ +RustDemanglerV0.java and RustDemanglerLegacy.java: the recursion bound in the v0 +demangler and the strict hash handling in the legacy demangler. Ghidra is +licensed under the Apache License, Version 2.0, available at + + http://www.apache.org/licenses/LICENSE-2.0 + +Ghidra's own V0 demangler is a port of the rustc-demangle crate +(https://github.com/rust-lang/rustc-demangle), dual-licensed Apache-2.0 OR MIT. + +================================================================================ +Tarjan's algorithm +-------------------------------------------------------------------------------- +src/smda/common/Tarjan.py is based on the implementation by Bas Westerbaan +(https://github.com/bwesterb/py-tarjan), refactored into a class for pooled +computation. + +================================================================================ +Lengauer-Tarjan dominator tree +-------------------------------------------------------------------------------- +src/smda/common/DominatorTree.py is based on the implementation by Armin Rigo +(https://bitbucket.org/arigo/arigo/src/default/hack/pypy-hack/heapstats/dominator.py). diff --git a/README.md b/README.md index 3ba60181..27385001 100644 --- a/README.md +++ b/README.md @@ -197,6 +197,7 @@ Thanks to Jonathan Crussell for helping me to beef up SMDA enough to make it a d Thanks to Willi Ballenthin for improving the handling of ELF files, including properly handling API usage! Thanks to Daniel Enders for his contributions to the parsing of the Golang function registry and label information! The project uses the implementation of Tarjan's Algorithm by Bas Westerbaan and the implementation of Lengauer-Tarjan's Algorithm for the DominatorTree by Armin Rigo. +Rust symbol demangling is derived from the rust_demangler package by Team bi0s (MIT), with behaviour reimplemented from Ghidra's Rust demanglers (Apache-2.0); see [NOTICE](NOTICE) for the full list of third-party components. Thanks to r0ny123 for his major code quality improvements via ruff and various contributions for several aspects of this project! Pull requests welcome! :) diff --git a/src/smda/common/labelprovider/rust_demangler/__init__.py b/src/smda/common/labelprovider/rust_demangler/__init__.py index 72d2219f..8a9d575b 100644 --- a/src/smda/common/labelprovider/rust_demangler/__init__.py +++ b/src/smda/common/labelprovider/rust_demangler/__init__.py @@ -1,3 +1,7 @@ +"""Rust symbol demangling, derived from Team bi0s' rust_demangler (MIT) with +behaviour reimplemented from Ghidra's Rust demanglers. See NOTICE. +""" + from .main import demangle __all__ = ["demangle"] diff --git a/src/smda/common/labelprovider/rust_demangler/rust_legacy.py b/src/smda/common/labelprovider/rust_demangler/rust_legacy.py index 1534b389..ab41e08a 100644 --- a/src/smda/common/labelprovider/rust_demangler/rust_legacy.py +++ b/src/smda/common/labelprovider/rust_demangler/rust_legacy.py @@ -146,7 +146,7 @@ def is_ascii_punctuation(self, c): return c in string.punctuation def is_rust_hash(self, s): - # Improved robustness based on Ghidra's rust-demangle.c + # Improved robustness based on Ghidra's RustDemanglerLegacy # Legacy Rust symbols end with a path segment that encodes a 16 hex digit hash, # prefixed with "17h", i.e. '17h[a-f0-9]{16}'. if len(s) == 19 and s.startswith("17h"): diff --git a/src/smda/common/labelprovider/rust_demangler/rust_v0.py b/src/smda/common/labelprovider/rust_demangler/rust_v0.py index 5dcb889c..ea6d4ba7 100644 --- a/src/smda/common/labelprovider/rust_demangler/rust_v0.py +++ b/src/smda/common/labelprovider/rust_demangler/rust_v0.py @@ -490,7 +490,7 @@ def skip_const(self): class Printer: - # Based on Ghidra's rust-demangle.c, we limit recursion to prevent stack overflows + # Following Ghidra's RustDemanglerV0, we limit recursion to prevent stack overflows # or excessive resource usage on malformed inputs. # Must fire well below CPython's own recursion limit (default 1000), or a # self-referential backref chain raises RecursionError before this guard. From f33431a4ff682bfbae715e99e8610d7df7bc99ac Mon Sep 17 00:00:00 2001 From: r0ny123 <49360849+r0ny123@users.noreply.github.com> Date: Sat, 15 Aug 2026 08:57:26 +0530 Subject: [PATCH 03/23] fix(labels): demangle the names a PE actually carries Two reasons a PE reached the report with mangled names. RustSymbolProvider recovered nothing at all from a PE. Its COFF loop skipped a symbol when Symbol.section was None, and lief never populates that attribute for PE -- PeSymbolProvider already says so in a comment and resolves through section_idx instead. On the bundled mingw-linked Rust fixture, 0 of 4818 symbols carry a section while 4366 carry a usable section_idx, so the guard rejected every one, the provider contributed nothing, and PeSymbolProvider's raw spellings won by default: 2098 names reached the report spelled _RNv... Resolving through section_idx recovers those 2098 and none of them stay mangled. PeSymbolProvider never demangled anything. ElfSymbolProvider and MachoSymbolProvider both route recovered names through a demangler, while this one stored them verbatim, so a mingw-built C++ PE reported _ZN12FileExplorerC2Ev rather than a signature. It now applies the same Itanium helper ElfSymbolProvider uses, which leaves every non-Itanium name untouched. Making that COFF loop live also made the gate in front of it matter, and it was the wrong one. _is_rust_symbol tested the prefixes alone, but legacy Rust mangling shares _ZN with the C++ Itanium ABI, and this provider is consulted before the format providers -- so on a Rust binary that also carries C++ symbols it would have claimed names like _ZN4test4funcEv and replaced the full Itanium signature "test::func()" with the "test::func" the Rust legacy demangler degrades it to. It now uses the shared evidence gate, which parses a name before claiming it. Recovery on the fixture is unchanged at 2098 names. No bundled PE carried Itanium C++ symbols to show the second fix on, so add one: a small C++ translation unit compiled for x86_64-w64-mingw32 by g++ 16.2.0. Before the change it yields three mangled names and nothing readable; after it, three readable signatures and nothing mangled. The mocks in testRustSymbolProvider modelled a PE symbol through Symbol.section, which no real PE symbol has, so they exercised a path that could not work on a real binary; they now carry section_idx, and the provider reads it directly rather than through a getattr default that would turn a future lief rename back into "skip every symbol". Several tests used _ZN3foo3barE as their Rust fixture, which has no 17h suffix and so is Itanium C++ rather than legacy Rust; they now use a name that really is Rust-legacy, and the PE provider test expects foo::bar from the C++ demangler. --- .../common/labelprovider/PeSymbolProvider.py | 5 +- .../labelprovider/RustSymbolProvider.py | 24 +++--- tests/cxx_pe_gnu_xored | Bin 0 -> 192297 bytes tests/testPeSymbolProvider.py | 55 ++++++++++++++ tests/testRustSymbolProvider.py | 69 +++++++++++++----- 5 files changed, 120 insertions(+), 33 deletions(-) create mode 100644 tests/cxx_pe_gnu_xored diff --git a/src/smda/common/labelprovider/PeSymbolProvider.py b/src/smda/common/labelprovider/PeSymbolProvider.py index b97d7a18..2dabe9d2 100644 --- a/src/smda/common/labelprovider/PeSymbolProvider.py +++ b/src/smda/common/labelprovider/PeSymbolProvider.py @@ -7,6 +7,7 @@ from .AbstractLabelProvider import AbstractLabelProvider from .import_parsers import parse_pe_delay_imports, parse_pe_imports, resolve_pe_base_addr +from .ItaniumDemangler import demangle_itanium_symbol lief.logging.disable() LOGGER = logging.getLogger(__name__) @@ -67,7 +68,7 @@ def parseExports(self, lief_binary, base_addr=None): # UnicodeDecodeError: 'utf-32-le' codec can't decode bytes in position 0-3: code point not in range(0x110000) function_name = function.name if function_name and all(ord(c) in range(0x20, 0x7F) for c in function_name): - function_symbols[active_base + function.address] = function_name + function_symbols[active_base + function.address] = demangle_itanium_symbol(function_name) return function_symbols def parseSymbols(self, lief_binary, base_addr=None): @@ -94,7 +95,7 @@ def parseSymbols(self, lief_binary, base_addr=None): if function_name and all(ord(c) in range(0x20, 0x7F) for c in function_name): function_offset = active_base + sections[section_idx - 1].virtual_address + symbol.value if function_offset not in function_symbols: - function_symbols[function_offset] = function_name + function_symbols[function_offset] = demangle_itanium_symbol(function_name) if num_candidates and not function_symbols: # the previous failure mode was silent: a whole corpus could be built unnamed # without anything complaining, so say so rather than contributing nothing diff --git a/src/smda/common/labelprovider/RustSymbolProvider.py b/src/smda/common/labelprovider/RustSymbolProvider.py index 4e6efeff..2af37eb6 100644 --- a/src/smda/common/labelprovider/RustSymbolProvider.py +++ b/src/smda/common/labelprovider/RustSymbolProvider.py @@ -199,12 +199,11 @@ def _update_pe(self, lief_binary, base_addr=None): # working example: 3969e1a88a063155a6f61b0ca1ac33114c1a39151f3c7dd019084abd30553eab # Parse PE symbols (COFF) if available and LIEF extracted them # (Similar logic to PeSymbolProvider but focusing on Rust) + sections = list(lief_binary.sections) for symbol in lief_binary.symbols: # Check if it is a function symbol and has a section if hasattr(symbol.complex_type, "name") and symbol.complex_type.name == "FUNCTION": - if symbol.section is None: - # section_idx 0/-1/-2 (undefined-external/absolute/debug): not a locally - # defined function, its value is not a usable in-image offset. + if not 1 <= symbol.section_idx <= len(sections): continue raw_name = "" try: @@ -215,7 +214,9 @@ def _update_pe(self, lief_binary, base_addr=None): if self._is_rust_symbol(raw_name): demangled = demangle(raw_name) if demangled: - function_offset = active_base + symbol.section.virtual_address + symbol.value + function_offset = ( + active_base + sections[symbol.section_idx - 1].virtual_address + symbol.value + ) if function_offset not in self._func_symbols: self._func_symbols[function_offset] = demangled except _DEMANGLE_ERRORS as exc: @@ -241,16 +242,15 @@ def _parse_lief_symbols(self, symbols): return function_symbols def _is_rust_symbol(self, name: str) -> bool: - """Check if a symbol name appears to be a Rust mangled symbol. + """Check whether a symbol name is a Rust mangled symbol. - Legacy Rust mangling uses _ZN prefix (compatible with C++ Itanium ABI). - Rust v0 mangling uses _R prefix. - Some platforms may use __ prefix variants. - - Note: We intentionally exclude bare 'R' and 'ZN' prefixes as they are - too broad and could match non-Rust symbols. + The prefixes alone are not enough to decide: legacy Rust mangling shares _ZN with + the C++ Itanium ABI, and this provider is consulted before the format providers, + so claiming a C++ name here replaces a full Itanium signature with the degraded + spelling the Rust legacy demangler produces for it. The shared evidence gate parses + the name before answering, which is what tells the two apart. """ - return name.startswith(("_ZN", "_R", "__ZN", "__R")) + return is_rust_language_evidence(name) def getSymbol(self, address): return self._func_symbols.get(address, "") diff --git a/tests/cxx_pe_gnu_xored b/tests/cxx_pe_gnu_xored new file mode 100644 index 0000000000000000000000000000000000000000..0f244a20b2f1e53f8e37e5d9ee7403dbb680c2b6 GIT binary patch literal 192297 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

vuoZ$IEm1P3X&;SM%RYt-(@) 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

wC~vZ(lE|)Zoy_M)lbExW~%W^sLwP{KDc~ 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

kRUjG~a7L^~5 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С7UizrCQJK_sJWabt0_K%F{f&XP{;#U6U_dmC^1p4^pU;HP~{{TxV=Gvd# zUuCsLW3^wt{p@Y$;9=td{U^5w%>S)CjPBT(nDPiZ8ydcP&Gg#E!OcqH9ty64^iydG zLS+I{q?=^d$yEt%xn3b5QhfLfS?jT)i?g9M?QIrwRuMWT0eb33I&Cs4Pfy_d7nHl? zpFB1|-_FC9keHBM=kNUgVB=tYc4cEDe{Sw5A~Z4f{PcLI>hJs? zG5^ZS%g#NR(uCB1SFl(VOx1tT^q_vq1AF3Ox zt9L)uWJ2vfiO_d8#fC+N`vm!fwfp)fZmez|`lWikjY*729ZP!iHu8Lz4D?T=ba4Ko zw*vnS_lhvQ|451|kF<3ZAE_!omasQAuzY2}*f>FZ4(~swxxBKvl!T6^rkk3co=7|I zolF_J~iw&{qy}{?PyO?*t_I`r*XJ#n#;3;=$~4oONoPYg$UGfBN|7m)60)uJ-oX z)~>HpO<)L^nygUzoKsL%TbJ_beN`nH^#A*l{euGDM!rexrtI7tJ|OJ_{#($mn98!6 zshQ>VjwOWuw`StKpLft^$-(J3=>O06b34joqZ7|RV_aiJzk)`Lt|5BQ*uV@|iG*B; zn2-pUSe{MU(axDthlve~k)8pa(V5CnT2h|Ff?tFb^#7E6ZfZ{-Y04g99}KGUsi!B2M?G3U^F+1Ssza2$l<3Ka&+Fk$q$#DI+_gKqe^sfJot< z^HXJ&uBTUcnKe;$p1jn4rr&JDO>)gt)W-OpwWZZ}Yg!^Q2TpspXYMYpo$j2no|6HQ z1>XJv;=vOYq5UDgIdRc3DsinjIf<=r<168gYL@XkCNBFZBip;ApwPYK%-gqou%afd zyr$N$ZZj;r@!j{{wdQZ%nc7oMF1kwEzZ?z?42lf-Ep3d|PWJCjOwZhyFAI-b^4(ha zvbnxN+$#Y0|AoDS?c&s=$TyLN$A!E~$U}6a&ky>n+8_B)2v+uNZ5hBLM5l5xEiHp6mxjwQsvUL!I z9UhPs6cPD2E^T^mX>MTV_+stX?)LW5V0X`c*Kp45;v8S_KR&N4tJ~g~{Zn0kC-XDt zAF2}~q5{8uSN!P-_0QMfBrl)1fj-SPMg;$PdbGa0)H@FDzw`g;zaj!6e75(uw*%4= zL&C>nVuH^PRF#yJq_osjv@mgnXuV|aoL!ujXPzp}LIYw8t01RfsWu#}e1@AC%VI7LOz zg8na50G_{L7U*9Q{Dj!r6R9+|CtsiCnXi_RR-=H=zp-7%gAUvlazX{C!v0qid%FO^q(Rk zR`+Rm`MC&K@6yQ|DoG|QAnITAJ3J_Y3?iDZbkt(Cp#NjUjZBP6M0pKY%TnTnNiE#} z?HqZ~u!yhTy+(P1Y|*>WFG2F{ z{679HEv0Cz|3p!{TvYg{CHG5@QFnWXTW4J6`?>zcp-DU7AKv=uTUNR;9kH}3H@mpR zzf~3(FuLLW>rnOJ&o0q+NnC92N!aneHX1U@9~4~+b1_V}Ygh8H!T&=*^uXDT`lba1 zK@IGGT-5Y#3i6ap+8nfzEbJV3-2CQxn%DTG#JSVNL_~4LWN&eDynHEp=aJ;*!v{*Z z57jWm#Y~LVXr4Ve*VEC()iV{u!&Ox`y>4UB1N$GY^(z)CN*UQ#c%F7s9v_a(?+2U@+Hb zfd9C?_UA17V@*|2V`lD|>mlw4^#Aa$ga4lq3JQp{$`LulG>pp`@g=l}U>S$>H91M_VHg*nFXg81uA<_g-;h_RL%C+lM1W-I9 zW8Q*!4<-dB84~cZ_(35mAjZrHDKX4++p}KjKqVziKe@?Gn8<@Rvuw!9m zadojXFmdx~ss;jT0xhaBKek+z0SK z!~A=5W2Shce0gbodG=@#{4ezvjlb5?W8$N|{X%`h;{AgIg2Ge!;`(6z@9&!snx5_( zlNy)(zH0v6a%n|1d;rDe?~3Xw%W`%rc47Ws+i+TUn)g&s@{#(3dl(Yfz<72>2fcPIivYV1IhSHbBDw{0|emSBB2!f!?UK#QI@&Bqq$yEy8~H z+c%%rp^-5E|2a6(-qbre5p9(k`?kI9*Y}m3lP`zc7eC$?#8#Jr{waL-4@jLyzHdy9 z_QUtl-m@??**8&`Roe2+VHD*{d&ieviqDt)$FtzF+=9m3+PtZ$()CV6{S)A~GB*PI z;MVV@(?7e%!oYvnQ{RUEH>;kBm7SImG-q8sO$_xPJKMf?a<(-lxs^@U3;Y`@@yq^y z6+-_H{6~($Em5AkjQ_ho>eEy6(Mlg#B*`f#9>^#?T-1D`e9!-hn);H;1DO9Qsp=a% ze_?F$8XcPg^$Lnr|MM*1f8O?dSk5v>ggrv{}AS1+CIS{G6CTcS$h#NgNXSr zT|#n7dTLiHYR2!(cYL)uzjE^n3ZG_{l$w63fbs8QZFp_n#;1tJ9n=jJT_=4JnDcK;akzlG-l$QTD?m;Rp} zWE@-}fOlLck|W_HyLq3KjRJgZm+PNmU}1HoW9MMwHsZnNzx$jIQ&60VT=cL1u}(oo zmKD}7uSE68QROkJvaza$iSwoYb=n5_k0$)jb<8Yz?_09lfc`(!!|~;+GvvRya=ey( z?cL{F?w2-~6Vx6G`e#EQ$Uj)Cj2j03SF&OB+cd3`ckeT@4{}lqa!>N#7WvRcmFqzJ2dfY3k^ljfs9=+xRgnG~i8o;HTWYiW2W?@5q2i?}VkL z`d>`IKaJSA)IVDx^bf%Qyf7kPh5L^h7Y!yR79$1@E-Bd!e55C)hOb;-I+!^+TWTsx zJd%ajr$6xgD`EbhA1({9m1SB_z`!Ay^faMo5TNfP;~h7GG2pvQOwV z@YEwg|H)-(V0hQe#MB)VixLGD)tdjsC+PpjxH>uKLjT9I#LdflA~**8UoCzip=DkX zk=|QFs$p4eC`LLV^{eM-kFq*zAuc)qEsPd|-Uu_8gT=r%9i!b

bGsb$Py1UdN5_X} zB^O_juEd=D2{{OF$&=kCzbS`IZ9oV7t7%qd7O-?f0bt05#l>sP zk1imh#vtMj{wK-$hte{vYVt4uqI}@`;0cn7(Npaqh0FOb%|polGvqT?es1;M@pAqX zQQZ!GSz?jHSK6~5kziGgjPe+zT;jf`Rh{dauAx1^$^@BWMMcFfpF#h-c582I_h=jTM+E=mLt$adx6z^o2L%7Pv~+xQ zcq(~dp#Agbui*Z!E_|0)RJ8j3eQi(A(C}aX&-<$UicjS=br->r@$-udn=t{&zF|H= zA-!*s-gxh>pZ|d%ky>z)U!Z#6Oei&B6@Q0{|O5TJyh4=pl38&#c z{uU|@7LLA&iqZx2zij^M!_};fli>Zg7vtj-S>)1`iIc*j-H91KYG55O%6wS zQ$`0kCxHHc@?h`JUOIbb{X@k3Gk@*-moMLoY07h-!T0~Ux)cfYA9XwXZ{pqtgha=t zwaK>iyo3#~e{8tAKBKa}VQivvw5+TwusI+sIxxiN*RTKKzx+A*9i8Np9G8A_c5$2` zYh`6&i_GY$RN~TQ`u2fd52S)?r*gxH0*qJ+6U0gi8n4RC+oBh48I=6pO`?+*~=X560HwgT%Nx|W7Ze1eky6qsjn@sXe{_pQCV2}@ndFfeS@kj3XeQB zKH)V?EKChY5A*9f{8Y@K|Gi13`NHM3xvjF1l*A*ur?U4S7|F=Xt8sb|is3>3n^(vc zg43Vs>apQ7QFF1eu*jp^Sf4@ttD%BIBh1G`f18L`fS&`OkATRKN75-4_}8v4SWrnR zFz-;HlOn%j)OF->eeJI2-U9mW*|i-Y^t3M=%^yd8sZ1Y-^RFU#8V;z}5dv%@u!?>;6vpX~L1vZ14iqPfuv41G41hO5ATz}I!R z^;B~K{R^qR;0<4R|5<5XNysPzyiXdk&$Ph*r+GuL0N#H-K?lXh;D05)%}32dCr@i5 zvjqBo2^C5aelfH=yaKE|JenN`$~m77;|bU3@cK6kiue6V}` zYwh%CYj5XwWAVbbzNT;An-Wvf0^dZW2KXg{zc<7`CMEn$Y|PKnuf9c843~78TJIB#JA{1mvjnSKU1g)5QXP{o8SN6SyM2@euld<@p1n-#eS; zCPktDH`3N@+tbxOmGv<G#UP?2!C zkiqYafniS8YGG~j@*e1)UcR`2MNZ}B_|elw9Qq&LB?$jxKn73IDGY4QGr8MWE5z&}lTn-+KB$`19vD*fgB^Wrc6 z1i?Q`zkZ7p-%-=v!A#_6u-!DIEYVVNU?EX9d9?(3vy#Mwh`2XO4T6{e_G_$gLE(`jPeXG7dm7sq( z{O}v@f0IsEkx?=k|MR~W!uNk8f#?D7Ur@?#kdl!Kh*Iez{BN#o!2e`p!~+;C%)dopTwPt-F8rPF7bB z2G0Aww|De?Ir!2$@TYye=}Xxk8@T^sB-I|*f&PgD^iK%>|M#adEuCFov-9)%-<-UK z{DbA;@rmVusp-||sDPNdaG&Uvyght412aMYR)4&Iu#+4aoEe`IfFwhHhKz=8iGhh_ zhKhrW+|Grs0`EV`Ar&c^Cm|&j9kneh6N5UF88f2(VaCPhwg>$`U#gg(5Fb93m?u1c zsS_z#x%=`r?>$lyztq2|s>xuWqp_L4K)!>ChemiEhfn|$2lp<n0u#qCj{U%z3twY8&u?dom{*zS)3|Nrpt^w{{X$m^Hue~i#S0{=m5Qtj7$X_;bqd4)ph2TEo~8c&pc9aV`z|Enbj{ci;S zzuMZw)OOmy+#<~o^6xzC9qiw!ISIf8__ZSV{{yu%Fa4jn;bGx<&r2`;@3BdpiCdt5 zjwbTH)PKpBpI(`}+A|t6FyA32Lcx_n!!{$u#L&=C0GYouHWuN{$C~Pd8V^;UD5`NtJ`B;5 zk$tPIpvbSS^iT;89|;5dCW^YI>a!oPel?>f%0~3YF#j~Swgx_$jcq^N|G-0J%W4Ao z=Yrfn-Zj@XWLE>EG^@TaW4<;E{EvG}C;3MkoAqVCS66$!rhjejZ28gL5faecHQ6;9 z))DHH=;fOj`!gkaCoFxVyejADeQx>o@71OJ%y)C;S@n$>pK3;iY?~)0O}|agcsI{y zf&c9n_@99Pwe?$W@z3r-UUX1sdQejE`8g(rA@ybbgNc}bBKp6Pgk5m|Atxmze2Yv) zO$Yq}G!x1jm}C?vR7h0U$)BMi5s_(WDypOFDkxG}vGMV-QoB*JGjR$sa9A6Pa#3@M zm~q%xJ6hVjwiWbL;&?2jK#Y&73dxsXBfdvWMWIKGL`8P(I-#5GwxOpNW16pDFc`Cg zehf{GHcgC8j&yzfKKNyF7=Z)qpS;*u`~CZPer0`sub==M^nW#FJKrw#k2MYTKS$nG zeEE`+92*nx);AzHCM`A2FFrLi@D1df`zORjgm^X1uFAmtb8{^(WvvkWPrtX*mUjUx zakQg9tmFA%cJ8;%MT^;B0Lp!sTG0`bdsaN<#jgmV^Q} zvcw}@6-~VR*HFojuiYZiA-`^iqi@O0Pwh;@B5LbkVQ`z8+MS0<2(;mX8mgaVb##aB z=^Nln82`=x7%)TlpKa~#S~$LR@bvu4Kf=Ghx%7U26_NkFzFDvc_rJKP*x#qS3kd-j zz<-F{*dPCT2L5;dP&TN4^Rf&6@`3)Be^m3SrXsJXtaPygNWK8{&D#XD`@nEtUt?|I z*!1++-}`@Pc+}$j{8p-8qJIcDV*rrowfE=GMO<85%Fobf2QvWL8ZcU$x?|zupt%_s zIln~kT^((m&B#!RNaSDG=v!L6V5C>4?}Yu2$qG8bJp7+IIY9q^#{$v+zSpA}3HKjK z9Ef(reE8t!12xcpsHo@wCQg(^PmPOC82XPPH|DT&UJ)NMKh>Oddl{)C3 z{e1oZoB#h>SU-&fX79}D@xpw~@7=A`*v`SNt;N&b?cJQhL_bdG|9{wBI-7-8eSWp} z^OY20;2*a}goXHgzi9tS3;Lh7WQ4vaF*z}6w0(3>1J1w2m8HPYD8JC~m93Ls-rn8` z{qd>Z3CS^sr{@>R(a~v+$WXZ$ys$SnvBAc~!+Nad?rslFEfNYcN@*!iXAWYTJ5&q| zjL%tFScP&y|IE)VZt3pqWb1C~;b;SG5@a-t8`5YhT9c4}rNn%LmYYwQ>nVU^Z)kx3 zUq=`T6&07j*xt>82J~Onme^bb$o3X5T*MLlC)ZoF6gca1KFLYJy}*A93t1W1xY(XQ zK3Iwi3yJMZNlA@MKfdVd1Se=mTW4NbRmMU|Nojq>@Y?7|FKFC`rW@YB?+R*x{IAyc z33aud{e52o`uhid{wNL3${qXqy)SlpdS-h*pgtfj#(!mReKed|!bH`!1s)v_E*c zpIU+claGLbw^%?#lva#^lAV!-jaJU;sVoZc4=kSC#S!5a0FEA?mb>H=5^9ObY9hSCsTJ|?sNpIXDp}O{3)yUxy)W0?U@jak_;{4-G zf14IT5B!6o?2OIDUmKf$R%TDPj{eTas-v?SPChq$u8j(c^zrpd@(oOm1CvWgh)@4X zPxqZ4BclV!pnn|n$}7ywdRLcGoK^TSGp`V$^0t?pR@A}&a9Ew34xlj;E~PMZfkFS`7;51r4d=h-Yj)z}g>J|? zpPLJMl@Krv{NLE9=Ht!ZW83`)f425ccVPefzWlAfH7B<^=d1|wZ_4H-+WU|D=7#%* z2HJ+E2lMK4a=fz2KeV+CeNTq|$7J7|>e8Hwtb+JYY2|r^A4($-``^ORV&mG{`q@@d zv`@mPm@3fz933rg7l8ha2vzZgv4NqYnTz!`%C}Zzt1#KD~d*Hu3l2v@d zNXtk68s>kv9IrI=)Lv>n1C`%1Re4bX8YL3&KN!eivCuIHqKe)YX8TtPxY5knzHCq$tnres0E!z9BWUikfcWA#^{S37`Lkb}n!X%@1GH~uprd1OXrQ6;-QVwjtu-Vs zGrKmurtnkVd%z$1R%91Ie`9jEX)q7`uR{w35di^-HA#_g<9+=T(%wh;`9;Uap8THQ zsX|6Z;YL%oc_A#UW9$!6&2IHFJB1*G(Sz}c@&4|*lJ|v%p#P2el2G{RePD?X2p&`OGb=N) z%YL-9x1)jo)qgrHI3=*qr^q)TF&`i+e#u))Tl1WN|42Cc7?+%w7*=bkZ(@r873%^2 z!1$VktGk1ffwqIRfs2Nt=OagBR}~r|Q5FX#CT5x2HcFb0EHtImA4_OSKNO&*XX1qU zKbNF57UmUX6cTh&Dl`-%Tyk7eVxk)aXx3I*lJ~USwe$^)dDxkmsM+}#gaznnXt+h$ zg}I@D#`@@?o2ilBW{|f}P>xqXV8mF+vP zZ{T1jB_0R1CNJoq1Iob{hW1H;b1*xL2gOFIV>3qcPC3Ko8G34K}FC2h$TNd-k6 zat3}8!8<(SMBI14|IMO(A$1k@|ChWtBrpm`6GS1tYTaqgf67S!KR?4F#PRgDpvL{?v$x3RH9Eh58y0`m_799;ub4-+Q?30EzTSFatd?L6JARA_=OpFi(y z6HN&~p?W+uc%rHKR8vXisq#aySUn|d$UifcMHgXWVG&1WAr}zg6QUO86}`=({aZ?z z7~21m<}RTB`08jX1pK?Fc8+wS0{mQ}jNlJs;pgg^>iGVxx#dR?Q*1mw)PE^Npnt87 zi9xRkdl>&V?b9dF|GX0lEIEhySN+`N_mR=bfzdzH-#WWD2ipgKH0sa9BKS`o30{FA zp+V6BZ@u4odxgZrgeC{Ygat>3&+K`A$=F<4?oynA`~S?^-uBLuABPz*|2aRdiwO%d za4SK%>gkOZ}Lckjzgia+GknY}A0Y(zukAJ#zrS#?H7PkY~JU!&LH3#k8ZcD1#SV!`~QcQPXU zb3=VuMj#^J!Kb=BDk3VOJ~+}pIVdrv!Z*?<&SyuO_Z<9B)5rC2|GB6^a z^B-i>>mP)6!2d>i75YEdC@61Uqo<{N%XXiUnT1*q^e=G?SA==)k}&cMFt7@X(5ovW z)`vvROE15p#wn-tkWAunf6hox!Ti$=3zGzP1((wY9fbO?USwHT4e^eTBQ>;x!}V6O5q&|5N~zjC>83in{*} z9X%t~U1pXmF#ozL#>LHjg^8E1ibYUJnjQK-5=s-<8(0(z$p^bL*tBrf}38~^pcUankoadvr$%}jm`@}GRD-~EFAA6ZIh7zXf<;y=g7 z#_^=>B_^i?ZvhUtsSh|7ACR*Nq5qkqT2NX>lz+Fesv0l1t{$`SbK{^7te;`NHK+}d ze?ABP59gWjAH#z+u>Ps4?&+EN^xq4M3pNP;S;_h47Uj_8{P(Em|XC&g=01zW-Pn5A}_0bmVn`Z|?Tm^|7_R+byFMVhYM< zXqWTv`~T%XaujyK{9j1LMO{#eDFL28yUIN&c6C{~M<$Q%Kj4$21^$Po>|-@``Uf`3 zI=a+K`UaG+e#=zL7v{t-H;ip;PxKue{}?+tJBWb(3(3aO+sBtJAs~<=B_!0Z=WS$E zfAwYlWppz5pQz%qn#(e?a=I&X^Kyq9i;9Z`-j`Q=+Rv`8IY_TR1^z`-Q#+I^yo`n3DV&Fd+c8rai_dx&44AxJ3u(Zr}^0)snUs9Z&oE+nw+n5@Wo0Jv;`@f{6 zorOJy%`f0zQry1uzX?G91Cjqp?o-4C`PWw=|3Z>l4(@;SqP*O9G-;5;`32UPg+(^l z#3dAvA4|)q8OmcK{7?6@q5mnS`-D$gLsNt65%?b|>z{`X;yYSJpb0e`L~}ocgM&3mv+`B#}N9kv)aha;&|)se zN>T6$C^upL!-R@>|I2u2XoL9~7}>4aSZ@=Ej>GqVN`062lwLrP#hFu7Owjl;|5Rol z{Et|>I*%T!-huT$6}hjerAhM;>Yo)mBV$hnN4Wn#y3~Jw|NSK&)c>wVm;O(mFn{l* zSl~a-q=!d@ZNT~+;U|#|0sRXRtp7f||4JVb{M|K)V`w7|w;p(_XZ10gy9>cy^c;*(R{TA-()y&{?o{eKQi4mNgl z@vp%DyUX#yKvPxanXZENE8rh7qSN!z%FCKU{rdx+|3gpEe``qH1^>G|2N~xbR&>Bi z6PY=Cez38!HZydzcQDnL@^E&s!REoi;$NIuTH5!IdrRpJ`iHFU)TrpW(%2s`|4yn( zf%6}6{}Zn%`;hagy|$oG70y4IccB0InN?F;TJou(rl6_0>GKHiFD1r7{~htOx9`LD zW&ZIfg8$k-wd=h-zi_g%{A>GsZGFohp8s~l^4@;$`q6Rkue0;hAlUyUZ=hq`r-1Jt zhyEPp27U!U(ck>D`w0CL@Q-P0xWUZD;wH|DF1=%%7v=g@e7un&OXTbyX1oy^-O8ppa{C z4fpYBN%aH3+L*szV%T5(|M?uMEFsdBnOhiF?TN6jEfN6#m4L{}-O1!889uqKy{$76 zLLZN&qM$9IB%`2!i{QUkT(|X&Oiiz1T}8UgKQlM`-~2P+KfLy|c3ayJA@zdwwp@H$Vj^gryto)4k%kOeN zclPvleyaJLk@MI8Ggy(iu;yLl@1NjR9~BfA=pRrCfGzO9?(F_1hWXcF)R`po0FgC? zc(v7~q;wTkl_Vd){7ZqA;l7-TijtPhGZh8NNA}jHbPV(!5CrCIZRTucYh?S1hwrX6 z9xmvAoz2|!uOZzak(QN}rs1}CClC~-;l&jeg5*^EJ?r&2AUxxd8zk}+vrxU9BrlHA^{)rzwiHRvKSq06V z;Ey~#*q=W<{B?M-xIXuLZg+3}_wS{h{j-a`i-pe(%}K|5Dba6&!{d_@q5mD>YG^%B zLge3Qs!GX9+<&4Xr>m}~r>rMOMNjQYhVN=`Zwmag7ap#*uWUW^?aV>bYzhUEp^F2m zq})AnciWrfiX%+KZS;xSrVIoa3O_GRSjx1U4d0TFF^SzS#{qXVr&)5C+ktxX%hwl^2I7qy!}{{iw9409bfLT3E+;cijF_q<`Ss=#YfilrV6{q=qCn7FSni z=9lD^RDnPE^XA!hUdOkBcbT;fAM*1m%F1d~rFGTRAIM9J|(Qq-*6rU;)y>h`r!=SiY%D>rBB*FprMCg_+{h_e>P5M_qNuSH!n6Xiu{Xz1^dD8ak#U* zzH&ald2tZXy!j>d>~~^ns$ayL#J3@lF=>HOKTd|dX4n0Kf})ZeD`B*nQ@;HD-}5hj zU)NZX(^y|!QuD4}=83M%eRWM483`2A{PZZT9)gS9gYw0LIF!3-ob#&D? zbA9DuXJh2biNs}&jbe`Q$C|jB+dH{YVW8rXAl*X6xkZG7N_7K=97Wks7022D0|$@j z777&^>s@YsVJ>D~CI&_eJxe}ySq=mrm_-2I|2smoqFg;AKRY^x+XCx@1}f?X#>XMp zbl}VQ^kD1I&Y>@2PCZSJ0J&F*gg*~(!BTJf5`nIflhjJ266dylP)OmKFoSq7TUEQ7RER3A3>^vMH)r|zh+U~V0 z=!0Kby%0thF~xXANlc7|bPEXw1Me1qP$`Irfp~^aa1|SqfaJQaiJpMwQxPsMW?FtB zK@J`PY7P+rUQupt7GZt?F?t?$$bW3=@vZTjYWzGrHT9*ff3jt$`}_D12zP*Q+X8Vm zI}2y)yTCtNSl=>!p zXl*2NX3IY^_)UrzpoKq`eyVz(Raje7@wqPJedW79ox8=oJs*mT%WK{h!v6p0zVtnQ z_4^2Hv83+xCj|G|=oF-s)SpSKswisNdpKEH*}0fM_XO*Pi-E1FqaDx7zvsWTwY{^! zD_kOs8)Vn7;*t|%Tp_(mah({If)e6T2sOz5?SGIDODn`D&cGnZ%_c5R&&S9pF2Kj? zD&Wo_t1$gzq`P^1_(ykd@7Q2(H{9}unkPovfp0zy6;9g3zy1H{WM<*`_u1~n`pM$q zpXH0amHCs+8Q+MoIPZj**tW3FH@hd%DgNmRVcr36f+GIj@4S;rKff!?ugLuPscyAs zJtD|;bR!ak>9`aA+HFT+8 z(7BNlKo-<<8%HZQ15-z9bUP~p2QxDZPxqH5Zk7^PrN~}8+#;aBz$3gxiG7{qIy%`6 z3`~=&H?QL1=@R~X{vz=F#n{0I!B2Brj**X*oyCp+HKSbB)bMae)7Zd7*N?95vF{yU zdOo!LX#Mgv@yEaW&o~H?W|Au{L0$4e_BG6cd~ats&{14 zn?1z+$9ph&C}L|fAR;0r(L1CftD!Upm^nrD9|u7nS)aMxuv6IGlv`L>`8o4bZmEKd z?j1f+2}K1pc;?a)I@cxtJ%8Q%Pt=S(t`p-MS(rMR8=9LKxpNx)KfixR2RoFj7&ozz zu~AScA5uKQG&jLUM!iLWe(l9gGGrVE2L8YLP<}xH4pwFnE&(n+etPa#^#At%e%Svz z+on3&hsLHl`yk)vXJ1QvOH%XuqL%i~{>Ag{-?PVX(x0we03Y}F%>2RL_VUW|+V$mZ# zuJ3JA6Fq4y|K9(#v)%NRq^;Al#qHJI&E=Ip=NE??jc@tw;oSBa{~`cI}=xD7co7_Tdpt3 zZV+JN5MyJIT_YjKBO#z5xrRx43kUxSB#J&m`FH+{(Tda1iEz*`F$w}=fz*vo`aaF= zAHA)UBi+5tlQkh*;XkTsd%v~*?CTKV>N~F#aju*ODf&yLpd}^`4@lwXwUEg`Ep%0xS$YEG>;* za%Qc5f&c|8EC%=K$nCt*o8y&&=-T zwif(}P1!zKyf|1tJv})*Jzd;CTiI_c`S<)2Q@sOIBHzA!`z9m>?t{S-g_8lx!=J0F z@-u47>kB_tlw{`>{r37-|KvgaqxA48&lM+(XI$c{&org(Ki1QeeQ*!>r|?lc)zF}{ zq;Vl7a*iirmVYypq#^^V1hD zQ2!b`8tbbWnV1<{nH$~MhW_6Tq6JJ+G71?+7EgU!eM?spIR990v%e(11p)T9ugzb& zni&8c1oF=i>qjF&!z01MAS1*@ziLLH{N(Tb=jPR$ged5g^bFvCLe#&^0?hPx*hHAP zXdP_-K7U~rab6mp=7C{fZ{XjBtSv6ggoj1M|KIBWh-8Sh>h6n;P0uZ;EBv$5+Wgha zD>Sxg^lSeV$i*5yHGEC;D=Ek--#>3}>+LNDvs6QM!EQ)YeRWOo>gvzQk$Io+h}dA? z^tQ+Vzwjim5bi{$LbVvYG8l!~f*pN^9BKc|5&- zos6Cdo&LV8oVJ9#8lH`l9$XVJ(2xYVbrn=}^|^Stq-1T>3=sU&Q|SMvN+_#nsH$AW zAjKubMZqWL;T07zR?K+?`4@M2ScIr)ImCpRXjqt?73$4jySw&Ad;0{&`v-(z001&P zJV?qrI_5mlATS{@D(Ph7l7E#gUHKvBd$wL(VNp)uOjG%vk1+qLp#=S7&F9Z;U9%v7 z_6~l#61=mwadC7$e>%T=SUU$;idcod}%*7wrZv3MrYQqLTau=zp~z6Pl=zfc{zgnWi=$E|H

gyyg>Oe9HIep@#eeRWKYsD_XCt&dOa|I$5Y*Jv=IYe}=f<GPvQq%jsx! zTkq*b`aLk1wR5R|-uh4f9B~5v->Q#cK8fL_0Z~QiZv!CzWp-Kr^z3}+Dj*8&EX^GZ zo~ycBDI1y^nc!!@{O^DAFNxp4{D)RW&RJSQ^BEf_H*Y2MKj8+et*)+1_Ba3XE*lp+ zEi*0K3pxe?ME-@Ss3_0NzxmI1?y%_T8Jga}#wNj+qrkX@f{hA%?3?5mj<3T(|Lf-I zLUTwNyjT7 z!Yho$K*tKr0-dF2?!bT3zlw#1i;jn8(P#Nu7V;k)oNf{l-XI|-eBI<}#S8i`Xn>C{ zEUm20{OUi6;(QaBkr1Dd>p#bbzD(7YH@>az zYykdOYHVIkR?)j`$jJKCJG3+QVHnZ>2m3V0LX&3}yN~mimzIyKJ|-px2YQvnyiH3= zPRxx+4+)NY6Ox34hKqtt0Q#?vtJv2tr$GNo+z0C?_(2BoSK{PU)C_ar|6^o$cAND! z(9*cQ28+(H3vCAH!TMfv5+7Goe&G27%h{ikN~{~xau>WLI1zZ z(8$>CI`U02RJ5De))v*a?k2#$#CQgu81(-iK>i0p{~bW+{iAzvb}h0_GwMxjgl|G( zSS0BGn#Xzvd%yH$ufNRC%TKQ@D)xq~ullv!uYJ3H?Y*CtJ%IliIsEmT_dr|w^ux8w z`NtJlf3wv2W&XSIJmkMER4=BiY^_#qC2#HhF4+0{d)I69Bog%h=enp6O?u@Dt|J>J z7RG%kZB7;D+dK?(0t|dCLVOTZC64nz>aoI2ITamD3|S2g2}K-0TRo9`_Vgy9y4FKI zH536CM{9dKQ*+K2&#!n`8Hv#I+T0bQwy}106yfEu6=8*o5f+w-`Re@_<{FQztf$p4 z`FA`p|3L7+-lTc^_)m<_uAHs@INKPQTbf^ti4F_Gf&PEOo0c!9tL@#x9UaGg-wz

gMuL4-Q7qx3W#(mNJxj2pa_a|H%Nn|gwoOo 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::{closure#0}", names) + + +class TestPeCxxSymbolFixture(unittest.TestCase): + """A PE whose COFF symbol table carries Itanium C++ names. + + None of the other bundled PEs has any: the Rust fixture's names are all Rust-mangled, + and the rest carry no symbol table at all. Built here rather than sampled - a small C++ + translation unit compiled for x86_64-w64-mingw32 by g++ 16.2.0 at -O1 -g. + """ + + @classmethod + def setUpClass(cls): + fixture = os.path.join(os.path.dirname(os.path.abspath(__file__)), "cxx_pe_gnu_xored") + raw = Path(fixture).read_bytes() + binary = bytes(byte ^ (index % 256) for index, byte in enumerate(raw)) + binary_info = BinaryInfo(binary) + binary_info.file_path = "" + binary_info.base_addr = 0x140000000 + provider = PeSymbolProvider(None) + provider.update(binary_info) + cls.symbols = provider.getFunctionSymbols() + + def _symbols(self): + return self.symbols + + def test_itanium_cxx_names_are_demangled(self): + names = set(self._symbols().values()) + + self.assertEqual([name for name in names if name.startswith(("_Z", "__Z"))], []) + self.assertIn("demo::Widget::Widget()", names) + self.assertIn("demo::Widget::~Widget()", names) + + def test_a_rust_name_would_be_left_to_the_rust_provider(self): + # the two providers partition the namespace: this one expands Itanium C++, and a + # name the Rust evidence gate claims is not its to rewrite + provider = RustSymbolProvider(None) + + self.assertFalse(provider._is_rust_symbol("_ZN12FileExplorerC2Ev")) + self.assertTrue(provider._is_rust_symbol("_RNvC6_123foo3bar")) + + def test_a_signature_with_arguments_is_expanded(self): + measure = [name for name in self._symbols().values() if "measure" in name] + + self.assertEqual(len(measure), 1) + self.assertIn("demo::Widget::measure(", measure[0]) + self.assertIn("double) const", measure[0]) + if __name__ == "__main__": unittest.main() diff --git a/tests/testRustSymbolProvider.py b/tests/testRustSymbolProvider.py index fc755920..9e4a5a12 100644 --- a/tests/testRustSymbolProvider.py +++ b/tests/testRustSymbolProvider.py @@ -21,13 +21,16 @@ class MockSymbol: - def __init__(self, name, value, is_function=True, demangled_name=None, section=None): + # section_idx is what a PE symbol actually carries: lief leaves Symbol.section None for + # every PE symbol, so a mock that only sets `section` does not model the real contract. + def __init__(self, name, value, is_function=True, demangled_name=None, section_idx=0): self.name = name self.value = value self.is_function = is_function self._demangled_name = demangled_name self.complex_type = type("obj", (object,), {"name": "FUNCTION"}) - self.section = section + self.section = None + self.section_idx = section_idx @property def demangled_name(self): @@ -288,7 +291,7 @@ def test_rust_symbol_provider_elf_logic(self): def test_rust_elf_symbols_skip_malformed_names(self): provider = RustSymbolProvider(None) - symbols = [MalformedNameSymbol(), MockSymbol("_ZN3foo3barE", 0x4000)] + symbols = [MalformedNameSymbol(), MockSymbol("_ZN3foo3bar17h0123456789abcdefE", 0x4000)] self.assertEqual(provider._parse_lief_symbols(symbols), {0x4000: "foo::bar"}) @@ -296,10 +299,12 @@ def test_is_rust_symbol_detection(self): """Test _is_rust_symbol correctly identifies Rust mangled symbols.""" provider = RustSymbolProvider(None) - # Valid Rust prefixes - self.assertTrue(provider._is_rust_symbol("_ZN3foo3barE")) + # a legacy Rust name carries the 17h suffix; without it the name is C++ and + # belongs to the Itanium demangler in the format providers + self.assertFalse(provider._is_rust_symbol("_ZN3foo3barE")) + self.assertTrue(provider._is_rust_symbol("_ZN3foo3bar17h0123456789abcdefE")) self.assertTrue(provider._is_rust_symbol("_RNvC6_123foo3bar")) - self.assertTrue(provider._is_rust_symbol("__ZN3foo3barE")) + self.assertTrue(provider._is_rust_symbol("__ZN3foo3bar17h0123456789abcdefE")) self.assertTrue(provider._is_rust_symbol("__RNvC6_123foo3bar")) # Invalid/too broad prefixes (bare R and ZN) should NOT be detected @@ -349,7 +354,7 @@ def test_is_symbol_provider(self): def test_pe_rust_symbols_use_base_addr_not_imagebase(self): provider = RustSymbolProvider(None) mock_binary = MockLiefBinary( - [MockSymbol("_ZN3foo3barE", 0x200, section=MockSection(0x20000000, 0x1000))], + [MockSymbol("_ZN3foo3bar17h0123456789abcdefE", 0x200, section_idx=1)], exported_functions=[MockExport("_RNvC6_123foo3bar", 0x1000)], ) mock_binary.imagebase = 0x140000000 @@ -366,7 +371,7 @@ def test_pe_rust_symbols_use_base_addr_not_imagebase(self): def test_macho_rust_path_demangles_and_adjusts_addresses(self): class FakeMacho: symbols = [ - MockSymbol("__ZN3foo3barE", 0x100001000), + MockSymbol("__ZN3foo3bar17h0123456789abcdefE", 0x100001000), MockSymbol("_main", 0x100002000), ] exported_symbols = [MockSymbol("__RNvC6_123foo3bar", 0x100003000)] @@ -464,12 +469,30 @@ def test_komplex_cpp_macho_does_not_activate_rust_provider(self): provider.update(binary_info) self.assertFalse(provider.is_active()) + def test_a_coff_symbol_that_fails_to_demangle_does_not_stop_the_scan(self): + provider = RustSymbolProvider(None) + mock_binary = MockLiefBinary( + [ + MockSymbol("_RNvC6_123foo3bar", 0x200, section_idx=1), + MockSymbol("_RNvC6_123foo3baz", 0x300, section_idx=1), + ] + ) + mock_binary.sections = [MockSection(0x20000000, 0x1000)] + + with mock.patch( + "smda.common.labelprovider.RustSymbolProvider.demangle", + side_effect=TypeNotFoundError("boom"), + ): + provider._update_pe(mock_binary, base_addr=0x400000) + + self.assertEqual(provider.getFunctionSymbols(), {}) + def test_pe_rust_symbols_skip_forwarded_exports_and_sectionless_symbols(self): provider = RustSymbolProvider(None) mock_binary = MockLiefBinary( [ - MockSymbol("_ZN3foo3barE", 0x200, section=MockSection(0x20000000, 0x1000)), - MockSymbol("_ZN3foo3bazE", 0, section=None), + MockSymbol("_ZN3foo3bar17h0123456789abcdefE", 0x200, section_idx=1), + MockSymbol("_ZN3foo3baz17h0123456789abcdefE", 0, section_idx=0), ], exported_functions=[ MockExport("_RNvC6_123foo3bar", 0x1000), @@ -530,28 +553,36 @@ def test_elf_symbol_provider_returns_raw_rust_names(self): self.assertEqual(results[0x3000], "main") -class TestPeSymbolProviderWithoutRustDemangling(unittest.TestCase): - """Tests to verify PeSymbolProvider no longer performs Rust demangling.""" +class TestPeSymbolProviderNameDemangling(unittest.TestCase): + """PeSymbolProvider expands C++ names and leaves Rust ones to RustSymbolProvider.""" - def test_pe_symbol_provider_returns_raw_rust_names(self): - """Test that PeSymbolProvider returns raw names (no Rust demangling).""" + def test_pe_symbol_provider_expands_cxx_and_leaves_rust_alone(self): + """PeSymbolProvider leaves Rust names alone; C++ names are its own to demangle.""" provider = PeSymbolProvider(None) - # Test exports - should return raw names - exp_legacy = MockExport("_ZN3foo3barE", 0x1000) + # _ZN3foo3barE carries no 17h suffix, so it is an Itanium C++ name rather + # than a legacy Rust one, and the C++ demangler is right to expand it + exp_cxx = MockExport("_ZN3foo3barE", 0x1000) exp_v0 = MockExport("_RNvC6_123foo3bar", 0x2000) exp_normal = MockExport("ExportedFunc", 0x3000) - mock_binary = MockLiefBinary([], exported_functions=[exp_legacy, exp_v0, exp_normal]) + mock_binary = MockLiefBinary([], exported_functions=[exp_cxx, exp_v0, exp_normal]) results = provider.parseExports(mock_binary) # PeSymbolProvider adds imagebase (0x400000) + address - # Raw Rust names should be preserved (no demangling) - self.assertEqual(results[0x401000], "_ZN3foo3barE") + self.assertEqual(results[0x401000], "foo::bar") self.assertEqual(results[0x402000], "_RNvC6_123foo3bar") self.assertEqual(results[0x403000], "ExportedFunc") + def test_pe_symbol_provider_leaves_a_legacy_rust_name_to_the_rust_provider(self): + provider = PeSymbolProvider(None) + hashed = MockExport("_ZN3foo3bar17h0123456789abcdefE", 0x1000) + + results = provider.parseExports(MockLiefBinary([], exported_functions=[hashed])) + + self.assertEqual(results[0x401000], "_ZN3foo3bar17h0123456789abcdefE") + class TestDemangledSpacing(unittest.TestCase): """Demangled names reach the report spelled the way rustc spells them.""" From 415deb237da5278b7b493892877fc1b07c2a3781 Mon Sep 17 00:00:00 2001 From: r0ny123 <49360849+r0ny123@users.noreply.github.com> Date: Sat, 15 Aug 2026 09:22:17 +0530 Subject: [PATCH 04/23] feat(labels): demangle MSVC decorated symbol names MSVC decoration was the one mangling scheme with no demangler here, so a binary built with the Microsoft toolchain reported ?__crt_rotate_pointer_value@@YAIIH@Z where a GCC-built one reported a signature. Add one, and let the PE provider pick a demangler by the decoration a name carries. The contract is that a name comes back either as the reference implementation spells it or exactly as it went in, never as a third spelling. That matters more than coverage does: a decorated name is still a usable identity, matchable against a symbol server or another report, while a confidently wrong expansion matches neither the decorated form nor the real one. Every construct the parser does not fully model therefore declines. A type is built as a tree and spelled around a declarator rather than rendered eagerly, because C nests the two: the name belongs inside its own type. That is what makes int (__stdcall *j)(signed char) and int (* __cdecl f(void))[2] come out right, and it is why a function pointer used as a data declaration, a return type, or the pointee of another pointer are all ordinary cases here instead of refusals. Measured against llvm-undname on the corpus LLVM tests its own demangler with -- names from llvm/test/Demangle/ms-*.test, a compiler stress suite rather than a sample of ordinary binaries -- 363 of 609 are spelled identically, 246 come back untouched, and none get a third spelling. On the MSVC names carried by real PDBs: 10 exact of 14, none wrong. The corpus ships as test data so the demangler is measured against a reference rather than against its own output. Understood: qualified and nested names, name and argument back-references, the basic and extended type set, pointers and references with their own and their pointee's qualifiers, __restrict, arrays, function pointers, declarator composition over all of those, variadic lists, tagged types, templates, data symbols with their storage class, constructors, destructors, operators and the vftable family. Declined: back-references resolved against a table holding a template render, whose numbering does not follow the obvious rule; qualified back-references, which the reference implementation does not form; local-scope and lambda names; RTTI descriptors. The grammar comes from Microsoft's documented format, with llvm-undname settling the cases the documentation leaves ambiguous -- pointer qualifier composition, where PBQAD and PAQAD both spell char *const *; the storage classes 3 and 4, which both render with no prefix; and the spacing around a declarator, where a pointer abuts a name but is separated from a nested function declarator. Both resources a hostile name can spend are bounded. Depth is capped below the point where CPython's own recursion limit could be what stops a parse, so the answer cannot depend on how deep the caller already is. Each rendered type is capped too: argument back-references can reuse an earlier rendering repeatedly, which grew a 132-byte name into half a gigabyte of output before the bound. A symbol table holds whatever bytes were written into it, so every prefix of every corpus name is fed through in the tests and none may raise, malformed shapes each have a case asserting they come back untouched, and a hypothesis target checks that an expansion is printable and bounded rather than merely a string. --- NOTICE | 13 + .../common/labelprovider/MsvcDemangler.py | 607 +++++++++++++++++ .../common/labelprovider/PeSymbolProvider.py | 20 +- tests/msvc_reference_corpus.txt | 620 ++++++++++++++++++ tests/testMsvcDemangler.py | 180 +++++ tests/testPeSymbolProvider.py | 28 + tests/test_fuzz_msvc_demangler.py | 57 ++ 7 files changed, 1523 insertions(+), 2 deletions(-) create mode 100644 src/smda/common/labelprovider/MsvcDemangler.py create mode 100644 tests/msvc_reference_corpus.txt create mode 100644 tests/testMsvcDemangler.py create mode 100644 tests/test_fuzz_msvc_demangler.py diff --git a/NOTICE b/NOTICE index c781b75a..c7b43189 100644 --- a/NOTICE +++ b/NOTICE @@ -50,6 +50,19 @@ licensed under the Apache License, Version 2.0, available at Ghidra's own V0 demangler is a port of the rustc-demangle crate (https://github.com/rust-lang/rustc-demangle), dual-licensed Apache-2.0 OR MIT. +================================================================================ +LLVM demangler test corpus +-------------------------------------------------------------------------------- +tests/msvc_reference_corpus.txt lists MSVC mangled symbol names taken from the +LLVM Project's demangler tests (llvm/test/Demangle/ms-*.test), together with the +spelling llvm-undname produces for each. It is used to measure this project's +MSVC demangler against a reference implementation. + +The LLVM Project is licensed under the Apache License, Version 2.0, with the +LLVM exception. The license is available at + + https://llvm.org/LICENSE.txt + ================================================================================ Tarjan's algorithm -------------------------------------------------------------------------------- diff --git a/src/smda/common/labelprovider/MsvcDemangler.py b/src/smda/common/labelprovider/MsvcDemangler.py new file mode 100644 index 00000000..81d7ad7d --- /dev/null +++ b/src/smda/common/labelprovider/MsvcDemangler.py @@ -0,0 +1,607 @@ +"""Demangling for MSVC decorated symbol names.""" + +import string +from functools import lru_cache + +_BASIC_TYPES = { + "X": "void", + "D": "char", + "C": "signed char", + "E": "unsigned char", + "F": "short", + "G": "unsigned short", + "H": "int", + "I": "unsigned int", + "J": "long", + "K": "unsigned long", + "M": "float", + "N": "double", + "O": "long double", +} +_EXTENDED_TYPES = { + "D": "__int8", + "E": "unsigned __int8", + "F": "__int16", + "G": "unsigned __int16", + "H": "__int32", + "I": "unsigned __int32", + "J": "__int64", + "K": "unsigned __int64", + "L": "__int128", + "M": "unsigned __int128", + "N": "bool", + "Q": "char8_t", + "S": "char16_t", + "U": "char32_t", + "W": "wchar_t", +} +_TAGGED_TYPES = {"T": "union", "U": "struct", "V": "class", "W": "enum"} +_CALLING_CONVENTIONS = { + "A": "__cdecl", + "B": "__cdecl", + "C": "__pascal", + "D": "__pascal", + "E": "__thiscall", + "F": "__thiscall", + "G": "__stdcall", + "H": "__stdcall", + "I": "__fastcall", + "J": "__fastcall", + "M": "__clrcall", + "N": "__clrcall", + "O": "__eabi", + "P": "__eabi", + "Q": "__vectorcall", +} +_FUNCTION_ACCESS = { + "A": ("private", False, False), + "B": ("private", False, False), + "C": ("private", True, False), + "D": ("private", True, False), + "E": ("private", False, True), + "F": ("private", False, True), + "I": ("protected", False, False), + "J": ("protected", False, False), + "K": ("protected", True, False), + "L": ("protected", True, False), + "M": ("protected", False, True), + "N": ("protected", False, True), + "Q": ("public", False, False), + "R": ("public", False, False), + "S": ("public", True, False), + "T": ("public", True, False), + "U": ("public", False, True), + "V": ("public", False, True), +} +_OPERATORS = { + "2": "operator new", + "3": "operator delete", + "4": "operator=", + "5": "operator>>", + "6": "operator<<", + "7": "operator!", + "8": "operator==", + "9": "operator!=", + "A": "operator[]", + "C": "operator->", + "D": "operator*", + "E": "operator++", + "F": "operator--", + "G": "operator-", + "H": "operator+", + "I": "operator&", + "J": "operator->*", + "K": "operator/", + "L": "operator%", + "M": "operator<", + "N": "operator<=", + "O": "operator>", + "P": "operator>=", + "Q": "operator,", + "R": "operator()", + "S": "operator~", + "T": "operator^", + "U": "operator|", + "V": "operator&&", + "W": "operator||", + "X": "operator*=", + "Y": "operator+=", + "Z": "operator-=", +} +_EXTENDED_OPERATORS = { + "0": "operator/=", + "1": "operator%=", + "2": "operator>>=", + "3": "operator<<=", + "4": "operator&=", + "5": "operator|=", + "6": "operator^=", + "7": "`vftable'", + "8": "`vbtable'", + "9": "`vcall'", + "A": "`typeof'", + "B": "`local static guard'", + "D": "`vbase dtor'", + "E": "`vector deleting dtor'", + "F": "`default ctor closure'", + "G": "`scalar deleting dtor'", + "H": "`vector ctor iterator'", + "I": "`vector dtor iterator'", + "J": "`vector vbase ctor iterator'", + "K": "`virtual displacement map'", + "L": "`eh vector ctor iterator'", + "M": "`eh vector dtor iterator'", + "N": "`eh vector vbase ctor iterator'", + "O": "`copy ctor closure'", + "S": "`local vftable'", + "T": "`local vftable ctor closure'", + "U": "operator new[]", + "V": "operator delete[]", + "X": "`placement delete closure'", + "Y": "`placement delete[] closure'", +} +_DATA_ACCESS = { + "0": "private: static ", + "1": "protected: static ", + "2": "public: static ", + "3": "", + "4": "", +} +_POINTER_KINDS = {"P": (), "Q": ("const",), "R": ("volatile",), "S": ("const", "volatile")} +_CV_QUALS = {"A": (), "B": ("const",), "C": ("volatile",), "D": ("const", "volatile")} +_CV = {"A": "", "B": " const", "C": " volatile", "D": " const volatile"} + + +def _base(text): + return ("base", text) + + +def _indirection(symbol, quals, inner): + return ("ind", symbol, quals, inner) + + +def _array(dims, inner): + return ("array", dims, inner) + + +def _function(convention, params, returns): + return ("func", convention, params, returns) + + +def _render(node, declarator=""): + """Spell a type around a declarator, the way C nests one inside the other. + + A pointer or array binds to the declarator built so far, and a name therefore ends up + *inside* its type: `int (*j)[2]`, not `int (*)[2] j`. + """ + kind = node[0] + if kind == "base": + if not declarator: + return node[1] + return node[1] + ("" if declarator.startswith("[") else " ") + declarator + if kind == "ind": + token = node[1] + " ".join(node[2]) + nested_function = "(" in declarator and not declarator.startswith(("*", "&")) + separator = " " if declarator and (node[2] or nested_function) else "" + return _render(node[3], token + separator + declarator) + if kind == "array": + if declarator.startswith(("*", "&")): + declarator = f"({declarator})" + return _render(node[2], declarator + node[1]) + convention, params, returns = node[1], node[2], node[3] + if declarator.startswith(("*", "&")): + declarator = f"({convention} {declarator})" + else: + declarator = f"{convention} {declarator}" if declarator else convention + return _render(returns, f"{declarator}({params})") + + +def _merge(left, right): + merged = [qual for qual in ("const", "volatile") if qual in left or qual in right] + return tuple(merged) + + +def _apply_quals(node, quals): + """Qualify a named type, as a pointee qualifier or a $$C wrapper does. + + Only ever reached with a base node: an indirection merges its qualifiers as it is built, + and a back-reference declines rather than accept one. + """ + return _base(f"{node[1]} {' '.join(quals)}") if quals else node + + +class _Structor: + """A constructor or destructor: its spelling comes from the class it belongs to.""" + + def __init__(self, is_destructor): + self.is_destructor = is_destructor + + +class _Bail(Exception): + """The name is not one this demangler fully understands.""" + + +class _Demangler: + """A cursor over one decorated name. + + MAX_DEPTH bounds the mutually recursive name and type parser. A level costs several + interpreter frames here, so the bound is set low enough that CPython's own recursion + limit is never the thing that stops a parse - otherwise the answer would depend on how + deep the caller already is. max_render bounds the rendered result, which back-reference + reuse can otherwise grow multiplicatively. + """ + + MAX_DEPTH = 64 + + def __init__(self, mangled): + self.text = mangled + self.pos = 0 + self.name_backrefs = [] + self.arg_backrefs = [] + self.simple = True + self.template_depth = 0 + self.templated_table = False + self.member_cv = "" + self.depth = 0 + self.max_render = 8 * len(mangled) + 256 + + def eof(self): + return self.pos >= len(self.text) + + def peek(self): + if self.eof(): + raise _Bail + return self.text[self.pos] + + def take(self): + char = self.peek() + self.pos += 1 + return char + + def eat(self, char): + if not self.eof() and self.text[self.pos] == char: + self.pos += 1 + return True + return False + + def expect(self, char): + if not self.eat(char): + raise _Bail + + def identifier(self): + end = self.text.find("@", self.pos) + if end < 0: + raise _Bail + name = self.text[self.pos : end] + if not name: + raise _Bail + self.pos = end + 1 + return name + + def templateArguments(self): + """Template arguments, in their own back-reference scopes. + + How those scopes interact with the enclosing name table is not modelled, so a name + back-reference inside them declines instead of risking a wrong name. + """ + args = [] + saved_names, saved_args = self.name_backrefs, self.arg_backrefs + self.name_backrefs, self.arg_backrefs = [], [] + self.template_depth += 1 + try: + while not self.eat("@"): + if self.eof(): + raise _Bail + args.append(self.rendered(self.type())) + finally: + self.template_depth -= 1 + self.name_backrefs, self.arg_backrefs = saved_names, saved_args + return args + + def nameFragment(self, is_leading): + char = self.peek() + if char in string.digits: + if self.template_depth or self.templated_table: + raise _Bail + index = int(self.take()) + if index >= len(self.name_backrefs): + raise _Bail + return self.name_backrefs[index] + if char == "?": + self.take() + if self.eat("$"): + base = self.identifier() + if self.eof() or self.peek() == "@": + raise _Bail + args = self.templateArguments() + rendered = f"{base}<{', '.join(args)}>" + self.name_backrefs.append(rendered) + self.templated_table = True + return rendered + if is_leading: + return self.operatorName() + raise _Bail + name = self.identifier() + self.name_backrefs.append(name) + return name + + def operatorName(self): + if self.eat("_"): + code = self.take() + name = _EXTENDED_OPERATORS.get(code) + if name is None: + raise _Bail + return name + code = self.take() + if code in ("0", "1"): + return _Structor(code == "1") + name = _OPERATORS.get(code) + if name is None: + raise _Bail + return name + + def qualifiedName(self): + """Count a name level against the depth bound; type() is what enforces it.""" + self.depth += 1 + try: + return self.qualifiedNameBody() + finally: + self.depth -= 1 + + def qualifiedNameBody(self): + first = self.nameFragment(True) + scopes = [] + while True: + if self.eat("@"): + break + if self.eof(): + raise _Bail + scopes.append(self.nameFragment(False)) + scopes.reverse() + if isinstance(first, _Structor): + if not scopes: + raise _Bail + klass = scopes[-1] + first = "~" + klass if first.is_destructor else klass + return "::".join(scopes + [first]), True + return "::".join(scopes + [first]), False + + def type(self, quals=()): + self.depth += 1 + if self.depth > self.MAX_DEPTH: + raise _Bail + try: + return self.typeBody(quals) + finally: + self.depth -= 1 + + def typeBody(self, quals): + """One type, qualified by `quals`. + + A digit is a back-reference standing for a whole argument type. The reference + implementation rejects a qualifier in front of one, so a qualified back-reference + declines rather than inventing a spelling. + """ + char = self.take() + if char in _BASIC_TYPES: + return _apply_quals(_base(_BASIC_TYPES[char]), quals) + if char == "_": + name = _EXTENDED_TYPES.get(self.take()) + if name is None: + raise _Bail + self.simple = False + return _apply_quals(_base(name), quals) + if char in _TAGGED_TYPES: + kind = _TAGGED_TYPES[char] + if kind == "enum": + self.expect("4") + name, _ = self.qualifiedName() + self.simple = False + return _apply_quals(_base(f"{kind} {name}"), quals) + if char == "Y": + return self.arrayType(quals) + if char in _POINTER_KINDS: + return self.indirection(_merge(_POINTER_KINDS[char], quals), "*") + if char in ("A", "B"): + own = ("volatile",) if char == "B" else () + return self.indirection(_merge(own, quals), "&") + if char == "$": + return self.dollarType(quals) + if char in string.digits: + if quals: + raise _Bail + index = int(char) + if index >= len(self.arg_backrefs): + raise _Bail + return self.arg_backrefs[index] + raise _Bail + + def rendered(self, node, declarator=""): + text = _render(node, declarator) + if len(text) > self.max_render: + raise _Bail + return text + + def dimension(self): + char = self.take() + if char in string.digits: + return int(char) + 1 + raise _Bail + + def arrayType(self, quals): + count = self.dimension() + dims = "".join(f"[{self.dimension()}]" for _ in range(count)) + element = self.type(quals) + self.simple = False + return _array(dims, element) + + def dollarType(self, quals): + if not self.eat("$"): + raise _Bail + kind = self.take() + if kind == "Q": + return self.indirection(quals, "&&") + if kind == "C": + extra = _CV_QUALS.get(self.take()) + if extra is None: + raise _Bail + return self.type(_merge(extra, quals)) + if kind == "T": + self.simple = False + return _apply_quals(_base("std::nullptr_t"), quals) + if kind == "A": + return self.functionTypeArgument() + raise _Bail + + def indirection(self, own_quals, token): + """A pointer or reference: `token` plus its own quals, over a qualified pointee.""" + self.eat("E") + if self.eat("I"): + own_quals = own_quals + ("__restrict",) + if self.eat("6"): + convention = _CALLING_CONVENTIONS.get(self.take()) + if convention is None: + raise _Bail + returns = self.type() + params = self.parameters() + self.expect("Z") + self.simple = False + return _indirection(token, own_quals, _function(convention, params, returns)) + pointee_quals = _CV_QUALS.get(self.take()) + if pointee_quals is None: + raise _Bail + pointee = self.type(pointee_quals) + self.simple = False + return _indirection(token, own_quals, pointee) + + def functionTypeArgument(self): + self.simple = False + self.expect("6") + convention = _CALLING_CONVENTIONS.get(self.take()) + if convention is None: + raise _Bail + returns = self.type() + params = self.parameters() + self.expect("Z") + return _function(convention, params, returns) + + def parameters(self): + """A parameter list, recording each composite parameter for later back-references. + + A trailing Z before the terminator marks a variadic list. + """ + if self.eat("X"): + return "void" + params = [] + while True: + if self.eof(): + raise _Bail + if self.eat("@"): + break + if self.peek() == "Z": + if not params: + raise _Bail + self.take() + params.append("...") + break + if self.peek() in string.digits: + index = int(self.take()) + if index >= len(self.arg_backrefs): + raise _Bail + params.append(self.rendered(self.arg_backrefs[index])) + continue + self.simple = True + node = self.type() + if not self.simple and len(self.arg_backrefs) < 10: + self.arg_backrefs.append(node) + params.append(self.rendered(node)) + return ", ".join(params) + + def parse(self): + self.expect("?") + name, has_no_return_type = self.qualifiedName() + if self.eof(): + raise _Bail + char = self.peek() + if char == "6": + self.take() + qualifier = _CV.get(self.take()) + if qualifier is None: + raise _Bail + self.expect("@") + if not self.eof(): + raise _Bail + return f"{qualifier.strip()} {name}".strip() + if char in _DATA_ACCESS: + self.take() + self.simple = True + declared = self.type() + trailing = self.take() + if trailing not in _CV_QUALS or not self.eof(): + raise _Bail + if self.simple: + declared = _apply_quals(declared, _CV_QUALS[trailing]) + return f"{_DATA_ACCESS[char]}{self.rendered(declared, name)}" + return self.function(name, has_no_return_type) + + def function(self, name, has_no_return_type): + access_char = self.take() + if access_char == "Y": + access, is_static, is_virtual = None, False, False + else: + entry = _FUNCTION_ACCESS.get(access_char) + if entry is None: + raise _Bail + access, is_static, is_virtual = entry + if not is_static: + self.eat("E") + if _CV.get(self.peek()) is None: + raise _Bail + self.member_cv = _CV[self.take()] + else: + self.member_cv = "" + convention = _CALLING_CONVENTIONS.get(self.take()) + if convention is None: + raise _Bail + if has_no_return_type: + self.expect("@") + returns = None + else: + returns = self.type() + params = self.parameters() + if not self.eof(): + self.expect("Z") + if not self.eof(): + raise _Bail + pieces = [] + if access: + pieces.append(f"{access}: ") + if is_static: + pieces.append("static ") + if is_virtual: + pieces.append("virtual ") + if returns is None: + pieces.append(f"{convention} {name}({params})") + else: + pieces.append(self.rendered(_function(convention, params, returns), name)) + if access and not is_static: + pieces.append(self.member_cv) + return "".join(pieces) + + +@lru_cache(maxsize=4096) +def demangle_msvc_symbol(name): + """Return a readable C++ name, or the original when it is not fully understood. + + A name carrying a NUL is refused outright: a decorated name is read from a NUL-terminated + string and cannot contain one, and an expansion holding a control character would travel + into the report as a symbol name. + """ + if not name or not name.startswith("?"): + return name + if "\x00" in name: + return name + try: + return _Demangler(name).parse() + except (_Bail, RecursionError): + return name diff --git a/src/smda/common/labelprovider/PeSymbolProvider.py b/src/smda/common/labelprovider/PeSymbolProvider.py index 2dabe9d2..d53205e5 100644 --- a/src/smda/common/labelprovider/PeSymbolProvider.py +++ b/src/smda/common/labelprovider/PeSymbolProvider.py @@ -8,11 +8,27 @@ from .AbstractLabelProvider import AbstractLabelProvider from .import_parsers import parse_pe_delay_imports, parse_pe_imports, resolve_pe_base_addr from .ItaniumDemangler import demangle_itanium_symbol +from .MsvcDemangler import demangle_msvc_symbol lief.logging.disable() LOGGER = logging.getLogger(__name__) +def _readable_name(name): + """Expand a decorated PE symbol name, whichever compiler decorated it. + + The MSVC arm keys on the leading "?" rather than on ItaniumDemangler's + is_msvc_cpp_symbol, whose job is language detection rather than dispatch: it wants a + class-qualified shape, so it turns away the global operator forms ("??2@YAPAXI@Z" and + friends) that this demangler reads perfectly well - 9 of the 355 names it expands in the + reference corpus. Letting the demangler itself decide costs nothing, because a name it + cannot read comes back unchanged. + """ + if name.startswith("?"): + return demangle_msvc_symbol(name) + return demangle_itanium_symbol(name) + + class PeSymbolProvider(AbstractLabelProvider): """Minimal resolver for PE symbols""" @@ -68,7 +84,7 @@ def parseExports(self, lief_binary, base_addr=None): # UnicodeDecodeError: 'utf-32-le' codec can't decode bytes in position 0-3: code point not in range(0x110000) function_name = function.name if function_name and all(ord(c) in range(0x20, 0x7F) for c in function_name): - function_symbols[active_base + function.address] = demangle_itanium_symbol(function_name) + function_symbols[active_base + function.address] = _readable_name(function_name) return function_symbols def parseSymbols(self, lief_binary, base_addr=None): @@ -95,7 +111,7 @@ def parseSymbols(self, lief_binary, base_addr=None): if function_name and all(ord(c) in range(0x20, 0x7F) for c in function_name): function_offset = active_base + sections[section_idx - 1].virtual_address + symbol.value if function_offset not in function_symbols: - function_symbols[function_offset] = demangle_itanium_symbol(function_name) + function_symbols[function_offset] = _readable_name(function_name) if num_candidates and not function_symbols: # the previous failure mode was silent: a whole corpus could be built unnamed # without anything complaining, so say so rather than contributing nothing diff --git a/tests/msvc_reference_corpus.txt b/tests/msvc_reference_corpus.txt new file mode 100644 index 00000000..d5ca9a20 --- /dev/null +++ b/tests/msvc_reference_corpus.txt @@ -0,0 +1,620 @@ +# MSVC mangled names, one per line, followed by a tab and the spelling that +# llvm-undname 22.1.7 produces for it. +# +# The mangled names are the corpus from LLVM's demangler tests +# (llvm/test/Demangle/ms-*.test), which LLVM publishes under the Apache License 2.0 +# with the LLVM exception; the expected column was produced by running llvm-undname +# over them. Duplicates present in the upstream files were dropped. +# +# They are kept here so the demangler can be measured against a reference rather than +# against its own output: every name must come back either exactly as listed, or +# unchanged, and never as a third spelling. +?foo@@YAXI@Z void __cdecl foo(unsigned int) +?foo@@YAXN@Z void __cdecl foo(double) +?foo_pad@@YAXPAD@Z void __cdecl foo_pad(char *) +?foo_pad@@YAXPEAD@Z void __cdecl foo_pad(char *) +?foo_pbd@@YAXPBD@Z void __cdecl foo_pbd(char const *) +?foo_pbd@@YAXPEBD@Z void __cdecl foo_pbd(char const *) +?foo_pcd@@YAXPCD@Z void __cdecl foo_pcd(char volatile *) +?foo_pcd@@YAXPECD@Z void __cdecl foo_pcd(char volatile *) +?foo_qad@@YAXQAD@Z void __cdecl foo_qad(char *const) +?foo_qad@@YAXQEAD@Z void __cdecl foo_qad(char *const) +?foo_rad@@YAXRAD@Z void __cdecl foo_rad(char *volatile) +?foo_rad@@YAXREAD@Z void __cdecl foo_rad(char *volatile) +?foo_sad@@YAXSAD@Z void __cdecl foo_sad(char *const volatile) +?foo_sad@@YAXSEAD@Z void __cdecl foo_sad(char *const volatile) +?foo_piad@@YAXPIAD@Z void __cdecl foo_piad(char *__restrict) +?foo_piad@@YAXPEIAD@Z void __cdecl foo_piad(char *__restrict) +?foo_qiad@@YAXQIAD@Z void __cdecl foo_qiad(char *const __restrict) +?foo_qiad@@YAXQEIAD@Z void __cdecl foo_qiad(char *const __restrict) +?foo_riad@@YAXRIAD@Z void __cdecl foo_riad(char *volatile __restrict) +?foo_riad@@YAXREIAD@Z void __cdecl foo_riad(char *volatile __restrict) +?foo_siad@@YAXSIAD@Z void __cdecl foo_siad(char *const volatile __restrict) +?foo_siad@@YAXSEIAD@Z void __cdecl foo_siad(char *const volatile __restrict) +?foo_papad@@YAXPAPAD@Z void __cdecl foo_papad(char **) +?foo_papad@@YAXPEAPEAD@Z void __cdecl foo_papad(char **) +?foo_papbd@@YAXPAPBD@Z void __cdecl foo_papbd(char const **) +?foo_papbd@@YAXPEAPEBD@Z void __cdecl foo_papbd(char const **) +?foo_papcd@@YAXPAPCD@Z void __cdecl foo_papcd(char volatile **) +?foo_papcd@@YAXPEAPECD@Z void __cdecl foo_papcd(char volatile **) +?foo_pbqad@@YAXPBQAD@Z void __cdecl foo_pbqad(char *const *) +?foo_pbqad@@YAXPEBQEAD@Z void __cdecl foo_pbqad(char *const *) +?foo_pcrad@@YAXPCRAD@Z void __cdecl foo_pcrad(char *volatile *) +?foo_pcrad@@YAXPECREAD@Z void __cdecl foo_pcrad(char *volatile *) +?foo_qapad@@YAXQAPAD@Z void __cdecl foo_qapad(char **const) +?foo_qapad@@YAXQEAPEAD@Z void __cdecl foo_qapad(char **const) +?foo_rapad@@YAXRAPAD@Z void __cdecl foo_rapad(char **volatile) +?foo_rapad@@YAXREAPEAD@Z void __cdecl foo_rapad(char **volatile) +?foo_pbqbd@@YAXPBQBD@Z void __cdecl foo_pbqbd(char const *const *) +?foo_pbqbd@@YAXPEBQEBD@Z void __cdecl foo_pbqbd(char const *const *) +?foo_pbqcd@@YAXPBQCD@Z void __cdecl foo_pbqcd(char volatile *const *) +?foo_pbqcd@@YAXPEBQECD@Z void __cdecl foo_pbqcd(char volatile *const *) +?foo_pcrbd@@YAXPCRBD@Z void __cdecl foo_pcrbd(char const *volatile *) +?foo_pcrbd@@YAXPECREBD@Z void __cdecl foo_pcrbd(char const *volatile *) +?foo_pcrcd@@YAXPCRCD@Z void __cdecl foo_pcrcd(char volatile *volatile *) +?foo_pcrcd@@YAXPECRECD@Z void __cdecl foo_pcrcd(char volatile *volatile *) +?foo_aad@@YAXAAD@Z void __cdecl foo_aad(char &) +?foo_aad@@YAXAEAD@Z void __cdecl foo_aad(char &) +?foo_abd@@YAXABD@Z void __cdecl foo_abd(char const &) +?foo_abd@@YAXAEBD@Z void __cdecl foo_abd(char const &) +?foo_aapad@@YAXAAPAD@Z void __cdecl foo_aapad(char *&) +?foo_aapad@@YAXAEAPEAD@Z void __cdecl foo_aapad(char *&) +?foo_aapbd@@YAXAAPBD@Z void __cdecl foo_aapbd(char const *&) +?foo_aapbd@@YAXAEAPEBD@Z void __cdecl foo_aapbd(char const *&) +?foo_abqad@@YAXABQAD@Z void __cdecl foo_abqad(char *const &) +?foo_abqad@@YAXAEBQEAD@Z void __cdecl foo_abqad(char *const &) +?foo_abqbd@@YAXABQBD@Z void __cdecl foo_abqbd(char const *const &) +?foo_abqbd@@YAXAEBQEBD@Z void __cdecl foo_abqbd(char const *const &) +?foo_aay144h@@YAXAAY144H@Z void __cdecl foo_aay144h(int (&)[5][5]) +?foo_aay144h@@YAXAEAY144H@Z void __cdecl foo_aay144h(int (&)[5][5]) +?foo_aay144cbh@@YAXAAY144$$CBH@Z void __cdecl foo_aay144cbh(int const (&)[5][5]) +?foo_aay144cbh@@YAXAEAY144$$CBH@Z void __cdecl foo_aay144cbh(int const (&)[5][5]) +?foo_qay144h@@YAX$$QAY144H@Z void __cdecl foo_qay144h(int (&&)[5][5]) +?foo_qay144h@@YAX$$QEAY144H@Z void __cdecl foo_qay144h(int (&&)[5][5]) +?foo_qay144cbh@@YAX$$QAY144$$CBH@Z void __cdecl foo_qay144cbh(int const (&&)[5][5]) +?foo_qay144cbh@@YAX$$QEAY144$$CBH@Z void __cdecl foo_qay144cbh(int const (&&)[5][5]) +?foo_p6ahxz@@YAXP6AHXZ@Z void __cdecl foo_p6ahxz(int (__cdecl *)(void)) +?foo_a6ahxz@@YAXA6AHXZ@Z void __cdecl foo_a6ahxz(int (__cdecl &)(void)) +?foo_q6ahxz@@YAX$$Q6AHXZ@Z void __cdecl foo_q6ahxz(int (__cdecl &&)(void)) +?foo_qay04h@@YAXQAY04H@Z void __cdecl foo_qay04h(int (*const)[5]) +?foo_qay04h@@YAXQEAY04H@Z void __cdecl foo_qay04h(int (*const)[5]) +?foo_qay04cbh@@YAXQAY04$$CBH@Z void __cdecl foo_qay04cbh(int const (*const)[5]) +?foo_qay04cbh@@YAXQEAY04$$CBH@Z void __cdecl foo_qay04cbh(int const (*const)[5]) +?foo@@YAXPAY02N@Z void __cdecl foo(double (*)[3]) +?foo@@YAXPEAY02N@Z void __cdecl foo(double (*)[3]) +?foo@@YAXQAN@Z void __cdecl foo(double *const) +?foo@@YAXQEAN@Z void __cdecl foo(double *const) +?foo_const@@YAXQBN@Z void __cdecl foo_const(double const *const) +?foo_const@@YAXQEBN@Z void __cdecl foo_const(double const *const) +?foo_volatile@@YAXQCN@Z void __cdecl foo_volatile(double volatile *const) +?foo_volatile@@YAXQECN@Z void __cdecl foo_volatile(double volatile *const) +?foo@@YAXPAY02NQBNN@Z void __cdecl foo(double (*)[3], double const *const, double) +?foo@@YAXPEAY02NQEBNN@Z void __cdecl foo(double (*)[3], double const *const, double) +?foo_fnptrconst@@YAXP6AXQAH@Z@Z void __cdecl foo_fnptrconst(void (__cdecl *)(int *const)) +?foo_fnptrconst@@YAXP6AXQEAH@Z@Z void __cdecl foo_fnptrconst(void (__cdecl *)(int *const)) +?foo_fnptrarray@@YAXP6AXQAH@Z@Z void __cdecl foo_fnptrarray(void (__cdecl *)(int *const)) +?foo_fnptrarray@@YAXP6AXQEAH@Z@Z void __cdecl foo_fnptrarray(void (__cdecl *)(int *const)) +?foo_fnptrbackref1@@YAXP6AXQAH@Z1@Z void __cdecl foo_fnptrbackref1(void (__cdecl *)(int *const), void (__cdecl *)(int *const)) +?foo_fnptrbackref1@@YAXP6AXQEAH@Z1@Z void __cdecl foo_fnptrbackref1(void (__cdecl *)(int *const), void (__cdecl *)(int *const)) +?foo_fnptrbackref2@@YAXP6AXQAH@Z1@Z void __cdecl foo_fnptrbackref2(void (__cdecl *)(int *const), void (__cdecl *)(int *const)) +?foo_fnptrbackref2@@YAXP6AXQEAH@Z1@Z void __cdecl foo_fnptrbackref2(void (__cdecl *)(int *const), void (__cdecl *)(int *const)) +?foo_fnptrbackref3@@YAXP6AXQAH@Z1@Z void __cdecl foo_fnptrbackref3(void (__cdecl *)(int *const), void (__cdecl *)(int *const)) +?foo_fnptrbackref3@@YAXP6AXQEAH@Z1@Z void __cdecl foo_fnptrbackref3(void (__cdecl *)(int *const), void (__cdecl *)(int *const)) +?foo_fnptrbackref4@@YAXP6AXPAH@Z1@Z void __cdecl foo_fnptrbackref4(void (__cdecl *)(int *), void (__cdecl *)(int *)) +?foo_fnptrbackref4@@YAXP6AXPEAH@Z1@Z void __cdecl foo_fnptrbackref4(void (__cdecl *)(int *), void (__cdecl *)(int *)) +?ret_fnptrarray@@YAP6AXQAH@ZXZ void (__cdecl * __cdecl ret_fnptrarray(void))(int *const) +?ret_fnptrarray@@YAP6AXQEAH@ZXZ void (__cdecl * __cdecl ret_fnptrarray(void))(int *const) +?mangle_no_backref0@@YAXQAHPAH@Z void __cdecl mangle_no_backref0(int *const, int *) +?mangle_no_backref0@@YAXQEAHPEAH@Z void __cdecl mangle_no_backref0(int *const, int *) +?mangle_no_backref1@@YAXQAHQAH@Z void __cdecl mangle_no_backref1(int *const, int *const) +?mangle_no_backref1@@YAXQEAHQEAH@Z void __cdecl mangle_no_backref1(int *const, int *const) +?mangle_no_backref2@@YAXP6AXXZP6AXXZ@Z void __cdecl mangle_no_backref2(void (__cdecl *)(void), void (__cdecl *)(void)) +?mangle_yes_backref0@@YAXQAH0@Z void __cdecl mangle_yes_backref0(int *const, int *const) +?mangle_yes_backref0@@YAXQEAH0@Z void __cdecl mangle_yes_backref0(int *const, int *const) +?mangle_yes_backref1@@YAXQAH0@Z void __cdecl mangle_yes_backref1(int *const, int *const) +?mangle_yes_backref1@@YAXQEAH0@Z void __cdecl mangle_yes_backref1(int *const, int *const) +?mangle_yes_backref2@@YAXQBQ6AXXZ0@Z void __cdecl mangle_yes_backref2(void (__cdecl *const *const)(void), void (__cdecl *const *const)(void)) +?mangle_yes_backref2@@YAXQEBQ6AXXZ0@Z void __cdecl mangle_yes_backref2(void (__cdecl *const *const)(void), void (__cdecl *const *const)(void)) +?mangle_yes_backref3@@YAXQAP6AXXZ0@Z void __cdecl mangle_yes_backref3(void (__cdecl **const)(void), void (__cdecl **const)(void)) +?mangle_yes_backref3@@YAXQEAP6AXXZ0@Z void __cdecl mangle_yes_backref3(void (__cdecl **const)(void), void (__cdecl **const)(void)) +?mangle_yes_backref4@@YAXQIAH0@Z void __cdecl mangle_yes_backref4(int *const __restrict, int *const __restrict) +?mangle_yes_backref4@@YAXQEIAH0@Z void __cdecl mangle_yes_backref4(int *const __restrict, int *const __restrict) +?pr23325@@YAXQBUS@@0@Z void __cdecl pr23325(struct S const *const, struct S const *const) +?pr23325@@YAXQEBUS@@0@Z void __cdecl pr23325(struct S const *const, struct S const *const) +?f1@@YAXPBD0@Z void __cdecl f1(char const *, char const *) +?f2@@YAXPBDPAD@Z void __cdecl f2(char const *, char *) +?f3@@YAXHPBD0@Z void __cdecl f3(int, char const *, char const *) +?f4@@YAPBDPBD0@Z char const * __cdecl f4(char const *, char const *) +?f5@@YAXPBDIDPBX0I@Z void __cdecl f5(char const *, unsigned int, char, void const *, char const *, unsigned int) +?f6@@YAX_N0@Z void __cdecl f6(bool, bool) +?f7@@YAXHPAHH0_N1PA_N@Z void __cdecl f7(int, int *, int, int *, bool, bool, bool *) +?g1@@YAXUS@@@Z void __cdecl g1(struct S) +?g2@@YAXUS@@0@Z void __cdecl g2(struct S, struct S) +?g3@@YAXUS@@0PAU1@1@Z void __cdecl g3(struct S, struct S, struct S *, struct S *) +?g4@@YAXPBDPAUS@@01@Z void __cdecl g4(char const *, struct S *, char const *, struct S *) +?mbb@S@@QAEX_N0@Z public: void __thiscall S::mbb(bool, bool) +?h1@@YAXPBD0P6AXXZ1@Z void __cdecl h1(char const *, char const *, void (__cdecl *)(void), void (__cdecl *)(void)) +?h2@@YAXP6AXPAX@Z0@Z void __cdecl h2(void (__cdecl *)(void *), void *) +?h3@@YAP6APAHPAH0@ZP6APAH00@Z10@Z int * (__cdecl * __cdecl h3(int * (__cdecl *)(int *, int *), int * (__cdecl *)(int *, int *), int *))(int *, int *) +?foo@0@YAXXZ void __cdecl foo::foo(void) +??$?HH@S@@QEAAAEAU0@H@Z public: struct S & __cdecl S::operator+(int) +?foo_abbb@@YAXV?$A@V?$B@D@@V1@V1@@@@Z void __cdecl foo_abbb(class A, class B, class B>) +?foo_abb@@YAXV?$A@DV?$B@D@@V1@@@@Z void __cdecl foo_abb(class A, class B>) +?foo_abc@@YAXV?$A@DV?$B@D@@V?$C@D@@@@@Z void __cdecl foo_abc(class A, class C>) +?foo_bt@@YAX_NV?$B@$$A6A_N_N@Z@@@Z void __cdecl foo_bt(bool, class B) +?foo_abbb@@YAXV?$A@V?$B@D@N@@V12@V12@@N@@@Z void __cdecl foo_abbb(class N::A, class N::B, class N::B>) +?foo_abb@@YAXV?$A@DV?$B@D@N@@V12@@N@@@Z void __cdecl foo_abb(class N::A, class N::B>) +?foo_abc@@YAXV?$A@DV?$B@D@N@@V?$C@D@2@@N@@@Z void __cdecl foo_abc(class N::A, class N::C>) +?abc_foo@@YA?AV?$A@DV?$B@D@N@@V?$C@D@2@@N@@XZ class N::A, class N::C> __cdecl abc_foo(void) +?z_foo@@YA?AVZ@N@@V12@@Z class N::Z __cdecl z_foo(class N::Z) +?b_foo@@YA?AV?$B@D@N@@V12@@Z class N::B __cdecl b_foo(class N::B) +?d_foo@@YA?AV?$D@DD@N@@V12@@Z class N::D __cdecl d_foo(class N::D) +?abc_foo_abc@@YA?AV?$A@DV?$B@D@N@@V?$C@D@2@@N@@V12@@Z class N::A, class N::C> __cdecl abc_foo_abc(class N::A, class N::C>) +?foo5@@YAXV?$Y@V?$Y@V?$Y@V?$Y@VX@NA@@@NB@@@NA@@@NB@@@NA@@@Z void __cdecl foo5(class NA::Y>>>) +?foo11@@YAXV?$Y@VX@NA@@@NA@@V1NB@@@Z void __cdecl foo11(class NA::Y, class NB::Y) +?foo112@@YAXV?$Y@VX@NA@@@NA@@V?$Y@VX@NB@@@NB@@@Z void __cdecl foo112(class NA::Y, class NB::Y) +?foo22@@YAXV?$Y@V?$Y@VX@NA@@@NB@@@NA@@V?$Y@V?$Y@VX@NA@@@NA@@@NB@@@Z void __cdecl foo22(class NA::Y>, class NB::Y>) +?foo@L@PR13207@@QAEXV?$I@VA@PR13207@@@2@@Z public: void __thiscall PR13207::L::foo(class PR13207::I) +?foo@PR13207@@YAXV?$I@VA@PR13207@@@1@@Z void __cdecl PR13207::foo(class PR13207::I) +?foo2@PR13207@@YAXV?$I@VA@PR13207@@@1@0@Z void __cdecl PR13207::foo2(class PR13207::I, class PR13207::I) +?bar@PR13207@@YAXV?$J@VA@PR13207@@VB@2@@1@@Z void __cdecl PR13207::bar(class PR13207::J) +?spam@PR13207@@YAXV?$K@VA@PR13207@@VB@2@VC@2@@1@@Z void __cdecl PR13207::spam(class PR13207::K) +?baz@PR13207@@YAXV?$K@DV?$F@D@PR13207@@V?$I@D@2@@1@@Z void __cdecl PR13207::baz(class PR13207::K, class PR13207::I>) +?qux@PR13207@@YAXV?$K@DV?$I@D@PR13207@@V12@@1@@Z void __cdecl PR13207::qux(class PR13207::K, class PR13207::I>) +?foo@NA@PR13207@@YAXV?$Y@VX@NA@PR13207@@@12@@Z void __cdecl PR13207::NA::foo(class PR13207::NA::Y) +?foofoo@NA@PR13207@@YAXV?$Y@V?$Y@VX@NA@PR13207@@@NA@PR13207@@@12@@Z void __cdecl PR13207::NA::foofoo(class PR13207::NA::Y>) +?foo@NB@PR13207@@YAXV?$Y@VX@NA@PR13207@@@12@@Z void __cdecl PR13207::NB::foo(class PR13207::NB::Y) +?bar@NB@PR13207@@YAXV?$Y@VX@NB@PR13207@@@NA@2@@Z void __cdecl PR13207::NB::bar(class PR13207::NA::Y) +?spam@NB@PR13207@@YAXV?$Y@VX@NA@PR13207@@@NA@2@@Z void __cdecl PR13207::NB::spam(class PR13207::NA::Y) +?foobar@NB@PR13207@@YAXV?$Y@V?$Y@VX@NB@PR13207@@@NB@PR13207@@@NA@2@V312@@Z void __cdecl PR13207::NB::foobar(class PR13207::NA::Y>, class PR13207::NB::Y>) +?foobarspam@NB@PR13207@@YAXV?$Y@VX@NB@PR13207@@@12@V?$Y@V?$Y@VX@NB@PR13207@@@NB@PR13207@@@NA@2@V412@@Z void __cdecl PR13207::NB::foobarspam(class PR13207::NB::Y, class PR13207::NA::Y>, class PR13207::NB::Y>) +?foobarbaz@NB@PR13207@@YAXV?$Y@VX@NB@PR13207@@@12@V?$Y@V?$Y@VX@NB@PR13207@@@NB@PR13207@@@NA@2@V412@2@Z void __cdecl PR13207::NB::foobarbaz(class PR13207::NB::Y, class PR13207::NA::Y>, class PR13207::NB::Y>, class PR13207::NB::Y>) +?foobarbazqux@NB@PR13207@@YAXV?$Y@VX@NB@PR13207@@@12@V?$Y@V?$Y@VX@NB@PR13207@@@NB@PR13207@@@NA@2@V412@2V?$Y@V?$Y@V?$Y@VX@NB@PR13207@@@NB@PR13207@@@NB@PR13207@@@52@@Z void __cdecl PR13207::NB::foobarbazqux(class PR13207::NB::Y, class PR13207::NA::Y>, class PR13207::NB::Y>, class PR13207::NB::Y>, class PR13207::NA::Y>>) +?foo@NC@PR13207@@YAXV?$Y@VX@NB@PR13207@@@12@@Z void __cdecl PR13207::NC::foo(class PR13207::NC::Y) +?foobar@NC@PR13207@@YAXV?$Y@V?$Y@V?$Y@VX@NA@PR13207@@@NA@PR13207@@@NB@PR13207@@@12@@Z void __cdecl PR13207::NC::foobar(class PR13207::NC::Y>>) +?fun_normal@fn_space@@YA?AURetVal@1@H@Z struct fn_space::RetVal __cdecl fn_space::fun_normal(int) +??$fun_tmpl@H@fn_space@@YA?AURetVal@0@ABH@Z struct fn_space::RetVal __cdecl fn_space::fun_tmpl(int const &) +??$fun_tmpl_recurse@H$1??$fun_tmpl_recurse@H$1?ident@fn_space@@YA?AURetVal@2@H@Z@fn_space@@YA?AURetVal@1@H@Z@fn_space@@YA?AURetVal@0@H@Z struct fn_space::RetVal __cdecl fn_space::fun_tmpl_recurse(int)>(int) +??$fun_tmpl_recurse@H$1?ident@fn_space@@YA?AURetVal@2@H@Z@fn_space@@YA?AURetVal@0@H@Z struct fn_space::RetVal __cdecl fn_space::fun_tmpl_recurse(int) +?AddEmitPasses@EmitAssemblyHelper@?A0x43583946@@AEAA_NAEAVPassManager@legacy@llvm@@W4BackendAction@clang@@AEAVraw_pwrite_stream@5@PEAV85@@Z private: bool __cdecl `anonymous namespace'::EmitAssemblyHelper::AddEmitPasses(class llvm::legacy::PassManager &, enum clang::BackendAction, class llvm::raw_pwrite_stream &, class llvm::raw_pwrite_stream *) +??$forward@P8?$DecoderStream@$01@media@@AEXXZ@std@@YA$$QAP8?$DecoderStream@$01@media@@AEXXZAAP812@AEXXZ@Z void (__thiscall media::DecoderStream<2>::*&& __cdecl std::forward::*)(void)>(void (__thiscall media::DecoderStream<2>::*&)(void)))(void) +?a@FTypeWithQuals@@3U?$S@$$A8@@BAHXZ@1@A struct FTypeWithQuals::S FTypeWithQuals::a +?b@FTypeWithQuals@@3U?$S@$$A8@@CAHXZ@1@A struct FTypeWithQuals::S FTypeWithQuals::b +?c@FTypeWithQuals@@3U?$S@$$A8@@IAAHXZ@1@A struct FTypeWithQuals::S FTypeWithQuals::c +?d@FTypeWithQuals@@3U?$S@$$A8@@GBAHXZ@1@A struct FTypeWithQuals::S FTypeWithQuals::d +?e@FTypeWithQuals@@3U?$S@$$A8@@GCAHXZ@1@A struct FTypeWithQuals::S FTypeWithQuals::e +?f@FTypeWithQuals@@3U?$S@$$A8@@IGAAHXZ@1@A struct FTypeWithQuals::S FTypeWithQuals::f +?g@FTypeWithQuals@@3U?$S@$$A8@@HBAHXZ@1@A struct FTypeWithQuals::S FTypeWithQuals::g +?h@FTypeWithQuals@@3U?$S@$$A8@@HCAHXZ@1@A struct FTypeWithQuals::S FTypeWithQuals::h +?i@FTypeWithQuals@@3U?$S@$$A8@@IHAAHXZ@1@A struct FTypeWithQuals::S FTypeWithQuals::i +?j@FTypeWithQuals@@3U?$S@$$A6AHXZ@1@A struct FTypeWithQuals::S FTypeWithQuals::j +?k@FTypeWithQuals@@3U?$S@$$A8@@GAAHXZ@1@A struct FTypeWithQuals::S FTypeWithQuals::k +?l@FTypeWithQuals@@3U?$S@$$A8@@HAAHXZ@1@A struct FTypeWithQuals::S FTypeWithQuals::l +?Char16Var@@3_SA char16_t Char16Var +?Char32Var@@3_UA char32_t Char32Var +?LRef@@YAXAAH@Z void __cdecl LRef(int &) +?RRef@@YAH$$QAH@Z int __cdecl RRef(int &&) +?Null@@YAX$$T@Z void __cdecl Null(std::nullptr_t) +?fun@PR18022@@YA?AU@1@U21@0@Z struct PR18022:: __cdecl PR18022::fun(struct PR18022::, struct PR18022::) +?lambda@?1??define_lambda@@YAHXZ@4V@?0??1@YAHXZ@A class `int __cdecl define_lambda(void)'::`1':: `int __cdecl define_lambda(void)'::`2'::lambda +??R@?0??define_lambda@@YAHXZ@QBE@XZ public: __thiscall `int __cdecl define_lambda(void)'::`1'::::operator()(void) const +?local@?2???R@?0??define_lambda@@YAHXZ@QBE@XZ@4HA int `public: __thiscall `int __cdecl define_lambda(void)'::`1'::::operator()(void) const'::`3'::local +??$use_lambda_arg@V@?0??call_with_lambda_arg1@@YAXXZ@@@YAXV@?0??call_with_lambda_arg1@@YAXXZ@@Z void __cdecl use_lambda_arg>(class `void __cdecl call_with_lambda_arg1(void)'::`1'::) +?foo@A@PR19361@@QIGAEXXZ public: void __thiscall PR19361::A::foo(void) __restrict & +?foo@A@PR19361@@QIHAEXXZ public: void __thiscall PR19361::A::foo(void) __restrict && +??__K_deg@@YAHO@Z int __cdecl operator ""_deg(long double) +??$templ_fun_with_pack@$S@@YAXXZ void __cdecl templ_fun_with_pack<>(void) +??$func@H$$ZH@@YAHAEBU?$Foo@H@@0@Z int __cdecl func(struct Foo const &, struct Foo const &) +??$templ_fun_with_ty_pack@$$$V@@YAXXZ void __cdecl templ_fun_with_ty_pack<>(void) +??$templ_fun_with_ty_pack@$$V@@YAXXZ void __cdecl templ_fun_with_ty_pack<>(void) +??$f@$$YAliasA@PR20047@@@PR20047@@YAXXZ void __cdecl PR20047::f(void) +?f@UnnamedType@@YAXAAU@A@1@@Z void __cdecl UnnamedType::f(struct UnnamedType::A:: &) +?f@UnnamedType@@YAXPAW4@?$B@H@1@@Z void __cdecl UnnamedType::f(enum UnnamedType::B:: *) +??$f@W4@?1??g@PR24651@@YAXXZ@@PR24651@@YAXW4@?1??g@0@YAXXZ@@Z void __cdecl PR24651::f>(enum `void __cdecl PR24651::g(void)'::`2'::) +??$f@T@PR18204@@@PR18204@@YAHPAT@0@@Z int __cdecl PR18204::f>(union PR18204:: *) +??R@?0??PR26105@@YAHXZ@QBE@H@Z public: __thiscall `int __cdecl PR26105(void)'::`1'::::operator()(int) const +??R@?0???R@?0??PR26105@@YAHXZ@QBE@H@Z@QBE@H@Z public: __thiscall `public: __thiscall `int __cdecl PR26105(void)'::`1'::::operator()(int) const'::`1'::::operator()(int) const +?unaligned_foo1@@YAPFAHXZ int __unaligned * __cdecl unaligned_foo1(void) +?unaligned_foo2@@YAPFAPFAHXZ int __unaligned *__unaligned * __cdecl unaligned_foo2(void) +?unaligned_foo3@@YAHXZ int __cdecl unaligned_foo3(void) +?unaligned_foo4@@YAXPFAH@Z void __cdecl unaligned_foo4(int __unaligned *) +?unaligned_foo5@@YAXPIFAH@Z void __cdecl unaligned_foo5(int __unaligned *__restrict) +??$unaligned_foo6@PAH@@YAPAHPAH@Z int * __cdecl unaligned_foo6(int *) +??$unaligned_foo6@PFAH@@YAPFAHPFAH@Z int __unaligned * __cdecl unaligned_foo6(int __unaligned *) +?unaligned_foo8@unaligned_foo8_S@@QFCEXXZ public: void __thiscall unaligned_foo8_S::unaligned_foo8(void) volatile __unaligned +??R@x@A@PR31197@@QBE@XZ public: __thiscall PR31197::A::x::::operator()(void) const +?white@?1???R@x@A@PR31197@@QBE@XZ@4HA int `public: __thiscall PR31197::A::x::::operator()(void) const'::`2'::white +?f@@YAXW4@@@Z void __cdecl f(enum ) +?a@@3HA int a +?b@N@@3HA int N::b +?anonymous@?A@N@@3HA int N::`anonymous namespace'::anonymous +?$RT1@NeedsReferenceTemporary@@3ABHB int const &NeedsReferenceTemporary::$RT1 +?$RT1@NeedsReferenceTemporary@@3AEBHEB int const &NeedsReferenceTemporary::$RT1 +?_c@@YAHXZ int __cdecl _c(void) +?d@foo@@0FB private: static short const foo::d +?e@foo@@1JC protected: static long volatile foo::e +?f@foo@@2DD public: static char const volatile foo::f +??0foo@@QAE@XZ public: __thiscall foo::foo(void) +??0foo@@QEAA@XZ public: __cdecl foo::foo(void) +??1foo@@QAE@XZ public: __thiscall foo::~foo(void) +??1foo@@QEAA@XZ public: __cdecl foo::~foo(void) +??0foo@@QAE@H@Z public: __thiscall foo::foo(int) +??0foo@@QEAA@H@Z public: __cdecl foo::foo(int) +??0foo@@QAE@PAD@Z public: __thiscall foo::foo(char *) +??0foo@@QEAA@PEAD@Z public: __cdecl foo::foo(char *) +?bar@@YA?AVfoo@@XZ class foo __cdecl bar(void) +??Hfoo@@QAEHH@Z public: int __thiscall foo::operator+(int) +??Hfoo@@QEAAHH@Z public: int __cdecl foo::operator+(int) +??$?HH@S@@QEAAAEANH@Z public: double & __cdecl S::operator+(int) +?static_method@foo@@SAPAV1@XZ public: static class foo * __cdecl foo::static_method(void) +?static_method@foo@@SAPEAV1@XZ public: static class foo * __cdecl foo::static_method(void) +?g@bar@@2HA public: static int bar::g +?h1@@3QAHA int *const h1 +?h2@@3QBHB int const *const h2 +?h3@@3QIAHIA int *const __restrict h3 +?h3@@3QEIAHEIA int *const __restrict h3 +?i@@3PAY0BE@HA int (*i)[20] +?FunArr@@3PAY0BE@P6AHHH@ZA int (__cdecl *(*FunArr)[20])(int, int) +?j@@3P6GHCE@ZA int (__stdcall *j)(signed char, unsigned char) +?funptr@@YAP6AHXZXZ int (__cdecl * __cdecl funptr(void))(void) +?m@@3PRfoo@@DR1@ char const foo::*m +?m@@3PERfoo@@DER1@ char const foo::*m +?k@@3PTfoo@@DT1@ char const volatile foo::*k +?k@@3PETfoo@@DET1@ char const volatile foo::*k +?l@@3P8foo@@AEHH@ZQ1@ int (__thiscall foo::*l)(int) +?g_cInt@@3HB int const g_cInt +?g_vInt@@3HC int volatile g_vInt +?g_cvInt@@3HD int const volatile g_cvInt +?beta@@YI_N_J_W@Z bool __fastcall beta(__int64, wchar_t) +?beta@@YA_N_J_W@Z bool __cdecl beta(__int64, wchar_t) +?alpha@@YGXMN@Z void __stdcall alpha(float, double) +?alpha@@YAXMN@Z void __cdecl alpha(float, double) +?gamma@@YAXVfoo@@Ubar@@Tbaz@@W4quux@@@Z void __cdecl gamma(class foo, struct bar, union baz, enum quux) +?delta@@YAXQAHABJ@Z void __cdecl delta(int *const, long const &) +?delta@@YAXQEAHAEBJ@Z void __cdecl delta(int *const, long const &) +?epsilon@@YAXQAY19BE@H@Z void __cdecl epsilon(int (*const)[10][20]) +?epsilon@@YAXQEAY19BE@H@Z void __cdecl epsilon(int (*const)[10][20]) +?zeta@@YAXP6AHHH@Z@Z void __cdecl zeta(int (__cdecl *)(int, int)) +??2@YAPAXI@Z void * __cdecl operator new(unsigned int) +??3@YAXPAX@Z void __cdecl operator delete(void *) +??_U@YAPAXI@Z void * __cdecl operator new[](unsigned int) +??_V@YAXPAX@Z void __cdecl operator delete[](void *) +?color1@@3PANA double *color1 +?color2@@3QBNB double const *const color2 +?color3@@3QAY02$$CBNA double const (*const color3)[3] +?color4@@3QAY02$$CBNA double const (*const color4)[3] +?memptr1@@3RESB@@HES1@ int volatile B::*volatile memptr1 +?memptr2@@3PESB@@HES1@ int volatile B::*memptr2 +?memptr3@@3REQB@@HEQ1@ int B::*volatile memptr3 +?funmemptr1@@3RESB@@R6AHXZES1@ int (__cdecl *volatile B::*volatile funmemptr1)(void) +?funmemptr2@@3PESB@@R6AHXZES1@ int (__cdecl *volatile B::*funmemptr2)(void) +?funmemptr3@@3REQB@@P6AHXZEQ1@ int (__cdecl *B::*volatile funmemptr3)(void) +?memptrtofun1@@3R8B@@EAAXXZEQ1@ void (__cdecl B::*volatile memptrtofun1)(void) +?memptrtofun2@@3P8B@@EAAXXZEQ1@ void (__cdecl B::*memptrtofun2)(void) +?memptrtofun3@@3P8B@@EAAXXZEQ1@ void (__cdecl B::*memptrtofun3)(void) +?memptrtofun4@@3R8B@@EAAHXZEQ1@ int (__cdecl B::*volatile memptrtofun4)(void) +?memptrtofun5@@3P8B@@EAA?CHXZEQ1@ int volatile (__cdecl B::*memptrtofun5)(void) +?memptrtofun6@@3P8B@@EAA?BHXZEQ1@ int const (__cdecl B::*memptrtofun6)(void) +?memptrtofun7@@3R8B@@EAAP6AHXZXZEQ1@ int (__cdecl * (__cdecl B::*volatile memptrtofun7)(void))(void) +?memptrtofun8@@3P8B@@EAAR6AHXZXZEQ1@ int (__cdecl *volatile (__cdecl B::*memptrtofun8)(void))(void) +?memptrtofun9@@3P8B@@EAAQ6AHXZXZEQ1@ int (__cdecl *const (__cdecl B::*memptrtofun9)(void))(void) +?fooE@@YA?AW4E@@XZ enum E __cdecl fooE(void) +?fooX@@YA?AVX@@XZ class X __cdecl fooX(void) +?s0@PR13182@@3PADA char *PR13182::s0 +?s1@PR13182@@3PADA char *PR13182::s1 +?s2@PR13182@@3QBDB char const *const PR13182::s2 +?s3@PR13182@@3QBDB char const *const PR13182::s3 +?s4@PR13182@@3RCDC char volatile *volatile PR13182::s4 +?s5@PR13182@@3SDDD char const volatile *const volatile PR13182::s5 +?s6@PR13182@@3PBQBDB char const *const *PR13182::s6 +?local@?1??extern_c_func@@9@4HA int `extern "C" extern_c_func'::`2'::local +?v@?1??f@@YAHXZ@4U@?1??1@YAHXZ@A struct `int __cdecl f(void)'::`2':: `int __cdecl f(void)'::`2'::v +?v@?1???$f@H@@YAHXZ@4U@?1???$f@H@@YAHXZ@A struct `int __cdecl f(void)'::`2':: `int __cdecl f(void)'::`2'::v +??2OverloadedNewDelete@@SAPAXI@Z public: static void * __cdecl OverloadedNewDelete::operator new(unsigned int) +??_UOverloadedNewDelete@@SAPAXI@Z public: static void * __cdecl OverloadedNewDelete::operator new[](unsigned int) +??3OverloadedNewDelete@@SAXPAX@Z public: static void __cdecl OverloadedNewDelete::operator delete(void *) +??_VOverloadedNewDelete@@SAXPAX@Z public: static void __cdecl OverloadedNewDelete::operator delete[](void *) +??HOverloadedNewDelete@@QAEHH@Z public: int __thiscall OverloadedNewDelete::operator+(int) +??2OverloadedNewDelete@@SAPEAX_K@Z public: static void * __cdecl OverloadedNewDelete::operator new(unsigned __int64) +??_UOverloadedNewDelete@@SAPEAX_K@Z public: static void * __cdecl OverloadedNewDelete::operator new[](unsigned __int64) +??3OverloadedNewDelete@@SAXPEAX@Z public: static void __cdecl OverloadedNewDelete::operator delete(void *) +??_VOverloadedNewDelete@@SAXPEAX@Z public: static void __cdecl OverloadedNewDelete::operator delete[](void *) +??HOverloadedNewDelete@@QEAAHH@Z public: int __cdecl OverloadedNewDelete::operator+(int) +??2TypedefNewDelete@@SAPAXI@Z public: static void * __cdecl TypedefNewDelete::operator new(unsigned int) +??_UTypedefNewDelete@@SAPAXI@Z public: static void * __cdecl TypedefNewDelete::operator new[](unsigned int) +??3TypedefNewDelete@@SAXPAX@Z public: static void __cdecl TypedefNewDelete::operator delete(void *) +??_VTypedefNewDelete@@SAXPAX@Z public: static void __cdecl TypedefNewDelete::operator delete[](void *) +?vector_func@@YQXXZ void __vectorcall vector_func(void) +?swift_func@@YSXXZ void __attribute__((__swiftcall__)) swift_func(void) +?swift_async_func@@YWXXZ void __attribute__((__swiftasynccall__)) swift_async_func(void) +??$fn_tmpl@$1?extern_c_func@@YAXXZ@@YAXXZ void __cdecl fn_tmpl<&void __cdecl extern_c_func(void)>(void) +?overloaded_fn@@$$J0YAXXZ extern "C" void __cdecl overloaded_fn(void) +?f@UnnamedType@@YAXQAPAU@S@1@@Z void __cdecl UnnamedType::f(struct UnnamedType::S:: **const) +?f@UnnamedType@@YAXUT2@S@1@@Z void __cdecl UnnamedType::f(struct UnnamedType::S::T2) +?f@UnnamedType@@YAXPAUT4@S@1@@Z void __cdecl UnnamedType::f(struct UnnamedType::S::T4 *) +?f@UnnamedType@@YAXUT4@S@1@@Z void __cdecl UnnamedType::f(struct UnnamedType::S::T4) +?f@UnnamedType@@YAXUT5@S@1@@Z void __cdecl UnnamedType::f(struct UnnamedType::S::T5) +?f@Atomic@@YAXU?$_Atomic@H@__clang@@@Z void __cdecl Atomic::f(struct __clang::_Atomic) +?f@Complex@@YAXU?$_Complex@H@__clang@@@Z void __cdecl Complex::f(struct __clang::_Complex) +?f@Float16@@YAXU_Float16@__clang@@@Z void __cdecl Float16::f(struct __clang::_Float16) +??0?$L@H@NS@@QEAA@XZ public: __cdecl NS::L::L(void) +??0Bar@Foo@@QEAA@XZ public: __cdecl Foo::Bar::Bar(void) +??0?$L@V?$H@PAH@PR26029@@@PR26029@@QAE@XZ public: __thiscall PR26029::L>::L>(void) +??$emplace_back@ABH@?$vector@HV?$allocator@H@std@@@std@@QAE?A?@@ABH@Z public: __thiscall std::vector>::emplace_back(int const &) +?pub_foo@S@@QAEXXZ public: void __thiscall S::pub_foo(void) +?pub_stat_foo@S@@SAXXZ public: static void __cdecl S::pub_stat_foo(void) +?pub_virt_foo@S@@UAEXXZ public: virtual void __thiscall S::pub_virt_foo(void) +?prot_foo@S@@IAEXXZ protected: void __thiscall S::prot_foo(void) +?prot_stat_foo@S@@KAXXZ protected: static void __cdecl S::prot_stat_foo(void) +?prot_virt_foo@S@@MAEXXZ protected: virtual void __thiscall S::prot_virt_foo(void) +?priv_foo@S@@AAEXXZ private: void __thiscall S::priv_foo(void) +?priv_stat_foo@S@@CAXXZ private: static void __cdecl S::priv_stat_foo(void) +?priv_virt_foo@S@@EAEXXZ private: virtual void __thiscall S::priv_virt_foo(void) +??@a6a285da2eea70dba6b578022be61d81@ ??@a6a285da2eea70dba6b578022be61d81@ +??@a6a285da2eea70dba6b578022be61d81@asdf ??@a6a285da2eea70dba6b578022be61d81@ +??@a6a285da2eea70dba6b578022be61d81@??_R4@ ??@a6a285da2eea70dba6b578022be61d81@??_R4@ +?M@?@??L@@YAHXZ@4HA int `int __cdecl L(void)'::`0'::M +?M@?0??L@@YAHXZ@4HA int `int __cdecl L(void)'::`1'::M +?M@?1??L@@YAHXZ@4HA int `int __cdecl L(void)'::`2'::M +?M@?2??L@@YAHXZ@4HA int `int __cdecl L(void)'::`3'::M +?M@?3??L@@YAHXZ@4HA int `int __cdecl L(void)'::`4'::M +?M@?4??L@@YAHXZ@4HA int `int __cdecl L(void)'::`5'::M +?M@?5??L@@YAHXZ@4HA int `int __cdecl L(void)'::`6'::M +?M@?6??L@@YAHXZ@4HA int `int __cdecl L(void)'::`7'::M +?M@?7??L@@YAHXZ@4HA int `int __cdecl L(void)'::`8'::M +?M@?8??L@@YAHXZ@4HA int `int __cdecl L(void)'::`9'::M +?M@?9??L@@YAHXZ@4HA int `int __cdecl L(void)'::`10'::M +?M@?L@??L@@YAHXZ@4HA int `int __cdecl L(void)'::`11'::M +?M@?M@??L@@YAHXZ@4HA int `int __cdecl L(void)'::`12'::M +?M@?N@??L@@YAHXZ@4HA int `int __cdecl L(void)'::`13'::M +?M@?O@??L@@YAHXZ@4HA int `int __cdecl L(void)'::`14'::M +?M@?P@??L@@YAHXZ@4HA int `int __cdecl L(void)'::`15'::M +?M@?BA@??L@@YAHXZ@4HA int `int __cdecl L(void)'::`16'::M +?M@?BB@??L@@YAHXZ@4HA int `int __cdecl L(void)'::`17'::M +?j@?1??L@@YAHXZ@4UJ@@A struct J `int __cdecl L(void)'::`2'::j +?NN@0XX@@3HA int XX::NN::NN +?MM@0NN@XX@@3HA int XX::NN::MM::MM +?NN@MM@0XX@@3HA int XX::NN::MM::NN +?OO@0NN@01XX@@3HA int XX::NN::OO::NN::OO::OO +?NN@OO@010XX@@3HA int XX::NN::OO::NN::OO::NN +?M@?1??0@YAHXZ@4HA int `int __cdecl M(void)'::`2'::M +?L@?2??M@0?2??0@YAHXZ@QEAAHXZ@4HA int `public: int __cdecl `int __cdecl L(void)'::`3'::L::M(void)'::`3'::L +?M@?2??0L@?2??1@YAHXZ@QEAAHXZ@4HA int `public: int __cdecl `int __cdecl L(void)'::`3'::L::M(void)'::`3'::M +?M@?1???$L@H@@YAHXZ@4HA int `int __cdecl L(void)'::`2'::M +?SN@?$NS@H@NS@@QEAAHXZ public: int __cdecl NS::NS::SN(void) +?NS@?1??SN@?$NS@H@0@QEAAHXZ@4HA int `public: int __cdecl NS::NS::SN(void)'::`2'::NS +?SN@?1??0?$NS@H@NS@@QEAAHXZ@4HA int `public: int __cdecl NS::NS::SN(void)'::`2'::SN +?NS@?1??SN@?$NS@H@10@QEAAHXZ@4HA int `public: int __cdecl NS::SN::NS::SN(void)'::`2'::NS +?SN@?1??0?$NS@H@0NS@@QEAAHXZ@4HA int `public: int __cdecl NS::SN::NS::SN(void)'::`2'::SN +?X@?$C@H@C@0@2HB public: static int const X::C::C::X +?X@?$C@H@C@1@2HB public: static int const C::C::C::X +?X@?$C@H@C@2@2HB public: static int const C::C::C::X +?C@?1??B@?$C@H@0101A@@QEAAHXZ@4U201013@A struct A::B::C::B::C::C `public: int __cdecl A::B::C::B::C::C::B(void)'::`2'::C +?B@?1??0?$C@H@C@020A@@QEAAHXZ@4HA int `public: int __cdecl A::B::C::B::C::C::B(void)'::`2'::B +?A@?1??B@?$C@H@C@1310@QEAAHXZ@4HA int `public: int __cdecl A::B::C::B::C::C::B(void)'::`2'::A +?a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@a@@3HA int a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a::a +??0Base@@QEAA@XZ public: __cdecl Base::Base(void) +??1Base@@UEAA@XZ public: virtual __cdecl Base::~Base(void) +??2@YAPEAX_K@Z void * __cdecl operator new(unsigned __int64) +??3@YAXPEAX_K@Z void __cdecl operator delete(void *, unsigned __int64) +??4Base@@QEAAHH@Z public: int __cdecl Base::operator=(int) +??6Base@@QEAAHH@Z public: int __cdecl Base::operator<<(int) +??5Base@@QEAAHH@Z public: int __cdecl Base::operator>>(int) +??7Base@@QEAAHXZ public: int __cdecl Base::operator!(void) +??8Base@@QEAAHH@Z public: int __cdecl Base::operator==(int) +??9Base@@QEAAHH@Z public: int __cdecl Base::operator!=(int) +??ABase@@QEAAHH@Z public: int __cdecl Base::operator[](int) +??BBase@@QEAAHXZ public: int __cdecl Base::operator int(void) +??CBase@@QEAAHXZ public: int __cdecl Base::operator->(void) +??DBase@@QEAAHXZ public: int __cdecl Base::operator*(void) +??EBase@@QEAAHXZ public: int __cdecl Base::operator++(void) +??EBase@@QEAAHH@Z public: int __cdecl Base::operator++(int) +??FBase@@QEAAHXZ public: int __cdecl Base::operator--(void) +??FBase@@QEAAHH@Z public: int __cdecl Base::operator--(int) +??GBase@@QEAAHH@Z public: int __cdecl Base::operator-(int) +??HBase@@QEAAHH@Z public: int __cdecl Base::operator+(int) +??IBase@@QEAAHH@Z public: int __cdecl Base::operator&(int) +??JBase@@QEAAHH@Z public: int __cdecl Base::operator->*(int) +??KBase@@QEAAHH@Z public: int __cdecl Base::operator/(int) +??LBase@@QEAAHH@Z public: int __cdecl Base::operator%(int) +??MBase@@QEAAHH@Z public: int __cdecl Base::operator<(int) +??NBase@@QEAAHH@Z public: int __cdecl Base::operator<=(int) +??OBase@@QEAAHH@Z public: int __cdecl Base::operator>(int) +??PBase@@QEAAHH@Z public: int __cdecl Base::operator>=(int) +??QBase@@QEAAHH@Z public: int __cdecl Base::operator,(int) +??RBase@@QEAAHXZ public: int __cdecl Base::operator()(void) +??SBase@@QEAAHXZ public: int __cdecl Base::operator~(void) +??TBase@@QEAAHH@Z public: int __cdecl Base::operator^(int) +??UBase@@QEAAHH@Z public: int __cdecl Base::operator|(int) +??VBase@@QEAAHH@Z public: int __cdecl Base::operator&&(int) +??WBase@@QEAAHH@Z public: int __cdecl Base::operator||(int) +??XBase@@QEAAHH@Z public: int __cdecl Base::operator*=(int) +??YBase@@QEAAHH@Z public: int __cdecl Base::operator+=(int) +??ZBase@@QEAAHH@Z public: int __cdecl Base::operator-=(int) +??_0Base@@QEAAHH@Z public: int __cdecl Base::operator/=(int) +??_1Base@@QEAAHH@Z public: int __cdecl Base::operator%=(int) +??_2Base@@QEAAHH@Z public: int __cdecl Base::operator>>=(int) +??_3Base@@QEAAHH@Z public: int __cdecl Base::operator<<=(int) +??_4Base@@QEAAHH@Z public: int __cdecl Base::operator&=(int) +??_5Base@@QEAAHH@Z public: int __cdecl Base::operator|=(int) +??_6Base@@QEAAHH@Z public: int __cdecl Base::operator^=(int) +??_7Base@@6B@ const Base::`vftable' +??_7A@B@@6BC@D@@@ const B::A::`vftable'{for `D::C'} +??_7A@B@@6BC@D@@E@F@@@ const B::A::`vftable'{for `D::C's `F::E'} +??_7A@B@@6BC@D@@E@F@@G@H@@@ const B::A::`vftable'{for `D::C's `F::E's `H::G'} +??_8Middle2@@7B@ const Middle2::`vbtable' +??_7A@@6BB@@@ const A::`vftable'{for `B'} +??_7A@@6BB@@C@@@ const A::`vftable'{for `B's `C'} +??_7A@@6BB@@C@@D@@@ const A::`vftable'{for `B's `C's `D'} +??_9Base@@$B7AA [thunk]: __cdecl Base::`vcall'{8, {flat}} +??_B?1??getS@@YAAAUS@@XZ@51 `struct S & __cdecl getS(void)'::`2'::`local static guard'{2} +??_C@_02PCEFGMJL@hi?$AA@ "hi" +??_DDiamond@@QEAAXXZ public: void __cdecl Diamond::`vbase dtor'(void) +??_EBase@@UEAAPEAXI@Z public: virtual void * __cdecl Base::`vector deleting dtor'(unsigned int) +??_EBase@@G3AEPAXI@Z [thunk]: private: void * __thiscall Base::`vector deleting dtor'`adjustor{4}'(unsigned int) +??_F?$SomeTemplate@H@@QAEXXZ public: void __thiscall SomeTemplate::`default ctor closure'(void) +??_GBase@@UEAAPEAXI@Z public: virtual void * __cdecl Base::`scalar deleting dtor'(unsigned int) +??_H@YAXPEAX_K1P6APEAX0@Z@Z void __cdecl `vector ctor iterator'(void *, unsigned __int64, unsigned __int64, void * (__cdecl *)(void *)) +??_I@YAXPEAX_K1P6AX0@Z@Z void __cdecl `vector dtor iterator'(void *, unsigned __int64, unsigned __int64, void (__cdecl *)(void *)) +??_JBase@@UEAAPEAXI@Z public: virtual void * __cdecl Base::`vector vbase ctor iterator'(unsigned int) +??_KBase@@UEAAPEAXI@Z public: virtual void * __cdecl Base::`virtual displacement map'(unsigned int) +??_LBase@@UEAAPEAXI@Z public: virtual void * __cdecl Base::`eh vector ctor iterator'(unsigned int) +??_MBase@@UEAAPEAXI@Z public: virtual void * __cdecl Base::`eh vector dtor iterator'(unsigned int) +??_NBase@@UEAAPEAXI@Z public: virtual void * __cdecl Base::`eh vector vbase ctor iterator'(unsigned int) +??_O?$SomeTemplate@H@@QAEXXZ public: void __thiscall SomeTemplate::`copy ctor closure'(void) +??_SBase@@6B@ const Base::`local vftable' +??_TDerived@@QEAAXXZ public: void __cdecl Derived::`local vftable ctor closure'(void) +??_U@YAPEAX_KAEAVklass@@@Z void * __cdecl operator new[](unsigned __int64, class klass &) +??_V@YAXPEAXAEAVklass@@@Z void __cdecl operator delete[](void *, class klass &) +??_R0?AUBase@@@8 struct Base `RTTI Type Descriptor' +??_R1A@?0A@EA@Base@@8 Base::`RTTI Base Class Descriptor at (0, -1, 0, 64)' +??_R2Base@@8 Base::`RTTI Base Class Array' +??_R3Base@@8 Base::`RTTI Class Hierarchy Descriptor' +??_R4Base@@6B@ const Base::`RTTI Complete Object Locator' +??__EFoo@@YAXXZ void __cdecl `dynamic initializer for 'Foo''(void) +??__E?i@C@@0HA@@YAXXZ void __cdecl `dynamic initializer for `private: static int C::i''(void) +??__FFoo@@YAXXZ void __cdecl `dynamic atexit destructor for 'Foo''(void) +??__F_decisionToDFA@XPathLexer@@0V?$vector@VDFA@dfa@antlr4@@V?$allocator@VDFA@dfa@antlr4@@@std@@@std@@A@YAXXZ void __cdecl `dynamic atexit destructor for `private: static class std::vector> XPathLexer::_decisionToDFA''(void) +??__J?1??f@@YAAAUS@@XZ@51 `struct S & __cdecl f(void)'::`2'::`local static thread guard'{2} +?a1@@YAXXZ void __cdecl a1(void) +?a2@@YAHXZ int __cdecl a2(void) +?a3@@YA?BHXZ int const __cdecl a3(void) +?a4@@YA?CHXZ int volatile __cdecl a4(void) +?a5@@YA?DHXZ int const volatile __cdecl a5(void) +?a6@@YAMXZ float __cdecl a6(void) +?b1@@YAPAHXZ int * __cdecl b1(void) +?b2@@YAPBDXZ char const * __cdecl b2(void) +?b3@@YAPAMXZ float * __cdecl b3(void) +?b4@@YAPBMXZ float const * __cdecl b4(void) +?b5@@YAPCMXZ float volatile * __cdecl b5(void) +?b6@@YAPDMXZ float const volatile * __cdecl b6(void) +?b7@@YAAAMXZ float & __cdecl b7(void) +?b8@@YAABMXZ float const & __cdecl b8(void) +?b9@@YAACMXZ float volatile & __cdecl b9(void) +?b10@@YAADMXZ float const volatile & __cdecl b10(void) +?b11@@YAPAPBDXZ char const ** __cdecl b11(void) +?c1@@YA?AVA@@XZ class A __cdecl c1(void) +?c2@@YA?BVA@@XZ class A const __cdecl c2(void) +?c3@@YA?CVA@@XZ class A volatile __cdecl c3(void) +?c4@@YA?DVA@@XZ class A const volatile __cdecl c4(void) +?c5@@YAPBVA@@XZ class A const * __cdecl c5(void) +?c6@@YAPCVA@@XZ class A volatile * __cdecl c6(void) +?c7@@YAPDVA@@XZ class A const volatile * __cdecl c7(void) +?c8@@YAAAVA@@XZ class A & __cdecl c8(void) +?c9@@YAABVA@@XZ class A const & __cdecl c9(void) +?c10@@YAACVA@@XZ class A volatile & __cdecl c10(void) +?c11@@YAADVA@@XZ class A const volatile & __cdecl c11(void) +?d1@@YA?AV?$B@H@@XZ class B __cdecl d1(void) +?d2@@YA?AV?$B@PBD@@XZ class B __cdecl d2(void) +?d3@@YA?AV?$B@VA@@@@XZ class B __cdecl d3(void) +?d4@@YAPAV?$B@VA@@@@XZ class B * __cdecl d4(void) +?d5@@YAPBV?$B@VA@@@@XZ class B const * __cdecl d5(void) +?d6@@YAPCV?$B@VA@@@@XZ class B volatile * __cdecl d6(void) +?d7@@YAPDV?$B@VA@@@@XZ class B const volatile * __cdecl d7(void) +?d8@@YAAAV?$B@VA@@@@XZ class B & __cdecl d8(void) +?d9@@YAABV?$B@VA@@@@XZ class B const & __cdecl d9(void) +?d10@@YAACV?$B@VA@@@@XZ class B volatile & __cdecl d10(void) +?d11@@YAADV?$B@VA@@@@XZ class B const volatile & __cdecl d11(void) +?e1@@YA?AW4Enum@@XZ enum Enum __cdecl e1(void) +?e2@@YA?BW4Enum@@XZ enum Enum const __cdecl e2(void) +?e3@@YAPAW4Enum@@XZ enum Enum * __cdecl e3(void) +?e4@@YAAAW4Enum@@XZ enum Enum & __cdecl e4(void) +?f1@@YA?AUS@@XZ struct S __cdecl f1(void) +?f2@@YA?BUS@@XZ struct S const __cdecl f2(void) +?f3@@YAPAUS@@XZ struct S * __cdecl f3(void) +?f4@@YAPBUS@@XZ struct S const * __cdecl f4(void) +?f5@@YAPDUS@@XZ struct S const volatile * __cdecl f5(void) +?f6@@YAAAUS@@XZ struct S & __cdecl f6(void) +?f7@@YAQAUS@@XZ struct S *const __cdecl f7(void) +?f8@@YAPQS@@HXZ int S::* __cdecl f8(void) +?f9@@YAQQS@@HXZ int S::*const __cdecl f9(void) +?f10@@YAPIQS@@HXZ int S::*__restrict __cdecl f10(void) +?f11@@YAQIQS@@HXZ int S::*const __restrict __cdecl f11(void) +?g1@@YAP6AHH@ZXZ int (__cdecl * __cdecl g1(void))(int) +?g2@@YAQ6AHH@ZXZ int (__cdecl *const __cdecl g2(void))(int) +?g3@@YAPAP6AHH@ZXZ int (__cdecl ** __cdecl g3(void))(int) +?g4@@YAPBQ6AHH@ZXZ int (__cdecl *const * __cdecl g4(void))(int) +?h1@@YAAIAHXZ int &__restrict __cdecl h1(void) +?f@@3V?$C@H@@A class C f +??0?$Class@VTypename@@@@QAE@XZ public: __thiscall Class::Class(void) +??0?$Class@VTypename@@@@QEAA@XZ public: __cdecl Class::Class(void) +??0?$Class@$$CBVTypename@@@@QAE@XZ public: __thiscall Class::Class(void) +??0?$Class@$$CBVTypename@@@@QEAA@XZ public: __cdecl Class::Class(void) +??0?$Class@$$CCVTypename@@@@QAE@XZ public: __thiscall Class::Class(void) +??0?$Class@$$CCVTypename@@@@QEAA@XZ public: __cdecl Class::Class(void) +??0?$Class@$$CDVTypename@@@@QAE@XZ public: __thiscall Class::Class(void) +??0?$Class@$$CDVTypename@@@@QEAA@XZ public: __cdecl Class::Class(void) +??0?$Class@V?$Nested@VTypename@@@@@@QAE@XZ public: __thiscall Class>::Class>(void) +??0?$Class@V?$Nested@VTypename@@@@@@QEAA@XZ public: __cdecl Class>::Class>(void) +??0?$Class@QAH@@QAE@XZ public: __thiscall Class::Class(void) +??0?$Class@QEAH@@QEAA@XZ public: __cdecl Class::Class(void) +??0?$Class@$$A6AHXZ@@QAE@XZ public: __thiscall Class::Class(void) +??0?$Class@$$A6AHXZ@@QEAA@XZ public: __cdecl Class::Class(void) +??0?$Class@$$BY0A@H@@QAE@XZ public: __thiscall Class::Class(void) +??0?$Class@$$BY0A@H@@QEAA@XZ public: __cdecl Class::Class(void) +??0?$Class@$$BY04H@@QAE@XZ public: __thiscall Class::Class(void) +??0?$Class@$$BY04H@@QEAA@XZ public: __cdecl Class::Class(void) +??0?$Class@$$BY04$$CBH@@QAE@XZ public: __thiscall Class::Class(void) +??0?$Class@$$BY04$$CBH@@QEAA@XZ public: __cdecl Class::Class(void) +??0?$Class@$$BY04QAH@@QAE@XZ public: __thiscall Class::Class(void) +??0?$Class@$$BY04QEAH@@QEAA@XZ public: __cdecl Class::Class(void) +??0?$BoolTemplate@$0A@@@QAE@XZ public: __thiscall BoolTemplate<0>::BoolTemplate<0>(void) +??0?$BoolTemplate@$0A@@@QEAA@XZ public: __cdecl BoolTemplate<0>::BoolTemplate<0>(void) +??0?$BoolTemplate@$00@@QAE@XZ public: __thiscall BoolTemplate<1>::BoolTemplate<1>(void) +??0?$BoolTemplate@$00@@QEAA@XZ public: __cdecl BoolTemplate<1>::BoolTemplate<1>(void) +??$Foo@H@?$BoolTemplate@$00@@QAEXH@Z public: void __thiscall BoolTemplate<1>::Foo(int) +??$Foo@H@?$BoolTemplate@$00@@QEAAXH@Z public: void __cdecl BoolTemplate<1>::Foo(int) +??0?$IntTemplate@$0A@@@QAE@XZ public: __thiscall IntTemplate<0>::IntTemplate<0>(void) +??0?$IntTemplate@$0A@@@QEAA@XZ public: __cdecl IntTemplate<0>::IntTemplate<0>(void) +??0?$IntTemplate@$04@@QAE@XZ public: __thiscall IntTemplate<5>::IntTemplate<5>(void) +??0?$IntTemplate@$04@@QEAA@XZ public: __cdecl IntTemplate<5>::IntTemplate<5>(void) +??0?$IntTemplate@$0L@@@QAE@XZ public: __thiscall IntTemplate<11>::IntTemplate<11>(void) +??0?$IntTemplate@$0L@@@QEAA@XZ public: __cdecl IntTemplate<11>::IntTemplate<11>(void) +??0?$IntTemplate@$0BAA@@@QAE@XZ public: __thiscall IntTemplate<256>::IntTemplate<256>(void) +??0?$IntTemplate@$0BAA@@@QEAA@XZ public: __cdecl IntTemplate<256>::IntTemplate<256>(void) +??0?$IntTemplate@$0CAB@@@QAE@XZ public: __thiscall IntTemplate<513>::IntTemplate<513>(void) +??0?$IntTemplate@$0CAB@@@QEAA@XZ public: __cdecl IntTemplate<513>::IntTemplate<513>(void) +??0?$IntTemplate@$0EAC@@@QAE@XZ public: __thiscall IntTemplate<1026>::IntTemplate<1026>(void) +??0?$IntTemplate@$0EAC@@@QEAA@XZ public: __cdecl IntTemplate<1026>::IntTemplate<1026>(void) +??0?$IntTemplate@$0PPPP@@@QAE@XZ public: __thiscall IntTemplate<65535>::IntTemplate<65535>(void) +??0?$IntTemplate@$0PPPP@@@QEAA@XZ public: __cdecl IntTemplate<65535>::IntTemplate<65535>(void) +??0?$IntTemplate@$0?0@@QAE@XZ public: __thiscall IntTemplate<-1>::IntTemplate<-1>(void) +??0?$IntTemplate@$0?0@@QEAA@XZ public: __cdecl IntTemplate<-1>::IntTemplate<-1>(void) +??0?$IntTemplate@$0?8@@QAE@XZ public: __thiscall IntTemplate<-9>::IntTemplate<-9>(void) +??0?$IntTemplate@$0?8@@QEAA@XZ public: __cdecl IntTemplate<-9>::IntTemplate<-9>(void) +??0?$IntTemplate@$0?9@@QAE@XZ public: __thiscall IntTemplate<-10>::IntTemplate<-10>(void) +??0?$IntTemplate@$0?9@@QEAA@XZ public: __cdecl IntTemplate<-10>::IntTemplate<-10>(void) +??0?$IntTemplate@$0?L@@@QAE@XZ public: __thiscall IntTemplate<-11>::IntTemplate<-11>(void) +??0?$IntTemplate@$0?L@@@QEAA@XZ public: __cdecl IntTemplate<-11>::IntTemplate<-11>(void) +??0?$UnsignedIntTemplate@$0PPPPPPPP@@@QAE@XZ public: __thiscall UnsignedIntTemplate<4294967295>::UnsignedIntTemplate<4294967295>(void) +??0?$UnsignedIntTemplate@$0PPPPPPPP@@@QEAA@XZ public: __cdecl UnsignedIntTemplate<4294967295>::UnsignedIntTemplate<4294967295>(void) +??0?$LongLongTemplate@$0?IAAAAAAAAAAAAAAA@@@QAE@XZ public: __thiscall LongLongTemplate<-9223372036854775808>::LongLongTemplate<-9223372036854775808>(void) +??0?$LongLongTemplate@$0?IAAAAAAAAAAAAAAA@@@QEAA@XZ public: __cdecl LongLongTemplate<-9223372036854775808>::LongLongTemplate<-9223372036854775808>(void) +??0?$LongLongTemplate@$0HPPPPPPPPPPPPPPP@@@QAE@XZ public: __thiscall LongLongTemplate<9223372036854775807>::LongLongTemplate<9223372036854775807>(void) +??0?$LongLongTemplate@$0HPPPPPPPPPPPPPPP@@@QEAA@XZ public: __cdecl LongLongTemplate<9223372036854775807>::LongLongTemplate<9223372036854775807>(void) +??0?$UnsignedLongLongTemplate@$0?0@@QAE@XZ public: __thiscall UnsignedLongLongTemplate<-1>::UnsignedLongLongTemplate<-1>(void) +??0?$UnsignedLongLongTemplate@$0?0@@QEAA@XZ public: __cdecl UnsignedLongLongTemplate<-1>::UnsignedLongLongTemplate<-1>(void) +??$foo@H@space@@YAABHABH@Z int const & __cdecl space::foo(int const &) +??$foo@H@space@@YAAEBHAEBH@Z int const & __cdecl space::foo(int const &) +??$FunctionPointerTemplate@$1?spam@@YAXXZ@@YAXXZ void __cdecl FunctionPointerTemplate<&void __cdecl spam(void)>(void) +??$variadic_fn_template@HHHH@@YAXABH000@Z void __cdecl variadic_fn_template(int const &, int const &, int const &, int const &) +??$variadic_fn_template@HHD$$BY01D@@YAXABH0ABDAAY01$$CBD@Z void __cdecl variadic_fn_template(int const &, int const &, char const &, char const (&)[2]) +??0?$VariadicClass@HD_N@@QAE@XZ public: __thiscall VariadicClass::VariadicClass(void) +??0?$VariadicClass@_NDH@@QAE@XZ public: __thiscall VariadicClass::VariadicClass(void) +?template_template_fun@@YAXU?$Type@U?$Thing@USecond@@$00@@USecond@@@@@Z void __cdecl template_template_fun(struct Type, struct Second>) +??$template_template_specialization@$$A6AXU?$Type@U?$Thing@USecond@@$00@@USecond@@@@@Z@@YAXXZ void __cdecl template_template_specialization, struct Second>)>(void) +?f@@YAXU?$S1@$0A@@@@Z void __cdecl f(struct S1<0>) +?recref@@YAXU?$type1@$E?inst@@3Urecord@@B@@@Z void __cdecl recref(struct type1) +?fun@@YAXU?$UUIDType1@Uuuid@@$1?_GUID_12345678_1234_1234_1234_1234567890ab@@3U__s_GUID@@B@@@Z void __cdecl fun(struct UUIDType1) +?fun@@YAXU?$UUIDType2@Uuuid@@$E?_GUID_12345678_1234_1234_1234_1234567890ab@@3U__s_GUID@@B@@@Z void __cdecl fun(struct UUIDType2) +?FunctionDefinedWithInjectedName@@YAXU?$TypeWithFriendDefinition@H@@@Z void __cdecl FunctionDefinedWithInjectedName(struct TypeWithFriendDefinition) +?bar@?$UUIDType4@$1?_GUID_12345678_1234_1234_1234_1234567890ab@@3U__s_GUID@@B@@QAEXXZ public: void __thiscall UUIDType4<&struct __s_GUID const _GUID_12345678_1234_1234_1234_1234567890ab>::bar(void) +??$f@US@@$1?g@1@QEAAXXZ@@YAXXZ void __cdecl f(void) +??$?0N@?$Foo@H@@QEAA@N@Z public: __cdecl Foo::Foo(double) +?f@C@@WBA@EAAHXZ [thunk]: public: virtual int __cdecl C::f`adjustor{16}'(void) +??_EDerived@@$4PPPPPPPM@A@EAAPEAXI@Z [thunk]: public: virtual void * __cdecl Derived::`vector deleting dtor'`vtordisp{-4, 0}'(unsigned int) +?f@A@simple@@$R477PPPPPPPM@7AEXXZ [thunk]: public: virtual void __thiscall simple::A::f`vtordispex{8, 8, -4, 8}'(void) +?bar@Foo@@SGXXZ public: static void __stdcall Foo::bar(void) +?bar@Foo@@QAGXXZ public: void __stdcall Foo::bar(void) +?f2@@YIXXZ void __fastcall f2(void) +?f1@@YGXXZ void __stdcall f1(void) diff --git a/tests/testMsvcDemangler.py b/tests/testMsvcDemangler.py new file mode 100644 index 00000000..e5097312 --- /dev/null +++ b/tests/testMsvcDemangler.py @@ -0,0 +1,180 @@ +import unittest +from pathlib import Path + +from smda.common.labelprovider.MsvcDemangler import demangle_msvc_symbol + +# Every pair below is a real MSVC mangled name with the spelling llvm-undname produces +# for it. The mangled names come from LLVM's own demangler test corpus, except the two +# marked as read out of a PDB. +DEMANGLED = [ + ("?foo@@YAXI@Z", "void __cdecl foo(unsigned int)"), + ("?foo@@YAXN@Z", "void __cdecl foo(double)"), + ("?foo_pad@@YAXPAD@Z", "void __cdecl foo_pad(char *)"), + ("?foo_pbd@@YAXPBD@Z", "void __cdecl foo_pbd(char const *)"), + ("?foo_qad@@YAXQAD@Z", "void __cdecl foo_qad(char *const)"), + ("?foo_papad@@YAXPAPAD@Z", "void __cdecl foo_papad(char **)"), + ("?foo_pbqad@@YAXPBQAD@Z", "void __cdecl foo_pbqad(char *const *)"), + ("?foo_aad@@YAXAAD@Z", "void __cdecl foo_aad(char &)"), + ("?foo_aay144h@@YAXAAY144H@Z", "void __cdecl foo_aay144h(int (&)[5][5])"), + ("?foo_aay144cbh@@YAXAAY144$$CBH@Z", "void __cdecl foo_aay144cbh(int const (&)[5][5])"), + ("?foo_piad@@YAXPIAD@Z", "void __cdecl foo_piad(char *__restrict)"), + ("?foo_p6ahxz@@YAXP6AHXZ@Z", "void __cdecl foo_p6ahxz(int (__cdecl *)(void))"), + ("??0foo@@QAE@XZ", "public: __thiscall foo::foo(void)"), + ("??1foo@@QAE@XZ", "public: __thiscall foo::~foo(void)"), + ("??Hfoo@@QAEHH@Z", "public: int __thiscall foo::operator+(int)"), + ("??_V@YAXPAX@Z", "void __cdecl operator delete[](void *)"), + ("?static_method@foo@@SAPAV1@XZ", "public: static class foo * __cdecl foo::static_method(void)"), + ("?d@foo@@0FB", "private: static short const foo::d"), + ("?e@foo@@1JC", "protected: static long volatile foo::e"), + ("?Char16Var@@3_SA", "char16_t Char16Var"), + ("?h2@@3QBHB", "int const *const h2"), + ("?mbb@S@@QAEX_N0@Z", "public: void __thiscall S::mbb(bool, bool)"), + ("?f@@YAXHZZ", "void __cdecl f(int, ...)"), + ("?g@@YAXUS@@PA0@Z", "void __cdecl g(struct S, struct S *)"), + # the declarator cases: a name or a further pointer belongs inside its own type + ("?j@@3P6GHCE@ZA", "int (__stdcall *j)(signed char, unsigned char)"), + ("?g@@3PAP6AHXZA", "int (__cdecl **g)(void)"), + ("?f@@YAPAY01HXZ", "int (* __cdecl f(void))[2]"), + ("?f@@YAAAY01HXZ", "int (& __cdecl f(void))[2]"), + ("?ret_fnptrarray@@YAP6AXQAH@ZXZ", "void (__cdecl * __cdecl ret_fnptrarray(void))(int *const)"), + ("?color3@@3QAY02$$CBNA", "double const (*const color3)[3]"), + ("?f@@YAXY01H@Z", "void __cdecl f(int[2])"), + ("?b11@@YAPAPBDXZ", "char const ** __cdecl b11(void)"), + ("?foo_abc@@YAXV?$A@DV?$B@D@@V?$C@D@@@@@Z", "void __cdecl foo_abc(class A, class C>)"), + # read out of real PDBs rather than the LLVM corpus + ("??_7type_info@@6B@", "const type_info::`vftable'"), + ( + "?__crt_rotate_pointer_value@@YAIIH@Z", + "unsigned int __cdecl __crt_rotate_pointer_value(unsigned int, int)", + ), +] + +# Measured against llvm-undname 22.1.7 on the shipped corpus. Both are exact so that a +# change in either direction has to be an explicit edit rather than passing silently. +UNIQUE_CORPUS_NAMES = 609 +CORPUS_NAMES_UNDERSTOOD = 363 + +# Forms this demangler does not model. Each must come back exactly as it went in: a wrong +# expansion is worse than a decorated name, because it matches neither spelling. +DECLINED = [ + "??$f@T@PR18204@@@PR18204@@YAHPAT@0@@Z", + "?x@@3PAY02Hz", # truncated + "?", + "??", + # a symbol table holds whatever bytes were written into it, so every malformed shape + # below has to be answerable rather than raise + "?@@YAXXZ", # empty name fragment + "?a@1@@YAXXZ", # name back-reference past the end of the table + "??0@@QAE@XZ", # constructor with no class to name it after + "?f@@YAX_Y@Z", # unknown extended basic type + "?f@@YAX$$CZ@Z", # $$C without a qualifier + "?f@@YAXP6ZHXZ@Z", # function pointer with an unknown calling convention + "?f@@YAX$$A6ZXZ@Z", # function type argument with an unknown calling convention + "??_7type_info@@6Z@", # vftable with an unknown qualifier + "??_7type_info@@6B@X", # vftable with trailing bytes + "?foo@@YAXI@ZX", # function with trailing bytes + "?g@@YAXPAUS@@PA1@Z", # argument back-reference past the end of the table + "?g@@YAX0@Z", # argument back-reference with nothing recorded yet + "?f@@YAXPAHPB0@Z", # a qualifier in front of a back-reference, which MSVC does not form + "?f@@YAXZZ", # variadic marker with no parameter before it + "?f@@YAX_", # truncated extended type + "??_?@@YAXXZ", # unknown extended operator + # the declarator placeholder is a NUL; an identifier carrying one would otherwise be + # mistaken for the slot a pointer writes itself into, yielding "class a(*)b" + "?f@@YAXPAVa\x00b@@@Z", + "?a\x00b@@YAXPAY01D@Z", +] + + +class MsvcDemanglerTestSuite(unittest.TestCase): + def test_known_names_match_the_reference_spelling(self): + for mangled, expected in DEMANGLED: + with self.subTest(mangled=mangled): + self.assertEqual(demangle_msvc_symbol(mangled), expected) + + def test_unmodelled_forms_come_back_untouched(self): + for mangled in DECLINED: + with self.subTest(mangled=mangled): + self.assertEqual(demangle_msvc_symbol(mangled), mangled) + + def test_a_name_carrying_the_declarator_placeholder_is_refused(self): + # not merely declined by accident: without the guard this produced "class a(*)b", + # a spelling matching neither the input nor the truth + evil = "?f@@YAXPAVa\x00b@@@Z" + + self.assertEqual(demangle_msvc_symbol(evil), evil) + + def test_names_that_are_not_msvc_decorated_are_left_alone(self): + for name in ("", "plain_name", "_ZN4test4funcEv", "_RNvC6_123foo3bar", "_ReadFile@20"): + with self.subTest(name=name): + self.assertEqual(demangle_msvc_symbol(name), name) + + def test_a_deeply_nested_type_stops_at_the_depth_bound(self): + # each "PA" is another pointer level, so this nests types far past the bound; it must + # decline on the bound rather than on the interpreter's recursion limit + shallow = "?f@@YAX" + "PA" * 30 + "D@Z" + deep = "?f@@YAX" + "PA" * 300 + "D@Z" + + self.assertTrue(demangle_msvc_symbol(shallow).startswith("void __cdecl f(char ")) + self.assertEqual(demangle_msvc_symbol(deep), deep) + + def test_a_deeply_nested_name_stops_at_the_depth_bound(self): + # nesting in the *name* rather than the type: each level is another template + deep = "?f@@YAX" + "V?$A@" * 200 + "H" + "@" * 200 + "@@Z" + + self.assertEqual(demangle_msvc_symbol(deep), deep) + + def test_a_result_that_would_balloon_is_refused(self): + # each layer re-uses every earlier argument back-reference, so the rendered result + # grows multiplicatively while the name itself stays short + name = "?f@@YAXPAD" + "".join("P6AX" + str(index) * 9 + "@Z" for index in range(8)) + "@Z" + + self.assertLess(len(name), 200) + self.assertEqual(demangle_msvc_symbol(name), name) + + def test_a_truncated_name_never_raises(self): + # symbol tables carry damaged strings; every prefix must be answerable + source = "?static_method@foo@@SAPAV1@XZ" + for end in range(len(source) + 1): + with self.subTest(prefix=source[:end]): + self.assertIsInstance(demangle_msvc_symbol(source[:end]), str) + + +class MsvcReferenceCorpusTestSuite(unittest.TestCase): + """Measure the demangler against llvm-undname's output on LLVM's own corpus.""" + + @classmethod + def setUpClass(cls): + path = Path(__file__).parent / "msvc_reference_corpus.txt" + cls.corpus = [] + for line in path.read_text(encoding="utf-8").splitlines(): + if line.startswith("#") or "\t" not in line: + continue + mangled, expected = line.split("\t", 1) + cls.corpus.append((mangled, expected)) + + def test_no_name_is_given_a_third_spelling(self): + """The guarantee: a name is either demangled correctly or returned untouched.""" + wrong = [] + for mangled, expected in self.corpus: + got = demangle_msvc_symbol(mangled) + if got not in (expected, mangled): + wrong.append((mangled, got, expected)) + + self.assertEqual(wrong, []) + + def test_the_share_that_is_understood_is_exactly_what_was_measured(self): + """A ratchet in both directions: improving coverage means updating this number.""" + exact = sum(1 for mangled, expected in self.corpus if demangle_msvc_symbol(mangled) == expected) + + self.assertEqual(len(self.corpus), UNIQUE_CORPUS_NAMES) + self.assertEqual(exact, CORPUS_NAMES_UNDERSTOOD) + + def test_every_name_survives_truncation_at_any_point(self): + for mangled, _ in self.corpus: + for end in range(len(mangled) + 1): + self.assertIsInstance(demangle_msvc_symbol(mangled[:end]), str) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/testPeSymbolProvider.py b/tests/testPeSymbolProvider.py index a25f91f7..36e6450c 100644 --- a/tests/testPeSymbolProvider.py +++ b/tests/testPeSymbolProvider.py @@ -492,5 +492,33 @@ def test_a_signature_with_arguments_is_expanded(self): self.assertIn("double) const", measure[0]) +class PeNameDispatchTestSuite(unittest.TestCase): + """The PE provider picks a demangler from the decoration each name carries.""" + + def _exportedNames(self, *names): + entries = [ + SimpleNamespace(name=name, address=0x1000 + index * 0x10, is_extern=False, is_forwarded=False) + for index, name in enumerate(names) + ] + binary = SimpleNamespace( + imagebase=0x400000, + get_export=lambda: SimpleNamespace(entries=entries), + ) + return sorted(PeSymbolProvider(None).parseExports(binary, base_addr=0x400000).items()) + + def test_each_decoration_reaches_its_own_demangler(self): + recovered = self._exportedNames("?foo@@YAXI@Z", "_ZN4test4funcEv", "plain_name") + + self.assertEqual( + [name for _, name in recovered], + ["void __cdecl foo(unsigned int)", "test::func()", "plain_name"], + ) + + def test_a_rust_name_is_left_for_the_rust_provider(self): + recovered = self._exportedNames("_RNvC6_123foo3bar") + + self.assertEqual([name for _, name in recovered], ["_RNvC6_123foo3bar"]) + + if __name__ == "__main__": unittest.main() diff --git a/tests/test_fuzz_msvc_demangler.py b/tests/test_fuzz_msvc_demangler.py new file mode 100644 index 00000000..5dddd541 --- /dev/null +++ b/tests/test_fuzz_msvc_demangler.py @@ -0,0 +1,57 @@ +"""Fuzz tests for the MSVC symbol demangler using Hypothesis. + +The demangler accepts arbitrary strings and must: + - never raise, whatever the input + - never hang (unbounded recursion, runaway loops) + - either expand a name or hand it back unchanged, never a third spelling +""" + +from hypothesis import given, settings +from hypothesis.strategies import lists, sampled_from, text + +from smda.common.labelprovider.MsvcDemangler import demangle_msvc_symbol + +# the alphabet a decorated name is actually built from, so the fuzzer spends its budget +# inside the grammar rather than rejecting on the first character +_MANGLING_ALPHABET = list("?@$_0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz") + + +def _assertAnswered(name, result): + """Every property the module docstring promises, checked on one answer.""" + assert isinstance(result, str) + if result == name: + return + # an expansion is a readable C++ spelling: no control characters, and in particular + # never the declarator placeholder the renderer uses internally + assert result.isprintable(), result + # back-reference reuse can multiply a rendered type, so the result stays bounded by the + # size of the name that produced it + assert len(result) <= 8 * len(name) + 256, (len(name), len(result)) + + +@given(s=text(max_size=256)) +@settings(max_examples=500, deadline=None) +def test_arbitrary_text_is_answered(s): + result = demangle_msvc_symbol(s) + + _assertAnswered(s, result) + if not s.startswith("?"): + assert result == s + + +@given(pieces=lists(sampled_from(_MANGLING_ALPHABET), min_size=1, max_size=64)) +@settings(max_examples=500, deadline=None) +def test_decoration_shaped_input_is_answered(pieces): + name = "?" + "".join(pieces) + + _assertAnswered(name, demangle_msvc_symbol(name)) + + +@given(pieces=lists(sampled_from(_MANGLING_ALPHABET), min_size=1, max_size=64)) +@settings(max_examples=500, deadline=None) +def test_every_prefix_of_a_generated_name_is_answered(pieces): + name = "?" + "".join(pieces) + + for end in range(len(name) + 1): + prefix = name[:end] + _assertAnswered(prefix, demangle_msvc_symbol(prefix)) From 099ac7760093cedc99a1a194931cbe7688eaa6bb Mon Sep 17 00:00:00 2001 From: r0ny123 <49360849+r0ny123@users.noreply.github.com> Date: Sat, 15 Aug 2026 12:38:17 +0530 Subject: [PATCH 05/23] fix(labels): refuse MSVC names the grammar does not actually allow Differential fuzzing against llvm-undname 22.1.7 - 15044 names derived by mutating and splicing the reference corpus - found 256 shapes this demangler expanded confidently while the reference refused them. A decorated name is a usable identity, so answering where the grammar does not is the one failure this demangler is built to avoid. Five tightenings take that to 4. The largest class was a missing terminator. A parameter list is closed by the throw specification, so a fixed list ends "@Z" and a variadic one "ZZ", but the terminator was consumed only when the parser was not already at end of input. Any name truncated just past its parameters therefore got a plausible answer: "?a2@@YAHX" read as "int __cdecl a2(void)" and "?f@@YAXHZ" as a variadic call whose trailing byte was never there. The extended integer table carried _D through _I. No Microsoft-compatible mangler emits those - __int8, __int16 and __int32 are spelled with the plain char, short and int codes - so a name containing one is not MSVC-decorated. _L and _M stay: clang's Microsoft mangler does emit them for __int128, which is also why they are spelled from the mangler's table rather than the reference's, the reference having no reader for them. The remaining three each restore a rule the grammar states and the parser did not check: a template name is an identifier, not a digit or an operator code; a special name takes a signature or a storage class by which code it is, never both, so a vftable with a parameter list and an operator with a vftable's storage class are equally malformed; and a reference cannot carry a qualifier from the type enclosing it, which had been rendering as "char const &const volatile *". Every change refuses more and expands nothing new. The reference corpus is unchanged at 363 of 609 exact with none given a different spelling, and the MSVC names carried by real PDBs are unchanged at 11 of 14. --- .../common/labelprovider/MsvcDemangler.py | 51 +++++++++++-------- tests/testMsvcDemangler.py | 20 ++++++++ 2 files changed, 50 insertions(+), 21 deletions(-) diff --git a/src/smda/common/labelprovider/MsvcDemangler.py b/src/smda/common/labelprovider/MsvcDemangler.py index 81d7ad7d..fff213f9 100644 --- a/src/smda/common/labelprovider/MsvcDemangler.py +++ b/src/smda/common/labelprovider/MsvcDemangler.py @@ -19,12 +19,6 @@ "O": "long double", } _EXTENDED_TYPES = { - "D": "__int8", - "E": "unsigned __int8", - "F": "__int16", - "G": "unsigned __int16", - "H": "__int32", - "I": "unsigned __int32", "J": "__int64", "K": "unsigned __int64", "L": "__int128", @@ -108,6 +102,7 @@ "Y": "operator+=", "Z": "operator-=", } +_DATA_SPECIAL_OPERATORS = frozenset("789ABS") _EXTENDED_OPERATORS = { "0": "operator/=", "1": "operator%=", @@ -299,6 +294,12 @@ def templateArguments(self): return args def nameFragment(self, is_leading): + """One fragment, paired with which spelling its special-name code takes, if any. + + The caller needs that apart from the spelling: a tag type is always named by an + identifier, and a special name takes either a signature or a storage class by + which code it is, never both. + """ char = self.peek() if char in string.digits: if self.template_depth or self.templated_table: @@ -306,10 +307,12 @@ def nameFragment(self, is_leading): index = int(self.take()) if index >= len(self.name_backrefs): raise _Bail - return self.name_backrefs[index] + return self.name_backrefs[index], None if char == "?": self.take() if self.eat("$"): + if self.peek() in string.digits + "?": + raise _Bail base = self.identifier() if self.eof() or self.peek() == "@": raise _Bail @@ -317,28 +320,29 @@ def nameFragment(self, is_leading): rendered = f"{base}<{', '.join(args)}>" self.name_backrefs.append(rendered) self.templated_table = True - return rendered + return rendered, None if is_leading: return self.operatorName() raise _Bail name = self.identifier() self.name_backrefs.append(name) - return name + return name, None def operatorName(self): + """The operator or special name, paired with which spelling it takes.""" if self.eat("_"): code = self.take() name = _EXTENDED_OPERATORS.get(code) if name is None: raise _Bail - return name + return name, "data" if code in _DATA_SPECIAL_OPERATORS else "func" code = self.take() if code in ("0", "1"): - return _Structor(code == "1") + return _Structor(code == "1"), "func" name = _OPERATORS.get(code) if name is None: raise _Bail - return name + return name, "func" def qualifiedName(self): """Count a name level against the depth bound; type() is what enforces it.""" @@ -349,22 +353,22 @@ def qualifiedName(self): self.depth -= 1 def qualifiedNameBody(self): - first = self.nameFragment(True) + first, special_form = self.nameFragment(True) scopes = [] while True: if self.eat("@"): break if self.eof(): raise _Bail - scopes.append(self.nameFragment(False)) + scopes.append(self.nameFragment(False)[0]) scopes.reverse() if isinstance(first, _Structor): if not scopes: raise _Bail klass = scopes[-1] first = "~" + klass if first.is_destructor else klass - return "::".join(scopes + [first]), True - return "::".join(scopes + [first]), False + return "::".join(scopes + [first]), True, special_form + return "::".join(scopes + [first]), False, special_form def type(self, quals=()): self.depth += 1 @@ -395,7 +399,9 @@ def typeBody(self, quals): kind = _TAGGED_TYPES[char] if kind == "enum": self.expect("4") - name, _ = self.qualifiedName() + name, _, special_form = self.qualifiedName() + if special_form is not None: + raise _Bail self.simple = False return _apply_quals(_base(f"{kind} {name}"), quals) if char == "Y": @@ -403,8 +409,10 @@ def typeBody(self, quals): if char in _POINTER_KINDS: return self.indirection(_merge(_POINTER_KINDS[char], quals), "*") if char in ("A", "B"): + if quals: + raise _Bail own = ("volatile",) if char == "B" else () - return self.indirection(_merge(own, quals), "&") + return self.indirection(own, "&") if char == "$": return self.dollarType(quals) if char in string.digits: @@ -519,10 +527,12 @@ def parameters(self): def parse(self): self.expect("?") - name, has_no_return_type = self.qualifiedName() + name, has_no_return_type, special_form = self.qualifiedName() if self.eof(): raise _Bail char = self.peek() + if (special_form == "data") != (char == "6"): + raise _Bail if char == "6": self.take() qualifier = _CV.get(self.take()) @@ -569,8 +579,7 @@ def function(self, name, has_no_return_type): else: returns = self.type() params = self.parameters() - if not self.eof(): - self.expect("Z") + self.expect("Z") if not self.eof(): raise _Bail pieces = [] diff --git a/tests/testMsvcDemangler.py b/tests/testMsvcDemangler.py index e5097312..1d98ba46 100644 --- a/tests/testMsvcDemangler.py +++ b/tests/testMsvcDemangler.py @@ -41,6 +41,10 @@ ("?f@@YAXY01H@Z", "void __cdecl f(int[2])"), ("?b11@@YAPAPBDXZ", "char const ** __cdecl b11(void)"), ("?foo_abc@@YAXV?$A@DV?$B@D@@V?$C@D@@@@@Z", "void __cdecl foo_abc(class A, class C>)"), + # clang's Microsoft mangler emits _L/_M for __int128, but llvm-undname cannot read them + # back, so these two are spelled from the mangler's table rather than the reference's + ("?f@@YAX_L@Z", "void __cdecl f(__int128)"), + ("?f@@YAX_M@Z", "void __cdecl f(unsigned __int128)"), # read out of real PDBs rather than the LLVM corpus ("??_7type_info@@6B@", "const type_info::`vftable'"), ( @@ -78,6 +82,22 @@ "?f@@YAXPAHPB0@Z", # a qualifier in front of a back-reference, which MSVC does not form "?f@@YAXZZ", # variadic marker with no parameter before it "?f@@YAX_", # truncated extended type + # a parameter list is closed by the throw specification, so a name that stops before it + # is truncated however plausible the prefix looks + "?f@@YAXHZ", # variadic marker, then nothing where the throw specification belongs + "?a2@@YAHX", # void parameter list with no terminator at all + "?b7@@YANAAMXZ", # parameters, then a variadic marker standing in for the terminator + # __int8/__int16/__int32 are spelled with the plain char/short/int codes, so no mangler + # emits these and a name carrying one is not MSVC-decorated + "?f@@YAX_H@Z", + "?f@@YAX_D@Z", + "??0?$5Class@QAH@@QAE@XZ", # template name starting with a digit + "??$?HH@S@@QEAAAEAU0@H@Z", # operator template, whose name is not modelled + "?e@FTypeWithQuals@@3U?K@A", # tag type named by an operator rather than an identifier + # a special name takes a signature or a storage class by which code it is, never both + "??_7A@B@ad@@YAXPEBQEAD@Z", # vftable given a function signature + "??7Base@@6B@", # operator! given the vftable storage class + "?foo_pbqbd@@YAXPEBBBD@Z", # reference under an enclosing qualifier, which C++ has no form for "??_?@@YAXXZ", # unknown extended operator # the declarator placeholder is a NUL; an identifier carrying one would otherwise be # mistaken for the slot a pointer writes itself into, yielding "class a(*)b" From 407537c15b1302def7bf395a7dc33c421b8cd66f Mon Sep 17 00:00:00 2001 From: r0ny123 <49360849+r0ny123@users.noreply.github.com> Date: Sat, 15 Aug 2026 13:02:06 +0530 Subject: [PATCH 06/23] feat(labels): read the return type of a function returning a class A return type is the one position that carries a qualifier of its own, and that qualifier is not optional decoration: every function returning a class by value is spelled with it, "?A" being the unqualified case rather than an absent one. Nothing here parsed the prefix, so those names were refused - "?combine@geometry@@YA?AUMatrix@@AEBU2@NI@Z" among them. A return type is parsed at three places: a plain function, a function pointer, and a "$$A6" function type. All three take the prefix, so the three now share one reader rather than each calling type() directly. This came out of building a fixture with the toolchain the demangler is meant to read. No PE available here carries MSVC-decorated names - sqlite3's 277 exports are all C - so the MSVC arm of the PE provider had only synthetic symbols behind it, and its first real export was one it could not read. tests/msvc_cxx_pe_xored is that binary: a small translation unit built for x86_64-pc-windows-msvc by clang-cl 22.1.7, exporting free functions, a namespace, a class returned by value, and an extern "C" name. All four of its decorated exports now come back as signatures. Worth twenty names on the reference corpus, 363 to 383 of 609, none given a different spelling. Twenty-one corpus names used the form the whole time; they sat in the declined bucket, where a single aggregate count kept them from reading as a gap. --- .../common/labelprovider/MsvcDemangler.py | 19 ++++++-- tests/msvc_cxx_pe_xored | Bin 0 -> 3072 bytes tests/testMsvcDemangler.py | 11 ++++- tests/testPeSymbolProvider.py | 41 ++++++++++++++++++ 4 files changed, 67 insertions(+), 4 deletions(-) create mode 100644 tests/msvc_cxx_pe_xored diff --git a/src/smda/common/labelprovider/MsvcDemangler.py b/src/smda/common/labelprovider/MsvcDemangler.py index fff213f9..0c3c332f 100644 --- a/src/smda/common/labelprovider/MsvcDemangler.py +++ b/src/smda/common/labelprovider/MsvcDemangler.py @@ -461,6 +461,19 @@ def dollarType(self, quals): return self.functionTypeArgument() raise _Bail + def returnType(self): + """A return type, which unlike any other position may carry a qualifier of its own. + + Every function returning a class by value is spelled this way, so the prefix is + ordinary rather than exotic: `?A` is the unqualified case, not an absent one. + """ + quals = () + if self.eat("?"): + quals = _CV_QUALS.get(self.take()) + if quals is None: + raise _Bail + return self.type(quals) + def indirection(self, own_quals, token): """A pointer or reference: `token` plus its own quals, over a qualified pointee.""" self.eat("E") @@ -470,7 +483,7 @@ def indirection(self, own_quals, token): convention = _CALLING_CONVENTIONS.get(self.take()) if convention is None: raise _Bail - returns = self.type() + returns = self.returnType() params = self.parameters() self.expect("Z") self.simple = False @@ -488,7 +501,7 @@ def functionTypeArgument(self): convention = _CALLING_CONVENTIONS.get(self.take()) if convention is None: raise _Bail - returns = self.type() + returns = self.returnType() params = self.parameters() self.expect("Z") return _function(convention, params, returns) @@ -577,7 +590,7 @@ def function(self, name, has_no_return_type): self.expect("@") returns = None else: - returns = self.type() + returns = self.returnType() params = self.parameters() self.expect("Z") if not self.eof(): diff --git a/tests/msvc_cxx_pe_xored b/tests/msvc_cxx_pe_xored new file mode 100644 index 0000000000000000000000000000000000000000..ca7fff818e956018200193a519f5200dc81ad6a2 GIT binary patch literal 3072 zcmeH}i#ODHAIAr|j^vVYogt&4EHlDRCgT!9l5?*;DK}B`bX0@->HHak5En3<-Uz4}$Qnr~; z^$o1u7j~JjZ4v|7mi%$SvMRbtVR!;XbEB>{Z7WraL|Siz;xmzw5Hv@vu~b!}+F)@Q zjH}(@NL4*rwDck^Rxyox{6xaZQ}ok`Nk>x3E6!)+W_+K1HY+t$x7cZEwaePZ*3RC+arYi4XBXGKZu{IlJiWYq_WSzz z2LuLv6C85jVCbQ+@Q7~@M}8MYi#~ERCYCunwm5_E^CcxFCFlJkH7)bZC;oH4^Peax zzJId6y|woNvx0sV;m^ELQ(KSlS2s4@`pn2{+?7>FE^eMRrU*Y8*`NRS{iTup-LN>1f3^Q#!GA0+{`lwp zlT#4>bc8<>+5Zdv!lIA+mzBf*l?Z<|!vEX;U-CbA_-X$xgufT~VgEni|GfVa><|2Z zwEur_|NjpB=J9W}tdhm?hR2>6Cs=Iech25H)*F-Iv!YTQGVLh*0i}NYdLay==S4-I z5?_MC!S4;Is*_W@BMmxfd=Xp>h%7s<9>d4(j7zDI=4a{blquSSPQhOV4CE0P5J_ln0P4ah&M zqCXaUQJd0(DNOJkJ9Y*351FPH!Ty>ZX*aL6rm!`evS$b1y=oyn=(;V!x_~PGX;`?v zt@Md*uSYtkf3HlxefBS%+CxFb_b2@bue})UBmA3u%et29LP=9P>GQ8+7GZycA8(ax zj~6vMZl;Rma(*;|<+jztJ#wSsz<;@JyaW{&*#qUlYbic9UP5Zk`by-bqc_9lH-tHd z%1|EK*zHNWv2zbP*r?V-GPS`_Anl@?saTlD6Kssfi#(4WWm7-;^Q^$guXcIkr>|I- zZ{_5p^_Gea*i~CNsxIziLEBa#RspkvYmm9fTGL#%B>LnD+-52P=jOyh>P%V!zs#b>G!KZfrVKIUHKsS9;_YD#=XIZmP^M-kPXv zn$i}~8S!mcc&M%vU;an`2V82G^DVkZ2rf9VPOgZC=8LM26~)FhL}10A1mC%RtA*Fk z{~DKfs0G!?gd-Rf)hBs4ylJ0*)?;M#R;ZEh4-O@iVONqp|~v$ zM`-xkaBuzCM*{67$3&WcNEX_e*loSt7@P_21c!o4!I|Jra45JGoC)p(hk{F?S>SGX z!|;}&S>P^c7_~{Lc5@0&@yla3vnkj1Y82n0C#{xpe4|ZH{%mOLqotN zaA%+$;E)-_rEq7VUEnZq88i#r1r7t3fwPcym4U;cWzZ~e7dQ;Igl0fHpdrwb|2F@Z z{!>;LSto{^nHX|*SmX>hQE>`^TuS&Tk=P}`4+Ev!Kl#c$DaQTduIElt^GMbtOYbJ%Vi dynlxoYwu3WW)Eu{&(J^j|M`Fa|F{32UjeJM*+>8Y literal 0 HcmV?d00001 diff --git a/tests/testMsvcDemangler.py b/tests/testMsvcDemangler.py index 1d98ba46..08c49fba 100644 --- a/tests/testMsvcDemangler.py +++ b/tests/testMsvcDemangler.py @@ -41,6 +41,14 @@ ("?f@@YAXY01H@Z", "void __cdecl f(int[2])"), ("?b11@@YAPAPBDXZ", "char const ** __cdecl b11(void)"), ("?foo_abc@@YAXV?$A@DV?$B@D@@V?$C@D@@@@@Z", "void __cdecl foo_abc(class A, class C>)"), + # a return type, alone among positions, carries a qualifier of its own, so every + # function returning a class by value is spelled with one - at all three sites a + # return type is parsed + ("?f@@YA?AUMatrix@@XZ", "struct Matrix __cdecl f(void)"), + ("?f@@YA?BUMatrix@@XZ", "struct Matrix const __cdecl f(void)"), + ("?f@@YAXP6A?AUMatrix@@XZ@Z", "void __cdecl f(struct Matrix (__cdecl *)(void))"), + ("?f@@YAX$$A6A?AUMatrix@@XZ@Z", "void __cdecl f(struct Matrix __cdecl(void))"), + ("?g@@3P6A?AUMatrix@@XZA", "struct Matrix (__cdecl *g)(void)"), # clang's Microsoft mangler emits _L/_M for __int128, but llvm-undname cannot read them # back, so these two are spelled from the mangler's table rather than the reference's ("?f@@YAX_L@Z", "void __cdecl f(__int128)"), @@ -56,7 +64,7 @@ # Measured against llvm-undname 22.1.7 on the shipped corpus. Both are exact so that a # change in either direction has to be an explicit edit rather than passing silently. UNIQUE_CORPUS_NAMES = 609 -CORPUS_NAMES_UNDERSTOOD = 363 +CORPUS_NAMES_UNDERSTOOD = 383 # Forms this demangler does not model. Each must come back exactly as it went in: a wrong # expansion is worse than a decorated name, because it matches neither spelling. @@ -91,6 +99,7 @@ # emits these and a name carrying one is not MSVC-decorated "?f@@YAX_H@Z", "?f@@YAX_D@Z", + "?f@@YA?ZUMatrix@@XZ", # return type carrying a qualifier that is not one "??0?$5Class@QAH@@QAE@XZ", # template name starting with a digit "??$?HH@S@@QEAAAEAU0@H@Z", # operator template, whose name is not modelled "?e@FTypeWithQuals@@3U?K@A", # tag type named by an operator rather than an identifier diff --git a/tests/testPeSymbolProvider.py b/tests/testPeSymbolProvider.py index 36e6450c..52678bdd 100644 --- a/tests/testPeSymbolProvider.py +++ b/tests/testPeSymbolProvider.py @@ -492,6 +492,47 @@ def test_a_signature_with_arguments_is_expanded(self): self.assertIn("double) const", measure[0]) +class TestPeMsvcSymbolFixture(unittest.TestCase): + """The MSVC arm of the dispatch, on a PE that really carries decorated names. + + tests/msvc_cxx_pe_xored is a small C++ translation unit built for + x86_64-pc-windows-msvc by clang-cl 22.1.7, exporting free functions, a namespace, a + class returned by value and one extern "C" name. + """ + + @classmethod + def setUpClass(cls): + fixture = os.path.join(os.path.dirname(os.path.abspath(__file__)), "msvc_cxx_pe_xored") + raw = Path(fixture).read_bytes() + binary = bytes(byte ^ (index % 256) for index, byte in enumerate(raw)) + binary_info = BinaryInfo(binary) + binary_info.file_path = "" + binary_info.base_addr = 0x180000000 + provider = PeSymbolProvider(None) + provider.update(binary_info) + cls.symbols = provider.getFunctionSymbols() + + def test_no_exported_name_is_left_decorated(self): + self.assertEqual([name for name in self.symbols.values() if name.startswith("?")], []) + + def test_a_namespaced_signature_is_expanded(self): + names = set(self.symbols.values()) + + self.assertIn("double __cdecl geometry::dot(struct Matrix const &, struct Matrix const &)", names) + self.assertIn("int __cdecl geometry::classify(struct Matrix const *, char, bool)", names) + + def test_a_class_returned_by_value_keeps_its_return_type(self): + names = set(self.symbols.values()) + + self.assertIn( + "struct Matrix __cdecl geometry::combine(struct Matrix const &, double, unsigned int)", + names, + ) + + def test_an_undecorated_name_is_left_alone(self): + self.assertIn("c_linkage", set(self.symbols.values())) + + class PeNameDispatchTestSuite(unittest.TestCase): """The PE provider picks a demangler from the decoration each name carries.""" From b12acc33b56a0fecb1dba5a04b709faa6eec2163 Mon Sep 17 00:00:00 2001 From: r0ny123 <49360849+r0ny123@users.noreply.github.com> Date: Mon, 17 Aug 2026 12:42:27 +0530 Subject: [PATCH 07/23] fix(labels): refuse a decorated name holding any control character The NUL guard was written because "an expansion holding a control character would travel into the report as a symbol name", and then tested only for NUL. Every other control byte went straight through: "?ctrl\x01@@YAXXZ" expanded to "void __cdecl ctrl\x01(void)". An identifier is copied into the answer verbatim, so a control character in the input is a control character in a reported symbol name, and from there in the serialized report. The parser has no reason to reject one on its own -- it reads them as ordinary identifier bytes -- so testing the input is what keeps the answer clean. A decorated name is read from a NUL-terminated string of source-legal characters and cannot hold any of these, so refusing them costs no real name: the reference corpus is unchanged at 381 names expanded. --- src/smda/common/labelprovider/MsvcDemangler.py | 9 +++++---- tests/testMsvcDemangler.py | 9 +++++++++ 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/src/smda/common/labelprovider/MsvcDemangler.py b/src/smda/common/labelprovider/MsvcDemangler.py index 0c3c332f..48882b57 100644 --- a/src/smda/common/labelprovider/MsvcDemangler.py +++ b/src/smda/common/labelprovider/MsvcDemangler.py @@ -615,13 +615,14 @@ def function(self, name, has_no_return_type): def demangle_msvc_symbol(name): """Return a readable C++ name, or the original when it is not fully understood. - A name carrying a NUL is refused outright: a decorated name is read from a NUL-terminated - string and cannot contain one, and an expansion holding a control character would travel - into the report as a symbol name. + A name carrying a control character is refused outright: a decorated name is read from a + NUL-terminated string of source-legal characters and cannot hold one, and an expansion + holding it would travel into the report as a symbol name. The identifier is copied into + the answer verbatim, so testing the input is what keeps the answer clean. """ if not name or not name.startswith("?"): return name - if "\x00" in name: + if any(char < " " or char == "\x7f" for char in name): return name try: return _Demangler(name).parse() diff --git a/tests/testMsvcDemangler.py b/tests/testMsvcDemangler.py index 08c49fba..59923a4c 100644 --- a/tests/testMsvcDemangler.py +++ b/tests/testMsvcDemangler.py @@ -133,6 +133,15 @@ def test_a_name_carrying_the_declarator_placeholder_is_refused(self): self.assertEqual(demangle_msvc_symbol(evil), evil) + def test_a_name_carrying_any_control_character_is_refused(self): + # an identifier is copied into the answer verbatim, so a control character in the + # input is one in a reported symbol name; the parser reads these as ordinary + # identifier bytes and would otherwise expand them + for code in (0x01, 0x07, 0x0A, 0x0D, 0x1B, 0x7F): + evil = f"?ctrl{chr(code)}@@YAXXZ" + with self.subTest(code=code): + self.assertEqual(demangle_msvc_symbol(evil), evil) + def test_names_that_are_not_msvc_decorated_are_left_alone(self): for name in ("", "plain_name", "_ZN4test4funcEv", "_RNvC6_123foo3bar", "_ReadFile@20"): with self.subTest(name=name): From 07d93114ecdef3386c057aa6423ba66d292e535a Mon Sep 17 00:00:00 2001 From: r0ny123 <49360849+r0ny123@users.noreply.github.com> Date: Mon, 17 Aug 2026 20:50:48 +0530 Subject: [PATCH 08/23] feat(labels): resolve back-references the way the mangler wrote them Back-references were the largest thing this demangler could not read. A name table entry was appended for every name met, so any name that used one after a repeat resolved to the wrong entry, and a template made the table unusable altogether: every back-reference inside or after one declined. The mangler's rules are three, and clang's own Microsoft mangler is where they are stated rather than the demangler that reads it. A name is recorded only when the table does not already hold it and while it holds fewer than ten. A template instantiation is written in a fresh scope, which opens before the template's own name, so that name takes index 0 and its arguments are numbered from 1. The rendered template belongs to the enclosing scope instead -- except for the symbol's own name, which is not recorded at all, so "??$f@H@N@@YAXV0@@Z" resolves 0 to N. Two shapes that are not names also stopped being answered. "B" was read as a reference introducer, which would make it a volatile-qualified reference -- something C++ cannot write and no mangler emits. And an argument back-reference was read as a type anywhere a type belongs, when it stands for a whole argument and is only one where a whole argument is: "?h@@YAXPAHPA0@Z" got an answer for a name that cannot exist. Measured against llvm-undname 22.1.8. The reference corpus goes from 383 of 609 spelled identically to 415, with none given a different spelling. Differential fuzzing over 15785 names derived from that corpus, on three seeds: wrong answers fall from 4, 5 and 6 to 0, 3 and 2, and the names read rise about nine per cent on each. The five that remain are shapes this demangler already answered before this change, in three families it still models incorrectly; each is recorded with the name that reproduces it. One corpus pair asserted a spelling the reference does not produce -- "?g@@YAXUS@@PA0@Z" was recorded as a demangled name although llvm-undname refuses it, which the argument back-reference rule now surfaces. It moves to the declined list. --- .../common/labelprovider/MsvcDemangler.py | 95 +++++++++++++------ tests/testMsvcDemangler.py | 66 ++++++++++++- 2 files changed, 127 insertions(+), 34 deletions(-) diff --git a/src/smda/common/labelprovider/MsvcDemangler.py b/src/smda/common/labelprovider/MsvcDemangler.py index 48882b57..35301c09 100644 --- a/src/smda/common/labelprovider/MsvcDemangler.py +++ b/src/smda/common/labelprovider/MsvcDemangler.py @@ -235,7 +235,8 @@ def __init__(self, mangled): self.arg_backrefs = [] self.simple = True self.template_depth = 0 - self.templated_table = False + self.at_symbol_name = True + self.pointee_depth = 0 self.member_cv = "" self.depth = 0 self.max_render = 8 * len(mangled) + 256 @@ -273,25 +274,42 @@ def identifier(self): self.pos = end + 1 return name - def templateArguments(self): - """Template arguments, in their own back-reference scopes. + def rememberName(self, name): + """Record a name for later back-references, the way the mangler recorded it. - How those scopes interact with the enclosing name table is not modelled, so a name - back-reference inside them declines instead of risking a wrong name. + A name is added only when it is not already held and while the table is under ten + entries. Both rules move the indices every later back-reference resolves against, so + appending unconditionally does not merely miss a compression - it reads "?3" as the + fourth name where the mangler counted three, and answers a name the grammar refuses. + """ + if name not in self.name_backrefs and len(self.name_backrefs) < 10: + self.name_backrefs.append(name) + + def templateInstantiation(self): + """A "?$" template name, read in its own back-reference scope. + + The scope opens before the template's own name does, so that name takes index 0 + inside it and the arguments are numbered from 1 - which is what a back-reference + written inside the argument list resolves against. The rendered result belongs to + the enclosing scope instead, and the caller records it there. """ - args = [] saved_names, saved_args = self.name_backrefs, self.arg_backrefs self.name_backrefs, self.arg_backrefs = [], [] self.template_depth += 1 try: + base = self.identifier() + self.rememberName(base) + if self.eof() or self.peek() == "@": + raise _Bail + args = [] while not self.eat("@"): if self.eof(): raise _Bail args.append(self.rendered(self.type())) + return f"{base}<{', '.join(args)}>" finally: self.template_depth -= 1 self.name_backrefs, self.arg_backrefs = saved_names, saved_args - return args def nameFragment(self, is_leading): """One fragment, paired with which spelling its special-name code takes, if any. @@ -300,10 +318,13 @@ def nameFragment(self, is_leading): identifier, and a special name takes either a signature or a storage class by which code it is, never both. """ + # every later qualified name belongs to a type, and the exception below is only for + # the symbol's own name, so the very first leading fragment is the one that counts + is_symbol_name = is_leading and self.at_symbol_name + if is_leading: + self.at_symbol_name = False char = self.peek() if char in string.digits: - if self.template_depth or self.templated_table: - raise _Bail index = int(self.take()) if index >= len(self.name_backrefs): raise _Bail @@ -313,19 +334,18 @@ def nameFragment(self, is_leading): if self.eat("$"): if self.peek() in string.digits + "?": raise _Bail - base = self.identifier() - if self.eof() or self.peek() == "@": - raise _Bail - args = self.templateArguments() - rendered = f"{base}<{', '.join(args)}>" - self.name_backrefs.append(rendered) - self.templated_table = True + rendered = self.templateInstantiation() + if not is_symbol_name: + # the symbol's own template name is the one exception the mangler makes: + # it is not recorded, so "??$f@H@N@@YAXV0@@Z" resolves 0 to N, not to + # f. A template met anywhere else is recorded like any other name. + self.rememberName(rendered) return rendered, None if is_leading: return self.operatorName() raise _Bail name = self.identifier() - self.name_backrefs.append(name) + self.rememberName(name) return name, None def operatorName(self): @@ -382,9 +402,10 @@ def type(self, quals=()): def typeBody(self, quals): """One type, qualified by `quals`. - A digit is a back-reference standing for a whole argument type. The reference - implementation rejects a qualifier in front of one, so a qualified back-reference - declines rather than inventing a spelling. + A digit is a back-reference standing for a whole argument type, so it is only a type + where a whole argument is one. A qualifier in front of it, or a pointer or reference + around it - "?h@@YAXPAHPA0@Z" - is a name the mangler cannot have produced, and + reading one invented a spelling for it. """ char = self.take() if char in _BASIC_TYPES: @@ -408,20 +429,21 @@ def typeBody(self, quals): return self.arrayType(quals) if char in _POINTER_KINDS: return self.indirection(_merge(_POINTER_KINDS[char], quals), "*") - if char in ("A", "B"): + if char == "A": if quals: raise _Bail - own = ("volatile",) if char == "B" else () - return self.indirection(own, "&") + # only "A" introduces a reference. "B" would be a volatile-qualified one, which + # C++ has no way to write and no Microsoft-compatible mangler emits, so reading + # it produced answers for names that cannot exist: "?f2@@YAXBDPAD@Z". + return self.indirection((), "&") if char == "$": return self.dollarType(quals) if char in string.digits: - if quals: - raise _Bail - index = int(char) - if index >= len(self.arg_backrefs): - raise _Bail - return self.arg_backrefs[index] + # a digit is an argument back-reference, which stands for a whole argument and is + # read as one in parameters(). Reaching it here means it was written where only a + # type belongs - a pointee, a template argument, a return type - and the mangler + # writes none of those; reading them invented spellings for impossible names. + raise _Bail raise _Bail def rendered(self, node, declarator=""): @@ -484,14 +506,25 @@ def indirection(self, own_quals, token): if convention is None: raise _Bail returns = self.returnType() - params = self.parameters() + # a parameter of this function type is a whole-argument position again, so a + # back-reference is legal there even when the function type is itself a pointee + saved_pointee_depth = self.pointee_depth + self.pointee_depth = 0 + try: + params = self.parameters() + finally: + self.pointee_depth = saved_pointee_depth self.expect("Z") self.simple = False return _indirection(token, own_quals, _function(convention, params, returns)) pointee_quals = _CV_QUALS.get(self.take()) if pointee_quals is None: raise _Bail - pointee = self.type(pointee_quals) + self.pointee_depth += 1 + try: + pointee = self.type(pointee_quals) + finally: + self.pointee_depth -= 1 self.simple = False return _indirection(token, own_quals, pointee) diff --git a/tests/testMsvcDemangler.py b/tests/testMsvcDemangler.py index 59923a4c..0515a290 100644 --- a/tests/testMsvcDemangler.py +++ b/tests/testMsvcDemangler.py @@ -30,7 +30,6 @@ ("?h2@@3QBHB", "int const *const h2"), ("?mbb@S@@QAEX_N0@Z", "public: void __thiscall S::mbb(bool, bool)"), ("?f@@YAXHZZ", "void __cdecl f(int, ...)"), - ("?g@@YAXUS@@PA0@Z", "void __cdecl g(struct S, struct S *)"), # the declarator cases: a name or a further pointer belongs inside its own type ("?j@@3P6GHCE@ZA", "int (__stdcall *j)(signed char, unsigned char)"), ("?g@@3PAP6AHXZA", "int (__cdecl **g)(void)"), @@ -64,12 +63,11 @@ # Measured against llvm-undname 22.1.7 on the shipped corpus. Both are exact so that a # change in either direction has to be an explicit edit rather than passing silently. UNIQUE_CORPUS_NAMES = 609 -CORPUS_NAMES_UNDERSTOOD = 383 +CORPUS_NAMES_UNDERSTOOD = 418 # Forms this demangler does not model. Each must come back exactly as it went in: a wrong # expansion is worse than a decorated name, because it matches neither spelling. DECLINED = [ - "??$f@T@PR18204@@@PR18204@@YAHPAT@0@@Z", "?x@@3PAY02Hz", # truncated "?", "??", @@ -115,6 +113,68 @@ ] +# Back-reference behaviour, each pair checked against llvm-undname. A name is recorded for +# later reference only when it is not already held and while the table is under ten entries, +# and a template instantiation is read in its own scope. +BACKREFS = [ + # the table holds the function's own name first, so "1" is the first type named after it + ("?f@@YAXVA@@V1@@Z", "void __cdecl f(class A, class A)"), + # a repeat is not recorded again: the table is f, A, B, so "1" is still A + ("?f@@YAXVA@@VB@@VA@@V1@@Z", "void __cdecl f(class A, class B, class A, class A)"), + # the tenth entry is the last one recorded, so "9" is I and J never enters the table + ( + "?f@@YAXVA@@VB@@VC@@VD@@VE@@VF@@VG@@VH@@VI@@VJ@@V9@@Z", + "void __cdecl f(class A, class B, class C, class D, class E, class F, class G, " + "class H, class I, class J, class I)", + ), + # a template opens its own scope, taking index 0 itself, so its first argument is 1 + ( + "?foo_abbb@@YAXV?$A@V?$B@D@@V1@V1@@@@Z", + "void __cdecl foo_abbb(class A, class B, class B>)", + ), + # the rendered template belongs to the enclosing scope: 1 is B and 2 is N + ("?b_foo@@YA?AV?$B@D@N@@V12@@Z", "class N::B __cdecl b_foo(class N::B)"), + ( + "?abc_foo@@YA?AV?$A@DV?$B@D@N@@V?$C@D@2@@N@@XZ", + "class N::A, class N::C> __cdecl abc_foo(void)", + ), + # the symbol's own template name is the exception: it is not recorded, so 0 is N + ("??$f@H@N@@YAXV0@@Z", "void __cdecl N::f(class N)"), +] + +# Shapes the grammar does not allow, each confirmed refused by llvm-undname. Every one of +# these was answered before the back-reference table learned how the mangler fills it. +BACKREF_DECLINED = [ + "?f@@YAXVA@@VB@@VA@@V3@@Z", # A is recorded once, so there is no fourth name + "??$f@H@N@@YAXV1@@Z", # only N is recorded, and the symbol's own template name is not + "?f2@@YAXBDPAD@Z", # "B" would introduce a volatile reference, which C++ cannot write + "?foo_qay04h@@YAXBEAY04H@Z", + "?h@@YAXPAHPA0@Z", # an argument back-reference is a whole argument, never a pointee + "?h@@YAXPAHAA0@Z", + "?g@@YAXUS@@PA0@Z", # the reference refuses this too, whatever the back-reference names + # declined rather than refused: the reference spells this "int &const *", a qualifier on + # a reference that C++ has no form for, so the decorated name stays the better answer + "?f@@YAXPBAAH@Z", +] + + +class MsvcBackReferenceTestSuite(unittest.TestCase): + def test_back_references_resolve_the_way_the_mangler_numbered_them(self): + for mangled, expected in BACKREFS: + with self.subTest(mangled=mangled): + self.assertEqual(demangle_msvc_symbol(mangled), expected) + + def test_names_the_back_reference_rules_forbid_are_refused(self): + for mangled in BACKREF_DECLINED: + with self.subTest(mangled=mangled): + self.assertEqual(demangle_msvc_symbol(mangled), mangled) + + def test_a_reference_and_a_back_referenced_argument_still_read(self): + # the two refusals above are positional, not a retreat from these forms + self.assertEqual(demangle_msvc_symbol("?f@@YAXADPAD@Z"), "void __cdecl f(char *const volatile &)") + self.assertEqual(demangle_msvc_symbol("?h@@YAXPAH0@Z"), "void __cdecl h(int *, int *)") + + class MsvcDemanglerTestSuite(unittest.TestCase): def test_known_names_match_the_reference_spelling(self): for mangled, expected in DEMANGLED: From a4c532cd21e87b35e492406c492df519d6a52729 Mon Sep 17 00:00:00 2001 From: r0ny123 <49360849+r0ny123@users.noreply.github.com> Date: Mon, 17 Aug 2026 21:33:42 +0530 Subject: [PATCH 09/23] fix(labels): read four rules the grammar states and this did not Differential fuzzing against llvm-undname 22.1.8, widened from three seeds to seven, kept finding names answered where the grammar has no answer, and one class spelled differently from the reference. Four rules close them. The "6" storage form belongs to the vftable family. Every data-special operator was reading it, so "??_Bx@@6B@" got an answer; vcall, typeof and the local static guard take a storage class this parser does not model, and now decline rather than borrow the vftable's. No mangler writes __ptr64 in front of a function type. "P6A" and "R6A" are names, "PE6A" and "RE6A" are not. "$$C" qualifies an array element or a template argument. Written as a parameter of its own, or as the pointee of a pointer or reference, it is not a type: "?f@@YAX$$CBH@Z" was answered. An earlier attempt to require a template scope cost eight real names, because an array element is the other place it belongs. A data symbol's trailing qualifier belongs to what its outermost pointer points at, not to the pointer, and was dropped whenever the type was not a plain one: "?s@@3PADB" is "char const *s", and "?s@@3PAPADB" is "char *const *s". A qualifier the pointee already spells is not spelled twice. Spacing follows from the same source: a pointer or reference sigil abuts a type ending in neither an alphanumeric character nor ">", while a named declarator is spaced off whatever precedes it. "struct S_*" and "enum ", and abuts it to anything else: "struct S *" but "struct S_*" + # and "enum " or (tail.isascii() and tail.isalnum())) + return node[1] + ("" if abuts else " ") + declarator if kind == "ind": token = node[1] + " ".join(node[2]) nested_function = "(" in declarator and not declarator.startswith(("*", "&")) @@ -205,6 +217,22 @@ def _apply_quals(node, quals): return _base(f"{node[1]} {' '.join(quals)}") if quals else node +def _qualify(node, quals): + """Add qualifiers to a parsed type, wherever that type keeps them. + + A named type spells them in its text; a pointer or reference carries its own, so they + join those instead of being appended to a rendering that already placed the sigil. + """ + if not quals: + return node + if node[0] == "ind": + return _indirection(node[1], _merge(node[2], quals), node[3]) + # a named type spells its qualifiers in its own text, so one it already carries must not + # be spelled twice: "?s@@3QBDD" is "char const volatile *const", not "char const const .." + spelled = node[1].split() + return _apply_quals(node, tuple(qual for qual in quals if qual not in spelled)) + + class _Structor: """A constructor or destructor: its spelling comes from the class it belongs to.""" @@ -237,6 +265,7 @@ def __init__(self, mangled): self.template_depth = 0 self.at_symbol_name = True self.pointee_depth = 0 + self.array_element_depth = 0 self.member_cv = "" self.depth = 0 self.max_render = 8 * len(mangled) + 256 @@ -355,6 +384,8 @@ def operatorName(self): name = _EXTENDED_OPERATORS.get(code) if name is None: raise _Bail + if code in _UNMODELLED_DATA_SPECIAL_OPERATORS: + raise _Bail return name, "data" if code in _DATA_SPECIAL_OPERATORS else "func" code = self.take() if code in ("0", "1"): @@ -461,7 +492,11 @@ def dimension(self): def arrayType(self, quals): count = self.dimension() dims = "".join(f"[{self.dimension()}]" for _ in range(count)) - element = self.type(quals) + self.array_element_depth += 1 + try: + element = self.type(quals) + finally: + self.array_element_depth -= 1 self.simple = False return _array(dims, element) @@ -472,6 +507,11 @@ def dollarType(self, quals): if kind == "Q": return self.indirection(quals, "&&") if kind == "C": + if not (self.template_depth or self.array_element_depth): + # "$$C" qualifies an array element or a template argument. As a parameter of + # its own, or as the pointee of a pointer or reference, it is not a type: + # "?f@@YAX$$CBH@Z" and "?f@@YAXPA$$CBH@Z" are not names + raise _Bail extra = _CV_QUALS.get(self.take()) if extra is None: raise _Bail @@ -498,10 +538,14 @@ def returnType(self): def indirection(self, own_quals, token): """A pointer or reference: `token` plus its own quals, over a qualified pointee.""" - self.eat("E") + has_ptr64 = self.eat("E") if self.eat("I"): own_quals = own_quals + ("__restrict",) if self.eat("6"): + if has_ptr64: + # no mangler writes __ptr64 in front of a function type: "P6A" and "R6A" + # are names, "PE6A" and "RE6A" are not + raise _Bail convention = _CALLING_CONVENTIONS.get(self.take()) if convention is None: raise _Bail @@ -597,6 +641,11 @@ def parse(self): raise _Bail if self.simple: declared = _apply_quals(declared, _CV_QUALS[trailing]) + elif declared[0] == "ind": + # a data symbol's trailing qualifier belongs to what its outermost pointer + # points at, not to the pointer: "?s@@3PADB" is "char const *s", and + # "?s@@3PAPADB" is "char *const *s" + declared = _indirection(declared[1], declared[2], _qualify(declared[3], _CV_QUALS[trailing])) return f"{_DATA_ACCESS[char]}{self.rendered(declared, name)}" return self.function(name, has_no_return_type) diff --git a/tests/testMsvcDemangler.py b/tests/testMsvcDemangler.py index 0515a290..de631871 100644 --- a/tests/testMsvcDemangler.py +++ b/tests/testMsvcDemangler.py @@ -158,6 +158,59 @@ ] +# Rules derived from llvm-undname probes, each pinned with the name that proved it. +GRAMMAR_RULES = [ + # the "6" storage form belongs to the vftable family only + ("??_7x@@6B@", "const x::`vftable'"), + ("??_8x@@6B@", "const x::`vbtable'"), + ("??_Sx@@6B@", "const x::`local vftable'"), + # a data symbol's trailing qualifier belongs to what its outermost pointer points at + ("?s@@3PADB", "char const *s"), + ("?s@@3PADD", "char const volatile *s"), + ("?s@@3QBDD", "char const volatile *const s"), + ("?s@@3PAPADB", "char *const *s"), + ("?s@@3HB", "int const s"), + # a sigil abuts a type that ends in neither an alphanumeric character nor ">" + ("?f@@YAXPAUS@@@Z", "void __cdecl f(struct S *)"), + ("?f@@YAXPAUS_@@@Z", "void __cdecl f(struct S_*)"), + ("?f@@YAXPAUS$@@@Z", "void __cdecl f(struct S$*)"), + ("?f@@YAXPAV?$B@VA@@@@@Z", "void __cdecl f(class B *)"), + # ... while a named declarator is spaced off whatever precedes it + ("?fooE@@YA?AW4E$@@XZ", "enum E$ __cdecl fooE(void)"), + # "$$C" qualifies an array element or a template argument + ("?f@@YAXAAY144$$CBH@Z", "void __cdecl f(int const (&)[5][5])"), + ("?f@@YAXV?$T@$$CBH@@@Z", "void __cdecl f(class T)"), + # a function pointer takes no __ptr64 modifier, but is otherwise read + ("?p@@3R6AHHH@ZA", "int (__cdecl *volatile p)(int, int)"), + ("?p@@3Q6AHHH@ZA", "int (__cdecl *const p)(int, int)"), +] + +# ... and the shapes those same rules refuse, each confirmed refused by llvm-undname +GRAMMAR_DECLINED = [ + "??_9x@@6B@", # vcall, typeof and the local static guard take a storage class this + "??_Ax@@6B@", # parser does not model, never the vftable family's "6" form + "??_Bx@@6B@", + "?p@@3PE6AHHH@ZA", # __ptr64 is not written in front of a function type + "?p@@3RE6AHHH@ZA", + "?f@@YAX$$CBH@Z", # "$$C" is not a parameter of its own, nor a pointee + "?f@@YAXPA$$CBH@Z", + "?f@@YAXAA$$CBH@Z", + "?f@@YAXAAY144$$CZH@Z", # ... and in those positions it still needs a real qualifier +] + + +class MsvcGrammarRuleTestSuite(unittest.TestCase): + def test_rules_spell_names_the_way_the_reference_does(self): + for mangled, expected in GRAMMAR_RULES: + with self.subTest(mangled=mangled): + self.assertEqual(demangle_msvc_symbol(mangled), expected) + + def test_shapes_those_rules_forbid_are_refused(self): + for mangled in GRAMMAR_DECLINED: + with self.subTest(mangled=mangled): + self.assertEqual(demangle_msvc_symbol(mangled), mangled) + + class MsvcBackReferenceTestSuite(unittest.TestCase): def test_back_references_resolve_the_way_the_mangler_numbered_them(self): for mangled, expected in BACKREFS: From dedd1ead4d99323df149b48ab520ae1caabef43b Mon Sep 17 00:00:00 2001 From: r0ny123 <49360849+r0ny123@users.noreply.github.com> Date: Mon, 17 Aug 2026 22:03:21 +0530 Subject: [PATCH 10/23] feat(labels): read the unnamed namespace a translation unit gets Every anonymous namespace in an MSVC-built binary is spelled "?A" with a discriminator, and none of them were read: a name qualified by one declined outright, which is why the LLVM sources in the reference corpus were among the names this could not spell. Two details decide it. The discriminator tells two unnamed namespaces apart inside one binary and the reference does not spell it, so both render as `anonymous namespace' - which is what the source looks like too. But it is the discriminator, not that spelling, that a later back-reference resolves to: "?f@?A0x1@@YAXV1@@Z" names its parameter "class 0x1". Recording the rendering instead shifts every later index and spells a different name, which is how the mutated LLVM symbols in the fuzz corpus caught it. The fragment is read only where a namespace can appear. A leading "??A" is operator[], and taking it as a namespace turned "??AFoo@@QAGXXZ" into "Foo::`anonymous namespace'(void)". Corpus 415 to 417 of 609 exact, none spelled differently, and about 25 more names read per fuzz seed. --- .../common/labelprovider/MsvcDemangler.py | 28 +++++++++++++++++++ tests/testMsvcDemangler.py | 27 +++++++++++++++++- 2 files changed, 54 insertions(+), 1 deletion(-) diff --git a/src/smda/common/labelprovider/MsvcDemangler.py b/src/smda/common/labelprovider/MsvcDemangler.py index 75a03e9b..e3e49e50 100644 --- a/src/smda/common/labelprovider/MsvcDemangler.py +++ b/src/smda/common/labelprovider/MsvcDemangler.py @@ -371,12 +371,40 @@ def nameFragment(self, is_leading): self.rememberName(rendered) return rendered, None if is_leading: + # "??A" here is operator[], not the namespace below: the leading fragment is + # the symbol's own name, and a namespace can only qualify it return self.operatorName() + if self.peek() == "A": + return self.anonymousNamespace(), None raise _Bail name = self.identifier() self.rememberName(name) return name, None + def anonymousNamespace(self): + """The unnamed namespace of one translation unit: "?A" and an optional discriminator. + + The discriminator tells two of them apart inside one binary, and the reference does + not spell it, so two anonymous namespaces render alike - which is what C++ source + looks like too. + """ + self.expect("A") + start = self.pos + if self.eat("0"): + if not self.eat("x"): + raise _Bail + digits = 0 + while not self.eof() and self.peek() in string.hexdigits: + self.take() + digits += 1 + if not digits: + raise _Bail + # the discriminator, not the spelling, is what a later back-reference resolves to: + # "?f@?A0x1@@YAXV1@@Z" names its parameter "class 0x1" + self.rememberName(self.text[start : self.pos]) + self.expect("@") + return "`anonymous namespace'" + def operatorName(self): """The operator or special name, paired with which spelling it takes.""" if self.eat("_"): diff --git a/tests/testMsvcDemangler.py b/tests/testMsvcDemangler.py index de631871..3c8a98aa 100644 --- a/tests/testMsvcDemangler.py +++ b/tests/testMsvcDemangler.py @@ -63,7 +63,7 @@ # Measured against llvm-undname 22.1.7 on the shipped corpus. Both are exact so that a # change in either direction has to be an explicit edit rather than passing silently. UNIQUE_CORPUS_NAMES = 609 -CORPUS_NAMES_UNDERSTOOD = 418 +CORPUS_NAMES_UNDERSTOOD = 420 # Forms this demangler does not model. Each must come back exactly as it went in: a wrong # expansion is worse than a decorated name, because it matches neither spelling. @@ -199,6 +199,31 @@ ] +ANONYMOUS_NAMESPACE = [ + ("?x@?A0x12345678@@3HA", "int `anonymous namespace'::x"), + ("?x@?A@@3HA", "int `anonymous namespace'::x"), + ("?x@?A0xABCDEF12@N@@3HA", "int N::`anonymous namespace'::x"), + ("?f@?A0x1@@YAXXZ", "void __cdecl `anonymous namespace'::f(void)"), + # the discriminator, not the spelling, is what a later back-reference resolves to + ("?f@?A0x1@@YAXV1@@Z", "void __cdecl `anonymous namespace'::f(class 0x1)"), + ("?f@?A0x1@N@@YAXV2@@Z", "void __cdecl N::`anonymous namespace'::f(class N)"), + # a leading "??A" is operator[], which a namespace fragment must not claim + ("??AFoo@@QAGXXZ", "public: void __stdcall Foo::operator[](void)"), +] + + +class MsvcAnonymousNamespaceTestSuite(unittest.TestCase): + def test_an_unnamed_namespace_is_spelled_and_recorded_the_way_it_is_mangled(self): + for mangled, expected in ANONYMOUS_NAMESPACE: + with self.subTest(mangled=mangled): + self.assertEqual(demangle_msvc_symbol(mangled), expected) + + def test_a_discriminator_that_is_not_hexadecimal_is_refused(self): + for mangled in ("?x@?A0@@3HA", "?x@?A0x@@3HA", "?x@?A0xZZ@@3HA"): + with self.subTest(mangled=mangled): + self.assertEqual(demangle_msvc_symbol(mangled), mangled) + + class MsvcGrammarRuleTestSuite(unittest.TestCase): def test_rules_spell_names_the_way_the_reference_does(self): for mangled, expected in GRAMMAR_RULES: From ed1ed8e89957eb5e5234e846ef35ceb94eba4d54 Mon Sep 17 00:00:00 2001 From: r0ny123 <49360849+r0ny123@users.noreply.github.com> Date: Mon, 17 Aug 2026 22:30:55 +0530 Subject: [PATCH 11/23] feat(labels): read member function pointers and template integers Two constructs ordinary C++ produces, and neither was read. A pointer to member function is how any callback into a class is spelled, and an integer template argument is what std::array and every fixed-size container carry. Between them they account for most of what the reference corpus still declined. A member pointer qualifies its declarator with the class rather than its type -- "void (__thiscall S::*)(void)" -- and the member's own cv follows the parameter list, where a member function keeps it. The pointer's own qualifiers stay on the pointer: "Q8" is "S::*const". The __ptr64 modifier is no more written here than in front of a plain function. An integer is a single digit standing for itself plus one, or nibbles "A" to "P" ended by "@", with a leading "?" for negative. The accumulator is 64 bits and wraps, which the corpus shows: the eighteen nibbles of "$0HPPPPPPPPPPPPPPPPPP@" are 18446744073709551615, and a magnitude that wraps to zero under a minus sign is spelled "-0". Parenthesising the declarator needed an anchored test rather than a substring one. A rendered parameter may itself hold "::*", and treating that as a member pointer put brackets around "std::forward" -- caught on the corpus, which is why the test now only matches a declarator whose own prefix is an owner. Corpus 417 to 457 of 609 exact, none spelled differently, and about 250 more names read per fuzz seed. --- .../common/labelprovider/MsvcDemangler.py | 76 +++++++++++++++++-- tests/testMsvcDemangler.py | 46 ++++++++++- 2 files changed, 116 insertions(+), 6 deletions(-) diff --git a/src/smda/common/labelprovider/MsvcDemangler.py b/src/smda/common/labelprovider/MsvcDemangler.py index e3e49e50..711c8647 100644 --- a/src/smda/common/labelprovider/MsvcDemangler.py +++ b/src/smda/common/labelprovider/MsvcDemangler.py @@ -1,5 +1,6 @@ """Demangling for MSVC decorated symbol names.""" +import re import string from functools import lru_cache @@ -149,6 +150,7 @@ _POINTER_KINDS = {"P": (), "Q": ("const",), "R": ("volatile",), "S": ("const", "volatile")} _CV_QUALS = {"A": (), "B": ("const",), "C": ("volatile",), "D": ("const", "volatile")} _CV = {"A": "", "B": " const", "C": " volatile", "D": " const volatile"} +_MEMBER_POINTER_RE = re.compile(r"^[^()]*::\*") def _base(text): @@ -163,8 +165,8 @@ def _array(dims, inner): return ("array", dims, inner) -def _function(convention, params, returns): - return ("func", convention, params, returns) +def _function(convention, params, returns, member_cv=""): + return ("func", convention, params, returns, member_cv) def _render(node, declarator=""): @@ -195,12 +197,14 @@ def _render(node, declarator=""): if declarator.startswith(("*", "&")): declarator = f"({declarator})" return _render(node[2], declarator + node[1]) - convention, params, returns = node[1], node[2], node[3] - if declarator.startswith(("*", "&")): + convention, params, returns, member_cv = node[1], node[2], node[3], node[4] + # a member-pointer declarator is "Owner::*", possibly qualified; the test is anchored so + # that a nested type's own "::*" - which a rendered parameter may hold - does not count + if declarator.startswith(("*", "&")) or _MEMBER_POINTER_RE.match(declarator): declarator = f"({convention} {declarator})" else: declarator = f"{convention} {declarator}" if declarator else convention - return _render(returns, f"{declarator}({params})") + return _render(returns, f"{declarator}({params}){member_cv}") def _merge(left, right): @@ -528,7 +532,40 @@ def arrayType(self, quals): self.simple = False return _array(dims, element) + def templateInteger(self): + """The integer a "$0" template argument carries. + + A single digit stands for itself plus one, so "$00" is 1. Anything larger is spelled + as nibbles from "A" to "P" terminated by "@", most significant first, and a leading + "?" negates it: "$0M@" is 12 and "$0?0" is -1. + + The accumulator is 64 bits wide and wraps, which is visible in the corpus: the + eighteen nibbles of "$0HPPPPPPPPPPPPPPPPPP@" are 18446744073709551615, and a + magnitude that wraps to zero under a minus sign is spelled "-0". + """ + negative = self.eat("?") + char = self.peek() + if char in string.digits: + value = int(self.take()) + 1 + else: + value = 0 + digits = 0 + while not self.eof() and "A" <= self.peek() <= "P": + value = (value * 16 + (ord(self.take()) - ord("A"))) & 0xFFFFFFFFFFFFFFFF + digits += 1 + if not digits: + raise _Bail + self.expect("@") + return f"-{value}" if negative else str(value) + def dollarType(self, quals): + if self.peek() == "0": + if not self.template_depth: + # an integer is an argument, not a type: it appears only in a template list + raise _Bail + self.take() + self.simple = False + return _base(self.templateInteger()) if not self.eat("$"): raise _Bail kind = self.take() @@ -569,6 +606,8 @@ def indirection(self, own_quals, token): has_ptr64 = self.eat("E") if self.eat("I"): own_quals = own_quals + ("__restrict",) + if self.eat("8"): + return self.memberFunctionPointer(own_quals, token, has_ptr64) if self.eat("6"): if has_ptr64: # no mangler writes __ptr64 in front of a function type: "P6A" and "R6A" @@ -600,6 +639,33 @@ def indirection(self, own_quals, token): self.simple = False return _indirection(token, own_quals, pointee) + def memberFunctionPointer(self, own_quals, token, has_ptr64): + """A pointer to member function: "P8" and the class it points into. + + The class qualifies the declarator rather than the type - "void (__thiscall S::*)()" + - and the member's own cv follows the parameter list, where a member function keeps + it. The __ptr64 modifier is no more written here than in front of a plain function. + """ + if has_ptr64: + raise _Bail + owner = self.qualifiedName()[0] + member_cv = _CV.get(self.take()) + if member_cv is None: + raise _Bail + convention = _CALLING_CONVENTIONS.get(self.take()) + if convention is None: + raise _Bail + returns = self.returnType() + saved_pointee_depth = self.pointee_depth + self.pointee_depth = 0 + try: + params = self.parameters() + finally: + self.pointee_depth = saved_pointee_depth + self.expect("Z") + self.simple = False + return _indirection(f"{owner}::{token}", own_quals, _function(convention, params, returns, member_cv)) + def functionTypeArgument(self): self.simple = False self.expect("6") diff --git a/tests/testMsvcDemangler.py b/tests/testMsvcDemangler.py index 3c8a98aa..148df2fe 100644 --- a/tests/testMsvcDemangler.py +++ b/tests/testMsvcDemangler.py @@ -63,7 +63,7 @@ # Measured against llvm-undname 22.1.7 on the shipped corpus. Both are exact so that a # change in either direction has to be an explicit edit rather than passing silently. UNIQUE_CORPUS_NAMES = 609 -CORPUS_NAMES_UNDERSTOOD = 420 +CORPUS_NAMES_UNDERSTOOD = 460 # Forms this demangler does not model. Each must come back exactly as it went in: a wrong # expansion is worse than a decorated name, because it matches neither spelling. @@ -224,6 +224,50 @@ def test_a_discriminator_that_is_not_hexadecimal_is_refused(self): self.assertEqual(demangle_msvc_symbol(mangled), mangled) +MEMBER_POINTERS_AND_INTEGERS = [ + ("?f@@YAXP8S@@AEXXZ@Z", "void __cdecl f(void (__thiscall S::*)(void))"), + ("?f@@YAXP8S@@BEXXZ@Z", "void __cdecl f(void (__thiscall S::*)(void) const)"), + ("?f@@YAXP8S@@DEXXZ@Z", "void __cdecl f(void (__thiscall S::*)(void) const volatile)"), + ("?f@@YAXP8N@S@@AEXXZ@Z", "void __cdecl f(void (__thiscall S::N::*)(void))"), + ("?f@@YAXQ8S@@AEXXZ@Z", "void __cdecl f(void (__thiscall S::*const)(void))"), + ("?f@@YAXP8S@@AAHH@Z@Z", "void __cdecl f(int (__cdecl S::*)(int))"), + ("?f@@YAXP8?$T@$01@@AEXXZ@Z", "void __cdecl f(void (__thiscall T<2>::*)(void))"), + # a single digit is itself plus one; anything larger is nibbles "A" to "P" ended by "@" + ("??$f@$00@@YAXXZ", "void __cdecl f<1>(void)"), + ("??$f@$0A@@@YAXXZ", "void __cdecl f<0>(void)"), + ("??$f@$0M@@@YAXXZ", "void __cdecl f<12>(void)"), + ("??$f@$0BAA@@@YAXXZ", "void __cdecl f<256>(void)"), + ("??$f@$0?0@@YAXXZ", "void __cdecl f<-1>(void)"), + # the accumulator is 64 bits and wraps, and a magnitude that wraps to zero keeps its sign + ( + "??0?$LongLongTemplate@$0HPPPPPPPPPPPPPPPPPP@@@QAE@XZ", + "public: __thiscall LongLongTemplate<18446744073709551615>::LongLongTemplate<18446744073709551615>(void)", + ), + ( + "??0?$LongLongTemplate@$0?IAAAAAAAAAAAAAAAAAA@@@QEAA@XZ", + "public: __cdecl LongLongTemplate<-0>::LongLongTemplate<-0>(void)", + ), +] + + +class MsvcMemberPointerTestSuite(unittest.TestCase): + def test_member_pointers_and_template_integers_match_the_reference(self): + for mangled, expected in MEMBER_POINTERS_AND_INTEGERS: + with self.subTest(mangled=mangled): + self.assertEqual(demangle_msvc_symbol(mangled), expected) + + def test_shapes_neither_form_allows_are_refused(self): + for mangled in ( + "?f@@YAXPE8S@@AEAXXZ@Z", # __ptr64 is not written in front of a member function + "?f@@YAX$0A@@Z", # an integer is a template argument, never a parameter + "??$f@$0@@YAXXZ", # ... and needs digits + "?f@@YAXP8S@@AZXXZ@Z", # "Z" is not a calling convention + "?f@@YAXP8?0S@@@AEXXZ@Z", # nor is a constructor a class to point into + ): + with self.subTest(mangled=mangled): + self.assertEqual(demangle_msvc_symbol(mangled), mangled) + + class MsvcGrammarRuleTestSuite(unittest.TestCase): def test_rules_spell_names_the_way_the_reference_does(self): for mangled, expected in GRAMMAR_RULES: From 9af0d89421fe1cfba6c874b845e06d5a61497bb3 Mon Sep 17 00:00:00 2001 From: r0ny123 <49360849+r0ny123@users.noreply.github.com> Date: Mon, 17 Aug 2026 22:57:36 +0530 Subject: [PATCH 12/23] feat(labels): read a name scoped inside a function A lambda, a function-local static and a local class are all named by the function they sit in, and none of them were read: any name qualified by one declined. They are the largest thing left that an ordinary build emits. The enclosing function is a complete decorated name in its own right, so a second cursor reads it over the same text, and the fragment stops where that name stops - the "@" after it terminates the qualified name it belongs to, not the fragment. The scope number is one less than the one spelled: "?1??f@@YAXXZ@" is the second scope of "void __cdecl f(void)". Two rules decide whether the answer is right, and both cost wrong spellings before they were settled against the reference. The enclosing name continues the outer back-reference table rather than opening its own, so "?N@?1??SN@?$NS@H@0@QEAAHXZ@4HA" resolves its "0" to the outer N - reading it in a fresh table spelled that "SN::NS::SN". And the enclosing name is a symbol, so its own leading template is not recorded either, the same exception the outer name gets. Corpus 457 to 484 of 609 exact, none spelled differently. Across seven fuzz seeds the count of wrong answers is unchanged at 7, all in shapes this already answered: the lambda-data spacing corner, the two divergences kept deliberately, and one spliced mutant. --- .../common/labelprovider/MsvcDemangler.py | 34 +++++++++++++++++-- tests/testMsvcDemangler.py | 25 +++++++++++++- 2 files changed, 55 insertions(+), 4 deletions(-) diff --git a/src/smda/common/labelprovider/MsvcDemangler.py b/src/smda/common/labelprovider/MsvcDemangler.py index 711c8647..b6ef37bb 100644 --- a/src/smda/common/labelprovider/MsvcDemangler.py +++ b/src/smda/common/labelprovider/MsvcDemangler.py @@ -268,6 +268,7 @@ def __init__(self, mangled): self.simple = True self.template_depth = 0 self.at_symbol_name = True + self.nested = False self.pointee_depth = 0 self.array_element_depth = 0 self.member_cv = "" @@ -380,11 +381,38 @@ def nameFragment(self, is_leading): return self.operatorName() if self.peek() == "A": return self.anonymousNamespace(), None + if self.peek() in string.digits: + return self.localScope(), None raise _Bail name = self.identifier() self.rememberName(name) return name, None + def localScope(self): + """A scope inside a function: the function's own name, and which scope of it. + + "?1??f@@YAXXZ@" is the second scope of "void __cdecl f(void)", so the number is one + less than the one spelled. The enclosing name is a complete decorated name in its + own right and is read as one, in its own back-reference scopes - which is why it is + parsed by a separate cursor over the same text rather than inline. + """ + index = int(self.take()) + self.expect("?") + inner = _Demangler(self.text) + inner.pos = self.pos + inner.nested = True + # the enclosing name continues this name's back-reference table rather than opening + # its own: "?N@?1??SN@?$NS@H@0@QEAAHXZ@4HA" resolves its 0 to the outer N + inner.name_backrefs = self.name_backrefs + inner.arg_backrefs = self.arg_backrefs + # the enclosing name is a symbol in its own right, so its own leading template is + # not recorded either - the same exception the outer name gets + inner.at_symbol_name = True + inner.depth = self.depth + enclosing = inner.parse() + self.pos = inner.pos + return f"`{enclosing}'::`{index + 1}'" + def anonymousNamespace(self): """The unnamed namespace of one translation unit: "?A" and an optional discriminator. @@ -723,7 +751,7 @@ def parse(self): if qualifier is None: raise _Bail self.expect("@") - if not self.eof(): + if not self.nested and not self.eof(): raise _Bail return f"{qualifier.strip()} {name}".strip() if char in _DATA_ACCESS: @@ -731,7 +759,7 @@ def parse(self): self.simple = True declared = self.type() trailing = self.take() - if trailing not in _CV_QUALS or not self.eof(): + if trailing not in _CV_QUALS or (not self.nested and not self.eof()): raise _Bail if self.simple: declared = _apply_quals(declared, _CV_QUALS[trailing]) @@ -769,7 +797,7 @@ def function(self, name, has_no_return_type): returns = self.returnType() params = self.parameters() self.expect("Z") - if not self.eof(): + if not self.nested and not self.eof(): raise _Bail pieces = [] if access: diff --git a/tests/testMsvcDemangler.py b/tests/testMsvcDemangler.py index 148df2fe..0268dd16 100644 --- a/tests/testMsvcDemangler.py +++ b/tests/testMsvcDemangler.py @@ -63,7 +63,7 @@ # Measured against llvm-undname 22.1.7 on the shipped corpus. Both are exact so that a # change in either direction has to be an explicit edit rather than passing silently. UNIQUE_CORPUS_NAMES = 609 -CORPUS_NAMES_UNDERSTOOD = 460 +CORPUS_NAMES_UNDERSTOOD = 487 # Forms this demangler does not model. Each must come back exactly as it went in: a wrong # expansion is worse than a decorated name, because it matches neither spelling. @@ -250,6 +250,29 @@ def test_a_discriminator_that_is_not_hexadecimal_is_refused(self): ] +LOCAL_SCOPES = [ + ("?x@?0??f@@YAXXZ@4HA", "int `void __cdecl f(void)'::`1'::x"), + ("?x@?1??f@@YAXXZ@4HA", "int `void __cdecl f(void)'::`2'::x"), + ("?x@?2??f@@YAXXZ@4HA", "int `void __cdecl f(void)'::`3'::x"), + # the enclosing name continues this name's back-reference table rather than opening its + # own, so the "0" below is the outer N and not the enclosing symbol's own first fragment + ("?N@?1??SN@?$NS@H@0@QEAAHXZ@4HA", "int `public: int __cdecl N::NS::SN(void)'::`2'::N"), + ("?M@?0??L@@YAHXZ@YA?AURetVal@1@H@Z", "struct L::RetVal __cdecl `int __cdecl L(void)'::`1'::M(int)"), +] + + +class MsvcLocalScopeTestSuite(unittest.TestCase): + def test_a_scope_inside_a_function_names_the_function_and_which_scope(self): + for mangled, expected in LOCAL_SCOPES: + with self.subTest(mangled=mangled): + self.assertEqual(demangle_msvc_symbol(mangled), expected) + + def test_a_local_scope_without_an_enclosing_name_is_refused(self): + for mangled in ("?x@?1@4HA", "?x@?1?@4HA", "?x@?1??@4HA"): + with self.subTest(mangled=mangled): + self.assertEqual(demangle_msvc_symbol(mangled), mangled) + + class MsvcMemberPointerTestSuite(unittest.TestCase): def test_member_pointers_and_template_integers_match_the_reference(self): for mangled, expected in MEMBER_POINTERS_AND_INTEGERS: From a303880a153249100299dcf729b339845103d3c4 Mon Sep 17 00:00:00 2001 From: r0ny123 <49360849+r0ny123@users.noreply.github.com> Date: Mon, 17 Aug 2026 23:17:47 +0530 Subject: [PATCH 13/23] feat(labels): read unaligned pointers, literal operators and the zero scope Three more forms an ordinary build emits, and the largest of what the reference corpus still declined. "__unaligned" qualifies what a pointer points at, and is spelled after that pointee's own const and volatile: "int const __unaligned *". It also travels with them, so a pointer pointing at an unaligned pointer keeps it -- "int __unaligned *__unaligned *" -- which needed the qualifier merge to carry more than const and volatile. A user-defined literal is spelled "operator ""suffix", and its suffix is the identifier following the code, so "??__K_deg@@YAHO@Z" is operator ""_deg. Any other double-underscore code still declines. A scope inside a function can be written "@" rather than a digit, and that one is spelled zero -- a digit is spelled one higher than written, so the two forms meet at nothing. Corpus 484 to 491 of 609 exact, none spelled differently, and the count of wrong answers across seven fuzz seeds is unchanged at 7. --- .../common/labelprovider/MsvcDemangler.py | 19 +++++++++-- tests/testMsvcDemangler.py | 32 ++++++++++++++++++- 2 files changed, 47 insertions(+), 4 deletions(-) diff --git a/src/smda/common/labelprovider/MsvcDemangler.py b/src/smda/common/labelprovider/MsvcDemangler.py index b6ef37bb..65104ac0 100644 --- a/src/smda/common/labelprovider/MsvcDemangler.py +++ b/src/smda/common/labelprovider/MsvcDemangler.py @@ -208,7 +208,9 @@ def _render(node, declarator=""): def _merge(left, right): - merged = [qual for qual in ("const", "volatile") if qual in left or qual in right] + # "__unaligned" travels with const and volatile: a pointer that points at an unaligned + # pointer keeps it - "int __unaligned *__unaligned *" + merged = [qual for qual in ("const", "volatile", "__unaligned") if qual in left or qual in right] return tuple(merged) @@ -381,7 +383,7 @@ def nameFragment(self, is_leading): return self.operatorName() if self.peek() == "A": return self.anonymousNamespace(), None - if self.peek() in string.digits: + if self.peek() in string.digits or self.peek() == "@": return self.localScope(), None raise _Bail name = self.identifier() @@ -396,7 +398,8 @@ def localScope(self): own right and is read as one, in its own back-reference scopes - which is why it is parsed by a separate cursor over the same text rather than inline. """ - index = int(self.take()) + # "@" is the scope spelled zero, and a digit is spelled one higher than it is written + index = -1 if self.eat("@") else int(self.take()) self.expect("?") inner = _Demangler(self.text) inner.pos = self.pos @@ -440,6 +443,12 @@ def anonymousNamespace(self): def operatorName(self): """The operator or special name, paired with which spelling it takes.""" if self.eat("_"): + if self.eat("_"): + if self.take() != "K": + raise _Bail + # a user-defined literal: the identifier after the code is its suffix, and + # the reference spells the pair as operator ""suffix + return f'operator ""{self.identifier()}', "func" code = self.take() name = _EXTENDED_OPERATORS.get(code) if name is None: @@ -634,6 +643,9 @@ def indirection(self, own_quals, token): has_ptr64 = self.eat("E") if self.eat("I"): own_quals = own_quals + ("__restrict",) + # "__unaligned" qualifies what the pointer points at, and is spelled after the + # pointee's own const and volatile: "int const __unaligned *" + unaligned = ("__unaligned",) if self.eat("F") else () if self.eat("8"): return self.memberFunctionPointer(own_quals, token, has_ptr64) if self.eat("6"): @@ -659,6 +671,7 @@ def indirection(self, own_quals, token): pointee_quals = _CV_QUALS.get(self.take()) if pointee_quals is None: raise _Bail + pointee_quals = pointee_quals + unaligned self.pointee_depth += 1 try: pointee = self.type(pointee_quals) diff --git a/tests/testMsvcDemangler.py b/tests/testMsvcDemangler.py index 0268dd16..6d736d3b 100644 --- a/tests/testMsvcDemangler.py +++ b/tests/testMsvcDemangler.py @@ -63,7 +63,7 @@ # Measured against llvm-undname 22.1.7 on the shipped corpus. Both are exact so that a # change in either direction has to be an explicit edit rather than passing silently. UNIQUE_CORPUS_NAMES = 609 -CORPUS_NAMES_UNDERSTOOD = 487 +CORPUS_NAMES_UNDERSTOOD = 494 # Forms this demangler does not model. Each must come back exactly as it went in: a wrong # expansion is worse than a decorated name, because it matches neither spelling. @@ -261,6 +261,36 @@ def test_a_discriminator_that_is_not_hexadecimal_is_refused(self): ] +UNALIGNED_AND_LITERALS = [ + # "__unaligned" qualifies the pointee, after its own const and volatile + ("?f@@YAPFAHXZ", "int __unaligned * __cdecl f(void)"), + ("?f@@YAXPFBH@Z", "void __cdecl f(int const __unaligned *)"), + ("?f@@YAXAFAH@Z", "void __cdecl f(int __unaligned &)"), + ("?f@@YAXQFAH@Z", "void __cdecl f(int __unaligned *const)"), + # ... and travels with them, so a pointer to an unaligned pointer keeps it + ("?f@@YAXPFAPFAH@Z", "void __cdecl f(int __unaligned *__unaligned *)"), + ("?f@@YAXPFAPAH@Z", "void __cdecl f(int *__unaligned *)"), + ("?f@@YAXPIFAH@Z", "void __cdecl f(int __unaligned *__restrict)"), + # a user-defined literal takes its suffix from the identifier after the code + ("??__K_deg@@YAHO@Z", 'int __cdecl operator ""_deg(long double)'), + ("??__Kmm@@YAHO@Z", 'int __cdecl operator ""mm(long double)'), + # "@" is the scope spelled zero + ("?M@?@??L@@YAHXZ@4HA", "int `int __cdecl L(void)'::`0'::M"), +] + + +class MsvcUnalignedAndLiteralTestSuite(unittest.TestCase): + def test_the_forms_are_spelled_the_way_the_reference_spells_them(self): + for mangled, expected in UNALIGNED_AND_LITERALS: + with self.subTest(mangled=mangled): + self.assertEqual(demangle_msvc_symbol(mangled), expected) + + def test_an_unknown_double_underscore_operator_is_refused(self): + for mangled in ("??__L_deg@@YAHO@Z", "??__@@YAHO@Z"): + with self.subTest(mangled=mangled): + self.assertEqual(demangle_msvc_symbol(mangled), mangled) + + class MsvcLocalScopeTestSuite(unittest.TestCase): def test_a_scope_inside_a_function_names_the_function_and_which_scope(self): for mangled, expected in LOCAL_SCOPES: From 9ab022a1a269b987ddac85662bdac742cf2c5a9f Mon Sep 17 00:00:00 2001 From: r0ny123 <49360849+r0ny123@users.noreply.github.com> Date: Mon, 17 Aug 2026 23:41:50 +0530 Subject: [PATCH 14/23] fix(labels): put a data symbol's trailing qualifier where it declares "const MyClass instance" reached the report as "class MyClass inst": the trailing qualifier was applied only when the declared type was a plain one, so every class, struct and enum lost it. That is an ordinary declaration, not an exotic one. An array had it worse. The qualifier was appended to the array node's own text, which holds the dimensions rather than a type name, so "?arr@@3QAY01HB" came out as "[2] const *const arr" - a spelling of nothing. That one arrived with the earlier fix for pointers and is the reason this replaces the special cases with one rule. The rule the reference follows: the qualifier belongs to what the symbol declares. A pointer passes it one level in, to what it points at, which is why "?s@@3PADB" is "char const *s" while "?s@@3PAPADB" keeps it on the inner pointer. An array passes it on to its element, the way C spells one, so "?arr@@3QAY01HB" is "int const (*const arr)[2]". Neither the reference corpus nor the fuzz corpus moved: they carry no name of these shapes, which is why the nine spellings this settles are pinned directly instead. --- .../common/labelprovider/MsvcDemangler.py | 31 +++++++++++++------ tests/testMsvcDemangler.py | 24 ++++++++++++++ 2 files changed, 46 insertions(+), 9 deletions(-) diff --git a/src/smda/common/labelprovider/MsvcDemangler.py b/src/smda/common/labelprovider/MsvcDemangler.py index 65104ac0..d5693e2c 100644 --- a/src/smda/common/labelprovider/MsvcDemangler.py +++ b/src/smda/common/labelprovider/MsvcDemangler.py @@ -223,14 +223,33 @@ def _apply_quals(node, quals): return _base(f"{node[1]} {' '.join(quals)}") if quals else node +def _qualifyDeclared(node, quals): + """Place a data symbol's trailing qualifier on what the symbol declares. + + It belongs one level inside an outermost pointer rather than on the pointer - + "?s@@3PADB" is "char const *s" and "?s@@3PAPADB" is "char *const *s" - and an array + passes it on to its element, the way C spells one: "?arr@@3QAY01HB" is + "int const (*const arr)[2]". + """ + if not quals: + return node + if node[0] == "ind": + return _indirection(node[1], node[2], _qualifyElement(node[3], quals)) + return _qualifyElement(node, quals) + + +def _qualifyElement(node, quals): + if node[0] == "array": + return _array(node[1], _qualifyElement(node[2], quals)) + return _qualify(node, quals) + + def _qualify(node, quals): """Add qualifiers to a parsed type, wherever that type keeps them. A named type spells them in its text; a pointer or reference carries its own, so they join those instead of being appended to a rendering that already placed the sigil. """ - if not quals: - return node if node[0] == "ind": return _indirection(node[1], _merge(node[2], quals), node[3]) # a named type spells its qualifiers in its own text, so one it already carries must not @@ -774,13 +793,7 @@ def parse(self): trailing = self.take() if trailing not in _CV_QUALS or (not self.nested and not self.eof()): raise _Bail - if self.simple: - declared = _apply_quals(declared, _CV_QUALS[trailing]) - elif declared[0] == "ind": - # a data symbol's trailing qualifier belongs to what its outermost pointer - # points at, not to the pointer: "?s@@3PADB" is "char const *s", and - # "?s@@3PAPADB" is "char *const *s" - declared = _indirection(declared[1], declared[2], _qualify(declared[3], _CV_QUALS[trailing])) + declared = _qualifyDeclared(declared, _CV_QUALS[trailing]) return f"{_DATA_ACCESS[char]}{self.rendered(declared, name)}" return self.function(name, has_no_return_type) diff --git a/tests/testMsvcDemangler.py b/tests/testMsvcDemangler.py index 6d736d3b..83f8a818 100644 --- a/tests/testMsvcDemangler.py +++ b/tests/testMsvcDemangler.py @@ -279,6 +279,30 @@ def test_a_discriminator_that_is_not_hexadecimal_is_refused(self): ] +TRAILING_QUALIFIERS = [ + # a plain type takes it directly + ("?s@@3HB", "int const s"), + # a class, struct or enum does too - "const MyClass instance" is spelled this way + ("?inst@@3Urecord@@B", "struct record const inst"), + ("?inst@@3VMyClass@@B", "class MyClass const inst"), + ("?inst@@3W4E@@B", "enum E const inst"), + # a pointer passes it to what it points at, not to itself + ("?s@@3PADB", "char const *s"), + ("?a@@3PAUS@@B", "struct S const *a"), + ("?s@@3PAPADB", "char *const *s"), + ("?s@@3QBDD", "char const volatile *const s"), + # and an array passes it on to its element, the way C spells one + ("?arr@@3QAY01HB", "int const (*const arr)[2]"), +] + + +class MsvcTrailingQualifierTestSuite(unittest.TestCase): + def test_a_data_symbols_trailing_qualifier_lands_where_it_is_declared(self): + for mangled, expected in TRAILING_QUALIFIERS: + with self.subTest(mangled=mangled): + self.assertEqual(demangle_msvc_symbol(mangled), expected) + + class MsvcUnalignedAndLiteralTestSuite(unittest.TestCase): def test_the_forms_are_spelled_the_way_the_reference_spells_them(self): for mangled, expected in UNALIGNED_AND_LITERALS: From c85e3f24280f5ab3f64ccdf9e4c61d6cc4aa055c Mon Sep 17 00:00:00 2001 From: r0ny123 <49360849+r0ny123@users.noreply.github.com> Date: Tue, 18 Aug 2026 00:05:06 +0530 Subject: [PATCH 15/23] feat(labels): read a member function's qualifiers and a numbered scope Two forms left over from the ones already read, and between them most of what the reference corpus still declined. A function type written as a template argument may carry what only a member function carries. "$$A8" is that form: it names no class -- the reference refuses one -- so it reads as an ordinary function type with the qualifier appended, and the qualifier is more than cv. A reference qualifier and __restrict are written in front of it and spelled after it, so "GB" is " const &" and "IA" is " __restrict". Each is written at most once, so "HH" is not a name. A scope inside a function carries the same number a template argument does, and it was read as a single digit. Nibbles are just as legal: "?L@" is the eleventh scope, and reading only digits declined every name past the ninth. "A" stays out of that set, because "?A" is the unnamed namespace. Corpus 493 to 509 of 609 exact, none spelled differently, and the count of wrong answers across seven fuzz seeds is unchanged at 7. --- .../common/labelprovider/MsvcDemangler.py | 55 ++++++++++++++++--- tests/testMsvcDemangler.py | 38 ++++++++++++- 2 files changed, 84 insertions(+), 9 deletions(-) diff --git a/src/smda/common/labelprovider/MsvcDemangler.py b/src/smda/common/labelprovider/MsvcDemangler.py index d5693e2c..b5f5f9b7 100644 --- a/src/smda/common/labelprovider/MsvcDemangler.py +++ b/src/smda/common/labelprovider/MsvcDemangler.py @@ -402,7 +402,9 @@ def nameFragment(self, is_leading): return self.operatorName() if self.peek() == "A": return self.anonymousNamespace(), None - if self.peek() in string.digits or self.peek() == "@": + # a scope number is written the way a template argument's is, so it may be + # nibbles too; "A" is not among them because "?A" is the namespace above + if self.peek() in string.digits or self.peek() in "@BCDEFGHIJKLMNOP": return self.localScope(), None raise _Bail name = self.identifier() @@ -412,13 +414,14 @@ def nameFragment(self, is_leading): def localScope(self): """A scope inside a function: the function's own name, and which scope of it. - "?1??f@@YAXXZ@" is the second scope of "void __cdecl f(void)", so the number is one - less than the one spelled. The enclosing name is a complete decorated name in its + "?1??f@@YAXXZ@" is the second scope of "void __cdecl f(void)". The enclosing name is a complete decorated name in its own right and is read as one, in its own back-reference scopes - which is why it is parsed by a separate cursor over the same text rather than inline. """ - # "@" is the scope spelled zero, and a digit is spelled one higher than it is written - index = -1 if self.eat("@") else int(self.take()) + # the scope carries the number a template argument does - a digit standing for itself + # plus one, nibbles ended by "@" standing for themselves - except that a bare "@" is + # the scope spelled zero + spelled = "0" if self.eat("@") else self.templateInteger() self.expect("?") inner = _Demangler(self.text) inner.pos = self.pos @@ -433,7 +436,7 @@ def localScope(self): inner.depth = self.depth enclosing = inner.parse() self.pos = inner.pos - return f"`{enclosing}'::`{index + 1}'" + return f"`{enclosing}'::`{spelled}'" def anonymousNamespace(self): """The unnamed namespace of one translation unit: "?A" and an optional discriminator. @@ -727,15 +730,51 @@ def memberFunctionPointer(self, own_quals, token, has_ptr64): return _indirection(f"{owner}::{token}", own_quals, _function(convention, params, returns, member_cv)) def functionTypeArgument(self): + """A function type written as a template argument: "$$A6", or "$$A8" with a qualifier. + + The "8" form is the one a member function's type takes, but it names no class - the + reference refuses "$$A8S@@AEHXZ" - so it reads as an ordinary function type carrying + the qualifier that only a member function can have: "int __cdecl(void) const". + """ self.simple = False - self.expect("6") + member_cv = "" + if self.eat("8"): + self.expect("@") + self.expect("@") + member_cv = self.memberQualifiers() + else: + self.expect("6") convention = _CALLING_CONVENTIONS.get(self.take()) if convention is None: raise _Bail returns = self.returnType() params = self.parameters() self.expect("Z") - return _function(convention, params, returns) + return _function(convention, params, returns, member_cv) + + def memberQualifiers(self): + """What a member function may carry after its parameters: cv, __restrict, a ref. + + They are written modifier-first and spelled the other way round, so "GB" is + " const &" and "IA" is " __restrict". + """ + restrict = "" + reference = "" + seen = set() + while self.peek() in "EIGH": + char = self.take() + # each of them is written at most once: "HH" is not a name + if char in seen or (char in "GH" and seen & {"G", "H"}): + raise _Bail + seen.add(char) + if char == "I": + restrict = " __restrict" + elif char in ("G", "H"): + reference = " &" if char == "G" else " &&" + qualifier = _CV.get(self.take()) + if qualifier is None: + raise _Bail + return f"{qualifier}{restrict}{reference}" def parameters(self): """A parameter list, recording each composite parameter for later back-references. diff --git a/tests/testMsvcDemangler.py b/tests/testMsvcDemangler.py index 83f8a818..48db43ca 100644 --- a/tests/testMsvcDemangler.py +++ b/tests/testMsvcDemangler.py @@ -63,7 +63,7 @@ # Measured against llvm-undname 22.1.7 on the shipped corpus. Both are exact so that a # change in either direction has to be an explicit edit rather than passing silently. UNIQUE_CORPUS_NAMES = 609 -CORPUS_NAMES_UNDERSTOOD = 494 +CORPUS_NAMES_UNDERSTOOD = 512 # Forms this demangler does not model. Each must come back exactly as it went in: a wrong # expansion is worse than a decorated name, because it matches neither spelling. @@ -296,6 +296,42 @@ def test_a_discriminator_that_is_not_hexadecimal_is_refused(self): ] +MEMBER_QUALIFIERS_AND_SCOPES = [ + # "$$A8" is a function type carrying what only a member function may carry + ("??$f@$$A8@@BAHXZ@@YAXXZ", "void __cdecl f(void)"), + ("??$f@$$A8@@IAAHXZ@@YAXXZ", "void __cdecl f(void)"), + ("??$f@$$A8@@GBAHXZ@@YAXXZ", "void __cdecl f(void)"), + ("??$f@$$A8@@HBAHXZ@@YAXXZ", "void __cdecl f(void)"), + # a scope number is written the way a template argument's is, nibbles included + ("?M@?L@??L@@YAHXZ@4HA", "int `int __cdecl L(void)'::`11'::M"), + ("?x@?1??f@@YAXXZ@4HA", "int `void __cdecl f(void)'::`2'::x"), + ("?M@?@??L@@YAHXZ@4HA", "int `int __cdecl L(void)'::`0'::M"), +] + + +class MsvcMemberQualifierTestSuite(unittest.TestCase): + def test_member_qualifiers_and_scope_numbers_match_the_reference(self): + for mangled, expected in MEMBER_QUALIFIERS_AND_SCOPES: + with self.subTest(mangled=mangled): + self.assertEqual(demangle_msvc_symbol(mangled), expected) + + def test_a_qualifier_written_twice_is_refused(self): + # each of them is written at most once, so "HH" is not a name + for mangled in ("??$f@$$A8@@HHBAHXZ@@YAXXZ", "??$f@$$A8@@IIAAHXZ@@YAXXZ"): + with self.subTest(mangled=mangled): + self.assertEqual(demangle_msvc_symbol(mangled), mangled) + + def test_a_named_class_is_not_written_in_this_position(self): + self.assertEqual(demangle_msvc_symbol("??$f@$$A8S@@AEHXZ@@YAXXZ"), "??$f@$$A8S@@AEHXZ@@YAXXZ") + + def test_a_qualifier_the_table_does_not_hold_is_refused(self): + self.assertEqual(demangle_msvc_symbol("??$f@$$A8@@GZAHXZ@@YAXXZ"), "??$f@$$A8@@GZAHXZ@@YAXXZ") + + def test_a_qualified_fragment_that_is_neither_a_namespace_nor_a_scope_is_refused(self): + # "?Q" names no scope: the numbers stop at P and "?A" is the unnamed namespace + self.assertEqual(demangle_msvc_symbol("?x@?Q@@3HA"), "?x@?Q@@3HA") + + class MsvcTrailingQualifierTestSuite(unittest.TestCase): def test_a_data_symbols_trailing_qualifier_lands_where_it_is_declared(self): for mangled, expected in TRAILING_QUALIFIERS: From f4488b7ff946267b6a5bbdcd644eb83b589647ac Mon Sep 17 00:00:00 2001 From: r0ny123 <49360849+r0ny123@users.noreply.github.com> Date: Tue, 18 Aug 2026 00:40:22 +0530 Subject: [PATCH 16/23] feat(labels): read a pointer to data member The code standing where a pointee's qualifier would be says both that a pointer points into a class and what the member is qualified by, so "PRfoo@@D" is "char const foo::*". As a data symbol the same pointer repeats that qualifier and names its class again by back-reference, which is the form "?m@@3PRfoo@@DR1@" takes. Two shapes stay refused rather than guessed. C++ has no reference to member, so "AT..." is not a name however much it parses like one. And when the member type opens with a qualified pointer the reference drops qualifiers this would keep -- "PQfoo@@SAPEAX" is "void **foo::*" there, not "void *const volatile *foo::*" -- with nothing on the producer side to settle which is right, so that one declines. Corpus 509 to 516 of 609 exact, none spelled differently. Two more fuzz divergences appear, both the declarator-spacing corner already recorded, now reachable through a member pointer written as a data symbol. --- .../common/labelprovider/MsvcDemangler.py | 38 ++++++++++++++++++- tests/testMsvcDemangler.py | 35 ++++++++++++++++- 2 files changed, 70 insertions(+), 3 deletions(-) diff --git a/src/smda/common/labelprovider/MsvcDemangler.py b/src/smda/common/labelprovider/MsvcDemangler.py index b5f5f9b7..cee9e7ec 100644 --- a/src/smda/common/labelprovider/MsvcDemangler.py +++ b/src/smda/common/labelprovider/MsvcDemangler.py @@ -148,6 +148,8 @@ "4": "", } _POINTER_KINDS = {"P": (), "Q": ("const",), "R": ("volatile",), "S": ("const", "volatile")} +# what stands where a pointee's cv would, when the pointer points into a class instead +_MEMBER_DATA_QUALS = {"Q": (), "R": ("const",), "S": ("volatile",), "T": ("const", "volatile")} _CV_QUALS = {"A": (), "B": ("const",), "C": ("volatile",), "D": ("const", "volatile")} _CV = {"A": "", "B": " const", "C": " volatile", "D": " const volatile"} _MEMBER_POINTER_RE = re.compile(r"^[^()]*::\*") @@ -670,6 +672,10 @@ def indirection(self, own_quals, token): unaligned = ("__unaligned",) if self.eat("F") else () if self.eat("8"): return self.memberFunctionPointer(own_quals, token, has_ptr64) + if token == "*" and self.peek() in _MEMBER_DATA_QUALS: + # only a pointer points into a class; C++ has no reference to member, so "AT..." + # is not a name however much it looks like one + return self.memberDataPointer(own_quals, token) if self.eat("6"): if has_ptr64: # no mangler writes __ptr64 in front of a function type: "P6A" and "R6A" @@ -702,6 +708,24 @@ def indirection(self, own_quals, token): self.simple = False return _indirection(token, own_quals, pointee) + def memberDataPointer(self, own_quals, token): + """A pointer to data member: the class qualifies the declarator, as it does a method. + + The code standing where a pointee's cv would be says both that this points into a + class and what the member itself is qualified by, so "PRfoo@@D" is + "char const foo::*". + """ + member_quals = _MEMBER_DATA_QUALS[self.take()] + owner = self.qualifiedName()[0] + if self.peek() in ("Q", "R", "S"): + # the reference does not spell the qualifiers such a pointer would carry here - + # "PQfoo@@SAPEAX" is "void **foo::*", not "void *const volatile *foo::*" - and + # nothing on the producer side explains which is right, so this declines + raise _Bail + member = self.type(member_quals) + self.simple = False + return _indirection(f"{owner}::{token}", own_quals, member) + def memberFunctionPointer(self, own_quals, token, has_ptr64): """A pointer to member function: "P8" and the class it points into. @@ -830,9 +854,19 @@ def parse(self): self.simple = True declared = self.type() trailing = self.take() - if trailing not in _CV_QUALS or (not self.nested and not self.eof()): + if trailing in _MEMBER_DATA_QUALS: + # a pointer to data member repeats the member's qualifier here and names its + # class again by back-reference: "?m@@3PQfoo@@HR1@" is "int const foo::*m" + member_quals = _MEMBER_DATA_QUALS[trailing] + self.nameFragment(False) + self.expect("@") + elif trailing in _CV_QUALS: + member_quals = _CV_QUALS[trailing] + else: + raise _Bail + if not self.nested and not self.eof(): raise _Bail - declared = _qualifyDeclared(declared, _CV_QUALS[trailing]) + declared = _qualifyDeclared(declared, member_quals) return f"{_DATA_ACCESS[char]}{self.rendered(declared, name)}" return self.function(name, has_no_return_type) diff --git a/tests/testMsvcDemangler.py b/tests/testMsvcDemangler.py index 48db43ca..a8d0bbc4 100644 --- a/tests/testMsvcDemangler.py +++ b/tests/testMsvcDemangler.py @@ -63,7 +63,7 @@ # Measured against llvm-undname 22.1.7 on the shipped corpus. Both are exact so that a # change in either direction has to be an explicit edit rather than passing silently. UNIQUE_CORPUS_NAMES = 609 -CORPUS_NAMES_UNDERSTOOD = 512 +CORPUS_NAMES_UNDERSTOOD = 519 # Forms this demangler does not model. Each must come back exactly as it went in: a wrong # expansion is worse than a decorated name, because it matches neither spelling. @@ -309,6 +309,36 @@ def test_a_discriminator_that_is_not_hexadecimal_is_refused(self): ] +MEMBER_DATA_POINTERS = [ + ("?f@@YAXPQfoo@@H@Z", "void __cdecl f(int foo::*)"), + ("?f@@YAXPRfoo@@D@Z", "void __cdecl f(char const foo::*)"), + ("?f@@YAXPSfoo@@H@Z", "void __cdecl f(int volatile foo::*)"), + ("?f@@YAXPTfoo@@H@Z", "void __cdecl f(int const volatile foo::*)"), + ("?f@@YAXQQfoo@@H@Z", "void __cdecl f(int foo::*const)"), + ("?f@@YAXPQ?$T@H@@H@Z", "void __cdecl f(int T::*)"), + # as a data symbol it repeats the qualifier and names its class again by back-reference + ("?m@@3PQfoo@@HQ1@", "int foo::*m"), + ("?m@@3PRfoo@@DR1@", "char const foo::*m"), + ("?m@@3PQfoo@@HR1@", "int const foo::*m"), +] + + +class MsvcMemberDataPointerTestSuite(unittest.TestCase): + def test_a_pointer_into_a_class_is_spelled_around_the_class(self): + for mangled, expected in MEMBER_DATA_POINTERS: + with self.subTest(mangled=mangled): + self.assertEqual(demangle_msvc_symbol(mangled), expected) + + def test_a_reference_into_a_class_is_refused(self): + # C++ has no reference to member, however much "AT..." looks like one + self.assertEqual(demangle_msvc_symbol("?k@@3ATfoo@@DT1@"), "?k@@3ATfoo@@DT1@") + + def test_a_member_type_the_reference_spells_differently_is_declined(self): + # "PQfoo@@SAPEAX" is "void **foo::*" there, dropping qualifiers this would keep, and + # nothing on the producer side settles which is right + self.assertEqual(demangle_msvc_symbol("?f@@YAXPQfoo@@SAPEAX@Z"), "?f@@YAXPQfoo@@SAPEAX@Z") + + class MsvcMemberQualifierTestSuite(unittest.TestCase): def test_member_qualifiers_and_scope_numbers_match_the_reference(self): for mangled, expected in MEMBER_QUALIFIERS_AND_SCOPES: @@ -333,6 +363,9 @@ def test_a_qualified_fragment_that_is_neither_a_namespace_nor_a_scope_is_refused class MsvcTrailingQualifierTestSuite(unittest.TestCase): + def test_a_data_symbol_with_bytes_after_it_is_refused(self): + self.assertEqual(demangle_msvc_symbol("?s@@3HBX"), "?s@@3HBX") + def test_a_data_symbols_trailing_qualifier_lands_where_it_is_declared(self): for mangled, expected in TRAILING_QUALIFIERS: with self.subTest(mangled=mangled): From 2fd1a0eb173878eb4e23c97778c2a1b83422c08d Mon Sep 17 00:00:00 2001 From: r0ny123 <49360849+r0ny123@users.noreply.github.com> Date: Tue, 18 Aug 2026 01:10:19 +0530 Subject: [PATCH 17/23] feat(labels): read the storage forms a data symbol may take Three of them, and between them most of what the reference corpus still declined. __ptr64 stands in front of a data symbol's qualifier, where something is pointed at: "?s@@3PEAHEA" is a name and "?s@@3HEA" is not, since nothing there is pointed at to carry it. A pointer into a class spells its storage the long way even so - the member's qualifier and its class again by back-reference - so "?m@@3PEFRfoo@@DER1@" carries both that and the __unaligned in front of it, and the short form the other types use is not a name for it. And a name may carry no signature at all, in which case the linkage is what is being spelled: "?extern_c_func@@9" is extern "C". Nothing follows the marker. Corpus 516 to 524 of 609 exact, none spelled differently. __unaligned was also being dropped when it stood in front of a pointer into a class, which the fuzz corpus caught as three wrong spellings. --- .../common/labelprovider/MsvcDemangler.py | 23 +++++++++++++--- tests/testMsvcDemangler.py | 27 ++++++++++++++++++- 2 files changed, 45 insertions(+), 5 deletions(-) diff --git a/src/smda/common/labelprovider/MsvcDemangler.py b/src/smda/common/labelprovider/MsvcDemangler.py index cee9e7ec..a20b71fa 100644 --- a/src/smda/common/labelprovider/MsvcDemangler.py +++ b/src/smda/common/labelprovider/MsvcDemangler.py @@ -675,7 +675,7 @@ def indirection(self, own_quals, token): if token == "*" and self.peek() in _MEMBER_DATA_QUALS: # only a pointer points into a class; C++ has no reference to member, so "AT..." # is not a name however much it looks like one - return self.memberDataPointer(own_quals, token) + return self.memberDataPointer(own_quals, token, unaligned) if self.eat("6"): if has_ptr64: # no mangler writes __ptr64 in front of a function type: "P6A" and "R6A" @@ -708,14 +708,14 @@ def indirection(self, own_quals, token): self.simple = False return _indirection(token, own_quals, pointee) - def memberDataPointer(self, own_quals, token): + def memberDataPointer(self, own_quals, token, unaligned=()): """A pointer to data member: the class qualifies the declarator, as it does a method. The code standing where a pointee's cv would be says both that this points into a class and what the member itself is qualified by, so "PRfoo@@D" is "char const foo::*". """ - member_quals = _MEMBER_DATA_QUALS[self.take()] + member_quals = _MEMBER_DATA_QUALS[self.take()] + unaligned owner = self.qualifiedName()[0] if self.peek() in ("Q", "R", "S"): # the reference does not spell the qualifiers such a pointer would carry here - @@ -838,6 +838,12 @@ def parse(self): if self.eof(): raise _Bail char = self.peek() + if char == "9": + # a name with no signature at all: the linkage is what is being spelled + self.take() + if not self.nested and not self.eof(): + raise _Bail + return f'extern "C" {name}' if (special_form == "data") != (char == "6"): raise _Bail if char == "6": @@ -853,14 +859,23 @@ def parse(self): self.take() self.simple = True declared = self.type() + # a pointer into a class spells its own storage the long way, below; the short + # forms are for everything else + points_into_class = declared[0] == "ind" and declared[1].endswith("::*") trailing = self.take() + if trailing == "E": + # __ptr64 stands in front of the qualifier, and only where something is + # pointed at: "?s@@3PEAHEA" is a name and "?s@@3HEA" is not + if declared[0] != "ind": + raise _Bail + trailing = self.take() if trailing in _MEMBER_DATA_QUALS: # a pointer to data member repeats the member's qualifier here and names its # class again by back-reference: "?m@@3PQfoo@@HR1@" is "int const foo::*m" member_quals = _MEMBER_DATA_QUALS[trailing] self.nameFragment(False) self.expect("@") - elif trailing in _CV_QUALS: + elif trailing in _CV_QUALS and not points_into_class: member_quals = _CV_QUALS[trailing] else: raise _Bail diff --git a/tests/testMsvcDemangler.py b/tests/testMsvcDemangler.py index a8d0bbc4..c62b4d06 100644 --- a/tests/testMsvcDemangler.py +++ b/tests/testMsvcDemangler.py @@ -63,7 +63,7 @@ # Measured against llvm-undname 22.1.7 on the shipped corpus. Both are exact so that a # change in either direction has to be an explicit edit rather than passing silently. UNIQUE_CORPUS_NAMES = 609 -CORPUS_NAMES_UNDERSTOOD = 519 +CORPUS_NAMES_UNDERSTOOD = 527 # Forms this demangler does not model. Each must come back exactly as it went in: a wrong # expansion is worse than a decorated name, because it matches neither spelling. @@ -363,6 +363,31 @@ def test_a_qualified_fragment_that_is_neither_a_namespace_nor_a_scope_is_refused class MsvcTrailingQualifierTestSuite(unittest.TestCase): + def test_the_storage_forms_a_data_symbol_may_take(self): + cases = [ + # __ptr64 stands in front of the qualifier, where something is pointed at + ("?s@@3PEAHEA", "int *s"), + ("?$RT1@NeedsReferenceTemporary@@3AEBHEB", "int const &NeedsReferenceTemporary::$RT1"), + # a pointer into a class spells its storage the long way, "E" included + ("?m@@3PEFRfoo@@DER1@", "char const __unaligned foo::*m"), + # and a name with no signature at all is spelling its linkage + ("?extern_c_func@@9", 'extern "C" extern_c_func'), + ("?local@?1??extern_c_func@@9@4HA", "int `extern \"C\" extern_c_func'::`2'::local"), + ] + for mangled, expected in cases: + with self.subTest(mangled=mangled): + self.assertEqual(demangle_msvc_symbol(mangled), expected) + + def test_storage_forms_the_grammar_does_not_pair_are_refused(self): + for mangled in ( + "?s@@3HEA", # nothing is pointed at, so no __ptr64 belongs here + "?s@@3HEB", + "?memptr1@@3RESB@@HEA", # a pointer into a class takes the long form, not this + "?extern_c_func@@9X", # the linkage marker ends the name, so nothing follows it + ): + with self.subTest(mangled=mangled): + self.assertEqual(demangle_msvc_symbol(mangled), mangled) + def test_a_data_symbol_with_bytes_after_it_is_refused(self): self.assertEqual(demangle_msvc_symbol("?s@@3HBX"), "?s@@3HBX") From 4c0f6cab32701e13f72ee3a28ad233551a34662d Mon Sep 17 00:00:00 2001 From: r0ny123 <49360849+r0ny123@users.noreply.github.com> Date: Tue, 18 Aug 2026 11:44:12 +0530 Subject: [PATCH 18/23] feat(labels): read seven more forms an ordinary build emits A pack separator and an empty pack stand between template arguments without being arguments themselves, so "f" and "f<>" are what "H$$ZH" and "$$$V" spell. An array's extent is written the way a template argument's number is, which reading only digits declined past the ninth: "Y0BE@" is [20]. The two dynamic-initialisation codes name what they run around, and that name is recorded for later reference, unlike a literal operator's suffix. Both take a signature rather than a storage class, since they run code. A name may be mangled although it is extern "C", which "$$J" marks; the digit after it counts characters of the original mangling and is not spelled, but it is written, so a marker without one is not a name. A vftable or vbtable may say which base it is the table for, spelled {for `D::C'} after it. A member function pointer written as a data symbol keeps that symbol's qualifier after its parameters, where a member function keeps it, rather than on what the pointer points at - which had spelled a name with no return type at all. And the declarator spacing had one case the other way round: a parenthesised pointer declarator abuts the sigil while a function declarator is separated from it, so "int (__cdecl *(*a)[20])(int, int)" and "int * (__cdecl *)(int)" are both right. Corpus 534 to 548 of 609 exact, none spelled differently. --- .../common/labelprovider/MsvcDemangler.py | 91 ++++++++++++++++--- tests/testMsvcDemangler.py | 44 ++++++++- 2 files changed, 120 insertions(+), 15 deletions(-) diff --git a/src/smda/common/labelprovider/MsvcDemangler.py b/src/smda/common/labelprovider/MsvcDemangler.py index a20b71fa..5681862b 100644 --- a/src/smda/common/labelprovider/MsvcDemangler.py +++ b/src/smda/common/labelprovider/MsvcDemangler.py @@ -106,6 +106,8 @@ # the special names spelled with the "6" storage form. The vcall, typeof and local static # guard codes are data too, but take a storage class this parser does not model, so they # decline instead of being read as one of these +# what runs around a namespace-scope object with a non-trivial lifetime +_DYNAMIC_INITIALISERS = {"E": "dynamic initializer for", "F": "dynamic atexit destructor for"} _DATA_SPECIAL_OPERATORS = frozenset("78S") _UNMODELLED_DATA_SPECIAL_OPERATORS = frozenset("9AB") _EXTENDED_OPERATORS = { @@ -192,7 +194,9 @@ def _render(node, declarator=""): return node[1] + ("" if abuts else " ") + declarator if kind == "ind": token = node[1] + " ".join(node[2]) - nested_function = "(" in declarator and not declarator.startswith(("*", "&")) + # a nested *function* declarator is separated from the sigil - "int * (__cdecl *)()" + # - while a parenthesised pointer declarator abuts it: "int (*(*a)[20])()" + nested_function = "(" in declarator and not declarator.startswith(("*", "&", "(*", "(&")) separator = " " if declarator and (node[2] or nested_function) else "" return _render(node[3], token + separator + declarator) if kind == "array": @@ -225,6 +229,10 @@ def _apply_quals(node, quals): return _base(f"{node[1]} {' '.join(quals)}") if quals else node +def _isMemberFunctionPointer(node): + return node[0] == "ind" and node[1].endswith("::*") and node[3][0] == "func" + + def _qualifyDeclared(node, quals): """Place a data symbol's trailing qualifier on what the symbol declares. @@ -291,6 +299,7 @@ def __init__(self, mangled): self.simple = True self.template_depth = 0 self.at_symbol_name = True + self.requires_signature = False self.nested = False self.pointee_depth = 0 self.array_element_depth = 0 @@ -362,6 +371,14 @@ def templateInstantiation(self): while not self.eat("@"): if self.eof(): raise _Bail + if self.text.startswith("$$$V", self.pos): + # an empty pack: "f<>" has an argument list and no arguments in it + self.pos += 4 + continue + if self.text.startswith("$$Z", self.pos): + # a pack separator, which stands between arguments and is not one + self.pos += 3 + continue args.append(self.rendered(self.type())) return f"{base}<{', '.join(args)}>" finally: @@ -468,6 +485,18 @@ def operatorName(self): """The operator or special name, paired with which spelling it takes.""" if self.eat("_"): if self.eat("_"): + code = self.peek() + if code in _DYNAMIC_INITIALISERS: + self.take() + if self.peek() == "?": + # the object it runs for is named plainly, not by another special name + raise _Bail + # what it runs for is recorded, unlike a literal operator's suffix: + # "??__EFoo@@YAXU0@@Z" resolves its 0 to Foo + self.requires_signature = True + target = self.identifier() + self.rememberName(target) + return f"`{_DYNAMIC_INITIALISERS[code]} '{target}''", "func" if self.take() != "K": raise _Bail # a user-defined literal: the identifier after the code is its suffix, and @@ -577,10 +606,11 @@ def rendered(self, node, declarator=""): return text def dimension(self): - char = self.take() - if char in string.digits: - return int(char) + 1 - raise _Bail + """A count or an extent, written the way a template argument's number is.""" + spelled = self.templateInteger() + if spelled.startswith("-"): + raise _Bail + return int(spelled) def arrayType(self, quals): count = self.dimension() @@ -736,9 +766,7 @@ def memberFunctionPointer(self, own_quals, token, has_ptr64): if has_ptr64: raise _Bail owner = self.qualifiedName()[0] - member_cv = _CV.get(self.take()) - if member_cv is None: - raise _Bail + member_cv = self.memberQualifiers() convention = _CALLING_CONVENTIONS.get(self.take()) if convention is None: raise _Bail @@ -834,27 +862,51 @@ def parameters(self): def parse(self): self.expect("?") + # "$$J" marks a name that was mangled although it is extern "C"; the digit after it + # counts how many characters of the original mangling it kept, which is not spelled + extern_c = "" name, has_no_return_type, special_form = self.qualifiedName() if self.eof(): raise _Bail char = self.peek() + if char == "$": + self.take() + if not self.eat("$") or self.take() != "J": + raise _Bail + digits = 0 + while not self.eof() and self.peek() in string.digits: + self.take() + digits += 1 + if not digits: + raise _Bail + extern_c = 'extern "C" ' + char = self.peek() if char == "9": # a name with no signature at all: the linkage is what is being spelled self.take() if not self.nested and not self.eof(): raise _Bail return f'extern "C" {name}' - if (special_form == "data") != (char == "6"): + if (special_form == "data") != (char in "67"): raise _Bail - if char == "6": + if self.requires_signature and char != "Y" and char not in _FUNCTION_ACCESS: + # this one runs code, so it is spelled with a signature and never with storage + raise _Bail + if char in "67": self.take() qualifier = _CV.get(self.take()) if qualifier is None: raise _Bail - self.expect("@") + # a vftable may say which base it is the table for, as a qualified name of its + # own: "??_7A@B@@6BC@D@@@" is B::A's table for D::C + base = "" + if not self.eat("@"): + base = self.qualifiedName()[0] + self.expect("@") if not self.nested and not self.eof(): raise _Bail - return f"{qualifier.strip()} {name}".strip() + spelled = f"{qualifier.strip()} {name}".strip() + return f"{spelled}{{for `{base}'}}" if base else spelled if char in _DATA_ACCESS: self.take() self.simple = True @@ -881,9 +933,20 @@ def parse(self): raise _Bail if not self.nested and not self.eof(): raise _Bail - declared = _qualifyDeclared(declared, member_quals) + if member_quals and _isMemberFunctionPointer(declared): + # a member function keeps its qualifier after the parameters, not on what + # the pointer points at, so this one joins the function rather than the type + function = declared[3] + trailing_cv = "".join(f" {qual}" for qual in member_quals) + declared = _indirection( + declared[1], + declared[2], + _function(function[1], function[2], function[3], function[4] + trailing_cv), + ) + else: + declared = _qualifyDeclared(declared, member_quals) return f"{_DATA_ACCESS[char]}{self.rendered(declared, name)}" - return self.function(name, has_no_return_type) + return extern_c + self.function(name, has_no_return_type) def function(self, name, has_no_return_type): access_char = self.take() diff --git a/tests/testMsvcDemangler.py b/tests/testMsvcDemangler.py index c62b4d06..b872b6e9 100644 --- a/tests/testMsvcDemangler.py +++ b/tests/testMsvcDemangler.py @@ -63,7 +63,7 @@ # Measured against llvm-undname 22.1.7 on the shipped corpus. Both are exact so that a # change in either direction has to be an explicit edit rather than passing silently. UNIQUE_CORPUS_NAMES = 609 -CORPUS_NAMES_UNDERSTOOD = 527 +CORPUS_NAMES_UNDERSTOOD = 548 # Forms this demangler does not model. Each must come back exactly as it went in: a wrong # expansion is worse than a decorated name, because it matches neither spelling. @@ -323,6 +323,48 @@ def test_a_discriminator_that_is_not_hexadecimal_is_refused(self): ] +LATER_FORMS = [ + # a pack separator and an empty pack stand between arguments without being one + ("??$f@H$$ZH@@YAXXZ", "void __cdecl f(void)"), + ("??$f@$$$V@@YAXXZ", "void __cdecl f<>(void)"), + # an extent is written the way a template argument's number is + ("?i@@3PAY0BE@HA", "int (*i)[20]"), + # what runs around an object with a non-trivial lifetime, whose name is recorded + ("??__EFoo@@YAXXZ", "void __cdecl `dynamic initializer for 'Foo''(void)"), + ("??__FFoo@@YAXXZ", "void __cdecl `dynamic atexit destructor for 'Foo''(void)"), + ("??__EFoo@@YAXU0@@Z", "void __cdecl `dynamic initializer for 'Foo''(struct Foo)"), + # a name mangled although it is extern "C" + ("?overloaded_fn@@$$J0YAXXZ", 'extern "C" void __cdecl overloaded_fn(void)'), + # a vftable may say which base it is the table for + ("??_7A@B@@6BC@D@@@", "const B::A::`vftable'{for `D::C'}"), + ("??_8A@B@@7BC@D@@@", "const B::A::`vbtable'{for `D::C'}"), + # a member function pointer keeps a data symbol's qualifier after its parameters + ("?p@@3P8B@@EAA?CHXZES1@", "int volatile (__cdecl B::*p)(void) volatile"), + # a parenthesised pointer declarator abuts the sigil; a function declarator does not + ("?FunArr@@3PAY0BE@P6AHHH@ZA", "int (__cdecl *(*FunArr)[20])(int, int)"), + ("?f@@YAP6AHXZXZ", "int (__cdecl * __cdecl f(void))(void)"), +] + + +class MsvcLaterFormsTestSuite(unittest.TestCase): + def test_the_later_forms_match_the_reference(self): + for mangled, expected in LATER_FORMS: + with self.subTest(mangled=mangled): + self.assertEqual(demangle_msvc_symbol(mangled), expected) + + def test_the_shapes_these_forms_do_not_allow_are_refused(self): + for mangled in ( + "??__E?i@C@0HA@@YAXXZ", # what it runs for is named plainly, not by a special name + "??__EFooTypeWithQuals@@3U?$S@$$A8@@GBAHXZ@1@A", # it runs code, so it takes a signature + "?overloaded_fn@@$$JYAXXZ", # the marker counts characters, so a digit belongs here + "??__K_deg@@YAXU0@@Z", # a literal operator's suffix is not recorded, so 0 names nothing + "?i@@3PAY0?0HA", # an array does not have a negative extent + "??_7A@@6B?0@@", # nor is a base named by anything but a name + ): + with self.subTest(mangled=mangled): + self.assertEqual(demangle_msvc_symbol(mangled), mangled) + + class MsvcMemberDataPointerTestSuite(unittest.TestCase): def test_a_pointer_into_a_class_is_spelled_around_the_class(self): for mangled, expected in MEMBER_DATA_POINTERS: From 48c301934fc5b2e8b6f3cfa997ea4599bb698998 Mon Sep 17 00:00:00 2001 From: r0ny123 <49360849+r0ny123@users.noreply.github.com> Date: Tue, 18 Aug 2026 12:01:35 +0530 Subject: [PATCH 19/23] feat(labels): read alias templates, base lists and __restrict An empty pack has a second spelling, "$$V", and an alias template is named rather than described: "$$YAliasA@PR20047@@" is the name itself. A vftable or vbtable may name more than one base, and the reference strings them together: {for `D::C's `F::E'}. The terminator closes the list rather than each name in it. __restrict appears in two more places. On a member function it sits with the reference qualifier, which is the same set a member function type carries, so the two now read through one reader. On a data symbol it qualifies the pointer rather than what is pointed at, and is written once however many times it is spelled: "?h3@@3QIAHIA" and "?h3@@3QAHIA" are both "int *const __restrict h3". Corpus 548 to 558 of 609 exact, none spelled differently. --- .../common/labelprovider/MsvcDemangler.py | 34 +++++++++++++------ tests/testMsvcDemangler.py | 25 +++++++++++++- 2 files changed, 47 insertions(+), 12 deletions(-) diff --git a/src/smda/common/labelprovider/MsvcDemangler.py b/src/smda/common/labelprovider/MsvcDemangler.py index 5681862b..ac52e0b2 100644 --- a/src/smda/common/labelprovider/MsvcDemangler.py +++ b/src/smda/common/labelprovider/MsvcDemangler.py @@ -371,6 +371,10 @@ def templateInstantiation(self): while not self.eat("@"): if self.eof(): raise _Bail + if self.text.startswith("$$V", self.pos) and not self.text.startswith("$$$V", self.pos): + # the other spelling of an empty pack + self.pos += 3 + continue if self.text.startswith("$$$V", self.pos): # an empty pack: "f<>" has an argument list and no arguments in it self.pos += 4 @@ -659,6 +663,10 @@ def dollarType(self, quals): return _base(self.templateInteger()) if not self.eat("$"): raise _Bail + if self.eat("Y"): + # an alias template is named rather than described + self.simple = False + return _base(self.qualifiedName()[0]) kind = self.take() if kind == "Q": return self.indirection(quals, "&&") @@ -899,10 +907,10 @@ def parse(self): raise _Bail # a vftable may say which base it is the table for, as a qualified name of its # own: "??_7A@B@@6BC@D@@@" is B::A's table for D::C - base = "" - if not self.eat("@"): - base = self.qualifiedName()[0] - self.expect("@") + bases = [] + while not self.eat("@"): + bases.append(self.qualifiedName()[0]) + base = "'s `".join(bases) if not self.nested and not self.eof(): raise _Bail spelled = f"{qualifier.strip()} {name}".strip() @@ -914,12 +922,15 @@ def parse(self): # a pointer into a class spells its own storage the long way, below; the short # forms are for everything else points_into_class = declared[0] == "ind" and declared[1].endswith("::*") + # __ptr64 and __restrict stand in front of the qualifier, and only where + # something is pointed at: "?s@@3PEAHEA" is a name and "?s@@3HEA" is not trailing = self.take() - if trailing == "E": - # __ptr64 stands in front of the qualifier, and only where something is - # pointed at: "?s@@3PEAHEA" is a name and "?s@@3HEA" is not + restrict = () + while trailing in ("E", "I"): if declared[0] != "ind": raise _Bail + if trailing == "I": + restrict = ("__restrict",) trailing = self.take() if trailing in _MEMBER_DATA_QUALS: # a pointer to data member repeats the member's qualifier here and names its @@ -933,6 +944,10 @@ def parse(self): raise _Bail if not self.nested and not self.eof(): raise _Bail + if restrict and "__restrict" not in declared[2]: + # it qualifies the pointer, not what is pointed at, and is written once + # however many times it is spelled: "?h3@@3QIAHIA" is "int *const __restrict" + declared = _indirection(declared[1], declared[2] + restrict, declared[3]) if member_quals and _isMemberFunctionPointer(declared): # a member function keeps its qualifier after the parameters, not on what # the pointer points at, so this one joins the function rather than the type @@ -958,10 +973,7 @@ def function(self, name, has_no_return_type): raise _Bail access, is_static, is_virtual = entry if not is_static: - self.eat("E") - if _CV.get(self.peek()) is None: - raise _Bail - self.member_cv = _CV[self.take()] + self.member_cv = self.memberQualifiers() else: self.member_cv = "" convention = _CALLING_CONVENTIONS.get(self.take()) diff --git a/tests/testMsvcDemangler.py b/tests/testMsvcDemangler.py index b872b6e9..16251639 100644 --- a/tests/testMsvcDemangler.py +++ b/tests/testMsvcDemangler.py @@ -63,7 +63,7 @@ # Measured against llvm-undname 22.1.7 on the shipped corpus. Both are exact so that a # change in either direction has to be an explicit edit rather than passing silently. UNIQUE_CORPUS_NAMES = 609 -CORPUS_NAMES_UNDERSTOOD = 548 +CORPUS_NAMES_UNDERSTOOD = 558 # Forms this demangler does not model. Each must come back exactly as it went in: a wrong # expansion is worse than a decorated name, because it matches neither spelling. @@ -346,6 +346,29 @@ def test_a_discriminator_that_is_not_hexadecimal_is_refused(self): ] +LATEST_FORMS = [ + # a second spelling of the empty pack, and an alias template named rather than described + ("??$templ_fun_with_ty_pack@$$V@@YAXXZ", "void __cdecl templ_fun_with_ty_pack<>(void)"), + ("??$f@$$YAliasA@PR20047@@@PR20047@@YAXXZ", "void __cdecl PR20047::f(void)"), + # a vftable may name more than one base + ("??_7A@B@@6BC@D@@E@F@@@", "const B::A::`vftable'{for `D::C's `F::E'}"), + # __restrict qualifies a member function, next to its reference qualifier + ("?foo@A@PR19361@@QIGAEXXZ", "public: void __thiscall PR19361::A::foo(void) __restrict &"), + # and on a data symbol it qualifies the pointer, once however often it is spelled + ("?h3@@3QAHIA", "int *const __restrict h3"), + ("?h3@@3QIAHA", "int *const __restrict h3"), + ("?h3@@3QIAHIA", "int *const __restrict h3"), + ("?h3@@3PAHIA", "int *__restrict h3"), +] + + +class MsvcLatestFormsTestSuite(unittest.TestCase): + def test_the_latest_forms_match_the_reference(self): + for mangled, expected in LATEST_FORMS: + with self.subTest(mangled=mangled): + self.assertEqual(demangle_msvc_symbol(mangled), expected) + + class MsvcLaterFormsTestSuite(unittest.TestCase): def test_the_later_forms_match_the_reference(self): for mangled, expected in LATER_FORMS: From f8f776d24067d1cedea24b4b2b0b9e0ae371b35a Mon Sep 17 00:00:00 2001 From: r0ny123 <49360849+r0ny123@users.noreply.github.com> Date: Tue, 18 Aug 2026 12:30:30 +0530 Subject: [PATCH 20/23] feat(labels): read operator templates, undecayed types and the rest of the conventions An operator may be a template, which the name reader refused outright: "??$?HH@S@@QEAAAEAU0@H@Z" is S::operator+. It reads through the same path an identifier template does, since only the name differs. An operator may also leave its return slot empty, the way a constructor does -- every one of them but the conversion operator, whose return is the type it converts to. "$$B" introduces a type as written rather than as a parameter would decay it, so a template argument keeps its extent: "int[2]", and "int[]" where the extent is nothing. Every letter names a calling convention, and most of them are spelled with nothing at all. A convention spelled with nothing still leaves the parentheses a pointer needs, and the space it would have filled: "int ( *)(void)". This had been reading only nine of the twenty-six, declining the rest. Two spacing rules came out of the same names: an array declarator abuts the qualifier before it, so "int *const[5]" rather than "int *const [5]". Corpus 558 to 574 of 609 exact, none spelled differently. --- .../common/labelprovider/MsvcDemangler.py | 73 +++++++++++++++---- tests/testMsvcDemangler.py | 47 +++++++++++- 2 files changed, 100 insertions(+), 20 deletions(-) diff --git a/src/smda/common/labelprovider/MsvcDemangler.py b/src/smda/common/labelprovider/MsvcDemangler.py index ac52e0b2..0ac53603 100644 --- a/src/smda/common/labelprovider/MsvcDemangler.py +++ b/src/smda/common/labelprovider/MsvcDemangler.py @@ -47,6 +47,18 @@ "O": "__eabi", "P": "__eabi", "Q": "__vectorcall", + "S": "__attribute__((__swiftcall__))", + "W": "__attribute__((__swiftasynccall__))", + # the remaining letters are conventions the reference spells with nothing at all + "K": "", + "L": "", + "R": "", + "T": "", + "U": "", + "V": "", + "X": "", + "Y": "", + "Z": "", } _FUNCTION_ACCESS = { "A": ("private", False, False), @@ -197,13 +209,17 @@ def _render(node, declarator=""): # a nested *function* declarator is separated from the sigil - "int * (__cdecl *)()" # - while a parenthesised pointer declarator abuts it: "int (*(*a)[20])()" nested_function = "(" in declarator and not declarator.startswith(("*", "&", "(*", "(&")) - separator = " " if declarator and (node[2] or nested_function) else "" + separator = " " if declarator and not declarator.startswith("[") and (node[2] or nested_function) else "" return _render(node[3], token + separator + declarator) if kind == "array": if declarator.startswith(("*", "&")): declarator = f"({declarator})" return _render(node[2], declarator + node[1]) convention, params, returns, member_cv = node[1], node[2], node[3], node[4] + if not convention and not declarator.startswith(("*", "&")) and "::*" not in declarator: + # a convention spelled with nothing leaves a named declarator alone, while a pointer + # keeps its parentheses and the space the convention would have filled: "int ( *)()" + return _render(returns, f"{declarator}({params}){member_cv}") # a member-pointer declarator is "Owner::*", possibly qualified; the test is anchored so # that a nested type's own "::*" - which a rendered parameter may hold - does not count if declarator.startswith(("*", "&")) or _MEMBER_POINTER_RE.match(declarator): @@ -351,9 +367,13 @@ def rememberName(self, name): if name not in self.name_backrefs and len(self.name_backrefs) < 10: self.name_backrefs.append(name) - def templateInstantiation(self): + def templateInstantiation(self, operator=None): """A "?$" template name, read in its own back-reference scope. + The name is usually an identifier, and is recorded inside the fresh scope; when it + is an operator instead the caller has already read it, and there is nothing to + record - "??$?HH@S@@QEAAAEAU0@H@Z" is S::operator+. + The scope opens before the template's own name does, so that name takes index 0 inside it and the arguments are numbered from 1 - which is what a back-reference written inside the argument list resolves against. The rendered result belongs to @@ -363,10 +383,13 @@ def templateInstantiation(self): self.name_backrefs, self.arg_backrefs = [], [] self.template_depth += 1 try: - base = self.identifier() - self.rememberName(base) - if self.eof() or self.peek() == "@": - raise _Bail + if operator is None: + base = self.identifier() + self.rememberName(base) + if self.eof() or self.peek() == "@": + raise _Bail + else: + base = operator args = [] while not self.eat("@"): if self.eof(): @@ -410,15 +433,22 @@ def nameFragment(self, is_leading): if char == "?": self.take() if self.eat("$"): - if self.peek() in string.digits + "?": + if self.peek() in string.digits: raise _Bail - rendered = self.templateInstantiation() + operator = None + if self.peek() == "?": + # a template whose name is an operator: "??$?HH@S@@" is operator+ + self.take() + operator, _ = self.operatorName() + if not isinstance(operator, str): + raise _Bail + rendered = self.templateInstantiation(operator=operator) if not is_symbol_name: # the symbol's own template name is the one exception the mangler makes: # it is not recorded, so "??$f@H@N@@YAXV0@@Z" resolves 0 to N, not to # f. A template met anywhere else is recorded like any other name. self.rememberName(rendered) - return rendered, None + return rendered, "func" if operator else None if is_leading: # "??A" here is operator[], not the namespace below: the leading fragment is # the symbol's own name, and a namespace can only qualify it @@ -499,6 +529,11 @@ def operatorName(self): # "??__EFoo@@YAXU0@@Z" resolves its 0 to Foo self.requires_signature = True target = self.identifier() + if not self.eat("@"): + # the object may be named with scopes, and the whole of that name + # belongs inside the quotes rather than around them + raise _Bail + self.pos -= 1 self.rememberName(target) return f"`{_DYNAMIC_INITIALISERS[code]} '{target}''", "func" if self.take() != "K": @@ -618,7 +653,8 @@ def dimension(self): def arrayType(self, quals): count = self.dimension() - dims = "".join(f"[{self.dimension()}]" for _ in range(count)) + # an extent of nothing is spelled with nothing: "$$BY0A@H" is "int[]" + dims = "".join(f"[{extent or ''}]" for extent in (self.dimension() for _ in range(count))) self.array_element_depth += 1 try: element = self.type(quals) @@ -663,6 +699,12 @@ def dollarType(self, quals): return _base(self.templateInteger()) if not self.eat("$"): raise _Bail + if self.eat("B"): + # the type as written rather than as a parameter would decay it; an integer is + # an argument rather than a type, so it is not what this introduces + if self.peek() == "$": + raise _Bail + return self.type(quals) if self.eat("Y"): # an alias template is named rather than described self.simple = False @@ -881,12 +923,9 @@ def parse(self): self.take() if not self.eat("$") or self.take() != "J": raise _Bail - digits = 0 - while not self.eof() and self.peek() in string.digits: - self.take() - digits += 1 - if not digits: + if self.eof() or self.peek() not in string.digits: raise _Bail + self.take() extern_c = 'extern "C" ' char = self.peek() if char == "9": @@ -979,7 +1018,9 @@ def function(self, name, has_no_return_type): convention = _CALLING_CONVENTIONS.get(self.take()) if convention is None: raise _Bail - if has_no_return_type: + if has_no_return_type or self.peek() == "@": + # an operator may leave the return slot empty, the way a constructor does; a + # conversion operator may not, since its return is the type it converts to self.expect("@") returns = None else: diff --git a/tests/testMsvcDemangler.py b/tests/testMsvcDemangler.py index 16251639..c4b7ddc5 100644 --- a/tests/testMsvcDemangler.py +++ b/tests/testMsvcDemangler.py @@ -63,7 +63,7 @@ # Measured against llvm-undname 22.1.7 on the shipped corpus. Both are exact so that a # change in either direction has to be an explicit edit rather than passing silently. UNIQUE_CORPUS_NAMES = 609 -CORPUS_NAMES_UNDERSTOOD = 558 +CORPUS_NAMES_UNDERSTOOD = 577 # Forms this demangler does not model. Each must come back exactly as it went in: a wrong # expansion is worse than a decorated name, because it matches neither spelling. @@ -78,7 +78,6 @@ "??0@@QAE@XZ", # constructor with no class to name it after "?f@@YAX_Y@Z", # unknown extended basic type "?f@@YAX$$CZ@Z", # $$C without a qualifier - "?f@@YAXP6ZHXZ@Z", # function pointer with an unknown calling convention "?f@@YAX$$A6ZXZ@Z", # function type argument with an unknown calling convention "??_7type_info@@6Z@", # vftable with an unknown qualifier "??_7type_info@@6B@X", # vftable with trailing bytes @@ -99,7 +98,6 @@ "?f@@YAX_D@Z", "?f@@YA?ZUMatrix@@XZ", # return type carrying a qualifier that is not one "??0?$5Class@QAH@@QAE@XZ", # template name starting with a digit - "??$?HH@S@@QEAAAEAU0@H@Z", # operator template, whose name is not modelled "?e@FTypeWithQuals@@3U?K@A", # tag type named by an operator rather than an identifier # a special name takes a signature or a storage class by which code it is, never both "??_7A@B@ad@@YAXPEBQEAD@Z", # vftable given a function signature @@ -362,6 +360,48 @@ def test_a_discriminator_that_is_not_hexadecimal_is_refused(self): ] +FINAL_FORMS = [ + # a template whose name is an operator, and an operator that leaves its return empty + ("??$?HH@S@@QEAAAEAU0@H@Z", "public: struct S & __cdecl S::operator+(int)"), + ("??RFoo@@QBE@XZ", "public: __thiscall Foo::operator()(void) const"), + ("??RFoo@@QBEHXZ", "public: int __thiscall Foo::operator()(void) const"), + # the type as written rather than as a parameter would decay it, extent and all + ("??$f@$$BY01H@@YAXXZ", "void __cdecl f(void)"), + ("??0?$Class@$$BY0A@H@@QAE@XZ", "public: __thiscall Class::Class(void)"), + ("??0?$Class@$$BY04QAH@@QAE@XZ", "public: __thiscall Class::Class(void)"), + # two conventions spelled with an attribute, and two spelled with nothing + ("?swift_func@@YSXXZ", "void __attribute__((__swiftcall__)) swift_func(void)"), + ("?f@@YWXXZ", "void __attribute__((__swiftasynccall__)) f(void)"), + ("?f@@YTXXZ", "void f(void)"), + # a convention spelled with nothing keeps the parentheses a pointer needs + ("?f@@YAXP6ZHXZ@Z", "void __cdecl f(int ( *)(void))"), + ("?f@@YAXP8S@@AZXXZ@Z", "void __cdecl f(void ( S::*)(void))"), +] + + +class MsvcFinalFormsTestSuite(unittest.TestCase): + def test_the_final_forms_match_the_reference(self): + for mangled, expected in FINAL_FORMS: + with self.subTest(mangled=mangled): + self.assertEqual(demangle_msvc_symbol(mangled), expected) + + def test_shapes_these_forms_do_not_allow_are_refused(self): + for mangled in ( + "?overloaded_fn@@$$J00YAXXZ", # the marker counts with one digit, not two + "??0?$Class@$$B$0?9@@QAE@XZ", # "$$B" introduces a type, and an integer is not one + "??BFoo@@QBE@XZ", # a conversion operator's return names what it converts to + # every letter names a convention, most of them spelled with nothing. The + # reference takes any byte at all there; this requires a letter, so that a + # name mangled with something else declines rather than reads as a function + "?f@@Y1XXZ", + "?f@@YAXP61HXZ@Z", + "?f@@YAXP8S@@A1XXZ@Z", + "??$f@$$A61HXZ@@YAXXZ", + ): + with self.subTest(mangled=mangled): + self.assertEqual(demangle_msvc_symbol(mangled), mangled) + + class MsvcLatestFormsTestSuite(unittest.TestCase): def test_the_latest_forms_match_the_reference(self): for mangled, expected in LATEST_FORMS: @@ -497,7 +537,6 @@ def test_shapes_neither_form_allows_are_refused(self): "?f@@YAXPE8S@@AEAXXZ@Z", # __ptr64 is not written in front of a member function "?f@@YAX$0A@@Z", # an integer is a template argument, never a parameter "??$f@$0@@YAXXZ", # ... and needs digits - "?f@@YAXP8S@@AZXXZ@Z", # "Z" is not a calling convention "?f@@YAXP8?0S@@@AEXXZ@Z", # nor is a constructor a class to point into ): with self.subTest(mangled=mangled): From 571519d2029cb138b48d0dbcf3fd821c437e7a18 Mon Sep 17 00:00:00 2001 From: r0ny123 <49360849+r0ny123@users.noreply.github.com> Date: Tue, 18 Aug 2026 13:01:51 +0530 Subject: [PATCH 21/23] feat(labels): read the address of a symbol, and thunks A template argument may be the address of a symbol, or the symbol itself. What follows "$1" or "$E" is a complete decorated name, and it is read in the template's own back-reference scope rather than a fresh one or the enclosing one: "??$f@VBar@@$1?x@0@3HA@@YAXXZ" resolves its 0 to f, the template's own entry, and not to Bar. Two earlier attempts at this guessed the scope instead of asking for it, and each cost wrong answers. A thunk stands in for a member function and adjusts "this" on the way through. Three forms: an adjustor, whose access letter says how much and which access it carries; a vtordisp, which writes two signed displacements through a virtual base; and a vcall, which names no access at all and carries no parameters, the slot being the whole of it. A vcall name and a vcall thunk require each other, so neither reads alone. A member data pointer may point at a function type as well as at a qualified pointer, which the guard against the second was refusing along with it. Corpus 574 to 592 of 609 exact, none spelled differently. --- .../common/labelprovider/MsvcDemangler.py | 125 +++++++++++++++--- tests/testMsvcDemangler.py | 41 +++++- 2 files changed, 147 insertions(+), 19 deletions(-) diff --git a/src/smda/common/labelprovider/MsvcDemangler.py b/src/smda/common/labelprovider/MsvcDemangler.py index 0ac53603..375740d4 100644 --- a/src/smda/common/labelprovider/MsvcDemangler.py +++ b/src/smda/common/labelprovider/MsvcDemangler.py @@ -60,6 +60,18 @@ "Y": "", "Z": "", } +# a thunk stands in for a member function and adjusts "this" on the way through; the +# reference prefixes the whole spelling and marks the name with the adjustment +_ADJUSTOR_ACCESS = { + "G": ("private", False, False), + "H": ("private", False, False), + "O": ("protected", False, True), + "P": ("protected", False, True), + "W": ("public", False, True), + "X": ("public", False, True), +} +# a thunk that adjusts through a virtual base writes two displacements after its access +_VTORDISP_ACCESS = {"0": "private", "2": "protected", "4": "public"} _FUNCTION_ACCESS = { "A": ("private", False, False), "B": ("private", False, False), @@ -121,7 +133,7 @@ # what runs around a namespace-scope object with a non-trivial lifetime _DYNAMIC_INITIALISERS = {"E": "dynamic initializer for", "F": "dynamic atexit destructor for"} _DATA_SPECIAL_OPERATORS = frozenset("78S") -_UNMODELLED_DATA_SPECIAL_OPERATORS = frozenset("9AB") +_UNMODELLED_DATA_SPECIAL_OPERATORS = frozenset("AB") _EXTENDED_OPERATORS = { "0": "operator/=", "1": "operator%=", @@ -476,20 +488,27 @@ def localScope(self): # the scope spelled zero spelled = "0" if self.eat("@") else self.templateInteger() self.expect("?") + return f"`{self.nestedSymbol()}'::`{spelled}'" + + def nestedSymbol(self): + """A complete decorated name written inside another one. + + It continues this name's back-reference table rather than opening its own, so + "?N@?1??SN@?$NS@H@0@QEAAHXZ@4HA" resolves its 0 to the outer N and + "??$f@VBar@@$1?x@0@3HA@@YAXXZ" resolves its 0 to the template's own f. It is still a + symbol, so its own leading template is not recorded either, and it ends where it + ends rather than at the end of the text. + """ inner = _Demangler(self.text) inner.pos = self.pos inner.nested = True - # the enclosing name continues this name's back-reference table rather than opening - # its own: "?N@?1??SN@?$NS@H@0@QEAAHXZ@4HA" resolves its 0 to the outer N inner.name_backrefs = self.name_backrefs inner.arg_backrefs = self.arg_backrefs - # the enclosing name is a symbol in its own right, so its own leading template is - # not recorded either - the same exception the outer name gets inner.at_symbol_name = True inner.depth = self.depth - enclosing = inner.parse() + rendered = inner.parse() self.pos = inner.pos - return f"`{enclosing}'::`{spelled}'" + return rendered def anonymousNamespace(self): """The unnamed namespace of one translation unit: "?A" and an optional discriminator. @@ -547,6 +566,8 @@ def operatorName(self): raise _Bail if code in _UNMODELLED_DATA_SPECIAL_OPERATORS: raise _Bail + if code == "9": + return name, "vcall" return name, "data" if code in _DATA_SPECIAL_OPERATORS else "func" code = self.take() if code in ("0", "1"): @@ -690,6 +711,13 @@ def templateInteger(self): return f"-{value}" if negative else str(value) def dollarType(self, quals): + if self.peek() in ("1", "E") and self.template_depth: + # the address of a symbol, or the symbol itself: what follows is a complete + # decorated name, read in this template's back-reference scope - which is why + # "??$f@VBar@@$1?x@0@3HA@@YAXXZ" resolves its 0 to f rather than to Bar + prefix = "&" if self.take() == "1" else "" + self.simple = False + return _base(prefix + self.nestedSymbol()) if self.peek() == "0": if not self.template_depth: # an integer is an argument, not a type: it appears only in a template list @@ -797,10 +825,11 @@ class and what the member itself is qualified by, so "PRfoo@@D" is """ member_quals = _MEMBER_DATA_QUALS[self.take()] + unaligned owner = self.qualifiedName()[0] - if self.peek() in ("Q", "R", "S"): - # the reference does not spell the qualifiers such a pointer would carry here - - # "PQfoo@@SAPEAX" is "void **foo::*", not "void *const volatile *foo::*" - and - # nothing on the producer side explains which is right, so this declines + if self.peek() in ("Q", "R", "S") and self.text[self.pos + 1 : self.pos + 2] not in ("6", "8"): + # a qualified pointer as the member type is the one shape to avoid: the reference + # does not spell the qualifiers it would carry - "PQfoo@@SAPEAX" is + # "void **foo::*" - and nothing on the producer side says which is right. A + # function type after the same letter is not that shape and reads normally. raise _Bail member = self.type(member_quals) self.simple = False @@ -919,10 +948,8 @@ def parse(self): if self.eof(): raise _Bail char = self.peek() - if char == "$": - self.take() - if not self.eat("$") or self.take() != "J": - raise _Bail + if char == "$" and self.text.startswith("$$J", self.pos): + self.pos += 3 if self.eof() or self.peek() not in string.digits: raise _Bail self.take() @@ -965,9 +992,11 @@ def parse(self): # something is pointed at: "?s@@3PEAHEA" is a name and "?s@@3HEA" is not trailing = self.take() restrict = () + seen = set() while trailing in ("E", "I"): - if declared[0] != "ind": + if declared[0] != "ind" or trailing in seen: raise _Bail + seen.add(trailing) if trailing == "I": restrict = ("__restrict",) trailing = self.take() @@ -1000,12 +1029,69 @@ def parse(self): else: declared = _qualifyDeclared(declared, member_quals) return f"{_DATA_ACCESS[char]}{self.rendered(declared, name)}" - return extern_c + self.function(name, has_no_return_type) + return extern_c + self.function(name, has_no_return_type, special_form == "vcall") + + def signedDisplacement(self): + """One of a vtordisp thunk's two displacements, which are signed and 32 bits wide.""" + value = int(self.templateInteger()) + return value - (1 << 32) if value >= (1 << 31) else value - def function(self, name, has_no_return_type): + def thunkFunction(self, name, is_vcall): + """A thunk whose access slot begins with "$": a vcall, or an adjustment through a + virtual base. + + A vcall names no access and carries no parameters - the whole of it is the slot it + dispatches through - while the vtordisp forms are ordinary virtual member functions + with two displacements written in front of the signature. + """ + code = self.take() + if code == "B": + if not is_vcall: + raise _Bail + slot = self.templateInteger() + self.memberQualifiers() + convention = _CALLING_CONVENTIONS.get(self.take()) + if convention is None: + raise _Bail + if not self.nested and not self.eof(): + raise _Bail + return f"[thunk]: {convention} {name}{{{slot}, {{flat}}}}".replace(" ", " ") + access = _VTORDISP_ACCESS.get(code) + if access is None: + raise _Bail + first = self.signedDisplacement() + second = self.signedDisplacement() + self.member_cv = self.memberQualifiers() + convention = _CALLING_CONVENTIONS.get(self.take()) + if convention is None: + raise _Bail + returns = None if self.peek() == "@" and self.take() else self.returnType() + params = self.parameters() + self.expect("Z") + if not self.nested and not self.eof(): + raise _Bail + spelled = f"{name}`vtordisp{{{first}, {second}}}'" + body = ( + f"{convention} {spelled}({params})" + if returns is None + else self.rendered(_function(convention, params, returns), spelled) + ) + return f"[thunk]: {access}: virtual {body}{self.member_cv}" + + def function(self, name, has_no_return_type, is_vcall=False): access_char = self.take() + thunk = "" + if access_char == "$": + return self.thunkFunction(name, is_vcall) + if is_vcall: + # the slot dispatched through is the whole of a vcall, so it is spelled one way + raise _Bail if access_char == "Y": access, is_static, is_virtual = None, False, False + elif access_char in _ADJUSTOR_ACCESS: + access, is_static, is_virtual = _ADJUSTOR_ACCESS[access_char] + thunk = f"`adjustor{{{self.templateInteger()}}}'" + self.member_cv = self.memberQualifiers() else: entry = _FUNCTION_ACCESS.get(access_char) if entry is None: @@ -1030,6 +1116,9 @@ def function(self, name, has_no_return_type): if not self.nested and not self.eof(): raise _Bail pieces = [] + if thunk: + pieces.append("[thunk]: ") + name = f"{name}{thunk}" if access: pieces.append(f"{access}: ") if is_static: diff --git a/tests/testMsvcDemangler.py b/tests/testMsvcDemangler.py index c4b7ddc5..08814816 100644 --- a/tests/testMsvcDemangler.py +++ b/tests/testMsvcDemangler.py @@ -63,7 +63,7 @@ # Measured against llvm-undname 22.1.7 on the shipped corpus. Both are exact so that a # change in either direction has to be an explicit edit rather than passing silently. UNIQUE_CORPUS_NAMES = 609 -CORPUS_NAMES_UNDERSTOOD = 577 +CORPUS_NAMES_UNDERSTOOD = 592 # Forms this demangler does not model. Each must come back exactly as it went in: a wrong # expansion is worse than a decorated name, because it matches neither spelling. @@ -379,6 +379,45 @@ def test_a_discriminator_that_is_not_hexadecimal_is_refused(self): ] +THUNKS_AND_ADDRESSES = [ + # the address of a symbol, read in the template's own back-reference scope + ("??$f@$1?x@@3HA@@YAXXZ", "void __cdecl f<&int x>(void)"), + ("??$f@VBar@@$1?x@0@3HA@@YAXXZ", "void __cdecl f(void)"), + ("??$f@$E?x@@3HA@@YAXXZ", "void __cdecl f(void)"), + # a thunk that adjusts "this" on the way through + ( + "??_EBase@@G3AEPAXI@Z", + "[thunk]: private: void * __thiscall Base::`vector deleting dtor'`adjustor{4}'(unsigned int)", + ), + ( + "??_EDerived@@$4PPPPPPPM@A@EAAPEAXI@Z", + "[thunk]: public: virtual void * __cdecl Derived::`vector deleting dtor'`vtordisp{-4, 0}'(unsigned int)", + ), + # a vcall names no access and carries no parameters + ("??_9Base@@$B7AA", "[thunk]: __cdecl Base::`vcall'{8, {flat}}"), +] + + +class MsvcThunkTestSuite(unittest.TestCase): + def test_thunks_and_addresses_match_the_reference(self): + for mangled, expected in THUNKS_AND_ADDRESSES: + with self.subTest(mangled=mangled): + self.assertEqual(demangle_msvc_symbol(mangled), expected) + + def test_a_vcall_and_its_thunk_require_each_other(self): + for mangled in ( + "??0f9Base@@$B7AA", + "??_9Base10@@YAADMXZ", + # the reference reads these; this declines them, as it does elsewhere, rather + # than take a digit for a convention or ignore bytes after the name + "??_9Base@@$B7A1", + "??_9Base@@$B7AAX??_EDerived@@$4A@A@EA1PEAXI@Z", + "??_EDerived@@$4A@A@EAAPEAXI@ZX", + ): + with self.subTest(mangled=mangled): + self.assertEqual(demangle_msvc_symbol(mangled), mangled) + + class MsvcFinalFormsTestSuite(unittest.TestCase): def test_the_final_forms_match_the_reference(self): for mangled, expected in FINAL_FORMS: From 26fb8b303a22643eca8bd0d3753c326740d5fe12 Mon Sep 17 00:00:00 2001 From: r0ny123 <49360849+r0ny123@users.noreply.github.com> Date: Tue, 18 Aug 2026 14:03:47 +0530 Subject: [PATCH 22/23] feat(labels): read every name in the reference corpus The last of the forms, and with them the corpus is understood end to end: all 609 names come back spelled as llvm-undname spells them, and the test now pins each one rather than counting how many pass. A string literal is spelled by its contents, not by a placeholder. The length counts the bytes with their terminator, eight characters of hash follow it, and a byte is written plainly, as one of ten punctuation escapes, or as "$" and two nibbles. The wider encodings spell their bytes differently and are left alone. The rest of the RTTI family names a class rather than a type, each with its own storage: the base class descriptor says where the base sits, the array and hierarchy descriptors carry nothing, and the complete object locator is written like a vftable. A conversion operator is named by the type it converts to, which is written in the return slot and so is only known once the signature has been read. Also here: a placeholder type the compiler writes where a deduced one belongs, a name replaced by a hash of itself along with whatever decorated name follows it, the guards around a function-local static and the thread-local form, and a dynamic initialiser whose object is a data symbol written without its leading "?". Two displacements were being read too wide: a vtordisp field is 32 bits, so the value is masked before its sign is taken. --- .../common/labelprovider/MsvcDemangler.py | 231 ++++++++++++++++-- tests/testMsvcDemangler.py | 69 +++++- 2 files changed, 283 insertions(+), 17 deletions(-) diff --git a/src/smda/common/labelprovider/MsvcDemangler.py b/src/smda/common/labelprovider/MsvcDemangler.py index 375740d4..bd14dabe 100644 --- a/src/smda/common/labelprovider/MsvcDemangler.py +++ b/src/smda/common/labelprovider/MsvcDemangler.py @@ -132,8 +132,29 @@ # decline instead of being read as one of these # what runs around a namespace-scope object with a non-trivial lifetime _DYNAMIC_INITIALISERS = {"E": "dynamic initializer for", "F": "dynamic atexit destructor for"} +# what guards a function-local static, and the storage class both forms are written with +_GUARDS = {"B": "`local static guard'", "__J": "`local static thread guard'"} +# what a string literal's escapes stand for, and how the reference spells a byte back +_LITERAL_ESCAPES = { + "0": ",", + "1": "/", + "2": "\\", + "3": ":", + "4": ".", + "5": " ", + "6": "\n", + "7": "\t", + "8": "'", + "9": "-", +} +_LITERAL_SPELLINGS = {"\\": "\\\\", "\n": "\\n", "\t": "\\t", "'": "\\'", "\0": "\\0", '"': '\\"'} +_RTTI_NAMES = { + "2": "`RTTI Base Class Array'", + "3": "`RTTI Class Hierarchy Descriptor'", + "4": "`RTTI Complete Object Locator'", +} _DATA_SPECIAL_OPERATORS = frozenset("78S") -_UNMODELLED_DATA_SPECIAL_OPERATORS = frozenset("AB") +_UNMODELLED_DATA_SPECIAL_OPERATORS = frozenset("A") _EXTENDED_OPERATORS = { "0": "operator/=", "1": "operator%=", @@ -296,11 +317,24 @@ def _qualify(node, quals): return _apply_quals(node, tuple(qual for qual in quals if qual not in spelled)) +class _Conversion: + """A conversion operator, whose name is the type it converts to. + + That type is written in the return slot, which is read long after the name, so the + spelling is finished once the signature has been. + """ + + class _Structor: - """A constructor or destructor: its spelling comes from the class it belongs to.""" + """A constructor or destructor: its spelling comes from the class it belongs to. - def __init__(self, is_destructor): + It may itself be a template, in which case the arguments follow the class name it + borrows: "??$?0N@?$Foo@H@@QEAA@N@Z" is Foo::Foo. + """ + + def __init__(self, is_destructor, arguments=""): self.is_destructor = is_destructor + self.arguments = arguments class _Bail(Exception): @@ -406,6 +440,10 @@ def templateInstantiation(self, operator=None): while not self.eat("@"): if self.eof(): raise _Bail + if self.text.startswith("$S", self.pos): + # a third spelling of the empty pack + self.pos += 2 + continue if self.text.startswith("$$V", self.pos) and not self.text.startswith("$$$V", self.pos): # the other spelling of an empty pack self.pos += 3 @@ -452,6 +490,11 @@ def nameFragment(self, is_leading): # a template whose name is an operator: "??$?HH@S@@" is operator+ self.take() operator, _ = self.operatorName() + if isinstance(operator, _Structor): + # a constructor may be a template too, and its arguments follow the + # class name it borrows rather than replacing it + arguments = self.templateInstantiation(operator="") + return _Structor(operator.is_destructor, arguments), "func" if not isinstance(operator, str): raise _Bail rendered = self.templateInstantiation(operator=operator) @@ -490,7 +533,22 @@ def localScope(self): self.expect("?") return f"`{self.nestedSymbol()}'::`{spelled}'" - def nestedSymbol(self): + def namesADataSymbol(self): + """Whether what follows is a data symbol rather than a function, without reading it. + + Only the storage class tells the two apart, and it comes after the whole name, so + this walks a throwaway cursor to it and reports what it found. + """ + probe = _Demangler("?" + self.text[self.pos :]) + probe.nested = True + try: + probe.expect("?") + probe.qualifiedName() + except (_Bail, RecursionError): + return False + return not probe.eof() and probe.peek() in _DATA_ACCESS + + def nestedSymbol(self, leading_question=True): """A complete decorated name written inside another one. It continues this name's back-reference table rather than opening its own, so @@ -499,15 +557,20 @@ def nestedSymbol(self): symbol, so its own leading template is not recorded either, and it ends where it ends rather than at the end of the text. """ - inner = _Demangler(self.text) - inner.pos = self.pos + if leading_question: + inner = _Demangler(self.text) + inner.pos = self.pos + else: + # the "?" this name would open with was spent on the code that introduced it, + # so it is read over a copy that has one + inner = _Demangler("?" + self.text[self.pos :]) inner.nested = True inner.name_backrefs = self.name_backrefs inner.arg_backrefs = self.arg_backrefs inner.at_symbol_name = True inner.depth = self.depth rendered = inner.parse() - self.pos = inner.pos + self.pos = inner.pos if leading_question else self.pos + inner.pos - 1 return rendered def anonymousNamespace(self): @@ -539,17 +602,31 @@ def operatorName(self): if self.eat("_"): if self.eat("_"): code = self.peek() + if code == "J": + self.take() + return _GUARDS["__J"], "guard" if code in _DYNAMIC_INITIALISERS: self.take() + self.requires_signature = True if self.peek() == "?": - # the object it runs for is named plainly, not by another special name - raise _Bail + # it may run for a whole symbol of its own, scopes and storage and + # all: "??__E?i@C@@0HA@@YAXXZ" runs for "private: static int C::i" + target = self.nestedSymbol() + # the symbol it runs for is followed by the terminator its own name + # would have carried, and the enclosing name still needs one + self.expect("@") + return f"`{_DYNAMIC_INITIALISERS[code]} `{target}''", "func" + if self.namesADataSymbol(): + # it may run for a data symbol written without its leading "?", the + # rest reading as it does for one that has it + whole = self.nestedSymbol(leading_question=False) + # this one leaves the single terminator the enclosing name needs + return f"`{_DYNAMIC_INITIALISERS[code]} `{whole}''", "func" # what it runs for is recorded, unlike a literal operator's suffix: # "??__EFoo@@YAXU0@@Z" resolves its 0 to Foo - self.requires_signature = True target = self.identifier() if not self.eat("@"): - # the object may be named with scopes, and the whole of that name + # a plain name may still be qualified, and the whole of that name # belongs inside the quotes rather than around them raise _Bail self.pos -= 1 @@ -561,6 +638,28 @@ def operatorName(self): # the reference spells the pair as operator ""suffix return f'operator ""{self.identifier()}', "func" code = self.take() + if code == "C": + return self.stringLiteral(), "descriptor" + if code == "R" and self.peek() in "1234": + # the rest of the RTTI family names a class rather than a type, and each + # is written with its own storage: "8" for these three, the vftable form + # for the locator. The descriptor carries where the base sits. + which = self.take() + if which == "1": + at = ", ".join(str(int(self.templateInteger())) for _ in range(4)) + return f"`RTTI Base Class Descriptor at ({at})'", "rtti" + return _RTTI_NAMES[which], "data" if which == "4" else "rtti" + if code == "R" and self.peek() == "0": + # a type descriptor names the type it describes rather than a function + self.take() + described = self.rendered(self.returnType()) + self.expect("@") + self.expect("8") + if not self.nested and not self.eof(): + raise _Bail + # the marker abuts a type that already ends in a sigil, as a declarator does + separator = "" if described.endswith(("*", "&")) else " " + return f"{described}{separator}`RTTI Type Descriptor'", "descriptor" name = _EXTENDED_OPERATORS.get(code) if name is None: raise _Bail @@ -568,10 +667,24 @@ def operatorName(self): raise _Bail if code == "9": return name, "vcall" + if code == "B": + return name, "guard" return name, "data" if code in _DATA_SPECIAL_OPERATORS else "func" + if self.eat("@"): + # a name the compiler replaced with a hash of itself; whatever follows the hash + # is not part of it + digest = [] + while not self.eat("@"): + digest.append(self.take()) + # a further decorated name after the hash belongs to it; anything else does not + suffix = self.text[self.pos :] if self.text.startswith("??", self.pos) else "" + self.pos = len(self.text) + return f"??@{''.join(digest)}@{suffix}", "descriptor" code = self.take() if code in ("0", "1"): return _Structor(code == "1"), "func" + if code == "B": + return _Conversion(), "func" name = _OPERATORS.get(code) if name is None: raise _Bail @@ -587,6 +700,9 @@ def qualifiedName(self): def qualifiedNameBody(self): first, special_form = self.nameFragment(True) + if special_form == "descriptor": + # a type descriptor has read the whole name, the type it describes included + return first, False, special_form scopes = [] while True: if self.eat("@"): @@ -595,11 +711,14 @@ def qualifiedNameBody(self): raise _Bail scopes.append(self.nameFragment(False)[0]) scopes.reverse() + if isinstance(first, _Conversion): + return "::".join(scopes + ["\0conversion\0"]), False, special_form if isinstance(first, _Structor): if not scopes: raise _Bail klass = scopes[-1] - first = "~" + klass if first.is_destructor else klass + spelled = klass + first.arguments + first = "~" + spelled if first.is_destructor else spelled return "::".join(scopes + [first]), True, special_form return "::".join(scopes + [first]), False, special_form @@ -657,6 +776,12 @@ def typeBody(self, quals): # type belongs - a pointee, a template argument, a return type - and the mangler # writes none of those; reading them invented spellings for impossible names. raise _Bail + if char == "?" and self.peek() == "<": + # a placeholder the compiler writes where a type would go, named in brackets: + # "?A?@@" is the deduced return of a function declared with it + placeholder = self.identifier() + self.expect("@") + return _apply_quals(_base(placeholder), quals) raise _Bail def rendered(self, node, declarator=""): @@ -891,8 +1016,9 @@ def memberQualifiers(self): """ restrict = "" reference = "" + unaligned = "" seen = set() - while self.peek() in "EIGH": + while self.peek() in "EIGHF": char = self.take() # each of them is written at most once: "HH" is not a name if char in seen or (char in "GH" and seen & {"G", "H"}): @@ -900,12 +1026,14 @@ def memberQualifiers(self): seen.add(char) if char == "I": restrict = " __restrict" + elif char == "F": + unaligned = " __unaligned" elif char in ("G", "H"): reference = " &" if char == "G" else " &&" qualifier = _CV.get(self.take()) if qualifier is None: raise _Bail - return f"{qualifier}{restrict}{reference}" + return f"{qualifier}{unaligned}{restrict}{reference}" def parameters(self): """A parameter list, recording each composite parameter for later back-references. @@ -945,6 +1073,23 @@ def parse(self): # counts how many characters of the original mangling it kept, which is not spelled extern_c = "" name, has_no_return_type, special_form = self.qualifiedName() + if special_form == "descriptor": + # it is the whole name: what it describes has already been read + return name + if special_form == "rtti": + # these three are written with one storage class and nothing else + self.expect("8") + if not self.nested and not self.eof(): + raise _Bail + return name + if special_form == "guard": + # a guard is written with one storage class and a number, which counts the + # static it guards within its function and is left out when it is the first + self.expect("5") + counted = self.templateInteger() + if not self.nested and not self.eof(): + raise _Bail + return name if counted == "0" else f"{name}{{{counted}}}" if self.eof(): raise _Bail char = self.peek() @@ -1031,9 +1176,48 @@ def parse(self): return f"{_DATA_ACCESS[char]}{self.rendered(declared, name)}" return extern_c + self.function(name, has_no_return_type, special_form == "vcall") + def stringLiteral(self): + """The literal a "??_C" name stands for, spelled the way the reference spells it. + + The length counts the terminator, the eight characters after it are a hash of the + bytes, and the bytes themselves are written plainly or as an escape - a digit for + one of ten punctuation characters, or "$" and two nibbles for any byte at all. + """ + self.expect("@") + self.expect("_") + if self.take() != "0": + # "_0" is a narrow string; the wider encodings spell their bytes differently + raise _Bail + length = int(self.templateInteger()) + while not self.eat("@"): + # the hash is not spelled, but it has to be walked past + self.take() + decoded = [] + while not self.eat("@"): + char = self.take() + if char != "?": + decoded.append(char) + continue + marker = self.take() + if marker == "$": + high, low = self.take(), self.take() + if not ("A" <= high <= "P" and "A" <= low <= "P"): + raise _Bail + decoded.append(chr((ord(high) - 65) * 16 + ord(low) - 65)) + elif marker in _LITERAL_ESCAPES: + decoded.append(_LITERAL_ESCAPES[marker]) + else: + raise _Bail + if len(decoded) != length or (decoded and decoded[-1] != "\0"): + raise _Bail + if not self.nested and not self.eof(): + raise _Bail + spelled = "".join(_LITERAL_SPELLINGS.get(char, char) for char in decoded[:-1]) + return f'"{spelled}"' + def signedDisplacement(self): """One of a vtordisp thunk's two displacements, which are signed and 32 bits wide.""" - value = int(self.templateInteger()) + value = int(self.templateInteger()) & 0xFFFFFFFF return value - (1 << 32) if value >= (1 << 31) else value def thunkFunction(self, name, is_vcall): @@ -1056,11 +1240,21 @@ def thunkFunction(self, name, is_vcall): if not self.nested and not self.eof(): raise _Bail return f"[thunk]: {convention} {name}{{{slot}, {{flat}}}}".replace(" ", " ") + if code == "R": + access = _VTORDISP_ACCESS.get(self.take()) + if access is None: + raise _Bail + displacements = [self.signedDisplacement() for _ in range(4)] + return self.thunkBody(name, access, "vtordispex", displacements) access = _VTORDISP_ACCESS.get(code) if access is None: raise _Bail first = self.signedDisplacement() second = self.signedDisplacement() + return self.thunkBody(name, access, "vtordisp", [first, second]) + + def thunkBody(self, name, access, kind, displacements): + """The signature a vtordisp or vtordispex thunk carries, once its numbers are read.""" self.member_cv = self.memberQualifiers() convention = _CALLING_CONVENTIONS.get(self.take()) if convention is None: @@ -1070,7 +1264,8 @@ def thunkFunction(self, name, is_vcall): self.expect("Z") if not self.nested and not self.eof(): raise _Bail - spelled = f"{name}`vtordisp{{{first}, {second}}}'" + written = ", ".join(str(value) for value in displacements) + spelled = f"{name}`{kind}{{{written}}}'" body = ( f"{convention} {spelled}({params})" if returns is None @@ -1125,6 +1320,10 @@ def function(self, name, has_no_return_type, is_vcall=False): pieces.append("static ") if is_virtual: pieces.append("virtual ") + if "\0conversion\0" in name: + if returns is None: + raise _Bail + name = name.replace("\0conversion\0", f"operator {self.rendered(returns)}") if returns is None: pieces.append(f"{convention} {name}({params})") else: diff --git a/tests/testMsvcDemangler.py b/tests/testMsvcDemangler.py index 08814816..343eade0 100644 --- a/tests/testMsvcDemangler.py +++ b/tests/testMsvcDemangler.py @@ -63,7 +63,7 @@ # Measured against llvm-undname 22.1.7 on the shipped corpus. Both are exact so that a # change in either direction has to be an explicit edit rather than passing silently. UNIQUE_CORPUS_NAMES = 609 -CORPUS_NAMES_UNDERSTOOD = 592 +CORPUS_NAMES_UNDERSTOOD = 609 # Forms this demangler does not model. Each must come back exactly as it went in: a wrong # expansion is worse than a decorated name, because it matches neither spelling. @@ -398,6 +398,73 @@ def test_a_discriminator_that_is_not_hexadecimal_is_refused(self): ] +COMPLETING_FORMS = [ + # a literal is spelled by its contents, which the length counts with the terminator + ("??_C@_02PCEFGMJL@hi?$AA@", '"hi"'), + ("??_C@_00CNPNBAHC@?$AA@", '""'), + ("??_C@_0M@LACCLLLM@Hello?5world?$AA@", '"Hello world"'), + # the rest of the RTTI family names a class, and the descriptor says where the base sits + ("??_R1A@?0A@EA@Base@@8", "Base::`RTTI Base Class Descriptor at (0, -1, 0, 64)'"), + ("??_R2Base@@8", "Base::`RTTI Base Class Array'"), + ("??_R3Base@@8", "Base::`RTTI Class Hierarchy Descriptor'"), + ("??_R4Base@@6B@", "const Base::`RTTI Complete Object Locator'"), + # a conversion operator is named by the type it converts to, which it writes as its return + ("??BBase@@QEAAHXZ", "public: int __cdecl Base::operator int(void)"), + ("??BFoo@@QBEPAHXZ", "public: int * __thiscall Foo::operator int *(void) const"), + # a placeholder the compiler writes where a type would go + ("?f@@YA?A?@@XZ", " __cdecl f(void)"), + # a name replaced by a hash of itself, and what may follow it + ("??@a6a285da2eea70dba6b578022be61d81@", "??@a6a285da2eea70dba6b578022be61d81@"), + ("??@a6a285da2eea70dba6b578022be61d81@asdf", "??@a6a285da2eea70dba6b578022be61d81@"), + # a guard, and what runs for a static with a lifetime + ("??_Bx@@51", "x::`local static guard'{2}"), + ("??__Jx@@51", "x::`local static thread guard'{2}"), + ("??__E?i@C@@0HA@@YAXXZ", "void __cdecl `dynamic initializer for `private: static int C::i''(void)"), +] + + +COMPLETING_DECLINED = [ + "??_R0?AUBase@@@8X", # a type descriptor ends where it ends + "??$f@$X@@YAXXZ", # "$" introduces one of a fixed set, and "X" is not among them + "??_R2Base@@8X", # nor does the rest of the RTTI family carry anything after its storage + "??_Bx@@51X", # nor a guard + "??__EFoo@@3HA", # what runs code takes a signature, never a storage class + "??_C@_12ABCDEFGH@hi?$AA@", # a wide literal spells its bytes differently + "??_C@_02ABCDEFGH@h?$Qi?$AA@", # a byte is written as two nibbles from "A" to "P" + "??_C@_02ABCDEFGH@h?zi?$AA@", # and an escape names one of ten characters + "??_C@_05ABCDEFGH@hi?$AA@", # the length counts the bytes, terminator included + "??_C@_02ABCDEFGH@hi?$AA@X", # and nothing follows the literal + "??_9Base@@$RB7AA", # a thunk through a virtual base names an access this does not + # a conversion operator is named by its return, which a template argument list displaces + "??$?BH@S@@QEAAAEAU0@H@Z", + "??_7Base@@3HA", # a vftable is written with its own storage class and no other + "??__EFoo@@51", # and what runs code takes no storage class at all, guard or otherwise +] + + +class MsvcCompletingFormsTestSuite(unittest.TestCase): + def test_shapes_the_completing_forms_do_not_allow_are_refused(self): + for mangled in COMPLETING_DECLINED: + with self.subTest(mangled=mangled): + self.assertEqual(demangle_msvc_symbol(mangled), mangled) + + def test_the_completing_forms_match_the_reference(self): + for mangled, expected in COMPLETING_FORMS: + with self.subTest(mangled=mangled): + self.assertEqual(demangle_msvc_symbol(mangled), expected) + + def test_the_whole_reference_corpus_is_understood(self): + corpus = [ + line.rstrip("\n").split("\t") + for line in (Path(__file__).parent / "msvc_reference_corpus.txt").read_text(encoding="utf-8").splitlines() + if line.strip() and not line.startswith("#") and "\t" in line + ] + self.assertEqual(len(corpus), UNIQUE_CORPUS_NAMES) + for mangled, expected in corpus: + with self.subTest(mangled=mangled): + self.assertEqual(demangle_msvc_symbol(mangled), expected) + + class MsvcThunkTestSuite(unittest.TestCase): def test_thunks_and_addresses_match_the_reference(self): for mangled, expected in THUNKS_AND_ADDRESSES: From 5d5d15ca5b15c484c03e43f1dc0da4e40b29580f Mon Sep 17 00:00:00 2001 From: r0ny123 <49360849+r0ny123@users.noreply.github.com> Date: Tue, 18 Aug 2026 15:18:13 +0530 Subject: [PATCH 23/23] feat(labels): settle twelve grammar rules against the reference Each rule below was established by probing llvm-undname directly, because the reference corpus carries no name that exercises it: - an attribute-spelled calling convention carries a space of its own in front of a declarator, where __cdecl and __vectorcall carry only the separator, and a convention spelled with nothing leaves no gap at all - a member function's modifiers are written __ptr64, __restrict, __unaligned then a reference qualifier, each at most once, and are spelled back in that same order - a vcall is the slot it dispatches through: it carries neither a qualifier nor a convention of its own, and is never spelled with storage - "$$B" stands where an argument stands and nowhere a type may nest, and never over a function type - an integer is an argument, so it is not an array's element either, and an array of no dimensions is not a type - no modifier stands in front of a function type or a member function pointer - how far a base class table reaches, and what it is flagged with, are not negative - what a dynamic initialiser runs for is a name, and a digit there stands for an earlier name that does not exist - a conversion operator reads what it converts to from its return slot, so it is spelled with a signature and never with storage A rendering fix comes with them. Whether a declarator was a nested function was decided by looking for "(" in the rendered text, which mis-fires on a name that contains one of its own - an operator() name, or a local scope embedding a signature. The producing site now says so explicitly. The same class of test appeared once more in the file, an unanchored "::*" search where the anchored expression six lines below asks the same question; both now use it. Corpus stays at 609 of 609 spelled exactly as the reference spells them, 0 given a different spelling. Differential fuzzing over seven seeds and 110495 derived names goes from 57 wrong answers to 2, both of them the divergences kept deliberately: __int128, which clang's own Microsoft mangler emits and the reference cannot read back, and the vector-deleting-destructor family, which it spells with an empty operator name. --- .../common/labelprovider/MsvcDemangler.py | 108 ++++++++++++------ tests/testMsvcDemangler.py | 75 ++++++++++++ 2 files changed, 146 insertions(+), 37 deletions(-) diff --git a/src/smda/common/labelprovider/MsvcDemangler.py b/src/smda/common/labelprovider/MsvcDemangler.py index bd14dabe..66683a51 100644 --- a/src/smda/common/labelprovider/MsvcDemangler.py +++ b/src/smda/common/labelprovider/MsvcDemangler.py @@ -218,7 +218,7 @@ def _function(convention, params, returns, member_cv=""): return ("func", convention, params, returns, member_cv) -def _render(node, declarator=""): +def _render(node, declarator="", declarator_is_function=False): """Spell a type around a declarator, the way C nests one inside the other. A pointer or array binds to the declarator built so far, and a name therefore ends up @@ -241,7 +241,7 @@ def _render(node, declarator=""): token = node[1] + " ".join(node[2]) # a nested *function* declarator is separated from the sigil - "int * (__cdecl *)()" # - while a parenthesised pointer declarator abuts it: "int (*(*a)[20])()" - nested_function = "(" in declarator and not declarator.startswith(("*", "&", "(*", "(&")) + nested_function = declarator_is_function and not declarator.startswith(("*", "&", "(*", "(&")) separator = " " if declarator and not declarator.startswith("[") and (node[2] or nested_function) else "" return _render(node[3], token + separator + declarator) if kind == "array": @@ -249,17 +249,25 @@ def _render(node, declarator=""): declarator = f"({declarator})" return _render(node[2], declarator + node[1]) convention, params, returns, member_cv = node[1], node[2], node[3], node[4] - if not convention and not declarator.startswith(("*", "&")) and "::*" not in declarator: + if not convention and not declarator.startswith(("*", "&")) and not _MEMBER_POINTER_RE.match(declarator): # a convention spelled with nothing leaves a named declarator alone, while a pointer # keeps its parentheses and the space the convention would have filled: "int ( *)()" - return _render(returns, f"{declarator}({params}){member_cv}") + return _render(returns, f"{declarator}({params}){member_cv}", True) # a member-pointer declarator is "Owner::*", possibly qualified; the test is anchored so # that a nested type's own "::*" - which a rendered parameter may hold - does not count if declarator.startswith(("*", "&")) or _MEMBER_POINTER_RE.match(declarator): - declarator = f"({convention} {declarator})" + # an attribute-spelled convention carries a space of its own here, so a pointer to a + # __swiftcall function reads "int (__attribute__((__swiftcall__)) *j)(int)" + gap = " " if convention.startswith("__attribute__") else " " + declarator = f"({convention}{gap}{declarator})" else: declarator = f"{convention} {declarator}" if declarator else convention - return _render(returns, f"{declarator}({params}){member_cv}") + return _render(returns, f"{declarator}({params}){member_cv}", True) + + +def _spelled_after(convention, text): + """Join a calling convention to what follows it, skipping the ones spelled with nothing.""" + return f"{convention} {text}" if convention else text def _merge(left, right): @@ -365,6 +373,8 @@ def __init__(self, mangled): self.nested = False self.pointee_depth = 0 self.array_element_depth = 0 + # set only while the next type read stands directly as a template argument + self.at_argument = False self.member_cv = "" self.depth = 0 self.max_render = 8 * len(mangled) + 256 @@ -456,6 +466,7 @@ def templateInstantiation(self, operator=None): # a pack separator, which stands between arguments and is not one self.pos += 3 continue + self.at_argument = True args.append(self.rendered(self.type())) return f"{base}<{', '.join(args)}>" finally: @@ -624,6 +635,9 @@ def operatorName(self): return f"`{_DYNAMIC_INITIALISERS[code]} `{whole}''", "func" # what it runs for is recorded, unlike a literal operator's suffix: # "??__EFoo@@YAXU0@@Z" resolves its 0 to Foo + if self.peek().isdigit(): + # a digit there stands for an earlier name, and there is none + raise _Bail target = self.identifier() if not self.eat("@"): # a plain name may still be qualified, and the whole of that name @@ -646,7 +660,12 @@ def operatorName(self): # for the locator. The descriptor carries where the base sits. which = self.take() if which == "1": - at = ", ".join(str(int(self.templateInteger())) for _ in range(4)) + written = [self.templateInteger() for _ in range(4)] + # where the base sits may be negative, but how far the table reaches and + # what it is flagged with may not + if any(value.startswith("-") for value in written[2:]): + raise _Bail + at = ", ".join(str(int(value)) for value in written) return f"`RTTI Base Class Descriptor at ({at})'", "rtti" return _RTTI_NAMES[which], "data" if which == "4" else "rtti" if code == "R" and self.peek() == "0": @@ -666,6 +685,9 @@ def operatorName(self): if code in _UNMODELLED_DATA_SPECIAL_OPERATORS: raise _Bail if code == "9": + # it dispatches through a slot, so it is spelled with that and never with + # storage: "??_9Base@@3QAHA" is not a name + self.requires_signature = True return name, "vcall" if code == "B": return name, "guard" @@ -684,6 +706,9 @@ def operatorName(self): if code in ("0", "1"): return _Structor(code == "1"), "func" if code == "B": + # what it converts to is read from the return slot, so it is spelled with a + # signature and never with storage + self.requires_signature = True return _Conversion(), "func" name = _OPERATORS.get(code) if name is None: @@ -726,12 +751,13 @@ def type(self, quals=()): self.depth += 1 if self.depth > self.MAX_DEPTH: raise _Bail + at_argument, self.at_argument = self.at_argument, False try: - return self.typeBody(quals) + return self.typeBody(quals, at_argument) finally: self.depth -= 1 - def typeBody(self, quals): + def typeBody(self, quals, at_argument=False): """One type, qualified by `quals`. A digit is a back-reference standing for a whole argument type, so it is only a type @@ -769,7 +795,7 @@ def typeBody(self, quals): # it produced answers for names that cannot exist: "?f2@@YAXBDPAD@Z". return self.indirection((), "&") if char == "$": - return self.dollarType(quals) + return self.dollarType(quals, at_argument) if char in string.digits: # a digit is an argument back-reference, which stands for a whole argument and is # read as one in parameters(). Reaching it here means it was written where only a @@ -799,6 +825,8 @@ def dimension(self): def arrayType(self, quals): count = self.dimension() + if count == 0: + raise _Bail # an extent of nothing is spelled with nothing: "$$BY0A@H" is "int[]" dims = "".join(f"[{extent or ''}]" for extent in (self.dimension() for _ in range(count))) self.array_element_depth += 1 @@ -835,7 +863,7 @@ def templateInteger(self): self.expect("@") return f"-{value}" if negative else str(value) - def dollarType(self, quals): + def dollarType(self, quals, at_argument=False): if self.peek() in ("1", "E") and self.template_depth: # the address of a symbol, or the symbol itself: what follows is a complete # decorated name, read in this template's back-reference scope - which is why @@ -844,8 +872,9 @@ def dollarType(self, quals): self.simple = False return _base(prefix + self.nestedSymbol()) if self.peek() == "0": - if not self.template_depth: - # an integer is an argument, not a type: it appears only in a template list + if not at_argument: + # an integer is an argument, not a type: it stands where an argument stands + # and nowhere a type may nest, so it is not an array's element either raise _Bail self.take() self.simple = False @@ -853,9 +882,11 @@ def dollarType(self, quals): if not self.eat("$"): raise _Bail if self.eat("B"): - # the type as written rather than as a parameter would decay it; an integer is - # an argument rather than a type, so it is not what this introduces - if self.peek() == "$": + # the type as written rather than as a parameter would decay it, which is a thing + # to say only where a parameter stands: "?f@@YAX$$BY01H@Z" is not a name. An + # integer is an argument rather than a type, and a function type is never written + # this way either + if self.peek() in "$6" or not at_argument: raise _Bail return self.type(quals) if self.eat("Y"): @@ -903,16 +934,19 @@ def indirection(self, own_quals, token): # "__unaligned" qualifies what the pointer points at, and is spelled after the # pointee's own const and volatile: "int const __unaligned *" unaligned = ("__unaligned",) if self.eat("F") else () + modified = has_ptr64 or unaligned or "__restrict" in own_quals if self.eat("8"): - return self.memberFunctionPointer(own_quals, token, has_ptr64) + if modified: + # nothing is pointed at in front of a function type, so no modifier stands + # there: "P8B@@" and "R8B@@" are names, "PE8B@@" and "RF8B@@" are not + raise _Bail + return self.memberFunctionPointer(own_quals, token) if token == "*" and self.peek() in _MEMBER_DATA_QUALS: # only a pointer points into a class; C++ has no reference to member, so "AT..." # is not a name however much it looks like one return self.memberDataPointer(own_quals, token, unaligned) if self.eat("6"): - if has_ptr64: - # no mangler writes __ptr64 in front of a function type: "P6A" and "R6A" - # are names, "PE6A" and "RE6A" are not + if modified: raise _Bail convention = _CALLING_CONVENTIONS.get(self.take()) if convention is None: @@ -960,15 +994,13 @@ class and what the member itself is qualified by, so "PRfoo@@D" is self.simple = False return _indirection(f"{owner}::{token}", own_quals, member) - def memberFunctionPointer(self, own_quals, token, has_ptr64): + def memberFunctionPointer(self, own_quals, token): """A pointer to member function: "P8" and the class it points into. The class qualifies the declarator rather than the type - "void (__thiscall S::*)()" - and the member's own cv follows the parameter list, where a member function keeps - it. The __ptr64 modifier is no more written here than in front of a plain function. + it. """ - if has_ptr64: - raise _Bail owner = self.qualifiedName()[0] member_cv = self.memberQualifiers() convention = _CALLING_CONVENTIONS.get(self.take()) @@ -1017,13 +1049,15 @@ def memberQualifiers(self): restrict = "" reference = "" unaligned = "" - seen = set() + # they are written in this order and each at most once, so "HH" and "IE" are not + # names however much they parse like one + rank = {"E": 0, "I": 1, "F": 2, "G": 3, "H": 3} + written = -1 while self.peek() in "EIGHF": char = self.take() - # each of them is written at most once: "HH" is not a name - if char in seen or (char in "GH" and seen & {"G", "H"}): + if rank[char] <= written: raise _Bail - seen.add(char) + written = rank[char] if char == "I": restrict = " __restrict" elif char == "F": @@ -1033,7 +1067,7 @@ def memberQualifiers(self): qualifier = _CV.get(self.take()) if qualifier is None: raise _Bail - return f"{qualifier}{unaligned}{restrict}{reference}" + return f"{qualifier}{restrict}{unaligned}{reference}" def parameters(self): """A parameter list, recording each composite parameter for later back-references. @@ -1108,7 +1142,7 @@ def parse(self): return f'extern "C" {name}' if (special_form == "data") != (char in "67"): raise _Bail - if self.requires_signature and char != "Y" and char not in _FUNCTION_ACCESS: + if self.requires_signature and char not in "Y$" and char not in _FUNCTION_ACCESS: # this one runs code, so it is spelled with a signature and never with storage raise _Bail if char in "67": @@ -1233,13 +1267,13 @@ def thunkFunction(self, name, is_vcall): if not is_vcall: raise _Bail slot = self.templateInteger() - self.memberQualifiers() - convention = _CALLING_CONVENTIONS.get(self.take()) - if convention is None: - raise _Bail + # the slot is the whole of it: neither the qualifier nor the convention may be + # anything else, so "$B7DA" and "$B7FAA" are not names + self.expect("A") + self.expect("A") if not self.nested and not self.eof(): raise _Bail - return f"[thunk]: {convention} {name}{{{slot}, {{flat}}}}".replace(" ", " ") + return f"[thunk]: __cdecl {name}{{{slot}, {{flat}}}}" if code == "R": access = _VTORDISP_ACCESS.get(self.take()) if access is None: @@ -1267,7 +1301,7 @@ def thunkBody(self, name, access, kind, displacements): written = ", ".join(str(value) for value in displacements) spelled = f"{name}`{kind}{{{written}}}'" body = ( - f"{convention} {spelled}({params})" + _spelled_after(convention, f"{spelled}({params})") if returns is None else self.rendered(_function(convention, params, returns), spelled) ) @@ -1325,7 +1359,7 @@ def function(self, name, has_no_return_type, is_vcall=False): raise _Bail name = name.replace("\0conversion\0", f"operator {self.rendered(returns)}") if returns is None: - pieces.append(f"{convention} {name}({params})") + pieces.append(_spelled_after(convention, f"{name}({params})")) else: pieces.append(self.rendered(_function(convention, params, returns), name)) if access and not is_static: diff --git a/tests/testMsvcDemangler.py b/tests/testMsvcDemangler.py index 343eade0..cfcb9bd1 100644 --- a/tests/testMsvcDemangler.py +++ b/tests/testMsvcDemangler.py @@ -78,6 +78,11 @@ "??0@@QAE@XZ", # constructor with no class to name it after "?f@@YAX_Y@Z", # unknown extended basic type "?f@@YAX$$CZ@Z", # $$C without a qualifier + # a byte the calling-convention table does not hold. The reference spells an unknown one + # with nothing at all rather than refusing it, so declining here is deliberate: a + # convention silently dropped from a thunk's signature is not a name worth reporting + "??_EDerived@@$4PPPPPPPM@A@EA$PEAXI@Z", + "?f@A@simple@@$R477PPPPPPPM@7A$XXZ", "?f@@YAX$$A6ZXZ@Z", # function type argument with an unknown calling convention "??_7type_info@@6Z@", # vftable with an unknown qualifier "??_7type_info@@6B@X", # vftable with trailing bytes @@ -678,6 +683,76 @@ def test_a_reference_and_a_back_referenced_argument_still_read(self): self.assertEqual(demangle_msvc_symbol("?h@@YAXPAH0@Z"), "void __cdecl h(int *, int *)") +# Each rule below was settled by probing llvm-undname directly, because the reference corpus +# carries no name that exercises it. +PROBED_RULES = [ + # an attribute-spelled calling convention carries a space of its own in front of a + # declarator, where __cdecl and __vectorcall carry only the separator + ("?j@@3P6SHH@ZA", "int (__attribute__((__swiftcall__)) *j)(int)"), + ("?g@@YAP6SHH@ZXZ", "int (__attribute__((__swiftcall__)) * __cdecl g(void))(int)"), + ("?memptrtofun3@@3P8B@@EAWXXZEQ1@", "void (__attribute__((__swiftasynccall__)) B::*memptrtofun3)(void)"), + ("?swift_func@@YSXXZ", "void __attribute__((__swiftcall__)) swift_func(void)"), + # a convention spelled with nothing leaves no gap behind it either + ("??0foo@@QAV@XZ", "public: foo::foo(void)"), + ("??1foo@@QEAZ@XZ", "public: foo::~foo(void)"), + # a member function's modifiers are written __ptr64, __restrict, __unaligned, then a + # reference qualifier, and are spelled back in that same order + ("??0foo@@QEIFAA@XZ", "public: __cdecl foo::foo(void) __restrict __unaligned"), + ("??0foo@@QEGAA@XZ", "public: __cdecl foo::foo(void) &"), + ("??0foo@@QFGAA@XZ", "public: __cdecl foo::foo(void) __unaligned &"), + # a vcall is the slot it dispatches through, written with no qualifier and no convention + ("??_9Base@@$B7AA", "[thunk]: __cdecl Base::`vcall'{8, {flat}}"), + # an operator name ending in "()" is a name rather than a parameter list, so the sigil + # in front of it abuts as it does any other declarator + ("??RBasy@@3ABHB", "int const &Basy::operator()"), + ("??Rmemptrtofun6@@3P8B@@EAA?BHXZEQ1@", "int const (__cdecl B::*memptrtofun6::operator())(void)"), + # "$$B" says a type is written as it stands rather than as a parameter would decay it + ("??0?$C@$$BH@@QAE@XZ", "public: __thiscall C::C(void)"), + ("??0?$C@$$BY04H@@QAE@XZ", "public: __thiscall C::C(void)"), + # a "::*" deep inside a rendered parameter is not the declarator's own, so the enclosing + # signature a local scope carries does not change how that scope's name is spaced + ("?g@?1??f@@YAXP8Owner@@AEXXZ@Z@YVXXZ", "void `void __cdecl f(void (__thiscall Owner::*)(void))'::`2'::g(void)"), + ("?g@?1??f@@YAXH@Z@YVXXZ", "void `void __cdecl f(int)'::`2'::g(void)"), + ("??_R1BA@?0A@EA@Base@@8", "Base::`RTTI Base Class Descriptor at (16, -1, 0, 64)'"), + ("??__FFoo@@YAXXZ", "void __cdecl `dynamic atexit destructor for 'Foo''(void)"), +] +PROBED_DECLINED = [ + "??0foo@@QIEAA@XZ", # the modifiers are written in one order, and each at most once + "??0foo@@QEFIAA@XZ", + "??0foo@@QGFAA@XZ", + "??0foo@@QGIAA@XZ", + "??0foo@@QEGHAA@XZ", + "??_9Base@@$B7DA", # a vcall carries neither a qualifier nor a convention of its own + "??_9Base@@$B7FAA", + "??_9Base?h1@@3QAHA", # ... and it is never spelled with storage + "??BBa@@3HA", # a conversion operator reads what it converts to from its return slot + "?f@@YAX$$BY01H@Z", # "$$B" stands where an argument stands and nowhere else + "?f@@YAXQAY04$$BH@Z", + "??0?$C@$$B6AXXZ@@QAE@XZ", # ... and never over a function type + "??0?$C@$$BY04$$BH@@QAE@XZ", # ... nor nested inside another argument + "??0?$C@$$BY04$04$$CBH@@QAE@XZ", # an integer is an argument, not an array's element + "??0?$C@$$BYA@H@@QAE@XZ", # an array of no dimensions is not a type + "?f@@YAXQF6AXXZ@Z", # no modifier stands in front of a function type + "?m@@3RF8B@@EAAHXZEQ1@", + "?m@@3PE8B@@EAAHXZEQ1@", + "??_R1A@4?0A@EA@Base@@8", # how far the table reaches and its flags are not negative + "??_R1A@1A@?0A@EA@Base@@8", + "??__F1Foo@@YAXXZ", # a digit there stands for an earlier name, and there is none +] + + +class MsvcProbedRuleTestSuite(unittest.TestCase): + def test_probed_rules_spell_names_the_way_the_reference_does(self): + for mangled, expected in PROBED_RULES: + with self.subTest(mangled=mangled): + self.assertEqual(demangle_msvc_symbol(mangled), expected) + + def test_shapes_those_rules_forbid_are_refused(self): + for mangled in PROBED_DECLINED: + with self.subTest(mangled=mangled): + self.assertEqual(demangle_msvc_symbol(mangled), mangled) + + class MsvcDemanglerTestSuite(unittest.TestCase): def test_known_names_match_the_reference_spelling(self): for mangled, expected in DEMANGLED: