| /* 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" } { "" } } */ |
| // { dg-additional-options "-Wno-objc-root-class" } |
| |
| /* To get the modern GNU Objective-C Runtime API, you include |
| objc/runtime.h. */ |
| #include <objc/runtime.h> |
| |
| /* For __objc_msg_forward2. */ |
| #include <objc/message.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); |
| } |