// TEST_CONFIG
#include "test.h"
#include "testroot.i"
@interface OrdinaryClass : TestRoot @end
@implementation OrdinaryClass @end
objc_hook_getClass OnePreviousHook;
static int HookOneCalls = 0;
BOOL GetClassHookOne(const char *name, Class *outClass)
{
HookOneCalls++;
if (0 == strcmp(name, "TwoClass")) {
fail("other hook should have handled this already");
} else if (0 == strcmp(name, "OrdinaryClass")) {
fail("runtime should have handled this already");
} else if (0 == strcmp(name, "OneClass")) {
Class cls = objc_allocateClassPair([OrdinaryClass class], "OneClass", 0);
objc_registerClassPair(cls);
*outClass = cls;
return YES;
} else {
return OnePreviousHook(name, outClass);
}
}
objc_hook_getClass TwoPreviousHook;
static int HookTwoCalls = 0;
BOOL GetClassHookTwo(const char *name, Class *outClass)
{
HookTwoCalls++;
if (0 == strcmp(name, "OrdinaryClass")) {
fail("runtime should have handled this already");
} else if (0 == strcmp(name, "TwoClass")) {
Class cls = objc_allocateClassPair([OrdinaryClass class], "TwoClass", 0);
objc_registerClassPair(cls);
*outClass = cls;
return YES;
} else {
return TwoPreviousHook(name, outClass);
}
}
objc_hook_getClass ThreePreviousHook;
static int HookThreeCalls = 0;
#define MAXDEPTH 100
BOOL GetClassHookThree(const char *name, Class *outClass)
{
// Re-entrant hook test.
// libobjc must prevent re-entrancy when a getClass
// hook provokes another getClass call.
static int depth = 0;
static char *names[MAXDEPTH] = {0};
if (depth < MAXDEPTH) {
// Re-entrantly call objc_getClass() with a new class name.
if (!names[depth]) asprintf(&names[depth], "Reentrant const char *reentrantName = names[depth];
depth++;
(void)objc_getClass(reentrantName);
depth--;
} else if (depth == MAXDEPTH) {
// We now have maxdepth getClass hooks stacked up.
// Call objc_getClass() on all of those names a second time.
// None of those lookups should call this hook again.
HookThreeCalls++;
depth = -1;
for (int i = 0; i < MAXDEPTH; i++) {
testassert(!objc_getClass(names[i]));
}
depth = MAXDEPTH;
} else {
fail("reentrancy protection failed");
}
// Chain to the previous hook after all of the reentrancy is unwound.
if (depth == 0) {
return ThreePreviousHook(name, outClass);
} else {
return NO;
}
}
void testLookup(const char *name, int expectedHookOneCalls,
int expectedHookTwoCalls, int expectedHookThreeCalls)
{
HookOneCalls = HookTwoCalls = HookThreeCalls = 0;
Class cls = objc_getClass(name);
testassert(HookOneCalls == expectedHookOneCalls &&
HookTwoCalls == expectedHookTwoCalls &&
HookThreeCalls == expectedHookThreeCalls);
testassert(cls);
testassert(0 == strcmp(class_getName(cls), name));
testassert(cls == [cls self]);
}
int main()
{
testassert(objc_getClass("OrdinaryClass"));
testassert(!objc_getClass("OneClass"));
testassert(!objc_getClass("TwoClass"));
testassert(!objc_getClass("NoSuchClass"));
objc_setHook_getClass(GetClassHookOne, &OnePreviousHook);
objc_setHook_getClass(GetClassHookTwo, &TwoPreviousHook);
objc_setHook_getClass(GetClassHookThree, &ThreePreviousHook);
// invocation order: HookThree -> Hook Two -> Hook One
HookOneCalls = HookTwoCalls = HookThreeCalls = 0;
testassert(!objc_getClass("NoSuchClass"));
testassert(HookOneCalls == 1 && HookTwoCalls == 1 && HookThreeCalls == 1);
testLookup("OneClass", 1, 1, 1);
testLookup("TwoClass", 0, 1, 1);
testLookup("OrdinaryClass", 0, 0, 0);
// Check again. No hooks should be needed this time.
testLookup("OneClass", 0, 0, 0);
testLookup("TwoClass", 0, 0, 0);
testLookup("OrdinaryClass", 0, 0, 0);
succeed(__FILE__);
}