From f91fb2e745171cd1511a083cc87ebe5c4a63dd08 Mon Sep 17 00:00:00 2001 From: David Chisnall Date: Fri, 29 Dec 2017 15:39:04 +0000 Subject: [PATCH] Checkpoint more work on new ABI. We're now using a new class and category structure and auto-upgrading the old ones. Other changes: - The Ivar structure now points to the ivar offset variable, so we can more easily find it. - Categories can now add properties. --- CMakeLists.txt | 1 + category.h | 37 +++++++++++---- category_loader.c | 11 +++++ class.h | 117 ++++++++++++++++++++++++++++++++++++++++++++-- class_table.c | 15 +++--- ivar.c | 88 ++++++++-------------------------- ivar.h | 6 +-- legacy.c | 93 ++++++++++++++++++++++++++++++++++++ legacy.h | 11 +++++ loader.c | 23 +++++---- runtime.c | 18 +++---- 11 files changed, 306 insertions(+), 114 deletions(-) create mode 100644 legacy.c create mode 100644 legacy.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 1f15662..6d66c5e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -42,6 +42,7 @@ set(libobjc_C_SRCS hooks.c ivar.c legacy_malloc.c + legacy.c loader.c mutation.m protocol.c diff --git a/category.h b/category.h index d87efa5..2797255 100644 --- a/category.h +++ b/category.h @@ -1,14 +1,9 @@ - +#pragma once /** * The structure used to represent a category. * * This provides a set of new definitions that are used to replace those * contained within a class. - * - * Note: Objective-C 2 allows properties to be added to classes. The current - * ABI does not provide a field for adding properties in categories. This is - * likely to be added with ABI version 10. Until then, the methods created by - * a declared property will work, but introspection on the property will not. */ struct objc_category { @@ -34,8 +29,34 @@ struct objc_category struct objc_protocol_list *protocols; /** * The list of properties added by this category - * - * Only present in V2 ABI! */ struct objc_property_list *properties; + /** + * Class properties. + */ + struct objc_property_list *class_properties; +}; + +struct objc_category_legacy +{ + /** + * The name of this category. + */ + const char *name; + /** + * The name of the class to which this category should be applied. + */ + const char *class_name; + /** + * The list of instance methods to add to the class. + */ + struct objc_method_list *instance_methods; + /** + * The list of class methods to add to the class. + */ + struct objc_method_list *class_methods; + /** + * The list of protocols adopted by this category. + */ + struct objc_protocol_list *protocols; }; diff --git a/category_loader.c b/category_loader.c index 8dce139..9f8f395 100644 --- a/category_loader.c +++ b/category_loader.c @@ -3,6 +3,7 @@ #include "visibility.h" #include "loader.h" #include "dtable.h" +#include "properties.h" #define BUFFER_TYPE struct objc_category * #include "buffer.h" @@ -39,6 +40,16 @@ static void load_category(struct objc_category *cat, struct objc_class *class) cat->protocols->next = class->protocols; class->protocols = cat->protocols; } + if (cat->properties) + { + cat->properties->next = class->properties; + class->properties = cat->properties; + } + if (cat->class_properties) + { + cat->class_properties->next = class->isa->properties; + class->isa->properties = cat->class_properties; + } } static BOOL try_load_category(struct objc_category *cat) diff --git a/class.h b/class.h index 222b78f..dacb3a5 100644 --- a/class.h +++ b/class.h @@ -1,6 +1,7 @@ #ifndef __OBJC_CLASS_H_INCLUDED #define __OBJC_CLASS_H_INCLUDED #include "visibility.h" +#include /** * Overflow bitfield. Used for bitfields that are more than 63 bits. @@ -118,6 +119,101 @@ struct objc_class * ABI by using the CLS_ISNEW_ABI() macro. */ + /** + * The version of the ABI used for this class. Zero indicates the ABI first + * implemented by clang 1.0. One indicates the presence of bitmaps + * indicating the offsets of strong, weak, and unretained ivars. Two + * indicates that the new ivar structure is used. + */ + long abi_version; + /** + * List of declared properties on this class (NULL if none). + */ + struct objc_property_list *properties; +}; + +struct legacy_gnustep_objc_class +{ + /** + * Pointer to the metaclass for this class. The metaclass defines the + * methods use when a message is sent to the class, rather than an + * instance. + */ + struct objc_class *isa; + /** + * Pointer to the superclass. The compiler will set this to the name of + * the superclass, the runtime will initialize it to point to the real + * class. + */ + struct objc_class *super_class; + /** + * The name of this class. Set to the same value for both the class and + * its associated metaclass. + */ + const char *name; + /** + * The version of this class. This is not used by the language, but may be + * set explicitly at class load time. + */ + long version; + /** + * A bitfield containing various flags. See the objc_class_flags + * enumerated type for possible values. + */ + unsigned long info; + /** + * The size of this class. For classes using the non-fragile ABI, the + * compiler will set this to a negative value The absolute value will be + * the size of the instance variables defined on just this class. When + * using the fragile ABI, the instance size is the size of instances of + * this class, including any instance variables defined on superclasses. + * + * In both cases, this will be set to the size of an instance of the class + * after the class is registered with the runtime. + */ + long instance_size; + /** + * Metadata describing the instance variables in this class. + */ + struct objc_ivar_list_legacy *ivars; + /** + * Metadata for for defining the mappings from selectors to IMPs. Linked + * list of method list structures, one per class and one per category. + */ + struct objc_method_list *methods; + /** + * The dispatch table for this class. Intialized and maintained by the + * runtime. + */ + void *dtable; + /** + * A pointer to the first subclass for this class. Filled in by the + * runtime. + */ + struct objc_class *subclass_list; + /** + * A pointer to the next sibling class to this. You may find all + * subclasses of a given class by following the subclass_list pointer and + * then subsequently following the sibling_class pointers in the + * subclasses. + */ + struct objc_class *sibling_class; + + /** + * Metadata describing the protocols adopted by this class. Not used by + * the runtime. + */ + struct objc_protocol_list *protocols; + /** + * Linked list of extra data attached to this class. + */ + struct reference_list *extra_data; + /** + * New ABI. The following fields are only available with classes compiled to + * support the new ABI. You may test whether any given class supports this + * ABI by using the CLS_ISNEW_ABI() macro. + */ + /** * The version of the ABI used for this class. Zero indicates the ABI first * implemented by clang 1.0. One indicates the presence of bitmaps @@ -170,12 +266,12 @@ struct objc_class }; /** - * Structure representing the old ABI class structure. This is only ever + * Structure representing the GCC ABI class structure. This is only ever * required so that we can take its size - struct objc_class begins with the * same fields, and you can test the new abi flag to tell whether it is safe to * access the subsequent fields. */ -struct legacy_abi_objc_class +struct legacy_gcc_objc_class { struct objc_class *isa; struct objc_class *super_class; @@ -183,7 +279,7 @@ struct legacy_abi_objc_class long version; unsigned long info; long instance_size; - struct objc_ivar_list *ivars; + struct objc_ivar_list_legacy *ivars; struct objc_method_list *methods; void *dtable; struct objc_class *subclass_list; @@ -266,6 +362,12 @@ static inline BOOL objc_test_class_flag(struct objc_class *aClass, { return (aClass->info & (unsigned long)flag) == (unsigned long)flag; } + +static inline BOOL objc_test_class_flag_legacy(struct legacy_gnustep_objc_class *aClass, + enum objc_class_flags flag) +{ + return (aClass->info & (unsigned long)flag) == (unsigned long)flag; +} /** * Checks the version of a class. Return values are: * 0. Legacy GCC ABI compatible class. @@ -282,6 +384,15 @@ static inline int objc_get_class_version(struct objc_class *aClass) return aClass->abi_version + 1; } +static inline int objc_get_class_version_legacy(struct legacy_gnustep_objc_class *aClass) +{ + if (!objc_test_class_flag_legacy(aClass, objc_class_flag_new_abi)) + { + return 0; + } + return aClass->abi_version + 1; +} + /** * Adds a class to the class table. */ diff --git a/class_table.c b/class_table.c index 2942251..c8b3549 100644 --- a/class_table.c +++ b/class_table.c @@ -12,7 +12,6 @@ #include #include -void objc_register_selectors_from_class(Class class); void objc_init_protocols(struct objc_protocol_list *protos); void objc_compute_ivar_offsets(Class class); @@ -243,6 +242,12 @@ PRIVATE BOOL objc_resolve_class(Class cls) // Fix up the ivar offsets objc_compute_ivar_offsets(cls); + struct legacy_gnustep_objc_class *oldCls = objc_legacy_class_for_class(cls); + if (oldCls) + { + oldCls->super_class = cls->super_class; + oldCls->isa->super_class = cls->isa->super_class; + } // Send the +load message, if required if (!objc_test_class_flag(cls, objc_class_flag_user_created)) { @@ -361,10 +366,6 @@ static void reload_class(struct objc_class *class, struct objc_class *old) // this class will now return this class. class_table_internal_table_set(class_table, (void*)class->name, class); - // Register all of the selectors used by this class and its metaclass - objc_register_selectors_from_class(class); - objc_register_selectors_from_class(class->isa); - // Set the uninstalled dtable. The compiler could do this as well. class->dtable = uninstalled_dtable; class->isa->dtable = uninstalled_dtable; @@ -415,10 +416,6 @@ PRIVATE void objc_load_class(struct objc_class *class) // Insert the class into the class table class_table_insert(class); - // Register all of the selectors used by this class and its metaclass - objc_register_selectors_from_class(class); - objc_register_selectors_from_class(class->isa); - // Set the uninstalled dtable. The compiler could do this as well. class->dtable = uninstalled_dtable; class->isa->dtable = uninstalled_dtable; diff --git a/ivar.c b/ivar.c index 20b8c42..9411d68 100644 --- a/ivar.c +++ b/ivar.c @@ -11,7 +11,6 @@ ptrdiff_t objc_alignof_type(const char *); ptrdiff_t objc_sizeof_type(const char *); -static struct objc_ivar_list *upgradeIvarList(Class cls, struct objc_ivar_list_legacy *l); PRIVATE void objc_compute_ivar_offsets(Class class) { @@ -25,13 +24,6 @@ PRIVATE void objc_compute_ivar_offsets(Class class) } return; } - // If this is an old ABI class, then replace the ivar list with the new - // version - if (objc_get_class_version(class) < 3) - { - legacy = (struct objc_ivar_list_legacy *)class->ivars; - class->ivars = upgradeIvarList(class, legacy); - } if (class->ivars->size != sizeof(struct objc_ivar)) { fprintf(stderr, "Downgrading ivar struct not yet implemented"); @@ -79,49 +71,47 @@ PRIVATE void objc_compute_ivar_offsets(Class class) // the time, but if we have an instance variable that is a vector type // then we will need to ensure that we are properly aligned again. long ivar_size = (i+1 == class->ivars->count) - ? (class_size - ivar->offset) - : class->ivars->ivar_list[i+1].offset - ivar->offset ; + ? (class_size - *ivar->offset) + : *class->ivars->ivar_list[i+1].offset - *ivar->offset ; // FIXME: use alignment - ivar->offset += cumulative_fudge; + *ivar->offset += cumulative_fudge; // We only need to do the realignment for things that are // bigger than a pointer, and we don't need to do it in GC mode // where we don't add any extra padding. if (!isGCEnabled && (ivar_size > sizeof(void*))) { - long offset = ivar_start + ivar->offset + sizeof(intptr_t); + long offset = ivar_start + *ivar->offset + sizeof(intptr_t); // For now, assume that nothing needs to be more than 16-byte aligned. // This is not correct for AVX vectors, but we probably // can't do anything about that for now (as malloc is only // giving us 16-byte aligned memory) long fudge = 16 - (offset % 16); - ivar->offset += fudge; + *ivar->offset += fudge; class->instance_size += fudge; cumulative_fudge += fudge; - assert((ivar_start + ivar->offset + sizeof(intptr_t)) % 16 == 0); - } - ivar->offset += ivar_start; - /* If we're using the new ABI then we also set up the faster ivar - * offset variables. - */ - if (objc_test_class_flag(class, objc_class_flag_new_abi)) - { - *(class->ivar_offsets[i]) = ivar->offset; + assert((ivar_start + *ivar->offset + sizeof(intptr_t)) % 16 == 0); } - // If we have a legacy ivar list, update the offset in it too - - // code from older compilers may access this directly! - if (legacy != NULL) + *ivar->offset += ivar_start; + } + // If we have a legacy ivar list, update the offset in it too - + // code from older compilers may access this directly! + struct legacy_gnustep_objc_class* legacy = objc_legacy_class_for_class(class); + if (legacy) + { + for (i = 0 ; i < class->ivars->count ; i++) { - legacy->ivar_list[i].offset = ivar->offset; + legacy->ivars->ivar_list[i].offset = *class->ivars->ivar_list[i].offset; } } } } - else + //else + if (0) { if (NULL == class->ivars) { return; } Class super = class_getSuperclass(class); - int start = class->ivars->ivar_list[0].offset; + int start = *class->ivars->ivar_list[0].offset; /* Quick and dirty test. If the first ivar comes straight after the last * class, then it's fine. */ if (Nil == super || start == super->instance_size) {return; } @@ -136,7 +126,7 @@ PRIVATE void objc_compute_ivar_offsets(Class class) // Find the end of the last ivar - instance_size contains some padding // for alignment. - int real_end = ivar->offset + objc_sizeof_type(ivar->type); + int real_end = *ivar->offset + objc_sizeof_type(ivar->type); // Keep going if the new class starts at the end of the superclass if (start == real_end) { @@ -160,7 +150,7 @@ PRIVATE void objc_compute_ivar_offsets(Class class) class->ivars->ivar_list[0].name, start); fprintf(stderr, "Last instance variable in superclass, %s, ends at offset %d. ", - ivar->name, ivar->offset + + ivar->name, *ivar->offset + (int)objc_sizeof_type(ivar->type)); fprintf(stderr, "This probably means that you are subclassing a" "class from a library, which has changed in a binary-incompatible" @@ -170,44 +160,6 @@ PRIVATE void objc_compute_ivar_offsets(Class class) } -ivar_ownership ownershipForIvar(Class cls, int idx) -{ - if (objc_get_class_version(cls) < 2) - { - return ownership_unsafe; - } - if (objc_bitfield_test(cls->strong_pointers, idx)) - { - return ownership_strong; - } - if (objc_bitfield_test(cls->weak_pointers, idx)) - { - return ownership_weak; - } - return ownership_unsafe; -} - -static struct objc_ivar_list *upgradeIvarList(Class cls, struct objc_ivar_list_legacy *l) -{ - if (l == NULL) - { - return NULL; - } - struct objc_ivar_list *n = calloc(1, sizeof(struct objc_ivar_list) + - l->count*sizeof(struct objc_ivar)); - n->size = sizeof(struct objc_ivar); - n->count = l->count; - for (int i=0 ; icount ; i++) - { - n->ivar_list[i].name = l->ivar_list[i].name; - n->ivar_list[i].type = l->ivar_list[i].type; - n->ivar_list[i].offset = l->ivar_list[i].offset; - const char *type = l->ivar_list[i].type; - n->ivar_list[i].align = ((type == NULL) || type[0] == 0) ? __alignof__(void*) : objc_alignof_type(type); - ivarSetOwnership(&n->ivar_list[i], ownershipForIvar(cls, i)); - } - return n; -} //////////////////////////////////////////////////////////////////////////////// // Public API functions diff --git a/ivar.h b/ivar.h index 5ebcf20..4ab592e 100644 --- a/ivar.h +++ b/ivar.h @@ -19,15 +19,15 @@ struct objc_ivar * the ivars declared by this class. It is then set by the runtime to the * offset from the object pointer. */ - int offset; + int *offset; /** * Alignment of this ivar. */ - int align; + int32_t align; /** * Flags for this instance variable. */ - long flags; + int32_t flags; }; /** diff --git a/legacy.c b/legacy.c new file mode 100644 index 0000000..dcb5901 --- /dev/null +++ b/legacy.c @@ -0,0 +1,93 @@ +#include +#include +#include + +#include "objc/runtime.h" +#include "objc/encoding.h" +#include "ivar.h" +#include "class.h" +#include "loader.h" + +static ivar_ownership ownershipForIvar(struct legacy_gnustep_objc_class *cls, int idx) +{ + if (objc_get_class_version_legacy(cls) < 2) + { + return ownership_unsafe; + } + if (objc_bitfield_test(cls->strong_pointers, idx)) + { + return ownership_strong; + } + if (objc_bitfield_test(cls->weak_pointers, idx)) + { + return ownership_weak; + } + return ownership_unsafe; +} + +static struct objc_ivar_list *upgradeIvarList(struct legacy_gnustep_objc_class *cls) +{ + struct objc_ivar_list_legacy *l = cls->ivars; + if (l == NULL) + { + return NULL; + } + struct objc_ivar_list *n = calloc(1, sizeof(struct objc_ivar_list) + + l->count*sizeof(struct objc_ivar)); + n->size = sizeof(struct objc_ivar); + n->count = l->count; + for (int i=0 ; icount ; i++) + { + n->ivar_list[i].name = l->ivar_list[i].name; + n->ivar_list[i].type = l->ivar_list[i].type; + if (objc_test_class_flag_legacy(cls, objc_class_flag_new_abi)) + { + n->ivar_list[i].offset = cls->ivar_offsets[i]; + } + else + { + n->ivar_list[i].offset = &l->ivar_list[i].offset; + } + const char *type = l->ivar_list[i].type; + n->ivar_list[i].align = ((type == NULL) || type[0] == 0) ? __alignof__(void*) : objc_alignof_type(type); + ivarSetOwnership(&n->ivar_list[i], ownershipForIvar(cls, i)); + } + return n; +} + +static int legacy_key; + +PRIVATE struct legacy_gnustep_objc_class* objc_legacy_class_for_class(Class cls) +{ + return (struct legacy_gnustep_objc_class*)objc_getAssociatedObject((id)cls, &legacy_key); +} + +PRIVATE Class objc_upgrade_class(struct legacy_gnustep_objc_class *oldClass) +{ + Class cls = calloc(sizeof(struct objc_class), 1); + cls->isa = oldClass->isa; + cls->super_class = (struct objc_class*)oldClass->super_class; + cls->name = oldClass->name; + cls->version = oldClass->version; + cls->info = oldClass->info; + cls->instance_size = oldClass->instance_size; + cls->ivars = upgradeIvarList(oldClass); + cls->methods = oldClass->methods; + cls->protocols = oldClass->protocols; + cls->abi_version = oldClass->abi_version; + cls->properties = oldClass->properties; + objc_register_selectors_from_class(cls); + if (!objc_test_class_flag(cls, objc_class_flag_meta)) + { + cls->isa = objc_upgrade_class((struct legacy_gnustep_objc_class*)cls->isa); + objc_setAssociatedObject((id)cls, &legacy_key, (id)oldClass, OBJC_ASSOCIATION_ASSIGN); + } + return cls; +} + +PRIVATE struct objc_category *objc_upgrade_category(struct objc_category_legacy *old) +{ + struct objc_category *cat = calloc(1, sizeof(struct objc_category)); + memcpy(cat, old, sizeof(struct objc_category_legacy)); + return cat; +} diff --git a/legacy.h b/legacy.h new file mode 100644 index 0000000..a1ca574 --- /dev/null +++ b/legacy.h @@ -0,0 +1,11 @@ +#pragma once +#include "visibility.h" +#include "ivar.h" +#include "class.h" +#include "category.h" + +PRIVATE Class objc_upgrade_class(struct legacy_gnustep_objc_class *oldClass); +PRIVATE struct objc_category *objc_upgrade_category(struct objc_category_legacy *); + +PRIVATE struct legacy_gnustep_objc_class* objc_legacy_class_for_class(Class); + diff --git a/loader.c b/loader.c index d0a2f54..18ea860 100644 --- a/loader.c +++ b/loader.c @@ -6,6 +6,7 @@ #include "lock.h" #include "loader.h" #include "visibility.h" +#include "legacy.h" #ifdef ENABLE_GC #include #endif @@ -97,8 +98,8 @@ struct objc_init SEL sel_end; Class *cls_begin; Class *cls_end; - char **cls_ref_begin; - char **cls_ref_end; + Class *cls_ref_begin; + Class *cls_ref_end; struct objc_category *cat_begin; struct objc_category *cat_end; struct objc_protocol2 *proto_begin; @@ -153,16 +154,14 @@ void __objc_load(struct objc_init *init) } objc_load_class(*cls); } - for (char **cls = init->cls_ref_begin ; cls < init->cls_ref_end ; cls++) +#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 + // the `objc_class` struct without breaking the ABI (again) + for (Class *cls = init->cls_ref_begin ; cls < init->cls_ref_end ; cls++) { - id *out = (id*)cls; - if (*out == nil) - { - continue; - } - *out = objc_getClass(*cls); - assert(*out); } +#endif for (struct objc_category *cat = init->cat_begin ; cat < init->cat_end ; cat++) { @@ -216,13 +215,13 @@ void __objc_exec_class(struct objc_module_abi_8 *module) // Load the classes from this module for (unsigned short i=0 ; iclass_count ; i++) { - objc_load_class(symbols->definitions[defs++]); + objc_load_class(objc_upgrade_class(symbols->definitions[defs++])); } unsigned int category_start = defs; // Load the categories from this module for (unsigned short i=0 ; icategory_count; i++) { - objc_try_load_category(symbols->definitions[defs++]); + objc_try_load_category(objc_upgrade_category(symbols->definitions[defs++])); } // Load the static instances struct objc_static_instance_list **statics = (void*)symbols->definitions[defs]; diff --git a/runtime.c b/runtime.c index a3f03bd..ac153ae 100644 --- a/runtime.c +++ b/runtime.c @@ -162,9 +162,11 @@ BOOL class_addIvar(Class cls, const char *name, size_t size, uint8_t alignment, offset <<= alignment; } - ivar->offset = offset; + // FIXME: This is stupid, but it will work for testing. + ivar->offset = malloc(sizeof(int)); + *ivar->offset = offset; // Increase the instance size to make space for this. - cls->instance_size = ivar->offset + size; + cls->instance_size = *ivar->offset + size; return YES; } @@ -353,6 +355,7 @@ id class_createInstance(Class cls, size_t extraBytes) } if (Nil == cls) { return nil; } + assert(cls->instance_size >= sizeof(Class)); id obj = gc->allocate_class(cls, extraBytes); obj->isa = cls; checkARCAccessorsSlow(cls); @@ -542,7 +545,7 @@ const char * ivar_getName(Ivar ivar) ptrdiff_t ivar_getOffset(Ivar ivar) { CHECK_ARG(ivar); - return ivar->offset; + return *ivar->offset; } const char * ivar_getTypeEncoding(Ivar ivar) @@ -752,14 +755,7 @@ void *object_getIndexedIvars(id obj) if ((0 == size) && class_isMetaClass(classForObject(obj))) { Class cls = (Class)obj; - if (objc_test_class_flag(cls, objc_class_flag_new_abi)) - { - size = sizeof(struct objc_class); - } - else - { - size = sizeof(struct legacy_abi_objc_class); - } + size = sizeof(struct objc_class); } return ((char*)obj) + size; }