After migrating to Xcode 6.1, unit tests that make use of OCMock's partial mocks have started failing.
The exception is an "unrecognized selector sent to instance" when calling the method to the mock. Trying to debug the issue, it looks like the instance being mocked has actually been released at this point.
Here are the details and findings.
Xcode 6.1
OCMock, 2.2.3 and 3.1.1
Architecture: i386
Optimisation: -O0
Simulator: iPhone 5s (7.1)
Here is a sample test that is failing.
@interface PartialMockDemoTests : XCTestCase
@property (nonatomic, strong) UITextField *field;
@end
@implementation PartialMockDemoTests
- (void)testExample2
{
self.field = [[UITextField alloc] init];
id partialMock = [OCMockObject partialMockForObject:self.field];
OCMExpect([partialMock setText:@"Test"]);
[self.field setText:@"Test"];
OCMVerifyAll(partialMock);
}
@end
Compared to this one which works as expected.
- (void)testExample {
UITextField *afield = [[UITextField alloc] init];
id partialMock = [OCMockObject partialMockForObject:afield];
OCMExpect([partialMock setText:@"Test"]);
[afield setText:@"Test"];
OCMVerifyAll(partialMock);
}
Putting a breakpoint on the first invocation of the OCPartialMockObject#forwardInvocationForRealObject I notice an objc_release call.
Looking at the assembled code using
otx -b
This is the output for testExample2
-(void)[PartialMockDemoTests testExample2]:
+0 000017d0 55 pushl %ebp
+1 000017d1 89e5 movl %esp,%ebp
+3 000017d3 57 pushl %edi
+4 000017d4 56 pushl %esi
+5 000017d5 83ec50 subl $0x50,%esp
+8 000017d8 e800000000 calll 0x000017dd
+13 000017dd 58 popl %eax
+14 000017de 8b4d0c movl 0x0c(%ebp),%ecx
+17 000017e1 8b5508 movl 0x08(%ebp),%edx
+20 000017e4 8955f4 movl %edx,0xf4(%ebp)
+23 000017e7 894df0 movl %ecx,0xf0(%ebp)
+26 000017ea 8b4df4 movl 0xf4(%ebp),%ecx
+29 000017ed 8b90f75f0100 movl 0x00015ff7(%eax),%edx
+35 000017f3 8bb0535c0100 movl 0x00015c53(%eax),%esi alloc
+41 000017f9 891424 movl %edx,(%esp)
+44 000017fc 89742404 movl %esi,0x04(%esp)
+48 00001800 8945e0 movl %eax,0xe0(%ebp)
+51 00001803 894ddc movl %ecx,0xdc(%ebp)
+54 00001806 e81be60000 calll 0x0000fe26 -[(%esp,1) alloc]
+59 0000180b 8b4de0 movl 0xe0(%ebp),%ecx
+62 0000180e 8b91575c0100 movl 0x00015c57(%ecx),%edx init
+68 00001814 890424 movl %eax,(%esp)
+71 00001817 89542404 movl %edx,0x04(%esp)
+75 0000181b e806e60000 calll 0x0000fe26 -[(%esp,1) init]
+80 00001820 8b4de0 movl 0xe0(%ebp),%ecx
+83 00001823 8b915b5c0100 movl 0x00015c5b(%ecx),%edx setField:
+89 00001829 8b75dc movl 0xdc(%ebp),%esi
+92 0000182c 893424 movl %esi,(%esp)
+95 0000182f 89542404 movl %edx,0x04(%esp)
+99 00001833 89442408 movl %eax,0x08(%esp)
+103 00001837 8945d8 movl %eax,0xd8(%ebp)
+106 0000183a e8e7e50000 calll 0x0000fe26 -[(%esp,1) setField:]
+111 0000183f 8b45d8 movl 0xd8(%ebp),%eax
+114 00001842 890424 movl %eax,(%esp)
+117 00001845 e8eee50000 calll 0x0000fe38 _objc_release
+122 0000184a 8b45e0 movl 0xe0(%ebp),%eax
+125 0000184d 8b88fb5f0100 movl 0x00015ffb(%eax),%ecx OCMockObject
+131 00001853 8b55f4 movl 0xf4(%ebp),%edx
+134 00001856 8bb05f5c0100 movl 0x00015c5f(%eax),%esi field
+140 0000185c 891424 movl %edx,(%esp)
+143 0000185f 89742404 movl %esi,0x04(%esp)
+147 00001863 894dd4 movl %ecx,0xd4(%ebp)
+150 00001866 e8bbe50000 calll 0x0000fe26 -[(%esp,1) field]
+155 0000186b 890424 movl %eax,(%esp)
+158 0000186e e8cbe50000 calll 0x0000fe3e _objc_retainAutoreleasedReturnValue
+163 00001873 89c1 movl %eax,%ecx
+165 00001875 8b55e0 movl 0xe0(%ebp),%edx
+168 00001878 8bb2635c0100 movl 0x00015c63(%edx),%esi partialMockForObject:
+174 0000187e 8b7dd4 movl 0xd4(%ebp),%edi
+177 00001881 893c24 movl %edi,(%esp)
+180 00001884 89742404 movl %esi,0x04(%esp)
+184 00001888 894c2408 movl %ecx,0x08(%esp)
+188 0000188c 8945d0 movl %eax,0xd0(%ebp)
+191 0000188f e892e50000 calll 0x0000fe26 +[OCMockObject partialMockForObject:]
+196 00001894 890424 movl %eax,(%esp)
+199 00001897 e8a2e50000 calll 0x0000fe3e _objc_retainAutoreleasedReturnValue
+204 0000189c 8945ec movl %eax,0xec(%ebp)
+207 0000189f 8b45d0 movl 0xd0(%ebp),%eax
+210 000018a2 890424 movl %eax,(%esp)
+213 000018a5 e88ee50000 calll 0x0000fe38 _objc_release
+218 000018aa 8b45e0 movl 0xe0(%ebp),%eax
+221 000018ad 8d885b3a0100 leal 0x00013a5b(%eax),%ecx Test
+227 000018b3 8b90ff5f0100 movl 0x00015fff(%eax),%edx OCMMacroState
+233 000018b9 8bb0675c0100 movl 0x00015c67(%eax),%esi beginExpectMacro
+239 000018bf 891424 movl %edx,(%esp)
+242 000018c2 89742404 movl %esi,0x04(%esp)
+246 000018c6 894dcc movl %ecx,0xcc(%ebp)
+249 000018c9 e858e50000 calll 0x0000fe26 +[OCMMacroState beginExpectMacro]
+254 000018ce 8b45ec movl 0xec(%ebp),%eax
+257 000018d1 8b4de0 movl 0xe0(%ebp),%ecx
+260 000018d4 8b916b5c0100 movl 0x00015c6b(%ecx),%edx setText:
+266 000018da 890424 movl %eax,(%esp)
+269 000018dd 89542404 movl %edx,0x04(%esp)
+273 000018e1 8b45cc movl 0xcc(%ebp),%eax
+276 000018e4 89442408 movl %eax,0x08(%esp)
+280 000018e8 e839e50000 calll 0x0000fe26 -[(%esp,1) setText:]
+285 000018ed 8b45e0 movl 0xe0(%ebp),%eax
+288 000018f0 8b88ff5f0100 movl 0x00015fff(%eax),%ecx OCMMacroState
+294 000018f6 8b906f5c0100 movl 0x00015c6f(%eax),%edx endExpectMacro
+300 000018fc 890c24 movl %ecx,(%esp)
+303 000018ff 89542404 movl %edx,0x04(%esp)
+307 00001903 e81ee50000 calll 0x0000fe26 +[OCMMacroState endExpectMacro]
+312 00001908 890424 movl %eax,(%esp)
+315 0000190b e82ee50000 calll 0x0000fe3e _objc_retainAutoreleasedReturnValue
+320 00001910 8945e4 movl %eax,0xe4(%ebp)
+323 00001913 8b45e4 movl 0xe4(%ebp),%eax
+326 00001916 8945e8 movl %eax,0xe8(%ebp)
+329 00001919 8b45e8 movl 0xe8(%ebp),%eax
+332 0000191c 890424 movl %eax,(%esp)
+335 0000191f e814e50000 calll 0x0000fe38 _objc_release
+340 00001924 8b45f4 movl 0xf4(%ebp),%eax
+343 00001927 8b4de0 movl 0xe0(%ebp),%ecx
+346 0000192a 8b915f5c0100 movl 0x00015c5f(%ecx),%edx field
+352 00001930 890424 movl %eax,(%esp)
+355 00001933 89542404 movl %edx,0x04(%esp)
+359 00001937 e8eae40000 calll 0x0000fe26 -[(%esp,1) field]
+364 0000193c 890424 movl %eax,(%esp)
+367 0000193f e8fae40000 calll 0x0000fe3e _objc_retainAutoreleasedReturnValue
+372 00001944 8b4de0 movl 0xe0(%ebp),%ecx
+375 00001947 8d915b3a0100 leal 0x00013a5b(%ecx),%edx Test
+381 0000194d 8bb16b5c0100 movl 0x00015c6b(%ecx),%esi setText:
+387 00001953 89c7 movl %eax,%edi
+389 00001955 893c24 movl %edi,(%esp)
+392 00001958 89742404 movl %esi,0x04(%esp)
+396 0000195c 89542408 movl %edx,0x08(%esp)
+400 00001960 8945c8 movl %eax,0xc8(%ebp)
+403 00001963 e8bee40000 calll 0x0000fe26 -[(%esp,1) setText:]
+408 00001968 8b45c8 movl 0xc8(%ebp),%eax
+411 0000196b 890424 movl %eax,(%esp)
+414 0000196e e8c5e40000 calll 0x0000fe38 _objc_release
+419 00001973 8b45e0 movl 0xe0(%ebp),%eax
+422 00001976 8d88b1fe0000 leal 0x0000feb1(%eax),%ecx /Users/qnoid/Downloads/PartialMockDemo/PartialMockDemoTests/PartialMockDemoTests.m
+428 0000197c ba18000000 movl $0x00000018,%edx
+433 00001981 8b75ec movl 0xec(%ebp),%esi
+436 00001984 8b7df4 movl 0xf4(%ebp),%edi
+439 00001987 893c24 movl %edi,(%esp)
+442 0000198a 894c2404 movl %ecx,0x04(%esp)
+446 0000198e c744240818000000 movl $0x00000018,0x08(%esp)
+454 00001996 8975c4 movl %esi,0xc4(%ebp)
+457 00001999 8955c0 movl %edx,0xc0(%ebp)
+460 0000199c e8ff7c0000 calll _OCMMakeLocation
+465 000019a1 890424 movl %eax,(%esp)
+468 000019a4 e895e40000 calll 0x0000fe3e _objc_retainAutoreleasedReturnValue
+473 000019a9 8b4de0 movl 0xe0(%ebp),%ecx
+476 000019ac 8b91735c0100 movl 0x00015c73(%ecx),%edx verifyAtLocation:
+482 000019b2 8b75c4 movl 0xc4(%ebp),%esi
+485 000019b5 893424 movl %esi,(%esp)
+488 000019b8 89542404 movl %edx,0x04(%esp)
+492 000019bc 89442408 movl %eax,0x08(%esp)
+496 000019c0 8945bc movl %eax,0xbc(%ebp)
+499 000019c3 e85ee40000 calll 0x0000fe26 -[(%esp,1) verifyAtLocation:]
+504 000019c8 890424 movl %eax,(%esp)
+507 000019cb e86ee40000 calll 0x0000fe3e _objc_retainAutoreleasedReturnValue
+512 000019d0 890424 movl %eax,(%esp)
+515 000019d3 e860e40000 calll 0x0000fe38 _objc_release
+520 000019d8 8b45bc movl 0xbc(%ebp),%eax
+523 000019db 890424 movl %eax,(%esp)
+526 000019de e855e40000 calll 0x0000fe38 _objc_release
+531 000019e3 8d45ec leal 0xec(%ebp),%eax
+534 000019e6 b900000000 movl $0x00000000,%ecx
+539 000019eb 890424 movl %eax,(%esp)
+542 000019ee c744240400000000 movl $0x00000000,0x04(%esp)
+550 000019f6 894db8 movl %ecx,0xb8(%ebp)
+553 000019f9 e852e40000 calll 0x0000fe50 _objc_storeStrong
+558 000019fe 83c450 addl $0x50,%esp
+561 00001a01 5e popl %esi
+562 00001a02 5f popl %edi
+563 00001a03 5d popl %ebp
+564 00001a04 c3 ret
+565 00001a05 66662e0f1f840000000000 nopl %cs:0x00000000(%eax,%eax)
Bare in mind that OCMock is non ARC. If I'm reading the assembly output correctly, it looks like the compiler is issuing a release on the instance (self.field) before creating the partial mock.
+117 00001845 e8eee50000 calll 0x0000fe38 _objc_release
This is what OCMockObject#partialMockForObject: looks like
+ (id)partialMockForObject:(NSObject *)anObject
{
return [[[OCPartialMockObject alloc] initWithObject:anObject] autorelease];
}
and OCPartialMockObject#initWithObject:
- (id)initWithObject:(NSObject *)anObject
{
[self assertClassIsSupported:[anObject class]];
[super initWithClass:[anObject class]];
realObject = [anObject retain];
[self prepareObjectForInstanceMethodMocking];
return self;
}
Not a full answer, just some validating.
It’s definitely getting released prematurely. If I enable MallocStackLogging and print the history for the object once the exception occurs, you can see that the object has already been released
$ malloc_history (pgrep '^PartialMockDemo$') 0x7b0889d0
[…]
ALLOC 0x7b0889d0-0x7b088a33 [size=100]: thread_2f121d4 |start | main | UIApplicationMain | GSEventRun | GSEventRunModal | CFRunLoopRunInMode | CFRunLoopRunSpecific | __CFRunLoopRun | __CFRunLoopDoTimer | __CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__ | __NSFireDelayedPerform | +[XCTestProbe runTests:] | -[XCTestDriver runTestSuite:completionHandler:] | -[XCTestDriver _checkForTestManager] | -[XCTestDriver _runSuite] | -[XCTestObservationCenter _observeTestExecutionForBlock:] | __25-[XCTestDriver _runSuite]_block_invoke | -[XCTest run] | -[XCTestSuite performTest:] | -[XCTest run] | -[XCTestSuite performTest:] | -[XCTest run] | -[XCTestSuite performTest:] | -[XCTest run] | -[XCTestCase performTest:] | -[XCTestCase invokeTest] | -[NSInvocation invoke] | __invoking___ | -[PartialMockDemoTests testExample2] | -[UIView init] | -[UITextField initWithFrame:] | -[UITextField _frameForLabel:inTextRect:] | -[UITextField(PasscodeStyleTextField) _isPasscodeStyle] | -[UITextField textInputTraits] | _objc_rootAlloc | class_createInstance | calloc | malloc_zone_calloc
----
FREE 0x7b0889d0-0x7b088a33 [size=100]: thread_2f121d4 |start | main | UIApplicationMain | GSEventRun | GSEventRunModal | CFRunLoopRunInMode | CFRunLoopRunSpecific | __CFRunLoopRun | __CFRunLoopDoTimer | __CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__ | __NSFireDelayedPerform | +[XCTestProbe runTests:] | -[XCTestDriver runTestSuite:completionHandler:] | -[XCTestDriver _checkForTestManager] | -[XCTestDriver _runSuite] | -[XCTestObservationCenter _observeTestExecutionForBlock:] | __25-[XCTestDriver _runSuite]_block_invoke | -[XCTest run] | -[XCTestSuite performTest:] | -[XCTest run] | -[XCTestSuite performTest:] | -[XCTest run] | -[XCTestSuite performTest:] | -[XCTest run] | -[XCTestCase performTest:] | -[XCTestCase invokeTest] | -[NSInvocation invoke] | __invoking___ | -[PartialMockDemoTests testExample2] | objc_release | -[NSObject release] | objc_object::sidetable_release(bool) | -[UITextInputTraits dealloc] | object_dispose | free
[…]
After removing everything but the first two lines, the resulting code boils down to:
-(void)testExample2
{
id field;
field = [[UITextField alloc] init];
[self setField:field];
[field release];
field = [[self field] retain];
id partialMock = [[OCMockObject partialMockForObject:field] retain];
[field release];
objc_storeStrong(partialMock, 0x0);
}
Those retains and releases in this scope look good and once +[OCMockObject partialMockForObject:]
is entered the object is still alive, so the problem definitely appears to be related to further down from -[OCPartialMockObject initWithObject:]
.
Alas, I don’t have time right now to dig into that method and the many things it ends up doing.
Hope it helps.
User contributions licensed under CC BY-SA 3.0