Public API¶
What import demangle gives a caller. A few of its exports belong to modules documented
with the rest of the core; the list at the end says where.
import demangle
demangle.demangle("_ZNSt6vectorIiSaIiEE9push_backERKi")
# 'std::vector<int, std::allocator<int>>::push_back(int const&)'
demangle.demangle_type("PKFvRiE", language="itanium")
# 'void (*)(int&) const'
demangle.demangle("?f@@YAXH@Z", style=demangle.style("llvm", msvc={"calling_convention": False}))
# 'void f(int)'
demangle.signature("_ZNSt6vectorIiSaIiEE9push_backERKi").base_name
# 'push_back'
Functions¶
demangle.api
¶
The public API.
Deliberately small. A handful of functions cover what callers actually do, and every one of them is stable ground that later versions can build on without breaking:
demangle(name) the readable spelling, or the name unchanged -- never raises
demangle_strict(name) the readable spelling -- raises
parse(name) a tree to inspect -- raises
detect(name) which scheme, if any
demangle_all(names) the batch form, cached
preload(*languages) import schemes now, not on first use
demangle_type(enc, language=...) a bare type encoding, spelled -- raises
parse_type(enc, language=...) a bare type encoding, as a tree -- raises
"Raises" means over an unreadable name. Each also raises TypeError or ValueError for
an argument that is wrong rather than unreadable -- an unknown language, say; detect
only the ValueError, since it answers None to any name, a str or not.
language is one scheme's name, which forces that scheme, or None, which detects among
all of them -- or, everywhere but the two type readers, a sequence of names, which
detects among those alone.
Each single-name entry point has a ...b form taking and returning bytes, because a
symbol table holds bytes rather than text.
The split between demangle and demangle_strict is the important one. A tool
labelling every symbol in a binary meets far more non-mangled names than mangled ones,
and treating that as an error would mean an exception per symbol; it wants the name
back. A tool demangling one name a user typed wants to be told what was wrong with it.
Serving both from one function -- with a sentinel, or a flag -- makes the common case
carry the uncommon one's error handling, so they are two functions.
demangle
¶
demangle(
mangled,
*,
language=None,
style=DEFAULT_STYLE,
limits=DEFAULT_LIMITS,
)
Return the readable spelling of mangled, or mangled unchanged.
Never raises over the name: whatever string it is given, a name this library
cannot read comes back exactly as it went in, because a wrong expansion is worse
than a mangled name -- it matches neither the original symbol nor the real
declaration, so it corrupts every downstream lookup that trusted it. That promise is
about the name, not about the other arguments: a mangled that is not a str, or a
language, style or limits that names nothing, is a mistake in the calling code
and is reported as one.
A sequence of names is an allow-list, for a caller that knows which schemes a table can hold -- a Mach-O image's C++ and Swift, say -- and wants nothing else rewriting its Objective-C metadata or its paths:
>>> import demangle
>>> demangle.demangle("_OBJC_CLASS_$_NSData", language=("itanium", "swift"))
'_OBJC_CLASS_$_NSData'
>>> demangle.demangle("$s10Foundation4DataV5countSivg", language=["itanium", "swift"])
'Foundation.Data.count.getter : Swift.Int'
It detects, among those schemes, in the usual order. So ("gnuv2",) is not
"gnuv2", which forces the scheme on a name its detection would decline:
PyInit__lldb stays as it is under the first and is read as pre-Itanium C++ under
the second. One name or several, a sequence is a filter on detection, so an
allow-list built at run time means the same whatever its length.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
mangled
|
str
|
the symbol name. Any string; need not be mangled. |
required |
language
|
str | Sequence[str] | None
|
force a scheme by name, detect among a sequence of names, or None to detect among all of them. |
None
|
style
|
str | Style | None
|
output spelling policy -- a style name ( |
DEFAULT_STYLE
|
limits
|
Limits
|
resource bounds for the parse. |
DEFAULT_LIMITS
|
Returns:
| Type | Description |
|---|---|
str
|
The demangled name, or |
Raises:
| Type | Description |
|---|---|
TypeError
|
|
ValueError
|
|
Source code in src/demangle/api.py
236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 | |
demangle_strict
¶
demangle_strict(
mangled,
*,
language=None,
style=DEFAULT_STYLE,
limits=DEFAULT_LIMITS,
)
Return the readable spelling of mangled, raising when it cannot be read.
Takes the same arguments as demangle(). Every failure to read the name is a
DemanglingError; a plugin that fails some other way is wrapped in a ParseError,
with the original chained.
Raises:
| Type | Description |
|---|---|
NotMangledError
|
the name matches no known scheme, or none that |
ParseError
|
the name has a known prefix but does not follow the grammar. |
LimitExceeded
|
a resource bound was hit. |
TypeError
|
|
ValueError
|
|
Source code in src/demangle/api.py
393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 | |
parse
¶
parse(
mangled,
*,
language=None,
style=DEFAULT_STYLE,
limits=DEFAULT_LIMITS,
)
Parse mangled into a tree.
Use this when the parts matter -- the namespace, the template arguments, the
parameter types -- rather than the spelling. The result is a core.ast.Node
supporting .walk(), .find(kind) and .spell().
Raises the same errors as demangle_strict. Which kinds of node a tree can hold
differs from scheme to scheme; see node_kinds().
Source code in src/demangle/api.py
420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 | |
detect
¶
detect(mangled, *, language=None, strict=False)
Name the scheme mangled appears to use, or None.
Most schemes are recognised by a prefix, so this reports what the name looks like,
not that it will parse. The schemes whose names carry no marker -- gnuv2,
codewarrior and ada -- parse the whole name to claim it, and so does a legacy
Rust name written without its underscore (a bare ZN...), which is read up to
max_input to decide.
strict=True asks the other question: which scheme reads the name. The answer is
the one demangle() would use, under the default style and limits -- the first in
detection order that claims the name and then parses it -- or None where it would
hand the name back. It costs a parse, which is why it is not the default:
>>> import demangle
>>> demangle.detect("_ZN3Foo")
'itanium'
>>> demangle.detect("_ZN3Foo", strict=True) is None
True
language narrows the question to the schemes it names, one or a sequence: the
answer is the first of them, in the usual order, that claims the name. With
strict, one name forces that scheme as it does for demangle(), and the answer
is whether it reads the name.
Never raises over the name: like demangle(), it is called on every symbol in a
table. A language that names nothing is the calling code's mistake, and a
ValueError.
Source code in src/demangle/api.py
709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 | |
demangle_all
¶
demangle_all(
names_,
*,
language=None,
style=DEFAULT_STYLE,
limits=DEFAULT_LIMITS,
)
Demangle an iterable of names, yielding results in order.
Returns a generator, so a caller streaming a large symbol table never holds more than one result at a time beyond what it keeps itself. Shares the module cache, which is where the real gain is: symbol tables repeat names heavily.
Arguments are validated before the generator is created, so a bad language,
style or limits is reported at the call rather than at the first next().
Source code in src/demangle/api.py
881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 | |
demangle_type
¶
demangle_type(
mangled,
*,
language,
style=DEFAULT_STYLE,
limits=DEFAULT_LIMITS,
)
Return the readable spelling of a bare type encoding.
Pi is int*, PEAX is void *, SaySiG is [Swift.Int]. This is what a
typeinfo name, an RTTI type descriptor and a Swift metadata typeref carry: a type
on its own, with none of the surrounding symbol a name has.
language is required, and cannot be made optional. A whole symbol says which
scheme it belongs to -- _Z, ?, $s -- but a type encoding says nothing at all:
i is a valid Itanium type, a valid Swift type, and a valid C identifier, so there
is no evidence to detect on. Guessing would mean reading plain C symbols as types,
which is why every reference puts this behind a flag of its own -- c++filt -t,
libiberty's DMGL_TYPES, UnDecorateSymbolName's UNDNAME_TYPE_ONLY.
Raises rather than passing the name through, unlike demangle(). A caller who named
the scheme is asking a question about one encoding they already believe is one, not
labelling a table of symbols, and wants to be told when it is not.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
mangled
|
str
|
the type encoding. |
required |
language
|
str
|
which scheme to read it as -- required. See |
required |
style
|
str | Style | None
|
output spelling policy -- a style name ( |
DEFAULT_STYLE
|
limits
|
Limits
|
resource bounds for the parse. |
DEFAULT_LIMITS
|
Raises:
| Type | Description |
|---|---|
ValueError
|
|
TypeError
|
|
NotMangledError
|
the encoding is empty. |
ParseError
|
the encoding does not follow the scheme's type grammar. |
LimitExceeded
|
a resource bound was hit. |
Source code in src/demangle/api.py
440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 | |
parse_type
¶
parse_type(
mangled,
*,
language,
style=DEFAULT_STYLE,
limits=DEFAULT_LIMITS,
)
Parse a bare type encoding into a tree.
demangle_type() is to demangle_strict() as this is to parse(): same input,
same required language, same errors -- a core.ast.Node instead of a string.
Source code in src/demangle/api.py
485 486 487 488 489 490 491 492 493 494 495 496 497 498 | |
style
¶
style(base=DEFAULT_STYLE, /, **languages)
Compose a style for one call: a named one, with some per-language changes.
The two named styles say how to spell a name; what a caller usually wants to vary is
how much of it to spell. Every peer tool composes that at the call site --
llvm-undname --no-calling-convention, UnDecorateSymbolName's mask -- and this is
the same thing without a global registration:
>>> import demangle
>>> narrow = demangle.style("llvm", msvc={"calling_convention": False})
>>> demangle.demangle("?f@@YAXH@Z", style=narrow)
'void f(int)'
>>> demangle.demangle("?f@@YAXH@Z")
'void __cdecl f(int)'
Each keyword is a language name; each value is that language's options object or a
mapping of the fields to change. languages() lists the names, and each scheme's
options module documents what it accepts.
The result is an object rather than a registered name, and that is what keeps it to
one call: demangle() does not cache a call that passes a style object, so a
narrower spelling asked for here is never served to a caller asking for the default.
Raises:
| Type | Description |
|---|---|
ValueError
|
|
Source code in src/demangle/api.py
525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 | |
node_kinds
¶
node_kinds(language=None)
Every kind a parse() tree can hold, sorted. One scheme's, or all of them.
The vocabulary to switch on, published rather than left to be read out of the source:
>>> import demangle
>>> demangle.node_kinds("d")
('name', 'path', 'symbol')
What each kind means differs with what the language has to say. A C++ tree carries
declarator shape -- pointer, array, function -- because a C++ type wraps the
name it declares. Rust has no declarator syntax, so its trees carry path structure
instead: symbol, path, impl, namespace. name, template and literal mean
the same thing wherever they appear.
Raises:
| Type | Description |
|---|---|
ValueError
|
|
Source code in src/demangle/api.py
554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 | |
languages
¶
languages()
Registered language names. Third-party plugins appear once load_plugins() has run.
Source code in src/demangle/api.py
908 909 910 | |
styles
¶
styles()
Registered output style names.
Source code in src/demangle/api.py
941 942 943 | |
preload
¶
preload(*languages)
Import schemes now rather than when a name first reaches them.
A built-in scheme is imported on first use, so a script reading one name pays for the scheme that name needs and no other -- and the first MSVC name a process reads pays about 7 ms for the import. A long-running service would rather pay at start-up:
>>> import demangle
>>> demangle.preload("itanium", "msvc")
>>> demangle.preload()
Names, or aliases, import those schemes; none imports every built-in. The order and every answer are the same either way: this moves the cost, nothing else.
Raises:
| Type | Description |
|---|---|
ValueError
|
a name is not a registered language. Nothing is imported then. |
Source code in src/demangle/api.py
913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 | |
load_plugins
¶
load_plugins()
Register the plugins installed distributions advertise, once.
Third-party languages are opt-in: a distribution advertising the demangle.languages
entry-point group is not read until this is called, because a plugin may carry the
name of a built-in and register lets it replace that scheme. Calling it is the
caller's decision to accept that. Explicit register calls are not affected.
Safe to call from any thread and as often as you like; only the first call looks.
A plugin that fails to load is skipped with a RuntimeWarning.
Source code in src/demangle/core/registry.py
274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 | |
demangleb
¶
demangleb(
mangled,
*,
language=None,
style=DEFAULT_STYLE,
limits=DEFAULT_LIMITS,
)
demangle() over bytes, returning bytes.
The form to use when the names come from a symbol table, which is where they usually do: an ELF or Mach-O string table holds bytes, and a tool that has read one should not have to guess an encoding to ask what a name says.
Like demangle(), never raises for a name it cannot read -- it hands the bytes back
exactly as they arrived, including any that are not valid UTF-8. Raises TypeError
if given something that is not bytes-like, and ValueError for the arguments
demangle() refuses, which are mistakes in the calling code rather than properties
of the symbol.
>>> import demangle
>>> demangle.demangleb(b"_ZN3foo3barEv")
b'foo::bar()'
Source code in src/demangle/api.py
785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 | |
demangleb_strict
¶
demangleb_strict(
mangled,
*,
language=None,
style=DEFAULT_STYLE,
limits=DEFAULT_LIMITS,
)
demangle_strict() over bytes, returning bytes. Raises what it raises.
Source code in src/demangle/api.py
813 814 815 816 817 818 819 820 821 822 823 | |
demangleb_type
¶
demangleb_type(
mangled,
*,
language,
style=DEFAULT_STYLE,
limits=DEFAULT_LIMITS,
)
demangle_type() over bytes, returning bytes.
A type encoding is read out of a binary as often as a symbol is -- an Itanium
typeinfo name sits in .rodata and an MSVC type descriptor's name in .rdata --
so it gets the same bytes form the rest of the package has. Raises what
demangle_type() raises.
Source code in src/demangle/api.py
837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 | |
parseb
¶
parseb(
mangled,
*,
language=None,
style=DEFAULT_STYLE,
limits=DEFAULT_LIMITS,
)
parse() over bytes. The tree it returns spells str, as every tree does.
Source code in src/demangle/api.py
826 827 828 829 830 831 832 833 834 | |
parseb_type
¶
parseb_type(
mangled,
*,
language,
style=DEFAULT_STYLE,
limits=DEFAULT_LIMITS,
)
parse_type() over bytes. The tree it returns spells str, as every tree does.
Source code in src/demangle/api.py
856 857 858 859 860 861 862 863 864 | |
detectb
¶
detectb(mangled, *, language=None, strict=False)
detect() over bytes. The scheme's name is a str; it is this package's own.
Source code in src/demangle/api.py
867 868 869 870 871 872 873 874 875 876 877 878 | |
cache_clear
¶
cache_clear()
Empty the module-level result cache.
Source code in src/demangle/api.py
946 947 948 | |
cache_stats
¶
cache_stats()
Hit rate and occupancy of the result cache.
A name no scheme is offered is looked up but never stored, so it counts as a miss on every call.
Source code in src/demangle/api.py
951 952 953 954 955 956 957 | |
A file, not a name¶
demangle.filter
¶
Demangling the symbols out of text that is not only symbols.
nm writes an address and a type letter before a name; a crash log writes a frame
number; an objdump listing writes a whole disassembly around one. So the useful operation
over a file is not "demangle this name" but "substitute every symbol-shaped word and
copy everything else through" -- which is what c++filt, demumble and rustfilt all
do, and what the demangle command does with no arguments.
rustc-demangle ships demangle_stream as a crate function rather than only inside
rustfilt, and this is the same thing, so a Python caller need neither shell out to the
command nor re-implement its tokeniser:
>>> import demangle
>>> demangle.demangle_text("0000000000001139 T _ZN3foo3barEv\n")
'0000000000001139 T foo::bar()\n'
find_symbols is the same scan without the substitution, for a caller who needs to know
where in a line a symbol was -- which is the question microsoftDemangle's n_read
out-parameter and llvm-undname --warn-trailing are asked in C.
Found
¶
Bases: NamedTuple
One symbol inside a longer string, and what it says.
find_symbols
¶
find_symbols(
text,
*,
language=None,
style=DEFAULT_STYLE,
limits=DEFAULT_LIMITS,
)
Yield every symbol in text that this library can read, in order.
Only the ones it can read: a word that looks symbol-shaped and does not demangle is not a symbol, and is left out rather than reported as one that spells itself.
The spans do not overlap and are in increasing order, so a caller can rebuild the
string around them -- which is all demangle_text does.
Source code in src/demangle/filter.py
84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 | |
demangle_text
¶
demangle_text(
text,
*,
language=None,
style=DEFAULT_STYLE,
limits=DEFAULT_LIMITS,
)
Substitute every symbol in text, copying everything else through unchanged.
Never raises over the text, for the reason demangle() does not: this is run over
whole files, and one unreadable word must not end the run. Like demangle(), it
raises ValueError when language or style is not a registered name, or limits
is not a Limits.
Source code in src/demangle/filter.py
126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 | |
demangle_stream
¶
demangle_stream(
fin,
fout,
*,
language=None,
style=DEFAULT_STYLE,
limits=DEFAULT_LIMITS,
)
demangle_text over a file, a line at a time.
A line at a time rather than all at once, so this stays a pipe: someone watching
nm ... | demangle should not have to wait for the input to end. fin is anything
iterable by lines and fout anything with write.
Encoding is the caller's: a symbol table holds bytes and they are not reliably UTF-8,
so open both ends with errors="surrogateescape" if the input is one. The demangle
command does exactly that.
Source code in src/demangle/filter.py
168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 | |
The parts of a name¶
The parts of a symbol, in one shape, for every scheme.
demangle() answers "what does this name say"; this answers "what are its pieces". A
disassembler labelling a call site wants the base name without its namespace; a
cross-reference wants the namespace without the base name; a signature matcher wants the
parameter types and nothing else. All three can be had by splitting the spelling, and
all three get it wrong the same way, because :: and , and ( occur inside template
arguments and inside function types as well as between the parts a caller means.
So the split is done on the tree the parser already built. Two shapes of tree, because the schemes come in two kinds:
- Itanium and MSVC build a declaration -- a function node with its name inside its own type, or a declarator standing beside one. The fields are read off it directly.
- Swift, D, Go, Nim, Free Pascal, Delphi, Ada, JNI and the pre-Itanium C++ schemes
(
gnuv2,codewarrior) build a node that is its own fragments, in output order:partsinterleaves the literal text the printer wrote with the subtrees it wrote between them. That interleaving is the structure -- a.inpartsseparates two components and a.inside a name does not -- so reading it gives the same answer the printer gave, which splitting the printer's output could not.
Rust's path and Objective-C's class and selector are read off their nodes as they stand, and an Objective-C runtime data symbol is split into the label it opens with and the class, category, instance variable or selector the label is about.
Text is the last resort, for the places where a tree has already flattened the answer, and even there with a reader that counts brackets and that declines a spelling which is a phrase rather than a name.
What each scheme can say still differs, and the difference is real rather than a gap to be filled in later:
- C++, in all four of its manglings here -- Itanium, MSVC, pre-Itanium g++ and its relatives, CodeWarrior -- encodes parameter types, the return type where the mangling writes one, cv- and ref-qualifiers, and for MSVC the calling convention and the declared access.
- Swift encodes the whole function type, so parameters, result and
throwsall come back. D, Free Pascal and Delphi encode their parameter types, Free Pascal a function's result, and Delphi the result of a template function and its calling convention; JNI encodes them for an overloaded method, the one form that needs them. - Rust, Go, Nim, Ada and Objective-C encode a path, or a class and a selector, and no
signature at all.
parametersisNonefor them, which is not the same as(): one says "the name does not carry this", the other says "it carries an empty list".
The Nones are the point. A field that guesses is worse than a field that declines.
Signature
dataclass
¶
Signature(
language,
demangled,
qualified_name,
base_name,
namespace,
parameters,
return_type,
calling_convention,
qualifiers=(),
special=None,
decoration="",
is_function=False,
is_data=False,
is_ctor_or_dtor=False,
)
What a mangled name says about the entity it names.
Every field is either what the name encodes or None/empty where it encodes
nothing. Nothing here is inferred from a spelling that does not say it.
The three name fields hold to one invariant, which is what makes them safe to
recombine: where namespace is not empty, joining it to base_name with the
scheme's separator gives qualified_name exactly. Template arguments therefore stay
on base_name -- sort<int*>, not sort -- because they are part of the component
they belong to.
language
instance-attribute
¶
language
The scheme that read the name: "itanium", "msvc", "rust" and so on.
demangled
instance-attribute
¶
demangled
The whole readable spelling, the same string demangle() returns.
qualified_name
instance-attribute
¶
qualified_name
The entity's name with its scope and without its type.
base_name
instance-attribute
¶
base_name
The last component of that. Never empty for a name that parsed.
namespace
instance-attribute
¶
namespace
Everything before the last component. Empty at the top level.
parameters
instance-attribute
¶
parameters
The parameter types, spelled, or None where the name encodes no parameter list.
() and None mean different things: an Itanium function taking no arguments has
(), and a Rust path has None because Rust does not put the signature in the
symbol. MSVC and pre-Itanium g++ spell an empty list (void), and it comes back as
they spell it: ('void',).
return_type
instance-attribute
¶
return_type
The return type, spelled, or None where the name does not encode one.
Most C++ functions do not: the ABI omits it, because overloads cannot differ by it. A template specialisation does. For a data symbol this is the type of the object.
calling_convention
instance-attribute
¶
calling_convention
__cdecl, __stdcall, ... . MSVC and Delphi; no other scheme writes one.
Delphi's __saveregs is kept with it, after the convention it modifies.
qualifiers
class-attribute
instance-attribute
¶
qualifiers = ()
What qualifies the declaration rather than its type.
The trailing cv- and ref-qualifiers of a member function -- const, &&,
noexcept -- for MSVC the leading access and storage words private, static,
virtual, and for Swift the effects a function declares: throws, async.
special
class-attribute
instance-attribute
¶
special = None
What the symbol is about, where it is about something rather than being it.
vtable for, typeinfo for, guard variable for, non-virtual thunk to; D's
initializer for; Swift's protocol requirements base descriptor for;
Objective-C's Objective-C class and instance variable offset for. The other
fields then describe the entity the symbol is about, so base_name on a
vtable for std::ostream is ostream, and on _OBJC_CLASS_$_NSData is NSData.
Not every scheme writes its label first. MSVC writes most of its labels after the
name, Base::`vftable', C::f`adjustor{16}', and Delphi writes
__linkproc__ between a unit and the procedure, System::__linkproc__ Abort; the
label is taken from wherever it sits, so qualified_name is Base, C::f,
System::Abort. The label comes without its quotes and without what follows it
about its own table: adjustor, not adjustor{16}; RTTI Base Class Descriptor,
without its at (0, -1, 0, 64); vftable, without its {for `A'}. A label with
nothing to be about stands as the name too: MSVC's `vector ctor iterator' has
special and qualified_name both vector ctor iterator. Only the last label is
taken: a thunk over a compiler-made member,
Base::`vector deleting dtor'`adjustor{4}', is adjustor, and the member it
adjusts, Base::`vector deleting dtor', is the name.
decoration
class-attribute
instance-attribute
¶
decoration = ''
What the symbol table appended, separator included: "@@GLIBCXX_3.4", ".cold".
is_function
class-attribute
instance-attribute
¶
is_function = False
Whether the name says it encodes a function.
False where the name does not say. A Rust or Go symbol is a path, and a path to a function reads exactly like a path to a static, so neither is claimed to be either.
is_data
class-attribute
instance-attribute
¶
is_data = False
Whether the name encodes an object rather than a function.
Of the symbols with a special label, MSVC's tables, descriptors and guards set
it. The others -- Itanium's vtable for and typeinfo for, Delphi's __tpdsc__,
Free Pascal's run-time type information for -- leave it False.
is_ctor_or_dtor
class-attribute
instance-attribute
¶
is_ctor_or_dtor = False
Whether it is a constructor or a destructor of the type it sits in.
C++ repeats the class's own name, or negates it; Swift writes init and deinit;
Delphi's $bctr and $bdtr spell C++'s TForm::TForm and TForm::~TForm, and its
`class constructor` and `class destructor` are structors of the class
too. No other scheme here marks the two, so no other scheme reports them. MSVC's
scalar deleting dtor, vector deleting dtor and vbase dtor are destructors as
Itanium's deleting destructor is, and so is a thunk to one,
Base::`vector deleting dtor'`adjustor{4}'; its closures and iterators are not.
signature
¶
signature(
mangled,
*,
language=None,
style=DEFAULT_STYLE,
limits=DEFAULT_LIMITS,
)
The parts of mangled.
Raises the same errors as demangle_strict: a name that cannot be read has no
parts, and a Signature full of None would say that it did and that they were
all empty.
Source code in src/demangle/_signature.py
207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 | |
signatureb
¶
signatureb(
mangled,
*,
language=None,
style=DEFAULT_STYLE,
limits=DEFAULT_LIMITS,
)
signature() over bytes. Its fields are str, as a tree's spellings are.
The form to use when the names come from a symbol table: an ELF or Mach-O string table holds bytes, and a tool that has read one should not have to guess an encoding to ask what a name's parts are.
Source code in src/demangle/_signature.py
227 228 229 230 231 232 233 234 235 236 237 238 239 240 | |
Errors¶
What the strict entry points raise when a name cannot be read. An argument that is
wrong rather than unreadable is a TypeError or a ValueError, as each function
says.
demangle.core.errors
¶
The exception hierarchy.
Two audiences need different things from a failure. A tool labelling every symbol in a
binary wants the name back and no exception, because most symbols are not mangled and
that is not an error. A tool parsing one specific name the user typed wants to be told
precisely what was wrong with it. The split between demangle() and parse() in the
public API is exactly this split, and these are the types the second half raises.
DemanglingError
¶
Bases: Exception
Base class for every failure this package reports.
NotMangledError
¶
NotMangledError(mangled, message='not a mangled name')
Bases: DemanglingError
The name carries no recognised mangling prefix.
Distinct from ParseError: this is "not addressed to us", not "malformed". A plain
C symbol like memcpy is not an error, it is simply not a mangled name.
Source code in src/demangle/core/errors.py
32 33 34 | |
ParseError
¶
ParseError(
mangled, position=None, message="malformed mangled name"
)
Bases: DemanglingError
The name carries a known prefix but does not follow the grammar.
Records where the parser gave up, which is the difference between a usable bug report and a shrug.
Source code in src/demangle/core/errors.py
44 45 46 47 48 | |
TruncatedError
¶
TruncatedError(mangled, position=None)
Bases: ParseError
The name ended in the middle of a production.
Common enough in real symbol tables -- names get cut by fixed-width fields and by tools that assume a length limit -- to be worth telling apart from other malformations.
Source code in src/demangle/core/errors.py
58 59 | |
LimitExceeded
¶
LimitExceeded(mangled, limit_name, limit_value)
Bases: ParseError
A bound protecting against pathological input was hit.
A mangled name is attacker-controlled input in any tool that opens files it did not produce. Recursion depth, output size and substitution count are all capped, and hitting a cap is reported rather than being allowed to exhaust the process.
Source code in src/demangle/core/errors.py
70 71 72 73 | |
Limits¶
demangle.core.limits
¶
Bounds on what a parser will do before giving up.
Every one of these exists because a mangled name is untrusted input. Symbol tables are read from files the tool did not write, and the schemes are recursive: a few dozen bytes can describe a type nested deeply enough to exhaust the C stack, or one whose spelling is larger than memory. The defaults are set well above anything a real compiler emits.
DEFAULT_LIMITS
module-attribute
¶
DEFAULT_LIMITS = Limits()
The bounds every entry point uses unless it is passed others.
RELAXED_LIMITS
module-attribute
¶
RELAXED_LIMITS = Limits(
max_depth=2048,
max_output=1 << 22,
max_substitutions=1 << 16,
max_input=1 << 22,
)
Bounds for callers that trust their input and want the ceiling out of the way.
Still finite: "trusted" is a statement about intent, not about correctness.
max_depth here is not reachable at the interpreter's default recursion limit -- see
the note on Limits.max_depth. It is left high on purpose: it says what this package
will allow, and a caller who raises sys.setrecursionlimit() gets it.
Limits
dataclass
¶
Limits(
max_depth=256,
max_output=1 << 16,
max_substitutions=8192,
max_input=1 << 16,
)
Resource bounds for one parse.
Frozen so a limit set can be shared between threads and cached without defensive copying.
max_depth
class-attribute
instance-attribute
¶
max_depth = 256
Nesting depth of recursive productions. Real names rarely pass 20.
At the defaults this is the bound that decides: an Itanium name nested past it stops
with LimitExceeded naming max_depth, whatever the shape. The interpreter's
recursion limit is a second, independent ceiling. A production costs several Python
frames, so at the default limit of 1000 it binds only when this number is raised,
at roughly 140 levels of nested template, 164 of function type and 494 of pointer,
or when the caller's stack is already deep. It is reported the same way, as
LimitExceeded("recursion depth") naming this bound, and it is then the caller's
bound in fact. sys.setrecursionlimit() moves it.
max_output
class-attribute
instance-attribute
¶
max_output = 1 << 16
Characters of rendered output. Template expansion can amplify enormously.
max_substitutions
class-attribute
instance-attribute
¶
max_substitutions = 8192
Entries in the back-reference dictionary.
max_input
class-attribute
instance-attribute
¶
max_input = 1 << 16
Characters of mangled input considered at all.
Documented elsewhere¶
NodeandDecorated: the tree.Styleandregister_style: styles.LanguagePluginandregister_language, which isdemangle.core.registry.register: plugins.- The options objects
style()takes, one per scheme that has any:ItaniumOptions,MsvcOptions,RustOptions,SwiftOptions,GnuV2OptionsandCodeWarriorOptions.