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

Commit 460451e

Browse files
committed
New version of Injection vIII
1 parent 5ed25b1 commit 460451e

27 files changed

+2016
-99
lines changed

InjectionBundle/Info.plist

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
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>$(DEVELOPMENT_LANGUAGE)</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>BNDL</string>
17+
<key>CFBundleShortVersionString</key>
18+
<string>1.0</string>
19+
<key>CFBundleVersion</key>
20+
<string>1</string>
21+
<key>NSHumanReadableCopyright</key>
22+
<string>Copyright © 2017 John Holdsworth. All rights reserved.</string>
23+
<key>NSPrincipalClass</key>
24+
<string></string>
25+
</dict>
26+
</plist>

InjectionBundle/InjectionClient.h

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
//
2+
// InjectionClient.h
3+
// InjectionBundle
4+
//
5+
// Created by John Holdsworth on 06/11/2017.
6+
// Copyright © 2017 John Holdsworth. All rights reserved.
7+
//
8+
9+
#import "SimpleSocket.h"
10+
11+
@interface InjectionClient : SimpleSocket
12+
13+
@end

InjectionBundle/InjectionClient.mm

+29
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
//
2+
// InjectionClient.m
3+
// InjectionBundle
4+
//
5+
// Created by John Holdsworth on 06/11/2017.
6+
// Copyright © 2017 John Holdsworth. All rights reserved.
7+
//
8+
9+
#import "InjectionClient.h"
10+
#import "InjectionServer.h"
11+
12+
#ifdef __IPHONE_OS_VERSION_MIN_REQUIRED
13+
#import "iOSInjection-Swift.h"
14+
#else
15+
#import "macOSInjection-Swift.h"
16+
#endif
17+
18+
@implementation InjectionClient
19+
20+
+ (void)load {
21+
[[self connectTo:INJECTION_ADDRESS] run];
22+
}
23+
24+
- (void)runInBackground {
25+
while (NSString *swiftSource = [self readString])
26+
[NSObject injectWithFile:swiftSource];
27+
}
28+
29+
@end

SwiftEval/SwiftEval.swift InjectionBundle/SwiftEval.swift

+30-24
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#5 $
8+
// $Id: //depot/ResidentEval/SwiftEval/SwiftEval.swift#8 $
99
//
1010
// Basic implementation of ra Swift "eval()" including the
1111
// mechanics of recompiling a class and loading the new
@@ -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)?.first {
58+
let newClass = SwiftEval.instance.rebuildClass(oldClass: oldClass, className: className, extra: extra)?.first {
5959

6060
// swizzle new version of evalImpl onto class
6161

@@ -91,13 +91,19 @@ extension String {
9191
}
9292
}
9393

94-
class SwiftEval {
94+
@objc
95+
public class SwiftEval: NSObject {
9596

96-
static var dylibNumber = 0
97-
static var compileByClass = [String: String]()
97+
static var instance = SwiftEval()
9898

99-
static func rebuildClass(oldClass: AnyClass?, className: String, extra: String?) -> [AnyClass]? {
100-
let sourceURL = URL(fileURLWithPath: #file)
99+
var derivedData: URL?
100+
var projectInfo: (file: URL, info: URL)?
101+
102+
var dylibNumber = 0
103+
var compileByClass = [String: String]()
104+
105+
func rebuildClass(oldClass: AnyClass?, className: String, extra: String?) -> [AnyClass]? {
106+
let sourceURL = URL(fileURLWithPath: className.contains("/") ? "/" + className : #file)
101107
guard let derivedData = findDerivedData(url: sourceURL) else {
102108
return evalError("Could not locate derived data")
103109
}
@@ -107,6 +113,8 @@ class SwiftEval {
107113

108114
// locate compile command for class
109115

116+
dylibNumber += 1
117+
let tmpfile = "/tmp/eval\(dylibNumber)"
110118
let regexp = " -primary-file (\"([^\"]*?/\(className)\\.swift)\"|(\\S*?/\(className)\\.swift)) "
111119

112120
guard var compileCommand = compileByClass[className] ?? {
@@ -118,14 +126,14 @@ class SwiftEval {
118126
echo "Scanning $log"
119127
# grep log for build of class source
120128
/usr/bin/gunzip <"$log" | perl -lpe 's/\\r/\\n/g' | \
121-
time /usr/bin/grep -E '\(regexp)' >/tmp/eval.sh && exit 0;
129+
/usr/bin/grep -E '\(regexp)' >\(tmpfile).sh && exit 0;
122130
done;
123131
exit 1
124132
""") else {
125133
return nil
126134
}
127135

128-
var compileCommand = try! String(contentsOfFile: "/tmp/eval.sh")
136+
var compileCommand = try! String(contentsOfFile: "\(tmpfile).sh")
129137
compileCommand = compileCommand.components(separatedBy: " -o ")[0]
130138
compileByClass[className] = compileCommand
131139
return compileCommand
@@ -180,15 +188,13 @@ class SwiftEval {
180188
let projectDir = projectFile.deletingLastPathComponent().path
181189

182190
guard shell(command: """
183-
cd "\(projectDir)" && \(compileCommand) -o /tmp/eval.o >/tmp/eval.log 2>&1 || (cat /tmp/eval.log && exit 1)
191+
cd "\(projectDir)" && \(compileCommand) -o \(tmpfile).o >\(tmpfile).log 2>&1 || (cat \(tmpfile).log && exit 1)
184192
""") else {
185-
return evalError("Re-compilation failed\n\(try! String(contentsOfFile: "/tmp/eval.log"))")
193+
return evalError("Re-compilation failed\n\(try! String(contentsOfFile: "\(tmpfile).log"))")
186194
}
187195

188196
// link object to create dynamic library
189197

190-
dylibNumber += 1
191-
let dylib = "/tmp/eval\(dylibNumber).dylib"
192198
let xcode = "/Applications/Xcode.app/Contents/Developer"
193199

194200
#if os(iOS)
@@ -200,27 +206,27 @@ class SwiftEval {
200206
#endif
201207

202208
guard shell(command: """
203-
\(xcode)/Toolchains/XcodeDefault.xctoolchain/usr/bin/clang -arch x86_64 -bundle \(osSpecific) -dead_strip -Xlinker -objc_abi_version -Xlinker 2 -fobjc-arc /tmp/eval.o -L \(frameworkPath) -F \(frameworkPath) -rpath \(frameworkPath) -o \(dylib)
209+
\(xcode)/Toolchains/XcodeDefault.xctoolchain/usr/bin/clang -arch x86_64 -bundle \(osSpecific) -dead_strip -Xlinker -objc_abi_version -Xlinker 2 -fobjc-arc \(tmpfile).o -L \(frameworkPath) -F \(frameworkPath) -rpath \(frameworkPath) -o \(tmpfile).dylib
204210
""") else {
205211
return evalError("Link failed")
206212
}
207213

208214
#if os(iOS)
209215
// have to delegate code signing to macOS "signer" service
210-
guard (try? String(contentsOf: URL(string: "http://localhost:8899" + dylib)!)) != nil else {
216+
guard (try? String(contentsOf: URL(string: "http://localhost:8899\(tmpfile).dylib")!)) != nil else {
211217
return evalError("Codesign failed. Is 'signer' daemon running?")
212218
}
213219
#else
214220
guard shell(command: """
215-
export CODESIGN_ALLOCATE=\(xcode)/Toolchains/XcodeDefault.xctoolchain/usr/bin/codesign_allocate; codesign --force -s '-' "\(dylib)"
221+
export CODESIGN_ALLOCATE=\(xcode)/Toolchains/XcodeDefault.xctoolchain/usr/bin/codesign_allocate; codesign --force -s '-' "\(tmpfile).dylib"
216222
""") else {
217223
return evalError("Codesign failed")
218224
}
219225
#endif
220226

221227
// load patch .dylib into process with new version of class
222228

223-
guard let dl = dlopen(dylib, RTLD_NOW) else {
229+
guard let dl = dlopen("\(tmpfile).dylib", RTLD_NOW) else {
224230
return evalError("dlopen() error: \(String(cString: dlerror()))")
225231
}
226232

@@ -240,18 +246,18 @@ class SwiftEval {
240246
return [unsafeBitCast(newSymbol, to: AnyClass.self)]
241247
}
242248
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 {
249+
guard shell(command: "\(xcode)/Toolchains/XcodeDefault.xctoolchain/usr/bin/nm \(tmpfile).o | grep 'S _OBJC_CLASS_$_' | awk '{print $3}' >\(tmpfile).classes") else {
244250
return evalError("Could not list classes")
245251
}
246-
guard var symbols = (try? String(contentsOfFile: "/tmp/eval.classes"))?.components(separatedBy: "\n") else {
252+
guard var symbols = (try? String(contentsOfFile: "\(tmpfile).classes"))?.components(separatedBy: "\n") else {
247253
return evalError("Could not load class list")
248254
}
249255
symbols.removeLast()
250256
return symbols.map { unsafeBitCast(dlsym(dl, String($0.dropFirst()))!, to: AnyClass.self) }
251257
}
252258
}
253259

254-
static func findDerivedData(url: URL) -> URL? {
260+
func findDerivedData(url: URL) -> URL? {
255261
let dir = url.deletingLastPathComponent()
256262
if dir.path == "/" {
257263
return nil
@@ -265,7 +271,7 @@ class SwiftEval {
265271
return findDerivedData(url: dir)
266272
}
267273

268-
static func findProject(for source: URL, derivedData: URL) -> (URL, URL)? {
274+
func findProject(for source: URL, derivedData: URL) -> (URL, URL)? {
269275
let dir = source.deletingLastPathComponent()
270276
if dir.path == "/" {
271277
return nil
@@ -280,11 +286,11 @@ class SwiftEval {
280286
return findProject(for: dir, derivedData: derivedData)
281287
}
282288

283-
static func file(withExt ext: String, in files: [String]) -> String? {
289+
func file(withExt ext: String, in files: [String]) -> String? {
284290
return files.first { URL(fileURLWithPath: $0).pathExtension == ext }
285291
}
286292

287-
static func logDir(project: URL, derivedData: URL) -> URL? {
293+
func logDir(project: URL, derivedData: URL) -> URL? {
288294
let filemgr = FileManager.default
289295
let projectPrefix = project.deletingPathExtension()
290296
.lastPathComponent.replacingOccurrences(of: " ", with: "_")
@@ -305,7 +311,7 @@ class SwiftEval {
305311
.first
306312
}
307313

308-
static func shell(command: String) -> Bool {
314+
func shell(command: String) -> Bool {
309315
debug(command)
310316

311317
let pid = fork()

SwiftEval/SwiftInjection.swift InjectionBundle/SwiftInjection.swift

+23-9
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#8 $
8+
// $Id: //depot/ResidentEval/SwiftEval/SwiftInjection.swift#11 $
99
//
1010
// Cut-down version of code injection in Swift. Uses code
1111
// from SwiftEval.swift to recompile and reload class.
@@ -56,10 +56,10 @@ extension NSObject {
5656
}
5757
}
5858

59-
class SwiftInjection {
59+
public class SwiftInjection {
6060

6161
static func inject(oldClass: AnyClass?, className: String) {
62-
if let newClasses = SwiftEval.rebuildClass(oldClass: oldClass, className: className, extra: nil) {
62+
if let newClasses = SwiftEval.instance.rebuildClass(oldClass: oldClass, className: className, extra: nil) {
6363
let oldClasses = //oldClass != nil ? [oldClass!] :
6464
newClasses.map { objc_getClass(class_getName($0)) as! AnyClass }
6565
for i in 0..<oldClasses.count {
@@ -91,7 +91,7 @@ class SwiftInjection {
9191
#else
9292
let app = NSApplication.shared
9393
#endif
94-
let seeds: [Any] = [app.delegate as Any] + app.windows
94+
let seeds = DispatchQueue.main.sync { () -> [Any] in [app.delegate as Any] + app.windows }
9595
sweepValue(seeds, for: oldClass)
9696
seen.removeAll()
9797
}
@@ -177,7 +177,7 @@ class SwiftInjection {
177177
}
178178

179179
sweepMembers(instance, for: targetClass)
180-
sweepIvars(instance, for: targetClass)
180+
instance.legacySweep?(for: targetClass)
181181
}
182182
}
183183

@@ -190,18 +190,20 @@ class SwiftInjection {
190190
mirror = mirror!.superclassMirror
191191
}
192192
}
193+
}
193194

194-
static func sweepIvars(_ instance: AnyObject, for targetClass: AnyClass) {
195-
var icnt: UInt32 = 0, cls: AnyClass? = object_getClass(instance)!
195+
extension NSObject {
196+
@objc func legacySweep(for targetClass: AnyClass) {
197+
var icnt: UInt32 = 0, cls: AnyClass? = object_getClass(self)!
196198
let object = "@".utf16.first!
197199
while cls != nil && cls != NSURL.self {
198200
if let ivars = class_copyIvarList(cls, &icnt) {
199201
for i in 0 ..< Int(icnt) {
200202
if let type = ivar_getTypeEncoding(ivars[i]), type[0] == object {
201-
(unsafeBitCast(instance, to: UnsafePointer<Int8>.self) + ivar_getOffset(ivars[i]))
203+
(unsafeBitCast(self, to: UnsafePointer<Int8>.self) + ivar_getOffset(ivars[i]))
202204
.withMemoryRebound(to: AnyObject?.self, capacity: 1) {
203205
if let obj = $0.pointee {
204-
sweepValue(obj, for: targetClass)
206+
SwiftInjection.sweepInstance(obj, for: targetClass)
205207
}
206208
}
207209
}
@@ -213,6 +215,18 @@ class SwiftInjection {
213215
}
214216
}
215217

218+
extension NSArray {
219+
@objc override func legacySweep(for targetClass: AnyClass) {
220+
self.forEach { SwiftInjection.sweepInstance($0 as AnyObject, for: targetClass) }
221+
}
222+
}
223+
224+
extension NSDictionary {
225+
@objc override func legacySweep(for targetClass: AnyClass) {
226+
self.allValues.forEach { SwiftInjection.sweepInstance($0 as AnyObject, for: targetClass) }
227+
}
228+
}
229+
216230
/**
217231
Layout of a class instance. Needs to be kept in sync with ~swift/include/swift/Runtime/Metadata.h
218232
*/

InjectionIII/App.icns

620 KB
Binary file not shown.

InjectionIII/AppDelegate.h

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
//
2+
// AppDelegate.h
3+
// InjectionIII
4+
//
5+
// Created by John Holdsworth on 06/11/2017.
6+
// Copyright © 2017 John Holdsworth. All rights reserved.
7+
//
8+
9+
#import <Cocoa/Cocoa.h>
10+
11+
@interface AppDelegate : NSObject <NSApplicationDelegate>
12+
13+
@property (weak) IBOutlet NSMenuItem *enableWatcher;
14+
15+
- (void)setMenuIcon:(NSString *)tiffName;
16+
17+
@end
18+
19+
extern AppDelegate *appDelegate;

0 commit comments

Comments
 (0)