569 lines
14 KiB
Objective-C
569 lines
14 KiB
Objective-C
/* 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 <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;
|
|
}
|