mirror of
https://github.com/darlinghq/darling-objc4.git
synced 2024-11-23 12:19:48 +00:00
188 lines
4.4 KiB
Objective-C
188 lines
4.4 KiB
Objective-C
// 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__);
|
|
}
|
|
|