Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

NSProxy pretending to be Class doesn't handle respondsToSelector in 64-bit runtime

In OCMockito, test doubles are implemented with NSProxy. A double standing in for an instance implements -respondsToSelector: as follows:

- (BOOL)respondsToSelector:(SEL)aSelector {
    return [_mockedClass instancesRespondToSelector:aSelector];
}

But a double standing in for a class implements -respondsToSelector: like this:

- (BOOL)respondsToSelector:(SEL)aSelector {
    return [_mockedClass respondsToSelector:aSelector];
}

This all works in the 32-bit runtime. For example, if _mockedClass is [NSString class], the proxy correctly answers that it responds to the selector +pathWithComponents:

But in the 64-bit runtime, it crashes:

Crashed Thread:  0  Dispatch queue: com.apple.main-thread

Exception Type:  EXC_BAD_ACCESS (SIGSEGV)
Exception Codes: EXC_I386_GPFLT

Application Specific Information:
objc[1868]: GC: forcing GC OFF because OBJC_DISABLE_GC is set

Thread 0 Crashed:: Dispatch queue: com.apple.main-thread
0   libobjc.A.dylib                 0x00007fff95cbffc6 cache_getImp + 6
1   libobjc.A.dylib                 0x00007fff95ccd1dc lookUpImpOrForward + 50
2   libobjc.A.dylib                 0x00007fff95ccd198 lookUpImpOrNil + 20
3   libobjc.A.dylib                 0x00007fff95cc218a class_respondsToSelector + 37
4   com.apple.CoreFoundation        0x00007fff91c131ad ___forwarding___ + 429
5   com.apple.CoreFoundation        0x00007fff91c12f78 _CF_forwarding_prep_0 + 120
6   org.mockito.OCMockitoTests      0x000000010451a55b -[StubClassTest testStubbedMethod_ShouldReturnGivenObject] + 107 (StubClassTest.m:48)

Note that it's calling class_respondsToSelector(…). I suspect that I'm being bitten by an optimization made to the runtime. What can I do to fix this?

like image 364
Jon Reid Avatar asked May 25 '14 17:05

Jon Reid


1 Answers

it's a bit long answer, so bear with me. I ran a simple code just to verify the behavior:

Class mock = mockClass([NSProcessInfo class]);
[mock processInfo];
[verify(mock) processInfo];

Indeed It does crash with bad pointer exception. Replacing first line with

id mock = mockClass([NSProcessInfo class]);

works as expected. I figured that it might be worth to look at the code after ARC. Those snippets are a bit to long, so here are the gists: Class-based test, id-based test

As you can see, when you declare variable of type Class there is an extra release. My guess is that since classes are registered for the entire runtime duration (unless removed using runtime api) it's ok to have Class variable as __unsafe_unretained.

To summarize, you have two possible solutions:

@implementation StubClassTest
{
    __strong Class mockClass;
}

or

@implementation StubClassTest
{
    id mockClass;
}

seem to fix the issue for me.

Update

As a special case, if the object’s base type is Class (possibly protocol-qualified), the type is adjusted to have __unsafe_unretained qualification instead.

From http://clang.llvm.org/docs/AutomaticReferenceCounting.html#objects

like image 149
Sash Zats Avatar answered Sep 21 '22 20:09

Sash Zats