cocoanstouchbar

How can I prevent this crash when updating the Touch Bar's escape key?


I added Touch Bar support to my app when the latest MacBook Pro launched. Later I made various minor improvements, including custom escape keys where it made sense. After releasing that update, I started getting crash reports when the app tries to update the escape key.

Here's one example:

Exception Type:  SIGBUS
Exception Codes: BUS_ADRERR at 0x7fff54a05ff8
Crashed Thread:  0

Application Specific Information:
Selector name found in current argument registers: objectForKey:

Thread 0 Crashed:
0   Foundation                           0x00007fffacbaea98 -[NSConcreteMapTable objectForKey:] + 21
1   Foundation                           0x00007fffacbfc019 -[NSISEngine outgoingRowHeadForRemovingConstraintWithMarker:] + 214
2   Foundation                           0x00007fffacbfbb4c -[NSISEngine removeConstraintWithMarker:] + 479
3   Foundation                           0x00007fffacbf76a6 -[NSISEngine _flushPendingRemovals] + 615
4   Foundation                           0x00007fffacbf4b06 -[NSISEngine withBehaviors:performModifications:] + 197
5   AppKit                               0x00007fffa8c83760 -[NSView(NSConstraintBasedLayout) _withAutomaticEngineOptimizationDisabled:] + 69
6   AppKit                               0x00007fffa8d129dd -[NSView(NSConstraintBasedLayout) removeConstraints:] + 276
7   AppKit                               0x00007fffa8c88f9e -[NSView(NSConstraintBasedLayout) _constraints_snipDangliesWithForce:] + 595
8   AppKit                               0x00007fffa8c82d9e -[NSView _setSuperview:] + 1076
9   AppKit                               0x00007fffa8c88945 -[NSView removeFromSuperview] + 446
10  AppKit                               0x00007fffa9593eef -[NSTouchBarEscapeKeyViewController setTouchBarItem:] + 145
11  AppKit                               0x00007fffa9176bf5 -[NSApplicationFunctionRowController _updateEscapeKeyItem] + 438
12  AppKit                               0x00007fffa9176bf5 -[NSApplicationFunctionRowController _updateEscapeKeyItem] + 438
13  AppKit                               0x00007fffa9176bf5 -[NSApplicationFunctionRowController _updateEscapeKeyItem] + 438
…
509 AppKit                               0x00007fffa9176bf5 -[NSApplicationFunctionRowController _updateEscapeKeyItem] + 438
510 AppKit                               0x00007fffa9176bf5 -[NSApplicationFunctionRowController _updateEscapeKeyItem] + 438
511 AppKit                               0x00007fffa9176bf5 -[NSApplicationFunctionRowController _updateEscapeKeyItem] + 438

I've removed about 500 lines for brevity—all of them are calls to [NSApplicationFunctionRowController _updateEscapeKeyItem] + 438

Here's another abbreviated report. This one is my most common crash. It actually calls my own code, though I suspect the code it's calling is not the actual problem:

Exception Type:  SIGBUS
Exception Codes: BUS_ADRERR at 0x7fff5b8c2f74
Crashed Thread:  0

Thread 0 Crashed:
0   CoreText                             0x00007fffc87d2e8d _ZNK3OTL7GCommon9NthLookupEj + 41
1   CoreText                             0x00007fffc87d658b _ZNK3OTL4GPOS12ApplyLookupsER8TRunGlueiRNS_12GlyphLookupsE + 155
2   CoreText                             0x00007fffc87d5f53 _ZN26TOpenTypePositioningEngine12PositionRunsER9SyncStateR13KerningStatus + 839
3   CoreText                             0x00007fffc8823920 _ZN14TKerningEngine14PositionGlyphsER8TRunGlue11ShapingTypePK10__CFString + 168
4   CoreText                             0x00007fffc87ddcf7 CTFontTransformGlyphs + 463
5   UIFoundation                         0x00007fffd9d2a795 __NSStringDrawingEngine + 7348
6   UIFoundation                         0x00007fffd9d315ea -[NSAttributedString(NSExtendedStringDrawing) boundingRectWithSize:options:context:] + 605
7   UIFoundation                         0x00007fffd9d31efd -[NSAttributedString(NSExtendedStringDrawing) boundingRectWithSize:options:] + 32
8   AppKit                               0x00007fffc4e711cb -[NSAttributedString(NSStringDrawingExtension) _sizeWithSize:] + 55
9   AppKit                               0x00007fffc4e710ff -[NSButtonCell(NSButtonCellPrivate) _titleSizeWithSize:] + 97
10  AppKit                               0x00007fffc4e70eab -[NSButtonCell(NSButtonCellPrivate) _alignedTitleRectWithRect:] + 235
11  AppKit                               0x00007fffc4e25162 -[NSButtonCell cellSizeForBounds:] + 918
12  AppKit                               0x00007fffc4da2a21 -[NSCell cellSize] + 68
13  AppKit                               0x00007fffc4da295a -[NSControl sizeToFit] + 53
14  AppKit                               0x00007fffc520392c +[NSButton(NSButtonConvenience) _buttonWithTitle:image:target:action:] + 421
15  AppKit                               0x00007fffc5203a05 +[NSButton(NSButtonConvenience) buttonWithTitle:target:action:] + 199
16  Deliveries                           0x0000000103b4c655 +[JUNTouchBar cancelButtonItemWithIdentifier:] (JUNTouchBar.m:75)
17  Deliveries                           0x0000000103b67e9b -[JUNEditWindowController touchBar:makeItemForIdentifier:] (JUNEditWindowController.m:183)
18  AppKit                               0x00007fffc57d7d2a __32-[NSTouchBar itemForIdentifier:]_block_invoke + 34
19  AppKit                               0x00007fffc4e71bd0 +[NSAppearance _performWithCurrentAppearance:usingBlock:] + 79
20  AppKit                               0x00007fffc57d7b9b -[NSTouchBar itemForIdentifier:] + 1158
21  AppKit                               0x00007fffc57d868e -[NSTouchBar(NSEscapeKeyReplacementOld) escapeKeyReplacementItem] + 51
22  AppKit                               0x00007fffc5250e80 -[NSApplicationFunctionRowController _updateEscapeKeyItem] + 238
23  AppKit                               0x00007fffc5250f49 -[NSApplicationFunctionRowController _updateEscapeKeyItem] + 439
24  AppKit                               0x00007fffc5250f49 -[NSApplicationFunctionRowController _updateEscapeKeyItem] + 439
25  AppKit                               0x00007fffc5250f49 -[NSApplicationFunctionRowController _updateEscapeKeyItem] + 439
…
509 AppKit                               0x00007fffc5250f49 -[NSApplicationFunctionRowController _updateEscapeKeyItem] + 439
510 AppKit                               0x00007fffc5250f49 -[NSApplicationFunctionRowController _updateEscapeKeyItem] + 439
511 AppKit                               0x00007fffc5250f49 -[NSApplicationFunctionRowController _updateEscapeKeyItem] + 439

My touchBar:makeItemForIdentifier: method is just this:

- (nullable NSTouchBarItem *)touchBar:(NSTouchBar *)touchBar makeItemForIdentifier:(NSTouchBarItemIdentifier)identifier {

    if ([identifier isEqualToString:JUNTouchBarItemIdentifierCancel]) {
        NSCustomTouchBarItem *item = [JUNTouchBar cancelButtonItemWithIdentifier:identifier];
        return item;
    }
    return nil;

}

And here's cancelButtonItemWithIdentifier:, also pretty simple:

+ (NSCustomTouchBarItem *)cancelButtonItemWithIdentifier:(NSString *)identifier {
    NSString *title = NSLocalizedString(@"Cancel", nil);
    NSButton *button = [NSButton buttonWithTitle:title target:nil action:@selector(cancelOperation:)];
    NSLayoutConstraint *constraint = [NSLayoutConstraint constraintWithItem:button attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationGreaterThanOrEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1.0 constant:80.0];
    constraint.priority = 950;
    constraint.active = YES;
    NSCustomTouchBarItem *item = [[NSCustomTouchBarItem alloc] initWithIdentifier:identifier];
    item.view = button;
    return item;
}

Of course I can't reproduce this crash myself. It happens in macOS 10.12.2 through beta versions of 10.12.5. Many of the reports are from the same 13" model I'm testing on myself. The few people that include a comment with their crash report mostly say it's happening immediately after an action that would open a new window (thus changing the escape key)—one said it happened after an action that would close a window. One person mentioned in hanging for 10 seconds before crashing—which makes sense given the 500 calls to the same method. A couple of people have mentioned it happening more than once, but of course they didn't leave any contact information so I have no way to follow up with them.

I know I could work around the crash by removing my custom escape keys, but I'd prefer not to. Any other ideas on how I can deal with this?

Update: Since originally posting this, I've updated the app to remove the constraint, so my code for creating the button is about as simple as it can get now:

+ (NSCustomTouchBarItem *)cancelButtonItemWithIdentifier:(NSString *)identifier {
    NSString *title = JUNLocalizedString(@"Cancel", nil);
    NSButton *button = [NSButton buttonWithTitle:title target:nil action:@selector(cancelOperation:)];
    NSCustomTouchBarItem *item = [[NSCustomTouchBarItem alloc] initWithIdentifier:identifier];
    item.view = button;
    return item;
}

Unfortunately I'm still getting crash reports from the new version, with identical stack traces. So I don't think the issue is with the button I'm supplying.


Solution

  • Sorry not an answer, but too long for a comment.

    I built a debug version for our users that experienced crashes, that swizzles the _updateEscapeKeyItem method, in an attempt to pinpoint the crash origin, which isn't visible due to the bottom of the stack being blown away by the infinite recursion.

    My guess is they changed the 10.13 implementation radically (they mentioned something about "KVO running amok"), and might not look into 10.12 crashes unless they have a solid clue.

    This is the quick and dirty code in you can drop in (mostly borrowed from How to swizzle a method of a private class), that stops the infinite recursion. The resulting crash reports might be more useful.

    @interface NSObject (swizzle_touchbar_stuff)
    @end
    
    static NSInteger updateEscapeKeyItem = 0;
    
    @implementation NSObject (swizzle_touchbar_stuff)
    + (void)load
    {
        static dispatch_once_t onceToken = 0;
        dispatch_once(&onceToken, ^{
            Method original, swizzled;
    
            original = class_getInstanceMethod(objc_getClass("NSApplicationFunctionRowController"), NSSelectorFromString(@"_updateEscapeKeyItem"));
            swizzled = class_getInstanceMethod(self, @selector(sparkle_updateEscapeKeyItem));
            method_exchangeImplementations(original, swizzled);
        });
    }
    
    - (void)sparkle_updateEscapeKeyItem
    {
        updateEscapeKeyItem++;
        NSLog(@"sparkle_updateEscapeKeyItem %ld", updateEscapeKeyItem);
        assert(updateEscapeKeyItem < 10);
        [(NSObject *)self sparkle_updateEscapeKeyItem];
        updateEscapeKeyItem--;
    }
    
    @end