Skip to content

Commit c7b3b28

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 a837057 commit c7b3b28

File tree

5 files changed

+144
-18
lines changed

5 files changed

+144
-18
lines changed

Source/OCMock/OCClassMockObject.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,4 +23,6 @@
2323
- (Class)mockedClass;
2424
- (Class)mockObjectClass; // since -class returns the mockedClass
2525

26+
- (void)scribbleOnMemory:(void *)start ofSize:(size_t)size;
27+
- (void)verifyScribbleAt:(void *)start ofSize:(size_t)size;
2628
@end

Source/OCMock/OCClassMockObject.m

Lines changed: 49 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ @interface OCClassMockObjectInstanceVars : NSObject
2525
@property (nonatomic) Class mockedClass;
2626
@property (nonatomic) Class originalMetaClass;
2727
@property (nonatomic) Class classCreatedForNewMetaClass;
28+
@property (nonatomic) void *classScribbleStart;
29+
@property (nonatomic) size_t classScribbleSize;
2830
@end
2931

3032
@implementation OCClassMockObjectInstanceVars
@@ -34,24 +36,38 @@ @interface OCClassMockObject ()
3436
@property (nonatomic) Class mockedClass;
3537
@property (nonatomic) Class originalMetaClass;
3638
@property (nonatomic) Class classCreatedForNewMetaClass;
39+
@property (nonatomic) void *classScribbleStart;
40+
@property (nonatomic) size_t classScribbleSize;
3741
@end
3842

3943
static const char *OCClassMockObjectInstanceVarsKey = "OCClassMockObjectInstanceVarsKey";
4044

4145
@implementation OCClassMockObject
4246

43-
#pragma mark Initialisers, description, accessors, etc.
47+
#pragma mark Initialisers, description, etc.
4448

4549
- (id)initWithClass:(Class)aClass
4650
{
4751
if(aClass == Nil)
4852
[NSException raise:NSInvalidArgumentException format:@"Class cannot be Nil."];
4953

54+
size_t allocedSize = class_getInstanceSize(aClass);
55+
Class selfClass = object_getClass(self);
56+
size_t selfSize = class_getInstanceSize(selfClass);
57+
if(allocedSize > selfSize)
58+
{
59+
self = realloc(self, allocedSize);
60+
}
5061
self = [super init];
62+
5163
OCClassMockObjectInstanceVars *vars = [[OCClassMockObjectInstanceVars alloc] init];
5264
objc_setAssociatedObject(self, OCClassMockObjectInstanceVarsKey, vars, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
5365
[vars release];
5466

67+
self.classScribbleSize = allocedSize - selfSize;
68+
self.classScribbleStart = (void *)self + selfSize;
69+
[self scribbleOnMemory:self.classScribbleStart ofSize:self.classScribbleSize];
70+
5571
self.mockedClass = aClass;
5672
[self prepareClassForClassMethodMocking];
5773
return self;
@@ -68,9 +84,20 @@ - (NSString *)description
6884
return [NSString stringWithFormat:@"OCClassMockObject(%@)", NSStringFromClass(self.mockedClass)];
6985
}
7086

87+
- (void)scribbleOnMemory:(void *)start ofSize:(size_t)size
88+
{
89+
bzero(start, size);
90+
}
91+
92+
- (void)verifyScribbleAt:(void *)start ofSize:(size_t)size
93+
{
94+
// Default version does no verification
95+
}
96+
7197
#pragma mark Setters/Getters
7298

73-
- (OCClassMockObjectInstanceVars *)classMockObjectInstanceVars {
99+
- (OCClassMockObjectInstanceVars *)classMockObjectInstanceVars
100+
{
74101
return objc_getAssociatedObject(self, OCClassMockObjectInstanceVarsKey);
75102
}
76103

@@ -89,6 +116,16 @@ - (Class)originalMetaClass
89116
return self.classMockObjectInstanceVars.originalMetaClass;
90117
}
91118

119+
- (void *)classScribbleStart
120+
{
121+
return self.classMockObjectInstanceVars.classScribbleStart;
122+
}
123+
124+
- (size_t)classScribbleSize
125+
{
126+
return self.classMockObjectInstanceVars.classScribbleSize;
127+
}
128+
92129
- (void)setMockedClass:(Class)mockedClass
93130
{
94131
self.classMockObjectInstanceVars.mockedClass = mockedClass;
@@ -104,6 +141,15 @@ - (void)setOriginalMetaClass:(Class)originalMetaClass
104141
self.classMockObjectInstanceVars.originalMetaClass = originalMetaClass;
105142
}
106143

144+
- (void)setClassScribbleSize:(size_t)classScribbleSize
145+
{
146+
self.classMockObjectInstanceVars.classScribbleSize = classScribbleSize;
147+
}
148+
149+
- (void)setClassScribbleStart:(void *)classScribbleStart
150+
{
151+
self.classMockObjectInstanceVars.classScribbleStart = classScribbleStart;
152+
}
107153

108154
#pragma mark Extending/overriding superclass behaviour
109155

@@ -119,6 +165,7 @@ - (void)stopMocking
119165
self.classCreatedForNewMetaClass = nil;
120166
}
121167
[super stopMocking];
168+
[self verifyScribbleAt:self.classScribbleStart ofSize:self.classScribbleSize];
122169
}
123170

124171

Source/OCMock/OCMockObject.m

Lines changed: 28 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ + (id)observerMock
106106
}
107107

108108

109-
#pragma mark Initialisers, description, accessors, etc.
109+
#pragma mark Initialisers, description, etc.
110110

111111
- (instancetype)init
112112
{
@@ -176,47 +176,61 @@ - (void)assertInvocationsArrayIsPresent
176176
}
177177

178178
# pragma mark Getters/Setters
179-
- (OCMockObjectInstanceVars *)mockObjectInstanceVars {
179+
- (OCMockObjectInstanceVars *)mockObjectInstanceVars
180+
{
180181
return objc_getAssociatedObject(self, OCMockObjectInstanceVarsKey);
181182
}
182183

183-
- (BOOL)isNice {
184+
- (BOOL)isNice
185+
{
184186
return self.mockObjectInstanceVars.isNice;
185187
}
186188

187-
- (BOOL)expectationOrderMatters {
189+
- (BOOL)expectationOrderMatters
190+
{
188191
return self.mockObjectInstanceVars.expectationOrderMatters;
189192
}
190-
- (NSMutableArray *)stubs {
193+
- (NSMutableArray *)stubs
194+
{
191195
return self.mockObjectInstanceVars.stubs;
192196
}
193-
- (NSMutableArray *)expectations {
197+
- (NSMutableArray *)expectations
198+
{
194199
return self.mockObjectInstanceVars.expectations;
195200
}
196-
- (NSMutableArray *)exceptions {
201+
- (NSMutableArray *)exceptions
202+
{
197203
return self.mockObjectInstanceVars.exceptions;
198204
}
199-
- (NSMutableArray *)invocations {
205+
206+
- (NSMutableArray *)invocations
207+
{
200208
return self.mockObjectInstanceVars.invocations;
201209
}
202210

203-
- (void)setIsNice:(BOOL)isNice {
211+
- (void)setIsNice:(BOOL)isNice
212+
{
204213
self.mockObjectInstanceVars.isNice = isNice;
205214
}
206215

207-
- (void)setExpectationOrderMatters:(BOOL)expectationOrderMatters {
216+
- (void)setExpectationOrderMatters:(BOOL)expectationOrderMatters
217+
{
208218
self.mockObjectInstanceVars.expectationOrderMatters = expectationOrderMatters;
209219
}
210-
- (void)setStubs:(NSMutableArray *)stubs {
220+
- (void)setStubs:(NSMutableArray *)stubs
221+
{
211222
self.mockObjectInstanceVars.stubs = stubs;
212223
}
213-
- (void)setExpectations:(NSMutableArray *)expectations {
224+
- (void)setExpectations:(NSMutableArray *)expectations
225+
{
214226
self.mockObjectInstanceVars.expectations = expectations;
215227
}
216-
- (void)setExceptions:(NSMutableArray *)exceptions {
228+
- (void)setExceptions:(NSMutableArray *)exceptions
229+
{
217230
self.mockObjectInstanceVars.exceptions = exceptions;
218231
}
219-
- (void)setInvocations:(NSMutableArray *)invocations {
232+
- (void)setInvocations:(NSMutableArray *)invocations
233+
{
220234
self.mockObjectInstanceVars.invocations = invocations;
221235
}
222236

Source/OCMock/OCPartialMockObject.m

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

3944
@implementation OCPartialMockObject
4045

41-
#pragma mark Initialisers, description, accessors, etc.
46+
#pragma mark Initialisers, description, etc.
4247

4348
- (id)initWithObject:(NSObject *)anObject
4449
{
@@ -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: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,22 @@ - (NSString *)stringValue;
193193

194194
@end
195195

196+
@interface TestClassLargeClass : NSObject
197+
{
198+
int foo[4096];
199+
}
200+
@end
201+
202+
@implementation TestClassLargeClass
203+
204+
- (void)dirtyInstanceVariables:(TestClassLargeClass *)cls
205+
{
206+
for(int i = 0; i < 4096; ++i) {
207+
cls->foo[i] = i;
208+
}
209+
}
210+
211+
@end
196212

197213
static NSString *TestNotification = @"TestNotification";
198214

@@ -1108,6 +1124,28 @@ - (void)testMockObjectsHaveNoInstanceVariables
11081124
XCTAssertEqual(class_getInstanceSize([NSProxy class]), class_getInstanceSize([OCClassMockObject class]));
11091125
}
11101126

1127+
- (void)testClassMockAllowsDirectMemoryAccess
1128+
{
1129+
TestClassLargeClass *one = [[TestClassLargeClass alloc] init];
1130+
id mockOne = OCMClassMock([TestClassLargeClass class]);
1131+
[one dirtyInstanceVariables:mockOne];
1132+
}
1133+
1134+
- (void)performDirectMemoryAccess
1135+
{
1136+
@autoreleasepool {
1137+
TestClassLargeClass *one = [[TestClassLargeClass alloc] init];
1138+
TestClassLargeClass *two = [[TestClassLargeClass alloc] init];
1139+
id mockTwo = OCMPartialMock(two);
1140+
[one dirtyInstanceVariables:mockTwo];
1141+
}
1142+
}
1143+
1144+
- (void)testPartialClassMockDoesNotAllowDirectMemoryAccess
1145+
{
1146+
XCTAssertThrowsSpecificNamed([self performDirectMemoryAccess], NSException, NSInternalInconsistencyException);
1147+
}
1148+
11111149
@end
11121150

11131151

0 commit comments

Comments
 (0)