Showing preview only (443K chars total). Download the full file or copy to clipboard to get everything.
Repository: johnno1962/HotReloading
Branch: main
Commit: b8a80e54f268
Files: 55
Total size: 423.3 KB
Directory structure:
gitextract_6t44zbop/
├── .github/
│ └── FUNDING.yml
├── .gitignore
├── Contents/
│ ├── Info.plist
│ ├── PkgInfo
│ └── Resources/
│ ├── Base.lproj/
│ │ ├── MainMenu.nib
│ │ ├── RMWindowController.nib
│ │ ├── XprobeConsole.nib
│ │ └── XprobePluginMenuController.nib
│ ├── Credits.rtf
│ ├── InjectionBusy.tif
│ ├── InjectionError.tif
│ ├── InjectionIdle.tif
│ ├── InjectionOK.tif
│ ├── LICENSE
│ ├── README.md
│ ├── SwiftTrace.h
│ ├── fishhook.h
│ ├── graph.gv
│ └── log.html
├── LICENSE
├── Package.swift
├── README.md
├── Sources/
│ ├── HotReloading/
│ │ ├── DeviceInjection.swift
│ │ ├── DynamicCast.swift
│ │ ├── FileWatcher.swift
│ │ ├── InjectionClient.swift
│ │ ├── InjectionStats.swift
│ │ ├── ObjcInjection.swift
│ │ ├── ReducerInjection.swift
│ │ ├── StandaloneInjection.swift
│ │ ├── SwiftEval.swift
│ │ ├── SwiftInjection.swift
│ │ ├── SwiftInterpose.swift
│ │ ├── SwiftKeyPath.swift
│ │ ├── SwiftSweeper.swift
│ │ ├── UnhidingEval.swift
│ │ └── Vaccine.swift
│ ├── HotReloadingGuts/
│ │ ├── ClientBoot.mm
│ │ ├── SimpleSocket.mm
│ │ ├── Unhide.mm
│ │ └── include/
│ │ ├── InjectionClient.h
│ │ ├── SimpleSocket.h
│ │ └── UserDefaults.h
│ ├── injectiond/
│ │ ├── AppDelegate.swift
│ │ ├── DeviceServer.swift
│ │ ├── Experimental.swift
│ │ ├── InjectionServer.swift
│ │ ├── UpdateCheck.swift
│ │ └── main.swift
│ └── injectiondGuts/
│ ├── SignerService.m
│ └── include/
│ ├── SignerService.h
│ └── Xcode.h
├── copy_bundle.sh
├── fix_previews.sh
└── start_daemon.sh
================================================
FILE CONTENTS
================================================
================================================
FILE: .github/FUNDING.yml
================================================
# These are supported funding model platforms
github: johnno1962 # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
patreon: # Replace with a single Patreon username
open_collective: # Replace with a single Open Collective username
ko_fi: # Replace with a single Ko-fi username
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
liberapay: # Replace with a single Liberapay username
issuehunt: # Replace with a single IssueHunt username
otechie: # Replace with a single Otechie username
custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
================================================
FILE: .gitignore
================================================
# Xcode
#
# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
## User settings
xcuserdata/
## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9)
*.xcscmblueprint
*.xccheckout
## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4)
build/
DerivedData/
*.moved-aside
*.pbxuser
!default.pbxuser
*.mode1v3
!default.mode1v3
*.mode2v3
!default.mode2v3
*.perspectivev3
!default.perspectivev3
## Obj-C/Swift specific
*.hmap
## App packaging
*.ipa
*.dSYM.zip
*.dSYM
## Playgrounds
timeline.xctimeline
playground.xcworkspace
# Swift Package Manager
#
# Add this line if you want to avoid checking in source code from Swift Package Manager dependencies.
# Packages/
# Package.pins
Package.resolved
# *.xcodeproj
#
# Xcode automatically generates this directory with a .xcworkspacedata file and xcuserdata
# hence it is not needed unless you have added a package configuration file to your project
.swiftpm
.build/
# 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/
#
# Add this line if you want to avoid checking in source code from the Xcode workspace
# *.xcworkspace
# Carthage
#
# Add this line if you want to avoid checking in source code from Carthage dependencies.
# Carthage/Checkouts
Carthage/Build/
# Accio dependency management
Dependencies/
.accio/
# 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://docs.fastlane.tools/best-practices/source-control/#source-control
fastlane/report.xml
fastlane/Preview.html
fastlane/screenshots/**/*.png
fastlane/test_output
# Code Injection
#
# After new code Injection tools there's a generated folder /iOSInjectionProject
# https://github.com/johnno1962/injectionforxcode
iOSInjectionProject/
================================================
FILE: Contents/Info.plist
================================================
<?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>BuildMachineOSBuild</key>
<string>20D74</string>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
<string>InjectionIII</string>
<key>CFBundleIconFile</key>
<string>App.icns</string>
<key>CFBundleIdentifier</key>
<string>com.johnholdsworth.InjectionIII</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>🔥 HotReloading</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>2.6.0</string>
<key>CFBundleSupportedPlatforms</key>
<array>
<string>MacOSX</string>
</array>
<key>CFBundleVersion</key>
<string>6076</string>
<key>DTCompiler</key>
<string>com.apple.compilers.llvm.clang.1_0</string>
<key>DTPlatformBuild</key>
<string>12D4e</string>
<key>DTPlatformName</key>
<string>macosx</string>
<key>DTPlatformVersion</key>
<string>11.1</string>
<key>DTSDKBuild</key>
<string>20C63</string>
<key>DTSDKName</key>
<string>macosx11.1</string>
<key>DTXcode</key>
<string>1240</string>
<key>DTXcodeBuild</key>
<string>12D4e</string>
<key>LSApplicationCategoryType</key>
<string>public.app-category.developer-tools</string>
<key>LSMinimumSystemVersion</key>
<string>10.12</string>
<key>LSUIElement</key>
<true/>
<key>NSHumanReadableCopyright</key>
<string>Copyright © 2017-20 John Holdsworth. All rights reserved.</string>
<key>NSMainNibFile</key>
<string>MainMenu</string>
<key>NSPrincipalClass</key>
<string>NSApplication</string>
<key>NSServices</key>
<array>
<dict>
<key>NSMenuItem</key>
<dict>
<key>default</key>
<string>Injection Goto</string>
</dict>
<key>NSMessage</key>
<string>injectionGoto</string>
<key>NSPortName</key>
<string>InjectionIII</string>
<key>NSSendTypes</key>
<array>
<string>NSStringPboardType</string>
</array>
</dict>
</array>
<key>SMPrivilegedExecutables</key>
<dict>
<key>com.johnholdsworth.InjectionIII.Helper</key>
<string>identifier com.johnholdsworth.InjectionIII.Helper</string>
</dict>
</dict>
</plist>
================================================
FILE: Contents/PkgInfo
================================================
APPL????
================================================
FILE: Contents/Resources/Credits.rtf
================================================
{\rtf1\ansi\ansicpg1252\cocoartf1561\cocoasubrtf600
{\fonttbl\f0\fswiss\fcharset0 Helvetica;\f1\fnil\fcharset0 Menlo-Regular;\f2\fmodern\fcharset0 Courier;
\f3\fnil\fcharset0 HelveticaNeue;}
{\colortbl;\red255\green255\blue255;\red63\green110\blue116;\red255\green255\blue255;\red83\green98\blue108;
\red0\green0\blue0;\red131\green108\blue40;\red14\green14\blue255;}
{\*\expandedcolortbl;;\csgenericrgb\c24700\c43100\c45600;\csgenericrgb\c100000\c100000\c100000;\csgenericrgb\c32549\c38431\c42353;
\csgenericrgb\c0\c0\c0;\csgenericrgb\c51200\c42300\c15700;\csgenericrgb\c5500\c5500\c100000;}
\paperw11900\paperh16840\vieww9600\viewh8400\viewkind0
\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\partightenfactor0
\f0\fs24 \cf0 InjectionIII.app is a mostly Swift rewrite of {\field{\*\fldinst{HYPERLINK "https://github.com/johnno1962/injectionforxcode"}}{\fldrslt
\f1\fs22 injectionforxcode}} that runs in the menu bar. It works slightly differently from previous versions of injection in that it uses a file watcher to detect when a user saves a file in the current project to inject it. You can either use the "Start Injection" menu item to bootstrap injection into your application or include the following code in your application:\
\
\pard\tx543\pardeftab543\pardirnatural\partightenfactor0
\f2 \cf2 \cb3 #if DEBUG\cf0 \
\cf2 Bundle(path: "/Applications/InjectionIII.app/Contents/Resources/iOSInjection.bundle")?.load()\cf0 \
\cf2 //for tvOS:\cf0 \
\cf2 Bundle(path: "/Applications/InjectionIII.app/Contents/Resources/tvOSInjection.bundle")?.load()\cf0 \
\cf2 //Or for macOS:\cf0 \
\cf2 Bundle(path: "/Applications/InjectionIII.app/Contents/Resources/macOSInjection.bundle")?.load()\cf0 \
\cf2 #endif\cf0 \
\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardeftab543\partightenfactor0
\f0 \cf0 \cb1 \
Or, to use with Xcode 10:\
\
\pard\tx543\pardeftab543\pardirnatural\partightenfactor0
\f2 \cf2 \cb3 #if DEBUG\cf0 \
\cf2 Bundle(path: "/Applications/InjectionIII.app/Contents/Resources/iOSInjection10.bundle")?.load()\cf0 \
\cf2 //for tvOS:\cf0 \
\cf2 Bundle(path: "/Applications/InjectionIII.app/Contents/Resources/tvOSInjection10.bundle")?.load()\cf0 \
\cf2 //Or for macOS:\cf0 \
\cf2 Bundle(path: "/Applications/InjectionIII.app/Contents/Resources/macOSInjection10.bundle")?.load()\cf0 \
\cf2 #endif\cf0 \
\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardeftab543\partightenfactor0
\f0 \cf0 \cb1 \
\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\partightenfactor0
\cf0 For further help go to {\field{\*\fldinst{HYPERLINK "https://github.com/johnno1962/InjectionIII"}}{\fldrslt https://github.com/johnno1962/InjectionIII}} or {\field{\*\fldinst{HYPERLINK "http://johnholdsworth.com/injection.html"}}{\fldrslt http://johnholdsworth.com/injection.html}}\
\
\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\partightenfactor0
\f1\fs22 \cf0 Copyright (C) 2016-7 John Holdsworth {\field{\*\fldinst{HYPERLINK "mailto:injectionIII@johnholdsworth.com"}}{\fldrslt injectionIII@johnholdsworth.com}} {\field{\*\fldinst{HYPERLINK "https://twitter.com/Injection4Xcode"}}{\fldrslt
\fs24 \cf4 \expnd0\expndtw0\kerning0
@Injection4Xcode}}\
\
\pard\pardeftab720\partightenfactor0
\fs24 \cf0 \expnd0\expndtw0\kerning0
Permission is hereby granted, free of charge, to any person obtaining a copy\
of this software and associated documentation files (the "Software"), to deal\
in the Software without restriction, including without limitation the rights\
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\
copies of the Software, and to permit persons to whom the Software is\
furnished to do so, subject to the following conditions:\
\
The above copyright notice and this permission notice shall be included in\
all copies or substantial portions of the Software.\
\
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT \
LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. \
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, \
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE \
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\
\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\partightenfactor0
\fs22 \cf0 \kerning1\expnd0\expndtw0 \
\pard\tx543\pardeftab543\pardirnatural\partightenfactor0
\f3\fs26 \cf5 \cb3 This release includes a very slightly modified version of the excellent
\f0\fs24 \cf0 \
\f3\fs26 \cf6 [\cf5 canviz\cf6 ](\cf7 https://code.google.com/p/canviz/\cf6 )\cf5 library to render "dot" files
\f0\fs24 \cf0 \
\f3\fs26 \cf5 in an HTML canvas which is subject to an MIT license. The changes are to pass
\f0\fs24 \cf0 \
\f3\fs26 \cf5 through the ID of the node to the node label tag (line 212), to reverse
\f0\fs24 \cf0 \
\f3\fs26 \cf5 the rendering of nodes and the lines linking them (line 406) and to
\f0\fs24 \cf0 \
\f3\fs26 \cf5 store edge paths so they can be colored (line 66 and 303) in "canviz-0.1/canviz.js".
\f0\fs24 \cf0 \
\
\f3\fs26 \cf5 It now also includes \cf6 [\cf5 CodeMirror\cf6 ](\cf7 http://codemirror.net/\cf6 )\cf5 JavaScript editor
\f0\fs24 \cf0 \
\f3\fs26 \cf5 for the code to be evaluated using injection under an MIT license.
\f0\fs24 \cf0 \
}
================================================
FILE: Contents/Resources/LICENSE
================================================
MIT License
Copyright (c) 2017 John Holdsworth
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: Contents/Resources/README.md
================================================
# InjectionIII - overdue Swift rewrite of InjectionForXcode

Code injection allows you to update the implementation of functions and any method of a class, struct or enum incrementally
in the iOS simulator without having to rebuild or restart your application. This saves the developer a significant amount of time tweaking code or iterating over a design.
This start-over implementation of [Injection for Xcode](https://github.com/johnno1962/injectionforxcode)
has been built into a standalone app: `InjectionIII.app` which runs in the status bar and is [available from the Mac App Store](https://itunes.apple.com/app/injectioniii/id1380446739?mt=12).
This README includes descriptions of some newer features that are only available in more recent
releases of the InjectionIII.app [available on github](https://github.com/johnno1962/InjectionIII/releases).
You will need to use one of these releases for Apple Silicon or if you have upgraded to Big Sur
due to changes to macOS codesigning that affect the sandboxed App Store version of the app.

`InjectionIII.app` needs an Xcode 10.2 or greater at the path `/Applications/Xcode.app` , works for `Swift` and `Objective-C` and can be used alongside [AppCode](https://www.jetbrains.com/help/objc/create-a-swiftui-application.html) or by using the [AppCode Plugin](https://github.com/johnno1962/InjectionIII/blob/master/AppCodePlugin/INSTALL.md).
To understand how InjectionIII works and the techniques it uses consult the book [Swift Secrets](http://books.apple.com/us/book/id1551005489).
### Getting Started
To use injection, download the app from the App Store and run it. Then, you must add `"-Xlinker -interposable"` (without the double quotes) to your project's `"Other Linker Flags"` for the Debug target (qualified by the simulator SDK to avoid complications with bitcode). Finally, add one of the following to your application delegate's `applicationDidFinishLaunching:`
Xcode 10.2 and later (Swift 5+):
```Swift
#if DEBUG
Bundle(path: "/Applications/InjectionIII.app/Contents/Resources/iOSInjection.bundle")?.load()
//for tvOS:
Bundle(path: "/Applications/InjectionIII.app/Contents/Resources/tvOSInjection.bundle")?.load()
//Or for macOS:
Bundle(path: "/Applications/InjectionIII.app/Contents/Resources/macOSInjection.bundle")?.load()
#endif
```
Adding one of these lines loads a bundle included in the `InjectionIII.app`'s
resources which connects over a localhost socket to the InjectionII app which runs on the task bar.
Once injection is connected, you'll be prompted to select the directory containing the project file for the app you wish to inject. This starts a `file watcher` for that directory inside the Mac app so whenever
you save to disk a Swift (or Objective-C) source in the project, the target app is messaged through the socket to compile, link, dynamically load and update the implementation of methods in the file being injected.
If your project is organised across multiple directories or the project file is not at the root of the source tree you can add other directories to be watched for file changes using the "Add Directory"
menu item. This list resets when you select a new project.
The file watcher can be disabled & enabled while the app is running using the status bar men.
While the file watcher is disabled you can still force injections through manually using a hotkey `ctrl-=` (remember to save the file first!)
If you inject a subclass of `XCTest` InjectionIII will try running that individual test inside your application provided has been compiled at some time in the past and doesn't require test specific support code.
When you run your application without rebuilding (^⌘R), recent injections will be re-applied.
You can detect when a *class* has been injected in your code (to reload a view controller for example) by adding an `@objc func
injected()` class or instance method. The instance `@objc
func injected()` method relies on a "sweep" of all objects in your application to find those of
the class you have just injected which can be unreliable when using `unowned` instance variables. If you encounter problems, remomve the injected() method and subscribe to the `"INJECTION_BUNDLE_NOTIFICATION"` instead along the lines of the following:
```
NotificationCenter.default.addObserver(self,
selector: #selector(configureView),
name: Notification.Name("INJECTION_BUNDLE_NOTIFICATION"), object: nil)
```
Included in this release is "Xprobe" which allows you to browse and inspect the objects in
your application through a web-like interface and execute code against them. Enter text into the search textfield to locate objects quickly by class name.
If you want to build this project from source (which you may need to do to use injection with macOS apps) you'll need to use:
git clone https://github.com/johnno1962/InjectionIII --recurse-submodules
### Available downloads
| Xcode 10.2+ | For Big Sur | AppCode Plugin |
| ------------- | ------------- | ------------- |
| [Mac app store](https://itunes.apple.com/app/injectioniii/id1380446739?mt=12) | [Github Releases](https://github.com/johnno1962/InjectionIII/releases) | [Install Injection.jar](https://github.com/johnno1962/InjectionIII/tree/master/AppCodePlugin) |
### Limitations/FAQ
New releases of InjectionIII use a [different patching technique](http://johnholdsworth.com/dyld_dynamic_interpose.html)
than previous versions in that you can now update the implementations of class, struct and enum methods (final or not)
provided they have not been inlined which shouldn't be the case for a debug build. You can't however alter the layout of
a class or struct in the course of an injection i.e. add or rearrange properties with storage or add or move methods of a
non-final class or your app will likely crash. Also, see the notes below for injecting `SwiftUI` views and how they require
type erasure.
If you have a complex project including Objective-C or C dependancies, using the `-interposable` flag may provoke the following error on linking:
```
Can't find ordinal for imported symbol for architecture x86_64
```
If this is the case, add the following additional "Other linker Flags" and it should go away.
```
-Xlinker -undefined -Xlinker dynamic_lookup
```
If you inject code which calls a function with default arguments you may
get an error starting as follows reporting an undefined symbol:
```
💉 *** dlopen() error: dlopen(/var/folders/nh/gqmp6jxn4tn2tyhwqdcwcpkc0000gn/T/com.johnholdsworth.InjectionIII/eval101.dylib, 2): Symbol not found: _$s13TestInjection15QTNavigationRowC4text10detailText4icon6object13customization6action21accessoryButtonActionACyxGSS_AA08QTDetailG0OAA6QTIconOSgypSgySo15UITableViewCellC_AA5QTRow_AA0T5StyleptcSgyAaT_pcSgAWtcfcfA1_
Referenced from: /var/folders/nh/gqmp6jxn4tn2tyhwqdcwcpkc0000gn/T/com.johnholdsworth.InjectionIII/eval101.dylib
Expected in: flat namespace
in /var/folders/nh/gqmp6jxn4tn2tyhwqdcwcpkc0000gn/T/com.johnholdsworth.InjectionIII/eval101.dylib ***
```
If you encounter this problem, download and build [the unhide project](https://github.com/johnno1962/unhide) then add the following
as a "Run Script", "Build Phase" to your project after the linking phase:
```
UNHIDE=~/bin/unhide.sh
if [ -f $UNHIDE ]; then
$UNHIDE
else
echo "File $UNHIDE used for code Injection does not exist. Download and build the https://github.com/johnno1962/unhide project."
fi
```
This changes the visibility of symbols for default argument generators
and this issue should disappear.
If you are using Code Coverage, you may need to disable it or you will receive a:
> `Symbol not found: ___llvm_profile_runtime` error.`
Go to `Edit Scheme -> Test -> Options -> Code Coverage` and (temporarily) disable.
Keep in mind global state -- If the file you're injecting has top level variables e.g. singletons, static or global vars
they will be reset when you inject the code as the new method implementations will refer to the newly loaded
object file containing the type.
As injection needs to know how to compile Swift files individually it is not compatible with building using
`Whole Module Optimisation`. A workaround for this is to build with `WMO` switched off so there are
logs of individual compiles available then switching `WMO` back on if it suits your workflow better.
### SwiftUI Injection
It is possible to inject `SwiftUI` interfaces but it requires some minor
code changes. This is because when you add elements to an interface or
use modifiers that change their type, this changes the return type of the
body properties' `Content` which causes a crash. To avoid this you need
to erase the return type. The easiest way to do this is to add the code below
to your source somewhere then add the modifier `.eraseToAnyView()` at
the very end of any declaration of a view's body property that you want to inject:
```Swift
#if DEBUG
private var loadInjection: () = {
#if os(macOS)
let bundleName = "macOSInjection.bundle"
#elseif os(tvOS)
let bundleName = "tvOSInjection.bundle"
#elseif targetEnvironment(simulator)
let bundleName = "iOSInjection.bundle"
#else
let bundleName = "maciOSInjection.bundle"
#endif
Bundle(path: "/Applications/InjectionIII.app/Contents/Resources/"+bundleName)!.load()
}()
import Combine
public let injectionObserver = InjectionObserver()
public class InjectionObserver: ObservableObject {
@Published var injectionNumber = 0
var cancellable: AnyCancellable? = nil
let publisher = PassthroughSubject<Void, Never>()
init() {
cancellable = NotificationCenter.default.publisher(for:
Notification.Name("INJECTION_BUNDLE_NOTIFICATION"))
.sink { [weak self] change in
self?.injectionNumber += 1
self?.publisher.send()
}
}
}
extension View {
public func eraseToAnyView() -> some View {
_ = loadInjection
return AnyView(self)
}
public func onInjection(bumpState: @escaping () -> ()) -> some View {
return self
.onReceive(injectionObserver.publisher, perform: bumpState)
.eraseToAnyView()
}
}
#else
extension View {
public func eraseToAnyView() -> some View { return self }
public func onInjection(bumpState: @escaping () -> ()) -> some View {
return self
}
}
#endif
```
To have the view you are working on redisplay automatically when it is injected it's sufficient
to add an `@ObservedObject`, initialised to the `injectionObserver` instance as follows:
```Swift
.eraseToAnyView()
}
#if DEBUG
@ObservedObject var iO = injectionObserver
#endif
```
You can make all these changes automatically once you've opened a project using the
`"Prepare Project"` menu item. If you'd like to execute some code each time your interface is injected, use the
`.onInjection { ... }` modifier instead of .`eraseToAnyView()`.
### macOS Injection
It is possible to use injection with a macOS/Catalyst project but it is getting progressively more difficult
with each release of the OS. You need to make sure to turn off the "App Sandbox" and also "Disable
Library Validation" under the "Hardened Runtime" options for your project while you inject.
With an Apple Silicon Mac it is possible to run your iOS application natively on macOS.
You cuse injection with these apps but as you can't turn off library validation it's a little
involved. You need re-codesign the maciOSInjection.bundle contained in the InjectionIII
app package using the signing identity used by your target app which you can determine
from the `Sign` phase in your app's build logs. You will also need to set a user default with
the path to your project file as the name and the signing identity as the value to injected
code changes can be signed properly.
All this is best done by adding the following as a build phase to your target project:
```
# Type a script or drag a script file from your workspace to insert its path.
export CODESIGN_ALLOCATE\=/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/codesign_allocate
INJECTION_APP_RESOURCES=/Applications/InjectionIII.app/Contents/Resources
/usr/bin/codesign --force --sign $EXPANDED_CODE_SIGN_IDENTITY $INJECTION_APP_RESOURCES/maciOSInjection.bundle/maciOSInjection
/usr/bin/codesign --force --sign $EXPANDED_CODE_SIGN_IDENTITY $INJECTION_APP_RESOURCES/maciOSSwiftUISupport.bundle/maciOSSwiftUISupport
/usr/bin/codesign --force --sign $EXPANDED_CODE_SIGN_IDENTITY $INJECTION_APP_RESOURCES/maciOSInjection.bundle/Frameworks/SwiftTrace.framework/SwiftTrace
defaults write com.johnholdsworth.InjectionIII "$PROJECT_FILE_PATH" $EXPANDED_CODE_SIGN_IDENTITY
```
### Storyboard injection
Sometimes when you are iterating over a UI it is useful to be able to inject storyboards. This works slightly differently from code injection. To inject changes to a storyboard scene, make your changes then _build_ the project instead of saving the storyboard. The "nib" of the currently displayed view controlled should be reloaded and viewDidLoad etc. will be called.
### Vaccine
Injection now includes the higher level `Vaccine` functionality, for more information consult the [project README](https://github.com/zenangst/Vaccine) or one of the [following](https://medium.com/itch-design-no/code-injection-in-swift-c49be095414c) [references](https://medium.com/@robnorback/the-secret-to-1-second-compile-times-in-xcode-9de4ec8345a1).
### Method Tracing menu item (SwiftTrace)
It's possible to inject tracing aspects into your program that don't
affect it's operation but log every method call. Where possible
it will also decorate their arguments. You can add logging to all
methods in your app's main bundle or the frameworks it uses
or trace calls to system frameworks such as UIKit or SwiftUI.
If you opt into "Type Lookup", custom types in your appliction
can also be decorated using the CustomStringConvertable
conformance or the default formatter for structs.
These features are implemented by the package [SwiftTrace](https://github.com/johnno1962/SwiftTrace)
which is built into the InjectionBundle. If you want finer grain control of what is being traced,
include the following header file in your project's bridging header and a subset of the internal
api will be available to Swift (after an injection bundle has been loaded):
```C++
#import "/Applications/InjectionIII.app/Contents/Resources/SwiftTrace.h"
```
The "Trace Main Bundle" menu item can be mimicked by using the following call:
```Swift
NSObject.swiftTraceMainBundleMethods()
```
If you want instead to also trace all Swift calls your application makes to a system
framework such as SwiftUI you can use the following:
```Swift
NSObject.swiftTraceMethods(inFrameworkContaining:UIHostingController<ContentView>.self)
```
To include or exclude the methods to be traced use the `methodInclusionPattern`
and `methodExclusionPattern` class properties of SwiftTrace. For more information
consult the [SwiftTrace source repo](https://github.com/johnno1962/SwiftTrace). It's also
possible to access the Swift API of SwiftTrace directly in your app. For example, to add
a new handler to format a particular type by importing SwiftTrace and adding the
following to your app's `"Framework Search Paths"` and `"Runpath Search Paths"`
(for the Debug configuration):
```
/Applications/InjectionIII.app/Contents/Resources/iOSInjection.bundle/Frameworks
```
Then, you can use something like the following to register the type:
```
SwiftTrace.makeTraceable(types: [MovieSwift.MovieRow.Props.self])
```
In this case however the `MovieSwift.MovieRow.Props` type from the excellent
`MovieSwift` SwiftUI [example project](https://github.com/Dimillian/MovieSwiftUI)
is too large to format but can be changed to be a class instead of a struct.
Finally, if you'd like to go directly to the file that defines a logged method, select the
fully qualified method and use the service `Injection Goto` to open the file declaring
that function. (To have the `Injection Goto` item appear on your services context menu
you need to select it in System Preferences/Keyboard, tab Shortcuts/Services, under the
"Text" section.)
There are other SwifTrace features that allow you to "profile" your application to optimise
the order object files are linked into your application which could potentially minimise
paging on startup. These are surfaced in the "Method Tracing" submenu but if I'm honest,
these would only make a difference if you had a very, very large application binary.
### Remote Control
Newer versions of InjectionIII contain a server that allows you to control your development device from your desktop once the service has been started. The UI allows you to record and replay macros of UI actions then verify the device screen against snapshots for end-to-end testing.
To use, import the Swift Package `https://github.com/johnno1962/Remote.git`
and call `RemoteCapture.start("hostname")` where hostname is a space
separated list of hostnames or IP addreses.
When InjectionIII is running, select the "Remote/Start Server" menu item to start the
server and then run your app. It should connect to the server which will pop up a
window showing the device display and accepting tap events. Events can be
saved as `macros` and replayed. If you include a snapshot in a macro this will
be compared against the device display (within a tolerance) when you replay
the macro for automated testing. Remote can also be used to capture videos
of your app in operation but, as it operates over the network, it isn't fast enough
to capture animated transitions.
## SwiftEval - Yes, it's eval() for Swift

InjectionIII started out as the SwiftEval class which is a [single Swift source](InjectionBundle/SwiftEval.swift)
that can be added to your iOS simulator or macOS projects to implement an eval function inside
classes that inherit from NSObject. There is a generic form which has the following signature:
```Swift
extension NSObject {
public func eval<T>(_ expression: String, type: T.Type) -> T {
```
This takes a Swift expression as a String and returns an entity of the type specified.
There is also a shorthand function for expressions of type String which accepts the
contents of the String literal as it's argument:
```Swift
public func swiftEvalString(contents: String) -> String {
return eval("\"" + expression + "\"", String.self)
}
```
An example of how it is used can be found in the EvalApp example.
```Swift
@IBAction func performEval(_: Any) {
textView.string = swiftEvalString(contents: textField.stringValue)
}
@IBAction func closureEval(_: Any) {
_ = swiftEval(code: closureText.stringValue+"()")
}
```
The code works by adding an extension to your class source containing the expression.
It then compiles and loads this new version of the class "swizzling" this extension onto
the original class. The expression can refer to instance members in the class containing
the eval class and global variables & functions in other class sources.
### Acknowledgements:
This project includes code from [rentzsch/mach_inject](https://github.com/rentzsch/mach_inject),
[erwanb/MachInjectSample](https://github.com/erwanb/MachInjectSample),
[davedelong/DDHotKey](https://github.com/davedelong/DDHotKey) and
[acj/TimeLapseBuilder-Swift](https://github.com/acj/TimeLapseBuilder-Swift) under their
respective licenses.
The App Tracing functionality uses the [OliverLetterer/imp_implementationForwardingToSelector](https://github.com/OliverLetterer/imp_implementationForwardingToSelector) trampoline implementation via the [SwiftTrace](https://github.com/johnno1962/SwiftTrace) project under an MIT license.
SwiftTrace uses the very handy [https://github.com/facebook/fishhook](https://github.com/facebook/fishhook).
See the project source and header file included in the app bundle
for licensing details.
This release includes a very slightly modified version of the excellent
[canviz](https://code.google.com/p/canviz/) library to render "dot" files
in an HTML canvas which is subject to an MIT license. The changes are to pass
through the ID of the node to the node label tag (line 212), to reverse
the rendering of nodes and the lines linking them (line 406) and to
store edge paths so they can be coloured (line 66 and 303) in "canviz-0.1/canviz.js".
It also includes [CodeMirror](http://codemirror.net/) JavaScript editor
for the code to be evaluated using injection under an MIT license.
$Date: 2021/02/18 $
================================================
FILE: Contents/Resources/SwiftTrace.h
================================================
//
// SwiftTrace.h
// SwiftTrace
//
// Created by John Holdsworth on 10/06/2016.
// Copyright © 2016 John Holdsworth. All rights reserved.
//
// Repo: https://github.com/johnno1962/SwiftTrace
// $Id: //depot/SwiftTrace/SwiftTraceGuts/include/SwiftTrace.h#45 $
//
#ifndef SWIFTTRACE_H
#define SWIFTTRACE_H
#import <Foundation/Foundation.h>
//! Project version number for SwiftTrace.
FOUNDATION_EXPORT double SwiftTraceVersionNumber;
//! Project version string for SwiftTrace.
FOUNDATION_EXPORT const unsigned char SwiftTraceVersionString[];
// In this header, you should import all the public headers of your framework using statements like #import <SwiftTrace/PublicHeader.h>
/**
Objective-C inteface to SwftTrace as a category on NSObject
as a summary of the functionality available. Intended to be
used from Swift where SwifTrace has been provided from a
dynamically loaded bundle, for example, from InjectionIII.
Each trace superceeds any previous traces when they where
not explicit about the class or instance being traced
(see swiftTraceIntances and swiftTraceInstance). For
example, the following code:
UIView.swiftTraceBundle()
UITouch.traceInstances(withSubLevels: 3)
Will put a trace on all of the UIKit frameowrk which is then
refined by the specific trace for only instances of class
UITouch to be printed and any calls to UIKit made by those
methods up to three levels deep.
*/
@interface NSObject(SwiftTrace)
/**
The default regexp used to exclude certain methods from tracing.
*/
+ (NSString * _Nonnull)swiftTraceDefaultMethodExclusions;
/**
Optional filter of methods to be included in subsequent traces.
*/
@property (nonatomic, class, copy) NSString *_Nullable swiftTraceMethodInclusionPattern;
/**
Provide a regular expression to exclude methods.
*/
@property (nonatomic, class, copy) NSString *_Nullable swiftTraceMethodExclusionPattern;
/**
Real time control over methods to be traced (regular expressions)
*/
@property (nonatomic, class, copy) NSString *_Nullable swiftTraceFilterInclude;
@property (nonatomic, class, copy) NSString *_Nullable swiftTraceFilterExclude;
/**
Function type suffixes at end of mangled symbol name.
*/
@property (nonatomic, class, copy) NSArray<NSString *> * _Nonnull swiftTraceFunctionSuffixes;
/** Are we tracing? */
@property (readonly, class) BOOL swiftTracing;
/** Pointer to common interposed state dictionary */
@property (readonly, class) void * _Nonnull swiftTraceInterposed;
/** lookup unknown types */
@property (class) BOOL swiftTraceTypeLookup;
/**
Class will be traced (as opposed to swiftTraceInstances which
will trace methods declared in super classes as well and only
for instances of that particular class not any subclasses.)
*/
+ (void)swiftTrace;
/**
Trace all methods defined in classes contained in the main
executable of the application.
*/
+ (void)swiftTraceMainBundle;
/**
Trace all methods of classes in the main bundle but also
up to subLevels of calls made by those methods if a more
general trace has already been placed on them.
*/
+ (void)swiftTraceMainBundleWithSubLevels:(int)subLevels;
/**
Add a trace to all methods of all classes defined in the
bundle or framework that contains the receiving class.
*/
+ (void)swiftTraceBundle;
/**
Add a trace to all methods of all classes defined in the
all frameworks in the app bundle.
*/
+ (void)swiftTraceFrameworkMethods;
/**
Output a trace of methods defined in the bundle containing
the reciever and up to subLevels of calls made by them.
*/
+ (void)swiftTraceBundleWithSubLevels:(int)subLevels;
/**
Trace classes in the application that have names matching
the regular expression.
*/
+ (void)swiftTraceClassesMatchingPattern:(NSString * _Nonnull)pattern;
/**
Trace classes in the application that have names matching
the regular expression and subLevels of cals they make to
classes that have already been traced.
*/
+ (void)swiftTraceClassesMatchingPattern:(NSString * _Nonnull)pattern subLevels:(intptr_t)subLevels;
/**
Return an array of the demangled names of methods declared
in the reciving Swift class that can be traced.
*/
+ (NSArray<NSString *> * _Nonnull)swiftTraceMethodNames;
/**
Return an array of the demangled names of methods declared
in the Swift class provided.
*/
+ (NSArray<NSString *> * _Nonnull)switTraceMethodsNamesOfClass:(Class _Nonnull)aClass;
/**
Trace instances of the specific receiving class (including
the methods of its superclasses.)
*/
+ (void)swiftTraceInstances;
/**
Trace instances of the specific receiving class (including
the methods of its superclasses and subLevels of previously
traced methods called by those methods.)
*/
+ (void)swiftTraceInstancesWithSubLevels:(int)subLevels;
/**
Trace a methods (including those of all superclasses) for
a particular instance only.
*/
- (void)swiftTraceInstance;
/**
Trace methods including those of all superclasses for a
particular instance only and subLevels of calls they make.
*/
- (void)swiftTraceInstanceWithSubLevels:(int)subLevels;
/**
Trace all protocols contained in the bundle declaring the receiver class
*/
+ (void)swiftTraceProtocolsInBundle;
/**
Trace protocols in bundle with qualifications
*/
+ (void)swiftTraceProtocolsInBundleWithMatchingPattern:(NSString * _Nullable)pattern;
+ (void)swiftTraceProtocolsInBundleWithSubLevels:(int)subLevels;
+ (void)swiftTraceProtocolsInBundleWithMatchingPattern:(NSString * _Nullable)pattern subLevels:(int)subLevels;
/**
Use interposing to trace all methods in main bundle
Use swiftTraceInclusionPattern, swiftTraceExclusionPattern to filter
*/
+ (void)swiftTraceMethodsInFrameworkContaining:(Class _Nonnull)aClass;
+ (void)swiftTraceMainBundleMethods;
+ (void)swiftTraceMethodsInBundle:(const char * _Nonnull)bundlePath
packageName:(NSString * _Nullable)packageName;
+ (void)swiftTraceBundlePath:(const char * _Nonnull)bundlePath;
/**
Remove most recent trace
*/
+ (BOOL)swiftTraceUndoLastTrace;
/**
Remove all tracing swizles.
*/
+ (void)swiftTraceRemoveAllTraces;
/**
Remove all interposes from tracing.
*/
+ (void)swiftTraceRevertAllInterposes;
/**
Total elapsed time by traced method.
*/
+ (NSDictionary<NSString *, NSNumber *> * _Nonnull)swiftTraceElapsedTimes;
/**
Invocation counts by traced method.
*/
+ (NSDictionary<NSString *, NSNumber *> * _Nonnull)swiftTraceInvocationCounts;
@end
#import <mach-o/loader.h>
#import <objc/runtime.h>
#import <dlfcn.h>
#ifdef __cplusplus
extern "C" {
#endif
IMP _Nonnull imp_implementationForwardingToTracer(void * _Nonnull patch, IMP _Nonnull onEntry, IMP _Nonnull onExit);
NSArray<Class> * _Nonnull objc_classArray(void);
NSMethodSignature * _Nullable method_getSignature(Method _Nonnull Method);
const char * _Nonnull sig_argumentType(id _Nonnull signature, NSUInteger index);
const char * _Nonnull sig_returnType(id _Nonnull signature);
const char * _Nonnull classesIncludingObjc();
void findSwiftSymbols(const char * _Nullable path, const char * _Nonnull suffix, void (^ _Nonnull callback)(const void * _Nonnull address, const char * _Nonnull symname, void * _Nonnull typeref, void * _Nonnull typeend));
void appBundleImages(void (^ _Nonnull callback)(const char * _Nonnull imageName, const struct mach_header * _Nonnull header, intptr_t slide));
const char * _Nullable swiftUIBundlePath();
const char * _Nullable callerBundle(void);
int fast_dladdr(const void * _Nonnull, Dl_info * _Nonnull);
#ifdef __cplusplus
}
#endif
struct dyld_interpose_tuple {
const void * _Nonnull replacement;
const void * _Nonnull replacee;
};
#ifdef __IPHONE_OS_VERSION_MIN_REQUIRED
#import <CoreGraphics/CGGeometry.h>
#define OSRect CGRect
#define OSMakeRect CGRectMake
#else
#define OSRect NSRect
#define OSMakeRect NSMakeRect
#endif
@interface ObjcTraceTester: NSObject
- (OSRect)a:(float)a i:(int)i b:(double)b c:(NSString *_Nullable)c o:o s:(SEL _Nullable)s;
@end
#endif
// Copy paste of fishhook.h follows...
// ===================================
// Copyright (c) 2013, Facebook, Inc.
// All rights reserved.
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
// * Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
// * Neither the name Facebook nor the names of its contributors may be used to
// endorse or promote products derived from this software without specific
// prior written permission.
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#ifndef fishhook_h
#define fishhook_h
#include <stddef.h>
#include <stdint.h>
#if !defined(FISHHOOK_EXPORT)
#define FISHHOOK_VISIBILITY __attribute__((visibility("hidden")))
#else
#define FISHHOOK_VISIBILITY __attribute__((visibility("default")))
#endif
#ifdef __cplusplus
extern "C" {
#endif //__cplusplus
/*
* A structure representing a particular intended rebinding from a symbol
* name to its replacement
*/
struct rebinding {
const char * _Nonnull name;
void * _Nonnull replacement;
void * _Nonnull * _Nullable replaced;
};
/*
* For each rebinding in rebindings, rebinds references to external, indirect
* symbols with the specified name to instead point at replacement for each
* image in the calling process as well as for all future images that are loaded
* by the process. If rebind_functions is called more than once, the symbols to
* rebind are added to the existing list of rebindings, and if a given symbol
* is rebound more than once, the later rebinding will take precedence.
*/
FISHHOOK_VISIBILITY
int rebind_symbols(struct rebinding rebindings[_Nonnull], size_t rebindings_nel);
/*
* Rebinds as above, but only in the specified image. The header should point
* to the mach-o header, the slide should be the slide offset. Others as above.
*/
FISHHOOK_VISIBILITY
int rebind_symbols_image(void * _Nonnull header,
intptr_t slide,
struct rebinding rebindings[_Nonnull],
size_t rebindings_nel);
#ifdef __cplusplus
}
#endif //__cplusplus
#endif //fishhook_h
================================================
FILE: Contents/Resources/fishhook.h
================================================
// Copyright (c) 2013, Facebook, Inc.
// All rights reserved.
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
// * Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
// * Neither the name Facebook nor the names of its contributors may be used to
// endorse or promote products derived from this software without specific
// prior written permission.
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#ifndef fishhook_h
#define fishhook_h
#include <stddef.h>
#include <stdint.h>
#if !defined(FISHHOOK_EXPORT)
#define FISHHOOK_VISIBILITY __attribute__((visibility("hidden")))
#else
#define FISHHOOK_VISIBILITY __attribute__((visibility("default")))
#endif
#ifdef __cplusplus
extern "C" {
#endif //__cplusplus
/*
* A structure representing a particular intended rebinding from a symbol
* name to its replacement
*/
struct rebinding {
const char *name;
void *replacement;
void **replaced;
};
/*
* For each rebinding in rebindings, rebinds references to external, indirect
* symbols with the specified name to instead point at replacement for each
* image in the calling process as well as for all future images that are loaded
* by the process. If rebind_functions is called more than once, the symbols to
* rebind are added to the existing list of rebindings, and if a given symbol
* is rebound more than once, the later rebinding will take precedence.
*/
FISHHOOK_VISIBILITY
int rebind_symbols(struct rebinding rebindings[], size_t rebindings_nel);
/*
* Rebinds as above, but only in the specified image. The header should point
* to the mach-o header, the slide should be the slide offset. Others as above.
*/
FISHHOOK_VISIBILITY
int rebind_symbols_image(void *header,
intptr_t slide,
struct rebinding rebindings[],
size_t rebindings_nel);
#ifdef __cplusplus
}
#endif //__cplusplus
#endif //fishhook_h
================================================
FILE: Contents/Resources/graph.gv
================================================
digraph sweep {
node [href="javascript:void(click_node('\N'))" id="\N" fontname="Arial"];
0 [label="UIApplication" tooltip="<UIApplication 0x129704620> #0" color="#000000"];
1 [label="ztruct.AppDelegate" tooltip="<ztruct.AppDelegate 0x600003e78310> #1" style="filled" fillcolor="#e0e0e0" color="#000000"];
0 -> 1 [label="_delegate" color="#000000" eid="1"];
6 [label="UIMotionEvent" tooltip="<UIMotionEvent 0x600000060000> #6" color="#000000"];
7 [label="BKSAccelerometer" tooltip="<BKSAccelerometer 0x60000186c000> #7" style="filled" fillcolor="#e0e0e0" color="#000000"];
6 -> 7 [label="_motionAccelerometer" color="#000000" eid="9"];
7 -> 6 [label="_delegate" color="#000000" eid="10"];
8 [label="NSLock" tooltip="<NSLock 0x60000186c060> #8" color="#000000"];
7 -> 8 [label="_lock" color="#000000" eid="11"];
7 -> 6 [label="delegate" color="#000000" eid="12"];
14 [label="BSSimpleAssertion" tooltip="<BSSimpleAssertion 0x600003218810> #14" style="filled" fillcolor="#e0e0e0" color="#000000"];
0 -> 14 [label="_keyCommandToken" color="#000000" eid="25"];
15 [label="BSAtomicSignal" tooltip="<BSAtomicSignal 0x600003e7ca20> #15" style="filled" fillcolor="#e0e0e0" color="#000000"];
14 -> 15 [label="_invalidated" color="#000000" eid="26"];
21 [label="BSServiceConnectionEndpointMonitor" tooltip="<BSServiceConnectionEndpointMonitor 0x600001f68910> #21" style="filled" fillcolor="#e0e0e0" color="#000000"];
0 -> 21 [label="_endpointMonitor" color="#000000" eid="33"];
22 [label="BSServiceManager" tooltip="<BSServiceManager 0x6000017743f0> #22" style="filled" fillcolor="#e0e0e0" color="#000000"];
21 -> 22 [label="_manager" color="#000000" eid="34"];
23 [label="BSServicesConfiguration" tooltip="<BSServicesConfiguration 0x600003c715c0> #23" style="filled" fillcolor="#e0e0e0" color="#000000"];
22 -> 23 [label="_configuration" color="#000000" eid="35"];
24 [label="RBSService" tooltip="<RBSService 0x600003271050> #24" style="filled" fillcolor="#e0e0e0" color="#000000"];
22 -> 24 [label="_RBSService" color="#000000" eid="36"];
24 -> 22 [label="_delegate" color="#000000" eid="37"];
25 [label="RBSConnection" tooltip="<RBSConnection 0x600000e701b0> #25" style="filled" fillcolor="#e0e0e0" color="#000000"];
24 -> 25 [label="_connection" color="#000000" eid="38"];
26 [label="OS_xpc_connection" tooltip="<OS_xpc_connection 0x6000007780d0> #26" color="#000000"];
25 -> 26 [label="_connection" color="#000000" eid="39"];
27 [label="RBSProcessHandle" tooltip="<RBSProcessHandle 0x60000187f360> #27" style="filled" fillcolor="#e0e0e0" color="#000000"];
25 -> 27 [label="_handle" color="#000000" eid="40"];
28 [label="BSAuditToken" tooltip="<BSAuditToken 0x60000294b600> #28" style="filled" fillcolor="#e0e0e0" color="#000000"];
27 -> 28 [label="_bsAuditToken" color="#000000" eid="41"];
29 [label="RBSEmbeddedAppProcessIdentity" tooltip="<RBSEmbeddedAppProcessIdentity 0x600003226010> #29" style="filled" fillcolor="#e0e0e0" color="#000000"];
27 -> 29 [label="_identity" color="#000000" eid="42"];
30 [label="RBSProcessBundle" tooltip="<RBSProcessBundle 0x60000294b540> #30" style="filled" fillcolor="#e0e0e0" color="#000000"];
27 -> 30 [label="_bundle" color="#000000" eid="43"];
31 [label="RBSProcessInstance" tooltip="<RBSProcessInstance 0x600003c54e20> #31" style="filled" fillcolor="#e0e0e0" color="#000000"];
30 -> 31 [label="_instance" color="#000000" eid="44"];
31 -> 29 [label="_identity" color="#000000" eid="45"];
32 [label="RBSProcessIdentifier" tooltip="<RBSProcessIdentifier 0x600003c54da0> #32" style="filled" fillcolor="#e0e0e0" color="#000000"];
31 -> 32 [label="_identifier" color="#000000" eid="46"];
25 -> 24 [label="_serviceDelegate" color="#000000" eid="47"];
33 [label="OS_dispatch_queue_serial" tooltip="<OS_dispatch_queue_serial 0x600001271080> #33" color="#000000"];
25 -> 33 [label="_connectionQueue" color="#000000" eid="48"];
34 [label="OS_dispatch_queue_serial" tooltip="<OS_dispatch_queue_serial 0x600001271000> #34" color="#000000"];
25 -> 34 [label="_handshakeQueue" color="#000000" eid="49"];
35 [label="OS_dispatch_queue_serial" tooltip="<OS_dispatch_queue_serial 0x600001271300> #35" color="#000000"];
25 -> 35 [label="_monitorCalloutQueue" color="#000000" eid="50"];
37 [label="OS_dispatch_queue_serial" tooltip="<OS_dispatch_queue_serial 0x600001271380> #37" color="#000000"];
24 -> 37 [label="_calloutQueue" color="#000000" eid="52"];
38 [label="BSSimpleAssertion" tooltip="<BSSimpleAssertion 0x600003220c60> #38" style="filled" fillcolor="#e0e0e0" color="#000000"];
21 -> 38 [label="_registrationLock_assertion" color="#000000" eid="53"];
39 [label="BSAtomicSignal" tooltip="<BSAtomicSignal 0x600003e781b0> #39" style="filled" fillcolor="#e0e0e0" color="#000000"];
38 -> 39 [label="_invalidated" color="#000000" eid="54"];
0 -> 1 [label="delegate" color="#000000" eid="55"];
42 [label="UISplitViewControllerPanelImpl" tooltip="<UISplitViewControllerPanelImpl 0x1297131d0> #42" color="#000000"];
64 [label="ztruct.SceneDelegate" tooltip="<ztruct.SceneDelegate 0x600003c559c0> #64" style="filled" fillcolor="#e0e0e0" color="#000000"];
42 -> 64 [label="_delegate" color="#000000" eid="100"];
42 -> 64 [label="delegate" color="#000000" eid="122"];
100 [label="UIButtonLabel" tooltip="<UIButtonLabel 0x1297294d0> #100" shape=box color="#000000"];
102 [label="CUIStyleEffectConfiguration" tooltip="<CUIStyleEffectConfiguration 0x600001f45450> #102" style="filled" fillcolor="#e0e0e0" color="#000000"];
100 -> 102 [label="_cuiStyleEffectConfiguration" color="#000000" eid="173"];
117 [label="UILabel" tooltip="<UILabel 0x129614b00> #117" shape=box color="#000000"];
119 [label="CUIStyleEffectConfiguration" tooltip="<CUIStyleEffectConfiguration 0x600001f6f7f0> #119" style="filled" fillcolor="#e0e0e0" color="#000000"];
117 -> 119 [label="_cuiStyleEffectConfiguration" color="#000000" eid="206"];
130 [label="UITableView" tooltip="<UITableView 0x12a031e00> #130" shape=box color="#000000"];
131 [label="ztruct.MasterViewController" tooltip="<ztruct.MasterViewController 0x12960ed40> #131" style="filled" fillcolor="#e0e0e0" color="#000000"];
130 -> 131 [label="_dataSource" color="#000000" eid="237"];
132 [label="UIAutoRespondingScrollViewControllerKeyboardSupport" tooltip="<UIAutoRespondingScrollViewControllerKeyboardSupport 0x600003c27ea0> #132" color="#000000"];
131 -> 132 [label="_keyboardSupport" color="#000000" eid="238"];
132 -> 131 [label="_viewController" color="#000000" eid="239"];
131 -> 130 [label="_view" color="#000000" eid="240"];
133 [label="UINavigationItem" tooltip="<UINavigationItem 0x12960f0f0> #133" color="#000000"];
131 -> 133 [label="_navigationItem" color="#000000" eid="241"];
134 [label="NSBundle" tooltip="<NSBundle 0x600001f68140> #134" color="#000000"];
131 -> 134 [label="_nibBundle" color="#000000" eid="243"];
79 [label="UINavigationController" tooltip="<UINavigationController 0x129814600> #79" color="#000000"];
131 -> 79 [label="_parentViewController" color="#000000" eid="244"];
135 [label="UIStoryboard" tooltip="<UIStoryboard 0x60000187f0c0> #135" color="#000000"];
131 -> 135 [label="_storyboard" color="#000000" eid="245"];
136 [label="UIBarButtonItem" tooltip="<UIBarButtonItem 0x12971a9b0> #136" color="#000000"];
131 -> 136 [label="_editButtonItem" color="#000000" eid="247"];
136 -> 131 [label="_target" color="#000000" eid="248"];
136 -> 131 [label="_toggleEditing:" color="#000000" eid="250"];
138 [label="UITraitCollection" tooltip="<UITraitCollection 0x60000077c8f0> #138" color="#000000"];
131 -> 138 [label="_lastNotifiedTraitCollection" color="#000000" eid="251"];
139 [label="UINavigationContentAdjustments" tooltip="<UINavigationContentAdjustments 0x600003201c80> #139" color="#000000"];
131 -> 139 [label="_navigationInsetAdjustment" color="#000000" eid="252"];
130 -> 131 [label="_delegate" color="#000000" eid="262"];
130 -> 131 [label="_viewDelegate" color="#000000" eid="303"];
130 -> 131 [label="delegate" color="#000000" eid="304"];
41 [label="UISplitViewController" tooltip="<UISplitViewController 0x12960e9e0> #41" color="#000000"];
41 -> 64 [label="delegate" color="#000000" eid="354"];
189 [label="UIWindowScene" tooltip="<UIWindowScene 0x12960d260> #189" color="#000000"];
189 -> 64 [label="_delegate" color="#000000" eid="375"];
212 [label="FBSWorkspace" tooltip="<FBSWorkspace 0x600001f689b0> #212" color="#000000"];
215 [label="BSAtomicSignal" tooltip="<BSAtomicSignal 0x600003e781d0> #215" style="filled" fillcolor="#e0e0e0" color="#000000"];
212 -> 215 [label="_activateSignal" color="#000000" eid="395"];
216 [label="FBSWorkspaceFencingImpl" tooltip="<FBSWorkspaceFencingImpl 0x600002935240> #216" color="#000000"];
218 [label="BSMutableIntegerMap" tooltip="<BSMutableIntegerMap 0x600003c51e00> #218" style="filled" fillcolor="#e0e0e0" color="#000000"];
216 -> 218 [label="_triggerToFenceNameMap" color="#000000" eid="399"];
219 [label="BSMutableIntegerSet" tooltip="<BSMutableIntegerSet 0x600003c51ea0> #219" style="filled" fillcolor="#e0e0e0" color="#000000"];
216 -> 219 [label="_triggersToIgnore" color="#000000" eid="400"];
220 [label="BSServiceConnectionEndpointMonitor" tooltip="<BSServiceConnectionEndpointMonitor 0x600001f68a00> #220" style="filled" fillcolor="#e0e0e0" color="#000000"];
212 -> 220 [label="_connectionEndpointMonitor" color="#000000" eid="402"];
220 -> 22 [label="_manager" color="#000000" eid="403"];
220 -> 212 [label="_lock_delegate" color="#000000" eid="404"];
221 [label="BSSimpleAssertion" tooltip="<BSSimpleAssertion 0x600003214d20> #221" style="filled" fillcolor="#e0e0e0" color="#000000"];
220 -> 221 [label="_registrationLock_assertion" color="#000000" eid="405"];
222 [label="BSAtomicSignal" tooltip="<BSAtomicSignal 0x600003e60020> #222" style="filled" fillcolor="#e0e0e0" color="#000000"];
221 -> 222 [label="_invalidated" color="#000000" eid="406"];
220 -> 212 [label="delegate" color="#000000" eid="407"];
223 [label="BSServiceConnectionEndpoint" tooltip="<BSServiceConnectionEndpoint 0x600002920000> #223" style="filled" fillcolor="#e0e0e0" color="#000000"];
212 -> 223 [label="_defaultShellEndpoint" color="#000000" eid="408"];
224 [label="OS_xpc_endpoint" tooltip="<OS_xpc_endpoint 0x600003c44260> #224" color="#000000"];
223 -> 224 [label="_endpoint" color="#000000" eid="409"];
211 [label="FBSWorkspaceScenesClient" tooltip="<FBSWorkspaceScenesClient 0x60000177c310> #211" color="#000000"];
225 [label="BSServiceConnection" tooltip="<BSServiceConnection 0x600001f54500> #225" style="filled" fillcolor="#e0e0e0" color="#000000"];
211 -> 225 [label="_connection" color="#000000" eid="414"];
226 [label="BSXPCServiceConnection" tooltip="<BSXPCServiceConnection 0x600001278d00> #226" style="filled" fillcolor="#e0e0e0" color="#000000"];
225 -> 226 [label="_connection" color="#000000" eid="415"];
227 [label="BSXPCServiceConnectionPeer" tooltip="<BSXPCServiceConnectionPeer 0x6000032140c0> #227" style="filled" fillcolor="#e0e0e0" color="#000000"];
226 -> 227 [label="_lock_peer" color="#000000" eid="416"];
228 [label="BSProcessHandle" tooltip="<BSProcessHandle 0x6000032140f0> #228" style="filled" fillcolor="#e0e0e0" color="#000000"];
227 -> 228 [label="_processHandle" color="#000000" eid="417"];
229 [label="BSAuditToken" tooltip="<BSAuditToken 0x600002935680> #229" style="filled" fillcolor="#e0e0e0" color="#000000"];
228 -> 229 [label="_auditToken" color="#000000" eid="418"];
230 [label="BSMachPortTaskNameRight" tooltip="<BSMachPortTaskNameRight 0x600003221590> #230" style="filled" fillcolor="#e0e0e0" color="#000000"];
228 -> 230 [label="_taskNameRight" color="#000000" eid="419"];
231 [label="BSXPCServiceConnectionMessage" tooltip="<BSXPCServiceConnectionMessage 0x600001864ae0> #231" style="filled" fillcolor="#e0e0e0" color="#000000"];
226 -> 231 [label="_lock_invalidationMessage" color="#000000" eid="420"];
232 [label="OS_dispatch_queue_serial" tooltip="<OS_dispatch_queue_serial 0x600001271580> #232" color="#000000"];
231 -> 232 [label="_targetQueue" color="#000000" eid="421"];
233 [label="OS_xpc_dictionary" tooltip="<OS_xpc_dictionary 0x600001864600> #233" color="#000000"];
231 -> 233 [label="_message" color="#000000" eid="422"];
234 [label="OS_xpc_connection" tooltip="<OS_xpc_connection 0x600000070200> #234" color="#000000"];
231 -> 234 [label="_xpcConnection" color="#000000" eid="423"];
235 [label="BSXPCServiceConnectionEventHandler" tooltip="<BSXPCServiceConnectionEventHandler 0x600000e78120> #235" style="filled" fillcolor="#e0e0e0" color="#000000"];
226 -> 235 [label="_lock_eventHandler" color="#000000" eid="424"];
236 [label="BSXPCServiceConnectionProxy<FBSWorkspaceServiceClientInterface>" tooltip="<BSXPCServiceConnectionProxy<FBSWorkspaceServiceClientInterface> 0x600002931200> #236" style="filled" fillcolor="#e0e0e0" color="#000000"];
235 -> 236 [label="_lock_remoteTarget" color="#000000" eid="425"];
237 [label="BSObjCProtocol" tooltip="<BSObjCProtocol 0x60000322c870> #237" style="filled" fillcolor="#e0e0e0" color="#000000"];
236 -> 237 [label="_remoteProtocol" color="#000000" eid="426"];
238 [label="Protocol" tooltip="<Protocol 0x1cd7c4678> #238" style="filled" fillcolor="#e0e0e0" color="#000000"];
237 -> 238 [label="_protocol" color="#000000" eid="427"];
239 [label="BSObjCProtocol" tooltip="<BSObjCProtocol 0x60000322c6f0> #239" style="filled" fillcolor="#e0e0e0" color="#000000"];
236 -> 239 [label="_localProtocol" color="#000000" eid="428"];
240 [label="Protocol" tooltip="<Protocol 0x1cd7c65f8> #240" style="filled" fillcolor="#e0e0e0" color="#000000"];
239 -> 240 [label="_protocol" color="#000000" eid="429"];
236 -> 226 [label="_connection" color="#000000" eid="430"];
236 -> 234 [label="_XPCConnection" color="#000000" eid="431"];
236 -> 232 [label="_XPCConnectionTargetQueue" color="#000000" eid="432"];
235 -> 211 [label="_interfaceTarget" color="#000000" eid="433"];
241 [label="BSZeroingWeakReference" tooltip="<BSZeroingWeakReference 0x600003c5e020> #241" style="filled" fillcolor="#e0e0e0" color="#000000"];
235 -> 241 [label="_context" color="#000000" eid="434"];
241 -> 225 [label="_object" color="#000000" eid="435"];
242 [label="OS_dispatch_queue_serial" tooltip="<OS_dispatch_queue_serial 0x600001278c00> #242" color="#000000"];
235 -> 242 [label="_targetQueue" color="#000000" eid="436"];
243 [label="BSServiceQuality" tooltip="<BSServiceQuality 0x600003c71fa0> #243" style="filled" fillcolor="#e0e0e0" color="#000000"];
235 -> 243 [label="_serviceQuality" color="#000000" eid="437"];
244 [label="BSServiceInterface" tooltip="<BSServiceInterface 0x60000322dd40> #244" style="filled" fillcolor="#e0e0e0" color="#000000"];
235 -> 244 [label="_interface" color="#000000" eid="438"];
244 -> 237 [label="_server" color="#000000" eid="439"];
244 -> 239 [label="_client" color="#000000" eid="440"];
245 [label="BSXPCCoder" tooltip="<BSXPCCoder 0x600002931040> #245" style="filled" fillcolor="#e0e0e0" color="#000000"];
235 -> 245 [label="_initiatingContext" color="#000000" eid="441"];
246 [label="OS_xpc_dictionary" tooltip="<OS_xpc_dictionary 0x6000018649c0> #246" color="#000000"];
245 -> 246 [label="_message" color="#000000" eid="442"];
247 [label="BSXPCServiceConnection" tooltip="<BSXPCServiceConnection 0x600001271400> #247" style="filled" fillcolor="#e0e0e0" color="#000000"];
226 -> 247 [label="_lock_parent" color="#000000" eid="443"];
247 -> 227 [label="_lock_peer" color="#000000" eid="444"];
247 -> 234 [label="_lock_connection" color="#000000" eid="445"];
248 [label="BSXPCServiceConnectionEventHandler" tooltip="<BSXPCServiceConnectionEventHandler 0x600000e70240> #248" style="filled" fillcolor="#e0e0e0" color="#000000"];
247 -> 248 [label="_lock_eventHandler" color="#000000" eid="446"];
249 [label="BSXPCServiceConnectionProxy" tooltip="<BSXPCServiceConnectionProxy 0x600002971700> #249" style="filled" fillcolor="#e0e0e0" color="#000000"];
248 -> 249 [label="_lock_remoteTarget" color="#000000" eid="447"];
249 -> 247 [label="_connection" color="#000000" eid="448"];
249 -> 234 [label="_XPCConnection" color="#000000" eid="449"];
249 -> 232 [label="_XPCConnectionTargetQueue" color="#000000" eid="450"];
248 -> 232 [label="_targetQueue" color="#000000" eid="451"];
248 -> 243 [label="_serviceQuality" color="#000000" eid="452"];
250 [label="BSXPCServiceConnectionRootClientEndpointContext" tooltip="<BSXPCServiceConnectionRootClientEndpointContext 0x600002971600> #250" style="filled" fillcolor="#e0e0e0" color="#000000"];
247 -> 250 [label="_context" color="#000000" eid="453"];
251 [label="OS_xpc_endpoint" tooltip="<OS_xpc_endpoint 0x600003c50f80> #251" color="#000000"];
250 -> 251 [label="_endpoint" color="#000000" eid="454"];
252 [label="BSXPCServiceConnectionChildContext" tooltip="<BSXPCServiceConnectionChildContext 0x60000322cc60> #252" style="filled" fillcolor="#e0e0e0" color="#000000"];
226 -> 252 [label="_context" color="#000000" eid="455"];
252 -> 250 [label="_parent" color="#000000" eid="456"];
211 -> 223 [label="_endpoint" color="#000000" eid="457"];
253 [label="UIApplicationSceneSettings" tooltip="<UIApplicationSceneSettings 0x600001268380> #253" color="#000000"];
257 [label="BSSettings" tooltip="<BSSettings 0x600003c207c0> #257" style="filled" fillcolor="#e0e0e0" color="#000000"];
253 -> 257 [label="_otherSettings" color="#000000" eid="462"];
258 [label="BSMutableIntegerMap" tooltip="<BSMutableIntegerMap 0x600003c20bc0> #258" style="filled" fillcolor="#e0e0e0" color="#000000"];
257 -> 258 [label="_settingToFlagMap" color="#000000" eid="463"];
259 [label="BSMutableIntegerMap" tooltip="<BSMutableIntegerMap 0x600003c20ce0> #259" style="filled" fillcolor="#e0e0e0" color="#000000"];
257 -> 259 [label="_settingToObjectMap" color="#000000" eid="464"];
260 [label="BSCornerRadiusConfiguration" tooltip="<BSCornerRadiusConfiguration 0x60000322f5d0> #260" style="filled" fillcolor="#e0e0e0" color="#000000"];
259 -> 260 [label="_mapTable" color="#000000" eid="465"];
257 -> 253 [label="_descriptionProvider" color="#000000" eid="466"];
261 [label="BSSettings" tooltip="<BSSettings 0x600003c20780> #261" style="filled" fillcolor="#e0e0e0" color="#000000"];
253 -> 261 [label="_transientLocalSettings" color="#000000" eid="467"];
262 [label="UIApplicationSceneClientSettings" tooltip="<UIApplicationSceneClientSettings 0x600002937980> #262" color="#000000"];
263 [label="BSSettings" tooltip="<BSSettings 0x600003c2cf60> #263" style="filled" fillcolor="#e0e0e0" color="#000000"];
262 -> 263 [label="_otherSettings" color="#000000" eid="469"];
264 [label="BSMutableIntegerMap" tooltip="<BSMutableIntegerMap 0x600003c2cfc0> #264" style="filled" fillcolor="#e0e0e0" color="#000000"];
263 -> 264 [label="_settingToFlagMap" color="#000000" eid="470"];
265 [label="BSMutableIntegerMap" tooltip="<BSMutableIntegerMap 0x600003c2d000> #265" style="filled" fillcolor="#e0e0e0" color="#000000"];
263 -> 265 [label="_settingToObjectMap" color="#000000" eid="471"];
263 -> 262 [label="_descriptionProvider" color="#000000" eid="472"];
266 [label="FBSSceneIdentityToken" tooltip="<FBSSceneIdentityToken 0x600003c5fec0> #266" color="#000000"];
266 -> 223 [label="_endpoint" color="#000000" eid="475"];
189 -> 64 [label="delegate" color="#000000" eid="480"];
40 [label="UIWindow" tooltip="<UIWindow 0x1297137b0> #40" shape=box color="#000000"];
269 [label="BSSimpleAssertion" tooltip="<BSSimpleAssertion 0x600003227db0> #269" style="filled" fillcolor="#e0e0e0" color="#000000"];
40 -> 269 [label="_eventFocusDeferralToken" color="#000000" eid="485"];
270 [label="BSAtomicSignal" tooltip="<BSAtomicSignal 0x600003e707e0> #270" style="filled" fillcolor="#e0e0e0" color="#000000"];
269 -> 270 [label="_invalidated" color="#000000" eid="486"];
}
================================================
FILE: Contents/Resources/log.html
================================================
<html><header><style>
body, table { font: 8pt Arial; margin: 0px; border: 3px inset lightgrey; }
img.snapshot { border: 1px outset grey; }
div.complete { color: darkgreen; }
div.active { color: orange; }
@media (prefers-color-scheme: dark) {
body { background: #292A30; color: #DFDFE0; }
}
</style><script>
function $(id) {
return id ? document.getElementById(id) : $('macro');
}
function logSet( html ) {
$().innerHTML = " "+html;
}
var id = 0;
function logAdd( entry ) {
var div = document.createElement("div");
if ( entry.match( /^Ended / ) )
entry += "<p>";
div.innerHTML = " "+entry;
div.id = ++id;
$().appendChild(div);
scrollTo(0,1000000);
}
var prevID, divIDs;
function logAnimate( divID ) {
if ( !prevID )
divIDs = [];
else
$(prevID).className = "complete";
if ( divID == "" )
divID = null;
if ( divID ) {
$(divID).className = "active";
divIDs.push(divID);
}
else {
for ( var i=0 ; i<divIDs.length ; i++ )
$(divIDs[i]).className = "";
ids = null;
}
prevID = divID;
}
function logUpdate( divID, snapshotHTML ) {
$(divID).innerHTML = snapshotHTML;
}
function showSnapshot(img) {
prompt("snapshot",img.parentElement.children[2].innerText);
}
</script></header><body><div id='macro'>
This area is fully editable and can be annotated.
</div>
================================================
FILE: LICENSE
================================================
MIT License
Copyright (c) 2021 John Holdsworth
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: Package.swift
================================================
// swift-tools-version:5.2
// The swift-tools-version declares the minimum version of Swift required to build this package.
//
// Repo: https://github.com/johnno1962/HotReloading
// $Id: //depot/HotReloading/Package.swift#205 $
//
import PackageDescription
import Foundation
// This means of locating the IP address of developer's
// Mac has been replaced by a multicast implementation.
// If the multicast implementation fails to connect,
// clone the HotReloading project and hardcode the IP
// address of your Mac into the hostname value below.
// Then drag the clone onto your project to have it
// take precedence over the configured version.
var hostname = Host.current().name ?? "localhost"
// hostname = "192.168.0.243" // for example
let simulateDlopenOnDevice = false
let package = Package(
name: "HotReloading",
platforms: [.macOS("10.12"), .iOS("10.0"), .tvOS("10.0")],
products: [
.library(name: "HotReloading", targets: ["HotReloading"]),
.library(name: "HotReloadingGuts", targets: ["HotReloadingGuts"]),
.library(name: "injectiondGuts", targets: ["injectiondGuts"]),
.executable(name: "injectiond", targets: ["injectiond"]),
],
dependencies: [
.package(url: "https://github.com/johnno1962/SwiftTrace",
.upToNextMinor(from: "8.6.1")),
.package(name: "SwiftRegex",
url: "https://github.com/johnno1962/SwiftRegex5",
.upToNextMinor(from: "6.1.2")),
.package(url: "https://github.com/johnno1962/XprobePlugin",
.upToNextMinor(from: "2.9.10")),
.package(name: "RemotePlugin",
url: "https://github.com/johnno1962/Remote",
.upToNextMinor(from: "2.3.5")),
.package(url: "https://github.com/johnno1962/ProfileSwiftUI",
.upToNextMinor(from: "1.1.3")),
// .package(url: "https://github.com/johnno1962/DLKit",
// .upToNextMinor(from: "1.2.1")),
] + (simulateDlopenOnDevice ? [
.package(url: "https://github.com/johnno1962/InjectionScratch",
.upToNextMinor(from: "1.2.13"))] : []),
targets: [
.target(name: "HotReloading", dependencies: ["HotReloadingGuts",
.product(name: "SwiftTraceD", package: "SwiftTrace"),
.product(name: "Xprobe", package: "XprobePlugin"),
.product(name: "SwiftRegex", package: "SwiftRegex"),
"ProfileSwiftUI" /*, "DLKit",
*/] + (simulateDlopenOnDevice ? ["InjectionScratch"] : [])
/*, linkerSettings: [.unsafeFlags([
"-Xlinker", "-interposable", "-undefined", "dynamic_lookup"])]*/),
.target(name: "HotReloadingGuts",
cSettings: [.define("DEVELOPER_HOST", to: "\"\(hostname)\"")]),
.target(name: "injectiondGuts"),
.target(name: "injectiond", dependencies: ["HotReloadingGuts", "injectiondGuts",
.product(name: "SwiftRegex", package: "SwiftRegex"),
.product(name: "XprobeUI", package: "XprobePlugin"),
.product(name: "RemoteUI", package: "RemotePlugin")],
swiftSettings: [.define("INJECTION_III_APP")])],
cxxLanguageStandard: .cxx11
)
================================================
FILE: README.md
================================================
# Yes, HotReloading for Swift, Objective-C & C++!
Note: While this was once a way of using the InjectionIII.app on real devices
and for its development you would not normally need to use this repo any
more as you can use the pre-built bundles using the `copy_bundle.sh`
script. It has also been largely superseded by the newer and simpler
[InjectionNext](https://github.com/johnno1962/InjectionNext) project.
You should only add the HotReloading product to your main target.
This project is the [InjectionIII](https://github.com/johnno1962/InjectionIII) app
for live code updates available as a Swift Package. i.e.:

Then, you can inject function implementations without having to rebuild your app...

To try out an example project that is already set-up, clone this fork of
[SwiftUI-Kit](https://github.com/johnno1962/SwiftUI-Kit).
To use on your project, add this repo as a Swift Package and add
"Other Linker Flags": -Xlinker -interposable. You no longer need
to add a "Run Script" build phase. If want to inject on a device,
see the notes below on how to configure the InjectionIII app.
Note however, on an M1/M2 Mac this project only works with
an iOS/tvOS 14 or later simulator. Also, due to a quirk of how
Xcode how enables a DEBUG build of Swift Packages, your
"configuration" needs to contain the string "Debug".
***Remember not to release your app with this package configured.***
You should see a message that the app is watching for source file
changes in your home directory. You can change this scope by
adding comma separated list in the environment variable
`INJECTION_DIRECTORIES`. Should you want to connect to the
InjectionIII.app when using the simulator, add the environment
variable `INJECTION_DAEMON` to your scheme.
Consult the README of the [InjectionIII](https://github.com/johnno1962/InjectionIII)
project for more information in particular how to use it to inject `SwiftUI` using the
[HotSwiftUI](https://github.com/johnno1962/HotSwiftUI) protocol extension.
### HotReloading using VSCode
It's possible to use HotReloading from inside the VSCode editor and realise a
form of "VScode Previews". Consult [this project](https://github.com/markst/hotreloading-vscode-ios) for the setup required.
### Device Injection
This version of the HotReloading project and it's dependencies now support
injection on a real iOS or tvOS device.
Device injection now connects to the [InjectionIII.app](https://github.com/johnno1962/InjectionIII)
([github release](https://github.com/johnno1962/InjectionIII/releases)
4.6.0 or above) and requires you type the following commands into a Terminal
then restart the app to opt into receiving remote connections from a device:
$ rm ~/Library/Containers/com.johnholdsworth.InjectionIII/Data/Library/Preferences/com.johnholdsworth.InjectionIII.plist
$ defaults write com.johnholdsworth.InjectionIII deviceUnlock any
Note, if you've used the App Store version of InjectionIII in the past,
the binary releases have a different preferences file and the two can
get confused and prevent writing this preference from taking effect.
This is why the first `rm` command above can be necessary. If your
device doesn't connect check the app is listening on port `8899`:
```
% netstat -an | grep LIST | grep 88
tcp4 0 0 127.0.0.1.8898 *.* LISTEN
tcp4 0 0 *.8899 *.* LISTEN
```
If your device still doesn't connect either add an `INJECTION_HOST`
environment variable to your scheme containg the WiFi IP address of
the host you're running the InjectionIII.app on or clone this project and
code your mac's IP address into the `hostname` variable in Package.swift.
Then, drag the clone onto your project to have it take the place of the
configured Swift Package as outlined in [these instructions](https://developer.apple.com/documentation/xcode/editing-a-package-dependency-as-a-local-package).
Note: as the HotReloading package needs to connect a network
socket to your Mac to receive commands and new versions of code, expect
a message the first time you run your app after adding the package
asking you to "Trust" that your app should be allowed to do this.
Likewise, at the Mac end (as the InjectionIII app needs to open
a network port to accept this connection) you may be prompted for
permission if you have the macOS firewall turned on.
For `SwiftUI` you can force screen updates by following the conventions
outlined in the [HotSwiftUI](https://github.com/johnno1962/HotSwiftUI)
project then you can experience something like "Xcode Previews", except
for a fully functional app on an actual device!
### Vapor injection
To use injection with Vapor web server, it is now possible to just
download the [InjectionIII.app](https://github.com/johnno1962/InjectionIII)
and add the following line to be called as the server configures
(when running Vapor from inside Xcode):
```
#if DEBUG && os(macOS)
Bundle(path: "/Applications/InjectionIII.app/Contents/Resources/macOSInjection.bundle")?.load()
#endif
```
It will also be necessary to add the following argument to your targets:
```
linkerSettings: [.unsafeFlags(["-Xlinker", "-interposable"],
.when(platforms: [.macOS], configuration: .debug))]
```
As an alternative, you can add this Swift package as a dependency to Vapor's
Package.swift of the "App" target.
### Thanks to...
The App Tracing functionality uses the [OliverLetterer/imp_implementationForwardingToSelector](https://github.com/OliverLetterer/imp_implementationForwardingToSelector) trampoline implementation
via the [SwiftTrace](https://github.com/johnno1962/SwiftTrace) project under an MIT license.
SwiftTrace uses the very handy [https://github.com/facebook/fishhook](https://github.com/facebook/fishhook)
as an alternative to the dyld_dynamic_interpose dynamic loader private api. See the
project source and header file included in the framework for licensing details.
The ["Remote"](https://github.com/johnno1962/Remote) server in this project which
allows you to capture videos from your device includes code adapted from
[acj/TimeLapseBuilder-Swift](https://github.com/acj/TimeLapseBuilder-Swift)
This release includes a very slightly modified version of the excellent
[canviz](https://code.google.com/p/canviz/) library to render "dot" files
in an HTML canvas which is subject to an MIT license. The changes are to pass
through the ID of the node to the node label tag (line 212), to reverse
the rendering of nodes and the lines linking them (line 406) and to
store edge paths so they can be coloured (line 66 and 303) in "canviz-0.1/canviz.js".
It also includes [CodeMirror](http://codemirror.net/) JavaScript editor for
the code to be evaluated in the Xprobe browser under an MIT license.
$Date: 2025/08/03 $
================================================
FILE: Sources/HotReloading/DeviceInjection.swift
================================================
//
// DeviceInjection.swift
//
// Created by John Holdsworth on 17/03/2022.
// Copyright © 2022 John Holdsworth. All rights reserved.
//
// $Id: //depot/HotReloading/Sources/HotReloading/DeviceInjection.swift#44 $
//
// Code specific to injecting on an actual device.
//
#if DEBUG || !SWIFT_PACKAGE
#if !targetEnvironment(simulator) && SWIFT_PACKAGE && canImport(InjectionScratch)
#if SWIFT_PACKAGE
import SwiftRegex
#endif
extension SwiftInjection {
/// Emulate remaining functions of the dynamic linker.
/// - Parameter pseudoImage: last image read into memory
public class func onDeviceSpecificProcessing(
for pseudoImage: MachImage, _ sweepClasses: [AnyClass]) {
// register types, protocols, conformances...
var section_size: UInt64 = 0
for (section, regsiter) in [
("types", "swift_registerTypeMetadataRecords"),
("protos", "swift_registerProtocols"),
("proto", "swift_registerProtocolConformances")] {
if let section_start =
getsectdatafromheader_64(autoBitCast(pseudoImage),
SEG_TEXT, "__swift5_"+section, §ion_size),
section_size != 0, let call: @convention(c)
(UnsafeRawPointer, UnsafeRawPointer) -> Void =
autoBitCast(dlsym(SwiftMeta.RTLD_DEFAULT, regsiter)) {
call(section_start, section_start+Int(section_size))
}
}
// Redirect symbolic type references to main bundle
reverse_symbolics(pseudoImage)
// Initialise offsets to ivars
adjustIvarOffsets(in: pseudoImage)
// Fixup references to Objective-C classes
fixupObjcClassReferences(in: pseudoImage)
// Fix Objective-C messages to super
var supersSize: UInt64 = 0
if let injectedClass = sweepClasses.first,
let supersSection: UnsafeMutablePointer<AnyClass?> = autoBitCast(
getsectdatafromheader_64(autoBitCast(pseudoImage), SEG_DATA,
"__objc_superrefs", &supersSize)), supersSize != 0 {
supersSection[0] = injectedClass
}
// Populate "l_got.*" descriptor references
bindDescriptorReferences(in: pseudoImage)
}
struct ObjcClassMetaData {
var metaClass: AnyClass?
var metaData: UnsafeMutablePointer<ObjcClassMetaData>? {
return autoBitCast(metaClass)
}
var superClass: AnyClass?
var superData: UnsafeMutablePointer<ObjcClassMetaData>? {
return autoBitCast(superClass)
}
var methodCache: UnsafeMutableRawPointer
var bits: uintptr_t
var data: UnsafeMutablePointer<ObjcReadOnlyMetaData>?
}
public class func fillinObjcClassMetadata(in pseudoImage: MachImage) {
func getClass(_ sym: UnsafePointer<Int8>)
-> UnsafeMutablePointer<ObjcClassMetaData>? {
return autoBitCast(dlsym(SwiftMeta.RTLD_DEFAULT, sym))
}
var sectionSize: UInt64 = 0
let info = getsectdatafromheader_64(autoBitCast(pseudoImage),
SEG_DATA, "__objc_imageinfo", §ionSize)
let metaNSObject = getClass("OBJC_CLASS_$_NSObject")
let emptyCache = dlsym(SwiftMeta.RTLD_DEFAULT, "_objc_empty_cache")!
func fillin(newClass: UnsafeRawPointer, symname: UnsafePointer<Int8>) {
let metaData:
UnsafeMutablePointer<ObjcClassMetaData> = autoBitCast(newClass)
if let oldClass = getClass(symname) {
metaData.pointee.methodCache = emptyCache
metaData.pointee.superClass = oldClass.pointee.superClass
metaData.pointee.metaData?.pointee.methodCache = emptyCache
metaData.pointee.metaData?.pointee.metaClass =
metaNSObject?.pointee.metaClass
metaData.pointee.metaData?.pointee.superClass =
oldClass.pointee.metaClass // should be super of metaclass..
if registerClasses, #available(macOS 10.10, iOS 8.0, tvOS 9.0, *) {
detail("\(newClass): \(metaData.pointee) -> " +
"\((metaData.pointee.metaData ?? metaData).pointee)")
// _objc_realizeClassFromSwift(autoBitCast(aClass), oldClass)
objc_readClassPair(autoBitCast(newClass), autoBitCast(info))
} else {
// Fallback on earlier versions
}
}
}
SwiftTrace.forAllClasses(bundlePath: searchLastLoaded()) {
(aClass, stop) in
var info = Dl_info()
let address: UnsafeRawPointer = autoBitCast(aClass)
if fast_dladdr(address, &info) != 0, let symname = info.dli_sname {
fillin(newClass: address, symname: symname)
}
}
}
// Used to enumerate methods
// on an "unrealised" class.
struct ObjcMethodMetaData {
let name: UnsafePointer<CChar>
let type: UnsafePointer<CChar>
let impl: IMP
}
struct ObjcMethodListMetaData {
let flags: Int32, methodCount: Int32
var firstMethod: ObjcMethodMetaData
}
struct ObjcReadOnlyMetaData {
let skip: (Int32, Int32, Int32, Int32) = (0, 0, 0, 0)
let names: (UnsafeRawPointer?, UnsafePointer<CChar>?)
let methods: UnsafeMutablePointer<ObjcMethodListMetaData>?
}
public class func onDevice(swizzle oldClass: AnyClass,
from newClass: AnyClass) -> Int {
var swizzled = 0
let metaData: UnsafePointer<ObjcClassMetaData> = autoBitCast(newClass)
if !class_isMetaClass(oldClass), // class methods...
let metaClass = metaData.pointee.metaClass,
let metaOldClass = object_getClass(oldClass) {
swizzled += onDevice(swizzle: metaOldClass, from: metaClass)
}
let swiftBits: uintptr_t = 0x3
guard let roData: UnsafePointer<ObjcReadOnlyMetaData> =
autoBitCast(autoBitCast(metaData.pointee.data) & ~swiftBits),
let methodInfo = roData.pointee.methods else { return swizzled }
withUnsafePointer(to: &methodInfo.pointee.firstMethod) {
methods in
for i in 0 ..< Int(methodInfo.pointee.methodCount) {
let selector = sel_registerName(methods[i].name)
let method = class_getInstanceMethod(oldClass, selector)
let existing = method.flatMap { method_getImplementation($0) }
traceAndReplace(existing, replacement: autoBitCast(methods[i].impl),
objcMethod: method, objcClass: newClass) {
(replacement: IMP) -> String? in
if class_replaceMethod(oldClass, selector, replacement,
methods[i].type) != replacement {
swizzled += 1
return "Swizzled"
}
return nil
}
}
}
return swizzled
}
public class func adjustIvarOffsets(in pseudoImage: MachImage) {
var ivarOffsetPtr: UnsafeMutablePointer<ptrdiff_t>!
// Objective-C source version
pseudoImage.symbols(withPrefix: "_OBJC_IVAR_$_") {
(address, symname, suffix) in
if let classname = strdup(suffix),
var ivarname = strchr(classname, Int32(UInt8(ascii: "."))) {
ivarname[0] = 0
ivarname += 1
if let oldClass = objc_getClass(classname) as? AnyClass,
let ivar = class_getInstanceVariable(oldClass, ivarname) {
ivarOffsetPtr = autoBitCast(address)
ivarOffsetPtr.pointee = ivar_getOffset(ivar)
detail(String(cString: classname)+"." +
String(cString: ivarname) +
" offset: \(ivarOffsetPtr.pointee)")
}
free(classname)
} else {
log("⚠️ Could not parse ivar: \(String(cString: symname))")
}
}
// Swift source version
findHiddenSwiftSymbols(searchLastLoaded(), "Wvd", .any) {
(address, symname, _, _) -> Void in
if let fieldInfo = SwiftMeta.demangle(symbol: symname),
let (classname, ivarname): (String, String) =
fieldInfo[#"direct field offset for (\S+)\.\(?(\w+) "#],
let oldClass = objc_getClass(classname) as? AnyClass,
let ivar = class_getInstanceVariable(oldClass, ivarname),
get_protection(autoBitCast(address)) & VM_PROT_WRITE != 0 {
ivarOffsetPtr = autoBitCast(address)
ivarOffsetPtr.pointee = ivar_getOffset(ivar)
detail(classname+"."+ivarname +
" direct offset: \(ivarOffsetPtr.pointee)")
} else {
log("⚠️ Could not parse ivar: \(String(cString: symname))")
}
}
}
/// Fixup references to Objective-C classes on device
public class func fixupObjcClassReferences(in pseudoImage: MachImage) {
var sectionSize: UInt64 = 0
if let classNames = objcClassRefs as? [String], classNames.first != "",
let classRefsSection: UnsafeMutablePointer<AnyClass?> = autoBitCast(
getsectdatafromheader_64(autoBitCast(pseudoImage),
SEG_DATA, "__objc_classrefs", §ionSize)) {
let nClassRefs = Int(sectionSize)/MemoryLayout<AnyClass>.size
let objcClasses = classNames.compactMap {
return dlsym(SwiftMeta.RTLD_DEFAULT, "OBJC_CLASS_$_"+$0)
}
if nClassRefs == objcClasses.count {
for i in 0 ..< nClassRefs {
classRefsSection[i] = autoBitCast(objcClasses[i])
}
} else {
log("⚠️ Number of class refs \(nClassRefs) does not equal \(classNames)")
}
}
}
/// Populate "l_got.*" external references to "descriptors"
/// - Parameter pseudoImage: lastLoadedImage
public class func bindDescriptorReferences(in pseudoImage: MachImage) {
if let descriptorSyms = descriptorRefs as? [String],
descriptorSyms.first != "" {
var forces: UnsafeRawPointer?
let forcePrefix = "__swift_FORCE_LOAD_$_"
let forcePrefixLen = strlen(forcePrefix)
fast_dlscan(pseudoImage, .any, { symname in
return strncmp(symname, forcePrefix, forcePrefixLen) == 0
}) { value, symname, _, _ in
forces = value
}
if var descriptorRefs:
UnsafeMutablePointer<UnsafeMutableRawPointer?> = autoBitCast(forces) {
for descriptorSym in descriptorSyms {
descriptorRefs = descriptorRefs.advanced(by: 1)
if let value = dlsym(SwiftMeta.RTLD_DEFAULT, descriptorSym),
descriptorRefs.pointee == nil {
descriptorRefs.pointee = value
} else {
detail("⚠️ Could not bind " + describeImageSymbol(descriptorSym))
}
}
} else {
log("⚠️ Could not locate descriptors section")
}
}
}
}
#endif
#endif
================================================
FILE: Sources/HotReloading/DynamicCast.swift
================================================
//
// DynamicCast.swift
// InjectionIII
//
// Created by John Holdsworth on 02/24/2021.
// Copyright © 2021 John Holdsworth. All rights reserved.
//
// $Id: //depot/HotReloading/Sources/HotReloading/DynamicCast.swift#12 $
//
// Dynamic casting in an "as?" expression to a type that has been injected.
//
#if DEBUG || !SWIFT_PACKAGE
import Foundation
public func injection_dynamicCast(inp: UnsafeRawPointer,
out: UnsafeMutablePointer<UnsafeRawPointer>,
from: Any.Type, to: Any.Type, size: size_t) -> Bool {
let toName = _typeName(to)
// print("HERE \(inp) \(out) \(_typeName(from)) \(toName) \(size)")
let to = toName.hasPrefix("__C.") ? to :
SwiftMeta.lookupType(named: toName, protocols: true) ?? to
return DynamicCast.original_dynamicCast?(inp, out,
autoBitCast(from), autoBitCast(to), size) ?? false
}
class DynamicCast {
typealias injection_dynamicCast_t = @convention(c)
(_ inp: UnsafeRawPointer,
_ out: UnsafeMutablePointer<UnsafeRawPointer>,
_ from: UnsafeRawPointer, _ to: UnsafeRawPointer,
_ size: size_t) -> Bool
static let swift_dynamicCast = strdup("swift_dynamicCast")!
static let original_dynamicCast: injection_dynamicCast_t? =
autoBitCast(dlsym(SwiftMeta.RTLD_DEFAULT, swift_dynamicCast))
static var hooked_dynamicCast: UnsafeMutableRawPointer? = {
let module = _typeName(DynamicCast.self)
.components(separatedBy: ".")[0]
return dlsym(SwiftMeta.RTLD_DEFAULT,
"$s\(module.count)\(module)" +
"21injection_dynamicCast" +
"3inp3out4from2to4sizeSbSV_" +
"SpySVGypXpypXpSitF")
}()
static var rebinds = original_dynamicCast != nil &&
hooked_dynamicCast != nil ? [
rebinding(name: swift_dynamicCast,
replacement: hooked_dynamicCast!,
replaced: nil)] : []
static var hook_appDynamicCast: Void = {
appBundleImages { imageName, header, slide in
rebind_symbols_image(autoBitCast(header), slide,
&rebinds, rebinds.count)
}
}()
static func hook_lastInjected() {
_ = DynamicCast.hook_appDynamicCast
let lastLoaded = _dyld_image_count()-1
rebind_symbols_image(
UnsafeMutableRawPointer(mutating: lastPseudoImage() ??
_dyld_get_image_header(lastLoaded)),
lastPseudoImage() != nil ? 0 :
_dyld_get_image_vmaddr_slide(lastLoaded),
&rebinds, rebinds.count)
}
}
#endif
================================================
FILE: Sources/HotReloading/FileWatcher.swift
================================================
//
// FileWatcher.swift
// InjectionIII
//
// Created by John Holdsworth on 08/03/2015.
// Copyright (c) 2015 John Holdsworth. All rights reserved.
//
// $Id: //depot/HotReloading/Sources/HotReloading/FileWatcher.swift#49 $
//
// Started out as an abstraction to watch files under a directory.
// "Enhanced" to extract the last modified build log directory by
// backdating the event stream to just before the app launched.
//
#if DEBUG || !SWIFT_PACKAGE
#if targetEnvironment(simulator) && !APP_SANDBOXED || os(macOS)
import Foundation
public class FileWatcher: NSObject {
public typealias InjectionCallback = (_ filesChanged: NSArray, _ ideProcPath: String) -> Void
static var INJECTABLE_PATTERN = try! NSRegularExpression(
pattern: "[^~]\\.(mm?|cpp|swift|storyboard|xib)$")
static let logsPref = "HotReloadingBuildLogsDir"
static var derivedLog =
UserDefaults.standard.string(forKey: logsPref) {
didSet {
UserDefaults.standard.set(derivedLog, forKey: logsPref)
}
}
var initStream: ((FSEventStreamEventId) -> Void)!
var eventsStart =
FSEventStreamEventId(kFSEventStreamEventIdSinceNow)
#if SWIFT_PACKAGE
var eventsToBackdate: UInt64 = 10_000
#else
var eventsToBackdate: UInt64 = 50_000
#endif
var fileEvents: FSEventStreamRef! = nil
var callback: InjectionCallback
var context = FSEventStreamContext()
@objc public init(roots: [String], callback: @escaping InjectionCallback,
runLoop: CFRunLoop? = nil) {
self.callback = callback
super.init()
#if os(macOS)
context.info = unsafeBitCast(self, to: UnsafeMutableRawPointer.self)
#else
guard let FSEventStreamCreate = FSEventStreamCreate else {
fatalError("Could not locate FSEventStreamCreate")
}
#endif
initStream = { [weak self] since in
guard let self = self else { return }
let fileEvents = FSEventStreamCreate(kCFAllocatorDefault,
{ (streamRef: FSEventStreamRef,
clientCallBackInfo: UnsafeMutableRawPointer?,
numEvents: Int, eventPaths: UnsafeMutableRawPointer,
eventFlags: UnsafePointer<FSEventStreamEventFlags>,
eventIds: UnsafePointer<FSEventStreamEventId>) in
#if os(macOS)
let watcher = unsafeBitCast(clientCallBackInfo, to: FileWatcher.self)
#else
guard let watcher = watchers[streamRef] else { return }
#endif
// Check that the event flags include an item renamed flag, this helps avoid
// unnecessary injection, such as triggering injection when switching between
// files in Xcode.
for i in 0 ..< numEvents {
let flag = Int(eventFlags[i])
if (flag & (kFSEventStreamEventFlagItemRenamed | kFSEventStreamEventFlagItemModified)) != 0 {
let changes = unsafeBitCast(eventPaths, to: NSArray.self)
if CFRunLoopGetCurrent() != CFRunLoopGetMain() {
return watcher.filesChanged(changes: changes)
}
DispatchQueue.main.async {
watcher.filesChanged(changes: changes)
}
return
}
}
},
&self.context, roots as CFArray, since, 0.1,
FSEventStreamCreateFlags(kFSEventStreamCreateFlagUseCFTypes |
kFSEventStreamCreateFlagFileEvents))!
#if !os(macOS)
watchers[fileEvents] = self
#endif
FSEventStreamScheduleWithRunLoop(fileEvents, runLoop ?? CFRunLoopGetMain(),
"kCFRunLoopDefaultMode" as CFString)
_ = FSEventStreamStart(fileEvents)
self.fileEvents = fileEvents
}
initStream(eventsStart)
}
func filesChanged(changes: NSArray) {
var changed = Set<String>()
#if !INJECTION_III_APP
let eventId = FSEventStreamGetLatestEventId(fileEvents)
if eventId != kFSEventStreamEventIdSinceNow &&
eventsStart == kFSEventStreamEventIdSinceNow {
eventsStart = eventId
FSEventStreamStop(fileEvents)
initStream(max(0, eventsStart-eventsToBackdate))
return
}
#endif
for path in changes {
guard var path = path as? String else { continue }
#if !INJECTION_III_APP
if path.hasSuffix(".xcactivitylog") &&
path.contains("/Logs/Build/") {
Self.derivedLog = path
}
if eventId <= eventsStart { continue }
#endif
if Self.INJECTABLE_PATTERN.firstMatch(in: path,
range: NSMakeRange(0, path.utf16.count)) != nil &&
path.range(of: "DerivedData/|InjectionProject/|.DocumentRevisions-|@__swiftmacro_|main.mm?$",
options: .regularExpression) == nil {
if let absolute = try? URL(fileURLWithPath: path)
.resourceValues(forKeys: [.canonicalPathKey])
.canonicalPath {
path = absolute
}
changed.insert(path)
}
}
if changed.count != 0 {
var path = ""
#if os(macOS)
if let application = NSWorkspace.shared.frontmostApplication {
path = getProcPath(pid: application.processIdentifier)
}
#endif
callback(Array(changed) as NSArray, path)
}
}
#if os(macOS)
deinit {
FSEventStreamStop(fileEvents)
FSEventStreamInvalidate(fileEvents)
FSEventStreamRelease(fileEvents)
#if DEBUG
NSLog("\(self).deinit()")
#endif
}
func getProcPath(pid: pid_t) -> String {
let pathBuffer = UnsafeMutablePointer<UInt8>.allocate(capacity: Int(MAXPATHLEN))
defer {
pathBuffer.deallocate()
}
proc_pidpath(pid, pathBuffer, UInt32(MAXPATHLEN))
let path = String(cString: pathBuffer)
return path
}
#endif
}
#if !os(macOS) // Yes, this api is available inside the simulator...
typealias FSEventStreamRef = OpaquePointer
typealias ConstFSEventStreamRef = OpaquePointer
struct FSEventStreamContext {
var version: CFIndex = 0
var info: UnsafeRawPointer?
var retain: UnsafeRawPointer?
var release: UnsafeRawPointer?
var copyDescription: UnsafeRawPointer?
}
typealias FSEventStreamCreateFlags = UInt32
typealias FSEventStreamEventId = UInt64
typealias FSEventStreamEventFlags = UInt32
typealias FSEventStreamCallback = @convention(c) (ConstFSEventStreamRef, UnsafeMutableRawPointer?, Int, UnsafeMutableRawPointer, UnsafePointer<FSEventStreamEventFlags>, UnsafePointer<FSEventStreamEventId>) -> Void
#if true // avoid linker flags -undefined dynamic_lookup
let RTLD_DEFAULT = UnsafeMutableRawPointer(bitPattern: -2)
let FSEventStreamCreate = unsafeBitCast(dlsym(RTLD_DEFAULT, "FSEventStreamCreate"), to: (@convention(c) (_ allocator: CFAllocator?, _ callback: FSEventStreamCallback, _ context: UnsafeMutableRawPointer?, _ pathsToWatch: CFArray, _ sinceWhen: FSEventStreamEventId, _ latency: CFTimeInterval, _ flags: FSEventStreamCreateFlags) -> FSEventStreamRef?)?.self)
let FSEventStreamScheduleWithRunLoop = unsafeBitCast(dlsym(RTLD_DEFAULT, "FSEventStreamScheduleWithRunLoop"), to: (@convention(c) (_ streamRef: FSEventStreamRef, _ runLoop: CFRunLoop, _ runLoopMode: CFString) -> Void).self)
let FSEventStreamStart = unsafeBitCast(dlsym(RTLD_DEFAULT, "FSEventStreamStart"), to: (@convention(c) (_ streamRef: FSEventStreamRef) -> Bool).self)
let FSEventStreamGetLatestEventId = unsafeBitCast(dlsym(RTLD_DEFAULT, "FSEventStreamGetLatestEventId"), to: (@convention(c) (_ streamRef: FSEventStreamRef) -> FSEventStreamEventId).self)
let FSEventStreamStop = unsafeBitCast(dlsym(RTLD_DEFAULT, "FSEventStreamStop"), to: (@convention(c) (_ streamRef: FSEventStreamRef) -> Void).self)
#else
@_silgen_name("FSEventStreamCreate")
func FSEventStreamCreate(_ allocator: CFAllocator?, _ callback: FSEventStreamCallback, _ context: UnsafeMutablePointer<FSEventStreamContext>?, _ pathsToWatch: CFArray, _ sinceWhen: FSEventStreamEventId, _ latency: CFTimeInterval, _ flags: FSEventStreamCreateFlags) -> FSEventStreamRef?
@_silgen_name("FSEventStreamScheduleWithRunLoop")
func FSEventStreamScheduleWithRunLoop(_ streamRef: FSEventStreamRef, _ runLoop: CFRunLoop, _ runLoopMode: CFString)
@_silgen_name("FSEventStreamStart")
func FSEventStreamStart(_ streamRef: FSEventStreamRef) -> Bool
#endif
let kFSEventStreamEventIdSinceNow: UInt64 = 18446744073709551615
let kFSEventStreamCreateFlagUseCFTypes: FSEventStreamCreateFlags = 1
let kFSEventStreamCreateFlagFileEvents: FSEventStreamCreateFlags = 16
let kFSEventStreamEventFlagItemRenamed = 0x00000800
let kFSEventStreamEventFlagItemModified = 0x00001000
fileprivate var watchers = [FSEventStreamRef: FileWatcher]()
#endif
#endif
#endif
================================================
FILE: Sources/HotReloading/InjectionClient.swift
================================================
//
// InjectionClient.swift
// InjectionIII
//
// Created by John Holdsworth on 02/24/2021.
// Copyright © 2021 John Holdsworth. All rights reserved.
//
// $Id: //depot/HotReloading/Sources/HotReloading/InjectionClient.swift#91 $
//
// Client app side of HotReloading started by +load
// method in HotReloadingGuts/ClientBoot.mm
//
#if DEBUG || !SWIFT_PACKAGE
import Foundation
#if SWIFT_PACKAGE
#if canImport(InjectionScratch)
import InjectionScratch
#endif
import Xprobe
import ProfileSwiftUI
public struct HotReloading {
public static var stack: Void {
injection_stack()
}
}
#endif
#if os(macOS)
let isVapor = true
#else
let isVapor = dlsym(SwiftMeta.RTLD_DEFAULT, VAPOR_SYMBOL) != nil
#endif
@objc(InjectionClient)
public class InjectionClient: SimpleSocket, InjectionReader {
let injectionQueue = isVapor ? DispatchQueue(label: "InjectionQueue") : .main
var appVersion: String?
open func log(_ msg: String) {
print(APP_PREFIX+msg)
}
#if canImport(InjectionScratch)
func next(scratch: UnsafeMutableRawPointer) -> UnsafeMutableRawPointer {
return scratch
}
#endif
public override func runInBackground() {
let builder = SwiftInjectionEval.sharedInstance()
builder.tmpDir = NSTemporaryDirectory()
write(INJECTION_SALT)
write(INJECTION_KEY)
let frameworksPath = Bundle.main.privateFrameworksPath!
write(builder.tmpDir)
write(builder.arch)
let executable = Bundle.main.executablePath!
write(executable)
#if canImport(InjectionScratch)
var isiOSAppOnMac = false
if #available(iOS 14.0, *) {
isiOSAppOnMac = ProcessInfo.processInfo.isiOSAppOnMac
}
if !isiOSAppOnMac, let scratch = loadScratchImage(nil, 0, self, nil) {
log("⚠️ You are using device injection which is very much a work in progress. Expect the unexpected.")
writeCommand(InjectionResponse
.scratchPointer.rawValue, with: nil)
writePointer(next(scratch: scratch))
}
#endif
builder.forceUnhide = { self.writeCommand(InjectionResponse
.forceUnhide.rawValue, with: nil) }
builder.tmpDir = readString() ?? "/tmp"
builder.createUnhider(executable: executable,
SwiftInjection.objcClassRefs,
SwiftInjection.descriptorRefs)
if getenv(INJECTION_UNHIDE) != nil {
builder.legacyUnhide = true
writeCommand(InjectionResponse.legacyUnhide.rawValue, with: "1")
}
var frameworkPaths = [String: String]()
let isPlugin = builder.tmpDir == "/tmp"
if (!isPlugin) {
var frameworks = [String]()
var sysFrameworks = [String]()
for i in stride(from: _dyld_image_count()-1, through: 0, by: -1) {
guard let imageName = _dyld_get_image_name(i),
strstr(imageName, ".framework/") != nil else {
continue
}
let imagePath = String(cString: imageName)
let frameworkName = URL(fileURLWithPath: imagePath).lastPathComponent
frameworkPaths[frameworkName] = imagePath
if imagePath.hasPrefix(frameworksPath) {
frameworks.append(frameworkName)
} else {
sysFrameworks.append(frameworkName)
}
}
writeCommand(InjectionResponse.frameworkList.rawValue, with:
frameworks.joined(separator: FRAMEWORK_DELIMITER))
write(sysFrameworks.joined(separator: FRAMEWORK_DELIMITER))
write(SwiftInjection.packageNames()
.joined(separator: FRAMEWORK_DELIMITER))
}
var codesignStatusPipe = [Int32](repeating: 0, count: 2)
pipe(&codesignStatusPipe)
let reader = SimpleSocket(socket: codesignStatusPipe[0])
let writer = SimpleSocket(socket: codesignStatusPipe[1])
builder.signer = { dylib -> Bool in
self.writeCommand(InjectionResponse.getXcodeDev.rawValue,
with: builder.xcodeDev)
self.writeCommand(InjectionResponse.sign.rawValue, with: dylib)
return reader.readString() == "1"
}
SwiftTrace.swizzleFactory = SwiftTrace.LifetimeTracker.self
if let projectRoot = getenv(INJECTION_PROJECT_ROOT) {
writeCommand(InjectionResponse.projectRoot.rawValue,
with: String(cString: projectRoot))
}
if let derivedData = getenv(INJECTION_DERIVED_DATA) {
writeCommand(InjectionResponse.derivedData.rawValue,
with: String(cString: derivedData))
}
// Find client platform
#if os(macOS) || targetEnvironment(macCatalyst)
var platform = "Mac"
#elseif os(tvOS)
var platform = "AppleTV"
#elseif os(visionOS)
var platform = "XR"
#elseif os(watchOS)
var platform = "Watch"
#else
var platform = "iPhone"
#endif
#if targetEnvironment(simulator)
platform += "Simulator"
#else
platform += "OS"
#endif
#if os(macOS)
platform += "X"
#endif
writeCommand(InjectionResponse.platform.rawValue, with: platform)
commandLoop:
while true {
let commandInt = readInt()
guard let command = InjectionCommand(rawValue: commandInt) else {
log("Invalid commandInt: \(commandInt)")
break
}
switch command {
case .EOF:
log("EOF received from server..")
break commandLoop
case .signed:
writer.write(readString() ?? "0")
case .traceFramework:
let frameworkName = readString() ?? "Misssing framework"
if let frameworkPath = frameworkPaths[frameworkName] {
print("\(APP_PREFIX)Tracing %s\n", frameworkPath)
_ = SwiftTrace.interposeMethods(inBundlePath: frameworkPath,
packageName: nil)
SwiftTrace.trace(bundlePath:frameworkPath)
} else {
log("Tracing package \(frameworkName)")
let mainBundlePath = Bundle.main.executablePath ?? "Missing"
_ = SwiftTrace.interposeMethods(inBundlePath: mainBundlePath,
packageName: frameworkName)
}
filteringChanged()
default:
process(command: command, builder: builder)
}
}
builder.forceUnhide = {}
log("\(APP_NAME) disconnected.")
}
func process(command: InjectionCommand, builder: SwiftEval) {
switch command {
case .vaccineSettingChanged:
if let data = readString()?.data(using: .utf8),
let json = try? JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] {
builder.vaccineEnabled = json[UserDefaultsVaccineEnabled] as! Bool
}
case .connected:
let projectFile = readString() ?? "Missing project"
log("\(APP_NAME) connected \(projectFile)")
builder.projectFile = projectFile
builder.derivedLogs = nil;
case .watching:
log("Watching files under the directory \(readString() ?? "Missing directory")")
case .log:
log(readString() ?? "Missing log message")
case .ideProcPath:
builder.lastIdeProcPath = readString() ?? ""
case .invalid:
log("⚠️ Server has rejected your connection. Are you running InjectionIII.app or start_daemon.sh from the right directory? ⚠️")
case .quietInclude:
SwiftTrace.traceFilterInclude = readString()
case .include:
SwiftTrace.traceFilterInclude = readString()
filteringChanged()
case .exclude:
SwiftTrace.traceFilterExclude = readString()
filteringChanged()
case .feedback:
SwiftInjection.traceInjection = readString() == "1"
case .lookup:
SwiftTrace.typeLookup = readString() == "1"
if SwiftTrace.swiftTracing {
log("Discovery of target app's types switched \(SwiftTrace.typeLookup ? "on" : "off")");
}
case .trace:
if SwiftTrace.traceMainBundleMethods() == 0 {
log("⚠️ Tracing Swift methods can only work if you have -Xlinker -interposable to your project's Debug \"Other Linker Flags\"")
} else {
log("Added trace to methods in main bundle")
}
filteringChanged()
case .untrace:
SwiftTrace.removeAllTraces()
case .traceUI:
if SwiftTrace.traceMainBundleMethods() == 0 {
log("⚠️ Tracing Swift methods can only work if you have -Xlinker -interposable to your project's Debug \"Other Linker Flags\"")
}
SwiftTrace.traceMainBundle()
log("Added trace to methods in main bundle")
filteringChanged()
case .traceUIKit:
DispatchQueue.main.sync {
let OSView: AnyClass = (objc_getClass("UIView") ??
objc_getClass("NSView")) as! AnyClass
log("Adding trace to the framework containg \(OSView), this will take a while...")
SwiftTrace.traceBundle(containing: OSView)
log("Completed adding trace.")
}
filteringChanged()
case .traceSwiftUI:
if let bundleOfAnyTextStorage = swiftUIBundlePath() {
log("Adding trace to SwiftUI calls.")
_ = SwiftTrace.interposeMethods(inBundlePath: bundleOfAnyTextStorage, packageName:nil)
filteringChanged()
} else {
log("Your app doesn't seem to use SwiftUI.")
}
case .uninterpose:
SwiftTrace.revertInterposes()
SwiftTrace.removeAllTraces()
log("Removed all traces (and injections).")
break;
case .stats:
let top = 200;
print("""
\(APP_PREFIX)Sorted top \(top) elapsed time/invocations by method
\(APP_PREFIX)=================================================
""")
SwiftInjection.dumpStats(top:top)
needsTracing()
case .callOrder:
print("""
\(APP_PREFIX)Function names in the order they were first called:
\(APP_PREFIX)===================================================
""")
for signature in SwiftInjection.callOrder() {
print(signature)
}
needsTracing()
case .fileOrder:
print("""
\(APP_PREFIX)Source files in the order they were first referenced:
\(APP_PREFIX)=====================================================
\(APP_PREFIX)(Order the source files should be compiled in target)
""")
SwiftInjection.fileOrder()
needsTracing()
case .counts:
print("""
\(APP_PREFIX)Counts of live objects by class:
\(APP_PREFIX)================================
""")
SwiftInjection.objectCounts()
needsTracing()
case .fileReorder:
writeCommand(InjectionResponse.callOrderList.rawValue,
with:SwiftInjection.callOrder().joined(separator: CALLORDER_DELIMITER))
needsTracing()
case .copy:
if let data = readData() {
injectionQueue.async {
var err: String?
do {
builder.injectionNumber += 1
try data.write(to: URL(fileURLWithPath: "\(builder.tmpfile).dylib"))
try SwiftInjection.inject(tmpfile: builder.tmpfile)
} catch {
self.log("⚠️ Injection error: \(error)")
err = "\(error)"
}
let response: InjectionResponse = err != nil ? .error : .complete
self.writeCommand(response.rawValue, with: err)
}
}
case .pseudoUnlock:
#if canImport(InjectionScratch)
presentInjectionScratch(readString() ?? "")
#endif
case .objcClassRefs:
if let array = readString()?
.components(separatedBy: ",") as NSArray?,
let mutable = array.mutableCopy() as? NSMutableArray {
SwiftInjection.objcClassRefs = mutable
}
case .descriptorRefs:
if let array = readString()?
.components(separatedBy: ",") as NSArray?,
let mutable = array.mutableCopy() as? NSMutableArray {
SwiftInjection.descriptorRefs = mutable
}
case .setXcodeDev:
if let xcodeDev = readString() {
builder.xcodeDev = xcodeDev
}
case .appVersion:
appVersion = readString()
writeCommand(InjectionResponse.buildCache.rawValue,
with: builder.buildCacheFile)
case .profileUI:
DispatchQueue.main.async {
ProfileSwiftUI.profile()
}
default:
processOnMainThread(command: command, builder: builder)
}
}
func processOnMainThread(command: InjectionCommand, builder: SwiftEval) {
guard let changed = self.readString() else {
log("⚠️ Could not read changed filename?")
return
}
#if canImport(InjectionScratch)
if command == .pseudoInject,
let imagePointer = self.readPointer() {
var percent = 0.0
pushPseudoImage(changed, imagePointer)
guard let imageEnd = loadScratchImage(imagePointer,
self.readInt(), self, &percent) else { return }
DispatchQueue.main.async {
do {
builder.injectionNumber += 1
let tmpfile = String(cString: searchLastLoaded())
let newClasses = try SwiftEval.instance.extractClasses(dl: UnsafeMutableRawPointer(bitPattern: ~0)!, tmpfile: tmpfile)
try SwiftInjection.inject(tmpfile: tmpfile, newClasses: newClasses)
} catch {
NSLog("Pseudo: \(error)")
}
if percent > 75 {
print(String(format: "\(APP_PREFIX)You have used %.1f%% of InjectionScratch space.", percent))
}
self.writeCommand(InjectionResponse.scratchPointer.rawValue, with: nil)
self.writePointer(self.next(scratch: imageEnd))
}
return
}
#endif
injectionQueue.async {
var err: String?
switch command {
case .load:
do {
builder.injectionNumber += 1
try SwiftInjection.inject(tmpfile: changed)
let countKey = "__injectionsPerformed", howOften = 100
let count = UserDefaults.standard.integer(forKey: countKey)+1
UserDefaults.standard.set(count, forKey: countKey)
if count % howOften == 0 && getenv("INJECTION_SPONSOR") == nil {
SwiftInjection.log("""
ℹ️ Seems like you're using injection quite a bit. \
Have you considered sponsoring the project at \
https://github.com/johnno1962/\(APP_NAME) or \
asking your boss if they should? (This message \
prints every \(howOften) injections.)
""")
}
} catch {
err = error.localizedDescription
}
case .inject:
if changed.hasSuffix("storyboard") || changed.hasSuffix("xib") {
#if os(iOS) || os(tvOS)
if !NSObject.injectUI(changed) {
err = "Interface injection failed"
}
#else
err = "Interface injection not available on macOS."
#endif
} else {
builder.forceUnhide = { builder.startUnhide() }
SwiftInjection.inject(classNameOrFile: changed)
}
#if SWIFT_PACKAGE
case .xprobe:
Xprobe.connect(to: nil, retainObjects:true)
Xprobe.search("")
case .eval:
let parts = changed.components(separatedBy:"^")
guard let pathID = Int(parts[0]) else { break }
self.writeCommand(InjectionResponse.pause.rawValue, with:"5")
if let object = (xprobePaths[pathID] as? XprobePath)?
.object() as? NSObject, object.responds(to: Selector(("swiftEvalWithCode:"))),
let code = (parts[3] as NSString).removingPercentEncoding,
object.swiftEval(code: code) {
} else {
self.log("Xprobe: Eval only works on NSObject subclasses where the source file has the same name as the class and is in your project.")
}
Xprobe.write("$('BUSY\(pathID)').hidden = true; ")
#endif
default:
self.log("⚠️ Unimplemented command: #\(command.rawValue). " +
"Are you running the most recent versions?")
}
let response: InjectionResponse = err != nil ? .error : .complete
self.writeCommand(response.rawValue, with: err)
}
}
func needsTracing() {
if !SwiftTrace.swiftTracing {
log("⚠️ You need to have traced something to gather stats.")
}
}
func filteringChanged() {
if SwiftTrace.swiftTracing {
let exclude = SwiftTrace.traceFilterExclude
if let include = SwiftTrace.traceFilterInclude {
print(String(format: exclude != nil ?
"\(APP_PREFIX)Filtering trace to include methods matching '%@' but not '%@'." :
"\(APP_PREFIX)Filtering trace to include methods matching '%@'.",
include, exclude != nil ? exclude! : ""))
} else {
print(String(format: exclude != nil ?
"\(APP_PREFIX)Filtering trace to exclude methods matching '%@'." :
"\(APP_PREFIX)Not filtering trace (Menu Item: 'Set Filters')",
exclude != nil ? exclude! : ""))
}
}
}
}
#endif
================================================
FILE: Sources/HotReloading/InjectionStats.swift
================================================
//
// InjectionStats.swift
//
// Created by John Holdsworth on 26/10/2022.
// Copyright © 2022 John Holdsworth. All rights reserved.
//
// $Id: //depot/HotReloading/Sources/HotReloading/InjectionStats.swift#4 $
//
#if DEBUG || !SWIFT_PACKAGE
import Foundation
extension SwiftInjection {
@objc public class func dumpStats(top: Int) {
let invocationCounts = SwiftTrace.invocationCounts()
for (method, elapsed) in SwiftTrace.sortedElapsedTimes(onlyFirst: top) {
print("\(String(format: "%.1f", elapsed*1000.0))ms/\(invocationCounts[method] ?? 0)\t\(method)")
}
}
@objc public class func callOrder() -> [String] {
return SwiftTrace.callOrder().map { $0.signature }
}
@objc public class func fileOrder() {
let builder = SwiftEval.sharedInstance()
let signatures = callOrder()
guard let projectRoot = builder.projectFile.flatMap({
URL(fileURLWithPath: $0).deletingLastPathComponent().path+"/"
}),
let (_, logsDir) =
try? builder.determineEnvironment(classNameOrFile: "") else {
log("File ordering not available.")
return
}
let tmpfile = builder.tmpDir+"/eval101"
var found = false
SwiftEval.uniqueTypeNames(signatures: signatures) { typeName in
if !typeName.contains("("), let (_, foundSourceFile) =
try? builder.findCompileCommand(logsDir: logsDir,
classNameOrFile: typeName, tmpfile: tmpfile) {
print(foundSourceFile
.replacingOccurrences(of: projectRoot, with: ""))
found = true
}
}
if !found {
log("Do you have the right project selected?")
}
}
@objc public class func packageNames() -> [String] {
var packages = Set<String>()
for suffix in SwiftTrace.traceableFunctionSuffixes {
findSwiftSymbols(Bundle.main.executablePath!, suffix) {
(_, symname: UnsafePointer<Int8>, _, _) in
if let sym = SwiftMeta.demangle(symbol: String(cString: symname)),
!sym.hasPrefix("(extension in "),
let endPackage = sym.firstIndex(of: ".") {
packages.insert(sym[..<(endPackage+0)])
}
}
}
return Array(packages)
}
@objc public class func objectCounts() {
for (className, count) in SwiftTrace.liveObjects
.map({(_typeName(autoBitCast($0.key)), $0.value.count)})
.sorted(by: {$0.0 < $1.0}) {
print("\(count)\t\(className)")
}
}
}
#endif
================================================
FILE: Sources/HotReloading/ObjcInjection.swift
================================================
//
// ObjcInjection.swift
//
// Created by John Holdsworth on 17/03/2022.
// Copyright © 2022 John Holdsworth. All rights reserved.
//
// $Id: //depot/HotReloading/Sources/HotReloading/ObjcInjection.swift#23 $
//
// Code specific to "classic" Objective-C method swizzling.
//
#if DEBUG || !SWIFT_PACKAGE
import Foundation
extension SwiftInjection.MachImage {
func symbols(withPrefix: UnsafePointer<CChar>,
apply: @escaping (UnsafeRawPointer, UnsafePointer<CChar>,
UnsafePointer<CChar>) -> Void) {
let prefixLen = strlen(withPrefix)
fast_dlscan(self, .any, {
return strncmp($0, withPrefix, prefixLen) == 0}) {
(address, symname, _, _) in
apply(address, symname, symname + prefixLen - 1)
}
}
}
extension SwiftInjection {
public typealias MachImage = UnsafePointer<mach_header>
/// New method of swizzling based on symbol names
/// - Parameters:
/// - oldClass: original class to be swizzled
/// - tmpfile: no longer used
/// - Returns: # methods swizzled
public class func injection(swizzle oldClass: AnyClass, tmpfile: String) -> Int {
var methodCount: UInt32 = 0, swizzled = 0
if let methods = class_copyMethodList(oldClass, &methodCount) {
for i in 0 ..< Int(methodCount) {
swizzled += swizzle(oldClass: oldClass,
selector: method_getName(methods[i]), tmpfile)
}
free(methods)
}
return swizzled
}
/// Swizzle the newly loaded implementation of a selector onto oldClass
/// - Parameters:
/// - oldClass: orignal class to be swizzled
/// - selector: method selector to be swizzled
/// - tmpfile: no longer used
/// - Returns: # methods swizzled
public class func swizzle(oldClass: AnyClass, selector: Selector,
_ tmpfile: String) -> Int {
var swizzled = 0
if let method = class_getInstanceMethod(oldClass, selector),
let existing = unsafeBitCast(method_getImplementation(method),
to: UnsafeMutableRawPointer?.self),
let selsym = originalSym(for: existing) {
if let replacement = fast_dlsym(lastLoadedImage(), selsym) {
traceAndReplace(existing, replacement: replacement,
objcMethod: method, objcClass: oldClass) {
(replacement: IMP) -> String? in
if class_replaceMethod(oldClass, selector, replacement,
method_getTypeEncoding(method)) != nil {
swizzled += 1
return "Swizzled"
}
return nil
}
} else {
detail("⚠️ Swizzle failed "+describeImageSymbol(selsym))
}
}
return swizzled
}
/// Fallback to make sure at least the @objc func injected() and viewDidLoad() methods are swizzled
public class func swizzleBasics(oldClass: AnyClass, tmpfile: String) -> Int {
var swizzled = swizzle(oldClass: oldClass, selector: injectedSEL, tmpfile)
#if os(iOS) || os(tvOS)
swizzled += swizzle(oldClass: oldClass, selector: viewDidLoadSEL, tmpfile)
#endif
return swizzled
}
/// Original Objective-C swizzling
/// - Parameters:
/// - oldClass: Original class to be swizzle
/// - newClass: Newly loaded class
/// - Returns: # of methods swizzled
public class func injection(swizzle oldClass: AnyClass?,
from newClass: AnyClass?) -> Int {
var methodCount: UInt32 = 0, swizzled = 0
if let methods = class_copyMethodList(newClass, &methodCount) {
for i in 0 ..< Int(methodCount) {
let selector = method_getName(methods[i])
let replacement = method_getImplementation(methods[i])
guard let method = class_getInstanceMethod(oldClass, selector) ??
class_getInstanceMethod(newClass, selector),
let existing = i < 0 ? nil : method_getImplementation(method) else {
continue
}
traceAndReplace(existing, replacement: autoBitCast(replacement),
objcMethod: methods[i], objcClass: newClass) {
(replacement: IMP) -> String? in
if class_replaceMethod(oldClass, selector, replacement,
method_getTypeEncoding(methods[i])) != replacement {
swizzled += 1
return "Swizzled"
}
return nil
}
}
free(methods)
}
return swizzled
}
}
#endif
================================================
FILE: Sources/HotReloading/ReducerInjection.swift
================================================
//
// ReducerInjection.swift
//
// Created by John Holdsworth on 09/06/2022.
// Copyright © 2022 John Holdsworth. All rights reserved.
//
// $Id: //depot/HotReloading/Sources/HotReloading/ReducerInjection.swift#12 $
//
// Support for injecting "The Composble Architecture" Reducers using TCA fork:
// https://github.com/thebrowsercompany/swift-composable-architecture/tree/develop
// Top level Reducer var initialisations are wrapped in ARCInjectable() call.
// Reducers are now deprecated in favour of using the new "ReducerProtocol".
//
#if DEBUG || !SWIFT_PACKAGE
import Foundation
extension NSObject {
@objc
public func registerInjectableTCAReducer(_ symbol: String) {
SwiftInjection.injectableReducerSymbols.insert(symbol)
}
}
extension SwiftInjection {
static var injectableReducerSymbols = Set<String>()
static var checkReducerInitializers: Void = {
var expectedInjectableReducerSymbols = Set<String>()
findHiddenSwiftSymbols(searchBundleImages(), "Reducer_WZ", .any) {
_, symname, _, _ in
expectedInjectableReducerSymbols.insert(String(cString: symname))
}
for symname in expectedInjectableReducerSymbols
.subtracting(injectableReducerSymbols) {
let sym = SwiftMeta.demangle(symbol: symname) ?? symname
let variable = sym.components(separatedBy: " ").last ?? sym
log("⚠️ \(variable) is not injectable (or unused), wrap it with ARCInjectable")
}
}()
/// Support for re-initialising "The Composable Architecture", "Reducer"
/// variables declared at the top level. Requires custom version of TCA:
/// https://github.com/thebrowsercompany/swift-composable-architecture/tree/develop
public class func reinitializeInjectedReducers(_ tmpfile: String,
reinitialized: UnsafeMutablePointer<[SymbolName]>) {
_ = checkReducerInitializers
findHiddenSwiftSymbols(searchLastLoaded(), "_WZ", .local) {
accessor, symname, _, _ in
if injectableReducerSymbols.contains(String(cString: symname)) {
typealias OneTimeInitialiser = @convention(c) () -> Void
let reinitialise: OneTimeInitialiser = autoBitCast(accessor)
reinitialise()
reinitialized.pointee.append(symname)
}
}
}
}
#endif
================================================
FILE: Sources/HotReloading/StandaloneInjection.swift
================================================
//
// StandaloneInjection.swift
//
// Created by John Holdsworth on 15/03/2022.
// Copyright © 2022 John Holdsworth. All rights reserved.
//
// $Id: //depot/HotReloading/Sources/HotReloading/StandaloneInjection.swift#77 $
//
// Standalone version of the HotReloading version of the InjectionIII project
// https://github.com/johnno1962/InjectionIII. This file allows you to
// add HotReloading to a project without having to add a "Run Script"
// build phase to run the daemon process.
//
// The most recent change was for the InjectionIII.app injection bundles
// to fall back to this implementation if the user is not running the app.
// This was made possible by using the FileWatcher to find the build log
// directory in DerivedData of the most recently built project.
//
#if DEBUG || !SWIFT_PACKAGE
#if targetEnvironment(simulator) && !APP_SANDBOXED || os(macOS)
#if canImport(UIKit)
import UIKit
#endif
@objc(StandaloneInjection)
class StandaloneInjection: InjectionClient {
static var singleton: StandaloneInjection?
var watchers = [FileWatcher]()
override func runInBackground() {
let builder = SwiftInjectionEval.sharedInstance()
builder.tmpDir = NSTemporaryDirectory()
#if SWIFT_PACKAGE
let swiftTracePath = String(cString: swiftTrace_path())
// convert SwiftTrace path into path to logs.
builder.derivedLogs = swiftTracePath.replacingOccurrences(of:
#"SourcePackages/checkouts/SwiftTrace/SwiftTraceGutsD?/SwiftTrace.mm$"#,
with: "Logs/Build", options: .regularExpression)
if builder.derivedLogs == swiftTracePath {
log("⚠️ HotReloading could find log directory from: \(swiftTracePath)")
builder.derivedLogs = nil // let FileWatcher find logs
}
#endif
signal(SIGPIPE, { _ in print(APP_PREFIX+"⚠️ SIGPIPE") })
builder.signer = { _ in
#if os(tvOS)
let dylib = builder.tmpfile+".dylib"
let codesign = """
(export CODESIGN_ALLOCATE=\"\(builder.xcodeDev
)/Toolchains/XcodeDefault.xctoolchain/usr/bin/codesign_allocate\"; \
if /usr/bin/file \"\(dylib)\" | /usr/bin/grep ' shared library ' >/dev/null; \
then /usr/bin/codesign --force -s - \"\(dylib)\";\
else exit 1; fi) >>/tmp/hot_reloading.log 2>&1
"""
return builder.shell(command: codesign)
#else
return true
#endif
}
builder.debug = { (what: Any...) in
//print("\(APP_PREFIX)***** %@", what.map {"\($0)"}.joined(separator: " "))
}
builder.forceUnhide = { builder.startUnhide() }
builder.bazelLight = true
let home = NSHomeDirectory()
.replacingOccurrences(of: #"(/Users/[^/]+).*"#, with: "$1",
options: .regularExpression)
setenv("USER_HOME", home, 1)
var dirs = [home]
let library = home+"/Library"
if let extra = getenv(INJECTION_DIRECTORIES) {
dirs = String(cString: extra).components(separatedBy: ",")
.map { $0[#"^~"#, substitute: home] } // expand ~ in paths
if builder.derivedLogs == nil && dirs.allSatisfy({
$0 != home && !$0.hasPrefix(library) }) {
log("⚠️ INJECTION_DIRECTORIES should contain ~/Library")
dirs.append(library)
}
}
var lastInjected = [String: TimeInterval]()
if getenv(INJECTION_REPLAY) != nil {
injectionQueue.sync {
_ = SwiftInjection.replayInjections()
}
}
let isVapor = injectionQueue != .main
let holdOff = 1.0, minInterval = 0.33 // seconds
let firstInjected = Date.timeIntervalSinceReferenceDate + holdOff
watchers.append(FileWatcher(roots: dirs,
callback: { filesChanged, idePath in
#if canImport(UIKit) && !os(watchOS)
if UIApplication.shared.applicationState != .active { return }
#endif
builder.lastIdeProcPath = idePath
if builder.derivedLogs == nil {
if let lastBuilt = FileWatcher.derivedLog {
builder.derivedLogs = URL(fileURLWithPath: lastBuilt)
.deletingLastPathComponent().path
self.log("Using logs: \(lastBuilt).")
} else {
self.log("⚠️ Build log for project not found. " +
"Please edit a file and build it.")
return
}
}
for changed in filesChanged {
guard let changed = changed as? String,
!changed.hasPrefix(library) && !changed.contains("/."),
Date.timeIntervalSinceReferenceDate -
lastInjected[changed, default: firstInjected] >
minInterval else {
continue
}
if changed.hasSuffix(".storyboard") ||
changed.hasSuffix(".xib") {
#if os(iOS) || os(tvOS)
if !NSObject.injectUI(changed) {
self.log("⚠️ Interface injection failed")
}
#endif
} else {
SwiftInjection.inject(classNameOrFile: changed)
}
lastInjected[changed] = Date.timeIntervalSinceReferenceDate
}
}, runLoop: isVapor ? CFRunLoopGetCurrent() : nil))
log("Standalone \(APP_NAME) available for sources under \(dirs)")
if #available(iOS 14.0, tvOS 14.0, *) {
} else {
log("ℹ️ HotReloading not available on Apple Silicon before iOS 14.0")
}
if let executable = Bundle.main.executablePath {
builder.createUnhider(executable: executable,
SwiftInjection.objcClassRefs,
SwiftInjection.descriptorRefs)
}
Self.singleton = self
if isVapor {
CFRunLoopRun()
}
}
var swiftTracing: String?
func maybeTrace() {
if let pattern = getenv(INJECTION_TRACE)
.flatMap({String(cString: $0)}), pattern != swiftTracing {
SwiftTrace.typeLookup = getenv(INJECTION_LOOKUP) != nil
SwiftInjection.traceInjection = true
if pattern != "" {
// This alone will not work for non-final class methods.
_ = SwiftTrace.interpose(aBundle: searchBundleImages(),
methodName: pattern)
}
swiftTracing = pattern
}
}
}
#endif
#endif
================================================
FILE: Sources/HotReloading/SwiftEval.swift
================================================
//
// SwiftEval.swift
// InjectionBundle
//
// Created by John Holdsworth on 02/11/2017.
// Copyright © 2017 John Holdsworth. All rights reserved.
//
// $Id: //depot/HotReloading/Sources/HotReloading/SwiftEval.swift#302 $
//
// Basic implementation of a Swift "eval()" including the
// mechanics of recompiling a class and loading the new
// version used in the associated injection version.
// Used as the basis of a new version of Injection.
//
#if DEBUG || !SWIFT_PACKAGE
#if arch(x86_64) || arch(i386) || arch(arm64) // simulator/macOS only
import Foundation
#if SWIFT_PACKAGE
@_exported import HotReloadingGuts
#elseif !INJECTION_III_APP
private let APP_PREFIX = "💉 ",
INJECTION_DERIVED_DATA = "INJECTION_DERIVED_DATA"
#endif
#if !INJECTION_III_APP
#if canImport(SwiftTraceD)
import SwiftTraceD
#endif
@objc protocol SwiftEvalImpl {
@objc optional func evalImpl(_ptr: UnsafeMutableRawPointer)
}
extension NSObject {
private static var lastEvalByClass = [String: String]()
@objc public func swiftEval(code: String) -> Bool {
if let closure = swiftEval("{\n\(code)\n}", type: (() -> ())?.self) {
closure()
return true
}
return false
}
/// eval() for String value
@objc public func swiftEvalString(contents: String) -> String {
return swiftEval("""
"\(contents)"
""", type: String.self)
}
/// eval() for value of any type
public func swiftEval<T>(_ expression: String, type: T.Type) -> T {
let oldClass: AnyClass = object_getClass(self)!
let className = "\(oldClass)"
let extra = """
extension \(className) {
@objc func evalImpl(_ptr: UnsafeMutableRawPointer) {
func xprint<T>(_ str: T) {
if let xprobe = NSClassFromString("Xprobe") {
#if swift(>=4.0)
_ = (xprobe as AnyObject).perform(Selector(("xlog:")), with: "\\(str)")
#elseif swift(>=3.0)
Thread.detachNewThreadSelector(Selector(("xlog:")), toTarget:xprobe, with:"\\(str)" as NSString)
#else
NSThread.detachNewThreadSelector(Selector("xlog:"), toTarget:xprobe, withObject:"\\(str)" as NSString)
#endif
}
}
#if swift(>=3.0)
struct XprobeOutputStream: TextOutputStream {
var out = ""
mutating func write(_ string: String) {
out += string
}
}
func xdump<T>(_ arg: T) {
var stream = XprobeOutputStream()
dump(arg, to: &stream)
xprint(stream.out)
}
#endif
let _ptr = _ptr.assumingMemoryBound(to: (\(type)).self)
_ptr.pointee = \(expression)
}
}
"""
// update evalImpl to implement expression
if NSObject.lastEvalByClass[className] != expression {
do {
let tmpfile = try SwiftEval.instance.rebuildClass(oldClass: oldClass,
classNameOrFile: className, extra: extra)
if let newClass = try SwiftEval.instance
.loadAndInject(tmpfile: tmpfile, oldClass: oldClass).first {
if NSStringFromClass(newClass) != NSStringFromClass(oldClass) {
NSLog("Class names different. Have the right class been loaded?")
}
// swizzle new version of evalImpl onto class
let selector = #selector(SwiftEvalImpl.evalImpl(_ptr:))
if let newMethod = class_getInstanceMethod(newClass, selector) {
class_replaceMethod(oldClass, selector,
method_getImplementation(newMethod),
method_getTypeEncoding(newMethod))
NSObject.lastEvalByClass[className] = expression
}
}
}
catch {
}
}
// call patched evalImpl to realise expression
let ptr = UnsafeMutablePointer<T>.allocate(capacity: 1)
bzero(ptr, MemoryLayout<T>.size)
if NSObject.lastEvalByClass[className] == expression {
unsafeBitCast(self, to: SwiftEvalImpl.self).evalImpl?(_ptr: ptr)
}
let out = ptr.pointee
ptr.deallocate()
return out
}
}
#endif
extension StringProtocol {
subscript(range: NSRange) -> String? {
return Range(range, in: String(self)).flatMap { String(self[$0]) }
}
func escaping(_ chars: String = "' {}()&*",
with template: String = "\\$0") -> String {
return self.replacingOccurrences(of: "[\(chars)]",
with: template.replacingOccurrences(of: #"\"#, with: "\\\\"),
options: [.regularExpression])
}
func unescape() -> String {
return replacingOccurrences(of: #"\\(.)"#, with: "$1",
options: .regularExpression)
}
}
@objc(SwiftEval)
public class SwiftEval: NSObject {
static var instance = SwiftEval()
static let bundleLink = "/tmp/injection.link"
@objc public class func sharedInstance() -> SwiftEval {
return instance
}
@objc public var signer: ((_: String) -> Bool)?
@objc public var vaccineEnabled: Bool = false
// client specific info
@objc public var frameworks = Bundle.main.privateFrameworksPath
?? Bundle.main.bundlePath + "/Frameworks"
#if arch(arm64)
@objc public var arch = "arm64"
#elseif arch(x86_64)
@objc public var arch = "x86_64"
#else
@objc public var arch = "i386"
#endif
var forceUnhide = {}
var legacyUnhide = false
var objectUnhider: ((String) -> Void)?
var linkerOptions = ""
let bazelWorkspace = "IGNORE_WORKSPACE"
let skipBazelLinking = "--skip_linking"
var bazelLight = getenv("INJECTION_BAZEL") != nil ||
UserDefaults.standard.bool(forKey: "bazelLight")
var moduleLibraries = Set<String>()
/// Additional logging to /tmp/hot\_reloading.log for "HotReloading" version of injection.
var debug = { (what: Any...) in
if getenv("INJECTION_DEBUG") != nil || getenv("DERIVED_LOGS") != nil {
NSLog("\(APP_PREFIX)***** %@", what.map {"\($0)"}.joined(separator: " "))
}
}
// Xcode related info
@objc public var xcodeDev = "/Applications/Xcode.app/Contents/Developer" {
willSet(newValue) {
if newValue != xcodeDev {
print(APP_PREFIX+"Selecting Xcode \(newValue)")
}
}
}
@objc public var projectFile: String?
@objc public var derivedLogs: String?
@objc public var tmpDir = "/tmp" {
didSet {
// SwiftEval.buildCacheFile = "\(tmpDir)/eval_builds.plist"
}
}
@objc public var injectionNumber = 100
@objc public var lastIdeProcPath = ""
var tmpfile: String { URL(fileURLWithPath: tmpDir)
.appendingPathComponent("eval\(injectionNumber)").path }
var logfile: String { "\(tmpfile).log" }
var cmdfile: String { URL(fileURLWithPath: tmpDir)
.appendingPathComponent("command.sh").path
}
/// Error handler
@objc public var evalError = {
(_ message: String) -> Error in
print(APP_PREFIX+(message.hasPrefix("Compiling") ?"":"⚠️ ")+message)
return NSError(domain: "SwiftEval", code: -1,
userInfo: [NSLocalizedDescriptionKey: message])
}
func scriptError(_ what: String) -> Error {
var log = (try? String(contentsOfFile: logfile)) ??
"Could not read log file '\(logfile)'"
if log.contains(".h' file not found") {
log += "\(APP_PREFIX)⚠️ Adjust the \"Header Search Paths\" in your project's Build Settings"
}
return evalError("""
\(what) failed (see: \(cmdfile))
\(log)
""")
}
var compileByClass = [String: (String, String)]()
static let simulatorCacheFile = "/tmp/iOS_Simulator_builds.plist"
#if os(macOS) || targetEnvironment(macCatalyst)
var buildCacheFile = "/tmp/macOS_builds.plist"
#elseif os(tvOS)
var buildCacheFile = "/tmp/tvOS_builds.plist"
#elseif os(visionOS)
var buildCacheFile = "/tmp/xrOS_builds.plist"
#elseif targetEnvironment(simulator)
var buildCacheFile = SwiftEval.simulatorCacheFile
#else
var buildCacheFile = "/tmp/iOS_builds.plist"
#endif
lazy var longTermCache =
NSMutableDictionary(contentsOfFile: buildCacheFile) ?? NSMutableDictionary()
public func determineEnvironment(classNameOrFile: String) throws -> (URL, URL) {
// Largely obsolete section used find Xcode paths from source file being injected.
let sourceURL = URL(fileURLWithPath:
classNameOrFile.hasPrefix("/") ? classNameOrFile : #file)
debug("Project file:", projectFile ?? "nil")
guard let derivedData = getenv(INJECTION_DERIVED_DATA).flatMap({
let url = URL(fileURLWithPath: String(cString: $0))
guard FileManager.default.fileExists(atPath: url.path) else {
_ = evalError("Invalid path in \(INJECTION_DERIVED_DATA): "+url.path)
return nil }
return url }) ??
findDerivedData(url: URL(fileURLWithPath: NSHomeDirectory())) ??
(projectFile == nil ? findDerivedData(url: sourceURL) :
findDerivedData(url: URL(fileURLWithPath: projectFile!))) else {
throw evalError("""
Could not locate derived data. Is the project under your \
home directory? If you are using a custom derived data path, \
add it as an environment variable \(INJECTION_DERIVED_DATA) \
in your scheme.
""")
}
debug("DerivedData:", derivedData.path)
guard let (projectFile, logsDir) =
derivedLogs.flatMap({
(findProject(for: sourceURL, derivedData:derivedData)?
.projectFile ?? URL(fileURLWithPath: "/tmp/x.xcodeproj"),
URL(fileURLWithPath: $0)) }) ??
projectFile
.flatMap({ logsDir(project: URL(fileURLWithPath: $0), derivedData: derivedData) })
.flatMap({ (URL(fileURLWithPath: projectFile!), $0) }) ??
findProject(for: sourceURL, derivedData: derivedData) else {
throw evalError("""
Could not locate containing project or it's logs.
For a macOS app you need to turn off the App Sandbox.
Are using a custom DerivedData path? This is not supported.
""")
}
if false == (try? String(contentsOf: projectFile
.appendingPathComponent("project.pbxproj")))?.contains("-interposable") {
print(APP_PREFIX+"""
⚠️ Project file \(projectFile.path) does not contain the -interposable \
linker flag. In order to be able to inject methods of structs and final \
classes, please add \"Other Linker Flags\" -Xlinker -interposable for Debug builds only.
""")
}
return (projectFile, logsDir)
}
public func actualCase(path: String) -> String? {
let fm = FileManager.default
if fm.fileExists(atPath: path) {
return path
}
var out = ""
for component in path.split(separator: "/") {
var real: String?
if fm.fileExists(atPath: out+"/"+component) {
real = String(component)
} else {
guard let contents = try? fm.contentsOfDirectory(atPath: "/"+out) else {
return nil
}
real = contents.first { $0.lowercased() == component.lowercased() }
}
guard let found = real else {
return nil
}
out += "/" + found
}
return out
}
let detectFilepaths = try! NSRegularExpression(pattern: #"(/(?:[^\ ]*\\.)*[^\ ]*) "#)
@objc public func rebuildClass(oldClass: AnyClass?,
classNameOrFile: String, extra: String?) throws -> String {
let (projectFile, logsDir) = try
determineEnvironment(classNameOrFile: classNameOrFile)
let projectRoot = projectFile.deletingLastPathComponent().path
// if self.projectFile == nil { self.projectFile = projectFile.path }
// locate compile command for class
injectionNumber += 1
if projectFile.lastPathComponent == bazelWorkspace,
let dylib = try bazelLight(projectRoot: projectRoot,
recompile: classNameOrFile) {
return dylib
}
guard var (compileCommand, sourceFile) = try
compileByClass[classNameOrFile] ??
(longTermCache[classNameOrFile] as? String)
.flatMap({ ($0, classNameOrFile) }) ??
findCompileCommand(logsDir: logsDir,
classNameOrFile: classNameOrFile, tmpfile: tmpfile) else {
throw evalError("""
Could not locate compile command for "\(classNameOrFile)" in \
\(logsDir.path)/.\nThis could be due to one of the following:
1. Injection does not work with Whole Module Optimization.
2. There are restrictions on characters allowed in paths.
3. File paths in the simulator are case sensitive.
4. The modified source file is not in the current project.
5. The source file is an XCTest that has not been run yet.
6. Xcode has removed the build logs. Edit a file and re-run \
or try a build clean then rebuild to make logs available or \
consult: "\(cmdfile)".
7. If you're using Xcode 16.3+, Swift compilation details are no \
longer logged by default. See the note in the project README. \
You'll need to add a EMIT_FRONTEND_COMMAND_LINES custom \
build setting to your project to continue using InjectionIII.
Whatever the problem, if you see this error it may be worth \
trying the start-over implementation https://github.com/johnno1962/InjectionNext.
""")
}
sourceFile += "" // remove warning
#if targetEnvironment(simulator)
// Normalise paths in compile command with the actual casing
// of files as the simulator has a case-sensitive file system.
for filepath in detectFilepaths.matches(in: compileCommand, options: [],
range: NSMakeRange(0, compileCommand.utf16.count))
.compactMap({ compileCommand[$0.range(at: 1)] }) {
let unescaped = filepath.unescape()
if let normalised = actualCase(path: unescaped) {
let escaped = normalised.escaping("' ${}()&*~")
if filepath != escaped {
print("""
\(APP_PREFIX)Mapped: \(filepath)
\(APP_PREFIX)... to: \(escaped)
""")
compileCommand = compileCommand
.replacingOccurrences(of: filepath, with: escaped,
options: .caseInsensitive)
}
}
}
#endif
// load and patch class source if there is an extension to add
let filemgr = FileManager.default, backup = sourceFile + ".tmp"
if extra != nil {
guard var classSource = try? String(contentsOfFile: sourceFile) else {
throw evalError("Could not load source file \(sourceFile)")
}
let changesTag = "// extension added to implement eval"
classSource = classSource.components(separatedBy: "\n\(changesTag)\n")[0] + """
\(changesTag)
\(extra!)
"""
debug(classSource)
// backup original and compile patched class source
if !filemgr.fileExists(atPath: backup) {
try! filemgr.moveItem(atPath: sourceFile, toPath: backup)
}
try! classSource.write(toFile: sourceFile, atomically: true, encoding: .utf8)
}
defer {
if extra != nil {
try! filemgr.removeItem(atPath: sourceFile)
try! filemgr.moveItem(atPath: backup, toPath: sourceFile)
}
}
// Extract object path (overidden in UnhidingEval.swift for Xcode 13)
let objectFile = xcode13Fix(sourceFile: sourceFile,
compileCommand: &compileCommand)
_ = evalError("Compiling \(sourceFile)")
let isBazelCompile = compileCommand.contains(skipBazelLinking)
if isBazelCompile, arch == "x86_64", !compileCommand.contains(arch) {
compileCommand = compileCommand
.replacingOccurrences(of: #"(--cpu=ios_)\w+"#,
with: "$1\(arch)", options: .regularExpression)
}
if !sourceFile.hasSuffix(".swift") {
compileCommand += " -Xclang -fno-validate-pch"
}
debug("Final command:", compileCommand, "-->", objectFile)
guard shell(command: """
(cd "\(projectRoot.escaping("$"))" && \
\(compileCommand) >\"\(logfile)\" 2>&1)
""") || isBazelCompile else {
if longTermCache[classNameOrFile] != nil {
updateLongTermCache(remove: classNameOrFile)
do {
return try rebuildClass(oldClass: oldClass,
classNameOrFile: classNameOrFile, extra: extra)
} catch {
#if true || !os(macOS)
_ = evalError("Recompilation failing, are you renaming/adding " +
"files? Build your project to generate a new Xcode build " +
"log and try injecting again or relauch your app.")
throw error
#else
// Retry again with new build log in case of added/renamed files...
_ = evalError("Compilation failed, rebuilding \(projectFile.path)")
_ = shell(command: """
/usr/bin/osascript -e 'tell application "Xcode"
set targetProject to active workspace document
if (build targetProject) is equal to "Build succeeded" then
end if
end tell'
""")
return try rebuildClass(oldClass: oldClass,
classNameOrFile: classNameOrFile, extra: extra)
#endif
}
}
throw scriptError("Re-compilation")
}
compileByClass[classNameOrFile] = (compileCommand, sourceFile)
if longTermCache[classNameOrFile] as? String != compileCommand &&
classNameOrFile.hasPrefix("/") {//&& scanTime > slowLogScan {
longTermCache[classNameOrFile] = compileCommand
updateLongTermCache()
}
if isBazelCompile {
let projectRoot = objectFile // returned by xcode13Fix()
return try bazelLink(in: projectRoot, since: sourceFile,
compileCommand: compileCommand)
}
// link resulting object file to create dynamic library
_ = objectUnhider?(objectFile)
var speclib = ""
if sourceFile.contains("Spec.") && (try? String(
contentsOfFile: classNameOrFile))?.contains("Quick") == true {
speclib = logsDir.path+"/../../Build/Products/"+Self.quickFiles
}
try link(dylib: "\(tmpfile).dylib", compileCommand: compileCommand,
contents: "\"\(objectFile)\" \(speclib)")
return tmpfile
}
func updateLongTermCache(remove: String? = nil) {
if let source = remove {
compileByClass.removeValue(forKey: source)
longTermCache.removeObject(forKey: source)
// longTermCache.removeAllObjects()
// compileByClass.removeAll()
}
longTermCache.write(toFile: buildCacheFile,
atomically: false)
}
// Implementations provided in UnhidingEval.swift
func bazelLight(projectRoot: String, recompile sourceFile: String) throws -> String? {
throw evalError("No bazel support")
}
func bazelLink(in projectRoot: String, since sourceFile: String,
compileCommand: String) throws -> String {
throw evalError("No bazel support")
}
static let quickFiles = getenv("INJECTION_QUICK_FILES").flatMap {
String(cString: $0) } ?? "Debug-*/{Quick*,Nimble,Cwl*}.o"
static let quickDylib = "_spec.dylib"
static let dylibDelim = "==="
static let parsePlatform = try! NSRegularExpression(pattern:
#"-(?:isysroot|sdk)(?: |"\n")((\#(fileNameRegex)/Contents/Developer)/Platforms/(\w+)\.platform\#(fileNameRegex)\#\.sdk)"#)
func link(dylib: String, compileCommand: String, contents: String,
cd: String = "") throws {
var platform: String
switch buildCacheFile {
case Self.simulatorCacheFile: platform = "iPhoneSimulator"
case "/tmp/xrOS_builds.plist": platform = "XRSimulator"
case "/tmp/tvOS_builds.plist": platform = "AppleTVSimulator"
case "/tmp/macOS_builds.plist": platform = "MacOSX"
default: platform = "iPhoneOS"
}
var sdk = "\(xcodeDev)/Platforms/\(platform).platform/Developer/SDKs/\(platform).sdk"
if let match = Self.parsePlatform.firstMatch(in: compileCommand,
options: [], range: NSMakeRange(0, compileCommand.utf16.count)) {
func extract(group: Int, into: inout String) {
if let range = Range(match.range(at: group), in: compileCommand) {
into = compileCommand[range]
.replacingOccurrences(of: #"\\(.)"#, with: "$1",
options: .regularExpression)
}
}
extract(group: 1, into: &sdk)
extract(group: 2, into: &xcodeDev)
extract(group: 4, into: &platform)
} else if compileCommand.contains(".swift ") {
_ = evalError("Unable to parse SDK from: \(compileCommand)")
}
var osSpecific = ""
switch platform {
case "iPhoneSimulator":
osSpecific = "-mios-simulator-version-min=9.0"
case "iPhoneOS":
osSpecific = "-miphoneos-version-min=9.0"
case "AppleTVSimulator":
osSpecific = "-mtvos-simulator-version-min=9.0"
case "AppleTVOS":
osSpecific = "-mtvos-version-min=9.0"
case "MacOSX":
let target = compileCommand
.replacingOccurrences(of: #"^.*( -target \S+).*$"#,
with: "$1", options: .regularExpression)
osSpecific = "-mmacosx-version-min=10.11"+target
case "XRSimulator": fallthrough case "XROS":
osSpecific = ""
#if os(watchOS)
case "WatchSimulator":
osSpecific = ""
#endif
default:
_ = evalError("Invalid platform \(platform)")
// -Xlinker -bundle_loader -Xlinker \"\(Bundle.main.executablePath!)\""
}
let toolchain = xcodeDev+"/Toolchains/XcodeDefault.xctoolchain"
let cd = cd == "" ? "" : "cd \"\(cd)\" && "
if cd != "" && !contents.contains(arch) {
_ = evalError("Modified object files \(contents) not built for architecture \(arch)")
}
guard shell(command: """
\(cd)"\(toolchain)/usr/bin/clang" -arch "\(arch)" \
-Xlinker -dylib -isysroot "__PLATFORM__" \
-L"\(toolchain)/usr/lib/swift/\(platform.lowercased())" \(osSpecific) \
-undefined dynamic_lookup -dead_strip -Xlinker -objc_abi_version \
-Xlinker 2 -Xlinker -interposable\(linkerOptions) -fobjc-arc \
-fprofile-instr-generate \(contents) -L "\(frameworks)" -F "\(frameworks)" \
-rpath "\(frameworks)" -o \"\(dylib)\" >>\"\(logfile)\" 2>&1
""".replacingOccurrences(of: "__PLATFORM__", with: sdk)) else {
throw scriptError("Linking")
}
// codesign dylib
if signer != nil {
guard dylib.hasSuffix(Self.quickDylib) ||
buildCacheFile == Self.simulatorCacheFile ||
signer!("\(injectionNumber).dylib") else {
#if SWIFT_PACKAGE
throw evalError("Codesign failed. Consult /tmp/hot_reloading.log or Console.app")
#else
throw evalError("Codesign failed, consult Console. If you are using macOS 11+, Please download a new release from https://github.com/johnno1962/InjectionIII/releases")
#endif
}
}
else {
#if os(iOS)
// have to delegate code signing to macOS "signer" service
guard (try? String(contentsOf: URL(string: "http://localhost:8899\(tmpfile).dylib")!)) != nil else {
throw evalError("Codesign failed. Is 'signer' daemon running?")
}
#else
guard shell(command: """
export CODESIGN_ALLOCATE=\(xcodeDev)/Toolchains/XcodeDefault.xctoolchain/usr/bin/codesign_allocate; codesign --force -s '-' "\(tmpfile).dylib"
""") else {
throw evalError("Codesign failed")
}
#endif
}
// Rewrite dylib to prevent macOS 10.15+ from quarantining it
let url = URL(fileURLWithPath: dylib)
let dylib = try Data(contentsOf: url)
try FileManager.default.removeItem(at: url)
try dylib.write(to: url)
}
/// Regex for path argument, perhaps containg escaped spaces
static let argumentRegex = #"[^\s\\]*(?:\\.[^\s\\]*)*"#
/// Regex to extract filename base, perhaps containg escaped spaces
static let fileNameRegex = #"/(\#(argumentRegex))\.\w+"#
/// Extract full file path and name either quoted or escaped
static let filePathRegex =
#""/[^"]*\#(fileNameRegex)"|/\#(argumentRegex)"#
// Overridden in UnhidingEval.swift
func xcode13Fix(sourceFile: String,
compileCommand: inout String) -> String {
// Trim off junk at end of compile command
if sourceFile.hasSuffix(".swift") {
compileCommand = compileCommand.replacingOccurrences(
of: " -o (\(Self.filePathRegex))",
with: "", options: .regularExpression)
.components(separatedBy: " -index-system-modules")[0]
} else {
compileCommand = compileCommand
.components(separatedBy: " -o ")[0]
}
if compileCommand.contains("/bazel ") {
// force ld to fail as it is not needed
compileCommand += " --linkopt=-Wl,"+skipBazelLinking
// return path to workspace instead of object file
return compileCommand[#"^cd "([^"]+)""#] ?? "dir?"
}
let objectFile = "/tmp/injection_\(injectionNumber).o"
compileCommand += " -o "+objectFile
unlink(objectFile)
return objectFile
}
func createUnhider(executable: String, _ objcClassRefs: NSMutableArray,
_ descriptorRefs: NSMutableArray) {
}
#if !INJECTION_III_APP
lazy var loadXCTest: () = {
#if targetEnvironment(simulator)
#if os(macOS)
let sdk = "MacOSX"
#elseif os(tvOS)
let sdk = "AppleTVSimulator"
#elseif targetEnvironment(simulator)
let sdk = "iPhoneSimulator"
#else
let sdk = "iPhoneOS"
#endif
let platform = "\(xcodeDev)/Platforms/\(sdk).platform/Developer/"
guard FileManager.default.fileExists(atPath: platform) else { return }
if dlopen(platform+"Library/Frameworks/XCTest.framework/XCTest", RTLD_LAZY) == nil {
debug(String(cString: dlerror()))
}
if dlopen(platform+"usr/lib/libXCTestSwiftSupport.dylib", RTLD_LAZY) == nil {
debug(String(cString: dlerror()))
}
#else
let copiedFrameworks = Bundle.main.bundlePath+"/iOSInjection.bundle/Frameworks/"
for fw in ["XCTestCore", "XCUnit", "XCUIAutomation", "XCTest"] {
if dlopen(copiedFrameworks+fw+".framework/\(fw)", RTLD_LAZY) == nil {
debug(String(cString: dlerror()))
}
}
if dlopen(copiedFrameworks+"libXCTestSwiftSupport.dylib", RTLD_LAZY) == nil {
debug(String(cString: dlerror()))
}
#endif
}()
lazy var loadTestsBundle: () = {
do {
guard let testsBundlePath = try testsBundlePath() else {
debug("Tests bundle wasn't found - did you run the tests target before running the application?")
return
}
guard let bundle = Bundle(path: testsBundlePath), bundle.load() else {
debug("Failed loading tests bundle")
return
}
} catch {
debug("Error while searching for the tests bundle: \(error)")
return
}
}()
func testsBundlePath() throws -> String? {
guard let pluginsDirectory = Bundle.main.path(forResource: "PlugIns", ofType: nil) else {
return nil
}
let bundlePaths: [String] = try FileManager.default.contentsOfDirectory(atPath: pluginsDirectory)
.filter { $0.hasSuffix(".xctest") }
.map { directoryName in
return "\(pluginsDirectory)/\(directoryName)"
}
if bundlePaths.count > 1 {
debug("Found more than one tests bundle, using the first one")
}
return bundlePaths.first
}
@objc func loadAndInject(tmpfile: String, oldClass: AnyClass? = nil)
throws -> [AnyClass] {
print("\(APP_PREFIX)Loading .dylib ...")
// load patched .dylib into process with new version of class
var dl: UnsafeMutableRawPointer?
for dylib in "\(tmpfile).dylib".components(separatedBy: Self.dylibDelim) {
if let object = NSData(contentsOfFile: dylib),
memmem(object.bytes, object.count, "XCTest", 6) != nil ||
memmem(object.bytes, object.count, "Quick", 5) != nil,
object.count != 0 {
_ = loadXCTest
_ = loadTestsBundle
}
#if canImport(SwiftTrace) || canImport(SwiftTraceD)
dl = fast_dlopen(dylib, RTLD_NOW)
#else
dl = dlopen(dylib, RTLD_NOW)
#endif
guard dl != nil else {
var error = String(cString: dlerror())
if error.contains("___llvm_profile_runtime") {
error += """
\n\(APP_PREFIX)⚠️ Loading .dylib has failed, try turning off \
collection of test coverage in your scheme
"""
} else if error.contains("ymbol not found") {
error += """
\n\(APP_PREFIX)⚠️ Loading .dylib has failed, This is likely \
because Swift code being injected references a function \
using a default argument or a member with access control \
that is too restrictive or perhaps an XCTest that depends on \
code not normally linked into your application. Rebuilding and \
re-running your project (without a build clean) can resolve this.
"""
forceUnhide()
} else if error.contains("code signature invalid") {
error += """
\n\(APP_PREFIX)⚠️ Loading .dylib has failed due to invalid code signing.
\(APP_PREFIX)Add the following as a Run Script/Build Phase:
defaults write com.johnholdsworth.InjectionIII "$PROJECT_FILE_PATH" "$EXPANDED_CODE_SIGN_IDENTITY"
"""
} else if error.contains("rying to load an unsigned library") {
error += """
\n\(APP_PREFIX)⚠️ Loading .dylib in Xcode 15+ requires code signing.
\(APP_PREFIX)You will need to run the InjectionIII.app
"""
} else if error.contains("incompatible platform") {
error += """
\n\(APP_PREFIX)⚠️ Clean build folder when switching platform
"""
}
throw evalError("dlopen() error: \(error)")
}
}
if oldClass != nil {
// find patched version of class using symbol for existing
var info = Dl_info()
guard dladdr(unsafeBitCast(oldClass, to: UnsafeRawPointer.self), &info) != 0 else {
throw evalError("Could not locate class symbol")
}
debug(String(cString: info.dli_sname))
guard let newSymbol = dlsym(dl, info.dli_sname) else {
throw evalError("Could not locate newly loaded class symbol")
}
return [unsafeBitCast(newSymbol, to: AnyClass.self)]
}
else {
// grep out symbols for classes being injected from object file
return try extractClasses(dl: dl!, tmpfile: tmpfile)
}
}
#endif
func startUnhide() {
}
// Overridden by SwiftInjectionEval subclass for injection
@objc func extractClasses(dl: UnsafeMutableRawPointer,
tmpfile: String) throws -> [AnyClass] {
guard shell(command: """
\(xcodeDev)/Toolchains/XcodeDefault.xctoolchain/usr/bin/nm \(tmpfile).o | \
grep -E ' S _OBJC_CLASS_\\$_| _(_T0|\\$S|\\$s).*CN$' | awk '{print $3}' \
>\(tmpfile).classes
""") else {
throw evalError("Could not list class symbols")
}
guard var classSymbolNames = (try? String(contentsOfFile:
"\(tmpfile).classes"))?.components(separatedBy: "\n") else {
throw evalError("Could not load class symbol list")
}
classSymbolNames.removeLast()
return Set(classSymbolNames.compactMap {
dlsym(dl, String($0.dropFirst())) })
.map { unsafeBitCast($0, to: AnyClass.self) }
}
func findCompileCommand(logsDir: URL, classNameOrFile: String, tmpfile: String)
throws -> (compileCommand: String, sourceFile: String)? {
// path to project can contain spaces and '$&(){}
// Objective-C paths can only contain space and '
// project file itself can only contain spaces
let isFile = classNameOrFile.hasPrefix("/")
let sourceRegex = isFile ?
#"\Q\#(classNameOrFile)\E"# : #"/\#(classNameOrFile)\.\w+"#
let swiftEscaped = (isFile ? "" : #"[^"]*?"#) + sourceRegex.escaping("'$", with: #"\E\\*$0\Q"#)
let objcEscaped = (isFile ? "" :
#"(?:/(?:[^/\\]*\\.)*[^/\\ ]+)+"#) + sourceRegex.escaping()
var regexp = #" -(?:primary-file|c(?<!-frontend -c)) (?:\\?"(\#(swiftEscaped))\\?"|(\#(objcEscaped))) "#
let sourceName = URL(fileURLWithPath: classNameOrFile).lastPathComponent
.replacingOccurrences(of: "'", with: "_")
// print(regexp)
let swiftpm = projectFile?.hasSuffix(".swiftpm") == true ?
" and $line !~ / -module-name App /" : ""
#if targetEnvironment(simulator)
let actualPath = #"""
my $out = "/";
for my $name (split "/", $_[0]) {
my $next = "$out/$name";
if (! -f $next) {
opendir my $dh, $out;
while (my $entry = readdir $dh) {
if (uc $name eq uc $entry) {
$next = "$out/$entry";
last;
}
}
}
$out = $next;
}
return substr $out, 2;
"""#
#else
let actualPath = #"""
return $_[0];
"""#
#endif
#if os(watchOS)
let filterWatchOS = "=~ /-watchos/"
#else
let filterWatchOS = "!~ /-watchos/"
#endif
// messy but fast
try #"""
use JSON::PP;
use English;
use strict;
# line separator in Xcode logs
$INPUT_RECORD_SEPARATOR = "\r";
# format is gzip
open GUNZIP, "/usr/bin/gunzip <\"$ARGV[0]\" 2>/dev/null |" or die "gnozip";
sub actualPath {
\#(actualPath)
}
sub recoverFilelist {
my ($filemap) = $_[0] =~ / -output-file-map (\#(
Self.argumentRegex)) /;
$filemap =~ s/\\//g;
return if ! -s $filemap;
my $file_handle = IO::File->new( "< $filemap" )
or die "Could not open filemap '$filemap'";
my $json_text = join'', $file_handle->getlines();
return unless index($json_text, '\#(sourceName)') > 0;
my $json_map = decode_json( $json_text, { utf8 => 1 } );
my $swift_sources = join "\n", map { actualPath($_) } keys %$json_map;
mkdir "/tmp/filelists";
my $filelist = '/tmp/filelists/\#(sourceName)';
unlink $filelist;
my $listfile = IO::File->new( "> $filelist" )
or die "Could not open list file '$filelist'";
binmode $listfile, ':utf8';
$listfile->print( $swift_sources );
$listfile->close();
return $filelist;
}
# grep the log until there is a match
my ($realPath, $command, $filelist);
while (defined (my $line = <GUNZIP>)) {
if ($line =~ /^\s*cd /) {
$realPath = $line;
}
elsif ($line =~ m@\#(regexp.escaping("\"$")
.escaping("@", with: #"\E\$0\Q"#)
)@oi and $line \#(filterWatchOS)
and $line =~ "\#(arch)"\#(swiftpm)) {
# found compile command..
# may need to recover file list
my ($flarg) = $line =~ / -filelist (\#(
Self.argumentRegex))/;
if ($flarg && ! -s $flarg) {
while (defined (my $line2 = <GUNZIP>)) {
if (my ($fl) = recoverFilelist($line2)) {
$filelist = $fl;
last;
}
}
}
if ($realPath and (undef, $realPath) =
$realPath =~ /cd (\"?)(.*?)\1\r/) {
$realPath =~ s/\\([^\$])/$1/g;
$line = "cd \"$realPath\"; $line";
}
# find last
$command = $line;
#exit 0;
}
elsif (my ($bazel, $dir) = $line =~ /^Running "([^"]+)".* (?:patching output for workspace root|with project path) at ("[^"]+")/) {
$command = "cd $dir && $bazel";
last;
}
elsif (my ($identity, $bundle) = $line =~ m@/usr/bin/codesign --force --sign (\S+) --entitlements \#(Self.argumentRegex) .+ (\#(Self.argumentRegex))@) {
$bundle =~ s/\\(.)/$1/g;
unlink "\#(Self.bundleLink)";
symlink $bundle, "\#(Self.bundleLink)";
system "rm -f ~/Library/Containers/com.johnholdsworth.InjectionIII/Data/Library/Preferences/com.johnholdsworth.InjectionIII.plist"
if $identity ne "-";
system (qw(/usr/bin/env defaults write com.johnholdsworth.InjectionIII),
'\#(projectFile?.escaping("'") ?? "current project")', $identity);
}
elsif (!$filelist &&
index($line, " -output-file-map ") > 0 and
my ($fl) = recoverFilelist($line)) {
$filelist = $fl;
}
}
if ($command) {
my ($flarg) = $command =~ / -filelist (\#(
Self.argumentRegex))/;
if ($flarg && $filelist && ! -s $flarg) {
$command =~ s/( -filelist )(\#(
Self.argumentRegex)) /$1'$filelist' /;
}
print $command;
exit 0;
}
# class/file not found
exit 1;
"""#.write(toFile: "\(tmpfile).pl",
atomically: false, encoding: .utf8)
guard shell(command: """
# search through build logs, most recent first
cp \(cmdfile) \(cmdfile).save 2>/dev/null ; \
cd "\(logsDir.path.escaping("$"))" &&
for log in `ls -t *.xcactivitylog`; do
#echo "Scanning $log"
/usr/bin/env perl "\(tmpfile).pl" "$log" \
>"\(tmpfile).sh" 2>>"\(tmpfile).err" && exit 0
done
exit 1;
""") else {
#if targetEnvironment(simulator)
if #available(iOS 14.0, tvOS 14.0, *) {
} else {
print(APP_PREFIX+"""
⚠️ Injection unable to search logs. \
Try a more recent iOS 14+ simulator \
or, download a release directly from \
https://github.com/johnno1962/InjectionIII/releases
""")
}
#endif
if let log = try? String(contentsOfFile: "\(tmpfile).err"),
!log.isEmpty {
_ = evalError("stderr contains: "+log)
}
return nil
}
var compileCommand: String
do {
compileCommand = try String(contentsOfFile: "\(tmpfile).sh")
} catch {
throw evalError("""
Error reading \(tmpfile).sh, scanCommand: \(cmdfile)
""")
}
// // escape ( & ) outside quotes
// .replacingOccurrences(of: "[()](?=(?:(?:[^\"]*\"){2})*[^\"]$)", with: "\\\\$0", options: [.regularExpression])
// (logs of new build system escape ', $ and ")
debug("Found command:", compileCommand)
compileCommand = compileCommand.replacingOccurrences(of:
#"builtin-swift(DriverJob|Task)Execution --|-frontend-parseable-output|\r$"#,
with: "", options: .regularExpression)
// // remove excess escaping in new build system (no linger necessary)
// .replacingOccurrences(of: #"\\([\"'\\])"#, with: "$1", options: [.regularExpression])
// these files may no longer exist
.replacingOccurrences(of:
#" -(pch-output-dir|supplementary-output-file-map|index-store-path|Xcc -ivfsstatcache -Xcc) \#(Self.argumentRegex) "#,
with: " ", options: .regularExpression)
// Strip junk with Xcode 16.3 and EMIT_FRONTEND_COMMAND_LINES
.replacingOccurrences(of: #"^(.*?"; )?\S*?"(?=/)"#,
with: "", options: .regularExpression)
debug("Replaced command:", compileCommand)
if isFile {
return (compileCommand, classNameOrFile)
}
// for eval() extract full path to file from compile command
let fileExtractor: NSRegularExpression
regexp = regexp.escaping("$")
do {
fileExtractor = try NSRegularExpression(pattern: regexp, options: [])
}
catch {
throw evalError("Regexp parse error: \(error) -- \(regexp)")
}
guard let matches = fileExtractor
.firstMatch(in: compileCommand, options: [],
range: NSMakeRange(0, compileCommand.utf16.count)),
var sourceFile = compileCommand[matches.range(at: 1)] ??
compileCommand[matches.range(at: 2)] else {
throw evalError("Could not locate source file \(compileCommand) -- \(regexp)")
}
sourceFile = actualCase(path: sourceFile.unescape()) ?? sourceFile
return (compileCommand, sourceFile)
}
func getAppCodeDerivedData(procPath: String) -> String {
//Default with current year
let derivedDataPath = { (year: Int, pathSelector: String) -> String in
"Library/Caches/\(year > 2019 ? "JetBrains/" : "")\(pathSelector)/DerivedData"
}
let year = Calendar.current.component(.year, from: Date())
let month = Calendar.current.component(.month, from: Date())
let defaultPath = derivedDataPath(year, "AppCode\(month / 4 == 0 ? year - 1 : year).\(month / 4 + (month / 4 == 0 ? 3 : 0))")
var plistPath = URL(fileURLWithPath: procPath)
plistPath.deleteLastPathComponent()
plistPath.deleteLastPathComponent()
plistPath = plistPath.appendingPathComponent("Info.plist")
guard let dictionary = NSDictionary(contentsOf: plistPath) as? Dictionary<String, Any> else { return defaultPath }
guard let jvmOptions = dictionary["JVMOptions"] as? Dictionary<String, Any> else { return defaultPath }
guard let properties = jvmOptions["Properties"] as? Dictionary<String, Any> else { return defaultPath }
guard let pathSelector: String = properties["idea.paths.selector"] as? String else { return defaultPath }
let components = pathSelector.replacingOccurrences(of: "AppCode", with: "").components(separatedBy: ".")
guard components.count == 2 else { return defaultPath }
guard let realYear = Int(components[0]) else { return defaultPath }
return derivedDataPath(realYear, pathSelector)
}
func findDerivedData(url: URL) -> URL? {
if url.path == "/" {
return nil
}
var relativeDirs = ["DerivedData", "build/DerivedData"]
if lastIdeProcPath.lowercased().contains("appcode") {
relativeDirs.append(getAppCodeDerivedData(procPath: lastIdeProcPath))
} else {
relativeDirs.append("Library/Developer/Xcode/DerivedData")
}
for relative in relativeDirs {
let derived = url.appendingPathComponent(relative)
if FileManager.default.fileExists(atPath: derived.path) {
return derived
}
}
return findDerivedData(url: url.deletingLastPathComponent())
}
func findProject(for source: URL, derivedData: URL) -> (projectFile: URL, logsDir: URL)? {
let dir = source.deletingLastPathComponent()
if dir.path == "/" {
return nil
}
if bazelLight {
let workspaceURL = dir.deletingLastPathComponent()
.appendingPathComponent(bazelWorkspace)
if FileManager.default.fileExists(atPath: workspaceURL.path) {
return (workspaceURL, derivedLogs.flatMap({
URL(fileURLWithPath: $0)}) ?? dir)
}
}
var candidate = findProject(for: dir, derivedData: derivedData)
if let files =
try? FileManager.default.contentsOfDirectory(atPath: dir.path),
let project = Self.projects(in: files)?.first,
let logsDir = logsDir(project: dir.appendingPathComponent(project), derivedData: derivedData),
mtime(logsDir) > candidate.flatMap({ mtime($0.logsDir) }) ?? 0 {
candidate = (dir.appendingPathComponent(project), logsDir)
}
return candidate
}
class func projects(in files: [String]) -> [String]? {
return names(withSuffix: ".xcworkspace", in: files) ??
names(withSuffix: ".xcodeproj", in: files) ??
names(withSuffix: "Package.swift", in: files)
}
class func names(withSuffix ext: String, in files: [String]) -> [String]? {
let matches = files.filter { $0.hasSuffix(ext) }
return matches.count != 0 ? matches : nil
}
func mtime(_ url: URL) -> time_t {
var info = stat()
return stat(url.path, &info) == 0 ? info.st_mtimespec.tv_sec : 0
}
func logsDir(project: URL, derivedData: URL) -> URL? {
let filemgr = FileManager.default
var projectPrefix = project.deletingPathExtension().lastPathComponent
if project.lastPathComponent == "Package.swift" {
projectPrefix = project.deletingLastPathComponent().lastPathComponent
}
projectPrefix = projectPrefix.replacingOccurrences(of: #"\s+"#, with: "_",
options: .regularExpression, range: nil)
let derivedDirs = (try? filemgr
.contentsOfDirectory(atPath: derivedData.path)) ?? []
let namedDirs = derivedDirs
.filter { $0.starts(with: projectPrefix + "-") }
var possibleDerivedData = (namedDirs.isEmpty ? derivedDirs : namedDirs)
.map { derivedData.appendingPathComponent($0 + "/Logs/Build") }
possibleDerivedData.append(project.deletingLastPathComponent()
.appendingPathComponent("DerivedData/\(projectPrefix)/Logs/Build"))
debug("Possible DerivedDatas: \(possibleDerivedData)")
// use most recentry modified
return possibleDerivedData
.filter { filemgr.fileExists(atPath: $0.path) }
.sorted { mtime($0) > mtime($1) }
.first
}
class func uniqueTypeNames(signatures: [String], exec: (String) -> Void) {
var typesSearched = Set<String>()
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)
}
}
}
func shell(command: String) -> Bool {
try! command.write(toFile: cmdfile, atomically: false, encoding: .utf8)
debug(command)
#if os(macOS)
let task = Process()
task.launchPath = "/bin/bash"
task.arguments = [cmdfile]
task.launch()
task.waitUntilExit()
let status = task.terminationStatus
#else
let status = runner.run(script: cmdfile)
#endif
return status == EXIT_SUCCESS
}
#if !os(macOS)
lazy var runner = ScriptRunner()
class ScriptRunner {
let commandsOut: UnsafeMutablePointer<FILE>
let statusesIn: UnsafeMutablePointer<FILE>
init() {
let ForReading = 0, ForWriting = 1
var commandsPipe = [Int32](repeating: 0, count: 2)
var statusesPipe = [Int32](repeating: 0, count: 2)
pipe(&commandsPipe)
pipe(&statusesPipe)
var envp = [UnsafeMutablePointer<CChar>?](repeating: nil, count: 2)
if let home = getenv("USER_HOME") {
envp[0] = strdup("HOME=\(home)")!
}
if fork() == 0 {
let commandsIn = fdopen(commandsPipe[ForReading], "r")
let statusesOut = fdopen(statusesPipe[ForWriting], "w")
var buffer = [Int8](repeating: 0, count: Int(MAXPATHLEN))
close(commandsPipe[ForWriting])
close(statusesPipe[ForReading])
setbuf(statusesOut, nil)
while let script = fgets(&buffer, Int32(buffer.count), commandsIn) {
script[strlen(script)-1] = 0
let pid = fork()
if pid == 0 {
var argv = [UnsafeMutablePointer<Int8>?](repeating: nil, count: 3)
argv[0] = strdup("/bin/bash")!
argv[1] = strdup(script)!
_ = execve(argv[0], &argv, &envp)
fatalError("execve() fails \(String(cString: strerror(errno)))")
}
var status: Int32 = 0
while waitpid(pid, &status, 0) == -1 {}
fputs("\(status)\n", statusesOut)
}
exit(0)
}
commandsOut = fdopen(commandsPipe[ForWriting], "w")
statusesIn = fdopen(statusesPipe[ForReading], "r")
close(commandsPipe[ForReading])
close(statusesPipe[ForWriting])
setbuf(commandsOut, nil)
}
func run(script: String) -> Int32 {
fputs("\(script)\n", commandsOut)
var buffer = [Int8](repeating: 0, count: 20)
fgets(&buffer, Int32(buffer.count), statusesIn)
let status = atoi(buffer)
return status >> 8 | status & 0xff
}
}
#endif
#if DEBUG
deinit {
print("\(self).deinit()")
}
#endif
}
@_silgen_name("fork")
func fork() -> Int32
@_silgen_name("execve")
func execve(_ __file: UnsafePointer<Int8>!,
_ __argv: UnsafePointer<UnsafeMutablePointer<Int8>?>!,
_ __envp: UnsafePointer<UnsafeMutablePointer<Int8>?>!) -> Int32
#endif
#endif
================================================
FILE: Sources/HotReloading/SwiftInjection.swift
================================================
//
// SwiftInjection.swift
// InjectionBundle
//
// Created by John Holdsworth on 05/11/2017.
// Copyright © 2017 John Holdsworth. All rights reserved.
//
// $Id: //depot/HotReloading/Sources/HotReloading/SwiftInjection.swift#223 $
//
// Cut-down version of code injection in Swift. Uses code
// from SwiftEval.swift to recompile and reload class.
//
// There is a lot of history in this file. Originaly injection for Swift
// worked by patching the vtable of non final classes which worked fairly
// well but then we discovered "interposing" which is a mechanisim used by
// the dynamic linker to resolve references to system frameworks that can
// be used to rebind symbols at run time if you use the -interposable linker
// flag. This meant we were able to support injecting final methods of classes
// and methods of structs and enums. The code still updates the vtable though.
//
// A more recent change is to better supprt injection of generic classes
// and classes that inherit from generics which causes problems (crashes) in
// the Objective-C runtime. As one can't anticipate the specialisation of
// a generic in use from the object file (.dylib) alone, the patching of the
// vtable has been moved to the being a part of the sweep which mea
gitextract_6t44zbop/ ├── .github/ │ └── FUNDING.yml ├── .gitignore ├── Contents/ │ ├── Info.plist │ ├── PkgInfo │ └── Resources/ │ ├── Base.lproj/ │ │ ├── MainMenu.nib │ │ ├── RMWindowController.nib │ │ ├── XprobeConsole.nib │ │ └── XprobePluginMenuController.nib │ ├── Credits.rtf │ ├── InjectionBusy.tif │ ├── InjectionError.tif │ ├── InjectionIdle.tif │ ├── InjectionOK.tif │ ├── LICENSE │ ├── README.md │ ├── SwiftTrace.h │ ├── fishhook.h │ ├── graph.gv │ └── log.html ├── LICENSE ├── Package.swift ├── README.md ├── Sources/ │ ├── HotReloading/ │ │ ├── DeviceInjection.swift │ │ ├── DynamicCast.swift │ │ ├── FileWatcher.swift │ │ ├── InjectionClient.swift │ │ ├── InjectionStats.swift │ │ ├── ObjcInjection.swift │ │ ├── ReducerInjection.swift │ │ ├── StandaloneInjection.swift │ │ ├── SwiftEval.swift │ │ ├── SwiftInjection.swift │ │ ├── SwiftInterpose.swift │ │ ├── SwiftKeyPath.swift │ │ ├── SwiftSweeper.swift │ │ ├── UnhidingEval.swift │ │ └── Vaccine.swift │ ├── HotReloadingGuts/ │ │ ├── ClientBoot.mm │ │ ├── SimpleSocket.mm │ │ ├── Unhide.mm │ │ └── include/ │ │ ├── InjectionClient.h │ │ ├── SimpleSocket.h │ │ └── UserDefaults.h │ ├── injectiond/ │ │ ├── AppDelegate.swift │ │ ├── DeviceServer.swift │ │ ├── Experimental.swift │ │ ├── InjectionServer.swift │ │ ├── UpdateCheck.swift │ │ └── main.swift │ └── injectiondGuts/ │ ├── SignerService.m │ └── include/ │ ├── SignerService.h │ └── Xcode.h ├── copy_bundle.sh ├── fix_previews.sh └── start_daemon.sh
SYMBOL INDEX (16 symbols across 5 files)
FILE: Contents/Resources/SwiftTrace.h
type mach_header (line 201) | struct mach_header
type dyld_interpose_tuple (line 209) | struct dyld_interpose_tuple {
type rebinding (line 276) | struct rebinding {
type rebinding (line 291) | struct rebinding
type rebinding (line 300) | struct rebinding
FILE: Contents/Resources/fishhook.h
type rebinding (line 44) | struct rebinding {
type rebinding (line 59) | struct rebinding
type rebinding (line 68) | struct rebinding
FILE: Sources/HotReloadingGuts/include/InjectionClient.h
type InjectionComplete (line 146) | typedef NS_ENUM(int, InjectionResponse) {
type objc_image_info (line 182) | struct objc_image_info
type objc_image_info (line 184) | struct objc_image_info
FILE: Sources/HotReloadingGuts/include/SimpleSocket.h
function interface (line 14) | interface SimpleSocket : NSObject {
type sockaddr_storage (line 26) | struct sockaddr_storage
FILE: Sources/injectiondGuts/include/Xcode.h
type XcodeSaveOptions (line 24) | typedef enum XcodeSaveOptions XcodeSaveOptions;
type XcodeSchemeActionResultStatus (line 27) | enum XcodeSchemeActionResultStatus {
type XcodeSchemeActionResultStatus (line 35) | typedef enum XcodeSchemeActionResultStatus XcodeSchemeActionResultStatus;
Condensed preview — 55 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (453K chars).
[
{
"path": ".github/FUNDING.yml",
"chars": 722,
"preview": "# These are supported funding model platforms\n\ngithub: johnno1962 # Replace with up to 4 GitHub Sponsors-enabled usernam"
},
{
"path": ".gitignore",
"chars": 2167,
"preview": "# Xcode\n#\n# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore\n\n"
},
{
"path": "Contents/Info.plist",
"chars": 2256,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
},
{
"path": "Contents/PkgInfo",
"chars": 8,
"preview": "APPL????"
},
{
"path": "Contents/Resources/Credits.rtf",
"chars": 5601,
"preview": "{\\rtf1\\ansi\\ansicpg1252\\cocoartf1561\\cocoasubrtf600\n{\\fonttbl\\f0\\fswiss\\fcharset0 Helvetica;\\f1\\fnil\\fcharset0 Menlo-Reg"
},
{
"path": "Contents/Resources/LICENSE",
"chars": 1073,
"preview": "MIT License\n\nCopyright (c) 2017 John Holdsworth \n\nPermission is hereby granted, free of charge, to any person obtaining "
},
{
"path": "Contents/Resources/README.md",
"chars": 20816,
"preview": "# InjectionIII - overdue Swift rewrite of InjectionForXcode\n\n\n\nCode in"
},
{
"path": "Contents/Resources/SwiftTrace.h",
"chars": 11157,
"preview": "//\n// SwiftTrace.h\n// SwiftTrace\n//\n// Created by John Holdsworth on 10/06/2016.\n// Copyright © 2016 John Holdsworth"
},
{
"path": "Contents/Resources/fishhook.h",
"chars": 3059,
"preview": "// Copyright (c) 2013, Facebook, Inc.\n// All rights reserved.\n// Redistribution and use in source and binary forms, with"
},
{
"path": "Contents/Resources/graph.gv",
"chars": 20457,
"preview": "digraph sweep {\n node [href=\"javascript:void(click_node('\\N'))\" id=\"\\N\" fontname=\"Arial\"];\n 0 [label=\"UIApplicatio"
},
{
"path": "Contents/Resources/log.html",
"chars": 1410,
"preview": "<html><header><style>\n\nbody, table { font: 8pt Arial; margin: 0px; border: 3px inset lightgrey; }\nimg.snapshot { border:"
},
{
"path": "LICENSE",
"chars": 1072,
"preview": "MIT License\n\nCopyright (c) 2021 John Holdsworth\n\nPermission is hereby granted, free of charge, to any person obtaining a"
},
{
"path": "Package.swift",
"chars": 3337,
"preview": "// swift-tools-version:5.2\n// The swift-tools-version declares the minimum version of Swift required to build this packa"
},
{
"path": "README.md",
"chars": 6957,
"preview": "# Yes, HotReloading for Swift, Objective-C & C++!\n\nNote: While this was once a way of using the InjectionIII.app on real"
},
{
"path": "Sources/HotReloading/DeviceInjection.swift",
"chars": 11525,
"preview": "//\n// DeviceInjection.swift\n//\n// Created by John Holdsworth on 17/03/2022.\n// Copyright © 2022 John Holdsworth. All "
},
{
"path": "Sources/HotReloading/DynamicCast.swift",
"chars": 2619,
"preview": "//\n// DynamicCast.swift\n// InjectionIII\n//\n// Created by John Holdsworth on 02/24/2021.\n// Copyright © 2021 John Hol"
},
{
"path": "Sources/HotReloading/FileWatcher.swift",
"chars": 9265,
"preview": "//\n// FileWatcher.swift\n// InjectionIII\n//\n// Created by John Holdsworth on 08/03/2015.\n// Copyright (c) 2015 John H"
},
{
"path": "Sources/HotReloading/InjectionClient.swift",
"chars": 19374,
"preview": "//\n// InjectionClient.swift\n// InjectionIII\n//\n// Created by John Holdsworth on 02/24/2021.\n// Copyright © 2021 John"
},
{
"path": "Sources/HotReloading/InjectionStats.swift",
"chars": 2719,
"preview": "//\n// InjectionStats.swift\n// \n// Created by John Holdsworth on 26/10/2022.\n// Copyright © 2022 John Holdsworth. All"
},
{
"path": "Sources/HotReloading/ObjcInjection.swift",
"chars": 4958,
"preview": "//\n// ObjcInjection.swift\n//\n// Created by John Holdsworth on 17/03/2022.\n// Copyright © 2022 John Holdsworth. All ri"
},
{
"path": "Sources/HotReloading/ReducerInjection.swift",
"chars": 2434,
"preview": "//\n// ReducerInjection.swift\n//\n// Created by John Holdsworth on 09/06/2022.\n// Copyright © 2022 John Holdsworth. All"
},
{
"path": "Sources/HotReloading/StandaloneInjection.swift",
"chars": 6852,
"preview": "//\n// StandaloneInjection.swift\n//\n// Created by John Holdsworth on 15/03/2022.\n// Copyright © 2022 John Holdsworth. "
},
{
"path": "Sources/HotReloading/SwiftEval.swift",
"chars": 55626,
"preview": "//\n// SwiftEval.swift\n// InjectionBundle\n//\n// Created by John Holdsworth on 02/11/2017.\n// Copyright © 2017 John Ho"
},
{
"path": "Sources/HotReloading/SwiftInjection.swift",
"chars": 29015,
"preview": "//\n// SwiftInjection.swift\n// InjectionBundle\n//\n// Created by John Holdsworth on 05/11/2017.\n// Copyright © 2017 Jo"
},
{
"path": "Sources/HotReloading/SwiftInterpose.swift",
"chars": 8702,
"preview": "//\n// SwiftInterpose.swift\n//\n// Created by John Holdsworth on 25/04/2022.\n//\n// Interpose processing (-Xlinker -inte"
},
{
"path": "Sources/HotReloading/SwiftKeyPath.swift",
"chars": 4490,
"preview": "//\n// SwiftKeyPath.swift\n//\n// Created by John Holdsworth on 20/03/2024.\n// Copyright © 2024 John Holdsworth. All rig"
},
{
"path": "Sources/HotReloading/SwiftSweeper.swift",
"chars": 9085,
"preview": "//\n// SwiftSweeper.swift\n//\n// Created by John Holdsworth on 15/04/2021.\n//\n// The implementation of the memeory swee"
},
{
"path": "Sources/HotReloading/UnhidingEval.swift",
"chars": 21462,
"preview": "//\n// UnhidingEval.swift\n//\n// Created by John Holdsworth on 13/04/2021.\n//\n// $Id: //depot/HotReloading/Sources/HotR"
},
{
"path": "Sources/HotReloading/Vaccine.swift",
"chars": 8439,
"preview": "#if (DEBUG || !SWIFT_PACKAGE) && !os(watchOS)\n#if os(macOS)\nimport Cocoa\ntypealias View = NSView\ntypealias ViewControlle"
},
{
"path": "Sources/HotReloadingGuts/ClientBoot.mm",
"chars": 15069,
"preview": "//\n// ClientBoot.mm\n// InjectionIII\n//\n// Created by John Holdsworth on 02/24/2021.\n// Copyright © 2021 John Holdswo"
},
{
"path": "Sources/HotReloadingGuts/SimpleSocket.mm",
"chars": 15317,
"preview": "//\n// SimpleSocket.mm\n// InjectionIII\n//\n// Created by John Holdsworth on 06/11/2017.\n// Copyright © 2017 John Holds"
},
{
"path": "Sources/HotReloadingGuts/Unhide.mm",
"chars": 20892,
"preview": "//\n// Unhide.mm\n//\n// Created by John Holdsworth on 07/03/2021.\n//\n// Removes \"hidden\" visibility for certain Swift s"
},
{
"path": "Sources/HotReloadingGuts/include/InjectionClient.h",
"chars": 5533,
"preview": "//\n// InjectionClient.h\n// InjectionBundle\n//\n// Created by John Holdsworth on 06/11/2017.\n// Copyright © 2017 John "
},
{
"path": "Sources/HotReloadingGuts/include/SimpleSocket.h",
"chars": 1550,
"preview": "//\n// SimpleSocket.h\n// InjectionIII\n//\n// Created by John Holdsworth on 06/11/2017.\n// Copyright © 2017 John Holdsw"
},
{
"path": "Sources/HotReloadingGuts/include/UserDefaults.h",
"chars": 1087,
"preview": "//\n// UserDefaults.h\n// InjectionIII\n//\n// Created by Christoffer Winterkvist on 10/25/18.\n// Copyright © 2018 John "
},
{
"path": "Sources/injectiond/AppDelegate.swift",
"chars": 25160,
"preview": "//\n// AppDelegate.swift\n// InjectionIII\n//\n// Created by John Holdsworth on 06/11/2017.\n// Copyright © 2017 John Hol"
},
{
"path": "Sources/injectiond/DeviceServer.swift",
"chars": 5135,
"preview": "//\n// DeviceServer.swift\n// InjectionIII\n// \n// Created by John Holdsworth on 13/01/2022.\n// Copyright © 2017 John "
},
{
"path": "Sources/injectiond/Experimental.swift",
"chars": 20554,
"preview": "//\n// Experimental.swift\n// InjectionIII\n//\n// Created by User on 20/10/2020.\n// Copyright © 2020 John Holdsworth. A"
},
{
"path": "Sources/injectiond/InjectionServer.swift",
"chars": 19508,
"preview": "//\n// InjectionServer.swift\n// InjectionIII\n//\n// Created by John Holdsworth on 06/11/2017.\n// Copyright © 2017 John"
},
{
"path": "Sources/injectiond/UpdateCheck.swift",
"chars": 4079,
"preview": "//\n// UpdateCheck.swift\n// InjectionIII\n//\n// Created by John Holdsworth on 17/09/2020.\n// Copyright © 2020 John Hol"
},
{
"path": "Sources/injectiond/main.swift",
"chars": 497,
"preview": "//\n// main.swift\n// HotReloading\n//\n// Created by John Holdsworth on 02/24/2021.\n// Copyright © 2021 John Holdsworth"
},
{
"path": "Sources/injectiondGuts/SignerService.m",
"chars": 2206,
"preview": "//\n// SignerService.m\n// InjectionIII\n//\n// Created by John Holdsworth on 06/11/2017.\n// Copyright © 2017 John Holds"
},
{
"path": "Sources/injectiondGuts/include/SignerService.h",
"chars": 335,
"preview": "//\n// SignerService.h\n// InjectionIII\n//\n// Created by John Holdsworth on 06/11/2017.\n// Copyright © 2017 John Holds"
},
{
"path": "Sources/injectiondGuts/include/Xcode.h",
"chars": 15176,
"preview": "/*\n * Xcode.h -- extracted using: sdef /Applications/Xcode.app | sdp -fh --basename Xcode\n */\n\n#import <AppKit/AppKit.h>"
},
{
"path": "copy_bundle.sh",
"chars": 2201,
"preview": "#!/bin/bash -x\n#\n# copy_bundle.sh\n# InjectionIII\n#\n# Copies injection bundle for on-device injection.\n# Thanks @oryo"
},
{
"path": "fix_previews.sh",
"chars": 629,
"preview": "#!/bin/bash\n#\n# Workaround for limitations of Xcode Previews\n# for projects that have dynamic SPM libraries.\n# Runnin"
},
{
"path": "start_daemon.sh",
"chars": 1892,
"preview": "#!/bin/bash\n#\n# Start up daemon process to rebuild changed sources\n#\n# $Id: //depot/HotReloading/start_daemon.sh#42 $\n#\n"
}
]
// ... and 8 more files (download for full content)
About this extraction
This page contains the full source code of the johnno1962/HotReloading GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 55 files (423.3 KB), approximately 100.1k tokens, and a symbol index with 16 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.