From e235ffbff12990945bc71f84290225fd18d8d736 Mon Sep 17 00:00:00 2001 From: David Chisnall Date: Tue, 26 Dec 2017 19:14:11 +0000 Subject: [PATCH 01/26] Add some more release notes. --- ANNOUNCE | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/ANNOUNCE b/ANNOUNCE index 6dfdef21..1c523009 100644 --- a/ANNOUNCE +++ b/ANNOUNCE @@ -43,6 +43,13 @@ Highlights of this release include: requirements, which should fix issues relating to using vector types in Objective-C objects. +- The option to build a separate libobjcxx has been removed. The runtime will + now depend on the C++ standard library implementation if no useable C++ + runtime is available. Note that C++ exception interworking does not work + because LLVM's libc++abi (shipped by Apple) does not provide GNU-compatible + hooks and so Objective-C++ exception support will be automatically disabled + on this platform. Any other platforms shipping libc++abi should consider + either GNU libsupc++ or libcxxrt as an alternative. You may obtain the code for this release from git and use the 1.x branch: From 14889c540fe7da84080aaa87342df40e0a7091e7 Mon Sep 17 00:00:00 2001 From: David Chisnall Date: Tue, 9 Jan 2018 12:12:43 +0000 Subject: [PATCH 02/26] Bump cmake minimum version. Fixes #51 --- CMake/CMakeLists.txt | 2 +- CMakeLists.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CMake/CMakeLists.txt b/CMake/CMakeLists.txt index 9cbdd09b..cdbc20f4 100644 --- a/CMake/CMakeLists.txt +++ b/CMake/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 2.8) +cmake_minimum_required(VERSION 3.1) add_executable(test_cxx_runtime typeinfo_test.cc) set(CMAKE_CXX_IMPLICIT_LINK_LIBRARIES "") diff --git a/CMakeLists.txt b/CMakeLists.txt index 30314620..8cd7736b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 2.8) +cmake_minimum_required(VERSION 3.1) project(libobjc) enable_language(ASM) From 2e45da40c407e2ca6703c2128ad8fa0b8b9c4612 Mon Sep 17 00:00:00 2001 From: Dustin Howett Date: Sat, 20 Jan 2018 16:00:26 -0800 Subject: [PATCH 03/26] fix flipped sense on ASSERT() --- visibility.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/visibility.h b/visibility.h index 86c379b0..d98d052d 100644 --- a/visibility.h +++ b/visibility.h @@ -17,7 +17,7 @@ # define ASSERT(x) assert(x) #else # define UNREACHABLE(x) __builtin_unreachable() -# define ASSERT(x) do { if (x) __builtin_unreachable(); } while(0) +# define ASSERT(x) do { if (!(x)) __builtin_unreachable(); } while(0) #endif #define LIKELY(x) __builtin_expect(x, 1) From a0eec52bb88f039b5f8a14603a1609caf3609691 Mon Sep 17 00:00:00 2001 From: "Dustin L. Howett" Date: Fri, 17 Feb 2017 14:42:02 -0800 Subject: [PATCH 04/26] fix a mismanagement of the hash table that could lead to data loss This commit fixes a data loss bug in our hopscotch table implementation. Removing values from the table can result in other values becoming disconnected and lost. Let A, B, and C be values that all hash to cell 0. Assume the hopscotch distance factor H = 2. 0 1 2 +-----+-----+-----+ | | | | +-----+-----+-----+ After adding A 0 1 2 +-----+-----+-----+ | A | | | +-----+-----+-----+ | +-Neighbors = After adding B 0 1 2 +-----+-----+-----+ | A | B | | +-----+-----+-----+ | +-Neighbors = 1 After adding C 0 1 2 +-----+-----+-----+ | A | B | C | +-----+-----+-----+ | +-Neighbors = 1, 2 If we then remove B, 0 1 2 +-----+-----+-----+ | A | [X] | C | +-----+-----+-----+ | +-Neighbors = 1, 2 * It is replaced with a placeholder [X]. * A's neighbor table is not updated to reflect the loss. If we then remove A, 0 1 2 +-----+-----+-----+ | [X] | [X] | [C] | +-----+-----+-----+ | +-Neighbors = 2 * The table is rebalanced to promote A's lowest neighbor to the primary cell position. * C from cell 2 remains cell 0's neighbor. The bug manifests if [X] the placeholder value passes the null check set out in MAP_TABLE_VALUE_NULL; that is, the placeholder is "effectively null". Looking up the key that matches C will first evaluate its base cell, the one that collided with the key in the first place. Since that is placeholder [X], and [X] is "effectively null", the lookup stops. C is never retrieved from the hash table. --- The expedient solution to this bug is to update cell 0's neighbors when B is first removed, effectively skipping the hole: If we remove B as above, 0 1 2 +-----+-----+-----+ | A | [X] | C | +-----+-----+-----+ | +-Neighbors = 2 <<< HERE but clear the neighbor bit for cell 1, the promotion that happens when A is later removed places C in cell 0. 0 1 2 +-----+-----+-----+ | C | [X] | [X] | +-----+-----+-----+ | +-Neighbors = --- hash_table.h | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/hash_table.h b/hash_table.h index aaae7728..efa5cf63 100644 --- a/hash_table.h +++ b/hash_table.h @@ -392,6 +392,22 @@ static void PREFIX(_remove)(PREFIX(_table) *table, void *key) MAP_LOCK(); PREFIX(_table_cell) cell = PREFIX(_table_get_cell)(table, key); if (NULL == cell) { return; } + + uint32_t hash = MAP_TABLE_HASH_KEY(key); + PREFIX(_table_cell) baseCell = PREFIX(_table_lookup)(table, hash); + if (baseCell && baseCell <= cell && cell - baseCell <= 32) + { + uint32_t jump = 1 << (cell - baseCell - 1); + if ((baseCell->secondMaps & jump)) + { + // If we are removing a cell stored adjacent to its base due to hash + // collision, we have to clear the base cell's neighbor bit. + // Otherwise, a later remove can move the new placeholder value to the head + // which will cause further chained lookups to fail. + baseCell->secondMaps &= ~jump; + } + } + // If the cell contains a value, set it to the placeholder and shuffle up // everything if (0 == cell->secondMaps) From 7d5a0ff85e71a6f2fe64e3b45eda27fe5dc4ddc6 Mon Sep 17 00:00:00 2001 From: "Dustin L. Howett" Date: Tue, 7 Jun 2016 17:14:17 -0700 Subject: [PATCH 05/26] fix objc_resolve_class_links to actually rescan the unresolved list It looks like this was the initial intent of 4ea82e1, but as implemented it would still only scan the unresolved class list once. Since unresolved_class_list represents the head and classes are pushed onto the head, any classes added to the resolution list after resolution started would be skipped. --- class_table.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/class_table.c b/class_table.c index fa9a876c..ec81255e 100644 --- a/class_table.c +++ b/class_table.c @@ -258,10 +258,10 @@ PRIVATE BOOL objc_resolve_class(Class cls) PRIVATE void objc_resolve_class_links(void) { LOCK_RUNTIME_FOR_SCOPE(); - Class class = unresolved_class_list; BOOL resolvedClass; do { + Class class = unresolved_class_list; resolvedClass = NO; while ((Nil != class)) { From a421d628212c3d74db670a7712e617a66861b9db Mon Sep 17 00:00:00 2001 From: "Dustin L. Howett" Date: Tue, 5 Apr 2016 17:26:21 -0700 Subject: [PATCH 06/26] teach class_setSuperclass about metaclasses, subclass lists, and dtables --- dtable.c | 4 ++-- runtime.c | 38 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 40 insertions(+), 2 deletions(-) diff --git a/dtable.c b/dtable.c index 4aa63e02..5f9611a8 100644 --- a/dtable.c +++ b/dtable.c @@ -189,7 +189,7 @@ PRIVATE void init_dispatch_tables () Class class_getSuperclass(Class); -static dtable_t create_dtable_for_class(Class class, dtable_t root_dtable) +PRIVATE dtable_t create_dtable_for_class(Class class, dtable_t root_dtable) { // Don't create a dtable for a class that already has one if (classHasDtable(class)) { return dtable_for_class(class); } @@ -639,7 +639,7 @@ PRIVATE void add_method_list_to_class(Class cls, checkARCAccessors(cls); } -static dtable_t create_dtable_for_class(Class class, dtable_t root_dtable) +PRIVATE dtable_t create_dtable_for_class(Class class, dtable_t root_dtable) { // Don't create a dtable for a class that already has one if (classHasDtable(class)) { return dtable_for_class(class); } diff --git a/runtime.c b/runtime.c index c7e7c62c..2995c612 100644 --- a/runtime.c +++ b/runtime.c @@ -22,6 +22,8 @@ struct objc_slot *objc_get_slot(Class cls, SEL selector); #define CHECK_ARG(arg) if (0 == arg) { return 0; } +static inline void safe_remove_from_subclass_list(Class cls); + /** * Calls C++ destructors in the correct order. */ @@ -519,8 +521,44 @@ Class class_setSuperclass(Class cls, Class newSuper) CHECK_ARG(cls); CHECK_ARG(newSuper); if (Nil == cls) { return Nil; } + + LOCK_RUNTIME_FOR_SCOPE(); + + safe_remove_from_subclass_list(cls); + Class oldSuper = cls->super_class; cls->super_class = newSuper; + + // The super class's subclass list is used in certain method resolution scenarios. + cls->sibling_class = cls->super_class->subclass_list; + cls->super_class->subclass_list = cls; + + if (!class_isMetaClass(cls)) + { + // Update the metaclass's superclass. + class_setSuperclass(cls->isa, newSuper->isa); + } + else + { + // newSuper is presumably a metaclass. Its isa will therefore be the appropriate root metaclass. + cls->isa = newSuper->isa; + } + + // Make sure the superclass is initialized if we're initialized. + if (objc_test_class_flag(cls, objc_class_flag_initialized)) + { + objc_send_initialize(newSuper); + // Update the class's dtable to reflect its new superclass's dtable. + if (cls->dtable != uninstalled_dtable) + { + // we can't use objc_update_dtable_for_class here, as it doesn't take into account + // superclasses. It only walks downward. + free_dtable(cls->dtable); + cls->dtable = uninstalled_dtable; + cls->dtable = create_dtable_for_class(cls, uninstalled_dtable); + } + } + return oldSuper; } From 21ad183e3e0dbe55882f5fb0ea716e8f35742578 Mon Sep 17 00:00:00 2001 From: "Dustin L. Howett" Date: Mon, 11 Jan 2016 17:43:25 -0800 Subject: [PATCH 07/26] Wrap some headers in extern C for C++ compat. --- objc/encoding.h | 8 ++++++++ objc/hooks.h | 8 +++++++- objc/objc-arc.h | 10 ++++++++++ 3 files changed, 25 insertions(+), 1 deletion(-) diff --git a/objc/encoding.h b/objc/encoding.h index 4c4ff53b..ceccd596 100644 --- a/objc/encoding.h +++ b/objc/encoding.h @@ -5,6 +5,10 @@ #ifndef __LIBOBJC_ENCODING_H_INCLUDED__ #define __LIBOBJC_ENCODING_H_INCLUDED__ +#ifdef __cplusplus +extern "C" { +#endif + const char *objc_skip_type_qualifiers (const char *type); const char *objc_skip_typespec(const char *type); @@ -71,4 +75,8 @@ void objc_layout_structure_get_info (struct objc_struct_layout *layout, #define _F_ONEWAY 0x10 #define _F_GCINVISIBLE 0x20 +#ifdef __cplusplus +} +#endif + #endif // __LIBOBJC_ENCODING_H_INCLUDED__ diff --git a/objc/hooks.h b/objc/hooks.h index 42c67edd..8b7e045c 100644 --- a/objc/hooks.h +++ b/objc/hooks.h @@ -2,6 +2,10 @@ #pragma clang system_header #endif +#ifdef __cplusplus +extern "C" { +#endif + /** * This file includes all of the hooks that can be used to alter the behaviour * of the runtime. @@ -102,4 +106,6 @@ typedef IMP (*objc_tracing_hook)(id, SEL, IMP, int, void*); */ int objc_registerTracingHook(SEL, objc_tracing_hook); - +#ifdef __cplusplus +} +#endif diff --git a/objc/objc-arc.h b/objc/objc-arc.h index 32ecb7a9..14f7d000 100644 --- a/objc/objc-arc.h +++ b/objc/objc-arc.h @@ -4,6 +4,11 @@ #ifndef __OBJC_ARC_INCLUDED__ #define __OBJC_ARC_INCLUDED__ + +#ifdef __cplusplus +extern "C" { +#endif + /** * Autoreleases the argument. Equivalent to [obj autorelease]. */ @@ -138,5 +143,10 @@ unsigned long objc_arc_autorelease_count_np(void); * this thread. */ unsigned long objc_arc_autorelease_count_for_object_np(id); + +#ifdef __cplusplus +} +#endif + #endif // __OBJC_ARC_INCLUDED__ From e3b069cedcae09eb19d28d34f6cee97c0c4943bc Mon Sep 17 00:00:00 2001 From: "Dustin L. Howett" Date: Mon, 4 Jan 2016 10:25:03 -0800 Subject: [PATCH 08/26] Use the old association policy to determine whether to release an object This commit has been augmented to address the comments in https://github.com/Microsoft/libobjc2/commit/df7f9061670cce994730d1720150adc --- associate.m | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/associate.m b/associate.m index 37b9c269..d3ab8a9a 100644 --- a/associate.m +++ b/associate.m @@ -166,12 +166,17 @@ static void setReference(struct reference_list *list, lock = lock_for_pointer(r); lock_spinlock(lock); } - r->policy = policy; - id old = r->object; - r->object = obj; - if (OBJC_ASSOCIATION_ASSIGN != r->policy) + @try { - objc_release(old); + if (OBJC_ASSOCIATION_ASSIGN != r->policy) + { + objc_release(r->object); + } + } + @finally + { + r->policy = policy; + r->object = obj; } if (needLock) { From 45f6572379deab4d8766bcedf922cfb606247f11 Mon Sep 17 00:00:00 2001 From: Dustin Howett Date: Thu, 22 Feb 2018 17:51:54 -0800 Subject: [PATCH 09/26] Add a test for a0eec52 --- Test/CMakeLists.txt | 1 + Test/hash_table_delete.c | 64 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 65 insertions(+) create mode 100644 Test/hash_table_delete.c diff --git a/Test/CMakeLists.txt b/Test/CMakeLists.txt index 7db5183a..97b66b92 100644 --- a/Test/CMakeLists.txt +++ b/Test/CMakeLists.txt @@ -33,6 +33,7 @@ set(TESTS MethodArguments.m zeroSizedIVar.m exchange.m + hash_table_delete.c ) # Function for adding a test. This takes the name of the test and the list of diff --git a/Test/hash_table_delete.c b/Test/hash_table_delete.c new file mode 100644 index 00000000..9b924618 --- /dev/null +++ b/Test/hash_table_delete.c @@ -0,0 +1,64 @@ +#include +#include + +struct test_struct { + uint32_t key; +}; + +struct test_struct null_placeholder = {0}; + +static int test_compare(uint32_t value, const struct test_struct test) { + return value == test.key; +} + +// force hash collisions +static uint32_t test_key_hash(const void *ptr) { + return ((uint32_t)ptr)>>2; +} + +static uint32_t test_value_hash(const struct test_struct test) { + return test.key>>2; +} + +static int test_is_null(const struct test_struct test) { + return test.key == 0; +} + +#define MAP_TABLE_NAME test +#define MAP_TABLE_COMPARE_FUNCTION test_compare +#define MAP_TABLE_VALUE_TYPE struct test_struct +#define MAP_TABLE_VALUE_NULL test_is_null +#define MAP_TABLE_HASH_KEY test_key_hash +#define MAP_TABLE_HASH_VALUE test_value_hash +#define MAP_TABLE_VALUE_PLACEHOLDER null_placeholder +#define MAP_TABLE_ACCESS_BY_REFERENCE 1 +#define MAP_TABLE_SINGLE_THREAD 1 +#define MAP_TABLE_NO_LOCK 1 + +#include "../hash_table.h" + +int main(int argc, char *argv[]) +{ + test_table *testTable; + test_initialize(&testTable, 128); + + struct test_struct one, two, three; + one.key = 1; + two.key = 2; + three.key = 3; + + test_insert(testTable, one); + test_insert(testTable, two); + test_insert(testTable, three); + + test_remove(testTable, 2); + test_remove(testTable, 1); + + struct test_struct *pthree = test_table_get(testTable, 3); + if (!pthree) { + fprintf(stderr, "failed to find value (key=3) inserted into hash table\n"); + return 1; + } + + return 0; +} From 7539d7e042411244194bde76d48e32de79662fc0 Mon Sep 17 00:00:00 2001 From: David Chisnall Date: Fri, 23 Feb 2018 09:22:57 +0000 Subject: [PATCH 10/26] Fix warnings in test. --- Test/hash_table_delete.c | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Test/hash_table_delete.c b/Test/hash_table_delete.c index 9b924618..ad7af896 100644 --- a/Test/hash_table_delete.c +++ b/Test/hash_table_delete.c @@ -2,18 +2,18 @@ #include struct test_struct { - uint32_t key; + uintptr_t key; }; struct test_struct null_placeholder = {0}; -static int test_compare(uint32_t value, const struct test_struct test) { - return value == test.key; +static int test_compare(const void *key, const struct test_struct test) { + return (uintptr_t)key == test.key; } // force hash collisions static uint32_t test_key_hash(const void *ptr) { - return ((uint32_t)ptr)>>2; + return ((uint32_t)(uintptr_t)ptr)>>2; } static uint32_t test_value_hash(const struct test_struct test) { @@ -51,10 +51,10 @@ int main(int argc, char *argv[]) test_insert(testTable, two); test_insert(testTable, three); - test_remove(testTable, 2); - test_remove(testTable, 1); + test_remove(testTable, (void*)2); + test_remove(testTable, (void*)1); - struct test_struct *pthree = test_table_get(testTable, 3); + struct test_struct *pthree = test_table_get(testTable, (void*)3); if (!pthree) { fprintf(stderr, "failed to find value (key=3) inserted into hash table\n"); return 1; From 3c036b3f4f830cebc53b4e5c4bef47f2fb1aae39 Mon Sep 17 00:00:00 2001 From: David Chisnall Date: Fri, 23 Feb 2018 09:23:05 +0000 Subject: [PATCH 11/26] Use nullptr for nil in C++11. --- objc/runtime.h | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/objc/runtime.h b/objc/runtime.h index 3092f72e..12b49d59 100644 --- a/objc/runtime.h +++ b/objc/runtime.h @@ -198,7 +198,11 @@ typedef struct #ifdef __GNUC # define _OBJC_NULL_PTR __null #elif defined(__cplusplus) -# define _OBJC_NULL_PTR 0 +# if __has_feature(cxx_nullptr) +# define _OBJC_NULL_PTR nullptr +# else +# define _OBJC_NULL_PTR 0 +# endif #else # define _OBJC_NULL_PTR ((void*)0) #endif From 81f7b16af5f9e84d22d85e7a947a40594fd2c785 Mon Sep 17 00:00:00 2001 From: Dustin Howett Date: Wed, 28 Feb 2018 17:14:19 -0800 Subject: [PATCH 12/26] Add a test for e3b069c (association policy) --- Test/AssociatedObject.m | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/Test/AssociatedObject.m b/Test/AssociatedObject.m index cd9464ea..e41da6f7 100644 --- a/Test/AssociatedObject.m +++ b/Test/AssociatedObject.m @@ -19,8 +19,21 @@ int main(void) @autoreleasepool { Associated *object = [Associated new]; Test *holder = [[Test new] autorelease]; - objc_setAssociatedObject(object, &objc_setAssociatedObjectKey, holder, OBJC_ASSOCIATION_RETAIN); + objc_setAssociatedObject(holder, &objc_setAssociatedObjectKey, object, OBJC_ASSOCIATION_RETAIN); [object release]; - } + assert(!deallocCalled); + } + // dealloc should be called when holder is released during pool drain + assert(deallocCalled); + + deallocCalled = NO; + + Associated *object = [Associated new]; + Test *holder = [Test new]; + objc_setAssociatedObject(holder, &objc_setAssociatedObjectKey, object, OBJC_ASSOCIATION_RETAIN); + [object release]; // commuted into associated object storage + objc_setAssociatedObject(holder, &objc_setAssociatedObjectKey, nil, OBJC_ASSOCIATION_ASSIGN); + [holder release]; + assert(deallocCalled); } From 430ab750144256c2b9d4caef991e981102610402 Mon Sep 17 00:00:00 2001 From: "Dustin L. Howett" Date: Mon, 4 Jan 2016 10:20:50 -0800 Subject: [PATCH 13/26] win32: use native threading APIs instead of pthreads * use fiber local storage if NO_PTHREADS is defined * use critical sections instead of mutexes Contributing-author: Ben Viglietta --- arc.m | 57 ++++++++++++++++++++++++++++++++++++++++++--------- lock.h | 16 +++++++-------- safewindows.h | 20 ++++++++++++++++++ spinlock.h | 4 ++-- 4 files changed, 76 insertions(+), 21 deletions(-) create mode 100644 safewindows.h diff --git a/arc.m b/arc.m index e18872b3..0540391a 100644 --- a/arc.m +++ b/arc.m @@ -12,9 +12,44 @@ #import "objc/objc-arc.h" #import "objc/blocks_runtime.h" -#ifndef NO_PTHREADS -#include -pthread_key_t ARCThreadKey; +#if defined(_WIN32) +// We're using the Fiber-Local Storage APIs on Windows +// because the TLS APIs won't pass app certification. +// Additionally, the FLS API surface is 1:1 mapped to +// the TLS API surface when fibers are not in use. +# include "safewindows.h" +# define arc_tls_store FlsSetValue +# define arc_tls_load FlsGetValue +# define TLS_CALLBACK(name) void WINAPI name + +typedef DWORD arc_tls_key_t; +typedef void WINAPI(*arc_cleanup_function_t)(void*); +static inline arc_tls_key_t arc_tls_key_create(arc_cleanup_function_t cleanupFunction) +{ + return FlsAlloc(cleanupFunction); +} + +#else // if defined(_WIN32) + +# ifndef NO_PTHREADS +# include +# define arc_tls_store pthread_setspecific +# define arc_tls_load pthread_getspecific +# define TLS_CALLBACK(name) void name + +typedef pthread_key_t arc_tls_key_t; +typedef void (*arc_cleanup_function_t)(void*); +static inline arc_tls_key_t arc_tls_key_create(arc_cleanup_function_t cleanupFunction) +{ + pthread_key_t key; + pthread_key_create(&key, cleanupFunction); + return key; +} +# endif +#endif + +#ifdef arc_tls_store +arc_tls_key_t ARCThreadKey; #endif extern void _NSConcreteMallocBlock; @@ -60,14 +95,14 @@ - (void)release; static inline struct arc_tls* getARCThreadData(void) { -#ifdef NO_PTHREADS +#ifndef arc_tls_store return NULL; -#else - struct arc_tls *tls = pthread_getspecific(ARCThreadKey); +#else // !defined arc_tls_store + struct arc_tls *tls = arc_tls_load(ARCThreadKey); if (NULL == tls) { tls = calloc(sizeof(struct arc_tls), 1); - pthread_setspecific(ARCThreadKey, tls); + arc_tls_store(ARCThreadKey, tls); } return tls; #endif @@ -133,7 +168,8 @@ static void emptyPool(struct arc_tls *tls, id *stop) //fprintf(stderr, "New insert: %p. Stop: %p\n", tls->pool->insert, stop); } -static void cleanupPools(struct arc_tls* tls) +#ifdef arc_tls_store +static TLS_CALLBACK(cleanupPools)(struct arc_tls* tls) { if (tls->returnRetained) { @@ -151,6 +187,7 @@ static void cleanupPools(struct arc_tls* tls) } free(tls); } +#endif static Class AutoreleasePool; @@ -596,8 +633,8 @@ PRIVATE void init_arc(void) { weak_ref_initialize(&weakRefs, 128); INIT_LOCK(weakRefLock); -#ifndef NO_PTHREADS - pthread_key_create(&ARCThreadKey, (void(*)(void*))cleanupPools); +#ifdef arc_tls_store + ARCThreadKey = arc_tls_key_create((arc_cleanup_function_t)cleanupPools); #endif } diff --git a/lock.h b/lock.h index d39f07b3..2f3cd119 100644 --- a/lock.h +++ b/lock.h @@ -6,15 +6,13 @@ #ifndef __LIBOBJC_LOCK_H_INCLUDED__ #define __LIBOBJC_LOCK_H_INCLUDED__ -#ifdef WIN32 -#define BOOL _WINBOOL -# include -#undef BOOL -typedef HANDLE mutex_t; -# define INIT_LOCK(x) x = CreateMutex(NULL, FALSE, NULL) -# define LOCK(x) WaitForSingleObject(*x, INFINITE) -# define UNLOCK(x) ReleaseMutex(*x) -# define DESTROY_LOCK(x) CloseHandle(*x) +#ifdef _WIN32 +# include "safewindows.h" +typedef CRITICAL_SECTION mutex_t; +# define INIT_LOCK(x) InitializeCriticalSection(&(x)) +# define LOCK(x) EnterCriticalSection(x) +# define UNLOCK(x) LeaveCriticalSection(x) +# define DESTROY_LOCK(x) DeleteCriticalSection(x) #else # include diff --git a/safewindows.h b/safewindows.h new file mode 100644 index 00000000..8a0850d2 --- /dev/null +++ b/safewindows.h @@ -0,0 +1,20 @@ +#ifndef __LIBOBJC_SAFEWINDOWS_H_INCLUDED__ +#define __LIBOBJC_SAFEWINDOWS_H_INCLUDED__ + +#pragma push_macro("BOOL") + +#ifdef BOOL +#undef BOOL +#endif +#define BOOL _WINBOOL + +#include + +// Windows.h defines interface -> struct +#ifdef interface +#undef interface +#endif + +#pragma pop_macro("BOOL") + +#endif // __LIBOBJC_SAFEWINDOWS_H_INCLUDED__ diff --git a/spinlock.h b/spinlock.h index e65e6081..2efec475 100644 --- a/spinlock.h +++ b/spinlock.h @@ -1,5 +1,5 @@ -#ifdef __MINGW32__ -#include +#ifdef _WIN32 +#include "safewindows.h" static unsigned sleep(unsigned seconds) { Sleep(seconds*1000); From 6af4fcb219e839d01781083668764ddd11d5efc8 Mon Sep 17 00:00:00 2001 From: Dustin Howett Date: Thu, 1 Mar 2018 15:57:02 -0800 Subject: [PATCH 14/26] Fix eh_personality for NO_PTHREADS --- eh_personality.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/eh_personality.c b/eh_personality.c index 90afbbb3..4c551e29 100644 --- a/eh_personality.c +++ b/eh_personality.c @@ -562,7 +562,7 @@ struct thread_data *get_thread_data(void) } return td; #else - return &td; + return &thread_data; #endif } @@ -572,7 +572,7 @@ struct thread_data *get_thread_data_fast(void) struct thread_data *td = pthread_getspecific(key); return td; #else - return &td; + return &thread_data; #endif } From 9459f53ffa8eea5692d31152a71661571e8a4b96 Mon Sep 17 00:00:00 2001 From: Dustin Howett Date: Fri, 2 Mar 2018 17:49:51 -0800 Subject: [PATCH 15/26] Add a class inheritance helper --- class.h | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/class.h b/class.h index 8da94eb2..e90db48d 100644 --- a/class.h +++ b/class.h @@ -303,4 +303,15 @@ static inline Class classForObject(id obj) return obj->isa; } +static inline BOOL classIsOrInherits(Class cls, Class base) +{ + for (Class c = cls ; + Nil != c ; + c = c->super_class) + { + if (c == base) { return YES; } + } + return NO; +} + #endif //__OBJC_CLASS_H_INCLUDED From 82d4d3fbfdbf0a3f4ed29bb5d1d386bd22b9153f Mon Sep 17 00:00:00 2001 From: Dustin Howett Date: Fri, 2 Mar 2018 17:51:26 -0800 Subject: [PATCH 16/26] merge dtables inplace, don't hold rt lock while initializing --- dtable.c | 68 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ dtable.h | 5 ++++ runtime.c | 54 +++++++++++++++++++++++++++---------------- 3 files changed, 107 insertions(+), 20 deletions(-) diff --git a/dtable.c b/dtable.c index 5f9611a8..6f5f8a9f 100644 --- a/dtable.c +++ b/dtable.c @@ -360,6 +360,11 @@ PRIVATE void objc_update_dtable_for_class(Class cls) update_dtable(dtable); } +PRIVATE void objc_update_dtable_for_new_superclass(Class cls, Class newSuper) +{ + // This is not required. objc_dtable_lookup looks directly at the class's + // superclass for each lookup, and the cache only stores local slots. +} PRIVATE void add_method_list_to_class(Class cls, struct objc_method_list *list) { @@ -622,6 +627,69 @@ PRIVATE void objc_update_dtable_for_class(Class cls) checkARCAccessors(cls); } +static void rebaseDtableRecursive(Class cls, Class owner, Class newSuper) +{ + dtable_t parentDtable = dtable_for_class(newSuper); + dtable_t dtable = dtable_for_class(cls); + uint32_t idx = 0; + struct objc_slot *slot; + while ((slot = SparseArrayNext(parentDtable, &idx))) + { + struct objc_slot *existingSlot = SparseArrayLookup(dtable, idx); + if (NULL != existingSlot) + { + if (slot->method == existingSlot->method + || slot->owner == existingSlot->owner + || classIsOrInherits(existingSlot->owner, owner)) + { + // IMPs should not be changing right now; disregard anything + // that's already owned by the new parent. + // Slots from base or lower are safe from replacement. + continue; + } + // version doesn't need to be updated since we're replacing slots inherited + // directly from the old parent; this does not invalidate them. + } + // propagate changes downward + SparseArrayInsert(dtable, idx, slot); + } + + idx = 0; + while ((slot = SparseArrayNext(dtable, &idx))) + { + void *exist = SparseArrayLookup(parentDtable, idx); + // Everything that exists in the parent dtable was merged above, + // and everything implemented at base or below is safe. + if (exist || classIsOrInherits(slot->owner, owner)) { continue; } + + SparseArrayInsert(dtable, idx, 0); + } + + // merge can make a class ARC-compatible. + checkARCAccessors(cls); + + for (struct objc_class *subclass=cls->subclass_list ; + Nil != subclass ; subclass = subclass->sibling_class) + { + // Don't bother updating dtables for subclasses that haven't been + // initialized yet + if (!classHasDtable(subclass)) { continue; } + rebaseDtableRecursive(subclass, owner, newSuper); + } + +} + +PRIVATE void objc_update_dtable_for_new_superclass(Class cls, Class newSuper) +{ + // Only update real dtables + if (!classHasDtable(cls)) { return; } + + LOCK_RUNTIME_FOR_SCOPE(); + rebaseDtableRecursive(cls, cls, newSuper); + + return; +} + PRIVATE void add_method_list_to_class(Class cls, struct objc_method_list *list) { diff --git a/dtable.h b/dtable.h index c3748929..d841dbcf 100644 --- a/dtable.h +++ b/dtable.h @@ -115,6 +115,11 @@ static inline int classHasDtable(struct objc_class *cls) * modifying a class's method list. */ void objc_update_dtable_for_class(Class); +/** + * Updates the dtable for a class and its subclasses. Must be called after + * changing and initializing a class's superclass. + */ +void objc_update_dtable_for_new_superclass(Class, Class); /** * Adds a single method list to a class. This is used when loading categories, * and is faster than completely rebuilding the dtable. diff --git a/runtime.c b/runtime.c index 2995c612..5ba686bf 100644 --- a/runtime.c +++ b/runtime.c @@ -23,6 +23,8 @@ struct objc_slot *objc_get_slot(Class cls, SEL selector); #define CHECK_ARG(arg) if (0 == arg) { return 0; } static inline void safe_remove_from_subclass_list(Class cls); +PRIVATE void objc_resolve_class(Class); +void objc_send_initialize(id object); /** * Calls C++ destructors in the correct order. @@ -522,9 +524,12 @@ Class class_setSuperclass(Class cls, Class newSuper) CHECK_ARG(newSuper); if (Nil == cls) { return Nil; } - LOCK_RUNTIME_FOR_SCOPE(); + LOCK_RUNTIME(); + + if (cls->super_class == newSuper) { return newSuper; } safe_remove_from_subclass_list(cls); + objc_resolve_class(newSuper); Class oldSuper = cls->super_class; cls->super_class = newSuper; @@ -533,32 +538,43 @@ Class class_setSuperclass(Class cls, Class newSuper) cls->sibling_class = cls->super_class->subclass_list; cls->super_class->subclass_list = cls; - if (!class_isMetaClass(cls)) + if (UNLIKELY(class_isMetaClass(cls))) { - // Update the metaclass's superclass. - class_setSuperclass(cls->isa, newSuper->isa); + // newSuper is presumably a metaclass. Its isa will therefore be the appropriate root metaclass. + cls->isa = newSuper->isa; } else { - // newSuper is presumably a metaclass. Its isa will therefore be the appropriate root metaclass. - cls->isa = newSuper->isa; + Class meta = cls->isa, newSuperMeta = newSuper->isa; + // Update the metaclass's superclass. + safe_remove_from_subclass_list(meta); + objc_resolve_class(newSuperMeta); + + meta->super_class = newSuperMeta; + meta->isa = newSuperMeta->isa; + + // The super class's subclass list is used in certain method resolution scenarios. + meta->sibling_class = newSuperMeta->subclass_list; + newSuperMeta->subclass_list = meta; } - // Make sure the superclass is initialized if we're initialized. - if (objc_test_class_flag(cls, objc_class_flag_initialized)) + LOCK(&initialize_lock); + if (!objc_test_class_flag(cls, objc_class_flag_initialized)) { - objc_send_initialize(newSuper); - // Update the class's dtable to reflect its new superclass's dtable. - if (cls->dtable != uninstalled_dtable) - { - // we can't use objc_update_dtable_for_class here, as it doesn't take into account - // superclasses. It only walks downward. - free_dtable(cls->dtable); - cls->dtable = uninstalled_dtable; - cls->dtable = create_dtable_for_class(cls, uninstalled_dtable); - } + // Uninitialized classes don't have dtables to update + // and don't need their superclasses initialized. + UNLOCK(&initialize_lock); + UNLOCK_RUNTIME(); + return oldSuper; } + UNLOCK(&initialize_lock); + UNLOCK_RUNTIME(); + + objc_send_initialize(newSuper); // also initializes the metaclass + objc_update_dtable_for_new_superclass(cls->isa, newSuper->isa); + objc_update_dtable_for_new_superclass(cls, newSuper); + return oldSuper; } @@ -828,8 +844,6 @@ const char *object_getClassName(id obj) return class_getName(object_getClass(obj)); } -PRIVATE void objc_resolve_class(Class); - void objc_registerClassPair(Class cls) { LOCK_RUNTIME_FOR_SCOPE(); From 14cb89350900af2236ce8ff20907d5a5b9e897d2 Mon Sep 17 00:00:00 2001 From: Dustin Howett Date: Fri, 2 Mar 2018 16:48:31 -0800 Subject: [PATCH 17/26] Add a test for class_setSuperclass --- Test/CMakeLists.txt | 1 + Test/setSuperclass.m | 293 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 294 insertions(+) create mode 100644 Test/setSuperclass.m diff --git a/Test/CMakeLists.txt b/Test/CMakeLists.txt index 7db5183a..a1332d74 100644 --- a/Test/CMakeLists.txt +++ b/Test/CMakeLists.txt @@ -33,6 +33,7 @@ set(TESTS MethodArguments.m zeroSizedIVar.m exchange.m + setSuperclass.m ) # Function for adding a test. This takes the name of the test and the list of diff --git a/Test/setSuperclass.m b/Test/setSuperclass.m new file mode 100644 index 00000000..236eff35 --- /dev/null +++ b/Test/setSuperclass.m @@ -0,0 +1,293 @@ +#include "../objc/runtime.h" +#include +#include + +#pragma clang diagnostic ignored "-Wdeprecated-declarations" + +__attribute__((objc_root_class)) +@interface Root @end +@interface DefaultSuperclass: Root @end + +// test: new superclass when not initialized at the time of class_setSuperclass +@interface NotInitializedSuperclass1: Root @end +@interface Subclass1: DefaultSuperclass @end + +// test: new superclass when already initialized at the time of class_setSuperclass +@interface NotInitializedSuperclass2: Root @end +@interface Subclass2: DefaultSuperclass @end +@interface Subclass2Subclass: Subclass2 @end + +@interface ChangesDuringInitialize: DefaultSuperclass @end + +// test: class gets reparented under its parent's parent. +@interface RemovedFromHierarchy: DefaultSuperclass @end +@interface MovesUpwardsInHierarchy: RemovedFromHierarchy @end + +// test: one class initializes anotherwhile initializing during class_setSuperclass +@interface OtherInitializedClass: Root @end +@interface InitializesOneClassWhileBeingInitialized: NotInitializedSuperclass2 @end +@interface Subclass3: DefaultSuperclass @end + +@implementation Root ++ (Class)class { return self; } ++ (BOOL)respondsToSelector:(SEL)selector { + return class_respondsToSelector(object_getClass(self), selector); +} ++ (BOOL)instancesRespondToSelector:(SEL)selector { + return class_respondsToSelector(self, selector); +} +@end + +@implementation NotInitializedSuperclass1 +static BOOL _notInitializedSuperclass1Initialized = NO; ++ (void)initialize { + _notInitializedSuperclass1Initialized = YES; +} ++ (void)existsOnNotInitializedSuperclassMeta { }; ++ (int)sameNameMeta { return 12; } ++ (int)overriddenMeta { return 12; } + +- (BOOL)existsOnNotInitializedSuperclass { return YES; } +- (int)sameName { return 2; } +- (int)overridden { return 2; } +@end + +@implementation NotInitializedSuperclass2 +static BOOL _notInitializedSuperclass2Initialized = NO; ++ (void)initialize { + _notInitializedSuperclass2Initialized = YES; +} ++ (void)existsOnNotInitializedSuperclassMeta { }; ++ (int)sameNameMeta { return 13; } ++ (int)overriddenMeta { return 13; } +- (BOOL)existsOnNotInitializedSuperclass { return YES; } +- (int)sameName { return 3; } +- (int)overridden { return 3; } +@end + +@implementation DefaultSuperclass +static BOOL _alreadyInitializedSuperclassInitialized = NO; ++ (void)initialize { + _alreadyInitializedSuperclassInitialized = YES; +} ++ (void)existsOnDefaultSuperclassMeta { }; ++ (int)sameNameMeta { return 14; } ++ (int)overriddenMeta { return 14; } +- (BOOL)existsOnDefaultSuperclass { return YES; } +- (int)sameName { return 4; } +- (int)overridden { return 4; } +@end + +@implementation Subclass1 +static BOOL _subclass1Initialized = NO; ++ (void)initialize { + _subclass1Initialized = YES; +} ++ (int)overriddenMeta { return 15; } // shadows 14 +- (BOOL)existsOnSubclass1 { return YES; } +- (int)overridden { return 5; } // shadows 4 +@end + +@implementation Subclass2 +static BOOL _subclass2Initialized = NO; ++ (void)initialize { + _subclass2Initialized = YES; +} ++ (int)overriddenMeta { return 16; } // shadows 14 +- (BOOL)existsOnSubclass2 { return YES; } +- (int)overridden { return 6; } // shadows 4 +- (int)intermediateOverride { return 100; } +@end + +@implementation Subclass2Subclass +- (int)intermediateOverride { return 200; } +@end + +@implementation ChangesDuringInitialize ++ (void)initialize { + class_setSuperclass(self, objc_getClass("NotInitializedSuperclass1")); +} ++ (int)overriddenMeta { return 18; } +@end + +@implementation RemovedFromHierarchy ++ (int)overriddenMeta { return 19; } // shadows 14 on DefaultSuperClass ++ (int)sameNameMeta { return 19; } // shadows 14 on DefaultSuperClass ++ (void)onlyExistsOnRemovedClassMeta { } +- (void)onlyExistsOnRemovedClass { } +@end + +@implementation MovesUpwardsInHierarchy ++ (int)overriddenMeta { return 20; } // shadows 19 on RemovedFromHierarchy or 14 on DefaultSuperClass +@end + +@implementation OtherInitializedClass +static BOOL _otherInitializedClassInitialized = NO; ++ (void)initialize { + _otherInitializedClassInitialized = YES; +} +@end + +@implementation InitializesOneClassWhileBeingInitialized ++ (void)initialize { + [OtherInitializedClass class]; +} +@end + +@implementation Subclass3 +@end + +static int failures = 0; + +#define expect(x) do \ +{ \ + if (!(x)) \ + { \ + fprintf(stderr, "expectation FAILED: %s\n", #x); \ + ++failures; \ + } \ +} while(0) + +int main(int argc, char **argv) { + Class firstSuperclass = objc_getClass("DefaultSuperclass"); + Class subclass1 = objc_getClass("Subclass1"); + + /* Transitioning to a new superclass before +initialize has been called */ + { + Class subclass1 = objc_getClass("Subclass1"); + Class secondSuperclass = objc_getClass("NotInitializedSuperclass1"); + + assert(!_notInitializedSuperclass1Initialized); + assert(!_subclass1Initialized); + + class_setSuperclass(subclass1, secondSuperclass); + + // assert: dtable has not been installed; new superclass is still not initialized + assert(!_notInitializedSuperclass1Initialized); + + [Subclass1 class]; + // initialization and dtable installation has taken place + assert(_notInitializedSuperclass1Initialized); + + Subclass1 *subclass1instance1 = class_createInstance(subclass1, 0); + + // CLASS + // can call method on subclass + expect([subclass1instance1 existsOnSubclass1]); + // can call method on _new_ superclass + expect([subclass1instance1 existsOnNotInitializedSuperclass]); + // does not respond to selector from original superclass + expect(![subclass1 instancesRespondToSelector:@selector(existsOnDefaultSuperclass)]); + // *does* respond to selector from new superclass + expect([subclass1 instancesRespondToSelector:@selector(existsOnNotInitializedSuperclass)]); + // method existing on both old and new superclass kept, IMP updated + expect(2 == [subclass1instance1 sameName]); + // method existing on subclass, old and new superclass kept, IMP kept + expect(5 == [subclass1instance1 overridden]); + + + // METACLASS + // metaclass does not respond to selector from original meta superclass + expect(![subclass1 respondsToSelector:@selector(existsOnDefaultSuperclassMeta)]); + // metaclass *does* respond to selector from new meta superclass + expect([subclass1 respondsToSelector:@selector(existsOnNotInitializedSuperclassMeta)]); + // method existing on both old and new superclass kept, IMP updated + expect(12 == [subclass1 sameNameMeta]); + // method existing on subclass, old and new superclass kept, IMP kept + expect(15 == [subclass1 overriddenMeta]); + } + + /* Transitioning to a new superclass when +initialize has already been called */ + { + Class subclass2 = objc_getClass("Subclass2"); + Class secondSuperclass = objc_getClass("NotInitializedSuperclass2"); + assert(!_notInitializedSuperclass2Initialized); + assert(!_subclass2Initialized); + + [Subclass2 class]; + [Subclass2Subclass class]; // Make sure the subclass is initialized too. + assert(_alreadyInitializedSuperclassInitialized); + assert(_subclass2Initialized); + + Subclass2 *subclass2instance1 = class_createInstance(subclass2, 0); + assert([subclass2instance1 existsOnSubclass2]); + + class_setSuperclass(subclass2, secondSuperclass); + assert(_notInitializedSuperclass2Initialized); + + Subclass2 *subclass2instance2 = class_createInstance(subclass2, 0); + + // CLASS + // can call method on subclass + expect([subclass2instance1 existsOnSubclass2]); + // can call method on _new_ superclass + expect([subclass2instance1 existsOnNotInitializedSuperclass]); + // does not respond to selector from original superclass + expect(![subclass2 instancesRespondToSelector:@selector(existsOnDefaultSuperclass)]); + // *does* respond to selector from new superclass + expect([subclass2 instancesRespondToSelector:@selector(existsOnNotInitializedSuperclass)]); + + // method existing on both old and new superclass kept, IMP updated + expect(3 == [subclass2instance1 sameName]); + // method existing on subclass, old and new superclass kept, IMP kept + expect(6 == [subclass2instance1 overridden]); + // method existing only on subclass preserved + expect(100 == [subclass2instance1 intermediateOverride]); + + // METACLASS + // metaclass does not respond to selector from original meta superclass + expect(![subclass2 respondsToSelector:@selector(existsOnDefaultSuperclassMeta)]); + // metaclass *does* respond to selector from new meta superclass + expect([subclass2 respondsToSelector:@selector(existsOnNotInitializedSuperclassMeta)]); + // method existing on both old and new superclass kept, IMP updated + expect(13 == [subclass2 sameNameMeta]); + // method existing on subclass, old and new superclass kept, IMP kept + expect(16 == [subclass2 overriddenMeta]); + + // SUBCLASS + Subclass2 *subclass2subclassInstance = class_createInstance([Subclass2Subclass class], 0); + expect(![Subclass2Subclass instancesRespondToSelector:@selector(existsOnDefaultSuperclass)]); + expect(![Subclass2Subclass respondsToSelector:@selector(existsOnDefaultSuperclassMeta)]); + expect(3 == [subclass2subclassInstance sameName]); + expect(6 == [subclass2subclassInstance overridden]); + expect(200 == [subclass2subclassInstance intermediateOverride]); + expect(13 == [Subclass2Subclass sameNameMeta]); + expect(16 == [Subclass2Subclass overriddenMeta]); + } + + /* Transitioning ourselves to a new superclass while +initialize is running */ + { + expect(12 == [ChangesDuringInitialize sameNameMeta]); + expect(18 == [ChangesDuringInitialize overriddenMeta]); + } + + /* Transitioning to a superclass that's in our inheritance hierarchy already */ + { + assert(20 == [MovesUpwardsInHierarchy overriddenMeta]); + assert(19 == [MovesUpwardsInHierarchy sameNameMeta]); + assert([MovesUpwardsInHierarchy respondsToSelector:@selector(onlyExistsOnRemovedClassMeta)]); + assert([MovesUpwardsInHierarchy instancesRespondToSelector:@selector(onlyExistsOnRemovedClass)]); + + class_setSuperclass([MovesUpwardsInHierarchy class], [DefaultSuperclass class]); + + expect(20 == [MovesUpwardsInHierarchy overriddenMeta]); // still overridden + expect(14 == [MovesUpwardsInHierarchy sameNameMeta]); // falls back to DefaultSuperclass + expect(![MovesUpwardsInHierarchy respondsToSelector:@selector(onlyExistsOnRemovedClassMeta)]); + expect(![MovesUpwardsInHierarchy instancesRespondToSelector:@selector(onlyExistsOnRemovedClass)]); + } + + /* Transitioning to a superclass that may cause initialize lock contention */ + { + assert(!_otherInitializedClassInitialized); + expect(14 == [Subclass3 sameNameMeta]); + expect(14 == [Subclass3 overriddenMeta]); + + class_setSuperclass([Subclass3 class], objc_getClass("InitializesOneClassWhileBeingInitialized")); + + expect(_otherInitializedClassInitialized); + expect(13 == [Subclass3 sameNameMeta]); + expect(13 == [Subclass3 overriddenMeta]); + } + + return failures; +} From 072972600a13feddac2b2f835f02b631c689f009 Mon Sep 17 00:00:00 2001 From: Dustin Howett Date: Fri, 2 Mar 2018 18:32:37 -0800 Subject: [PATCH 18/26] fix objc_msgSend.x86-32.S for win32 --- common.S | 2 +- objc_msgSend.x86-32.S | 8 +++++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/common.S b/common.S index a8cc09af..4fab30cf 100644 --- a/common.S +++ b/common.S @@ -1,4 +1,4 @@ -#if defined(__WIN32__) || defined(__APPLE__) +#if (defined(_WIN32) && defined(__i386__)) || defined(__APPLE__) #define CDECL(symbol) _##symbol #else #define CDECL(symbol) symbol diff --git a/objc_msgSend.x86-32.S b/objc_msgSend.x86-32.S index 77193bda..7d83a4c7 100644 --- a/objc_msgSend.x86-32.S +++ b/objc_msgSend.x86-32.S @@ -54,7 +54,7 @@ push %ecx # _cmd push %eax # &self .cfi_def_cfa_offset 12 - call slowMsgLookup@PLT + call CDECL(slowMsgLookup)@PLT add $8, %esp # restore the stack @@ -65,8 +65,14 @@ 7: popl %ebx; 8: +#if __ELF__ + # ELF can support GOT-relative addressing; + # PE/COFF and Mach-O need a text relocation. addl $_GLOBAL_OFFSET_TABLE_+(8b-7b), %ebx leal SmallObjectClasses@GOTOFF(%ebx), %eax +#else + leal CDECL(SmallObjectClasses), %eax +#endif mov (%eax), %eax popl %ebx jmp 1b From d66d6e5a14bed489a4cc655d9ad2ba76b4a0ff0d Mon Sep 17 00:00:00 2001 From: Dustin Howett Date: Fri, 2 Mar 2018 18:43:15 -0800 Subject: [PATCH 19/26] Fix a warning in runtime.c --- runtime.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/runtime.c b/runtime.c index 5ba686bf..926bd069 100644 --- a/runtime.c +++ b/runtime.c @@ -571,7 +571,7 @@ Class class_setSuperclass(Class cls, Class newSuper) UNLOCK(&initialize_lock); UNLOCK_RUNTIME(); - objc_send_initialize(newSuper); // also initializes the metaclass + objc_send_initialize((id)newSuper); // also initializes the metaclass objc_update_dtable_for_new_superclass(cls->isa, newSuper->isa); objc_update_dtable_for_new_superclass(cls, newSuper); From 828e6ebf70f85d098da0c627c6fe097d76ae77af Mon Sep 17 00:00:00 2001 From: Dustin Howett Date: Fri, 2 Mar 2018 18:52:57 -0800 Subject: [PATCH 20/26] always look beyond the first page of associated objects Without this fix, we would lose associated objects silently after adding the 11th. We would also allocate full pages for each object after the 11th because we couldn't find empty slots. --- Test/AssociatedObject.m | 22 ++++++++++++++++++++++ associate.m | 12 +++++++----- 2 files changed, 29 insertions(+), 5 deletions(-) diff --git a/Test/AssociatedObject.m b/Test/AssociatedObject.m index e41da6f7..f3a50e1d 100644 --- a/Test/AssociatedObject.m +++ b/Test/AssociatedObject.m @@ -1,4 +1,6 @@ #include "Test.h" +#include +#include static BOOL deallocCalled = NO; static const char* objc_setAssociatedObjectKey = "objc_setAssociatedObjectKey"; @@ -36,4 +38,24 @@ int main(void) [holder release]; assert(deallocCalled); + + object = [Associated new]; + holder = [Test new]; + for (uintptr_t i = 1; i <= 20; ++i) + { + objc_setAssociatedObject(holder, (void*)i, object, OBJC_ASSOCIATION_RETAIN); + } + int lost = 0; + for (uintptr_t i = 1; i <= 20; ++i) + { + if (object != objc_getAssociatedObject(holder, (void*)i)) + { + fprintf(stderr, "lost object %" PRIuPTR "\n", i); + ++lost; + } + } + [holder release]; + [object release]; + assert(0 == lost); + return 0; } diff --git a/associate.m b/associate.m index d3ab8a9a..8f9d7ab4 100644 --- a/associate.m +++ b/associate.m @@ -75,14 +75,16 @@ static BOOL isAtomic(uintptr_t policy) static struct reference* findReference(struct reference_list *list, void *key) { - if (NULL == list) { return NULL; } - - for (int i=0 ; ilist[i].key == key) + for (int i=0 ; ilist[i]; + if (list->list[i].key == key) + { + return &list->list[i]; + } } + list = list->next; } return NULL; } From 682717b08ddef5c4e7adad6ae7ed87f8506d522c Mon Sep 17 00:00:00 2001 From: Jason Barmparesos Date: Tue, 10 Apr 2018 15:32:12 +0300 Subject: [PATCH 21/26] Clang++ provides its own exception types on Linux. (#68) Fix type of `__cxa_allocate_exception` with recent libsupc++. Recent versions of GNU libsupc++ provide a definition of `__cxa_allocate_exception` that has a `noexcept` qualifier. This is sensible (if allocating an exception throws an exception, then something is badly wrong) but it not what the ABI spec says. We provide our own definition of this, which must match another if provided. This wouldn't normally be a problem, but recent libstdc++ headers appear to leak libsupc++ headers into the namespace, so we're seeing these definitions even without explicitly including any C++ ABI-related headers. --- CMakeLists.txt | 16 ++++++++++++++++ objcxx_eh.h | 12 +++++++++++- 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 8cd7736b..b644ebce 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -3,6 +3,8 @@ cmake_minimum_required(VERSION 3.1) project(libobjc) enable_language(ASM) +INCLUDE (CheckCXXSourceCompiles) + macro(install_symlink filepath sympath) install(CODE "execute_process(COMMAND ${CMAKE_COMMAND} -E create_symlink ${filepath} ${sympath})") install(CODE "message(\"-- Symlinking: ${sympath} -> ${filepath}\")") @@ -368,3 +370,17 @@ if (TESTS) add_subdirectory(Test) endif (TESTS) +CHECK_CXX_SOURCE_COMPILES(" + #include + extern \"C\" { + __attribute__((weak)) + void *__cxa_allocate_exception(size_t thrown_size) noexcept; + } + #include + int main() { return 0; }" CXA_ALLOCATE_EXCEPTION_NOEXCEPT_COMPILES) + +if (CXA_ALLOCATE_EXCEPTION_NOEXCEPT_COMPILES) + add_definitions(-DCXA_ALLOCATE_EXCEPTION_SPECIFIER=noexcept) +else () + add_definitions(-DCXA_ALLOCATE_EXCEPTION_SPECIFIER=) +endif () diff --git a/objcxx_eh.h b/objcxx_eh.h index 987479c1..024283a0 100644 --- a/objcxx_eh.h +++ b/objcxx_eh.h @@ -5,8 +5,18 @@ extern "C" { * Allocates a C++ exception. This function is part of the Itanium C++ ABI and * is provided externally. */ +/* + * Note: Recent versions of libsupc++ already provide a prototype for + * __cxa__allocate_exception(). Since the libsupc++ version is defined with + * _GLIBCXX_NOTHROW, clang gives a type mismatch error. + */ +#ifndef __cplusplus +#undef CXA_ALLOCATE_EXCEPTION_SPECIFIER +#define CXA_ALLOCATE_EXCEPTION_SPECIFIER +#endif __attribute__((weak)) -void *__cxa_allocate_exception(size_t thrown_size); +void *__cxa_allocate_exception(size_t thrown_size) CXA_ALLOCATE_EXCEPTION_SPECIFIER; + /** * Initialises an exception object returned by __cxa_allocate_exception() for * storing an Objective-C object. The return value is the location of the From d32bafc6e1879e4a0d27d3fead97404c266a530e Mon Sep 17 00:00:00 2001 From: Dustin Howett Date: Tue, 10 Apr 2018 12:04:08 -0700 Subject: [PATCH 22/26] Let go of the runtime lock properly in class_setSuperclass --- runtime.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/runtime.c b/runtime.c index 926bd069..7b0ebd51 100644 --- a/runtime.c +++ b/runtime.c @@ -526,7 +526,10 @@ Class class_setSuperclass(Class cls, Class newSuper) LOCK_RUNTIME(); - if (cls->super_class == newSuper) { return newSuper; } + if (cls->super_class == newSuper) { + UNLOCK_RUNTIME(); + return newSuper; + } safe_remove_from_subclass_list(cls); objc_resolve_class(newSuper); From 00d23b0013f8310d2f36ce9a9f8cd059f7e14950 Mon Sep 17 00:00:00 2001 From: David Chisnall Date: Wed, 11 Apr 2018 07:42:54 +0100 Subject: [PATCH 23/26] Fix LOCK_SCOPE to allow multiple locks per scope. --- lock.h | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/lock.h b/lock.h index 2f3cd119..08c0777f 100644 --- a/lock.h +++ b/lock.h @@ -47,13 +47,26 @@ __attribute__((unused)) static void objc_release_lock(void *x) mutex_t *lock = *(mutex_t**)x; UNLOCK(lock); } +/** + * Concatenate strings during macro expansion. + */ +#define LOCK_HOLDERN_NAME_CAT(x, y) x ## y +/** + * Concatenate string with unique variable during macro expansion. + */ +#define LOCK_HOLDER_NAME_COUNTER(x, y) LOCK_HOLDERN_NAME_CAT(x, y) +/** + * Create a unique name for a lock holder variable + */ +#define LOCK_HOLDER_NAME(x) LOCK_HOLDER_NAME_COUNTER(x, __COUNTER__) + /** * Acquires the lock and automatically releases it at the end of the current * scope. */ #define LOCK_FOR_SCOPE(lock) \ __attribute__((cleanup(objc_release_lock)))\ - __attribute__((unused)) mutex_t *lock_pointer = lock;\ + __attribute__((unused)) mutex_t *LOCK_HOLDER_NAME(lock_pointer) = lock;\ LOCK(lock) /** From a4f97f75633044e7fdeab09e0ffbffd1ec2f7db9 Mon Sep 17 00:00:00 2001 From: David Chisnall Date: Wed, 11 Apr 2018 07:43:09 +0100 Subject: [PATCH 24/26] Refactor locking in setSuperclass. Now all lock ownership is tied to scopes, so will be released correctly in all return paths. --- runtime.c | 76 ++++++++++++++++++++++++++----------------------------- 1 file changed, 36 insertions(+), 40 deletions(-) diff --git a/runtime.c b/runtime.c index 7b0ebd51..41276c0c 100644 --- a/runtime.c +++ b/runtime.c @@ -522,58 +522,54 @@ Class class_setSuperclass(Class cls, Class newSuper) { CHECK_ARG(cls); CHECK_ARG(newSuper); + Class oldSuper; if (Nil == cls) { return Nil; } - LOCK_RUNTIME(); + { + LOCK_RUNTIME_FOR_SCOPE(); - if (cls->super_class == newSuper) { - UNLOCK_RUNTIME(); - return newSuper; - } + oldSuper = cls->super_class; - safe_remove_from_subclass_list(cls); - objc_resolve_class(newSuper); + if (oldSuper == newSuper) { return newSuper; } - Class oldSuper = cls->super_class; - cls->super_class = newSuper; + safe_remove_from_subclass_list(cls); + objc_resolve_class(newSuper); - // The super class's subclass list is used in certain method resolution scenarios. - cls->sibling_class = cls->super_class->subclass_list; - cls->super_class->subclass_list = cls; + cls->super_class = newSuper; - if (UNLIKELY(class_isMetaClass(cls))) - { - // newSuper is presumably a metaclass. Its isa will therefore be the appropriate root metaclass. - cls->isa = newSuper->isa; - } - else - { - Class meta = cls->isa, newSuperMeta = newSuper->isa; - // Update the metaclass's superclass. - safe_remove_from_subclass_list(meta); - objc_resolve_class(newSuperMeta); + // The super class's subclass list is used in certain method resolution scenarios. + cls->sibling_class = cls->super_class->subclass_list; + cls->super_class->subclass_list = cls; + + if (UNLIKELY(class_isMetaClass(cls))) + { + // newSuper is presumably a metaclass. Its isa will therefore be the appropriate root metaclass. + cls->isa = newSuper->isa; + } + else + { + Class meta = cls->isa, newSuperMeta = newSuper->isa; + // Update the metaclass's superclass. + safe_remove_from_subclass_list(meta); + objc_resolve_class(newSuperMeta); - meta->super_class = newSuperMeta; - meta->isa = newSuperMeta->isa; + meta->super_class = newSuperMeta; + meta->isa = newSuperMeta->isa; - // The super class's subclass list is used in certain method resolution scenarios. - meta->sibling_class = newSuperMeta->subclass_list; - newSuperMeta->subclass_list = meta; - } + // The super class's subclass list is used in certain method resolution scenarios. + meta->sibling_class = newSuperMeta->subclass_list; + newSuperMeta->subclass_list = meta; + } - LOCK(&initialize_lock); - if (!objc_test_class_flag(cls, objc_class_flag_initialized)) - { - // Uninitialized classes don't have dtables to update - // and don't need their superclasses initialized. - UNLOCK(&initialize_lock); - UNLOCK_RUNTIME(); - return oldSuper; + LOCK_FOR_SCOPE(&initialize_lock); + if (!objc_test_class_flag(cls, objc_class_flag_initialized)) + { + // Uninitialized classes don't have dtables to update + // and don't need their superclasses initialized. + return oldSuper; + } } - UNLOCK(&initialize_lock); - UNLOCK_RUNTIME(); - objc_send_initialize((id)newSuper); // also initializes the metaclass objc_update_dtable_for_new_superclass(cls->isa, newSuper->isa); objc_update_dtable_for_new_superclass(cls, newSuper); From 2bdf85ee50d1b808a06b1c5b3fcb97edb4a03585 Mon Sep 17 00:00:00 2001 From: David Chisnall Date: Wed, 11 Apr 2018 08:51:54 +0100 Subject: [PATCH 25/26] Add missing isa pointer. This test was accidentally passing sometimes, with the isa pointer being set using some bit of memory in inter-object padding. This breaks horribly with an allocator that packs objects densely. --- Test/setSuperclass.m | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Test/setSuperclass.m b/Test/setSuperclass.m index 236eff35..8d77564b 100644 --- a/Test/setSuperclass.m +++ b/Test/setSuperclass.m @@ -5,7 +5,11 @@ #pragma clang diagnostic ignored "-Wdeprecated-declarations" __attribute__((objc_root_class)) -@interface Root @end +@interface Root +{ + id isa; +} +@end @interface DefaultSuperclass: Root @end // test: new superclass when not initialized at the time of class_setSuperclass From 8021533d4c0504fb8db649f05d66a528bd573eff Mon Sep 17 00:00:00 2001 From: David Chisnall Date: Wed, 11 Apr 2018 08:52:57 +0100 Subject: [PATCH 26/26] Don't allocate objects of size 0. There's nowhere for the class pointer, so there's nothing that you can do with them. --- runtime.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/runtime.c b/runtime.c index 41276c0c..b3b734fd 100644 --- a/runtime.c +++ b/runtime.c @@ -356,6 +356,9 @@ id class_createInstance(Class cls, size_t extraBytes) } if (Nil == cls) { return nil; } + // Don't try to allocate an object of size 0, because there's no space for + // its isa pointer! + if (cls->instance_size == 0) { return nil; } id obj = gc->allocate_class(cls, extraBytes); obj->isa = cls; checkARCAccessorsSlow(cls);