-
Notifications
You must be signed in to change notification settings - Fork 517
/
Copy pathaddMethods.m
329 lines (288 loc) · 10.5 KB
/
addMethods.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
// TEST_CONFIG
#include "test.h"
#include "testroot.i"
#include <objc/runtime.h>
#include <objc/objc-internal.h>
// Macros for array construction.
// ten IMPs
#define IMPS10 (IMP)fn0, (IMP)fn1, (IMP)fn2, (IMP)fn3, (IMP)fn4, \
(IMP)fn5, (IMP)fn6, (IMP)fn7, (IMP)fn8, (IMP)fn9
// ten method types
#define TYPES10 "", "", "", "", "", "", "", "", "", ""
// ten selectors of the form name0..name9
#define SELS10(name) \
@selector(name##0), @selector(name##1), @selector(name##2), \
@selector(name##3), @selector(name##4), @selector(name##5), \
@selector(name##6), @selector(name##7), @selector(name##8), \
@selector(name##9)
@interface Super : TestRoot @end
@implementation Super
-(int)superMethod0 { return 0; }
-(int)superMethod1 { return 0; }
-(int)superMethod2 { return 0; }
-(int)superMethod3 { return 0; }
-(int)superMethod4 { return 0; }
-(int)superMethod5 { return 0; }
-(int)superMethod6 { return 0; }
-(int)superMethod7 { return 0; }
-(int)superMethod8 { return 0; }
-(int)superMethod9 { return 0; }
-(int)bothMethod0 { return 0; }
-(int)bothMethod1 { return 0; }
-(int)bothMethod2 { return 0; }
-(int)bothMethod3 { return 0; }
-(int)bothMethod4 { return 0; }
-(int)bothMethod5 { return 0; }
-(int)bothMethod6 { return 0; }
-(int)bothMethod7 { return 0; }
-(int)bothMethod8 { return 0; }
-(int)bothMethod9 { return 0; }
@end
@interface Sub : Super @end
@implementation Sub
-(int)subMethod0 { return 0; }
-(int)subMethod1 { return 0; }
-(int)subMethod2 { return 0; }
-(int)subMethod3 { return 0; }
-(int)subMethod4 { return 0; }
-(int)subMethod5 { return 0; }
-(int)subMethod6 { return 0; }
-(int)subMethod7 { return 0; }
-(int)subMethod8 { return 0; }
-(int)subMethod9 { return 0; }
-(int)bothMethod0 { return 0; }
-(int)bothMethod1 { return 0; }
-(int)bothMethod2 { return 0; }
-(int)bothMethod3 { return 0; }
-(int)bothMethod4 { return 0; }
-(int)bothMethod5 { return 0; }
-(int)bothMethod6 { return 0; }
-(int)bothMethod7 { return 0; }
-(int)bothMethod8 { return 0; }
-(int)bothMethod9 { return 0; }
@end
@interface Sub2 : Super @end
@implementation Sub2
-(int)subMethod0 { return 0; }
-(int)subMethod1 { return 0; }
-(int)subMethod2 { return 0; }
-(int)subMethod3 { return 0; }
-(int)subMethod4 { return 0; }
-(int)subMethod5 { return 0; }
-(int)subMethod6 { return 0; }
-(int)subMethod7 { return 0; }
-(int)subMethod8 { return 0; }
-(int)subMethod9 { return 0; }
-(int)bothMethod0 { return 0; }
-(int)bothMethod1 { return 0; }
-(int)bothMethod2 { return 0; }
-(int)bothMethod3 { return 0; }
-(int)bothMethod4 { return 0; }
-(int)bothMethod5 { return 0; }
-(int)bothMethod6 { return 0; }
-(int)bothMethod7 { return 0; }
-(int)bothMethod8 { return 0; }
-(int)bothMethod9 { return 0; }
@end
id fn0(id self __attribute__((unused)), SEL cmd __attribute__((unused)), ...) {
return nil;
}
id fn1(id self __attribute__((unused)), SEL cmd __attribute__((unused)), ...) {
return nil;
}
id fn2(id self __attribute__((unused)), SEL cmd __attribute__((unused)), ...) {
return nil;
}
id fn3(id self __attribute__((unused)), SEL cmd __attribute__((unused)), ...) {
return nil;
}
id fn4(id self __attribute__((unused)), SEL cmd __attribute__((unused)), ...) {
return nil;
}
id fn5(id self __attribute__((unused)), SEL cmd __attribute__((unused)), ...) {
return nil;
}
id fn6(id self __attribute__((unused)), SEL cmd __attribute__((unused)), ...) {
return nil;
}
id fn7(id self __attribute__((unused)), SEL cmd __attribute__((unused)), ...) {
return nil;
}
id fn8(id self __attribute__((unused)), SEL cmd __attribute__((unused)), ...) {
return nil;
}
id fn9(id self __attribute__((unused)), SEL cmd __attribute__((unused)), ...) {
return nil;
}
void testBulkMemoryOnce(void)
{
Class c = objc_allocateClassPair([TestRoot class], "c", 0);
objc_registerClassPair(c);
SEL sels[10] = {
SELS10(method)
};
IMP imps[10] = {
IMPS10
};
const char *types[10] = {
TYPES10
};
uint32_t failureCount = 0;
SEL *failed;
// Test all successes.
failed = class_addMethodsBulk(c, sels, imps, types, 4, &failureCount);
testassert(failed == NULL);
testassert(failureCount == 0);
// Test mixed success and failure (this overlaps the previous one, so there
// will be one of each).
failed = class_addMethodsBulk(c, sels + 3, imps + 3, types + 3, 2,
&failureCount);
testassert(failed != NULL);
testassert(failureCount == 1);
testassert(failed[0] == sels[3]);
free(failed);
// Test total failure.
failed = class_addMethodsBulk(c, sels, imps, types, 5, &failureCount);
testassert(failed != NULL);
testassert(failureCount == 5);
for(int i = 0; i < 5; i++) {
testassert(failed[i] == sels[i]);
}
free(failed);
class_replaceMethodsBulk(c, sels, imps, types, 10);
for(int i = 0; i < 10; i++) {
testassert(class_getMethodImplementation(c, sels[i]) == imps[i]);
}
objc_disposeClassPair(c);
}
int main()
{
IMP dummyIMPs[130] = {
IMPS10, IMPS10, IMPS10, IMPS10, IMPS10,
IMPS10, IMPS10, IMPS10, IMPS10, IMPS10,
IMPS10, IMPS10, IMPS10,
};
// similar to dummyIMPs but with different values in each slot
IMP dummyIMPs2[130] = {
(IMP)fn5, (IMP)fn6, (IMP)fn7, (IMP)fn8, (IMP)fn9,
IMPS10, IMPS10, IMPS10, IMPS10, IMPS10,
IMPS10, IMPS10, IMPS10, IMPS10, IMPS10,
IMPS10, IMPS10,
(IMP)fn0, (IMP)fn1, (IMP)fn2, (IMP)fn3, (IMP)fn4,
};
const char *dummyTypes[130] = {
TYPES10, TYPES10, TYPES10, TYPES10, TYPES10,
TYPES10, TYPES10, TYPES10, TYPES10, TYPES10,
TYPES10, TYPES10, TYPES10,
};
SEL addSELs[20] = {
SELS10(superMethod),
SELS10(superMethodAddNew)
};
uint32_t failedCount = 0;
SEL *failed;
failed = class_addMethodsBulk([Super class], addSELs, dummyIMPs, dummyTypes,
20, &failedCount);
// class_addMethodsBulk reports failures for all methods that already exist
testassert(failed != NULL);
testassert(failedCount == 10);
// class_addMethodsBulk failed for existing implementations
for(int i = 0; i < 10; i++) {
testassert(failed[i] == addSELs[i]);
testassert(class_getMethodImplementation([Super class], addSELs[i])
!= dummyIMPs[i]);
}
free(failed);
// class_addMethodsBulk does add root implementations
for(int i = 10; i < 20; i++) {
testassert(class_getMethodImplementation([Super class], addSELs[i])
== dummyIMPs[i]);
}
// class_addMethod does override superclass implementations
failed = class_addMethodsBulk([Sub class], addSELs, dummyIMPs, dummyTypes,
10, &failedCount);
testassert(failedCount == 0);
testassert(failed == NULL);
for(int i = 0; i < 10; i++) {
testassert(class_getMethodImplementation([Sub class], addSELs[i])
== dummyIMPs[i]);
}
SEL subReplaceSELs[40] = {
SELS10(superMethod),
SELS10(subMethodNew),
SELS10(subMethod),
SELS10(bothMethod),
};
// class_replaceMethodsBulk adds new implementations or replaces existing
// ones for methods that exist on the superclass, the subclass, both, or
// neither
class_replaceMethodsBulk([Sub2 class], subReplaceSELs, dummyIMPs,
dummyTypes, 40);
for(int i = 0; i < 40; i++) {
IMP newIMP = class_getMethodImplementation([Sub2 class],
subReplaceSELs[i]);
testassert(newIMP == dummyIMPs[i]);
}
SEL superReplaceSELs[20] = {
SELS10(superMethod),
SELS10(superMethodNew),
};
// class_replaceMethodsBulk adds new implementations or replaces existing
// ones in the superclass
class_replaceMethodsBulk([Super class], superReplaceSELs, dummyIMPs,
dummyTypes, 20);
for(int i = 0; i < 20; i++) {
IMP newIMP = class_getMethodImplementation([Super class],
superReplaceSELs[i]);
testassert(newIMP == dummyIMPs[i]);
}
// class_addMethodsBulk, where almost all of the requested additions
// already exist and thus can't be added. (They were already added
// above by class_replaceMethodsBulk([Sub2 class], subReplaceSELs, ...).)
// This list is large in the hope of provoking any realloc() of the
// new method list inside addMethods().
// The runtime doesn't care that the list contains lots of duplicates.
SEL subAddMostlyExistingSELs[130] = {
SELS10(superMethod), SELS10(subMethodNew), SELS10(subMethod),
SELS10(superMethod), SELS10(subMethodNew), SELS10(subMethod),
SELS10(superMethod), SELS10(subMethodNew), SELS10(subMethod),
SELS10(superMethod), SELS10(subMethodNew), SELS10(subMethod),
SELS10(bothMethod),
};
subAddMostlyExistingSELs[16] = @selector(INDEX_16_IS_DIFFERENT);
failed = class_addMethodsBulk([Sub2 class], subAddMostlyExistingSELs,
dummyIMPs2, dummyTypes, 130, &failedCount);
testassert(failedCount == 129);
testassert(failed != NULL);
for(int i = 0; i < 130; i++) {
IMP newIMP = class_getMethodImplementation([Sub2 class],
subAddMostlyExistingSELs[i]);
if (i == 16) {
// the only one that was actually added
testassert(newIMP != dummyIMPs[i]);
testassert(newIMP == dummyIMPs2[i]);
} else {
// the others should all have failed
testassert(newIMP == dummyIMPs[i]);
testassert(newIMP != dummyIMPs2[i]);
}
}
for (uint32_t i = 0; i < failedCount; i++) {
testassert(failed[i] != NULL);
testassert(failed[i] != subAddMostlyExistingSELs[16]);
}
// fixme actually try calling them
// make sure the Bulk functions aren't leaking
testBulkMemoryOnce();
leak_mark();
for(int i = 0; i < 10000; i++) {
testBulkMemoryOnce();
}
// These tests can trigger some runtime-internal allocations that don't
// build up past a certain point, like SyncData allocations and
// garbage_refs. So we run it a lot, and ignore a certain amount of growth.
// Any real leak in the code should be much worse than 2 bytes per
// iteration.
leak_check(20000);
succeed(__FILE__);
}