Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

NSAttributedString rangeOfTextList:atIndex: locks up on adjacent lists #295

Open
fjmilens3 opened this issue Sep 23, 2024 · 1 comment
Open

Comments

@fjmilens3
Copy link

The current rangeOfTextList:atIndex: implementation will lock up when two paragraphs with different styles and different textlists are adjacent. I found this while looking into #294 (the issue is far more likely to surface when #294 is fixed because attribute ranges aren't incorrectly merged). A fix for #293 would also likely depend on this being fixed.

The following example will reproduce the issue:

- (void) causeRangeOfTextListProblem 
{
  // Create two lists 
  NSTextList *list1 = [[NSTextList alloc] initWithMarkerFormat: @"{decimal}" options: 0];
  NSTextList *list2 = [[NSTextList alloc] initWithMarkerFormat: @"{box}" options: 0];
  
  // Create two paragraph styles
  NSMutableParagraphStyle *style1 = [[NSParagraphStyle defaultParagraphStyle] mutableCopy];
  NSMutableParagraphStyle *style2 = [[NSParagraphStyle defaultParagraphStyle] mutableCopy];
  
  // Change the paragraph styles so that they don't get merged together (equality testing problems)
  [style1 setHeadIndent: 36.0];
  [style2 setHeadIndent: 72.0];
  
  // Use one list per paragraph style
  [style1 setTextLists: [NSArray arrayWithObject: list1]];
  [style2 setTextLists: [NSArray arrayWithObject: list2]];

  // Create an attributed string for each list
  NSAttributedString *str1 = [[NSAttributedString alloc] 
    initWithString: [NSString stringWithFormat: @"%@ list1\n", [list1 markerForItemNumber: 1]] 
    attributes: [NSDictionary dictionaryWithObject: style1 forKey: NSParagraphStyleAttributeName]];
  NSAttributedString *str2 = [[NSAttributedString alloc] 
    initWithString: [NSString stringWithFormat: @"%@ list2\n", [list2 markerForItemNumber: 1]] 
    attributes: [NSDictionary dictionaryWithObject: style2 forKey: NSParagraphStyleAttributeName]];

  // Append the attributed strings to the text storage
  NSTextStorage *textStorage = [textView textStorage];
  [textStorage appendAttributedString: str1];
  [textStorage appendAttributedString: str2];
  
  // Attempt to find the list range for one of the two adjacent lists
  // This causes a problem and the call will not return
  NSLog(@"Preparing to find list range...");
  
  NSRange range = [textStorage rangeOfTextList: list2 atIndex: [textStorage length] - 5];

  NSLog(@"Found range %@", NSStringFromRange(range));
}

I'm guessing it's getting stuck in an infinite loop in one of two places in the code. Perhaps not finding the specified list in the current textLists should also be a terminating condition for the loops: What if we get the style and the textLists, but don't advance the effective range because the list we have isn't included in the textLists, so the loop repeats forever.

- (NSRange) rangeOfTextList: (NSTextList *)list
atIndex: (NSUInteger)location
{
NSRange effRange;
NSParagraphStyle *style = [self attribute: NSParagraphStyleAttributeName
atIndex: location
effectiveRange: &effRange];
if (style != nil)
{
NSArray *textLists = [style textLists];
if ((textLists != nil) && [textLists containsObject: list])
{
NSRange newEffRange;
NSUInteger len = [self length];
while ((effRange.location > 0) && style && textLists)
{
style = [self attribute: NSParagraphStyleAttributeName
atIndex: effRange.location - 1
effectiveRange: &newEffRange];
if (style != nil)
{
textLists = [style textLists];
if ((textLists != nil) && [textLists containsObject: list])
{
effRange.location = newEffRange.location;
effRange.length += newEffRange.length;
}
}
}
while (NSMaxRange(effRange) < len && style && textLists)
{
style = [self attribute: NSParagraphStyleAttributeName
atIndex: NSMaxRange(effRange)
effectiveRange: &newEffRange];
if (style != nil)
{
textLists = [style textLists];
if ((textLists != nil) && [textLists containsObject: list])
{
effRange.length += newEffRange.length;
}
}
}
return effRange;
}
}
return NSMakeRange(NSNotFound, 0);
}

Similar implementations exist for text blocks and text tables which may have the same problem, but I haven't looked at those at all.

(I can try to put together a PR for these issues if there's interest in your end, but I just installed GNUStep a couple of days ago and don't have a proper build/development set up yet.)

@fredkiefer
Copy link
Member

This sounds like a serious issue, having a PR for it would be great.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

No branches or pull requests

2 participants