Skip to content
This repository was archived by the owner on Mar 11, 2024. It is now read-only.

Commit 5ed25b1

Browse files
committed
Implemented Sweep
1 parent fdc17c5 commit 5ed25b1

6 files changed

+193
-37
lines changed

EvalApp/AppDelegate.swift

+4
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,10 @@ class AppDelegate: NSObject, NSApplicationDelegate {
2626
}
2727
}
2828

29+
@objc func injected() {
30+
print("I've been injected!")
31+
}
32+
2933
func applicationDidFinishLaunching(_ aNotification: Notification) {
3034
// Insert code here to initialize your application
3135
evalError = {

README.md

+2-2
Original file line numberDiff line numberDiff line change
@@ -52,8 +52,8 @@ be running a small server "'signer", included in this project to do this alas.
5252
Included is a simple implementation of ["code injection"](SwiftEval/SwiftInjection.swift).
5353
If you are stopped in a class, you can edit the class' implementation, save it and type
5454
"p inject()". Your changes will be applied without having to restart the application.
55-
To detect this in your code to reload a view controller for example, subscribe to the
56-
"INJECTION_BUNDLE_NOTIFICATION".
55+
To detect this in your code to reload a view controller for example, add an @objc
56+
injected() method or subscribe to the "INJECTION_BUNDLE_NOTIFICATION".
5757

5858
### But Why?
5959

SwiftEval/AppDelegate.swift

+2
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UISplitViewControllerDele
2222
let navigationController = splitViewController.viewControllers[splitViewController.viewControllers.count-1] as! UINavigationController
2323
navigationController.topViewController!.navigationItem.leftBarButtonItem = splitViewController.displayModeButtonItem
2424
splitViewController.delegate = self
25+
// Bundle(path: "/Applications/Injection.app/Contents/Resources/InjectionLoader.bundle")?.load()
26+
FSEventStreamCreate()
2527
return true
2628
}
2729

SwiftEval/MasterViewController.swift

+3
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@ class MasterViewController: UITableViewController {
1313
var detailViewController: DetailViewController? = nil
1414
var objects = [Any]()
1515

16+
@objc func injected() {
17+
print("I've been injected!")
18+
}
1619

1720
override func viewDidLoad() {
1821
super.viewDidLoad()

SwiftEval/SwiftEval.swift

+28-16
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
// Created by John Holdsworth on 02/11/2017.
66
// Copyright © 2017 John Holdsworth. All rights reserved.
77
//
8-
// $Id: //depot/ResidentEval/SwiftEval/SwiftEval.swift#4 $
8+
// $Id: //depot/ResidentEval/SwiftEval/SwiftEval.swift#5 $
99
//
1010
// Basic implementation of ra Swift "eval()" including the
1111
// mechanics of recompiling a class and loading the new
@@ -21,7 +21,7 @@ private func debug(_ str: String) {
2121

2222
/// Error handler
2323
public var evalError = {
24-
(_ err: String) -> AnyClass? in
24+
(_ err: String) -> [AnyClass]? in
2525
print("** \(err) **")
2626
return nil
2727
}
@@ -55,7 +55,7 @@ extension NSObject {
5555
// update evalImpl to implement expression
5656

5757
if NSObject.lastEvalByClass[className] != expression,
58-
let newClass = SwiftEval.rebuildClass(oldClass: oldClass, className: className, extra: extra) {
58+
let newClass = SwiftEval.rebuildClass(oldClass: oldClass, className: className, extra: extra)?.first {
5959

6060
// swizzle new version of evalImpl onto class
6161

@@ -96,7 +96,7 @@ class SwiftEval {
9696
static var dylibNumber = 0
9797
static var compileByClass = [String: String]()
9898

99-
static func rebuildClass(oldClass: AnyClass, className: String, extra: String?) -> AnyClass? {
99+
static func rebuildClass(oldClass: AnyClass?, className: String, extra: String?) -> [AnyClass]? {
100100
let sourceURL = URL(fileURLWithPath: #file)
101101
guard let derivedData = findDerivedData(url: sourceURL) else {
102102
return evalError("Could not locate derived data")
@@ -107,7 +107,7 @@ class SwiftEval {
107107

108108
// locate compile command for class
109109

110-
let regexp = " -primary-file (\"([^\"]+?/\(className)\\.swift)\"|(\\S+?/\(className)\\.swift)) "
110+
let regexp = " -primary-file (\"([^\"]*?/\(className)\\.swift)\"|(\\S*?/\(className)\\.swift)) "
111111

112112
guard var compileCommand = compileByClass[className] ?? {
113113
() -> String? in
@@ -118,7 +118,7 @@ class SwiftEval {
118118
echo "Scanning $log"
119119
# grep log for build of class source
120120
/usr/bin/gunzip <"$log" | perl -lpe 's/\\r/\\n/g' | \
121-
/usr/bin/grep -E '\(regexp)' >/tmp/eval.sh && exit 0;
121+
time /usr/bin/grep -E '\(regexp)' >/tmp/eval.sh && exit 0;
122122
done;
123123
exit 1
124124
""") else {
@@ -224,19 +224,31 @@ class SwiftEval {
224224
return evalError("dlopen() error: \(String(cString: dlerror()))")
225225
}
226226

227-
// find patched version of class using symbol for existing
227+
if oldClass != nil {
228+
// find patched version of class using symbol for existing
228229

229-
var info = Dl_info()
230-
guard dladdr(unsafeBitCast(oldClass, to: UnsafeRawPointer.self), &info) != 0 else {
231-
return evalError("Could not locate class symbol")
232-
}
230+
var info = Dl_info()
231+
guard dladdr(unsafeBitCast(oldClass, to: UnsafeRawPointer.self), &info) != 0 else {
232+
return evalError("Could not locate class symbol")
233+
}
233234

234-
debug(String(cString: info.dli_sname))
235-
guard let newSymbol = dlsym(dl, info.dli_sname) else {
236-
return evalError("Could not locate newly loaded class symbol")
237-
}
235+
debug(String(cString: info.dli_sname))
236+
guard let newSymbol = dlsym(dl, info.dli_sname) else {
237+
return evalError("Could not locate newly loaded class symbol")
238+
}
238239

239-
return unsafeBitCast(newSymbol, to: AnyClass.self)
240+
return [unsafeBitCast(newSymbol, to: AnyClass.self)]
241+
}
242+
else {
243+
guard shell(command: "\(xcode)/Toolchains/XcodeDefault.xctoolchain/usr/bin/nm /tmp/eval.o | grep 'S _OBJC_CLASS_$_' | awk '{print $3}' >/tmp/eval.classes") else {
244+
return evalError("Could not list classes")
245+
}
246+
guard var symbols = (try? String(contentsOfFile: "/tmp/eval.classes"))?.components(separatedBy: "\n") else {
247+
return evalError("Could not load class list")
248+
}
249+
symbols.removeLast()
250+
return symbols.map { unsafeBitCast(dlsym(dl, String($0.dropFirst()))!, to: AnyClass.self) }
251+
}
240252
}
241253

242254
static func findDerivedData(url: URL) -> URL? {

SwiftEval/SwiftInjection.swift

+154-19
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
// Created by John Holdsworth on 05/11/2017.
66
// Copyright © 2017 John Holdsworth. All rights reserved.
77
//
8-
// $Id: //depot/ResidentEval/SwiftEval/SwiftInjection.swift#4 $
8+
// $Id: //depot/ResidentEval/SwiftEval/SwiftInjection.swift#8 $
99
//
1010
// Cut-down version of code injection in Swift. Uses code
1111
// from SwiftEval.swift to recompile and reload class.
@@ -14,6 +14,10 @@
1414
#if arch(x86_64) // simulator/macOS only
1515
import Foundation
1616

17+
@objc public protocol SwiftInjected {
18+
@objc optional func injected()
19+
}
20+
1721
#if os(iOS)
1822
import UIKit
1923

@@ -33,39 +37,72 @@ extension UIViewController {
3337
viewDidLoad()
3438
}
3539
}
40+
#else
41+
import Cocoa
3642
#endif
3743

3844
extension NSObject {
3945

4046
public func inject() {
41-
if let oldClass: AnyClass = object_getClass(self),
42-
let newClass = SwiftEval.rebuildClass(oldClass: oldClass, className: "\(oldClass)", extra: nil) {
47+
if let oldClass: AnyClass = object_getClass(self) {
48+
SwiftInjection.inject(oldClass: oldClass, className: "\(oldClass)")
49+
}
50+
}
4351

44-
// old-school swizzle Objective-C class & instance methods
45-
injection(swizzle: object_getClass(newClass), onto: object_getClass(oldClass))
46-
injection(swizzle: newClass, onto: oldClass)
52+
@objc
53+
public class func inject(file: String) {
54+
let path = URL(fileURLWithPath: file).deletingPathExtension().path
55+
SwiftInjection.inject(oldClass: nil, className: String(path.dropFirst()))
56+
}
57+
}
4758

48-
// overwrite Swift vtable of existing class with implementations from new class
49-
let existingClass = unsafeBitCast(oldClass, to: UnsafeMutablePointer<ClassMetadataSwift>.self)
50-
let classMetadata = unsafeBitCast(newClass, to: UnsafeMutablePointer<ClassMetadataSwift>.self)
59+
class SwiftInjection {
5160

52-
func byteAddr<T>(_ location: UnsafeMutablePointer<T>) -> UnsafeMutablePointer<UInt8> {
53-
return location.withMemoryRebound(to: UInt8.self, capacity: 1) { $0 }
54-
}
61+
static func inject(oldClass: AnyClass?, className: String) {
62+
if let newClasses = SwiftEval.rebuildClass(oldClass: oldClass, className: className, extra: nil) {
63+
let oldClasses = //oldClass != nil ? [oldClass!] :
64+
newClasses.map { objc_getClass(class_getName($0)) as! AnyClass }
65+
for i in 0..<oldClasses.count {
66+
let oldClass: AnyClass = oldClasses[i], newClass: AnyClass = newClasses[i]
67+
68+
// old-school swizzle Objective-C class & instance methods
69+
injection(swizzle: object_getClass(newClass), onto: object_getClass(oldClass))
70+
injection(swizzle: newClass, onto: oldClass)
71+
72+
// overwrite Swift vtable of existing class with implementations from new class
73+
let existingClass = unsafeBitCast(oldClass, to: UnsafeMutablePointer<ClassMetadataSwift>.self)
74+
let classMetadata = unsafeBitCast(newClass, to: UnsafeMutablePointer<ClassMetadataSwift>.self)
5575

56-
let vtableOffset = byteAddr(&existingClass.pointee.IVarDestroyer) - byteAddr(existingClass)
57-
let vtableLength = Int(existingClass.pointee.ClassSize -
58-
existingClass.pointee.ClassAddressPoint) - vtableOffset
76+
func byteAddr<T>(_ location: UnsafeMutablePointer<T>) -> UnsafeMutablePointer<UInt8> {
77+
return location.withMemoryRebound(to: UInt8.self, capacity: 1) { $0 }
78+
}
5979

60-
NSLog("\(unsafeBitCast(classMetadata, to: AnyClass.self)), vtable length: \(vtableLength)")
61-
memcpy(byteAddr(existingClass) + vtableOffset, byteAddr(classMetadata) + vtableOffset, vtableLength)
80+
let vtableOffset = byteAddr(&existingClass.pointee.IVarDestroyer) - byteAddr(existingClass)
81+
let vtableLength = Int(existingClass.pointee.ClassSize -
82+
existingClass.pointee.ClassAddressPoint) - vtableOffset
83+
84+
NSLog("\(unsafeBitCast(classMetadata, to: AnyClass.self)), vtable length: \(vtableLength)")
85+
memcpy(byteAddr(existingClass) + vtableOffset, byteAddr(classMetadata) + vtableOffset, vtableLength)
86+
87+
// implement -injected() method using sweep of objects in application
88+
if class_getInstanceMethod(oldClass, #selector(SwiftInjected.injected)) != nil {
89+
#if os(iOS)
90+
let app = UIApplication.shared
91+
#else
92+
let app = NSApplication.shared
93+
#endif
94+
let seeds: [Any] = [app.delegate as Any] + app.windows
95+
sweepValue(seeds, for: oldClass)
96+
seen.removeAll()
97+
}
98+
}
6299

63100
let notification = Notification.Name("INJECTION_BUNDLE_NOTIFICATION")
64-
NotificationCenter.default.post(name: notification, object: [oldClass])
101+
NotificationCenter.default.post(name: notification, object: oldClasses)
65102
}
66103
}
67104

68-
private func injection(swizzle newClass: AnyClass?, onto oldClass: AnyClass?) {
105+
static func injection(swizzle newClass: AnyClass?, onto oldClass: AnyClass?) {
69106
var methodCount: UInt32 = 0
70107
if let methods = class_copyMethodList(newClass, &methodCount) {
71108
for i in 0 ..< Int(methodCount) {
@@ -76,6 +113,104 @@ extension NSObject {
76113
free(methods)
77114
}
78115
}
116+
117+
static func sweepValue(_ value: Any, for targetClass: AnyClass) {
118+
let mirror = Mirror(reflecting: value)
119+
if var style = mirror.displayStyle {
120+
if _typeName(mirror.subjectType).hasPrefix("Swift.ImplicitlyUnwrappedOptional<") {
121+
style = .optional
122+
}
123+
switch style {
124+
case .set:
125+
fallthrough
126+
case .collection:
127+
for (_, child) in mirror.children {
128+
sweepValue(child, for: targetClass)
129+
}
130+
return
131+
case .dictionary:
132+
for (_, child) in mirror.children {
133+
for (_, element) in Mirror(reflecting: child).children {
134+
sweepValue(element, for: targetClass)
135+
}
136+
}
137+
return
138+
case .class:
139+
sweepInstance(value as AnyObject, for: targetClass)
140+
return
141+
case .optional:
142+
if let some = mirror.children.first?.value {
143+
sweepValue(some, for: targetClass)
144+
}
145+
return
146+
default:
147+
break
148+
}
149+
}
150+
151+
if let style = mirror.displayStyle {
152+
switch style {
153+
case .enum:
154+
if let evals = mirror.children.first?.value {
155+
sweepValue(evals, for: targetClass)
156+
}
157+
case .tuple:
158+
sweepMembers(value, for: targetClass)
159+
case .struct:
160+
sweepMembers(value, for: targetClass)
161+
default:
162+
break
163+
}
164+
}
165+
}
166+
167+
static var seen = [UnsafeRawPointer: Bool]()
168+
169+
static func sweepInstance(_ instance: AnyObject, for targetClass: AnyClass) {
170+
let reference = unsafeBitCast(instance, to: UnsafeRawPointer.self)
171+
if seen[reference] == nil {
172+
seen[reference] = true
173+
174+
if object_getClass(instance) == targetClass {
175+
let proto = unsafeBitCast(instance, to: SwiftInjected.self)
176+
proto.injected?()
177+
}
178+
179+
sweepMembers(instance, for: targetClass)
180+
sweepIvars(instance, for: targetClass)
181+
}
182+
}
183+
184+
static func sweepMembers(_ instance: Any, for targetClass: AnyClass) {
185+
var mirror: Mirror? = Mirror(reflecting: instance)
186+
while mirror != nil {
187+
for (_, value) in mirror!.children {
188+
sweepValue(value, for: targetClass)
189+
}
190+
mirror = mirror!.superclassMirror
191+
}
192+
}
193+
194+
static func sweepIvars(_ instance: AnyObject, for targetClass: AnyClass) {
195+
var icnt: UInt32 = 0, cls: AnyClass? = object_getClass(instance)!
196+
let object = "@".utf16.first!
197+
while cls != nil && cls != NSURL.self {
198+
if let ivars = class_copyIvarList(cls, &icnt) {
199+
for i in 0 ..< Int(icnt) {
200+
if let type = ivar_getTypeEncoding(ivars[i]), type[0] == object {
201+
(unsafeBitCast(instance, to: UnsafePointer<Int8>.self) + ivar_getOffset(ivars[i]))
202+
.withMemoryRebound(to: AnyObject?.self, capacity: 1) {
203+
if let obj = $0.pointee {
204+
sweepValue(obj, for: targetClass)
205+
}
206+
}
207+
}
208+
}
209+
free(ivars)
210+
}
211+
cls = class_getSuperclass(cls)
212+
}
213+
}
79214
}
80215

81216
/**

0 commit comments

Comments
 (0)