gnustep/libs-gui

NSAttributedString rangeOfTextList:atIndex: locks up on adjacent lists

fjmilens3 opened this issue · 1 comments

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.)

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