2 // TEST_CFLAGS -framework Foundation
4 // Problem: If weak reference operations provoke +initialize, the runtime
5 // can deadlock (recursive weak lock, or lock inversion between weak lock
6 // and +initialize lock).
7 // Solution: object_setClass() and objc_storeWeak() perform +initialize
8 // if needed so that no weakly-referenced object can ever have an
9 // un-+initialized isa.
11 #include <Foundation/Foundation.h>
12 #include <objc/objc-internal.h>
15 #pragma clang diagnostic ignored "-Warc-unsafe-retained-assign"
17 // This is StripedMap's pointer hash
18 #if TARGET_OS_IPHONE && !TARGET_OS_SIMULATOR
19 enum { StripeCount = 8 };
21 enum { StripeCount = 64 };
23 uintptr_t stripehash(id obj) {
24 uintptr_t addr = (uintptr_t)obj;
25 return ((addr >> 4) ^ (addr >> 9)) % StripeCount;
28 bool sameAlignment(id o1, id o2)
30 return stripehash(o1) == stripehash(o2);
33 // Return a new non-tagged object that uses the same striped weak locks as `obj`
34 NSObject *newAlignedObject(id obj)
36 // Use immutable arrays because their contents are stored inline,
37 // which prevents Guard Malloc from using the same alignment for all of them
38 NSArray *result = [NSArray new];
39 while (!sameAlignment(obj, result)) {
40 result = [result arrayByAddingObject:result];
46 __weak NSObject *weak1;
47 __weak NSObject *weak2;
50 @interface A : NSObject @end
53 weak2 = strong2; // weak store #2
60 // Weak store #1 provokes +initialize which performs weak store #2.
61 // Solution: weak store #1 runs +initialize if needed
62 // without holding locks.
65 strong2 = newAlignedObject(obj);
66 [obj addObserver:obj forKeyPath:@"foo" options:0 context:0];
67 weak1 = obj; // weak store #1
68 [obj removeObserver:obj forKeyPath:@"foo"];
74 __weak NSObject *weak3;
75 __weak NSObject *weak4;
78 @interface B : NSObject @end
81 weak4 = strong4; // weak store #4
89 // Weak load #3 provokes +initialize which performs weak store #4.
90 // Solution: object_setClass() runs +initialize if needed
91 // without holding locks.
94 strong4 = newAlignedObject(obj);
96 [obj addObserver:obj forKeyPath:@"foo" options:0 context:0];
97 [weak3 self]; // weak load #3
98 [obj removeObserver:obj forKeyPath:@"foo"];
106 @interface C : NSObject @end
115 // +initialize performs a weak store of itself.
116 // Make sure the retry in objc_storeWeak() doesn't spin.
128 void *Dthread(void *arg __unused)
132 for (int i = 0; i < 1000; i++) {
134 testassert(x == strong6);
140 @interface D : NSObject @end
143 strong6 = [self new];
145 semaphore_signal(Dgo);
146 for (int i = 0; i < 1000; i++) {
148 testassert(x == strong6);
155 // +initialize performs a weak store of itself, then another thread
156 // tries to load that weak variable before +initialize completes.
157 // Deadlock occurs if the +initialize thread tries to acquire the
158 // sidetable lock for another operation and the second thread holds
159 // the sidetable lock while waiting for +initialize.
162 semaphore_create(mach_task_self(), &Dgo, 0, 0);
163 semaphore_create(mach_task_self(), &Ddone, 0, 0);
165 pthread_create(&th, nil, Dthread, nil);
167 pthread_join(th, nil);
173 if (is_guardmalloc() && getenv("MALLOC_PROTECT_BEFORE")) {
174 testwarn("fixme malloc guard before breaks this with debug libobjc");
177 alarm(10); // replace hangs with crashes