Skip to content

Commit d642751

Browse files
committed
Initial Commit
0 parents  commit d642751

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

47 files changed

+3362
-0
lines changed

.gitignore

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
# Xcode
2+
#
3+
# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
4+
5+
## Build generated
6+
build/
7+
DerivedData/
8+
9+
## Various settings
10+
*.pbxuser
11+
!default.pbxuser
12+
*.mode1v3
13+
!default.mode1v3
14+
*.mode2v3
15+
!default.mode2v3
16+
*.perspectivev3
17+
!default.perspectivev3
18+
xcuserdata/
19+
20+
## Other
21+
*.moved-aside
22+
*.xcuserstate
23+
24+
## Obj-C/Swift specific
25+
*.hmap
26+
*.ipa
27+
*.dSYM.zip
28+
*.dSYM
29+
30+
# CocoaPods
31+
#
32+
# We recommend against adding the Pods directory to your .gitignore. However
33+
# you should judge for yourself, the pros and cons are mentioned at:
34+
# https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
35+
#
36+
# Pods/
37+
38+
# Carthage
39+
#
40+
# Add this line if you want to avoid checking in source code from Carthage dependencies.
41+
# Carthage/Checkouts
42+
43+
Carthage/Build
44+
45+
# fastlane
46+
#
47+
# It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the
48+
# screenshots whenever they are needed.
49+
# For more information about the recommended setup visit:
50+
# https://github.com/fastlane/fastlane/blob/master/fastlane/docs/Gitignore.md
51+
52+
fastlane/report.xml
53+
fastlane/screenshots
54+
55+
#Code Injection
56+
#
57+
# After new code Injection tools there's a generated folder /iOSInjectionProject
58+
# https://github.com/johnno1962/injectionforxcode
59+
60+
iOSInjectionProject/
61+
*.DS_Store
62+
.AppleDouble
63+
.LSOverride
64+
65+
# Icon must end with two \r
66+
Icon
67+
68+
# Thumbnails
69+
._*
70+
71+
# Files that might appear in the root of a volume
72+
.DocumentRevisions-V100
73+
.fseventsd
74+
.Spotlight-V100
75+
.TemporaryItems
76+
.Trashes
77+
.VolumeIcon.icns
78+
.com.apple.timemachine.donotpresent
79+
80+
# Directories potentially created on remote AFP share
81+
.AppleDB
82+
.AppleDesktop
83+
Network Trash Folder
84+
Temporary Items
85+
.apdisk

Demangler/Demangler.swift

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
import Foundation
2+
3+
public enum DemangleResult {
4+
case Success(String)
5+
case Ignored(String)
6+
case Failed(String?)
7+
}
8+
9+
public final class Demangler {
10+
private let ExtractFromObjCRegex = try! NSRegularExpression(pattern: "^[-+]\\[([^\\]]+)\\s+[^\\]]+\\]$",
11+
options: [.AnchorsMatchLines])
12+
13+
private var internalDemangler: InternalDemangler = LibraryDemangler()
14+
15+
public init() {}
16+
17+
/**
18+
Demangle a mangled swift string
19+
20+
- parameter name: A mangled swift string
21+
22+
- returns: The demangled name
23+
*/
24+
public func demangle(string mangledName: String?) -> DemangleResult {
25+
guard let string = mangledName else {
26+
return .Failed(mangledName)
27+
}
28+
29+
if !self.shouldDemangle(string: string) {
30+
return .Ignored(string)
31+
}
32+
33+
let (extracted, wasExtracted) = self.extractMangledString(fromObjCSelector: string)
34+
if let demangled = self.internalDemangler.demangle(string: extracted) {
35+
if wasExtracted {
36+
return .Success(self.integrateDemangledString(intoSelector: string, string: demangled))
37+
} else {
38+
return .Success(demangled)
39+
}
40+
} else {
41+
return .Failed(string)
42+
}
43+
}
44+
45+
/**
46+
Integrate a demangled name back into the original ObjC selector it was pulled from.
47+
Example: -[_MangledName_ foo] -> -[Demangled foo]
48+
49+
- parameter selector: The original ObjC style selector
50+
- parameter name: The demangled name
51+
52+
- returns: The demangled name integrated into the ObjC selector
53+
*/
54+
func integrateDemangledString(intoSelector selector: String, string: String) -> String {
55+
let range = NSRange(location: 0, length: selector.characters.count)
56+
let matches = self.ExtractFromObjCRegex.matchesInString(selector, options: [], range: range)
57+
assert(matches.count <= 1)
58+
assert(matches.first?.numberOfRanges == 2)
59+
60+
let match = matches.first!
61+
let matchRange = match.rangeAtIndex(1)
62+
let selectorString = selector as NSString
63+
let start = selectorString.substringWithRange(NSRange(location: 0, length: matchRange.location))
64+
let position = matchRange.location + matchRange.length
65+
let end = selectorString.substringWithRange(NSRange(location: position,
66+
length: selectorString.length - position))
67+
return "\(start)\(string)\(end)"
68+
}
69+
70+
func extractMangledString(fromObjCSelector selector: String) -> (String, Bool) {
71+
let range = NSRange(location: 0, length: selector.characters.count)
72+
let matches = self.ExtractFromObjCRegex.matchesInString(selector, options: [], range: range)
73+
assert(matches.count <= 1)
74+
75+
if let match = matches.first where match.numberOfRanges == 2 {
76+
let range = match.rangeAtIndex(1)
77+
return ((selector as NSString).substringWithRange(range), true)
78+
}
79+
80+
return (selector, false)
81+
}
82+
83+
/**
84+
Does a simple check to see if the string should be demangled. This only exists to reduce the number of
85+
times we have to shell out to the demangle tool.
86+
87+
- SEEALSO: https://github.com/apple/swift/blob/82509cbd7451e72fb99d22556ad259ceb335cb1f/lib/SwiftDemangle/SwiftDemangle.cpp#L22
88+
89+
- parameter string: The possibly mangled string
90+
91+
- returns: true if a demangle should be attempted
92+
*/
93+
func shouldDemangle(string string: String) -> Bool {
94+
if string.hasPrefix("__T") {
95+
return true
96+
}
97+
98+
if string.hasPrefix("_T") {
99+
return true
100+
}
101+
102+
if string.hasPrefix("-[_T") {
103+
return true
104+
}
105+
106+
if string.hasPrefix("+[_T") {
107+
return true
108+
}
109+
110+
return false
111+
}
112+
}

Demangler/InternalDemangler.swift

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
protocol InternalDemangler {
2+
func demangle(string string: String) -> String?
3+
}

Demangler/LibraryDemangler.swift

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import Foundation
2+
3+
private typealias SwiftDemangleFunction = @convention(c) (UnsafePointer<CChar>,
4+
UnsafeMutablePointer<CChar>, size_t) -> size_t
5+
6+
private let kDemangleLibraryPath = ("/Applications/Xcode.app/Contents/Developer/Toolchains" +
7+
"/XcodeDefault.xctoolchain/usr/lib/libswiftDemangle.dylib")
8+
private let kBufferSize = 1024
9+
10+
final class LibraryDemangler: InternalDemangler {
11+
private var handle: UnsafeMutablePointer<Void> = {
12+
return dlopen(kDemangleLibraryPath, RTLD_NOW)
13+
}()
14+
15+
private lazy var internalDemangleFunction: SwiftDemangleFunction = {
16+
let address = dlsym(self.handle, "swift_demangle_getDemangledName")
17+
return unsafeBitCast(address, SwiftDemangleFunction.self)
18+
}()
19+
20+
func demangle(string string: String) -> String? {
21+
let formattedString = self.removingExcessLeadingUnderscores(fromString: string)
22+
let outputString = UnsafeMutablePointer<CChar>.alloc(kBufferSize)
23+
let resultSize = self.internalDemangleFunction(formattedString, outputString, kBufferSize)
24+
if resultSize > kBufferSize {
25+
NSLog("Attempted to demangle string with length \(resultSize) but buffer size \(kBufferSize)")
26+
}
27+
28+
return String(CString: outputString, encoding: NSUTF8StringEncoding)
29+
}
30+
31+
private func removingExcessLeadingUnderscores(fromString string: String) -> String {
32+
if string.hasPrefix("__T") {
33+
return String(string.characters.dropFirst())
34+
}
35+
36+
return string
37+
}
38+
39+
deinit {
40+
dlclose(self.handle)
41+
}
42+
}

Demangler/ShellDemangler.swift

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import Foundation
2+
3+
final class ShellDemangler: InternalDemangler {
4+
private lazy var executablePath: String = {
5+
return self.output(forShellCommand: "/usr/bin/xcrun --find swift-demangle")!
6+
}()
7+
8+
func demangle(string string: String) -> String? {
9+
return self.output(forShellCommand: self.demangleCommand(forString: string))
10+
}
11+
12+
private func demangleCommand(forString string: String) -> String {
13+
return "\(self.executablePath) -compact \(string)"
14+
}
15+
16+
private func output(forShellCommand command: String) -> String? {
17+
assert(command.split().count >= 2)
18+
19+
let task = NSTask()
20+
let pipe = NSPipe()
21+
let components = command.split()
22+
23+
task.launchPath = components.first
24+
task.arguments = Array(components.dropFirst())
25+
task.standardOutput = pipe
26+
27+
task.launch()
28+
task.waitUntilExit()
29+
30+
let data = pipe.fileHandleForReading.readDataToEndOfFile()
31+
return String(data: data, encoding: NSUTF8StringEncoding)?.strip()
32+
}
33+
}

Demangler/String+Extension.swift

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import Foundation
2+
3+
extension String {
4+
func split() -> [String] {
5+
return self.characters.split(" ").map(String.init)
6+
}
7+
8+
func strip() -> String {
9+
return self.stringByTrimmingCharactersInSet(NSCharacterSet.whitespaceAndNewlineCharacterSet())
10+
}
11+
}
12+

Demangler/Supporting Files/Info.plist

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3+
<plist version="1.0">
4+
<dict>
5+
<key>CFBundleDevelopmentRegion</key>
6+
<string>en</string>
7+
<key>CFBundleExecutable</key>
8+
<string>$(EXECUTABLE_NAME)</string>
9+
<key>CFBundleIdentifier</key>
10+
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
11+
<key>CFBundleInfoDictionaryVersion</key>
12+
<string>6.0</string>
13+
<key>CFBundleName</key>
14+
<string>$(PRODUCT_NAME)</string>
15+
<key>CFBundlePackageType</key>
16+
<string>FMWK</string>
17+
<key>CFBundleShortVersionString</key>
18+
<string>1.0</string>
19+
<key>CFBundleSignature</key>
20+
<string>????</string>
21+
<key>CFBundleVersion</key>
22+
<string>$(CURRENT_PROJECT_VERSION)</string>
23+
<key>NSHumanReadableCopyright</key>
24+
<string>Copyright © 2016 smileykeith. All rights reserved.</string>
25+
<key>NSPrincipalClass</key>
26+
<string></string>
27+
</dict>
28+
</plist>
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
@testable import Demangler
2+
import XCTest
3+
4+
final class DemangleObjCSelectorTests: XCTestCase {
5+
private var demangler: Demangler!
6+
7+
override func setUp() {
8+
super.setUp()
9+
10+
demangler = Demangler()
11+
}
12+
13+
func testExtractsString() {
14+
let (name, extracted) = demangler.extractMangledString(fromObjCSelector: "-[Foo bar]")
15+
16+
XCTAssertEqual(name, "Foo")
17+
XCTAssertTrue(extracted)
18+
}
19+
20+
func testDoesNotExtractNonObjCString() {
21+
let (name, extracted) = demangler.extractMangledString(fromObjCSelector: "_TCFoo")
22+
23+
XCTAssertEqual(name, "_TCFoo")
24+
XCTAssertFalse(extracted)
25+
}
26+
27+
func testIntegrateString() {
28+
let selector = demangler.integrateDemangledString(intoSelector: "-[Foo bar]", string: "Baz")
29+
30+
XCTAssertEqual(selector, "-[Baz bar]")
31+
}
32+
33+
func testIntegrateStringIntoClassSelector() {
34+
let selector = demangler.integrateDemangledString(intoSelector: "+[Foo.qux bar]", string: "Baz")
35+
36+
XCTAssertEqual(selector, "+[Baz bar]")
37+
}
38+
}

0 commit comments

Comments
 (0)