- Proposal: SE-0262
- Author: Alejandro Alonso
- Review Manager: Joe Groff
- Status: Returned for revision
- Implementation: apple/swift#25314
- Decision Notes: Returned for revision
Introduce a new standard library function, demangle
, that takes a mangled Swift symbol, like $sSS7cStringSSSPys4Int8VG_tcfC
, and output the human readable Swift symbol, like Swift.String.init(cString: Swift.UnsafePointer<Swift.Int8>) -> Swift.String
.
Swift-evolution thread: Demangle Function
Currently in Swift, if a user is given an unreadable mangled symbol, they're most likely to use the swift-demangle
tool to get the demangled version. However, this is a little awkward when you want to demangle a symbol in-process in Swift. One could create a new Process
from Foundation and set it up to launch a new process within the process to use swift-demangle
, but the standard library can do better and easier.
The standard library will add the following 3 new functions.
// Given a mangled Swift symbol, return the demangled symbol.
public func demangle(_ input: String) -> String?
// Given a mangled Swift symbol in a buffer and a preallocated buffer,
// write the demangled symbol into the buffer.
public func demangle(
_ mangledNameBuffer: UnsafeBufferPointer<Int8>,
into buffer: UnsafeMutableBufferPointer<Int8>
) -> DemangleResult
// Given a mangled Swift symbol and a preallocated buffer,
// write the demangle symbol into the buffer.
public func demangle(
_ input: String,
into buffer: UnsafeMutableBufferPointer<Int8>
) -> DemangleResult
as well as the following enum to indicate success or the different forms of failure:
public enum DemangleResult: Equatable {
// The demangle was successful
case success
// The result was truncated. Payload contains the number of bytes
// required for the complete demangle.
case truncated(Int)
// The given Swift symbol was invalid.
case invalidSymbol
}
Examples:
print(demangle("$s8Demangle3FooV")!) // Demangle.Foo
// Demangle.Foo is 13 characters + 1 null terminator
let buffer = UnsafeMutableBufferPointer<Int8>.allocate(capacity: 14)
defer { buffer.deallocate() }
let result = demangle("$s8Demangle3BarV", into: buffer)
guard result == .success else {
// Handle failure here
switch result {
case let .truncated(required):
print("We need \(required - buffer.count) more bytes!")
case .invalidSymbol:
print("I was given a faulty symbol?!")
default:
break
}
return
}
print(String(cString: buffer.baseAddress!)) // Demangle.Foo
If one were to pass a string that wasn't a valid Swift mangled symbol, like abc123
, then the (String) -> String?
would simply return nil to indicate failure. With the (String, into: UnsafeMutableBufferPointer<Int8>) -> DemangleResult
version and the buffer input version, we wouldn't write the passed string into the buffer if it were invalid.
This proposal includes a trivial (String) -> String?
version of the function, as well as a version that takes a buffer. In addition to the invalid input error case, the buffer variants can also fail due to truncation. This occurs when the output buffer doesn't have enough allocated space for the entire demangled result. In this case, we return .truncated(Int)
where the payload is equal to the total number of bytes required for the entire demangled result. We're still able to demangle a truncated version of the symbol into the buffer, but not the whole symbol if the buffer is smaller than needed. E.g.
// Swift.Int requires 10 bytes = 9 characters + 1 null terminator
// Give this 9 to exercise truncation
let buffer = UnsafeMutableBufferPointer<Int8>.allocate(capacity: 9)
defer { buffer.deallocate() }
if case let .truncated(required) = demangle("$sSi", into: buffer) {
print(required) // 10 (this is the amount needed for the full Swift.Int)
let difference = required - buffer.count
print(difference) // 1 (we only need 1 more byte in addition to the 9 we already allocated)
}
print(String(cString: buffer.baseAddress!)) // Swift.In (notice the missing T)
This implementation relies on the Swift runtime function swift_demangle
which accepts symbols that start with _T
, _T0
, $S
, and $s
.
These are completely new standard library functions, thus source compatibility is unaffected.
These are completely new standard library functions, thus ABI compatibility is unaffected.
These are completely new standard library functions, thus API resilience is unaffected.
We could choose to only provide one of the proposed functions, but each of these brings unique purposes. The trivial take a string and return a string version is a very simplistic version in cases where maybe you're not worried about allocating new memory, and the buffer variants where you don't want to alloc new memory and want to pass in some memory you've already allocated.
The swift_demangle
runtime function has an extra flags
parameter, but currently it is not being used for anything. In the future if that function ever supports any flags, it would make sense to introduce new overloads or something similar to expose those flags to the standard library as well. E.g.
public func demangle(_ input: String, flags: DemangleFlags) -> String?
public func demangle(
_ mangledNameBuffer: UnsafeBufferPointer<Int8>,
into buffer: UnsafeMutableBufferPointer<Int8>,
flags: DemangleFlags
) -> DemangleResult
public func demangle(
_ input: String,
into buffer: UnsafeMutableBufferPointer<Int8>,
flags: DemangleFlags
) -> DemangleResult
where DemangleFlags
could be an enum, OptionSet
, [DemangleFlag]
, etc.