7

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?

Jon Reid
  • 20,545
  • 2
  • 64
  • 95
  • Summoned Greg Parker. I don't have enough of a clue to know from the evidence given. That looks like a corrupt `isa`? – bbum May 27 '14 at 06:12
  • @bbum The thing is, I'm not touching `isa`. The failing test is checked in to https://github.com/jonreid/OCMockito – Jon Reid May 27 '14 at 14:50
  • Do you have a sample project, btw? – bbum May 27 '14 at 17:33
  • Beats me. Sample project and crash log might help. – Greg Parker May 27 '14 at 22:51
  • I've boiled down a sample: http://qualitycoding.org/RuntimeProblem.zip Run the unit tests and you should see a crash in StubClassTest's testStubbedMethod_ShouldReturnGivenObject. But to make things even more fun, disable the ArgumentCaptorTest… and the formerly crashing test passes. – Jon Reid May 28 '14 at 05:07
  • One interesting thing is this: if you put a breakpoint in : - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector it no longer crashes. – Daniel May 28 '14 at 08:43

1 Answers1

7

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

Sash Zats
  • 5,376
  • 2
  • 28
  • 42
  • Thank you so much!! I've assumed all this time that `Class` ivars (being objects in my mind) were `__strong` by default. How does one see that intermediate code with retains & releases? – Jon Reid May 28 '14 at 14:55
  • Glad it helped, sadly, it doesn't explain why this code works on 32bit os and doesn't work on 64, maybe sir Bill Bumgarner or Greg Parker can shed some light on this matter. – Sash Zats May 28 '14 at 14:59
  • On Intel, the 32 bit runtime is the *legacy runtime* whereas the 64 bit runtime is modern (like iOS). The difference lies within, I'd guess. – bbum May 28 '14 at 16:53
  • 32-bit OS X doesn't support ARC. Presumably your 32-bit manual memory management is retaining the proxy object even though its compile-time type is Class. – Greg Parker May 28 '14 at 19:58
  • That makes sense. What about iOS? Both 32 and 64 bit are ARC-enabled in this case – Sash Zats May 28 '14 at 20:03