darling-objc4/test/initializeVersusWeak.m
2020-06-09 21:50:17 -04:00

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__);
}