Skip to content

Commit a0a3113

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 d423291 commit a0a3113

File tree

5 files changed

+117
-5
lines changed

5 files changed

+117
-5
lines changed

Source/OCMock/OCClassMockObject.h

+2
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

+49-2
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

+1-1
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
{

Source/OCMock/OCPartialMockObject.m

+27-2
Original file line numberDiff line numberDiff line change
@@ -33,14 +33,19 @@ @implementation OCPartialMockObjectInstanceVars
3333

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

36+
// 0xEB chosen intentionally to try and force crashes.
37+
// It has both the high and low bit set, and 0xEBEBEBEBEB..etc
38+
// should be recognizable in a debugger as a bad value.
39+
static uint8_t OCScribbleByte = 0xEB;
40+
3641
@interface OCPartialMockObject ()
3742
@property (nonatomic) NSObject *realObject;
3843
@property (nonatomic) NSInvocation *invocationFromMock;
3944
@end
4045

4146
@implementation OCPartialMockObject
4247

43-
#pragma mark Initialisers, description, accessors, etc.
48+
#pragma mark Initialisers, description, etc.
4449

4550
- (id)initWithObject:(NSObject *)anObject
4651
{
@@ -63,9 +68,29 @@ - (NSString *)description
6368
return [NSString stringWithFormat:@"OCPartialMockObject(%@)", NSStringFromClass(self.mockedClass)];
6469
}
6570

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

68-
- (OCPartialMockObjectInstanceVars *)partialMockObjectInstanceVars {
92+
- (OCPartialMockObjectInstanceVars *)partialMockObjectInstanceVars
93+
{
6994
return objc_getAssociatedObject(self, OCPartialMockObjectInstanceVarsKey);
7095
}
7196

Source/OCMockTests/OCMockObjectTests.m

+38
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)