-
Notifications
You must be signed in to change notification settings - Fork 43
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit d642751
Showing
47 changed files
with
3,362 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,112 @@ | ||
import Foundation | ||
|
||
public enum DemangleResult { | ||
case Success(String) | ||
case Ignored(String) | ||
case Failed(String?) | ||
} | ||
|
||
public final class Demangler { | ||
private let ExtractFromObjCRegex = try! NSRegularExpression(pattern: "^[-+]\\[([^\\]]+)\\s+[^\\]]+\\]$", | ||
options: [.AnchorsMatchLines]) | ||
|
||
private var internalDemangler: InternalDemangler = LibraryDemangler() | ||
|
||
public init() {} | ||
|
||
/** | ||
Demangle a mangled swift string | ||
- parameter name: A mangled swift string | ||
- returns: The demangled name | ||
*/ | ||
public func demangle(string mangledName: String?) -> DemangleResult { | ||
guard let string = mangledName else { | ||
return .Failed(mangledName) | ||
} | ||
|
||
if !self.shouldDemangle(string: string) { | ||
return .Ignored(string) | ||
} | ||
|
||
let (extracted, wasExtracted) = self.extractMangledString(fromObjCSelector: string) | ||
if let demangled = self.internalDemangler.demangle(string: extracted) { | ||
if wasExtracted { | ||
return .Success(self.integrateDemangledString(intoSelector: string, string: demangled)) | ||
} else { | ||
return .Success(demangled) | ||
} | ||
} else { | ||
return .Failed(string) | ||
} | ||
} | ||
|
||
/** | ||
Integrate a demangled name back into the original ObjC selector it was pulled from. | ||
Example: -[_MangledName_ foo] -> -[Demangled foo] | ||
- parameter selector: The original ObjC style selector | ||
- parameter name: The demangled name | ||
- returns: The demangled name integrated into the ObjC selector | ||
*/ | ||
func integrateDemangledString(intoSelector selector: String, string: String) -> String { | ||
let range = NSRange(location: 0, length: selector.characters.count) | ||
let matches = self.ExtractFromObjCRegex.matchesInString(selector, options: [], range: range) | ||
assert(matches.count <= 1) | ||
assert(matches.first?.numberOfRanges == 2) | ||
|
||
let match = matches.first! | ||
let matchRange = match.rangeAtIndex(1) | ||
let selectorString = selector as NSString | ||
let start = selectorString.substringWithRange(NSRange(location: 0, length: matchRange.location)) | ||
let position = matchRange.location + matchRange.length | ||
let end = selectorString.substringWithRange(NSRange(location: position, | ||
length: selectorString.length - position)) | ||
return "\(start)\(string)\(end)" | ||
} | ||
|
||
func extractMangledString(fromObjCSelector selector: String) -> (String, Bool) { | ||
let range = NSRange(location: 0, length: selector.characters.count) | ||
let matches = self.ExtractFromObjCRegex.matchesInString(selector, options: [], range: range) | ||
assert(matches.count <= 1) | ||
|
||
if let match = matches.first where match.numberOfRanges == 2 { | ||
let range = match.rangeAtIndex(1) | ||
return ((selector as NSString).substringWithRange(range), true) | ||
} | ||
|
||
return (selector, false) | ||
} | ||
|
||
/** | ||
Does a simple check to see if the string should be demangled. This only exists to reduce the number of | ||
times we have to shell out to the demangle tool. | ||
- SEEALSO: https://github.com/apple/swift/blob/82509cbd7451e72fb99d22556ad259ceb335cb1f/lib/SwiftDemangle/SwiftDemangle.cpp#L22 | ||
- parameter string: The possibly mangled string | ||
- returns: true if a demangle should be attempted | ||
*/ | ||
func shouldDemangle(string string: String) -> Bool { | ||
if string.hasPrefix("__T") { | ||
return true | ||
} | ||
|
||
if string.hasPrefix("_T") { | ||
return true | ||
} | ||
|
||
if string.hasPrefix("-[_T") { | ||
return true | ||
} | ||
|
||
if string.hasPrefix("+[_T") { | ||
return true | ||
} | ||
|
||
return false | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
protocol InternalDemangler { | ||
func demangle(string string: String) -> String? | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
import Foundation | ||
|
||
private typealias SwiftDemangleFunction = @convention(c) (UnsafePointer<CChar>, | ||
UnsafeMutablePointer<CChar>, size_t) -> size_t | ||
|
||
private let kDemangleLibraryPath = ("/Applications/Xcode.app/Contents/Developer/Toolchains" + | ||
"/XcodeDefault.xctoolchain/usr/lib/libswiftDemangle.dylib") | ||
private let kBufferSize = 1024 | ||
|
||
final class LibraryDemangler: InternalDemangler { | ||
private var handle: UnsafeMutablePointer<Void> = { | ||
return dlopen(kDemangleLibraryPath, RTLD_NOW) | ||
}() | ||
|
||
private lazy var internalDemangleFunction: SwiftDemangleFunction = { | ||
let address = dlsym(self.handle, "swift_demangle_getDemangledName") | ||
return unsafeBitCast(address, SwiftDemangleFunction.self) | ||
}() | ||
|
||
func demangle(string string: String) -> String? { | ||
let formattedString = self.removingExcessLeadingUnderscores(fromString: string) | ||
let outputString = UnsafeMutablePointer<CChar>.alloc(kBufferSize) | ||
let resultSize = self.internalDemangleFunction(formattedString, outputString, kBufferSize) | ||
if resultSize > kBufferSize { | ||
NSLog("Attempted to demangle string with length \(resultSize) but buffer size \(kBufferSize)") | ||
} | ||
|
||
return String(CString: outputString, encoding: NSUTF8StringEncoding) | ||
} | ||
|
||
private func removingExcessLeadingUnderscores(fromString string: String) -> String { | ||
if string.hasPrefix("__T") { | ||
return String(string.characters.dropFirst()) | ||
} | ||
|
||
return string | ||
} | ||
|
||
deinit { | ||
dlclose(self.handle) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
import Foundation | ||
|
||
final class ShellDemangler: InternalDemangler { | ||
private lazy var executablePath: String = { | ||
return self.output(forShellCommand: "/usr/bin/xcrun --find swift-demangle")! | ||
}() | ||
|
||
func demangle(string string: String) -> String? { | ||
return self.output(forShellCommand: self.demangleCommand(forString: string)) | ||
} | ||
|
||
private func demangleCommand(forString string: String) -> String { | ||
return "\(self.executablePath) -compact \(string)" | ||
} | ||
|
||
private func output(forShellCommand command: String) -> String? { | ||
assert(command.split().count >= 2) | ||
|
||
let task = NSTask() | ||
let pipe = NSPipe() | ||
let components = command.split() | ||
|
||
task.launchPath = components.first | ||
task.arguments = Array(components.dropFirst()) | ||
task.standardOutput = pipe | ||
|
||
task.launch() | ||
task.waitUntilExit() | ||
|
||
let data = pipe.fileHandleForReading.readDataToEndOfFile() | ||
return String(data: data, encoding: NSUTF8StringEncoding)?.strip() | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
import Foundation | ||
|
||
extension String { | ||
func split() -> [String] { | ||
return self.characters.split(" ").map(String.init) | ||
} | ||
|
||
func strip() -> String { | ||
return self.stringByTrimmingCharactersInSet(NSCharacterSet.whitespaceAndNewlineCharacterSet()) | ||
} | ||
} | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
<?xml version="1.0" encoding="UTF-8"?> | ||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> | ||
<plist version="1.0"> | ||
<dict> | ||
<key>CFBundleDevelopmentRegion</key> | ||
<string>en</string> | ||
<key>CFBundleExecutable</key> | ||
<string>$(EXECUTABLE_NAME)</string> | ||
<key>CFBundleIdentifier</key> | ||
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string> | ||
<key>CFBundleInfoDictionaryVersion</key> | ||
<string>6.0</string> | ||
<key>CFBundleName</key> | ||
<string>$(PRODUCT_NAME)</string> | ||
<key>CFBundlePackageType</key> | ||
<string>FMWK</string> | ||
<key>CFBundleShortVersionString</key> | ||
<string>1.0</string> | ||
<key>CFBundleSignature</key> | ||
<string>????</string> | ||
<key>CFBundleVersion</key> | ||
<string>$(CURRENT_PROJECT_VERSION)</string> | ||
<key>NSHumanReadableCopyright</key> | ||
<string>Copyright © 2016 smileykeith. All rights reserved.</string> | ||
<key>NSPrincipalClass</key> | ||
<string></string> | ||
</dict> | ||
</plist> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
@testable import Demangler | ||
import XCTest | ||
|
||
final class DemangleObjCSelectorTests: XCTestCase { | ||
private var demangler: Demangler! | ||
|
||
override func setUp() { | ||
super.setUp() | ||
|
||
demangler = Demangler() | ||
} | ||
|
||
func testExtractsString() { | ||
let (name, extracted) = demangler.extractMangledString(fromObjCSelector: "-[Foo bar]") | ||
|
||
XCTAssertEqual(name, "Foo") | ||
XCTAssertTrue(extracted) | ||
} | ||
|
||
func testDoesNotExtractNonObjCString() { | ||
let (name, extracted) = demangler.extractMangledString(fromObjCSelector: "_TCFoo") | ||
|
||
XCTAssertEqual(name, "_TCFoo") | ||
XCTAssertFalse(extracted) | ||
} | ||
|
||
func testIntegrateString() { | ||
let selector = demangler.integrateDemangledString(intoSelector: "-[Foo bar]", string: "Baz") | ||
|
||
XCTAssertEqual(selector, "-[Baz bar]") | ||
} | ||
|
||
func testIntegrateStringIntoClassSelector() { | ||
let selector = demangler.integrateDemangledString(intoSelector: "+[Foo.qux bar]", string: "Baz") | ||
|
||
XCTAssertEqual(selector, "+[Baz bar]") | ||
} | ||
} |
Oops, something went wrong.