From 899d2e994062fc1d2c7f6e86f5c1103c95c516eb Mon Sep 17 00:00:00 2001 From: Azoy Date: Thu, 9 Oct 2025 16:51:36 +0900 Subject: [PATCH 1/4] Runtime: expose demangle() in Runtime module We should finally expose the demangle functionality; It's been widely used by calling into internal _swift_demangle and instead we should offer a real API. Previous discussions happened between 2019 and 2024, and just never materialized in a complete implementation and proposal. Right now, even more tools are in need of this API, as we are building continious profiling solutions etc, so it is a good time to revisit this topic. This PR is roughly based off @Azoy's https://github.com/swiftlang/swift/pull/25314/files#diff-fd379a721cc9a1c9ef6486eae713e945da842b42170d4d069029a57334371eba from 2019, however it brings it over to the new Runtime module which is a great place to put this functionality - even Backtrace had to recently reinvent calling the demangling infra in this module. Pending SE review. --- include/swift/Runtime/Backtrace.h | 7 + stdlib/public/RuntimeModule/CMakeLists.txt | 1 + stdlib/public/RuntimeModule/Mangling.swift | 174 ++++++++++++++++++ .../RuntimeModule/SymbolicatedBacktrace.swift | 19 +- .../RuntimeModule/modules/Runtime/Runtime.h | 7 + stdlib/public/runtime/Backtrace.cpp | 32 +++- stdlib/public/runtime/Demangle.cpp | 11 ++ test/Runtime/RuntimeManglingDemangle.swift | 109 +++++++++++ 8 files changed, 338 insertions(+), 22 deletions(-) create mode 100644 stdlib/public/RuntimeModule/Mangling.swift create mode 100644 test/Runtime/RuntimeManglingDemangle.swift diff --git a/include/swift/Runtime/Backtrace.h b/include/swift/Runtime/Backtrace.h index ca13ca42aa375..ca6afcb5848a7 100644 --- a/include/swift/Runtime/Backtrace.h +++ b/include/swift/Runtime/Backtrace.h @@ -76,6 +76,13 @@ char *_swift_backtrace_demangle(const char *mangledName, size_t mangledNameLength, char *outputBuffer, size_t *outputBufferSize); + +SWIFT_RUNTIME_STDLIB_SPI +char *_swift_runtime_demangle(const char *mangledName, + size_t mangledNameLength, + char *outputBuffer, + size_t *outputBufferSize, + uint32_t flags); #ifdef __cplusplus } // namespace backtrace } // namespace runtime diff --git a/stdlib/public/RuntimeModule/CMakeLists.txt b/stdlib/public/RuntimeModule/CMakeLists.txt index 6866569e844e0..2e21b29a77dce 100644 --- a/stdlib/public/RuntimeModule/CMakeLists.txt +++ b/stdlib/public/RuntimeModule/CMakeLists.txt @@ -55,6 +55,7 @@ set(RUNTIME_SOURCES ImageSource.swift Libc.swift LimitSequence.swift + Mangling.swift MemoryReader.swift OSReleaseScanner.swift ProcMapsScanner.swift diff --git a/stdlib/public/RuntimeModule/Mangling.swift b/stdlib/public/RuntimeModule/Mangling.swift new file mode 100644 index 0000000000000..d05bacfcfe079 --- /dev/null +++ b/stdlib/public/RuntimeModule/Mangling.swift @@ -0,0 +1,174 @@ +//===--- Backtrace.swift --------------------------------------*- swift -*-===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2023 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// +// +// Defines functions and types allowing to handle Swift's mangling scheme. +// +//===----------------------------------------------------------------------===// + +import Swift + +#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS) +internal import Darwin +#elseif os(Windows) +internal import ucrt +#elseif canImport(Glibc) +internal import Glibc +#elseif canImport(Musl) +internal import Musl +#endif +internal import BacktracingImpl.Runtime + +// - MARK: Demangling + +/// Given a mangled Swift symbol, demangle it into a human readable format. +/// +/// Valid Swift symbols begin with the following prefixes: +/// ┌─────────────────────╥────────┐ +/// │ Swift Version ║ │ +/// ╞═════════════════════╬════════╡ +/// │ Swift 3 and below ║ _T │ +/// ├─────────────────────╫────────┤ +/// │ Swift 4 ║ _T0 │ +/// ├─────────────────────╫────────┤ +/// │ Swift 4.x ║ $S │ +/// ├─────────────────────╫────────┤ +/// │ Swift 5+ ║ $s │ +/// └─────────────────────╨────────┘ +/// +/// - Parameters: +/// - mangledName: A mangled Swift symbol. +/// - Returns: A human readable demangled Swift symbol. +/// - Warning: The demangled output is lossy is not not guaranteed to be stable across Swift versions. +/// Future versions of Swift may choose to print more (or less) information in the demangled format. +public func demangle(_ mangledName: String) -> String? { + var length: size_t = 0 + + let demangled = _swift_runtime_demangle( + mangledName, mangledName.utf8.count, + nil, &length, + /*flags=*/0 + ) + + guard let demangled else { + return nil + } + defer { free(demangled) } + + // length is the size of the buffer that was allocated, *not* the + // length of the string. + let stringLen = strlen(demangled) + guard stringLen > 0 else { + return nil + } + + return demangled.withMemoryRebound(to: UInt8.self, capacity: stringLen) { + let demangledBytes = UnsafeBufferPointer(start: $0, count: stringLen) + return String(decoding: demangledBytes, as: UTF8.self) + } +} + + +/// Given a mangled Swift symbol, demangle it into a human readable format. +/// +/// Valid Swift symbols begin with the following prefixes: +/// ┌─────────────────────╥────────┐ +/// │ Swift Version ║ │ +/// ╞═════════════════════╬════════╡ +/// │ Swift 3 and below ║ _T │ +/// ├─────────────────────╫────────┤ +/// │ Swift 4 ║ _T0 │ +/// ├─────────────────────╫────────┤ +/// │ Swift 4.x ║ $S │ +/// ├─────────────────────╫────────┤ +/// │ Swift 5+ ║ $s │ +/// └─────────────────────╨────────┘ +/// +/// - Parameters: +/// - mangledNameBuffer: A buffer pointer pointing to a null-terminated C +/// string that contains the mangled Swift symbol. +/// - buffer: A pre-allocated buffer to demangle the Swift symbol as a c-string into. +/// - Returns: An enum, `DemanglingResult`, indicating the various result states +/// of demangling. +/// - Warning: The demangled output is lossy is not not guaranteed to be stable across Swift versions. +/// Future versions of Swift may choose to print more (or less) information in the demangled format. +public func demangle( + _ mangledNameBuffer: UnsafeBufferPointer, + into buffer: UnsafeMutableBufferPointer +) -> DemanglingResult { + var bufferSize = buffer.count + + let demangledPtr = _swift_runtime_demangle( + /*mangledName=*/mangledNameBuffer.baseAddress, + /*mangledNameLength=*/mangledNameBuffer.count - 1, + /*outputBuffer=*/buffer.baseAddress, + /*outputBufferSize=*/&bufferSize, + /*flags=*/0 + ) + + guard let demangledPtr else { + return .invalidSymbol + } + + // If the buffer size is still equal to the buffer count, the demangle was + // successful. + if bufferSize <= buffer.count { + return .success + } + + // However if it's not equal, the result was truncated. Return the amount + // needed to get a full demangle. + return .truncated(bufferSize) +} + +/// Given a mangled Swift symbol, demangle it into a human readable format. +/// +/// Valid Swift symbols begin with the following prefixes: +/// ┌─────────────────────╥────────┐ +/// │ Swift Version ║ │ +/// ╞═════════════════════╬════════╡ +/// │ Swift 3 and below ║ _T │ +/// ├─────────────────────╫────────┤ +/// │ Swift 4 ║ _T0 │ +/// ├─────────────────────╫────────┤ +/// │ Swift 4.x ║ $S │ +/// ├─────────────────────╫────────┤ +/// │ Swift 5+ ║ $s │ +/// └─────────────────────╨────────┘ +/// +/// - Parameters: +/// - mangledName: A mangled Swift symbol. +/// - buffer: A pre-allocated buffer to demangle the Swift symbol as a c-string into. +/// - Returns: An enum, `DemanglingResult`, indicating the various result states +/// of demangling. +/// - Warning: The demangled output is lossy is not not guaranteed to be stable across Swift versions. +/// Future versions of Swift may choose to print more (or less) information in the demangled format. +public func demangle( + _ mangledName: String, + into buffer: UnsafeMutableBufferPointer +) -> DemanglingResult { + mangledName.utf8CString.withUnsafeBufferPointer { + demangle($0, into: buffer) + } +} + +/// Represents whether or not demangling of a symbol was successful. +public enum DemanglingResult: Equatable { + /// Demangling completed successfully. + case success + + /// Demangling resulted in truncating the result. The payload value is the + /// number of bytes necessary for a full demangle. + case truncated(Int) + + /// The passed Swift mangled symbol was invalid. + case invalidSymbol +} diff --git a/stdlib/public/RuntimeModule/SymbolicatedBacktrace.swift b/stdlib/public/RuntimeModule/SymbolicatedBacktrace.swift index 19ce102a6dd56..61080a9aa4261 100644 --- a/stdlib/public/RuntimeModule/SymbolicatedBacktrace.swift +++ b/stdlib/public/RuntimeModule/SymbolicatedBacktrace.swift @@ -202,24 +202,7 @@ public struct SymbolicatedBacktrace: CustomStringConvertible { /// Demangle the raw name, if possible. private func demangleRawName() -> String { - var length: size_t = 0 - if let demangled = _swift_backtrace_demangle(rawName, rawName.utf8.count, - nil, &length) { - defer { free(demangled) } - - // length is the size of the buffer that was allocated, *not* the - // length of the string. - let stringLen = strlen(demangled) - if stringLen > 0 { - return demangled.withMemoryRebound(to: UInt8.self, - capacity: stringLen) { - let demangledBytes = UnsafeBufferPointer(start: $0, - count: stringLen) - return String(decoding: demangledBytes, as: UTF8.self) - } - } - } - return rawName + demangle(rawName) ?? rawName } /// A textual description of this symbol. diff --git a/stdlib/public/RuntimeModule/modules/Runtime/Runtime.h b/stdlib/public/RuntimeModule/modules/Runtime/Runtime.h index 0cda2acc68493..b68b9eb75440e 100644 --- a/stdlib/public/RuntimeModule/modules/Runtime/Runtime.h +++ b/stdlib/public/RuntimeModule/modules/Runtime/Runtime.h @@ -38,6 +38,13 @@ char *_swift_backtrace_demangle(const char *rawName, char *outputBuffer, size_t *outputBufferSize); +// Demangle the given raw name (supports Swift and C++) +char *_swift_runtime_demangle(const char *rawName, + size_t rawNameLength, + char *outputBuffer, + size_t *outputBufferSize, + uint32_t flags); + #ifdef __cplusplus } #endif diff --git a/stdlib/public/runtime/Backtrace.cpp b/stdlib/public/runtime/Backtrace.cpp index 18bad7bf620b6..f119226c3d96a 100644 --- a/stdlib/public/runtime/Backtrace.cpp +++ b/stdlib/public/runtime/Backtrace.cpp @@ -996,10 +996,18 @@ _swift_backtrace_isThunkFunction(const char *mangledName) { // Try to demangle a symbol. SWIFT_RUNTIME_STDLIB_SPI char * -_swift_backtrace_demangle(const char *mangledName, +_swift_runtime_demangle(const char *mangledName, size_t mangledNameLength, char *outputBuffer, - size_t *outputBufferSize) { + size_t *outputBufferSize, + uint32_t flags) { + if (flags > 1) { + swift::fatalError(0, "Only 'flags' value of '0' and '1' is currently supported."); + } + if (outputBuffer != nullptr && outputBufferSize == nullptr) { + swift::fatalError(0, "'outputBuffer' is passed but the size is 'nullptr'."); + } + llvm::StringRef name = llvm::StringRef(mangledName, mangledNameLength); // You must provide buffer size if you're providing your own output buffer @@ -1008,8 +1016,13 @@ _swift_backtrace_demangle(const char *mangledName, } if (Demangle::isSwiftSymbol(name)) { - // This is a Swift mangling - auto options = DemangleOptions::SimplifiedUIDemangleOptions(); + // Determine demangling/formatting options: + auto options = DemangleOptions(); + if (flags == 1) { + // simplified display options, for backtraces + options = DemangleOptions::SimplifiedUIDemangleOptions(); + } + auto result = Demangle::demangleSymbolAsString(name, options); size_t bufferSize; @@ -1067,6 +1080,17 @@ _swift_backtrace_demangle(const char *mangledName, return nullptr; } +SWIFT_RUNTIME_STDLIB_SPI char * +_swift_backtrace_demangle(const char *mangledName, + size_t mangledNameLength, + char *outputBuffer, + size_t *outputBufferSize) { + return _swift_runtime_demangle( + mangledName, mangledNameLength, + outputBuffer, outputBufferSize, + /*flags=*/1 /* to use the SimplifiedUIDemangleOptions*/); +} + #if SWIFT_BACKTRACE_ON_CRASH_SUPPORTED namespace { diff --git a/stdlib/public/runtime/Demangle.cpp b/stdlib/public/runtime/Demangle.cpp index 9fd0edd57977b..365b2f715077b 100644 --- a/stdlib/public/runtime/Demangle.cpp +++ b/stdlib/public/runtime/Demangle.cpp @@ -1033,3 +1033,14 @@ char *swift_demangle(const char *mangledName, return outputBuffer; #endif } + + +SWIFT_RUNTIME_STDLIB_SPI char * +_swift_stdlib_demangle(const char *mangledName, + size_t mangledNameLength, + char *outputBuffer, + size_t *outputBufferSize, + uint32_t flags) { + return swift_demangle(mangledName, mangledNameLength, outputBuffer, + outputBufferSize, flags); +} diff --git a/test/Runtime/RuntimeManglingDemangle.swift b/test/Runtime/RuntimeManglingDemangle.swift new file mode 100644 index 0000000000000..b75d511195eea --- /dev/null +++ b/test/Runtime/RuntimeManglingDemangle.swift @@ -0,0 +1,109 @@ +// RUN: %target-run-simple-swift +// REQUIRES: executable_test + +// UNSUPPORTED: use_os_stdlib +// UNSUPPORTED: back_deployment_runtime +// REQUIRES: executable_test +// REQUIRES: OS=macosx || OS=linux-gnu + +import Swift +import Runtime +import StdlibUnittest + +var DemangleTests = TestSuite("Demangle") + +DemangleTests.test("basic string return API") { + // First, test that we get back the mangled name with invalid input. + expectEqual(demangle("abc123"), nil) + expectEqual(demangle("Si"), nil) + expectEqual(demangle("Swift is super cool!"), nil) + + // Test that correct symbols are demangled. (Test all documented prefixes) + expectEqual(demangle("_TSb"), "Swift.Bool") + expectEqual(demangle("_T0Si"), "Swift.Int") + expectEqual(demangle("$SSdXSaXSq"), "[Swift.Double]?") + expectEqual(demangle("_$S8Demangle4main4argc4argvs5Int32VAF_SpySpys4Int8VGSgGtF"), "Demangle.main(argc: Swift.Int32, argv: Swift.UnsafeMutablePointer>>) -> Swift.Int32") + expectEqual(demangle("$sSG"), "Swift.RandomNumberGenerator") + expectEqual(demangle("_$sSS7cStringSSSPys4Int8VG_tcfC"), "Swift.String.init(cString: Swift.UnsafePointer) -> Swift.String") +} + +DemangleTests.test("buffer API") { + let buffer = UnsafeMutableBufferPointer.allocate(capacity: 140) + + defer { + buffer.deallocate() + } + + buffer[0] = 0 // Ensure that when we do String(cString: ptr) it halts at first byte. + let ptr = buffer.baseAddress! + + // First, test that the buffer is still empty after failed demanglings. + var res = demangle("abc123", into: buffer) + expectEqual(res, .invalidSymbol) + expectEqual(String(cString: ptr), "") + + res = demangle("Si", into: buffer) + expectEqual(res, .invalidSymbol) + expectEqual(String(cString: ptr), "") + + res = demangle("Swift is super cool!", into: buffer) + expectEqual(res, .invalidSymbol) + expectEqual(String(cString: ptr), "") + + // Test that correct symbols are demangled. (Test all documented prefixes) + res = demangle("_TSb", into: buffer) + print("res = \(String(cString: ptr)) len:\(String(cString: ptr).count)") + expectEqual(res, .success) + expectEqual(String(cString: ptr), "Swift.Bool") + + res = demangle("_T0Si", into: buffer) + print("res = \(String(cString: ptr)) len:\(String(cString: ptr).count)") + expectEqual(res, .success) + expectEqual(String(cString: ptr), "Swift.Int") + + res = demangle("$SSdXSaXSq", into: buffer) + print("res = \(String(cString: ptr)) len:\(String(cString: ptr).count)") + expectEqual(res, .success) + expectEqual(String(cString: ptr), "[Swift.Double]?") + + res = demangle("_$S8Demangle4main4argc4argvs5Int32VAF_SpySpys4Int8VGSgGtF", into: buffer) + print("res = \(String(cString: ptr)) len:\(String(cString: ptr).count)") + expectEqual(res, .success) + expectEqual(String(cString: ptr), "Demangle.main(argc: Swift.Int32, argv: Swift.UnsafeMutablePointer>>) -> Swift.Int32") + + res = demangle("$sSG", into: buffer) + print("res = \(String(cString: ptr)) len:\(String(cString: ptr).count)") + expectEqual(res, .success) + expectEqual(String(cString: ptr), "Swift.RandomNumberGenerator") + + res = demangle("_$sSS7cStringSSSPys4Int8VG_tcfC", into: buffer) + print("res = \(String(cString: ptr)) len:\(String(cString: ptr).count)") + expectEqual(res, .success) + expectEqual(String(cString: ptr), "Swift.String.init(cString: Swift.UnsafePointer) -> Swift.String") +} + +DemangleTests.test("buffer API - too small buffer") { + // Test the return of demangle into with a smaller buffer. + // Swift.Int requires 10 bytes, give this 9 + let smolBuffer = UnsafeMutableBufferPointer.allocate(capacity: 9) + defer { smolBuffer.deallocate() } + + let smolPtr = smolBuffer.baseAddress! + + let fail = demangle("$sSi", into: smolBuffer) + expectEqual(fail, .truncated(10)) + expectEqual(String(cString: smolPtr), "Swift.In") +} + +DemangleTests.test("buffer API - small buffer, success") { + let smolBuffer = UnsafeMutableBufferPointer.allocate(capacity: 9) + defer { smolBuffer.deallocate() } + + let smolPtr = smolBuffer.baseAddress! + // Test nil return on successful demangle. + let success = demangle("$s4Smol3IntV", into: smolBuffer) + expectEqual(success, .success) + expectEqual(String(cString: smolPtr), "Smol.Int") +} + +runAllTests() \ No newline at end of file From fcb442997e58b0b033c0fcfcc1fe66722d639059 Mon Sep 17 00:00:00 2001 From: Azoy Date: Thu, 9 Oct 2025 16:51:36 +0900 Subject: [PATCH 2/4] Runtime: expose demangle() in Runtime module We should finally expose the demangle functionality; It's been widely used by calling into internal _swift_demangle and instead we should offer a real API. Previous discussions happened between 2019 and 2024, and just never materialized in a complete implementation and proposal. Right now, even more tools are in need of this API, as we are building continious profiling solutions etc, so it is a good time to revisit this topic. This PR is roughly based off @Azoy's https://github.com/swiftlang/swift/pull/25314/files#diff-fd379a721cc9a1c9ef6486eae713e945da842b42170d4d069029a57334371eba from 2019, however it brings it over to the new Runtime module which is a great place to put this functionality - even Backtrace had to recently reinvent calling the demangling infra in this module. Pending SE review. --- include/swift/Runtime/Backtrace.h | 6 ------ 1 file changed, 6 deletions(-) diff --git a/include/swift/Runtime/Backtrace.h b/include/swift/Runtime/Backtrace.h index ca6afcb5848a7..2e5cbe4beeb26 100644 --- a/include/swift/Runtime/Backtrace.h +++ b/include/swift/Runtime/Backtrace.h @@ -77,12 +77,6 @@ char *_swift_backtrace_demangle(const char *mangledName, char *outputBuffer, size_t *outputBufferSize); -SWIFT_RUNTIME_STDLIB_SPI -char *_swift_runtime_demangle(const char *mangledName, - size_t mangledNameLength, - char *outputBuffer, - size_t *outputBufferSize, - uint32_t flags); #ifdef __cplusplus } // namespace backtrace } // namespace runtime From c9078f08f004e79594b5f737baa1644aa8e6b1ef Mon Sep 17 00:00:00 2001 From: Konrad Malawski Date: Fri, 10 Oct 2025 11:25:06 +0900 Subject: [PATCH 3/4] cleanup a bit where and how we expose what functions; new Demangling.h --- include/swift/Runtime/Runtime.h | 53 +++++++++++++++ stdlib/public/runtime/Backtrace.cpp | 88 +----------------------- stdlib/public/runtime/Demangle.cpp | 102 +++++++++++++++++++++++++--- 3 files changed, 149 insertions(+), 94 deletions(-) create mode 100644 include/swift/Runtime/Runtime.h diff --git a/include/swift/Runtime/Runtime.h b/include/swift/Runtime/Runtime.h new file mode 100644 index 0000000000000..b74e8200ae8f2 --- /dev/null +++ b/include/swift/Runtime/Runtime.h @@ -0,0 +1,53 @@ +//===--- Runtime.cpp - Runtime functions exposed in Runtime module ---- ===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2022 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// +// +// Definitions relating to public runtime module. +// +//===----------------------------------------------------------------------===// + +#ifndef SWIFT_RUNTIME_MANGLING_H +#define SWIFT_RUNTIME_MANGLING_H + +#ifdef __linux__ +#include +#include + +#include +#endif // defined(__linux__) + +#include "swift/Runtime/Config.h" +#include "swift/Runtime/CrashInfo.h" + +#include "swift/shims/Visibility.h" + +#include + +#ifdef __cplusplus +namespace swift { +namespace runtime { +namespace mangling { +#endif + + SWIFT_RUNTIME_STDLIB_SPI + char * _swift_runtime_demangle(const char *mangledName, + size_t mangledNameLength, + char *outputBuffer, + size_t *outputBufferSize, + uint32_t flags); + +#ifdef __cplusplus +} // namespace mangling +} // namespace runtime +} // namespace swift +#endif + +#endif // SWIFT_RUNTIME_MANGLING_H diff --git a/stdlib/public/runtime/Backtrace.cpp b/stdlib/public/runtime/Backtrace.cpp index f119226c3d96a..8544531912cc7 100644 --- a/stdlib/public/runtime/Backtrace.cpp +++ b/stdlib/public/runtime/Backtrace.cpp @@ -20,6 +20,7 @@ #include "swift/Runtime/Config.h" #include "swift/Runtime/Backtrace.h" +#include "swift/Runtime/Runtime.h" #include "swift/Runtime/Debug.h" #include "swift/Runtime/Paths.h" #include "swift/Runtime/EnvironmentVariables.h" @@ -78,6 +79,7 @@ extern "C" int csops(int, unsigned int, void *, size_t); #endif using namespace swift::runtime::backtrace; +using namespace swift::runtime::mangling; namespace swift { namespace runtime { @@ -994,92 +996,6 @@ _swift_backtrace_isThunkFunction(const char *mangledName) { return ctx.isThunkSymbol(mangledName); } -// Try to demangle a symbol. -SWIFT_RUNTIME_STDLIB_SPI char * -_swift_runtime_demangle(const char *mangledName, - size_t mangledNameLength, - char *outputBuffer, - size_t *outputBufferSize, - uint32_t flags) { - if (flags > 1) { - swift::fatalError(0, "Only 'flags' value of '0' and '1' is currently supported."); - } - if (outputBuffer != nullptr && outputBufferSize == nullptr) { - swift::fatalError(0, "'outputBuffer' is passed but the size is 'nullptr'."); - } - - llvm::StringRef name = llvm::StringRef(mangledName, mangledNameLength); - - // You must provide buffer size if you're providing your own output buffer - if (outputBuffer && !outputBufferSize) { - return nullptr; - } - - if (Demangle::isSwiftSymbol(name)) { - // Determine demangling/formatting options: - auto options = DemangleOptions(); - if (flags == 1) { - // simplified display options, for backtraces - options = DemangleOptions::SimplifiedUIDemangleOptions(); - } - - auto result = Demangle::demangleSymbolAsString(name, options); - size_t bufferSize; - - if (outputBufferSize) { - bufferSize = *outputBufferSize; - *outputBufferSize = result.length() + 1; - } - - if (outputBuffer == nullptr) { - outputBuffer = (char *)::malloc(result.length() + 1); - bufferSize = result.length() + 1; - } - - size_t toCopy = std::min(bufferSize - 1, result.length()); - ::memcpy(outputBuffer, result.data(), toCopy); - outputBuffer[toCopy] = '\0'; - - return outputBuffer; -#ifndef _WIN32 - } else if (name.starts_with("_Z")) { - // Try C++; note that we don't want to force callers to use malloc() to - // allocate their buffer, which is a requirement for __cxa_demangle - // because it may call realloc() on the incoming pointer. As a result, - // we never pass the caller's buffer to __cxa_demangle. - size_t resultLen; - int status = 0; - char *result = abi::__cxa_demangle(mangledName, nullptr, &resultLen, &status); - - if (result) { - size_t bufferSize; - - if (outputBufferSize) { - bufferSize = *outputBufferSize; - *outputBufferSize = resultLen; - } - - if (outputBuffer == nullptr) { - return result; - } - - size_t toCopy = std::min(bufferSize - 1, resultLen - 1); - ::memcpy(outputBuffer, result, toCopy); - outputBuffer[toCopy] = '\0'; - - free(result); - - return outputBuffer; - } -#else - // On Windows, the mangling is different. - // ###TODO: Call __unDName() -#endif - } - - return nullptr; -} - SWIFT_RUNTIME_STDLIB_SPI char * _swift_backtrace_demangle(const char *mangledName, size_t mangledNameLength, diff --git a/stdlib/public/runtime/Demangle.cpp b/stdlib/public/runtime/Demangle.cpp index 365b2f715077b..903cccaf22ca8 100644 --- a/stdlib/public/runtime/Demangle.cpp +++ b/stdlib/public/runtime/Demangle.cpp @@ -24,6 +24,12 @@ #include #endif +#ifdef _WIN32 +// We'll probably want dbghelp.h here +#else +#include +#endif + using namespace swift; Demangle::NodePointer @@ -1034,13 +1040,93 @@ char *swift_demangle(const char *mangledName, #endif } +namespace swift { + namespace runtime { + // Demangle entry point for backtrace and public demangle() Swift API. + SWIFT_RUNTIME_STDLIB_SPI char * + _swift_runtime_demangle(const char *mangledName, + size_t mangledNameLength, + char *outputBuffer, + size_t *outputBufferSize, + uint32_t flags) { + if (flags > 1) { + swift::fatalError(0, "Only 'flags' value of '0' and '1' is currently supported."); + } + if (outputBuffer != nullptr && outputBufferSize == nullptr) { + swift::fatalError(0, "'outputBuffer' is passed but the size is 'nullptr'."); + } + + llvm::StringRef name = llvm::StringRef(mangledName, mangledNameLength); -SWIFT_RUNTIME_STDLIB_SPI char * -_swift_stdlib_demangle(const char *mangledName, - size_t mangledNameLength, - char *outputBuffer, - size_t *outputBufferSize, - uint32_t flags) { - return swift_demangle(mangledName, mangledNameLength, outputBuffer, - outputBufferSize, flags); + // You must provide buffer size if you're providing your own output buffer + if (outputBuffer && !outputBufferSize) { + return nullptr; + } + + if (Demangle::isSwiftSymbol(name)) { + // Determine demangling/formatting options: + auto options = DemangleOptions(); + if (flags == 1) { + // simplified display options, for backtraces + options = DemangleOptions::SimplifiedUIDemangleOptions(); + } + + auto result = Demangle::demangleSymbolAsString(name, options); + size_t bufferSize; + + if (outputBufferSize) { + bufferSize = *outputBufferSize; + *outputBufferSize = result.length() + 1; + } + + if (outputBuffer == nullptr) { + outputBuffer = (char *)::malloc(result.length() + 1); + bufferSize = result.length() + 1; + } + + size_t toCopy = std::min(bufferSize - 1, result.length()); + ::memcpy(outputBuffer, result.data(), toCopy); + outputBuffer[toCopy] = '\0'; + + return outputBuffer; + #ifndef _WIN32 + } else if (name.starts_with("_Z")) { + // Try C++; note that we don't want to force callers to use malloc() to + // allocate their buffer, which is a requirement for __cxa_demangle + // because it may call realloc() on the incoming pointer. As a result, + // we never pass the caller's buffer to __cxa_demangle. + size_t resultLen; + int status = 0; + char *result = abi::__cxa_demangle(mangledName, nullptr, &resultLen, &status); + + if (result) { + size_t bufferSize; + + if (outputBufferSize) { + bufferSize = *outputBufferSize; + *outputBufferSize = resultLen; + } + + if (outputBuffer == nullptr) { + return result; + } + + size_t toCopy = std::min(bufferSize - 1, resultLen - 1); + ::memcpy(outputBuffer, result, toCopy); + outputBuffer[toCopy] = '\0'; + + free(result); + + return outputBuffer; + } + #else + // On Windows, the mangling is different. + // ###TODO: Call __unDName() + #endif + } + + return nullptr; + } + } } + From ff6de75057957d26a01647b677acb550e6f8784a Mon Sep 17 00:00:00 2001 From: Konrad Malawski Date: Fri, 10 Oct 2025 13:00:30 +0900 Subject: [PATCH 4/4] add MutableSpan version --- stdlib/public/RuntimeModule/Mangling.swift | 70 ++++++- test/Runtime/RuntimeManglingDemangle.swift | 226 +++++++++++++-------- 2 files changed, 211 insertions(+), 85 deletions(-) diff --git a/stdlib/public/RuntimeModule/Mangling.swift b/stdlib/public/RuntimeModule/Mangling.swift index d05bacfcfe079..71fba09e7add8 100644 --- a/stdlib/public/RuntimeModule/Mangling.swift +++ b/stdlib/public/RuntimeModule/Mangling.swift @@ -49,6 +49,7 @@ internal import BacktracingImpl.Runtime /// - Returns: A human readable demangled Swift symbol. /// - Warning: The demangled output is lossy is not not guaranteed to be stable across Swift versions. /// Future versions of Swift may choose to print more (or less) information in the demangled format. +// @_alwaysEmitIntoClient // FIXME: how to keep this usable by backtrace public func demangle(_ mangledName: String) -> String? { var length: size_t = 0 @@ -76,6 +77,37 @@ public func demangle(_ mangledName: String) -> String? { } } +//@available(SwiftStdlib 6.3, *) +//@lifetime(borrow mangledName) +//public func demangle(_ mangledName: UTF8Span) -> String? { +// var length: size_t = 0 +// +// let demangledPtr = +// mangledName._withUnsafeBufferPointer { mangledNamePtr in +// _swift_runtime_demangle( +// mangledNamePtr.baseAddress, mangledName.count, +// nil, &length, +// /*flags=*/0) +// } +// +// let demangledBufferPtr = UnsafeMutableBufferPointer(start: demangledPtr, count: length) +// let demangledOutput = OutputSpan(buffer: demangledPtr, initializedCount: length) +// let demangledSpan = UTF8Span(validating: demangledOutput.span) +// +// guard let demangledPtr else { +// return nil +// } +// defer { free(demangledPtr) } +// +// // length is the size of the buffer that was allocated, *not* the +// // length of the string. +// let stringLen = strlen(demangledPtr) +// guard stringLen > 0 else { +// return nil +// } +// +// return String(copying: demangledSpan) +//} /// Given a mangled Swift symbol, demangle it into a human readable format. /// @@ -100,13 +132,14 @@ public func demangle(_ mangledName: String) -> String? { /// of demangling. /// - Warning: The demangled output is lossy is not not guaranteed to be stable across Swift versions. /// Future versions of Swift may choose to print more (or less) information in the demangled format. +@available(SwiftStdlib 6.3, *) public func demangle( _ mangledNameBuffer: UnsafeBufferPointer, into buffer: UnsafeMutableBufferPointer ) -> DemanglingResult { var bufferSize = buffer.count - let demangledPtr = _swift_runtime_demangle( + let demangledPtr = _swift_runtime_demangle( /*mangledName=*/mangledNameBuffer.baseAddress, /*mangledNameLength=*/mangledNameBuffer.count - 1, /*outputBuffer=*/buffer.baseAddress, @@ -129,6 +162,39 @@ public func demangle( return .truncated(bufferSize) } +@available(SwiftStdlib 6.3, *) +public func demangle( + _ mangledNameSpan: Span, + into buffer: inout MutableSpan +) -> DemanglingResult { + var outputBufferSize = buffer.count + + let demangledPtr = + mangledNameSpan.withUnsafeBufferPointer { mangledNameBuffer in + buffer.withUnsafeMutableBufferPointer { outputBuffer in + _swift_runtime_demangle(/*mangledName=*/mangledNameBuffer.baseAddress, + /*mangledNameLength=*/mangledNameBuffer.count, + /*outputBuffer=*/outputBuffer.baseAddress, + /*outputBufferSize=*/&outputBufferSize, + /*flags=*/0) + } + } + + guard let demangledPtr else { + return .invalidSymbol + } + + // If the buffer size is still equal to the buffer count, the demangle was + // successful. + if outputBufferSize <= buffer.count { + return .success + } + + // However if it's not equal, the result was truncated. Return the amount + // needed to get a full demangle. + return .truncated(outputBufferSize) +} + /// Given a mangled Swift symbol, demangle it into a human readable format. /// /// Valid Swift symbols begin with the following prefixes: @@ -151,6 +217,7 @@ public func demangle( /// of demangling. /// - Warning: The demangled output is lossy is not not guaranteed to be stable across Swift versions. /// Future versions of Swift may choose to print more (or less) information in the demangled format. +@available(SwiftStdlib 6.3, *) public func demangle( _ mangledName: String, into buffer: UnsafeMutableBufferPointer @@ -161,6 +228,7 @@ public func demangle( } /// Represents whether or not demangling of a symbol was successful. +@available(SwiftStdlib 6.3, *) public enum DemanglingResult: Equatable { /// Demangling completed successfully. case success diff --git a/test/Runtime/RuntimeManglingDemangle.swift b/test/Runtime/RuntimeManglingDemangle.swift index b75d511195eea..0d5aa59af1046 100644 --- a/test/Runtime/RuntimeManglingDemangle.swift +++ b/test/Runtime/RuntimeManglingDemangle.swift @@ -12,98 +12,156 @@ import StdlibUnittest var DemangleTests = TestSuite("Demangle") -DemangleTests.test("basic string return API") { - // First, test that we get back the mangled name with invalid input. - expectEqual(demangle("abc123"), nil) - expectEqual(demangle("Si"), nil) - expectEqual(demangle("Swift is super cool!"), nil) - - // Test that correct symbols are demangled. (Test all documented prefixes) - expectEqual(demangle("_TSb"), "Swift.Bool") - expectEqual(demangle("_T0Si"), "Swift.Int") - expectEqual(demangle("$SSdXSaXSq"), "[Swift.Double]?") - expectEqual(demangle("_$S8Demangle4main4argc4argvs5Int32VAF_SpySpys4Int8VGSgGtF"), "Demangle.main(argc: Swift.Int32, argv: Swift.UnsafeMutablePointer>>) -> Swift.Int32") - expectEqual(demangle("$sSG"), "Swift.RandomNumberGenerator") - expectEqual(demangle("_$sSS7cStringSSSPys4Int8VG_tcfC"), "Swift.String.init(cString: Swift.UnsafePointer) -> Swift.String") -} - -DemangleTests.test("buffer API") { - let buffer = UnsafeMutableBufferPointer.allocate(capacity: 140) +if #available(SwiftStdlib 6.3, *) { + DemangleTests.test("basic string return API") { + // First, test that we get back the mangled name with invalid input. + expectEqual(demangle("abc123"), nil) + expectEqual(demangle("Si"), nil) + expectEqual(demangle("Swift is super cool!"), nil) + + // Test that correct symbols are demangled. (Test all documented prefixes) + expectEqual(demangle("_TSb"), "Swift.Bool") + expectEqual(demangle("_T0Si"), "Swift.Int") + expectEqual(demangle("$SSdXSaXSq"), "[Swift.Double]?") + expectEqual(demangle("_$S8Demangle4main4argc4argvs5Int32VAF_SpySpys4Int8VGSgGtF"), "Demangle.main(argc: Swift.Int32, argv: Swift.UnsafeMutablePointer>>) -> Swift.Int32") + expectEqual(demangle("$sSG"), "Swift.RandomNumberGenerator") + expectEqual(demangle("_$sSS7cStringSSSPys4Int8VG_tcfC"), "Swift.String.init(cString: Swift.UnsafePointer) -> Swift.String") + } - defer { - buffer.deallocate() + DemangleTests.test("Span API") { + let buffer = UnsafeMutableBufferPointer.allocate(capacity: 140) + var outputSpan = buffer.mutableSpan + + defer { + buffer.deallocate() + } + + buffer[0] = 0 // Ensure that when we do String(cString: ptr) it halts at first byte. + let ptr = buffer.baseAddress! + + // First, test that the buffer is still empty after failed demanglings. + var res = demangle("abc123".utf8Span.span, into: &outputSpan) + expectEqual(res, .invalidSymbol) + expectEqual(String(cString: ptr), "") + + res = demangle("Si".utf8Span.span, into: &outputSpan) + expectEqual(res, .invalidSymbol) + expectEqual(String(cString: ptr), "") + + res = demangle("Swift is super cool!".utf8Span.span, into: &outputSpan) + expectEqual(res, .invalidSymbol) + expectEqual(String(cString: ptr), "") + + // Test that correct symbols are demangled. (Test all documented prefixes) + res = demangle("_TSb".utf8Span.span, into: &outputSpan) + print("res = \(String(cString: ptr)) len:\(String(cString: ptr).count)") + expectEqual(res, .success) + expectEqual(String(cString: ptr), "Swift.Bool") + + res = demangle("_T0Si".utf8Span.span, into: &outputSpan) + print("res = \(String(cString: ptr)) len:\(String(cString: ptr).count)") + expectEqual(res, .success) + expectEqual(String(cString: ptr), "Swift.Int") + + res = demangle("$SSdXSaXSq".utf8Span.span, into: &outputSpan) + print("res = \(String(cString: ptr)) len:\(String(cString: ptr).count)") + expectEqual(res, .success) + expectEqual(String(cString: ptr), "[Swift.Double]?") + + res = demangle("_$S8Demangle4main4argc4argvs5Int32VAF_SpySpys4Int8VGSgGtF".utf8Span.span, into: &outputSpan) + print("res = \(String(cString: ptr)) len:\(String(cString: ptr).count)") + expectEqual(res, .success) + expectEqual(String(cString: ptr), "Demangle.main(argc: Swift.Int32, argv: Swift.UnsafeMutablePointer>>) -> Swift.Int32") + + res = demangle("$sSG".utf8Span.span, into: &outputSpan) + print("res = \(String(cString: ptr)) len:\(String(cString: ptr).count)") + expectEqual(res, .success) + expectEqual(String(cString: ptr), "Swift.RandomNumberGenerator") + + res = demangle("_$sSS7cStringSSSPys4Int8VG_tcfC".utf8Span.span, into: &outputSpan) + print("res = \(String(cString: ptr)) len:\(String(cString: ptr).count)") + expectEqual(res, .success) + expectEqual(String(cString: ptr), "Swift.String.init(cString: Swift.UnsafePointer) -> Swift.String") } - buffer[0] = 0 // Ensure that when we do String(cString: ptr) it halts at first byte. - let ptr = buffer.baseAddress! - - // First, test that the buffer is still empty after failed demanglings. - var res = demangle("abc123", into: buffer) - expectEqual(res, .invalidSymbol) - expectEqual(String(cString: ptr), "") - - res = demangle("Si", into: buffer) - expectEqual(res, .invalidSymbol) - expectEqual(String(cString: ptr), "") - - res = demangle("Swift is super cool!", into: buffer) - expectEqual(res, .invalidSymbol) - expectEqual(String(cString: ptr), "") - - // Test that correct symbols are demangled. (Test all documented prefixes) - res = demangle("_TSb", into: buffer) - print("res = \(String(cString: ptr)) len:\(String(cString: ptr).count)") - expectEqual(res, .success) - expectEqual(String(cString: ptr), "Swift.Bool") - - res = demangle("_T0Si", into: buffer) - print("res = \(String(cString: ptr)) len:\(String(cString: ptr).count)") - expectEqual(res, .success) - expectEqual(String(cString: ptr), "Swift.Int") - - res = demangle("$SSdXSaXSq", into: buffer) - print("res = \(String(cString: ptr)) len:\(String(cString: ptr).count)") - expectEqual(res, .success) - expectEqual(String(cString: ptr), "[Swift.Double]?") - - res = demangle("_$S8Demangle4main4argc4argvs5Int32VAF_SpySpys4Int8VGSgGtF", into: buffer) - print("res = \(String(cString: ptr)) len:\(String(cString: ptr).count)") - expectEqual(res, .success) - expectEqual(String(cString: ptr), "Demangle.main(argc: Swift.Int32, argv: Swift.UnsafeMutablePointer>>) -> Swift.Int32") - - res = demangle("$sSG", into: buffer) - print("res = \(String(cString: ptr)) len:\(String(cString: ptr).count)") - expectEqual(res, .success) - expectEqual(String(cString: ptr), "Swift.RandomNumberGenerator") - - res = demangle("_$sSS7cStringSSSPys4Int8VG_tcfC", into: buffer) - print("res = \(String(cString: ptr)) len:\(String(cString: ptr).count)") - expectEqual(res, .success) - expectEqual(String(cString: ptr), "Swift.String.init(cString: Swift.UnsafePointer) -> Swift.String") -} + DemangleTests.test("buffer API") { + let buffer = UnsafeMutableBufferPointer.allocate(capacity: 140) + + defer { + buffer.deallocate() + } + + buffer[0] = 0 // Ensure that when we do String(cString: ptr) it halts at first byte. + let ptr = buffer.baseAddress! + + // First, test that the buffer is still empty after failed demanglings. + var res = demangle("abc123", into: buffer) + expectEqual(res, .invalidSymbol) + expectEqual(String(cString: ptr), "") + + res = demangle("Si", into: buffer) + expectEqual(res, .invalidSymbol) + expectEqual(String(cString: ptr), "") + + res = demangle("Swift is super cool!", into: buffer) + expectEqual(res, .invalidSymbol) + expectEqual(String(cString: ptr), "") + + // Test that correct symbols are demangled. (Test all documented prefixes) + res = demangle("_TSb", into: buffer) + print("res = \(String(cString: ptr)) len:\(String(cString: ptr).count)") + expectEqual(res, .success) + expectEqual(String(cString: ptr), "Swift.Bool") + + res = demangle("_T0Si", into: buffer) + print("res = \(String(cString: ptr)) len:\(String(cString: ptr).count)") + expectEqual(res, .success) + expectEqual(String(cString: ptr), "Swift.Int") + + res = demangle("$SSdXSaXSq", into: buffer) + print("res = \(String(cString: ptr)) len:\(String(cString: ptr).count)") + expectEqual(res, .success) + expectEqual(String(cString: ptr), "[Swift.Double]?") + + res = demangle("_$S8Demangle4main4argc4argvs5Int32VAF_SpySpys4Int8VGSgGtF", into: buffer) + print("res = \(String(cString: ptr)) len:\(String(cString: ptr).count)") + expectEqual(res, .success) + expectEqual(String(cString: ptr), "Demangle.main(argc: Swift.Int32, argv: Swift.UnsafeMutablePointer>>) -> Swift.Int32") + + res = demangle("$sSG", into: buffer) + print("res = \(String(cString: ptr)) len:\(String(cString: ptr).count)") + expectEqual(res, .success) + expectEqual(String(cString: ptr), "Swift.RandomNumberGenerator") + + res = demangle("_$sSS7cStringSSSPys4Int8VG_tcfC", into: buffer) + print("res = \(String(cString: ptr)) len:\(String(cString: ptr).count)") + expectEqual(res, .success) + expectEqual(String(cString: ptr), "Swift.String.init(cString: Swift.UnsafePointer) -> Swift.String") + } -DemangleTests.test("buffer API - too small buffer") { - // Test the return of demangle into with a smaller buffer. - // Swift.Int requires 10 bytes, give this 9 - let smolBuffer = UnsafeMutableBufferPointer.allocate(capacity: 9) - defer { smolBuffer.deallocate() } + DemangleTests.test("buffer API - too small buffer") { + // Test the return of demangle into with a smaller buffer. + // Swift.Int requires 10 bytes, give this 9 + let smolBuffer = UnsafeMutableBufferPointer.allocate(capacity: 9) + defer { smolBuffer.deallocate() } - let smolPtr = smolBuffer.baseAddress! + let smolPtr = smolBuffer.baseAddress! - let fail = demangle("$sSi", into: smolBuffer) - expectEqual(fail, .truncated(10)) - expectEqual(String(cString: smolPtr), "Swift.In") -} + let fail = demangle("$sSi", into: smolBuffer) + expectEqual(fail, .truncated(10)) + expectEqual(String(cString: smolPtr), "Swift.In") + } -DemangleTests.test("buffer API - small buffer, success") { - let smolBuffer = UnsafeMutableBufferPointer.allocate(capacity: 9) - defer { smolBuffer.deallocate() } + DemangleTests.test("buffer API - small buffer, success") { + let smolBuffer = UnsafeMutableBufferPointer.allocate(capacity: 9) + defer { smolBuffer.deallocate() } - let smolPtr = smolBuffer.baseAddress! - // Test nil return on successful demangle. - let success = demangle("$s4Smol3IntV", into: smolBuffer) - expectEqual(success, .success) - expectEqual(String(cString: smolPtr), "Smol.Int") + let smolPtr = smolBuffer.baseAddress! + // Test nil return on successful demangle. + let success = demangle("$s4Smol3IntV", into: smolBuffer) + expectEqual(success, .success) + expectEqual(String(cString: smolPtr), "Smol.Int") + } } runAllTests() \ No newline at end of file