Assigning from a Class method binds the class instead of its return value

68 views Asked by At

I've noticed something strange in the behavior of code I'm currently writing and thought I would ask here to see if I'm doing something silly that would cause this to happen.

Basically, when I assign a variable to the return value of my class method, instead of the variable holding a reference to the return value, it's holding a reference to the class. See the code below:

NSArray * newAddresses = [MyHost addressesForHostname: @"google.com"];

Which has a method signature of

+ (NSArray *) addressesForHostname: (NSString *)hostname

And returns

return (__bridge_transfer NSArray *) ipAddresses; // ipAddresses is a CFMutableArrayRef

As you can see, I'm using toll-free bridging to use CoreFoundation objects as I'm collecting a list of IP addresses for some network interfaces.

After newAddresses has been assigned to, I look at the class of the newAddresses array in LLDB and get:

(lldb) po [newAddresses class]
MyHost

Am I mistaken in my assumptions about how I'm using __bridge_transfer? All of the objects use to make up ipAddresses are CFStringRefs.

EDIT: I was asked for the whole method, so here it is!

+ (NSArray *) addressesForHostname: (NSString *)hostname {

    CFMutableArrayRef ipAddresses;

    DLog(@"Getting addresses for host name %@", hostname);

    CFHostRef hostRef = CFHostCreateWithName(kCFAllocatorDefault, (__bridge CFStringRef)(hostname));
    CFStreamError error;

    BOOL didResolve = CFHostStartInfoResolution(hostRef, kCFHostNames, &error); // synchronously get the host.

    if (didResolve) {

        CFArrayRef responseObjects = CFHostGetAddressing(hostRef, NULL);
        long numberOfResponses = CFArrayGetCount(responseObjects);
        ipAddresses = CFArrayCreateMutable(kCFAllocatorDefault, numberOfResponses, &kCFTypeArrayCallBacks);

        for ( int i = 0 ; i < numberOfResponses; ++i ) {

            char * ipAddress;
            CFDataRef responseObject = CFArrayGetValueAtIndex(responseObjects, i);
            struct sockaddr * currentAddress = (struct sockaddr *) CFDataGetBytePtr(responseObject); // Unwrap the CFData wrapper aound the sockaddr struct

            switch (currentAddress->sa_family) {

                case AF_INET: { // Internetworking AKA IPV4

                    DLog(@"Extracting IPV4 address");
                    struct sockaddr_in * socketAddress = (struct sockaddr_in *) currentAddress;

                    ipAddress = malloc(sizeof(INET_ADDRSTRLEN));
                    inet_ntop(AF_INET,
                              &(socketAddress->sin_addr),
                              ipAddress,
                              INET_ADDRSTRLEN);

                    CFStringRef ipAddressString = CFStringCreateWithCString(kCFAllocatorDefault, ipAddress, kCFStringEncodingASCII);
                    CFArrayInsertValueAtIndex(ipAddresses, i, ipAddressString);

                    free(ipAddress);
                    break;

                }

                case AF_INET6: { // IPV6

                    DLog(@"Extracting IPV6 address");
                    struct sockaddr_in6 * socketAddress = (struct sockaddr_in6 *) currentAddress;

                    ipAddress = malloc(sizeof(INET6_ADDRSTRLEN));
                    inet_ntop(AF_INET6,
                              &(socketAddress->sin6_addr),
                              ipAddress,
                              INET6_ADDRSTRLEN);

                    CFStringRef ipAddressString = CFStringCreateWithCString(kCFAllocatorDefault, ipAddress, kCFStringEncodingASCII);
                    CFArrayInsertValueAtIndex(ipAddresses, i, ipAddressString);

                    free(ipAddress);
                    break;
                }

                default:
                    DLog(@"Unsupported addressing protocol encountered. Gracefully ignoring and continuing.");
                    break;
            }


        }

        CFRelease(responseObjects);

    }

    CFRelease(hostRef);

    return (__bridge_transfer NSArray *) ipAddresses;
}
1

There are 1 answers

0
Sean Michael Dorian On

So I found the solution, and it lies in me forgetting to initialize ipAddresses = nil before anything happens. The way this code is written, it won't assign a value to ipAddresses if it's unable to resolve the hostRef given to CFHostStartInfoResolution. With no value in ipAddresses, it returns an uninitialized pointer that gets casted and has its ownership transferred.

I can't find formal documentation that states this, but I believe this would be undefined behavior.

I should state that if anyone is using this code as reference, I'm experiencing inconsistent crashes on the line where I release hostRef. This is unrelated to the issue that I created for this thread, but is worthwhile to point out.