demangle 0.3.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (82) hide show
  1. demangle/__init__.py +103 -0
  2. demangle/_signature.py +623 -0
  3. demangle/api.py +749 -0
  4. demangle/cli.py +521 -0
  5. demangle/core/__init__.py +20 -0
  6. demangle/core/ast.py +899 -0
  7. demangle/core/builder.py +160 -0
  8. demangle/core/cache.py +70 -0
  9. demangle/core/decorations.py +73 -0
  10. demangle/core/errors.py +75 -0
  11. demangle/core/limits.py +50 -0
  12. demangle/core/plugin.py +98 -0
  13. demangle/core/reader.py +247 -0
  14. demangle/core/registry.py +221 -0
  15. demangle/core/spelling.py +470 -0
  16. demangle/core/style.py +192 -0
  17. demangle/filter.py +226 -0
  18. demangle/py.typed +0 -0
  19. demangle/schemes/__init__.py +7 -0
  20. demangle/schemes/ada/__init__.py +175 -0
  21. demangle/schemes/ada/_parser.py +313 -0
  22. demangle/schemes/ada/nodes.py +89 -0
  23. demangle/schemes/codewarrior/__init__.py +215 -0
  24. demangle/schemes/codewarrior/_parser.py +583 -0
  25. demangle/schemes/codewarrior/nodes.py +131 -0
  26. demangle/schemes/codewarrior/options.py +33 -0
  27. demangle/schemes/d/__init__.py +118 -0
  28. demangle/schemes/d/_parser.py +1616 -0
  29. demangle/schemes/d/nodes.py +93 -0
  30. demangle/schemes/delphi/__init__.py +79 -0
  31. demangle/schemes/delphi/_parser.py +897 -0
  32. demangle/schemes/delphi/nodes.py +128 -0
  33. demangle/schemes/gnuv2/__init__.py +325 -0
  34. demangle/schemes/gnuv2/_parser.py +2487 -0
  35. demangle/schemes/gnuv2/nodes.py +145 -0
  36. demangle/schemes/gnuv2/options.py +54 -0
  37. demangle/schemes/go/__init__.py +127 -0
  38. demangle/schemes/go/_parser.py +278 -0
  39. demangle/schemes/go/nodes.py +138 -0
  40. demangle/schemes/itanium/__init__.py +61 -0
  41. demangle/schemes/itanium/options.py +332 -0
  42. demangle/schemes/itanium/parser.py +5456 -0
  43. demangle/schemes/itanium/substitutions.py +362 -0
  44. demangle/schemes/itanium/tables.py +376 -0
  45. demangle/schemes/jni/__init__.py +96 -0
  46. demangle/schemes/jni/_parser.py +226 -0
  47. demangle/schemes/jni/nodes.py +91 -0
  48. demangle/schemes/msvc/__init__.py +291 -0
  49. demangle/schemes/msvc/_parser.py +2024 -0
  50. demangle/schemes/msvc/nodes.py +450 -0
  51. demangle/schemes/msvc/options.py +140 -0
  52. demangle/schemes/nim/__init__.py +81 -0
  53. demangle/schemes/nim/_parser.py +470 -0
  54. demangle/schemes/nim/nodes.py +81 -0
  55. demangle/schemes/objc/__init__.py +119 -0
  56. demangle/schemes/objc/_parser.py +823 -0
  57. demangle/schemes/objc/nodes.py +122 -0
  58. demangle/schemes/pascal/__init__.py +85 -0
  59. demangle/schemes/pascal/_parser.py +428 -0
  60. demangle/schemes/pascal/nodes.py +131 -0
  61. demangle/schemes/rust/__init__.py +244 -0
  62. demangle/schemes/rust/_dispatch.py +85 -0
  63. demangle/schemes/rust/_legacy.py +266 -0
  64. demangle/schemes/rust/_v0.py +1867 -0
  65. demangle/schemes/rust/nodes.py +194 -0
  66. demangle/schemes/rust/options.py +36 -0
  67. demangle/schemes/swift/__init__.py +247 -0
  68. demangle/schemes/swift/_demangler.py +3224 -0
  69. demangle/schemes/swift/_node.py +644 -0
  70. demangle/schemes/swift/_old_demangler.py +1525 -0
  71. demangle/schemes/swift/_printer.py +2737 -0
  72. demangle/schemes/swift/_punycode.py +116 -0
  73. demangle/schemes/swift/nodes.py +143 -0
  74. demangle/schemes/swift/options.py +155 -0
  75. demangle/schemes/swift/resolve.py +577 -0
  76. demangle/schemes/swift/symbolic.py +201 -0
  77. demangle-0.3.0.dist-info/METADATA +382 -0
  78. demangle-0.3.0.dist-info/RECORD +82 -0
  79. demangle-0.3.0.dist-info/WHEEL +4 -0
  80. demangle-0.3.0.dist-info/entry_points.txt +2 -0
  81. demangle-0.3.0.dist-info/licenses/LICENSE +21 -0
  82. demangle-0.3.0.dist-info/licenses/NOTICE +178 -0
@@ -0,0 +1,201 @@
1
+ """Symbolic references: the part of the Swift mangling that points into the binary.
2
+
3
+ A mangled name in a Swift binary's *metadata* is not always self-contained. Where a name
4
+ would have to spell a type the image already describes, the compiler writes a symbolic
5
+ reference instead: one byte saying what kind of thing is referred to and how, then a
6
+ four-byte signed offset relative to itself. The bytes are transcribed from
7
+ `Demangler.cpp::demangleSymbolicReference` in the Swift 5.10 sources.
8
+
9
+ 01 a context descriptor, directly
10
+ 02 a context descriptor, through a pointer
11
+ 09 an accessor function that yields the entity when run
12
+ 0A a unique extended existential type shape
13
+ 0B a non-unique extended existential type shape
14
+ 03-08, 0C reserved, and refused: the reference itself refuses them
15
+ FF alignment padding in front of a reference, and skipped
16
+
17
+ Two consequences fall out of the encoding and both matter to a reader:
18
+
19
+ * **A metadata blob cannot be split on NUL.** The four-byte offset is arbitrary bytes
20
+ and very often contains a zero, so finding where a mangled name ends means parsing
21
+ it. `end_of_name` does exactly that and nothing else.
22
+ * **A name holding one is not text.** It is bytes, so the entry points here take
23
+ `bytes` where the rest of the package takes `str`.
24
+
25
+ Resolving a reference needs the image the name came out of, which is why this is a
26
+ separate entry point rather than something `demangle()` could do: see `resolve.py` for a
27
+ resolver that reads one, and `demangle_symbolic` for the parse that uses it.
28
+ """
29
+
30
+ import struct
31
+
32
+ __all__ = [
33
+ "ACCESSOR_FUNCTION",
34
+ "CONTEXT",
35
+ "DIRECT",
36
+ "INDIRECT",
37
+ "KINDS",
38
+ "NON_UNIQUE_EXTENDED_EXISTENTIAL_TYPE_SHAPE",
39
+ "PADDING",
40
+ "RESERVED",
41
+ "UNIQUE_EXTENDED_EXISTENTIAL_TYPE_SHAPE",
42
+ "SymbolicReference",
43
+ "end_of_name",
44
+ "read",
45
+ "scan",
46
+ ]
47
+
48
+ #: What a reference points at.
49
+ CONTEXT = "context"
50
+ ACCESSOR_FUNCTION = "accessor function"
51
+ UNIQUE_EXTENDED_EXISTENTIAL_TYPE_SHAPE = "unique extended existential type shape"
52
+ NON_UNIQUE_EXTENDED_EXISTENTIAL_TYPE_SHAPE = "non-unique extended existential type shape"
53
+
54
+ #: Whether the offset reaches the entity or a pointer to it.
55
+ DIRECT = "direct"
56
+ INDIRECT = "indirect"
57
+
58
+ #: Introducer byte -> `(kind, directness)`. Exactly the reference's switch, including
59
+ #: which values it leaves out: 3 through 8 are reserved for protocol- and
60
+ #: associated-conformance descriptors and are *not* emitted, and 0x0C reaches the
61
+ #: switch only to fall through its default.
62
+ KINDS = {
63
+ 0x01: (CONTEXT, DIRECT),
64
+ 0x02: (CONTEXT, INDIRECT),
65
+ 0x09: (ACCESSOR_FUNCTION, DIRECT),
66
+ 0x0A: (UNIQUE_EXTENDED_EXISTENTIAL_TYPE_SHAPE, DIRECT),
67
+ 0x0B: (NON_UNIQUE_EXTENDED_EXISTENTIAL_TYPE_SHAPE, DIRECT),
68
+ }
69
+
70
+ #: Introducer bytes the grammar accepts and the reference then refuses.
71
+ RESERVED = frozenset({0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x0C})
72
+
73
+ #: Every byte that begins a symbolic reference, refused or not.
74
+ INTRODUCERS = frozenset(KINDS) | RESERVED
75
+
76
+ #: Written in front of a reference where the platform's relocations need the offset
77
+ #: aligned. It carries nothing and is skipped.
78
+ PADDING = 0xFF
79
+
80
+ #: The offset is a 32-bit signed little-endian integer, and it is relative to its own
81
+ #: first byte -- not to the introducer, and not to the end of the field.
82
+ _OFFSET = struct.Struct("<i")
83
+ OFFSET_WIDTH = _OFFSET.size
84
+
85
+
86
+ class SymbolicReference:
87
+ """One symbolic reference, as the bytes described it.
88
+
89
+ `at` is where the *offset field* begins, because that is what the offset is relative
90
+ to: the address a resolver wants is `address_of(at) + offset`. Keeping the position
91
+ rather than the resolved address is what lets this module know nothing about images.
92
+ """
93
+
94
+ __slots__ = ("at", "directness", "kind", "offset", "raw_kind")
95
+
96
+ def __init__(self, raw_kind, kind, directness, offset, at):
97
+ self.raw_kind = raw_kind
98
+ self.kind = kind
99
+ self.directness = directness
100
+ self.offset = offset
101
+ self.at = at
102
+
103
+ @property
104
+ def reserved(self):
105
+ """Whether this is one of the introducers the reference declines to read."""
106
+ return self.kind is None
107
+
108
+ def __repr__(self): # pragma: no cover - debugging aid
109
+ kind = self.kind or f"reserved 0x{self.raw_kind:02x}"
110
+ return f"SymbolicReference({kind}, {self.directness}, {self.offset:+d}, at={self.at})"
111
+
112
+ def __eq__(self, other):
113
+ if not isinstance(other, SymbolicReference):
114
+ return NotImplemented
115
+ return (self.raw_kind, self.offset, self.at) == (other.raw_kind, other.offset, other.at)
116
+
117
+ def __hash__(self):
118
+ return hash((self.raw_kind, self.offset, self.at))
119
+
120
+
121
+ def read(data, pos):
122
+ """Read the reference whose introducer is at `data[pos]`.
123
+
124
+ Returns `(reference, position after it)`. Raises `ValueError` if `pos` does not hold
125
+ an introducer, or if the four offset bytes are not all there -- which the reference
126
+ also treats as failure rather than reading a short integer.
127
+ """
128
+ if pos < 0 or pos >= len(data):
129
+ raise ValueError(f"no byte at {pos}")
130
+ raw = data[pos]
131
+ if raw not in INTRODUCERS:
132
+ raise ValueError(f"0x{raw:02x} does not introduce a symbolic reference")
133
+ start = pos + 1
134
+ if start + OFFSET_WIDTH > len(data):
135
+ raise ValueError("truncated symbolic reference")
136
+ (offset,) = _OFFSET.unpack_from(data, start)
137
+ kind, directness = KINDS.get(raw, (None, None))
138
+ return SymbolicReference(raw, kind, directness, offset, start), start + OFFSET_WIDTH
139
+
140
+
141
+ def scan(data):
142
+ """Every symbolic reference in `data`, in order.
143
+
144
+ Reading rather than searching: a byte inside an offset can look like an introducer,
145
+ and a name that has been walked properly is the only way to know it is not one.
146
+ """
147
+ found = []
148
+ pos = 0
149
+ length = len(data)
150
+ while pos < length:
151
+ byte = data[pos]
152
+ if byte in INTRODUCERS:
153
+ reference, pos = read(data, pos)
154
+ found.append(reference)
155
+ else:
156
+ pos += 1
157
+ return tuple(found)
158
+
159
+
160
+ def end_of_name(data, start=0):
161
+ """Where the NUL-terminated mangled name beginning at `start` ends.
162
+
163
+ The index returned is of the terminating NUL, or of the end of `data` if the name
164
+ runs to it. This exists because the obvious thing does not work: a reference's
165
+ four-byte offset is arbitrary bytes and very often holds a zero, so `data.index(0)`
166
+ cuts a name in half. Swift's own reflection reader walks the name for the same
167
+ reason.
168
+ """
169
+ if start < 0 or start > len(data):
170
+ raise ValueError(f"no byte at {start}")
171
+ pos = start
172
+ length = len(data)
173
+ while pos < length:
174
+ byte = data[pos]
175
+ if byte == 0:
176
+ return pos
177
+ if byte in INTRODUCERS:
178
+ if pos + 1 + OFFSET_WIDTH > length:
179
+ # Truncated. The name cannot be read, and neither can the one after it,
180
+ # so the whole remainder is this name.
181
+ return length
182
+ pos += 1 + OFFSET_WIDTH
183
+ else:
184
+ pos += 1
185
+ return length
186
+
187
+
188
+ def names(data):
189
+ """Split a metadata blob of NUL-terminated mangled names into those names.
190
+
191
+ Empty entries are dropped: a section is padded with them.
192
+ """
193
+ found = []
194
+ pos = 0
195
+ length = len(data)
196
+ while pos < length:
197
+ stop = end_of_name(data, pos)
198
+ if stop > pos:
199
+ found.append(data[pos:stop])
200
+ pos = stop + 1
201
+ return tuple(found)
@@ -0,0 +1,382 @@
1
+ Metadata-Version: 2.5
2
+ Name: demangle
3
+ Version: 0.3.0
4
+ Summary: Read mangled symbol names -- Itanium C++, MSVC, pre-Itanium C++, Ada/GNAT, Rust, Swift, Objective-C, Go, D, Nim, Free Pascal, Delphi and JNI -- in pure Python, with no dependencies.
5
+ Project-URL: Homepage, https://github.com/r0ny123/demangle
6
+ Project-URL: Issues, https://github.com/r0ny123/demangle/issues
7
+ Project-URL: Changelog, https://github.com/r0ny123/demangle/blob/main/CHANGELOG.md
8
+ Author: demangle contributors
9
+ License-Expression: MIT
10
+ License-File: LICENSE
11
+ License-File: NOTICE
12
+ Keywords: abi,binary-analysis,c++,demangle,demangler,disassembler,itanium,mangling,msvc,reverse-engineering,rust,symbol
13
+ Classifier: Development Status :: 4 - Beta
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: Operating System :: OS Independent
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3.13
18
+ Classifier: Programming Language :: Python :: 3.14
19
+ Classifier: Programming Language :: Python :: Implementation :: CPython
20
+ Classifier: Topic :: Security
21
+ Classifier: Topic :: Software Development :: Debuggers
22
+ Classifier: Topic :: Software Development :: Disassemblers
23
+ Classifier: Typing :: Typed
24
+ Requires-Python: >=3.13
25
+ Description-Content-Type: text/markdown
26
+
27
+ # demangle
28
+
29
+ [![CI](https://github.com/r0ny123/demangle/actions/workflows/ci.yml/badge.svg)](https://github.com/r0ny123/demangle/actions/workflows/ci.yml)
30
+ [![Python](https://img.shields.io/badge/python-3.13%2B-blue)](https://www.python.org/downloads/)
31
+ [![Licence](https://img.shields.io/badge/licence-MIT-green)](https://github.com/r0ny123/demangle/blob/main/LICENSE)
32
+
33
+ **Read mangled symbol names in pure Python** — Itanium C++ (GCC/Clang), MSVC, Rust,
34
+ Swift, Objective-C, Go, D, Nim, Free Pascal, Delphi, Ada/GNAT, JNI, and the pre-Itanium
35
+ C++ families (g++ 2.x, cfront/ARM, Lucid, HP aCC, CodeWarrior). No dependencies, no
36
+ native code, no compiler required.
37
+
38
+ ```python
39
+ >>> import demangle
40
+ >>> demangle.demangle("_ZNSt6vectorIiSaIiEE9push_backERKi")
41
+ 'std::vector<int, std::allocator<int>>::push_back(int const&)'
42
+ >>> demangle.demangle("?f@@YAXH@Z")
43
+ 'void __cdecl f(int)'
44
+ >>> demangle.demangle("_ZN4core3fmt9Formatter3pad17h9b2b3a0e5b4d1b31E")
45
+ 'core::fmt::Formatter::pad'
46
+ >>> demangle.demangle("$s10Foundation4DataV5countSivg")
47
+ 'Foundation.Data.count.getter : Swift.Int'
48
+ >>> demangle.demangle("example.com/m/v2%2e5.(*T).Method", language="go")
49
+ 'example.com/m/v2.5.(*T).Method'
50
+ >>> demangle.demangle("eqdestroy___systemZassertions_23")
51
+ 'system/assertions.=destroy'
52
+ >>> demangle.demangle("MYUNIT$_$TWIDGET_$__$$_AREA$$LONGINT")
53
+ 'MYUNIT.TWIDGET.AREA: LONGINT'
54
+ >>> demangle.demangle("@Unit@Class@Method$qqrv")
55
+ '__fastcall Unit::Class::Method()'
56
+ >>> demangle.demangle("AddAlignment__9ivTSolverUiP12ivInteractorP7ivTGlue")
57
+ 'ivTSolver::AddAlignment(unsigned int, ivInteractor *, ivTGlue *)'
58
+ >>> demangle.demangle("__dt__6CActorFv")
59
+ 'CActor::~CActor()'
60
+ ```
61
+
62
+ - **No toolchain.** Every other option in Python binds to a native demangler —
63
+ `cxxfilt` and `pycxxfilt` wrap LLVM or libstdc++, `undname` wraps Wine through CFFI —
64
+ which means a C compiler at install time, a platform-specific wheel, and for MSVC
65
+ nothing maintained at all. This is Python and nothing else.
66
+ - **A tree, not just a string.** `parse()` answers with nodes to walk, so the namespace,
67
+ the template arguments and the parameter types are fields rather than a regular
68
+ expression against C++ declaration syntax, which nests and so cannot be parsed that
69
+ way.
70
+ - **Measured, not asserted.** Every scheme is scored against the reference demangler for
71
+ its mangling, over about 2,400,000 symbols out of shipped libraries, and every name
72
+ that differs is accounted for by name. [CONFORMANCE.md](https://github.com/r0ny123/demangle/blob/main/CONFORMANCE.md) has the
73
+ numbers and what each is measured against.
74
+ - **Safe on untrusted input.** `demangle()` never raises, for any input; recursion depth,
75
+ output size, substitution count and input length are all bounded, and every bound is
76
+ configurable per call.
77
+
78
+ ## Install
79
+
80
+ ```console
81
+ pip install demangle
82
+ ```
83
+
84
+ Python 3.13 or newer. That is the whole dependency list.
85
+
86
+ ## Use
87
+
88
+ ### The string you probably want
89
+
90
+ ```python
91
+ demangle.demangle(name) # never raises; returns `name` unchanged if unreadable
92
+ demangle.demangle_strict(name) # raises DemanglingError instead
93
+ ```
94
+
95
+ `demangle()` is built for the case where you are labelling every symbol in a binary and
96
+ most of them are not mangled at all. A name it cannot read comes back exactly as it went
97
+ in, because a wrong expansion is worse than a mangled one — it matches neither the
98
+ symbol nor the declaration.
99
+
100
+ ### The structure, when you need it
101
+
102
+ ```python
103
+ >>> tree = demangle.parse("_ZNK3Foo3barIiEEvPKc")
104
+ >>> tree.spell()
105
+ 'void Foo::bar<int>(char const*) const'
106
+ >>> [node.text for node in tree.find("name")]
107
+ ['Foo', 'bar']
108
+ >>> function = next(tree.find("function"))
109
+ >>> len(function.parameters)
110
+ 1
111
+ ```
112
+
113
+ Every node supports `.walk()`, `.find(kind)`, `.children()` and `.spell()`.
114
+
115
+ All schemes return full trees. A Rust symbol comes back as a `symbol` holding a
116
+ `path` of `name` components, with `impl`, `template`, `type` and `literal` nodes for what
117
+ the path carries:
118
+
119
+ ```python
120
+ >>> tree = demangle.parse("_ZN4core3fmt9Formatter3pad17h9b2b3a0e5b4d1b31E")
121
+ >>> [node.text for node in tree.find("name")]
122
+ ['core', 'fmt', 'Formatter', 'pad']
123
+ ```
124
+
125
+ ### The parts, when the spelling is not what you want
126
+
127
+ `signature()` gives the pieces rather than the string — the base name without its
128
+ scope, the scope without the base name, the parameter types on their own:
129
+
130
+ ```python
131
+ >>> parts = demangle.signature("_ZNSt6vectorIiSaIiEE9push_backERKi")
132
+ >>> parts.namespace, parts.base_name
133
+ ('std::vector<int, std::allocator<int>>', 'push_back')
134
+ >>> parts.parameters
135
+ ('int const&',)
136
+ >>> demangle.signature("$s4main3FooV3baryS2i_SStF").return_type
137
+ 'Swift.Int'
138
+ ```
139
+
140
+ `namespace`, the scheme's separator and `base_name` spell `qualified_name` exactly, so
141
+ the three recombine. Splitting the *string* would not give that: `::` and `.` and `,`
142
+ occur inside template arguments and inside operator names as well as between components.
143
+
144
+ What each scheme records differs, and the fields say so rather than guessing. A C++ or
145
+ Swift name carries a signature; a Rust or Go path does not, and its `parameters` is
146
+ `None` — which is not `()`. `is_function` is `False` where the name does not say.
147
+
148
+ ### A type on its own, not a whole symbol
149
+
150
+ A `typeinfo` name, an RTTI type descriptor and a Swift metadata typeref all carry a
151
+ *type* rather than a symbol. `demangle_type()` reads one, and `parse_type()` gives its
152
+ tree:
153
+
154
+ ```python
155
+ >>> demangle.demangle_type("PKFvRiE", language="itanium")
156
+ 'void (*)(int&) const'
157
+ >>> demangle.demangle_type(".PEAX", language="msvc") # a type descriptor's own symbol
158
+ 'void *'
159
+ >>> demangle.demangle_type("SaySiG", language="swift")
160
+ '[Swift.Int]'
161
+ ```
162
+
163
+ `language` is required, and that is not an oversight: a whole symbol announces its
164
+ scheme — `_Z`, `?`, `$s` — and a type encoding announces nothing at all, so `Si` is
165
+ `std::istream` to the Itanium reader and `Swift.Int` to the Swift one. Every reference
166
+ tool puts this behind a flag for the same reason. On the command line it is
167
+ `demangle --types -l itanium`, one encoding per argument rather than a filter over mixed
168
+ text — `Pi` is an ordinary word, and `I like Pi` should stay as it is.
169
+
170
+ ### A file, not a name
171
+
172
+ `nm` writes an address and a type letter before a name; a crash log writes a frame
173
+ number. So the useful operation over a file is "substitute every symbol-shaped word and
174
+ copy the rest through", which is what the command does with no arguments — and now what
175
+ the library does too:
176
+
177
+ ```python
178
+ >>> demangle.demangle_text("0000000000001139 T _ZN3foo3barEv")
179
+ '0000000000001139 T foo::bar()'
180
+ >>> demangle.demangle_stream(sys.stdin, sys.stdout) # a line at a time, for pipes
181
+ >>> [f.mangled for f in demangle.find_symbols("a _ZN3foo3barEv b")]
182
+ ['_ZN3foo3barEv']
183
+ ```
184
+
185
+ `find_symbols` gives the span as well as the spelling, for a caller that needs to know
186
+ *where* in a line a symbol was.
187
+
188
+ ### Detection and batches
189
+
190
+ ```python
191
+ >>> demangle.detect("?f@@YAXH@Z")
192
+ 'msvc'
193
+ >>> list(demangle.demangle_all(symbol_table)) # generator, shares the cache
194
+ ```
195
+
196
+ ### Bytes, when the names came from a symbol table
197
+
198
+ An ELF or Mach-O string table holds bytes, and they are not reliably UTF-8 — a truncated
199
+ table cuts a name mid-character. Every entry point has a bytes form, so reading one does
200
+ not mean guessing an encoding first:
201
+
202
+ ```python
203
+ >>> demangle.demangleb(b"_ZN3foo3barEv")
204
+ b'foo::bar()'
205
+ ```
206
+
207
+ `demangleb_strict`, `detectb`, `parseb`, `signatureb`, `demangleb_type` and
208
+ `parseb_type` go with it. Undecodable bytes
209
+ survive the round trip: `demangleb` hands back exactly what it was given, byte for byte,
210
+ rather than raising.
211
+
212
+ ### The tree as data
213
+
214
+ `parse()` returns a walkable tree; `to_dict()` turns it into plain data, `--json` prints
215
+ it, and `node_kinds()` is the vocabulary to switch on rather than something to read out
216
+ of our source:
217
+
218
+ ```python
219
+ >>> demangle.parse("_Z1fPi").to_dict()
220
+ {'kind': 'function', 'name': {'kind': 'name', 'text': 'f'}, 'parameters': [{'kind': 'pointer', 'inner': {'kind': 'builtin', 'spelling': 'int'}}], 'returns': None, 'suffix': ''}
221
+ >>> demangle.node_kinds("d")
222
+ ('name', 'path', 'symbol')
223
+ ```
224
+
225
+ The nodes carry `__match_args__`, so structural pattern matching works:
226
+
227
+ ```python
228
+ >>> from demangle.core.ast import Builtin, Pointer
229
+ >>> match demangle.parse("_Z1fPi").parameters[0]:
230
+ ... case Pointer(Builtin(name)): print("pointer to", name)
231
+ pointer to int
232
+ ```
233
+
234
+ A node reached more than once — an Itanium substitution, a Rust node named both by
235
+ position and by role — is written once with an `id` and afterwards as `{"$ref": id}`. The
236
+ structure is a graph, and expanding it in full does not always terminate in useful time.
237
+
238
+ ### Styles
239
+
240
+ The two reference demanglers legitimately disagree on some spellings. Both are
241
+ available, and neither is wrong:
242
+
243
+ ```python
244
+ >>> demangle.demangle("_ZNSt6vectorIiSaIiEE9push_backERKi", style="llvm") # default
245
+ 'std::vector<int, std::allocator<int>>::push_back(int const&)'
246
+ >>> demangle.demangle("_ZNSt6vectorIiSaIiEE9push_backERKi", style="gnu")
247
+ 'std::vector<int, std::allocator<int> >::push_back(int const&)'
248
+ ```
249
+
250
+ ### Printing less of a name
251
+
252
+ A decorated name expands to a great deal more than the name. `style()` composes what to
253
+ leave out, at the call site rather than by registering anything globally:
254
+
255
+ ```python
256
+ >>> demangle.demangle("?g@C@@UEAAXXZ")
257
+ 'public: virtual void __cdecl C::g(void)'
258
+ >>> narrow = demangle.style("llvm", msvc={"calling_convention": False, "access_specifier": False})
259
+ >>> demangle.demangle("?g@C@@UEAAXXZ", style=narrow)
260
+ 'virtual void C::g(void)'
261
+ ```
262
+
263
+ The MSVC scheme has nine such options: the five flags `llvm-undname` has —
264
+ `calling_convention`, `access_specifier`, `member_type`, `return_type`, `variable_type`
265
+ — and four `UnDecorateSymbolName` mask bits it has no flag for, `ms_keywords`,
266
+ `leading_underscores`, `this_type` and `tag_kind`. The two kinds reach differently, which
267
+ is the references' doing rather than a convenience:
268
+ [`MsvcOptions`](https://github.com/r0ny123/demangle/blob/main/src/demangle/schemes/msvc/options.py) says which is which, field by
269
+ field, and each kind is scored against its own reference in
270
+ [CONFORMANCE.md](https://github.com/r0ny123/demangle/blob/main/CONFORMANCE.md). Every one of them has a command-line flag.
271
+
272
+ Swift has the bundle Xcode and LLDB show instead of the full spelling — `Either` for
273
+ `Monads.Either`, `(_:)` for `(Swift.Int) -> Swift.UInt`, `specialized f()` for a page of
274
+ specialisation arguments. It is `--simplified` on the command line, and exact against
275
+ Swift's own 217 vectors:
276
+
277
+ ```python
278
+ >>> from demangle.schemes.swift import SIMPLIFIED_OPTIONS
279
+ >>> demangle.demangle("_TtFSiSu", style=demangle.style("llvm", swift=SIMPLIFIED_OPTIONS))
280
+ '(_:)'
281
+ ```
282
+
283
+ ### Command line
284
+
285
+ ```console
286
+ $ nm -a libfoo.so | demangle
287
+ $ demangle _ZNSt6vectorIiSaIiEE9push_backERKi
288
+ $ demangle --tree _Z1fPKc
289
+ $ demangle --detect _RNvC6_123foo3bar
290
+ $ demangle -p _ZNSt6vectorIiSaIiEE9push_backERKi # the name, without the signature
291
+ $ demangle --base-name _ZSt4sortIPiEvT_S1_ # `sort<int*>`
292
+ $ demangle --no-return-type _ZSt4sortIPiEvT_S1_ # the declaration, minus `void `
293
+ $ demangle --ret-postfix _Z1fIiET_S0_ # `f<int>(int)int`, as `DMGL_RET_POSTFIX`
294
+ $ demangle --strip-underscore '_?f@@YAXH@Z' # ignore one leading underscore
295
+ $ demangle --keep-hash _ZN4core3fmt5write17h05af221e174051e9E # keep Rust's hash
296
+ $ demangle --types -l itanium PKFvRiE # a bare type, as `c++filt -t`
297
+ $ demangle --no-calling-convention '?f@@YAXH@Z' # `void f(int)`
298
+ $ demangle --no-tag-kind '?g3@@YAXVV@@@Z' # `void __cdecl g3(V)`
299
+ $ demangle --simplified _TtFSiSu # Swift, the way Xcode shows it
300
+ $ demangle --json _Z1fPi # the parse tree as JSON
301
+ ```
302
+
303
+ ## Correctness
304
+
305
+ Correctness here is a measurement rather than a claim. Every scheme is scored against the
306
+ reference implementation for its mangling — `llvm-cxxfilt`, GNU `c++filt`,
307
+ `llvm-undname`, `rustfilt`, `swift-demangle`, `cwdemangle`, Embarcadero's own unmangler —
308
+ and, where no reference exists, against a property the mangling itself has to satisfy:
309
+ re-mangling what was read must reproduce the symbol the compiler wrote.
310
+
311
+ The checked-in corpora are replayed by the test suite with no compiler and no reference
312
+ demangler present. Beyond them, whole symbol tables are run live against the references:
313
+ about 2,400,000 real symbols from shipped libraries, and every name that differs is
314
+ accounted for one by one.
315
+
316
+ **[CONFORMANCE.md](https://github.com/r0ny123/demangle/blob/main/CONFORMANCE.md) is the whole picture** — what each corpus is measured
317
+ against, the notes behind every number, the live runs, and the places where following a
318
+ reference would itself be the defect.
319
+
320
+ ## Safety
321
+
322
+ A mangled name is untrusted input in any tool that opens files it did not produce, so
323
+ the bounds on depth, output size, substitution count and input length are the design
324
+ rather than a hardening pass. Results are deterministic, and the property-based suite
325
+ runs every parser against arbitrary text, mangling-alphabet text, arbitrary bytes, every
326
+ truncation of a known-good name, and inputs built to blow up a naive parser.
327
+ [SECURITY.md](https://github.com/r0ny123/demangle/blob/main/SECURITY.md) has the threat model, what is in scope, and how to report
328
+ privately.
329
+
330
+ ## Performance
331
+
332
+ Pure Python, measured on the conformance corpora (`benchmarks/bench.py`):
333
+
334
+ | Workload | Throughput |
335
+ |---|---|
336
+ | Cold — every name distinct | ~24,000 names/sec |
337
+ | Warm — names repeat, as in a real symbol table | ~2,200,000 names/sec |
338
+ | Non-mangled names rejected | ~510,000 names/sec |
339
+ | Full AST construction | ~16,500 names/sec |
340
+
341
+ Treat these as ratios rather than absolutes. The gap between cold and warm is the point:
342
+ symbol tables repeat themselves relentlessly, and results are cached.
343
+
344
+ `bench.py --check` gates CI against the committed baseline. It compares figures normalised
345
+ against a calibration workload measured in the same run, so the gate reports a slower
346
+ *demangler* rather than a slower *machine*.
347
+
348
+ ## Architecture
349
+
350
+ The short version: **parsers never build their own output**. Each one is written against
351
+ a `Builder` protocol and reports the grammar productions it recognises; a fast text
352
+ builder or a tree builder decides what those become. That is what lets one parser serve
353
+ both `demangle()` and `parse()` with no second implementation to drift.
354
+
355
+ Schemes are plugins. `core` never imports one, they never import each other, and a
356
+ separate distribution can add a language through the `demangle.languages`
357
+ entry-point group without patching this package. Both rules are enforced by tests.
358
+
359
+ [Working with the tree](https://github.com/r0ny123/demangle/blob/main/docs/analysing-a-binary.md) works
360
+ one real task through `parse()` end to end — finding every function in libstdc++ that
361
+ takes a string by const reference, and measuring what the regular-expression version of
362
+ the same question gets wrong.
363
+
364
+ See [ARCHITECTURE.md](https://github.com/r0ny123/demangle/blob/main/ARCHITECTURE.md) for the full picture and
365
+ [Adding a scheme](https://github.com/r0ny123/demangle/blob/main/docs/adding-a-scheme.md) to add a language
366
+ of your own. The API reference is published at <https://r0ny123.github.io/demangle/>.
367
+
368
+ ## Contributing
369
+
370
+ New schemes, corpus contributions, and conformance bug reports are all welcome — see
371
+ [CONTRIBUTING.md](https://github.com/r0ny123/demangle/blob/main/CONTRIBUTING.md). A good bug report is a mangled name, what the
372
+ reference prints, and what this library prints.
373
+
374
+ Fixing a spelling or adding a language should not require understanding the whole
375
+ codebase. Where it does, that is a bug.
376
+
377
+ ## Licence
378
+
379
+ MIT. The Rust demangler derives from Team bi0s' `rust_demangler` (MIT) and the MSVC
380
+ demangler from [SMDA](https://github.com/danielplohmann/smda) (BSD 2-Clause); both are
381
+ substantially modified, and both upstream licences are reproduced in
382
+ [NOTICE](https://github.com/r0ny123/demangle/blob/main/NOTICE).
@@ -0,0 +1,82 @@
1
+ demangle/__init__.py,sha256=ZXJT87l6tidPkQoe6QDhxtSfBDhbziOldBdCdkQTBlg,2516
2
+ demangle/_signature.py,sha256=LKJWggi2rtMeYPua64LX1Sc_9jIA6joWIP9APrsTLzY,25959
3
+ demangle/api.py,sha256=M8o7EGokKfTsx5N8PpQ8RlwJiatMK-osyQCsBleGKlo,32512
4
+ demangle/cli.py,sha256=O6PV8fEEzyf5J2gdkEju6_LNZmpYYX6Tkam3AmWuk58,23715
5
+ demangle/filter.py,sha256=8RPEru5Cl32_dyMT55pxxn-bRsabgwmvscGMbKWKjmc,10090
6
+ demangle/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
7
+ demangle/core/__init__.py,sha256=AGi-0r12rDEdUf-MFVqOMVEGQipu_-buqoA-x4chqz4,1073
8
+ demangle/core/ast.py,sha256=CiZe43i_kqSnrUUdkUg3K5YR9Ff6mJgJkRjuAR0Uhcg,33325
9
+ demangle/core/builder.py,sha256=AZA7-RH90iMKQ6uW9HxGQdHPD6hr3SgkfQqJAKa6IJk,7352
10
+ demangle/core/cache.py,sha256=411yLDifXOOHTYoUE6uVynqEJbh_bXYNySaJsuBLfuM,2081
11
+ demangle/core/decorations.py,sha256=oWphv2iwfG8RZCt-hBGTE79lWknC44yORxT-eWglrro,3544
12
+ demangle/core/errors.py,sha256=PxKzucafAgKZf4dHdeyh_QG0v8Dvdl2LixBX22RSd6k,3004
13
+ demangle/core/limits.py,sha256=D1c1pEamvcG7H5xCFaWNbwA0WnBXc4S0cMC55odgcwQ,2174
14
+ demangle/core/plugin.py,sha256=gHC31fx7isWyIBpvMmX3h0y4tk5_vZon4QNaEruHsLI,4261
15
+ demangle/core/reader.py,sha256=RMfaH1plMC429T8wvBBXqVxKUQJRaCw7VuQEUBcKfy0,10635
16
+ demangle/core/registry.py,sha256=mf7LpG5o5jnQXXKe_K7YuPww4eBRydfHxmnbk8UB8XA,8302
17
+ demangle/core/spelling.py,sha256=65MxXgUHbZzHz3lXYXyvwtprvUtXMwpwuy5To2uAlOY,24062
18
+ demangle/core/style.py,sha256=hddj1NJvaEsLLB74hLT1Mi0hIVdZAnYawObSZ7iuXj0,7883
19
+ demangle/schemes/__init__.py,sha256=6AjYmDZPnvO4ED9lDqrxolaXzu0NlbYOlQ9pzg90CLo,319
20
+ demangle/schemes/ada/__init__.py,sha256=VRHK5kTjyMF-TwuYHeEwOmJUwmAvPp5mgI5_qJ3jCSI,8998
21
+ demangle/schemes/ada/_parser.py,sha256=VworSHrS85FoIm1hFqISO74JrS60qPbknLzdPLMZpUo,12235
22
+ demangle/schemes/ada/nodes.py,sha256=qyf6mhIbkgHCkMPvANEKlzgrMzRmupEKD4HvuqvtoIU,2805
23
+ demangle/schemes/codewarrior/__init__.py,sha256=1MQO5iYdrZv1T_y5lGC55RQ3QNiRNWrPLq8j8hq5myk,8895
24
+ demangle/schemes/codewarrior/_parser.py,sha256=4N3DmfLdT9HejBlaxs7J47LXjtnb826zUBhl07GaJ_k,20653
25
+ demangle/schemes/codewarrior/nodes.py,sha256=Fwu4vNKePMCd92OMWaYAI2VzAmz99YRuQF0ImDAmDEs,3996
26
+ demangle/schemes/codewarrior/options.py,sha256=q0o5afuIyKSVXmGwvAf4-M2bGR6JIsDCofSxw0muFJI,1235
27
+ demangle/schemes/d/__init__.py,sha256=unROT-mBlhdcktLtnPppSpOu6Ie19nU_48b3Cy7ImGk,4687
28
+ demangle/schemes/d/_parser.py,sha256=jJ6a2JegjDPeCqqfwtckoErh_dVEE3829cHRlbyrCCA,81885
29
+ demangle/schemes/d/nodes.py,sha256=y5HLVrYsAZ-JwODZgkc1Kn8r3FLyT8l5EOT54eX1pT4,2569
30
+ demangle/schemes/delphi/__init__.py,sha256=LYdQluOHjJYs9mdxujpUCC6w7e5KuXpBD6ewMoZ-O4o,3210
31
+ demangle/schemes/delphi/_parser.py,sha256=GdDbgvUPatUXRkA_YqOWDtuqPw_uWGOM8RTUoX8XmTY,34035
32
+ demangle/schemes/delphi/nodes.py,sha256=EbQwhtPOwlMYWT_SVCzWGOfl0X_M31wjb5sJqT_8sZE,3521
33
+ demangle/schemes/gnuv2/__init__.py,sha256=5GZ3Gsmt3qj5Ky3TlA5HLvMbSqb_qzIpR8-UGIdtZ0w,15853
34
+ demangle/schemes/gnuv2/_parser.py,sha256=5ZS7_oOiIrSxcx0nX3ELbdG8rBAHggFgnivmT908vCI,86138
35
+ demangle/schemes/gnuv2/nodes.py,sha256=J2jJqRP7vb6c7KjdbMpabcyGqCQhJKWzjBH8Lq04aj0,4745
36
+ demangle/schemes/gnuv2/options.py,sha256=Sft-bJAI_qfzJYxnxKqf30RVGwLFtEIR1-EuFKo17sc,2184
37
+ demangle/schemes/go/__init__.py,sha256=EKQ8Mj6RaVMju9fRq7tE_1lUnnA5F0h5pLTneeIvCD8,5144
38
+ demangle/schemes/go/_parser.py,sha256=xiFjM5_11UwVbLAhDfPTvFyqSJ0AKMR4apEOR2B3-Wc,12083
39
+ demangle/schemes/go/nodes.py,sha256=toeMLnKMFL_aD1htXdfRwN6xC2Qz-5umZ14XoLlBTj8,4083
40
+ demangle/schemes/itanium/__init__.py,sha256=3k0t5OYGOi8jYY8dgsfvZezNiP982JZJoMWH5ANXkPY,1607
41
+ demangle/schemes/itanium/options.py,sha256=G6__n1TD6jI-S1OUQfpdHDrXV1oYpQ0n0pwSLaPF4MI,17849
42
+ demangle/schemes/itanium/parser.py,sha256=IPlIEqJK5Jqw6Cm3jIXSKx_bjGvC7TuhQR8kZ1KFfyQ,290679
43
+ demangle/schemes/itanium/substitutions.py,sha256=szXj6aPrxTdqyn_aU8kC7DN1X3nAf7Ma-olAFNCsV-g,16471
44
+ demangle/schemes/itanium/tables.py,sha256=Ek4s952qceOmMPb1mDw_rf6vv0GPxSPg_rX9xZynQHs,12799
45
+ demangle/schemes/jni/__init__.py,sha256=E8Vb0Kr9FXye6QzRYK4mdCZ3bvrMxWO6CmCI2TRzT7I,3999
46
+ demangle/schemes/jni/_parser.py,sha256=cIskDqwluojm0w2bw-f5F-DIzmkQSb0kiUi7SfKE0k0,9879
47
+ demangle/schemes/jni/nodes.py,sha256=A7YCBHrNulVSZaUmDUUGtF7oMgt0OzdZMGL6gF7vYuQ,2634
48
+ demangle/schemes/msvc/__init__.py,sha256=prgQAkOYVlb1CuDq0hqGteUYRp3n3Gd6OdTAwHgOdng,14735
49
+ demangle/schemes/msvc/_parser.py,sha256=ZLG3JQNxpPom2ZYJp9wyUQxTWvMAkggX5_ml5M9Vrwo,97310
50
+ demangle/schemes/msvc/nodes.py,sha256=5RBw7McmN4FLMK3_p9xb3SbmhcQoSy3hqhCVcojhUaM,22428
51
+ demangle/schemes/msvc/options.py,sha256=SarkabnhN66O9UCMGcHEjFwCRdMxGKth6XlD4dEF1HI,6395
52
+ demangle/schemes/nim/__init__.py,sha256=ADbv1ePyr5Z6YJp9FrmBGHOypzOe8io_xuxTSUle1Hw,3138
53
+ demangle/schemes/nim/_parser.py,sha256=4QRh6uUIitr-4VbwNexNFoCZF-Ndngan0kudm0gx9HE,19032
54
+ demangle/schemes/nim/nodes.py,sha256=DmNjGboIhIjTt5Qinx6TBPqNOGFm1g_KVUUpizJBd_w,2377
55
+ demangle/schemes/objc/__init__.py,sha256=dxWSdbyZECxwn-fZeqiNUek8bsBlhznXYrHCM1j4X4Y,4970
56
+ demangle/schemes/objc/_parser.py,sha256=zx35Ev_jDism1-6Jkm5m9-B8z9SMLrwHUV2Z1VLUYHs,38712
57
+ demangle/schemes/objc/nodes.py,sha256=DnJJHjgsSV4dOfXGcumclUVzvN7qH6oYF12DUkhUHCg,3768
58
+ demangle/schemes/pascal/__init__.py,sha256=23YaRZm255WL32rHrrEVSzZ-T2c-SvNqW7DhRh91wRc,3550
59
+ demangle/schemes/pascal/_parser.py,sha256=apC5Ri8o6wS1upKWv5WvccqRQ-8OlqHQoCs4sIU3eNs,15397
60
+ demangle/schemes/pascal/nodes.py,sha256=ij2qlpiqJWyySksN6kG66JJ42tgwDA5foXr1Z8JuINQ,4547
61
+ demangle/schemes/rust/__init__.py,sha256=LwXnI5ZgOCORfI_h-HfqY8ojXyuXAiZN-_VT1FBICTY,11471
62
+ demangle/schemes/rust/_dispatch.py,sha256=z_Xbup6nZlUJnjJqoI-IlanvhNb1KkHZkeMGFEfwnTw,3261
63
+ demangle/schemes/rust/_legacy.py,sha256=XJY9t6xlbp6N4cfSKMdfpakdkWcsGS9kMklPx576Za8,11384
64
+ demangle/schemes/rust/_v0.py,sha256=PKzfXj2ZJOwXAkBZY5A1xmTJLd4htRqeGQ1SJiJhwl0,73466
65
+ demangle/schemes/rust/nodes.py,sha256=urcmSyZx0pnIz7uzzYkPRL2Fs8IUWdXowl3PiVAKeQs,6489
66
+ demangle/schemes/rust/options.py,sha256=vVZYmEfiHNgtR9Amjjoy16qVoJZOPiVS3MxjI8ARib8,1489
67
+ demangle/schemes/swift/__init__.py,sha256=WB5ju0yKWUE1bZTWr1RmBif4QvZmolGsD7enNCnnR1Y,10288
68
+ demangle/schemes/swift/_demangler.py,sha256=M422fUZO4VcV-rbCO_x_mJnuMryhmpOLV5jTwWorvzE,132940
69
+ demangle/schemes/swift/_node.py,sha256=LI8txslNyQA9nfVzzSzrd-iM99nsrP2W3IL7e7IXNLI,21212
70
+ demangle/schemes/swift/_old_demangler.py,sha256=p2rgpR_BCLKVXjvFmOHNblowlOejDtvN8wjv6CDKNF4,57355
71
+ demangle/schemes/swift/_printer.py,sha256=xm5_lbsGjI55zzrFA14EBIY9arKLV3tfm_JEpXklwtc,101774
72
+ demangle/schemes/swift/_punycode.py,sha256=9j9yaydBVnjDvzho1A5TOXMgBZqDVoa0mvBmQCKARH8,4341
73
+ demangle/schemes/swift/nodes.py,sha256=Vb_OtVjpGwl7yWV-NBWbLtq7mJ-Oo91pTRaa0DPowyY,5068
74
+ demangle/schemes/swift/options.py,sha256=UTXZUIJTLrfIhHsceVtNw5BTsK-9ZVqDLyxtlk9Mu9Q,6340
75
+ demangle/schemes/swift/resolve.py,sha256=OPC1Vr-aYwVrq_0M4oEQ_Lcrr4pqHfO7lyExgBF02Rs,26227
76
+ demangle/schemes/swift/symbolic.py,sha256=Cz4hapUn1maZ92AjcrqE5XeAcsS7WCB30C75S1Fcty8,7493
77
+ demangle-0.3.0.dist-info/METADATA,sha256=YpfBP3iUSHFjhabFq1EOcdWMDojMAojsrFY_4EkOHD0,17189
78
+ demangle-0.3.0.dist-info/WHEEL,sha256=W3fkpkm7-wf9vBI5Z-7s0eWkeM-spu78I8Neb98DeEg,87
79
+ demangle-0.3.0.dist-info/entry_points.txt,sha256=SEc09SLy0tnFqkFudxqACUKuPu2bCnfw2dpk_0Mspi0,47
80
+ demangle-0.3.0.dist-info/licenses/LICENSE,sha256=BiVCDUXWJRpSX7YsgqxkhsUT_tcfVTFmNHHUfiujrh8,1078
81
+ demangle-0.3.0.dist-info/licenses/NOTICE,sha256=cYig6w7q6AXFFJU8s2ClgR3SqYUpN_mmuz3DrAwXfnk,10123
82
+ demangle-0.3.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.32.4
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ demangle = demangle.cli:main