Skip to content

Commit 26cfcb2

Browse files
committed
Avoid retaining objects that are being deallocated.
We do not want to retain objects that are being deallocated because this will cause a crash later when the retaining object releases them. An example of this is when a delegate (that is mocked) is called in a dealloc for an object. The mock retains the deallocating object as part of an invocation and then things crash later when the invocation is released as part of the mock deallocating itself.
1 parent 1272b08 commit 26cfcb2

5 files changed

+53
-8
lines changed

Source/OCMock/NSInvocation+OCMAdditions.m

+3-3
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ - (void)retainObjectArgumentsExcludingObject:(id)objectToExclude
6666
NSMutableArray *retainedArguments = [[NSMutableArray alloc] init];
6767

6868
id target = [self target];
69-
if((target != nil) && (target != objectToExclude) && !object_isClass(target))
69+
if((target != nil) && (target != objectToExclude) && !object_isClass(target) && !OCMIsDeallocating(target))
7070
{
7171
// Bad things will happen if the target is a block since it's not being
7272
// copied. There isn't a very good way to tell if an invocation's target
@@ -84,7 +84,7 @@ - (void)retainObjectArgumentsExcludingObject:(id)objectToExclude
8484
{
8585
id argument;
8686
[self getArgument:&argument atIndex:index];
87-
if((argument != nil) && (argument != objectToExclude))
87+
if((argument != nil) && (argument != objectToExclude) && !OCMIsDeallocating(argument))
8888
{
8989
if(OCMIsBlockType(argumentType))
9090
{
@@ -120,7 +120,7 @@ - (void)retainObjectArgumentsExcludingObject:(id)objectToExclude
120120
{
121121
id returnValue;
122122
[self getReturnValue:&returnValue];
123-
if((returnValue != nil) && (returnValue != objectToExclude))
123+
if((returnValue != nil) && (returnValue != objectToExclude) && !OCMIsDeallocating(returnValue))
124124
{
125125
if(OCMIsBlockType(returnType))
126126
{

Source/OCMock/OCMFunctions.m

+14
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,14 @@ - (void)recordFailureWithDescription:(NSString *)description inFile:(NSString *)
3232
- (void)failWithException:(NSException *)exception;
3333
@end
3434

35+
@interface NSObject(OCMKnownNSObjectMethods)
36+
- (BOOL)_isDeallocating;
37+
@end
38+
39+
// From objc/runtime/objc-internal.h
40+
// Only available on macOS 10.11/iOS 9.
41+
extern id objc_initWeakOrNil(id *location, id newObj) __attribute__((weak_import));
42+
extern void objc_destroyWeak(id _Nullable * _Nonnull location) __attribute__((weak_import));
3543

3644
#pragma mark Functions related to ObjC type system
3745

@@ -466,3 +474,9 @@ void OCMReportFailure(OCMLocation *loc, NSString *description)
466474
}
467475

468476
}
477+
478+
BOOL OCMIsDeallocating(id anObject)
479+
{
480+
return [anObject _isDeallocating];
481+
}
482+

Source/OCMock/OCMFunctionsPrivate.h

+2
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,8 @@ OCPartialMockObject *OCMGetAssociatedMockForObject(id anObject);
4848

4949
void OCMReportFailure(OCMLocation *loc, NSString *description);
5050

51+
BOOL OCMIsDeallocating(id anObject);
52+
5153
BOOL OCMIsNonEscapingBlock(id block);
5254

5355

Source/OCMock/OCMStubRecorder.m

+2-1
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
#import "OCMBlockCaller.h"
2525
#import "OCMRealObjectForwarder.h"
2626
#import "OCMInvocationStub.h"
27+
#import "OCMFunctionsPrivate.h"
2728

2829

2930
@implementation OCMStubRecorder
@@ -51,7 +52,7 @@ - (OCMInvocationStub *)stub
5152
- (id)andReturn:(id)anObject
5253
{
5354
id action;
54-
if(anObject == mockObject)
55+
if(anObject == mockObject || OCMIsDeallocating(anObject))
5556
{
5657
action = [[[OCMNonRetainingObjectReturnValueProvider alloc] initWithValue:anObject] autorelease];
5758
}

Source/OCMockTests/OCMockObjectRuntimeTests.m

+32-4
Original file line numberDiff line numberDiff line change
@@ -54,14 +54,15 @@ - (NSString *)stringForTypedef:(TypedefString *)string
5454

5555
@interface TestDelegate : NSObject
5656

57-
- (void)go;
57+
- (id)go:(id)sender;
5858

5959
@end
6060

6161
@implementation TestDelegate
6262

63-
- (void)go
63+
- (id)go:(id)sender
6464
{
65+
return sender;
6566
}
6667

6768
@end
@@ -77,7 +78,24 @@ @implementation TestClassWithDelegate
7778
- (void)run
7879
{
7980
TestDelegate *delegate = self.delegate;
80-
[delegate go];
81+
[delegate go:nil];
82+
}
83+
84+
@end
85+
86+
87+
@interface TestClassThatCallsDelegateOnDealloc : NSObject
88+
89+
@property (nonatomic, weak) TestDelegate *delegate;
90+
91+
@end
92+
93+
@implementation TestClassThatCallsDelegateOnDealloc
94+
95+
- (void)dealloc
96+
{
97+
TestDelegate *delegate = self.delegate;
98+
[delegate go:self];
8199
}
82100

83101
@end
@@ -313,7 +331,7 @@ - (void)testWeakReferencesShouldStayAround
313331

314332
[object run];
315333

316-
OCMVerify([mockDelegate go]);
334+
OCMVerify([mockDelegate go:nil]);
317335
XCTAssertNotNil(object.delegate, @"Should still have delegate");
318336
}
319337

@@ -329,6 +347,16 @@ - (void)testDynamicSubclassesShouldBeDisposed
329347
XCTAssertEqual(numClassesBefore, numClassesAfter, @"Should have disposed dynamically generated classes.");
330348
}
331349

350+
- (void)testHandlesCallingMockWithSelfAsArgumentInDealloc
351+
{
352+
// Note that this test will crash on failure.
353+
id mock = [OCMockObject mockForClass:[TestDelegate class]];
354+
[[mock expect] go:OCMOCK_ANY];
355+
TestClassThatCallsDelegateOnDealloc *foo = [[TestClassThatCallsDelegateOnDealloc alloc] init];
356+
foo.delegate = mock;
357+
foo = nil;
358+
[mock verify];
359+
}
332360

333361
- (void)testClassesWithResolveMethodsCanBeMocked
334362
{

0 commit comments

Comments
 (0)