cocoabits / MASShortcut

Modern framework for managing global keyboard shortcuts compatible with Mac App Store. More details:
http://blog.shpakovski.com/2012/07/global-keyboard-shortcuts-in-cocoa.html
BSD 2-Clause "Simplified" License
1.52k stars 220 forks source link

EXC_BAD_ACCESS on Monterey when using swift String for defaultsKey #175

Open timbertson opened 2 years ago

timbertson commented 2 years ago

After upgrading to MacOS monterey, I consistently saw the following crash in slinger, a window management app I maintain.

lldb shows the stacktrace as follows:

* thread #1, queue = 'com.apple.main-thread', stop reason = EXC_BAD_ACCESS (code=EXC_I386_GPFLT)
  * frame #0: 0x0000000100922ae4 CoreFoundation`CFGetTypeID + 4
    frame #1: 0x00007ff820bbfe47 libswiftCore.dylib`_swift_stdlib_isNSString + 39
    frame #2: 0x00007ff8209d98ad libswiftCore.dylib`Swift.__StringStorage.isEqualToString(to: Swift.Optional<Swift.AnyObject>) -> Swift.Int8 + 125
    frame #3: 0x00007ff8209d9a12 libswiftCore.dylib`@objc Swift.__StringStorage.isEqualToString(to: Swift.Optional<Swift.AnyObject>) -> Swift.Int8 + 18
    frame #4: 0x00007ff813401e9b CoreFoundation`-[__NSDictionaryM __setObject:forKey:] + 573
    frame #5: 0x00007ff81341dcbc CoreFoundation`-[__NSFrozenDictionaryM __apply:context:] + 112
    frame #6: 0x00007ff81346226a CoreFoundation`___CFPrefsDeliverPendingKVONotificationsGuts_block_invoke + 324
    frame #7: 0x00007ff81346211e CoreFoundation`__CFDictionaryApplyFunction_block_invoke + 22
    frame #8: 0x00007ff81342aee3 CoreFoundation`CFBasicHashApply + 115
    frame #9: 0x00007ff81341dc0b CoreFoundation`CFDictionaryApplyFunction + 131
    frame #10: 0x00007ff813462093 CoreFoundation`_CFPrefsDeliverPendingKVONotificationsGuts + 262
    frame #11: 0x00007ff81340e9a6 CoreFoundation`-[_CFXPreferences _deliverPendingKVONotifications] + 90
    frame #12: 0x00007ff813548888 CoreFoundation`-[_CFXPreferences withNamedVolatileSourceForIdentifier:perform:] + 286
    frame #13: 0x00007ff813424932 CoreFoundation`__41-[_CFXPreferences registerDefaultValues:]_block_invoke + 98
    frame #14: 0x00007ff81340db9a CoreFoundation`withKeysAndValues + 268
    frame #15: 0x00007ff8134248b5 CoreFoundation`-[_CFXPreferences registerDefaultValues:] + 79
    frame #16: 0x00007ff813424836 CoreFoundation`_CFXPreferencesRegisterDefaultValues + 75
    frame #17: 0x00007ff81425b650 Foundation`-[NSUserDefaults(NSUserDefaults) registerDefaults:] + 255
    frame #18: 0x000000010001b0e5 Slinger`__46-[MASShortcutBinder registerDefaultShortcuts:]_block_invoke(.block_descriptor=<unavailable>, defaultsKey=<unavailable>, shortcut=<unavailable>, stop=<unavailable>) at MASShortcutBinder.m:77:9 [opt]
    frame #19: 0x00007ff820ad6751 libswiftCore.dylib`function signature specialization <Arg[0] = Dead> of Swift._SwiftDeferredNSDictionary.enumerateKeysAndObjects(options: Swift.Int, using: @convention(block) (Swift.Unmanaged<Swift.AnyObject>, Swift.Unmanaged<Swift.AnyObject>, Swift.UnsafeMutablePointer<Swift.UInt8>) -> ()) -> () + 321
    frame #20: 0x00007ff8208c70f5 libswiftCore.dylib`@objc Swift._SwiftDeferredNSDictionary.enumerateKeysAndObjects(options: Swift.Int, using: @convention(block) (Swift.Unmanaged<Swift.AnyObject>, Swift.Unmanaged<Swift.AnyObject>, Swift.UnsafeMutablePointer<Swift.UInt8>) -> ()) -> () + 37
    frame #21: 0x000000010001af9f Slinger`-[MASShortcutBinder registerDefaultShortcuts:](self=<unavailable>, _cmd=<unavailable>, defaultShortcuts=<unavailable>) at MASShortcutBinder.m:75:5 [opt]
    frame #22: 0x000000010001d22d Slinger`bind #1 (pref=<unavailable>, key=<unavailable>, modifiers=<unavailable>, slow=false, fn=0x000000010001f0c0 Slinger`closure #1 () -> () in bind(_: Swift.String, key: Swift.String, modifiers: __C.NSEventModifierFlags, action: Swift.String, arguments: Swift.Array<Any>, slow: Swift.Bool) -> ()partial apply forwarder with unmangled suffix ".32", keycodeMap=Swift.Dictionary<Swift.String, Swift.Array<Slinger.Keybinding.AMKeyCode>> @ 0x00007ff7bfefcfb0, status=0x0000600001729540) in ApplicationDelegate.run() at AppDelegate.swift:29:42 [opt]

I figured the obvious suspects were bad serialization, a sneaky nil somewhere, or threading. But I couldn't find any of those.

Eventually I took the stacktrace at its word - the fact it's doing string comparisons implies it's something to do with the key of the NSDictionary, nothing to do with the value.

Somehow I turned up this thread from 7 years ago: https://stackoverflow.com/questions/24208594/swift-string-manipulation-causing-exc-bad-access It's slightly different, yet the same fix works - pass an NSString key instead of a swift String.

Here's the actual fix I made to slinger: https://github.com/timbertson/Slinger.app/commit/dc55f531be1c07c03529b84676f1461ea6a589df

I think this may have something to do with the binding code as well, given the presence of ___CFPrefsDeliverPendingKVONotificationsGuts_block_invoke. If I comment out the actual binder.bindShortcut(...) call it doesn't happen, but that may be a red herring. Oh, and the original crash happened when setting up the second keybinding. I.e. it works once, but fails after the first binding set up.

Anyway, I don't know if you want to investigate, feel free to close. I just figured it might at least help some poor soul googling for the same issue.