123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187 |
- // TEST_CONFIG MEM=arc
- // TEST_CFLAGS -framework Foundation
- // Problem: If weak reference operations provoke +initialize, the runtime
- // can deadlock (recursive weak lock, or lock inversion between weak lock
- // and +initialize lock).
- // Solution: object_setClass() and objc_storeWeak() perform +initialize
- // if needed so that no weakly-referenced object can ever have an
- // un-+initialized isa.
- #include <Foundation/Foundation.h>
- #include <objc/objc-internal.h>
- #include "test.h"
- #pragma clang diagnostic ignored "-Warc-unsafe-retained-assign"
- // This is StripedMap's pointer hash
- #if TARGET_OS_IPHONE && !TARGET_OS_SIMULATOR
- enum { StripeCount = 8 };
- #else
- enum { StripeCount = 64 };
- #endif
- uintptr_t stripehash(id obj) {
- uintptr_t addr = (uintptr_t)obj;
- return ((addr >> 4) ^ (addr >> 9)) % StripeCount;
- }
- bool sameAlignment(id o1, id o2)
- {
- return stripehash(o1) == stripehash(o2);
- }
- // Return a new non-tagged object that uses the same striped weak locks as `obj`
- NSObject *newAlignedObject(id obj)
- {
- // Use immutable arrays because their contents are stored inline,
- // which prevents Guard Malloc from using the same alignment for all of them
- NSArray *result = [NSArray new];
- while (!sameAlignment(obj, result)) {
- result = [result arrayByAddingObject:result];
- }
- return result;
- }
- __weak NSObject *weak1;
- __weak NSObject *weak2;
- NSObject *strong2;
- @interface A : NSObject @end
- @implementation A
- +(void)initialize {
- weak2 = strong2; // weak store #2
- strong2 = nil;
- }
- @end
- void testA()
- {
- // Weak store #1 provokes +initialize which performs weak store #2.
- // Solution: weak store #1 runs +initialize if needed
- // without holding locks.
- @autoreleasepool {
- A *obj = [A new];
- strong2 = newAlignedObject(obj);
- [obj addObserver:obj forKeyPath:@"foo" options:0 context:0];
- weak1 = obj; // weak store #1
- [obj removeObserver:obj forKeyPath:@"foo"];
- obj = nil;
- }
- }
- __weak NSObject *weak3;
- __weak NSObject *weak4;
- NSObject *strong4;
- @interface B : NSObject @end
- @implementation B
- +(void)initialize {
- weak4 = strong4; // weak store #4
- strong4 = nil;
- }
- @end
- void testB()
- {
- // Weak load #3 provokes +initialize which performs weak store #4.
- // Solution: object_setClass() runs +initialize if needed
- // without holding locks.
- @autoreleasepool {
- B *obj = [B new];
- strong4 = newAlignedObject(obj);
- weak3 = obj;
- [obj addObserver:obj forKeyPath:@"foo" options:0 context:0];
- [weak3 self]; // weak load #3
- [obj removeObserver:obj forKeyPath:@"foo"];
- obj = nil;
- }
- }
- __weak id weak5;
- @interface C : NSObject @end
- @implementation C
- +(void)initialize {
- weak5 = [self new];
- }
- @end
- void testC()
- {
- // +initialize performs a weak store of itself.
- // Make sure the retry in objc_storeWeak() doesn't spin.
- @autoreleasepool {
- [C self];
- }
- }
- __weak id weak6;
- NSObject *strong6;
- semaphore_t Dgo;
- semaphore_t Ddone;
- void *Dthread(void *arg __unused)
- {
- @autoreleasepool {
- semaphore_wait(Dgo);
- for (int i = 0; i < 1000; i++) {
- id x = weak6;
- testassert(x == strong6);
- }
- return nil;
- }
- }
- @interface D : NSObject @end
- @implementation D
- +(void)initialize {
- strong6 = [self new];
- weak6 = strong6;
- semaphore_signal(Dgo);
- for (int i = 0; i < 1000; i++) {
- id x = weak6;
- testassert(x == strong6);
- }
- }
- @end
- void testD()
- {
- // +initialize performs a weak store of itself, then another thread
- // tries to load that weak variable before +initialize completes.
- // Deadlock occurs if the +initialize thread tries to acquire the
- // sidetable lock for another operation and the second thread holds
- // the sidetable lock while waiting for +initialize.
-
- @autoreleasepool {
- semaphore_create(mach_task_self(), &Dgo, 0, 0);
- semaphore_create(mach_task_self(), &Ddone, 0, 0);
- pthread_t th;
- pthread_create(&th, nil, Dthread, nil);
- [D self];
- pthread_join(th, nil);
- }
- }
- int main()
- {
- if (is_guardmalloc() && getenv("MALLOC_PROTECT_BEFORE")) {
- testwarn("fixme malloc guard before breaks this with debug libobjc");
- }
- else {
- alarm(10); // replace hangs with crashes
-
- testA();
- testB();
- testC();
- testD();
- }
- succeed(__FILE__);
- }
|