diff --git a/Source/OCMock/OCClassMockObject.h b/Source/OCMock/OCClassMockObject.h index d654d810..1130cd15 100644 --- a/Source/OCMock/OCClassMockObject.h +++ b/Source/OCMock/OCClassMockObject.h @@ -23,4 +23,6 @@ - (Class)mockedClass; - (Class)mockObjectClass; // since -class returns the mockedClass +- (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 e2aa3680..acfe9c69 100644 --- a/Source/OCMock/OCClassMockObject.m +++ b/Source/OCMock/OCClassMockObject.m @@ -25,6 +25,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 @@ -34,24 +36,38 @@ @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 { if(aClass == Nil) [NSException raise:NSInvalidArgumentException format:@"Class cannot be Nil."]; + 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; @@ -68,9 +84,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); } @@ -89,6 +116,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; @@ -104,6 +141,15 @@ - (void)setOriginalMetaClass:(Class)originalMetaClass self.classMockObjectInstanceVars.originalMetaClass = originalMetaClass; } +- (void)setClassScribbleSize:(size_t)classScribbleSize +{ + self.classMockObjectInstanceVars.classScribbleSize = classScribbleSize; +} + +- (void)setClassScribbleStart:(void *)classScribbleStart +{ + self.classMockObjectInstanceVars.classScribbleStart = classScribbleStart; +} #pragma mark Extending/overriding superclass behaviour @@ -119,6 +165,7 @@ - (void)stopMocking self.classCreatedForNewMetaClass = nil; } [super stopMocking]; + [self verifyScribbleAt:self.classScribbleStart ofSize:self.classScribbleSize]; } diff --git a/Source/OCMock/OCMockObject.m b/Source/OCMock/OCMockObject.m index 95b1911e..bc6d03c1 100644 --- a/Source/OCMock/OCMockObject.m +++ b/Source/OCMock/OCMockObject.m @@ -106,7 +106,7 @@ + (id)observerMock } -#pragma mark Initialisers, description, accessors, etc. +#pragma mark Initialisers, description, etc. - (instancetype)init { @@ -176,47 +176,61 @@ - (void)assertInvocationsArrayIsPresent } # pragma mark Getters/Setters -- (OCMockObjectInstanceVars *)mockObjectInstanceVars { +- (OCMockObjectInstanceVars *)mockObjectInstanceVars +{ return objc_getAssociatedObject(self, OCMockObjectInstanceVarsKey); } -- (BOOL)isNice { +- (BOOL)isNice +{ return self.mockObjectInstanceVars.isNice; } -- (BOOL)expectationOrderMatters { +- (BOOL)expectationOrderMatters +{ return self.mockObjectInstanceVars.expectationOrderMatters; } -- (NSMutableArray *)stubs { +- (NSMutableArray *)stubs +{ return self.mockObjectInstanceVars.stubs; } -- (NSMutableArray *)expectations { +- (NSMutableArray *)expectations +{ return self.mockObjectInstanceVars.expectations; } -- (NSMutableArray *)exceptions { +- (NSMutableArray *)exceptions +{ return self.mockObjectInstanceVars.exceptions; } -- (NSMutableArray *)invocations { + +- (NSMutableArray *)invocations +{ return self.mockObjectInstanceVars.invocations; } -- (void)setIsNice:(BOOL)isNice { +- (void)setIsNice:(BOOL)isNice +{ self.mockObjectInstanceVars.isNice = isNice; } -- (void)setExpectationOrderMatters:(BOOL)expectationOrderMatters { +- (void)setExpectationOrderMatters:(BOOL)expectationOrderMatters +{ self.mockObjectInstanceVars.expectationOrderMatters = expectationOrderMatters; } -- (void)setStubs:(NSMutableArray *)stubs { +- (void)setStubs:(NSMutableArray *)stubs +{ self.mockObjectInstanceVars.stubs = stubs; } -- (void)setExpectations:(NSMutableArray *)expectations { +- (void)setExpectations:(NSMutableArray *)expectations +{ self.mockObjectInstanceVars.expectations = expectations; } -- (void)setExceptions:(NSMutableArray *)exceptions { +- (void)setExceptions:(NSMutableArray *)exceptions +{ self.mockObjectInstanceVars.exceptions = exceptions; } -- (void)setInvocations:(NSMutableArray *)invocations { +- (void)setInvocations:(NSMutableArray *)invocations +{ self.mockObjectInstanceVars.invocations = invocations; } diff --git a/Source/OCMock/OCPartialMockObject.m b/Source/OCMock/OCPartialMockObject.m index c1f3d0f5..c6368a10 100644 --- a/Source/OCMock/OCPartialMockObject.m +++ b/Source/OCMock/OCPartialMockObject.m @@ -32,13 +32,18 @@ @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; @end @implementation OCPartialMockObject -#pragma mark Initialisers, description, accessors, etc. +#pragma mark Initialisers, description, etc. - (id)initWithObject:(NSObject *)anObject { @@ -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 081fd058..833ddffc 100644 --- a/Source/OCMockTests/OCMockObjectTests.m +++ b/Source/OCMockTests/OCMockObjectTests.m @@ -193,6 +193,22 @@ - (NSString *)stringValue; @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"; @@ -1108,6 +1124,28 @@ - (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