Repository navigation
Rebaseline the vendored demangler and bound stack use - #1
Merged
Merged
Conversation
bdash
force-pushed
the
harden-demangling
branch
9 times, most recently
from
September 28, 2026 23:27
0ad61ad to
5f6bb30
Compare
At `-O0`, `NodePrinter` overflows a 512 KiB thread stack on real symbols. On MSVC, set the configuration's compiler flags ourselves. The `cmake` crate otherwise replaces them with flags that drop optimization.
They weren't updated after the repository was reorganized.
…-2026-09-21-a The previous baseline has signed integer overflow in `Demangler::demangleNatural` and `demangleIndex`, fixed upstream in swiftlang/swift@7f2612f. This is the first tag with the fix. Map the new and renamed node kinds in the Rust layer, keeping the old names as deprecated aliases. Upstream now attaches propagated names in function signature specializations as `Identifier` children, so `FunctionSignatureParam::payloads()` reads those too. Closure-propagated payloads now print as mangled names rather than demangled, an upstream regression that affects about 0.25% of real symbols. No symbol that demangled before fails now, and 1,286 more demangle.
`NodePrinter::print` recurses once per level of the tree, and on Windows each call used about 8 KiB of stack, against about 0.3 KiB on macOS and Linux. The Windows x64 calling convention passes a 16-byte `StringRef` argument via a copy on the caller's stack, and neither MSVC nor clang-cl shares those copies' slots, so each of `print`'s hundreds of `Printer << "..."` calls got a slot of its own. Add a `const char *` overload of `DemanglerPrinter::operator<<` so string literals are passed in a register. This is a local patch to `vendor/`, noted in the vendoring README. Also build `NodePrinter.cpp` without inlining on MSVC, since the temporaries that inlining adds to `print` don't share slots either. Together these bring `print` to 480 bytes per call on MSVC.
The parser represents N stacked wrappers as N+1 siblings under `Global`. Wrappers include specializations, async and metadata markers, and merged or inlined thunks. `build_specialization_chain` and `build_marker_chain` recursed once per sibling, and dropping the resulting `Box<Symbol>` chain recursed once per layer, as did `Symbol::raw()` for `Suffixed`. So `$s4main5helloSSyYaKF` followed by 5000 copies of `yTg5` overflowed the stack, although its tree is only 7 deep. Build the chains from the innermost symbol outwards, make `raw()` a loop, and implement `Drop` for `Symbol` to detach nested symbols into a work list. Because `Symbol` now implements `Drop`, nested symbols can't be moved out of it by value. Match on a reference instead, as the tests now do.
`TypeRef::classify` recursed through `Type` wrappers and signature-less `DependentGenericType` nodes, and `NamedType::extract_name` recursed through `Type` wrappers, once per level of nesting. Unwrap both in loops. The classification is unchanged.
`Debug` output for `Symbol`, `TypeRef` and `Node` nests once per wrapper
layer or tree level, and wrapper chains can be arbitrarily long, so
`{:?}` on a long specialization chain overflowed the stack.
Route all three through a shared, thread-local nesting budget of 128
levels, eliding anything deeper as `..`. `Symbol`'s `Debug` impl is now
written by hand, with the same output as the derive, so it can take
part. The budget is well above the nesting of any real symbol and keeps
stack use well below thread stack limits.
`Symbol::parse` and `Symbol::from_node` now return `None` for trees deeper than `MAX_NODE_DEPTH`, so the high-level API's recursive walks are bounded. It is 768, matching `NodePrinter`'s own recursion limit and well above the depth of any real symbol. The printer can also recurse without bound by itself. It demangles function signature specialization payloads that are themselves mangled symbols, such as constant-propagated functions and globals, and each nested demangle restarts its depth count, so payloads nested in payloads can overflow the stack even though the outer tree stays shallow. The C wrapper therefore measures depth as the printer sees it, counting each text node that is a Swift symbol as its demangled tree nested at that point. `swift_demangle_symbol`, `swift_demangle_node_to_string` and `swift_demangle_get_function_info` now fail on deeper trees instead of printing them, which covers `demangle()`, `Node`'s `Display` and `FunctionInfo::parse`. The check is exposed as `swift_demangle_node_depth_within` and `Node::depth_within`.
`print_symbol` recurses into each wrapped symbol and indents it further. For a long wrapper chain the recursion grows without bound and the output grows quadratically. Print at most 64 nested levels and `...` after that. `print_symbol` now takes the nesting depth rather than an indent string. An `Indent` type's `Display` impl writes the indentation, so no strings are built for it.
With `SWIFT_DEMANGLE_UBSAN=1`, `build.rs` compiles the vendored C++ with `-fsanitize=undefined -fno-sanitize-recover=undefined`, so any undefined behavior aborts the test run. It also links Clang's UBSan runtime explicitly, because rustc links with `-nodefaultlibs` and the linker driver won't add it. Only macOS is supported for now. Run it in CI as a separate macOS job.
bdash
force-pushed
the
harden-demangling
branch
from
September 28, 2026 23:42
5f6bb30 to
ee53de6
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Rebaselines the vendored Swift demangler to
swift-DEVELOPMENT-SNAPSHOT-2026-09-21-a, which fixes signed integer overflow indemangleNaturalanddemangleIndex. The crate also now fails cleanly instead of overflowing the stack on pathological symbols.Symbolwrapper chains are built, dropped and formatted without recursion.Symbol::parse,demangleand the other printing functions returnNonefor trees deeper thanMAX_NODE_DEPTH, counting nested specialization payloads that the printer recurses into.NodePrinter::print's stack use on Windows from ~8 KiB to ~500 bytes per level of recursion.