diff --git a/InjectionBundle/InjectionClient.h b/InjectionBundle/InjectionClient.h index ed7c11f5..418603be 100644 --- a/InjectionBundle/InjectionClient.h +++ b/InjectionBundle/InjectionClient.h @@ -43,6 +43,7 @@ typedef NS_ENUM(int, InjectionCommand) { InjectionStats, InjectionCallOrder, InjectionFileOrder, + InjectionFileReorder, InjectionInvalid = 1000, @@ -57,6 +58,7 @@ typedef NS_ENUM(int, InjectionResponse) { InjectionError, InjectionFrameworkList, InjectionCallOrderList, + InjectionCallReorderList, InjectionExit = ~0 }; diff --git a/InjectionBundle/InjectionClient.mm b/InjectionBundle/InjectionClient.mm index d704fe60..82d7e15a 100644 --- a/InjectionBundle/InjectionClient.mm +++ b/InjectionBundle/InjectionClient.mm @@ -5,7 +5,7 @@ // Created by John Holdsworth on 06/11/2017. // Copyright © 2017 John Holdsworth. All rights reserved. // -// $Id: //depot/ResidentEval/InjectionBundle/InjectionClient.mm#131 $ +// $Id: //depot/ResidentEval/InjectionBundle/InjectionClient.mm#133 $ // #import "InjectionClient.h" @@ -320,6 +320,12 @@ - (void)runInBackground { componentsJoinedByString:CALLORDER_DELIMITER]]; [self needsTracing]; break; + case InjectionFileReorder: + [self writeCommand:InjectionCallReorderList + withString:[[SwiftInjection callOrder] + componentsJoinedByString:CALLORDER_DELIMITER]]; + [self needsTracing]; + break; case InjectionInvalid: printf("💉 ⚠️ Connection rejected. Are you running the correct version of InjectionIII.app from /Applications? ⚠️\n"); break; diff --git a/InjectionIII.xcodeproj/project.pbxproj b/InjectionIII.xcodeproj/project.pbxproj index 00b65fca..3c1cfbfb 100644 --- a/InjectionIII.xcodeproj/project.pbxproj +++ b/InjectionIII.xcodeproj/project.pbxproj @@ -83,6 +83,8 @@ BBE64E5D2524D1B50049B6D4 /* SwiftEval.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB439B8A1FABA65D00B4F50B /* SwiftEval.swift */; }; BBEB704C1FD28C6F00127711 /* XcodeHash.m in Sources */ = {isa = PBXBuildFile; fileRef = BBEB704B1FD28C6F00127711 /* XcodeHash.m */; }; BD35949E21A6C5DE0020EB94 /* Vaccine.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD35949D21A6C5DE0020EB94 /* Vaccine.swift */; }; + CEC1702A253ED117002E823F /* Experimental.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEC17029253ED117002E823F /* Experimental.swift */; }; + CEC17035253ED472002E823F /* SwiftRegex in Frameworks */ = {isa = PBXBuildFile; productRef = CEC17034253ED472002E823F /* SwiftRegex */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -261,6 +263,7 @@ BBEB704B1FD28C6F00127711 /* XcodeHash.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = XcodeHash.m; sourceTree = ""; }; BD35949D21A6C5DE0020EB94 /* Vaccine.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Vaccine.swift; sourceTree = ""; }; BDB6A7CE21824C800001CF95 /* UserDefaults.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = UserDefaults.h; sourceTree = ""; }; + CEC17029253ED117002E823F /* Experimental.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Experimental.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -291,6 +294,7 @@ files = ( BBB64FEC1FD585D50020BE47 /* WebKit.framework in Frameworks */, BBB040641FB17A6C007DDD0A /* ScriptingBridge.framework in Frameworks */, + CEC17035253ED472002E823F /* SwiftRegex in Frameworks */, BBB64DE51FD571310020BE47 /* libz.tbd in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -483,6 +487,7 @@ BBCA02011FB0F10300E45F0F /* AppDelegate.swift */, BB7D7CAB2512F8F00069FE2C /* UpdateCheck.swift */, BBCA02551FB1099500E45F0F /* InjectionServer.swift */, + CEC17029253ED117002E823F /* Experimental.swift */, BBE490CE1FB2368A003D41BB /* FileWatcher.swift */, BBCA025A1FB1132700E45F0F /* Xcode.h */, BBEB704A1FD28C6F00127711 /* XcodeHash.h */, @@ -657,6 +662,9 @@ BB1A8406252266D4003873C3 /* PBXTargetDependency */, ); name = InjectionIII; + packageProductDependencies = ( + CEC17034253ED472002E823F /* SwiftRegex */, + ); productName = InjectionIII; productReference = BBCA01FE1FB0F10300E45F0F /* InjectionIII.app */; productType = "com.apple.product-type.application"; @@ -750,6 +758,9 @@ Base, ); mainGroup = BB439B5C1FABA64300B4F50B; + packageReferences = ( + CEC17033253ED472002E823F /* XCRemoteSwiftPackageReference "SwiftRegex5" */, + ); productRefGroup = BB439B661FABA64300B4F50B /* Products */; projectDirPath = ""; projectRoot = ""; @@ -935,6 +946,7 @@ BBB64DD41FD56F570020BE47 /* XprobeConsole.m in Sources */, BBCA022A1FB0F64800E45F0F /* SimpleSocket.mm in Sources */, BB79FC79245CC02200A4B4CB /* TimeLapseBuilder.swift in Sources */, + CEC1702A253ED117002E823F /* Experimental.swift in Sources */, BB56393C1FD5C25A002FFCEF /* SignerService.m in Sources */, BBEB704C1FD28C6F00127711 /* XcodeHash.m in Sources */, BBCA02021FB0F10300E45F0F /* AppDelegate.swift in Sources */, @@ -1047,7 +1059,11 @@ INFOPLIST_FILE = SwiftUISupport/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Bundles"; IPHONEOS_DEPLOYMENT_TARGET = 13.0; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../../macOSInjection.bundle/Contents/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + "@loader_path/../../macOSInjection.bundle/Contents/Frameworks", + ); MACOSX_DEPLOYMENT_TARGET = 10.15; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; @@ -1084,7 +1100,11 @@ INFOPLIST_FILE = SwiftUISupport/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Bundles"; IPHONEOS_DEPLOYMENT_TARGET = 13.0; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../../macOSInjection.bundle/Contents/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + "@loader_path/../../macOSInjection.bundle/Contents/Frameworks", + ); MACOSX_DEPLOYMENT_TARGET = 10.15; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = com.johnholdsworth.SwiftUISupport; @@ -1150,7 +1170,10 @@ GCC_WARN_UNUSED_VARIABLE = YES; HELPER_MACH_ID = com.johnholdsworth.InjectionIII.Helper.mach; IPHONEOS_DEPLOYMENT_TARGET = 10.3; - LD_RUNPATH_SEARCH_PATHS = "$(TOOLCHAIN_DIR)/usr/lib/swift/$(SWIFT_PLATFORM_TARGET_PREFIX) $PLATFORM_DIR/Developer/Library/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(TOOLCHAIN_DIR)/usr/lib/swift/$(SWIFT_PLATFORM_TARGET_PREFIX)", + $PLATFORM_DIR/Developer/Library/Frameworks, + ); MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; OTHER_SWIFT_FLAGS = "-Xfrontend -warn-long-expression-type-checking=150"; @@ -1213,11 +1236,15 @@ GCC_WARN_UNUSED_VARIABLE = YES; HELPER_MACH_ID = com.johnholdsworth.InjectionIII.Helper.mach; IPHONEOS_DEPLOYMENT_TARGET = 10.3; - LD_RUNPATH_SEARCH_PATHS = "$(TOOLCHAIN_DIR)/usr/lib/swift/$(SWIFT_PLATFORM_TARGET_PREFIX) $PLATFORM_DIR/Developer/Library/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(TOOLCHAIN_DIR)/usr/lib/swift/$(SWIFT_PLATFORM_TARGET_PREFIX)", + $PLATFORM_DIR/Developer/Library/Frameworks, + ); MTL_ENABLE_DEBUG_INFO = NO; PRODUCT_NAME = macOSInjection; SDKROOT = iphoneos; - SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; SWIFT_VERSION = 5.0; VALIDATE_PRODUCT = YES; }; @@ -1231,7 +1258,11 @@ CODE_SIGN_STYLE = Manual; DEVELOPMENT_TEAM = ""; INFOPLIST_FILE = SwiftEvalTests/Info.plist; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); MACOSX_DEPLOYMENT_TARGET = 10.12; PRODUCT_BUNDLE_IDENTIFIER = com.johnholdsworth.SwiftEvalTests; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -1249,7 +1280,11 @@ CODE_SIGN_STYLE = Manual; DEVELOPMENT_TEAM = ""; INFOPLIST_FILE = SwiftEvalTests/Info.plist; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); MACOSX_DEPLOYMENT_TARGET = 10.12; PRODUCT_BUNDLE_IDENTIFIER = com.johnholdsworth.SwiftEvalTests; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -1281,7 +1316,11 @@ ); INFOPLIST_FILE = SwiftTrace/SwiftTrace/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + "@loader_path/Frameworks", + ); MACOSX_DEPLOYMENT_TARGET = 10.12; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; @@ -1314,7 +1353,11 @@ DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = SwiftTrace/SwiftTrace/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + "@loader_path/Frameworks", + ); MACOSX_DEPLOYMENT_TARGET = 10.12; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = com.johnholdsworth.SwiftTrace; @@ -1384,7 +1427,11 @@ ENABLE_HARDENED_RUNTIME = YES; INFOPLIST_FILE = InjectionBundle/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Bundles"; - LD_RUNPATH_SEARCH_PATHS = "/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/swift/$(PLATFORM_NAME) /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/Library/Frameworks @loader_path/../Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/swift/$(PLATFORM_NAME)", + /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/Library/Frameworks, + "@loader_path/../Frameworks", + ); MACH_O_TYPE = mh_dylib; MACOSX_DEPLOYMENT_TARGET = 10.12; PRODUCT_BUNDLE_IDENTIFIER = com.johnholdsworth.InjectionBundle; @@ -1409,7 +1456,11 @@ ENABLE_HARDENED_RUNTIME = YES; INFOPLIST_FILE = InjectionBundle/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Bundles"; - LD_RUNPATH_SEARCH_PATHS = "/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/swift/$(PLATFORM_NAME) /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/Library/Frameworks @loader_path/../Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/swift/$(PLATFORM_NAME)", + /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/Library/Frameworks, + "@loader_path/../Frameworks", + ); MACH_O_TYPE = mh_dylib; MACOSX_DEPLOYMENT_TARGET = 10.12; PRODUCT_BUNDLE_IDENTIFIER = com.johnholdsworth.InjectionBundle; @@ -1442,7 +1493,10 @@ "$(inherited)", ); INFOPLIST_FILE = EvalApp/Info.plist; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); MACOSX_DEPLOYMENT_TARGET = 10.14; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; @@ -1467,7 +1521,10 @@ DEVELOPMENT_TEAM = ""; ENABLE_HARDENED_RUNTIME = YES; INFOPLIST_FILE = EvalApp/Info.plist; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); MACOSX_DEPLOYMENT_TARGET = 10.14; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = com.johnholdsworth.EvalApp; @@ -1544,6 +1601,25 @@ defaultConfigurationName = Release; }; /* End XCConfigurationList section */ + +/* Begin XCRemoteSwiftPackageReference section */ + CEC17033253ED472002E823F /* XCRemoteSwiftPackageReference "SwiftRegex5" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "http://github.com/johnno1962/SwiftRegex5"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 5.2.1; + }; + }; +/* End XCRemoteSwiftPackageReference section */ + +/* Begin XCSwiftPackageProductDependency section */ + CEC17034253ED472002E823F /* SwiftRegex */ = { + isa = XCSwiftPackageProductDependency; + package = CEC17033253ED472002E823F /* XCRemoteSwiftPackageReference "SwiftRegex5" */; + productName = SwiftRegex; + }; +/* End XCSwiftPackageProductDependency section */ }; rootObject = BB439B5D1FABA64300B4F50B /* Project object */; } diff --git a/InjectionIII/AppDelegate.swift b/InjectionIII/AppDelegate.swift index a35f789c..cb60860a 100644 --- a/InjectionIII/AppDelegate.swift +++ b/InjectionIII/AppDelegate.swift @@ -5,7 +5,7 @@ // Created by John Holdsworth on 06/11/2017. // Copyright © 2017 John Holdsworth. All rights reserved. // -// $Id: //depot/ResidentEval/InjectionIII/AppDelegate.swift#89 $ +// $Id: //depot/ResidentEval/InjectionIII/AppDelegate.swift#90 $ // import Cocoa @@ -378,159 +378,6 @@ class AppDelegate : NSObject, NSApplicationDelegate { // #endif } - @IBAction func runXprobe(_ sender: NSMenuItem) { - if xprobePlugin == nil { - xprobePlugin = XprobePluginMenuController() - xprobePlugin.applicationDidFinishLaunching( - Notification(name: Notification.Name(rawValue: ""))) - xprobePlugin.injectionPlugin = unsafeBitCast(self, to: AnyClass.self) - } - lastConnection?.sendCommand(.xprobe, with: "") - windowItem.isHidden = false - } - - @IBAction func callOrder(_ sender: NSMenuItem) { - lastConnection?.sendCommand(.callOrder, with: nil) - } - - @IBAction func fileOrder(_ sender: NSMenuItem) { - lastConnection?.sendCommand(.fileOrder, with: nil) - } - - func fileOrder(signatures: [String]) { - let builder = SwiftEval() - builder.projectFile = selectedProject - - guard let projectRoot = selectedProject.flatMap({ - URL(fileURLWithPath: $0).deletingLastPathComponent().path+"/" - }), - let (_, logsDir) = - try? builder.determineEnvironment(classNameOrFile: "") else { - lastConnection?.sendCommand(.log, with: - "💉 File ordering not available.") - return - } - - let tmpfile = NSTemporaryDirectory()+"/eval101" - var typesSearched = Set() - - for signature in signatures { - let parts = signature.components(separatedBy: ".") - if parts.count < 3 { - continue - } - let typeName = parts[1] - if typesSearched.insert(typeName).inserted, - let (_, foundSourceFile) = - try? builder.findCompileCommand(logsDir: logsDir, - classNameOrFile: typeName, tmpfile: tmpfile) { - let relativePath = foundSourceFile - .replacingOccurrences(of: projectRoot, with: "") - lastConnection?.sendCommand(.log, with: relativePath) - } - } - } - - @objc func injectionGoto(_ pboard: NSPasteboard, userData: NSString, - error: UnsafeMutablePointer) { - guard pboard.canReadObject(forClasses: [NSString.self], options:nil), - let target = pboard.string(forType: .string) else { return } - - let parts = target.components(separatedBy: ".") - .filter { !$0.hasSuffix("init") } - let builder = SwiftEval() - builder.projectFile = selectedProject - - guard parts.count > 0, let (_, logsDir) = - try? builder.determineEnvironment(classNameOrFile: "") else { - lastConnection?.sendCommand(.log, with: - "💉 Injection Goto service not availble.") - return - } - - var className: String!, sourceFile: String? - let tmpDir = NSTemporaryDirectory() - - for part in parts { - let subParts = part.components(separatedBy: " ") - className = subParts[0] - if let (_, foundSourceFile) = - try? builder.findCompileCommand(logsDir: logsDir, - classNameOrFile: className, tmpfile: tmpDir+"/eval101") { - sourceFile = foundSourceFile - className = subParts.count > 1 ? subParts.last : parts.last - break - } - } - - className = className.replacingOccurrences(of: #"\((\S+).*"#, - with: "$1", - options: .regularExpression) - - guard sourceFile != nil, - let sourceText = try? NSString(contentsOfFile: sourceFile!, - encoding: String.Encoding.utf8.rawValue), - let finder = try? NSRegularExpression(pattern: - #"(?:\b(?:var|func|struct|class|enum)\s+|^[+-]\s*(?:\([^)]*\)\s*)?)(\#(className!))\b"#, - options: [.anchorsMatchLines]) else { - lastConnection?.sendCommand(.log, with: - "💉 Unable to find source file for type '\(className!)'.\n") - return - } - - let match = finder.firstMatch(in: sourceText as String, options: [], - range: NSMakeRange(0, sourceText.length)) - - DispatchQueue.main.async { - if let xCode = SBApplication(bundleIdentifier: XcodeBundleID), -// xCode.activeWorkspaceDocument.path != nil, - let doc = xCode.open(sourceFile!) as? SBObject, - doc.selectedCharacterRange != nil, - let range = match?.range(at: 1) { - doc.selectedCharacterRange = - [NSNumber(value: range.location+1), - NSNumber(value: range.location+range.length)] - } else { - var numberOfLine = 0, index = 0 - - if let range = match?.range(at: 1) { - while index < range.location { - index = NSMaxRange(sourceText - .lineRange(for: NSMakeRange(index, 0))) - numberOfLine += 1 - } - } - - guard numberOfLine != 0 else { return } - - var xed = "/usr/bin/xed" - if let xcodeURL = self.runningXcodeDevURL { - xed = xcodeURL - .appendingPathComponent("usr/bin/xed").path - } - - let script = tmpDir+"/injection_goto.sh" - do { - try "\"\(xed)\" --line \(numberOfLine) \"\(sourceFile!)\"" - .write(toFile: script, atomically: false, encoding: .utf8) - chmod(script, 0o700) - - let task = Process() - task.launchPath = "/usr/bin/open" - task.arguments = ["-b", "com.apple.Terminal", script] - task.launch() - task.waitUntilExit() - } catch { - NSLog("Failed to write \(script)") - } - } - } - } - - @objc func evalCode(_ swift: String) { - lastConnection?.sendCommand(.eval, with:swift) - } - @IBAction func help(_ sender: Any) { _ = NSWorkspace.shared.open(URL(string: "https://github.com/johnno1962/InjectionIII")!) diff --git a/InjectionIII/Base.lproj/MainMenu.xib b/InjectionIII/Base.lproj/MainMenu.xib index 3681dc72..bc0c44a9 100644 --- a/InjectionIII/Base.lproj/MainMenu.xib +++ b/InjectionIII/Base.lproj/MainMenu.xib @@ -818,6 +818,13 @@ + + + + + + + diff --git a/InjectionIII/Experimental.swift b/InjectionIII/Experimental.swift new file mode 100644 index 00000000..6d38cadc --- /dev/null +++ b/InjectionIII/Experimental.swift @@ -0,0 +1,248 @@ +// +// Experimental.swift +// InjectionIII +// +// Created by User on 20/10/2020. +// Copyright © 2020 John Holdsworth. All rights reserved. +// +// $Id: //depot/ResidentEval/InjectionIII/Experimental.swift#10 $ +// + +import Cocoa +import SwiftRegex + +extension AppDelegate { + + @IBAction func runXprobe(_ sender: NSMenuItem) { + if xprobePlugin == nil { + xprobePlugin = XprobePluginMenuController() + xprobePlugin.applicationDidFinishLaunching( + Notification(name: Notification.Name(rawValue: ""))) + xprobePlugin.injectionPlugin = unsafeBitCast(self, to: AnyClass.self) + } + lastConnection?.sendCommand(.xprobe, with: "") + windowItem.isHidden = false + } + + @objc func evalCode(_ swift: String) { + lastConnection?.sendCommand(.eval, with:swift) + } + + @IBAction func callOrder(_ sender: NSMenuItem) { + lastConnection?.sendCommand(.callOrder, with: nil) + } + + @IBAction func fileOrder(_ sender: NSMenuItem) { + lastConnection?.sendCommand(.fileOrder, with: nil) + } + + @IBAction func fileReorder(_ sender: NSMenuItem) { + lastConnection?.sendCommand(.fileReorder, with: nil) + } + + func fileOrder(signatures: [String]) { + let builder = SwiftEval() + builder.projectFile = selectedProject + + guard let projectRoot = selectedProject.flatMap({ + URL(fileURLWithPath: $0).deletingLastPathComponent().path+"/" + }), + let (_, logsDir) = + try? builder.determineEnvironment(classNameOrFile: "") else { + lastConnection?.sendCommand(.log, with: + "💉 File ordering not available.") + return + } + + let tmpfile = NSTemporaryDirectory()+"/eval101" + + uniqueTypeNames(signatures: signatures) { typeName in + if let (_, foundSourceFile) = + try? builder.findCompileCommand(logsDir: logsDir, + classNameOrFile: typeName, tmpfile: tmpfile) { + let relativePath = foundSourceFile + .replacingOccurrences(of: projectRoot, with: "") + lastConnection?.sendCommand(.log, with: relativePath) + } + } + } + + func fileReorder(signatures: [String]) { + var projectEncoding: String.Encoding = .utf8 + guard let projectURL = selectedProject.flatMap({ + URL(fileURLWithPath: $0).appendingPathComponent("project.pbxproj") + }), + let projectSource = try? String(contentsOf: projectURL, + usedEncoding: &projectEncoding) + else { + lastConnection?.sendCommand(.log, with: + "💉 Could not load project file.") + return + } + + var orders = ["AppDelegate.swift": 0] + var order = 1 + uniqueTypeNames(signatures: signatures) { typeName in + orders[typeName+".swift"] = order + order += 1 + } + + var newProjectSource = projectSource + newProjectSource[#""" + ^\s+isa = PBXSourcesBuildPhase; + \s+buildActionMask = \d+; + \s+files = \( + ((?:[^\n]+\n)*?)\# + \s+\); + + """#.anchorsMatchLines, group: 1] = { + (sources: String, stop) -> String in + return (sources[#"(\s+\S+ /\* (\S+) in Sources \*/,\n)"#] + as [(line: String, file: String)]).sorted(by: { + orders[$0.file] ?? order < orders[$1.file] ?? order + }).map { $0.line }.joined() + } + + DispatchQueue.main.sync { + let backup = projectURL.path+".preorder" + let alert = NSAlert() + alert.messageText = "About to reorder project's source files" + alert.informativeText = "This experimental feature will modify the order of source files in memory to reduce paging on startup. There will be a backup of the project file before re-ordering at: \(backup)" + alert.addButton(withTitle: "Cancel") + alert.addButton(withTitle: "Go ahead") + switch alert.runModal() { + case .alertSecondButtonReturn: + do { + if !FileManager.default.fileExists(atPath: backup) { + try projectSource.write(toFile: backup, atomically: true, + encoding: projectEncoding) + } + try newProjectSource.write(to: projectURL, atomically: true, + encoding: projectEncoding) + } catch { + NSAlert(error: error).runModal() + } + default: + break + } + } + } + + func uniqueTypeNames(signatures: [String], exec: (String) -> Void) { + var typesSearched = Set() + + for signature in signatures { + let parts = signature.components(separatedBy: ".") + if parts.count < 3 { + continue + } + let typeName = parts[1] + if typesSearched.insert(typeName).inserted { + exec(typeName) + } + } + } + + + /// Entry point for "Injection Goto" service + /// - Parameters: + /// - pboard: NSPasteboard containing selected type [+method) name + /// - userData: N/A + /// - error: N/A + @objc func injectionGoto(_ pboard: NSPasteboard, userData: NSString, + error errorPtr: UnsafeMutablePointer) { + guard pboard.canReadObject(forClasses: [NSString.self], options:nil), + let target = pboard.string(forType: .string) else { return } + + let parts = target.components(separatedBy: ".") + .filter { !$0.hasSuffix("init") } + let builder = SwiftEval() + builder.projectFile = selectedProject + + guard parts.count > 0, let (_, logsDir) = + try? builder.determineEnvironment(classNameOrFile: "") else { + errorPtr.pointee = "💉 Injection Goto service not availble." + lastConnection?.sendCommand(.log, with: errorPtr.pointee as String) + return + } + + var className: String!, sourceFile: String? + let tmpDir = NSTemporaryDirectory() + + for part in parts { + let subParts = part.components(separatedBy: " ") + className = subParts[0] + if let (_, foundSourceFile) = + try? builder.findCompileCommand(logsDir: logsDir, + classNameOrFile: className, tmpfile: tmpDir+"/eval101") { + sourceFile = foundSourceFile + className = subParts.count > 1 ? subParts.last : parts.last + break + } + } + + className = className.replacingOccurrences(of: #"\((\S+).*"#, + with: "$1", + options: .regularExpression) + + guard sourceFile != nil, + let sourceText = try? NSString(contentsOfFile: sourceFile!, + encoding: String.Encoding.utf8.rawValue), + let finder = try? NSRegularExpression(pattern: + #"(?:\b(?:var|func|struct|class|enum)\s+|^[+-]\s*(?:\([^)]*\)\s*)?)(\#(className!))\b"#, + options: [.anchorsMatchLines]) else { + errorPtr.pointee = "💉 Unable to find source file for type '\(className!)' using build logs. Try with a clean build.\n" as NSString + lastConnection?.sendCommand(.log, with: errorPtr.pointee as String) + return + } + + let match = finder.firstMatch(in: sourceText as String, options: [], + range: NSMakeRange(0, sourceText.length)) + + DispatchQueue.main.async { + if let xCode = SBApplication(bundleIdentifier: XcodeBundleID), +// xCode.activeWorkspaceDocument.path != nil, + let doc = xCode.open(sourceFile!) as? SBObject, + doc.selectedCharacterRange != nil, + let range = match?.range(at: 1) { + doc.selectedCharacterRange = + [NSNumber(value: range.location+1), + NSNumber(value: range.location+range.length)] + } else { + var numberOfLine = 0, index = 0 + + if let range = match?.range(at: 1) { + while index < range.location { + index = NSMaxRange(sourceText + .lineRange(for: NSMakeRange(index, 0))) + numberOfLine += 1 + } + } + + guard numberOfLine != 0 else { return } + + var xed = "/usr/bin/xed" + if let xcodeURL = self.runningXcodeDevURL { + xed = xcodeURL + .appendingPathComponent("usr/bin/xed").path + } + + let script = tmpDir+"/injection_goto.sh" + do { + try "\"\(xed)\" --line \(numberOfLine) \"\(sourceFile!)\"" + .write(toFile: script, atomically: false, encoding: .utf8) + chmod(script, 0o700) + + let task = Process() + task.launchPath = "/usr/bin/open" + task.arguments = ["-b", "com.apple.Terminal", script] + task.launch() + task.waitUntilExit() + } catch { + errorPtr.pointee = "💉 Failed to write \(script): \(error)" as NSString + NSLog("\(errorPtr.pointee)") + } + } + } + } +} diff --git a/InjectionIII/Info.plist b/InjectionIII/Info.plist index 6d0efb2e..a1a3dff4 100644 --- a/InjectionIII/Info.plist +++ b/InjectionIII/Info.plist @@ -19,7 +19,7 @@ CFBundleShortVersionString $(MARKETING_VERSION) CFBundleVersion - 4578 + 4662 LSApplicationCategoryType public.app-category.developer-tools LSMinimumSystemVersion diff --git a/InjectionIII/InjectionServer.swift b/InjectionIII/InjectionServer.swift index d7fd4530..112dab89 100644 --- a/InjectionIII/InjectionServer.swift +++ b/InjectionIII/InjectionServer.swift @@ -5,7 +5,7 @@ // Created by John Holdsworth on 06/11/2017. // Copyright © 2017 John Holdsworth. All rights reserved. // -// $Id: //depot/ResidentEval/InjectionIII/InjectionServer.swift#56 $ +// $Id: //depot/ResidentEval/InjectionIII/InjectionServer.swift#57 $ // let commandQueue = DispatchQueue(label: "InjectionCommand") @@ -220,9 +220,15 @@ public class InjectionServer: SimpleSocket { sendCommand(.signed, with: signedOK ? "1": "0") break case .callOrderList: + fallthrough + case .callReorderList: if let calls = readString()? .components(separatedBy: CALLORDER_DELIMITER) { - appDelegate.fileOrder(signatures: calls) + if command == .callOrderList { + appDelegate.fileOrder(signatures: calls) + } else { + appDelegate.fileReorder(signatures: calls) + } } break case .error: