From 44b807d0f0683f0ca5adca32fa9c273acfcf942b Mon Sep 17 00:00:00 2001 From: Tony Arnold Date: Wed, 25 May 2022 14:21:33 +1000 Subject: [PATCH 1/2] Revise SE-0262 to include feedback from initial review --- proposals/0262-demangle.md | 110 ++++++++++++------------------------- 1 file changed, 34 insertions(+), 76 deletions(-) diff --git a/proposals/0262-demangle.md b/proposals/0262-demangle.md index 447a02c7f9..43f4a57a54 100644 --- a/proposals/0262-demangle.md +++ b/proposals/0262-demangle.md @@ -1,7 +1,7 @@ # Demangle Function * Proposal: [SE-0262](0262-demangle.md) -* Author: [Alejandro Alonso](https://github.com/Azoy) +* Author: [Alejandro Alonso](https://github.com/Azoy), [Tony Arnold](https://github.com/tonyarnold) * Review Manager: [Joe Groff](https://github.com/jckarter) * Status: **Returned for revision** * Implementation: [apple/swift#25314](https://github.com/apple/swift/pull/25314) @@ -9,104 +9,44 @@ ## 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.String`. +Introduce a new standard library function, `demangle`, that takes a mangled Swift symbol such as `$sSS7cStringSSSPys4Int8VG_tcfC`, and --- if it can --- outputs the human readable Swift symbol, like `Swift.String.init(cString: Swift.UnsafePointer) -> Swift.String`. Swift-evolution thread: [Demangle Function](https://forums.swift.org/t/demangle-function/25416) ## 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. +Currently, 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 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 current process to use `swift-demangle`, but the standard library can do this more easily, and without the intermediary steps. ## Proposed solution -The standard library will add the following 3 new functions. +The standard library will add the following new enumeration and function: ```swift -// 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, - into buffer: UnsafeMutableBufferPointer -) -> 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 -) -> DemangleResult -``` - -as well as the following enum to indicate success or the different forms of failure: - -```swift -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 +public enum DemangledStyle { + case full + case simplified } + +/// Given a mangled Swift symbol, return the demangled symbol. Defaults to the simplified style used by LLDB, Instruments and similar tools. +public func demangle(_ input: String, style: DemangledStyle = .simplified) -> String? ``` Examples: ```swift print(demangle("$s8Demangle3FooV")!) // Demangle.Foo - -// Demangle.Foo is 13 characters + 1 null terminator -let buffer = UnsafeMutableBufferPointer.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 ``` ## 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 to indicate failure. With the `(String, into: UnsafeMutableBufferPointer) -> 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 -// Swift.Int requires 10 bytes = 9 characters + 1 null terminator -// Give this 9 to exercise truncation -let buffer = UnsafeMutableBufferPointer.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) -``` +If one were to pass a string that wasn't a valid Swift mangled symbol, like `abc123`, then the `(String) -> String?` will return `nil` to indicate failure. This implementation relies on the Swift runtime function `swift_demangle` which accepts symbols that start with `_T`, `_T0`, `$S`, and `$s`. +The `style` parameter of the `demangle(…)` function accepts one of two potential cases: +- `full`: this is equivalent to the output of `swift-demangle` +- `simplified`: this is equivalent to the output of `swift-demangle -simplified` + ## Source compatibility These are completely new standard library functions, thus source compatibility is unaffected. @@ -121,7 +61,25 @@ These are completely new standard library functions, thus API resilience is unaf ## 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 variants where you don't want to alloc new memory and want to pass in some memory you've already allocated. +Earlier versions of this proposal included additional functions that supported demangling in limited runtime contexts using unsafe buffer-based APIs: + +```swift +public func demangle( + _ mangledNameBuffer: UnsafeBufferPointer, + into buffer: UnsafeMutableBufferPointer +) -> DemangleResult + +public func demangle( + _ input: String, + into buffer: UnsafeMutableBufferPointer +) -> DemangleResult +``` + +Unfortunately, the current demangler implementation is not suitable for such applications, because even if it were given a preallocated output buffer for the returned string, it still freely allocates in the course of parsing the mangling and forming the parse tree for it. Presenting an API that might seem safe for use in contexts that can't allocate would be misleading. + +This alternative could be considered under “Future Directions” as well, if/when the underlying implementation is made suitable for this purpose. + +Discussion on the forums also raised the concern of polluting the global namespace, and the suggestion was made to create a new “Runtime” module to house this function (and potentially others). The Core Team thought that the proposed demangle function makes sense as a standalone, top-level function, however it would be a natural candidate for inclusion in such a module if it existed. ## Future Directions From cec3d33b74fcaccb91f7d6fdae8b8fe93c52284d Mon Sep 17 00:00:00 2001 From: Tony Arnold Date: Sat, 28 May 2022 10:55:51 +1000 Subject: [PATCH 2/2] Update SE-0262 based on feedback and implementation findings --- proposals/0262-demangle.md | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/proposals/0262-demangle.md b/proposals/0262-demangle.md index 43f4a57a54..c6ae677702 100644 --- a/proposals/0262-demangle.md +++ b/proposals/0262-demangle.md @@ -22,30 +22,38 @@ Currently, if a user is given an unreadable mangled symbol, they're most likely The standard library will add the following new enumeration and function: ```swift -public enum DemangledStyle { +/// Represents the demangler function output style. +public enum DemangledOutputStyle { + /// Includes module names and implicit self types. case full + /// Excludes module names and implicit self types. case simplified } /// Given a mangled Swift symbol, return the demangled symbol. Defaults to the simplified style used by LLDB, Instruments and similar tools. -public func demangle(_ input: String, style: DemangledStyle = .simplified) -> String? +public func demangle( + _ input: String, + outputStyle: DemangledOutputStyle = .simplified +) -> String? ``` Examples: ```swift -print(demangle("$s8Demangle3FooV")!) // Demangle.Foo +print(demangle("$s8Demangle3FooV")!) // Foo + +print(demangle("$s8Demangle3FooV", outputStyle: .full)!) // Demangle.Foo ``` ## Detailed design -If one were to pass a string that wasn't a valid Swift mangled symbol, like `abc123`, then the `(String) -> String?` will return `nil` to indicate failure. +If one were to pass a string that wasn't a valid Swift mangled symbol, like `abc123`, then the function will return `nil` to indicate failure. This implementation relies on the Swift runtime function `swift_demangle` which accepts symbols that start with `_T`, `_T0`, `$S`, and `$s`. -The `style` parameter of the `demangle(…)` function accepts one of two potential cases: +The `outputStyle` parameter of the `demangle(…)` function accepts one of two potential cases: - `full`: this is equivalent to the output of `swift-demangle` -- `simplified`: this is equivalent to the output of `swift-demangle -simplified` +- `simplified`: this is equivalent to the output of `swift-demangle --simplified` ## Source compatibility