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.
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];
Related
I am new to objective C programming.
I created a PygLatin application but it keeps crashing it. Do tell me if you have spotted any problems with my code.
Terminating app due to uncaught exception 'NSRangeException', reason: ' -[__NSArrayM objectAtIndex:]: index 5 beyond bounds [0 .. 4]'
First throw call stack:
would appreciate all the help I can get thanks! :)
#implementation RootViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.userInputTextField.delegate = self;
self.userInputTextField.text = #"";
}
-(void)print: (NSString*)printWords {
self.resultLabel.text = #"%#", printWords;
}
-(void)charsplitter: (NSArray*)charArraysplitter {
//word selection loop
int indexCount = 0;
for(indexCount = 0; indexCount < charArraysplitter.count; indexCount++)
{
self.wordsToBeSplit = charArraysplitter[indexCount];
NSMutableArray *characters = [[NSMutableArray alloc] initWithCapacity:[self.wordsToBeSplit length]];
[self.wordsToBeSplit enumerateSubstringsInRange:NSMakeRange(0, self.wordsToBeSplit.length)
options:NSStringEnumerationByComposedCharacterSequences
usingBlock:^(NSString *substring, NSRange substringRange, NSRange enclosingRange, BOOL *stop) {
[characters addObject:substring];
}];
[self vowelsComparator:characters];
}
}
-(void)vowelsComparator: (NSArray*)comparator {
self.myVowels = [NSArray arrayWithObjects: #"a", #"e", #"i", #"o",#"u", nil];
int charIndex;
self.subsequentCharCount = 0;
for(charIndex = 0; charIndex < comparator.count; charIndex++)
//loops to find if first character is a vowel
if([self.myVowels containsObject:comparator[0]]){
[self print: self.userInputTextField]; NSLog(#"working fine:");
}else{
//loops to find other vowels
while (self.subsequentCharCount < comparator.count){
self.subsequentCharCount++;
if ([self.myVowels containsObject:comparator[self.subsequentCharCount]]){
//moving the consonants behing the vowels
NSLog(#"working fine:");
NSString *combinedWords = [[self.wordsToBeSplit substringFromIndex:self.subsequentCharCount]stringByAppendingString:[self.wordsToBeSplit substringToIndex:self.subsequentCharCount]];
NSString *completedWord = [combinedWords stringByAppendingString:#"ay"];
[self print: completedWord];
}
};
}
}
-(BOOL)textFieldShouldReturn:(UITextField *)textField{
[textField resignFirstResponder];//resigns the keyboard every time return button is pressed
return YES;
}
- (IBAction)pygButton:(id)sender
{
self.inputText = [self.userInputTextField.text lowercaseString];//user input is lowercase
NSArray *wordsArray = [self.inputText componentsSeparatedByString: #" "]; //separate words into arrays.
[self charsplitter: wordsArray];
}
#end
put
self.subsequentCharCount++;
at the end of while loop means below
[self print: completedWord];
I'm trying to use an NSLinguisticTagger to monitor the contents of an NSTextStorage and provide some contextual information based on what the user types. To that end, I have an OverlayManager object, which wires up this relationship:
-(void) setView:(NSTextView*) view {
_view = view;
_layout = view.layoutManager;
_storage = view.layoutManager.textStorage; //get the TextStorage from the view
[_tagger setString:_storage.string]; //pull the string out, this grabs the mutable version
[self registerForNotificationsOn:self->_storage]; //subscribe to the willProcessEditing notification
}
When an edit occurs, I make sure to trap it and notify the tagger (and yes, I know I'm being annoyingly inconsistent with member access, I'm rusty on Obj-C, I'll fix it later):
- (void) textStorageWillProcessEditing:(NSNotification*) notification{
if ([self->_storage editedMask] & NSTextStorageEditedCharacters) {
NSRange editedRange = [self->_storage editedRange];
NSUInteger delta = [self->_storage changeInLength];
[_tagger stringEditedInRange:editedRange changeInLength:delta]; //should notify the tagger of the changes- if I replace this line with [_tagger setString:[_storage string]] it works, but gobbles CPU as it retags the entire string
[self highlightEdits:self];
}
}
The highlightEdits message delegates the job out to a pool of "Overlay" objects. Each contains a block of code similar to this:
[tagger enumerateTagsInRange:range scheme:NSLinguisticTagSchemeLexicalClass options:0 usingBlock:^(NSString *tag, NSRange tokenRange, NSRange sentenceRange, BOOL *stop) {
if (tag == PartOfSpeech) {
[self applyHighlightToRange:tokenRange onStorage:storage];
}
}];
And that's where the problem is- the enumerateTagsInRange method crashes out with a message:
2014-06-04 10:07:19.692 WritersEditor[40191:303] NSMutableRLEArray replaceObjectsInRange:withObject:length:: Out of bounds
This problem doesn't occur if I don't link to the mutable copy of the underlying string and instead do a [[_storage string] copy], but obviously I don't want to copy the entire backing store every time I want to do tagging. This all should be happening in the main run loop, so I don't think this is a threading issue. The NSRange I'm enumerating tags on exists both in the NSTextStorage and in the NSLinguisticTagger's view of the string. It's not even the fact that the applyHighlightToRange call adds attributes to the string, because it crashes before even reaching that line.
I attempted to build a test case around the problem, but can't replicate it in those situations:
- (void) testEdit
{
NSAttributedString* str = [[NSMutableAttributedString alloc] initWithString:#"Quickly, this is a test."];
text = [[NSTextStorage alloc] initWithAttributedString:str];
NSArray* schemes = [NSLinguisticTagger availableTagSchemesForLanguage:#"en"];
tagger = [[NSLinguisticTagger alloc] initWithTagSchemes:schemes options:0];
[tagger setString:[text string]];
[text beginEditing];
[[text mutableString] appendString:#"T"];
NSRange edited = [text editedRange];
NSUInteger length = [text changeInLength];
[text endEditing];
[tagger stringEditedInRange:edited changeInLength:length];
[tagger enumerateTagsInRange:edited scheme:NSLinguisticTagSchemeLexicalClass options:0 usingBlock:^(NSString *tag, NSRange tokenRange, NSRange sentenceRange, BOOL *stop) {
//doesn't matter, this should crash
}];
}
That code doesn't crash.
Why does this code crash in 64 bit? - It works fine 32 bit.
It's loading a nib for a UIView
- (id) awakeAfterUsingCoder:(NSCoder*)aDecoder {
BOOL theThingThatGotLoadedWasJustAPlaceholder = ([[self subviews] count] == 0);
if (theThingThatGotLoadedWasJustAPlaceholder) {
ALTimelineView* theRealThing = [[self class] loadFromNib];
// pass properties through
theRealThing.frame = self.frame;
theRealThing.autoresizingMask = self.autoresizingMask;
theRealThing.alpha = self.alpha;
theRealThing.hidden = self.hidden;
[theRealThing internalInit];
// convince ARC that we're legit
CFRelease((__bridge const void*)self);
CFRetain((__bridge const void*)theRealThing);
return theRealThing;
}
return self;
}
loadFromNib
+ (id) loadFromNib {
NSString* nibName = NSStringFromClass([self class]);
NSArray* elements = [[NSBundle mainBundle] loadNibNamed:nibName owner:nil options:nil];
for (NSObject* anObject in elements) {
if ([anObject isKindOfClass:[self class]]) {
return anObject;
}
}
return nil;
}
The error I'm getting is EXC_BAD_ACCESS (code=EXC_I386_GPFLT)
EXC_I386_GPFLT is surely referring to "General Protection fault", which is the x86's way to tell you that "you did something that you are not allowed to do". It typically DOESN'T mean that you access out of memory bounds, but it could be that your code is going out of bounds and causing bad code/data to be used in a way that makes for an protection violation of some sort.
check this Reference Link for good explanation of EXC_I386_GPFLT
Long time listener, first time caller here so forgive me if I am not being explicit enough. I am currently scratching my head over an issue with NSError objects. Here is how things are setup:
-(void)mainMethod {
NSError *myError = nil;
NSString *myString = #"A really great string. 1234.";
if ([self parseAndStoreString:myString error:&myError] == NO) {
[self popupWithErrorMessage:[[myError localizedDescription] copy]];
}
-(BOOL)parseAndStoreString:(NSString *)incomingString error:(NSError *__autoreleasing *)err {
if ([self setupSomething error:err] == NO) {
return NO;
}
if ([self doSomethingWithString:incomingString error:err] == NO) {
return NO;
}
if ([self storeString:incomingString error:err] == NO) {
return NO;
}
}
-(BOOL)doSomethingWithString:(NSString *)incomingString error:(NSError *__autoreleasing *)err {
if (incomingString == nil) {
DLog(#"String was null! See? : %#", incomingString);
NSDictionary *errorInfo = [NSDictionary dictionaryWithObject:EDEmptyStringErrorDescription forKey:NSLocalizedDescriptionKey];
if (err != nil) *err = [NSError errorWithDomain:EDStringDomain code:EDEmptyStringError userInfo:errorInfo];
return NO;
}
if (somethingElseIsWrongWithString) {
DLog(#"Another Problem Geesh");
NSDictionary *errorInfo = [NSDictionary dictionaryWithObject:EDAnotherStringErrorDescription forKey:NSLocalizedDescriptionKey];
if (err != nil) *err = [NSError errorWithDomain:EDStringDomain code:EDAnotherStringError userInfo:errorInfo];
return NO;
}
}
Now whichever error hits is being properly established and displayed in the popup as expected the first time through. The problem comes in when the user clicks on something that activates either the 'mainMethod' again or another method that calls 'parseAndStoreString:error:' (there are three or four that call the method). If there is another error, the popup is showing the description for the first error and the second error glued together into one string. This is ARC code, so my understanding is that the compiler should be releasing the myError object (or whatever NSError object the other methods are creating) after the error is presented. I don't think the problem is in the 'popupWithErrorMessage' method since I only pass in a copy of the localized description which gets displayed and then destroyed by [popupView orderOut:self]. Any ideas on how the heck these error messages keep piling up in the NSError object?
#torrey.lyons : Sure, here is the code for togglePopupWithMessage:onView-
- (void)popupWithErrorMessage:(NSString *)errorToDisplay {
if (!messagePopup) {
if (errorToDisplay == nil) {
DLog(#"Warning, incoming error message was nil");
return;
}
NSDictionary *messageAttributes = [NSDictionary dictionaryWithObjectsAndKeys:
[NSColor whiteColor], NSForegroundColorAttributeName,
[NSFont systemFontOfSize:12], NSFontAttributeName,
nil];
NSAttributedString *messageWithAttributes = [[NSAttributedString alloc]
initWithString:errorToDisplay
attributes:messageAttributes];
NSPoint messagePoint = NSMakePoint(NSMidX([[self.mainSplitView.subviews objectAtIndex:1] frame]),
NSMinY([anchorView frame])+4.0f);
messagePopup = [[MAAttachedWindow alloc] initWithView:popupView
attachedToPoint:messagePoint
inWindow:[self window]
onSide:MAPositionBottom
atDistance:0];
[messagePopup setAlphaValue:0.0f];
NSTextStorage *messageStorage = popupMessage.textStorage;
[messageStorage beginEditing];
[messageStorage insertAttributedString:messageWithAttributes atIndex:0];
[messageStorage endEditing];
[messagePopup setFrame:NSMakeRect(messagePopup.frame.origin.x, messagePopup.frame.origin.y, 250.0f, 100.0f)
display:YES];
messageWithAttributes = nil;
messageAttributes = nil;
errorToDisplay = #"";
[[self window] addChildWindow:messagePopup ordered:NSWindowAbove];
[[messagePopup animator] setAlphaValue:1.0f];
[popupMessage setNeedsDisplay:YES];
[NSTimer scheduledTimerWithTimeInterval:3.5
target:self
selector:#selector(turnOffPopupFromTimer:)
userInfo:nil
repeats:NO];
} else {
[[self window] removeChildWindow:messagePopup];
[messagePopup fadeOutAndOrderOut:YES];
messagePopup = nil;
}
- (void)turnOffPopupFromTimer:(NSTimer *)timer {
if (messagePopup) {
//Added to correct problem \/\/\/\/
NSTextStorage *messageStorage = popupMessage.textStorage;
[messageStorage beginEditing];
[messageStorage deleteCharactersInRange:NSMakeRange(0, messageStorage.characters.count)];
[messageStorage endEditing];
//Added to correct problem /\/\/\/\
[[self window] removeChildWindow:messagePopup];
[messagePopup fadeOutAndOrderOut:YES];
messagePopup = nil;
}
}
I suspect the problem is in popupWithErrorMessage, which is piling up the localized descriptions. Even if there is a bug in the lifecycle of your NSError objects, each one created for a separate error is a separate object and won't have the localized description of other NSError objects stuffed into it. On the other hand, your description of popupWithErrorMessage sounds suspicious: [popupView orderOut:self] just removes the window from the screen but does not release it or cause it to be destroyed. Can you post the code for popupWithErrorMessage?
I'm subclassing NSNumberFormatter to allow the currency symbol to be optional. Here's my code:
- (BOOL)getObjectValue:(out id *)anObject forString:(NSString *)aString range:(inout NSRange *)rangep error:(out NSError **)error {
if (rangep == NULL){
NSRange newRange = NSMakeRange(0, [aString length]);
rangep = &newRange;
}
NSString *newStr = [aString stringByReplacingOccurrencesOfString:[self currencySymbol]
withString:#""
options:0
range:*rangep];
if (![newStr isEqualToString:aString]) {
NSRange newRange;
newRange.location = ((NSRange)*rangep).location;
newRange.length = ((NSRange)*rangep).length;
newRange.length -= ([aString length] - [newStr length]);
rangep = &newRange;
}
return [super getObjectValue:anObject
forString:newStr
range:rangep
error:error];
}
On Mac OS X 10.6 and higher, this code works absolutely fine. However, on 10.5.x, the code fails — the call to [super getObjectValue:...] fails, and the error returned is the rather generic "NSFormattingError" (code 2048).
Does anyone know what's going on?
The formatter is set to generate decimal numbers.