Skip to content

Commit

Permalink
Refactor glue file hierarchy
Browse files Browse the repository at this point in the history
Should increase performance (inlining due to Swift being able to see concrete _Glue types) while hopefully simplifying Orion's code conceptually (fewer existentials, single "bridge" point etc)
  • Loading branch information
kabiroberai committed Mar 4, 2021
1 parent c042a52 commit 594adbf
Show file tree
Hide file tree
Showing 16 changed files with 945 additions and 821 deletions.
1 change: 0 additions & 1 deletion .jazzy.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@ custom_categories:
- name: All Hooks
children:
- AnyHook
- AnyHookBase
- name: Groups
children:
- HookGroup
Expand Down
8 changes: 8 additions & 0 deletions Orion.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@
56AFA08B25A3CE5F00884E2D /* dealloc.h in Headers */ = {isa = PBXBuildFile; fileRef = 56AFA08A25A3CE5F00884E2D /* dealloc.h */; settings = {ATTRIBUTES = (Public, ); }; };
56AFA08D25A3CE6500884E2D /* dealloc.m in Sources */ = {isa = PBXBuildFile; fileRef = 56AFA08C25A3CE6500884E2D /* dealloc.m */; };
56AFA08F25A3CE8B00884E2D /* ClassHook+Deinit.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56AFA08E25A3CE8A00884E2D /* ClassHook+Deinit.swift */; };
56D50FAE25F11DEE00AE26FA /* ClassHook+Glue.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56D50FAD25F11DEE00AE26FA /* ClassHook+Glue.swift */; };
56D50FB125F11DF500AE26FA /* FunctionHook+Glue.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56D50FB025F11DF500AE26FA /* FunctionHook+Glue.swift */; };
56D5B37F258F6F9D00F3DB6E /* ErrorHandling.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56D5B37E258F6F9D00F3DB6E /* ErrorHandling.swift */; };
56D9F14925C4940900EDB315 /* LazyAtomic.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56D9F14825C4940900EDB315 /* LazyAtomic.swift */; };
/* End PBXBuildFile section */
Expand Down Expand Up @@ -68,6 +70,8 @@
56AFA08A25A3CE5F00884E2D /* dealloc.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = dealloc.h; sourceTree = "<group>"; };
56AFA08C25A3CE6500884E2D /* dealloc.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = dealloc.m; sourceTree = "<group>"; };
56AFA08E25A3CE8A00884E2D /* ClassHook+Deinit.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "ClassHook+Deinit.swift"; sourceTree = "<group>"; };
56D50FAD25F11DEE00AE26FA /* ClassHook+Glue.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "ClassHook+Glue.swift"; sourceTree = "<group>"; };
56D50FB025F11DF500AE26FA /* FunctionHook+Glue.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "FunctionHook+Glue.swift"; sourceTree = "<group>"; };
56D5B37E258F6F9D00F3DB6E /* ErrorHandling.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ErrorHandling.swift; sourceTree = "<group>"; };
56D9F14825C4940900EDB315 /* LazyAtomic.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LazyAtomic.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */
Expand Down Expand Up @@ -195,10 +199,12 @@
568974A4252BBA15006F48E9 /* Backend.swift */,
5689749C252BBA15006F48E9 /* ClassHook.swift */,
56AFA08E25A3CE8A00884E2D /* ClassHook+Deinit.swift */,
56D50FAD25F11DEE00AE26FA /* ClassHook+Glue.swift */,
568974A0252BBA15006F48E9 /* ClassHook+Super.swift */,
5689749D252BBA15006F48E9 /* Dynamic.swift */,
56D5B37E258F6F9D00F3DB6E /* ErrorHandling.swift */,
5689749E252BBA15006F48E9 /* FunctionHook.swift */,
56D50FB025F11DF500AE26FA /* FunctionHook+Glue.swift */,
56080B5B25C1F8BE005803BC /* Group.swift */,
568974A8252BBA15006F48E9 /* Hook.swift */,
5689749F252BBA15006F48E9 /* InternalBackend.swift */,
Expand Down Expand Up @@ -397,6 +403,8 @@
568974B5252BBA15006F48E9 /* ReadWriteLock.swift in Sources */,
56D9F14925C4940900EDB315 /* LazyAtomic.swift in Sources */,
568974C4252BBA4F006F48E9 /* super.m in Sources */,
56D50FB125F11DF500AE26FA /* FunctionHook+Glue.swift in Sources */,
56D50FAE25F11DEE00AE26FA /* ClassHook+Glue.swift in Sources */,
568974AA252BBA15006F48E9 /* ClassHook.swift in Sources */,
568974B1252BBA15006F48E9 /* Property.swift in Sources */,
568974B3252BBA15006F48E9 /* MutexLock.swift in Sources */,
Expand Down
2 changes: 1 addition & 1 deletion Sources/Orion/Backend.swift
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ public protocol Backend {
}

extension Backend {
func activate(hooks: [_AnyGlueHook.Type]) {
func activate(hooks: [_GlueAnyHook.Type]) {
let hooksToActivate = hooks.filter { $0.hookWillActivate() }
apply(descriptors: hooksToActivate.flatMap { $0.activate() })
hooksToActivate.forEach { $0.hookDidActivate() }
Expand Down
6 changes: 3 additions & 3 deletions Sources/Orion/ClassHook+Deinit.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,15 @@ public enum DeinitPolicy {
}

/// :nodoc:
extension _ClassHookBuilder {
extension _GlueClassHookBuilder {
private static let deallocSelector = NSSelectorFromString("dealloc")

public typealias Deinitializer = @convention(c) (Any, Selector) -> Void

// for some reason using `@escaping Deinitializer` instead of `Code`
// works in SPM but not with the binary framework (maybe a swiftinterface
// bug or library evolution thing?)
public mutating func addDeinitializer<T: _GlueClassHook, Code>(
public mutating func addDeinitializer<T: ClassHookProtocol, Code>(
to classHook: T.Type,
getOrig: @escaping () -> Deinitializer,
setOrig: @escaping (Code) -> Void
Expand Down Expand Up @@ -60,7 +60,7 @@ extension _ClassHookBuilder {
}

/// :nodoc:
extension _GlueClassHook {
extension _GlueClassHookTrampoline {
public func deinitOrigError(file: StaticString = #file, line: UInt = #line) -> Never {
orionError("Do not call `orig.deinitializer()`.", file: file, line: line)
}
Expand Down
166 changes: 166 additions & 0 deletions Sources/Orion/ClassHook+Glue.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
import Foundation

/// Internal storage associated with a `ClassHook`. Do not use this yourself.
///
/// :nodoc:
public final class _GlueClassHookStorage {
let hookType: AnyClass
@LazyAtomic private(set) var targetType: AnyObject.Type
@LazyAtomic private(set) var group: HookGroup

// additional fields may be added here in the future

init(
hookType: AnyClass,
loadTargetType: @escaping () -> AnyObject.Type,
loadGroup: @escaping () -> HookGroup
) {
self.hookType = hookType
_targetType = LazyAtomic(wrappedValue: loadTargetType())
_group = LazyAtomic(wrappedValue: loadGroup())
}
}

/// A placeholder class hook glue type. Do not use this yourself.
///
/// This type is the default value for the `ClassHookProtocol._Glue` constraint,
/// used to satisfy the compiler until the actual glue is provided.
///
/// :nodoc:
public enum _GlueClassHookPlaceholder<HookType: ClassHookProtocol>: _GlueClassHook {
public typealias OrigType = HookType
public typealias SuprType = HookType

public static var storage: _GlueClassHookStorage { error() }
public static func activate(withClassHookBuilder builder: inout _GlueClassHookBuilder) { error() }

private static func error() -> Never {
orionError("Placeholder class hook used. Has the glue file been compiled?")
}
}

/// A marker protocol to which `_GlueClassHook`'s orig/supr trampolines conform. Do
/// not use this yourself.
///
/// :nodoc:
public protocol _GlueClassHookTrampoline: ClassHookProtocol {}

/// A helper type used in the glue file for applying class hooks. Do not
/// use this directly.
///
/// :nodoc:
public struct _GlueClassHookBuilder {
let target: AnyClass

var descriptors: [HookDescriptor] = []

public mutating func addHook<Code>(
_ sel: Selector,
_ replacement: Code,
isClassMethod: Bool,
saveOrig: @escaping (Code) -> Void
) {
let cls: AnyClass = isClassMethod ? object_getClass(target)! : target
descriptors.append(
.method(cls: cls, sel: sel, replacement: unsafeBitCast(replacement, to: UnsafeMutableRawPointer.self)) {
saveOrig(unsafeBitCast($0, to: Code.self))
}
)
}
}

/// A concrete class hook, implemented in the glue file. Do not use
/// this directly.
///
/// :nodoc:
public protocol _GlueClassHook: _GlueAnyHook {
associatedtype HookType: ClassHookProtocol
associatedtype OrigType: ClassHookProtocol where OrigType.Target == HookType.Target
associatedtype SuprType: ClassHookProtocol where SuprType.Target == HookType.Target

static var storage: _GlueClassHookStorage { get }
static func activate(withClassHookBuilder builder: inout _GlueClassHookBuilder)
}

/// :nodoc:
extension _GlueClassHook {
public static func addMethod<Code>(_ selector: Selector, _ implementation: Code, isClassMethod: Bool) {
let methodDescription = { "\(isClassMethod ? "+" : "-")[\(self) \(selector)]" }
guard let method = (isClassMethod ? class_getClassMethod : class_getInstanceMethod)(HookType.self, selector)
else { orionError("Could not find method \(methodDescription())") }
guard let types = method_getTypeEncoding(method)
else { orionError("Could not get method signature for \(methodDescription())") }
let cls: AnyClass = isClassMethod ? object_getClass(HookType.target)! : HookType.target
guard class_addMethod(cls, selector, unsafeBitCast(implementation, to: IMP.self), types)
else { orionError("Failed to add method \(methodDescription())") }
}

public static func activate() -> [HookDescriptor] {
var classHookBuilder = _GlueClassHookBuilder(target: HookType.target)
activate(withClassHookBuilder: &classHookBuilder)
return classHookBuilder.descriptors
}

public static var groupType: HookGroup.Type {
HookType.Group.self
}

public static func hookWillActivate() -> Bool {
HookType.hookWillActivate()
}

public static func hookDidActivate() {
HookType.hookDidActivate()
}

public static func initializeStorage() -> _GlueClassHookStorage {
// this gives us the type of the user's hook rather than
// our concrete subclass
_GlueClassHookStorage(
hookType: HookType.self,
loadTargetType: HookType.initializeTargetType,
loadGroup: HookType.loadGroup
)
}
}

extension SubclassMode {
fileprivate func subclassName(withType type: AnyClass) -> String? {
switch self {
case .none:
return nil
case .createSubclass:
return "OrionSubclass.\(NSStringFromClass(type))"
case .createSubclassNamed(let name):
return name
}
}
}

/// :nodoc:
extension ClassHookProtocol {
// since `target` is referred to in `activate()`, this will deterministically be called
// when a class hook is activated.
fileprivate static func initializeTargetType() -> Target.Type {
let targetName = self.targetName // only call getter once
let baseTarget = targetName.isEmpty ? Target.self : Dynamic(targetName).as(type: Target.self)

let target: Target.Type
if let subclassName = subclassMode.subclassName(withType: _Glue.storage.hookType) {
guard let pair: AnyClass = objc_allocateClassPair(baseTarget, subclassName, 0)
else { orionError("Could not allocate subclass for \(self)") }
objc_registerClassPair(pair)
guard let _target = pair as? Target.Type
else { orionError("Allocated invalid subclass for \(self)") }
target = _target
} else {
target = baseTarget
}

protocols.forEach {
guard class_addProtocol(target, $0)
else { orionError("Could not add protocol \($0) to \(target)") }
}
return target
}
}
2 changes: 1 addition & 1 deletion Sources/Orion/ClassHook+Super.swift
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ enum MessageSendSuperType {
}

/// :nodoc:
extension _GlueClassHook {
extension _GlueClassHookTrampoline {
private static func callSuper<ReturnType, MessageType>(
_ type: MessageType.Type,
receiver: Any,
Expand Down
Loading

0 comments on commit 594adbf

Please sign in to comment.