Patchwork =?UTF-8?Q?libobjc=20-=20more=20modern=20Objective-C=20runtime=20API=20(7?= =?UTF-8?Q?)?=

login
register
mail settings
Submitter Nicola Pero
Date Oct. 15, 2010, 10:31 p.m.
Message ID <1287181899.161417877@192.168.2.228>
Download mbox | patch
Permalink /patch/68005/
State New
Headers show

Comments

Nicola Pero - Oct. 15, 2010, 10:31 p.m.
This batch adds the delicate 'method_setImplementation' and 'method_exchangeImplementations' functions to the runtime, plus a new testcase that tests all the method_xxx functions in the new API. :-)

Committed to trunk.

Thanks

In libobjc/:

2010-10-15  Nicola Pero  <nicola.pero@meta-innovation.com>

        * objc-private/runtime.h (__objc_update_classes_with_methods): New.
        * class.c (__objc_update_classes_with_methods): New.
        (objc_getClassList): Do not lock the class lock.
        * methods.c (method_exchangeImplementations): New.
        (method_setImplementation): New.
        * objc/runtime.h (method_setImplementation): New.
        (method_exchangeImplementations): New.
        
In gcc/testsuite/:

2010-10-15  Nicola Pero  <nicola.pero@meta-innovation.com>

        * objc.dg/gnu-api-2-method.m: New.

Index: ChangeLog
===================================================================
--- ChangeLog	(revision 165524)
+++ ChangeLog	(working copy)
@@ -1,3 +1,7 @@
+2010-10-15  Nicola Pero  <nicola.pero@meta-innovation.com>
+
+	* objc.dg/gnu-api-2-method.m: New.
+
 2010-10-15  Jason Merrill  <jason@redhat.com>
 
 	* g++.dg/lto/pr45983_0.C: New.
Index: objc.dg/gnu-api-2-method.m
===================================================================
--- objc.dg/gnu-api-2-method.m	(revision 0)
+++ objc.dg/gnu-api-2-method.m	(revision 0)
@@ -0,0 +1,227 @@
+/* Test the Modern GNU Objective-C Runtime API.
+
+  This is test 'method', covering all functions starting with 'method'.  */
+
+/* { 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
+
+@protocol MyProtocol
+- (id) variable;
+@end
+
+@protocol MySecondProtocol
+- (id) setVariable: (id)value;
+@end
+
+@interface MySubClass : MyRootClass <MyProtocol>
+{ id variable_ivar; }
+- (void) setVariable: (id)value;
+- (id) variable;
+- (id) constant;
+@end
+
+@implementation MySubClass
+- (void) setVariable: (id)value { variable_ivar = value; }
+- (id) variable { return variable_ivar; }
+- (id) constant { return nil; }
+@end
+
+
+int main(int argc, void **args)
+{
+  /* Functions are tested in alphabetical order.  */
+
+  printf ("Testing method_copyArgumentType () ...\n");
+  {
+    Method method = class_getInstanceMethod (objc_getClass ("MySubClass"),
+					     @selector (setVariable:));
+    char *type = method_copyArgumentType (method, 2);
+
+    if (type == NULL  ||  type[0] != '@')
+      abort ();
+  }
+
+  printf ("Testing method_copyReturnType () ...\n");
+  {
+    Method method = class_getClassMethod (objc_getClass ("MyRootClass"),
+					  @selector (alloc));
+    char *type = method_copyReturnType (method);
+
+    /* Check that it returns an object.  */
+    if (type == NULL  ||  type[0] != '@')
+      abort ();
+  }
+
+  printf ("Testing method_exchangeImplementations () ...\n");
+  {
+    Method method_a = class_getInstanceMethod (objc_getClass ("MySubClass"),
+					       @selector (variable));
+    Method method_b = class_getInstanceMethod (objc_getClass ("MySubClass"),
+					       @selector (constant));
+    MySubClass *object = [[MySubClass alloc] init];
+
+    /* Check that things work as expected before the swap.  */
+    [object setVariable: object];
+
+    if ([object variable] != object  ||  [object constant] != nil)
+      abort ();
+
+    /* Swap the methods.  */
+    method_exchangeImplementations (method_a, method_b);
+
+    /* Check that behaviour has changed.  */
+    if ([object variable] != nil  ||  [object constant] != object)
+      abort ();
+
+    /* Swap the methods again.  */
+    method_exchangeImplementations (method_a, method_b);
+    
+    /* Check that behaviour is back to normal.  */
+    if ([object variable] != object  ||  [object constant] != nil)
+      abort ();
+  }
+
+  printf ("Testing method_getArgumentType () ...\n");
+  {
+    Method method = class_getInstanceMethod (objc_getClass ("MyRootClass"),
+					     @selector (init));
+    char type[16];
+    
+    method_getArgumentType (method, 1, type, 16);
+
+    /* Check the second argument (_cmd), which should be a SEL.  */
+    if (type[0] != ':')
+      abort ();
+  }
+
+  printf ("Testing method_getDescription () ...\n");
+  {
+    Method method = class_getInstanceMethod (objc_getClass ("MySubClass"),
+					     @selector (variable));
+    struct objc_method_description *description = method_getDescription (method);
+
+    if (strcmp (sel_getName (description->name), "variable") != 0)
+      abort ();
+
+    if (method_getDescription (NULL) != NULL)
+      abort ();
+  }
+
+  printf ("Testing method_getImplementation () ...\n");
+  {
+    typedef void (*set_variable_function) (id receiver, SEL _cmd, id variable);
+    Method method = class_getInstanceMethod (objc_getClass ("MySubClass"),
+					     @selector (setVariable:));
+    set_variable_function imp;
+    MySubClass *object = [[MySubClass alloc] init];
+
+    imp = (set_variable_function)(method_getImplementation (method));
+    
+    (*imp)(object, @selector (setVariable:), object);
+
+    if ([object variable] != object)
+      abort ();
+  }
+
+  printf ("Testing method_getName () ...\n");
+  {
+    Method method = class_getInstanceMethod (objc_getClass ("MySubClass"),
+					     @selector (setVariable:));
+    if (strcmp (sel_getName (method_getName (method)), "setVariable:") != 0)
+      abort ();
+  }
+
+  printf ("Testing method_getNumberOfArguments () ...\n");
+  {
+    Method method = class_getInstanceMethod (objc_getClass ("MySubClass"),
+					     @selector (setVariable:));
+    if (method_getNumberOfArguments (method) != 3)
+      abort ();
+
+    method = class_getInstanceMethod (objc_getClass ("MySubClass"),
+				      @selector (variable));
+    if (method_getNumberOfArguments (method) != 2)
+      abort ();
+  }
+
+  printf ("Testing method_getTypeEncoding () ...\n");
+  {
+    Method method = class_getInstanceMethod (objc_getClass ("MySubClass"),
+					     @selector (setVariable:));
+    const char *types = method_getTypeEncoding (method);
+
+    /* Check that method type string starts with 'v' (void)  */
+    if (types == NULL || types[0] != 'v')
+      abort ();    
+  }
+
+  printf ("Testing method_getReturnType () ...\n");
+  {
+    Method method = class_getInstanceMethod (objc_getClass ("MySubClass"),
+					     @selector (setVariable:));
+    char type[16];
+    
+    method_getReturnType (method, type, 16);
+
+    if (type[0] != 'v')
+      abort ();
+
+    method_getReturnType (NULL, type, 16);
+
+    if (type[0] != 0)
+      abort ();
+  }
+
+  printf ("Testing method_setImplementation () ...\n");
+  {
+    Method method_a = class_getInstanceMethod (objc_getClass ("MySubClass"),
+					       @selector (variable));
+    Method method_b = class_getInstanceMethod (objc_getClass ("MySubClass"),
+					       @selector (constant));
+    IMP original_imp_a = method_getImplementation (method_a);
+    IMP original_imp_b = method_getImplementation (method_b);
+    MySubClass *object = [[MySubClass alloc] init];
+
+    /* Check that things work as expected before the swap.  */
+    [object setVariable: object];
+    
+    if ([object variable] != object  ||  [object constant] != nil)
+      abort ();
+    
+    /* Have 'variable' use the same implementation as 'constant'.  */
+    if (method_setImplementation (method_a, original_imp_b) != original_imp_a)
+      abort ();
+
+    /* Check that behaviour has changed.  */
+    if ([object variable] != nil  ||  [object constant] != nil)
+      abort ();
+
+    /* Put the original method back.  */
+    if (method_setImplementation (method_a, original_imp_a) != original_imp_b)
+      abort ();
+    
+    /* Check that behaviour is back to normal.  */
+    if ([object variable] != object  ||  [object constant] != nil)
+      abort ();
+  }
+
+  return 0;
+}

Patch

Index: objc-private/runtime.h
===================================================================
--- objc-private/runtime.h	(revision 165524)
+++ objc-private/runtime.h	(working copy)
@@ -74,6 +74,9 @@  extern void class_add_method_list(Class, struct ob
 extern void __objc_register_instance_methods_to_class(Class);
 extern struct objc_method * search_for_method_in_list(struct objc_method_list * list, SEL op);
 
+extern void
+__objc_update_classes_with_methods (struct objc_method *method_a, struct objc_method *method_b); /* class.c */
+
 /* True when class links has been resolved */     
 extern BOOL __objc_class_links_resolved;
 
Index: class.c
===================================================================
--- class.c	(revision 165524)
+++ class.c	(working copy)
@@ -93,6 +93,7 @@  see the files COPYING3 and COPYING.RUNTIME respect
 #include "objc/thr.h"
 #include "objc-private/module-abi-8.h"  /* For CLS_ISCLASS and similar.  */
 #include "objc-private/runtime.h"       /* the kitchen sink */
+#include "objc-private/sarray.h"        /* For sarray_put_at_safe.  */
 #include <string.h>                     /* For memset */
 
 /* We use a table which maps a class name to the corresponding class
@@ -546,8 +547,6 @@  objc_getClassList (Class *returnValue, int maxNumb
   /* Iterate over all entries in the table.  */
   int hash, count = 0;
 
-  objc_mutex_lock (__class_table_lock); 
-
   for (hash = 0; hash < CLASS_TABLE_SIZE; hash++)
     {
       class_node_ptr node = class_table_array[hash];
@@ -560,7 +559,6 @@  objc_getClassList (Class *returnValue, int maxNumb
 		returnValue[count] = node->pointer;
 	      else
 		{
-		  objc_mutex_unlock (__class_table_lock);
 		  return count;
 		}
 	    }
@@ -569,7 +567,6 @@  objc_getClassList (Class *returnValue, int maxNumb
 	}
     }
   
-  objc_mutex_unlock (__class_table_lock);
   return count;
 }
 
@@ -647,6 +644,62 @@  objc_next_class (void **enum_state)
   return class;
 }
 
+/* This is used when the implementation of a method changes.  It goes
+   through all classes, looking for the ones that have these methods
+   (either method_a or method_b; method_b can be NULL), and reloads
+   the implementation for these.  You should call this with the
+   runtime mutex already locked.  */
+void
+__objc_update_classes_with_methods (struct objc_method *method_a, struct objc_method *method_b)
+{
+  int hash;
+
+  /* Iterate over all classes.  */
+  for (hash = 0; hash < CLASS_TABLE_SIZE; hash++)
+    {
+      class_node_ptr node = class_table_array[hash];
+      
+      while (node != NULL)
+	{
+	  /* Iterate over all methods in the class.  */
+	  Class class = node->pointer;
+	  struct objc_method_list * method_list = class->methods;
+
+	  while (method_list)
+	    {
+	      int i;
+
+	      for (i = 0; i < method_list->method_count; ++i)
+		{
+		  struct objc_method *method = &method_list->method_list[i];
+
+		  /* If the method is one of the ones we are looking
+		     for, update the implementation.  */
+		  if (method == method_a)
+		    {
+		      sarray_at_put_safe (class->dtable,
+					  (sidx) method_a->method_name->sel_id,
+					  method_a->method_imp);
+		    }
+
+		  if (method == method_b)
+		    {
+		      if (method_b != NULL)
+			{
+			  sarray_at_put_safe (class->dtable,
+					      (sidx) method_b->method_name->sel_id,
+					      method_b->method_imp);
+			}
+		    }
+		}
+	  
+	      method_list = method_list->method_next;
+	    }
+	  node = node->next;
+	}
+    }
+}
+
 /* Resolve super/subclass links for all classes.  The only thing we
    can be sure of is that the class_pointer for class objects point to
    the right meta class objects.  */
Index: methods.c
===================================================================
--- methods.c	(revision 165524)
+++ methods.c	(working copy)
@@ -124,3 +124,54 @@  class_copyMethodList (Class class_, unsigned int *
 
   return returnValue;
 }
+
+IMP
+method_setImplementation (struct objc_method * method, IMP implementation)
+{
+  IMP old_implementation;
+
+  if (method == NULL  ||  implementation == NULL)
+    return NULL;
+
+  /* We lock the runtime mutex so that concurrent calls to change the
+     same method won't conflict with each other.  */
+  objc_mutex_lock (__objc_runtime_mutex);
+
+  old_implementation = method->method_imp;
+  method->method_imp = implementation;
+
+  /* That was easy :-).  But now we need to find all classes that use
+     this method, and update the IMP in the dispatch tables.  */
+  __objc_update_classes_with_methods (method, NULL);
+
+  objc_mutex_unlock (__objc_runtime_mutex);
+
+  return old_implementation;
+}
+
+void
+method_exchangeImplementations (struct objc_method * method_a, struct objc_method * method_b)
+{
+  IMP old_implementation_a;
+  IMP old_implementation_b;
+
+  if (method_a == NULL  ||  method_b == NULL)
+    return;
+
+  /* We lock the runtime mutex so that concurrent calls to exchange
+     similar methods won't conflict with each other.  Each of them
+     should be atomic.  */
+  objc_mutex_lock (__objc_runtime_mutex);
+
+  old_implementation_a = method_a->method_imp;
+  old_implementation_b = method_b->method_imp;
+
+  method_a->method_imp = old_implementation_b;
+  method_b->method_imp = old_implementation_a;
+
+  /* That was easy :-).  But now we need to find all classes that use
+     these methods, and update the IMP in the dispatch tables.  */
+  __objc_update_classes_with_methods (method_a, method_b);
+
+  objc_mutex_unlock (__objc_runtime_mutex);
+}
Index: ChangeLog
===================================================================
--- ChangeLog	(revision 165524)
+++ ChangeLog	(working copy)
@@ -1,5 +1,15 @@ 
 2010-10-15  Nicola Pero  <nicola.pero@meta-innovation.com>
 
+	* objc-private/runtime.h (__objc_update_classes_with_methods): New.
+	* class.c (__objc_update_classes_with_methods): New.
+	(objc_getClassList): Do not lock the class lock.
+	* methods.c (method_exchangeImplementations): New.
+	(method_setImplementation): New.
+	* objc/runtime.h (method_setImplementation): New.
+	(method_exchangeImplementations): New.
+	
+2010-10-15  Nicola Pero  <nicola.pero@meta-innovation.com>
+
 	* Protocol.m: Include objc/runtime.h and
 	objc-private/module-abi-8.h instead of objc/objc-api.h.  Do not
 	repeat Protocol's instance variables.
Index: objc/runtime.h
===================================================================
--- objc/runtime.h	(revision 165524)
+++ objc/runtime.h	(working copy)
@@ -545,7 +545,26 @@  objc_EXPORT void method_getReturnType (Method meth
 objc_EXPORT void method_getArgumentType (Method method, unsigned int argumentNumber,
 					 char *returnValue, size_t returnValueSize);
 
+/* Change the implementation of the method.  It also searches all
+   classes for any class implementing the method, and replaces the
+   existing implementation with the new one.  For that to work,
+   'method' must be a method returned by class_getInstanceMethod() or
+   class_getClassMethod() as the matching is done by comparing the
+   pointers; in that case, only the implementation in the class is
+   modified.  Return the previous implementation that has been
+   replaced.  If method or implementation is NULL, do nothing and
+   return NULL.  */
+objc_EXPORT IMP
+method_setImplementation (Method method, IMP implementation);
 
+/* Swap the implementation of two methods in a single, atomic
+   operation.  This is equivalent to getting the implementation of
+   each method and then calling method_setImplementation() on the
+   other one.  For this to work, the two methods must have been
+   returned by class_getInstanceMethod() or class_getClassMethod().
+   If 'method_a' or 'method_b' is NULL, do nothing.  */
+objc_EXPORT void
+method_exchangeImplementations (Method method_a, Method method_b);
 
 /** Implementation: the following functions are in protocols.c.  */