Last active
May 28, 2022 12:21
-
-
Save steipete/f955aaa0742021af15add0133d8482b9 to your computer and use it in GitHub Desktop.
Two variants: Without helper https://steipete.wtf/posts/mac-catalyst-crash-hunt/ and then with InterposeKit https://github.com/steipete/InterposeKit
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// Copyright © 2020 Peter Steinberger. MIT Licensed. | |
// | |
import Foundation | |
/// Text input can crash in Mac Catalyst due to a property not being atomic. | |
/// We swizzle the `documentState` property of `RTIInputSystemSession` to make it thread safe. | |
func installMacCatalystAppKitTextCrashFix() { | |
// RTIInputSystemSession is loaded later, so we will check a few times. | |
_dyld_register_func_for_add_image { _, _ in | |
DispatchQueue.main.async { | |
if fixMacCatalystInputSystemSessionRace() { | |
print("Successfully installed Mac Catalyst Text Input Race Fix.") | |
} | |
} | |
} | |
} | |
private var didInstallCrashFix = false | |
/// https://github.com/w0lfschild/macOS_headers/blob/master/macOS/PrivateFrameworks/RemoteTextInput/2399/RTIInputSystemSession.h | |
/// Sample crashers: | |
/// https://gist.github.com/steipete/504e79558d861211a3a9ff794e09c817 | |
/// https://gist.github.com/steipete/7a125b20cce461bf9a072dfacd805507 | |
private func fixMacCatalystInputSystemSessionRace() -> Bool { | |
guard let klass = NSClassFromString("RTIInputSystemSession") else { return false } | |
guard didInstallCrashFix == false else { return false } | |
var lockStore = os_unfair_lock() | |
let sel = NSSelectorFromString("documentState") | |
var origIMP : IMP? = nil | |
let newHandler: @convention(block) (AnyObject) -> AnyObject = { blockSelf in | |
typealias ClosureType = @convention(c) (AnyObject, Selector) -> AnyObject | |
let callableIMP = unsafeBitCast(origIMP, to: ClosureType.self) | |
os_unfair_lock_lock(&lockStore) | |
defer { | |
os_unfair_lock_unlock(&lockStore) | |
} | |
return callableIMP(blockSelf, sel) | |
} | |
guard let method = class_getInstanceMethod(klass, sel) else { return false } | |
origIMP = class_replaceMethod(klass, sel, imp_implementationWithBlock(newHandler), method_getTypeEncoding(method)) | |
let setSel = NSSelectorFromString("setDocumentState:") | |
var setOrigIMP : IMP? = nil | |
let newSetHandler: @convention(block) (AnyObject, AnyObject) -> Void = { blockSelf, newValue in | |
typealias ClosureType = @convention(c) (AnyObject, Selector, AnyObject) -> Void | |
let callableIMP = unsafeBitCast(setOrigIMP, to: ClosureType.self) | |
os_unfair_lock_lock(&lockStore) | |
callableIMP(blockSelf, sel, newValue) | |
os_unfair_lock_unlock(&lockStore) | |
} | |
guard let setMethod = class_getInstanceMethod(klass, setSel) else { return false } | |
setOrigIMP = class_replaceMethod(klass, setSel, imp_implementationWithBlock(newSetHandler), method_getTypeEncoding(setMethod)) | |
didInstallCrashFix = true | |
return origIMP != nil && setOrigIMP != nil | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Version using InterposeKit: https://github.com/steipete/InterposeKit | |
private func fixMacCatalystInputSystemSessionRace() { | |
do { | |
// Better to separate this string for static analysis | |
try Interpose.whenAvailable(["RTIInput", "SystemSession"]) { | |
let lock = DispatchQueue(label: "com.steipete.document-state-hack") | |
try $0.hook("documentState", { store in { `self` in | |
lock.sync { | |
store((@convention(c) (AnyObject, Selector) -> AnyObject).self)(`self`, store.selector) | |
}} as @convention(block) (AnyObject) -> AnyObject}) | |
try $0.hook("setDocumentState:", { store in { `self`, newValue in | |
lock.sync { | |
store((@convention(c) (AnyObject, Selector, AnyObject) -> Void).self)(`self`, store.selector, newValue) | |
}} as @convention(block) (AnyObject, AnyObject) -> Void}) | |
} | |
} catch { | |
print("Failed to fix input system: \(error).") | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment