From 180e89be5e8f72f72836ae4d3244bb1fe6865089 Mon Sep 17 00:00:00 2001 From: Erik Doernenburg Date: Wed, 30 Dec 2020 19:21:39 +0100 Subject: [PATCH 1/5] Extended simulator launch timeout even more. This should help with spurious build failures on Travis. --- Source/OCMock/OCClassMockObject.h | 7 +----- Source/OCMock/OCClassMockObject.m | 5 ++++ Source/OCMock/OCMockObject.h | 9 ------- Source/OCMock/OCMockObject.m | 12 +++++++++ Source/OCMock/OCPartialMockObject.h | 4 --- Source/OCMock/OCPartialMockObject.m | 25 +++++++++++-------- Source/OCMock/OCProtocolMockObject.h | 5 +--- Source/OCMock/OCProtocolMockObject.m | 3 +++ .../OCMockObjectPartialMocksTests.m | 4 +-- 9 files changed, 39 insertions(+), 35 deletions(-) diff --git a/Source/OCMock/OCClassMockObject.h b/Source/OCMock/OCClassMockObject.h index 6f1ccffd..f5a15fec 100644 --- a/Source/OCMock/OCClassMockObject.h +++ b/Source/OCMock/OCClassMockObject.h @@ -16,12 +16,7 @@ #import "OCMockObject.h" -@interface OCClassMockObject : OCMockObject -{ - Class mockedClass; - Class originalMetaClass; - Class classCreatedForNewMetaClass; -} +@interface OCClassMockObject : OCMockObject - (id)initWithClass:(Class)aClass; diff --git a/Source/OCMock/OCClassMockObject.m b/Source/OCMock/OCClassMockObject.m index b7cbb71f..84519d2b 100644 --- a/Source/OCMock/OCClassMockObject.m +++ b/Source/OCMock/OCClassMockObject.m @@ -27,6 +27,11 @@ + (BOOL)supportsMocking:(NSString **)reason; @implementation OCClassMockObject +{ + Class mockedClass; + Class originalMetaClass; + Class classCreatedForNewMetaClass; +} #pragma mark Initialisers, description, accessors, etc. diff --git a/Source/OCMock/OCMockObject.h b/Source/OCMock/OCMockObject.h index f5739716..73d1f082 100644 --- a/Source/OCMock/OCMockObject.h +++ b/Source/OCMock/OCMockObject.h @@ -25,15 +25,6 @@ @interface OCMockObject : NSProxy -{ - BOOL isNice; - BOOL expectationOrderMatters; - NSMutableArray *stubs; - NSMutableArray *expectations; - NSMutableArray *exceptions; - NSMutableArray *invocations; -} - + (id)mockForClass:(Class)aClass; + (id)mockForProtocol:(Protocol *)aProtocol; + (id)partialMockForObject:(NSObject *)anObject; diff --git a/Source/OCMock/OCMockObject.m b/Source/OCMock/OCMockObject.m index ec652b5d..b652cdcb 100644 --- a/Source/OCMock/OCMockObject.m +++ b/Source/OCMock/OCMockObject.m @@ -31,6 +31,14 @@ @implementation OCMockObject +{ + BOOL isNice; + BOOL expectationOrderMatters; + NSMutableArray *stubs; + NSMutableArray *expectations; + NSMutableArray *exceptions; + NSMutableArray *invocations; +} #pragma mark Class initialisation @@ -180,6 +188,10 @@ - (void)addInvocation:(NSInvocation *)anInvocation } } +- (NSArray *)invocations +{ + return invocations; +} #pragma mark Public API diff --git a/Source/OCMock/OCPartialMockObject.h b/Source/OCMock/OCPartialMockObject.h index afc3bb08..412bb50a 100644 --- a/Source/OCMock/OCPartialMockObject.h +++ b/Source/OCMock/OCPartialMockObject.h @@ -17,10 +17,6 @@ #import "OCClassMockObject.h" @interface OCPartialMockObject : OCClassMockObject -{ - NSObject *realObject; - NSInvocation *invocationFromMock; -} - (id)initWithObject:(NSObject *)anObject; diff --git a/Source/OCMock/OCPartialMockObject.m b/Source/OCMock/OCPartialMockObject.m index c482e4f2..9e417a8a 100644 --- a/Source/OCMock/OCPartialMockObject.m +++ b/Source/OCMock/OCPartialMockObject.m @@ -24,6 +24,10 @@ @implementation OCPartialMockObject +{ + NSObject *realObject; + NSInvocation *invocationFromMock; +} #pragma mark Initialisers, description, accessors, etc. @@ -32,15 +36,16 @@ - (id)initWithObject:(NSObject *)anObject if(anObject == nil) [NSException raise:NSInvalidArgumentException format:@"Object cannot be nil."]; Class const class = [self classToSubclassForObject:anObject]; - [super initWithClass:class]; - realObject = [anObject retain]; + [self assertClassIsSupported:class]; + self = [super initWithClass:class]; + realObject = [anObject retain]; [self prepareObjectForInstanceMethodMocking]; return self; } - (NSString *)description { - return [NSString stringWithFormat:@"OCPartialMockObject(%@)", NSStringFromClass(mockedClass)]; + return [NSString stringWithFormat:@"OCPartialMockObject(%@)", NSStringFromClass([self mockedClass])]; } - (NSObject *)realObject @@ -151,7 +156,7 @@ - (void)prepareObjectForInstanceMethodMocking OCMSetAssociatedMockForObject(self, realObject); /* dynamically create a subclass and set it as the class of the object */ - Class subclass = OCMCreateSubclass(mockedClass, realObject); + Class subclass = OCMCreateSubclass([self mockedClass], realObject); object_setClass(realObject, subclass); /* point forwardInvocation: of the object to the implementation in the mock */ @@ -162,7 +167,7 @@ - (void)prepareObjectForInstanceMethodMocking /* do the same for forwardingTargetForSelector, remember existing imp with alias selector */ Method myForwardingTargetMethod = class_getInstanceMethod([self mockObjectClass], @selector(forwardingTargetForSelectorForRealObject:)); IMP myForwardingTargetIMP = method_getImplementation(myForwardingTargetMethod); - IMP originalForwardingTargetIMP = [mockedClass instanceMethodForSelector:@selector(forwardingTargetForSelector:)]; + IMP originalForwardingTargetIMP = [[self mockedClass] instanceMethodForSelector:@selector(forwardingTargetForSelector:)]; class_addMethod(subclass, @selector(forwardingTargetForSelector:), myForwardingTargetIMP, method_getTypeEncoding(myForwardingTargetMethod)); class_addMethod(subclass, @selector(ocmock_replaced_forwardingTargetForSelector:), originalForwardingTargetIMP, method_getTypeEncoding(myForwardingTargetMethod)); @@ -189,7 +194,7 @@ - (void)prepareObjectForInstanceMethodMocking // ignore for now } }; - [NSObject enumerateMethodsInClass:mockedClass usingBlock:setupForwarderFiltered]; + [NSObject enumerateMethodsInClass:[self mockedClass] usingBlock:setupForwarderFiltered]; } - (void)setupForwarderForSelector:(SEL)sel @@ -198,16 +203,16 @@ - (void)setupForwarderForSelector:(SEL)sel if(class_getInstanceMethod(object_getClass(realObject), aliasSelector) != NULL) return; - Method originalMethod = class_getInstanceMethod(mockedClass, sel); + Method originalMethod = class_getInstanceMethod([self mockedClass], sel); /* Might be NULL if the selector is forwarded to another class */ IMP originalIMP = (originalMethod != NULL) ? method_getImplementation(originalMethod) : NULL; const char *types = (originalMethod != NULL) ? method_getTypeEncoding(originalMethod) : NULL; // TODO: check the fallback implementation is actually sufficient if(types == NULL) - types = ([[mockedClass instanceMethodSignatureForSelector:sel] fullObjCTypes]); + types = ([[[self mockedClass] instanceMethodSignatureForSelector:sel] fullObjCTypes]); Class subclass = object_getClass([self realObject]); - IMP forwarderIMP = [mockedClass instanceMethodForwarderForSelector:sel]; + IMP forwarderIMP = [[self mockedClass] instanceMethodForwarderForSelector:sel]; class_replaceMethod(subclass, sel, forwarderIMP, types); class_addMethod(subclass, aliasSelector, originalIMP, types); } @@ -264,7 +269,7 @@ - (NSString *)descriptionForVerificationFailureWithMatcher:(OCMInvocationMatcher { SEL matcherSel = [[matcher recordedInvocation] selector]; __block BOOL stubbingMightHelp = NO; - [NSObject enumerateMethodsInClass:mockedClass usingBlock:^(Class cls, SEL sel) { + [NSObject enumerateMethodsInClass:[self mockedClass] usingBlock:^(Class cls, SEL sel) { if(sel == matcherSel) stubbingMightHelp = OCMIsAppleBaseClass(cls) || OCMIsApplePrivateMethod(cls, sel); }]; diff --git a/Source/OCMock/OCProtocolMockObject.h b/Source/OCMock/OCProtocolMockObject.h index a6bac51f..abe71813 100644 --- a/Source/OCMock/OCProtocolMockObject.h +++ b/Source/OCMock/OCProtocolMockObject.h @@ -16,10 +16,7 @@ #import "OCMockObject.h" -@interface OCProtocolMockObject : OCMockObject -{ - Protocol *mockedProtocol; -} +@interface OCProtocolMockObject : OCMockObject - (id)initWithProtocol:(Protocol *)aProtocol; diff --git a/Source/OCMock/OCProtocolMockObject.m b/Source/OCMock/OCProtocolMockObject.m index 76f44f15..2aa1b780 100644 --- a/Source/OCMock/OCProtocolMockObject.m +++ b/Source/OCMock/OCProtocolMockObject.m @@ -19,6 +19,9 @@ @implementation OCProtocolMockObject +{ + Protocol *mockedProtocol; +} #pragma mark Initialisers, description, accessors, etc. diff --git a/Source/OCMockTests/OCMockObjectPartialMocksTests.m b/Source/OCMockTests/OCMockObjectPartialMocksTests.m index 8e15b2d5..96e0680a 100644 --- a/Source/OCMockTests/OCMockObjectPartialMocksTests.m +++ b/Source/OCMockTests/OCMockObjectPartialMocksTests.m @@ -205,6 +205,7 @@ @implementation OCTestManagedObject @interface OCPartialMockObject (AccessToInvocationsForTesting) +- (NSArray *)invocations; - (NSArray *)invocationsExcludingInitialize; @end @@ -214,10 +215,9 @@ @implementation OCPartialMockObject (AccessToInvocationsForTesting) - (NSArray *)invocationsExcludingInitialize { NSMutableArray *filteredInvocations = [[NSMutableArray alloc] init]; - for(NSInvocation *i in invocations) + for(NSInvocation *i in [self invocations]) if([NSStringFromSelector([i selector]) hasSuffix:@"initialize"] == NO) [filteredInvocations addObject:i]; - return filteredInvocations; } From dc9298b5bfdbbd91551053d25158f0af1bb734d9 Mon Sep 17 00:00:00 2001 From: Dave MacLachlan Date: Thu, 9 Jul 2020 09:45:07 -0700 Subject: [PATCH 2/5] Assign `self` to `[super init]` in init methods. --- Source/OCMock/OCClassMockObject.m | 2 +- Source/OCMock/OCProtocolMockObject.m | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Source/OCMock/OCClassMockObject.m b/Source/OCMock/OCClassMockObject.m index 84519d2b..1a025b3d 100644 --- a/Source/OCMock/OCClassMockObject.m +++ b/Source/OCMock/OCClassMockObject.m @@ -38,7 +38,7 @@ @implementation OCClassMockObject - (id)initWithClass:(Class)aClass { [self assertClassIsSupported:aClass]; - [super init]; + self = [super init]; mockedClass = aClass; [self prepareClassForClassMethodMocking]; return self; diff --git a/Source/OCMock/OCProtocolMockObject.m b/Source/OCMock/OCProtocolMockObject.m index 2aa1b780..85619ab9 100644 --- a/Source/OCMock/OCProtocolMockObject.m +++ b/Source/OCMock/OCProtocolMockObject.m @@ -30,7 +30,7 @@ - (id)initWithProtocol:(Protocol *)aProtocol if(aProtocol == nil) [NSException raise:NSInvalidArgumentException format:@"Protocol cannot be nil."]; - [super init]; + self = [super init]; mockedProtocol = aProtocol; return self; } From 073bd105a55197aa6be22e4c77cf063f5e065a8e Mon Sep 17 00:00:00 2001 From: Dave MacLachlan Date: Thu, 9 Jul 2020 09:59:53 -0700 Subject: [PATCH 3/5] Move all ivars over to properties. --- Source/OCMock/OCClassMockObject.m | 94 ++++++++++---------- Source/OCMock/OCMockObject.m | 127 +++++++++++++--------------- Source/OCMock/OCPartialMockObject.m | 65 +++++++------- 3 files changed, 136 insertions(+), 150 deletions(-) diff --git a/Source/OCMock/OCClassMockObject.m b/Source/OCMock/OCClassMockObject.m index 1a025b3d..b2d9d196 100644 --- a/Source/OCMock/OCClassMockObject.m +++ b/Source/OCMock/OCClassMockObject.m @@ -26,12 +26,13 @@ + (BOOL)supportsMocking:(NSString **)reason; @end +@interface OCClassMockObject () +@property (nonatomic) Class mockedClass; +@property (nonatomic) Class originalMetaClass; +@property (nonatomic) Class classCreatedForNewMetaClass; +@end + @implementation OCClassMockObject -{ - Class mockedClass; - Class originalMetaClass; - Class classCreatedForNewMetaClass; -} #pragma mark Initialisers, description, accessors, etc. @@ -39,7 +40,7 @@ - (id)initWithClass:(Class)aClass { [self assertClassIsSupported:aClass]; self = [super init]; - mockedClass = aClass; + self.mockedClass = aClass; [self prepareClassForClassMethodMocking]; return self; } @@ -52,12 +53,7 @@ - (void)dealloc - (NSString *)description { - return [NSString stringWithFormat:@"OCClassMockObject(%@)", NSStringFromClass(mockedClass)]; -} - -- (Class)mockedClass -{ - return mockedClass; + return [NSString stringWithFormat:@"OCClassMockObject(%@)", NSStringFromClass(self.mockedClass)]; } - (void)assertClassIsSupported:(Class)aClass @@ -77,14 +73,14 @@ - (void)assertClassIsSupported:(Class)aClass - (void)stopMocking { - if(originalMetaClass != nil) + if(self.originalMetaClass != nil) { [self stopMockingClassMethods]; } - if(classCreatedForNewMetaClass != nil) + if(self.classCreatedForNewMetaClass != nil) { - OCMDisposeSubclass(classCreatedForNewMetaClass); - classCreatedForNewMetaClass = nil; + OCMDisposeSubclass(self.classCreatedForNewMetaClass); + self.classCreatedForNewMetaClass = nil; } [super stopMocking]; } @@ -92,9 +88,9 @@ - (void)stopMocking - (void)stopMockingClassMethods { - OCMSetAssociatedMockForClass(nil, mockedClass); - object_setClass(mockedClass, originalMetaClass); - originalMetaClass = nil; + OCMSetAssociatedMockForClass(nil, self.mockedClass); + object_setClass(self.mockedClass, self.originalMetaClass); + self.originalMetaClass = nil; /* created meta class will be disposed later because partial mocks create another subclass depending on it */ } @@ -112,24 +108,24 @@ - (void)addStub:(OCMInvocationStub *)aStub - (void)prepareClassForClassMethodMocking { /* the runtime and OCMock depend on string and array; we don't intercept methods on them to avoid endless loops */ - if([[mockedClass class] isSubclassOfClass:[NSString class]] || [[mockedClass class] isSubclassOfClass:[NSArray class]]) + if([[self.mockedClass class] isSubclassOfClass:[NSString class]] || [[self.mockedClass class] isSubclassOfClass:[NSArray class]]) return; /* trying to replace class methods on NSManagedObject and subclasses of it doesn't work; see #339 */ - if([mockedClass isSubclassOfClass:objc_getClass("NSManagedObject")]) + if([self.mockedClass isSubclassOfClass:objc_getClass("NSManagedObject")]) return; /* if there is another mock for this exact class, stop it */ - id otherMock = OCMGetAssociatedMockForClass(mockedClass, NO); + id otherMock = OCMGetAssociatedMockForClass(self.mockedClass, NO); if(otherMock != nil) [otherMock stopMockingClassMethods]; - OCMSetAssociatedMockForClass(self, mockedClass); + OCMSetAssociatedMockForClass(self, self.mockedClass); /* dynamically create a subclass and use its meta class as the meta class for the mocked class */ - classCreatedForNewMetaClass = OCMCreateSubclass(mockedClass, mockedClass); - originalMetaClass = object_getClass(mockedClass); - id newMetaClass = object_getClass(classCreatedForNewMetaClass); + self.classCreatedForNewMetaClass = OCMCreateSubclass(self.mockedClass, self.mockedClass); + self.originalMetaClass = object_getClass(self.mockedClass); + id newMetaClass = object_getClass(self.classCreatedForNewMetaClass); /* create a dummy initialize method */ Method myDummyInitializeMethod = class_getInstanceMethod([self mockObjectClass], @selector(initializeForClassObject)); @@ -137,7 +133,7 @@ - (void)prepareClassForClassMethodMocking IMP myDummyInitializeIMP = method_getImplementation(myDummyInitializeMethod); class_addMethod(newMetaClass, @selector(initialize), myDummyInitializeIMP, initializeTypes); - object_setClass(mockedClass, newMetaClass); // only after dummy initialize is installed (iOS9) + object_setClass(self.mockedClass, newMetaClass); // only after dummy initialize is installed (iOS9) /* point forwardInvocation: of the object to the implementation in the mock */ Method myForwardMethod = class_getInstanceMethod([self mockObjectClass], @selector(forwardInvocationForClassObject:)); @@ -165,22 +161,22 @@ - (void)prepareClassForClassMethodMocking // ignore for now } }; - [NSObject enumerateMethodsInClass:originalMetaClass usingBlock:setupForwarderFiltered]; + [NSObject enumerateMethodsInClass:self.originalMetaClass usingBlock:setupForwarderFiltered]; } - (void)setupForwarderForClassMethodSelector:(SEL)selector { SEL aliasSelector = OCMAliasForOriginalSelector(selector); - if(class_getClassMethod(mockedClass, aliasSelector) != NULL) + if(class_getClassMethod(self.mockedClass, aliasSelector) != NULL) return; - Method originalMethod = class_getClassMethod(mockedClass, selector); + Method originalMethod = class_getClassMethod(self.mockedClass, selector); IMP originalIMP = method_getImplementation(originalMethod); const char *types = method_getTypeEncoding(originalMethod); - Class metaClass = object_getClass(mockedClass); - IMP forwarderIMP = [originalMetaClass instanceMethodForwarderForSelector:selector]; + Class metaClass = object_getClass(self.mockedClass); + IMP forwarderIMP = [self.originalMetaClass instanceMethodForwarderForSelector:selector]; class_addMethod(metaClass, aliasSelector, originalIMP, types); class_replaceMethod(metaClass, selector, forwarderIMP, types); } @@ -211,10 +207,10 @@ - (void)initializeForClassObject - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector { - NSMethodSignature *signature = [mockedClass instanceMethodSignatureForSelector:aSelector]; + NSMethodSignature *signature = [self.mockedClass instanceMethodSignatureForSelector:aSelector]; if(signature == nil) { - signature = [NSMethodSignature signatureForDynamicPropertyAccessedWithSelector:aSelector inClass:mockedClass]; + signature = [NSMethodSignature signatureForDynamicPropertyAccessedWithSelector:aSelector inClass:self.mockedClass]; } return signature; } @@ -226,25 +222,25 @@ - (Class)mockObjectClass - (Class)class { - return mockedClass; + return _mockedClass; } - (BOOL)respondsToSelector:(SEL)selector { - return [mockedClass instancesRespondToSelector:selector]; + return [self.mockedClass instancesRespondToSelector:selector]; } - (BOOL)isKindOfClass:(Class)aClass { - return [mockedClass isSubclassOfClass:aClass]; + return [self.mockedClass isSubclassOfClass:aClass]; } - (BOOL)conformsToProtocol:(Protocol *)aProtocol { - Class clazz = mockedClass; + Class clazz = self.mockedClass; while(clazz != nil) { - if(class_conformsToProtocol(clazz, aProtocol)) + if (class_conformsToProtocol(clazz, aProtocol)) { return YES; } @@ -279,52 +275,52 @@ @implementation OCClassMockObject(NSIsKindsImplementation) - (BOOL)isNSValue__ { - return [mockedClass isSubclassOfClass:[NSValue class]]; + return [self.mockedClass isSubclassOfClass:[NSValue class]]; } - (BOOL)isNSTimeZone__ { - return [mockedClass isSubclassOfClass:[NSTimeZone class]]; + return [self.mockedClass isSubclassOfClass:[NSTimeZone class]]; } - (BOOL)isNSSet__ { - return [mockedClass isSubclassOfClass:[NSSet class]]; + return [self.mockedClass isSubclassOfClass:[NSSet class]]; } - (BOOL)isNSOrderedSet__ { - return [mockedClass isSubclassOfClass:[NSOrderedSet class]]; + return [self.mockedClass isSubclassOfClass:[NSOrderedSet class]]; } - (BOOL)isNSNumber__ { - return [mockedClass isSubclassOfClass:[NSNumber class]]; + return [self.mockedClass isSubclassOfClass:[NSNumber class]]; } - (BOOL)isNSDate__ { - return [mockedClass isSubclassOfClass:[NSDate class]]; + return [self.mockedClass isSubclassOfClass:[NSDate class]]; } - (BOOL)isNSString__ { - return [mockedClass isSubclassOfClass:[NSString class]]; + return [self.mockedClass isSubclassOfClass:[NSString class]]; } - (BOOL)isNSDictionary__ { - return [mockedClass isSubclassOfClass:[NSDictionary class]]; + return [self.mockedClass isSubclassOfClass:[NSDictionary class]]; } - (BOOL)isNSData__ { - return [mockedClass isSubclassOfClass:[NSData class]]; + return [self.mockedClass isSubclassOfClass:[NSData class]]; } - (BOOL)isNSArray__ { - return [mockedClass isSubclassOfClass:[NSArray class]]; + return [self.mockedClass isSubclassOfClass:[NSArray class]]; } @end diff --git a/Source/OCMock/OCMockObject.m b/Source/OCMock/OCMockObject.m index b652cdcb..480d2923 100644 --- a/Source/OCMock/OCMockObject.m +++ b/Source/OCMock/OCMockObject.m @@ -29,16 +29,16 @@ #import "OCPartialMockObject.h" #import "OCProtocolMockObject.h" +@interface OCMockObject () +@property (nonatomic) BOOL isNice; +@property (nonatomic) BOOL expectationOrderMatters; +@property (nonatomic) NSMutableArray *stubs; +@property (nonatomic) NSMutableArray *expectations; +@property (nonatomic) NSMutableArray *exceptions; +@property (nonatomic) NSMutableArray *invocations; +@end @implementation OCMockObject -{ - BOOL isNice; - BOOL expectationOrderMatters; - NSMutableArray *stubs; - NSMutableArray *expectations; - NSMutableArray *exceptions; - NSMutableArray *invocations; -} #pragma mark Class initialisation @@ -80,7 +80,7 @@ + (id)niceMockForProtocol:(Protocol *)aProtocol + (id)_makeNice:(OCMockObject *)mock { - mock->isNice = YES; + mock.isNice = YES; return mock; } @@ -103,8 +103,8 @@ - (instancetype)init return (id)[recorder init]; } - // skip initialisation when init is called again, which can happen when stubbing alloc/init - if(stubs != nil) + // skip initialisation when init is called again, which can happen when stubbing alloc/init + if(self.stubs != nil) { return self; } @@ -115,20 +115,20 @@ - (instancetype)init } // no [super init], we're inheriting from NSProxy - expectationOrderMatters = NO; - stubs = [[NSMutableArray alloc] init]; - expectations = [[NSMutableArray alloc] init]; - exceptions = [[NSMutableArray alloc] init]; - invocations = [[NSMutableArray alloc] init]; + self.expectationOrderMatters = NO; + self.stubs = [[NSMutableArray alloc] init]; + self.expectations = [[NSMutableArray alloc] init]; + self.exceptions = [[NSMutableArray alloc] init]; + self.invocations = [[NSMutableArray alloc] init]; return self; } - (void)dealloc { - [stubs release]; - [expectations release]; - [exceptions release]; - [invocations release]; + [self.stubs release]; + [self.expectations release]; + [self.exceptions release]; + [self.invocations release]; [super dealloc]; } @@ -140,17 +140,17 @@ - (NSString *)description - (void)addStub:(OCMInvocationStub *)aStub { [self assertInvocationsArrayIsPresent]; - @synchronized(stubs) + @synchronized(self.stubs) { - [stubs addObject:aStub]; + [self.stubs addObject:aStub]; } } - (OCMInvocationStub *)stubForInvocation:(NSInvocation *)anInvocation { - @synchronized(stubs) + @synchronized(self.stubs) { - for(OCMInvocationStub *stub in stubs) + for(OCMInvocationStub *stub in self.stubs) if([stub matchesInvocation:anInvocation]) return stub; return nil; @@ -159,15 +159,15 @@ - (OCMInvocationStub *)stubForInvocation:(NSInvocation *)anInvocation - (void)addExpectation:(OCMInvocationExpectation *)anExpectation { - @synchronized(expectations) + @synchronized(self.expectations) { - [expectations addObject:anExpectation]; + [self.expectations addObject:anExpectation]; } } - (void)assertInvocationsArrayIsPresent { - if(invocations == nil) + if(self.invocations == nil) { [NSException raise:NSInternalInconsistencyException format:@"** Cannot use mock object %@ at %p. This error usually occurs when a mock object is used after stopMocking has been called on it. In most cases it is not necessary to call stopMocking. If you know you have to, please make sure that the mock object is not used afterwards.", [self description], (void *)self]; } @@ -175,7 +175,7 @@ - (void)assertInvocationsArrayIsPresent - (void)addInvocation:(NSInvocation *)anInvocation { - @synchronized(invocations) + @synchronized(self.invocations) { // We can't do a normal retain arguments on anInvocation because its target/arguments/return // value could be self. That would produce a retain cycle self->invocations->anInvocation->self. @@ -184,20 +184,13 @@ - (void)addInvocation:(NSInvocation *)anInvocation // This still doesn't completely prevent retain cycles since any of the arguments could have a // strong reference to self. Those will have to be broken with manual calls to -stopMocking. [anInvocation retainObjectArgumentsExcludingObject:self]; - [invocations addObject:anInvocation]; + [self.invocations addObject:anInvocation]; } } -- (NSArray *)invocations -{ - return invocations; -} - -#pragma mark Public API - - (void)setExpectationOrderMatters:(BOOL)flag { - expectationOrderMatters = flag; + _expectationOrderMatters = flag; } - (void)stopMocking @@ -206,11 +199,11 @@ - (void)stopMocking // and they can also have a strong reference to self, creating a retain cycle. Get // rid of all of the invocations to hopefully let their objects deallocate, and to // break any retain cycles involving self. - @synchronized(invocations) + @synchronized(self.invocations) { - [invocations removeAllObjects]; - [invocations autorelease]; - invocations = nil; + [self.invocations removeAllObjects]; + [self.invocations autorelease]; + self.invocations = nil; } } @@ -239,9 +232,9 @@ - (id)verify - (id)verifyAtLocation:(OCMLocation *)location { NSMutableArray *unsatisfiedExpectations = [NSMutableArray array]; - @synchronized(expectations) + @synchronized(self.expectations) { - for(OCMInvocationExpectation *e in expectations) + for(OCMInvocationExpectation *e in self.expectations) { if(![e isSatisfied]) [unsatisfiedExpectations addObject:e]; @@ -262,9 +255,9 @@ - (id)verifyAtLocation:(OCMLocation *)location } OCMInvocationExpectation *firstException = nil; - @synchronized(exceptions) + @synchronized(self.exceptions) { - firstException = [exceptions.firstObject retain]; + firstException = [self.exceptions.firstObject retain]; } if(firstException) { @@ -288,10 +281,10 @@ - (void)verifyWithDelay:(NSTimeInterval)delay atLocation:(OCMLocation *)location NSTimeInterval step = 0.01; while(delay > 0) { - @synchronized(expectations) + @synchronized(self.expectations) { BOOL allExpectationsAreMatchAndReject = YES; - for(OCMInvocationExpectation *expectation in expectations) + for(OCMInvocationExpectation *expectation in self.expectations) { if(![expectation isMatchAndReject]) { @@ -326,9 +319,9 @@ - (void)verifyInvocation:(OCMInvocationMatcher *)matcher withQuantifier:(OCMQuan { NSUInteger count = 0; [self assertInvocationsArrayIsPresent]; - @synchronized(invocations) + @synchronized(self.invocations) { - for(NSInvocation *invocation in invocations) + for(NSInvocation *invocation in self.invocations) { if([matcher matchesInvocation:invocation]) count += 1; @@ -379,9 +372,9 @@ - (id)forwardingTargetForSelector:(SEL)aSelector - (BOOL)handleSelector:(SEL)sel { - @synchronized(stubs) + @synchronized(self.stubs) { - for(OCMInvocationStub *recorder in stubs) + for(OCMInvocationStub *recorder in self.stubs) if([recorder matchesSelector:sel]) return YES; } @@ -404,9 +397,9 @@ - (void)forwardInvocation:(NSInvocation *)anInvocation else { // add non-stubbed method to list of exceptions to be re-raised in verify - @synchronized(exceptions) + @synchronized(self.exceptions) { - [exceptions addObject:e]; + [self.exceptions addObject:e]; } } [e raise]; @@ -416,9 +409,11 @@ - (void)forwardInvocation:(NSInvocation *)anInvocation - (BOOL)handleInvocation:(NSInvocation *)anInvocation { [self assertInvocationsArrayIsPresent]; + [self addInvocation:anInvocation]; OCMInvocationStub *stub = [self stubForInvocation:anInvocation]; + if(stub == nil) return NO; @@ -426,16 +421,16 @@ - (BOOL)handleInvocation:(NSInvocation *)anInvocation [stub retain]; BOOL removeStub = NO; - @synchronized(expectations) + @synchronized(self.expectations) { - if([expectations containsObject:stub]) + if([self.expectations containsObject:stub]) { OCMInvocationExpectation *expectation = [self _nextExpectedInvocation]; - if(expectationOrderMatters && (expectation != stub)) + if(self.expectationOrderMatters && (expectation != stub)) { [NSException raise:NSInternalInconsistencyException format:@"%@: unexpected method invoked: %@\n\texpected:\t%@", - [self description], [stub description], [[expectations objectAtIndex:0] description]]; + [self description], [stub description], [[self.expectations objectAtIndex:0] description]]; } // We can't check isSatisfied yet, since the stub won't be satisfied until we call @@ -444,16 +439,16 @@ - (BOOL)handleInvocation:(NSInvocation *)anInvocation // expected methods to be called yet if(![(OCMInvocationExpectation *)stub isMatchAndReject]) { - [expectations removeObject:stub]; + [self.expectations removeObject:stub]; removeStub = YES; } } } if(removeStub) { - @synchronized(stubs) + @synchronized(self.stubs) { - [stubs removeObject:stub]; + [self.stubs removeObject:stub]; } } @@ -472,7 +467,7 @@ - (BOOL)handleInvocation:(NSInvocation *)anInvocation // Must be synchronized on expectations when calling this method. - (OCMInvocationExpectation *)_nextExpectedInvocation { - for(OCMInvocationExpectation *expectation in expectations) + for(OCMInvocationExpectation *expectation in self.expectations) if(![expectation isMatchAndReject]) return expectation; return nil; @@ -480,7 +475,7 @@ - (OCMInvocationExpectation *)_nextExpectedInvocation - (void)handleUnRecordedInvocation:(NSInvocation *)anInvocation { - if(isNice == NO) + if(self.isNice == NO) { [NSException raise:NSInternalInconsistencyException format:@"%@: unexpected method invoked: %@ %@", @@ -512,16 +507,16 @@ - (NSString *)_stubDescriptions:(BOOL)onlyExpectations { NSMutableString *outputString = [NSMutableString string]; NSArray *stubsCopy = nil; - @synchronized(stubs) + @synchronized(self.stubs) { - stubsCopy = [stubs copy]; + stubsCopy = [self.stubs copy]; } for(OCMStubRecorder *stub in stubsCopy) { BOOL expectationsContainStub = NO; - @synchronized(expectations) + @synchronized(self.expectations) { - expectationsContainStub = [expectations containsObject:stub]; + expectationsContainStub = [self.expectations containsObject:stub]; } NSString *prefix = @""; diff --git a/Source/OCMock/OCPartialMockObject.m b/Source/OCMock/OCPartialMockObject.m index 9e417a8a..ad3b22b4 100644 --- a/Source/OCMock/OCPartialMockObject.m +++ b/Source/OCMock/OCPartialMockObject.m @@ -22,12 +22,12 @@ #import "OCMFunctionsPrivate.h" #import "OCMInvocationStub.h" +@interface OCPartialMockObject () +@property (nonatomic) NSObject *realObject; +@property (nonatomic) NSInvocation *invocationFromMock; +@end @implementation OCPartialMockObject -{ - NSObject *realObject; - NSInvocation *invocationFromMock; -} #pragma mark Initialisers, description, accessors, etc. @@ -37,20 +37,15 @@ - (id)initWithObject:(NSObject *)anObject [NSException raise:NSInvalidArgumentException format:@"Object cannot be nil."]; Class const class = [self classToSubclassForObject:anObject]; [self assertClassIsSupported:class]; - self = [super initWithClass:class]; - realObject = [anObject retain]; + self = [super initWithClass:class]; + self.realObject = [anObject retain]; [self prepareObjectForInstanceMethodMocking]; return self; } - (NSString *)description { - return [NSString stringWithFormat:@"OCPartialMockObject(%@)", NSStringFromClass([self mockedClass])]; -} - -- (NSObject *)realObject -{ - return realObject; + return [NSString stringWithFormat:@"OCPartialMockObject(%@)", NSStringFromClass(self.mockedClass)]; } #pragma mark Helper methods @@ -89,13 +84,13 @@ - (Class)classToSubclassForObject:(id)object - (void)stopMocking { - if(realObject != nil) + if(self.realObject != nil) { - Class partialMockClass = object_getClass(realObject); - OCMSetAssociatedMockForObject(nil, realObject); - object_setClass(realObject, [self mockedClass]); - [realObject release]; - realObject = nil; + Class partialMockClass = object_getClass(self.realObject); + OCMSetAssociatedMockForObject(nil, self.realObject); + object_setClass(self.realObject, self.mockedClass); + [self.realObject release]; + self.realObject = nil; OCMDisposeSubclass(partialMockClass); } [super stopMocking]; @@ -112,7 +107,7 @@ - (void)addInvocation:(NSInvocation *)anInvocation { // If the mock invokes a method on the real object we end up here a second time, but because // the mock has added the invocation already we do not want to add it again. - if((invocationFromMock == nil) || ([anInvocation selector] != [invocationFromMock selector])) + if((self.invocationFromMock == nil) || ([anInvocation selector] != [self.invocationFromMock selector])) [super addInvocation:anInvocation]; } @@ -127,21 +122,21 @@ - (void)handleUnRecordedInvocation:(NSInvocation *)anInvocation if([anInvocation methodIsInInitFamily]) { targetReceivingInit = [anInvocation target]; - [realObject retain]; + [self.realObject retain]; } - invocationFromMock = anInvocation; - [anInvocation invokeWithTarget:realObject]; - invocationFromMock = nil; + self.invocationFromMock = anInvocation; + [anInvocation invokeWithTarget:self.realObject]; + self.invocationFromMock = nil; if(targetReceivingInit) { id returnVal; [anInvocation getReturnValue:&returnVal]; - if(returnVal == realObject) + if(returnVal == self.realObject) { [anInvocation setReturnValue:&self]; - [realObject release]; + [self.realObject release]; [self retain]; } [targetReceivingInit release]; @@ -153,11 +148,11 @@ - (void)handleUnRecordedInvocation:(NSInvocation *)anInvocation - (void)prepareObjectForInstanceMethodMocking { - OCMSetAssociatedMockForObject(self, realObject); + OCMSetAssociatedMockForObject(self, self.realObject); /* dynamically create a subclass and set it as the class of the object */ - Class subclass = OCMCreateSubclass([self mockedClass], realObject); - object_setClass(realObject, subclass); + Class subclass = OCMCreateSubclass(self.mockedClass, self.realObject); + object_setClass(self.realObject, subclass); /* point forwardInvocation: of the object to the implementation in the mock */ Method myForwardMethod = class_getInstanceMethod([self mockObjectClass], @selector(forwardInvocationForRealObject:)); @@ -167,7 +162,7 @@ - (void)prepareObjectForInstanceMethodMocking /* do the same for forwardingTargetForSelector, remember existing imp with alias selector */ Method myForwardingTargetMethod = class_getInstanceMethod([self mockObjectClass], @selector(forwardingTargetForSelectorForRealObject:)); IMP myForwardingTargetIMP = method_getImplementation(myForwardingTargetMethod); - IMP originalForwardingTargetIMP = [[self mockedClass] instanceMethodForSelector:@selector(forwardingTargetForSelector:)]; + IMP originalForwardingTargetIMP = [self.mockedClass instanceMethodForSelector:@selector(forwardingTargetForSelector:)]; class_addMethod(subclass, @selector(forwardingTargetForSelector:), myForwardingTargetIMP, method_getTypeEncoding(myForwardingTargetMethod)); class_addMethod(subclass, @selector(ocmock_replaced_forwardingTargetForSelector:), originalForwardingTargetIMP, method_getTypeEncoding(myForwardingTargetMethod)); @@ -194,25 +189,25 @@ - (void)prepareObjectForInstanceMethodMocking // ignore for now } }; - [NSObject enumerateMethodsInClass:[self mockedClass] usingBlock:setupForwarderFiltered]; + [NSObject enumerateMethodsInClass:self.mockedClass usingBlock:setupForwarderFiltered]; } - (void)setupForwarderForSelector:(SEL)sel { SEL aliasSelector = OCMAliasForOriginalSelector(sel); - if(class_getInstanceMethod(object_getClass(realObject), aliasSelector) != NULL) + if(class_getInstanceMethod(object_getClass(self.realObject), aliasSelector) != NULL) return; - Method originalMethod = class_getInstanceMethod([self mockedClass], sel); + Method originalMethod = class_getInstanceMethod(self.mockedClass, sel); /* Might be NULL if the selector is forwarded to another class */ IMP originalIMP = (originalMethod != NULL) ? method_getImplementation(originalMethod) : NULL; const char *types = (originalMethod != NULL) ? method_getTypeEncoding(originalMethod) : NULL; // TODO: check the fallback implementation is actually sufficient if(types == NULL) - types = ([[[self mockedClass] instanceMethodSignatureForSelector:sel] fullObjCTypes]); + types = ([[self.mockedClass instanceMethodSignatureForSelector:sel] fullObjCTypes]); Class subclass = object_getClass([self realObject]); - IMP forwarderIMP = [[self mockedClass] instanceMethodForwarderForSelector:sel]; + IMP forwarderIMP = [self.mockedClass instanceMethodForwarderForSelector:sel]; class_replaceMethod(subclass, sel, forwarderIMP, types); class_addMethod(subclass, aliasSelector, originalIMP, types); } @@ -269,7 +264,7 @@ - (NSString *)descriptionForVerificationFailureWithMatcher:(OCMInvocationMatcher { SEL matcherSel = [[matcher recordedInvocation] selector]; __block BOOL stubbingMightHelp = NO; - [NSObject enumerateMethodsInClass:[self mockedClass] usingBlock:^(Class cls, SEL sel) { + [NSObject enumerateMethodsInClass:self.mockedClass usingBlock:^(Class cls, SEL sel) { if(sel == matcherSel) stubbingMightHelp = OCMIsAppleBaseClass(cls) || OCMIsApplePrivateMethod(cls, sel); }]; From 56066b0a5c9df3290fb516b8c79b5b8271c4c69a Mon Sep 17 00:00:00 2001 From: Dave MacLachlan Date: Thu, 9 Jul 2020 14:57:37 -0700 Subject: [PATCH 4/5] Move all iVars to external storage --- Source/OCMock/OCClassMockObject.m | 53 ++++++++++++++- Source/OCMock/OCMockObject.m | 94 ++++++++++++++++++++++++-- Source/OCMock/OCPartialMockObject.m | 41 ++++++++++- Source/OCMockTests/OCMockObjectTests.m | 10 ++- 4 files changed, 188 insertions(+), 10 deletions(-) diff --git a/Source/OCMock/OCClassMockObject.m b/Source/OCMock/OCClassMockObject.m index b2d9d196..ba7976a4 100644 --- a/Source/OCMock/OCClassMockObject.m +++ b/Source/OCMock/OCClassMockObject.m @@ -26,12 +26,23 @@ + (BOOL)supportsMocking:(NSString **)reason; @end +@interface OCClassMockObjectInstanceVars : NSObject +@property (nonatomic) Class mockedClass; +@property (nonatomic) Class originalMetaClass; +@property (nonatomic) Class classCreatedForNewMetaClass; +@end + +@implementation OCClassMockObjectInstanceVars +@end + @interface OCClassMockObject () @property (nonatomic) Class mockedClass; @property (nonatomic) Class originalMetaClass; @property (nonatomic) Class classCreatedForNewMetaClass; @end +static const char *OCClassMockObjectInstanceVarsKey = "OCClassMockObjectInstanceVarsKey"; + @implementation OCClassMockObject #pragma mark Initialisers, description, accessors, etc. @@ -40,6 +51,10 @@ - (id)initWithClass:(Class)aClass { [self assertClassIsSupported:aClass]; self = [super init]; + OCClassMockObjectInstanceVars *vars = [[OCClassMockObjectInstanceVars alloc] init]; + objc_setAssociatedObject(self, OCClassMockObjectInstanceVarsKey, vars, OBJC_ASSOCIATION_RETAIN_NONATOMIC); + [vars release]; + self.mockedClass = aClass; [self prepareClassForClassMethodMocking]; return self; @@ -56,6 +71,42 @@ - (NSString *)description return [NSString stringWithFormat:@"OCClassMockObject(%@)", NSStringFromClass(self.mockedClass)]; } +#pragma mark Setters/Getters + +- (OCClassMockObjectInstanceVars *)classMockObjectInstanceVars { + return objc_getAssociatedObject(self, OCClassMockObjectInstanceVarsKey); +} + +- (Class)mockedClass +{ + return self.classMockObjectInstanceVars.mockedClass; +} + +- (Class)classCreatedForNewMetaClass +{ + return self.classMockObjectInstanceVars.classCreatedForNewMetaClass; +} + +- (Class)originalMetaClass +{ + return self.classMockObjectInstanceVars.originalMetaClass; +} + +- (void)setMockedClass:(Class)mockedClass +{ + self.classMockObjectInstanceVars.mockedClass = mockedClass; +} + +- (void)setClassCreatedForNewMetaClass:(Class)classCreatedForNewMetaClass +{ + self.classMockObjectInstanceVars.classCreatedForNewMetaClass = classCreatedForNewMetaClass; +} + +- (void)setOriginalMetaClass:(Class)originalMetaClass +{ + self.classMockObjectInstanceVars.originalMetaClass = originalMetaClass; +} + - (void)assertClassIsSupported:(Class)aClass { if(aClass == Nil) @@ -222,7 +273,7 @@ - (Class)mockObjectClass - (Class)class { - return _mockedClass; + return self.mockedClass; } - (BOOL)respondsToSelector:(SEL)selector diff --git a/Source/OCMock/OCMockObject.m b/Source/OCMock/OCMockObject.m index 480d2923..cdabef00 100644 --- a/Source/OCMock/OCMockObject.m +++ b/Source/OCMock/OCMockObject.m @@ -29,6 +29,20 @@ #import "OCPartialMockObject.h" #import "OCProtocolMockObject.h" +@interface OCMockObjectInstanceVars : NSObject +@property (nonatomic) BOOL isNice; +@property (nonatomic) BOOL expectationOrderMatters; +@property (nonatomic, assign) NSMutableArray *stubs; +@property (nonatomic, assign) NSMutableArray *expectations; +@property (nonatomic, assign) NSMutableArray *exceptions; +@property (nonatomic, assign) NSMutableArray *invocations; +@end + +@implementation OCMockObjectInstanceVars +@end + +static const char *OCMockObjectInstanceVarsKey = "OCMockObjectInstanceVarsKey"; + @interface OCMockObject () @property (nonatomic) BOOL isNice; @property (nonatomic) BOOL expectationOrderMatters; @@ -103,8 +117,8 @@ - (instancetype)init return (id)[recorder init]; } - // skip initialisation when init is called again, which can happen when stubbing alloc/init - if(self.stubs != nil) + // skip initialisation when init is called again, which can happen when stubbing alloc/init + if(self.mockObjectInstanceVars != nil) { return self; } @@ -114,6 +128,10 @@ - (instancetype)init [NSException raise:NSInternalInconsistencyException format:@"*** Cannot create instances of OCMockObject. Please use one of the subclasses."]; } + OCMockObjectInstanceVars *vars = [[OCMockObjectInstanceVars alloc] init]; + objc_setAssociatedObject(self, OCMockObjectInstanceVarsKey, vars, OBJC_ASSOCIATION_RETAIN_NONATOMIC); + [vars release]; + // no [super init], we're inheriting from NSProxy self.expectationOrderMatters = NO; self.stubs = [[NSMutableArray alloc] init]; @@ -188,11 +206,74 @@ - (void)addInvocation:(NSInvocation *)anInvocation } } -- (void)setExpectationOrderMatters:(BOOL)flag +# pragma mark Getters/Setters +- (OCMockObjectInstanceVars *)mockObjectInstanceVars +{ + return objc_getAssociatedObject(self, OCMockObjectInstanceVarsKey); +} + +- (BOOL)isNice +{ + return self.mockObjectInstanceVars.isNice; +} + +- (BOOL)expectationOrderMatters +{ + return self.mockObjectInstanceVars.expectationOrderMatters; +} + +- (NSMutableArray *)stubs +{ + return self.mockObjectInstanceVars.stubs; +} + +- (NSMutableArray *)expectations +{ + return self.mockObjectInstanceVars.expectations; +} + +- (NSMutableArray *)exceptions { - _expectationOrderMatters = flag; + return self.mockObjectInstanceVars.exceptions; } +- (NSMutableArray *)invocations +{ + return self.mockObjectInstanceVars.invocations; +} + +- (void)setIsNice:(BOOL)isNice +{ + self.mockObjectInstanceVars.isNice = isNice; +} + +- (void)setExpectationOrderMatters:(BOOL)expectationOrderMatters +{ + self.mockObjectInstanceVars.expectationOrderMatters = expectationOrderMatters; +} + +- (void)setStubs:(NSMutableArray *)stubs +{ + self.mockObjectInstanceVars.stubs = stubs; +} + +- (void)setExpectations:(NSMutableArray *)expectations +{ + self.mockObjectInstanceVars.expectations = expectations; +} + +- (void)setExceptions:(NSMutableArray *)exceptions +{ + self.mockObjectInstanceVars.exceptions = exceptions; +} + +- (void)setInvocations:(NSMutableArray *)invocations +{ + self.mockObjectInstanceVars.invocations = invocations; +} + +#pragma mark Public API + - (void)stopMocking { // invocations can contain objects that clients expect to be deallocated by now, @@ -202,12 +283,11 @@ - (void)stopMocking @synchronized(self.invocations) { [self.invocations removeAllObjects]; - [self.invocations autorelease]; - self.invocations = nil; + [self.invocations release]; + self.invocations = nil; } } - - (id)stub { return [[[OCMStubRecorder alloc] initWithMockObject:self] autorelease]; diff --git a/Source/OCMock/OCPartialMockObject.m b/Source/OCMock/OCPartialMockObject.m index ad3b22b4..f66a230b 100644 --- a/Source/OCMock/OCPartialMockObject.m +++ b/Source/OCMock/OCPartialMockObject.m @@ -22,6 +22,16 @@ #import "OCMFunctionsPrivate.h" #import "OCMInvocationStub.h" +@interface OCPartialMockObjectInstanceVars : NSObject +@property (nonatomic, assign) NSObject *realObject; +@property (nonatomic, assign) NSInvocation *invocationFromMock; +@end + +@implementation OCPartialMockObjectInstanceVars +@end + +static const char *OCPartialMockObjectInstanceVarsKey = "OCPartialMockObjectInstanceVarsKey"; + @interface OCPartialMockObject () @property (nonatomic) NSObject *realObject; @property (nonatomic) NSInvocation *invocationFromMock; @@ -38,6 +48,9 @@ - (id)initWithObject:(NSObject *)anObject Class const class = [self classToSubclassForObject:anObject]; [self assertClassIsSupported:class]; self = [super initWithClass:class]; + OCPartialMockObjectInstanceVars *vars = [[OCPartialMockObjectInstanceVars alloc] init]; + objc_setAssociatedObject(self, OCPartialMockObjectInstanceVarsKey, vars, OBJC_ASSOCIATION_RETAIN_NONATOMIC); + [vars release]; self.realObject = [anObject retain]; [self prepareObjectForInstanceMethodMocking]; return self; @@ -48,7 +61,33 @@ - (NSString *)description return [NSString stringWithFormat:@"OCPartialMockObject(%@)", NSStringFromClass(self.mockedClass)]; } -#pragma mark Helper methods +#pragma mark Setters/Getters + +- (OCPartialMockObjectInstanceVars *)partialMockObjectInstanceVars { + return objc_getAssociatedObject(self, OCPartialMockObjectInstanceVarsKey); +} + +- (NSObject *)realObject +{ + return self.partialMockObjectInstanceVars.realObject; +} + +- (void)setRealObject:(NSObject *)realObject +{ + self.partialMockObjectInstanceVars.realObject = realObject; +} + +- (NSInvocation *)invocationFromMock +{ + return self.partialMockObjectInstanceVars.invocationFromMock; +} + +- (void)setInvocationFromMock:(NSInvocation *)invocationFromMock +{ + self.partialMockObjectInstanceVars.invocationFromMock = invocationFromMock; +} + +#pragma mark Helper methods - (void)assertClassIsSupported:(Class)class { diff --git a/Source/OCMockTests/OCMockObjectTests.m b/Source/OCMockTests/OCMockObjectTests.m index 27543673..147bc618 100644 --- a/Source/OCMockTests/OCMockObjectTests.m +++ b/Source/OCMockTests/OCMockObjectTests.m @@ -16,7 +16,9 @@ #import #import "OCMock/OCMock.h" - +#import "OCMBoxedReturnValueProvider.h" +#import "OCPartialMockObject.h" +#import "OCClassMockObject.h" #pragma mark Helper classes and protocols for testing @@ -1157,5 +1159,11 @@ - (void)testTryingToCreateAnInstanceOfOCMockObjectRaisesAnException XCTAssertThrows([[OCMockObject alloc] init]); } +- (void)testMockObjectsHaveNoInstanceVariables +{ + XCTAssertEqual(class_getInstanceSize([NSProxy class]), class_getInstanceSize([OCMockObject class])); + XCTAssertEqual(class_getInstanceSize([NSProxy class]), class_getInstanceSize([OCPartialMockObject class])); + XCTAssertEqual(class_getInstanceSize([NSProxy class]), class_getInstanceSize([OCClassMockObject class])); +} @end From bd8eefd2c6a24dc75ecc95c442a71eb7a8dbc4b3 Mon Sep 17 00:00:00 2001 From: Dave MacLachlan Date: Thu, 9 Jul 2020 20:26:37 -0700 Subject: [PATCH 5/5] Deal with mocks that do direct referencing of instance variables. Reallocate class and partial mocks based on the size of the object they are mocking. For class mocks allow direct referencing of instance variables. For Partial mocks fills the space with 0xEC and will throw an exception if we detect that an instance variable has been written to. The value 0xEB will intentionally likely cause crashes if the memory is read. --- Source/OCMock/OCClassMockObject.h | 2 + Source/OCMock/OCClassMockObject.m | 53 +++++++++++++++++++++++++- Source/OCMock/OCPartialMockObject.m | 27 ++++++++++++- Source/OCMockTests/OCMockObjectTests.m | 39 +++++++++++++++++++ 4 files changed, 118 insertions(+), 3 deletions(-) diff --git a/Source/OCMock/OCClassMockObject.h b/Source/OCMock/OCClassMockObject.h index f5a15fec..48533067 100644 --- a/Source/OCMock/OCClassMockObject.h +++ b/Source/OCMock/OCClassMockObject.h @@ -25,4 +25,6 @@ - (void)assertClassIsSupported:(Class)aClass; +- (void)scribbleOnMemory:(void *)start ofSize:(size_t)size; +- (void)verifyScribbleAt:(void *)start ofSize:(size_t)size; @end diff --git a/Source/OCMock/OCClassMockObject.m b/Source/OCMock/OCClassMockObject.m index ba7976a4..6409d5ab 100644 --- a/Source/OCMock/OCClassMockObject.m +++ b/Source/OCMock/OCClassMockObject.m @@ -30,6 +30,8 @@ @interface OCClassMockObjectInstanceVars : NSObject @property (nonatomic) Class mockedClass; @property (nonatomic) Class originalMetaClass; @property (nonatomic) Class classCreatedForNewMetaClass; +@property (nonatomic) void *classScribbleStart; +@property (nonatomic) size_t classScribbleSize; @end @implementation OCClassMockObjectInstanceVars @@ -39,22 +41,37 @@ @interface OCClassMockObject () @property (nonatomic) Class mockedClass; @property (nonatomic) Class originalMetaClass; @property (nonatomic) Class classCreatedForNewMetaClass; +@property (nonatomic) void *classScribbleStart; +@property (nonatomic) size_t classScribbleSize; @end static const char *OCClassMockObjectInstanceVarsKey = "OCClassMockObjectInstanceVarsKey"; @implementation OCClassMockObject -#pragma mark Initialisers, description, accessors, etc. +#pragma mark Initialisers, description, etc. - (id)initWithClass:(Class)aClass { [self assertClassIsSupported:aClass]; + + size_t allocedSize = class_getInstanceSize(aClass); + Class selfClass = object_getClass(self); + size_t selfSize = class_getInstanceSize(selfClass); + if(allocedSize > selfSize) + { + self = realloc(self, allocedSize); + } self = [super init]; + OCClassMockObjectInstanceVars *vars = [[OCClassMockObjectInstanceVars alloc] init]; objc_setAssociatedObject(self, OCClassMockObjectInstanceVarsKey, vars, OBJC_ASSOCIATION_RETAIN_NONATOMIC); [vars release]; + self.classScribbleSize = allocedSize - selfSize; + self.classScribbleStart = (void *)self + selfSize; + [self scribbleOnMemory:self.classScribbleStart ofSize:self.classScribbleSize]; + self.mockedClass = aClass; [self prepareClassForClassMethodMocking]; return self; @@ -71,9 +88,20 @@ - (NSString *)description return [NSString stringWithFormat:@"OCClassMockObject(%@)", NSStringFromClass(self.mockedClass)]; } +- (void)scribbleOnMemory:(void *)start ofSize:(size_t)size +{ + bzero(start, size); +} + +- (void)verifyScribbleAt:(void *)start ofSize:(size_t)size +{ + // Default version does no verification +} + #pragma mark Setters/Getters -- (OCClassMockObjectInstanceVars *)classMockObjectInstanceVars { +- (OCClassMockObjectInstanceVars *)classMockObjectInstanceVars +{ return objc_getAssociatedObject(self, OCClassMockObjectInstanceVarsKey); } @@ -92,6 +120,16 @@ - (Class)originalMetaClass return self.classMockObjectInstanceVars.originalMetaClass; } +- (void *)classScribbleStart +{ + return self.classMockObjectInstanceVars.classScribbleStart; +} + +- (size_t)classScribbleSize +{ + return self.classMockObjectInstanceVars.classScribbleSize; +} + - (void)setMockedClass:(Class)mockedClass { self.classMockObjectInstanceVars.mockedClass = mockedClass; @@ -120,6 +158,16 @@ - (void)assertClassIsSupported:(Class)aClass } } +- (void)setClassScribbleSize:(size_t)classScribbleSize +{ + self.classMockObjectInstanceVars.classScribbleSize = classScribbleSize; +} + +- (void)setClassScribbleStart:(void *)classScribbleStart +{ + self.classMockObjectInstanceVars.classScribbleStart = classScribbleStart; +} + #pragma mark Extending/overriding superclass behaviour - (void)stopMocking @@ -134,6 +182,7 @@ - (void)stopMocking self.classCreatedForNewMetaClass = nil; } [super stopMocking]; + [self verifyScribbleAt:self.classScribbleStart ofSize:self.classScribbleSize]; } diff --git a/Source/OCMock/OCPartialMockObject.m b/Source/OCMock/OCPartialMockObject.m index f66a230b..a989d819 100644 --- a/Source/OCMock/OCPartialMockObject.m +++ b/Source/OCMock/OCPartialMockObject.m @@ -32,6 +32,11 @@ @implementation OCPartialMockObjectInstanceVars static const char *OCPartialMockObjectInstanceVarsKey = "OCPartialMockObjectInstanceVarsKey"; +// 0xEB chosen intentionally to try and force crashes. +// It has both the high and low bit set, and 0xEBEBEBEBEB..etc +// should be recognizable in a debugger as a bad value. +static uint8_t OCScribbleByte = 0xEB; + @interface OCPartialMockObject () @property (nonatomic) NSObject *realObject; @property (nonatomic) NSInvocation *invocationFromMock; @@ -61,9 +66,29 @@ - (NSString *)description return [NSString stringWithFormat:@"OCPartialMockObject(%@)", NSStringFromClass(self.mockedClass)]; } +- (void)scribbleOnMemory:(void *)start ofSize:(size_t)size; +{ + for(size_t i = 0; i < size; ++i) + { + ((uint8_t*)start)[i] = OCScribbleByte; + } +} + +- (void)verifyScribbleAt:(void *)start ofSize:(size_t)size; +{ + for(size_t i = 0; i < size; ++i) + { + if(((uint8_t*)start)[i] != OCScribbleByte) + { + [NSException raise:NSInternalInconsistencyException format:@"The class that partial mock `%@` does internal direct ivar accesses. You must use the real object instead of the mock for all uses other than setting/verifying stubs/expectations etc.", self]; + } + } +} + #pragma mark Setters/Getters -- (OCPartialMockObjectInstanceVars *)partialMockObjectInstanceVars { +- (OCPartialMockObjectInstanceVars *)partialMockObjectInstanceVars +{ return objc_getAssociatedObject(self, OCPartialMockObjectInstanceVarsKey); } diff --git a/Source/OCMockTests/OCMockObjectTests.m b/Source/OCMockTests/OCMockObjectTests.m index 147bc618..33c23c0f 100644 --- a/Source/OCMockTests/OCMockObjectTests.m +++ b/Source/OCMockTests/OCMockObjectTests.m @@ -216,6 +216,23 @@ + (BOOL)supportsMocking:(NSString **)reasonPtr @end +@interface TestClassLargeClass : NSObject +{ + int foo[4096]; +} +@end + +@implementation TestClassLargeClass + +- (void)dirtyInstanceVariables:(TestClassLargeClass *)cls +{ + for(int i = 0; i < 4096; ++i) { + cls->foo[i] = i; + } +} + +@end + static NSString *TestNotification = @"TestNotification"; @@ -1166,4 +1183,26 @@ - (void)testMockObjectsHaveNoInstanceVariables XCTAssertEqual(class_getInstanceSize([NSProxy class]), class_getInstanceSize([OCClassMockObject class])); } +- (void)testClassMockAllowsDirectMemoryAccess +{ + TestClassLargeClass *one = [[TestClassLargeClass alloc] init]; + id mockOne = OCMClassMock([TestClassLargeClass class]); + [one dirtyInstanceVariables:mockOne]; +} + +- (void)performDirectMemoryAccess +{ + @autoreleasepool { + TestClassLargeClass *one = [[TestClassLargeClass alloc] init]; + TestClassLargeClass *two = [[TestClassLargeClass alloc] init]; + id mockTwo = OCMPartialMock(two); + [one dirtyInstanceVariables:mockTwo]; + } +} + +- (void)testPartialClassMockDoesNotAllowDirectMemoryAccess +{ + XCTAssertThrowsSpecificNamed([self performDirectMemoryAccess], NSException, NSInternalInconsistencyException); +} + @end