]>
Commit | Line | Data |
---|---|---|
13ba007e A |
1 | // TEST_CFLAGS -fobjc-weak |
2 | ||
3 | #include "test.h" | |
4 | #include <objc/runtime.h> | |
5 | #include <objc/objc-internal.h> | |
6 | #include <objc/objc-gdb.h> | |
7 | #include <dlfcn.h> | |
8 | #import <Foundation/NSObject.h> | |
9 | ||
10 | #if OBJC_HAVE_TAGGED_POINTERS | |
11 | ||
12 | #if !__x86_64__ && !__arm64__ | |
13 | #error wrong architecture for tagged pointers | |
14 | #endif | |
15 | ||
16 | static BOOL didIt; | |
17 | ||
18 | @interface WeakContainer : NSObject | |
19 | { | |
20 | @public | |
21 | __weak id weaks[10000]; | |
22 | } | |
23 | @end | |
24 | @implementation WeakContainer | |
25 | -(void) dealloc { | |
26 | for (unsigned int i = 0; i < sizeof(weaks)/sizeof(weaks[0]); i++) { | |
27 | testassert(weaks[i] == nil); | |
28 | } | |
29 | SUPER_DEALLOC(); | |
30 | } | |
31 | @end | |
32 | ||
33 | OBJC_ROOT_CLASS | |
34 | @interface TaggedBaseClass60 | |
35 | @end | |
36 | ||
37 | @implementation TaggedBaseClass60 | |
38 | -(id) self { return self; } | |
39 | ||
40 | + (void) initialize { | |
41 | } | |
42 | ||
43 | - (void) instanceMethod { | |
44 | didIt = YES; | |
45 | } | |
46 | ||
47 | - (uintptr_t) taggedValue { | |
48 | return _objc_getTaggedPointerValue((__bridge void*)self); | |
49 | } | |
50 | ||
51 | - (struct stret) stret: (struct stret) aStruct { | |
52 | return aStruct; | |
53 | } | |
54 | ||
55 | - (long double) fpret: (long double) aValue { | |
56 | return aValue; | |
57 | } | |
58 | ||
59 | ||
60 | -(void) dealloc { | |
61 | fail("TaggedBaseClass60 dealloc called!"); | |
62 | } | |
63 | ||
64 | static void * | |
65 | retain_fn(void *self, SEL _cmd __unused) { | |
66 | void * (*fn)(void *) = (typeof(fn))_objc_rootRetain; | |
67 | return fn(self); | |
68 | } | |
69 | ||
70 | static void | |
71 | release_fn(void *self, SEL _cmd __unused) { | |
72 | void (*fn)(void *) = (typeof(fn))_objc_rootRelease; | |
73 | fn(self); | |
74 | } | |
75 | ||
76 | static void * | |
77 | autorelease_fn(void *self, SEL _cmd __unused) { | |
78 | void * (*fn)(void *) = (typeof(fn))_objc_rootAutorelease; | |
79 | return fn(self); | |
80 | } | |
81 | ||
82 | static unsigned long | |
83 | retaincount_fn(void *self, SEL _cmd __unused) { | |
84 | unsigned long (*fn)(void *) = (typeof(fn))_objc_rootRetainCount; | |
85 | return fn(self); | |
86 | } | |
87 | ||
88 | +(void) load { | |
89 | class_addMethod(self, sel_registerName("retain"), (IMP)retain_fn, ""); | |
90 | class_addMethod(self, sel_registerName("release"), (IMP)release_fn, ""); | |
91 | class_addMethod(self, sel_registerName("autorelease"), (IMP)autorelease_fn, ""); | |
92 | class_addMethod(self, sel_registerName("retainCount"), (IMP)retaincount_fn, ""); | |
93 | } | |
94 | ||
95 | @end | |
96 | ||
97 | @interface TaggedSubclass52: TaggedBaseClass60 | |
98 | @end | |
99 | ||
100 | @implementation TaggedSubclass52 | |
101 | ||
102 | - (void) instanceMethod { | |
103 | return [super instanceMethod]; | |
104 | } | |
105 | ||
106 | - (uintptr_t) taggedValue { | |
107 | return [super taggedValue]; | |
108 | } | |
109 | ||
110 | - (struct stret) stret: (struct stret) aStruct { | |
111 | return [super stret: aStruct]; | |
112 | } | |
113 | ||
114 | - (long double) fpret: (long double) aValue { | |
115 | return [super fpret: aValue]; | |
116 | } | |
117 | @end | |
118 | ||
119 | @interface TaggedNSObjectSubclass : NSObject | |
120 | @end | |
121 | ||
122 | @implementation TaggedNSObjectSubclass | |
123 | ||
124 | - (void) instanceMethod { | |
125 | didIt = YES; | |
126 | } | |
127 | ||
128 | - (uintptr_t) taggedValue { | |
129 | return _objc_getTaggedPointerValue((__bridge void*)self); | |
130 | } | |
131 | ||
132 | - (struct stret) stret: (struct stret) aStruct { | |
133 | return aStruct; | |
134 | } | |
135 | ||
136 | - (long double) fpret: (long double) aValue { | |
137 | return aValue; | |
138 | } | |
139 | @end | |
140 | ||
141 | void testTaggedPointerValue(Class cls, objc_tag_index_t tag, uintptr_t value) | |
142 | { | |
143 | void *taggedAddress = _objc_makeTaggedPointer(tag, value); | |
144 | testprintf("obj %p, tag %p, value %p\n", | |
145 | taggedAddress, (void*)tag, (void*)value); | |
146 | ||
147 | bool ext = (tag >= OBJC_TAG_First52BitPayload); | |
148 | ||
149 | // _objc_makeTaggedPointer must quietly mask out of range values for now | |
150 | if (ext) { | |
151 | value = (value << 12) >> 12; | |
152 | } else { | |
153 | value = (value << 4) >> 4; | |
154 | } | |
155 | ||
156 | testassert(_objc_isTaggedPointer(taggedAddress)); | |
157 | testassert(_objc_getTaggedPointerTag(taggedAddress) == tag); | |
158 | testassert(_objc_getTaggedPointerValue(taggedAddress) == value); | |
159 | testassert(objc_debug_taggedpointer_obfuscator != 0); | |
160 | ||
161 | if (ext) { | |
162 | uintptr_t slot = ((uintptr_t)taggedAddress >> objc_debug_taggedpointer_ext_slot_shift) & objc_debug_taggedpointer_ext_slot_mask; | |
163 | testassert(objc_debug_taggedpointer_ext_classes[slot] == cls); | |
164 | uintptr_t deobfuscated = (uintptr_t)taggedAddress ^ objc_debug_taggedpointer_obfuscator; | |
165 | testassert(((deobfuscated << objc_debug_taggedpointer_ext_payload_lshift) >> objc_debug_taggedpointer_ext_payload_rshift) == value); | |
166 | } | |
167 | else { | |
168 | testassert(((uintptr_t)taggedAddress & objc_debug_taggedpointer_mask) == objc_debug_taggedpointer_mask); | |
169 | uintptr_t slot = ((uintptr_t)taggedAddress >> objc_debug_taggedpointer_slot_shift) & objc_debug_taggedpointer_slot_mask; | |
170 | testassert(objc_debug_taggedpointer_classes[slot] == cls); | |
171 | uintptr_t deobfuscated = (uintptr_t)taggedAddress ^ objc_debug_taggedpointer_obfuscator; | |
172 | testassert(((deobfuscated << objc_debug_taggedpointer_payload_lshift) >> objc_debug_taggedpointer_payload_rshift) == value); | |
173 | } | |
174 | ||
175 | id taggedPointer = (__bridge id)taggedAddress; | |
176 | testassert(!object_isClass(taggedPointer)); | |
177 | testassert(object_getClass(taggedPointer) == cls); | |
178 | testassert([taggedPointer taggedValue] == value); | |
179 | ||
180 | didIt = NO; | |
181 | [taggedPointer instanceMethod]; | |
182 | testassert(didIt); | |
183 | ||
184 | struct stret orig = STRET_RESULT; | |
185 | testassert(stret_equal(orig, [taggedPointer stret: orig])); | |
186 | ||
187 | long double dblvalue = 3.14156789; | |
188 | testassert(dblvalue == [taggedPointer fpret: dblvalue]); | |
189 | ||
190 | objc_setAssociatedObject(taggedPointer, (__bridge void *)taggedPointer, taggedPointer, OBJC_ASSOCIATION_RETAIN); | |
191 | testassert(objc_getAssociatedObject(taggedPointer, (__bridge void *)taggedPointer) == taggedPointer); | |
192 | objc_setAssociatedObject(taggedPointer, (__bridge void *)taggedPointer, nil, OBJC_ASSOCIATION_RETAIN); | |
193 | testassert(objc_getAssociatedObject(taggedPointer, (__bridge void *)taggedPointer) == nil); | |
194 | } | |
195 | ||
196 | void testGenericTaggedPointer(objc_tag_index_t tag, Class cls) | |
197 | { | |
198 | testassert(cls); | |
199 | testprintf("%s\n", class_getName(cls)); | |
200 | ||
201 | testTaggedPointerValue(cls, tag, 0); | |
202 | testTaggedPointerValue(cls, tag, 1UL << 0); | |
203 | testTaggedPointerValue(cls, tag, 1UL << 1); | |
204 | testTaggedPointerValue(cls, tag, 1UL << 50); | |
205 | testTaggedPointerValue(cls, tag, 1UL << 51); | |
206 | testTaggedPointerValue(cls, tag, 1UL << 52); | |
207 | testTaggedPointerValue(cls, tag, 1UL << 58); | |
208 | testTaggedPointerValue(cls, tag, 1UL << 59); | |
209 | testTaggedPointerValue(cls, tag, ~0UL >> 4); | |
210 | testTaggedPointerValue(cls, tag, ~0UL); | |
211 | ||
212 | // Tagged pointers should bypass refcount tables and autorelease pools | |
213 | // and weak reference tables | |
214 | WeakContainer *w = [WeakContainer new]; | |
215 | ||
216 | // force sidetable retain of the WeakContainer before leak checking | |
217 | objc_retain(w); | |
218 | #if !__has_feature(objc_arc) | |
219 | // prime method caches before leak checking | |
220 | id taggedPointer = (id)_objc_makeTaggedPointer(tag, 1234); | |
221 | [taggedPointer retain]; | |
222 | [taggedPointer release]; | |
223 | [taggedPointer autorelease]; | |
224 | #endif | |
225 | // prime is_debug() before leak checking | |
226 | (void)is_debug(); | |
227 | ||
228 | leak_mark(); | |
229 | testonthread(^(void) { | |
230 | for (uintptr_t i = 0; i < sizeof(w->weaks)/sizeof(w->weaks[0]); i++) { | |
231 | id o = (__bridge id)_objc_makeTaggedPointer(tag, i); | |
232 | testassert(object_getClass(o) == cls); | |
233 | ||
234 | id result = WEAK_STORE(w->weaks[i], o); | |
235 | testassert(result == o); | |
236 | testassert(w->weaks[i] == o); | |
237 | ||
238 | result = WEAK_LOAD(w->weaks[i]); | |
239 | testassert(result == o); | |
240 | ||
241 | uintptr_t rc = _objc_rootRetainCount(o); | |
242 | testassert(rc != 0); | |
243 | _objc_rootRelease(o); testassert(_objc_rootRetainCount(o) == rc); | |
244 | _objc_rootRelease(o); testassert(_objc_rootRetainCount(o) == rc); | |
245 | _objc_rootRetain(o); testassert(_objc_rootRetainCount(o) == rc); | |
246 | _objc_rootRetain(o); testassert(_objc_rootRetainCount(o) == rc); | |
247 | _objc_rootRetain(o); testassert(_objc_rootRetainCount(o) == rc); | |
248 | #if !__has_feature(objc_arc) | |
249 | [o release]; testassert(_objc_rootRetainCount(o) == rc); | |
250 | [o release]; testassert(_objc_rootRetainCount(o) == rc); | |
251 | [o retain]; testassert(_objc_rootRetainCount(o) == rc); | |
252 | [o retain]; testassert(_objc_rootRetainCount(o) == rc); | |
253 | [o retain]; testassert(_objc_rootRetainCount(o) == rc); | |
254 | objc_release(o); testassert(_objc_rootRetainCount(o) == rc); | |
255 | objc_release(o); testassert(_objc_rootRetainCount(o) == rc); | |
256 | objc_retain(o); testassert(_objc_rootRetainCount(o) == rc); | |
257 | objc_retain(o); testassert(_objc_rootRetainCount(o) == rc); | |
258 | objc_retain(o); testassert(_objc_rootRetainCount(o) == rc); | |
259 | #endif | |
260 | PUSH_POOL { | |
261 | testassert(_objc_rootRetainCount(o) == rc); | |
262 | _objc_rootAutorelease(o); | |
263 | testassert(_objc_rootRetainCount(o) == rc); | |
264 | #if !__has_feature(objc_arc) | |
265 | [o autorelease]; | |
266 | testassert(_objc_rootRetainCount(o) == rc); | |
267 | objc_autorelease(o); | |
268 | testassert(_objc_rootRetainCount(o) == rc); | |
269 | objc_retainAutorelease(o); | |
270 | testassert(_objc_rootRetainCount(o) == rc); | |
271 | objc_autoreleaseReturnValue(o); | |
272 | testassert(_objc_rootRetainCount(o) == rc); | |
273 | objc_retainAutoreleaseReturnValue(o); | |
274 | testassert(_objc_rootRetainCount(o) == rc); | |
275 | objc_retainAutoreleasedReturnValue(o); | |
276 | testassert(_objc_rootRetainCount(o) == rc); | |
277 | #endif | |
278 | } POP_POOL; | |
279 | testassert(_objc_rootRetainCount(o) == rc); | |
280 | } | |
281 | }); | |
282 | if (is_debug()) { | |
283 | // libobjc's debug lock checking makes this leak check fail | |
284 | testwarn("skipping leak check with debug libobjc build"); | |
285 | } else { | |
286 | leak_check(0); | |
287 | } | |
288 | for (uintptr_t i = 0; i < 10000; i++) { | |
289 | testassert(w->weaks[i] != NULL); | |
290 | WEAK_STORE(w->weaks[i], NULL); | |
291 | testassert(w->weaks[i] == NULL); | |
292 | testassert(WEAK_LOAD(w->weaks[i]) == NULL); | |
293 | } | |
294 | objc_release(w); | |
295 | RELEASE_VAR(w); | |
296 | } | |
297 | ||
34d5b5e8 A |
298 | #if OBJC_SPLIT_TAGGED_POINTERS |
299 | void testConstantTaggedPointerRoundTrip(void *ptr) | |
300 | { | |
301 | uintptr_t tagged = (uintptr_t)ptr | objc_debug_constant_cfstring_tag_bits; | |
302 | void *untagged = _objc_getTaggedPointerRawPointerValue((void *)tagged); | |
303 | testassert(ptr == untagged); | |
304 | } | |
305 | ||
306 | void testConstantTaggedPointers(void) | |
307 | { | |
308 | testConstantTaggedPointerRoundTrip(0); | |
309 | testConstantTaggedPointerRoundTrip((void *)sizeof(void *)); | |
310 | testConstantTaggedPointerRoundTrip((void *)(MACH_VM_MAX_ADDRESS - sizeof(void *))); | |
311 | } | |
312 | #endif | |
313 | ||
13ba007e A |
314 | int main() |
315 | { | |
316 | testassert(objc_debug_taggedpointer_mask != 0); | |
317 | testassert(_objc_taggedPointersEnabled()); | |
318 | ||
319 | PUSH_POOL { | |
320 | // Avoid CF's tagged pointer tags because of rdar://11368528 | |
321 | ||
322 | // Reserved slot should be nil until the | |
323 | // first extended tag is registered. | |
324 | // This test no longer works because XPC now uses extended tags. | |
325 | #define HAVE_XPC_TAGS 1 | |
326 | ||
327 | uintptr_t extSlot = (~objc_debug_taggedpointer_obfuscator >> objc_debug_taggedpointer_slot_shift) & objc_debug_taggedpointer_slot_mask; | |
328 | Class extPlaceholder = objc_getClass("__NSUnrecognizedTaggedPointer"); | |
329 | testassert(extPlaceholder != nil); | |
330 | ||
331 | #if !HAVE_XPC_TAGS | |
332 | testassert(objc_debug_taggedpointer_classes[extSlot] == nil); | |
333 | #endif | |
334 | ||
335 | _objc_registerTaggedPointerClass(OBJC_TAG_1, | |
336 | objc_getClass("TaggedBaseClass60")); | |
337 | testGenericTaggedPointer(OBJC_TAG_1, | |
338 | objc_getClass("TaggedBaseClass60")); | |
339 | ||
340 | #if !HAVE_XPC_TAGS | |
341 | testassert(objc_debug_taggedpointer_classes[extSlot] == nil); | |
342 | #endif | |
343 | ||
344 | _objc_registerTaggedPointerClass(OBJC_TAG_First52BitPayload, | |
345 | objc_getClass("TaggedSubclass52")); | |
346 | testGenericTaggedPointer(OBJC_TAG_First52BitPayload, | |
347 | objc_getClass("TaggedSubclass52")); | |
348 | ||
349 | testassert(objc_debug_taggedpointer_classes[extSlot] == extPlaceholder); | |
350 | ||
351 | _objc_registerTaggedPointerClass(OBJC_TAG_NSManagedObjectID, | |
352 | objc_getClass("TaggedNSObjectSubclass")); | |
353 | testGenericTaggedPointer(OBJC_TAG_NSManagedObjectID, | |
354 | objc_getClass("TaggedNSObjectSubclass")); | |
34d5b5e8 A |
355 | |
356 | #if OBJC_SPLIT_TAGGED_POINTERS | |
357 | testConstantTaggedPointers(); | |
358 | #endif | |
13ba007e A |
359 | } POP_POOL; |
360 | ||
361 | succeed(__FILE__); | |
362 | } | |
363 | ||
364 | // OBJC_HAVE_TAGGED_POINTERS | |
365 | #else | |
366 | // not OBJC_HAVE_TAGGED_POINTERS | |
367 | ||
368 | // Tagged pointers not supported. | |
369 | ||
370 | int main() | |
371 | { | |
372 | testassert(objc_debug_taggedpointer_mask == 0); | |
373 | succeed(__FILE__); | |
374 | } | |
375 | ||
376 | #endif |