Crashes on the Toll-Free Bridge

This thread has been locked by a moderator.

Over the years I’ve helped a lot of folks investigate a lot of crashes. In some cases those crashes only make sense if you know a little about how Foundation and Core Foundation types are toll-free bridged. This post is my attempt to explain that.

If you have questions or comments, please put them in a new thread. Tag it with Foundation and Debugging so that I see it.

Share and Enjoy

Quinn “The Eskimo!” @ Devel oper Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"


Crashes on the Toll-Free Bridge

Certain Core Foundation (CF) types are toll-free bridged to their Foundation equivalent. That allows you to pass the Foundation object to a CF routine and vice versa [1]. For example, NSData and CFData are toll-free bridged, allowing you to pass an NSData object to a CF routine like CFDataGetBytePtr. For more information on this topic, see Toll-Free Bridged Types within Core Foundation Design Concepts in the Documentation Archive.

This is cool, but it does present some interesting challenges. One of these relates to subclassing. Many of the toll-free bridge Foundation types support subclassing and, if you create a instance of your subclass and pass it to CF, CF has to do the right thing.

Continuing the NSData example above, it’s legal [2] to create your own subclass of NSData with a completely custom implementation. As long as you implement the -bytes method and the length property, all the other NSData methods will just work. Moreover, this class works with CFData routines as well. If you pass an instance of your subclass to CFDataGetBytePtr, CF detects that it’s an Objective-C object and calls your -bytes method.

Exciting!

So, how does this actually work? It relies on the fact that CF and Objective-C types share a common object header. That object header is an implementation detail, but the first word of the header is always an indication of the class [3]. CF uses this word to distinguish between CF and Objective-C objects.

Note This basic technique is used by other Objective-C compatible types, including Swift objects and XPC objects. If, for example, you call CFRetain on Swift object, it detects that this is an Objective-C compatible object and calls objc_retain, which in turns detects that this is a Swift object and calls swift_retain.

To see this in action, check out the Swift Foundation open source. Continuing our NSData example, the first line of CFDataGetBytePtr uses the CF_OBJC_FUNCDISPATCHV macro to check if this is an Objective-C object and, if so, call -bytes on it.

Sadly, the open source trail goes cold here, because Objective-C integration is only supported on Apple platforms. However, some disassembly reveals the presence of an internal routine called CF_IS_OBJC.

(lldb) disas -n CFDataGetBytePtr
CoreFoundation`CFDataGetBytePtr:
    … <+0>:   pacibsp 
    … <+4>:   stp    x20, x19, [sp, #-0x20]!
    … <+8>:   stp    x29, x30, [sp, #0x10]
    … <+12>:  add    x29, sp, #0x10
    … <+16>:  mov    x19, x0
    … <+20>:  mov    w0, #0x14
    … <+24>:  mov    x1, x19
    … <+28>:  bl     0x19cc60100               ; CF_IS_OBJC
    …

WARNING Do not rely on the presence or behaviour of CF_IS_OBJC. This is an implementation detail. It has changed many times in the past and may well change in the future [4].

While this is an implementation detail, it’s useful to know about when debugging. If you pass something that’s not a valid object to CFDataGetBytePtr, you might see it crash in CF_IS_OBJC. For example, this code:

void test(void) {
    struct { int i; } s = { 0 };
    CFDataGetBytePtr( (const struct __CFData *) &s );
}

crashes like this:

Exception Type:        EXC_BREAKPOINT (SIGTRAP)
…

Thread 0 Crashed::  Dispatch queue: com.apple.main-thread
0   CoreFoundation … CF_IS_OBJC + 76
1   CoreFoundation … CFDataGetBytePtr + 32
2   xxot           … test + 24 …
…

In this case CF_IS_OBJC has detected the problem and trapped, resulting in an EXC_BREAKPOINT crash. However, if you pass it a pointer that looks more like an object, this might crash trying to dereference a bad pointer, which will result in a EXC_BAD_ACCESS crash.

The other common failure you see occurs when you pass it an Objective-C object of the wrong type. Consider code like this:

void test(void) {
    id str = [NSString stringWithFormat:@"Hello Cruel World!-%d", (int) getpid()];
    const void * ptr = CFDataGetBytePtr( (__bridge CFDataRef) str);
    …
}

When you run this code, it throws a language exception like this:

*** Terminating app due to uncaught exception 'NSInvalidArgumentException', 
	reason: '-[__NSCFString bytes]: unrecognized selector sent to 
	instance 0x6000028545a0'
*** First throw call stack:
(
	0   CoreFoundation  … __exceptionPreprocess + 176
	1   libobjc.A.dylib … objc_exception_throw + 60
	2   CoreFoundation  … -[NSObject(NSObject) __retain_OA] + 0
	3   CoreFoundation  … ___forwarding___ + 1580
	4   CoreFoundation  … _CF_forwarding_prep_0 + 96
	5   xxot            … test + 92
	…
)

CFDataGetBytePtr has detected this is an Objective-C object and called -bytes on it. However, this is actually an NSString [5] and NSString doesn’t implement the -bytes method. The end result is an unrecognized selector exception.

[1] To be clear, when using CF objects in Objective-C you first cast the CF object to its Foundation equivalent and then call Objective-C methods on it.

[2] While it’s legal to do this, it’s probably not very sensible. Subclassing Foundation types is something that might’ve made sense back in the day, but these days there are generally better ways to solve your problems.

[3] Historically this word was called isa and was of type Class, that is, a pointer to the actual Objective-C class. These days things are much more complex (-:

[4] Historically, CF_IS_OBJC was very simple: If the object’s isa word was 0, it was a CF object, otherwise it was an Objective-C object. That’s no longer the case.

[5] The actual type is __NSCFString. That’s because NSString is a class cluster. For more about that, see Class Clusters within Cocoa Fundamentals Guide in the Documentation Archive.

Up vote post of eskimo
68 views