From ab84589b5d73cd519214424676d119b15cdca45a Mon Sep 17 00:00:00 2001 From: David Chisnall Date: Wed, 28 Mar 2018 15:05:58 +0100 Subject: [PATCH] Improve protocol method metadata. Methods now include a selector and extended type encoding, rather than a method name and lgacy type encoding. Older ones are auto-upgraded. Expose the extended type encoding via a function that JavaScriptCore expects to exist. --- Test/CMakeLists.txt | 5 +- Test/ProtocolExtendedProperties.m | 19 +++++++ legacy.c | 38 ++++++++++---- method_list.h | 7 +-- objc/runtime.h | 10 ++++ protocol.c | 83 +++++++++++++++++++++---------- protocol.h | 65 +++++++++++++++++------- 7 files changed, 166 insertions(+), 61 deletions(-) create mode 100644 Test/ProtocolExtendedProperties.m diff --git a/Test/CMakeLists.txt b/Test/CMakeLists.txt index 20e6bde..6de6624 100644 --- a/Test/CMakeLists.txt +++ b/Test/CMakeLists.txt @@ -20,6 +20,7 @@ set(TESTS ManyManySelectors.m NestedExceptions.m PropertyAttributeTest.m + ProtocolExtendedProperties.m PropertyIntrospectionTest.m PropertyIntrospectionTest2_arc.m ProtocolCreation.m @@ -82,9 +83,9 @@ function(addtest_flags TEST_NAME FLAGS TEST_SOURCE) endfunction(addtest_flags) function(addtest_variants TEST TEST_SOURCE LEGACY) - addtest_flags(${TEST} "-O0 -fobjc-runtime=gnustep-2.0 -UNDEBUG" "${TEST_SOURCE}") + addtest_flags(${TEST} "-O0 -fobjc-runtime=gnustep-2.0 -UNDEBUG -DGS_RUNTIME_V2" "${TEST_SOURCE}") target_sources(${TEST} PRIVATE $) - addtest_flags("${TEST}_optimised" "-O3 -fobjc-runtime=gnustep-2.0 -UNDEBUG" "${TEST_SOURCE}") + addtest_flags("${TEST}_optimised" "-O3 -fobjc-runtime=gnustep-2.0 -UNDEBUG -DGS_RUNTIME_V2" "${TEST_SOURCE}") target_sources("${TEST}_optimised" PRIVATE $) if (LEGACY) addtest_flags("${TEST}_legacy" "-O0 -fobjc-runtime=gnustep-1.7 -UNDEBUG" "${TEST_SOURCE}") diff --git a/Test/ProtocolExtendedProperties.m b/Test/ProtocolExtendedProperties.m new file mode 100644 index 0000000..2974c1b --- /dev/null +++ b/Test/ProtocolExtendedProperties.m @@ -0,0 +1,19 @@ +#include "Test.h" +#include + +@class NSString; +@protocol Foo +- (NSString*)aMethod: (void(^)(int))aBlock; +@end + +int main(void) +{ + const char *encoding = _protocol_getMethodTypeEncoding(@protocol(Foo), @selector(aMethod:), YES, YES); +#ifdef GS_RUNTIME_V2 + // We expect something like this (LP64): @"NSString"24@0:8@?16 + assert(strstr(encoding, "@\"NSString\"") == encoding); + assert(strstr(encoding, "@?") != NULL); +#else + assert(strstr(encoding, "@?") != NULL); +#endif +} diff --git a/legacy.c b/legacy.c index f803b2e..7d64c26 100644 --- a/legacy.c +++ b/legacy.c @@ -283,6 +283,26 @@ PRIVATE struct objc_category *objc_upgrade_category(struct objc_category_legacy return cat; } +static struct objc_protocol_method_description_list* +upgrade_protocol_method_list_gcc(struct objc_protocol_method_description_list_gcc *l) +{ + if ((l == NULL) || (l->count == 0)) + { + return NULL; + } + struct objc_protocol_method_description_list *n = + malloc(sizeof(struct objc_protocol_method_description_list) + + l->count * sizeof(struct objc_protocol_method_description)); + n->count = l->count; + n->size = sizeof(struct objc_protocol_method_description); + for (int i=0 ; icount ; i++) + { + n->methods[i].selector = sel_registerTypedName_np(l->methods[i].name, l->methods[i].types); + n->methods[i].types = l->methods[i].types; + } + return n; +} + PRIVATE struct objc_protocol *objc_upgrade_protocol_gcc(struct objc_protocol_gcc *p) { // If the protocol has already been upgraded, the don't try to upgrade it twice. @@ -297,8 +317,8 @@ PRIVATE struct objc_protocol *objc_upgrade_protocol_gcc(struct objc_protocol_gcc proto->name = p->name; // Aliasing this means that when this returns these will all be updated. proto->protocol_list = p->protocol_list; - proto->instance_methods = p->instance_methods; - proto->class_methods = p->class_methods; + proto->instance_methods = upgrade_protocol_method_list_gcc(p->instance_methods); + proto->class_methods = upgrade_protocol_method_list_gcc(p->class_methods); assert(proto->isa); return proto; } @@ -310,14 +330,12 @@ PRIVATE struct objc_protocol *objc_upgrade_protocol_gsv1(struct objc_protocol_gs { return (struct objc_protocol*)p; } - if (p->properties) - { - p->properties = (struct objc_property_list_gsv1*)upgradePropertyList(p->properties); - } - if (p->optional_properties) - { - p->optional_properties = (struct objc_property_list_gsv1*)upgradePropertyList(p->optional_properties); - } + // We do in-place upgrading of these, because they might be referenced + // directly + p->instance_methods = (struct objc_protocol_method_description_list_gcc*)upgrade_protocol_method_list_gcc(p->instance_methods); + p->class_methods = (struct objc_protocol_method_description_list_gcc*)upgrade_protocol_method_list_gcc(p->class_methods); + p->properties = (struct objc_property_list_gsv1*)upgradePropertyList(p->properties); + p->optional_properties = (struct objc_property_list_gsv1*)upgradePropertyList(p->optional_properties); p->isa = objc_getClass("Protocol"); assert(p->isa); return (struct objc_protocol*)p; diff --git a/method_list.h b/method_list.h index 3e64eec..c9f56da 100644 --- a/method_list.h +++ b/method_list.h @@ -8,14 +8,11 @@ struct objc_method */ IMP imp; /** - * Selector used to send messages to this method. The type encoding of - * this method should match the types field. + * Selector used to send messages to this method. */ SEL selector; /** - * The type encoding for this selector. Used only for introspection, and - * only required because of the stupid selector handling in the old GNU - * runtime. In future, this field may be reused for something else. + * The extended type encoding for this method. */ const char *types; }; diff --git a/objc/runtime.h b/objc/runtime.h index 6a720c4..984a74f 100644 --- a/objc/runtime.h +++ b/objc/runtime.h @@ -740,6 +740,16 @@ Protocol *__unsafe_unretained*objc_copyProtocolList(unsigned int *outCount); struct objc_method_description protocol_getMethodDescription(Protocol *p, SEL aSel, BOOL isRequiredMethod, BOOL isInstanceMethod); +/** + * Returns the extended type encoding of the specified method. + * + * Note: This function is used by JavaScriptCore but is not public in Apple's + * implementation and so its semantics may change in the future and this + * runtime may diverge from Apple's. + */ +const char *_protocol_getMethodTypeEncoding(Protocol *p, SEL aSel, + BOOL isRequiredMethod, BOOL isInstanceMethod); + /** * Returns the name of the specified protocol. */ diff --git a/protocol.c b/protocol.c index 3b10235..a72ec18 100644 --- a/protocol.c +++ b/protocol.c @@ -316,12 +316,12 @@ BOOL class_conformsToProtocol(Class cls, Protocol *protocol) return NO; } -static struct objc_method_description_list * +static struct objc_protocol_method_description_list * get_method_list(Protocol *p, BOOL isRequiredMethod, BOOL isInstanceMethod) { - struct objc_method_description_list *list; + struct objc_protocol_method_description_list *list; if (isRequiredMethod) { if (isInstanceMethod) @@ -353,7 +353,7 @@ struct objc_method_description *protocol_copyMethodDescriptionList(Protocol *p, BOOL isRequiredMethod, BOOL isInstanceMethod, unsigned int *count) { if ((NULL == p) || (NULL == count)){ return NULL; } - struct objc_method_description_list *list = + struct objc_protocol_method_description_list *list = get_method_list(p, isRequiredMethod, isInstanceMethod); *count = 0; if (NULL == list || list->count == 0) { return NULL; } @@ -363,9 +363,8 @@ struct objc_method_description *protocol_copyMethodDescriptionList(Protocol *p, calloc(sizeof(struct objc_method_description), list->count); for (int i=0 ; i < (list->count) ; i++) { - out[i].name = sel_registerTypedName_np(list->methods[i].name, - list->methods[i].types); - out[i].types = list->methods[i].types; + out[i].name = list->methods[i].selector; + out[i].types = sel_getType_np(list->methods[i].selector); } return out; } @@ -461,34 +460,61 @@ objc_property_t protocol_getProperty(Protocol *p, return NULL; } - -struct objc_method_description -protocol_getMethodDescription(Protocol *p, - SEL aSel, - BOOL isRequiredMethod, - BOOL isInstanceMethod) +static struct objc_protocol_method_description * +get_method_description(Protocol *p, + SEL aSel, + BOOL isRequiredMethod, + BOOL isInstanceMethod) { - struct objc_method_description d = {0,0}; - struct objc_method_description_list *list = + struct objc_protocol_method_description_list *list = get_method_list(p, isRequiredMethod, isInstanceMethod); if (NULL == list) { - return d; + return NULL; } - // TODO: We could make this much more efficient if for (int i=0 ; icount ; i++) { - SEL s = sel_registerTypedName_np(list->methods[i].name, 0); + SEL s = list->methods[i].selector; if (sel_isEqual(s, aSel)) { - d.name = s; - d.types = list->methods[i].types; - break; + return &list->methods[i]; } } + return NULL; +} + +struct objc_method_description +protocol_getMethodDescription(Protocol *p, + SEL aSel, + BOOL isRequiredMethod, + BOOL isInstanceMethod) +{ + struct objc_method_description d = {0,0}; + struct objc_protocol_method_description *m = + get_method_description(p, aSel, isRequiredMethod, isInstanceMethod); + if (m != NULL) + { + SEL s = m->selector; + d.name = s; + d.types = sel_getType_np(s); + } return d; } +const char *_protocol_getMethodTypeEncoding(Protocol *p, + SEL aSel, + BOOL isRequiredMethod, + BOOL isInstanceMethod) +{ + struct objc_protocol_method_description *m = + get_method_description(p, aSel, isRequiredMethod, isInstanceMethod); + if (m != NULL) + { + return m->types; + } + return NULL; +} + const char *protocol_getName(Protocol *p) { @@ -569,7 +595,7 @@ void protocol_addMethodDescription(Protocol *aProtocol, { if ((NULL == aProtocol) || (NULL == name) || (NULL == types)) { return; } if (incompleteProtocolClass() != aProtocol->isa) { return; } - struct objc_method_description_list **listPtr; + struct objc_protocol_method_description_list **listPtr; if (isInstanceMethod) { if (isRequiredMethod) @@ -594,19 +620,22 @@ void protocol_addMethodDescription(Protocol *aProtocol, } if (NULL == *listPtr) { - *listPtr = calloc(1, sizeof(struct objc_method_description_list) + sizeof(struct objc_method_description)); + // FIXME: Factor this out, we do the same thing in multiple places. + *listPtr = calloc(1, sizeof(struct objc_protocol_method_description_list) + + sizeof(struct objc_protocol_method_description)); (*listPtr)->count = 1; + (*listPtr)->size = sizeof(struct objc_protocol_method_description); } else { (*listPtr)->count++; - *listPtr = realloc(*listPtr, sizeof(struct objc_method_description_list) + - sizeof(struct objc_method_description) * (*listPtr)->count); + *listPtr = realloc(*listPtr, sizeof(struct objc_protocol_method_description_list) + + sizeof(struct objc_protocol_method_description) * (*listPtr)->count); } - struct objc_method_description_list *list = *listPtr; + struct objc_protocol_method_description_list *list = *listPtr; int index = list->count-1; - list->methods[index].name = sel_getName(name); - list->methods[index].types= types; + list->methods[index].selector = sel_registerTypedName_np(sel_getName(name), types); + list->methods[index].types = types; } void protocol_addProtocol(Protocol *aProtocol, Protocol *addition) { diff --git a/protocol.h b/protocol.h index db5375a..653234e 100644 --- a/protocol.h +++ b/protocol.h @@ -4,9 +4,9 @@ #include "selector.h" #include -struct objc_method_description_list +struct objc_protocol_method_description_list_gcc { - /** + /** * Number of method descriptions in this list. */ int count; @@ -18,6 +18,37 @@ struct objc_method_description_list struct objc_selector methods[]; }; +/** + * A description of a method in a protocol. + */ +struct objc_protocol_method_description +{ + /** + * The selector for this method, includes traditional type encoding. + */ + SEL selector; + /** + * The extended type encoding. + */ + const char *types; +}; + +struct objc_protocol_method_description_list +{ + /** + * Number of method descriptions in this list. + */ + int count; + /** + * Size of `struct objc_method_description` + */ + int size; + /** + * Methods in this list. `count` elements long. + */ + struct objc_protocol_method_description methods[]; +}; + struct objc_protocol { /** @@ -26,22 +57,22 @@ struct objc_protocol id isa; char *name; struct objc_protocol_list *protocol_list; - struct objc_method_description_list *instance_methods; - struct objc_method_description_list *class_methods; + struct objc_protocol_method_description_list *instance_methods; + struct objc_protocol_method_description_list *class_methods; /** * Instance methods that are declared as optional for this protocol. */ - struct objc_method_description_list *optional_instance_methods; + struct objc_protocol_method_description_list *optional_instance_methods; /** * Class methods that are declared as optional for this protocol. */ - struct objc_method_description_list *optional_class_methods; + struct objc_protocol_method_description_list *optional_class_methods; /** * Properties that are required by this protocol. */ struct objc_property_list *properties; /** - * Optional properties. + * Optional properties. */ struct objc_property_list *optional_properties; }; @@ -51,9 +82,9 @@ struct objc_protocol_gcc { /** Class pointer. */ id isa; - /** + /** * The name of this protocol. Two protocols are regarded as identical if - * they have the same name. + * they have the same name. */ char *name; /** @@ -63,11 +94,11 @@ struct objc_protocol_gcc /** * List of instance methods required by this protocol. */ - struct objc_method_description_list *instance_methods; + struct objc_protocol_method_description_list_gcc *instance_methods; /** * List of class methods required by this protocol. */ - struct objc_method_description_list *class_methods; + struct objc_protocol_method_description_list_gcc *class_methods; }; struct objc_protocol_gsv1 @@ -78,22 +109,22 @@ struct objc_protocol_gsv1 id isa; char *name; struct objc_protocol_list *protocol_list; - struct objc_method_description_list *instance_methods; - struct objc_method_description_list *class_methods; + struct objc_protocol_method_description_list_gcc *instance_methods; + struct objc_protocol_method_description_list_gcc *class_methods; /** * Instance methods that are declared as optional for this protocol. */ - struct objc_method_description_list *optional_instance_methods; + struct objc_protocol_method_description_list_gcc *optional_instance_methods; /** * Class methods that are declared as optional for this protocol. */ - struct objc_method_description_list *optional_class_methods; + struct objc_protocol_method_description_list_gcc *optional_class_methods; /** * Properties that are required by this protocol. */ struct objc_property_list_gsv1 *properties; /** - * Optional properties. + * Optional properties. */ struct objc_property_list_gsv1 *optional_properties; }; @@ -101,7 +132,7 @@ struct objc_protocol_gsv1 // Note: If you introduce a new protocol type that is larger than the current // one then it's fine to auto-upgrade anything using the v2 ABI, because // protocol structures there are referenced only via the indirection layer or -// via other runtime-managed structures. +// via other runtime-managed structures. // // Auto-upgrading GNUstep v1 ABI protocols relies on their being the same size // as v2, so the upgrade can happen in place. If this isn't possible, then we