Skip to content

Commit

Permalink
Initial Commit
Browse files Browse the repository at this point in the history
  • Loading branch information
keith committed Jun 9, 2016
0 parents commit d642751
Show file tree
Hide file tree
Showing 47 changed files with 3,362 additions and 0 deletions.
85 changes: 85 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
# Xcode
#
# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore

## Build generated
build/
DerivedData/

## Various settings
*.pbxuser
!default.pbxuser
*.mode1v3
!default.mode1v3
*.mode2v3
!default.mode2v3
*.perspectivev3
!default.perspectivev3
xcuserdata/

## Other
*.moved-aside
*.xcuserstate

## Obj-C/Swift specific
*.hmap
*.ipa
*.dSYM.zip
*.dSYM

# CocoaPods
#
# We recommend against adding the Pods directory to your .gitignore. However
# you should judge for yourself, the pros and cons are mentioned at:
# https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
#
# Pods/

# Carthage
#
# Add this line if you want to avoid checking in source code from Carthage dependencies.
# Carthage/Checkouts

Carthage/Build

# fastlane
#
# It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the
# screenshots whenever they are needed.
# For more information about the recommended setup visit:
# https://github.com/fastlane/fastlane/blob/master/fastlane/docs/Gitignore.md

fastlane/report.xml
fastlane/screenshots

#Code Injection
#
# After new code Injection tools there's a generated folder /iOSInjectionProject
# https://github.com/johnno1962/injectionforxcode

iOSInjectionProject/
*.DS_Store
.AppleDouble
.LSOverride

# Icon must end with two \r
Icon

# Thumbnails
._*

# Files that might appear in the root of a volume
.DocumentRevisions-V100
.fseventsd
.Spotlight-V100
.TemporaryItems
.Trashes
.VolumeIcon.icns
.com.apple.timemachine.donotpresent

# Directories potentially created on remote AFP share
.AppleDB
.AppleDesktop
Network Trash Folder
Temporary Items
.apdisk
Expand Down
112 changes: 112 additions & 0 deletions Demangler/Demangler.swift
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
}
}
3 changes: 3 additions & 0 deletions Demangler/InternalDemangler.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
protocol InternalDemangler {
func demangle(string string: String) -> String?
}
42 changes: 42 additions & 0 deletions Demangler/LibraryDemangler.swift
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)
}
}
33 changes: 33 additions & 0 deletions Demangler/ShellDemangler.swift
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()
}
}
12 changes: 12 additions & 0 deletions Demangler/String+Extension.swift
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())
}
}

28 changes: 28 additions & 0 deletions Demangler/Supporting Files/Info.plist
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>
38 changes: 38 additions & 0 deletions DemanglerTests/DemangleObjCSelectorTests.swift
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]")
}
}
Loading

0 comments on commit d642751

Please sign in to comment.