Under Xcode 6.1, in unit tests using OCMock, the instance to create a partial mock from is prematurely released

1

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

enter image description here

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.

enter image description here

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;
}
objective-c
unit-testing
xcode6
clang
ocmock
asked on Stack Overflow Dec 15, 2014 by qnoid

1 Answer

2

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.

answered on Stack Overflow Dec 16, 2014 by alloy

User contributions licensed under CC BY-SA 3.0