Guillaume
Guillaume

Reputation: 21736

AddressBook - Crash in iOS app, ARC enabled, near CF objects manipulation

I have a crash, very likely related to memory management that I can not spot.

Crash never happened on me, I only know it's occurring because of crash reports I have received.
This also means the only current way I have to confirm crash is fixed is to ship the app and wait for crash reports to come in (bad news) or not (happy me!).

Extract of crash report:

Exception Type:  SIGSEGV
Exception Codes: SEGV_ACCERR at 0x9
Crashed Thread:  0

Thread 0 Crashed:
0   CoreFoundation                      0x375f29e8 0x375e4000 + 59880
1   MyApp                            0x000bf22f -[UIViewController(AddressPicker) fullNameAndAddressFromPerson:identifier:] (UIViewController+AddressPicker.m:108)
2   MyApp                            0x000bf3f9 -[UIViewController(AddressPicker) guessedUserAddress] (UIViewController+AddressPicker.m:161)
3   MyApp                            0x0009cc99 -[RecipientsViewController loadUserAddressFromMyAppSender] (RecipientsViewController.m:190)
4   MyApp                            0x0009c189 -[RecipientsViewController viewDidLoad] (RecipientsViewController.m:78)
5   UIKit                               0x31a5e541 0x31a3c000 + 140609

Original Code:

- (NSString *) fullNameAndAddressFromPerson:(ABRecordRef) person identifier:(ABMultiValueIdentifier) identifier {
    NSMutableString *address = [NSMutableString new];

    // Get and add first and last name
    CFStringRef cfFirstName = ABRecordCopyValue(person, kABPersonFirstNameProperty);
    NSString *firstName = (NSString *)CFBridgingRelease(cfFirstName);

    CFStringRef cfLastName = ABRecordCopyValue(person, kABPersonLastNameProperty);
    NSString *lastName = (NSString *)CFBridgingRelease(cfLastName);

    [address appendFormat:@"%@ %@\n", firstName ?: @"", lastName ?: @""];


    // Get and add address
    ABMultiValueRef addressMultiValue = ABRecordCopyValue(person, kABPersonAddressProperty);

    CFTypeRef addressRef = ABMultiValueCopyValueAtIndex(addressMultiValue, ABMultiValueGetIndexForIdentifier(addressMultiValue, identifier)); // This is line 108
    CFRelease(addressMultiValue);

    NSDictionary *addressDictionary = (NSDictionary *) (CFBridgingRelease(addressRef));

    if ([addressDictionary isKindOfClass:NSDictionary.class]) {
        [address appendString:ABCreateStringWithAddressDictionary(addressDictionary, YES)];
    }

    return [address stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
}

Updated code, including checks suggested by Nirav:

- (NSString *) fullNameAndAddressFromPerson:(ABRecordRef) person identifier:(ABMultiValueIdentifier) identifier {
    NSMutableString *address = [NSMutableString new];
    NSString *firstName, *lastName;

    // Get and add first and last name
    if (person) {
        CFStringRef cfFirstName = ABRecordCopyValue(person, kABPersonFirstNameProperty);
        if (cfFirstName) {
            firstName = (NSString *)CFBridgingRelease(cfFirstName);
        }

        CFStringRef cfLastName = ABRecordCopyValue(person, kABPersonLastNameProperty);

        if (cfLastName) {
            lastName = (NSString *)CFBridgingRelease(cfLastName);
        }
    }

    [address appendFormat:@"%@ %@\n", firstName ?: @"", lastName ?: @""];


    // Get and add address
    ABMultiValueRef addressMultiValue = ABRecordCopyValue(person, kABPersonAddressProperty);

    CFTypeRef addressRef;
    if (addressMultiValue && identifier) {
        addressRef = ABMultiValueCopyValueAtIndex(addressMultiValue, ABMultiValueGetIndexForIdentifier(addressMultiValue, identifier));
        CFRelease(addressMultiValue);
    }

    NSDictionary *addressDictionary;

    if (addressRef) {
        addressDictionary = (NSDictionary *) (CFBridgingRelease(addressRef));

        if ([addressDictionary isKindOfClass:NSDictionary.class]) {
            [address appendString:ABCreateStringWithAddressDictionary(addressDictionary, YES)];
        }
    }

    return [address stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
}

I guess I'm missing the obvious, so instead of me just replying "duh, you are right", I will accept the answer that likely fix the bug and point out ways this code can be made safer and improved.

Upvotes: 2

Views: 1011

Answers (1)

Nirav Bhatt
Nirav Bhatt

Reputation: 6969

I faced similar crash doing similar thing and here is what I did:

  • Do null check for every CFStringRef before using them - here: cfFirstName, cfLastName
  • Do null check for every ABMultiValueRef being returned - here: addressMultiValue

And so on. The gist is to perform null check before bridging macros.

This is to handle phonebook records that don't have certain fields populated, that's it.

Upvotes: 1

Related Questions