Skip to content

Commit d9aa80e

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 443d3c8 commit d9aa80e

File tree

5 files changed

+120
-5
lines changed

5 files changed

+120
-5
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/OCMockObject.m

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ + (id)observerMock
105105
}
106106

107107

108-
#pragma mark Initialisers, description, accessors, etc.
108+
#pragma mark Initialisers, description, etc.
109109

110110
- (instancetype)init
111111
{

Source/OCMock/OCPartialMockObject.m

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,14 +32,19 @@ @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;
3843
@end
3944

4045
@implementation OCPartialMockObject
4146

42-
#pragma mark Initialisers, description, accessors, etc.
47+
#pragma mark Initialisers, description, etc.
4348

4449
- (id)initWithObject:(NSObject *)anObject
4550
{
@@ -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
@@ -215,6 +215,23 @@ + (BOOL)supportsMocking:(NSString **)reasonPtr
215215

216216
@end
217217

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

220237

@@ -1167,6 +1184,28 @@ - (void)testMockObjectsHaveNoInstanceVariables
11671184
XCTAssertEqual(class_getInstanceSize([NSProxy class]), class_getInstanceSize([OCClassMockObject class]));
11681185
}
11691186

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

11721211

0 commit comments

Comments
 (0)