/*
TEST_CRASHES
TEST_RUN_OUTPUT
objc\[\d+\]: \+\[BlockingSub initialize\] may have been in progress in another thread when fork\(\) was called\.
objc\[\d+\]: \+\[BlockingSub initialize\] may have been in progress in another thread when fork\(\) was called\. We cannot safely call it or ignore it in the fork\(\) child process\. Crashing instead\. Set a breakpoint on objc_initializeAfterForkError to debug\.
objc\[\d+\]: HALTED
OK: forkInitialize\.m
END
*/
#include "test.h"
static void *retain_fn(void *self, SEL _cmd __unused) { return self; }
static void release_fn(void *self __unused, SEL _cmd __unused) { }
OBJC_ROOT_CLASS
@interface BlockingRootClass @end
@implementation BlockingRootClass
+(id)self { return self; }
+(void)initialize {
class_addMethod(self, sel_registerName("retain"), (IMP)retain_fn, "");
class_addMethod(self, sel_registerName("release"), (IMP)release_fn, "");
if (self == [BlockingRootClass self]) {
while (1) sleep(1);
}
}
@end
@interface BlockingRootSub : BlockingRootClass @end
@implementation BlockingRootSub
@end
OBJC_ROOT_CLASS
@interface BlockingSubRoot @end
@implementation BlockingSubRoot
+(id)self { return self; }
+(void)initialize {
class_addMethod(self, sel_registerName("retain"), (IMP)retain_fn, "");
class_addMethod(self, sel_registerName("release"), (IMP)release_fn, "");
}
@end
@interface BlockingSub : BlockingSubRoot @end
@implementation BlockingSub
+(void)initialize {
class_addMethod(self, sel_registerName("retain"), (IMP)retain_fn, "");
class_addMethod(self, sel_registerName("release"), (IMP)release_fn, "");
while (1) sleep(1);
}
@end
OBJC_ROOT_CLASS
@interface AnotherRootClass @end
@interface BoringSub : AnotherRootClass @end
@implementation BoringSub
// can't implement +initialize here
@end
@implementation AnotherRootClass
void doFork()
{
testprintf("FORK\n");
pid_t child;
switch((child = fork())) {
case -1:
fail("fork failed");
case 0:
// child
// This one succeeds even though we're nested inside it's
// superclass's +initialize, because ordinary +initialize nesting
// still works across fork().
// This falls in the isInitializing() case in _class_initialize.
[BoringSub self];
#if !SINGLETHREADED
// This one succeeds even though another thread is in its
// superclass's +initialize, because that superclass is a root class
// so we assume that +initialize is empty and therefore this one
// is safe to call.
// This falls in the reallyInitialize case in _class_initialize.
[BlockingRootSub self];
// This one aborts without deadlocking because it was in progress
// when fork() was called.
// This falls in the isInitializing() case in _class_initialize.
[BlockingSub self];
fail("should have crashed");
#endif
break;
default: {
// parent
int result = 0;
while (waitpid(child, &result, 0) < 0) {
if (errno != EINTR) {
fail("waitpid failed (errno errno, strerror(errno));
}
}
if (!WIFEXITED(result)) {
fail("child crashed (waitpid result }
break;
}
}
}
+(void)initialize {
class_addMethod(self, sel_registerName("retain"), (IMP)retain_fn, "");
class_addMethod(self, sel_registerName("release"), (IMP)release_fn, "");
if (self == [AnotherRootClass self]) {
static bool called = false;
if (!called) {
doFork();
called = true;
} else {
fail("+[AnotherRootClass initialize] called again");
}
}
}
+(id)self {
return self;
}
@end
void *blocker(void *arg __unused)
{
[BlockingSub self];
return nil;
}
void *blocker2(void *arg __unused)
{
[BlockingRootClass self];
return nil;
}
int main()
{
#if !SINGLETHREADED
pthread_t th;
pthread_create(&th, nil, blocker, nil);
pthread_detach(th);
pthread_create(&th, nil, blocker2, nil);
pthread_detach(th);
sleep(1);
#endif
[AnotherRootClass self];
succeed(__FILE__);
}