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
demangle/api.py ADDED
@@ -0,0 +1,749 @@
1
+ """The public API.
2
+
3
+ Deliberately small. A handful of functions cover what callers actually do, and every
4
+ one of them is stable ground that later versions can build on without breaking:
5
+
6
+ demangle(name) the readable spelling, or the name unchanged -- never raises
7
+ demangle_strict(name) the readable spelling -- raises
8
+ parse(name) a tree to inspect -- raises
9
+ detect(name) which scheme, if any
10
+ demangle_all(names) the batch form, cached
11
+
12
+ demangle_type(enc, language=...) a bare type encoding, spelled -- raises
13
+ parse_type(enc, language=...) a bare type encoding, as a tree -- raises
14
+
15
+ Each of those has a `...b` form taking and returning bytes, because a symbol table holds
16
+ bytes rather than text.
17
+
18
+ The split between `demangle` and `demangle_strict` is the important one. A tool
19
+ labelling every symbol in a binary meets far more non-mangled names than mangled ones,
20
+ and treating that as an error would mean an exception per symbol; it wants the name
21
+ back. A tool demangling one name a user typed wants to be told what was wrong with it.
22
+ Serving both from one function -- with a sentinel, or a flag -- makes the common case
23
+ carry the uncommon one's error handling, so they are two functions.
24
+ """
25
+
26
+ from collections.abc import Iterable, Iterator
27
+ from typing import Any, NoReturn
28
+
29
+ from .core import registry as _registry
30
+ from .core import style as _style_module
31
+ from .core.ast import Node, builder_for
32
+ from .core.cache import MISSING, BoundedCache
33
+ from .core.decorations import VERSION_SEPARATOR, split_decorations
34
+ from .core.errors import (
35
+ DemanglingError,
36
+ LimitExceeded,
37
+ NotMangledError,
38
+ ParseError,
39
+ reraise_if_operational,
40
+ )
41
+ from .core.limits import DEFAULT_LIMITS, Limits
42
+ from .core.registry import candidates, get, names
43
+ from .core.style import DEFAULT_STYLE, Style, available_styles, get_style
44
+
45
+ __all__ = [
46
+ "cache_clear",
47
+ "cache_stats",
48
+ "demangle",
49
+ "demangle_all",
50
+ "demangle_strict",
51
+ "demangle_type",
52
+ "demangleb",
53
+ "demangleb_strict",
54
+ "demangleb_type",
55
+ "detect",
56
+ "detectb",
57
+ "languages",
58
+ "node_kinds",
59
+ "parse",
60
+ "parse_type",
61
+ "parseb",
62
+ "parseb_type",
63
+ "style",
64
+ "styles",
65
+ ]
66
+
67
+ #: Symbol tables repeat names relentlessly -- one binary can name `std::allocator<char>`
68
+ #: thousands of times -- so memoisation is worth more here than any micro-optimisation.
69
+ #:
70
+ #: Keyed by everything that changes the answer, `limits` included. Leaving the limits
71
+ #: out is not merely a missed bound: one caller passing tight limits would poison the
72
+ #: entry for every other caller of that name in the process, and `demangle()` cannot
73
+ #: report it because it never raises. `Limits` is a frozen slots dataclass, so it
74
+ #: hashes by value and two equal limit sets share a cache entry.
75
+ _CACHE = BoundedCache(max_size=16384)
76
+
77
+
78
+ #: Emptied whenever a style or a language is registered. Replacing `llvm`, or replacing
79
+ #: a whole scheme, used to leave every name demangled beforehand still answering from
80
+ #: cache with the older spelling.
81
+ #:
82
+ #: Done by notification rather than by folding a generation counter into the key,
83
+ #: because the key is built once per `demangle()` call and that is the hottest path in
84
+ #: the package. Asking two modules "have you changed" there measured 58% slower on the
85
+ #: warm path than clearing a cache on the rare occasion one has.
86
+ _style_module.notify_on_change(_CACHE.clear)
87
+ _registry.notify_on_change(_CACHE.clear)
88
+
89
+
90
+ def _refuse_non_string(mangled):
91
+ """Report a non-`str` argument as the caller's mistake it is.
92
+
93
+ Bytes used to be handed straight back, unchanged and unremarked, so a tool reading
94
+ an ELF string table -- where names *are* bytes -- saw every symbol come back exactly
95
+ as it went in and concluded the library did not work. The strict entry points were
96
+ worse: they reached the registry's first-character screen and raised
97
+ `TypeError: 'in <string>' requires string as left operand, not int`, which names
98
+ neither the problem nor the fix, and which the documented "raises only
99
+ `DemanglingError`" contract said could not happen.
100
+ """
101
+ if isinstance(mangled, _BYTES_LIKE):
102
+ raise TypeError(f"expected str, got {type(mangled).__name__}; symbol tables hold bytes, so use demangleb()")
103
+ raise TypeError(f"expected str, got {type(mangled).__name__}")
104
+
105
+
106
+ def _resolve(language):
107
+ """Pick the plugin for `language`, or None to mean "detect"."""
108
+ if language is None:
109
+ return None
110
+ try:
111
+ return get(language)
112
+ except (KeyError, TypeError):
113
+ raise ValueError(f"unknown language {language!r}; known languages are {names()}") from None
114
+
115
+
116
+ def _parse_with(plugin, mangled, builder, limits, style):
117
+ """Run one plugin, handling any symbol-table decoration around the name.
118
+
119
+ Splitting here rather than in each parser keeps the grammars free of ELF and
120
+ toolchain conventions, and means a new scheme inherits the behaviour by setting one
121
+ flag instead of reimplementing it.
122
+ """
123
+ options = style.options_for(plugin.name)
124
+ decoration = ""
125
+ if plugin.symbol_table_decorations:
126
+ mangled, decoration = split_decorations(mangled)
127
+
128
+ if options is None:
129
+ handle = plugin.parse(mangled, builder, limits)
130
+ else:
131
+ handle = plugin.parse(mangled, builder, limits, options)
132
+
133
+ return builder.decorated(handle, decoration) if decoration else handle
134
+
135
+
136
+ def _refuse_unhashable(language, limits) -> NoReturn:
137
+ """Name the argument that could not go into the cache key, as a `ValueError`.
138
+
139
+ Only reached once a lookup has raised `TypeError`, so one of the two is unhashable;
140
+ the name is a `str` and a style's name is one too.
141
+ """
142
+ try:
143
+ hash(language)
144
+ except TypeError:
145
+ raise ValueError(f"unknown language {language!r}; known languages are {names()}") from None
146
+ try:
147
+ hash(limits)
148
+ except TypeError:
149
+ raise ValueError(f"unhashable limits {limits!r}; pass a Limits instance") from None
150
+ raise TypeError("arguments to demangle() must be hashable")
151
+
152
+
153
+ def demangle(
154
+ mangled: str,
155
+ *,
156
+ language: str | None = None,
157
+ style: str | Style | None = DEFAULT_STYLE,
158
+ limits: Limits = DEFAULT_LIMITS,
159
+ ) -> str:
160
+ """Return the readable spelling of `mangled`, or `mangled` unchanged.
161
+
162
+ Never raises for any *string*: a name this library cannot read comes back exactly as
163
+ it went in, because a wrong expansion is worse than a mangled name -- it matches
164
+ neither the original symbol nor the real declaration, so it corrupts every
165
+ downstream lookup that trusted it. That promise is about the name, not about the
166
+ argument's type: passing something that is not a `str` is a mistake in the calling
167
+ code and is reported as one.
168
+
169
+ Args:
170
+ mangled: the symbol name. Any string; need not be mangled.
171
+ language: force a scheme by name, or None to detect.
172
+ style: output spelling policy -- `"llvm"` (default) or `"gnu"`.
173
+ limits: resource bounds for the parse.
174
+
175
+ Returns:
176
+ The demangled name, or `mangled` unchanged.
177
+
178
+ Raises:
179
+ TypeError: `mangled` is not a `str`. Use `demangleb()` for bytes.
180
+ """
181
+ if not isinstance(mangled, str):
182
+ _refuse_non_string(mangled)
183
+ if not mangled:
184
+ return mangled
185
+ resolved_style = get_style(style)
186
+ # A caller may hand in a `Style` object rather than a name, and two different objects
187
+ # can carry the same name -- so keying on the name alone served one of them the
188
+ # other's spelling. A style holds a builder and a mapping of per-language options,
189
+ # neither of which hashes by value, so it cannot go into the key itself; a call that
190
+ # passes one is simply not cached. That is the rare path. The common one is a name,
191
+ # and it stays a four-element tuple. The test is for the name -- `None` or a `str` --
192
+ # rather than for a `Style`, so a subclass of one carrying a different builder under
193
+ # the same name takes the rare path too, instead of being served whatever was cached
194
+ # under that name first. `__class__ is str` rather than `isinstance`, and the
195
+ # registry's flag rather than its `_load()`, because this is the hottest line in the
196
+ # package: the two calls together cost the warm path a sixth of its time.
197
+ #
198
+ # Warmed before the cache is touched: the first `candidates()`/`get()` loads the
199
+ # registry, and loading registers plugins, which clears the cache -- wiping the miss
200
+ # just recorded if the `get` runs first.
201
+ if not _registry._loaded:
202
+ _registry._load()
203
+ if style is not None and style.__class__ is not str:
204
+ key = None
205
+ else:
206
+ key = (mangled, language, resolved_style.name, limits)
207
+ # The lookup hashes the key once. An argument that cannot be hashed -- a list
208
+ # for `limits`, say -- surfaces there as a `TypeError`, and is reported as the
209
+ # bad argument it is. Reported from the failure rather than checked for in
210
+ # advance, because `Limits` is a frozen dataclass whose hash is computed from
211
+ # its fields every time: checking it first would hash it twice on every warm
212
+ # call, which is the call the cache exists to make cheap.
213
+ try:
214
+ cached = _CACHE.get(key)
215
+ except TypeError:
216
+ _refuse_unhashable(language, limits)
217
+ if cached is not MISSING:
218
+ return cached
219
+
220
+ builder = resolved_style.spelling_builder
221
+ plugin = None if language is None else _resolve(language)
222
+ if plugin is not None:
223
+ tried, base = (plugin,), None
224
+ else:
225
+ tried, base = candidates(mangled), _undecorated(mangled)
226
+
227
+ for candidate in tried:
228
+ try:
229
+ # `_claims` written out. It is the same two tests in the same order, and its
230
+ # `try` is redundant *here*: a `detect` that throws is caught by this loop's
231
+ # own handler, which calls the same `reraise_if_operational` and then tries
232
+ # the next scheme, exactly as returning False would have. What the call cost
233
+ # was an interpreter frame for every candidate a name is offered to -- around
234
+ # seven per name on the benchmark corpus, six of them on a symbol that is not
235
+ # mangled at all, which is most of a real symbol table. `detect()` keeps the
236
+ # helper: it is not the hot path and one implementation of the rule should be
237
+ # readable somewhere.
238
+ if (
239
+ plugin is None
240
+ and not candidate.detect(mangled)
241
+ and (base is None or not candidate.symbol_table_decorations or not candidate.detect(base))
242
+ ):
243
+ continue
244
+ handle = _parse_with(candidate, mangled, builder, limits, resolved_style)
245
+ except LimitExceeded:
246
+ # A limit is not a "this name is not mine". The scheme claimed it and then
247
+ # ran out of the budget the caller set, which says the name is expensive --
248
+ # not that some other scheme should be handed the same text. Offering it on
249
+ # is how `_ZN11Expressions2f2ILi1EEEvPApsT__i`, an Itanium name that spends
250
+ # more substitutions than a tightened budget allows, came back as
251
+ # `_ZN11Expressions2f2ILi1EEEvPApsT(int)`: the pre-Itanium scheme reading the
252
+ # mangling itself as an identifier and the trailing `i` as a parameter. A
253
+ # declaration that names nothing is the one answer this library treats as
254
+ # worse than no answer, and a caller who *lowers* a limit is defending
255
+ # against hostile input, which is the last place to start guessing.
256
+ break
257
+ except Exception as exc:
258
+ reraise_if_operational(exc)
259
+ # Try the next scheme. Detection is a cheap prefix test and is allowed to be
260
+ # wrong, and a third-party plugin is allowed to be buggy -- the registry
261
+ # already takes care not to let a broken plugin bring the library down, and
262
+ # keeping the `detect` call inside this `try` is what stops it doing so here.
263
+ continue
264
+ # Recorded here rather than through a helper: `demangle` is the hot entry
265
+ # point, and this is a conditional and a call either way.
266
+ result = builder.spell(handle)
267
+ return result if key is None else _CACHE.put(key, result)
268
+
269
+ return mangled if key is None else _CACHE.put(key, mangled)
270
+
271
+
272
+ def demangle_strict(
273
+ mangled: str,
274
+ *,
275
+ language: str | None = None,
276
+ style: str | Style | None = DEFAULT_STYLE,
277
+ limits: Limits = DEFAULT_LIMITS,
278
+ ) -> str:
279
+ """Return the readable spelling of `mangled`, raising when it cannot be read.
280
+
281
+ Raises:
282
+ NotMangledError: the name matches no known scheme.
283
+ ParseError: the name has a known prefix but does not follow the grammar.
284
+ LimitExceeded: a resource bound was hit.
285
+ """
286
+ resolved_style = get_style(style)
287
+ builder = resolved_style.spelling_builder
288
+ return builder.spell(_parse_handle(mangled, builder, language, resolved_style, limits))
289
+
290
+
291
+ def parse(
292
+ mangled: str,
293
+ *,
294
+ language: str | None = None,
295
+ style: str | Style | None = DEFAULT_STYLE,
296
+ limits: Limits = DEFAULT_LIMITS,
297
+ ) -> Node:
298
+ """Parse `mangled` into a tree.
299
+
300
+ Use this when the *parts* matter -- the namespace, the template arguments, the
301
+ parameter types -- rather than the spelling. The result is a `core.ast.Node`
302
+ supporting `.walk()`, `.find(kind)` and `.spell()`.
303
+
304
+ Raises the same errors as `demangle_strict`.
305
+
306
+ Note:
307
+ Every scheme returns a tree, but the kinds differ with what each language has to
308
+ say. C++ trees carry declarator shape -- pointers, references, parameter lists --
309
+ because C++ types wrap the name they declare. Rust has no declarator syntax, so
310
+ its trees carry path structure instead: `symbol`, `path`, `impl`, `namespace`.
311
+ `name`, `template` and `literal` mean the same thing in all three.
312
+ """
313
+ resolved = get_style(style)
314
+ return _parse_handle(mangled, builder_for(resolved), language, resolved, limits)
315
+
316
+
317
+ def demangle_type(
318
+ mangled: str,
319
+ *,
320
+ language: str,
321
+ style: str | Style | None = DEFAULT_STYLE,
322
+ limits: Limits = DEFAULT_LIMITS,
323
+ ) -> str:
324
+ """Return the readable spelling of a bare *type* encoding.
325
+
326
+ `Pi` is `int*`, `PEAX` is `void *`, `SaySiG` is `[Swift.Int]`. This is what a
327
+ `typeinfo` name, an RTTI type descriptor and a Swift metadata typeref carry: a type
328
+ on its own, with none of the surrounding symbol a name has.
329
+
330
+ `language` is required, and cannot be made optional. A whole symbol says which
331
+ scheme it belongs to -- `_Z`, `?`, `$s` -- but a type encoding says nothing at all:
332
+ `i` is a valid Itanium type, a valid Swift type, and a valid C identifier, so there
333
+ is no evidence to detect on. Guessing would mean reading plain C symbols as types,
334
+ which is why every reference puts this behind a flag of its own -- `c++filt -t`,
335
+ libiberty's `DMGL_TYPES`, `UnDecorateSymbolName`'s `UNDNAME_TYPE_ONLY`.
336
+
337
+ Raises rather than passing the name through, unlike `demangle()`. A caller who named
338
+ the scheme is asking a question about one encoding they already believe is one, not
339
+ labelling a table of symbols, and wants to be told when it is not.
340
+
341
+ Args:
342
+ mangled: the type encoding.
343
+ language: which scheme to read it as -- required. See `languages()`.
344
+ style: output spelling policy -- `"llvm"` (default) or `"gnu"`.
345
+ limits: resource bounds for the parse.
346
+
347
+ Raises:
348
+ ValueError: `language` is unknown, or names a scheme with no type grammar.
349
+ NotMangledError: the encoding is empty.
350
+ ParseError: the encoding does not follow the scheme's type grammar.
351
+ LimitExceeded: a resource bound was hit.
352
+ """
353
+ resolved = get_style(style)
354
+ builder = resolved.spelling_builder
355
+ return builder.spell(_parse_type_handle(mangled, builder, language, resolved, limits))
356
+
357
+
358
+ def parse_type(
359
+ mangled: str,
360
+ *,
361
+ language: str,
362
+ style: str | Style | None = DEFAULT_STYLE,
363
+ limits: Limits = DEFAULT_LIMITS,
364
+ ) -> Node:
365
+ """Parse a bare *type* encoding into a tree.
366
+
367
+ `demangle_type()` is to `demangle_strict()` as this is to `parse()`: same input,
368
+ same required `language`, same errors -- a `core.ast.Node` instead of a string.
369
+ """
370
+ resolved = get_style(style)
371
+ return _parse_type_handle(mangled, builder_for(resolved), language, resolved, limits)
372
+
373
+
374
+ def _parse_type_handle(mangled, builder, language, style, limits):
375
+ if not isinstance(mangled, str):
376
+ _refuse_non_string(mangled)
377
+ plugin = _resolve(language)
378
+ if plugin is None:
379
+ raise ValueError("demangle_type needs a language; a type encoding carries no marker to detect on")
380
+ if plugin.parse_type is None:
381
+ raise ValueError(f"{plugin.name} has no type grammar of its own; {_type_languages()} do")
382
+ options = style.options_for(plugin.name)
383
+ try:
384
+ if options is None:
385
+ return plugin.parse_type(mangled, builder, limits)
386
+ return plugin.parse_type(mangled, builder, limits, options)
387
+ except RecursionError as exc:
388
+ raise _depth_exceeded(mangled, limits) from exc
389
+ except DemanglingError:
390
+ raise
391
+ except Exception as exc:
392
+ reraise_if_operational(exc)
393
+ # Same contract as `_parse_handle`: these entry points raise `DemanglingError`
394
+ # and nothing else, so a defect in a plugin is wrapped rather than let out as an
395
+ # `AttributeError` no caller can reasonably catch. The original is chained.
396
+ raise ParseError(mangled, None, f"{plugin.name} type parser failed: {exc!r}") from exc
397
+
398
+
399
+ def style(base: str | Style | None = DEFAULT_STYLE, /, **languages: Any) -> Style:
400
+ """Compose a style for one call: a named one, with some per-language changes.
401
+
402
+ The two named styles say how to spell a name; what a caller usually wants to vary is
403
+ how *much* of it to spell. Every peer tool composes that at the call site --
404
+ `llvm-undname --no-calling-convention`, `UnDecorateSymbolName`'s mask -- and this is
405
+ the same thing without a global registration:
406
+
407
+ >>> import demangle
408
+ >>> narrow = demangle.style("llvm", msvc={"calling_convention": False})
409
+ >>> demangle.demangle("?f@@YAXH@Z", style=narrow)
410
+ 'void f(int)'
411
+ >>> demangle.demangle("?f@@YAXH@Z")
412
+ 'void __cdecl f(int)'
413
+
414
+ Each keyword is a language name; each value is that language's options object or a
415
+ mapping of the fields to change. `languages()` lists the names, and each scheme's
416
+ `options` module documents what it accepts.
417
+
418
+ The result is an object rather than a registered name, and that is what keeps it to
419
+ one call: `demangle()` does not cache a call that passes a style object, so a
420
+ narrower spelling asked for here is never served to a caller asking for the default.
421
+
422
+ Raises:
423
+ ValueError: `base` is not a known style, or a language named has no options.
424
+ """
425
+ return get_style(base).with_options(**languages)
426
+
427
+
428
+ def node_kinds(language: str | None = None) -> tuple[str, ...]:
429
+ """Every `kind` a `parse()` tree can hold, sorted. One scheme's, or all of them.
430
+
431
+ The vocabulary to switch on, published rather than left to be read out of the source:
432
+
433
+ >>> import demangle
434
+ >>> demangle.node_kinds("d")
435
+ ('name', 'path', 'symbol')
436
+
437
+ What each kind *means* differs with what the language has to say. A C++ tree carries
438
+ declarator shape -- `pointer`, `array`, `function` -- because a C++ type wraps the
439
+ name it declares. Rust has no declarator syntax, so its trees carry path structure
440
+ instead: `symbol`, `path`, `impl`, `namespace`. `name`, `template` and `literal` mean
441
+ the same thing wherever they appear.
442
+
443
+ Raises:
444
+ ValueError: `language` is not a known scheme.
445
+ """
446
+ if language is not None:
447
+ return tuple(sorted(_resolve(language).node_kinds))
448
+ from .core.registry import available
449
+
450
+ return tuple(sorted({kind for plugin in available() for kind in plugin.node_kinds}))
451
+
452
+
453
+ def _type_languages():
454
+ """The schemes that read a bare type, for the error that says one does not."""
455
+ return ", ".join(sorted(name for name in names() if get(name).parse_type is not None))
456
+
457
+
458
+ def _parse_handle(mangled, builder, language, style, limits):
459
+ if not isinstance(mangled, str):
460
+ _refuse_non_string(mangled)
461
+ if not mangled:
462
+ raise NotMangledError(mangled, "empty name")
463
+ plugin = _resolve(language)
464
+ if plugin is not None:
465
+ try:
466
+ return _parse_with(plugin, mangled, builder, limits, style)
467
+ except RecursionError as exc:
468
+ raise _depth_exceeded(mangled, limits) from exc
469
+
470
+ first_error = None
471
+ base = _undecorated(mangled)
472
+ for candidate in candidates(mangled):
473
+ try:
474
+ if not _claims(candidate, mangled, base):
475
+ continue
476
+ return _parse_with(candidate, mangled, builder, limits, style)
477
+ except RecursionError as exc:
478
+ if first_error is None:
479
+ first_error = _depth_exceeded(mangled, limits)
480
+ first_error.__cause__ = exc
481
+ except LimitExceeded as exc:
482
+ # Raised, not remembered: see the note in `demangle`. The next candidate
483
+ # would be reading a name this one has already claimed, and the answer it
484
+ # gives is a reading of the mangling rather than of the name.
485
+ raise exc
486
+ except DemanglingError as exc:
487
+ # Keep the first failure: it came from the highest-priority plugin that
488
+ # claimed the name, so it is the most likely to be the useful diagnostic.
489
+ if first_error is None:
490
+ first_error = exc
491
+ except Exception as exc:
492
+ reraise_if_operational(exc)
493
+ # A plugin raised something that is not a demangling failure -- a defect in
494
+ # it, or in this package. The documented contract is that these entry points
495
+ # raise `DemanglingError` and nothing else, so it is wrapped rather than
496
+ # allowed to escape as an `AttributeError` a caller cannot reasonably catch.
497
+ # The original is chained, so the bug is still diagnosable.
498
+ if first_error is None:
499
+ first_error = ParseError(mangled, None, f"{candidate.name} parser failed: {exc!r}")
500
+ first_error.__cause__ = exc
501
+ if first_error is not None:
502
+ raise first_error
503
+ raise NotMangledError(mangled)
504
+
505
+
506
+ def _depth_exceeded(mangled, limits):
507
+ """A `RecursionError` from a parser, reported as the bound it is.
508
+
509
+ Two bounds govern how deep a name may nest, and `max_depth` is only one of them. The
510
+ other is the interpreter's own stack, and it is the *lower* of the two in practice:
511
+ a production costs several Python frames, so at the default recursion limit of 1000
512
+ an Itanium name gives out around 141 levels of nested template, 164 of `decltype`,
513
+ 197 of function type and 493 of pointer -- all of them under the default
514
+ `max_depth` of 256, let alone `RELAXED_LIMITS`' 2048.
515
+
516
+ Which of the two binds first therefore depends on the shape of the name and on how
517
+ deep the caller's own stack already was. Both are the same fact -- this name nests
518
+ further than this process will follow -- so both are reported the same way. Before
519
+ this, one arrived as `LimitExceeded` and the other as
520
+ `ParseError: itanium parser failed: RecursionError(...)`, which reads as a defect in
521
+ the parser rather than a bound doing its job, and leaks an implementation detail
522
+ into a message a caller was meant to be able to act on.
523
+
524
+ A caller who needs the deeper limits to be reachable can raise
525
+ `sys.setrecursionlimit()`; on the versions this package supports, a Python-to-Python
526
+ call does not consume the C stack, so that is safer than it once was.
527
+ """
528
+ return LimitExceeded(mangled, "recursion depth", limits.max_depth)
529
+
530
+
531
+ def _undecorated(mangled):
532
+ """The name with any ELF version suffix taken off, or None if it carries none.
533
+
534
+ Asked once per name rather than once per candidate. The split depends on nothing but
535
+ the name, and a symbol table is mostly names that carry no decoration at all -- so
536
+ the answer for the first plugin that asks is the answer for all of them, and it is
537
+ almost always "there is nothing to take off".
538
+
539
+ `split_decorations` is what defines the split and stays the place it is written; this
540
+ is its answer read directly off the same `find`, because building and unpacking a
541
+ two-tuple through a second interpreter frame is a per-name cost for a question whose
542
+ answer is `None` for nearly every symbol. The separator is imported rather than
543
+ spelled again, so the two cannot drift apart.
544
+ """
545
+ version = mangled.find(VERSION_SEPARATOR)
546
+ return mangled[:version] if version > 0 else None
547
+
548
+
549
+ def _claims(plugin, mangled, base):
550
+ """Whether `plugin` recognises `mangled`, ignoring any symbol-table decoration.
551
+
552
+ The raw name is tried first because this runs on every symbol a caller offers, and
553
+ in a real binary most of them are not mangled at all. A decoration is a *suffix*, so
554
+ a prefix test sees straight through it and `base` -- what `_undecorated` made of the
555
+ name, or None -- is only consulted when the cheap test has already failed.
556
+
557
+ Never raises. `detect` belongs to a plugin that may be a third party's, and the
558
+ registry already declines to let a broken one take the library down; this is the
559
+ other half of that, since a `detect` that throws would otherwise escape through
560
+ `demangle()`, which is documented never to raise.
561
+ """
562
+ try:
563
+ if plugin.detect(mangled):
564
+ return True
565
+ return base is not None and plugin.symbol_table_decorations and plugin.detect(base)
566
+ except Exception as exc:
567
+ reraise_if_operational(exc)
568
+ return False
569
+
570
+
571
+ def detect(mangled: str) -> str | None:
572
+ """Name the scheme `mangled` appears to use, or None.
573
+
574
+ A prefix test only -- it reports what the name looks like, not that it will parse.
575
+ Never raises: like `demangle()`, it is called on every symbol in a table.
576
+ """
577
+ if not isinstance(mangled, str):
578
+ # `detect` is offered every symbol in a table and answers None for anything it
579
+ # does not recognise, so a wrong type is answered the same way rather than
580
+ # raised: a caller looping over a table wants a verdict, not an exception.
581
+ return None
582
+ if not mangled:
583
+ return None
584
+ base = _undecorated(mangled)
585
+ for plugin in candidates(mangled):
586
+ if _claims(plugin, mangled, base):
587
+ return plugin.name
588
+ return None
589
+
590
+
591
+ #: How a symbol table's bytes become a `str` and back again.
592
+ #:
593
+ #: A mangled name is read out of an object file, where it is a run of bytes ending at a
594
+ #: NUL and nothing else -- not text in any declared encoding. Almost all of them are
595
+ #: ASCII, but not all: a raw identifier can carry anything the assembler accepted, and a
596
+ #: truncated symbol table can cut a name mid-character.
597
+ #:
598
+ #: `surrogateescape` is what makes the round trip total. Every byte that is not valid
599
+ #: UTF-8 is parked in a lone surrogate, and encoding back with the same handler restores
600
+ #: exactly the byte that went in. So a name this package cannot read comes back out of
601
+ #: `demangleb` byte for byte, which is the same promise `demangle` makes for a `str`.
602
+ _BYTES_ENCODING = "utf-8"
603
+ _BYTES_ERRORS = "surrogateescape"
604
+
605
+ #: The types `demangleb` accepts. `memoryview` is included because that is what a caller
606
+ #: slicing a mapped object file has in hand, and copying it to ask a question would be a
607
+ #: strange thing to make them do.
608
+ _BYTES_LIKE = (bytes, bytearray, memoryview)
609
+
610
+
611
+ def _decode(mangled):
612
+ """Bytes in, `str` out, or a `TypeError` naming what was actually passed."""
613
+ if isinstance(mangled, _BYTES_LIKE):
614
+ return bytes(mangled).decode(_BYTES_ENCODING, _BYTES_ERRORS)
615
+ raise TypeError(f"expected bytes, got {type(mangled).__name__}")
616
+
617
+
618
+ def demangleb(
619
+ mangled: bytes,
620
+ *,
621
+ language: str | None = None,
622
+ style: str | Style | None = DEFAULT_STYLE,
623
+ limits: Limits = DEFAULT_LIMITS,
624
+ ) -> bytes:
625
+ """`demangle()` over bytes, returning bytes.
626
+
627
+ The form to use when the names come from a symbol table, which is where they usually
628
+ do: an ELF or Mach-O string table holds bytes, and a tool that has read one should
629
+ not have to guess an encoding to ask what a name says.
630
+
631
+ Like `demangle()`, never raises for a name it cannot read -- it hands the bytes back
632
+ exactly as they arrived, including any that are not valid UTF-8. Raises `TypeError`
633
+ if given something that is not bytes-like, which is a mistake in the calling code
634
+ rather than a property of the symbol.
635
+
636
+ >>> import demangle
637
+ >>> demangle.demangleb(b"_ZN3foo3barEv")
638
+ b'foo::bar()'
639
+ """
640
+ return demangle(_decode(mangled), language=language, style=style, limits=limits).encode(
641
+ _BYTES_ENCODING, _BYTES_ERRORS
642
+ )
643
+
644
+
645
+ def demangleb_strict(
646
+ mangled: bytes,
647
+ *,
648
+ language: str | None = None,
649
+ style: str | Style | None = DEFAULT_STYLE,
650
+ limits: Limits = DEFAULT_LIMITS,
651
+ ) -> bytes:
652
+ """`demangle_strict()` over bytes, returning bytes. Raises what it raises."""
653
+ return demangle_strict(_decode(mangled), language=language, style=style, limits=limits).encode(
654
+ _BYTES_ENCODING, _BYTES_ERRORS
655
+ )
656
+
657
+
658
+ def parseb(
659
+ mangled: bytes,
660
+ *,
661
+ language: str | None = None,
662
+ style: str | Style | None = DEFAULT_STYLE,
663
+ limits: Limits = DEFAULT_LIMITS,
664
+ ) -> Node:
665
+ """`parse()` over bytes. The tree it returns spells `str`, as every tree does."""
666
+ return parse(_decode(mangled), language=language, style=style, limits=limits)
667
+
668
+
669
+ def demangleb_type(
670
+ mangled: bytes,
671
+ *,
672
+ language: str,
673
+ style: str | Style | None = DEFAULT_STYLE,
674
+ limits: Limits = DEFAULT_LIMITS,
675
+ ) -> bytes:
676
+ """`demangle_type()` over bytes, returning bytes.
677
+
678
+ A type encoding is read out of a binary as often as a symbol is -- an Itanium
679
+ `typeinfo` name sits in `.rodata` and an MSVC type descriptor's name in `.rdata` --
680
+ so it gets the same bytes form the rest of the package has. Raises what
681
+ `demangle_type()` raises.
682
+ """
683
+ return demangle_type(_decode(mangled), language=language, style=style, limits=limits).encode(
684
+ _BYTES_ENCODING, _BYTES_ERRORS
685
+ )
686
+
687
+
688
+ def parseb_type(
689
+ mangled: bytes,
690
+ *,
691
+ language: str,
692
+ style: str | Style | None = DEFAULT_STYLE,
693
+ limits: Limits = DEFAULT_LIMITS,
694
+ ) -> Node:
695
+ """`parse_type()` over bytes. The tree it returns spells `str`, as every tree does."""
696
+ return parse_type(_decode(mangled), language=language, style=style, limits=limits)
697
+
698
+
699
+ def detectb(mangled: bytes) -> str | None:
700
+ """`detect()` over bytes. The scheme's name is a `str`; it is this package's own."""
701
+ if not isinstance(mangled, _BYTES_LIKE):
702
+ return None
703
+ return detect(bytes(mangled).decode(_BYTES_ENCODING, _BYTES_ERRORS))
704
+
705
+
706
+ def demangle_all(
707
+ names_: Iterable[str],
708
+ *,
709
+ language: str | None = None,
710
+ style: str | Style | None = DEFAULT_STYLE,
711
+ limits: Limits = DEFAULT_LIMITS,
712
+ ) -> Iterator[str]:
713
+ """Demangle an iterable of names, yielding results in order.
714
+
715
+ Returns a generator, so a caller streaming a large symbol table never holds more
716
+ than one result at a time beyond what it keeps itself. Shares the module cache,
717
+ which is where the real gain is: symbol tables repeat names heavily.
718
+
719
+ Arguments are validated before the generator is created, so a bad `language` or
720
+ `style` is reported at the call rather than at the first `next()`.
721
+ """
722
+ _resolve(language)
723
+ get_style(style)
724
+
725
+ def _stream():
726
+ for name in names_:
727
+ yield demangle(name, language=language, style=style, limits=limits)
728
+
729
+ return _stream()
730
+
731
+
732
+ def languages() -> list[str]:
733
+ """Registered language names, including any third-party plugins."""
734
+ return names()
735
+
736
+
737
+ def styles() -> list[str]:
738
+ """Registered output style names."""
739
+ return available_styles()
740
+
741
+
742
+ def cache_clear() -> None:
743
+ """Empty the module-level result cache."""
744
+ _CACHE.clear()
745
+
746
+
747
+ def cache_stats() -> dict[str, Any]:
748
+ """Hit rate and occupancy of the result cache."""
749
+ return _CACHE.stats