From b2b4d1b5b0835e9e53e70e65a2be39ace257d31f Mon Sep 17 00:00:00 2001 From: Azoy Date: Sat, 8 Jun 2019 15:54:59 -0500 Subject: [PATCH] Introduce demangle function to stdlib newline remove ? remove user label prefixes Add swift_demangle shim in SwiftShims INTERNAL Update based on forum comments Update tests Address some feedback --- stdlib/public/SwiftShims/RuntimeShims.h | 7 ++ stdlib/public/core/CMakeLists.txt | 1 + stdlib/public/core/Demangle.swift | 143 ++++++++++++++++++++++++ stdlib/public/core/GroupInfo.json | 3 +- stdlib/public/stubs/Stubs.cpp | 9 ++ test/stdlib/Demangle.swift | 82 ++++++++++++++ 6 files changed, 244 insertions(+), 1 deletion(-) create mode 100644 stdlib/public/core/Demangle.swift create mode 100644 test/stdlib/Demangle.swift diff --git a/stdlib/public/SwiftShims/RuntimeShims.h b/stdlib/public/SwiftShims/RuntimeShims.h index b9da9c47c3566..4d553ae1f3202 100644 --- a/stdlib/public/SwiftShims/RuntimeShims.h +++ b/stdlib/public/SwiftShims/RuntimeShims.h @@ -59,6 +59,13 @@ int _swift_stdlib_putc_stderr(int C); SWIFT_RUNTIME_STDLIB_API __swift_size_t _swift_stdlib_getHardwareConcurrency(void); +SWIFT_RUNTIME_STDLIB_INTERNAL +char *_swift_stdlib_demangle(const char *mangledName, + __swift_size_t mangledNameLength, + char *outputBuffer, + __swift_size_t *outputBufferSize, + __swift_uint32_t flags); + /// Manually allocated memory is at least 16-byte aligned in Swift. /// /// When swift_slowAlloc is called with "default" alignment (alignMask == diff --git a/stdlib/public/core/CMakeLists.txt b/stdlib/public/core/CMakeLists.txt index ecb5a5a0743a6..42207d448f2df 100644 --- a/stdlib/public/core/CMakeLists.txt +++ b/stdlib/public/core/CMakeLists.txt @@ -50,6 +50,7 @@ set(SWIFTLIB_ESSENTIAL CString.swift CTypes.swift DebuggerSupport.swift + Demangle.swift Dictionary.swift DictionaryBridging.swift DictionaryBuilder.swift diff --git a/stdlib/public/core/Demangle.swift b/stdlib/public/core/Demangle.swift new file mode 100644 index 0000000000000..be9557529bcc3 --- /dev/null +++ b/stdlib/public/core/Demangle.swift @@ -0,0 +1,143 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2019 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 +// +//===----------------------------------------------------------------------===// + +import SwiftShims + +/// Represents the potential return types from a call to demangle. +public enum DemangleResult: Equatable { + /// The demangle completed successfully. + case success + + /// The demangle 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 +} + +/// 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. +public func demangle(_ mangledName: String) -> String? { + return mangledName.utf8CString.withUnsafeBufferPointer { + let demangledPtr = _swift_stdlib_demangle( + /* mangledName */ $0.baseAddress, + /* mangledNameLength */ $0.count - 1, + /* outputBuffer */ nil, + /* outputBufferSize */ nil, + /* flags */ 0 + ) + + guard demangledPtr != nil else { + return nil + } + + let demangledName = String(cString: demangledPtr!) + _swift_stdlib_free(demangledPtr!) + return demangledName + } +} + +/// 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 into. +/// - Returns: An enum, `DemangleResult`, indicating the various result states +/// of demangling. +public func demangle( + _ mangledNameBuffer: UnsafeBufferPointer, + into buffer: UnsafeMutableBufferPointer +) -> DemangleResult { + var bufferSize = buffer.count + + let demangledPtr = _swift_stdlib_demangle( + /* mangledName */ mangledNameBuffer.baseAddress, + /* mangledNameLength */ mangledNameBuffer.count - 1, + /* outputBuffer */ buffer.baseAddress, + /* outputBufferSize */ &bufferSize, + /* flags */ 0 + ) + + guard demangledPtr != nil 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 into. +/// - Returns: An enum, `DemangleResult`, indicating the various result states +/// of demangling. +public func demangle( + _ mangledName: String, + into buffer: UnsafeMutableBufferPointer +) -> DemangleResult { + mangledName.utf8CString.withUnsafeBufferPointer { + demangle($0, into: buffer) + } +} diff --git a/stdlib/public/core/GroupInfo.json b/stdlib/public/core/GroupInfo.json index a09e00d5c39c3..79d727c0b45c6 100644 --- a/stdlib/public/core/GroupInfo.json +++ b/stdlib/public/core/GroupInfo.json @@ -223,7 +223,8 @@ "Comparable.swift", "Codable.swift", "LegacyABI.swift", - "MigrationSupport.swift" + "MigrationSupport.swift", + "Demangle.swift" ], "Result": [ "Result.swift" diff --git a/stdlib/public/stubs/Stubs.cpp b/stdlib/public/stubs/Stubs.cpp index 55bada6a1db92..666815dd70c23 100644 --- a/stdlib/public/stubs/Stubs.cpp +++ b/stdlib/public/stubs/Stubs.cpp @@ -76,6 +76,7 @@ static float swift_strtof_l(const char *nptr, char **endptr, locale_t loc) { #include #include "llvm/ADT/StringExtras.h" #include "llvm/Support/Compiler.h" +#include "swift/Demangling/Demangle.h" #include "swift/Runtime/Debug.h" #include "swift/Runtime/SwiftDtoa.h" #include "swift/Basic/Lazy.h" @@ -524,3 +525,11 @@ size_t swift::_swift_stdlib_getHardwareConcurrency() { return std::thread::hardware_concurrency(); } +char *swift::_swift_stdlib_demangle(const char *mangledName, + __swift_size_t mangledNameLength, + char *outputBuffer, + __swift_size_t *outputBufferSize, + __swift_uint32_t flags) { + return swift_demangle(mangledName, mangledNameLength, outputBuffer, + outputBufferSize, flags); +} diff --git a/test/stdlib/Demangle.swift b/test/stdlib/Demangle.swift new file mode 100644 index 0000000000000..690a03c0b1eae --- /dev/null +++ b/test/stdlib/Demangle.swift @@ -0,0 +1,82 @@ +// RUN: %target-run-simple-swift +// REQUIRES: executable_test + +// We don't really want to excerise actual demangling here, but rather just that +// the stdlib demangle function actually works as intended. + +import Swift +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. + expectEqual(demangle("abc123", into: buffer), .invalidSymbol) + expectEqual(String(cString: ptr), "") + + expectEqual(demangle("Si", into: buffer), .invalidSymbol) + expectEqual(String(cString: ptr), "") + + expectEqual(demangle("Swift is super cool!", into: buffer), .invalidSymbol) + expectEqual(String(cString: ptr), "") + + // Test that correct symbols are demangled. (Test all documented prefixes) + expectEqual(demangle("_TSb", into: buffer), .success) + expectEqual(String(cString: ptr), "Swift.Bool") + + expectEqual(demangle("_T0Si", into: buffer), .success) + expectEqual(String(cString: ptr), "Swift.Int") + + expectEqual(demangle("$SSdXSaXSq", into: buffer), .success) + expectEqual(String(cString: ptr), "[Swift.Double]?") + + expectEqual(demangle("_$S8Demangle4main4argc4argvs5Int32VAF_SpySpys4Int8VGSgGtF", into: buffer), .success) + expectEqual(String(cString: ptr), "Demangle.main(argc: Swift.Int32, argv: Swift.UnsafeMutablePointer>>) -> Swift.Int32") + + expectEqual(demangle("$sSG", into: buffer), .success) + expectEqual(String(cString: ptr), "Swift.RandomNumberGenerator") + + expectEqual(demangle("_$sSS7cStringSSSPys4Int8VG_tcfC", into: buffer), .success) + expectEqual(String(cString: ptr), "Swift.String.init(cString: Swift.UnsafePointer) -> Swift.String") + + // 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") + + // Test nil return on successful demangle. + let success = demangle("$s4Smol3IntV", into: smolBuffer) + expectEqual(success, .success) + expectEqual(String(cString: smolPtr), "Smol.Int") +} + +runAllTests()