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.
- demangle/__init__.py +103 -0
- demangle/_signature.py +623 -0
- demangle/api.py +749 -0
- demangle/cli.py +521 -0
- demangle/core/__init__.py +20 -0
- demangle/core/ast.py +899 -0
- demangle/core/builder.py +160 -0
- demangle/core/cache.py +70 -0
- demangle/core/decorations.py +73 -0
- demangle/core/errors.py +75 -0
- demangle/core/limits.py +50 -0
- demangle/core/plugin.py +98 -0
- demangle/core/reader.py +247 -0
- demangle/core/registry.py +221 -0
- demangle/core/spelling.py +470 -0
- demangle/core/style.py +192 -0
- demangle/filter.py +226 -0
- demangle/py.typed +0 -0
- demangle/schemes/__init__.py +7 -0
- demangle/schemes/ada/__init__.py +175 -0
- demangle/schemes/ada/_parser.py +313 -0
- demangle/schemes/ada/nodes.py +89 -0
- demangle/schemes/codewarrior/__init__.py +215 -0
- demangle/schemes/codewarrior/_parser.py +583 -0
- demangle/schemes/codewarrior/nodes.py +131 -0
- demangle/schemes/codewarrior/options.py +33 -0
- demangle/schemes/d/__init__.py +118 -0
- demangle/schemes/d/_parser.py +1616 -0
- demangle/schemes/d/nodes.py +93 -0
- demangle/schemes/delphi/__init__.py +79 -0
- demangle/schemes/delphi/_parser.py +897 -0
- demangle/schemes/delphi/nodes.py +128 -0
- demangle/schemes/gnuv2/__init__.py +325 -0
- demangle/schemes/gnuv2/_parser.py +2487 -0
- demangle/schemes/gnuv2/nodes.py +145 -0
- demangle/schemes/gnuv2/options.py +54 -0
- demangle/schemes/go/__init__.py +127 -0
- demangle/schemes/go/_parser.py +278 -0
- demangle/schemes/go/nodes.py +138 -0
- demangle/schemes/itanium/__init__.py +61 -0
- demangle/schemes/itanium/options.py +332 -0
- demangle/schemes/itanium/parser.py +5456 -0
- demangle/schemes/itanium/substitutions.py +362 -0
- demangle/schemes/itanium/tables.py +376 -0
- demangle/schemes/jni/__init__.py +96 -0
- demangle/schemes/jni/_parser.py +226 -0
- demangle/schemes/jni/nodes.py +91 -0
- demangle/schemes/msvc/__init__.py +291 -0
- demangle/schemes/msvc/_parser.py +2024 -0
- demangle/schemes/msvc/nodes.py +450 -0
- demangle/schemes/msvc/options.py +140 -0
- demangle/schemes/nim/__init__.py +81 -0
- demangle/schemes/nim/_parser.py +470 -0
- demangle/schemes/nim/nodes.py +81 -0
- demangle/schemes/objc/__init__.py +119 -0
- demangle/schemes/objc/_parser.py +823 -0
- demangle/schemes/objc/nodes.py +122 -0
- demangle/schemes/pascal/__init__.py +85 -0
- demangle/schemes/pascal/_parser.py +428 -0
- demangle/schemes/pascal/nodes.py +131 -0
- demangle/schemes/rust/__init__.py +244 -0
- demangle/schemes/rust/_dispatch.py +85 -0
- demangle/schemes/rust/_legacy.py +266 -0
- demangle/schemes/rust/_v0.py +1867 -0
- demangle/schemes/rust/nodes.py +194 -0
- demangle/schemes/rust/options.py +36 -0
- demangle/schemes/swift/__init__.py +247 -0
- demangle/schemes/swift/_demangler.py +3224 -0
- demangle/schemes/swift/_node.py +644 -0
- demangle/schemes/swift/_old_demangler.py +1525 -0
- demangle/schemes/swift/_printer.py +2737 -0
- demangle/schemes/swift/_punycode.py +116 -0
- demangle/schemes/swift/nodes.py +143 -0
- demangle/schemes/swift/options.py +155 -0
- demangle/schemes/swift/resolve.py +577 -0
- demangle/schemes/swift/symbolic.py +201 -0
- demangle-0.3.0.dist-info/METADATA +382 -0
- demangle-0.3.0.dist-info/RECORD +82 -0
- demangle-0.3.0.dist-info/WHEEL +4 -0
- demangle-0.3.0.dist-info/entry_points.txt +2 -0
- demangle-0.3.0.dist-info/licenses/LICENSE +21 -0
- demangle-0.3.0.dist-info/licenses/NOTICE +178 -0
demangle/cli.py
ADDED
|
@@ -0,0 +1,521 @@
|
|
|
1
|
+
"""The `demangle` command.
|
|
2
|
+
|
|
3
|
+
Reads names from arguments or standard input and writes their expansions, so it drops
|
|
4
|
+
into a pipeline the way `c++filt` does:
|
|
5
|
+
|
|
6
|
+
nm -a libfoo.so | demangle
|
|
7
|
+
demangle _ZNSt6vectorIiSaIiEE9push_backERKi
|
|
8
|
+
|
|
9
|
+
Names that cannot be read pass through unchanged, which is what makes it safe to run
|
|
10
|
+
over a whole symbol table.
|
|
11
|
+
|
|
12
|
+
On reading a stream
|
|
13
|
+
-------------------
|
|
14
|
+
With no arguments this is a *filter*, not a line reader: every symbol-shaped word in the
|
|
15
|
+
input is a candidate and everything around it is copied through untouched. That is what
|
|
16
|
+
`c++filt`, `demumble` and `rustfilt` all do, and it is the only behaviour under which
|
|
17
|
+
the first example above works. `nm` writes an address and a type letter before the name,
|
|
18
|
+
so treating the whole line as one symbol demangled nothing at all:
|
|
19
|
+
|
|
20
|
+
$ printf '0000000000001139 T _ZN3foo3barEv\\n' | demangle
|
|
21
|
+
0000000000001139 T _ZN3foo3barEv # every line, unchanged
|
|
22
|
+
|
|
23
|
+
Encoding
|
|
24
|
+
--------
|
|
25
|
+
A symbol table holds bytes, and they are not reliably UTF-8. Both streams are read and
|
|
26
|
+
written with `surrogateescape`, so a name this package cannot read comes back byte for
|
|
27
|
+
byte -- and so a non-UTF-8 symbol does not end the run with a `UnicodeDecodeError`
|
|
28
|
+
traceback, which is what happened wherever the interpreter's error handler was `strict`.
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
import argparse
|
|
32
|
+
import contextlib
|
|
33
|
+
import json
|
|
34
|
+
import os
|
|
35
|
+
import sys
|
|
36
|
+
|
|
37
|
+
from . import __version__
|
|
38
|
+
from ._signature import signature
|
|
39
|
+
from .api import demangle, demangle_strict, demangle_type, detect, languages, parse, parse_type, style, styles
|
|
40
|
+
from .core.ast import Function
|
|
41
|
+
from .core.errors import DemanglingError
|
|
42
|
+
from .core.limits import DEFAULT_LIMITS, RELAXED_LIMITS, Limits
|
|
43
|
+
from .filter import TOKEN, TOKEN_MUST_HOLD
|
|
44
|
+
|
|
45
|
+
#: The token scan, shared with `demangle.filter` so there is one of it rather than
|
|
46
|
+
#: two. That module is the library form of this command's default behaviour; see its
|
|
47
|
+
#: docstring for what the two patterns are for and why the second one is needed.
|
|
48
|
+
_TOKEN = TOKEN
|
|
49
|
+
_TOKEN_MUST_HOLD = TOKEN_MUST_HOLD
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def build_parser():
|
|
53
|
+
parser = argparse.ArgumentParser(
|
|
54
|
+
prog="demangle",
|
|
55
|
+
description="Demangle C++, Rust, Swift, MSVC and other symbol names.",
|
|
56
|
+
epilog=(
|
|
57
|
+
"With no NAME arguments, names are read from standard input: every "
|
|
58
|
+
"symbol-shaped word is demangled and the text around it is copied through."
|
|
59
|
+
),
|
|
60
|
+
)
|
|
61
|
+
parser.add_argument("names", nargs="*", metavar="NAME", help="symbol names to demangle")
|
|
62
|
+
parser.add_argument("-l", "--language", help="force a scheme instead of detecting (aliases accepted)")
|
|
63
|
+
parser.add_argument("-s", "--style", default="llvm", help="output style (default: llvm)")
|
|
64
|
+
parser.add_argument("-d", "--detect", action="store_true", help="print the detected scheme, not the expansion")
|
|
65
|
+
parser.add_argument("-t", "--tree", action="store_true", help="print the parse tree")
|
|
66
|
+
parser.add_argument("--json", action="store_true", help="print the parse tree as JSON")
|
|
67
|
+
parser.add_argument(
|
|
68
|
+
"--types",
|
|
69
|
+
action="store_true",
|
|
70
|
+
help="read each NAME as a bare type encoding, not a symbol (needs --language)",
|
|
71
|
+
)
|
|
72
|
+
parser.add_argument(
|
|
73
|
+
"-_",
|
|
74
|
+
"--strip-underscore",
|
|
75
|
+
action="store_true",
|
|
76
|
+
help="ignore one leading underscore, as `c++filt --strip-underscore` does",
|
|
77
|
+
)
|
|
78
|
+
parser.add_argument("--strict", action="store_true", help="report failures instead of echoing the input")
|
|
79
|
+
parser.add_argument(
|
|
80
|
+
"-b", "--both", action="store_true", help="print the mangled name and its expansion, as `mangled ==> demangled`"
|
|
81
|
+
)
|
|
82
|
+
parser.add_argument(
|
|
83
|
+
"-m", "--only-demangled", action="store_true", help="print only what demangled, skipping the rest"
|
|
84
|
+
)
|
|
85
|
+
# One name, one part: asking for two of these would print one of them and drop the
|
|
86
|
+
# other, and a flag that is silently ignored is worse than an error.
|
|
87
|
+
parts = parser.add_mutually_exclusive_group()
|
|
88
|
+
parts.add_argument(
|
|
89
|
+
"-p",
|
|
90
|
+
"--no-params",
|
|
91
|
+
action="store_true",
|
|
92
|
+
help="print the name without its parameter list or return type, as `c++filt -p` does",
|
|
93
|
+
)
|
|
94
|
+
parts.add_argument(
|
|
95
|
+
"--base-name", action="store_true", help="print only the last component of the name, without its scope"
|
|
96
|
+
)
|
|
97
|
+
parts.add_argument(
|
|
98
|
+
"--no-return-type", action="store_true", help="print the whole declaration except the return type"
|
|
99
|
+
)
|
|
100
|
+
parts.add_argument(
|
|
101
|
+
"--ret-postfix",
|
|
102
|
+
action="store_true",
|
|
103
|
+
help="print the return type after the parameter list, as libiberty's `DMGL_RET_POSTFIX` does",
|
|
104
|
+
)
|
|
105
|
+
less = parser.add_argument_group(
|
|
106
|
+
"printing less of a name",
|
|
107
|
+
"MSVC decorated names expand to a great deal more than the name. "
|
|
108
|
+
"`--no-calling-convention`, `--no-access-specifier`, `--no-member-type` and "
|
|
109
|
+
"`--no-variable-type` are `llvm-undname`'s flags and mean the same, and apply to "
|
|
110
|
+
"the declaration. `--no-ms-keywords`, `--no-leading-underscores`, "
|
|
111
|
+
"`--no-this-type` and `--no-tag-kind` are `UnDecorateSymbolName` mask bits it has "
|
|
112
|
+
"no flag for, and reach every occurrence of what they name -- inside a template "
|
|
113
|
+
"argument, and inside the symbol a local name is scoped by. `--no-return-type` is "
|
|
114
|
+
"shared with the selection above and applies to every scheme.",
|
|
115
|
+
)
|
|
116
|
+
less.add_argument("--no-calling-convention", action="store_true", help="omit `__cdecl` and its siblings")
|
|
117
|
+
less.add_argument("--no-access-specifier", action="store_true", help="omit `public: `, `private: `")
|
|
118
|
+
less.add_argument("--no-member-type", action="store_true", help="omit `static ` and `virtual `")
|
|
119
|
+
less.add_argument("--no-variable-type", action="store_true", help="print a data symbol as its name alone")
|
|
120
|
+
less.add_argument("--no-ms-keywords", action="store_true", help="omit every Microsoft keyword, wherever it stands")
|
|
121
|
+
less.add_argument(
|
|
122
|
+
"--no-leading-underscores", action="store_true", help="spell those keywords as `cdecl`, `restrict`"
|
|
123
|
+
)
|
|
124
|
+
less.add_argument("--no-this-type", action="store_true", help="omit what a member function writes after `()`")
|
|
125
|
+
less.add_argument("--no-tag-kind", action="store_true", help="omit `class`, `struct`, `union`, `enum`")
|
|
126
|
+
less.add_argument(
|
|
127
|
+
"--simplified",
|
|
128
|
+
action="store_true",
|
|
129
|
+
help="Swift names the way Xcode shows them, as `swift-demangle --simplified`",
|
|
130
|
+
)
|
|
131
|
+
parser.add_argument(
|
|
132
|
+
"--keep-hash",
|
|
133
|
+
action="store_true",
|
|
134
|
+
help="spell the hash Rust writes to keep a symbol unique, as rustc-demangle's `{}` does",
|
|
135
|
+
)
|
|
136
|
+
parser.add_argument("--relaxed", action="store_true", help="raise the resource bounds, for input you trust")
|
|
137
|
+
parser.add_argument("--max-input", type=int, metavar="N", help="characters of input to consider")
|
|
138
|
+
parser.add_argument("--max-output", type=int, metavar="N", help="characters of output to allow")
|
|
139
|
+
parser.add_argument("--max-depth", type=int, metavar="N", help="nesting depth to allow")
|
|
140
|
+
parser.add_argument("--list-languages", action="store_true", help="list supported schemes and exit")
|
|
141
|
+
parser.add_argument("--list-styles", action="store_true", help="list output styles and exit")
|
|
142
|
+
parser.add_argument("--version", action="version", version=f"demangle {__version__}")
|
|
143
|
+
return parser
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
#: `--flag` to the MSVC option it turns off. `--no-return-type` and `--ret-postfix` are
|
|
147
|
+
#: shared with the selection flags -- both need the declaration without its return type,
|
|
148
|
+
#: and differ only in whether the type is then written after it. For MSVC that has to be
|
|
149
|
+
#: an option, because a return type there wraps *around* the declarator:
|
|
150
|
+
#: `int (__cdecl * __cdecl fn(void))(int)` has no prefix to strip. Itanium has the same
|
|
151
|
+
#: shape in `int (*g<int>(int))(int)` and answers it from the tree instead; see
|
|
152
|
+
#: `_without_return_type`.
|
|
153
|
+
_MSVC_SUPPRESSIONS = {
|
|
154
|
+
"no_calling_convention": "calling_convention",
|
|
155
|
+
"no_access_specifier": "access_specifier",
|
|
156
|
+
"no_member_type": "member_type",
|
|
157
|
+
"no_variable_type": "variable_type",
|
|
158
|
+
"no_return_type": "return_type",
|
|
159
|
+
"ret_postfix": "return_type",
|
|
160
|
+
"no_ms_keywords": "ms_keywords",
|
|
161
|
+
"no_leading_underscores": "leading_underscores",
|
|
162
|
+
"no_this_type": "this_type",
|
|
163
|
+
"no_tag_kind": "tag_kind",
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def _style_from(arguments):
|
|
168
|
+
"""The style this run spells with: the named one, plus whatever it is to leave out."""
|
|
169
|
+
changes = {}
|
|
170
|
+
off = {option: False for flag, option in _MSVC_SUPPRESSIONS.items() if getattr(arguments, flag)}
|
|
171
|
+
if off:
|
|
172
|
+
changes["msvc"] = off
|
|
173
|
+
if arguments.simplified:
|
|
174
|
+
from .schemes.swift.options import SIMPLIFIED_OPTIONS
|
|
175
|
+
|
|
176
|
+
changes["swift"] = SIMPLIFIED_OPTIONS
|
|
177
|
+
if arguments.keep_hash:
|
|
178
|
+
changes["rust"] = {"keep_hash": True}
|
|
179
|
+
if not changes:
|
|
180
|
+
return arguments.style
|
|
181
|
+
return style(arguments.style, **changes)
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
def _limits_from(arguments):
|
|
185
|
+
"""The bounds this run should use.
|
|
186
|
+
|
|
187
|
+
`--relaxed` moves the floor; the three explicit flags override whatever is under
|
|
188
|
+
them, so `--relaxed --max-output 4096` means what it reads as.
|
|
189
|
+
"""
|
|
190
|
+
base = RELAXED_LIMITS if arguments.relaxed else DEFAULT_LIMITS
|
|
191
|
+
if arguments.max_input is arguments.max_output is arguments.max_depth is None:
|
|
192
|
+
return base
|
|
193
|
+
return Limits(
|
|
194
|
+
max_depth=base.max_depth if arguments.max_depth is None else arguments.max_depth,
|
|
195
|
+
max_output=base.max_output if arguments.max_output is None else arguments.max_output,
|
|
196
|
+
max_substitutions=base.max_substitutions,
|
|
197
|
+
max_input=base.max_input if arguments.max_input is None else arguments.max_input,
|
|
198
|
+
)
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
def _dump(node, indent=0):
|
|
202
|
+
"""Render a parse tree, one node per line."""
|
|
203
|
+
pad = " " * indent
|
|
204
|
+
detail = ""
|
|
205
|
+
# Every slot in the hierarchy, not just the most-derived class's: a node whose text
|
|
206
|
+
# lives on a shared base declares none of its own and would print no detail at all.
|
|
207
|
+
for slot in node._fields() if hasattr(node, "_fields") else getattr(node, "__slots__", ()):
|
|
208
|
+
value = getattr(node, slot, None)
|
|
209
|
+
if isinstance(value, str) and value:
|
|
210
|
+
detail = f" {value!r}"
|
|
211
|
+
break
|
|
212
|
+
yield f"{pad}{node.kind}{detail}"
|
|
213
|
+
for child in node.children():
|
|
214
|
+
yield from _dump(child, indent + 1)
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
def _reconfigure(stream, **kwargs):
|
|
218
|
+
"""Set an error handler on a stream, where the stream has one to set.
|
|
219
|
+
|
|
220
|
+
Under `pytest`'s capture, and behind some redirections, the standard streams are
|
|
221
|
+
substitutes with no `reconfigure`. Nothing here is load-bearing for correctness --
|
|
222
|
+
it decides how undecodable bytes are handled -- so a stream that cannot be
|
|
223
|
+
reconfigured is left alone.
|
|
224
|
+
"""
|
|
225
|
+
reconfigure = getattr(stream, "reconfigure", None)
|
|
226
|
+
if reconfigure is not None:
|
|
227
|
+
with contextlib.suppress(ValueError, OSError):
|
|
228
|
+
reconfigure(**kwargs)
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
def main(argv=None):
|
|
232
|
+
parser = build_parser()
|
|
233
|
+
arguments = parser.parse_args(argv)
|
|
234
|
+
|
|
235
|
+
if arguments.list_languages:
|
|
236
|
+
from .core.registry import available
|
|
237
|
+
|
|
238
|
+
for plugin in available():
|
|
239
|
+
aliases = f" (aliases: {', '.join(plugin.aliases)})" if plugin.aliases else ""
|
|
240
|
+
print(f"{plugin.name:10} {plugin.description}{aliases}")
|
|
241
|
+
return 0
|
|
242
|
+
|
|
243
|
+
if arguments.list_styles:
|
|
244
|
+
for name in styles():
|
|
245
|
+
print(name)
|
|
246
|
+
return 0
|
|
247
|
+
|
|
248
|
+
# Validated through the registry rather than against `languages()`, so the aliases
|
|
249
|
+
# that `--list-languages` advertises are actually accepted.
|
|
250
|
+
if arguments.language:
|
|
251
|
+
from .core.registry import aliases, get
|
|
252
|
+
|
|
253
|
+
try:
|
|
254
|
+
get(arguments.language)
|
|
255
|
+
except KeyError:
|
|
256
|
+
known = ", ".join(sorted(set(languages()) | set(aliases())))
|
|
257
|
+
parser.error(f"unknown language {arguments.language!r}; choose from {known}")
|
|
258
|
+
if arguments.json and arguments.tree:
|
|
259
|
+
parser.error("--tree and --json are two spellings of the same answer; choose one")
|
|
260
|
+
if arguments.types:
|
|
261
|
+
if not arguments.language:
|
|
262
|
+
parser.error("--types needs --language: a type encoding carries no marker to detect on")
|
|
263
|
+
# Checked here rather than per name: a scheme with no type grammar fails on every
|
|
264
|
+
# line, and one message about the run beats one message per symbol.
|
|
265
|
+
from .core.registry import get as _get_plugin
|
|
266
|
+
|
|
267
|
+
if _get_plugin(arguments.language).parse_type is None:
|
|
268
|
+
readable = ", ".join(sorted(name for name in languages() if _get_plugin(name).parse_type is not None))
|
|
269
|
+
parser.error(f"{arguments.language} has no type grammar of its own; {readable} do")
|
|
270
|
+
if arguments.detect:
|
|
271
|
+
parser.error("--types and --detect ask different questions; --types already names the scheme")
|
|
272
|
+
if arguments.no_params or arguments.base_name or arguments.no_return_type or arguments.ret_postfix:
|
|
273
|
+
parser.error("--types reads a type, which has no name, parameters or return type to select")
|
|
274
|
+
if arguments.style not in styles():
|
|
275
|
+
parser.error(f"unknown style {arguments.style!r}; choose from {', '.join(styles())}")
|
|
276
|
+
for flag in ("max_input", "max_output", "max_depth"):
|
|
277
|
+
value = getattr(arguments, flag)
|
|
278
|
+
if value is not None and value < 1:
|
|
279
|
+
parser.error(f"--{flag.replace('_', '-')} must be positive")
|
|
280
|
+
|
|
281
|
+
arguments.style = _style_from(arguments)
|
|
282
|
+
|
|
283
|
+
_reconfigure(sys.stdin, errors="surrogateescape")
|
|
284
|
+
_reconfigure(sys.stdout, errors="surrogateescape")
|
|
285
|
+
|
|
286
|
+
try:
|
|
287
|
+
if arguments.types:
|
|
288
|
+
return _run_types(arguments.names or sys.stdin, arguments)
|
|
289
|
+
if arguments.names:
|
|
290
|
+
return _run_names(arguments.names, arguments)
|
|
291
|
+
return _run_stream(sys.stdin, arguments)
|
|
292
|
+
except BrokenPipeError:
|
|
293
|
+
# `demangle | head` closes the pipe on us. This has to be caught outside the
|
|
294
|
+
# loop: caught per name it prints one error for every remaining symbol, thousands
|
|
295
|
+
# of them. Point stdout at the null device so the interpreter's shutdown flush
|
|
296
|
+
# does not raise it again, and exit cleanly.
|
|
297
|
+
_silence_stdout()
|
|
298
|
+
return 0
|
|
299
|
+
|
|
300
|
+
|
|
301
|
+
def _silence_stdout():
|
|
302
|
+
"""Point stdout at the null device, so the shutdown flush has somewhere to go."""
|
|
303
|
+
try:
|
|
304
|
+
null = os.open(os.devnull, os.O_WRONLY)
|
|
305
|
+
except OSError: # pragma: no cover - no /dev/null
|
|
306
|
+
return
|
|
307
|
+
try:
|
|
308
|
+
os.dup2(null, sys.stdout.fileno())
|
|
309
|
+
except (OSError, ValueError, AttributeError): # pragma: no cover - not a real stream
|
|
310
|
+
pass
|
|
311
|
+
finally:
|
|
312
|
+
# `dup2` duplicated the descriptor, so this one has done its job either way.
|
|
313
|
+
# Left open it leaked -- which mattered not at all for one exit, and would have
|
|
314
|
+
# mattered for a library caller invoking `main()` in a loop.
|
|
315
|
+
os.close(null)
|
|
316
|
+
|
|
317
|
+
|
|
318
|
+
def _expand(name, arguments, limits):
|
|
319
|
+
"""What this run has to say about one name, after any leading underscore is dealt with.
|
|
320
|
+
|
|
321
|
+
`--strip-underscore` is for the targets whose assembler prepends one, where the
|
|
322
|
+
symbol in the table is `__Z1fv` and the name the compiler mangled is `_Z1fv`. Both
|
|
323
|
+
references drop exactly one and, where what is left does not read, print the name
|
|
324
|
+
they were *given* rather than the stripped form -- so a table full of `_foo` comes
|
|
325
|
+
back untouched instead of a character short.
|
|
326
|
+
"""
|
|
327
|
+
if arguments.strip_underscore and name.startswith("_"):
|
|
328
|
+
answer = _expand_read(name[1:], arguments, limits)
|
|
329
|
+
return name if answer == name[1:] else answer
|
|
330
|
+
return _expand_read(name, arguments, limits)
|
|
331
|
+
|
|
332
|
+
|
|
333
|
+
def _expand_read(name, arguments, limits):
|
|
334
|
+
"""What this run has to say about one name."""
|
|
335
|
+
if arguments.detect:
|
|
336
|
+
return arguments.language or detect(name) or "-"
|
|
337
|
+
if arguments.types:
|
|
338
|
+
if arguments.json or arguments.tree:
|
|
339
|
+
node = parse_type(name, language=arguments.language, style=arguments.style, limits=limits)
|
|
340
|
+
return json.dumps(node.to_dict()) if arguments.json else "\n".join(_dump(node))
|
|
341
|
+
return demangle_type(name, language=arguments.language, style=arguments.style, limits=limits)
|
|
342
|
+
if arguments.json:
|
|
343
|
+
return json.dumps(parse(name, language=arguments.language, style=arguments.style, limits=limits).to_dict())
|
|
344
|
+
if arguments.tree:
|
|
345
|
+
return "\n".join(_dump(parse(name, language=arguments.language, style=arguments.style, limits=limits)))
|
|
346
|
+
if arguments.no_params or arguments.base_name or arguments.no_return_type or arguments.ret_postfix:
|
|
347
|
+
return _part_of(name, arguments, limits)
|
|
348
|
+
if arguments.strict:
|
|
349
|
+
return demangle_strict(name, language=arguments.language, style=arguments.style, limits=limits)
|
|
350
|
+
return demangle(name, language=arguments.language, style=arguments.style, limits=limits)
|
|
351
|
+
|
|
352
|
+
|
|
353
|
+
def _part_of(name, arguments, limits):
|
|
354
|
+
"""One piece of a name rather than the whole spelling.
|
|
355
|
+
|
|
356
|
+
A name that cannot be read has no pieces, so without `--strict` it comes back
|
|
357
|
+
unchanged -- the same bargain `demangle` makes, because a filter over a symbol table
|
|
358
|
+
meets far more names that are not mangled than names that are.
|
|
359
|
+
"""
|
|
360
|
+
try:
|
|
361
|
+
parts = signature(name, language=arguments.language, style=arguments.style, limits=limits)
|
|
362
|
+
except Exception:
|
|
363
|
+
if arguments.strict:
|
|
364
|
+
raise
|
|
365
|
+
return name
|
|
366
|
+
if arguments.base_name:
|
|
367
|
+
return parts.base_name
|
|
368
|
+
if arguments.no_params:
|
|
369
|
+
# What `c++filt -p` prints: the name with its scope, and neither the signature
|
|
370
|
+
# around it nor the qualifiers after it. A `vtable for` still says so.
|
|
371
|
+
lead = f"{parts.special} " if parts.special else ""
|
|
372
|
+
return f"{lead}{parts.qualified_name}{parts.decoration}"
|
|
373
|
+
without = _without_return_type(name, arguments, limits, parts)
|
|
374
|
+
if arguments.ret_postfix and parts.return_type:
|
|
375
|
+
# libiberty writes the return type straight after the parameter list with no
|
|
376
|
+
# space between them -- `f<int>(int)int` -- because the space it puts between a
|
|
377
|
+
# return type and a signature belongs to the prefix form. Checked against
|
|
378
|
+
# `cplus_demangle_v3(name, DMGL_PARAMS | DMGL_RET_POSTFIX)` rather than assumed.
|
|
379
|
+
return f"{without}{parts.return_type}"
|
|
380
|
+
return without
|
|
381
|
+
|
|
382
|
+
|
|
383
|
+
def _without_return_type(name, arguments, limits, parts):
|
|
384
|
+
"""The whole declaration with the return type taken out of it.
|
|
385
|
+
|
|
386
|
+
Cutting it off the front of the spelling is right only where it *is* a prefix. A
|
|
387
|
+
return type that wraps the declarator has none to cut -- `int (*g<int>(int))(int)`,
|
|
388
|
+
a function returning a pointer to a function -- and the cut then left the return type
|
|
389
|
+
in place and reported success, which is the silently ignored flag this file warns
|
|
390
|
+
about a few lines up. MSVC met the same shape first and answers it with a scheme
|
|
391
|
+
option; an Itanium `function` node answers it directly, by spelling the same node
|
|
392
|
+
with nothing where the return type was. `libiberty`'s own `DMGL_RET_DROP` agrees.
|
|
393
|
+
"""
|
|
394
|
+
if parts.return_type:
|
|
395
|
+
try:
|
|
396
|
+
tree = parse(name, language=arguments.language, style=arguments.style, limits=limits)
|
|
397
|
+
except Exception: # the cut below is the answer when there is no tree
|
|
398
|
+
tree = None
|
|
399
|
+
if isinstance(tree, Function) and tree.returns is not None:
|
|
400
|
+
bare = Function(returns=None, parameters=tree.parameters, suffix=tree.suffix, name=tree.name)
|
|
401
|
+
return bare.spell(style=arguments.style)
|
|
402
|
+
# Cut by what the return type is rather than at the first space, so one with spaces
|
|
403
|
+
# in it goes whole.
|
|
404
|
+
spelling = parts.demangled
|
|
405
|
+
prefix = f"{parts.return_type} " if parts.return_type else ""
|
|
406
|
+
return spelling[len(prefix) :] if prefix and spelling.startswith(prefix) else spelling
|
|
407
|
+
|
|
408
|
+
|
|
409
|
+
def _run_names(names, arguments):
|
|
410
|
+
"""One name per argument: the whole argument is the name, whatever it holds.
|
|
411
|
+
|
|
412
|
+
No word-splitting here. A caller who typed a name meant that name, and an
|
|
413
|
+
Objective-C method or a Go symbol has spaces and slashes in it.
|
|
414
|
+
"""
|
|
415
|
+
limits = _limits_from(arguments)
|
|
416
|
+
status = 0
|
|
417
|
+
out = sys.stdout
|
|
418
|
+
for name in names:
|
|
419
|
+
if not name:
|
|
420
|
+
out.write("\n")
|
|
421
|
+
continue
|
|
422
|
+
try:
|
|
423
|
+
expanded = _expand(name, arguments, limits)
|
|
424
|
+
except BrokenPipeError:
|
|
425
|
+
raise
|
|
426
|
+
except Exception as exc:
|
|
427
|
+
print(f"{name}: {exc}", file=sys.stderr)
|
|
428
|
+
status = 1
|
|
429
|
+
continue
|
|
430
|
+
if arguments.only_demangled and expanded == name:
|
|
431
|
+
continue
|
|
432
|
+
out.write(f"{name} ==> {expanded}\n" if arguments.both else f"{expanded}\n")
|
|
433
|
+
return status
|
|
434
|
+
|
|
435
|
+
|
|
436
|
+
def _run_types(names, arguments):
|
|
437
|
+
"""`--types`: one *type* encoding per argument, or per line of standard input.
|
|
438
|
+
|
|
439
|
+
Not the filter the default path is, and it cannot be. A type encoding is not
|
|
440
|
+
symbol-shaped -- `Pi`, `H`, `Si` are ordinary words -- so picking them out of mixed
|
|
441
|
+
text would mean turning `I like Pi` into `I like int*`, which is the very thing
|
|
442
|
+
`_TOKEN_MUST_HOLD` exists to prevent. Each input is one encoding, whole.
|
|
443
|
+
|
|
444
|
+
An encoding that does not parse comes back unchanged, the bargain `demangle()`
|
|
445
|
+
makes, unless `--strict` asks to hear about it instead.
|
|
446
|
+
"""
|
|
447
|
+
limits = _limits_from(arguments)
|
|
448
|
+
status = 0
|
|
449
|
+
out = sys.stdout
|
|
450
|
+
for raw in names:
|
|
451
|
+
# A line off stdin carries its newline, and an argument does not. No type
|
|
452
|
+
# encoding in any of these grammars holds a `\r` either, so a file with CRLF
|
|
453
|
+
# endings reads the same as one without.
|
|
454
|
+
name = raw.rstrip("\r\n")
|
|
455
|
+
if not name:
|
|
456
|
+
out.write("\n")
|
|
457
|
+
continue
|
|
458
|
+
try:
|
|
459
|
+
expanded = _expand(name, arguments, limits)
|
|
460
|
+
except BrokenPipeError:
|
|
461
|
+
raise
|
|
462
|
+
except DemanglingError as exc:
|
|
463
|
+
if arguments.strict:
|
|
464
|
+
print(f"{name}: {exc}", file=sys.stderr)
|
|
465
|
+
status = 1
|
|
466
|
+
continue
|
|
467
|
+
expanded = name
|
|
468
|
+
except Exception as exc:
|
|
469
|
+
print(f"{name}: {exc}", file=sys.stderr)
|
|
470
|
+
status = 1
|
|
471
|
+
continue
|
|
472
|
+
if arguments.only_demangled and expanded == name:
|
|
473
|
+
continue
|
|
474
|
+
out.write(f"{name} ==> {expanded}\n" if arguments.both else f"{expanded}\n")
|
|
475
|
+
return status
|
|
476
|
+
|
|
477
|
+
|
|
478
|
+
def _run_stream(stream, arguments):
|
|
479
|
+
"""A filter: substitute every symbol-shaped word, copy everything else through.
|
|
480
|
+
|
|
481
|
+
Line by line rather than all at once, so `demangle` in a pipe stays a pipe: someone
|
|
482
|
+
watching `nm ... | demangle` should not have to wait for the input to end.
|
|
483
|
+
"""
|
|
484
|
+
limits = _limits_from(arguments)
|
|
485
|
+
status = 0
|
|
486
|
+
out = sys.stdout
|
|
487
|
+
|
|
488
|
+
for line in stream:
|
|
489
|
+
pieces = []
|
|
490
|
+
demangled = []
|
|
491
|
+
end = 0
|
|
492
|
+
for match in _TOKEN.finditer(line):
|
|
493
|
+
word = match.group()
|
|
494
|
+
if not _TOKEN_MUST_HOLD.search(word):
|
|
495
|
+
continue
|
|
496
|
+
try:
|
|
497
|
+
expanded = _expand(word, arguments, limits)
|
|
498
|
+
except BrokenPipeError:
|
|
499
|
+
raise
|
|
500
|
+
except Exception as exc:
|
|
501
|
+
print(f"{word}: {exc}", file=sys.stderr)
|
|
502
|
+
status = 1
|
|
503
|
+
continue
|
|
504
|
+
if expanded == word and not arguments.detect:
|
|
505
|
+
continue
|
|
506
|
+
replacement = f"{word} ==> {expanded}" if arguments.both else expanded
|
|
507
|
+
demangled.append(replacement)
|
|
508
|
+
pieces.append(line[end : match.start()])
|
|
509
|
+
pieces.append(replacement)
|
|
510
|
+
end = match.end()
|
|
511
|
+
if arguments.only_demangled:
|
|
512
|
+
for replacement in demangled:
|
|
513
|
+
out.write(replacement + "\n")
|
|
514
|
+
continue
|
|
515
|
+
pieces.append(line[end:])
|
|
516
|
+
out.write("".join(pieces))
|
|
517
|
+
return status
|
|
518
|
+
|
|
519
|
+
|
|
520
|
+
if __name__ == "__main__": # pragma: no cover
|
|
521
|
+
sys.exit(main())
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"""The scheme-agnostic half of the library.
|
|
2
|
+
|
|
3
|
+
Nothing here imports a mangling scheme, and a new scheme is written against these
|
|
4
|
+
modules and nothing else:
|
|
5
|
+
|
|
6
|
+
- `builder` -- the contract between a parser and its output, and the reason one parser
|
|
7
|
+
can serve both `demangle()` and `parse()` with no second implementation to drift.
|
|
8
|
+
- `spelling` -- C-family declarator placement, shared by every scheme that spells its
|
|
9
|
+
types the way C does.
|
|
10
|
+
- `ast` -- the tree `parse()` returns.
|
|
11
|
+
- `reader` -- a bounds-checked cursor; the only input primitive parsers use.
|
|
12
|
+
- `errors`, `limits` -- the failure and resource contracts.
|
|
13
|
+
- `plugin`, `registry` -- how a scheme announces itself.
|
|
14
|
+
- `decorations` -- what a symbol table appends to a name, which belongs to no scheme.
|
|
15
|
+
- `style` -- the choices on which the reference implementations legitimately differ.
|
|
16
|
+
|
|
17
|
+
An explicit `__init__` rather than a namespace package: an implicit one silently merges
|
|
18
|
+
with any same-named directory another installed distribution happens to ship, which for
|
|
19
|
+
a package called `core` is not a remote possibility.
|
|
20
|
+
"""
|