From a9d0313080586c79e1aa4af189a499d627a9b6cf Mon Sep 17 00:00:00 2001 From: David Chisnall Date: Thu, 6 Jun 2024 09:12:17 +0000 Subject: [PATCH] Don't rely on load order for built-in classes. Generate these classes using the structures that the runtime expects internally, rather than relying on the Objective-C compiler. This change means that they can always be the latest version, even if the runtime is compiled with an older compiler, and ensures that the `Protocol` class is always available, independent of global constructor ordering between libraries. Fixes #283 --- CMakeLists.txt | 2 +- Protocol2.m | 45 ----------- Test/PropertyIntrospectionTest2_arc.m | 2 +- builtin_classes.c | 106 ++++++++++++++++++++++++++ class.h | 2 + class_table.c | 1 + legacy.c | 17 ++--- loader.c | 32 ++------ loader.h | 59 ++++++++++++++ method.h | 1 + protocol.c | 98 +++++------------------- protocol.h | 21 ++--- selector.h | 2 + 13 files changed, 212 insertions(+), 176 deletions(-) delete mode 100644 Protocol2.m create mode 100644 builtin_classes.c diff --git a/CMakeLists.txt b/CMakeLists.txt index 0242d976..bf38b34c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -57,13 +57,13 @@ set(libobjc_OBJCXX_SRCS ) set(libobjc_OBJC_SRCS NSBlocks.m - Protocol2.m associate.m blocks_runtime_np.m properties.m) set(libobjc_C_SRCS alias_table.c block_to_imp.c + builtin_classes.c caps.c category_loader.c class_table.c diff --git a/Protocol2.m b/Protocol2.m deleted file mode 100644 index 154403ba..00000000 --- a/Protocol2.m +++ /dev/null @@ -1,45 +0,0 @@ -#include "objc/runtime.h" -#include "protocol.h" -#include "class.h" -#include -#include - -@implementation Protocol -// FIXME: This needs removing, but it's included for now because GNUstep's -// implementation of +[NSObject conformsToProtocol:] calls it. -- (BOOL)conformsTo: (Protocol*)p -{ - return protocol_conformsToProtocol(self, p); -} -- (id)retain -{ - return self; -} -- (void)release {} -+ (Class)class { return self; } -- (id)self { return self; } -@end -@interface __IncompleteProtocol : Protocol @end -@implementation __IncompleteProtocol @end - -/** - * This class exists for the sole reason that the legacy GNU ABI did not - * provide a way of registering protocols with the runtime. With the new ABI, - * every protocol in a compilation unit that is not referenced should be added - * in a category on this class. This ensures that the runtime sees every - * protocol at least once and can perform uniquing. - */ -@interface __ObjC_Protocol_Holder_Ugly_Hack { id isa; } @end -@implementation __ObjC_Protocol_Holder_Ugly_Hack @end - -@implementation Object @end - -@implementation ProtocolGCC @end -@implementation ProtocolGSv1 @end - -PRIVATE void link_protocol_classes(void) -{ - [Protocol class]; - [ProtocolGCC class]; - [ProtocolGSv1 class]; -} diff --git a/Test/PropertyIntrospectionTest2_arc.m b/Test/PropertyIntrospectionTest2_arc.m index a0fe2e5c..0f8eb79c 100644 --- a/Test/PropertyIntrospectionTest2_arc.m +++ b/Test/PropertyIntrospectionTest2_arc.m @@ -302,7 +302,7 @@ static BOOL testPropertyForProperty_alt(objc_property_t p, attrsList = property_copyAttributeList(p, NULL); OPT_ASSERT(0 != attrsList); objc_property_attribute_t *ra; - for (attrsCount = 0, ra = attrsList; (ra->name != NULL) && (attrsCount < size); attrsCount++, ra++) {} + for (attrsCount = 0, ra = attrsList; (attrsCount < size) && (ra->name != NULL) ; attrsCount++, ra++) {} OPT_ASSERT(attrsCount == size); free(attrsList); for (unsigned int index=0; indexisa == objc_getClass("ProtocolGCC")) + if (p->isa == (id)&_OBJC_CLASS_ProtocolGCC) { return objc_getProtocol(p->name); } - p->isa = objc_getClass("ProtocolGCC"); + p->isa = (id)&_OBJC_CLASS_ProtocolGCC; Protocol *proto = - (Protocol*)class_createInstance((Class)objc_getClass("Protocol"), - sizeof(struct objc_protocol) - sizeof(id)); + (Protocol*)class_createInstance(&_OBJC_CLASS_Protocol, 0); proto->name = p->name; // Aliasing of this between the new and old structures means that when this // returns these will all be updated. @@ -432,13 +432,12 @@ PRIVATE struct objc_protocol *objc_upgrade_protocol_gcc(struct objc_protocol_gcc PRIVATE struct objc_protocol *objc_upgrade_protocol_gsv1(struct objc_protocol_gsv1 *p) { // If the protocol has already been upgraded, the don't try to upgrade it twice. - if (p->isa == objc_getClass("ProtocolGSv1")) + if (p->isa == (id)&_OBJC_CLASS_ProtocolGSv1) { return objc_getProtocol(p->name); } Protocol *n = - (Protocol*)class_createInstance((Class)objc_getClass("Protocol"), - sizeof(struct objc_protocol) - sizeof(id)); + (Protocol*)class_createInstance(&_OBJC_CLASS_Protocol, 0); n->instance_methods = upgrade_protocol_method_list_gcc(p->instance_methods); // Aliasing of this between the new and old structures means that when this // returns these will all be updated. @@ -447,14 +446,14 @@ PRIVATE struct objc_protocol *objc_upgrade_protocol_gsv1(struct objc_protocol_gs n->class_methods = upgrade_protocol_method_list_gcc(p->class_methods); n->properties = upgradePropertyList(p->properties); n->optional_properties = upgradePropertyList(p->optional_properties); - n->isa = objc_getClass("Protocol"); + n->isa = (id)&_OBJC_CLASS_Protocol; // We do in-place upgrading of these, because they might be referenced // directly p->instance_methods = (struct objc_protocol_method_description_list_gcc*)n->instance_methods; p->class_methods = (struct objc_protocol_method_description_list_gcc*)n->class_methods; p->properties = (struct objc_property_list_gsv1*)n->properties; p->optional_properties = (struct objc_property_list_gsv1*)n->optional_properties; - p->isa = objc_getClass("ProtocolGSv1"); + p->isa = (id)&_OBJC_CLASS_ProtocolGSv1; assert(p->isa); return n; } diff --git a/loader.c b/loader.c index a49100a9..449d7a7c 100644 --- a/loader.c +++ b/loader.c @@ -19,17 +19,6 @@ PRIVATE mutex_t runtime_mutex; LEGACY void *__objc_runtime_mutex = &runtime_mutex; -void init_alias_table(void); -void init_arc(void); -void init_class_tables(void); -void init_dispatch_tables(void); -void init_gc(void); -void init_protocol_table(void); -void init_selector_tables(void); -void init_trampolines(void); -void init_early_blocks(void); -void objc_send_load_message(Class class); - void log_selector_memory_usage(void); static void log_memory_stats(void) @@ -46,20 +35,11 @@ __attribute__((weak)) void (*dispatch_end_thread_4GC)(void); __attribute__((weak)) void *(*_dispatch_begin_NSAutoReleasePool)(void); __attribute__((weak)) void (*_dispatch_end_NSAutoReleasePool)(void *); -__attribute__((used)) -static void link_protos(void) -{ - link_protocol_classes(); -} - static void init_runtime(void) { static BOOL first_run = YES; if (first_run) { -#if ENABLE_GC - init_gc(); -#endif // Create the main runtime lock. This is not safe in theory, but in // practice the first time that this function is called will be in the // loader, from the main thread. Future loaders may run concurrently, @@ -80,6 +60,7 @@ static void init_runtime(void) init_early_blocks(); init_arc(); init_trampolines(); + init_builtin_classes(); first_run = NO; if (getenv("LIBOBJC_MEMORY_PROFILE")) { @@ -253,22 +234,23 @@ OBJC_PUBLIC void __objc_load(struct objc_init *init) assert(p); *proto = p; } + int classesLoaded = 0; for (Class *cls = init->cls_begin ; cls < init->cls_end ; cls++) { if (*cls == NULL) { continue; } - // As a special case, allow using legacy ABI code with a new runtime. - if (isFirstLoad && (strcmp((*cls)->name, "Protocol") == 0)) - { - CurrentABI = UnknownABI; - } #ifdef DEBUG_LOADING fprintf(stderr, "Loading class %s\n", (*cls)->name); #endif objc_load_class(*cls); } + if (isFirstLoad && (classesLoaded == 0)) + { + // As a special case, allow using legacy ABI code with a new runtime. + CurrentABI = UnknownABI; + } #if 0 // We currently don't do anything with these pointers. They exist to // provide a level of indirection that will permit us to completely change diff --git a/loader.h b/loader.h index 21ff1d63..541c31ee 100644 --- a/loader.h +++ b/loader.h @@ -64,4 +64,63 @@ void objc_init_statics(struct objc_static_instance_list *statics); */ void objc_init_buffered_statics(void); +/** + * Initialise built-in classes (Object and Protocol). This must be called + * after `init_class_tables`. + */ +void init_builtin_classes(void); + +/** + * Initialise the aliases table. + */ +void init_alias_table(void); + +/** + * Initialise the automatic reference counting system. + */ +void init_arc(void); + +/** + * Initialise the class tables. + */ +void init_class_tables(void); + +/** + * Initialise the dispatch table machinery. + */ +void init_dispatch_tables(void); + +/** + * Initialise the protocol tables. + */ +void init_protocol_table(void); + +/** + * Initialise the selector tables. + */ +void init_selector_tables(void); + +/** + * Initialise the trampolines for using blocks as methods. + */ +void init_trampolines(void); + +/** + * Send +load messages to a class if required. + */ +void objc_send_load_message(Class cls); + +/** + * Resolve a class (populate its superclass and sibling class links). Returns + * YES if the class can be resolved, NO otherwise. Classes cannot be resolved + * unless their superclasses have all been resolved. + */ +BOOL objc_resolve_class(Class cls); + +/** + * Initialise the block classes. + */ +void init_early_blocks(void); + + #endif //__OBJC_LOADER_H_INCLUDED diff --git a/method.h b/method.h index 4e2997bf..a155cb84 100644 --- a/method.h +++ b/method.h @@ -1,3 +1,4 @@ +#pragma once #include /** diff --git a/protocol.c b/protocol.c index e0144e24..bd13dad5 100644 --- a/protocol.c +++ b/protocol.c @@ -31,6 +31,8 @@ static int protocol_hash(const struct objc_protocol *protocol) static protocol_table *known_protocol_table; mutex_t protocol_table_lock; + + PRIVATE void init_protocol_table(void) { protocol_initialize(&known_protocol_table, 128); @@ -47,68 +49,14 @@ struct objc_protocol *protocol_for_name(const char *name) return protocol_table_get(known_protocol_table, name); } -static id incompleteProtocolClass(void) -{ - static id IncompleteProtocolClass = 0; - if (IncompleteProtocolClass == nil) - { - IncompleteProtocolClass = objc_getClass("__IncompleteProtocol"); - } - return IncompleteProtocolClass; -} - -/** - * Class used for legacy GCC protocols (`ProtocolGCC`). - */ -static id protocol_class_gcc; -/** - * Class used for legacy GNUstep V1 ABI protocols (`ProtocolGSv1`). - */ -static id protocol_class_gsv1; -/** - * Class used for protocols (`Protocol`). - */ -static id protocol_class_gsv2; - -static BOOL init_protocol_classes(void) -{ - if (nil == protocol_class_gcc) - { - protocol_class_gcc = objc_getClass("ProtocolGCC"); - } - if (nil == protocol_class_gsv1) - { - protocol_class_gsv1 = objc_getClass("ProtocolGSv1"); - } - if (nil == protocol_class_gsv2) - { - protocol_class_gsv2 = objc_getClass("Protocol"); - } - if ((nil == protocol_class_gcc) || - (nil == protocol_class_gsv1) || - (nil == protocol_class_gsv2)) - { - return NO; - } - return YES; -} - static BOOL protocol_hasClassProperties(struct objc_protocol *p) { - if (!init_protocol_classes()) - { - return NO; - } - return p->isa == protocol_class_gsv2; + return p->isa == (id)&_OBJC_CLASS_Protocol; } static BOOL protocol_hasOptionalMethodsAndProperties(struct objc_protocol *p) { - if (!init_protocol_classes()) - { - return NO; - } - if (p->isa == protocol_class_gcc) + if (p->isa == (id)&_OBJC_CLASS_ProtocolGCC) { return NO; } @@ -198,18 +146,13 @@ static struct objc_protocol *unique_protocol(struct objc_protocol *aProto) static BOOL init_protocols(struct objc_protocol_list *protocols) { - if (!init_protocol_classes()) - { - return NO; - } - for (unsigned i=0 ; icount ; i++) { struct objc_protocol *aProto = protocols->list[i]; // Don't initialise a protocol twice - if ((aProto->isa == protocol_class_gcc) || - (aProto->isa == protocol_class_gsv1) || - (aProto->isa == protocol_class_gsv2)) + if ((aProto->isa == (id)&_OBJC_CLASS_ProtocolGCC) || + (aProto->isa == (id)&_OBJC_CLASS_ProtocolGSv1) || + (aProto->isa == (id)&_OBJC_CLASS_Protocol)) { continue; } @@ -226,19 +169,19 @@ static BOOL init_protocols(struct objc_protocol_list *protocols) #ifdef OLDABI_COMPAT case protocol_version_gcc: protocols->list[i] = objc_upgrade_protocol_gcc((struct objc_protocol_gcc *)aProto); - assert(aProto->isa == protocol_class_gcc); - assert(protocols->list[i]->isa == protocol_class_gsv2); + assert(aProto->isa == (id)&_OBJC_CLASS_ProtocolGCC); + assert(protocols->list[i]->isa == (id)&_OBJC_CLASS_Protocol); aProto = protocols->list[i]; break; case protocol_version_gsv1: protocols->list[i] = objc_upgrade_protocol_gsv1((struct objc_protocol_gsv1 *)aProto); - assert(aProto->isa == protocol_class_gsv1); - assert(protocols->list[i]->isa == protocol_class_gsv2); + assert(aProto->isa == (id)&_OBJC_CLASS_ProtocolGSv1); + assert(protocols->list[i]->isa == (id)&_OBJC_CLASS_Protocol); aProto = protocols->list[i]; break; #endif case protocol_version_gsv2: - aProto->isa = protocol_class_gsv2; + aProto->isa = (id)&_OBJC_CLASS_Protocol; break; } // Initialize all of the protocols that this protocol refers to @@ -585,8 +528,7 @@ Protocol *objc_allocateProtocol(const char *name) { if (objc_getProtocol(name) != NULL) { return NULL; } // Create this as an object and add extra space at the end for the properties. - Protocol *p = (Protocol*)class_createInstance((Class)incompleteProtocolClass(), - sizeof(struct objc_protocol) - sizeof(id)); + Protocol *p = (Protocol*)class_createInstance(&_OBJC_CLASS___IncompleteProtocol, 0); p->name = strdup(name); return p; } @@ -595,16 +537,14 @@ void objc_registerProtocol(Protocol *proto) if (NULL == proto) { return; } LOCK_FOR_SCOPE(&protocol_table_lock); if (objc_getProtocol(proto->name) != NULL) { return; } - if (incompleteProtocolClass() != proto->isa) { return; } - init_protocol_classes(); - proto->isa = protocol_class_gsv2; + if (proto->isa != (id)&_OBJC_CLASS___IncompleteProtocol) { return; } + proto->isa = (id)&_OBJC_CLASS_Protocol; protocol_table_insert(proto); } PRIVATE void registerProtocol(Protocol *proto) { - init_protocol_classes(); LOCK_FOR_SCOPE(&protocol_table_lock); - proto->isa = protocol_class_gsv2; + proto->isa = (id)&_OBJC_CLASS_Protocol; if (protocol_for_name(proto->name) == NULL) { protocol_table_insert(proto); @@ -617,7 +557,7 @@ void protocol_addMethodDescription(Protocol *aProtocol, BOOL isInstanceMethod) { if ((NULL == aProtocol) || (NULL == name) || (NULL == types)) { return; } - if (incompleteProtocolClass() != aProtocol->isa) { return; } + if (aProtocol->isa != (id)(id)&_OBJC_CLASS___IncompleteProtocol) { return; } struct objc_protocol_method_description_list **listPtr; if (isInstanceMethod) { @@ -663,7 +603,7 @@ void protocol_addMethodDescription(Protocol *aProtocol, void protocol_addProtocol(Protocol *aProtocol, Protocol *addition) { if ((NULL == aProtocol) || (NULL == addition)) { return; } - if (incompleteProtocolClass() != aProtocol->isa) { return; } + if (aProtocol->isa != (id)&_OBJC_CLASS___IncompleteProtocol) { return; } if (NULL == aProtocol->protocol_list) { aProtocol->protocol_list = calloc(1, sizeof(struct objc_property_list) + sizeof(Protocol*)); @@ -685,7 +625,7 @@ void protocol_addProperty(Protocol *aProtocol, BOOL isInstanceProperty) { if ((NULL == aProtocol) || (NULL == name)) { return; } - if (incompleteProtocolClass() != aProtocol->isa) { return; } + if (aProtocol->isa != (id)&_OBJC_CLASS___IncompleteProtocol) { return; } if (!isInstanceProperty) { return; } struct objc_property_list **listPtr = isInstanceProperty ? diff --git a/protocol.h b/protocol.h index 15a0838a..a5fd7ec7 100644 --- a/protocol.h +++ b/protocol.h @@ -187,22 +187,11 @@ struct objc_protocol_gsv1 struct objc_property_list_gsv1 *optional_properties; }; -#ifdef __OBJC__ -@interface Object { id isa; } @end -/** - * Definition of the Protocol type. Protocols are objects, but are rarely used - * as such. - */ -@interface Protocol : Object -@end - -@interface ProtocolGCC : Protocol -@end - -@interface ProtocolGSv1 : Protocol -@end - -#endif +OBJC_PUBLIC extern struct objc_class _OBJC_CLASS_Object; +OBJC_PUBLIC extern struct objc_class _OBJC_CLASS_Protocol; +OBJC_PUBLIC extern struct objc_class _OBJC_CLASS___IncompleteProtocol; +OBJC_PUBLIC extern struct objc_class _OBJC_CLASS_ProtocolGCC; +OBJC_PUBLIC extern struct objc_class _OBJC_CLASS_ProtocolGSv1; /** * List of protocols. Attached to a class or a category by the compiler and to diff --git a/selector.h b/selector.h index 10307492..c9409873 100644 --- a/selector.h +++ b/selector.h @@ -1,5 +1,7 @@ #ifndef OBJC_SELECTOR_H_INCLUDED #define OBJC_SELECTOR_H_INCLUDED +#include +#include "objc/runtime.h" /** * Structure used to store selectors in the list.