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: 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 26be035c..a0ace72f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,8 +1,10 @@ -cmake_minimum_required(VERSION 2.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}\")") @@ -364,3 +366,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/Test/AssociatedObject.m b/Test/AssociatedObject.m index cd9464ea..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"; @@ -19,8 +21,41 @@ 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); + + 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/Test/CMakeLists.txt b/Test/CMakeLists.txt index 5a19051e..6658e82e 100644 --- a/Test/CMakeLists.txt +++ b/Test/CMakeLists.txt @@ -38,6 +38,8 @@ set(TESTS MethodArguments.m zeroSizedIVar.m exchange.m + hash_table_delete.c + setSuperclass.m ) # List of single-file tests that won't work with the legacy ABI and so diff --git a/Test/hash_table_delete.c b/Test/hash_table_delete.c new file mode 100644 index 00000000..ad7af896 --- /dev/null +++ b/Test/hash_table_delete.c @@ -0,0 +1,64 @@ +#include +#include + +struct test_struct { + uintptr_t key; +}; + +struct test_struct null_placeholder = {0}; + +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)(uintptr_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, (void*)2); + test_remove(testTable, (void*)1); + + 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; + } + + return 0; +} diff --git a/Test/setSuperclass.m b/Test/setSuperclass.m new file mode 100644 index 00000000..3ff468b1 --- /dev/null +++ b/Test/setSuperclass.m @@ -0,0 +1,297 @@ +#include "../objc/runtime.h" +#include +#include + +#pragma clang diagnostic ignored "-Wdeprecated-declarations" + +__attribute__((objc_root_class)) +@interface Root +{ + id isa; +} +@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([(id)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([(id)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; +} 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/associate.m b/associate.m index fd3317c6..cfae3c80 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; } @@ -166,12 +168,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 + { + if (OBJC_ASSOCIATION_ASSIGN != r->policy) + { + objc_release(r->object); + } + } + @finally { - objc_release(old); + r->policy = policy; + r->object = obj; } if (needLock) { diff --git a/class.h b/class.h index a00043e0..339b5c88 100644 --- a/class.h +++ b/class.h @@ -410,4 +410,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 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/dtable.c b/dtable.c index 112b4f85..5b0aefe6 100644 --- a/dtable.c +++ b/dtable.c @@ -392,6 +392,78 @@ PRIVATE void objc_update_dtable_for_class(Class cls) checkARCAccessors(cls); } +static void rebaseDtableRecursive(Class cls, Class newSuper) +{ + dtable_t parentDtable = dtable_for_class(newSuper); + // Collect all of the methods for this class: + dtable_t temporaryDtable = SparseArrayNewWithDepth(dtable_depth); + + for (struct objc_method_list *list = cls->methods ; list != NULL ; list = list->next) + { + for (unsigned i=0 ; icount ; i++) + { + struct objc_method *m = method_at_index(list, i); + uint32_t idx = m->selector->index; + // Don't replace existing methods - we're doing the traversal + // pre-order so we'll see methods from categories first. + if (SparseArrayLookup(temporaryDtable, idx) == NULL) + { + SparseArrayInsert(temporaryDtable, idx, m); + } + } + } + + + dtable_t dtable = dtable_for_class(cls); + uint32_t idx = 0; + struct objc_method *method; + // Install all methods in the dtable with the correct ones. + while ((method = SparseArrayNext(temporaryDtable, &idx))) + { + SparseArrayInsert(dtable, idx, method); + } + idx = 0; + // Now look at all of the methods in the dtable. If they're not ones from + // the dtable that we've just created, then they must come from the + // superclass, so replace them with whatever the superclass has (which may + // be NULL). + while ((method = SparseArrayNext(dtable, &idx))) + { + if (SparseArrayLookup(temporaryDtable, idx) == NULL) + { + SparseArrayInsert(dtable, idx, SparseArrayLookup(parentDtable, idx)); + } + } + SparseArrayDestroy(temporaryDtable); + + // merge can make a class ARC-compatible. + checkARCAccessors(cls); + + // Now visit all of our subclasses and propagate the changes downwards. + 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, cls); + } + +} + +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, newSuper); + // Invalidate all caches after this operation. + objc_method_cache_version++; + + return; +} + PRIVATE void add_method_list_to_class(Class cls, struct objc_method_list *list) { @@ -410,7 +482,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/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/eh_personality.c b/eh_personality.c index f9a882c6..41d1675f 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 } 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) diff --git a/lock.h b/lock.h index d39f07b3..08c0777f 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 @@ -49,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) /** 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 e7bfe25f..f72f1cf9 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. @@ -106,4 +110,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__ diff --git a/objc/runtime.h b/objc/runtime.h index 299ef80c..e6a93342 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 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 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 diff --git a/runtime.c b/runtime.c index 6472c6c7..96ef72ff 100644 --- a/runtime.c +++ b/runtime.c @@ -21,6 +21,10 @@ #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. */ @@ -331,7 +335,9 @@ id class_createInstance(Class cls, size_t extraBytes) } if (Nil == cls) { return nil; } - assert(cls->instance_size >= sizeof(Class)); + // Don't try to allocate an object of size 0, because there's no space for + // its isa pointer! + if (cls->instance_size < sizeof(Class)) { return nil; } id obj = gc->allocate_class(cls, extraBytes); obj->isa = cls; checkARCAccessorsSlow(cls); @@ -488,9 +494,58 @@ Class class_setSuperclass(Class cls, Class newSuper) { CHECK_ARG(cls); CHECK_ARG(newSuper); + Class oldSuper; if (Nil == cls) { return Nil; } - Class oldSuper = cls->super_class; - cls->super_class = newSuper; + + { + LOCK_RUNTIME_FOR_SCOPE(); + + oldSuper = cls->super_class; + + if (oldSuper == newSuper) { return newSuper; } + + safe_remove_from_subclass_list(cls); + objc_resolve_class(newSuper); + + 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 (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; + + // The super class's subclass list is used in certain method resolution scenarios. + meta->sibling_class = newSuperMeta->subclass_list; + newSuperMeta->subclass_list = meta; + } + + 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; + } + } + + 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); + return oldSuper; } @@ -756,8 +811,6 @@ const char *object_getClassName(id obj) return class_getName(object_getClass(obj)); } -PRIVATE void objc_resolve_class(Class); - void objc_registerClassPair(Class cls) { if (cls->ivars != NULL) 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); 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)