Skip to content

Commit bd8eefd

Browse files
committed
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.
1 parent 56066b0 commit bd8eefd

File tree

4 files changed

+118
-3
lines changed

4 files changed

+118
-3
lines changed

Source/OCMock/OCClassMockObject.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,4 +25,6 @@
2525

2626
- (void)assertClassIsSupported:(Class)aClass;
2727

28+
- (void)scribbleOnMemory:(void *)start ofSize:(size_t)size;
29+
- (void)verifyScribbleAt:(void *)start ofSize:(size_t)size;
2830
@end

Source/OCMock/OCClassMockObject.m

Lines changed: 51 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ @interface OCClassMockObjectInstanceVars : NSObject
3030
@property (nonatomic) Class mockedClass;
3131
@property (nonatomic) Class originalMetaClass;
3232
@property (nonatomic) Class classCreatedForNewMetaClass;
33+
@property (nonatomic) void *classScribbleStart;
34+
@property (nonatomic) size_t classScribbleSize;
3335
@end
3436

3537
@implementation OCClassMockObjectInstanceVars
@@ -39,22 +41,37 @@ @interface OCClassMockObject ()
3941
@property (nonatomic) Class mockedClass;
4042
@property (nonatomic) Class originalMetaClass;
4143
@property (nonatomic) Class classCreatedForNewMetaClass;
44+
@property (nonatomic) void *classScribbleStart;
45+
@property (nonatomic) size_t classScribbleSize;
4246
@end
4347

4448
static const char *OCClassMockObjectInstanceVarsKey = "OCClassMockObjectInstanceVarsKey";
4549

4650
@implementation OCClassMockObject
4751

48-
#pragma mark Initialisers, description, accessors, etc.
52+
#pragma mark Initialisers, description, etc.
4953

5054
- (id)initWithClass:(Class)aClass
5155
{
5256
[self assertClassIsSupported:aClass];
57+
58+
size_t allocedSize = class_getInstanceSize(aClass);
59+
Class selfClass = object_getClass(self);
60+
size_t selfSize = class_getInstanceSize(selfClass);
61+
if(allocedSize > selfSize)
62+
{
63+
self = realloc(self, allocedSize);
64+
}
5365
self = [super init];
66+
5467
OCClassMockObjectInstanceVars *vars = [[OCClassMockObjectInstanceVars alloc] init];
5568
objc_setAssociatedObject(self, OCClassMockObjectInstanceVarsKey, vars, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
5669
[vars release];
5770

71+
self.classScribbleSize = allocedSize - selfSize;
72+
self.classScribbleStart = (void *)self + selfSize;
73+
[self scribbleOnMemory:self.classScribbleStart ofSize:self.classScribbleSize];
74+
5875
self.mockedClass = aClass;
5976
[self prepareClassForClassMethodMocking];
6077
return self;
@@ -71,9 +88,20 @@ - (NSString *)description
7188
return [NSString stringWithFormat:@"OCClassMockObject(%@)", NSStringFromClass(self.mockedClass)];
7289
}
7390

91+
- (void)scribbleOnMemory:(void *)start ofSize:(size_t)size
92+
{
93+
bzero(start, size);
94+
}
95+
96+
- (void)verifyScribbleAt:(void *)start ofSize:(size_t)size
97+
{
98+
// Default version does no verification
99+
}
100+
74101
#pragma mark Setters/Getters
75102

76-
- (OCClassMockObjectInstanceVars *)classMockObjectInstanceVars {
103+
- (OCClassMockObjectInstanceVars *)classMockObjectInstanceVars
104+
{
77105
return objc_getAssociatedObject(self, OCClassMockObjectInstanceVarsKey);
78106
}
79107

@@ -92,6 +120,16 @@ - (Class)originalMetaClass
92120
return self.classMockObjectInstanceVars.originalMetaClass;
93121
}
94122

123+
- (void *)classScribbleStart
124+
{
125+
return self.classMockObjectInstanceVars.classScribbleStart;
126+
}
127+
128+
- (size_t)classScribbleSize
129+
{
130+
return self.classMockObjectInstanceVars.classScribbleSize;
131+
}
132+
95133
- (void)setMockedClass:(Class)mockedClass
96134
{
97135
self.classMockObjectInstanceVars.mockedClass = mockedClass;
@@ -120,6 +158,16 @@ - (void)assertClassIsSupported:(Class)aClass
120158
}
121159
}
122160

161+
- (void)setClassScribbleSize:(size_t)classScribbleSize
162+
{
163+
self.classMockObjectInstanceVars.classScribbleSize = classScribbleSize;
164+
}
165+
166+
- (void)setClassScribbleStart:(void *)classScribbleStart
167+
{
168+
self.classMockObjectInstanceVars.classScribbleStart = classScribbleStart;
169+
}
170+
123171
#pragma mark Extending/overriding superclass behaviour
124172

125173
- (void)stopMocking
@@ -134,6 +182,7 @@ - (void)stopMocking
134182
self.classCreatedForNewMetaClass = nil;
135183
}
136184
[super stopMocking];
185+
[self verifyScribbleAt:self.classScribbleStart ofSize:self.classScribbleSize];
137186
}
138187

139188

Source/OCMock/OCPartialMockObject.m

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,11 @@ @implementation OCPartialMockObjectInstanceVars
3232

3333
static const char *OCPartialMockObjectInstanceVarsKey = "OCPartialMockObjectInstanceVarsKey";
3434

35+
// 0xEB chosen intentionally to try and force crashes.
36+
// It has both the high and low bit set, and 0xEBEBEBEBEB..etc
37+
// should be recognizable in a debugger as a bad value.
38+
static uint8_t OCScribbleByte = 0xEB;
39+
3540
@interface OCPartialMockObject ()
3641
@property (nonatomic) NSObject *realObject;
3742
@property (nonatomic) NSInvocation *invocationFromMock;
@@ -61,9 +66,29 @@ - (NSString *)description
6166
return [NSString stringWithFormat:@"OCPartialMockObject(%@)", NSStringFromClass(self.mockedClass)];
6267
}
6368

69+
- (void)scribbleOnMemory:(void *)start ofSize:(size_t)size;
70+
{
71+
for(size_t i = 0; i < size; ++i)
72+
{
73+
((uint8_t*)start)[i] = OCScribbleByte;
74+
}
75+
}
76+
77+
- (void)verifyScribbleAt:(void *)start ofSize:(size_t)size;
78+
{
79+
for(size_t i = 0; i < size; ++i)
80+
{
81+
if(((uint8_t*)start)[i] != OCScribbleByte)
82+
{
83+
[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];
84+
}
85+
}
86+
}
87+
6488
#pragma mark Setters/Getters
6589

66-
- (OCPartialMockObjectInstanceVars *)partialMockObjectInstanceVars {
90+
- (OCPartialMockObjectInstanceVars *)partialMockObjectInstanceVars
91+
{
6792
return objc_getAssociatedObject(self, OCPartialMockObjectInstanceVarsKey);
6893
}
6994

Source/OCMockTests/OCMockObjectTests.m

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,23 @@ + (BOOL)supportsMocking:(NSString **)reasonPtr
216216

217217
@end
218218

219+
@interface TestClassLargeClass : NSObject
220+
{
221+
int foo[4096];
222+
}
223+
@end
224+
225+
@implementation TestClassLargeClass
226+
227+
- (void)dirtyInstanceVariables:(TestClassLargeClass *)cls
228+
{
229+
for(int i = 0; i < 4096; ++i) {
230+
cls->foo[i] = i;
231+
}
232+
}
233+
234+
@end
235+
219236
static NSString *TestNotification = @"TestNotification";
220237

221238

@@ -1166,4 +1183,26 @@ - (void)testMockObjectsHaveNoInstanceVariables
11661183
XCTAssertEqual(class_getInstanceSize([NSProxy class]), class_getInstanceSize([OCClassMockObject class]));
11671184
}
11681185

1186+
- (void)testClassMockAllowsDirectMemoryAccess
1187+
{
1188+
TestClassLargeClass *one = [[TestClassLargeClass alloc] init];
1189+
id mockOne = OCMClassMock([TestClassLargeClass class]);
1190+
[one dirtyInstanceVariables:mockOne];
1191+
}
1192+
1193+
- (void)performDirectMemoryAccess
1194+
{
1195+
@autoreleasepool {
1196+
TestClassLargeClass *one = [[TestClassLargeClass alloc] init];
1197+
TestClassLargeClass *two = [[TestClassLargeClass alloc] init];
1198+
id mockTwo = OCMPartialMock(two);
1199+
[one dirtyInstanceVariables:mockTwo];
1200+
}
1201+
}
1202+
1203+
- (void)testPartialClassMockDoesNotAllowDirectMemoryAccess
1204+
{
1205+
XCTAssertThrowsSpecificNamed([self performDirectMemoryAccess], NSException, NSInternalInconsistencyException);
1206+
}
1207+
11691208
@end

0 commit comments

Comments
 (0)