From patchwork Fri Oct 15 22:31:39 2010 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Nicola Pero X-Patchwork-Id: 68005 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Received: from sourceware.org (server1.sourceware.org [209.132.180.131]) by ozlabs.org (Postfix) with SMTP id 442B8B70E7 for ; Sat, 16 Oct 2010 09:32:00 +1100 (EST) Received: (qmail 26566 invoked by alias); 15 Oct 2010 22:31:56 -0000 Received: (qmail 26555 invoked by uid 22791); 15 Oct 2010 22:31:52 -0000 X-SWARE-Spam-Status: No, hits=-1.2 required=5.0 tests=AWL, BAYES_00, SARE_SUB_ENC_UTF8, TW_BJ, T_RP_MATCHES_RCVD X-Spam-Check-By: sourceware.org Received: from fencepost.gnu.org (HELO fencepost.gnu.org) (140.186.70.10) by sourceware.org (qpsmtpd/0.43rc1) with ESMTP; Fri, 15 Oct 2010 22:31:44 +0000 Received: from eggs.gnu.org ([140.186.70.92]:41071) by fencepost.gnu.org with esmtps (TLS-1.0:RSA_AES_256_CBC_SHA1:32) (Exim 4.69) (envelope-from ) id 1P6soP-0004eW-R8 for gcc-patches@gnu.org; Fri, 15 Oct 2010 18:31:41 -0400 Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1P6soN-0007ob-Q7 for gcc-patches@gnu.org; Fri, 15 Oct 2010 18:31:41 -0400 Received: from smtp141.iad.emailsrvr.com ([207.97.245.141]:59541) by eggs.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1P6soN-0007oX-Ln for gcc-patches@gnu.org; Fri, 15 Oct 2010 18:31:39 -0400 Received: from localhost (localhost.localdomain [127.0.0.1]) by smtp34.relay.iad1a.emailsrvr.com (SMTP Server) with ESMTP id 4B558380A2A for ; Fri, 15 Oct 2010 18:31:39 -0400 (EDT) X-Orig-To: gcc-patches@gnu.org Received: from dynamic8.wm-web.iad.mlsrvr.com (dynamic8.wm-web.iad1a.rsapps.net [192.168.2.149]) by smtp34.relay.iad1a.emailsrvr.com (SMTP Server) with ESMTP id 3682E3806AF for ; Fri, 15 Oct 2010 18:31:39 -0400 (EDT) Received: from meta-innovation.com (localhost [127.0.0.1]) by dynamic8.wm-web.iad.mlsrvr.com (Postfix) with ESMTP id 27D4D305006C for ; Fri, 15 Oct 2010 18:31:39 -0400 (EDT) Received: by www2.webmail.us (Authenticated sender: nicola.pero@meta-innovation.com, from: nicola.pero@meta-innovation.com) with HTTP; Sat, 16 Oct 2010 00:31:39 +0200 (CEST) Date: Sat, 16 Oct 2010 00:31:39 +0200 (CEST) Subject: =?UTF-8?Q?libobjc=20-=20more=20modern=20Objective-C=20runtime=20API=20(7?= =?UTF-8?Q?)?= From: "Nicola Pero" To: "gcc-patches@gnu.org" MIME-Version: 1.0 X-Type: plain Message-ID: <1287181899.161417877@192.168.2.228> X-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.6 (newer, 3) X-IsSubscribed: yes Mailing-List: contact gcc-patches-help@gcc.gnu.org; run by ezmlm Precedence: bulk List-Id: List-Unsubscribe: List-Archive: List-Post: List-Help: Sender: gcc-patches-owner@gcc.gnu.org Delivered-To: mailing list gcc-patches@gcc.gnu.org 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 * 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 * 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 + + * objc.dg/gnu-api-2-method.m: New. + 2010-10-15 Jason Merrill * 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 +#include +#include +#include + +@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 +{ 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; +} 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 /* 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 + * 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 + * 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. */