diff options
-rw-r--r-- | gcc/testsuite/ChangeLog | 5 | ||||
-rw-r--r-- | gcc/testsuite/obj-c++.dg/gnu-api-2-resolve-method.mm | 563 | ||||
-rw-r--r-- | gcc/testsuite/objc.dg/gnu-api-2-resolve-method.m | 563 | ||||
-rw-r--r-- | libobjc/ChangeLog | 16 | ||||
-rw-r--r-- | libobjc/objc/runtime.h | 18 | ||||
-rw-r--r-- | libobjc/sendmsg.c | 183 |
6 files changed, 1331 insertions, 17 deletions
diff --git a/gcc/testsuite/ChangeLog b/gcc/testsuite/ChangeLog index a2eac8999ed..08baf3439a1 100644 --- a/gcc/testsuite/ChangeLog +++ b/gcc/testsuite/ChangeLog @@ -1,3 +1,8 @@ +2010-12-11 Nicola Pero <nicola.pero@meta-innovation.com> + + * objc.dg/gnu-api-2-resolve-method.m: New. + * obj-c++.dg/gnu-api-2-resolve-method.mm: New. + 2010-12-10 Ahmad Sharif <asharif@google.com> * gcc.target/i386/max-stack-align.c: New testcase. diff --git a/gcc/testsuite/obj-c++.dg/gnu-api-2-resolve-method.mm b/gcc/testsuite/obj-c++.dg/gnu-api-2-resolve-method.mm new file mode 100644 index 00000000000..09f21b9ec63 --- /dev/null +++ b/gcc/testsuite/obj-c++.dg/gnu-api-2-resolve-method.mm @@ -0,0 +1,563 @@ +/* Test the Modern GNU Objective-C Runtime API. + + This is test 'resolve-method', covering +resolveClassMethod: and + +resolveInstanceMethod:. */ + +/* { dg-do run } */ +/* { dg-skip-if "" { *-*-* } { "-fnext-runtime" } { "" } } */ + +/* To get the modern GNU Objective-C Runtime API, you include + objc/runtime.h. */ +#include <objc/runtime.h> +#include <stdlib.h> +#include <iostream> +#include <cstring> + +@interface MyRootClass +{ Class isa; } ++ alloc; +- init; +@end + +@implementation MyRootClass ++ alloc { return class_createInstance (self, 0); } +- init { return self; } +@end + + +/* A number of tests will try invoking methods that don't exist. We + want to record the fact, but not abort the program, so we supply + our own fowarding implementation which will invoke the following + function for any method that is not found. */ + +/* Keep track of how many times a non-existing method was executed. */ +static int nonExistingMethodCount = 0; + +/* Inspired by nil_method in libobjc. */ +id nonExisting_method (id receiver __attribute__ ((__unused__)), + SEL sel __attribute__ ((__unused__))) +{ + nonExistingMethodCount++; + return nil; +} + +/* Keep track of how many times the forwarding lookup was invoked. */ +static int forwardingCount = 0; + +/* We install this forwarding hook to cause all failed method lookups + to call our 'nonExisting_method' function. */ +IMP forward_everything_to_non_existing_method (id receiver __attribute__ ((__unused__)), + SEL sel __attribute__ ((__unused__))) +{ + forwardingCount++; + return (IMP)nonExisting_method; +} + + +/* 'CountClass' is used to test that +resolveClassMethod: and + +resolveInstanceMethod: are called when expected. They do nothing + other than recording that they are called. */ +@interface CountClass : MyRootClass ++ (BOOL) resolveClassMethod: (SEL)selector; ++ (BOOL) resolveInstanceMethod: (SEL)selector; ++ (void) existingClassMethod; +- (void) existingInstanceMethod; +@end + +/* Count how many times the methods are called for class + 'CountClass'. */ +static int resolveClassMethodCount = 0; +static int resolveInstanceMethodCount = 0; + +@implementation CountClass : MyRootClass ++ (BOOL) resolveClassMethod: (SEL)selector +{ + resolveClassMethodCount++; + return NO; +} ++ (BOOL) resolveInstanceMethod: (SEL)selector +{ + resolveInstanceMethodCount++; + return NO; +} ++ (void) existingClassMethod +{ + return; +} +- (void) existingInstanceMethod +{ + return; +} +@end + +@protocol NonExistingStuff ++ (void) nonExistingClassMethod; +- (void) nonExistingInstanceMethod; +@end + +/* Declare a category with some non existing methods, but don't + actually implement them. */ +@interface CountClass (NonExistingStuff) <NonExistingStuff> +@end + + +/* 'SelfExtendingClass' is used to test that +resolveClassMethod: and + +resolveInstanceMethod: can extend the class. Any time they are + called, they install the requested method, mapping it to the same + implementation as 'countHits'. */ +@interface SelfExtendingClass : MyRootClass ++ (int) countHits; ++ (BOOL) resolveClassMethod: (SEL)selector; ++ (BOOL) resolveInstanceMethod: (SEL)selector; +@end + +/* How many times the countHits method (or a clone) was called. */ +static int hitCount = 0; + +@implementation SelfExtendingClass : MyRootClass ++ (int) countHits +{ + hitCount++; + return hitCount; +} ++ (BOOL) resolveClassMethod: (SEL)selector +{ + /* Duplicate the 'countHits' method into the new method. */ + Method method = class_getClassMethod (self, @selector (countHits)); + class_addMethod (object_getClass (self), selector, + method_getImplementation (method), + method_getTypeEncoding (method)); + resolveClassMethodCount++; + return YES; +} ++ (BOOL) resolveInstanceMethod: (SEL)selector +{ + /* Duplicate the 'countHits' method into the new method. */ + Method method = class_getClassMethod (self, @selector (countHits)); + class_addMethod (self, selector, + method_getImplementation (method), + method_getTypeEncoding (method)); + resolveInstanceMethodCount++; + return YES; + +} +@end + +@protocol NonExistingStuff2 ++ (int) nonExistingCountHitsMethod; +- (int) nonExistingCountHitsMethod; + ++ (int) nonExistingCountHitsMethod2; +- (int) nonExistingCountHitsMethod2; + ++ (int) nonExistingCountHitsMethod3; +- (int) nonExistingCountHitsMethod3; +@end + +/* Declare a category with some non existing methods, but don't + actually implement them. */ +@interface SelfExtendingClass (NonExistingStuff) <NonExistingStuff2> +@end + + +int main () +{ + /* Functions are tested in alphabetical order. */ + + /* Install our test forwarding hook. */ + __objc_msg_forward2 = forward_everything_to_non_existing_method; + + std::cout << "Testing [+resolveClassMethod:] ...\n"; + { + Method m; + IMP i; + + /** CountClass tests. **/ + + /* Call an existing method. No +resolveClassMethod and no + forwarding should be triggered. */ + [CountClass existingClassMethod]; + + if (resolveClassMethodCount != 0) + abort (); + + if (forwardingCount != 0) + abort (); + + if (nonExistingMethodCount != 0) + abort (); + + /* Call a non-existing method. Both +resolveClassMethod and the + forwarding should be triggered. */ + [CountClass nonExistingClassMethod]; + + if (resolveClassMethodCount != 1) + abort (); + + if (forwardingCount != 1) + abort (); + + if (nonExistingMethodCount != 1) + abort (); + + /* Now try the same tests with class_getClassMethod(), which + should trigger the resolve methods too, but not the + forwarding. */ + m = class_getClassMethod (objc_getClass ("CountClass"), + @selector (existingClassMethod)); + if (resolveClassMethodCount != 1) + abort (); + + if (forwardingCount != 1) + abort (); + + if (nonExistingMethodCount != 1) + abort (); + + m = class_getClassMethod (objc_getClass ("CountClass"), + @selector (nonExistingClassMethod)); + if (resolveClassMethodCount != 2) + abort (); + + if (forwardingCount != 1) + abort (); + + if (nonExistingMethodCount != 1) + abort (); + + /* Now try the same tests with class_getMethodImplementation(), + which should trigger the resolve methods and the forwarding + (but not execute the forwarding, obviously). */ + i = class_getMethodImplementation (object_getClass (objc_getClass ("CountClass")), + @selector (existingClassMethod)); + if (resolveClassMethodCount != 2) + abort (); + + if (forwardingCount != 1) + abort (); + + if (nonExistingMethodCount != 1) + abort (); + + i = class_getMethodImplementation (object_getClass (objc_getClass ("CountClass")), + @selector (nonExistingClassMethod)); + if (resolveClassMethodCount != 3) + abort (); + + if (forwardingCount != 2) + abort (); + + if (nonExistingMethodCount != 1) + abort (); + + + /* Reset the counters for the next test. */ + resolveClassMethodCount = 0; + forwardingCount = 0; + nonExistingMethodCount = 0; + + + /** SelfExtendingClass tests. **/ + + /* Try the direct countHits method first. No resolving or + forwarding should be triggered. */ + if ([SelfExtendingClass countHits] != 1) + abort (); + + if (resolveClassMethodCount != 0) + abort (); + + if (forwardingCount != 0) + abort (); + + if (nonExistingMethodCount != 0) + abort (); + + /* Now, try calling a non-existing count method; it should be + installed and invoked. */ + if ([SelfExtendingClass nonExistingCountHitsMethod] != 2) + abort (); + + if (resolveClassMethodCount != 1) + abort (); + + if (forwardingCount != 0) + abort (); + + if (nonExistingMethodCount != 0) + abort (); + + /* Try it again. The method has now been installed, so it should + be used and work, but with no additional resolving + involved. */ + if ([SelfExtendingClass nonExistingCountHitsMethod] != 3) + abort (); + + if (resolveClassMethodCount != 1) + abort (); + + if (forwardingCount != 0) + abort (); + + if (nonExistingMethodCount != 0) + abort (); + + + /* Now try the same tests with class_getClassMethod(). */ + m = class_getClassMethod (objc_getClass ("SelfExtendingClass"), + @selector (nonExistingCountHitsMethod2)); + if (resolveClassMethodCount != 2) + abort (); + + if (forwardingCount != 0) + abort (); + + if (nonExistingMethodCount != 0) + abort (); + + /* Try it again. The method has now been installed, so it should + be used and work, but with no additional resolving + involved. */ + if ([SelfExtendingClass nonExistingCountHitsMethod2] != 4) + abort (); + + if (resolveClassMethodCount != 2) + abort (); + + if (forwardingCount != 0) + abort (); + + if (nonExistingMethodCount != 0) + abort (); + + + /* Now try the same tests with class_getMethodImplementation(). */ + i = class_getMethodImplementation (object_getClass (objc_getClass ("SelfExtendingClass")), + @selector (nonExistingCountHitsMethod3)); + if (resolveClassMethodCount != 3) + abort (); + + if (forwardingCount != 0) + abort (); + + if (nonExistingMethodCount != 0) + abort (); + + /* Try it again. The method has now been installed, so it should + be used and work, but with no additional resolving + involved. */ + if ([SelfExtendingClass nonExistingCountHitsMethod3] != 5) + abort (); + + if (resolveClassMethodCount != 3) + abort (); + + if (forwardingCount != 0) + abort (); + + if (nonExistingMethodCount != 0) + abort (); + } + + /* Reset the counters for the next test. */ + nonExistingMethodCount = 0; + forwardingCount = 0; + hitCount = 0; + + std::cout << "Testing [+resolveInstanceMethod:] ...\n"; + { + Method m; + IMP i; + CountClass *object = [[CountClass alloc] init]; + SelfExtendingClass *object2 = [[SelfExtendingClass alloc] init]; + + /** CountClass tests. **/ + + /* Call an existing method. No +resolveInstanceMethod and no + forwarding should be triggered. */ + [object existingInstanceMethod]; + + if (resolveInstanceMethodCount != 0) + abort (); + + if (forwardingCount != 0) + abort (); + + if (nonExistingMethodCount != 0) + abort (); + + /* Call a non-existing method. Both +resolveInstanceMethod and the + forwarding should be triggered. */ + [object nonExistingInstanceMethod]; + + if (resolveInstanceMethodCount != 1) + abort (); + + if (forwardingCount != 1) + abort (); + + if (nonExistingMethodCount != 1) + abort (); + + /* Now try the same tests with class_getInstanceMethod(), which + should trigger the resolve methods too, but not the + forwarding. */ + m = class_getInstanceMethod (objc_getClass ("CountClass"), + @selector (existingInstanceMethod)); + + if (resolveInstanceMethodCount != 1) + abort (); + + if (forwardingCount != 1) + abort (); + + if (nonExistingMethodCount != 1) + abort (); + + m = class_getInstanceMethod (objc_getClass ("CountClass"), + @selector (nonExistingInstanceMethod)); + + if (resolveInstanceMethodCount != 2) + abort (); + + if (forwardingCount != 1) + abort (); + + if (nonExistingMethodCount != 1) + abort (); + + /* Now try the same tests with class_getMethodImplementation(), + which should trigger the resolve methods and the + forwarding. */ + i = class_getMethodImplementation (objc_getClass ("CountClass"), + @selector (existingInstanceMethod)); + if (resolveInstanceMethodCount != 2) + abort (); + + if (forwardingCount != 1) + abort (); + + if (nonExistingMethodCount != 1) + abort (); + + i = class_getMethodImplementation (objc_getClass ("CountClass"), + @selector (nonExistingInstanceMethod)); + if (resolveInstanceMethodCount != 3) + abort (); + + if (forwardingCount != 2) + abort (); + + if (nonExistingMethodCount != 1) + abort (); + + /* Reset the counters for the next test. */ + resolveInstanceMethodCount = 0; + forwardingCount = 0; + nonExistingMethodCount = 0; + + + /** SelfExtendingClass tests. **/ + + /* Try the direct countHits method first. No resolving or + forwarding should be triggered. */ + if ([SelfExtendingClass countHits] != 1) + abort (); + + if (resolveInstanceMethodCount != 0) + abort (); + + if (forwardingCount != 0) + abort (); + + if (nonExistingMethodCount != 0) + abort (); + + /* Now, try calling a non-existing count method; it should be + installed and invoked. */ + if ([object2 nonExistingCountHitsMethod] != 2) + abort (); + + if (resolveInstanceMethodCount != 1) + abort (); + + if (forwardingCount != 0) + abort (); + + if (nonExistingMethodCount != 0) + abort (); + + /* Try it again. The method has now been installed, so it should + be used and work, but with no additional resolving + involved. */ + if ([object2 nonExistingCountHitsMethod] != 3) + abort (); + + if (resolveInstanceMethodCount != 1) + abort (); + + if (forwardingCount != 0) + abort (); + + if (nonExistingMethodCount != 0) + abort (); + + /* Now try the same tests with class_getInstanceMethod(). */ + m = class_getInstanceMethod (objc_getClass ("SelfExtendingClass"), + @selector (nonExistingCountHitsMethod2)); + if (resolveInstanceMethodCount != 2) + abort (); + + if (forwardingCount != 0) + abort (); + + if (nonExistingMethodCount != 0) + abort (); + + /* Try it again. The method has now been installed, so it should + be used and work, but with no additional resolving + involved. */ + if ([object2 nonExistingCountHitsMethod2] != 4) + abort (); + + if (resolveInstanceMethodCount != 2) + abort (); + + if (forwardingCount != 0) + abort (); + + if (nonExistingMethodCount != 0) + abort (); + + + /* Now try the same tests with class_getMethodImplementation(). */ + i = class_getMethodImplementation (objc_getClass ("SelfExtendingClass"), + @selector (nonExistingCountHitsMethod3)); + if (resolveInstanceMethodCount != 3) + abort (); + + if (forwardingCount != 0) + abort (); + + if (nonExistingMethodCount != 0) + abort (); + + /* Try it again. The method has now been installed, so it should + be used and work, but with no additional resolving + involved. */ + if ([object2 nonExistingCountHitsMethod3] != 5) + abort (); + + if (resolveInstanceMethodCount != 3) + abort (); + + if (forwardingCount != 0) + abort (); + + if (nonExistingMethodCount != 0) + abort (); + } + + + return (0); +} diff --git a/gcc/testsuite/objc.dg/gnu-api-2-resolve-method.m b/gcc/testsuite/objc.dg/gnu-api-2-resolve-method.m new file mode 100644 index 00000000000..767dc73cf33 --- /dev/null +++ b/gcc/testsuite/objc.dg/gnu-api-2-resolve-method.m @@ -0,0 +1,563 @@ +/* Test the Modern GNU Objective-C Runtime API. + + This is test 'resolve-method', covering +resolveClassMethod: and + +resolveInstanceMethod:. */ + +/* { dg-do run } */ +/* { dg-skip-if "" { *-*-* } { "-fnext-runtime" } { "" } } */ + +/* To get the modern GNU Objective-C Runtime API, you include + objc/runtime.h. */ +#include <objc/runtime.h> +#include <stdlib.h> +#include <stdio.h> +#include <string.h> + +@interface MyRootClass +{ Class isa; } ++ alloc; +- init; +@end + +@implementation MyRootClass ++ alloc { return class_createInstance (self, 0); } +- init { return self; } +@end + + +/* A number of tests will try invoking methods that don't exist. We + want to record the fact, but not abort the program, so we supply + our own fowarding implementation which will invoke the following + function for any method that is not found. */ + +/* Keep track of how many times a non-existing method was executed. */ +static int nonExistingMethodCount = 0; + +/* Inspired by nil_method in libobjc. */ +id nonExisting_method (id receiver __attribute__ ((__unused__)), + SEL sel __attribute__ ((__unused__))) +{ + nonExistingMethodCount++; + return nil; +} + +/* Keep track of how many times the forwarding lookup was invoked. */ +static int forwardingCount = 0; + +/* We install this forwarding hook to cause all failed method lookups + to call our 'nonExisting_method' function. */ +IMP forward_everything_to_non_existing_method (id receiver __attribute__ ((__unused__)), + SEL sel __attribute__ ((__unused__))) +{ + forwardingCount++; + return (IMP)nonExisting_method; +} + + +/* 'CountClass' is used to test that +resolveClassMethod: and + +resolveInstanceMethod: are called when expected. They do nothing + other than recording that they are called. */ +@interface CountClass : MyRootClass ++ (BOOL) resolveClassMethod: (SEL)selector; ++ (BOOL) resolveInstanceMethod: (SEL)selector; ++ (void) existingClassMethod; +- (void) existingInstanceMethod; +@end + +/* Count how many times the methods are called for class + 'CountClass'. */ +static int resolveClassMethodCount = 0; +static int resolveInstanceMethodCount = 0; + +@implementation CountClass : MyRootClass ++ (BOOL) resolveClassMethod: (SEL)selector +{ + resolveClassMethodCount++; + return NO; +} ++ (BOOL) resolveInstanceMethod: (SEL)selector +{ + resolveInstanceMethodCount++; + return NO; +} ++ (void) existingClassMethod +{ + return; +} +- (void) existingInstanceMethod +{ + return; +} +@end + +@protocol NonExistingStuff ++ (void) nonExistingClassMethod; +- (void) nonExistingInstanceMethod; +@end + +/* Declare a category with some non existing methods, but don't + actually implement them. */ +@interface CountClass (NonExistingStuff) <NonExistingStuff> +@end + + +/* 'SelfExtendingClass' is used to test that +resolveClassMethod: and + +resolveInstanceMethod: can extend the class. Any time they are + called, they install the requested method, mapping it to the same + implementation as 'countHits'. */ +@interface SelfExtendingClass : MyRootClass ++ (int) countHits; ++ (BOOL) resolveClassMethod: (SEL)selector; ++ (BOOL) resolveInstanceMethod: (SEL)selector; +@end + +/* How many times the countHits method (or a clone) was called. */ +static int hitCount = 0; + +@implementation SelfExtendingClass : MyRootClass ++ (int) countHits +{ + hitCount++; + return hitCount; +} ++ (BOOL) resolveClassMethod: (SEL)selector +{ + /* Duplicate the 'countHits' method into the new method. */ + Method method = class_getClassMethod (self, @selector (countHits)); + class_addMethod (object_getClass (self), selector, + method_getImplementation (method), + method_getTypeEncoding (method)); + resolveClassMethodCount++; + return YES; +} ++ (BOOL) resolveInstanceMethod: (SEL)selector +{ + /* Duplicate the 'countHits' method into the new method. */ + Method method = class_getClassMethod (self, @selector (countHits)); + class_addMethod (self, selector, + method_getImplementation (method), + method_getTypeEncoding (method)); + resolveInstanceMethodCount++; + return YES; + +} +@end + +@protocol NonExistingStuff2 ++ (int) nonExistingCountHitsMethod; +- (int) nonExistingCountHitsMethod; + ++ (int) nonExistingCountHitsMethod2; +- (int) nonExistingCountHitsMethod2; + ++ (int) nonExistingCountHitsMethod3; +- (int) nonExistingCountHitsMethod3; +@end + +/* Declare a category with some non existing methods, but don't + actually implement them. */ +@interface SelfExtendingClass (NonExistingStuff) <NonExistingStuff2> +@end + + +int main (int argc, void **args) +{ + /* Functions are tested in alphabetical order. */ + + /* Install our test forwarding hook. */ + __objc_msg_forward2 = forward_everything_to_non_existing_method; + + printf ("Testing [+resolveClassMethod:]...\n"); + { + Method m; + IMP i; + + /** CountClass tests. **/ + + /* Call an existing method. No +resolveClassMethod and no + forwarding should be triggered. */ + [CountClass existingClassMethod]; + + if (resolveClassMethodCount != 0) + abort (); + + if (forwardingCount != 0) + abort (); + + if (nonExistingMethodCount != 0) + abort (); + + /* Call a non-existing method. Both +resolveClassMethod and the + forwarding should be triggered. */ + [CountClass nonExistingClassMethod]; + + if (resolveClassMethodCount != 1) + abort (); + + if (forwardingCount != 1) + abort (); + + if (nonExistingMethodCount != 1) + abort (); + + /* Now try the same tests with class_getClassMethod(), which + should trigger the resolve methods too, but not the + forwarding. */ + m = class_getClassMethod (objc_getClass ("CountClass"), + @selector (existingClassMethod)); + if (resolveClassMethodCount != 1) + abort (); + + if (forwardingCount != 1) + abort (); + + if (nonExistingMethodCount != 1) + abort (); + + m = class_getClassMethod (objc_getClass ("CountClass"), + @selector (nonExistingClassMethod)); + if (resolveClassMethodCount != 2) + abort (); + + if (forwardingCount != 1) + abort (); + + if (nonExistingMethodCount != 1) + abort (); + + /* Now try the same tests with class_getMethodImplementation(), + which should trigger the resolve methods and the forwarding + (but not execute the forwarding, obviously). */ + i = class_getMethodImplementation (object_getClass (objc_getClass ("CountClass")), + @selector (existingClassMethod)); + if (resolveClassMethodCount != 2) + abort (); + + if (forwardingCount != 1) + abort (); + + if (nonExistingMethodCount != 1) + abort (); + + i = class_getMethodImplementation (object_getClass (objc_getClass ("CountClass")), + @selector (nonExistingClassMethod)); + if (resolveClassMethodCount != 3) + abort (); + + if (forwardingCount != 2) + abort (); + + if (nonExistingMethodCount != 1) + abort (); + + + /* Reset the counters for the next test. */ + resolveClassMethodCount = 0; + forwardingCount = 0; + nonExistingMethodCount = 0; + + + /** SelfExtendingClass tests. **/ + + /* Try the direct countHits method first. No resolving or + forwarding should be triggered. */ + if ([SelfExtendingClass countHits] != 1) + abort (); + + if (resolveClassMethodCount != 0) + abort (); + + if (forwardingCount != 0) + abort (); + + if (nonExistingMethodCount != 0) + abort (); + + /* Now, try calling a non-existing count method; it should be + installed and invoked. */ + if ([SelfExtendingClass nonExistingCountHitsMethod] != 2) + abort (); + + if (resolveClassMethodCount != 1) + abort (); + + if (forwardingCount != 0) + abort (); + + if (nonExistingMethodCount != 0) + abort (); + + /* Try it again. The method has now been installed, so it should + be used and work, but with no additional resolving + involved. */ + if ([SelfExtendingClass nonExistingCountHitsMethod] != 3) + abort (); + + if (resolveClassMethodCount != 1) + abort (); + + if (forwardingCount != 0) + abort (); + + if (nonExistingMethodCount != 0) + abort (); + + + /* Now try the same tests with class_getClassMethod(). */ + m = class_getClassMethod (objc_getClass ("SelfExtendingClass"), + @selector (nonExistingCountHitsMethod2)); + if (resolveClassMethodCount != 2) + abort (); + + if (forwardingCount != 0) + abort (); + + if (nonExistingMethodCount != 0) + abort (); + + /* Try it again. The method has now been installed, so it should + be used and work, but with no additional resolving + involved. */ + if ([SelfExtendingClass nonExistingCountHitsMethod2] != 4) + abort (); + + if (resolveClassMethodCount != 2) + abort (); + + if (forwardingCount != 0) + abort (); + + if (nonExistingMethodCount != 0) + abort (); + + + /* Now try the same tests with class_getMethodImplementation(). */ + i = class_getMethodImplementation (object_getClass (objc_getClass ("SelfExtendingClass")), + @selector (nonExistingCountHitsMethod3)); + if (resolveClassMethodCount != 3) + abort (); + + if (forwardingCount != 0) + abort (); + + if (nonExistingMethodCount != 0) + abort (); + + /* Try it again. The method has now been installed, so it should + be used and work, but with no additional resolving + involved. */ + if ([SelfExtendingClass nonExistingCountHitsMethod3] != 5) + abort (); + + if (resolveClassMethodCount != 3) + abort (); + + if (forwardingCount != 0) + abort (); + + if (nonExistingMethodCount != 0) + abort (); + } + + /* Reset the counters for the next test. */ + nonExistingMethodCount = 0; + forwardingCount = 0; + hitCount = 0; + + printf ("Testing [+resolveInstanceMethod:]...\n"); + { + Method m; + IMP i; + CountClass *object = [[CountClass alloc] init]; + SelfExtendingClass *object2 = [[SelfExtendingClass alloc] init]; + + /** CountClass tests. **/ + + /* Call an existing method. No +resolveInstanceMethod and no + forwarding should be triggered. */ + [object existingInstanceMethod]; + + if (resolveInstanceMethodCount != 0) + abort (); + + if (forwardingCount != 0) + abort (); + + if (nonExistingMethodCount != 0) + abort (); + + /* Call a non-existing method. Both +resolveInstanceMethod and the + forwarding should be triggered. */ + [object nonExistingInstanceMethod]; + + if (resolveInstanceMethodCount != 1) + abort (); + + if (forwardingCount != 1) + abort (); + + if (nonExistingMethodCount != 1) + abort (); + + /* Now try the same tests with class_getInstanceMethod(), which + should trigger the resolve methods too, but not the + forwarding. */ + m = class_getInstanceMethod (objc_getClass ("CountClass"), + @selector (existingInstanceMethod)); + + if (resolveInstanceMethodCount != 1) + abort (); + + if (forwardingCount != 1) + abort (); + + if (nonExistingMethodCount != 1) + abort (); + + m = class_getInstanceMethod (objc_getClass ("CountClass"), + @selector (nonExistingInstanceMethod)); + + if (resolveInstanceMethodCount != 2) + abort (); + + if (forwardingCount != 1) + abort (); + + if (nonExistingMethodCount != 1) + abort (); + + /* Now try the same tests with class_getMethodImplementation(), + which should trigger the resolve methods and the + forwarding. */ + i = class_getMethodImplementation (objc_getClass ("CountClass"), + @selector (existingInstanceMethod)); + if (resolveInstanceMethodCount != 2) + abort (); + + if (forwardingCount != 1) + abort (); + + if (nonExistingMethodCount != 1) + abort (); + + i = class_getMethodImplementation (objc_getClass ("CountClass"), + @selector (nonExistingInstanceMethod)); + if (resolveInstanceMethodCount != 3) + abort (); + + if (forwardingCount != 2) + abort (); + + if (nonExistingMethodCount != 1) + abort (); + + /* Reset the counters for the next test. */ + resolveInstanceMethodCount = 0; + forwardingCount = 0; + nonExistingMethodCount = 0; + + + /** SelfExtendingClass tests. **/ + + /* Try the direct countHits method first. No resolving or + forwarding should be triggered. */ + if ([SelfExtendingClass countHits] != 1) + abort (); + + if (resolveInstanceMethodCount != 0) + abort (); + + if (forwardingCount != 0) + abort (); + + if (nonExistingMethodCount != 0) + abort (); + + /* Now, try calling a non-existing count method; it should be + installed and invoked. */ + if ([object2 nonExistingCountHitsMethod] != 2) + abort (); + + if (resolveInstanceMethodCount != 1) + abort (); + + if (forwardingCount != 0) + abort (); + + if (nonExistingMethodCount != 0) + abort (); + + /* Try it again. The method has now been installed, so it should + be used and work, but with no additional resolving + involved. */ + if ([object2 nonExistingCountHitsMethod] != 3) + abort (); + + if (resolveInstanceMethodCount != 1) + abort (); + + if (forwardingCount != 0) + abort (); + + if (nonExistingMethodCount != 0) + abort (); + + /* Now try the same tests with class_getInstanceMethod(). */ + m = class_getInstanceMethod (objc_getClass ("SelfExtendingClass"), + @selector (nonExistingCountHitsMethod2)); + if (resolveInstanceMethodCount != 2) + abort (); + + if (forwardingCount != 0) + abort (); + + if (nonExistingMethodCount != 0) + abort (); + + /* Try it again. The method has now been installed, so it should + be used and work, but with no additional resolving + involved. */ + if ([object2 nonExistingCountHitsMethod2] != 4) + abort (); + + if (resolveInstanceMethodCount != 2) + abort (); + + if (forwardingCount != 0) + abort (); + + if (nonExistingMethodCount != 0) + abort (); + + + /* Now try the same tests with class_getMethodImplementation(). */ + i = class_getMethodImplementation (objc_getClass ("SelfExtendingClass"), + @selector (nonExistingCountHitsMethod3)); + if (resolveInstanceMethodCount != 3) + abort (); + + if (forwardingCount != 0) + abort (); + + if (nonExistingMethodCount != 0) + abort (); + + /* Try it again. The method has now been installed, so it should + be used and work, but with no additional resolving + involved. */ + if ([object2 nonExistingCountHitsMethod3] != 5) + abort (); + + if (resolveInstanceMethodCount != 3) + abort (); + + if (forwardingCount != 0) + abort (); + + if (nonExistingMethodCount != 0) + abort (); + } + + + return 0; +} diff --git a/libobjc/ChangeLog b/libobjc/ChangeLog index bc522133feb..fa609a58f86 100644 --- a/libobjc/ChangeLog +++ b/libobjc/ChangeLog @@ -1,5 +1,21 @@ 2010-12-11 Nicola Pero <nicola.pero@meta-innovation.com> + * sendmsg.c (selector_resolveClassMethod): New. + (selector_resolveInstanceMethod): New. + (__objc_resolve_class_method): New. + (__objc_resolve_instance_method): New. + (get_imp): Call __objc_resolve_class_method or + __objc_resolve_instance_method at the appropriate time. + (objc_msg_lookup): Same. + (class_getClassMethod): Same. + (class_getInstanceMethod): Same. + (__objc_init_dispatch_tables): Initialize + selector_resolveClassMethod and selector_resolveInstanceMethod. + * objc/runtime.h: Updated documentation of class_getClassMethod, + class_getInstanceMethod and class_getMethodImplementation. + +2010-12-11 Nicola Pero <nicola.pero@meta-innovation.com> + * objc-private/module-abi-8.h (struct objc_symtab): Updated description of sel_ref_cnt and refs. * objc/deprecated/struct_objc_symtab.h (objc_symtab): Same change. diff --git a/libobjc/objc/runtime.h b/libobjc/objc/runtime.h index b37eee2125b..f5df691fe52 100644 --- a/libobjc/objc/runtime.h +++ b/libobjc/objc/runtime.h @@ -590,13 +590,17 @@ objc_disposeClassPair (Class class_); /* Return the instance method with selector 'selector' of class 'class_', or NULL if the class (or one of its superclasses) does not implement the method. Return NULL if class_ is Nil or selector - is NULL. */ + is NULL. Calling this function may trigger a call to + +resolveInstanceMethod:, but does not return a forwarding + function. */ objc_EXPORT Method class_getInstanceMethod (Class class_, SEL selector); /* Return the class method with selector 'selector' of class 'class_', or NULL if the class (or one of its superclasses) does not implement the method. Return NULL if class_ is Nil or selector is - NULL. */ + NULL. Calling this function may trigger a call to + +resolveClassMethod:, but does not return a forwarding + function. */ objc_EXPORT Method class_getClassMethod (Class class_, SEL selector); /* Return the IMP (pointer to the function implementing a method) for @@ -607,7 +611,15 @@ objc_EXPORT Method class_getClassMethod (Class class_, SEL selector); arguments and return type before calling it. To get a class method, you can pass the meta-class as the class_ argument (ie, use class_getMethodImplementation (object_getClass (class_), - selector)). Return NULL if class_ is Nil or selector is NULL. */ + selector)). Return NULL if class_ is Nil or selector is NULL. + This function first looks for an existing method; if it is not + found, it calls +resolveClassMethod: or +resolveInstanceMethod: + (depending on whether a class or instance method is being looked + up) if it is implemented. If the method returns YES, then it tries + the look up again (the assumption being that +resolveClassMethod: + or resolveInstanceMethod: will add the method using + class_addMethod()). If it is still not found, it returns a + forwarding function. */ objc_EXPORT IMP class_getMethodImplementation (Class class_, SEL selector); /* Compatibility Note: the Apple/NeXT runtime has the function diff --git a/libobjc/sendmsg.c b/libobjc/sendmsg.c index 7f7024c275a..4748695938d 100644 --- a/libobjc/sendmsg.c +++ b/libobjc/sendmsg.c @@ -138,6 +138,102 @@ __objc_get_forward_imp (id rcv, SEL sel) } } +/* Selectors for +resolveClassMethod: and +resolveInstanceMethod:. + These are set up at startup. */ +static SEL selector_resolveClassMethod = NULL; +static SEL selector_resolveInstanceMethod = NULL; + +/* Internal routines use to resolve a class method using + +resolveClassMethod:. 'class' is always a non-Nil class (*not* a + meta-class), and 'sel' is the selector that we are trying to + resolve. This must be called when class is not Nil, and the + dispatch table for class methods has already been installed. + + This routine tries to call +resolveClassMethod: to give an + opportunity to resolve the method. If +resolveClassMethod: returns + YES, it tries looking up the method again, and if found, it returns + it. Else, it returns NULL. */ +static inline +IMP +__objc_resolve_class_method (Class class, SEL sel) +{ + /* We need to lookup +resolveClassMethod:. */ + BOOL (*resolveMethodIMP) (id, SEL, SEL); + + /* The dispatch table for class methods is already installed and we + don't want any forwarding to happen when looking up this method, + so we just look it up directly. Note that if 'sel' is precisely + +resolveClassMethod:, this would look it up yet again and find + nothing. That's no problem and there's no recursion. */ + resolveMethodIMP = (BOOL (*) (id, SEL, SEL))sarray_get_safe + (class->class_pointer->dtable, (size_t) selector_resolveClassMethod->sel_id); + + if (resolveMethodIMP && resolveMethodIMP ((id)class, selector_resolveClassMethod, sel)) + { + /* +resolveClassMethod: returned YES. Look the method up again. + We already know the dtable is installed. */ + + /* TODO: There is the case where +resolveClassMethod: is buggy + and returned YES without actually adding the method. We + could maybe print an error message. */ + return sarray_get_safe (class->class_pointer->dtable, (size_t) sel->sel_id); + } + + return NULL; +} + +/* Internal routines use to resolve a instance method using + +resolveInstanceMethod:. 'class' is always a non-Nil class, and + 'sel' is the selector that we are trying to resolve. This must be + called when class is not Nil, and the dispatch table for instance + methods has already been installed. + + This routine tries to call +resolveInstanceMethod: to give an + opportunity to resolve the method. If +resolveInstanceMethod: + returns YES, it tries looking up the method again, and if found, it + returns it. Else, it returns NULL. */ +static inline +IMP +__objc_resolve_instance_method (Class class, SEL sel) +{ + /* We need to lookup +resolveInstanceMethod:. */ + BOOL (*resolveMethodIMP) (id, SEL, SEL); + + /* The dispatch table for class methods may not be already installed + so we have to install it if needed. */ + resolveMethodIMP = sarray_get_safe (class->class_pointer->dtable, + (size_t) selector_resolveInstanceMethod->sel_id); + if (resolveMethodIMP == 0) + { + /* Try again after installing the dtable. */ + if (class->class_pointer->dtable == __objc_uninstalled_dtable) + { + objc_mutex_lock (__objc_runtime_mutex); + if (class->class_pointer->dtable == __objc_uninstalled_dtable) + __objc_install_dispatch_table_for_class (class->class_pointer); + objc_mutex_unlock (__objc_runtime_mutex); + } + resolveMethodIMP = sarray_get_safe (class->class_pointer->dtable, + (size_t) selector_resolveInstanceMethod->sel_id); + } + + if (resolveMethodIMP && resolveMethodIMP ((id)class, selector_resolveInstanceMethod, sel)) + { + /* +resolveInstanceMethod: returned YES. Look the method up + again. We already know the dtable is installed. */ + + /* TODO: There is the case where +resolveInstanceMethod: is + buggy and returned YES without actually adding the method. + We could maybe print an error message. */ + return sarray_get_safe (class->dtable, (size_t) sel->sel_id); + } + + return NULL; +} + +/* Temporary definition until we include objc/runtime.h. */ +objc_EXPORT Class objc_lookupClass (const char *name); + /* Given a class and selector, return the selector's implementation. */ inline IMP @@ -188,14 +284,35 @@ get_imp (Class class, SEL sel) { /* The dispatch table has been installed, and the method is not in the dispatch table. So the method just - doesn't exist for the class. Return the forwarding - implementation. We don't know the receiver (only its - class), so we have to pass 'nil' as the first - argument. Passing the class as first argument is - wrong because the class is not the receiver; it can - result in us calling a class method when we want an - instance method of the same name. */ - res = __objc_get_forward_imp (nil, sel); + doesn't exist for the class. */ + + /* Try going through the +resolveClassMethod: or + +resolveInstanceMethod: process. */ + if (CLS_ISMETA (class)) + { + /* We have the meta class, but we need to invoke the + +resolveClassMethod: method on the class. So, we + need to obtain the class from the meta class, + which we do using the fact that both the class + and the meta-class have the same name. */ + Class realClass = objc_lookupClass (class->name); + if (realClass) + res = __objc_resolve_class_method (realClass, sel); + } + else + res = __objc_resolve_instance_method (class, sel); + + if (res == 0) + { + /* If that fails, then return the forwarding + implementation. We don't know the receiver (only its + class), so we have to pass 'nil' as the first + argument. Passing the class as first argument is + wrong because the class is not the receiver; it can + result in us calling a class method when we want an + instance method of the same name. */ + res = __objc_get_forward_imp (nil, sel); + } } } } @@ -304,9 +421,20 @@ objc_msg_lookup (id receiver, SEL op) (sidx)op->sel_id); if (result == 0) { - /* If the method still just doesn't exist for the - class, attempt to forward the method. */ - result = __objc_get_forward_imp (receiver, op); + /* Try going through the +resolveClassMethod: or + +resolveInstanceMethod: process. */ + if (CLS_ISMETA (receiver->class_pointer)) + result = __objc_resolve_class_method ((Class)receiver, op); + else + result = __objc_resolve_instance_method (receiver->class_pointer, + op); + + if (result == 0) + { + /* If the method still just doesn't exist for + the class, attempt to forward the method. */ + result = __objc_get_forward_imp (receiver, op); + } } } } @@ -343,6 +471,10 @@ void __objc_init_dispatch_tables () { __objc_uninstalled_dtable = sarray_new (200, 0); + + /* TODO: It would be cool to register typed selectors here. */ + selector_resolveClassMethod = sel_register_name ("resolveClassMethod:"); + selector_resolveInstanceMethod =sel_register_name ("resolveInstanceMethod:"); } /* This function is called by objc_msg_lookup when the @@ -566,20 +698,43 @@ class_get_class_method (MetaClass class, SEL op) struct objc_method * class_getInstanceMethod (Class class_, SEL selector) { + struct objc_method *m; + if (class_ == Nil || selector == NULL) return NULL; - return search_for_method_in_hierarchy (class_, selector); + m = search_for_method_in_hierarchy (class_, selector); + if (m) + return m; + + /* Try going through +resolveInstanceMethod:, and do + the search again if successful. */ + if (__objc_resolve_instance_method (class_, selector)) + return search_for_method_in_hierarchy (class_, selector); + + return NULL; } struct objc_method * class_getClassMethod (Class class_, SEL selector) { + struct objc_method *m; + if (class_ == Nil || selector == NULL) return NULL; - return search_for_method_in_hierarchy (class_->class_pointer, - selector); + m = search_for_method_in_hierarchy (class_->class_pointer, + selector); + if (m) + return m; + + /* Try going through +resolveClassMethod:, and do the search again + if successful. */ + if (__objc_resolve_class_method (class_, selector)) + return search_for_method_in_hierarchy (class_->class_pointer, + selector); + + return NULL; } BOOL |