demangle 0.3.0__tar.gz
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-0.3.0/.gitignore +20 -0
- demangle-0.3.0/ARCHITECTURE.md +210 -0
- demangle-0.3.0/CHANGELOG.md +3691 -0
- demangle-0.3.0/CONFORMANCE.md +499 -0
- demangle-0.3.0/CONTRIBUTING.md +157 -0
- demangle-0.3.0/LICENSE +21 -0
- demangle-0.3.0/NOTICE +178 -0
- demangle-0.3.0/PKG-INFO +382 -0
- demangle-0.3.0/README.md +356 -0
- demangle-0.3.0/ROADMAP.md +1064 -0
- demangle-0.3.0/SECURITY.md +75 -0
- demangle-0.3.0/benchmarks/baseline.json +38 -0
- demangle-0.3.0/benchmarks/bench.py +433 -0
- demangle-0.3.0/docs/ARCHITECTURE.md +1 -0
- demangle-0.3.0/docs/CHANGELOG.md +1 -0
- demangle-0.3.0/docs/CONFORMANCE.md +1 -0
- demangle-0.3.0/docs/CONTRIBUTING.md +1 -0
- demangle-0.3.0/docs/ROADMAP.md +1 -0
- demangle-0.3.0/docs/SECURITY.md +1 -0
- demangle-0.3.0/docs/adding-a-scheme.md +122 -0
- demangle-0.3.0/docs/analysing-a-binary.md +166 -0
- demangle-0.3.0/docs/index.md +1 -0
- demangle-0.3.0/docs/reference/api.md +204 -0
- demangle-0.3.0/docs/reference/core.md +99 -0
- demangle-0.3.0/docs/reference/schemes.md +302 -0
- demangle-0.3.0/docs/specs/itanium-grammar.txt +289 -0
- demangle-0.3.0/docs/testing.md +212 -0
- demangle-0.3.0/mkdocs.yml +96 -0
- demangle-0.3.0/pyproject.toml +181 -0
- demangle-0.3.0/src/demangle/__init__.py +103 -0
- demangle-0.3.0/src/demangle/_signature.py +623 -0
- demangle-0.3.0/src/demangle/api.py +749 -0
- demangle-0.3.0/src/demangle/cli.py +521 -0
- demangle-0.3.0/src/demangle/core/__init__.py +20 -0
- demangle-0.3.0/src/demangle/core/ast.py +899 -0
- demangle-0.3.0/src/demangle/core/builder.py +160 -0
- demangle-0.3.0/src/demangle/core/cache.py +70 -0
- demangle-0.3.0/src/demangle/core/decorations.py +73 -0
- demangle-0.3.0/src/demangle/core/errors.py +75 -0
- demangle-0.3.0/src/demangle/core/limits.py +50 -0
- demangle-0.3.0/src/demangle/core/plugin.py +98 -0
- demangle-0.3.0/src/demangle/core/reader.py +247 -0
- demangle-0.3.0/src/demangle/core/registry.py +221 -0
- demangle-0.3.0/src/demangle/core/spelling.py +470 -0
- demangle-0.3.0/src/demangle/core/style.py +192 -0
- demangle-0.3.0/src/demangle/filter.py +226 -0
- demangle-0.3.0/src/demangle/py.typed +0 -0
- demangle-0.3.0/src/demangle/schemes/__init__.py +7 -0
- demangle-0.3.0/src/demangle/schemes/ada/__init__.py +175 -0
- demangle-0.3.0/src/demangle/schemes/ada/_parser.py +313 -0
- demangle-0.3.0/src/demangle/schemes/ada/nodes.py +89 -0
- demangle-0.3.0/src/demangle/schemes/codewarrior/__init__.py +215 -0
- demangle-0.3.0/src/demangle/schemes/codewarrior/_parser.py +583 -0
- demangle-0.3.0/src/demangle/schemes/codewarrior/nodes.py +131 -0
- demangle-0.3.0/src/demangle/schemes/codewarrior/options.py +33 -0
- demangle-0.3.0/src/demangle/schemes/d/__init__.py +118 -0
- demangle-0.3.0/src/demangle/schemes/d/_parser.py +1616 -0
- demangle-0.3.0/src/demangle/schemes/d/nodes.py +93 -0
- demangle-0.3.0/src/demangle/schemes/delphi/__init__.py +79 -0
- demangle-0.3.0/src/demangle/schemes/delphi/_parser.py +897 -0
- demangle-0.3.0/src/demangle/schemes/delphi/nodes.py +128 -0
- demangle-0.3.0/src/demangle/schemes/gnuv2/__init__.py +325 -0
- demangle-0.3.0/src/demangle/schemes/gnuv2/_parser.py +2487 -0
- demangle-0.3.0/src/demangle/schemes/gnuv2/nodes.py +145 -0
- demangle-0.3.0/src/demangle/schemes/gnuv2/options.py +54 -0
- demangle-0.3.0/src/demangle/schemes/go/__init__.py +127 -0
- demangle-0.3.0/src/demangle/schemes/go/_parser.py +278 -0
- demangle-0.3.0/src/demangle/schemes/go/nodes.py +138 -0
- demangle-0.3.0/src/demangle/schemes/itanium/__init__.py +61 -0
- demangle-0.3.0/src/demangle/schemes/itanium/options.py +332 -0
- demangle-0.3.0/src/demangle/schemes/itanium/parser.py +5456 -0
- demangle-0.3.0/src/demangle/schemes/itanium/substitutions.py +362 -0
- demangle-0.3.0/src/demangle/schemes/itanium/tables.py +376 -0
- demangle-0.3.0/src/demangle/schemes/jni/__init__.py +96 -0
- demangle-0.3.0/src/demangle/schemes/jni/_parser.py +226 -0
- demangle-0.3.0/src/demangle/schemes/jni/nodes.py +91 -0
- demangle-0.3.0/src/demangle/schemes/msvc/__init__.py +291 -0
- demangle-0.3.0/src/demangle/schemes/msvc/_parser.py +2024 -0
- demangle-0.3.0/src/demangle/schemes/msvc/nodes.py +450 -0
- demangle-0.3.0/src/demangle/schemes/msvc/options.py +140 -0
- demangle-0.3.0/src/demangle/schemes/nim/__init__.py +81 -0
- demangle-0.3.0/src/demangle/schemes/nim/_parser.py +470 -0
- demangle-0.3.0/src/demangle/schemes/nim/nodes.py +81 -0
- demangle-0.3.0/src/demangle/schemes/objc/__init__.py +119 -0
- demangle-0.3.0/src/demangle/schemes/objc/_parser.py +823 -0
- demangle-0.3.0/src/demangle/schemes/objc/nodes.py +122 -0
- demangle-0.3.0/src/demangle/schemes/pascal/__init__.py +85 -0
- demangle-0.3.0/src/demangle/schemes/pascal/_parser.py +428 -0
- demangle-0.3.0/src/demangle/schemes/pascal/nodes.py +131 -0
- demangle-0.3.0/src/demangle/schemes/rust/__init__.py +244 -0
- demangle-0.3.0/src/demangle/schemes/rust/_dispatch.py +85 -0
- demangle-0.3.0/src/demangle/schemes/rust/_legacy.py +266 -0
- demangle-0.3.0/src/demangle/schemes/rust/_v0.py +1867 -0
- demangle-0.3.0/src/demangle/schemes/rust/nodes.py +194 -0
- demangle-0.3.0/src/demangle/schemes/rust/options.py +36 -0
- demangle-0.3.0/src/demangle/schemes/swift/__init__.py +247 -0
- demangle-0.3.0/src/demangle/schemes/swift/_demangler.py +3224 -0
- demangle-0.3.0/src/demangle/schemes/swift/_node.py +644 -0
- demangle-0.3.0/src/demangle/schemes/swift/_old_demangler.py +1525 -0
- demangle-0.3.0/src/demangle/schemes/swift/_printer.py +2737 -0
- demangle-0.3.0/src/demangle/schemes/swift/_punycode.py +116 -0
- demangle-0.3.0/src/demangle/schemes/swift/nodes.py +143 -0
- demangle-0.3.0/src/demangle/schemes/swift/options.py +155 -0
- demangle-0.3.0/src/demangle/schemes/swift/resolve.py +577 -0
- demangle-0.3.0/src/demangle/schemes/swift/symbolic.py +201 -0
- demangle-0.3.0/tests/__init__.py +0 -0
- demangle-0.3.0/tests/conformance/ada-libiberty.txt +45 -0
- demangle-0.3.0/tests/conformance/ada-real-world.txt +1449 -0
- demangle-0.3.0/tests/conformance/codewarrior-cwdemangle.txt +60 -0
- demangle-0.3.0/tests/conformance/d-libiberty.txt +382 -0
- demangle-0.3.0/tests/conformance/d-real-world.txt +1268 -0
- demangle-0.3.0/tests/conformance/delphi-constructs.txt +93 -0
- demangle-0.3.0/tests/conformance/delphi-real-world.txt +79 -0
- demangle-0.3.0/tests/conformance/delphi-refusals.txt +28 -0
- demangle-0.3.0/tests/conformance/delphi-tdump.txt +11374 -0
- demangle-0.3.0/tests/conformance/gnuv2-libiberty.txt +671 -0
- demangle-0.3.0/tests/conformance/gnuv2-real-world.txt +12674 -0
- demangle-0.3.0/tests/conformance/go-real-world.txt +1532 -0
- demangle-0.3.0/tests/conformance/itanium-libcxxabi.txt.gz +0 -0
- demangle-0.3.0/tests/conformance/itanium-libstdcxx.txt +5923 -0
- demangle-0.3.0/tests/conformance/itanium-real-world-gnu.txt +325 -0
- demangle-0.3.0/tests/conformance/itanium-real-world.txt +332 -0
- demangle-0.3.0/tests/conformance/itanium-reference-defects.txt +273 -0
- demangle-0.3.0/tests/conformance/itanium-regressions.txt +43 -0
- demangle-0.3.0/tests/conformance/itanium-types-llvm.txt +1079 -0
- demangle-0.3.0/tests/conformance/itanium-types.txt +1079 -0
- demangle-0.3.0/tests/conformance/jni-real-world.txt +59 -0
- demangle-0.3.0/tests/conformance/msvc-arm64ec.txt +615 -0
- demangle-0.3.0/tests/conformance/msvc-boost.txt +5854 -0
- demangle-0.3.0/tests/conformance/msvc-clang.txt +174 -0
- demangle-0.3.0/tests/conformance/msvc-dbghelp.txt +1084 -0
- demangle-0.3.0/tests/conformance/msvc-llvm-corpus.txt +629 -0
- demangle-0.3.0/tests/conformance/msvc-name-only.txt +498 -0
- demangle-0.3.0/tests/conformance/msvc-reference-defects.txt +56 -0
- demangle-0.3.0/tests/conformance/msvc-suppressions.txt +1258 -0
- demangle-0.3.0/tests/conformance/msvc-type-descriptors.txt +111 -0
- demangle-0.3.0/tests/conformance/nim-lossy.txt +17 -0
- demangle-0.3.0/tests/conformance/nim-real-world.txt +2123 -0
- demangle-0.3.0/tests/conformance/objc-lossy.txt +34 -0
- demangle-0.3.0/tests/conformance/objc-real-world.txt +2675 -0
- demangle-0.3.0/tests/conformance/objc-refusals.txt +116 -0
- demangle-0.3.0/tests/conformance/pascal-real-world.txt +3907 -0
- demangle-0.3.0/tests/conformance/pascal-refusals.txt +759 -0
- demangle-0.3.0/tests/conformance/rust-real-world.txt +5329 -0
- demangle-0.3.0/tests/conformance/rust-toolchain.txt +404 -0
- demangle-0.3.0/tests/conformance/rustc-upstream.txt +59 -0
- demangle-0.3.0/tests/conformance/swift-real-world.txt +8500 -0
- demangle-0.3.0/tests/conformance/swift-reference-defects.txt +40 -0
- demangle-0.3.0/tests/conformance/swift-refusals.txt +74 -0
- demangle-0.3.0/tests/conformance/swift-simplified.txt +224 -0
- demangle-0.3.0/tests/conformance/swift-symbolic.txt +279 -0
- demangle-0.3.0/tests/conformance/swift-upstream.txt +520 -0
- demangle-0.3.0/tests/conftest.py +67 -0
- demangle-0.3.0/tests/corpus/msvc.txt +620 -0
- demangle-0.3.0/tests/test_ada.py +320 -0
- demangle-0.3.0/tests/test_address_of.py +133 -0
- demangle-0.3.0/tests/test_api.py +347 -0
- demangle-0.3.0/tests/test_api_surface.py +473 -0
- demangle-0.3.0/tests/test_architecture.py +445 -0
- demangle-0.3.0/tests/test_cli.py +576 -0
- demangle-0.3.0/tests/test_codewarrior.py +286 -0
- demangle-0.3.0/tests/test_concurrency.py +207 -0
- demangle-0.3.0/tests/test_conformance.py +834 -0
- demangle-0.3.0/tests/test_core.py +366 -0
- demangle-0.3.0/tests/test_d.py +1391 -0
- demangle-0.3.0/tests/test_delphi.py +321 -0
- demangle-0.3.0/tests/test_docs.py +243 -0
- demangle-0.3.0/tests/test_expressions.py +2241 -0
- demangle-0.3.0/tests/test_gnu_expressions.py +963 -0
- demangle-0.3.0/tests/test_gnuv2.py +585 -0
- demangle-0.3.0/tests/test_go.py +379 -0
- demangle-0.3.0/tests/test_itanium_apple_auto.py +126 -0
- demangle-0.3.0/tests/test_itanium_closure_prefix.py +270 -0
- demangle-0.3.0/tests/test_jni.py +262 -0
- demangle-0.3.0/tests/test_limits.py +397 -0
- demangle-0.3.0/tests/test_local_names.py +173 -0
- demangle-0.3.0/tests/test_msvc.py +1776 -0
- demangle-0.3.0/tests/test_msvc_arm64ec.py +102 -0
- demangle-0.3.0/tests/test_msvc_ast.py +162 -0
- demangle-0.3.0/tests/test_msvc_descriptors.py +186 -0
- demangle-0.3.0/tests/test_msvc_options.py +483 -0
- demangle-0.3.0/tests/test_nim.py +323 -0
- demangle-0.3.0/tests/test_objc.py +290 -0
- demangle-0.3.0/tests/test_parity.py +168 -0
- demangle-0.3.0/tests/test_pascal.py +340 -0
- demangle-0.3.0/tests/test_readme.py +233 -0
- demangle-0.3.0/tests/test_robustness.py +533 -0
- demangle-0.3.0/tests/test_rust.py +368 -0
- demangle-0.3.0/tests/test_rust_ast.py +149 -0
- demangle-0.3.0/tests/test_rust_consts.py +241 -0
- demangle-0.3.0/tests/test_signature.py +446 -0
- demangle-0.3.0/tests/test_special_names.py +310 -0
- demangle-0.3.0/tests/test_substitution_parameters.py +373 -0
- demangle-0.3.0/tests/test_swift.py +803 -0
- demangle-0.3.0/tests/test_swift_simplified.py +172 -0
- demangle-0.3.0/tests/test_swift_symbolic.py +443 -0
- demangle-0.3.0/tests/test_types.py +1055 -0
- demangle-0.3.0/tools/corpus_sources/features.cpp +96 -0
- demangle-0.3.0/tools/corpus_sources/go/go.mod +3 -0
- demangle-0.3.0/tools/corpus_sources/go/main.go +18 -0
- demangle-0.3.0/tools/corpus_sources/go/plain/lib.go +3 -0
- demangle-0.3.0/tools/corpus_sources/go/v2.5/lib.go +37 -0
- demangle-0.3.0/tools/corpus_sources/go/weird.pkg.name/lib.go +5 -0
- demangle-0.3.0/tools/corpus_sources/modern.cpp +153 -0
- demangle-0.3.0/tools/corpus_sources/modern23.cpp +75 -0
- demangle-0.3.0/tools/corpus_sources/msvc/modern.cpp +158 -0
- demangle-0.3.0/tools/corpus_sources/msvc/msvc.cpp +235 -0
- demangle-0.3.0/tools/corpus_sources/reference_defects/README.md +37 -0
- demangle-0.3.0/tools/corpus_sources/reference_defects/auto_marshall.cpp +39 -0
- demangle-0.3.0/tools/corpus_sources/reference_defects/closure_prefix.cpp +18 -0
- demangle-0.3.0/tools/corpus_sources/reference_defects/division.cpp +4 -0
- demangle-0.3.0/tools/corpus_sources/reference_defects/generic_lambda.cpp +10 -0
- demangle-0.3.0/tools/corpus_sources/reference_defects/inheriting_constructor.cpp +27 -0
- demangle-0.3.0/tools/corpus_sources/reference_defects/insort.cpp +28 -0
- demangle-0.3.0/tools/corpus_sources/reference_defects/insort_composite.cpp +31 -0
- demangle-0.3.0/tools/corpus_sources/reference_defects/member_template_lambda.cpp +29 -0
- demangle-0.3.0/tools/corpus_sources/reference_defects/prepare_execution.cpp +27 -0
- demangle-0.3.0/tools/corpus_sources/reference_defects/template_template_param.cpp +25 -0
- demangle-0.3.0/tools/corpus_sources/reference_defects/value_init_new.cpp +4 -0
- demangle-0.3.0/tools/corpus_sources/rust/consts.rs +41 -0
- demangle-0.3.0/tools/corpus_sources/rust/features.rs +562 -0
- demangle-0.3.0/tools/corpus_sources/rust/library.rs +322 -0
- demangle-0.3.0/tools/corpus_sources/rust/types.rs +200 -0
- demangle-0.3.0/tools/cplus-dem-reference/README.md +79 -0
- demangle-0.3.0/tools/cplus-dem-reference/SHA256SUMS +17 -0
- demangle-0.3.0/tools/cplus-dem-reference/build.sh +57 -0
- demangle-0.3.0/tools/cplus-dem-reference/main.c +54 -0
- demangle-0.3.0/tools/differential.py +453 -0
- demangle-0.3.0/tools/enumerate.py +1576 -0
- demangle-0.3.0/tools/generate_ada_corpus.py +115 -0
- demangle-0.3.0/tools/generate_corpus.py +192 -0
- demangle-0.3.0/tools/generate_d_corpus.py +91 -0
- demangle-0.3.0/tools/generate_delphi_corpus.py +256 -0
- demangle-0.3.0/tools/generate_go_corpus.py +177 -0
- demangle-0.3.0/tools/generate_msvc_dbghelp_corpus.py +594 -0
- demangle-0.3.0/tools/generate_rust_corpus.py +173 -0
- demangle-0.3.0/tools/invariants.py +169 -0
- demangle-0.3.0/tools/mutate.py +484 -0
- demangle-0.3.0/tools/probe_substitutions.py +53 -0
- demangle-0.3.0/tools/rustc-demangle-reference/Cargo.lock +16 -0
- demangle-0.3.0/tools/rustc-demangle-reference/Cargo.toml +11 -0
- demangle-0.3.0/tools/rustc-demangle-reference/README.md +16 -0
- demangle-0.3.0/tools/rustc-demangle-reference/src/main.rs +32 -0
- demangle-0.3.0/tools/sweep_rust_chars.py +113 -0
- demangle-0.3.0/tools/swift-demangle-reference/README.md +102 -0
- demangle-0.3.0/tools/swift-demangle-reference/build.sh +74 -0
- demangle-0.3.0/tools/swift-demangle-reference/main.cpp +59 -0
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
__pycache__/
|
|
2
|
+
*.py[cod]
|
|
3
|
+
*.egg-info/
|
|
4
|
+
.eggs/
|
|
5
|
+
build/
|
|
6
|
+
dist/
|
|
7
|
+
.venv/
|
|
8
|
+
venv/
|
|
9
|
+
.pytest_cache/
|
|
10
|
+
.ruff_cache/
|
|
11
|
+
.ty_cache/
|
|
12
|
+
site/
|
|
13
|
+
.coverage
|
|
14
|
+
htmlcov/
|
|
15
|
+
*.o
|
|
16
|
+
tools/rustc-demangle-reference/target/
|
|
17
|
+
tools/swift-demangle-reference/swift/
|
|
18
|
+
tools/swift-demangle-reference/build/
|
|
19
|
+
tools/cplus-dem-reference/gcc/
|
|
20
|
+
tools/cplus-dem-reference/build/
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
# Architecture
|
|
2
|
+
|
|
3
|
+
`demangle` turns a mangled symbol name back into the declaration a programmer wrote.
|
|
4
|
+
It supports several unrelated mangling schemes, is written in pure Python with no
|
|
5
|
+
dependencies, and is meant to be embedded in tools that process millions of symbols.
|
|
6
|
+
|
|
7
|
+
Those three facts pull in different directions, and the architecture exists to resolve
|
|
8
|
+
them.
|
|
9
|
+
|
|
10
|
+
## The central problem
|
|
11
|
+
|
|
12
|
+
A demangler has two plausible shapes and each is wrong on its own.
|
|
13
|
+
|
|
14
|
+
**Shape one: parse straight to a string.** Fast, small, and what nearly every existing
|
|
15
|
+
demangler does. But the caller gets an opaque string. A tool that wants the namespace,
|
|
16
|
+
the template arguments, or the parameter types has to re-parse the output with a
|
|
17
|
+
regular expression -- and C++ declaration syntax is not a language you can pull apart
|
|
18
|
+
with regular expressions.
|
|
19
|
+
|
|
20
|
+
**Shape two: parse to an abstract syntax tree.** Structured and inspectable, but it
|
|
21
|
+
allocates an object per grammar node. In Python that cost is not theoretical: on a
|
|
22
|
+
binary with 400,000 symbols it is the difference between seconds and minutes, and most
|
|
23
|
+
callers only ever wanted the string.
|
|
24
|
+
|
|
25
|
+
## The resolution: parsers write to a builder
|
|
26
|
+
|
|
27
|
+
No parser in this codebase constructs its own output. Each one is written against the
|
|
28
|
+
`Builder` protocol in `core/builder.py` and calls methods like `builder.pointer(inner)`
|
|
29
|
+
or `builder.template(name, args)` as it recognises grammar productions. What those
|
|
30
|
+
calls *produce* is the builder's business, not the parser's.
|
|
31
|
+
|
|
32
|
+
Two builders ship:
|
|
33
|
+
|
|
34
|
+
| Builder | Produces | Use |
|
|
35
|
+
|---|---|---|
|
|
36
|
+
| `SpellingBuilder` | `Spelling` pairs, immediately concatenable | `demangle()` -- the hot path |
|
|
37
|
+
| `AstBuilder` | `Node` trees | `parse()` -- structured access |
|
|
38
|
+
|
|
39
|
+
The parser is written once and stays honest about the grammar; the cost model is chosen
|
|
40
|
+
by the caller. Adding a third backend -- emitting JSON, or a token stream for a
|
|
41
|
+
syntax highlighter -- means writing one class and touching no parser.
|
|
42
|
+
|
|
43
|
+
This is the single most important thing to understand about the codebase. A change that
|
|
44
|
+
makes a parser build strings directly, however locally convenient, breaks it.
|
|
45
|
+
|
|
46
|
+
### Expressions
|
|
47
|
+
|
|
48
|
+
Expressions were the last place the rule did not hold: the parser assembled
|
|
49
|
+
`f"sizeof ({...})"` itself, so an expression inside a type reached the tree as one opaque
|
|
50
|
+
node. They now go through `Builder.expression(form, parts)`, where `parts` interleaves the
|
|
51
|
+
production's fixed text with its operands' handles and `form` names the shape — `binary`,
|
|
52
|
+
`conditional`, `call`, `sizeof`.
|
|
53
|
+
|
|
54
|
+
One method rather than one per operator. Fifteen methods would be fifteen things every
|
|
55
|
+
future builder must implement, and would still not cover the next operator someone
|
|
56
|
+
mangles. The parser already owns operator *spelling* — it comes from `tables.py`, which
|
|
57
|
+
exists to be checked against the ABI — so what is left for the builder to decide is
|
|
58
|
+
structure, and `form` plus operands is that.
|
|
59
|
+
|
|
60
|
+
Brackets are parts like any other. Whether an operand needs them is a precedence question
|
|
61
|
+
only the parser can answer, so it arrives settled; a consumer walking the tree sees a
|
|
62
|
+
`paren` expression wrapping the operand rather than punctuation glued into a string.
|
|
63
|
+
|
|
64
|
+
Rust reaches the same place by a different route, and the difference is worth knowing.
|
|
65
|
+
Its printer emits one linear stream of fragments and a *sink* decides what to do with
|
|
66
|
+
them: `TextSink` concatenates, `TreeSink` remembers where each production began and
|
|
67
|
+
ended. So the tree is not a second traversal that has to be kept in step with the text —
|
|
68
|
+
it is the same traversal with its boundaries kept, and a tree renders to what
|
|
69
|
+
`demangle()` returns by construction rather than by test. Rust can do this because its
|
|
70
|
+
spelling is strictly left to right; a C-family declarator, which wraps the name it
|
|
71
|
+
declares, cannot be recovered from a linear stream and needs the builder proper.
|
|
72
|
+
|
|
73
|
+
### When a scheme needs its own spelling
|
|
74
|
+
|
|
75
|
+
`core/spelling.py` implements *C-family* declarator placement. A scheme whose output
|
|
76
|
+
looks like a C declaration uses it and gets the hard part for free. A scheme whose output
|
|
77
|
+
does not — MSVC, where the calling convention sits inside the parentheses and the spacing
|
|
78
|
+
rules differ — supplies its own node kinds and renderer instead, subclassing `core.ast.Node`
|
|
79
|
+
so `walk()`, `find()` and `spell()` keep working. `AstBuilder` is shared regardless.
|
|
80
|
+
|
|
81
|
+
That is the extension point, not a workaround: forcing every scheme through one
|
|
82
|
+
renderer would mean MSVC-shaped branches inside `core`.
|
|
83
|
+
|
|
84
|
+
## Layout
|
|
85
|
+
|
|
86
|
+
```
|
|
87
|
+
demangle/
|
|
88
|
+
api.py demangle(), parse(), detect() -- the public surface
|
|
89
|
+
_signature.py signature(): the parts of a name rather than its spelling
|
|
90
|
+
filter.py demangling the symbols out of text that is not only symbols
|
|
91
|
+
cli.py the `demangle` command
|
|
92
|
+
core/
|
|
93
|
+
reader.py a bounds-checked cursor; the only input primitive parsers use
|
|
94
|
+
builder.py the Builder protocol -- the contract between parser and output
|
|
95
|
+
spelling.py SpellingBuilder: C++ declarator placement, the fast path
|
|
96
|
+
ast.py AstBuilder and the Node hierarchy
|
|
97
|
+
errors.py the exception hierarchy
|
|
98
|
+
limits.py the bounds a parser reads before it recurses or emits
|
|
99
|
+
style.py named styles, and composing one for a single call
|
|
100
|
+
decorations.py what a symbol table adds around a name, which belongs to no scheme
|
|
101
|
+
plugin.py LanguagePlugin: the contract a scheme implements
|
|
102
|
+
cache.py bounded memoisation
|
|
103
|
+
registry.py language discovery, including third-party plugins
|
|
104
|
+
schemes/
|
|
105
|
+
itanium/ the Itanium C++ ABI (GCC, Clang, and everything that follows them)
|
|
106
|
+
msvc/ Microsoft's scheme, as UnDecorateSymbolName reverses it
|
|
107
|
+
rust/ Rust legacy (_ZN) and v0 (_R)
|
|
108
|
+
swift/ Swift, both the current mangling and Swift 3's
|
|
109
|
+
d/ D, as GNU binutils reverses it
|
|
110
|
+
go/ Go package paths and receivers
|
|
111
|
+
nim/ Nim, whose symbols are ordinary C identifiers
|
|
112
|
+
objc/ Objective-C
|
|
113
|
+
pascal/ Free Pascal
|
|
114
|
+
delphi/ Borland/Embarcadero Delphi and C++Builder
|
|
115
|
+
gnuv2/ pre-Itanium C++: g++ before 3.0, cfront/ARM, Lucid, HP aCC, EDG
|
|
116
|
+
codewarrior/ Metrowerks CodeWarrior, the other pre-Itanium C++ mangling
|
|
117
|
+
ada/ Ada, as GNAT encodes it
|
|
118
|
+
jni/ the C function a Java `native` method is called through
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
`core` never imports from `schemes`; `schemes/*` never import from each other. Both are
|
|
122
|
+
enforced by a test — including a test that the enforcement itself fires on a constructed
|
|
123
|
+
violation, because the first version of the rule had a hole in it and looked fine.
|
|
124
|
+
|
|
125
|
+
One exception, and it is in the test: `core/style.py` names the built-in option objects
|
|
126
|
+
inside a function body, so the import is lazy and cycle-free.
|
|
127
|
+
|
|
128
|
+
## Why declarator placement is its own module
|
|
129
|
+
|
|
130
|
+
C++ does not spell a type before the name; it spells it *around* the name. The type
|
|
131
|
+
"pointer to function taking char, returning int" is written `int (*)(char)`, and a
|
|
132
|
+
variable of that type is `int (*f)(char)` -- the name lands in a hole in the middle.
|
|
133
|
+
|
|
134
|
+
So a type under construction is not a string but a pair of strings, `left` and `right`,
|
|
135
|
+
and rendering is `left + declarator + right`. Every constructor in `core/spelling.py`
|
|
136
|
+
exists to keep that hole in the correct place through pointers, arrays, references and
|
|
137
|
+
nested function types. It is the part of a demangler that is most often subtly wrong,
|
|
138
|
+
so it is isolated, independently testable, and shared by every scheme whose output looks
|
|
139
|
+
like a C declaration.
|
|
140
|
+
|
|
141
|
+
## Substitutions are load-bearing, not an optimisation
|
|
142
|
+
|
|
143
|
+
The Itanium scheme compresses repeated components into back-references: `S_`, `S0_`,
|
|
144
|
+
`S1_`. The numbering is implicit -- it depends entirely on which components the
|
|
145
|
+
*encoder* considered substitutable, and in what order. Add one entry the specification
|
|
146
|
+
does not, or miss one it does, and every later back-reference in the name resolves to
|
|
147
|
+
the wrong component.
|
|
148
|
+
|
|
149
|
+
The rules are not folklore; they are ABI section 5.1.10, and `schemes/itanium/
|
|
150
|
+
substitutions.py` implements them as a separate, directly testable component with the
|
|
151
|
+
specification quoted at each decision. Rust v0 has its own back-reference scheme with
|
|
152
|
+
different rules, kept in its own module for the same reason.
|
|
153
|
+
|
|
154
|
+
## Adding a language
|
|
155
|
+
|
|
156
|
+
A language is a module under `schemes/` exposing a `LanguagePlugin`. Nothing in `core`
|
|
157
|
+
knows the list of schemes at import time; `core/registry.py` finds built-ins lazily and third-party
|
|
158
|
+
plugins through the `demangle.languages` entry-point group, so a separate distribution
|
|
159
|
+
can add a language without a patch to this one.
|
|
160
|
+
|
|
161
|
+
A plugin declares:
|
|
162
|
+
|
|
163
|
+
- `name` -- the identifier used in the API and on the command line
|
|
164
|
+
- `detect(name)` -- a cheap, allocation-free test for "is this plausibly mine?"
|
|
165
|
+
- `parse(name, builder)` -- the parser, written against the builder protocol
|
|
166
|
+
|
|
167
|
+
`detect` must be cheap because `demangle()` on an unknown name calls every registered
|
|
168
|
+
`detect` before giving up, and that path runs on every non-mangled symbol in a binary --
|
|
169
|
+
which, in a typical binary, is most of them.
|
|
170
|
+
|
|
171
|
+
## Performance
|
|
172
|
+
|
|
173
|
+
Design rules, in the order they matter:
|
|
174
|
+
|
|
175
|
+
1. **The common call must not allocate an AST.** Hence the builder protocol.
|
|
176
|
+
2. **Detection precedes parsing.** A one-character prefix test rejects the majority of
|
|
177
|
+
real symbols before any parser starts.
|
|
178
|
+
3. **Results are memoised.** Symbol tables repeat names heavily -- the same
|
|
179
|
+
`std::allocator<char>` appears thousands of times in one binary. Caches are bounded
|
|
180
|
+
so a long-running process cannot grow without limit.
|
|
181
|
+
4. **`__slots__` on every hot class.** Node and Spelling instances are created in the
|
|
182
|
+
millions.
|
|
183
|
+
5. **No regular expressions in a parse loop.** The parsers are character dispatch.
|
|
184
|
+
|
|
185
|
+
`benchmarks/` measures all of this against real symbol corpora, and the numbers are
|
|
186
|
+
part of the release checklist rather than a thing to check when someone complains.
|
|
187
|
+
|
|
188
|
+
## Correctness
|
|
189
|
+
|
|
190
|
+
Correctness is defined against the reference implementations, not against our own
|
|
191
|
+
reading of the specifications. Each scheme has one: `llvm-cxxfilt` and GNU `c++filt` for
|
|
192
|
+
Itanium, `llvm-undname` for MSVC, the `rustc-demangle` crate for Rust, `c++filt
|
|
193
|
+
--format=dlang` for D and `--format=gnat` for Ada, Embarcadero's own unmangler for
|
|
194
|
+
Delphi, and -- where a distribution ships nothing that reads the mangling -- a reference
|
|
195
|
+
built here from the compiler's own sources, for Swift, pre-Itanium C++ and CodeWarrior.
|
|
196
|
+
Go, Nim, Free Pascal, Objective-C and JNI have no reference anywhere, and are held to a
|
|
197
|
+
property instead: re-mangling what was read has to reproduce the symbol.
|
|
198
|
+
[CONFORMANCE.md](CONFORMANCE.md) records what each is measured against and what the
|
|
199
|
+
measurement says.
|
|
200
|
+
|
|
201
|
+
`tests/conformance/` holds frozen corpora with the reference output recorded next to
|
|
202
|
+
each name, and the pass counts are pinned as exact numbers so that a change in either
|
|
203
|
+
direction has to be a deliberate edit rather than something that slips through. The
|
|
204
|
+
harness in `tools/` regenerates those corpora and can run a live differential against
|
|
205
|
+
the reference binaries when they are installed.
|
|
206
|
+
|
|
207
|
+
The contract at the boundary is deliberately narrow: `demangle()` never raises and
|
|
208
|
+
returns its input unchanged when it cannot do better, because a wrong expansion is
|
|
209
|
+
worse than a mangled name -- it matches neither spelling. Callers who need to know the
|
|
210
|
+
difference use `parse()`, which raises.
|