views:

181

answers:

3

Hi all,

I am getting an infuriating EXC_BAD_ACCESS error in an objective c app I am working on. Any help you could offer would be much appreciated. I have tried the normal debug methods for this error (turning on NSZombieEnabled, checking retain/release/autorelease to make sure I'm not trying to access a deallocated object, etc.) and it hasn't seemed to help.

Basically, the error always occurs in this function:

void op_TJ(CGPDFScannerRef scanner, void *info)
{
    PDFPage *self = info;
    CGPDFArrayRef array;

    NSMutableString *tempString = [NSMutableString stringWithCapacity:1];
    NSMutableArray *kernArray = [[NSMutableArray alloc] initWithCapacity:1];

    if(!CGPDFScannerPopArray(scanner, &array)) {
        [kernArray release];
        return;
    }

    for(size_t n = 0; n < CGPDFArrayGetCount(array); n += 2)
    {
        if(n >= CGPDFArrayGetCount(array))
            continue;

        CGPDFStringRef pdfString;

        // if we get a PDF string
        if (CGPDFArrayGetString(array, n, &pdfString))
        {
            //get the actual string
            const unsigned char *charstring = CGPDFStringGetBytePtr(pdfString);

            //add this string to our temp string
            [tempString appendString:[NSString stringWithCString:(const    char*)charstring encoding:[self pageEncoding]]];
            //NSLog(@"string: %@", tempString);

            //get the space after this string
            CGPDFReal r = 0;
            if (n+1 < CGPDFArrayGetCount(array)) {
                CGPDFArrayGetNumber(array, n+1, &r);

                // multiply by the font size
                CGFloat k = r;
                k = -k/1000 * self.tmatrix.a * self.fontSize;


                CGFloat kKern = self.kern * self.tmatrix.a;
                k = k + kKern;

                // add the location and kern to the array
                NSNumber *tempKern = [NSNumber numberWithFloat:k];
                NSLog(@"tempKern address: %p", tempKern);
                [kernArray addObject:[NSArray arrayWithObjects:[NSNumber numberWithInt:[tempString length] - 1], tempKern, nil]];

            }
        }
    }

    // create an attribute string
    CFMutableAttributedStringRef attString =    CFAttributedStringCreateMutable(kCFAllocatorDefault, 10);

    CFAttributedStringReplaceString(attString, CFRangeMake(0, 0), (CFStringRef)tempString);

    //apply overall kerning
    NSNumber *tkern = [NSNumber numberWithFloat:self.kern * self.tmatrix.a * self.fontSize];
    CFAttributedStringSetAttribute(attString, CFRangeMake(0, CFAttributedStringGetLength(attString)), kCTKernAttributeName, (CFNumberRef)tkern);

    //apply individual kern attributes
    for (NSArray *kernLoc in kernArray) {
        NSLog(@"kern location: %i, %i", [[kernLoc objectAtIndex:0] intValue],[[kernLoc objectAtIndex:1] floatValue]);
        CFAttributedStringSetAttribute(attString, CFRangeMake([[kernLoc objectAtIndex:0] intValue], 1), kCTKernAttributeName, (CFNumberRef)[kernLoc objectAtIndex:1]);
    }

    CFAttributedStringReplaceAttributedString([self cfAttString], CFRangeMake(CFAttributedStringGetLength([self cfAttString]), 0), attString);

    //release
    CFRelease(attString);
    [kernArray release];


}

The program always crashes because of line

CFAttributedStringSetAttribute(attString, CFRangeMake([[kernLoc objectAtIndex:0] intValue], 1), kCTKernAttributeName, (CFNumberRef)[kernLoc objectAtIndex:1])

And it seems to depend on a few things:

  1. if [kernLoc objectAtIndex:1] refers to an [NSNumber numberWithFloat:k] where k = 0 (in other words, if k = 0 above where I populate kernArray) then the program crashes almost immediately

  2. If I comment out the line k = k + kKern, it takes longer for the program to crash, but does eventually (why would the crash depend on this value?)

  3. If I change the length of CFRangeMake from 1 to 0, it takes a lot longer for the program to crash, but still eventually does. (I don't think I am trying to access beyond the bounds of attString, but am I missing something?)

When it crashes, I get something similar to:

#0  0x942c7ed7 in objc_msgSend ()
#1  0x00000013 in ?? ()
#2  0x0285b827 in CFAttributedStringSetAttribute ()
#3  0x0000568f in op_TJ (scanner=0x472a590, info=0x4a32320) at /Users/Richard/Desktop/AppTest/PDFHighlight 2/PDFScannerOperators.m:251

Any ideas? It seems like somewhere along the way I am overwriting memory or trying to access memory that has been changed, but I have no idea. If there's anymore information I can provide, please let me know.

Thanks, Richard

A: 

Disregard this answer:

drawnonward
Where did you learn this? It is very wrong. The documentation for `initWithCapacity:` says "Mutable arrays expand as needed; numItems simply establishes the object’s initial capacity."
dreamlax
Hi drawnonward - Thanks for your reply. I did consider that and I tried capacity of zero and a larger capacity (10) to see if it made a difference, but it still crashes at the same point.
RichardR
@dreamlax it has been years since I looked at this. Either it changed or I have been wrong for years. Learn something new ...
drawnonward
@dreamlax I took a look and prior to 10.5 the capacity was fixed if 0 was not passed. It never applied to iPhone.
drawnonward
@drawnonward: I don't think that is true; I ran a test case on 10.4 adding 10000 items to an array with capacity 1, and it still worked. I think you could be thinking of Core Foundation CFMutableArrays where the capacity does indicate the maximum size, and adding values beyond its capacity is undefined.
dreamlax
@dreamlax I do not have any old documentation for NSArray, only the CFArray header. I usually think of them as interchangeable. I'll take your word for it.
drawnonward
+1  A: 

Your crash looks like the result of an over release somewhere. It's difficult to track down what the issue might be, particularly as you are mixing core foundation with Cocoa. The memory management rules are different for core foundation.

I think I would pull all of the references to data out of CFAttributedStringSetAttribute so that I could NSLog them or inspect them with the debugger, like this:

NSNumber* rangeStart = [kernLoc objectAtIndex:0];      // Debug: make sure it is a number
NSNumber attrValue = [kernLoc objectAtIndex:1];        // Debug: make sure it is a number
CFRange range = CFRangeMake([rangeStart intValue], 1); // Debug: make sure it is a valid range for the string
CFAttributedStringSetAttribute(attString, range, kCTKernAttributeName, (CFNumberRef)attrValue)
JeremyP
Hi JeremyP, thanks for the suggestion. I tried this and found that all values were correct before going into CFAttributedStringSetAttribute (and range is w/n bounds). I experimented with values and found that a crash depends on the value of attrValue AND if attrValue=tkern (the other kern value already applied) the program crashes. I created a simple test program with a CFMutableAttributedString, tried overwriting the same attribute with the same value, and it crashed! I don't want to be presumptuous about my coding skill, but this seems like a bug in Apple's function. Could that be the case?
RichardR
When you say 'the same value' do you mean the exact same CFNumberRef? OR a different number ref with the same value? It kind of looks like the old attrValue is being released *before* being replaced by the new attrValue which could be a disaster if they were the same object. So it certainly looks like a bug, but that might just mean we have both missed something.
JeremyP
Try bracketing the call to CFAttributedStringSetAttribute with a call to CFRetain on the attribute value before the call and a call to CFRelease on the attribute value after the call.
JeremyP
A: 

Follow up - it seems like CFAttributedStringSetAttributes (plural) is the way to go. I still need to implement it into my program, but in a small test, this function allows me to overwrite previously defined values without getting an EXC_BAD_ACCESS crash. Would have been nice if this was documented somewhere. Hope this helps other people out there. Thanks!

RichardR