I wrote up a quick proposal that proposes what is currently implemented, although design discussion, proposal fixes, etc. is always welcome!
Demangle Function
Introduction
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
Motivation
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.
Proposed solution
The standard library will add the following 2 new functions.
// Given a mangled Swift symbol, return the demangled symbol.
public func demangle(_ input: String) -> String?
// Given a mangled Swift symbol and a preallocated buffer,
// write the demangle symbol into the buffer.
@discardableResult
public func demangle(
_ input: String,
into buffer: UnsafeMutableBufferPointer<Int8>
) -> Int?
Examples:
print(demangle("$s8Demangle3FooV")!) // Demangle.Foo
// Demangle.Foo is 13 characters + 1 null terminator
let buffer = UnsafeMutableBufferPointer<Int8>.allocate(
capacity: 14
)
defer { buffer.deallocate() }
demangle("$s8Demangle3BarV", into: buffer)
print(String(cString: buffer.baseAddress!)) // Demangle.Bar
Detailed design
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. With the (String, into: UnsafeMutableBufferPointer<Int8>) -> Int?
version, we would return nil indicating success, but wouldn't write the passed string into the buffer.
This proposal includes a trivial (String) -> String?
version of the function, as well as a version that takes a buffer. The buffer version is marked @discardableResult
because it returns an optional integer indicating whether or not we were able to fully demangle the symbol given the buffer's size. In the case of a successful demangle, this functions returns nil, however in the case that it's not nil, the integer returned is the number of bytes required for the full demangle. We're still able to demangle a truncated version of the symbol, but not the whole symbol if the buffer is smaller than needed (Because this byte sequence could be truncated at any point, there is a possibility of breaking a non-ascii sequence resulting in unknown text. You might be demangling a declaration with in the name, but truncation could break the emoji sequence). E.g.
// Swift.Int requires 10 bytes = 9 characters + 1 null terminator
// Give this 9 to excercise truncation
let buffer = UnsafeMutableBufferPointer<Int8>.allocate(
capacity: 9
)
defer { buffer.deallocate() }
if let 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
This implementation relies on the Swift runtime function swift_demangle
which accepts symbols that start with _T
, _T0
, $S
, and $s
.
Source compatibility
These are completely new standard library functions, thus source compatibility is unaffected.
Effect on ABI stability
These are completely new standard library functions, thus ABI compatibility is unaffected.
Effect on API resilience
These are completely new standard library functions, thus API resilience is unaffected.
Alternatives considered
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 version where you don't want to alloc new memory and want to pass in some memory you've already allocated.
Future Directions
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(
_ input: String,
into buffer: UnsafeMutableBufferPointer<Int8>,
flags: DemangleFlags
) -> Int?
where DemangleFlags
could be an enum, OptionSet
, [DemangleFlag]
, etc.