diff --git a/NOTICE b/NOTICE new file mode 100644 index 00000000..c7b43189 --- /dev/null +++ b/NOTICE @@ -0,0 +1,77 @@ +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. + +================================================================================ +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 +-------------------------------------------------------------------------------- +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/MsvcDemangler.py b/src/smda/common/labelprovider/MsvcDemangler.py new file mode 100644 index 00000000..66683a51 --- /dev/null +++ b/src/smda/common/labelprovider/MsvcDemangler.py @@ -0,0 +1,1386 @@ +"""Demangling for MSVC decorated symbol names.""" + +import re +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 = { + "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", + "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": "", +} +# 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), + "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-=", +} +# 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"} +# 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("A") +_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")} +# 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"^[^()]*::\*") + + +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, member_cv=""): + return ("func", convention, params, returns, member_cv) + + +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 + *inside* its type: `int (*j)[2]`, not `int (*)[2] j`. + """ + kind = node[0] + if kind == "base": + if not declarator: + return node[1] + if declarator.startswith("["): + return node[1] + declarator + # the reference spaces a declarator off a type ending in an alphanumeric character + # or a template's ">", 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]) + # a nested *function* declarator is separated from the sigil - "int * (__cdecl *)()" + # - while a parenthesised pointer declarator abuts it: "int (*(*a)[20])()" + 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": + 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 _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}", 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): + # 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}", 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): + # "__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) + + +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 + + +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. + + 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 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 _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. + + 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): + """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.at_symbol_name = True + self.requires_signature = False + 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 + + 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 rememberName(self, name): + """Record a name for later back-references, the way the mangler recorded it. + + 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, 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 + the enclosing scope instead, and the caller records it there. + """ + saved_names, saved_args = self.name_backrefs, self.arg_backrefs + self.name_backrefs, self.arg_backrefs = [], [] + self.template_depth += 1 + try: + 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(): + 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 + continue + 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 + self.at_argument = True + 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 + + 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. + """ + # 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: + index = int(self.take()) + if index >= len(self.name_backrefs): + raise _Bail + return self.name_backrefs[index], None + if char == "?": + self.take() + if self.eat("$"): + if self.peek() in string.digits: + raise _Bail + operator = None + if self.peek() == "?": + # 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) + 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, "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 + return self.operatorName() + if self.peek() == "A": + return self.anonymousNamespace(), None + # 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() + 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)". 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. + """ + # 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("?") + return f"`{self.nestedSymbol()}'::`{spelled}'" + + 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 + "?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. + """ + 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 if leading_question else self.pos + inner.pos - 1 + return rendered + + 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("_"): + 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() == "?": + # 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 + 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 + # 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": + 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() + 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": + 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": + # 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 + 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" + 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": + # 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: + raise _Bail + return name, "func" + + 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, 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("@"): + break + if self.eof(): + 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] + 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 + + 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, at_argument) + finally: + self.depth -= 1 + + 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 + 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: + 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, _, 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": + return self.arrayType(quals) + if char in _POINTER_KINDS: + return self.indirection(_merge(_POINTER_KINDS[char], quals), "*") + if char == "A": + if quals: + raise _Bail + # 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, 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 + # 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=""): + text = _render(node, declarator) + if len(text) > self.max_render: + raise _Bail + return text + + def dimension(self): + """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() + 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 + try: + element = self.type(quals) + finally: + self.array_element_depth -= 1 + 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, 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 + # "??$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 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 + 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, 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"): + # 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, "&&") + 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 + 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 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.""" + 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 () + modified = has_ptr64 or unaligned or "__restrict" in own_quals + if self.eat("8"): + 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 modified: + raise _Bail + convention = _CALLING_CONVENTIONS.get(self.take()) + if convention is None: + raise _Bail + returns = self.returnType() + # 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_quals = pointee_quals + unaligned + self.pointee_depth += 1 + try: + pointee = self.type(pointee_quals) + finally: + self.pointee_depth -= 1 + self.simple = False + return _indirection(token, own_quals, pointee) + + 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()] + unaligned + owner = self.qualifiedName()[0] + 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 + return _indirection(f"{owner}::{token}", own_quals, member) + + 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. + """ + owner = self.qualifiedName()[0] + member_cv = self.memberQualifiers() + 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): + """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 + 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, 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 = "" + unaligned = "" + # 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() + if rank[char] <= written: + raise _Bail + written = rank[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}{unaligned}{reference}" + + 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("?") + # "$$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 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() + 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() + 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 in "67"): + raise _Bail + 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": + self.take() + qualifier = _CV.get(self.take()) + if qualifier is None: + 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 + 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() + return f"{spelled}{{for `{base}'}}" if base else spelled + if char in _DATA_ACCESS: + 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("::*") + # __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() + restrict = () + seen = set() + while trailing in ("E", "I"): + if declared[0] != "ind" or trailing in seen: + raise _Bail + seen.add(trailing) + 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 + # 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 and not points_into_class: + member_quals = _CV_QUALS[trailing] + else: + 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 + 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 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()) & 0xFFFFFFFF + return value - (1 << 32) if value >= (1 << 31) else value + + 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() + # 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]: __cdecl {name}{{{slot}, {{flat}}}}" + 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: + 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 + written = ", ".join(str(value) for value in displacements) + spelled = f"{name}`{kind}{{{written}}}'" + body = ( + _spelled_after(convention, f"{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: + raise _Bail + access, is_static, is_virtual = entry + if not is_static: + self.member_cv = self.memberQualifiers() + else: + self.member_cv = "" + convention = _CALLING_CONVENTIONS.get(self.take()) + if convention is None: + raise _Bail + 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: + returns = self.returnType() + params = self.parameters() + self.expect("Z") + 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: + 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(_spelled_after(convention, f"{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 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 any(char < " " or char == "\x7f" for char 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 b97d7a18..d53205e5 100644 --- a/src/smda/common/labelprovider/PeSymbolProvider.py +++ b/src/smda/common/labelprovider/PeSymbolProvider.py @@ -7,11 +7,28 @@ 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""" @@ -67,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] = function_name + function_symbols[active_base + function.address] = _readable_name(function_name) return function_symbols def parseSymbols(self, lief_binary, base_addr=None): @@ -94,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] = 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/src/smda/common/labelprovider/RustSymbolProvider.py b/src/smda/common/labelprovider/RustSymbolProvider.py index 7d02a06b..2af37eb6 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) @@ -202,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: @@ -218,8 +214,9 @@ 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 + 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: @@ -239,23 +236,21 @@ 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) 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/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 25a22637..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. @@ -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/cxx_pe_gnu_xored b/tests/cxx_pe_gnu_xored new file mode 100644 index 00000000..0f244a20 Binary files /dev/null and b/tests/cxx_pe_gnu_xored differ diff --git a/tests/msvc_cxx_pe_xored b/tests/msvc_cxx_pe_xored new file mode 100644 index 00000000..ca7fff81 Binary files /dev/null and b/tests/msvc_cxx_pe_xored differ 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..cfcb9bd1 --- /dev/null +++ b/tests/testMsvcDemangler.py @@ -0,0 +1,856 @@ +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, ...)"), + # 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>)"), + # 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)"), + ("?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'"), + ( + "?__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 = 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. +DECLINED = [ + "?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 + # 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 + "?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 + # 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", + "?f@@YA?ZUMatrix@@XZ", # return type carrying a qualifier that is not one + "??0?$5Class@QAH@@QAE@XZ", # template name starting with a digit + "?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" + "?f@@YAXPAVa\x00b@@@Z", + "?a\x00b@@YAXPAY01D@Z", +] + + +# 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", +] + + +# 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 +] + + +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) + + +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)", + ), +] + + +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)"), +] + + +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"), +] + + +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]"), +] + + +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"), +] + + +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"), +] + + +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)"), +] + + +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"), +] + + +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))"), +] + + +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}}"), +] + + +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: + 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: + 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: + 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: + 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: + 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: + 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_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") + + 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: + 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: + 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: + 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@@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: + 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: + 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 *)") + + +# 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: + 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_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): + 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 d6d82b4d..52678bdd 100644 --- a/tests/testPeSymbolProvider.py +++ b/tests/testPeSymbolProvider.py @@ -11,6 +11,7 @@ from smda.common.BinaryInfo import BinaryInfo from smda.common.labelprovider.PeSymbolProvider import PeSymbolProvider +from smda.common.labelprovider.RustSymbolProvider import RustSymbolProvider from smda.common.labelprovider.WinApiResolver import WinApiResolver from smda.Disassembler import Disassembler from smda.SmdaConfig import SmdaConfig @@ -436,6 +437,129 @@ def test_a_known_mingw_entry_point_is_named(self): self.assertIn("mainCRTStartup", names) self.assertIn("__tmainCRTStartup", names) + def test_rust_names_reach_the_report_demangled(self): + names = {f.function_name for f in self.report.getFunctions() if f.function_name} + + # RustSymbolProvider resolved PE COFF symbols through Symbol.section, which lief + # never populates, so it contributed nothing and these arrived spelled "_RNv..." + self.assertEqual([name for name in names if name.startswith(("_R", "__R"))], []) + self.assertIn("std::rt::lang_start::<()>::{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]) + + +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.""" + + 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/testRustSymbolProvider.py b/tests/testRustSymbolProvider.py index d9dffdef..9e4a5a12 100644 --- a/tests/testRustSymbolProvider.py +++ b/tests/testRustSymbolProvider.py @@ -16,19 +16,21 @@ 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 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): @@ -122,7 +124,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.""" @@ -289,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"}) @@ -297,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 @@ -350,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 @@ -367,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)] @@ -465,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), @@ -531,38 +553,64 @@ 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.""" + + # 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>") -class TestUtilityFunctions(unittest.TestCase): - """Tests for utility functions.""" + 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) - 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") + self.assertEqual(provider.getSymbol(0x401000), demangle(self.TRAIT_IMPL)) class TestRustV0ConstBackrefs(unittest.TestCase): 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))