X-Git-Url: https://git.saurik.com/apple/objc4.git/blobdiff_plain/7c0e6487d7b67b6bf6c632300ee4b74e8950b051..7af964d1562d70f51a8e9aca24215ac3d83d0624:/test/resolve.m diff --git a/test/resolve.m b/test/resolve.m new file mode 100644 index 0000000..a590b19 --- /dev/null +++ b/test/resolve.m @@ -0,0 +1,275 @@ +/* resolve.m + * Test +resolveClassMethod: and +resolveInstanceMethod: + */ + +#include "test.h" +#include +#include +#include + +// fixme new API? +extern void _objc_flush_caches(Class cls, BOOL flushMeta); + +static int state = 0; + +@interface Super { id isa; } @end +@interface Sub : Super @end + + +@implementation Super ++class { return self; } ++(void)initialize { + if (self == [Super class]) { + testassert(state == 1); + state = 2; + } +} ++new { return class_createInstance(self, 0); } +-(void)dealloc { object_dispose(self); } +-forward:(SEL)sel :(marg_list)args +{ + if (sel == @selector(missingClassMethod)) { + testassert(state == 21 || state == 25 || state == 80); + if (state == 21) state = 22; + if (state == 25) state = 26; + if (state == 80) state = 81;; + return nil; + } else if (sel == @selector(lyingClassMethod)) { + testassert(state == 31 || state == 35); + if (state == 31) state = 32; + if (state == 35) state = 36; + return nil; + } else if (sel == @selector(missingInstanceMethod)) { + testassert(state == 61 || state == 65); + if (state == 61) state = 62; + if (state == 65) state = 66; + return nil; + } else if (sel == @selector(lyingInstanceMethod)) { + testassert(state == 71 || state == 75); + if (state == 71) state = 72; + if (state == 75) state = 76; + return nil; + } + fail("forward:: shouldn't be called (sel %s)", sel_getName(sel)); + return args; // unused +} +@end + + +static id classMethod_c(id self, SEL sel) +{ + testassert(state == 4 || state == 10); + if (state == 4) state = 5; + if (state == 10) state = 11; + self = (id)sel; // unused + return [Super class]; +} + +static id instanceMethod_c(id self, SEL sel) +{ + testassert(state == 41 || state == 50); + if (state == 41) state = 42; + if (state == 50) state = 51; + self = (id)sel; // unused + return [Sub class]; +} + + +@implementation Sub + ++(void)method2 { } ++(void)method3 { } ++(void)method4 { } ++(void)method5 { } + ++(void)initialize { + if (self == [Sub class]) { + testassert(state == 2); + state = 3; + } +} + ++(BOOL)resolveClassMethod:(SEL)sel +{ + if (sel == @selector(classMethod)) { + testassert(state == 3); + state = 4; + class_addMethod(self->isa, sel, (IMP)&classMethod_c, ""); + return YES; + } else if (sel == @selector(missingClassMethod)) { + testassert(state == 20); + state = 21; + return NO; + } else if (sel == @selector(lyingClassMethod)) { + testassert(state == 30); + state = 31; + return YES; // lie + } else { + fail("+resolveClassMethod: called incorrectly (sel %s)", + sel_getName(sel)); + return NO; + } +} + ++(BOOL)resolveInstanceMethod:(SEL)sel +{ + if (sel == @selector(instanceMethod)) { + testassert(state == 40); + state = 41; + class_addMethod(self, sel, (IMP)instanceMethod_c, ""); + return YES; + } else if (sel == @selector(missingInstanceMethod)) { + testassert(state == 60); + state = 61; + return NO; + } else if (sel == @selector(lyingInstanceMethod)) { + testassert(state == 70); + state = 71; + return YES; // lie + } else { + fail("+resolveInstanceMethod: called incorrectly (sel %s)", + sel_getName(sel)); + return NO; + } +} + +@end + +@interface Super (MissingMethods) ++missingClassMethod; +@end + +@interface Sub (ResolvedMethods) ++classMethod; +-instanceMethod; ++missingClassMethod; +-missingInstanceMethod; ++lyingClassMethod; +-lyingInstanceMethod; +@end + + +int main() +{ + Sub *s; + id ret; + Class dup = objc_duplicateClass(objc_getClass("Sub"), "Sub_copy", 0); + + // Resolve a class method + // +initialize should fire first + state = 1; + ret = [Sub classMethod]; + testassert(state == 5); + testassert(ret == [Super class]); + + // Call it again, cached + // Resolver shouldn't be called again. + state = 10; + ret = [Sub classMethod]; + testassert(state == 11); + testassert(ret == [Super class]); + + _objc_flush_caches([Sub class]->isa, NO); + + // Call a method that won't get resolved + state = 20; + ret = [Sub missingClassMethod]; + testassert(state == 22); + testassert(ret == nil); + + // Call it again, cached + // Resolver shouldn't be called again. + state = 25; + ret = [Sub missingClassMethod]; + testassert(state == 26); + testassert(ret == nil); + + _objc_flush_caches([Sub class]->isa, NO); + + // Call a method that won't get resolved but the resolver lies about it + state = 30; + ret = [Sub lyingClassMethod]; + testassert(state == 32); + testassert(ret == nil); + + // Call it again, cached + // Resolver shouldn't be called again. + state = 35; + ret = [Sub lyingClassMethod]; + testassert(state == 36); + testassert(ret == nil); + + _objc_flush_caches([Sub class]->isa, NO); + + + // Resolve an instance method + s = [Sub new]; + state = 40; + ret = [s instanceMethod]; + testassert(state == 42); + testassert(ret == [Sub class]); + + // Call it again, cached + // Resolver shouldn't be called again. + state = 50; + ret = [s instanceMethod]; + testassert(state == 51); + testassert(ret == [Sub class]); + + _objc_flush_caches([Sub class], NO); + + // Call a method that won't get resolved + state = 60; + ret = [s missingInstanceMethod]; + testassert(state == 62); + testassert(ret == nil); + + // Call it again, cached + // Resolver shouldn't be called again. + state = 65; + ret = [s missingInstanceMethod]; + testassert(state == 66); + testassert(ret == nil); + + _objc_flush_caches([Sub class], NO); + + // Call a method that won't get resolved but the resolver lies about it + state = 70; + ret = [s lyingInstanceMethod]; + testassert(state == 72); + testassert(ret == nil); + + // Call it again, cached + // Resolver shouldn't be called again. + state = 75; + ret = [s lyingInstanceMethod]; + testassert(state == 76); + testassert(ret == nil); + + _objc_flush_caches([Sub class], NO); + + // Call a missing method on a class that doesn't support resolving + state = 80; + ret = [Super missingClassMethod]; + testassert(state == 81); + testassert(ret == nil); + [s dealloc]; + + // Resolve an instance method on a class duplicated before resolving + s = [dup new]; + state = 40; + ret = [s instanceMethod]; + testassert(state == 42); + testassert(ret == [Sub class]); + + // Call it again, cached + // Resolver shouldn't be called again. + state = 50; + ret = [s instanceMethod]; + testassert(state == 51); + testassert(ret == [Sub class]); + [s dealloc]; + + succeed(__FILE__); + return 0; +}