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;
}
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 toipAddresses
if it's unable to resolve thehostRef
given toCFHostStartInfoResolution
. With no value inipAddresses
, 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.