OSDN Git Service

In libobjc/:
authornicola <nicola@138bc75d-0d04-0410-961f-82ee72b054a4>
Sat, 11 Dec 2010 19:43:21 +0000 (19:43 +0000)
committernicola <nicola@138bc75d-0d04-0410-961f-82ee72b054a4>
Sat, 11 Dec 2010 19:43:21 +0000 (19:43 +0000)
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.

In gcc/testsuite/:
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.

git-svn-id: svn+ssh://gcc.gnu.org/svn/gcc/trunk@167712 138bc75d-0d04-0410-961f-82ee72b054a4

gcc/testsuite/ChangeLog
gcc/testsuite/obj-c++.dg/gnu-api-2-resolve-method.mm [new file with mode: 0644]
gcc/testsuite/objc.dg/gnu-api-2-resolve-method.m [new file with mode: 0644]
libobjc/ChangeLog
libobjc/objc/runtime.h
libobjc/sendmsg.c

index a2eac89..08baf34 100644 (file)
@@ -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 (file)
index 0000000..09f21b9
--- /dev/null
@@ -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 (file)
index 0000000..767dc73
--- /dev/null
@@ -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;
+}
index bc52213..fa609a5 100644 (file)
@@ -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.
index b37eee2..f5df691 100644 (file)
@@ -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
index 7f7024c..4748695 100644 (file)
@@ -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