forkInitialize.m   [plain text]


/*
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 %d %s)", 
                     errno, strerror(errno));
            }
        }
        if (!WIFEXITED(result)) {
            fail("child crashed (waitpid result %d)", 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__);
}