-1

I've got the following code that inserts a new NSAttributedString into NSTextView. This works fine on all versions of macOS 10.14+, however when the NSTextView is empty, the following code causes an immediate crash when inserting any amount of string. I've tried all variants of trying to replace / append string, but it seems internally NSTextView uses replaceCharactersInRange which seems to be the causes of the crash.

This is a method added to an extension / category on NSTextView:

- (void) insertAttributedString:(nonnull NSAttributedString *)attribString {
  NSTextStorage *textStorage = [self textStorage];
  NSRange selectedRange = [self selectedRange];
  if (selectedRange.location == NSNotFound) {
    selectedRange = NSMakeRange([textStorage length], 0);
  }

  if ([self shouldChangeTextInRange:selectedRange replacementString:[attribString string]]) {
    [textStorage beginEditing];
    [textStorage replaceCharactersInRange:selectedRange withAttributedString:attribString];
    [textStorage endEditing];

    if ([self respondsToSelector:@selector(didChangeText)]) {
      [self didChangeText];
    }
  }
}

The exception thrown is always the following:

Exception Type:  SIGSEGV
Exception Codes: SEGV_MAPERR at 0x18
Crashed Thread:  0

Thread 0 Crashed:
0   CoreFoundation                       0x00007fffaf21334a CFStorageInsertValues + 26
1   Foundation                           0x00007fffb0c49559 -[NSBigMutableString replaceCharactersInRange:withString:] + 1093
2   Foundation                           0x00007fffb0c2ce1b -[NSConcreteMutableAttributedString replaceCharactersInRange:withString:] + 336
3   UIFoundation                         0x00007fffc1ece41c __NSConcreteTextStorageLockedForwarding + 48
4   UIFoundation                         0x00007fffc1ece3e5 -[NSConcreteTextStorage replaceCharactersInRange:withString:] + 79

Any ideas on how to get around this on macOS 10.12 / 10.13? The only way I was able to prevent it from crashing was if I was to assign a newly allocated NSTextStorage when NSTextView is empty - however this then causes the app to crash when undoing the change. I don't think that's the correct approach.

strangetimes
  • 4,953
  • 1
  • 34
  • 62
  • I tried your code and don't see the crash, from where is `replaceCharactersInRange:withString:` called? Post a [mre] please. – Willeke Jun 24 '21 at 21:34
  • A button press - trying to insert a formatted "date" into a `NSTextView`. For it to crash, I need to try this multiple times rapidly or by switching between views. I've spent hours diagnosing this and have been unable to figure out why - and why only 10.13 below. I think I've been able to *just* find an alternative, using `[[textStorage mutableString] setString: ...]` instead. Seems to work and no more crashes. – strangetimes Jun 24 '21 at 21:37
  • It's probably also due to the fact we use a custom field editor, but I've tried disabling that part of the code and it would still crash. It's a very large project and so it could be other factors I'm unable to determine. Either way, the crashing most notably started when calling this code from "Swift". – strangetimes Jun 24 '21 at 21:39
  • 1
    @OlSen don't think that matters, it's Obj-C and passing a message to `nil` is a no-op. Also, it's never `nil`. – strangetimes Jun 24 '21 at 21:42
  • Where does a guy named attribString come from? – El Tomato Jun 24 '21 at 22:15
  • @ElTomato added the method signature to the question, it's passed as a `nonnull` parameter. – strangetimes Jun 24 '21 at 22:17
  • "when the NSTextView is empty, the following code causes an immediate crash when inserting any amount of string" You've already said yourself what you need to do. – El Tomato Jun 24 '21 at 22:17
  • @ElTomato No I haven't. Empty simply means the text view was initialized with an empty `NSAttributedString`. And like I said, this does not crash on macOS 10.14 onwards. – strangetimes Jun 24 '21 at 22:18

1 Answers1

0

I don't have a reason why the following works, but it does. It now does not crash on macOS 10.13 and below:

- (void) insertAttributedString:(NSAttributedString *)attribString {
  NSTextStorage *textStorage = [self textStorage];
  NSRange selectedRange = [self selectedRange];
  if (selectedRange.location == NSNotFound) {
    selectedRange = NSMakeRange([textStorage length], 0);
  }

  if ([self shouldChangeTextInRange:selectedRange replacementString:[attribString string]]) {
    [textStorage beginEditing];
    
    if ([textStorage length] == 0) {
      [[textStorage mutableString] setString:attribString.string];
    }
    else {
      [textStorage replaceCharactersInRange:selectedRange withAttributedString:attribString];
    }
    [textStorage endEditing];

    if ([self respondsToSelector:@selector(didChangeText)]) {
      [self didChangeText];
    }
  }
}

Notably, I've switched to using [[textStorage mutableString] setString:attribString.string];

strangetimes
  • 4,953
  • 1
  • 34
  • 62
  • 1
    If the receiver is nil, it' a no-op. Clearly the receiver is not nil either way since otherwise `mutableString` would fail. – strangetimes Jun 24 '21 at 21:46
  • It would be if the receiver is nil :) Passing messages to nil in Obj-C does not lead to a crash. The only scenario where it would crash is if the passed in parameter `attribString` is nil - which it isn't (the header declares it as `nonnull` and it's checked a the calling site). – strangetimes Jun 24 '21 at 21:49