From 8a971da97b47dba48980e4fddfc5ccafe988ce50 Mon Sep 17 00:00:00 2001 From: wangzhaowei Date: Wed, 14 Dec 2016 00:11:51 +0800 Subject: [PATCH 01/12] Version 706 --- libobjc.order | 1 - markgc.cpp | 86 +- objc.sln | 0 objc.suo | Bin objc.xcodeproj/project.pbxproj | 26 +- prebuild.bat | 0 runtime/Messengers.subproj/objc-msg-arm.s | 675 +++-- runtime/Messengers.subproj/objc-msg-arm64.s | 279 +- runtime/Messengers.subproj/objc-msg-i386.s | 25 +- .../objc-msg-simulator-i386.s | 615 +++-- .../objc-msg-simulator-x86_64.s | 800 +++--- runtime/Messengers.subproj/objc-msg-win32.m | 31 - runtime/Messengers.subproj/objc-msg-x86_64.s | 856 ++++--- runtime/NSObject.h | 10 +- runtime/NSObject.mm | 376 +-- runtime/Object.h | 3 +- runtime/Object.mm | 4 +- runtime/OldClasses.subproj/List.h | 2 +- runtime/Protocol.h | 14 +- runtime/Protocol.mm | 13 +- runtime/hashtable2.h | 4 +- runtime/maptable.h | 4 +- runtime/maptable.mm | 5 +- runtime/message.h | 46 +- runtime/objc-abi.h | 228 +- runtime/objc-accessors.h | 44 - runtime/objc-accessors.mm | 57 +- runtime/objc-api.h | 65 +- runtime/objc-auto-dump.h | 55 - runtime/objc-auto-dump.mm | 224 -- runtime/objc-auto.h | 212 +- runtime/objc-auto.mm | 1331 +--------- runtime/objc-cache-old.mm | 52 - runtime/objc-cache.mm | 13 +- runtime/objc-class-old.mm | 192 +- runtime/objc-class.mm | 346 ++- runtime/objc-config.h | 57 +- runtime/objc-env.h | 6 +- runtime/objc-errors.mm | 80 +- runtime/objc-exception.h | 43 +- runtime/objc-exception.mm | 147 +- runtime/objc-externalref.mm | 283 -- runtime/objc-file-old.h | 3 + runtime/objc-file-old.mm | 23 +- runtime/objc-file.mm | 29 +- runtime/objc-gdb.h | 97 +- runtime/objc-initialize.h | 2 + runtime/objc-initialize.mm | 86 +- runtime/objc-internal.h | 408 +-- runtime/objc-lockdebug.mm | 39 + runtime/objc-object.h | 454 ++-- runtime/objc-opt.mm | 79 +- runtime/objc-os.h | 223 +- runtime/objc-os.mm | 633 ++--- runtime/objc-private.h | 372 +-- runtime/objc-runtime-new.h | 170 +- runtime/objc-runtime-new.mm | 1377 +++++----- runtime/objc-runtime-old.h | 233 +- runtime/objc-runtime-old.mm | 204 +- runtime/objc-runtime.mm | 469 +++- runtime/objc-sel-old.mm | 19 +- runtime/objc-sel-table.s | 27 +- runtime/objc-sel.mm | 15 +- runtime/objc-sync.h | 4 +- runtime/objc-weak.h | 50 +- runtime/objc-weak.mm | 106 +- runtime/objc.h | 51 +- runtime/runtime.h | 311 ++- test/ARRBase.h | 16 - test/ARRBase.m | 24 - test/ARRLayouts.m | 99 - test/ARRMRR.h | 13 - test/ARRMRR.m | 11 - test/MRRARR.h | 13 - test/MRRARR.m | 11 - test/MRRBase.h | 16 - test/MRRBase.m | 24 - test/Makefile | 20 - test/accessors.m | 79 - test/accessors2.m | 143 -- test/addMethod.m | 104 - test/addProtocol.m | 211 -- test/applescriptobjc.m | 14 - test/applescriptobjc2.m | 17 - test/arr-cast.m | 20 - test/arr-weak.m | 329 --- test/association-cf.m | 38 - test/association.m | 145 -- test/atomicProperty.mm | 41 - test/badAltHandler.m | 81 - test/badCache.m | 86 - test/badTagClass.m | 42 - test/badTagIndex.m | 33 - test/bigrc.m | 133 - test/blocksAsImps.m | 263 -- test/cacheflush.h | 7 - test/cacheflush.m | 61 - test/cacheflush0.m | 7 - test/cacheflush2.m | 6 - test/cacheflush3.m | 6 - test/category.m | 105 - test/cdtors.mm | 311 --- test/classgetclass.m | 20 - test/classname.m | 39 - test/classpair.m | 376 --- test/classversion.m | 19 - test/concurrentcat.m | 129 - test/concurrentcat_category.m | 70 - test/copyIvarList.m | 115 - test/copyMethodList.m | 155 -- test/copyPropertyList.m | 124 - test/createInstance.m | 63 - test/customrr-cat1.m | 7 - test/customrr-cat2.m | 7 - test/customrr-nsobject-awz.m | 16 - test/customrr-nsobject-none.m | 15 - test/customrr-nsobject-rr.m | 16 - test/customrr-nsobject-rrawz.m | 17 - test/customrr-nsobject.m | 154 -- test/customrr.m | 904 ------- test/customrr2.m | 9 - test/definitions.c | 56 - test/designatedinit.m | 26 - test/duplicateClass.m | 164 -- test/duplicatedClasses.m | 27 - test/evil-category-0.m | 18 - test/evil-category-00.m | 24 - test/evil-category-000.m | 18 - test/evil-category-1.m | 24 - test/evil-category-2.m | 24 - test/evil-category-3.m | 24 - test/evil-category-4.m | 24 - test/evil-category-def.m | 72 - test/evil-class-0.m | 22 - test/evil-class-00.m | 28 - test/evil-class-000.m | 22 - test/evil-class-1.m | 28 - test/evil-class-2.m | 28 - test/evil-class-3.m | 28 - test/evil-class-4.m | 28 - test/evil-class-5.m | 30 - test/evil-class-def.m | 319 --- test/evil-main.m | 15 - test/exc.m | 888 ------- test/exchangeImp.m | 87 - test/foreach.m | 227 -- test/forward.m | 1002 -------- test/forwardDefault.m | 31 - test/forwardDefaultStret.m | 31 - test/future.h | 15 - test/future.m | 82 - test/future0.m | 2 - test/future2.m | 17 - test/gc-main.m | 10 - test/gc.c | 1 - test/gc.m | 8 - test/gcenforcer-nogc-1.m | 15 - test/gcenforcer-nogc-2.m | 22 - test/gcenforcer-noobjc.m | 13 - test/gcenforcer-requiresgc-1.m | 23 - test/gcenforcer-requiresgc-2.m | 16 - test/gcenforcer-supportsgc.m | 13 - test/gcenforcer.m | 36 - test/gdb.m | 59 - test/getMethod.m | 130 - test/ignoredSelector.m | 350 --- test/ignoredSelector2.m | 37 - test/imageorder.h | 20 - test/imageorder.m | 41 - test/imageorder1.m | 52 - test/imageorder2.m | 33 - test/imageorder3.m | 33 - test/includes.c | 38 - test/initialize.m | 277 -- test/initializeVersusWeak.m | 129 - test/instanceSize.m | 59 - test/ismeta.m | 13 - test/ivar.m | 110 - test/ivarSlide.h | 110 - test/ivarSlide.m | 533 ---- test/ivarSlide1.m | 21 - test/layout.m | 97 - test/literals.m | 56 - test/load-noobjc.m | 52 - test/load-noobjc2.m | 16 - test/load-noobjc3.m | 13 - test/load-order.m | 18 - test/load-order1.m | 15 - test/load-order2.m | 15 - test/load-order3.m | 12 - test/load-parallel.m | 65 - test/load-parallel0.m | 46 - test/load-parallel00.m | 1 - test/load-reentrant.m | 36 - test/load-reentrant2.m | 23 - test/load.m | 106 - test/methodArgs.m | 166 -- test/methodListSize.m | 56 - test/method_getName.m | 27 - test/msgSend.m | 2278 ----------------- test/nilAPIArgs.m | 13 - test/nonpointerisa.m | 223 -- test/nopool.m | 38 - test/nscdtors.mm | 6 - test/nsexc.m | 21 - test/nsobject.m | 114 - test/nsprotocol.m | 47 - test/objectCopy.m | 37 - test/property.m | 65 - test/propertyDesc.m | 323 --- test/protocol.m | 320 --- test/protocol_copyMethodList.m | 146 -- test/protocol_copyPropertyList.m | 109 - test/protocol_cw.m | 40 - test/rawisa.m | 27 - test/readClassPair.m | 77 - test/resolve.m | 298 --- test/rr-autorelease-fast.m | 317 --- test/rr-autorelease-fastarc.m | 231 -- test/rr-autorelease-stacklogging.m | 12 - test/rr-autorelease.m | 9 - test/rr-autorelease2.m | 364 --- test/rr-nsautorelease.m | 7 - test/rr-sidetable.m | 59 - test/runtime.m | 216 -- test/sel.m | 45 - test/setSuper.m | 44 - test/subscripting.m | 154 -- test/super.m | 21 - test/synchronized-counter.m | 90 - test/synchronized-grid.m | 114 - test/synchronized.m | 104 - test/taggedNSPointers.m | 80 - test/taggedPointers.m | 322 --- test/taggedPointersDisabled.m | 31 - test/tbi.c | 14 - test/test.h | 475 ---- test/test.pl | 1477 ----------- test/testroot.i | 229 -- test/unload.h | 6 - test/unload.m | 174 -- test/unload2.m | 26 - test/unload3.c | 17 - test/unload4.m | 11 - test/unwind.m | 93 - test/verify-exports.pl | 254 -- test/weak.h | 67 - test/weak.m | 316 --- test/weak2.m | 82 - test/weakcopy.m | 84 - test/weakframework-missing.m | 14 - test/weakframework-not-missing.m | 11 - test/weakimport-missing.m | 13 - test/weakimport-not-missing.m | 11 - test/weakrace.m | 76 - test/xref.m | 32 - test/zone.m | 40 - version.bat | 0 258 files changed, 5988 insertions(+), 27967 deletions(-) mode change 100644 => 100755 objc.sln mode change 100644 => 100755 objc.suo mode change 100644 => 100755 prebuild.bat delete mode 100644 runtime/objc-accessors.h delete mode 100644 runtime/objc-auto-dump.h delete mode 100644 runtime/objc-auto-dump.mm delete mode 100644 runtime/objc-externalref.mm delete mode 100644 test/ARRBase.h delete mode 100644 test/ARRBase.m delete mode 100644 test/ARRLayouts.m delete mode 100644 test/ARRMRR.h delete mode 100644 test/ARRMRR.m delete mode 100644 test/MRRARR.h delete mode 100644 test/MRRARR.m delete mode 100644 test/MRRBase.h delete mode 100644 test/MRRBase.m delete mode 100644 test/Makefile delete mode 100644 test/accessors.m delete mode 100644 test/accessors2.m delete mode 100644 test/addMethod.m delete mode 100644 test/addProtocol.m delete mode 100644 test/applescriptobjc.m delete mode 100644 test/applescriptobjc2.m delete mode 100644 test/arr-cast.m delete mode 100644 test/arr-weak.m delete mode 100644 test/association-cf.m delete mode 100644 test/association.m delete mode 100644 test/atomicProperty.mm delete mode 100644 test/badAltHandler.m delete mode 100644 test/badCache.m delete mode 100644 test/badTagClass.m delete mode 100644 test/badTagIndex.m delete mode 100644 test/bigrc.m delete mode 100644 test/blocksAsImps.m delete mode 100644 test/cacheflush.h delete mode 100644 test/cacheflush.m delete mode 100644 test/cacheflush0.m delete mode 100644 test/cacheflush2.m delete mode 100644 test/cacheflush3.m delete mode 100644 test/category.m delete mode 100644 test/cdtors.mm delete mode 100644 test/classgetclass.m delete mode 100644 test/classname.m delete mode 100644 test/classpair.m delete mode 100644 test/classversion.m delete mode 100644 test/concurrentcat.m delete mode 100644 test/concurrentcat_category.m delete mode 100644 test/copyIvarList.m delete mode 100644 test/copyMethodList.m delete mode 100644 test/copyPropertyList.m delete mode 100644 test/createInstance.m delete mode 100644 test/customrr-cat1.m delete mode 100644 test/customrr-cat2.m delete mode 100644 test/customrr-nsobject-awz.m delete mode 100644 test/customrr-nsobject-none.m delete mode 100644 test/customrr-nsobject-rr.m delete mode 100644 test/customrr-nsobject-rrawz.m delete mode 100644 test/customrr-nsobject.m delete mode 100644 test/customrr.m delete mode 100644 test/customrr2.m delete mode 100644 test/definitions.c delete mode 100644 test/designatedinit.m delete mode 100644 test/duplicateClass.m delete mode 100644 test/duplicatedClasses.m delete mode 100644 test/evil-category-0.m delete mode 100644 test/evil-category-00.m delete mode 100644 test/evil-category-000.m delete mode 100644 test/evil-category-1.m delete mode 100644 test/evil-category-2.m delete mode 100644 test/evil-category-3.m delete mode 100644 test/evil-category-4.m delete mode 100644 test/evil-category-def.m delete mode 100644 test/evil-class-0.m delete mode 100644 test/evil-class-00.m delete mode 100644 test/evil-class-000.m delete mode 100644 test/evil-class-1.m delete mode 100644 test/evil-class-2.m delete mode 100644 test/evil-class-3.m delete mode 100644 test/evil-class-4.m delete mode 100644 test/evil-class-5.m delete mode 100644 test/evil-class-def.m delete mode 100644 test/evil-main.m delete mode 100644 test/exc.m delete mode 100644 test/exchangeImp.m delete mode 100644 test/foreach.m delete mode 100644 test/forward.m delete mode 100644 test/forwardDefault.m delete mode 100644 test/forwardDefaultStret.m delete mode 100644 test/future.h delete mode 100644 test/future.m delete mode 100644 test/future0.m delete mode 100644 test/future2.m delete mode 100644 test/gc-main.m delete mode 100644 test/gc.c delete mode 100644 test/gc.m delete mode 100644 test/gcenforcer-nogc-1.m delete mode 100644 test/gcenforcer-nogc-2.m delete mode 100644 test/gcenforcer-noobjc.m delete mode 100644 test/gcenforcer-requiresgc-1.m delete mode 100644 test/gcenforcer-requiresgc-2.m delete mode 100644 test/gcenforcer-supportsgc.m delete mode 100644 test/gcenforcer.m delete mode 100644 test/gdb.m delete mode 100644 test/getMethod.m delete mode 100644 test/ignoredSelector.m delete mode 100644 test/ignoredSelector2.m delete mode 100644 test/imageorder.h delete mode 100644 test/imageorder.m delete mode 100644 test/imageorder1.m delete mode 100644 test/imageorder2.m delete mode 100644 test/imageorder3.m delete mode 100644 test/includes.c delete mode 100644 test/initialize.m delete mode 100644 test/initializeVersusWeak.m delete mode 100644 test/instanceSize.m delete mode 100644 test/ismeta.m delete mode 100644 test/ivar.m delete mode 100644 test/ivarSlide.h delete mode 100644 test/ivarSlide.m delete mode 100644 test/ivarSlide1.m delete mode 100644 test/layout.m delete mode 100644 test/literals.m delete mode 100644 test/load-noobjc.m delete mode 100644 test/load-noobjc2.m delete mode 100644 test/load-noobjc3.m delete mode 100644 test/load-order.m delete mode 100644 test/load-order1.m delete mode 100644 test/load-order2.m delete mode 100644 test/load-order3.m delete mode 100644 test/load-parallel.m delete mode 100644 test/load-parallel0.m delete mode 100644 test/load-parallel00.m delete mode 100644 test/load-reentrant.m delete mode 100644 test/load-reentrant2.m delete mode 100644 test/load.m delete mode 100644 test/methodArgs.m delete mode 100644 test/methodListSize.m delete mode 100644 test/method_getName.m delete mode 100644 test/msgSend.m delete mode 100644 test/nilAPIArgs.m delete mode 100644 test/nonpointerisa.m delete mode 100644 test/nopool.m delete mode 100644 test/nscdtors.mm delete mode 100644 test/nsexc.m delete mode 100644 test/nsobject.m delete mode 100644 test/nsprotocol.m delete mode 100644 test/objectCopy.m delete mode 100644 test/property.m delete mode 100644 test/propertyDesc.m delete mode 100644 test/protocol.m delete mode 100644 test/protocol_copyMethodList.m delete mode 100644 test/protocol_copyPropertyList.m delete mode 100644 test/protocol_cw.m delete mode 100644 test/rawisa.m delete mode 100644 test/readClassPair.m delete mode 100644 test/resolve.m delete mode 100644 test/rr-autorelease-fast.m delete mode 100644 test/rr-autorelease-fastarc.m delete mode 100644 test/rr-autorelease-stacklogging.m delete mode 100644 test/rr-autorelease.m delete mode 100644 test/rr-autorelease2.m delete mode 100644 test/rr-nsautorelease.m delete mode 100644 test/rr-sidetable.m delete mode 100644 test/runtime.m delete mode 100644 test/sel.m delete mode 100644 test/setSuper.m delete mode 100644 test/subscripting.m delete mode 100644 test/super.m delete mode 100644 test/synchronized-counter.m delete mode 100644 test/synchronized-grid.m delete mode 100644 test/synchronized.m delete mode 100644 test/taggedNSPointers.m delete mode 100644 test/taggedPointers.m delete mode 100644 test/taggedPointersDisabled.m delete mode 100644 test/tbi.c delete mode 100644 test/test.h delete mode 100755 test/test.pl delete mode 100644 test/testroot.i delete mode 100644 test/unload.h delete mode 100644 test/unload.m delete mode 100644 test/unload2.m delete mode 100644 test/unload3.c delete mode 100644 test/unload4.m delete mode 100644 test/unwind.m delete mode 100755 test/verify-exports.pl delete mode 100644 test/weak.h delete mode 100644 test/weak.m delete mode 100644 test/weak2.m delete mode 100644 test/weakcopy.m delete mode 100644 test/weakframework-missing.m delete mode 100644 test/weakframework-not-missing.m delete mode 100644 test/weakimport-missing.m delete mode 100644 test/weakimport-not-missing.m delete mode 100644 test/weakrace.m delete mode 100644 test/xref.m delete mode 100644 test/zone.m mode change 100644 => 100755 version.bat diff --git a/libobjc.order b/libobjc.order index 88206c1..c2f2c6e 100644 --- a/libobjc.order +++ b/libobjc.order @@ -341,7 +341,6 @@ __class_getMethodNoSuper _object_cxxDestruct _object_cxxDestructFromClass _class_copyIvarList -__object_addExternalReference_rr __objc_rootRetain_slow __objc_rootReleaseWasZero_slow _object_copy diff --git a/markgc.cpp b/markgc.cpp index bbef255..49fd2ba 100644 --- a/markgc.cpp +++ b/markgc.cpp @@ -35,10 +35,6 @@ #include #include -// from "objc-private.h" -// masks for objc_image_info.flags -#define OBJC_IMAGE_SUPPORTS_GC (1<<1) - // Some OS X SDKs don't define these. #ifndef CPU_TYPE_ARM #define CPU_TYPE_ARM ((cpu_type_t) 12) @@ -378,58 +374,40 @@ bool sectnameEquals(const char *lhs, const char *rhs) template -void dosect(uint8_t *start, macho_section

*sect, bool isOldABI, bool isOSX) +void dosect(uint8_t *start, macho_section

*sect) { if (debug) printf("section %.16s from segment %.16s\n", sect->sectname(), sect->segname()); - if (isOSX) { - // Add "supports GC" flag to objc image info - if ((segnameStartsWith(sect->segname(), "__DATA") && - sectnameEquals(sect->sectname(), "__objc_imageinfo")) || - (segnameEquals(sect->segname(), "__OBJC") && - sectnameEquals(sect->sectname(), "__image_info"))) - { - imageinfo *ii = (imageinfo*)(start + sect->offset()); - P::E::set32(ii->flags, P::E::get32(ii->flags) | OBJC_IMAGE_SUPPORTS_GC); - if (debug) printf("added GC support flag\n"); - } + // Strip S_MOD_INIT/TERM_FUNC_POINTERS. We don't want dyld to call + // our init funcs because it is too late, and we don't want anyone to + // call our term funcs ever. + if (segnameStartsWith(sect->segname(), "__DATA") && + sectnameEquals(sect->sectname(), "__mod_init_func")) + { + // section type 0 is S_REGULAR + sect->set_flags(sect->flags() & ~SECTION_TYPE); + sect->set_sectname("__objc_init_func"); + if (debug) printf("disabled __mod_init_func section\n"); } - - if (isOldABI) { - // Keep init funcs because libSystem doesn't call _objc_init(). - } else { - // Strip S_MOD_INIT/TERM_FUNC_POINTERS. We don't want dyld to call - // our init funcs because it is too late, and we don't want anyone to - // call our term funcs ever. - if (segnameStartsWith(sect->segname(), "__DATA") && - sectnameEquals(sect->sectname(), "__mod_init_func")) - { - // section type 0 is S_REGULAR - sect->set_flags(sect->flags() & ~SECTION_TYPE); - sect->set_sectname("__objc_init_func"); - if (debug) printf("disabled __mod_init_func section\n"); - } - if (segnameStartsWith(sect->segname(), "__DATA") && - sectnameEquals(sect->sectname(), "__mod_term_func")) - { - // section type 0 is S_REGULAR - sect->set_flags(sect->flags() & ~SECTION_TYPE); - sect->set_sectname("__objc_term_func"); - if (debug) printf("disabled __mod_term_func section\n"); - } + if (segnameStartsWith(sect->segname(), "__DATA") && + sectnameEquals(sect->sectname(), "__mod_term_func")) + { + // section type 0 is S_REGULAR + sect->set_flags(sect->flags() & ~SECTION_TYPE); + sect->set_sectname("__objc_term_func"); + if (debug) printf("disabled __mod_term_func section\n"); } } template -void doseg(uint8_t *start, macho_segment_command

*seg, - bool isOldABI, bool isOSX) +void doseg(uint8_t *start, macho_segment_command

*seg) { if (debug) printf("segment name: %.16s, nsects %u\n", seg->segname(), seg->nsects()); macho_section

*sect = (macho_section

*)(seg + 1); for (uint32_t i = 0; i < seg->nsects(); ++i) { - dosect(start, §[i], isOldABI, isOSX); + dosect(start, §[i]); } } @@ -438,32 +416,12 @@ template bool parse_macho(uint8_t *buffer) { macho_header

* mh = (macho_header

*)buffer; - uint8_t *cmds; - - bool isOldABI = false; - bool isOSX = false; - cmds = (uint8_t *)(mh + 1); - for (uint32_t c = 0; c < mh->ncmds(); c++) { - macho_load_command

* cmd = (macho_load_command

*)cmds; - cmds += cmd->cmdsize(); - if (cmd->cmd() == LC_SEGMENT || cmd->cmd() == LC_SEGMENT_64) { - macho_segment_command

* seg = (macho_segment_command

*)cmd; - if (segnameEquals(seg->segname(), "__OBJC")) isOldABI = true; - } - else if (cmd->cmd() == LC_VERSION_MIN_MACOSX) { - isOSX = true; - } - } - - if (debug) printf("ABI=%s, OS=%s\n", - isOldABI ? "old" : "new", isOSX ? "osx" : "ios"); - - cmds = (uint8_t *)(mh + 1); + uint8_t *cmds = (uint8_t *)(mh + 1); for (uint32_t c = 0; c < mh->ncmds(); c++) { macho_load_command

* cmd = (macho_load_command

*)cmds; cmds += cmd->cmdsize(); if (cmd->cmd() == LC_SEGMENT || cmd->cmd() == LC_SEGMENT_64) { - doseg(buffer, (macho_segment_command

*)cmd, isOldABI, isOSX); + doseg(buffer, (macho_segment_command

*)cmd); } } diff --git a/objc.sln b/objc.sln old mode 100644 new mode 100755 diff --git a/objc.suo b/objc.suo old mode 100644 new mode 100755 diff --git a/objc.xcodeproj/project.pbxproj b/objc.xcodeproj/project.pbxproj index 3e41fbb..2c6187d 100644 --- a/objc.xcodeproj/project.pbxproj +++ b/objc.xcodeproj/project.pbxproj @@ -23,13 +23,11 @@ /* Begin PBXBuildFile section */ 393CEAC00DC69E3E000B69DE /* objc-references.mm in Sources */ = {isa = PBXBuildFile; fileRef = 393CEABF0DC69E3E000B69DE /* objc-references.mm */; }; 393CEAC60DC69E67000B69DE /* objc-references.h in Headers */ = {isa = PBXBuildFile; fileRef = 393CEAC50DC69E67000B69DE /* objc-references.h */; }; - 399BC72E1224831B007FBDF0 /* objc-externalref.mm in Sources */ = {isa = PBXBuildFile; fileRef = 399BC72D1224831B007FBDF0 /* objc-externalref.mm */; }; 39ABD72312F0B61800D1054C /* objc-weak.h in Headers */ = {isa = PBXBuildFile; fileRef = 39ABD71F12F0B61800D1054C /* objc-weak.h */; }; 39ABD72412F0B61800D1054C /* objc-weak.mm in Sources */ = {isa = PBXBuildFile; fileRef = 39ABD72012F0B61800D1054C /* objc-weak.mm */; }; 830F2A740D737FB800392440 /* objc-msg-arm.s in Sources */ = {isa = PBXBuildFile; fileRef = 830F2A690D737FB800392440 /* objc-msg-arm.s */; }; 830F2A750D737FB900392440 /* objc-msg-i386.s in Sources */ = {isa = PBXBuildFile; fileRef = 830F2A6A0D737FB800392440 /* objc-msg-i386.s */; }; 830F2A7D0D737FBB00392440 /* objc-msg-x86_64.s in Sources */ = {isa = PBXBuildFile; fileRef = 830F2A720D737FB800392440 /* objc-msg-x86_64.s */; }; - 830F2A940D73876100392440 /* objc-accessors.h in Headers */ = {isa = PBXBuildFile; fileRef = 830F2A920D73876100392440 /* objc-accessors.h */; }; 830F2A950D73876100392440 /* objc-accessors.mm in Sources */ = {isa = PBXBuildFile; fileRef = 830F2A930D73876100392440 /* objc-accessors.mm */; }; 830F2A980D738DC200392440 /* hashtable.h in Headers */ = {isa = PBXBuildFile; fileRef = 830F2A970D738DC200392440 /* hashtable.h */; settings = {ATTRIBUTES = (Public, ); }; }; 83112ED40F00599600A5FBAF /* objc-internal.h in Headers */ = {isa = PBXBuildFile; fileRef = 83112ED30F00599600A5FBAF /* objc-internal.h */; settings = {ATTRIBUTES = (Private, ); }; }; @@ -100,8 +98,6 @@ 83F550E0155E030800E95D3B /* objc-cache-old.mm in Sources */ = {isa = PBXBuildFile; fileRef = 83F550DF155E030800E95D3B /* objc-cache-old.mm */; }; 87BB4EA70EC39854005D08E1 /* objc-probes.d in Sources */ = {isa = PBXBuildFile; fileRef = 87BB4E900EC39633005D08E1 /* objc-probes.d */; }; 9672F7EE14D5F488007CEC96 /* NSObject.mm in Sources */ = {isa = PBXBuildFile; fileRef = 9672F7ED14D5F488007CEC96 /* NSObject.mm */; }; - BC07A00C0EF72D360014EC61 /* objc-auto-dump.h in Headers */ = {isa = PBXBuildFile; fileRef = BC07A00B0EF72D360014EC61 /* objc-auto-dump.h */; settings = {ATTRIBUTES = (Private, ); }; }; - BC07A0110EF72D9C0014EC61 /* objc-auto-dump.mm in Sources */ = {isa = PBXBuildFile; fileRef = BC07A0100EF72D9C0014EC61 /* objc-auto-dump.mm */; }; E8923DA1116AB2820071B552 /* a1a2-blocktramps-i386.s in Sources */ = {isa = PBXBuildFile; fileRef = E8923D9C116AB2820071B552 /* a1a2-blocktramps-i386.s */; }; E8923DA2116AB2820071B552 /* a1a2-blocktramps-x86_64.s in Sources */ = {isa = PBXBuildFile; fileRef = E8923D9D116AB2820071B552 /* a1a2-blocktramps-x86_64.s */; }; E8923DA3116AB2820071B552 /* a2a3-blocktramps-i386.s in Sources */ = {isa = PBXBuildFile; fileRef = E8923D9E116AB2820071B552 /* a2a3-blocktramps-i386.s */; }; @@ -122,13 +118,11 @@ /* Begin PBXFileReference section */ 393CEABF0DC69E3E000B69DE /* objc-references.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = "objc-references.mm"; path = "runtime/objc-references.mm"; sourceTree = ""; }; 393CEAC50DC69E67000B69DE /* objc-references.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "objc-references.h"; path = "runtime/objc-references.h"; sourceTree = ""; }; - 399BC72D1224831B007FBDF0 /* objc-externalref.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = "objc-externalref.mm"; path = "runtime/objc-externalref.mm"; sourceTree = ""; }; 39ABD71F12F0B61800D1054C /* objc-weak.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "objc-weak.h"; path = "runtime/objc-weak.h"; sourceTree = ""; }; 39ABD72012F0B61800D1054C /* objc-weak.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = "objc-weak.mm"; path = "runtime/objc-weak.mm"; sourceTree = ""; }; 830F2A690D737FB800392440 /* objc-msg-arm.s */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.asm; name = "objc-msg-arm.s"; path = "runtime/Messengers.subproj/objc-msg-arm.s"; sourceTree = ""; }; 830F2A6A0D737FB800392440 /* objc-msg-i386.s */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.asm; name = "objc-msg-i386.s"; path = "runtime/Messengers.subproj/objc-msg-i386.s"; sourceTree = ""; }; 830F2A720D737FB800392440 /* objc-msg-x86_64.s */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.asm; name = "objc-msg-x86_64.s"; path = "runtime/Messengers.subproj/objc-msg-x86_64.s"; sourceTree = ""; tabWidth = 8; usesTabs = 1; }; - 830F2A920D73876100392440 /* objc-accessors.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "objc-accessors.h"; path = "runtime/objc-accessors.h"; sourceTree = ""; }; 830F2A930D73876100392440 /* objc-accessors.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = "objc-accessors.mm"; path = "runtime/objc-accessors.mm"; sourceTree = ""; }; 830F2A970D738DC200392440 /* hashtable.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = hashtable.h; path = runtime/hashtable.h; sourceTree = ""; }; 830F2AA50D7394C200392440 /* markgc.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = markgc.cpp; sourceTree = ""; }; @@ -203,8 +197,6 @@ 83F550DF155E030800E95D3B /* objc-cache-old.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = "objc-cache-old.mm"; path = "runtime/objc-cache-old.mm"; sourceTree = ""; }; 87BB4E900EC39633005D08E1 /* objc-probes.d */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.dtrace; name = "objc-probes.d"; path = "runtime/objc-probes.d"; sourceTree = ""; }; 9672F7ED14D5F488007CEC96 /* NSObject.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = NSObject.mm; path = runtime/NSObject.mm; sourceTree = ""; }; - BC07A00B0EF72D360014EC61 /* objc-auto-dump.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "objc-auto-dump.h"; path = "runtime/objc-auto-dump.h"; sourceTree = ""; }; - BC07A0100EF72D9C0014EC61 /* objc-auto-dump.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = "objc-auto-dump.mm"; path = "runtime/objc-auto-dump.mm"; sourceTree = ""; }; BC8B5D1212D3D48100C78A5B /* libauto.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libauto.dylib; path = /usr/lib/libauto.dylib; sourceTree = ""; }; D2AAC0630554660B00DB518D /* libobjc.A.dylib */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.dylib"; includeInIndex = 0; path = libobjc.A.dylib; sourceTree = BUILT_PRODUCTS_DIR; }; E8923D9C116AB2820071B552 /* a1a2-blocktramps-i386.s */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.asm; name = "a1a2-blocktramps-i386.s"; path = "runtime/a1a2-blocktramps-i386.s"; sourceTree = ""; }; @@ -252,7 +244,6 @@ 838486190D6D68A800CEA253 /* Protocol.mm */, 830F2A930D73876100392440 /* objc-accessors.mm */, 838485CA0D6D68A200CEA253 /* objc-auto.mm */, - BC07A0100EF72D9C0014EC61 /* objc-auto-dump.mm */, 39ABD72012F0B61800D1054C /* objc-weak.mm */, E8923DA0116AB2820071B552 /* objc-block-trampolines.mm */, 838485CB0D6D68A200CEA253 /* objc-cache.mm */, @@ -261,7 +252,6 @@ 838485CE0D6D68A200CEA253 /* objc-class.mm */, 838485D00D6D68A200CEA253 /* objc-errors.mm */, 838485D20D6D68A200CEA253 /* objc-exception.mm */, - 399BC72D1224831B007FBDF0 /* objc-externalref.mm */, 838485D30D6D68A200CEA253 /* objc-file.mm */, 83BE02E30FCCB23400661494 /* objc-file-old.mm */, 838485D50D6D68A200CEA253 /* objc-initialize.mm */, @@ -338,7 +328,6 @@ 83112ED30F00599600A5FBAF /* objc-internal.h */, 834EC0A311614167009B2563 /* objc-abi.h */, 838485BB0D6D687300CEA253 /* maptable.h */, - BC07A00B0EF72D360014EC61 /* objc-auto-dump.h */, 834266D70E665A8B002E4DA2 /* objc-gdb.h */, ); name = "Private Headers"; @@ -371,7 +360,6 @@ 8384862A0D6D6ABC00CEA253 /* Project Headers */ = { isa = PBXGroup; children = ( - 830F2A920D73876100392440 /* objc-accessors.h */, 838485CF0D6D68A200CEA253 /* objc-config.h */, 83BE02E60FCCB24D00661494 /* objc-file.h */, 83BE02E50FCCB24D00661494 /* objc-file-old.h */, @@ -401,9 +389,7 @@ 838485C30D6D687300CEA253 /* maptable.h in Headers */, 838486280D6D6A2400CEA253 /* message.h in Headers */, 834EC0A411614167009B2563 /* objc-abi.h in Headers */, - 830F2A940D73876100392440 /* objc-accessors.h in Headers */, 838485EF0D6D68A200CEA253 /* objc-api.h in Headers */, - BC07A00C0EF72D360014EC61 /* objc-auto-dump.h in Headers */, 838485F00D6D68A200CEA253 /* objc-auto.h in Headers */, 838485F40D6D68A200CEA253 /* objc-class.h in Headers */, 838485F60D6D68A200CEA253 /* objc-config.h in Headers */, @@ -493,7 +479,7 @@ 830F2AB60D739AB600392440 /* Run Script (markgc) */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; - comments = "Set the GC-supported bit in libobjc itself.\n\nlibobjc cannot be built with -fobjc-gc, because it needs more precise control over write-barrier use.\n\nThis is done on all architectures and platforms, even though some don't actually support GC. In those cases, a program that actually tries to use GC will fail with link errors."; + comments = "Modify the built dylib (mod_init_funcs and mod_term_funcs)."; files = ( ); inputPaths = ( @@ -503,7 +489,7 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "set -x\n/usr/bin/xcrun -toolchain XcodeDefault -sdk macosx clang++ -Wall -mmacosx-version-min=10.9 -arch x86_64 -std=c++11 \"${SRCROOT}/markgc.cpp\" -o \"${BUILT_PRODUCTS_DIR}/markgc\"\n\"${BUILT_PRODUCTS_DIR}/markgc\" \"${BUILT_PRODUCTS_DIR}/libobjc.A.dylib\""; + shellScript = "set -x\n/usr/bin/xcrun -sdk macosx clang++ -Wall -mmacosx-version-min=10.9 -arch x86_64 -std=c++11 \"${SRCROOT}/markgc.cpp\" -o \"${BUILT_PRODUCTS_DIR}/markgc\"\n\"${BUILT_PRODUCTS_DIR}/markgc\" \"${BUILT_PRODUCTS_DIR}/libobjc.A.dylib\""; }; 830F2AFA0D73BC5800392440 /* Run Script (symlink) */ = { isa = PBXShellScriptBuildPhase; @@ -557,7 +543,6 @@ 393CEAC00DC69E3E000B69DE /* objc-references.mm in Sources */, 831C85D60E10CF850066E64C /* objc-os.mm in Sources */, 87BB4EA70EC39854005D08E1 /* objc-probes.d in Sources */, - BC07A0110EF72D9C0014EC61 /* objc-auto-dump.mm in Sources */, 83BE02E40FCCB23400661494 /* objc-file-old.mm in Sources */, E8923DA1116AB2820071B552 /* a1a2-blocktramps-i386.s in Sources */, E8923DA2116AB2820071B552 /* a1a2-blocktramps-x86_64.s in Sources */, @@ -568,7 +553,6 @@ 83EB007B121C9EC200B92C16 /* objc-sel-table.s in Sources */, 8383A3A3122600E9009290B8 /* a1a2-blocktramps-arm.s in Sources */, 8383A3A4122600E9009290B8 /* a2a3-blocktramps-arm.s in Sources */, - 399BC72E1224831B007FBDF0 /* objc-externalref.mm in Sources */, 39ABD72412F0B61800D1054C /* objc-weak.mm in Sources */, 83D49E4F13C7C84F0057F1DD /* objc-msg-arm64.s in Sources */, 8379996E13CBAF6F007C2B5F /* a1a2-blocktramps-arm64.s in Sources */, @@ -600,13 +584,13 @@ EXECUTABLE_PREFIX = lib; GCC_CW_ASM_SYNTAX = NO; GCC_OPTIMIZATION_LEVEL = 0; - GCC_THREADSAFE_STATICS = NO; GCC_WARN_ABOUT_DEPRECATED_FUNCTIONS = NO; HEADER_SEARCH_PATHS = ( "$(DSTROOT)/usr/include/**", "$(DSTROOT)/usr/local/include/**", "$(CONFIGURATION_BUILD_DIR)/usr/include/**", "$(CONFIGURATION_BUILD_DIR)/usr/local/include/**", + /System/Library/Frameworks/System.framework/PrivateHeaders, ); INSTALL_PATH = /usr/lib; ORDER_FILE = "$(SDKROOT)/AppleInternal/OrderFiles/libobjc.order"; @@ -654,13 +638,13 @@ DYLIB_CURRENT_VERSION = 228; EXECUTABLE_PREFIX = lib; GCC_CW_ASM_SYNTAX = NO; - GCC_THREADSAFE_STATICS = NO; GCC_WARN_ABOUT_DEPRECATED_FUNCTIONS = NO; HEADER_SEARCH_PATHS = ( "$(DSTROOT)/usr/include/**", "$(DSTROOT)/usr/local/include/**", "$(CONFIGURATION_BUILD_DIR)/usr/include/**", "$(CONFIGURATION_BUILD_DIR)/usr/local/include/**", + /System/Library/Frameworks/System.framework/PrivateHeaders, ); INSTALL_PATH = /usr/lib; ORDER_FILE = "$(SDKROOT)/AppleInternal/OrderFiles/libobjc.order"; @@ -710,6 +694,7 @@ CLANG_LINK_OBJC_RUNTIME = NO; CLANG_OBJC_RUNTIME = NO; DEBUG_INFORMATION_FORMAT = dwarf; + EXCLUDED_INSTALLSRC_SUBDIRECTORY_PATTERNS = "$(inherited) test"; GCC_ENABLE_CPP_EXCEPTIONS = NO; GCC_ENABLE_CPP_RTTI = NO; GCC_INLINES_ARE_PRIVATE_EXTERN = YES; @@ -750,6 +735,7 @@ CLANG_LINK_OBJC_RUNTIME = NO; CLANG_OBJC_RUNTIME = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + EXCLUDED_INSTALLSRC_SUBDIRECTORY_PATTERNS = "$(inherited) test"; GCC_ENABLE_CPP_EXCEPTIONS = NO; GCC_ENABLE_CPP_RTTI = NO; GCC_INLINES_ARE_PRIVATE_EXTERN = YES; diff --git a/prebuild.bat b/prebuild.bat old mode 100644 new mode 100755 diff --git a/runtime/Messengers.subproj/objc-msg-arm.s b/runtime/Messengers.subproj/objc-msg-arm.s index 142e285..bd88c6a 100644 --- a/runtime/Messengers.subproj/objc-msg-arm.s +++ b/runtime/Messengers.subproj/objc-msg-arm.s @@ -67,36 +67,57 @@ #endif +// Define SUPPORT_INDEXED_ISA for targets which store the class in the ISA as +// an index in to a class table. +// Note, keep this in sync with objc-config.h. +// FIXME: Remove this duplication. We should get this from objc-config.h. +#if __ARM_ARCH_7K__ >= 2 +# define SUPPORT_INDEXED_ISA 1 +#else +# define SUPPORT_INDEXED_ISA 0 +#endif + +// Note, keep these in sync with objc-private.h +#define ISA_INDEX_IS_NPI 1 +#define ISA_INDEX_MASK 0x0001FFFC +#define ISA_INDEX_SHIFT 2 +#define ISA_INDEX_BITS 15 +#define ISA_INDEX_COUNT (1 << ISA_INDEX_BITS) +#define ISA_INDEX_MAGIC_MASK 0x001E0001 +#define ISA_INDEX_MAGIC_VALUE 0x001C0001 + .syntax unified #define MI_EXTERN(var) \ .non_lazy_symbol_pointer ;\ -L ## var ## $$non_lazy_ptr: ;\ +L##var##$$non_lazy_ptr: ;\ .indirect_symbol var ;\ .long 0 #define MI_GET_EXTERN(reg,var) \ - movw reg, :lower16:(L##var##$$non_lazy_ptr-4f-4) ;\ - movt reg, :upper16:(L##var##$$non_lazy_ptr-4f-4) ;\ -4: add reg, pc ;\ + movw reg, :lower16:(L##var##$$non_lazy_ptr-7f-4) ;\ + movt reg, :upper16:(L##var##$$non_lazy_ptr-7f-4) ;\ +7: add reg, pc ;\ ldr reg, [reg] - -#define MI_CALL_EXTERNAL(var) \ - MI_GET_EXTERN(r12,var) ;\ - blx r12 - #define MI_GET_ADDRESS(reg,var) \ - movw reg, :lower16:(var-4f-4) ;\ - movt reg, :upper16:(var-4f-4) ;\ -4: add reg, pc ;\ + movw reg, :lower16:(var-7f-4) ;\ + movt reg, :upper16:(var-7f-4) ;\ +7: add reg, pc ;\ -MI_EXTERN(__class_lookupMethodAndLoadCache3) -MI_EXTERN(___objc_error) +.data + +#if SUPPORT_INDEXED_ISA + + .align 2 + .globl _objc_indexed_classes +_objc_indexed_classes: + .fill ISA_INDEX_COUNT, 4, 0 + +#endif -.data // _objc_entryPoints and _objc_exitPoints are used by method dispatch // caching code to figure out whether any threads are actively @@ -113,17 +134,25 @@ _objc_entryPoints: .long _objc_msgSendSuper_stret .long _objc_msgSendSuper2 .long _objc_msgSendSuper2_stret + .long _objc_msgLookup + .long _objc_msgLookup_stret + .long _objc_msgLookupSuper2 + .long _objc_msgLookupSuper2_stret .long 0 .private_extern _objc_exitPoints _objc_exitPoints: - .long LGetImpExit - .long LMsgSendExit - .long LMsgSendStretExit - .long LMsgSendSuperExit - .long LMsgSendSuperStretExit - .long LMsgSendSuper2Exit - .long LMsgSendSuper2StretExit + .long LExit_cache_getImp + .long LExit_objc_msgSend + .long LExit_objc_msgSend_stret + .long LExit_objc_msgSendSuper + .long LExit_objc_msgSendSuper_stret + .long LExit_objc_msgSendSuper2 + .long LExit_objc_msgSendSuper2_stret + .long LExit_objc_msgLookup + .long LExit_objc_msgLookup_stret + .long LExit_objc_msgLookupSuper2 + .long LExit_objc_msgLookupSuper2_stret .long 0 @@ -154,30 +183,30 @@ _gdb_objc_messenger_breakpoints: // contents populated by the macros below .macro MESSENGER_START -4: +7: .section __DATA,__objc_msg_break - .long 4b + .long 7b .long ENTER .text .endmacro .macro MESSENGER_END_FAST -4: +7: .section __DATA,__objc_msg_break - .long 4b + .long 7b .long FAST_EXIT .text .endmacro .macro MESSENGER_END_SLOW -4: +7: .section __DATA,__objc_msg_break - .long 4b + .long 7b .long SLOW_EXIT .text .endmacro .macro MESSENGER_END_NIL -4: +7: .section __DATA,__objc_msg_break - .long 4b + .long 7b .long NIL_EXIT .text .endmacro @@ -186,11 +215,11 @@ _gdb_objc_messenger_breakpoints: /******************************************************************** * Names for relative labels * DO NOT USE THESE LABELS ELSEWHERE - * Reserved labels: 8: 9: + * Reserved labels: 6: 7: 8: 9: ********************************************************************/ -#define LCacheMiss 8 -#define LCacheMiss_f 8f -#define LCacheMiss_b 8b +// 6: used by CacheLookup +// 7: used by MI_GET_ADDRESS etc and MESSENGER_START etc +// 8: used by CacheLookup #define LNilReceiver 9 #define LNilReceiver_f 9f #define LNilReceiver_b 9b @@ -201,14 +230,7 @@ _gdb_objc_messenger_breakpoints: ********************************************************************/ #define NORMAL 0 -#define FPRET 1 -#define FP2RET 2 -#define GETIMP 3 -#define STRET 4 -#define SUPER 5 -#define SUPER2 6 -#define SUPER_STRET 7 -#define SUPER2_STRET 8 +#define STRET 1 /******************************************************************** @@ -246,18 +268,18 @@ _gdb_objc_messenger_breakpoints: .text .thumb .align 5 - .globl _$0 + .globl $0 .thumb_func -_$0: +$0: .endmacro .macro STATIC_ENTRY /*name*/ .text .thumb .align 5 - .private_extern _$0 + .private_extern $0 .thumb_func -_$0: +$0: .endmacro @@ -272,110 +294,94 @@ _$0: ////////////////////////////////////////////////////////////////////// .macro END_ENTRY /* name */ +LExit$0: .endmacro ///////////////////////////////////////////////////////////////////// // -// CacheLookup return-type +// CacheLookup NORMAL|STRET +// CacheLookup2 NORMAL|STRET // // Locate the implementation for a selector in a class's method cache. // // Takes: -// $0 = NORMAL, STRET, SUPER, SUPER_STRET, SUPER2, SUPER2_STRET, GETIMP +// $0 = NORMAL, STRET // r0 or r1 (STRET) = receiver // r1 or r2 (STRET) = selector // r9 = class to search in // -// On exit: r9 and r12 clobbered -// (found) calls or returns IMP, eq/ne/r9 set for forwarding -// (not found) jumps to LCacheMiss +// On exit: r9 clobbered +// (found) continues after CacheLookup, IMP in r12, eq set +// (not found) continues after CacheLookup2 // ///////////////////////////////////////////////////////////////////// -.macro CacheHit - -.if $0 == GETIMP - ldr r0, [r9, #4] // r0 = bucket->imp - MI_GET_ADDRESS(r1, __objc_msgSend_uncached_impcache) - teq r0, r1 - it eq - moveq r0, #0 // don't return msgSend_uncached - bx lr // return imp -.elseif $0 == NORMAL - ldr r12, [r9, #4] // r12 = bucket->imp - // eq already set for nonstret forward - MESSENGER_END_FAST - bx r12 // call imp -.elseif $0 == STRET - ldr r12, [r9, #4] // r12 = bucket->imp - movs r9, #1 // r9=1, Z=0 for stret forwarding - MESSENGER_END_FAST - bx r12 // call imp -.elseif $0 == SUPER - ldr r12, [r9, #4] // r12 = bucket->imp - ldr r9, [r0, #CLASS] // r9 = class to search for forwarding - ldr r0, [r0, #RECEIVER] // fetch real receiver - tst r12, r12 // set ne for forwarding (r12!=0) - MESSENGER_END_FAST - bx r12 // call imp -.elseif $0 == SUPER2 - ldr r12, [r9, #4] // r12 = bucket->imp - ldr r9, [r0, #CLASS] - ldr r9, [r9, #SUPERCLASS] // r9 = class to search for forwarding - ldr r0, [r0, #RECEIVER] // fetch real receiver - tst r12, r12 // set ne for forwarding (r12!=0) - MESSENGER_END_FAST - bx r12 // call imp -.elseif $0 == SUPER_STRET - ldr r12, [r9, #4] // r12 = bucket->imp - ldr r9, [r1, #CLASS] // r9 = class to search for forwarding - orr r9, r9, #1 // r9 = class|1 for super_stret forward - ldr r1, [r1, #RECEIVER] // fetch real receiver - tst r12, r12 // set ne for forwarding (r12!=0) - MESSENGER_END_FAST - bx r12 // call imp -.elseif $0 == SUPER2_STRET - ldr r12, [r9, #4] // r12 = bucket->imp - ldr r9, [r1, #CLASS] // r9 = class to search for forwarding - ldr r9, [r9, #SUPERCLASS] // r9 = class to search for forwarding - orr r9, r9, #1 // r9 = class|1 for super_stret forward - ldr r1, [r1, #RECEIVER] // fetch real receiver - tst r12, r12 // set ne for forwarding (r12!=0) - MESSENGER_END_FAST - bx r12 // call imp -.else -.abort oops -.endif - -.endmacro - .macro CacheLookup ldrh r12, [r9, #CACHE_MASK] // r12 = mask ldr r9, [r9, #CACHE] // r9 = buckets -.if $0 == STRET || $0 == SUPER_STRET +.if $0 == STRET and r12, r12, r2 // r12 = index = SEL & mask .else and r12, r12, r1 // r12 = index = SEL & mask .endif add r9, r9, r12, LSL #3 // r9 = bucket = buckets+index*8 ldr r12, [r9] // r12 = bucket->sel -2: -.if $0 == STRET || $0 == SUPER_STRET +6: +.if $0 == STRET teq r12, r2 .else teq r12, r1 .endif - bne 1f - CacheHit $0 -1: + bne 8f + ldr r12, [r9, #4] // r12 = bucket->imp + +.if $0 == STRET + tst r12, r12 // set ne for stret forwarding +.else + // eq already set for nonstret forwarding by `teq` above +.endif + +.endmacro + +.macro CacheLookup2 + +8: cmp r12, #1 - blo LCacheMiss_f // if (bucket->sel == 0) cache miss + blo 8f // if (bucket->sel == 0) cache miss it eq // if (bucket->sel == 1) cache wrap ldreq r9, [r9, #4] // bucket->imp is before first bucket ldr r12, [r9, #8]! // r12 = (++bucket)->sel - b 2b + b 6b +8: + +.endmacro + +///////////////////////////////////////////////////////////////////// +// +// GetClassFromIsa return-type +// +// Given an Isa, return the class for the Isa. +// +// Takes: +// r9 = class +// +// On exit: r12 clobbered +// r9 contains the class for this Isa. +// +///////////////////////////////////////////////////////////////////// +.macro GetClassFromIsa + +#if SUPPORT_INDEXED_ISA + // Note: We are doing a little wasted work here to load values we might not + // need. Branching turns out to be even worse when performance was measured. + MI_GET_ADDRESS(r12, _objc_indexed_classes) + tst.w r9, #ISA_INDEX_IS_NPI + itt ne + ubfxne r9, r9, #ISA_INDEX_SHIFT, #ISA_INDEX_BITS + ldrne.w r9, [r12, r9, lsl #2] +#endif .endmacro @@ -390,37 +396,51 @@ _$0: * If not found, returns NULL. ********************************************************************/ - STATIC_ENTRY cache_getImp + STATIC_ENTRY _cache_getImp mov r9, r0 - CacheLookup GETIMP // returns IMP on success + CacheLookup NORMAL + // cache hit, IMP in r12 + mov r0, r12 + bx lr // return imp -LCacheMiss: - mov r0, #0 // return nil if cache miss + CacheLookup2 GETIMP + // cache miss, return nil + mov r0, #0 bx lr -LGetImpExit: - END_ENTRY cache_getImp + END_ENTRY _cache_getImp /******************************************************************** * - * id objc_msgSend(id self, SEL _cmd,...); + * id objc_msgSend(id self, SEL _cmd, ...); + * IMP objc_msgLookup(id self, SEL _cmd, ...); + * + * objc_msgLookup ABI: + * IMP returned in r12 + * Forwarding returned in Z flag + * r9 reserved for our use but not used * ********************************************************************/ - ENTRY objc_msgSend + ENTRY _objc_msgSend MESSENGER_START cbz r0, LNilReceiver_f ldr r9, [r0] // r9 = self->isa + GetClassFromIsa // r9 = class CacheLookup NORMAL - // calls IMP or LCacheMiss + // cache hit, IMP in r12, eq already set for nonstret forwarding + MESSENGER_END_FAST + bx r12 // call imp -LCacheMiss: + CacheLookup2 NORMAL + // cache miss + ldr r9, [r0] // r9 = self->isa + GetClassFromIsa // r9 = class MESSENGER_END_SLOW - ldr r9, [r0, #ISA] // class = receiver->isa b __objc_msgSend_uncached LNilReceiver: @@ -432,24 +452,47 @@ LNilReceiver: MESSENGER_END_NIL bx lr -LMsgSendExit: - END_ENTRY objc_msgSend + END_ENTRY _objc_msgSend + + ENTRY _objc_msgLookup -/******************************************************************** - * id objc_msgSend_noarg(id self, SEL op) - * - * On entry: r0 is the message receiver, - * r1 is the selector - ********************************************************************/ + cbz r0, LNilReceiver_f - ENTRY objc_msgSend_noarg - b _objc_msgSend - END_ENTRY objc_msgSend_noarg + ldr r9, [r0] // r9 = self->isa + GetClassFromIsa // r9 = class + CacheLookup NORMAL + // cache hit, IMP in r12, eq already set for nonstret forwarding + bx lr + + CacheLookup2 NORMAL + // cache miss + ldr r9, [r0] // r9 = self->isa + GetClassFromIsa // r9 = class + b __objc_msgLookup_uncached + +LNilReceiver: + MI_GET_ADDRESS(r12, __objc_msgNil) + bx lr + + END_ENTRY _objc_msgLookup + + + STATIC_ENTRY __objc_msgNil + + // r0 is already zero + mov r1, #0 + mov r2, #0 + mov r3, #0 + FP_RETURN_ZERO + bx lr + + END_ENTRY __objc_msgNil /******************************************************************** * void objc_msgSend_stret(void *st_addr, id self, SEL op, ...); + * IMP objc_msgLookup_stret(void *st_addr, id self, SEL op, ...); * * objc_msgSend_stret is the struct-return form of msgSend. * The ABI calls for r0 to be used as the address of the structure @@ -460,26 +503,60 @@ LMsgSendExit: * r2 is the selector ********************************************************************/ - ENTRY objc_msgSend_stret + ENTRY _objc_msgSend_stret MESSENGER_START cbz r1, LNilReceiver_f ldr r9, [r1] // r9 = self->isa + GetClassFromIsa // r9 = class CacheLookup STRET - // calls IMP or LCacheMiss + // cache hit, IMP in r12, ne already set for stret forwarding + MESSENGER_END_FAST + bx r12 -LCacheMiss: - MESSENGER_END_SLOW + CacheLookup2 STRET + // cache miss ldr r9, [r1] // r9 = self->isa + GetClassFromIsa // r9 = class + MESSENGER_END_SLOW b __objc_msgSend_stret_uncached LNilReceiver: MESSENGER_END_NIL bx lr -LMsgSendStretExit: - END_ENTRY objc_msgSend_stret + END_ENTRY _objc_msgSend_stret + + + ENTRY _objc_msgLookup_stret + + cbz r1, LNilReceiver_f + + ldr r9, [r1] // r9 = self->isa + GetClassFromIsa // r9 = class + CacheLookup STRET + // cache hit, IMP in r12, ne already set for stret forwarding + bx lr + + CacheLookup2 STRET + // cache miss + ldr r9, [r1] // r9 = self->isa + GetClassFromIsa // r9 = class + b __objc_msgLookup_stret_uncached + +LNilReceiver: + MI_GET_ADDRESS(r12, __objc_msgNil_stret) + bx lr + + END_ENTRY _objc_msgLookup_stret + + + STATIC_ENTRY __objc_msgNil_stret + + bx lr + + END_ENTRY __objc_msgNil_stret /******************************************************************** @@ -491,21 +568,24 @@ LMsgSendStretExit: * } ********************************************************************/ - ENTRY objc_msgSendSuper + ENTRY _objc_msgSendSuper MESSENGER_START ldr r9, [r0, #CLASS] // r9 = struct super->class - CacheLookup SUPER - // calls IMP or LCacheMiss + CacheLookup NORMAL + // cache hit, IMP in r12, eq already set for nonstret forwarding + ldr r0, [r0, #RECEIVER] // load real receiver + MESSENGER_END_FAST + bx r12 // call imp -LCacheMiss: - MESSENGER_END_SLOW + CacheLookup2 NORMAL + // cache miss ldr r9, [r0, #CLASS] // r9 = struct super->class ldr r0, [r0, #RECEIVER] // load real receiver + MESSENGER_END_SLOW b __objc_msgSend_uncached -LMsgSendSuperExit: - END_ENTRY objc_msgSendSuper + END_ENTRY _objc_msgSendSuper /******************************************************************** @@ -517,23 +597,45 @@ LMsgSendSuperExit: * } ********************************************************************/ - ENTRY objc_msgSendSuper2 + ENTRY _objc_msgSendSuper2 MESSENGER_START ldr r9, [r0, #CLASS] // class = struct super->class - ldr r9, [r9, #SUPERCLASS] // class = class->superclass - CacheLookup SUPER2 - // calls IMP or LCacheMiss + ldr r9, [r9, #SUPERCLASS] // class = class->superclass + CacheLookup NORMAL + // cache hit, IMP in r12, eq already set for nonstret forwarding + ldr r0, [r0, #RECEIVER] // load real receiver + MESSENGER_END_FAST + bx r12 // call imp -LCacheMiss: - MESSENGER_END_SLOW + CacheLookup2 NORMAL + // cache miss ldr r9, [r0, #CLASS] // class = struct super->class - ldr r9, [r9, #SUPERCLASS] // class = class->superclass + ldr r9, [r9, #SUPERCLASS] // class = class->superclass ldr r0, [r0, #RECEIVER] // load real receiver + MESSENGER_END_SLOW b __objc_msgSend_uncached -LMsgSendSuper2Exit: - END_ENTRY objc_msgSendSuper2 + END_ENTRY _objc_msgSendSuper2 + + + ENTRY _objc_msgLookupSuper2 + + ldr r9, [r0, #CLASS] // class = struct super->class + ldr r9, [r9, #SUPERCLASS] // class = class->superclass + CacheLookup NORMAL + // cache hit, IMP in r12, eq already set for nonstret forwarding + ldr r0, [r0, #RECEIVER] // load real receiver + bx lr + + CacheLookup2 NORMAL + // cache miss + ldr r9, [r0, #CLASS] + ldr r9, [r9, #SUPERCLASS] // r9 = class to search + ldr r0, [r0, #RECEIVER] // load real receiver + b __objc_msgLookup_uncached + + END_ENTRY _objc_msgLookupSuper2 /******************************************************************** @@ -548,132 +650,171 @@ LMsgSendSuper2Exit: * r2 is the selector ********************************************************************/ - ENTRY objc_msgSendSuper_stret + ENTRY _objc_msgSendSuper_stret MESSENGER_START - ldr r9, [r1, #CLASS] // r9 = struct super->class - CacheLookup SUPER_STRET - // calls IMP or LCacheMiss + ldr r9, [r1, #CLASS] // r9 = struct super->class + CacheLookup STRET + // cache hit, IMP in r12, ne already set for stret forwarding + ldr r1, [r1, #RECEIVER] // load real receiver + MESSENGER_END_FAST + bx r12 // call imp -LCacheMiss: + CacheLookup2 STRET + // cache miss + ldr r9, [r1, #CLASS] // r9 = struct super->class + ldr r1, [r1, #RECEIVER] // load real receiver MESSENGER_END_SLOW - ldr r9, [r1, #CLASS] // r9 = struct super->class - ldr r1, [r1, #RECEIVER] // load real receiver b __objc_msgSend_stret_uncached -LMsgSendSuperStretExit: - END_ENTRY objc_msgSendSuper_stret + END_ENTRY _objc_msgSendSuper_stret /******************************************************************** * id objc_msgSendSuper2_stret ********************************************************************/ - ENTRY objc_msgSendSuper2_stret + ENTRY _objc_msgSendSuper2_stret MESSENGER_START - ldr r9, [r1, #CLASS] // class = struct super->class - ldr r9, [r9, #SUPERCLASS] // class = class->superclass - CacheLookup SUPER2_STRET + ldr r9, [r1, #CLASS] // class = struct super->class + ldr r9, [r9, #SUPERCLASS] // class = class->superclass + CacheLookup STRET + // cache hit, IMP in r12, ne already set for stret forwarding + ldr r1, [r1, #RECEIVER] // load real receiver + MESSENGER_END_FAST + bx r12 // call imp -LCacheMiss: - MESSENGER_END_SLOW - ldr r9, [r1, #CLASS] // class = struct super->class - ldr r9, [r9, #SUPERCLASS] // class = class->superclass + CacheLookup2 STRET + // cache miss + ldr r9, [r1, #CLASS] // class = struct super->class + ldr r9, [r9, #SUPERCLASS] // class = class->superclass ldr r1, [r1, #RECEIVER] // load real receiver + MESSENGER_END_SLOW b __objc_msgSend_stret_uncached -LMsgSendSuper2StretExit: - END_ENTRY objc_msgSendSuper2_stret + END_ENTRY _objc_msgSendSuper2_stret + + + ENTRY _objc_msgLookupSuper2_stret + + ldr r9, [r1, #CLASS] // class = struct super->class + ldr r9, [r9, #SUPERCLASS] // class = class->superclass + CacheLookup STRET + // cache hit, IMP in r12, ne already set for stret forwarding + ldr r1, [r1, #RECEIVER] // load real receiver + bx lr + + CacheLookup2 STRET + // cache miss + ldr r9, [r1, #CLASS] + ldr r9, [r9, #SUPERCLASS] // r9 = class to search + ldr r1, [r1, #RECEIVER] // load real receiver + b __objc_msgLookup_stret_uncached + + END_ENTRY _objc_msgLookupSuper2_stret + + +///////////////////////////////////////////////////////////////////// +// +// MethodTableLookup NORMAL|STRET +// +// Locate the implementation for a selector in a class's method lists. +// +// Takes: +// $0 = NORMAL, STRET +// r0 or r1 (STRET) = receiver +// r1 or r2 (STRET) = selector +// r9 = class to search in +// +// On exit: IMP in r12, eq/ne set for forwarding +// +///////////////////////////////////////////////////////////////////// + +.macro MethodTableLookup + + stmfd sp!, {r0-r3,r7,lr} + add r7, sp, #16 + sub sp, #8 // align stack + FP_SAVE + +.if $0 == NORMAL + // receiver already in r0 + // selector already in r1 +.else + mov r0, r1 // receiver + mov r1, r2 // selector +.endif + mov r2, r9 // class to search + + blx __class_lookupMethodAndLoadCache3 + mov r12, r0 // r12 = IMP + +.if $0 == NORMAL + cmp r12, r12 // set eq for nonstret forwarding +.else + tst r12, r12 // set ne for stret forwarding +.endif + + FP_RESTORE + add sp, #8 // align stack + ldmfd sp!, {r0-r3,r7,lr} + +.endmacro /******************************************************************** - * - * _objc_msgSend_uncached_impcache - * Used to erase method cache entries in-place by - * bouncing them to the uncached lookup. * * _objc_msgSend_uncached * _objc_msgSend_stret_uncached - * The uncached lookup. + * _objc_msgLookup_uncached + * _objc_msgLookup_stret_uncached + * The uncached method lookup. * ********************************************************************/ - - STATIC_ENTRY _objc_msgSend_uncached_impcache - // Method cache version - // THIS IS NOT A CALLABLE C FUNCTION - // Out-of-band Z is 0 (EQ) for normal, 1 (NE) for stret and/or super - // Out-of-band r9 is 1 for stret, cls for super, cls|1 for super_stret - // Note objc_msgForward_impcache uses the same parameters + STATIC_ENTRY __objc_msgSend_uncached - MESSENGER_START - nop - MESSENGER_END_SLOW + // THIS IS NOT A CALLABLE C FUNCTION + // Out-of-band r9 is the class to search - ite eq - ldreq r9, [r0] // normal: r9 = class = self->isa - tstne r9, #1 // low bit clear? - beq __objc_msgSend_uncached // super: r9 is already the class - // stret or super_stret - eors r9, r9, #1 // clear low bit - it eq // r9 now zero? - ldreq r9, [r1] // stret: r9 = class = self->isa - // super_stret: r9 is already the class - b __objc_msgSend_stret_uncached + MethodTableLookup NORMAL // returns IMP in r12 + bx r12 - END_ENTRY _objc_msgSend_uncached_impcache + END_ENTRY __objc_msgSend_uncached - STATIC_ENTRY _objc_msgSend_uncached + STATIC_ENTRY __objc_msgSend_stret_uncached // THIS IS NOT A CALLABLE C FUNCTION // Out-of-band r9 is the class to search - stmfd sp!, {r0-r3,r7,lr} - add r7, sp, #16 - sub sp, #8 // align stack - FP_SAVE - // receiver already in r0 - // selector already in r1 - mov r2, r9 // class to search - - MI_CALL_EXTERNAL(__class_lookupMethodAndLoadCache3) - mov r12, r0 // r12 = IMP - - movs r9, #0 // r9=0, Z=1 for nonstret forwarding - FP_RESTORE - add sp, #8 // align stack - ldmfd sp!, {r0-r3,r7,lr} + MethodTableLookup STRET // returns IMP in r12 bx r12 + + END_ENTRY __objc_msgSend_stret_uncached - END_ENTRY _objc_msgSend_uncached - - - STATIC_ENTRY _objc_msgSend_stret_uncached + + STATIC_ENTRY __objc_msgLookup_uncached // THIS IS NOT A CALLABLE C FUNCTION // Out-of-band r9 is the class to search - stmfd sp!, {r0-r3,r7,lr} - add r7, sp, #16 - sub sp, #8 // align stack - FP_SAVE + MethodTableLookup NORMAL // returns IMP in r12 + bx lr - mov r0, r1 // receiver - mov r1, r2 // selector - mov r2, r9 // class to search + END_ENTRY __objc_msgLookup_uncached - MI_CALL_EXTERNAL(__class_lookupMethodAndLoadCache3) - mov r12, r0 // r12 = IMP - movs r9, #1 // r9=1, Z=0 for stret forwarding - FP_RESTORE - add sp, #8 // align stack - ldmfd sp!, {r0-r3,r7,lr} - bx r12 + STATIC_ENTRY __objc_msgLookup_stret_uncached + + // THIS IS NOT A CALLABLE C FUNCTION + // Out-of-band r9 is the class to search + + MethodTableLookup STRET // returns IMP in r12 + bx lr - END_ENTRY _objc_msgSend_stret_uncached + END_ENTRY __objc_msgLookup_stret_uncached /******************************************************************** @@ -690,85 +831,77 @@ LMsgSendSuper2StretExit: MI_EXTERN(__objc_forward_handler) MI_EXTERN(__objc_forward_stret_handler) - STATIC_ENTRY _objc_msgForward_impcache + STATIC_ENTRY __objc_msgForward_impcache // Method cache version // THIS IS NOT A CALLABLE C FUNCTION - // Out-of-band Z is 0 (EQ) for normal, 1 (NE) for stret and/or super - // Out-of-band r9 is 1 for stret, cls for super, cls|1 for super_stret - // Note _objc_msgSend_uncached_impcache uses the same parameters + // Out-of-band Z is 0 (EQ) for normal, 1 (NE) for stret MESSENGER_START nop MESSENGER_END_SLOW - it ne - tstne r9, #1 beq __objc_msgForward b __objc_msgForward_stret - END_ENTRY _objc_msgForward_impcache + END_ENTRY __objc_msgForward_impcache - ENTRY _objc_msgForward + ENTRY __objc_msgForward // Non-stret version MI_GET_EXTERN(r12, __objc_forward_handler) ldr r12, [r12] bx r12 - END_ENTRY _objc_msgForward + END_ENTRY __objc_msgForward - ENTRY _objc_msgForward_stret + ENTRY __objc_msgForward_stret // Struct-return version MI_GET_EXTERN(r12, __objc_forward_stret_handler) ldr r12, [r12] bx r12 - END_ENTRY _objc_msgForward_stret + END_ENTRY __objc_msgForward_stret - ENTRY objc_msgSend_debug + ENTRY _objc_msgSend_noarg + b _objc_msgSend + END_ENTRY _objc_msgSend_noarg + + ENTRY _objc_msgSend_debug b _objc_msgSend - END_ENTRY objc_msgSend_debug + END_ENTRY _objc_msgSend_debug - ENTRY objc_msgSendSuper2_debug + ENTRY _objc_msgSendSuper2_debug b _objc_msgSendSuper2 - END_ENTRY objc_msgSendSuper2_debug + END_ENTRY _objc_msgSendSuper2_debug - ENTRY objc_msgSend_stret_debug + ENTRY _objc_msgSend_stret_debug b _objc_msgSend_stret - END_ENTRY objc_msgSend_stret_debug + END_ENTRY _objc_msgSend_stret_debug - ENTRY objc_msgSendSuper2_stret_debug + ENTRY _objc_msgSendSuper2_stret_debug b _objc_msgSendSuper2_stret - END_ENTRY objc_msgSendSuper2_stret_debug + END_ENTRY _objc_msgSendSuper2_stret_debug - ENTRY method_invoke + ENTRY _method_invoke // r1 is method triplet instead of SEL ldr r12, [r1, #METHOD_IMP] ldr r1, [r1, #METHOD_NAME] bx r12 - END_ENTRY method_invoke + END_ENTRY _method_invoke - ENTRY method_invoke_stret + ENTRY _method_invoke_stret // r2 is method triplet instead of SEL ldr r12, [r2, #METHOD_IMP] ldr r2, [r2, #METHOD_NAME] bx r12 - END_ENTRY method_invoke_stret - - - STATIC_ENTRY _objc_ignored_method - - // self is already in a0 - bx lr - - END_ENTRY _objc_ignored_method + END_ENTRY _method_invoke_stret .section __DATA,__objc_msg_break diff --git a/runtime/Messengers.subproj/objc-msg-arm64.s b/runtime/Messengers.subproj/objc-msg-arm64.s index d2391e2..c24de97 100755 --- a/runtime/Messengers.subproj/objc-msg-arm64.s +++ b/runtime/Messengers.subproj/objc-msg-arm64.s @@ -45,6 +45,8 @@ _objc_entryPoints: .quad _objc_msgSend .quad _objc_msgSendSuper .quad _objc_msgSendSuper2 + .quad _objc_msgLookup + .quad _objc_msgLookupSuper2 .quad 0 .private_extern _objc_exitPoints @@ -53,6 +55,8 @@ _objc_exitPoints: .quad LExit_objc_msgSend .quad LExit_objc_msgSendSuper .quad LExit_objc_msgSendSuper2 + .quad LExit_objc_msgLookup + .quad LExit_objc_msgLookupSuper2 .quad 0 @@ -121,7 +125,7 @@ _gdb_objc_messenger_breakpoints: #define CACHE 16 /* Selected field offsets in isa field */ -#define ISA_MASK 0x00000001fffffff8 +#define ISA_MASK 0x0000000ffffffff8 /* Selected field offsets in method structure */ #define METHOD_NAME 0 @@ -154,69 +158,103 @@ LExit$0: .endmacro +/******************************************************************** + * UNWIND name, flags + * Unwind info generation + ********************************************************************/ +.macro UNWIND + .section __LD,__compact_unwind,regular,debug + .quad $0 + .set LUnwind$0, LExit$0 - $0 + .long LUnwind$0 + .long $1 + .quad 0 /* no personality */ + .quad 0 /* no LSDA */ + .text +.endmacro + +#define NoFrame 0x02000000 // no frame, no SP adjustment +#define FrameWithNoSaves 0x04000000 // frame, no non-volatile saves + + /******************************************************************** * - * CacheLookup NORMAL|GETIMP + * CacheLookup NORMAL|GETIMP|LOOKUP * * Locate the implementation for a selector in a class method cache. * * Takes: * x1 = selector - * x9 = class to be searched + * x16 = class to be searched * * Kills: - * x10,x11,x12, x16,x17 + * x9,x10,x11,x12, x17 * - * On exit: (found) exits CacheLookup - * with x9 = class, x17 = IMP + * On exit: (found) calls or returns IMP + * with x16 = class, x17 = IMP * (not found) jumps to LCacheMiss * ********************************************************************/ #define NORMAL 0 #define GETIMP 1 +#define LOOKUP 2 .macro CacheHit - MESSENGER_END_FAST .if $0 == NORMAL + MESSENGER_END_FAST br x17 // call imp +.elseif $0 == GETIMP + mov x0, x17 // return imp + ret +.elseif $0 == LOOKUP + ret // return imp via x17 .else - b LGetImpHit +.abort oops .endif .endmacro .macro CheckMiss -.if $0 == NORMAL // miss if bucket->cls == 0 - cbz x16, __objc_msgSend_uncached_impcache + // miss if bucket->sel == 0 +.if $0 == GETIMP + cbz x9, LGetImpMiss +.elseif $0 == NORMAL + cbz x9, __objc_msgSend_uncached +.elseif $0 == LOOKUP + cbz x9, __objc_msgLookup_uncached .else - cbz x16, LGetImpMiss +.abort oops .endif .endmacro .macro JumpMiss -.if $0 == NORMAL - b __objc_msgSend_uncached_impcache -.else +.if $0 == GETIMP b LGetImpMiss +.elseif $0 == NORMAL + b __objc_msgSend_uncached +.elseif $0 == LOOKUP + b __objc_msgLookup_uncached +.else +.abort oops .endif .endmacro .macro CacheLookup - // x1 = SEL, x9 = isa - ldp x10, x11, [x9, #CACHE] // x10 = buckets, x11 = occupied|mask + // x1 = SEL, x16 = isa + ldp x10, x11, [x16, #CACHE] // x10 = buckets, x11 = occupied|mask and w12, w1, w11 // x12 = _cmd & mask add x12, x10, x12, LSL #4 // x12 = buckets + ((_cmd & mask)<<4) - ldp x16, x17, [x12] // {x16, x17} = *bucket -1: cmp x16, x1 // if (bucket->sel != _cmd) + ldp x9, x17, [x12] // {x9, x17} = *bucket +1: cmp x9, x1 // if (bucket->sel != _cmd) b.ne 2f // scan more CacheHit $0 // call or return imp 2: // not hit: x12 = not-hit bucket - CheckMiss $0 // miss if bucket->cls == 0 + CheckMiss $0 // miss if bucket->sel == 0 cmp x12, x10 // wrap if bucket == buckets b.eq 3f - ldp x16, x17, [x12, #-16]! // {x16, x17} = *--bucket + ldp x9, x17, [x12, #-16]! // {x9, x17} = *--bucket b 1b // loop 3: // wrap: x12 = first bucket, w11 = mask @@ -225,16 +263,16 @@ LExit$0: // Clone scanning loop to miss instead of hang when cache is corrupt. // The slow path may detect any corruption and halt later. - ldp x16, x17, [x12] // {x16, x17} = *bucket -1: cmp x16, x1 // if (bucket->sel != _cmd) + ldp x9, x17, [x12] // {x9, x17} = *bucket +1: cmp x9, x1 // if (bucket->sel != _cmd) b.ne 2f // scan more CacheHit $0 // call or return imp 2: // not hit: x12 = not-hit bucket - CheckMiss $0 // miss if bucket->cls == 0 + CheckMiss $0 // miss if bucket->sel == 0 cmp x12, x10 // wrap if bucket == buckets b.eq 3f - ldp x16, x17, [x12, #-16]! // {x16, x17} = *--bucket + ldp x9, x17, [x12, #-16]! // {x9, x17} = *--bucket b 1b // loop 3: // double wrap @@ -243,19 +281,34 @@ LExit$0: .endmacro +/******************************************************************** + * + * id objc_msgSend(id self, SEL _cmd, ...); + * IMP objc_msgLookup(id self, SEL _cmd, ...); + * + * objc_msgLookup ABI: + * IMP returned in x17 + * x16 reserved for our use but not used + * + ********************************************************************/ + .data .align 3 .globl _objc_debug_taggedpointer_classes _objc_debug_taggedpointer_classes: .fill 16, 8, 0 + .globl _objc_debug_taggedpointer_ext_classes +_objc_debug_taggedpointer_ext_classes: + .fill 256, 8, 0 ENTRY _objc_msgSend + UNWIND _objc_msgSend, NoFrame MESSENGER_START cmp x0, #0 // nil check and tagged pointer check b.le LNilOrTagged // (MSB tagged pointer looks negative) ldr x13, [x0] // x13 = isa - and x9, x13, #ISA_MASK // x9 = class + and x16, x13, #ISA_MASK // x16 = class LGetIsaDone: CacheLookup NORMAL // calls imp or objc_msgSend_uncached @@ -263,12 +316,23 @@ LNilOrTagged: b.eq LReturnZero // nil check // tagged + mov x10, #0xf000000000000000 + cmp x0, x10 + b.hs LExtTag adrp x10, _objc_debug_taggedpointer_classes@PAGE add x10, x10, _objc_debug_taggedpointer_classes@PAGEOFF ubfx x11, x0, #60, #4 - ldr x9, [x10, x11, LSL #3] + ldr x16, [x10, x11, LSL #3] b LGetIsaDone +LExtTag: + // ext tagged + adrp x10, _objc_debug_taggedpointer_ext_classes@PAGE + add x10, x10, _objc_debug_taggedpointer_ext_classes@PAGEOFF + ubfx x11, x0, #52, #8 + ldr x16, [x10, x11, LSL #3] + b LGetIsaDone + LReturnZero: // x0 is already zero mov x1, #0 @@ -282,45 +346,95 @@ LReturnZero: END_ENTRY _objc_msgSend + ENTRY _objc_msgLookup + UNWIND _objc_msgLookup, NoFrame + + cmp x0, #0 // nil check and tagged pointer check + b.le LLookup_NilOrTagged // (MSB tagged pointer looks negative) + ldr x13, [x0] // x13 = isa + and x16, x13, #ISA_MASK // x16 = class +LLookup_GetIsaDone: + CacheLookup LOOKUP // returns imp + +LLookup_NilOrTagged: + b.eq LLookup_Nil // nil check + + // tagged + mov x10, #0xf000000000000000 + cmp x0, x10 + b.hs LLookup_ExtTag + adrp x10, _objc_debug_taggedpointer_classes@PAGE + add x10, x10, _objc_debug_taggedpointer_classes@PAGEOFF + ubfx x11, x0, #60, #4 + ldr x16, [x10, x11, LSL #3] + b LLookup_GetIsaDone + +LLookup_ExtTag: + adrp x10, _objc_debug_taggedpointer_ext_classes@PAGE + add x10, x10, _objc_debug_taggedpointer_ext_classes@PAGEOFF + ubfx x11, x0, #52, #8 + ldr x16, [x10, x11, LSL #3] + b LLookup_GetIsaDone + +LLookup_Nil: + adrp x17, __objc_msgNil@PAGE + add x17, x17, __objc_msgNil@PAGEOFF + ret + + END_ENTRY _objc_msgLookup + + + STATIC_ENTRY __objc_msgNil + + // x0 is already zero + mov x1, #0 + movi d0, #0 + movi d1, #0 + movi d2, #0 + movi d3, #0 + ret + + END_ENTRY __objc_msgNil + + ENTRY _objc_msgSendSuper + UNWIND _objc_msgSendSuper, NoFrame MESSENGER_START - ldr x9, [x0, #CLASS] // load class to search - ldr x0, [x0, #RECEIVER] // load real receiver + ldp x0, x16, [x0] // x0 = real receiver, x16 = class CacheLookup NORMAL // calls imp or objc_msgSend_uncached END_ENTRY _objc_msgSendSuper - + // no _objc_msgLookupSuper + ENTRY _objc_msgSendSuper2 + UNWIND _objc_msgSendSuper2, NoFrame MESSENGER_START - ldr x9, [x0, #CLASS] - ldr x9, [x9, #SUPERCLASS] // load class to search - ldr x0, [x0, #RECEIVER] // load real receiver + ldp x0, x16, [x0] // x0 = real receiver, x16 = class + ldr x16, [x16, #SUPERCLASS] // x16 = class->superclass CacheLookup NORMAL END_ENTRY _objc_msgSendSuper2 + + ENTRY _objc_msgLookupSuper2 + UNWIND _objc_msgLookupSuper2, NoFrame - ENTRY _objc_msgSend_noarg - b _objc_msgSend - END_ENTRY _objc_msgSend_noarg - + ldp x0, x16, [x0] // x0 = real receiver, x16 = class + ldr x16, [x16, #SUPERCLASS] // x16 = class->superclass + CacheLookup LOOKUP - STATIC_ENTRY __objc_msgSend_uncached_impcache + END_ENTRY _objc_msgLookupSuper2 - // THIS IS NOT A CALLABLE C FUNCTION - // Out-of-band x9 is the class to search - MESSENGER_START +.macro MethodTableLookup // push frame stp fp, lr, [sp, #-16]! mov fp, sp - MESSENGER_END_SLOW - // save parameter registers: x0..x8, q0..q7 sub sp, sp, #(10*8 + 8*16) stp q0, q1, [sp, #(0*16)] @@ -334,7 +448,7 @@ LReturnZero: str x8, [sp, #(8*16+8*8)] // receiver and selector already in x0 and x1 - mov x2, x9 + mov x2, x16 bl __class_lookupMethodAndLoadCache3 // imp in x0 @@ -353,59 +467,38 @@ LReturnZero: mov sp, fp ldp fp, lr, [sp], #16 + +.endmacro + + STATIC_ENTRY __objc_msgSend_uncached + UNWIND __objc_msgSend_uncached, FrameWithNoSaves + + // THIS IS NOT A CALLABLE C FUNCTION + // Out-of-band x16 is the class to search + MethodTableLookup br x17 - END_ENTRY __objc_msgSend_uncached_impcache - - -.section __LD,__compact_unwind,regular,debug - .quad _objc_msgSend - .set LUnwind_objc_msgSend, LExit_objc_msgSend-_objc_msgSend - .long LUnwind_objc_msgSend - .long 0x02000000 // no frame, no SP adjustment - .quad 0 // no personality - .quad 0 // no LSDA - -.section __LD,__compact_unwind,regular,debug - .quad _objc_msgSendSuper - .set LUnwind_objc_msgSendSuper, LExit_objc_msgSendSuper-_objc_msgSendSuper - .long LUnwind_objc_msgSendSuper - .long 0x02000000 // no frame, no SP adjustment - .quad 0 // no personality - .quad 0 // no LSDA - -.section __LD,__compact_unwind,regular,debug - .quad _objc_msgSendSuper2 - .set LUnwind_objc_msgSendSuper2, LExit_objc_msgSendSuper2-_objc_msgSendSuper2 - .long LUnwind_objc_msgSendSuper2 - .long 0x02000000 // no frame, no SP adjustment - .quad 0 // no personality - .quad 0 // no LSDA - -.section __LD,__compact_unwind,regular,debug - .quad __objc_msgSend_uncached_impcache - .set LUnwind__objc_msgSend_uncached_impcache, LExit__objc_msgSend_uncached_impcache-__objc_msgSend_uncached_impcache - .long LUnwind__objc_msgSend_uncached_impcache - .long 0x04000000 // frame, no non-volatile registers saved - .quad 0 // no personality - .quad 0 // no LSDA + END_ENTRY __objc_msgSend_uncached + + + STATIC_ENTRY __objc_msgLookup_uncached + UNWIND __objc_msgLookup_uncached, FrameWithNoSaves + + // THIS IS NOT A CALLABLE C FUNCTION + // Out-of-band x16 is the class to search + + MethodTableLookup + ret + + END_ENTRY __objc_msgLookup_uncached STATIC_ENTRY _cache_getImp - and x9, x0, #ISA_MASK + and x16, x0, #ISA_MASK CacheLookup GETIMP -LGetImpHit: - // imp in x17 - // don't return msgSend_uncached - adrp x16, __objc_msgSend_uncached_impcache@PAGE - add x16, x16, __objc_msgSend_uncached_impcache@PAGEOFF - cmp x16, x17 - csel x0, x17, xzr, ne // if imp!=uncached then imp else 0 - ret - LGetImpMiss: mov x0, #0 ret @@ -445,6 +538,10 @@ LGetImpMiss: END_ENTRY __objc_msgForward + ENTRY _objc_msgSend_noarg + b _objc_msgSend + END_ENTRY _objc_msgSend_noarg + ENTRY _objc_msgSend_debug b _objc_msgSend END_ENTRY _objc_msgSend_debug @@ -461,12 +558,4 @@ LGetImpMiss: br x17 END_ENTRY _method_invoke - - STATIC_ENTRY __objc_ignored_method - - // self is already in x0 - ret - - END_ENTRY __objc_ignored_method - #endif diff --git a/runtime/Messengers.subproj/objc-msg-i386.s b/runtime/Messengers.subproj/objc-msg-i386.s index 48f95f8..8a64ba0 100644 --- a/runtime/Messengers.subproj/objc-msg-i386.s +++ b/runtime/Messengers.subproj/objc-msg-i386.s @@ -22,7 +22,7 @@ */ #include -#if defined(__i386__) && !TARGET_IPHONE_SIMULATOR +#if defined(__i386__) && !TARGET_OS_SIMULATOR /******************************************************************** ******************************************************************** @@ -32,9 +32,6 @@ ******************************************************************** ********************************************************************/ -// for kIgnore -#include "objc-config.h" - /******************************************************************** * Data used by the ObjC runtime. @@ -603,10 +600,6 @@ LGetImpExit: movl selector(%esp), %ecx movl self(%esp), %eax -// check whether selector is ignored - cmpl $ kIgnore, %ecx - je LMsgSendDone // return self from %eax - // check whether receiver is nil testl %eax, %eax je LMsgSendNilSelf @@ -662,10 +655,6 @@ LMsgSendExit: movl selector(%esp), %ecx movl class(%eax), %edx // struct objc_super->class -// check whether selector is ignored - cmpl $ kIgnore, %ecx - je LMsgSendSuperIgnored // return self from %eax - // search the cache (class in %edx) CacheLookup WORD_RETURN, MSG_SENDSUPER, LMsgSendSuperCacheMiss xor %edx, %edx // set nonstret for msgForward_internal @@ -754,10 +743,6 @@ LMsgSendvArgsOK: movl selector(%esp), %ecx movl self(%esp), %eax -// check whether selector is ignored - cmpl $ kIgnore, %ecx - je LMsgSendFpretDone // return self from %eax - // check whether receiver is nil testl %eax, %eax je LMsgSendFpretNilSelf @@ -1171,14 +1156,6 @@ LMsgForwardStretError: END_ENTRY _method_invoke_stret - - STATIC_ENTRY __objc_ignored_method - - movl self(%esp), %eax - ret - - END_ENTRY __objc_ignored_method - .section __DATA,__objc_msg_break .long 0 diff --git a/runtime/Messengers.subproj/objc-msg-simulator-i386.s b/runtime/Messengers.subproj/objc-msg-simulator-i386.s index 12cc010..beb7ac5 100644 --- a/runtime/Messengers.subproj/objc-msg-simulator-i386.s +++ b/runtime/Messengers.subproj/objc-msg-simulator-i386.s @@ -22,7 +22,7 @@ */ #include -#if defined(__i386__) && TARGET_IPHONE_SIMULATOR +#if defined(__i386__) && TARGET_OS_SIMULATOR #include "objc-config.h" @@ -43,18 +43,28 @@ _objc_entryPoints: .long _objc_msgSendSuper2 .long _objc_msgSendSuper_stret .long _objc_msgSendSuper2_stret + .long _objc_msgLookup + .long _objc_msgLookup_fpret + .long _objc_msgLookup_stret + .long _objc_msgLookupSuper2 + .long _objc_msgLookupSuper2_stret .long 0 .private_extern _objc_exitPoints _objc_exitPoints: - .long LGetImpExit - .long LMsgSendExit - .long LMsgSendFpretExit - .long LMsgSendStretExit - .long LMsgSendSuperExit - .long LMsgSendSuper2Exit - .long LMsgSendSuperStretExit - .long LMsgSendSuper2StretExit + .long LExit_cache_getImp + .long LExit_objc_msgSend + .long LExit_objc_msgSend_fpret + .long LExit_objc_msgSend_stret + .long LExit_objc_msgSendSuper + .long LExit_objc_msgSendSuper2 + .long LExit_objc_msgSendSuper_stret + .long LExit_objc_msgSendSuper2_stret + .long LExit_objc_msgLookup + .long LExit_objc_msgLookup_fpret + .long LExit_objc_msgLookup_stret + .long LExit_objc_msgLookupSuper2 + .long LExit_objc_msgLookupSuper2_stret .long 0 @@ -139,12 +149,14 @@ _gdb_objc_messenger_breakpoints: * Macro parameters ********************************************************************/ + #define NORMAL 0 #define FPRET 1 -#define GETIMP 3 -#define STRET 4 -#define SUPER 5 -#define SUPER_STRET 6 +#define STRET 2 + +#define CALL 100 +#define GETIMP 101 +#define LOOKUP 102 /******************************************************************** @@ -196,7 +208,6 @@ _gdb_objc_messenger_breakpoints: .globl $0 .align 2, 0x90 $0: - .cfi_startproc .endmacro .macro STATIC_ENTRY @@ -204,7 +215,6 @@ $0: .private_extern $0 .align 4, 0x90 $0: - .cfi_startproc .endmacro ////////////////////////////////////////////////////////////////////// @@ -218,18 +228,38 @@ $0: ////////////////////////////////////////////////////////////////////// .macro END_ENTRY - .cfi_endproc +LExit$0: +.endmacro + + + /******************************************************************** + * UNWIND name, flags + * Unwind info generation + ********************************************************************/ +.macro UNWIND + .section __LD,__compact_unwind,regular,debug + .long $0 + .set LUnwind$0, LExit$0 - $0 + .long LUnwind$0 + .long $1 + .long 0 /* no personality */ + .long 0 /* no LSDA */ + .text .endmacro +#define NoFrame 0x02010000 // no frame, no SP adjustment except return address +#define FrameWithNoSaves 0x01000000 // frame, no non-volatile saves + ///////////////////////////////////////////////////////////////////// // -// CacheLookup return-type +// CacheLookup return-type, caller // // Locate the implementation for a selector in a class method cache. // // Takes: -// $0 = NORMAL, FPRET, STRET, SUPER, SUPER_STRET, GETIMP +// $0 = NORMAL, FPRET, STRET +// $1 = CALL, LOOKUP, GETIMP // ecx = selector to search for // edx = class to search // @@ -244,43 +274,34 @@ $0: // CacheHit must always be preceded by a not-taken `jne` instruction // in case the imp is _objc_msgForward_impcache. -.if $0 == GETIMP + // eax = found bucket + +.if $1 == GETIMP movl 4(%eax), %eax // return imp - call 4f -4: pop %edx - leal __objc_msgSend_uncached_impcache-4b(%edx), %edx - cmpl %edx, %eax - jne 4f - xor %eax, %eax // don't return msgSend_uncached -4: ret -.elseif $0 == NORMAL || $0 == FPRET + ret + +.else + +.if $0 != STRET // eq already set for forwarding by `jne` - MESSENGER_END_FAST - jmp *4(%eax) // call imp -.elseif $0 == STRET - test %eax, %eax // set ne for stret forwarding - MESSENGER_END_FAST - jmp *4(%eax) // call imp -.elseif $0 == SUPER - // replace "super" arg with "receiver" - movl super(%esp), %ecx // get super structure - movl receiver(%ecx), %ecx // get messaged object - movl %ecx, super(%esp) // make it the first argument - cmp %eax, %eax // set eq for non-stret forwarding - MESSENGER_END_FAST - jmp *4(%eax) // call imp -.elseif $0 == SUPER_STRET - // replace "super" arg with "receiver" - movl super_stret(%esp), %ecx // get super structure - movl receiver(%ecx), %ecx // get messaged object - movl %ecx, super_stret(%esp) // make it the first argument +.else test %eax, %eax // set ne for stret forwarding +.endif + +.if $1 == CALL MESSENGER_END_FAST jmp *4(%eax) // call imp + +.elseif $1 == LOOKUP + movl 4(%eax), %eax // return imp + ret + .else .abort oops .endif +.endif + .endmacro @@ -293,7 +314,7 @@ $0: cmpl (%eax), %ecx // if (bucket->sel != SEL) jne 1f // scan more // The `jne` above sets flags for CacheHit - CacheHit $0 // call or return imp + CacheHit $0, $1 // call or return imp 1: // loop @@ -305,7 +326,7 @@ $0: cmpl (%eax), %ecx // if (bucket->sel != sel) jne 1b // scan more // The `jne` above sets flags for CacheHit - CacheHit $0 // call or return imp + CacheHit $0, $1 // call or return imp 3: // wrap or miss @@ -327,7 +348,7 @@ $0: cmpl (%eax), %ecx // if (bucket->sel != sel) jne 1b // scan more // The `jne` above sets flags for CacheHit - CacheHit $0 // call or return imp + CacheHit $0, $1 // call or return imp 3: // double wrap or miss @@ -338,29 +359,30 @@ $0: ///////////////////////////////////////////////////////////////////// // -// MethodTableLookup +// MethodTableLookup NORMAL|STRET // // Takes: -// $0 = NORMAL, FPRET, STRET, SUPER, SUPER_STRET -// eax = receiver -// ecx = selector +// receiver (not struct objc_super) and selector on stack // edx = class to search // -// On exit: calls IMP, eq/ne set for forwarding +// On exit: IMP in eax, eq/ne set for forwarding // ///////////////////////////////////////////////////////////////////// .macro MethodTableLookup - MESSENGER_END_SLOW pushl %ebp - .cfi_def_cfa_offset 8 - .cfi_offset ebp, -8 - movl %esp, %ebp - .cfi_def_cfa_register ebp subl $$(8+5*16), %esp +.if $0 == NORMAL + movl self+4(%ebp), %eax + movl selector+4(%ebp), %ecx +.else + movl self_stret+4(%ebp), %eax + movl selector_stret+4(%ebp), %ecx +.endif + movdqa %xmm3, 4*16(%esp) movdqa %xmm2, 3*16(%esp) movdqa %xmm1, 2*16(%esp) @@ -378,32 +400,14 @@ $0: movdqa 2*16(%esp), %xmm1 movdqa 1*16(%esp), %xmm0 - leave - .cfi_def_cfa esp, 4 - .cfi_same_value ebp - -.if $0 == SUPER - // replace "super" arg with "receiver" - movl super(%esp), %ecx // get super structure - movl receiver(%ecx), %ecx // get messaged object - movl %ecx, super(%esp) // make it the first argument -.elseif $0 == SUPER_STRET - // replace "super" arg with "receiver" - movl super_stret(%esp), %ecx // get super structure - movl receiver(%ecx), %ecx // get messaged object - movl %ecx, super_stret(%esp) // make it the first argument -.endif - -.if $0 == STRET || $0 == SUPER_STRET - // set ne (stret) for forwarding; eax != 0 - test %eax, %eax - jmp *%eax // call imp +.if $0 == NORMAL + cmp %eax, %eax // set eq for nonstret forwarding .else - // set eq (non-stret) for forwarding - cmp %eax, %eax - jmp *%eax // call imp + test %eax, %eax // set ne for stret forwarding .endif + leave + .endmacro @@ -417,7 +421,7 @@ $0: // On exit: Loads non-nil receiver in eax and self(esp) or self_stret(esp), // or returns zero. // -// NilTestSupport return-type +// NilTestReturnZero return-type // // Takes: $0 = NORMAL or FPRET or STRET // eax = receiver @@ -425,33 +429,90 @@ $0: // On exit: Loads non-nil receiver in eax and self(esp) or self_stret(esp), // or returns zero. // +// NilTestReturnIMP return-type +// +// Takes: $0 = NORMAL or FPRET or STRET +// eax = receiver +// +// On exit: Loads non-nil receiver in eax and self(esp) or self_stret(esp), +// or returns an IMP in eax that returns zero. +// ///////////////////////////////////////////////////////////////////// +.macro ZeroReturn + xorl %eax, %eax + xorl %edx, %edx + xorps %xmm0, %xmm0 + xorps %xmm1, %xmm1 +.endmacro + +.macro ZeroReturnFPRET + fldz +.endmacro + +.macro ZeroReturnSTRET + // empty +.endmacro + + STATIC_ENTRY __objc_msgNil + ZeroReturn + ret + END_ENTRY __objc_msgNil + + STATIC_ENTRY __objc_msgNil_fpret + ZeroReturnFPRET + ret + END_ENTRY __objc_msgNil_fpret + + STATIC_ENTRY __objc_msgNil_stret + ZeroReturnSTRET + ret $4 + END_ENTRY __objc_msgNil_stret + + .macro NilTest testl %eax, %eax jz LNilTestSlow_f LNilTestDone: .endmacro -.macro NilTestSupport +.macro NilTestReturnZero .align 3 LNilTestSlow: -.if $0 == FPRET - fldz +.if $0 == NORMAL + ZeroReturn + MESSENGER_END_NIL + ret +.elseif $0 == FPRET + ZeroReturnFPRET MESSENGER_END_NIL ret .elseif $0 == STRET + ZeroReturnSTRET MESSENGER_END_NIL ret $$4 -.elseif $0 == NORMAL - // eax is already zero - xorl %edx, %edx - xorps %xmm0, %xmm0 - xorps %xmm1, %xmm1 - MESSENGER_END_NIL - ret +.else +.abort oops +.endif +.endmacro + +.macro NilTestReturnIMP + .align 3 +LNilTestSlow: + + call 1f +1: pop %eax +.if $0 == NORMAL + leal __objc_msgNil-1b(%eax), %eax +.elseif $0 == FPRET + leal __objc_msgNil_fpret-1b(%eax), %eax +.elseif $0 == STRET + leal __objc_msgNil_stret-1b(%eax), %eax +.else +.abort oops .endif + ret .endmacro @@ -468,24 +529,30 @@ LNilTestSlow: movl selector(%esp), %ecx movl self(%esp), %edx - CacheLookup GETIMP // returns IMP on success + CacheLookup NORMAL, GETIMP // returns IMP on success LCacheMiss: // cache miss, return nil xorl %eax, %eax ret -LGetImpExit: END_ENTRY _cache_getImp /******************************************************************** * - * id objc_msgSend(id self, SEL _cmd,...); + * id objc_msgSend(id self, SEL _cmd, ...); + * IMP objc_msgLookup(id self, SEL _cmd, ...); + * + * objc_msgLookup ABI: + * IMP returned in eax + * Forwarding returned in Z flag + * edx reserved for our use but not used * ********************************************************************/ - ENTRY _objc_msgSend + ENTRY _objc_msgSend + UNWIND _objc_msgSend, NoFrame MESSENGER_START movl selector(%esp), %ecx @@ -494,76 +561,136 @@ LGetImpExit: NilTest NORMAL movl isa(%eax), %edx // class = self->isa - CacheLookup NORMAL // calls IMP on success + CacheLookup NORMAL, CALL // calls IMP on success - NilTestSupport NORMAL + NilTestReturnZero NORMAL LCacheMiss: // isa still in edx + MESSENGER_END_SLOW + jmp __objc_msgSend_uncached + + END_ENTRY _objc_msgSend + + + ENTRY _objc_msgLookup + UNWIND _objc_msgLookup, NoFrame + movl selector(%esp), %ecx movl self(%esp), %eax - MethodTableLookup NORMAL // calls IMP -LMsgSendExit: - END_ENTRY _objc_msgSend + NilTest NORMAL + + movl isa(%eax), %edx // class = self->isa + CacheLookup NORMAL, LOOKUP // returns IMP on success + + NilTestReturnIMP NORMAL + +LCacheMiss: + // isa still in edx + jmp __objc_msgLookup_uncached + + END_ENTRY _objc_msgLookup /******************************************************************** * - * id objc_msgSendSuper(struct objc_super *super, SEL _cmd,...); + * id objc_msgSendSuper(struct objc_super *super, SEL _cmd, ...); + * IMP objc_msgLookupSuper(struct objc_super *super, SEL _cmd, ...); * - * struct objc_super { - * id receiver; - * Class class; - * }; ********************************************************************/ - ENTRY _objc_msgSendSuper + ENTRY _objc_msgSendSuper + UNWIND _objc_msgSendSuper, NoFrame MESSENGER_START movl selector(%esp), %ecx movl super(%esp), %eax // struct objc_super movl class(%eax), %edx // struct objc_super->class - CacheLookup SUPER // calls IMP on success + movl receiver(%eax), %eax // struct objc_super->receiver + movl %eax, super(%esp) // replace super arg with receiver + CacheLookup NORMAL, CALL // calls IMP on success LCacheMiss: // class still in edx + MESSENGER_END_SLOW + jmp __objc_msgSend_uncached + + END_ENTRY _objc_msgSendSuper + + + + ENTRY _objc_msgLookupSuper + UNWIND _objc_msgLookupSuper, NoFrame + movl selector(%esp), %ecx - movl super(%esp), %eax - movl receiver(%eax), %eax - MethodTableLookup SUPER // calls IMP - -LMsgSendSuperExit: - END_ENTRY _objc_msgSendSuper + movl super(%esp), %eax // struct objc_super + movl class(%eax), %edx // struct objc_super->class + movl receiver(%eax), %eax // struct objc_super->receiver + movl %eax, super(%esp) // replace super arg with receiver + CacheLookup NORMAL, LOOKUP // returns IMP on success + +LCacheMiss: + // class still in edx + jmp __objc_msgLookup_uncached + + END_ENTRY _objc_msgLookupSuper - ENTRY _objc_msgSendSuper2 +/******************************************************************** + * + * id objc_msgSendSuper2(struct objc_super *super, SEL _cmd, ...); + * IMP objc_msgLookupSuper2(struct objc_super *super, SEL _cmd, ...); + * + ********************************************************************/ + + ENTRY _objc_msgSendSuper2 + UNWIND _objc_msgSendSuper2, NoFrame MESSENGER_START movl selector(%esp), %ecx movl super(%esp), %eax // struct objc_super - movl class(%eax), %eax // struct objc_super->class - mov superclass(%eax), %edx // edx = objc_super->class->super_class - CacheLookup SUPER // calls IMP on success + movl class(%eax), %edx // struct objc_super->class + movl receiver(%eax), %eax // struct objc_super->receiver + movl %eax, super(%esp) // replace super arg with receiver + movl superclass(%edx), %edx // edx = objc_super->class->super_class + CacheLookup NORMAL, CALL // calls IMP on success LCacheMiss: // class still in edx + MESSENGER_END_SLOW + jmp __objc_msgSend_uncached + + END_ENTRY _objc_msgSendSuper2 + + + ENTRY _objc_msgLookupSuper2 + UNWIND _objc_msgLookupSuper2, NoFrame + movl selector(%esp), %ecx - movl super(%esp), %eax - movl receiver(%eax), %eax - MethodTableLookup SUPER // calls IMP + movl super(%esp), %eax // struct objc_super + movl class(%eax), %edx // struct objc_super->class + movl receiver(%eax), %eax // struct objc_super->receiver + movl %eax, super(%esp) // replace super arg with receiver + movl superclass(%edx), %edx // edx = objc_super->class->super_class + CacheLookup NORMAL, LOOKUP // returns IMP on success -LMsgSendSuper2Exit: - END_ENTRY _objc_msgSendSuper2 +LCacheMiss: + // class still in edx + jmp __objc_msgLookup_uncached + + END_ENTRY _objc_msgLookupSuper2 /******************************************************************** * - * double objc_msgSend_fpret(id self, SEL _cmd,...); + * double objc_msgSend_fpret(id self, SEL _cmd, ...); + * IMP objc_msgLookup_fpret(id self, SEL _cmd, ...); * ********************************************************************/ - ENTRY _objc_msgSend_fpret + ENTRY _objc_msgSend_fpret + UNWIND _objc_msgSend_fpret, NoFrame MESSENGER_START movl selector(%esp), %ecx @@ -572,35 +699,47 @@ LMsgSendSuper2Exit: NilTest FPRET movl isa(%eax), %edx // class = self->isa - CacheLookup FPRET // calls IMP on success + CacheLookup FPRET, CALL // calls IMP on success - NilTestSupport FPRET + NilTestReturnZero FPRET LCacheMiss: // class still in edx + MESSENGER_END_SLOW + jmp __objc_msgSend_uncached + + END_ENTRY _objc_msgSend_fpret + + + ENTRY _objc_msgLookup_fpret + UNWIND _objc_msgLookup_fpret, NoFrame + movl selector(%esp), %ecx movl self(%esp), %eax - MethodTableLookup FPRET // calls IMP -LMsgSendFpretExit: - END_ENTRY _objc_msgSend_fpret + NilTest FPRET + + movl isa(%eax), %edx // class = self->isa + CacheLookup FPRET, LOOKUP // returns IMP on success + + NilTestReturnIMP FPRET + +LCacheMiss: + // class still in edx + jmp __objc_msgLookup_uncached + + END_ENTRY _objc_msgLookup_fpret /******************************************************************** * - * void objc_msgSend_stret(void *st_addr , id self, SEL _cmd, ...); - * - * - * objc_msgSend_stret is the struct-return form of msgSend. - * The ABI calls for (sp+4) to be used as the address of the structure - * being returned, with the parameters in the succeeding locations. + * void objc_msgSend_stret(void *st_addr, id self, SEL _cmd, ...); + * IMP objc_msgLookup_stret(void *st_addr, id self, SEL _cmd, ...); * - * On entry: (sp+4)is the address where the structure is returned, - * (sp+8) is the message receiver, - * (sp+12) is the selector ********************************************************************/ - ENTRY _objc_msgSend_stret + ENTRY _objc_msgSend_stret + UNWIND _objc_msgSend_stret, NoFrame MESSENGER_START movl selector_stret(%esp), %ecx @@ -609,132 +748,191 @@ LMsgSendFpretExit: NilTest STRET movl isa(%eax), %edx // class = self->isa - CacheLookup STRET // calls IMP on success + CacheLookup STRET, CALL // calls IMP on success - NilTestSupport STRET + NilTestReturnZero STRET LCacheMiss: // class still in edx + MESSENGER_END_SLOW + jmp __objc_msgSend_stret_uncached + + END_ENTRY _objc_msgSend_stret + + + ENTRY _objc_msgLookup_stret + UNWIND _objc_msgLookup_stret, NoFrame + movl selector_stret(%esp), %ecx movl self_stret(%esp), %eax - MethodTableLookup STRET // calls IMP -LMsgSendStretExit: - END_ENTRY _objc_msgSend_stret + NilTest STRET + + movl isa(%eax), %edx // class = self->isa + CacheLookup STRET, LOOKUP // returns IMP on success + + NilTestReturnIMP STRET + +LCacheMiss: + // class still in edx + jmp __objc_msgLookup_stret_uncached + + END_ENTRY _objc_msgLookup_stret /******************************************************************** * * void objc_msgSendSuper_stret(void *st_addr, struct objc_super *super, SEL _cmd, ...); - * - * struct objc_super { - * id receiver; - * Class class; - * }; - * - * objc_msgSendSuper_stret is the struct-return form of msgSendSuper. - * The ABI calls for (sp+4) to be used as the address of the structure - * being returned, with the parameters in the succeeding registers. - * - * On entry: (sp+4)is the address where the structure is returned, - * (sp+8) is the address of the objc_super structure, - * (sp+12) is the selector + * IMP objc_msgLookupSuper_stret(void *st_addr, struct objc_super *super, SEL _cmd, ...); * ********************************************************************/ - ENTRY _objc_msgSendSuper_stret + ENTRY _objc_msgSendSuper_stret + UNWIND _objc_msgSendSuper_stret, NoFrame MESSENGER_START movl selector_stret(%esp), %ecx movl super_stret(%esp), %eax // struct objc_super movl class(%eax), %edx // struct objc_super->class - CacheLookup SUPER_STRET // calls IMP on success + movl receiver(%eax), %eax // struct objc_super->receiver + movl %eax, super_stret(%esp) // replace super arg with receiver + CacheLookup STRET, CALL // calls IMP on success LCacheMiss: // class still in edx + MESSENGER_END_SLOW + jmp __objc_msgSend_stret_uncached + + END_ENTRY _objc_msgSendSuper_stret + + + ENTRY _objc_msgLookupSuper_stret + UNWIND _objc_msgLookupSuper_stret, NoFrame + movl selector_stret(%esp), %ecx - movl super_stret(%esp), %eax - movl receiver(%eax), %eax - MethodTableLookup SUPER_STRET // calls IMP + movl super_stret(%esp), %eax // struct objc_super + movl class(%eax), %edx // struct objc_super->class + movl receiver(%eax), %eax // struct objc_super->receiver + movl %eax, super_stret(%esp) // replace super arg with receiver + CacheLookup STRET, LOOKUP // returns IMP on success + +LCacheMiss: + // class still in edx + jmp __objc_msgLookup_stret_uncached -LMsgSendSuperStretExit: - END_ENTRY _objc_msgSendSuper_stret + END_ENTRY _objc_msgLookupSuper_stret - ENTRY _objc_msgSendSuper2_stret +/******************************************************************** + * + * void objc_msgSendSuper2_stret(void *st_addr, struct objc_super *super, SEL _cmd, ...); + * IMP objc_msgLookupSuper2_stret(void *st_addr, struct objc_super *super, SEL _cmd, ...); + * + ********************************************************************/ + + ENTRY _objc_msgSendSuper2_stret + UNWIND _objc_msgSendSuper2_stret, NoFrame MESSENGER_START movl selector_stret(%esp), %ecx movl super_stret(%esp), %eax // struct objc_super - movl class(%eax), %eax // struct objc_super->class - mov superclass(%eax), %edx // edx = objc_super->class->super_class - CacheLookup SUPER_STRET // calls IMP on success + movl class(%eax), %edx // struct objc_super->class + movl receiver(%eax), %eax // struct objc_super->receiver + movl %eax, super_stret(%esp) // replace super arg with receiver + mov superclass(%edx), %edx // edx = objc_super->class->super_class + CacheLookup STRET, CALL // calls IMP on success // cache miss: go search the method lists LCacheMiss: // class still in edx + MESSENGER_END_SLOW + jmp __objc_msgSend_stret_uncached + + END_ENTRY _objc_msgSendSuper2_stret + + + ENTRY _objc_msgLookupSuper2_stret + UNWIND _objc_msgLookupSuper2_stret, NoFrame + movl selector_stret(%esp), %ecx - movl super_stret(%esp), %eax - movl receiver(%eax), %eax - MethodTableLookup SUPER_STRET // calls IMP + movl super_stret(%esp), %eax // struct objc_super + movl class(%eax), %edx // struct objc_super->class + movl receiver(%eax), %eax // struct objc_super->receiver + movl %eax, super_stret(%esp) // replace super arg with receiver + mov superclass(%edx), %edx // edx = objc_super->class->super_class + CacheLookup STRET, LOOKUP // returns IMP on success -LMsgSendSuper2StretExit: - END_ENTRY _objc_msgSendSuper2_stret +// cache miss: go search the method lists +LCacheMiss: + // class still in edx + jmp __objc_msgLookup_stret_uncached + + END_ENTRY _objc_msgLookupSuper2_stret /******************************************************************** * - * _objc_msgSend_uncached_impcache * _objc_msgSend_uncached * _objc_msgSend_stret_uncached - * - * Used to erase method cache entries in-place by - * bouncing them to the uncached lookup. + * _objc_msgLookup_uncached + * _objc_msgLookup_stret_uncached + * + * The uncached method lookup. * ********************************************************************/ - - STATIC_ENTRY __objc_msgSend_uncached_impcache - // Method cache version + + STATIC_ENTRY __objc_msgSend_uncached + UNWIND __objc_msgSend_uncached, FrameWithNoSaves // THIS IS NOT A CALLABLE C FUNCTION - // Out-of-band condition register is NE for stret, EQ otherwise. // Out-of-band edx is the searched class - MESSENGER_START - nop - MESSENGER_END_SLOW + // edx is already the class to search + MethodTableLookup NORMAL + jmp *%eax // call imp + + END_ENTRY __objc_msgSend_uncached + - jne __objc_msgSend_stret_uncached - jmp __objc_msgSend_uncached + STATIC_ENTRY __objc_msgSend_stret_uncached + UNWIND __objc_msgSend_stret_uncached, FrameWithNoSaves - END_ENTRY __objc_msgSend_uncached_impcache + // THIS IS NOT A CALLABLE C FUNCTION + // Out-of-band edx is the searched class + // edx is already the class to search + MethodTableLookup STRET + jmp *%eax // call imp + + END_ENTRY __objc_msgSend_stret_uncached - STATIC_ENTRY __objc_msgSend_uncached + + STATIC_ENTRY __objc_msgLookup_uncached + UNWIND __objc_msgLookup_uncached, FrameWithNoSaves // THIS IS NOT A CALLABLE C FUNCTION // Out-of-band edx is the searched class // edx is already the class to search - movl selector(%esp), %ecx - MethodTableLookup NORMAL // calls IMP + MethodTableLookup NORMAL // eax = IMP + ret - END_ENTRY __objc_msgSend_uncached + END_ENTRY __objc_msgLookup_uncached - STATIC_ENTRY __objc_msgSend_stret_uncached + STATIC_ENTRY __objc_msgLookup_stret_uncached + UNWIND __objc_msgLookup_stret_uncached, FrameWithNoSaves // THIS IS NOT A CALLABLE C FUNCTION // Out-of-band edx is the searched class // edx is already the class to search - movl selector_stret(%esp), %ecx - MethodTableLookup STRET // calls IMP + MethodTableLookup STRET // eax = IMP + ret - END_ENTRY __objc_msgSend_stret_uncached + END_ENTRY __objc_msgLookup_stret_uncached - /******************************************************************** * * id _objc_msgForward(id self, SEL _cmd,...); @@ -754,7 +952,7 @@ L_forward_stret_handler: .indirect_symbol __objc_forward_stret_handler .long 0 - STATIC_ENTRY __objc_msgForward_impcache + STATIC_ENTRY __objc_msgForward_impcache // Method cache version // THIS IS NOT A CALLABLE C FUNCTION @@ -767,10 +965,10 @@ L_forward_stret_handler: jne __objc_msgForward_stret jmp __objc_msgForward - END_ENTRY _objc_msgForward_impcache + END_ENTRY _objc_msgForward_impcache - ENTRY __objc_msgForward + ENTRY __objc_msgForward // Non-struct return version call 1f @@ -778,10 +976,10 @@ L_forward_stret_handler: movl L_forward_handler-1b(%edx), %edx jmp *(%edx) - END_ENTRY __objc_msgForward + END_ENTRY __objc_msgForward - ENTRY __objc_msgForward_stret + ENTRY __objc_msgForward_stret // Struct return version call 1f @@ -789,7 +987,7 @@ L_forward_stret_handler: movl L_forward_stret_handler-1b(%edx), %edx jmp *(%edx) - END_ENTRY __objc_msgForward_stret + END_ENTRY __objc_msgForward_stret ENTRY _objc_msgSend_debug @@ -838,15 +1036,6 @@ L_forward_stret_handler: jmp *%eax END_ENTRY _method_invoke_stret - -#if DEBUG - STATIC_ENTRY __objc_ignored_method - - movl self(%esp), %eax - ret - - END_ENTRY __objc_ignored_method -#endif .section __DATA,__objc_msg_break diff --git a/runtime/Messengers.subproj/objc-msg-simulator-x86_64.s b/runtime/Messengers.subproj/objc-msg-simulator-x86_64.s index 6237c27..41b5aaa 100644 --- a/runtime/Messengers.subproj/objc-msg-simulator-x86_64.s +++ b/runtime/Messengers.subproj/objc-msg-simulator-x86_64.s @@ -22,7 +22,7 @@ */ #include -#if __x86_64__ && TARGET_IPHONE_SIMULATOR +#if __x86_64__ && TARGET_OS_SIMULATOR /******************************************************************** ******************************************************************** @@ -50,6 +50,12 @@ _objc_entryPoints: .quad _objc_msgSendSuper_stret .quad _objc_msgSendSuper2 .quad _objc_msgSendSuper2_stret + .quad _objc_msgLookup + .quad _objc_msgLookup_fpret + .quad _objc_msgLookup_fp2ret + .quad _objc_msgLookup_stret + .quad _objc_msgLookupSuper2 + .quad _objc_msgLookupSuper2_stret .quad 0 .private_extern _objc_exitPoints @@ -63,6 +69,12 @@ _objc_exitPoints: .quad LExit_objc_msgSendSuper_stret .quad LExit_objc_msgSendSuper2 .quad LExit_objc_msgSendSuper2_stret + .quad LExit_objc_msgLookup + .quad LExit_objc_msgLookup_fpret + .quad LExit_objc_msgLookup_fp2ret + .quad LExit_objc_msgLookup_stret + .quad LExit_objc_msgLookupSuper2 + .quad LExit_objc_msgLookupSuper2_stret .quad 0 @@ -182,13 +194,12 @@ _gdb_objc_messenger_breakpoints: #define NORMAL 0 #define FPRET 1 #define FP2RET 2 -#define GETIMP 3 -#define STRET 4 -#define SUPER 5 -#define SUPER_STRET 6 -#define SUPER2 7 -#define SUPER2_STRET 8 - +#define STRET 3 + +#define CALL 100 +#define GETIMP 101 +#define LOOKUP 102 + /******************************************************************** * @@ -222,7 +233,6 @@ _gdb_objc_messenger_breakpoints: .globl $0 .align 6, 0x90 $0: - .cfi_startproc .endmacro .macro STATIC_ENTRY @@ -230,7 +240,6 @@ $0: .private_extern $0 .align 2, 0x90 $0: - .cfi_startproc .endmacro ////////////////////////////////////////////////////////////////////// @@ -244,92 +253,27 @@ $0: ////////////////////////////////////////////////////////////////////// .macro END_ENTRY - .cfi_endproc LExit$0: .endmacro -///////////////////////////////////////////////////////////////////// -// -// SaveRegisters -// -// Pushes a stack frame and saves all registers that might contain -// parameter values. -// -// On entry: -// stack = ret -// -// On exit: -// %rsp is 16-byte aligned -// -///////////////////////////////////////////////////////////////////// - -.macro SaveRegisters - - push %rbp - .cfi_def_cfa_offset 16 - .cfi_offset rbp, -16 - - mov %rsp, %rbp - .cfi_def_cfa_register rbp - - sub $$0x80+8, %rsp // +8 for alignment - - movdqa %xmm0, -0x80(%rbp) - push %rax // might be xmm parameter count - movdqa %xmm1, -0x70(%rbp) - push %a1 - movdqa %xmm2, -0x60(%rbp) - push %a2 - movdqa %xmm3, -0x50(%rbp) - push %a3 - movdqa %xmm4, -0x40(%rbp) - push %a4 - movdqa %xmm5, -0x30(%rbp) - push %a5 - movdqa %xmm6, -0x20(%rbp) - push %a6 - movdqa %xmm7, -0x10(%rbp) - + /******************************************************************** + * UNWIND name, flags + * Unwind info generation + ********************************************************************/ +.macro UNWIND + .section __LD,__compact_unwind,regular,debug + .quad $0 + .set LUnwind$0, LExit$0 - $0 + .long LUnwind$0 + .long $1 + .quad 0 /* no personality */ + .quad 0 /* no LSDA */ + .text .endmacro -///////////////////////////////////////////////////////////////////// -// -// RestoreRegisters -// -// Pops a stack frame pushed by SaveRegisters -// -// On entry: -// %rbp unchanged since SaveRegisters -// -// On exit: -// stack = ret -// -///////////////////////////////////////////////////////////////////// - -.macro RestoreRegisters - - movdqa -0x80(%rbp), %xmm0 - pop %a6 - movdqa -0x70(%rbp), %xmm1 - pop %a5 - movdqa -0x60(%rbp), %xmm2 - pop %a4 - movdqa -0x50(%rbp), %xmm3 - pop %a3 - movdqa -0x40(%rbp), %xmm4 - pop %a2 - movdqa -0x30(%rbp), %xmm5 - pop %a1 - movdqa -0x20(%rbp), %xmm6 - pop %rax - movdqa -0x10(%rbp), %xmm7 - - leave - .cfi_def_cfa rsp, 8 - .cfi_same_value rbp - -.endmacro +#define NoFrame 0x02010000 // no frame, no SP adjustment except return address +#define FrameWithNoSaves 0x01000000 // frame, no non-volatile saves ///////////////////////////////////////////////////////////////////// @@ -339,13 +283,15 @@ LExit$0: // Locate the implementation for a class in a selector's method cache. // // Takes: -// $0 = NORMAL, FPRET, FP2RET, STRET, SUPER, SUPER_STRET, SUPER2, SUPER2_STRET, GETIMP -// a2 or a3 (STRET) = selector a.k.a. cache -// r11 = class to search +// $0 = NORMAL, FPRET, FP2RET, STRET +// $1 = CALL, LOOKUP, GETIMP +// a1 or a2 (STRET) = receiver +// a2 or a3 (STRET) = selector +// r10 = class to search // // On exit: r10 clobbered -// (found) calls or returns IMP, eq/ne/r11 set for forwarding -// (not found) jumps to LCacheMiss, class still in r11 +// (found) calls or returns IMP in r11, eq/ne set for forwarding +// (not found) jumps to LCacheMiss, class still in r10 // ///////////////////////////////////////////////////////////////////// @@ -354,95 +300,77 @@ LExit$0: // CacheHit must always be preceded by a not-taken `jne` instruction // in order to set the correct flags for _objc_msgForward_impcache. - // r10 = found bucket + // r11 = found bucket -.if $0 == GETIMP - movq 8(%r10), %rax // return imp - leaq __objc_msgSend_uncached_impcache(%rip), %r11 - cmpq %rax, %r11 - jne 4f - xorl %eax, %eax // don't return msgSend_uncached -4: ret -.elseif $0 == NORMAL || $0 == FPRET || $0 == FP2RET +.if $1 == GETIMP + movq 8(%r11), %rax // return imp + ret + +.else + +.if $0 != STRET // eq already set for forwarding by `jne` +.else + test %r11, %r11 // set ne for stret forwarding +.endif + +.if $1 == CALL MESSENGER_END_FAST - jmp *8(%r10) // call imp - -.elseif $0 == SUPER - movq receiver(%a1), %a1 // load real receiver - cmp %r10, %r10 // set eq for non-stret forwarding - MESSENGER_END_FAST - jmp *8(%r10) // call imp - -.elseif $0 == SUPER2 - movq receiver(%a1), %a1 // load real receiver - cmp %r10, %r10 // set eq for non-stret forwarding - MESSENGER_END_FAST - jmp *8(%r10) // call imp - -.elseif $0 == STRET - test %r10, %r10 // set ne for stret forwarding - MESSENGER_END_FAST - jmp *8(%r10) // call imp + jmp *8(%r11) // call imp -.elseif $0 == SUPER_STRET - movq receiver(%a2), %a2 // load real receiver - test %r10, %r10 // set ne for stret forwarding - MESSENGER_END_FAST - jmp *8(%r10) // call imp +.elseif $1 == LOOKUP + movq 8(%r11), %r11 // return imp + ret -.elseif $0 == SUPER2_STRET - movq receiver(%a2), %a2 // load real receiver - test %r10, %r10 // set ne for stret forwarding - MESSENGER_END_FAST - jmp *8(%r10) // call imp .else .abort oops .endif - + +.endif + .endmacro .macro CacheLookup -.if $0 != STRET && $0 != SUPER_STRET && $0 != SUPER2_STRET - movq %a2, %r10 // r10 = _cmd +.if $0 != STRET + movq %a2, %r11 // r11 = _cmd .else - movq %a3, %r10 // r10 = _cmd + movq %a3, %r11 // r11 = _cmd .endif - andl 24(%r11), %r10d // r10 = _cmd & class->cache.mask - shlq $$4, %r10 // r10 = offset = (_cmd & mask)<<4 - addq 16(%r11), %r10 // r10 = class->cache.buckets + offset + andl 24(%r10), %r11d // r11 = _cmd & class->cache.mask + shlq $$4, %r11 // r11 = offset = (_cmd & mask)<<4 + addq 16(%r10), %r11 // r11 = class->cache.buckets + offset -.if $0 != STRET && $0 != SUPER_STRET && $0 != SUPER2_STRET - cmpq (%r10), %a2 // if (bucket->sel != _cmd) +.if $0 != STRET + cmpq (%r11), %a2 // if (bucket->sel != _cmd) .else - cmpq (%r10), %a3 // if (bucket->sel != _cmd) + cmpq (%r11), %a3 // if (bucket->sel != _cmd) .endif jne 1f // scan more // CacheHit must always be preceded by a not-taken `jne` instruction - CacheHit $0 // call or return imp + CacheHit $0, $1 // call or return imp 1: // loop - cmpq $$1, (%r10) + cmpq $$1, (%r11) jbe 3f // if (bucket->sel <= 1) wrap or miss - addq $$16, %r10 // bucket++ + addq $$16, %r11 // bucket++ 2: -.if $0 != STRET && $0 != SUPER_STRET && $0 != SUPER2_STRET - cmpq (%r10), %a2 // if (bucket->sel != _cmd) +.if $0 != STRET + cmpq (%r11), %a2 // if (bucket->sel != _cmd) .else - cmpq (%r10), %a3 // if (bucket->sel != _cmd) + cmpq (%r11), %a3 // if (bucket->sel != _cmd) .endif jne 1b // scan more // CacheHit must always be preceded by a not-taken `jne` instruction - CacheHit $0 // call or return imp + CacheHit $0, $1 // call or return imp 3: // wrap or miss jb LCacheMiss_f // if (bucket->sel < 1) cache miss // wrap - movq 8(%r10), %r10 // bucket->imp is really first bucket + movq 8(%r11), %r11 // bucket->imp is really first bucket jmp 2f // Clone scanning loop to miss instead of hang when cache is corrupt. @@ -450,19 +378,19 @@ LExit$0: 1: // loop - cmpq $$1, (%r10) + cmpq $$1, (%r11) jbe 3f // if (bucket->sel <= 1) wrap or miss - addq $$16, %r10 // bucket++ + addq $$16, %r11 // bucket++ 2: -.if $0 != STRET && $0 != SUPER_STRET && $0 != SUPER2_STRET - cmpq (%r10), %a2 // if (bucket->sel != _cmd) +.if $0 != STRET + cmpq (%r11), %a2 // if (bucket->sel != _cmd) .else - cmpq (%r10), %a3 // if (bucket->sel != _cmd) + cmpq (%r11), %a3 // if (bucket->sel != _cmd) .endif jne 1b // scan more // CacheHit must always be preceded by a not-taken `jne` instruction - CacheHit $0 // call or return imp + CacheHit $0, $1 // call or return imp 3: // double wrap or miss @@ -473,32 +401,77 @@ LExit$0: ///////////////////////////////////////////////////////////////////// // -// MethodTableLookup classRegister, selectorRegister +// MethodTableLookup NORMAL|STRET // -// Takes: $0 = class to search (a1 or a2 or r10 ONLY) -// $1 = selector to search for (a2 or a3 ONLY) -// r11 = class to search +// Takes: a1 or a2 (STRET) = receiver +// a2 or a3 (STRET) = selector to search for +// r10 = class to search // -// On exit: imp in %r11 +// On exit: imp in %r11, eq/ne set for forwarding // ///////////////////////////////////////////////////////////////////// + .macro MethodTableLookup - MESSENGER_END_SLOW + push %rbp + mov %rsp, %rbp - SaveRegisters + sub $$0x80+8, %rsp // +8 for alignment + + movdqa %xmm0, -0x80(%rbp) + push %rax // might be xmm parameter count + movdqa %xmm1, -0x70(%rbp) + push %a1 + movdqa %xmm2, -0x60(%rbp) + push %a2 + movdqa %xmm3, -0x50(%rbp) + push %a3 + movdqa %xmm4, -0x40(%rbp) + push %a4 + movdqa %xmm5, -0x30(%rbp) + push %a5 + movdqa %xmm6, -0x20(%rbp) + push %a6 + movdqa %xmm7, -0x10(%rbp) // _class_lookupMethodAndLoadCache3(receiver, selector, class) - movq $0, %a1 - movq $1, %a2 - movq %r11, %a3 +.if $0 == NORMAL + // receiver already in a1 + // selector already in a2 +.else + movq %a2, %a1 + movq %a3, %a2 +.endif + movq %r10, %a3 call __class_lookupMethodAndLoadCache3 // IMP is now in %rax movq %rax, %r11 - RestoreRegisters + movdqa -0x80(%rbp), %xmm0 + pop %a6 + movdqa -0x70(%rbp), %xmm1 + pop %a5 + movdqa -0x60(%rbp), %xmm2 + pop %a4 + movdqa -0x50(%rbp), %xmm3 + pop %a3 + movdqa -0x40(%rbp), %xmm4 + pop %a2 + movdqa -0x30(%rbp), %xmm5 + pop %a1 + movdqa -0x20(%rbp), %xmm6 + pop %rax + movdqa -0x10(%rbp), %xmm7 + +.if $0 == NORMAL + cmp %r11, %r11 // set eq for nonstret forwarding +.else + test %r11, %r11 // set ne for stret forwarding +.endif + + leave .endmacro @@ -506,25 +479,68 @@ LExit$0: ///////////////////////////////////////////////////////////////////// // // GetIsaCheckNil return-type -// GetIsaSupport return-type +// GetIsaSupport return-type +// NilTestReturnZero return-type +// NilTestReturnIMP return-type // -// Sets r11 = receiver->isa. +// Sets r10 = obj->isa. // Looks up the real class if receiver is a tagged pointer object. -// Returns zero if obj is nil. +// Returns zero or a zero-returning IMP if obj is nil. // // Takes: $0 = NORMAL or FPRET or FP2RET or STRET // a1 or a2 (STRET) = receiver // -// On exit: r11 = receiver->isa -// r10 is clobbered +// On exit from GetIsaCheckNil: +// r10 = receiver->isa +// r11 is clobbered // ///////////////////////////////////////////////////////////////////// -.macro GetIsaCheckNil -.if $0 == SUPER || $0 == SUPER_STRET - error super dispatch does not test for nil -.endif +.macro ZeroReturn + xorl %eax, %eax + xorl %edx, %edx + xorps %xmm0, %xmm0 + xorps %xmm1, %xmm1 +.endmacro + +.macro ZeroReturnFPRET + fldz + ZeroReturn +.endmacro + +.macro ZeroReturnFP2RET + fldz + fldz + ZeroReturn +.endmacro + +.macro ZeroReturnSTRET + // rax gets the struct-return address as passed in rdi + movq %rdi, %rax +.endmacro + + STATIC_ENTRY __objc_msgNil + ZeroReturn + ret + END_ENTRY __objc_msgNil + + STATIC_ENTRY __objc_msgNil_fpret + ZeroReturnFPRET + ret + END_ENTRY __objc_msgNil_fpret + STATIC_ENTRY __objc_msgNil_fp2ret + ZeroReturnFP2RET + ret + END_ENTRY __objc_msgNil_fp2ret + + STATIC_ENTRY __objc_msgNil_stret + ZeroReturnSTRET + ret + END_ENTRY __objc_msgNil_stret + + +.macro GetIsaCheckNil .if $0 != STRET testq %a1, %a1 .else @@ -533,9 +549,9 @@ LExit$0: jle LNilOrTagged_f // MSB tagged pointer looks negative .if $0 != STRET - movq (%a1), %r11 // r11 = isa + movq (%a1), %r10 // r10 = isa .else - movq (%a2), %r11 // r11 = isa + movq (%a2), %r10 // r10 = isa .endif LGetIsaDone: @@ -545,42 +561,69 @@ LGetIsaDone: .macro GetIsaSupport .align 3 LNilOrTagged: - jz LNil_f // flags set by NilOrTaggedTest - - // tagged - - leaq _objc_debug_taggedpointer_classes(%rip), %r11 + jz LNil_f // flags set by GetIsaCheckNil .if $0 != STRET - movq %a1, %r10 + movq %a1, %r11 .else - movq %a2, %r10 + movq %a2, %r11 .endif - shrq $$60, %r10 - movq (%r11, %r10, 8), %r11 // read isa from table + shrq $$60, %r11 + cmpl $$0xf, %r11d + je 1f + // basic tagged + leaq _objc_debug_taggedpointer_classes(%rip), %r10 + movq (%r10, %r11, 8), %r10 // read isa from table jmp LGetIsaDone_b +1: + // ext tagged +.if $0 != STRET + movq %a1, %r11 +.else + movq %a2, %r11 +.endif + shrq $$52, %r11 + andl $$0xff, %r11d + leaq _objc_debug_taggedpointer_ext_classes(%rip), %r10 + movq (%r10, %r11, 8), %r10 // read isa from table + jmp LGetIsaDone_b +.endmacro -LNil: - // nil -.if $0 == FPRET - fldz +.macro NilTestReturnZero +LNil: +.if $0 == NORMAL + ZeroReturn +.elseif $0 == FPRET + ZeroReturnFPRET .elseif $0 == FP2RET - fldz - fldz -.endif -.if $0 == STRET - movq %rdi, %rax + ZeroReturnFP2RET +.elseif $0 == STRET + ZeroReturnSTRET .else - xorl %eax, %eax - xorl %edx, %edx - xorps %xmm0, %xmm0 - xorps %xmm1, %xmm1 +.abort oops .endif MESSENGER_END_NIL ret .endmacro +.macro NilTestReturnIMP +LNil: +.if $0 == NORMAL + leaq __objc_msgNil(%rip), %r11 +.elseif $0 == FPRET + leaq __objc_msgNil_fpret(%rip), %r11 +.elseif $0 == FP2RET + leaq __objc_msgNil_fp2ret(%rip), %r11 +.elseif $0 == STRET + leaq __objc_msgNil_stret(%rip), %r11 +.else +.abort oops +.endif + ret +.endmacro + + /******************************************************************** * IMP cache_getImp(Class cls, SEL sel) * @@ -594,21 +637,26 @@ LNil: STATIC_ENTRY _cache_getImp // do lookup - movq %a1, %r11 // move class to r11 for CacheLookup - CacheLookup GETIMP // returns IMP on success + movq %a1, %r10 // move class to r10 for CacheLookup + CacheLookup NORMAL, GETIMP // returns IMP on success LCacheMiss: // cache miss, return nil xorl %eax, %eax ret -LGetImpExit: - END_ENTRY _cache_getImp + END_ENTRY _cache_getImp /******************************************************************** * * id objc_msgSend(id self, SEL _cmd,...); + * IMP objc_msgLookup(id self, SEL _cmd, ...); + * + * objc_msgLookup ABI: + * IMP returned in r11 + * Forwarding returned in Z flag + * r10 reserved for our use but not used * ********************************************************************/ @@ -617,23 +665,43 @@ LGetImpExit: .globl _objc_debug_taggedpointer_classes _objc_debug_taggedpointer_classes: .fill 16, 8, 0 + .globl _objc_debug_taggedpointer_ext_classes +_objc_debug_taggedpointer_ext_classes: + .fill 256, 8, 0 - ENTRY _objc_msgSend + ENTRY _objc_msgSend + UNWIND _objc_msgSend, NoFrame MESSENGER_START - GetIsaCheckNil NORMAL // r11 = self->isa, or return zero - CacheLookup NORMAL // calls IMP on success + GetIsaCheckNil NORMAL // r10 = self->isa, or return zero + CacheLookup NORMAL, CALL // calls IMP on success - GetIsaSupport NORMAL + GetIsaSupport NORMAL + NilTestReturnZero NORMAL // cache miss: go search the method lists LCacheMiss: - // isa still in r11 - MethodTableLookup %a1, %a2 // r11 = IMP - cmp %r11, %r11 // set eq (nonstret) for forwarding - jmp *%r11 // goto *imp + // isa still in r10 + MESSENGER_END_SLOW + jmp __objc_msgSend_uncached - END_ENTRY _objc_msgSend + END_ENTRY _objc_msgSend + + + ENTRY _objc_msgLookup + + GetIsaCheckNil NORMAL // r10 = self->isa, or return zero IMP + CacheLookup NORMAL, LOOKUP // returns IMP on success + + GetIsaSupport NORMAL + NilTestReturnIMP NORMAL + +// cache miss: go search the method lists +LCacheMiss: + // isa still in r10 + jmp __objc_msgLookup_uncached + + END_ENTRY _objc_msgLookup ENTRY _objc_msgSend_fixup @@ -658,23 +726,22 @@ LCacheMiss: * }; ********************************************************************/ - ENTRY _objc_msgSendSuper + ENTRY _objc_msgSendSuper + UNWIND _objc_msgSendSuper, NoFrame MESSENGER_START // search the cache (objc_super in %a1) - movq class(%a1), %r11 // class = objc_super->class - CacheLookup SUPER // calls IMP on success + movq class(%a1), %r10 // class = objc_super->class + movq receiver(%a1), %a1 // load real receiver + CacheLookup NORMAL, CALL // calls IMP on success // cache miss: go search the method lists LCacheMiss: - // class still in r11 - movq receiver(%a1), %r10 - MethodTableLookup %r10, %a2 // r11 = IMP - movq receiver(%a1), %a1 // load real receiver - cmp %r11, %r11 // set eq (nonstret) for forwarding - jmp *%r11 // goto *imp + // class still in r10 + MESSENGER_END_SLOW + jmp __objc_msgSend_uncached - END_ENTRY _objc_msgSendSuper + END_ENTRY _objc_msgSendSuper /******************************************************************** @@ -682,27 +749,44 @@ LCacheMiss: ********************************************************************/ ENTRY _objc_msgSendSuper2 + UNWIND _objc_msgSendSuper2, NoFrame MESSENGER_START // objc_super->class is superclass of class to search // search the cache (objc_super in %a1) - movq class(%a1), %r11 // cls = objc_super->class - movq 8(%r11), %r11 // cls = class->superclass - CacheLookup SUPER2 // calls IMP on success + movq class(%a1), %r10 // cls = objc_super->class + movq receiver(%a1), %a1 // load real receiver + movq 8(%r10), %r10 // cls = class->superclass + CacheLookup NORMAL, CALL // calls IMP on success // cache miss: go search the method lists LCacheMiss: - // superclass still in r11 - movq receiver(%a1), %r10 - MethodTableLookup %r10, %a2 // r11 = IMP - movq receiver(%a1), %a1 // load real receiver - cmp %r11, %r11 // set eq (nonstret) for forwarding - jmp *%r11 // goto *imp + // superclass still in r10 + MESSENGER_END_SLOW + jmp __objc_msgSend_uncached - END_ENTRY _objc_msgSendSuper2 + END_ENTRY _objc_msgSendSuper2 + + ENTRY _objc_msgLookupSuper2 + // objc_super->class is superclass of class to search + +// search the cache (objc_super in %a1) + movq class(%a1), %r10 // cls = objc_super->class + movq receiver(%a1), %a1 // load real receiver + movq 8(%r10), %r10 // cls = class->superclass + CacheLookup NORMAL, LOOKUP // returns IMP on success + +// cache miss: go search the method lists +LCacheMiss: + // superclass still in r10 + jmp __objc_msgLookup_uncached + + END_ENTRY _objc_msgLookupSuper2 + + ENTRY _objc_msgSendSuper2_fixup int3 END_ENTRY _objc_msgSendSuper2_fixup @@ -722,22 +806,39 @@ LCacheMiss: * ********************************************************************/ - ENTRY _objc_msgSend_fpret + ENTRY _objc_msgSend_fpret + UNWIND _objc_msgSend_fpret, NoFrame MESSENGER_START - GetIsaCheckNil FPRET // r11 = self->isa, or return zero - CacheLookup FPRET // calls IMP on success + GetIsaCheckNil FPRET // r10 = self->isa, or return zero + CacheLookup FPRET, CALL // calls IMP on success - GetIsaSupport FPRET + GetIsaSupport FPRET + NilTestReturnZero FPRET // cache miss: go search the method lists LCacheMiss: - // isa still in r11 - MethodTableLookup %a1, %a2 // r11 = IMP - cmp %r11, %r11 // set eq (nonstret) for forwarding - jmp *%r11 // goto *imp + // isa still in r10 + MESSENGER_END_SLOW + jmp __objc_msgSend_uncached + + END_ENTRY _objc_msgSend_fpret - END_ENTRY _objc_msgSend_fpret + + ENTRY _objc_msgLookup_fpret + + GetIsaCheckNil FPRET // r10 = self->isa, or return zero IMP + CacheLookup FPRET, LOOKUP // returns IMP on success + + GetIsaSupport FPRET + NilTestReturnIMP FPRET + +// cache miss: go search the method lists +LCacheMiss: + // isa still in r10 + jmp __objc_msgLookup_uncached + + END_ENTRY _objc_msgLookup_fpret ENTRY _objc_msgSend_fpret_fixup @@ -759,22 +860,39 @@ LCacheMiss: * ********************************************************************/ - ENTRY _objc_msgSend_fp2ret + ENTRY _objc_msgSend_fp2ret + UNWIND _objc_msgSend_fp2ret, NoFrame MESSENGER_START - GetIsaCheckNil FP2RET // r11 = self->isa, or return zero - CacheLookup FP2RET // calls IMP on success + GetIsaCheckNil FP2RET // r10 = self->isa, or return zero + CacheLookup FP2RET, CALL // calls IMP on success - GetIsaSupport FP2RET + GetIsaSupport FP2RET + NilTestReturnZero FP2RET // cache miss: go search the method lists LCacheMiss: - // isa still in r11 - MethodTableLookup %a1, %a2 // r11 = IMP - cmp %r11, %r11 // set eq (nonstret) for forwarding - jmp *%r11 // goto *imp + // isa still in r10 + MESSENGER_END_SLOW + jmp __objc_msgSend_uncached + + END_ENTRY _objc_msgSend_fp2ret + + + ENTRY _objc_msgLookup_fp2ret + + GetIsaCheckNil FP2RET // r10 = self->isa, or return zero IMP + CacheLookup FP2RET, LOOKUP // returns IMP on success + + GetIsaSupport FP2RET + NilTestReturnIMP FP2RET + +// cache miss: go search the method lists +LCacheMiss: + // isa still in r10 + jmp __objc_msgLookup_uncached - END_ENTRY _objc_msgSend_fp2ret + END_ENTRY _objc_msgLookup_fp2ret ENTRY _objc_msgSend_fp2ret_fixup @@ -802,22 +920,39 @@ LCacheMiss: * %a3 is the selector ********************************************************************/ - ENTRY _objc_msgSend_stret + ENTRY _objc_msgSend_stret + UNWIND _objc_msgSend_stret, NoFrame MESSENGER_START + + GetIsaCheckNil STRET // r10 = self->isa, or return zero + CacheLookup STRET, CALL // calls IMP on success + + GetIsaSupport STRET + NilTestReturnZero STRET + +// cache miss: go search the method lists +LCacheMiss: + // isa still in r10 + MESSENGER_END_SLOW + jmp __objc_msgSend_stret_uncached + + END_ENTRY _objc_msgSend_stret + + + ENTRY _objc_msgLookup_stret - GetIsaCheckNil STRET // r11 = self->isa, or return zero - CacheLookup STRET // calls IMP on success + GetIsaCheckNil STRET // r10 = self->isa, or return zero IMP + CacheLookup STRET, LOOKUP // returns IMP on success - GetIsaSupport STRET + GetIsaSupport STRET + NilTestReturnIMP STRET // cache miss: go search the method lists LCacheMiss: - // isa still in r11 - MethodTableLookup %a2, %a3 // r11 = IMP - test %r11, %r11 // set ne (stret) for forward; r11!=0 - jmp *%r11 // goto *imp + // isa still in r10 + jmp __objc_msgLookup_stret_uncached - END_ENTRY _objc_msgSend_stret + END_ENTRY _objc_msgLookup_stret ENTRY _objc_msgSend_stret_fixup @@ -851,47 +986,61 @@ LCacheMiss: * ********************************************************************/ - ENTRY _objc_msgSendSuper_stret + ENTRY _objc_msgSendSuper_stret + UNWIND _objc_msgSendSuper_stret, NoFrame MESSENGER_START // search the cache (objc_super in %a2) - movq class(%a2), %r11 // class = objc_super->class - CacheLookup SUPER_STRET // calls IMP on success + movq class(%a2), %r10 // class = objc_super->class + movq receiver(%a2), %a2 // load real receiver + CacheLookup STRET, CALL // calls IMP on success // cache miss: go search the method lists LCacheMiss: - // class still in r11 - movq receiver(%a2), %r10 - MethodTableLookup %r10, %a3 // r11 = IMP - movq receiver(%a2), %a2 // load real receiver - test %r11, %r11 // set ne (stret) for forward; r11!=0 - jmp *%r11 // goto *imp - - END_ENTRY _objc_msgSendSuper_stret + // class still in r10 + MESSENGER_END_SLOW + jmp __objc_msgSend_stret_uncached + + END_ENTRY _objc_msgSendSuper_stret /******************************************************************** * id objc_msgSendSuper2_stret ********************************************************************/ - ENTRY _objc_msgSendSuper2_stret + ENTRY _objc_msgSendSuper2_stret + UNWIND _objc_msgSendSuper2_stret, NoFrame MESSENGER_START // search the cache (objc_super in %a2) - movq class(%a2), %r11 // class = objc_super->class - movq 8(%r11), %r11 // class = class->superclass - CacheLookup SUPER2_STRET // calls IMP on success + movq class(%a2), %r10 // class = objc_super->class + movq receiver(%a2), %a2 // load real receiver + movq 8(%r10), %r10 // class = class->superclass + CacheLookup STRET, CALL // calls IMP on success // cache miss: go search the method lists LCacheMiss: - // superclass still in r11 - movq receiver(%a2), %r10 - MethodTableLookup %r10, %a3 // r11 = IMP + // superclass still in r10 + MESSENGER_END_SLOW + jmp __objc_msgSend_stret_uncached + + END_ENTRY _objc_msgSendSuper2_stret + + + ENTRY _objc_msgLookupSuper2_stret + +// search the cache (objc_super in %a2) + movq class(%a2), %r10 // class = objc_super->class movq receiver(%a2), %a2 // load real receiver - test %r11, %r11 // set ne (stret) for forward; r11!=0 - jmp *%r11 // goto *imp + movq 8(%r10), %r10 // class = class->superclass + CacheLookup STRET, LOOKUP // returns IMP on success + +// cache miss: go search the method lists +LCacheMiss: + // superclass still in r10 + jmp __objc_msgLookup_stret_uncached - END_ENTRY _objc_msgSendSuper2_stret + END_ENTRY _objc_msgLookupSuper2_stret ENTRY _objc_msgSendSuper2_stret_fixup @@ -908,57 +1057,64 @@ LCacheMiss: /******************************************************************** * - * _objc_msgSend_uncached_impcache * _objc_msgSend_uncached * _objc_msgSend_stret_uncached - * - * Used to erase method cache entries in-place by - * bouncing them to the uncached lookup. + * The uncached method lookup. * ********************************************************************/ - - STATIC_ENTRY __objc_msgSend_uncached_impcache - // Method cache version - - // THIS IS NOT A CALLABLE C FUNCTION - // Out-of-band condition register is NE for stret, EQ otherwise. - // Out-of-band r11 is the searched class - - MESSENGER_START - nop - MESSENGER_END_SLOW - - jne __objc_msgSend_stret_uncached - jmp __objc_msgSend_uncached - - END_ENTRY __objc_msgSend_uncached_impcache - STATIC_ENTRY __objc_msgSend_uncached - + UNWIND __objc_msgSend_uncached, FrameWithNoSaves + // THIS IS NOT A CALLABLE C FUNCTION - // Out-of-band r11 is the searched class + // Out-of-band r10 is the searched class - // r11 is already the class to search - MethodTableLookup %a1, %a2 // r11 = IMP - cmp %r11, %r11 // set eq (nonstret) for forwarding + // r10 is already the class to search + MethodTableLookup NORMAL // r11 = IMP jmp *%r11 // goto *imp END_ENTRY __objc_msgSend_uncached STATIC_ENTRY __objc_msgSend_stret_uncached + UNWIND __objc_msgSend_stret_uncached, FrameWithNoSaves + // THIS IS NOT A CALLABLE C FUNCTION - // Out-of-band r11 is the searched class + // Out-of-band r10 is the searched class - // r11 is already the class to search - MethodTableLookup %a2, %a3 // r11 = IMP - test %r11, %r11 // set ne (stret) for forward; r11!=0 + // r10 is already the class to search + MethodTableLookup STRET // r11 = IMP jmp *%r11 // goto *imp END_ENTRY __objc_msgSend_stret_uncached + STATIC_ENTRY __objc_msgLookup_uncached + UNWIND __objc_msgLookup_uncached, FrameWithNoSaves + + // THIS IS NOT A CALLABLE C FUNCTION + // Out-of-band r10 is the searched class + + // r10 is already the class to search + MethodTableLookup NORMAL // r11 = IMP + ret + + END_ENTRY __objc_msgLookup_uncached + + + STATIC_ENTRY __objc_msgLookup_stret_uncached + UNWIND __objc_msgLookup_stret_uncached, FrameWithNoSaves + + // THIS IS NOT A CALLABLE C FUNCTION + // Out-of-band r10 is the searched class + + // r10 is already the class to search + MethodTableLookup STRET // r11 = IMP + ret + + END_ENTRY __objc_msgLookup_stret_uncached + + /******************************************************************** * * id _objc_msgForward(id self, SEL _cmd,...); @@ -970,7 +1126,7 @@ LCacheMiss: * ********************************************************************/ - STATIC_ENTRY __objc_msgForward_impcache + STATIC_ENTRY __objc_msgForward_impcache // Method cache version // THIS IS NOT A CALLABLE C FUNCTION @@ -983,25 +1139,25 @@ LCacheMiss: jne __objc_msgForward_stret jmp __objc_msgForward - END_ENTRY __objc_msgForward_impcache + END_ENTRY __objc_msgForward_impcache - ENTRY __objc_msgForward + ENTRY __objc_msgForward // Non-stret version movq __objc_forward_handler(%rip), %r11 jmp *%r11 - END_ENTRY __objc_msgForward + END_ENTRY __objc_msgForward - ENTRY __objc_msgForward_stret + ENTRY __objc_msgForward_stret // Struct-return version - + movq __objc_forward_stret_handler(%rip), %r11 jmp *%r11 - END_ENTRY __objc_msgForward_stret + END_ENTRY __objc_msgForward_stret ENTRY _objc_msgSend_debug @@ -1052,14 +1208,6 @@ LCacheMiss: END_ENTRY _method_invoke_stret - STATIC_ENTRY __objc_ignored_method - - movq %a1, %rax - ret - - END_ENTRY __objc_ignored_method - - .section __DATA,__objc_msg_break .quad 0 .quad 0 diff --git a/runtime/Messengers.subproj/objc-msg-win32.m b/runtime/Messengers.subproj/objc-msg-win32.m index 014daa8..8821b0a 100644 --- a/runtime/Messengers.subproj/objc-msg-win32.m +++ b/runtime/Messengers.subproj/objc-msg-win32.m @@ -135,11 +135,6 @@ OBJC_EXPORT __declspec(naked) id objc_msgSend(id a, SEL b, ...) mov ecx, SELECTOR mov eax, SELF -#if SUPPORT_GC - // check whether selector is ignored -#error oops -#endif - // check whether receiver is nil test eax, eax je NIL @@ -209,11 +204,6 @@ OBJC_EXPORT __declspec(naked) double objc_msgSend_fpret(id a, SEL b, ...) mov ecx, SELECTOR mov eax, SELF -#if SUPPORT_GC - // check whether selector is ignored -#error oops -#endif - // check whether receiver is nil test eax, eax je NIL @@ -283,11 +273,6 @@ OBJC_EXPORT __declspec(naked) id objc_msgSendSuper(struct objc_super *a, SEL b, mov ecx, SELECTOR mov edx, super_class[eax] -#if SUPPORT_GC - // check whether selector is ignored -#error oops -#endif - // search the cache (class in edx) // CacheLookup WORD_RETURN, MSG_SENDSUPER push edi @@ -350,11 +335,6 @@ OBJC_EXPORT __declspec(naked) void objc_msgSend_stret(void) mov ecx, SELECTOR_STRET mov eax, SELF_STRET -#if SUPPORT_GC - // check whether selector is ignored -#error oops -#endif - // check whether receiver is nil test eax, eax je NIL @@ -425,11 +405,6 @@ OBJC_EXPORT __declspec(naked) id objc_msgSendSuper_stret(struct objc_super *a, S mov ecx, SELECTOR_STRET mov edx, super_class[eax] -#if SUPPORT_GC - // check whether selector is ignored -#error oops -#endif - // search the cache (class in edx) // CacheLookup WORD_RETURN, MSG_SENDSUPER push edi @@ -543,9 +518,3 @@ OBJC_EXPORT __declspec(naked) void method_invoke_stret(void) jmp eax } } - - -__declspec(naked) id _objc_ignored_method(id obj, SEL sel) -{ - return obj; -} diff --git a/runtime/Messengers.subproj/objc-msg-x86_64.s b/runtime/Messengers.subproj/objc-msg-x86_64.s index 50d7e0b..343b300 100644 --- a/runtime/Messengers.subproj/objc-msg-x86_64.s +++ b/runtime/Messengers.subproj/objc-msg-x86_64.s @@ -22,7 +22,7 @@ */ #include -#if __x86_64__ && !TARGET_IPHONE_SIMULATOR +#if __x86_64__ && !TARGET_OS_SIMULATOR /******************************************************************** ******************************************************************** @@ -32,11 +32,6 @@ ******************************************************************** ********************************************************************/ -/******************************************************************** -* Data used by the ObjC runtime. -* -********************************************************************/ - .data // _objc_entryPoints and _objc_exitPoints are used by objc @@ -55,6 +50,12 @@ _objc_entryPoints: .quad _objc_msgSendSuper_stret .quad _objc_msgSendSuper2 .quad _objc_msgSendSuper2_stret + .quad _objc_msgLookup + .quad _objc_msgLookup_fpret + .quad _objc_msgLookup_fp2ret + .quad _objc_msgLookup_stret + .quad _objc_msgLookupSuper2 + .quad _objc_msgLookupSuper2_stret .quad 0 .private_extern _objc_exitPoints @@ -68,6 +69,12 @@ _objc_exitPoints: .quad LExit_objc_msgSendSuper_stret .quad LExit_objc_msgSendSuper2 .quad LExit_objc_msgSendSuper2_stret + .quad LExit_objc_msgLookup + .quad LExit_objc_msgLookup_fpret + .quad LExit_objc_msgLookup_fp2ret + .quad LExit_objc_msgLookup_stret + .quad LExit_objc_msgLookupSuper2 + .quad LExit_objc_msgLookupSuper2_stret .quad 0 @@ -194,13 +201,12 @@ _gdb_objc_messenger_breakpoints: #define NORMAL 0 #define FPRET 1 #define FP2RET 2 -#define GETIMP 3 -#define STRET 4 -#define SUPER 5 -#define SUPER_STRET 6 -#define SUPER2 7 -#define SUPER2_STRET 8 - +#define STRET 3 + +#define CALL 100 +#define GETIMP 101 +#define LOOKUP 102 + /******************************************************************** * @@ -219,17 +225,6 @@ _gdb_objc_messenger_breakpoints: #define method_name 0 #define method_imp 16 -// typedef struct { -// uint128_t floatingPointArgs[8]; // xmm0..xmm7 -// long linkageArea[4]; // r10, rax, ebp, ret -// long registerArgs[6]; // a1..a6 -// long stackArgs[0]; // variable-size -// } *marg_list; -#define FP_AREA 0 -#define LINK_AREA (FP_AREA+8*16) -#define REG_AREA (LINK_AREA+4*8) -#define STACK_AREA (REG_AREA+6*8) - ////////////////////////////////////////////////////////////////////// // @@ -245,7 +240,6 @@ _gdb_objc_messenger_breakpoints: .globl $0 .align 6, 0x90 $0: - .cfi_startproc .endmacro .macro STATIC_ENTRY @@ -253,7 +247,6 @@ $0: .private_extern $0 .align 2, 0x90 $0: - .cfi_startproc .endmacro ////////////////////////////////////////////////////////////////////// @@ -267,92 +260,27 @@ $0: ////////////////////////////////////////////////////////////////////// .macro END_ENTRY - .cfi_endproc LExit$0: .endmacro -///////////////////////////////////////////////////////////////////// -// -// SaveRegisters -// -// Pushes a stack frame and saves all registers that might contain -// parameter values. -// -// On entry: -// stack = ret -// -// On exit: -// %rsp is 16-byte aligned -// -///////////////////////////////////////////////////////////////////// - -.macro SaveRegisters - - push %rbp - .cfi_def_cfa_offset 16 - .cfi_offset rbp, -16 - - mov %rsp, %rbp - .cfi_def_cfa_register rbp - - sub $$0x80+8, %rsp // +8 for alignment - - movdqa %xmm0, -0x80(%rbp) - push %rax // might be xmm parameter count - movdqa %xmm1, -0x70(%rbp) - push %a1 - movdqa %xmm2, -0x60(%rbp) - push %a2 - movdqa %xmm3, -0x50(%rbp) - push %a3 - movdqa %xmm4, -0x40(%rbp) - push %a4 - movdqa %xmm5, -0x30(%rbp) - push %a5 - movdqa %xmm6, -0x20(%rbp) - push %a6 - movdqa %xmm7, -0x10(%rbp) - + /******************************************************************** + * UNWIND name, flags + * Unwind info generation + ********************************************************************/ +.macro UNWIND + .section __LD,__compact_unwind,regular,debug + .quad $0 + .set LUnwind$0, LExit$0 - $0 + .long LUnwind$0 + .long $1 + .quad 0 /* no personality */ + .quad 0 /* no LSDA */ + .text .endmacro -///////////////////////////////////////////////////////////////////// -// -// RestoreRegisters -// -// Pops a stack frame pushed by SaveRegisters -// -// On entry: -// %rbp unchanged since SaveRegisters -// -// On exit: -// stack = ret -// -///////////////////////////////////////////////////////////////////// - -.macro RestoreRegisters - - movdqa -0x80(%rbp), %xmm0 - pop %a6 - movdqa -0x70(%rbp), %xmm1 - pop %a5 - movdqa -0x60(%rbp), %xmm2 - pop %a4 - movdqa -0x50(%rbp), %xmm3 - pop %a3 - movdqa -0x40(%rbp), %xmm4 - pop %a2 - movdqa -0x30(%rbp), %xmm5 - pop %a1 - movdqa -0x20(%rbp), %xmm6 - pop %rax - movdqa -0x10(%rbp), %xmm7 - - leave - .cfi_def_cfa rsp, 8 - .cfi_same_value rbp - -.endmacro +#define NoFrame 0x02010000 // no frame, no SP adjustment except return address +#define FrameWithNoSaves 0x01000000 // frame, no non-volatile saves ///////////////////////////////////////////////////////////////////// @@ -362,13 +290,15 @@ LExit$0: // Locate the implementation for a class in a selector's method cache. // // Takes: -// $0 = NORMAL, FPRET, FP2RET, STRET, SUPER, SUPER_STRET, SUPER2, SUPER2_STRET, GETIMP -// a2 or a3 (STRET) = selector a.k.a. cache -// r11 = class to search +// $0 = NORMAL, FPRET, FP2RET, STRET +// $1 = CALL, LOOKUP, GETIMP +// a1 or a2 (STRET) = receiver +// a2 or a3 (STRET) = selector +// r10 = class to search // // On exit: r10 clobbered -// (found) calls or returns IMP, eq/ne/r11 set for forwarding -// (not found) jumps to LCacheMiss, class still in r11 +// (found) calls or returns IMP in r11, eq/ne set for forwarding +// (not found) jumps to LCacheMiss, class still in r10 // ///////////////////////////////////////////////////////////////////// @@ -377,95 +307,77 @@ LExit$0: // CacheHit must always be preceded by a not-taken `jne` instruction // in order to set the correct flags for _objc_msgForward_impcache. - // r10 = found bucket + // r11 = found bucket -.if $0 == GETIMP - movq 8(%r10), %rax // return imp - leaq __objc_msgSend_uncached_impcache(%rip), %r11 - cmpq %rax, %r11 - jne 4f - xorl %eax, %eax // don't return msgSend_uncached -4: ret -.elseif $0 == NORMAL || $0 == FPRET || $0 == FP2RET +.if $1 == GETIMP + movq 8(%r11), %rax // return imp + ret + +.else + +.if $0 != STRET // eq already set for forwarding by `jne` +.else + test %r11, %r11 // set ne for stret forwarding +.endif + +.if $1 == CALL MESSENGER_END_FAST - jmp *8(%r10) // call imp - -.elseif $0 == SUPER - movq receiver(%a1), %a1 // load real receiver - cmp %r10, %r10 // set eq for non-stret forwarding - MESSENGER_END_FAST - jmp *8(%r10) // call imp - -.elseif $0 == SUPER2 - movq receiver(%a1), %a1 // load real receiver - cmp %r10, %r10 // set eq for non-stret forwarding - MESSENGER_END_FAST - jmp *8(%r10) // call imp - -.elseif $0 == STRET - test %r10, %r10 // set ne for stret forwarding - MESSENGER_END_FAST - jmp *8(%r10) // call imp + jmp *8(%r11) // call imp -.elseif $0 == SUPER_STRET - movq receiver(%a2), %a2 // load real receiver - test %r10, %r10 // set ne for stret forwarding - MESSENGER_END_FAST - jmp *8(%r10) // call imp +.elseif $1 == LOOKUP + movq 8(%r11), %r11 // return imp + ret -.elseif $0 == SUPER2_STRET - movq receiver(%a2), %a2 // load real receiver - test %r10, %r10 // set ne for stret forwarding - MESSENGER_END_FAST - jmp *8(%r10) // call imp .else .abort oops .endif - + +.endif + .endmacro .macro CacheLookup -.if $0 != STRET && $0 != SUPER_STRET && $0 != SUPER2_STRET - movq %a2, %r10 // r10 = _cmd +.if $0 != STRET + movq %a2, %r11 // r11 = _cmd .else - movq %a3, %r10 // r10 = _cmd + movq %a3, %r11 // r11 = _cmd .endif - andl 24(%r11), %r10d // r10 = _cmd & class->cache.mask - shlq $$4, %r10 // r10 = offset = (_cmd & mask)<<4 - addq 16(%r11), %r10 // r10 = class->cache.buckets + offset + andl 24(%r10), %r11d // r11 = _cmd & class->cache.mask + shlq $$4, %r11 // r11 = offset = (_cmd & mask)<<4 + addq 16(%r10), %r11 // r11 = class->cache.buckets + offset -.if $0 != STRET && $0 != SUPER_STRET && $0 != SUPER2_STRET - cmpq (%r10), %a2 // if (bucket->sel != _cmd) +.if $0 != STRET + cmpq (%r11), %a2 // if (bucket->sel != _cmd) .else - cmpq (%r10), %a3 // if (bucket->sel != _cmd) + cmpq (%r11), %a3 // if (bucket->sel != _cmd) .endif jne 1f // scan more // CacheHit must always be preceded by a not-taken `jne` instruction - CacheHit $0 // call or return imp + CacheHit $0, $1 // call or return imp 1: // loop - cmpq $$1, (%r10) + cmpq $$1, (%r11) jbe 3f // if (bucket->sel <= 1) wrap or miss - addq $$16, %r10 // bucket++ + addq $$16, %r11 // bucket++ 2: -.if $0 != STRET && $0 != SUPER_STRET && $0 != SUPER2_STRET - cmpq (%r10), %a2 // if (bucket->sel != _cmd) +.if $0 != STRET + cmpq (%r11), %a2 // if (bucket->sel != _cmd) .else - cmpq (%r10), %a3 // if (bucket->sel != _cmd) + cmpq (%r11), %a3 // if (bucket->sel != _cmd) .endif jne 1b // scan more // CacheHit must always be preceded by a not-taken `jne` instruction - CacheHit $0 // call or return imp + CacheHit $0, $1 // call or return imp 3: // wrap or miss jb LCacheMiss_f // if (bucket->sel < 1) cache miss // wrap - movq 8(%r10), %r10 // bucket->imp is really first bucket + movq 8(%r11), %r11 // bucket->imp is really first bucket jmp 2f // Clone scanning loop to miss instead of hang when cache is corrupt. @@ -473,19 +385,19 @@ LExit$0: 1: // loop - cmpq $$1, (%r10) + cmpq $$1, (%r11) jbe 3f // if (bucket->sel <= 1) wrap or miss - addq $$16, %r10 // bucket++ + addq $$16, %r11 // bucket++ 2: -.if $0 != STRET && $0 != SUPER_STRET && $0 != SUPER2_STRET - cmpq (%r10), %a2 // if (bucket->sel != _cmd) +.if $0 != STRET + cmpq (%r11), %a2 // if (bucket->sel != _cmd) .else - cmpq (%r10), %a3 // if (bucket->sel != _cmd) + cmpq (%r11), %a3 // if (bucket->sel != _cmd) .endif jne 1b // scan more // CacheHit must always be preceded by a not-taken `jne` instruction - CacheHit $0 // call or return imp + CacheHit $0, $1 // call or return imp 3: // double wrap or miss @@ -496,47 +408,93 @@ LExit$0: ///////////////////////////////////////////////////////////////////// // -// MethodTableLookup classRegister, selectorRegister +// MethodTableLookup NORMAL|STRET // -// Takes: $0 = class to search (a1 or a2 or r10 ONLY) -// $1 = selector to search for (a2 or a3 ONLY) -// r11 = class to search +// Takes: a1 or a2 (STRET) = receiver +// a2 or a3 (STRET) = selector to search for +// r10 = class to search // -// On exit: imp in %r11 +// On exit: imp in %r11, eq/ne set for forwarding // ///////////////////////////////////////////////////////////////////// + .macro MethodTableLookup - MESSENGER_END_SLOW + push %rbp + mov %rsp, %rbp - SaveRegisters + sub $$0x80+8, %rsp // +8 for alignment + + movdqa %xmm0, -0x80(%rbp) + push %rax // might be xmm parameter count + movdqa %xmm1, -0x70(%rbp) + push %a1 + movdqa %xmm2, -0x60(%rbp) + push %a2 + movdqa %xmm3, -0x50(%rbp) + push %a3 + movdqa %xmm4, -0x40(%rbp) + push %a4 + movdqa %xmm5, -0x30(%rbp) + push %a5 + movdqa %xmm6, -0x20(%rbp) + push %a6 + movdqa %xmm7, -0x10(%rbp) // _class_lookupMethodAndLoadCache3(receiver, selector, class) - movq $0, %a1 - movq $1, %a2 - movq %r11, %a3 +.if $0 == NORMAL + // receiver already in a1 + // selector already in a2 +.else + movq %a2, %a1 + movq %a3, %a2 +.endif + movq %r10, %a3 call __class_lookupMethodAndLoadCache3 // IMP is now in %rax movq %rax, %r11 - RestoreRegisters + movdqa -0x80(%rbp), %xmm0 + pop %a6 + movdqa -0x70(%rbp), %xmm1 + pop %a5 + movdqa -0x60(%rbp), %xmm2 + pop %a4 + movdqa -0x50(%rbp), %xmm3 + pop %a3 + movdqa -0x40(%rbp), %xmm4 + pop %a2 + movdqa -0x30(%rbp), %xmm5 + pop %a1 + movdqa -0x20(%rbp), %xmm6 + pop %rax + movdqa -0x10(%rbp), %xmm7 + +.if $0 == NORMAL + cmp %r11, %r11 // set eq for nonstret forwarding +.else + test %r11, %r11 // set ne for stret forwarding +.endif + + leave .endmacro + ///////////////////////////////////////////////////////////////////// // // GetIsaFast return-type // GetIsaSupport return-type // -// Sets r11 = obj->isa. Consults the tagged isa table if necessary. +// Sets r10 = obj->isa. Consults the tagged isa table if necessary. // // Takes: $0 = NORMAL or FPRET or FP2RET or STRET // a1 or a2 (STRET) = receiver // -// On exit: r11 = receiver->isa -// r10 is clobbered +// On exit: r10 = receiver->isa +// r11 is clobbered // ///////////////////////////////////////////////////////////////////// @@ -545,40 +503,44 @@ LExit$0: testb $$1, %a1b PN jnz LGetIsaSlow_f - movq $$0x00007ffffffffff8, %r11 - andq (%a1), %r11 + movq $$0x00007ffffffffff8, %r10 + andq (%a1), %r10 .else testb $$1, %a2b PN jnz LGetIsaSlow_f - movq $$0x00007ffffffffff8, %r11 - andq (%a2), %r11 + movq $$0x00007ffffffffff8, %r10 + andq (%a2), %r10 .endif LGetIsaDone: .endmacro -.macro GetIsaSupport2 +.macro GetIsaSupport LGetIsaSlow: - leaq _objc_debug_taggedpointer_classes(%rip), %r11 .if $0 != STRET - movl %a1d, %r10d + movl %a1d, %r11d .else - movl %a2d, %r10d + movl %a2d, %r11d .endif - andl $$0xF, %r10d - movq (%r11, %r10, 8), %r11 // read isa from table -.endmacro - -.macro GetIsaSupport - GetIsaSupport2 $0 + andl $$0xF, %r11d + cmp $$0xF, %r11d + je 1f + // basic tagged + leaq _objc_debug_taggedpointer_classes(%rip), %r10 + movq (%r10, %r11, 8), %r10 // read isa from table + jmp LGetIsaDone_b +1: + // extended tagged +.if $0 != STRET + movl %a1d, %r11d +.else + movl %a2d, %r11d +.endif + shrl $$4, %r11d + andl $$0xFF, %r11d + leaq _objc_debug_taggedpointer_ext_classes(%rip), %r10 + movq (%r10, %r11, 8), %r10 // read isa from table jmp LGetIsaDone_b -.endmacro - -.macro GetIsa - GetIsaFast $0 - jmp LGetIsaDone_f - GetIsaSupport2 $0 -LGetIsaDone: .endmacro @@ -589,22 +551,72 @@ LGetIsaDone: // Takes: $0 = NORMAL or FPRET or FP2RET or STRET // %a1 or %a2 (STRET) = receiver // -// On exit: Loads non-nil receiver in %a1 or %a2 (STRET), or returns zero. +// On exit: Loads non-nil receiver in %a1 or %a2 (STRET) +// or returns. // -// NilTestSupport return-type +// NilTestReturnZero return-type // // Takes: $0 = NORMAL or FPRET or FP2RET or STRET // %a1 or %a2 (STRET) = receiver // -// On exit: Loads non-nil receiver in %a1 or %a2 (STRET), or returns zero. +// On exit: Loads non-nil receiver in %a1 or %a2 (STRET) +// or returns zero. +// +// NilTestReturnIMP return-type +// +// Takes: $0 = NORMAL or FPRET or FP2RET or STRET +// %a1 or %a2 (STRET) = receiver +// +// On exit: Loads non-nil receiver in %a1 or %a2 (STRET) +// or returns an IMP in r11 that returns zero. // ///////////////////////////////////////////////////////////////////// -.macro NilTest -.if $0 == SUPER || $0 == SUPER_STRET - error super dispatch does not test for nil -.endif +.macro ZeroReturn + xorl %eax, %eax + xorl %edx, %edx + xorps %xmm0, %xmm0 + xorps %xmm1, %xmm1 +.endmacro + +.macro ZeroReturnFPRET + fldz + ZeroReturn +.endmacro +.macro ZeroReturnFP2RET + fldz + fldz + ZeroReturn +.endmacro + +.macro ZeroReturnSTRET + // rax gets the struct-return address as passed in rdi + movq %rdi, %rax +.endmacro + + STATIC_ENTRY __objc_msgNil + ZeroReturn + ret + END_ENTRY __objc_msgNil + + STATIC_ENTRY __objc_msgNil_fpret + ZeroReturnFPRET + ret + END_ENTRY __objc_msgNil_fpret + + STATIC_ENTRY __objc_msgNil_fp2ret + ZeroReturnFP2RET + ret + END_ENTRY __objc_msgNil_fp2ret + + STATIC_ENTRY __objc_msgNil_stret + ZeroReturnSTRET + ret + END_ENTRY __objc_msgNil_stret + + +.macro NilTest .if $0 != STRET testq %a1, %a1 .else @@ -614,24 +626,42 @@ LGetIsaDone: jz LNilTestSlow_f .endmacro -.macro NilTestSupport + +.macro NilTestReturnZero .align 3 LNilTestSlow: -.if $0 == FPRET - fldz + +.if $0 == NORMAL + ZeroReturn +.elseif $0 == FPRET + ZeroReturnFPRET .elseif $0 == FP2RET - fldz - fldz -.endif -.if $0 == STRET - movq %rdi, %rax + ZeroReturnFP2RET +.elseif $0 == STRET + ZeroReturnSTRET .else - xorl %eax, %eax - xorl %edx, %edx - xorps %xmm0, %xmm0 - xorps %xmm1, %xmm1 +.abort oops .endif MESSENGER_END_NIL + ret +.endmacro + + +.macro NilTestReturnIMP + .align 3 +LNilTestSlow: + +.if $0 == NORMAL + leaq __objc_msgNil(%rip), %r11 +.elseif $0 == FPRET + leaq __objc_msgNil_fpret(%rip), %r11 +.elseif $0 == FP2RET + leaq __objc_msgNil_fp2ret(%rip), %r11 +.elseif $0 == STRET + leaq __objc_msgNil_stret(%rip), %r11 +.else +.abort oops +.endif ret .endmacro @@ -649,21 +679,26 @@ LNilTestSlow: STATIC_ENTRY _cache_getImp // do lookup - movq %a1, %r11 // move class to r11 for CacheLookup - CacheLookup GETIMP // returns IMP on success + movq %a1, %r10 // move class to r10 for CacheLookup + CacheLookup NORMAL, GETIMP // returns IMP on success LCacheMiss: // cache miss, return nil xorl %eax, %eax ret -LGetImpExit: - END_ENTRY _cache_getImp + END_ENTRY _cache_getImp /******************************************************************** * * id objc_msgSend(id self, SEL _cmd,...); + * IMP objc_msgLookup(id self, SEL _cmd, ...); + * + * objc_msgLookup ABI: + * IMP returned in r11 + * Forwarding returned in Z flag + * r10 reserved for our use but not used * ********************************************************************/ @@ -672,27 +707,49 @@ LGetImpExit: .globl _objc_debug_taggedpointer_classes _objc_debug_taggedpointer_classes: .fill 16, 8, 0 + .globl _objc_debug_taggedpointer_ext_classes +_objc_debug_taggedpointer_ext_classes: + .fill 256, 8, 0 - ENTRY _objc_msgSend + ENTRY _objc_msgSend + UNWIND _objc_msgSend, NoFrame MESSENGER_START NilTest NORMAL - GetIsaFast NORMAL // r11 = self->isa - CacheLookup NORMAL // calls IMP on success + GetIsaFast NORMAL // r10 = self->isa + CacheLookup NORMAL, CALL // calls IMP on success - NilTestSupport NORMAL + NilTestReturnZero NORMAL - GetIsaSupport NORMAL + GetIsaSupport NORMAL // cache miss: go search the method lists LCacheMiss: - // isa still in r11 - MethodTableLookup %a1, %a2 // r11 = IMP - cmp %r11, %r11 // set eq (nonstret) for forwarding - jmp *%r11 // goto *imp + // isa still in r10 + MESSENGER_END_SLOW + jmp __objc_msgSend_uncached - END_ENTRY _objc_msgSend + END_ENTRY _objc_msgSend + + + ENTRY _objc_msgLookup + + NilTest NORMAL + + GetIsaFast NORMAL // r10 = self->isa + CacheLookup NORMAL, LOOKUP // returns IMP on success + + NilTestReturnIMP NORMAL + + GetIsaSupport NORMAL + +// cache miss: go search the method lists +LCacheMiss: + // isa still in r10 + jmp __objc_msgLookup_uncached + + END_ENTRY _objc_msgLookup ENTRY _objc_msgSend_fixup @@ -717,23 +774,22 @@ LCacheMiss: * }; ********************************************************************/ - ENTRY _objc_msgSendSuper + ENTRY _objc_msgSendSuper + UNWIND _objc_msgSendSuper, NoFrame MESSENGER_START // search the cache (objc_super in %a1) - movq class(%a1), %r11 // class = objc_super->class - CacheLookup SUPER // calls IMP on success + movq class(%a1), %r10 // class = objc_super->class + movq receiver(%a1), %a1 // load real receiver + CacheLookup NORMAL, CALL // calls IMP on success // cache miss: go search the method lists LCacheMiss: - // class still in r11 - movq receiver(%a1), %r10 - MethodTableLookup %r10, %a2 // r11 = IMP - movq receiver(%a1), %a1 // load real receiver - cmp %r11, %r11 // set eq (nonstret) for forwarding - jmp *%r11 // goto *imp + // class still in r10 + MESSENGER_END_SLOW + jmp __objc_msgSend_uncached - END_ENTRY _objc_msgSendSuper + END_ENTRY _objc_msgSendSuper /******************************************************************** @@ -741,27 +797,44 @@ LCacheMiss: ********************************************************************/ ENTRY _objc_msgSendSuper2 + UNWIND _objc_msgSendSuper2, NoFrame MESSENGER_START // objc_super->class is superclass of class to search // search the cache (objc_super in %a1) - movq class(%a1), %r11 // cls = objc_super->class - movq 8(%r11), %r11 // cls = class->superclass - CacheLookup SUPER2 // calls IMP on success + movq class(%a1), %r10 // cls = objc_super->class + movq receiver(%a1), %a1 // load real receiver + movq 8(%r10), %r10 // cls = class->superclass + CacheLookup NORMAL, CALL // calls IMP on success // cache miss: go search the method lists LCacheMiss: - // superclass still in r11 - movq receiver(%a1), %r10 - MethodTableLookup %r10, %a2 // r11 = IMP - movq receiver(%a1), %a1 // load real receiver - cmp %r11, %r11 // set eq (nonstret) for forwarding - jmp *%r11 // goto *imp + // superclass still in r10 + MESSENGER_END_SLOW + jmp __objc_msgSend_uncached + + END_ENTRY _objc_msgSendSuper2 + + + ENTRY _objc_msgLookupSuper2 + + // objc_super->class is superclass of class to search - END_ENTRY _objc_msgSendSuper2 +// search the cache (objc_super in %a1) + movq class(%a1), %r10 // cls = objc_super->class + movq receiver(%a1), %a1 // load real receiver + movq 8(%r10), %r10 // cls = class->superclass + CacheLookup NORMAL, LOOKUP // returns IMP on success +// cache miss: go search the method lists +LCacheMiss: + // superclass still in r10 + jmp __objc_msgLookup_uncached + END_ENTRY _objc_msgLookupSuper2 + + ENTRY _objc_msgSendSuper2_fixup int3 END_ENTRY _objc_msgSendSuper2_fixup @@ -781,26 +854,45 @@ LCacheMiss: * ********************************************************************/ - ENTRY _objc_msgSend_fpret + ENTRY _objc_msgSend_fpret + UNWIND _objc_msgSend_fpret, NoFrame MESSENGER_START NilTest FPRET - GetIsaFast FPRET // r11 = self->isa - CacheLookup FPRET // calls IMP on success + GetIsaFast FPRET // r10 = self->isa + CacheLookup FPRET, CALL // calls IMP on success - NilTestSupport FPRET + NilTestReturnZero FPRET - GetIsaSupport FPRET + GetIsaSupport FPRET // cache miss: go search the method lists LCacheMiss: - // isa still in r11 - MethodTableLookup %a1, %a2 // r11 = IMP - cmp %r11, %r11 // set eq (nonstret) for forwarding - jmp *%r11 // goto *imp + // isa still in r10 + MESSENGER_END_SLOW + jmp __objc_msgSend_uncached + + END_ENTRY _objc_msgSend_fpret - END_ENTRY _objc_msgSend_fpret + + ENTRY _objc_msgLookup_fpret + + NilTest FPRET + + GetIsaFast FPRET // r10 = self->isa + CacheLookup FPRET, LOOKUP // returns IMP on success + + NilTestReturnIMP FPRET + + GetIsaSupport FPRET + +// cache miss: go search the method lists +LCacheMiss: + // isa still in r10 + jmp __objc_msgLookup_uncached + + END_ENTRY _objc_msgLookup_fpret ENTRY _objc_msgSend_fpret_fixup @@ -822,26 +914,45 @@ LCacheMiss: * ********************************************************************/ - ENTRY _objc_msgSend_fp2ret + ENTRY _objc_msgSend_fp2ret + UNWIND _objc_msgSend_fp2ret, NoFrame MESSENGER_START NilTest FP2RET - GetIsaFast FP2RET // r11 = self->isa - CacheLookup FP2RET // calls IMP on success + GetIsaFast FP2RET // r10 = self->isa + CacheLookup FP2RET, CALL // calls IMP on success - NilTestSupport FP2RET + NilTestReturnZero FP2RET - GetIsaSupport FP2RET + GetIsaSupport FP2RET // cache miss: go search the method lists LCacheMiss: - // isa still in r11 - MethodTableLookup %a1, %a2 // r11 = IMP - cmp %r11, %r11 // set eq (nonstret) for forwarding - jmp *%r11 // goto *imp + // isa still in r10 + MESSENGER_END_SLOW + jmp __objc_msgSend_uncached + + END_ENTRY _objc_msgSend_fp2ret + + + ENTRY _objc_msgLookup_fp2ret + + NilTest FP2RET - END_ENTRY _objc_msgSend_fp2ret + GetIsaFast FP2RET // r10 = self->isa + CacheLookup FP2RET, LOOKUP // returns IMP on success + + NilTestReturnIMP FP2RET + + GetIsaSupport FP2RET + +// cache miss: go search the method lists +LCacheMiss: + // isa still in r10 + jmp __objc_msgLookup_uncached + + END_ENTRY _objc_msgLookup_fp2ret ENTRY _objc_msgSend_fp2ret_fixup @@ -869,26 +980,45 @@ LCacheMiss: * %a3 is the selector ********************************************************************/ - ENTRY _objc_msgSend_stret + ENTRY _objc_msgSend_stret + UNWIND _objc_msgSend_stret, NoFrame MESSENGER_START NilTest STRET - GetIsaFast STRET // r11 = self->isa - CacheLookup STRET // calls IMP on success + GetIsaFast STRET // r10 = self->isa + CacheLookup STRET, CALL // calls IMP on success - NilTestSupport STRET + NilTestReturnZero STRET - GetIsaSupport STRET + GetIsaSupport STRET // cache miss: go search the method lists LCacheMiss: - // isa still in r11 - MethodTableLookup %a2, %a3 // r11 = IMP - test %r11, %r11 // set ne (stret) for forward; r11!=0 - jmp *%r11 // goto *imp + // isa still in r10 + MESSENGER_END_SLOW + jmp __objc_msgSend_stret_uncached + + END_ENTRY _objc_msgSend_stret + - END_ENTRY _objc_msgSend_stret + ENTRY _objc_msgLookup_stret + + NilTest STRET + + GetIsaFast STRET // r10 = self->isa + CacheLookup STRET, LOOKUP // returns IMP on success + + NilTestReturnIMP STRET + + GetIsaSupport STRET + +// cache miss: go search the method lists +LCacheMiss: + // isa still in r10 + jmp __objc_msgLookup_stret_uncached + + END_ENTRY _objc_msgLookup_stret ENTRY _objc_msgSend_stret_fixup @@ -922,47 +1052,61 @@ LCacheMiss: * ********************************************************************/ - ENTRY _objc_msgSendSuper_stret + ENTRY _objc_msgSendSuper_stret + UNWIND _objc_msgSendSuper_stret, NoFrame MESSENGER_START // search the cache (objc_super in %a2) - movq class(%a2), %r11 // class = objc_super->class - CacheLookup SUPER_STRET // calls IMP on success + movq class(%a2), %r10 // class = objc_super->class + movq receiver(%a2), %a2 // load real receiver + CacheLookup STRET, CALL // calls IMP on success // cache miss: go search the method lists LCacheMiss: - // class still in r11 - movq receiver(%a2), %r10 - MethodTableLookup %r10, %a3 // r11 = IMP - movq receiver(%a2), %a2 // load real receiver - test %r11, %r11 // set ne (stret) for forward; r11!=0 - jmp *%r11 // goto *imp - - END_ENTRY _objc_msgSendSuper_stret + // class still in r10 + MESSENGER_END_SLOW + jmp __objc_msgSend_stret_uncached + + END_ENTRY _objc_msgSendSuper_stret /******************************************************************** * id objc_msgSendSuper2_stret ********************************************************************/ - ENTRY _objc_msgSendSuper2_stret + ENTRY _objc_msgSendSuper2_stret + UNWIND _objc_msgSendSuper2_stret, NoFrame MESSENGER_START // search the cache (objc_super in %a2) - movq class(%a2), %r11 // class = objc_super->class - movq 8(%r11), %r11 // class = class->superclass - CacheLookup SUPER2_STRET // calls IMP on success + movq class(%a2), %r10 // class = objc_super->class + movq receiver(%a2), %a2 // load real receiver + movq 8(%r10), %r10 // class = class->superclass + CacheLookup STRET, CALL // calls IMP on success // cache miss: go search the method lists LCacheMiss: - // superclass still in r11 - movq receiver(%a2), %r10 - MethodTableLookup %r10, %a3 // r11 = IMP + // superclass still in r10 + MESSENGER_END_SLOW + jmp __objc_msgSend_stret_uncached + + END_ENTRY _objc_msgSendSuper2_stret + + + ENTRY _objc_msgLookupSuper2_stret + +// search the cache (objc_super in %a2) + movq class(%a2), %r10 // class = objc_super->class movq receiver(%a2), %a2 // load real receiver - test %r11, %r11 // set ne (stret) for forward; r11!=0 - jmp *%r11 // goto *imp + movq 8(%r10), %r10 // class = class->superclass + CacheLookup STRET, LOOKUP // returns IMP on success + +// cache miss: go search the method lists +LCacheMiss: + // superclass still in r10 + jmp __objc_msgLookup_stret_uncached - END_ENTRY _objc_msgSendSuper2_stret + END_ENTRY _objc_msgLookupSuper2_stret ENTRY _objc_msgSendSuper2_stret_fixup @@ -979,57 +1123,67 @@ LCacheMiss: /******************************************************************** * - * _objc_msgSend_uncached_impcache * _objc_msgSend_uncached * _objc_msgSend_stret_uncached - * - * Used to erase method cache entries in-place by - * bouncing them to the uncached lookup. + * _objc_msgLookup_uncached + * _objc_msgLookup_stret_uncached + * + * The uncached method lookup. * ********************************************************************/ - - STATIC_ENTRY __objc_msgSend_uncached_impcache - // Method cache version - - // THIS IS NOT A CALLABLE C FUNCTION - // Out-of-band condition register is NE for stret, EQ otherwise. - // Out-of-band r11 is the searched class - - MESSENGER_START - nop - MESSENGER_END_SLOW - - jne __objc_msgSend_stret_uncached - jmp __objc_msgSend_uncached - - END_ENTRY __objc_msgSend_uncached_impcache - STATIC_ENTRY __objc_msgSend_uncached - + UNWIND __objc_msgSend_uncached, FrameWithNoSaves + // THIS IS NOT A CALLABLE C FUNCTION - // Out-of-band r11 is the searched class + // Out-of-band r10 is the searched class - // r11 is already the class to search - MethodTableLookup %a1, %a2 // r11 = IMP - cmp %r11, %r11 // set eq (nonstret) for forwarding + // r10 is already the class to search + MethodTableLookup NORMAL // r11 = IMP jmp *%r11 // goto *imp END_ENTRY __objc_msgSend_uncached STATIC_ENTRY __objc_msgSend_stret_uncached + UNWIND __objc_msgSend_stret_uncached, FrameWithNoSaves + // THIS IS NOT A CALLABLE C FUNCTION - // Out-of-band r11 is the searched class + // Out-of-band r10 is the searched class - // r11 is already the class to search - MethodTableLookup %a2, %a3 // r11 = IMP - test %r11, %r11 // set ne (stret) for forward; r11!=0 + // r10 is already the class to search + MethodTableLookup STRET // r11 = IMP jmp *%r11 // goto *imp END_ENTRY __objc_msgSend_stret_uncached + STATIC_ENTRY __objc_msgLookup_uncached + UNWIND __objc_msgLookup_uncached, FrameWithNoSaves + + // THIS IS NOT A CALLABLE C FUNCTION + // Out-of-band r10 is the searched class + + // r10 is already the class to search + MethodTableLookup NORMAL // r11 = IMP + ret + + END_ENTRY __objc_msgLookup_uncached + + + STATIC_ENTRY __objc_msgLookup_stret_uncached + UNWIND __objc_msgLookup_stret_uncached, FrameWithNoSaves + + // THIS IS NOT A CALLABLE C FUNCTION + // Out-of-band r10 is the searched class + + // r10 is already the class to search + MethodTableLookup STRET // r11 = IMP + ret + + END_ENTRY __objc_msgLookup_stret_uncached + + /******************************************************************** * * id _objc_msgForward(id self, SEL _cmd,...); @@ -1041,7 +1195,7 @@ LCacheMiss: * ********************************************************************/ - STATIC_ENTRY __objc_msgForward_impcache + STATIC_ENTRY __objc_msgForward_impcache // Method cache version // THIS IS NOT A CALLABLE C FUNCTION @@ -1054,25 +1208,25 @@ LCacheMiss: jne __objc_msgForward_stret jmp __objc_msgForward - END_ENTRY __objc_msgForward_impcache + END_ENTRY __objc_msgForward_impcache - ENTRY __objc_msgForward + ENTRY __objc_msgForward // Non-stret version movq __objc_forward_handler(%rip), %r11 jmp *%r11 - END_ENTRY __objc_msgForward + END_ENTRY __objc_msgForward - ENTRY __objc_msgForward_stret + ENTRY __objc_msgForward_stret // Struct-return version movq __objc_forward_stret_handler(%rip), %r11 jmp *%r11 - END_ENTRY __objc_msgForward_stret + END_ENTRY __objc_msgForward_stret ENTRY _objc_msgSend_debug @@ -1123,14 +1277,6 @@ LCacheMiss: END_ENTRY _method_invoke_stret - STATIC_ENTRY __objc_ignored_method - - movq %a1, %rax - ret - - END_ENTRY __objc_ignored_method - - .section __DATA,__objc_msg_break .quad 0 .quad 0 diff --git a/runtime/NSObject.h b/runtime/NSObject.h index efce452..f42b446 100644 --- a/runtime/NSObject.h +++ b/runtime/NSObject.h @@ -47,7 +47,7 @@ @end -__OSX_AVAILABLE_STARTING(__MAC_10_0, __IPHONE_2_0) +OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0) OBJC_ROOT_CLASS OBJC_EXPORT @interface NSObject { @@ -68,7 +68,7 @@ OBJC_EXPORT + (instancetype)alloc OBJC_SWIFT_UNAVAILABLE("use object initializers instead"); - (void)dealloc OBJC_SWIFT_UNAVAILABLE("use 'deinit' to define a de-initializer"); -- (void)finalize; +- (void)finalize OBJC_DEPRECATED("Objective-C garbage collection is no longer supported"); - (id)copy; - (id)mutableCopy; @@ -82,7 +82,7 @@ OBJC_EXPORT + (IMP)instanceMethodForSelector:(SEL)aSelector; - (void)doesNotRecognizeSelector:(SEL)aSelector; -- (id)forwardingTargetForSelector:(SEL)aSelector __OSX_AVAILABLE_STARTING(__MAC_10_5, __IPHONE_2_0); +- (id)forwardingTargetForSelector:(SEL)aSelector OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0); - (void)forwardInvocation:(NSInvocation *)anInvocation OBJC_SWIFT_UNAVAILABLE(""); - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector OBJC_SWIFT_UNAVAILABLE(""); @@ -93,8 +93,8 @@ OBJC_EXPORT + (BOOL)isSubclassOfClass:(Class)aClass; -+ (BOOL)resolveClassMethod:(SEL)sel __OSX_AVAILABLE_STARTING(__MAC_10_5, __IPHONE_2_0); -+ (BOOL)resolveInstanceMethod:(SEL)sel __OSX_AVAILABLE_STARTING(__MAC_10_5, __IPHONE_2_0); ++ (BOOL)resolveClassMethod:(SEL)sel OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0); ++ (BOOL)resolveInstanceMethod:(SEL)sel OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0); + (NSUInteger)hash; + (Class)superclass; diff --git a/runtime/NSObject.mm b/runtime/NSObject.mm index 0559c8b..c7e7f43 100644 --- a/runtime/NSObject.mm +++ b/runtime/NSObject.mm @@ -80,7 +80,7 @@ - (SEL)selector; NSOBJECT_ELSEWHERE_IN(2.2); NSOBJECT_ELSEWHERE_IN(2.1); NSOBJECT_ELSEWHERE_IN(2.0); -#elif TARGET_OS_MAC && !TARGET_OS_IPHONE +#elif TARGET_OS_OSX NSOBJECT_ELSEWHERE_IN(10.7); NSOBJECT_ELSEWHERE_IN(10.6); NSOBJECT_ELSEWHERE_IN(10.5); @@ -151,7 +151,6 @@ void _objc_setBadAllocHandler(id(*newHandler)(Class)) void lock() { slock.lock(); } void unlock() { slock.unlock(); } - bool trylock() { return slock.trylock(); } // Address-ordered lock discipline for a pair of side tables. @@ -440,27 +439,70 @@ BOOL objc_should_deallocate(id object) { } +/* + Once upon a time we eagerly cleared *location if we saw the object + was deallocating. This confuses code like NSPointerFunctions which + tries to pre-flight the raw storage and assumes if the storage is + zero then the weak system is done interfering. That is false: the + weak system is still going to check and clear the storage later. + This can cause objc_weak_error complaints and crashes. + So we now don't touch the storage until deallocation completes. +*/ + id objc_loadWeakRetained(id *location) { + id obj; id result; + Class cls; SideTable *table; retry: - result = *location; - if (!result) return nil; + // fixme std::atomic this load + obj = *location; + if (!obj) return nil; + if (obj->isTaggedPointer()) return obj; - table = &SideTables()[result]; + table = &SideTables()[obj]; table->lock(); - if (*location != result) { + if (*location != obj) { table->unlock(); goto retry; } - - result = weak_read_no_lock(&table->weak_table, location); - + + result = obj; + + cls = obj->ISA(); + if (! cls->hasCustomRR()) { + // Fast case. We know +initialize is complete because + // default-RR can never be set before then. + assert(cls->isInitialized()); + if (! obj->rootTryRetain()) { + result = nil; + } + } + else { + // Slow case. We must check for +initialize and call it outside + // the lock if necessary in order to avoid deadlocks. + if (cls->isInitialized() || _thisThreadIsInitializingClass(cls)) { + BOOL (*tryRetain)(id, SEL) = (BOOL(*)(id, SEL)) + class_getMethodImplementation(cls, SEL_retainWeakReference); + if ((IMP)tryRetain == _objc_msgForward) { + result = nil; + } + else if (! (*tryRetain)(obj, SEL_retainWeakReference)) { + result = nil; + } + } + else { + table->unlock(); + _class_initialize(cls); + goto retry; + } + } + table->unlock(); return result; } @@ -527,9 +569,9 @@ BOOL objc_should_deallocate(id object) { Autorelease pool implementation A thread's autorelease pool is a stack of pointers. - Each pointer is either an object to release, or POOL_SENTINEL which is + Each pointer is either an object to release, or POOL_BOUNDARY which is an autorelease pool boundary. - A pool token is a pointer to the POOL_SENTINEL for that pool. When + A pool token is a pointer to the POOL_BOUNDARY for that pool. When the pool is popped, every object hotter than the sentinel is released. The stack is divided into a doubly-linked list of pages. Pages are added and deleted as necessary. @@ -537,7 +579,15 @@ BOOL objc_should_deallocate(id object) { objects are stored. **********************************************************************/ +// Set this to 1 to mprotect() autorelease pool contents +#define PROTECT_AUTORELEASEPOOL 0 + +// Set this to 1 to validate the entire autorelease pool header all the time +// (i.e. use check() instead of fastcheck() everywhere) +#define CHECK_AUTORELEASEPOOL (DEBUG) + BREAKPOINT_FUNCTION(void objc_autoreleaseNoPool(id obj)); +BREAKPOINT_FUNCTION(void objc_autoreleasePoolInvalid(const void *token)); namespace { @@ -564,7 +614,7 @@ bool check() const { } bool fastcheck() const { -#if DEBUG +#if CHECK_AUTORELEASEPOOL return check(); #else return (m[0] == M0); @@ -575,13 +625,15 @@ bool fastcheck() const { }; -// Set this to 1 to mprotect() autorelease pool contents -#define PROTECT_AUTORELEASEPOOL 0 - class AutoreleasePoolPage { + // EMPTY_POOL_PLACEHOLDER is stored in TLS when exactly one pool is + // pushed and it has never contained any objects. This saves memory + // when the top level (i.e. libdispatch) pushes and pops pools but + // never uses them. +# define EMPTY_POOL_PLACEHOLDER ((id*)1) -#define POOL_SENTINEL nil +# define POOL_BOUNDARY nil static pthread_key_t const key = AUTORELEASE_POOL_KEY; static uint8_t const SCRIBBLE = 0xA3; // 0xA3A3A3A3 after releasing static size_t const SIZE = @@ -675,9 +727,13 @@ void check(bool die = true) void fastcheck(bool die = true) { +#if CHECK_AUTORELEASEPOOL + check(die); +#else if (! magic.fastcheck()) { busted(die); } +#endif } @@ -737,7 +793,7 @@ void releaseUntil(id *stop) memset((void*)page->next, SCRIBBLE, sizeof(*page->next)); page->protect(); - if (obj != POOL_SENTINEL) { + if (obj != POOL_BOUNDARY) { objc_release(obj); } } @@ -774,6 +830,11 @@ void kill() static void tls_dealloc(void *p) { + if (p == (void*)EMPTY_POOL_PLACEHOLDER) { + // No objects or pool pages to clean up here. + return; + } + // reinstate TLS value while we work setHotPage((AutoreleasePoolPage *)p); @@ -809,10 +870,24 @@ static void tls_dealloc(void *p) } + static inline bool haveEmptyPoolPlaceholder() + { + id *tls = (id *)tls_get_direct(key); + return (tls == EMPTY_POOL_PLACEHOLDER); + } + + static inline id* setEmptyPoolPlaceholder() + { + assert(tls_get_direct(key) == nil); + tls_set_direct(key, (void *)EMPTY_POOL_PLACEHOLDER); + return EMPTY_POOL_PLACEHOLDER; + } + static inline AutoreleasePoolPage *hotPage() { AutoreleasePoolPage *result = (AutoreleasePoolPage *) tls_get_direct(key); + if ((id *)result == EMPTY_POOL_PLACEHOLDER) return nil; if (result) result->fastcheck(); return result; } @@ -869,31 +944,48 @@ static __attribute__((noinline)) static __attribute__((noinline)) id *autoreleaseNoPage(id obj) { - // No pool in place. + // "No page" could mean no pool has been pushed + // or an empty placeholder pool has been pushed and has no contents yet assert(!hotPage()); - if (obj != POOL_SENTINEL && DebugMissingPools) { + bool pushExtraBoundary = false; + if (haveEmptyPoolPlaceholder()) { + // We are pushing a second pool over the empty placeholder pool + // or pushing the first object into the empty placeholder pool. + // Before doing that, push a pool boundary on behalf of the pool + // that is currently represented by the empty placeholder. + pushExtraBoundary = true; + } + else if (obj != POOL_BOUNDARY && DebugMissingPools) { // We are pushing an object with no pool in place, // and no-pool debugging was requested by environment. - _objc_inform("MISSING POOLS: Object %p of class %s " + _objc_inform("MISSING POOLS: (%p) Object %p of class %s " "autoreleased with no pool in place - " "just leaking - break on " "objc_autoreleaseNoPool() to debug", - (void*)obj, object_getClassName(obj)); + pthread_self(), (void*)obj, object_getClassName(obj)); objc_autoreleaseNoPool(obj); return nil; } + else if (obj == POOL_BOUNDARY && !DebugPoolAllocation) { + // We are pushing a pool with no pool in place, + // and alloc-per-pool debugging was not requested. + // Install and return the empty pool placeholder. + return setEmptyPoolPlaceholder(); + } + + // We are pushing an object or a non-placeholder'd pool. // Install the first page. AutoreleasePoolPage *page = new AutoreleasePoolPage(nil); setHotPage(page); - - // Push an autorelease pool boundary if it wasn't already requested. - if (obj != POOL_SENTINEL) { - page->add(POOL_SENTINEL); + + // Push a boundary on behalf of the previously-placeholder'd pool. + if (pushExtraBoundary) { + page->add(POOL_BOUNDARY); } - - // Push the requested object. + + // Push the requested object or pool. return page->add(obj); } @@ -912,7 +1004,7 @@ static inline id autorelease(id obj) assert(obj); assert(!obj->isTaggedPointer()); id *dest __unused = autoreleaseFast(obj); - assert(!dest || *dest == obj); + assert(!dest || dest == EMPTY_POOL_PLACEHOLDER || *dest == obj); return obj; } @@ -922,26 +1014,69 @@ static inline id autorelease(id obj) id *dest; if (DebugPoolAllocation) { // Each autorelease pool starts on a new pool page. - dest = autoreleaseNewPage(POOL_SENTINEL); + dest = autoreleaseNewPage(POOL_BOUNDARY); } else { - dest = autoreleaseFast(POOL_SENTINEL); + dest = autoreleaseFast(POOL_BOUNDARY); } - assert(*dest == POOL_SENTINEL); + assert(dest == EMPTY_POOL_PLACEHOLDER || *dest == POOL_BOUNDARY); return dest; } + static void badPop(void *token) + { + // Error. For bincompat purposes this is not + // fatal in executables built with old SDKs. + + if (DebugPoolAllocation || sdkIsAtLeast(10_12, 10_0, 10_0, 3_0)) { + // OBJC_DEBUG_POOL_ALLOCATION or new SDK. Bad pop is fatal. + _objc_fatal + ("Invalid or prematurely-freed autorelease pool %p.", token); + } + + // Old SDK. Bad pop is warned once. + static bool complained = false; + if (!complained) { + complained = true; + _objc_inform_now_and_on_crash + ("Invalid or prematurely-freed autorelease pool %p. " + "Set a breakpoint on objc_autoreleasePoolInvalid to debug. " + "Proceeding anyway because the app is old " + "(SDK version " SDK_FORMAT "). Memory errors are likely.", + token, FORMAT_SDK(sdkVersion())); + } + objc_autoreleasePoolInvalid(token); + } + static inline void pop(void *token) { AutoreleasePoolPage *page; id *stop; + if (token == (void*)EMPTY_POOL_PLACEHOLDER) { + // Popping the top-level placeholder pool. + if (hotPage()) { + // Pool was used. Pop its contents normally. + // Pool pages remain allocated for re-use as usual. + pop(coldPage()->begin()); + } else { + // Pool was never used. Clear the placeholder. + setHotPage(nil); + } + return; + } + page = pageForPointer(token); stop = (id *)token; - if (DebugPoolAllocation && *stop != POOL_SENTINEL) { - // This check is not valid with DebugPoolAllocation off - // after an autorelease with a pool page but no pool in place. - _objc_fatal("invalid or prematurely-freed autorelease pool %p; ", - token); + if (*stop != POOL_BOUNDARY) { + if (stop == page->begin() && !page->parent) { + // Start of coldest page may correctly not be POOL_BOUNDARY: + // 1. top-level pool is popped, leaving the cold page in place + // 2. an object is autoreleased with no pool + } else { + // Error. For bincompat purposes this is not + // fatal in executables built with old SDKs. + return badPop(token); + } } if (PrintPoolHiwat) printHiwat(); @@ -986,7 +1121,7 @@ void print() this == coldPage() ? "(cold)" : ""); check(false); for (id *p = begin(); p < next; p++) { - if (*p == POOL_SENTINEL) { + if (*p == POOL_BOUNDARY) { _objc_inform("[%p] ################ POOL %p", p, p); } else { _objc_inform("[%p] %#16lx %s", @@ -1007,8 +1142,16 @@ static void printAll() } _objc_inform("%llu releases pending.", (unsigned long long)objects); - for (page = coldPage(); page; page = page->child) { - page->print(); + if (haveEmptyPoolPlaceholder()) { + _objc_inform("[%p] ................ PAGE (placeholder)", + EMPTY_POOL_PLACEHOLDER); + _objc_inform("[%p] ################ POOL (placeholder)", + EMPTY_POOL_PLACEHOLDER); + } + else { + for (page = coldPage(); page; page = page->child) { + page->print(); + } } _objc_inform("##############"); @@ -1028,7 +1171,7 @@ static void printHiwat() } _objc_inform("POOL HIGHWATER: new high water mark of %u " - "pending autoreleases for thread %p:", + "pending releases for thread %p:", mark, pthread_self()); void *stack[128]; @@ -1041,7 +1184,7 @@ static void printHiwat() } } -#undef POOL_SENTINEL +#undef POOL_BOUNDARY }; // anonymous namespace @@ -1069,13 +1212,13 @@ static void printHiwat() // Slow path of clearDeallocating() -// for objects with indexed isa +// for objects with nonpointer isa // that were ever weakly referenced // or whose retain count ever overflowed to the side table. NEVER_INLINE void objc_object::clearDeallocating_slow() { - assert(isa.indexed && (isa.weakly_referenced || isa.has_sidetable_rc)); + assert(isa.nonpointer && (isa.weakly_referenced || isa.has_sidetable_rc)); SideTable& table = SideTables()[this]; table.lock(); @@ -1164,7 +1307,7 @@ void objc_overrelease_during_dealloc_error(void) bool isDeallocating, bool weaklyReferenced) { - assert(!isa.indexed); // should already be changed to not-indexed + assert(!isa.nonpointer); // should already be changed to raw pointer SideTable& table = SideTables()[this]; size_t& refcntStorage = table.refcnts[this]; @@ -1188,7 +1331,7 @@ void objc_overrelease_during_dealloc_error(void) bool objc_object::sidetable_addExtraRC_nolock(size_t delta_rc) { - assert(isa.indexed); + assert(isa.nonpointer); SideTable& table = SideTables()[this]; size_t& refcntStorage = table.refcnts[this]; @@ -1219,7 +1362,7 @@ void objc_overrelease_during_dealloc_error(void) size_t objc_object::sidetable_subExtraRC_nolock(size_t delta_rc) { - assert(isa.indexed); + assert(isa.nonpointer); SideTable& table = SideTables()[this]; RefcountMap::iterator it = table.refcnts.find(this); @@ -1243,7 +1386,7 @@ void objc_overrelease_during_dealloc_error(void) size_t objc_object::sidetable_getExtraRC_nolock() { - assert(isa.indexed); + assert(isa.nonpointer); SideTable& table = SideTables()[this]; RefcountMap::iterator it = table.refcnts.find(this); if (it == table.refcnts.end()) return 0; @@ -1255,14 +1398,14 @@ void objc_overrelease_during_dealloc_error(void) #endif -__attribute__((used,noinline,nothrow)) id -objc_object::sidetable_retain_slow(SideTable& table) +objc_object::sidetable_retain() { #if SUPPORT_NONPOINTER_ISA - assert(!isa.indexed); + assert(!isa.nonpointer); #endif - + SideTable& table = SideTables()[this]; + table.lock(); size_t& refcntStorage = table.refcnts[this]; if (! (refcntStorage & SIDE_TABLE_RC_PINNED)) { @@ -1274,31 +1417,11 @@ void objc_overrelease_during_dealloc_error(void) } -id -objc_object::sidetable_retain() -{ -#if SUPPORT_NONPOINTER_ISA - assert(!isa.indexed); -#endif - SideTable& table = SideTables()[this]; - - if (table.trylock()) { - size_t& refcntStorage = table.refcnts[this]; - if (! (refcntStorage & SIDE_TABLE_RC_PINNED)) { - refcntStorage += SIDE_TABLE_RC_ONE; - } - table.unlock(); - return (id)this; - } - return sidetable_retain_slow(table); -} - - bool objc_object::sidetable_tryRetain() { #if SUPPORT_NONPOINTER_ISA - assert(!isa.indexed); + assert(!isa.nonpointer); #endif SideTable& table = SideTables()[this]; @@ -1386,7 +1509,7 @@ void objc_overrelease_during_dealloc_error(void) objc_object::sidetable_setWeaklyReferenced_nolock() { #if SUPPORT_NONPOINTER_ISA - assert(!isa.indexed); + assert(!isa.nonpointer); #endif SideTable& table = SideTables()[this]; @@ -1398,13 +1521,14 @@ void objc_overrelease_during_dealloc_error(void) // rdar://20206767 // return uintptr_t instead of bool so that the various raw-isa // -release paths all return zero in eax -__attribute__((used,noinline,nothrow)) uintptr_t -objc_object::sidetable_release_slow(SideTable& table, bool performDealloc) +objc_object::sidetable_release(bool performDealloc) { #if SUPPORT_NONPOINTER_ISA - assert(!isa.indexed); + assert(!isa.nonpointer); #endif + SideTable& table = SideTables()[this]; + bool do_dealloc = false; table.lock(); @@ -1427,42 +1551,6 @@ void objc_overrelease_during_dealloc_error(void) } -// rdar://20206767 -// return uintptr_t instead of bool so that the various raw-isa -// -release paths all return zero in eax -uintptr_t -objc_object::sidetable_release(bool performDealloc) -{ -#if SUPPORT_NONPOINTER_ISA - assert(!isa.indexed); -#endif - SideTable& table = SideTables()[this]; - - bool do_dealloc = false; - - if (table.trylock()) { - RefcountMap::iterator it = table.refcnts.find(this); - if (it == table.refcnts.end()) { - do_dealloc = true; - table.refcnts[this] = SIDE_TABLE_DEALLOCATING; - } else if (it->second < SIDE_TABLE_DEALLOCATING) { - // SIDE_TABLE_WEAKLY_REFERENCED may be set. Don't change it. - do_dealloc = true; - it->second |= SIDE_TABLE_DEALLOCATING; - } else if (! (it->second & SIDE_TABLE_RC_PINNED)) { - it->second -= SIDE_TABLE_RC_ONE; - } - table.unlock(); - if (do_dealloc && performDealloc) { - ((void(*)(objc_object *, SEL))objc_msgSend)(this, SEL_dealloc); - } - return do_dealloc; - } - - return sidetable_release_slow(table, performDealloc); -} - - void objc_object::sidetable_clearDeallocating() { @@ -1558,7 +1646,6 @@ void objc_overrelease_during_dealloc_error(void) objc_clear_deallocating(id obj) { assert(obj); - assert(!UseGC); if (obj->isTaggedPointer()) return; obj->clearDeallocating(); @@ -1578,9 +1665,6 @@ void objc_overrelease_during_dealloc_error(void) _objc_rootAutorelease(id obj) { assert(obj); - // assert(!UseGC); - if (UseGC) return obj; // fixme CF calls this when GC is on - return obj->rootAutorelease(); } @@ -1620,7 +1704,7 @@ void objc_overrelease_during_dealloc_error(void) (void)zone; obj = class_createInstance(cls, 0); #else - if (!zone || UseGC) { + if (!zone) { obj = class_createInstance(cls, 0); } else { @@ -1628,7 +1712,7 @@ void objc_overrelease_during_dealloc_error(void) } #endif - if (!obj) obj = callBadAllocHandler(cls); + if (slowpath(!obj)) obj = callBadAllocHandler(cls); return obj; } @@ -1638,25 +1722,25 @@ void objc_overrelease_during_dealloc_error(void) static ALWAYS_INLINE id callAlloc(Class cls, bool checkNil, bool allocWithZone=false) { - if (checkNil && !cls) return nil; + if (slowpath(checkNil && !cls)) return nil; #if __OBJC2__ - if (! cls->ISA()->hasCustomAWZ()) { + if (fastpath(!cls->ISA()->hasCustomAWZ())) { // No alloc/allocWithZone implementation. Go straight to the allocator. // fixme store hasCustomAWZ in the non-meta class and // add it to canAllocFast's summary - if (cls->canAllocFast()) { + if (fastpath(cls->canAllocFast())) { // No ctors, raw isa, etc. Go straight to the metal. bool dtor = cls->hasCxxDtor(); id obj = (id)calloc(1, cls->bits.fastInstanceSize()); - if (!obj) return callBadAllocHandler(cls); + if (slowpath(!obj)) return callBadAllocHandler(cls); obj->initInstanceIsa(cls, dtor); return obj; } else { // Has ctor or raw isa or something. Use the slower path. id obj = class_createInstance(cls, 0); - if (!obj) return callBadAllocHandler(cls); + if (slowpath(!obj)) return callBadAllocHandler(cls); return obj; } } @@ -1703,11 +1787,6 @@ void objc_overrelease_during_dealloc_error(void) _objc_rootFinalize(id obj __unused) { assert(obj); - assert(UseGC); - - if (UseGC) { - return; - } _objc_fatal("_objc_rootFinalize called with garbage collection off"); } @@ -1725,9 +1804,6 @@ void objc_overrelease_during_dealloc_error(void) _objc_rootZone(id obj) { (void)obj; - if (gc_zone) { - return gc_zone; - } #if __OBJC2__ // allocWithZone under __OBJC2__ ignores the zone parameter return malloc_default_zone(); @@ -1740,23 +1816,18 @@ void objc_overrelease_during_dealloc_error(void) uintptr_t _objc_rootHash(id obj) { - if (UseGC) { - return _object_getExternalHash(obj); - } return (uintptr_t)obj; } void * objc_autoreleasePoolPush(void) { - if (UseGC) return nil; return AutoreleasePoolPage::push(); } void objc_autoreleasePoolPop(void *ctxt) { - if (UseGC) return; AutoreleasePoolPage::pop(ctxt); } @@ -1776,7 +1847,6 @@ void objc_overrelease_during_dealloc_error(void) void _objc_autoreleasePoolPrint(void) { - if (UseGC) return; AutoreleasePoolPage::printAll(); } @@ -1852,10 +1922,6 @@ void objc_overrelease_during_dealloc_error(void) [obj dealloc]; } -#undef objc_retainedObject -#undef objc_unretainedObject -#undef objc_unretainedPointer - // convert objc_objectptr_t to id, callee must take ownership. id objc_retainedObject(objc_objectptr_t pointer) { return (id)pointer; } @@ -1872,10 +1938,28 @@ void arr_init(void) SideTableInit(); } + +#if SUPPORT_TAGGED_POINTERS + +// Placeholder for old debuggers. When they inspect an +// extended tagged pointer object they will see this isa. + +@interface __NSUnrecognizedTaggedPointer : NSObject +@end + +@implementation __NSUnrecognizedTaggedPointer ++(void) load { } +-(id) retain { return self; } +-(oneway void) release { } +-(id) autorelease { return self; } +@end + +#endif + + @implementation NSObject + (void)load { - if (UseGC) gc_init2(); } + (void)initialize { @@ -2227,12 +2311,8 @@ - (void)dealloc { _objc_rootDealloc(self); } -// Replaced by CF (throws an NSException) -+ (void)finalize { -} - -- (void)finalize { - _objc_rootFinalize(self); +// Previously used by GC. Now a placeholder for binary compatibility. +- (void) finalize { } + (struct _NSZone *)zone { diff --git a/runtime/Object.h b/runtime/Object.h index dd8838d..5c857ac 100644 --- a/runtime/Object.h +++ b/runtime/Object.h @@ -37,7 +37,8 @@ #if __OBJC__ && !__OBJC2__ -__OSX_AVAILABLE_STARTING(__MAC_10_0, __IPHONE_NA) +__OSX_AVAILABLE(10.0) +__IOS_UNAVAILABLE __TVOS_UNAVAILABLE __WATCHOS_UNAVAILABLE OBJC_ROOT_CLASS @interface Object { diff --git a/runtime/Object.mm b/runtime/Object.mm index c4981cf..3ec14be 100644 --- a/runtime/Object.mm +++ b/runtime/Object.mm @@ -35,7 +35,8 @@ #if __OBJC2__ -__OSX_AVAILABLE_STARTING(__MAC_10_0, __IPHONE_NA) +__OSX_AVAILABLE(10.0) +__IOS_UNAVAILABLE __TVOS_UNAVAILABLE __WATCHOS_UNAVAILABLE OBJC_ROOT_CLASS @interface Object { Class isa; @@ -99,7 +100,6 @@ +(id) autorelease #include "Object.h" #include "Protocol.h" #include "objc-runtime.h" -#include "objc-auto.h" // Error Messages diff --git a/runtime/OldClasses.subproj/List.h b/runtime/OldClasses.subproj/List.h index 993cf2c..d1f7ff6 100644 --- a/runtime/OldClasses.subproj/List.h +++ b/runtime/OldClasses.subproj/List.h @@ -32,7 +32,7 @@ #ifndef _OBJC_LIST_H_ #define _OBJC_LIST_H_ -#if __OBJC__ && !__OBJC2__ && !__cplusplus +#if __OBJC__ && !__OBJC2__ && !__cplusplus && !__has_feature(objc_arc) #include #include diff --git a/runtime/Protocol.h b/runtime/Protocol.h index 0e78850..1f2a7b5 100644 --- a/runtime/Protocol.h +++ b/runtime/Protocol.h @@ -41,7 +41,7 @@ // All methods of class Protocol are unavailable. // Use the functions in objc/runtime.h instead. -__OSX_AVAILABLE_STARTING(__MAC_10_0, __IPHONE_2_0) +OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0) @interface Protocol : NSObject @end @@ -50,7 +50,7 @@ __OSX_AVAILABLE_STARTING(__MAC_10_0, __IPHONE_2_0) #include -__OSX_AVAILABLE_STARTING(__MAC_10_0, __IPHONE_2_0) +OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0) @interface Protocol : Object { @private @@ -71,9 +71,15 @@ __OSX_AVAILABLE_STARTING(__MAC_10_0, __IPHONE_2_0) /* Looking up information specific to a protocol */ - (struct objc_method_description *) descriptionForInstanceMethod:(SEL)aSel - __OSX_AVAILABLE_BUT_DEPRECATED(__MAC_10_0,__MAC_10_5, __IPHONE_2_0,__IPHONE_2_0); + __OSX_DEPRECATED(10.0, 10.5, "use protocol_getMethodDescription instead") + __IOS_DEPRECATED(2.0, 2.0, "use protocol_getMethodDescription instead") + __TVOS_DEPRECATED(9.0, 9.0, "use protocol_getMethodDescription instead") + __WATCHOS_DEPRECATED(1.0, 1.0, "use protocol_getMethodDescription instead"); - (struct objc_method_description *) descriptionForClassMethod:(SEL)aSel - __OSX_AVAILABLE_BUT_DEPRECATED(__MAC_10_0,__MAC_10_5, __IPHONE_2_0,__IPHONE_2_0); + __OSX_DEPRECATED(10.0, 10.5, "use protocol_getMethodDescription instead") + __IOS_DEPRECATED(2.0, 2.0, "use protocol_getMethodDescription instead") + __TVOS_DEPRECATED(9.0, 9.0, "use protocol_getMethodDescription instead") + __WATCHOS_DEPRECATED(1.0, 1.0, "use protocol_getMethodDescription instead"); @end diff --git a/runtime/Protocol.mm b/runtime/Protocol.mm index 137eac9..2a2b3da 100644 --- a/runtime/Protocol.mm +++ b/runtime/Protocol.mm @@ -36,14 +36,23 @@ #include #include "Protocol.h" +#include "NSObject.h" + +// __IncompleteProtocol is used as the return type of objc_allocateProtocol(). + +// Old ABI uses NSObject as the superclass even though Protocol uses Object +// because the R/R implementation for class Protocol is added at runtime +// by CF, so __IncompleteProtocol would be left without an R/R implementation +// otherwise, which would break ARC. -#if __OBJC2__ @interface __IncompleteProtocol : NSObject @end @implementation __IncompleteProtocol +#if __OBJC2__ // fixme hack - make __IncompleteProtocol a non-lazy class + (void) load { } -@end #endif +@end + @implementation Protocol diff --git a/runtime/hashtable2.h b/runtime/hashtable2.h index d87c564..197a17f 100644 --- a/runtime/hashtable2.h +++ b/runtime/hashtable2.h @@ -30,7 +30,9 @@ #define _OBJC_LITTLE_HASHTABLE_H_ #ifndef _OBJC_PRIVATE_H_ -# define OBJC_HASH_AVAILABILITY __OSX_AVAILABLE_BUT_DEPRECATED(__MAC_10_0,__MAC_10_1, __IPHONE_NA,__IPHONE_NA); +# define OBJC_HASH_AVAILABILITY \ + __OSX_DEPRECATED(10.0, 10.1, "NXHashTable is deprecated") \ + __IOS_UNAVAILABLE __TVOS_UNAVAILABLE __WATCHOS_UNAVAILABLE #else # define OBJC_HASH_AVAILABILITY #endif diff --git a/runtime/maptable.h b/runtime/maptable.h index bc2a635..e43da78 100644 --- a/runtime/maptable.h +++ b/runtime/maptable.h @@ -30,7 +30,9 @@ #define _OBJC_MAPTABLE_H_ #ifndef _OBJC_PRIVATE_H_ -# define OBJC_MAP_AVAILABILITY __OSX_AVAILABLE_BUT_DEPRECATED(__MAC_10_0,__MAC_10_1, __IPHONE_NA,__IPHONE_NA); +# define OBJC_MAP_AVAILABILITY \ + __OSX_DEPRECATED(10.0, 10.1, "NXMapTable is deprecated") \ + __IOS_UNAVAILABLE __TVOS_UNAVAILABLE __WATCHOS_UNAVAILABLE #else # define OBJC_MAP_AVAILABILITY #endif diff --git a/runtime/maptable.mm b/runtime/maptable.mm index a8ecb77..0413ecd 100644 --- a/runtime/maptable.mm +++ b/runtime/maptable.mm @@ -339,7 +339,7 @@ int NXNextMapState(NXMapTable *table, NXMapState *state, const void **key, const // key DOES exist in table - use table's key for insertion } else { // key DOES NOT exist in table - copy the new key before insertion - realKey = (void *)strdup((char *)key); + realKey = (void *)strdupIfMutable((char *)key); } return NXMapInsert(table, realKey, value); } @@ -358,7 +358,8 @@ int NXNextMapState(NXMapTable *table, NXMapState *state, const void **key, const if ((realKey = NXMapMember(table, key, &realValue)) != NX_MAPNOTAKEY) { // key DOES exist in table - remove pair and free key realValue = NXMapRemove(table, realKey); - free(realKey); // the key from the table, not necessarily the one given + // free the key from the table, not necessarily the one given + freeIfMutable((char *)realKey); return realValue; } else { // key DOES NOT exist in table - nothing to do diff --git a/runtime/message.h b/runtime/message.h index 9f2a1c6..725e912 100644 --- a/runtime/message.h +++ b/runtime/message.h @@ -62,9 +62,9 @@ struct objc_super { */ #if !OBJC_OLD_DISPATCH_PROTOTYPES OBJC_EXPORT void objc_msgSend(void /* id self, SEL op, ... */ ) - __OSX_AVAILABLE_STARTING(__MAC_10_0, __IPHONE_2_0); + OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0); OBJC_EXPORT void objc_msgSendSuper(void /* struct objc_super *super, SEL op, ... */ ) - __OSX_AVAILABLE_STARTING(__MAC_10_0, __IPHONE_2_0); + OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0); #else /** * Sends a message with a simple return value to an instance of a class. @@ -83,7 +83,7 @@ OBJC_EXPORT void objc_msgSendSuper(void /* struct objc_super *super, SEL op, ... * are sent using \c objc_msgSendSuper_stret and \c objc_msgSend_stret. */ OBJC_EXPORT id objc_msgSend(id self, SEL op, ...) - __OSX_AVAILABLE_STARTING(__MAC_10_0, __IPHONE_2_0); + OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0); /** * Sends a message with a simple return value to the superclass of an instance of a class. * @@ -99,7 +99,7 @@ OBJC_EXPORT id objc_msgSend(id self, SEL op, ...) * @see objc_msgSend */ OBJC_EXPORT id objc_msgSendSuper(struct objc_super *super, SEL op, ...) - __OSX_AVAILABLE_STARTING(__MAC_10_0, __IPHONE_2_0); + OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0); #endif @@ -114,10 +114,10 @@ OBJC_EXPORT id objc_msgSendSuper(struct objc_super *super, SEL op, ...) */ #if !OBJC_OLD_DISPATCH_PROTOTYPES OBJC_EXPORT void objc_msgSend_stret(void /* id self, SEL op, ... */ ) - __OSX_AVAILABLE_STARTING(__MAC_10_0, __IPHONE_2_0) + OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0) OBJC_ARM64_UNAVAILABLE; OBJC_EXPORT void objc_msgSendSuper_stret(void /* struct objc_super *super, SEL op, ... */ ) - __OSX_AVAILABLE_STARTING(__MAC_10_0, __IPHONE_2_0) + OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0) OBJC_ARM64_UNAVAILABLE; #else /** @@ -126,7 +126,7 @@ OBJC_EXPORT void objc_msgSendSuper_stret(void /* struct objc_super *super, SEL o * @see objc_msgSend */ OBJC_EXPORT void objc_msgSend_stret(id self, SEL op, ...) - __OSX_AVAILABLE_STARTING(__MAC_10_0, __IPHONE_2_0) + OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0) OBJC_ARM64_UNAVAILABLE; /** @@ -135,7 +135,7 @@ OBJC_EXPORT void objc_msgSend_stret(id self, SEL op, ...) * @see objc_msgSendSuper */ OBJC_EXPORT void objc_msgSendSuper_stret(struct objc_super *super, SEL op, ...) - __OSX_AVAILABLE_STARTING(__MAC_10_0, __IPHONE_2_0) + OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0) OBJC_ARM64_UNAVAILABLE; #endif @@ -162,14 +162,14 @@ OBJC_EXPORT void objc_msgSendSuper_stret(struct objc_super *super, SEL op, ...) # if defined(__i386__) OBJC_EXPORT void objc_msgSend_fpret(void /* id self, SEL op, ... */ ) - __OSX_AVAILABLE_STARTING(__MAC_10_4, __IPHONE_2_0); + OBJC_AVAILABLE(10.4, 2.0, 9.0, 1.0); # elif defined(__x86_64__) OBJC_EXPORT void objc_msgSend_fpret(void /* id self, SEL op, ... */ ) - __OSX_AVAILABLE_STARTING(__MAC_10_5, __IPHONE_2_0); + OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0); OBJC_EXPORT void objc_msgSend_fp2ret(void /* id self, SEL op, ... */ ) - __OSX_AVAILABLE_STARTING(__MAC_10_5, __IPHONE_2_0); + OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0); # endif @@ -188,7 +188,7 @@ OBJC_EXPORT void objc_msgSend_fp2ret(void /* id self, SEL op, ... */ ) * \c long \c double return types, cast the function to an appropriate function pointer type first. */ OBJC_EXPORT double objc_msgSend_fpret(id self, SEL op, ...) - __OSX_AVAILABLE_STARTING(__MAC_10_4, __IPHONE_2_0); + OBJC_AVAILABLE(10.4, 2.0, 9.0, 1.0); /* Use objc_msgSendSuper() for fp-returning messages to super. */ /* See also objc_msgSendv_fpret() below. */ @@ -200,14 +200,14 @@ OBJC_EXPORT double objc_msgSend_fpret(id self, SEL op, ...) * @see objc_msgSend */ OBJC_EXPORT long double objc_msgSend_fpret(id self, SEL op, ...) - __OSX_AVAILABLE_STARTING(__MAC_10_5, __IPHONE_2_0); + OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0); # if __STDC_VERSION__ >= 199901L OBJC_EXPORT _Complex long double objc_msgSend_fp2ret(id self, SEL op, ...) - __OSX_AVAILABLE_STARTING(__MAC_10_5, __IPHONE_2_0); + OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0); # else OBJC_EXPORT void objc_msgSend_fp2ret(id self, SEL op, ...) - __OSX_AVAILABLE_STARTING(__MAC_10_5, __IPHONE_2_0); + OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0); # endif /* Use objc_msgSendSuper() for fp-returning messages to super. */ @@ -230,15 +230,15 @@ OBJC_EXPORT void objc_msgSend_fp2ret(id self, SEL op, ...) */ #if !OBJC_OLD_DISPATCH_PROTOTYPES OBJC_EXPORT void method_invoke(void /* id receiver, Method m, ... */ ) - __OSX_AVAILABLE_STARTING(__MAC_10_5, __IPHONE_2_0); + OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0); OBJC_EXPORT void method_invoke_stret(void /* id receiver, Method m, ... */ ) - __OSX_AVAILABLE_STARTING(__MAC_10_5, __IPHONE_2_0) + OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0) OBJC_ARM64_UNAVAILABLE; #else OBJC_EXPORT id method_invoke(id receiver, Method m, ...) - __OSX_AVAILABLE_STARTING(__MAC_10_5, __IPHONE_2_0); + OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0); OBJC_EXPORT void method_invoke_stret(id receiver, Method m, ...) - __OSX_AVAILABLE_STARTING(__MAC_10_5, __IPHONE_2_0) + OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0) OBJC_ARM64_UNAVAILABLE; #endif @@ -260,15 +260,15 @@ OBJC_EXPORT void method_invoke_stret(id receiver, Method m, ...) */ #if !OBJC_OLD_DISPATCH_PROTOTYPES OBJC_EXPORT void _objc_msgForward(void /* id receiver, SEL sel, ... */ ) - __OSX_AVAILABLE_STARTING(__MAC_10_0, __IPHONE_2_0); + OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0); OBJC_EXPORT void _objc_msgForward_stret(void /* id receiver, SEL sel, ... */ ) - __OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_3_0) + OBJC_AVAILABLE(10.6, 3.0, 9.0, 1.0) OBJC_ARM64_UNAVAILABLE; #else OBJC_EXPORT id _objc_msgForward(id receiver, SEL sel, ...) - __OSX_AVAILABLE_STARTING(__MAC_10_0, __IPHONE_2_0); + OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0); OBJC_EXPORT void _objc_msgForward_stret(id receiver, SEL sel, ...) - __OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_3_0) + OBJC_AVAILABLE(10.6, 3.0, 9.0, 1.0) OBJC_ARM64_UNAVAILABLE; #endif diff --git a/runtime/objc-abi.h b/runtime/objc-abi.h index fb325ef..7d9e89e 100644 --- a/runtime/objc-abi.h +++ b/runtime/objc-abi.h @@ -38,6 +38,7 @@ */ #include +#include #include #include #include @@ -46,7 +47,7 @@ // Old static initializer. Used by old crt1.o and old bug workarounds. OBJC_EXPORT void _objcInit(void) - __OSX_AVAILABLE_STARTING(__MAC_10_0, __IPHONE_2_0); + OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0); /* Images */ @@ -55,54 +56,103 @@ OBJC_EXPORT void _objcInit(void) typedef struct objc_image_info { uint32_t version; // currently 0 uint32_t flags; + +#if __cplusplus >= 201103L + private: + enum : uint32_t { + IsReplacement = 1<<0, // used for Fix&Continue, now ignored + SupportsGC = 1<<1, // image supports GC + RequiresGC = 1<<2, // image requires GC + OptimizedByDyld = 1<<3, // image is from an optimized shared cache + CorrectedSynthesize = 1<<4, // used for an old workaround, now ignored + IsSimulated = 1<<5, // image compiled for a simulator platform + HasCategoryClassProperties = 1<<6, // class properties in category_t + + SwiftVersionMaskShift = 8, + SwiftVersionMask = 0xff << SwiftVersionMaskShift // Swift ABI version + + }; + public: + enum : uint32_t { + SwiftVersion1 = 1, + SwiftVersion1_2 = 2, + SwiftVersion2 = 3, + SwiftVersion3 = 4 + }; + + public: + bool isReplacement() const { return flags & IsReplacement; } + bool supportsGC() const { return flags & SupportsGC; } + bool requiresGC() const { return flags & RequiresGC; } + bool optimizedByDyld() const { return flags & OptimizedByDyld; } + bool hasCategoryClassProperties() const { return flags & HasCategoryClassProperties; } + bool containsSwift() const { return (flags & SwiftVersionMask) != 0; } + uint32_t swiftVersion() const { return (flags & SwiftVersionMask) >> SwiftVersionMaskShift; } +#endif } objc_image_info; -// Values for objc_image_info.flags -#define OBJC_IMAGE_IS_REPLACEMENT (1<<0) -#define OBJC_IMAGE_SUPPORTS_GC (1<<1) -#define OBJC_IMAGE_REQUIRES_GC (1<<2) -#define OBJC_IMAGE_OPTIMIZED_BY_DYLD (1<<3) -#define OBJC_IMAGE_SUPPORTS_COMPACTION (1<<4) // might be re-assignable +/* +IsReplacement: + Once used for Fix&Continue in old OS X object files (not final linked images) + Not currently used. + +SupportsGC: + App: GC is required. Framework: GC is supported but not required. + +RequiresGC: + Framework: GC is required. + +OptimizedByDyld: + Assorted metadata precooked in the dyld shared cache. + Never set for images outside the shared cache file itself. + +CorrectedSynthesize: + Once used on old iOS to mark images that did not have a particular + miscompile. Not used by the runtime. + +IsSimulated: + Image was compiled for a simulator platform. Not used by the runtime. + +HasClassProperties: + New ABI: category_t.classProperties fields are present. + Old ABI: Set by some compilers. Not used by the runtime. +*/ /* Properties */ // Read or write an object property. Not all object properties use these. OBJC_EXPORT id objc_getProperty(id self, SEL _cmd, ptrdiff_t offset, BOOL atomic) - __OSX_AVAILABLE_STARTING(__MAC_10_5, __IPHONE_2_0); + OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0); OBJC_EXPORT void objc_setProperty(id self, SEL _cmd, ptrdiff_t offset, id newValue, BOOL atomic, signed char shouldCopy) - __OSX_AVAILABLE_STARTING(__MAC_10_5, __IPHONE_2_0); + OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0); OBJC_EXPORT void objc_setProperty_atomic(id self, SEL _cmd, id newValue, ptrdiff_t offset) - __OSX_AVAILABLE_STARTING(__MAC_10_8, __IPHONE_6_0) - OBJC_GC_UNAVAILABLE; + OBJC_AVAILABLE(10.8, 6.0, 9.0, 1.0); OBJC_EXPORT void objc_setProperty_nonatomic(id self, SEL _cmd, id newValue, ptrdiff_t offset) - __OSX_AVAILABLE_STARTING(__MAC_10_8, __IPHONE_6_0) - OBJC_GC_UNAVAILABLE; + OBJC_AVAILABLE(10.8, 6.0, 9.0, 1.0); OBJC_EXPORT void objc_setProperty_atomic_copy(id self, SEL _cmd, id newValue, ptrdiff_t offset) - __OSX_AVAILABLE_STARTING(__MAC_10_8, __IPHONE_6_0) - OBJC_GC_UNAVAILABLE; + OBJC_AVAILABLE(10.8, 6.0, 9.0, 1.0); OBJC_EXPORT void objc_setProperty_nonatomic_copy(id self, SEL _cmd, id newValue, ptrdiff_t offset) - __OSX_AVAILABLE_STARTING(__MAC_10_8, __IPHONE_6_0) - OBJC_GC_UNAVAILABLE; + OBJC_AVAILABLE(10.8, 6.0, 9.0, 1.0); // Read or write a non-object property. Not all uses are C structs, // and not all C struct properties use this. OBJC_EXPORT void objc_copyStruct(void *dest, const void *src, ptrdiff_t size, BOOL atomic, BOOL hasStrong) - __OSX_AVAILABLE_STARTING(__MAC_10_5, __IPHONE_2_0); + OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0); // Perform a copy of a C++ object using striped locks. Used by non-POD C++ typed atomic properties. OBJC_EXPORT void objc_copyCppObjectAtomic(void *dest, const void *src, void (*copyHelper) (void *dest, const void *source)) - __OSX_AVAILABLE_STARTING(__MAC_10_8, __IPHONE_6_0); + OBJC_AVAILABLE(10.8, 6.0, 9.0, 1.0); /* Classes. */ #if __OBJC2__ OBJC_EXPORT IMP _objc_empty_vtable - __OSX_AVAILABLE_STARTING(__MAC_10_5, __IPHONE_2_0); + OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0); #endif OBJC_EXPORT struct objc_cache _objc_empty_cache - __OSX_AVAILABLE_STARTING(__MAC_10_0, __IPHONE_2_0); + OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0); /* Messages */ @@ -110,14 +160,14 @@ OBJC_EXPORT struct objc_cache _objc_empty_cache #if __OBJC2__ // objc_msgSendSuper2() takes the current search class, not its superclass. OBJC_EXPORT id objc_msgSendSuper2(struct objc_super *super, SEL op, ...) - __OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_2_0); + OBJC_AVAILABLE(10.6, 2.0, 9.0, 1.0); OBJC_EXPORT void objc_msgSendSuper2_stret(struct objc_super *super, SEL op,...) - __OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_2_0) + OBJC_AVAILABLE(10.6, 2.0, 9.0, 1.0) OBJC_ARM64_UNAVAILABLE; // objc_msgSend_noarg() may be faster for methods with no additional arguments. OBJC_EXPORT id objc_msgSend_noarg(id self, SEL _cmd) - __OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0); + OBJC_AVAILABLE(10.7, 5.0, 9.0, 1.0); #endif #if __OBJC2__ @@ -126,47 +176,92 @@ OBJC_EXPORT id objc_msgSend_noarg(id self, SEL _cmd) // Old objc_msgSendSuper() does not have a debug version; this is OBJC2 only. // *_fixup() do not have debug versions; use non-fixup only for debug mode. OBJC_EXPORT id objc_msgSend_debug(id self, SEL op, ...) - __OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0); + OBJC_AVAILABLE(10.7, 5.0, 9.0, 1.0); OBJC_EXPORT id objc_msgSendSuper2_debug(struct objc_super *super, SEL op, ...) - __OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0); + OBJC_AVAILABLE(10.7, 5.0, 9.0, 1.0); OBJC_EXPORT void objc_msgSend_stret_debug(id self, SEL op, ...) - __OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0) + OBJC_AVAILABLE(10.7, 5.0, 9.0, 1.0) OBJC_ARM64_UNAVAILABLE; OBJC_EXPORT void objc_msgSendSuper2_stret_debug(struct objc_super *super, SEL op,...) - __OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0) + OBJC_AVAILABLE(10.7, 5.0, 9.0, 1.0) OBJC_ARM64_UNAVAILABLE; # if defined(__i386__) OBJC_EXPORT double objc_msgSend_fpret_debug(id self, SEL op, ...) - __OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0); + OBJC_AVAILABLE(10.7, 5.0, 9.0, 1.0); # elif defined(__x86_64__) OBJC_EXPORT long double objc_msgSend_fpret_debug(id self, SEL op, ...) - __OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0); + OBJC_AVAILABLE(10.7, 5.0, 9.0, 1.0); # if __STDC_VERSION__ >= 199901L OBJC_EXPORT _Complex long double objc_msgSend_fp2ret_debug(id self, SEL op, ...) - __OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0); + OBJC_AVAILABLE(10.7, 5.0, 9.0, 1.0); # else OBJC_EXPORT void objc_msgSend_fp2ret_debug(id self, SEL op, ...) - __OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0); + OBJC_AVAILABLE(10.7, 5.0, 9.0, 1.0); # endif # endif #endif -#if defined(__x86_64__) && TARGET_OS_MAC && !TARGET_IPHONE_SIMULATOR +#if __OBJC2__ +// Lookup messengers. +// These are not callable C functions. Do not call them directly. +// The caller should set the method parameters, call objc_msgLookup(), +// then immediately call the returned IMP. +// +// Generic ABI: +// - Callee-saved registers are preserved. +// - Receiver and selector registers may be modified. These values must +// be passed to the called IMP. Other parameter registers are preserved. +// - Caller-saved non-parameter registers are not preserved. Some of +// these registers are used to pass data from objc_msgLookup() to +// the called IMP and must not be disturbed by the caller. +// - Red zone is not preserved. +// See each architecture's implementation for details. + +OBJC_EXPORT void objc_msgLookup(void) + OBJC_AVAILABLE(10.12, 10.0, 10.0, 3.0); +OBJC_EXPORT void objc_msgLookupSuper2(void) + OBJC_AVAILABLE(10.12, 10.0, 10.0, 3.0); +OBJC_EXPORT void objc_msgLookup_stret(void) + OBJC_AVAILABLE(10.12, 10.0, 10.0, 3.0) + OBJC_ARM64_UNAVAILABLE; +OBJC_EXPORT void objc_msgLookupSuper2_stret(void) + OBJC_AVAILABLE(10.12, 10.0, 10.0, 3.0) + OBJC_ARM64_UNAVAILABLE; + +# if defined(__i386__) +OBJC_EXPORT void objc_msgLookup_fpret(void) + OBJC_AVAILABLE(10.12, 10.0, 10.0, 3.0); +# elif defined(__x86_64__) +OBJC_EXPORT void objc_msgLookup_fpret(void) + OBJC_AVAILABLE(10.12, 10.0, 10.0, 3.0); +OBJC_EXPORT void objc_msgLookup_fp2ret(void) + OBJC_AVAILABLE(10.12, 10.0, 10.0, 3.0); +# endif + +#endif + +#if TARGET_OS_OSX && defined(__x86_64__) // objc_msgSend_fixup() is used for vtable-dispatchable call sites. OBJC_EXPORT void objc_msgSend_fixup(void) - __OSX_AVAILABLE_BUT_DEPRECATED(__MAC_10_5, __MAC_10_8, __IPHONE_NA, __IPHONE_NA); + __OSX_DEPRECATED(10.5, 10.8, "fixup dispatch is no longer optimized") + __IOS_UNAVAILABLE __TVOS_UNAVAILABLE __WATCHOS_UNAVAILABLE; OBJC_EXPORT void objc_msgSend_stret_fixup(void) - __OSX_AVAILABLE_BUT_DEPRECATED(__MAC_10_5, __MAC_10_8, __IPHONE_NA, __IPHONE_NA); + __OSX_DEPRECATED(10.5, 10.8, "fixup dispatch is no longer optimized") + __IOS_UNAVAILABLE __TVOS_UNAVAILABLE __WATCHOS_UNAVAILABLE; OBJC_EXPORT void objc_msgSendSuper2_fixup(void) - __OSX_AVAILABLE_BUT_DEPRECATED(__MAC_10_5, __MAC_10_8, __IPHONE_NA, __IPHONE_NA); + __OSX_DEPRECATED(10.5, 10.8, "fixup dispatch is no longer optimized") + __IOS_UNAVAILABLE __TVOS_UNAVAILABLE __WATCHOS_UNAVAILABLE; OBJC_EXPORT void objc_msgSendSuper2_stret_fixup(void) - __OSX_AVAILABLE_BUT_DEPRECATED(__MAC_10_5, __MAC_10_8, __IPHONE_NA, __IPHONE_NA); + __OSX_DEPRECATED(10.5, 10.8, "fixup dispatch is no longer optimized") + __IOS_UNAVAILABLE __TVOS_UNAVAILABLE __WATCHOS_UNAVAILABLE; OBJC_EXPORT void objc_msgSend_fpret_fixup(void) - __OSX_AVAILABLE_BUT_DEPRECATED(__MAC_10_5, __MAC_10_8, __IPHONE_NA, __IPHONE_NA); + __OSX_DEPRECATED(10.5, 10.8, "fixup dispatch is no longer optimized") + __IOS_UNAVAILABLE __TVOS_UNAVAILABLE __WATCHOS_UNAVAILABLE; OBJC_EXPORT void objc_msgSend_fp2ret_fixup(void) - __OSX_AVAILABLE_BUT_DEPRECATED(__MAC_10_5, __MAC_10_8, __IPHONE_NA, __IPHONE_NA); + __OSX_DEPRECATED(10.5, 10.8, "fixup dispatch is no longer optimized") + __IOS_UNAVAILABLE __TVOS_UNAVAILABLE __WATCHOS_UNAVAILABLE; #endif /* C++-compatible exception handling. */ @@ -177,11 +272,11 @@ OBJC_EXPORT void objc_msgSend_fp2ret_fixup(void) // Vtable for C++ exception typeinfo for Objective-C types. OBJC_EXPORT const void *objc_ehtype_vtable[] - __OSX_AVAILABLE_STARTING(__MAC_10_5, __IPHONE_2_0); + OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0); // C++ exception typeinfo for type `id`. OBJC_EXPORT struct objc_typeinfo OBJC_EHTYPE_id - __OSX_AVAILABLE_STARTING(__MAC_10_5, __IPHONE_2_0); + OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0); #endif @@ -194,13 +289,58 @@ __objc_personality_v0(int version, uint64_t exceptionClass, struct _Unwind_Exception *exceptionObject, struct _Unwind_Context *context) - __OSX_AVAILABLE_STARTING(__MAC_10_5, __IPHONE_2_0); + OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0); #endif -/* ARR */ +/* ARC */ OBJC_EXPORT id objc_retainBlock(id) - __OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0); + OBJC_AVAILABLE(10.7, 5.0, 9.0, 1.0); + + +/* Non-pointer isa */ + +#if __OBJC2__ + +// Extract class pointer from an isa field. + +#if TARGET_OS_SIMULATOR + // No simulators use nonpointer isa yet. + +#elif __LP64__ +# define OBJC_HAVE_NONPOINTER_ISA 1 +# define OBJC_HAVE_PACKED_NONPOINTER_ISA 1 + +// Packed-isa version. This one is used directly by Swift code. +// (Class)(isa & (uintptr_t)&objc_absolute_packed_isa_class_mask) == class ptr +OBJC_EXPORT const struct { char c; } objc_absolute_packed_isa_class_mask + OBJC_AVAILABLE(10.12, 10.0, 10.0, 3.0); + +#elif __ARM_ARCH_7K__ >= 2 +# define OBJC_HAVE_NONPOINTER_ISA 1 +# define OBJC_HAVE_INDEXED_NONPOINTER_ISA 1 + +// Indexed-isa version. +// if (isa & (uintptr_t)&objc_absolute_indexed_isa_magic_mask == (uintptr_t)&objc_absolute_indexed_isa_magic_value) { +// uintptr_t index = (isa & (uintptr_t)&objc_absolute_indexed_isa_index_mask) >> (uintptr_t)&objc_absolute_indexed_isa_index_shift; +// cls = objc_indexed_classes[index]; +// } else +// cls = (Class)isa; +// } +OBJC_EXPORT const struct { char c; } objc_absolute_indexed_isa_magic_mask + OBJC_AVAILABLE(10.12, 10.0, 10.0, 3.0); +OBJC_EXPORT const struct { char c; } objc_absolute_indexed_isa_magic_value + OBJC_AVAILABLE(10.12, 10.0, 10.0, 3.0); +OBJC_EXPORT const struct { char c; } objc_absolute_indexed_isa_index_mask + OBJC_AVAILABLE(10.12, 10.0, 10.0, 3.0); +OBJC_EXPORT const struct { char c; } objc_absolute_indexed_isa_index_shift + OBJC_AVAILABLE(10.12, 10.0, 10.0, 3.0); + +#endif + +// OBJC2 +#endif +// _OBJC_ABI_H #endif diff --git a/runtime/objc-accessors.h b/runtime/objc-accessors.h deleted file mode 100644 index 8b2f5f1..0000000 --- a/runtime/objc-accessors.h +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright (c) 2006-2007 Apple Inc. All Rights Reserved. - * - * @APPLE_LICENSE_HEADER_START@ - * - * This file contains Original Code and/or Modifications of Original Code - * as defined in and that are subject to the Apple Public Source License - * Version 2.0 (the 'License'). You may not use this file except in - * compliance with the License. Please obtain a copy of the License at - * http://www.opensource.apple.com/apsl/ and read it before using this - * file. - * - * The Original Code and all software distributed under the License are - * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER - * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, - * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. - * Please see the License for the specific language governing rights and - * limitations under the License. - * - * @APPLE_LICENSE_HEADER_END@ - */ - -#ifndef _OBJC_ACCESSORS_H_ -#define _OBJC_ACCESSORS_H_ - -#include -#include - -__BEGIN_DECLS - -#if SUPPORT_GC - -extern void objc_setProperty_non_gc(id self, SEL _cmd, ptrdiff_t offset, id newValue, BOOL atomic, signed char shouldCopy); -extern id objc_getProperty_non_gc(id self, SEL _cmd, ptrdiff_t offset, BOOL atomic); - -extern void objc_setProperty_gc(id self, SEL _cmd, ptrdiff_t offset, id newValue, BOOL atomic, signed char shouldCopy); -extern id objc_getProperty_gc(id self, SEL _cmd, ptrdiff_t offset, BOOL atomic); - -#endif - -__END_DECLS - -#endif diff --git a/runtime/objc-accessors.mm b/runtime/objc-accessors.mm index 67d9846..25ec71b 100644 --- a/runtime/objc-accessors.mm +++ b/runtime/objc-accessors.mm @@ -27,9 +27,7 @@ #include #include "objc-private.h" -#include "objc-auto.h" #include "runtime.h" -#include "objc-accessors.h" // stub interface declarations to make compiler happy. @@ -41,11 +39,14 @@ @interface __NSMutableCopyable - (id)mutableCopyWithZone:(void *)zone; @end +// These locks must not be at function scope. static StripedMap PropertyLocks; +static StripedMap StructLocks; +static StripedMap CppObjectLocks; #define MUTABLE_COPY 2 -id objc_getProperty_non_gc(id self, SEL _cmd, ptrdiff_t offset, BOOL atomic) { +id objc_getProperty(id self, SEL _cmd, ptrdiff_t offset, BOOL atomic) { if (offset == 0) { return object_getClass(self); } @@ -100,7 +101,7 @@ static inline void reallySetProperty(id self, SEL _cmd, id newValue, ptrdiff_t o objc_release(oldValue); } -void objc_setProperty_non_gc(id self, SEL _cmd, ptrdiff_t offset, id newValue, BOOL atomic, signed char shouldCopy) +void objc_setProperty(id self, SEL _cmd, ptrdiff_t offset, id newValue, BOOL atomic, signed char shouldCopy) { bool copy = (shouldCopy && shouldCopy != MUTABLE_COPY); bool mutableCopy = (shouldCopy == MUTABLE_COPY); @@ -129,44 +130,10 @@ void objc_setProperty_nonatomic_copy(id self, SEL _cmd, id newValue, ptrdiff_t o } -#if SUPPORT_GC - -id objc_getProperty_gc(id self, SEL _cmd, ptrdiff_t offset, BOOL atomic) { - return *(id*) ((char*)self + offset); -} - -void objc_setProperty_gc(id self, SEL _cmd, ptrdiff_t offset, id newValue, BOOL atomic, signed char shouldCopy) { - if (shouldCopy) { - newValue = (shouldCopy == MUTABLE_COPY ? [newValue mutableCopyWithZone:nil] : [newValue copyWithZone:nil]); - } - objc_assign_ivar(newValue, self, offset); -} - -// objc_getProperty and objc_setProperty are resolver functions in objc-auto.mm - -#else - -id -objc_getProperty(id self, SEL _cmd, ptrdiff_t offset, BOOL atomic) -{ - return objc_getProperty_non_gc(self, _cmd, offset, atomic); -} - -void -objc_setProperty(id self, SEL _cmd, ptrdiff_t offset, id newValue, - BOOL atomic, signed char shouldCopy) -{ - objc_setProperty_non_gc(self, _cmd, offset, newValue, atomic, shouldCopy); -} - -#endif - - // This entry point was designed wrong. When used as a getter, src needs to be locked so that // if simultaneously used for a setter then there would be contention on src. // So we need two locks - one of which will be contended. -void objc_copyStruct(void *dest, const void *src, ptrdiff_t size, BOOL atomic, BOOL hasStrong) { - static StripedMap StructLocks; +void objc_copyStruct(void *dest, const void *src, ptrdiff_t size, BOOL atomic, BOOL hasStrong __unused) { spinlock_t *srcLock = nil; spinlock_t *dstLock = nil; if (atomic) { @@ -174,21 +141,15 @@ void objc_copyStruct(void *dest, const void *src, ptrdiff_t size, BOOL atomic, B dstLock = &StructLocks[dest]; spinlock_t::lockTwo(srcLock, dstLock); } -#if SUPPORT_GC - if (UseGC && hasStrong) { - auto_zone_write_barrier_memmove(gc_zone, dest, src, size); - } else -#endif - { - memmove(dest, src, size); - } + + memmove(dest, src, size); + if (atomic) { spinlock_t::unlockTwo(srcLock, dstLock); } } void objc_copyCppObjectAtomic(void *dest, const void *src, void (*copyHelper) (void *dest, const void *source)) { - static StripedMap CppObjectLocks; spinlock_t *srcLock = &CppObjectLocks[src]; spinlock_t *dstLock = &CppObjectLocks[dest]; spinlock_t::lockTwo(srcLock, dstLock); diff --git a/runtime/objc-api.h b/runtime/objc-api.h index dcc83f9..42f88e7 100644 --- a/runtime/objc-api.h +++ b/runtime/objc-api.h @@ -57,24 +57,22 @@ /* * OBJC_NO_GC 1: GC is not supported - * OBJC_NO_GC undef: GC is supported + * OBJC_NO_GC undef: GC is supported. This SDK no longer supports this mode. * * OBJC_NO_GC_API undef: Libraries must export any symbols that * dual-mode code may links to. * OBJC_NO_GC_API 1: Libraries need not export GC-related symbols. */ -#if TARGET_OS_EMBEDDED || TARGET_OS_IPHONE || TARGET_OS_WIN32 - /* GC is unsupported. GC API symbols are not exported. */ -# define OBJC_NO_GC 1 -# define OBJC_NO_GC_API 1 -#elif TARGET_OS_MAC && __x86_64h__ +#if defined(__OBJC_GC__) +# error Objective-C garbage collection is not supported. +#elif TARGET_OS_OSX /* GC is unsupported. GC API symbols are exported. */ # define OBJC_NO_GC 1 # undef OBJC_NO_GC_API #else - /* GC is supported. */ -# undef OBJC_NO_GC -# undef OBJC_GC_API + /* GC is unsupported. GC API symbols are not exported. */ +# define OBJC_NO_GC 1 +# define OBJC_NO_GC_API 1 #endif @@ -92,6 +90,14 @@ #endif +/* OBJC_AVAILABLE: shorthand for all-OS availability */ +#if !defined(OBJC_AVAILABLE) +# define OBJC_AVAILABLE(x, i, t, w) \ + __OSX_AVAILABLE(x) __IOS_AVAILABLE(i) \ + __TVOS_AVAILABLE(t) __WATCHOS_AVAILABLE(w) +#endif + + /* OBJC_ISA_AVAILABILITY: `isa` will be deprecated or unavailable * in the future */ #if !defined(OBJC_ISA_AVAILABILITY) @@ -109,18 +115,35 @@ # define OBJC2_UNAVAILABLE UNAVAILABLE_ATTRIBUTE # else /* plain C code also falls here, but this is close enough */ -# define OBJC2_UNAVAILABLE __OSX_AVAILABLE_BUT_DEPRECATED(__MAC_10_5,__MAC_10_5, __IPHONE_2_0,__IPHONE_2_0) +# define OBJC2_UNAVAILABLE \ + __OSX_DEPRECATED(10.5, 10.5, "not available in __OBJC2__") \ + __IOS_DEPRECATED(2.0, 2.0, "not available in __OBJC2__") \ + __TVOS_UNAVAILABLE __WATCHOS_UNAVAILABLE +# endif +#endif + +/* OBJC_UNAVAILABLE: unavailable, with a message where supported */ +#if !defined(OBJC_UNAVAILABLE) +# if __has_extension(attribute_unavailable_with_message) +# define OBJC_UNAVAILABLE(_msg) __attribute__((unavailable(_msg))) +# else +# define OBJC_UNAVAILABLE(_msg) __attribute__((unavailable)) +# endif +#endif + +/* OBJC_DEPRECATED: deprecated, with a message where supported */ +#if !defined(OBJC_DEPRECATED) +# if __has_extension(attribute_deprecated_with_message) +# define OBJC_DEPRECATED(_msg) __attribute__((deprecated(_msg))) +# else +# define OBJC_DEPRECATED(_msg) __attribute__((deprecated)) # endif #endif /* OBJC_ARC_UNAVAILABLE: unavailable with -fobjc-arc */ #if !defined(OBJC_ARC_UNAVAILABLE) # if __has_feature(objc_arc) -# if __has_extension(attribute_unavailable_with_message) -# define OBJC_ARC_UNAVAILABLE __attribute__((unavailable("not available in automatic reference counting mode"))) -# else -# define OBJC_ARC_UNAVAILABLE __attribute__((unavailable)) -# endif +# define OBJC_ARC_UNAVAILABLE OBJC_UNAVAILABLE("not available in automatic reference counting mode") # else # define OBJC_ARC_UNAVAILABLE # endif @@ -138,7 +161,7 @@ /* OBJC_ARM64_UNAVAILABLE: unavailable on arm64 (i.e. stret dispatch) */ #if !defined(OBJC_ARM64_UNAVAILABLE) # if defined(__arm64__) -# define OBJC_ARM64_UNAVAILABLE __attribute__((unavailable("not available in arm64"))) +# define OBJC_ARM64_UNAVAILABLE OBJC_UNAVAILABLE("not available in arm64") # else # define OBJC_ARM64_UNAVAILABLE # endif @@ -146,15 +169,7 @@ /* OBJC_GC_UNAVAILABLE: unavailable with -fobjc-gc or -fobjc-gc-only */ #if !defined(OBJC_GC_UNAVAILABLE) -# if __OBJC_GC__ -# if __has_extension(attribute_unavailable_with_message) -# define OBJC_GC_UNAVAILABLE __attribute__((unavailable("not available in garbage collecting mode"))) -# else -# define OBJC_GC_UNAVAILABLE __attribute__((unavailable)) -# endif -# else -# define OBJC_GC_UNAVAILABLE -# endif +# define OBJC_GC_UNAVAILABLE #endif #if !defined(OBJC_EXTERN) diff --git a/runtime/objc-auto-dump.h b/runtime/objc-auto-dump.h deleted file mode 100644 index d58e0c0..0000000 --- a/runtime/objc-auto-dump.h +++ /dev/null @@ -1,55 +0,0 @@ -// -// objc-auto-dump.h -// objc -// The raw dump file format -// See objc-gdb.h for the primitive. -// -// Created by Blaine Garst on 12/8/08. -// Copyright 2008 Apple, Inc. All rights reserved. -// -#ifndef _OBJC_AUTO_DUMP_H_ -#define _OBJC_AUTO_DUMP_H_ - -/* - * Raw file format definitions - */ - -// must be unique in first letter... -// RAW FORMAT -#define HEADER "dumpster" -#define THREAD 't' -#define LOCAL 'l' -#define NODE 'n' -#define REGISTER 'r' -#define ROOT 'g' -#define WEAK 'w' -#define CLASS 'c' -#define END 'e' - -#define SixtyFour 1 -#define Little 2 - -/* - -Raw format, not that anyone should really care. Most programs should use the cooked file reader. - - * -

:= 'd' 'u' 'm' 'p' 's' 't' 'e' 'r' ; the HEADER string - := SixtyFour? + Little? ; architecture - := | | | | - := * ; the triple - := 'r' longLength [bytes] ; the register bank - := 't' longLength [bytes] ; the stack - := 'l' [long] ; a thread local node - := 'g' longAddress longValue - := 'n' longAddress longSize intLayout longRefcount longIsa? - := 'w' longAddress longValue - := 'c' longAddress - := intLength [bytes] ; no null byte - := intLength [bytes] ; including 0 byte at end - := intLength [bytes] ; including 0 byte at end - := 'e' - - */ - -#endif diff --git a/runtime/objc-auto-dump.mm b/runtime/objc-auto-dump.mm deleted file mode 100644 index 9746daa..0000000 --- a/runtime/objc-auto-dump.mm +++ /dev/null @@ -1,224 +0,0 @@ -/* - * Copyright (c) 2008 Apple Inc. All Rights Reserved. - * - * @APPLE_LICENSE_HEADER_START@ - * - * This file contains Original Code and/or Modifications of Original Code - * as defined in and that are subject to the Apple Public Source License - * Version 2.0 (the 'License'). You may not use this file except in - * compliance with the License. Please obtain a copy of the License at - * http://www.opensource.apple.com/apsl/ and read it before using this - * file. - * - * The Original Code and all software distributed under the License are - * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER - * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, - * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. - * Please see the License for the specific language governing rights and - * limitations under the License. - * - * @APPLE_LICENSE_HEADER_END@ - */ - -#include "objc-config.h" - -#if SUPPORT_GC - -#include "objc-private.h" -#include "objc-auto-dump.h" - -#include -#include -#include -#include - -/* - * Utilities - */ - -static char myType() { - char type = 0; - if (sizeof(void *) == 8) type |= SixtyFour; -#if __LITTLE_ENDIAN__ - type |= Little; -#endif - return type; -} - -/* - * Sigh, a mutable set. - */ - -typedef struct { - long *items; - long count; - long capacity; -} pointer_set_t; - -static pointer_set_t *new_pointer_set() { - pointer_set_t *result = (pointer_set_t *)malloc(sizeof(pointer_set_t)); - result->items = (long *)calloc(64, sizeof(long)); - result->count = 0; - result->capacity = 63; // last valid ptr, also mask - return result; -} - -static void pointer_set_grow(pointer_set_t *set); - -static void pointer_set_add(pointer_set_t *set, long ptr) { - long hash = ptr & set->capacity; - while (1) { - if (!set->items[hash]) { - set->items[hash] = ptr; - ++set->count; - if (set->count*3 > set->capacity*2) - pointer_set_grow(set); - return; - } - if (set->items[hash] == ptr) return; - hash = (hash + 1) & set->capacity; - } -} - -static void pointer_set_grow(pointer_set_t *set) { - long oldCapacity = set->capacity; - long *oldItems = set->items; - long i; - set->count = 0; - set->capacity = 2*(oldCapacity+1)-1; - set->items = (long *)calloc(2*(oldCapacity+1), sizeof(long)); - for (i = 0; i < oldCapacity; ++i) - if (oldItems[i]) pointer_set_add(set, oldItems[i]); - free(oldItems); -} - -static void pointer_set_iterate(pointer_set_t *set, void (^block)(long item)) { - long i; - for (i = 0; i < set->capacity; ++i) - if (set->items[i]) block(set->items[i]); -} - -static void pointer_set_dispose(pointer_set_t *set) { - free(set->items); - free(set); -} - -/* - Quickly dump heap to a named file in a pretty raw format. - */ -bool _objc_dumpHeap(auto_zone_t *zone, const char *filename) { - // just write interesting info to disk - int fd = secure_open(filename, O_WRONLY|O_CREAT, geteuid()); - if (fd < 0) return NO; - FILE *fp = fdopen(fd, "w"); - if (fp == NULL) { - return NO; - } - - fwrite(HEADER, strlen(HEADER), 1, fp); - char type2 = myType(); - fwrite(&type2, 1, 1, fp); - - // for each thread... - - // do registers first - auto_zone_register_dump dump_registers = ^(const void *base, unsigned long byte_size) { - char type = REGISTER; - fwrite(&type, 1, 1, fp); - //fwrite(REGISTER, strlen(REGISTER), 1, fp); - fwrite(&byte_size, sizeof(byte_size), 1, fp); - fwrite(base, byte_size, 1, fp); - }; - - // then stacks - auto_zone_stack_dump dump_stack = ^(const void *base, unsigned long byte_size) { - char type = THREAD; - fwrite(&type, 1, 1, fp); - //fwrite(THREAD, strlen(THREAD), 1, fp); - fwrite(&byte_size, sizeof(byte_size), 1, fp); - fwrite(base, byte_size, 1, fp); - }; - - // then locals - void (^dump_local)(const void *, unsigned long, unsigned int, unsigned long) = - ^(const void *address, unsigned long size, unsigned int layout, unsigned long refcount) { - // just write the value - rely on it showing up again as a node later - char type = LOCAL; - fwrite(&type, 1, 1, fp); - fwrite(&address, sizeof(address), 1, fp); - }; - - - - // roots - auto_zone_root_dump dump_root = ^(const void **address) { - char type = ROOT; - fwrite(&type, 1, 1, fp); - // write the address so that we can catch misregistered globals - fwrite(&address, sizeof(address), 1, fp); - // write content, even (?) if zero - fwrite(address, sizeof(*address), 1, fp); - }; - - // the nodes - pointer_set_t *classes = new_pointer_set(); - auto_zone_node_dump dump_node = ^(const void *address, unsigned long size, unsigned int layout, unsigned long refcount) { - char type = NODE; - fwrite(&type, 1, 1, fp); - fwrite(&address, sizeof(address), 1, fp); - fwrite(&size, sizeof(size), 1, fp); - fwrite(&layout, sizeof(layout), 1, fp); - fwrite(&refcount, sizeof(refcount), 1, fp); - if ((layout & AUTO_UNSCANNED) != AUTO_UNSCANNED) { - // now the nodes unfiltered content - fwrite(address, size, 1, fp); - } - if ((layout & AUTO_OBJECT) == AUTO_OBJECT) { - long theClass = *(long *)address; - if (theClass) pointer_set_add(classes, theClass); - } - }; - - // weak - auto_zone_weak_dump dump_weak = ^(const void **address, const void *item) { - char type = WEAK; - fwrite(&type, 1, 1, fp); - fwrite(&address, sizeof(address), 1, fp); - fwrite(&item, sizeof(item), 1, fp); - }; - - auto_zone_dump(zone, dump_stack, dump_registers, dump_local, dump_root, dump_node, dump_weak); - - pointer_set_iterate(classes, ^(long cls) { - char type = CLASS; - fwrite(&type, 1, 1, fp); - fwrite(&cls, sizeof(cls), 1, fp); // write address so that we can map it from node isa's - // classname (for grins) - const char *className = class_getName((Class)cls); - unsigned int length = (int)strlen(className); - fwrite(&length, sizeof(length), 1, fp); // n - fwrite(className, length, 1, fp); // n bytes - // strong layout - const uint8_t *layout = class_getIvarLayout((Class)cls); - length = layout ? (int)strlen((char *)layout)+1 : 0; // format is ending with <0><0> - fwrite(&length, sizeof(length), 1, fp); // n - fwrite(layout, length, 1, fp); // n bytes - // weak layout - layout = class_getWeakIvarLayout((Class)cls); - length = layout ? (int)strlen((char *)layout)+1 : 0; // format is ending with <0><0> - fwrite(&length, sizeof(length), 1, fp); // n - fwrite(layout, length, 1, fp); // n bytes - }); - - { - // end - char type = END; - fwrite(&type, 1, 1, fp); - fclose(fp); - pointer_set_dispose(classes); - } - return YES; -} - -#endif diff --git a/runtime/objc-auto.h b/runtime/objc-auto.h index 8c755a4..3624af5 100644 --- a/runtime/objc-auto.h +++ b/runtime/objc-auto.h @@ -34,165 +34,102 @@ #include #include -#if !TARGET_OS_WIN32 #include #include + + +// Define OBJC_SILENCE_GC_DEPRECATIONS=1 to temporarily +// silence deprecation warnings for GC functions. + +#if OBJC_SILENCE_GC_DEPRECATIONS +# define OBJC_GC_DEPRECATED(message) +#elif __has_extension(attribute_deprecated_with_message) +# define OBJC_GC_DEPRECATED(message) __attribute__((deprecated(message ". Define OBJC_SILENCE_GC_DEPRECATIONS=1 to temporarily silence this diagnostic."))) #else -# define WINVER 0x0501 // target Windows XP and later -# define _WIN32_WINNT 0x0501 // target Windows XP and later -# define WIN32_LEAN_AND_MEAN -// workaround: windef.h typedefs BOOL as int -# define BOOL WINBOOL -# include -# undef BOOL +# define OBJC_GC_DEPRECATED(message) __attribute__((deprecated)) #endif -/* objc_collect() options */ enum { - // choose one - OBJC_RATIO_COLLECTION = (0 << 0), // run "ratio" generational collections, then a full - OBJC_GENERATIONAL_COLLECTION = (1 << 0), // run fast incremental collection - OBJC_FULL_COLLECTION = (2 << 0), // run full collection. - OBJC_EXHAUSTIVE_COLLECTION = (3 << 0), // run full collections until memory available stops improving + OBJC_RATIO_COLLECTION = (0 << 0), + OBJC_GENERATIONAL_COLLECTION = (1 << 0), + OBJC_FULL_COLLECTION = (2 << 0), + OBJC_EXHAUSTIVE_COLLECTION = (3 << 0), - OBJC_COLLECT_IF_NEEDED = (1 << 3), // run collection only if needed (allocation threshold exceeded) - OBJC_WAIT_UNTIL_DONE = (1 << 4), // wait (when possible) for collection to end before returning (when collector is running on dedicated thread) + OBJC_COLLECT_IF_NEEDED = (1 << 3), + OBJC_WAIT_UNTIL_DONE = (1 << 4), }; -/* objc_clear_stack() options */ enum { OBJC_CLEAR_RESIDENT_STACK = (1 << 0) }; -#ifndef OBJC_NO_GC +#ifndef OBJC_NO_GC -/* GC declarations */ -/* Collection utilities */ +/* Out-of-line declarations */ OBJC_EXPORT void objc_collect(unsigned long options) - __OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_NA); + __OSX_DEPRECATED(10.6, 10.8, "it does nothing") __IOS_UNAVAILABLE __TVOS_UNAVAILABLE __WATCHOS_UNAVAILABLE; OBJC_EXPORT BOOL objc_collectingEnabled(void) - __OSX_AVAILABLE_STARTING(__MAC_10_5, __IPHONE_NA); + __OSX_DEPRECATED(10.5, 10.8, "it always returns NO") __IOS_UNAVAILABLE __TVOS_UNAVAILABLE __WATCHOS_UNAVAILABLE; OBJC_EXPORT malloc_zone_t *objc_collectableZone(void) - __OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_NA); - -/* GC configuration */ - -/* Tells collector to wait until specified bytes have been allocated before trying to collect again. */ + __OSX_DEPRECATED(10.7, 10.8, "it always returns nil") __IOS_UNAVAILABLE __TVOS_UNAVAILABLE __WATCHOS_UNAVAILABLE; OBJC_EXPORT void objc_setCollectionThreshold(size_t threshold) - __OSX_AVAILABLE_STARTING(__MAC_10_5, __IPHONE_NA); - -/* Tells collector to run a full collection for every ratio generational collections. */ + __OSX_DEPRECATED(10.5, 10.8, "it does nothing") __IOS_UNAVAILABLE __TVOS_UNAVAILABLE __WATCHOS_UNAVAILABLE; OBJC_EXPORT void objc_setCollectionRatio(size_t ratio) - __OSX_AVAILABLE_STARTING(__MAC_10_5, __IPHONE_NA); - -// -// GC-safe compare-and-swap -// - -/* Atomic update, with write barrier. */ + __OSX_DEPRECATED(10.5, 10.8, "it does nothing") __IOS_UNAVAILABLE __TVOS_UNAVAILABLE __WATCHOS_UNAVAILABLE; OBJC_EXPORT BOOL objc_atomicCompareAndSwapPtr(id predicate, id replacement, volatile id *objectLocation) - __OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_NA) OBJC_ARC_UNAVAILABLE; -/* "Barrier" version also includes memory barrier. */ + __OSX_DEPRECATED(10.6, 10.8, "use OSAtomicCompareAndSwapPtr instead") __IOS_UNAVAILABLE __TVOS_UNAVAILABLE __WATCHOS_UNAVAILABLE OBJC_ARC_UNAVAILABLE; OBJC_EXPORT BOOL objc_atomicCompareAndSwapPtrBarrier(id predicate, id replacement, volatile id *objectLocation) - __OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_NA) OBJC_ARC_UNAVAILABLE; - -// atomic update of a global variable + __OSX_DEPRECATED(10.6, 10.8, "use OSAtomicCompareAndSwapPtrBarrier instead") __IOS_UNAVAILABLE __TVOS_UNAVAILABLE __WATCHOS_UNAVAILABLE OBJC_ARC_UNAVAILABLE; OBJC_EXPORT BOOL objc_atomicCompareAndSwapGlobal(id predicate, id replacement, volatile id *objectLocation) - __OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_NA) OBJC_ARC_UNAVAILABLE; + __OSX_DEPRECATED(10.6, 10.8, "use OSAtomicCompareAndSwapPtr instead") __IOS_UNAVAILABLE __TVOS_UNAVAILABLE __WATCHOS_UNAVAILABLE OBJC_ARC_UNAVAILABLE; OBJC_EXPORT BOOL objc_atomicCompareAndSwapGlobalBarrier(id predicate, id replacement, volatile id *objectLocation) - __OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_NA) OBJC_ARC_UNAVAILABLE; -// atomic update of an instance variable + __OSX_DEPRECATED(10.6, 10.8, "use OSAtomicCompareAndSwapPtrBarrier instead") __IOS_UNAVAILABLE __TVOS_UNAVAILABLE __WATCHOS_UNAVAILABLE OBJC_ARC_UNAVAILABLE; OBJC_EXPORT BOOL objc_atomicCompareAndSwapInstanceVariable(id predicate, id replacement, volatile id *objectLocation) - __OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_NA) OBJC_ARC_UNAVAILABLE; + __OSX_DEPRECATED(10.6, 10.8, "use OSAtomicCompareAndSwapPtr instead") __IOS_UNAVAILABLE __TVOS_UNAVAILABLE __WATCHOS_UNAVAILABLE OBJC_ARC_UNAVAILABLE; OBJC_EXPORT BOOL objc_atomicCompareAndSwapInstanceVariableBarrier(id predicate, id replacement, volatile id *objectLocation) - __OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_NA) OBJC_ARC_UNAVAILABLE; - - -// -// Read and write barriers -// - + __OSX_DEPRECATED(10.6, 10.8, "use OSAtomicCompareAndSwapPtrBarrier instead") __IOS_UNAVAILABLE __TVOS_UNAVAILABLE __WATCHOS_UNAVAILABLE OBJC_ARC_UNAVAILABLE; OBJC_EXPORT id objc_assign_strongCast(id val, id *dest) - __OSX_AVAILABLE_STARTING(__MAC_10_4, __IPHONE_NA); + __OSX_DEPRECATED(10.4, 10.8, "use a simple assignment instead") __IOS_UNAVAILABLE __TVOS_UNAVAILABLE __WATCHOS_UNAVAILABLE; OBJC_EXPORT id objc_assign_global(id val, id *dest) - __OSX_AVAILABLE_STARTING(__MAC_10_4, __IPHONE_NA); + __OSX_DEPRECATED(10.4, 10.8, "use a simple assignment instead") __IOS_UNAVAILABLE __TVOS_UNAVAILABLE __WATCHOS_UNAVAILABLE; OBJC_EXPORT id objc_assign_threadlocal(id val, id *dest) - __OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_NA); + __OSX_DEPRECATED(10.7, 10.8, "use a simple assignment instead") __IOS_UNAVAILABLE __TVOS_UNAVAILABLE __WATCHOS_UNAVAILABLE; OBJC_EXPORT id objc_assign_ivar(id value, id dest, ptrdiff_t offset) - __OSX_AVAILABLE_STARTING(__MAC_10_4, __IPHONE_NA); + __OSX_DEPRECATED(10.4, 10.8, "use a simple assignment instead") __IOS_UNAVAILABLE __TVOS_UNAVAILABLE __WATCHOS_UNAVAILABLE; OBJC_EXPORT void *objc_memmove_collectable(void *dst, const void *src, size_t size) - __OSX_AVAILABLE_STARTING(__MAC_10_4, __IPHONE_NA); - + __OSX_DEPRECATED(10.4, 10.8, "use memmove instead") __IOS_UNAVAILABLE __TVOS_UNAVAILABLE __WATCHOS_UNAVAILABLE; OBJC_EXPORT id objc_read_weak(id *location) - __OSX_AVAILABLE_STARTING(__MAC_10_5, __IPHONE_NA); + __OSX_DEPRECATED(10.5, 10.8, "use a simple read instead, or convert to zeroing __weak") __IOS_UNAVAILABLE __TVOS_UNAVAILABLE __WATCHOS_UNAVAILABLE; OBJC_EXPORT id objc_assign_weak(id value, id *location) - __OSX_AVAILABLE_STARTING(__MAC_10_5, __IPHONE_NA); - - -// -// Thread management -// - -/* Register the calling thread with the garbage collector. */ + __OSX_DEPRECATED(10.5, 10.8, "use a simple assignment instead, or convert to zeroing __weak") __IOS_UNAVAILABLE __TVOS_UNAVAILABLE __WATCHOS_UNAVAILABLE; OBJC_EXPORT void objc_registerThreadWithCollector(void) - __OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_NA); - -/* Unregisters the calling thread with the garbage collector. - Unregistration also happens automatically at thread exit. */ + __OSX_DEPRECATED(10.6, 10.8, "it does nothing") __IOS_UNAVAILABLE __TVOS_UNAVAILABLE __WATCHOS_UNAVAILABLE; OBJC_EXPORT void objc_unregisterThreadWithCollector(void) - __OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_NA); - -/* To be called from code which must only execute on a registered thread. */ -/* If the calling thread is unregistered then an error message is emitted and the thread is implicitly registered. */ + __OSX_DEPRECATED(10.6, 10.8, "it does nothing") __IOS_UNAVAILABLE __TVOS_UNAVAILABLE __WATCHOS_UNAVAILABLE; OBJC_EXPORT void objc_assertRegisteredThreadWithCollector(void) - __OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_NA); - -/* Erases any stale references in unused parts of the stack. */ + __OSX_DEPRECATED(10.6, 10.8, "it does nothing") __IOS_UNAVAILABLE __TVOS_UNAVAILABLE __WATCHOS_UNAVAILABLE; OBJC_EXPORT void objc_clear_stack(unsigned long options) - __OSX_AVAILABLE_STARTING(__MAC_10_5, __IPHONE_NA); - - -// -// Finalization -// - -/* Returns true if object has been scheduled for finalization. Can be used to avoid operations that may lead to resurrection, which are fatal. */ + __OSX_DEPRECATED(10.5, 10.8, "it does nothing") __IOS_UNAVAILABLE __TVOS_UNAVAILABLE __WATCHOS_UNAVAILABLE; OBJC_EXPORT BOOL objc_is_finalized(void *ptr) - __OSX_AVAILABLE_STARTING(__MAC_10_4, __IPHONE_NA); - -// Deprcated. Tells runtime to issue finalize calls on the main thread only. + __OSX_DEPRECATED(10.4, 10.8, "it always returns NO") __IOS_UNAVAILABLE __TVOS_UNAVAILABLE __WATCHOS_UNAVAILABLE; OBJC_EXPORT void objc_finalizeOnMainThread(Class cls) - __OSX_AVAILABLE_BUT_DEPRECATED(__MAC_10_5,__MAC_10_5, __IPHONE_NA,__IPHONE_NA); - - -// -// Deprecated names. -// - -/* Deprecated. Use objc_collectingEnabled() instead. */ + __OSX_DEPRECATED(10.5, 10.5, "it does nothing") __IOS_UNAVAILABLE __TVOS_UNAVAILABLE __WATCHOS_UNAVAILABLE; OBJC_EXPORT BOOL objc_collecting_enabled(void) - __OSX_AVAILABLE_BUT_DEPRECATED(__MAC_10_4,__MAC_10_5, __IPHONE_NA,__IPHONE_NA); -/* Deprecated. Use objc_setCollectionThreshold() instead. */ + __OSX_DEPRECATED(10.4, 10.5, "it always returns NO") __IOS_UNAVAILABLE __TVOS_UNAVAILABLE __WATCHOS_UNAVAILABLE; OBJC_EXPORT void objc_set_collection_threshold(size_t threshold) - __OSX_AVAILABLE_BUT_DEPRECATED(__MAC_10_4,__MAC_10_5, __IPHONE_NA,__IPHONE_NA); -/* Deprecated. Use objc_setCollectionRatio() instead. */ + __OSX_DEPRECATED(10.4, 10.5, "it does nothing") __IOS_UNAVAILABLE __TVOS_UNAVAILABLE __WATCHOS_UNAVAILABLE; OBJC_EXPORT void objc_set_collection_ratio(size_t ratio) - __OSX_AVAILABLE_BUT_DEPRECATED(__MAC_10_4,__MAC_10_5, __IPHONE_NA,__IPHONE_NA); -/* Deprecated. Use objc_startCollectorThread() instead. */ + __OSX_DEPRECATED(10.4, 10.5, "it does nothing") __IOS_UNAVAILABLE __TVOS_UNAVAILABLE __WATCHOS_UNAVAILABLE; OBJC_EXPORT void objc_start_collector_thread(void) - __OSX_AVAILABLE_BUT_DEPRECATED(__MAC_10_4,__MAC_10_5, __IPHONE_NA,__IPHONE_NA); -/* Deprecated. No replacement. Formerly told the collector to run using a dedicated background thread. */ + __OSX_DEPRECATED(10.4, 10.5, "it does nothing") __IOS_UNAVAILABLE __TVOS_UNAVAILABLE __WATCHOS_UNAVAILABLE; OBJC_EXPORT void objc_startCollectorThread(void) -__OSX_AVAILABLE_BUT_DEPRECATED(__MAC_10_5,__MAC_10_7, __IPHONE_NA,__IPHONE_NA); - - -/* Deprecated. Use class_createInstance() instead. */ + __OSX_DEPRECATED(10.5, 10.7, "it does nothing") __IOS_UNAVAILABLE __TVOS_UNAVAILABLE __WATCHOS_UNAVAILABLE; OBJC_EXPORT id objc_allocate_object(Class cls, int extra) -__OSX_AVAILABLE_BUT_DEPRECATED(__MAC_10_4,__MAC_10_4, __IPHONE_NA,__IPHONE_NA); + __OSX_DEPRECATED(10.4, 10.4, "use class_createInstance instead") __IOS_UNAVAILABLE __TVOS_UNAVAILABLE __WATCHOS_UNAVAILABLE; /* !defined(OBJC_NO_GC) */ @@ -200,15 +137,21 @@ __OSX_AVAILABLE_BUT_DEPRECATED(__MAC_10_4,__MAC_10_4, __IPHONE_NA,__IPHONE_NA); /* defined(OBJC_NO_GC) */ -/* Non-GC declarations */ +/* Inline declarations */ +OBJC_GC_DEPRECATED("it does nothing") static OBJC_INLINE void objc_collect(unsigned long options __unused) { } +OBJC_GC_DEPRECATED("it always returns NO") static OBJC_INLINE BOOL objc_collectingEnabled(void) { return NO; } -#if TARGET_OS_MAC && !TARGET_OS_EMBEDDED && !TARGET_IPHONE_SIMULATOR +#if TARGET_OS_OSX +OBJC_GC_DEPRECATED("it always returns nil") static OBJC_INLINE malloc_zone_t *objc_collectableZone(void) { return nil; } #endif +OBJC_GC_DEPRECATED("it does nothing") static OBJC_INLINE void objc_setCollectionThreshold(size_t threshold __unused) { } +OBJC_GC_DEPRECATED("it does nothing") static OBJC_INLINE void objc_setCollectionRatio(size_t ratio __unused) { } +OBJC_GC_DEPRECATED("it does nothing") static OBJC_INLINE void objc_startCollectorThread(void) { } #if __has_feature(objc_arc) @@ -217,89 +160,96 @@ static OBJC_INLINE void objc_startCollectorThread(void) { } #else -#if TARGET_OS_WIN32 -static OBJC_INLINE BOOL objc_atomicCompareAndSwapPtr(id predicate, id replacement, volatile id *objectLocation) - { void *original = InterlockedCompareExchangePointer((void * volatile *)objectLocation, (void *)replacement, (void *)predicate); return (original == predicate); } - -static OBJC_INLINE BOOL objc_atomicCompareAndSwapPtrBarrier(id predicate, id replacement, volatile id *objectLocation) - { void *original = InterlockedCompareExchangePointer((void * volatile *)objectLocation, (void *)replacement, (void *)predicate); return (original == predicate); } -#else +OBJC_GC_DEPRECATED("use OSAtomicCompareAndSwapPtr instead") static OBJC_INLINE BOOL objc_atomicCompareAndSwapPtr(id predicate, id replacement, volatile id *objectLocation) { return OSAtomicCompareAndSwapPtr((void *)predicate, (void *)replacement, (void * volatile *)objectLocation); } +OBJC_GC_DEPRECATED("use OSAtomicCompareAndSwapPtrBarrier instead") static OBJC_INLINE BOOL objc_atomicCompareAndSwapPtrBarrier(id predicate, id replacement, volatile id *objectLocation) { return OSAtomicCompareAndSwapPtrBarrier((void *)predicate, (void *)replacement, (void * volatile *)objectLocation); } -#endif +OBJC_GC_DEPRECATED("use OSAtomicCompareAndSwapPtr instead") static OBJC_INLINE BOOL objc_atomicCompareAndSwapGlobal(id predicate, id replacement, volatile id *objectLocation) { return objc_atomicCompareAndSwapPtr(predicate, replacement, objectLocation); } +OBJC_GC_DEPRECATED("use OSAtomicCompareAndSwapPtrBarrier instead") static OBJC_INLINE BOOL objc_atomicCompareAndSwapGlobalBarrier(id predicate, id replacement, volatile id *objectLocation) { return objc_atomicCompareAndSwapPtrBarrier(predicate, replacement, objectLocation); } +OBJC_GC_DEPRECATED("use OSAtomicCompareAndSwapPtr instead") static OBJC_INLINE BOOL objc_atomicCompareAndSwapInstanceVariable(id predicate, id replacement, volatile id *objectLocation) { return objc_atomicCompareAndSwapPtr(predicate, replacement, objectLocation); } +OBJC_GC_DEPRECATED("use OSAtomicCompareAndSwapPtrBarrier instead") static OBJC_INLINE BOOL objc_atomicCompareAndSwapInstanceVariableBarrier(id predicate, id replacement, volatile id *objectLocation) { return objc_atomicCompareAndSwapPtrBarrier(predicate, replacement, objectLocation); } +OBJC_GC_DEPRECATED("use a simple assignment instead") static OBJC_INLINE id objc_assign_strongCast(id val, id *dest) { return (*dest = val); } +OBJC_GC_DEPRECATED("use a simple assignment instead") static OBJC_INLINE id objc_assign_global(id val, id *dest) { return (*dest = val); } +OBJC_GC_DEPRECATED("use a simple assignment instead") static OBJC_INLINE id objc_assign_threadlocal(id val, id *dest) { return (*dest = val); } +OBJC_GC_DEPRECATED("use a simple assignment instead") static OBJC_INLINE id objc_assign_ivar(id val, id dest, ptrdiff_t offset) { return (*(id*)((char *)dest+offset) = val); } +OBJC_GC_DEPRECATED("use a simple read instead, or convert to zeroing __weak") static OBJC_INLINE id objc_read_weak(id *location) { return *location; } +OBJC_GC_DEPRECATED("use a simple assignment instead, or convert to zeroing __weak") static OBJC_INLINE id objc_assign_weak(id value, id *location) { return (*location = value); } /* MRC */ #endif +OBJC_GC_DEPRECATED("use memmove instead") static OBJC_INLINE void *objc_memmove_collectable(void *dst, const void *src, size_t size) { return memmove(dst, src, size); } +OBJC_GC_DEPRECATED("it does nothing") static OBJC_INLINE void objc_finalizeOnMainThread(Class cls __unused) { } +OBJC_GC_DEPRECATED("it always returns NO") static OBJC_INLINE BOOL objc_is_finalized(void *ptr __unused) { return NO; } +OBJC_GC_DEPRECATED("it does nothing") static OBJC_INLINE void objc_clear_stack(unsigned long options __unused) { } - +OBJC_GC_DEPRECATED("it always returns NO") static OBJC_INLINE BOOL objc_collecting_enabled(void) { return NO; } +OBJC_GC_DEPRECATED("it does nothing") static OBJC_INLINE void objc_set_collection_threshold(size_t threshold __unused) { } +OBJC_GC_DEPRECATED("it does nothing") static OBJC_INLINE void objc_set_collection_ratio(size_t ratio __unused) { } +OBJC_GC_DEPRECATED("it does nothing") static OBJC_INLINE void objc_start_collector_thread(void) { } #if __has_feature(objc_arc) extern id objc_allocate_object(Class cls, int extra) UNAVAILABLE_ATTRIBUTE; #else OBJC_EXPORT id class_createInstance(Class cls, size_t extraBytes) - __OSX_AVAILABLE_STARTING(__MAC_10_0, __IPHONE_2_0); + OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0); +OBJC_GC_DEPRECATED("use class_createInstance instead") static OBJC_INLINE id objc_allocate_object(Class cls, int extra) { return class_createInstance(cls, extra); } #endif +OBJC_GC_DEPRECATED("it does nothing") static OBJC_INLINE void objc_registerThreadWithCollector() { } +OBJC_GC_DEPRECATED("it does nothing") static OBJC_INLINE void objc_unregisterThreadWithCollector() { } +OBJC_GC_DEPRECATED("it does nothing") static OBJC_INLINE void objc_assertRegisteredThreadWithCollector() { } /* defined(OBJC_NO_GC) */ #endif -#if TARGET_OS_EMBEDDED -enum { - OBJC_GENERATIONAL = (1 << 0) -}; -static OBJC_INLINE void objc_collect_if_needed(unsigned long options) __attribute__((deprecated)); -static OBJC_INLINE void objc_collect_if_needed(unsigned long options __unused) { } -#endif - #endif diff --git a/runtime/objc-auto.mm b/runtime/objc-auto.mm index 2030cc8..bf7dceb 100644 --- a/runtime/objc-auto.mm +++ b/runtime/objc-auto.mm @@ -23,16 +23,24 @@ #include "objc-private.h" +// GC is no longer supported. -#if OBJC_NO_GC && OBJC_NO_GC_API +#if OBJC_NO_GC_API // No GC and no GC symbols needed. We're done here. +# if SUPPORT_GC_COMPAT +# error inconsistent config settings +# endif -#elif OBJC_NO_GC && !OBJC_NO_GC_API +#else // No GC but we do need to export GC symbols. // These are mostly the same as the OBJC_NO_GC inline versions in objc-auto.h. +# if !SUPPORT_GC_COMPAT +# error inconsistent config settings +# endif + OBJC_EXPORT void objc_collect(unsigned long options __unused) { } OBJC_EXPORT BOOL objc_collectingEnabled(void) { return NO; } OBJC_EXPORT void objc_setCollectionThreshold(size_t threshold __unused) { } @@ -65,7 +73,6 @@ OBJC_EXPORT BOOL objc_atomicCompareAndSwapInstanceVariable(id predicate, id repl OBJC_EXPORT BOOL objc_atomicCompareAndSwapInstanceVariableBarrier(id predicate, id replacement, volatile id *objectLocation) { return objc_atomicCompareAndSwapPtrBarrier(predicate, replacement, objectLocation); } - OBJC_EXPORT id objc_assign_strongCast(id val, id *dest) { return (*dest = val); } @@ -110,1321 +117,5 @@ OBJC_EXPORT void objc_assertRegisteredThreadWithCollector() { } OBJC_EXPORT BOOL objc_dumpHeap(char *filename __unused, unsigned long length __unused) { return NO; } -// OBJC_NO_GC && !OBJC_NO_GC_API -#else -// !OBJC_NO_GC - -// Garbage collection. - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include - -#include "objc-private.h" -#include "objc-config.h" -#include "objc-accessors.h" -#include "objc-auto.h" -#include "objc-references.h" -#include "maptable.h" -#include "message.h" -#include "objc-gdb.h" - -#if DEBUG && !__OBJC2__ -#include "objc-exception.h" -#endif - - -static auto_zone_t *gc_zone_init(void); -static void gc_block_init(void); -static void registeredClassTableInit(void); -static bool objc_isRegisteredClass(Class candidate); - -int8_t UseGC = -1; -static bool WantsMainThreadFinalization = NO; - -auto_zone_t *gc_zone = nil; - - -/* Method prototypes */ -@interface DoesNotExist -- (const char *)UTF8String; -- (id)description; -@end - - -/*********************************************************************** -* Break-on-error functions -**********************************************************************/ - -BREAKPOINT_FUNCTION( - void objc_assign_ivar_error(id base, ptrdiff_t offset) -); - -BREAKPOINT_FUNCTION( - void objc_assign_global_error(id value, id *slot) -); - -BREAKPOINT_FUNCTION( - void objc_exception_during_finalize_error(void) -); - -/*********************************************************************** -* Utility exports -* Called by various libraries. -**********************************************************************/ - -OBJC_EXPORT void objc_set_collection_threshold(size_t threshold) { // Old naming - if (UseGC) { - auto_collection_parameters(gc_zone)->collection_threshold = threshold; - } -} - -OBJC_EXPORT void objc_setCollectionThreshold(size_t threshold) { - if (UseGC) { - auto_collection_parameters(gc_zone)->collection_threshold = threshold; - } -} - -void objc_setCollectionRatio(size_t ratio) { - if (UseGC) { - auto_collection_parameters(gc_zone)->full_vs_gen_frequency = ratio; - } -} - -void objc_set_collection_ratio(size_t ratio) { // old naming - if (UseGC) { - auto_collection_parameters(gc_zone)->full_vs_gen_frequency = ratio; - } -} - -void objc_finalizeOnMainThread(Class cls) { - if (UseGC) { - WantsMainThreadFinalization = YES; - cls->setShouldFinalizeOnMainThread(); - } -} - -// stack based data structure queued if/when there is main-thread-only finalization work TBD -typedef struct BatchFinalizeBlock { - auto_zone_foreach_object_t foreach; - auto_zone_cursor_t cursor; - size_t cursor_size; - volatile bool finished; - volatile bool started; - struct BatchFinalizeBlock *next; -} BatchFinalizeBlock_t; - -// The Main Thread Finalization Work Queue Head -static struct { - pthread_mutex_t mutex; - pthread_cond_t condition; - BatchFinalizeBlock_t *head; - BatchFinalizeBlock_t *tail; -} MainThreadWorkQ; - - -void objc_startCollectorThread(void) { -} - -void objc_start_collector_thread(void) { -} - -static void batchFinalizeOnMainThread(void); - -void objc_collect(unsigned long options) { - if (!UseGC) return; - bool onMainThread = pthread_main_np(); - - // while we're here, sneak off and do some finalization work (if any) - if (onMainThread) batchFinalizeOnMainThread(); - // now on with our normally scheduled programming - auto_zone_options_t amode = AUTO_ZONE_COLLECT_NO_OPTIONS; - if (!(options & OBJC_COLLECT_IF_NEEDED)) { - switch (options & 0x3) { - case OBJC_RATIO_COLLECTION: amode = AUTO_ZONE_COLLECT_RATIO_COLLECTION; break; - case OBJC_GENERATIONAL_COLLECTION: amode = AUTO_ZONE_COLLECT_GENERATIONAL_COLLECTION; break; - case OBJC_FULL_COLLECTION: amode = AUTO_ZONE_COLLECT_FULL_COLLECTION; break; - case OBJC_EXHAUSTIVE_COLLECTION: amode = AUTO_ZONE_COLLECT_EXHAUSTIVE_COLLECTION; break; - } - amode |= AUTO_ZONE_COLLECT_COALESCE; - amode |= AUTO_ZONE_COLLECT_LOCAL_COLLECTION; - } - if (options & OBJC_WAIT_UNTIL_DONE) { - __block bool done = NO; - // If executing on the main thread, use the main thread work queue condition to block, - // so main thread finalization can complete. Otherwise, use a thread-local condition. - pthread_mutex_t localMutex = PTHREAD_MUTEX_INITIALIZER, *mutex = &localMutex; - pthread_cond_t localCondition = PTHREAD_COND_INITIALIZER, *condition = &localCondition; - if (onMainThread) { - mutex = &MainThreadWorkQ.mutex; - condition = &MainThreadWorkQ.condition; - } - pthread_mutex_lock(mutex); - auto_zone_collect_and_notify(gc_zone, amode, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ - pthread_mutex_lock(mutex); - done = YES; - pthread_cond_signal(condition); - pthread_mutex_unlock(mutex); - }); - while (!done) { - pthread_cond_wait(condition, mutex); - if (onMainThread && MainThreadWorkQ.head) { - pthread_mutex_unlock(mutex); - batchFinalizeOnMainThread(); - pthread_mutex_lock(mutex); - } - } - pthread_mutex_unlock(mutex); - } else { - auto_zone_collect(gc_zone, amode); - } -} - - -// USED BY CF & ONE OTHER -BOOL objc_isAuto(id object) -{ - return UseGC && auto_zone_is_valid_pointer(gc_zone, object) != 0; -} - - -BOOL objc_collectingEnabled(void) -{ - return UseGC; -} - -BOOL objc_collecting_enabled(void) // Old naming -{ - return UseGC; -} - -malloc_zone_t *objc_collectableZone(void) { - return gc_zone; -} - -BOOL objc_dumpHeap(char *filenamebuffer, unsigned long length) { - static int counter = 0; - ++counter; - char buffer[1024]; - sprintf(buffer, OBJC_HEAP_DUMP_FILENAME_FORMAT, getpid(), counter); - if (!_objc_dumpHeap(gc_zone, buffer)) return NO; - if (filenamebuffer) { - unsigned long blen = strlen(buffer); - if (blen < length) - strncpy(filenamebuffer, buffer, blen+1); - else if (length > 0) - filenamebuffer[0] = 0; // give some answer - } - return YES; -} - - -/*********************************************************************** -* Memory management. -* Called by CF and Foundation. -**********************************************************************/ - -// Allocate an object in the GC zone, with the given number of extra bytes. -id objc_allocate_object(Class cls, int extra) -{ - return class_createInstance(cls, extra); -} - - -/*********************************************************************** -* Write barrier implementations, optimized for when GC is known to be on -* Called by the write barrier exports only. -* These implementations assume GC is on. The exported function must -* either perform the check itself or be conditionally stomped at -* startup time. -**********************************************************************/ - -id objc_assign_strongCast_gc(id value, id *slot) { - if (!auto_zone_set_write_barrier(gc_zone, (void*)slot, value)) { // stores & returns true if slot points into GC allocated memory - auto_zone_root_write_barrier(gc_zone, slot, value); // always stores - } - return value; -} - -id objc_assign_global_gc(id value, id *slot) { - // use explicit root registration. - if (value && auto_zone_is_valid_pointer(gc_zone, value)) { - if (auto_zone_is_finalized(gc_zone, value)) { - _objc_inform("GC: storing an already collected object %p into global memory at %p, break on objc_assign_global_error to debug\n", (void*)value, slot); - objc_assign_global_error(value, slot); - } - auto_zone_add_root(gc_zone, slot, value); - } - else - *slot = value; - - return value; -} - -id objc_assign_threadlocal_gc(id value, id *slot) -{ - if (value && auto_zone_is_valid_pointer(gc_zone, value)) { - auto_zone_add_root(gc_zone, slot, value); - } - else { - *slot = value; - } - - return value; -} - -id objc_assign_ivar_gc(id value, id base, ptrdiff_t offset) -{ - id *slot = (id*) ((char *)base + offset); - - if (value) { - if (!auto_zone_set_write_barrier(gc_zone, (char *)base + offset, value)) { - _objc_inform("GC: %p + %tu isn't in the auto_zone, break on objc_assign_ivar_error to debug.\n", (void*)base, offset); - objc_assign_ivar_error(base, offset); - } - } - else - *slot = value; - - return value; -} - -id objc_assign_strongCast_non_gc(id value, id *slot) { - return (*slot = value); -} - -id objc_assign_global_non_gc(id value, id *slot) { - return (*slot = value); -} - -id objc_assign_threadlocal_non_gc(id value, id *slot) { - return (*slot = value); -} - -id objc_assign_ivar_non_gc(id value, id base, ptrdiff_t offset) { - id *slot = (id*) ((char *)base + offset); - return (*slot = value); -} - - -/*********************************************************************** -* Non-trivial write barriers -**********************************************************************/ - -void *objc_memmove_collectable(void *dst, const void *src, size_t size) -{ - if (UseGC) { - return auto_zone_write_barrier_memmove(gc_zone, dst, src, size); - } else { - return memmove(dst, src, size); - } -} - -BOOL objc_atomicCompareAndSwapPtr(id predicate, id replacement, volatile id *objectLocation) { - const BOOL issueMemoryBarrier = NO; - if (UseGC) - return auto_zone_atomicCompareAndSwapPtr(gc_zone, (void *)predicate, (void *)replacement, (void * volatile *)objectLocation, issueMemoryBarrier); - else - return OSAtomicCompareAndSwapPtr((void *)predicate, (void *)replacement, (void * volatile *)objectLocation); -} - -BOOL objc_atomicCompareAndSwapPtrBarrier(id predicate, id replacement, volatile id *objectLocation) { - const BOOL issueMemoryBarrier = YES; - if (UseGC) - return auto_zone_atomicCompareAndSwapPtr(gc_zone, (void *)predicate, (void *)replacement, (void * volatile *)objectLocation, issueMemoryBarrier); - else - return OSAtomicCompareAndSwapPtrBarrier((void *)predicate, (void *)replacement, (void * volatile *)objectLocation); -} - -BOOL objc_atomicCompareAndSwapGlobal(id predicate, id replacement, volatile id *objectLocation) { - const BOOL isGlobal = YES; - const BOOL issueMemoryBarrier = NO; - if (UseGC) - return auto_zone_atomicCompareAndSwap(gc_zone, (void *)predicate, (void *)replacement, (void * volatile *)objectLocation, isGlobal, issueMemoryBarrier); - else - return OSAtomicCompareAndSwapPtr((void *)predicate, (void *)replacement, (void * volatile *)objectLocation); -} - -BOOL objc_atomicCompareAndSwapGlobalBarrier(id predicate, id replacement, volatile id *objectLocation) { - const BOOL isGlobal = YES; - const BOOL issueMemoryBarrier = YES; - if (UseGC) - return auto_zone_atomicCompareAndSwap(gc_zone, (void *)predicate, (void *)replacement, (void * volatile *)objectLocation, isGlobal, issueMemoryBarrier); - else - return OSAtomicCompareAndSwapPtrBarrier((void *)predicate, (void *)replacement, (void * volatile *)objectLocation); -} - -BOOL objc_atomicCompareAndSwapInstanceVariable(id predicate, id replacement, volatile id *objectLocation) { - const BOOL isGlobal = NO; - const BOOL issueMemoryBarrier = NO; - if (UseGC) - return auto_zone_atomicCompareAndSwap(gc_zone, (void *)predicate, (void *)replacement, (void * volatile *)objectLocation, isGlobal, issueMemoryBarrier); - else - return OSAtomicCompareAndSwapPtr((void *)predicate, (void *)replacement, (void * volatile *)objectLocation); -} - -BOOL objc_atomicCompareAndSwapInstanceVariableBarrier(id predicate, id replacement, volatile id *objectLocation) { - const BOOL isGlobal = NO; - const BOOL issueMemoryBarrier = YES; - if (UseGC) - return auto_zone_atomicCompareAndSwap(gc_zone, (void *)predicate, (void *)replacement, (void * volatile *)objectLocation, isGlobal, issueMemoryBarrier); - else - return OSAtomicCompareAndSwapPtrBarrier((void *)predicate, (void *)replacement, (void * volatile *)objectLocation); -} - - -/*********************************************************************** -* Weak ivar support -**********************************************************************/ - -id objc_read_weak_gc(id *location) { - id result = *location; - if (result) { - result = (id)auto_read_weak_reference(gc_zone, (void **)location); - } - return result; -} - -id objc_read_weak_non_gc(id *location) { - return *location; -} - -id objc_assign_weak_gc(id value, id *location) { - auto_assign_weak_reference(gc_zone, value, (const void **)location, nil); - return value; -} - -id objc_assign_weak_non_gc(id value, id *location) { - return (*location = value); -} - - -void gc_fixup_weakreferences(id newObject, id oldObject) { - // fix up weak references if any. - const unsigned char *weakLayout = (const unsigned char *)class_getWeakIvarLayout(newObject->ISA()); - if (weakLayout) { - void **newPtr = (void **)newObject, **oldPtr = (void **)oldObject; - unsigned char byte; - while ((byte = *weakLayout++)) { - unsigned skips = (byte >> 4); - unsigned weaks = (byte & 0x0F); - newPtr += skips, oldPtr += skips; - while (weaks--) { - *newPtr = nil; - auto_assign_weak_reference(gc_zone, auto_read_weak_reference(gc_zone, oldPtr), (const void **)newPtr, nil); - ++newPtr, ++oldPtr; - } - } - } -} - - -/*********************************************************************** -* dyld resolver functions for basic GC write barriers -* dyld calls the resolver function to bind the symbol. -* We return the GC or non-GC variant as appropriate. -**********************************************************************/ - -#define GC_RESOLVER(name) \ - OBJC_EXPORT void *name##_resolver(void) __asm__("_" #name); \ - void *name##_resolver(void) \ - { \ - __asm__(".symbol_resolver _" #name); \ - if (UseGC) return (void*)name##_gc; \ - else return (void*)name##_non_gc; \ - } - -GC_RESOLVER(objc_assign_ivar) -GC_RESOLVER(objc_assign_strongCast) -GC_RESOLVER(objc_assign_global) -GC_RESOLVER(objc_assign_threadlocal) -GC_RESOLVER(objc_read_weak) -GC_RESOLVER(objc_assign_weak) -GC_RESOLVER(objc_getProperty) -GC_RESOLVER(objc_setProperty) -GC_RESOLVER(objc_getAssociatedObject) -GC_RESOLVER(objc_setAssociatedObject) -GC_RESOLVER(_object_addExternalReference) -GC_RESOLVER(_object_readExternalReference) -GC_RESOLVER(_object_removeExternalReference) - - -/*********************************************************************** -* Testing tools -* Used to isolate resurrection of garbage objects during finalization. -**********************************************************************/ -BOOL objc_is_finalized(void *ptr) { - if (ptr != nil && UseGC) { - return auto_zone_is_finalized(gc_zone, ptr); - } - return NO; -} - - -/*********************************************************************** -* Stack clearing. -* Used by top-level thread loops to reduce false pointers from the stack. -**********************************************************************/ -void objc_clear_stack(unsigned long options) { - if (!UseGC) return; - auto_zone_clear_stack(gc_zone, 0); -} - - -/*********************************************************************** -* Finalization support -**********************************************************************/ - -// Finalizer crash debugging -static void *finalizing_object; - -// finalize a single object without fuss -// When there are no main-thread-only classes this is used directly -// Otherwise, it is used indirectly by smarter code that knows main-thread-affinity requirements -static void finalizeOneObject(void *obj, void *ignored) { - id object = (id)obj; - finalizing_object = obj; - - Class cls = object->ISA(); - CRSetCrashLogMessage2(class_getName(cls)); - - /// call -finalize method. - ((void(*)(id, SEL))objc_msgSend)(object, @selector(finalize)); - - // Call C++ destructors. - // This would be objc_destructInstance() but for performance. - if (cls->hasCxxDtor()) { - object_cxxDestruct(object); - } - - finalizing_object = nil; - CRSetCrashLogMessage2(nil); -} - -// finalize object only if it is a main-thread-only object. -// Called only from the main thread. -static void finalizeOneMainThreadOnlyObject(void *obj, void *arg) { - id object = (id)obj; - Class cls = object->ISA(); - if (cls == nil) { - _objc_fatal("object with nil ISA passed to finalizeOneMainThreadOnlyObject: %p\n", obj); - } - if (cls->shouldFinalizeOnMainThread()) { - finalizeOneObject(obj, nil); - } -} - -// finalize one object only if it is not a main-thread-only object -// called from any other thread than the main thread -// Important: if a main-thread-only object is passed, return that fact in the needsMain argument -static void finalizeOneAnywhereObject(void *obj, void *needsMain) { - id object = (id)obj; - Class cls = object->ISA(); - bool *needsMainThreadWork = (bool *)needsMain; - if (cls == nil) { - _objc_fatal("object with nil ISA passed to finalizeOneAnywhereObject: %p\n", obj); - } - if (!cls->shouldFinalizeOnMainThread()) { - finalizeOneObject(obj, nil); - } - else { - *needsMainThreadWork = true; - } -} - - -// Utility workhorse. -// Set up the expensive @try block and ask the collector to hand the next object to -// our finalizeAnObject function. -// Track and return a boolean that records whether or not any main thread work is necessary. -// (When we know that there are no main thread only objects then the boolean isn't even computed) -static bool batchFinalize(auto_zone_t *zone, - auto_zone_foreach_object_t foreach, - auto_zone_cursor_t cursor, - size_t cursor_size, - void (*finalizeAnObject)(void *, void*)) -{ -#if DEBUG && !__OBJC2__ - // debug: don't call try/catch before exception handlers are installed - objc_exception_functions_t table = {}; - objc_exception_get_functions(&table); - assert(table.throw_exc); -#endif - - bool needsMainThreadWork = false; - for (;;) { - @try { - foreach(cursor, finalizeAnObject, &needsMainThreadWork); - // non-exceptional return means finalization is complete. - break; - } - @catch (id exception) { - // whoops, note exception, then restart at cursor's position - _objc_inform("GC: -finalize resulted in an exception (%p) being thrown, break on objc_exception_during_finalize_error to debug\n\t%s", exception, (const char*)[[exception description] UTF8String]); - objc_exception_during_finalize_error(); - } - @catch (...) { - // whoops, note exception, then restart at cursor's position - _objc_inform("GC: -finalize resulted in an exception being thrown, break on objc_exception_during_finalize_error to debug"); - objc_exception_during_finalize_error(); - } - } - return needsMainThreadWork; -} - -// Called on main thread-only. -// Pick up work from global queue. -// called parasitically by anyone requesting a collection -// called explicitly when there is known to be main thread only finalization work -// In both cases we are on the main thread -// Guard against recursion by something called from a finalizer -static void batchFinalizeOnMainThread() { - pthread_mutex_lock(&MainThreadWorkQ.mutex); - if (!MainThreadWorkQ.head || MainThreadWorkQ.head->started) { - // No work or we're already here - pthread_mutex_unlock(&MainThreadWorkQ.mutex); - return; - } - while (MainThreadWorkQ.head) { - BatchFinalizeBlock_t *bfb = MainThreadWorkQ.head; - bfb->started = YES; - pthread_mutex_unlock(&MainThreadWorkQ.mutex); - - batchFinalize(gc_zone, bfb->foreach, bfb->cursor, bfb->cursor_size, finalizeOneMainThreadOnlyObject); - // signal the collector thread(s) that finalization has finished. - pthread_mutex_lock(&MainThreadWorkQ.mutex); - bfb->finished = YES; - pthread_cond_broadcast(&MainThreadWorkQ.condition); - MainThreadWorkQ.head = bfb->next; - } - MainThreadWorkQ.tail = nil; - pthread_mutex_unlock(&MainThreadWorkQ.mutex); -} - - -// Knowing that we possibly have main thread only work to do, first process everything -// that is not main-thread-only. If we discover main thread only work, queue a work block -// to the main thread that will do just the main thread only work. Wait for it. -// Called from a non main thread. -static void batchFinalizeOnTwoThreads(auto_zone_t *zone, - auto_zone_foreach_object_t foreach, - auto_zone_cursor_t cursor, - size_t cursor_size) -{ - // First, lets get rid of everything we can on this thread, then ask main thread to help if needed - char cursor_copy[cursor_size]; - memcpy(cursor_copy, cursor, cursor_size); - bool needsMainThreadFinalization = batchFinalize(zone, foreach, (auto_zone_cursor_t)cursor_copy, cursor_size, finalizeOneAnywhereObject); - - if (! needsMainThreadFinalization) - return; // no help needed - - // set up the control block. Either our ping of main thread with _callOnMainThread will get to it, or - // an objc_collect(if_needed) will get to it. Either way, this block will be processed on the main thread. - BatchFinalizeBlock_t bfb; - bfb.foreach = foreach; - bfb.cursor = cursor; - bfb.cursor_size = cursor_size; - bfb.started = NO; - bfb.finished = NO; - bfb.next = nil; - pthread_mutex_lock(&MainThreadWorkQ.mutex); - if (MainThreadWorkQ.tail) { - - // link to end so that ordering of finalization is preserved. - MainThreadWorkQ.tail->next = &bfb; - MainThreadWorkQ.tail = &bfb; - } - else { - MainThreadWorkQ.head = &bfb; - MainThreadWorkQ.tail = &bfb; - } - pthread_mutex_unlock(&MainThreadWorkQ.mutex); - - //printf("----->asking main thread to finalize\n"); - dispatch_async(dispatch_get_main_queue(), ^{ batchFinalizeOnMainThread(); }); - - // wait for the main thread to finish finalizing instances of classes marked CLS_FINALIZE_ON_MAIN_THREAD. - pthread_mutex_lock(&MainThreadWorkQ.mutex); - while (!bfb.finished) { - // the main thread might be blocked waiting for a synchronous collection to complete, so wake it here - pthread_cond_signal(&MainThreadWorkQ.condition); - pthread_cond_wait(&MainThreadWorkQ.condition, &MainThreadWorkQ.mutex); - } - pthread_mutex_unlock(&MainThreadWorkQ.mutex); - //printf("<------ main thread finalize done\n"); - -} - - - -// collector calls this with garbage ready -// thread collectors, too, so this needs to be thread-safe -static void BatchInvalidate(auto_zone_t *zone, - auto_zone_foreach_object_t foreach, - auto_zone_cursor_t cursor, - size_t cursor_size) -{ - if (pthread_main_np() || !WantsMainThreadFinalization) { - // Collect all objects. We're either pre-multithreaded on main thread or we're on the collector thread - // but no main-thread-only objects have been allocated. - batchFinalize(zone, foreach, cursor, cursor_size, finalizeOneObject); - } - else { - // We're on the dedicated thread. Collect some on main thread, the rest here. - batchFinalizeOnTwoThreads(zone, foreach, cursor, cursor_size); - } - -} - - -/* - * Zombie support - * Collector calls into this system when it finds resurrected objects. - * This keeps them pitifully alive and leaked, even if they reference garbage. - */ - -// idea: keep a side table mapping resurrected object pointers to their original Class, so we don't -// need to smash anything. alternatively, could use associative references to track against a secondary -// object with information about the resurrection, such as a stack crawl, etc. - -static Class _NSResurrectedObjectClass; -static NXMapTable *_NSResurrectedObjectMap = nil; -static pthread_mutex_t _NSResurrectedObjectLock = PTHREAD_MUTEX_INITIALIZER; - -static Class resurrectedObjectOriginalClass(id object) { - Class originalClass; - pthread_mutex_lock(&_NSResurrectedObjectLock); - originalClass = (Class) NXMapGet(_NSResurrectedObjectMap, object); - pthread_mutex_unlock(&_NSResurrectedObjectLock); - return originalClass; -} - -static id _NSResurrectedObject_classMethod(id self, SEL selector) { return self; } - -static id _NSResurrectedObject_instanceMethod(id self, SEL name) { - _objc_inform("**resurrected** object %p of class %s being sent message '%s'\n", (void*)self, class_getName(resurrectedObjectOriginalClass(self)), sel_getName(name)); - return self; -} - -static void _NSResurrectedObject_finalize(id self, SEL _cmd) { - Class originalClass; - pthread_mutex_lock(&_NSResurrectedObjectLock); - originalClass = (Class) NXMapRemove(_NSResurrectedObjectMap, self); - pthread_mutex_unlock(&_NSResurrectedObjectLock); - if (originalClass) _objc_inform("**resurrected** object %p of class %s being finalized\n", (void*)self, class_getName(originalClass)); - _objc_rootFinalize(self); -} - -static bool _NSResurrectedObject_resolveInstanceMethod(id self, SEL _cmd, SEL name) { - class_addMethod((Class)self, name, (IMP)_NSResurrectedObject_instanceMethod, "@@:"); - return YES; -} - -static bool _NSResurrectedObject_resolveClassMethod(id self, SEL _cmd, SEL name) { - class_addMethod(self->ISA(), name, (IMP)_NSResurrectedObject_classMethod, "@@:"); - return YES; -} - -static void _NSResurrectedObject_initialize() { - _NSResurrectedObjectMap = NXCreateMapTable(NXPtrValueMapPrototype, 128); - _NSResurrectedObjectClass = objc_allocateClassPair(objc_getClass("NSObject"), "_NSResurrectedObject", 0); - class_addMethod(_NSResurrectedObjectClass, @selector(finalize), (IMP)_NSResurrectedObject_finalize, "v@:"); - Class metaClass = _NSResurrectedObjectClass->ISA(); - class_addMethod(metaClass, @selector(resolveInstanceMethod:), (IMP)_NSResurrectedObject_resolveInstanceMethod, "c@::"); - class_addMethod(metaClass, @selector(resolveClassMethod:), (IMP)_NSResurrectedObject_resolveClassMethod, "c@::"); - objc_registerClassPair(_NSResurrectedObjectClass); -} - -static void resurrectZombie(auto_zone_t *zone, void *ptr) { - id object = (id) ptr; - Class cls = object->ISA(); - if (cls != _NSResurrectedObjectClass) { - // remember the original class for this instance. - pthread_mutex_lock(&_NSResurrectedObjectLock); - NXMapInsert(_NSResurrectedObjectMap, ptr, cls); - pthread_mutex_unlock(&_NSResurrectedObjectLock); - object_setClass(object, _NSResurrectedObjectClass); - } -} - -/*********************************************************************** -* Pretty printing support -* For development purposes. -**********************************************************************/ - - -static char *name_for_address(auto_zone_t *zone, vm_address_t base, vm_address_t offset, int withRetainCount); - -static char* objc_name_for_address(auto_zone_t *zone, vm_address_t base, vm_address_t offset) -{ - return name_for_address(zone, base, offset, false); -} - -static const char* objc_name_for_object(auto_zone_t *zone, void *object) { - Class cls = *(Class *)object; - if (!objc_isRegisteredClass(cls)) return ""; - return class_getName(cls); -} - -/*********************************************************************** -* Collection support -**********************************************************************/ - -static bool objc_isRegisteredClass(Class candidate); - -static const unsigned char *objc_layout_for_address(auto_zone_t *zone, void *address) { - id object = (id)address; - volatile void *clsptr = (void*)object->ISA(); - Class cls = (Class)clsptr; - return objc_isRegisteredClass(cls) ? _object_getIvarLayout(cls, object) : nil; -} - -static const unsigned char *objc_weak_layout_for_address(auto_zone_t *zone, void *address) { - id object = (id)address; - volatile void *clsptr = (void*)object->ISA(); - Class cls = (Class)clsptr; - return objc_isRegisteredClass(cls) ? class_getWeakIvarLayout(cls) : nil; -} - -void gc_register_datasegment(uintptr_t base, size_t size) { - auto_zone_register_datasegment(gc_zone, (void*)base, size); -} - -void gc_unregister_datasegment(uintptr_t base, size_t size) { - auto_zone_unregister_datasegment(gc_zone, (void*)base, size); -} - - -/*********************************************************************** -* Initialization -**********************************************************************/ - -static void objc_will_grow(auto_zone_t *zone, auto_heap_growth_info_t info) { - if (auto_zone_is_collecting(gc_zone)) { - ; - } - else { - auto_zone_collect(gc_zone, AUTO_ZONE_COLLECT_COALESCE|AUTO_ZONE_COLLECT_RATIO_COLLECTION); - } -} - - -static auto_zone_t *gc_zone_init(void) -{ - auto_zone_t *result; - static int didOnce = 0; - if (!didOnce) { - didOnce = 1; - - // initialize the batch finalization queue - MainThreadWorkQ.head = nil; - MainThreadWorkQ.tail = nil; - pthread_mutex_init(&MainThreadWorkQ.mutex, nil); - pthread_cond_init(&MainThreadWorkQ.condition, nil); - } - - result = auto_zone_create("auto_zone"); - - auto_zone_disable_compaction(result); - - auto_collection_control_t *control = auto_collection_parameters(result); - - // set up the magic control parameters - control->batch_invalidate = BatchInvalidate; - control->will_grow = objc_will_grow; - control->resurrect = resurrectZombie; - control->layout_for_address = objc_layout_for_address; - control->weak_layout_for_address = objc_weak_layout_for_address; - control->name_for_address = objc_name_for_address; - - if (control->version >= sizeof(auto_collection_control_t)) { - control->name_for_object = objc_name_for_object; - } - - return result; -} - - -/* should be defined in /usr/local/include/libdispatch_private.h. */ -extern void (*dispatch_begin_thread_4GC)(void); -extern void (*dispatch_end_thread_4GC)(void); - -static void objc_reapThreadLocalBlocks() -{ - if (UseGC) auto_zone_reap_all_local_blocks(gc_zone); -} - -void objc_registerThreadWithCollector() -{ - if (UseGC) auto_zone_register_thread(gc_zone); -} - -void objc_unregisterThreadWithCollector() -{ - if (UseGC) auto_zone_unregister_thread(gc_zone); -} - -void objc_assertRegisteredThreadWithCollector() -{ - if (UseGC) auto_zone_assert_thread_registered(gc_zone); -} - -// Always called by _objcInit, even if GC is off. -void gc_init(bool wantsGC) -{ - assert(UseGC == -1); - UseGC = wantsGC; - - if (PrintGC) { - _objc_inform("GC: is %s", wantsGC ? "ON" : "OFF"); - } - - if (UseGC) { - // Set up the GC zone - gc_zone = gc_zone_init(); - - // tell libdispatch to register its threads with the GC. - dispatch_begin_thread_4GC = objc_registerThreadWithCollector; - dispatch_end_thread_4GC = objc_reapThreadLocalBlocks; - - // set up the registered classes list - registeredClassTableInit(); - - // tell Blocks to use collectable memory. CF will cook up the classes separately. - gc_block_init(); - - // Add GC state to crash log reports - _objc_inform_on_crash("garbage collection is ON"); - } -} - - -// Called by NSObject +load to perform late GC setup -// This work must wait until after all of libSystem initializes. -void gc_init2(void) -{ - assert(UseGC); - - // create the _NSResurrectedObject class used to track resurrections. - _NSResurrectedObject_initialize(); - - // tell libauto to set up its dispatch queues - auto_collect_multithreaded(gc_zone); -} - -// Called by Foundation. -// This function used to initialize NSObject stuff, but now does nothing. -malloc_zone_t *objc_collect_init(int (*callback)(void) __unused) -{ - return (malloc_zone_t *)gc_zone; -} - -/* - * Support routines for the Block implementation - */ - - -// The Block runtime now needs to sometimes allocate a Block that is an Object - namely -// when it neesd to have a finalizer which, for now, is only if there are C++ destructors -// in the helper function. Hence the isObject parameter. -// Under GC a -copy message should allocate a refcount 0 block, ergo the isOne parameter. -static void *block_gc_alloc5(const unsigned long size, const bool isOne, const bool isObject) { - auto_memory_type_t type = isObject ? (AUTO_OBJECT|AUTO_MEMORY_SCANNED) : AUTO_MEMORY_SCANNED; - return auto_zone_allocate_object(gc_zone, size, type, isOne, false); -} - -// The Blocks runtime keeps track of everything above 1 and so it only calls -// up to the collector to tell it about the 0->1 transition and then the 1->0 transition -static void block_gc_setHasRefcount(const void *block, const bool hasRefcount) { - if (hasRefcount) - auto_zone_retain(gc_zone, (void *)block); - else - auto_zone_release(gc_zone, (void *)block); -} - -static void block_gc_memmove(void *dst, void *src, unsigned long size) { - auto_zone_write_barrier_memmove(gc_zone, dst, src, (size_t)size); -} - -static void gc_block_init(void) { - _Block_use_GC( - block_gc_alloc5, - block_gc_setHasRefcount, - (void (*)(void *, void **))objc_assign_strongCast_gc, - (void (*)(const void *, void *))objc_assign_weak, - block_gc_memmove - ); -} - - -/*********************************************************************** -* Track classes. -* In addition to the global class hashtable (set) indexed by name, we -* also keep one based purely by pointer when running under Garbage Collection. -* This allows the background collector to race against objects recycled from TLC. -* Specifically, the background collector can read the admin byte and see that -* a thread local object is an object, get scheduled out, and the TLC recovers it, -* linking it into the cache, then the background collector reads the isa field and -* finds linkage info. By qualifying all isa fields read we avoid this. -**********************************************************************/ - -// This is a self-contained hash table of all classes. The first two elements contain the (size-1) and count. -static volatile Class *AllClasses = nil; - -#define SHIFT 3 -#define INITIALSIZE 512 -#define REMOVED ~0ul - -// Allocate the side table. -static void registeredClassTableInit() { - assert(UseGC); - // allocate a collectable (refcount 0) zeroed hunk of unscanned memory - uintptr_t *table = (uintptr_t *)auto_zone_allocate_object(gc_zone, INITIALSIZE*sizeof(void *), AUTO_MEMORY_UNSCANNED, true, true); - // set initial capacity (as mask) - table[0] = INITIALSIZE - 1; - // set initial count - table[1] = 0; - AllClasses = (Class *)table; -} - -// Verify that a particular pointer is to a class. -// Safe from any thread anytime -static bool objc_isRegisteredClass(Class candidate) { - assert(UseGC); - // nil is never a valid ISA. - if (candidate == nil) return NO; - // We don't care about a race with another thread adding a class to which we randomly might have a pointer - // Get local copy of classes so that we're immune from updates. - // We keep the size of the list as the first element so there is no race as the list & size get updated. - uintptr_t *allClasses = (uintptr_t *)AllClasses; - // Slot 0 is always the size of the list in log 2 masked terms (e.g. size - 1) where size is always power of 2 - // Slot 1 is count - uintptr_t slot = (((uintptr_t)candidate) >> SHIFT) & allClasses[0]; - // avoid slot 0 and 1 - if (slot < 2) slot = 2; - for(;;) { - long int slotValue = allClasses[slot]; - if (slotValue == (long int)candidate) { - return YES; - } - if (slotValue == 0) { - return NO; - } - ++slot; - if (slot > allClasses[0]) - slot = 2; // skip size, count - } -} - -// Utility used when growing -// Assumes lock held -static void addClassHelper(uintptr_t *table, uintptr_t candidate) { - uintptr_t slot = (((long int)candidate) >> SHIFT) & table[0]; - if (slot < 2) slot = 2; - for(;;) { - uintptr_t slotValue = table[slot]; - if (slotValue == 0) { - table[slot] = candidate; - ++table[1]; - return; - } - ++slot; - if (slot > table[0]) - slot = 2; // skip size, count - } -} - -// lock held by callers -void objc_addRegisteredClass(Class candidate) { - if (!UseGC) return; - uintptr_t *table = (uintptr_t *)AllClasses; - // Slot 0 is always the size of the list in log 2 masked terms (e.g. size - 1) where size is always power of 2 - // Slot 1 is count - always non-zero - uintptr_t slot = (((long int)candidate) >> SHIFT) & table[0]; - if (slot < 2) slot = 2; - for(;;) { - uintptr_t slotValue = table[slot]; - assert(slotValue != (uintptr_t)candidate); - if (slotValue == REMOVED) { - table[slot] = (long)candidate; - return; - } - else if (slotValue == 0) { - table[slot] = (long)candidate; - if (2*++table[1] > table[0]) { // add to count; check if we cross 50% utilization - // grow - uintptr_t oldSize = table[0]+1; - uintptr_t *newTable = (uintptr_t *)auto_zone_allocate_object(gc_zone, oldSize*2*sizeof(void *), AUTO_MEMORY_UNSCANNED, true, true); - uintptr_t i; - newTable[0] = 2*oldSize - 1; - newTable[1] = 0; - for (i = 2; i < oldSize; ++i) { - if (table[i] && table[i] != REMOVED) - addClassHelper(newTable, table[i]); - } - AllClasses = (Class *)newTable; - // let the old table be collected when other threads are no longer reading it. - auto_zone_release(gc_zone, (void *)table); - } - return; - } - ++slot; - if (slot > table[0]) - slot = 2; // skip size, count - } -} - -// lock held by callers -void objc_removeRegisteredClass(Class candidate) { - if (!UseGC) return; - uintptr_t *table = (uintptr_t *)AllClasses; - // Slot 0 is always the size of the list in log 2 masked terms (e.g. size - 1) where size is always power of 2 - // Slot 1 is count - always non-zero - uintptr_t slot = (((uintptr_t)candidate) >> SHIFT) & table[0]; - if (slot < 2) slot = 2; - for(;;) { - uintptr_t slotValue = table[slot]; - if (slotValue == (uintptr_t)candidate) { - table[slot] = REMOVED; // if next slot == 0 we could set to 0 here and decr count - return; - } - assert(slotValue != 0); - ++slot; - if (slot > table[0]) - slot = 2; // skip size, count - } -} - - -/*********************************************************************** -* Debugging - support for smart printouts when errors occur -**********************************************************************/ - - -static malloc_zone_t *objc_debug_zone(void) -{ - static malloc_zone_t *z = nil; - if (!z) { - z = malloc_create_zone(PAGE_MAX_SIZE, 0); - malloc_set_zone_name(z, "objc-auto debug"); - } - return z; -} - -static char *_malloc_append_unsigned(uintptr_t value, unsigned base, char *head) { - if (!value) { - head[0] = '0'; - } else { - if (value >= base) head = _malloc_append_unsigned(value / base, base, head); - value = value % base; - head[0] = (value < 10) ? '0' + value : 'a' + value - 10; - } - return head+1; -} - -static void strlcati(char *str, uintptr_t value, size_t bufSize) -{ - if ( (bufSize - strlen(str)) < 30) - return; - str = _malloc_append_unsigned(value, 10, str + strlen(str)); - str[0] = '\0'; -} - - -static Ivar ivar_for_offset(Class cls, vm_address_t offset) -{ - unsigned i; - vm_address_t ivar_offset; - Ivar super_ivar, result; - Ivar *ivars; - unsigned int ivar_count; - - if (!cls) return nil; - - // scan base classes FIRST - super_ivar = ivar_for_offset(cls->superclass, offset); - // result is best-effort; our ivars may be closer - - ivars = class_copyIvarList(cls, &ivar_count); - if (ivars && ivar_count) { - // Try our first ivar. If it's too big, use super's best ivar. - // (lose 64-bit precision) - ivar_offset = ivar_getOffset(ivars[0]); - if (ivar_offset > offset) result = super_ivar; - else if (ivar_offset == offset) result = ivars[0]; - else result = nil; - - // Try our other ivars. If any is too big, use the previous. - for (i = 1; result == nil && i < ivar_count; i++) { - ivar_offset = ivar_getOffset(ivars[i]); - if (ivar_offset == offset) { - result = ivars[i]; - } else if (ivar_offset > offset) { - result = ivars[i - 1]; - } - } - - // Found nothing. Return our last ivar. - if (result == nil) - result = ivars[ivar_count - 1]; - - free(ivars); - } else { - result = super_ivar; - } - - return result; -} - -static void append_ivar_at_offset(char *buf, Class cls, vm_address_t offset, size_t bufSize) -{ - Ivar ivar = nil; - - if (offset == 0) return; // don't bother with isa - if (offset >= class_getInstanceSize(cls)) { - strlcat(buf, ".+", bufSize); - strlcati(buf, offset, bufSize); - return; - } - - ivar = ivar_for_offset(cls, offset); - if (!ivar) { - strlcat(buf, ".", bufSize); - return; - } - - // fixme doesn't handle structs etc. - - strlcat(buf, ".", bufSize); - const char *ivar_name = ivar_getName(ivar); - if (ivar_name) strlcat(buf, ivar_name, bufSize); - else strlcat(buf, "", bufSize); - - offset -= ivar_getOffset(ivar); - if (offset > 0) { - strlcat(buf, "+", bufSize); - strlcati(buf, offset, bufSize); - } -} - - -static const char *cf_class_for_object(void *cfobj) -{ - // ick - we don't link against CF anymore - - struct fake_cfclass { - size_t version; - const char *className; - // don't care about the rest - }; - - const char *result; - void *dlh; - size_t (*CFGetTypeID)(void *); - fake_cfclass * (*_CFRuntimeGetClassWithTypeID)(size_t); - - result = "anonymous_NSCFType"; - - dlh = dlopen("/System/Library/Frameworks/CoreFoundation.framework/Versions/A/CoreFoundation", RTLD_LAZY | RTLD_NOLOAD | RTLD_FIRST); - if (!dlh) return result; - - CFGetTypeID = (size_t(*)(void*)) dlsym(dlh, "CFGetTypeID"); - _CFRuntimeGetClassWithTypeID = (fake_cfclass*(*)(size_t)) dlsym(dlh, "_CFRuntimeGetClassWithTypeID"); - - if (CFGetTypeID && _CFRuntimeGetClassWithTypeID) { - size_t cfid = (*CFGetTypeID)(cfobj); - result = (*_CFRuntimeGetClassWithTypeID)(cfid)->className; - } - - dlclose(dlh); - return result; -} - - -static char *name_for_address(auto_zone_t *zone, vm_address_t base, vm_address_t offset, int withRetainCount) -{ -#define APPEND_SIZE(s) \ - strlcat(buf, "[", sizeof(buf)); \ - strlcati(buf, s, sizeof(buf)); \ - strlcat(buf, "]", sizeof(buf)); - - char buf[1500]; - char *result; - - buf[0] = '\0'; - - size_t size = - auto_zone_size(zone, (void *)base); - auto_memory_type_t type = size ? - auto_zone_get_layout_type(zone, (void *)base) : AUTO_TYPE_UNKNOWN; - unsigned int refcount = size ? - auto_zone_retain_count(zone, (void *)base) : 0; - - switch (type) { - case AUTO_OBJECT_SCANNED: - case AUTO_OBJECT_UNSCANNED: - case AUTO_OBJECT_ALL_POINTERS: { - const char *class_name = object_getClassName((id)base); - if ((0 == strcmp(class_name, "__NSCFType")) || (0 == strcmp(class_name, "NSCFType"))) { - strlcat(buf, cf_class_for_object((void *)base), sizeof(buf)); - } else { - strlcat(buf, class_name, sizeof(buf)); - } - if (offset) { - append_ivar_at_offset(buf, ((id)base)->ISA(), offset, sizeof(buf)); - } - APPEND_SIZE(size); - break; - } - case AUTO_MEMORY_SCANNED: - strlcat(buf, "{conservative-block}", sizeof(buf)); - APPEND_SIZE(size); - break; - case AUTO_MEMORY_UNSCANNED: - strlcat(buf, "{no-pointers-block}", sizeof(buf)); - APPEND_SIZE(size); - break; - case AUTO_MEMORY_ALL_POINTERS: - strlcat(buf, "{all-pointers-block}", sizeof(buf)); - APPEND_SIZE(size); - break; - case AUTO_MEMORY_ALL_WEAK_POINTERS: - strlcat(buf, "{all-weak-pointers-block}", sizeof(buf)); - APPEND_SIZE(size); - break; - case AUTO_TYPE_UNKNOWN: - strlcat(buf, "{uncollectable-memory}", sizeof(buf)); - break; - default: - strlcat(buf, "{unknown-memory-type}", sizeof(buf)); - } - - if (withRetainCount && refcount > 0) { - strlcat(buf, " [[refcount=", sizeof(buf)); - strlcati(buf, refcount, sizeof(buf)); - strlcat(buf, "]]", sizeof(buf)); - } - - size_t len = 1 + strlen(buf); - result = (char *)malloc_zone_malloc(objc_debug_zone(), len); - memcpy(result, buf, len); - return result; - -#undef APPEND_SIZE -} - - -// not OBJC_NO_GC +// not OBJC_NO_GC_API #endif diff --git a/runtime/objc-cache-old.mm b/runtime/objc-cache-old.mm index a14556f..12baf36 100644 --- a/runtime/objc-cache-old.mm +++ b/runtime/objc-cache-old.mm @@ -576,58 +576,6 @@ void _cache_addForwardEntry(Class cls, SEL sel) } -/*********************************************************************** -* _cache_addIgnoredEntry -* Add an entry for the ignored selector to cls's method cache. -* Does nothing if the cache addition fails for any reason. -* Returns the ignored IMP. -* Cache locks: cacheUpdateLock must not be held. -**********************************************************************/ -#if SUPPORT_GC && !SUPPORT_IGNORED_SELECTOR_CONSTANT -static cache_entry *alloc_ignored_entries(void) -{ - cache_entry *e = (cache_entry *)malloc(5 * sizeof(cache_entry)); - e[0] = (cache_entry){ @selector(retain), 0,(IMP)&_objc_ignored_method}; - e[1] = (cache_entry){ @selector(release), 0,(IMP)&_objc_ignored_method}; - e[2] = (cache_entry){ @selector(autorelease),0,(IMP)&_objc_ignored_method}; - e[3] = (cache_entry){ @selector(retainCount),0,(IMP)&_objc_ignored_method}; - e[4] = (cache_entry){ @selector(dealloc), 0,(IMP)&_objc_ignored_method}; - return e; -} -#endif - -IMP _cache_addIgnoredEntry(Class cls, SEL sel) -{ - cache_entry *entryp = NULL; - -#if !SUPPORT_GC - _objc_fatal("selector ignored with GC off"); -#elif SUPPORT_IGNORED_SELECTOR_CONSTANT - static cache_entry entry = { (SEL)kIgnore, 0, (IMP)&_objc_ignored_method }; - entryp = &entry; - assert(sel == (SEL)kIgnore); -#else - // hack - int i; - static cache_entry *entries; - INIT_ONCE_PTR(entries, alloc_ignored_entries(), free(v)); - - assert(ignoreSelector(sel)); - for (i = 0; i < 5; i++) { - if (sel == entries[i].name) { - entryp = &entries[i]; - break; - } - } - if (!entryp) _objc_fatal("selector %s (%p) is not ignored", - sel_getName(sel), sel); -#endif - - _cache_fill(cls, (Method)entryp, sel); - return entryp->imp; -} - - /*********************************************************************** * _cache_flush. Invalidate all valid entries in the given class' cache. * diff --git a/runtime/objc-cache.mm b/runtime/objc-cache.mm index ae3723b..a0c6545 100644 --- a/runtime/objc-cache.mm +++ b/runtime/objc-cache.mm @@ -173,11 +173,6 @@ static inline mask_t cache_next(mask_t i, mask_t mask) { #endif -#if SUPPORT_IGNORED_SELECTOR_CONSTANT -#error sorry not implemented -#endif - - // copied from dispatch_atomic_maximally_synchronizing_barrier // fixme verify that this barrier hack does in fact work here #if __x86_64__ @@ -494,7 +489,8 @@ cache_key_t getKey(SEL sel) _objc_inform_now_and_on_crash ("isa '%s'", isa->nameForLogging()); _objc_fatal - ("Method cache corrupted."); + ("Method cache corrupted. This may be a message to an " + "invalid object, or a memory error somewhere else."); } @@ -853,8 +849,11 @@ void cache_collect(bool collectALot) } // Dispose all refs now in the garbage + // Erase each entry so debugging tools don't see stale pointers. while (garbage_count--) { - free(garbage_refs[garbage_count]); + auto dead = garbage_refs[garbage_count]; + garbage_refs[garbage_count] = nil; + free(dead); } // Clear the garbage count and total size indicator diff --git a/runtime/objc-class-old.mm b/runtime/objc-class-old.mm index e07080b..f62bdee 100644 --- a/runtime/objc-class-old.mm +++ b/runtime/objc-class-old.mm @@ -99,7 +99,6 @@ static void allocateExt(Class cls) static inline old_method *_findNamedMethodInList(old_method_list * mlist, const char *meth_name) { int i; if (!mlist) return nil; - if (ignoreSelectorNamed(meth_name)) return nil; for (i = 0; i < mlist->method_count; i++) { old_method *m = &mlist->method_list[i]; if (0 == strcmp((const char *)(m->method_name), meth_name)) { @@ -134,7 +133,6 @@ void disableSharedCacheOptimizations(void) /*********************************************************************** * fixupSelectorsInMethodList * Uniques selectors in the given method list. -* Also replaces imps for GC-ignored selectors * The given method list must be non-nil and not already fixed-up. * If the class was loaded from a bundle: * fixes up the given list in place with heap-allocated selector strings @@ -170,10 +168,6 @@ void disableSharedCacheOptimizations(void) method = &mlist->method_list[i]; method->method_name = sel_registerNameNoLock((const char *)method->method_name, isBundle); // Always copy selector data from bundles. - - if (ignoreSelector(method->method_name)) { - method->method_imp = (IMP)&_objc_ignored_method; - } } sel_unlock(); mlist->obsolete = fixed_up_method_list; @@ -312,7 +306,7 @@ void disableSharedCacheOptimizations(void) } -// fixme for gc debugging temporary use +// called by a debugging check in _objc_insertMethods IMP findIMPInClass(Class cls, SEL sel) { old_method *m = _findMethodInClass(cls, sel); @@ -414,12 +408,6 @@ IMP lookUpImpOrForward(Class cls, SEL sel, id inst, retry: methodListLock.lock(); - // Ignore GC selectors - if (ignoreSelector(sel)) { - methodPC = _cache_addIgnoredEntry(cls, sel); - goto done; - } - // Try this class's cache. methodPC = _cache_getImp(cls, sel); @@ -482,9 +470,6 @@ IMP lookUpImpOrForward(Class cls, SEL sel, id inst, done: methodListLock.unlock(); - // paranoia: look for ignored selectors with non-ignored implementations - assert(!(ignoreSelector(sel) && methodPC != (IMP)&_objc_ignored_method)); - return methodPC; } @@ -537,11 +522,32 @@ IMP lookupMethodInClassAndLoadCache(Class cls, SEL sel) } +/*********************************************************************** +* _class_getClassForIvar +* Given a class and an ivar that is in it or one of its superclasses, +* find the actual class that defined the ivar. +**********************************************************************/ +Class _class_getClassForIvar(Class cls, Ivar ivar) +{ + for ( ; cls; cls = cls->superclass) { + if (auto ivars = cls->ivars) { + if (ivar >= &ivars->ivar_list[0] && + ivar < &ivars->ivar_list[ivars->ivar_count]) + { + return cls; + } + } + } + + return nil; +} + + /*********************************************************************** * class_getVariable. Return the named instance variable. **********************************************************************/ -Ivar _class_getVariable(Class cls, const char *name, Class *memberOf) +Ivar _class_getVariable(Class cls, const char *name) { for (; cls != Nil; cls = cls->superclass) { int i; @@ -555,7 +561,6 @@ Ivar _class_getVariable(Class cls, const char *name, Class *memberOf) // (e.g. for anonymous bit fields). old_ivar *ivar = &cls->ivars->ivar_list[i]; if (ivar->ivar_name && 0 == strcmp(name, ivar->ivar_name)) { - if (memberOf) *memberOf = cls; return (Ivar)ivar; } } @@ -1065,7 +1070,7 @@ void change_class_references(Class imposter, // Replace the original with the imposter in all class refs // Major loop - process all headers - for (hInfo = FirstHeader; hInfo != nil; hInfo = hInfo->next) + for (hInfo = FirstHeader; hInfo != nil; hInfo = hInfo->getNext()) { Class *cls_refs; size_t refCount; @@ -1150,7 +1155,6 @@ Class class_poseAs(Class imposter, Class original) NXHashRemove (class_hash, original); NXHashInsert (class_hash, copy); - objc_addRegisteredClass(copy); // imposter & original will rejoin later, just track the new guy // Mark the imposter as such imposter->setInfo(CLS_POSING); @@ -1449,17 +1453,6 @@ bool _class_hasLoadMethod(Class cls) return nil; } -BOOL _class_usesAutomaticRetainRelease(Class cls) -{ - return NO; -} - -uint32_t _class_getInstanceStart(Class cls) -{ - _objc_fatal("_class_getInstanceStart() unimplemented for fragile instance variables"); - return 0; // PCB: never used just provided for ARR consistency. -} - ptrdiff_t ivar_getOffset(Ivar ivar) { return oldivar(ivar)->ivar_offset; @@ -1519,11 +1512,6 @@ IMP method_setImplementation(Method m_gen, IMP imp) old_method *m = oldmethod(m_gen); if (!m) return nil; if (!imp) return nil; - - if (ignoreSelector(m->method_name)) { - // Ignored methods stay ignored - return m->method_imp; - } impLock.lock(); old = m->method_imp; @@ -1540,13 +1528,6 @@ void method_exchangeImplementations(Method m1_gen, Method m2_gen) old_method *m2 = oldmethod(m2_gen); if (!m1 || !m2) return; - if (ignoreSelector(m1->method_name) || ignoreSelector(m2->method_name)) { - // Ignored methods stay ignored. Now they're both ignored. - m1->method_imp = (IMP)&_objc_ignored_method; - m2->method_imp = (IMP)&_objc_ignored_method; - return; - } - impLock.lock(); m1_imp = m1->method_imp; m1->method_imp = m2->method_imp; @@ -1621,11 +1602,7 @@ static IMP _class_addMethod(Class cls, SEL name, IMP imp, mlist->method_count = 1; mlist->method_list[0].method_name = name; mlist->method_list[0].method_types = strdup(types); - if (!ignoreSelector(name)) { - mlist->method_list[0].method_imp = imp; - } else { - mlist->method_list[0].method_imp = (IMP)&_objc_ignored_method; - } + mlist->method_list[0].method_imp = imp; _objc_insertMethods(cls, mlist, nil); if (!(cls->info & CLS_CONSTRUCTING)) { @@ -2020,12 +1997,7 @@ objc_property_t class_getProperty(Class cls, const char *name) while ((mlist = nextMethodList(cls, &iterator))) { int i; for (i = 0; i < mlist->method_count; i++) { - Method aMethod = (Method)&mlist->method_list[i]; - if (ignoreSelector(method_getName(aMethod))) { - count--; - continue; - } - result[m++] = aMethod; + result[m++] = (Method)&mlist->method_list[i]; } } result[m] = nil; @@ -2208,59 +2180,6 @@ void objc_registerClassPair(Class cls) mutex_locker_t lock(classLock); - // Build ivar layouts - if (UseGC) { - if (cls->ivar_layout != &UnsetLayout) { - // Class builder already called class_setIvarLayout. - } - else if (!cls->superclass) { - // Root class. Scan conservatively (should be isa ivar only). - cls->ivar_layout = nil; - } - else if (cls->ivars == nil) { - // No local ivars. Use superclass's layout. - cls->ivar_layout = - ustrdupMaybeNil(cls->superclass->ivar_layout); - } - else { - // Has local ivars. Build layout based on superclass. - Class supercls = cls->superclass; - const uint8_t *superlayout = - class_getIvarLayout(supercls); - layout_bitmap bitmap = - layout_bitmap_create(superlayout, supercls->instance_size, - cls->instance_size, NO); - int i; - for (i = 0; i < cls->ivars->ivar_count; i++) { - old_ivar *iv = &cls->ivars->ivar_list[i]; - layout_bitmap_set_ivar(bitmap, iv->ivar_type, iv->ivar_offset); - } - cls->ivar_layout = layout_string_create(bitmap); - layout_bitmap_free(bitmap); - } - - if (cls->ext->weak_ivar_layout != &UnsetLayout) { - // Class builder already called class_setWeakIvarLayout. - } - else if (!cls->superclass) { - // Root class. No weak ivars (should be isa ivar only) - cls->ext->weak_ivar_layout = nil; - } - else if (cls->ivars == nil) { - // No local ivars. Use superclass's layout. - const uint8_t *weak = - class_getWeakIvarLayout(cls->superclass); - cls->ext->weak_ivar_layout = ustrdupMaybeNil(weak); - } - else { - // Has local ivars. Build layout based on superclass. - // No way to add weak ivars yet. - const uint8_t *weak = - class_getWeakIvarLayout(cls->superclass); - cls->ext->weak_ivar_layout = ustrdupMaybeNil(weak); - } - } - // Clear "under construction" bit, set "done constructing" bit cls->info &= ~CLS_CONSTRUCTING; cls->ISA()->info &= ~CLS_CONSTRUCTING; @@ -2268,8 +2187,6 @@ void objc_registerClassPair(Class cls) cls->ISA()->info |= CLS_CONSTRUCTED; NXHashInsertIfAbsent(class_hash, cls); - objc_addRegisteredClass(cls); - //objc_addRegisteredClass(cls->ISA()); if we ever allocate classes from GC } @@ -2323,7 +2240,6 @@ Class objc_duplicateClass(Class original, const char *name, size_t extraBytes) mutex_locker_t lock(classLock); NXHashInsert(class_hash, duplicate); - objc_addRegisteredClass(duplicate); return duplicate; } @@ -2349,7 +2265,6 @@ void objc_disposeClassPair(Class cls) mutex_locker_t lock(classLock); NXHashRemove(class_hash, cls); - objc_removeRegisteredClass(cls); unload_class(cls->ISA()); unload_class(cls); } @@ -2402,12 +2317,6 @@ void objc_disposeClassPair(Class cls) // CF requires all objects be at least 16 bytes. if (size < 16) size = 16; -#if SUPPORT_GC - if (UseGC) { - bytes = auto_zone_allocate_object(gc_zone, size, - AUTO_OBJECT_SCANNED, 0, 1); - } else -#endif if (zone) { bytes = malloc_zone_calloc((malloc_zone_t *)zone, 1, size); } else { @@ -2440,11 +2349,9 @@ static id _object_copyFromZone(id oldObj, size_t extraBytes, void *zone) size = oldObj->ISA()->alignedInstanceSize() + extraBytes; // fixme need C++ copy constructor - objc_memmove_collectable(obj, oldObj, size); + memmove(obj, oldObj, size); -#if SUPPORT_GC - if (UseGC) gc_fixup_weakreferences(obj, oldObj); -#endif + fixupCopiedIvars(obj, oldObj); return obj; } @@ -2456,7 +2363,6 @@ static id _object_copyFromZone(id oldObj, size_t extraBytes, void *zone) * Calls C++ destructors. * Removes associative references. * Returns `obj`. Does nothing if `obj` is nil. -* Be warned that GC DOES NOT CALL THIS. If you edit this, also edit finalize. * CoreFoundation and other clients do call this under GC. **********************************************************************/ void *objc_destructInstance(id obj) @@ -2472,7 +2378,7 @@ static id _object_copyFromZone(id oldObj, size_t extraBytes, void *zone) _object_remove_assocations(obj); } - if (!UseGC) objc_clear_deallocating(obj); + objc_clear_deallocating(obj); } return obj; @@ -2485,15 +2391,8 @@ static id _object_copyFromZone(id oldObj, size_t extraBytes, void *zone) objc_destructInstance(anObject); -#if SUPPORT_GC - if (UseGC) { - auto_zone_retain(gc_zone, anObject); // gc free expects rc==1 - } else -#endif - { - // only clobber isa for non-gc - anObject->initIsa(_objc_getFreedObjectClass ()); - } + anObject->initIsa(_objc_getFreedObjectClass ()); + free(anObject); return nil; } @@ -2556,27 +2455,19 @@ static id _object_realloc(id anObject, size_t nBytes) id class_createInstance(Class cls, size_t extraBytes) { - if (UseGC) { - return _class_createInstance(cls, extraBytes); - } else { - return (*_alloc)(cls, extraBytes); - } + return (*_alloc)(cls, extraBytes); } id class_createInstanceFromZone(Class cls, size_t extraBytes, void *z) { OBJC_WARN_DEPRECATED; - if (UseGC) { - return _class_createInstanceFromZone(cls, extraBytes, z); - } else { - return (*_zoneAlloc)(cls, extraBytes, z); - } + return (*_zoneAlloc)(cls, extraBytes, z); } unsigned class_createInstances(Class cls, size_t extraBytes, id *results, unsigned num_requested) { - if (UseGC || _alloc == &_class_createInstance) { + if (_alloc == &_class_createInstance) { return _class_createInstancesFromZone(cls, extraBytes, nil, results, num_requested); } else { @@ -2587,35 +2478,30 @@ unsigned class_createInstances(Class cls, size_t extraBytes, id object_copy(id obj, size_t extraBytes) { - if (UseGC) return _object_copy(obj, extraBytes); - else return (*_copy)(obj, extraBytes); + return (*_copy)(obj, extraBytes); } id object_copyFromZone(id obj, size_t extraBytes, void *z) { OBJC_WARN_DEPRECATED; - if (UseGC) return _object_copyFromZone(obj, extraBytes, z); - else return (*_zoneCopy)(obj, extraBytes, z); + return (*_zoneCopy)(obj, extraBytes, z); } id object_dispose(id obj) { - if (UseGC) return _object_dispose(obj); - else return (*_dealloc)(obj); + return (*_dealloc)(obj); } id object_realloc(id obj, size_t nBytes) { OBJC_WARN_DEPRECATED; - if (UseGC) return _object_realloc(obj, nBytes); - else return (*_realloc)(obj, nBytes); + return (*_realloc)(obj, nBytes); } id object_reallocFromZone(id obj, size_t nBytes, void *z) { OBJC_WARN_DEPRECATED; - if (UseGC) return _object_reallocFromZone(obj, nBytes, z); - else return (*_zoneRealloc)(obj, nBytes, z); + return (*_zoneRealloc)(obj, nBytes, z); } diff --git a/runtime/objc-class.mm b/runtime/objc-class.mm index cbcdc20..d16ebc2 100644 --- a/runtime/objc-class.mm +++ b/runtime/objc-class.mm @@ -158,7 +158,6 @@ #include "objc-private.h" #include "objc-abi.h" -#include "objc-auto.h" #include @@ -256,127 +255,184 @@ IMP object_getMethodImplementation_stret(id obj, SEL name) #endif -Ivar object_setInstanceVariable(id obj, const char *name, void *value) -{ - Ivar ivar = nil; - - if (obj && name && !obj->isTaggedPointer()) { - if ((ivar = class_getInstanceVariable(obj->ISA(), name))) { - object_setIvar(obj, ivar, (id)value); - } - } - return ivar; -} - -Ivar object_getInstanceVariable(id obj, const char *name, void **value) +static bool isScanned(ptrdiff_t ivar_offset, const uint8_t *layout) { - if (obj && name && !obj->isTaggedPointer()) { - Ivar ivar; - if ((ivar = class_getInstanceVariable(obj->ISA(), name))) { - if (value) *value = (void *)object_getIvar(obj, ivar); - return ivar; - } - } - if (value) *value = nil; - return nil; -} + if (!layout) return NO; -static bool is_scanned_offset(ptrdiff_t ivar_offset, const uint8_t *layout) { ptrdiff_t index = 0, ivar_index = ivar_offset / sizeof(void*); uint8_t byte; while ((byte = *layout++)) { unsigned skips = (byte >> 4); unsigned scans = (byte & 0x0F); index += skips; - while (scans--) { - if (index == ivar_index) return YES; - if (index > ivar_index) return NO; - ++index; - } + if (index > ivar_index) return NO; + index += scans; + if (index > ivar_index) return YES; } return NO; } -// FIXME: this could be optimized. - -static Class _ivar_getClass(Class cls, Ivar ivar) { - Class ivar_class = nil; - const char *ivar_name = ivar_getName(ivar); - Ivar named_ivar = _class_getVariable(cls, ivar_name, &ivar_class); - if (named_ivar) { - // the same ivar name can appear multiple times along the superclass chain. - while (named_ivar != ivar && ivar_class != nil) { - ivar_class = ivar_class->superclass; - named_ivar = _class_getVariable(cls, ivar_getName(ivar), &ivar_class); + +/*********************************************************************** +* _class_lookUpIvar +* Given an object and an ivar in it, look up some data about that ivar: +* - its offset +* - its memory management behavior +* The ivar is assumed to be word-aligned and of of object type. +**********************************************************************/ +static void +_class_lookUpIvar(Class cls, Ivar ivar, ptrdiff_t& ivarOffset, + objc_ivar_memory_management_t& memoryManagement) +{ + ivarOffset = ivar_getOffset(ivar); + + // Look for ARC variables and ARC-style weak. + + // Preflight the hasAutomaticIvars check + // because _class_getClassForIvar() may need to take locks. + bool hasAutomaticIvars = NO; + for (Class c = cls; c; c = c->superclass) { + if (c->hasAutomaticIvars()) { + hasAutomaticIvars = YES; + break; } } - return ivar_class; -} -void object_setIvar(id obj, Ivar ivar, id value) -{ - if (obj && ivar && !obj->isTaggedPointer()) { - Class cls = _ivar_getClass(obj->ISA(), ivar); - ptrdiff_t ivar_offset = ivar_getOffset(ivar); - id *location = (id *)((char *)obj + ivar_offset); - // if this ivar is a member of an ARR compiled class, then issue the correct barrier according to the layout. - if (_class_usesAutomaticRetainRelease(cls)) { - // for ARR, layout strings are relative to the instance start. - uint32_t instanceStart = _class_getInstanceStart(cls); - const uint8_t *weak_layout = class_getWeakIvarLayout(cls); - if (weak_layout && is_scanned_offset(ivar_offset - instanceStart, weak_layout)) { - // use the weak system to write to this variable. - objc_storeWeak(location, value); + if (hasAutomaticIvars) { + Class ivarCls = _class_getClassForIvar(cls, ivar); + if (ivarCls->hasAutomaticIvars()) { + // ARC layout bitmaps encode the class's own ivars only. + // Use alignedInstanceStart() because unaligned bytes at the start + // of this class's ivars are not represented in the layout bitmap. + ptrdiff_t localOffset = + ivarOffset - ivarCls->alignedInstanceStart(); + + if (isScanned(localOffset, class_getIvarLayout(ivarCls))) { + memoryManagement = objc_ivar_memoryStrong; return; } - const uint8_t *strong_layout = class_getIvarLayout(cls); - if (strong_layout && is_scanned_offset(ivar_offset - instanceStart, strong_layout)) { - objc_storeStrong(location, value); + + if (isScanned(localOffset, class_getWeakIvarLayout(ivarCls))) { + memoryManagement = objc_ivar_memoryWeak; return; } - } -#if SUPPORT_GC - if (UseGC) { - // for GC, check for weak references. - const uint8_t *weak_layout = class_getWeakIvarLayout(cls); - if (weak_layout && is_scanned_offset(ivar_offset, weak_layout)) { - objc_assign_weak(value, location); + + // Unretained is only for true ARC classes. + if (ivarCls->isARC()) { + memoryManagement = objc_ivar_memoryUnretained; + return; } } - objc_assign_ivar(value, obj, ivar_offset); -#else - *location = value; -#endif } + + memoryManagement = objc_ivar_memoryUnknown; +} + + +/*********************************************************************** +* _class_getIvarMemoryManagement +* SPI for KVO and others to decide what memory management to use +* when setting instance variables directly. +**********************************************************************/ +objc_ivar_memory_management_t +_class_getIvarMemoryManagement(Class cls, Ivar ivar) +{ + ptrdiff_t offset; + objc_ivar_memory_management_t memoryManagement; + _class_lookUpIvar(cls, ivar, offset, memoryManagement); + return memoryManagement; +} + + +static ALWAYS_INLINE +void _object_setIvar(id obj, Ivar ivar, id value, bool assumeStrong) +{ + if (!obj || !ivar || obj->isTaggedPointer()) return; + + ptrdiff_t offset; + objc_ivar_memory_management_t memoryManagement; + _class_lookUpIvar(obj->ISA(), ivar, offset, memoryManagement); + + if (memoryManagement == objc_ivar_memoryUnknown) { + if (assumeStrong) memoryManagement = objc_ivar_memoryStrong; + else memoryManagement = objc_ivar_memoryUnretained; + } + + id *location = (id *)((char *)obj + offset); + + switch (memoryManagement) { + case objc_ivar_memoryWeak: objc_storeWeak(location, value); break; + case objc_ivar_memoryStrong: objc_storeStrong(location, value); break; + case objc_ivar_memoryUnretained: *location = value; break; + case objc_ivar_memoryUnknown: _objc_fatal("impossible"); + } +} + +void object_setIvar(id obj, Ivar ivar, id value) +{ + return _object_setIvar(obj, ivar, value, false /*not strong default*/); +} + +void object_setIvarWithStrongDefault(id obj, Ivar ivar, id value) +{ + return _object_setIvar(obj, ivar, value, true /*strong default*/); } id object_getIvar(id obj, Ivar ivar) { - if (obj && ivar && !obj->isTaggedPointer()) { - Class cls = obj->ISA(); - ptrdiff_t ivar_offset = ivar_getOffset(ivar); - if (_class_usesAutomaticRetainRelease(cls)) { - // for ARR, layout strings are relative to the instance start. - uint32_t instanceStart = _class_getInstanceStart(cls); - const uint8_t *weak_layout = class_getWeakIvarLayout(cls); - if (weak_layout && is_scanned_offset(ivar_offset - instanceStart, weak_layout)) { - // use the weak system to read this variable. - id *location = (id *)((char *)obj + ivar_offset); - return objc_loadWeak(location); - } + if (!obj || !ivar || obj->isTaggedPointer()) return nil; + + ptrdiff_t offset; + objc_ivar_memory_management_t memoryManagement; + _class_lookUpIvar(obj->ISA(), ivar, offset, memoryManagement); + + id *location = (id *)((char *)obj + offset); + + if (memoryManagement == objc_ivar_memoryWeak) { + return objc_loadWeak(location); + } else { + return *location; + } +} + + +static ALWAYS_INLINE +Ivar _object_setInstanceVariable(id obj, const char *name, void *value, + bool assumeStrong) +{ + Ivar ivar = nil; + + if (obj && name && !obj->isTaggedPointer()) { + if ((ivar = _class_getVariable(obj->ISA(), name))) { + _object_setIvar(obj, ivar, (id)value, assumeStrong); } - id *idx = (id *)((char *)obj + ivar_offset); -#if SUPPORT_GC - if (UseGC) { - const uint8_t *weak_layout = class_getWeakIvarLayout(cls); - if (weak_layout && is_scanned_offset(ivar_offset, weak_layout)) { - return objc_read_weak(idx); - } + } + return ivar; +} + +Ivar object_setInstanceVariable(id obj, const char *name, void *value) +{ + return _object_setInstanceVariable(obj, name, value, false); +} + +Ivar object_setInstanceVariableWithStrongDefault(id obj, const char *name, + void *value) +{ + return _object_setInstanceVariable(obj, name, value, true); +} + + +Ivar object_getInstanceVariable(id obj, const char *name, void **value) +{ + if (obj && name && !obj->isTaggedPointer()) { + Ivar ivar; + if ((ivar = class_getInstanceVariable(obj->ISA(), name))) { + if (value) *value = (void *)object_getIvar(obj, ivar); + return ivar; } -#endif - return *idx; } + if (value) *value = nil; return nil; } @@ -471,6 +527,55 @@ void object_cxxDestruct(id obj) } +/*********************************************************************** +* fixupCopiedIvars +* Fix up ARC strong and ARC-style weak variables +* after oldObject was memcpy'd to newObject. +**********************************************************************/ +void fixupCopiedIvars(id newObject, id oldObject) +{ + for (Class cls = oldObject->ISA(); cls; cls = cls->superclass) { + if (cls->hasAutomaticIvars()) { + // Use alignedInstanceStart() because unaligned bytes at the start + // of this class's ivars are not represented in the layout bitmap. + size_t instanceStart = cls->alignedInstanceStart(); + + const uint8_t *strongLayout = class_getIvarLayout(cls); + if (strongLayout) { + id *newPtr = (id *)((char*)newObject + instanceStart); + unsigned char byte; + while ((byte = *strongLayout++)) { + unsigned skips = (byte >> 4); + unsigned scans = (byte & 0x0F); + newPtr += skips; + while (scans--) { + // ensure strong references are properly retained. + id value = *newPtr++; + if (value) objc_retain(value); + } + } + } + + const uint8_t *weakLayout = class_getWeakIvarLayout(cls); + // fix up weak references if any. + if (weakLayout) { + id *newPtr = (id *)((char*)newObject + instanceStart), *oldPtr = (id *)((char*)oldObject + instanceStart); + unsigned char byte; + while ((byte = *weakLayout++)) { + unsigned skips = (byte >> 4); + unsigned weaks = (byte & 0x0F); + newPtr += skips, oldPtr += skips; + while (weaks--) { + objc_copyWeak(newPtr, oldPtr); + ++newPtr, ++oldPtr; + } + } + } + } + } +} + + /*********************************************************************** * _class_resolveClassMethod * Call +resolveClassMethod, looking for a method to be added to class cls. @@ -602,7 +707,7 @@ Ivar class_getInstanceVariable(Class cls, const char *name) { if (!cls || !name) return nil; - return _class_getVariable(cls, name, nil); + return _class_getVariable(cls, name); } @@ -721,6 +826,7 @@ void instrumentObjcMessageSends(BOOL flag) bool objcMsgLogEnabled = false; static int objcMsgLogFD = -1; +static spinlock_t objcMsgLogLock; bool logMessageSend(bool isClassMethod, const char *objectsClass, @@ -749,10 +855,9 @@ bool logMessageSend(bool isClassMethod, implementingClass, sel_getName(selector)); - static spinlock_t lock; - lock.lock(); + objcMsgLogLock.lock(); write (objcMsgLogFD, buf, strlen(buf)); - lock.unlock(); + objcMsgLogLock.unlock(); // Tell caller to not cache the method return false; @@ -783,9 +888,6 @@ void instrumentObjcMessageSends(BOOL flag) Class _calloc_class(size_t size) { -#if SUPPORT_GC - if (UseGC) return (Class) malloc_zone_calloc(gc_zone, 1, size); -#endif return (Class) calloc(1, size); } @@ -858,14 +960,7 @@ void method_getArgumentType(Method m, unsigned int index, assert(cls->hasCxxCtor()); // for performance, not correctness id obj = object_cxxConstructFromClass(bytes, cls); - if (!obj) { -#if SUPPORT_GC - if (UseGC) { - auto_zone_retain(gc_zone, bytes); // gc free expects rc==1 - } -#endif - free(bytes); - } + if (!obj) free(bytes); return obj; } @@ -887,31 +982,20 @@ void method_getArgumentType(Method m, unsigned int index, size_t size = cls->instanceSize(extraBytes); -#if SUPPORT_GC - if (UseGC) { - num_allocated = - auto_zone_batch_allocate(gc_zone, size, AUTO_OBJECT_SCANNED, 0, 1, - (void**)results, num_requested); - } else -#endif - { - unsigned i; - num_allocated = - malloc_zone_batch_malloc((malloc_zone_t *)(zone ? zone : malloc_default_zone()), - size, (void**)results, num_requested); - for (i = 0; i < num_allocated; i++) { - bzero(results[i], size); - } + num_allocated = + malloc_zone_batch_malloc((malloc_zone_t *)(zone ? zone : malloc_default_zone()), + size, (void**)results, num_requested); + for (unsigned i = 0; i < num_allocated; i++) { + bzero(results[i], size); } // Construct each object, and delete any that fail construction. unsigned shift = 0; - unsigned i; bool ctor = cls->hasCxxCtor(); - for (i = 0; i < num_allocated; i++) { + for (unsigned i = 0; i < num_allocated; i++) { id obj = results[i]; - obj->initIsa(cls); // fixme allow indexed + obj->initIsa(cls); // fixme allow nonpointer if (ctor) obj = _objc_constructOrFree(obj, cls); if (obj) { @@ -929,21 +1013,21 @@ void method_getArgumentType(Method m, unsigned int index, * inform_duplicate. Complain about duplicate class implementations. **********************************************************************/ void -inform_duplicate(const char *name, Class oldCls, Class cls) +inform_duplicate(const char *name, Class oldCls, Class newCls) { #if TARGET_OS_WIN32 (DebugDuplicateClasses ? _objc_fatal : _objc_inform) ("Class %s is implemented in two different images.", name); #else const header_info *oldHeader = _headerForClass(oldCls); - const header_info *newHeader = _headerForClass(cls); - const char *oldName = oldHeader ? oldHeader->fname : "??"; - const char *newName = newHeader ? newHeader->fname : "??"; + const header_info *newHeader = _headerForClass(newCls); + const char *oldName = oldHeader ? oldHeader->fname() : "??"; + const char *newName = newHeader ? newHeader->fname() : "??"; (DebugDuplicateClasses ? _objc_fatal : _objc_inform) - ("Class %s is implemented in both %s and %s. " + ("Class %s is implemented in both %s (%p) and %s (%p). " "One of the two will be used. Which one is undefined.", - name, oldName, newName); + name, oldName, oldCls, newName, newCls); #endif } diff --git a/runtime/objc-config.h b/runtime/objc-config.h index 4bf952e..979b467 100644 --- a/runtime/objc-config.h +++ b/runtime/objc-config.h @@ -33,16 +33,16 @@ # define DEBUG 0 #endif -// Define SUPPORT_GC=1 to enable garbage collection. -// Be sure to edit OBJC_NO_GC and OBJC_NO_GC_API in objc-api.h as well. -#if TARGET_OS_EMBEDDED || TARGET_OS_IPHONE || TARGET_OS_WIN32 || (TARGET_OS_MAC && __x86_64h__) -# define SUPPORT_GC 0 +// Define SUPPORT_GC_COMPAT=1 to enable compatibility where GC once was. +// OBJC_NO_GC and OBJC_NO_GC_API in objc-api.h mean something else. +#if !TARGET_OS_OSX +# define SUPPORT_GC_COMPAT 0 #else -# define SUPPORT_GC 1 +# define SUPPORT_GC_COMPAT 1 #endif // Define SUPPORT_ZONES=1 to enable malloc zone support in NXHashTable. -#if TARGET_OS_EMBEDDED || TARGET_OS_IPHONE +#if !TARGET_OS_OSX # define SUPPORT_ZONES 0 #else # define SUPPORT_ZONES 1 @@ -56,7 +56,7 @@ #endif // Define SUPPORT_PREOPT=1 to enable dyld shared cache optimizations -#if TARGET_OS_WIN32 || TARGET_IPHONE_SIMULATOR +#if TARGET_OS_WIN32 || TARGET_OS_SIMULATOR # define SUPPORT_PREOPT 0 #else # define SUPPORT_PREOPT 1 @@ -79,8 +79,27 @@ # define SUPPORT_MSB_TAGGED_POINTERS 1 #endif -// Define SUPPORT_NONPOINTER_ISA=1 to enable extra data in the isa field. -#if !__LP64__ || TARGET_OS_WIN32 || TARGET_IPHONE_SIMULATOR +// Define SUPPORT_INDEXED_ISA=1 on platforms that store the class in the isa +// field as an index into a class table. +// Note, keep this in sync with any .s files which also define it. +// Be sure to edit objc-abi.h as well. +#if __ARM_ARCH_7K__ >= 2 +# define SUPPORT_INDEXED_ISA 1 +#else +# define SUPPORT_INDEXED_ISA 0 +#endif + +// Define SUPPORT_PACKED_ISA=1 on platforms that store the class in the isa +// field as a maskable pointer with other data around it. +#if (!__LP64__ || TARGET_OS_WIN32 || TARGET_OS_SIMULATOR) +# define SUPPORT_PACKED_ISA 0 +#else +# define SUPPORT_PACKED_ISA 1 +#endif + +// Define SUPPORT_NONPOINTER_ISA=1 on any platform that may store something +// in the isa field that is not a raw pointer. +#if !SUPPORT_INDEXED_ISA && !SUPPORT_PACKED_ISA # define SUPPORT_NONPOINTER_ISA 0 #else # define SUPPORT_NONPOINTER_ISA 1 @@ -89,28 +108,12 @@ // Define SUPPORT_FIXUP=1 to repair calls sites for fixup dispatch. // Fixup messaging itself is no longer supported. // Be sure to edit objc-abi.h as well (objc_msgSend*_fixup) -// Note TARGET_OS_MAC is also set for iOS simulator. -#if !__x86_64__ || !TARGET_OS_MAC +#if !(defined(__x86_64__) && (TARGET_OS_OSX || TARGET_OS_SIMULATOR)) # define SUPPORT_FIXUP 0 #else # define SUPPORT_FIXUP 1 #endif -// Define SUPPORT_IGNORED_SELECTOR_CONSTANT to remap GC-ignored selectors. -// Good: fast ignore in objc_msgSend. Bad: disable shared cache optimizations -// Now used only for old-ABI GC. -// This is required for binary compatibility on 32-bit Mac: rdar://13757938 -#if __OBJC2__ || !SUPPORT_GC -# define SUPPORT_IGNORED_SELECTOR_CONSTANT 0 -#else -# define SUPPORT_IGNORED_SELECTOR_CONSTANT 1 -# if defined(__i386__) -# define kIgnore 0xfffeb010 -# else -# error unknown architecture -# endif -#endif - // Define SUPPORT_ZEROCOST_EXCEPTIONS to use "zero-cost" exceptions for OBJC2. // Be sure to edit objc-exception.h as well (objc_add/removeExceptionHandler) #if !__OBJC2__ || (defined(__arm__) && __USING_SJLJ_EXCEPTIONS__) @@ -129,7 +132,7 @@ #endif // Define SUPPORT_RETURN_AUTORELEASE to optimize autoreleased return values -#if !__OBJC2__ || TARGET_OS_WIN32 +#if TARGET_OS_WIN32 # define SUPPORT_RETURN_AUTORELEASE 0 #else # define SUPPORT_RETURN_AUTORELEASE 1 diff --git a/runtime/objc-env.h b/runtime/objc-env.h index a9a18ad..7eb0afa 100644 --- a/runtime/objc-env.h +++ b/runtime/objc-env.h @@ -14,7 +14,6 @@ OPTION( PrintVtables, OBJC_PRINT_VTABLE_SETUP, "log processi OPTION( PrintVtableImages, OBJC_PRINT_VTABLE_IMAGES, "print vtable images showing overridden methods") OPTION( PrintCaches, OBJC_PRINT_CACHE_SETUP, "log processing of method caches") OPTION( PrintFuture, OBJC_PRINT_FUTURE_CLASSES, "log use of future classes for toll-free bridging") -OPTION( PrintGC, OBJC_PRINT_GC, "log some GC operations") OPTION( PrintPreopt, OBJC_PRINT_PREOPTIMIZATION, "log preoptimization courtesy of dyld shared cache") OPTION( PrintCxxCtors, OBJC_PRINT_CXX_CTORS, "log calls to C++ ctors and dtors for instance variables") OPTION( PrintExceptions, OBJC_PRINT_EXCEPTIONS, "log exception handling") @@ -29,16 +28,15 @@ OPTION( PrintRawIsa, OBJC_PRINT_RAW_ISA, "log classes OPTION( DebugUnload, OBJC_DEBUG_UNLOAD, "warn about poorly-behaving bundles when unloaded") OPTION( DebugFragileSuperclasses, OBJC_DEBUG_FRAGILE_SUPERCLASSES, "warn about subclasses that may have been broken by subsequent changes to superclasses") -OPTION( DebugFinalizers, OBJC_DEBUG_FINALIZERS, "warn about classes that implement -dealloc but not -finalize") OPTION( DebugNilSync, OBJC_DEBUG_NIL_SYNC, "warn about @synchronized(nil), which does no synchronization") OPTION( DebugNonFragileIvars, OBJC_DEBUG_NONFRAGILE_IVARS, "capriciously rearrange non-fragile ivars") OPTION( DebugAltHandlers, OBJC_DEBUG_ALT_HANDLERS, "record more info about bad alt handler use") OPTION( DebugMissingPools, OBJC_DEBUG_MISSING_POOLS, "warn about autorelease with no pool in place, which may be a leak") OPTION( DebugPoolAllocation, OBJC_DEBUG_POOL_ALLOCATION, "halt when autorelease pools are popped out of order, and allow heap debuggers to track autorelease pools") OPTION( DebugDuplicateClasses, OBJC_DEBUG_DUPLICATE_CLASSES, "halt when multiple classes with the same name are present") +OPTION( DebugDontCrash, OBJC_DEBUG_DONT_CRASH, "halt the process by exiting instead of crashing") -OPTION( DisableGC, OBJC_DISABLE_GC, "force GC OFF, even if the executable wants it on") OPTION( DisableVtables, OBJC_DISABLE_VTABLES, "disable vtable dispatch") OPTION( DisablePreopt, OBJC_DISABLE_PREOPTIMIZATION, "disable preoptimization courtesy of dyld shared cache") OPTION( DisableTaggedPointers, OBJC_DISABLE_TAGGED_POINTERS, "disable tagged pointer optimization of NSNumber et al.") -OPTION( DisableIndexedIsa, OBJC_DISABLE_NONPOINTER_ISA, "disable non-pointer isa fields") +OPTION( DisableNonpointerIsa, OBJC_DISABLE_NONPOINTER_ISA, "disable non-pointer isa fields") diff --git a/runtime/objc-errors.mm b/runtime/objc-errors.mm index 650fcd2..4c426b0 100644 --- a/runtime/objc-errors.mm +++ b/runtime/objc-errors.mm @@ -74,13 +74,10 @@ void _objc_error(id rcv, const char *fmt, va_list args) #else -#include #include <_simple.h> OBJC_EXPORT void (*_error)(id, const char *, va_list); -static void _objc_trap(void) __attribute__((noreturn)); - // Return true if c is a UTF8 continuation byte static bool isUTF8Continuation(char c) { @@ -88,6 +85,7 @@ static bool isUTF8Continuation(char c) } // Add "message" to any forthcoming crash log. +static mutex_t crashlog_lock; static void _objc_crashlog(const char *message) { char *newmsg; @@ -104,7 +102,6 @@ static void _objc_crashlog(const char *message) } #endif - static mutex_t crashlog_lock; mutex_locker_t lock(crashlog_lock); char *oldmsg = (char *)CRGetCrashLogMessage(); @@ -160,23 +157,15 @@ static void _objc_syslog(const char *message) /* * _objc_error is the default *_error handler. */ -#if __OBJC2__ -__attribute__((noreturn)) -#else +#if !__OBJC2__ // used by ExceptionHandling.framework #endif +__attribute__((noreturn)) void _objc_error(id self, const char *fmt, va_list ap) { - char *buf1; - char *buf2; - - vasprintf(&buf1, fmt, ap); - asprintf(&buf2, "objc[%d]: %s: %s\n", - getpid(), object_getClassName(self), buf1); - _objc_syslog(buf2); - _objc_crashlog(buf2); - - _objc_trap(); + char *buf; + vasprintf(&buf, fmt, ap); + _objc_fatal("%s: %s", object_getClassName(self), buf); } /* @@ -194,25 +183,42 @@ void __objc_error(id rcv, const char *fmt, ...) va_end(vp); } -/* - * this routine handles severe runtime errors...like not being able - * to read the mach headers, allocate space, etc...very uncommon. - */ -void _objc_fatal(const char *fmt, ...) +static __attribute__((noreturn)) +void _objc_fatalv(uint64_t reason, uint64_t flags, const char *fmt, va_list ap) { - va_list ap; char *buf1; - char *buf2; - - va_start(ap,fmt); vasprintf(&buf1, fmt, ap); - va_end (ap); + char *buf2; asprintf(&buf2, "objc[%d]: %s\n", getpid(), buf1); _objc_syslog(buf2); - _objc_crashlog(buf2); - _objc_trap(); + if (DebugDontCrash) { + char *buf3; + asprintf(&buf3, "objc[%d]: HALTED\n", getpid()); + _objc_syslog(buf3); + _Exit(1); + } + else { + abort_with_reason(OS_REASON_OBJC, reason, buf1, flags); + } +} + +void _objc_fatal_with_reason(uint64_t reason, uint64_t flags, + const char *fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + _objc_fatalv(reason, flags, fmt, ap); +} + +void _objc_fatal(const char *fmt, ...) +{ + va_list ap; + va_start(ap,fmt); + _objc_fatalv(OBJC_EXIT_REASON_UNSPECIFIED, + OS_REASON_FLAG_ONE_TIME_FAILURE, + fmt, ap); } /* @@ -280,22 +286,6 @@ void _objc_inform_now_and_on_crash(const char *fmt, ...) free(buf1); } - -/* Kill the process in a way that generates a crash log. - * This is better than calling exit(). */ -static void _objc_trap(void) -{ - __builtin_trap(); -} - -/* Try to keep _objc_warn_deprecated out of crash logs - * caused by _objc_trap(). rdar://4546883 */ -__attribute__((used)) -static void _objc_trap2(void) -{ - __builtin_trap(); -} - #endif diff --git a/runtime/objc-exception.h b/runtime/objc-exception.h index 40668af..c67b92a 100644 --- a/runtime/objc-exception.h +++ b/runtime/objc-exception.h @@ -32,15 +32,20 @@ // compiler reserves a setjmp buffer + 4 words as localExceptionData OBJC_EXPORT void objc_exception_throw(id exception) - __OSX_AVAILABLE_STARTING(__MAC_10_3, __IPHONE_NA); + __OSX_AVAILABLE(10.3) + __IOS_UNAVAILABLE __TVOS_UNAVAILABLE __WATCHOS_UNAVAILABLE; OBJC_EXPORT void objc_exception_try_enter(void *localExceptionData) - __OSX_AVAILABLE_STARTING(__MAC_10_3, __IPHONE_NA); + __OSX_AVAILABLE(10.3) + __IOS_UNAVAILABLE __TVOS_UNAVAILABLE __WATCHOS_UNAVAILABLE; OBJC_EXPORT void objc_exception_try_exit(void *localExceptionData) - __OSX_AVAILABLE_STARTING(__MAC_10_3, __IPHONE_NA); + __OSX_AVAILABLE(10.3) + __IOS_UNAVAILABLE __TVOS_UNAVAILABLE __WATCHOS_UNAVAILABLE; OBJC_EXPORT id objc_exception_extract(void *localExceptionData) - __OSX_AVAILABLE_STARTING(__MAC_10_3, __IPHONE_NA); + __OSX_AVAILABLE(10.3) + __IOS_UNAVAILABLE __TVOS_UNAVAILABLE __WATCHOS_UNAVAILABLE; OBJC_EXPORT int objc_exception_match(Class exceptionClass, id exception) - __OSX_AVAILABLE_STARTING(__MAC_10_3, __IPHONE_NA); + __OSX_AVAILABLE(10.3) + __IOS_UNAVAILABLE __TVOS_UNAVAILABLE __WATCHOS_UNAVAILABLE; typedef struct { @@ -54,11 +59,13 @@ typedef struct { // get table; version tells how many OBJC_EXPORT void objc_exception_get_functions(objc_exception_functions_t *table) - __OSX_AVAILABLE_STARTING(__MAC_10_3, __IPHONE_NA); + __OSX_AVAILABLE(10.3) + __IOS_UNAVAILABLE __TVOS_UNAVAILABLE __WATCHOS_UNAVAILABLE; // set table OBJC_EXPORT void objc_exception_set_functions(objc_exception_functions_t *table) - __OSX_AVAILABLE_STARTING(__MAC_10_3, __IPHONE_NA); + __OSX_AVAILABLE(10.3) + __IOS_UNAVAILABLE __TVOS_UNAVAILABLE __WATCHOS_UNAVAILABLE; // !__OBJC2__ @@ -77,28 +84,30 @@ typedef void (*objc_exception_handler)(id unused, void *context); * @param exception The exception to be thrown. */ OBJC_EXPORT void objc_exception_throw(id exception) - __OSX_AVAILABLE_STARTING(__MAC_10_5, __IPHONE_2_0); + OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0); OBJC_EXPORT void objc_exception_rethrow(void) - __OSX_AVAILABLE_STARTING(__MAC_10_5, __IPHONE_2_0); + OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0); OBJC_EXPORT id objc_begin_catch(void *exc_buf) - __OSX_AVAILABLE_STARTING(__MAC_10_5, __IPHONE_2_0); + OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0); OBJC_EXPORT void objc_end_catch(void) - __OSX_AVAILABLE_STARTING(__MAC_10_5, __IPHONE_2_0); + OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0); OBJC_EXPORT void objc_terminate(void) - __OSX_AVAILABLE_STARTING(__MAC_10_8, __IPHONE_6_0); + OBJC_AVAILABLE(10.8, 6.0, 9.0, 1.0); OBJC_EXPORT objc_exception_preprocessor objc_setExceptionPreprocessor(objc_exception_preprocessor fn) - __OSX_AVAILABLE_STARTING(__MAC_10_5, __IPHONE_2_0); + OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0); OBJC_EXPORT objc_exception_matcher objc_setExceptionMatcher(objc_exception_matcher fn) - __OSX_AVAILABLE_STARTING(__MAC_10_5, __IPHONE_2_0); + OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0); OBJC_EXPORT objc_uncaught_exception_handler objc_setUncaughtExceptionHandler(objc_uncaught_exception_handler fn) - __OSX_AVAILABLE_STARTING(__MAC_10_5, __IPHONE_2_0); + OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0); // Not for iOS. OBJC_EXPORT uintptr_t objc_addExceptionHandler(objc_exception_handler fn, void *context) - __OSX_AVAILABLE_STARTING(__MAC_10_5, __IPHONE_NA); + __OSX_AVAILABLE(10.5) + __IOS_UNAVAILABLE __TVOS_UNAVAILABLE __WATCHOS_UNAVAILABLE; OBJC_EXPORT void objc_removeExceptionHandler(uintptr_t token) - __OSX_AVAILABLE_STARTING(__MAC_10_5, __IPHONE_NA); + __OSX_AVAILABLE(10.5) + __IOS_UNAVAILABLE __TVOS_UNAVAILABLE __WATCHOS_UNAVAILABLE; // __OBJC2__ #endif diff --git a/runtime/objc-exception.mm b/runtime/objc-exception.mm index e9dbf2c..d6b1d83 100644 --- a/runtime/objc-exception.mm +++ b/runtime/objc-exception.mm @@ -509,17 +509,7 @@ static void _objc_exception_destructor(void *exc_gen) exc, obj, object_getClassName(obj)); } -#if SUPPORT_GC - if (UseGC) { - if (auto_zone_is_valid_pointer(gc_zone, obj)) { - auto_zone_release(gc_zone, exc->obj); - } - } - else -#endif - { - [obj release]; - } + [obj release]; } @@ -530,20 +520,9 @@ void objc_exception_throw(id obj) obj = (*exception_preprocessor)(obj); - // Retain the exception object during unwinding. - // GC: because `exc` is unscanned memory - // Non-GC: because otherwise an autorelease pool pop can cause a crash -#if SUPPORT_GC - if (UseGC) { - if (auto_zone_is_valid_pointer(gc_zone, obj)) { - auto_zone_retain(gc_zone, obj); - } - } - else -#endif - { - [obj retain]; - } + // Retain the exception object during unwinding + // because otherwise an autorelease pool pop can cause a crash + [obj retain]; exc->obj = obj; exc->tinfo.vtable = objc_ehtype_vtable+2; @@ -1084,7 +1063,8 @@ static struct frame_range findHandler(void) static struct alt_handler_list *DebugLists; static uintptr_t DebugCounter; -void alt_handler_error(uintptr_t token) __attribute__((noinline)); +__attribute__((noinline, noreturn)) +void alt_handler_error(uintptr_t token); static struct alt_handler_list * fetch_handler_list(bool create) @@ -1243,7 +1223,6 @@ void objc_removeExceptionHandler(uintptr_t token) if (!list || !list->handlers) { // no alt handlers active alt_handler_error(token); - __builtin_trap(); } uintptr_t i = token-1; @@ -1259,7 +1238,6 @@ void objc_removeExceptionHandler(uintptr_t token) if (i >= list->allocated) { // token out of range alt_handler_error(token); - __builtin_trap(); } struct alt_handler_data *data = &list->handlers[i]; @@ -1267,7 +1245,6 @@ void objc_removeExceptionHandler(uintptr_t token) if (data->frame.ip_start == 0 && data->frame.ip_end == 0 && data->frame.cfa == 0) { // token in range, but invalid alt_handler_error(token); - __builtin_trap(); } if (PrintAltHandlers) { @@ -1283,77 +1260,69 @@ void objc_removeExceptionHandler(uintptr_t token) list->used--; } -void objc_alt_handler_error(void) __attribute__((noinline)); +BREAKPOINT_FUNCTION( +void objc_alt_handler_error(void)); + +__attribute__((noinline, noreturn)) void alt_handler_error(uintptr_t token) { - if (!DebugAltHandlers) { - _objc_inform_now_and_on_crash - ("objc_removeExceptionHandler() called with unknown alt handler; " - "this is probably a bug in multithreaded AppKit use. " - "Set environment variable OBJC_DEBUG_ALT_HANDLERS=YES " - "or break in objc_alt_handler_error() to debug."); - objc_alt_handler_error(); - } + _objc_inform + ("objc_removeExceptionHandler() called with unknown alt handler; " + "this is probably a bug in multithreaded AppKit use. " + "Set environment variable OBJC_DEBUG_ALT_HANDLERS=YES " + "or break in objc_alt_handler_error() to debug."); - DebugLock.lock(); - - // Search other threads' alt handler lists for this handler. - struct alt_handler_list *list; - for (list = DebugLists; list; list = list->next_DEBUGONLY) { - unsigned h; - for (h = 0; h < list->allocated; h++) { - struct alt_handler_data *data = &list->handlers[h]; - if (data->debug && data->debug->token == token) { - // found it - int i; - - // Build a string from the recorded backtrace - char *symbolString; - char **symbols = - backtrace_symbols(data->debug->backtrace, - data->debug->backtraceSize); - size_t len = 1; - for (i = 0; i < data->debug->backtraceSize; i++){ - len += 4 + strlen(symbols[i]) + 1; - } - symbolString = (char *)calloc(len, 1); - for (i = 0; i < data->debug->backtraceSize; i++){ - strcat(symbolString, " "); - strcat(symbolString, symbols[i]); - strcat(symbolString, "\n"); + if (DebugAltHandlers) { + DebugLock.lock(); + + // Search other threads' alt handler lists for this handler. + struct alt_handler_list *list; + for (list = DebugLists; list; list = list->next_DEBUGONLY) { + unsigned h; + for (h = 0; h < list->allocated; h++) { + struct alt_handler_data *data = &list->handlers[h]; + if (data->debug && data->debug->token == token) { + // found it + int i; + + // Build a string from the recorded backtrace + char *symbolString; + char **symbols = + backtrace_symbols(data->debug->backtrace, + data->debug->backtraceSize); + size_t len = 1; + for (i = 0; i < data->debug->backtraceSize; i++){ + len += 4 + strlen(symbols[i]) + 1; + } + symbolString = (char *)calloc(len, 1); + for (i = 0; i < data->debug->backtraceSize; i++){ + strcat(symbolString, " "); + strcat(symbolString, symbols[i]); + strcat(symbolString, "\n"); + } + + free(symbols); + + _objc_inform_now_and_on_crash + ("The matching objc_addExceptionHandler() was called " + "by:\nThread '%s': Dispatch queue: '%s': \n%s", + data->debug->thread, data->debug->queue, symbolString); + + goto done; } - - free(symbols); - - _objc_inform_now_and_on_crash - ("objc_removeExceptionHandler() called with " - "unknown alt handler; this is probably a bug in " - "multithreaded AppKit use. \n" - "The matching objc_addExceptionHandler() was called by:\n" - "Thread '%s': Dispatch queue: '%s': \n%s", - data->debug->thread, data->debug->queue, symbolString); - - DebugLock.unlock(); - free(symbolString); - - objc_alt_handler_error(); } } + done: + DebugLock.unlock(); } - DebugLock.unlock(); - // not found - _objc_inform_now_and_on_crash - ("objc_removeExceptionHandler() called with unknown alt handler; " - "this is probably a bug in multithreaded AppKit use"); objc_alt_handler_error(); -} - -void objc_alt_handler_error(void) -{ - __builtin_trap(); + + _objc_fatal + ("objc_removeExceptionHandler() called with unknown alt handler; " + "this is probably a bug in multithreaded AppKit use. "); } // called in order registered, to match 32-bit _NSAddAltHandler2 diff --git a/runtime/objc-externalref.mm b/runtime/objc-externalref.mm deleted file mode 100644 index 8511fe9..0000000 --- a/runtime/objc-externalref.mm +++ /dev/null @@ -1,283 +0,0 @@ -/* - * Copyright (c) 2010 Apple Inc. All Rights Reserved. - * - * @APPLE_LICENSE_HEADER_START@ - * - * This file contains Original Code and/or Modifications of Original Code - * as defined in and that are subject to the Apple Public Source License - * Version 2.0 (the 'License'). You may not use this file except in - * compliance with the License. Please obtain a copy of the License at - * http://www.opensource.apple.com/apsl/ and read it before using this - * file. - * - * The Original Code and all software distributed under the License are - * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER - * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, - * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. - * Please see the License for the specific language governing rights and - * limitations under the License. - * - * @APPLE_LICENSE_HEADER_END@ - */ - -#include "objc-private.h" - -#include -#include -#include "runtime.h" -#include "objc-os.h" -#include "message.h" -#if SUPPORT_GC -#include "auto_zone.h" -#endif - -enum { - // external references to data segment objects all use this type - OBJC_XREF_TYPE_STATIC = 3, - - OBJC_XREF_TYPE_MASK = 3 -}; - -// Macros to encode/decode reference values and types. -#define encode_pointer_and_type(pointer, type) (~((uintptr_t)(pointer) | type)) -#define decode_pointer(encoded) ((id)((~(encoded)) & (~OBJC_XREF_TYPE_MASK))) -#define decode_type(encoded) ((~(encoded)) & OBJC_XREF_TYPE_MASK) -#define encode_index_and_type(index, type) (~((index<<3) | type)) -#define decode_index(encoded) ((~encoded)>>3) - -#if SUPPORT_GC - -typedef struct { - objc_xref_type_t _type; // type of list. - dispatch_queue_t _synchronizer; // a reader/write lock - __strong void **_buffer; // a retained all pointers block - size_t _size; // number of pointers that fit in _list (buffer size) - size_t _count; // count of pointers in _list (in use count) - size_t _search; // lowest index in list which *might* be unused -} external_ref_list; - -static external_ref_list _xref_lists[2]; - -#define is_strong(list) (list->_type == OBJC_XREF_STRONG) -#define is_weak(list) (list->_type == OBJC_XREF_WEAK) - -inline static size_t _index_for_type(objc_xref_type_t ref_type) { - assert(ref_type == OBJC_XREF_STRONG || ref_type == OBJC_XREF_WEAK); - return (ref_type - 1); -} - -static void _initialize_gc() { - static dispatch_once_t init_guard; - dispatch_once(&init_guard, ^{ - external_ref_list *_strong_list = &_xref_lists[_index_for_type(OBJC_XREF_STRONG)]; - _strong_list->_type = OBJC_XREF_STRONG; - _strong_list->_synchronizer = dispatch_queue_create("OBJC_XREF_STRONG synchronizer", DISPATCH_QUEUE_CONCURRENT); - - external_ref_list *_weak_list = &_xref_lists[_index_for_type(OBJC_XREF_WEAK)]; - _weak_list->_type = OBJC_XREF_WEAK; - _weak_list->_synchronizer = dispatch_queue_create("OBJC_XREF_WEAK synchronizer", DISPATCH_QUEUE_CONCURRENT); - }); -} - -#define EMPTY_SLOT ((void*)0x1) - -// grow the buffer by one page -static bool _grow_list(external_ref_list *list) { - auto_memory_type_t memory_type = (is_strong(list) ? AUTO_MEMORY_ALL_POINTERS : AUTO_MEMORY_ALL_WEAK_POINTERS); - size_t new_size = list->_size + PAGE_MAX_SIZE / sizeof(void *); - // auto_realloc() has been enhanced to handle strong and weak memory. - void **new_list = (void **)(list->_buffer ? malloc_zone_realloc(gc_zone, list->_buffer, new_size * sizeof(void *)) : auto_zone_allocate_object(gc_zone, new_size * sizeof(void *), memory_type, false, false)); - if (!new_list) _objc_fatal("unable to allocate, size = %ld\n", new_size); - - list->_search = list->_size; - // Fill the newly allocated space with empty slot tokens. - for (size_t index = list->_size; index < new_size; ++index) - new_list[index] = EMPTY_SLOT; - list->_size = new_size; - auto_zone_root_write_barrier(gc_zone, &list->_buffer, new_list); - return true; -} - - -// find an unused slot in the list, growing the list if necessary -static size_t _find_unused_index(external_ref_list *list) { - size_t index; - if (list->_size == list->_count) { - _grow_list(list); - } - // find the lowest unused index in _list - index = list->_search; - while (list->_buffer[index] != EMPTY_SLOT) - index++; - // mark the slot as no longer empty, good form for weak slots. - list->_buffer[index] = NULL; - return index; -} - - -// return the strong or weak list -inline static external_ref_list *_list_for_type(objc_xref_type_t ref_type) { - return &_xref_lists[_index_for_type(ref_type)]; -} - - -// create a GC external reference -objc_xref_t _object_addExternalReference_gc(id obj, objc_xref_type_t ref_type) { - _initialize_gc(); - __block size_t index; - objc_xref_t xref; - - if (auto_zone_is_valid_pointer(gc_zone, obj)) { - external_ref_list *list = _list_for_type(ref_type); - - // writer lock - dispatch_barrier_sync(list->_synchronizer, (dispatch_block_t)^{ - index = _find_unused_index(list); - if (ref_type == OBJC_XREF_STRONG) { - auto_zone_set_write_barrier(gc_zone, &list->_buffer[index], obj); - } else { - auto_assign_weak_reference(gc_zone, obj, (const void **)&list->_buffer[index], NULL); - } - list->_count++; - }); - xref = encode_index_and_type(index, ref_type); - } else { - // data segment object - xref = encode_pointer_and_type(obj, OBJC_XREF_TYPE_STATIC); - } - return xref; -} - - -id _object_readExternalReference_gc(objc_xref_t ref) { - _initialize_gc(); - __block id result; - objc_xref_type_t ref_type = decode_type(ref); - if (ref_type != OBJC_XREF_TYPE_STATIC) { - size_t index = decode_index(ref); - external_ref_list *list = _list_for_type(ref_type); - - dispatch_sync(list->_synchronizer, ^{ - if (index >= list->_size) { - _objc_fatal("attempted to resolve invalid external reference\n"); - } - if (ref_type == OBJC_XREF_STRONG) - result = (id)list->_buffer[index]; - else - result = (id)auto_read_weak_reference(gc_zone, &list->_buffer[index]); - if (result == (id)EMPTY_SLOT) - _objc_fatal("attempted to resolve unallocated external reference\n"); - }); - } else { - // data segment object - result = decode_pointer(ref); - } - return result; -} - - -void _object_removeExternalReference_gc(objc_xref_t ref) { - _initialize_gc(); - objc_xref_type_t ref_type = decode_type(ref); - if (ref_type != OBJC_XREF_TYPE_STATIC) { - size_t index = decode_index(ref); - external_ref_list *list = _list_for_type(ref_type); - - dispatch_barrier_sync(list->_synchronizer, ^{ - if (index >= list->_size) { - _objc_fatal("attempted to destroy invalid external reference\n"); - } - id old_value; - if (ref_type == OBJC_XREF_STRONG) { - old_value = (id)list->_buffer[index]; - } else { - old_value = (id)auto_read_weak_reference(gc_zone, &list->_buffer[index]); - auto_assign_weak_reference(gc_zone, NULL, (const void **)&list->_buffer[index], NULL); - } - list->_buffer[index] = EMPTY_SLOT; - if (old_value == (id)EMPTY_SLOT) - _objc_fatal("attempted to destroy unallocated external reference\n"); - list->_count--; - if (list->_search > index) - list->_search = index; - }); - } else { - // nothing for data segment object - } -} - - -// SUPPORT_GC -#endif - - -objc_xref_t _object_addExternalReference_non_gc(id obj, objc_xref_type_t ref_type) { - switch (ref_type) { - case OBJC_XREF_STRONG: - ((id(*)(id, SEL))objc_msgSend)(obj, SEL_retain); - break; - case OBJC_XREF_WEAK: - break; - default: - _objc_fatal("invalid external reference type: %d", (int)ref_type); - break; - } - return encode_pointer_and_type(obj, ref_type); -} - - -id _object_readExternalReference_non_gc(objc_xref_t ref) { - id obj = decode_pointer(ref); - return obj; -} - - -void _object_removeExternalReference_non_gc(objc_xref_t ref) { - id obj = decode_pointer(ref); - objc_xref_type_t ref_type = decode_type(ref); - switch (ref_type) { - case OBJC_XREF_STRONG: - ((void(*)(id, SEL))objc_msgSend)(obj, SEL_release); - break; - case OBJC_XREF_WEAK: - break; - default: - _objc_fatal("invalid external reference type: %d", (int)ref_type); - break; - } -} - - -uintptr_t _object_getExternalHash(id object) { - return (uintptr_t)object; -} - - -#if SUPPORT_GC - -// These functions are resolver functions in objc-auto.mm. - -#else - -objc_xref_t -_object_addExternalReference(id obj, objc_xref_t type) -{ - return _object_addExternalReference_non_gc(obj, type); -} - - -id -_object_readExternalReference(objc_xref_t ref) -{ - return _object_readExternalReference_non_gc(ref); -} - - -void -_object_removeExternalReference(objc_xref_t ref) -{ - _object_removeExternalReference_non_gc(ref); -} - -#endif diff --git a/runtime/objc-file-old.h b/runtime/objc-file-old.h index 9d4dfa7..3feb82b 100644 --- a/runtime/objc-file-old.h +++ b/runtime/objc-file-old.h @@ -40,6 +40,9 @@ extern struct old_protocol **_getObjcProtocols(const header_info *hi, size_t *np extern Class *_getObjcClassRefs(const header_info *hi, size_t *nclasses); extern const char *_getObjcClassNames(const header_info *hi, size_t *size); +using Initializer = void(*)(void); +extern Initializer* getLibobjcInitializers(const headerType *mhdr, size_t *count); + __END_DECLS #endif diff --git a/runtime/objc-file-old.mm b/runtime/objc-file-old.mm index 772e75d..8e170d4 100644 --- a/runtime/objc-file-old.mm +++ b/runtime/objc-file-old.mm @@ -69,21 +69,26 @@ #else -#define GETSECT(name, type, sectname) \ - type *name(const header_info *hi, size_t *outCount) \ +#define GETSECT(name, type, segname, sectname) \ + type *name(const headerType *mhdr, size_t *outCount) \ { \ unsigned long byteCount = 0; \ type *data = (type *) \ - getsectiondata(hi->mhdr, SEG_OBJC, sectname, &byteCount); \ + getsectiondata(mhdr, segname, sectname, &byteCount); \ *outCount = byteCount / sizeof(type); \ return data; \ + } \ + type *name(const header_info *hi, size_t *outCount) \ + { \ + return name(hi->mhdr(), outCount); \ } -GETSECT(_getObjcModules, struct objc_module, "__module_info"); -GETSECT(_getObjcSelectorRefs, SEL, "__message_refs"); -GETSECT(_getObjcClassRefs, Class, "__cls_refs"); -GETSECT(_getObjcClassNames, const char, "__class_names"); +GETSECT(_getObjcModules, objc_module, "__OBJC", "__module_info"); +GETSECT(_getObjcSelectorRefs, SEL, "__OBJC", "__message_refs"); +GETSECT(_getObjcClassRefs, Class, "__OBJC", "__cls_refs"); +GETSECT(_getObjcClassNames, const char, "__OBJC", "__class_names"); // __OBJC,__class_names section only emitted by CodeWarrior rdar://4951638 +GETSECT(getLibobjcInitializers, Initializer, "__DATA", "__objc_init_func"); objc_image_info * @@ -102,7 +107,7 @@ { unsigned long size = 0; struct old_protocol *protos = (struct old_protocol *) - getsectiondata(hi->mhdr, SEG_OBJC, "__protocol", &size); + getsectiondata(hi->mhdr(), SEG_OBJC, "__protocol", &size); *nprotos = size / sizeof(struct old_protocol); if (!hi->proto_refs && *nprotos) { @@ -141,7 +146,7 @@ _hasObjcContents(const header_info *hi) { // Look for an __OBJC,* section other than __OBJC,__image_info - const segmentType *seg = getsegbynamefromheader(hi->mhdr, "__OBJC"); + const segmentType *seg = getsegbynamefromheader(hi->mhdr(), "__OBJC"); const sectionType *sect; uint32_t i; for (i = 0; i < seg->nsects; i++) { diff --git a/runtime/objc-file.mm b/runtime/objc-file.mm index dac4ecd..c9ec260 100644 --- a/runtime/objc-file.mm +++ b/runtime/objc-file.mm @@ -26,23 +26,6 @@ #include "objc-private.h" #include "objc-file.h" -// Segment and section names are 16 bytes and may be un-terminated. -bool segnameEquals(const char *lhs, const char *rhs) { - return 0 == strncmp(lhs, rhs, 16); -} - -bool segnameStartsWith(const char *segname, const char *prefix) { - return 0 == strncmp(segname, prefix, strlen(prefix)); -} - -bool sectnameEquals(const char *lhs, const char *rhs) { - return segnameEquals(lhs, rhs); -} - -bool sectnameStartsWith(const char *sectname, const char *prefix) { - return segnameStartsWith(sectname, prefix); -} - // Look for a __DATA or __DATA_CONST or __DATA_DIRTY section // with the given name that stores an array of T. @@ -68,7 +51,7 @@ bool sectnameStartsWith(const char *sectname, const char *prefix) { return getDataSection(mhdr, sectname, nil, outCount); \ } \ type *name(const header_info *hi, size_t *outCount) { \ - return getDataSection(hi->mhdr, sectname, nil, outCount); \ + return getDataSection(hi->mhdr(), sectname, nil, outCount); \ } // function name content type section name @@ -82,7 +65,7 @@ bool sectnameStartsWith(const char *sectname, const char *prefix) { GETSECT(_getObjc2NonlazyCategoryList, category_t *, "__objc_nlcatlist"); GETSECT(_getObjc2ProtocolList, protocol_t *, "__objc_protolist"); GETSECT(_getObjc2ProtocolRefs, protocol_t *, "__objc_protorefs"); -GETSECT(getLibobjcInitializers, Initializer, "__objc_init_func"); +GETSECT(getLibobjcInitializers, Initializer, "__objc_init_func"); objc_image_info * @@ -128,15 +111,17 @@ static bool segmentHasObjcContents(const segmentType *seg) _hasObjcContents(const header_info *hi) { const segmentType *data = - getsegbynamefromheader(hi->mhdr, "__DATA"); + getsegbynamefromheader(hi->mhdr(), "__DATA"); const segmentType *data_const = - getsegbynamefromheader(hi->mhdr, "__DATA_CONST"); + getsegbynamefromheader(hi->mhdr(), "__DATA_CONST"); const segmentType *data_dirty = - getsegbynamefromheader(hi->mhdr, "__DATA_CONST"); + getsegbynamefromheader(hi->mhdr(), "__DATA_DIRTY"); return segmentHasObjcContents(data) || segmentHasObjcContents(data_const) || segmentHasObjcContents(data_dirty); } + +// OBJC2 #endif diff --git a/runtime/objc-gdb.h b/runtime/objc-gdb.h index 65b3862..0c01590 100644 --- a/runtime/objc-gdb.h +++ b/runtime/objc-gdb.h @@ -49,14 +49,14 @@ __BEGIN_DECLS // Return cls if it's a valid class, or crash. OBJC_EXPORT Class gdb_class_getClass(Class cls) #if __OBJC2__ - __OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_3_1); + OBJC_AVAILABLE(10.6, 3.1, 9.0, 1.0); #else - __OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_3_1); + OBJC_AVAILABLE(10.7, 3.1, 9.0, 1.0); #endif // Same as gdb_class_getClass(object_getClass(cls)). OBJC_EXPORT Class gdb_object_getClass(id obj) - __OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_4_3); + OBJC_AVAILABLE(10.7, 4.3, 9.0, 1.0); /*********************************************************************** @@ -67,13 +67,14 @@ OBJC_EXPORT Class gdb_object_getClass(id obj) // Maps class name to Class, for in-use classes only. NXStrValueMapPrototype. OBJC_EXPORT NXMapTable *gdb_objc_realized_classes - __OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_3_1); + OBJC_AVAILABLE(10.6, 3.1, 9.0, 1.0); #else // Hashes Classes, for all known classes. Custom prototype. OBJC_EXPORT NXHashTable *_objc_debug_class_hash - __OSX_AVAILABLE_STARTING(__MAC_10_2, __IPHONE_NA); + __OSX_AVAILABLE(10.2) + __IOS_UNAVAILABLE __TVOS_UNAVAILABLE __WATCHOS_UNAVAILABLE; #endif @@ -87,14 +88,35 @@ OBJC_EXPORT NXHashTable *_objc_debug_class_hash // Extract isa pointer from an isa field. // (Class)(isa & mask) == class pointer OBJC_EXPORT const uintptr_t objc_debug_isa_class_mask - __OSX_AVAILABLE_STARTING(__MAC_10_10, __IPHONE_7_0); + OBJC_AVAILABLE(10.10, 7.0, 9.0, 1.0); // Extract magic cookie from an isa field. // (isa & magic_mask) == magic_value OBJC_EXPORT const uintptr_t objc_debug_isa_magic_mask - __OSX_AVAILABLE_STARTING(__MAC_10_10, __IPHONE_7_0); + OBJC_AVAILABLE(10.10, 7.0, 9.0, 1.0); OBJC_EXPORT const uintptr_t objc_debug_isa_magic_value - __OSX_AVAILABLE_STARTING(__MAC_10_10, __IPHONE_7_0); + OBJC_AVAILABLE(10.10, 7.0, 9.0, 1.0); + +// Use indexed ISAs for targets which store index of the class in the ISA. +// This index can be used to index the array of classes. +OBJC_EXPORT const uintptr_t objc_debug_indexed_isa_magic_mask; +OBJC_EXPORT const uintptr_t objc_debug_indexed_isa_magic_value; + +// Then these are used to extract the index from the ISA. +OBJC_EXPORT const uintptr_t objc_debug_indexed_isa_index_mask; +OBJC_EXPORT const uintptr_t objc_debug_indexed_isa_index_shift; + +// And then we can use that index to get the class from this array. Note +// the size is provided so that clients can ensure the index they get is in +// bounds and not read off the end of the array. +OBJC_EXPORT Class objc_indexed_classes[]; + +// When we don't have enough bits to store a class*, we can instead store an +// index in to this array. Classes are added here when they are realized. +// Note, an index of 0 is illegal. +OBJC_EXPORT uintptr_t objc_indexed_classes_count; + +// Absolute symbols for some of the above values are in objc-abi.h. #endif @@ -104,26 +126,57 @@ OBJC_EXPORT const uintptr_t objc_debug_isa_magic_value **********************************************************************/ #if __OBJC2__ +// Basic tagged pointers (7 classes, 60-bit payload). + // if (obj & mask) obj is a tagged pointer object OBJC_EXPORT uintptr_t objc_debug_taggedpointer_mask - __OSX_AVAILABLE_STARTING(__MAC_10_9, __IPHONE_7_0); + OBJC_AVAILABLE(10.9, 7.0, 9.0, 1.0); // tag_slot = (obj >> slot_shift) & slot_mask OBJC_EXPORT unsigned int objc_debug_taggedpointer_slot_shift - __OSX_AVAILABLE_STARTING(__MAC_10_9, __IPHONE_7_0); + OBJC_AVAILABLE(10.9, 7.0, 9.0, 1.0); OBJC_EXPORT uintptr_t objc_debug_taggedpointer_slot_mask - __OSX_AVAILABLE_STARTING(__MAC_10_9, __IPHONE_7_0); + OBJC_AVAILABLE(10.9, 7.0, 9.0, 1.0); // class = classes[tag_slot] OBJC_EXPORT Class objc_debug_taggedpointer_classes[] - __OSX_AVAILABLE_STARTING(__MAC_10_9, __IPHONE_7_0); + OBJC_AVAILABLE(10.9, 7.0, 9.0, 1.0); // payload = (obj << payload_lshift) >> payload_rshift // Payload signedness is determined by the signedness of the right-shift. OBJC_EXPORT unsigned int objc_debug_taggedpointer_payload_lshift - __OSX_AVAILABLE_STARTING(__MAC_10_9, __IPHONE_7_0); + OBJC_AVAILABLE(10.9, 7.0, 9.0, 1.0); OBJC_EXPORT unsigned int objc_debug_taggedpointer_payload_rshift - __OSX_AVAILABLE_STARTING(__MAC_10_9, __IPHONE_7_0); + OBJC_AVAILABLE(10.9, 7.0, 9.0, 1.0); + + +// Extended tagged pointers (255 classes, 52-bit payload). + +// If you interrogate an extended tagged pointer using the basic +// tagged pointer scheme alone, it will appear to have an isa +// that is either nil or class __NSUnrecognizedTaggedPointer. + +// if (ext_mask != 0 && (obj & ext_mask) == ext_mask) +// obj is a ext tagged pointer object +OBJC_EXPORT uintptr_t objc_debug_taggedpointer_ext_mask + OBJC_AVAILABLE(10.12, 10.0, 10.0, 3.0); + +// ext_tag_slot = (obj >> ext_slot_shift) & ext_slot_mask +OBJC_EXPORT unsigned int objc_debug_taggedpointer_ext_slot_shift + OBJC_AVAILABLE(10.12, 10.0, 10.0, 3.0); +OBJC_EXPORT uintptr_t objc_debug_taggedpointer_ext_slot_mask + OBJC_AVAILABLE(10.12, 10.0, 10.0, 3.0); + +// class = ext_classes[ext_tag_slot] +OBJC_EXPORT Class objc_debug_taggedpointer_ext_classes[] + OBJC_AVAILABLE(10.12, 10.0, 10.0, 3.0); + +// payload = (obj << ext_payload_lshift) >> ext_payload_rshift +// Payload signedness is determined by the signedness of the right-shift. +OBJC_EXPORT unsigned int objc_debug_taggedpointer_ext_payload_lshift + OBJC_AVAILABLE(10.12, 10.0, 10.0, 3.0); +OBJC_EXPORT unsigned int objc_debug_taggedpointer_ext_payload_rshift + OBJC_AVAILABLE(10.12, 10.0, 10.0, 3.0); #endif @@ -158,25 +211,11 @@ struct objc_messenger_breakpoint { OBJC_EXPORT struct objc_messenger_breakpoint gdb_objc_messenger_breakpoints[] - __OSX_AVAILABLE_STARTING(__MAC_10_9, __IPHONE_7_0); + OBJC_AVAILABLE(10.9, 7.0, 9.0, 1.0); #endif -#ifndef OBJC_NO_GC - -/*********************************************************************** - * Garbage Collector heap dump -**********************************************************************/ - -/* Dump GC heap; if supplied the name is returned in filenamebuffer. Returns YES on success. */ -OBJC_EXPORT BOOL objc_dumpHeap(char *filenamebuffer, unsigned long length) - __OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_NA); - -#define OBJC_HEAP_DUMP_FILENAME_FORMAT "/tmp/objc-gc-heap-dump-%d-%d" - -#endif - __END_DECLS #endif diff --git a/runtime/objc-initialize.h b/runtime/objc-initialize.h index f516959..9ec99b5 100644 --- a/runtime/objc-initialize.h +++ b/runtime/objc-initialize.h @@ -34,6 +34,8 @@ extern void _class_initialize(Class cls); extern void _destroyInitializingClassList(struct _objc_initializing_classes *list); +extern bool _thisThreadIsInitializingClass(Class cls); + __END_DECLS #endif diff --git a/runtime/objc-initialize.mm b/runtime/objc-initialize.mm index b4f0ba0..0857305 100644 --- a/runtime/objc-initialize.mm +++ b/runtime/objc-initialize.mm @@ -122,7 +122,7 @@ * If create == YES, create the list when no classes are being initialized by this thread. * If create == NO, return nil when no classes are being initialized by this thread. **********************************************************************/ -static _objc_initializing_classes *_fetchInitializingClassList(BOOL create) +static _objc_initializing_classes *_fetchInitializingClassList(bool create) { _objc_pthread_data *data; _objc_initializing_classes *list; @@ -178,7 +178,7 @@ void _destroyInitializingClassList(struct _objc_initializing_classes *list) * _thisThreadIsInitializingClass * Return TRUE if this thread is currently initializing the given class. **********************************************************************/ -static BOOL _thisThreadIsInitializingClass(Class cls) +bool _thisThreadIsInitializingClass(Class cls) { int i; @@ -284,11 +284,6 @@ static void _finishInitializing(Class cls, Class supercls) cls->nameForLogging()); } - // propagate finalization affinity. - if (UseGC && supercls && supercls->shouldFinalizeOnMainThread()) { - cls->setShouldFinalizeOnMainThread(); - } - // mark this class as fully +initialized cls->setInitialized(); classInitLock.notifyAll(); @@ -346,6 +341,32 @@ static void _finishInitializingAfter(Class cls, Class supercls) } +// Provide helpful messages in stack traces. +OBJC_EXTERN __attribute__((noinline, used, visibility("hidden"))) +void waitForInitializeToComplete(Class cls) + asm("_WAITING_FOR_ANOTHER_THREAD_TO_FINISH_CALLING_+initialize"); +OBJC_EXTERN __attribute__((noinline, used, visibility("hidden"))) +void callInitialize(Class cls) + asm("_CALLING_SOME_+initialize_METHOD"); + + +void waitForInitializeToComplete(Class cls) +{ + monitor_locker_t lock(classInitLock); + while (!cls->isInitialized()) { + classInitLock.wait(); + } + asm(""); +} + + +void callInitialize(Class cls) +{ + ((void(*)(Class, SEL))objc_msgSend)(cls, SEL_initialize); + asm(""); +} + + /*********************************************************************** * class_initialize. Send the '+initialize' message on demand to any * uninitialized class. Force initialization of superclasses first. @@ -355,7 +376,7 @@ void _class_initialize(Class cls) assert(!cls->isMetaClass()); Class supercls; - BOOL reallyInitialize = NO; + bool reallyInitialize = NO; // Make sure super is done initializing BEFORE beginning to initialize cls. // See note about deadlock above. @@ -387,23 +408,35 @@ void _class_initialize(Class cls) cls->nameForLogging()); } - ((void(*)(Class, SEL))objc_msgSend)(cls, SEL_initialize); + // Exceptions: A +initialize call that throws an exception + // is deemed to be a complete and successful +initialize. + @try { + callInitialize(cls); - if (PrintInitializing) { - _objc_inform("INITIALIZE: finished +[%s initialize]", - cls->nameForLogging()); - } - - // Done initializing. - // If the superclass is also done initializing, then update - // the info bits and notify waiting threads. - // If not, update them later. (This can happen if this +initialize - // was itself triggered from inside a superclass +initialize.) - monitor_locker_t lock(classInitLock); - if (!supercls || supercls->isInitialized()) { - _finishInitializing(cls, supercls); - } else { - _finishInitializingAfter(cls, supercls); + if (PrintInitializing) { + _objc_inform("INITIALIZE: finished +[%s initialize]", + cls->nameForLogging()); + } + } + @catch (...) { + if (PrintInitializing) { + _objc_inform("INITIALIZE: +[%s initialize] threw an exception", + cls->nameForLogging()); + } + @throw; + } + @finally { + // Done initializing. + // If the superclass is also done initializing, then update + // the info bits and notify waiting threads. + // If not, update them later. (This can happen if this +initialize + // was itself triggered from inside a superclass +initialize.) + monitor_locker_t lock(classInitLock); + if (!supercls || supercls->isInitialized()) { + _finishInitializing(cls, supercls); + } else { + _finishInitializingAfter(cls, supercls); + } } return; } @@ -418,10 +451,7 @@ void _class_initialize(Class cls) if (_thisThreadIsInitializingClass(cls)) { return; } else { - monitor_locker_t lock(classInitLock); - while (!cls->isInitialized()) { - classInitLock.wait(); - } + waitForInitializeToComplete(cls); return; } } diff --git a/runtime/objc-internal.h b/runtime/objc-internal.h index f77cb18..5bcb28c 100644 --- a/runtime/objc-internal.h +++ b/runtime/objc-internal.h @@ -45,6 +45,10 @@ __BEGIN_DECLS +// Termination reasons in the OS_REASON_OBJC namespace. +#define OBJC_EXIT_REASON_UNSPECIFIED 1 +#define OBJC_EXIT_REASON_GC_NOT_SUPPORTED 2 + // This is the allocation size required for each of the class and the metaclass // with objc_initializeClassPair() and objc_readClassPair(). // The runtime's class structure will never grow beyond this. @@ -56,7 +60,7 @@ __BEGIN_DECLS // Returns nil if the superclass is under construction. // Call objc_registerClassPair() when you are done. OBJC_EXPORT Class objc_initializeClassPair(Class superclass, const char *name, Class cls, Class metacls) - __OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_3_0); + OBJC_AVAILABLE(10.6, 3.0, 9.0, 1.0); // Class and metaclass construction from a compiler-generated memory image. // cls and cls->isa must each be OBJC_MAX_CLASS_SIZE bytes. @@ -70,38 +74,40 @@ OBJC_EXPORT Class objc_initializeClassPair(Class superclass, const char *name, C struct objc_image_info; OBJC_EXPORT Class objc_readClassPair(Class cls, const struct objc_image_info *info) - __OSX_AVAILABLE_STARTING(__MAC_10_10, __IPHONE_8_0); + OBJC_AVAILABLE(10.10, 8.0, 9.0, 1.0); #endif // Batch object allocation using malloc_zone_batch_malloc(). OBJC_EXPORT unsigned class_createInstances(Class cls, size_t extraBytes, id *results, unsigned num_requested) - __OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_4_3) + OBJC_AVAILABLE(10.7, 4.3, 9.0, 1.0) OBJC_ARC_UNAVAILABLE; // Get the isa pointer written into objects just before being freed. OBJC_EXPORT Class _objc_getFreedObjectClass(void) - __OSX_AVAILABLE_STARTING(__MAC_10_0, __IPHONE_2_0); - -// Return YES if GC is on and `object` is a GC allocation. -OBJC_EXPORT BOOL objc_isAuto(id object) - __OSX_AVAILABLE_STARTING(__MAC_10_4, __IPHONE_NA); + OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0); // env NSObjCMessageLoggingEnabled OBJC_EXPORT void instrumentObjcMessageSends(BOOL flag) - __OSX_AVAILABLE_STARTING(__MAC_10_0, __IPHONE_2_0); + OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0); // Initializer called by libSystem -#if __OBJC2__ OBJC_EXPORT void _objc_init(void) - __OSX_AVAILABLE_STARTING(__MAC_10_8, __IPHONE_6_0); +#if __OBJC2__ + OBJC_AVAILABLE(10.8, 6.0, 9.0, 1.0); +#else + OBJC_AVAILABLE(10.12, 10.0, 10.0, 3.0); #endif -#ifndef OBJC_NO_GC +// Return YES if GC is on and `object` is a GC allocation. +OBJC_EXPORT BOOL objc_isAuto(id object) + __OSX_DEPRECATED(10.4, 10.8, "it always returns NO") + __IOS_UNAVAILABLE __TVOS_UNAVAILABLE __WATCHOS_UNAVAILABLE; + // GC startup callback from Foundation OBJC_EXPORT malloc_zone_t *objc_collect_init(int (*callback)(void)) - __OSX_AVAILABLE_STARTING(__MAC_10_4, __IPHONE_NA); -#endif + __OSX_DEPRECATED(10.4, 10.8, "it does nothing") + __IOS_UNAVAILABLE __TVOS_UNAVAILABLE __WATCHOS_UNAVAILABLE; // Plainly-implemented GC barriers. Rosetta used to use these. OBJC_EXPORT id objc_assign_strongCast_generic(id value, id *dest) @@ -113,25 +119,35 @@ OBJC_EXPORT id objc_assign_threadlocal_generic(id value, id *dest) OBJC_EXPORT id objc_assign_ivar_generic(id value, id dest, ptrdiff_t offset) UNAVAILABLE_ATTRIBUTE; +// GC preflight for an app executable. +// 1: some slice requires GC +// 0: no slice requires GC +// -1: I/O or file format error +OBJC_EXPORT int objc_appRequiresGC(int fd) + __OSX_AVAILABLE(10.11) + __IOS_UNAVAILABLE __TVOS_UNAVAILABLE __WATCHOS_UNAVAILABLE; + // Install missing-class callback. Used by the late unlamented ZeroLink. OBJC_EXPORT void _objc_setClassLoader(BOOL (*newClassLoader)(const char *)) OBJC2_UNAVAILABLE; // Install handler for allocation failures. // Handler may abort, or throw, or provide an object to return. OBJC_EXPORT void _objc_setBadAllocHandler(id (*newHandler)(Class isa)) - __OSX_AVAILABLE_STARTING(__MAC_10_8, __IPHONE_6_0); + OBJC_AVAILABLE(10.8, 6.0, 9.0, 1.0); // This can go away when AppKit stops calling it (rdar://7811851) #if __OBJC2__ OBJC_EXPORT void objc_setMultithreaded (BOOL flag) - __OSX_AVAILABLE_BUT_DEPRECATED(__MAC_10_0,__MAC_10_5, __IPHONE_NA,__IPHONE_NA); + __OSX_DEPRECATED(10.0, 10.5, "multithreading is always available") + __IOS_UNAVAILABLE __TVOS_UNAVAILABLE __WATCHOS_UNAVAILABLE; #endif // Used by ExceptionHandling.framework #if !__OBJC2__ OBJC_EXPORT void _objc_error(id rcv, const char *fmt, va_list args) __attribute__((noreturn)) - __OSX_AVAILABLE_BUT_DEPRECATED(__MAC_10_0,__MAC_10_5, __IPHONE_NA,__IPHONE_NA); + __OSX_DEPRECATED(10.0, 10.5, "use other logging facilities instead") + __IOS_UNAVAILABLE __TVOS_UNAVAILABLE __WATCHOS_UNAVAILABLE; #endif @@ -144,18 +160,17 @@ OBJC_EXPORT void _objc_error(id rcv, const char *fmt, va_list args) #if OBJC_HAVE_TAGGED_POINTERS -// Tagged pointer layout and usage is subject to change -// on different OS versions. The current layout is: -// (MSB) -// 60 bits payload -// 3 bits tag index -// 1 bit 1 for tagged pointer objects, 0 for ordinary objects -// (LSB) +// Tagged pointer layout and usage is subject to change on different OS versions. + +// Tag indexes 0..<7 have a 60-bit payload. +// Tag index 7 is reserved. +// Tag indexes 8..<264 have a 52-bit payload. +// Tag index 264 is reserved. #if __has_feature(objc_fixed_enum) || __cplusplus >= 201103L -enum objc_tag_index_t : uint8_t +enum objc_tag_index_t : uint16_t #else -typedef uint8_t objc_tag_index_t; +typedef uint16_t objc_tag_index_t; enum #endif { @@ -166,134 +181,188 @@ enum OBJC_TAG_NSIndexPath = 4, OBJC_TAG_NSManagedObjectID = 5, OBJC_TAG_NSDate = 6, - OBJC_TAG_7 = 7 + OBJC_TAG_RESERVED_7 = 7, + + OBJC_TAG_First60BitPayload = 0, + OBJC_TAG_Last60BitPayload = 6, + OBJC_TAG_First52BitPayload = 8, + OBJC_TAG_Last52BitPayload = 263, + + OBJC_TAG_RESERVED_264 = 264 }; #if __has_feature(objc_fixed_enum) && !defined(__cplusplus) typedef enum objc_tag_index_t objc_tag_index_t; #endif -OBJC_EXPORT void _objc_registerTaggedPointerClass(objc_tag_index_t tag, Class cls) - __OSX_AVAILABLE_STARTING(__MAC_10_9, __IPHONE_7_0); - -OBJC_EXPORT Class _objc_getClassForTag(objc_tag_index_t tag) - __OSX_AVAILABLE_STARTING(__MAC_10_9, __IPHONE_7_0); +// Returns true if tagged pointers are enabled. +// The other functions below must not be called if tagged pointers are disabled. static inline bool -_objc_taggedPointersEnabled(void) -{ - extern uintptr_t objc_debug_taggedpointer_mask; - return (objc_debug_taggedpointer_mask != 0); -} +_objc_taggedPointersEnabled(void); + +// Register a class for a tagged pointer tag. +// Aborts if the tag is invalid or already in use. +OBJC_EXPORT void _objc_registerTaggedPointerClass(objc_tag_index_t tag, Class cls) + OBJC_AVAILABLE(10.9, 7.0, 9.0, 1.0); -#if TARGET_OS_IPHONE -// tagged pointer marker is MSB +// Returns the registered class for the given tag. +// Returns nil if the tag is valid but has no registered class. +// Aborts if the tag is invalid. +OBJC_EXPORT Class _objc_getClassForTag(objc_tag_index_t tag) + OBJC_AVAILABLE(10.9, 7.0, 9.0, 1.0); +// Create a tagged pointer object with the given tag and payload. +// Assumes the tag is valid. +// Assumes tagged pointers are enabled. +// The payload will be silently truncated to fit. static inline void * -_objc_makeTaggedPointer(objc_tag_index_t tag, uintptr_t value) -{ - // assert(_objc_taggedPointersEnabled()); - // assert((unsigned int)tag < 8); - // assert(((value << 4) >> 4) == value); - return (void*)((1UL << 63) | ((uintptr_t)tag << 60) | (value & ~(0xFUL << 60))); -} +_objc_makeTaggedPointer(objc_tag_index_t tag, uintptr_t payload); +// Return true if ptr is a tagged pointer object. +// Does not check the validity of ptr's class. static inline bool -_objc_isTaggedPointer(const void *ptr) -{ - return (intptr_t)ptr < 0; // a.k.a. ptr & 0x8000000000000000 -} +_objc_isTaggedPointer(const void *ptr); +// Extract the tag value from the given tagged pointer object. +// Assumes ptr is a valid tagged pointer object. +// Does not check the validity of ptr's tag. static inline objc_tag_index_t -_objc_getTaggedPointerTag(const void *ptr) -{ - // assert(_objc_isTaggedPointer(ptr)); - return (objc_tag_index_t)(((uintptr_t)ptr >> 60) & 0x7); -} +_objc_getTaggedPointerTag(const void *ptr); +// Extract the payload from the given tagged pointer object. +// Assumes ptr is a valid tagged pointer object. +// The payload value is zero-extended. static inline uintptr_t -_objc_getTaggedPointerValue(const void *ptr) -{ - // assert(_objc_isTaggedPointer(ptr)); - return (uintptr_t)ptr & 0x0fffffffffffffff; -} +_objc_getTaggedPointerValue(const void *ptr); +// Extract the payload from the given tagged pointer object. +// Assumes ptr is a valid tagged pointer object. +// The payload value is sign-extended. static inline intptr_t -_objc_getTaggedPointerSignedValue(const void *ptr) -{ - // assert(_objc_isTaggedPointer(ptr)); - return ((intptr_t)ptr << 4) >> 4; -} +_objc_getTaggedPointerSignedValue(const void *ptr); + +// Don't use the values below. Use the declarations above. -// TARGET_OS_IPHONE +#if TARGET_OS_OSX && __x86_64__ + // 64-bit Mac - tag bit is LSB +# define OBJC_MSB_TAGGED_POINTERS 0 #else -// not TARGET_OS_IPHONE -// tagged pointer marker is LSB + // Everything else - tag bit is MSB +# define OBJC_MSB_TAGGED_POINTERS 1 +#endif + +#define _OBJC_TAG_INDEX_MASK 0x7 +// array slot includes the tag bit itself +#define _OBJC_TAG_SLOT_COUNT 16 +#define _OBJC_TAG_SLOT_MASK 0xf + +#define _OBJC_TAG_EXT_INDEX_MASK 0xff +// array slot has no extra bits +#define _OBJC_TAG_EXT_SLOT_COUNT 256 +#define _OBJC_TAG_EXT_SLOT_MASK 0xff + +#if OBJC_MSB_TAGGED_POINTERS +# define _OBJC_TAG_MASK (1ULL<<63) +# define _OBJC_TAG_INDEX_SHIFT 60 +# define _OBJC_TAG_SLOT_SHIFT 60 +# define _OBJC_TAG_PAYLOAD_LSHIFT 4 +# define _OBJC_TAG_PAYLOAD_RSHIFT 4 +# define _OBJC_TAG_EXT_MASK (0xfULL<<60) +# define _OBJC_TAG_EXT_INDEX_SHIFT 52 +# define _OBJC_TAG_EXT_SLOT_SHIFT 52 +# define _OBJC_TAG_EXT_PAYLOAD_LSHIFT 12 +# define _OBJC_TAG_EXT_PAYLOAD_RSHIFT 12 +#else +# define _OBJC_TAG_MASK 1 +# define _OBJC_TAG_INDEX_SHIFT 1 +# define _OBJC_TAG_SLOT_SHIFT 0 +# define _OBJC_TAG_PAYLOAD_LSHIFT 0 +# define _OBJC_TAG_PAYLOAD_RSHIFT 4 +# define _OBJC_TAG_EXT_MASK 0xfULL +# define _OBJC_TAG_EXT_INDEX_SHIFT 4 +# define _OBJC_TAG_EXT_SLOT_SHIFT 4 +# define _OBJC_TAG_EXT_PAYLOAD_LSHIFT 0 +# define _OBJC_TAG_EXT_PAYLOAD_RSHIFT 12 +#endif + +static inline bool +_objc_taggedPointersEnabled(void) +{ + extern uintptr_t objc_debug_taggedpointer_mask; + return (objc_debug_taggedpointer_mask != 0); +} static inline void * _objc_makeTaggedPointer(objc_tag_index_t tag, uintptr_t value) { + // PAYLOAD_LSHIFT and PAYLOAD_RSHIFT are the payload extraction shifts. + // They are reversed here for payload insertion. + // assert(_objc_taggedPointersEnabled()); - // assert((unsigned int)tag < 8); - // assert(((value << 4) >> 4) == value); - return (void *)((value << 4) | ((uintptr_t)tag << 1) | 1); + if (tag <= OBJC_TAG_Last60BitPayload) { + // assert(((value << _OBJC_TAG_PAYLOAD_RSHIFT) >> _OBJC_TAG_PAYLOAD_LSHIFT) == value); + return (void*) + (_OBJC_TAG_MASK | + ((uintptr_t)tag << _OBJC_TAG_INDEX_SHIFT) | + ((value << _OBJC_TAG_PAYLOAD_RSHIFT) >> _OBJC_TAG_PAYLOAD_LSHIFT)); + } else { + // assert(tag >= OBJC_TAG_First52BitPayload); + // assert(tag <= OBJC_TAG_Last52BitPayload); + // assert(((value << _OBJC_TAG_EXT_PAYLOAD_RSHIFT) >> _OBJC_TAG_EXT_PAYLOAD_LSHIFT) == value); + return (void*) + (_OBJC_TAG_EXT_MASK | + ((uintptr_t)(tag - OBJC_TAG_First52BitPayload) << _OBJC_TAG_EXT_INDEX_SHIFT) | + ((value << _OBJC_TAG_EXT_PAYLOAD_RSHIFT) >> _OBJC_TAG_EXT_PAYLOAD_LSHIFT)); + } } static inline bool _objc_isTaggedPointer(const void *ptr) { - return (uintptr_t)ptr & 1; + return ((intptr_t)ptr & _OBJC_TAG_MASK) == _OBJC_TAG_MASK; } static inline objc_tag_index_t _objc_getTaggedPointerTag(const void *ptr) { // assert(_objc_isTaggedPointer(ptr)); - return (objc_tag_index_t)(((uintptr_t)ptr & 0xe) >> 1); + uintptr_t basicTag = ((uintptr_t)ptr >> _OBJC_TAG_INDEX_SHIFT) & _OBJC_TAG_INDEX_MASK; + uintptr_t extTag = ((uintptr_t)ptr >> _OBJC_TAG_EXT_INDEX_SHIFT) & _OBJC_TAG_EXT_INDEX_MASK; + if (basicTag == _OBJC_TAG_INDEX_MASK) { + return (objc_tag_index_t)(extTag + OBJC_TAG_First52BitPayload); + } else { + return (objc_tag_index_t)basicTag; + } } static inline uintptr_t _objc_getTaggedPointerValue(const void *ptr) { // assert(_objc_isTaggedPointer(ptr)); - return (uintptr_t)ptr >> 4; + uintptr_t basicTag = ((uintptr_t)ptr >> _OBJC_TAG_INDEX_SHIFT) & _OBJC_TAG_INDEX_MASK; + if (basicTag == _OBJC_TAG_INDEX_MASK) { + return ((uintptr_t)ptr << _OBJC_TAG_EXT_PAYLOAD_LSHIFT) >> _OBJC_TAG_EXT_PAYLOAD_RSHIFT; + } else { + return ((uintptr_t)ptr << _OBJC_TAG_PAYLOAD_LSHIFT) >> _OBJC_TAG_PAYLOAD_RSHIFT; + } } static inline intptr_t _objc_getTaggedPointerSignedValue(const void *ptr) { // assert(_objc_isTaggedPointer(ptr)); - return (intptr_t)ptr >> 4; + uintptr_t basicTag = ((uintptr_t)ptr >> _OBJC_TAG_INDEX_SHIFT) & _OBJC_TAG_INDEX_MASK; + if (basicTag == _OBJC_TAG_INDEX_MASK) { + return ((intptr_t)ptr << _OBJC_TAG_EXT_PAYLOAD_LSHIFT) >> _OBJC_TAG_EXT_PAYLOAD_RSHIFT; + } else { + return ((intptr_t)ptr << _OBJC_TAG_PAYLOAD_LSHIFT) >> _OBJC_TAG_PAYLOAD_RSHIFT; + } } -// not TARGET_OS_IPHONE -#endif - - -OBJC_EXPORT void _objc_insert_tagged_isa(unsigned char slotNumber, Class isa) - __OSX_AVAILABLE_BUT_DEPRECATED(__MAC_10_7,__MAC_10_9, __IPHONE_4_3,__IPHONE_7_0); - +// OBJC_HAVE_TAGGED_POINTERS #endif -// External Reference support. Used to support compaction. - -enum { - OBJC_XREF_STRONG = 1, - OBJC_XREF_WEAK = 2 -}; -typedef uintptr_t objc_xref_type_t; -typedef uintptr_t objc_xref_t; - -OBJC_EXPORT objc_xref_t _object_addExternalReference(id object, objc_xref_type_t type) - __OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_4_3); -OBJC_EXPORT void _object_removeExternalReference(objc_xref_t xref) - __OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_4_3); -OBJC_EXPORT id _object_readExternalReference(objc_xref_t xref) - __OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_4_3); - -OBJC_EXPORT uintptr_t _object_getExternalHash(id object) - __OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0); - /** * Returns the method implementation of an object. * @@ -308,197 +377,190 @@ OBJC_EXPORT uintptr_t _object_getExternalHash(id object) * class_getMethodImplementation(object_getClass(obj), name); */ OBJC_EXPORT IMP object_getMethodImplementation(id obj, SEL name) - __OSX_AVAILABLE_STARTING(__MAC_10_9, __IPHONE_7_0); + OBJC_AVAILABLE(10.9, 7.0, 9.0, 1.0); OBJC_EXPORT IMP object_getMethodImplementation_stret(id obj, SEL name) - __OSX_AVAILABLE_STARTING(__MAC_10_9, __IPHONE_7_0) + OBJC_AVAILABLE(10.9, 7.0, 9.0, 1.0) OBJC_ARM64_UNAVAILABLE; // Instance-specific instance variable layout. OBJC_EXPORT void _class_setIvarLayoutAccessor(Class cls_gen, const uint8_t* (*accessor) (id object)) - __OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_NA); + __OSX_AVAILABLE(10.7) + __IOS_UNAVAILABLE __TVOS_UNAVAILABLE __WATCHOS_UNAVAILABLE; OBJC_EXPORT const uint8_t *_object_getIvarLayout(Class cls_gen, id object) - __OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_NA); + __OSX_AVAILABLE(10.7) + __IOS_UNAVAILABLE __TVOS_UNAVAILABLE __WATCHOS_UNAVAILABLE; -OBJC_EXPORT BOOL _class_usesAutomaticRetainRelease(Class cls) - __OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0); +/* + "Unknown" includes non-object ivars and non-ARC non-__weak ivars + "Strong" includes ARC __strong ivars + "Weak" includes ARC and new MRC __weak ivars + "Unretained" includes ARC __unsafe_unretained and old GC+MRC __weak ivars +*/ +typedef enum { + objc_ivar_memoryUnknown, // unknown / unknown + objc_ivar_memoryStrong, // direct access / objc_storeStrong + objc_ivar_memoryWeak, // objc_loadWeak[Retained] / objc_storeWeak + objc_ivar_memoryUnretained // direct access / direct access +} objc_ivar_memory_management_t; + +OBJC_EXPORT objc_ivar_memory_management_t _class_getIvarMemoryManagement(Class cls, Ivar ivar) + OBJC_AVAILABLE(10.12, 10.0, 10.0, 3.0); OBJC_EXPORT BOOL _class_isFutureClass(Class cls) - __OSX_AVAILABLE_STARTING(__MAC_10_9, __IPHONE_7_0); - - -// Obsolete ARC conversions. - -// hack - remove and reinstate objc.h's definitions -#undef objc_retainedObject -#undef objc_unretainedObject -#undef objc_unretainedPointer -OBJC_EXPORT id objc_retainedObject(objc_objectptr_t pointer) - __OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0); -OBJC_EXPORT id objc_unretainedObject(objc_objectptr_t pointer) - __OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0); -OBJC_EXPORT objc_objectptr_t objc_unretainedPointer(id object) - __OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0); -#if __has_feature(objc_arc) -# define objc_retainedObject(o) ((__bridge_transfer id)(objc_objectptr_t)(o)) -# define objc_unretainedObject(o) ((__bridge id)(objc_objectptr_t)(o)) -# define objc_unretainedPointer(o) ((__bridge objc_objectptr_t)(id)(o)) -#else -# define objc_retainedObject(o) ((id)(objc_objectptr_t)(o)) -# define objc_unretainedObject(o) ((id)(objc_objectptr_t)(o)) -# define objc_unretainedPointer(o) ((objc_objectptr_t)(id)(o)) -#endif + OBJC_AVAILABLE(10.9, 7.0, 9.0, 1.0); + // API to only be called by root classes like NSObject or NSProxy OBJC_EXPORT id _objc_rootRetain(id obj) - __OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0); + OBJC_AVAILABLE(10.7, 5.0, 9.0, 1.0); OBJC_EXPORT void _objc_rootRelease(id obj) - __OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0); + OBJC_AVAILABLE(10.7, 5.0, 9.0, 1.0); OBJC_EXPORT bool _objc_rootReleaseWasZero(id obj) - __OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0); + OBJC_AVAILABLE(10.7, 5.0, 9.0, 1.0); OBJC_EXPORT bool _objc_rootTryRetain(id obj) -__OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0); + OBJC_AVAILABLE(10.7, 5.0, 9.0, 1.0); OBJC_EXPORT bool _objc_rootIsDeallocating(id obj) -__OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0); + OBJC_AVAILABLE(10.7, 5.0, 9.0, 1.0); OBJC_EXPORT id _objc_rootAutorelease(id obj) - __OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0); + OBJC_AVAILABLE(10.7, 5.0, 9.0, 1.0); OBJC_EXPORT uintptr_t _objc_rootRetainCount(id obj) - __OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0); + OBJC_AVAILABLE(10.7, 5.0, 9.0, 1.0); OBJC_EXPORT id _objc_rootInit(id obj) - __OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0); + OBJC_AVAILABLE(10.7, 5.0, 9.0, 1.0); OBJC_EXPORT id _objc_rootAllocWithZone(Class cls, malloc_zone_t *zone) - __OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0); + OBJC_AVAILABLE(10.7, 5.0, 9.0, 1.0); OBJC_EXPORT id _objc_rootAlloc(Class cls) - __OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0); + OBJC_AVAILABLE(10.7, 5.0, 9.0, 1.0); OBJC_EXPORT void _objc_rootDealloc(id obj) - __OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0); + OBJC_AVAILABLE(10.7, 5.0, 9.0, 1.0); OBJC_EXPORT void _objc_rootFinalize(id obj) - __OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0); + OBJC_AVAILABLE(10.7, 5.0, 9.0, 1.0); OBJC_EXPORT malloc_zone_t * _objc_rootZone(id obj) - __OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0); + OBJC_AVAILABLE(10.7, 5.0, 9.0, 1.0); OBJC_EXPORT uintptr_t _objc_rootHash(id obj) - __OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0); + OBJC_AVAILABLE(10.7, 5.0, 9.0, 1.0); OBJC_EXPORT void * objc_autoreleasePoolPush(void) - __OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0); + OBJC_AVAILABLE(10.7, 5.0, 9.0, 1.0); OBJC_EXPORT void objc_autoreleasePoolPop(void *context) - __OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0); + OBJC_AVAILABLE(10.7, 5.0, 9.0, 1.0); OBJC_EXPORT id objc_alloc(Class cls) - __OSX_AVAILABLE_STARTING(__MAC_10_9, __IPHONE_7_0); + OBJC_AVAILABLE(10.9, 7.0, 9.0, 1.0); OBJC_EXPORT id objc_allocWithZone(Class cls) - __OSX_AVAILABLE_STARTING(__MAC_10_9, __IPHONE_7_0); + OBJC_AVAILABLE(10.9, 7.0, 9.0, 1.0); OBJC_EXPORT id objc_retain(id obj) __asm__("_objc_retain") - __OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0); + OBJC_AVAILABLE(10.7, 5.0, 9.0, 1.0); OBJC_EXPORT void objc_release(id obj) __asm__("_objc_release") - __OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0); + OBJC_AVAILABLE(10.7, 5.0, 9.0, 1.0); OBJC_EXPORT id objc_autorelease(id obj) __asm__("_objc_autorelease") - __OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0); + OBJC_AVAILABLE(10.7, 5.0, 9.0, 1.0); // Prepare a value at +1 for return through a +0 autoreleasing convention. OBJC_EXPORT id objc_autoreleaseReturnValue(id obj) - __OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0); + OBJC_AVAILABLE(10.7, 5.0, 9.0, 1.0); // Prepare a value at +0 for return through a +0 autoreleasing convention. OBJC_EXPORT id objc_retainAutoreleaseReturnValue(id obj) - __OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0); + OBJC_AVAILABLE(10.7, 5.0, 9.0, 1.0); // Accept a value returned through a +0 autoreleasing convention for use at +1. OBJC_EXPORT id objc_retainAutoreleasedReturnValue(id obj) - __OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0); + OBJC_AVAILABLE(10.7, 5.0, 9.0, 1.0); // Accept a value returned through a +0 autoreleasing convention for use at +0. OBJC_EXPORT id objc_unsafeClaimAutoreleasedReturnValue(id obj) - __OSX_AVAILABLE_STARTING(__MAC_10_11, __IPHONE_9_0); + OBJC_AVAILABLE(10.11, 9.0, 9.0, 1.0); OBJC_EXPORT void objc_storeStrong(id *location, id obj) - __OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0); + OBJC_AVAILABLE(10.7, 5.0, 9.0, 1.0); OBJC_EXPORT id objc_retainAutorelease(id obj) - __OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0); + OBJC_AVAILABLE(10.7, 5.0, 9.0, 1.0); // obsolete. OBJC_EXPORT id objc_retain_autorelease(id obj) - __OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0); + OBJC_AVAILABLE(10.7, 5.0, 9.0, 1.0); OBJC_EXPORT id objc_loadWeakRetained(id *location) - __OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0); + OBJC_AVAILABLE(10.7, 5.0, 9.0, 1.0); OBJC_EXPORT id objc_initWeak(id *location, id val) - __OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0); + OBJC_AVAILABLE(10.7, 5.0, 9.0, 1.0); // Like objc_storeWeak, but stores nil if the new object is deallocating // or the new object's class does not support weak references. @@ -506,7 +568,7 @@ objc_initWeak(id *location, id val) OBJC_EXPORT id objc_storeWeakOrNil(id *location, id obj) - __OSX_AVAILABLE_STARTING(__MAC_10_11, __IPHONE_9_0); + OBJC_AVAILABLE(10.11, 9.0, 9.0, 1.0); // Like objc_initWeak, but stores nil if the new object is deallocating // or the new object's class does not support weak references. @@ -514,34 +576,34 @@ objc_storeWeakOrNil(id *location, id obj) OBJC_EXPORT id objc_initWeakOrNil(id *location, id val) - __OSX_AVAILABLE_STARTING(__MAC_10_11, __IPHONE_9_0); + OBJC_AVAILABLE(10.11, 9.0, 9.0, 1.0); OBJC_EXPORT void objc_destroyWeak(id *location) - __OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0); + OBJC_AVAILABLE(10.7, 5.0, 9.0, 1.0); OBJC_EXPORT void objc_copyWeak(id *to, id *from) - __OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0); + OBJC_AVAILABLE(10.7, 5.0, 9.0, 1.0); OBJC_EXPORT void objc_moveWeak(id *to, id *from) - __OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0); + OBJC_AVAILABLE(10.7, 5.0, 9.0, 1.0); OBJC_EXPORT void _objc_autoreleasePoolPrint(void) - __OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0); + OBJC_AVAILABLE(10.7, 5.0, 9.0, 1.0); OBJC_EXPORT BOOL objc_should_deallocate(id object) - __OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0); + OBJC_AVAILABLE(10.7, 5.0, 9.0, 1.0); OBJC_EXPORT void objc_clear_deallocating(id object) - __OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0); + OBJC_AVAILABLE(10.7, 5.0, 9.0, 1.0); // to make CF link for now @@ -549,17 +611,17 @@ OBJC_EXPORT void objc_clear_deallocating(id object) OBJC_EXPORT void * _objc_autoreleasePoolPush(void) - __OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0); + OBJC_AVAILABLE(10.7, 5.0, 9.0, 1.0); OBJC_EXPORT void _objc_autoreleasePoolPop(void *context) - __OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0); + OBJC_AVAILABLE(10.7, 5.0, 9.0, 1.0); // Extra @encode data for XPC, or NULL OBJC_EXPORT const char *_protocol_getMethodTypeEncoding(Protocol *p, SEL sel, BOOL isRequiredMethod, BOOL isInstanceMethod) - __OSX_AVAILABLE_STARTING(__MAC_10_8, __IPHONE_6_0); + OBJC_AVAILABLE(10.8, 6.0, 9.0, 1.0); // API to only be called by classes that provide their own reference count storage @@ -567,7 +629,7 @@ OBJC_EXPORT const char *_protocol_getMethodTypeEncoding(Protocol *p, SEL sel, BO OBJC_EXPORT void _objc_deallocOnMainThreadHelper(void *context) - __OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0); + OBJC_AVAILABLE(10.7, 5.0, 9.0, 1.0); // On async versus sync deallocation and the _dealloc2main flag // diff --git a/runtime/objc-lockdebug.mm b/runtime/objc-lockdebug.mm index 5423acd..ed94011 100644 --- a/runtime/objc-lockdebug.mm +++ b/runtime/objc-lockdebug.mm @@ -152,6 +152,42 @@ * Mutex checking **********************************************************************/ +#if !TARGET_OS_SIMULATOR +// Non-simulator platforms have lock debugging built into os_unfair_lock. + + +void +lockdebug_mutex_lock(mutex_t *lock) +{ + // empty +} + +void +lockdebug_mutex_unlock(mutex_t *lock) +{ + // empty +} + +void +lockdebug_mutex_assert_locked(mutex_t *lock) +{ + os_unfair_lock_assert_owner((os_unfair_lock *)lock); +} + +void +lockdebug_mutex_assert_unlocked(mutex_t *lock) +{ + os_unfair_lock_assert_not_owner((os_unfair_lock *)lock); +} + + +// !TARGET_OS_SIMULATOR +#else +// TARGET_OS_SIMULATOR + +// Simulator platforms have no built-in lock debugging in os_unfair_lock. + + void lockdebug_mutex_lock(mutex_t *lock) { @@ -206,6 +242,9 @@ } +// TARGET_OS_SIMULATOR +#endif + /*********************************************************************** * Recursive mutex checking **********************************************************************/ diff --git a/runtime/objc-object.h b/runtime/objc-object.h index cf592d4..821b0a7 100644 --- a/runtime/objc-object.h +++ b/runtime/objc-object.h @@ -42,23 +42,23 @@ bool prepareOptimizedReturn(ReturnDisposition disposition); #if SUPPORT_TAGGED_POINTERS -#define TAG_COUNT 8 -#define TAG_SLOT_MASK 0xf - -#if SUPPORT_MSB_TAGGED_POINTERS -# define TAG_MASK (1ULL<<63) -# define TAG_SLOT_SHIFT 60 -# define TAG_PAYLOAD_LSHIFT 4 -# define TAG_PAYLOAD_RSHIFT 4 -#else -# define TAG_MASK 1 -# define TAG_SLOT_SHIFT 0 -# define TAG_PAYLOAD_LSHIFT 0 -# define TAG_PAYLOAD_RSHIFT 4 +extern "C" { + extern Class objc_debug_taggedpointer_classes[_OBJC_TAG_SLOT_COUNT*2]; + extern Class objc_debug_taggedpointer_ext_classes[_OBJC_TAG_EXT_SLOT_COUNT]; +} +#define objc_tag_classes objc_debug_taggedpointer_classes +#define objc_tag_ext_classes objc_debug_taggedpointer_ext_classes + #endif -extern "C" { extern Class objc_debug_taggedpointer_classes[TAG_COUNT*2]; } -#define objc_tag_classes objc_debug_taggedpointer_classes +#if SUPPORT_INDEXED_ISA + +ALWAYS_INLINE Class & +classForIndex(uintptr_t index) { + assert(index > 0); + assert(index < (uintptr_t)objc_indexed_classes_count); + return objc_indexed_classes[index]; +} #endif @@ -70,38 +70,106 @@ objc_object::isClass() return ISA()->isMetaClass(); } -#if SUPPORT_NONPOINTER_ISA - -# if !SUPPORT_TAGGED_POINTERS -# error sorry -# endif +#if SUPPORT_TAGGED_POINTERS inline Class -objc_object::ISA() +objc_object::getIsa() { - assert(!isTaggedPointer()); - return (Class)(isa.bits & ISA_MASK); + if (!isTaggedPointer()) return ISA(); + + uintptr_t ptr = (uintptr_t)this; + if (isExtTaggedPointer()) { + uintptr_t slot = + (ptr >> _OBJC_TAG_EXT_SLOT_SHIFT) & _OBJC_TAG_EXT_SLOT_MASK; + return objc_tag_ext_classes[slot]; + } else { + uintptr_t slot = + (ptr >> _OBJC_TAG_SLOT_SHIFT) & _OBJC_TAG_SLOT_MASK; + return objc_tag_classes[slot]; + } } inline bool -objc_object::hasIndexedIsa() +objc_object::isTaggedPointer() { - return isa.indexed; + return _objc_isTaggedPointer(this); } +inline bool +objc_object::isBasicTaggedPointer() +{ + return isTaggedPointer() && !isExtTaggedPointer(); +} + +inline bool +objc_object::isExtTaggedPointer() +{ + return ((uintptr_t)this & _OBJC_TAG_EXT_MASK) == _OBJC_TAG_EXT_MASK; +} + + +// SUPPORT_TAGGED_POINTERS +#else +// not SUPPORT_TAGGED_POINTERS + + inline Class objc_object::getIsa() { - if (isTaggedPointer()) { - uintptr_t slot = ((uintptr_t)this >> TAG_SLOT_SHIFT) & TAG_SLOT_MASK; - return objc_tag_classes[slot]; - } return ISA(); } +inline bool +objc_object::isTaggedPointer() +{ + return false; +} + +inline bool +objc_object::isBasicTaggedPointer() +{ + return false; +} + +inline bool +objc_object::isExtTaggedPointer() +{ + return false; +} + + +// not SUPPORT_TAGGED_POINTERS +#endif + + +#if SUPPORT_NONPOINTER_ISA + +inline Class +objc_object::ISA() +{ + assert(!isTaggedPointer()); +#if SUPPORT_INDEXED_ISA + if (isa.nonpointer) { + uintptr_t slot = isa.indexcls; + return classForIndex((unsigned)slot); + } + return (Class)isa.bits; +#else + return (Class)(isa.bits & ISA_MASK); +#endif +} + + +inline bool +objc_object::hasNonpointerIsa() +{ + return isa.nonpointer; +} + + inline void objc_object::initIsa(Class cls) { @@ -111,10 +179,10 @@ objc_object::initIsa(Class cls) inline void objc_object::initClassIsa(Class cls) { - if (DisableIndexedIsa) { - initIsa(cls, false, false); + if (DisableNonpointerIsa || cls->instancesRequireRawIsa()) { + initIsa(cls, false/*not nonpointer*/, false); } else { - initIsa(cls, true, false); + initIsa(cls, true/*nonpointer*/, false); } } @@ -127,27 +195,47 @@ objc_object::initProtocolIsa(Class cls) inline void objc_object::initInstanceIsa(Class cls, bool hasCxxDtor) { - assert(!UseGC); - assert(!cls->requiresRawIsa()); + assert(!cls->instancesRequireRawIsa()); assert(hasCxxDtor == cls->hasCxxDtor()); initIsa(cls, true, hasCxxDtor); } inline void -objc_object::initIsa(Class cls, bool indexed, bool hasCxxDtor) +objc_object::initIsa(Class cls, bool nonpointer, bool hasCxxDtor) { assert(!isTaggedPointer()); - if (!indexed) { + if (!nonpointer) { isa.cls = cls; } else { - assert(!DisableIndexedIsa); - isa.bits = ISA_MAGIC_VALUE; + assert(!DisableNonpointerIsa); + assert(!cls->instancesRequireRawIsa()); + + isa_t newisa(0); + +#if SUPPORT_INDEXED_ISA + assert(cls->classArrayIndex() > 0); + newisa.bits = ISA_INDEX_MAGIC_VALUE; // isa.magic is part of ISA_MAGIC_VALUE - // isa.indexed is part of ISA_MAGIC_VALUE - isa.has_cxx_dtor = hasCxxDtor; - isa.shiftcls = (uintptr_t)cls >> 3; + // isa.nonpointer is part of ISA_MAGIC_VALUE + newisa.has_cxx_dtor = hasCxxDtor; + newisa.indexcls = (uintptr_t)cls->classArrayIndex(); +#else + newisa.bits = ISA_MAGIC_VALUE; + // isa.magic is part of ISA_MAGIC_VALUE + // isa.nonpointer is part of ISA_MAGIC_VALUE + newisa.has_cxx_dtor = hasCxxDtor; + newisa.shiftcls = (uintptr_t)cls >> 3; +#endif + + // This write must be performed in a single store in some cases + // (for example when realizing a class because other threads + // may simultaneously try to use the class). + // fixme use atomics here to guarantee single-store and to + // guarantee memory order w.r.t. the class index table + // ...but not too atomic because we don't want to hurt instantiation + isa = newisa; } } @@ -155,7 +243,7 @@ objc_object::initIsa(Class cls, bool indexed, bool hasCxxDtor) inline Class objc_object::changeIsa(Class newCls) { - // This is almost always rue but there are + // This is almost always true but there are // enough edge cases that we can't assert it. // assert(newCls->isFuture() || // newCls->isInitializing() || newCls->isInitialized()); @@ -171,20 +259,30 @@ objc_object::changeIsa(Class newCls) do { transcribeToSideTable = false; oldisa = LoadExclusive(&isa.bits); - if ((oldisa.bits == 0 || oldisa.indexed) && - !newCls->isFuture() && newCls->canAllocIndexed()) + if ((oldisa.bits == 0 || oldisa.nonpointer) && + !newCls->isFuture() && newCls->canAllocNonpointer()) { - // 0 -> indexed - // indexed -> indexed + // 0 -> nonpointer + // nonpointer -> nonpointer +#if SUPPORT_INDEXED_ISA + if (oldisa.bits == 0) newisa.bits = ISA_INDEX_MAGIC_VALUE; + else newisa = oldisa; + // isa.magic is part of ISA_MAGIC_VALUE + // isa.nonpointer is part of ISA_MAGIC_VALUE + newisa.has_cxx_dtor = newCls->hasCxxDtor(); + assert(newCls->classArrayIndex() > 0); + newisa.indexcls = (uintptr_t)newCls->classArrayIndex(); +#else if (oldisa.bits == 0) newisa.bits = ISA_MAGIC_VALUE; else newisa = oldisa; // isa.magic is part of ISA_MAGIC_VALUE - // isa.indexed is part of ISA_MAGIC_VALUE + // isa.nonpointer is part of ISA_MAGIC_VALUE newisa.has_cxx_dtor = newCls->hasCxxDtor(); newisa.shiftcls = (uintptr_t)newCls >> 3; +#endif } - else if (oldisa.indexed) { - // indexed -> not indexed + else if (oldisa.nonpointer) { + // nonpointer -> raw pointer // Need to copy retain count et al to side table. // Acquire side table lock before setting isa to // prevent races such as concurrent -release. @@ -194,14 +292,13 @@ objc_object::changeIsa(Class newCls) newisa.cls = newCls; } else { - // not indexed -> not indexed + // raw pointer -> raw pointer newisa.cls = newCls; } } while (!StoreExclusive(&isa.bits, oldisa.bits, newisa.bits)); if (transcribeToSideTable) { // Copy oldisa's retain count et al to side table. - // oldisa.weakly_referenced: nothing to do // oldisa.has_assoc: nothing to do // oldisa.has_cxx_dtor: nothing to do sidetable_moveExtraRC_nolock(oldisa.extra_rc, @@ -211,18 +308,16 @@ objc_object::changeIsa(Class newCls) if (sideTableLocked) sidetable_unlock(); - Class oldCls; - if (oldisa.indexed) oldCls = (Class)((uintptr_t)oldisa.shiftcls << 3); - else oldCls = oldisa.cls; - - return oldCls; -} - - -inline bool -objc_object::isTaggedPointer() -{ - return ((uintptr_t)this & TAG_MASK); + if (oldisa.nonpointer) { +#if SUPPORT_INDEXED_ISA + return classForIndex(oldisa.indexcls); +#else + return (Class)((uintptr_t)oldisa.shiftcls << 3); +#endif + } + else { + return oldisa.cls; + } } @@ -230,7 +325,7 @@ inline bool objc_object::hasAssociatedObjects() { if (isTaggedPointer()) return true; - if (isa.indexed) return isa.has_assoc; + if (isa.nonpointer) return isa.has_assoc; return true; } @@ -243,8 +338,10 @@ objc_object::setHasAssociatedObjects() retry: isa_t oldisa = LoadExclusive(&isa.bits); isa_t newisa = oldisa; - if (!newisa.indexed) return; - if (newisa.has_assoc) return; + if (!newisa.nonpointer || newisa.has_assoc) { + ClearExclusive(&isa.bits); + return; + } newisa.has_assoc = true; if (!StoreExclusive(&isa.bits, oldisa.bits, newisa.bits)) goto retry; } @@ -254,7 +351,7 @@ inline bool objc_object::isWeaklyReferenced() { assert(!isTaggedPointer()); - if (isa.indexed) return isa.weakly_referenced; + if (isa.nonpointer) return isa.weakly_referenced; else return sidetable_isWeaklyReferenced(); } @@ -265,8 +362,15 @@ objc_object::setWeaklyReferenced_nolock() retry: isa_t oldisa = LoadExclusive(&isa.bits); isa_t newisa = oldisa; - if (!newisa.indexed) return sidetable_setWeaklyReferenced_nolock(); - if (newisa.weakly_referenced) return; + if (slowpath(!newisa.nonpointer)) { + ClearExclusive(&isa.bits); + sidetable_setWeaklyReferenced_nolock(); + return; + } + if (newisa.weakly_referenced) { + ClearExclusive(&isa.bits); + return; + } newisa.weakly_referenced = true; if (!StoreExclusive(&isa.bits, oldisa.bits, newisa.bits)) goto retry; } @@ -276,7 +380,7 @@ inline bool objc_object::hasCxxDtor() { assert(!isTaggedPointer()); - if (isa.indexed) return isa.has_cxx_dtor; + if (isa.nonpointer) return isa.has_cxx_dtor; else return isa.cls->hasCxxDtor(); } @@ -285,10 +389,8 @@ objc_object::hasCxxDtor() inline bool objc_object::rootIsDeallocating() { - assert(!UseGC); - if (isTaggedPointer()) return false; - if (isa.indexed) return isa.deallocating; + if (isa.nonpointer) return isa.deallocating; return sidetable_isDeallocating(); } @@ -296,11 +398,11 @@ objc_object::rootIsDeallocating() inline void objc_object::clearDeallocating() { - if (!isa.indexed) { + if (slowpath(!isa.nonpointer)) { // Slow path for raw pointer isa. sidetable_clearDeallocating(); } - else if (isa.weakly_referenced || isa.has_sidetable_rc) { + else if (slowpath(isa.weakly_referenced || isa.has_sidetable_rc)) { // Slow path for non-pointer isa with weak refs and/or side table data. clearDeallocating_slow(); } @@ -312,14 +414,13 @@ objc_object::clearDeallocating() inline void objc_object::rootDealloc() { - assert(!UseGC); - if (isTaggedPointer()) return; + if (isTaggedPointer()) return; // fixme necessary? - if (isa.indexed && - !isa.weakly_referenced && - !isa.has_assoc && - !isa.has_cxx_dtor && - !isa.has_sidetable_rc) + if (fastpath(isa.nonpointer && + !isa.weakly_referenced && + !isa.has_assoc && + !isa.has_cxx_dtor && + !isa.has_sidetable_rc)) { assert(!sidetable_present()); free(this); @@ -334,11 +435,9 @@ objc_object::rootDealloc() inline id objc_object::retain() { - // UseGC is allowed here, but requires hasCustomRR. - assert(!UseGC || ISA()->hasCustomRR()); assert(!isTaggedPointer()); - if (! ISA()->hasCustomRR()) { + if (fastpath(!ISA()->hasCustomRR())) { return rootRetain(); } @@ -370,7 +469,6 @@ objc_object::rootTryRetain() ALWAYS_INLINE id objc_object::rootRetain(bool tryRetain, bool handleOverflow) { - assert(!UseGC); if (isTaggedPointer()) return (id)this; bool sideTableLocked = false; @@ -383,15 +481,27 @@ objc_object::rootRetain(bool tryRetain, bool handleOverflow) transcribeToSideTable = false; oldisa = LoadExclusive(&isa.bits); newisa = oldisa; - if (!newisa.indexed) goto unindexed; + if (slowpath(!newisa.nonpointer)) { + ClearExclusive(&isa.bits); + if (!tryRetain && sideTableLocked) sidetable_unlock(); + if (tryRetain) return sidetable_tryRetain() ? (id)this : nil; + else return sidetable_retain(); + } // don't check newisa.fast_rr; we already called any RR overrides - if (tryRetain && newisa.deallocating) goto tryfail; + if (slowpath(tryRetain && newisa.deallocating)) { + ClearExclusive(&isa.bits); + if (!tryRetain && sideTableLocked) sidetable_unlock(); + return nil; + } uintptr_t carry; newisa.bits = addc(newisa.bits, RC_ONE, 0, &carry); // extra_rc++ - if (carry) { + if (slowpath(carry)) { // newisa.extra_rc++ overflowed - if (!handleOverflow) return rootRetain_overflow(tryRetain); + if (!handleOverflow) { + ClearExclusive(&isa.bits); + return rootRetain_overflow(tryRetain); + } // Leave half of the retain counts inline and // prepare to copy the other half to the side table. if (!tryRetain && !sideTableLocked) sidetable_lock(); @@ -400,24 +510,15 @@ objc_object::rootRetain(bool tryRetain, bool handleOverflow) newisa.extra_rc = RC_HALF; newisa.has_sidetable_rc = true; } - } while (!StoreExclusive(&isa.bits, oldisa.bits, newisa.bits)); + } while (slowpath(!StoreExclusive(&isa.bits, oldisa.bits, newisa.bits))); - if (transcribeToSideTable) { + if (slowpath(transcribeToSideTable)) { // Copy the other half of the retain counts to the side table. sidetable_addExtraRC_nolock(RC_HALF); } - if (!tryRetain && sideTableLocked) sidetable_unlock(); + if (slowpath(!tryRetain && sideTableLocked)) sidetable_unlock(); return (id)this; - - tryfail: - if (!tryRetain && sideTableLocked) sidetable_unlock(); - return nil; - - unindexed: - if (!tryRetain && sideTableLocked) sidetable_unlock(); - if (tryRetain) return sidetable_tryRetain() ? (id)this : nil; - else return sidetable_retain(); } @@ -425,11 +526,9 @@ objc_object::rootRetain(bool tryRetain, bool handleOverflow) inline void objc_object::release() { - // UseGC is allowed here, but requires hasCustomRR. - assert(!UseGC || ISA()->hasCustomRR()); assert(!isTaggedPointer()); - if (! ISA()->hasCustomRR()) { + if (fastpath(!ISA()->hasCustomRR())) { rootRelease(); return; } @@ -463,7 +562,6 @@ objc_object::rootReleaseShouldDealloc() ALWAYS_INLINE bool objc_object::rootRelease(bool performDealloc, bool handleUnderflow) { - assert(!UseGC); if (isTaggedPointer()) return false; bool sideTableLocked = false; @@ -475,14 +573,22 @@ objc_object::rootRelease(bool performDealloc, bool handleUnderflow) do { oldisa = LoadExclusive(&isa.bits); newisa = oldisa; - if (!newisa.indexed) goto unindexed; + if (slowpath(!newisa.nonpointer)) { + ClearExclusive(&isa.bits); + if (sideTableLocked) sidetable_unlock(); + return sidetable_release(performDealloc); + } // don't check newisa.fast_rr; we already called any RR overrides uintptr_t carry; newisa.bits = subc(newisa.bits, RC_ONE, 0, &carry); // extra_rc-- - if (carry) goto underflow; - } while (!StoreReleaseExclusive(&isa.bits, oldisa.bits, newisa.bits)); + if (slowpath(carry)) { + // don't ClearExclusive() + goto underflow; + } + } while (slowpath(!StoreReleaseExclusive(&isa.bits, + oldisa.bits, newisa.bits))); - if (sideTableLocked) sidetable_unlock(); + if (slowpath(sideTableLocked)) sidetable_unlock(); return false; underflow: @@ -491,22 +597,21 @@ objc_object::rootRelease(bool performDealloc, bool handleUnderflow) // abandon newisa to undo the decrement newisa = oldisa; - if (newisa.has_sidetable_rc) { + if (slowpath(newisa.has_sidetable_rc)) { if (!handleUnderflow) { + ClearExclusive(&isa.bits); return rootRelease_underflow(performDealloc); } // Transfer retain count from side table to inline storage. if (!sideTableLocked) { + ClearExclusive(&isa.bits); sidetable_lock(); sideTableLocked = true; - if (!isa.indexed) { - // Lost a race vs the indexed -> not indexed transition - // before we got the side table lock. Stop now to avoid - // breaking the safety checks in the sidetable ExtraRC code. - goto unindexed; - } + // Need to start over to avoid a race against + // the nonpointer -> raw pointer transition. + goto retry; } // Try to remove some retain counts from the side table. @@ -519,7 +624,8 @@ objc_object::rootRelease(bool performDealloc, bool handleUnderflow) // Side table retain count decreased. // Try to add them to the inline count. newisa.extra_rc = borrowed - 1; // redo the original decrement too - bool stored = StoreExclusive(&isa.bits, oldisa.bits, newisa.bits); + bool stored = StoreReleaseExclusive(&isa.bits, + oldisa.bits, newisa.bits); if (!stored) { // Inline update failed. // Try it again right now. This prevents livelock on LL/SC @@ -527,7 +633,7 @@ objc_object::rootRelease(bool performDealloc, bool handleUnderflow) // dropped the reservation. isa_t oldisa2 = LoadExclusive(&isa.bits); isa_t newisa2 = oldisa2; - if (newisa2.indexed) { + if (newisa2.nonpointer) { uintptr_t overflow; newisa2.bits = addc(newisa2.bits, RC_ONE * (borrowed-1), 0, &overflow); @@ -559,22 +665,22 @@ objc_object::rootRelease(bool performDealloc, bool handleUnderflow) // Really deallocate. - if (sideTableLocked) sidetable_unlock(); - - if (newisa.deallocating) { + if (slowpath(newisa.deallocating)) { + ClearExclusive(&isa.bits); + if (sideTableLocked) sidetable_unlock(); return overrelease_error(); + // does not actually return } newisa.deallocating = true; if (!StoreExclusive(&isa.bits, oldisa.bits, newisa.bits)) goto retry; + + if (slowpath(sideTableLocked)) sidetable_unlock(); + __sync_synchronize(); if (performDealloc) { ((void(*)(objc_object *, SEL))objc_msgSend)(this, SEL_dealloc); } return true; - - unindexed: - if (sideTableLocked) sidetable_unlock(); - return sidetable_release(performDealloc); } @@ -582,11 +688,8 @@ objc_object::rootRelease(bool performDealloc, bool handleUnderflow) inline id objc_object::autorelease() { - // UseGC is allowed here, but requires hasCustomRR. - assert(!UseGC || ISA()->hasCustomRR()); - if (isTaggedPointer()) return (id)this; - if (! ISA()->hasCustomRR()) return rootAutorelease(); + if (fastpath(!ISA()->hasCustomRR())) return rootAutorelease(); return ((id(*)(objc_object *, SEL))objc_msgSend)(this, SEL_autorelease); } @@ -596,8 +699,6 @@ objc_object::autorelease() inline id objc_object::rootAutorelease() { - assert(!UseGC); - if (isTaggedPointer()) return (id)this; if (prepareOptimizedReturn(ReturnAtPlus1)) return (id)this; @@ -608,12 +709,12 @@ objc_object::rootAutorelease() inline uintptr_t objc_object::rootRetainCount() { - assert(!UseGC); if (isTaggedPointer()) return (uintptr_t)this; sidetable_lock(); isa_t bits = LoadExclusive(&isa.bits); - if (bits.indexed) { + ClearExclusive(&isa.bits); + if (bits.nonpointer) { uintptr_t rc = 1 + bits.extra_rc; if (bits.has_sidetable_rc) { rc += sidetable_getExtraRC_nolock(); @@ -641,25 +742,12 @@ objc_object::ISA() inline bool -objc_object::hasIndexedIsa() +objc_object::hasNonpointerIsa() { return false; } -inline Class -objc_object::getIsa() -{ -#if SUPPORT_TAGGED_POINTERS - if (isTaggedPointer()) { - uintptr_t slot = ((uintptr_t)this >> TAG_SLOT_SHIFT) & TAG_SLOT_MASK; - return objc_tag_classes[slot]; - } -#endif - return ISA(); -} - - inline void objc_object::initIsa(Class cls) { @@ -720,22 +808,9 @@ objc_object::changeIsa(Class cls) } -inline bool -objc_object::isTaggedPointer() -{ -#if SUPPORT_TAGGED_POINTERS - return ((uintptr_t)this & TAG_MASK); -#else - return false; -#endif -} - - inline bool objc_object::hasAssociatedObjects() { - assert(!UseGC); - return getIsa()->instancesHaveAssociatedObjects(); } @@ -743,8 +818,6 @@ objc_object::hasAssociatedObjects() inline void objc_object::setHasAssociatedObjects() { - assert(!UseGC); - getIsa()->setInstancesHaveAssociatedObjects(); } @@ -753,7 +826,6 @@ inline bool objc_object::isWeaklyReferenced() { assert(!isTaggedPointer()); - assert(!UseGC); return sidetable_isWeaklyReferenced(); } @@ -763,7 +835,6 @@ inline void objc_object::setWeaklyReferenced_nolock() { assert(!isTaggedPointer()); - assert(!UseGC); sidetable_setWeaklyReferenced_nolock(); } @@ -780,8 +851,6 @@ objc_object::hasCxxDtor() inline bool objc_object::rootIsDeallocating() { - assert(!UseGC); - if (isTaggedPointer()) return false; return sidetable_isDeallocating(); } @@ -806,11 +875,9 @@ objc_object::rootDealloc() inline id objc_object::retain() { - // UseGC is allowed here, but requires hasCustomRR. - assert(!UseGC || ISA()->hasCustomRR()); assert(!isTaggedPointer()); - if (! ISA()->hasCustomRR()) { + if (fastpath(!ISA()->hasCustomRR())) { return sidetable_retain(); } @@ -824,8 +891,6 @@ objc_object::retain() inline id objc_object::rootRetain() { - assert(!UseGC); - if (isTaggedPointer()) return (id)this; return sidetable_retain(); } @@ -835,11 +900,9 @@ objc_object::rootRetain() inline void objc_object::release() { - // UseGC is allowed here, but requires hasCustomRR. - assert(!UseGC || ISA()->hasCustomRR()); assert(!isTaggedPointer()); - if (! ISA()->hasCustomRR()) { + if (fastpath(!ISA()->hasCustomRR())) { sidetable_release(); return; } @@ -856,8 +919,6 @@ objc_object::release() inline bool objc_object::rootRelease() { - assert(!UseGC); - if (isTaggedPointer()) return false; return sidetable_release(true); } @@ -874,11 +935,8 @@ objc_object::rootReleaseShouldDealloc() inline id objc_object::autorelease() { - // UseGC is allowed here, but requires hasCustomRR. - assert(!UseGC || ISA()->hasCustomRR()); - if (isTaggedPointer()) return (id)this; - if (! ISA()->hasCustomRR()) return rootAutorelease(); + if (fastpath(!ISA()->hasCustomRR())) return rootAutorelease(); return ((id(*)(objc_object *, SEL))objc_msgSend)(this, SEL_autorelease); } @@ -888,8 +946,6 @@ objc_object::autorelease() inline id objc_object::rootAutorelease() { - assert(!UseGC); - if (isTaggedPointer()) return (id)this; if (prepareOptimizedReturn(ReturnAtPlus1)) return (id)this; @@ -903,8 +959,6 @@ objc_object::rootAutorelease() inline bool objc_object::rootTryRetain() { - assert(!UseGC); - if (isTaggedPointer()) return true; return sidetable_tryRetain(); } @@ -913,8 +967,6 @@ objc_object::rootTryRetain() inline uintptr_t objc_object::rootRetainCount() { - assert(!UseGC); - if (isTaggedPointer()) return (uintptr_t)this; return sidetable_retainCount(); } @@ -972,9 +1024,10 @@ objc_object::rootRetainCount() Caller sees the TLS, clears it, and accepts the result at +1 as-is. The callee's recognition of the optimized caller is architecture-dependent. - i386 and x86_64: Callee looks for `mov rax, rdi` followed by a call or + x86_64: Callee looks for `mov rax, rdi` followed by a call or jump instruction to objc_retainAutoreleasedReturnValue or objc_unsafeClaimAutoreleasedReturnValue. + i386: Callee looks for a magic nop `movl %ebp, %ebp` (frame pointer register) armv7: Callee looks for a magic nop `mov r7, r7` (frame pointer register). arm64: Callee looks for a magic nop `mov x29, x29` (frame pointer register). @@ -989,8 +1042,8 @@ static ALWAYS_INLINE bool callerAcceptsOptimizedReturn(const void * const ra0) { const uint8_t *ra1 = (const uint8_t *)ra0; - const uint16_t *ra2; - const uint32_t *ra4 = (const uint32_t *)ra1; + const unaligned_uint16_t *ra2; + const unaligned_uint32_t *ra4 = (const unaligned_uint32_t *)ra1; const void **sym; #define PREFER_GOTPCREL 0 @@ -1010,14 +1063,14 @@ callerAcceptsOptimizedReturn(const void * const ra0) if (*ra4 != 0xe8c78948) { return false; } - ra1 += (long)*(const int32_t *)(ra1 + 4) + 8l; - ra2 = (const uint16_t *)ra1; + ra1 += (long)*(const unaligned_int32_t *)(ra1 + 4) + 8l; + ra2 = (const unaligned_uint16_t *)ra1; // ff 25 jmpq *symbol@DYLDMAGIC(%rip) if (*ra2 != 0x25ff) { return false; } #endif - ra1 += 6l + (long)*(const int32_t *)(ra1 + 2); + ra1 += 6l + (long)*(const unaligned_int32_t *)(ra1 + 2); sym = (const void **)ra1; if (*sym != objc_retainAutoreleasedReturnValue && *sym != objc_unsafeClaimAutoreleasedReturnValue) @@ -1038,12 +1091,14 @@ callerAcceptsOptimizedReturn(const void *ra) if ((uintptr_t)ra & 1) { // 3f 46 mov r7, r7 // we mask off the low bit via subtraction + // 16-bit instructions are well-aligned if (*(uint16_t *)((uint8_t *)ra - 1) == 0x463f) { return true; } } else { // 07 70 a0 e1 mov r7, r7 - if (*(uint32_t *)ra == 0xe1a07007) { + // 32-bit instructions may be only 16-bit aligned + if (*(unaligned_uint32_t *)ra == 0xe1a07007) { return true; } } @@ -1057,6 +1112,7 @@ static ALWAYS_INLINE bool callerAcceptsOptimizedReturn(const void *ra) { // fd 03 1d aa mov fp, fp + // arm64 instructions are well-aligned if (*(uint32_t *)ra == 0xaa1d03fd) { return true; } @@ -1064,15 +1120,19 @@ callerAcceptsOptimizedReturn(const void *ra) } // __arm64__ -# elif __i386__ && TARGET_IPHONE_SIMULATOR +# elif __i386__ -static inline bool +static ALWAYS_INLINE bool callerAcceptsOptimizedReturn(const void *ra) { + // 89 ed movl %ebp, %ebp + if (*(unaligned_uint16_t *)ra == 0xed89) { + return true; + } return false; } -// __i386__ && TARGET_IPHONE_SIMULATOR +// __i386__ # else #warning unknown architecture diff --git a/runtime/objc-opt.mm b/runtime/objc-opt.mm index cc168e1..19533df 100644 --- a/runtime/objc-opt.mm +++ b/runtime/objc-opt.mm @@ -39,6 +39,11 @@ bool isPreoptimized(void) return false; } +bool noMissingWeakSuperclasses(void) +{ + return false; +} + bool header_info::isPreoptimized() const { return false; @@ -70,6 +75,11 @@ Class getPreoptimizedClass(const char *name) return nil; } +header_info_rw *getPreoptimizedHeaderRW(const struct header_info *const hdr) +{ + return nil; +} + void preopt_init(void) { disableSharedCacheOptimizations(); @@ -90,7 +100,8 @@ void preopt_init(void) using objc_opt::objc_stringhash_offset_t; using objc_opt::objc_protocolopt_t; using objc_opt::objc_clsopt_t; -using objc_opt::objc_headeropt_t; +using objc_opt::objc_headeropt_ro_t; +using objc_opt::objc_headeropt_rw_t; using objc_opt::objc_opt_t; __BEGIN_DECLS @@ -104,12 +115,26 @@ void preopt_init(void) extern const objc_opt_t _objc_opt_data; // in __TEXT, __objc_opt_ro +/*********************************************************************** +* Return YES if we have a valid optimized shared cache. +**********************************************************************/ bool isPreoptimized(void) { return preoptimized; } +/*********************************************************************** +* Return YES if the shared cache does not have any classes with +* missing weak superclasses. +**********************************************************************/ +bool noMissingWeakSuperclasses(void) +{ + if (!preoptimized) return NO; // might have missing weak superclasses + return opt->flags & objc_opt::NoMissingWeakSuperclasses; +} + + /*********************************************************************** * Return YES if this image's dyld shared cache optimizations are valid. **********************************************************************/ @@ -119,7 +144,7 @@ bool isPreoptimized(void) if (!preoptimized) return NO; // image not from shared cache, or not fixed inside shared cache - if (!_objcHeaderOptimizedByDyld(this)) return NO; + if (!info()->optimizedByDyld()) return NO; return YES; } @@ -211,7 +236,7 @@ Class getPreoptimizedClass(const char *name) } namespace objc_opt { -struct objc_headeropt_t { +struct objc_headeropt_ro_t { uint32_t count; uint32_t entsize; header_info headers[0]; // sorted by mhdr address @@ -225,15 +250,15 @@ Class getPreoptimizedClass(const char *name) while (start <= end) { int32_t i = (start+end)/2; header_info *hi = headers+i; - if (mhdr == hi->mhdr) return hi; - else if (mhdr < hi->mhdr) end = i-1; + if (mhdr == hi->mhdr()) return hi; + else if (mhdr < hi->mhdr()) end = i-1; else start = i+1; } #if DEBUG for (uint32_t i = 0; i < count; i++) { header_info *hi = headers+i; - if (mhdr == hi->mhdr) { + if (mhdr == hi->mhdr()) { _objc_fatal("failed to find header %p (%d/%d)", mhdr, i, count); } @@ -243,17 +268,47 @@ Class getPreoptimizedClass(const char *name) return nil; } }; + +struct objc_headeropt_rw_t { + uint32_t count; + uint32_t entsize; + header_info_rw headers[0]; // sorted by mhdr address +}; }; header_info *preoptimizedHinfoForHeader(const headerType *mhdr) { - objc_headeropt_t *hinfos = opt ? opt->headeropt() : nil; +#if !__OBJC2__ + // fixme old ABI shared cache doesn't prepare these properly + return nil; +#endif + + objc_headeropt_ro_t *hinfos = opt ? opt->headeropt_ro() : nil; if (hinfos) return hinfos->get(mhdr); else return nil; } +header_info_rw *getPreoptimizedHeaderRW(const struct header_info *const hdr) +{ +#if !__OBJC2__ + // fixme old ABI shared cache doesn't prepare these properly + return nil; +#endif + + objc_headeropt_ro_t *hinfoRO = opt ? opt->headeropt_ro() : nil; + objc_headeropt_rw_t *hinfoRW = opt ? opt->headeropt_rw() : nil; + if (!hinfoRO || !hinfoRW) { + _objc_fatal("preoptimized header_info missing for %s (%p %p %p)", + hdr->fname(), hdr, hinfoRO, hinfoRW); + } + int32_t index = (int32_t)(hdr - hinfoRO->headers); + assert(hinfoRW->entsize == sizeof(header_info_rw)); + return &hinfoRW->headers[index]; +} + + void preopt_init(void) { // `opt` not set at compile time in order to detect too-early usage @@ -272,18 +327,10 @@ void preopt_init(void) _objc_fatal("bad objc preopt version (want %d, got %d)", objc_opt::VERSION, opt->version); } - else if (!opt->selopt() || !opt->headeropt()) { + else if (!opt->selopt() || !opt->headeropt_ro()) { // One of the tables is missing. failure = "(dyld shared cache is absent or out of date)"; } -#if SUPPORT_IGNORED_SELECTOR_CONSTANT - else if (UseGC) { - // GC is on, which renames some selectors - // Non-selector optimizations are still valid, but we don't have - // any of those yet - failure = "(GC is on)"; - } -#endif if (failure) { // All preoptimized selector references are invalid. diff --git a/runtime/objc-os.h b/runtime/objc-os.h index c43ccbb..904184f 100644 --- a/runtime/objc-os.h +++ b/runtime/objc-os.h @@ -63,6 +63,8 @@ class nocopy_t { #if TARGET_OS_MAC +# define OS_UNFAIR_LOCK_INLINE 1 + # ifndef __STDC_LIMIT_MACROS # define __STDC_LIMIT_MACROS # endif @@ -89,6 +91,7 @@ class nocopy_t { # include # include # include +# include # include # include # include @@ -113,6 +116,8 @@ void vsyslog(int, const char *, va_list) UNAVAILABLE_ATTRIBUTE; #define ALWAYS_INLINE inline __attribute__((always_inline)) #define NEVER_INLINE inline __attribute__((noinline)) +#define fastpath(x) (__builtin_expect(bool(x), 1)) +#define slowpath(x) (__builtin_expect(bool(x), 0)) static ALWAYS_INLINE uintptr_t @@ -164,6 +169,14 @@ StoreReleaseExclusive(uintptr_t *dst, uintptr_t oldvalue __unused, uintptr_t val return !result; } +static ALWAYS_INLINE +void +ClearExclusive(uintptr_t *dst) +{ + // pretend it writes to *dst for instruction ordering purposes + asm("clrex" : "=m" (*dst)); +} + #elif __arm__ @@ -190,6 +203,12 @@ StoreReleaseExclusive(uintptr_t *dst, uintptr_t oldvalue, uintptr_t value) (void **)dst); } +static ALWAYS_INLINE +void +ClearExclusive(uintptr_t *dst __unused) +{ +} + #elif __x86_64__ || __i386__ @@ -215,40 +234,18 @@ StoreReleaseExclusive(uintptr_t *dst, uintptr_t oldvalue, uintptr_t value) return StoreExclusive(dst, oldvalue, value); } +static ALWAYS_INLINE +void +ClearExclusive(uintptr_t *dst __unused) +{ +} + + #else # error unknown architecture #endif -class spinlock_t { - os_lock_handoff_s mLock; - public: - spinlock_t() : mLock(OS_LOCK_HANDOFF_INIT) { } - - void lock() { os_lock_lock(&mLock); } - void unlock() { os_lock_unlock(&mLock); } - bool trylock() { return os_lock_trylock(&mLock); } - - - // Address-ordered lock discipline for a pair of locks. - - static void lockTwo(spinlock_t *lock1, spinlock_t *lock2) { - if (lock1 > lock2) { - lock1->lock(); - lock2->lock(); - } else { - lock2->lock(); - if (lock2 != lock1) lock1->lock(); - } - } - - static void unlockTwo(spinlock_t *lock1, spinlock_t *lock2) { - lock1->unlock(); - if (lock2 != lock1) lock2->unlock(); - } -}; - - #if !TARGET_OS_IPHONE # include #else @@ -256,7 +253,6 @@ class spinlock_t { __BEGIN_DECLS extern const char *CRSetCrashLogMessage(const char *msg); extern const char *CRGetCrashLogMessage(void); - extern const char *CRSetCrashLogMessage2(const char *msg); __END_DECLS #endif @@ -333,7 +329,11 @@ class spinlock_t { #include #include -extern void _objc_fatal(const char *fmt, ...) __attribute__((noreturn, format (printf, 1, 2))); +extern void _objc_fatal(const char *fmt, ...) + __attribute__((noreturn, format (printf, 1, 2))); +extern void _objc_fatal_with_reason(uint64_t reason, uint64_t flags, + const char *fmt, ...) + __attribute__((noreturn, format (printf, 3, 4))); #define INIT_ONCE_PTR(var, create, delete) \ do { \ @@ -789,45 +789,33 @@ template class monitor_tt; template class rwlock_tt; template class recursive_mutex_tt; +using spinlock_t = mutex_tt; +using mutex_t = mutex_tt; +using monitor_t = monitor_tt; +using rwlock_t = rwlock_tt; +using recursive_mutex_t = recursive_mutex_tt; + #include "objc-lockdebug.h" template class mutex_tt : nocopy_t { - pthread_mutex_t mLock; - - public: - mutex_tt() : mLock(PTHREAD_MUTEX_INITIALIZER) { } + os_unfair_lock mLock; + public: + mutex_tt() : mLock(OS_UNFAIR_LOCK_INIT) { } - void lock() - { + void lock() { lockdebug_mutex_lock(this); - int err = pthread_mutex_lock(&mLock); - if (err) _objc_fatal("pthread_mutex_lock failed (%d)", err); - } - - bool tryLock() - { - int err = pthread_mutex_trylock(&mLock); - if (err == 0) { - lockdebug_mutex_try_lock_success(this); - return true; - } else if (err == EBUSY) { - return false; - } else { - _objc_fatal("pthread_mutex_trylock failed (%d)", err); - } + os_unfair_lock_lock_with_options_inline + (&mLock, OS_UNFAIR_LOCK_DATA_SYNCHRONIZATION); } - void unlock() - { + void unlock() { lockdebug_mutex_unlock(this); - int err = pthread_mutex_unlock(&mLock); - if (err) _objc_fatal("pthread_mutex_unlock failed (%d)", err); + os_unfair_lock_unlock_inline(&mLock); } - void assertLocked() { lockdebug_mutex_assert_locked(this); } @@ -835,9 +823,25 @@ class mutex_tt : nocopy_t { void assertUnlocked() { lockdebug_mutex_assert_unlocked(this); } -}; -using mutex_t = mutex_tt; + + // Address-ordered lock discipline for a pair of locks. + + static void lockTwo(mutex_tt *lock1, mutex_tt *lock2) { + if (lock1 > lock2) { + lock1->lock(); + lock2->lock(); + } else { + lock2->lock(); + if (lock2 != lock1) lock1->lock(); + } + } + + static void unlockTwo(mutex_tt *lock1, mutex_tt *lock2) { + lock1->unlock(); + if (lock2 != lock1) lock2->unlock(); + } +}; template @@ -855,20 +859,6 @@ class recursive_mutex_tt : nocopy_t { if (err) _objc_fatal("pthread_mutex_lock failed (%d)", err); } - bool tryLock() - { - int err = pthread_mutex_trylock(&mLock); - if (err == 0) { - lockdebug_recursive_mutex_lock(this); - return true; - } else if (err == EBUSY) { - return false; - } else { - _objc_fatal("pthread_mutex_trylock failed (%d)", err); - } - } - - void unlock() { lockdebug_recursive_mutex_unlock(this); @@ -900,8 +890,6 @@ class recursive_mutex_tt : nocopy_t { } }; -using recursive_mutex_t = recursive_mutex_tt; - template class monitor_tt { @@ -959,8 +947,6 @@ class monitor_tt { } }; -using monitor_t = monitor_tt; - // semaphore_create formatted for INIT_ONCE use static inline semaphore_t create_semaphore(void) @@ -1118,8 +1104,6 @@ class rwlock_tt : nocopy_t { } }; -using rwlock_t = rwlock_tt; - #ifndef __LP64__ typedef struct mach_header headerType; @@ -1130,7 +1114,7 @@ typedef struct mach_header_64 headerType; typedef struct segment_command_64 segmentType; typedef struct section_64 sectionType; #endif -#define headerIsBundle(hi) (hi->mhdr->filetype == MH_BUNDLE) +#define headerIsBundle(hi) (hi->mhdr()->filetype == MH_BUNDLE) #define libobjc_header ((headerType *)&_mh_dylib_header) // Prototypes @@ -1156,19 +1140,28 @@ memdup(const void *mem, size_t len) return dup; } -// unsigned strdup -static inline uint8_t * -ustrdup(const uint8_t *str) +// strdup that doesn't copy read-only memory +static inline char * +strdupIfMutable(const char *str) { - return (uint8_t *)strdup((char *)str); + size_t size = strlen(str) + 1; + if (_dyld_is_memory_immutable(str, size)) { + return (char *)str; + } else { + return (char *)memdup(str, size); + } } -// nil-checking strdup -static inline uint8_t * -strdupMaybeNil(const uint8_t *str) +// free strdupIfMutable() result +static inline void +freeIfMutable(char *str) { - if (!str) return nil; - return (uint8_t *)strdup((char *)str); + size_t size = strlen(str) + 1; + if (_dyld_is_memory_immutable(str, size)) { + // nothing + } else { + free(str); + } } // nil-checking unsigned strdup @@ -1176,7 +1169,57 @@ static inline uint8_t * ustrdupMaybeNil(const uint8_t *str) { if (!str) return nil; - return (uint8_t *)strdup((char *)str); + return (uint8_t *)strdupIfMutable((char *)str); } +// OS version checking: +// +// sdkVersion() +// DYLD_OS_VERSION(mac, ios, tv, watch) +// sdkIsOlderThan(mac, ios, tv, watch) +// sdkIsAtLeast(mac, ios, tv, watch) +// +// This version order matches OBJC_AVAILABLE. + +#if TARGET_OS_OSX +# define DYLD_OS_VERSION(x, i, t, w) DYLD_MACOSX_VERSION_##x +# define sdkVersion() dyld_get_program_sdk_version() + +#elif TARGET_OS_IOS +# define DYLD_OS_VERSION(x, i, t, w) DYLD_IOS_VERSION_##i +# define sdkVersion() dyld_get_program_sdk_version() + +#elif TARGET_OS_TV + // dyld does not currently have distinct constants for tvOS +# define DYLD_OS_VERSION(x, i, t, w) DYLD_IOS_VERSION_##t +# define sdkVersion() dyld_get_program_sdk_version() + +#elif TARGET_OS_WATCH +# define DYLD_OS_VERSION(x, i, t, w) DYLD_WATCHOS_VERSION_##w + // watchOS has its own API for compatibility reasons +# define sdkVersion() dyld_get_program_sdk_watch_os_version() + +#else +# error unknown OS +#endif + + +#define sdkIsOlderThan(x, i, t, w) \ + (sdkVersion() < DYLD_OS_VERSION(x, i, t, w)) +#define sdkIsAtLeast(x, i, t, w) \ + (sdkVersion() >= DYLD_OS_VERSION(x, i, t, w)) + +// Allow bare 0 to be used in DYLD_OS_VERSION() and sdkIsOlderThan() +#define DYLD_MACOSX_VERSION_0 0 +#define DYLD_IOS_VERSION_0 0 +#define DYLD_TVOS_VERSION_0 0 +#define DYLD_WATCHOS_VERSION_0 0 + +// Pretty-print a DYLD_*_VERSION_* constant. +#define SDK_FORMAT "%hu.%hhu.%hhu" +#define FORMAT_SDK(v) \ + (unsigned short)(((uint32_t)(v))>>16), \ + (unsigned char)(((uint32_t)(v))>>8), \ + (unsigned char)(((uint32_t)(v))>>0) + #endif diff --git a/runtime/objc-os.mm b/runtime/objc-os.mm index 0dcea10..cb077e5 100644 --- a/runtime/objc-os.mm +++ b/runtime/objc-os.mm @@ -101,7 +101,7 @@ WINBOOL APIENTRY DllMain( HMODULE hModule, environ_init(); tls_init(); lock_init(); - sel_init(NO, 3500); // old selector heuristic + sel_init(3500); // old selector heuristic exception_init(); break; @@ -155,13 +155,24 @@ WINBOOL APIENTRY DllMain( HMODULE hModule, appendHeader(hi); if (PrintImages) { - _objc_inform("IMAGES: loading image for %s%s%s\n", - hi->fname, - headerIsBundle(hi) ? " (bundle)" : "", - _objcHeaderIsReplacement(hi) ? " (replacement)":""); + _objc_inform("IMAGES: loading image for %s%s%s%s\n", + hi->fname, + headerIsBundle(hi) ? " (bundle)" : "", + hi->info->isReplacement() ? " (replacement)":"", + hi->info->hasCategoryClassProperties() ? " (has class properties)":""); + } + + // Count classes. Size various table based on the total. + int total = 0; + int unoptimizedTotal = 0; + { + if (_getObjc2ClassList(hi, &count)) { + total += (int)count; + if (!hi->getInSharedCache()) unoptimizedTotal += count; + } } - _read_images(&hi, 1); + _read_images(&hi, 1, total, unoptimizedTotal); return hi; } @@ -178,12 +189,6 @@ OBJC_EXPORT void _objc_unload_image(HMODULE image, header_info *hinfo) } -bool crashlog_header_name(header_info *hi) -{ - return true; -} - - // TARGET_OS_WIN32 #elif TARGET_OS_MAC @@ -191,6 +196,15 @@ bool crashlog_header_name(header_info *hi) #include "objc-file.h" +/*********************************************************************** +* libobjc must never run static destructors. +* Cover libc's __cxa_atexit with our own definition that runs nothing. +* rdar://21734598 ER: Compiler option to suppress C++ static destructors +**********************************************************************/ +extern "C" int __cxa_atexit(); +extern "C" int __cxa_atexit() { return 0; } + + /*********************************************************************** * bad_magic. * Return YES if the header has invalid Mach-o magic. @@ -202,48 +216,51 @@ bool bad_magic(const headerType *mhdr) } -static header_info * addHeader(const headerType *mhdr) +static header_info * addHeader(const headerType *mhdr, const char *path, int &totalClasses, int &unoptimizedTotalClasses) { header_info *hi; if (bad_magic(mhdr)) return NULL; -#if __OBJC2__ + bool inSharedCache = false; + // Look for hinfo from the dyld shared cache. hi = preoptimizedHinfoForHeader(mhdr); if (hi) { // Found an hinfo in the dyld shared cache. // Weed out duplicates. - if (hi->loaded) { + if (hi->isLoaded()) { return NULL; } + inSharedCache = true; + // Initialize fields not set by the shared cache // hi->next is set by appendHeader - hi->fname = dyld_image_path_containing_address(hi->mhdr); - hi->loaded = true; - hi->inSharedCache = true; + hi->setLoaded(true); if (PrintPreopt) { - _objc_inform("PREOPTIMIZATION: honoring preoptimized header info at %p for %s", hi, hi->fname); + _objc_inform("PREOPTIMIZATION: honoring preoptimized header info at %p for %s", hi, hi->fname()); } -# if DEBUG +#if !__OBJC2__ + _objc_fatal("shouldn't be here"); +#endif +#if DEBUG // Verify image_info size_t info_size = 0; const objc_image_info *image_info = _getObjcImageInfo(mhdr,&info_size); - assert(image_info == hi->info); -# endif + assert(image_info == hi->info()); +#endif } else -#endif { // Didn't find an hinfo in the dyld shared cache. // Weed out duplicates - for (hi = FirstHeader; hi; hi = hi->next) { - if (mhdr == hi->mhdr) return NULL; + for (hi = FirstHeader; hi; hi = hi->getNext()) { + if (mhdr == hi->mhdr()) return NULL; } // Locate the __OBJC segment @@ -254,27 +271,34 @@ bool bad_magic(const headerType *mhdr) if (!objc_segment && !image_info) return NULL; // Allocate a header_info entry. - hi = (header_info *)calloc(sizeof(header_info), 1); + // Note we also allocate space for a single header_info_rw in the + // rw_data[] inside header_info. + hi = (header_info *)calloc(sizeof(header_info) + sizeof(header_info_rw), 1); // Set up the new header_info entry. - hi->mhdr = mhdr; + hi->setmhdr(mhdr); #if !__OBJC2__ // mhdr must already be set hi->mod_count = 0; hi->mod_ptr = _getObjcModules(hi, &hi->mod_count); #endif - hi->info = image_info; - hi->fname = dyld_image_path_containing_address(hi->mhdr); - hi->loaded = true; - hi->inSharedCache = false; - hi->allClassesRealized = NO; + // Install a placeholder image_info if absent to simplify code elsewhere + static const objc_image_info emptyInfo = {0, 0}; + hi->setinfo(image_info ?: &emptyInfo); + + hi->setLoaded(true); + hi->setAllClassesRealized(NO); } - // dylibs are not allowed to unload - // ...except those with image_info and nothing else (5359412) - if (hi->mhdr->filetype == MH_DYLIB && _hasObjcContents(hi)) { - dlopen(hi->fname, RTLD_NOLOAD); +#if __OBJC2__ + { + size_t count = 0; + if (_getObjc2ClassList(hi, &count)) { + totalClasses += (int)count; + if (!inSharedCache) unoptimizedTotalClasses += count; + } } +#endif appendHeader(hi); @@ -282,43 +306,6 @@ bool bad_magic(const headerType *mhdr) } -#if !SUPPORT_GC - -const char *_gcForHInfo(const header_info *hinfo) -{ - return ""; -} -const char *_gcForHInfo2(const header_info *hinfo) -{ - return ""; -} - -#else - -/*********************************************************************** -* _gcForHInfo. -**********************************************************************/ -const char *_gcForHInfo(const header_info *hinfo) -{ - if (_objcHeaderRequiresGC(hinfo)) { - return "requires GC"; - } else if (_objcHeaderSupportsGC(hinfo)) { - return "supports GC"; - } else { - return "does not support GC"; - } -} -const char *_gcForHInfo2(const header_info *hinfo) -{ - if (_objcHeaderRequiresGC(hinfo)) { - return "(requires GC)"; - } else if (_objcHeaderSupportsGC(hinfo)) { - return "(supports GC)"; - } - return ""; -} - - /*********************************************************************** * linksToLibrary * Returns true if the image links directly to a dylib whose install name @@ -330,8 +317,8 @@ bool bad_magic(const headerType *mhdr) const struct dylib_command *cmd; unsigned long i; - cmd = (const struct dylib_command *) (hi->mhdr + 1); - for (i = 0; i < hi->mhdr->ncmds; i++) { + cmd = (const struct dylib_command *) (hi->mhdr() + 1); + for (i = 0; i < hi->mhdr()->ncmds; i++) { if (cmd->cmd == LC_LOAD_DYLIB || cmd->cmd == LC_LOAD_UPWARD_DYLIB || cmd->cmd == LC_LOAD_WEAK_DYLIB || cmd->cmd == LC_REEXPORT_DYLIB) { @@ -345,213 +332,88 @@ bool bad_magic(const headerType *mhdr) } +#if SUPPORT_GC_COMPAT + /*********************************************************************** -* check_gc -* Check whether the executable supports or requires GC, and make sure -* all already-loaded libraries support the executable's GC mode. -* Returns TRUE if the executable wants GC on. +* shouldRejectGCApp +* Return YES if the executable requires GC. **********************************************************************/ -static void check_wants_gc(bool *appWantsGC) +static bool shouldRejectGCApp(const header_info *hi) { - const header_info *hi; + assert(hi->mhdr()->filetype == MH_EXECUTE); - // Environment variables can override the following. - if (DisableGC) { - _objc_inform_on_crash("GC: forcing GC OFF because OBJC_DISABLE_GC is set"); - *appWantsGC = NO; + if (!hi->info()->supportsGC()) { + // App does not use GC. Don't reject it. + return NO; } - else { - // Find the executable and check its GC bits. - // If the executable cannot be found, default to NO. - // (The executable will not be found if the executable contains - // no Objective-C code.) - *appWantsGC = NO; - for (hi = FirstHeader; hi != NULL; hi = hi->next) { - if (hi->mhdr->filetype == MH_EXECUTE) { - *appWantsGC = _objcHeaderSupportsGC(hi); - - if (PrintGC) { - _objc_inform("GC: executable '%s' %s", - hi->fname, _gcForHInfo(hi)); - } - - if (*appWantsGC) { - // Exception: AppleScriptObjC apps run without GC in 10.9+ - // 1. executable defines no classes - // 2. executable references NSBundle only - // 3. executable links to AppleScriptObjC.framework - size_t classcount = 0; - size_t refcount = 0; + + // Exception: Trivial AppleScriptObjC apps can run without GC. + // 1. executable defines no classes + // 2. executable references NSBundle only + // 3. executable links to AppleScriptObjC.framework + // Note that objc_appRequiresGC() also knows about this. + size_t classcount = 0; + size_t refcount = 0; #if __OBJC2__ - _getObjc2ClassList(hi, &classcount); - _getObjc2ClassRefs(hi, &refcount); + _getObjc2ClassList(hi, &classcount); + _getObjc2ClassRefs(hi, &refcount); #else - if (hi->mod_count == 0 || (hi->mod_count == 1 && !hi->mod_ptr[0].symtab)) classcount = 0; - else classcount = 1; - _getObjcClassRefs(hi, &refcount); + if (hi->mod_count == 0 || (hi->mod_count == 1 && !hi->mod_ptr[0].symtab)) classcount = 0; + else classcount = 1; + _getObjcClassRefs(hi, &refcount); #endif - if (classcount == 0 && refcount == 1 && - linksToLibrary(hi, "/System/Library/Frameworks" - "/AppleScriptObjC.framework/Versions/A" - "/AppleScriptObjC")) - { - *appWantsGC = NO; - if (PrintGC) { - _objc_inform("GC: forcing GC OFF because this is " - "a trivial AppleScriptObjC app"); - } - } - } - } - } - } -} - - -/*********************************************************************** -* verify_gc_readiness -* if we want gc, verify that every header describes files compiled -* and presumably ready for gc. -************************************************************************/ -static void verify_gc_readiness(bool wantsGC, - header_info **hList, uint32_t hCount) -{ - bool busted = NO; - uint32_t i; - - // Find the libraries and check their GC bits against the app's request - for (i = 0; i < hCount; i++) { - header_info *hi = hList[i]; - if (hi->mhdr->filetype == MH_EXECUTE) { - continue; - } - else if (hi->mhdr == &_mh_dylib_header) { - // libobjc itself works with anything even though it is not - // compiled with -fobjc-gc (fixme should it be?) - } - else if (wantsGC && ! _objcHeaderSupportsGC(hi)) { - // App wants GC but library does not support it - bad - _objc_inform_now_and_on_crash - ("'%s' was not compiled with -fobjc-gc or -fobjc-gc-only, " - "but the application requires GC", - hi->fname); - busted = YES; - } - else if (!wantsGC && _objcHeaderRequiresGC(hi)) { - // App doesn't want GC but library requires it - bad - _objc_inform_now_and_on_crash - ("'%s' was compiled with -fobjc-gc-only, " - "but the application does not support GC", - hi->fname); - busted = YES; - } - - if (PrintGC) { - _objc_inform("GC: library '%s' %s", - hi->fname, _gcForHInfo(hi)); - } - } - - if (busted) { - // GC state is not consistent. - // Kill the process unless one of the forcing flags is set. - if (!DisableGC) { - _objc_fatal("*** GC capability of application and some libraries did not match"); - } + if (classcount == 0 && refcount == 1 && + linksToLibrary(hi, "/System/Library/Frameworks" + "/AppleScriptObjC.framework/Versions/A" + "/AppleScriptObjC")) + { + // It's AppleScriptObjC. Don't reject it. + return NO; + } + else { + // GC and not trivial AppleScriptObjC. Reject it. + return YES; } } /*********************************************************************** -* gc_enforcer -* Make sure that images about to be loaded by dyld are GC-acceptable. -* Images linked to the executable are always permitted; they are -* enforced inside map_images() itself. +* rejectGCImage +* Halt if an image requires GC. +* Testing of the main executable should use rejectGCApp() instead. **********************************************************************/ -static bool InitialDyldRegistration = NO; -static const char *gc_enforcer(enum dyld_image_states state, - uint32_t infoCount, - const struct dyld_image_info info[]) +static bool shouldRejectGCImage(const headerType *mhdr) { - uint32_t i; - - // Linked images get a free pass - if (InitialDyldRegistration) return NULL; - - if (PrintImages) { - _objc_inform("IMAGES: checking %d images for compatibility...", - infoCount); - } - - for (i = 0; i < infoCount; i++) { - crashlog_header_name_string(info[i].imageFilePath); - - const headerType *mhdr = (const headerType *)info[i].imageLoadAddress; - if (bad_magic(mhdr)) continue; - - objc_image_info *image_info; - size_t size; - - if (mhdr == &_mh_dylib_header) { - // libobjc itself - OK - continue; - } + assert(mhdr->filetype != MH_EXECUTE); + objc_image_info *image_info; + size_t size; + #if !__OBJC2__ - unsigned long seg_size; - // 32-bit: __OBJC seg but no image_info means no GC support - if (!getsegmentdata(mhdr, "__OBJC", &seg_size)) { - // not objc - assume OK - continue; - } - image_info = _getObjcImageInfo(mhdr, &size); - if (!image_info) { - // No image_info - assume GC unsupported - if (!UseGC) { - // GC is OFF - ok - continue; - } else { - // GC is ON - bad - if (PrintImages || PrintGC) { - _objc_inform("IMAGES: rejecting %d images because %s doesn't support GC (no image_info)", infoCount, info[i].imageFilePath); - } - goto reject; - } - } + unsigned long seg_size; + // 32-bit: __OBJC seg but no image_info means no GC support + if (!getsegmentdata(mhdr, "__OBJC", &seg_size)) { + // Not objc, therefore not GC. Don't reject it. + return NO; + } + image_info = _getObjcImageInfo(mhdr, &size); + if (!image_info) { + // No image_info, therefore not GC. Don't reject it. + return NO; + } #else - // 64-bit: no image_info means no objc at all - image_info = _getObjcImageInfo(mhdr, &size); - if (!image_info) { - // not objc - assume OK - continue; - } -#endif - - if (UseGC && !_objcInfoSupportsGC(image_info)) { - // GC is ON, but image does not support GC - if (PrintImages || PrintGC) { - _objc_inform("IMAGES: rejecting %d images because %s doesn't support GC", infoCount, info[i].imageFilePath); - } - goto reject; - } - if (!UseGC && _objcInfoRequiresGC(image_info)) { - // GC is OFF, but image requires GC - if (PrintImages || PrintGC) { - _objc_inform("IMAGES: rejecting %d images because %s requires GC", infoCount, info[i].imageFilePath); - } - goto reject; - } + // 64-bit: no image_info means no objc at all + image_info = _getObjcImageInfo(mhdr, &size); + if (!image_info) { + // Not objc, therefore not GC. Don't reject it. + return NO; } +#endif - crashlog_header_name_string(NULL); - return NULL; - - reject: - crashlog_header_name_string(NULL); - return "GC capability mismatch"; + return image_info->requiresGC(); } -// SUPPORT_GC +// SUPPORT_GC_COMPAT #endif @@ -572,15 +434,12 @@ static void verify_gc_readiness(bool wantsGC, #include "objc-file-old.h" #endif -const char * -map_images_nolock(enum dyld_image_states state, uint32_t infoCount, - const struct dyld_image_info infoList[]) +void +map_images_nolock(unsigned mhCount, const char * const mhPaths[], + const struct mach_header * const mhdrs[]) { static bool firstTime = YES; - static bool wantsGC = NO; - uint32_t i; - header_info *hi; - header_info *hList[infoCount]; + header_info *hList[mhCount]; uint32_t hCount; size_t selrefCount = 0; @@ -589,53 +448,64 @@ static void verify_gc_readiness(bool wantsGC, // fixme defer initialization until an objc-using image is found? if (firstTime) { preopt_init(); -#if SUPPORT_GC - InitialDyldRegistration = YES; - dyld_register_image_state_change_handler(dyld_image_state_mapped, 0 /* batch */, &gc_enforcer); - InitialDyldRegistration = NO; -#endif } if (PrintImages) { - _objc_inform("IMAGES: processing %u newly-mapped images...\n", infoCount); + _objc_inform("IMAGES: processing %u newly-mapped images...\n", mhCount); } // Find all images with Objective-C metadata. hCount = 0; - i = infoCount; - while (i--) { - const headerType *mhdr = (headerType *)infoList[i].imageLoadAddress; - - hi = addHeader(mhdr); - if (!hi) { - // no objc data in this entry - continue; - } - if (mhdr->filetype == MH_EXECUTE) { - // Size some data structures based on main executable's size + // Count classes. Size various table based on the total. + int totalClasses = 0; + int unoptimizedTotalClasses = 0; + { + uint32_t i = mhCount; + while (i--) { + const headerType *mhdr = (const headerType *)mhdrs[i]; + + auto hi = addHeader(mhdr, mhPaths[i], totalClasses, unoptimizedTotalClasses); + if (!hi) { + // no objc data in this entry + continue; + } + + if (mhdr->filetype == MH_EXECUTE) { + // Size some data structures based on main executable's size #if __OBJC2__ - size_t count; - _getObjc2SelectorRefs(hi, &count); - selrefCount += count; - _getObjc2MessageRefs(hi, &count); - selrefCount += count; + size_t count; + _getObjc2SelectorRefs(hi, &count); + selrefCount += count; + _getObjc2MessageRefs(hi, &count); + selrefCount += count; #else - _getObjcSelectorRefs(hi, &selrefCount); + _getObjcSelectorRefs(hi, &selrefCount); #endif - } - - hList[hCount++] = hi; - - - if (PrintImages) { - _objc_inform("IMAGES: loading image for %s%s%s%s%s\n", - hi->fname, - mhdr->filetype == MH_BUNDLE ? " (bundle)" : "", - _objcHeaderIsReplacement(hi) ? " (replacement)" : "", - _objcHeaderOptimizedByDyld(hi)?" (preoptimized)" : "", - _gcForHInfo2(hi)); + +#if SUPPORT_GC_COMPAT + // Halt if this is a GC app. + if (shouldRejectGCApp(hi)) { + _objc_fatal_with_reason + (OBJC_EXIT_REASON_GC_NOT_SUPPORTED, + OS_REASON_FLAG_CONSISTENT_FAILURE, + "Objective-C garbage collection " + "is no longer supported."); + } +#endif + } + + hList[hCount++] = hi; + + if (PrintImages) { + _objc_inform("IMAGES: loading image for %s%s%s%s%s\n", + hi->fname(), + mhdr->filetype == MH_BUNDLE ? " (bundle)" : "", + hi->info()->isReplacement() ? " (replacement)" : "", + hi->info()->hasCategoryClassProperties() ? " (has class properties)" : "", + hi->info()->optimizedByDyld()?" (preoptimized)":""); + } } } @@ -644,80 +514,35 @@ static void verify_gc_readiness(bool wantsGC, // further initialization. // (The executable may not be present in this infoList if the // executable does not contain Objective-C code but Objective-C - // is dynamically loaded later. In that case, check_wants_gc() - // will do the right thing.) -#if SUPPORT_GC + // is dynamically loaded later. if (firstTime) { - check_wants_gc(&wantsGC); - - verify_gc_readiness(wantsGC, hList, hCount); - - gc_init(wantsGC); // needs executable for GC decision - } else { - verify_gc_readiness(wantsGC, hList, hCount); - } - - if (wantsGC) { - // tell the collector about the data segment ranges. - for (i = 0; i < hCount; ++i) { - uint8_t *seg; - unsigned long seg_size; - hi = hList[i]; - - seg = getsegmentdata(hi->mhdr, "__DATA", &seg_size); - if (seg) gc_register_datasegment((uintptr_t)seg, seg_size); - - seg = getsegmentdata(hi->mhdr, "__DATA_CONST", &seg_size); - if (seg) gc_register_datasegment((uintptr_t)seg, seg_size); - - seg = getsegmentdata(hi->mhdr, "__DATA_DIRTY", &seg_size); - if (seg) gc_register_datasegment((uintptr_t)seg, seg_size); + sel_init(selrefCount); + arr_init(); - seg = getsegmentdata(hi->mhdr, "__OBJC", &seg_size); - if (seg) gc_register_datasegment((uintptr_t)seg, seg_size); - // __OBJC contains no GC data, but pointers to it are - // used as associated reference values (rdar://6953570) +#if SUPPORT_GC_COMPAT + // Reject any GC images linked to the main executable. + // We already rejected the app itself above. + // Images loaded after launch will be rejected by dyld. + + for (uint32_t i = 0; i < hCount; i++) { + auto hi = hList[i]; + auto mh = hi->mhdr(); + if (mh->filetype != MH_EXECUTE && shouldRejectGCImage(mh)) { + _objc_fatal_with_reason + (OBJC_EXIT_REASON_GC_NOT_SUPPORTED, + OS_REASON_FLAG_CONSISTENT_FAILURE, + "%s requires Objective-C garbage collection " + "which is no longer supported.", hi->fname()); + } } - } #endif - - if (firstTime) { - sel_init(wantsGC, selrefCount); - arr_init(); } - _read_images(hList, hCount); - - firstTime = NO; - - return NULL; -} - - -/*********************************************************************** -* load_images_nolock -* Prepares +load in the given images which are being mapped in by dyld. -* Returns YES if there are now +load methods to be called by call_load_methods. -* -* Locking: loadMethodLock(both) and runtimeLock(new) acquired by load_images -**********************************************************************/ -bool -load_images_nolock(enum dyld_image_states state,uint32_t infoCount, - const struct dyld_image_info infoList[]) -{ - bool found = NO; - uint32_t i; - - i = infoCount; - while (i--) { - const headerType *mhdr = (headerType*)infoList[i].imageLoadAddress; - if (!hasLoadMethods(mhdr)) continue; - - prepare_load_methods(mhdr); - found = YES; + if (hCount > 0) { + _read_images(hList, hCount, totalClasses, unoptimizedTotalClasses); } - return found; + firstTime = NO; } @@ -739,40 +564,20 @@ static void verify_gc_readiness(bool wantsGC, header_info *hi; // Find the runtime's header_info struct for the image - for (hi = FirstHeader; hi != NULL; hi = hi->next) { - if (hi->mhdr == (const headerType *)mh) { + for (hi = FirstHeader; hi != NULL; hi = hi->getNext()) { + if (hi->mhdr() == (const headerType *)mh) { break; } } if (!hi) return; - if (PrintImages) { - _objc_inform("IMAGES: unloading image for %s%s%s%s\n", - hi->fname, - hi->mhdr->filetype == MH_BUNDLE ? " (bundle)" : "", - _objcHeaderIsReplacement(hi) ? " (replacement)" : "", - _gcForHInfo2(hi)); - } - -#if SUPPORT_GC - if (UseGC) { - uint8_t *seg; - unsigned long seg_size; - - seg = getsegmentdata(hi->mhdr, "__DATA", &seg_size); - if (seg) gc_unregister_datasegment((uintptr_t)seg, seg_size); - - seg = getsegmentdata(hi->mhdr, "__DATA_CONST", &seg_size); - if (seg) gc_unregister_datasegment((uintptr_t)seg, seg_size); - - seg = getsegmentdata(hi->mhdr, "__DATA_DIRTY", &seg_size); - if (seg) gc_unregister_datasegment((uintptr_t)seg, seg_size); - - seg = getsegmentdata(hi->mhdr, "__OBJC", &seg_size); - if (seg) gc_unregister_datasegment((uintptr_t)seg, seg_size); + if (PrintImages) { + _objc_inform("IMAGES: unloading image for %s%s%s\n", + hi->fname(), + hi->mhdr()->filetype == MH_BUNDLE ? " (bundle)" : "", + hi->info()->isReplacement() ? " (replacement)" : ""); } -#endif _unload_image(hi); @@ -790,26 +595,20 @@ static void verify_gc_readiness(bool wantsGC, **********************************************************************/ static void static_init() { -#if __OBJC2__ size_t count; Initializer *inits = getLibobjcInitializers(&_mh_dylib_header, &count); for (size_t i = 0; i < count; i++) { inits[i](); } -#endif } /*********************************************************************** * _objc_init * Bootstrap initialization. Registers our image notifier with dyld. -* Old ABI: called by dyld as a library initializer -* New ABI: called by libSystem BEFORE library initialization time +* Called by libSystem BEFORE library initialization time **********************************************************************/ -#if !__OBJC2__ -static __attribute__((constructor)) -#endif void _objc_init(void) { static bool initialized = false; @@ -822,12 +621,8 @@ void _objc_init(void) static_init(); lock_init(); exception_init(); - - // Register for unmap first, in case some +load unmaps something - _dyld_register_func_for_remove_image(&unmap_image); - dyld_register_image_state_change_handler(dyld_image_state_bound, - 1/*batch*/, &map_2_images); - dyld_register_image_state_change_handler(dyld_image_state_dependents_initialized, 0/*not batch*/, &load_images); + + _dyld_objc_notify_register(&map_2_images, load_images, unmap_image); } @@ -844,10 +639,10 @@ void _objc_init(void) #endif header_info *hi; - for (hi = FirstHeader; hi != NULL; hi = hi->next) { + for (hi = FirstHeader; hi != NULL; hi = hi->getNext()) { for (size_t i = 0; i < sizeof(segnames)/sizeof(segnames[0]); i++) { unsigned long seg_size; - uint8_t *seg = getsegmentdata(hi->mhdr, segnames[i], &seg_size); + uint8_t *seg = getsegmentdata(hi->mhdr(), segnames[i], &seg_size); if (!seg) continue; // Is the class in this header? @@ -957,18 +752,6 @@ int secure_open(const char *filename, int flags, uid_t euid) } -bool crashlog_header_name(header_info *hi) -{ - return crashlog_header_name_string(hi ? hi->fname : NULL); -} - -bool crashlog_header_name_string(const char *name) -{ - CRSetCrashLogMessage2(name); - return true; -} - - #if TARGET_OS_IPHONE const char *__crashreporter_info__ = NULL; @@ -983,12 +766,6 @@ bool crashlog_header_name_string(const char *name) return __crashreporter_info__; } -const char *CRSetCrashLogMessage2(const char *msg) -{ - // sorry - return msg; -} - #endif // TARGET_OS_MAC diff --git a/runtime/objc-private.h b/runtime/objc-private.h index 69265c1..07bf78c 100644 --- a/runtime/objc-private.h +++ b/runtime/objc-private.h @@ -56,6 +56,15 @@ namespace { }; +#if (!SUPPORT_NONPOINTER_ISA && !SUPPORT_PACKED_ISA && !SUPPORT_INDEXED_ISA) ||\ + ( SUPPORT_NONPOINTER_ISA && SUPPORT_PACKED_ISA && !SUPPORT_INDEXED_ISA) ||\ + ( SUPPORT_NONPOINTER_ISA && !SUPPORT_PACKED_ISA && SUPPORT_INDEXED_ISA) + // good config +#else +# error bad config +#endif + + union isa_t { isa_t() { } @@ -64,10 +73,10 @@ union isa_t Class cls; uintptr_t bits; -#if SUPPORT_NONPOINTER_ISA +#if SUPPORT_PACKED_ISA // extra_rc must be the MSB-most field (so it matches carry/overflow flags) - // indexed must be the LSB (fixme or get rid of it) + // nonpointer must be the LSB (fixme or get rid of it) // shiftcls must occupy the same bits that a real class pointer would // bits + RC_ONE is equivalent to extra_rc + 1 // RC_HALF is the high bit of extra_rc (i.e. half of its range) @@ -82,7 +91,7 @@ union isa_t # define ISA_MAGIC_MASK 0x000003f000000001ULL # define ISA_MAGIC_VALUE 0x000001a000000001ULL struct { - uintptr_t indexed : 1; + uintptr_t nonpointer : 1; uintptr_t has_assoc : 1; uintptr_t has_cxx_dtor : 1; uintptr_t shiftcls : 33; // MACH_VM_MAX_ADDRESS 0x1000000000 @@ -100,7 +109,7 @@ union isa_t # define ISA_MAGIC_MASK 0x001f800000000001ULL # define ISA_MAGIC_VALUE 0x001d800000000001ULL struct { - uintptr_t indexed : 1; + uintptr_t nonpointer : 1; uintptr_t has_assoc : 1; uintptr_t has_cxx_dtor : 1; uintptr_t shiftcls : 44; // MACH_VM_MAX_ADDRESS 0x7fffffe00000 @@ -114,11 +123,43 @@ union isa_t }; # else - // Available bits in isa field are architecture-specific. -# error unknown architecture +# error unknown architecture for packed isa +# endif + +// SUPPORT_PACKED_ISA +#endif + + +#if SUPPORT_INDEXED_ISA + +# if __ARM_ARCH_7K__ >= 2 + +# define ISA_INDEX_IS_NPI 1 +# define ISA_INDEX_MASK 0x0001FFFC +# define ISA_INDEX_SHIFT 2 +# define ISA_INDEX_BITS 15 +# define ISA_INDEX_COUNT (1 << ISA_INDEX_BITS) +# define ISA_INDEX_MAGIC_MASK 0x001E0001 +# define ISA_INDEX_MAGIC_VALUE 0x001C0001 + struct { + uintptr_t nonpointer : 1; + uintptr_t has_assoc : 1; + uintptr_t indexcls : 15; + uintptr_t magic : 4; + uintptr_t has_cxx_dtor : 1; + uintptr_t weakly_referenced : 1; + uintptr_t deallocating : 1; + uintptr_t has_sidetable_rc : 1; + uintptr_t extra_rc : 7; +# define RC_ONE (1ULL<<25) +# define RC_HALF (1ULL<<6) + }; + +# else +# error unknown architecture for indexed isa # endif -// SUPPORT_NONPOINTER_ISA +// SUPPORT_INDEXED_ISA #endif }; @@ -142,17 +183,19 @@ struct objc_object { // initClassIsa(): class objects // initProtocolIsa(): protocol objects // initIsa(): other objects - void initIsa(Class cls /*indexed=false*/); - void initClassIsa(Class cls /*indexed=maybe*/); - void initProtocolIsa(Class cls /*indexed=maybe*/); + void initIsa(Class cls /*nonpointer=false*/); + void initClassIsa(Class cls /*nonpointer=maybe*/); + void initProtocolIsa(Class cls /*nonpointer=maybe*/); void initInstanceIsa(Class cls, bool hasCxxDtor); // changeIsa() should be used to change the isa of existing objects. // If this is a new object, use initIsa() for performance. Class changeIsa(Class newCls); - bool hasIndexedIsa(); + bool hasNonpointerIsa(); bool isTaggedPointer(); + bool isBasicTaggedPointer(); + bool isExtTaggedPointer(); bool isClass(); // object may have associated objects? @@ -185,7 +228,7 @@ struct objc_object { void rootDealloc(); private: - void initIsa(Class newCls, bool indexed, bool hasCxxDtor); + void initIsa(Class newCls, bool nonpointer, bool hasCxxDtor); // Slow paths for inline control id rootAutorelease2(); @@ -256,10 +299,6 @@ typedef struct old_property *objc_property_t; #include "maptable.h" #include "hashtable2.h" -#if SUPPORT_GC -#include "objc-auto.h" -#endif - /* Do not include message.h here. */ /* #include "message.h" */ @@ -289,87 +328,122 @@ struct objc_selopt_t; #endif +#define STRINGIFY(x) #x +#define STRINGIFY2(x) STRINGIFY(x) + __BEGIN_DECLS +struct header_info; -#if (defined(OBJC_NO_GC) && SUPPORT_GC) || \ - (!defined(OBJC_NO_GC) && !SUPPORT_GC) -# error OBJC_NO_GC and SUPPORT_GC inconsistent -#endif +// Split out the rw data from header info. For now put it in a huge array +// that more than exceeds the space needed. In future we'll just allocate +// this in the shared cache builder. +typedef struct header_info_rw { -#if SUPPORT_GC -# include - // PRIVATE_EXTERN is needed to help the compiler know "how" extern these are - PRIVATE_EXTERN extern int8_t UseGC; // equivalent to calling objc_collecting_enabled() - PRIVATE_EXTERN extern auto_zone_t *gc_zone; // the GC zone, or NULL if no GC - extern void objc_addRegisteredClass(Class c); - extern void objc_removeRegisteredClass(Class c); -#else -# define UseGC NO -# define gc_zone NULL -# define objc_addRegisteredClass(c) do {} while(0) -# define objc_removeRegisteredClass(c) do {} while(0) - /* Uses of the following must be protected with UseGC. */ - extern id gc_unsupported_dont_call(); -# define auto_zone_allocate_object gc_unsupported_dont_call -# define auto_zone_retain gc_unsupported_dont_call -# define auto_zone_release gc_unsupported_dont_call -# define auto_zone_is_valid_pointer gc_unsupported_dont_call -# define auto_zone_write_barrier_memmove gc_unsupported_dont_call -# define AUTO_OBJECT_SCANNED 0 -#endif + bool getLoaded() const { + return isLoaded; + } + void setLoaded(bool v) { + isLoaded = v ? 1: 0; + } -#define _objcHeaderIsReplacement(h) ((h)->info && ((h)->info->flags & OBJC_IMAGE_IS_REPLACEMENT)) + bool getAllClassesRealized() const { + return allClassesRealized; + } -/* OBJC_IMAGE_IS_REPLACEMENT: - Don't load any classes - Don't load any categories - Do fix up selector refs (@selector points to them) - Do fix up class refs (@class and objc_msgSend points to them) - Do fix up protocols (@protocol points to them) - Do fix up superclass pointers in classes ([super ...] points to them) - Future: do load new classes? - Future: do load new categories? - Future: do insert new methods on existing classes? - Future: do insert new methods on existing categories? -*/ + void setAllClassesRealized(bool v) { + allClassesRealized = v ? 1: 0; + } -#define _objcInfoSupportsGC(info) (((info)->flags & OBJC_IMAGE_SUPPORTS_GC) ? 1 : 0) -#define _objcInfoRequiresGC(info) (((info)->flags & OBJC_IMAGE_REQUIRES_GC) ? 1 : 0) -#define _objcHeaderSupportsGC(h) ((h)->info && _objcInfoSupportsGC((h)->info)) -#define _objcHeaderRequiresGC(h) ((h)->info && _objcInfoRequiresGC((h)->info)) + header_info *getNext() const { + return (header_info *)(next << 2); + } -/* OBJC_IMAGE_SUPPORTS_GC: - was compiled with -fobjc-gc flag, regardless of whether write-barriers were issued - if executable image compiled this way, then all subsequent libraries etc. must also be this way -*/ + void setNext(header_info *v) { + next = ((uintptr_t)v) >> 2; + } -#define _objcHeaderOptimizedByDyld(h) ((h)->info && ((h)->info->flags & OBJC_IMAGE_OPTIMIZED_BY_DYLD)) +private: +#ifdef __LP64__ + uintptr_t isLoaded : 1; + uintptr_t allClassesRealized : 1; + uintptr_t next : 62; +#else + uintptr_t isLoaded : 1; + uintptr_t allClassesRealized : 1; + uintptr_t next : 30; +#endif +} header_info_rw; -/* OBJC_IMAGE_OPTIMIZED_BY_DYLD: - Assorted metadata precooked in the dyld shared cache. - Never set for images outside the shared cache file itself. -*/ - +struct header_info_rw* getPreoptimizedHeaderRW(const struct header_info *const hdr); typedef struct header_info { - struct header_info *next; - const headerType *mhdr; - const objc_image_info *info; - const char *fname; // same as Dl_info.dli_fname - bool loaded; - bool inSharedCache; - bool allClassesRealized; +private: + // Note, this is no longer a pointer, but instead an offset to a pointer + // from this location. + intptr_t mhdr_offset; + + // Note, this is no longer a pointer, but instead an offset to a pointer + // from this location. + intptr_t info_offset; // Do not add fields without editing ObjCModernAbstraction.hpp +public: + + header_info_rw *getHeaderInfoRW() { + header_info_rw *preopt = + isPreoptimized() ? getPreoptimizedHeaderRW(this) : nil; + if (preopt) return preopt; + else return &rw_data[0]; + } + + const headerType *mhdr() const { + return (const headerType *)(((intptr_t)&mhdr_offset) + mhdr_offset); + } + + void setmhdr(const headerType *mhdr) { + mhdr_offset = (intptr_t)mhdr - (intptr_t)&mhdr_offset; + } + + const objc_image_info *info() const { + return (const objc_image_info *)(((intptr_t)&info_offset) + info_offset); + } + + void setinfo(const objc_image_info *info) { + info_offset = (intptr_t)info - (intptr_t)&info_offset; + } bool isLoaded() { - return loaded; + return getHeaderInfoRW()->getLoaded(); + } + + void setLoaded(bool v) { + getHeaderInfoRW()->setLoaded(v); + } + + bool areAllClassesRealized() { + return getHeaderInfoRW()->getAllClassesRealized(); + } + + void setAllClassesRealized(bool v) { + getHeaderInfoRW()->setAllClassesRealized(v); + } + + header_info *getNext() { + return getHeaderInfoRW()->getNext(); + } + + void setNext(header_info *v) { + getHeaderInfoRW()->setNext(v); } bool isBundle() { - return mhdr->filetype == MH_BUNDLE; + return mhdr()->filetype == MH_BUNDLE; + } + + const char *fname() const { + return dyld_image_path_containing_address(mhdr()); } bool isPreoptimized() const; @@ -392,6 +466,11 @@ typedef struct header_info { TCHAR *moduleName; # endif #endif + +private: + // Images in the shared cache will have an empty array here while those + // allocated at run time will allocate a single entry. + header_info_rw rw_data[]; } header_info; extern header_info *FirstHeader; @@ -405,8 +484,27 @@ extern objc_image_info *_getObjcImageInfo(const headerType *head, size_t *size); extern bool _hasObjcContents(const header_info *hi); +// Mach-O segment and section names are 16 bytes and may be un-terminated. + +static inline bool segnameEquals(const char *lhs, const char *rhs) { + return 0 == strncmp(lhs, rhs, 16); +} + +static inline bool segnameStartsWith(const char *segname, const char *prefix) { + return 0 == strncmp(segname, prefix, strlen(prefix)); +} + +static inline bool sectnameEquals(const char *lhs, const char *rhs) { + return segnameEquals(lhs, rhs); +} + +static inline bool sectnameStartsWith(const char *sectname, const char *prefix){ + return segnameStartsWith(sectname, prefix); +} + + /* selectors */ -extern void sel_init(bool gc, size_t selrefCount); +extern void sel_init(size_t selrefCount); extern SEL sel_registerNameNoLock(const char *str, bool copy); extern void sel_lock(void); extern void sel_unlock(void); @@ -426,7 +524,6 @@ extern SEL SEL_allocWithZone; extern SEL SEL_dealloc; extern SEL SEL_copy; extern SEL SEL_new; -extern SEL SEL_finalize; extern SEL SEL_forwardInvocation; extern SEL SEL_tryRetain; extern SEL SEL_isDeallocating; @@ -437,6 +534,7 @@ extern SEL SEL_allowsWeakReference; extern void preopt_init(void); extern void disableSharedCacheOptimizations(void); extern bool isPreoptimized(void); +extern bool noMissingWeakSuperclasses(void); extern header_info *preoptimizedHinfoForHeader(const headerType *mhdr); extern objc_selopt_t *preoptimizedSelectors(void); @@ -466,12 +564,8 @@ extern IMP _class_lookupMethodAndLoadCache3(id, SEL, Class); #if !OBJC_OLD_DISPATCH_PROTOTYPES extern void _objc_msgForward_impcache(void); -extern void _objc_ignored_method(void); -extern void _objc_msgSend_uncached_impcache(void); #else extern id _objc_msgForward_impcache(id, SEL, ...); -extern id _objc_ignored_method(id, SEL, ...); -extern id _objc_msgSend_uncached_impcache(id, SEL, ...); #endif /* errors */ @@ -481,8 +575,6 @@ extern void _objc_inform_on_crash(const char *fmt, ...) __attribute__((format (p extern void _objc_inform_now_and_on_crash(const char *fmt, ...) __attribute__((format (printf, 1, 2))); extern void _objc_inform_deprecated(const char *oldname, const char *newname) __attribute__((noinline)); extern void inform_duplicate(const char *name, Class oldCls, Class cls); -extern bool crashlog_header_name(header_info *hi); -extern bool crashlog_header_name_string(const char *name); /* magic */ extern Class _objc_getFreedObjectClass (void); @@ -549,51 +641,6 @@ class rwlock_writer_t : nocopy_t { ~rwlock_writer_t() { lock.unlockWrite(); } }; -/* ignored selector support */ - -/* Non-GC: no ignored selectors - GC (i386 Mac): some selectors ignored, remapped to kIgnore - GC (others): some selectors ignored, but not remapped -*/ - -static inline int ignoreSelector(SEL sel) -{ -#if !SUPPORT_GC - return NO; -#elif SUPPORT_IGNORED_SELECTOR_CONSTANT - return UseGC && sel == (SEL)kIgnore; -#else - return UseGC && - (sel == @selector(retain) || - sel == @selector(release) || - sel == @selector(autorelease) || - sel == @selector(retainCount) || - sel == @selector(dealloc)); -#endif -} - -static inline int ignoreSelectorNamed(const char *sel) -{ -#if !SUPPORT_GC - return NO; -#else - // release retain retainCount dealloc autorelease - return (UseGC && - ( (sel[0] == 'r' && sel[1] == 'e' && - (strcmp(&sel[2], "lease") == 0 || - strcmp(&sel[2], "tain") == 0 || - strcmp(&sel[2], "tainCount") == 0 )) - || - (strcmp(sel, "dealloc") == 0) - || - (sel[0] == 'a' && sel[1] == 'u' && - strcmp(&sel[2], "torelease") == 0))); -#endif -} - -/* GC startup */ -extern void gc_init(bool wantsGC); -extern void gc_init2(void); /* Exceptions */ struct alt_handler_list; @@ -608,36 +655,6 @@ extern void _destroyAltHandlerList(struct alt_handler_list *list); extern void gdb_objc_class_changed(Class cls, unsigned long changes, const char *classname) __attribute__((noinline)); -#if SUPPORT_GC - -/* Write barrier implementations */ -extern id objc_getAssociatedObject_non_gc(id object, const void *key); -extern void objc_setAssociatedObject_non_gc(id object, const void *key, id value, objc_AssociationPolicy policy); - -extern id objc_getAssociatedObject_gc(id object, const void *key); -extern void objc_setAssociatedObject_gc(id object, const void *key, id value, objc_AssociationPolicy policy); - -/* xrefs */ -extern objc_xref_t _object_addExternalReference_non_gc(id obj, objc_xref_t type); -extern id _object_readExternalReference_non_gc(objc_xref_t ref); -extern void _object_removeExternalReference_non_gc(objc_xref_t ref); - -extern objc_xref_t _object_addExternalReference_gc(id obj, objc_xref_t type); -extern id _object_readExternalReference_gc(objc_xref_t ref); -extern void _object_removeExternalReference_gc(objc_xref_t ref); - -/* GC weak reference fixup. */ -extern void gc_fixup_weakreferences(id newObject, id oldObject); - -/* GC datasegment registration. */ -extern void gc_register_datasegment(uintptr_t base, size_t size); -extern void gc_unregister_datasegment(uintptr_t base, size_t size); - -/* objc_dumpHeap implementation */ -extern bool _objc_dumpHeap(auto_zone_t *zone, const char *filename); - -#endif - // Settings from environment variables #define OPTION(var, env, help) extern bool var; @@ -707,15 +724,14 @@ extern void layout_bitmap_print(layout_bitmap bits); // fixme runtime extern Class look_up_class(const char *aClassName, bool includeUnconnected, bool includeClassHandler); -extern "C" const char *map_2_images(enum dyld_image_states state, uint32_t infoCount, const struct dyld_image_info infoList[]); -extern const char *map_images_nolock(enum dyld_image_states state, uint32_t infoCount, const struct dyld_image_info infoList[]); -extern const char * load_images(enum dyld_image_states state, uint32_t infoCount, const struct dyld_image_info infoList[]); -extern bool load_images_nolock(enum dyld_image_states state, uint32_t infoCount, const struct dyld_image_info infoList[]); -extern void unmap_image(const struct mach_header *mh, intptr_t vmaddr_slide); +extern "C" void map_2_images(unsigned count, const char * const paths[], + const struct mach_header * const mhdrs[]); +extern void map_images_nolock(unsigned count, const char * const paths[], + const struct mach_header * const mhdrs[]); +extern void load_images(const char *path, const struct mach_header *mh); +extern void unmap_image(const char *path, const struct mach_header *mh); extern void unmap_image_nolock(const struct mach_header *mh); -extern void _read_images(header_info **hList, uint32_t hCount); -extern void prepare_load_methods(const headerType *mhdr); -extern bool hasLoadMethods(const headerType *mhdr); +extern void _read_images(header_info **hList, uint32_t hCount, int totalClasses, int unoptimizedTotalClass); extern void _unload_image(header_info *hi); extern const char ** _objc_copyClassNamesForImage(header_info *hi, unsigned int *outCount); @@ -724,8 +740,7 @@ extern const header_info *_headerForClass(Class cls); extern Class _class_remap(Class cls); extern Class _class_getNonMetaClass(Class cls, id obj); -extern Ivar _class_getVariable(Class cls, const char *name, Class *memberOf); -extern uint32_t _class_getInstanceStart(Class cls); +extern Ivar _class_getVariable(Class cls, const char *name); extern unsigned _class_createInstancesFromZone(Class cls, size_t extraBytes, void *zone, id *results, unsigned num_requested); extern id _objc_constructOrFree(id bytes, Class cls); @@ -740,6 +755,10 @@ extern void object_cxxDestruct(id obj); extern void _class_resolveMethod(Class cls, SEL sel, id inst); +extern void fixupCopiedIvars(id newObject, id oldObject); +extern Class _class_getClassForIvar(Class cls, Ivar ivar); + + #define OBJC_WARN_DEPRECATED \ do { \ static int warned = 0; \ @@ -793,6 +812,16 @@ static T exp2m1u(T x) { #endif +// Misalignment-safe integer types +__attribute__((aligned(1))) typedef uintptr_t unaligned_uintptr_t; +__attribute__((aligned(1))) typedef intptr_t unaligned_intptr_t; +__attribute__((aligned(1))) typedef uint64_t unaligned_uint64_t; +__attribute__((aligned(1))) typedef int64_t unaligned_int64_t; +__attribute__((aligned(1))) typedef uint32_t unaligned_uint32_t; +__attribute__((aligned(1))) typedef int32_t unaligned_int32_t; +__attribute__((aligned(1))) typedef uint16_t unaligned_uint16_t; +__attribute__((aligned(1))) typedef int16_t unaligned_int16_t; + // Global operator new and delete. We must not use any app overrides. // This ALSO REQUIRES each of these be in libobjc's unexported symbol list. @@ -880,7 +909,8 @@ class StripedMap { // DisguisedPtr acts like pointer type T*, except the // stored value is disguised to hide it from tools like `leaks`. // nil is disguised as itself so zero-filled memory works as expected, -// which means 0x80..00 is also diguised as itself but we don't care +// which means 0x80..00 is also disguised as itself but we don't care. +// Note that weak_entry_t knows about this encoding. template class DisguisedPtr { uintptr_t value; diff --git a/runtime/objc-runtime-new.h b/runtime/objc-runtime-new.h index cdea941..7b7dbac 100644 --- a/runtime/objc-runtime-new.h +++ b/runtime/objc-runtime-new.h @@ -260,6 +260,9 @@ struct method_list_t : entsize_list_tt { }; struct ivar_list_t : entsize_list_tt { + bool containsIvar(Ivar ivar) const { + return (ivar >= (Ivar)&*begin() && ivar < (Ivar)&*end()); + } }; struct property_list_t : entsize_list_tt { @@ -271,6 +274,7 @@ typedef uintptr_t protocol_ref_t; // protocol_t *, but unremapped // Values for protocol_t->flags #define PROTOCOL_FIXED_UP_2 (1<<31) // must never be set by compiler #define PROTOCOL_FIXED_UP_1 (1<<30) // must never be set by compiler +// Bits 0..15 are reserved for Swift's use. #define PROTOCOL_FIXED_UP_MASK (PROTOCOL_FIXED_UP_1 | PROTOCOL_FIXED_UP_2) @@ -285,8 +289,9 @@ struct protocol_t : objc_object { uint32_t size; // sizeof(protocol_t) uint32_t flags; // Fields below this point are not always present on disk. - const char **extendedMethodTypes; + const char **_extendedMethodTypes; const char *_demangledName; + property_list_t *_classProperties; const char *demangledName(); @@ -297,12 +302,26 @@ struct protocol_t : objc_object { bool isFixedUp() const; void setFixedUp(); +# define HAS_FIELD(f) (size >= offsetof(protocol_t, f) + sizeof(f)) + bool hasExtendedMethodTypesField() const { - return size >= (offsetof(protocol_t, extendedMethodTypes) - + sizeof(extendedMethodTypes)); + return HAS_FIELD(_extendedMethodTypes); + } + bool hasDemangledNameField() const { + return HAS_FIELD(_demangledName); + } + bool hasClassPropertiesField() const { + return HAS_FIELD(_classProperties); + } + +# undef HAS_FIELD + + const char **extendedMethodTypes() const { + return hasExtendedMethodTypesField() ? _extendedMethodTypes : nil; } - bool hasExtendedMethodTypes() const { - return hasExtendedMethodTypesField() && extendedMethodTypes; + + property_list_t *classProperties() const { + return hasClassPropertiesField() ? _classProperties : nil; } }; @@ -354,7 +373,8 @@ struct locstamped_category_list_t { // The extra bits are optimized for the retain/release and alloc/dealloc paths. // Values for class_ro_t->flags -// These are emitted by the compiler and are part of the ABI. +// These are emitted by the compiler and are part of the ABI. +// Note: See CGObjCNonFragileABIMac::BuildClassRoTInitializer in clang // class is a metaclass #define RO_META (1<<0) // class is a root class @@ -369,10 +389,12 @@ struct locstamped_category_list_t { #define RO_EXCEPTION (1<<5) // this bit is available for reassignment // #define RO_REUSE_ME (1<<6) -// class compiled with -fobjc-arc (automatic retain/release) -#define RO_IS_ARR (1<<7) +// class compiled with ARC +#define RO_IS_ARC (1<<7) // class has .cxx_destruct but no .cxx_construct (with RO_HAS_CXX_STRUCTORS) #define RO_HAS_CXX_DTOR_ONLY (1<<8) +// class is not ARC but has ARC-style weak ivar layout +#define RO_HAS_WEAK_WITHOUT_ARC (1<<9) // class is in an unloadable bundle - must never be set by compiler #define RO_FROM_BUNDLE (1<<29) @@ -398,8 +420,8 @@ struct locstamped_category_list_t { #define RW_CONSTRUCTING (1<<26) // class allocated and registered #define RW_CONSTRUCTED (1<<25) -// GC: class has unsafe finalize method -#define RW_FINALIZE_ON_MAIN_THREAD (1<<24) +// available for use; was RW_FINALIZE_ON_MAIN_THREAD +// #define RW_24 (1<<24) // class +load has been called #define RW_LOADED (1<<23) #if !SUPPORT_NONPOINTER_ISA @@ -430,8 +452,9 @@ struct locstamped_category_list_t { // Note this is is stored in the metaclass. #define RW_HAS_DEFAULT_AWZ (1<<16) // class's instances requires raw isa -// not tracked for 32-bit because it only applies to non-pointer isa -// #define RW_REQUIRES_RAW_ISA +#if SUPPORT_NONPOINTER_ISA +#define RW_REQUIRES_RAW_ISA (1<<15) +#endif // class is a Swift class #define FAST_IS_SWIFT (1UL<<0) @@ -483,7 +506,7 @@ struct locstamped_category_list_t { // _tryRetain/_isDeallocating/retainWeakReference/allowsWeakReference #define FAST_HAS_DEFAULT_RR (1UL<<49) // summary bit for fast alloc path: !hasCxxCtor and -// !requiresRawIsa and instanceSize fits into shiftedSize +// !instancesRequireRawIsa and instanceSize fits into shiftedSize #define FAST_ALLOC (1UL<<50) // instance size in units of 16 bytes // or 0 if the instance size is too big in this field @@ -776,6 +799,7 @@ class protocol_array_t : struct class_rw_t { + // Be warned that Symbolication knows the layout of this structure. uint32_t flags; uint32_t version; @@ -790,6 +814,10 @@ struct class_rw_t { char *demangledName; +#if SUPPORT_INDEXED_ISA + uint32_t index; +#endif + void setFlags(uint32_t set) { OSAtomicOr32Barrier(set, &flags); @@ -941,20 +969,24 @@ struct class_data_bits_t { #endif #if FAST_REQUIRES_RAW_ISA - bool requiresRawIsa() { + bool instancesRequireRawIsa() { return getBit(FAST_REQUIRES_RAW_ISA); } - void setRequiresRawIsa() { + void setInstancesRequireRawIsa() { setBits(FAST_REQUIRES_RAW_ISA); } +#elif SUPPORT_NONPOINTER_ISA + bool instancesRequireRawIsa() { + return data()->flags & RW_REQUIRES_RAW_ISA; + } + void setInstancesRequireRawIsa() { + data()->setFlags(RW_REQUIRES_RAW_ISA); + } #else -# if SUPPORT_NONPOINTER_ISA -# error oops -# endif - bool requiresRawIsa() { + bool instancesRequireRawIsa() { return true; } - void setRequiresRawIsa() { + void setInstancesRequireRawIsa() { // nothing } #endif @@ -999,6 +1031,22 @@ struct class_data_bits_t { } #endif + void setClassArrayIndex(unsigned Idx) { +#if SUPPORT_INDEXED_ISA + // 0 is unused as then we can rely on zero-initialisation from calloc. + assert(Idx > 0); + data()->index = Idx; +#endif + } + + unsigned classArrayIndex() { +#if SUPPORT_INDEXED_ISA + return data()->index; +#else + return 0; +#endif + } + bool isSwift() { return getBit(FAST_IS_SWIFT); } @@ -1059,15 +1107,15 @@ struct objc_class : objc_object { void setHasCustomAWZ(bool inherited = false); void printCustomAWZ(bool inherited); - bool requiresRawIsa() { - return bits.requiresRawIsa(); + bool instancesRequireRawIsa() { + return bits.instancesRequireRawIsa(); } - void setRequiresRawIsa(bool inherited = false); - void printRequiresRawIsa(bool inherited); + void setInstancesRequireRawIsa(bool inherited = false); + void printInstancesRequireRawIsa(bool inherited); - bool canAllocIndexed() { + bool canAllocNonpointer() { assert(!isFuture()); - return !requiresRawIsa(); + return !instancesRequireRawIsa(); } bool canAllocFast() { assert(!isFuture()); @@ -1099,6 +1147,18 @@ struct objc_class : objc_object { } + // Return YES if the class's ivars are managed by ARC, + // or the class is MRC but has ARC-style weak ivars. + bool hasAutomaticIvars() { + return data()->ro->flags & (RO_IS_ARC | RO_HAS_WEAK_WITHOUT_ARC); + } + + // Return YES if the class's ivars are managed by ARC. + bool isARC() { + return data()->ro->flags & RO_IS_ARC; + } + + #if SUPPORT_NONPOINTER_ISA // Tracked in non-pointer isas; not tracked otherwise #else @@ -1123,17 +1183,6 @@ struct objc_class : objc_object { // fixme good or bad for memory use? } - bool shouldFinalizeOnMainThread() { - // finishInitializing() propagates this flag from the superclass. - assert(isRealized()); - return data()->flags & RW_FINALIZE_ON_MAIN_THREAD; - } - - void setShouldFinalizeOnMainThread() { - assert(isRealized()); - setInfo(RW_FINALIZE_ON_MAIN_THREAD); - } - bool isInitializing() { return getMeta()->data()->flags & RW_INITIALIZING; } @@ -1200,6 +1249,18 @@ struct objc_class : objc_object { const char *demangledName(bool realize = false); const char *nameForLogging(); + // May be unaligned depending on class's ivars. + uint32_t unalignedInstanceStart() { + assert(isRealized()); + return data()->ro->instanceStart; + } + + // Class's instance start rounded up to a pointer-size boundary. + // This is used for ARC layout bitmaps. + uint32_t alignedInstanceStart() { + return word_align(unalignedInstanceStart()); + } + // May be unaligned depending on class's ivars. uint32_t unalignedInstanceSize() { assert(isRealized()); @@ -1226,6 +1287,17 @@ struct objc_class : objc_object { } bits.setFastInstanceSize(newSize); } + + void chooseClassArrayIndex(); + + void setClassArrayIndex(unsigned Idx) { + bits.setClassArrayIndex(Idx); + } + + unsigned classArrayIndex() { + return bits.classArrayIndex(); + } + }; @@ -1254,16 +1326,15 @@ struct category_t { struct method_list_t *classMethods; struct protocol_list_t *protocols; struct property_list_t *instanceProperties; + // Fields below this point are not always present on disk. + struct property_list_t *_classProperties; method_list_t *methodsForMeta(bool isMeta) { if (isMeta) return classMethods; else return instanceMethods; } - property_list_t *propertiesForMeta(bool isMeta) { - if (isMeta) return nil; // classProperties; - else return instanceProperties; - } + property_list_t *propertiesForMeta(bool isMeta, struct header_info *hi); }; struct objc_super2 { @@ -1300,6 +1371,7 @@ foreach_realized_class_and_subclass_2(Class top, bool (^code)(Class)) } } +// Enumerates a class and all of its realized subclasses. static inline void foreach_realized_class_and_subclass(Class top, void (^code)(Class)) { @@ -1308,4 +1380,20 @@ foreach_realized_class_and_subclass(Class top, void (^code)(Class)) }); } +// Enumerates all realized classes and metaclasses. +extern Class firstRealizedClass(); +static inline void +foreach_realized_class_and_metaclass(void (^code)(Class)) +{ + for (Class top = firstRealizedClass(); + top != nil; + top = top->data()->nextSiblingClass) + { + foreach_realized_class_and_subclass_2(top, ^bool(Class cls) { + code(cls); return true; + }); + } + +} + #endif diff --git a/runtime/objc-runtime-new.mm b/runtime/objc-runtime-new.mm index cff8635..b366c19 100644 --- a/runtime/objc-runtime-new.mm +++ b/runtime/objc-runtime-new.mm @@ -46,7 +46,6 @@ static method_t *getMethodNoSuper_nolock(Class cls, SEL sel); static method_t *getMethod_nolock(Class cls, SEL sel); static IMP addMethod(Class cls, SEL name, IMP imp, const char *types, bool replace); -static NXHashTable *realizedClasses(void); static bool isRRSelector(SEL sel); static bool isAWZSelector(SEL sel); static bool methodListImplementsRR(const method_list_t *mlist); @@ -61,13 +60,6 @@ static bool MetaclassNSObjectAWZSwizzled; static bool ClassNSObjectRRSwizzled; -#define SDK_FORMAT "%hu.%hhu.%hhu" -#define FORMAT_SDK(v) \ - (unsigned short)(((uint32_t)(v))>>16), \ - (unsigned char)(((uint32_t)(v))>>8), \ - (unsigned char)(((uint32_t)(v))>>0) - - id objc_noop_imp(id self, SEL _cmd __unused) { return self; } @@ -107,7 +99,67 @@ void lock_init(void) /*********************************************************************** * Non-pointer isa decoding **********************************************************************/ -#if SUPPORT_NONPOINTER_ISA +#if SUPPORT_INDEXED_ISA + +// Indexed non-pointer isa. + +// These are used to mask the ISA and see if its got an index or not. +const uintptr_t objc_debug_indexed_isa_magic_mask = ISA_INDEX_MAGIC_MASK; +const uintptr_t objc_debug_indexed_isa_magic_value = ISA_INDEX_MAGIC_VALUE; + +// die if masks overlap +STATIC_ASSERT((ISA_INDEX_MASK & ISA_INDEX_MAGIC_MASK) == 0); + +// die if magic is wrong +STATIC_ASSERT((~ISA_INDEX_MAGIC_MASK & ISA_INDEX_MAGIC_VALUE) == 0); + +// Then these are used to extract the index from the ISA. +const uintptr_t objc_debug_indexed_isa_index_mask = ISA_INDEX_MASK; +const uintptr_t objc_debug_indexed_isa_index_shift = ISA_INDEX_SHIFT; + +asm("\n .globl _objc_absolute_indexed_isa_magic_mask" \ + "\n _objc_absolute_indexed_isa_magic_mask = " STRINGIFY2(ISA_INDEX_MAGIC_MASK)); +asm("\n .globl _objc_absolute_indexed_isa_magic_value" \ + "\n _objc_absolute_indexed_isa_magic_value = " STRINGIFY2(ISA_INDEX_MAGIC_VALUE)); +asm("\n .globl _objc_absolute_indexed_isa_index_mask" \ + "\n _objc_absolute_indexed_isa_index_mask = " STRINGIFY2(ISA_INDEX_MASK)); +asm("\n .globl _objc_absolute_indexed_isa_index_shift" \ + "\n _objc_absolute_indexed_isa_index_shift = " STRINGIFY2(ISA_INDEX_SHIFT)); + + +// And then we can use that index to get the class from this array. Note +// the size is provided so that clients can ensure the index they get is in +// bounds and not read off the end of the array. +// Defined in the objc-msg-*.s files +// const Class objc_indexed_classes[] + +// When we don't have enough bits to store a class*, we can instead store an +// index in to this array. Classes are added here when they are realized. +// Note, an index of 0 is illegal. +uintptr_t objc_indexed_classes_count = 0; + +// SUPPORT_INDEXED_ISA +#else +// not SUPPORT_INDEXED_ISA + +// These variables exist but are all set to 0 so that they are ignored. +const uintptr_t objc_debug_indexed_isa_magic_mask = 0; +const uintptr_t objc_debug_indexed_isa_magic_value = 0; +const uintptr_t objc_debug_indexed_isa_index_mask = 0; +const uintptr_t objc_debug_indexed_isa_index_shift = 0; +Class objc_indexed_classes[1] = { nil }; +uintptr_t objc_indexed_classes_count = 0; + +// not SUPPORT_INDEXED_ISA +#endif + + +#if SUPPORT_PACKED_ISA + +// Packed non-pointer isa. + +asm("\n .globl _objc_absolute_packed_isa_class_mask" \ + "\n _objc_absolute_packed_isa_class_mask = " STRINGIFY2(ISA_MASK)); const uintptr_t objc_debug_isa_class_mask = ISA_MASK; const uintptr_t objc_debug_isa_magic_mask = ISA_MAGIC_MASK; @@ -123,13 +175,16 @@ void lock_init(void) STATIC_ASSERT((~ISA_MASK & MACH_VM_MAX_ADDRESS) == 0 || ISA_MASK + sizeof(void*) == MACH_VM_MAX_ADDRESS); +// SUPPORT_PACKED_ISA #else +// not SUPPORT_PACKED_ISA // These variables exist but enforce pointer alignment only. const uintptr_t objc_debug_isa_class_mask = (~WORD_MASK); const uintptr_t objc_debug_isa_magic_mask = WORD_MASK; const uintptr_t objc_debug_isa_magic_value = 0; +// not SUPPORT_PACKED_ISA #endif @@ -438,9 +493,6 @@ static void printReplacements(Class cls, category_list *cats) for (const auto& meth : *mlist) { SEL s = sel_registerName(sel_cname(meth.name)); - // Don't warn about GC-ignored selectors - if (ignoreSelector(s)) continue; - // Search for replaced methods in method lookup order. // Complain about the first duplicate only. @@ -498,13 +550,7 @@ static bool isBundleClass(Class cls) // Unique selectors in list. for (auto& meth : *mlist) { const char *name = sel_cname(meth.name); - - SEL sel = sel_registerNameNoLock(name, bundleCopy); - meth.name = sel; - - if (ignoreSelector(sel)) { - meth.imp = (IMP)&_objc_ignored_method; - } + meth.name = sel_registerNameNoLock(name, bundleCopy); } sel_unlock(); @@ -529,8 +575,8 @@ static bool isBundleClass(Class cls) if (addedCount == 0) return; // Don't scan redundantly - bool scanForCustomRR = !UseGC && !cls->hasCustomRR(); - bool scanForCustomAWZ = !UseGC && !cls->hasCustomAWZ(); + bool scanForCustomRR = !cls->hasCustomRR(); + bool scanForCustomAWZ = !cls->hasCustomAWZ(); // There exist RR/AWZ special cases for some class's base methods. // But this code should never need to scan base methods for RR/AWZ: @@ -600,7 +646,8 @@ static bool isBundleClass(Class cls) fromBundle |= entry.hi->isBundle(); } - property_list_t *proplist = entry.cat->propertiesForMeta(isMeta); + property_list_t *proplist = + entry.cat->propertiesForMeta(isMeta, entry.hi); if (proplist) { proplists[propcount++] = proplist; } @@ -693,8 +740,7 @@ static void methodizeClass(Class cls) _objc_inform("METHOD %c[%s %s]", isMeta ? '+' : '-', cls->nameForLogging(), sel_getName(meth.name)); } - assert(ignoreSelector(meth.name) || - sel_registerName(sel_getName(meth.name)) == meth.name); + assert(sel_registerName(sel_getName(meth.name)) == meth.name); } #endif } @@ -823,10 +869,11 @@ static bool scanMangledField(const char *&string, const char *end, // Module name. const char *prefix; int prefixLength; - if (strncmp(string, "Ss", 2) == 0) { + if (string[0] == 's') { + // "s" is the Swift module. prefix = "Swift"; prefixLength = 5; - string += 2; + string += 1; } else { if (! scanMangledField(string, end, prefix, prefixLength)) return nil; } @@ -883,7 +930,7 @@ static bool scanMangledField(const char *&string, const char *end, char *name; if (prefixLength == 5 && memcmp(prefix, "Swift", 5) == 0) { - asprintf(&name, "_Tt%cSs%zu%.*s%s", + asprintf(&name, "_Tt%cs%zu%.*s%s", isProtocol ? 'P' : 'C', suffixLength, (int)suffixLength, suffix, isProtocol ? "_" : ""); @@ -988,103 +1035,6 @@ static void removeNamedClass(Class cls, const char *name) } -/*********************************************************************** -* realizedClasses -* Returns the class list for realized non-meta classes. -* Locking: runtimeLock must be read- or write-locked by the caller -**********************************************************************/ -static NXHashTable *realized_class_hash = nil; - -static NXHashTable *realizedClasses(void) -{ - runtimeLock.assertLocked(); - - // allocated in _read_images - assert(realized_class_hash); - - return realized_class_hash; -} - - -/*********************************************************************** -* realizedMetaclasses -* Returns the class list for realized metaclasses. -* Locking: runtimeLock must be read- or write-locked by the caller -**********************************************************************/ -static NXHashTable *realized_metaclass_hash = nil; -static NXHashTable *realizedMetaclasses(void) -{ - runtimeLock.assertLocked(); - - // allocated in _read_images - assert(realized_metaclass_hash); - - return realized_metaclass_hash; -} - - -/*********************************************************************** -* addRealizedClass -* Adds cls to the realized non-meta class hash. -* Locking: runtimeLock must be held by the caller -**********************************************************************/ -static void addRealizedClass(Class cls) -{ - runtimeLock.assertWriting(); - void *old; - old = NXHashInsert(realizedClasses(), cls); - objc_addRegisteredClass(cls); - assert(!cls->isMetaClass()); - assert(!old); -} - - -/*********************************************************************** -* removeRealizedClass -* Removes cls from the realized non-meta class hash. -* Locking: runtimeLock must be held by the caller -**********************************************************************/ -static void removeRealizedClass(Class cls) -{ - runtimeLock.assertWriting(); - if (cls->isRealized()) { - assert(!cls->isMetaClass()); - NXHashRemove(realizedClasses(), cls); - objc_removeRegisteredClass(cls); - } -} - - -/*********************************************************************** -* addRealizedMetaclass -* Adds cls to the realized metaclass hash. -* Locking: runtimeLock must be held by the caller -**********************************************************************/ -static void addRealizedMetaclass(Class cls) -{ - runtimeLock.assertWriting(); - void *old; - old = NXHashInsert(realizedMetaclasses(), cls); - assert(cls->isMetaClass()); - assert(!old); -} - - -/*********************************************************************** -* removeRealizedMetaclass -* Removes cls from the realized metaclass hash. -* Locking: runtimeLock must be held by the caller -**********************************************************************/ -static void removeRealizedMetaclass(Class cls) -{ - runtimeLock.assertWriting(); - if (cls->isRealized()) { - assert(cls->isMetaClass()); - NXHashRemove(realizedMetaclasses(), cls); - } -} - - /*********************************************************************** * futureNamedClasses * Returns the classname => future class map for unrealized future classes. @@ -1105,6 +1055,11 @@ static void removeRealizedMetaclass(Class cls) } +static bool haveFutureNamedClasses() { + return future_named_class_map && NXCountMapTable(future_named_class_map); +} + + /*********************************************************************** * addFutureNamedClass * Installs cls as the class structure to use for the named class if it appears. @@ -1122,7 +1077,7 @@ static void addFutureNamedClass(const char *name, Class cls) class_rw_t *rw = (class_rw_t *)calloc(sizeof(class_rw_t), 1); class_ro_t *ro = (class_ro_t *)calloc(sizeof(class_ro_t), 1); - ro->name = strdup(name); + ro->name = strdupIfMutable(name); rw->ro = ro; cls->setData(rw); cls->data()->flags = RO_FUTURE; @@ -1404,6 +1359,41 @@ Class _class_getNonMetaClass(Class cls, id obj) } +/*********************************************************************** +* addRootClass +* Adds cls as a new realized root class. +* Locking: runtimeLock must be held by the caller. +**********************************************************************/ +static Class _firstRealizedClass = nil; +Class firstRealizedClass() +{ + runtimeLock.assertLocked(); + return _firstRealizedClass; +} + +static void addRootClass(Class cls) +{ + runtimeLock.assertWriting(); + + assert(cls->isRealized()); + cls->data()->nextSiblingClass = _firstRealizedClass; + _firstRealizedClass = cls; +} + +static void removeRootClass(Class cls) +{ + runtimeLock.assertWriting(); + + Class *classp; + for (classp = &_firstRealizedClass; + *classp != cls; + classp = &(*classp)->data()->nextSiblingClass) + { } + + *classp = (*classp)->data()->nextSiblingClass; +} + + /*********************************************************************** * addSubclass * Adds subcls as a subclass of supercls. @@ -1435,8 +1425,10 @@ static void addSubclass(Class supercls, Class subcls) subcls->setHasCustomAWZ(true); } - if (supercls->requiresRawIsa()) { - subcls->setRequiresRawIsa(true); + // Special case: instancesRequireRawIsa does not propagate + // from root class to root metaclass + if (supercls->instancesRequireRawIsa() && supercls->superclass) { + subcls->setInstancesRequireRawIsa(true); } } } @@ -1545,12 +1537,10 @@ static void remapProtocolRef(protocol_t **protoref) /*********************************************************************** * moveIvars * Slides a class's ivars to accommodate the given superclass size. -* Also slides ivar and weak GC layouts if provided. * Ivars are NOT compacted to compensate for a superclass that shrunk. * Locking: runtimeLock must be held by the caller. **********************************************************************/ -static void moveIvars(class_ro_t *ro, uint32_t superSize, - layout_bitmap *ivarBitmap, layout_bitmap *weakBitmap) +static void moveIvars(class_ro_t *ro, uint32_t superSize) { runtimeLock.assertWriting(); @@ -1571,7 +1561,7 @@ static void moveIvars(class_ro_t *ro, uint32_t superSize, // Compute a slide value that preserves that alignment uint32_t alignMask = maxAlignment - 1; - if (diff & alignMask) diff = (diff + alignMask) & ~alignMask; + diff = (diff + alignMask) & ~alignMask; // Slide all of this class's ivars en masse for (const auto& ivar : *ro->ivars) { @@ -1588,58 +1578,10 @@ static void moveIvars(class_ro_t *ro, uint32_t superSize, ivar.size, ivar.alignment()); } } - - // Slide GC layouts - uint32_t oldOffset = ro->instanceStart; - uint32_t newOffset = ro->instanceStart + diff; - - if (ivarBitmap) { - layout_bitmap_slide(ivarBitmap, - oldOffset >> WORD_SHIFT, - newOffset >> WORD_SHIFT); - } - if (weakBitmap) { - layout_bitmap_slide(weakBitmap, - oldOffset >> WORD_SHIFT, - newOffset >> WORD_SHIFT); - } } *(uint32_t *)&ro->instanceStart += diff; *(uint32_t *)&ro->instanceSize += diff; - - if (!ro->ivars) { - // No ivars slid, but superclass changed size. - // Expand bitmap in preparation for layout_bitmap_splat(). - if (ivarBitmap) layout_bitmap_grow(ivarBitmap, ro->instanceSize >> WORD_SHIFT); - if (weakBitmap) layout_bitmap_grow(weakBitmap, ro->instanceSize >> WORD_SHIFT); - } -} - - -/*********************************************************************** -* getIvar -* Look up an ivar by name. -* Locking: runtimeLock must be read- or write-locked by the caller. -**********************************************************************/ -static ivar_t *getIvar(Class cls, const char *name) -{ - runtimeLock.assertLocked(); - - const ivar_list_t *ivars; - assert(cls->isRealized()); - if ((ivars = cls->data()->ro->ivars)) { - for (auto& ivar : *ivars) { - if (!ivar.offset) continue; // anonymous bitfield - - // ivar.name may be nil for anonymous bitfields etc. - if (ivar.name && 0 == strcmp(name, ivar.name)) { - return &ivar; - } - } - } - - return nil; } @@ -1663,10 +1605,6 @@ static void reconcileInstanceVariables(Class cls, Class supercls, const class_ro */ // Non-fragile ivars - reconcile this class with its superclass - layout_bitmap ivarBitmap; - layout_bitmap weakBitmap; - bool layoutsChanged = NO; - bool mergeLayouts = UseGC; const class_ro_t *super_ro = supercls->data()->ro; if (DebugNonFragileIvars) { @@ -1688,7 +1626,6 @@ static void reconcileInstanceVariables(Class cls, Class supercls, const class_ro 0 != strcmp(clsname, "NSSimpleCString")) { uint32_t oldStart = ro->instanceStart; - uint32_t oldSize = ro->instanceSize; class_ro_t *ro_w = make_ro_writeable(rw); ro = rw->ro; @@ -1720,65 +1657,15 @@ static void reconcileInstanceVariables(Class cls, Class supercls, const class_ro *ivar.offset -= delta; } } - - if (mergeLayouts) { - layout_bitmap layout; - if (ro->ivarLayout) { - layout = layout_bitmap_create(ro->ivarLayout, - oldSize, oldSize, NO); - layout_bitmap_slide_anywhere(&layout, - delta >> WORD_SHIFT, 0); - ro_w->ivarLayout = layout_string_create(layout); - layout_bitmap_free(layout); - } - if (ro->weakIvarLayout) { - layout = layout_bitmap_create(ro->weakIvarLayout, - oldSize, oldSize, YES); - layout_bitmap_slide_anywhere(&layout, - delta >> WORD_SHIFT, 0); - ro_w->weakIvarLayout = layout_string_create(layout); - layout_bitmap_free(layout); - } - } } } - if (ro->instanceStart >= super_ro->instanceSize && !mergeLayouts) { - // Superclass has not overgrown its space, and we don't - // need to rebuild GC layouts. We're done here. + if (ro->instanceStart >= super_ro->instanceSize) { + // Superclass has not overgrown its space. We're done here. return; } // fixme can optimize for "class has no new ivars", etc - if (mergeLayouts) { - // WARNING: gcc c++ sets instanceStart/Size=0 for classes with - // no local ivars, but does provide a layout bitmap. - // Handle that case specially so layout_bitmap_create doesn't die - // The other ivar sliding code below still works fine, and - // the final result is a good class. - if (ro->instanceStart == 0 && ro->instanceSize == 0) { - // We can't use ro->ivarLayout because we don't know - // how long it is. Force a new layout to be created. - if (PrintIvars) { - _objc_inform("IVARS: instanceStart/Size==0 for class %s; " - "disregarding ivar layout", cls->nameForLogging()); - } - ivarBitmap = layout_bitmap_create_empty(super_ro->instanceSize, NO); - weakBitmap = layout_bitmap_create_empty(super_ro->instanceSize, YES); - layoutsChanged = YES; - } - else { - ivarBitmap = - layout_bitmap_create(ro->ivarLayout, - ro->instanceSize, - ro->instanceSize, NO); - weakBitmap = - layout_bitmap_create(ro->weakIvarLayout, - ro->instanceSize, - ro->instanceSize, YES); - } - } - if (ro->instanceStart < super_ro->instanceSize) { // Superclass has changed size. This class's ivars must move. // Also slide layout bits in parallel. @@ -1792,52 +1679,9 @@ static void reconcileInstanceVariables(Class cls, Class supercls, const class_ro } class_ro_t *ro_w = make_ro_writeable(rw); ro = rw->ro; - moveIvars(ro_w, super_ro->instanceSize, - mergeLayouts ? &ivarBitmap : nil, - mergeLayouts ? &weakBitmap : nil); + moveIvars(ro_w, super_ro->instanceSize); gdb_objc_class_changed(cls, OBJC_CLASS_IVARS_CHANGED, ro->name); - layoutsChanged = YES; } - - if (mergeLayouts) { - // Check superclass's layout against this class's layout. - // This needs to be done even if the superclass is not bigger. - layout_bitmap superBitmap; - - superBitmap = layout_bitmap_create(super_ro->ivarLayout, - super_ro->instanceSize, - super_ro->instanceSize, NO); - layoutsChanged |= layout_bitmap_splat(ivarBitmap, superBitmap, - ro->instanceStart); - layout_bitmap_free(superBitmap); - - // check the superclass' weak layout. - superBitmap = layout_bitmap_create(super_ro->weakIvarLayout, - super_ro->instanceSize, - super_ro->instanceSize, YES); - layoutsChanged |= layout_bitmap_splat(weakBitmap, superBitmap, - ro->instanceStart); - layout_bitmap_free(superBitmap); - - // Rebuild layout strings if necessary. - if (layoutsChanged) { - if (PrintIvars) { - _objc_inform("IVARS: gc layout changed for class %s", - cls->nameForLogging()); - } - class_ro_t *ro_w = make_ro_writeable(rw); - ro = rw->ro; - if (DebugNonFragileIvars) { - try_free(ro_w->ivarLayout); - try_free(ro_w->weakIvarLayout); - } - ro_w->ivarLayout = layout_string_create(ivarBitmap); - ro_w->weakIvarLayout = layout_string_create(weakBitmap); - } - - layout_bitmap_free(ivarBitmap); - layout_bitmap_free(weakBitmap); - } } @@ -1882,17 +1726,58 @@ static Class realizeClass(Class cls) rw->version = isMeta ? 7 : 0; // old runtime went up to 6 + + // Choose an index for this class. + // Sets cls->instancesRequireRawIsa if indexes no more indexes are available + cls->chooseClassArrayIndex(); + if (PrintConnecting) { - _objc_inform("CLASS: realizing class '%s' %s %p %p", - cls->nameForLogging(), isMeta ? "(meta)" : "", - (void*)cls, ro); + _objc_inform("CLASS: realizing class '%s'%s %p %p #%u", + cls->nameForLogging(), isMeta ? " (meta)" : "", + (void*)cls, ro, cls->classArrayIndex()); } // Realize superclass and metaclass, if they aren't already. // This needs to be done after RW_REALIZED is set above, for root classes. + // This needs to be done after class index is chosen, for root metaclasses. supercls = realizeClass(remapClass(cls->superclass)); metacls = realizeClass(remapClass(cls->ISA())); +#if SUPPORT_NONPOINTER_ISA + // Disable non-pointer isa for some classes and/or platforms. + // Set instancesRequireRawIsa. + bool instancesRequireRawIsa = cls->instancesRequireRawIsa(); + bool rawIsaIsInherited = false; + static bool hackedDispatch = false; + + if (DisableNonpointerIsa) { + // Non-pointer isa disabled by environment or app SDK version + instancesRequireRawIsa = true; + } + else if (!hackedDispatch && !(ro->flags & RO_META) && + 0 == strcmp(ro->name, "OS_object")) + { + // hack for libdispatch et al - isa also acts as vtable pointer + hackedDispatch = true; + instancesRequireRawIsa = true; + } + else if (supercls && supercls->superclass && + supercls->instancesRequireRawIsa()) + { + // This is also propagated by addSubclass() + // but nonpointer isa setup needs it earlier. + // Special case: instancesRequireRawIsa does not propagate + // from root class to root metaclass + instancesRequireRawIsa = true; + rawIsaIsInherited = true; + } + + if (instancesRequireRawIsa) { + cls->setInstancesRequireRawIsa(rawIsaIsInherited); + } +// SUPPORT_NONPOINTER_ISA +#endif + // Update superclass and metaclass in case of remapping cls->superclass = supercls; cls->initClassIsa(metacls); @@ -1912,44 +1797,16 @@ static Class realizeClass(Class cls) } } - // Disable non-pointer isa for some classes and/or platforms. -#if SUPPORT_NONPOINTER_ISA - { - bool disable = false; - static bool hackedDispatch = false; - - if (DisableIndexedIsa) { - // Non-pointer isa disabled by environment or GC or app SDK version - disable = true; - } - else if (!hackedDispatch && !(ro->flags & RO_META) && - 0 == strcmp(ro->name, "OS_object")) - { - // hack for libdispatch et al - isa also acts as vtable pointer - hackedDispatch = true; - disable = true; - } - - if (disable) { - cls->setRequiresRawIsa(false/*inherited*/); - } - } -#endif - // Connect this class to its superclass's subclass lists if (supercls) { addSubclass(supercls, cls); + } else { + addRootClass(cls); } // Attach categories methodizeClass(cls); - if (!isMeta) { - addRealizedClass(cls); - } else { - addRealizedMetaclass(cls); - } - return cls; } @@ -1990,7 +1847,7 @@ static void realizeAllClassesInImage(header_info *hi) size_t count, i; classref_t *classlist; - if (hi->allClassesRealized) return; + if (hi->areAllClassesRealized()) return; classlist = _getObjc2ClassList(hi, &count); @@ -1998,7 +1855,7 @@ static void realizeAllClassesInImage(header_info *hi) realizeClass(remapClass(classlist[i])); } - hi->allClassesRealized = YES; + hi->setAllClassesRealized(YES); } @@ -2012,7 +1869,7 @@ static void realizeAllClasses(void) runtimeLock.assertWriting(); header_info *hi; - for (hi = FirstHeader; hi; hi = hi->next) { + for (hi = FirstHeader; hi; hi = hi->getNext()) { realizeAllClassesInImage(hi); } } @@ -2097,27 +1954,11 @@ static void flushCaches(Class cls) foreach_realized_class_and_subclass(cls, ^(Class c){ cache_erase_nolock(c); }); - - if (!cls->superclass) { - // root; metaclasses are subclasses and were flushed above - } else { - foreach_realized_class_and_subclass(cls->ISA(), ^(Class c){ - cache_erase_nolock(c); - }); - } } else { - Class c; - NXHashTable *classes = realizedClasses(); - NXHashState state = NXInitHashState(classes); - while (NXNextHashState(classes, &state, (void **)&c)) { - cache_erase_nolock(c); - } - classes = realizedMetaclasses(); - state = NXInitHashState(classes); - while (NXNextHashState(classes, &state, (void **)&c)) { + foreach_realized_class_and_metaclass(^(Class c){ cache_erase_nolock(c); - } + }); } } @@ -2127,6 +1968,12 @@ void _objc_flush_caches(Class cls) { rwlock_writer_t lock(runtimeLock); flushCaches(cls); + if (cls && cls->superclass && cls != cls->getIsa()) { + flushCaches(cls->getIsa()); + } else { + // cls is a root class or root metaclass. Its metaclass is itself + // or a subclass so the metaclass caches were already flushed. + } } if (!cls) { @@ -2144,65 +1991,51 @@ void _objc_flush_caches(Class cls) * * Locking: write-locks runtimeLock **********************************************************************/ -const char * -map_2_images(enum dyld_image_states state, uint32_t infoCount, - const struct dyld_image_info infoList[]) +void +map_2_images(unsigned count, const char * const paths[], + const struct mach_header * const mhdrs[]) { rwlock_writer_t lock(runtimeLock); - return map_images_nolock(state, infoCount, infoList); + return map_images_nolock(count, paths, mhdrs); } /*********************************************************************** * load_images * Process +load in the given images which are being mapped in by dyld. -* Calls ABI-agnostic code after taking ABI-specific locks. * * Locking: write-locks runtimeLock and loadMethodLock **********************************************************************/ -const char * -load_images(enum dyld_image_states state, uint32_t infoCount, - const struct dyld_image_info infoList[]) -{ - bool found; +extern bool hasLoadMethods(const headerType *mhdr); +extern void prepare_load_methods(const headerType *mhdr); +void +load_images(const char *path __unused, const struct mach_header *mh) +{ // Return without taking locks if there are no +load methods here. - found = false; - for (uint32_t i = 0; i < infoCount; i++) { - if (hasLoadMethods((const headerType *)infoList[i].imageLoadAddress)) { - found = true; - break; - } - } - if (!found) return nil; + if (!hasLoadMethods((const headerType *)mh)) return; recursive_mutex_locker_t lock(loadMethodLock); // Discover load methods { rwlock_writer_t lock2(runtimeLock); - found = load_images_nolock(state, infoCount, infoList); + prepare_load_methods((const headerType *)mh); } // Call +load methods (without runtimeLock - re-entrant) - if (found) { - call_load_methods(); - } - - return nil; + call_load_methods(); } /*********************************************************************** * unmap_image * Process the given image which is about to be unmapped by dyld. -* mh is mach_header instead of headerType because that's what -* dyld_priv.h says even for 64-bit. * * Locking: write-locks runtimeLock and loadMethodLock **********************************************************************/ void -unmap_image(const struct mach_header *mh, intptr_t vmaddr_slide) +unmap_image(const char *path __unused, const struct mach_header *mh) { recursive_mutex_locker_t lock(loadMethodLock); rwlock_writer_t lock2(runtimeLock); @@ -2212,6 +2045,52 @@ void _objc_flush_caches(Class cls) +/*********************************************************************** +* mustReadClasses +* Preflight check in advance of readClass() from an image. +**********************************************************************/ +bool mustReadClasses(header_info *hi) +{ + const char *reason; + + // If the image is not preoptimized then we must read classes. + if (!hi->isPreoptimized()) { + reason = nil; // Don't log this one because it is noisy. + goto readthem; + } + + // If iOS simulator then we must read classes. +#if TARGET_OS_SIMULATOR + reason = "the image is for iOS simulator"; + goto readthem; +#endif + + assert(!hi->isBundle()); // no MH_BUNDLE in shared cache + + // If the image may have missing weak superclasses then we must read classes + if (!noMissingWeakSuperclasses()) { + reason = "the image may contain classes with missing weak superclasses"; + goto readthem; + } + + // If there are unresolved future classes then we must read classes. + if (haveFutureNamedClasses()) { + reason = "there are unresolved future classes pending"; + goto readthem; + } + + // readClass() does not need to do anything. + return NO; + + readthem: + if (PrintPreopt && reason) { + _objc_inform("PREOPTIMIZATION: reading classes manually from %s " + "because %s", hi->fname(), reason); + } + return YES; +} + + /*********************************************************************** * readClass * Read a class and metaclass as written by a compiler. @@ -2220,6 +2099,9 @@ void _objc_flush_caches(Class cls) * - nil (cls has a missing weak-linked superclass) * - something else (space for this class was reserved by a future class) * +* Note that all work performed by this function is preflighted by +* mustReadClasses(). Do not change this function without updating that one. +* * Locking: runtimeLock acquired by map_images or objc_readClassPair **********************************************************************/ Class readClass(Class cls, bool headerIsBundle, bool headerIsPreoptimized) @@ -2248,7 +2130,7 @@ Class readClass(Class cls, bool headerIsBundle, bool headerIsPreoptimized) // does not bind shared cache absolute symbols as expected. // This (and the __ARCLite__ hack below) can be removed // once the simulator drops 10.8 support. -#if TARGET_IPHONE_SIMULATOR +#if TARGET_OS_SIMULATOR if (cls->cache._mask) cls->cache._mask = 0; if (cls->cache._occupied) cls->cache._occupied = 0; if (cls->ISA()->cache._mask) cls->ISA()->cache._mask = 0; @@ -2272,7 +2154,7 @@ Class readClass(Class cls, bool headerIsBundle, bool headerIsPreoptimized) memcpy(newCls, cls, sizeof(objc_class)); rw->ro = (class_ro_t *)newCls->data(); newCls->setData(rw); - free((void *)old_ro->name); + freeIfMutable((char *)old_ro->name); free((void *)old_ro); addRemappedClass(cls, newCls); @@ -2399,7 +2281,7 @@ Class readClass(Class cls, bool headerIsBundle, bool headerIsPreoptimized) * * Locking: runtimeLock acquired by map_images **********************************************************************/ -void _read_images(header_info **hList, uint32_t hCount) +void _read_images(header_info **hList, uint32_t hCount, int totalClasses, int unoptimizedTotalClasses) { header_info *hi; uint32_t hIndex; @@ -2414,19 +2296,37 @@ void _read_images(header_info **hList, uint32_t hCount) #define EACH_HEADER \ hIndex = 0; \ - crashlog_header_name(nil) && hIndex < hCount && (hi = hList[hIndex]) && crashlog_header_name(hi); \ + hIndex < hCount && (hi = hList[hIndex]); \ hIndex++ if (!doneOnce) { doneOnce = YES; #if SUPPORT_NONPOINTER_ISA + // Disable non-pointer isa under some conditions. + +# if SUPPORT_INDEXED_ISA + // Disable nonpointer isa if any image contains old Swift code + for (EACH_HEADER) { + if (hi->info()->containsSwift() && + hi->info()->swiftVersion() < objc_image_info::SwiftVersion3) + { + DisableNonpointerIsa = true; + if (PrintRawIsa) { + _objc_inform("RAW ISA: disabling non-pointer isa because " + "the app or a framework contains Swift code " + "older than Swift 3.0"); + } + break; + } + } +# endif -# if TARGET_OS_MAC && !TARGET_OS_IPHONE +# if TARGET_OS_OSX // Disable non-pointer isa if the app is too old // (linked before OS X 10.11) if (dyld_get_program_sdk_version() < DYLD_MACOSX_VERSION_10_11) { - DisableIndexedIsa = true; + DisableNonpointerIsa = true; if (PrintRawIsa) { _objc_inform("RAW ISA: disabling non-pointer isa because " "the app is too old (SDK version " SDK_FORMAT ")", @@ -2437,10 +2337,10 @@ void _read_images(header_info **hList, uint32_t hCount) // Disable non-pointer isa if the app has a __DATA,__objc_rawisa section // New apps that load old extensions may need this. for (EACH_HEADER) { - if (hi->mhdr->filetype != MH_EXECUTE) continue; + if (hi->mhdr()->filetype != MH_EXECUTE) continue; unsigned long size; - if (getsectiondata(hi->mhdr, "__DATA", "__objc_rawisa", &size)) { - DisableIndexedIsa = true; + if (getsectiondata(hi->mhdr(), "__DATA", "__objc_rawisa", &size)) { + DisableNonpointerIsa = true; if (PrintRawIsa) { _objc_inform("RAW ISA: disabling non-pointer isa because " "the app has a __DATA,__objc_rawisa section"); @@ -2450,48 +2350,23 @@ void _read_images(header_info **hList, uint32_t hCount) } # endif - // Disable non-pointer isa for all GC apps. - if (UseGC) { - DisableIndexedIsa = true; - if (PrintRawIsa) { - _objc_inform("RAW ISA: disabling non-pointer isa because " - "the app is GC"); - } - } - #endif if (DisableTaggedPointers) { disableTaggedPointers(); } - // Count classes. Size various table based on the total. - int total = 0; - int unoptimizedTotal = 0; - for (EACH_HEADER) { - if (_getObjc2ClassList(hi, &count)) { - total += (int)count; - if (!hi->inSharedCache) unoptimizedTotal += count; - } - } - if (PrintConnecting) { - _objc_inform("CLASS: found %d classes during launch", total); + _objc_inform("CLASS: found %d classes during launch", totalClasses); } - // namedClasses (NOT realizedClasses) + // namedClasses // Preoptimized classes don't go in this table. // 4/3 is NXMapTable's load factor int namedClassesSize = - (isPreoptimized() ? unoptimizedTotal : total) * 4 / 3; + (isPreoptimized() ? unoptimizedTotalClasses : totalClasses) * 4 / 3; gdb_objc_realized_classes = NXCreateMapTable(NXStrValueMapPrototype, namedClassesSize); - - // realizedClasses and realizedMetaclasses - less than the full total - realized_class_hash = - NXCreateHashTable(NXPtrPrototype, total / 8, nil); - realized_metaclass_hash = - NXCreateHashTable(NXPtrPrototype, total / 8, nil); ts.log("IMAGE TIMES: first time tasks"); } @@ -2500,6 +2375,11 @@ void _read_images(header_info **hList, uint32_t hCount) // Discover classes. Fix up unresolved future classes. Mark bundle classes. for (EACH_HEADER) { + if (! mustReadClasses(hi)) { + // Image is sufficiently optimized that we need not call readClass() + continue; + } + bool headerIsBundle = hi->isBundle(); bool headerIsPreoptimized = hi->isPreoptimized(); @@ -2514,8 +2394,7 @@ void _read_images(header_info **hList, uint32_t hCount) // Non-lazily realize the class below. resolvedFutureClasses = (Class *) realloc(resolvedFutureClasses, - (resolvedFutureClassCount+1) - * sizeof(Class)); + (resolvedFutureClassCount+1) * sizeof(Class)); resolvedFutureClasses[resolvedFutureClassCount++] = newCls; } } @@ -2569,7 +2448,7 @@ void _read_images(header_info **hList, uint32_t hCount) if (PrintVtables) { _objc_inform("VTABLES: repairing %zu unsupported vtable dispatch " - "call sites in %s", count, hi->fname); + "call sites in %s", count, hi->fname()); } for (i = 0; i < count; i++) { fixupMessageRef(refs+i); @@ -2618,7 +2497,7 @@ void _read_images(header_info **hList, uint32_t hCount) if (!cls) continue; // hack for class __ARCLite__, which didn't get this above -#if TARGET_IPHONE_SIMULATOR +#if TARGET_OS_SIMULATOR if (cls->cache._buckets == (void*)&_objc_empty_cache && (cls->cache._mask || cls->cache._occupied)) { @@ -2643,7 +2522,7 @@ void _read_images(header_info **hList, uint32_t hCount) if (resolvedFutureClasses) { for (i = 0; i < resolvedFutureClassCount; i++) { realizeClass(resolvedFutureClasses[i]); - resolvedFutureClasses[i]->setRequiresRawIsa(false/*inherited*/); + resolvedFutureClasses[i]->setInstancesRequireRawIsa(false/*inherited*/); } free(resolvedFutureClasses); } @@ -2654,6 +2533,8 @@ void _read_images(header_info **hList, uint32_t hCount) for (EACH_HEADER) { category_t **catlist = _getObjc2CategoryList(hi, &count); + bool hasClassProperties = hi->info()->hasCategoryClassProperties(); + for (i = 0; i < count; i++) { category_t *cat = catlist[i]; Class cls = remapClass(cat->cls); @@ -2691,7 +2572,7 @@ void _read_images(header_info **hList, uint32_t hCount) } if (cat->classMethods || cat->protocols - /* || cat->classProperties */) + || (hasClassProperties && cat->_classProperties)) { addUnattachedCategoryForClass(cat, cls->ISA(), hi); if (cls->ISA()->isRealized()) { @@ -2728,11 +2609,11 @@ void _read_images(header_info **hList, uint32_t hCount) for (EACH_HEADER) { if (hi->isPreoptimized()) { _objc_inform("PREOPTIMIZATION: honoring preoptimized selectors " - "in %s", hi->fname); + "in %s", hi->fname()); } - else if (_objcHeaderOptimizedByDyld(hi)) { + else if (hi->info()->optimizedByDyld()) { _objc_inform("PREOPTIMIZATION: IGNORING preoptimized selectors " - "in %s", hi->fname); + "in %s", hi->fname()); } classref_t *classlist = _getObjc2ClassList(hi, &count); @@ -2867,27 +2748,44 @@ void _unload_image(header_info *hi) // Unload classes. - classref_t *classlist = _getObjc2ClassList(hi, &count); + // Gather classes from both __DATA,__objc_clslist + // and __DATA,__objc_nlclslist. arclite's hack puts a class in the latter + // only, and we need to unload that class if we unload an arclite image. - // First detach classes from each other. Then free each class. - // This avoid bugs where this loop unloads a subclass before its superclass + NXHashTable *classes = NXCreateHashTable(NXPtrPrototype, 0, nil); + classref_t *classlist; + classlist = _getObjc2ClassList(hi, &count); for (i = 0; i < count; i++) { Class cls = remapClass(classlist[i]); - if (cls) { - remove_class_from_loadable_list(cls); - detach_class(cls->ISA(), YES); - detach_class(cls, NO); - } + if (cls) NXHashInsert(classes, cls); } - + + classlist = _getObjc2NonlazyClassList(hi, &count); for (i = 0; i < count; i++) { Class cls = remapClass(classlist[i]); - if (cls) { - free_class(cls->ISA()); - free_class(cls); - } + if (cls) NXHashInsert(classes, cls); } + + // First detach classes from each other. Then free each class. + // This avoid bugs where this loop unloads a subclass before its superclass + + NXHashState hs; + Class cls; + + hs = NXInitHashState(classes); + while (NXNextHashState(classes, &hs, (void**)&cls)) { + remove_class_from_loadable_list(cls); + detach_class(cls->ISA(), YES); + detach_class(cls, NO); + } + hs = NXInitHashState(classes); + while (NXNextHashState(classes, &hs, (void**)&cls)) { + free_class(cls->ISA()); + free_class(cls); + } + + NXFreeHashTable(classes); // XXX FIXME -- Clean up protocols: // Support unloading protocols at dylib/image unload time @@ -2960,11 +2858,6 @@ void _unload_image(header_info *hi) if (!m) return nil; if (!imp) return nil; - if (ignoreSelector(m->name)) { - // Ignored methods stay ignored - return m->imp; - } - IMP old = m->imp; m->imp = imp; @@ -2995,13 +2888,6 @@ void method_exchangeImplementations(Method m1, Method m2) rwlock_writer_t lock(runtimeLock); - if (ignoreSelector(m1->name) || ignoreSelector(m2->name)) { - // Ignored methods stay ignored. Now they're both ignored. - m1->imp = (IMP)&_objc_ignored_method; - m2->imp = (IMP)&_objc_ignored_method; - return; - } - IMP m1_imp = m1->imp; m1->imp = m2->imp; m2->imp = m1_imp; @@ -3161,11 +3047,11 @@ static uint32_t getExtendedTypesIndexForMethod(protocol_t *proto, const method_t if (!mlist) return; if (mlist->isFixedUp()) return; - bool hasExtendedMethodTypes = proto->hasExtendedMethodTypes(); + const char **extTypes = proto->extendedMethodTypes(); fixupMethodList(mlist, true/*always copy for simplicity*/, - !hasExtendedMethodTypes/*sort if no ext*/); + !extTypes/*sort if no extended method types*/); - if (hasExtendedMethodTypes) { + if (extTypes) { // Sort method list and extended method types together. // fixupMethodList() can't do this. // fixme COW stomp @@ -3174,14 +3060,13 @@ static uint32_t getExtendedTypesIndexForMethod(protocol_t *proto, const method_t uint32_t junk; getExtendedTypesIndexesForMethod(proto, &mlist->get(0), required, instance, prefix, junk); - const char **types = proto->extendedMethodTypes; for (uint32_t i = 0; i < count; i++) { for (uint32_t j = i+1; j < count; j++) { method_t& mi = mlist->get(i); method_t& mj = mlist->get(j); if (mi.name > mj.name) { std::swap(mi, mj); - std::swap(types[prefix+i], types[prefix+j]); + std::swap(extTypes[prefix+i], extTypes[prefix+j]); } } } @@ -3323,7 +3208,7 @@ static uint32_t getExtendedTypesIndexForMethod(protocol_t *proto, const method_t runtimeLock.assertLocked(); if (!proto) return nil; - if (!proto->hasExtendedMethodTypes()) return nil; + if (!proto->extendedMethodTypes()) return nil; assert(proto->isFixedUp()); @@ -3334,7 +3219,7 @@ static uint32_t getExtendedTypesIndexForMethod(protocol_t *proto, const method_t uint32_t i = getExtendedTypesIndexForMethod(proto, m, isRequiredMethod, isInstanceMethod); - return proto->extendedMethodTypes[i]; + return proto->extendedMethodTypes()[i]; } // No method with that name. Search incorporated protocols. @@ -3379,7 +3264,7 @@ static uint32_t getExtendedTypesIndexForMethod(protocol_t *proto, const method_t const char * protocol_t::demangledName() { - assert(size >= offsetof(protocol_t, _demangledName)+sizeof(_demangledName)); + assert(hasDemangledNameField()); if (! _demangledName) { char *de = copySwiftV1DemangledName(mangledName, true/*isProtocol*/); @@ -3541,13 +3426,14 @@ BOOL protocol_isEqual(Protocol *self, Protocol *other) { runtimeLock.assertLocked(); - if (!isRequiredProperty || !isInstanceProperty) { - // Only required instance properties are currently supported + if (!isRequiredProperty) { + // Only required properties are currently supported. return nil; } - property_list_t *plist; - if ((plist = proto->instanceProperties)) { + property_list_t *plist = isInstanceProperty ? + proto->instanceProperties : proto->classProperties(); + if (plist) { for (auto& prop : *plist) { if (0 == strcmp(name, prop.name)) { return ∝ @@ -3584,6 +3470,7 @@ objc_property_t protocol_getProperty(Protocol *p, const char *name, /*********************************************************************** * protocol_copyPropertyList +* protocol_copyPropertyList2 * fixme * Locking: acquires runtimeLock **********************************************************************/ @@ -3611,19 +3498,31 @@ objc_property_t protocol_getProperty(Protocol *p, const char *name, return result; } -objc_property_t *protocol_copyPropertyList(Protocol *proto, unsigned int *outCount) +objc_property_t * +protocol_copyPropertyList2(Protocol *proto, unsigned int *outCount, + BOOL isRequiredProperty, BOOL isInstanceProperty) { - if (!proto) { + if (!proto || !isRequiredProperty) { + // Optional properties are not currently supported. if (outCount) *outCount = 0; return nil; } rwlock_reader_t lock(runtimeLock); - property_list_t *plist = newprotocol(proto)->instanceProperties; + property_list_t *plist = isInstanceProperty + ? newprotocol(proto)->instanceProperties + : newprotocol(proto)->classProperties(); return (objc_property_t *)copyPropertyList(plist, outCount); } +objc_property_t * +protocol_copyPropertyList(Protocol *proto, unsigned int *outCount) +{ + return protocol_copyPropertyList2(proto, outCount, + YES/*required*/, YES/*instance*/); +} + /*********************************************************************** * protocol_copyProtocolList @@ -3686,7 +3585,7 @@ objc_property_t protocol_getProperty(Protocol *p, const char *name, result->initProtocolIsa(cls); result->size = sizeof(protocol_t); // fixme mangle the name if it looks swift-y? - result->mangledName = strdup(name); + result->mangledName = strdupIfMutable(name); // fixme reserve name without installing @@ -3797,7 +3696,7 @@ void objc_registerProtocol(Protocol *proto_gen) method_t& meth = list->get(list->count++); meth.name = name; - meth.types = strdup(types ? types : ""); + meth.types = types ? strdupIfMutable(types) : ""; meth.imp = nil; } @@ -3852,7 +3751,7 @@ void objc_registerProtocol(Protocol *proto_gen) } property_t& prop = plist->get(plist->count++); - prop.name = strdup(name); + prop.name = strdupIfMutable(name); prop.attributes = copyPropertyAttributeString(attrs, count); } @@ -3881,11 +3780,13 @@ void objc_registerProtocol(Protocol *proto_gen) if (isRequiredProperty && isInstanceProperty) { protocol_addProperty_nolock(proto->instanceProperties, name, attrs, count); } - //else if (isRequiredProperty && !isInstanceProperty) { - // protocol_addProperty_nolock(proto->classProperties, name, attrs, count); - //} else if (!isRequiredProperty && isInstanceProperty) { + else if (isRequiredProperty && !isInstanceProperty) { + protocol_addProperty_nolock(proto->_classProperties, name, attrs, count); + } + //else if (!isRequiredProperty && isInstanceProperty) { // protocol_addProperty_nolock(proto->optionalInstanceProperties, name, attrs, count); - //} else /* !isRequiredProperty && !isInstanceProperty) */ { + //} + //else /* !isRequiredProperty && !isInstanceProperty) */ { // protocol_addProperty_nolock(proto->optionalClassProperties, name, attrs, count); //} } @@ -3904,25 +3805,21 @@ void objc_registerProtocol(Protocol *proto_gen) realizeAllClasses(); - int count; - Class cls; - NXHashState state; - NXHashTable *classes = realizedClasses(); - int allCount = NXCountHashTable(classes); - - if (!buffer) { - return allCount; - } + __block int count = 0; + foreach_realized_class_and_metaclass(^(Class cls) { + if (!cls->isMetaClass()) count++; + }); - count = 0; - state = NXInitHashState(classes); - while (count < bufferLen && - NXNextHashState(classes, &state, (void **)&cls)) - { - buffer[count++] = cls; + if (buffer) { + __block int c = 0; + foreach_realized_class_and_metaclass(^(Class cls) { + if (c < bufferLen && !cls->isMetaClass()) { + buffer[c++] = cls; + } + }); } - return allCount; + return count; } @@ -3944,20 +3841,23 @@ void objc_registerProtocol(Protocol *proto_gen) realizeAllClasses(); Class *result = nil; - NXHashTable *classes = realizedClasses(); - unsigned int count = NXCountHashTable(classes); + + __block unsigned int count = 0; + foreach_realized_class_and_metaclass(^(Class cls) { + if (!cls->isMetaClass()) count++; + }); if (count > 0) { - Class cls; - NXHashState state = NXInitHashState(classes); result = (Class *)malloc((1+count) * sizeof(Class)); - count = 0; - while (NXNextHashState(classes, &state, (void **)&cls)) { - result[count++] = cls; - } - result[count] = nil; + __block unsigned int c = 0; + foreach_realized_class_and_metaclass(^(Class cls) { + if (!cls->isMetaClass()) { + result[c++] = cls; + } + }); + result[c] = nil; } - + if (outCount) *outCount = count; return result; } @@ -4040,9 +3940,7 @@ void objc_registerProtocol(Protocol *proto_gen) count = 0; for (auto& meth : cls->data()->methods) { - if (! ignoreSelector(meth.name)) { - result[count++] = &meth; - } + result[count++] = &meth; } result[count] = nil; } @@ -4227,6 +4125,20 @@ void objc_registerProtocol(Protocol *proto_gen) } +/*********************************************************************** +* category_t::propertiesForMeta +* Return a category's instance or class properties. +* hi is the image containing the category. +**********************************************************************/ +property_list_t * +category_t::propertiesForMeta(bool isMeta, struct header_info *hi) +{ + if (!isMeta) return instanceProperties; + else if (hi->info()->hasCategoryClassProperties()) return _classProperties; + else return nil; +} + + /*********************************************************************** * class_copyProtocolList * fixme @@ -4299,26 +4211,6 @@ void objc_registerProtocol(Protocol *proto_gen) } -/*********************************************************************** - * _class_getInstanceStart - * Uses alignedInstanceStart() to ensure that ARR layout strings are - * interpreted relative to the first word aligned ivar of an object. - * Locking: none - **********************************************************************/ - -static uint32_t -alignedInstanceStart(Class cls) -{ - assert(cls); - assert(cls->isRealized()); - return (uint32_t)word_align(cls->data()->ro->instanceStart); -} - -uint32_t _class_getInstanceStart(Class cls) { - return alignedInstanceStart(cls); -} - - /*********************************************************************** * saveTemporaryString * Save a string in a thread-local FIFO buffer. @@ -4372,6 +4264,8 @@ uint32_t _class_getInstanceStart(Class cls) { * If realize=false, the class must already be realized or future. * Locking: If realize=true, runtimeLock must be held for writing by the caller. **********************************************************************/ +static mutex_t DemangleCacheLock; +static NXHashTable *DemangleCache; const char * objc_class::demangledName(bool realize) { @@ -4403,14 +4297,30 @@ uint32_t _class_getInstanceStart(Class cls) { // Class is not yet realized and name is mangled. Realize the class. // Only objc_copyClassNamesForImage() should get here. - runtimeLock.assertWriting(); - assert(realize); + + // fixme lldb's calls to class_getName() can also get here when + // interrogating the dyld shared cache. (rdar://27258517) + // fixme runtimeLock.assertWriting(); + // fixme assert(realize); + if (realize) { + runtimeLock.assertWriting(); realizeClass((Class)this); data()->demangledName = de; return de; - } else { - return de; // bug - just leak + } + else { + // Save the string to avoid leaks. + char *cached; + { + mutex_locker_t lock(DemangleCacheLock); + if (!DemangleCache) { + DemangleCache = NXCreateHashTable(NXStrPrototype, 0, nil); + } + cached = (char *)NXHashInsertIfAbsent(DemangleCache, de); + } + if (cached != de) free(de); + return cached; } } @@ -4423,7 +4333,8 @@ uint32_t _class_getInstanceStart(Class cls) { const char *class_getName(Class cls) { if (!cls) return "nil"; - assert(cls->isRealized() || cls->isFuture()); + // fixme lldb calls class_getName() on unrealized classes (rdar://27258517) + // assert(cls->isRealized() || cls->isFuture()); return cls->demangledName(); } @@ -4690,13 +4601,6 @@ IMP lookUpImpOrForward(Class cls, SEL sel, id inst, retry: runtimeLock.read(); - // Ignore GC selectors - if (ignoreSelector(sel)) { - imp = _objc_ignored_method; - cache_fill(cls, sel, imp, inst); - goto done; - } - // Try this class's cache. imp = cache_getImp(cls, sel); @@ -4760,12 +4664,6 @@ IMP lookUpImpOrForward(Class cls, SEL sel, id inst, done: runtimeLock.unlockRead(); - // paranoia: look for ignored selectors with non-ignored implementations - assert(!(ignoreSelector(sel) && imp != (IMP)&_objc_ignored_method)); - - // paranoia: never let uncached leak out - assert(imp != _objc_msgSend_uncached_impcache); - return imp; } @@ -4793,7 +4691,7 @@ IMP lookupMethodInClassAndLoadCache(Class cls, SEL sel) Method meth; IMP imp; - // fixme this is incomplete - no resolver, +initialize, GC - + // fixme this is incomplete - no resolver, +initialize - // but it's only used for .cxx_construct/destruct so we don't care assert(sel == SEL_cxx_construct || sel == SEL_cxx_destruct); @@ -4885,7 +4783,6 @@ Class gdb_object_getClass(id obj) // Also print custom RR/AWZ because we probably haven't done it yet. // Special cases: - // GC's RR and AWZ are never default. // NSObject AWZ class methods are default. // NSObject RR instance methods are default. // updateCustomRR_AWZ() also knows these special cases. @@ -4893,12 +4790,7 @@ Class gdb_object_getClass(id obj) bool inherited; bool metaCustomAWZ = NO; - if (UseGC) { - // GC is always custom AWZ - metaCustomAWZ = YES; - inherited = NO; - } - else if (MetaclassNSObjectAWZSwizzled) { + if (MetaclassNSObjectAWZSwizzled) { // Somebody already swizzled NSObject's methods metaCustomAWZ = YES; inherited = NO; @@ -4945,12 +4837,7 @@ Class gdb_object_getClass(id obj) bool clsCustomRR = NO; - if (UseGC) { - // GC is always custom RR - clsCustomRR = YES; - inherited = NO; - } - else if (ClassNSObjectRRSwizzled) { + if (ClassNSObjectRRSwizzled) { // Somebody already swizzled NSObject's methods clsCustomRR = YES; inherited = NO; @@ -5006,16 +4893,6 @@ Class gdb_object_getClass(id obj) } -/*********************************************************************** - * _class_usesAutomaticRetainRelease - * Returns YES if class was compiled with -fobjc-arc - **********************************************************************/ -BOOL _class_usesAutomaticRetainRelease(Class cls) -{ - return bool(cls->data()->ro->flags & RO_IS_ARR); -} - - /*********************************************************************** * Return YES if sel is used by retain/release implementors **********************************************************************/ @@ -5088,10 +4965,10 @@ BOOL _class_usesAutomaticRetainRelease(Class cls) } void -objc_class::printRequiresRawIsa(bool inherited) +objc_class::printInstancesRequireRawIsa(bool inherited) { assert(PrintRawIsa); - assert(requiresRawIsa()); + assert(instancesRequireRawIsa()); _objc_inform("RAW ISA: %s%s%s", nameForLogging(), isMetaClass() ? " (meta)" : "", inherited ? " (inherited)" : ""); @@ -5158,30 +5035,51 @@ BOOL _class_usesAutomaticRetainRelease(Class cls) /*********************************************************************** * Mark this class and all of its subclasses as requiring raw isa pointers **********************************************************************/ -void objc_class::setRequiresRawIsa(bool inherited) +void objc_class::setInstancesRequireRawIsa(bool inherited) { Class cls = (Class)this; runtimeLock.assertWriting(); - if (requiresRawIsa()) return; + if (instancesRequireRawIsa()) return; foreach_realized_class_and_subclass(cls, ^(Class c){ - if (c->isInitialized()) { - _objc_fatal("too late to require raw isa"); - return; - } - if (c->requiresRawIsa()) { + if (c->instancesRequireRawIsa()) { // fixme short circuit recursion? return; } - c->bits.setRequiresRawIsa(); + c->bits.setInstancesRequireRawIsa(); - if (PrintRawIsa) c->printRequiresRawIsa(inherited || c != cls); + if (PrintRawIsa) c->printInstancesRequireRawIsa(inherited || c != cls); }); } +/*********************************************************************** +* Choose a class index. +* Set instancesRequireRawIsa if no more class indexes are available. +**********************************************************************/ +void objc_class::chooseClassArrayIndex() +{ +#if SUPPORT_INDEXED_ISA + Class cls = (Class)this; + runtimeLock.assertWriting(); + + if (objc_indexed_classes_count >= ISA_INDEX_COUNT) { + // No more indexes available. + assert(cls->classArrayIndex() == 0); + cls->setInstancesRequireRawIsa(false/*not inherited*/); + return; + } + + unsigned index = objc_indexed_classes_count++; + if (index == 0) index = objc_indexed_classes_count++; // index 0 is unused + classForIndex(index) = cls; + cls->setClassArrayIndex(index); +#endif +} + + /*********************************************************************** * Update custom RR and AWZ when a method changes its IMP **********************************************************************/ @@ -5301,7 +5199,7 @@ BOOL _class_usesAutomaticRetainRelease(Class cls) /*********************************************************************** * class_setIvarLayout -* Changes the class's GC scan layout. +* Changes the class's ivar layout. * nil layout means no unscanned ivars * The class must be under construction. * fixme: sanity-check layout vs instance size? @@ -5317,7 +5215,7 @@ BOOL _class_usesAutomaticRetainRelease(Class cls) // Can only change layout of in-construction classes. // note: if modifications to post-construction classes were - // allowed, there would be a race below (us vs. concurrent GC scan) + // allowed, there would be a race below (us vs. concurrent object_setIvar) if (!(cls->data()->flags & RW_CONSTRUCTING)) { _objc_inform("*** Can't set ivar layout for already-registered " "class '%s'", cls->nameForLogging()); @@ -5362,7 +5260,7 @@ BOOL _class_usesAutomaticRetainRelease(Class cls) /*********************************************************************** * class_setWeakIvarLayout -* Changes the class's GC weak layout. +* Changes the class's weak ivar layout. * nil layout means no weak ivars * The class must be under construction. * fixme: sanity-check layout vs instance size? @@ -5378,7 +5276,7 @@ BOOL _class_usesAutomaticRetainRelease(Class cls) // Can only change layout of in-construction classes. // note: if modifications to post-construction classes were - // allowed, there would be a race below (us vs. concurrent GC scan) + // allowed, there would be a race below (us vs. concurrent object_setIvar) if (!(cls->data()->flags & RW_CONSTRUCTING)) { _objc_inform("*** Can't set weak ivar layout for already-registered " "class '%s'", cls->nameForLogging()); @@ -5392,20 +5290,66 @@ BOOL _class_usesAutomaticRetainRelease(Class cls) } +/*********************************************************************** +* getIvar +* Look up an ivar by name. +* Locking: runtimeLock must be read- or write-locked by the caller. +**********************************************************************/ +static ivar_t *getIvar(Class cls, const char *name) +{ + runtimeLock.assertLocked(); + + const ivar_list_t *ivars; + assert(cls->isRealized()); + if ((ivars = cls->data()->ro->ivars)) { + for (auto& ivar : *ivars) { + if (!ivar.offset) continue; // anonymous bitfield + + // ivar.name may be nil for anonymous bitfields etc. + if (ivar.name && 0 == strcmp(name, ivar.name)) { + return &ivar; + } + } + } + + return nil; +} + + +/*********************************************************************** +* _class_getClassForIvar +* Given a class and an ivar that is in it or one of its superclasses, +* find the actual class that defined the ivar. +**********************************************************************/ +Class _class_getClassForIvar(Class cls, Ivar ivar) +{ + rwlock_reader_t lock(runtimeLock); + + for ( ; cls; cls = cls->superclass) { + if (auto ivars = cls->data()->ro->ivars) { + if (ivars->containsIvar(ivar)) { + return cls; + } + } + } + + return nil; +} + + /*********************************************************************** * _class_getVariable * fixme * Locking: read-locks runtimeLock **********************************************************************/ Ivar -_class_getVariable(Class cls, const char *name, Class *memberOf) +_class_getVariable(Class cls, const char *name) { rwlock_reader_t lock(runtimeLock); for ( ; cls; cls = cls->superclass) { ivar_t *ivar = getIvar(cls, name); if (ivar) { - if (memberOf) *memberOf = cls; return ivar; } } @@ -5472,12 +5416,8 @@ BOOL class_conformsToProtocol(Class cls, Protocol *proto_gen) (uint32_t)sizeof(method_t) | fixed_up_method_list; newlist->count = 1; newlist->first.name = name; - newlist->first.types = strdup(types); - if (!ignoreSelector(name)) { - newlist->first.imp = imp; - } else { - newlist->first.imp = (IMP)&_objc_ignored_method; - } + newlist->first.types = strdupIfMutable(types); + newlist->first.imp = imp; prepareMethodLists(cls, &newlist, 1, NO, NO); cls->data()->methods.attachLists(&newlist, 1); @@ -5573,8 +5513,8 @@ BOOL class_conformsToProtocol(Class cls, Protocol *proto_gen) ivar.offset = (int32_t *)malloc(sizeof(int32_t)); #endif *ivar.offset = offset; - ivar.name = name ? strdup(name) : nil; - ivar.type = strdup(type); + ivar.name = name ? strdupIfMutable(name) : nil; + ivar.type = strdupIfMutable(type); ivar.alignment_raw = alignment; ivar.size = (uint32_t)size; @@ -5651,7 +5591,7 @@ BOOL class_addProtocol(Class cls, Protocol *protocol_gen) malloc(sizeof(*proplist)); proplist->count = 1; proplist->entsizeAndFlags = sizeof(proplist->first); - proplist->first.name = strdup(name); + proplist->first.name = strdupIfMutable(name); proplist->first.attributes = copyPropertyAttributeString(attrs, count); cls->data()->properties.attachLists(&proplist, 1); @@ -5736,7 +5676,7 @@ bool includeClassHandler __attribute__((unused))) rw->ro = (class_ro_t *) memdup(original->data()->ro, sizeof(*original->data()->ro)); - *(char **)&rw->ro->name = strdup(name); + *(char **)&rw->ro->name = strdupIfMutable(name); rw->methods = original->data()->methods.duplicate(); @@ -5744,16 +5684,18 @@ bool includeClassHandler __attribute__((unused))) rw->properties = original->data()->properties; rw->protocols = original->data()->protocols; + duplicate->chooseClassArrayIndex(); + if (duplicate->superclass) { addSubclass(duplicate->superclass, duplicate); + // duplicate->isa == original->isa so don't addSubclass() for it + } else { + addRootClass(duplicate); } // Don't methodize class - construction above is correct addNamedClass(duplicate, duplicate->data()->ro->name); - addRealizedClass(duplicate); - // no: duplicate->ISA == original->ISA - // addRealizedMetaclass(duplicate->ISA); if (PrintConnecting) { _objc_inform("CLASS: realizing class '%s' (duplicate of %s) %p %p", @@ -5779,9 +5721,6 @@ static void objc_initializeClassPair_internal(Class superclass, const char *name runtimeLock.assertWriting(); class_ro_t *cls_ro_w, *meta_ro_w; - - cls->cache.initializeToEmpty(); - meta->cache.initializeToEmpty(); cls->setData((class_rw_t *)calloc(sizeof(class_rw_t), 1)); meta->setData((class_rw_t *)calloc(sizeof(class_rw_t), 1)); @@ -5815,12 +5754,15 @@ static void objc_initializeClassPair_internal(Class superclass, const char *name meta->setInstanceSize(meta_ro_w->instanceStart); } - cls_ro_w->name = strdup(name); - meta_ro_w->name = strdup(name); + cls_ro_w->name = strdupIfMutable(name); + meta_ro_w->name = strdupIfMutable(name); cls_ro_w->ivarLayout = &UnsetLayout; cls_ro_w->weakIvarLayout = &UnsetLayout; + meta->chooseClassArrayIndex(); + cls->chooseClassArrayIndex(); + // Connect to superclasses and metaclasses cls->initClassIsa(meta); if (superclass) { @@ -5833,8 +5775,12 @@ static void objc_initializeClassPair_internal(Class superclass, const char *name meta->initClassIsa(meta); cls->superclass = Nil; meta->superclass = cls; + addRootClass(cls); addSubclass(cls, meta); } + + cls->cache.initializeToEmpty(); + meta->cache.initializeToEmpty(); } @@ -5937,66 +5883,12 @@ void objc_registerClassPair(Class cls) return; } - // Build ivar layouts - if (UseGC) { - Class supercls = cls->superclass; - class_ro_t *ro_w = (class_ro_t *)cls->data()->ro; - - if (ro_w->ivarLayout != &UnsetLayout) { - // Class builder already called class_setIvarLayout. - } - else if (!supercls) { - // Root class. Scan conservatively (should be isa ivar only). - ro_w->ivarLayout = nil; - } - else if (ro_w->ivars == nil) { - // No local ivars. Use superclass's layouts. - ro_w->ivarLayout = - ustrdupMaybeNil(supercls->data()->ro->ivarLayout); - } - else { - // Has local ivars. Build layouts based on superclass. - layout_bitmap bitmap = - layout_bitmap_create(supercls->data()->ro->ivarLayout, - supercls->unalignedInstanceSize(), - cls->unalignedInstanceSize(), NO); - for (const auto& ivar : *ro_w->ivars) { - if (!ivar.offset) continue; // anonymous bitfield - - layout_bitmap_set_ivar(bitmap, ivar.type, *ivar.offset); - } - ro_w->ivarLayout = layout_string_create(bitmap); - layout_bitmap_free(bitmap); - } - - if (ro_w->weakIvarLayout != &UnsetLayout) { - // Class builder already called class_setWeakIvarLayout. - } - else if (!supercls) { - // Root class. No weak ivars (should be isa ivar only). - ro_w->weakIvarLayout = nil; - } - else if (ro_w->ivars == nil) { - // No local ivars. Use superclass's layout. - ro_w->weakIvarLayout = - ustrdupMaybeNil(supercls->data()->ro->weakIvarLayout); - } - else { - // Has local ivars. Build layout based on superclass. - // No way to add weak ivars yet. - ro_w->weakIvarLayout = - ustrdupMaybeNil(supercls->data()->ro->weakIvarLayout); - } - } - // Clear "under construction" bit, set "done constructing" bit cls->ISA()->changeInfo(RW_CONSTRUCTED, RW_CONSTRUCTING | RW_REALIZING); cls->changeInfo(RW_CONSTRUCTED, RW_CONSTRUCTING | RW_REALIZING); - // Add to named and realized classes + // Add to named class table. addNamedClass(cls, cls->data()->ro->name); - addRealizedClass(cls); - addRealizedMetaclass(cls->ISA()); } @@ -6056,15 +5948,14 @@ static void detach_class(Class cls, bool isMeta) Class supercls = cls->superclass; if (supercls) { removeSubclass(supercls, cls); + } else { + removeRootClass(cls); } } // class tables and +load queue if (!isMeta) { removeNamedClass(cls, cls->mangledName()); - removeRealizedClass(cls); - } else { - removeRealizedMetaclass(cls); } } @@ -6180,9 +6071,9 @@ void objc_disposeClassPair(Class cls) // Read class's info bits all at once for performance bool hasCxxCtor = cls->hasCxxCtor(); bool hasCxxDtor = cls->hasCxxDtor(); - bool fast = cls->canAllocIndexed(); + bool fast = cls->canAllocNonpointer(); - if (!UseGC && fast) { + if (fast) { obj->initInstanceIsa(cls, hasCxxDtor); } else { obj->initIsa(cls); @@ -6215,24 +6106,18 @@ static __attribute__((always_inline)) // Read class's info bits all at once for performance bool hasCxxCtor = cls->hasCxxCtor(); bool hasCxxDtor = cls->hasCxxDtor(); - bool fast = cls->canAllocIndexed(); + bool fast = cls->canAllocNonpointer(); size_t size = cls->instanceSize(extraBytes); if (outAllocatedSize) *outAllocatedSize = size; id obj; - if (!UseGC && !zone && fast) { + if (!zone && fast) { obj = (id)calloc(1, size); if (!obj) return nil; obj->initInstanceIsa(cls, hasCxxDtor); } else { -#if SUPPORT_GC - if (UseGC) { - obj = (id)auto_zone_allocate_object(gc_zone, size, - AUTO_OBJECT_SCANNED, 0, 1); - } else -#endif if (zone) { obj = (id)malloc_zone_calloc ((malloc_zone_t *)zone, 1, size); } else { @@ -6240,7 +6125,7 @@ static __attribute__((always_inline)) } if (!obj) return nil; - // Use non-indexed isa on the assumption that they might be + // Use raw pointer isa on the assumption that they might be // doing something weird with the zone or RR. obj->initIsa(cls); } @@ -6276,58 +6161,6 @@ static __attribute__((always_inline)) results, num_requested); } -static bool classOrSuperClassesUseARR(Class cls) { - while (cls) { - if (_class_usesAutomaticRetainRelease(cls)) return true; - cls = cls->superclass; - } - return false; -} - -static void arr_fixup_copied_references(id newObject, id oldObject) -{ - // use ARR layouts to correctly copy the references from old object to new, both strong and weak. - Class cls = oldObject->ISA(); - for ( ; cls; cls = cls->superclass) { - if (_class_usesAutomaticRetainRelease(cls)) { - // FIXME: align the instance start to nearest id boundary. This currently handles the case where - // the the compiler folds a leading BOOL (char, short, etc.) into the alignment slop of a superclass. - size_t instanceStart = _class_getInstanceStart(cls); - const uint8_t *strongLayout = class_getIvarLayout(cls); - if (strongLayout) { - id *newPtr = (id *)((char*)newObject + instanceStart); - unsigned char byte; - while ((byte = *strongLayout++)) { - unsigned skips = (byte >> 4); - unsigned scans = (byte & 0x0F); - newPtr += skips; - while (scans--) { - // ensure strong references are properly retained. - id value = *newPtr++; - if (value) objc_retain(value); - } - } - } - const uint8_t *weakLayout = class_getWeakIvarLayout(cls); - // fix up weak references if any. - if (weakLayout) { - id *newPtr = (id *)((char*)newObject + instanceStart), *oldPtr = (id *)((char*)oldObject + instanceStart); - unsigned char byte; - while ((byte = *weakLayout++)) { - unsigned skips = (byte >> 4); - unsigned weaks = (byte & 0x0F); - newPtr += skips, oldPtr += skips; - while (weaks--) { - *newPtr = nil; - objc_storeWeak(newPtr, objc_loadWeak(oldPtr)); - ++newPtr, ++oldPtr; - } - } - } - } - } -} - /*********************************************************************** * object_copyFromZone * fixme @@ -6350,19 +6183,9 @@ static void arr_fixup_copied_references(id newObject, id oldObject) uint8_t *copyDst = (uint8_t *)obj + sizeof(Class); uint8_t *copySrc = (uint8_t *)oldObj + sizeof(Class); size_t copySize = size - sizeof(Class); -#if SUPPORT_GC - objc_memmove_collectable(copyDst, copySrc, copySize); -#else memmove(copyDst, copySrc, copySize); -#endif -#if SUPPORT_GC - if (UseGC) - gc_fixup_weakreferences(obj, oldObj); - else -#endif - if (classOrSuperClassesUseARR(cls)) - arr_fixup_copied_references(obj, oldObj); + fixupCopiedIvars(obj, oldObj); return obj; } @@ -6411,24 +6234,21 @@ static void arr_fixup_copied_references(id newObject, id oldObject) * objc_destructInstance * Destroys an instance without freeing memory. * Calls C++ destructors. -* Calls ARR ivar cleanup. +* Calls ARC ivar cleanup. * Removes associative references. * Returns `obj`. Does nothing if `obj` is nil. -* Be warned that GC DOES NOT CALL THIS. If you edit this, also edit finalize. -* CoreFoundation and other clients do call this under GC. **********************************************************************/ void *objc_destructInstance(id obj) { if (obj) { // Read all of the flags at once for performance. bool cxx = obj->hasCxxDtor(); - bool assoc = !UseGC && obj->hasAssociatedObjects(); - bool dealloc = !UseGC; + bool assoc = obj->hasAssociatedObjects(); // This order is important. if (cxx) object_cxxDestruct(obj); if (assoc) _object_remove_assocations(obj); - if (dealloc) obj->clearDeallocating(); + obj->clearDeallocating(); } return obj; @@ -6445,14 +6265,7 @@ static void arr_fixup_copied_references(id newObject, id oldObject) { if (!obj) return nil; - objc_destructInstance(obj); - -#if SUPPORT_GC - if (UseGC) { - auto_zone_retain(gc_zone, obj); // gc free expects rc==1 - } -#endif - + objc_destructInstance(obj); free(obj); return nil; @@ -6486,6 +6299,17 @@ Class _objc_getFreedObjectClass (void) * The tag index defines the object's class. * The payload format is defined by the object's class. * +* If the tag index is 0b111, the tagged pointer object uses an +* "extended" representation, allowing more classes but with smaller payloads: +* (LSB) +* 1 bit set if tagged, clear if ordinary object pointer +* 3 bits 0b111 +* 8 bits extended tag index +* 52 bits payload +* (MSB) +* +* Some architectures reverse the MSB and LSB in these representations. +* * This representation is subject to change. Representation-agnostic SPI is: * objc-internal.h for class implementers. * objc-gdb.h for debuggers. @@ -6500,6 +6324,13 @@ Class _objc_getFreedObjectClass (void) unsigned objc_debug_taggedpointer_payload_rshift = 0; Class objc_debug_taggedpointer_classes[1] = { nil }; +uintptr_t objc_debug_taggedpointer_ext_mask = 0; +unsigned objc_debug_taggedpointer_ext_slot_shift = 0; +uintptr_t objc_debug_taggedpointer_ext_slot_mask = 0; +unsigned objc_debug_taggedpointer_ext_payload_lshift = 0; +unsigned objc_debug_taggedpointer_ext_payload_rshift = 0; +Class objc_debug_taggedpointer_ext_classes[1] = { nil }; + static void disableTaggedPointers() { } @@ -6507,14 +6338,22 @@ Class _objc_getFreedObjectClass (void) // The "slot" used in the class table and given to the debugger // includes the is-tagged bit. This makes objc_msgSend faster. +// The "ext" representation doesn't do that. -uintptr_t objc_debug_taggedpointer_mask = TAG_MASK; -unsigned objc_debug_taggedpointer_slot_shift = TAG_SLOT_SHIFT; -uintptr_t objc_debug_taggedpointer_slot_mask = TAG_SLOT_MASK; -unsigned objc_debug_taggedpointer_payload_lshift = TAG_PAYLOAD_LSHIFT; -unsigned objc_debug_taggedpointer_payload_rshift = TAG_PAYLOAD_RSHIFT; +uintptr_t objc_debug_taggedpointer_mask = _OBJC_TAG_MASK; +unsigned objc_debug_taggedpointer_slot_shift = _OBJC_TAG_SLOT_SHIFT; +uintptr_t objc_debug_taggedpointer_slot_mask = _OBJC_TAG_SLOT_MASK; +unsigned objc_debug_taggedpointer_payload_lshift = _OBJC_TAG_PAYLOAD_LSHIFT; +unsigned objc_debug_taggedpointer_payload_rshift = _OBJC_TAG_PAYLOAD_RSHIFT; // objc_debug_taggedpointer_classes is defined in objc-msg-*.s +uintptr_t objc_debug_taggedpointer_ext_mask = _OBJC_TAG_EXT_MASK; +unsigned objc_debug_taggedpointer_ext_slot_shift = _OBJC_TAG_EXT_SLOT_SHIFT; +uintptr_t objc_debug_taggedpointer_ext_slot_mask = _OBJC_TAG_EXT_SLOT_MASK; +unsigned objc_debug_taggedpointer_ext_payload_lshift = _OBJC_TAG_EXT_PAYLOAD_LSHIFT; +unsigned objc_debug_taggedpointer_ext_payload_rshift = _OBJC_TAG_EXT_PAYLOAD_RSHIFT; +// objc_debug_taggedpointer_ext_classes is defined in objc-msg-*.s + static void disableTaggedPointers() { @@ -6523,19 +6362,46 @@ Class _objc_getFreedObjectClass (void) objc_debug_taggedpointer_slot_mask = 0; objc_debug_taggedpointer_payload_lshift = 0; objc_debug_taggedpointer_payload_rshift = 0; + + objc_debug_taggedpointer_ext_mask = 0; + objc_debug_taggedpointer_ext_slot_shift = 0; + objc_debug_taggedpointer_ext_slot_mask = 0; + objc_debug_taggedpointer_ext_payload_lshift = 0; + objc_debug_taggedpointer_ext_payload_rshift = 0; } -static int -tagSlotForTagIndex(objc_tag_index_t tag) + +// Returns a pointer to the class's storage in the tagged class arrays. +// Assumes the tag is a valid basic tag. +static Class * +classSlotForBasicTagIndex(objc_tag_index_t tag) { + // Array index in objc_tag_classes includes the tagged bit itself #if SUPPORT_MSB_TAGGED_POINTERS - return 0x8 | tag; + return &objc_tag_classes[0x8 | tag]; #else - return (tag << 1) | 1; + return &objc_tag_classes[(tag << 1) | 1]; #endif } +// Returns a pointer to the class's storage in the tagged class arrays, +// or nil if the tag is out of range. +static Class * +classSlotForTagIndex(objc_tag_index_t tag) +{ + if (tag >= OBJC_TAG_First60BitPayload && tag <= OBJC_TAG_Last60BitPayload) { + return classSlotForBasicTagIndex(tag); + } + + if (tag >= OBJC_TAG_First52BitPayload && tag <= OBJC_TAG_Last52BitPayload) { + return &objc_tag_ext_classes[tag - OBJC_TAG_First52BitPayload]; + } + + return nil; +} + + /*********************************************************************** * _objc_registerTaggedPointerClass * Set the class to use for the given tagged pointer index. @@ -6544,17 +6410,17 @@ Class _objc_getFreedObjectClass (void) **********************************************************************/ void _objc_registerTaggedPointerClass(objc_tag_index_t tag, Class cls) -{ +{ if (objc_debug_taggedpointer_mask == 0) { _objc_fatal("tagged pointers are disabled"); } - if ((unsigned int)tag >= TAG_COUNT) { - _objc_fatal("tag index %u is too large.", tag); + Class *slot = classSlotForTagIndex(tag); + if (!slot) { + _objc_fatal("tag index %u is invalid", (unsigned int)tag); } - int slot = tagSlotForTagIndex(tag); - Class oldCls = objc_tag_classes[slot]; + Class oldCls = *slot; if (cls && oldCls && cls != oldCls) { _objc_fatal("tag index %u used for two different classes " @@ -6563,14 +6429,19 @@ Class _objc_getFreedObjectClass (void) cls, cls->nameForLogging()); } - objc_tag_classes[slot] = cls; -} - + *slot = cls; -// Deprecated name. -void _objc_insert_tagged_isa(unsigned char slotNumber, Class isa) -{ - return _objc_registerTaggedPointerClass((objc_tag_index_t)slotNumber, isa); + // Store a placeholder class in the basic tag slot that is + // reserved for the extended tag space, if it isn't set already. + // Do this lazily when the first extended tag is registered so + // that old debuggers characterize bogus pointers correctly more often. + if (tag < OBJC_TAG_First60BitPayload || tag > OBJC_TAG_Last60BitPayload) { + Class *extSlot = classSlotForBasicTagIndex(OBJC_TAG_RESERVED_7); + if (*extSlot == nil) { + extern objc_class OBJC_CLASS_$___NSUnrecognizedTaggedPointer; + *extSlot = (Class)&OBJC_CLASS_$___NSUnrecognizedTaggedPointer; + } + } } @@ -6582,8 +6453,9 @@ void _objc_insert_tagged_isa(unsigned char slotNumber, Class isa) Class _objc_getClassForTag(objc_tag_index_t tag) { - if ((unsigned int)tag >= TAG_COUNT) return nil; - return objc_tag_classes[tagSlotForTagIndex(tag)]; + Class *slot = classSlotForTagIndex(tag); + if (slot) return *slot; + else return nil; } #endif @@ -6623,11 +6495,7 @@ void _objc_insert_tagged_isa(unsigned char slotNumber, Class isa) { msg->sel = sel_registerName((const char *)msg->sel); - if (ignoreSelector(msg->sel)) { - // ignored selector - bypass dispatcher - msg->imp = (IMP)&_objc_ignored_method; - } - else if (msg->imp == &objc_msgSend_fixup) { + if (msg->imp == &objc_msgSend_fixup) { if (msg->sel == SEL_alloc) { msg->imp = (IMP)&objc_alloc; } else if (msg->sel == SEL_allocWithZone) { @@ -6688,6 +6556,7 @@ static Class setSuperclass(Class cls, Class newSuper) // Flush subclass's method caches. flushCaches(cls); + flushCaches(cls->ISA()); return oldSuper; } diff --git a/runtime/objc-runtime-old.h b/runtime/objc-runtime-old.h index 76cd8e4..5f0ff02 100644 --- a/runtime/objc-runtime-old.h +++ b/runtime/objc-runtime-old.h @@ -52,8 +52,8 @@ #define CLS_CONSTRUCTING 0x10000 // visibility=hidden #define CLS_HIDDEN 0x20000 -// GC: class has unsafe finalize method -#define CLS_FINALIZE_ON_MAIN_THREAD 0x40000 +// available for use; was CLS_FINALIZE_ON_MAIN_THREAD +#define CLS_40000 0x40000 // Lazy property list arrays #define CLS_NO_PROPERTY_ARRAY 0x80000 // +load implementation @@ -67,6 +67,10 @@ #define CLS_INSTANCES_HAVE_ASSOCIATED_OBJECTS 0x1000000 // class has instance-specific GC layout #define CLS_HAS_INSTANCE_SPECIFIC_LAYOUT 0x2000000 +// class compiled with ARC +#define CLS_IS_ARC 0x4000000 +// class is not ARC but has ARC-style weak ivar layout +#define CLS_HAS_WEAK_WITHOUT_ARC 0x8000000 // Terminator for array of method lists @@ -77,6 +81,105 @@ #define GETMETA(cls) (ISMETA(cls) ? (cls) : (cls)->ISA()) +struct old_class_ext { + uint32_t size; + const uint8_t *weak_ivar_layout; + struct old_property_list **propertyLists; +}; + +struct old_category { + char *category_name; + char *class_name; + struct old_method_list *instance_methods; + struct old_method_list *class_methods; + struct old_protocol_list *protocols; + // Fields below this point are in version 7 or later only. + uint32_t size; + struct old_property_list *instance_properties; + // Check size for fields below this point. + struct old_property_list *class_properties; + + bool hasClassPropertiesField() const { + return size >= offsetof(old_category, class_properties) + sizeof(class_properties); + } +}; + +struct old_ivar { + char *ivar_name; + char *ivar_type; + int ivar_offset; +#ifdef __LP64__ + int space; +#endif +}; + +struct old_ivar_list { + int ivar_count; +#ifdef __LP64__ + int space; +#endif + /* variable length structure */ + struct old_ivar ivar_list[1]; +}; + + +struct old_method { + SEL method_name; + char *method_types; + IMP method_imp; +}; + +struct old_method_list { + void *obsolete; + + int method_count; +#ifdef __LP64__ + int space; +#endif + /* variable length structure */ + struct old_method method_list[1]; +}; + +struct old_protocol { + Class isa; + const char *protocol_name; + struct old_protocol_list *protocol_list; + struct objc_method_description_list *instance_methods; + struct objc_method_description_list *class_methods; +}; + +struct old_protocol_list { + struct old_protocol_list *next; + long count; + struct old_protocol *list[1]; +}; + +struct old_protocol_ext { + uint32_t size; + struct objc_method_description_list *optional_instance_methods; + struct objc_method_description_list *optional_class_methods; + struct old_property_list *instance_properties; + const char **extendedMethodTypes; + struct old_property_list *class_properties; + + bool hasClassPropertiesField() const { + return size >= offsetof(old_protocol_ext, class_properties) + sizeof(class_properties); + } +}; + + +struct old_property { + const char *name; + const char *attributes; +}; + +struct old_property_list { + uint32_t entsize; + uint32_t count; + struct old_property first; +}; + + struct objc_class : objc_object { Class superclass; const char *name; @@ -120,6 +223,17 @@ struct objc_class : objc_object { return hasCxxCtor(); // one bit for both ctor and dtor } + // Return YES if the class's ivars are managed by ARC, + // or the class is MRC but has ARC-style weak ivars. + bool hasAutomaticIvars() { + return info & (CLS_IS_ARC | CLS_HAS_WEAK_WITHOUT_ARC); + } + + // Return YES if the class's ivars are managed by ARC. + bool isARC() { + return info & CLS_IS_ARC; + } + bool hasCustomRR() { return true; } @@ -151,14 +265,6 @@ struct objc_class : objc_object { else clearInfo(CLS_GROW_CACHE); } - bool shouldFinalizeOnMainThread() { - return info & CLS_FINALIZE_ON_MAIN_THREAD; - } - - void setShouldFinalizeOnMainThread() { - setInfo(CLS_FINALIZE_ON_MAIN_THREAD); - } - // +initialize bits are stored on the metaclass only bool isInitializing() { return getMeta()->info & CLS_INITIALIZING; @@ -205,6 +311,25 @@ struct objc_class : objc_object { else return this->ISA(); } + // May be unaligned depending on class's ivars. + uint32_t unalignedInstanceStart() { + // This is not simply superclass->instance_size. + // superclass->instance_size is padded to its sizeof() boundary, + // which may envelop one of this class's ivars. + // That in turn would break ARC-style ivar layouts. + // Instead, we use the address of this class's first ivar when possible. + if (!superclass) return 0; + if (!ivars || ivars->ivar_count == 0) return superclass->instance_size; + return ivars->ivar_list[0].ivar_offset; + } + + // Class's instance start rounded up to a pointer-size boundary. + // This is used for ARC layout bitmaps. + uint32_t alignedInstanceStart() { + return word_align(unalignedInstanceStart()); + } + + // May be unaligned depending on class's ivars. uint32_t unalignedInstanceSize() { return instance_size; @@ -212,7 +337,7 @@ struct objc_class : objc_object { // Class's ivar size rounded up to a pointer-size boundary. uint32_t alignedInstanceSize() { - return (unalignedInstanceSize() + WORD_MASK) & ~WORD_MASK; + return word_align(unalignedInstanceSize()); } size_t instanceSize(size_t extraBytes) { @@ -224,92 +349,6 @@ struct objc_class : objc_object { }; -struct old_class_ext { - uint32_t size; - const uint8_t *weak_ivar_layout; - struct old_property_list **propertyLists; -}; - -struct old_category { - char *category_name; - char *class_name; - struct old_method_list *instance_methods; - struct old_method_list *class_methods; - struct old_protocol_list *protocols; - uint32_t size; - struct old_property_list *instance_properties; -}; - -struct old_ivar { - char *ivar_name; - char *ivar_type; - int ivar_offset; -#ifdef __LP64__ - int space; -#endif -}; - -struct old_ivar_list { - int ivar_count; -#ifdef __LP64__ - int space; -#endif - /* variable length structure */ - struct old_ivar ivar_list[1]; -}; - - -struct old_method { - SEL method_name; - char *method_types; - IMP method_imp; -}; - -struct old_method_list { - void *obsolete; - - int method_count; -#ifdef __LP64__ - int space; -#endif - /* variable length structure */ - struct old_method method_list[1]; -}; - -struct old_protocol { - Class isa; - const char *protocol_name; - struct old_protocol_list *protocol_list; - struct objc_method_description_list *instance_methods; - struct objc_method_description_list *class_methods; -}; - -struct old_protocol_list { - struct old_protocol_list *next; - long count; - struct old_protocol *list[1]; -}; - -struct old_protocol_ext { - uint32_t size; - struct objc_method_description_list *optional_instance_methods; - struct objc_method_description_list *optional_class_methods; - struct old_property_list *instance_properties; - const char **extendedMethodTypes; -}; - - -struct old_property { - const char *name; - const char *attributes; -}; - -struct old_property_list { - uint32_t entsize; - uint32_t count; - struct old_property first; -}; - #include "hashtable2.h" diff --git a/runtime/objc-runtime-old.mm b/runtime/objc-runtime-old.mm index 045db63..d6b393e 100644 --- a/runtime/objc-runtime-old.mm +++ b/runtime/objc-runtime-old.mm @@ -861,67 +861,6 @@ static void really_connect_class(Class cls, // Connect superclass pointers. set_superclass(cls, supercls, YES); - // Update GC layouts - // For paranoia, this is a conservative update: - // only non-strong -> strong and weak -> strong are corrected. - if (UseGC && supercls && - (cls->info & CLS_EXT) && (supercls->info & CLS_EXT)) - { - bool layoutChanged; - layout_bitmap ivarBitmap = - layout_bitmap_create(cls->ivar_layout, - cls->instance_size, - cls->instance_size, NO); - - layout_bitmap superBitmap = - layout_bitmap_create(supercls->ivar_layout, - supercls->instance_size, - supercls->instance_size, NO); - - // non-strong -> strong: bits set in super should be set in sub - layoutChanged = layout_bitmap_or(ivarBitmap, superBitmap, cls->name); - layout_bitmap_free(superBitmap); - - if (layoutChanged) { - layout_bitmap weakBitmap = {}; - bool weakLayoutChanged = NO; - - if (cls->ext && cls->ext->weak_ivar_layout) { - // weak -> strong: strong bits should be cleared in weak layout - // This is a subset of non-strong -> strong - weakBitmap = - layout_bitmap_create(cls->ext->weak_ivar_layout, - cls->instance_size, - cls->instance_size, YES); - - weakLayoutChanged = - layout_bitmap_clear(weakBitmap, ivarBitmap, cls->name); - } else { - // no existing weak ivars, so no weak -> strong changes - } - - // Rebuild layout strings. - if (PrintIvars) { - _objc_inform("IVARS: gc layout changed " - "for class %s (super %s)", - cls->name, supercls->name); - if (weakLayoutChanged) { - _objc_inform("IVARS: gc weak layout changed " - "for class %s (super %s)", - cls->name, supercls->name); - } - } - cls->ivar_layout = layout_string_create(ivarBitmap); - if (weakLayoutChanged) { - cls->ext->weak_ivar_layout = layout_string_create(weakBitmap); - } - - layout_bitmap_free(weakBitmap); - } - - layout_bitmap_free(ivarBitmap); - } - // Done! cls->info |= CLS_CONNECTED; @@ -931,7 +870,6 @@ static void really_connect_class(Class cls, // Update hash tables. NXHashRemove(unconnected_class_hash, cls); oldCls = (Class)NXHashInsert(class_hash, cls); - objc_addRegisteredClass(cls); // Delete unconnected_class_hash if it is now empty. if (NXCountHashTable(unconnected_class_hash) == 0) { @@ -950,16 +888,6 @@ static void really_connect_class(Class cls, // Connect newly-connectable subclasses resolve_subclasses_of_class(cls); - // GC debugging: make sure all classes with -dealloc also have -finalize - if (DebugFinalizers) { - extern IMP findIMPInClass(Class cls, SEL sel); - if (findIMPInClass(cls, sel_getUid("dealloc")) && - ! findIMPInClass(cls, sel_getUid("finalize"))) - { - _objc_inform("GC: class '%s' implements -dealloc but not -finalize", cls->name); - } - } - // Debugging: if this class has ivars, make sure this class's ivars don't // overlap with its super's. This catches some broken fragile base classes. // Do not use super->instance_size vs. self->ivar[0] to check this. @@ -1080,12 +1008,11 @@ static bool _objc_read_categories_from_image (header_info * hi) size_t midx; bool needFlush = NO; - if (_objcHeaderIsReplacement(hi)) { + if (hi->info()->isReplacement()) { // Ignore any categories in this image return NO; } - // Major loop - process all modules in the header mods = hi->mod_ptr; @@ -1133,7 +1060,7 @@ static void _objc_read_classes_from_image(header_info *hi) Module mods; int isBundle = headerIsBundle(hi); - if (_objcHeaderIsReplacement(hi)) { + if (hi->info()->isReplacement()) { // Ignore any classes in this image return; } @@ -1144,7 +1071,7 @@ static void _objc_read_classes_from_image(header_info *hi) // to add lots of classes. { mutex_locker_t lock(classLock); - if (hi->mhdr != libobjc_header && _NXHashCapacity(class_hash) < 1024) { + if (hi->mhdr() != libobjc_header && _NXHashCapacity(class_hash) < 1024) { _NXHashRehashToCapacity(class_hash, 1024); } } @@ -1258,7 +1185,7 @@ static void _objc_connect_classes_from_image(header_info *hi) unsigned int index; unsigned int midx; Module mods; - bool replacement = _objcHeaderIsReplacement(hi); + bool replacement = hi->info()->isReplacement(); // Major loop - process all modules in the image mods = hi->mod_ptr; @@ -1627,8 +1554,9 @@ static void map_method_descs (struct objc_method_description_list * methods, bo } -objc_property_t protocol_getProperty(Protocol *p, const char *name, - BOOL isRequiredProperty, BOOL isInstanceProperty) +objc_property_t +protocol_getProperty(Protocol *p, const char *name, + BOOL isRequiredProperty, BOOL isInstanceProperty) { old_protocol *proto = oldprotocol(p); old_protocol_ext *ext; @@ -1636,14 +1564,18 @@ objc_property_t protocol_getProperty(Protocol *p, const char *name, if (!proto || !name) return nil; - if (!isRequiredProperty || !isInstanceProperty) { - // Only required instance properties are currently supported + if (!isRequiredProperty) { + // Only required properties are currently supported return nil; } if ((ext = ext_for_protocol(proto))) { old_property_list *plist; - if ((plist = ext->instance_properties)) { + if (isInstanceProperty) plist = ext->instance_properties; + else if (ext->hasClassPropertiesField()) plist = ext->class_properties; + else plist = nil; + + if (plist) { uint32_t i; for (i = 0; i < plist->count; i++) { old_property *prop = property_list_nth(plist, i); @@ -1668,24 +1600,35 @@ objc_property_t protocol_getProperty(Protocol *p, const char *name, } -objc_property_t *protocol_copyPropertyList(Protocol *p, unsigned int *outCount) +objc_property_t * +protocol_copyPropertyList2(Protocol *p, unsigned int *outCount, + BOOL isRequiredProperty, BOOL isInstanceProperty) { old_property **result = nil; old_protocol_ext *ext; old_property_list *plist; old_protocol *proto = oldprotocol(p); - if (! (ext = ext_for_protocol(proto))) { + if (! (ext = ext_for_protocol(proto)) || !isRequiredProperty) { + // Only required properties are currently supported. if (outCount) *outCount = 0; return nil; } - plist = ext->instance_properties; + if (isInstanceProperty) plist = ext->instance_properties; + else if (ext->hasClassPropertiesField()) plist = ext->class_properties; + else plist = nil; + result = copyPropertyList(plist, outCount); return (objc_property_t *)result; } +objc_property_t *protocol_copyPropertyList(Protocol *p, unsigned int *outCount) +{ + return protocol_copyPropertyList2(p, outCount, YES, YES); +} + /*********************************************************************** * protocol_copyProtocolList @@ -1841,6 +1784,7 @@ BOOL protocol_isEqual(Protocol *self, Protocol *other) objc_allocateProtocol(const char *name) { Class cls = objc_getClass("__IncompleteProtocol"); + assert(cls); mutex_locker_t lock(classLock); @@ -2048,11 +1992,13 @@ void objc_registerProtocol(Protocol *proto_gen) if (isRequiredProperty && isInstanceProperty) { _protocol_addProperty(&ext->instance_properties, name, attrs, count); } - //else if (isRequiredProperty && !isInstanceProperty) { - // _protocol_addProperty(&ext->class_properties, name, attrs, count); - //} else if (!isRequiredProperty && isInstanceProperty) { + else if (isRequiredProperty && !isInstanceProperty) { + _protocol_addProperty(&ext->class_properties, name, attrs, count); + } + // else if (!isRequiredProperty && isInstanceProperty) { // _protocol_addProperty(&ext->optional_instance_properties, name, attrs, count); - //} else /* !isRequiredProperty && !isInstanceProperty) */ { + //} + // else /* !isRequiredProperty && !isInstanceProperty) */ { // _protocol_addProperty(&ext->optional_class_properties, name, attrs, count); //} } @@ -2163,19 +2109,15 @@ static void _objc_fixup_selector_refs (const header_info *hi) SEL *sels; bool preoptimized = hi->isPreoptimized(); -# if SUPPORT_IGNORED_SELECTOR_CONSTANT - // shared cache can't fix constant ignored selectors - if (UseGC) preoptimized = NO; -# endif if (PrintPreopt) { if (preoptimized) { _objc_inform("PREOPTIMIZATION: honoring preoptimized selectors in %s", - hi->fname); + hi->fname()); } - else if (_objcHeaderOptimizedByDyld(hi)) { + else if (hi->info()->optimizedByDyld()) { _objc_inform("PREOPTIMIZATION: IGNORING preoptimized selectors in %s", - hi->fname); + hi->fname()); } } @@ -2202,7 +2144,7 @@ static inline bool _is_threaded() { * dyld_priv.h says even for 64-bit. **********************************************************************/ void -unmap_image(const struct mach_header *mh, intptr_t vmaddr_slide) +unmap_image(const char *path __unused, const struct mach_header *mh) { recursive_mutex_locker_t lock(loadMethodLock); unmap_image_nolock(mh); @@ -2214,39 +2156,33 @@ static inline bool _is_threaded() { * Process the given images which are being mapped in by dyld. * Calls ABI-agnostic code after taking ABI-specific locks. **********************************************************************/ -const char * -map_2_images(enum dyld_image_states state, uint32_t infoCount, - const struct dyld_image_info infoList[]) +void +map_2_images(unsigned count, const char * const paths[], + const struct mach_header * const mhdrs[]) { recursive_mutex_locker_t lock(loadMethodLock); - return map_images_nolock(state, infoCount, infoList); + map_images_nolock(count, paths, mhdrs); } /*********************************************************************** * load_images * Process +load in the given images which are being mapped in by dyld. -* Calls ABI-agnostic code after taking ABI-specific locks. * * Locking: acquires classLock and loadMethodLock **********************************************************************/ -const char * -load_images(enum dyld_image_states state, uint32_t infoCount, - const struct dyld_image_info infoList[]) -{ - bool found; +extern void prepare_load_methods(const headerType *mhdr); +void +load_images(const char *path __unused, const struct mach_header *mh) +{ recursive_mutex_locker_t lock(loadMethodLock); // Discover +load methods - found = load_images_nolock(state, infoCount, infoList); + prepare_load_methods((const headerType *)mh); // Call +load methods (without classLock - re-entrant) - if (found) { - call_load_methods(); - } - - return nil; + call_load_methods(); } #endif @@ -2255,7 +2191,7 @@ static inline bool _is_threaded() { * _read_images * Perform metadata processing for hCount images starting with firstNewHeader **********************************************************************/ -void _read_images(header_info **hList, uint32_t hCount) +void _read_images(header_info **hList, uint32_t hCount, int totalClasses, int unoptimizedTotalClass) { uint32_t i; bool categoriesLoaded = NO; @@ -2323,23 +2259,18 @@ static void schedule_class_load(Class cls) cls->info |= CLS_LOADED; } -bool hasLoadMethods(const headerType *mhdr) -{ - return true; -} - void prepare_load_methods(const headerType *mhdr) { Module mods; unsigned int midx; header_info *hi; - for (hi = FirstHeader; hi; hi = hi->next) { - if (mhdr == hi->mhdr) break; + for (hi = FirstHeader; hi; hi = hi->getNext()) { + if (mhdr == hi->mhdr()) break; } if (!hi) return; - if (_objcHeaderIsReplacement(hi)) { + if (hi->info()->isReplacement()) { // Ignore any classes in this image return; } @@ -2600,7 +2531,6 @@ static void _objc_remove_classes_in_image(header_info *hi) // Remove from class_hash NXHashRemove(class_hash, cls); - objc_removeRegisteredClass(cls); // Free heap memory pointed to by the class unload_class(cls->ISA()); @@ -2615,10 +2545,10 @@ static void _objc_remove_classes_in_image(header_info *hi) // Get the location of the dying image's __OBJC segment uintptr_t seg; unsigned long seg_size; - seg = (uintptr_t)getsegmentdata(hi->mhdr, "__OBJC", &seg_size); + seg = (uintptr_t)getsegmentdata(hi->mhdr(), "__OBJC", &seg_size); header_info *other_hi; - for (other_hi = FirstHeader; other_hi != nil; other_hi = other_hi->next) { + for (other_hi = FirstHeader; other_hi != nil; other_hi = other_hi->getNext()) { Class *other_refs; size_t count; if (other_hi == hi) continue; // skip the image being unloaded @@ -2691,10 +2621,10 @@ static void unload_paranoia(header_info *hi) // Get the location of the dying image's __OBJC segment uintptr_t seg; unsigned long seg_size; - seg = (uintptr_t)getsegmentdata(hi->mhdr, "__OBJC", &seg_size); + seg = (uintptr_t)getsegmentdata(hi->mhdr(), "__OBJC", &seg_size); _objc_inform("UNLOAD DEBUG: unloading image '%s' [%p..%p]", - hi->fname, (void *)seg, (void*)(seg+seg_size)); + hi->fname(), (void *)seg, (void*)(seg+seg_size)); mutex_locker_t lock(classLock); @@ -2772,7 +2702,8 @@ void _unload_image(header_info *hi) _objc_remove_classes_in_image(hi); _objc_remove_categories_in_image(hi); _objc_remove_pending_class_refs_in_image(hi); - + if (hi->proto_refs) try_free(hi->proto_refs); + // Perform various debugging checks if requested. if (DebugUnload) unload_paranoia(hi); } @@ -2814,7 +2745,6 @@ void objc_addClass (Class cls) // Add the class to the table (void) NXHashInsert (class_hash, cls); - objc_addRegisteredClass(cls); // Superclass is no longer a leaf for cache flushing if (cls->superclass && (cls->superclass->info & CLS_LEAF)) { @@ -3016,12 +2946,24 @@ static inline void _objc_add_category(Class cls, old_category *category, int ver } } - // Augment properties + // Augment instance properties if (version >= 7 && category->instance_properties) { if (cls->ISA()->version >= 6) { _class_addProperties(cls, category->instance_properties); } else { - _objc_inform ("unable to add properties from category %s...\n", category->category_name); + _objc_inform ("unable to add instance properties from category %s...\n", category->category_name); + _objc_inform ("class `%s' must be recompiled\n", category->class_name); + } + } + + // Augment class properties + if (version >= 7 && category->hasClassPropertiesField() && + category->class_properties) + { + if (cls->ISA()->version >= 6) { + _class_addProperties(cls->ISA(), category->class_properties); + } else { + _objc_inform ("unable to add class properties from category %s...\n", category->category_name); _objc_inform ("class `%s' must be recompiled\n", category->class_name); } } diff --git a/runtime/objc-runtime.mm b/runtime/objc-runtime.mm index b7a25a5..e9efe55 100644 --- a/runtime/objc-runtime.mm +++ b/runtime/objc-runtime.mm @@ -82,7 +82,6 @@ SEL SEL_dealloc = NULL; SEL SEL_copy = NULL; SEL SEL_new = NULL; -SEL SEL_finalize = NULL; SEL SEL_forwardInvocation = NULL; SEL SEL_tryRetain = NULL; SEL SEL_isDeallocating = NULL; @@ -169,7 +168,7 @@ void appendHeader(header_info *hi) // Add the header to the header list. // The header is appended to the list, to preserve the bottom-up order. HeaderCount++; - hi->next = NULL; + hi->setNext(NULL); if (!FirstHeader) { // list is empty FirstHeader = LastHeader = hi; @@ -177,10 +176,10 @@ void appendHeader(header_info *hi) if (!LastHeader) { // list is not empty, but LastHeader is invalid - recompute it LastHeader = FirstHeader; - while (LastHeader->next) LastHeader = LastHeader->next; + while (LastHeader->getNext()) LastHeader = LastHeader->getNext(); } // LastHeader is now valid - LastHeader->next = hi; + LastHeader->setNext(hi); LastHeader = hi; } } @@ -195,14 +194,18 @@ void appendHeader(header_info *hi) **********************************************************************/ void removeHeader(header_info *hi) { - header_info **hiP; - - for (hiP = &FirstHeader; *hiP != NULL; hiP = &(**hiP).next) { - if (*hiP == hi) { - header_info *deadHead = *hiP; - - // Remove from the linked list (updating FirstHeader if necessary). - *hiP = (**hiP).next; + header_info *prev = NULL; + header_info *current = NULL; + + for (current = FirstHeader; current != NULL; current = current->getNext()) { + if (current == hi) { + header_info *deadHead = current; + + // Remove from the linked list. + if (prev) + prev->setNext(current->getNext()); + else + FirstHeader = current->getNext(); // no prev so removing head // Update LastHeader if necessary. if (LastHeader == deadHead) { @@ -212,6 +215,7 @@ void removeHeader(header_info *hi) HeaderCount--; break; } + prev = current; } } @@ -518,14 +522,15 @@ void objc_setForwardHandler(void *fwd, void *fwd_stret) const char **names = (const char **)calloc(max+1, sizeof(char *)); #endif - for (hi = FirstHeader; hi != NULL && count < max; hi = hi->next) { + for (hi = FirstHeader; hi != NULL && count < max; hi = hi->getNext()) { #if TARGET_OS_WIN32 if (hi->moduleName) { names[count++] = hi->moduleName; } #else - if (hi->fname) { - names[count++] = hi->fname; + const char *fname = hi->fname(); + if (fname) { + names[count++] = fname; } #endif } @@ -556,11 +561,11 @@ void objc_setForwardHandler(void *fwd, void *fwd_stret) } // Find the image. - for (hi = FirstHeader; hi != NULL; hi = hi->next) { + for (hi = FirstHeader; hi != NULL; hi = hi->getNext()) { #if TARGET_OS_WIN32 if (0 == wcscmp((TCHAR *)image, hi->moduleName)) break; #else - if (0 == strcmp(image, hi->fname)) break; + if (0 == strcmp(image, hi->fname())) break; #endif } @@ -604,67 +609,425 @@ void objc_setEnumerationMutationHandler(void (*handler)(id)) { * Associative Reference Support **********************************************************************/ -id objc_getAssociatedObject_non_gc(id object, const void *key) { +id objc_getAssociatedObject(id object, const void *key) { return _object_get_associative_reference(object, (void *)key); } -void objc_setAssociatedObject_non_gc(id object, const void *key, id value, objc_AssociationPolicy policy) { +void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy) { _object_set_associative_reference(object, (void *)key, value, policy); } -#if SUPPORT_GC +void objc_removeAssociatedObjects(id object) +{ + if (object && object->hasAssociatedObjects()) { + _object_remove_assocations(object); + } +} -id objc_getAssociatedObject_gc(id object, const void *key) { - // auto_zone doesn't handle tagged pointer objects. Track it ourselves. - if (object->isTaggedPointer()) return objc_getAssociatedObject_non_gc(object, key); - return (id)auto_zone_get_associative_ref(gc_zone, object, (void *)key); -} -void objc_setAssociatedObject_gc(id object, const void *key, id value, objc_AssociationPolicy policy) { - // auto_zone doesn't handle tagged pointer objects. Track it ourselves. - if (object->isTaggedPointer()) return objc_setAssociatedObject_non_gc(object, key, value, policy); +#if SUPPORT_GC_COMPAT + +#include + +// GC preflight for an app executable. + +enum GCness { + WithGC = 1, + WithoutGC = 0, + Error = -1 +}; + +// Overloaded template wrappers around clang's overflow-checked arithmetic. + +template bool uadd_overflow(T x, T y, T* sum); +template bool usub_overflow(T x, T y, T* diff); +template bool umul_overflow(T x, T y, T* prod); + +template bool sadd_overflow(T x, T y, T* sum); +template bool ssub_overflow(T x, T y, T* diff); +template bool smul_overflow(T x, T y, T* prod); + +template <> bool uadd_overflow(unsigned x, unsigned y, unsigned* sum) { return __builtin_uadd_overflow(x, y, sum); } +template <> bool uadd_overflow(unsigned long x, unsigned long y, unsigned long* sum) { return __builtin_uaddl_overflow(x, y, sum); } +template <> bool uadd_overflow(unsigned long long x, unsigned long long y, unsigned long long* sum) { return __builtin_uaddll_overflow(x, y, sum); } + +template <> bool usub_overflow(unsigned x, unsigned y, unsigned* diff) { return __builtin_usub_overflow(x, y, diff); } +template <> bool usub_overflow(unsigned long x, unsigned long y, unsigned long* diff) { return __builtin_usubl_overflow(x, y, diff); } +template <> bool usub_overflow(unsigned long long x, unsigned long long y, unsigned long long* diff) { return __builtin_usubll_overflow(x, y, diff); } + +template <> bool umul_overflow(unsigned x, unsigned y, unsigned* prod) { return __builtin_umul_overflow(x, y, prod); } +template <> bool umul_overflow(unsigned long x, unsigned long y, unsigned long* prod) { return __builtin_umull_overflow(x, y, prod); } +template <> bool umul_overflow(unsigned long long x, unsigned long long y, unsigned long long* prod) { return __builtin_umulll_overflow(x, y, prod); } + +template <> bool sadd_overflow(signed x, signed y, signed* sum) { return __builtin_sadd_overflow(x, y, sum); } +template <> bool sadd_overflow(signed long x, signed long y, signed long* sum) { return __builtin_saddl_overflow(x, y, sum); } +template <> bool sadd_overflow(signed long long x, signed long long y, signed long long* sum) { return __builtin_saddll_overflow(x, y, sum); } + +template <> bool ssub_overflow(signed x, signed y, signed* diff) { return __builtin_ssub_overflow(x, y, diff); } +template <> bool ssub_overflow(signed long x, signed long y, signed long* diff) { return __builtin_ssubl_overflow(x, y, diff); } +template <> bool ssub_overflow(signed long long x, signed long long y, signed long long* diff) { return __builtin_ssubll_overflow(x, y, diff); } + +template <> bool smul_overflow(signed x, signed y, signed* prod) { return __builtin_smul_overflow(x, y, prod); } +template <> bool smul_overflow(signed long x, signed long y, signed long* prod) { return __builtin_smull_overflow(x, y, prod); } +template <> bool smul_overflow(signed long long x, signed long long y, signed long long* prod) { return __builtin_smulll_overflow(x, y, prod); } + + +// Range-checking subview of a file. +class FileSlice { + int fd; + uint64_t sliceOffset; + uint64_t sliceSize; + +public: + FileSlice() : fd(-1), sliceOffset(0), sliceSize(0) { } + + FileSlice(int newfd, uint64_t newOffset, uint64_t newSize) + : fd(newfd) , sliceOffset(newOffset) , sliceSize(newSize) { } + + // Read bytes from this slice. + // Returns YES if all bytes were read successfully. + bool pread(void *buf, uint64_t readSize, uint64_t readOffset = 0) { + uint64_t readEnd; + if (uadd_overflow(readOffset, readSize, &readEnd)) return NO; + if (readEnd > sliceSize) return NO; - if ((policy & OBJC_ASSOCIATION_COPY_NONATOMIC) == OBJC_ASSOCIATION_COPY_NONATOMIC) { - value = ((id(*)(id, SEL))objc_msgSend)(value, SEL_copy); + uint64_t preadOffset; + if (uadd_overflow(sliceOffset, readOffset, &preadOffset)) return NO; + + int64_t readed = ::pread(fd, buf, (size_t)readSize, preadOffset); + if (readed < 0 || (uint64_t)readed != readSize) return NO; + return YES; } - auto_zone_set_associative_ref(gc_zone, object, (void *)key, value); -} -// objc_setAssociatedObject and objc_getAssociatedObject are -// resolver functions in objc-auto.mm. + // Create a new slice that is a subset of this slice. + // Returnes YES if successful. + bool slice(uint64_t newOffset, uint64_t newSize, FileSlice& result) { + // fixme arithmetic overflow + uint64_t newEnd; + if (uadd_overflow(newOffset, newSize, &newEnd)) return NO; + if (newEnd > sliceSize) return NO; -#else + if (uadd_overflow(sliceOffset, newOffset, &result.sliceOffset)) { + return NO; + } + result.sliceSize = newSize; + result.fd = fd; + return YES; + } + + // Shorten this slice in place by removing a range from the start. + bool advance(uint64_t distance) { + if (distance > sliceSize) return NO; + if (uadd_overflow(sliceOffset, distance, &sliceOffset)) return NO; + if (usub_overflow(sliceSize, distance, &sliceSize)) return NO; + return YES; + } +}; + + +// Arch32 and Arch64 are used to specialize sliceRequiresGC() +// to interrogate old-ABI i386 and new-ABI x86_64 files. + +struct Arch32 { + using mh_t = struct mach_header; + using segment_command_t = struct segment_command; + using section_t = struct section; + + enum : cpu_type_t { cputype = CPU_TYPE_X86 }; + enum : int { segment_cmd = LC_SEGMENT }; + + static bool isObjCSegment(const char *segname) { + return segnameEquals(segname, "__OBJC"); + } + + static bool isImageInfoSection(const char *sectname) { + return sectnameEquals(sectname, "__image_info"); + } + + static bool countClasses(FileSlice file, section_t& sect, + int& classCount, int& classrefCount) + { + if (sectnameEquals(sect.sectname, "__cls_refs")) { + classrefCount += sect.size / 4; + } + else if (sectnameEquals(sect.sectname, "__module_info")) { + struct module_t { + uint32_t version; + uint32_t size; + uint32_t name; // not bound + uint32_t symtab; // not bound + }; + size_t mod_count = sect.size / sizeof(module_t); + if (mod_count == 0) { + // no classes defined + } else if (mod_count > 1) { + // AppleScriptObjC apps only have one module. + // Disqualify this app by setting classCount to non-zero. + // We don't actually need an accurate count. + classCount = 1; + } else if (mod_count == 1) { + FileSlice moduleSlice; + if (!file.slice(sect.offset, sect.size, moduleSlice)) return NO; + module_t module; + if (!moduleSlice.pread(&module, sizeof(module))) return NO; + if (module.symtab) { + // AppleScriptObjC apps only have a module with no symtab. + // Disqualify this app by setting classCount to non-zero. + // We don't actually need an accurate count. + classCount = 1; + } + } + + } + return YES; + } + +}; -id -objc_getAssociatedObject(id object, const void *key) +struct Arch64 { + using mh_t = struct mach_header_64; + using segment_command_t = struct segment_command_64; + using section_t = struct section_64; + + enum : cpu_type_t { cputype = CPU_TYPE_X86_64 }; + enum : int { segment_cmd = LC_SEGMENT_64 }; + + static bool isObjCSegment(const char *segname) { + return + segnameEquals(segname, "__DATA") || + segnameEquals(segname, "__DATA_CONST") || + segnameEquals(segname, "__DATA_DIRTY"); + } + + static bool isImageInfoSection(const char *sectname) { + return sectnameEquals(sectname, "__objc_imageinfo"); + } + + static bool countClasses(FileSlice, section_t& sect, + int& classCount, int& classrefCount) + { + if (sectnameEquals(sect.sectname, "__objc_classlist")) { + classCount += sect.size / 8; + } + else if (sectnameEquals(sect.sectname, "__objc_classrefs")) { + classrefCount += sect.size / 8; + } + return YES; + } +}; + + +#define SANE_HEADER_SIZE (32*1024) + +template +static int sliceRequiresGC(typename Arch::mh_t mh, FileSlice file) { - return objc_getAssociatedObject_non_gc(object, key); + // We assume there is only one arch per pointer size that can support GC. + // (i386 and x86_64) + if (mh.cputype != Arch::cputype) return 0; + + // We only check the main executable. + if (mh.filetype != MH_EXECUTE) return 0; + + // Look for ObjC segment. + // Look for AppleScriptObjC linkage. + FileSlice cmds; + if (!file.slice(sizeof(mh), mh.sizeofcmds, cmds)) return Error; + + // Exception: Some AppleScriptObjC apps built for GC can run without GC. + // 1. executable defines no classes + // 2. executable references NSBundle only + // 3. executable links to AppleScriptObjC.framework + // Note that shouldRejectGCApp() also knows about this. + bool wantsGC = NO; + bool linksToAppleScriptObjC = NO; + int classCount = 0; + int classrefCount = 0; + + // Disallow abusively-large executables that could hang this checker. + // dyld performs similar checks (MAX_MACH_O_HEADER_AND_LOAD_COMMANDS_SIZE) + if (mh.sizeofcmds > SANE_HEADER_SIZE) return Error; + if (mh.ncmds > mh.sizeofcmds / sizeof(struct load_command)) return Error; + + for (uint32_t cmdindex = 0; cmdindex < mh.ncmds; cmdindex++) { + struct load_command lc; + if (!cmds.pread(&lc, sizeof(lc))) return Error; + + // Disallow abusively-small load commands that could hang this checker. + // dyld performs a similar check. + if (lc.cmdsize < sizeof(lc)) return Error; + + if (lc.cmd == LC_LOAD_DYLIB || lc.cmd == LC_LOAD_UPWARD_DYLIB || + lc.cmd == LC_LOAD_WEAK_DYLIB || lc.cmd == LC_REEXPORT_DYLIB) + { + // Look for AppleScriptObjC linkage. + FileSlice dylibSlice; + if (!cmds.slice(0, lc.cmdsize, dylibSlice)) return Error; + struct dylib_command dylib; + if (!dylibSlice.pread(&dylib, sizeof(dylib))) return Error; + + const char *asoFramework = + "/System/Library/Frameworks/AppleScriptObjC.framework" + "/Versions/A/AppleScriptObjC"; + size_t asoLen = strlen(asoFramework); + + FileSlice nameSlice; + if (dylibSlice.slice(dylib.dylib.name.offset, asoLen, nameSlice)) { + char name[asoLen]; + if (!nameSlice.pread(name, asoLen)) return Error; + if (0 == memcmp(name, asoFramework, asoLen)) { + linksToAppleScriptObjC = YES; + } + } + } + else if (lc.cmd == Arch::segment_cmd) { + typename Arch::segment_command_t seg; + if (!cmds.pread(&seg, sizeof(seg))) return Error; + + if (Arch::isObjCSegment(seg.segname)) { + // ObjC segment. + // Look for image info section. + // Look for class implementations and class references. + FileSlice sections; + if (!cmds.slice(0, seg.cmdsize, sections)) return Error; + if (!sections.advance(sizeof(seg))) return Error; + + for (uint32_t segindex = 0; segindex < seg.nsects; segindex++) { + typename Arch::section_t sect; + if (!sections.pread(§, sizeof(sect))) return Error; + if (!Arch::isObjCSegment(sect.segname)) return Error; + + if (!Arch::countClasses(file, sect, + classCount, classrefCount)) + { + return Error; + } + + if ((sect.flags & SECTION_TYPE) == S_REGULAR && + Arch::isImageInfoSection(sect.sectname)) + { + // ObjC image info section. + // Check its contents. + FileSlice section; + if (!file.slice(sect.offset, sect.size, section)) { + return Error; + } + // The subset of objc_image_info that was in use for GC. + struct { + uint32_t version; + uint32_t flags; + } ii; + if (!section.pread(&ii, sizeof(ii))) return Error; + if (ii.flags & (1<<1)) { + // App wants GC. + // Don't return yet because we need to + // check the AppleScriptObjC exception. + wantsGC = YES; + } + } + + if (!sections.advance(sizeof(sect))) return Error; + } + } + } + + if (!cmds.advance(lc.cmdsize)) return Error; + } + + if (!wantsGC) { + // No GC bit set. + return WithoutGC; + } + else if (linksToAppleScriptObjC && classCount == 0 && classrefCount == 1) { + // Has GC bit but falls under the AppleScriptObjC exception. + return WithoutGC; + } + else { + // Has GC bit and is not AppleScriptObjC. + return WithGC; + } } -void -objc_setAssociatedObject(id object, const void *key, id value, - objc_AssociationPolicy policy) + +static int sliceRequiresGC(FileSlice file) { - objc_setAssociatedObject_non_gc(object, key, value, policy); + // Read mach-o header. + struct mach_header_64 mh; + if (!file.pread(&mh, sizeof(mh))) return Error; + + // Check header magic. We assume only host-endian slices can support GC. + switch (mh.magic) { + case MH_MAGIC: + return sliceRequiresGC(*(struct mach_header *)&mh, file); + case MH_MAGIC_64: + return sliceRequiresGC(mh, file); + default: + return WithoutGC; + } } -#endif - -void objc_removeAssociatedObjects(id object) +// Returns 1 if any slice requires GC. +// Returns 0 if no slice requires GC. +// Returns -1 on any I/O or file format error. +int objc_appRequiresGC(int fd) { -#if SUPPORT_GC - if (UseGC) { - auto_zone_erase_associative_refs(gc_zone, object); - } else -#endif - { - if (object && object->hasAssociatedObjects()) { - _object_remove_assocations(object); + struct stat st; + if (fstat(fd, &st) < 0) return Error; + + FileSlice file(fd, 0, st.st_size); + + // Read fat header, if any. + struct fat_header fh; + + if (! file.pread(&fh, sizeof(fh))) return Error; + + int result; + + if (OSSwapBigToHostInt32(fh.magic) == FAT_MAGIC) { + // Fat header. + + size_t nfat_arch = OSSwapBigToHostInt32(fh.nfat_arch); + // Disallow abusively-large files that could hang this checker. + if (nfat_arch > SANE_HEADER_SIZE/sizeof(struct fat_arch)) return Error; + + size_t fat_size; + if (umul_overflow(nfat_arch, sizeof(struct fat_arch), &fat_size)) { + return Error; + } + + FileSlice archlist; + if (!file.slice(sizeof(fh), fat_size, archlist)) return Error; + + result = WithoutGC; + for (size_t i = 0; i < nfat_arch; i++) { + struct fat_arch fa; + if (!archlist.pread(&fa, sizeof(fa))) return Error; + if (!archlist.advance(sizeof(fa))) return Error; + + FileSlice thin; + if (!file.slice(OSSwapBigToHostInt32(fa.offset), + OSSwapBigToHostInt32(fa.size), thin)) + { + return Error; + } + switch (sliceRequiresGC(thin)) { + case WithoutGC: break; // no change + case WithGC: if (result != Error) result = WithGC; break; + case Error: result = Error; break; + } } } + else { + // Thin header or not a header. + result = sliceRequiresGC(file); + } + + return result; } +// SUPPORT_GC_COMPAT +#endif diff --git a/runtime/objc-sel-old.mm b/runtime/objc-sel-old.mm index 6962475..c956494 100644 --- a/runtime/objc-sel-old.mm +++ b/runtime/objc-sel-old.mm @@ -53,10 +53,6 @@ static SEL _objc_search_builtins(const char *key) #endif if (!key) return (SEL)0; -#if SUPPORT_IGNORED_SELECTOR_CONSTANT - if ((uintptr_t)key == kIgnore) return (SEL)kIgnore; - if (ignoreSelectorNamed(key)) return (SEL)kIgnore; -#endif if ('\0' == *key) return (SEL)_objc_empty_selector; #if SUPPORT_PREOPT @@ -68,9 +64,6 @@ static SEL _objc_search_builtins(const char *key) const char *sel_getName(SEL sel) { -#if SUPPORT_IGNORED_SELECTOR_CONSTANT - if ((uintptr_t)sel == kIgnore) return ""; -#endif return sel ? (const char *)sel : ""; } @@ -80,9 +73,6 @@ BOOL sel_isMapped(SEL name) SEL sel; if (!name) return NO; -#if SUPPORT_IGNORED_SELECTOR_CONSTANT - if ((uintptr_t)name == kIgnore) return YES; -#endif sel = _objc_search_builtins((const char *)name); if (sel) return YES; @@ -174,7 +164,7 @@ BOOL sel_isEqual(SEL lhs, SEL rhs) * sel_init * Initialize selector tables and register selectors used internally. **********************************************************************/ -void sel_init(bool wantsGC, size_t selrefCount) +void sel_init(size_t selrefCount) { // save this value for later SelrefCount = selrefCount; @@ -185,12 +175,6 @@ void sel_init(bool wantsGC, size_t selrefCount) // Register selectors used by libobjc - if (wantsGC) { - // Registering retain/release/autorelease requires GC decision first. - // sel_init doesn't actually need the wantsGC parameter, it just - // helps enforce the initialization order. - } - #define s(x) SEL_##x = sel_registerNameNoLock(#x, NO) #define t(x,y) SEL_##y = sel_registerNameNoLock(#x, NO) @@ -211,7 +195,6 @@ void sel_init(bool wantsGC, size_t selrefCount) s(dealloc); s(copy); s(new); - s(finalize); t(forwardInvocation:, forwardInvocation); t(_tryRetain, tryRetain); t(_isDeallocating, isDeallocating); diff --git a/runtime/objc-sel-table.s b/runtime/objc-sel-table.s index 04e3cee..359df9b 100644 --- a/runtime/objc-sel-table.s +++ b/runtime/objc-sel-table.s @@ -11,21 +11,24 @@ .align 3 .private_extern __objc_opt_data __objc_opt_data: -.long 13 /* table.version */ +.long 15 /* table.version */ +.long 0 /* table.flags */ .long 0 /* table.selopt_offset */ -.long 0 /* table.headeropt_offset */ -.long 0 /* table.clsopt_offset */ -.space PAGE_MAX_SIZE-16 +.long 0 /* table.headeropt_ro_offset */ +.long 0 /* table.clsopt_offset */ +.long 0 /* table.protocolopt_offset */ +.long 0 /* table.headeropt_rw_offset */ +.space PAGE_MAX_SIZE-28 -/* space for selopt, smax/capacity=262144, blen/mask=262143+1 */ +/* space for selopt, smax/capacity=524288, blen/mask=262143+1 */ .space 262144 /* mask tab */ .space 524288 /* checkbytes */ .space 524288*4 /* offsets */ -/* space for clsopt, smax/capacity=32768, blen/mask=16383+1 */ +/* space for clsopt, smax/capacity=65536, blen/mask=16383+1 */ .space 16384 /* mask tab */ -.space 32768 /* checkbytes */ -.space 32768*12 /* offsets to name and class and header_info */ +.space 65536 /* checkbytes */ +.space 65536*12 /* offsets to name and class and header_info */ .space PAGE_MAX_SIZE /* some duplicate classes */ /* space for protocolopt, smax/capacity=8192, blen/mask=4095+1 */ @@ -33,13 +36,15 @@ __objc_opt_data: .space 8192 /* checkbytes */ .space 8192*4 /* offsets */ +/* space for header_info (RO) structures */ +.space 16384 .section __DATA,__objc_opt_rw .align 3 .private_extern __objc_opt_rw_data __objc_opt_rw_data: -/* space for header_info structures */ -.space 32768 +/* space for header_info (RW) structures */ +.space 16384 /* space for 8192 protocols */ #if __LP64__ @@ -53,7 +58,7 @@ __objc_opt_rw_data: .section __DATA,__objc_opt_ptrs .align 3 -#if TARGET_OS_MAC && !TARGET_OS_IPHONE && __i386__ +#if TARGET_OS_OSX && __i386__ // old ABI .globl .objc_class_name_Protocol PTR(.objc_class_name_Protocol) diff --git a/runtime/objc-sel.mm b/runtime/objc-sel.mm index fe87f31..4c7fd29 100644 --- a/runtime/objc-sel.mm +++ b/runtime/objc-sel.mm @@ -30,10 +30,6 @@ static const objc_selopt_t *builtins = NULL; #endif -#if SUPPORT_IGNORED_SELECTOR_CONSTANT -#error sorry -#endif - static size_t SelrefCount = 0; @@ -46,7 +42,7 @@ * sel_init * Initialize selector tables and register selectors used internally. **********************************************************************/ -void sel_init(bool wantsGC, size_t selrefCount) +void sel_init(size_t selrefCount) { // save this value for later SelrefCount = selrefCount; @@ -68,12 +64,6 @@ void sel_init(bool wantsGC, size_t selrefCount) // Register selectors used by libobjc - if (wantsGC) { - // Registering retain/release/autorelease requires GC decision first. - // sel_init doesn't actually need the wantsGC parameter, it just - // helps enforce the initialization order. - } - #define s(x) SEL_##x = sel_registerNameNoLock(#x, NO) #define t(x,y) SEL_##y = sel_registerNameNoLock(#x, NO) @@ -94,7 +84,6 @@ void sel_init(bool wantsGC, size_t selrefCount) s(dealloc); s(copy); s(new); - s(finalize); t(forwardInvocation:, forwardInvocation); t(_tryRetain, tryRetain); t(_isDeallocating, isDeallocating); @@ -111,7 +100,7 @@ void sel_init(bool wantsGC, size_t selrefCount) static SEL sel_alloc(const char *name, bool copy) { selLock.assertWriting(); - return (SEL)(copy ? strdup(name) : name); + return (SEL)(copy ? strdupIfMutable(name) : name); } diff --git a/runtime/objc-sync.h b/runtime/objc-sync.h index 77ecf01..93a96fc 100644 --- a/runtime/objc-sync.h +++ b/runtime/objc-sync.h @@ -36,7 +36,7 @@ * @return OBJC_SYNC_SUCCESS once lock is acquired. */ OBJC_EXPORT int objc_sync_enter(id obj) - __OSX_AVAILABLE_STARTING(__MAC_10_3, __IPHONE_2_0); + OBJC_AVAILABLE(10.3, 2.0, 9.0, 1.0); /** * End synchronizing on 'obj'. @@ -46,7 +46,7 @@ OBJC_EXPORT int objc_sync_enter(id obj) * @return OBJC_SYNC_SUCCESS or OBJC_SYNC_NOT_OWNING_THREAD_ERROR */ OBJC_EXPORT int objc_sync_exit(id obj) - __OSX_AVAILABLE_STARTING(__MAC_10_3, __IPHONE_2_0); + OBJC_AVAILABLE(10.3, 2.0, 9.0, 1.0); // The wait/notify functions have never worked correctly and no longer exist. OBJC_EXPORT int objc_sync_wait(id obj, long long milliSecondsMaxWait) diff --git a/runtime/objc-weak.h b/runtime/objc-weak.h index f8c9400..8c50050 100644 --- a/runtime/objc-weak.h +++ b/runtime/objc-weak.h @@ -43,44 +43,73 @@ see its previous reference. So, in the hash table, indexed by the weakly referenced item, is a list of all locations where this address is currently being stored. -For ARR, we also keep track of whether an arbitrary object is being +For ARC, we also keep track of whether an arbitrary object is being deallocated by briefly placing it in the table just prior to invoking dealloc, and removing it via objc_clear_deallocating just prior to memory reclamation. */ -/// The address of a __weak object reference -typedef objc_object ** weak_referrer_t; +// The address of a __weak variable. +// These pointers are stored disguised so memory analysis tools +// don't see lots of interior pointers from the weak table into objects. +typedef DisguisedPtr weak_referrer_t; #if __LP64__ -#define PTR_MINUS_1 63 +#define PTR_MINUS_2 62 #else -#define PTR_MINUS_1 31 +#define PTR_MINUS_2 30 #endif /** * The internal structure stored in the weak references table. * It maintains and stores * a hash set of weak references pointing to an object. - * If out_of_line==0, the set is instead a small inline array. + * If out_of_line_ness != REFERRERS_OUT_OF_LINE then the set + * is instead a small inline array. */ #define WEAK_INLINE_COUNT 4 + +// out_of_line_ness field overlaps with the low two bits of inline_referrers[1]. +// inline_referrers[1] is a DisguisedPtr of a pointer-aligned address. +// The low two bits of a pointer-aligned DisguisedPtr will always be 0b00 +// (disguised nil or 0x80..00) or 0b11 (any other address). +// Therefore out_of_line_ness == 0b10 is used to mark the out-of-line state. +#define REFERRERS_OUT_OF_LINE 2 + struct weak_entry_t { DisguisedPtr referent; union { struct { weak_referrer_t *referrers; - uintptr_t out_of_line : 1; - uintptr_t num_refs : PTR_MINUS_1; + uintptr_t out_of_line_ness : 2; + uintptr_t num_refs : PTR_MINUS_2; uintptr_t mask; uintptr_t max_hash_displacement; }; struct { - // out_of_line=0 is LSB of one of these (don't care which) + // out_of_line_ness field is low bits of inline_referrers[1] weak_referrer_t inline_referrers[WEAK_INLINE_COUNT]; }; }; + + bool out_of_line() { + return (out_of_line_ness == REFERRERS_OUT_OF_LINE); + } + + weak_entry_t& operator=(const weak_entry_t& other) { + memcpy(this, &other, sizeof(other)); + return *this; + } + + weak_entry_t(objc_object *newReferent, objc_object **newReferrer) + : referent(newReferent) + { + inline_referrers[0] = newReferrer; + for (int i = 1; i < WEAK_INLINE_COUNT; i++) { + inline_referrers[i] = nil; + } + } }; /** @@ -106,9 +135,6 @@ void weak_unregister_no_lock(weak_table_t *weak_table, id referent, id *referrer bool weak_is_registered_no_lock(weak_table_t *weak_table, id referent); #endif -/// Assert a weak pointer is valid and retain the object during its use. -id weak_read_no_lock(weak_table_t *weak_table, id *referrer); - /// Called on object destruction. Sets all remaining weak pointers to nil. void weak_clear_no_lock(weak_table_t *weak_table, id referent); diff --git a/runtime/objc-weak.mm b/runtime/objc-weak.mm index c822953..3dd6d0a 100644 --- a/runtime/objc-weak.mm +++ b/runtime/objc-weak.mm @@ -38,6 +38,12 @@ void objc_weak_error(void) ); +static void bad_weak_table(weak_entry_t *entries) +{ + _objc_fatal("bad weak table at %p. This may be a runtime bug or a " + "memory error somewhere else.", entries); +} + /** * Unique hash function for object pointers only. * @@ -70,7 +76,7 @@ static inline uintptr_t w_hash_pointer(objc_object **key) { static void grow_refs_and_insert(weak_entry_t *entry, objc_object **new_referrer) { - assert(entry->out_of_line); + assert(entry->out_of_line()); size_t old_size = TABLE_SIZE(entry); size_t new_size = old_size ? old_size * 2 : 8; @@ -105,7 +111,7 @@ static void grow_refs_and_insert(weak_entry_t *entry, */ static void append_referrer(weak_entry_t *entry, objc_object **new_referrer) { - if (! entry->out_of_line) { + if (! entry->out_of_line()) { // Try to insert inline. for (size_t i = 0; i < WEAK_INLINE_COUNT; i++) { if (entry->inline_referrers[i] == nil) { @@ -124,21 +130,23 @@ static void append_referrer(weak_entry_t *entry, objc_object **new_referrer) } entry->referrers = new_referrers; entry->num_refs = WEAK_INLINE_COUNT; - entry->out_of_line = 1; + entry->out_of_line_ness = REFERRERS_OUT_OF_LINE; entry->mask = WEAK_INLINE_COUNT-1; entry->max_hash_displacement = 0; } - assert(entry->out_of_line); + assert(entry->out_of_line()); if (entry->num_refs >= TABLE_SIZE(entry) * 3/4) { return grow_refs_and_insert(entry, new_referrer); } - size_t index = w_hash_pointer(new_referrer) & (entry->mask); + size_t begin = w_hash_pointer(new_referrer) & (entry->mask); + size_t index = begin; size_t hash_displacement = 0; - while (entry->referrers[index] != NULL) { - index = (index+1) & entry->mask; + while (entry->referrers[index] != nil) { hash_displacement++; + index = (index+1) & entry->mask; + if (index == begin) bad_weak_table(entry); } if (hash_displacement > entry->max_hash_displacement) { entry->max_hash_displacement = hash_displacement; @@ -159,7 +167,7 @@ static void append_referrer(weak_entry_t *entry, objc_object **new_referrer) */ static void remove_referrer(weak_entry_t *entry, objc_object **old_referrer) { - if (! entry->out_of_line) { + if (! entry->out_of_line()) { for (size_t i = 0; i < WEAK_INLINE_COUNT; i++) { if (entry->inline_referrers[i] == old_referrer) { entry->inline_referrers[i] = nil; @@ -175,10 +183,12 @@ static void remove_referrer(weak_entry_t *entry, objc_object **old_referrer) return; } - size_t index = w_hash_pointer(old_referrer) & (entry->mask); + size_t begin = w_hash_pointer(old_referrer) & (entry->mask); + size_t index = begin; size_t hash_displacement = 0; while (entry->referrers[index] != old_referrer) { index = (index+1) & entry->mask; + if (index == begin) bad_weak_table(entry); hash_displacement++; if (hash_displacement > entry->max_hash_displacement) { _objc_inform("Attempted to unregister unknown __weak variable " @@ -203,10 +213,12 @@ static void weak_entry_insert(weak_table_t *weak_table, weak_entry_t *new_entry) weak_entry_t *weak_entries = weak_table->weak_entries; assert(weak_entries != nil); - size_t index = hash_pointer(new_entry->referent) & (weak_table->mask); + size_t begin = hash_pointer(new_entry->referent) & (weak_table->mask); + size_t index = begin; size_t hash_displacement = 0; while (weak_entries[index].referent != nil) { index = (index+1) & weak_table->mask; + if (index == begin) bad_weak_table(weak_entries); hash_displacement++; } @@ -274,7 +286,7 @@ static void weak_compact_maybe(weak_table_t *weak_table) static void weak_entry_remove(weak_table_t *weak_table, weak_entry_t *entry) { // remove entry - if (entry->out_of_line) free(entry->referrers); + if (entry->out_of_line()) free(entry->referrers); bzero(entry, sizeof(*entry)); weak_table->num_entries--; @@ -302,10 +314,12 @@ static void weak_entry_remove(weak_table_t *weak_table, weak_entry_t *entry) if (!weak_entries) return nil; - size_t index = hash_pointer(referent) & weak_table->mask; + size_t begin = hash_pointer(referent) & weak_table->mask; + size_t index = begin; size_t hash_displacement = 0; while (weak_table->weak_entries[index].referent != referent) { index = (index+1) & weak_table->mask; + if (index == begin) bad_weak_table(weak_table->weak_entries); hash_displacement++; if (hash_displacement > weak_table->max_hash_displacement) { return nil; @@ -344,7 +358,7 @@ static void weak_entry_remove(weak_table_t *weak_table, weak_entry_t *entry) if ((entry = weak_entry_for_referent(weak_table, referent))) { remove_referrer(entry, referrer); bool empty = true; - if (entry->out_of_line && entry->num_refs != 0) { + if (entry->out_of_line() && entry->num_refs != 0) { empty = false; } else { @@ -416,14 +430,7 @@ static void weak_entry_remove(weak_table_t *weak_table, weak_entry_t *entry) append_referrer(entry, referrer); } else { - weak_entry_t new_entry; - new_entry.referent = referent; - new_entry.out_of_line = 0; - new_entry.inline_referrers[0] = referrer; - for (size_t i = 1; i < WEAK_INLINE_COUNT; i++) { - new_entry.inline_referrers[i] = nil; - } - + weak_entry_t new_entry(referent, referrer); weak_grow_maybe(weak_table); weak_entry_insert(weak_table, &new_entry); } @@ -467,7 +474,7 @@ static void weak_entry_remove(weak_table_t *weak_table, weak_entry_t *entry) weak_referrer_t *referrers; size_t count; - if (entry->out_of_line) { + if (entry->out_of_line()) { referrers = entry->referrers; count = TABLE_SIZE(entry); } @@ -496,58 +503,3 @@ static void weak_entry_remove(weak_table_t *weak_table, weak_entry_t *entry) weak_entry_remove(weak_table, entry); } - -/** - * This function gets called when the value of a weak pointer is being - * used in an expression. Called by objc_loadWeakRetained() which is - * ultimately called by objc_loadWeak(). The objective is to assert that - * there is in fact a weak pointer(s) entry for this particular object being - * stored in the weak-table, and to retain that object so it is not deallocated - * during the weak pointer's usage. - * - * @param weak_table - * @param referrer The weak pointer address. - */ -/* - Once upon a time we eagerly cleared *referrer if we saw the referent - was deallocating. This confuses code like NSPointerFunctions which - tries to pre-flight the raw storage and assumes if the storage is - zero then the weak system is done interfering. That is false: the - weak system is still going to check and clear the storage later. - This can cause objc_weak_error complaints and crashes. - So we now don't touch the storage until deallocation completes. -*/ -id -weak_read_no_lock(weak_table_t *weak_table, id *referrer_id) -{ - objc_object **referrer = (objc_object **)referrer_id; - objc_object *referent = *referrer; - if (referent->isTaggedPointer()) return (id)referent; - - weak_entry_t *entry; - if (referent == nil || - !(entry = weak_entry_for_referent(weak_table, referent))) - { - return nil; - } - - if (! referent->ISA()->hasCustomRR()) { - if (! referent->rootTryRetain()) { - return nil; - } - } - else { - BOOL (*tryRetain)(objc_object *, SEL) = (BOOL(*)(objc_object *, SEL)) - object_getMethodImplementation((id)referent, - SEL_retainWeakReference); - if ((IMP)tryRetain == _objc_msgForward) { - return nil; - } - if (! (*tryRetain)(referent, SEL_retainWeakReference)) { - return nil; - } - } - - return (id)referent; -} - diff --git a/runtime/objc.h b/runtime/objc.h index b0554cc..7417ebc 100644 --- a/runtime/objc.h +++ b/runtime/objc.h @@ -93,13 +93,22 @@ typedef signed char BOOL; # endif #endif -#if ! (defined(__OBJC_GC__) || __has_feature(objc_arc)) -#define __strong /* empty */ +#ifndef __strong +# if !__has_feature(objc_arc) +# define __strong /* empty */ +# endif #endif -#if !__has_feature(objc_arc) -#define __unsafe_unretained /* empty */ -#define __autoreleasing /* empty */ +#ifndef __unsafe_unretained +# if !__has_feature(objc_arc) +# define __unsafe_unretained /* empty */ +# endif +#endif + +#ifndef __autoreleasing +# if !__has_feature(objc_arc) +# define __autoreleasing /* empty */ +# endif #endif @@ -111,7 +120,7 @@ typedef signed char BOOL; * @return A C string indicating the name of the selector. */ OBJC_EXPORT const char *sel_getName(SEL sel) - __OSX_AVAILABLE_STARTING(__MAC_10_0, __IPHONE_2_0); + OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0); /** * Registers a method with the Objective-C runtime system, maps the method @@ -126,7 +135,7 @@ OBJC_EXPORT const char *sel_getName(SEL sel) * has already been registered, this function simply returns the selector. */ OBJC_EXPORT SEL sel_registerName(const char *str) - __OSX_AVAILABLE_STARTING(__MAC_10_0, __IPHONE_2_0); + OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0); /** * Returns the class name of a given object. @@ -136,7 +145,7 @@ OBJC_EXPORT SEL sel_registerName(const char *str) * @return The name of the class of which \e obj is an instance. */ OBJC_EXPORT const char *object_getClassName(id obj) - __OSX_AVAILABLE_STARTING(__MAC_10_0, __IPHONE_2_0); + OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0); /** * Returns a pointer to any extra bytes allocated with an instance given object. @@ -155,7 +164,7 @@ OBJC_EXPORT const char *object_getClassName(id obj) * @note In a garbage-collected environment, the memory is scanned conservatively. */ OBJC_EXPORT void *object_getIndexedIvars(id obj) - __OSX_AVAILABLE_STARTING(__MAC_10_0, __IPHONE_2_0); + OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0); /** * Identifies a selector as being valid or invalid. @@ -168,7 +177,7 @@ OBJC_EXPORT void *object_getIndexedIvars(id obj) * a crash. */ OBJC_EXPORT BOOL sel_isMapped(SEL sel) - __OSX_AVAILABLE_STARTING(__MAC_10_0, __IPHONE_2_0); + OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0); /** * Registers a method name with the Objective-C runtime system. @@ -183,23 +192,19 @@ OBJC_EXPORT BOOL sel_isMapped(SEL sel) * observed that many of the callers of this function did not check the return value for \c NULL. */ OBJC_EXPORT SEL sel_getUid(const char *str) - __OSX_AVAILABLE_STARTING(__MAC_10_0, __IPHONE_2_0); + OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0); +typedef const void* objc_objectptr_t; -// Obsolete ARC conversions. Deprecation forthcoming. -// Use CFBridgingRetain, CFBridgingRelease, and __bridge casts instead. -typedef const void* objc_objectptr_t; +// Obsolete ARC conversions. -#if __has_feature(objc_arc) -# define objc_retainedObject(o) ((__bridge_transfer id)(objc_objectptr_t)(o)) -# define objc_unretainedObject(o) ((__bridge id)(objc_objectptr_t)(o)) -# define objc_unretainedPointer(o) ((__bridge objc_objectptr_t)(id)(o)) -#else -# define objc_retainedObject(o) ((id)(objc_objectptr_t)(o)) -# define objc_unretainedObject(o) ((id)(objc_objectptr_t)(o)) -# define objc_unretainedPointer(o) ((objc_objectptr_t)(id)(o)) -#endif +OBJC_EXPORT id objc_retainedObject(objc_objectptr_t obj) + OBJC_UNAVAILABLE("use CFBridgingRelease() or a (__bridge_transfer id) cast instead"); +OBJC_EXPORT id objc_unretainedObject(objc_objectptr_t obj) + OBJC_UNAVAILABLE("use a (__bridge id) cast instead"); +OBJC_EXPORT objc_objectptr_t objc_unretainedPointer(id obj) + OBJC_UNAVAILABLE("use a __bridge cast instead"); #if !__OBJC2__ diff --git a/runtime/runtime.h b/runtime/runtime.h index 7c7ce6e..38e74f1 100644 --- a/runtime/runtime.h +++ b/runtime/runtime.h @@ -104,7 +104,7 @@ typedef struct { * @return A copy of \e obj. */ OBJC_EXPORT id object_copy(id obj, size_t size) - __OSX_AVAILABLE_STARTING(__MAC_10_0, __IPHONE_2_0) + OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0) OBJC_ARC_UNAVAILABLE; /** @@ -115,7 +115,7 @@ OBJC_EXPORT id object_copy(id obj, size_t size) * @return nil */ OBJC_EXPORT id object_dispose(id obj) - __OSX_AVAILABLE_STARTING(__MAC_10_0, __IPHONE_2_0) + OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0) OBJC_ARC_UNAVAILABLE; /** @@ -127,7 +127,7 @@ OBJC_EXPORT id object_dispose(id obj) * or \c Nil if \e object is \c nil. */ OBJC_EXPORT Class object_getClass(id obj) - __OSX_AVAILABLE_STARTING(__MAC_10_5, __IPHONE_2_0); + OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0); /** * Sets the class of an object. @@ -138,7 +138,7 @@ OBJC_EXPORT Class object_getClass(id obj) * @return The previous value of \e object's class, or \c Nil if \e object is \c nil. */ OBJC_EXPORT Class object_setClass(id obj, Class cls) - __OSX_AVAILABLE_STARTING(__MAC_10_5, __IPHONE_2_0); + OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0); /** @@ -149,7 +149,7 @@ OBJC_EXPORT Class object_setClass(id obj, Class cls) * @return true if the object is a class or metaclass, false otherwise. */ OBJC_EXPORT BOOL object_isClass(id obj) - __OSX_AVAILABLE_STARTING(__MAC_10_10, __IPHONE_8_0); + OBJC_AVAILABLE(10.10, 8.0, 9.0, 1.0); /** @@ -160,7 +160,7 @@ OBJC_EXPORT BOOL object_isClass(id obj) * @return The name of the class of which \e obj is an instance. */ OBJC_EXPORT const char *object_getClassName(id obj) - __OSX_AVAILABLE_STARTING(__MAC_10_0, __IPHONE_2_0); + OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0); /** * Returns a pointer to any extra bytes allocated with an instance given object. @@ -179,7 +179,7 @@ OBJC_EXPORT const char *object_getClassName(id obj) * @note In a garbage-collected environment, the memory is scanned conservatively. */ OBJC_EXPORT void *object_getIndexedIvars(id obj) - __OSX_AVAILABLE_STARTING(__MAC_10_0, __IPHONE_2_0) + OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0) OBJC_ARC_UNAVAILABLE; /** @@ -194,7 +194,7 @@ OBJC_EXPORT void *object_getIndexedIvars(id obj) * for the instance variable is already known. */ OBJC_EXPORT id object_getIvar(id obj, Ivar ivar) - __OSX_AVAILABLE_STARTING(__MAC_10_5, __IPHONE_2_0); + OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0); /** * Sets the value of an instance variable in an object. @@ -203,11 +203,30 @@ OBJC_EXPORT id object_getIvar(id obj, Ivar ivar) * @param ivar The Ivar describing the instance variable whose value you want to set. * @param value The new value for the instance variable. * + * @note Instance variables with known memory management (such as ARC strong and weak) + * use that memory management. Instance variables with unknown memory management + * are assigned as if they were unsafe_unretained. * @note \c object_setIvar is faster than \c object_setInstanceVariable if the Ivar * for the instance variable is already known. */ OBJC_EXPORT void object_setIvar(id obj, Ivar ivar, id value) - __OSX_AVAILABLE_STARTING(__MAC_10_5, __IPHONE_2_0); + OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0); + +/** + * Sets the value of an instance variable in an object. + * + * @param obj The object containing the instance variable whose value you want to set. + * @param ivar The Ivar describing the instance variable whose value you want to set. + * @param value The new value for the instance variable. + * + * @note Instance variables with known memory management (such as ARC strong and weak) + * use that memory management. Instance variables with unknown memory management + * are assigned as if they were strong. + * @note \c object_setIvar is faster than \c object_setInstanceVariable if the Ivar + * for the instance variable is already known. + */ +OBJC_EXPORT void object_setIvarWithStrongDefault(id obj, Ivar ivar, id value) + OBJC_AVAILABLE(10.12, 10.0, 10.0, 3.0); /** * Changes the value of an instance variable of a class instance. @@ -219,9 +238,32 @@ OBJC_EXPORT void object_setIvar(id obj, Ivar ivar, id value) * * @return A pointer to the \c Ivar data structure that defines the type and * name of the instance variable specified by \e name. + * + * @note Instance variables with known memory management (such as ARC strong and weak) + * use that memory management. Instance variables with unknown memory management + * are assigned as if they were unsafe_unretained. */ OBJC_EXPORT Ivar object_setInstanceVariable(id obj, const char *name, void *value) - __OSX_AVAILABLE_STARTING(__MAC_10_0, __IPHONE_2_0) + OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0) + OBJC_ARC_UNAVAILABLE; + +/** + * Changes the value of an instance variable of a class instance. + * + * @param obj A pointer to an instance of a class. Pass the object containing + * the instance variable whose value you wish to modify. + * @param name A C string. Pass the name of the instance variable whose value you wish to modify. + * @param value The new value for the instance variable. + * + * @return A pointer to the \c Ivar data structure that defines the type and + * name of the instance variable specified by \e name. + * + * @note Instance variables with known memory management (such as ARC strong and weak) + * use that memory management. Instance variables with unknown memory management + * are assigned as if they were strong. + */ +OBJC_EXPORT Ivar object_setInstanceVariableWithStrongDefault(id obj, const char *name, void *value) + OBJC_AVAILABLE(10.12, 10.0, 10.0, 3.0) OBJC_ARC_UNAVAILABLE; /** @@ -236,7 +278,7 @@ OBJC_EXPORT Ivar object_setInstanceVariable(id obj, const char *name, void *valu * the instance variable specified by \e name. */ OBJC_EXPORT Ivar object_getInstanceVariable(id obj, const char *name, void **outValue) - __OSX_AVAILABLE_STARTING(__MAC_10_0, __IPHONE_2_0) + OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0) OBJC_ARC_UNAVAILABLE; @@ -259,7 +301,7 @@ OBJC_EXPORT Ivar object_getInstanceVariable(id obj, const char *name, void **out * terminate the program if the class does not exist. */ OBJC_EXPORT Class objc_getClass(const char *name) - __OSX_AVAILABLE_STARTING(__MAC_10_0, __IPHONE_2_0); + OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0); /** * Returns the metaclass definition of a specified class. @@ -275,7 +317,7 @@ OBJC_EXPORT Class objc_getClass(const char *name) * whether it’s valid or not. */ OBJC_EXPORT Class objc_getMetaClass(const char *name) - __OSX_AVAILABLE_STARTING(__MAC_10_0, __IPHONE_2_0); + OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0); /** * Returns the class definition of a specified class. @@ -290,7 +332,7 @@ OBJC_EXPORT Class objc_getMetaClass(const char *name) * time to see whether the class is registered. This function does not call the class handler callback. */ OBJC_EXPORT Class objc_lookUpClass(const char *name) - __OSX_AVAILABLE_STARTING(__MAC_10_0, __IPHONE_2_0); + OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0); /** * Returns the class definition of a specified class. @@ -303,7 +345,7 @@ OBJC_EXPORT Class objc_lookUpClass(const char *name) * @note This function is used by ZeroLink, where failing to find a class would be a compile-time link error without ZeroLink. */ OBJC_EXPORT Class objc_getRequiredClass(const char *name) - __OSX_AVAILABLE_STARTING(__MAC_10_0, __IPHONE_2_0); + OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0); /** * Obtains the list of registered class definitions. @@ -325,7 +367,7 @@ OBJC_EXPORT Class objc_getRequiredClass(const char *name) * so you cannot safely call any methods on such classes without detecting that the method is implemented first. */ OBJC_EXPORT int objc_getClassList(Class *buffer, int bufferCount) - __OSX_AVAILABLE_STARTING(__MAC_10_0, __IPHONE_2_0); + OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0); /** * Creates and returns a list of pointers to all registered class definitions. @@ -338,7 +380,7 @@ OBJC_EXPORT int objc_getClassList(Class *buffer, int bufferCount) * @see objc_getClassList */ OBJC_EXPORT Class *objc_copyClassList(unsigned int *outCount) - __OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_3_1); + OBJC_AVAILABLE(10.7, 3.1, 9.0, 1.0); /* Working with Classes */ @@ -351,7 +393,7 @@ OBJC_EXPORT Class *objc_copyClassList(unsigned int *outCount) * @return The name of the class, or the empty string if \e cls is \c Nil. */ OBJC_EXPORT const char *class_getName(Class cls) - __OSX_AVAILABLE_STARTING(__MAC_10_5, __IPHONE_2_0); + OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0); /** * Returns a Boolean value that indicates whether a class object is a metaclass. @@ -362,7 +404,7 @@ OBJC_EXPORT const char *class_getName(Class cls) * \c NO if \e cls is \c Nil. */ OBJC_EXPORT BOOL class_isMetaClass(Class cls) - __OSX_AVAILABLE_STARTING(__MAC_10_5, __IPHONE_2_0); + OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0); /** * Returns the superclass of a class. @@ -375,7 +417,7 @@ OBJC_EXPORT BOOL class_isMetaClass(Class cls) * @note You should usually use \c NSObject's \c superclass method instead of this function. */ OBJC_EXPORT Class class_getSuperclass(Class cls) - __OSX_AVAILABLE_STARTING(__MAC_10_5, __IPHONE_2_0); + OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0); /** * Sets the superclass of a given class. @@ -388,7 +430,10 @@ OBJC_EXPORT Class class_getSuperclass(Class cls) * @warning You should not use this function. */ OBJC_EXPORT Class class_setSuperclass(Class cls, Class newSuper) - __OSX_AVAILABLE_BUT_DEPRECATED(__MAC_10_5,__MAC_10_5, __IPHONE_2_0,__IPHONE_2_0); + __OSX_DEPRECATED(10.5, 10.5, "not recommended") + __IOS_DEPRECATED(2.0, 2.0, "not recommended") + __TVOS_DEPRECATED(9.0, 9.0, "not recommended") + __WATCHOS_DEPRECATED(1.0, 1.0, "not recommended"); /** * Returns the version number of a class definition. @@ -401,7 +446,7 @@ OBJC_EXPORT Class class_setSuperclass(Class cls, Class newSuper) * @see class_setVersion */ OBJC_EXPORT int class_getVersion(Class cls) - __OSX_AVAILABLE_STARTING(__MAC_10_0, __IPHONE_2_0); + OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0); /** * Sets the version number of a class definition. @@ -418,7 +463,7 @@ OBJC_EXPORT int class_getVersion(Class cls) * version number using the \c setVersion: class method, which is implemented using the \c class_setVersion function. */ OBJC_EXPORT void class_setVersion(Class cls, int version) - __OSX_AVAILABLE_STARTING(__MAC_10_0, __IPHONE_2_0); + OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0); /** * Returns the size of instances of a class. @@ -428,7 +473,7 @@ OBJC_EXPORT void class_setVersion(Class cls, int version) * @return The size in bytes of instances of the class \e cls, or \c 0 if \e cls is \c Nil. */ OBJC_EXPORT size_t class_getInstanceSize(Class cls) - __OSX_AVAILABLE_STARTING(__MAC_10_5, __IPHONE_2_0); + OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0); /** * Returns the \c Ivar for a specified instance variable of a given class. @@ -440,7 +485,7 @@ OBJC_EXPORT size_t class_getInstanceSize(Class cls) * the instance variable specified by \e name. */ OBJC_EXPORT Ivar class_getInstanceVariable(Class cls, const char *name) - __OSX_AVAILABLE_STARTING(__MAC_10_0, __IPHONE_2_0); + OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0); /** * Returns the Ivar for a specified class variable of a given class. @@ -451,7 +496,7 @@ OBJC_EXPORT Ivar class_getInstanceVariable(Class cls, const char *name) * @return A pointer to an \c Ivar data structure containing information about the class variable specified by \e name. */ OBJC_EXPORT Ivar class_getClassVariable(Class cls, const char *name) - __OSX_AVAILABLE_STARTING(__MAC_10_5, __IPHONE_2_0); + OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0); /** * Describes the instance variables declared by a class. @@ -467,7 +512,7 @@ OBJC_EXPORT Ivar class_getClassVariable(Class cls, const char *name) * If the class declares no instance variables, or cls is Nil, NULL is returned and *outCount is 0. */ OBJC_EXPORT Ivar *class_copyIvarList(Class cls, unsigned int *outCount) - __OSX_AVAILABLE_STARTING(__MAC_10_5, __IPHONE_2_0); + OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0); /** * Returns a specified instance method for a given class. @@ -482,7 +527,7 @@ OBJC_EXPORT Ivar *class_copyIvarList(Class cls, unsigned int *outCount) * @note This function searches superclasses for implementations, whereas \c class_copyMethodList does not. */ OBJC_EXPORT Method class_getInstanceMethod(Class cls, SEL name) - __OSX_AVAILABLE_STARTING(__MAC_10_0, __IPHONE_2_0); + OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0); /** * Returns a pointer to the data structure describing a given class method for a given class. @@ -498,7 +543,7 @@ OBJC_EXPORT Method class_getInstanceMethod(Class cls, SEL name) * whereas \c class_copyMethodList does not. */ OBJC_EXPORT Method class_getClassMethod(Class cls, SEL name) - __OSX_AVAILABLE_STARTING(__MAC_10_0, __IPHONE_2_0); + OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0); /** * Returns the function pointer that would be called if a @@ -516,7 +561,7 @@ OBJC_EXPORT Method class_getClassMethod(Class cls, SEL name) * the selector, the function pointer returned will be part of the runtime's message forwarding machinery. */ OBJC_EXPORT IMP class_getMethodImplementation(Class cls, SEL name) - __OSX_AVAILABLE_STARTING(__MAC_10_5, __IPHONE_2_0); + OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0); /** * Returns the function pointer that would be called if a particular @@ -529,8 +574,8 @@ OBJC_EXPORT IMP class_getMethodImplementation(Class cls, SEL name) * with an instance of the class, or \c NULL if \e cls is \c Nil. */ OBJC_EXPORT IMP class_getMethodImplementation_stret(Class cls, SEL name) - __OSX_AVAILABLE_STARTING(__MAC_10_5, __IPHONE_2_0) - OBJC_ARM64_UNAVAILABLE; + OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0) + OBJC_ARM64_UNAVAILABLE; /** * Returns a Boolean value that indicates whether instances of a class respond to a particular selector. @@ -544,7 +589,7 @@ OBJC_EXPORT IMP class_getMethodImplementation_stret(Class cls, SEL name) * methods instead of this function. */ OBJC_EXPORT BOOL class_respondsToSelector(Class cls, SEL sel) - __OSX_AVAILABLE_STARTING(__MAC_10_5, __IPHONE_2_0); + OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0); /** * Describes the instance methods implemented by a class. @@ -564,7 +609,7 @@ OBJC_EXPORT BOOL class_respondsToSelector(Class cls, SEL sel) * use \c class_getInstanceMethod or \c class_getClassMethod. */ OBJC_EXPORT Method *class_copyMethodList(Class cls, unsigned int *outCount) - __OSX_AVAILABLE_STARTING(__MAC_10_5, __IPHONE_2_0); + OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0); /** * Returns a Boolean value that indicates whether a class conforms to a given protocol. @@ -577,7 +622,7 @@ OBJC_EXPORT Method *class_copyMethodList(Class cls, unsigned int *outCount) * @note You should usually use NSObject's conformsToProtocol: method instead of this function. */ OBJC_EXPORT BOOL class_conformsToProtocol(Class cls, Protocol *protocol) - __OSX_AVAILABLE_STARTING(__MAC_10_5, __IPHONE_2_0); + OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0); /** * Describes the protocols adopted by a class. @@ -593,7 +638,7 @@ OBJC_EXPORT BOOL class_conformsToProtocol(Class cls, Protocol *protocol) * If cls adopts no protocols, or cls is Nil, returns NULL and *outCount is 0. */ OBJC_EXPORT Protocol * __unsafe_unretained *class_copyProtocolList(Class cls, unsigned int *outCount) - __OSX_AVAILABLE_STARTING(__MAC_10_5, __IPHONE_2_0); + OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0); /** * Returns a property with a given name of a given class. @@ -606,7 +651,7 @@ OBJC_EXPORT Protocol * __unsafe_unretained *class_copyProtocolList(Class cls, un * or \c NULL if \e cls is \c Nil. */ OBJC_EXPORT objc_property_t class_getProperty(Class cls, const char *name) - __OSX_AVAILABLE_STARTING(__MAC_10_5, __IPHONE_2_0); + OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0); /** * Describes the properties declared by a class. @@ -622,7 +667,7 @@ OBJC_EXPORT objc_property_t class_getProperty(Class cls, const char *name) * If \e cls declares no properties, or \e cls is \c Nil, returns \c NULL and \c *outCount is \c 0. */ OBJC_EXPORT objc_property_t *class_copyPropertyList(Class cls, unsigned int *outCount) - __OSX_AVAILABLE_STARTING(__MAC_10_5, __IPHONE_2_0); + OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0); /** * Returns a description of the \c Ivar layout for a given class. @@ -632,7 +677,7 @@ OBJC_EXPORT objc_property_t *class_copyPropertyList(Class cls, unsigned int *out * @return A description of the \c Ivar layout for \e cls. */ OBJC_EXPORT const uint8_t *class_getIvarLayout(Class cls) - __OSX_AVAILABLE_STARTING(__MAC_10_5, __IPHONE_2_0); + OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0); /** * Returns a description of the layout of weak Ivars for a given class. @@ -642,7 +687,7 @@ OBJC_EXPORT const uint8_t *class_getIvarLayout(Class cls) * @return A description of the layout of the weak \c Ivars for \e cls. */ OBJC_EXPORT const uint8_t *class_getWeakIvarLayout(Class cls) - __OSX_AVAILABLE_STARTING(__MAC_10_5, __IPHONE_2_0); + OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0); /** * Adds a new method to a class with a given name and implementation. @@ -661,7 +706,7 @@ OBJC_EXPORT const uint8_t *class_getWeakIvarLayout(Class cls) */ OBJC_EXPORT BOOL class_addMethod(Class cls, SEL name, IMP imp, const char *types) - __OSX_AVAILABLE_STARTING(__MAC_10_5, __IPHONE_2_0); + OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0); /** * Replaces the implementation of a method for a given class. @@ -683,7 +728,7 @@ OBJC_EXPORT BOOL class_addMethod(Class cls, SEL name, IMP imp, */ OBJC_EXPORT IMP class_replaceMethod(Class cls, SEL name, IMP imp, const char *types) - __OSX_AVAILABLE_STARTING(__MAC_10_5, __IPHONE_2_0); + OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0); /** * Adds a new instance variable to a class. @@ -700,7 +745,7 @@ OBJC_EXPORT IMP class_replaceMethod(Class cls, SEL name, IMP imp, */ OBJC_EXPORT BOOL class_addIvar(Class cls, const char *name, size_t size, uint8_t alignment, const char *types) - __OSX_AVAILABLE_STARTING(__MAC_10_5, __IPHONE_2_0); + OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0); /** * Adds a protocol to a class. @@ -712,7 +757,7 @@ OBJC_EXPORT BOOL class_addIvar(Class cls, const char *name, size_t size, * (for example, the class already conforms to that protocol). */ OBJC_EXPORT BOOL class_addProtocol(Class cls, Protocol *protocol) - __OSX_AVAILABLE_STARTING(__MAC_10_5, __IPHONE_2_0); + OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0); /** * Adds a property to a class. @@ -726,7 +771,7 @@ OBJC_EXPORT BOOL class_addProtocol(Class cls, Protocol *protocol) * (for example, the class already has that property). */ OBJC_EXPORT BOOL class_addProperty(Class cls, const char *name, const objc_property_attribute_t *attributes, unsigned int attributeCount) - __OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_4_3); + OBJC_AVAILABLE(10.7, 4.3, 9.0, 1.0); /** * Replace a property of a class. @@ -737,7 +782,7 @@ OBJC_EXPORT BOOL class_addProperty(Class cls, const char *name, const objc_prope * @param attributeCount The number of attributes in \e attributes. */ OBJC_EXPORT void class_replaceProperty(Class cls, const char *name, const objc_property_attribute_t *attributes, unsigned int attributeCount) - __OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_4_3); + OBJC_AVAILABLE(10.7, 4.3, 9.0, 1.0); /** * Sets the Ivar layout for a given class. @@ -746,7 +791,7 @@ OBJC_EXPORT void class_replaceProperty(Class cls, const char *name, const objc_p * @param layout The layout of the \c Ivars for \e cls. */ OBJC_EXPORT void class_setIvarLayout(Class cls, const uint8_t *layout) - __OSX_AVAILABLE_STARTING(__MAC_10_5, __IPHONE_2_0); + OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0); /** * Sets the layout for weak Ivars for a given class. @@ -755,7 +800,7 @@ OBJC_EXPORT void class_setIvarLayout(Class cls, const uint8_t *layout) * @param layout The layout of the weak Ivars for \e cls. */ OBJC_EXPORT void class_setWeakIvarLayout(Class cls, const uint8_t *layout) - __OSX_AVAILABLE_STARTING(__MAC_10_5, __IPHONE_2_0); + OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0); /** * Used by CoreFoundation's toll-free bridging. @@ -768,8 +813,8 @@ OBJC_EXPORT void class_setWeakIvarLayout(Class cls, const uint8_t *layout) * @warning Do not call this function yourself. */ OBJC_EXPORT Class objc_getFutureClass(const char *name) - __OSX_AVAILABLE_STARTING(__MAC_10_5, __IPHONE_2_0) - OBJC_ARC_UNAVAILABLE; + OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0) + OBJC_ARC_UNAVAILABLE; /* Instantiating Classes */ @@ -786,7 +831,7 @@ OBJC_EXPORT Class objc_getFutureClass(const char *name) * @return An instance of the class \e cls. */ OBJC_EXPORT id class_createInstance(Class cls, size_t extraBytes) - __OSX_AVAILABLE_STARTING(__MAC_10_0, __IPHONE_2_0) + OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0) OBJC_ARC_UNAVAILABLE; /** @@ -803,7 +848,7 @@ OBJC_EXPORT id class_createInstance(Class cls, size_t extraBytes) * @see class_createInstance */ OBJC_EXPORT id objc_constructInstance(Class cls, void *bytes) - __OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_3_0) + OBJC_AVAILABLE(10.6, 3.0, 9.0, 1.0) OBJC_ARC_UNAVAILABLE; /** @@ -814,12 +859,10 @@ OBJC_EXPORT id objc_constructInstance(Class cls, void *bytes) * * @return \e obj. Does nothing if \e obj is nil. * - * @warning GC does not call this. If you edit this, also edit finalize. - * * @note CF and other clients do call this under GC. */ OBJC_EXPORT void *objc_destructInstance(id obj) - __OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_3_0) + OBJC_AVAILABLE(10.6, 3.0, 9.0, 1.0) OBJC_ARC_UNAVAILABLE; @@ -844,7 +887,7 @@ OBJC_EXPORT void *objc_destructInstance(id obj) */ OBJC_EXPORT Class objc_allocateClassPair(Class superclass, const char *name, size_t extraBytes) - __OSX_AVAILABLE_STARTING(__MAC_10_5, __IPHONE_2_0); + OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0); /** * Registers a class that was allocated using \c objc_allocateClassPair. @@ -852,7 +895,7 @@ OBJC_EXPORT Class objc_allocateClassPair(Class superclass, const char *name, * @param cls The class you want to register. */ OBJC_EXPORT void objc_registerClassPair(Class cls) - __OSX_AVAILABLE_STARTING(__MAC_10_5, __IPHONE_2_0); + OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0); /** * Used by Foundation's Key-Value Observing. @@ -860,7 +903,7 @@ OBJC_EXPORT void objc_registerClassPair(Class cls) * @warning Do not call this function yourself. */ OBJC_EXPORT Class objc_duplicateClass(Class original, const char *name, size_t extraBytes) - __OSX_AVAILABLE_STARTING(__MAC_10_5, __IPHONE_2_0); + OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0); /** * Destroy a class and its associated metaclass. @@ -871,7 +914,7 @@ OBJC_EXPORT Class objc_duplicateClass(Class original, const char *name, size_t e * @warning Do not call if instances of this class or a subclass exist. */ OBJC_EXPORT void objc_disposeClassPair(Class cls) - __OSX_AVAILABLE_STARTING(__MAC_10_5, __IPHONE_2_0); + OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0); /* Working with Methods */ @@ -886,7 +929,7 @@ OBJC_EXPORT void objc_disposeClassPair(Class cls) * @note To get the method name as a C string, call \c sel_getName(method_getName(method)). */ OBJC_EXPORT SEL method_getName(Method m) - __OSX_AVAILABLE_STARTING(__MAC_10_5, __IPHONE_2_0); + OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0); /** * Returns the implementation of a method. @@ -896,7 +939,7 @@ OBJC_EXPORT SEL method_getName(Method m) * @return A function pointer of type IMP. */ OBJC_EXPORT IMP method_getImplementation(Method m) - __OSX_AVAILABLE_STARTING(__MAC_10_5, __IPHONE_2_0); + OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0); /** * Returns a string describing a method's parameter and return types. @@ -906,7 +949,7 @@ OBJC_EXPORT IMP method_getImplementation(Method m) * @return A C string. The string may be \c NULL. */ OBJC_EXPORT const char *method_getTypeEncoding(Method m) - __OSX_AVAILABLE_STARTING(__MAC_10_5, __IPHONE_2_0); + OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0); /** * Returns the number of arguments accepted by a method. @@ -916,7 +959,7 @@ OBJC_EXPORT const char *method_getTypeEncoding(Method m) * @return An integer containing the number of arguments accepted by the given method. */ OBJC_EXPORT unsigned int method_getNumberOfArguments(Method m) - __OSX_AVAILABLE_STARTING(__MAC_10_0, __IPHONE_2_0); + OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0); /** * Returns a string describing a method's return type. @@ -926,7 +969,7 @@ OBJC_EXPORT unsigned int method_getNumberOfArguments(Method m) * @return A C string describing the return type. You must free the string with \c free(). */ OBJC_EXPORT char *method_copyReturnType(Method m) - __OSX_AVAILABLE_STARTING(__MAC_10_5, __IPHONE_2_0); + OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0); /** * Returns a string describing a single parameter type of a method. @@ -938,7 +981,7 @@ OBJC_EXPORT char *method_copyReturnType(Method m) * if method has no parameter index \e index. You must free the string with \c free(). */ OBJC_EXPORT char *method_copyArgumentType(Method m, unsigned int index) - __OSX_AVAILABLE_STARTING(__MAC_10_5, __IPHONE_2_0); + OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0); /** * Returns by reference a string describing a method's return type. @@ -951,7 +994,7 @@ OBJC_EXPORT char *method_copyArgumentType(Method m, unsigned int index) * \e dst is filled as if \c strncpy(dst, parameter_type, dst_len) were called. */ OBJC_EXPORT void method_getReturnType(Method m, char *dst, size_t dst_len) - __OSX_AVAILABLE_STARTING(__MAC_10_5, __IPHONE_2_0); + OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0); /** * Returns by reference a string describing a single parameter type of a method. @@ -967,9 +1010,9 @@ OBJC_EXPORT void method_getReturnType(Method m, char *dst, size_t dst_len) */ OBJC_EXPORT void method_getArgumentType(Method m, unsigned int index, char *dst, size_t dst_len) - __OSX_AVAILABLE_STARTING(__MAC_10_5, __IPHONE_2_0); + OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0); OBJC_EXPORT struct objc_method_description *method_getDescription(Method m) - __OSX_AVAILABLE_STARTING(__MAC_10_5, __IPHONE_2_0); + OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0); /** * Sets the implementation of a method. @@ -980,7 +1023,7 @@ OBJC_EXPORT struct objc_method_description *method_getDescription(Method m) * @return The previous implementation of the method. */ OBJC_EXPORT IMP method_setImplementation(Method m, IMP imp) - __OSX_AVAILABLE_STARTING(__MAC_10_5, __IPHONE_2_0); + OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0); /** * Exchanges the implementations of two methods. @@ -997,7 +1040,7 @@ OBJC_EXPORT IMP method_setImplementation(Method m, IMP imp) * \endcode */ OBJC_EXPORT void method_exchangeImplementations(Method m1, Method m2) - __OSX_AVAILABLE_STARTING(__MAC_10_5, __IPHONE_2_0); + OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0); /* Working with Instance Variables */ @@ -1010,7 +1053,7 @@ OBJC_EXPORT void method_exchangeImplementations(Method m1, Method m2) * @return A C string containing the instance variable's name. */ OBJC_EXPORT const char *ivar_getName(Ivar v) - __OSX_AVAILABLE_STARTING(__MAC_10_5, __IPHONE_2_0); + OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0); /** * Returns the type string of an instance variable. @@ -1022,7 +1065,7 @@ OBJC_EXPORT const char *ivar_getName(Ivar v) * @note For possible values, see Objective-C Runtime Programming Guide > Type Encodings. */ OBJC_EXPORT const char *ivar_getTypeEncoding(Ivar v) - __OSX_AVAILABLE_STARTING(__MAC_10_5, __IPHONE_2_0); + OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0); /** * Returns the offset of an instance variable. @@ -1035,7 +1078,7 @@ OBJC_EXPORT const char *ivar_getTypeEncoding(Ivar v) * and \c object_setIvar instead of using this offset to access the instance variable data directly. */ OBJC_EXPORT ptrdiff_t ivar_getOffset(Ivar v) - __OSX_AVAILABLE_STARTING(__MAC_10_5, __IPHONE_2_0); + OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0); /* Working with Properties */ @@ -1048,7 +1091,7 @@ OBJC_EXPORT ptrdiff_t ivar_getOffset(Ivar v) * @return A C string containing the property's name. */ OBJC_EXPORT const char *property_getName(objc_property_t property) - __OSX_AVAILABLE_STARTING(__MAC_10_5, __IPHONE_2_0); + OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0); /** * Returns the attribute string of a property. @@ -1060,7 +1103,7 @@ OBJC_EXPORT const char *property_getName(objc_property_t property) * @note The format of the attribute string is described in Declared Properties in Objective-C Runtime Programming Guide. */ OBJC_EXPORT const char *property_getAttributes(objc_property_t property) - __OSX_AVAILABLE_STARTING(__MAC_10_5, __IPHONE_2_0); + OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0); /** * Returns an array of property attributes for a property. @@ -1071,7 +1114,7 @@ OBJC_EXPORT const char *property_getAttributes(objc_property_t property) * @return An array of property attributes; must be free'd() by the caller. */ OBJC_EXPORT objc_property_attribute_t *property_copyAttributeList(objc_property_t property, unsigned int *outCount) - __OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_4_3); + OBJC_AVAILABLE(10.7, 4.3, 9.0, 1.0); /** * Returns the value of a property attribute given the attribute name. @@ -1083,7 +1126,7 @@ OBJC_EXPORT objc_property_attribute_t *property_copyAttributeList(objc_property_ * \e property, \c nil otherwise. */ OBJC_EXPORT char *property_copyAttributeValue(objc_property_t property, const char *attributeName) - __OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_4_3); + OBJC_AVAILABLE(10.7, 4.3, 9.0, 1.0); /* Working with Protocols */ @@ -1098,7 +1141,7 @@ OBJC_EXPORT char *property_copyAttributeValue(objc_property_t property, const ch * @note This function acquires the runtime lock. */ OBJC_EXPORT Protocol *objc_getProtocol(const char *name) - __OSX_AVAILABLE_STARTING(__MAC_10_5, __IPHONE_2_0); + OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0); /** * Returns an array of all the protocols known to the runtime. @@ -1111,7 +1154,7 @@ OBJC_EXPORT Protocol *objc_getProtocol(const char *name) * @note This function acquires the runtime lock. */ OBJC_EXPORT Protocol * __unsafe_unretained *objc_copyProtocolList(unsigned int *outCount) - __OSX_AVAILABLE_STARTING(__MAC_10_5, __IPHONE_2_0); + OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0); /** * Returns a Boolean value that indicates whether one protocol conforms to another protocol. @@ -1129,7 +1172,7 @@ OBJC_EXPORT Protocol * __unsafe_unretained *objc_copyProtocolList(unsigned int * * All the protocols listed between angle brackets are considered part of the ProtocolName protocol. */ OBJC_EXPORT BOOL protocol_conformsToProtocol(Protocol *proto, Protocol *other) - __OSX_AVAILABLE_STARTING(__MAC_10_5, __IPHONE_2_0); + OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0); /** * Returns a Boolean value that indicates whether two protocols are equal. @@ -1140,7 +1183,7 @@ OBJC_EXPORT BOOL protocol_conformsToProtocol(Protocol *proto, Protocol *other) * @return \c YES if \e proto is the same as \e other, otherwise \c NO. */ OBJC_EXPORT BOOL protocol_isEqual(Protocol *proto, Protocol *other) - __OSX_AVAILABLE_STARTING(__MAC_10_5, __IPHONE_2_0); + OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0); /** * Returns the name of a protocol. @@ -1150,7 +1193,7 @@ OBJC_EXPORT BOOL protocol_isEqual(Protocol *proto, Protocol *other) * @return The name of the protocol \e p as a C string. */ OBJC_EXPORT const char *protocol_getName(Protocol *p) - __OSX_AVAILABLE_STARTING(__MAC_10_5, __IPHONE_2_0); + OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0); /** * Returns a method description structure for a specified method of a given protocol. @@ -1168,7 +1211,7 @@ OBJC_EXPORT const char *protocol_getName(Protocol *p) * @note This function recursively searches any protocols that this protocol conforms to. */ OBJC_EXPORT struct objc_method_description protocol_getMethodDescription(Protocol *p, SEL aSel, BOOL isRequiredMethod, BOOL isInstanceMethod) - __OSX_AVAILABLE_STARTING(__MAC_10_5, __IPHONE_2_0); + OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0); /** * Returns an array of method descriptions of methods meeting a given specification for a given protocol. @@ -1188,35 +1231,48 @@ OBJC_EXPORT struct objc_method_description protocol_getMethodDescription(Protoco * @note Methods in other protocols adopted by this protocol are not included. */ OBJC_EXPORT struct objc_method_description *protocol_copyMethodDescriptionList(Protocol *p, BOOL isRequiredMethod, BOOL isInstanceMethod, unsigned int *outCount) - __OSX_AVAILABLE_STARTING(__MAC_10_5, __IPHONE_2_0); + OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0); /** * Returns the specified property of a given protocol. * * @param proto A protocol. * @param name The name of a property. - * @param isRequiredProperty A Boolean value that indicates whether name is a required property. - * @param isInstanceProperty A Boolean value that indicates whether name is a required property. + * @param isRequiredProperty \c YES searches for a required property, \c NO searches for an optional property. + * @param isInstanceProperty \c YES searches for an instance property, \c NO searches for a class property. * * @return The property specified by \e name, \e isRequiredProperty, and \e isInstanceProperty for \e proto, * or \c NULL if none of \e proto's properties meets the specification. */ OBJC_EXPORT objc_property_t protocol_getProperty(Protocol *proto, const char *name, BOOL isRequiredProperty, BOOL isInstanceProperty) - __OSX_AVAILABLE_STARTING(__MAC_10_5, __IPHONE_2_0); + OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0); + +/** + * Returns an array of the required instance properties declared by a protocol. + * + * @note Identical to + * \code + * protocol_copyPropertyList2(proto, outCount, YES, YES); + * \endcode + */ +OBJC_EXPORT objc_property_t *protocol_copyPropertyList(Protocol *proto, unsigned int *outCount) + OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0); /** - * Returns an array of the properties declared by a protocol. + * Returns an array of properties declared by a protocol. * * @param proto A protocol. * @param outCount Upon return, contains the number of elements in the returned array. + * @param isRequiredProperty \c YES returns required properties, \c NO returns optional properties. + * @param isInstanceProperty \c YES returns instance properties, \c NO returns class properties. * * @return A C array of pointers of type \c objc_property_t describing the properties declared by \e proto. * Any properties declared by other protocols adopted by this protocol are not included. The array contains * \c *outCount pointers followed by a \c NULL terminator. You must free the array with \c free(). - * If the protocol declares no properties, \c NULL is returned and \c *outCount is \c 0. + * If the protocol declares no matching properties, \c NULL is returned and \c *outCount is \c 0. */ -OBJC_EXPORT objc_property_t *protocol_copyPropertyList(Protocol *proto, unsigned int *outCount) - __OSX_AVAILABLE_STARTING(__MAC_10_5, __IPHONE_2_0); +OBJC_EXPORT objc_property_t *protocol_copyPropertyList2(Protocol *proto, unsigned int *outCount, BOOL isRequiredProperty, BOOL isInstanceProperty) + OBJC_AVAILABLE(10.12, 10.0, 10.0, 3.0); /** * Returns an array of the protocols adopted by a protocol. @@ -1229,7 +1285,7 @@ OBJC_EXPORT objc_property_t *protocol_copyPropertyList(Protocol *proto, unsigned * If the protocol declares no properties, \c NULL is returned and \c *outCount is \c 0. */ OBJC_EXPORT Protocol * __unsafe_unretained *protocol_copyProtocolList(Protocol *proto, unsigned int *outCount) - __OSX_AVAILABLE_STARTING(__MAC_10_5, __IPHONE_2_0); + OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0); /** * Creates a new protocol instance that cannot be used until registered with @@ -1242,7 +1298,7 @@ OBJC_EXPORT Protocol * __unsafe_unretained *protocol_copyProtocolList(Protocol * * @note There is no dispose method for this. */ OBJC_EXPORT Protocol *objc_allocateProtocol(const char *name) - __OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_4_3); + OBJC_AVAILABLE(10.7, 4.3, 9.0, 1.0); /** * Registers a newly constructed protocol with the runtime. The protocol @@ -1251,7 +1307,7 @@ OBJC_EXPORT Protocol *objc_allocateProtocol(const char *name) * @param proto The protocol you want to register. */ OBJC_EXPORT void objc_registerProtocol(Protocol *proto) - __OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_4_3); + OBJC_AVAILABLE(10.7, 4.3, 9.0, 1.0); /** * Adds a method to a protocol. The protocol must be under construction. @@ -1263,7 +1319,7 @@ OBJC_EXPORT void objc_registerProtocol(Protocol *proto) * @param isInstanceMethod YES if the method is an instance method. */ OBJC_EXPORT void protocol_addMethodDescription(Protocol *proto, SEL name, const char *types, BOOL isRequiredMethod, BOOL isInstanceMethod) - __OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_4_3); + OBJC_AVAILABLE(10.7, 4.3, 9.0, 1.0); /** * Adds an incorporated protocol to another protocol. The protocol being @@ -1274,7 +1330,7 @@ OBJC_EXPORT void protocol_addMethodDescription(Protocol *proto, SEL name, const * @param addition The protocol you want to incorporate into \e proto, it must be registered. */ OBJC_EXPORT void protocol_addProtocol(Protocol *proto, Protocol *addition) - __OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_4_3); + OBJC_AVAILABLE(10.7, 4.3, 9.0, 1.0); /** * Adds a property to a protocol. The protocol must be under construction. @@ -1289,7 +1345,7 @@ OBJC_EXPORT void protocol_addProtocol(Protocol *proto, Protocol *addition) * not add the property to the protocol at all. */ OBJC_EXPORT void protocol_addProperty(Protocol *proto, const char *name, const objc_property_attribute_t *attributes, unsigned int attributeCount, BOOL isRequiredProperty, BOOL isInstanceProperty) - __OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_4_3); + OBJC_AVAILABLE(10.7, 4.3, 9.0, 1.0); /* Working with Libraries */ @@ -1303,7 +1359,7 @@ OBJC_EXPORT void protocol_addProperty(Protocol *proto, const char *name, const o * @return An array of C strings of names. Must be free()'d by caller. */ OBJC_EXPORT const char **objc_copyImageNames(unsigned int *outCount) - __OSX_AVAILABLE_STARTING(__MAC_10_5, __IPHONE_2_0); + OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0); /** * Returns the dynamic library name a class originated from. @@ -1313,7 +1369,7 @@ OBJC_EXPORT const char **objc_copyImageNames(unsigned int *outCount) * @return The name of the library containing this class. */ OBJC_EXPORT const char *class_getImageName(Class cls) - __OSX_AVAILABLE_STARTING(__MAC_10_5, __IPHONE_2_0); + OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0); /** * Returns the names of all the classes within a library. @@ -1325,7 +1381,7 @@ OBJC_EXPORT const char *class_getImageName(Class cls) */ OBJC_EXPORT const char **objc_copyClassNamesForImage(const char *image, unsigned int *outCount) - __OSX_AVAILABLE_STARTING(__MAC_10_5, __IPHONE_2_0); + OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0); /* Working with Selectors */ @@ -1338,7 +1394,7 @@ OBJC_EXPORT const char **objc_copyClassNamesForImage(const char *image, * @return A C string indicating the name of the selector. */ OBJC_EXPORT const char *sel_getName(SEL sel) - __OSX_AVAILABLE_STARTING(__MAC_10_0, __IPHONE_2_0); + OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0); /** * Registers a method name with the Objective-C runtime system. @@ -1353,7 +1409,7 @@ OBJC_EXPORT const char *sel_getName(SEL sel) * observed that many of the callers of this function did not check the return value for \c NULL. */ OBJC_EXPORT SEL sel_getUid(const char *str) - __OSX_AVAILABLE_STARTING(__MAC_10_0, __IPHONE_2_0); + OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0); /** * Registers a method with the Objective-C runtime system, maps the method @@ -1368,7 +1424,7 @@ OBJC_EXPORT SEL sel_getUid(const char *str) * has already been registered, this function simply returns the selector. */ OBJC_EXPORT SEL sel_registerName(const char *str) - __OSX_AVAILABLE_STARTING(__MAC_10_0, __IPHONE_2_0); + OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0); /** * Returns a Boolean value that indicates whether two selectors are equal. @@ -1376,12 +1432,12 @@ OBJC_EXPORT SEL sel_registerName(const char *str) * @param lhs The selector to compare with rhs. * @param rhs The selector to compare with lhs. * - * @return \c YES if \e rhs and \e rhs are equal, otherwise \c NO. + * @return \c YES if \e lhs and \e rhs are equal, otherwise \c NO. * * @note sel_isEqual is equivalent to ==. */ OBJC_EXPORT BOOL sel_isEqual(SEL lhs, SEL rhs) - __OSX_AVAILABLE_STARTING(__MAC_10_5, __IPHONE_2_0); + OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0); /* Objective-C Language Features */ @@ -1396,7 +1452,7 @@ OBJC_EXPORT BOOL sel_isEqual(SEL lhs, SEL rhs) * */ OBJC_EXPORT void objc_enumerationMutation(id obj) - __OSX_AVAILABLE_STARTING(__MAC_10_5, __IPHONE_2_0); + OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0); /** * Sets the current mutation handler. @@ -1404,7 +1460,7 @@ OBJC_EXPORT void objc_enumerationMutation(id obj) * @param handler Function pointer to the new mutation handler. */ OBJC_EXPORT void objc_setEnumerationMutationHandler(void (*handler)(id)) - __OSX_AVAILABLE_STARTING(__MAC_10_5, __IPHONE_2_0); + OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0); /** * Set the function to be called by objc_msgForward. @@ -1415,7 +1471,7 @@ OBJC_EXPORT void objc_setEnumerationMutationHandler(void (*handler)(id)) * @see message.h::_objc_msgForward */ OBJC_EXPORT void objc_setForwardHandler(void *fwd, void *fwd_stret) - __OSX_AVAILABLE_STARTING(__MAC_10_5, __IPHONE_2_0); + OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0); /** * Creates a pointer to a function that will call the block @@ -1430,7 +1486,7 @@ OBJC_EXPORT void objc_setForwardHandler(void *fwd, void *fwd_stret) * \c imp_removeBlock. */ OBJC_EXPORT IMP imp_implementationWithBlock(id block) - __OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_4_3); + OBJC_AVAILABLE(10.7, 4.3, 9.0, 1.0); /** * Return the block associated with an IMP that was created using @@ -1441,7 +1497,7 @@ OBJC_EXPORT IMP imp_implementationWithBlock(id block) * @return The block called by \e anImp. */ OBJC_EXPORT id imp_getBlock(IMP anImp) - __OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_4_3); + OBJC_AVAILABLE(10.7, 4.3, 9.0, 1.0); /** * Disassociates a block from an IMP that was created using @@ -1454,7 +1510,7 @@ OBJC_EXPORT id imp_getBlock(IMP anImp) * (For example, the block might not have been used to create an IMP previously). */ OBJC_EXPORT BOOL imp_removeBlock(IMP anImp) - __OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_4_3); + OBJC_AVAILABLE(10.7, 4.3, 9.0, 1.0); /** * This loads the object referenced by a weak pointer and returns it, after @@ -1467,7 +1523,7 @@ OBJC_EXPORT BOOL imp_removeBlock(IMP anImp) * @return The object pointed to by \e location, or \c nil if \e location is \c nil. */ OBJC_EXPORT id objc_loadWeak(id *location) - __OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0); + OBJC_AVAILABLE(10.7, 5.0, 9.0, 1.0); /** * This function stores a new value into a __weak variable. It would @@ -1479,7 +1535,7 @@ OBJC_EXPORT id objc_loadWeak(id *location) * @return The value stored into \e location, i.e. \e obj */ OBJC_EXPORT id objc_storeWeak(id *location, id obj) - __OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0); + OBJC_AVAILABLE(10.7, 5.0, 9.0, 1.0); /* Associative References */ @@ -1512,7 +1568,7 @@ typedef OBJC_ENUM(uintptr_t, objc_AssociationPolicy) { * @see objc_removeAssociatedObjects */ OBJC_EXPORT void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy) - __OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_3_1); + OBJC_AVAILABLE(10.6, 3.1, 9.0, 1.0); /** * Returns the value associated with a given object for a given key. @@ -1525,7 +1581,7 @@ OBJC_EXPORT void objc_setAssociatedObject(id object, const void *key, id value, * @see objc_setAssociatedObject */ OBJC_EXPORT id objc_getAssociatedObject(id object, const void *key) - __OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_3_1); + OBJC_AVAILABLE(10.6, 3.1, 9.0, 1.0); /** * Removes all associations for a given object. @@ -1542,7 +1598,7 @@ OBJC_EXPORT id objc_getAssociatedObject(id object, const void *key) * @see objc_getAssociatedObject */ OBJC_EXPORT void objc_removeAssociatedObjects(id object) - __OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_3_1); + OBJC_AVAILABLE(10.6, 3.1, 9.0, 1.0); #define _C_ID '@' @@ -1630,7 +1686,7 @@ struct objc_method_description_list { struct objc_protocol_list { struct objc_protocol_list *next; long count; - Protocol *list[1]; + __unsafe_unretained Protocol *list[1]; }; @@ -1727,14 +1783,24 @@ struct objc_method_list; /* Obsolete functions */ OBJC_EXPORT IMP class_lookupMethod(Class cls, SEL sel) - __OSX_AVAILABLE_BUT_DEPRECATED(__MAC_10_0,__MAC_10_5, __IPHONE_2_0,__IPHONE_2_0); + __OSX_DEPRECATED(10.0, 10.5, "use class_getMethodImplementation instead") + __IOS_DEPRECATED(2.0, 2.0, "use class_getMethodImplementation instead") + __TVOS_DEPRECATED(9.0, 9.0, "use class_getMethodImplementation instead") + __WATCHOS_DEPRECATED(1.0, 1.0, "use class_getMethodImplementation instead"); OBJC_EXPORT BOOL class_respondsToMethod(Class cls, SEL sel) - __OSX_AVAILABLE_BUT_DEPRECATED(__MAC_10_0,__MAC_10_5, __IPHONE_2_0,__IPHONE_2_0); + __OSX_DEPRECATED(10.0, 10.5, "use class_respondsToSelector instead") + __IOS_DEPRECATED(2.0, 2.0, "use class_respondsToSelector instead") + __TVOS_DEPRECATED(9.0, 9.0, "use class_respondsToSelector instead") + __WATCHOS_DEPRECATED(1.0, 1.0, "use class_respondsToSelector instead"); OBJC_EXPORT void _objc_flush_caches(Class cls) - __OSX_AVAILABLE_BUT_DEPRECATED(__MAC_10_0,__MAC_10_5, __IPHONE_2_0,__IPHONE_2_0); + __OSX_DEPRECATED(10.0, 10.5, "not recommended") + __IOS_DEPRECATED(2.0, 2.0, "not recommended") + __TVOS_DEPRECATED(9.0, 9.0, "not recommended") + __WATCHOS_DEPRECATED(1.0, 1.0, "not recommended"); OBJC_EXPORT id object_copyFromZone(id anObject, size_t nBytes, void *z) - __OSX_AVAILABLE_BUT_DEPRECATED(__MAC_10_0,__MAC_10_5, __IPHONE_NA,__IPHONE_NA) + __OSX_DEPRECATED(10.0, 10.5, "use object_copy instead") + __IOS_UNAVAILABLE __TVOS_UNAVAILABLE __WATCHOS_UNAVAILABLE OBJC_ARC_UNAVAILABLE; OBJC_EXPORT id object_realloc(id anObject, size_t nBytes) OBJC2_UNAVAILABLE; OBJC_EXPORT id object_reallocFromZone(id anObject, size_t nBytes, void *z) OBJC2_UNAVAILABLE; @@ -1746,7 +1812,8 @@ OBJC_EXPORT void objc_setClassHandler(int (*)(const char *)) OBJC2_UNAVAILABLE; OBJC_EXPORT void objc_setMultithreaded (BOOL flag) OBJC2_UNAVAILABLE; OBJC_EXPORT id class_createInstanceFromZone(Class, size_t idxIvars, void *z) - __OSX_AVAILABLE_BUT_DEPRECATED(__MAC_10_0,__MAC_10_5, __IPHONE_NA,__IPHONE_NA) + __OSX_DEPRECATED(10.0, 10.5, "use class_createInstance instead") + __IOS_UNAVAILABLE __TVOS_UNAVAILABLE __WATCHOS_UNAVAILABLE OBJC_ARC_UNAVAILABLE; OBJC_EXPORT void class_addMethods(Class, struct objc_method_list *) OBJC2_UNAVAILABLE; diff --git a/test/ARRBase.h b/test/ARRBase.h deleted file mode 100644 index 81b8fed..0000000 --- a/test/ARRBase.h +++ /dev/null @@ -1,16 +0,0 @@ -// -// ARRBase.h -// TestARRLayouts -// -// Created by Patrick Beard on 3/8/11. -// Copyright 2011 __MyCompanyName__. All rights reserved. -// - -#import - -@interface ARRBase : NSObject -@property long number; -@property(retain) id object; -@property void *pointer; -@property(weak) __weak id delegate; -@end diff --git a/test/ARRBase.m b/test/ARRBase.m deleted file mode 100644 index 737f1c6..0000000 --- a/test/ARRBase.m +++ /dev/null @@ -1,24 +0,0 @@ -// -// ARRBase.m -// TestARRLayouts -// -// Created by Patrick Beard on 3/8/11. -// Copyright 2011 __MyCompanyName__. All rights reserved. -// - -#import "ARRBase.h" - -#if 1 -@interface ARRBase () { -@private - long number; - id object; - void *pointer; - __weak id delegate; -} -@end -#endif - -@implementation ARRBase -@synthesize number, object, pointer, delegate; -@end diff --git a/test/ARRLayouts.m b/test/ARRLayouts.m deleted file mode 100644 index 5db5213..0000000 --- a/test/ARRLayouts.m +++ /dev/null @@ -1,99 +0,0 @@ -/* -TEST_CONFIG MEM=arc CC=clang -TEST_BUILD - $C{COMPILE_NOLINK_NOMEM} -c $DIR/MRRBase.m - $C{COMPILE_NOLINK_NOMEM} -c $DIR/MRRARR.m - $C{COMPILE_NOLINK} -c $DIR/ARRBase.m - $C{COMPILE_NOLINK} -c $DIR/ARRMRR.m - $C{COMPILE} -fobjc-arc $DIR/ARRLayouts.m -x none MRRBase.o MRRARR.o ARRBase.o ARRMRR.o -framework Foundation -o ARRLayouts.out -END -*/ - -#include "test.h" -#import -#import -#import - -#import "ARRMRR.h" -#import "MRRARR.h" - -@interface NSObject (Layouts) -+ (const char *)strongLayout; -+ (const char *)weakLayout; -@end - -void printlayout(const char *name, const uint8_t *layout) -{ - if (! getenv("VERBOSE")) return; - - testprintf("%s: ", name); - - if (!layout) { - fprintf(stderr, "NULL\n"); - return; - } - - const uint8_t *c; - for (c = layout; *c; c++) { - fprintf(stderr, "%02x ", *c); - } - - fprintf(stderr, "00\n"); -} - -@implementation NSObject (Layouts) - -+ (const char *)strongLayout { - const uint8_t *layout = class_getIvarLayout(self); - printlayout("strong", layout); - return (const char *)layout; -} - -+ (const char *)weakLayout { - const uint8_t *weakLayout = class_getWeakIvarLayout(self); - printlayout("weak", weakLayout); - return (const char *)weakLayout; -} - -+ (Ivar)instanceVariable:(const char *)name { - return class_getInstanceVariable(self, name); -} - -@end - -int main (int argc __unused, const char * argv[] __unused) { - // Under ARR, layout strings are relative to the class' own ivars. - testassert(strcmp([ARRBase strongLayout], "\x11\x20") == 0); - testassert(strcmp([ARRBase weakLayout], "\x31") == 0); - testassert([MRRBase strongLayout] == NULL); - testassert([MRRBase weakLayout] == NULL); - testassert(strcmp([ARRMRR strongLayout], "\x01") == 0); - testassert([ARRMRR weakLayout] == NULL); - testassert([MRRARR strongLayout] == NULL); - testassert([MRRARR weakLayout] == NULL); - - // now check consistency between dynamic accessors and KVC, etc. - ARRMRR *am = [ARRMRR new]; - MRRARR *ma = [MRRARR new]; - - NSString *am_description = [[NSString alloc] initWithFormat:@"%s %p", "ARRMRR", am]; - NSString *ma_description = [[NSString alloc] initWithFormat:@"%s %p", "MRRARR", ma]; - - am.number = M_PI; - object_setIvar(am, [ARRMRR instanceVariable:"object"], am_description); - testassert(CFGetRetainCount(objc_unretainedPointer(am_description)) == 1); - am.pointer = @selector(ARRMRR); - object_setIvar(am, [ARRMRR instanceVariable:"delegate"], ma); - testassert(CFGetRetainCount(objc_unretainedPointer(ma)) == 1); - - ma.number = M_E; - object_setIvar(ma, [MRRARR instanceVariable:"object"], ma_description); - testassert(CFGetRetainCount(objc_unretainedPointer(ma_description)) == 2); - ma.pointer = @selector(MRRARR); - ma.delegate = am; - object_setIvar(ma, [MRRARR instanceVariable:"delegate"], am); - testassert(CFGetRetainCount(objc_unretainedPointer(am)) == 1); - - succeed(__FILE__); - return 0; -} diff --git a/test/ARRMRR.h b/test/ARRMRR.h deleted file mode 100644 index 8029eed..0000000 --- a/test/ARRMRR.h +++ /dev/null @@ -1,13 +0,0 @@ -// -// ARRMRR.h -// TestARRLayouts -// -// Created by Patrick Beard on 3/8/11. -// Copyright 2011 __MyCompanyName__. All rights reserved. -// - -#import "MRRBase.h" - -@interface ARRMRR : MRRBase -@property(retain) id dataSource; -@end diff --git a/test/ARRMRR.m b/test/ARRMRR.m deleted file mode 100644 index ce0b7a6..0000000 --- a/test/ARRMRR.m +++ /dev/null @@ -1,11 +0,0 @@ -// -// ARRMRR.m -// - -#import "ARRMRR.h" - -@implementation ARRMRR - -@synthesize dataSource; - -@end diff --git a/test/MRRARR.h b/test/MRRARR.h deleted file mode 100644 index 275ae2f..0000000 --- a/test/MRRARR.h +++ /dev/null @@ -1,13 +0,0 @@ -// -// MRRARR.h -// TestARRLayouts -// -// Created by Patrick Beard on 3/8/11. -// Copyright 2011 __MyCompanyName__. All rights reserved. -// - -#import "ARRBase.h" - -@interface MRRARR : ARRBase -@property(retain) id dataSource; -@end diff --git a/test/MRRARR.m b/test/MRRARR.m deleted file mode 100644 index fa32a34..0000000 --- a/test/MRRARR.m +++ /dev/null @@ -1,11 +0,0 @@ -// -// MRRARR.m -// - -#import "MRRARR.h" - -@implementation MRRARR - -@synthesize dataSource; - -@end diff --git a/test/MRRBase.h b/test/MRRBase.h deleted file mode 100644 index c339f61..0000000 --- a/test/MRRBase.h +++ /dev/null @@ -1,16 +0,0 @@ -// -// MRRBase.h -// TestARRLayouts -// -// Created by Patrick Beard on 3/8/11. -// Copyright 2011 __MyCompanyName__. All rights reserved. -// - -#import - -@interface MRRBase : NSObject -@property double number; -@property(retain) id object; -@property void *pointer; -@property(weak) __weak id delegate; -@end diff --git a/test/MRRBase.m b/test/MRRBase.m deleted file mode 100644 index 5720dcc..0000000 --- a/test/MRRBase.m +++ /dev/null @@ -1,24 +0,0 @@ -// -// MRRBase.m -// TestARRLayouts -// -// Created by Patrick Beard on 3/8/11. -// Copyright 2011 __MyCompanyName__. All rights reserved. -// - -#import "MRRBase.h" - -#if 1 -@interface MRRBase () { -@private - double number; - id object; - void *pointer; - __weak id delegate; -} -@end -#endif - -@implementation MRRBase -@synthesize number, object, pointer, delegate; -@end diff --git a/test/Makefile b/test/Makefile deleted file mode 100644 index e5852e8..0000000 --- a/test/Makefile +++ /dev/null @@ -1,20 +0,0 @@ -# quick test -all: - perl test.pl $(MAKEFLAGS) - -# default-arch but otherwise comprehensive test for buildbot -buildbot: - perl test.pl $(MAKEFLAGS) MEM=mrc,arc,gc CC=clang LANGUAGE=objc,objc++ - -# comprehensive tests -mac macos macosx: - perl test.pl $(MAKEFLAGS) ARCH=x86_64,i386 MEM=mrc,arc,gc CC=clang LANGUAGE=objc,objc++ - -iphonesimulator: - perl test.pl $(MAKEFLAGS) ARCH=i386 SDK=iphonesimulator MEM=mrc,arc CC=clang LANGUAGE=objc,objc++ - -iphoneos: - perl test.pl $(MAKEFLAGS) ARCH=armv6,armv7 SDK=iphoneos MEM=mrc,arc CC=clang LANGUAGE=objc,objc++ - -clean: - @ perl test.pl clean diff --git a/test/accessors.m b/test/accessors.m deleted file mode 100644 index 9111e64..0000000 --- a/test/accessors.m +++ /dev/null @@ -1,79 +0,0 @@ -// TEST_CFLAGS -framework Foundation - -#import -#import -#import -#include "test.h" - -@interface Test : NSObject { - NSString *_value; - // _object is at the last optimized property offset - id _object __attribute__((aligned(64))); -} -@property(readonly) Class cls; -@property(copy) NSString *value; -@property(assign) id object; -@end - -typedef struct { - void *isa; - void *_value; - // _object is at the last optimized property offset - void *_object __attribute__((aligned(64))); -} TestDefs; - -@implementation Test - -// Question: why can't this code be automatically generated? - -#if !__has_feature(objc_arc) -- (void)dealloc { - self.value = nil; - self.object = nil; - [super dealloc]; -} -#endif - -- (Class)cls { return objc_getProperty(self, _cmd, 0, YES); } - -- (NSString*)value { return (NSString*) objc_getProperty(self, _cmd, offsetof(TestDefs, _value), YES); } -- (void)setValue:(NSString*)inValue { objc_setProperty(self, _cmd, offsetof(TestDefs, _value), inValue, YES, YES); } - -- (id)object { return objc_getProperty(self, _cmd, offsetof(TestDefs, _object), YES); } -- (void)setObject:(id)inObject { objc_setProperty(self, _cmd, offsetof(TestDefs, _object), inObject, YES, NO); } - -- (NSString *)description { - return [NSString stringWithFormat:@"value = %@, object = %@", self.value, self.object]; -} - -@end - -int main() { - PUSH_POOL { - - NSMutableString *value = [NSMutableString stringWithUTF8String:"test"]; - id object = [NSNumber numberWithInt:11]; - Test *t = AUTORELEASE([Test new]); - t.value = value; - [value setString:@"yuck"]; // mutate the string. - testassert(t.value != value); // must copy, since it was mutable. - testassert([t.value isEqualToString:@"test"]); - - Class testClass = [Test class]; - Class cls = t.cls; - testassert(testClass == cls); - cls = t.cls; - testassert(testClass == cls); - - t.object = object; - t.object = object; - - // NSLog(@"t.object = %@, t.value = %@", t.object, t.value); - // NSLog(@"t.object = %@, t.value = %@", t.object, t.value); // second call will optimized getters. - - } POP_POOL; - - succeed(__FILE__); - - return 0; -} diff --git a/test/accessors2.m b/test/accessors2.m deleted file mode 100644 index 3c21b9f..0000000 --- a/test/accessors2.m +++ /dev/null @@ -1,143 +0,0 @@ -// TEST_CONFIG MEM=mrc,arc - -#include "test.h" -#include -#include - -#include "testroot.i" - -@interface Base : TestRoot { - @public - id ivar; -} -@end -@implementation Base @end - -int main() -{ - SEL _cmd = @selector(foo); - Base *o = [Base new]; - ptrdiff_t offset = ivar_getOffset(class_getInstanceVariable([Base class], "ivar")); - testassert(offset == sizeof(id)); - - TestRoot *value = [TestRoot new]; - - // fixme test atomicity - - // Original setter API - - testprintf("original nonatomic retain\n"); - o->ivar = nil; - TestRootRetain = 0; - TestRootCopyWithZone = 0; - TestRootMutableCopyWithZone = 0; - objc_setProperty(o, _cmd, offset, value, NO/*atomic*/, NO/*copy*/); - testassert(TestRootRetain == 1); - testassert(TestRootCopyWithZone == 0); - testassert(TestRootMutableCopyWithZone == 0); - testassert(o->ivar == value); - - testprintf("original atomic retain\n"); - o->ivar = nil; - TestRootRetain = 0; - TestRootCopyWithZone = 0; - TestRootMutableCopyWithZone = 0; - objc_setProperty(o, _cmd, offset, value, YES/*atomic*/, NO/*copy*/); - testassert(TestRootRetain == 1); - testassert(TestRootCopyWithZone == 0); - testassert(TestRootMutableCopyWithZone == 0); - testassert(o->ivar == value); - - testprintf("original nonatomic copy\n"); - o->ivar = nil; - TestRootRetain = 0; - TestRootCopyWithZone = 0; - TestRootMutableCopyWithZone = 0; - objc_setProperty(o, _cmd, offset, value, NO/*atomic*/, YES/*copy*/); - testassert(TestRootRetain == 0); - testassert(TestRootCopyWithZone == 1); - testassert(TestRootMutableCopyWithZone == 0); - testassert(o->ivar && o->ivar != value); - - testprintf("original atomic copy\n"); - o->ivar = nil; - TestRootRetain = 0; - TestRootCopyWithZone = 0; - TestRootMutableCopyWithZone = 0; - objc_setProperty(o, _cmd, offset, value, YES/*atomic*/, YES/*copy*/); - testassert(TestRootRetain == 0); - testassert(TestRootCopyWithZone == 1); - testassert(TestRootMutableCopyWithZone == 0); - testassert(o->ivar && o->ivar != value); - - testprintf("original nonatomic mutablecopy\n"); - o->ivar = nil; - TestRootRetain = 0; - TestRootCopyWithZone = 0; - TestRootMutableCopyWithZone = 0; - objc_setProperty(o, _cmd, offset, value, NO/*atomic*/, 2/*copy*/); - testassert(TestRootRetain == 0); - testassert(TestRootCopyWithZone == 0); - testassert(TestRootMutableCopyWithZone == 1); - testassert(o->ivar && o->ivar != value); - - testprintf("original atomic mutablecopy\n"); - o->ivar = nil; - TestRootRetain = 0; - TestRootCopyWithZone = 0; - TestRootMutableCopyWithZone = 0; - objc_setProperty(o, _cmd, offset, value, YES/*atomic*/, 2/*copy*/); - testassert(TestRootRetain == 0); - testassert(TestRootCopyWithZone == 0); - testassert(TestRootMutableCopyWithZone == 1); - testassert(o->ivar && o->ivar != value); - - - // Optimized setter API - - testprintf("optimized nonatomic retain\n"); - o->ivar = nil; - TestRootRetain = 0; - TestRootCopyWithZone = 0; - TestRootMutableCopyWithZone = 0; - objc_setProperty_nonatomic(o, _cmd, value, offset); - testassert(TestRootRetain == 1); - testassert(TestRootCopyWithZone == 0); - testassert(TestRootMutableCopyWithZone == 0); - testassert(o->ivar == value); - - testprintf("optimized atomic retain\n"); - o->ivar = nil; - TestRootRetain = 0; - TestRootCopyWithZone = 0; - TestRootMutableCopyWithZone = 0; - objc_setProperty_atomic(o, _cmd, value, offset); - testassert(TestRootRetain == 1); - testassert(TestRootCopyWithZone == 0); - testassert(TestRootMutableCopyWithZone == 0); - testassert(o->ivar == value); - - testprintf("optimized nonatomic copy\n"); - o->ivar = nil; - TestRootRetain = 0; - TestRootCopyWithZone = 0; - TestRootMutableCopyWithZone = 0; - objc_setProperty_nonatomic_copy(o, _cmd, value, offset); - testassert(TestRootRetain == 0); - testassert(TestRootCopyWithZone == 1); - testassert(TestRootMutableCopyWithZone == 0); - testassert(o->ivar && o->ivar != value); - - testprintf("optimized atomic copy\n"); - o->ivar = nil; - TestRootRetain = 0; - TestRootCopyWithZone = 0; - TestRootMutableCopyWithZone = 0; - objc_setProperty_atomic_copy(o, _cmd, value, offset); - testassert(TestRootRetain == 0); - testassert(TestRootCopyWithZone == 1); - testassert(TestRootMutableCopyWithZone == 0); - testassert(o->ivar && o->ivar != value); - - succeed(__FILE__); -} diff --git a/test/addMethod.m b/test/addMethod.m deleted file mode 100644 index 0b1f96c..0000000 --- a/test/addMethod.m +++ /dev/null @@ -1,104 +0,0 @@ -// TEST_CONFIG - -#include "test.h" -#include "testroot.i" -#include - -@interface Super : TestRoot @end -@implementation Super --(int)superMethod { return 0; } --(int)bothMethod { return 0; } -@end - -@interface Sub : Super @end -@implementation Sub --(int)subMethod { return 0; } --(int)bothMethod { return 0; } -@end - -@interface Sub2 : Super @end -@implementation Sub2 --(int)subMethod { return 0; } --(int)bothMethod { return 0; } -@end - - -id fn(id self __attribute__((unused)), SEL cmd __attribute__((unused)), ...) { return nil; } - -int main() -{ - IMP superMethodFromSuper = class_getMethodImplementation([Super class], @selector(superMethod)); - IMP bothMethodFromSuper = class_getMethodImplementation([Super class], @selector(bothMethod)); - IMP subMethodFromSub = class_getMethodImplementation([Sub class], @selector(subMethod)); - IMP bothMethodFromSub = class_getMethodImplementation([Sub class], @selector(bothMethod)); - IMP subMethodFromSub2 = class_getMethodImplementation([Sub2 class], @selector(subMethod)); - IMP bothMethodFromSub2 = class_getMethodImplementation([Sub2 class], @selector(bothMethod)); - - testassert(superMethodFromSuper); - testassert(bothMethodFromSuper); - testassert(subMethodFromSub); - testassert(bothMethodFromSub); - testassert(subMethodFromSub2); - testassert(bothMethodFromSub2); - - BOOL ok; - IMP imp; - - // class_addMethod doesn't replace existing implementations - ok = class_addMethod([Super class], @selector(superMethod), (IMP)fn, NULL); - testassert(!ok); - testassert(class_getMethodImplementation([Super class], @selector(superMethod)) == superMethodFromSuper); - - // class_addMethod does override superclass implementations - ok = class_addMethod([Sub class], @selector(superMethod), (IMP)fn, NULL); - testassert(ok); - testassert(class_getMethodImplementation([Sub class], @selector(superMethod)) == (IMP)fn); - - // class_addMethod does add root implementations - ok = class_addMethod([Super class], @selector(superMethodNew2), (IMP)fn, NULL); - testassert(ok); - testassert(class_getMethodImplementation([Super class], @selector(superMethodNew2)) == (IMP)fn); - testassert(class_getMethodImplementation([Sub class], @selector(superMethodNew2)) == (IMP)fn); - - - // class_replaceMethod does add new implementations, - // returning NULL if super has an implementation - imp = class_replaceMethod([Sub2 class], @selector(superMethod), (IMP)fn, NULL); - testassert(imp == NULL); - testassert(class_getMethodImplementation([Sub2 class], @selector(superMethod)) == (IMP)fn); - - // class_replaceMethod does add new implementations, - // returning NULL if super has no implementation - imp = class_replaceMethod([Sub2 class], @selector(subMethodNew), (IMP)fn, NULL); - testassert(imp == NULL); - testassert(class_getMethodImplementation([Sub2 class], @selector(subMethodNew)) == (IMP)fn); - - // class_replaceMethod does add new implemetations - // returning NULL if there is no super class - imp = class_replaceMethod([Super class], @selector(superMethodNew), (IMP)fn, NULL); - testassert(imp == NULL); - testassert(class_getMethodImplementation([Super class], @selector(superMethodNew)) == (IMP)fn); - - - // class_replaceMethod does replace existing implementations, - // returning existing implementation (regardless of super) - imp = class_replaceMethod([Sub2 class], @selector(subMethod), (IMP)fn, NULL); - testassert(imp == subMethodFromSub2); - testassert(class_getMethodImplementation([Sub2 class], @selector(subMethod)) == (IMP)fn); - - // class_replaceMethod does replace existing implemetations, - // returning existing implementation (regardless of super) - imp = class_replaceMethod([Sub2 class], @selector(bothMethod), (IMP)fn, NULL); - testassert(imp == bothMethodFromSub2); - testassert(class_getMethodImplementation([Sub2 class], @selector(bothMethod)) == (IMP)fn); - - // class_replaceMethod does replace existing implemetations, - // returning existing implementation (regardless of super) - imp = class_replaceMethod([Super class], @selector(superMethod), (IMP)fn, NULL); - testassert(imp == superMethodFromSuper); - testassert(class_getMethodImplementation([Super class], @selector(superMethod)) == (IMP)fn); - - // fixme actually try calling them - - succeed(__FILE__); -} diff --git a/test/addProtocol.m b/test/addProtocol.m deleted file mode 100644 index e95bc9a..0000000 --- a/test/addProtocol.m +++ /dev/null @@ -1,211 +0,0 @@ -/* -TEST_RUN_OUTPUT -objc\[\d+\]: protocol_addProtocol: added protocol 'EmptyProto' is still under construction! -objc\[\d+\]: objc_registerProtocol: protocol 'Proto1' was already registered! -objc\[\d+\]: protocol_addProtocol: modified protocol 'Proto1' is not under construction! -objc\[\d+\]: protocol_addMethodDescription: protocol 'Proto1' is not under construction! -objc\[\d+\]: objc_registerProtocol: protocol 'SuperProto' was already registered! -objc\[\d+\]: protocol_addProtocol: modified protocol 'SuperProto' is not under construction! -objc\[\d+\]: protocol_addMethodDescription: protocol 'SuperProto' is not under construction! -OK: addProtocol.m -END -*/ - -#include "test.h" - -#include - -@protocol SuperProto @end -@protocol SuperProto2 @end -@protocol UnrelatedProto @end - - -void Crash(id self, SEL _cmd) -{ - fail("%c[%s %s] called unexpectedly", - class_isMetaClass(object_getClass(self)) ? '+' : '-', - object_getClassName(self), sel_getName(_cmd)); -} - - -int main() -{ - Protocol *proto, *proto2; - Protocol * __unsafe_unretained *protolist; - struct objc_method_description *desclist; - objc_property_t *proplist; - unsigned int count; - - // If objc_registerProtocol() fails to preserve the retain count - // then ARC will deallocate Protocol objects too early. - class_replaceMethod(objc_getClass("Protocol"), - sel_registerName("dealloc"), (IMP)Crash, "v@:"); - class_replaceMethod(objc_getClass("__IncompleteProtocol"), - sel_registerName("dealloc"), (IMP)Crash, "v@:"); - - // make sure binary contains hard copies of these protocols - proto = @protocol(SuperProto); - proto = @protocol(SuperProto2); - - // Adding a protocol - - char *name = strdup("Proto1"); - proto = objc_allocateProtocol(name); - testassert(proto); - testassert(!objc_getProtocol(name)); - - protocol_addProtocol(proto, @protocol(SuperProto)); - protocol_addProtocol(proto, @protocol(SuperProto2)); - // no inheritance cycles - proto2 = objc_allocateProtocol("EmptyProto"); - protocol_addProtocol(proto, proto2); // fails - objc_registerProtocol(proto2); - protocol_addProtocol(proto, proto2); // succeeds - - char *types = strdup("@:"); - protocol_addMethodDescription(proto, @selector(ReqInst0), types, YES, YES); - protocol_addMethodDescription(proto, @selector(ReqInst1), types, YES, YES); - protocol_addMethodDescription(proto, @selector(ReqInst2), types, YES, YES); - protocol_addMethodDescription(proto, @selector(ReqInst3), types, YES, YES); - - protocol_addMethodDescription(proto, @selector(ReqClas0), types, YES, NO); - protocol_addMethodDescription(proto, @selector(ReqClas1), types, YES, NO); - protocol_addMethodDescription(proto, @selector(ReqClas2), types, YES, NO); - protocol_addMethodDescription(proto, @selector(ReqClas3), types, YES, NO); - - protocol_addMethodDescription(proto, @selector(OptInst0), types, NO, YES); - protocol_addMethodDescription(proto, @selector(OptInst1), types, NO, YES); - protocol_addMethodDescription(proto, @selector(OptInst2), types, NO, YES); - protocol_addMethodDescription(proto, @selector(OptInst3), types, NO, YES); - - protocol_addMethodDescription(proto, @selector(OptClas0), types, NO, NO); - protocol_addMethodDescription(proto, @selector(OptClas1), types, NO, NO); - protocol_addMethodDescription(proto, @selector(OptClas2), types, NO, NO); - protocol_addMethodDescription(proto, @selector(OptClas3), types, NO, NO); - - char *name0 = strdup("ReqInst0"); - char *name1 = strdup("ReqInst1"); - char *name2 = strdup("ReqInst2"); - char *name3 = strdup("ReqInst3"); - char *attrname = strdup("T"); - char *attrvalue = strdup("i"); - objc_property_attribute_t attrs[] = {{attrname, attrvalue}}; - int attrcount = sizeof(attrs) / sizeof(attrs[0]); - protocol_addProperty(proto, name0, attrs, attrcount, YES, YES); - protocol_addProperty(proto, name1, attrs, attrcount, YES, YES); - protocol_addProperty(proto, name2, attrs, attrcount, YES, YES); - protocol_addProperty(proto, name3, attrs, attrcount, YES, YES); - - objc_registerProtocol(proto); - testassert(0 == strcmp(protocol_getName(proto), "Proto1")); - - // Use of added protocols - - testassert(proto == objc_getProtocol("Proto1")); - strcpy(name, "XXXXXX"); // name is copied - testassert(0 == strcmp(protocol_getName(proto), "Proto1")); - - protolist = protocol_copyProtocolList(proto, &count); - testassert(protolist); - testassert(count == 3); - // note this order is not required - testassert(protolist[0] == @protocol(SuperProto) && - protolist[1] == @protocol(SuperProto2) && - protolist[2] == proto2); - free(protolist); - - testassert(protocol_conformsToProtocol(proto, proto2)); - testassert(protocol_conformsToProtocol(proto, @protocol(SuperProto))); - testassert(!protocol_conformsToProtocol(proto, @protocol(UnrelatedProto))); - - strcpy(types, "XX"); // types is copied - desclist = protocol_copyMethodDescriptionList(proto, YES, YES, &count); - testassert(desclist && count == 4); - testprintf("%p %p\n", desclist[0].name, @selector(ReqInst0)); - // testassert(desclist[0].name == @selector(ReqInst0)); - testassert(0 == strcmp(desclist[0].types, "@:")); - free(desclist); - desclist = protocol_copyMethodDescriptionList(proto, YES, NO, &count); - testassert(desclist && count == 4); - testassert(desclist[1].name == @selector(ReqClas1)); - testassert(0 == strcmp(desclist[1].types, "@:")); - free(desclist); - desclist = protocol_copyMethodDescriptionList(proto, NO, YES, &count); - testassert(desclist && count == 4); - testassert(desclist[2].name == @selector(OptInst2)); - testassert(0 == strcmp(desclist[2].types, "@:")); - free(desclist); - desclist = protocol_copyMethodDescriptionList(proto, NO, NO, &count); - testassert(desclist && count == 4); - testassert(desclist[3].name == @selector(OptClas3)); - testassert(0 == strcmp(desclist[3].types, "@:")); - free(desclist); - - strcpy(name0, "XXXXXXXX"); // name is copied - strcpy(name1, "XXXXXXXX"); // name is copied - strcpy(name2, "XXXXXXXX"); // name is copied - strcpy(name3, "XXXXXXXX"); // name is copied - strcpy(attrname, "X"); // description is copied - strcpy(attrvalue, "X"); // description is copied - memset(attrs, 'X', sizeof(attrs)); // description is copied - proplist = protocol_copyPropertyList(proto, &count); - testassert(proplist); - testassert(count == 4); - // note this order is not required - testassert(0 == strcmp(property_getName(proplist[0]), "ReqInst0")); - testassert(0 == strcmp(property_getName(proplist[1]), "ReqInst1")); - testassert(0 == strcmp(property_getName(proplist[2]), "ReqInst2")); - testassert(0 == strcmp(property_getName(proplist[3]), "ReqInst3")); - testassert(0 == strcmp(property_getAttributes(proplist[0]), "Ti")); - testassert(0 == strcmp(property_getAttributes(proplist[1]), "Ti")); - testassert(0 == strcmp(property_getAttributes(proplist[2]), "Ti")); - testassert(0 == strcmp(property_getAttributes(proplist[3]), "Ti")); - free(proplist); - - - testassert(proto2 == objc_getProtocol("EmptyProto")); - testassert(0 == strcmp(protocol_getName(proto2), "EmptyProto")); - - protolist = protocol_copyProtocolList(proto2, &count); - testassert(!protolist); - testassert(count == 0); - - testassert(!protocol_conformsToProtocol(proto2, proto)); - testassert(!protocol_conformsToProtocol(proto2,@protocol(SuperProto))); - testassert(!protocol_conformsToProtocol(proto2,@protocol(UnrelatedProto))); - - desclist = protocol_copyMethodDescriptionList(proto2, YES, YES, &count); - testassert(!desclist && count == 0); - desclist = protocol_copyMethodDescriptionList(proto2, YES, NO, &count); - testassert(!desclist && count == 0); - desclist = protocol_copyMethodDescriptionList(proto2, NO, YES, &count); - testassert(!desclist && count == 0); - desclist = protocol_copyMethodDescriptionList(proto2, NO, NO, &count); - testassert(!desclist && count == 0); - - // Immutability of existing protocols - - objc_registerProtocol(proto); - protocol_addProtocol(proto, @protocol(SuperProto2)); - protocol_addMethodDescription(proto, @selector(foo), "", YES, YES); - - objc_registerProtocol(@protocol(SuperProto)); - protocol_addProtocol(@protocol(SuperProto), @protocol(SuperProto2)); - protocol_addMethodDescription(@protocol(SuperProto), @selector(foo), "", YES, YES); - - // No duplicates - - proto = objc_allocateProtocol("SuperProto"); - testassert(!proto); - proto = objc_allocateProtocol("Proto1"); - testassert(!proto); - - // NULL protocols ignored - - protocol_addProtocol((Protocol *)objc_unretainedObject((void*)1), NULL); - protocol_addProtocol(NULL, (Protocol *)objc_unretainedObject((void*)1)); - protocol_addProtocol(NULL, NULL); - protocol_addMethodDescription(NULL, @selector(foo), "", YES, YES); - - succeed(__FILE__); -} diff --git a/test/applescriptobjc.m b/test/applescriptobjc.m deleted file mode 100644 index 158c301..0000000 --- a/test/applescriptobjc.m +++ /dev/null @@ -1,14 +0,0 @@ -// TEST_CONFIG OS=macosx -// TEST_CFLAGS -framework AppleScriptObjC -framework Foundation - -// Verify that trivial AppleScriptObjC apps run with GC off. - -#include -#include "test.h" - -int main() -{ - [NSBundle class]; - testassert(!objc_collectingEnabled()); - succeed(__FILE__); -} diff --git a/test/applescriptobjc2.m b/test/applescriptobjc2.m deleted file mode 100644 index 832b3cd..0000000 --- a/test/applescriptobjc2.m +++ /dev/null @@ -1,17 +0,0 @@ -// TEST_CFLAGS -framework AppleScriptObjC -framework Foundation -// TEST_CONFIG MEM=gc - -// Verify that non-trivial AppleScriptObjC apps run with GC ON. - -#include -#include "test.h" - -@interface NonTrivial : NSObject @end -@implementation NonTrivial @end - -int main() -{ - [NSBundle class]; - testassert(objc_collectingEnabled()); - succeed(__FILE__); -} diff --git a/test/arr-cast.m b/test/arr-cast.m deleted file mode 100644 index ddb6e21..0000000 --- a/test/arr-cast.m +++ /dev/null @@ -1,20 +0,0 @@ -// TEST_CONFIG - -#include "test.h" - -// objc.h redefines these calls into bridge casts. -// This test verifies that the function implementations are exported. -__BEGIN_DECLS -extern void *retainedObject(void *arg) __asm__("_objc_retainedObject"); -extern void *unretainedObject(void *arg) __asm__("_objc_unretainedObject"); -extern void *unretainedPointer(void *arg) __asm__("_objc_unretainedPointer"); -__END_DECLS - -int main() -{ - void *p = (void*)&main; - testassert(p == retainedObject(p)); - testassert(p == unretainedObject(p)); - testassert(p == unretainedPointer(p)); - succeed(__FILE__); -} diff --git a/test/arr-weak.m b/test/arr-weak.m deleted file mode 100644 index 3ea58a3..0000000 --- a/test/arr-weak.m +++ /dev/null @@ -1,329 +0,0 @@ -// TEST_CONFIG MEM=mrc -// TEST_CRASHES -/* -TEST_RUN_OUTPUT -objc\[\d+\]: Cannot form weak reference to instance \(0x[0-9a-f]+\) of class Crash. It is possible that this object was over-released, or is in the process of deallocation. -CRASHED: SIG(ILL|TRAP) -END -*/ - -#include "test.h" - -#include - -static id weak; -static id weak2; -static id weak3; -static id weak4; -static bool did_dealloc; - -static int state; - -@interface NSObject (WeakInternals) --(BOOL)_tryRetain; --(BOOL)_isDeallocating; -@end - -@interface Test : NSObject @end -@implementation Test --(void)dealloc { - testprintf("Weak storeOrNil does not crash while deallocating\n"); - weak4 = (id)0x100; // old value must not be used - id result = objc_initWeakOrNil(&weak4, self); - testassert(result == nil); - testassert(weak4 == nil); - result = objc_storeWeakOrNil(&weak4, self); - testassert(result == nil); - testassert(weak4 == nil); - - // The value returned by objc_loadWeak() is now nil, - // but the storage is not yet cleared. - testassert(weak == self); - testassert(weak2 == self); - - // objc_loadWeak() does not eagerly clear the storage. - testassert(objc_loadWeakRetained(&weak) == nil); - testassert(weak != nil); - - // dealloc clears the storage. - testprintf("Weak references clear during super dealloc\n"); - testassert(weak2 != nil); - [super dealloc]; - testassert(weak == nil); - testassert(weak2 == nil); - - did_dealloc = true; -} -@end - -@interface CustomTryRetain : Test @end -@implementation CustomTryRetain --(BOOL)_tryRetain { state++; return [super _tryRetain]; } -@end - -@interface CustomIsDeallocating : Test @end -@implementation CustomIsDeallocating --(BOOL)_isDeallocating { state++; return [super _isDeallocating]; } -@end - -@interface CustomAllowsWeakReference : Test @end -@implementation CustomAllowsWeakReference --(BOOL)allowsWeakReference { state++; return [super allowsWeakReference]; } -@end - -@interface CustomRetainWeakReference : Test @end -@implementation CustomRetainWeakReference --(BOOL)retainWeakReference { state++; return [super retainWeakReference]; } -@end - -@interface Crash : NSObject @end -@implementation Crash --(void)dealloc { - testassert(weak == self); - testassert(weak2 == self); - testassert(objc_loadWeakRetained(&weak) == nil); - testassert(objc_loadWeakRetained(&weak2) == nil); - - testprintf("Weak storeOrNil does not crash while deallocating\n"); - id result = objc_storeWeakOrNil(&weak, self); - testassert(result == nil); - - testprintf("Weak store crashes while deallocating\n"); - objc_storeWeak(&weak, self); - fail("objc_storeWeak of deallocating value should have crashed"); - [super dealloc]; -} -@end - - -void cycle(Class cls, Test *obj, Test *obj2, bool storeOrNil) -{ - testprintf("Cycling class %s\n", class_getName(cls)); - - id result; - - id (*storeWeak)(id *location, id obj); - id (*initWeak)(id *location, id obj); - if (storeOrNil) { - testprintf("Using objc_storeWeakOrNil\n"); - storeWeak = objc_storeWeakOrNil; - initWeak = objc_initWeakOrNil; - } else { - testprintf("Using objc_storeWeak\n"); - storeWeak = objc_storeWeak; - initWeak = objc_initWeak; - } - - // state counts calls to custom weak methods - // Difference test classes have different expected values. - int storeTarget; - int loadTarget; - if (cls == [Test class]) { - storeTarget = 0; - loadTarget = 0; - } - else if (cls == [CustomTryRetain class] || - cls == [CustomRetainWeakReference class]) - { - storeTarget = 0; - loadTarget = 1; - } - else if (cls == [CustomIsDeallocating class] || - cls == [CustomAllowsWeakReference class]) - { - storeTarget = 1; - loadTarget = 0; - } - else fail("wut"); - - testprintf("Weak assignment\n"); - state = 0; - result = storeWeak(&weak, obj); - testassert(state == storeTarget); - testassert(result == obj); - testassert(weak == obj); - - testprintf("Weak assignment to the same value\n"); - state = 0; - result = storeWeak(&weak, obj); - testassert(state == storeTarget); - testassert(result == obj); - testassert(weak == obj); - - testprintf("Weak load\n"); - state = 0; - result = objc_loadWeakRetained(&weak); - if (state != loadTarget) testprintf("state %d target %d\n", state, loadTarget); - testassert(state == loadTarget); - testassert(result == obj); - testassert(result == weak); - [result release]; - - testprintf("Weak assignment to different value\n"); - state = 0; - result = storeWeak(&weak, obj2); - testassert(state == storeTarget); - testassert(result == obj2); - testassert(weak == obj2); - - testprintf("Weak assignment to NULL\n"); - state = 0; - result = storeWeak(&weak, NULL); - testassert(state == 0); - testassert(result == NULL); - testassert(weak == NULL); - - testprintf("Weak re-assignment to NULL\n"); - state = 0; - result = storeWeak(&weak, NULL); - testassert(state == 0); - testassert(result == NULL); - testassert(weak == NULL); - - testprintf("Weak move\n"); - state = 0; - result = storeWeak(&weak, obj); - testassert(state == storeTarget); - testassert(result == obj); - testassert(weak == obj); - weak2 = (id)(PAGE_MAX_SIZE-16); - objc_moveWeak(&weak2, &weak); - testassert(weak == nil); - testassert(weak2 == obj); - storeWeak(&weak2, NULL); - - testprintf("Weak copy\n"); - state = 0; - result = storeWeak(&weak, obj); - testassert(state == storeTarget); - testassert(result == obj); - testassert(weak == obj); - weak2 = (id)(PAGE_MAX_SIZE-16); - objc_copyWeak(&weak2, &weak); - testassert(weak == obj); - testassert(weak2 == obj); - storeWeak(&weak, NULL); - storeWeak(&weak2, NULL); - - testprintf("Weak clear\n"); - - id obj3 = [cls new]; - - state = 0; - result = storeWeak(&weak, obj3); - testassert(state == storeTarget); - testassert(result == obj3); - testassert(weak == obj3); - - state = 0; - result = storeWeak(&weak2, obj3); - testassert(state == storeTarget); - testassert(result == obj3); - testassert(weak2 == obj3); - - did_dealloc = false; - [obj3 release]; - testassert(did_dealloc); - testassert(weak == NULL); - testassert(weak2 == NULL); - - - testprintf("Weak init and destroy\n"); - - id obj4 = [cls new]; - - state = 0; - weak = (id)0x100; // old value must not be used - result = initWeak(&weak, obj4); - testassert(state == storeTarget); - testassert(result == obj4); - testassert(weak == obj4); - - state = 0; - weak2 = (id)0x100; // old value must not be used - result = initWeak(&weak2, obj4); - testassert(state == storeTarget); - testassert(result == obj4); - testassert(weak2 == obj4); - - state = 0; - weak3 = (id)0x100; // old value must not be used - result = initWeak(&weak3, obj4); - testassert(state == storeTarget); - testassert(result == obj4); - testassert(weak3 == obj4); - - state = 0; - objc_destroyWeak(&weak3); - testassert(state == 0); - testassert(weak3 == obj4); // storage is unchanged - - did_dealloc = false; - [obj4 release]; - testassert(did_dealloc); - testassert(weak == NULL); // not destroyed earlier so cleared now - testassert(weak2 == NULL); // not destroyed earlier so cleared now - testassert(weak3 == obj4); // destroyed earlier so not cleared now - - objc_destroyWeak(&weak); - objc_destroyWeak(&weak2); -} - - -void test_class(Class cls) -{ - // prime strong and weak side tables before leak checking - Test *prime[256] = {nil}; - for (size_t i = 0; i < sizeof(prime)/sizeof(prime[0]); i++) { - objc_storeWeak(&prime[i], [cls new]); - } - - Test *obj = [cls new]; - Test *obj2 = [cls new]; - - for (int i = 0; i < 100000; i++) { - cycle(cls, obj, obj2, false); - cycle(cls, obj, obj2, true); - } - leak_mark(); - for (int i = 0; i < 100000; i++) { - cycle(cls, obj, obj2, false); - cycle(cls, obj, obj2, true); - } - // allow some slop for side table expansion - // 5120 is common with this configuration - leak_check(6000); - - // rdar://14105994 - id weaks[8]; - for (size_t i = 0; i < sizeof(weaks)/sizeof(weaks[0]); i++) { - objc_storeWeak(&weaks[i], obj); - } - for (size_t i = 0; i < sizeof(weaks)/sizeof(weaks[0]); i++) { - objc_storeWeak(&weaks[i], nil); - } -} - -int main() -{ - test_class([Test class]); - test_class([CustomTryRetain class]); - test_class([CustomIsDeallocating class]); - test_class([CustomAllowsWeakReference class]); - test_class([CustomRetainWeakReference class]); - - - id result; - - Crash *obj3 = [Crash new]; - result = objc_storeWeak(&weak, obj3); - testassert(result == obj3); - testassert(weak == obj3); - - result = objc_storeWeak(&weak2, obj3); - testassert(result == obj3); - testassert(weak2 == obj3); - - [obj3 release]; - fail("should have crashed in -[Crash dealloc]"); -} diff --git a/test/association-cf.m b/test/association-cf.m deleted file mode 100644 index 5930c5c..0000000 --- a/test/association-cf.m +++ /dev/null @@ -1,38 +0,0 @@ -// TEST_CFLAGS -framework CoreFoundation - -#include -#include - -#include "test.h" - -#if __has_feature(objc_arc) - -int main() -{ - testwarn("rdar://11368528 confused by Foundation"); - succeed(__FILE__); -} - -#else - -int main() -{ - // rdar://6164781 setAssociatedObject on pure-CF object crashes LP64 - - id obj; - id array = objc_retainedObject(CFArrayCreate(0, 0, 0, 0)); - testassert(array); - - testassert(! objc_getClass("NSCFArray")); - - objc_setAssociatedObject(array, (void*)1, array, OBJC_ASSOCIATION_ASSIGN); - - obj = objc_getAssociatedObject(array, (void*)1); - testassert(obj == array); - - RELEASE_VAR(array); - - succeed(__FILE__); -} - -#endif diff --git a/test/association.m b/test/association.m deleted file mode 100644 index 429a1c0..0000000 --- a/test/association.m +++ /dev/null @@ -1,145 +0,0 @@ -// TEST_CONFIG - -#include "test.h" -#include -#include - -static int values; -static int supers; -static int subs; - -static const char *key = "key"; - - -@interface Value : NSObject @end -@interface Super : NSObject @end -@interface Sub : NSObject @end - -@interface Super2 : NSObject @end -@interface Sub2 : NSObject @end - -@implementation Super --(id) init -{ - // rdar://8270243 don't lose associations after isa swizzling - - id value = [Value new]; - objc_setAssociatedObject(self, &key, value, OBJC_ASSOCIATION_RETAIN); - RELEASE_VAR(value); - - object_setClass(self, [Sub class]); - - return self; -} - --(void) dealloc -{ - supers++; - SUPER_DEALLOC(); -} --(void) finalize -{ - supers++; - [super finalize]; -} - -@end - -@implementation Sub --(void) dealloc -{ - subs++; - SUPER_DEALLOC(); -} --(void) finalize -{ - subs++; - [super finalize]; -} -@end - -@implementation Super2 --(id) init -{ - // rdar://9617109 don't lose associations after isa swizzling - - id value = [Value new]; - object_setClass(self, [Sub2 class]); - objc_setAssociatedObject(self, &key, value, OBJC_ASSOCIATION_RETAIN); - RELEASE_VAR(value); - object_setClass(self, [Super2 class]); - - return self; -} - --(void) dealloc -{ - supers++; - SUPER_DEALLOC(); -} --(void) finalize -{ - supers++; - [super finalize]; -} - -@end - -@implementation Sub2 --(void) dealloc -{ - subs++; - SUPER_DEALLOC(); -} --(void) finalize -{ - subs++; - [super finalize]; -} -@end - -@implementation Value --(void) dealloc { - values++; - SUPER_DEALLOC(); -} --(void) finalize { - values++; - [super finalize]; -} -@end - - -int main() -{ - testonthread(^{ - int i; - for (i = 0; i < 100; i++) { - RELEASE_VALUE([[Super alloc] init]); - } - }); - testcollect(); - - testassert(supers == 0); - testassert(subs > 0); - testassert(subs == values); - - - supers = 0; - subs = 0; - values = 0; - - testonthread(^{ - int i; - for (i = 0; i < 100; i++) { - RELEASE_VALUE([[Super2 alloc] init]); - } - }); - testcollect(); - - testassert(supers > 0); - testassert(subs == 0); - testassert(supers == values); - - succeed(__FILE__); -} diff --git a/test/atomicProperty.mm b/test/atomicProperty.mm deleted file mode 100644 index ea8bade..0000000 --- a/test/atomicProperty.mm +++ /dev/null @@ -1,41 +0,0 @@ -// TEST_CONFIG CC=clang - -#include "test.h" -#include -#include -#import - -class SerialNumber { - size_t _number; -public: - SerialNumber() : _number(42) {} - SerialNumber(const SerialNumber &number) : _number(number._number + 1) {} - SerialNumber &operator=(const SerialNumber &number) { _number = number._number + 1; return *this; } - - int operator==(const SerialNumber &number) { return _number == number._number; } - int operator!=(const SerialNumber &number) { return _number != number._number; } -}; - -@interface TestAtomicProperty : NSObject { - SerialNumber number; -} -@property(atomic) SerialNumber number; -@end - -@implementation TestAtomicProperty - -@synthesize number; - -@end - -int main() -{ - PUSH_POOL { - SerialNumber number; - TestAtomicProperty *test = [TestAtomicProperty new]; - test.number = number; - testassert(test.number != number); - } POP_POOL; - - succeed(__FILE__); -} diff --git a/test/badAltHandler.m b/test/badAltHandler.m deleted file mode 100644 index efee852..0000000 --- a/test/badAltHandler.m +++ /dev/null @@ -1,81 +0,0 @@ -// for OBJC2 mac only -/* TEST_CONFIG OS=macosx ARCH=x86_64 - TEST_CRASHES - -TEST_RUN_OUTPUT -objc\[\d+\]: objc_removeExceptionHandler\(\) called with unknown alt handler; this is probably a bug in multithreaded AppKit use. Set environment variable OBJC_DEBUG_ALT_HANDLERS=YES or break in objc_alt_handler_error\(\) to debug. -CRASHED: SIGILL -END -*/ - -#include "test.h" - -#include - -/* - rdar://6888838 - Mail installs an alt handler on one thread and deletes it on another. - This confuses the alt handler machinery, which halts the process. -*/ - -uintptr_t Token; - -void handler(id unused __unused, void *context __unused) -{ -} - -int main() -{ -#if __clang__ && __cplusplus - // alt handlers need the objc personality - // catch (id) workaround forces the objc personality - @try { - testwarn("rdar://9183014 clang uses wrong exception personality"); - } @catch (id e __unused) { - } -#endif - - @try { - // Install 4 alt handlers - uintptr_t t1, t2, t3, t4; - t1 = objc_addExceptionHandler(&handler, NULL); - t2 = objc_addExceptionHandler(&handler, NULL); - t3 = objc_addExceptionHandler(&handler, NULL); - t4 = objc_addExceptionHandler(&handler, NULL); - - // Remove 3 of them. - objc_removeExceptionHandler(t1); - objc_removeExceptionHandler(t2); - objc_removeExceptionHandler(t3); - - // Create an alt handler on another thread - // that collides with one of the removed handlers - testonthread(^{ - @try { - Token = objc_addExceptionHandler(&handler, NULL); - } @catch (...) { - } - }); - - // Incorrectly remove the other thread's handler - objc_removeExceptionHandler(Token); - // Remove the 4th handler - objc_removeExceptionHandler(t4); - - // Install 8 more handlers. - // If the other thread's handler was not ignored, - // this will fail. - objc_addExceptionHandler(&handler, NULL); - objc_addExceptionHandler(&handler, NULL); - objc_addExceptionHandler(&handler, NULL); - objc_addExceptionHandler(&handler, NULL); - objc_addExceptionHandler(&handler, NULL); - objc_addExceptionHandler(&handler, NULL); - objc_addExceptionHandler(&handler, NULL); - objc_addExceptionHandler(&handler, NULL); - } @catch (...) { - } - - // This should have crashed earlier. - fail(__FILE__); -} diff --git a/test/badCache.m b/test/badCache.m deleted file mode 100644 index 96f455b..0000000 --- a/test/badCache.m +++ /dev/null @@ -1,86 +0,0 @@ -/* -TEST_CRASHES -TEST_RUN_OUTPUT -objc1 -OK: badCache.m -OR -crash now -objc\[\d+\]: Method cache corrupted.* -objc\[\d+\]: .* -objc\[\d+\]: .* -objc\[\d+\]: .* -objc\[\d+\]: .* -objc\[\d+\]: Method cache corrupted\. -CRASHED: SIG(ILL|TRAP) -END -*/ - - -#include "test.h" - -#if !__OBJC2__ || __arm__ - -int main() -{ - fprintf(stderr, "objc1\n"); - succeed(__FILE__); -} - -#else - -#include "testroot.i" - -#if __LP64__ -typedef uint32_t mask_t; -#else -typedef uint16_t mask_t; -#endif - -struct bucket_t { - uintptr_t sel; - uintptr_t imp; -}; - -struct cache_t { - struct bucket_t *buckets; - mask_t mask; - mask_t occupied; -}; - -struct class_t { - void *isa; - void *supercls; - struct cache_t cache; -}; - -@interface Subclass : TestRoot @end -@implementation Subclass @end - -int main() -{ - Class cls = [TestRoot class]; - id obj = [cls new]; - [obj self]; - - struct cache_t *cache = &((__bridge struct class_t *)cls)->cache; - -# define COUNT 4 - struct bucket_t *buckets = calloc(sizeof(struct bucket_t), COUNT+1); - for (int i = 0; i < COUNT; i++) { - buckets[i].sel = ~0; - buckets[i].imp = ~0; - } - buckets[COUNT].sel = 1; - buckets[COUNT].imp = (uintptr_t)buckets; - - cache->mask = COUNT-1; - cache->occupied = 0; - cache->buckets = buckets; - - fprintf(stderr, "crash now\n"); - [obj self]; - - fail("should have crashed"); -} - -#endif diff --git a/test/badTagClass.m b/test/badTagClass.m deleted file mode 100644 index d7d10be..0000000 --- a/test/badTagClass.m +++ /dev/null @@ -1,42 +0,0 @@ -/* -TEST_CRASHES -TEST_RUN_OUTPUT -objc\[\d+\]: tag index 7 used for two different classes \(was 0x[0-9a-fA-F]+ NSObject, now 0x[0-9a-fA-F]+ TestRoot\) -CRASHED: SIG(ILL|TRAP) -OR -no tagged pointers -OK: badTagClass.m -END -*/ - -#include "test.h" -#include "testroot.i" - -#include -#include - -#if OBJC_HAVE_TAGGED_POINTERS - -int main() -{ - // re-registration and nil registration allowed - _objc_registerTaggedPointerClass(OBJC_TAG_7, [NSObject class]); - _objc_registerTaggedPointerClass(OBJC_TAG_7, [NSObject class]); - _objc_registerTaggedPointerClass(OBJC_TAG_7, nil); - _objc_registerTaggedPointerClass(OBJC_TAG_7, [NSObject class]); - - // colliding registration disallowed - _objc_registerTaggedPointerClass(OBJC_TAG_7, [TestRoot class]); - - fail(__FILE__); -} - -#else - -int main() -{ - fprintf(stderr, "no tagged pointers\n"); - succeed(__FILE__); -} - -#endif diff --git a/test/badTagIndex.m b/test/badTagIndex.m deleted file mode 100644 index 7a84b66..0000000 --- a/test/badTagIndex.m +++ /dev/null @@ -1,33 +0,0 @@ -/* -TEST_CRASHES -TEST_RUN_OUTPUT -objc\[\d+\]: tag index 8 is too large. -CRASHED: SIG(ILL|TRAP) -OR -no tagged pointers -OK: badTagIndex.m -END -*/ - -#include "test.h" - -#include -#include - -#if OBJC_HAVE_TAGGED_POINTERS - -int main() -{ - _objc_registerTaggedPointerClass((objc_tag_index_t)8, [NSObject class]); - fail(__FILE__); -} - -#else - -int main() -{ - fprintf(stderr, "no tagged pointers\n"); - succeed(__FILE__); -} - -#endif diff --git a/test/bigrc.m b/test/bigrc.m deleted file mode 100644 index f171c6e..0000000 --- a/test/bigrc.m +++ /dev/null @@ -1,133 +0,0 @@ -// TEST_CONFIG MEM=mrc -/* -TEST_RUN_OUTPUT -objc\[\d+\]: Deallocator object 0x[0-9a-fA-F]+ overreleased while already deallocating; break on objc_overrelease_during_dealloc_error to debug -OK: bigrc.m -OR -no overrelease enforcement -OK: bigrc.m -END - */ - -#include "test.h" -#include "testroot.i" - -static size_t LOTS; - -@interface Deallocator : TestRoot @end -@implementation Deallocator - --(void)dealloc -{ - id o = self; - size_t rc = 1; - - - testprintf("Retain a lot during dealloc\n"); - - testassert(rc == 1); - testassert([o retainCount] == rc); - do { - [o retain]; - if (rc % 0x100000 == 0) testprintf("%zx/%zx ++\n", rc, LOTS); - } while (++rc < LOTS); - - testassert([o retainCount] == rc); - - do { - [o release]; - if (rc % 0x100000 == 0) testprintf("%zx/%zx --\n", rc, LOTS); - } while (--rc > 1); - - testassert(rc == 1); - testassert([o retainCount] == rc); - - - testprintf("Overrelease during dealloc\n"); - - // Not all architectures enforce this. -#if !SUPPORT_NONPOINTER_ISA - testwarn("no overrelease enforcement"); - fprintf(stderr, "no overrelease enforcement\n"); -#endif - [o release]; - - [super dealloc]; -} - -@end - - -int main() -{ - Deallocator *o = [Deallocator new]; - size_t rc = 1; - - [o retain]; - - uintptr_t isa = *(uintptr_t *)o; - if (isa & 1) { - // Assume refcount in high bits. - LOTS = 1 << (4 + __builtin_clzll(isa)); - testprintf("LOTS %zu via cntlzw\n", LOTS); - } else { - LOTS = 0x1000000; - testprintf("LOTS %zu via guess\n", LOTS); - } - - [o release]; - - - testprintf("Retain a lot\n"); - - testassert(rc == 1); - testassert([o retainCount] == rc); - do { - [o retain]; - if (rc % 0x100000 == 0) testprintf("%zx/%zx ++\n", rc, LOTS); - } while (++rc < LOTS); - - testassert([o retainCount] == rc); - - do { - [o release]; - if (rc % 0x100000 == 0) testprintf("%zx/%zx --\n", rc, LOTS); - } while (--rc > 1); - - testassert(rc == 1); - testassert([o retainCount] == rc); - - - testprintf("tryRetain a lot\n"); - - id w; - objc_storeWeak(&w, o); - testassert(w == o); - - testassert(rc == 1); - testassert([o retainCount] == rc); - do { - objc_loadWeakRetained(&w); - if (rc % 0x100000 == 0) testprintf("%zx/%zx ++\n", rc, LOTS); - } while (++rc < LOTS); - - testassert([o retainCount] == rc); - - do { - [o release]; - if (rc % 0x100000 == 0) testprintf("%zx/%zx --\n", rc, LOTS); - } while (--rc > 1); - - testassert(rc == 1); - testassert([o retainCount] == rc); - - testprintf("dealloc\n"); - - testassert(TestRootDealloc == 0); - testassert(w != nil); - [o release]; - testassert(TestRootDealloc == 1); - testassert(w == nil); - - succeed(__FILE__); -} diff --git a/test/blocksAsImps.m b/test/blocksAsImps.m deleted file mode 100644 index 360321a..0000000 --- a/test/blocksAsImps.m +++ /dev/null @@ -1,263 +0,0 @@ -// TEST_CFLAGS -framework Foundation - -#include "test.h" -#include -#import - -#include - -#if !__has_feature(objc_arc) -# define __bridge -#endif - -#if !__clang__ - // gcc and llvm-gcc will never support struct-return marking -# define STRET_OK 0 -# define STRET_SPECIAL 0 -#elif __arm64__ - // stret supported, but is identical to non-stret -# define STRET_OK 1 -# define STRET_SPECIAL 0 -#else - // stret supported and distinct from non-stret -# define STRET_OK 1 -# define STRET_SPECIAL 1 -#endif - -typedef struct BigStruct { - uintptr_t datums[200]; -} BigStruct; - -@interface Foo:NSObject -@end -@implementation Foo -- (BigStruct) methodThatReturnsBigStruct: (BigStruct) b -{ - return b; -} -@end - -@interface Foo(bar) -- (int) boo: (int) a; -- (BigStruct) structThatIsBig: (BigStruct) b; -- (BigStruct) methodThatReturnsBigStruct: (BigStruct) b; -- (float) methodThatReturnsFloat: (float) aFloat; -@end - -// This is void* instead of id to prevent interference from ARC. -typedef uintptr_t (*FuncPtr)(void *, SEL); -typedef BigStruct (*BigStructFuncPtr)(id, SEL, BigStruct); -typedef float (*FloatFuncPtr)(id, SEL, float); - -BigStruct bigfunc(BigStruct a) { - return a; -} - -@interface Deallocator : NSObject @end -@implementation Deallocator --(void) methodThatNobodyElseCalls1 { } --(void) methodThatNobodyElseCalls2 { } -id retain_imp(Deallocator *self, SEL _cmd) { - _objc_flush_caches([Deallocator class]); - [self methodThatNobodyElseCalls1]; - struct objc_super sup = { self, [[Deallocator class] superclass] }; - return ((id(*)(struct objc_super *, SEL))objc_msgSendSuper)(&sup, _cmd); -} -void dealloc_imp(Deallocator *self, SEL _cmd) { - _objc_flush_caches([Deallocator class]); - [self methodThatNobodyElseCalls2]; - struct objc_super sup = { self, [[Deallocator class] superclass] }; - ((void(*)(struct objc_super *, SEL))objc_msgSendSuper)(&sup, _cmd); -} -+(void) load { - class_addMethod(self, sel_registerName("retain"), (IMP)retain_imp, ""); - class_addMethod(self, sel_registerName("dealloc"), (IMP)dealloc_imp, ""); -} -@end - -/* Code copied from objc-block-trampolines.m to test Block innards */ -typedef enum { - ReturnValueInRegisterArgumentMode, -#if STRET_SPECIAL - ReturnValueOnStackArgumentMode, -#endif - - ArgumentModeMax -} ArgumentMode; - -static ArgumentMode _argumentModeForBlock(id block) { - ArgumentMode aMode = ReturnValueInRegisterArgumentMode; -#if STRET_SPECIAL - if ( _Block_use_stret((__bridge void *)block) ) - aMode = ReturnValueOnStackArgumentMode; -#else - testassert(!_Block_use_stret((__bridge void *)block)); -#endif - - return aMode; -} -/* End copied code */ - -int main () { - // make sure the bits are in place - int (^registerReturn)() = ^(){ return 42; }; - ArgumentMode aMode; - - aMode = _argumentModeForBlock(registerReturn); - testassert(aMode == ReturnValueInRegisterArgumentMode); - -#if STRET_OK - BigStruct (^stackReturn)() = ^() { BigStruct k; return k; }; - aMode = _argumentModeForBlock(stackReturn); -# if STRET_SPECIAL - testassert(aMode == ReturnValueOnStackArgumentMode); -# else - testassert(aMode == ReturnValueInRegisterArgumentMode); -# endif -#endif - -#define TEST_QUANTITY 100000 - static FuncPtr funcArray[TEST_QUANTITY]; - - uintptr_t i; - for(i = 0; i 37.1212) ); - - -#if !__has_feature(objc_arc) - // Make sure imp_implementationWithBlock() and imp_removeBlock() - // don't deadlock while calling Block_copy() and Block_release() - Deallocator *dead = [[Deallocator alloc] init]; - IMP deadlockImp = imp_implementationWithBlock(^{ [dead self]; }); - [dead release]; - imp_removeBlock(deadlockImp); -#endif - - } POP_POOL; - - succeed(__FILE__); -} - diff --git a/test/cacheflush.h b/test/cacheflush.h deleted file mode 100644 index 5553dbb..0000000 --- a/test/cacheflush.h +++ /dev/null @@ -1,7 +0,0 @@ -#include -#include "test.h" - -@interface TestRoot(cat) -+(int)classMethod; --(int)instanceMethod; -@end diff --git a/test/cacheflush.m b/test/cacheflush.m deleted file mode 100644 index 85136d7..0000000 --- a/test/cacheflush.m +++ /dev/null @@ -1,61 +0,0 @@ -/* -TEST_BUILD - $C{COMPILE} $DIR/cacheflush0.m -o cacheflush0.dylib -dynamiclib - $C{COMPILE} $DIR/cacheflush2.m -x none cacheflush0.dylib -o cacheflush2.dylib -dynamiclib - $C{COMPILE} $DIR/cacheflush3.m -x none cacheflush0.dylib -o cacheflush3.dylib -dynamiclib - $C{COMPILE} $DIR/cacheflush.m -x none cacheflush0.dylib -o cacheflush.out -END -*/ - -#include "test.h" -#include -#include - -#include "cacheflush.h" - -@interface Sub : TestRoot @end -@implementation Sub @end - - -int main() -{ - TestRoot *sup = [TestRoot new]; - Sub *sub = [Sub new]; - - // Fill method cache - testassert(1 == [TestRoot classMethod]); - testassert(1 == [sup instanceMethod]); - testassert(1 == [TestRoot classMethod]); - testassert(1 == [sup instanceMethod]); - - testassert(1 == [Sub classMethod]); - testassert(1 == [sub instanceMethod]); - testassert(1 == [Sub classMethod]); - testassert(1 == [sub instanceMethod]); - - // Dynamically load a category - dlopen("cacheflush2.dylib", 0); - - // Make sure old cache results are gone - testassert(2 == [TestRoot classMethod]); - testassert(2 == [sup instanceMethod]); - - testassert(2 == [Sub classMethod]); - testassert(2 == [sub instanceMethod]); - - // Dynamically load another category - dlopen("cacheflush3.dylib", 0); - - // Make sure old cache results are gone - testassert(3 == [TestRoot classMethod]); - testassert(3 == [sup instanceMethod]); - - testassert(3 == [Sub classMethod]); - testassert(3 == [sub instanceMethod]); - - // fixme test subclasses - - // fixme test objc_flush_caches(), class_addMethod(), class_addMethods() - - succeed(__FILE__); -} diff --git a/test/cacheflush0.m b/test/cacheflush0.m deleted file mode 100644 index 8536071..0000000 --- a/test/cacheflush0.m +++ /dev/null @@ -1,7 +0,0 @@ -#include "cacheflush.h" -#include "testroot.i" - -@implementation TestRoot(cat) -+(int)classMethod { return 1; } --(int)instanceMethod { return 1; } -@end diff --git a/test/cacheflush2.m b/test/cacheflush2.m deleted file mode 100644 index 0337e3f..0000000 --- a/test/cacheflush2.m +++ /dev/null @@ -1,6 +0,0 @@ -#include "cacheflush.h" - -@implementation TestRoot (Category2) -+(int)classMethod { return 2; } --(int)instanceMethod { return 2; } -@end diff --git a/test/cacheflush3.m b/test/cacheflush3.m deleted file mode 100644 index c180e98..0000000 --- a/test/cacheflush3.m +++ /dev/null @@ -1,6 +0,0 @@ -#include "cacheflush.h" - -@implementation TestRoot (Category3) -+(int)classMethod { return 3; } --(int)instanceMethod { return 3; } -@end diff --git a/test/category.m b/test/category.m deleted file mode 100644 index 3e55a15..0000000 --- a/test/category.m +++ /dev/null @@ -1,105 +0,0 @@ -// TEST_CFLAGS -Wl,-no_objc_category_merging - -#include "test.h" -#include "testroot.i" -#include -#include - -static int state = 0; - -@interface Super : TestRoot @end -@implementation Super --(void)instancemethod { fail("-instancemethod not overridden by category"); } -+(void)method { fail("+method not overridden by category"); } -@end - -@interface Super (Category) @end -@implementation Super (Category) -+(void)method { - testprintf("in [Super(Category) method]\n"); - testassert(self == [Super class]); - testassert(state == 0); - state = 1; -} --(void)instancemethod { - testprintf("in [Super(Category) instancemethod]\n"); - testassert(object_getClass(self) == [Super class]); - testassert(state == 1); - state = 2; -} -@end - -@interface Super (PropertyCategory) -@property int i; -@end -@implementation Super (PropertyCategory) -- (int)i { return 0; } -- (void)setI:(int)value { (void)value; } -@end - -// rdar://5086110 memory smasher in category with class method and property -@interface Super (r5086110) -@property int property5086110; -@end -@implementation Super (r5086110) -+(void)method5086110 { - fail("method method5086110 called!"); -} -- (int)property5086110 { fail("property5086110 called!"); return 0; } -- (void)setProperty5086110:(int)value { fail("setProperty5086110 called!"); (void)value; } -@end - - -@interface PropertyClass : Super { - int q; -} -@property(readonly) int q; -@end -@implementation PropertyClass -@synthesize q; -@end - -@interface PropertyClass (PropertyCategory) -@property int q; -@end -@implementation PropertyClass (PropertyCategory) -@dynamic q; -@end - - -int main() -{ - // methods introduced by category - state = 0; - [Super method]; - [[Super new] instancemethod]; - testassert(state == 2); - - // property introduced by category - objc_property_t p = class_getProperty([Super class], "i"); - testassert(p); - testassert(0 == strcmp(property_getName(p), "i")); - testassert(property_getAttributes(p)); - - // methods introduced by category's property - Method m; - m = class_getInstanceMethod([Super class], @selector(i)); - testassert(m); - m = class_getInstanceMethod([Super class], @selector(setI:)); - testassert(m); - - // class's property shadowed by category's property - objc_property_t *plist = class_copyPropertyList([PropertyClass class], NULL); - testassert(plist); - testassert(plist[0]); - testassert(0 == strcmp(property_getName(plist[0]), "q")); - testassert(0 == strcmp(property_getAttributes(plist[0]), "Ti,D")); - testassert(plist[1]); - testassert(0 == strcmp(property_getName(plist[1]), "q")); - testassert(0 == strcmp(property_getAttributes(plist[1]), "Ti,R,Vq")); - testassert(!plist[2]); - free(plist); - - succeed(__FILE__); -} - diff --git a/test/cdtors.mm b/test/cdtors.mm deleted file mode 100644 index c91e738..0000000 --- a/test/cdtors.mm +++ /dev/null @@ -1,311 +0,0 @@ -// TEST_CONFIG - -#if USE_FOUNDATION -#include -#define SUPERCLASS NSObject -#define FILENAME "nscdtors.mm" -#else -#define SUPERCLASS TestRoot -#define FILENAME "cdtors.mm" -#endif - -#include "test.h" - -#include -#include "objc/objc-internal.h" -#include "testroot.i" - -static unsigned ctors1 = 0; -static unsigned dtors1 = 0; -static unsigned ctors2 = 0; -static unsigned dtors2 = 0; - -class cxx1 { - unsigned & ctors; - unsigned& dtors; - - public: - cxx1() : ctors(ctors1), dtors(dtors1) { ctors++; } - - ~cxx1() { dtors++; } -}; -class cxx2 { - unsigned& ctors; - unsigned& dtors; - - public: - cxx2() : ctors(ctors2), dtors(dtors2) { ctors++; } - - ~cxx2() { dtors++; } -}; - -/* - Class hierarchy: - TestRoot - CXXBase - NoCXXSub - CXXSub - - This has two cxx-wielding classes, and a class in between without cxx. -*/ - - -@interface CXXBase : SUPERCLASS { - cxx1 baseIvar; -} -@end -@implementation CXXBase @end - -@interface NoCXXSub : CXXBase { - int nocxxIvar; -} -@end -@implementation NoCXXSub @end - -@interface CXXSub : NoCXXSub { - cxx2 subIvar; -} -@end -@implementation CXXSub @end - - -void test_single(void) -{ - // Single allocation - - ctors1 = dtors1 = ctors2 = dtors2 = 0; - testonthread(^{ - id o = [TestRoot new]; - testassert(ctors1 == 0 && dtors1 == 0 && - ctors2 == 0 && dtors2 == 0); - testassert([o class] == [TestRoot class]); - RELEASE_VAR(o); - }); - testcollect(); - testassert(ctors1 == 0 && dtors1 == 0 && - ctors2 == 0 && dtors2 == 0); - - ctors1 = dtors1 = ctors2 = dtors2 = 0; - testonthread(^{ - id o = [CXXBase new]; - testassert(ctors1 == 1 && dtors1 == 0 && - ctors2 == 0 && dtors2 == 0); - testassert([o class] == [CXXBase class]); - RELEASE_VAR(o); - }); - testcollect(); - testassert(ctors1 == 1 && dtors1 == 1 && - ctors2 == 0 && dtors2 == 0); - - ctors1 = dtors1 = ctors2 = dtors2 = 0; - testonthread(^{ - id o = [NoCXXSub new]; - testassert(ctors1 == 1 && dtors1 == 0 && - ctors2 == 0 && dtors2 == 0); - testassert([o class] == [NoCXXSub class]); - RELEASE_VAR(o); - }); - testcollect(); - testassert(ctors1 == 1 && dtors1 == 1 && - ctors2 == 0 && dtors2 == 0); - - ctors1 = dtors1 = ctors2 = dtors2 = 0; - testonthread(^{ - id o = [CXXSub new]; - testassert(ctors1 == 1 && dtors1 == 0 && - ctors2 == 1 && dtors2 == 0); - testassert([o class] == [CXXSub class]); - RELEASE_VAR(o); - }); - testcollect(); - testassert(ctors1 == 1 && dtors1 == 1 && - ctors2 == 1 && dtors2 == 1); -} - -void test_inplace(void) -{ - __unsafe_unretained volatile id o; - char o2[64]; - - id (*objc_constructInstance_fn)(Class, void*) = (id(*)(Class, void*))dlsym(RTLD_DEFAULT, "objc_constructInstance"); - void (*objc_destructInstance_fn)(id) = (void(*)(id))dlsym(RTLD_DEFAULT, "objc_destructInstance"); - - // In-place allocation - - ctors1 = dtors1 = ctors2 = dtors2 = 0; - o = objc_constructInstance_fn([TestRoot class], o2); - testassert(ctors1 == 0 && dtors1 == 0 && - ctors2 == 0 && dtors2 == 0); - testassert([o class] == [TestRoot class]); - objc_destructInstance_fn(o), o = nil; - testcollect(); - testassert(ctors1 == 0 && dtors1 == 0 && - ctors2 == 0 && dtors2 == 0); - - ctors1 = dtors1 = ctors2 = dtors2 = 0; - o = objc_constructInstance_fn([CXXBase class], o2); - testassert(ctors1 == 1 && dtors1 == 0 && - ctors2 == 0 && dtors2 == 0); - testassert([o class] == [CXXBase class]); - objc_destructInstance_fn(o), o = nil; - testcollect(); - testassert(ctors1 == 1 && dtors1 == 1 && - ctors2 == 0 && dtors2 == 0); - - ctors1 = dtors1 = ctors2 = dtors2 = 0; - o = objc_constructInstance_fn([NoCXXSub class], o2); - testassert(ctors1 == 1 && dtors1 == 0 && - ctors2 == 0 && dtors2 == 0); - testassert([o class] == [NoCXXSub class]); - objc_destructInstance_fn(o), o = nil; - testcollect(); - testassert(ctors1 == 1 && dtors1 == 1 && - ctors2 == 0 && dtors2 == 0); - - ctors1 = dtors1 = ctors2 = dtors2 = 0; - o = objc_constructInstance_fn([CXXSub class], o2); - testassert(ctors1 == 1 && dtors1 == 0 && - ctors2 == 1 && dtors2 == 0); - testassert([o class] == [CXXSub class]); - objc_destructInstance_fn(o), o = nil; - testcollect(); - testassert(ctors1 == 1 && dtors1 == 1 && - ctors2 == 1 && dtors2 == 1); -} - - -#if __has_feature(objc_arc) - -void test_batch(void) -{ - // not converted to ARC yet - return; -} - -#else - -// Like class_createInstances(), but refuses to accept zero allocations -static unsigned -reallyCreateInstances(Class cls, size_t extraBytes, id *dst, unsigned want) -{ - unsigned count; - while (0 == (count = class_createInstances(cls, extraBytes, dst, want))) { - testprintf("class_createInstances created nothing; retrying\n"); - RELEASE_VALUE([[TestRoot alloc] init]); - } - return count; -} - -void test_batch(void) -{ - id o2[100]; - unsigned int count, i; - - // Batch allocation - - for (i = 0; i < 100; i++) { - o2[i] = (id)malloc(class_getInstanceSize([TestRoot class])); - } - for (i = 0; i < 100; i++) { - free(o2[i]); - } - - ctors1 = dtors1 = ctors2 = dtors2 = 0; - count = reallyCreateInstances([TestRoot class], 0, o2, 10); - testassert(count > 0); - testassert(ctors1 == 0 && dtors1 == 0 && - ctors2 == 0 && dtors2 == 0); - for (i = 0; i < count; i++) testassert([o2[i] class] == [TestRoot class]); - for (i = 0; i < count; i++) object_dispose(o2[i]), o2[i] = nil; - testcollect(); - testassert(ctors1 == 0 && dtors1 == 0 && - ctors2 == 0 && dtors2 == 0); - - for (i = 0; i < 100; i++) { - // prime batch allocator - free(malloc(class_getInstanceSize([TestRoot class]))); - } - - ctors1 = dtors1 = ctors2 = dtors2 = 0; - count = reallyCreateInstances([CXXBase class], 0, o2, 10); - testassert(count > 0); - testassert(ctors1 == count && dtors1 == 0 && - ctors2 == 0 && dtors2 == 0); - for (i = 0; i < count; i++) testassert([o2[i] class] == [CXXBase class]); - for (i = 0; i < count; i++) object_dispose(o2[i]), o2[i] = nil; - testcollect(); - testassert(ctors1 == count && dtors1 == count && - ctors2 == 0 && dtors2 == 0); - - for (i = 0; i < 100; i++) { - // prime batch allocator - free(malloc(class_getInstanceSize([TestRoot class]))); - } - - ctors1 = dtors1 = ctors2 = dtors2 = 0; - count = reallyCreateInstances([NoCXXSub class], 0, o2, 10); - testassert(count > 0); - testassert(ctors1 == count && dtors1 == 0 && - ctors2 == 0 && dtors2 == 0); - for (i = 0; i < count; i++) testassert([o2[i] class] == [NoCXXSub class]); - for (i = 0; i < count; i++) object_dispose(o2[i]), o2[i] = nil; - testcollect(); - testassert(ctors1 == count && dtors1 == count && - ctors2 == 0 && dtors2 == 0); - - for (i = 0; i < 100; i++) { - // prime batch allocator - free(malloc(class_getInstanceSize([TestRoot class]))); - } - - ctors1 = dtors1 = ctors2 = dtors2 = 0; - count = reallyCreateInstances([CXXSub class], 0, o2, 10); - testassert(count > 0); - testassert(ctors1 == count && dtors1 == 0 && - ctors2 == count && dtors2 == 0); - for (i = 0; i < count; i++) testassert([o2[i] class] == [CXXSub class]); - for (i = 0; i < count; i++) object_dispose(o2[i]), o2[i] = nil; - testcollect(); - testassert(ctors1 == count && dtors1 == count && - ctors2 == count && dtors2 == count); -} - -// not ARC -#endif - - -int main() -{ - if (objc_collectingEnabled()) { - testwarn("rdar://19042235 test disabled in GC because it is slow"); - succeed(FILENAME); - } - - for (int i = 0; i < 1000; i++) { - testonthread(^{ test_single(); }); - testonthread(^{ test_inplace(); }); - testonthread(^{ test_batch(); }); - } - - testonthread(^{ test_single(); }); - testonthread(^{ test_inplace(); }); - testonthread(^{ test_batch(); }); - - leak_mark(); - - for (int i = 0; i < 1000; i++) { - testonthread(^{ test_single(); }); - testonthread(^{ test_inplace(); }); - testonthread(^{ test_batch(); }); - } - - leak_check(0); - - // fixme ctor exceptions aren't caught inside .cxx_construct ? - // Single allocation, ctors fail - // In-place allocation, ctors fail - // Batch allocation, ctors fail for every object - // Batch allocation, ctors fail for every other object - - succeed(FILENAME); -} diff --git a/test/classgetclass.m b/test/classgetclass.m deleted file mode 100644 index 70686eb..0000000 --- a/test/classgetclass.m +++ /dev/null @@ -1,20 +0,0 @@ -// TEST_CONFIG - -#include "test.h" -#include -#include -#import - -@interface Foo:NSObject -@end -@implementation Foo -@end - -int main() -{ -#if __OBJC2__ - testassert(gdb_class_getClass([Foo class]) == [Foo class]); -#endif - - succeed(__FILE__); -} diff --git a/test/classname.m b/test/classname.m deleted file mode 100644 index 7234641..0000000 --- a/test/classname.m +++ /dev/null @@ -1,39 +0,0 @@ -// TEST_CONFIG - -#include "test.h" -#include "testroot.i" -#include -#include - -@interface Fake : TestRoot @end -@implementation Fake @end - -int main() -{ - TestRoot *obj = [TestRoot new]; - Class __unsafe_unretained * buf = (Class *)objc_unretainedPointer(obj); - *buf = [Fake class]; - - testassert(object_getClass(obj) == [Fake class]); - testassert(object_setClass(obj, [TestRoot class]) == [Fake class]); - testassert(object_getClass(obj) == [TestRoot class]); - testassert(object_setClass(nil, [TestRoot class]) == nil); - - testassert(malloc_size(buf) >= sizeof(id)); - bzero(buf, malloc_size(buf)); - testassert(object_setClass(obj, [TestRoot class]) == nil); - - testassert(object_getClass(obj) == [TestRoot class]); - testassert(object_getClass([TestRoot class]) == object_getClass([TestRoot class])); - testassert(object_getClass(nil) == Nil); - - testassert(0 == strcmp(object_getClassName(obj), "TestRoot")); - testassert(0 == strcmp(object_getClassName([TestRoot class]), "TestRoot")); - testassert(0 == strcmp(object_getClassName(nil), "nil")); - - testassert(0 == strcmp(class_getName([TestRoot class]), "TestRoot")); - testassert(0 == strcmp(class_getName(object_getClass([TestRoot class])), "TestRoot")); - testassert(0 == strcmp(class_getName(nil), "nil")); - - succeed(__FILE__); -} diff --git a/test/classpair.m b/test/classpair.m deleted file mode 100644 index c30733b..0000000 --- a/test/classpair.m +++ /dev/null @@ -1,376 +0,0 @@ -// TEST_CFLAGS -Wno-deprecated-declarations - -#include "test.h" - -#include "testroot.i" -#include -#include -#ifndef OBJC_NO_GC -#include -#include -#endif - -@protocol Proto --(void) instanceMethod; -+(void) classMethod; -@optional --(void) instanceMethod2; -+(void) classMethod2; -@end - -@protocol Proto2 --(void) instanceMethod; -+(void) classMethod; -@optional --(void) instanceMethod2; -+(void) classMethod_that_does_not_exist; -@end - -@protocol Proto3 --(void) instanceMethod; -+(void) classMethod_that_does_not_exist; -@optional --(void) instanceMethod2; -+(void) classMethod2; -@end - -static int super_initialize; - -@interface Super : TestRoot -@property int superProp; -@end -@implementation Super -@dynamic superProp; -+(void)initialize { super_initialize++; } - -+(void) classMethod { fail("+[Super classMethod] called"); } -+(void) classMethod2 { fail("+[Super classMethod2] called"); } --(void) instanceMethod { fail("-[Super instanceMethod] called"); } --(void) instanceMethod2 { fail("-[Super instanceMethod2] called"); } -@end - -@interface WeakSuper : Super { __weak id weakIvar; } @end -@implementation WeakSuper @end - -static int state; - -static void instance_fn(id self, SEL _cmd __attribute__((unused))) -{ - testassert(!class_isMetaClass(object_getClass(self))); - state++; -} - -static void class_fn(id self, SEL _cmd __attribute__((unused))) -{ - testassert(class_isMetaClass(object_getClass(self))); - state++; -} - -static void fail_fn(id self __attribute__((unused)), SEL _cmd) -{ - fail("fail_fn '%s' called", sel_getName(_cmd)); -} - - -static void cycle(void) -{ - Class cls; - BOOL ok; - objc_property_t prop; - char namebuf[256]; - - testassert(!objc_getClass("Sub")); - testassert([Super class]); - - // Test subclass with bells and whistles - - cls = objc_allocateClassPair([Super class], "Sub", 0); - testassert(cls); -#ifndef OBJC_NO_GC - if (objc_collectingEnabled()) { - testassert(auto_zone_size(objc_collectableZone(), objc_unretainedPointer(cls))); - testassert(auto_zone_size(objc_collectableZone(), objc_unretainedPointer(object_getClass(cls)))); - } -#endif - - class_addMethod(cls, @selector(instanceMethod), - (IMP)&instance_fn, "v@:"); - class_addMethod(object_getClass(cls), @selector(classMethod), - (IMP)&class_fn, "v@:"); - class_addMethod(object_getClass(cls), @selector(initialize), - (IMP)&class_fn, "v@:"); - class_addMethod(object_getClass(cls), @selector(load), - (IMP)&fail_fn, "v@:"); - - ok = class_addProtocol(cls, @protocol(Proto)); - testassert(ok); - ok = class_addProtocol(cls, @protocol(Proto)); - testassert(!ok); - - char attrname[2]; - char attrvalue[2]; - objc_property_attribute_t attrs[1]; - unsigned int attrcount = sizeof(attrs) / sizeof(attrs[0]); - - attrs[0].name = attrname; - attrs[0].value = attrvalue; - strcpy(attrname, "T"); - strcpy(attrvalue, "x"); - - strcpy(namebuf, "subProp"); - ok = class_addProperty(cls, namebuf, attrs, attrcount); - testassert(ok); - strcpy(namebuf, "subProp"); - ok = class_addProperty(cls, namebuf, attrs, attrcount); - testassert(!ok); - strcpy(attrvalue, "i"); - class_replaceProperty(cls, namebuf, attrs, attrcount); - strcpy(namebuf, "superProp"); - ok = class_addProperty(cls, namebuf, attrs, attrcount); - testassert(!ok); - bzero(namebuf, sizeof(namebuf)); - bzero(attrs, sizeof(attrs)); - bzero(attrname, sizeof(attrname)); - bzero(attrvalue, sizeof(attrvalue)); - -#ifndef __LP64__ -# define size 4 -# define align 2 -#else -#define size 8 -# define align 3 -#endif - - /* - { - int ivar; - id ivarid; - id* ivaridstar; - Block_t ivarblock; - } - */ - ok = class_addIvar(cls, "ivar", 4, 2, "i"); - testassert(ok); - ok = class_addIvar(cls, "ivarid", size, align, "@"); - testassert(ok); - ok = class_addIvar(cls, "ivaridstar", size, align, "^@"); - testassert(ok); - ok = class_addIvar(cls, "ivarblock", size, align, "@?"); - testassert(ok); - - ok = class_addIvar(cls, "ivar", 4, 2, "i"); - testassert(!ok); - ok = class_addIvar(object_getClass(cls), "classvar", 4, 2, "i"); - testassert(!ok); - - objc_registerClassPair(cls); - - // should call cls's +initialize, not super's - // Provoke +initialize using class_getMethodImplementation(class method) - // in order to test getNonMetaClass's slow case - super_initialize = 0; - state = 0; - class_getMethodImplementation(object_getClass(cls), @selector(class)); - testassert(super_initialize == 0); - testassert(state == 1); - - testassert(cls == [cls class]); - testassert(cls == objc_getClass("Sub")); - - testassert(!class_isMetaClass(cls)); - testassert(class_isMetaClass(object_getClass(cls))); - - testassert(class_getSuperclass(cls) == [Super class]); - testassert(class_getSuperclass(object_getClass(cls)) == object_getClass([Super class])); - - testassert(class_getInstanceSize(cls) >= sizeof(Class) + 4 + 3*size); - testassert(class_conformsToProtocol(cls, @protocol(Proto))); - - if (objc_collectingEnabled()) { - testassert(0 == strcmp((char *)class_getIvarLayout(cls), "\x01\x13")); - testassert(NULL == class_getWeakIvarLayout(cls)); - } - - class_addMethod(cls, @selector(instanceMethod2), - (IMP)&instance_fn, "v@:"); - class_addMethod(object_getClass(cls), @selector(classMethod2), - (IMP)&class_fn, "v@:"); - - ok = class_addIvar(cls, "ivar2", 4, 4, "i"); - testassert(!ok); - ok = class_addIvar(object_getClass(cls), "classvar2", 4, 4, "i"); - testassert(!ok); - - ok = class_addProtocol(cls, @protocol(Proto2)); - testassert(ok); - ok = class_addProtocol(cls, @protocol(Proto2)); - testassert(!ok); - ok = class_addProtocol(cls, @protocol(Proto)); - testassert(!ok); - - attrs[0].name = attrname; - attrs[0].value = attrvalue; - strcpy(attrname, "T"); - strcpy(attrvalue, "i"); - - strcpy(namebuf, "subProp2"); - ok = class_addProperty(cls, namebuf, attrs, attrcount); - testassert(ok); - strcpy(namebuf, "subProp"); - ok = class_addProperty(cls, namebuf, attrs, attrcount); - testassert(!ok); - strcpy(namebuf, "superProp"); - ok = class_addProperty(cls, namebuf, attrs, attrcount); - testassert(!ok); - bzero(namebuf, sizeof(namebuf)); - bzero(attrs, sizeof(attrs)); - bzero(attrname, sizeof(attrname)); - bzero(attrvalue, sizeof(attrvalue)); - - prop = class_getProperty(cls, "subProp"); - testassert(prop); - testassert(0 == strcmp(property_getName(prop), "subProp")); - testassert(0 == strcmp(property_getAttributes(prop), "Ti")); - prop = class_getProperty(cls, "subProp2"); - testassert(prop); - testassert(0 == strcmp(property_getName(prop), "subProp2")); - testassert(0 == strcmp(property_getAttributes(prop), "Ti")); - - // note: adding more methods here causes a false leak check failure - state = 0; - [cls classMethod]; - [cls classMethod2]; - testassert(state == 2); - - // put instance tests on a separate thread so they - // are reliably GC'd before class destruction - testonthread(^{ - id obj = [cls new]; - state = 0; - [obj instanceMethod]; - [obj instanceMethod2]; - testassert(state == 2); - RELEASE_VAR(obj); - }); - - // Test ivar layouts of sub-subclass - Class cls2 = objc_allocateClassPair(cls, "SubSub", 0); - testassert(cls2); - - /* - { - id ivarid2; - id idarray[16]; - void* ptrarray[16]; - char a; - char b; - char c; - } - */ - ok = class_addIvar(cls2, "ivarid2", size, align, "@"); - testassert(ok); - ok = class_addIvar(cls2, "idarray", 16*sizeof(id), align, "[16@]"); - testassert(ok); - ok = class_addIvar(cls2, "ptrarray", 16*sizeof(void*), align, "[16^]"); - testassert(ok); - ok = class_addIvar(cls2, "a", 1, 0, "c"); - testassert(ok); - ok = class_addIvar(cls2, "b", 1, 0, "c"); - testassert(ok); - ok = class_addIvar(cls2, "c", 1, 0, "c"); - testassert(ok); - - objc_registerClassPair(cls2); - - if (objc_collectingEnabled()) { - testassert(0 == strcmp((char *)class_getIvarLayout(cls2), "\x01\x1f\x05\xf0\x10")); - testassert(NULL == class_getWeakIvarLayout(cls2)); - } - - // 1-byte ivars should be well packed - testassert(ivar_getOffset(class_getInstanceVariable(cls2, "b")) == - ivar_getOffset(class_getInstanceVariable(cls2, "a")) + 1); - testassert(ivar_getOffset(class_getInstanceVariable(cls2, "c")) == - ivar_getOffset(class_getInstanceVariable(cls2, "b")) + 1); - - testcollect(); // GC: finalize "obj" above before disposing its class - objc_disposeClassPair(cls2); - objc_disposeClassPair(cls); - - testassert(!objc_getClass("Sub")); - - - // Test unmodified ivar layouts - - cls = objc_allocateClassPair([Super class], "Sub2", 0); - testassert(cls); - objc_registerClassPair(cls); - if (objc_collectingEnabled()) { - const char *l1, *l2; - l1 = (char *)class_getIvarLayout([Super class]); - l2 = (char *)class_getIvarLayout(cls); - testassert(l1 == l2 || 0 == strcmp(l1, l2)); - l1 = (char *)class_getWeakIvarLayout([Super class]); - l2 = (char *)class_getWeakIvarLayout(cls); - testassert(l1 == l2 || 0 == strcmp(l1, l2)); - } - objc_disposeClassPair(cls); - - cls = objc_allocateClassPair([WeakSuper class], "Sub3", 0); - testassert(cls); - objc_registerClassPair(cls); - if (objc_collectingEnabled()) { - const char *l1, *l2; - l1 = (char *)class_getIvarLayout([WeakSuper class]); - l2 = (char *)class_getIvarLayout(cls); - testassert(l1 == l2 || 0 == strcmp(l1, l2)); - l1 = (char *)class_getWeakIvarLayout([WeakSuper class]); - l2 = (char *)class_getWeakIvarLayout(cls); - testassert(l1 == l2 || 0 == strcmp(l1, l2)); - } - objc_disposeClassPair(cls); - - // Test layout setters - if (objc_collectingEnabled()) { - cls = objc_allocateClassPair([Super class], "Sub4", 0); - testassert(cls); - class_setIvarLayout(cls, (uint8_t *)"foo"); - class_setWeakIvarLayout(cls, NULL); - objc_registerClassPair(cls); - testassert(0 == strcmp("foo", (char *)class_getIvarLayout(cls))); - testassert(NULL == class_getWeakIvarLayout(cls)); - objc_disposeClassPair(cls); - - cls = objc_allocateClassPair([Super class], "Sub5", 0); - testassert(cls); - class_setIvarLayout(cls, NULL); - class_setWeakIvarLayout(cls, (uint8_t *)"bar"); - objc_registerClassPair(cls); - testassert(NULL == class_getIvarLayout(cls)); - testassert(0 == strcmp("bar", (char *)class_getWeakIvarLayout(cls))); - objc_disposeClassPair(cls); - } -} - -int main() -{ - int count = 1000; - - testonthread(^{ cycle(); }); - testonthread(^{ cycle(); }); - testonthread(^{ cycle(); }); - - leak_mark(); - while (count--) { - testonthread(^{ cycle(); }); - } -#if __OBJC_GC__ - testwarn("rdar://19042235 possible leaks suppressed under GC"); - leak_check(16000); -#else - leak_check(0); -#endif - - succeed(__FILE__); -} - diff --git a/test/classversion.m b/test/classversion.m deleted file mode 100644 index c34b98e..0000000 --- a/test/classversion.m +++ /dev/null @@ -1,19 +0,0 @@ -// TEST_CONFIG - -#include "test.h" -#include "testroot.i" -#include - -int main() -{ - Class cls = [TestRoot class]; - testassert(class_getVersion(cls) == 0); - testassert(class_getVersion(object_getClass(cls)) > 5); - class_setVersion(cls, 100); - testassert(class_getVersion(cls) == 100); - - testassert(class_getVersion(Nil) == 0); - class_setVersion(Nil, 100); - - succeed(__FILE__); -} diff --git a/test/concurrentcat.m b/test/concurrentcat.m deleted file mode 100644 index 0f6ef69..0000000 --- a/test/concurrentcat.m +++ /dev/null @@ -1,129 +0,0 @@ -/* -TEST_BUILD - $C{COMPILE} $DIR/concurrentcat.m -o concurrentcat.out -framework Foundation - - $C{COMPILE} -undefined dynamic_lookup -dynamiclib $DIR/concurrentcat_category.m -DTN=cc1 -o cc1.dylib - $C{COMPILE} -undefined dynamic_lookup -dynamiclib $DIR/concurrentcat_category.m -DTN=cc2 -o cc2.dylib - $C{COMPILE} -undefined dynamic_lookup -dynamiclib $DIR/concurrentcat_category.m -DTN=cc3 -o cc3.dylib - $C{COMPILE} -undefined dynamic_lookup -dynamiclib $DIR/concurrentcat_category.m -DTN=cc4 -o cc4.dylib - $C{COMPILE} -undefined dynamic_lookup -dynamiclib $DIR/concurrentcat_category.m -DTN=cc5 -o cc5.dylib - $C{COMPILE} -undefined dynamic_lookup -dynamiclib $DIR/concurrentcat_category.m -DTN=cc6 -o cc6.dylib - $C{COMPILE} -undefined dynamic_lookup -dynamiclib $DIR/concurrentcat_category.m -DTN=cc7 -o cc7.dylib - $C{COMPILE} -undefined dynamic_lookup -dynamiclib $DIR/concurrentcat_category.m -DTN=cc8 -o cc8.dylib - $C{COMPILE} -undefined dynamic_lookup -dynamiclib $DIR/concurrentcat_category.m -DTN=cc9 -o cc9.dylib - $C{COMPILE} -undefined dynamic_lookup -dynamiclib $DIR/concurrentcat_category.m -DTN=cc10 -o cc10.dylib - $C{COMPILE} -undefined dynamic_lookup -dynamiclib $DIR/concurrentcat_category.m -DTN=cc11 -o cc11.dylib - $C{COMPILE} -undefined dynamic_lookup -dynamiclib $DIR/concurrentcat_category.m -DTN=cc12 -o cc12.dylib - $C{COMPILE} -undefined dynamic_lookup -dynamiclib $DIR/concurrentcat_category.m -DTN=cc13 -o cc13.dylib - $C{COMPILE} -undefined dynamic_lookup -dynamiclib $DIR/concurrentcat_category.m -DTN=cc14 -o cc14.dylib - $C{COMPILE} -undefined dynamic_lookup -dynamiclib $DIR/concurrentcat_category.m -DTN=cc15 -o cc15.dylib -END -*/ - -#include "test.h" -#include -#include -#include -#include -#include -#include - -@interface TargetClass : NSObject -@end - -@interface TargetClass(LoadedMethods) -- (void) m0; -- (void) m1; -- (void) m2; -- (void) m3; -- (void) m4; -- (void) m5; -- (void) m6; -- (void) m7; -- (void) m8; -- (void) m9; -- (void) m10; -- (void) m11; -- (void) m12; -- (void) m13; -- (void) m14; -- (void) m15; -@end - -@implementation TargetClass -- (void) m0 { fail("shoulda been loaded!"); } -- (void) m1 { fail("shoulda been loaded!"); } -- (void) m2 { fail("shoulda been loaded!"); } -- (void) m3 { fail("shoulda been loaded!"); } -- (void) m4 { fail("shoulda been loaded!"); } -- (void) m5 { fail("shoulda been loaded!"); } -- (void) m6 { fail("shoulda been loaded!"); } -@end - -void *threadFun(void *aTargetClassName) { - const char *className = (const char *)aTargetClassName; - - objc_registerThreadWithCollector(); - - PUSH_POOL { - - Class targetSubclass = objc_getClass(className); - testassert(targetSubclass); - - id target = [targetSubclass new]; - testassert(target); - - while(1) { - [target m0]; - RETAIN(target); - [target addObserver: target forKeyPath: @"m3" options: 0 context: NULL]; - [target addObserver: target forKeyPath: @"m4" options: 0 context: NULL]; - [target m1]; - RELEASE_VALUE(target); - [target m2]; - AUTORELEASE(target); - [target m3]; - RETAIN(target); - [target removeObserver: target forKeyPath: @"m4"]; - [target addObserver: target forKeyPath: @"m5" options: 0 context: NULL]; - [target m4]; - RETAIN(target); - [target m5]; - AUTORELEASE(target); - [target m6]; - [target m7]; - [target m8]; - [target m9]; - [target m10]; - [target m11]; - [target m12]; - [target m13]; - [target m14]; - [target m15]; - [target removeObserver: target forKeyPath: @"m3"]; - [target removeObserver: target forKeyPath: @"m5"]; - } - } POP_POOL; - return NULL; -} - -int main() -{ - int i; - - void *dylib; - - for(i=1; i<16; i++) { - pthread_t t; - char dlName[100]; - sprintf(dlName, "cc%d.dylib", i); - dylib = dlopen(dlName, RTLD_LAZY); - char className[100]; - sprintf(className, "cc%d", i); - pthread_create(&t, NULL, threadFun, strdup(className)); - testassert(dylib); - } - sleep(1); - - succeed(__FILE__); -} diff --git a/test/concurrentcat_category.m b/test/concurrentcat_category.m deleted file mode 100644 index c59f663..0000000 --- a/test/concurrentcat_category.m +++ /dev/null @@ -1,70 +0,0 @@ -#include -#include -#import - -@interface TargetClass : NSObject -@end - -@interface TargetClass(LoadedMethods) -- (void) m0; -- (void) m1; -- (void) m2; -- (void) m3; -- (void) m4; -- (void) m5; -- (void) m6; -- (void) m7; -- (void) m8; -- (void) m9; -- (void) m10; -- (void) m11; -- (void) m12; -- (void) m13; -- (void) m14; -- (void) m15; -@end - -@interface TN:TargetClass -@end - -@implementation TN -- (void) m1 { [super m1]; } -- (void) m3 { [self m1]; } - -- (void) m2 -{ - [self willChangeValueForKey: @"m4"]; - [self didChangeValueForKey: @"m4"]; -} - -- (void)observeValueForKeyPath:(NSString *) keyPath - ofObject:(id)object - change:(NSDictionary *)change - context:(void *)context -{ - // suppress warning - keyPath = nil; - object = nil; - change = nil; - context = NULL; -} -@end - -@implementation TargetClass(LoadedMethods) -- (void) m0 { ; } -- (void) m1 { ; } -- (void) m2 { ; } -- (void) m3 { ; } -- (void) m4 { ; } -- (void) m5 { ; } -- (void) m6 { ; } -- (void) m7 { ; } -- (void) m8 { ; } -- (void) m9 { ; } -- (void) m10 { ; } -- (void) m11 { ; } -- (void) m12 { ; } -- (void) m13 { ; } -- (void) m14 { ; } -- (void) m15 { ; } -@end diff --git a/test/copyIvarList.m b/test/copyIvarList.m deleted file mode 100644 index b924108..0000000 --- a/test/copyIvarList.m +++ /dev/null @@ -1,115 +0,0 @@ -// TEST_CONFIG - -#include "test.h" -#include -#include -#include - -OBJC_ROOT_CLASS -@interface SuperIvars { - id isa; - int ivar1; - int ivar2; -} @end -@implementation SuperIvars @end - -@interface SubIvars : SuperIvars { - int ivar3; - int ivar4; -} @end -@implementation SubIvars @end - -OBJC_ROOT_CLASS -@interface FourIvars { - int ivar1; - int ivar2; - int ivar3; - int ivar4; -} @end -@implementation FourIvars @end - -OBJC_ROOT_CLASS -@interface NoIvars { } @end -@implementation NoIvars @end - -static int isNamed(Ivar iv, const char *name) -{ - return (0 == strcmp(name, ivar_getName(iv))); -} - -int main() -{ - Ivar *ivars; - unsigned int count; - Class cls; - - cls = objc_getClass("SubIvars"); - testassert(cls); - - count = 100; - ivars = class_copyIvarList(cls, &count); - testassert(ivars); - testassert(count == 2); - testassert(isNamed(ivars[0], "ivar3")); - testassert(isNamed(ivars[1], "ivar4")); - // ivars[] should be null-terminated - testassert(ivars[2] == NULL); - free(ivars); - - cls = objc_getClass("SuperIvars"); - testassert(cls); - - count = 100; - ivars = class_copyIvarList(cls, &count); - testassert(ivars); - testassert(count == 3); - testassert(isNamed(ivars[0], "isa")); - testassert(isNamed(ivars[1], "ivar1")); - testassert(isNamed(ivars[2], "ivar2")); - // ivars[] should be null-terminated - testassert(ivars[3] == NULL); - free(ivars); - - // Check null-termination - this ivar list block would be 16 bytes - // if it weren't for the terminator - cls = objc_getClass("FourIvars"); - testassert(cls); - - count = 100; - ivars = class_copyIvarList(cls, &count); - testassert(ivars); - testassert(count == 4); - testassert(malloc_size(ivars) >= 5 * sizeof(Ivar)); - testassert(ivars[3] != NULL); - testassert(ivars[4] == NULL); - free(ivars); - - // Check NULL count parameter - ivars = class_copyIvarList(cls, NULL); - testassert(ivars); - testassert(ivars[4] == NULL); - testassert(ivars[3] != NULL); - free(ivars); - - // Check NULL class parameter - count = 100; - ivars = class_copyIvarList(NULL, &count); - testassert(!ivars); - testassert(count == 0); - - // Check NULL class and count - ivars = class_copyIvarList(NULL, NULL); - testassert(!ivars); - - // Check class with no ivars - cls = objc_getClass("NoIvars"); - testassert(cls); - - count = 100; - ivars = class_copyIvarList(cls, &count); - testassert(!ivars); - testassert(count == 0); - - succeed(__FILE__); - return 0; -} diff --git a/test/copyMethodList.m b/test/copyMethodList.m deleted file mode 100644 index 18a0d6c..0000000 --- a/test/copyMethodList.m +++ /dev/null @@ -1,155 +0,0 @@ -// TEST_CFLAGS -Wl,-no_objc_category_merging - -#include "test.h" -#include "testroot.i" -#include -#include - -@interface SuperMethods : TestRoot { } @end -@implementation SuperMethods -+(BOOL)SuperMethodClass { return NO; } -+(BOOL)SuperMethodClass2 { return NO; } --(BOOL)SuperMethodInstance { return NO; } --(BOOL)SuperMethodInstance2 { return NO; } -@end - -@interface SubMethods : SuperMethods { } @end -@implementation SubMethods -+(BOOL)SubMethodClass { return NO; } -+(BOOL)SubMethodClass2 { return NO; } --(BOOL)SubMethodInstance { return NO; } --(BOOL)SubMethodInstance2 { return NO; } -@end - -@interface SuperMethods (Category) @end -@implementation SuperMethods (Category) -+(BOOL)SuperMethodClass { return YES; } -+(BOOL)SuperMethodClass2 { return YES; } --(BOOL)SuperMethodInstance { return YES; } --(BOOL)SuperMethodInstance2 { return YES; } -@end - -@interface SubMethods (Category) @end -@implementation SubMethods (Category) -+(BOOL)SubMethodClass { return YES; } -+(BOOL)SubMethodClass2 { return YES; } --(BOOL)SubMethodInstance { return YES; } --(BOOL)SubMethodInstance2 { return YES; } -@end - - -@interface FourMethods : TestRoot @end -@implementation FourMethods --(void)one { } --(void)two { } --(void)three { } --(void)four { } -@end - -@interface NoMethods : TestRoot @end -@implementation NoMethods @end - -static void checkReplacement(Method *list, const char *name) -{ - Method first = NULL, second = NULL; - SEL sel = sel_registerName(name); - int i; - - testassert(list); - - // Find the methods. There should be two. - for (i = 0; list[i]; i++) { - if (method_getName(list[i]) == sel) { - if (!first) first = list[i]; - else if (!second) second = list[i]; - else testassert(0); - } - } - - // Call the methods. The first should be the category (returns YES). - BOOL isCat; - isCat = ((BOOL(*)(id, Method))method_invoke)(NULL, first); - testassert(isCat); - isCat = ((BOOL(*)(id, Method))method_invoke)(NULL, second); - testassert(! isCat); -} - -int main() -{ - // Class SubMethods has not yet been touched, so runtime must attach - // the lazy categories - Method *methods; - unsigned int count; - Class cls; - - cls = objc_getClass("SubMethods"); - testassert(cls); - - testprintf("calling class_copyMethodList(SubMethods) (should be unmethodized)\n"); - - count = 100; - methods = class_copyMethodList(cls, &count); - testassert(methods); - testassert(count == 4); - // methods[] should be null-terminated - testassert(methods[4] == NULL); - // Class and category methods may be mixed in the method list thanks - // to linker / shared cache sorting, but a category's replacement should - // always precede the class's implementation. - checkReplacement(methods, "SubMethodInstance"); - checkReplacement(methods, "SubMethodInstance2"); - free(methods); - - testprintf("calling class_copyMethodList(SubMethods(meta)) (should be unmethodized)\n"); - - count = 100; - methods = class_copyMethodList(object_getClass(cls), &count); - testassert(methods); - testassert(count == 4); - // methods[] should be null-terminated - testassert(methods[4] == NULL); - // Class and category methods may be mixed in the method list thanks - // to linker / shared cache sorting, but a category's replacement should - // always precede the class's implementation. - checkReplacement(methods, "SubMethodClass"); - checkReplacement(methods, "SubMethodClass2"); - free(methods); - - // Check null-termination - this method list block would be 16 bytes - // if it weren't for the terminator - count = 100; - cls = objc_getClass("FourMethods"); - methods = class_copyMethodList(cls, &count); - testassert(methods); - testassert(count == 4); - testassert(malloc_size(methods) >= (4+1) * sizeof(Method)); - testassert(methods[3] != NULL); - testassert(methods[4] == NULL); - free(methods); - - // Check NULL count parameter - methods = class_copyMethodList(cls, NULL); - testassert(methods); - testassert(methods[4] == NULL); - testassert(methods[3] != NULL); - free(methods); - - // Check NULL class parameter - count = 100; - methods = class_copyMethodList(NULL, &count); - testassert(!methods); - testassert(count == 0); - - // Check NULL class and count - methods = class_copyMethodList(NULL, NULL); - testassert(!methods); - - // Check class with no methods - count = 100; - cls = objc_getClass("NoMethods"); - methods = class_copyMethodList(cls, &count); - testassert(!methods); - testassert(count == 0); - - succeed(__FILE__); -} diff --git a/test/copyPropertyList.m b/test/copyPropertyList.m deleted file mode 100644 index 40dbec2..0000000 --- a/test/copyPropertyList.m +++ /dev/null @@ -1,124 +0,0 @@ -// TEST_CONFIG - -#include "test.h" -#include -#include -#include - -OBJC_ROOT_CLASS -@interface SuperProps { id isa; int prop1; int prop2; } -@property int prop1; -@property int prop2; -@end -@implementation SuperProps -@synthesize prop1; -@synthesize prop2; -@end - -@interface SubProps : SuperProps { int prop3; int prop4; } -@property int prop3; -@property int prop4; -@end -@implementation SubProps -@synthesize prop3; -@synthesize prop4; -@end - -OBJC_ROOT_CLASS -@interface FourProps { int prop1; int prop2; int prop3; int prop4; } -@property int prop1; -@property int prop2; -@property int prop3; -@property int prop4; -@end -@implementation FourProps -@synthesize prop1; -@synthesize prop2; -@synthesize prop3; -@synthesize prop4; -@end - -OBJC_ROOT_CLASS -@interface NoProps @end -@implementation NoProps @end - -static int isNamed(objc_property_t p, const char *name) -{ - return (0 == strcmp(name, property_getName(p))); -} - -int main() -{ - objc_property_t *props; - unsigned int count; - Class cls; - - cls = objc_getClass("SubProps"); - testassert(cls); - - count = 100; - props = class_copyPropertyList(cls, &count); - testassert(props); - testassert(count == 2); - testassert((isNamed(props[0], "prop3") && isNamed(props[1], "prop4")) || - (isNamed(props[1], "prop3") && isNamed(props[0], "prop4"))); - // props[] should be null-terminated - testassert(props[2] == NULL); - free(props); - - cls = objc_getClass("SuperProps"); - testassert(cls); - - count = 100; - props = class_copyPropertyList(cls, &count); - testassert(props); - testassert(count == 2); - testassert((isNamed(props[0], "prop1") && isNamed(props[1], "prop2")) || - (isNamed(props[1], "prop1") && isNamed(props[0], "prop2"))); - // props[] should be null-terminated - testassert(props[2] == NULL); - free(props); - - // Check null-termination - this property list block would be 16 bytes - // if it weren't for the terminator - cls = objc_getClass("FourProps"); - testassert(cls); - - count = 100; - props = class_copyPropertyList(cls, &count); - testassert(props); - testassert(count == 4); - testassert(malloc_size(props) >= 5 * sizeof(objc_property_t)); - testassert(props[3] != NULL); - testassert(props[4] == NULL); - free(props); - - // Check NULL count parameter - props = class_copyPropertyList(cls, NULL); - testassert(props); - testassert(props[4] == NULL); - testassert(props[3] != NULL); - free(props); - - // Check NULL class parameter - count = 100; - props = class_copyPropertyList(NULL, &count); - testassert(!props); - testassert(count == 0); - - // Check NULL class and count - props = class_copyPropertyList(NULL, NULL); - testassert(!props); - - // Check class with no properties - cls = objc_getClass("NoProps"); - testassert(cls); - - count = 100; - props = class_copyPropertyList(cls, &count); - testassert(!props); - testassert(count == 0); - - succeed(__FILE__); - return 0; -} diff --git a/test/createInstance.m b/test/createInstance.m deleted file mode 100644 index e53e20a..0000000 --- a/test/createInstance.m +++ /dev/null @@ -1,63 +0,0 @@ -// TEST_CONFIG MEM=mrc,gc -// TEST_CFLAGS -Wno-deprecated-declarations - -#import -#import -#ifndef OBJC_NO_GC -#include -#else -static BOOL auto_zone_is_valid_pointer(void *a, void *b) { return a||b; } -#endif -#include "test.h" - -OBJC_ROOT_CLASS -@interface Super { @public id isa; } @end -@implementation Super -+(void) initialize { } -+(Class) class { return self; } -@end - -@interface Sub : Super { int array[128]; } @end -@implementation Sub @end - -int main() -{ - Super *s; - - s = class_createInstance([Super class], 0); - testassert(s); - testassert(object_getClass(s) == [Super class]); - testassert(malloc_size(s) >= class_getInstanceSize([Super class])); - if (objc_collectingEnabled()) testassert(auto_zone_is_valid_pointer(objc_collectableZone(), s)); - - object_dispose(s); - - s = class_createInstance([Sub class], 0); - testassert(s); - testassert(object_getClass(s) == [Sub class]); - testassert(malloc_size(s) >= class_getInstanceSize([Sub class])); - if (objc_collectingEnabled()) testassert(auto_zone_is_valid_pointer(objc_collectableZone(), s)); - - object_dispose(s); - - s = class_createInstance([Super class], 100); - testassert(s); - testassert(object_getClass(s) == [Super class]); - testassert(malloc_size(s) >= class_getInstanceSize([Super class]) + 100); - if (objc_collectingEnabled()) testassert(auto_zone_is_valid_pointer(objc_collectableZone(), s)); - - object_dispose(s); - - s = class_createInstance([Sub class], 100); - testassert(s); - testassert(object_getClass(s) == [Sub class]); - testassert(malloc_size(s) >= class_getInstanceSize([Sub class]) + 100); - if (objc_collectingEnabled()) testassert(auto_zone_is_valid_pointer(objc_collectableZone(), s)); - - object_dispose(s); - - s = class_createInstance(Nil, 0); - testassert(!s); - - succeed(__FILE__); -} diff --git a/test/customrr-cat1.m b/test/customrr-cat1.m deleted file mode 100644 index 823238d..0000000 --- a/test/customrr-cat1.m +++ /dev/null @@ -1,7 +0,0 @@ -@interface InheritingSubCat @end - -@interface InheritingSubCat (NonClobberingCategory) @end - -@implementation InheritingSubCat (NonClobberingCategory) --(id) unrelatedMethod { return self; } -@end diff --git a/test/customrr-cat2.m b/test/customrr-cat2.m deleted file mode 100644 index 55c96df..0000000 --- a/test/customrr-cat2.m +++ /dev/null @@ -1,7 +0,0 @@ -@interface InheritingSubCat @end - -@interface InheritingSubCat (ClobberingCategory) @end - -@implementation InheritingSubCat (ClobberingCategory) --(int) retainCount { return 1; } -@end diff --git a/test/customrr-nsobject-awz.m b/test/customrr-nsobject-awz.m deleted file mode 100644 index b788729..0000000 --- a/test/customrr-nsobject-awz.m +++ /dev/null @@ -1,16 +0,0 @@ -/* - -TEST_CONFIG MEM=mrc -TEST_ENV OBJC_PRINT_CUSTOM_RR=YES OBJC_PRINT_CUSTOM_AWZ=YES - -TEST_BUILD - $C{COMPILE} $DIR/customrr-nsobject.m -o customrr-nsobject-awz.out -DSWIZZLE_AWZ=1 -END - -TEST_RUN_OUTPUT -objc\[\d+\]: CUSTOM AWZ: NSObject \(meta\) -OK: customrr-nsobject-awz.out -END - -*/ - diff --git a/test/customrr-nsobject-none.m b/test/customrr-nsobject-none.m deleted file mode 100644 index fb20f24..0000000 --- a/test/customrr-nsobject-none.m +++ /dev/null @@ -1,15 +0,0 @@ -/* - -TEST_CONFIG MEM=mrc -TEST_ENV OBJC_PRINT_CUSTOM_RR=YES OBJC_PRINT_CUSTOM_AWZ=YES - -TEST_BUILD - $C{COMPILE} $DIR/customrr-nsobject.m -o customrr-nsobject-none.out -END - -TEST_RUN_OUTPUT -OK: customrr-nsobject-none.out -END - -*/ - diff --git a/test/customrr-nsobject-rr.m b/test/customrr-nsobject-rr.m deleted file mode 100644 index 859d155..0000000 --- a/test/customrr-nsobject-rr.m +++ /dev/null @@ -1,16 +0,0 @@ -/* - -TEST_CONFIG MEM=mrc -TEST_ENV OBJC_PRINT_CUSTOM_RR=YES OBJC_PRINT_CUSTOM_AWZ=YES - -TEST_BUILD - $C{COMPILE} $DIR/customrr-nsobject.m -o customrr-nsobject-rr.out -DSWIZZLE_RELEASE=1 -END - -TEST_RUN_OUTPUT -objc\[\d+\]: CUSTOM RR: NSObject -OK: customrr-nsobject-rr.out -END - -*/ - diff --git a/test/customrr-nsobject-rrawz.m b/test/customrr-nsobject-rrawz.m deleted file mode 100644 index b0e546c..0000000 --- a/test/customrr-nsobject-rrawz.m +++ /dev/null @@ -1,17 +0,0 @@ -/* - -TEST_CONFIG MEM=mrc -TEST_ENV OBJC_PRINT_CUSTOM_RR=YES OBJC_PRINT_CUSTOM_AWZ=YES - -TEST_BUILD - $C{COMPILE} $DIR/customrr-nsobject.m -o customrr-nsobject-rrawz.out -DSWIZZLE_RELEASE=1 -DSWIZZLE_AWZ=1 -END - -TEST_RUN_OUTPUT -objc\[\d+\]: CUSTOM AWZ: NSObject \(meta\) -objc\[\d+\]: CUSTOM RR: NSObject -OK: customrr-nsobject-rrawz.out -END - -*/ - diff --git a/test/customrr-nsobject.m b/test/customrr-nsobject.m deleted file mode 100644 index d055f70..0000000 --- a/test/customrr-nsobject.m +++ /dev/null @@ -1,154 +0,0 @@ -// This file is used in the customrr-nsobject-*.m tests - -#include "test.h" -#include - -#if __OBJC2__ -# define BYPASS 1 -#else -// old ABI does not implement the optimization -# define BYPASS 0 -#endif - -static int Retains; -static int Releases; -static int Autoreleases; -static int PlusInitializes; -static int Allocs; -static int AllocWithZones; - -id (*RealRetain)(id self, SEL _cmd); -void (*RealRelease)(id self, SEL _cmd); -id (*RealAutorelease)(id self, SEL _cmd); -id (*RealAlloc)(id self, SEL _cmd); -id (*RealAllocWithZone)(id self, SEL _cmd, void *zone); - -id HackRetain(id self, SEL _cmd) { Retains++; return RealRetain(self, _cmd); } -void HackRelease(id self, SEL _cmd) { Releases++; return RealRelease(self, _cmd); } -id HackAutorelease(id self, SEL _cmd) { Autoreleases++; return RealAutorelease(self, _cmd); } - -id HackAlloc(Class self, SEL _cmd) { Allocs++; return RealAlloc(self, _cmd); } -id HackAllocWithZone(Class self, SEL _cmd, void *zone) { AllocWithZones++; return RealAllocWithZone(self, _cmd, zone); } - -void HackPlusInitialize(id self __unused, SEL _cmd __unused) { PlusInitializes++; } - - -int main(int argc __unused, char **argv) -{ - Class cls = objc_getClass("NSObject"); - Method meth; - - meth = class_getClassMethod(cls, @selector(initialize)); - method_setImplementation(meth, (IMP)HackPlusInitialize); - - // We either swizzle the method normally (testing that it properly - // disables optimizations), or we hack the implementation into place - // behind objc's back (so we can see whether it got called with the - // optimizations still enabled). - - meth = class_getClassMethod(cls, @selector(allocWithZone:)); - RealAllocWithZone = (typeof(RealAllocWithZone))method_getImplementation(meth); -#if SWIZZLE_AWZ - method_setImplementation(meth, (IMP)HackAllocWithZone); -#else - ((IMP *)meth)[2] = (IMP)HackAllocWithZone; -#endif - - meth = class_getInstanceMethod(cls, @selector(release)); - RealRelease = (typeof(RealRelease))method_getImplementation(meth); -#if SWIZZLE_RELEASE - method_setImplementation(meth, (IMP)HackRelease); -#else - ((IMP *)meth)[2] = (IMP)HackRelease; -#endif - - // These other methods get hacked for counting purposes only - - meth = class_getInstanceMethod(cls, @selector(retain)); - RealRetain = (typeof(RealRetain))method_getImplementation(meth); - ((IMP *)meth)[2] = (IMP)HackRetain; - - meth = class_getInstanceMethod(cls, @selector(autorelease)); - RealAutorelease = (typeof(RealAutorelease))method_getImplementation(meth); - ((IMP *)meth)[2] = (IMP)HackAutorelease; - - meth = class_getClassMethod(cls, @selector(alloc)); - RealAlloc = (typeof(RealAlloc))method_getImplementation(meth); - ((IMP *)meth)[2] = (IMP)HackAlloc; - - // Verify that the swizzles occurred before +initialize by provoking it now - testassert(PlusInitializes == 0); - [NSObject self]; - testassert(PlusInitializes == 1); - -#if !__OBJC2__ - // hack: fool the expected output because old ABI doesn't optimize this -# if SWIZZLE_AWZ - fprintf(stderr, "objc[1234]: CUSTOM AWZ: NSObject (meta)\n"); -# endif -# if SWIZZLE_RELEASE - fprintf(stderr, "objc[1234]: CUSTOM RR: NSObject\n"); -# endif -#endif - - id obj; - - Allocs = 0; - AllocWithZones = 0; - obj = objc_alloc(cls); -#if SWIZZLE_AWZ || !BYPASS - testprintf("swizzled AWZ should be called\n"); - testassert(Allocs == 1); - testassert(AllocWithZones == 1); -#else - testprintf("unswizzled AWZ should be bypassed\n"); - testassert(Allocs == 0); - testassert(AllocWithZones == 0); -#endif - - Allocs = 0; - AllocWithZones = 0; - obj = [NSObject alloc]; -#if SWIZZLE_AWZ || !BYPASS - testprintf("swizzled AWZ should be called\n"); - testassert(Allocs == 1); - testassert(AllocWithZones == 1); -#else - testprintf("unswizzled AWZ should be bypassed\n"); - testassert(Allocs == 1); - testassert(AllocWithZones == 0); -#endif - - Retains = 0; - objc_retain(obj); -#if SWIZZLE_RELEASE || !BYPASS - testprintf("swizzled release should force retain\n"); - testassert(Retains == 1); -#else - testprintf("unswizzled release should bypass retain\n"); - testassert(Retains == 0); -#endif - - Releases = 0; - Autoreleases = 0; - PUSH_POOL { - objc_autorelease(obj); -#if SWIZZLE_RELEASE || !BYPASS - testprintf("swizzled release should force autorelease\n"); - testassert(Autoreleases == 1); -#else - testprintf("unswizzled release should bypass autorelease\n"); - testassert(Autoreleases == 0); -#endif - } POP_POOL - -#if SWIZZLE_RELEASE || !BYPASS - testprintf("swizzled release should be called\n"); - testassert(Releases == 1); -#else - testprintf("unswizzled release should be bypassed\n"); - testassert(Releases == 0); -#endif - - succeed(basename(argv[0])); -} diff --git a/test/customrr.m b/test/customrr.m deleted file mode 100644 index 7fa21ae..0000000 --- a/test/customrr.m +++ /dev/null @@ -1,904 +0,0 @@ -// These options must match customrr2.m -// TEST_CONFIG MEM=mrc -/* -TEST_BUILD - $C{COMPILE} $DIR/customrr.m -fvisibility=default -o customrr.out - $C{COMPILE} -undefined dynamic_lookup -dynamiclib $DIR/customrr-cat1.m -o customrr-cat1.dylib - $C{COMPILE} -undefined dynamic_lookup -dynamiclib $DIR/customrr-cat2.m -o customrr-cat2.dylib -END -*/ - - -#include "test.h" -#include -#include - -#if !__OBJC2__ - -// pacify exported symbols list -OBJC_ROOT_CLASS -@interface InheritingSubCat @end -@implementation InheritingSubCat @end - -int main(int argc __unused, char **argv) -{ - succeed(basename(argv[0])); -} - -#else - -static int Retains; -static int Releases; -static int Autoreleases; -static int RetainCounts; -static int PlusRetains; -static int PlusReleases; -static int PlusAutoreleases; -static int PlusRetainCounts; -static int Allocs; -static int AllocWithZones; - -static int SubRetains; -static int SubReleases; -static int SubAutoreleases; -static int SubRetainCounts; -static int SubPlusRetains; -static int SubPlusReleases; -static int SubPlusAutoreleases; -static int SubPlusRetainCounts; -static int SubAllocs; -static int SubAllocWithZones; - -static int Imps; - -static id imp_fn(id self, SEL _cmd __unused, ...) -{ - Imps++; - return self; -} - -static void zero(void) { - Retains = 0; - Releases = 0; - Autoreleases = 0; - RetainCounts = 0; - PlusRetains = 0; - PlusReleases = 0; - PlusAutoreleases = 0; - PlusRetainCounts = 0; - Allocs = 0; - AllocWithZones = 0; - - SubRetains = 0; - SubReleases = 0; - SubAutoreleases = 0; - SubRetainCounts = 0; - SubPlusRetains = 0; - SubPlusReleases = 0; - SubPlusAutoreleases = 0; - SubPlusRetainCounts = 0; - SubAllocs = 0; - SubAllocWithZones = 0; - - Imps = 0; -} - - -id HackRetain(id self, SEL _cmd __unused) { Retains++; return self; } -void HackRelease(id self __unused, SEL _cmd __unused) { Releases++; } -id HackAutorelease(id self, SEL _cmd __unused) { Autoreleases++; return self; } -NSUInteger HackRetainCount(id self __unused, SEL _cmd __unused) { RetainCounts++; return 1; } -id HackPlusRetain(id self, SEL _cmd __unused) { PlusRetains++; return self; } -void HackPlusRelease(id self __unused, SEL _cmd __unused) { PlusReleases++; } -id HackPlusAutorelease(id self, SEL _cmd __unused) { PlusAutoreleases++; return self; } -NSUInteger HackPlusRetainCount(id self __unused, SEL _cmd __unused) { PlusRetainCounts++; return 1; } -id HackAlloc(Class self, SEL _cmd __unused) { Allocs++; return class_createInstance(self, 0); } -id HackAllocWithZone(Class self, SEL _cmd __unused) { AllocWithZones++; return class_createInstance(self, 0); } - - -@interface OverridingSub : NSObject @end -@implementation OverridingSub - --(id) retain { SubRetains++; return self; } -+(id) retain { SubPlusRetains++; return self; } --(oneway void) release { SubReleases++; } -+(oneway void) release { SubPlusReleases++; } --(id) autorelease { SubAutoreleases++; return self; } -+(id) autorelease { SubPlusAutoreleases++; return self; } --(NSUInteger) retainCount { SubRetainCounts++; return 1; } -+(NSUInteger) retainCount { SubPlusRetainCounts++; return 1; } - -@end - -@interface OverridingASub : NSObject @end -@implementation OverridingASub -+(id) alloc { SubAllocs++; return class_createInstance(self, 0); } -@end - -@interface OverridingAWZSub : NSObject @end -@implementation OverridingAWZSub -+(id) allocWithZone:(NSZone * __unused)z { SubAllocWithZones++; return class_createInstance(self, 0); } -@end - -@interface OverridingAAWZSub : NSObject @end -@implementation OverridingAAWZSub -+(id) alloc { SubAllocs++; return class_createInstance(self, 0); } -+(id) allocWithZone:(NSZone * __unused)z { SubAllocWithZones++; return class_createInstance(self, 0); } -@end - - -@interface InheritingSub : NSObject @end -@implementation InheritingSub @end - -@interface InheritingSub2 : NSObject @end -@implementation InheritingSub2 @end -@interface InheritingSub2_2 : InheritingSub2 @end -@implementation InheritingSub2_2 @end - -@interface InheritingSub3 : NSObject @end -@implementation InheritingSub3 @end -@interface InheritingSub3_2 : InheritingSub3 @end -@implementation InheritingSub3_2 @end - -@interface InheritingSub4 : NSObject @end -@implementation InheritingSub4 @end -@interface InheritingSub4_2 : InheritingSub4 @end -@implementation InheritingSub4_2 @end - -@interface InheritingSub5 : NSObject @end -@implementation InheritingSub5 @end -@interface InheritingSub5_2 : InheritingSub5 @end -@implementation InheritingSub5_2 @end - -@interface InheritingSub6 : NSObject @end -@implementation InheritingSub6 @end -@interface InheritingSub6_2 : InheritingSub6 @end -@implementation InheritingSub6_2 @end - -@interface InheritingSub7 : NSObject @end -@implementation InheritingSub7 @end -@interface InheritingSub7_2 : InheritingSub7 @end -@implementation InheritingSub7_2 @end - -@interface InheritingSubCat : NSObject @end -@implementation InheritingSubCat @end -@interface InheritingSubCat_2 : InheritingSubCat @end -@implementation InheritingSubCat_2 @end - - -extern uintptr_t OBJC_CLASS_$_UnrealizedSubA1; -@interface UnrealizedSubA1 : NSObject @end -@implementation UnrealizedSubA1 @end -extern uintptr_t OBJC_CLASS_$_UnrealizedSubA2; -@interface UnrealizedSubA2 : NSObject @end -@implementation UnrealizedSubA2 @end -extern uintptr_t OBJC_CLASS_$_UnrealizedSubA3; -@interface UnrealizedSubA3 : NSObject @end -@implementation UnrealizedSubA3 @end - -extern uintptr_t OBJC_CLASS_$_UnrealizedSubB1; -@interface UnrealizedSubB1 : NSObject @end -@implementation UnrealizedSubB1 @end -extern uintptr_t OBJC_CLASS_$_UnrealizedSubB2; -@interface UnrealizedSubB2 : NSObject @end -@implementation UnrealizedSubB2 @end -extern uintptr_t OBJC_CLASS_$_UnrealizedSubB3; -@interface UnrealizedSubB3 : NSObject @end -@implementation UnrealizedSubB3 @end - -extern uintptr_t OBJC_CLASS_$_UnrealizedSubC1; -@interface UnrealizedSubC1 : NSObject @end -@implementation UnrealizedSubC1 @end -extern uintptr_t OBJC_CLASS_$_UnrealizedSubC2; -@interface UnrealizedSubC2 : NSObject @end -@implementation UnrealizedSubC2 @end -extern uintptr_t OBJC_CLASS_$_UnrealizedSubC3; -@interface UnrealizedSubC3 : NSObject @end -@implementation UnrealizedSubC3 @end - - -int main(int argc __unused, char **argv) -{ - objc_autoreleasePoolPush(); - - // Hack NSObject's RR methods. - // Don't use runtime functions to do this - - // we want the runtime to think that these are NSObject's real code - { - Class cls = [NSObject class]; - IMP *m; - IMP imp; - imp = class_getMethodImplementation(cls, @selector(retain)); - m = (IMP *)class_getInstanceMethod(cls, @selector(retain)); - testassert(m[2] == imp); // verify Method struct is as we expect - - m = (IMP *)class_getInstanceMethod(cls, @selector(retain)); - m[2] = (IMP)HackRetain; - m = (IMP *)class_getInstanceMethod(cls, @selector(release)); - m[2] = (IMP)HackRelease; - m = (IMP *)class_getInstanceMethod(cls, @selector(autorelease)); - m[2] = (IMP)HackAutorelease; - m = (IMP *)class_getInstanceMethod(cls, @selector(retainCount)); - m[2] = (IMP)HackRetainCount; - m = (IMP *)class_getClassMethod(cls, @selector(retain)); - m[2] = (IMP)HackPlusRetain; - m = (IMP *)class_getClassMethod(cls, @selector(release)); - m[2] = (IMP)HackPlusRelease; - m = (IMP *)class_getClassMethod(cls, @selector(autorelease)); - m[2] = (IMP)HackPlusAutorelease; - m = (IMP *)class_getClassMethod(cls, @selector(retainCount)); - m[2] = (IMP)HackPlusRetainCount; - m = (IMP *)class_getClassMethod(cls, @selector(alloc)); - m[2] = (IMP)HackAlloc; - m = (IMP *)class_getClassMethod(cls, @selector(allocWithZone:)); - m[2] = (IMP)HackAllocWithZone; - - _objc_flush_caches(cls); - - imp = class_getMethodImplementation(cls, @selector(retain)); - testassert(imp == (IMP)HackRetain); // verify hack worked - } - - Class cls = [NSObject class]; - Class icl = [InheritingSub class]; - Class ocl = [OverridingSub class]; - /* - Class oa1 = [OverridingASub class]; - Class oa2 = [OverridingAWZSub class]; - Class oa3 = [OverridingAAWZSub class]; - */ - NSObject *obj = [NSObject new]; - InheritingSub *inh = [InheritingSub new]; - OverridingSub *ovr = [OverridingSub new]; - - Class ccc; - id ooo; - Class cc2; - id oo2; - - void *dlh; - - -#if __x86_64__ - // vtable dispatch can introduce bypass just like the ARC entrypoints -#else - testprintf("method dispatch does not bypass\n"); - zero(); - - [obj retain]; - testassert(Retains == 1); - [obj release]; - testassert(Releases == 1); - [obj autorelease]; - testassert(Autoreleases == 1); - - [cls retain]; - testassert(PlusRetains == 1); - [cls release]; - testassert(PlusReleases == 1); - [cls autorelease]; - testassert(PlusAutoreleases == 1); - - [inh retain]; - testassert(Retains == 2); - [inh release]; - testassert(Releases == 2); - [inh autorelease]; - testassert(Autoreleases == 2); - - [icl retain]; - testassert(PlusRetains == 2); - [icl release]; - testassert(PlusReleases == 2); - [icl autorelease]; - testassert(PlusAutoreleases == 2); - - [ovr retain]; - testassert(SubRetains == 1); - [ovr release]; - testassert(SubReleases == 1); - [ovr autorelease]; - testassert(SubAutoreleases == 1); - - [ocl retain]; - testassert(SubPlusRetains == 1); - [ocl release]; - testassert(SubPlusReleases == 1); - [ocl autorelease]; - testassert(SubPlusAutoreleases == 1); - - [UnrealizedSubA1 retain]; - testassert(PlusRetains == 3); - [UnrealizedSubA2 release]; - testassert(PlusReleases == 3); - [UnrealizedSubA3 autorelease]; - testassert(PlusAutoreleases == 3); -#endif - - - testprintf("objc_msgSend() does not bypass\n"); - zero(); - - id (*retain_fn)(id, SEL) = (id(*)(id, SEL))objc_msgSend; - void (*release_fn)(id, SEL) = (void(*)(id, SEL))objc_msgSend; - id (*autorelease_fn)(id, SEL) = (id(*)(id, SEL))objc_msgSend; - - retain_fn(obj, @selector(retain)); - testassert(Retains == 1); - release_fn(obj, @selector(release)); - testassert(Releases == 1); - autorelease_fn(obj, @selector(autorelease)); - testassert(Autoreleases == 1); - - retain_fn(cls, @selector(retain)); - testassert(PlusRetains == 1); - release_fn(cls, @selector(release)); - testassert(PlusReleases == 1); - autorelease_fn(cls, @selector(autorelease)); - testassert(PlusAutoreleases == 1); - - retain_fn(inh, @selector(retain)); - testassert(Retains == 2); - release_fn(inh, @selector(release)); - testassert(Releases == 2); - autorelease_fn(inh, @selector(autorelease)); - testassert(Autoreleases == 2); - - retain_fn(icl, @selector(retain)); - testassert(PlusRetains == 2); - release_fn(icl, @selector(release)); - testassert(PlusReleases == 2); - autorelease_fn(icl, @selector(autorelease)); - testassert(PlusAutoreleases == 2); - - retain_fn(ovr, @selector(retain)); - testassert(SubRetains == 1); - release_fn(ovr, @selector(release)); - testassert(SubReleases == 1); - autorelease_fn(ovr, @selector(autorelease)); - testassert(SubAutoreleases == 1); - - retain_fn(ocl, @selector(retain)); - testassert(SubPlusRetains == 1); - release_fn(ocl, @selector(release)); - testassert(SubPlusReleases == 1); - autorelease_fn(ocl, @selector(autorelease)); - testassert(SubPlusAutoreleases == 1); - -#if __OBJC2__ - retain_fn((Class)&OBJC_CLASS_$_UnrealizedSubB1, @selector(retain)); - testassert(PlusRetains == 3); - release_fn((Class)&OBJC_CLASS_$_UnrealizedSubB2, @selector(release)); - testassert(PlusReleases == 3); - autorelease_fn((Class)&OBJC_CLASS_$_UnrealizedSubB3, @selector(autorelease)); - testassert(PlusAutoreleases == 3); -#endif - - - testprintf("arc function bypasses instance but not class or override\n"); - zero(); - - objc_retain(obj); - testassert(Retains == 0); - objc_release(obj); - testassert(Releases == 0); - objc_autorelease(obj); - testassert(Autoreleases == 0); - - objc_retain(cls); - testassert(PlusRetains == 1); - objc_release(cls); - testassert(PlusReleases == 1); - objc_autorelease(cls); - testassert(PlusAutoreleases == 1); - - objc_retain(inh); - testassert(Retains == 0); - objc_release(inh); - testassert(Releases == 0); - objc_autorelease(inh); - testassert(Autoreleases == 0); - - objc_retain(icl); - testassert(PlusRetains == 2); - objc_release(icl); - testassert(PlusReleases == 2); - objc_autorelease(icl); - testassert(PlusAutoreleases == 2); - - objc_retain(ovr); - testassert(SubRetains == 1); - objc_release(ovr); - testassert(SubReleases == 1); - objc_autorelease(ovr); - testassert(SubAutoreleases == 1); - - objc_retain(ocl); - testassert(SubPlusRetains == 1); - objc_release(ocl); - testassert(SubPlusReleases == 1); - objc_autorelease(ocl); - testassert(SubPlusAutoreleases == 1); - -#if __OBJC2__ - objc_retain((Class)&OBJC_CLASS_$_UnrealizedSubC1); - testassert(PlusRetains == 3); - objc_release((Class)&OBJC_CLASS_$_UnrealizedSubC2); - testassert(PlusReleases == 3); - objc_autorelease((Class)&OBJC_CLASS_$_UnrealizedSubC3); - testassert(PlusAutoreleases == 3); -#endif - - - testprintf("unrelated addMethod does not clobber\n"); - zero(); - - class_addMethod(cls, @selector(unrelatedMethod), (IMP)imp_fn, ""); - - objc_retain(obj); - testassert(Retains == 0); - objc_release(obj); - testassert(Releases == 0); - objc_autorelease(obj); - testassert(Autoreleases == 0); - - - testprintf("add class method does not clobber\n"); - zero(); - - objc_retain(obj); - testassert(Retains == 0); - objc_release(obj); - testassert(Releases == 0); - objc_autorelease(obj); - testassert(Autoreleases == 0); - - class_addMethod(object_getClass(cls), @selector(retain), (IMP)imp_fn, ""); - - objc_retain(obj); - testassert(Retains == 0); - objc_release(obj); - testassert(Releases == 0); - objc_autorelease(obj); - testassert(Autoreleases == 0); - - - testprintf("addMethod clobbers (InheritingSub2, retain)\n"); - zero(); - - ccc = [InheritingSub2 class]; - ooo = [ccc new]; - cc2 = [InheritingSub2_2 class]; - oo2 = [cc2 new]; - - objc_retain(ooo); - testassert(Retains == 0); - objc_release(ooo); - testassert(Releases == 0); - objc_autorelease(ooo); - testassert(Autoreleases == 0); - - objc_retain(oo2); - testassert(Retains == 0); - objc_release(oo2); - testassert(Releases == 0); - objc_autorelease(oo2); - testassert(Autoreleases == 0); - - class_addMethod(ccc, @selector(retain), (IMP)imp_fn, ""); - - objc_retain(ooo); - testassert(Retains == 0); - testassert(Imps == 1); - objc_release(ooo); - testassert(Releases == 1); - objc_autorelease(ooo); - testassert(Autoreleases == 1); - - objc_retain(oo2); - testassert(Retains == 0); - testassert(Imps == 2); - objc_release(oo2); - testassert(Releases == 2); - objc_autorelease(oo2); - testassert(Autoreleases == 2); - - - testprintf("addMethod clobbers (InheritingSub3, release)\n"); - zero(); - - ccc = [InheritingSub3 class]; - ooo = [ccc new]; - cc2 = [InheritingSub3_2 class]; - oo2 = [cc2 new]; - - objc_retain(ooo); - testassert(Retains == 0); - objc_release(ooo); - testassert(Releases == 0); - objc_autorelease(ooo); - testassert(Autoreleases == 0); - - objc_retain(oo2); - testassert(Retains == 0); - objc_release(oo2); - testassert(Releases == 0); - objc_autorelease(oo2); - testassert(Autoreleases == 0); - - class_addMethod(ccc, @selector(release), (IMP)imp_fn, ""); - - objc_retain(ooo); - testassert(Retains == 1); - objc_release(ooo); - testassert(Releases == 0); - testassert(Imps == 1); - objc_autorelease(ooo); - testassert(Autoreleases == 1); - - objc_retain(oo2); - testassert(Retains == 2); - objc_release(oo2); - testassert(Releases == 0); - testassert(Imps == 2); - objc_autorelease(oo2); - testassert(Autoreleases == 2); - - - testprintf("addMethod clobbers (InheritingSub4, autorelease)\n"); - zero(); - - ccc = [InheritingSub4 class]; - ooo = [ccc new]; - cc2 = [InheritingSub4_2 class]; - oo2 = [cc2 new]; - - objc_retain(ooo); - testassert(Retains == 0); - objc_release(ooo); - testassert(Releases == 0); - objc_autorelease(ooo); - testassert(Autoreleases == 0); - - objc_retain(oo2); - testassert(Retains == 0); - objc_release(oo2); - testassert(Releases == 0); - objc_autorelease(oo2); - testassert(Autoreleases == 0); - - class_addMethod(ccc, @selector(autorelease), (IMP)imp_fn, ""); - - objc_retain(ooo); - testassert(Retains == 1); - objc_release(ooo); - testassert(Releases == 1); - objc_autorelease(ooo); - testassert(Autoreleases == 0); - testassert(Imps == 1); - - objc_retain(oo2); - testassert(Retains == 2); - objc_release(oo2); - testassert(Releases == 2); - objc_autorelease(oo2); - testassert(Autoreleases == 0); - testassert(Imps == 2); - - - testprintf("addMethod clobbers (InheritingSub5, retainCount)\n"); - zero(); - - ccc = [InheritingSub5 class]; - ooo = [ccc new]; - cc2 = [InheritingSub5_2 class]; - oo2 = [cc2 new]; - - objc_retain(ooo); - testassert(Retains == 0); - objc_release(ooo); - testassert(Releases == 0); - objc_autorelease(ooo); - testassert(Autoreleases == 0); - - objc_retain(oo2); - testassert(Retains == 0); - objc_release(oo2); - testassert(Releases == 0); - objc_autorelease(oo2); - testassert(Autoreleases == 0); - - class_addMethod(ccc, @selector(retainCount), (IMP)imp_fn, ""); - - objc_retain(ooo); - testassert(Retains == 1); - objc_release(ooo); - testassert(Releases == 1); - objc_autorelease(ooo); - testassert(Autoreleases == 1); - // no bypassing call for -retainCount - - objc_retain(oo2); - testassert(Retains == 2); - objc_release(oo2); - testassert(Releases == 2); - objc_autorelease(oo2); - testassert(Autoreleases == 2); - // no bypassing call for -retainCount - - - testprintf("setSuperclass to clean super does not clobber (InheritingSub6)\n"); - zero(); - - ccc = [InheritingSub6 class]; - ooo = [ccc new]; - cc2 = [InheritingSub6_2 class]; - oo2 = [cc2 new]; - - objc_retain(ooo); - testassert(Retains == 0); - objc_release(ooo); - testassert(Releases == 0); - objc_autorelease(ooo); - testassert(Autoreleases == 0); - - objc_retain(oo2); - testassert(Retains == 0); - objc_release(oo2); - testassert(Releases == 0); - objc_autorelease(oo2); - testassert(Autoreleases == 0); - - class_setSuperclass(ccc, [InheritingSub class]); - - objc_retain(ooo); - testassert(Retains == 0); - objc_release(ooo); - testassert(Releases == 0); - objc_autorelease(ooo); - testassert(Autoreleases == 0); - - objc_retain(oo2); - testassert(Retains == 0); - objc_release(oo2); - testassert(Releases == 0); - objc_autorelease(oo2); - testassert(Autoreleases == 0); - - - testprintf("setSuperclass to dirty super clobbers (InheritingSub7)\n"); - zero(); - - ccc = [InheritingSub7 class]; - ooo = [ccc new]; - cc2 = [InheritingSub7_2 class]; - oo2 = [cc2 new]; - - objc_retain(ooo); - testassert(Retains == 0); - objc_release(ooo); - testassert(Releases == 0); - objc_autorelease(ooo); - testassert(Autoreleases == 0); - - objc_retain(oo2); - testassert(Retains == 0); - objc_release(oo2); - testassert(Releases == 0); - objc_autorelease(oo2); - testassert(Autoreleases == 0); - - class_setSuperclass(ccc, [OverridingSub class]); - - objc_retain(ooo); - testassert(SubRetains == 1); - objc_release(ooo); - testassert(SubReleases == 1); - objc_autorelease(ooo); - testassert(SubAutoreleases == 1); - - objc_retain(oo2); - testassert(SubRetains == 2); - objc_release(oo2); - testassert(SubReleases == 2); - objc_autorelease(oo2); - testassert(SubAutoreleases == 2); - - - testprintf("category replacement of unrelated method does not clobber (InheritingSubCat)\n"); - zero(); - - ccc = [InheritingSubCat class]; - ooo = [ccc new]; - cc2 = [InheritingSubCat_2 class]; - oo2 = [cc2 new]; - - objc_retain(ooo); - testassert(Retains == 0); - objc_release(ooo); - testassert(Releases == 0); - objc_autorelease(ooo); - testassert(Autoreleases == 0); - - objc_retain(oo2); - testassert(Retains == 0); - objc_release(oo2); - testassert(Releases == 0); - objc_autorelease(oo2); - testassert(Autoreleases == 0); - - dlh = dlopen("customrr-cat1.dylib", RTLD_LAZY); - testassert(dlh); - - objc_retain(ooo); - testassert(Retains == 0); - objc_release(ooo); - testassert(Releases == 0); - objc_autorelease(ooo); - testassert(Autoreleases == 0); - - objc_retain(oo2); - testassert(Retains == 0); - objc_release(oo2); - testassert(Releases == 0); - objc_autorelease(oo2); - testassert(Autoreleases == 0); - - - testprintf("category replacement clobbers (InheritingSubCat)\n"); - zero(); - - ccc = [InheritingSubCat class]; - ooo = [ccc new]; - cc2 = [InheritingSubCat_2 class]; - oo2 = [cc2 new]; - - objc_retain(ooo); - testassert(Retains == 0); - objc_release(ooo); - testassert(Releases == 0); - objc_autorelease(ooo); - testassert(Autoreleases == 0); - - objc_retain(oo2); - testassert(Retains == 0); - objc_release(oo2); - testassert(Releases == 0); - objc_autorelease(oo2); - testassert(Autoreleases == 0); - - dlh = dlopen("customrr-cat2.dylib", RTLD_LAZY); - testassert(dlh); - - objc_retain(ooo); - testassert(Retains == 1); - objc_release(ooo); - testassert(Releases == 1); - objc_autorelease(ooo); - testassert(Autoreleases == 1); - - objc_retain(oo2); - testassert(Retains == 2); - objc_release(oo2); - testassert(Releases == 2); - objc_autorelease(oo2); - testassert(Autoreleases == 2); - - - testprintf("allocateClassPair with clean super does not clobber\n"); - zero(); - - objc_retain(inh); - testassert(Retains == 0); - objc_release(inh); - testassert(Releases == 0); - objc_autorelease(inh); - testassert(Autoreleases == 0); - - ccc = objc_allocateClassPair([InheritingSub class], "CleanClassPair", 0); - objc_registerClassPair(ccc); - ooo = [ccc new]; - - objc_retain(inh); - testassert(Retains == 0); - objc_release(inh); - testassert(Releases == 0); - objc_autorelease(inh); - testassert(Autoreleases == 0); - - objc_retain(ooo); - testassert(Retains == 0); - objc_release(ooo); - testassert(Releases == 0); - objc_autorelease(ooo); - testassert(Autoreleases == 0); - - - testprintf("allocateClassPair with clobbered super clobbers\n"); - zero(); - - ccc = objc_allocateClassPair([OverridingSub class], "DirtyClassPair", 0); - objc_registerClassPair(ccc); - ooo = [ccc new]; - - objc_retain(ooo); - testassert(SubRetains == 1); - objc_release(ooo); - testassert(SubReleases == 1); - objc_autorelease(ooo); - testassert(SubAutoreleases == 1); - - - testprintf("allocateClassPair with clean super and override clobbers\n"); - zero(); - - ccc = objc_allocateClassPair([InheritingSub class], "Dirty2ClassPair", 0); - class_addMethod(ccc, @selector(autorelease), (IMP)imp_fn, ""); - objc_registerClassPair(ccc); - ooo = [ccc new]; - - objc_retain(ooo); - testassert(Retains == 1); - objc_release(ooo); - testassert(Releases == 1); - objc_autorelease(ooo); - testassert(Autoreleases == 0); - testassert(Imps == 1); - - - // method_setImplementation and method_exchangeImplementations only - // clobber when manipulating NSObject. We can only test one at a time. - // To test both, we need two tests: customrr and customrr2. - - // These tests also check recursive clobber. - -#if TEST_EXCHANGEIMPLEMENTATIONS - testprintf("exchangeImplementations clobbers (recursive)\n"); -#else - testprintf("setImplementation clobbers (recursive)\n"); -#endif - zero(); - - objc_retain(obj); - testassert(Retains == 0); - objc_release(obj); - testassert(Releases == 0); - objc_autorelease(obj); - testassert(Autoreleases == 0); - - objc_retain(inh); - testassert(Retains == 0); - objc_release(inh); - testassert(Releases == 0); - objc_autorelease(inh); - testassert(Autoreleases == 0); - - Method meth = class_getInstanceMethod(cls, @selector(retainCount)); - testassert(meth); -#if TEST_EXCHANGEIMPLEMENTATIONS - method_exchangeImplementations(meth, meth); -#else - method_setImplementation(meth, (IMP)imp_fn); -#endif - - objc_retain(obj); - testassert(Retains == 1); - objc_release(obj); - testassert(Releases == 1); - objc_autorelease(obj); - testassert(Autoreleases == 1); - - objc_retain(inh); - testassert(Retains == 2); - objc_release(inh); - testassert(Releases == 2); - objc_autorelease(inh); - testassert(Autoreleases == 2); - - - // do not add more tests here - the recursive test must be LAST - - succeed(basename(argv[0])); -} - -#endif diff --git a/test/customrr2.m b/test/customrr2.m deleted file mode 100644 index cd49f38..0000000 --- a/test/customrr2.m +++ /dev/null @@ -1,9 +0,0 @@ -// These options must match customrr.m -// TEST_CONFIG MEM=mrc -/* -TEST_BUILD - $C{COMPILE} $DIR/customrr.m -fvisibility=default -o customrr2.out -DTEST_EXCHANGEIMPLEMENTATIONS=1 - $C{COMPILE} -undefined dynamic_lookup -dynamiclib $DIR/customrr-cat1.m -o customrr-cat1.dylib - $C{COMPILE} -undefined dynamic_lookup -dynamiclib $DIR/customrr-cat2.m -o customrr-cat2.dylib -END -*/ diff --git a/test/definitions.c b/test/definitions.c deleted file mode 100644 index c351234..0000000 --- a/test/definitions.c +++ /dev/null @@ -1,56 +0,0 @@ -// TEST_CONFIG - -// DO NOT include anything else here -#include -// DO NOT include anything else here -Class c = Nil; -SEL s; -IMP i; -id o = nil; -BOOL b = YES; -BOOL b2 = NO; -#if !__has_feature(objc_arc) -__strong void *p; -#endif -id __unsafe_unretained u; -id __weak w; - -void fn(void) __unused; -void fn(void) { - id __autoreleasing a __unused; -} - -#if __llvm__ && !__clang__ -// llvm-gcc defines _NSConcreteGlobalBlock wrong -#else -// rdar://10118972 wrong type inference for blocks returning YES and NO -BOOL (^block1)(void) = ^{ return YES; }; -BOOL (^block2)(void) = ^{ return NO; }; -#endif - -#include "test.h" - -int main() -{ - testassert(YES); - testassert(!NO); -#if __cplusplus - testwarn("rdar://12371870 -Wnull-conversion"); - testassert(!(bool)nil); - testassert(!(bool)Nil); -#else - testassert(!nil); - testassert(!Nil); -#endif - -#if __has_feature(objc_bool) - // YES[array] is disallowed for objc just as true[array] is for C++ -#else - // this will fail if YES and NO do not have enough parentheses - int array[2] = { 888, 999 }; - testassert(NO[array] == 888); - testassert(YES[array] == 999); -#endif - - succeed(__FILE__); -} diff --git a/test/designatedinit.m b/test/designatedinit.m deleted file mode 100644 index 232625f..0000000 --- a/test/designatedinit.m +++ /dev/null @@ -1,26 +0,0 @@ -// TEST_CONFIG -/* TEST_BUILD_OUTPUT -.*designatedinit.m:\d+:\d+: warning: designated initializer should only invoke a designated initializer on 'super'.* -.*designatedinit.m:\d+:\d+: note: .* -.*designatedinit.m:\d+:\d+: warning: method override for the designated initializer of the superclass '-init' not found.* -.*NSObject.h:\d+:\d+: note: .* -END */ - -#define NS_ENFORCE_NSOBJECT_DESIGNATED_INITIALIZER 1 -#include "test.h" -#include - -@interface C : NSObject --(id) initWithInt:(int)i NS_DESIGNATED_INITIALIZER; -@end - -@implementation C --(id) initWithInt:(int)__unused i { - return [self init]; -} -@end - -int main() -{ - succeed(__FILE__); -} diff --git a/test/duplicateClass.m b/test/duplicateClass.m deleted file mode 100644 index 0ac2671..0000000 --- a/test/duplicateClass.m +++ /dev/null @@ -1,164 +0,0 @@ -// TEST_CFLAGS -Wno-deprecated-declarations -Wl,-no_objc_category_merging - -#include "test.h" -#include "testroot.i" -#include -#ifndef OBJC_NO_GC -#include -#include -#endif - -static int state; - -@protocol Proto -+(void)classMethod; --(void)instanceMethod; -@end - -@interface Super : TestRoot { - int i; -} -@property int i; -@end - -@implementation Super -@synthesize i; - -+(void)classMethod { - state = 1; -} - --(void)instanceMethod { - state = 3; -} - -@end - - -#if __clang__ -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wobjc-protocol-method-implementation" -#endif - -@implementation Super (Category) - -+(void)classMethod { - state = 2; -} - --(void)instanceMethod { - state = 4; -} - -@end - -#if __clang__ -#pragma clang diagnostic pop -#endif - - -int main() -{ - Class clone; - Class cls; - Method *m1, *m2; - int i; - - cls = [Super class]; - clone = objc_duplicateClass(cls, "Super_copy", 0); -#ifndef OBJC_NO_GC - if (objc_collectingEnabled()) { - testassert(auto_zone_size(objc_collectableZone(), objc_unretainedPointer(clone))); - // objc_duplicateClass() doesn't duplicate the metaclass - // no: testassert(auto_zone_size(objc_collectableZone(), clone->isa)); - } -#endif - - testassert(clone != cls); - testassert(object_getClass(clone) == object_getClass(cls)); - testassert(class_getSuperclass(clone) == class_getSuperclass(cls)); - testassert(class_getVersion(clone) == class_getVersion(cls)); - testassert(class_isMetaClass(clone) == class_isMetaClass(cls)); - testassert(class_getIvarLayout(clone) == class_getIvarLayout(cls)); - testassert(class_getWeakIvarLayout(clone) == class_getWeakIvarLayout(cls)); -#if !__OBJC2__ - testassert((clone->info & (CLS_CLASS|CLS_META)) == (cls->info & (CLS_CLASS|CLS_META))); -#endif - - // Check method list - - m1 = class_copyMethodList(cls, NULL); - m2 = class_copyMethodList(clone, NULL); - testassert(m1); - testassert(m2); - for (i = 0; m1[i] && m2[i]; i++) { - testassert(m1[i] != m2[i]); // method list must be deep-copied - testassert(method_getName(m1[i]) == method_getName(m2[i])); - testassert(method_getImplementation(m1[i]) == method_getImplementation(m2[i])); - testassert(method_getTypeEncoding(m1[i]) == method_getTypeEncoding(m2[i])); - } - testassert(m1[i] == NULL && m2[i] == NULL); - free(m1); - free(m2); - - // Check ivar list - Ivar *i1 = class_copyIvarList(cls, NULL); - Ivar *i2 = class_copyIvarList(clone, NULL); - testassert(i1); - testassert(i2); - for (i = 0; i1[i] && i2[i]; i++) { - testassert(i1[i] == i2[i]); // ivars are not deep-copied - } - testassert(i1[i] == NULL && i2[i] == NULL); - free(i1); - free(i2); - - // Check protocol list - Protocol * __unsafe_unretained *p1 = class_copyProtocolList(cls, NULL); - Protocol * __unsafe_unretained *p2 = class_copyProtocolList(clone, NULL); - testassert(p1); - testassert(p2); - for (i = 0; p1[i] && p2[i]; i++) { - testassert(p1[i] == p2[i]); // protocols are not deep-copied - } - testassert(p1[i] == NULL && p2[i] == NULL); - free(p1); - free(p2); - - // Check property list - objc_property_t *o1 = class_copyPropertyList(cls, NULL); - objc_property_t *o2 = class_copyPropertyList(clone, NULL); - testassert(o1); - testassert(o2); - for (i = 0; o1[i] && o2[i]; i++) { - testassert(o1[i] == o2[i]); // properties are not deep-copied - } - testassert(o1[i] == NULL && o2[i] == NULL); - free(o1); - free(o2); - - // Check method calls - - state = 0; - [cls classMethod]; - testassert(state == 2); - state = 0; - [clone classMethod]; - testassert(state == 2); - - // #4511660 Make sure category implementation is still the preferred one - id obj; - obj = [cls new]; - state = 0; - [obj instanceMethod]; - testassert(state == 4); - RELEASE_VAR(obj); - - obj = [clone new]; - state = 0; - [obj instanceMethod]; - testassert(state == 4); - RELEASE_VAR(obj); - - succeed(__FILE__); -} diff --git a/test/duplicatedClasses.m b/test/duplicatedClasses.m deleted file mode 100644 index 991b133..0000000 --- a/test/duplicatedClasses.m +++ /dev/null @@ -1,27 +0,0 @@ -// TEST_ENV OBJC_DEBUG_DUPLICATE_CLASSES=YES -// TEST_CRASHES -/* -TEST_RUN_OUTPUT -objc\[\d+\]: Class GKScore is implemented in both [^\s]+ and [^\s]+ One of the two will be used. Which one is undefined. -CRASHED: SIG(ILL|TRAP) -OR -OK: duplicatedClasses.m -END - */ - -#include "test.h" -#include "testroot.i" - -@interface GKScore : TestRoot @end -@implementation GKScore @end - -int main() -{ - if (objc_collectingEnabled()) { - testwarn("rdar://19042235 test disabled because GameKit is not GC"); - succeed(__FILE__); - } - void *dl = dlopen("/System/Library/Frameworks/GameKit.framework/GameKit", RTLD_LAZY); - if (!dl) fail("couldn't open GameKit"); - fail("should have crashed already"); -} diff --git a/test/evil-category-0.m b/test/evil-category-0.m deleted file mode 100644 index a0d6060..0000000 --- a/test/evil-category-0.m +++ /dev/null @@ -1,18 +0,0 @@ -/* -rdar://8553305 - -TEST_BUILD - $C{COMPILE} $DIR/evil-category-0.m -dynamiclib -o libevil.dylib - $C{COMPILE} $DIR/evil-main.m -x none -DNOT_EVIL libevil.dylib -o evil-category-0.out -END -*/ - -// NOT EVIL version - -#define EVIL_INSTANCE_METHOD 0 -#define EVIL_CLASS_METHOD 0 - -#define OMIT_CAT 0 -#define OMIT_NL_CAT 0 - -#include "evil-category-def.m" diff --git a/test/evil-category-00.m b/test/evil-category-00.m deleted file mode 100644 index 4bc050d..0000000 --- a/test/evil-category-00.m +++ /dev/null @@ -1,24 +0,0 @@ -/* -rdar://8553305 - -TEST_CONFIG OS=iphoneos -TEST_CRASHES - -TEST_BUILD - $C{COMPILE} $DIR/evil-category-00.m $DIR/evil-main.m -o evil-category-00.out -END - -TEST_RUN_OUTPUT -CRASHED: SIGSEGV -END -*/ - -// NOT EVIL version: apps are allowed through (then crash in +load) - -#define EVIL_INSTANCE_METHOD 1 -#define EVIL_CLASS_METHOD 1 - -#define OMIT_CAT 0 -#define OMIT_NL_CAT 0 - -#include "evil-category-def.m" diff --git a/test/evil-category-000.m b/test/evil-category-000.m deleted file mode 100644 index e451706..0000000 --- a/test/evil-category-000.m +++ /dev/null @@ -1,18 +0,0 @@ -/* -rdar://8553305 - -TEST_BUILD - $C{COMPILE} $DIR/evil-category-000.m -dynamiclib -o libevil.dylib - $C{COMPILE} $DIR/evil-main.m -x none -DNOT_EVIL libevil.dylib -o evil-category-000.out -END -*/ - -// NOT EVIL version: category omitted from all lists - -#define EVIL_INSTANCE_METHOD 1 -#define EVIL_CLASS_METHOD 1 - -#define OMIT_CAT 1 -#define OMIT_NL_CAT 1 - -#include "evil-category-def.m" diff --git a/test/evil-category-1.m b/test/evil-category-1.m deleted file mode 100644 index 7a84f84..0000000 --- a/test/evil-category-1.m +++ /dev/null @@ -1,24 +0,0 @@ -/* -rdar://8553305 - -TEST_CONFIG OS=iphoneos -TEST_CRASHES - -TEST_BUILD - $C{COMPILE} $DIR/evil-category-1.m -dynamiclib -o libevil.dylib - $C{COMPILE} $DIR/evil-main.m -x none libevil.dylib -o evil-category-1.out -END - -TEST_RUN_OUTPUT -objc\[\d+\]: bad method implementation \(0x[0-9a-f]+ at 0x[0-9a-f]+\) -CRASHED: SIG(ILL|TRAP) -END -*/ - -#define EVIL_INSTANCE_METHOD 1 -#define EVIL_CLASS_METHOD 0 - -#define OMIT_CAT 0 -#define OMIT_NL_CAT 0 - -#include "evil-category-def.m" diff --git a/test/evil-category-2.m b/test/evil-category-2.m deleted file mode 100644 index 65f7e3b..0000000 --- a/test/evil-category-2.m +++ /dev/null @@ -1,24 +0,0 @@ -/* -rdar://8553305 - -TEST_CONFIG OS=iphoneos -TEST_CRASHES - -TEST_BUILD - $C{COMPILE} $DIR/evil-category-2.m -dynamiclib -o libevil.dylib - $C{COMPILE} $DIR/evil-main.m -x none libevil.dylib -o evil-category-2.out -END - -TEST_RUN_OUTPUT -objc\[\d+\]: bad method implementation \(0x[0-9a-f]+ at 0x[0-9a-f]+\) -CRASHED: SIG(ILL|TRAP) -END -*/ - -#define EVIL_INSTANCE_METHOD 0 -#define EVIL_CLASS_METHOD 1 - -#define OMIT_CAT 0 -#define OMIT_NL_CAT 0 - -#include "evil-category-def.m" diff --git a/test/evil-category-3.m b/test/evil-category-3.m deleted file mode 100644 index 6be6d0b..0000000 --- a/test/evil-category-3.m +++ /dev/null @@ -1,24 +0,0 @@ -/* -rdar://8553305 - -TEST_CONFIG OS=iphoneos -TEST_CRASHES - -TEST_BUILD - $C{COMPILE} $DIR/evil-category-3.m -dynamiclib -o libevil.dylib - $C{COMPILE} $DIR/evil-main.m -x none libevil.dylib -o evil-category-3.out -END - -TEST_RUN_OUTPUT -objc\[\d+\]: bad method implementation \(0x[0-9a-f]+ at 0x[0-9a-f]+\) -CRASHED: SIG(ILL|TRAP) -END -*/ - -#define EVIL_INSTANCE_METHOD 0 -#define EVIL_CLASS_METHOD 1 - -#define OMIT_CAT 1 -#define OMIT_NL_CAT 0 - -#include "evil-category-def.m" diff --git a/test/evil-category-4.m b/test/evil-category-4.m deleted file mode 100644 index 4f0a9eb..0000000 --- a/test/evil-category-4.m +++ /dev/null @@ -1,24 +0,0 @@ -/* -rdar://8553305 - -TEST_CONFIG OS=iphoneos -TEST_CRASHES - -TEST_BUILD - $C{COMPILE} $DIR/evil-category-4.m -dynamiclib -o libevil.dylib - $C{COMPILE} $DIR/evil-main.m -x none libevil.dylib -o evil-category-4.out -END - -TEST_RUN_OUTPUT -objc\[\d+\]: bad method implementation \(0x[0-9a-f]+ at 0x[0-9a-f]+\) -CRASHED: SIG(ILL|TRAP) -END -*/ - -#define EVIL_INSTANCE_METHOD 0 -#define EVIL_CLASS_METHOD 1 - -#define OMIT_CAT 0 -#define OMIT_NL_CAT 1 - -#include "evil-category-def.m" diff --git a/test/evil-category-def.m b/test/evil-category-def.m deleted file mode 100644 index a4bd1dc..0000000 --- a/test/evil-category-def.m +++ /dev/null @@ -1,72 +0,0 @@ - -#if __OBJC2__ - -#include - -#if __LP64__ -# define PTR " .quad " -#else -# define PTR " .long " -#endif - -#define str(x) #x -#define str2(x) str(x) - -__BEGIN_DECLS -void nop(void) { } -__END_DECLS - -asm( - ".section __DATA,__objc_data \n" - ".align 3 \n" - "L_category: \n" - PTR "L_cat_name \n" - PTR "_OBJC_CLASS_$_NSObject \n" -#if EVIL_INSTANCE_METHOD - PTR "L_evil_methods \n" -#else - PTR "L_good_methods \n" -#endif -#if EVIL_CLASS_METHOD - PTR "L_evil_methods \n" -#else - PTR "L_good_methods \n" -#endif - PTR "0 \n" - PTR "0 \n" - - "L_evil_methods: \n" - ".long 24 \n" - ".long 1 \n" - PTR "L_load \n" - PTR "L_load \n" - PTR str2(SHARED_REGION_BASE+SHARED_REGION_SIZE-PAGE_MAX_SIZE) " \n" - - "L_good_methods: \n" - ".long 24 \n" - ".long 1 \n" - PTR "L_load \n" - PTR "L_load \n" - PTR "_nop \n" - - ".cstring \n" - "L_cat_name: .ascii \"Evil\\0\" \n" - "L_load: .ascii \"load\\0\" \n" - - ".section __DATA,__objc_catlist \n" -#if !OMIT_CAT - PTR "L_category \n" -#endif - - ".section __DATA,__objc_nlcatlist \n" -#if !OMIT_NL_CAT - PTR "L_category \n" -#endif - - ".text \n" - ); - -// __OBJC2__ -#endif - -void fn(void) { } diff --git a/test/evil-class-0.m b/test/evil-class-0.m deleted file mode 100644 index 7d35ad8..0000000 --- a/test/evil-class-0.m +++ /dev/null @@ -1,22 +0,0 @@ -/* -rdar://8553305 - -TEST_BUILD - $C{COMPILE} $DIR/evil-class-0.m -dynamiclib -o libevil.dylib - $C{COMPILE} $DIR/evil-main.m -x none -DNOT_EVIL libevil.dylib -o evil-class-0.out -END -*/ - -// NOT EVIL version - -#define EVIL_SUPER 0 -#define EVIL_SUPER_META 0 -#define EVIL_SUB 0 -#define EVIL_SUB_META 0 - -#define OMIT_SUPER 0 -#define OMIT_NL_SUPER 0 -#define OMIT_SUB 0 -#define OMIT_NL_SUB 0 - -#include "evil-class-def.m" diff --git a/test/evil-class-00.m b/test/evil-class-00.m deleted file mode 100644 index 335675e..0000000 --- a/test/evil-class-00.m +++ /dev/null @@ -1,28 +0,0 @@ -/* -rdar://8553305 - -TEST_CONFIG OS=iphoneos -TEST_CRASHES - -TEST_BUILD - $C{COMPILE} $DIR/evil-class-00.m $DIR/evil-main.m -o evil-class-00.out -END - -TEST_RUN_OUTPUT -CRASHED: SIGSEGV -END -*/ - -// NOT EVIL version: apps are allowed through (then crash in +load) - -#define EVIL_SUPER 0 -#define EVIL_SUPER_META 1 -#define EVIL_SUB 0 -#define EVIL_SUB_META 0 - -#define OMIT_SUPER 1 -#define OMIT_NL_SUPER 1 -#define OMIT_SUB 1 -#define OMIT_NL_SUB 0 - -#include "evil-class-def.m" diff --git a/test/evil-class-000.m b/test/evil-class-000.m deleted file mode 100644 index 003d4d4..0000000 --- a/test/evil-class-000.m +++ /dev/null @@ -1,22 +0,0 @@ -/* -rdar://8553305 - -TEST_BUILD - $C{COMPILE} $DIR/evil-class-000.m -dynamiclib -o libevil.dylib - $C{COMPILE} $DIR/evil-main.m -x none -DNOT_EVIL libevil.dylib -o evil-class-000.out -END -*/ - -// NOT EVIL version: all classes omitted from all lists - -#define EVIL_SUPER 1 -#define EVIL_SUPER_META 1 -#define EVIL_SUB 1 -#define EVIL_SUB_META 1 - -#define OMIT_SUPER 1 -#define OMIT_NL_SUPER 1 -#define OMIT_SUB 1 -#define OMIT_NL_SUB 1 - -#include "evil-class-def.m" diff --git a/test/evil-class-1.m b/test/evil-class-1.m deleted file mode 100644 index 9266a79..0000000 --- a/test/evil-class-1.m +++ /dev/null @@ -1,28 +0,0 @@ -/* -rdar://8553305 - -TEST_CONFIG OS=iphoneos -TEST_CRASHES - -TEST_BUILD - $C{COMPILE} $DIR/evil-class-1.m -dynamiclib -o libevil.dylib - $C{COMPILE} $DIR/evil-main.m -x none libevil.dylib -o evil-class-1.out -END - -TEST_RUN_OUTPUT -objc\[\d+\]: bad method implementation \(0x[0-9a-f]+ at 0x[0-9a-f]+\) -CRASHED: SIG(ILL|TRAP) -END -*/ - -#define EVIL_SUPER 1 -#define EVIL_SUPER_META 0 -#define EVIL_SUB 0 -#define EVIL_SUB_META 0 - -#define OMIT_SUPER 0 -#define OMIT_NL_SUPER 0 -#define OMIT_SUB 0 -#define OMIT_NL_SUB 0 - -#include "evil-class-def.m" diff --git a/test/evil-class-2.m b/test/evil-class-2.m deleted file mode 100644 index 47aa3e2..0000000 --- a/test/evil-class-2.m +++ /dev/null @@ -1,28 +0,0 @@ -/* -rdar://8553305 - -TEST_CONFIG OS=iphoneos -TEST_CRASHES - -TEST_BUILD - $C{COMPILE} $DIR/evil-class-2.m -dynamiclib -o libevil.dylib - $C{COMPILE} $DIR/evil-main.m -x none libevil.dylib -o evil-class-2.out -END - -TEST_RUN_OUTPUT -objc\[\d+\]: bad method implementation \(0x[0-9a-f]+ at 0x[0-9a-f]+\) -CRASHED: SIG(ILL|TRAP) -END -*/ - -#define EVIL_SUPER 0 -#define EVIL_SUPER_META 1 -#define EVIL_SUB 0 -#define EVIL_SUB_META 0 - -#define OMIT_SUPER 0 -#define OMIT_NL_SUPER 0 -#define OMIT_SUB 0 -#define OMIT_NL_SUB 0 - -#include "evil-class-def.m" diff --git a/test/evil-class-3.m b/test/evil-class-3.m deleted file mode 100644 index 1d2e213..0000000 --- a/test/evil-class-3.m +++ /dev/null @@ -1,28 +0,0 @@ -/* -rdar://8553305 - -TEST_CONFIG OS=iphoneos -TEST_CRASHES - -TEST_BUILD - $C{COMPILE} $DIR/evil-class-3.m -dynamiclib -o libevil.dylib - $C{COMPILE} $DIR/evil-main.m -x none libevil.dylib -o evil-class-3.out -END - -TEST_RUN_OUTPUT -objc\[\d+\]: bad method implementation \(0x[0-9a-f]+ at 0x[0-9a-f]+\) -CRASHED: SIG(ILL|TRAP) -END -*/ - -#define EVIL_SUPER 0 -#define EVIL_SUPER_META 0 -#define EVIL_SUB 1 -#define EVIL_SUB_META 0 - -#define OMIT_SUPER 0 -#define OMIT_NL_SUPER 0 -#define OMIT_SUB 0 -#define OMIT_NL_SUB 0 - -#include "evil-class-def.m" diff --git a/test/evil-class-4.m b/test/evil-class-4.m deleted file mode 100644 index 8aef1f5..0000000 --- a/test/evil-class-4.m +++ /dev/null @@ -1,28 +0,0 @@ -/* -rdar://8553305 - -TEST_CONFIG OS=iphoneos -TEST_CRASHES - -TEST_BUILD - $C{COMPILE} $DIR/evil-class-4.m -dynamiclib -o libevil.dylib - $C{COMPILE} $DIR/evil-main.m -x none libevil.dylib -o evil-class-4.out -END - -TEST_RUN_OUTPUT -objc\[\d+\]: bad method implementation \(0x[0-9a-f]+ at 0x[0-9a-f]+\) -CRASHED: SIG(ILL|TRAP) -END -*/ - -#define EVIL_SUPER 0 -#define EVIL_SUPER_META 0 -#define EVIL_SUB 0 -#define EVIL_SUB_META 1 - -#define OMIT_SUPER 0 -#define OMIT_NL_SUPER 0 -#define OMIT_SUB 0 -#define OMIT_NL_SUB 0 - -#include "evil-class-def.m" diff --git a/test/evil-class-5.m b/test/evil-class-5.m deleted file mode 100644 index d8146f6..0000000 --- a/test/evil-class-5.m +++ /dev/null @@ -1,30 +0,0 @@ -/* -rdar://8553305 - -TEST_DISABLED rdar://19200100 - -TEST_CONFIG OS=iphoneos -TEST_CRASHES - -TEST_BUILD - $C{COMPILE} $DIR/evil-class-5.m -dynamiclib -o libevil.dylib - $C{COMPILE} $DIR/evil-main.m -x none libevil.dylib -o evil-class-5.out -END - -TEST_RUN_OUTPUT -objc\[\d+\]: bad method implementation \(0x[0-9a-f]+ at 0x[0-9a-f]+\) -CRASHED: SIG(ILL|TRAP) -END -*/ - -#define EVIL_SUPER 0 -#define EVIL_SUPER_META 1 -#define EVIL_SUB 0 -#define EVIL_SUB_META 0 - -#define OMIT_SUPER 1 -#define OMIT_NL_SUPER 1 -#define OMIT_SUB 1 -#define OMIT_NL_SUB 0 - -#include "evil-class-def.m" diff --git a/test/evil-class-def.m b/test/evil-class-def.m deleted file mode 100644 index 25ec45a..0000000 --- a/test/evil-class-def.m +++ /dev/null @@ -1,319 +0,0 @@ -#if __OBJC2__ - -#include - -#if __LP64__ -# define PTR " .quad " -# define PTRSIZE "8" -# define LOGPTRSIZE "3" -#else -# define PTR " .long " -# define PTRSIZE "4" -# define LOGPTRSIZE "2" -#endif - -#define str(x) #x -#define str2(x) str(x) - -__BEGIN_DECLS -// not id to avoid ARC operations because the class doesn't implement RR methods -void* nop(void* self) { return self; } -__END_DECLS - -asm( - ".globl _OBJC_CLASS_$_Super \n" - ".section __DATA,__objc_data \n" - ".align 3 \n" - "_OBJC_CLASS_$_Super: \n" - PTR "_OBJC_METACLASS_$_Super \n" - PTR "0 \n" - PTR "__objc_empty_cache \n" - PTR "0 \n" - PTR "L_ro \n" - // pad to OBJC_MAX_CLASS_SIZE - PTR "0 \n" - PTR "0 \n" - PTR "0 \n" - PTR "0 \n" - PTR "0 \n" - PTR "0 \n" - PTR "0 \n" - PTR "0 \n" - PTR "0 \n" - PTR "0 \n" - PTR "0 \n" - PTR "0 \n" - PTR "0 \n" - PTR "0 \n" - PTR "0 \n" - PTR "0 \n" - PTR "0 \n" - PTR "0 \n" - PTR "0 \n" - PTR "0 \n" - PTR "0 \n" - PTR "0 \n" - PTR "0 \n" - PTR "0 \n" - PTR "0 \n" - PTR "0 \n" - PTR "0 \n" - "" - "_OBJC_METACLASS_$_Super: \n" - PTR "_OBJC_METACLASS_$_Super \n" - PTR "_OBJC_CLASS_$_Super \n" - PTR "__objc_empty_cache \n" - PTR "0 \n" - PTR "L_meta_ro \n" - // pad to OBJC_MAX_CLASS_SIZE - PTR "0 \n" - PTR "0 \n" - PTR "0 \n" - PTR "0 \n" - PTR "0 \n" - PTR "0 \n" - PTR "0 \n" - PTR "0 \n" - PTR "0 \n" - PTR "0 \n" - PTR "0 \n" - PTR "0 \n" - PTR "0 \n" - PTR "0 \n" - PTR "0 \n" - PTR "0 \n" - PTR "0 \n" - PTR "0 \n" - PTR "0 \n" - PTR "0 \n" - PTR "0 \n" - PTR "0 \n" - PTR "0 \n" - PTR "0 \n" - PTR "0 \n" - PTR "0 \n" - PTR "0 \n" - "" - "L_ro: \n" - ".long 2 \n" - ".long 0 \n" - ".long "PTRSIZE" \n" -#if __LP64__ - ".long 0 \n" -#endif - PTR "0 \n" - PTR "L_super_name \n" -#if EVIL_SUPER - PTR "L_evil_methods \n" -#else - PTR "L_good_methods \n" -#endif - PTR "0 \n" - PTR "L_super_ivars \n" - PTR "0 \n" - PTR "0 \n" - "" - "L_meta_ro: \n" - ".long 3 \n" - ".long 40 \n" - ".long 40 \n" -#if __LP64__ - ".long 0 \n" -#endif - PTR "0 \n" - PTR "L_super_name \n" -#if EVIL_SUPER_META - PTR "L_evil_methods \n" -#else - PTR "L_good_methods \n" -#endif - PTR "0 \n" - PTR "0 \n" - PTR "0 \n" - PTR "0 \n" - - ".globl _OBJC_CLASS_$_Sub \n" - ".section __DATA,__objc_data \n" - ".align 3 \n" - "_OBJC_CLASS_$_Sub: \n" - PTR "_OBJC_METACLASS_$_Sub \n" - PTR "_OBJC_CLASS_$_Super \n" - PTR "__objc_empty_cache \n" - PTR "0 \n" - PTR "L_sub_ro \n" - // pad to OBJC_MAX_CLASS_SIZE - PTR "0 \n" - PTR "0 \n" - PTR "0 \n" - PTR "0 \n" - PTR "0 \n" - PTR "0 \n" - PTR "0 \n" - PTR "0 \n" - PTR "0 \n" - PTR "0 \n" - PTR "0 \n" - PTR "0 \n" - PTR "0 \n" - PTR "0 \n" - PTR "0 \n" - PTR "0 \n" - PTR "0 \n" - PTR "0 \n" - PTR "0 \n" - PTR "0 \n" - PTR "0 \n" - PTR "0 \n" - PTR "0 \n" - PTR "0 \n" - PTR "0 \n" - PTR "0 \n" - PTR "0 \n" - "" - "_OBJC_METACLASS_$_Sub: \n" - PTR "_OBJC_METACLASS_$_Super \n" - PTR "_OBJC_METACLASS_$_Super \n" - PTR "__objc_empty_cache \n" - PTR "0 \n" - PTR "L_sub_meta_ro \n" - // pad to OBJC_MAX_CLASS_SIZE - PTR "0 \n" - PTR "0 \n" - PTR "0 \n" - PTR "0 \n" - PTR "0 \n" - PTR "0 \n" - PTR "0 \n" - PTR "0 \n" - PTR "0 \n" - PTR "0 \n" - PTR "0 \n" - PTR "0 \n" - PTR "0 \n" - PTR "0 \n" - PTR "0 \n" - PTR "0 \n" - PTR "0 \n" - PTR "0 \n" - PTR "0 \n" - PTR "0 \n" - PTR "0 \n" - PTR "0 \n" - PTR "0 \n" - PTR "0 \n" - PTR "0 \n" - PTR "0 \n" - PTR "0 \n" - "" - "L_sub_ro: \n" - ".long 2 \n" - ".long 0 \n" - ".long "PTRSIZE" \n" -#if __LP64__ - ".long 0 \n" -#endif - PTR "0 \n" - PTR "L_sub_name \n" -#if EVIL_SUB - PTR "L_evil_methods \n" -#else - PTR "L_good_methods \n" -#endif - PTR "0 \n" - PTR "L_sub_ivars \n" - PTR "0 \n" - PTR "0 \n" - "" - "L_sub_meta_ro: \n" - ".long 3 \n" - ".long 40 \n" - ".long 40 \n" -#if __LP64__ - ".long 0 \n" -#endif - PTR "0 \n" - PTR "L_sub_name \n" -#if EVIL_SUB_META - PTR "L_evil_methods \n" -#else - PTR "L_good_methods \n" -#endif - PTR "0 \n" - PTR "0 \n" - PTR "0 \n" - PTR "0 \n" - - "L_evil_methods: \n" - ".long 3*"PTRSIZE" \n" - ".long 1 \n" - PTR "L_load \n" - PTR "L_load \n" - PTR str2(SHARED_REGION_BASE+SHARED_REGION_SIZE-PAGE_MAX_SIZE) " \n" - - "L_good_methods: \n" - ".long 3*"PTRSIZE" \n" - ".long 2 \n" - PTR "L_load \n" - PTR "L_load \n" - PTR "_nop \n" - PTR "L_self \n" - PTR "L_self \n" - PTR "_nop \n" - - "L_super_ivars: \n" - ".long 4*"PTRSIZE" \n" - ".long 1 \n" - PTR "L_super_ivar_offset \n" - PTR "L_super_ivar_name \n" - PTR "L_super_ivar_type \n" - ".long "LOGPTRSIZE" \n" - ".long "PTRSIZE" \n" - - "L_sub_ivars: \n" - ".long 4*"PTRSIZE" \n" - ".long 1 \n" - PTR "L_sub_ivar_offset \n" - PTR "L_sub_ivar_name \n" - PTR "L_sub_ivar_type \n" - ".long "LOGPTRSIZE" \n" - ".long "PTRSIZE" \n" - - "L_super_ivar_offset: \n" - ".long 0 \n" - "L_sub_ivar_offset: \n" - ".long "PTRSIZE" \n" - - ".cstring \n" - "L_super_name: .ascii \"Super\\0\" \n" - "L_sub_name: .ascii \"Sub\\0\" \n" - "L_load: .ascii \"load\\0\" \n" - "L_self: .ascii \"self\\0\" \n" - "L_super_ivar_name: .ascii \"super_ivar\\0\" \n" - "L_super_ivar_type: .ascii \"c\\0\" \n" - "L_sub_ivar_name: .ascii \"sub_ivar\\0\" \n" - "L_sub_ivar_type: .ascii \"@\\0\" \n" - - - ".section __DATA,__objc_classlist \n" -#if !OMIT_SUPER - PTR "_OBJC_CLASS_$_Super \n" -#endif -#if !OMIT_SUB - PTR "_OBJC_CLASS_$_Sub \n" -#endif - - ".section __DATA,__objc_nlclslist \n" -#if !OMIT_NL_SUPER - PTR "_OBJC_CLASS_$_Super \n" -#endif -#if !OMIT_NL_SUB - PTR "_OBJC_CLASS_$_Sub \n" -#endif - - ".text \n" -); - -// __OBJC2__ -#endif - -void fn(void) { } diff --git a/test/evil-main.m b/test/evil-main.m deleted file mode 100644 index aa6a124..0000000 --- a/test/evil-main.m +++ /dev/null @@ -1,15 +0,0 @@ -#include "test.h" - -extern void fn(void); - -int main(int argc __unused, char **argv) -{ - fn(); - -#if TARGET_OS_EMBEDDED && !defined(NOT_EVIL) -#pragma unused (argv) - fail("All that is necessary for the triumph of evil is that good men do nothing."); -#else - succeed(basename(argv[0])); -#endif -} diff --git a/test/exc.m b/test/exc.m deleted file mode 100644 index 4f25b75..0000000 --- a/test/exc.m +++ /dev/null @@ -1,888 +0,0 @@ -/* -need exception-safe ARC for exception deallocation tests -need F/CF for testonthread() in GC mode -TEST_CFLAGS -fobjc-arc-exceptions -framework Foundation - -llvm-gcc unavoidably warns about our deliberately out-of-order handlers - -TEST_BUILD_OUTPUT -.*exc.m: In function .* -.*exc.m:\d+: warning: exception of type .* will be caught -.*exc.m:\d+: warning: by earlier handler for .* -.*exc.m:\d+: warning: exception of type .* will be caught -.*exc.m:\d+: warning: by earlier handler for .* -.*exc.m:\d+: warning: exception of type .* will be caught -.*exc.m:\d+: warning: by earlier handler for .* -OR -END -*/ - -#include "test.h" -#include "testroot.i" -#include -#include - -static volatile int state = 0; -static volatile int dealloced = 0; -#define BAD 1000000 - -#if defined(USE_FOUNDATION) - -#include - -@interface Super : NSException @end -@implementation Super -+(id)exception { return AUTORELEASE([[self alloc] initWithName:@"Super" reason:@"reason" userInfo:nil]); } --(void)check { state++; } -+(void)check { testassert(!"caught class object, not instance"); } --(void)dealloc { dealloced++; SUPER_DEALLOC(); } --(void)finalize { dealloced++; [super finalize]; } -@end - -#define FILENAME "nsexc.m" - -#else - -@interface Super : TestRoot @end -@implementation Super -+(id)exception { return AUTORELEASE([self new]); } --(void)check { state++; } -+(void)check { testassert(!"caught class object, not instance"); } --(void)dealloc { dealloced++; SUPER_DEALLOC(); } --(void)finalize { dealloced++; [super finalize]; } -@end - -#define FILENAME "exc.m" - -#endif - -@interface Sub : Super @end -@implementation Sub -@end - - -#if __OBJC2__ && !TARGET_OS_EMBEDDED && !TARGET_OS_IPHONE -void altHandlerFail(id unused __unused, void *context __unused) -{ - fail("altHandlerFail called"); -} - -#define ALT_HANDLER(n) \ - void altHandler##n(id unused __unused, void *context) \ - { \ - testassert(context == (void*)&altHandler##n); \ - testassert(state == n); \ - state++; \ - } - -ALT_HANDLER(1) -ALT_HANDLER(2) -ALT_HANDLER(3) -ALT_HANDLER(4) -ALT_HANDLER(5) -ALT_HANDLER(6) -ALT_HANDLER(7) - - -static void throwWithAltHandler(void) __attribute__((noinline)); -static void throwWithAltHandler(void) -{ - @try { - state++; - uintptr_t token = objc_addExceptionHandler(altHandler3, (void*)altHandler3); - // state++ inside alt handler - @throw [Super exception]; - state = BAD; - objc_removeExceptionHandler(token); - } - @catch (Sub *e) { - state = BAD; - } - state = BAD; -} - - -static void throwWithAltHandlerAndRethrow(void) __attribute__((noinline)); -static void throwWithAltHandlerAndRethrow(void) -{ - @try { - state++; - uintptr_t token = objc_addExceptionHandler(altHandler3, (void*)altHandler3); - // state++ inside alt handler - @throw [Super exception]; - state = BAD; - objc_removeExceptionHandler(token); - } - @catch (...) { - testassert(state == 4); - state++; - @throw; - } - state = BAD; -} - -#endif - -#if __cplusplus && __OBJC2__ -#include -void terminator() { - succeed(FILENAME); -} -#endif - - -#define TEST(code) \ - do { \ - testonthread(^{ PUSH_POOL { code } POP_POOL; }); \ - testcollect(); \ - } while (0) - - - -int main() -{ - testprintf("try-catch-finally, exception caught exactly\n"); - - TEST({ - state = 0; - dealloced = 0; - @try { - state++; - @try { - state++; - @throw [Super exception]; - state = BAD; - } - @catch (Super *e) { - state++; - [e check]; // state++ - } - @finally { - state++; - } - state++; - } - @catch (...) { - state = BAD; - } - }); - testassert(state == 6); - testassert(dealloced == 1); - - - testprintf("try-finally, no exception thrown\n"); - - TEST({ - state = 0; - dealloced = 0; - @try { - state++; - @try { - state++; - } - @finally { - state++; - } - state++; - } - @catch (...) { - state = BAD; - } - }); - testassert(state == 4); - testassert(dealloced == 0); - - - testprintf("try-finally, with exception\n"); - - TEST({ - state = 0; - dealloced = 0; - @try { - state++; - @try { - state++; - @throw [Super exception]; - state = BAD; - } - @finally { - state++; - } - state = BAD; - } - @catch (id e) { - state++; - [e check]; // state++ - } - }); - testassert(state == 5); - testassert(dealloced == 1); - - -#if __OBJC2__ - testprintf("try-finally, with autorelease pool pop during unwind\n"); - // Popping an autorelease pool during unwind used to deallocate the - // exception object, but now we retain them while in flight. - - // This use-after-free is undetected without MallocScribble or guardmalloc. - if (!getenv("MallocScribble") && - (!getenv("DYLD_INSERT_LIBRARIES") || - !strstr(getenv("DYLD_INSERT_LIBRARIES"), "libgmalloc"))) - { - testwarn("MallocScribble not set"); - } - - TEST({ - state = 0; - dealloced = 0; - @try { - void *pool2 = objc_autoreleasePoolPush(); - state++; - @try { - state++; - @throw [Super exception]; - state = BAD; - } - @finally { - state++; - objc_autoreleasePoolPop(pool2); - } - state = BAD; - } - @catch (id e) { - state++; - [e check]; // state++ - } - }); - testassert(state == 5); - testassert(dealloced == 1); -#endif - - - testprintf("try-catch-finally, no exception\n"); - - TEST({ - state = 0; - dealloced = 0; - @try { - state++; - @try { - state++; - } - @catch (...) { - state = BAD; - } - @finally { - state++; - } - state++; - } @catch (...) { - state = BAD; - } - }); - testassert(state == 4); - testassert(dealloced == 0); - - - testprintf("try-catch-finally, exception not caught\n"); - - TEST({ - state = 0; - dealloced = 0; - @try { - state++; - @try { - state++; - @throw [Super exception]; - state = BAD; - } - @catch (Sub *e) { - state = BAD; - } - @finally { - state++; - } - state = BAD; - } - @catch (id e) { - state++; - [e check]; // state++ - } - }); - testassert(state == 5); - testassert(dealloced == 1); - - - testprintf("try-catch-finally, exception caught exactly, rethrown\n"); - - TEST({ - state = 0; - dealloced = 0; - @try { - state++; - @try { - state++; - @throw [Super exception]; - state = BAD; - } - @catch (Super *e) { - state++; - [e check]; // state++ - @throw; - state = BAD; - } - @finally { - state++; - } - state = BAD; - } - @catch (id e) { - state++; - [e check]; // state++ - } - }); - testassert(state == 7); - testassert(dealloced == 1); - - - testprintf("try-catch, no exception\n"); - - TEST({ - state = 0; - dealloced = 0; - @try { - state++; - @try { - state++; - } - @catch (...) { - state = BAD; - } - state++; - } @catch (...) { - state = BAD; - } - }); - testassert(state == 3); - testassert(dealloced == 0); - - - testprintf("try-catch, exception not caught\n"); - - TEST({ - state = 0; - dealloced = 0; - @try { - state++; - @try { - state++; - @throw [Super exception]; - state = BAD; - } - @catch (Sub *e) { - state = BAD; - } - state = BAD; - } - @catch (id e) { - state++; - [e check]; // state++ - } - }); - testassert(state == 4); - testassert(dealloced == 1); - - - testprintf("try-catch, exception caught exactly\n"); - - TEST({ - state = 0; - dealloced = 0; - @try { - state++; - @try { - state++; - @throw [Super exception]; - state = BAD; - } - @catch (Super *e) { - state++; - [e check]; // state++ - } - state++; - } - @catch (...) { - state = BAD; - } - }); - testassert(state == 5); - testassert(dealloced == 1); - - - testprintf("try-catch, exception caught exactly, rethrown\n"); - - TEST({ - state = 0; - dealloced = 0; - @try { - state++; - @try { - state++; - @throw [Super exception]; - state = BAD; - } - @catch (Super *e) { - state++; - [e check]; // state++ - @throw; - state = BAD; - } - state = BAD; - } - @catch (id e) { - state++; - [e check]; // state++ - } - }); - testassert(state == 6); - testassert(dealloced == 1); - - - testprintf("try-catch, exception caught exactly, thrown again explicitly\n"); - - TEST({ - state = 0; - dealloced = 0; - @try { - state++; - @try { - state++; - @throw [Super exception]; - state = BAD; - } - @catch (Super *e) { - state++; - [e check]; // state++ - @throw e; - state = BAD; - } - state = BAD; - } - @catch (id e) { - state++; - [e check]; // state++ - } - }); - testassert(state == 6); - testassert(dealloced == 1); - - - testprintf("try-catch, default catch, rethrown\n"); - - TEST({ - state = 0; - dealloced = 0; - @try { - state++; - @try { - state++; - @throw [Super exception]; - state = BAD; - } - @catch (...) { - state++; - @throw; - state = BAD; - } - state = BAD; - } - @catch (id e) { - state++; - [e check]; // state++ - } - }); - testassert(state == 5); - testassert(dealloced == 1); - - - testprintf("try-catch, default catch, rethrown and caught inside nested handler\n"); - - TEST({ - state = 0; - dealloced = 0; - @try { - state++; - @try { - state++; - @throw [Super exception]; - state = BAD; - } - @catch (...) { - state++; - - @try { - state++; - @throw; - state = BAD; - } @catch (Sub *e) { - state = BAD; - } @catch (Super *e) { - state++; - [e check]; // state++ - } @catch (...) { - state = BAD; - } @finally { - state++; - } - - state++; - } - state++; - } - @catch (...) { - state = BAD; - } - }); - testassert(state == 9); - testassert(dealloced == 1); - - - testprintf("try-catch, default catch, rethrown inside nested handler but not caught\n"); - - TEST({ - state = 0; - dealloced = 0; - @try { - state++; - @try { - state++; - @throw [Super exception]; - state = BAD; - } - @catch (...) { - state++; - - @try { - state++; - @throw; - state = BAD; - } - @catch (Sub *e) { - state = BAD; - } - @finally { - state++; - } - - state = BAD; - } - state = BAD; - } - @catch (id e) { - state++; - [e check]; // state++ - } - }); - testassert(state == 7); - testassert(dealloced == 1); - - -#if __cplusplus && __OBJC2__ - testprintf("C++ try/catch, Objective-C exception superclass\n"); - - TEST({ - state = 0; - dealloced = 0; - try { - state++; - try { - state++; - try { - state++; - @throw [Super exception]; - state = BAD; - } catch (...) { - state++; - throw; - state = BAD; - } - state = BAD; - } catch (void *e) { - state = BAD; - } catch (int e) { - state = BAD; - } catch (Sub *e) { - state = BAD; - } catch (Super *e) { - state++; - [e check]; // state++ - throw; - } catch (...) { - state = BAD; - } - } catch (id e) { - state++; - [e check]; // state++; - } - }); - testassert(state == 8); - testassert(dealloced == 1); - - - testprintf("C++ try/catch, Objective-C exception subclass\n"); - - TEST({ - state = 0; - dealloced = 0; - try { - state++; - try { - state++; - try { - state++; - @throw [Sub exception]; - state = BAD; - } catch (...) { - state++; - throw; - state = BAD; - } - state = BAD; - } catch (void *e) { - state = BAD; - } catch (int e) { - state = BAD; - } catch (Super *e) { - state++; - [e check]; // state++ - throw; - } catch (Sub *e) { - state = BAD; - } catch (...) { - state = BAD; - } - } catch (id e) { - state++; - [e check]; // state++; - } - }); - testassert(state == 8); - testassert(dealloced == 1); - -#endif - - -#if !__OBJC2__ || TARGET_OS_EMBEDDED || TARGET_OS_IPHONE - // alt handlers for modern Mac OS only - -#else - { - // alt handlers - // run a lot to catch failed unregistration (runtime complains at 1000) -#define ALT_HANDLER_REPEAT 2000 - - testprintf("alt handler, no exception\n"); - - TEST({ - dealloced = 0; - for (int i = 0; i < ALT_HANDLER_REPEAT; i++) { - state = 0; - @try { - state++; - @try { - uintptr_t token = objc_addExceptionHandler(altHandlerFail, 0); - state++; - objc_removeExceptionHandler(token); - } - @catch (...) { - state = BAD; - } - state++; - } @catch (...) { - state = BAD; - } - testassert(state == 3); - } - }); - testassert(dealloced == 0); - - - testprintf("alt handler, exception thrown through\n"); - - TEST({ - dealloced = 0; - for (int i = 0; i < ALT_HANDLER_REPEAT; i++) { - state = 0; - @try { - state++; - @try { - state++; - uintptr_t token = objc_addExceptionHandler(altHandler2, (void*)altHandler2); - // state++ inside alt handler - @throw [Super exception]; - state = BAD; - objc_removeExceptionHandler(token); - } - @catch (Sub *e) { - state = BAD; - } - state = BAD; - } - @catch (id e) { - testassert(state == 3); - state++; - [e check]; // state++ - } - testassert(state == 5); - } - }); - testassert(dealloced == ALT_HANDLER_REPEAT); - - - testprintf("alt handler, nested\n"); - - TEST({ - dealloced = 0; - for (int i = 0; i < ALT_HANDLER_REPEAT; i++) { - state = 0; - @try { - state++; - @try { - state++; - // same-level handlers called in FIFO order (not stack-like) - uintptr_t token = objc_addExceptionHandler(altHandler4, (void*)altHandler4); - // state++ inside alt handler - uintptr_t token2 = objc_addExceptionHandler(altHandler5, (void*)altHandler5); - // state++ inside alt handler - throwWithAltHandler(); // state += 2 inside - state = BAD; - objc_removeExceptionHandler(token); - objc_removeExceptionHandler(token2); - } - @catch (id e) { - testassert(state == 6); - state++; - [e check]; // state++; - } - state++; - } - @catch (...) { - state = BAD; - } - testassert(state == 9); - } - }); - testassert(dealloced == ALT_HANDLER_REPEAT); - - - testprintf("alt handler, nested, rethrows in between\n"); - - TEST({ - dealloced = 0; - for (int i = 0; i < ALT_HANDLER_REPEAT; i++) { - state = 0; - @try { - state++; - @try { - state++; - // same-level handlers called in FIFO order (not stack-like) - uintptr_t token = objc_addExceptionHandler(altHandler5, (void*)altHandler5); - // state++ inside alt handler - uintptr_t token2 = objc_addExceptionHandler(altHandler6, (void*)altHandler6); - // state++ inside alt handler - throwWithAltHandlerAndRethrow(); // state += 3 inside - state = BAD; - objc_removeExceptionHandler(token); - objc_removeExceptionHandler(token2); - } - @catch (...) { - testassert(state == 7); - state++; - @throw; - } - state = BAD; - } - @catch (id e) { - testassert(state == 8); - state++; - [e check]; // state++ - } - testassert(state == 10); - } - }); - testassert(dealloced == ALT_HANDLER_REPEAT); - - - testprintf("alt handler, exception thrown and caught inside\n"); - - TEST({ - dealloced = 0; - for (int i = 0; i < ALT_HANDLER_REPEAT; i++) { - state = 0; - @try { - state++; - uintptr_t token = objc_addExceptionHandler(altHandlerFail, 0); - @try { - state++; - @throw [Super exception]; - state = BAD; - } - @catch (Super *e) { - state++; - [e check]; // state++ - } - state++; - objc_removeExceptionHandler(token); - } - @catch (...) { - state = BAD; - } - testassert(state == 5); - } - }); - testassert(dealloced == ALT_HANDLER_REPEAT); - - -#if defined(USE_FOUNDATION) - testprintf("alt handler, rdar://10055775\n"); - - TEST({ - dealloced = 0; - for (int i = 0; i < ALT_HANDLER_REPEAT; i++) { - state = 0; - @try { - uintptr_t token = objc_addExceptionHandler(altHandler1, (void*)altHandler1); - { - id x = [NSArray array]; - x = [NSArray array]; - } - state++; - // state++ inside alt handler - [Super raise:@"foo" format:@"bar"]; - state = BAD; - objc_removeExceptionHandler(token); - } @catch (id e) { - state++; - testassert(state == 3); - } - testassert(state == 3); - } - }); - testassert(dealloced == ALT_HANDLER_REPEAT); - -// defined(USE_FOUNDATION) -#endif - - } -// alt handlers -#endif - -#if __cplusplus && __OBJC2__ - std::set_terminate(terminator); - objc_terminate(); - fail("should not have returned from objc_terminate()"); -#else - succeed(FILENAME); -#endif -} - diff --git a/test/exchangeImp.m b/test/exchangeImp.m deleted file mode 100644 index 12f81e4..0000000 --- a/test/exchangeImp.m +++ /dev/null @@ -1,87 +0,0 @@ -// TEST_CONFIG - -#include "test.h" -#include "testroot.i" -#include - -static int state; - -#define ONE 1 -#define TWO 2 -#define LENGTH 3 -#define COUNT 4 - -@interface Super : TestRoot @end -@implementation Super -+(void) one { state = ONE; } -+(void) two { state = TWO; } -+(void) length { state = LENGTH; } -+(void) count { state = COUNT; } -@end - -#define checkExchange(s1, v1, s2, v2) \ - do { \ - Method m1, m2; \ - \ - testprintf("Check unexchanged version\n"); \ - state = 0; \ - [Super s1]; \ - testassert(state == v1); \ - state = 0; \ - [Super s2]; \ - testassert(state == v2); \ - \ - testprintf("Exchange\n"); \ - m1 = class_getClassMethod([Super class], @selector(s1)); \ - m2 = class_getClassMethod([Super class], @selector(s2)); \ - testassert(m1); \ - testassert(m2); \ - method_exchangeImplementations(m1, m2); \ - \ - testprintf("Check exchanged version\n"); \ - state = 0; \ - [Super s1]; \ - testassert(state == v2); \ - state = 0; \ - [Super s2]; \ - testassert(state == v1); \ - \ - testprintf("NULL should do nothing\n"); \ - method_exchangeImplementations(m1, NULL); \ - method_exchangeImplementations(NULL, m2); \ - method_exchangeImplementations(NULL, NULL); \ - \ - testprintf("Make sure NULL did nothing\n"); \ - state = 0; \ - [Super s1]; \ - testassert(state == v2); \ - state = 0; \ - [Super s2]; \ - testassert(state == v1); \ - \ - testprintf("Put them back\n"); \ - method_exchangeImplementations(m1, m2); \ - \ - testprintf("Check restored version\n"); \ - state = 0; \ - [Super s1]; \ - testassert(state == v1); \ - state = 0; \ - [Super s2]; \ - testassert(state == v2); \ - } while (0) - -int main() -{ - // Check ordinary selectors - checkExchange(one, ONE, two, TWO); - - // Check vtable selectors - checkExchange(length, LENGTH, count, COUNT); - - // Check ordinary<->vtable and vtable<->ordinary - checkExchange(count, COUNT, one, ONE); - checkExchange(two, TWO, length, LENGTH); - - succeed(__FILE__); -} diff --git a/test/foreach.m b/test/foreach.m deleted file mode 100644 index 4386db3..0000000 --- a/test/foreach.m +++ /dev/null @@ -1,227 +0,0 @@ -// TEST_CFLAGS -framework Foundation - -#include "test.h" -#import - -/* foreach tester */ - -int Errors = 0; - -bool testHandwritten(const char *style, const char *test, const char *message, id collection, NSSet *reference) { - unsigned int counter = 0; - bool result = true; - testprintf("testing: %s %s %s\n", style, test, message); -/* - for (id elem in collection) - if ([reference member:elem]) ++counter; - */ - NSFastEnumerationState state; - id __unsafe_unretained buffer[4]; - state.state = 0; - NSUInteger limit = [collection countByEnumeratingWithState:&state objects:buffer count:4]; - if (limit != 0) { - unsigned long mutationsPtr = *state.mutationsPtr; - do { - unsigned long innerCounter = 0; - do { - if (mutationsPtr != *state.mutationsPtr) objc_enumerationMutation(collection); - id elem = state.itemsPtr[innerCounter++]; - - if ([reference member:elem]) ++counter; - - } while (innerCounter < limit); - } while ((limit = [collection countByEnumeratingWithState:&state objects:buffer count:4])); - } - - - - if (counter == [reference count]) { - testprintf("success: %s %s %s\n", style, test, message); - } - else { - result = false; - printf("** failed: %s %s %s (%d vs %d)\n", style, test, message, counter, (int)[reference count]); - ++Errors; - } - return result; -} - -bool testCompiler(const char *style, const char *test, const char *message, id collection, NSSet *reference) { - unsigned int counter = 0; - bool result = true; - testprintf("testing: %s %s %s\n", style, test, message); - for (id elem in collection) - if ([reference member:elem]) ++counter; - if (counter == [reference count]) { - testprintf("success: %s %s %s\n", style, test, message); - } - else { - result = false; - printf("** failed: %s %s %s (%d vs %d)\n", style, test, message, counter, (int)[reference count]); - ++Errors; - } - return result; -} - -void testContinue(NSArray *array) { - bool broken = false; - testprintf("testing: continue statements\n"); - for (id __unused elem in array) { - if ([array count]) - continue; - broken = true; - } - if (broken) { - printf("** continue statement did not work\n"); - ++Errors; - } -} - - -// array is filled with NSNumbers, in order, from 0 - N -bool testBreak(unsigned int where, NSArray *array) { - PUSH_POOL { - unsigned int counter = 0; - id enumerator = [array objectEnumerator]; - for (id __unused elem in enumerator) { - if (++counter == where) - break; - } - if (counter != where) { - ++Errors; - printf("*** break at %d didn't work (actual was %d)\n", where, counter); - return false; - } - for (id __unused elem in enumerator) - ++counter; - if (counter != [array count]) { - ++Errors; - printf("*** break at %d didn't finish (actual was %d)\n", where, counter); - return false; - } - } POP_POOL; - return true; -} - -bool testBreaks(NSArray *array) { - bool result = true; - testprintf("testing breaks\n"); - unsigned int counter = 0; - for (counter = 1; counter < [array count]; ++counter) { - result = testBreak(counter, array) && result; - } - return result; -} - -bool testCompleteness(const char *test, const char *message, id collection, NSSet *reference) { - bool result = true; - result = result && testHandwritten("handwritten", test, message, collection, reference); - result = result && testCompiler("compiler", test, message, collection, reference); - return result; -} - -bool testEnumerator(const char *test, const char *message, id collection, NSSet *reference) { - bool result = true; - result = result && testHandwritten("handwritten", test, message, [collection objectEnumerator], reference); - result = result && testCompiler("compiler", test, message, [collection objectEnumerator], reference); - return result; -} - -NSMutableSet *ReferenceSet = nil; -NSMutableArray *ReferenceArray = nil; - -void makeReferences(int n) { - if (!ReferenceSet) { - int i; - ReferenceSet = [[NSMutableSet alloc] init]; - ReferenceArray = [[NSMutableArray alloc] init]; - for (i = 0; i < n; ++i) { - NSNumber *number = [[NSNumber alloc] initWithInt:i]; - [ReferenceSet addObject:number]; - [ReferenceArray addObject:number]; - RELEASE_VAR(number); - } - } -} - -void testCollections(const char *test, NSArray *array, NSSet *set) { - PUSH_POOL { - id collection; - collection = [NSMutableArray arrayWithArray:array]; - testCompleteness(test, "mutable array", collection, set); - testEnumerator(test, "mutable array enumerator", collection, set); - collection = [NSArray arrayWithArray:array]; - testCompleteness(test, "immutable array", collection, set); - testEnumerator(test, "immutable array enumerator", collection, set); - collection = set; - testCompleteness(test, "immutable set", collection, set); - testEnumerator(test, "immutable set enumerator", collection, set); - collection = [NSMutableSet setWithArray:array]; - testCompleteness(test, "mutable set", collection, set); - testEnumerator(test, "mutable set enumerator", collection, set); - } POP_POOL; -} - -void testInnerDecl(const char *test, const char *message, id collection) { - unsigned int counter = 0; - for (id __unused x in collection) - ++counter; - if (counter != [collection count]) { - printf("** failed: %s %s\n", test, message); - ++Errors; - } -} - - -void testOuterDecl(const char *test, const char *message, id collection) { - unsigned int counter = 0; - id x; - for (x in collection) - ++counter; - if (counter != [collection count]) { - printf("** failed: %s %s\n", test, message); - ++Errors; - } -} -void testInnerExpression(const char *test, const char *message, id collection) { - unsigned int counter = 0; - for (id __unused x in [collection self]) - ++counter; - if (counter != [collection count]) { - printf("** failed: %s %s\n", test, message); - ++Errors; - } -} -void testOuterExpression(const char *test, const char *message, id collection) { - unsigned int counter = 0; - id x; - for (x in [collection self]) - ++counter; - if (counter != [collection count]) { - printf("** failed: %s %s\n", test, message); - ++Errors; - } -} - -void testExpressions(const char *message, id collection) { - testInnerDecl("inner", message, collection); - testOuterDecl("outer", message, collection); - testInnerExpression("outer expression", message, collection); - testOuterExpression("outer expression", message, collection); -} - - -int main() { - PUSH_POOL { - testCollections("nil", nil, nil); - testCollections("empty", [NSArray array], [NSSet set]); - makeReferences(100); - testCollections("100 item", ReferenceArray, ReferenceSet); - testExpressions("array", ReferenceArray); - testBreaks(ReferenceArray); - testContinue(ReferenceArray); - if (Errors == 0) succeed(__FILE__); - else fail("foreach %d errors detected\n", Errors); - } POP_POOL; - exit(Errors); -} diff --git a/test/forward.m b/test/forward.m deleted file mode 100644 index 5f3ba1a..0000000 --- a/test/forward.m +++ /dev/null @@ -1,1002 +0,0 @@ -// TEST_CONFIG MEM=mrc,gc -// TEST_CFLAGS -Wno-deprecated-declarations - -#include "test.h" - -#if __cplusplus && !__clang__ - -int main() -{ - // llvm-g++ is confused by @selector(foo::) and will never be fixed - succeed(__FILE__); -} - -#else - -#include -#include - -id ID_RESULT = (id)0x12345678; -long long LL_RESULT = __LONG_LONG_MAX__ - 2LL*__INT_MAX__; -double FP_RESULT = __DBL_MIN__ + __DBL_EPSILON__; -long double LFP_RESULT = __LDBL_MIN__ + __LDBL_EPSILON__; -// STRET_RESULT in test.h - - -static int state = 0; -static id receiver; - -OBJC_ROOT_CLASS -@interface Super { id isa; } @end - -@interface Super (Forwarded) -+(id)idret: - (long)i1 :(long)i2 :(long)i3 :(long)i4 :(long)i5 :(long)i6 :(long)i7 :(long)i8 :(long)i9 :(long)i10 :(long)i11 :(long)i12 :(long)i13 :(double)f1 :(double)f2 :(double)f3 :(double)f4 :(double)f5 :(double)f6 :(double)f7 :(double)f8 :(double)f9 :(double)f10 :(double)f11 :(double)f12 :(double)f13 :(double)f14 :(double)f15; - -+(id)idre2: - (long)i1 :(long)i2 :(long)i3 :(long)i4 :(long)i5 :(long)i6 :(long)i7 :(long)i8 :(long)i9 :(long)i10 :(long)i11 :(long)i12 :(long)i13 :(double)f1 :(double)f2 :(double)f3 :(double)f4 :(double)f5 :(double)f6 :(double)f7 :(double)f8 :(double)f9 :(double)f10 :(double)f11 :(double)f12 :(double)f13 :(double)f14 :(double)f15; - -+(id)idre3: - (long)i1 :(long)i2 :(long)i3 :(long)i4 :(long)i5 :(long)i6 :(long)i7 :(long)i8 :(long)i9 :(long)i10 :(long)i11 :(long)i12 :(long)i13 :(double)f1 :(double)f2 :(double)f3 :(double)f4 :(double)f5 :(double)f6 :(double)f7 :(double)f8 :(double)f9 :(double)f10 :(double)f11 :(double)f12 :(double)f13 :(double)f14 :(double)f15; - -+(long long)llret: - (long)i1 :(long)i2 :(long)i3 :(long)i4 :(long)i5 :(long)i6 :(long)i7 :(long)i8 :(long)i9 :(long)i10 :(long)i11 :(long)i12 :(long)i13 :(double)f1 :(double)f2 :(double)f3 :(double)f4 :(double)f5 :(double)f6 :(double)f7 :(double)f8 :(double)f9 :(double)f10 :(double)f11 :(double)f12 :(double)f13 :(double)f14 :(double)f15; - -+(long long)llre2: - (long)i1 :(long)i2 :(long)i3 :(long)i4 :(long)i5 :(long)i6 :(long)i7 :(long)i8 :(long)i9 :(long)i10 :(long)i11 :(long)i12 :(long)i13 :(double)f1 :(double)f2 :(double)f3 :(double)f4 :(double)f5 :(double)f6 :(double)f7 :(double)f8 :(double)f9 :(double)f10 :(double)f11 :(double)f12 :(double)f13 :(double)f14 :(double)f15; - -+(long long)llre3: - (long)i1 :(long)i2 :(long)i3 :(long)i4 :(long)i5 :(long)i6 :(long)i7 :(long)i8 :(long)i9 :(long)i10 :(long)i11 :(long)i12 :(long)i13 :(double)f1 :(double)f2 :(double)f3 :(double)f4 :(double)f5 :(double)f6 :(double)f7 :(double)f8 :(double)f9 :(double)f10 :(double)f11 :(double)f12 :(double)f13 :(double)f14 :(double)f15; - -+(struct stret)stret: - (long)i1 :(long)i2 :(long)i3 :(long)i4 :(long)i5 :(long)i6 :(long)i7 :(long)i8 :(long)i9 :(long)i10 :(long)i11 :(long)i12 :(long)i13 :(double)f1 :(double)f2 :(double)f3 :(double)f4 :(double)f5 :(double)f6 :(double)f7 :(double)f8 :(double)f9 :(double)f10 :(double)f11 :(double)f12 :(double)f13 :(double)f14 :(double)f15; - -+(struct stret)stre2: - (long)i1 :(long)i2 :(long)i3 :(long)i4 :(long)i5 :(long)i6 :(long)i7 :(long)i8 :(long)i9 :(long)i10 :(long)i11 :(long)i12 :(long)i13 :(double)f1 :(double)f2 :(double)f3 :(double)f4 :(double)f5 :(double)f6 :(double)f7 :(double)f8 :(double)f9 :(double)f10 :(double)f11 :(double)f12 :(double)f13 :(double)f14 :(double)f15; - -+(struct stret)stre3: - (long)i1 :(long)i2 :(long)i3 :(long)i4 :(long)i5 :(long)i6 :(long)i7 :(long)i8 :(long)i9 :(long)i10 :(long)i11 :(long)i12 :(long)i13 :(double)f1 :(double)f2 :(double)f3 :(double)f4 :(double)f5 :(double)f6 :(double)f7 :(double)f8 :(double)f9 :(double)f10 :(double)f11 :(double)f12 :(double)f13 :(double)f14 :(double)f15; - -+(double)fpret: - (long)i1 :(long)i2 :(long)i3 :(long)i4 :(long)i5 :(long)i6 :(long)i7 :(long)i8 :(long)i9 :(long)i10 :(long)i11 :(long)i12 :(long)i13 :(double)f1 :(double)f2 :(double)f3 :(double)f4 :(double)f5 :(double)f6 :(double)f7 :(double)f8 :(double)f9 :(double)f10 :(double)f11 :(double)f12 :(double)f13 :(double)f14 :(double)f15; - -+(double)fpre2: - (long)i1 :(long)i2 :(long)i3 :(long)i4 :(long)i5 :(long)i6 :(long)i7 :(long)i8 :(long)i9 :(long)i10 :(long)i11 :(long)i12 :(long)i13 :(double)f1 :(double)f2 :(double)f3 :(double)f4 :(double)f5 :(double)f6 :(double)f7 :(double)f8 :(double)f9 :(double)f10 :(double)f11 :(double)f12 :(double)f13 :(double)f14 :(double)f15; - -+(double)fpre3: - (long)i1 :(long)i2 :(long)i3 :(long)i4 :(long)i5 :(long)i6 :(long)i7 :(long)i8 :(long)i9 :(long)i10 :(long)i11 :(long)i12 :(long)i13 :(double)f1 :(double)f2 :(double)f3 :(double)f4 :(double)f5 :(double)f6 :(double)f7 :(double)f8 :(double)f9 :(double)f10 :(double)f11 :(double)f12 :(double)f13 :(double)f14 :(double)f15; - -@end - - -long long forward_handler(id self, SEL _cmd, long i1, long i2, long i3, long i4, long i5, long i6, long i7, long i8, long i9, long i10, long i11, long i12, long i13, double f1, double f2, double f3, double f4, double f5, double f6, double f7, double f8, double f9, double f10, double f11, double f12, double f13, double f14, double f15) -{ -#if __arm64__ - void *struct_addr; - __asm__ volatile("mov %0, x8" : "=r" (struct_addr) : : "x8"); -#endif - - testassert(self == receiver); - - testassert(i1 == 1); - testassert(i2 == 2); - testassert(i3 == 3); - testassert(i4 == 4); - testassert(i5 == 5); - testassert(i6 == 6); - testassert(i7 == 7); - testassert(i8 == 8); - testassert(i9 == 9); - testassert(i10 == 10); - testassert(i11 == 11); - testassert(i12 == 12); - testassert(i13 == 13); - - testassert(f1 == 1.0); - testassert(f2 == 2.0); - testassert(f3 == 3.0); - testassert(f4 == 4.0); - testassert(f5 == 5.0); - testassert(f6 == 6.0); - testassert(f7 == 7.0); - testassert(f8 == 8.0); - testassert(f9 == 9.0); - testassert(f10 == 10.0); - testassert(f11 == 11.0); - testassert(f12 == 12.0); - testassert(f13 == 13.0); - testassert(f14 == 14.0); - testassert(f15 == 15.0); - - if (_cmd == @selector(idret::::::::::::::::::::::::::::) || - _cmd == @selector(idre2::::::::::::::::::::::::::::) || - _cmd == @selector(idre3::::::::::::::::::::::::::::)) - { - union { - id idval; - long long llval; - } result; - testassert(state == 11); - state = 12; - result.idval = ID_RESULT; - return result.llval; - } - else if (_cmd == @selector(llret::::::::::::::::::::::::::::) || - _cmd == @selector(llre2::::::::::::::::::::::::::::) || - _cmd == @selector(llre3::::::::::::::::::::::::::::)) - { - testassert(state == 13); - state = 14; - return LL_RESULT; - } - else if (_cmd == @selector(fpret::::::::::::::::::::::::::::) || - _cmd == @selector(fpre2::::::::::::::::::::::::::::) || - _cmd == @selector(fpre3::::::::::::::::::::::::::::)) - { - testassert(state == 15); - state = 16; -#if defined(__i386__) - __asm__ volatile("fldl %0" : : "m" (FP_RESULT)); -#elif defined(__x86_64__) - __asm__ volatile("movsd %0, %%xmm0" : : "m" (FP_RESULT)); -#elif defined(__arm__) - union { - double fpval; - long long llval; - } result; - result.fpval = FP_RESULT; - return result.llval; -#elif defined(__arm64__) - __asm__ volatile("ldr d0, %0" : : "m" (FP_RESULT)); -#else -# error unknown architecture -#endif - return 0; - } - else if (_cmd == @selector(stret::::::::::::::::::::::::::::) || - _cmd == @selector(stre2::::::::::::::::::::::::::::) || - _cmd == @selector(stre3::::::::::::::::::::::::::::)) - { -#if __i386__ || __x86_64__ || __arm__ - fail("stret message sent to non-stret forward_handler"); -#elif __arm64__ - testassert(state == 17); - state = 18; - memcpy(struct_addr, &STRET_RESULT, sizeof(STRET_RESULT)); - return 0; -#else -# error unknown architecture -#endif - } - else { - fail("unknown selector %s in forward_handler", sel_getName(_cmd)); - } -} - - -struct stret forward_stret_handler(id self, SEL _cmd, long i1, long i2, long i3, long i4, long i5, long i6, long i7, long i8, long i9, long i10, long i11, long i12, long i13, double f1, double f2, double f3, double f4, double f5, double f6, double f7, double f8, double f9, double f10, double f11, double f12, double f13, double f14, double f15) -{ - testassert(self == receiver); - - testassert(i1 == 1); - testassert(i2 == 2); - testassert(i3 == 3); - testassert(i4 == 4); - testassert(i5 == 5); - testassert(i6 == 6); - testassert(i7 == 7); - testassert(i8 == 8); - testassert(i9 == 9); - testassert(i10 == 10); - testassert(i11 == 11); - testassert(i12 == 12); - testassert(i13 == 13); - - testassert(f1 == 1.0); - testassert(f2 == 2.0); - testassert(f3 == 3.0); - testassert(f4 == 4.0); - testassert(f5 == 5.0); - testassert(f6 == 6.0); - testassert(f7 == 7.0); - testassert(f8 == 8.0); - testassert(f9 == 9.0); - testassert(f10 == 10.0); - testassert(f11 == 11.0); - testassert(f12 == 12.0); - testassert(f13 == 13.0); - testassert(f14 == 14.0); - testassert(f15 == 15.0); - - if (_cmd == @selector(idret::::::::::::::::::::::::::::) || - _cmd == @selector(idre2::::::::::::::::::::::::::::) || - _cmd == @selector(idre3::::::::::::::::::::::::::::) || - _cmd == @selector(llret::::::::::::::::::::::::::::) || - _cmd == @selector(llre2::::::::::::::::::::::::::::) || - _cmd == @selector(llre3::::::::::::::::::::::::::::) || - _cmd == @selector(fpret::::::::::::::::::::::::::::) || - _cmd == @selector(fpre2::::::::::::::::::::::::::::) || - _cmd == @selector(fpre3::::::::::::::::::::::::::::)) - { - fail("non-stret selector %s sent to forward_stret_handler", sel_getName(_cmd)); - } - else if (_cmd == @selector(stret::::::::::::::::::::::::::::) || - _cmd == @selector(stre2::::::::::::::::::::::::::::) || - _cmd == @selector(stre3::::::::::::::::::::::::::::)) - { - testassert(state == 17); - state = 18; - return STRET_RESULT; - } - else { - fail("unknown selector %s in forward_stret_handler", sel_getName(_cmd)); - } - -} - - -@implementation Super -+(void)initialize { } -+(id)class { return self; } - -#if __OBJC2__ -// forward:: not supported -#else --(long long) forward:(SEL)sel :(marg_list)args -{ - char *p; - uintptr_t *gp; - double *fp; - struct stret *struct_addr; - -#if defined(__i386__) - struct_addr = ((struct stret **)args)[-1]; -#elif defined(__x86_64__) - struct_addr = *(struct stret **)((char *)args + 8*16+4*8); -#elif defined(__arm__) - struct_addr = *(struct stret **)((char *)args + 0); -#else -# error unknown architecture -#endif - - testassert(self == receiver); - testassert(_cmd == sel_registerName("forward::")); - - p = (char *)args; -#if defined(__x86_64__) - p += 8*16 + 4*8; // skip over xmm and linkage - if (sel == @selector(stret::::::::::::::::::::::::::::) || - sel == @selector(stre2::::::::::::::::::::::::::::) || - sel == @selector(stre3::::::::::::::::::::::::::::)) - { - p += sizeof(void *); // struct return - } -#elif defined(__i386__) - // nothing to do -#elif defined(__arm__) - if (sel == @selector(stret::::::::::::::::::::::::::::) || - sel == @selector(stre2::::::::::::::::::::::::::::) || - sel == @selector(stre3::::::::::::::::::::::::::::)) - { - p += sizeof(void *); // struct return; - } -#else -# error unknown architecture -#endif - gp = (uintptr_t *)p; - testassert(*gp++ == (uintptr_t)self); - testassert(*gp++ == (uintptr_t)(void *)sel); - testassert(*gp++ == 1); - testassert(*gp++ == 2); - testassert(*gp++ == 3); - testassert(*gp++ == 4); - testassert(*gp++ == 5); - testassert(*gp++ == 6); - testassert(*gp++ == 7); - testassert(*gp++ == 8); - testassert(*gp++ == 9); - testassert(*gp++ == 10); - testassert(*gp++ == 11); - testassert(*gp++ == 12); - testassert(*gp++ == 13); - -#if defined(__i386__) || defined(__arm__) - - fp = (double *)gp; - testassert(*fp++ == 1.0); - testassert(*fp++ == 2.0); - testassert(*fp++ == 3.0); - testassert(*fp++ == 4.0); - testassert(*fp++ == 5.0); - testassert(*fp++ == 6.0); - testassert(*fp++ == 7.0); - testassert(*fp++ == 8.0); - testassert(*fp++ == 9.0); - testassert(*fp++ == 10.0); - testassert(*fp++ == 11.0); - testassert(*fp++ == 12.0); - testassert(*fp++ == 13.0); - testassert(*fp++ == 14.0); - testassert(*fp++ == 15.0); - -#elif defined(__x86_64__) - - fp = (double *)args; // xmm, double-wide - testassert(*fp++ == 1.0); fp++; - testassert(*fp++ == 2.0); fp++; - testassert(*fp++ == 3.0); fp++; - testassert(*fp++ == 4.0); fp++; - testassert(*fp++ == 5.0); fp++; - testassert(*fp++ == 6.0); fp++; - testassert(*fp++ == 7.0); fp++; - testassert(*fp++ == 8.0); fp++; - fp = (double *)gp; - testassert(*fp++ == 9.0); - testassert(*fp++ == 10.0); - testassert(*fp++ == 11.0); - testassert(*fp++ == 12.0); - testassert(*fp++ == 13.0); - testassert(*fp++ == 14.0); - testassert(*fp++ == 15.0); - -#else -# error unknown architecture -#endif - - if (sel == @selector(idret::::::::::::::::::::::::::::) || - sel == @selector(idre2::::::::::::::::::::::::::::) || - sel == @selector(idre3::::::::::::::::::::::::::::)) - { - union { - id idval; - long long llval; - } result; - testassert(state == 1); - state = 2; - result.idval = ID_RESULT; - return result.llval; - } else if (sel == @selector(llret::::::::::::::::::::::::::::) || - sel == @selector(llre2::::::::::::::::::::::::::::) || - sel == @selector(llre3::::::::::::::::::::::::::::)) - { - testassert(state == 3); - state = 4; - return LL_RESULT; - } else if (sel == @selector(fpret::::::::::::::::::::::::::::) || - sel == @selector(fpre2::::::::::::::::::::::::::::) || - sel == @selector(fpre3::::::::::::::::::::::::::::)) - { - testassert(state == 5); - state = 6; -#if defined(__i386__) - __asm__ volatile("fldl %0" : : "m" (FP_RESULT)); -#elif defined(__x86_64__) - __asm__ volatile("movsd %0, %%xmm0" : : "m" (FP_RESULT)); -#elif defined(__arm__) - union { - double fpval; - long long llval; - } result; - result.fpval = FP_RESULT; - return result.llval; -#else -# error unknown architecture -#endif - return 0; - } else if (sel == @selector(stret::::::::::::::::::::::::::::) || - sel == @selector(stre2::::::::::::::::::::::::::::) || - sel == @selector(stre3::::::::::::::::::::::::::::)) - { - testassert(state == 7); - state = 8; - *struct_addr = STRET_RESULT; - return 0; - } else { - fail("unknown selector %s in forward::", sel_getName(sel)); - } - return 0; -} - -#endif - -@end - -typedef id (*id_fn_t)(id self, SEL _cmd, long i1, long i2, long i3, long i4, long i5, long i6, long i7, long i8, long i9, long i10, long i11, long i12, long i13, double f1, double f2, double f3, double f4, double f5, double f6, double f7, double f8, double f9, double f10, double f11, double f12, double f13, double f14, double f15); - -typedef long long (*ll_fn_t)(id self, SEL _cmd, long i1, long i2, long i3, long i4, long i5, long i6, long i7, long i8, long i9, long i10, long i11, long i12, long i13, double f1, double f2, double f3, double f4, double f5, double f6, double f7, double f8, double f9, double f10, double f11, double f12, double f13, double f14, double f15); - -typedef double (*fp_fn_t)(id self, SEL _cmd, long i1, long i2, long i3, long i4, long i5, long i6, long i7, long i8, long i9, long i10, long i11, long i12, long i13, double f1, double f2, double f3, double f4, double f5, double f6, double f7, double f8, double f9, double f10, double f11, double f12, double f13, double f14, double f15); - -typedef struct stret (*st_fn_t)(id self, SEL _cmd, long i1, long i2, long i3, long i4, long i5, long i6, long i7, long i8, long i9, long i10, long i11, long i12, long i13, double f1, double f2, double f3, double f4, double f5, double f6, double f7, double f8, double f9, double f10, double f11, double f12, double f13, double f14, double f15); - -#if __x86_64__ -typedef struct stret * (*fake_st_fn_t)(struct stret *, id self, SEL _cmd, long i1, long i2, long i3, long i4, long i5, long i6, long i7, long i8, long i9, long i10, long i11, long i12, long i13, double f1, double f2, double f3, double f4, double f5, double f6, double f7, double f8, double f9, double f10, double f11, double f12, double f13, double f14, double f15); -#endif - -__BEGIN_DECLS -extern void *getSP(void); -__END_DECLS - -#if defined(__x86_64__) - asm(".text \n _getSP: movq %rsp, %rax \n retq \n"); -#elif defined(__i386__) - asm(".text \n _getSP: movl %esp, %eax \n ret \n"); -#elif defined(__arm__) - asm(".text \n .thumb \n .thumb_func _getSP \n " - "_getSP: mov r0, sp \n bx lr \n"); -#elif defined(__arm64__) - asm(".text \n _getSP: mov x0, sp \n ret \n"); -#else -# error unknown architecture -#endif - -int main() -{ - id idval; - long long llval; - struct stret stval; -#if __x86_64__ - struct stret *stptr; -#endif - double fpval; - void *sp1 = (void*)1; - void *sp2 = (void*)2; - - st_fn_t stret_fwd; -#if __arm64__ - stret_fwd = (st_fn_t)_objc_msgForward; -#else - stret_fwd = (st_fn_t)_objc_msgForward_stret; -#endif - - receiver = [Super class]; - -#if __OBJC2__ - // forward:: not supported -#else - // Test default forward handler - - state = 1; - sp1 = getSP(); - idval = [Super idret:1:2:3:4:5:6:7:8:9:10:11:12:13:1.0:2.0:3.0:4.0:5.0:6.0:7.0:8.0:9.0:10.0:11.0:12.0:13.0:14.0:15.0]; - sp2 = getSP(); - testassert(sp1 == sp2); - testassert(state == 2); - testassert(idval == ID_RESULT); - - state = 3; - sp1 = getSP(); - llval = [Super llret:1:2:3:4:5:6:7:8:9:10:11:12:13:1.0:2.0:3.0:4.0:5.0:6.0:7.0:8.0:9.0:10.0:11.0:12.0:13.0:14.0:15.0]; - sp2 = getSP(); - testassert(sp1 == sp2); - testassert(state == 4); - testassert(llval == LL_RESULT); - - state = 5; - sp1 = getSP(); - fpval = [Super fpret:1:2:3:4:5:6:7:8:9:10:11:12:13:1.0:2.0:3.0:4.0:5.0:6.0:7.0:8.0:9.0:10.0:11.0:12.0:13.0:14.0:15.0]; - sp2 = getSP(); - testassert(sp1 == sp2); - testassert(state == 6); - testassert(fpval == FP_RESULT); - - state = 7; - sp1 = getSP(); - stval = [Super stret:1:2:3:4:5:6:7:8:9:10:11:12:13:1.0:2.0:3.0:4.0:5.0:6.0:7.0:8.0:9.0:10.0:11.0:12.0:13.0:14.0:15.0]; - sp2 = getSP(); - testassert(sp1 == sp2); - testassert(state == 8); - testassert(stret_equal(stval, STRET_RESULT)); - -#if __x86_64__ - // check stret return register - state = 7; - sp1 = getSP(); - stptr = ((fake_st_fn_t)objc_msgSend_stret)(&stval, [Super class], @selector(stret::::::::::::::::::::::::::::), 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0); - sp2 = getSP(); - testassert(sp1 == sp2); - testassert(state == 8); - testassert(stret_equal(stval, STRET_RESULT)); - testassert(stptr == &stval); -#endif - - - // Test default forward handler, cached - - state = 1; - sp1 = getSP(); - idval = [Super idret:1:2:3:4:5:6:7:8:9:10:11:12:13:1.0:2.0:3.0:4.0:5.0:6.0:7.0:8.0:9.0:10.0:11.0:12.0:13.0:14.0:15.0]; - sp2 = getSP(); - testassert(sp1 == sp2); - testassert(state == 2); - testassert(idval == ID_RESULT); - - state = 3; - sp1 = getSP(); - llval = [Super llret:1:2:3:4:5:6:7:8:9:10:11:12:13:1.0:2.0:3.0:4.0:5.0:6.0:7.0:8.0:9.0:10.0:11.0:12.0:13.0:14.0:15.0]; - sp2 = getSP(); - testassert(sp1 == sp2); - testassert(state == 4); - testassert(llval == LL_RESULT); - - state = 5; - sp1 = getSP(); - fpval = [Super fpret:1:2:3:4:5:6:7:8:9:10:11:12:13:1.0:2.0:3.0:4.0:5.0:6.0:7.0:8.0:9.0:10.0:11.0:12.0:13.0:14.0:15.0]; - sp2 = getSP(); - testassert(sp1 == sp2); - testassert(state == 6); - testassert(fpval == FP_RESULT); - - state = 7; - sp1 = getSP(); - stval = [Super stret:1:2:3:4:5:6:7:8:9:10:11:12:13:1.0:2.0:3.0:4.0:5.0:6.0:7.0:8.0:9.0:10.0:11.0:12.0:13.0:14.0:15.0]; - sp2 = getSP(); - testassert(sp1 == sp2); - testassert(state == 8); - testassert(stret_equal(stval, STRET_RESULT)); - -#if __x86_64__ - // check stret return register - state = 7; - sp1 = getSP(); - stptr = ((fake_st_fn_t)objc_msgSend_stret)(&stval, [Super class], @selector(stret::::::::::::::::::::::::::::), 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0); - sp2 = getSP(); - testassert(sp1 == sp2); - testassert(state == 8); - testassert(stret_equal(stval, STRET_RESULT)); - testassert(stptr == &stval); -#endif - - - // Test default forward handler, uncached but fixed-up - - _objc_flush_caches(nil); - - state = 1; - sp1 = getSP(); - idval = [Super idret:1:2:3:4:5:6:7:8:9:10:11:12:13:1.0:2.0:3.0:4.0:5.0:6.0:7.0:8.0:9.0:10.0:11.0:12.0:13.0:14.0:15.0]; - sp2 = getSP(); - testassert(sp1 == sp2); - testassert(state == 2); - testassert(idval == ID_RESULT); - - state = 3; - sp1 = getSP(); - llval = [Super llret:1:2:3:4:5:6:7:8:9:10:11:12:13:1.0:2.0:3.0:4.0:5.0:6.0:7.0:8.0:9.0:10.0:11.0:12.0:13.0:14.0:15.0]; - sp2 = getSP(); - testassert(sp1 == sp2); - testassert(state == 4); - testassert(llval == LL_RESULT); - - state = 5; - sp1 = getSP(); - fpval = [Super fpret:1:2:3:4:5:6:7:8:9:10:11:12:13:1.0:2.0:3.0:4.0:5.0:6.0:7.0:8.0:9.0:10.0:11.0:12.0:13.0:14.0:15.0]; - sp2 = getSP(); - testassert(sp1 == sp2); - testassert(state == 6); - testassert(fpval == FP_RESULT); - - state = 7; - sp1 = getSP(); - stval = [Super stret:1:2:3:4:5:6:7:8:9:10:11:12:13:1.0:2.0:3.0:4.0:5.0:6.0:7.0:8.0:9.0:10.0:11.0:12.0:13.0:14.0:15.0]; - sp2 = getSP(); - testassert(sp1 == sp2); - testassert(state == 8); - testassert(stret_equal(stval, STRET_RESULT)); - -#if __x86_64__ - // check stret return register - state = 7; - sp1 = getSP(); - stptr = ((fake_st_fn_t)objc_msgSend_stret)(&stval, [Super class], @selector(stret::::::::::::::::::::::::::::), 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0); - sp2 = getSP(); - testassert(sp1 == sp2); - testassert(state == 8); - testassert(stret_equal(stval, STRET_RESULT)); - testassert(stptr == &stval); -#endif - - - // Test manual forwarding - - state = 1; - sp1 = getSP(); - idval = ((id_fn_t)_objc_msgForward)(receiver, @selector(idre2::::::::::::::::::::::::::::), 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0); - sp2 = getSP(); - testassert(sp1 == sp2); - testassert(state == 2); - testassert(idval == ID_RESULT); - - state = 3; - sp1 = getSP(); - llval = ((ll_fn_t)_objc_msgForward)(receiver, @selector(llre2::::::::::::::::::::::::::::), 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0); - sp2 = getSP(); - testassert(sp1 == sp2); - testassert(state == 4); - testassert(llval == LL_RESULT); - - state = 5; - sp1 = getSP(); - fpval = ((fp_fn_t)_objc_msgForward)(receiver, @selector(fpre2::::::::::::::::::::::::::::), 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0); - sp2 = getSP(); - testassert(sp1 == sp2); - testassert(state == 6); - testassert(fpval == FP_RESULT); - - state = 7; - sp1 = getSP(); - stval = stret_fwd(receiver, @selector(stre2::::::::::::::::::::::::::::), 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0); - sp2 = getSP(); - testassert(sp1 == sp2); - testassert(state == 8); - testassert(stret_equal(stval, STRET_RESULT)); - -#if __x86_64__ - // check stret return register - state = 7; - sp1 = getSP(); - stptr = ((fake_st_fn_t)_objc_msgForward_stret)(&stval, receiver, @selector(stre2::::::::::::::::::::::::::::), 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0); - sp2 = getSP(); - testassert(sp1 == sp2); - testassert(state == 8); - testassert(stret_equal(stval, STRET_RESULT)); - testassert(stptr == &stval); -#endif - - - // Test manual forwarding, cached - - state = 1; - sp1 = getSP(); - idval = ((id_fn_t)_objc_msgForward)(receiver, @selector(idre2::::::::::::::::::::::::::::), 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0); - sp2 = getSP(); - testassert(sp1 == sp2); - testassert(state == 2); - testassert(idval == ID_RESULT); - - state = 3; - sp1 = getSP(); - llval = ((ll_fn_t)_objc_msgForward)(receiver, @selector(llre2::::::::::::::::::::::::::::), 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0); - sp2 = getSP(); - testassert(sp1 == sp2); - testassert(state == 4); - testassert(llval == LL_RESULT); - - state = 5; - sp1 = getSP(); - fpval = ((fp_fn_t)_objc_msgForward)(receiver, @selector(fpre2::::::::::::::::::::::::::::), 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0); - sp2 = getSP(); - testassert(sp1 == sp2); - testassert(state == 6); - testassert(fpval == FP_RESULT); - - state = 7; - sp1 = getSP(); - stval = stret_fwd(receiver, @selector(stre2::::::::::::::::::::::::::::), 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0); - sp2 = getSP(); - testassert(sp1 == sp2); - testassert(state == 8); - testassert(stret_equal(stval, STRET_RESULT)); - -#if __x86_64__ - // check stret return register - state = 7; - sp1 = getSP(); - stptr = ((fake_st_fn_t)_objc_msgForward_stret)(&stval, receiver, @selector(stre2::::::::::::::::::::::::::::), 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0); - sp2 = getSP(); - testassert(sp1 == sp2); - testassert(state == 8); - testassert(stret_equal(stval, STRET_RESULT)); - testassert(stptr == &stval); -#endif - - - // Test manual forwarding, uncached but fixed-up - - _objc_flush_caches(nil); - - state = 1; - sp1 = getSP(); - idval = ((id_fn_t)_objc_msgForward)(receiver, @selector(idre2::::::::::::::::::::::::::::), 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0); - sp2 = getSP(); - testassert(sp1 == sp2); - testassert(state == 2); - testassert(idval == ID_RESULT); - - state = 3; - sp1 = getSP(); - llval = ((ll_fn_t)_objc_msgForward)(receiver, @selector(llre2::::::::::::::::::::::::::::), 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0); - sp2 = getSP(); - testassert(sp1 == sp2); - testassert(state == 4); - testassert(llval == LL_RESULT); - - state = 5; - sp1 = getSP(); - fpval = ((fp_fn_t)_objc_msgForward)(receiver, @selector(fpre2::::::::::::::::::::::::::::), 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0); - sp2 = getSP(); - testassert(sp1 == sp2); - testassert(state == 6); - testassert(fpval == FP_RESULT); - - state = 7; - sp1 = getSP(); - stval = stret_fwd(receiver, @selector(stre2::::::::::::::::::::::::::::), 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0); - sp2 = getSP(); - testassert(sp1 == sp2); - testassert(state == 8); - testassert(stret_equal(stval, STRET_RESULT)); - -#if __x86_64__ - // check stret return register - state = 7; - sp1 = getSP(); - stptr = ((fake_st_fn_t)_objc_msgForward_stret)(&stval, receiver, @selector(stre2::::::::::::::::::::::::::::), 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0); - sp2 = getSP(); - testassert(sp1 == sp2); - testassert(state == 8); - testassert(stret_equal(stval, STRET_RESULT)); - testassert(stptr == &stval); -#endif - -// !__OBJC2__ -#endif - - - // Test user-defined forward handler - - objc_setForwardHandler((void*)&forward_handler, (void*)&forward_stret_handler); - - state = 11; - sp1 = getSP(); - idval = [Super idre3:1:2:3:4:5:6:7:8:9:10:11:12:13:1.0:2.0:3.0:4.0:5.0:6.0:7.0:8.0:9.0:10.0:11.0:12.0:13.0:14.0:15.0]; - sp2 = getSP(); - testassert(sp1 == sp2); - testassert(state == 12); - testassert(idval == ID_RESULT); - - state = 13; - sp1 = getSP(); - llval = [Super llre3:1:2:3:4:5:6:7:8:9:10:11:12:13:1.0:2.0:3.0:4.0:5.0:6.0:7.0:8.0:9.0:10.0:11.0:12.0:13.0:14.0:15.0]; - sp2 = getSP(); - testassert(sp1 == sp2); - testassert(state == 14); - testassert(llval == LL_RESULT); - - state = 15; - sp1 = getSP(); - fpval = [Super fpre3:1:2:3:4:5:6:7:8:9:10:11:12:13:1.0:2.0:3.0:4.0:5.0:6.0:7.0:8.0:9.0:10.0:11.0:12.0:13.0:14.0:15.0]; - sp2 = getSP(); - testassert(sp1 == sp2); - testassert(state == 16); - testassert(fpval == FP_RESULT); - - state = 17; - sp1 = getSP(); - stval = [Super stre3:1:2:3:4:5:6:7:8:9:10:11:12:13:1.0:2.0:3.0:4.0:5.0:6.0:7.0:8.0:9.0:10.0:11.0:12.0:13.0:14.0:15.0]; - sp2 = getSP(); - testassert(sp1 == sp2); - testassert(state == 18); - testassert(stret_equal(stval, STRET_RESULT)); - -#if __x86_64__ - // check stret return register - state = 17; - sp1 = getSP(); - stptr = ((fake_st_fn_t)objc_msgSend_stret)(&stval, [Super class], @selector(stre3::::::::::::::::::::::::::::), 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0); - sp2 = getSP(); - testassert(sp1 == sp2); - testassert(state == 18); - testassert(stret_equal(stval, STRET_RESULT)); - testassert(stptr == &stval); -#endif - - - // Test user-defined forward handler, cached - - state = 11; - sp1 = getSP(); - idval = [Super idre3:1:2:3:4:5:6:7:8:9:10:11:12:13:1.0:2.0:3.0:4.0:5.0:6.0:7.0:8.0:9.0:10.0:11.0:12.0:13.0:14.0:15.0]; - sp2 = getSP(); - testassert(sp1 == sp2); - testassert(state == 12); - testassert(idval == ID_RESULT); - - state = 13; - sp1 = getSP(); - llval = [Super llre3:1:2:3:4:5:6:7:8:9:10:11:12:13:1.0:2.0:3.0:4.0:5.0:6.0:7.0:8.0:9.0:10.0:11.0:12.0:13.0:14.0:15.0]; - sp2 = getSP(); - testassert(sp1 == sp2); - testassert(state == 14); - testassert(llval == LL_RESULT); - - state = 15; - sp1 = getSP(); - fpval = [Super fpre3:1:2:3:4:5:6:7:8:9:10:11:12:13:1.0:2.0:3.0:4.0:5.0:6.0:7.0:8.0:9.0:10.0:11.0:12.0:13.0:14.0:15.0]; - sp2 = getSP(); - testassert(sp1 == sp2); - testassert(state == 16); - testassert(fpval == FP_RESULT); - - state = 17; - sp1 = getSP(); - stval = [Super stre3:1:2:3:4:5:6:7:8:9:10:11:12:13:1.0:2.0:3.0:4.0:5.0:6.0:7.0:8.0:9.0:10.0:11.0:12.0:13.0:14.0:15.0]; - sp2 = getSP(); - testassert(sp1 == sp2); - testassert(state == 18); - testassert(stret_equal(stval, STRET_RESULT)); - -#if __x86_64__ - // check stret return register - state = 17; - sp1 = getSP(); - stptr = ((fake_st_fn_t)objc_msgSend_stret)(&stval, [Super class], @selector(stre3::::::::::::::::::::::::::::), 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0); - sp2 = getSP(); - testassert(sp1 == sp2); - testassert(state == 18); - testassert(stret_equal(stval, STRET_RESULT)); - testassert(stptr == &stval); -#endif - - - // Test user-defined forward handler, uncached but fixed-up - - _objc_flush_caches(nil); - - state = 11; - sp1 = getSP(); - idval = [Super idre3:1:2:3:4:5:6:7:8:9:10:11:12:13:1.0:2.0:3.0:4.0:5.0:6.0:7.0:8.0:9.0:10.0:11.0:12.0:13.0:14.0:15.0]; - sp2 = getSP(); - testassert(sp1 == sp2); - testassert(state == 12); - testassert(idval == ID_RESULT); - - state = 13; - sp1 = getSP(); - llval = [Super llre3:1:2:3:4:5:6:7:8:9:10:11:12:13:1.0:2.0:3.0:4.0:5.0:6.0:7.0:8.0:9.0:10.0:11.0:12.0:13.0:14.0:15.0]; - sp2 = getSP(); - testassert(sp1 == sp2); - testassert(state == 14); - testassert(llval == LL_RESULT); - - state = 15; - sp1 = getSP(); - fpval = [Super fpre3:1:2:3:4:5:6:7:8:9:10:11:12:13:1.0:2.0:3.0:4.0:5.0:6.0:7.0:8.0:9.0:10.0:11.0:12.0:13.0:14.0:15.0]; - sp2 = getSP(); - testassert(sp1 == sp2); - testassert(state == 16); - testassert(fpval == FP_RESULT); - - state = 17; - sp1 = getSP(); - stval = [Super stre3:1:2:3:4:5:6:7:8:9:10:11:12:13:1.0:2.0:3.0:4.0:5.0:6.0:7.0:8.0:9.0:10.0:11.0:12.0:13.0:14.0:15.0]; - sp2 = getSP(); - testassert(sp1 == sp2); - testassert(state == 18); - testassert(stret_equal(stval, STRET_RESULT)); - -#if __x86_64__ - // check stret return register - state = 17; - sp1 = getSP(); - stptr = ((fake_st_fn_t)objc_msgSend_stret)(&stval, [Super class], @selector(stre3::::::::::::::::::::::::::::), 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0); - sp2 = getSP(); - testassert(sp1 == sp2); - testassert(state == 18); - testassert(stret_equal(stval, STRET_RESULT)); - testassert(stptr == &stval); -#endif - - - - // Test user-defined forward handler, manual forwarding - - state = 11; - sp1 = getSP(); - idval = ((id_fn_t)_objc_msgForward)(receiver, @selector(idre2::::::::::::::::::::::::::::), 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0); - sp2 = getSP(); - testassert(sp1 == sp2); - testassert(state == 12); - testassert(idval == ID_RESULT); - - state = 13; - sp1 = getSP(); - llval = ((ll_fn_t)_objc_msgForward)(receiver, @selector(llre2::::::::::::::::::::::::::::), 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0); - sp2 = getSP(); - testassert(sp1 == sp2); - testassert(state == 14); - testassert(llval == LL_RESULT); - - state = 15; - sp1 = getSP(); - fpval = ((fp_fn_t)_objc_msgForward)(receiver, @selector(fpre2::::::::::::::::::::::::::::), 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0); - sp2 = getSP(); - testassert(sp1 == sp2); - testassert(state == 16); - testassert(fpval == FP_RESULT); - - state = 17; - sp1 = getSP(); - stval = stret_fwd(receiver, @selector(stre2::::::::::::::::::::::::::::), 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0); - sp2 = getSP(); - testassert(sp1 == sp2); - testassert(state == 18); - testassert(stret_equal(stval, STRET_RESULT)); - - - // Test user-defined forward handler, manual forwarding, cached - - state = 11; - sp1 = getSP(); - idval = ((id_fn_t)_objc_msgForward)(receiver, @selector(idre2::::::::::::::::::::::::::::), 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0); - sp2 = getSP(); - testassert(sp1 == sp2); - testassert(state == 12); - testassert(idval == ID_RESULT); - - state = 13; - sp1 = getSP(); - llval = ((ll_fn_t)_objc_msgForward)(receiver, @selector(llre2::::::::::::::::::::::::::::), 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0); - sp2 = getSP(); - testassert(sp1 == sp2); - testassert(state == 14); - testassert(llval == LL_RESULT); - - state = 15; - sp1 = getSP(); - fpval = ((fp_fn_t)_objc_msgForward)(receiver, @selector(fpre2::::::::::::::::::::::::::::), 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0); - sp2 = getSP(); - testassert(sp1 == sp2); - testassert(state == 16); - testassert(fpval == FP_RESULT); - - state = 17; - sp1 = getSP(); - stval = stret_fwd(receiver, @selector(stre2::::::::::::::::::::::::::::), 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0); - sp2 = getSP(); - testassert(sp1 == sp2); - testassert(state == 18); - testassert(stret_equal(stval, STRET_RESULT)); - - - // Test user-defined forward handler, manual forwarding, uncached but fixed-up - - _objc_flush_caches(nil); - - state = 11; - sp1 = getSP(); - idval = ((id_fn_t)_objc_msgForward)(receiver, @selector(idre2::::::::::::::::::::::::::::), 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0); - sp2 = getSP(); - testassert(sp1 == sp2); - testassert(state == 12); - testassert(idval == ID_RESULT); - - state = 13; - sp1 = getSP(); - llval = ((ll_fn_t)_objc_msgForward)(receiver, @selector(llre2::::::::::::::::::::::::::::), 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0); - sp2 = getSP(); - testassert(sp1 == sp2); - testassert(state == 14); - testassert(llval == LL_RESULT); - - state = 15; - sp1 = getSP(); - fpval = ((fp_fn_t)_objc_msgForward)(receiver, @selector(fpre2::::::::::::::::::::::::::::), 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0); - sp2 = getSP(); - testassert(sp1 == sp2); - testassert(state == 16); - testassert(fpval == FP_RESULT); - - state = 17; - sp1 = getSP(); - stval = stret_fwd(receiver, @selector(stre2::::::::::::::::::::::::::::), 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0); - sp2 = getSP(); - testassert(sp1 == sp2); - testassert(state == 18); - testassert(stret_equal(stval, STRET_RESULT)); - - - succeed(__FILE__); -} - -#endif diff --git a/test/forwardDefault.m b/test/forwardDefault.m deleted file mode 100644 index 2d8b968..0000000 --- a/test/forwardDefault.m +++ /dev/null @@ -1,31 +0,0 @@ -/* -no arc, rdar://11368528 confused by Foundation -TEST_CONFIG MEM=mrc,gc -TEST_CRASHES -TEST_RUN_OUTPUT -objc\[\d+\]: \+\[NSObject fakeorama\]: unrecognized selector sent to instance 0x[0-9a-fA-F]+ \(no message forward handler is installed\) -CRASHED: SIG(ILL|TRAP) -OR -not OBJC2 -objc\[\d+\]: NSObject: Does not recognize selector forward:: \(while forwarding fakeorama\) -CRASHED: SIG(ILL|TRAP) -END -*/ - -#include "test.h" - -#include - -@interface NSObject (Fake) --(void)fakeorama; -@end - -int main() -{ -#if !__OBJC2__ - fprintf(stderr, "not OBJC2\n"); -#endif - [NSObject fakeorama]; - fail("should have crashed"); -} - diff --git a/test/forwardDefaultStret.m b/test/forwardDefaultStret.m deleted file mode 100644 index 6c6229d..0000000 --- a/test/forwardDefaultStret.m +++ /dev/null @@ -1,31 +0,0 @@ -/* -no arc, rdar://11368528 confused by Foundation -TEST_CONFIG MEM=mrc,gc -TEST_CRASHES -TEST_RUN_OUTPUT -objc\[\d+\]: \+\[NSObject fakeorama\]: unrecognized selector sent to instance 0x[0-9a-fA-F]+ \(no message forward handler is installed\) -CRASHED: SIG(ILL|TRAP) -OR -not OBJC2 -objc\[\d+\]: NSObject: Does not recognize selector forward:: \(while forwarding fakeorama\) -CRASHED: SIG(ILL|TRAP) -END -*/ - -#include "test.h" - -#include - -@interface NSObject (Fake) --(struct stret)fakeorama; -@end - -int main() -{ -#if !__OBJC2__ - fprintf(stderr, "not OBJC2\n"); -#endif - [NSObject fakeorama]; - fail("should have crashed"); -} - diff --git a/test/future.h b/test/future.h deleted file mode 100644 index a48dc9a..0000000 --- a/test/future.h +++ /dev/null @@ -1,15 +0,0 @@ -#include "test.h" - -@interface Sub1 : TestRoot -+(int)method; -+(Class)classref; -@end - -@interface Sub2 : TestRoot -+(int)method; -+(Class)classref; -@end - -@interface SubSub1 : Sub1 @end - -@interface SubSub2 : Sub2 @end diff --git a/test/future.m b/test/future.m deleted file mode 100644 index e36f2f6..0000000 --- a/test/future.m +++ /dev/null @@ -1,82 +0,0 @@ -/* -TEST_BUILD - $C{COMPILE} $DIR/future0.m -o future0.dylib -dynamiclib - $C{COMPILE} $DIR/future2.m -x none future0.dylib -o future2.dylib -dynamiclib - $C{COMPILE} $DIR/future.m -x none future0.dylib -o future.out -END -*/ - -#include "test.h" - -#if __has_feature(objc_arc) - -int main() -{ - testwarn("rdar://10041403 future class API is not ARC-compatible"); - succeed(__FILE__); -} - - -#else - -#include -#include -#include -#include -#include "future.h" - -@implementation Sub2 -+(int)method { - return 2; -} -+(Class)classref { - return [Sub2 class]; -} -@end - -@implementation SubSub2 -+(int)method { - return 1 + [super method]; -} -@end - -int main() -{ - Class oldTestRoot; - Class oldSub1; - Class newSub1; - - // objc_getFutureClass with existing class - oldTestRoot = objc_getFutureClass("TestRoot"); - testassert(oldTestRoot == [TestRoot class]); - testassert(! _class_isFutureClass(oldTestRoot)); - - // objc_getFutureClass with missing class - oldSub1 = objc_getFutureClass("Sub1"); - testassert(oldSub1); - testassert(malloc_size(objc_unretainedPointer(oldSub1)) > 0); - testassert(objc_getClass("Sub1") == Nil); - testassert(_class_isFutureClass(oldSub1)); - testassert(0 == strcmp(class_getName(oldSub1), "Sub1")); - testassert(object_getClass(oldSub1) == Nil); // CF expects this - - // objc_getFutureClass a second time - testassert(oldSub1 == objc_getFutureClass("Sub1")); - - // Load class Sub1 - dlopen("future2.dylib", 0); - - // Verify use of future class - newSub1 = objc_getClass("Sub1"); - testassert(oldSub1 == newSub1); - testassert(newSub1 == [newSub1 classref]); - testassert(newSub1 == class_getSuperclass(objc_getClass("SubSub1"))); - testassert(! _class_isFutureClass(newSub1)); - - testassert(1 == [oldSub1 method]); - testassert(1 == [newSub1 method]); - - succeed(__FILE__); -} - -#endif diff --git a/test/future0.m b/test/future0.m deleted file mode 100644 index 4dd3146..0000000 --- a/test/future0.m +++ /dev/null @@ -1,2 +0,0 @@ -#include "future.h" -#include "testroot.i" diff --git a/test/future2.m b/test/future2.m deleted file mode 100644 index c5ebb58..0000000 --- a/test/future2.m +++ /dev/null @@ -1,17 +0,0 @@ -#include "future.h" - - -@implementation Sub1 -+(Class)classref { - return [Sub1 class]; -} -+(int)method { - return 1; -} -@end - -@implementation SubSub1 -+(int)method { - return 1 + [super method]; -} -@end diff --git a/test/gc-main.m b/test/gc-main.m deleted file mode 100644 index 44f7476..0000000 --- a/test/gc-main.m +++ /dev/null @@ -1,10 +0,0 @@ -#include "test.h" - -OBJC_ROOT_CLASS -@interface Main @end -@implementation Main @end - -int main(int argc __attribute__((unused)), char **argv) -{ - succeed(basename(argv[0])); -} diff --git a/test/gc.c b/test/gc.c deleted file mode 100644 index dab0f7b..0000000 --- a/test/gc.c +++ /dev/null @@ -1 +0,0 @@ -int GC(void) { return 42; } diff --git a/test/gc.m b/test/gc.m deleted file mode 100644 index 65ba5f9..0000000 --- a/test/gc.m +++ /dev/null @@ -1,8 +0,0 @@ -#import - -OBJC_ROOT_CLASS -@interface GC @end -@implementation GC @end - -// silence "no debug symbols in executable" warning -void foo(void) { } diff --git a/test/gcenforcer-nogc-1.m b/test/gcenforcer-nogc-1.m deleted file mode 100644 index d72212b..0000000 --- a/test/gcenforcer-nogc-1.m +++ /dev/null @@ -1,15 +0,0 @@ -// gc-off app loading gc-off dylib: should work - -/* -TEST_CONFIG MEM=mrc,arc OS=macosx - -TEST_BUILD - $C{COMPILE_C} $DIR/gc.c -dynamiclib -o libnoobjc.dylib - $C{COMPILE_NOMEM} $DIR/gc.m -dynamiclib -o libnogc.dylib - $C{COMPILE_NOMEM} $DIR/gc.m -dynamiclib -o libsupportsgc.dylib -fobjc-gc - $C{COMPILE_NOMEM} $DIR/gc.m -dynamiclib -o librequiresgc.dylib -fobjc-gc-only - $C{COMPILE_NOMEM} $DIR/gc.m -dynamiclib -o librequiresgc.fake.dylib -fobjc-gc -install_name librequiresgc.dylib - - $C{COMPILE} $DIR/gc-main.m -x none libnogc.dylib -o gcenforcer-nogc-1.out -END -*/ diff --git a/test/gcenforcer-nogc-2.m b/test/gcenforcer-nogc-2.m deleted file mode 100644 index 75146fe..0000000 --- a/test/gcenforcer-nogc-2.m +++ /dev/null @@ -1,22 +0,0 @@ -// gc-on app loading gc-off dylib: should crash - -/* -TEST_CONFIG MEM=gc OS=macosx -TEST_CRASHES - -TEST_RUN_OUTPUT -objc\[\d+\]: '.*libnogc.dylib' was not compiled with -fobjc-gc or -fobjc-gc-only, but the application requires GC -objc\[\d+\]: \*\*\* GC capability of application and some libraries did not match -CRASHED: SIGILL -END - -TEST_BUILD - $C{COMPILE_C} $DIR/gc.c -dynamiclib -o libnoobjc.dylib - $C{COMPILE_NOMEM} $DIR/gc.m -dynamiclib -o libnogc.dylib - $C{COMPILE} $DIR/gc.m -dynamiclib -o libsupportsgc.dylib -fobjc-gc - $C{COMPILE} $DIR/gc.m -dynamiclib -o librequiresgc.dylib -fobjc-gc-only - $C{COMPILE} $DIR/gc.m -dynamiclib -o librequiresgc.fake.dylib -fobjc-gc -install_name librequiresgc.dylib - - $C{COMPILE} $DIR/gc-main.m -x none libnogc.dylib -o gcenforcer-nogc-2.out -END -*/ diff --git a/test/gcenforcer-noobjc.m b/test/gcenforcer-noobjc.m deleted file mode 100644 index f37c9ba..0000000 --- a/test/gcenforcer-noobjc.m +++ /dev/null @@ -1,13 +0,0 @@ -/* -TEST_CONFIG OS=macosx - -TEST_BUILD - $C{COMPILE_C} $DIR/gc.c -dynamiclib -o libnoobjc.dylib - $C{COMPILE_NOMEM} $DIR/gc.m -dynamiclib -o libnogc.dylib - $C{COMPILE_NOMEM} $DIR/gc.m -dynamiclib -o libsupportsgc.dylib -fobjc-gc - $C{COMPILE_NOMEM} $DIR/gc.m -dynamiclib -o librequiresgc.dylib -fobjc-gc-only - $C{COMPILE_NOMEM} $DIR/gc.m -dynamiclib -o librequiresgc.fake.dylib -fobjc-gc -install_name librequiresgc.dylib - - $C{COMPILE} $DIR/gc-main.m -x none libnoobjc.dylib -o gcenforcer-noobjc.out -END -*/ diff --git a/test/gcenforcer-requiresgc-1.m b/test/gcenforcer-requiresgc-1.m deleted file mode 100644 index 132e672..0000000 --- a/test/gcenforcer-requiresgc-1.m +++ /dev/null @@ -1,23 +0,0 @@ -// gc-off app loading gc-required dylib: should crash -// linker sees librequiresgc.fake.dylib, runtime uses librequiresgc.dylib - -/* -TEST_CONFIG MEM=mrc,arc OS=macosx -TEST_CRASHES - -TEST_RUN_OUTPUT -objc\[\d+\]: '.*librequiresgc.dylib' was compiled with -fobjc-gc-only, but the application does not support GC -objc\[\d+\]: \*\*\* GC capability of application and some libraries did not match -CRASHED: SIGILL -END - -TEST_BUILD - $C{COMPILE_C} $DIR/gc.c -dynamiclib -o libnoobjc.dylib - $C{COMPILE_NOMEM} $DIR/gc.m -dynamiclib -o libnogc.dylib - $C{COMPILE_NOMEM} $DIR/gc.m -dynamiclib -o libsupportsgc.dylib -fobjc-gc - $C{COMPILE_NOMEM} $DIR/gc.m -dynamiclib -o librequiresgc.dylib -fobjc-gc-only - $C{COMPILE_NOMEM} $DIR/gc.m -dynamiclib -o librequiresgc.fake.dylib -fobjc-gc -install_name librequiresgc.dylib - - $C{COMPILE} $DIR/gc-main.m -x none librequiresgc.fake.dylib -o gcenforcer-requiresgc-1.out -END -*/ diff --git a/test/gcenforcer-requiresgc-2.m b/test/gcenforcer-requiresgc-2.m deleted file mode 100644 index 530891a..0000000 --- a/test/gcenforcer-requiresgc-2.m +++ /dev/null @@ -1,16 +0,0 @@ -// gc-off app loading gc-required dylib: should crash -// linker sees librequiresgc.fake.dylib, runtime uses librequiresgc.dylib - -/* -TEST_CONFIG MEM=gc OS=macosx - -TEST_BUILD - $C{COMPILE_C} $DIR/gc.c -dynamiclib -o libnoobjc.dylib - $C{COMPILE_NOMEM} $DIR/gc.m -dynamiclib -o libnogc.dylib - $C{COMPILE} $DIR/gc.m -dynamiclib -o libsupportsgc.dylib -fobjc-gc - $C{COMPILE} $DIR/gc.m -dynamiclib -o librequiresgc.dylib -fobjc-gc-only - $C{COMPILE} $DIR/gc.m -dynamiclib -o librequiresgc.fake.dylib -fobjc-gc -install_name librequiresgc.dylib - - $C{COMPILE} $DIR/gc-main.m -x none librequiresgc.fake.dylib -o gcenforcer-requiresgc-2.out -END -*/ diff --git a/test/gcenforcer-supportsgc.m b/test/gcenforcer-supportsgc.m deleted file mode 100644 index 9551483..0000000 --- a/test/gcenforcer-supportsgc.m +++ /dev/null @@ -1,13 +0,0 @@ -/* -TEST_CONFIG OS=macosx - -TEST_BUILD - $C{COMPILE_C} $DIR/gc.c -dynamiclib -o libnoobjc.dylib - $C{COMPILE_NOMEM} $DIR/gc.m -dynamiclib -o libnogc.dylib - $C{COMPILE_NOMEM} $DIR/gc.m -dynamiclib -o libsupportsgc.dylib -fobjc-gc - $C{COMPILE_NOMEM} $DIR/gc.m -dynamiclib -o librequiresgc.dylib -fobjc-gc-only - $C{COMPILE_NOMEM} $DIR/gc.m -dynamiclib -o librequiresgc.fake.dylib -fobjc-gc -install_name librequiresgc.dylib - - $C{COMPILE} $DIR/gc-main.m -x none libsupportsgc.dylib -o gcenforcer-supportsgc.out -END -*/ diff --git a/test/gcenforcer.m b/test/gcenforcer.m deleted file mode 100644 index 66d66e9..0000000 --- a/test/gcenforcer.m +++ /dev/null @@ -1,36 +0,0 @@ -/* -TEST_CONFIG OS=macosx - -TEST_BUILD - $C{COMPILE_C} $DIR/gc.c -dynamiclib -o libnoobjc.dylib - $C{COMPILE_NOMEM} $DIR/gc.m -dynamiclib -o libnogc.dylib - $C{COMPILE_NOMEM} $DIR/gc.m -dynamiclib -o libsupportsgc.dylib -fobjc-gc - $C{COMPILE_NOMEM} $DIR/gc.m -dynamiclib -o librequiresgc.dylib -fobjc-gc-only - $C{COMPILE_NOMEM} $DIR/gc.m -dynamiclib -o librequiresgc.fake.dylib -fobjc-gc -install_name librequiresgc.dylib - - $C{COMPILE} $DIR/gcenforcer.m -o gcenforcer.out -END -*/ - -#include "test.h" -#include -#include - -int main() -{ - int i; - for (i = 0; i < 1000; i++) { - testassert(dlopen_preflight("libsupportsgc.dylib")); - testassert(dlopen_preflight("libnoobjc.dylib")); - - if (objc_collectingEnabled()) { - testassert(dlopen_preflight("librequiresgc.dylib")); - testassert(! dlopen_preflight("libnogc.dylib")); - } else { - testassert(! dlopen_preflight("librequiresgc.dylib")); - testassert(dlopen_preflight("libnogc.dylib")); - } - } - - succeed(__FILE__); -} diff --git a/test/gdb.m b/test/gdb.m deleted file mode 100644 index a5f571a..0000000 --- a/test/gdb.m +++ /dev/null @@ -1,59 +0,0 @@ -// TEST_CFLAGS -Wno-deprecated-declarations - -#include "test.h" - -#if TARGET_OS_IPHONE - -int main() -{ - succeed(__FILE__); -} - -#else - -#include "testroot.i" -#include -#include - -int main() -{ - // Class hashes -#if __OBJC2__ - - Class result; - - // Class should not be realized yet - // fixme not true during class hash rearrangement - // result = NXMapGet(gdb_objc_realized_classes, "TestRoot"); - // testassert(!result); - - [TestRoot class]; - // Now class should be realized - - result = (Class)objc_unretainedObject(NXMapGet(gdb_objc_realized_classes, "TestRoot")); - testassert(result); - testassert(result == [TestRoot class]); - - result = (Class)objc_unretainedObject(NXMapGet(gdb_objc_realized_classes, "DoesNotExist")); - testassert(!result); - -#else - - struct objc_class query; - Class result; - - query.name = "TestRoot"; - result = (Class)NXHashGet(_objc_debug_class_hash, &query); - testassert(result); - testassert((id)result == [TestRoot class]); - - query.name = "DoesNotExist"; - result = (Class)NXHashGet(_objc_debug_class_hash, &query); - testassert(!result); - -#endif - - succeed(__FILE__); -} - -#endif diff --git a/test/getMethod.m b/test/getMethod.m deleted file mode 100644 index 84408a8..0000000 --- a/test/getMethod.m +++ /dev/null @@ -1,130 +0,0 @@ -// TEST_CONFIG - -#include "test.h" -#include "testroot.i" -#include -#include - -static int state = 0; - -@interface Super : TestRoot @end -@implementation Super -+(void)classMethod { state = 1; } --(void)instanceMethod { state = 4; } -+(void)classMethodSuperOnly { state = 3; } --(void)instanceMethodSuperOnly { state = 6; } -@end - -@interface Sub : Super @end -@implementation Sub -+(void)classMethod { state = 2; } --(void)instanceMethod { state = 5; } -@end - -typedef void (*imp_t)(id, SEL); - -int main() -{ - Class Super_cls, Sub_cls; - Class buf[10]; - Method m; - SEL sel; - IMP imp; - - // don't use [Super class] to check laziness handing - Super_cls = objc_getClass("Super"); - Sub_cls = objc_getClass("Sub"); - - sel = sel_registerName("classMethod"); - m = class_getClassMethod(Super_cls, sel); - testassert(m); - testassert(sel == method_getName(m)); - imp = method_getImplementation(m); - testassert(imp == class_getMethodImplementation(object_getClass(Super_cls), sel)); - testassert(imp == object_getMethodImplementation(Super_cls, sel)); - state = 0; - (*(imp_t)imp)(Super_cls, sel); - testassert(state == 1); - - sel = sel_registerName("classMethod"); - m = class_getClassMethod(Sub_cls, sel); - testassert(m); - testassert(sel == method_getName(m)); - imp = method_getImplementation(m); - testassert(imp == class_getMethodImplementation(object_getClass(Sub_cls), sel)); - testassert(imp == object_getMethodImplementation(Sub_cls, sel)); - state = 0; - (*(imp_t)imp)(Sub_cls, sel); - testassert(state == 2); - - sel = sel_registerName("classMethodSuperOnly"); - m = class_getClassMethod(Sub_cls, sel); - testassert(m); - testassert(sel == method_getName(m)); - imp = method_getImplementation(m); - testassert(imp == class_getMethodImplementation(object_getClass(Sub_cls), sel)); - testassert(imp == object_getMethodImplementation(Sub_cls, sel)); - state = 0; - (*(imp_t)imp)(Sub_cls, sel); - testassert(state == 3); - - sel = sel_registerName("instanceMethod"); - m = class_getInstanceMethod(Super_cls, sel); - testassert(m); - testassert(sel == method_getName(m)); - imp = method_getImplementation(m); - testassert(imp == class_getMethodImplementation(Super_cls, sel)); - buf[0] = Super_cls; - testassert(imp == object_getMethodImplementation(objc_unretainedObject(buf), sel)); - state = 0; - (*(imp_t)imp)(objc_unretainedObject(buf), sel); - testassert(state == 4); - - sel = sel_registerName("instanceMethod"); - m = class_getInstanceMethod(Sub_cls, sel); - testassert(m); - testassert(sel == method_getName(m)); - imp = method_getImplementation(m); - testassert(imp == class_getMethodImplementation(Sub_cls, sel)); - buf[0] = Sub_cls; - testassert(imp == object_getMethodImplementation(objc_unretainedObject(buf), sel)); - state = 0; - (*(imp_t)imp)(objc_unretainedObject(buf), sel); - testassert(state == 5); - - sel = sel_registerName("instanceMethodSuperOnly"); - m = class_getInstanceMethod(Sub_cls, sel); - testassert(m); - testassert(sel == method_getName(m)); - imp = method_getImplementation(m); - testassert(imp == class_getMethodImplementation(Sub_cls, sel)); - buf[0] = Sub_cls; - testassert(imp == object_getMethodImplementation(objc_unretainedObject(buf), sel)); - state = 0; - (*(imp_t)imp)(objc_unretainedObject(buf), sel); - testassert(state == 6); - - // check class_getClassMethod(cls) == class_getInstanceMethod(cls->isa) - sel = sel_registerName("classMethod"); - testassert(class_getClassMethod(Sub_cls, sel) == class_getInstanceMethod(object_getClass(Sub_cls), sel)); - - sel = sel_registerName("nonexistent"); - testassert(! class_getInstanceMethod(Sub_cls, sel)); - testassert(! class_getClassMethod(Sub_cls, sel)); - testassert(class_getMethodImplementation(Sub_cls, sel) == (IMP)&_objc_msgForward); - buf[0] = Sub_cls; - testassert(object_getMethodImplementation(objc_unretainedObject(buf), sel) == (IMP)&_objc_msgForward); -#if !__arm64__ - testassert(class_getMethodImplementation_stret(Sub_cls, sel) == (IMP)&_objc_msgForward_stret); - testassert(object_getMethodImplementation_stret(objc_unretainedObject(buf), sel) == (IMP)&_objc_msgForward_stret); -#endif - - testassert(! class_getInstanceMethod(NULL, NULL)); - testassert(! class_getInstanceMethod(NULL, sel)); - testassert(! class_getInstanceMethod(Sub_cls, NULL)); - testassert(! class_getClassMethod(NULL, NULL)); - testassert(! class_getClassMethod(NULL, sel)); - testassert(! class_getClassMethod(Sub_cls, NULL)); - - succeed(__FILE__); -} diff --git a/test/ignoredSelector.m b/test/ignoredSelector.m deleted file mode 100644 index 733da6a..0000000 --- a/test/ignoredSelector.m +++ /dev/null @@ -1,350 +0,0 @@ -// TEST_CONFIG MEM=mrc,gc -// TEST_CFLAGS -Wno-deprecated-declarations - -#include "test.h" -#include -#include -#include - -static int state = 0; - -OBJC_ROOT_CLASS -@interface Super { id isa; } @end -@implementation Super -+(id)class { return self; } -+(void)initialize { } - -+(id)ordinary { state = 1; return self; } -+(id)ordinary2 { testassert(0); } -+(id)retain { state = 2; return self; } -+(void)release { state = 3; } -+(id)autorelease { state = 4; return self; } -+(void)dealloc { state = 5; } -+(uintptr_t)retainCount { state = 6; return 6; } -@end - -@interface Sub : Super @end -@implementation Sub @end - -@interface Sub2 : Super @end -@implementation Sub2 @end - -OBJC_ROOT_CLASS -@interface Empty { id isa; } @end -@implementation Empty -+(id)class { return self; } -+(void)initialize { } -@end - -void *forward_handler(id obj, SEL _cmd) { - testassert(obj == [Empty class]); - testassert(_cmd == @selector(ordinary)); - state = 1; - return nil; -} - -@interface Empty (Unimplemented) -+(id)ordinary; -+(id)retain; -+(void)release; -+(id)autorelease; -+(void)dealloc; -+(uintptr_t)retainCount; -@end - - -#define getImp(sel) \ - do { \ - sel##Method = class_getClassMethod(cls, @selector(sel)); \ - testassert(sel##Method); \ - testassert(@selector(sel) == method_getName(sel##Method)); \ - sel = method_getImplementation(sel##Method); \ - } while (0) - - -static IMP ordinary, ordinary2, retain, release, autorelease, dealloc, retainCount; -static Method ordinaryMethod, ordinary2Method, retainMethod, releaseMethod, autoreleaseMethod, deallocMethod, retainCountMethod; - -void cycle(Class cls) -{ - id idVal; - uintptr_t intVal; - -#if defined(__i386__) - if (objc_collectingEnabled()) { - // i386 GC: all ignored selectors are identical - testassert(@selector(retain) == @selector(release) && - @selector(retain) == @selector(autorelease) && - @selector(retain) == @selector(dealloc) && - @selector(retain) == @selector(retainCount) ); - } - else -#endif - { - // x86_64 GC or no GC: all ignored selectors are distinct - testassert(@selector(retain) != @selector(release) && - @selector(retain) != @selector(autorelease) && - @selector(retain) != @selector(dealloc) && - @selector(retain) != @selector(retainCount) ); - } - - // no ignored selector matches a real selector - testassert(@selector(ordinary) != @selector(retain) && - @selector(ordinary) != @selector(release) && - @selector(ordinary) != @selector(autorelease) && - @selector(ordinary) != @selector(dealloc) && - @selector(ordinary) != @selector(retainCount) ); - - getImp(ordinary); - getImp(ordinary2); - getImp(retain); - getImp(release); - getImp(autorelease); - getImp(dealloc); - getImp(retainCount); - - if (objc_collectingEnabled()) { - // GC: all ignored selector IMPs are identical - testassert(retain == release && - retain == autorelease && - retain == dealloc && - retain == retainCount ); - } - else { - // no GC: all ignored selector IMPs are distinct - testassert(retain != release && - retain != autorelease && - retain != dealloc && - retain != retainCount ); - } - - // no ignored selector IMP matches a real selector IMP - testassert(ordinary != retain && - ordinary != release && - ordinary != autorelease && - ordinary != dealloc && - ordinary != retainCount ); - - // Test calls via method_invoke - - idVal = ((id(*)(id, Method))method_invoke)(cls, ordinaryMethod); - testassert(state == 1); - testassert(idVal == cls); - - state = 0; - idVal = ((id(*)(id, Method))method_invoke)(cls, retainMethod); - testassert(state == (objc_collectingEnabled() ? 0 : 2)); - testassert(idVal == cls); - - (void) ((void(*)(id, Method))method_invoke)(cls, releaseMethod); - testassert(state == (objc_collectingEnabled() ? 0 : 3)); - - idVal = ((id(*)(id, Method))method_invoke)(cls, autoreleaseMethod); - testassert(state == (objc_collectingEnabled() ? 0 : 4)); - testassert(idVal == cls); - - (void) ((void(*)(id, Method))method_invoke)(cls, deallocMethod); - testassert(state == (objc_collectingEnabled() ? 0 : 5)); - - intVal = ((uintptr_t(*)(id, Method))method_invoke)(cls, retainCountMethod); - testassert(state == (objc_collectingEnabled() ? 0 : 6)); - testassert(intVal == (objc_collectingEnabled() ? (uintptr_t)cls : 6)); - - - // Test calls via compiled objc_msgSend - - state = 0; - idVal = [cls ordinary]; - testassert(state == 1); - testassert(idVal == cls); - - state = 0; - idVal = [cls retain]; - testassert(state == (objc_collectingEnabled() ? 0 : 2)); - testassert(idVal == cls); - - (void) [cls release]; - testassert(state == (objc_collectingEnabled() ? 0 : 3)); - - idVal = [cls autorelease]; - testassert(state == (objc_collectingEnabled() ? 0 : 4)); - testassert(idVal == cls); - - (void) [cls dealloc]; - testassert(state == (objc_collectingEnabled() ? 0 : 5)); - - intVal = [cls retainCount]; - testassert(state == (objc_collectingEnabled() ? 0 : 6)); - testassert(intVal == (objc_collectingEnabled() ? (uintptr_t)cls : 6)); - - // Test calls via handwritten objc_msgSend - - state = 0; - idVal = ((id(*)(id,SEL))objc_msgSend)(cls, @selector(ordinary)); - testassert(state == 1); - testassert(idVal == cls); - - state = 0; - idVal = ((id(*)(id,SEL))objc_msgSend)(cls, @selector(retain)); - testassert(state == (objc_collectingEnabled() ? 0 : 2)); - testassert(idVal == cls); - - (void) ((void(*)(id,SEL))objc_msgSend)(cls, @selector(release)); - testassert(state == (objc_collectingEnabled() ? 0 : 3)); - - idVal = ((id(*)(id,SEL))objc_msgSend)(cls, @selector(autorelease)); - testassert(state == (objc_collectingEnabled() ? 0 : 4)); - testassert(idVal == cls); - - (void) ((void(*)(id,SEL))objc_msgSend)(cls, @selector(dealloc)); - testassert(state == (objc_collectingEnabled() ? 0 : 5)); - - intVal = ((uintptr_t(*)(id,SEL))objc_msgSend)(cls, @selector(retainCount)); - testassert(state == (objc_collectingEnabled() ? 0 : 6)); - testassert(intVal == (objc_collectingEnabled() ? (uintptr_t)cls : 6)); -} - -int main() -{ - Class cls; - - objc_setForwardHandler((void*)&forward_handler, nil); - - // Test selector API - - testassert(sel_registerName("retain") == @selector(retain)); - testassert(sel_getUid("retain") == @selector(retain)); -#if defined(__i386__) - if (objc_collectingEnabled()) { - // only i386's GC currently remaps these - testassert(0 == strcmp(sel_getName(@selector(retain)), "")); - } else -#endif - { - testassert(0 == strcmp(sel_getName(@selector(retain)), "retain")); - } -#if !__OBJC2__ - testassert(sel_isMapped(@selector(retain))); -#endif - - cls = [Sub class]; - testassert(cls); - cycle(cls); - - cls = [Super class]; - testassert(cls); - cycle(cls); - - if (objc_collectingEnabled()) { - // rdar://6200570 Method manipulation shouldn't affect ignored methods. - - cls = [Super class]; - testassert(cls); - cycle(cls); - - method_setImplementation(retainMethod, (IMP)1); - method_setImplementation(releaseMethod, (IMP)1); - method_setImplementation(autoreleaseMethod, (IMP)1); - method_setImplementation(deallocMethod, (IMP)1); - method_setImplementation(retainCountMethod, (IMP)1); - cycle(cls); - - testassert(ordinary2 != retainCount); - method_exchangeImplementations(retainMethod, autoreleaseMethod); - method_exchangeImplementations(deallocMethod, releaseMethod); - method_exchangeImplementations(retainCountMethod, ordinary2Method); - cycle(cls); - // ordinary2 exchanged with ignored method is now ignored too - testassert(ordinary2 == retainCount); - - // replace == replace existing - class_replaceMethod(cls, @selector(retain), (IMP)1, ""); - class_replaceMethod(cls, @selector(release), (IMP)1, ""); - class_replaceMethod(cls, @selector(autorelease), (IMP)1, ""); - class_replaceMethod(cls, @selector(dealloc), (IMP)1, ""); - class_replaceMethod(cls, @selector(retainCount), (IMP)1, ""); - cycle(cls); - - cls = [Sub class]; - testassert(cls); - cycle(cls); - - // replace == add override - class_replaceMethod(cls, @selector(retain), (IMP)1, ""); - class_replaceMethod(cls, @selector(release), (IMP)1, ""); - class_replaceMethod(cls, @selector(autorelease), (IMP)1, ""); - class_replaceMethod(cls, @selector(dealloc), (IMP)1, ""); - class_replaceMethod(cls, @selector(retainCount), (IMP)1, ""); - cycle(cls); - - cls = [Sub2 class]; - testassert(cls); - cycle(cls); - - class_addMethod(cls, @selector(retain), (IMP)1, ""); - class_addMethod(cls, @selector(release), (IMP)1, ""); - class_addMethod(cls, @selector(autorelease), (IMP)1, ""); - class_addMethod(cls, @selector(dealloc), (IMP)1, ""); - class_addMethod(cls, @selector(retainCount), (IMP)1, ""); - cycle(cls); - } - - // Test calls via objc_msgSend - ignored selectors are ignored - // under GC even if the class provides no implementation for them - if (objc_collectingEnabled()) { - Class cls; - id idVal; - uintptr_t intVal; - - cls = [Empty class]; - state = 0; - - idVal = [Empty retain]; - testassert(state == 0); - testassert(idVal == cls); - - (void) [Empty release]; - testassert(state == 0); - - idVal = [Empty autorelease]; - testassert(state == 0); - testassert(idVal == cls); - - (void) [Empty dealloc]; - testassert(state == 0); - - intVal = [Empty retainCount]; - testassert(state == 0); - testassert(intVal == (uintptr_t)cls); - - idVal = [Empty ordinary]; - testassert(state == 1); - testassert(idVal == nil); - - state = 0; - - idVal = ((id(*)(id,SEL))objc_msgSend)(cls, @selector(retain)); - testassert(state == 0); - testassert(idVal == cls); - - (void) ((void(*)(id,SEL))objc_msgSend)(cls, @selector(release)); - testassert(state == 0); - - idVal = ((id(*)(id,SEL))objc_msgSend)(cls, @selector(autorelease)); - testassert(state == 0); - testassert(idVal == cls); - - (void) ((void(*)(id,SEL))objc_msgSend)(cls, @selector(dealloc)); - testassert(state == 0); - - intVal = ((uintptr_t(*)(id,SEL))objc_msgSend)(cls, @selector(retainCount)); - testassert(state == 0); - testassert(intVal == (uintptr_t)cls); - - idVal = ((id(*)(id,SEL))objc_msgSend)(cls, @selector(ordinary)); - testassert(state == 1); - testassert(idVal == nil); - } - - succeed(__FILE__); -} diff --git a/test/ignoredSelector2.m b/test/ignoredSelector2.m deleted file mode 100644 index d0a24c8..0000000 --- a/test/ignoredSelector2.m +++ /dev/null @@ -1,37 +0,0 @@ -// TEST_CONFIG MEM=gc -// TEST_CFLAGS -framework Foundation - -// This test must use CF and test ignoredSelector must not use CF. - -#include "test.h" -#include - -int main() -{ - if (objc_collectingEnabled()) { - // ARC RR functions don't retain and don't hit the side table. - __block int count; - testblock_t testblock = ^{ - for (int i = 0; i < count; i++) { - id obj = [NSObject new]; - objc_retain(obj); - objc_retain(obj); - objc_release(obj); - } - }; - count = 100; - testonthread(testblock); - testonthread(testblock); - leak_mark(); - count = 10000000; - testonthread(testblock); -#if __OBJC_GC__ - testwarn("rdar://19042235 possible leaks suppressed under GC"); - leak_check(2000); -#else - leak_check(0); -#endif - } - - succeed(__FILE__); -} diff --git a/test/imageorder.h b/test/imageorder.h deleted file mode 100644 index 654e48e..0000000 --- a/test/imageorder.h +++ /dev/null @@ -1,20 +0,0 @@ -extern int state; -extern int cstate; - -OBJC_ROOT_CLASS -@interface Super { id isa; } -+(void) method; -+(void) method0; -@end - -@interface Super (cat1) -+(void) method1; -@end - -@interface Super (cat2) -+(void) method2; -@end - -@interface Super (cat3) -+(void) method3; -@end diff --git a/test/imageorder.m b/test/imageorder.m deleted file mode 100644 index f417eb5..0000000 --- a/test/imageorder.m +++ /dev/null @@ -1,41 +0,0 @@ -/* -TEST_BUILD - $C{COMPILE} $DIR/imageorder1.m -o imageorder1.dylib -dynamiclib - $C{COMPILE} $DIR/imageorder2.m -x none imageorder1.dylib -o imageorder2.dylib -dynamiclib - $C{COMPILE} $DIR/imageorder3.m -x none imageorder2.dylib imageorder1.dylib -o imageorder3.dylib -dynamiclib - $C{COMPILE} $DIR/imageorder.m -x none imageorder3.dylib imageorder2.dylib imageorder1.dylib -o imageorder.out -END -*/ - -#include "test.h" -#include "imageorder.h" -#include -#include - -int main() -{ - // +load methods and C static initializers - testassert(state == 3); - testassert(cstate == 3); - - Class cls = objc_getClass("Super"); - testassert(cls); - - // make sure all categories arrived - state = -1; - [Super method0]; - testassert(state == 0); - [Super method1]; - testassert(state == 1); - [Super method2]; - testassert(state == 2); - [Super method3]; - testassert(state == 3); - - // make sure imageorder3.dylib is the last category to attach - state = 0; - [Super method]; - testassert(state == 3); - - succeed(__FILE__); -} diff --git a/test/imageorder1.m b/test/imageorder1.m deleted file mode 100644 index 2cc1d80..0000000 --- a/test/imageorder1.m +++ /dev/null @@ -1,52 +0,0 @@ -#include "test.h" -#include "imageorder.h" - -int state = -1; -int cstate = 0; - -static void c1(void) __attribute__((constructor)); -static void c1(void) -{ - testassert(state == 1); // +load before C/C++ - testassert(cstate == 0); - cstate = 1; -} - - -#if __clang__ -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wobjc-protocol-method-implementation" -#endif - -@implementation Super (cat1) -+(void) method { - fail("+[Super(cat1) method] not replaced!"); -} -+(void) method1 { - state = 1; -} -+(void) load { - testassert(state == 0); - state = 1; -} -@end - -#if __clang__ -#pragma clang diagnostic pop -#endif - - -@implementation Super -+(void) initialize { } -+(void) method { - fail("+[Super method] not replaced!"); -} -+(void) method0 { - state = 0; -} -+(void) load { - testassert(state == -1); - state = 0; -} -@end - diff --git a/test/imageorder2.m b/test/imageorder2.m deleted file mode 100644 index e4d690b..0000000 --- a/test/imageorder2.m +++ /dev/null @@ -1,33 +0,0 @@ -#include "test.h" -#include "imageorder.h" - -static void c2(void) __attribute__((constructor)); -static void c2(void) -{ - testassert(state == 2); // +load before C/C++ - testassert(cstate == 1); - cstate = 2; -} - - -#if __clang__ -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wobjc-protocol-method-implementation" -#endif - -@implementation Super (cat2) -+(void) method { - fail("+[Super(cat2) method] not replaced!"); -} -+(void) method2 { - state = 2; -} -+(void) load { - testassert(state == 1); - state = 2; -} -@end - -#if __clang__ -#pragma clang diagnostic pop -#endif diff --git a/test/imageorder3.m b/test/imageorder3.m deleted file mode 100644 index 217130f..0000000 --- a/test/imageorder3.m +++ /dev/null @@ -1,33 +0,0 @@ -#include "test.h" -#include "imageorder.h" - -static void c3(void) __attribute__((constructor)); -static void c3(void) -{ - testassert(state == 3); // +load before C/C++ - testassert(cstate == 2); - cstate = 3; -} - - -#if __clang__ -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wobjc-protocol-method-implementation" -#endif - -@implementation Super (cat3) -+(void) method { - state = 3; -} -+(void) method3 { - state = 3; -} -+(void) load { - testassert(state == 2); - state = 3; -} -@end - -#if __clang__ -#pragma clang diagnostic pop -#endif diff --git a/test/includes.c b/test/includes.c deleted file mode 100644 index 01c1686..0000000 --- a/test/includes.c +++ /dev/null @@ -1,38 +0,0 @@ -// TEST_CONFIG - -// Verify that all headers can be included in any language. - -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include - -#if !TARGET_OS_IPHONE -#include -#include -#include -#endif - -#include "test.h" - -int main() -{ - succeed(__FILE__); -} diff --git a/test/initialize.m b/test/initialize.m deleted file mode 100644 index c3bced1..0000000 --- a/test/initialize.m +++ /dev/null @@ -1,277 +0,0 @@ -// TEST_CONFIG - -// initialize.m -// Test basic +initialize behavior -// * +initialize before class method -// * superclass +initialize before subclass +initialize -// * subclass inheritance of superclass implementation -// * messaging during +initialize -// * +initialize provoked by class_getMethodImplementation -// * +initialize not provoked by objc_getClass -#include "test.h" -#include "testroot.i" - -int state = 0; - -@interface Super0 : TestRoot @end -@implementation Super0 -+(void)initialize { - fail("objc_getClass() must not trigger +initialize"); -} -@end - -@interface Super : TestRoot @end -@implementation Super -+(void)initialize { - testprintf("in [Super initialize]\n"); - testassert(state == 0); - state = 1; -} -+(void)method { - fail("[Super method] shouldn't be called"); -} -@end - -@interface Sub : Super @end -@implementation Sub -+(void)initialize { - testprintf("in [Sub initialize]\n"); - testassert(state == 1); - state = 2; -} -+(void)method { - testprintf("in [Sub method]\n"); - testassert(state == 2); - state = 3; -} -@end - - -@interface Super2 : TestRoot @end -@interface Sub2 : Super2 @end - -@implementation Super2 -+(void)initialize { - if (self == objc_getClass("Sub2")) { - testprintf("in [Super2 initialize] of Sub2\n"); - testassert(state == 1); - state = 2; - } else if (self == objc_getClass("Super2")) { - testprintf("in [Super2 initialize] of Super2\n"); - testassert(state == 0); - state = 1; - } else { - fail("in [Super2 initialize] of unknown class"); - } -} -+(void)method { - testprintf("in [Super2 method]\n"); - testassert(state == 2); - state = 3; -} -@end - -@implementation Sub2 -// nothing here -@end - - -@interface Super3 : TestRoot @end -@interface Sub3 : Super3 @end - -@implementation Super3 -+(void)initialize { - if (self == [Sub3 class]) { // this message triggers [Sub3 initialize] - testprintf("in [Super3 initialize] of Sub3\n"); - testassert(state == 0); - state = 1; - } else if (self == [Super3 class]) { - testprintf("in [Super3 initialize] of Super3\n"); - testassert(state == 1); - state = 2; - } else { - fail("in [Super3 initialize] of unknown class"); - } -} -+(void)method { - testprintf("in [Super3 method]\n"); - testassert(state == 2); - state = 3; -} -@end - -@implementation Sub3 -// nothing here -@end - - -@interface Super4 : TestRoot @end -@implementation Super4 --(void)instanceMethod { - testassert(state == 1); - state = 2; -} -+(void)initialize { - testprintf("in [Super4 initialize]\n"); - testassert(state == 0); - state = 1; - id x = [[self alloc] init]; - [x instanceMethod]; - RELEASE_VALUE(x); -} -@end - - -@interface Super5 : TestRoot @end -@implementation Super5 --(void)instanceMethod { -} -+(void)classMethod { - testassert(state == 1); - state = 2; -} -+(void)initialize { - testprintf("in [Super5 initialize]\n"); - testassert(state == 0); - state = 1; - class_getMethodImplementation(self, @selector(instanceMethod)); - // this is the "memoized" case for getNonMetaClass - class_getMethodImplementation(object_getClass(self), @selector(classMethod)); - [self classMethod]; -} -@end - - -@interface Super6 : TestRoot @end -@interface Sub6 : Super6 @end -@implementation Super6 -+(void)initialize { - static bool once; - bool wasOnce; - testprintf("in [Super6 initialize] (#%d)\n", 1+(int)once); - if (!once) { - once = true; - wasOnce = true; - testassert(state == 0); - state = 1; - } else { - wasOnce = false; - testassert(state == 2); - state = 3; - } - [Sub6 class]; - if (wasOnce) { - testassert(state == 5); - state = 6; - } else { - testassert(state == 3); - state = 4; - } -} -@end -@implementation Sub6 -+(void)initialize { - testprintf("in [Sub6 initialize]\n"); - testassert(state == 1); - state = 2; - [super initialize]; - testassert(state == 4); - state = 5; -} -@end - - -@interface Super7 : TestRoot @end -@interface Sub7 : Super7 @end -@implementation Super7 -+(void)initialize { - static bool once; - bool wasOnce; - testprintf("in [Super7 initialize] (#%d)\n", 1+(int)once); - if (!once) { - once = true; - wasOnce = true; - testassert(state == 0); - state = 1; - } else { - wasOnce = false; - testassert(state == 2); - state = 3; - } - [Sub7 class]; - if (wasOnce) { - testassert(state == 5); - state = 6; - } else { - testassert(state == 3); - state = 4; - } -} -@end -@implementation Sub7 -+(void)initialize { - testprintf("in [Sub7 initialize]\n"); - testassert(state == 1); - state = 2; - [super initialize]; - testassert(state == 4); - state = 5; -} -@end - - -int main() -{ - Class cls; - - // objc_getClass() must not +initialize anything - state = 0; - objc_getClass("Super0"); - testassert(state == 0); - - // initialize superclass, then subclass - state = 0; - [Sub method]; - testassert(state == 3); - - // check subclass's inheritance of superclass initialize - state = 0; - [Sub2 method]; - testassert(state == 3); - - // check subclass method called from superclass initialize - state = 0; - [Sub3 method]; - testassert(state == 3); - - // check class_getMethodImplementation (instance method) - state = 0; - cls = objc_getClass("Super4"); - testassert(state == 0); - class_getMethodImplementation(cls, @selector(classMethod)); - testassert(state == 2); - - // check class_getMethodImplementation (class method) - // this is the "slow" case for getNonMetaClass - state = 0; - cls = objc_getClass("Super5"); - testassert(state == 0); - class_getMethodImplementation(object_getClass(cls), @selector(instanceMethod)); - testassert(state == 2); - - // check +initialize cycles - // this is the "cls is a subclass" case for getNonMetaClass - state = 0; - [Super6 class]; - testassert(state == 6); - - // check +initialize cycles - // this is the "cls is a subclass" case for getNonMetaClass - state = 0; - [Sub7 class]; - testassert(state == 6); - - succeed(__FILE__); - - return 0; -} diff --git a/test/initializeVersusWeak.m b/test/initializeVersusWeak.m deleted file mode 100644 index c7a0e36..0000000 --- a/test/initializeVersusWeak.m +++ /dev/null @@ -1,129 +0,0 @@ -// TEST_CONFIG MEM=arc -// TEST_CFLAGS -framework Foundation - -// Problem: If weak reference operations provoke +initialize, the runtime -// can deadlock (recursive weak lock, or lock inversion between weak lock -// and +initialize lock). -// Solution: object_setClass() and objc_storeWeak() perform +initialize -// if needed so that no weakly-referenced object can ever have an -// un-+initialized isa. - -#include -#include -#include "test.h" - -#pragma clang diagnostic ignored "-Wdeprecated-declarations" -#pragma clang diagnostic ignored "-Warc-unsafe-retained-assign" - -// This is StripedMap's pointer hash -uintptr_t hash(id obj) { - uintptr_t addr = (uintptr_t)obj; - return ((addr >> 4) ^ (addr >> 9)) % 64; -} - -bool sameAlignment(id o1, id o2) -{ - return hash(o1) == hash(o2); -} - -// Return a new string object that uses the same striped weak locks as `obj`. -NSMutableString *newAlignedString(id obj) -{ - NSMutableArray *strings = [NSMutableArray new]; - NSMutableString *result; - do { - result = [NSMutableString new]; - [strings addObject:result]; - } while (!sameAlignment(obj, result)); - return result; -} - - -__weak NSObject *weak1; -__weak NSMutableString *weak2; -NSMutableString *strong2; - -@interface A : NSObject @end -@implementation A -+(void)initialize { - weak2 = strong2; // weak store #2 - strong2 = nil; -} -@end - -void testA() -{ - // Weak store #1 provokes +initialize which performs weak store #2. - // Solution: weak store #1 runs +initialize if needed - // without holding locks. - @autoreleasepool { - A *obj = [A new]; - strong2 = newAlignedString(obj); - [obj addObserver:obj forKeyPath:@"foo" options:0 context:0]; - weak1 = obj; // weak store #1 - [obj removeObserver:obj forKeyPath:@"foo"]; - obj = nil; - } -} - - -__weak NSObject *weak3; -__weak NSMutableString *weak4; -NSMutableString *strong4; - -@interface B : NSObject @end -@implementation B -+(void)initialize { - weak4 = strong4; // weak store #4 - strong4 = nil; -} -@end - - -void testB() -{ - // Weak load #3 provokes +initialize which performs weak store #4. - // Solution: object_setClass() runs +initialize if needed - // without holding locks. - @autoreleasepool { - B *obj = [B new]; - strong4 = newAlignedString(obj); - weak3 = obj; - [obj addObserver:obj forKeyPath:@"foo" options:0 context:0]; - [weak3 self]; // weak load #3 - [obj removeObserver:obj forKeyPath:@"foo"]; - obj = nil; - } -} - - -__weak id weak5; - -@interface C : NSObject @end -@implementation C -+(void)initialize { - weak5 = [self new]; -} -@end - -void testC() -{ - // +initialize performs a weak store of itself. - // Make sure the retry in objc_storeWeak() doesn't spin. - @autoreleasepool { - [C self]; - } -} - - -int main() -{ - alarm(10); // replace hangs with crashes - - testA(); - testB(); - testC(); - - succeed(__FILE__); -} - diff --git a/test/instanceSize.m b/test/instanceSize.m deleted file mode 100644 index 8034a5c..0000000 --- a/test/instanceSize.m +++ /dev/null @@ -1,59 +0,0 @@ -// TEST_CONFIG - -#include "test.h" -#include "testroot.i" -#include - - -@interface Sub1 : TestRoot { - // id isa; // 0..4 - BOOL b; // 4..5 -} -@end - -@implementation Sub1 @end - -@interface Sub2 : Sub1 { - // id isa // 0..4 0..8 - // BOOL b // 4..5 8..9 - BOOL b2; // 5..6 9..10 - id o; // 8..12 16..24 -} -@end -@implementation Sub2 @end - -@interface Sub3 : Sub1 { - // id isa; // 0..4 0..8 - // BOOL b; // 4..5 8..9 - id o; // 8..12 16..24 - BOOL b2; // 12..13 24..25 -} -@end -@implementation Sub3 @end - -int main() -{ - testassert(sizeof(id) == class_getInstanceSize([TestRoot class])); - testassert(2*sizeof(id) == class_getInstanceSize([Sub1 class])); - testassert(3*sizeof(id) == class_getInstanceSize([Sub2 class])); - testassert(4*sizeof(id) == class_getInstanceSize([Sub3 class])); - -#if !__has_feature(objc_arc) - id o; - - o = [TestRoot new]; - testassert(object_getIndexedIvars(o) == (char *)o + class_getInstanceSize(object_getClass(o))); - RELEASE_VAR(o); - o = [Sub1 new]; - testassert(object_getIndexedIvars(o) == (char *)o + class_getInstanceSize(object_getClass(o))); - RELEASE_VAR(o); - o = [Sub2 new]; - testassert(object_getIndexedIvars(o) == (char *)o + class_getInstanceSize(object_getClass(o))); - RELEASE_VAR(o); - o = [Sub3 new]; - testassert(object_getIndexedIvars(o) == (char *)o + class_getInstanceSize(object_getClass(o))); - RELEASE_VAR(o); -#endif - - succeed(__FILE__); -} diff --git a/test/ismeta.m b/test/ismeta.m deleted file mode 100644 index d0e580d..0000000 --- a/test/ismeta.m +++ /dev/null @@ -1,13 +0,0 @@ -// TEST_CONFIG - -#include "test.h" -#include "testroot.i" -#include - -int main() -{ - testassert(!class_isMetaClass([TestRoot class])); - testassert(class_isMetaClass(object_getClass([TestRoot class]))); - testassert(!class_isMetaClass(nil)); - succeed(__FILE__); -} diff --git a/test/ivar.m b/test/ivar.m deleted file mode 100644 index 84bda90..0000000 --- a/test/ivar.m +++ /dev/null @@ -1,110 +0,0 @@ -// TEST_CONFIG - -#include "test.h" -#include "testroot.i" -#include -#include -#include - -@interface Super : TestRoot { - @public - char superIvar; -} -@end - -@interface Sub : Super { - @public - id subIvar; -} -@end - -@implementation Super @end -@implementation Sub @end - - -int main() -{ - /* - Runtime layout of Sub: - [0] isa - [1] superIvar - [2] subIvar - */ - - Ivar ivar; - Sub *sub = [Sub new]; - sub->subIvar = [Sub class]; - testassert(((Class *)objc_unretainedPointer(sub))[2] == [Sub class]); - - ivar = class_getInstanceVariable([Sub class], "subIvar"); - testassert(ivar); - testassert(2*sizeof(intptr_t) == (size_t)ivar_getOffset(ivar)); - testassert(0 == strcmp(ivar_getName(ivar), "subIvar")); - testassert(0 == strcmp(ivar_getTypeEncoding(ivar), "@")); - - ivar = class_getInstanceVariable([Super class], "superIvar"); - testassert(ivar); - testassert(sizeof(intptr_t) == (size_t)ivar_getOffset(ivar)); - testassert(0 == strcmp(ivar_getName(ivar), "superIvar")); - testassert(0 == strcmp(ivar_getTypeEncoding(ivar), "c")); - testassert(ivar == class_getInstanceVariable([Sub class], "superIvar")); - - ivar = class_getInstanceVariable([Super class], "subIvar"); - testassert(!ivar); - - ivar = class_getInstanceVariable(object_getClass([Sub class]), "subIvar"); - testassert(!ivar); - - ivar = class_getInstanceVariable([Sub class], "subIvar"); - object_setIvar(sub, ivar, sub); - testassert(sub->subIvar == sub); - testassert(sub == object_getIvar(sub, ivar)); - - testassert(NULL == class_getInstanceVariable(NULL, "foo")); - testassert(NULL == class_getInstanceVariable([Sub class], NULL)); - testassert(NULL == class_getInstanceVariable(NULL, NULL)); - - testassert(NULL == object_getIvar(sub, NULL)); - testassert(NULL == object_getIvar(NULL, ivar)); - testassert(NULL == object_getIvar(NULL, NULL)); - - object_setIvar(sub, NULL, NULL); - object_setIvar(NULL, ivar, NULL); - object_setIvar(NULL, NULL, NULL); - -#if !__has_feature(objc_arc) - - uintptr_t value; - - sub->subIvar = (id)10; - value = 0; - object_getInstanceVariable(sub, "subIvar", (void **)&value); - testassert(value == 10); - - object_setInstanceVariable(sub, "subIvar", (id)11); - testassert(sub->subIvar == (id)11); - - ivar = class_getInstanceVariable([Sub class], "subIvar"); - testassert(ivar == object_getInstanceVariable(sub, "subIvar", NULL)); - - testassert(NULL == object_getInstanceVariable(sub, NULL, NULL)); - testassert(NULL == object_getInstanceVariable(NULL, "foo", NULL)); - testassert(NULL == object_getInstanceVariable(NULL, NULL, NULL)); - value = 10; - testassert(NULL == object_getInstanceVariable(sub, NULL, (void **)&value)); - testassert(value == 0); - value = 10; - testassert(NULL == object_getInstanceVariable(NULL, "foo", (void **)&value)); - testassert(value == 0); - value = 10; - testassert(NULL == object_getInstanceVariable(NULL, NULL, (void **)&value)); - testassert(value == 0); - - testassert(NULL == object_setInstanceVariable(sub, NULL, NULL)); - testassert(NULL == object_setInstanceVariable(NULL, "foo", NULL)); - testassert(NULL == object_setInstanceVariable(NULL, NULL, NULL)); -#endif - - succeed(__FILE__); - return 0; -} diff --git a/test/ivarSlide.h b/test/ivarSlide.h deleted file mode 100644 index d4e89ca..0000000 --- a/test/ivarSlide.h +++ /dev/null @@ -1,110 +0,0 @@ -@interface Super : TestRoot { - @public -#if OLD - // nothing -#else - char superIvar; -#endif -} -@end - - -@interface ShrinkingSuper : TestRoot { - @public -#if OLD - id superIvar[5]; - __weak id superIvar2[5]; -#else - // nothing -#endif -} -@end; - - -@interface MoreStrongSuper : TestRoot { - @public -#if OLD - void *superIvar; -#else - id superIvar; -#endif -} -@end; - - -@interface MoreWeakSuper : TestRoot { - @public -#if OLD - id superIvar; -#else - __weak id superIvar; -#endif -} -@end; - -@interface MoreWeak2Super : TestRoot { - @public -#if OLD - void *superIvar; -#else - __weak id superIvar; -#endif -} -@end; - -@interface LessStrongSuper : TestRoot { - @public -#if OLD - id superIvar; -#else - void *superIvar; -#endif -} -@end; - -@interface LessWeakSuper : TestRoot { - @public -#if OLD - __weak id superIvar; -#else - id superIvar; -#endif -} -@end; - -@interface LessWeak2Super : TestRoot { - @public -#if OLD - __weak id superIvar; -#else - void *superIvar; -#endif -} -@end; - -@interface NoGCChangeSuper : TestRoot { - @public - intptr_t d; - char superc1; -#if OLD - // nothing -#else - char superc2; -#endif -} -@end - -@interface RunsOf15 : TestRoot { - @public - id scan1; - intptr_t skip15[15]; - id scan15[15]; - intptr_t skip15_2[15]; - id scan15_2[15]; -#if OLD - // nothing -#else - intptr_t skip1; -#endif -} -@end diff --git a/test/ivarSlide.m b/test/ivarSlide.m deleted file mode 100644 index 43c3660..0000000 --- a/test/ivarSlide.m +++ /dev/null @@ -1,533 +0,0 @@ -/* -TEST_BUILD - $C{COMPILE} $DIR/ivarSlide1.m $DIR/ivarSlide.m -o ivarSlide.out -END -*/ - -#include "test.h" -#include -#include -#include -#include - -// ARC doesn't like __strong void* or __weak void* -#if __OBJC_GC__ -# define gc_weak __weak -# define gc_strong __strong -#else -# define gc_weak -# define gc_strong -#endif - -#define OLD 1 -#include "ivarSlide.h" - -#define ustrcmp(a, b) strcmp((char *)a, (char *)b) - -#ifdef __cplusplus -class CXX { - public: - static uintptr_t count; - uintptr_t magic; - CXX() : magic(1) { } - ~CXX() { count += magic; } -}; -uintptr_t CXX::count; -#endif - -@interface Bitfields : Super { - uint8_t uint8_ivar; - uint8_t uint8_bitfield1 :7; - uint8_t uint8_bitfield2 :1; - - id id_ivar; - - uintptr_t uintptr_ivar; - uintptr_t /*uintptr_bitfield1*/ :31; // anonymous (rdar://5723893) - uintptr_t uintptr_bitfield2 :1; - - id id_ivar2; -} -@end - -@implementation Bitfields @end - - -@interface Sub : Super { - @public - uintptr_t subIvar; - gc_strong void* subIvar2; - gc_weak void* subIvar3; -#ifdef __cplusplus - CXX cxx; -#else - // same layout as cxx - uintptr_t cxx_magic; -#endif -} -@end - -@implementation Sub @end - - -@interface Sub2 : ShrinkingSuper { - @public - gc_weak void* subIvar; - gc_strong void* subIvar2; -} -@end - -@implementation Sub2 @end - -@interface MoreStrongSub : MoreStrongSuper { id subIvar; } @end -@interface LessStrongSub : LessStrongSuper { id subIvar; } @end -@interface MoreWeakSub : MoreWeakSuper { id subIvar; } @end -@interface MoreWeak2Sub : MoreWeak2Super { id subIvar; } @end -@interface LessWeakSub : LessWeakSuper { id subIvar; } @end -@interface LessWeak2Sub : LessWeak2Super { id subIvar; } @end - -@implementation MoreStrongSub @end -@implementation LessStrongSub @end -@implementation MoreWeakSub @end -@implementation MoreWeak2Sub @end -@implementation LessWeakSub @end -@implementation LessWeak2Sub @end - -@interface NoGCChangeSub : NoGCChangeSuper { - @public - char subc3; -} -@end -@implementation NoGCChangeSub @end - -@interface RunsOf15Sub : RunsOf15 { - @public - char sub; -} -@end -@implementation RunsOf15Sub @end - - -int main(int argc __attribute__((unused)), char **argv) -{ -#if __OBJC2__ - -#if __has_feature(objc_arc) - testwarn("fixme check ARC layouts too"); -#endif - - /* - Bitfield ivars. - rdar://5723893 anonymous bitfield ivars crash when slid - rdar://5724385 bitfield ivar alignment incorrect - - Compile-time layout of Bitfields: - [0 scan] isa - [1 skip] uint8_ivar, uint8_bitfield - [2 scan] id_ivar - [3 skip] uintptr_ivar - [4 skip] uintptr_bitfield - [5 scan] id_ivar2 - - Runtime layout of Bitfields: - [0 scan] isa - [1 skip] superIvar - [2 skip] uint8_ivar, uint8_bitfield - [3 scan] id_ivar - [4 skip] uintptr_ivar - [5 skip] uintptr_bitfield - [6 scan] id_ivar2 - */ - - [Bitfields class]; - - testassert(class_getInstanceSize([Bitfields class]) == 7*sizeof(void*)); - - if (objc_collectingEnabled()) { - const uint8_t *bitfieldlayout; - bitfieldlayout = class_getIvarLayout([Bitfields class]); - testassert(0 == ustrcmp(bitfieldlayout, "\x01\x21\x21")); - - bitfieldlayout = class_getWeakIvarLayout([Bitfields class]); - testassert(bitfieldlayout == NULL); - } - - /* - Compile-time layout of Sub: - [0 scan] isa - [1 skip] subIvar - [2 scan] subIvar2 - [3 weak] subIvar3 - [6 skip] cxx - - Runtime layout of Sub: - [0 scan] isa - [1 skip] superIvar - [2 skip] subIvar - [3 scan] subIvar2 - [4 weak] subIvar3 - [6 skip] cxx - - Also, superIvar is only one byte, so subIvar's alignment must - be handled correctly. - - fixme test more layouts - */ - - Ivar ivar; - static Sub * volatile sub; - sub = [Sub new]; - sub->subIvar = 10; - testassert(((uintptr_t *)objc_unretainedPointer(sub))[2] == 10); - -#ifdef __cplusplus - testassert(((uintptr_t *)objc_unretainedPointer(sub))[5] == 1); - testassert(sub->cxx.magic == 1); - sub->cxx.magic++; - testassert(((uintptr_t *)objc_unretainedPointer(sub))[5] == 2); - testassert(sub->cxx.magic == 2); -# if __has_feature(objc_arc) - sub = nil; -# else - if (! objc_collectingEnabled()) { - [sub dealloc]; - } else { - // hack - can't get collector to reliably delete the object - object_dispose(sub); - } -# endif - testassert(CXX::count == 2); -#endif - - testassert(class_getInstanceSize([Sub class]) == 6*sizeof(void*)); - - ivar = class_getInstanceVariable([Sub class], "subIvar"); - testassert(ivar); - testassert(2*sizeof(void*) == (size_t)ivar_getOffset(ivar)); - testassert(0 == strcmp(ivar_getName(ivar), "subIvar")); - // rdar://7466570 clang miscompiles assert(#if __LP64__ ... #endif) -#if __LP64__ - testassert(0 == strcmp(ivar_getTypeEncoding(ivar), "Q")); -#elif __clang__ - testassert(0 == strcmp(ivar_getTypeEncoding(ivar), "L")); -#else - testassert(0 == strcmp(ivar_getTypeEncoding(ivar), "I")); -#endif - -#ifdef __cplusplus - ivar = class_getInstanceVariable([Sub class], "cxx"); - testassert(ivar); -#endif - - ivar = class_getInstanceVariable([Super class], "superIvar"); - testassert(ivar); - testassert(sizeof(void*) == (size_t)ivar_getOffset(ivar)); - testassert(0 == strcmp(ivar_getName(ivar), "superIvar")); - testassert(0 == strcmp(ivar_getTypeEncoding(ivar), "c")); - - ivar = class_getInstanceVariable([Super class], "subIvar"); - testassert(!ivar); - - if (objc_collectingEnabled()) { - const uint8_t *superlayout; - const uint8_t *sublayout; - superlayout = class_getIvarLayout([Super class]); - sublayout = class_getIvarLayout([Sub class]); - testassert(0 == ustrcmp(superlayout, "\x01\x10")); - testassert(0 == ustrcmp(sublayout, "\x01\x21\x20")); - - superlayout = class_getWeakIvarLayout([Super class]); - sublayout = class_getWeakIvarLayout([Sub class]); - testassert(superlayout == NULL); - testassert(0 == ustrcmp(sublayout, "\x41\x10")); - } - - /* - Shrinking superclass. - Subclass ivars do not compact, but the GC layout needs to - update, including the gap that the superclass no longer spans. - - Compile-time layout of Sub2: - [0 scan] isa - [1-5 scan] superIvar - [6-10 weak] superIvar2 - [11 weak] subIvar - [12 scan] subIvar2 - - Runtime layout of Sub2: - [0 scan] isa - [1-10 skip] was superIvar - [11 weak] subIvar - [12 scan] subIvar2 - */ - - Sub2 *sub2 = [Sub2 new]; - sub2->subIvar = (void *)10; - testassert(((uintptr_t *)objc_unretainedPointer(sub2))[11] == 10); - - testassert(class_getInstanceSize([Sub2 class]) == 13*sizeof(void*)); - - ivar = class_getInstanceVariable([Sub2 class], "subIvar"); - testassert(ivar); - testassert(11*sizeof(void*) == (size_t)ivar_getOffset(ivar)); - testassert(0 == strcmp(ivar_getName(ivar), "subIvar")); - - ivar = class_getInstanceVariable([ShrinkingSuper class], "superIvar"); - testassert(!ivar); - - if (objc_collectingEnabled()) { - const uint8_t *superlayout; - const uint8_t *sublayout; - superlayout = class_getIvarLayout([ShrinkingSuper class]); - sublayout = class_getIvarLayout([Sub2 class]); - // only `isa` is left; superIvar[] and superIvar2[] are gone - testassert(superlayout == NULL || 0 == ustrcmp(superlayout, "\x01")); - testassert(0 == ustrcmp(sublayout, "\x01\xb1")); - - superlayout = class_getWeakIvarLayout([ShrinkingSuper class]); - sublayout = class_getWeakIvarLayout([Sub2 class]); - testassert(superlayout == NULL); - testassert(0 == ustrcmp(sublayout, "\xb1\x10")); - } - - /* - Ivars slide but GC layouts stay the same - Here, the last word of the superclass is misaligned, but - its GC layout includes a bit for that whole word. - Additionally, all of the subclass ivars fit into that word too, - both before and after sliding. - The runtime will try to slide the GC layout and must not be - confused (rdar://6851700). Note that the second skip-word may or may - not actually be included, because it crosses the end of the object. - - - Compile-time layout of NoGCChangeSub: - [0 scan] isa - [1 skip] d - [2 skip] superc1, subc3 - - Runtime layout of NoGCChangeSub: - [0 scan] isa - [1 skip] d - [2 skip] superc1, superc2, subc3 - */ - if (objc_collectingEnabled()) { - Ivar ivar1 = class_getInstanceVariable([NoGCChangeSub class], "superc1"); - testassert(ivar1); - Ivar ivar2 = class_getInstanceVariable([NoGCChangeSub class], "superc2"); - testassert(ivar2); - Ivar ivar3 = class_getInstanceVariable([NoGCChangeSub class], "subc3"); - testassert(ivar3); - testassert(ivar_getOffset(ivar1) != ivar_getOffset(ivar2) && - ivar_getOffset(ivar1) != ivar_getOffset(ivar3) && - ivar_getOffset(ivar2) != ivar_getOffset(ivar3)); - } - - /* Ivar layout includes runs of 15 words. - rdar://6859875 this would generate a truncated GC layout. - */ - if (objc_collectingEnabled()) { - const uint8_t *layout = - class_getIvarLayout(objc_getClass("RunsOf15Sub")); - testassert(layout); - int totalSkip = 0; - int totalScan = 0; - // should find 30+ each of skip and scan - uint8_t c; - while ((c = *layout++)) { - totalSkip += c>>4; - totalScan += c&0xf; - } - testassert(totalSkip >= 30); - testassert(totalScan >= 30); - } - -// __OBJC2__ -#endif - - - /* - Non-strong -> strong - Classes do not change size, but GC layouts must be updated. - Both new and old ABI detect this case (rdar://5774578) - - Compile-time layout of MoreStrongSub: - [0 scan] isa - [1 skip] superIvar - [2 scan] subIvar - - Runtime layout of MoreStrongSub: - [0 scan] isa - [1 scan] superIvar - [2 scan] subIvar - */ - testassert(class_getInstanceSize([MoreStrongSub class]) == 3*sizeof(void*)); - if (objc_collectingEnabled()) { - const uint8_t *layout; - layout = class_getIvarLayout([MoreStrongSub class]); - testassert(layout == NULL); - - layout = class_getWeakIvarLayout([MoreStrongSub class]); - testassert(layout == NULL); - } - - - /* - Strong -> weak - Classes do not change size, but GC layouts must be updated. - Old ABI intentionally does not detect this case (rdar://5774578) - - Compile-time layout of MoreWeakSub: - [0 scan] isa - [1 scan] superIvar - [2 scan] subIvar - - Runtime layout of MoreWeakSub: - [0 scan] isa - [1 weak] superIvar - [2 scan] subIvar - */ - testassert(class_getInstanceSize([MoreWeakSub class]) == 3*sizeof(void*)); - if (objc_collectingEnabled()) { - const uint8_t *layout; - layout = class_getIvarLayout([MoreWeakSub class]); -#if __OBJC2__ - // fixed version: scan / weak / scan - testassert(0 == ustrcmp(layout, "\x01\x11")); -#else - // unfixed version: scan / scan / scan - testassert(layout == NULL || 0 == ustrcmp(layout, "\x03")); -#endif - - layout = class_getWeakIvarLayout([MoreWeakSub class]); -#if __OBJC2__ - testassert(0 == ustrcmp(layout, "\x11\x10")); -#else - testassert(layout == NULL); -#endif - } - - - /* - Non-strong -> weak - Classes do not change size, but GC layouts must be updated. - Old ABI intentionally does not detect this case (rdar://5774578) - - Compile-time layout of MoreWeak2Sub: - [0 scan] isa - [1 skip] superIvar - [2 scan] subIvar - - Runtime layout of MoreWeak2Sub: - [0 scan] isa - [1 weak] superIvar - [2 scan] subIvar - */ - testassert(class_getInstanceSize([MoreWeak2Sub class]) == 3*sizeof(void*)); - if (objc_collectingEnabled()) { - const uint8_t *layout; - layout = class_getIvarLayout([MoreWeak2Sub class]); - testassert(0 == ustrcmp(layout, "\x01\x11") || - 0 == ustrcmp(layout, "\x01\x10\x01")); - - layout = class_getWeakIvarLayout([MoreWeak2Sub class]); -#if __OBJC2__ - testassert(0 == ustrcmp(layout, "\x11\x10")); -#else - testassert(layout == NULL); -#endif - } - - - /* - Strong -> non-strong - Classes do not change size, but GC layouts must be updated. - Old ABI intentionally does not detect this case (rdar://5774578) - - Compile-time layout of LessStrongSub: - [0 scan] isa - [1 scan] superIvar - [2 scan] subIvar - - Runtime layout of LessStrongSub: - [0 scan] isa - [1 skip] superIvar - [2 scan] subIvar - */ - testassert(class_getInstanceSize([LessStrongSub class]) == 3*sizeof(void*)); - if (objc_collectingEnabled()) { - const uint8_t *layout; - layout = class_getIvarLayout([LessStrongSub class]); -#if __OBJC2__ - // fixed version: scan / skip / scan - testassert(0 == ustrcmp(layout, "\x01\x11")); -#else - // unfixed version: scan / scan / scan - testassert(layout == NULL || 0 == ustrcmp(layout, "\x03")); -#endif - - layout = class_getWeakIvarLayout([LessStrongSub class]); - testassert(layout == NULL); - } - - - /* - Weak -> strong - Classes do not change size, but GC layouts must be updated. - Both new and old ABI detect this case (rdar://5774578 rdar://6924114) - - Compile-time layout of LessWeakSub: - [0 scan] isa - [1 weak] superIvar - [2 scan] subIvar - - Runtime layout of LessWeakSub: - [0 scan] isa - [1 scan] superIvar - [2 scan] subIvar - */ - testassert(class_getInstanceSize([LessWeakSub class]) == 3*sizeof(void*)); - if (objc_collectingEnabled()) { - const uint8_t *layout; - layout = class_getIvarLayout([LessWeakSub class]); - testassert(layout == NULL); - - layout = class_getWeakIvarLayout([LessWeakSub class]); - testassert(layout == NULL); - } - - - /* - Weak -> non-strong - Classes do not change size, but GC layouts must be updated. - Old ABI intentionally does not detect this case (rdar://5774578) - - Compile-time layout of LessWeak2Sub: - [0 scan] isa - [1 weak] superIvar - [2 scan] subIvar - - Runtime layout of LessWeak2Sub: - [0 scan] isa - [1 skip] superIvar - [2 scan] subIvar - */ - testassert(class_getInstanceSize([LessWeak2Sub class]) == 3*sizeof(void*)); - if (objc_collectingEnabled()) { - const uint8_t *layout; - layout = class_getIvarLayout([LessWeak2Sub class]); - testassert(0 == ustrcmp(layout, "\x01\x11") || - 0 == ustrcmp(layout, "\x01\x10\x01")); - - layout = class_getWeakIvarLayout([LessWeak2Sub class]); -#if __OBJC2__ - testassert(layout == NULL); -#else - testassert(0 == ustrcmp(layout, "\x11\x10")); -#endif - } - - - succeed(basename(argv[0])); - return 0; -} diff --git a/test/ivarSlide1.m b/test/ivarSlide1.m deleted file mode 100644 index 47b2e63..0000000 --- a/test/ivarSlide1.m +++ /dev/null @@ -1,21 +0,0 @@ -#include "test.h" -#include -#include - -#define OLD 0 -#include "ivarSlide.h" - -#include "testroot.i" - -@implementation Super @end - -@implementation ShrinkingSuper @end - -@implementation MoreStrongSuper @end -@implementation LessStrongSuper @end -@implementation MoreWeakSuper @end -@implementation MoreWeak2Super @end -@implementation LessWeakSuper @end -@implementation LessWeak2Super @end -@implementation NoGCChangeSuper @end -@implementation RunsOf15 @end diff --git a/test/layout.m b/test/layout.m deleted file mode 100644 index 6c220fb..0000000 --- a/test/layout.m +++ /dev/null @@ -1,97 +0,0 @@ -// TEST_CONFIG MEM=gc OS=macosx - -#include "test.h" -#include -#include - -@class NSObject; - -void printlayout(const char *name, const uint8_t *layout) -{ - testprintf("%s: ", name); - - if (!layout) { - testprintf("NULL\n"); - return; - } - - const uint8_t *c; - for (c = layout; *c; c++) { - testprintf("%02x ", *c); - } - - testprintf("00\n"); -} - -OBJC_ROOT_CLASS -@interface Super { id isa; } @end -@implementation Super @end - - -// strong: 0c 00 (0a00 without structs) -// weak: NULL -@interface AllScanned : Super { - id id1; - NSObject *o1; - __strong void *v1; - __strong intptr_t *i1; - __strong long *l1; - /* fixme - struct { - id id1; - id id2; - } str; - */ - id arr1[4]; -} -@end -@implementation AllScanned @end - -// strong: 00 -// weak: 1b 00 (18 00 without structs) -@interface AllWeak : Super { - __weak id id1; - __weak NSObject *o1; - __weak void *v1; - __weak intptr_t *i1; - __weak long *l1; - /* fixme - struct { - __weak id id1; - __weak id id2; - } str; - */ - __weak id arr1[4]; -} -@end -@implementation AllWeak @end - -// strong: "" -// weak: NULL -OBJC_ROOT_CLASS -@interface NoScanned { long i; } @end -@implementation NoScanned @end - -int main() -{ - const uint8_t *layout; - - layout = class_getIvarLayout(objc_getClass("AllScanned")); - printlayout("AllScanned", layout); - layout = class_getWeakIvarLayout(objc_getClass("AllScanned")); - printlayout("AllScanned weak", layout); - // testassert(0 == strcmp(layout, "\x0a")); - - layout = class_getIvarLayout(objc_getClass("AllWeak")); - printlayout("AllWeak", layout); - layout = class_getWeakIvarLayout(objc_getClass("AllWeak")); - printlayout("AllWeak weak", layout); - // testassert(0 == strcmp(layout, "")); - - layout = class_getIvarLayout(objc_getClass("NoScanned")); - printlayout("NoScanned", layout); - // testassert(0 == strcmp(layout, "")); - - succeed(__FILE__); - return 0; -} diff --git a/test/literals.m b/test/literals.m deleted file mode 100644 index 17fffd2..0000000 --- a/test/literals.m +++ /dev/null @@ -1,56 +0,0 @@ -// TEST_CONFIG MEM=arc,mrc CC=clang LANGUAGE=objc,objc++ -// TEST_CFLAGS -framework Foundation - -#import -#import -#import -#import -#import -#include "test.h" - -int main() { - PUSH_POOL { - -#if __has_feature(objc_bool) // placeholder until we get a more precise macro. - NSArray *array = @[ @1, @2, @YES, @NO, @"Hello", @"World" ]; - testassert([array count] == 6); - NSDictionary *dict = @{ @"Name" : @"John Q. Public", @"Age" : @42 }; - testassert([dict count] == 2); - NSDictionary *numbers = @{ @"π" : @M_PI, @"e" : @M_E }; - testassert([[numbers objectForKey:@"π"] doubleValue] == M_PI); - testassert([[numbers objectForKey:@"e"] doubleValue] == M_E); - - BOOL yesBool = YES; - BOOL noBool = NO; - array = @[ - @(true), - @(YES), - [NSNumber numberWithBool:YES], - @YES, - @(yesBool), - @((BOOL)YES), - - @(false), - @(NO), - [NSNumber numberWithBool:NO], - @NO, - @(noBool), - @((BOOL)NO), - ]; - NSData * jsonData = [NSJSONSerialization dataWithJSONObject:array options:0 error:nil]; - NSString * string = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding]; -#if __cplusplus - testassert([string isEqualToString:@"[true,true,true,true,true,true,false,false,false,false,false,false]"]); -#else - // C99 @(true) and @(false) evaluate to @(1) and @(0). - testassert([string isEqualToString:@"[1,true,true,true,true,true,0,false,false,false,false,false]"]); -#endif - -#endif - - } POP_POOL; - - succeed(__FILE__); - - return 0; -} diff --git a/test/load-noobjc.m b/test/load-noobjc.m deleted file mode 100644 index e877e17..0000000 --- a/test/load-noobjc.m +++ /dev/null @@ -1,52 +0,0 @@ -/* -TEST_BUILD - $C{COMPILE} $DIR/load-noobjc.m -o load-noobjc.out - $C{COMPILE} $DIR/load-noobjc2.m -o libload-noobjc2.dylib -bundle -bundle_loader load-noobjc.out - $C{COMPILE} $DIR/load-noobjc3.m -o libload-noobjc3.dylib -bundle -bundle_loader load-noobjc.out -END -*/ - -#include "test.h" - -#if !__OBJC2__ -// old runtime can't fix this deadlock - -int main() -{ - succeed(__FILE__); -} - -#else - -#include - -int state = 0; -semaphore_t go; - -void *thread(void *arg __unused) -{ - objc_registerThreadWithCollector(); - dlopen("libload-noobjc2.dylib", RTLD_LAZY); - fail("dlopen should not have returned"); -} - -int main() -{ - semaphore_create(mach_task_self(), &go, SYNC_POLICY_FIFO, 0); - - pthread_t th; - pthread_create(&th, nil, &thread, nil); - - // Wait for thread to stop in libload-noobjc2's +load method. - semaphore_wait(go); - - // run nooobjc3's constructor function. - // There's no objc code here so it shouldn't require the +load lock. - void *dlh = dlopen("libload-noobjc3.dylib", RTLD_LAZY); - testassert(dlh); - testassert(state == 1); - - succeed(__FILE__); -} - -#endif diff --git a/test/load-noobjc2.m b/test/load-noobjc2.m deleted file mode 100644 index e8cf37d..0000000 --- a/test/load-noobjc2.m +++ /dev/null @@ -1,16 +0,0 @@ -#include "test.h" -#if __OBJC2__ - -extern semaphore_t go; - -OBJC_ROOT_CLASS -@interface noobjc @end -@implementation noobjc -+(void)load -{ - semaphore_signal(go); - while (1) sleep(1); -} -@end - -#endif diff --git a/test/load-noobjc3.m b/test/load-noobjc3.m deleted file mode 100644 index fc7cdb6..0000000 --- a/test/load-noobjc3.m +++ /dev/null @@ -1,13 +0,0 @@ -#include "test.h" - -#if __OBJC2__ - -extern int state; - -__attribute__((constructor)) -static void ctor(void) -{ - state = 1; -} - -#endif diff --git a/test/load-order.m b/test/load-order.m deleted file mode 100644 index fd09370..0000000 --- a/test/load-order.m +++ /dev/null @@ -1,18 +0,0 @@ -/* -TEST_BUILD - $C{COMPILE} $DIR/load-order3.m -o load-order3.dylib -dynamiclib - $C{COMPILE} $DIR/load-order2.m -o load-order2.dylib -x none load-order3.dylib -dynamiclib - $C{COMPILE} $DIR/load-order1.m -o load-order1.dylib -x none load-order3.dylib load-order2.dylib -dynamiclib - $C{COMPILE} $DIR/load-order.m -o load-order.out -x none load-order3.dylib load-order2.dylib load-order1.dylib -END -*/ - -#include "test.h" - -extern int state1, state2, state3; - -int main() -{ - testassert(state1 == 1 && state2 == 2 && state3 == 3); - succeed(__FILE__); -} diff --git a/test/load-order1.m b/test/load-order1.m deleted file mode 100644 index cdfa075..0000000 --- a/test/load-order1.m +++ /dev/null @@ -1,15 +0,0 @@ -#include "test.h" - -extern int state2, state3; - -int state1 = 0; - -OBJC_ROOT_CLASS -@interface One @end -@implementation One -+(void)load -{ - testassert(state2 == 2 && state3 == 3); - state1 = 1; -} -@end diff --git a/test/load-order2.m b/test/load-order2.m deleted file mode 100644 index 7537754..0000000 --- a/test/load-order2.m +++ /dev/null @@ -1,15 +0,0 @@ -#include "test.h" - -extern int state3; - -int state2 = 0; - -OBJC_ROOT_CLASS -@interface Two @end -@implementation Two -+(void)load -{ - testassert(state3 == 3); - state2 = 2; -} -@end diff --git a/test/load-order3.m b/test/load-order3.m deleted file mode 100644 index 7c34d5a..0000000 --- a/test/load-order3.m +++ /dev/null @@ -1,12 +0,0 @@ -#include "test.h" - -int state3 = 0; - -OBJC_ROOT_CLASS -@interface Three @end -@implementation Three -+(void)load -{ - state3 = 3; -} -@end diff --git a/test/load-parallel.m b/test/load-parallel.m deleted file mode 100644 index f050ea5..0000000 --- a/test/load-parallel.m +++ /dev/null @@ -1,65 +0,0 @@ -/* -TEST_BUILD - $C{COMPILE} $DIR/load-parallel00.m -o load-parallel00.dylib -dynamiclib - $C{COMPILE} $DIR/load-parallel.m -x none load-parallel00.dylib -o load-parallel.out -DCOUNT=10 - - $C{COMPILE} $DIR/load-parallel0.m -x none load-parallel00.dylib -o load-parallel0.dylib -dynamiclib -DN=0 - $C{COMPILE} $DIR/load-parallel0.m -x none load-parallel00.dylib -o load-parallel1.dylib -dynamiclib -DN=1 - $C{COMPILE} $DIR/load-parallel0.m -x none load-parallel00.dylib -o load-parallel2.dylib -dynamiclib -DN=2 - $C{COMPILE} $DIR/load-parallel0.m -x none load-parallel00.dylib -o load-parallel3.dylib -dynamiclib -DN=3 - $C{COMPILE} $DIR/load-parallel0.m -x none load-parallel00.dylib -o load-parallel4.dylib -dynamiclib -DN=4 - $C{COMPILE} $DIR/load-parallel0.m -x none load-parallel00.dylib -o load-parallel5.dylib -dynamiclib -DN=5 - $C{COMPILE} $DIR/load-parallel0.m -x none load-parallel00.dylib -o load-parallel6.dylib -dynamiclib -DN=6 - $C{COMPILE} $DIR/load-parallel0.m -x none load-parallel00.dylib -o load-parallel7.dylib -dynamiclib -DN=7 - $C{COMPILE} $DIR/load-parallel0.m -x none load-parallel00.dylib -o load-parallel8.dylib -dynamiclib -DN=8 - $C{COMPILE} $DIR/load-parallel0.m -x none load-parallel00.dylib -o load-parallel9.dylib -dynamiclib -DN=9 -END -*/ - -#include "test.h" - -#include -#include - -#ifndef COUNT -#error -DCOUNT=c missing -#endif - -extern int state; - -void *thread(void *arg) -{ - uintptr_t num = (uintptr_t)arg; - char *buf; - - objc_registerThreadWithCollector(); - - asprintf(&buf, "load-parallel%lu.dylib", (unsigned long)num); - testprintf("%s\n", buf); - void *dlh = dlopen(buf, RTLD_LAZY); - if (!dlh) { - fail("dlopen failed: %s", dlerror()); - } - free(buf); - - return NULL; -} - -int main() -{ - pthread_t t[COUNT]; - uintptr_t i; - - for (i = 0; i < COUNT; i++) { - pthread_create(&t[i], NULL, thread, (void *)i); - } - - for (i = 0; i < COUNT; i++) { - pthread_join(t[i], NULL); - } - - testprintf("loaded %d/%d\n", state, COUNT*26); - testassert(state == COUNT*26); - - succeed(__FILE__); -} diff --git a/test/load-parallel0.m b/test/load-parallel0.m deleted file mode 100644 index 9f3a3e1..0000000 --- a/test/load-parallel0.m +++ /dev/null @@ -1,46 +0,0 @@ -#ifndef N -#error -DN=n missing -#endif - -#import -#include -#include -#include -#include -extern int state; - -#define CLASS0(n,nn) \ - OBJC_ROOT_CLASS \ - @interface C_##n##_##nn @end \ - @implementation C_##n##_##nn \ - +(void)load { OSAtomicIncrement32(&state); usleep(10); } \ - @end - -#define CLASS(n,nn) CLASS0(n,nn) - -CLASS(a,N) -CLASS(b,N) -CLASS(c,N) -CLASS(d,N) -CLASS(e,N) -CLASS(f,N) -CLASS(g,N) -CLASS(h,N) -CLASS(i,N) -CLASS(j,N) -CLASS(k,N) -CLASS(l,N) -CLASS(m,N) -CLASS(n,N) -CLASS(o,N) -CLASS(p,N) -CLASS(q,N) -CLASS(r,N) -CLASS(s,N) -CLASS(t,N) -CLASS(u,N) -CLASS(v,N) -CLASS(w,N) -CLASS(x,N) -CLASS(y,N) -CLASS(z,N) diff --git a/test/load-parallel00.m b/test/load-parallel00.m deleted file mode 100644 index 4bef2b6..0000000 --- a/test/load-parallel00.m +++ /dev/null @@ -1 +0,0 @@ -int state = 0; diff --git a/test/load-reentrant.m b/test/load-reentrant.m deleted file mode 100644 index 25caf77..0000000 --- a/test/load-reentrant.m +++ /dev/null @@ -1,36 +0,0 @@ -/* -TEST_BUILD - $C{COMPILE} $DIR/load-reentrant.m -o load-reentrant.out - $C{COMPILE} $DIR/load-reentrant2.m -o libload-reentrant2.dylib -bundle -bundle_loader load-reentrant.out -END -*/ - -#include "test.h" -#include - -int state1 = 0; -int *state2_p; - -OBJC_ROOT_CLASS -@interface One @end -@implementation One -+(void)load -{ - state1 = 111; - - // Re-entrant +load doesn't get to complete until we do - void *dlh = dlopen("libload-reentrant2.dylib", RTLD_LAZY); - testassert(dlh); - state2_p = (int *)dlsym(dlh, "state2"); - testassert(state2_p); - testassert(*state2_p == 0); - - state1 = 1; -} -@end - -int main() -{ - testassert(state1 == 1 && state2_p && *state2_p == 2); - succeed(__FILE__); -} diff --git a/test/load-reentrant2.m b/test/load-reentrant2.m deleted file mode 100644 index 0cc6a40..0000000 --- a/test/load-reentrant2.m +++ /dev/null @@ -1,23 +0,0 @@ -#include "test.h" - -int state2 = 0; -extern int state1; - -static void ctor(void) __attribute__((constructor)); -static void ctor(void) -{ - // should be called during One's dlopen(), before Two's +load - testassert(state1 == 111); - testassert(state2 == 0); -} - -OBJC_ROOT_CLASS -@interface Two @end -@implementation Two -+(void) load -{ - // Does not run until One's +load completes - testassert(state1 == 1); - state2 = 2; -} -@end diff --git a/test/load.m b/test/load.m deleted file mode 100644 index 92bcdfe..0000000 --- a/test/load.m +++ /dev/null @@ -1,106 +0,0 @@ -// TEST_CONFIG - -#include "test.h" -#include "testroot.i" - -int state = 0; -int catstate = 0; -int deallocstate = 0; - -@interface Deallocator : TestRoot @end -@implementation Deallocator --(id)init { - self = [super init]; - if (objc_collectingEnabled()) { - deallocstate = 1; - } - return self; -} --(void)dealloc { - deallocstate = 1; - SUPER_DEALLOC(); -} -@end - - -@interface Super : TestRoot @end -@implementation Super -+(void)initialize { - if (self == [Super class]) { - testprintf("in +[Super initialize]\n"); - testassert(state == 2); - state = 3; - } else { - testprintf("in +[Super initialize] on behalf of Sub\n"); - testassert(state == 3); - state = 4; - } -} --(void)load { fail("-[Super load] called!"); } -+(void)load { - testprintf("in +[Super load]\n"); - testassert(state == 0); - state = 1; -} -@end - -@interface Sub : Super { } @end -@implementation Sub -+(void)load { - testprintf("in +[Sub load]\n"); - testassert(state == 1); - state = 2; -} --(void)load { fail("-[Sub load] called!"); } -@end - -@interface SubNoLoad : Super { } @end -@implementation SubNoLoad @end - -@interface Super (Category) @end -@implementation Super (Category) --(void)load { fail("-[Super(Category) load called!"); } -+(void)load { - testprintf("in +[Super(Category) load]\n"); - testassert(state >= 1); - catstate++; -} -@end - - -@interface Sub (Category) @end -@implementation Sub (Category) --(void)load { fail("-[Sub(Category) load called!"); } -+(void)load { - testprintf("in +[Sub(Category) load]\n"); - testassert(state >= 2); - catstate++; - - // test autorelease pool - __autoreleasing id x; - x = AUTORELEASE([Deallocator new]); -} -@end - - -@interface SubNoLoad (Category) @end -@implementation SubNoLoad (Category) --(void)load { fail("-[SubNoLoad(Category) load called!"); } -+(void)load { - testprintf("in +[SubNoLoad(Category) load]\n"); - testassert(state >= 1); - catstate++; -} -@end - -int main() -{ - testassert(state == 2); - testassert(catstate == 3); - testassert(deallocstate == 1); - [Sub class]; - testassert(state == 4); - testassert(catstate == 3); - - succeed(__FILE__); -} diff --git a/test/methodArgs.m b/test/methodArgs.m deleted file mode 100644 index 8948e9a..0000000 --- a/test/methodArgs.m +++ /dev/null @@ -1,166 +0,0 @@ -// TEST_CFLAGS -Wno-deprecated-declarations - -#include "test.h" -#include "testroot.i" -#include -#include - -@interface Super : TestRoot @end -@implementation Super -+(id)method:(int)__unused arg :(void(^)(void)) __unused arg2 { - return 0; -} -@end - - -int main() -{ - char buf[128]; - char *arg; - struct objc_method_description *desc; - Method m = class_getClassMethod([Super class], sel_registerName("method::")); - testassert(m); - - testassert(method_getNumberOfArguments(m) == 4); -#if !__OBJC2__ - testassert(method_getSizeOfArguments(m) == 16); -#endif - - arg = method_copyArgumentType(m, 0); - testassert(arg); - testassert(0 == strcmp(arg, "@")); - memset(buf, 1, 128); - method_getArgumentType(m, 0, buf, 1+strlen(arg)); - testassert(0 == strcmp(arg, buf)); - testassert(buf[1+strlen(arg)] == 1); - memset(buf, 1, 128); - method_getArgumentType(m, 0, buf, 2); - testassert(0 == strncmp(arg, buf, 2)); - testassert(buf[2] == 1); - free(arg); - - arg = method_copyArgumentType(m, 1); - testassert(arg); - testassert(0 == strcmp(arg, ":")); - memset(buf, 1, 128); - method_getArgumentType(m, 1, buf, 1+strlen(arg)); - testassert(0 == strcmp(arg, buf)); - testassert(buf[1+strlen(arg)] == 1); - memset(buf, 1, 128); - method_getArgumentType(m, 1, buf, 2); - testassert(0 == strncmp(arg, buf, 2)); - testassert(buf[2] == 1); - free(arg); - - arg = method_copyArgumentType(m, 2); - testassert(arg); - testassert(0 == strcmp(arg, "i")); - memset(buf, 1, 128); - method_getArgumentType(m, 2, buf, 1+strlen(arg)); - testassert(0 == strcmp(arg, buf)); - testassert(buf[1+strlen(arg)] == 1); - memset(buf, 1, 128); - method_getArgumentType(m, 2, buf, 2); - testassert(0 == strncmp(arg, buf, 2)); - testassert(buf[2] == 1); - free(arg); - - arg = method_copyArgumentType(m, 3); - testassert(arg); - testassert(0 == strcmp(arg, "@?")); - memset(buf, 1, 128); - method_getArgumentType(m, 3, buf, 1+strlen(arg)); - testassert(0 == strcmp(arg, buf)); - testassert(buf[1+strlen(arg)] == 1); - memset(buf, 1, 128); - method_getArgumentType(m, 3, buf, 2); - testassert(0 == strncmp(arg, buf, 2)); - testassert(buf[2] == 1); - memset(buf, 1, 128); - method_getArgumentType(m, 3, buf, 3); - testassert(0 == strncmp(arg, buf, 3)); - testassert(buf[3] == 1); - free(arg); - - arg = method_copyArgumentType(m, 4); - testassert(!arg); - - arg = method_copyArgumentType(m, -1); - testassert(!arg); - - memset(buf, 1, 128); - method_getArgumentType(m, 4, buf, 127); - testassert(buf[0] == 0); - testassert(buf[1] == 0); - testassert(buf[127] == 1); - - memset(buf, 1, 128); - method_getArgumentType(m, -1, buf, 127); - testassert(buf[0] == 0); - testassert(buf[1] == 0); - testassert(buf[127] == 1); - - arg = method_copyReturnType(m); - testassert(arg); - testassert(0 == strcmp(arg, "@")); - memset(buf, 1, 128); - method_getReturnType(m, buf, 1+strlen(arg)); - testassert(0 == strcmp(arg, buf)); - testassert(buf[1+strlen(arg)] == 1); - memset(buf, 1, 128); - method_getReturnType(m, buf, 2); - testassert(0 == strncmp(arg, buf, 2)); - testassert(buf[2] == 1); - free(arg); - - desc = method_getDescription(m); - testassert(desc); - testassert(desc->name == sel_registerName("method::")); -#if __LP64__ - testassert(0 == strcmp(desc->types, "@28@0:8i16@?20")); -#else - testassert(0 == strcmp(desc->types, "@16@0:4i8@?12")); -#endif - - testassert(0 == method_getNumberOfArguments(NULL)); -#if !__OBJC2__ - testassert(0 == method_getSizeOfArguments(NULL)); -#endif - testassert(NULL == method_copyArgumentType(NULL, 10)); - testassert(NULL == method_copyReturnType(NULL)); - testassert(NULL == method_getDescription(NULL)); - - memset(buf, 1, 128); - method_getArgumentType(NULL, 1, buf, 127); - testassert(buf[0] == 0); - testassert(buf[1] == 0); - testassert(buf[127] == 1); - - memset(buf, 1, 128); - method_getArgumentType(NULL, 1, buf, 0); - testassert(buf[0] == 1); - testassert(buf[1] == 1); - - method_getArgumentType(m, 1, NULL, 128); - method_getArgumentType(m, 1, NULL, 0); - method_getArgumentType(NULL, 1, NULL, 128); - method_getArgumentType(NULL, 1, NULL, 0); - - memset(buf, 1, 128); - method_getReturnType(NULL, buf, 127); - testassert(buf[0] == 0); - testassert(buf[1] == 0); - testassert(buf[127] == 1); - - memset(buf, 1, 128); - method_getReturnType(NULL, buf, 0); - testassert(buf[0] == 1); - testassert(buf[1] == 1); - - method_getReturnType(m, NULL, 128); - method_getReturnType(m, NULL, 0); - method_getReturnType(NULL, NULL, 128); - method_getReturnType(NULL, NULL, 0); - - succeed(__FILE__); -} diff --git a/test/methodListSize.m b/test/methodListSize.m deleted file mode 100644 index 3821844..0000000 --- a/test/methodListSize.m +++ /dev/null @@ -1,56 +0,0 @@ -// TEST_CONFIG -// rdar://8052003 rdar://8077031 - -#include "test.h" - -#include -#include - -// add SELCOUNT methods to each of CLASSCOUNT classes -#define CLASSCOUNT 100 -#define SELCOUNT 200 - -int main() -{ - int i, j; - malloc_statistics_t start, end; - - Class root; - root = objc_allocateClassPair(NULL, "Root", 0); - objc_registerClassPair(root); - - Class classes[CLASSCOUNT]; - for (i = 0; i < CLASSCOUNT; i++) { - char *classname; - asprintf(&classname, "GrP_class_%d", i); - classes[i] = objc_allocateClassPair(root, classname, 0); - objc_registerClassPair(classes[i]); - free(classname); - } - - SEL selectors[SELCOUNT]; - for (i = 0; i < SELCOUNT; i++) { - char *selname; - asprintf(&selname, "GrP_sel_%d", i); - selectors[i] = sel_registerName(selname); - free(selname); - } - - malloc_zone_statistics(NULL, &start); - - for (i = 0; i < CLASSCOUNT; i++) { - for (j = 0; j < SELCOUNT; j++) { - class_addMethod(classes[i], selectors[j], (IMP)main, ""); - } - } - - malloc_zone_statistics(NULL, &end); - - // expected: 3-word method struct plus two other words - ssize_t expected = (sizeof(void*) * (3+2)) * SELCOUNT * CLASSCOUNT; - ssize_t actual = end.size_in_use - start.size_in_use; - testassert(actual < expected * 3); // allow generous fudge factor - - succeed(__FILE__); -} - diff --git a/test/method_getName.m b/test/method_getName.m deleted file mode 100644 index f3f7413..0000000 --- a/test/method_getName.m +++ /dev/null @@ -1,27 +0,0 @@ -// TEST_CONFIG - -#include "test.h" -#include -#include - -#undef SUPPORT_NONPOINTER_ISA // remove test.h's definition -#include "../runtime/objc-config.h" - -int main() { - unsigned i; - Class c = [NSObject class]; - unsigned numMethods; - Method *methods = class_copyMethodList(c, &numMethods); - - for (i=0; i method_getName crash on NSObject method when GC is enabled - SEL aMethod; - aMethod = method_getName(methods[i]); -#if defined(kIgnore) - if (aMethod == (SEL)kIgnore) - fail(__FILE__); -#endif - } - - succeed(__FILE__); -} diff --git a/test/msgSend.m b/test/msgSend.m deleted file mode 100644 index bd3e9b1..0000000 --- a/test/msgSend.m +++ /dev/null @@ -1,2278 +0,0 @@ -// TEST_CFLAGS -Wno-unused-parameter -Wundeclared-selector - -#include "test.h" -#include "testroot.i" - -#if __cplusplus && !__clang__ - -int main() -{ - // llvm-g++ is confused by @selector(foo::) and will never be fixed - succeed(__FILE__); -} - -#else - -#include -#include -#include -#include -#include - -// rdar://21694990 simd.h should have a vector_equal(a, b) function -static bool vector_equal(vector_ulong2 lhs, vector_ulong2 rhs) { - return vector_all(lhs == rhs); -} - -#if __arm64__ - // no stret dispatchers -# define SUPPORT_STRET 0 -# define objc_msgSend_stret objc_msgSend -# define objc_msgSendSuper2_stret objc_msgSendSuper2 -# define objc_msgSend_stret_debug objc_msgSend_debug -# define objc_msgSendSuper2_stret_debug objc_msgSendSuper2_debug -# define method_invoke_stret method_invoke -#else -# define SUPPORT_STRET 1 -#endif - - -#if defined(__arm__) -// rdar://8331406 -# define ALIGN_() -#else -# define ALIGN_() asm(".align 4"); -#endif - -@interface Super : TestRoot @end - -@interface Sub : Super @end - -static int state = 0; - -static id SELF; - -// for typeof() shorthand only -id (*idmsg0)(id, SEL) __attribute__((unused)); -long long (*llmsg0)(id, SEL) __attribute__((unused)); -// struct stret (*stretmsg0)(id, SEL) __attribute__((unused)); -double (*fpmsg0)(id, SEL) __attribute__((unused)); -long double (*lfpmsg0)(id, SEL) __attribute__((unused)); -vector_ulong2 (*vecmsg0)(id, SEL) __attribute__((unused)); - -#define VEC1 ((vector_ulong2){1, 1}) -#define VEC2 ((vector_ulong2){2, 2}) -#define VEC3 ((vector_ulong2){3, 3}) -#define VEC4 ((vector_ulong2){4, 4}) -#define VEC5 ((vector_ulong2){5, 5}) -#define VEC6 ((vector_ulong2){6, 6}) -#define VEC7 ((vector_ulong2){7, 7}) -#define VEC8 ((vector_ulong2){8, 8}) - -#define CHECK_ARGS(sel) \ -do { \ - testassert(self == SELF); \ - testassert(_cmd == sel_registerName(#sel "::::::::::::::::::::::::::::::::::::"));\ - testassert(vector_all(v1 == 1)); \ - testassert(vector_all(v2 == 2)); \ - testassert(vector_all(v3 == 3)); \ - testassert(vector_all(v4 == 4)); \ - testassert(vector_all(v5 == 5)); \ - testassert(vector_all(v6 == 6)); \ - testassert(vector_all(v7 == 7)); \ - testassert(vector_all(v8 == 8)); \ - testassert(i1 == 1); \ - testassert(i2 == 2); \ - testassert(i3 == 3); \ - testassert(i4 == 4); \ - testassert(i5 == 5); \ - testassert(i6 == 6); \ - testassert(i7 == 7); \ - testassert(i8 == 8); \ - testassert(i9 == 9); \ - testassert(i10 == 10); \ - testassert(i11 == 11); \ - testassert(i12 == 12); \ - testassert(i13 == 13); \ - testassert(f1 == 1.0); \ - testassert(f2 == 2.0); \ - testassert(f3 == 3.0); \ - testassert(f4 == 4.0); \ - testassert(f5 == 5.0); \ - testassert(f6 == 6.0); \ - testassert(f7 == 7.0); \ - testassert(f8 == 8.0); \ - testassert(f9 == 9.0); \ - testassert(f10 == 10.0); \ - testassert(f11 == 11.0); \ - testassert(f12 == 12.0); \ - testassert(f13 == 13.0); \ - testassert(f14 == 14.0); \ - testassert(f15 == 15.0); \ -} while (0) - -#define CHECK_ARGS_NOARG(sel) \ -do { \ - testassert(self == SELF); \ - testassert(_cmd == sel_registerName(#sel "_noarg"));\ -} while (0) - -id NIL_RECEIVER; -id ID_RESULT; -long long LL_RESULT = __LONG_LONG_MAX__ - 2LL*__INT_MAX__; -double FP_RESULT = __DBL_MIN__ + __DBL_EPSILON__; -long double LFP_RESULT = __LDBL_MIN__ + __LDBL_EPSILON__; -vector_ulong2 VEC_RESULT = { 0x1234567890abcdefULL, 0xfedcba0987654321ULL }; -// STRET_RESULT in test.h - -static struct stret zero; - -struct stret_i1 { - uintptr_t i1; -}; -struct stret_i2 { - uintptr_t i1; - uintptr_t i2; -}; -struct stret_i3 { - uintptr_t i1; - uintptr_t i2; - uintptr_t i3; -}; -struct stret_i4 { - uintptr_t i1; - uintptr_t i2; - uintptr_t i3; -}; -struct stret_i5 { - uintptr_t i1; - uintptr_t i2; - uintptr_t i3; - uintptr_t i4; - uintptr_t i5; -}; -struct stret_i6 { - uintptr_t i1; - uintptr_t i2; - uintptr_t i3; - uintptr_t i4; - uintptr_t i5; - uintptr_t i6; -}; -struct stret_i7 { - uintptr_t i1; - uintptr_t i2; - uintptr_t i3; - uintptr_t i4; - uintptr_t i5; - uintptr_t i6; - uintptr_t i7; -}; -struct stret_i8 { - uintptr_t i1; - uintptr_t i2; - uintptr_t i3; - uintptr_t i4; - uintptr_t i5; - uintptr_t i8; - uintptr_t i9; -}; -struct stret_i9 { - uintptr_t i1; - uintptr_t i2; - uintptr_t i3; - uintptr_t i4; - uintptr_t i5; - uintptr_t i6; - uintptr_t i7; - uintptr_t i8; - uintptr_t i9; -}; - -struct stret_d1 { - double d1; -}; -struct stret_d2 { - double d1; - double d2; -}; -struct stret_d3 { - double d1; - double d2; - double d3; -}; -struct stret_d4 { - double d1; - double d2; - double d3; -}; -struct stret_d5 { - double d1; - double d2; - double d3; - double d4; - double d5; -}; -struct stret_d6 { - double d1; - double d2; - double d3; - double d4; - double d5; - double d6; -}; -struct stret_d7 { - double d1; - double d2; - double d3; - double d4; - double d5; - double d6; - double d7; -}; -struct stret_d8 { - double d1; - double d2; - double d3; - double d4; - double d5; - double d8; - double d9; -}; -struct stret_d9 { - double d1; - double d2; - double d3; - double d4; - double d5; - double d6; - double d7; - double d8; - double d9; -}; - - -@interface Super (Prototypes) - -// Method prototypes to pacify -Wundeclared-selector. - --(id)idret: - (vector_ulong2)v1 :(vector_ulong2)v2 :(vector_ulong2)v3 :(vector_ulong2)v4 :(vector_ulong2)v5 :(vector_ulong2)v6 :(vector_ulong2)v7 :(vector_ulong2)v8 :(int)i1 :(int)i2 :(int)i3 :(int)i4 :(int)i5 :(int)i6 :(int)i7 :(int)i8 :(int)i9 :(int)i10 :(int)i11 :(int)i12 :(int)i13 :(double)f1 :(double)f2 :(double)f3 :(double)f4 :(double)f5 :(double)f6 :(double)f7 :(double)f8 :(double)f9 :(double)f10 :(double)f11 :(double)f12 :(double)f13 :(double)f14 :(double)f15; - --(long long)llret: - (vector_ulong2)v1 :(vector_ulong2)v2 :(vector_ulong2)v3 :(vector_ulong2)v4 :(vector_ulong2)v5 :(vector_ulong2)v6 :(vector_ulong2)v7 :(vector_ulong2)v8 :(int)i1 :(int)i2 :(int)i3 :(int)i4 :(int)i5 :(int)i6 :(int)i7 :(int)i8 :(int)i9 :(int)i10 :(int)i11 :(int)i12 :(int)i13 :(double)f1 :(double)f2 :(double)f3 :(double)f4 :(double)f5 :(double)f6 :(double)f7 :(double)f8 :(double)f9 :(double)f10 :(double)f11 :(double)f12 :(double)f13 :(double)f14 :(double)f15; - --(struct stret)stret: - (vector_ulong2)v1 :(vector_ulong2)v2 :(vector_ulong2)v3 :(vector_ulong2)v4 :(vector_ulong2)v5 :(vector_ulong2)v6 :(vector_ulong2)v7 :(vector_ulong2)v8 :(int)i1 :(int)i2 :(int)i3 :(int)i4 :(int)i5 :(int)i6 :(int)i7 :(int)i8 :(int)i9 :(int)i10 :(int)i11 :(int)i12 :(int)i13 :(double)f1 :(double)f2 :(double)f3 :(double)f4 :(double)f5 :(double)f6 :(double)f7 :(double)f8 :(double)f9 :(double)f10 :(double)f11 :(double)f12 :(double)f13 :(double)f14 :(double)f15; - --(double)fpret: - (vector_ulong2)v1 :(vector_ulong2)v2 :(vector_ulong2)v3 :(vector_ulong2)v4 :(vector_ulong2)v5 :(vector_ulong2)v6 :(vector_ulong2)v7 :(vector_ulong2)v8 :(int)i1 :(int)i2 :(int)i3 :(int)i4 :(int)i5 :(int)i6 :(int)i7 :(int)i8 :(int)i9 :(int)i10 :(int)i11 :(int)i12 :(int)i13 :(double)f1 :(double)f2 :(double)f3 :(double)f4 :(double)f5 :(double)f6 :(double)f7 :(double)f8 :(double)f9 :(double)f10 :(double)f11 :(double)f12 :(double)f13 :(double)f14 :(double)f15; - --(long double)lfpret: - (vector_ulong2)v1 :(vector_ulong2)v2 :(vector_ulong2)v3 :(vector_ulong2)v4 :(vector_ulong2)v5 :(vector_ulong2)v6 :(vector_ulong2)v7 :(vector_ulong2)v8 :(int)i1 :(int)i2 :(int)i3 :(int)i4 :(int)i5 :(int)i6 :(int)i7 :(int)i8 :(int)i9 :(int)i10 :(int)i11 :(int)i12 :(int)i13 :(double)f1 :(double)f2 :(double)f3 :(double)f4 :(double)f5 :(double)f6 :(double)f7 :(double)f8 :(double)f9 :(double)f10 :(double)f11 :(double)f12 :(double)f13 :(double)f14 :(double)f15; - --(vector_ulong2)vecret: - (vector_ulong2)v1 :(vector_ulong2)v2 :(vector_ulong2)v3 :(vector_ulong2)v4 :(vector_ulong2)v5 :(vector_ulong2)v6 :(vector_ulong2)v7 :(vector_ulong2)v8 :(int)i1 :(int)i2 :(int)i3 :(int)i4 :(int)i5 :(int)i6 :(int)i7 :(int)i8 :(int)i9 :(int)i10 :(int)i11 :(int)i12 :(int)i13 :(double)f1 :(double)f2 :(double)f3 :(double)f4 :(double)f5 :(double)f6 :(double)f7 :(double)f8 :(double)f9 :(double)f10 :(double)f11 :(double)f12 :(double)f13 :(double)f14 :(double)f15; - -@end - - -// Zero all volatile registers. -#if __cplusplus -extern "C" -#endif -void stomp(void); - -#if __x86_64__ -asm("\n .text" - "\n .globl _stomp" - "\n _stomp:" - "\n mov $0, %rax" - "\n mov $0, %rcx" - "\n mov $0, %rdx" - "\n mov $0, %rsi" - "\n mov $0, %rdi" - "\n mov $0, %r8" - "\n mov $0, %r9" - "\n mov $0, %r10" - "\n mov $0, %r11" - "\n xorps %xmm0, %xmm0" - "\n xorps %xmm1, %xmm1" - "\n xorps %xmm2, %xmm2" - "\n xorps %xmm3, %xmm3" - "\n xorps %xmm4, %xmm4" - "\n xorps %xmm5, %xmm5" - "\n xorps %xmm6, %xmm6" - "\n xorps %xmm7, %xmm7" - "\n xorps %xmm8, %xmm8" - "\n xorps %xmm9, %xmm9" - "\n xorps %xmm10, %xmm10" - "\n xorps %xmm11, %xmm11" - "\n xorps %xmm12, %xmm12" - "\n xorps %xmm13, %xmm13" - "\n xorps %xmm14, %xmm14" - "\n xorps %xmm15, %xmm15" - "\n ret"); - -#elif __i386__ -asm("\n .text" - "\n .globl _stomp" - "\n _stomp:" - "\n mov $0, %eax" - "\n mov $0, %ecx" - "\n mov $0, %edx" - "\n xorps %xmm0, %xmm0" - "\n xorps %xmm1, %xmm1" - "\n xorps %xmm2, %xmm2" - "\n xorps %xmm3, %xmm3" - "\n xorps %xmm4, %xmm4" - "\n xorps %xmm5, %xmm5" - "\n xorps %xmm6, %xmm6" - "\n xorps %xmm7, %xmm7" - "\n ret"); - -#elif __arm64__ -asm("\n .text" - "\n .globl _stomp" - "\n _stomp:" - "\n mov x0, #0" - "\n mov x1, #0" - "\n mov x2, #0" - "\n mov x3, #0" - "\n mov x4, #0" - "\n mov x5, #0" - "\n mov x6, #0" - "\n mov x7, #0" - "\n mov x8, #0" - "\n mov x9, #0" - "\n mov x10, #0" - "\n mov x11, #0" - "\n mov x12, #0" - "\n mov x13, #0" - "\n mov x14, #0" - "\n mov x15, #0" - "\n mov x16, #0" - "\n mov x17, #0" - "\n movi d0, #0" - "\n movi d1, #0" - "\n movi d2, #0" - "\n movi d3, #0" - "\n movi d4, #0" - "\n movi d5, #0" - "\n movi d6, #0" - "\n movi d7, #0" - "\n ret" - ); - -#elif __arm__ -asm("\n .text" - "\n .globl _stomp" - "\n .thumb_func _stomp" - "\n _stomp:" - "\n mov r0, #0" - "\n mov r1, #0" - "\n mov r2, #0" - "\n mov r3, #0" - "\n mov r9, #0" - "\n mov r12, #0" - "\n vmov.i32 q0, #0" - "\n vmov.i32 q1, #0" - "\n vmov.i32 q2, #0" - "\n vmov.i32 q3, #0" - "\n vmov.i32 q4, #0" - "\n vmov.i32 q5, #0" - "\n vmov.i32 q6, #0" - "\n vmov.i32 q7, #0" - "\n vmov.i32 q8, #0" - "\n vmov.i32 q9, #0" - "\n vmov.i32 q10, #0" - "\n vmov.i32 q11, #0" - "\n vmov.i32 q12, #0" - "\n vmov.i32 q13, #0" - "\n vmov.i32 q14, #0" - "\n vmov.i32 q15, #0" - "\n bx lr" - ); - -#else -# error unknown architecture -#endif - - -@implementation Super --(struct stret)stret { return STRET_RESULT; } - -// The IMPL_ methods are not called directly. Instead the non IMPL_ name is -// called. The resolver function installs the real method. This allows -// the resolver function to stomp on registers to help test register -// preservation in the uncached path. - -+(BOOL) resolveInstanceMethod:(SEL)sel -{ - const char *name = sel_getName(sel); - if (! strstr(name, "::::::::")) return false; - - testprintf("resolving %s\n", name); - - stomp(); - char *realName; - asprintf(&realName, "IMPL_%s", name); - SEL realSel = sel_registerName(realName); - free(realName); - - IMP imp = class_getMethodImplementation(self, realSel); - if (imp == &_objc_msgForward) return false; - return class_addMethod(self, sel, imp, ""); -} - --(id)IMPL_idret: -(vector_ulong2)v1 :(vector_ulong2)v2 :(vector_ulong2)v3 :(vector_ulong2)v4 :(vector_ulong2)v5 :(vector_ulong2)v6 :(vector_ulong2)v7 :(vector_ulong2)v8 :(int)i1 :(int)i2 :(int)i3 :(int)i4 :(int)i5 :(int)i6 :(int)i7 :(int)i8 :(int)i9 :(int)i10 :(int)i11 :(int)i12 :(int)i13 :(double)f1 :(double)f2 :(double)f3 :(double)f4 :(double)f5 :(double)f6 :(double)f7 :(double)f8 :(double)f9 :(double)f10 :(double)f11 :(double)f12 :(double)f13 :(double)f14 :(double)f15 -{ - CHECK_ARGS(idret); - state = 1; - return ID_RESULT; -} - --(long long)IMPL_llret: - (vector_ulong2)v1 :(vector_ulong2)v2 :(vector_ulong2)v3 :(vector_ulong2)v4 :(vector_ulong2)v5 :(vector_ulong2)v6 :(vector_ulong2)v7 :(vector_ulong2)v8 :(int)i1 :(int)i2 :(int)i3 :(int)i4 :(int)i5 :(int)i6 :(int)i7 :(int)i8 :(int)i9 :(int)i10 :(int)i11 :(int)i12 :(int)i13 :(double)f1 :(double)f2 :(double)f3 :(double)f4 :(double)f5 :(double)f6 :(double)f7 :(double)f8 :(double)f9 :(double)f10 :(double)f11 :(double)f12 :(double)f13 :(double)f14 :(double)f15 -{ - CHECK_ARGS(llret); - state = 2; - return LL_RESULT; -} - --(struct stret)IMPL_stret: - (vector_ulong2)v1 :(vector_ulong2)v2 :(vector_ulong2)v3 :(vector_ulong2)v4 :(vector_ulong2)v5 :(vector_ulong2)v6 :(vector_ulong2)v7 :(vector_ulong2)v8 :(int)i1 :(int)i2 :(int)i3 :(int)i4 :(int)i5 :(int)i6 :(int)i7 :(int)i8 :(int)i9 :(int)i10 :(int)i11 :(int)i12 :(int)i13 :(double)f1 :(double)f2 :(double)f3 :(double)f4 :(double)f5 :(double)f6 :(double)f7 :(double)f8 :(double)f9 :(double)f10 :(double)f11 :(double)f12 :(double)f13 :(double)f14 :(double)f15 -{ - CHECK_ARGS(stret); - state = 3; - return STRET_RESULT; -} - --(double)IMPL_fpret: - (vector_ulong2)v1 :(vector_ulong2)v2 :(vector_ulong2)v3 :(vector_ulong2)v4 :(vector_ulong2)v5 :(vector_ulong2)v6 :(vector_ulong2)v7 :(vector_ulong2)v8 :(int)i1 :(int)i2 :(int)i3 :(int)i4 :(int)i5 :(int)i6 :(int)i7 :(int)i8 :(int)i9 :(int)i10 :(int)i11 :(int)i12 :(int)i13 :(double)f1 :(double)f2 :(double)f3 :(double)f4 :(double)f5 :(double)f6 :(double)f7 :(double)f8 :(double)f9 :(double)f10 :(double)f11 :(double)f12 :(double)f13 :(double)f14 :(double)f15 -{ - CHECK_ARGS(fpret); - state = 4; - return FP_RESULT; -} - --(long double)IMPL_lfpret: - (vector_ulong2)v1 :(vector_ulong2)v2 :(vector_ulong2)v3 :(vector_ulong2)v4 :(vector_ulong2)v5 :(vector_ulong2)v6 :(vector_ulong2)v7 :(vector_ulong2)v8 :(int)i1 :(int)i2 :(int)i3 :(int)i4 :(int)i5 :(int)i6 :(int)i7 :(int)i8 :(int)i9 :(int)i10 :(int)i11 :(int)i12 :(int)i13 :(double)f1 :(double)f2 :(double)f3 :(double)f4 :(double)f5 :(double)f6 :(double)f7 :(double)f8 :(double)f9 :(double)f10 :(double)f11 :(double)f12 :(double)f13 :(double)f14 :(double)f15 -{ - CHECK_ARGS(lfpret); - state = 5; - return LFP_RESULT; -} - --(vector_ulong2)IMPL_vecret: - (vector_ulong2)v1 :(vector_ulong2)v2 :(vector_ulong2)v3 :(vector_ulong2)v4 :(vector_ulong2)v5 :(vector_ulong2)v6 :(vector_ulong2)v7 :(vector_ulong2)v8 :(int)i1 :(int)i2 :(int)i3 :(int)i4 :(int)i5 :(int)i6 :(int)i7 :(int)i8 :(int)i9 :(int)i10 :(int)i11 :(int)i12 :(int)i13 :(double)f1 :(double)f2 :(double)f3 :(double)f4 :(double)f5 :(double)f6 :(double)f7 :(double)f8 :(double)f9 :(double)f10 :(double)f11 :(double)f12 :(double)f13 :(double)f14 :(double)f15 -{ - CHECK_ARGS(vecret); - state = 6; - return VEC_RESULT; -} - - --(id)idret_noarg -{ - CHECK_ARGS_NOARG(idret); - state = 11; - return ID_RESULT; -} - --(long long)llret_noarg -{ - CHECK_ARGS_NOARG(llret); - state = 12; - return LL_RESULT; -} - --(struct stret)stret_noarg -{ - CHECK_ARGS_NOARG(stret); - state = 13; - return STRET_RESULT; -} - --(double)fpret_noarg -{ - CHECK_ARGS_NOARG(fpret); - state = 14; - return FP_RESULT; -} - --(long double)lfpret_noarg -{ - CHECK_ARGS_NOARG(lfpret); - state = 15; - return LFP_RESULT; -} - --(vector_ulong2)vecret_noarg -{ - CHECK_ARGS_NOARG(vecret); - state = 16; - return VEC_RESULT; -} - - --(void)voidret_nop -{ - return; -} - --(void)voidret_nop2 -{ - return; -} - --(id)idret_nop -{ - return ID_RESULT; -} - --(long long)llret_nop -{ - return LL_RESULT; -} - --(struct stret)stret_nop -{ - return STRET_RESULT; -} - --(double)fpret_nop -{ - return FP_RESULT; -} - --(long double)lfpret_nop -{ - return LFP_RESULT; -} - --(vector_ulong2)vecret_nop -{ - return VEC_RESULT; -} - -#define STRET_IMP(n) \ -+(struct stret_##n)stret_##n##_zero \ -{ \ - struct stret_##n ret; \ - bzero(&ret, sizeof(ret)); \ - return ret; \ -} \ -+(struct stret_##n)stret_##n##_nonzero \ -{ \ - struct stret_##n ret; \ - memset(&ret, 0xff, sizeof(ret)); \ - return ret; \ -} - -STRET_IMP(i1) -STRET_IMP(i2) -STRET_IMP(i3) -STRET_IMP(i4) -STRET_IMP(i5) -STRET_IMP(i6) -STRET_IMP(i7) -STRET_IMP(i8) -STRET_IMP(i9) - -STRET_IMP(d1) -STRET_IMP(d2) -STRET_IMP(d3) -STRET_IMP(d4) -STRET_IMP(d5) -STRET_IMP(d6) -STRET_IMP(d7) -STRET_IMP(d8) -STRET_IMP(d9) - - -+(id)idret: - (vector_ulong2)v1 :(vector_ulong2)v2 :(vector_ulong2)v3 :(vector_ulong2)v4 :(vector_ulong2)v5 :(vector_ulong2)v6 :(vector_ulong2)v7 :(vector_ulong2)v8 :(int)i1 :(int)i2 :(int)i3 :(int)i4 :(int)i5 :(int)i6 :(int)i7 :(int)i8 :(int)i9 :(int)i10 :(int)i11 :(int)i12 :(int)i13 :(double)f1 :(double)f2 :(double)f3 :(double)f4 :(double)f5 :(double)f6 :(double)f7 :(double)f8 :(double)f9 :(double)f10 :(double)f11 :(double)f12 :(double)f13 :(double)f14 :(double)f15 -{ - fail("+idret called instead of -idret"); - CHECK_ARGS(idret); -} - -+(long long)llret: - (vector_ulong2)v1 :(vector_ulong2)v2 :(vector_ulong2)v3 :(vector_ulong2)v4 :(vector_ulong2)v5 :(vector_ulong2)v6 :(vector_ulong2)v7 :(vector_ulong2)v8 :(int)i1 :(int)i2 :(int)i3 :(int)i4 :(int)i5 :(int)i6 :(int)i7 :(int)i8 :(int)i9 :(int)i10 :(int)i11 :(int)i12 :(int)i13 :(double)f1 :(double)f2 :(double)f3 :(double)f4 :(double)f5 :(double)f6 :(double)f7 :(double)f8 :(double)f9 :(double)f10 :(double)f11 :(double)f12 :(double)f13 :(double)f14 :(double)f15 -{ - fail("+llret called instead of -llret"); - CHECK_ARGS(llret); -} - -+(struct stret)stret: - (vector_ulong2)v1 :(vector_ulong2)v2 :(vector_ulong2)v3 :(vector_ulong2)v4 :(vector_ulong2)v5 :(vector_ulong2)v6 :(vector_ulong2)v7 :(vector_ulong2)v8 :(int)i1 :(int)i2 :(int)i3 :(int)i4 :(int)i5 :(int)i6 :(int)i7 :(int)i8 :(int)i9 :(int)i10 :(int)i11 :(int)i12 :(int)i13 :(double)f1 :(double)f2 :(double)f3 :(double)f4 :(double)f5 :(double)f6 :(double)f7 :(double)f8 :(double)f9 :(double)f10 :(double)f11 :(double)f12 :(double)f13 :(double)f14 :(double)f15 -{ - fail("+stret called instead of -stret"); - CHECK_ARGS(stret); -} - -+(double)fpret: - (vector_ulong2)v1 :(vector_ulong2)v2 :(vector_ulong2)v3 :(vector_ulong2)v4 :(vector_ulong2)v5 :(vector_ulong2)v6 :(vector_ulong2)v7 :(vector_ulong2)v8 :(int)i1 :(int)i2 :(int)i3 :(int)i4 :(int)i5 :(int)i6 :(int)i7 :(int)i8 :(int)i9 :(int)i10 :(int)i11 :(int)i12 :(int)i13 :(double)f1 :(double)f2 :(double)f3 :(double)f4 :(double)f5 :(double)f6 :(double)f7 :(double)f8 :(double)f9 :(double)f10 :(double)f11 :(double)f12 :(double)f13 :(double)f14 :(double)f15 -{ - fail("+fpret called instead of -fpret"); - CHECK_ARGS(fpret); -} - -+(long double)lfpret: - (vector_ulong2)v1 :(vector_ulong2)v2 :(vector_ulong2)v3 :(vector_ulong2)v4 :(vector_ulong2)v5 :(vector_ulong2)v6 :(vector_ulong2)v7 :(vector_ulong2)v8 :(int)i1 :(int)i2 :(int)i3 :(int)i4 :(int)i5 :(int)i6 :(int)i7 :(int)i8 :(int)i9 :(int)i10 :(int)i11 :(int)i12 :(int)i13 :(double)f1 :(double)f2 :(double)f3 :(double)f4 :(double)f5 :(double)f6 :(double)f7 :(double)f8 :(double)f9 :(double)f10 :(double)f11 :(double)f12 :(double)f13 :(double)f14 :(double)f15 -{ - fail("+lfpret called instead of -lfpret"); - CHECK_ARGS(lfpret); -} - -+(id)idret_noarg -{ - fail("+idret_noarg called instead of -idret_noarg"); - CHECK_ARGS_NOARG(idret); -} - -+(long long)llret_noarg -{ - fail("+llret_noarg called instead of -llret_noarg"); - CHECK_ARGS_NOARG(llret); -} - -+(struct stret)stret_noarg -{ - fail("+stret_noarg called instead of -stret_noarg"); - CHECK_ARGS_NOARG(stret); -} - -+(double)fpret_noarg -{ - fail("+fpret_noarg called instead of -fpret_noarg"); - CHECK_ARGS_NOARG(fpret); -} - -+(long double)lfpret_noarg -{ - fail("+lfpret_noarg called instead of -lfpret_noarg"); - CHECK_ARGS_NOARG(lfpret); -} - -+(vector_ulong2)vecret_noarg -{ - fail("+vecret_noarg called instead of -vecret_noarg"); - CHECK_ARGS_NOARG(vecret); -} - -@end - - -@implementation Sub - --(id)IMPL_idret: - (vector_ulong2)v1 :(vector_ulong2)v2 :(vector_ulong2)v3 :(vector_ulong2)v4 :(vector_ulong2)v5 :(vector_ulong2)v6 :(vector_ulong2)v7 :(vector_ulong2)v8 :(int)i1 :(int)i2 :(int)i3 :(int)i4 :(int)i5 :(int)i6 :(int)i7 :(int)i8 :(int)i9 :(int)i10 :(int)i11 :(int)i12 :(int)i13 :(double)f1 :(double)f2 :(double)f3 :(double)f4 :(double)f5 :(double)f6 :(double)f7 :(double)f8 :(double)f9 :(double)f10 :(double)f11 :(double)f12 :(double)f13 :(double)f14 :(double)f15 -{ - id result; - CHECK_ARGS(idret); - state = 100; - result = [super idret:v1:v2:v3:v4:v5:v6:v7:v8:i1:i2:i3:i4:i5:i6:i7:i8:i9:i10:i11:i12:i13:f1:f2:f3:f4:f5:f6:f7:f8:f9:f10:f11:f12:f13:f14:f15]; - testassert(state == 1); - testassert(result == ID_RESULT); - state = 101; - return result; -} - --(long long)IMPL_llret: - (vector_ulong2)v1 :(vector_ulong2)v2 :(vector_ulong2)v3 :(vector_ulong2)v4 :(vector_ulong2)v5 :(vector_ulong2)v6 :(vector_ulong2)v7 :(vector_ulong2)v8 :(int)i1 :(int)i2 :(int)i3 :(int)i4 :(int)i5 :(int)i6 :(int)i7 :(int)i8 :(int)i9 :(int)i10 :(int)i11 :(int)i12 :(int)i13 :(double)f1 :(double)f2 :(double)f3 :(double)f4 :(double)f5 :(double)f6 :(double)f7 :(double)f8 :(double)f9 :(double)f10 :(double)f11 :(double)f12 :(double)f13 :(double)f14 :(double)f15 -{ - long long result; - CHECK_ARGS(llret); - state = 100; - result = [super llret:v1:v2:v3:v4:v5:v6:v7:v8:i1:i2:i3:i4:i5:i6:i7:i8:i9:i10:i11:i12:i13:f1:f2:f3:f4:f5:f6:f7:f8:f9:f10:f11:f12:f13:f14:f15]; - testassert(state == 2); - testassert(result == LL_RESULT); - state = 102; - return result; -} - --(struct stret)IMPL_stret: - (vector_ulong2)v1 :(vector_ulong2)v2 :(vector_ulong2)v3 :(vector_ulong2)v4 :(vector_ulong2)v5 :(vector_ulong2)v6 :(vector_ulong2)v7 :(vector_ulong2)v8 :(int)i1 :(int)i2 :(int)i3 :(int)i4 :(int)i5 :(int)i6 :(int)i7 :(int)i8 :(int)i9 :(int)i10 :(int)i11 :(int)i12 :(int)i13 :(double)f1 :(double)f2 :(double)f3 :(double)f4 :(double)f5 :(double)f6 :(double)f7 :(double)f8 :(double)f9 :(double)f10 :(double)f11 :(double)f12 :(double)f13 :(double)f14 :(double)f15 -{ - struct stret result; - CHECK_ARGS(stret); - state = 100; - result = [super stret:v1:v2:v3:v4:v5:v6:v7:v8:i1:i2:i3:i4:i5:i6:i7:i8:i9:i10:i11:i12:i13:f1:f2:f3:f4:f5:f6:f7:f8:f9:f10:f11:f12:f13:f14:f15]; - testassert(state == 3); - testassert(stret_equal(result, STRET_RESULT)); - state = 103; - return result; -} - --(double)IMPL_fpret: - (vector_ulong2)v1 :(vector_ulong2)v2 :(vector_ulong2)v3 :(vector_ulong2)v4 :(vector_ulong2)v5 :(vector_ulong2)v6 :(vector_ulong2)v7 :(vector_ulong2)v8 :(int)i1 :(int)i2 :(int)i3 :(int)i4 :(int)i5 :(int)i6 :(int)i7 :(int)i8 :(int)i9 :(int)i10 :(int)i11 :(int)i12 :(int)i13 :(double)f1 :(double)f2 :(double)f3 :(double)f4 :(double)f5 :(double)f6 :(double)f7 :(double)f8 :(double)f9 :(double)f10 :(double)f11 :(double)f12 :(double)f13 :(double)f14 :(double)f15 -{ - double result; - CHECK_ARGS(fpret); - state = 100; - result = [super fpret:v1:v2:v3:v4:v5:v6:v7:v8:i1:i2:i3:i4:i5:i6:i7:i8:i9:i10:i11:i12:i13:f1:f2:f3:f4:f5:f6:f7:f8:f9:f10:f11:f12:f13:f14:f15]; - testassert(state == 4); - testassert(result == FP_RESULT); - state = 104; - return result; -} - --(long double)IMPL_lfpret: - (vector_ulong2)v1 :(vector_ulong2)v2 :(vector_ulong2)v3 :(vector_ulong2)v4 :(vector_ulong2)v5 :(vector_ulong2)v6 :(vector_ulong2)v7 :(vector_ulong2)v8 :(int)i1 :(int)i2 :(int)i3 :(int)i4 :(int)i5 :(int)i6 :(int)i7 :(int)i8 :(int)i9 :(int)i10 :(int)i11 :(int)i12 :(int)i13 :(double)f1 :(double)f2 :(double)f3 :(double)f4 :(double)f5 :(double)f6 :(double)f7 :(double)f8 :(double)f9 :(double)f10 :(double)f11 :(double)f12 :(double)f13 :(double)f14 :(double)f15 -{ - long double result; - CHECK_ARGS(lfpret); - state = 100; - result = [super lfpret:v1:v2:v3:v4:v5:v6:v7:v8:i1:i2:i3:i4:i5:i6:i7:i8:i9:i10:i11:i12:i13:f1:f2:f3:f4:f5:f6:f7:f8:f9:f10:f11:f12:f13:f14:f15]; - testassert(state == 5); - testassert(result == LFP_RESULT); - state = 105; - return result; -} - --(vector_ulong2)IMPL_vecret: - (vector_ulong2)v1 :(vector_ulong2)v2 :(vector_ulong2)v3 :(vector_ulong2)v4 :(vector_ulong2)v5 :(vector_ulong2)v6 :(vector_ulong2)v7 :(vector_ulong2)v8 :(int)i1 :(int)i2 :(int)i3 :(int)i4 :(int)i5 :(int)i6 :(int)i7 :(int)i8 :(int)i9 :(int)i10 :(int)i11 :(int)i12 :(int)i13 :(double)f1 :(double)f2 :(double)f3 :(double)f4 :(double)f5 :(double)f6 :(double)f7 :(double)f8 :(double)f9 :(double)f10 :(double)f11 :(double)f12 :(double)f13 :(double)f14 :(double)f15 -{ - vector_ulong2 result; - CHECK_ARGS(vecret); - state = 100; - result = [super vecret:v1:v2:v3:v4:v5:v6:v7:v8:i1:i2:i3:i4:i5:i6:i7:i8:i9:i10:i11:i12:i13:f1:f2:f3:f4:f5:f6:f7:f8:f9:f10:f11:f12:f13:f14:f15]; - testassert(state == 6); - testassert(vector_equal(result, VEC_RESULT)); - state = 106; - return result; -} - - --(id)idret_noarg -{ - id result; - CHECK_ARGS_NOARG(idret); - state = 100; - result = [super idret_noarg]; - testassert(state == 11); - testassert(result == ID_RESULT); - state = 111; - return result; -} - --(long long)llret_noarg -{ - long long result; - CHECK_ARGS_NOARG(llret); - state = 100; - result = [super llret_noarg]; - testassert(state == 12); - testassert(result == LL_RESULT); - state = 112; - return result; -} - --(struct stret)stret_noarg -{ - struct stret result; - CHECK_ARGS_NOARG(stret); - state = 100; - result = [super stret_noarg]; - testassert(state == 13); - testassert(stret_equal(result, STRET_RESULT)); - state = 113; - return result; -} - --(double)fpret_noarg -{ - double result; - CHECK_ARGS_NOARG(fpret); - state = 100; - result = [super fpret_noarg]; - testassert(state == 14); - testassert(result == FP_RESULT); - state = 114; - return result; -} - --(long double)lfpret_noarg -{ - long double result; - CHECK_ARGS_NOARG(lfpret); - state = 100; - result = [super lfpret_noarg]; - testassert(state == 15); - testassert(result == LFP_RESULT); - state = 115; - return result; -} - --(vector_ulong2)vecret_noarg -{ - vector_ulong2 result; - CHECK_ARGS_NOARG(vecret); - state = 100; - result = [super vecret_noarg]; - testassert(state == 16); - testassert(vector_equal(result, VEC_RESULT)); - state = 116; - return result; -} - -@end - - -#if OBJC_HAVE_TAGGED_POINTERS - -@interface TaggedSub : Sub @end - -@implementation TaggedSub : Sub - -+(void)initialize -{ - _objc_registerTaggedPointerClass(OBJC_TAG_7, self); -} - -@end - -#endif - - -// DWARF checking machinery - -#if TARGET_OS_WIN32 -// unimplemented on this platform -#elif !__OBJC2__ -// 32-bit Mac doesn't use DWARF unwind -#elif TARGET_OS_IPHONE && __arm__ -// 32-bit iOS device doesn't use DWARF unwind -#elif __has_feature(objc_arc) -// ARC's extra RR calls hit the traps at the wrong times -#else - -#define TEST_DWARF 1 - -// Classes with no implementations and no cache contents from elsewhere. -@interface SuperDW : TestRoot @end -@implementation SuperDW @end - -@interface Sub0DW : SuperDW @end -@implementation Sub0DW @end - -@interface SubDW : Sub0DW @end -@implementation SubDW @end - -#include -#include -#include -#include - -#define UNW_STEP_SUCCESS 1 -#define UNW_STEP_END 0 - -bool caught = false; -uintptr_t clobbered; - -__BEGIN_DECLS -extern void callit(void *obj, void *sel, void *fn); -extern struct stret callit_stret(void *obj, void *sel, void *fn); -__END_DECLS - -#if __x86_64__ - -#define OTOOL "/usr/bin/xcrun otool -arch x86_64 " - -typedef uint8_t insn_t; -#define BREAK_INSN ((insn_t)0xcc) // int3 - -uintptr_t r12 = 0; -uintptr_t r13 = 0; -uintptr_t r14 = 0; -uintptr_t r15 = 0; -uintptr_t rbx = 0; -uintptr_t rbp = 0; -uintptr_t rsp = 0; -uintptr_t rip = 0; - -void handle_exception(x86_thread_state64_t *state) -{ - unw_cursor_t curs; - unw_word_t reg; - int err; - int step; - - err = unw_init_local(&curs, (unw_context_t *)state); - testassert(!err); - - step = unw_step(&curs); - testassert(step == UNW_STEP_SUCCESS); - - err = unw_get_reg(&curs, UNW_X86_64_R12, ®); - testassert(!err); - testassert(reg == r12); - - err = unw_get_reg(&curs, UNW_X86_64_R13, ®); - testassert(!err); - testassert(reg == r13); - - err = unw_get_reg(&curs, UNW_X86_64_R14, ®); - testassert(!err); - testassert(reg == r14); - - err = unw_get_reg(&curs, UNW_X86_64_R15, ®); - testassert(!err); - testassert(reg == r15); - - err = unw_get_reg(&curs, UNW_X86_64_RBX, ®); - testassert(!err); - testassert(reg == rbx); - - err = unw_get_reg(&curs, UNW_X86_64_RBP, ®); - testassert(!err); - testassert(reg == rbp); - - err = unw_get_reg(&curs, UNW_X86_64_RSP, ®); - testassert(!err); - testassert(reg == rsp); - - err = unw_get_reg(&curs, UNW_REG_IP, ®); - testassert(!err); - testassert(reg == rip); - - - // set thread state to unwound state - state->__r12 = r12; - state->__r13 = r13; - state->__r14 = r14; - state->__r15 = r15; - state->__rbx = rbx; - state->__rbp = rbp; - state->__rsp = rsp; - state->__rip = rip; - - caught = true; -} - - -void sigtrap(int sig, siginfo_t *info, void *cc) -{ - ucontext_t *uc = (ucontext_t *)cc; - mcontext_t mc = (mcontext_t)uc->uc_mcontext; - - testprintf(" handled\n"); - - testassert(sig == SIGTRAP); - testassert((uintptr_t)info->si_addr-1 == clobbered); - - handle_exception(&mc->__ss); - // handle_exception changed register state for continuation -} - -__asm__( -"\n .text" -"\n .globl _callit" -"\n _callit:" -// save sp and return address to variables -"\n movq (%rsp), %r10" -"\n movq %r10, _rip(%rip)" -"\n movq %rsp, _rsp(%rip)" -"\n addq $8, _rsp(%rip)" // rewind to pre-call value -// save other non-volatile registers to variables -"\n movq %rbx, _rbx(%rip)" -"\n movq %rbp, _rbp(%rip)" -"\n movq %r12, _r12(%rip)" -"\n movq %r13, _r13(%rip)" -"\n movq %r14, _r14(%rip)" -"\n movq %r15, _r15(%rip)" -"\n jmpq *%rdx" - ); - -__asm__( -"\n .text" -"\n .globl _callit_stret" -"\n _callit_stret:" -// save sp and return address to variables -"\n movq (%rsp), %r10" -"\n movq %r10, _rip(%rip)" -"\n movq %rsp, _rsp(%rip)" -"\n addq $8, _rsp(%rip)" // rewind to pre-call value -// save other non-volatile registers to variables -"\n movq %rbx, _rbx(%rip)" -"\n movq %rbp, _rbp(%rip)" -"\n movq %r12, _r12(%rip)" -"\n movq %r13, _r13(%rip)" -"\n movq %r14, _r14(%rip)" -"\n movq %r15, _r15(%rip)" -"\n jmpq *%rcx" - ); - - -// x86_64 - -#elif __i386__ - -#define OTOOL "/usr/bin/xcrun otool -arch i386 " - -typedef uint8_t insn_t; -#define BREAK_INSN ((insn_t)0xcc) // int3 - -uintptr_t eip = 0; -uintptr_t esp = 0; -uintptr_t ebx = 0; -uintptr_t ebp = 0; -uintptr_t edi = 0; -uintptr_t esi = 0; -uintptr_t espfix = 0; - -void handle_exception(i386_thread_state_t *state) -{ - unw_cursor_t curs; - unw_word_t reg; - int err; - int step; - - err = unw_init_local(&curs, (unw_context_t *)state); - testassert(!err); - - step = unw_step(&curs); - testassert(step == UNW_STEP_SUCCESS); - - err = unw_get_reg(&curs, UNW_REG_IP, ®); - testassert(!err); - testassert(reg == eip); - - err = unw_get_reg(&curs, UNW_X86_ESP, ®); - testassert(!err); - testassert(reg == esp); - - err = unw_get_reg(&curs, UNW_X86_EBX, ®); - testassert(!err); - testassert(reg == ebx); - - err = unw_get_reg(&curs, UNW_X86_EBP, ®); - testassert(!err); - testassert(reg == ebp); - - err = unw_get_reg(&curs, UNW_X86_EDI, ®); - testassert(!err); - testassert(reg == edi); - - err = unw_get_reg(&curs, UNW_X86_ESI, ®); - testassert(!err); - testassert(reg == esi); - - - // set thread state to unwound state - state->__eip = eip; - state->__esp = esp + espfix; - state->__ebx = ebx; - state->__ebp = ebp; - state->__edi = edi; - state->__esi = esi; - - caught = true; -} - - -void sigtrap(int sig, siginfo_t *info, void *cc) -{ - ucontext_t *uc = (ucontext_t *)cc; - mcontext_t mc = (mcontext_t)uc->uc_mcontext; - - testprintf(" handled\n"); - - testassert(sig == SIGTRAP); - testassert((uintptr_t)info->si_addr-1 == clobbered); - - handle_exception(&mc->__ss); - // handle_exception changed register state for continuation -} - -__asm__( -"\n .text" -"\n .globl _callit" -"\n _callit:" -// save sp and return address to variables -"\n call 1f" -"\n 1: popl %edx" -"\n movl (%esp), %eax" -"\n movl %eax, _eip-1b(%edx)" -"\n movl %esp, _esp-1b(%edx)" -"\n addl $4, _esp-1b(%edx)" // rewind to pre-call value -"\n movl $0, _espfix-1b(%edx)" -// save other non-volatile registers to variables -"\n movl %ebx, _ebx-1b(%edx)" -"\n movl %ebp, _ebp-1b(%edx)" -"\n movl %edi, _edi-1b(%edx)" -"\n movl %esi, _esi-1b(%edx)" -"\n jmpl *12(%esp)" - ); - -__asm__( -"\n .text" -"\n .globl _callit_stret" -"\n _callit_stret:" -// save sp and return address to variables -"\n call 1f" -"\n 1: popl %edx" -"\n movl (%esp), %eax" -"\n movl %eax, _eip-1b(%edx)" -"\n movl %esp, _esp-1b(%edx)" -"\n addl $4, _esp-1b(%edx)" // rewind to pre-call value -"\n movl $4, _espfix-1b(%edx)" -// save other non-volatile registers to variables -"\n movl %ebx, _ebx-1b(%edx)" -"\n movl %ebp, _ebp-1b(%edx)" -"\n movl %edi, _edi-1b(%edx)" -"\n movl %esi, _esi-1b(%edx)" -"\n jmpl *16(%esp)" - ); - - -// i386 -#elif __arm64__ - -#include - -// runs on iOS device, no xcrun command present -#define OTOOL "/usr/bin/otool -arch arm64 " - -typedef uint32_t insn_t; -#define BREAK_INSN ((insn_t)0xd4200020) // brk #1 - -uintptr_t x19 = 0; -uintptr_t x20 = 0; -uintptr_t x21 = 0; -uintptr_t x22 = 0; -uintptr_t x23 = 0; -uintptr_t x24 = 0; -uintptr_t x25 = 0; -uintptr_t x26 = 0; -uintptr_t x27 = 0; -uintptr_t x28 = 0; -uintptr_t fp = 0; -uintptr_t sp = 0; -uintptr_t pc = 0; - -void handle_exception(arm_thread_state64_t *state) -{ - unw_cursor_t curs; - unw_word_t reg; - int err; - int step; - - err = unw_init_local(&curs, (unw_context_t *)state); - testassert(!err); - - step = unw_step(&curs); - testassert(step == UNW_STEP_SUCCESS); - - err = unw_get_reg(&curs, UNW_ARM64_X19, ®); - testassert(!err); - testassert(reg == x19); - - err = unw_get_reg(&curs, UNW_ARM64_X20, ®); - testassert(!err); - testassert(reg == x20); - - err = unw_get_reg(&curs, UNW_ARM64_X21, ®); - testassert(!err); - testassert(reg == x21); - - err = unw_get_reg(&curs, UNW_ARM64_X22, ®); - testassert(!err); - testassert(reg == x22); - - err = unw_get_reg(&curs, UNW_ARM64_X23, ®); - testassert(!err); - testassert(reg == x23); - - err = unw_get_reg(&curs, UNW_ARM64_X24, ®); - testassert(!err); - testassert(reg == x24); - - err = unw_get_reg(&curs, UNW_ARM64_X25, ®); - testassert(!err); - testassert(reg == x25); - - err = unw_get_reg(&curs, UNW_ARM64_X26, ®); - testassert(!err); - testassert(reg == x26); - - err = unw_get_reg(&curs, UNW_ARM64_X27, ®); - testassert(!err); - testassert(reg == x27); - - err = unw_get_reg(&curs, UNW_ARM64_X28, ®); - testassert(!err); - testassert(reg == x28); - - err = unw_get_reg(&curs, UNW_ARM64_FP, ®); - testassert(!err); - testassert(reg == fp); - - err = unw_get_reg(&curs, UNW_ARM64_SP, ®); - testassert(!err); - testassert(reg == sp); - - err = unw_get_reg(&curs, UNW_REG_IP, ®); - testassert(!err); - testassert(reg == pc); - - // libunwind restores PC into LR and doesn't track LR - // err = unw_get_reg(&curs, UNW_ARM64_LR, ®); - // testassert(!err); - // testassert(reg == lr); - - // set thread state to unwound state - state->__x[19] = x19; - state->__x[20] = x20; - state->__x[20] = x21; - state->__x[22] = x22; - state->__x[23] = x23; - state->__x[24] = x24; - state->__x[25] = x25; - state->__x[26] = x26; - state->__x[27] = x27; - state->__x[28] = x28; - state->__fp = fp; - state->__lr = pc; // libunwind restores PC into LR - state->__sp = sp; - state->__pc = pc; - - caught = true; -} - - -void sigtrap(int sig, siginfo_t *info, void *cc) -{ - ucontext_t *uc = (ucontext_t *)cc; - struct __darwin_mcontext64 *mc = (struct __darwin_mcontext64 *)uc->uc_mcontext; - - testprintf(" handled\n"); - - testassert(sig == SIGTRAP); - testassert((uintptr_t)info->si_addr == clobbered); - - handle_exception(&mc->__ss); - // handle_exception changed register state for continuation -} - - -__asm__( -"\n .text" -"\n .globl _callit" -"\n _callit:" -// save sp and return address to variables -"\n mov x16, sp" -"\n adrp x17, _sp@PAGE" -"\n str x16, [x17, _sp@PAGEOFF]" -"\n adrp x17, _pc@PAGE" -"\n str lr, [x17, _pc@PAGEOFF]" -// save other non-volatile registers to variables -"\n adrp x17, _x19@PAGE" -"\n str x19, [x17, _x19@PAGEOFF]" -"\n adrp x17, _x19@PAGE" -"\n str x20, [x17, _x20@PAGEOFF]" -"\n adrp x17, _x19@PAGE" -"\n str x21, [x17, _x21@PAGEOFF]" -"\n adrp x17, _x19@PAGE" -"\n str x22, [x17, _x22@PAGEOFF]" -"\n adrp x17, _x19@PAGE" -"\n str x23, [x17, _x23@PAGEOFF]" -"\n adrp x17, _x19@PAGE" -"\n str x24, [x17, _x24@PAGEOFF]" -"\n adrp x17, _x19@PAGE" -"\n str x25, [x17, _x25@PAGEOFF]" -"\n adrp x17, _x19@PAGE" -"\n str x26, [x17, _x26@PAGEOFF]" -"\n adrp x17, _x19@PAGE" -"\n str x27, [x17, _x27@PAGEOFF]" -"\n adrp x17, _x19@PAGE" -"\n str x28, [x17, _x28@PAGEOFF]" -"\n adrp x17, _x19@PAGE" -"\n str fp, [x17, _fp@PAGEOFF]" -"\n br x2" - ); - - -// arm64 -#else - -#error unknown architecture - -#endif - - -insn_t set(uintptr_t dst, insn_t newvalue) -{ - uintptr_t start = dst & ~(PAGE_MAX_SIZE-1); - mprotect((void*)start, PAGE_MAX_SIZE, PROT_READ|PROT_WRITE); - insn_t oldvalue = *(insn_t *)dst; - *(insn_t *)dst = newvalue; - mprotect((void*)start, PAGE_MAX_SIZE, PROT_READ|PROT_EXEC); - return oldvalue; -} - -insn_t clobber(void *fn, uintptr_t offset) -{ - clobbered = (uintptr_t)fn + offset; - return set((uintptr_t)fn + offset, BREAK_INSN); -} - -void unclobber(void *fn, uintptr_t offset, insn_t oldvalue) -{ - set((uintptr_t)fn + offset, oldvalue); -} - - -uintptr_t *getOffsets(void *symbol, const char *symname, uintptr_t *outBase) -{ - uintptr_t *result = (uintptr_t *)malloc(1000 * sizeof(uintptr_t)); - uintptr_t *end = result + 1000; - uintptr_t *p = result; - - // find library - Dl_info dl; - dladdr(symbol, &dl); - - // call `otool` on library - unsetenv("DYLD_LIBRARY_PATH"); - unsetenv("DYLD_ROOT_PATH"); - unsetenv("DYLD_INSERT_LIBRARIES"); - unsetenv("DYLD_SHARED_REGION"); - unsetenv("DYLD_SHARED_CACHE_DIR"); - unsetenv("DYLD_SHARED_CACHE_DONT_VALIDATE"); - char *cmd; - asprintf(&cmd, OTOOL "-tv -p _%s %s", - symname, dl.dli_fname); - testprintf("%s\n", cmd); - FILE *disa = popen(cmd, "r"); - free(cmd); - testassert(disa); - - // read past "_symname:" line - char *line; - size_t len; - while ((line = fgetln(disa, &len))) { - if (0 == strncmp(1+line, symname, MIN(len-1, strlen(symname)))) break; - } - - // read instructions and save offsets - char op[128]; - long base = 0; - long addr; - while (2 == fscanf(disa, "%lx%s%*[^\n]\n", &addr, op)) { - if (base == 0) base = addr; - if (0 != strncmp(op, "nop", 3)) { - testassert(p < end); - *p++ = addr - base; - } else { - // assume nops are unreached (e.g. alignment padding) - } - } - pclose(disa); - -#if __arm64__ - // Also add breakpoints in _objc_msgSend_uncached_impcache - // (which is the slow path and has a frame to unwind) - if (0 != strcmp(symname, "_objc_msgSend_uncached_impcache")) { - uintptr_t base2; - uintptr_t *more_offsets = getOffsets(symbol, "_objc_msgSend_uncached_impcache", &base2); - uintptr_t *q = more_offsets; - // Skip prologue because it's imprecisely modeled in compact unwind - testassert(*q != ~0UL); - q++; - testassert(*q != ~0UL); - q++; - while (*q != ~0UL) *p++ = *q++ + base2 - base; - // Skip return because it's imprecisely modeled in compact unwind - p--; - free(more_offsets); - } -#endif - - testassert(p > result); - testassert(p < end); - *p = ~0UL; -#if __x86_64__ - // hack: skip last instruction because libunwind blows up if it's - // one byte long and followed by the next function with no NOPs first - p[-1] = ~0UL; -#endif - if (outBase) *outBase = base; - return result; -} - -void CALLIT(void *o, void *sel_arg, SEL s, void *f, bool stret) __attribute__((noinline)); -void CALLIT(void *o, void *sel_arg, SEL s, void *f, bool stret) -{ - uintptr_t message_ref[2]; - if (sel_arg != s) { - // fixup dispatch - // copy to a local buffer to keep sel_arg un-fixed-up - memcpy(message_ref, sel_arg, sizeof(message_ref)); - sel_arg = message_ref; - } - if (!stret) callit(o, sel_arg, f); -#if SUPPORT_STRET - else callit_stret(o, sel_arg, f); -#else - else fail("stret?"); -#endif -} - -void test_dw_forward(void) -{ - return; -} - -struct stret test_dw_forward_stret(void) -{ - return zero; -} - -// sub = ordinary receiver object -// tagged = tagged receiver object -// SEL = selector to send -// sub_arg = arg to pass in receiver register (may be objc_super struct) -// tagged_arg = arg to pass in receiver register (may be objc_super struct) -// sel_arg = arg to pass in sel register (may be message_ref) -// uncaughtAllowed is the number of acceptable unreachable instructions -// (for example, the ones that handle the corrupt-cache-error case) -void test_dw(const char *name, id sub, id tagged, bool stret, - int uncaughtAllowed) -{ - - testprintf("DWARF FOR %s%s\n", name, stret ? " (stret)" : ""); - - // We need 2 SELs of each alignment so we can generate hash collisions. - // sel_registerName() never returns those alignments because they - // differ from malloc's alignment. So we create lots of compiled-in - // SELs here and hope something fits. -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wundeclared-selector" - SEL sel = @selector(a); - SEL lotsOfSels[] = { - @selector(a1), @selector(a2), @selector(a3), @selector(a4), - @selector(a5), @selector(a6), @selector(a7), @selector(a8), - @selector(aa), @selector(ab), @selector(ac), @selector(ad), - @selector(ae), @selector(af), @selector(ag), @selector(ah), - @selector(A1), @selector(A2), @selector(A3), @selector(A4), - @selector(A5), @selector(A6), @selector(A7), @selector(A8), - @selector(AA), @selector(Ab), @selector(Ac), @selector(Ad), - @selector(Ae), @selector(Af), @selector(Ag), @selector(Ah), - @selector(bb1), @selector(bb2), @selector(bb3), @selector(bb4), - @selector(bb5), @selector(bb6), @selector(bb7), @selector(bb8), - @selector(bba), @selector(bbb), @selector(bbc), @selector(bbd), - @selector(bbe), @selector(bbf), @selector(bbg), @selector(bbh), - @selector(BB1), @selector(BB2), @selector(BB3), @selector(BB4), - @selector(BB5), @selector(BB6), @selector(BB7), @selector(BB8), - @selector(BBa), @selector(BBb), @selector(BBc), @selector(BBd), - @selector(BBe), @selector(BBf), @selector(BBg), @selector(BBh), - @selector(ccc1), @selector(ccc2), @selector(ccc3), @selector(ccc4), - @selector(ccc5), @selector(ccc6), @selector(ccc7), @selector(ccc8), - @selector(ccca), @selector(cccb), @selector(cccc), @selector(cccd), - @selector(ccce), @selector(cccf), @selector(cccg), @selector(ccch), - @selector(CCC1), @selector(CCC2), @selector(CCC3), @selector(CCC4), - @selector(CCC5), @selector(CCC6), @selector(CCC7), @selector(CCC8), - @selector(CCCa), @selector(CCCb), @selector(CCCc), @selector(CCCd), - @selector(CCCe), @selector(CCCf), @selector(CCCg), @selector(CCCh), - }; -#pragma clang diagnostic pop - - { - IMP imp = stret ? (IMP)test_dw_forward_stret : (IMP)test_dw_forward; - Class cls = object_getClass(sub); - Class tagcls = object_getClass(tagged); - class_replaceMethod(cls, sel, imp, ""); - class_replaceMethod(tagcls, sel, imp, ""); - for (size_t i = 0; i < sizeof(lotsOfSels)/sizeof(lotsOfSels[0]); i++) { - class_replaceMethod(cls, lotsOfSels[i], imp, ""); - class_replaceMethod(tagcls, lotsOfSels[i], imp, ""); - } - } - - #define ALIGNCOUNT 16 - SEL sels[ALIGNCOUNT][2] = {{0}}; - for (int align = 0; align < ALIGNCOUNT; align++) { - for (size_t i = 0; i < sizeof(lotsOfSels)/sizeof(lotsOfSels[0]); i++) { - if ((uintptr_t)(void*)lotsOfSels[i] % ALIGNCOUNT == align) { - if (sels[align][0]) { - sels[align][1] = lotsOfSels[i]; - } else { - sels[align][0] = lotsOfSels[i]; - } - } - } - if (!sels[align][0]) fail("no SEL with alignment %d", align); - if (!sels[align][1]) fail("only one SEL with alignment %d", align); - } - - void *fn = dlsym(RTLD_DEFAULT, name); - testassert(fn); - - // argument substitutions - - void *sub_arg = (void*)objc_unretainedPointer(sub); - void *tagged_arg = (void*)objc_unretainedPointer(tagged); - void *sel_arg = (void*)sel; - - struct objc_super sup_st = { sub, object_getClass(sub) }; - struct objc_super tagged_sup_st = { tagged, object_getClass(tagged) }; - struct { void *imp; SEL sel; } message_ref = { fn, sel }; - - Class cache_cls = object_getClass(sub); - - if (strstr(name, "Super")) { - // super version - replace receiver with objc_super - // clear caches of superclass - cache_cls = class_getSuperclass(cache_cls); - sub_arg = &sup_st; - tagged_arg = &tagged_sup_st; - } - - if (strstr(name, "_fixup")) { - // fixup version - replace sel with message_ref - sel_arg = &message_ref; - } - - - uintptr_t *insnOffsets = getOffsets(fn, name, nil); - uintptr_t offset; - int uncaughtCount = 0; - for (int oo = 0; insnOffsets[oo] != ~0UL; oo++) { - offset = insnOffsets[oo]; - testprintf("OFFSET %lu\n", offset); - - insn_t saved_insn = clobber(fn, offset); - caught = false; - - // nil - if ((void*)objc_unretainedPointer(sub) == sub_arg) { - SELF = nil; - testprintf(" nil\n"); - CALLIT(nil, sel_arg, sel, fn, stret); - CALLIT(nil, sel_arg, sel, fn, stret); - } - - // uncached - SELF = sub; - testprintf(" uncached\n"); - _objc_flush_caches(cache_cls); - CALLIT(sub_arg, sel_arg, sel, fn, stret); - _objc_flush_caches(cache_cls); - CALLIT(sub_arg, sel_arg, sel, fn, stret); - - // cached - SELF = sub; - testprintf(" cached\n"); - CALLIT(sub_arg, sel_arg, sel, fn, stret); - CALLIT(sub_arg, sel_arg, sel, fn, stret); - - // uncached,tagged - SELF = tagged; - testprintf(" uncached,tagged\n"); - _objc_flush_caches(cache_cls); - CALLIT(tagged_arg, sel_arg, sel, fn, stret); - _objc_flush_caches(cache_cls); - CALLIT(tagged_arg, sel_arg, sel, fn, stret); - - // cached,tagged - SELF = tagged; - testprintf(" cached,tagged\n"); - CALLIT(tagged_arg, sel_arg, sel, fn, stret); - CALLIT(tagged_arg, sel_arg, sel, fn, stret); - - // multiple SEL alignments, collisions, wraps - SELF = sub; - for (int a = 0; a < ALIGNCOUNT; a++) { - testprintf(" cached, SEL alignment %d\n", a); - - // Count both up and down to be independent of - // implementation's cache scan direction - - _objc_flush_caches(cache_cls); - for (int x2 = 0; x2 < 8; x2++) { - for (int s = 0; s < 4; s++) { - int align = (a+s) % ALIGNCOUNT; - CALLIT(sub_arg, sels[align][0], sels[align][0], fn, stret); - CALLIT(sub_arg, sels[align][1], sels[align][1], fn, stret); - } - } - - _objc_flush_caches(cache_cls); - for (int x2 = 0; x2 < 8; x2++) { - for (int s = 0; s < 4; s++) { - int align = abs(a-s) % ALIGNCOUNT; - CALLIT(sub_arg, sels[align][0], sels[align][0], fn, stret); - CALLIT(sub_arg, sels[align][1], sels[align][1], fn, stret); - } - } - } - - unclobber(fn, offset, saved_insn); - - // remember offsets that were caught by none of the above - if (caught) { - insnOffsets[oo] = 0; - } else { - uncaughtCount++; - testprintf("offset %s+%lu not caught (%d/%d)\n", - name, offset, uncaughtCount, uncaughtAllowed); - } - } - - // Complain if too many offsets went uncaught. - // Acceptably-uncaught offsets include the corrupt-cache-error handler. - if (uncaughtCount != uncaughtAllowed) { - for (int oo = 0; insnOffsets[oo] != ~0UL; oo++) { - if (insnOffsets[oo]) { - fprintf(stderr, "BAD: offset %s+%lu not caught\n", - name, insnOffsets[oo]); - } - } - fail("wrong instructions not reached for %s (missed %d, expected %d)", - name, uncaughtCount, uncaughtAllowed); - } - - free(insnOffsets); -} - - -// TEST_DWARF -#endif - - -void test_basic(id receiver) -{ - id idval; - long long llval; - struct stret stretval; - double fpval; - long double lfpval; - vector_ulong2 vecval; - - // message uncached - // message uncached long long - // message uncached stret - // message uncached fpret - // message uncached fpret long double - // message uncached noarg (as above) - // message cached - // message cached long long - // message cached stret - // message cached fpret - // message cached fpret long double - // message cached noarg (as above) - // fixme verify that uncached lookup didn't happen the 2nd time? - SELF = receiver; - for (int i = 0; i < 5; i++) { - testprintf("idret\n"); - state = 0; - idval = nil; - idval = [receiver idret :VEC1:VEC2:VEC3:VEC4:VEC5:VEC6:VEC7:VEC8:1:2:3:4:5:6:7:8:9:10:11:12:13:1.0:2.0:3.0:4.0:5.0:6.0:7.0:8.0:9.0:10.0:11.0:12.0:13.0:14.0:15.0]; - testassert(state == 101); - testassert(idval == ID_RESULT); - - testprintf("llret\n"); - llval = 0; - llval = [receiver llret :VEC1:VEC2:VEC3:VEC4:VEC5:VEC6:VEC7:VEC8:1:2:3:4:5:6:7:8:9:10:11:12:13:1.0:2.0:3.0:4.0:5.0:6.0:7.0:8.0:9.0:10.0:11.0:12.0:13.0:14.0:15.0]; - testassert(state == 102); - testassert(llval == LL_RESULT); - - testprintf("stret\n"); - stretval = zero; - stretval = [receiver stret :VEC1:VEC2:VEC3:VEC4:VEC5:VEC6:VEC7:VEC8:1:2:3:4:5:6:7:8:9:10:11:12:13:1.0:2.0:3.0:4.0:5.0:6.0:7.0:8.0:9.0:10.0:11.0:12.0:13.0:14.0:15.0]; - testassert(state == 103); - testassert(stret_equal(stretval, STRET_RESULT)); - - testprintf("fpret\n"); - fpval = 0; - fpval = [receiver fpret :VEC1:VEC2:VEC3:VEC4:VEC5:VEC6:VEC7:VEC8:1:2:3:4:5:6:7:8:9:10:11:12:13:1.0:2.0:3.0:4.0:5.0:6.0:7.0:8.0:9.0:10.0:11.0:12.0:13.0:14.0:15.0]; - testassert(state == 104); - testassert(fpval == FP_RESULT); - - testprintf("lfpret\n"); - lfpval = 0; - lfpval = [receiver lfpret :VEC1:VEC2:VEC3:VEC4:VEC5:VEC6:VEC7:VEC8:1:2:3:4:5:6:7:8:9:10:11:12:13:1.0:2.0:3.0:4.0:5.0:6.0:7.0:8.0:9.0:10.0:11.0:12.0:13.0:14.0:15.0]; - testassert(state == 105); - testassert(lfpval == LFP_RESULT); - - testprintf("vecret\n"); - vecval = 0; - vecval = [receiver vecret :VEC1:VEC2:VEC3:VEC4:VEC5:VEC6:VEC7:VEC8:1:2:3:4:5:6:7:8:9:10:11:12:13:1.0:2.0:3.0:4.0:5.0:6.0:7.0:8.0:9.0:10.0:11.0:12.0:13.0:14.0:15.0]; - testassert(state == 106); - testassert(vector_equal(vecval, VEC_RESULT)); - -#if __OBJC2__ - // explicitly call noarg messenger, even if compiler doesn't emit it - state = 0; - testprintf("idret noarg\n"); - idval = nil; - idval = ((typeof(idmsg0))objc_msgSend_noarg)(receiver, @selector(idret_noarg)); - testassert(state == 111); - testassert(idval == ID_RESULT); - - testprintf("llret noarg\n"); - llval = 0; - llval = ((typeof(llmsg0))objc_msgSend_noarg)(receiver, @selector(llret_noarg)); - testassert(state == 112); - testassert(llval == LL_RESULT); - /* - no objc_msgSend_stret_noarg - stretval = zero; - stretval = ((typeof(stretmsg0))objc_msgSend_stret_noarg)(receiver, @selector(stret_noarg)); - stretval = [receiver stret :VEC1:VEC2:VEC3:VEC4:VEC5:VEC6:VEC7:VEC8:1:2:3:4:5:6:7:8:9:10:11:12:13:1.0:2.0:3.0:4.0:5.0:6.0:7.0:8.0:9.0:10.0:11.0:12.0:13.0:14.0:15.0]; - testassert(state == 113); - testassert(stret_equal(stretval, STRET_RESULT)); - */ -# if !__i386__ - testprintf("fpret noarg\n"); - fpval = 0; - fpval = ((typeof(fpmsg0))objc_msgSend_noarg)(receiver, @selector(fpret_noarg)); - testassert(state == 114); - testassert(fpval == FP_RESULT); - - testprintf("vecret noarg\n"); - vecval = 0; - vecval = ((typeof(vecmsg0))objc_msgSend_noarg)(receiver, @selector(vecret_noarg)); - testassert(state == 116); - testassert(vector_equal(vecval, VEC_RESULT)); -# endif -# if !__i386__ && !__x86_64__ - testprintf("lfpret noarg\n"); - lfpval = 0; - lfpval = ((typeof(lfpmsg0))objc_msgSend_noarg)(receiver, @selector(lfpret_noarg)); - testassert(state == 115); - testassert(lfpval == LFP_RESULT); -# endif -#endif - } - - testprintf("basic done\n"); -} - -int main() -{ - PUSH_POOL { - int i; - - id idval; - long long llval; - struct stret stretval; - double fpval; - long double lfpval; - vector_ulong2 vecval; - -#if __x86_64__ - struct stret *stretptr; -#endif - - uint64_t startTime; - uint64_t totalTime; - uint64_t targetTime; - - Method idmethod; - Method llmethod; - Method stretmethod; - Method fpmethod; - Method lfpmethod; - Method vecmethod; - - id (*idfn)(id, Method, vector_ulong2, vector_ulong2, vector_ulong2, vector_ulong2, vector_ulong2, vector_ulong2, vector_ulong2, vector_ulong2, int, int, int, int, int, int, int, int, int, int, int, int, int, double, double, double, double, double, double, double, double, double, double, double, double, double, double, double); - long long (*llfn)(id, Method, vector_ulong2, vector_ulong2, vector_ulong2, vector_ulong2, vector_ulong2, vector_ulong2, vector_ulong2, vector_ulong2, int, int, int, int, int, int, int, int, int, int, int, int, int, double, double, double, double, double, double, double, double, double, double, double, double, double, double, double); - struct stret (*stretfn)(id, Method, vector_ulong2, vector_ulong2, vector_ulong2, vector_ulong2, vector_ulong2, vector_ulong2, vector_ulong2, vector_ulong2, int, int, int, int, int, int, int, int, int, int, int, int, int, double, double, double, double, double, double, double, double, double, double, double, double, double, double, double); - double (*fpfn)(id, Method, vector_ulong2, vector_ulong2, vector_ulong2, vector_ulong2, vector_ulong2, vector_ulong2, vector_ulong2, vector_ulong2, int, int, int, int, int, int, int, int, int, int, int, int, int, double, double, double, double, double, double, double, double, double, double, double, double, double, double, double); - long double (*lfpfn)(id, Method, vector_ulong2, vector_ulong2, vector_ulong2, vector_ulong2, vector_ulong2, vector_ulong2, vector_ulong2, vector_ulong2, int, int, int, int, int, int, int, int, int, int, int, int, int, double, double, double, double, double, double, double, double, double, double, double, double, double, double, double); - vector_ulong2 (*vecfn)(id, Method, vector_ulong2, vector_ulong2, vector_ulong2, vector_ulong2, vector_ulong2, vector_ulong2, vector_ulong2, vector_ulong2, int, int, int, int, int, int, int, int, int, int, int, int, int, double, double, double, double, double, double, double, double, double, double, double, double, double, double, double); - - id (*idmsg)(id, SEL, vector_ulong2, vector_ulong2, vector_ulong2, vector_ulong2, vector_ulong2, vector_ulong2, vector_ulong2, vector_ulong2, int, int, int, int, int, int, int, int, int, int, int, int, int, double, double, double, double, double, double, double, double, double, double, double, double, double, double, double) __attribute__((unused)); - id (*idmsgsuper)(struct objc_super *, SEL, vector_ulong2, vector_ulong2, vector_ulong2, vector_ulong2, vector_ulong2, vector_ulong2, vector_ulong2, vector_ulong2, int, int, int, int, int, int, int, int, int, int, int, int, int, double, double, double, double, double, double, double, double, double, double, double, double, double, double, double) __attribute__((unused)); - long long (*llmsg)(id, SEL, vector_ulong2, vector_ulong2, vector_ulong2, vector_ulong2, vector_ulong2, vector_ulong2, vector_ulong2, vector_ulong2, int, int, int, int, int, int, int, int, int, int, int, int, int, double, double, double, double, double, double, double, double, double, double, double, double, double, double, double) __attribute__((unused)); - struct stret (*stretmsg)(id, SEL, vector_ulong2, vector_ulong2, vector_ulong2, vector_ulong2, vector_ulong2, vector_ulong2, vector_ulong2, vector_ulong2, int, int, int, int, int, int, int, int, int, int, int, int, int, double, double, double, double, double, double, double, double, double, double, double, double, double, double, double) __attribute__((unused)); - struct stret (*stretmsgsuper)(struct objc_super *, SEL, vector_ulong2, vector_ulong2, vector_ulong2, vector_ulong2, vector_ulong2, vector_ulong2, vector_ulong2, vector_ulong2, int, int, int, int, int, int, int, int, int, int, int, int, int, double, double, double, double, double, double, double, double, double, double, double, double, double, double, double) __attribute__((unused)); - double (*fpmsg)(id, SEL, vector_ulong2, vector_ulong2, vector_ulong2, vector_ulong2, vector_ulong2, vector_ulong2, vector_ulong2, vector_ulong2, int, int, int, int, int, int, int, int, int, int, int, int, int, double, double, double, double, double, double, double, double, double, double, double, double, double, double, double) __attribute__((unused)); - long double (*lfpmsg)(id, SEL, vector_ulong2, vector_ulong2, vector_ulong2, vector_ulong2, vector_ulong2, vector_ulong2, vector_ulong2, vector_ulong2, int, int, int, int, int, int, int, int, int, int, int, int, int, double, double, double, double, double, double, double, double, double, double, double, double, double, double, double) __attribute__((unused)); - vector_ulong2 (*vecmsg)(id, SEL, vector_ulong2, vector_ulong2, vector_ulong2, vector_ulong2, vector_ulong2, vector_ulong2, vector_ulong2, vector_ulong2, int, int, int, int, int, int, int, int, int, int, int, int, int, double, double, double, double, double, double, double, double, double, double, double, double, double, double, double) __attribute__((unused)); - - // get +initialize out of the way - [Sub class]; -#if OBJC_HAVE_TAGGED_POINTERS - [TaggedSub class]; -#endif - - ID_RESULT = [Super new]; - - Sub *sub = [Sub new]; - Super *sup = [Super new]; -#if OBJC_HAVE_TAGGED_POINTERS - TaggedSub *tagged = objc_unretainedObject(_objc_makeTaggedPointer(OBJC_TAG_7, 999)); -#endif - - // Basic cached and uncached dispatch. - // Do this first before anything below caches stuff. - testprintf("basic\n"); - test_basic(sub); -#if OBJC_HAVE_TAGGED_POINTERS - testprintf("basic tagged\n"); - test_basic(tagged); -#endif - - idmethod = class_getInstanceMethod([Super class], @selector(idret::::::::::::::::::::::::::::::::::::)); - testassert(idmethod); - llmethod = class_getInstanceMethod([Super class], @selector(llret::::::::::::::::::::::::::::::::::::)); - testassert(llmethod); - stretmethod = class_getInstanceMethod([Super class], @selector(stret::::::::::::::::::::::::::::::::::::)); - testassert(stretmethod); - fpmethod = class_getInstanceMethod([Super class], @selector(fpret::::::::::::::::::::::::::::::::::::)); - testassert(fpmethod); - lfpmethod = class_getInstanceMethod([Super class], @selector(lfpret::::::::::::::::::::::::::::::::::::)); - testassert(lfpmethod); - vecmethod = class_getInstanceMethod([Super class], @selector(vecret::::::::::::::::::::::::::::::::::::)); - testassert(vecmethod); - - idfn = (id (*)(id, Method, vector_ulong2, vector_ulong2, vector_ulong2, vector_ulong2, vector_ulong2, vector_ulong2, vector_ulong2, vector_ulong2, int, int, int, int, int, int, int, int, int, int, int, int, int, double, double, double, double, double, double, double, double, double, double, double, double, double, double, double)) method_invoke; - llfn = (long long (*)(id, Method, vector_ulong2, vector_ulong2, vector_ulong2, vector_ulong2, vector_ulong2, vector_ulong2, vector_ulong2, vector_ulong2, int, int, int, int, int, int, int, int, int, int, int, int, int, double, double, double, double, double, double, double, double, double, double, double, double, double, double, double)) method_invoke; - stretfn = (struct stret (*)(id, Method, vector_ulong2, vector_ulong2, vector_ulong2, vector_ulong2, vector_ulong2, vector_ulong2, vector_ulong2, vector_ulong2, int, int, int, int, int, int, int, int, int, int, int, int, int, double, double, double, double, double, double, double, double, double, double, double, double, double, double, double)) method_invoke_stret; - fpfn = (double (*)(id, Method, vector_ulong2, vector_ulong2, vector_ulong2, vector_ulong2, vector_ulong2, vector_ulong2, vector_ulong2, vector_ulong2, int, int, int, int, int, int, int, int, int, int, int, int, int, double, double, double, double, double, double, double, double, double, double, double, double, double, double, double)) method_invoke; - lfpfn = (long double (*)(id, Method, vector_ulong2, vector_ulong2, vector_ulong2, vector_ulong2, vector_ulong2, vector_ulong2, vector_ulong2, vector_ulong2, int, int, int, int, int, int, int, int, int, int, int, int, int, double, double, double, double, double, double, double, double, double, double, double, double, double, double, double)) method_invoke; - vecfn = (vector_ulong2 (*)(id, Method, vector_ulong2, vector_ulong2, vector_ulong2, vector_ulong2, vector_ulong2, vector_ulong2, vector_ulong2, vector_ulong2, int, int, int, int, int, int, int, int, int, int, int, int, int, double, double, double, double, double, double, double, double, double, double, double, double, double, double, double)) method_invoke; - - // cached message performance - // catches failure to cache or (abi=2) failure to fixup (#5584187) - // fixme unless they all fail - // `.align 4` matches loop alignment to make -O0 work - // fill cache first - testprintf("time checks\n"); - - SELF = sub; - [sub voidret_nop]; - [sub voidret_nop2]; - [sub llret_nop]; - [sub stret_nop]; - [sub fpret_nop]; - [sub lfpret_nop]; - [sub vecret_nop]; - [sub voidret_nop]; - [sub voidret_nop2]; - [sub llret_nop]; - [sub stret_nop]; - [sub fpret_nop]; - [sub lfpret_nop]; - [sub vecret_nop]; - [sub voidret_nop]; - [sub voidret_nop2]; - [sub llret_nop]; - [sub stret_nop]; - [sub fpret_nop]; - [sub lfpret_nop]; - [sub vecret_nop]; - - // Some of these times have high variance on some compilers. - // The errors we're trying to catch should be catastrophically slow, - // so the margins here are generous to avoid false failures. - - // Use voidret because id return is too slow for perf test with ARC. - - // Pick smallest of voidret_nop and voidret_nop2 time - // in the hopes that one of them didn't collide in the method cache. - -#define COUNT 1000000 - - startTime = mach_absolute_time(); - ALIGN_(); - for (i = 0; i < COUNT; i++) { - [sub voidret_nop]; - } - totalTime = mach_absolute_time() - startTime; - testprintf("time: voidret %llu\n", totalTime); - targetTime = totalTime; - - startTime = mach_absolute_time(); - ALIGN_(); - for (i = 0; i < COUNT; i++) { - [sub voidret_nop2]; - } - totalTime = mach_absolute_time() - startTime; - testprintf("time: voidret2 %llu\n", totalTime); - if (totalTime < targetTime) targetTime = totalTime; - - startTime = mach_absolute_time(); - ALIGN_(); - for (i = 0; i < COUNT; i++) { - [sub llret_nop]; - } - totalTime = mach_absolute_time() - startTime; - timecheck("llret ", totalTime, targetTime * 0.7, targetTime * 2.0); - - startTime = mach_absolute_time(); - ALIGN_(); - for (i = 0; i < COUNT; i++) { - [sub stret_nop]; - } - totalTime = mach_absolute_time() - startTime; - timecheck("stret ", totalTime, targetTime * 0.7, targetTime * 5.0); - - startTime = mach_absolute_time(); - ALIGN_(); - for (i = 0; i < COUNT; i++) { - [sub fpret_nop]; - } - totalTime = mach_absolute_time() - startTime; - timecheck("fpret ", totalTime, targetTime * 0.7, targetTime * 4.0); - - startTime = mach_absolute_time(); - ALIGN_(); - for (i = 0; i < COUNT; i++) { - [sub lfpret_nop]; - } - totalTime = mach_absolute_time() - startTime; - timecheck("lfpret", totalTime, targetTime * 0.7, targetTime * 4.0); - - startTime = mach_absolute_time(); - ALIGN_(); - for (i = 0; i < COUNT; i++) { - [sub vecret_nop]; - } - totalTime = mach_absolute_time() - startTime; - timecheck("vecret", totalTime, targetTime * 0.7, targetTime * 4.0); - -#if __arm64__ - // Removing this testwarn(), or changing voidret_nop to nop;ret, - // changes the voidret_nop and stret_nop times above by a factor of 2. - testwarn("rdar://13896922 nop;ret is faster than ret?"); -#endif - -#undef COUNT - - // method_invoke - // method_invoke long long - // method_invoke_stret stret - // method_invoke_stret fpret - // method_invoke fpret long double - testprintf("method_invoke\n"); - - SELF = sup; - - state = 0; - idval = nil; - idval = (*idfn)(sup, idmethod, VEC1, VEC2, VEC3, VEC4, VEC5, VEC6, VEC7, VEC8, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0); - testassert(state == 1); - testassert(idval == ID_RESULT); - - llval = 0; - llval = (*llfn)(sup, llmethod, VEC1, VEC2, VEC3, VEC4, VEC5, VEC6, VEC7, VEC8, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0); - testassert(state == 2); - testassert(llval == LL_RESULT); - - stretval = zero; - stretval = (*stretfn)(sup, stretmethod, VEC1, VEC2, VEC3, VEC4, VEC5, VEC6, VEC7, VEC8, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0); - testassert(state == 3); - testassert(stret_equal(stretval, STRET_RESULT)); - - fpval = 0; - fpval = (*fpfn)(sup, fpmethod, VEC1, VEC2, VEC3, VEC4, VEC5, VEC6, VEC7, VEC8, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0); - testassert(state == 4); - testassert(fpval == FP_RESULT); - - lfpval = 0; - lfpval = (*lfpfn)(sup, lfpmethod, VEC1, VEC2, VEC3, VEC4, VEC5, VEC6, VEC7, VEC8, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0); - testassert(state == 5); - testassert(lfpval == LFP_RESULT); - - vecval = 0; - vecval = (*vecfn)(sup, vecmethod, VEC1, VEC2, VEC3, VEC4, VEC5, VEC6, VEC7, VEC8, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0); - testassert(state == 6); - testassert(vector_equal(vecval, VEC_RESULT)); - - - // message to nil - // message to nil long long - // message to nil stret - // message to nil fpret - // message to nil fpret long double - // Use NIL_RECEIVER to avoid compiler optimizations. - testprintf("message to nil\n"); - - state = 0; - idval = ID_RESULT; - idval = [(id)NIL_RECEIVER idret :VEC1:VEC2:VEC3:VEC4:VEC5:VEC6:VEC7:VEC8:1:2:3:4:5:6:7:8:9:10:11:12:13:1.0:2.0:3.0:4.0:5.0:6.0:7.0:8.0:9.0:10.0:11.0:12.0:13.0:14.0:15.0]; - testassert(state == 0); - testassert(idval == nil); - - state = 0; - llval = LL_RESULT; - llval = [(id)NIL_RECEIVER llret :VEC1:VEC2:VEC3:VEC4:VEC5:VEC6:VEC7:VEC8:1:2:3:4:5:6:7:8:9:10:11:12:13:1.0:2.0:3.0:4.0:5.0:6.0:7.0:8.0:9.0:10.0:11.0:12.0:13.0:14.0:15.0]; - testassert(state == 0); - testassert(llval == 0LL); - - state = 0; - stretval = zero; - stretval = [(id)NIL_RECEIVER stret :VEC1:VEC2:VEC3:VEC4:VEC5:VEC6:VEC7:VEC8:1:2:3:4:5:6:7:8:9:10:11:12:13:1.0:2.0:3.0:4.0:5.0:6.0:7.0:8.0:9.0:10.0:11.0:12.0:13.0:14.0:15.0]; - testassert(state == 0); -#if __clang__ - testassert(0 == memcmp(&stretval, &zero, sizeof(stretval))); -#else - // no stret result guarantee -#endif - -#if __x86_64__ - // check stret return register - state = 0; - stretval = zero; - stretptr = ((struct stret *(*)(struct stret *, id, SEL))objc_msgSend_stret) - (&stretval, nil, @selector(stret_nop)); - testassert(stretptr == &stretval); - testassert(state == 0); - // no stret result guarantee for hand-written calls, even with clang -#endif - -#if __i386__ - // check struct-return address stack pop - for (int i = 0; i < 10000000; i++) { - state = 0; - ((struct stret (*)(id, SEL))objc_msgSend_stret) - (nil, @selector(stret_nop)); - } -#endif - - state = 0; - fpval = FP_RESULT; - fpval = [(id)NIL_RECEIVER fpret :VEC1:VEC2:VEC3:VEC4:VEC5:VEC6:VEC7:VEC8:1:2:3:4:5:6:7:8:9:10:11:12:13:1.0:2.0:3.0:4.0:5.0:6.0:7.0:8.0:9.0:10.0:11.0:12.0:13.0:14.0:15.0]; - testassert(state == 0); - testassert(fpval == 0.0); - - state = 0; - lfpval = LFP_RESULT; - lfpval = [(id)NIL_RECEIVER lfpret :VEC1:VEC2:VEC3:VEC4:VEC5:VEC6:VEC7:VEC8:1:2:3:4:5:6:7:8:9:10:11:12:13:1.0:2.0:3.0:4.0:5.0:6.0:7.0:8.0:9.0:10.0:11.0:12.0:13.0:14.0:15.0]; - testassert(state == 0); - testassert(lfpval == 0.0); - - state = 0; - vecval = VEC_RESULT; - vecval = [(id)NIL_RECEIVER vecret :VEC1:VEC2:VEC3:VEC4:VEC5:VEC6:VEC7:VEC8:1:2:3:4:5:6:7:8:9:10:11:12:13:1.0:2.0:3.0:4.0:5.0:6.0:7.0:8.0:9.0:10.0:11.0:12.0:13.0:14.0:15.0]; - testassert(state == 0); - testassert(vector_all(vecval == 0)); - - // message to nil, different struct types - // This verifies that ordinary objc_msgSend() erases enough registers - // for structs that return in registers. -#define TEST_NIL_STRUCT(i,n) \ - do { \ - struct stret_##i##n z; \ - bzero(&z, sizeof(z)); \ - [Super stret_i##n##_nonzero]; \ - [Super stret_d##n##_nonzero]; \ - struct stret_##i##n val = [(id)NIL_RECEIVER stret_##i##n##_zero]; \ - testassert(0 == memcmp(&z, &val, sizeof(val))); \ - } while (0) - - TEST_NIL_STRUCT(i,1); - TEST_NIL_STRUCT(i,2); - TEST_NIL_STRUCT(i,3); - TEST_NIL_STRUCT(i,4); - TEST_NIL_STRUCT(i,5); - TEST_NIL_STRUCT(i,6); - TEST_NIL_STRUCT(i,7); - TEST_NIL_STRUCT(i,8); - TEST_NIL_STRUCT(i,9); - -#if __i386__ - testwarn("rdar://16267205 i386 struct{float} and struct{double}"); -#else - TEST_NIL_STRUCT(d,1); -#endif - TEST_NIL_STRUCT(d,2); - TEST_NIL_STRUCT(d,3); - TEST_NIL_STRUCT(d,4); - TEST_NIL_STRUCT(d,5); - TEST_NIL_STRUCT(d,6); - TEST_NIL_STRUCT(d,7); - TEST_NIL_STRUCT(d,8); - TEST_NIL_STRUCT(d,9); - - -#if __OBJC2__ - // message to nil noarg - // explicitly call noarg messenger, even if compiler doesn't emit it - state = 0; - idval = ID_RESULT; - idval = ((typeof(idmsg0))objc_msgSend_noarg)(nil, @selector(idret_noarg)); - testassert(state == 0); - testassert(idval == nil); - - state = 0; - llval = LL_RESULT; - llval = ((typeof(llmsg0))objc_msgSend_noarg)(nil, @selector(llret_noarg)); - testassert(state == 0); - testassert(llval == 0LL); - - // no stret_noarg messenger - -# if !__i386__ - state = 0; - fpval = FP_RESULT; - fpval = ((typeof(fpmsg0))objc_msgSend_noarg)(nil, @selector(fpret_noarg)); - testassert(state == 0); - testassert(fpval == 0.0); - - state = 0; - vecval = VEC_RESULT; - vecval = ((typeof(vecmsg0))objc_msgSend_noarg)(nil, @selector(vecret_noarg)); - testassert(state == 0); - testassert(vector_all(vecval == 0)); -# endif -# if !__i386__ && !__x86_64__ - state = 0; - lfpval = LFP_RESULT; - lfpval = ((typeof(lfpmsg0))objc_msgSend_noarg)(nil, @selector(lfpret_noarg)); - testassert(state == 0); - testassert(lfpval == 0.0); -# endif -#endif - - -#if __OBJC2__ - // rdar://8271364 objc_msgSendSuper2 must not change objc_super - testprintf("super struct\n"); - struct objc_super sup_st = { - sub, - object_getClass(sub), - }; - - SELF = sub; - - state = 100; - idval = nil; - idval = ((id(*)(struct objc_super *, SEL, vector_ulong2,vector_ulong2,vector_ulong2,vector_ulong2,vector_ulong2,vector_ulong2,vector_ulong2,vector_ulong2, int,int,int,int,int,int,int,int,int,int,int,int,int, double,double,double,double,double,double,double,double,double,double,double,double,double,double,double))objc_msgSendSuper2) (&sup_st, @selector(idret::::::::::::::::::::::::::::::::::::), VEC1,VEC2,VEC3,VEC4,VEC5,VEC6,VEC7,VEC8, 1,2,3,4,5,6,7,8,9,10,11,12,13, 1.0,2.0,3.0,4.0,5.0,6.0,7.0,8.0,9.0,10.0,11.0,12.0,13.0,14.0,15.0); - testassert(state == 1); - testassert(idval == ID_RESULT); - testassert(sup_st.receiver == sub); - testassert(sup_st.super_class == object_getClass(sub)); - - state = 100; - stretval = zero; - stretval = ((struct stret(*)(struct objc_super *, SEL, vector_ulong2,vector_ulong2,vector_ulong2,vector_ulong2,vector_ulong2,vector_ulong2,vector_ulong2,vector_ulong2, int,int,int,int,int,int,int,int,int,int,int,int,int, double,double,double,double,double,double,double,double,double,double,double,double,double,double,double))objc_msgSendSuper2_stret) (&sup_st, @selector(stret::::::::::::::::::::::::::::::::::::), VEC1,VEC2,VEC3,VEC4,VEC5,VEC6,VEC7,VEC8, 1,2,3,4,5,6,7,8,9,10,11,12,13, 1.0,2.0,3.0,4.0,5.0,6.0,7.0,8.0,9.0,10.0,11.0,12.0,13.0,14.0,15.0); - testassert(state == 3); - testassert(stret_equal(stretval, STRET_RESULT)); - testassert(sup_st.receiver == sub); - testassert(sup_st.super_class == object_getClass(sub)); -#endif - -#if __OBJC2__ && !__arm64__ - // Debug messengers. - testprintf("debug messengers\n"); - - state = 0; - idmsg = (typeof(idmsg))objc_msgSend_debug; - idval = nil; - idval = (*idmsg)(sub, @selector(idret::::::::::::::::::::::::::::::::::::), VEC1, VEC2, VEC3, VEC4, VEC5, VEC6, VEC7, VEC8, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0); - testassert(state == 101); - testassert(idval == ID_RESULT); - - state = 0; - llmsg = (typeof(llmsg))objc_msgSend_debug; - llval = 0; - llval = (*llmsg)(sub, @selector(llret::::::::::::::::::::::::::::::::::::), VEC1, VEC2, VEC3, VEC4, VEC5, VEC6, VEC7, VEC8, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0); - testassert(state == 102); - testassert(llval == LL_RESULT); - - state = 0; - stretmsg = (typeof(stretmsg))objc_msgSend_stret_debug; - stretval = zero; - stretval = (*stretmsg)(sub, @selector(stret::::::::::::::::::::::::::::::::::::), VEC1, VEC2, VEC3, VEC4, VEC5, VEC6, VEC7, VEC8, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0); - testassert(state == 103); - testassert(stret_equal(stretval, STRET_RESULT)); - - state = 100; - sup_st.receiver = sub; - sup_st.super_class = object_getClass(sub); - idmsgsuper = (typeof(idmsgsuper))objc_msgSendSuper2_debug; - idval = nil; - idval = (*idmsgsuper)(&sup_st, @selector(idret::::::::::::::::::::::::::::::::::::), VEC1, VEC2, VEC3, VEC4, VEC5, VEC6, VEC7, VEC8, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0); - testassert(state == 1); - testassert(idval == ID_RESULT); - - state = 100; - sup_st.receiver = sub; - sup_st.super_class = object_getClass(sub); - stretmsgsuper = (typeof(stretmsgsuper))objc_msgSendSuper2_stret_debug; - stretval = zero; - stretval = (*stretmsgsuper)(&sup_st, @selector(stret::::::::::::::::::::::::::::::::::::), VEC1, VEC2, VEC3, VEC4, VEC5, VEC6, VEC7, VEC8, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0); - testassert(state == 3); - testassert(stret_equal(stretval, STRET_RESULT)); - -#if __i386__ - state = 0; - fpmsg = (typeof(fpmsg))objc_msgSend_fpret_debug; - fpval = 0; - fpval = (*fpmsg)(sub, @selector(fpret::::::::::::::::::::::::::::::::::::), VEC1, VEC2, VEC3, VEC4, VEC5, VEC6, VEC7, VEC8, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0); - testassert(state == 104); - testassert(fpval == FP_RESULT); -#endif -#if __x86_64__ - state = 0; - lfpmsg = (typeof(lfpmsg))objc_msgSend_fpret_debug; - lfpval = 0; - lfpval = (*lfpmsg)(sub, @selector(lfpret::::::::::::::::::::::::::::::::::::), VEC1, VEC2, VEC3, VEC4, VEC5, VEC6, VEC7, VEC8, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0); - testassert(state == 105); - testassert(lfpval == LFP_RESULT); - - // fixme fp2ret -#endif - -// debug messengers -#endif - - -#if !TEST_DWARF - testwarn("no unwind tables in this configuration"); -#else - // DWARF unwind tables - testprintf("unwind tables\n"); - - // install exception handler - struct sigaction act; - act.sa_sigaction = sigtrap; - act.sa_mask = 0; - act.sa_flags = SA_SIGINFO; - sigaction(SIGTRAP, &act, NULL); - - SubDW *dw = [[SubDW alloc] init]; - - objc_setForwardHandler((void*)test_dw_forward, (void*)test_dw_forward_stret); - -# if __x86_64__ - test_dw("objc_msgSend", dw, tagged, false, 0); - test_dw("objc_msgSend_stret", dw, tagged, true, 0); - test_dw("objc_msgSend_fpret", dw, tagged, false, 0); - test_dw("objc_msgSend_fp2ret", dw, tagged, false, 0); - test_dw("objc_msgSendSuper", dw, tagged, false, 0); - test_dw("objc_msgSendSuper2", dw, tagged, false, 0); - test_dw("objc_msgSendSuper_stret", dw, tagged, true, 0); - test_dw("objc_msgSendSuper2_stret", dw, tagged, true, 0); -# elif __i386__ - test_dw("objc_msgSend", dw, dw, false, 0); - test_dw("objc_msgSend_stret", dw, dw, true, 0); - test_dw("objc_msgSend_fpret", dw, dw, false, 0); - test_dw("objc_msgSendSuper", dw, dw, false, 0); - test_dw("objc_msgSendSuper2", dw, dw, false, 0); - test_dw("objc_msgSendSuper_stret", dw, dw, true, 0); - test_dw("objc_msgSendSuper2_stret", dw, dw, true, 0); -# elif __arm64__ - test_dw("objc_msgSend", dw, tagged, false, 1); - test_dw("objc_msgSendSuper", dw, tagged, false, 1); - test_dw("objc_msgSendSuper2", dw, tagged, false, 1); -# else -# error unknown architecture -# endif - - // DWARF unwind tables -#endif - - } POP_POOL; - succeed(__FILE__); -} - -#endif diff --git a/test/nilAPIArgs.m b/test/nilAPIArgs.m deleted file mode 100644 index a133451..0000000 --- a/test/nilAPIArgs.m +++ /dev/null @@ -1,13 +0,0 @@ -// TEST_CONFIG - -#include "test.h" - -#import - -int main() { - // ensure various bits of API don't crash when tossed nil parameters - class_conformsToProtocol(nil, nil); - method_setImplementation(nil, NULL); - - succeed(__FILE__); -} diff --git a/test/nonpointerisa.m b/test/nonpointerisa.m deleted file mode 100644 index 1fe2a70..0000000 --- a/test/nonpointerisa.m +++ /dev/null @@ -1,223 +0,0 @@ -// TEST_CFLAGS -framework Foundation -// TEST_CONFIG MEM=mrc - -#include "test.h" - -#if !__OBJC2__ - -int main() -{ - succeed(__FILE__); -} - -#else - -#include - -#include -#include - -#define ISA(x) (*((uintptr_t *)(x))) -#define INDEXED(x) (ISA(x) & 1) - -#if SUPPORT_NONPOINTER_ISA -# if __x86_64__ -# define RC_ONE (1ULL<<56) -# elif __arm64__ -# define RC_ONE (1ULL<<45) -# else -# error unknown architecture -# endif -#endif - - -void check_unindexed(id obj, Class cls) -{ - testassert(object_getClass(obj) == cls); - testassert(!INDEXED(obj)); - - uintptr_t isa = ISA(obj); - testassert((Class)isa == cls); - testassert((Class)(isa & objc_debug_isa_class_mask) == cls); - testassert((Class)(isa & ~objc_debug_isa_class_mask) == 0); - - CFRetain(obj); - testassert(ISA(obj) == isa); - testassert([obj retainCount] == 2); - [obj retain]; - testassert(ISA(obj) == isa); - testassert([obj retainCount] == 3); - CFRelease(obj); - testassert(ISA(obj) == isa); - testassert([obj retainCount] == 2); - [obj release]; - testassert(ISA(obj) == isa); - testassert([obj retainCount] == 1); -} - - -#if ! SUPPORT_NONPOINTER_ISA - -int main() -{ - testprintf("Isa with index\n"); - id index_o = [NSObject new]; - check_unindexed(index_o, [NSObject class]); - - // These variables DO exist even without non-pointer isa support - testassert(dlsym(RTLD_DEFAULT, "objc_debug_isa_class_mask")); - testassert(dlsym(RTLD_DEFAULT, "objc_debug_isa_magic_mask")); - testassert(dlsym(RTLD_DEFAULT, "objc_debug_isa_magic_value")); - - succeed(__FILE__); -} - -#else -// SUPPORT_NONPOINTER_ISA - -void check_indexed(id obj, Class cls) -{ - testassert(object_getClass(obj) == cls); - testassert(INDEXED(obj)); - - uintptr_t isa = ISA(obj); - testassert((Class)(isa & objc_debug_isa_class_mask) == cls); - testassert((Class)(isa & ~objc_debug_isa_class_mask) != 0); - testassert((isa & objc_debug_isa_magic_mask) == objc_debug_isa_magic_value); - - CFRetain(obj); - testassert(ISA(obj) == isa + RC_ONE); - testassert([obj retainCount] == 2); - [obj retain]; - testassert(ISA(obj) == isa + RC_ONE*2); - testassert([obj retainCount] == 3); - CFRelease(obj); - testassert(ISA(obj) == isa + RC_ONE); - testassert([obj retainCount] == 2); - [obj release]; - testassert(ISA(obj) == isa); - testassert([obj retainCount] == 1); -} - - -@interface OS_object -+(id)new; -@end - -@interface Fake_OS_object : NSObject { - int refcnt; - int xref_cnt; -} -@end - -@implementation Fake_OS_object -+(void)initialize { - static bool initialized; - if (!initialized) { - initialized = true; - testprintf("Indexed during +initialize\n"); - testassert(INDEXED(self)); - id o = [Fake_OS_object new]; - check_indexed(o, self); - [o release]; - } -} -@end - -@interface Sub_OS_object : OS_object @end - -@implementation Sub_OS_object -@end - - - -int main() -{ - uintptr_t isa; - - testprintf("Isa with index\n"); - id index_o = [Fake_OS_object new]; - check_indexed(index_o, [Fake_OS_object class]); - - testprintf("Weakly referenced\n"); - isa = ISA(index_o); - id weak; - objc_storeWeak(&weak, index_o); - testassert(__builtin_popcountl(isa ^ ISA(index_o)) == 1); - - testprintf("Has associated references\n"); - id assoc = @"thing"; - isa = ISA(index_o); - objc_setAssociatedObject(index_o, assoc, assoc, OBJC_ASSOCIATION_ASSIGN); - testassert(__builtin_popcountl(isa ^ ISA(index_o)) == 1); - - - testprintf("Isa without index\n"); - id unindex_o = [OS_object new]; - check_unindexed(unindex_o, [OS_object class]); - - - id buf[4]; - id bufo = (id)buf; - - testprintf("Change isa 0 -> unindexed\n"); - bzero(buf, sizeof(buf)); - object_setClass(bufo, [OS_object class]); - check_unindexed(bufo, [OS_object class]); - - testprintf("Change isa 0 -> indexed\n"); - bzero(buf, sizeof(buf)); - object_setClass(bufo, [NSObject class]); - check_indexed(bufo, [NSObject class]); - - testprintf("Change isa indexed -> indexed\n"); - testassert(INDEXED(bufo)); - _objc_rootRetain(bufo); - testassert(_objc_rootRetainCount(bufo) == 2); - object_setClass(bufo, [Fake_OS_object class]); - testassert(_objc_rootRetainCount(bufo) == 2); - _objc_rootRelease(bufo); - testassert(_objc_rootRetainCount(bufo) == 1); - check_indexed(bufo, [Fake_OS_object class]); - - testprintf("Change isa indexed -> unindexed\n"); - // Retain count must be preserved. - // Use root* to avoid OS_object's overrides. - testassert(INDEXED(bufo)); - _objc_rootRetain(bufo); - testassert(_objc_rootRetainCount(bufo) == 2); - object_setClass(bufo, [OS_object class]); - testassert(_objc_rootRetainCount(bufo) == 2); - _objc_rootRelease(bufo); - testassert(_objc_rootRetainCount(bufo) == 1); - check_unindexed(bufo, [OS_object class]); - - testprintf("Change isa unindexed -> indexed (doesn't happen)\n"); - testassert(!INDEXED(bufo)); - _objc_rootRetain(bufo); - testassert(_objc_rootRetainCount(bufo) == 2); - object_setClass(bufo, [Fake_OS_object class]); - testassert(_objc_rootRetainCount(bufo) == 2); - _objc_rootRelease(bufo); - testassert(_objc_rootRetainCount(bufo) == 1); - check_unindexed(bufo, [Fake_OS_object class]); - - testprintf("Change isa unindexed -> unindexed\n"); - testassert(!INDEXED(bufo)); - _objc_rootRetain(bufo); - testassert(_objc_rootRetainCount(bufo) == 2); - object_setClass(bufo, [Sub_OS_object class]); - testassert(_objc_rootRetainCount(bufo) == 2); - _objc_rootRelease(bufo); - testassert(_objc_rootRetainCount(bufo) == 1); - check_unindexed(bufo, [Sub_OS_object class]); - - - succeed(__FILE__); -} - -// SUPPORT_NONPOINTER_ISA -#endif - -// __OBJC2__ -#endif diff --git a/test/nopool.m b/test/nopool.m deleted file mode 100644 index 57e8fcb..0000000 --- a/test/nopool.m +++ /dev/null @@ -1,38 +0,0 @@ -// TEST_CONFIG MEM=mrc - -#include "test.h" -#include "testroot.i" - -@implementation TestRoot (Loader) -+(void)load -{ - [[TestRoot new] autorelease]; - testassert(TestRootAutorelease == 1); - testassert(TestRootDealloc == 0); -} -@end - -int main() -{ - // +load's autoreleased object should have deallocated - testassert(TestRootDealloc == 1); - - [[TestRoot new] autorelease]; - testassert(TestRootAutorelease == 2); - - objc_autoreleasePoolPop(objc_autoreleasePoolPush()); - - [[TestRoot new] autorelease]; - testassert(TestRootAutorelease == 3); - - testonthread(^{ - [[TestRoot new] autorelease]; - testassert(TestRootAutorelease == 4); - testassert(TestRootDealloc == 1); - }); - - // thread's autoreleased object should have deallocated - testassert(TestRootDealloc == 2); - - succeed(__FILE__); -} diff --git a/test/nscdtors.mm b/test/nscdtors.mm deleted file mode 100644 index 876f6b0..0000000 --- a/test/nscdtors.mm +++ /dev/null @@ -1,6 +0,0 @@ -// TEST_CONFIG -// test cdtors, with NSObject instead of TestRoot as the root class - -#define USE_FOUNDATION 1 -#include "cdtors.mm" - diff --git a/test/nsexc.m b/test/nsexc.m deleted file mode 100644 index 4884345..0000000 --- a/test/nsexc.m +++ /dev/null @@ -1,21 +0,0 @@ -/* -need exception-safe ARC for exception deallocation tests -TEST_CFLAGS -fobjc-arc-exceptions -framework Foundation - -llvm-gcc unavoidably warns about our deliberately out-of-order handlers - -TEST_BUILD_OUTPUT -In file included from .* -.*exc.m: In function .* -.*exc.m:\d+: warning: exception of type .* will be caught -.*exc.m:\d+: warning: by earlier handler for .* -.*exc.m:\d+: warning: exception of type .* will be caught -.*exc.m:\d+: warning: by earlier handler for .* -.*exc.m:\d+: warning: exception of type .* will be caught -.*exc.m:\d+: warning: by earlier handler for .* -OR -END -*/ - -#define USE_FOUNDATION 1 -#include "exc.m" diff --git a/test/nsobject.m b/test/nsobject.m deleted file mode 100644 index 4a79f32..0000000 --- a/test/nsobject.m +++ /dev/null @@ -1,114 +0,0 @@ -// TEST_CONFIG MEM=mrc,gc - -#include "test.h" - -#import - -@interface Sub : NSObject @end -@implementation Sub -+(id)allocWithZone:(NSZone *)zone { - testprintf("in +[Sub alloc]\n"); - return [super allocWithZone:zone]; - } --(void)dealloc { - testprintf("in -[Sub dealloc]\n"); - [super dealloc]; -} -@end - - -// These declarations and definitions can be used -// to check the compile-time type of an object. -@interface NSObject (Checker) -// fixme this isn't actually enforced -+(void)NSObjectInstance __attribute__((unavailable)); -@end -@implementation NSObject (Checker) --(void)NSObjectInstance { } -+(void)NSObjectClass { } -@end -@interface Sub (Checker) --(void)NSObjectInstance __attribute__((unavailable)); -+(void)NSObjectClass __attribute__((unavailable)); -@end -@implementation Sub (Checker) --(void)SubInstance { } -+(void)SubClass { } -@end - -int main() -{ - PUSH_POOL { - [[Sub new] autorelease]; - } POP_POOL; - - // Verify that dot syntax on class objects works with some instance methods - // (void)NSObject.self; fixme - (void)NSObject.class; - (void)NSObject.superclass; - (void)NSObject.hash; - (void)NSObject.description; - (void)NSObject.debugDescription; - - // Verify that some methods return the correct type. - Class cls; - NSObject *nsobject = nil; - Sub *subobject = nil; - - cls = [NSObject self]; - cls = [Sub self]; - nsobject = [nsobject self]; - subobject = [subobject self]; - [[NSObject self] NSObjectClass]; - [[nsobject self] NSObjectInstance]; - [[Sub self] SubClass]; - [[subobject self] SubInstance]; - - // fixme - // cls = NSObject.self; - // cls = Sub.self; - // [NSObject.self NSObjectClass]; - // [nsobject.self NSObjectInstance]; - // [Sub.self SubClass]; - // [subobject.self SubInstance]; - - cls = [NSObject class]; - cls = [nsobject class]; - cls = [Sub class]; - cls = [subobject class]; - [[NSObject class] NSObjectClass]; - [[nsobject class] NSObjectClass]; - [[Sub class] SubClass]; - [[subobject class] SubClass]; - - cls = NSObject.class; - cls = nsobject.class; - cls = Sub.class; - cls = subobject.class; - [NSObject.class NSObjectClass]; - [nsobject.class NSObjectClass]; - [Sub.class SubClass]; - [subobject.class SubClass]; - - - cls = [NSObject superclass]; - cls = [nsobject superclass]; - cls = [Sub superclass]; - cls = [subobject superclass]; - [[NSObject superclass] NSObjectClass]; - [[nsobject superclass] NSObjectClass]; - [[Sub superclass] NSObjectClass]; - [[subobject superclass] NSObjectClass]; - - cls = NSObject.superclass; - cls = nsobject.superclass; - cls = Sub.superclass; - cls = subobject.superclass; - [NSObject.superclass NSObjectClass]; - [nsobject.superclass NSObjectClass]; - [Sub.superclass NSObjectClass]; - [subobject.superclass NSObjectClass]; - - - succeed(__FILE__); -} diff --git a/test/nsprotocol.m b/test/nsprotocol.m deleted file mode 100644 index 8305cc8..0000000 --- a/test/nsprotocol.m +++ /dev/null @@ -1,47 +0,0 @@ -// TEST_CONFIG - -#include "test.h" - -#if __OBJC2__ - -#include - -int main() -{ - // Class Protocol is always a subclass of NSObject - - testassert(objc_getClass("NSObject")); - - Class cls = objc_getClass("Protocol"); - testassert(class_getInstanceMethod(cls, sel_registerName("isProxy"))); - testassert(class_getSuperclass(cls) == objc_getClass("NSObject")); - - succeed(__FILE__); -} - -#else - -#include -#include - -int main() -{ - // Class Protocol is never a subclass of NSObject - // CoreFoundation adds NSObject methods to Protocol when it loads - - testassert(objc_getClass("NSObject")); - - Class cls = objc_getClass("Protocol"); - testassert(!class_getInstanceMethod(cls, sel_registerName("isProxy"))); - testassert(class_getSuperclass(cls) != objc_getClass("NSObject")); - - void *dl = dlopen("/System/Library/Frameworks/CoreFoundation.framework/CoreFoundation", RTLD_LAZY); - testassert(dl); - - testassert(class_getInstanceMethod(cls, sel_registerName("isProxy"))); - testassert(class_getSuperclass(cls) != objc_getClass("NSObject")); - - succeed(__FILE__); -} - -#endif diff --git a/test/objectCopy.m b/test/objectCopy.m deleted file mode 100644 index 275466c..0000000 --- a/test/objectCopy.m +++ /dev/null @@ -1,37 +0,0 @@ -// TEST_CONFIG MEM=mrc,gc - -#include "test.h" -#include - -@interface Test : NSObject { -@public - char bytes[32-sizeof(void*)]; -} -@end -@implementation Test -@end - - -int main() -{ - Test *o0 = [Test new]; - [o0 retain]; - Test *o1 = class_createInstance([Test class], 32); - [o1 retain]; - id o2 = object_copy(o0, 0); - id o3 = object_copy(o1, 0); - id o4 = object_copy(o1, 32); - testassert(malloc_size(o0) == 32); - testassert(malloc_size(o1) == 64); - testassert(malloc_size(o2) == 32); - testassert(malloc_size(o3) == 32); - testassert(malloc_size(o4) == 64); - if (!objc_collecting_enabled()) { - testassert([o0 retainCount] == 2); - testassert([o1 retainCount] == 2); - testassert([o2 retainCount] == 1); - testassert([o3 retainCount] == 1); - testassert([o4 retainCount] == 1); - } - succeed(__FILE__); -} diff --git a/test/property.m b/test/property.m deleted file mode 100644 index fbcabdd..0000000 --- a/test/property.m +++ /dev/null @@ -1,65 +0,0 @@ -// TEST_CONFIG - -#include "test.h" -#include "testroot.i" -#include -#include -#include - -@interface Super : TestRoot { - @public - char superIvar; -} - -@property(readonly) char superProp; -@end - -@implementation Super -@synthesize superProp = superIvar; -@end - - -@interface Sub : Super { - @public - uintptr_t subIvar; -} -@property(readonly) uintptr_t subProp; -@end - -@implementation Sub -@synthesize subProp = subIvar; -@end - - -int main() -{ - /* - Runtime layout of Sub: - [0] isa - [1] superIvar - [2] subIvar - */ - - objc_property_t prop; - - prop = class_getProperty([Sub class], "subProp"); - testassert(prop); - - prop = class_getProperty([Super class], "superProp"); - testassert(prop); - testassert(prop == class_getProperty([Sub class], "superProp")); - - prop = class_getProperty([Super class], "subProp"); - testassert(!prop); - - prop = class_getProperty(object_getClass([Sub class]), "subProp"); - testassert(!prop); - - - testassert(NULL == class_getProperty(NULL, "foo")); - testassert(NULL == class_getProperty([Sub class], NULL)); - testassert(NULL == class_getProperty(NULL, NULL)); - - succeed(__FILE__); - return 0; -} diff --git a/test/propertyDesc.m b/test/propertyDesc.m deleted file mode 100644 index 2a81655..0000000 --- a/test/propertyDesc.m +++ /dev/null @@ -1,323 +0,0 @@ -// TEST_CONFIG - -#include "test.h" -#include "testroot.i" -#include -#include -#include - -struct objc_property { - const char *name; - const char *attr; -}; - -#define checkattrlist(attrs, attrcount, target) \ - do { \ - if (target > 0) { \ - testassert(attrs); \ - testassert(attrcount == target); \ - testassert(malloc_size(attrs) >= \ - (1+target) * sizeof(objc_property_attribute_t)); \ - testassert(attrs[target].name == NULL); \ - testassert(attrs[target].value == NULL); \ - } else { \ - testassert(!attrs); \ - testassert(attrcount == 0); \ - } \ - } while (0) - -#define checkattr(attrs, i, n, v) \ - do { \ - char *attrsstart = (char *)attrs; \ - char *attrsend = (char *)attrs + malloc_size(attrs); \ - testassert((char*)(attrs+i+1) <= attrsend); \ - testassert(attrs[i].name >= attrsstart); \ - testassert(attrs[i].value >= attrsstart); \ - testassert(attrs[i].name + strlen(attrs[i].name) + 1 <= attrsend); \ - testassert(attrs[i].value + strlen(attrs[i].value) + 1 <= attrsend); \ - if (n) testassert(0 == strcmp(attrs[i].name, n)); \ - else testassert(attrs[i].name == NULL); \ - if (v) testassert(0 == strcmp(attrs[i].value, v)); \ - else testassert(attrs[i].value == NULL); \ - } while (0) - -int main() -{ - char *value; - objc_property_attribute_t *attrs; - unsigned int attrcount; - - // STRING TO ATTRIBUTE LIST (property_copyAttributeList) - - struct objc_property prop; - prop.name = "test"; - - // null property - attrcount = 42; - attrs = property_copyAttributeList(NULL, &attrcount); - testassert(!attrs); - testassert(attrcount == 0); - attrs = property_copyAttributeList(NULL, NULL); - testassert(!attrs); - - // null attributes - attrcount = 42; - prop.attr = NULL; - attrs = property_copyAttributeList(&prop, &attrcount); - checkattrlist(attrs, attrcount, 0); - attrs = property_copyAttributeList(&prop, NULL); - testassert(!attrs); - - // empty attributes - attrcount = 42; - prop.attr = ""; - attrs = property_copyAttributeList(&prop, &attrcount); - checkattrlist(attrs, attrcount, 0); - attrs = property_copyAttributeList(&prop, NULL); - testassert(!attrs); - - // commas only - attrcount = 42; - prop.attr = ",,,"; - attrs = property_copyAttributeList(&prop, &attrcount); - checkattrlist(attrs, attrcount, 0); - attrs = property_copyAttributeList(&prop, NULL); - testassert(!attrs); - - // long and short names, with and without values - attrcount = 42; - prop.attr = "?XX,',\"?!?!\"YY,\"''''\""; - attrs = property_copyAttributeList(&prop, &attrcount); - checkattrlist(attrs, attrcount, 4); - checkattr(attrs, 0, "?", "XX"); - checkattr(attrs, 1, "'", ""); - checkattr(attrs, 2, "?!?!", "YY"); - checkattr(attrs, 3, "''''", ""); - free(attrs); - - // all recognized attributes simultaneously, values with quotes - attrcount = 42; - prop.attr = "T11,V2222,S333333\",G\"44444444,W,P,D,R,N,C,&"; - attrs = property_copyAttributeList(&prop, &attrcount); - checkattrlist(attrs, attrcount, 11); - checkattr(attrs, 0, "T", "11"); - checkattr(attrs, 1, "V", "2222"); - checkattr(attrs, 2, "S", "333333\""); - checkattr(attrs, 3, "G", "\"44444444"); - checkattr(attrs, 4, "W", ""); - checkattr(attrs, 5, "P", ""); - checkattr(attrs, 6, "D", ""); - checkattr(attrs, 7, "R", ""); - checkattr(attrs, 8, "N", ""); - checkattr(attrs, 9, "C", ""); - checkattr(attrs,10, "&", ""); - free(attrs); - - // kitchen sink - attrcount = 42; - prop.attr = "W,T11,P,?XX,D,V2222,R,',N,S333333\",C,\"?!?!\"YY,&,G\"44444444,\"''''\""; - attrs = property_copyAttributeList(&prop, &attrcount); - checkattrlist(attrs, attrcount, 15); - checkattr(attrs, 0, "W", ""); - checkattr(attrs, 1, "T", "11"); - checkattr(attrs, 2, "P", ""); - checkattr(attrs, 3, "?", "XX"); - checkattr(attrs, 4, "D", ""); - checkattr(attrs, 5, "V", "2222"); - checkattr(attrs, 6, "R", ""); - checkattr(attrs, 7, "'", ""); - checkattr(attrs, 8, "N", ""); - checkattr(attrs, 9, "S", "333333\""); - checkattr(attrs,10, "C", ""); - checkattr(attrs,11, "?!?!", "YY"); - checkattr(attrs,12, "&", ""); - checkattr(attrs,13, "G", "\"44444444"); - checkattr(attrs,14, "''''", ""); - free(attrs); - - // SEARCH ATTRIBUTE LIST (property_copyAttributeValue) - - // null property, null name, empty name - value = property_copyAttributeValue(NULL, NULL); - testassert(!value); - value = property_copyAttributeValue(NULL, "foo"); - testassert(!value); - value = property_copyAttributeValue(NULL, ""); - testassert(!value); - value = property_copyAttributeValue(&prop, NULL); - testassert(!value); - value = property_copyAttributeValue(&prop, ""); - testassert(!value); - - // null attributes, empty attributes - prop.attr = NULL; - value = property_copyAttributeValue(&prop, "foo"); - testassert(!value); - prop.attr = ""; - value = property_copyAttributeValue(&prop, "foo"); - testassert(!value); - - // long and short names, with and without values - prop.attr = "?XX,',\"?!?!\"YY,\"''''\""; - value = property_copyAttributeValue(&prop, "missing"); - testassert(!value); - value = property_copyAttributeValue(&prop, "X"); - testassert(!value); - value = property_copyAttributeValue(&prop, "\""); - testassert(!value); - value = property_copyAttributeValue(&prop, "'''"); - testassert(!value); - value = property_copyAttributeValue(&prop, "'''''"); - testassert(!value); - - value = property_copyAttributeValue(&prop, "?"); - testassert(0 == strcmp(value, "XX")); - testassert(malloc_size(value) >= 1 + strlen(value)); - free(value); - value = property_copyAttributeValue(&prop, "'"); - testassert(0 == strcmp(value, "")); - testassert(malloc_size(value) >= 1 + strlen(value)); - free(value); - value = property_copyAttributeValue(&prop, "?!?!"); - testassert(0 == strcmp(value, "YY")); - testassert(malloc_size(value) >= 1 + strlen(value)); - free(value); - value = property_copyAttributeValue(&prop, "''''"); - testassert(0 == strcmp(value, "")); - testassert(malloc_size(value) >= 1 + strlen(value)); - free(value); - - // all recognized attributes simultaneously, values with quotes - prop.attr = "T11,V2222,S333333\",G\"44444444,W,P,D,R,N,C,&"; - value = property_copyAttributeValue(&prop, "T"); - testassert(0 == strcmp(value, "11")); - testassert(malloc_size(value) >= 1 + strlen(value)); - free(value); - value = property_copyAttributeValue(&prop, "V"); - testassert(0 == strcmp(value, "2222")); - testassert(malloc_size(value) >= 1 + strlen(value)); - free(value); - value = property_copyAttributeValue(&prop, "S"); - testassert(0 == strcmp(value, "333333\"")); - testassert(malloc_size(value) >= 1 + strlen(value)); - free(value); - value = property_copyAttributeValue(&prop, "G"); - testassert(0 == strcmp(value, "\"44444444")); - testassert(malloc_size(value) >= 1 + strlen(value)); - free(value); - value = property_copyAttributeValue(&prop, "W"); - testassert(0 == strcmp(value, "")); - testassert(malloc_size(value) >= 1 + strlen(value)); - free(value); - value = property_copyAttributeValue(&prop, "P"); - testassert(0 == strcmp(value, "")); - testassert(malloc_size(value) >= 1 + strlen(value)); - free(value); - value = property_copyAttributeValue(&prop, "D"); - testassert(0 == strcmp(value, "")); - testassert(malloc_size(value) >= 1 + strlen(value)); - free(value); - value = property_copyAttributeValue(&prop, "R"); - testassert(0 == strcmp(value, "")); - testassert(malloc_size(value) >= 1 + strlen(value)); - free(value); - value = property_copyAttributeValue(&prop, "N"); - testassert(0 == strcmp(value, "")); - testassert(malloc_size(value) >= 1 + strlen(value)); - free(value); - value = property_copyAttributeValue(&prop, "C"); - testassert(0 == strcmp(value, "")); - testassert(malloc_size(value) >= 1 + strlen(value)); - free(value); - value = property_copyAttributeValue(&prop, "&"); - testassert(0 == strcmp(value, "")); - testassert(malloc_size(value) >= 1 + strlen(value)); - free(value); - - // ATTRIBUTE LIST TO STRING (class_addProperty) - - BOOL ok; - objc_property_t prop2; - - // null name - ok = class_addProperty([TestRoot class], NULL, (objc_property_attribute_t *)1, 1); - testassert(!ok); - - // null description - ok = class_addProperty([TestRoot class], "test-null-desc", NULL, 0); - testassert(ok); - prop2 = class_getProperty([TestRoot class], "test-null-desc"); - testassert(prop2); - testassert(0 == strcmp(property_getAttributes(prop2), "")); - - // empty description - ok = class_addProperty([TestRoot class], "test-empty-desc", (objc_property_attribute_t*)1, 0); - testassert(ok); - prop2 = class_getProperty([TestRoot class], "test-empty-desc"); - testassert(prop2); - testassert(0 == strcmp(property_getAttributes(prop2), "")); - - // long and short names, with and without values - objc_property_attribute_t attrs2[] = { - { "!", NULL }, - { "?", "XX" }, - { "'", "" }, - { "?!?!", "YY" }, - { "''''", "" } - }; - ok = class_addProperty([TestRoot class], "test-unrecognized", attrs2, 5); - testassert(ok); - prop2 = class_getProperty([TestRoot class], "test-unrecognized"); - testassert(prop2); - testassert(0 == strcmp(property_getAttributes(prop2), "?XX,',\"?!?!\"YY,\"''''\"")); - - // all recognized attributes simultaneously, values with quotes - objc_property_attribute_t attrs3[] = { - { "&", "" }, - { "C", "" }, - { "N", "" }, - { "R", "" }, - { "D", "" }, - { "P", "" }, - { "W", "" }, - { "G", "\"44444444" }, - { "S", "333333\"" }, - { "V", "2222" }, - { "T", "11" }, - }; - ok = class_addProperty([TestRoot class], "test-recognized", attrs3, 11); - testassert(ok); - prop2 = class_getProperty([TestRoot class], "test-recognized"); - testassert(prop2); - testassert(0 == strcmp(property_getAttributes(prop2), - "&,C,N,R,D,P,W,G\"44444444,S333333\",V2222,T11")); - - // kitchen sink - objc_property_attribute_t attrs4[] = { - { "&", "" }, - { "C", "" }, - { "N", "" }, - { "R", "" }, - { "D", "" }, - { "P", "" }, - { "W", "" }, - { "!", NULL }, - { "G", "\"44444444" }, - { "S", "333333\"" }, - { "V", "2222" }, - { "T", "11" }, - { "?", "XX" }, - { "'", "" }, - { "?!?!", "YY" }, - { "''''", "" } - }; - ok = class_addProperty([TestRoot class], "test-sink", attrs4, 16); - testassert(ok); - prop2 = class_getProperty([TestRoot class], "test-sink"); - testassert(prop2); - testassert(0 == strcmp(property_getAttributes(prop2), - "&,C,N,R,D,P,W,G\"44444444,S333333\",V2222,T11," - "?XX,',\"?!?!\"YY,\"''''\"")); - - succeed(__FILE__); -} diff --git a/test/protocol.m b/test/protocol.m deleted file mode 100644 index c68bd33..0000000 --- a/test/protocol.m +++ /dev/null @@ -1,320 +0,0 @@ -// TEST_CFLAGS -framework Foundation -Wno-deprecated-declarations -// need Foundation to get NSObject compatibility additions for class Protocol -// because ARC calls [protocol retain] - -#include "test.h" -#include "testroot.i" -#include -#include -#include - -#if !__OBJC2__ -#include -#endif - -@protocol Proto1 -+(id)proto1ClassMethod; --(id)proto1InstanceMethod; -@end - -@protocol Proto2 -+(id)proto2ClassMethod; --(id)proto2InstanceMethod; -@end - -@protocol Proto3 -+(id)proto3ClassMethod; --(id)proto3InstanceMethod; -@end - -@protocol Proto4 -@property int i; -@end - -// Force some of Proto5's selectors out of address order rdar://10582325 -SEL fn(int x) { if (x) return @selector(m12:); else return @selector(m22:); } - -// This declaration order deliberately looks weird because it determines the -// selector address order on some architectures rdar://10582325 -@protocol Proto5 --(id)m11:(id)a; --(void)m12:(id)a; --(int)m13:(id)a; -+(void)m22:(TestRoot*)a; -+(int)m23:(TestRoot*)a; -+(TestRoot*)m21:(TestRoot*)a; -@optional --(id(^)(id))m31:(id(^)(id))a; --(void)m32:(id(^)(id))a; --(int)m33:(id(^)(id))a; -+(void)m42:(TestRoot*(^)(TestRoot*))a; -+(int)m43:(TestRoot*(^)(TestRoot*))a; -+(TestRoot*(^)(TestRoot*))m41:(TestRoot*(^)(TestRoot*))a; -@end - -@protocol Proto6 -@optional -+(TestRoot*(^)(TestRoot*))n41:(TestRoot*(^)(TestRoot*))a; -@end - -@protocol ProtoEmpty -@end - -#if __OBJC2__ -#define TEST_SWIFT 1 -#define SwiftV1MangledName "_TtP6Module15SwiftV1Protocol_" -#endif - -#if TEST_SWIFT -__attribute__((objc_runtime_name(SwiftV1MangledName))) -@protocol SwiftV1Protocol -@end -#endif - -@interface Super : TestRoot @end -@implementation Super -+(id)proto1ClassMethod { return self; } --(id)proto1InstanceMethod { return self; } -@end - -@interface SubNoProtocols : Super @end -@implementation SubNoProtocols @end - -@interface SuperNoProtocols : TestRoot @end -@implementation SuperNoProtocols -@end - -@interface SubProp : Super { int i; } @end -@implementation SubProp -@synthesize i; -@end - - -int main() -{ - Class cls; - Protocol * __unsafe_unretained *list; - Protocol *protocol, *empty; -#if !__OBJC2__ - struct objc_method_description *desc; -#endif - struct objc_method_description desc2; - objc_property_t *proplist; - unsigned int count; - - protocol = @protocol(Proto3); - empty = @protocol(ProtoEmpty); - testassert(protocol); - testassert(empty); - -#if !__OBJC2__ - testassert([protocol isKindOf:[Protocol class]]); - testassert([empty isKindOf:[Protocol class]]); - testassert(0 == strcmp([protocol name], "Proto3")); - testassert(0 == strcmp([empty name], "ProtoEmpty")); -#endif - testassert(0 == strcmp(protocol_getName(protocol), "Proto3")); - testassert(0 == strcmp(protocol_getName(empty), "ProtoEmpty")); - - testassert(class_conformsToProtocol([Super class], @protocol(Proto1))); - testassert(!class_conformsToProtocol([SubProp class], @protocol(Proto1))); - testassert(class_conformsToProtocol([SubProp class], @protocol(Proto4))); - testassert(!class_conformsToProtocol([SubProp class], @protocol(Proto3))); - testassert(!class_conformsToProtocol([Super class], @protocol(Proto3))); - - testassert(!protocol_conformsToProtocol(@protocol(Proto1), @protocol(Proto2))); - testassert(protocol_conformsToProtocol(@protocol(Proto3), @protocol(Proto2))); - testassert(!protocol_conformsToProtocol(@protocol(Proto2), @protocol(Proto3))); - -#if !__OBJC2__ - testassert([@protocol(Proto1) isEqual:@protocol(Proto1)]); - testassert(! [@protocol(Proto1) isEqual:@protocol(Proto2)]); -#endif - testassert(protocol_isEqual(@protocol(Proto1), @protocol(Proto1))); - testassert(! protocol_isEqual(@protocol(Proto1), @protocol(Proto2))); - -#if !__OBJC2__ - desc = [protocol descriptionForInstanceMethod:@selector(proto3InstanceMethod)]; - testassert(desc); - testassert(desc->name == @selector(proto3InstanceMethod)); - desc = [protocol descriptionForClassMethod:@selector(proto3ClassMethod)]; - testassert(desc); - testassert(desc->name == @selector(proto3ClassMethod)); - desc = [protocol descriptionForClassMethod:@selector(proto2ClassMethod)]; - testassert(desc); - testassert(desc->name == @selector(proto2ClassMethod)); - - desc = [protocol descriptionForInstanceMethod:@selector(proto3ClassMethod)]; - testassert(!desc); - desc = [protocol descriptionForClassMethod:@selector(proto3InstanceMethod)]; - testassert(!desc); - desc = [empty descriptionForInstanceMethod:@selector(proto3ClassMethod)]; - testassert(!desc); - desc = [empty descriptionForClassMethod:@selector(proto3InstanceMethod)]; - testassert(!desc); -#endif - desc2 = protocol_getMethodDescription(protocol, @selector(proto3InstanceMethod), YES, YES); - testassert(desc2.name && desc2.types); - testassert(desc2.name == @selector(proto3InstanceMethod)); - desc2 = protocol_getMethodDescription(protocol, @selector(proto3ClassMethod), YES, NO); - testassert(desc2.name && desc2.types); - testassert(desc2.name == @selector(proto3ClassMethod)); - desc2 = protocol_getMethodDescription(protocol, @selector(proto2ClassMethod), YES, NO); - testassert(desc2.name && desc2.types); - testassert(desc2.name == @selector(proto2ClassMethod)); - - desc2 = protocol_getMethodDescription(protocol, @selector(proto3ClassMethod), YES, YES); - testassert(!desc2.name && !desc2.types); - desc2 = protocol_getMethodDescription(protocol, @selector(proto3InstanceMethod), YES, NO); - testassert(!desc2.name && !desc2.types); - desc2 = protocol_getMethodDescription(empty, @selector(proto3ClassMethod), YES, YES); - testassert(!desc2.name && !desc2.types); - desc2 = protocol_getMethodDescription(empty, @selector(proto3InstanceMethod), YES, NO); - testassert(!desc2.name && !desc2.types); - - count = 100; - list = protocol_copyProtocolList(@protocol(Proto2), &count); - testassert(!list); - testassert(count == 0); - count = 100; - list = protocol_copyProtocolList(@protocol(Proto3), &count); - testassert(list); - testassert(count == 1); - testassert(protocol_isEqual(list[0], @protocol(Proto2))); - testassert(!list[1]); - free(list); - - count = 100; - cls = objc_getClass("Super"); - testassert(cls); - list = class_copyProtocolList(cls, &count); - testassert(list); - testassert(list[count] == NULL); - testassert(count == 1); - testassert(0 == strcmp(protocol_getName(list[0]), "Proto1")); - free(list); - - count = 100; - cls = objc_getClass("SuperNoProtocols"); - testassert(cls); - list = class_copyProtocolList(cls, &count); - testassert(!list); - testassert(count == 0); - - count = 100; - cls = objc_getClass("SubNoProtocols"); - testassert(cls); - list = class_copyProtocolList(cls, &count); - testassert(!list); - testassert(count == 0); - - - cls = objc_getClass("SuperNoProtocols"); - testassert(cls); - list = class_copyProtocolList(cls, NULL); - testassert(!list); - - cls = objc_getClass("Super"); - testassert(cls); - list = class_copyProtocolList(cls, NULL); - testassert(list); - free(list); - - count = 100; - list = class_copyProtocolList(NULL, &count); - testassert(!list); - testassert(count == 0); - - - // Check property added by protocol - cls = objc_getClass("SubProp"); - testassert(cls); - - count = 100; - list = class_copyProtocolList(cls, &count); - testassert(list); - testassert(count == 1); - testassert(0 == strcmp(protocol_getName(list[0]), "Proto4")); - testassert(list[1] == NULL); - free(list); - - count = 100; - proplist = class_copyPropertyList(cls, &count); - testassert(proplist); - testassert(count == 1); - testassert(0 == strcmp(property_getName(proplist[0]), "i")); - testassert(proplist[1] == NULL); - free(proplist); - - // Check extended type encodings - testassert(_protocol_getMethodTypeEncoding(@protocol(Proto5), @selector(DoesNotExist), true, true) == NULL); - testassert(_protocol_getMethodTypeEncoding(NULL, @selector(m11), true, true) == NULL); - testassert(_protocol_getMethodTypeEncoding(@protocol(Proto5), @selector(m11), true, false) == NULL); - testassert(_protocol_getMethodTypeEncoding(@protocol(Proto5), @selector(m11), false, false) == NULL); - testassert(_protocol_getMethodTypeEncoding(@protocol(Proto5), @selector(m11), false, true) == NULL); - testassert(_protocol_getMethodTypeEncoding(@protocol(Proto5), @selector(m21), true, true) == NULL); -#if __LP64__ - const char *types11 = "@24@0:8@\"\"16"; - const char *types12 = "v24@0:8@\"\"16"; - const char *types13 = "i24@0:8@\"\"16"; - const char *types21 = "@\"TestRoot\"24@0:8@\"TestRoot\"16"; - const char *types22 = "v24@0:8@\"TestRoot\"16"; - const char *types23 = "i24@0:8@\"TestRoot\"16"; - const char *types31 = "@?<@@?@>24@0:8@?<@\"\"@?@\"\">16"; - const char *types32 = "v24@0:8@?<@\"\"@?@\"\">16"; - const char *types33 = "i24@0:8@?<@\"\"@?@\"\">16"; - const char *types41 = "@?<@\"TestRoot\"@?@\"TestRoot\">24@0:8@?<@\"TestRoot\"@?@\"TestRoot\">16"; - const char *types42 = "v24@0:8@?<@\"TestRoot\"@?@\"TestRoot\">16"; - const char *types43 = "i24@0:8@?<@\"TestRoot\"@?@\"TestRoot\">16"; -#else - const char *types11 = "@12@0:4@\"\"8"; - const char *types12 = "v12@0:4@\"\"8"; - const char *types13 = "i12@0:4@\"\"8"; - const char *types21 = "@\"TestRoot\"12@0:4@\"TestRoot\"8"; - const char *types22 = "v12@0:4@\"TestRoot\"8"; - const char *types23 = "i12@0:4@\"TestRoot\"8"; - const char *types31 = "@?<@@?@>12@0:4@?<@\"\"@?@\"\">8"; - const char *types32 = "v12@0:4@?<@\"\"@?@\"\">8"; - const char *types33 = "i12@0:4@?<@\"\"@?@\"\">8"; - const char *types41 = "@?<@\"TestRoot\"@?@\"TestRoot\">12@0:4@?<@\"TestRoot\"@?@\"TestRoot\">8"; - const char *types42 = "v12@0:4@?<@\"TestRoot\"@?@\"TestRoot\">8"; - const char *types43 = "i12@0:4@?<@\"TestRoot\"@?@\"TestRoot\">8"; -#endif - - // Make sure some of Proto5's selectors are out of order rdar://10582325 - // These comparisons deliberately look weird because they determine the - // selector order on some architectures. - testassert(sel_registerName("m11:") > sel_registerName("m12:") || - sel_registerName("m21:") > sel_registerName("m22:") || - sel_registerName("m32:") < sel_registerName("m31:") || - sel_registerName("m42:") < sel_registerName("m41:") ); - - if (!_protocol_getMethodTypeEncoding(@protocol(Proto5), @selector(m11:), true, true)) { - fail("rdar://10492418 extended type encodings not present (is compiler old?)"); - } else { - testassert(0 == strcmp(_protocol_getMethodTypeEncoding(@protocol(Proto5), @selector(m11:), true, true), types11)); - testassert(0 == strcmp(_protocol_getMethodTypeEncoding(@protocol(Proto5), @selector(m12:), true, true), types12)); - testassert(0 == strcmp(_protocol_getMethodTypeEncoding(@protocol(Proto5), @selector(m13:), true, true), types13)); - testassert(0 == strcmp(_protocol_getMethodTypeEncoding(@protocol(Proto5), @selector(m21:), true, false), types21)); - testassert(0 == strcmp(_protocol_getMethodTypeEncoding(@protocol(Proto5), @selector(m22:), true, false), types22)); - testassert(0 == strcmp(_protocol_getMethodTypeEncoding(@protocol(Proto5), @selector(m23:), true, false), types23)); - testassert(0 == strcmp(_protocol_getMethodTypeEncoding(@protocol(Proto5), @selector(m31:), false, true), types31)); - testassert(0 == strcmp(_protocol_getMethodTypeEncoding(@protocol(Proto5), @selector(m32:), false, true), types32)); - testassert(0 == strcmp(_protocol_getMethodTypeEncoding(@protocol(Proto5), @selector(m33:), false, true), types33)); - testassert(0 == strcmp(_protocol_getMethodTypeEncoding(@protocol(Proto5), @selector(m41:), false, false), types41)); - testassert(0 == strcmp(_protocol_getMethodTypeEncoding(@protocol(Proto5), @selector(m42:), false, false), types42)); - testassert(0 == strcmp(_protocol_getMethodTypeEncoding(@protocol(Proto5), @selector(m43:), false, false), types43)); - - testassert(0 == strcmp(_protocol_getMethodTypeEncoding(@protocol(Proto6), @selector(n41:), false, false), types41)); - testassert(0 == strcmp(_protocol_getMethodTypeEncoding(@protocol(Proto6), @selector(m41:), false, false), types41)); - } - -#if TEST_SWIFT - testassert(@protocol(SwiftV1Protocol) == objc_getProtocol("Module.SwiftV1Protocol")); - testassert(@protocol(SwiftV1Protocol) == objc_getProtocol(SwiftV1MangledName)); - testassert(0 == strcmp(protocol_getName(@protocol(SwiftV1Protocol)), "Module.SwiftV1Protocol")); - testassert(!objc_getProtocol("SwiftV1Protocol")); -#endif - - succeed(__FILE__); -} diff --git a/test/protocol_copyMethodList.m b/test/protocol_copyMethodList.m deleted file mode 100644 index 53bf445..0000000 --- a/test/protocol_copyMethodList.m +++ /dev/null @@ -1,146 +0,0 @@ -// TEST_CFLAGS -framework Foundation -// need Foundation to get NSObject compatibility additions for class Protocol -// because ARC calls [protocol retain] - - -#include "test.h" -#include -#include - -@protocol SuperMethods -+(void)SuperMethodClass; -+(void)SuperMethodClass2; --(void)SuperMethodInstance; --(void)SuperMethodInstance2; -@end - -@protocol SubMethods -+(void)SubMethodClass; -+(void)SubMethodClass2; --(void)SubMethodInstance; --(void)SubMethodInstance2; -@end - -@protocol SuperOptionalMethods -@optional -+(void)SuperOptMethodClass; -+(void)SuperOptMethodClass2; --(void)SuperOptMethodInstance; --(void)SuperOptMethodInstance2; -@end - -@protocol SubOptionalMethods -@optional -+(void)SubOptMethodClass; -+(void)SubOptMethodClass2; --(void)SubOptMethodInstance; --(void)SubOptMethodInstance2; -@end - -@protocol NoMethods @end - -static int isNamed(struct objc_method_description m, const char *name) -{ - return (m.name == sel_registerName(name)); -} - -int main() -{ - struct objc_method_description *methods; - unsigned int count; - Protocol *proto; - - proto = @protocol(SubMethods); - testassert(proto); - - // Check required methods - count = 999; - methods = protocol_copyMethodDescriptionList(proto, YES, YES, &count); - testassert(methods); - testassert(count == 2); - testassert((isNamed(methods[0], "SubMethodInstance") && - isNamed(methods[1], "SubMethodInstance2")) - || - (isNamed(methods[1], "SubMethodInstance") && - isNamed(methods[0], "SubMethodInstance2"))); - free(methods); - - count = 999; - methods = protocol_copyMethodDescriptionList(proto, YES, NO, &count); - testassert(methods); - testassert(count == 2); - testassert((isNamed(methods[0], "SubMethodClass") && - isNamed(methods[1], "SubMethodClass2")) - || - (isNamed(methods[1], "SubMethodClass") && - isNamed(methods[0], "SubMethodClass2"))); - free(methods); - - // Check lack of optional methods - count = 999; - methods = protocol_copyMethodDescriptionList(proto, NO, YES, &count); - testassert(!methods); - testassert(count == 0); - count = 999; - methods = protocol_copyMethodDescriptionList(proto, NO, NO, &count); - testassert(!methods); - testassert(count == 0); - - - proto = @protocol(SubOptionalMethods); - testassert(proto); - - // Check optional methods - count = 999; - methods = protocol_copyMethodDescriptionList(proto, NO, YES, &count); - testassert(methods); - testassert(count == 2); - testassert((isNamed(methods[0], "SubOptMethodInstance") && - isNamed(methods[1], "SubOptMethodInstance2")) - || - (isNamed(methods[1], "SubOptMethodInstance") && - isNamed(methods[0], "SubOptMethodInstance2"))); - free(methods); - - count = 999; - methods = protocol_copyMethodDescriptionList(proto, NO, NO, &count); - testassert(methods); - testassert(count == 2); - testassert((isNamed(methods[0], "SubOptMethodClass") && - isNamed(methods[1], "SubOptMethodClass2")) - || - (isNamed(methods[1], "SubOptMethodClass") && - isNamed(methods[0], "SubOptMethodClass2"))); - free(methods); - - // Check lack of required methods - count = 999; - methods = protocol_copyMethodDescriptionList(proto, YES, YES, &count); - testassert(!methods); - testassert(count == 0); - count = 999; - methods = protocol_copyMethodDescriptionList(proto, YES, NO, &count); - testassert(!methods); - testassert(count == 0); - - - // Check NULL protocol parameter - count = 999; - methods = protocol_copyMethodDescriptionList(NULL, YES, YES, &count); - testassert(!methods); - testassert(count == 0); - count = 999; - methods = protocol_copyMethodDescriptionList(NULL, YES, NO, &count); - testassert(!methods); - testassert(count == 0); - count = 999; - methods = protocol_copyMethodDescriptionList(NULL, NO, YES, &count); - testassert(!methods); - testassert(count == 0); - count = 999; - methods = protocol_copyMethodDescriptionList(NULL, NO, NO, &count); - testassert(!methods); - testassert(count == 0); - - succeed(__FILE__); -} diff --git a/test/protocol_copyPropertyList.m b/test/protocol_copyPropertyList.m deleted file mode 100644 index 406a0bd..0000000 --- a/test/protocol_copyPropertyList.m +++ /dev/null @@ -1,109 +0,0 @@ -// TEST_CFLAGS -framework Foundation -// need Foundation to get NSObject compatibility additions for class Protocol -// because ARC calls [protocol retain] - -#include "test.h" -#include -#include -#include - -@protocol SuperProps -@property int prop1; -@property int prop2; -@end - -@protocol SubProps -@property int prop3; -@property int prop4; -@end - - -@protocol FourProps -@property int prop1; -@property int prop2; -@property int prop3; -@property int prop4; -@end - -@protocol NoProps @end - -static int isNamed(objc_property_t p, const char *name) -{ - return (0 == strcmp(name, property_getName(p))); -} - -int main() -{ - objc_property_t *props; - unsigned int count; - Protocol *proto; - - proto = @protocol(SubProps); - testassert(proto); - - count = 100; - props = protocol_copyPropertyList(proto, &count); - testassert(props); - testassert(count == 2); - testassert((isNamed(props[0], "prop4") && isNamed(props[1], "prop3")) || - (isNamed(props[0], "prop3") && isNamed(props[1], "prop4"))); - // props[] should be null-terminated - testassert(props[2] == NULL); - free(props); - - proto = @protocol(SuperProps); - testassert(proto); - - count = 100; - props = protocol_copyPropertyList(proto, &count); - testassert(props); - testassert(count == 2); - testassert((isNamed(props[0], "prop1") && isNamed(props[1], "prop2")) || - (isNamed(props[0], "prop2") && isNamed(props[1], "prop1"))); - // props[] should be null-terminated - testassert(props[2] == NULL); - free(props); - - // Check null-termination - this property list block would be 16 bytes - // if it weren't for the terminator - proto = @protocol(FourProps); - testassert(proto); - - count = 100; - props = protocol_copyPropertyList(proto, &count); - testassert(props); - testassert(count == 4); - testassert(malloc_size(props) >= 5 * sizeof(objc_property_t)); - testassert(props[3] != NULL); - testassert(props[4] == NULL); - free(props); - - // Check NULL count parameter - props = protocol_copyPropertyList(proto, NULL); - testassert(props); - testassert(props[4] == NULL); - testassert(props[3] != NULL); - free(props); - - // Check NULL protocol parameter - count = 100; - props = protocol_copyPropertyList(NULL, &count); - testassert(!props); - testassert(count == 0); - - // Check NULL protocol and count - props = protocol_copyPropertyList(NULL, NULL); - testassert(!props); - - // Check protocol with no properties - proto = @protocol(NoProps); - testassert(proto); - - count = 100; - props = protocol_copyPropertyList(proto, &count); - testassert(!props); - testassert(count == 0); - - succeed(__FILE__); - return 0; -} diff --git a/test/protocol_cw.m b/test/protocol_cw.m deleted file mode 100644 index a82f991..0000000 --- a/test/protocol_cw.m +++ /dev/null @@ -1,40 +0,0 @@ -// TEST_CFLAGS -Wno-deprecated-declarations - -#include "test.h" - -#if __OBJC2__ - -int main() -{ - succeed(__FILE__); -} - -#else - -// rdar://4951638 - -#include -#include - -char Protocol_name[] __attribute__((section("__OBJC,__class_names"))) = "Protocol"; - -struct st { - void *isa; - const char *protocol_name; - void *protocol_list; - void *instance_methods; - void *class_methods; -}; - -struct st Foo_protocol __attribute__((section("__OBJC,__protocol"))) = { Protocol_name, "Foo", 0, 0, 0 }; - -int main() -{ - Protocol *foo = objc_getProtocol("Foo"); - - testassert(foo == (Protocol *)&Foo_protocol); - testassert(0 == strcmp("Foo", [foo name])); - succeed(__FILE__); -} - -#endif diff --git a/test/rawisa.m b/test/rawisa.m deleted file mode 100644 index 3d36ed5..0000000 --- a/test/rawisa.m +++ /dev/null @@ -1,27 +0,0 @@ -/* -TEST_CFLAGS -Xlinker -sectcreate -Xlinker __DATA -Xlinker __objc_rawisa -Xlinker /dev/null -TEST_ENV OBJC_PRINT_RAW_ISA=YES - -TEST_RUN_OUTPUT -objc\[\d+\]: RAW ISA: disabling non-pointer isa because the app has a __DATA,__objc_rawisa section -(.* RAW ISA: .*\n)* -OK: rawisa.m -OR -(.* RAW ISA: .*\n)* -no __DATA,__rawisa support -OK: rawisa.m -END -*/ - -#include "test.h" - -int main() -{ - fprintf(stderr, "\n"); -#if ! (SUPPORT_NONPOINTER_ISA && TARGET_OS_MAC && !TARGET_OS_IPHONE) - // only 64-bit Mac supports this - fprintf(stderr, "no __DATA,__rawisa support\n"); -#endif - succeed(__FILE__); -} - diff --git a/test/readClassPair.m b/test/readClassPair.m deleted file mode 100644 index 5a6b9e4..0000000 --- a/test/readClassPair.m +++ /dev/null @@ -1,77 +0,0 @@ -// TEST_CONFIG - -#include "test.h" - -#if !__OBJC2__ - -int main() -{ - succeed(__FILE__); -} - -#else - -#include - -// Reuse evil-class-def.m as a non-evil class definition. - -#define EVIL_SUPER 0 -#define EVIL_SUPER_META 0 -#define EVIL_SUB 0 -#define EVIL_SUB_META 0 - -#define OMIT_SUPER 1 -#define OMIT_NL_SUPER 1 -#define OMIT_SUB 1 -#define OMIT_NL_SUB 1 - -#include "evil-class-def.m" - -int main() -{ - // This definition is ABI and is never allowed to change. - testassert(OBJC_MAX_CLASS_SIZE == 32*sizeof(void*)); - - struct objc_image_info ii = { 0, 0 }; - - // Read a root class. - testassert(!objc_getClass("Super")); - - extern intptr_t OBJC_CLASS_$_Super[OBJC_MAX_CLASS_SIZE/sizeof(void*)]; - Class Super = objc_readClassPair((__bridge Class)(void*)&OBJC_CLASS_$_Super, &ii); - testassert(Super); - - testassert(objc_getClass("Super") == Super); - testassert(0 == strcmp(class_getName(Super), "Super")); - testassert(class_getSuperclass(Super) == nil); - testassert(class_getClassMethod(Super, @selector(load))); - testassert(class_getInstanceMethod(Super, @selector(load))); - testassert(class_getInstanceVariable(Super, "super_ivar")); - testassert(class_getInstanceSize(Super) == sizeof(void*)); - [Super load]; - - // Read a non-root class. - testassert(!objc_getClass("Sub")); - - extern intptr_t OBJC_CLASS_$_Sub[OBJC_MAX_CLASS_SIZE/sizeof(void*)]; - intptr_t Sub2_buf[OBJC_MAX_CLASS_SIZE/sizeof(void*)]; - memcpy(Sub2_buf, &OBJC_CLASS_$_Sub, sizeof(Sub2_buf)); - Class Sub = objc_readClassPair((__bridge Class)(void*)&OBJC_CLASS_$_Sub, &ii); - testassert(Sub); - - testassert(0 == strcmp(class_getName(Sub), "Sub")); - testassert(objc_getClass("Sub") == Sub); - testassert(class_getSuperclass(Sub) == Super); - testassert(class_getClassMethod(Sub, @selector(load))); - testassert(class_getInstanceMethod(Sub, @selector(load))); - testassert(class_getInstanceVariable(Sub, "sub_ivar")); - testassert(class_getInstanceSize(Sub) == 2*sizeof(void*)); - [Sub load]; - - // Reading a class whose name already exists fails. - testassert(! objc_readClassPair((__bridge Class)(void*)Sub2_buf, &ii)); - - succeed(__FILE__); -} - -#endif diff --git a/test/resolve.m b/test/resolve.m deleted file mode 100644 index ba6fdd6..0000000 --- a/test/resolve.m +++ /dev/null @@ -1,298 +0,0 @@ -/* resolve.m - * Test +resolveClassMethod: and +resolveInstanceMethod: - */ - -// TEST_CFLAGS -Wno-deprecated-declarations - -#include "test.h" -#include "testroot.i" -#include -#include -#include - -#if __has_feature(objc_arc) - -int main() -{ - testwarn("rdar://11368528 confused by Foundation"); - succeed(__FILE__); -} - -#else - -static int state = 0; - -@interface Super : TestRoot @end -@interface Sub : Super @end - - -@implementation Super -+(void)initialize { - if (self == [Super class]) { - testassert(state == 1); - state = 2; - } -} -@end - -static id forward_handler(id self, SEL sel) -{ - if (class_isMetaClass(object_getClass(self))) { - // self is a class object - if (sel == @selector(missingClassMethod)) { - testassert(state == 21 || state == 25 || state == 80); - if (state == 21) state = 22; - if (state == 25) state = 26; - if (state == 80) state = 81;; - return nil; - } else if (sel == @selector(lyingClassMethod)) { - testassert(state == 31 || state == 35); - if (state == 31) state = 32; - if (state == 35) state = 36; - return nil; - } - fail("+forward:: shouldn't be called with sel %s", sel_getName(sel)); - return nil; - } - else { - // self is not a class object - if (sel == @selector(missingInstanceMethod)) { - testassert(state == 61 || state == 65); - if (state == 61) state = 62; - if (state == 65) state = 66; - return nil; - } else if (sel == @selector(lyingInstanceMethod)) { - testassert(state == 71 || state == 75); - if (state == 71) state = 72; - if (state == 75) state = 76; - return nil; - } - fail("-forward:: shouldn't be called with sel %s", sel_getName(sel)); - return nil; - } -} - - -static id classMethod_c(id __unused self, SEL __unused sel) -{ - testassert(state == 4 || state == 10); - if (state == 4) state = 5; - if (state == 10) state = 11; - return [Super class]; -} - -static id instanceMethod_c(id __unused self, SEL __unused sel) -{ - testassert(state == 41 || state == 50); - if (state == 41) state = 42; - if (state == 50) state = 51; - return [Sub class]; -} - - -@implementation Sub - -+(void)method2 { } -+(void)method3 { } -+(void)method4 { } -+(void)method5 { } - -+(void)initialize { - if (self == [Sub class]) { - testassert(state == 2); - state = 3; - } -} - -+(BOOL)resolveClassMethod:(SEL)sel -{ - if (sel == @selector(classMethod)) { - testassert(state == 3); - state = 4; - class_addMethod(object_getClass(self), sel, (IMP)&classMethod_c, ""); - return YES; - } else if (sel == @selector(missingClassMethod)) { - testassert(state == 20); - state = 21; - return NO; - } else if (sel == @selector(lyingClassMethod)) { - testassert(state == 30); - state = 31; - return YES; // lie - } else { - fail("+resolveClassMethod: called incorrectly (sel %s)", - sel_getName(sel)); - return NO; - } -} - -+(BOOL)resolveInstanceMethod:(SEL)sel -{ - if (sel == @selector(instanceMethod)) { - testassert(state == 40); - state = 41; - class_addMethod(self, sel, (IMP)instanceMethod_c, ""); - return YES; - } else if (sel == @selector(missingInstanceMethod)) { - testassert(state == 60); - state = 61; - return NO; - } else if (sel == @selector(lyingInstanceMethod)) { - testassert(state == 70); - state = 71; - return YES; // lie - } else { - fail("+resolveInstanceMethod: called incorrectly (sel %s)", - sel_getName(sel)); - return NO; - } -} - -@end - -@interface Super (MissingMethods) -+(id)missingClassMethod; -@end - -@interface Sub (ResolvedMethods) -+(id)classMethod; --(id)instanceMethod; -+(id)missingClassMethod; --(id)missingInstanceMethod; -+(id)lyingClassMethod; --(id)lyingInstanceMethod; -@end - - -int main() -{ - Sub *s; - id ret; - - objc_setForwardHandler((void*)&forward_handler, NULL); - - // Be ready for ARC to retain the class object and call +initialize early - state = 1; - - Class dup = objc_duplicateClass(objc_getClass("Sub"), "Sub_copy", 0); - - // Resolve a class method - // +initialize should fire first (if it hasn't already) - ret = [Sub classMethod]; - testassert(state == 5); - testassert(ret == [Super class]); - - // Call it again, cached - // Resolver shouldn't be called again. - state = 10; - ret = [Sub classMethod]; - testassert(state == 11); - testassert(ret == [Super class]); - - _objc_flush_caches(object_getClass([Sub class])); - - // Call a method that won't get resolved - state = 20; - ret = [Sub missingClassMethod]; - testassert(state == 22); - testassert(ret == nil); - - // Call it again, cached - // Resolver shouldn't be called again. - state = 25; - ret = [Sub missingClassMethod]; - testassert(state == 26); - testassert(ret == nil); - - _objc_flush_caches(object_getClass([Sub class])); - - // Call a method that won't get resolved but the resolver lies about it - state = 30; - ret = [Sub lyingClassMethod]; - testassert(state == 32); - testassert(ret == nil); - - // Call it again, cached - // Resolver shouldn't be called again. - state = 35; - ret = [Sub lyingClassMethod]; - testassert(state == 36); - testassert(ret == nil); - - _objc_flush_caches(object_getClass([Sub class])); - - - // Resolve an instance method - s = [Sub new]; - state = 40; - ret = [s instanceMethod]; - testassert(state == 42); - testassert(ret == [Sub class]); - - // Call it again, cached - // Resolver shouldn't be called again. - state = 50; - ret = [s instanceMethod]; - testassert(state == 51); - testassert(ret == [Sub class]); - - _objc_flush_caches([Sub class]); - - // Call a method that won't get resolved - state = 60; - ret = [s missingInstanceMethod]; - testassert(state == 62); - testassert(ret == nil); - - // Call it again, cached - // Resolver shouldn't be called again. - state = 65; - ret = [s missingInstanceMethod]; - testassert(state == 66); - testassert(ret == nil); - - _objc_flush_caches([Sub class]); - - // Call a method that won't get resolved but the resolver lies about it - state = 70; - ret = [s lyingInstanceMethod]; - testassert(state == 72); - testassert(ret == nil); - - // Call it again, cached - // Resolver shouldn't be called again. - state = 75; - ret = [s lyingInstanceMethod]; - testassert(state == 76); - testassert(ret == nil); - - _objc_flush_caches([Sub class]); - - // Call a missing method on a class that doesn't support resolving - state = 80; - ret = [Super missingClassMethod]; - testassert(state == 81); - testassert(ret == nil); - RELEASE_VAR(s); - - // Resolve an instance method on a class duplicated before resolving - s = [dup new]; - state = 40; - ret = [s instanceMethod]; - testassert(state == 42); - testassert(ret == [Sub class]); - - // Call it again, cached - // Resolver shouldn't be called again. - state = 50; - ret = [s instanceMethod]; - testassert(state == 51); - testassert(ret == [Sub class]); - RELEASE_VAR(s); - - succeed(__FILE__); - return 0; -} - -#endif - diff --git a/test/rr-autorelease-fast.m b/test/rr-autorelease-fast.m deleted file mode 100644 index f44b88d..0000000 --- a/test/rr-autorelease-fast.m +++ /dev/null @@ -1,317 +0,0 @@ -// TEST_CONFIG CC=clang MEM=mrc -// TEST_CFLAGS -Os - -#include "test.h" -#include "testroot.i" - -#if __i386__ - -int main() -{ - // no optimization on i386 (neither Mac nor Simulator) - succeed(__FILE__); -} - -#else - -#include -#include -#include - -@interface TestObject : TestRoot @end -@implementation TestObject @end - - -#ifdef __arm__ -# define MAGIC asm volatile("mov r7, r7") -# define NOT_MAGIC asm volatile("mov r6, r6") -#elif __arm64__ -# define MAGIC asm volatile("mov x29, x29") -# define NOT_MAGIC asm volatile("mov x28, x28") -#elif __x86_64__ -# define MAGIC asm volatile("") -# define NOT_MAGIC asm volatile("nop") -#else -# error unknown architecture -#endif - - -int -main() -{ - TestObject *tmp, *obj; - -#ifdef __x86_64__ - // need to get DYLD to resolve the stubs on x86 - PUSH_POOL { - TestObject *warm_up = [[TestObject alloc] init]; - testassert(warm_up); - warm_up = objc_retainAutoreleasedReturnValue(warm_up); - warm_up = objc_unsafeClaimAutoreleasedReturnValue(warm_up); - [warm_up release]; - warm_up = nil; - } POP_POOL; -#endif - - testprintf(" Successful +1 -> +1 handshake\n"); - - PUSH_POOL { - obj = [[TestObject alloc] init]; - testassert(obj); - - TestRootRetain = 0; - TestRootRelease = 0; - TestRootAutorelease = 0; - TestRootDealloc = 0; - - tmp = objc_autoreleaseReturnValue(obj); - MAGIC; - tmp = objc_retainAutoreleasedReturnValue(tmp); - - testassert(TestRootDealloc == 0); - testassert(TestRootRetain == 0); - testassert(TestRootRelease == 0); - testassert(TestRootAutorelease == 0); - - [tmp release]; - testassert(TestRootDealloc == 1); - testassert(TestRootRetain == 0); - testassert(TestRootRelease == 1); - testassert(TestRootAutorelease == 0); - - } POP_POOL; - - testprintf("Unsuccessful +1 -> +1 handshake\n"); - - PUSH_POOL { - obj = [[TestObject alloc] init]; - testassert(obj); - - TestRootRetain = 0; - TestRootRelease = 0; - TestRootAutorelease = 0; - TestRootDealloc = 0; - - tmp = objc_autoreleaseReturnValue(obj); - NOT_MAGIC; - tmp = objc_retainAutoreleasedReturnValue(tmp); - - testassert(TestRootDealloc == 0); - testassert(TestRootRetain == 1); - testassert(TestRootRelease == 0); - testassert(TestRootAutorelease == 1); - - [tmp release]; - testassert(TestRootDealloc == 0); - testassert(TestRootRetain == 1); - testassert(TestRootRelease == 1); - testassert(TestRootAutorelease == 1); - - } POP_POOL; - testassert(TestRootDealloc == 1); - testassert(TestRootRetain == 1); - testassert(TestRootRelease == 2); - testassert(TestRootAutorelease == 1); - - - testprintf(" Successful +0 -> +1 handshake\n"); - - PUSH_POOL { - obj = [[TestObject alloc] init]; - testassert(obj); - - TestRootRetain = 0; - TestRootRelease = 0; - TestRootAutorelease = 0; - TestRootDealloc = 0; - - tmp = objc_retainAutoreleaseReturnValue(obj); - MAGIC; - tmp = objc_retainAutoreleasedReturnValue(tmp); - - testassert(TestRootDealloc == 0); - testassert(TestRootRetain == 1); - testassert(TestRootRelease == 0); - testassert(TestRootAutorelease == 0); - - [tmp release]; - testassert(TestRootDealloc == 0); - testassert(TestRootRetain == 1); - testassert(TestRootRelease == 1); - testassert(TestRootAutorelease == 0); - - [tmp release]; - testassert(TestRootDealloc == 1); - testassert(TestRootRetain == 1); - testassert(TestRootRelease == 2); - testassert(TestRootAutorelease == 0); - - } POP_POOL; - - testprintf("Unsuccessful +0 -> +1 handshake\n"); - - PUSH_POOL { - obj = [[TestObject alloc] init]; - testassert(obj); - - TestRootRetain = 0; - TestRootRelease = 0; - TestRootAutorelease = 0; - TestRootDealloc = 0; - - tmp = objc_retainAutoreleaseReturnValue(obj); - NOT_MAGIC; - tmp = objc_retainAutoreleasedReturnValue(tmp); - - testassert(TestRootDealloc == 0); - testassert(TestRootRetain == 2); - testassert(TestRootRelease == 0); - testassert(TestRootAutorelease == 1); - - [tmp release]; - testassert(TestRootDealloc == 0); - testassert(TestRootRetain == 2); - testassert(TestRootRelease == 1); - testassert(TestRootAutorelease == 1); - - [tmp release]; - testassert(TestRootDealloc == 0); - testassert(TestRootRetain == 2); - testassert(TestRootRelease == 2); - testassert(TestRootAutorelease == 1); - - } POP_POOL; - testassert(TestRootDealloc == 1); - testassert(TestRootRetain == 2); - testassert(TestRootRelease == 3); - testassert(TestRootAutorelease == 1); - - - testprintf(" Successful +1 -> +0 handshake\n"); - - PUSH_POOL { - obj = [[[TestObject alloc] init] retain]; - testassert(obj); - - TestRootRetain = 0; - TestRootRelease = 0; - TestRootAutorelease = 0; - TestRootDealloc = 0; - - tmp = objc_autoreleaseReturnValue(obj); - MAGIC; - tmp = objc_unsafeClaimAutoreleasedReturnValue(tmp); - - testassert(TestRootDealloc == 0); - testassert(TestRootRetain == 0); - testassert(TestRootRelease == 1); - testassert(TestRootAutorelease == 0); - - [tmp release]; - testassert(TestRootDealloc == 1); - testassert(TestRootRetain == 0); - testassert(TestRootRelease == 2); - testassert(TestRootAutorelease == 0); - - } POP_POOL; - - testprintf("Unsuccessful +1 -> +0 handshake\n"); - - PUSH_POOL { - obj = [[[TestObject alloc] init] retain]; - testassert(obj); - - TestRootRetain = 0; - TestRootRelease = 0; - TestRootAutorelease = 0; - TestRootDealloc = 0; - - tmp = objc_autoreleaseReturnValue(obj); - NOT_MAGIC; - tmp = objc_unsafeClaimAutoreleasedReturnValue(tmp); - - testassert(TestRootDealloc == 0); - testassert(TestRootRetain == 0); - testassert(TestRootRelease == 0); - testassert(TestRootAutorelease == 1); - - [tmp release]; - testassert(TestRootDealloc == 0); - testassert(TestRootRetain == 0); - testassert(TestRootRelease == 1); - testassert(TestRootAutorelease == 1); - - } POP_POOL; - testassert(TestRootDealloc == 1); - testassert(TestRootRetain == 0); - testassert(TestRootRelease == 2); - testassert(TestRootAutorelease == 1); - - - testprintf(" Successful +0 -> +0 handshake\n"); - - PUSH_POOL { - obj = [[TestObject alloc] init]; - testassert(obj); - - TestRootRetain = 0; - TestRootRelease = 0; - TestRootAutorelease = 0; - TestRootDealloc = 0; - - tmp = objc_retainAutoreleaseReturnValue(obj); - MAGIC; - tmp = objc_unsafeClaimAutoreleasedReturnValue(tmp); - - testassert(TestRootDealloc == 0); - testassert(TestRootRetain == 0); - testassert(TestRootRelease == 0); - testassert(TestRootAutorelease == 0); - - [tmp release]; - testassert(TestRootDealloc == 1); - testassert(TestRootRetain == 0); - testassert(TestRootRelease == 1); - testassert(TestRootAutorelease == 0); - - } POP_POOL; - - testprintf("Unsuccessful +0 -> +0 handshake\n"); - - PUSH_POOL { - obj = [[TestObject alloc] init]; - testassert(obj); - - TestRootRetain = 0; - TestRootRelease = 0; - TestRootAutorelease = 0; - TestRootDealloc = 0; - - tmp = objc_retainAutoreleaseReturnValue(obj); - NOT_MAGIC; - tmp = objc_unsafeClaimAutoreleasedReturnValue(tmp); - - testassert(TestRootDealloc == 0); - testassert(TestRootRetain == 1); - testassert(TestRootRelease == 0); - testassert(TestRootAutorelease == 1); - - [tmp release]; - testassert(TestRootDealloc == 0); - testassert(TestRootRetain == 1); - testassert(TestRootRelease == 1); - testassert(TestRootAutorelease == 1); - - } POP_POOL; - testassert(TestRootDealloc == 1); - testassert(TestRootRetain == 1); - testassert(TestRootRelease == 2); - testassert(TestRootAutorelease == 1); - - succeed(__FILE__); - - return 0; -} - - -#endif diff --git a/test/rr-autorelease-fastarc.m b/test/rr-autorelease-fastarc.m deleted file mode 100644 index 364b30d..0000000 --- a/test/rr-autorelease-fastarc.m +++ /dev/null @@ -1,231 +0,0 @@ -// TEST_CFLAGS -Os -framework Foundation -// TEST_DISABLED pending clang support for rdar://20530049 - -#include "test.h" -#include "testroot.i" - -#if __i386__ - -int main() -{ - // no optimization on i386 (neither Mac nor Simulator) - succeed(__FILE__); -} - -#else - -#include -#include -#include - -@interface TestObject : TestRoot @end -@implementation TestObject @end - - -#ifdef __arm__ -# define MAGIC asm volatile("mov r7, r7") -# define NOT_MAGIC asm volatile("mov r6, r6") -#elif __arm64__ -# define MAGIC asm volatile("mov x29, x29") -# define NOT_MAGIC asm volatile("mov x28, x28") -#elif __x86_64__ -# define MAGIC asm volatile("") -# define NOT_MAGIC asm volatile("nop") -#else -# error unknown architecture -#endif - - -@interface Tester : NSObject @end -@implementation Tester { -@public - id ivar; -} - --(id) return0 { - return ivar; -} --(id) return1 { - id x = ivar; - [x self]; - return x; -} - -@end - -OBJC_EXPORT -id -objc_retainAutoreleasedReturnValue(id obj) - __OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0); - -// Accept a value returned through a +0 autoreleasing convention for use at +0. -OBJC_EXPORT -id -objc_unsafeClaimAutoreleasedReturnValue(id obj) - __OSX_AVAILABLE_STARTING(__MAC_10_11, __IPHONE_9_0); - - -int -main() -{ - TestObject *obj; - Tester *tt = [Tester new]; - -#ifdef __x86_64__ - // need to get DYLD to resolve the stubs on x86 - PUSH_POOL { - TestObject *warm_up = [[TestObject alloc] init]; - testassert(warm_up); - warm_up = objc_retainAutoreleasedReturnValue(warm_up); - warm_up = objc_unsafeClaimAutoreleasedReturnValue(warm_up); - warm_up = nil; - } POP_POOL; -#endif - - testprintf(" Successful +1 -> +1 handshake\n"); - - PUSH_POOL { - obj = [[TestObject alloc] init]; - testassert(obj); - tt->ivar = obj; - obj = nil; - - TestRootRetain = 0; - TestRootRelease = 0; - TestRootAutorelease = 0; - TestRootDealloc = 0; - - TestObject *tmp = [tt return1]; - - testassert(TestRootDealloc == 0); - testassert(TestRootRetain == 1); - testassert(TestRootRelease == 0); - testassert(TestRootAutorelease == 0); - - tt->ivar = nil; - testassert(TestRootDealloc == 0); - testassert(TestRootRetain == 1); - testassert(TestRootRelease == 1); - testassert(TestRootAutorelease == 0); - - tmp = nil; - testassert(TestRootDealloc == 1); - testassert(TestRootRetain == 1); - testassert(TestRootRelease == 2); - testassert(TestRootAutorelease == 0); - - } POP_POOL; - - testprintf(" Successful +0 -> +0 handshake\n"); - - PUSH_POOL { - obj = [[TestObject alloc] init]; - testassert(obj); - tt->ivar = obj; - obj = nil; - - TestRootRetain = 0; - TestRootRelease = 0; - TestRootAutorelease = 0; - TestRootDealloc = 0; - - __unsafe_unretained TestObject *tmp = [tt return0]; - - testassert(TestRootDealloc == 0); - testassert(TestRootRetain == 0); - testassert(TestRootRelease == 0); - testassert(TestRootAutorelease == 0); - - tmp = nil; - testassert(TestRootDealloc == 0); - testassert(TestRootRetain == 0); - testassert(TestRootRelease == 0); - testassert(TestRootAutorelease == 0); - - tt->ivar = nil; - testassert(TestRootDealloc == 1); - testassert(TestRootRetain == 0); - testassert(TestRootRelease == 1); - testassert(TestRootAutorelease == 0); - - } POP_POOL; - - - testprintf(" Successful +1 -> +0 handshake\n"); - - PUSH_POOL { - obj = [[TestObject alloc] init]; - testassert(obj); - tt->ivar = obj; - obj = nil; - - TestRootRetain = 0; - TestRootRelease = 0; - TestRootAutorelease = 0; - TestRootDealloc = 0; - - __unsafe_unretained TestObject *tmp = [tt return1]; - - testassert(TestRootDealloc == 0); - testassert(TestRootRetain == 1); - testassert(TestRootRelease == 1); - testassert(TestRootAutorelease == 0); - - tmp = nil; - testassert(TestRootDealloc == 0); - testassert(TestRootRetain == 1); - testassert(TestRootRelease == 1); - testassert(TestRootAutorelease == 0); - - tt->ivar = nil; - testassert(TestRootDealloc == 1); - testassert(TestRootRetain == 1); - testassert(TestRootRelease == 2); - testassert(TestRootAutorelease == 0); - - } POP_POOL; - - - testprintf(" Successful +0 -> +1 handshake\n"); - - PUSH_POOL { - obj = [[TestObject alloc] init]; - testassert(obj); - tt->ivar = obj; - obj = nil; - - TestRootRetain = 0; - TestRootRelease = 0; - TestRootAutorelease = 0; - TestRootDealloc = 0; - - TestObject *tmp = [tt return0]; - - testassert(TestRootDealloc == 0); - testassert(TestRootRetain == 1); - testassert(TestRootRelease == 0); - testassert(TestRootAutorelease == 0); - - tmp = nil; - testassert(TestRootDealloc == 0); - testassert(TestRootRetain == 1); - testassert(TestRootRelease == 1); - testassert(TestRootAutorelease == 0); - - tt->ivar = nil; - testassert(TestRootDealloc == 1); - testassert(TestRootRetain == 1); - testassert(TestRootRelease == 2); - testassert(TestRootAutorelease == 0); - - } POP_POOL; - - - - succeed(__FILE__); - - return 0; -} - - -#endif diff --git a/test/rr-autorelease-stacklogging.m b/test/rr-autorelease-stacklogging.m deleted file mode 100644 index 40aa039..0000000 --- a/test/rr-autorelease-stacklogging.m +++ /dev/null @@ -1,12 +0,0 @@ -// Test OBJC_DEBUG_POOL_ALLOCATION (which is also enabled by MallocStackLogging) - -// TEST_ENV OBJC_DEBUG_POOL_ALLOCATION=YES -// TEST_CFLAGS -framework Foundation -// TEST_CONFIG MEM=mrc - -#include "test.h" - -#define FOUNDATION 0 -#define NAME "rr-autorelease-stacklogging" - -#include "rr-autorelease2.m" diff --git a/test/rr-autorelease.m b/test/rr-autorelease.m deleted file mode 100644 index 01eb89e..0000000 --- a/test/rr-autorelease.m +++ /dev/null @@ -1,9 +0,0 @@ -// TEST_CFLAGS -framework Foundation -// TEST_CONFIG MEM=mrc - -#include "test.h" - -#define FOUNDATION 0 -#define NAME "rr-autorelease" - -#include "rr-autorelease2.m" diff --git a/test/rr-autorelease2.m b/test/rr-autorelease2.m deleted file mode 100644 index d68bd54..0000000 --- a/test/rr-autorelease2.m +++ /dev/null @@ -1,364 +0,0 @@ -// Define FOUNDATION=1 for NSObject and NSAutoreleasePool -// Define FOUNDATION=0 for _objc_root* and _objc_autoreleasePool* - -#include "test.h" - -#if FOUNDATION -# define RR_PUSH() [[NSAutoreleasePool alloc] init] -# define RR_POP(p) [(id)p release] -# define RR_RETAIN(o) [o retain] -# define RR_RELEASE(o) [o release] -# define RR_AUTORELEASE(o) [o autorelease] -# define RR_RETAINCOUNT(o) [o retainCount] -#else -# define RR_PUSH() _objc_autoreleasePoolPush() -# define RR_POP(p) _objc_autoreleasePoolPop(p) -# define RR_RETAIN(o) _objc_rootRetain((id)o) -# define RR_RELEASE(o) _objc_rootRelease((id)o) -# define RR_AUTORELEASE(o) _objc_rootAutorelease((id)o) -# define RR_RETAINCOUNT(o) _objc_rootRetainCount((id)o) -#endif - -#include -#include - -static int state; -static pthread_attr_t smallstack; - -#define NESTED_COUNT 8 - -@interface Deallocator : NSObject @end -@implementation Deallocator --(void) dealloc -{ - // testprintf("-[Deallocator %p dealloc]\n", self); - state++; - [super dealloc]; -} -@end - -@interface AutoreleaseDuringDealloc : NSObject @end -@implementation AutoreleaseDuringDealloc --(void) dealloc -{ - state++; - RR_AUTORELEASE([[Deallocator alloc] init]); - [super dealloc]; -} -@end - -@interface AutoreleasePoolDuringDealloc : NSObject @end -@implementation AutoreleasePoolDuringDealloc --(void) dealloc -{ - // caller's pool - for (int i = 0; i < NESTED_COUNT; i++) { - RR_AUTORELEASE([[Deallocator alloc] init]); - } - - // local pool, popped - void *pool = RR_PUSH(); - for (int i = 0; i < NESTED_COUNT; i++) { - RR_AUTORELEASE([[Deallocator alloc] init]); - } - RR_POP(pool); - - // caller's pool again - for (int i = 0; i < NESTED_COUNT; i++) { - RR_AUTORELEASE([[Deallocator alloc] init]); - } - -#if FOUNDATION - { - static bool warned; - if (!warned) testwarn("rdar://7138159 NSAutoreleasePool leaks"); - warned = true; - } - state += NESTED_COUNT; -#else - // local pool, not popped - RR_PUSH(); - for (int i = 0; i < NESTED_COUNT; i++) { - RR_AUTORELEASE([[Deallocator alloc] init]); - } -#endif - - [super dealloc]; -} -@end - -void *autorelease_lots_fn(void *singlePool) -{ - // Enough to blow out the stack if AutoreleasePoolPage is recursive. - const int COUNT = 1024*1024; - state = 0; - - int p = 0; - void **pools = (void**)malloc((COUNT+1) * sizeof(void*)); - pools[p++] = RR_PUSH(); - - id obj = RR_AUTORELEASE([[Deallocator alloc] init]); - - // last pool has only 1 autorelease in it - pools[p++] = RR_PUSH(); - - for (int i = 0; i < COUNT; i++) { - if (rand() % 1000 == 0 && !singlePool) { - pools[p++] = RR_PUSH(); - } else { - RR_AUTORELEASE(RR_RETAIN(obj)); - } - } - - testassert(state == 0); - while (--p) { - RR_POP(pools[p]); - } - testassert(state == 0); - testassert(RR_RETAINCOUNT(obj) == 1); - RR_POP(pools[0]); - testassert(state == 1); - free(pools); - - return NULL; -} - -void *nsthread_fn(void *arg __unused) -{ - [NSThread currentThread]; - void *pool = RR_PUSH(); - RR_AUTORELEASE([[Deallocator alloc] init]); - RR_POP(pool); - return NULL; -} - -void cycle(void) -{ - // Normal autorelease. - testprintf("-- Normal autorelease.\n"); - { - void *pool = RR_PUSH(); - state = 0; - RR_AUTORELEASE([[Deallocator alloc] init]); - testassert(state == 0); - RR_POP(pool); - testassert(state == 1); - } - - // Autorelease during dealloc during autoreleasepool-pop. - // That autorelease is handled by the popping pool, not the one above it. - testprintf("-- Autorelease during dealloc during autoreleasepool-pop.\n"); - { - void *pool = RR_PUSH(); - state = 0; - RR_AUTORELEASE([[AutoreleaseDuringDealloc alloc] init]); - testassert(state == 0); - RR_POP(pool); - testassert(state == 2); - } - - // Autorelease pool during dealloc during autoreleasepool-pop. - testprintf("-- Autorelease pool during dealloc during autoreleasepool-pop.\n"); - { - void *pool = RR_PUSH(); - state = 0; - RR_AUTORELEASE([[AutoreleasePoolDuringDealloc alloc] init]); - testassert(state == 0); - RR_POP(pool); - testassert(state == 4 * NESTED_COUNT); - } - - // Top-level thread pool popped normally. - testprintf("-- Thread-level pool popped normally.\n"); - { - state = 0; - testonthread(^{ - void *pool = RR_PUSH(); - RR_AUTORELEASE([[Deallocator alloc] init]); - RR_POP(pool); - }); - testassert(state == 1); - } - - - // Autorelease with no pool. - testprintf("-- Autorelease with no pool.\n"); - { - state = 0; - testonthread(^{ - RR_AUTORELEASE([[Deallocator alloc] init]); - }); - testassert(state == 1); - } - - // Autorelease with no pool after popping the top-level pool. - testprintf("-- Autorelease with no pool after popping the last pool.\n"); - { - state = 0; - testonthread(^{ - void *pool = RR_PUSH(); - RR_AUTORELEASE([[Deallocator alloc] init]); - RR_POP(pool); - RR_AUTORELEASE([[Deallocator alloc] init]); - }); - testassert(state == 2); - } - - // Top-level thread pool not popped. - // The runtime should clean it up. -#if FOUNDATION - { - static bool warned; - if (!warned) testwarn("rdar://7138159 NSAutoreleasePool leaks"); - warned = true; - } -#else - testprintf("-- Thread-level pool not popped.\n"); - { - state = 0; - testonthread(^{ - RR_PUSH(); - RR_AUTORELEASE([[Deallocator alloc] init]); - // pool not popped - }); - testassert(state == 1); - } -#endif - - // Intermediate pool not popped. - // Popping the containing pool should clean up the skipped pool first. -#if FOUNDATION - { - static bool warned; - if (!warned) testwarn("rdar://7138159 NSAutoreleasePool leaks"); - warned = true; - } -#else - testprintf("-- Intermediate pool not popped.\n"); - { - void *pool = RR_PUSH(); - void *pool2 = RR_PUSH(); - RR_AUTORELEASE([[Deallocator alloc] init]); - state = 0; - (void)pool2; // pool2 not popped - RR_POP(pool); - testassert(state == 1); - } -#endif -} - - -static void -slow_cycle(void) -{ - // Large autorelease stack. - // Do this only once because it's slow. - testprintf("-- Large autorelease stack.\n"); - { - // limit stack size: autorelease pop should not be recursive - pthread_t th; - pthread_create(&th, &smallstack, &autorelease_lots_fn, NULL); - pthread_join(th, NULL); - } - - // Single large autorelease pool. - // Do this only once because it's slow. - testprintf("-- Large autorelease pool.\n"); - { - // limit stack size: autorelease pop should not be recursive - pthread_t th; - pthread_create(&th, &smallstack, &autorelease_lots_fn, (void*)1); - pthread_join(th, NULL); - } -} - - -int main() -{ - pthread_attr_init(&smallstack); - pthread_attr_setstacksize(&smallstack, 16384); - - // inflate the refcount side table so it doesn't show up in leak checks - { - int count = 10000; - id *objs = (id *)malloc(count*sizeof(id)); - for (int i = 0; i < count; i++) { - objs[i] = RR_RETAIN([NSObject new]); - } - for (int i = 0; i < count; i++) { - RR_RELEASE(objs[i]); - RR_RELEASE(objs[i]); - } - free(objs); - } - -#if FOUNDATION - // inflate NSAutoreleasePool's instance cache - { - int count = 32; - id *objs = (id *)malloc(count * sizeof(id)); - for (int i = 0; i < count; i++) { - objs[i] = [[NSAutoreleasePool alloc] init]; - } - for (int i = 0; i < count; i++) { - [objs[count-i-1] release]; - } - - free(objs); - } -#endif - - // preheat - { - for (int i = 0; i < 100; i++) { - cycle(); - } - - slow_cycle(); - } - - // check for leaks using top-level pools - { - leak_mark(); - - for (int i = 0; i < 1000; i++) { - cycle(); - } - - leak_check(0); - - slow_cycle(); - - leak_check(0); - } - - // check for leaks using pools not at top level - void *pool = RR_PUSH(); - { - leak_mark(); - - for (int i = 0; i < 1000; i++) { - cycle(); - } - - leak_check(0); - - slow_cycle(); - - leak_check(0); - } - RR_POP(pool); - - // NSThread. - // Can't leak check this because it's too noisy. - testprintf("-- NSThread.\n"); - { - pthread_t th; - pthread_create(&th, &smallstack, &nsthread_fn, 0); - pthread_join(th, NULL); - } - - // NO LEAK CHECK HERE - - succeed(NAME); -} diff --git a/test/rr-nsautorelease.m b/test/rr-nsautorelease.m deleted file mode 100644 index 095ec36..0000000 --- a/test/rr-nsautorelease.m +++ /dev/null @@ -1,7 +0,0 @@ -// TEST_CFLAGS -framework Foundation -// TEST_CONFIG MEM=mrc - -#define FOUNDATION 1 -#define NAME "rr-nsautorelease" - -#include "rr-autorelease2.m" diff --git a/test/rr-sidetable.m b/test/rr-sidetable.m deleted file mode 100644 index daa4090..0000000 --- a/test/rr-sidetable.m +++ /dev/null @@ -1,59 +0,0 @@ -// TEST_CFLAGS -framework Foundation -// TEST_CONFIG MEM=mrc ARCH=x86_64 - -// Stress-test nonpointer isa's side table retain count transfers. - -// x86_64 only. arm64's side table limit is high enough that bugs -// are harder to reproduce. - -#include "test.h" -#import - -#define OBJECTS 1 -#define LOOPS 256 -#define THREADS 16 -#if __x86_64__ -# define RC_HALF (1ULL<<7) -#else -# error sorry -#endif -#define RC_DELTA RC_HALF - -static bool Deallocated = false; -@interface Deallocator : NSObject @end -@implementation Deallocator --(void)dealloc { - Deallocated = true; - [super dealloc]; -} -@end - -// This is global to avoid extra retains by the dispatch block objects. -static Deallocator *obj; - -int main() { - dispatch_queue_t queue = - dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); - - for (size_t i = 0; i < OBJECTS; i++) { - obj = [Deallocator new]; - - dispatch_apply(THREADS, queue, ^(size_t i __unused) { - for (size_t a = 0; a < LOOPS; a++) { - for (size_t b = 0; b < RC_DELTA; b++) { - [obj retain]; - } - for (size_t b = 0; b < RC_DELTA; b++) { - [obj release]; - } - } - }); - - testassert(!Deallocated); - [obj release]; - testassert(Deallocated); - Deallocated = false; - } - - succeed(__FILE__); -} diff --git a/test/runtime.m b/test/runtime.m deleted file mode 100644 index b4a592c..0000000 --- a/test/runtime.m +++ /dev/null @@ -1,216 +0,0 @@ -/* -TEST_RUN_OUTPUT -objc\[\d+\]: class `SwiftV1Class\' not linked into application -objc\[\d+\]: class `DoesNotExist\' not linked into application -OK: runtime.m -OR -confused by Foundation -OK: runtime.m -END -*/ - - -#include "test.h" -#include "testroot.i" -#include -#include -#include -#include - -#if __has_feature(objc_arc) - -int main() -{ - testwarn("rdar://11368528 confused by Foundation"); - fprintf(stderr, "confused by Foundation\n"); - succeed(__FILE__); -} - -#else - -@interface Sub : TestRoot @end -@implementation Sub @end - -#if __OBJC2__ -# define TEST_SWIFT 1 -#else -# define TEST_SWIFT 0 -#endif - -#define SwiftV1MangledName "_TtC6Module12SwiftV1Class" -#define SwiftV1MangledName2 "_TtC2Sw13SwiftV1Class2" -#define SwiftV1MangledName3 "_TtCSs13SwiftV1Class3" -#define SwiftV1MangledName4 "_TtC6Swiftt13SwiftV1Class4" - -#if TEST_SWIFT -__attribute__((objc_runtime_name(SwiftV1MangledName))) -@interface SwiftV1Class : TestRoot @end -@implementation SwiftV1Class @end - -__attribute__((objc_runtime_name(SwiftV1MangledName2))) -@interface SwiftV1Class2 : TestRoot @end -@implementation SwiftV1Class2 @end - -__attribute__((objc_runtime_name(SwiftV1MangledName3))) -@interface SwiftV1Class3 : TestRoot @end -@implementation SwiftV1Class3 @end - -__attribute__((objc_runtime_name(SwiftV1MangledName4))) -@interface SwiftV1Class4 : TestRoot @end -@implementation SwiftV1Class4 @end -#endif - - -int main() -{ - Class list[100]; - Class *list2; - unsigned int count, count0, count2; - unsigned int i; - int foundTestRoot; - int foundSub; - int foundSwiftV1; - int foundSwiftV1class2; - int foundSwiftV1class3; - int foundSwiftV1class4; - const char **names; - Dl_info info; - - [TestRoot class]; - - // This shouldn't touch any classes. - dladdr(&_mh_execute_header, &info); - names = objc_copyClassNamesForImage(info.dli_fname, &count); - testassert(names); -#if TEST_SWIFT - testassert(count == 6); -#else - testassert(count == 2); -#endif - testassert(names[count] == NULL); - foundTestRoot = 0; - foundSub = 0; - foundSwiftV1 = 0; - foundSwiftV1class2 = 0; - foundSwiftV1class3 = 0; - foundSwiftV1class4 = 0; - for (i = 0; i < count; i++) { - if (0 == strcmp(names[i], "TestRoot")) foundTestRoot++; - if (0 == strcmp(names[i], "Sub")) foundSub++; - if (0 == strcmp(names[i], "Module.SwiftV1Class")) foundSwiftV1++; - if (0 == strcmp(names[i], "Sw.SwiftV1Class2")) foundSwiftV1class2++; - if (0 == strcmp(names[i], "Swift.SwiftV1Class3")) foundSwiftV1class3++; - if (0 == strcmp(names[i], "Swiftt.SwiftV1Class4")) foundSwiftV1class4++; - } - testassert(foundTestRoot == 1); - testassert(foundSub == 1); -#if TEST_SWIFT - testassert(foundSwiftV1 == 1); - testassert(foundSwiftV1class2 == 1); - testassert(foundSwiftV1class3 == 1); - testassert(foundSwiftV1class4 == 1); -#endif - - - // class Sub hasn't been touched - make sure it's in the class list too - count0 = objc_getClassList(NULL, 0); - testassert(count0 >= 2 && count0 < 100); - - list[count0-1] = NULL; - count = objc_getClassList(list, count0-1); - testassert(list[count0-1] == NULL); - testassert(count == count0); - - count = objc_getClassList(list, count0); - testassert(count == count0); - - for (i = 0; i < count; i++) { - testprintf("%s\n", class_getName(list[i])); - } - - foundTestRoot = 0; - foundSub = 0; - foundSwiftV1 = 0; - foundSwiftV1class2 = 0; - foundSwiftV1class3 = 0; - foundSwiftV1class4 = 0; - for (i = 0; i < count; i++) { - if (0 == strcmp(class_getName(list[i]), "TestRoot")) foundTestRoot++; - if (0 == strcmp(class_getName(list[i]), "Sub")) foundSub++; - if (0 == strcmp(class_getName(list[i]), "Module.SwiftV1Class")) foundSwiftV1++; - if (0 == strcmp(class_getName(list[i]), "Sw.SwiftV1Class2")) foundSwiftV1class2++; - if (0 == strcmp(class_getName(list[i]), "Swift.SwiftV1Class3")) foundSwiftV1class3++; - if (0 == strcmp(class_getName(list[i]), "Swiftt.SwiftV1Class4")) foundSwiftV1class4++; - // list should be non-meta classes only - testassert(!class_isMetaClass(list[i])); - } - testassert(foundTestRoot == 1); - testassert(foundSub == 1); -#if TEST_SWIFT - testassert(foundSwiftV1 == 1); - testassert(foundSwiftV1class2 == 1); - testassert(foundSwiftV1class3 == 1); - testassert(foundSwiftV1class4 == 1); -#endif - - // fixme check class handler - testassert(objc_getClass("TestRoot") == [TestRoot class]); -#if TEST_SWIFT - testassert(objc_getClass("Module.SwiftV1Class") == [SwiftV1Class class]); - testassert(objc_getClass(SwiftV1MangledName) == [SwiftV1Class class]); - testassert(objc_getClass("Sw.SwiftV1Class2") == [SwiftV1Class2 class]); - testassert(objc_getClass(SwiftV1MangledName2) == [SwiftV1Class2 class]); - testassert(objc_getClass("Swift.SwiftV1Class3") == [SwiftV1Class3 class]); - testassert(objc_getClass(SwiftV1MangledName3) == [SwiftV1Class3 class]); - testassert(objc_getClass("Swiftt.SwiftV1Class4") == [SwiftV1Class4 class]); - testassert(objc_getClass(SwiftV1MangledName4) == [SwiftV1Class4 class]); -#endif - testassert(objc_getClass("SwiftV1Class") == nil); - testassert(objc_getClass("DoesNotExist") == nil); - testassert(objc_getClass(NULL) == nil); - - testassert(objc_getMetaClass("TestRoot") == object_getClass([TestRoot class])); -#if TEST_SWIFT - testassert(objc_getMetaClass("Module.SwiftV1Class") == object_getClass([SwiftV1Class class])); - testassert(objc_getMetaClass(SwiftV1MangledName) == object_getClass([SwiftV1Class class])); -#endif - testassert(objc_getMetaClass("SwiftV1Class") == nil); - testassert(objc_getMetaClass("DoesNotExist") == nil); - testassert(objc_getMetaClass(NULL) == nil); - - // fixme check class no handler - testassert(objc_lookUpClass("TestRoot") == [TestRoot class]); -#if TEST_SWIFT - testassert(objc_lookUpClass("Module.SwiftV1Class") == [SwiftV1Class class]); - testassert(objc_lookUpClass(SwiftV1MangledName) == [SwiftV1Class class]); -#endif - testassert(objc_lookUpClass("SwiftV1Class") == nil); - testassert(objc_lookUpClass("DoesNotExist") == nil); - testassert(objc_lookUpClass(NULL) == nil); - - testassert(! object_isClass(nil)); - testassert(! object_isClass([TestRoot new])); - testassert(object_isClass([TestRoot class])); - testassert(object_isClass(object_getClass([TestRoot class]))); - testassert(object_isClass([Sub class])); - testassert(object_isClass(object_getClass([Sub class]))); -#if TEST_SWIFT - testassert(object_isClass([SwiftV1Class class])); - testassert(object_isClass(object_getClass([SwiftV1Class class]))); -#endif - - list2 = objc_copyClassList(&count2); - testassert(count2 == count); - testassert(list2); - testassert(malloc_size(list2) >= (1+count2) * sizeof(Class)); - for (i = 0; i < count; i++) { - testassert(list[i] == list2[i]); - } - testassert(list2[count] == NULL); - free(list2); - free(objc_copyClassList(NULL)); - - succeed(__FILE__); -} - -#endif diff --git a/test/sel.m b/test/sel.m deleted file mode 100644 index 44618c5..0000000 --- a/test/sel.m +++ /dev/null @@ -1,45 +0,0 @@ -// TEST_CONFIG - -#include "test.h" -#include -#include -#include - -int main() -{ - // Make sure @selector values are correctly fixed up - testassert(@selector(foo) == sel_registerName("foo")); - - // sel_getName recognizes the zero SEL - testassert(0 == strcmp("", sel_getName(0))); - - // GC-ignored selectors. -#if __has_feature(objc_arc) - - // ARC dislikes `@selector(retain)` - -#else - -# if defined(__i386__) - // sel_getName recognizes GC-ignored SELs - if (objc_collectingEnabled()) { - testassert(0 == strcmp("", - sel_getName(@selector(retain)))); - } else { - testassert(0 == strcmp("retain", - sel_getName(@selector(retain)))); - } - - // _objc_search_builtins() shouldn't crash on GC-ignored SELs - union { - SEL sel; - const char *ptr; - } u; - u.sel = @selector(retain); - testassert(@selector(retain) == sel_registerName(u.ptr)); -# endif - -#endif - - succeed(__FILE__); -} diff --git a/test/setSuper.m b/test/setSuper.m deleted file mode 100644 index 7ce6dee..0000000 --- a/test/setSuper.m +++ /dev/null @@ -1,44 +0,0 @@ -// TEST_CFLAGS -Wno-deprecated-declarations - -#include "test.h" -#include "testroot.i" -#include - -@interface Super1 : TestRoot @end -@implementation Super1 -+(int)classMethod { return 1; } --(int)instanceMethod { return 10000; } -@end - -@interface Super2 : TestRoot @end -@implementation Super2 -+(int)classMethod { return 2; } --(int)instanceMethod { return 20000; } -@end - -@interface Sub : Super1 @end -@implementation Sub -+(int)classMethod { return [super classMethod] + 100; } --(int)instanceMethod { - return [super instanceMethod] + 1000000; -} -@end - -int main() -{ - Class cls; - Sub *obj = [Sub new]; - - testassert(101 == [[Sub class] classMethod]); - testassert(1010000 == [obj instanceMethod]); - - cls = class_setSuperclass([Sub class], [Super2 class]); - - testassert(cls == [Super1 class]); - testassert(object_getClass(cls) == object_getClass([Super1 class])); - - testassert(102 == [[Sub class] classMethod]); - testassert(1020000 == [obj instanceMethod]); - - succeed(__FILE__); -} diff --git a/test/subscripting.m b/test/subscripting.m deleted file mode 100644 index e05a370..0000000 --- a/test/subscripting.m +++ /dev/null @@ -1,154 +0,0 @@ -// TEST_CONFIG MEM=arc,mrc CC=clang LANGUAGE=objc,objc++ -// TEST_CFLAGS -framework Foundation - -#if !__OBJC2__ - -#include "test.h" - -int main() -{ - succeed(__FILE__); -} - -#else - -#import -#import -#import -#import -#include "test.h" - -@interface TestIndexed : NSObject { - NSMutableArray *indexedValues; -} -@property(readonly) NSUInteger count; -- (id)objectAtIndexedSubscript:(NSUInteger)index; -- (void)setObject:(id)object atIndexedSubscript:(NSUInteger)index; -@end - -@implementation TestIndexed - -- (id)init { - if ((self = [super init])) { - indexedValues = [NSMutableArray new]; - } - return self; -} - -#if !__has_feature(objc_arc) -- (void)dealloc { - [indexedValues release]; - [super dealloc]; -} -#endif - -- (NSUInteger)count { return [indexedValues count]; } -- (id)objectAtIndexedSubscript:(NSUInteger)index { return [indexedValues objectAtIndex:index]; } -- (void)setObject:(id)object atIndexedSubscript:(NSUInteger)index { - if (index == NSNotFound) - [indexedValues addObject:object]; - else - [indexedValues replaceObjectAtIndex:index withObject:object]; -} - -- (NSString *)description { - return [NSString stringWithFormat:@"indexedValues = %@", indexedValues]; -} - -- (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state objects:(id __unsafe_unretained [])buffer count:(NSUInteger)len { - return [indexedValues countByEnumeratingWithState:state objects:buffer count:len]; -} - - -@end - -@interface TestKeyed : NSObject { - NSMutableDictionary *keyedValues; -} -@property(readonly) NSUInteger count; -- (id)objectForKeyedSubscript:(id)key; -- (void)setObject:(id)object forKeyedSubscript:(id)key; -@end - -@implementation TestKeyed - -- (id)init { - if ((self = [super init])) { - keyedValues = [NSMutableDictionary new]; - } - return self; -} - -#if !__has_feature(objc_arc) -- (void)dealloc { - [keyedValues release]; - [super dealloc]; -} -#endif - -- (NSUInteger)count { return [keyedValues count]; } -- (id)objectForKeyedSubscript:(id)key { return [keyedValues objectForKey:key]; } -- (void)setObject:(id)object forKeyedSubscript:(id)key { - [keyedValues setObject:object forKey:key]; -} - -- (NSString *)description { - return [NSString stringWithFormat:@"keyedValues = %@", keyedValues]; -} - -- (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state objects:(id __unsafe_unretained [])buffer count:(NSUInteger)len { - return [keyedValues countByEnumeratingWithState:state objects:buffer count:len]; -} - -@end - -int main() { - PUSH_POOL { - -#if __has_feature(objc_bool) // placeholder until we get a more precise macro. - TestIndexed *testIndexed = [TestIndexed new]; - id objects[] = { @1, @2, @3, @4, @5 }; - size_t i, count = sizeof(objects) / sizeof(id); - for (i = 0; i < count; ++i) { - testIndexed[NSNotFound] = objects[i]; - } - for (i = 0; i < count; ++i) { - id object = testIndexed[i]; - testassert(object == objects[i]); - } - if (getenv("VERBOSE")) { - i = 0; - for (id object in testIndexed) { - NSString *message = [NSString stringWithFormat:@"testIndexed[%zu] = %@\n", i++, object]; - testprintf([message UTF8String]); - } - } - - TestKeyed *testKeyed = [TestKeyed new]; - id keys[] = { @"One", @"Two", @"Three", @"Four", @"Five" }; - for (i = 0; i < count; ++i) { - id key = keys[i]; - testKeyed[key] = objects[i]; - } - for (i = 0; i < count; ++i) { - id key = keys[i]; - id object = testKeyed[key]; - testassert(object == objects[i]); - } - if (getenv("VERBOSE")) { - for (id key in testKeyed) { - NSString *message = [NSString stringWithFormat:@"testKeyed[@\"%@\"] = %@\n", key, testKeyed[key]]; - testprintf([message UTF8String]); - } - } -#endif - - } POP_POOL; - - succeed(__FILE__); - - return 0; -} - -// __OBJC2__ -#endif diff --git a/test/super.m b/test/super.m deleted file mode 100644 index ff169f7..0000000 --- a/test/super.m +++ /dev/null @@ -1,21 +0,0 @@ -// TEST_CONFIG - -#include "test.h" -#include "testroot.i" -#include - -@interface Sub : TestRoot @end -@implementation Sub @end - -int main() -{ - // [super ...] messages are tested in msgSend.m - - testassert(class_getSuperclass([Sub class]) == [TestRoot class]); - testassert(class_getSuperclass(object_getClass([Sub class])) == object_getClass([TestRoot class])); - testassert(class_getSuperclass([TestRoot class]) == Nil); - testassert(class_getSuperclass(object_getClass([TestRoot class])) == [TestRoot class]); - testassert(class_getSuperclass(Nil) == Nil); - - succeed(__FILE__); -} diff --git a/test/synchronized-counter.m b/test/synchronized-counter.m deleted file mode 100644 index 7f6bd19..0000000 --- a/test/synchronized-counter.m +++ /dev/null @@ -1,90 +0,0 @@ -// TEST_CONFIG - -#include "test.h" - -#include -#include -#include -#include -#include -#include - -// synchronized stress test -// Single locked counter incremented by many threads. - -#if defined(__arm__) -#define THREADS 16 -#define COUNT 1024*24 -#else -// 64 / 1024*24 test takes about 20s on 4x2.6GHz Mac Pro -#define THREADS 64 -#define COUNT 1024*24 -#endif - -static id lock; -static int count; - -static void *threadfn(void *arg) -{ - int n, d; - int depth = 1 + (int)(intptr_t)arg % 4; - - objc_registerThreadWithCollector(); - - for (n = 0; n < COUNT; n++) { - // Lock - for (d = 0; d < depth; d++) { - int err = objc_sync_enter(lock); - testassert(err == OBJC_SYNC_SUCCESS); - } - - // Increment - count++; - - // Unlock - for (d = 0; d < depth; d++) { - int err = objc_sync_exit(lock); - testassert(err == OBJC_SYNC_SUCCESS); - } - } - - // Verify lack of objc pthread data (should have used sync fast cache) -#ifdef __PTK_FRAMEWORK_OBJC_KEY0 - testassert(! pthread_getspecific(__PTK_FRAMEWORK_OBJC_KEY0)); -#endif - - return NULL; -} - -int main() -{ - pthread_t threads[THREADS]; - int t; - int err; - - lock = [[NSObject alloc] init]; - - // Verify objc pthread data on this thread (from +initialize) - // Worker threads shouldn't have any because of sync fast cache. -#ifdef __PTK_FRAMEWORK_OBJC_KEY0 - testassert(pthread_getspecific(__PTK_FRAMEWORK_OBJC_KEY0)); -#endif - - // Start the threads - for (t = 0; t < THREADS; t++) { - pthread_create(&threads[t], NULL, &threadfn, (void*)(intptr_t)t); - } - - // Wait for threads to finish - for (t = 0; t < THREADS; t++) { - pthread_join(threads[t], NULL); - } - - // Verify lock: should be available - // Verify count: should be THREADS*COUNT - err = objc_sync_enter(lock); - testassert(err == OBJC_SYNC_SUCCESS); - testassert(count == THREADS*COUNT); - - succeed(__FILE__); -} diff --git a/test/synchronized-grid.m b/test/synchronized-grid.m deleted file mode 100644 index 64c99f4..0000000 --- a/test/synchronized-grid.m +++ /dev/null @@ -1,114 +0,0 @@ -// TEST_CONFIG - -#include "test.h" - -#include -#include -#include -#include -#include - -// synchronized stress test -// 2-D grid of counters and locks. -// Each thread increments all counters some number of times. -// To increment: -// * thread picks a target [row][col] -// * thread locks all locks [row][0] to [row][col], possibly recursively -// * thread increments counter [row][col] -// * thread unlocks all of the locks - -#if defined(__arm__) -// 16 / 4 / 3 / 1024*8 test takes about 30s on 2nd gen iPod touch -#define THREADS 16 -#define ROWS 4 -#define COLS 3 -#define COUNT 1024*8 -#else -// 64 / 4 / 3 / 1024*8 test takes about 20s on 4x2.6GHz Mac Pro -#define THREADS 64 -#define ROWS 4 -#define COLS 3 -#define COUNT 1024*8 -#endif - -static id locks[ROWS][COLS]; -static int counts[ROWS][COLS]; - - -static void *threadfn(void *arg) -{ - int n, d; - int depth = 1 + (int)(intptr_t)arg % 4; - - objc_registerThreadWithCollector(); - - for (n = 0; n < COUNT; n++) { - int rrr = rand() % ROWS; - int ccc = rand() % COLS; - int rr, cc; - for (rr = 0; rr < ROWS; rr++) { - int r = (rrr+rr) % ROWS; - for (cc = 0; cc < COLS; cc++) { - int c = (ccc+cc) % COLS; - int l; - - // Lock [r][0..c] - // ... in that order to prevent deadlock - for (l = 0; l <= c; l++) { - for (d = 0; d < depth; d++) { - int err = objc_sync_enter(locks[r][l]); - testassert(err == OBJC_SYNC_SUCCESS); - } - } - - // Increment count [r][c] - counts[r][c]++; - - // Unlock [r][0..c] - // ... in that order to increase contention - for (l = 0; l <= c; l++) { - for (d = 0; d < depth; d++) { - int err = objc_sync_exit(locks[r][l]); - testassert(err == OBJC_SYNC_SUCCESS); - } - } - } - } - } - - return NULL; -} - -int main() -{ - pthread_t threads[THREADS]; - int r, c, t; - - for (r = 0; r < ROWS; r++) { - for (c = 0; c < COLS; c++) { - locks[r][c] = [[NSObject alloc] init]; - } - } - - // Start the threads - for (t = 0; t < THREADS; t++) { - pthread_create(&threads[t], NULL, &threadfn, (void*)(intptr_t)t); - } - - // Wait for threads to finish - for (t = 0; t < THREADS; t++) { - pthread_join(threads[t], NULL); - } - - // Verify locks: all should be available - // Verify counts: all should be THREADS*COUNT - for (r = 0; r < ROWS; r++) { - for (c = 0; c < COLS; c++) { - int err = objc_sync_enter(locks[r][c]); - testassert(err == OBJC_SYNC_SUCCESS); - testassert(counts[r][c] == THREADS*COUNT); - } - } - - succeed(__FILE__); -} diff --git a/test/synchronized.m b/test/synchronized.m deleted file mode 100644 index 11922be..0000000 --- a/test/synchronized.m +++ /dev/null @@ -1,104 +0,0 @@ -// TEST_CONFIG - -#include "test.h" - -#include -#include -#include -#include -#include -#include - -// Basic @synchronized tests. - - -#define WAIT_SEC 3 - -static id obj; -static semaphore_t go; -static semaphore_t stop; - -void *thread(void *arg __unused) -{ - int err; - - objc_registerThreadWithCollector(); - - // non-blocking sync_enter - err = objc_sync_enter(obj); - testassert(err == OBJC_SYNC_SUCCESS); - - semaphore_signal(go); - // main thread: sync_exit of object locked on some other thread - semaphore_wait(stop); - - err = objc_sync_exit(obj); - testassert(err == OBJC_SYNC_SUCCESS); - err = objc_sync_enter(obj); - testassert(err == OBJC_SYNC_SUCCESS); - - semaphore_signal(go); - // main thread: blocking sync_enter - testassert(WAIT_SEC/3*3 == WAIT_SEC); - sleep(WAIT_SEC/3); - // recursive enter while someone waits - err = objc_sync_enter(obj); - testassert(err == OBJC_SYNC_SUCCESS); - sleep(WAIT_SEC/3); - // recursive exit while someone waits - err = objc_sync_exit(obj); - testassert(err == OBJC_SYNC_SUCCESS); - sleep(WAIT_SEC/3); - // sync_exit while someone waits - err = objc_sync_exit(obj); - testassert(err == OBJC_SYNC_SUCCESS); - - return NULL; -} - -int main() -{ - pthread_t th; - int err; - struct timeval start, end; - - obj = [[NSObject alloc] init]; - - // sync_exit of never-locked object - err = objc_sync_exit(obj); - testassert(err == OBJC_SYNC_NOT_OWNING_THREAD_ERROR); - - semaphore_create(mach_task_self(), &go, 0, 0); - semaphore_create(mach_task_self(), &stop, 0, 0); - pthread_create(&th, NULL, &thread, NULL); - semaphore_wait(go); - - // sync_exit of object locked on some other thread - err = objc_sync_exit(obj); - testassert(err == OBJC_SYNC_NOT_OWNING_THREAD_ERROR); - - semaphore_signal(stop); - semaphore_wait(go); - - // blocking sync_enter - gettimeofday(&start, NULL); - err = objc_sync_enter(obj); - gettimeofday(&end, NULL); - testassert(err == OBJC_SYNC_SUCCESS); - // should have waited more than WAIT_SEC but less than WAIT_SEC+1 - // fixme hack: sleep(1) is ending 500 usec too early on x86_64 buildbot - // (rdar://6456975) - testassert(end.tv_sec*1000000LL+end.tv_usec >= - start.tv_sec*1000000LL+start.tv_usec + WAIT_SEC*1000000LL - - 3*500 /*hack*/); - testassert(end.tv_sec*1000000LL+end.tv_usec < - start.tv_sec*1000000LL+start.tv_usec + (1+WAIT_SEC)*1000000LL); - - err = objc_sync_exit(obj); - testassert(err == OBJC_SYNC_SUCCESS); - - err = objc_sync_exit(obj); - testassert(err == OBJC_SYNC_NOT_OWNING_THREAD_ERROR); - - succeed(__FILE__); -} diff --git a/test/taggedNSPointers.m b/test/taggedNSPointers.m deleted file mode 100644 index f8b5732..0000000 --- a/test/taggedNSPointers.m +++ /dev/null @@ -1,80 +0,0 @@ -// TEST_CFLAGS -framework Foundation - -#include "test.h" -#include -#import - -#if OBJC_HAVE_TAGGED_POINTERS - -void testTaggedNumber() -{ - NSNumber *taggedNS = [NSNumber numberWithInt: 1234]; - CFNumberRef taggedCF = (CFNumberRef)objc_unretainedPointer(taggedNS); - int result; - - testassert( CFGetTypeID(taggedCF) == CFNumberGetTypeID() ); - testassert(_objc_getClassForTag(OBJC_TAG_NSNumber) == [taggedNS class]); - - CFNumberGetValue(taggedCF, kCFNumberIntType, &result); - testassert(result == 1234); - - testassert(_objc_isTaggedPointer(taggedCF)); - testassert(_objc_getTaggedPointerTag(taggedCF) == OBJC_TAG_NSNumber); - testassert(_objc_makeTaggedPointer(_objc_getTaggedPointerTag(taggedCF), _objc_getTaggedPointerValue(taggedCF)) == taggedCF); - - // do some generic object-y things to the taggedPointer instance - CFRetain(taggedCF); - CFRelease(taggedCF); - - NSMutableDictionary *dict = [NSMutableDictionary dictionary]; - [dict setObject: taggedNS forKey: @"fred"]; - testassert(taggedNS == [dict objectForKey: @"fred"]); - [dict setObject: @"bob" forKey: taggedNS]; - testassert([@"bob" isEqualToString: [dict objectForKey: taggedNS]]); - - NSNumber *iM88 = [NSNumber numberWithInt:-88]; - NSNumber *i12346 = [NSNumber numberWithInt: 12346]; - NSNumber *i12347 = [NSNumber numberWithInt: 12347]; - - NSArray *anArray = [NSArray arrayWithObjects: iM88, i12346, i12347, nil]; - testassert([anArray count] == 3); - testassert([anArray indexOfObject: i12346] == 1); - - NSSet *aSet = [NSSet setWithObjects: iM88, i12346, i12347, nil]; - testassert([aSet count] == 3); - testassert([aSet containsObject: i12346]); - - [taggedNS performSelector: @selector(intValue)]; - testassert(![taggedNS isProxy]); - testassert([taggedNS isKindOfClass: [NSNumber class]]); - testassert([taggedNS respondsToSelector: @selector(intValue)]); - - (void)[taggedNS description]; -} - -int main() -{ - PUSH_POOL { - testTaggedNumber(); // should be tested by CF... our tests are wrong, wrong, wrong. - } POP_POOL; - - succeed(__FILE__); -} - -// OBJC_HAVE_TAGGED_POINTERS -#else -// not OBJC_HAVE_TAGGED_POINTERS - -// Tagged pointers not supported. Crash if an NSNumber actually -// is a tagged pointer (which means this test is out of date). - -int main() -{ - PUSH_POOL { - testassert(*(void **)objc_unretainedPointer([NSNumber numberWithInt:1234])); - } POP_POOL; - - succeed(__FILE__); -} - -#endif diff --git a/test/taggedPointers.m b/test/taggedPointers.m deleted file mode 100644 index ae4e994..0000000 --- a/test/taggedPointers.m +++ /dev/null @@ -1,322 +0,0 @@ -// TEST_CONFIG - -#include "test.h" -#include -#include -#include -#include -#import - -#if OBJC_HAVE_TAGGED_POINTERS - -#if !__OBJC2__ || (!__x86_64__ && !__arm64__) -#error wrong architecture for tagged pointers -#endif - -static BOOL didIt; - -@interface WeakContainer : NSObject -{ - @public - __weak id weaks[10000]; -} -@end -@implementation WeakContainer --(void) dealloc { - for (unsigned int i = 0; i < sizeof(weaks)/sizeof(weaks[0]); i++) { - testassert(weaks[i] == nil); - } - SUPER_DEALLOC(); -} --(void) finalize { - for (unsigned int i = 0; i < sizeof(weaks)/sizeof(weaks[0]); i++) { - testassert(weaks[i] == nil); - } - [super finalize]; -} -@end - -OBJC_ROOT_CLASS -@interface TaggedBaseClass -@end - -@implementation TaggedBaseClass --(id) self { return self; } - -+ (void) initialize { -} - -- (void) instanceMethod { - didIt = YES; -} - -- (uintptr_t) taggedValue { - return _objc_getTaggedPointerValue(objc_unretainedPointer(self)); -} - -- (struct stret) stret: (struct stret) aStruct { - return aStruct; -} - -- (long double) fpret: (long double) aValue { - return aValue; -} - - --(void) dealloc { - fail("TaggedBaseClass dealloc called!"); -} - -static void * -retain_fn(void *self, SEL _cmd __unused) { - void * (*fn)(void *) = (typeof(fn))_objc_rootRetain; - return fn(self); -} - -static void -release_fn(void *self, SEL _cmd __unused) { - void (*fn)(void *) = (typeof(fn))_objc_rootRelease; - fn(self); -} - -static void * -autorelease_fn(void *self, SEL _cmd __unused) { - void * (*fn)(void *) = (typeof(fn))_objc_rootAutorelease; - return fn(self); -} - -static unsigned long -retaincount_fn(void *self, SEL _cmd __unused) { - unsigned long (*fn)(void *) = (typeof(fn))_objc_rootRetainCount; - return fn(self); -} - -+(void) load { - class_addMethod(self, sel_registerName("retain"), (IMP)retain_fn, ""); - class_addMethod(self, sel_registerName("release"), (IMP)release_fn, ""); - class_addMethod(self, sel_registerName("autorelease"), (IMP)autorelease_fn, ""); - class_addMethod(self, sel_registerName("retainCount"), (IMP)retaincount_fn, ""); -} - -@end - -@interface TaggedSubclass: TaggedBaseClass -@end - -@implementation TaggedSubclass - -- (void) instanceMethod { - return [super instanceMethod]; -} - -- (uintptr_t) taggedValue { - return [super taggedValue]; -} - -- (struct stret) stret: (struct stret) aStruct { - return [super stret: aStruct]; -} - -- (long double) fpret: (long double) aValue { - return [super fpret: aValue]; -} -@end - -@interface TaggedNSObjectSubclass : NSObject -@end - -@implementation TaggedNSObjectSubclass - -- (void) instanceMethod { - didIt = YES; -} - -- (uintptr_t) taggedValue { - return _objc_getTaggedPointerValue(objc_unretainedPointer(self)); -} - -- (struct stret) stret: (struct stret) aStruct { - return aStruct; -} - -- (long double) fpret: (long double) aValue { - return aValue; -} -@end - -void testTaggedPointerValue(Class cls, objc_tag_index_t tag, uintptr_t value) -{ - void *taggedAddress = _objc_makeTaggedPointer(tag, value); - testprintf("obj %p, tag %p, value %p\n", - taggedAddress, (void*)tag, (void*)value); - - // _objc_makeTaggedPointer must quietly mask out of range values for now - value = (value << 4) >> 4; - - testassert(_objc_isTaggedPointer(taggedAddress)); - testassert(_objc_getTaggedPointerTag(taggedAddress) == tag); - testassert(_objc_getTaggedPointerValue(taggedAddress) == value); - - testassert((uintptr_t)taggedAddress & objc_debug_taggedpointer_mask); - uintptr_t slot = ((uintptr_t)taggedAddress >> objc_debug_taggedpointer_slot_shift) & objc_debug_taggedpointer_slot_mask; - testassert(objc_debug_taggedpointer_classes[slot] == cls); - testassert((((uintptr_t)taggedAddress << objc_debug_taggedpointer_payload_lshift) >> objc_debug_taggedpointer_payload_rshift) == value); - - id taggedPointer = objc_unretainedObject(taggedAddress); - testassert(!object_isClass(taggedPointer)); - testassert(object_getClass(taggedPointer) == cls); - testassert([taggedPointer taggedValue] == value); - - didIt = NO; - [taggedPointer instanceMethod]; - testassert(didIt); - - struct stret orig = STRET_RESULT; - testassert(stret_equal(orig, [taggedPointer stret: orig])); - - long double dblvalue = 3.14156789; - testassert(dblvalue == [taggedPointer fpret: dblvalue]); - - objc_setAssociatedObject(taggedPointer, (__bridge void *)taggedPointer, taggedPointer, OBJC_ASSOCIATION_RETAIN); - testassert(objc_getAssociatedObject(taggedPointer, (__bridge void *)taggedPointer) == taggedPointer); - objc_setAssociatedObject(taggedPointer, (__bridge void *)taggedPointer, nil, OBJC_ASSOCIATION_RETAIN); - testassert(objc_getAssociatedObject(taggedPointer, (__bridge void *)taggedPointer) == nil); -} - -void testGenericTaggedPointer(objc_tag_index_t tag, Class cls) -{ - testassert(cls); - testprintf("%s\n", class_getName(cls)); - - testTaggedPointerValue(cls, tag, 0); - testTaggedPointerValue(cls, tag, 1UL << 0); - testTaggedPointerValue(cls, tag, 1UL << 1); - testTaggedPointerValue(cls, tag, 1UL << 58); - testTaggedPointerValue(cls, tag, 1UL << 59); - testTaggedPointerValue(cls, tag, ~0UL >> 4); - testTaggedPointerValue(cls, tag, ~0UL); - - // Tagged pointers should bypass refcount tables and autorelease pools - // and weak reference tables - WeakContainer *w = [WeakContainer new]; -#if !__has_feature(objc_arc) - // prime method caches before leak checking - id taggedPointer = (id)_objc_makeTaggedPointer(tag, 1234); - [taggedPointer retain]; - [taggedPointer release]; - [taggedPointer autorelease]; -#endif - leak_mark(); - for (uintptr_t i = 0; i < sizeof(w->weaks)/sizeof(w->weaks[0]); i++) { - id o = objc_unretainedObject(_objc_makeTaggedPointer(tag, i)); - testassert(object_getClass(o) == cls); - - id result = WEAK_STORE(w->weaks[i], o); - testassert(result == o); - testassert(w->weaks[i] == o); - - result = WEAK_LOAD(w->weaks[i]); - testassert(result == o); - - if (!objc_collectingEnabled()) { - uintptr_t rc = _objc_rootRetainCount(o); - testassert(rc != 0); - _objc_rootRelease(o); testassert(_objc_rootRetainCount(o) == rc); - _objc_rootRelease(o); testassert(_objc_rootRetainCount(o) == rc); - _objc_rootRetain(o); testassert(_objc_rootRetainCount(o) == rc); - _objc_rootRetain(o); testassert(_objc_rootRetainCount(o) == rc); - _objc_rootRetain(o); testassert(_objc_rootRetainCount(o) == rc); -#if !__has_feature(objc_arc) - [o release]; testassert(_objc_rootRetainCount(o) == rc); - [o release]; testassert(_objc_rootRetainCount(o) == rc); - [o retain]; testassert(_objc_rootRetainCount(o) == rc); - [o retain]; testassert(_objc_rootRetainCount(o) == rc); - [o retain]; testassert(_objc_rootRetainCount(o) == rc); - objc_release(o); testassert(_objc_rootRetainCount(o) == rc); - objc_release(o); testassert(_objc_rootRetainCount(o) == rc); - objc_retain(o); testassert(_objc_rootRetainCount(o) == rc); - objc_retain(o); testassert(_objc_rootRetainCount(o) == rc); - objc_retain(o); testassert(_objc_rootRetainCount(o) == rc); -#endif - PUSH_POOL { - testassert(_objc_rootRetainCount(o) == rc); - _objc_rootAutorelease(o); - testassert(_objc_rootRetainCount(o) == rc); -#if !__has_feature(objc_arc) - [o autorelease]; - testassert(_objc_rootRetainCount(o) == rc); - objc_autorelease(o); - testassert(_objc_rootRetainCount(o) == rc); - objc_retainAutorelease(o); - testassert(_objc_rootRetainCount(o) == rc); - objc_autoreleaseReturnValue(o); - testassert(_objc_rootRetainCount(o) == rc); - objc_retainAutoreleaseReturnValue(o); - testassert(_objc_rootRetainCount(o) == rc); - objc_retainAutoreleasedReturnValue(o); - testassert(_objc_rootRetainCount(o) == rc); -#endif - } POP_POOL; - testassert(_objc_rootRetainCount(o) == rc); - } - } - leak_check(0); - for (uintptr_t i = 0; i < 10000; i++) { - testassert(w->weaks[i] != NULL); - WEAK_STORE(w->weaks[i], NULL); - testassert(w->weaks[i] == NULL); - testassert(WEAK_LOAD(w->weaks[i]) == NULL); - } - RELEASE_VAR(w); -} - -int main() -{ - if (objc_collecting_enabled()) { - // GC's block objects crash without this - dlopen("/System/Library/Frameworks/Foundation.framework/Foundation", RTLD_LAZY); - } - - testassert(objc_debug_taggedpointer_mask != 0); - testassert(_objc_taggedPointersEnabled()); - - PUSH_POOL { - // Avoid CF's tagged pointer tags because of rdar://11368528 - - _objc_registerTaggedPointerClass(OBJC_TAG_1, - objc_getClass("TaggedBaseClass")); - testGenericTaggedPointer(OBJC_TAG_1, - objc_getClass("TaggedBaseClass")); - - _objc_registerTaggedPointerClass(OBJC_TAG_7, - objc_getClass("TaggedSubclass")); - testGenericTaggedPointer(OBJC_TAG_7, - objc_getClass("TaggedSubclass")); - - _objc_registerTaggedPointerClass(OBJC_TAG_NSManagedObjectID, - objc_getClass("TaggedNSObjectSubclass")); - testGenericTaggedPointer(OBJC_TAG_NSManagedObjectID, - objc_getClass("TaggedNSObjectSubclass")); - } POP_POOL; - - succeed(__FILE__); -} - -// OBJC_HAVE_TAGGED_POINTERS -#else -// not OBJC_HAVE_TAGGED_POINTERS - -// Tagged pointers not supported. - -int main() -{ -#if __OBJC2__ - testassert(objc_debug_taggedpointer_mask == 0); -#else - testassert(!dlsym(RTLD_DEFAULT, "objc_debug_taggedpointer_mask")); -#endif - - succeed(__FILE__); -} - -#endif diff --git a/test/taggedPointersDisabled.m b/test/taggedPointersDisabled.m deleted file mode 100644 index 5e1ce59..0000000 --- a/test/taggedPointersDisabled.m +++ /dev/null @@ -1,31 +0,0 @@ -// TEST_ENV OBJC_DISABLE_TAGGED_POINTERS=YES -// TEST_CRASHES -/* -TEST_RUN_OUTPUT -objc\[\d+\]: tagged pointers are disabled -CRASHED: SIG(ILL|TRAP) -OR -OK: taggedPointersDisabled.m -END -*/ - -#include "test.h" -#include - -#if !OBJC_HAVE_TAGGED_POINTERS - -int main() -{ - succeed(__FILE__); -} - -#else - -int main() -{ - testassert(!_objc_taggedPointersEnabled()); - _objc_registerTaggedPointerClass((objc_tag_index_t)0, nil); - fail("should have crashed in _objc_registerTaggedPointerClass()"); -} - -#endif diff --git a/test/tbi.c b/test/tbi.c deleted file mode 100644 index 93c2022..0000000 --- a/test/tbi.c +++ /dev/null @@ -1,14 +0,0 @@ -// TEST_CONFIG OS=iphoneos ARCH=arm64 - -#include "test.h" - -#ifndef __arm64__ -#error wrong architecture for TBI hardware feature -#endif - -volatile int x = 123456; - -int main(void) { - testassert(*(int *)((unsigned long)&x | 0xFF00000000000000ul) == 123456); - succeed(__FILE__); -} diff --git a/test/test.h b/test/test.h deleted file mode 100644 index 232b04b..0000000 --- a/test/test.h +++ /dev/null @@ -1,475 +0,0 @@ -// test.h -// Common definitions for trivial test harness - - -#ifndef TEST_H -#define TEST_H - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#if TARGET_OS_EMBEDDED || TARGET_IPHONE_SIMULATOR -static OBJC_INLINE malloc_zone_t *objc_collectableZone(void) { return nil; } -#endif - - -// Configuration macros - -#if !__LP64__ || TARGET_OS_WIN32 || __OBJC_GC__ || TARGET_IPHONE_SIMULATOR -# define SUPPORT_NONPOINTER_ISA 0 -#elif __x86_64__ -# define SUPPORT_NONPOINTER_ISA 1 -#elif __arm64__ -# define SUPPORT_NONPOINTER_ISA 1 -#else -# error unknown architecture -#endif - - -// Test output - -static inline void succeed(const char *name) __attribute__((noreturn)); -static inline void succeed(const char *name) -{ - if (name) { - char path[MAXPATHLEN+1]; - strcpy(path, name); - fprintf(stderr, "OK: %s\n", basename(path)); - } else { - fprintf(stderr, "OK\n"); - } - exit(0); -} - -static inline void fail(const char *msg, ...) __attribute__((noreturn)); -static inline void fail(const char *msg, ...) -{ - if (msg) { - char *msg2; - asprintf(&msg2, "BAD: %s\n", msg); - va_list v; - va_start(v, msg); - vfprintf(stderr, msg2, v); - va_end(v); - free(msg2); - } else { - fprintf(stderr, "BAD\n"); - } - exit(1); -} - -#define testassert(cond) \ - ((void) (((cond) != 0) ? (void)0 : __testassert(#cond, __FILE__, __LINE__))) -#define __testassert(cond, file, line) \ - (fail("failed assertion '%s' at %s:%u", cond, __FILE__, __LINE__)) - -/* time-sensitive assertion, disabled under valgrind */ -#define timecheck(name, time, fast, slow) \ - if (getenv("VALGRIND") && 0 != strcmp(getenv("VALGRIND"), "NO")) { \ - /* valgrind; do nothing */ \ - } else if (time > slow) { \ - fprintf(stderr, "SLOW: %s %llu, expected %llu..%llu\n", \ - name, (uint64_t)(time), (uint64_t)(fast), (uint64_t)(slow)); \ - } else if (time < fast) { \ - fprintf(stderr, "FAST: %s %llu, expected %llu..%llu\n", \ - name, (uint64_t)(time), (uint64_t)(fast), (uint64_t)(slow)); \ - } else { \ - testprintf("time: %s %llu, expected %llu..%llu\n", \ - name, (uint64_t)(time), (uint64_t)(fast), (uint64_t)(slow)); \ - } - - -static inline void testprintf(const char *msg, ...) -{ - static int verbose = -1; - if (verbose < 0) verbose = atoi(getenv("VERBOSE") ?: "0"); - - // VERBOSE=1 prints test harness info only - if (msg && verbose >= 2) { - char *msg2; - asprintf(&msg2, "VERBOSE: %s", msg); - va_list v; - va_start(v, msg); - vfprintf(stderr, msg2, v); - va_end(v); - free(msg2); - } -} - -// complain to output, but don't fail the test -// Use when warning that some test is being temporarily skipped -// because of something like a compiler bug. -static inline void testwarn(const char *msg, ...) -{ - if (msg) { - char *msg2; - asprintf(&msg2, "WARN: %s\n", msg); - va_list v; - va_start(v, msg); - vfprintf(stderr, msg2, v); - va_end(v); - free(msg2); - } -} - -static inline void testnoop() { } - -// Run GC. This is a macro to reach as high in the stack as possible. -#ifndef OBJC_NO_GC - -# if __OBJC2__ -# define testexc() -# else -# include -# define testexc() \ - do { \ - objc_exception_functions_t table = {0,0,0,0,0,0}; \ - objc_exception_get_functions(&table); \ - if (!table.throw_exc) { \ - table.throw_exc = (typeof(table.throw_exc))abort; \ - table.try_enter = (typeof(table.try_enter))testnoop; \ - table.try_exit = (typeof(table.try_exit))testnoop; \ - table.extract = (typeof(table.extract))abort; \ - table.match = (typeof(table.match))abort; \ - objc_exception_set_functions(&table); \ - } \ - } while (0) -# endif - -# define testcollect() \ - do { \ - if (objc_collectingEnabled()) { \ - testexc(); \ - objc_clear_stack(0); \ - objc_collect(OBJC_COLLECT_IF_NEEDED|OBJC_WAIT_UNTIL_DONE); \ - objc_collect(OBJC_EXHAUSTIVE_COLLECTION|OBJC_WAIT_UNTIL_DONE);\ - objc_collect(OBJC_EXHAUSTIVE_COLLECTION|OBJC_WAIT_UNTIL_DONE);\ - } \ - _objc_flush_caches(NULL); \ - } while (0) - -#else - -# define testcollect() \ - do { \ - _objc_flush_caches(NULL); \ - } while (0) - -#endif - - -// Synchronously run test code on another thread. -// This can help force GC to kill objects promptly, which some tests depend on. - -// The block object is unsafe_unretained because we must not allow -// ARC to retain them in non-Foundation tests -typedef void(^testblock_t)(void); -static __unsafe_unretained testblock_t testcodehack; -static inline void *_testthread(void *arg __unused) -{ - objc_registerThreadWithCollector(); - testcodehack(); - return NULL; -} -static inline void testonthread(__unsafe_unretained testblock_t code) -{ - // GC crashes without Foundation because the block object classes - // are insufficiently initialized. - if (objc_collectingEnabled()) { - static bool foundationified = false; - if (!foundationified) { - dlopen("/System/Library/Frameworks/Foundation.framework/Foundation", RTLD_LAZY); - foundationified = true; - } - } - - pthread_t th; - testcodehack = code; // force GC not-thread-local, avoid ARC void* casts - pthread_create(&th, NULL, _testthread, NULL); - pthread_join(th, NULL); -} - -/* Make sure libobjc does not call global operator new. - Any test that DOES need to call global operator new must - `#define TEST_CALLS_OPERATOR_NEW` before including test.h. - */ -#if __cplusplus && !defined(TEST_CALLS_OPERATOR_NEW) -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Winline-new-delete" -#import -inline void* operator new(std::size_t) throw (std::bad_alloc) { fail("called global operator new"); } -inline void* operator new[](std::size_t) throw (std::bad_alloc) { fail("called global operator new[]"); } -inline void* operator new(std::size_t, const std::nothrow_t&) throw() { fail("called global operator new(nothrow)"); } -inline void* operator new[](std::size_t, const std::nothrow_t&) throw() { fail("called global operator new[](nothrow)"); } -inline void operator delete(void*) throw() { fail("called global operator delete"); } -inline void operator delete[](void*) throw() { fail("called global operator delete[]"); } -inline void operator delete(void*, const std::nothrow_t&) throw() { fail("called global operator delete(nothrow)"); } -inline void operator delete[](void*, const std::nothrow_t&) throw() { fail("called global operator delete[](nothrow)"); } -#pragma clang diagnostic pop -#endif - - -/* Leak checking - Fails if total malloc memory in use at leak_check(n) - is more than n bytes above that at leak_mark(). -*/ - -static inline void leak_recorder(task_t task __unused, void *ctx, unsigned type __unused, vm_range_t *ranges, unsigned count) -{ - size_t *inuse = (size_t *)ctx; - while (count--) { - *inuse += ranges[count].size; - } -} - -static inline size_t leak_inuse(void) -{ - size_t total = 0; - vm_address_t *zones; - unsigned count; - malloc_get_all_zones(mach_task_self(), NULL, &zones, &count); - for (unsigned i = 0; i < count; i++) { - size_t inuse = 0; - malloc_zone_t *zone = (malloc_zone_t *)zones[i]; - if (!zone->introspect || !zone->introspect->enumerator) continue; - - // skip DispatchContinuations because it sometimes claims to be - // using lots of memory that then goes away later - if (0 == strcmp(zone->zone_name, "DispatchContinuations")) continue; - - zone->introspect->enumerator(mach_task_self(), &inuse, MALLOC_PTR_IN_USE_RANGE_TYPE, (vm_address_t)zone, NULL, leak_recorder); - // fprintf(stderr, "%zu in use for zone %s\n", inuse, zone->zone_name); - total += inuse; - } - - return total; -} - - -static inline void leak_dump_heap(const char *msg) -{ - fprintf(stderr, "%s\n", msg); - - // Make `heap` write to stderr - int outfd = dup(STDOUT_FILENO); - dup2(STDERR_FILENO, STDOUT_FILENO); - pid_t pid = getpid(); - char cmd[256]; - // environment variables reset for iOS simulator use - sprintf(cmd, "DYLD_LIBRARY_PATH= DYLD_ROOT_PATH= /usr/bin/heap -addresses all %d", (int)pid); - - system(cmd); - - dup2(outfd, STDOUT_FILENO); - close(outfd); -} - -static size_t _leak_start; -static inline void leak_mark(void) -{ - testcollect(); - if (getenv("LEAK_HEAP")) { - leak_dump_heap("HEAP AT leak_mark"); - } - _leak_start = leak_inuse(); -} - -#define leak_check(n) \ - do { \ - const char *_check = getenv("LEAK_CHECK"); \ - size_t inuse; \ - if (_check && 0 == strcmp(_check, "NO")) break; \ - testcollect(); \ - if (getenv("LEAK_HEAP")) { \ - leak_dump_heap("HEAP AT leak_check"); \ - } \ - inuse = leak_inuse(); \ - if (inuse > _leak_start + n) { \ - if (getenv("HANG_ON_LEAK")) { \ - printf("leaks %d\n", getpid()); \ - while (1) sleep(1); \ - } \ - fprintf(stderr, "BAD: %zu bytes leaked at %s:%u\n", \ - inuse - _leak_start, __FILE__, __LINE__); \ - } \ - } while (0) - -static inline bool is_guardmalloc(void) -{ - const char *env = getenv("GUARDMALLOC"); - return (env && 0 == strcmp(env, "YES")); -} - - -/* Memory management compatibility macros */ - -static id self_fn(id x) __attribute__((used)); -static id self_fn(id x) { return x; } - -#if __has_feature(objc_arc) - // ARC -# define RELEASE_VAR(x) x = nil -# define WEAK_STORE(dst, val) (dst = (val)) -# define WEAK_LOAD(src) (src) -# define SUPER_DEALLOC() -# define RETAIN(x) (self_fn(x)) -# define RELEASE_VALUE(x) ((void)self_fn(x)) -# define AUTORELEASE(x) (self_fn(x)) - -#elif defined(__OBJC_GC__) - // GC -# define RELEASE_VAR(x) x = nil -# define WEAK_STORE(dst, val) (dst = (val)) -# define WEAK_LOAD(src) (src) -# define SUPER_DEALLOC() [super dealloc] -# define RETAIN(x) [x self] -# define RELEASE_VALUE(x) (void)[x self] -# define AUTORELEASE(x) [x self] - -#else - // MRC -# define RELEASE_VAR(x) do { [x release]; x = nil; } while (0) -# define WEAK_STORE(dst, val) objc_storeWeak((id *)&dst, val) -# define WEAK_LOAD(src) objc_loadWeak((id *)&src) -# define SUPER_DEALLOC() [super dealloc] -# define RETAIN(x) [x retain] -# define RELEASE_VALUE(x) [x release] -# define AUTORELEASE(x) [x autorelease] -#endif - -/* gcc compatibility macros */ -/* @autoreleasepool should generate objc_autoreleasePoolPush/Pop on 10.7/5.0 */ -//#if !defined(__clang__) -# define PUSH_POOL { void *pool = objc_autoreleasePoolPush(); -# define POP_POOL objc_autoreleasePoolPop(pool); } -//#else -//# define PUSH_POOL @autoreleasepool -//# define POP_POOL -//#endif - -#if __OBJC__ - -/* General purpose root class */ - -OBJC_ROOT_CLASS -@interface TestRoot { - @public - Class isa; -} - -+(void) load; -+(void) initialize; - --(id) self; --(Class) class; --(Class) superclass; - -+(id) new; -+(id) alloc; -+(id) allocWithZone:(void*)zone; --(id) copy; --(id) mutableCopy; --(id) init; --(void) dealloc; --(void) finalize; -@end -@interface TestRoot (RR) --(id) retain; --(oneway void) release; --(id) autorelease; --(unsigned long) retainCount; --(id) copyWithZone:(void *)zone; --(id) mutableCopyWithZone:(void*)zone; -@end - -// incremented for each call of TestRoot's methods -extern int TestRootLoad; -extern int TestRootInitialize; -extern int TestRootAlloc; -extern int TestRootAllocWithZone; -extern int TestRootCopy; -extern int TestRootCopyWithZone; -extern int TestRootMutableCopy; -extern int TestRootMutableCopyWithZone; -extern int TestRootInit; -extern int TestRootDealloc; -extern int TestRootFinalize; -extern int TestRootRetain; -extern int TestRootRelease; -extern int TestRootAutorelease; -extern int TestRootRetainCount; -extern int TestRootTryRetain; -extern int TestRootIsDeallocating; -extern int TestRootPlusRetain; -extern int TestRootPlusRelease; -extern int TestRootPlusAutorelease; -extern int TestRootPlusRetainCount; - -#endif - - -// Struct that does not return in registers on any architecture - -struct stret { - int a; - int b; - int c; - int d; - int e; - int f; - int g; - int h; - int i; - int j; -}; - -static inline BOOL stret_equal(struct stret a, struct stret b) -{ - return (a.a == b.a && - a.b == b.b && - a.c == b.c && - a.d == b.d && - a.e == b.e && - a.f == b.f && - a.g == b.g && - a.h == b.h && - a.i == b.i && - a.j == b.j); -} - -static struct stret STRET_RESULT __attribute__((used)) = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; - - -#if TARGET_IPHONE_SIMULATOR -// Force cwd to executable's directory during launch. -// sim used to do this but simctl does not. -#include - __attribute__((constructor)) -static void hack_cwd(void) -{ - if (!getenv("HACKED_CWD")) { - chdir(dirname((*_NSGetArgv())[0])); - setenv("HACKED_CWD", "1", 1); - } -} -#endif - -#endif diff --git a/test/test.pl b/test/test.pl deleted file mode 100755 index dda6bfa..0000000 --- a/test/test.pl +++ /dev/null @@ -1,1477 +0,0 @@ -#!/usr/bin/perl - -# test.pl -# Run unit tests. - -use strict; -use File::Basename; - -chdir dirname $0; -chomp (my $DIR = `pwd`); - -my $TESTLIBNAME = "libobjc.A.dylib"; -my $TESTLIBPATH = "/usr/lib/$TESTLIBNAME"; - -my $BUILDDIR = "/tmp/test-$TESTLIBNAME-build"; - -# xterm colors -my $red = "\e[41;37m"; -my $yellow = "\e[43;30m"; -my $nocolor = "\e[0m"; - -# clean, help -if (scalar(@ARGV) == 1) { - my $arg = $ARGV[0]; - if ($arg eq "clean") { - my $cmd = "rm -rf $BUILDDIR *~"; - print "$cmd\n"; - `$cmd`; - exit 0; - } - elsif ($arg eq "-h" || $arg eq "-H" || $arg eq "-help" || $arg eq "help") { - print(< - OS=[sdk version][-[-]] - ROOT=/path/to/project.roots/ - - CC= - - LANGUAGE=c,c++,objective-c,objective-c++,swift - MEM=mrc,arc,gc - STDLIB=libc++,libstdc++ - GUARDMALLOC=0|1|before|after - - BUILD=0|1 - RUN=0|1 - VERBOSE=0|1|2 - -examples: - - test installed library, x86_64, no gc - $0 - - test buildit-built root, i386 and x86_64, MRC and ARC and GC, clang compiler - $0 ARCH=i386,x86_64 ROOT=/tmp/libclosure.roots MEM=mrc,arc,gc CC=clang - - test buildit-built root with iOS simulator, deploy to iOS 7, run on iOS 8 - $0 ARCH=i386 ROOT=/tmp/libclosure.roots OS=iphonesimulator-7.0-8.0 - - test buildit-built root on attached iOS device - $0 ARCH=armv7 ROOT=/tmp/libclosure.roots OS=iphoneos -END - exit 0; - } -} - -######################################################################### -## Tests - -my %ALL_TESTS; - -######################################################################### -## Variables for use in complex build and run rules - -# variable # example value - -# things you can multiplex on the command line -# ARCH=i386,x86_64,armv6,armv7 -# OS=macosx,iphoneos,iphonesimulator (plus sdk/deployment/run versions) -# LANGUAGE=c,c++,objective-c,objective-c++,swift -# CC=clang,gcc-4.2,llvm-gcc-4.2 -# MEM=mrc,arc,gc -# STDLIB=libc++,libstdc++ -# GUARDMALLOC=0,1,before,after - -# things you can set once on the command line -# ROOT=/path/to/project.roots -# BUILD=0|1 -# RUN=0|1 -# VERBOSE=0|1|2 - - - -my $BUILD; -my $RUN; -my $VERBOSE; - -my $crashcatch = <<'END'; -// interpose-able code to catch crashes, print, and exit cleanly -#include -#include -#include - -// from dyld-interposing.h -#define DYLD_INTERPOSE(_replacement,_replacee) __attribute__((used)) static struct{ const void* replacement; const void* replacee; } _interpose_##_replacee __attribute__ ((section ("__DATA,__interpose"))) = { (const void*)(unsigned long)&_replacement, (const void*)(unsigned long)&_replacee }; - -static void catchcrash(int sig) -{ - const char *msg; - switch (sig) { - case SIGILL: msg = "CRASHED: SIGILL\\n"; break; - case SIGBUS: msg = "CRASHED: SIGBUS\\n"; break; - case SIGSYS: msg = "CRASHED: SIGSYS\\n"; break; - case SIGSEGV: msg = "CRASHED: SIGSEGV\\n"; break; - case SIGTRAP: msg = "CRASHED: SIGTRAP\\n"; break; - case SIGABRT: msg = "CRASHED: SIGABRT\\n"; break; - default: msg = "SIG\?\?\?\?\\n"; break; - } - write(STDERR_FILENO, msg, strlen(msg)); - _exit(0); -} - -static void setupcrash(void) __attribute__((constructor)); -static void setupcrash(void) -{ - signal(SIGILL, &catchcrash); - signal(SIGBUS, &catchcrash); - signal(SIGSYS, &catchcrash); - signal(SIGSEGV, &catchcrash); - signal(SIGTRAP, &catchcrash); - signal(SIGABRT, &catchcrash); -} - - -static int hacked = 0; -ssize_t hacked_write(int fildes, const void *buf, size_t nbyte) -{ - if (!hacked) { - setupcrash(); - hacked = 1; - } - return write(fildes, buf, nbyte); -} - -DYLD_INTERPOSE(hacked_write, write); - -END - - -######################################################################### -## Harness - - -# map language to buildable extensions for that language -my %extensions_for_language = ( - "c" => ["c"], - "objective-c" => ["c", "m"], - "c++" => ["c", "cc", "cp", "cpp", "cxx", "c++"], - "objective-c++" => ["c", "m", "cc", "cp", "cpp", "cxx", "c++", "mm"], - "swift" => ["swift"], - - "any" => ["c", "m", "cc", "cp", "cpp", "cxx", "c++", "mm", "swift"], - ); - -# map extension to languages -my %languages_for_extension = ( - "c" => ["c", "objective-c", "c++", "objective-c++"], - "m" => ["objective-c", "objective-c++"], - "mm" => ["objective-c++"], - "cc" => ["c++", "objective-c++"], - "cp" => ["c++", "objective-c++"], - "cpp" => ["c++", "objective-c++"], - "cxx" => ["c++", "objective-c++"], - "c++" => ["c++", "objective-c++"], - "swift" => ["swift"], - ); - -# Run some newline-separated commands like `make` would, stopping if any fail -# run("cmd1 \n cmd2 \n cmd3") -sub make { - my $output = ""; - my @cmds = split("\n", $_[0]); - die if scalar(@cmds) == 0; - $? = 0; - foreach my $cmd (@cmds) { - chomp $cmd; - next if $cmd =~ /^\s*$/; - $cmd .= " 2>&1"; - print "$cmd\n" if $VERBOSE; - $output .= `$cmd`; - last if $?; - } - print "$output\n" if $VERBOSE; - return $output; -} - -sub chdir_verbose { - my $dir = shift; - print "cd $dir\n" if $VERBOSE; - chdir $dir || die; -} - - -# Return test names from the command line. -# Returns all tests if no tests were named. -sub gettests { - my @tests; - - foreach my $arg (@ARGV) { - push @tests, $arg if ($arg !~ /=/ && $arg !~ /^-/); - } - - opendir(my $dir, $DIR) || die; - while (my $file = readdir($dir)) { - my ($name, $ext) = ($file =~ /^([^.]+)\.([^.]+)$/); - next if ! $languages_for_extension{$ext}; - - open(my $in, "< $file") || die "$file"; - my $contents = join "", <$in>; - if (defined $ALL_TESTS{$name}) { - print "${yellow}SKIP: multiple tests named '$name'; skipping file '$file'.${nocolor}\n"; - } else { - $ALL_TESTS{$name} = $ext if ($contents =~ m#^[/*\s]*TEST_#m); - } - close($in); - } - closedir($dir); - - if (scalar(@tests) == 0) { - @tests = keys %ALL_TESTS; - } - - @tests = sort @tests; - - return @tests; -} - - -# Turn a C compiler name into a C++ compiler name. -sub cplusplus { - my ($c) = @_; - if ($c =~ /cc/) { - $c =~ s/cc/\+\+/; - return $c; - } - return $c . "++"; # e.g. clang => clang++ -} - -# Turn a C compiler name into a Swift compiler name -sub swift { - my ($c) = @_; - $c =~ s#[^/]*$#swift#; - return $c; -} - -# Returns an array of all sdks from `xcodebuild -showsdks` -my @sdks_memo; -sub getsdks { - if (!@sdks_memo) { - @sdks_memo = (`xcodebuild -showsdks` =~ /-sdk (.+)$/mg); - } - return @sdks_memo; -} - -my %sdk_path_memo = {}; -sub getsdkpath { - my ($sdk) = @_; - if (!defined $sdk_path_memo{$sdk}) { - ($sdk_path_memo{$sdk}) = (`xcodebuild -version -sdk '$sdk' Path` =~ /^\s*(.+?)\s*$/); - } - return $sdk_path_memo{$sdk}; -} - -# Extract a version number from a string. -# Ignore trailing "internal". -sub versionsuffix { - my ($str) = @_; - my ($vers) = ($str =~ /([0-9]+\.[0-9]+)(?:\.?internal)?$/); - return $vers; -} -sub majorversionsuffix { - my ($str) = @_; - my ($vers) = ($str =~ /([0-9]+)\.[0-9]+(?:\.?internal)?$/); - return $vers; -} -sub minorversionsuffix { - my ($str) = @_; - my ($vers) = ($str =~ /[0-9]+\.([0-9]+)(?:\.?internal)?$/); - return $vers; -} - -# Compares two SDK names and returns the newer one. -# Assumes the two SDKs are the same OS. -sub newersdk { - my ($lhs, $rhs) = @_; - - # Major version wins. - my $lhsMajor = majorversionsuffix($lhs); - my $rhsMajor = majorversionsuffix($rhs); - if ($lhsMajor > $rhsMajor) { return $lhs; } - if ($lhsMajor < $rhsMajor) { return $rhs; } - - # Minor version wins. - my $lhsMinor = minorversionsuffix($lhs); - my $rhsMinor = minorversionsuffix($rhs); - if ($lhsMinor > $rhsMinor) { return $lhs; } - if ($lhsMinor < $rhsMinor) { return $rhs; } - - # Lexically-last wins (i.e. internal is better than not internal) - if ($lhs gt $rhs) { return $lhs; } - return $rhs; -} - -# Returns whether the given sdk supports -lauto -sub supportslibauto { - my ($sdk) = @_; - return 1 if $sdk =~ /^macosx/; - return 0; -} - -# print text with a colored prefix on each line -sub colorprint { - my $color = shift; - while (my @lines = split("\n", shift)) { - for my $line (@lines) { - chomp $line; - print "$color $nocolor$line\n"; - } - } -} - -sub rewind { - seek($_[0], 0, 0); -} - -# parse name=value,value pairs -sub readconditions { - my ($conditionstring) = @_; - - my %results; - my @conditions = ($conditionstring =~ /\w+=(?:[^\s,]+,?)+/g); - for my $condition (@conditions) { - my ($name, $values) = ($condition =~ /(\w+)=(.+)/); - $results{$name} = [split ',', $values]; - } - - return %results; -} - -sub check_output { - my %C = %{shift()}; - my $name = shift; - my @output = @_; - - my %T = %{$C{"TEST_$name"}}; - - # Quietly strip MallocScribble before saving the "original" output - # because it is distracting. - filter_malloc(\@output); - - my @original_output = @output; - - # Run result-checking passes, reducing @output each time - my $xit = 1; - my $bad = ""; - my $warn = ""; - my $runerror = $T{TEST_RUN_OUTPUT}; - filter_hax(\@output); - filter_verbose(\@output); - filter_simulator(\@output); - $warn = filter_warn(\@output); - $bad |= filter_guardmalloc(\@output) if ($C{GUARDMALLOC}); - $bad |= filter_valgrind(\@output) if ($C{VALGRIND}); - $bad = filter_expected(\@output, \%C, $name) if ($bad eq ""); - $bad = filter_bad(\@output) if ($bad eq ""); - - # OK line should be the only one left - $bad = "(output not 'OK: $name')" if ($bad eq "" && (scalar(@output) != 1 || $output[0] !~ /^OK: $name/)); - - if ($bad ne "") { - print "${red}FAIL: /// test '$name' \\\\\\$nocolor\n"; - colorprint($red, @original_output); - print "${red}FAIL: \\\\\\ test '$name' ///$nocolor\n"; - print "${red}FAIL: $name: $bad$nocolor\n"; - $xit = 0; - } - elsif ($warn ne "") { - print "${yellow}PASS: /// test '$name' \\\\\\$nocolor\n"; - colorprint($yellow, @original_output); - print "${yellow}PASS: \\\\\\ test '$name' ///$nocolor\n"; - print "PASS: $name (with warnings)\n"; - } - else { - print "PASS: $name\n"; - } - return $xit; -} - -sub filter_expected -{ - my $outputref = shift; - my %C = %{shift()}; - my $name = shift; - - my %T = %{$C{"TEST_$name"}}; - my $runerror = $T{TEST_RUN_OUTPUT} || return ""; - - my $bad = ""; - - my $output = join("\n", @$outputref) . "\n"; - if ($output !~ /$runerror/) { - $bad = "(run output does not match TEST_RUN_OUTPUT)"; - @$outputref = ("FAIL: $name"); - } else { - @$outputref = ("OK: $name"); # pacify later filter - } - - return $bad; -} - -sub filter_bad -{ - my $outputref = shift; - my $bad = ""; - - my @new_output; - for my $line (@$outputref) { - if ($line =~ /^BAD: (.*)/) { - $bad = "(failed)"; - } else { - push @new_output, $line; - } - } - - @$outputref = @new_output; - return $bad; -} - -sub filter_warn -{ - my $outputref = shift; - my $warn = ""; - - my @new_output; - for my $line (@$outputref) { - if ($line !~ /^WARN: (.*)/) { - push @new_output, $line; - } else { - $warn = "(warned)"; - } - } - - @$outputref = @new_output; - return $warn; -} - -sub filter_verbose -{ - my $outputref = shift; - - my @new_output; - for my $line (@$outputref) { - if ($line !~ /^VERBOSE: (.*)/) { - push @new_output, $line; - } - } - - @$outputref = @new_output; -} - -sub filter_simulator -{ - my $outputref = shift; - - my @new_output; - for my $line (@$outputref) { - if ($line !~ /No simulator devices appear to be running/) { - push @new_output, $line; - } - } - - @$outputref = @new_output; -} - -sub filter_simulator -{ - my $outputref = shift; - - my @new_output; - for my $line (@$outputref) { - if ($line !~ /No simulator devices appear to be running/) { - push @new_output, $line; - } - } - - @$outputref = @new_output; -} - -sub filter_hax -{ - my $outputref = shift; - - my @new_output; - for my $line (@$outputref) { - if ($line !~ /Class OS_tcp_/) { - push @new_output, $line; - } - } - - @$outputref = @new_output; -} - -sub filter_valgrind -{ - my $outputref = shift; - my $errors = 0; - my $leaks = 0; - - my @new_output; - for my $line (@$outputref) { - if ($line =~ /^Approx: do_origins_Dirty\([RW]\): missed \d bytes$/) { - # --track-origins warning (harmless) - next; - } - if ($line =~ /^UNKNOWN __disable_threadsignal is unsupported. This warning will not be repeated.$/) { - # signals unsupported (harmless) - next; - } - if ($line =~ /^UNKNOWN __pthread_sigmask is unsupported. This warning will not be repeated.$/) { - # signals unsupported (harmless) - next; - } - if ($line !~ /^^\.*==\d+==/) { - # not valgrind output - push @new_output, $line; - next; - } - - my ($errcount) = ($line =~ /==\d+== ERROR SUMMARY: (\d+) errors/); - if (defined $errcount && $errcount > 0) { - $errors = 1; - } - - (my $leakcount) = ($line =~ /==\d+==\s+(?:definitely|possibly) lost:\s+([0-9,]+)/); - if (defined $leakcount && $leakcount > 0) { - $leaks = 1; - } - } - - @$outputref = @new_output; - - my $bad = ""; - $bad .= "(valgrind errors)" if ($errors); - $bad .= "(valgrind leaks)" if ($leaks); - return $bad; -} - - - -sub filter_malloc -{ - my $outputref = shift; - my $errors = 0; - - my @new_output; - my $count = 0; - for my $line (@$outputref) { - # Ignore MallocScribble prologue. - # Ignore MallocStackLogging prologue. - if ($line =~ /malloc: enabling scribbling to detect mods to free/ || - $line =~ /Deleted objects will be dirtied by the collector/ || - $line =~ /malloc: stack logs being written into/ || - $line =~ /malloc: stack logs deleted from/ || - $line =~ /malloc: process \d+ no longer exists/ || - $line =~ /malloc: recording malloc and VM allocation stacks/) - { - next; - } - - # not malloc output - push @new_output, $line; - - } - - @$outputref = @new_output; -} - -sub filter_guardmalloc -{ - my $outputref = shift; - my $errors = 0; - - my @new_output; - my $count = 0; - for my $line (@$outputref) { - if ($line !~ /^GuardMalloc\[[^\]]+\]: /) { - # not guardmalloc output - push @new_output, $line; - next; - } - - # Ignore 4 lines of guardmalloc prologue. - # Anything further is a guardmalloc error. - if (++$count > 4) { - $errors = 1; - } - } - - @$outputref = @new_output; - - my $bad = ""; - $bad .= "(guardmalloc errors)" if ($errors); - return $bad; -} - -# TEST_SOMETHING -# text -# text -# END -sub extract_multiline { - my ($flag, $contents, $name) = @_; - if ($contents =~ /$flag\n/) { - my ($output) = ($contents =~ /$flag\n(.*?\n)END[ *\/]*\n/s); - die "$name used $flag without END\n" if !defined($output); - return $output; - } - return undef; -} - - -# TEST_SOMETHING -# text -# OR -# text -# END -sub extract_multiple_multiline { - my ($flag, $contents, $name) = @_; - if ($contents =~ /$flag\n/) { - my ($output) = ($contents =~ /$flag\n(.*?\n)END[ *\/]*\n/s); - die "$name used $flag without END\n" if !defined($output); - - $output =~ s/\nOR\n/\n|/sg; - $output = "^(" . $output . ")\$"; - return $output; - } - return undef; -} - - -sub gather_simple { - my $CREF = shift; - my %C = %{$CREF}; - my $name = shift; - chdir_verbose $DIR; - - my $ext = $ALL_TESTS{$name}; - my $file = "$name.$ext"; - return 0 if !$file; - - # search file for 'TEST_CONFIG' or '#include "test.h"' - # also collect other values: - # TEST_DISABLED disable test with an optional message - # TEST_CRASHES test is expected to crash - # TEST_CONFIG test conditions - # TEST_ENV environment prefix - # TEST_CFLAGS compile flags - # TEST_BUILD build instructions - # TEST_BUILD_OUTPUT expected build stdout/stderr - # TEST_RUN_OUTPUT expected run stdout/stderr - open(my $in, "< $file") || die; - my $contents = join "", <$in>; - - my $test_h = ($contents =~ /^\s*#\s*(include|import)\s*"test\.h"/m); - my ($disabled) = ($contents =~ /\b(TEST_DISABLED\b.*)$/m); - my $crashes = ($contents =~ /\bTEST_CRASHES\b/m); - my ($conditionstring) = ($contents =~ /\bTEST_CONFIG\b(.*)$/m); - my ($envstring) = ($contents =~ /\bTEST_ENV\b(.*)$/m); - my ($cflags) = ($contents =~ /\bTEST_CFLAGS\b(.*)$/m); - my ($buildcmd) = extract_multiline("TEST_BUILD", $contents, $name); - my ($builderror) = extract_multiple_multiline("TEST_BUILD_OUTPUT", $contents, $name); - my ($runerror) = extract_multiple_multiline("TEST_RUN_OUTPUT", $contents, $name); - - return 0 if !$test_h && !$disabled && !$crashes && !defined($conditionstring) && !defined($envstring) && !defined($cflags) && !defined($buildcmd) && !defined($builderror) && !defined($runerror); - - if ($disabled) { - print "${yellow}SKIP: $name (disabled by $disabled)$nocolor\n"; - return 0; - } - - # check test conditions - - my $run = 1; - my %conditions = readconditions($conditionstring); - if (! $conditions{LANGUAGE}) { - # implicit language restriction from file extension - $conditions{LANGUAGE} = $languages_for_extension{$ext}; - } - for my $condkey (keys %conditions) { - my @condvalues = @{$conditions{$condkey}}; - - # special case: RUN=0 does not affect build - if ($condkey eq "RUN" && @condvalues == 1 && $condvalues[0] == 0) { - $run = 0; - next; - } - - my $testvalue = $C{$condkey}; - next if !defined($testvalue); - # testvalue is the configuration being run now - # condvalues are the allowed values for this test - - my $ok = 0; - for my $condvalue (@condvalues) { - - # special case: objc and objc++ - if ($condkey eq "LANGUAGE") { - $condvalue = "objective-c" if $condvalue eq "objc"; - $condvalue = "objective-c++" if $condvalue eq "objc++"; - } - - $ok = 1 if ($testvalue eq $condvalue); - - # special case: CC and CXX allow substring matches - if ($condkey eq "CC" || $condkey eq "CXX") { - $ok = 1 if ($testvalue =~ /$condvalue/); - } - - last if $ok; - } - - if (!$ok) { - my $plural = (@condvalues > 1) ? "one of: " : ""; - print "SKIP: $name ($condkey=$testvalue, but test requires $plural", join(' ', @condvalues), ")\n"; - return 0; - } - } - - # save some results for build and run phases - $$CREF{"TEST_$name"} = { - TEST_BUILD => $buildcmd, - TEST_BUILD_OUTPUT => $builderror, - TEST_CRASHES => $crashes, - TEST_RUN_OUTPUT => $runerror, - TEST_CFLAGS => $cflags, - TEST_ENV => $envstring, - TEST_RUN => $run, - }; - - return 1; -} - -# Builds a simple test -sub build_simple { - my %C = %{shift()}; - my $name = shift; - my %T = %{$C{"TEST_$name"}}; - chdir_verbose "$C{DIR}/$name.build"; - - my $ext = $ALL_TESTS{$name}; - my $file = "$DIR/$name.$ext"; - - if ($T{TEST_CRASHES}) { - `echo '$crashcatch' > crashcatch.c`; - make("$C{COMPILE_C} -dynamiclib -o libcrashcatch.dylib -x c crashcatch.c"); - die "$?" if $?; - } - - my $cmd = $T{TEST_BUILD} ? eval "return \"$T{TEST_BUILD}\"" : "$C{COMPILE} $T{TEST_CFLAGS} $file -o $name.out"; - - my $output = make($cmd); - - # rdar://10163155 - $output =~ s/ld: warning: could not create compact unwind for [^\n]+: does not use standard frame\n//g; - - my $ok; - if (my $builderror = $T{TEST_BUILD_OUTPUT}) { - # check for expected output and ignore $? - if ($output =~ /$builderror/) { - $ok = 1; - } else { - print "${red}FAIL: /// test '$name' \\\\\\$nocolor\n"; - colorprint $red, $output; - print "${red}FAIL: \\\\\\ test '$name' ///$nocolor\n"; - print "${red}FAIL: $name (build output does not match TEST_BUILD_OUTPUT)$nocolor\n"; - $ok = 0; - } - } elsif ($?) { - print "${red}FAIL: /// test '$name' \\\\\\$nocolor\n"; - colorprint $red, $output; - print "${red}FAIL: \\\\\\ test '$name' ///$nocolor\n"; - print "${red}FAIL: $name (build failed)$nocolor\n"; - $ok = 0; - } elsif ($output ne "") { - print "${red}FAIL: /// test '$name' \\\\\\$nocolor\n"; - colorprint $red, $output; - print "${red}FAIL: \\\\\\ test '$name' ///$nocolor\n"; - print "${red}FAIL: $name (unexpected build output)$nocolor\n"; - $ok = 0; - } else { - $ok = 1; - } - - - if ($ok) { - foreach my $file (glob("*.out *.dylib *.bundle")) { - make("dsymutil $file"); - } - } - - return $ok; -} - -# Run a simple test (testname.out, with error checking of stdout and stderr) -sub run_simple { - my %C = %{shift()}; - my $name = shift; - my %T = %{$C{"TEST_$name"}}; - - if (! $T{TEST_RUN}) { - print "PASS: $name (build only)\n"; - return 1; - } - - my $testdir = "$C{DIR}/$name.build"; - chdir_verbose $testdir; - - my $env = "$C{ENV} $T{TEST_ENV}"; - - my $output; - - if ($C{ARCH} =~ /^arm/ && `unamep -p` !~ /^arm/) { - # run on iOS or watchos device - - my $remotedir = "/var/root/objctest/" . basename($C{DIR}) . "/$name.build"; - - # Add test dir and libobjc's dir to DYLD_LIBRARY_PATH. - # Insert libcrashcatch.dylib if necessary. - $env .= " DYLD_LIBRARY_PATH=$remotedir"; - $env .= ":/var/root/objctest/" if ($C{TESTLIB} ne $TESTLIBPATH); - if ($T{TEST_CRASHES}) { - $env .= " DYLD_INSERT_LIBRARIES=$remotedir/libcrashcatch.dylib"; - } - - my $cmd = "ssh iphone 'cd $remotedir && env $env ./$name.out'"; - $output = make("$cmd"); - } - elsif ($C{OS} =~ /simulator/) { - # run locally in an iOS simulator - # fixme appletvsimulator and watchsimulator - # fixme SDK - my $sim = "xcrun -sdk iphonesimulator simctl spawn 'iPhone 6'"; - - # Add test dir and libobjc's dir to DYLD_LIBRARY_PATH. - # Insert libcrashcatch.dylib if necessary. - $env .= " DYLD_LIBRARY_PATH=$testdir"; - $env .= ":" . dirname($C{TESTLIB}) if ($C{TESTLIB} ne $TESTLIBPATH); - if ($T{TEST_CRASHES}) { - $env .= " DYLD_INSERT_LIBRARIES=$testdir/libcrashcatch.dylib"; - } - - my $simenv = ""; - foreach my $keyvalue (split(' ', $env)) { - $simenv .= "SIMCTL_CHILD_$keyvalue "; - } - # Use the full path here so hack_cwd in test.h works. - $output = make("env $simenv $sim $testdir/$name.out"); - } - else { - # run locally - - # Add test dir and libobjc's dir to DYLD_LIBRARY_PATH. - # Insert libcrashcatch.dylib if necessary. - $env .= " DYLD_LIBRARY_PATH=$testdir"; - $env .= ":" . dirname($C{TESTLIB}) if ($C{TESTLIB} ne $TESTLIBPATH); - if ($T{TEST_CRASHES}) { - $env .= " DYLD_INSERT_LIBRARIES=$testdir/libcrashcatch.dylib"; - } - - $output = make("sh -c '$env ./$name.out'"); - } - - return check_output(\%C, $name, split("\n", $output)); -} - - -my %compiler_memo; -sub find_compiler { - my ($cc, $toolchain, $sdk_path) = @_; - - # memoize - my $key = $cc . ':' . $toolchain; - my $result = $compiler_memo{$key}; - return $result if defined $result; - - $result = make("xcrun -toolchain $toolchain -find $cc 2>/dev/null"); - - chomp $result; - $compiler_memo{$key} = $result; - return $result; -} - -sub make_one_config { - my $configref = shift; - my $root = shift; - my %C = %{$configref}; - - # Aliases - $C{LANGUAGE} = "objective-c" if $C{LANGUAGE} eq "objc"; - $C{LANGUAGE} = "objective-c++" if $C{LANGUAGE} eq "objc++"; - - # Interpret OS version string from command line. - my ($sdk_arg, $deployment_arg, $run_arg, undef) = split('-', $C{OSVERSION}); - delete $C{OSVERSION}; - my ($os_arg) = ($sdk_arg =~ /^([^\.0-9]+)/); - $deployment_arg = "default" if !defined($deployment_arg); - $run_arg = "default" if !defined($run_arg); - - - die "unknown OS '$os_arg' (expected iphoneos or iphonesimulator or watchos or watchsimulator or macosx)\n" if ($os_arg ne "iphoneos" && $os_arg ne "iphonesimulator" && $os_arg ne "watchos" && $os_arg ne "watchsimulator" && $os_arg ne "macosx"); - - $C{OS} = $os_arg; - - if ($os_arg eq "iphoneos" || $os_arg eq "iphonesimulator") { - $C{TOOLCHAIN} = "ios"; - } elsif ($os_arg eq "watchos" || $os_arg eq "watchsimulator") { - $C{TOOLCHAIN} = "watchos"; - } elsif ($os_arg eq "macosx") { - $C{TOOLCHAIN} = "osx"; - } else { - print "${yellow}WARN: don't know toolchain for OS $C{OS}${nocolor}\n"; - $C{TOOLCHAIN} = "default"; - } - - # Look up SDK - # Try exact match first. - # Then try lexically-last prefix match (so "macosx" => "macosx10.7internal") - my @sdks = getsdks(); - if ($VERBOSE) { - print "note: Installed SDKs: @sdks\n"; - } - my $exactsdk = undef; - my $prefixsdk = undef; - foreach my $sdk (@sdks) { - $exactsdk = $sdk if ($sdk eq $sdk_arg); - $prefixsdk = newersdk($sdk, $prefixsdk) if ($sdk =~ /^$sdk_arg/); - } - - my $sdk; - if ($exactsdk) { - $sdk = $exactsdk; - } elsif ($prefixsdk) { - $sdk = $prefixsdk; - } else { - die "unknown SDK '$sdk_arg'\nInstalled SDKs: @sdks\n"; - } - - # Set deployment target and run target. - # fixme can't enforce version when run_arg eq "default" - # because we don't know it yet - $deployment_arg = versionsuffix($sdk) if $deployment_arg eq "default"; - if ($run_arg ne "default") { - die "Deployment target '$deployment_arg' is newer than run target '$run_arg'\n" if $deployment_arg > $run_arg; - } - $C{DEPLOYMENT_TARGET} = $deployment_arg; - $C{RUN_TARGET} = $run_arg; - - # set the config name now, after massaging the language and OS versions, - # but before adding other settings - my $configname = config_name(%C); - die if ($configname =~ /'/); - die if ($configname =~ / /); - ($C{NAME} = $configname) =~ s/~/ /g; - (my $configdir = $configname) =~ s#/##g; - $C{DIR} = "$BUILDDIR/$configdir"; - - $C{SDK_PATH} = getsdkpath($sdk); - - # Look up test library (possible in root or SDK_PATH) - - my $rootarg = $root; - my $symroot; - my @sympaths = ( (glob "$root/*~sym")[0], - (glob "$root/BuildRecords/*_install/Symbols")[0], - "$root/Symbols" ); - my @dstpaths = ( (glob "$root/*~dst")[0], - (glob "$root/BuildRecords/*_install/Root")[0], - "$root/Root" ); - for(my $i = 0; $i < scalar(@sympaths); $i++) { - if (-e $sympaths[$i] && -e $dstpaths[$i]) { - $symroot = $sympaths[$i]; - $root = $dstpaths[$i]; - last; - } - } - - if ($root ne "" && -e "$root$C{SDK_PATH}$TESTLIBPATH") { - $C{TESTLIB} = "$root$C{SDK_PATH}$TESTLIBPATH"; - } elsif (-e "$root$TESTLIBPATH") { - $C{TESTLIB} = "$root$TESTLIBPATH"; - } elsif (-e "$root/$TESTLIBNAME") { - $C{TESTLIB} = "$root/$TESTLIBNAME"; - } else { - die "No $TESTLIBNAME in root '$rootarg' for sdk '$C{SDK_PATH}'\n" - # . join("\n", @dstpaths) . "\n" - ; - } - - if (-e "$symroot/$TESTLIBNAME.dSYM") { - $C{TESTDSYM} = "$symroot/$TESTLIBNAME.dSYM"; - } - - if ($VERBOSE) { - my @uuids = `/usr/bin/dwarfdump -u '$C{TESTLIB}'`; - while (my $uuid = shift @uuids) { - print "note: $uuid"; - } - } - - # Look up compilers - my $cc = $C{CC}; - my $cxx = cplusplus($C{CC}); - my $swift = swift($C{CC}); - if (! $BUILD) { - $C{CC} = $cc; - $C{CXX} = $cxx; - $C{SWIFT} = $swift - } else { - $C{CC} = find_compiler($cc, $C{TOOLCHAIN}, $C{SDK_PATH}); - $C{CXX} = find_compiler($cxx, $C{TOOLCHAIN}, $C{SDK_PATH}); - $C{SWIFT} = find_compiler($swift, $C{TOOLCHAIN}, $C{SDK_PATH}); - - die "No compiler '$cc' ('$C{CC}') in toolchain '$C{TOOLCHAIN}'\n" if !-e $C{CC}; - die "No compiler '$cxx' ('$C{CXX}') in toolchain '$C{TOOLCHAIN}'\n" if !-e $C{CXX}; - die "No compiler '$swift' ('$C{SWIFT}') in toolchain '$C{TOOLCHAIN}'\n" if !-e $C{SWIFT}; - } - - # Populate cflags - - # save-temps so dsymutil works so debug info works - my $cflags = "-I$DIR -W -Wall -Wno-deprecated-declarations -Wshorten-64-to-32 -g -save-temps -Os -arch $C{ARCH} "; - my $objcflags = ""; - my $swiftflags = "-g "; - - $cflags .= " -isysroot '$C{SDK_PATH}'"; - $cflags .= " '-Wl,-syslibroot,$C{SDK_PATH}'"; - $swiftflags .= " -sdk '$C{SDK_PATH}'"; - - # Set deployment target cflags - my $target = undef; - die "No deployment target" if $C{DEPLOYMENT_TARGET} eq ""; - if ($C{OS} eq "iphoneos") { - $cflags .= " -mios-version-min=$C{DEPLOYMENT_TARGET}"; - $target = "$C{ARCH}-apple-ios$C{DEPLOYMENT_TARGET}"; - } - elsif ($C{OS} eq "iphonesimulator") { - $cflags .= " -mios-simulator-version-min=$C{DEPLOYMENT_TARGET}"; - $target = "$C{ARCH}-apple-ios$C{DEPLOYMENT_TARGET}"; - } - elsif ($C{OS} eq "watchos") { - $cflags .= " -mwatchos-version-min=$C{DEPLOYMENT_TARGET}"; - $target = "$C{ARCH}-apple-watchos$C{DEPLOYMENT_TARGET}"; - } - elsif ($C{OS} eq "watchsimulator") { - $cflags .= " -mwatch-simulator-version-min=$C{DEPLOYMENT_TARGET}"; - $target = "$C{ARCH}-apple-watchos$C{DEPLOYMENT_TARGET}"; - } - else { - $cflags .= " -mmacosx-version-min=$C{DEPLOYMENT_TARGET}"; - $target = "$C{ARCH}-apple-macosx$C{DEPLOYMENT_TARGET}"; - } - $swiftflags .= " -target $target"; - - # fixme still necessary? - if ($C{OS} eq "iphonesimulator" && $C{ARCH} eq "i386") { - $objcflags .= " -fobjc-abi-version=2 -fobjc-legacy-dispatch"; - } - - if ($root ne "") { - my $library_path = dirname($C{TESTLIB}); - $cflags .= " -L$library_path"; - $cflags .= " -I '$root/usr/include'"; - $cflags .= " -I '$root/usr/local/include'"; - - if ($C{SDK_PATH} ne "/") { - $cflags .= " -I '$root$C{SDK_PATH}/usr/include'"; - $cflags .= " -I '$root$C{SDK_PATH}/usr/local/include'"; - } - } - - if ($C{CC} =~ /clang/) { - $cflags .= " -Qunused-arguments -fno-caret-diagnostics"; - $cflags .= " -stdlib=$C{STDLIB}"; # fixme -fno-objc-link-runtime" - $cflags .= " -Wl,-segalign,0x4000 "; - } - - - # Populate objcflags - - $objcflags .= " -lobjc"; - if ($C{MEM} eq "gc") { - $objcflags .= " -fobjc-gc"; - } - elsif ($C{MEM} eq "arc") { - $objcflags .= " -fobjc-arc"; - } - elsif ($C{MEM} eq "mrc") { - # nothing - } - else { - die "unrecognized MEM '$C{MEM}'\n"; - } - - if (supportslibauto($C{OS})) { - # do this even for non-GC tests - $objcflags .= " -lauto"; - } - - # Populate ENV_PREFIX - $C{ENV} = "LANG=C MallocScribble=1"; - $C{ENV} .= " VERBOSE=$VERBOSE" if $VERBOSE; - if ($root ne "") { - die "no spaces allowed in root" if dirname($C{TESTLIB}) =~ /\s+/; - } - if ($C{GUARDMALLOC}) { - $ENV{GUARDMALLOC} = "1"; # checked by tests and errcheck.pl - $C{ENV} .= " DYLD_INSERT_LIBRARIES=/usr/lib/libgmalloc.dylib"; - if ($C{GUARDMALLOC} eq "before") { - $C{ENV} .= " MALLOC_PROTECT_BEFORE=1"; - } elsif ($C{GUARDMALLOC} eq "after") { - # protect after is the default - } else { - die "Unknown guard malloc mode '$C{GUARDMALLOC}'\n"; - } - } - - # Populate compiler commands - $C{COMPILE_C} = "env LANG=C '$C{CC}' $cflags -x c -std=gnu99"; - $C{COMPILE_CXX} = "env LANG=C '$C{CXX}' $cflags -x c++"; - $C{COMPILE_M} = "env LANG=C '$C{CC}' $cflags $objcflags -x objective-c -std=gnu99"; - $C{COMPILE_MM} = "env LANG=C '$C{CXX}' $cflags $objcflags -x objective-c++"; - $C{COMPILE_SWIFT} = "env LANG=C '$C{SWIFT}' $swiftflags"; - - $C{COMPILE} = $C{COMPILE_C} if $C{LANGUAGE} eq "c"; - $C{COMPILE} = $C{COMPILE_CXX} if $C{LANGUAGE} eq "c++"; - $C{COMPILE} = $C{COMPILE_M} if $C{LANGUAGE} eq "objective-c"; - $C{COMPILE} = $C{COMPILE_MM} if $C{LANGUAGE} eq "objective-c++"; - $C{COMPILE} = $C{COMPILE_SWIFT} if $C{LANGUAGE} eq "swift"; - die "unknown language '$C{LANGUAGE}'\n" if !defined $C{COMPILE}; - - ($C{COMPILE_NOMEM} = $C{COMPILE}) =~ s/ -fobjc-(?:gc|arc)\S*//g; - ($C{COMPILE_NOLINK} = $C{COMPILE}) =~ s/ '?-(?:Wl,|l)\S*//g; - ($C{COMPILE_NOLINK_NOMEM} = $C{COMPILE_NOMEM}) =~ s/ '?-(?:Wl,|l)\S*//g; - - - # Reject some self-inconsistent configurations - if ($C{MEM} !~ /^(mrc|arc|gc)$/) { - die "unknown MEM=$C{MEM} (expected one of mrc arc gc)\n"; - } - - if ($C{MEM} eq "gc" && $C{OS} !~ /^macosx/) { - print "note: skipping configuration $C{NAME}\n"; - print "note: because OS=$C{OS} does not support MEM=$C{MEM}\n"; - return 0; - } - if ($C{MEM} eq "gc" && $C{ARCH} eq "x86_64h") { - print "note: skipping configuration $C{NAME}\n"; - print "note: because ARCH=$C{ARCH} does not support MEM=$C{MEM}\n"; - return 0; - } - if ($C{MEM} eq "arc" && $C{OS} =~ /^macosx/ && $C{ARCH} eq "i386") { - print "note: skipping configuration $C{NAME}\n"; - print "note: because 32-bit Mac does not support MEM=$C{MEM}\n"; - return 0; - } - if ($C{MEM} eq "arc" && $C{CC} !~ /clang/) { - print "note: skipping configuration $C{NAME}\n"; - print "note: because CC=$C{CC} does not support MEM=$C{MEM}\n"; - return 0; - } - - if ($C{STDLIB} ne "libstdc++" && $C{CC} !~ /clang/) { - print "note: skipping configuration $C{NAME}\n"; - print "note: because CC=$C{CC} does not support STDLIB=$C{STDLIB}\n"; - return 0; - } - - # fixme - if ($C{LANGUAGE} eq "swift" && $C{ARCH} =~ /^arm/) { - print "note: skipping configuration $C{NAME}\n"; - print "note: because ARCH=$C{ARCH} does not support LANGUAGE=SWIFT\n"; - return 0; - } - - # fixme unimplemented run targets - if ($C{RUN_TARGET} ne "default" && $C{OS} !~ /simulator/) { - print "${yellow}WARN: skipping configuration $C{NAME}${nocolor}\n"; - print "${yellow}WARN: because OS=$C{OS} does not yet implement RUN_TARGET=$C{RUN_TARGET}${nocolor}\n"; - } - - %$configref = %C; -} - -sub make_configs { - my ($root, %args) = @_; - - my @results = ({}); # start with one empty config - - for my $key (keys %args) { - my @newresults; - my @values = @{$args{$key}}; - for my $configref (@results) { - my %config = %{$configref}; - for my $value (@values) { - my %newconfig = %config; - $newconfig{$key} = $value; - push @newresults, \%newconfig; - } - } - @results = @newresults; - } - - my @newresults; - for my $configref(@results) { - if (make_one_config($configref, $root)) { - push @newresults, $configref; - } - } - - return @newresults; -} - -sub config_name { - my %config = @_; - my $name = ""; - for my $key (sort keys %config) { - $name .= '~' if $name ne ""; - $name .= "$key=$config{$key}"; - } - return $name; -} - -sub run_one_config { - my %C = %{shift()}; - my @tests = @_; - - # Build and run - my $testcount = 0; - my $failcount = 0; - - my @gathertests; - foreach my $test (@tests) { - if ($VERBOSE) { - print "\nGATHER $test\n"; - } - - if ($ALL_TESTS{$test}) { - gather_simple(\%C, $test) || next; # not pass, not fail - push @gathertests, $test; - } else { - die "No test named '$test'\n"; - } - } - - my @builttests; - if (!$BUILD) { - @builttests = @gathertests; - $testcount = scalar(@gathertests); - } else { - my $configdir = $C{DIR}; - print $configdir, "\n" if $VERBOSE; - mkdir $configdir || die; - - foreach my $test (@gathertests) { - if ($VERBOSE) { - print "\nBUILD $test\n"; - } - mkdir "$configdir/$test.build" || die; - - if ($ALL_TESTS{$test}) { - $testcount++; - if (!build_simple(\%C, $test)) { - $failcount++; - } else { - push @builttests, $test; - } - } else { - die "No test named '$test'\n"; - } - } - } - - if (!$RUN || !scalar(@builttests)) { - # nothing to do - } - else { - if ($C{ARCH} =~ /^arm/ && `unamep -p` !~ /^arm/) { - # upload all tests to iOS device - make("RSYNC_PASSWORD=alpine rsync -av $C{DIR} rsync://root\@localhost:10873/root/var/root/objctest/"); - die "Couldn't rsync tests to device\n" if ($?); - - # upload library to iOS device - if ($C{TESTLIB} ne $TESTLIBPATH) { - make("RSYNC_PASSWORD=alpine rsync -av $C{TESTLIB} rsync://root\@localhost:10873/root/var/root/objctest/"); - die "Couldn't rsync $C{TESTLIB} to device\n" if ($?); - make("RSYNC_PASSWORD=alpine rsync -av $C{TESTDSYM} rsync://root\@localhost:10873/root/var/root/objctest/"); - } - } - - foreach my $test (@builttests) { - print "\nRUN $test\n" if ($VERBOSE); - - if ($ALL_TESTS{$test}) - { - if (!run_simple(\%C, $test)) { - $failcount++; - } - } else { - die "No test named '$test'\n"; - } - } - } - - return ($testcount, $failcount); -} - - - -# Return value if set by "$argname=value" on the command line -# Return $default if not set. -sub getargs { - my ($argname, $default) = @_; - - foreach my $arg (@ARGV) { - my ($value) = ($arg =~ /^$argname=(.+)$/); - return [split ',', $value] if defined $value; - } - - return [split ',', $default]; -} - -# Return 1 or 0 if set by "$argname=1" or "$argname=0" on the -# command line. Return $default if not set. -sub getbools { - my ($argname, $default) = @_; - - my @values = @{getargs($argname, $default)}; - return [( map { ($_ eq "0") ? 0 : 1 } @values )]; -} - -# Return an integer if set by "$argname=value" on the -# command line. Return $default if not set. -sub getints { - my ($argname, $default) = @_; - - my @values = @{getargs($argname, $default)}; - return [( map { int($_) } @values )]; -} - -sub getarg { - my ($argname, $default) = @_; - my @values = @{getargs($argname, $default)}; - die "Only one value allowed for $argname\n" if @values > 1; - return $values[0]; -} - -sub getbool { - my ($argname, $default) = @_; - my @values = @{getbools($argname, $default)}; - die "Only one value allowed for $argname\n" if @values > 1; - return $values[0]; -} - -sub getint { - my ($argname, $default) = @_; - my @values = @{getints($argname, $default)}; - die "Only one value allowed for $argname\n" if @values > 1; - return $values[0]; -} - - -# main -my %args; - - -my $default_arch = (`/usr/sbin/sysctl hw.optional.x86_64` eq "hw.optional.x86_64: 1\n") ? "x86_64" : "i386"; -$args{ARCH} = getargs("ARCH", 0); -$args{ARCH} = getargs("ARCHS", $default_arch) if !@{$args{ARCH}}[0]; - -$args{OSVERSION} = getargs("OS", "macosx-default-default"); - -$args{MEM} = getargs("MEM", "mrc"); -$args{LANGUAGE} = [ map { lc($_) } @{getargs("LANGUAGE", "objective-c,swift")} ]; -$args{STDLIB} = getargs("STDLIB", "libc++"); - -$args{CC} = getargs("CC", "clang"); - -{ - my $guardmalloc = getargs("GUARDMALLOC", 0); - # GUARDMALLOC=1 is the same as GUARDMALLOC=before,after - my @guardmalloc2 = (); - for my $arg (@$guardmalloc) { - if ($arg == 1) { push @guardmalloc2, "before"; - push @guardmalloc2, "after"; } - else { push @guardmalloc2, $arg } - } - $args{GUARDMALLOC} = \@guardmalloc2; -} - -$BUILD = getbool("BUILD", 1); -$RUN = getbool("RUN", 1); -$VERBOSE = getint("VERBOSE", 0); - -my $root = getarg("ROOT", ""); -$root =~ s#/*$##; - -my @tests = gettests(); - -print "note: -----\n"; -print "note: testing root '$root'\n"; - -my @configs = make_configs($root, %args); - -print "note: -----\n"; -print "note: testing ", scalar(@configs), " configurations:\n"; -for my $configref (@configs) { - my $configname = $$configref{NAME}; - print "note: configuration $configname\n"; -} - -if ($BUILD) { - `rm -rf '$BUILDDIR'`; - mkdir "$BUILDDIR" || die; -} - -my $failed = 0; - -my $testconfigs = @configs; -my $failconfigs = 0; -my $testcount = 0; -my $failcount = 0; -for my $configref (@configs) { - my $configname = $$configref{NAME}; - print "note: -----\n"; - print "note: \nnote: $configname\nnote: \n"; - - (my $t, my $f) = eval { run_one_config($configref, @tests); }; - if ($@) { - chomp $@; - print "${red}FAIL: $configname${nocolor}\n"; - print "${red}FAIL: $@${nocolor}\n"; - $failconfigs++; - } else { - my $color = ($f ? $red : ""); - print "note:\n"; - print "${color}note: $configname$nocolor\n"; - print "${color}note: $t tests, $f failures$nocolor\n"; - $testcount += $t; - $failcount += $f; - $failconfigs++ if ($f); - } -} - -print "note: -----\n"; -my $color = ($failconfigs ? $red : ""); -print "${color}note: $testconfigs configurations, $failconfigs with failures$nocolor\n"; -print "${color}note: $testcount tests, $failcount failures$nocolor\n"; - -$failed = ($failconfigs ? 1 : 0); - -exit ($failed ? 1 : 0); diff --git a/test/testroot.i b/test/testroot.i deleted file mode 100644 index 118c438..0000000 --- a/test/testroot.i +++ /dev/null @@ -1,229 +0,0 @@ -// testroot.i -// Implementation of class TestRoot -// Include this file into your main test file to use it. - -#include "test.h" -#include -#include - -int TestRootLoad = 0; -int TestRootInitialize = 0; -int TestRootAlloc = 0; -int TestRootAllocWithZone = 0; -int TestRootCopy = 0; -int TestRootCopyWithZone = 0; -int TestRootMutableCopy = 0; -int TestRootMutableCopyWithZone = 0; -int TestRootInit = 0; -int TestRootDealloc = 0; -int TestRootFinalize = 0; -int TestRootRetain = 0; -int TestRootRelease = 0; -int TestRootAutorelease = 0; -int TestRootRetainCount = 0; -int TestRootTryRetain = 0; -int TestRootIsDeallocating = 0; -int TestRootPlusRetain = 0; -int TestRootPlusRelease = 0; -int TestRootPlusAutorelease = 0; -int TestRootPlusRetainCount = 0; - - -@implementation TestRoot - -// These all use void* pending rdar://9310005. - -static void * -retain_fn(void *self, SEL _cmd __unused) { - OSAtomicIncrement32(&TestRootRetain); - void * (*fn)(void *) = (typeof(fn))_objc_rootRetain; - return fn(self); -} - -static void -release_fn(void *self, SEL _cmd __unused) { - OSAtomicIncrement32(&TestRootRelease); - void (*fn)(void *) = (typeof(fn))_objc_rootRelease; - fn(self); -} - -static void * -autorelease_fn(void *self, SEL _cmd __unused) { - OSAtomicIncrement32(&TestRootAutorelease); - void * (*fn)(void *) = (typeof(fn))_objc_rootAutorelease; - return fn(self); -} - -static unsigned long -retaincount_fn(void *self, SEL _cmd __unused) { - OSAtomicIncrement32(&TestRootRetainCount); - unsigned long (*fn)(void *) = (typeof(fn))_objc_rootRetainCount; - return fn(self); -} - -static void * -copywithzone_fn(void *self, SEL _cmd __unused, void *zone) { - OSAtomicIncrement32(&TestRootCopyWithZone); - void * (*fn)(void *, void *) = (typeof(fn))dlsym(RTLD_DEFAULT, "object_copy"); - return fn(self, zone); -} - -static void * -plusretain_fn(void *self __unused, SEL _cmd __unused) { - OSAtomicIncrement32(&TestRootPlusRetain); - return self; -} - -static void -plusrelease_fn(void *self __unused, SEL _cmd __unused) { - OSAtomicIncrement32(&TestRootPlusRelease); -} - -static void * -plusautorelease_fn(void *self, SEL _cmd __unused) { - OSAtomicIncrement32(&TestRootPlusAutorelease); - return self; -} - -static unsigned long -plusretaincount_fn(void *self __unused, SEL _cmd __unused) { - OSAtomicIncrement32(&TestRootPlusRetainCount); - return ULONG_MAX; -} - -+(void) load { - OSAtomicIncrement32(&TestRootLoad); - - // install methods that ARR refuses to compile - class_addMethod(self, sel_registerName("retain"), (IMP)retain_fn, ""); - class_addMethod(self, sel_registerName("release"), (IMP)release_fn, ""); - class_addMethod(self, sel_registerName("autorelease"), (IMP)autorelease_fn, ""); - class_addMethod(self, sel_registerName("retainCount"), (IMP)retaincount_fn, ""); - class_addMethod(self, sel_registerName("copyWithZone:"), (IMP)copywithzone_fn, ""); - - class_addMethod(object_getClass(self), sel_registerName("retain"), (IMP)plusretain_fn, ""); - class_addMethod(object_getClass(self), sel_registerName("release"), (IMP)plusrelease_fn, ""); - class_addMethod(object_getClass(self), sel_registerName("autorelease"), (IMP)plusautorelease_fn, ""); - class_addMethod(object_getClass(self), sel_registerName("retainCount"), (IMP)plusretaincount_fn, ""); -} - - -+(void) initialize { - OSAtomicIncrement32(&TestRootInitialize); -} - --(id) self { - return self; -} - -+(Class) class { - return self; -} - --(Class) class { - return object_getClass(self); -} - -+(Class) superclass { - return class_getSuperclass(self); -} - --(Class) superclass { - return class_getSuperclass([self class]); -} - -+(id) new { - return [[self alloc] init]; -} - -+(id) alloc { - OSAtomicIncrement32(&TestRootAlloc); - void * (*fn)(id __unsafe_unretained) = (typeof(fn))_objc_rootAlloc; - return objc_retainedObject(fn(self)); -} - -+(id) allocWithZone:(void *)zone { - OSAtomicIncrement32(&TestRootAllocWithZone); - void * (*fn)(id __unsafe_unretained, void *) = (typeof(fn))_objc_rootAllocWithZone; - return objc_retainedObject(fn(self, zone)); -} - -+(id) copy { - return self; -} - -+(id) copyWithZone:(void *) __unused zone { - return self; -} - --(id) copy { - OSAtomicIncrement32(&TestRootCopy); - return [self copyWithZone:NULL]; -} - -+(id) mutableCopyWithZone:(void *) __unused zone { - fail("+mutableCopyWithZone: called"); -} - --(id) mutableCopy { - OSAtomicIncrement32(&TestRootMutableCopy); - return [self mutableCopyWithZone:NULL]; -} - --(id) mutableCopyWithZone:(void *) __unused zone { - OSAtomicIncrement32(&TestRootMutableCopyWithZone); - void * (*fn)(id __unsafe_unretained) = (typeof(fn))_objc_rootAlloc; - return objc_retainedObject(fn(object_getClass(self))); -} - --(id) init { - OSAtomicIncrement32(&TestRootInit); - return _objc_rootInit(self); -} - -+(void) dealloc { - fail("+dealloc called"); -} - --(void) dealloc { - OSAtomicIncrement32(&TestRootDealloc); - _objc_rootDealloc(self); -} - -+(void) finalize { - fail("+finalize called"); -} - --(void) finalize { - OSAtomicIncrement32(&TestRootFinalize); - _objc_rootFinalize(self); -} - -+(BOOL) _tryRetain { - return YES; -} - --(BOOL) _tryRetain { - OSAtomicIncrement32(&TestRootTryRetain); - return _objc_rootTryRetain(self); -} - -+(BOOL) _isDeallocating { - return NO; -} - --(BOOL) _isDeallocating { - OSAtomicIncrement32(&TestRootIsDeallocating); - return _objc_rootIsDeallocating(self); -} - --(BOOL) allowsWeakReference { - return ! [self _isDeallocating]; -} - --(BOOL) retainWeakReference { - return [self _tryRetain]; -} - - -@end diff --git a/test/unload.h b/test/unload.h deleted file mode 100644 index 5287fe9..0000000 --- a/test/unload.h +++ /dev/null @@ -1,6 +0,0 @@ -#include "test.h" - -@interface SmallClass : TestRoot @end - -@interface BigClass : TestRoot @end - diff --git a/test/unload.m b/test/unload.m deleted file mode 100644 index 5b9c76b..0000000 --- a/test/unload.m +++ /dev/null @@ -1,174 +0,0 @@ -// xpc leaks memory in dlopen(). Disable it. -// TEST_ENV XPC_SERVICES_UNAVAILABLE=1 -/* -TEST_BUILD - $C{COMPILE} $DIR/unload4.m -o unload4.dylib -dynamiclib - $C{COMPILE_C} $DIR/unload3.c -o unload3.dylib -dynamiclib - $C{COMPILE} $DIR/unload2.m -o unload2.bundle -bundle - $C{COMPILE} $DIR/unload.m -o unload.out -END - */ - -#include "test.h" -#include -#include -#include - -#include "unload.h" - -#if __has_feature(objc_arc) - -int main() -{ - testwarn("rdar://11368528 confused by Foundation"); - succeed(__FILE__); -} - -#else - -static id forward_handler(void) -{ - return 0; -} - -static BOOL hasName(const char * const *names, const char *query) -{ - const char *name; - while ((name = *names++)) { - if (strstr(name, query)) return YES; - } - - return NO; -} - -void cycle(void) -{ - int i; - char buf[100]; - unsigned int imageCount, imageCount0; - const char **names; - const char *name; - - names = objc_copyImageNames(&imageCount0); - testassert(names); - free(names); - - void *bundle = dlopen("unload2.bundle", RTLD_LAZY); - testassert(bundle); - - names = objc_copyImageNames(&imageCount); - testassert(names); - testassert(imageCount == imageCount0 + 1); - testassert(hasName(names, "unload2.bundle")); - free(names); - - Class small = objc_getClass("SmallClass"); - Class big = objc_getClass("BigClass"); - testassert(small); - testassert(big); - - name = class_getImageName(small); - testassert(name); - testassert(strstr(name, "unload2.bundle")); - name = class_getImageName(big); - testassert(name); - testassert(strstr(name, "unload2.bundle")); - - id o1 = [small new]; - id o2 = [big new]; - testassert(o1); - testassert(o2); - - // give BigClass and BigClass->isa large method caches (4692641) - // Flush caches part way through to test large empty caches. - for (i = 0; i < 3000; i++) { - sprintf(buf, "method_%d", i); - SEL sel = sel_registerName(buf); - ((void(*)(id, SEL))objc_msgSend)(o2, sel); - ((void(*)(id, SEL))objc_msgSend)(object_getClass(o2), sel); - } - _objc_flush_caches(object_getClass(o2)); - for (i = 0; i < 17000; i++) { - sprintf(buf, "method_%d", i); - SEL sel = sel_registerName(buf); - ((void(*)(id, SEL))objc_msgSend)(o2, sel); - ((void(*)(id, SEL))objc_msgSend)(object_getClass(o2), sel); - } - - RELEASE_VAR(o1); - RELEASE_VAR(o2); - - testcollect(); - - int err = dlclose(bundle); - testassert(err == 0); - err = dlclose(bundle); - testassert(err == -1); // already closed - - testassert(objc_getClass("SmallClass") == NULL); - testassert(objc_getClass("BigClass") == NULL); - - names = objc_copyImageNames(&imageCount); - testassert(names); - testassert(imageCount == imageCount0); - testassert(! hasName(names, "unload2.bundle")); - free(names); - - // these selectors came from the bundle - testassert(0 == strcmp("unload2_instance_method", sel_getName(sel_registerName("unload2_instance_method")))); - testassert(0 == strcmp("unload2_category_method", sel_getName(sel_registerName("unload2_category_method")))); - - // This protocol came from the bundle. - // It isn't unloaded cleanly (rdar://20664713), but neither - // may it cause the protocol table to crash after unloading. - testassert(objc_getProtocol("SmallProtocol")); -} - - -int main() -{ - // fixme object_dispose() not aggressive enough? - if (objc_collectingEnabled()) succeed(__FILE__); - - objc_setForwardHandler((void*)&forward_handler, (void*)&forward_handler); - -#if defined(__arm__) || defined(__arm64__) - int count = 10; -#else - int count = is_guardmalloc() ? 10 : 100; -#endif - - cycle(); -#if __LP64__ - // fixme heap use goes up 512 bytes after the 2nd cycle only - bad or not? - cycle(); -#endif - - leak_mark(); - while (count--) { - cycle(); - } - leak_check(0); - - // 5359412 Make sure dylibs with nothing other than image_info can close - void *dylib = dlopen("unload3.dylib", RTLD_LAZY); - testassert(dylib); - int err = dlclose(dylib); - testassert(err == 0); - err = dlclose(dylib); - testassert(err == -1); // already closed - - // Make sure dylibs with real objc content cannot close - dylib = dlopen("unload4.dylib", RTLD_LAZY); - testassert(dylib); - err = dlclose(dylib); - testassert(err == 0); - err = dlclose(dylib); - testassert(err == 0); // dlopen from libobjc itself - err = dlclose(dylib); - testassert(err == -1); // already closed - - succeed(__FILE__); -} - -#endif diff --git a/test/unload2.m b/test/unload2.m deleted file mode 100644 index 16ac0ca..0000000 --- a/test/unload2.m +++ /dev/null @@ -1,26 +0,0 @@ -#include "unload.h" -#include "testroot.i" -#import - -@implementation SmallClass : TestRoot --(void)unload2_instance_method { } -@end - - -@implementation BigClass : TestRoot -@end - -OBJC_ROOT_CLASS -@interface UnusedClass { id isa; } @end -@implementation UnusedClass @end - - -@protocol SmallProtocol --(void)unload2_category_method; -@end - -@interface SmallClass (Category) @end - -@implementation SmallClass (Category) --(void)unload2_category_method { } -@end diff --git a/test/unload3.c b/test/unload3.c deleted file mode 100644 index 8cdb51f..0000000 --- a/test/unload3.c +++ /dev/null @@ -1,17 +0,0 @@ -// unload3: contains imageinfo but no other objc metadata -// libobjc must not keep it open -// DO NOT USE __OBJC2__; this is a C file. - -#include - -#if TARGET_OS_WIN32 || (TARGET_OS_MAC && TARGET_CPU_X86 && !TARGET_IPHONE_SIMULATOR) -// old ABI -int fake[2] __attribute__((section("__OBJC,__image_info"))) -#else -// new ABI -int fake[2] __attribute__((section("__DATA,__objc_imageinfo"))) -#endif - = { 0, TARGET_IPHONE_SIMULATOR ? (1<<5) : 0 }; - -// silence "no debug symbols in executable" warning -void fn(void) { } diff --git a/test/unload4.m b/test/unload4.m deleted file mode 100644 index 94dd034..0000000 --- a/test/unload4.m +++ /dev/null @@ -1,11 +0,0 @@ -// unload4: contains some objc metadata other than imageinfo -// libobjc must keep it open - -#if __OBJC2__ -int fake2 __attribute__((section("__DATA,__objc_foo"))) = 0; -#else -int fake2 __attribute__((section("__OBJC,__foo"))) = 0; -#endif - -// getsectiondata() falls over if __TEXT has no contents -const char *unload4 = "unload4"; diff --git a/test/unwind.m b/test/unwind.m deleted file mode 100644 index 7fa1159..0000000 --- a/test/unwind.m +++ /dev/null @@ -1,93 +0,0 @@ -// TEST_CONFIG - -#include "test.h" -#include -#include - -#if !defined(__OBJC2__) - -int main() -{ - succeed(__FILE__); -} - -#else - -static int state; - -@interface Foo : NSObject @end -@interface Bar : NSObject @end - -@interface Foo (Unimplemented) -+(void)method; -@end - -@implementation Bar @end - -@implementation Foo - --(void)check { state++; } -+(void)check { testassert(!"caught class object, not instance"); } - -static id exc; - -static void handler(id unused, void *ctx) __attribute__((used)); -static void handler(id unused __unused, void *ctx __unused) -{ - testassert(state == 3); state++; -} - -+(BOOL) resolveClassMethod:(SEL)__unused name -{ - testassert(state == 1); state++; -#if !TARGET_OS_EMBEDDED && !TARGET_OS_IPHONE && !TARGET_IPHONE_SIMULATOR - objc_addExceptionHandler(&handler, 0); - testassert(state == 2); -#else - state++; // handler would have done this -#endif - state++; - exc = [Foo new]; - @throw exc; -} - - -@end - -int main() -{ - int i; - - // unwind exception and alt handler through objc_msgSend() - - PUSH_POOL { - - state = 0; - for (i = 0; i < 100000; i++) { - @try { - testassert(state == 0); state++; - [Foo method]; - testassert(0); - } @catch (Bar *e) { - testassert(0); - } @catch (Foo *e) { - testassert(e == exc); - testassert(state == 4); state++; - testassert(state == 5); [e check]; // state++ - RELEASE_VAR(exc); - } @catch (id e) { - testassert(0); - } @catch (...) { - testassert(0); - } @finally { - testassert(state == 6); state++; - } - testassert(state == 7); state = 0; - } - - } POP_POOL; - - succeed(__FILE__); -} - -#endif diff --git a/test/verify-exports.pl b/test/verify-exports.pl deleted file mode 100755 index dc544ff..0000000 --- a/test/verify-exports.pl +++ /dev/null @@ -1,254 +0,0 @@ -#!/usr/bin/perl - -# verify-exports.pl -# Check exports in a library vs. declarations in header files. -# usage: verify-exports.pl /path/to/dylib /glob/path/to/headers decl-prefix [-arch ] [/path/to/project~dst] -# example: verify-exports.pl /usr/lib/libobjc.A.dylib '/usr/{local/,}include/objc/*' OBJC_EXPORT -arch x86_64 /tmp/objc-test.roots/objc-test~dst - -# requirements: -# - every export must have an @interface or specially-marked declaration -# - every @interface or specially-marked declaration must have an availability macro -# - no C++ exports allowed - -use strict; -use File::Basename; -use File::Glob ':glob'; - -my $bad = 0; - -$0 = basename($0, ".pl"); -my $usage = "/path/to/dylib /glob/path/to/headers decl-prefix [-arch ] [-sdk sdkname] [/path/to/project~dst]"; - -my $lib_arg = shift || die "$usage"; -die "$usage" unless ($lib_arg =~ /^\//); -my $headers_arg = shift || die "$usage"; -my $export_arg = shift || die "$usage"; - -my $arch = "x86_64"; -if ($ARGV[0] eq "-arch") { - shift; - $arch = shift || die "$0: -arch requires an architecture"; -} -my $sdk = "system"; -if ($ARGV[0] eq "-sdk") { - shift; - $sdk = shift || die "$0: -sdk requires an SDK name"; -} - -my $root = shift || ""; - - -# Collect symbols from dylib. -my $lib_path = "$root$lib_arg"; -die "$0: file not found: $lib_path\n" unless -e $lib_path; - -my %symbols; -my @symbollines = `nm -arch $arch '$lib_path'`; -die "$0: nm failed: (arch $arch) $lib_path\n" if ($?); -for my $line (@symbollines) { - chomp $line; - (my $type, my $name) = ($line =~ /^[[:xdigit:]]*\s+(.) (.*)$/); - if ($type =~ /^[A-TV-Z]$/) { - $symbols{$name} = 1; - } else { - # undefined (U) or non-external - ignore - } -} - -# Complain about C++ exports -for my $symbol (keys %symbols) { - if ($symbol =~ /^__Z/) { - print "BAD: C++ export '$symbol'\n"; $bad++; - } -} - - -# Translate arch to unifdef(1) parameters: archnames, __LP64__, __OBJC2__ -my @archnames = ("x86_64", "i386", "arm", "armv6", "armv7"); -my %archOBJC1 = (i386 => 1); -my %archLP64 = (x86_64 => 1); -my @archparams; - -my $OBJC1 = ($archOBJC1{$arch} && $sdk !~ /^iphonesimulator/); - -if ($OBJC1) { - push @archparams, "-U__OBJC2__"; -} else { - push @archparams, "-D__OBJC2__=1"; -} - -if ($archLP64{$arch}) { push @archparams, "-D__LP64__=1"; } -else { push @archparams, "-U__LP64__"; } - -for my $archname (@archnames) { - if ($archname eq $arch) { - push @archparams, "-D__${archname}__=1"; - push @archparams, "-D__$archname=1"; - } else { - push @archparams, "-U__${archname}__"; - push @archparams, "-U__$archname"; - } -} - -# TargetConditionals.h -# fixme iphone and simulator -push @archparams, "-DTARGET_OS_WIN32=0"; -push @archparams, "-DTARGET_OS_EMBEDDED=0"; -push @archparams, "-DTARGET_OS_IPHONE=0"; -push @archparams, "-DTARGET_OS_MAC=1"; - -# Gather declarations from header files -# A C declaration starts with $export_arg and ends with ';' -# A class declaration is @interface plus the line before it. -my $unifdef_cmd = "/usr/bin/unifdef " . join(" ", @archparams); -my @cdecls; -my @classdecls; -for my $header_path(bsd_glob("$root$headers_arg",GLOB_BRACE)) { - my $header; - # feed through unifdef(1) first to strip decls from other archs - # fixme strip other SDKs as well - open($header, "$unifdef_cmd < '$header_path' |"); - my $header_contents = join("", <$header>); - - # C decls - push @cdecls, ($header_contents =~ /^\s*$export_arg\s+([^;]*)/msg); - - # ObjC classes, but not categories. - # fixme ivars - push @classdecls, ($header_contents =~ /^([^\n]*\n\s*\@interface\s+[^(\n]+\n)/mg); -} - -# Find name and availability of C declarations -my %declarations; -for my $cdecl (@cdecls) { - $cdecl =~ s/\n/ /mg; # strip newlines - - # Pull availability macro off the end: - # __OSX_AVAILABLE_*(*) - # AVAILABLE_MAC_OS_X_VERSION_* - # OBJC2_UNAVAILABLE - # OBJC_HASH_AVAILABILITY - # OBJC_MAP_AVAILABILITY - # UNAVAILABLE_ATTRIBUTE - # (DEPRECATED_ATTRIBUTE is not good enough. Be specific.) - my $avail = undef; - my $cdecl2; - ($cdecl2, $avail) = ($cdecl =~ /^(.*)\s+(__OSX_AVAILABLE_\w+\([a-zA-Z0-9_, ]+\))\s*$/) if (!defined $avail); - ($cdecl2, $avail) = ($cdecl =~ /^(.*)\s+(AVAILABLE_MAC_OS_X_VERSION_\w+)\s*$/) if (!defined $avail); - ($cdecl2, $avail) = ($cdecl =~ /^(.*)\s+(OBJC2_UNAVAILABLE)\s*$/) if (!defined $avail); - ($cdecl2, $avail) = ($cdecl =~ /^(.*)\s+(OBJC_GC_UNAVAILABLE)\s*$/) if (!defined $avail); - ($cdecl2, $avail) = ($cdecl =~ /^(.*)\s+(OBJC_ARC_UNAVAILABLE)\s*$/) if (!defined $avail); - ($cdecl2, $avail) = ($cdecl =~ /^(.*)\s+(OBJC_HASH_AVAILABILITY)\s*$/) if (!defined $avail); - ($cdecl2, $avail) = ($cdecl =~ /^(.*)\s+(OBJC_MAP_AVAILABILITY)\s*$/) if (!defined $avail); - ($cdecl2, $avail) = ($cdecl =~ /^(.*)\s+(UNAVAILABLE_ATTRIBUTE)\s*$/) if (!defined $avail); - # ($cdecl2, $avail) = ($cdecl =~ /^(.*)\s+(DEPRECATED_\w+)\s*$/) if (!defined $avail); - $cdecl2 = $cdecl if (!defined $cdecl2); - - # Extract declaration name (assumes availability macro is already gone): - # `(*xxx)` (function pointer) - # `xxx(` (function) - # `xxx`$` or `xxx[nnn]$` (variable or array variable) - my $name = undef; - ($name) = ($cdecl2 =~ /^[^(]*\(\s*\*\s*(\w+)\s*\)/) if (!defined $name); - ($name) = ($cdecl2 =~ /(\w+)\s*\(/) if (!defined $name); - ($name) = ($cdecl2 =~ /(\w+)\s*(?:\[\d*\]\s*)*$/) if (!defined $name); - - if (!defined $name) { - print "BAD: unintellible declaration:\n $cdecl\n"; $bad++; - } elsif (!defined $avail) { - print "BAD: no availability on declaration of '$name':\n $cdecl\n"; $bad++; - } - - if ($avail eq "UNAVAILABLE_ATTRIBUTE") - { - $declarations{$name} = "unavailable"; - } elsif ($avail eq "OBJC2_UNAVAILABLE" && ! $OBJC1) { - # fixme OBJC2_UNAVAILABLE may or may not have an exported symbol - # $declarations{$name} = "unavailable"; - } else { - $declarations{"_$name"} = "available"; - } -} - -# Find name and availability of Objective-C classes -for my $classdecl (@classdecls) { - $classdecl =~ s/\n/ /mg; # strip newlines - - # Pull availability macro off the front: - # __OSX_AVAILABLE_*(*) - # AVAILABLE_MAC_OS_X_VERSION_* - # OBJC2_UNAVAILABLE - # OBJC_HASH_AVAILABILITY - # OBJC_MAP_AVAILABILITY - # UNAVAILABLE_ATTRIBUTE - # (DEPRECATED_ATTRIBUTE is not good enough. Be specific.) - my $avail = undef; - my $classdecl2; - ($avail, $classdecl2) = ($classdecl =~ /^\s*(__OSX_AVAILABLE_\w+\([a-zA-Z0-9_, ]+\))\s*(.*)$/) if (!defined $avail); - ($avail, $classdecl2) = ($classdecl =~ /^\s*(AVAILABLE_MAC_OS_X_VERSION_\w+)\s*(.*)$/) if (!defined $avail); - ($avail, $classdecl2) = ($classdecl =~ /^\s*(OBJC2_UNAVAILABLE)\s*(.*)$/) if (!defined $avail); - ($avail, $classdecl2) = ($classdecl =~ /^\s*(OBJC_HASH_AVAILABILITY)\s*(.*)$/) if (!defined $avail); - ($avail, $classdecl2) = ($classdecl =~ /^\s*(OBJC_MAP_AVAILABILITY)\s*(.*)$/) if (!defined $avail); - ($avail, $classdecl2) = ($classdecl =~ /^\s*(UNAVAILABLE_ATTRIBUTE)\s*(.*)$/) if (!defined $avail); - # ($avail, $classdecl2) = ($classdecl =~ /^\s*(DEPRECATED_\w+)\s*(.*)$/) if (!defined $avail); - $classdecl2 = $classdecl if (!defined $classdecl2); - - # Extract class name. - my $name = undef; - ($name) = ($classdecl2 =~ /\@interface\s+(\w+)/); - - if (!defined $name) { - print "BAD: unintellible declaration:\n $classdecl\n"; $bad++; - } elsif (!defined $avail) { - print "BAD: no availability on declaration of '$name':\n $classdecl\n"; $bad++; - } - - my $availability; - if ($avail eq "UNAVAILABLE_ATTRIBUTE") { - $availability = "unavailable"; - } elsif ($avail eq "OBJC2_UNAVAILABLE" && ! $OBJC1) { - # fixme OBJC2_UNAVAILABLE may or may not have an exported symbol - # $declarations{$name} = "unavailable"; - $availability = undef; - } else { - $availability = "available"; - } - - if (! $OBJC1) { - $declarations{"_OBJC_CLASS_\$_$name"} = $availability; - $declarations{"_OBJC_METACLASS_\$_$name"} = $availability; - # fixme ivars - $declarations{"_OBJC_IVAR_\$_$name.isa"} = $availability if ($name eq "Object"); - } else { - $declarations{".objc_class_name_$name"} = $availability; - } -} - -# All exported symbols must have an export declaration -my @missing_symbols; -for my $name (keys %symbols) { - my $avail = $declarations{$name}; - if ($avail eq "unavailable" || !defined $avail) { - push @missing_symbols, $name; - } -} -for my $symbol (sort @missing_symbols) { - print "BAD: symbol $symbol has no export declaration\n"; $bad++; -} - - -# All export declarations must have an exported symbol -my @missing_decls; -for my $name (keys %declarations) { - my $avail = $declarations{$name}; - my $hasSymbol = exists $symbols{$name}; - if ($avail ne "unavailable" && !$hasSymbol) { - push @missing_decls, $name; - } -} -for my $decl (sort @missing_decls) { - print "BAD: declaration $decl has no exported symbol\n"; $bad++; -} - -print "OK: verify-exports\n" unless $bad; -exit ($bad ? 1 : 0); diff --git a/test/weak.h b/test/weak.h deleted file mode 100644 index 6bae410..0000000 --- a/test/weak.h +++ /dev/null @@ -1,67 +0,0 @@ -/* - To test -weak-l or -weak-framework: - * -DWEAK_IMPORT= - * -DWEAK_FRAMEWORK=1 - * -UEMPTY when building the weak-not-missing library - * -DEMPTY= when building the weak-missing library - - To test attribute((weak_import)): - * -DWEAK_IMPORT=__attribute__((weak_import)) - * -UWEAK_FRAMEWORK - * -UEMPTY when building the weak-not-missing library - * -DEMPTY= when building the weak-missing library - -*/ - -#include "test.h" -#include - -extern int state; - -WEAK_IMPORT OBJC_ROOT_CLASS -@interface MissingRoot { - id isa; -} -+(void) initialize; -+(Class) class; -+(id) alloc; --(id) init; --(void) dealloc; -+(int) method; -@end - -@interface MissingRoot (RR) --(id) retain; --(void) release; -@end - -WEAK_IMPORT -@interface MissingSuper : MissingRoot { - @public - int ivar; -} -@end - -OBJC_ROOT_CLASS -@interface NotMissingRoot { - id isa; -} -+(void) initialize; -+(Class) class; -+(id) alloc; --(id) init; --(void) dealloc; -+(int) method; -@end - -@interface NotMissingRoot (RR) --(id) retain; --(void) release; -@end - -@interface NotMissingSuper : NotMissingRoot { - @public - int unused[100]; - int ivar; -} -@end diff --git a/test/weak.m b/test/weak.m deleted file mode 100644 index 3d1fa10..0000000 --- a/test/weak.m +++ /dev/null @@ -1,316 +0,0 @@ -// See instructions in weak.h - -#include "test.h" -#include "weak.h" - -// Subclass of superclass that isn't there -@interface MyMissingSuper : MissingSuper -+(int) method; -@end -@implementation MyMissingSuper -+(int) method { return 1+[super method]; } -+(void) load { state++; } -@end - -// Subclass of subclass of superclass that isn't there -@interface MyMissingSub : MyMissingSuper -+(int) method; -@end -@implementation MyMissingSub -+(int) method { return 1+[super method]; } -+(void) load { state++; } -@end - -// Subclass of real superclass -@interface MyNotMissingSuper : NotMissingSuper -+(int) method; -@end -@implementation MyNotMissingSuper -+(int) method { return 1+[super method]; } -+(void) load { state++; } -@end - -// Subclass of subclass of superclass that isn't there -@interface MyNotMissingSub : MyNotMissingSuper -+(int) method; -@end -@implementation MyNotMissingSub -+(int) method { return 1+[super method]; } -+(void) load { state++; } -@end - -// Categories on all of the above -@interface MissingRoot (MissingRootExtras) -+(void)load; -+(int) cat_method; -@end -@implementation MissingRoot (MissingRootExtras) -+(void)load { state++; } -+(int) cat_method { return 40; } -@end - -@interface MissingSuper (MissingSuperExtras) -+(void)load; -+(int) cat_method; -@end -@implementation MissingSuper (MissingSuperExtras) -+(void)load { state++; } -+(int) cat_method { return 1+[super cat_method]; } -@end - -@interface MyMissingSuper (MyMissingSuperExtras) -+(void)load; -+(int) cat_method; -@end -@implementation MyMissingSuper (MyMissingSuperExtras) -+(void)load { state++; } -+(int) cat_method { return 1+[super cat_method]; } -@end - -@interface MyMissingSub (MyMissingSubExtras) -+(void)load; -+(int) cat_method; -@end -@implementation MyMissingSub (MyMissingSubExtras) -+(void)load { state++; } -+(int) cat_method { return 1+[super cat_method]; } -@end - - -@interface NotMissingRoot (NotMissingRootExtras) -+(void)load; -+(int) cat_method; -@end -@implementation NotMissingRoot (NotMissingRootExtras) -+(void)load { state++; } -+(int) cat_method { return 30; } -@end - -@interface NotMissingSuper (NotMissingSuperExtras) -+(void)load; -+(int) cat_method; -@end -@implementation NotMissingSuper (NotMissingSuperExtras) -+(void)load { state++; } -+(int) cat_method { return 1+[super cat_method]; } -@end - -@interface MyNotMissingSuper (MyNotMissingSuperExtras) -+(void)load; -+(int) cat_method; -@end -@implementation MyNotMissingSuper (MyNotMissingSuperExtras) -+(void)load { state++; } -+(int) cat_method { return 1+[super cat_method]; } -@end - -@interface MyNotMissingSub (MyNotMissingSubExtras) -+(void)load; -+(int) cat_method; -@end -@implementation MyNotMissingSub (MyNotMissingSubExtras) -+(void)load { state++; } -+(int) cat_method { return 1+[super cat_method]; } -@end - - -#if WEAK_FRAMEWORK -# define TESTIVAR(cond) testassert(cond) -#else -# define TESTIVAR(cond) /* rdar */ -#endif - -static BOOL classInList(__unsafe_unretained Class classes[], const char *name) -{ - for (int i = 0; classes[i] != nil; i++) { - if (0 == strcmp(class_getName(classes[i]), name)) return YES; - } - return NO; -} - -static BOOL classInNameList(const char **names, const char *name) -{ - const char **cp; - for (cp = names; *cp; cp++) { - if (0 == strcmp(*cp, name)) return YES; - } - return NO; -} - -int main(int argc __unused, char **argv) -{ - BOOL weakMissing; - if (strstr(argv[0], "-not-missing.out")) { - weakMissing = NO; - } else if (strstr(argv[0], "-missing.out")) { - weakMissing = YES; - } else { - fail("executable name must be weak*-missing.out or weak*-not-missing.out"); - } - - // class and category +load methods - if (weakMissing) testassert(state == 8); - else testassert(state == 16); - state = 0; - - // classes - testassert([NotMissingRoot class]); - testassert([NotMissingSuper class]); - testassert([MyNotMissingSuper class]); - testassert([MyNotMissingSub class]); - if (weakMissing) { - testassert([MissingRoot class] == nil); - testassert([MissingSuper class] == nil); - testassert([MyMissingSuper class] == nil); - testassert([MyMissingSub class] == nil); - } else { - testassert([MissingRoot class]); - testassert([MissingSuper class]); - testassert([MyMissingSuper class]); - testassert([MyMissingSub class]); - } - - // objc_getClass - testassert(objc_getClass("NotMissingRoot")); - testassert(objc_getClass("NotMissingSuper")); - testassert(objc_getClass("MyNotMissingSuper")); - testassert(objc_getClass("MyNotMissingSub")); - if (weakMissing) { - testassert(objc_getClass("MissingRoot") == nil); - testassert(objc_getClass("MissingSuper") == nil); - testassert(objc_getClass("MyMissingSuper") == nil); - testassert(objc_getClass("MyMissingSub") == nil); - } else { - testassert(objc_getClass("MissingRoot")); - testassert(objc_getClass("MissingSuper")); - testassert(objc_getClass("MyMissingSuper")); - testassert(objc_getClass("MyMissingSub")); - } - - // class list - union { - Class *c; - void *v; - } classes; - classes.c = objc_copyClassList(NULL); - testassert(classInList(classes.c, "NotMissingRoot")); - testassert(classInList(classes.c, "NotMissingSuper")); - testassert(classInList(classes.c, "MyNotMissingSuper")); - testassert(classInList(classes.c, "MyNotMissingSub")); - if (weakMissing) { - testassert(! classInList(classes.c, "MissingRoot")); - testassert(! classInList(classes.c, "MissingSuper")); - testassert(! classInList(classes.c, "MyMissingSuper")); - testassert(! classInList(classes.c, "MyMissingSub")); - } else { - testassert(classInList(classes.c, "MissingRoot")); - testassert(classInList(classes.c, "MissingSuper")); - testassert(classInList(classes.c, "MyMissingSuper")); - testassert(classInList(classes.c, "MyMissingSub")); - } - free(classes.v); - - // class name list - const char *image = class_getImageName(objc_getClass("NotMissingRoot")); - testassert(image); - const char **names = objc_copyClassNamesForImage(image, NULL); - testassert(names); - testassert(classInNameList(names, "NotMissingRoot")); - testassert(classInNameList(names, "NotMissingSuper")); - if (weakMissing) { - testassert(! classInNameList(names, "MissingRoot")); - testassert(! classInNameList(names, "MissingSuper")); - } else { - testassert(classInNameList(names, "MissingRoot")); - testassert(classInNameList(names, "MissingSuper")); - } - free(names); - - image = class_getImageName(objc_getClass("MyNotMissingSub")); - testassert(image); - names = objc_copyClassNamesForImage(image, NULL); - testassert(names); - testassert(classInNameList(names, "MyNotMissingSuper")); - testassert(classInNameList(names, "MyNotMissingSub")); - if (weakMissing) { - testassert(! classInNameList(names, "MyMissingSuper")); - testassert(! classInNameList(names, "MyMissingSub")); - } else { - testassert(classInNameList(names, "MyMissingSuper")); - testassert(classInNameList(names, "MyMissingSub")); - } - free(names); - - // methods - testassert(20 == [NotMissingRoot method]); - testassert(21 == [NotMissingSuper method]); - testassert(22 == [MyNotMissingSuper method]); - testassert(23 == [MyNotMissingSub method]); - if (weakMissing) { - testassert(0 == [MissingRoot method]); - testassert(0 == [MissingSuper method]); - testassert(0 == [MyMissingSuper method]); - testassert(0 == [MyMissingSub method]); - } else { - testassert(10 == [MissingRoot method]); - testassert(11 == [MissingSuper method]); - testassert(12 == [MyMissingSuper method]); - testassert(13 == [MyMissingSub method]); - } - - // category methods - testassert(30 == [NotMissingRoot cat_method]); - testassert(31 == [NotMissingSuper cat_method]); - testassert(32 == [MyNotMissingSuper cat_method]); - testassert(33 == [MyNotMissingSub cat_method]); - if (weakMissing) { - testassert(0 == [MissingRoot cat_method]); - testassert(0 == [MissingSuper cat_method]); - testassert(0 == [MyMissingSuper cat_method]); - testassert(0 == [MyMissingSub cat_method]); - } else { - testassert(40 == [MissingRoot cat_method]); - testassert(41 == [MissingSuper cat_method]); - testassert(42 == [MyMissingSuper cat_method]); - testassert(43 == [MyMissingSub cat_method]); - } - - // allocations and ivars - id obj; - NotMissingSuper *obj2; - MissingSuper *obj3; - testassert((obj = [[NotMissingRoot alloc] init])); - RELEASE_VAR(obj); - testassert((obj2 = [[NotMissingSuper alloc] init])); - TESTIVAR(obj2->ivar == 200); - RELEASE_VAR(obj2); - testassert((obj2 = [[MyNotMissingSuper alloc] init])); - TESTIVAR(obj2->ivar == 200); - RELEASE_VAR(obj2); - testassert((obj2 = [[MyNotMissingSub alloc] init])); - TESTIVAR(obj2->ivar == 200); - RELEASE_VAR(obj2); - if (weakMissing) { - testassert([[MissingRoot alloc] init] == nil); - testassert([[MissingSuper alloc] init] == nil); - testassert([[MyMissingSuper alloc] init] == nil); - testassert([[MyMissingSub alloc] init] == nil); - } else { - testassert((obj = [[MissingRoot alloc] init])); - RELEASE_VAR(obj); - testassert((obj3 = [[MissingSuper alloc] init])); - TESTIVAR(obj3->ivar == 100); - RELEASE_VAR(obj3); - testassert((obj3 = [[MyMissingSuper alloc] init])); - TESTIVAR(obj3->ivar == 100); - RELEASE_VAR(obj3); - testassert((obj3 = [[MyMissingSub alloc] init])); - TESTIVAR(obj3->ivar == 100); - RELEASE_VAR(obj3); - } - - *strrchr(argv[0], '.') = 0; - succeed(basename(argv[0])); - return 0; -} - diff --git a/test/weak2.m b/test/weak2.m deleted file mode 100644 index 394363f..0000000 --- a/test/weak2.m +++ /dev/null @@ -1,82 +0,0 @@ -// See instructions in weak.h - -#include "test.h" -#include "weak.h" -#include - -int state = 0; - -static void *noop_fn(void *self, SEL _cmd __unused) { - return self; -} -static void *retain_fn(void *self, SEL _cmd __unused) { - void * (*fn)(void *) = (typeof(fn))_objc_rootRetain; - return fn(self); -} -static void release_fn(void *self, SEL _cmd __unused) { - void (*fn)(void *) = (typeof(fn))_objc_rootRelease; - fn(self); -} -static void *autorelease_fn(void *self, SEL _cmd __unused) { - void * (*fn)(void *) = (typeof(fn))_objc_rootAutorelease; - return fn(self); -} - -#if !defined(EMPTY) - -@implementation MissingRoot -+(void) initialize { } -+(Class) class { return self; } -+(id) alloc { return _objc_rootAlloc(self); } -+(id) allocWithZone:(void*)zone { return _objc_rootAllocWithZone(self, (malloc_zone_t *)zone); } --(id) init { return self; } --(void) dealloc { _objc_rootDealloc(self); } -+(int) method { return 10; } -+(void) load { - class_addMethod(self, sel_registerName("retain"), (IMP)retain_fn, ""); - class_addMethod(self, sel_registerName("release"), (IMP)release_fn, ""); - class_addMethod(self, sel_registerName("autorelease"), (IMP)autorelease_fn, ""); - - class_addMethod(object_getClass(self), sel_registerName("retain"), (IMP)noop_fn, ""); - class_addMethod(object_getClass(self), sel_registerName("release"), (IMP)noop_fn, ""); - class_addMethod(object_getClass(self), sel_registerName("autorelease"), (IMP)noop_fn, ""); - - state++; -} -@end - -@implementation MissingSuper -+(int) method { return 1+[super method]; } --(id) init { self = [super init]; ivar = 100; return self; } -+(void) load { state++; } -@end - -#endif - -@implementation NotMissingRoot -+(void) initialize { } -+(Class) class { return self; } -+(id) alloc { return _objc_rootAlloc(self); } -+(id) allocWithZone:(void*)zone { return _objc_rootAllocWithZone(self, (malloc_zone_t *)zone); } --(id) init { return self; } --(void) dealloc { _objc_rootDealloc(self); } -+(int) method { return 20; } -+(void) load { - class_addMethod(self, sel_registerName("retain"), (IMP)retain_fn, ""); - class_addMethod(self, sel_registerName("release"), (IMP)release_fn, ""); - class_addMethod(self, sel_registerName("autorelease"), (IMP)autorelease_fn, ""); - - class_addMethod(object_getClass(self), sel_registerName("retain"), (IMP)noop_fn, ""); - class_addMethod(object_getClass(self), sel_registerName("release"), (IMP)noop_fn, ""); - class_addMethod(object_getClass(self), sel_registerName("autorelease"), (IMP)noop_fn, ""); - - state++; -} -@end - -@implementation NotMissingSuper -+(int) method { return 1+[super method]; } --(id) init { self = [super init]; ivar = 200; return self; } -+(void) load { state++; } -@end - diff --git a/test/weakcopy.m b/test/weakcopy.m deleted file mode 100644 index 1ffdd8f..0000000 --- a/test/weakcopy.m +++ /dev/null @@ -1,84 +0,0 @@ -// TEST_CONFIG - -#include "test.h" - -#if __OBJC_GC__ && __cplusplus && __i386__ - -int main() -{ - testwarn("rdar://19042235 test disabled for 32-bit objc++ GC because of unknown bit rot"); - succeed(__FILE__); -} - -#else - -#include "testroot.i" -#include -#include -#include - -@interface Weak : TestRoot { - @public - __weak id value; -} -@end -@implementation Weak -@end - -Weak *oldObject; -Weak *newObject; - -void *fn(void *arg __unused) -{ - objc_registerThreadWithCollector(); - - return NULL; -} - -int main() -{ - testonthread(^{ - TestRoot *value; - - PUSH_POOL { - value = [TestRoot new]; - testassert(value); - oldObject = [Weak new]; - testassert(oldObject); - - oldObject->value = value; - testassert(oldObject->value == value); - - newObject = [oldObject copy]; - testassert(newObject); - testassert(newObject->value == oldObject->value); - - newObject->value = nil; - testassert(newObject->value == nil); - testassert(oldObject->value == value); - } POP_POOL; - - testcollect(); - TestRootDealloc = 0; - TestRootFinalize = 0; - RELEASE_VAR(value); - }); - - testcollect(); - testassert(TestRootDealloc || TestRootFinalize); - -#if defined(__OBJC_GC__) || __has_feature(objc_arc) - testassert(oldObject->value == nil); -#else - testassert(oldObject->value != nil); -#endif - testassert(newObject->value == nil); - - RELEASE_VAR(newObject); - RELEASE_VAR(oldObject); - - succeed(__FILE__); - return 0; -} - -#endif diff --git a/test/weakframework-missing.m b/test/weakframework-missing.m deleted file mode 100644 index 43de10b..0000000 --- a/test/weakframework-missing.m +++ /dev/null @@ -1,14 +0,0 @@ -/* -TEST_BUILD - $C{COMPILE} $DIR/weak2.m -DWEAK_FRAMEWORK=1 -DWEAK_IMPORT= -UEMPTY -dynamiclib -o libweakframework.dylib - - $C{COMPILE} $DIR/weakframework-missing.m -L. -weak-lweakframework -o weakframework-missing.out - - $C{COMPILE} $DIR/weak2.m -DWEAK_FRAMEWORK=1 -DWEAK_IMPORT= -DEMPTY= -dynamiclib -o libweakframework.dylib - -END -*/ - -#define WEAK_FRAMEWORK 1 -#define WEAK_IMPORT -#include "weak.m" diff --git a/test/weakframework-not-missing.m b/test/weakframework-not-missing.m deleted file mode 100644 index 2a11104..0000000 --- a/test/weakframework-not-missing.m +++ /dev/null @@ -1,11 +0,0 @@ -/* -TEST_BUILD - $C{COMPILE} $DIR/weak2.m -DWEAK_FRAMEWORK=1 -DWEAK_IMPORT= -UEMPTY -dynamiclib -o libweakframework.dylib - - $C{COMPILE} $DIR/weakframework-not-missing.m -L. -weak-lweakframework -o weakframework-not-missing.out -END -*/ - -#define WEAK_FRAMEWORK 1 -#define WEAK_IMPORT -#include "weak.m" diff --git a/test/weakimport-missing.m b/test/weakimport-missing.m deleted file mode 100644 index bd86f43..0000000 --- a/test/weakimport-missing.m +++ /dev/null @@ -1,13 +0,0 @@ -/* -TEST_BUILD - $C{COMPILE} $DIR/weak2.m -UWEAK_FRAMEWORK -DWEAK_IMPORT=__attribute__\\(\\(weak_import\\)\\) -UEMPTY -dynamiclib -o libweakimport.dylib - - $C{COMPILE} $DIR/weakimport-missing.m -L. -weak-lweakimport -o weakimport-missing.out - - $C{COMPILE} $DIR/weak2.m -UWEAK_FRAMEWORK -DWEAK_IMPORT=__attribute__\\(\\(weak_import\\)\\) -DEMPTY= -dynamiclib -o libweakimport.dylib -END -*/ - -// #define WEAK_FRAMEWORK -#define WEAK_IMPORT __attribute__((weak_import)) -#include "weak.m" diff --git a/test/weakimport-not-missing.m b/test/weakimport-not-missing.m deleted file mode 100644 index 440f79e..0000000 --- a/test/weakimport-not-missing.m +++ /dev/null @@ -1,11 +0,0 @@ -/* -TEST_BUILD - $C{COMPILE} $DIR/weak2.m -UWEAK_FRAMEWORK -DWEAK_IMPORT=__attribute__\\(\\(weak_import\\)\\) -UEMPTY -dynamiclib -o libweakimport.dylib - - $C{COMPILE} $DIR/weakimport-not-missing.m -L. -weak-lweakimport -o weakimport-not-missing.out -END -*/ - -// #define WEAK_FRAMEWORK -#define WEAK_IMPORT __attribute__((weak_import)) -#include "weak.m" diff --git a/test/weakrace.m b/test/weakrace.m deleted file mode 100644 index 2ff2ea9..0000000 --- a/test/weakrace.m +++ /dev/null @@ -1,76 +0,0 @@ -// TEST_CONFIG MEM=mrc - -#include "test.h" -#include - -static semaphore_t go1; -static semaphore_t go2; -static semaphore_t done; - -#define VARCOUNT 100000 -static id obj; -static id vars[VARCOUNT]; - - -void *destroyer(void *arg __unused) -{ - while (1) { - semaphore_wait(go1); - for (int i = 0; i < VARCOUNT; i++) { - objc_destroyWeak(&vars[i]); - } - semaphore_signal(done); - } -} - - -void *deallocator(void *arg __unused) -{ - while (1) { - semaphore_wait(go2); - [obj release]; - semaphore_signal(done); - } -} - - -void cycle(void) -{ - // rdar://12896779 objc_destroyWeak() versus weak clear in dealloc - - // Clean up from previous cycle - objc_destroyWeak() doesn't set var to nil - for (int i = 0; i < VARCOUNT; i++) { - vars[i] = nil; - } - - obj = [NSObject new]; - for (int i = 0; i < VARCOUNT; i++) { - objc_storeWeak(&vars[i], obj); - } - - // let destroyer start before deallocator runs - semaphore_signal(go1); - sched_yield(); - semaphore_signal(go2); - - semaphore_wait(done); - semaphore_wait(done); -} - - -int main() -{ - semaphore_create(mach_task_self(), &go1, 0, 0); - semaphore_create(mach_task_self(), &go2, 0, 0); - semaphore_create(mach_task_self(), &done, 0, 0); - - pthread_t th[2]; - pthread_create(&th[1], NULL, deallocator, NULL); - pthread_create(&th[1], NULL, destroyer, NULL); - - for (int i = 0; i < 100; i++) { - cycle(); - } - - succeed(__FILE__); -} diff --git a/test/xref.m b/test/xref.m deleted file mode 100644 index 6418e3f..0000000 --- a/test/xref.m +++ /dev/null @@ -1,32 +0,0 @@ -// TEST_CFLAGS - -#include -#include -#include - -#include "test.h" - -int main() -{ - // rdar://8350188 External references (handles) - - id object = [NSObject new]; - testassert(object); - - // STRONG - objc_xref_t xref = _object_addExternalReference(object, OBJC_XREF_STRONG); - testassert(xref); - testassert(_object_readExternalReference(xref) == object); - _object_removeExternalReference(xref); - // TODO: expect a crash if a stale xref is used. - - // WEAK - xref = _object_addExternalReference(object, OBJC_XREF_WEAK); - testassert(xref); - testassert(_object_readExternalReference(xref) == object); - _object_removeExternalReference(xref); - - RELEASE_VAR(object); - - succeed(__FILE__); -} diff --git a/test/zone.m b/test/zone.m deleted file mode 100644 index 46ec5ea..0000000 --- a/test/zone.m +++ /dev/null @@ -1,40 +0,0 @@ -// TEST_CONFIG - -#include "test.h" -#include -#include - -// Look for malloc zone "ObjC" iff OBJC_USE_INTERNAL_ZONE is set. -// This fails if objc tries to allocate before checking its own -// environment variables (rdar://6688423) - -int main() -{ - if (is_guardmalloc()) { - // guard malloc confuses this test - succeed(__FILE__); - } - - kern_return_t kr; - vm_address_t *zones; - unsigned int count, i; - BOOL has_objc = NO, want_objc = NO; - - want_objc = (getenv("OBJC_USE_INTERNAL_ZONE") != NULL) ? YES : NO; - testprintf("want objc %s\n", want_objc ? "YES" : "NO"); - - kr = malloc_get_all_zones(mach_task_self(), NULL, &zones, &count); - testassert(!kr); - for (i = 0; i < count; i++) { - const char *name = malloc_get_zone_name((malloc_zone_t *)zones[i]); - if (name) { - BOOL is_objc = (0 == strcmp(name, "ObjC_Internal")) ? YES : NO; - if (is_objc) has_objc = YES; - testprintf("zone %s\n", name); - } - } - - testassert(want_objc == has_objc); - - succeed(__FILE__); -} diff --git a/version.bat b/version.bat old mode 100644 new mode 100755 From 3bd31916e8507a394bc12ace522840c29881f3d6 Mon Sep 17 00:00:00 2001 From: wangzhaowei Date: Fri, 28 Jul 2017 11:41:49 +0800 Subject: [PATCH 02/12] Version 709 --- objc.xcodeproj/project.pbxproj | 2 - runtime/Messengers.subproj/objc-msg-x86_64.s | 45 --- runtime/NSObject.h | 2 +- runtime/NSObject.mm | 103 +++-- runtime/hashtable2.mm | 4 +- runtime/objc-accessors.mm | 7 +- runtime/objc-class-old.mm | 2 +- runtime/objc-class.mm | 4 +- runtime/objc-errors.mm | 2 +- runtime/objc-exception.mm | 14 +- runtime/objc-initialize.mm | 2 +- runtime/objc-internal.h | 8 + runtime/objc-lockdebug.h | 22 ++ runtime/objc-lockdebug.mm | 382 ++++++++++++------- runtime/objc-locks-new.h | 38 ++ runtime/objc-locks-old.h | 40 ++ runtime/objc-locks.h | 64 ++++ runtime/objc-opt.mm | 16 + runtime/objc-os.h | 81 +++- runtime/objc-os.mm | 222 ++++++++++- runtime/objc-private.h | 53 ++- runtime/objc-references.mm | 15 +- runtime/objc-runtime-new.h | 42 +- runtime/objc-runtime-new.mm | 128 +++++-- runtime/objc-runtime-old.mm | 4 +- runtime/objc-sync.mm | 4 +- runtime/objc.h | 37 +- runtime/runtime.h | 44 --- 28 files changed, 1032 insertions(+), 355 deletions(-) create mode 100644 runtime/objc-locks-new.h create mode 100644 runtime/objc-locks-old.h create mode 100644 runtime/objc-locks.h diff --git a/objc.xcodeproj/project.pbxproj b/objc.xcodeproj/project.pbxproj index 2c6187d..abf396d 100644 --- a/objc.xcodeproj/project.pbxproj +++ b/objc.xcodeproj/project.pbxproj @@ -614,7 +614,6 @@ "OTHER_LDFLAGS[sdk=iphonesimulator*][arch=*]" = "-lc++abi"; "OTHER_LDFLAGS[sdk=macosx*]" = ( "-lCrashReporterClient", - "-lauto", "-lc++abi", "-Xlinker", "-sectalign", @@ -668,7 +667,6 @@ "OTHER_LDFLAGS[sdk=iphonesimulator*][arch=*]" = "-lc++abi"; "OTHER_LDFLAGS[sdk=macosx*]" = ( "-lCrashReporterClient", - "-lauto", "-lc++abi", "-Xlinker", "-sectalign", diff --git a/runtime/Messengers.subproj/objc-msg-x86_64.s b/runtime/Messengers.subproj/objc-msg-x86_64.s index 343b300..8670555 100644 --- a/runtime/Messengers.subproj/objc-msg-x86_64.s +++ b/runtime/Messengers.subproj/objc-msg-x86_64.s @@ -1281,49 +1281,4 @@ LCacheMiss: .quad 0 .quad 0 - - // Workaround for Skype evil (rdar://19715989) - - .text - .align 4 - .private_extern _map_images - .private_extern _map_2_images - .private_extern _hax -_hax: - nop - nop - nop - nop - nop - nop - nop - nop - nop - nop - nop - nop - nop - nop - nop - nop - nop -_map_images: - nop - nop - nop - nop - nop - nop - nop - nop - nop - nop - nop - nop - nop - nop - nop - nop - jmp _map_2_images - #endif diff --git a/runtime/NSObject.h b/runtime/NSObject.h index f42b446..7bd8611 100644 --- a/runtime/NSObject.h +++ b/runtime/NSObject.h @@ -18,7 +18,7 @@ @property (readonly) NSUInteger hash; @property (readonly) Class superclass; -- (Class)class OBJC_SWIFT_UNAVAILABLE("use 'anObject.dynamicType' instead"); +- (Class)class OBJC_SWIFT_UNAVAILABLE("use 'type(of: anObject)' instead"); - (instancetype)self; - (id)performSelector:(SEL)aSelector; diff --git a/runtime/NSObject.mm b/runtime/NSObject.mm index c7e7f43..7a7b391 100644 --- a/runtime/NSObject.mm +++ b/runtime/NSObject.mm @@ -136,6 +136,10 @@ void _objc_setBadAllocHandler(id(*newHandler)(Class)) // don't want the table to act as a root for `leaks`. typedef objc::DenseMap,size_t,true> RefcountMap; +// Template parameters. +enum HaveOld { DontHaveOld = false, DoHaveOld = true }; +enum HaveNew { DontHaveNew = false, DoHaveNew = true }; + struct SideTable { spinlock_t slock; RefcountMap refcnts; @@ -151,46 +155,58 @@ void _objc_setBadAllocHandler(id(*newHandler)(Class)) void lock() { slock.lock(); } void unlock() { slock.unlock(); } + void forceReset() { slock.forceReset(); } // Address-ordered lock discipline for a pair of side tables. - template + template static void lockTwo(SideTable *lock1, SideTable *lock2); - template + template static void unlockTwo(SideTable *lock1, SideTable *lock2); }; template<> -void SideTable::lockTwo(SideTable *lock1, SideTable *lock2) { +void SideTable::lockTwo + (SideTable *lock1, SideTable *lock2) +{ spinlock_t::lockTwo(&lock1->slock, &lock2->slock); } template<> -void SideTable::lockTwo(SideTable *lock1, SideTable *) { +void SideTable::lockTwo + (SideTable *lock1, SideTable *) +{ lock1->lock(); } template<> -void SideTable::lockTwo(SideTable *, SideTable *lock2) { +void SideTable::lockTwo + (SideTable *, SideTable *lock2) +{ lock2->lock(); } template<> -void SideTable::unlockTwo(SideTable *lock1, SideTable *lock2) { +void SideTable::unlockTwo + (SideTable *lock1, SideTable *lock2) +{ spinlock_t::unlockTwo(&lock1->slock, &lock2->slock); } template<> -void SideTable::unlockTwo(SideTable *lock1, SideTable *) { +void SideTable::unlockTwo + (SideTable *lock1, SideTable *) +{ lock1->unlock(); } template<> -void SideTable::unlockTwo(SideTable *, SideTable *lock2) { +void SideTable::unlockTwo + (SideTable *, SideTable *lock2) +{ lock2->unlock(); } - // We cannot use a C++ static initializer to initialize SideTables because @@ -211,6 +227,29 @@ static void SideTableInit() { // anonymous namespace }; +void SideTableLockAll() { + SideTables().lockAll(); +} + +void SideTableUnlockAll() { + SideTables().unlockAll(); +} + +void SideTableForceResetAll() { + SideTables().forceResetAll(); +} + +void SideTableDefineLockOrder() { + SideTables().defineLockOrder(); +} + +void SideTableLocksPrecedeLock(const void *newlock) { + SideTables().precedeLock(newlock); +} + +void SideTableLocksSucceedLock(const void *oldlock) { + SideTables().succeedLock(oldlock); +} // // The -fobjc-arc flag causes the compiler to issue calls to objc_{retain/release/autorelease/retain_block} @@ -256,12 +295,16 @@ BOOL objc_should_deallocate(id object) { // If CrashIfDeallocating is true, the process is halted if newObj is // deallocating or newObj's class does not support weak references. // If CrashIfDeallocating is false, nil is stored instead. -template +enum CrashIfDeallocating { + DontCrashIfDeallocating = false, DoCrashIfDeallocating = true +}; +template static id storeWeak(id *location, objc_object *newObj) { - assert(HaveOld || HaveNew); - if (!HaveNew) assert(newObj == nil); + assert(haveOld || haveNew); + if (!haveNew) assert(newObj == nil); Class previouslyInitializedClass = nil; id oldObj; @@ -272,34 +315,34 @@ BOOL objc_should_deallocate(id object) { // Order by lock address to prevent lock ordering problems. // Retry if the old value changes underneath us. retry: - if (HaveOld) { + if (haveOld) { oldObj = *location; oldTable = &SideTables()[oldObj]; } else { oldTable = nil; } - if (HaveNew) { + if (haveNew) { newTable = &SideTables()[newObj]; } else { newTable = nil; } - SideTable::lockTwo(oldTable, newTable); + SideTable::lockTwo(oldTable, newTable); - if (HaveOld && *location != oldObj) { - SideTable::unlockTwo(oldTable, newTable); + if (haveOld && *location != oldObj) { + SideTable::unlockTwo(oldTable, newTable); goto retry; } // Prevent a deadlock between the weak reference machinery // and the +initialize machinery by ensuring that no // weakly-referenced object has an un-+initialized isa. - if (HaveNew && newObj) { + if (haveNew && newObj) { Class cls = newObj->getIsa(); if (cls != previouslyInitializedClass && !((objc_class *)cls)->isInitialized()) { - SideTable::unlockTwo(oldTable, newTable); + SideTable::unlockTwo(oldTable, newTable); _class_initialize(_class_getNonMetaClass(cls, (id)newObj)); // If this class is finished with +initialize then we're good. @@ -315,15 +358,15 @@ BOOL objc_should_deallocate(id object) { } // Clean up old value, if any. - if (HaveOld) { + if (haveOld) { weak_unregister_no_lock(&oldTable->weak_table, oldObj, location); } // Assign new value, if any. - if (HaveNew) { - newObj = (objc_object *)weak_register_no_lock(&newTable->weak_table, - (id)newObj, location, - CrashIfDeallocating); + if (haveNew) { + newObj = (objc_object *) + weak_register_no_lock(&newTable->weak_table, (id)newObj, location, + crashIfDeallocating); // weak_register_no_lock returns nil if weak store should be rejected // Set is-weakly-referenced bit in refcount table. @@ -338,7 +381,7 @@ BOOL objc_should_deallocate(id object) { // No new value. The storage is not changed. } - SideTable::unlockTwo(oldTable, newTable); + SideTable::unlockTwo(oldTable, newTable); return (id)newObj; } @@ -356,7 +399,7 @@ BOOL objc_should_deallocate(id object) { id objc_storeWeak(id *location, id newObj) { - return storeWeak + return storeWeak (location, (objc_object *)newObj); } @@ -374,7 +417,7 @@ BOOL objc_should_deallocate(id object) { id objc_storeWeakOrNil(id *location, id newObj) { - return storeWeak + return storeWeak (location, (objc_object *)newObj); } @@ -403,7 +446,7 @@ BOOL objc_should_deallocate(id object) { return nil; } - return storeWeak + return storeWeak (location, (objc_object*)newObj); } @@ -415,7 +458,7 @@ BOOL objc_should_deallocate(id object) { return nil; } - return storeWeak + return storeWeak (location, (objc_object*)newObj); } @@ -434,7 +477,7 @@ BOOL objc_should_deallocate(id object) { void objc_destroyWeak(id *location) { - (void)storeWeak + (void)storeWeak (location, nil); } diff --git a/runtime/hashtable2.mm b/runtime/hashtable2.mm index 238f381..e869f1d 100644 --- a/runtime/hashtable2.mm +++ b/runtime/hashtable2.mm @@ -562,7 +562,7 @@ static int isEqualStrStructKey (const void *info, const void *data1, const void static char *z = NULL; static size_t zSize = 0; -static mutex_t uniquerLock; +mutex_t NXUniqueStringLock; static const char *CopyIntoReadOnly (const char *str) { size_t len = strlen (str) + 1; @@ -574,7 +574,7 @@ static int isEqualStrStructKey (const void *info, const void *data1, const void return result; } - mutex_locker_t lock(uniquerLock); + mutex_locker_t lock(NXUniqueStringLock); if (zSize < len) { zSize = CHUNK_SIZE *((len + CHUNK_SIZE - 1) / CHUNK_SIZE); /* not enough room, we try to allocate. If no room left, too bad */ diff --git a/runtime/objc-accessors.mm b/runtime/objc-accessors.mm index 25ec71b..612abea 100644 --- a/runtime/objc-accessors.mm +++ b/runtime/objc-accessors.mm @@ -39,10 +39,9 @@ @interface __NSMutableCopyable - (id)mutableCopyWithZone:(void *)zone; @end -// These locks must not be at function scope. -static StripedMap PropertyLocks; -static StripedMap StructLocks; -static StripedMap CppObjectLocks; +StripedMap PropertyLocks; +StripedMap StructLocks; +StripedMap CppObjectLocks; #define MUTABLE_COPY 2 diff --git a/runtime/objc-class-old.mm b/runtime/objc-class-old.mm index f62bdee..2fe27d8 100644 --- a/runtime/objc-class-old.mm +++ b/runtime/objc-class-old.mm @@ -1504,7 +1504,7 @@ unsigned int method_getArgumentInfo(Method m, int arg, } -static spinlock_t impLock; +spinlock_t impLock; IMP method_setImplementation(Method m_gen, IMP imp) { diff --git a/runtime/objc-class.mm b/runtime/objc-class.mm index d16ebc2..9e96b6d 100644 --- a/runtime/objc-class.mm +++ b/runtime/objc-class.mm @@ -816,6 +816,9 @@ IMP class_getMethodImplementation_stret(Class cls, SEL sel) /*********************************************************************** * instrumentObjcMessageSends **********************************************************************/ +// Define this everywhere even if it isn't used to simplify fork() safety code. +spinlock_t objcMsgLogLock; + #if !SUPPORT_MESSAGE_LOGGING void instrumentObjcMessageSends(BOOL flag) @@ -826,7 +829,6 @@ void instrumentObjcMessageSends(BOOL flag) bool objcMsgLogEnabled = false; static int objcMsgLogFD = -1; -static spinlock_t objcMsgLogLock; bool logMessageSend(bool isClassMethod, const char *objectsClass, diff --git a/runtime/objc-errors.mm b/runtime/objc-errors.mm index 4c426b0..6d65ca2 100644 --- a/runtime/objc-errors.mm +++ b/runtime/objc-errors.mm @@ -85,7 +85,7 @@ static bool isUTF8Continuation(char c) } // Add "message" to any forthcoming crash log. -static mutex_t crashlog_lock; +mutex_t crashlog_lock; static void _objc_crashlog(const char *message) { char *newmsg; diff --git a/runtime/objc-exception.mm b/runtime/objc-exception.mm index d6b1d83..d510d23 100644 --- a/runtime/objc-exception.mm +++ b/runtime/objc-exception.mm @@ -1059,7 +1059,6 @@ static struct frame_range findHandler(void) struct alt_handler_list *next_DEBUGONLY; }; -static mutex_t DebugLock; static struct alt_handler_list *DebugLists; static uintptr_t DebugCounter; @@ -1080,7 +1079,7 @@ static struct frame_range findHandler(void) if (DebugAltHandlers) { // Save this list so the debug code can find it from other threads - mutex_locker_t lock(DebugLock); + mutex_locker_t lock(AltHandlerDebugLock); list->next_DEBUGONLY = DebugLists; DebugLists = list; } @@ -1095,7 +1094,7 @@ void _destroyAltHandlerList(struct alt_handler_list *list) if (list) { if (DebugAltHandlers) { // Detach from the list-of-lists. - mutex_locker_t lock(DebugLock); + mutex_locker_t lock(AltHandlerDebugLock); struct alt_handler_list **listp = &DebugLists; while (*listp && *listp != list) listp = &(*listp)->next_DEBUGONLY; if (*listp) *listp = (*listp)->next_DEBUGONLY; @@ -1160,7 +1159,7 @@ uintptr_t objc_addExceptionHandler(objc_exception_handler fn, void *context) if (DebugAltHandlers) { // Record backtrace in case this handler is misused later. - mutex_locker_t lock(DebugLock); + mutex_locker_t lock(AltHandlerDebugLock); token = DebugCounter++; if (token == 0) token = DebugCounter++; @@ -1274,7 +1273,7 @@ void alt_handler_error(uintptr_t token) "or break in objc_alt_handler_error() to debug."); if (DebugAltHandlers) { - DebugLock.lock(); + AltHandlerDebugLock.lock(); // Search other threads' alt handler lists for this handler. struct alt_handler_list *list; @@ -1314,7 +1313,7 @@ void alt_handler_error(uintptr_t token) } } done: - DebugLock.unlock(); + AltHandlerDebugLock.unlock(); } @@ -1394,3 +1393,6 @@ void exception_init(void) // __OBJC2__ #endif + +// Define this everywhere even if it isn't used, to simplify fork() safety code +mutex_t AltHandlerDebugLock; diff --git a/runtime/objc-initialize.mm b/runtime/objc-initialize.mm index 0857305..0f410b6 100644 --- a/runtime/objc-initialize.mm +++ b/runtime/objc-initialize.mm @@ -99,7 +99,7 @@ /* classInitLock protects CLS_INITIALIZED and CLS_INITIALIZING, and * is signalled when any class is done initializing. * Threads that are waiting for a class to finish initializing wait on this. */ -static monitor_t classInitLock; +monitor_t classInitLock; /*********************************************************************** diff --git a/runtime/objc-internal.h b/runtime/objc-internal.h index 5bcb28c..6a28c59 100644 --- a/runtime/objc-internal.h +++ b/runtime/objc-internal.h @@ -99,6 +99,14 @@ OBJC_EXPORT void _objc_init(void) OBJC_AVAILABLE(10.12, 10.0, 10.0, 3.0); #endif +// fork() safety called by libSystem +OBJC_EXPORT void _objc_atfork_prepare(void) + OBJC_AVAILABLE(10.12, 10.0, 10.0, 3.0); +OBJC_EXPORT void _objc_atfork_parent(void) + OBJC_AVAILABLE(10.12, 10.0, 10.0, 3.0); +OBJC_EXPORT void _objc_atfork_child(void) + OBJC_AVAILABLE(10.12, 10.0, 10.0, 3.0); + // Return YES if GC is on and `object` is a GC allocation. OBJC_EXPORT BOOL objc_isAuto(id object) __OSX_DEPRECATED(10.4, 10.8, "it always returns NO") diff --git a/runtime/objc-lockdebug.h b/runtime/objc-lockdebug.h index 071064d..211b7eb 100644 --- a/runtime/objc-lockdebug.h +++ b/runtime/objc-lockdebug.h @@ -21,12 +21,26 @@ * @APPLE_LICENSE_HEADER_END@ */ +#if DEBUG +extern void lockdebug_assert_all_locks_locked(); +extern void lockdebug_assert_no_locks_locked(); +extern void lockdebug_setInForkPrepare(bool); +extern void lockdebug_lock_precedes_lock(const void *oldlock, const void *newlock); +#else +static inline void lockdebug_assert_all_locks_locked() { } +static inline void lockdebug_assert_no_locks_locked() { } +static inline void lockdebug_setInForkPrepare(bool) { } +static inline void lockdebug_lock_precedes_lock(const void *, const void *) { } +#endif + +extern void lockdebug_remember_mutex(mutex_tt *lock); extern void lockdebug_mutex_lock(mutex_tt *lock); extern void lockdebug_mutex_try_lock(mutex_tt *lock); extern void lockdebug_mutex_unlock(mutex_tt *lock); extern void lockdebug_mutex_assert_locked(mutex_tt *lock); extern void lockdebug_mutex_assert_unlocked(mutex_tt *lock); +static inline void lockdebug_remember_mutex(mutex_tt *lock) { } static inline void lockdebug_mutex_lock(mutex_tt *lock) { } static inline void lockdebug_mutex_try_lock(mutex_tt *lock) { } static inline void lockdebug_mutex_unlock(mutex_tt *lock) { } @@ -34,12 +48,14 @@ static inline void lockdebug_mutex_assert_locked(mutex_tt *lock) { } static inline void lockdebug_mutex_assert_unlocked(mutex_tt *lock) { } +extern void lockdebug_remember_monitor(monitor_tt *lock); extern void lockdebug_monitor_enter(monitor_tt *lock); extern void lockdebug_monitor_leave(monitor_tt *lock); extern void lockdebug_monitor_wait(monitor_tt *lock); extern void lockdebug_monitor_assert_locked(monitor_tt *lock); extern void lockdebug_monitor_assert_unlocked(monitor_tt *lock); +static inline void lockdebug_remember_monitor(monitor_tt *lock) { } static inline void lockdebug_monitor_enter(monitor_tt *lock) { } static inline void lockdebug_monitor_leave(monitor_tt *lock) { } static inline void lockdebug_monitor_wait(monitor_tt *lock) { } @@ -47,6 +63,8 @@ static inline void lockdebug_monitor_assert_locked(monitor_tt *lock) { } static inline void lockdebug_monitor_assert_unlocked(monitor_tt *lock) {} +extern void +lockdebug_remember_recursive_mutex(recursive_mutex_tt *lock); extern void lockdebug_recursive_mutex_lock(recursive_mutex_tt *lock); extern void @@ -56,6 +74,8 @@ lockdebug_recursive_mutex_assert_locked(recursive_mutex_tt *lock); extern void lockdebug_recursive_mutex_assert_unlocked(recursive_mutex_tt *lock); +static inline void +lockdebug_remember_recursive_mutex(recursive_mutex_tt *lock) { } static inline void lockdebug_recursive_mutex_lock(recursive_mutex_tt *lock) { } static inline void @@ -66,6 +86,7 @@ static inline void lockdebug_recursive_mutex_assert_unlocked(recursive_mutex_tt *lock) { } +extern void lockdebug_remember_rwlock(rwlock_tt *lock); extern void lockdebug_rwlock_read(rwlock_tt *lock); extern void lockdebug_rwlock_try_read_success(rwlock_tt *lock); extern void lockdebug_rwlock_unlock_read(rwlock_tt *lock); @@ -77,6 +98,7 @@ extern void lockdebug_rwlock_assert_writing(rwlock_tt *lock); extern void lockdebug_rwlock_assert_locked(rwlock_tt *lock); extern void lockdebug_rwlock_assert_unlocked(rwlock_tt *lock); +static inline void lockdebug_remember_rwlock(rwlock_tt *) { } static inline void lockdebug_rwlock_read(rwlock_tt *) { } static inline void lockdebug_rwlock_try_read_success(rwlock_tt *) { } static inline void lockdebug_rwlock_unlock_read(rwlock_tt *) { } diff --git a/runtime/objc-lockdebug.mm b/runtime/objc-lockdebug.mm index ed94011..7b38de6 100644 --- a/runtime/objc-lockdebug.mm +++ b/runtime/objc-lockdebug.mm @@ -30,115 +30,224 @@ #if DEBUG && !TARGET_OS_WIN32 +#include + + +/*********************************************************************** +* Thread-local bool set during _objc_atfork_prepare(). +* That function is allowed to break some lock ordering rules. +**********************************************************************/ + +static tls_key_t fork_prepare_tls; + +void +lockdebug_setInForkPrepare(bool inForkPrepare) +{ + INIT_ONCE_PTR(fork_prepare_tls, tls_create(nil), (void)0); + tls_set(fork_prepare_tls, (void*)inForkPrepare); +} + +static bool +inForkPrepare() +{ + INIT_ONCE_PTR(fork_prepare_tls, tls_create(nil), (void)0); + return (bool)tls_get(fork_prepare_tls); +} + + + +/*********************************************************************** +* Lock order graph. +* "lock X precedes lock Y" means that X must be acquired first. +* This property is transitive. +**********************************************************************/ + +struct lockorder { + const void *l; + std::vector predecessors; +}; + +static std::unordered_map lockOrderList; + +static bool +lockPrecedesLock(const lockorder& oldlock, const lockorder& newlock) +{ + for (const auto *pre : newlock.predecessors) { + if (&oldlock == pre) return true; + if (lockPrecedesLock(oldlock, *pre)) return true; + } + return false; +} + +static bool +lockPrecedesLock(const void *oldlock, const void *newlock) +{ + auto oldorder = lockOrderList.find(oldlock); + auto neworder = lockOrderList.find(newlock); + if (neworder == lockOrderList.end() || oldorder == lockOrderList.end()) { + return false; + } + return lockPrecedesLock(oldorder->second, neworder->second); +} + +static bool +lockUnorderedWithLock(const void *oldlock, const void *newlock) +{ + auto oldorder = lockOrderList.find(oldlock); + auto neworder = lockOrderList.find(newlock); + if (neworder == lockOrderList.end() || oldorder == lockOrderList.end()) { + return true; + } + + if (lockPrecedesLock(oldorder->second, neworder->second) || + lockPrecedesLock(neworder->second, oldorder->second)) + { + return false; + } + + return true; +} + +void lockdebug_lock_precedes_lock(const void *oldlock, const void *newlock) +{ + if (lockPrecedesLock(newlock, oldlock)) { + _objc_fatal("contradiction in lock order declaration"); + } + + auto oldorder = lockOrderList.find(oldlock); + auto neworder = lockOrderList.find(newlock); + if (oldorder == lockOrderList.end()) { + lockOrderList[oldlock] = lockorder{oldlock, {}}; + oldorder = lockOrderList.find(oldlock); + } + if (neworder == lockOrderList.end()) { + lockOrderList[newlock] = lockorder{newlock, {}}; + neworder = lockOrderList.find(newlock); + } + + neworder->second.predecessors.push_back(&oldorder->second); +} + + /*********************************************************************** * Recording - per-thread list of mutexes and monitors held **********************************************************************/ -typedef struct { - void *l; // the lock itself - int k; // the kind of lock it is (MUTEX, MONITOR, etc) - int i; // the lock's nest count -} lockcount; +enum class lockkind { + MUTEX = 1, MONITOR = 2, RDLOCK = 3, WRLOCK = 4, RECURSIVE = 5 +}; + +#define MUTEX lockkind::MUTEX +#define MONITOR lockkind::MONITOR +#define RDLOCK lockkind::RDLOCK +#define WRLOCK lockkind::WRLOCK +#define RECURSIVE lockkind::RECURSIVE + +struct lockcount { + lockkind k; // the kind of lock it is (MUTEX, MONITOR, etc) + int i; // the lock's nest count +}; -#define MUTEX 1 -#define MONITOR 2 -#define RDLOCK 3 -#define WRLOCK 4 -#define RECURSIVE 5 +using objc_lock_list = std::unordered_map; -typedef struct _objc_lock_list { - int allocated; - int used; - lockcount list[0]; -} _objc_lock_list; +// Thread-local list of locks owned by a thread. +// Used by lock ownership checks. static tls_key_t lock_tls; +// Global list of all locks. +// Used by fork() safety check. +// This can't be a static struct because of C++ initialization order problems. +static objc_lock_list& AllLocks() { + static objc_lock_list *locks; + INIT_ONCE_PTR(locks, new objc_lock_list, (void)0); + return *locks; +} + + static void destroyLocks(void *value) { - _objc_lock_list *locks = (_objc_lock_list *)value; + auto locks = (objc_lock_list *)value; // fixme complain about any still-held locks? - if (locks) free(locks); + if (locks) delete locks; } -static struct _objc_lock_list * -getLocks(BOOL create) +static objc_lock_list& +ownedLocks() { - _objc_lock_list *locks; - // Use a dedicated tls key to prevent differences vs non-debug in // usage of objc's other tls keys (required for some unit tests). INIT_ONCE_PTR(lock_tls, tls_create(&destroyLocks), (void)0); - locks = (_objc_lock_list *)tls_get(lock_tls); + auto locks = (objc_lock_list *)tls_get(lock_tls); if (!locks) { - if (!create) { - return NULL; - } else { - locks = (_objc_lock_list *)calloc(1, sizeof(_objc_lock_list) + sizeof(lockcount) * 16); - locks->allocated = 16; - locks->used = 0; - tls_set(lock_tls, locks); - } - } - - if (locks->allocated == locks->used) { - if (!create) { - return locks; - } else { - _objc_lock_list *oldlocks = locks; - locks = (_objc_lock_list *)calloc(1, sizeof(_objc_lock_list) + 2 * oldlocks->used * sizeof(lockcount)); - locks->used = oldlocks->used; - locks->allocated = oldlocks->used * 2; - memcpy(locks->list, oldlocks->list, locks->used * sizeof(lockcount)); - tls_set(lock_tls, locks); - free(oldlocks); - } + locks = new objc_lock_list; + tls_set(lock_tls, locks); } - return locks; + return *locks; } -static BOOL -hasLock(_objc_lock_list *locks, void *lock, int kind) +static bool +hasLock(objc_lock_list& locks, const void *lock, lockkind kind) { - int i; - if (!locks) return NO; - - for (i = 0; i < locks->used; i++) { - if (locks->list[i].l == lock && locks->list[i].k == kind) return YES; - } - return NO; + auto iter = locks.find(lock); + if (iter != locks.end() && iter->second.k == kind) return true; + return false; } +static const char *sym(const void *lock) +{ + Dl_info info; + int ok = dladdr(lock, &info); + if (ok && info.dli_sname && info.dli_sname[0]) return info.dli_sname; + else return "??"; +} + static void -setLock(_objc_lock_list *locks, void *lock, int kind) +setLock(objc_lock_list& locks, const void *lock, lockkind kind) { - int i; - for (i = 0; i < locks->used; i++) { - if (locks->list[i].l == lock && locks->list[i].k == kind) { - locks->list[i].i++; - return; + // Check if we already own this lock. + auto iter = locks.find(lock); + if (iter != locks.end() && iter->second.k == kind) { + iter->second.i++; + return; + } + + // Newly-acquired lock. Verify lock ordering. + // Locks not in AllLocks are exempt (i.e. @synchronize locks) + if (&locks != &AllLocks() && AllLocks().find(lock) != AllLocks().end()) { + for (auto& oldlock : locks) { + if (lockPrecedesLock(lock, oldlock.first)) { + _objc_fatal("lock %p (%s) incorrectly acquired before %p (%s)", + oldlock.first, sym(oldlock.first), lock, sym(lock)); + } + if (!inForkPrepare() && + lockUnorderedWithLock(lock, oldlock.first)) + { + // _objc_atfork_prepare is allowed to acquire + // otherwise-unordered locks, but nothing else may. + _objc_fatal("lock %p (%s) acquired before %p (%s) " + "with no defined lock order", + oldlock.first, sym(oldlock.first), lock, sym(lock)); + } } } - locks->list[locks->used].l = lock; - locks->list[locks->used].i = 1; - locks->list[locks->used].k = kind; - locks->used++; + locks[lock] = lockcount{kind, 1}; } static void -clearLock(_objc_lock_list *locks, void *lock, int kind) -{ - int i; - for (i = 0; i < locks->used; i++) { - if (locks->list[i].l == lock && locks->list[i].k == kind) { - if (--locks->list[i].i == 0) { - locks->list[i].l = NULL; - locks->list[i] = locks->list[--locks->used]; +clearLock(objc_lock_list& locks, const void *lock, lockkind kind) +{ + auto iter = locks.find(lock); + if (iter != locks.end()) { + auto& l = iter->second; + if (l.k == kind) { + if (--l.i == 0) { + locks.erase(iter); } return; } @@ -149,49 +258,67 @@ /*********************************************************************** -* Mutex checking +* fork() safety checking **********************************************************************/ -#if !TARGET_OS_SIMULATOR -// Non-simulator platforms have lock debugging built into os_unfair_lock. - +void +lockdebug_remember_mutex(mutex_t *lock) +{ + setLock(AllLocks(), lock, MUTEX); +} void -lockdebug_mutex_lock(mutex_t *lock) +lockdebug_remember_recursive_mutex(recursive_mutex_t *lock) { - // empty + setLock(AllLocks(), lock, RECURSIVE); } void -lockdebug_mutex_unlock(mutex_t *lock) +lockdebug_remember_monitor(monitor_t *lock) { - // empty + setLock(AllLocks(), lock, MONITOR); } void -lockdebug_mutex_assert_locked(mutex_t *lock) +lockdebug_remember_rwlock(rwlock_t *lock) { - os_unfair_lock_assert_owner((os_unfair_lock *)lock); + setLock(AllLocks(), lock, WRLOCK); } void -lockdebug_mutex_assert_unlocked(mutex_t *lock) +lockdebug_assert_all_locks_locked() { - os_unfair_lock_assert_not_owner((os_unfair_lock *)lock); + auto& owned = ownedLocks(); + + for (const auto& l : AllLocks()) { + if (!hasLock(owned, l.first, l.second.k)) { + _objc_fatal("lock %p:%d is incorrectly not owned", + l.first, l.second.k); + } + } } +void +lockdebug_assert_no_locks_locked() +{ + auto& owned = ownedLocks(); -// !TARGET_OS_SIMULATOR -#else -// TARGET_OS_SIMULATOR + for (const auto& l : AllLocks()) { + if (hasLock(owned, l.first, l.second.k)) { + _objc_fatal("lock %p:%d is incorrectly owned", l.first, l.second.k); + } + } +} -// Simulator platforms have no built-in lock debugging in os_unfair_lock. +/*********************************************************************** +* Mutex checking +**********************************************************************/ void lockdebug_mutex_lock(mutex_t *lock) { - _objc_lock_list *locks = getLocks(YES); + auto& locks = ownedLocks(); if (hasLock(locks, lock, MUTEX)) { _objc_fatal("deadlock: relocking mutex"); @@ -205,14 +332,14 @@ void lockdebug_mutex_try_lock_success(mutex_t *lock) { - _objc_lock_list *locks = getLocks(YES); + auto& locks = ownedLocks(); setLock(locks, lock, MUTEX); } void lockdebug_mutex_unlock(mutex_t *lock) { - _objc_lock_list *locks = getLocks(NO); + auto& locks = ownedLocks(); if (!hasLock(locks, lock, MUTEX)) { _objc_fatal("unlocking unowned mutex"); @@ -224,7 +351,7 @@ void lockdebug_mutex_assert_locked(mutex_t *lock) { - _objc_lock_list *locks = getLocks(NO); + auto& locks = ownedLocks(); if (!hasLock(locks, lock, MUTEX)) { _objc_fatal("mutex incorrectly not locked"); @@ -234,7 +361,7 @@ void lockdebug_mutex_assert_unlocked(mutex_t *lock) { - _objc_lock_list *locks = getLocks(NO); + auto& locks = ownedLocks(); if (hasLock(locks, lock, MUTEX)) { _objc_fatal("mutex incorrectly locked"); @@ -242,24 +369,21 @@ } -// TARGET_OS_SIMULATOR -#endif - /*********************************************************************** * Recursive mutex checking **********************************************************************/ void -lockdebug_recursive_mutex_lock(recursive_mutex_tt *lock) +lockdebug_recursive_mutex_lock(recursive_mutex_t *lock) { - _objc_lock_list *locks = getLocks(YES); + auto& locks = ownedLocks(); setLock(locks, lock, RECURSIVE); } void -lockdebug_recursive_mutex_unlock(recursive_mutex_tt *lock) +lockdebug_recursive_mutex_unlock(recursive_mutex_t *lock) { - _objc_lock_list *locks = getLocks(NO); + auto& locks = ownedLocks(); if (!hasLock(locks, lock, RECURSIVE)) { _objc_fatal("unlocking unowned recursive mutex"); @@ -269,9 +393,9 @@ void -lockdebug_recursive_mutex_assert_locked(recursive_mutex_tt *lock) +lockdebug_recursive_mutex_assert_locked(recursive_mutex_t *lock) { - _objc_lock_list *locks = getLocks(NO); + auto& locks = ownedLocks(); if (!hasLock(locks, lock, RECURSIVE)) { _objc_fatal("recursive mutex incorrectly not locked"); @@ -279,9 +403,9 @@ } void -lockdebug_recursive_mutex_assert_unlocked(recursive_mutex_tt *lock) +lockdebug_recursive_mutex_assert_unlocked(recursive_mutex_t *lock) { - _objc_lock_list *locks = getLocks(NO); + auto& locks = ownedLocks(); if (hasLock(locks, lock, RECURSIVE)) { _objc_fatal("recursive mutex incorrectly locked"); @@ -296,7 +420,7 @@ void lockdebug_monitor_enter(monitor_t *lock) { - _objc_lock_list *locks = getLocks(YES); + auto& locks = ownedLocks(); if (hasLock(locks, lock, MONITOR)) { _objc_fatal("deadlock: relocking monitor"); @@ -307,7 +431,7 @@ void lockdebug_monitor_leave(monitor_t *lock) { - _objc_lock_list *locks = getLocks(NO); + auto& locks = ownedLocks(); if (!hasLock(locks, lock, MONITOR)) { _objc_fatal("unlocking unowned monitor"); @@ -318,7 +442,7 @@ void lockdebug_monitor_wait(monitor_t *lock) { - _objc_lock_list *locks = getLocks(NO); + auto& locks = ownedLocks(); if (!hasLock(locks, lock, MONITOR)) { _objc_fatal("waiting in unowned monitor"); @@ -329,7 +453,7 @@ void lockdebug_monitor_assert_locked(monitor_t *lock) { - _objc_lock_list *locks = getLocks(NO); + auto& locks = ownedLocks(); if (!hasLock(locks, lock, MONITOR)) { _objc_fatal("monitor incorrectly not locked"); @@ -339,7 +463,7 @@ void lockdebug_monitor_assert_unlocked(monitor_t *lock) { - _objc_lock_list *locks = getLocks(NO); + auto& locks = ownedLocks(); if (hasLock(locks, lock, MONITOR)) { _objc_fatal("monitor incorrectly held"); @@ -352,9 +476,9 @@ **********************************************************************/ void -lockdebug_rwlock_read(rwlock_tt *lock) +lockdebug_rwlock_read(rwlock_t *lock) { - _objc_lock_list *locks = getLocks(YES); + auto& locks = ownedLocks(); if (hasLock(locks, lock, RDLOCK)) { // Recursive rwlock read is bad (may deadlock vs pending writer) @@ -371,16 +495,16 @@ // try-read when already writing is OK (will fail) // try-read failure does nothing. void -lockdebug_rwlock_try_read_success(rwlock_tt *lock) +lockdebug_rwlock_try_read_success(rwlock_t *lock) { - _objc_lock_list *locks = getLocks(YES); + auto& locks = ownedLocks(); setLock(locks, lock, RDLOCK); } void -lockdebug_rwlock_unlock_read(rwlock_tt *lock) +lockdebug_rwlock_unlock_read(rwlock_t *lock) { - _objc_lock_list *locks = getLocks(NO); + auto& locks = ownedLocks(); if (!hasLock(locks, lock, RDLOCK)) { _objc_fatal("un-reading unowned rwlock"); @@ -390,9 +514,9 @@ void -lockdebug_rwlock_write(rwlock_tt *lock) +lockdebug_rwlock_write(rwlock_t *lock) { - _objc_lock_list *locks = getLocks(YES); + auto& locks = ownedLocks(); if (hasLock(locks, lock, RDLOCK)) { // Lock promotion not allowed (may deadlock) @@ -409,16 +533,16 @@ // try-write when already writing is OK (will fail) // try-write failure does nothing. void -lockdebug_rwlock_try_write_success(rwlock_tt *lock) +lockdebug_rwlock_try_write_success(rwlock_t *lock) { - _objc_lock_list *locks = getLocks(YES); + auto& locks = ownedLocks(); setLock(locks, lock, WRLOCK); } void -lockdebug_rwlock_unlock_write(rwlock_tt *lock) +lockdebug_rwlock_unlock_write(rwlock_t *lock) { - _objc_lock_list *locks = getLocks(NO); + auto& locks = ownedLocks(); if (!hasLock(locks, lock, WRLOCK)) { _objc_fatal("un-writing unowned rwlock"); @@ -428,9 +552,9 @@ void -lockdebug_rwlock_assert_reading(rwlock_tt *lock) +lockdebug_rwlock_assert_reading(rwlock_t *lock) { - _objc_lock_list *locks = getLocks(NO); + auto& locks = ownedLocks(); if (!hasLock(locks, lock, RDLOCK)) { _objc_fatal("rwlock incorrectly not reading"); @@ -438,9 +562,9 @@ } void -lockdebug_rwlock_assert_writing(rwlock_tt *lock) +lockdebug_rwlock_assert_writing(rwlock_t *lock) { - _objc_lock_list *locks = getLocks(NO); + auto& locks = ownedLocks(); if (!hasLock(locks, lock, WRLOCK)) { _objc_fatal("rwlock incorrectly not writing"); @@ -448,9 +572,9 @@ } void -lockdebug_rwlock_assert_locked(rwlock_tt *lock) +lockdebug_rwlock_assert_locked(rwlock_t *lock) { - _objc_lock_list *locks = getLocks(NO); + auto& locks = ownedLocks(); if (!hasLock(locks, lock, RDLOCK) && !hasLock(locks, lock, WRLOCK)) { _objc_fatal("rwlock incorrectly neither reading nor writing"); @@ -458,9 +582,9 @@ } void -lockdebug_rwlock_assert_unlocked(rwlock_tt *lock) +lockdebug_rwlock_assert_unlocked(rwlock_t *lock) { - _objc_lock_list *locks = getLocks(NO); + auto& locks = ownedLocks(); if (hasLock(locks, lock, RDLOCK) || hasLock(locks, lock, WRLOCK)) { _objc_fatal("rwlock incorrectly not unlocked"); diff --git a/runtime/objc-locks-new.h b/runtime/objc-locks-new.h new file mode 100644 index 0000000..73e3dd0 --- /dev/null +++ b/runtime/objc-locks-new.h @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2017 Apple Inc. All Rights Reserved. + * + * @APPLE_LICENSE_HEADER_START@ + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPLE_LICENSE_HEADER_END@ + */ + +/*********************************************************************** +* objc-locks-new.h +* Declarations of all locks used in the runtime. +**********************************************************************/ + +#ifndef _OBJC_LOCKS_NEW_H +#define _OBJC_LOCKS_NEW_H + +// fork() safety requires careful tracking of all locks used in the runtime. +// Thou shalt not declare any locks outside this file. + +extern rwlock_t runtimeLock; +extern mutex_t DemangleCacheLock; + +#endif diff --git a/runtime/objc-locks-old.h b/runtime/objc-locks-old.h new file mode 100644 index 0000000..1926136 --- /dev/null +++ b/runtime/objc-locks-old.h @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2017 Apple Inc. All Rights Reserved. + * + * @APPLE_LICENSE_HEADER_START@ + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPLE_LICENSE_HEADER_END@ + */ + +/*********************************************************************** +* objc-locks-old.h +* Declarations of all locks used in the runtime. +**********************************************************************/ + +#ifndef _OBJC_LOCKS_OLD_H +#define _OBJC_LOCKS_OLD_H + +// fork() safety requires careful tracking of all locks used in the runtime. +// Thou shalt not declare any locks outside this file. + +extern mutex_t classLock; +extern mutex_t methodListLock; +extern mutex_t NXUniqueStringLock; +extern spinlock_t impLock; + +#endif diff --git a/runtime/objc-locks.h b/runtime/objc-locks.h new file mode 100644 index 0000000..05bd22f --- /dev/null +++ b/runtime/objc-locks.h @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2017 Apple Inc. All Rights Reserved. + * + * @APPLE_LICENSE_HEADER_START@ + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPLE_LICENSE_HEADER_END@ + */ + +/*********************************************************************** +* objc-locks.h +* Declarations of all locks used in the runtime. +**********************************************************************/ + +#ifndef _OBJC_LOCKS_H +#define _OBJC_LOCKS_H + +// fork() safety requires careful tracking of all locks used in the runtime. +// Thou shalt not declare any locks outside this file. + +// Lock ordering is declared in _objc_fork_prepare() +// and is enforced by lockdebug. + +extern monitor_t classInitLock; +extern rwlock_t selLock; +extern mutex_t cacheUpdateLock; +extern recursive_mutex_t loadMethodLock; +extern mutex_t crashlog_lock; +extern spinlock_t objcMsgLogLock; +extern mutex_t AltHandlerDebugLock; +extern mutex_t AssociationsManagerLock; +extern StripedMap PropertyLocks; +extern StripedMap StructLocks; +extern StripedMap CppObjectLocks; + +// SideTable lock is buried awkwardly. Call a function to manipulate it. +extern void SideTableLockAll(); +extern void SideTableUnlockAll(); +extern void SideTableForceResetAll(); +extern void SideTableDefineLockOrder(); +extern void SideTableLocksPrecedeLock(const void *newlock); +extern void SideTableLocksSucceedLock(const void *oldlock); + +#if __OBJC2__ +#include "objc-locks-new.h" +#else +#include "objc-locks-old.h" +#endif + +#endif diff --git a/runtime/objc-opt.mm b/runtime/objc-opt.mm index 19533df..45c13b3 100644 --- a/runtime/objc-opt.mm +++ b/runtime/objc-opt.mm @@ -59,6 +59,11 @@ bool noMissingWeakSuperclasses(void) return nil; } +unsigned int getPreoptimizedClassUnreasonableCount() +{ + return 0; +} + Class getPreoptimizedClass(const char *name) { return nil; @@ -165,6 +170,17 @@ bool noMissingWeakSuperclasses(void) } +unsigned int getPreoptimizedClassUnreasonableCount() +{ + objc_clsopt_t *classes = opt ? opt->clsopt() : nil; + if (!classes) return 0; + + // This is an overestimate: each set of duplicates + // gets double-counted in `capacity` as well. + return classes->capacity + classes->duplicateCount(); +} + + Class getPreoptimizedClass(const char *name) { objc_clsopt_t *classes = opt ? opt->clsopt() : nil; diff --git a/runtime/objc-os.h b/runtime/objc-os.h index 904184f..7785d4a 100644 --- a/runtime/objc-os.h +++ b/runtime/objc-os.h @@ -795,13 +795,23 @@ using monitor_t = monitor_tt; using rwlock_t = rwlock_tt; using recursive_mutex_t = recursive_mutex_tt; +// Use fork_unsafe_lock to get a lock that isn't +// acquired and released around fork(). +// All fork-safe locks are checked in debug builds. +struct fork_unsafe_lock_t { }; +extern const fork_unsafe_lock_t fork_unsafe_lock; + #include "objc-lockdebug.h" template class mutex_tt : nocopy_t { os_unfair_lock mLock; public: - mutex_tt() : mLock(OS_UNFAIR_LOCK_INIT) { } + mutex_tt() : mLock(OS_UNFAIR_LOCK_INIT) { + lockdebug_remember_mutex(this); + } + + mutex_tt(const fork_unsafe_lock_t unsafe) : mLock(OS_UNFAIR_LOCK_INIT) { } void lock() { lockdebug_mutex_lock(this); @@ -816,6 +826,13 @@ class mutex_tt : nocopy_t { os_unfair_lock_unlock_inline(&mLock); } + void forceReset() { + lockdebug_mutex_unlock(this); + + bzero(&mLock, sizeof(mLock)); + mLock = os_unfair_lock OS_UNFAIR_LOCK_INIT; + } + void assertLocked() { lockdebug_mutex_assert_locked(this); } @@ -828,7 +845,7 @@ class mutex_tt : nocopy_t { // Address-ordered lock discipline for a pair of locks. static void lockTwo(mutex_tt *lock1, mutex_tt *lock2) { - if (lock1 > lock2) { + if (lock1 < lock2) { lock1->lock(); lock2->lock(); } else { @@ -849,7 +866,13 @@ class recursive_mutex_tt : nocopy_t { pthread_mutex_t mLock; public: - recursive_mutex_tt() : mLock(PTHREAD_RECURSIVE_MUTEX_INITIALIZER) { } + recursive_mutex_tt() : mLock(PTHREAD_RECURSIVE_MUTEX_INITIALIZER) { + lockdebug_remember_recursive_mutex(this); + } + + recursive_mutex_tt(const fork_unsafe_lock_t unsafe) + : mLock(PTHREAD_RECURSIVE_MUTEX_INITIALIZER) + { } void lock() { @@ -867,6 +890,14 @@ class recursive_mutex_tt : nocopy_t { if (err) _objc_fatal("pthread_mutex_unlock failed (%d)", err); } + void forceReset() + { + lockdebug_recursive_mutex_unlock(this); + + bzero(&mLock, sizeof(mLock)); + mLock = pthread_mutex_t PTHREAD_RECURSIVE_MUTEX_INITIALIZER; + } + bool tryUnlock() { int err = pthread_mutex_unlock(&mLock); @@ -898,7 +929,14 @@ class monitor_tt { public: monitor_tt() - : mutex(PTHREAD_MUTEX_INITIALIZER), cond(PTHREAD_COND_INITIALIZER) { } + : mutex(PTHREAD_MUTEX_INITIALIZER), cond(PTHREAD_COND_INITIALIZER) + { + lockdebug_remember_monitor(this); + } + + monitor_tt(const fork_unsafe_lock_t unsafe) + : mutex(PTHREAD_MUTEX_INITIALIZER), cond(PTHREAD_COND_INITIALIZER) + { } void enter() { @@ -936,6 +974,16 @@ class monitor_tt { if (err) _objc_fatal("pthread_cond_broadcast failed (%d)", err); } + void forceReset() + { + lockdebug_monitor_leave(this); + + bzero(&mutex, sizeof(mutex)); + bzero(&cond, sizeof(cond)); + mutex = pthread_mutex_t PTHREAD_MUTEX_INITIALIZER; + cond = pthread_cond_t PTHREAD_COND_INITIALIZER; + } + void assertLocked() { lockdebug_monitor_assert_locked(this); @@ -1018,7 +1066,13 @@ class rwlock_tt : nocopy_t { pthread_rwlock_t mLock; public: - rwlock_tt() : mLock(PTHREAD_RWLOCK_INITIALIZER) { } + rwlock_tt() : mLock(PTHREAD_RWLOCK_INITIALIZER) { + lockdebug_remember_rwlock(this); + } + + rwlock_tt(const fork_unsafe_lock_t unsafe) + : mLock(PTHREAD_RWLOCK_INITIALIZER) + { } void read() { @@ -1086,6 +1140,14 @@ class rwlock_tt : nocopy_t { } } + void forceReset() + { + lockdebug_rwlock_unlock_write(this); + + bzero(&mLock, sizeof(mLock)); + mLock = pthread_rwlock_t PTHREAD_RWLOCK_INITIALIZER; + } + void assertReading() { lockdebug_rwlock_assert_reading(this); @@ -1222,4 +1284,13 @@ ustrdupMaybeNil(const uint8_t *str) (unsigned char)(((uint32_t)(v))>>8), \ (unsigned char)(((uint32_t)(v))>>0) +// fork() safety requires careful tracking of all locks. +// Our custom lock types check this in debug builds. +// Disallow direct use of all other lock types. +typedef __darwin_pthread_mutex_t pthread_mutex_t UNAVAILABLE_ATTRIBUTE; +typedef __darwin_pthread_rwlock_t pthread_rwlock_t UNAVAILABLE_ATTRIBUTE; +typedef int32_t OSSpinLock UNAVAILABLE_ATTRIBUTE; +typedef struct os_unfair_lock_s os_unfair_lock UNAVAILABLE_ATTRIBUTE; + + #endif diff --git a/runtime/objc-os.mm b/runtime/objc-os.mm index cb077e5..edf9a46 100644 --- a/runtime/objc-os.mm +++ b/runtime/objc-os.mm @@ -34,6 +34,8 @@ #include "objc-runtime-old.h" #include "objcrt.h" +const fork_unsafe_lock_t fork_unsafe_lock; + int monitor_init(monitor_t *c) { // fixme error checking @@ -603,6 +605,224 @@ static void static_init() } +/*********************************************************************** +* _objc_atfork_prepare +* _objc_atfork_parent +* _objc_atfork_child +* Allow ObjC to be used between fork() and exec(). +* libc requires this because it has fork-safe functions that use os_objects. +* +* _objc_atfork_prepare() acquires all locks. +* _objc_atfork_parent() releases the locks again. +* _objc_atfork_child() forcibly resets the locks. +**********************************************************************/ + +// Declare lock ordering. +#if DEBUG +__attribute__((constructor)) +static void defineLockOrder() +{ + // Every lock precedes crashlog_lock + // on the assumption that fatal errors could be anywhere. + lockdebug_lock_precedes_lock(&loadMethodLock, &crashlog_lock); + lockdebug_lock_precedes_lock(&classInitLock, &crashlog_lock); +#if __OBJC2__ + lockdebug_lock_precedes_lock(&runtimeLock, &crashlog_lock); + lockdebug_lock_precedes_lock(&DemangleCacheLock, &crashlog_lock); +#else + lockdebug_lock_precedes_lock(&classLock, &crashlog_lock); + lockdebug_lock_precedes_lock(&methodListLock, &crashlog_lock); + lockdebug_lock_precedes_lock(&NXUniqueStringLock, &crashlog_lock); + lockdebug_lock_precedes_lock(&impLock, &crashlog_lock); +#endif + lockdebug_lock_precedes_lock(&selLock, &crashlog_lock); + lockdebug_lock_precedes_lock(&cacheUpdateLock, &crashlog_lock); + lockdebug_lock_precedes_lock(&objcMsgLogLock, &crashlog_lock); + lockdebug_lock_precedes_lock(&AltHandlerDebugLock, &crashlog_lock); + lockdebug_lock_precedes_lock(&AssociationsManagerLock, &crashlog_lock); + SideTableLocksPrecedeLock(&crashlog_lock); + PropertyLocks.precedeLock(&crashlog_lock); + StructLocks.precedeLock(&crashlog_lock); + CppObjectLocks.precedeLock(&crashlog_lock); + + // loadMethodLock precedes everything + // because it is held while +load methods run + lockdebug_lock_precedes_lock(&loadMethodLock, &classInitLock); +#if __OBJC2__ + lockdebug_lock_precedes_lock(&loadMethodLock, &runtimeLock); + lockdebug_lock_precedes_lock(&loadMethodLock, &DemangleCacheLock); +#else + lockdebug_lock_precedes_lock(&loadMethodLock, &methodListLock); + lockdebug_lock_precedes_lock(&loadMethodLock, &classLock); + lockdebug_lock_precedes_lock(&loadMethodLock, &NXUniqueStringLock); + lockdebug_lock_precedes_lock(&loadMethodLock, &impLock); +#endif + lockdebug_lock_precedes_lock(&loadMethodLock, &selLock); + lockdebug_lock_precedes_lock(&loadMethodLock, &cacheUpdateLock); + lockdebug_lock_precedes_lock(&loadMethodLock, &objcMsgLogLock); + lockdebug_lock_precedes_lock(&loadMethodLock, &AltHandlerDebugLock); + lockdebug_lock_precedes_lock(&loadMethodLock, &AssociationsManagerLock); + SideTableLocksSucceedLock(&loadMethodLock); + PropertyLocks.succeedLock(&loadMethodLock); + StructLocks.succeedLock(&loadMethodLock); + CppObjectLocks.succeedLock(&loadMethodLock); + + // PropertyLocks and CppObjectLocks precede everything + // because they are held while objc_retain() or C++ copy are called. + // (StructLocks do not precede everything because it calls memmove only.) + PropertyLocks.precedeLock(&classInitLock); + CppObjectLocks.precedeLock(&classInitLock); +#if __OBJC2__ + PropertyLocks.precedeLock(&runtimeLock); + CppObjectLocks.precedeLock(&runtimeLock); + PropertyLocks.precedeLock(&DemangleCacheLock); + CppObjectLocks.precedeLock(&DemangleCacheLock); +#else + PropertyLocks.precedeLock(&methodListLock); + CppObjectLocks.precedeLock(&methodListLock); + PropertyLocks.precedeLock(&classLock); + CppObjectLocks.precedeLock(&classLock); + PropertyLocks.precedeLock(&NXUniqueStringLock); + CppObjectLocks.precedeLock(&NXUniqueStringLock); + PropertyLocks.precedeLock(&impLock); + CppObjectLocks.precedeLock(&impLock); +#endif + PropertyLocks.precedeLock(&selLock); + CppObjectLocks.precedeLock(&selLock); + PropertyLocks.precedeLock(&cacheUpdateLock); + CppObjectLocks.precedeLock(&cacheUpdateLock); + PropertyLocks.precedeLock(&objcMsgLogLock); + CppObjectLocks.precedeLock(&objcMsgLogLock); + PropertyLocks.precedeLock(&AltHandlerDebugLock); + CppObjectLocks.precedeLock(&AltHandlerDebugLock); + PropertyLocks.precedeLock(&AssociationsManagerLock); + CppObjectLocks.precedeLock(&AssociationsManagerLock); + // fixme side table + +#if __OBJC2__ + lockdebug_lock_precedes_lock(&classInitLock, &runtimeLock); +#endif + +#if __OBJC2__ + // Runtime operations may occur inside SideTable locks + // (such as storeWeak calling getMethodImplementation) + SideTableLocksPrecedeLock(&runtimeLock); + // Some operations may occur inside runtimeLock. + lockdebug_lock_precedes_lock(&runtimeLock, &selLock); + lockdebug_lock_precedes_lock(&runtimeLock, &cacheUpdateLock); + lockdebug_lock_precedes_lock(&runtimeLock, &DemangleCacheLock); +#else + // Runtime operations may occur inside SideTable locks + // (such as storeWeak calling getMethodImplementation) + SideTableLocksPrecedeLock(&methodListLock); + // Method lookup and fixup. + lockdebug_lock_precedes_lock(&methodListLock, &classLock); + lockdebug_lock_precedes_lock(&methodListLock, &selLock); + lockdebug_lock_precedes_lock(&methodListLock, &cacheUpdateLock); + lockdebug_lock_precedes_lock(&methodListLock, &impLock); + lockdebug_lock_precedes_lock(&classLock, &selLock); + lockdebug_lock_precedes_lock(&classLock, &cacheUpdateLock); +#endif + + // Striped locks use address order internally. + SideTableDefineLockOrder(); + PropertyLocks.defineLockOrder(); + StructLocks.defineLockOrder(); + CppObjectLocks.defineLockOrder(); +} +// DEBUG +#endif + +void _objc_atfork_prepare() +{ + lockdebug_assert_no_locks_locked(); + lockdebug_setInForkPrepare(true); + + loadMethodLock.lock(); + PropertyLocks.lockAll(); + CppObjectLocks.lockAll(); + classInitLock.enter(); + SideTableLockAll(); +#if __OBJC2__ + runtimeLock.write(); + DemangleCacheLock.lock(); +#else + methodListLock.lock(); + classLock.lock(); + NXUniqueStringLock.lock(); + impLock.lock(); +#endif + selLock.write(); + cacheUpdateLock.lock(); + objcMsgLogLock.lock(); + AltHandlerDebugLock.lock(); + AssociationsManagerLock.lock(); + StructLocks.lockAll(); + crashlog_lock.lock(); + + lockdebug_assert_all_locks_locked(); + lockdebug_setInForkPrepare(false); +} + +void _objc_atfork_parent() +{ + lockdebug_assert_all_locks_locked(); + + CppObjectLocks.unlockAll(); + StructLocks.unlockAll(); + PropertyLocks.unlockAll(); + AssociationsManagerLock.unlock(); + AltHandlerDebugLock.unlock(); + objcMsgLogLock.unlock(); + crashlog_lock.unlock(); + loadMethodLock.unlock(); + cacheUpdateLock.unlock(); + selLock.unlockWrite(); + SideTableUnlockAll(); +#if __OBJC2__ + DemangleCacheLock.unlock(); + runtimeLock.unlockWrite(); +#else + impLock.unlock(); + NXUniqueStringLock.unlock(); + methodListLock.unlock(); + classLock.unlock(); +#endif + classInitLock.leave(); + + lockdebug_assert_no_locks_locked(); +} + +void _objc_atfork_child() +{ + lockdebug_assert_all_locks_locked(); + + CppObjectLocks.forceResetAll(); + StructLocks.forceResetAll(); + PropertyLocks.forceResetAll(); + AssociationsManagerLock.forceReset(); + AltHandlerDebugLock.forceReset(); + objcMsgLogLock.forceReset(); + crashlog_lock.forceReset(); + loadMethodLock.forceReset(); + cacheUpdateLock.forceReset(); + selLock.forceReset(); + SideTableForceResetAll(); +#if __OBJC2__ + DemangleCacheLock.forceReset(); + runtimeLock.forceReset(); +#else + impLock.forceReset(); + NXUniqueStringLock.forceReset(); + methodListLock.forceReset(); + classLock.forceReset(); +#endif + classInitLock.forceReset(); + + lockdebug_assert_no_locks_locked(); +} + + /*********************************************************************** * _objc_init * Bootstrap initialization. Registers our image notifier with dyld. @@ -622,7 +842,7 @@ void _objc_init(void) lock_init(); exception_init(); - _dyld_objc_notify_register(&map_2_images, load_images, unmap_image); + _dyld_objc_notify_register(&map_images, load_images, unmap_image); } diff --git a/runtime/objc-private.h b/runtime/objc-private.h index 07bf78c..b34b591 100644 --- a/runtime/objc-private.h +++ b/runtime/objc-private.h @@ -541,6 +541,7 @@ extern objc_selopt_t *preoptimizedSelectors(void); extern Protocol *getPreoptimizedProtocol(const char *name); +extern unsigned getPreoptimizedClassUnreasonableCount(); extern Class getPreoptimizedClass(const char *name); extern Class* copyPreoptimizedClasses(const char *name, int *outCount); @@ -594,15 +595,6 @@ extern char *copyPropertyAttributeValue(const char *attrs, const char *name); /* locking */ extern void lock_init(void); -extern rwlock_t selLock; -extern mutex_t cacheUpdateLock; -extern recursive_mutex_t loadMethodLock; -#if __OBJC2__ -extern rwlock_t runtimeLock; -#else -extern mutex_t classLock; -extern mutex_t methodListLock; -#endif class monitor_locker_t : nocopy_t { monitor_t& lock; @@ -724,8 +716,8 @@ extern void layout_bitmap_print(layout_bitmap bits); // fixme runtime extern Class look_up_class(const char *aClassName, bool includeUnconnected, bool includeClassHandler); -extern "C" void map_2_images(unsigned count, const char * const paths[], - const struct mach_header * const mhdrs[]); +extern "C" void map_images(unsigned count, const char * const paths[], + const struct mach_header * const mhdrs[]); extern void map_images_nolock(unsigned count, const char * const paths[], const struct mach_header * const mhdrs[]); extern void load_images(const char *path, const struct mach_header *mh); @@ -894,6 +886,41 @@ class StripedMap { return const_cast>(this)[p]; } + // Shortcuts for StripedMaps of locks. + void lockAll() { + for (unsigned int i = 0; i < StripeCount; i++) { + array[i].value.lock(); + } + } + + void unlockAll() { + for (unsigned int i = 0; i < StripeCount; i++) { + array[i].value.unlock(); + } + } + + void forceResetAll() { + for (unsigned int i = 0; i < StripeCount; i++) { + array[i].value.forceReset(); + } + } + + void defineLockOrder() { + for (unsigned int i = 1; i < StripeCount; i++) { + lockdebug_lock_precedes_lock(&array[i-1].value, &array[i].value); + } + } + + void precedeLock(const void *newlock) { + // assumes defineLockOrder is also called + lockdebug_lock_precedes_lock(&array[StripeCount-1].value, newlock); + } + + void succeedLock(const void *oldlock) { + // assumes defineLockOrder is also called + lockdebug_lock_precedes_lock(oldlock, &array[0].value); + } + #if DEBUG StripedMap() { // Verify alignment expectations. @@ -1015,6 +1042,10 @@ static uint32_t ptr_hash(uint32_t key) */ + +// Lock declarations +#include "objc-locks.h" + // Inlined parts of objc_object's implementation #include "objc-object.h" diff --git a/runtime/objc-references.mm b/runtime/objc-references.mm index 712b88e..20bf807 100644 --- a/runtime/objc-references.mm +++ b/runtime/objc-references.mm @@ -187,15 +187,17 @@ void construct(pointer p, const value_type& x) { using namespace objc_references_support; // class AssociationsManager manages a lock / hash table singleton pair. -// Allocating an instance acquires the lock, and calling its assocations() method -// lazily allocates it. +// Allocating an instance acquires the lock, and calling its assocations() +// method lazily allocates the hash table. + +spinlock_t AssociationsManagerLock; class AssociationsManager { - static spinlock_t _lock; - static AssociationsHashMap *_map; // associative references: object pointer -> PtrPtrHashMap. + // associative references: object pointer -> PtrPtrHashMap. + static AssociationsHashMap *_map; public: - AssociationsManager() { _lock.lock(); } - ~AssociationsManager() { _lock.unlock(); } + AssociationsManager() { AssociationsManagerLock.lock(); } + ~AssociationsManager() { AssociationsManagerLock.unlock(); } AssociationsHashMap &associations() { if (_map == NULL) @@ -204,7 +206,6 @@ void construct(pointer p, const value_type& x) { } }; -spinlock_t AssociationsManager::_lock; AssociationsHashMap *AssociationsManager::_map = NULL; // expanded policy bits. diff --git a/runtime/objc-runtime-new.h b/runtime/objc-runtime-new.h index 7b7dbac..dff7c10 100644 --- a/runtime/objc-runtime-new.h +++ b/runtime/objc-runtime-new.h @@ -901,7 +901,11 @@ struct class_data_bits_t { { assert(!data() || (newData->flags & (RW_REALIZING | RW_FUTURE))); // Set during realization or construction only. No locking needed. - bits = (bits & ~FAST_DATA_MASK) | (uintptr_t)newData; + // Use a store-release fence because there may be concurrent + // readers of data and data's contents. + uintptr_t newBits = (bits & ~FAST_DATA_MASK) | (uintptr_t)newData; + atomic_thread_fence(memory_order_release); + bits = newBits; } bool hasDefaultRR() { @@ -1351,12 +1355,16 @@ struct message_ref_t { extern Method protocol_getMethod(protocol_t *p, SEL sel, bool isRequiredMethod, bool isInstanceMethod, bool recursive); static inline void -foreach_realized_class_and_subclass_2(Class top, bool (^code)(Class)) +foreach_realized_class_and_subclass_2(Class top, unsigned& count, + std::function code) { // runtimeLock.assertWriting(); assert(top); Class cls = top; while (1) { + if (--count == 0) { + _objc_fatal("Memory corruption in class list."); + } if (!code(cls)) break; if (cls->data()->firstSubclass) { @@ -1364,6 +1372,9 @@ foreach_realized_class_and_subclass_2(Class top, bool (^code)(Class)) } else { while (!cls->data()->nextSiblingClass && cls != top) { cls = cls->superclass; + if (--count == 0) { + _objc_fatal("Memory corruption in class list."); + } } if (cls == top) break; cls = cls->data()->nextSiblingClass; @@ -1371,26 +1382,39 @@ foreach_realized_class_and_subclass_2(Class top, bool (^code)(Class)) } } +extern Class firstRealizedClass(); +extern unsigned int unreasonableClassCount(); + // Enumerates a class and all of its realized subclasses. static inline void -foreach_realized_class_and_subclass(Class top, void (^code)(Class)) +foreach_realized_class_and_subclass(Class top, + std::function code) { - foreach_realized_class_and_subclass_2(top, ^bool(Class cls) { - code(cls); return true; + unsigned int count = unreasonableClassCount(); + + foreach_realized_class_and_subclass_2(top, count, + [&code](Class cls) -> bool + { + code(cls); + return true; }); } // Enumerates all realized classes and metaclasses. -extern Class firstRealizedClass(); static inline void -foreach_realized_class_and_metaclass(void (^code)(Class)) +foreach_realized_class_and_metaclass(std::function code) { + unsigned int count = unreasonableClassCount(); + for (Class top = firstRealizedClass(); top != nil; top = top->data()->nextSiblingClass) { - foreach_realized_class_and_subclass_2(top, ^bool(Class cls) { - code(cls); return true; + foreach_realized_class_and_subclass_2(top, count, + [&code](Class cls) -> bool + { + code(cls); + return true; }); } diff --git a/runtime/objc-runtime-new.mm b/runtime/objc-runtime-new.mm index b366c19..37b2f6f 100644 --- a/runtime/objc-runtime-new.mm +++ b/runtime/objc-runtime-new.mm @@ -1035,6 +1035,25 @@ static void removeNamedClass(Class cls, const char *name) } +/*********************************************************************** +* unreasonableClassCount +* Provides an upper bound for any iteration of classes, +* to prevent spins when runtime metadata is corrupted. +**********************************************************************/ +unsigned unreasonableClassCount() +{ + runtimeLock.assertLocked(); + + int base = NXCountMapTable(gdb_objc_realized_classes) + + getPreoptimizedClassUnreasonableCount(); + + // Provide lots of slack here. Some iterations touch metaclasses too. + // Some iterations backtrack (like realized class iteration). + // We don't need an efficient bound, merely one that prevents spins. + return (base + 1) * 16; +} + + /*********************************************************************** * futureNamedClasses * Returns the classname => future class map for unrealized future classes. @@ -1992,8 +2011,8 @@ void _objc_flush_caches(Class cls) * Locking: write-locks runtimeLock **********************************************************************/ void -map_2_images(unsigned count, const char * const paths[], - const struct mach_header * const mhdrs[]) +map_images(unsigned count, const char * const paths[], + const struct mach_header * const mhdrs[]) { rwlock_writer_t lock(runtimeLock); return map_images_nolock(count, paths, mhdrs); @@ -4264,7 +4283,7 @@ void objc_registerProtocol(Protocol *proto_gen) * If realize=false, the class must already be realized or future. * Locking: If realize=true, runtimeLock must be held for writing by the caller. **********************************************************************/ -static mutex_t DemangleCacheLock; +mutex_t DemangleCacheLock; static NXHashTable *DemangleCache; const char * objc_class::demangledName(bool realize) @@ -4568,9 +4587,7 @@ IMP _class_lookupMethodAndLoadCache3(id obj, SEL sel, Class cls) IMP lookUpImpOrForward(Class cls, SEL sel, id inst, bool initialize, bool cache, bool resolver) { - Class curClass; IMP imp = nil; - Method meth; bool triedResolver = NO; runtimeLock.assertUnlocked(); @@ -4581,25 +4598,43 @@ IMP lookUpImpOrForward(Class cls, SEL sel, id inst, if (imp) return imp; } + // runtimeLock is held during isRealized and isInitialized checking + // to prevent races against concurrent realization. + + // runtimeLock is held during method search to make + // method-lookup + cache-fill atomic with respect to method addition. + // Otherwise, a category could be added but ignored indefinitely because + // the cache was re-filled with the old value after the cache flush on + // behalf of the category. + + runtimeLock.read(); + if (!cls->isRealized()) { - rwlock_writer_t lock(runtimeLock); + // Drop the read-lock and acquire the write-lock. + // realizeClass() checks isRealized() again to prevent + // a race while the lock is down. + runtimeLock.unlockRead(); + runtimeLock.write(); + realizeClass(cls); + + runtimeLock.unlockWrite(); + runtimeLock.read(); } if (initialize && !cls->isInitialized()) { + runtimeLock.unlockRead(); _class_initialize (_class_getNonMetaClass(cls, inst)); + runtimeLock.read(); // If sel == initialize, _class_initialize will send +initialize and // then the messenger will send +initialize again after this // procedure finishes. Of course, if this is not being called // from the messenger then it won't happen. 2778172 } - // The lock is held to make method-lookup + cache-fill atomic - // with respect to method addition. Otherwise, a category could - // be added but ignored indefinitely because the cache was re-filled - // with the old value after the cache flush on behalf of the category. - retry: - runtimeLock.read(); + + retry: + runtimeLock.assertReading(); // Try this class's cache. @@ -4607,40 +4642,50 @@ IMP lookUpImpOrForward(Class cls, SEL sel, id inst, if (imp) goto done; // Try this class's method lists. - - meth = getMethodNoSuper_nolock(cls, sel); - if (meth) { - log_and_fill_cache(cls, meth->imp, sel, inst, cls); - imp = meth->imp; - goto done; + { + Method meth = getMethodNoSuper_nolock(cls, sel); + if (meth) { + log_and_fill_cache(cls, meth->imp, sel, inst, cls); + imp = meth->imp; + goto done; + } } // Try superclass caches and method lists. - - curClass = cls; - while ((curClass = curClass->superclass)) { - // Superclass cache. - imp = cache_getImp(curClass, sel); - if (imp) { - if (imp != (IMP)_objc_msgForward_impcache) { - // Found the method in a superclass. Cache it in this class. - log_and_fill_cache(cls, imp, sel, inst, curClass); - goto done; + { + unsigned attempts = unreasonableClassCount(); + for (Class curClass = cls; + curClass != nil; + curClass = curClass->superclass) + { + // Halt if there is a cycle in the superclass chain. + if (--attempts == 0) { + _objc_fatal("Memory corruption in class list."); } - else { - // Found a forward:: entry in a superclass. - // Stop searching, but don't cache yet; call method - // resolver for this class first. - break; + + // Superclass cache. + imp = cache_getImp(curClass, sel); + if (imp) { + if (imp != (IMP)_objc_msgForward_impcache) { + // Found the method in a superclass. Cache it in this class. + log_and_fill_cache(cls, imp, sel, inst, curClass); + goto done; + } + else { + // Found a forward:: entry in a superclass. + // Stop searching, but don't cache yet; call method + // resolver for this class first. + break; + } + } + + // Superclass method list. + Method meth = getMethodNoSuper_nolock(curClass, sel); + if (meth) { + log_and_fill_cache(cls, meth->imp, sel, inst, curClass); + imp = meth->imp; + goto done; } - } - - // Superclass method list. - meth = getMethodNoSuper_nolock(curClass, sel); - if (meth) { - log_and_fill_cache(cls, meth->imp, sel, inst, curClass); - imp = meth->imp; - goto done; } } @@ -4649,6 +4694,7 @@ IMP lookUpImpOrForward(Class cls, SEL sel, id inst, if (resolver && !triedResolver) { runtimeLock.unlockRead(); _class_resolveMethod(cls, sel, inst); + runtimeLock.read(); // Don't cache the result; we don't hold the lock so it may have // changed already. Re-do the search from scratch instead. triedResolver = YES; diff --git a/runtime/objc-runtime-old.mm b/runtime/objc-runtime-old.mm index d6b393e..7c95bd7 100644 --- a/runtime/objc-runtime-old.mm +++ b/runtime/objc-runtime-old.mm @@ -2157,8 +2157,8 @@ static inline bool _is_threaded() { * Calls ABI-agnostic code after taking ABI-specific locks. **********************************************************************/ void -map_2_images(unsigned count, const char * const paths[], - const struct mach_header * const mhdrs[]) +map_images(unsigned count, const char * const paths[], + const struct mach_header * const mhdrs[]) { recursive_mutex_locker_t lock(loadMethodLock); map_images_nolock(count, paths, mhdrs); diff --git a/runtime/objc-sync.mm b/runtime/objc-sync.mm index cb69981..daf743d 100644 --- a/runtime/objc-sync.mm +++ b/runtime/objc-sync.mm @@ -60,7 +60,7 @@ SyncData *data; spinlock_t lock; - SyncList() : data(nil) { } + SyncList() : data(nil), lock(fork_unsafe_lock) { } }; // Use multiple parallel lists to decrease contention among unrelated objects. @@ -235,7 +235,7 @@ void _destroySyncCache(struct SyncCache *cache) result = (SyncData*)calloc(sizeof(SyncData), 1); result->object = (objc_object *)object; result->threadCount = 1; - new (&result->mutex) recursive_mutex_t(); + new (&result->mutex) recursive_mutex_t(fork_unsafe_lock); result->nextData = *listp; *listp = result; diff --git a/runtime/objc.h b/runtime/objc.h index 7417ebc..6cf776b 100644 --- a/runtime/objc.h +++ b/runtime/objc.h @@ -56,19 +56,35 @@ typedef void (*IMP)(void /* id, SEL, ... */ ); typedef id (*IMP)(id, SEL, ...); #endif -#define OBJC_BOOL_DEFINED - /// Type to represent a boolean value. -#if (TARGET_OS_IPHONE && __LP64__) || TARGET_OS_WATCH -#define OBJC_BOOL_IS_BOOL 1 -typedef bool BOOL; + +#if defined(__OBJC_BOOL_IS_BOOL) + // Honor __OBJC_BOOL_IS_BOOL when available. +# if __OBJC_BOOL_IS_BOOL +# define OBJC_BOOL_IS_BOOL 1 +# else +# define OBJC_BOOL_IS_BOOL 0 +# endif #else -#define OBJC_BOOL_IS_CHAR 1 -typedef signed char BOOL; -// BOOL is explicitly signed so @encode(BOOL) == "c" rather than "C" -// even if -funsigned-char is used. + // __OBJC_BOOL_IS_BOOL not set. +# if TARGET_OS_OSX || (TARGET_OS_IOS && !__LP64__ && !__ARM_ARCH_7K) +# define OBJC_BOOL_IS_BOOL 0 +# else +# define OBJC_BOOL_IS_BOOL 1 +# endif #endif +#if OBJC_BOOL_IS_BOOL + typedef bool BOOL; +#else +# define OBJC_BOOL_IS_CHAR 1 + typedef signed char BOOL; + // BOOL is explicitly signed so @encode(BOOL) == "c" rather than "C" + // even if -funsigned-char is used. +#endif + +#define OBJC_BOOL_DEFINED + #if __has_feature(objc_bool) #define YES __objc_yes #define NO __objc_no @@ -164,7 +180,8 @@ OBJC_EXPORT const char *object_getClassName(id obj) * @note In a garbage-collected environment, the memory is scanned conservatively. */ OBJC_EXPORT void *object_getIndexedIvars(id obj) - OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0); + OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0) + OBJC_ARC_UNAVAILABLE; /** * Identifies a selector as being valid or invalid. diff --git a/runtime/runtime.h b/runtime/runtime.h index 38e74f1..8ed2f43 100644 --- a/runtime/runtime.h +++ b/runtime/runtime.h @@ -152,36 +152,6 @@ OBJC_EXPORT BOOL object_isClass(id obj) OBJC_AVAILABLE(10.10, 8.0, 9.0, 1.0); -/** - * Returns the class name of a given object. - * - * @param obj An Objective-C object. - * - * @return The name of the class of which \e obj is an instance. - */ -OBJC_EXPORT const char *object_getClassName(id obj) - OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0); - -/** - * Returns a pointer to any extra bytes allocated with an instance given object. - * - * @param obj An Objective-C object. - * - * @return A pointer to any extra bytes allocated with \e obj. If \e obj was - * not allocated with any extra bytes, then dereferencing the returned pointer is undefined. - * - * @note This function returns a pointer to any extra bytes allocated with the instance - * (as specified by \c class_createInstance with extraBytes>0). This memory follows the - * object's ordinary ivars, but may not be adjacent to the last ivar. - * @note The returned pointer is guaranteed to be pointer-size aligned, even if the area following - * the object's last ivar is less aligned than that. Alignment greater than pointer-size is never - * guaranteed, even if the area following the object's last ivar is more aligned than that. - * @note In a garbage-collected environment, the memory is scanned conservatively. - */ -OBJC_EXPORT void *object_getIndexedIvars(id obj) - OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0) - OBJC_ARC_UNAVAILABLE; - /** * Reads the value of an instance variable in an object. * @@ -1396,20 +1366,6 @@ OBJC_EXPORT const char **objc_copyClassNamesForImage(const char *image, OBJC_EXPORT const char *sel_getName(SEL sel) OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0); -/** - * Registers a method name with the Objective-C runtime system. - * - * @param str A pointer to a C string. Pass the name of the method you wish to register. - * - * @return A pointer of type SEL specifying the selector for the named method. - * - * @note The implementation of this method is identical to the implementation of \c sel_registerName. - * @note Prior to OS X version 10.0, this method tried to find the selector mapped to the given name - * and returned \c NULL if the selector was not found. This was changed for safety, because it was - * observed that many of the callers of this function did not check the return value for \c NULL. - */ -OBJC_EXPORT SEL sel_getUid(const char *str) - OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0); /** * Registers a method with the Objective-C runtime system, maps the method From 29440e59553ad22c0c44a078995d49aa6fe9c60a Mon Sep 17 00:00:00 2001 From: wangzhaowei Date: Thu, 7 Sep 2017 17:52:09 +0800 Subject: [PATCH 03/12] Version 709.1 --- runtime/objc-os.mm | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/runtime/objc-os.mm b/runtime/objc-os.mm index edf9a46..3049901 100644 --- a/runtime/objc-os.mm +++ b/runtime/objc-os.mm @@ -741,8 +741,9 @@ void _objc_atfork_prepare() loadMethodLock.lock(); PropertyLocks.lockAll(); CppObjectLocks.lockAll(); - classInitLock.enter(); + AssociationsManagerLock.lock(); SideTableLockAll(); + classInitLock.enter(); #if __OBJC2__ runtimeLock.write(); DemangleCacheLock.lock(); @@ -756,7 +757,6 @@ void _objc_atfork_prepare() cacheUpdateLock.lock(); objcMsgLogLock.lock(); AltHandlerDebugLock.lock(); - AssociationsManagerLock.lock(); StructLocks.lockAll(); crashlog_lock.lock(); From d1d94f7efbd598b75456c088b4a95e3cdb810c6d Mon Sep 17 00:00:00 2001 From: shrekwzw Date: Mon, 10 Dec 2018 10:24:49 +0800 Subject: [PATCH 04/12] Version 723 --- interposable.txt | 1 + markgc.cpp | 22 +- objc.xcodeproj/project.pbxproj | 52 +- runtime/Module/ObjectiveC.apinotes | 437 ++++++++++++++++ runtime/Module/module.modulemap | 40 ++ runtime/NSObject.h | 11 +- runtime/NSObject.mm | 18 +- runtime/Object.h | 3 +- runtime/Object.mm | 3 +- runtime/Protocol.h | 10 +- runtime/hashtable2.h | 147 ++++-- runtime/maptable.h | 82 ++- runtime/message.h | 149 +++--- runtime/objc-abi.h | 234 ++++++--- runtime/objc-api.h | 38 +- runtime/objc-auto.h | 70 ++- runtime/objc-block-trampolines.mm | 73 +-- runtime/objc-env.h | 1 + runtime/objc-exception.h | 128 +++-- runtime/objc-gdb.h | 72 +-- runtime/objc-initialize.mm | 171 ++++++- runtime/objc-internal.h | 425 +++++++++------- runtime/objc-load.h | 21 +- runtime/objc-lockdebug.h | 2 +- runtime/objc-lockdebug.mm | 47 +- runtime/objc-locks.h | 2 + runtime/objc-os.h | 105 ++-- runtime/objc-os.mm | 97 +++- runtime/objc-private.h | 15 +- runtime/objc-references.mm | 10 +- runtime/objc-runtime-new.mm | 13 +- runtime/objc-runtime-old.h | 8 + runtime/objc-runtime.mm | 13 + runtime/objc-sel-table.s | 29 +- runtime/objc-sync.h | 28 +- runtime/objc.h | 34 +- runtime/runtime.h | 775 ++++++++++++++++++----------- 37 files changed, 2339 insertions(+), 1047 deletions(-) create mode 100644 interposable.txt create mode 100644 runtime/Module/ObjectiveC.apinotes create mode 100644 runtime/Module/module.modulemap diff --git a/interposable.txt b/interposable.txt new file mode 100644 index 0000000..f5d09f3 --- /dev/null +++ b/interposable.txt @@ -0,0 +1 @@ +_objc_release diff --git a/markgc.cpp b/markgc.cpp index 49fd2ba..4543ad6 100644 --- a/markgc.cpp +++ b/markgc.cpp @@ -31,6 +31,7 @@ #include #include #include +#include #include #include #include @@ -476,7 +477,15 @@ bool parse_fat(uint8_t *buffer, size_t size) fat_magic = OSSwapBigToHostInt32(fh->magic); fat_nfat_arch = OSSwapBigToHostInt32(fh->nfat_arch); - if (size < (sizeof(struct fat_header) + fat_nfat_arch * sizeof(struct fat_arch))) { + size_t fat_arch_size; + // fat_nfat_arch * sizeof(struct fat_arch) + sizeof(struct fat_header) + if (os_mul_and_add_overflow(fat_nfat_arch, sizeof(struct fat_arch), + sizeof(struct fat_header), &fat_arch_size)) + { + printf("too many fat archs\n"); + return false; + } + if (size < fat_arch_size) { printf("file is too small\n"); return false; } @@ -484,7 +493,14 @@ bool parse_fat(uint8_t *buffer, size_t size) archs = (struct fat_arch *)(buffer + sizeof(struct fat_header)); /* Special case hidden CPU_TYPE_ARM64 */ - if (size >= (sizeof(struct fat_header) + (fat_nfat_arch + 1) * sizeof(struct fat_arch))) { + size_t fat_arch_plus_one_size; + if (os_add_overflow(fat_arch_size, sizeof(struct fat_arch), + &fat_arch_plus_one_size)) + { + printf("too many fat archs\n"); + return false; + } + if (size >= fat_arch_plus_one_size) { if (fat_nfat_arch > 0 && OSSwapBigToHostInt32(archs[fat_nfat_arch].cputype) == CPU_TYPE_ARM64) { fat_nfat_arch++; @@ -505,7 +521,7 @@ bool parse_fat(uint8_t *buffer, size_t size) arch_cputype, arch_cpusubtype); /* Check that slice data is after all fat headers and archs */ - if (arch_offset < (sizeof(struct fat_header) + fat_nfat_arch * sizeof(struct fat_arch))) { + if (arch_offset < fat_arch_size) { printf("file is badly formed\n"); return false; } diff --git a/objc.xcodeproj/project.pbxproj b/objc.xcodeproj/project.pbxproj index abf396d..1cd3e32 100644 --- a/objc.xcodeproj/project.pbxproj +++ b/objc.xcodeproj/project.pbxproj @@ -63,7 +63,7 @@ 838485FF0D6D68A200CEA253 /* objc-load.mm in Sources */ = {isa = PBXBuildFile; fileRef = 838485D80D6D68A200CEA253 /* objc-load.mm */; }; 838486000D6D68A200CEA253 /* objc-loadmethod.h in Headers */ = {isa = PBXBuildFile; fileRef = 838485D90D6D68A200CEA253 /* objc-loadmethod.h */; }; 838486010D6D68A200CEA253 /* objc-loadmethod.mm in Sources */ = {isa = PBXBuildFile; fileRef = 838485DA0D6D68A200CEA253 /* objc-loadmethod.mm */; }; - 838486020D6D68A200CEA253 /* objc-lockdebug.mm in Sources */ = {isa = PBXBuildFile; fileRef = 838485DB0D6D68A200CEA253 /* objc-lockdebug.mm */; }; + 838486020D6D68A200CEA253 /* objc-lockdebug.mm in Sources */ = {isa = PBXBuildFile; fileRef = 838485DB0D6D68A200CEA253 /* objc-lockdebug.mm */; settings = {COMPILER_FLAGS = "-Os"; }; }; 838486030D6D68A200CEA253 /* objc-private.h in Headers */ = {isa = PBXBuildFile; fileRef = 838485DC0D6D68A200CEA253 /* objc-private.h */; }; 838486070D6D68A200CEA253 /* objc-runtime-new.h in Headers */ = {isa = PBXBuildFile; fileRef = 838485E00D6D68A200CEA253 /* objc-runtime-new.h */; }; 838486080D6D68A200CEA253 /* objc-runtime-new.mm in Sources */ = {isa = PBXBuildFile; fileRef = 838485E10D6D68A200CEA253 /* objc-runtime-new.mm */; }; @@ -85,6 +85,8 @@ 838486250D6D68F000CEA253 /* List.m in Sources */ = {isa = PBXBuildFile; fileRef = 838486230D6D68F000CEA253 /* List.m */; }; 838486260D6D68F000CEA253 /* List.h in Headers */ = {isa = PBXBuildFile; fileRef = 838486240D6D68F000CEA253 /* List.h */; settings = {ATTRIBUTES = (Public, ); }; }; 838486280D6D6A2400CEA253 /* message.h in Headers */ = {isa = PBXBuildFile; fileRef = 838485BD0D6D687300CEA253 /* message.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 83A4AEDC1EA0840800ACADDE /* module.modulemap in Headers */ = {isa = PBXBuildFile; fileRef = 83A4AED71EA06D9D00ACADDE /* module.modulemap */; settings = {ATTRIBUTES = (Public, ); }; }; + 83A4AEDE1EA08C7200ACADDE /* ObjectiveC.apinotes in Headers */ = {isa = PBXBuildFile; fileRef = 83A4AEDD1EA08C5700ACADDE /* ObjectiveC.apinotes */; settings = {ATTRIBUTES = (Public, ); }; }; 83B1A8BE0FF1AC0D0019EA5B /* objc-msg-simulator-i386.s in Sources */ = {isa = PBXBuildFile; fileRef = 83B1A8BC0FF1AC0D0019EA5B /* objc-msg-simulator-i386.s */; }; 83BE02E40FCCB23400661494 /* objc-file-old.mm in Sources */ = {isa = PBXBuildFile; fileRef = 83BE02E30FCCB23400661494 /* objc-file-old.mm */; }; 83BE02E80FCCB24D00661494 /* objc-file-old.h in Headers */ = {isa = PBXBuildFile; fileRef = 83BE02E50FCCB24D00661494 /* objc-file-old.h */; }; @@ -184,12 +186,15 @@ 8384861A0D6D68A800CEA253 /* runtime.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = runtime.h; path = runtime/runtime.h; sourceTree = ""; }; 838486230D6D68F000CEA253 /* List.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = List.m; path = runtime/OldClasses.subproj/List.m; sourceTree = ""; }; 838486240D6D68F000CEA253 /* List.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = List.h; path = runtime/OldClasses.subproj/List.h; sourceTree = ""; }; + 83A4AED71EA06D9D00ACADDE /* module.modulemap */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = "sourcecode.module-map"; name = module.modulemap; path = runtime/Module/module.modulemap; sourceTree = ""; }; + 83A4AEDD1EA08C5700ACADDE /* ObjectiveC.apinotes */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = ObjectiveC.apinotes; path = runtime/Module/ObjectiveC.apinotes; sourceTree = ""; }; 83B1A8BC0FF1AC0D0019EA5B /* objc-msg-simulator-i386.s */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.asm; name = "objc-msg-simulator-i386.s"; path = "runtime/Messengers.subproj/objc-msg-simulator-i386.s"; sourceTree = ""; }; 83BE02E30FCCB23400661494 /* objc-file-old.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = "objc-file-old.mm"; path = "runtime/objc-file-old.mm"; sourceTree = ""; }; 83BE02E50FCCB24D00661494 /* objc-file-old.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "objc-file-old.h"; path = "runtime/objc-file-old.h"; sourceTree = ""; }; 83BE02E60FCCB24D00661494 /* objc-file.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "objc-file.h"; path = "runtime/objc-file.h"; sourceTree = ""; }; 83BE02E70FCCB24D00661494 /* objc-runtime-old.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "objc-runtime-old.h"; path = "runtime/objc-runtime-old.h"; sourceTree = ""; }; 83C9C3381668B50E00F4E544 /* objc-msg-simulator-x86_64.s */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.asm; name = "objc-msg-simulator-x86_64.s"; path = "runtime/Messengers.subproj/objc-msg-simulator-x86_64.s"; sourceTree = ""; }; + 83CE671D1E6E76B60095A33E /* interposable.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = interposable.txt; sourceTree = ""; }; 83D49E4E13C7C84F0057F1DD /* objc-msg-arm64.s */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.asm; name = "objc-msg-arm64.s"; path = "runtime/Messengers.subproj/objc-msg-arm64.s"; sourceTree = ""; }; 83EB007A121C9EC200B92C16 /* objc-sel-table.s */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.asm; name = "objc-sel-table.s"; path = "runtime/objc-sel-table.s"; sourceTree = ""; }; 83F4B52615E843B100E0926F /* NSObjCRuntime.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = NSObjCRuntime.h; path = runtime/NSObjCRuntime.h; sourceTree = ""; }; @@ -301,6 +306,7 @@ 830F2AA50D7394C200392440 /* markgc.cpp */, 838485B40D6D683300CEA253 /* APPLE_LICENSE */, 838485B50D6D683300CEA253 /* ReleaseNotes.rtf */, + 83CE671D1E6E76B60095A33E /* interposable.txt */, 838485B30D6D682B00CEA253 /* libobjc.order */, ); name = Other; @@ -309,6 +315,8 @@ 838485C60D6D687700CEA253 /* Public Headers */ = { isa = PBXGroup; children = ( + 83A4AEDD1EA08C5700ACADDE /* ObjectiveC.apinotes */, + 83A4AED71EA06D9D00ACADDE /* module.modulemap */, 83F4B52615E843B100E0926F /* NSObjCRuntime.h */, 83F4B52715E843B100E0926F /* NSObject.h */, 838485BD0D6D687300CEA253 /* message.h */, @@ -383,6 +391,8 @@ isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( + 83A4AEDE1EA08C7200ACADDE /* ObjectiveC.apinotes in Headers */, + 83A4AEDC1EA0840800ACADDE /* module.modulemap in Headers */, 830F2A980D738DC200392440 /* hashtable.h in Headers */, 838485BF0D6D687300CEA253 /* hashtable2.h in Headers */, 838486260D6D68F000CEA253 /* List.h in Headers */, @@ -503,7 +513,7 @@ ); runOnlyForDeploymentPostprocessing = 1; shellPath = /bin/sh; - shellScript = "cd \"${INSTALL_DIR}\"\n/bin/ln -s libobjc.A.dylib libobjc.dylib\n"; + shellScript = "cd \"${INSTALL_DIR}\"\n/bin/ln -s libobjc.A.dylib libobjc.dylib\n\nTBD_UPPER=`echo ${GENERATE_TEXT_BASED_STUBS} | tr a-z A-Z`\n\nif [ ${TBD_UPPER} = \"YES\" ] || [ ${TBD_UPPER} = \"TRUE\" ] || [ ${TBD_UPPER} = \"1\" ]; then\nGENERATE_TBD=1\nfi\n\nif [ ${GENERATE_TBD} ]; then\n /bin/ln -s libobjc.A.tbd libobjc.tbd\nfi\n"; }; /* End PBXShellScriptBuildPhase section */ @@ -579,6 +589,9 @@ isa = XCBuildConfiguration; buildSettings = { ARCHS = "$(ARCHS_STANDARD_32_64_BIT)"; + COPY_HEADERS_RUN_UNIFDEF = YES; + COPY_HEADERS_UNIFDEF_FLAGS = "-UBUILD_FOR_OSX"; + "COPY_HEADERS_UNIFDEF_FLAGS[sdk=macosx*]" = "-DBUILD_FOR_OSX"; COPY_PHASE_STRIP = NO; DYLIB_CURRENT_VERSION = 228; EXECUTABLE_PREFIX = lib; @@ -610,8 +623,18 @@ __objc_data, "-Xlinker", 0x1000, + "-Xlinker", + "-interposable_list", + "-Xlinker", + interposable.txt, + ); + "OTHER_LDFLAGS[sdk=iphonesimulator*][arch=*]" = ( + "-lc++abi", + "-Xlinker", + "-interposable_list", + "-Xlinker", + interposable.txt, ); - "OTHER_LDFLAGS[sdk=iphonesimulator*][arch=*]" = "-lc++abi"; "OTHER_LDFLAGS[sdk=macosx*]" = ( "-lCrashReporterClient", "-lc++abi", @@ -623,6 +646,10 @@ __objc_data, "-Xlinker", 0x1000, + "-Xlinker", + "-interposable_list", + "-Xlinker", + interposable.txt, ); PRIVATE_HEADERS_FOLDER_PATH = /usr/local/include/objc; PRODUCT_NAME = objc.A; @@ -634,6 +661,9 @@ 1DEB914C08733D8E0010E9CD /* Release */ = { isa = XCBuildConfiguration; buildSettings = { + COPY_HEADERS_RUN_UNIFDEF = YES; + COPY_HEADERS_UNIFDEF_FLAGS = "-UBUILD_FOR_OSX"; + "COPY_HEADERS_UNIFDEF_FLAGS[sdk=macosx*]" = "-DBUILD_FOR_OSX"; DYLIB_CURRENT_VERSION = 228; EXECUTABLE_PREFIX = lib; GCC_CW_ASM_SYNTAX = NO; @@ -663,8 +693,18 @@ __objc_data, "-Xlinker", 0x1000, + "-Xlinker", + "-interposable_list", + "-Xlinker", + interposable.txt, + ); + "OTHER_LDFLAGS[sdk=iphonesimulator*][arch=*]" = ( + "-lc++abi", + "-Xlinker", + "-interposable_list", + "-Xlinker", + interposable.txt, ); - "OTHER_LDFLAGS[sdk=iphonesimulator*][arch=*]" = "-lc++abi"; "OTHER_LDFLAGS[sdk=macosx*]" = ( "-lCrashReporterClient", "-lc++abi", @@ -676,6 +716,10 @@ __objc_data, "-Xlinker", 0x1000, + "-Xlinker", + "-interposable_list", + "-Xlinker", + interposable.txt, ); PRIVATE_HEADERS_FOLDER_PATH = /usr/local/include/objc; PRODUCT_NAME = objc.A; diff --git a/runtime/Module/ObjectiveC.apinotes b/runtime/Module/ObjectiveC.apinotes new file mode 100644 index 0000000..5491c89 --- /dev/null +++ b/runtime/Module/ObjectiveC.apinotes @@ -0,0 +1,437 @@ +--- +Name: ObjectiveC +Classes: +- Name: NSArray + SwiftBridge: 'Swift.Array' +- Name: NSDictionary + SwiftBridge: 'Swift.Dictionary' +- Name: NSSet + SwiftBridge: 'Swift.Set' +- Name: NSString + SwiftBridge: 'Swift.String' +- Name: List + Methods: + - Selector: init + MethodKind: Instance + NullabilityOfRet: N + - Selector: 'isEqual:' + MethodKind: Instance + Nullability: + - O + NullabilityOfRet: S +- Name: NSObject + SwiftName: NSObject + Methods: + - Selector: alloc + MethodKind: Class + NullabilityOfRet: N + - Selector: 'allocWithZone:' + MethodKind: Class + Nullability: + - S + NullabilityOfRet: N + - Selector: class + MethodKind: Class + Availability: nonswift + AvailabilityMsg: use 'self' instead + - Selector: 'conformsToProtocol:' + MethodKind: Class + Nullability: + - N + NullabilityOfRet: S + - Selector: copy + MethodKind: Instance + NullabilityOfRet: N + - Selector: dealloc + MethodKind: Instance + Availability: nonswift + AvailabilityMsg: use 'deinit' to define a de-initializer + - Selector: debugDescription + MethodKind: Class + NullabilityOfRet: N + - Selector: description + MethodKind: Class + NullabilityOfRet: N + - Selector: 'forwardingTargetForSelector:' + MethodKind: Instance + Nullability: + - S + NullabilityOfRet: O + - Selector: 'forwardInvocation:' + MethodKind: Instance + Availability: nonswift + - Selector: init + MethodKind: Instance + NullabilityOfRet: N + DesignatedInit: true + - Selector: 'instanceMethodSignatureForSelector:' + MethodKind: Class + Availability: nonswift + - Selector: 'isSubclassOfClass:' + MethodKind: Class + Nullability: + - N + NullabilityOfRet: S + - Selector: 'methodSignatureForSelector:' + MethodKind: Instance + Availability: nonswift + - Selector: mutableCopy + MethodKind: Instance + NullabilityOfRet: N + - Selector: new + MethodKind: Class + NullabilityOfRet: N + - Selector: superclass + MethodKind: Class + NullabilityOfRet: O +- Name: Object + Methods: + - Selector: init + MethodKind: Instance + NullabilityOfRet: N + - Selector: 'isEqual:' + MethodKind: Instance + Nullability: + - O + NullabilityOfRet: S +Protocols: +- Name: NSObject + SwiftName: NSObjectProtocol + Methods: + - Selector: class + MethodKind: Instance + Availability: nonswift + AvailabilityMsg: use 'type(of:)' instead + - Selector: 'conformsToProtocol:' + MethodKind: Instance + Nullability: + - N + NullabilityOfRet: S + - Selector: 'isEqual:' + MethodKind: Instance + Nullability: + - O + NullabilityOfRet: S + - Selector: 'isKindOfClass:' + MethodKind: Instance + Nullability: + - N + NullabilityOfRet: S + - Selector: 'isMemberOfClass:' + MethodKind: Instance + Nullability: + - N + NullabilityOfRet: S + - Selector: self + MethodKind: Instance + NullabilityOfRet: N + Properties: + - Name: debugDescription + Nullability: N + - Name: description + Nullability: N + - Name: superclass + Nullability: O +Tags: +- Name: _NSZone + SwiftName: _NSZone + + +# Runtime functions did not yet have nullability in Swift 3. + +SwiftVersions: +- Version: 3 + Functions: + # objc.h swift3 + - Name: object_getClassName + NullabilityOfRet: U + Nullability: [U] + - Name: sel_isMapped + Nullability: [U] + - Name: sel_getUid + NullabilityOfRet: U + Nullability: [U] + + # objc-exception.h swift3 + - Name: objc_exception_throw + Nullability: [U] + - Name: objc_begin_catch + NullabilityOfRet: U + Nullability: [U] + - Name: objc_setExceptionPreprocessor + NullabilityOfRet: U + Nullability: [U] + - Name: objc_setExceptionMatcher + NullabilityOfRet: U + Nullability: [U] + - Name: objc_setUncaughtExceptionHandler + NullabilityOfRet: U + Nullability: [U] + - Name: objc_addExceptionHandler + Nullability: [U, U] + + # objc-sync.h swift3 + - Name: objc_sync_enter + Nullability: [U] + - Name: objc_sync_exit + Nullability: [U] + + # runtime.h swift3 + - Name: object_getClass + NullabilityOfRet: U + Nullability: [U] + - Name: object_setClass + NullabilityOfRet: U + Nullability: [U, U] + - Name: object_isClass + Nullability: [U] + - Name: object_getIvar + NullabilityOfRet: U + Nullability: [U, U] + - Name: object_setIvar + Nullability: [U, U, U] + - Name: object_setIvarWithStrongDefault + Nullability: [U, U, U] + - Name: objc_getClass + NullabilityOfRet: U + Nullability: [U] + - Name: objc_getMetaClass + NullabilityOfRet: U + Nullability: [U] + - Name: objc_lookUpClass + NullabilityOfRet: U + Nullability: [U] + - Name: objc_getRequiredClass + NullabilityOfRet: U + Nullability: [U] + - Name: objc_getClassList + Parameters: + - Position: 0 + Type: "Class _Nullable * _Null_unspecified" + - Name: objc_copyClassList + ResultType: "Class _Nullable * _Null_unspecified" + Nullability: [U] + - Name: class_getName + NullabilityOfRet: U + Nullability: [U] + - Name: class_isMetaClass + Nullability: [U] + - Name: class_getSuperclass + NullabilityOfRet: U + Nullability: [U] + - Name: class_getVersion + Nullability: [U] + - Name: class_setVersion + Nullability: [U] + - Name: class_getInstanceSize + Nullability: [U] + - Name: class_getInstanceVariable + NullabilityOfRet: U + Nullability: [U, U] + - Name: class_getClassVariable + NullabilityOfRet: U + Nullability: [U, U] + - Name: class_copyIvarList + ResultType: "Ivar _Nullable * _Null_unspecified" + Nullability: [U, U] + - Name: class_getInstanceMethod + NullabilityOfRet: U + Nullability: [U, U] + - Name: class_getClassMethod + NullabilityOfRet: U + Nullability: [U, U] + - Name: class_getMethodImplementation + NullabilityOfRet: U + Nullability: [U, U] + - Name: class_getMethodImplementation_stret + NullabilityOfRet: U + Nullability: [U, U] + - Name: class_respondsToSelector + Nullability: [U, U] + - Name: class_copyMethodList + Nullability: [U, U] + ResultType: "Method _Nullable * _Null_unspecified" + - Name: class_conformsToProtocol + Nullability: [U, U] + - Name: class_copyProtocolList +# fixme ResultType: + NullabilityOfRet: U + Nullability: [U, U] + - Name: class_getProperty + NullabilityOfRet: U + Nullability: [U, U] + - Name: class_copyPropertyList + ResultType: "objc_property_t _Nullable * _Null_unspecified" + Nullability: [U, U] + - Name: class_getIvarLayout + NullabilityOfRet: U + Nullability: [U] + - Name: class_getWeakIvarLayout + NullabilityOfRet: U + Nullability: [U] + - Name: class_addMethod + Nullability: [U, U, U, U] + - Name: class_replaceMethod + NullabilityOfRet: U + Nullability: [U, U, U, U] + - Name: class_addIvar + Nullability: [U, U, U, U, U] + - Name: class_addProtocol + Nullability: [U, U] + - Name: class_addProperty + Nullability: [U, U, U, U] + - Name: class_replaceProperty + Nullability: [U, U, U, U] + - Name: class_setIvarLayout + Nullability: [U, U] + - Name: class_setWeakIvarLayout + Nullability: [U, U] + - Name: class_createInstance + NullabilityOfRet: U + Nullability: [U, U] + - Name: objc_allocateClassPair + NullabilityOfRet: U + Nullability: [U, U, U] + - Name: objc_registerClassPair + Nullability: [U] + - Name: objc_duplicateClass + NullabilityOfRet: U + Nullability: [U, U, U] + - Name: objc_disposeClassPair + Nullability: [U] + - Name: method_getName + NullabilityOfRet: U + Nullability: [U] + - Name: method_getImplementation + NullabilityOfRet: U + Nullability: [U] + - Name: method_getTypeEncoding + NullabilityOfRet: U + Nullability: [U] + - Name: method_getNumberOfArguments + Nullability: [U] + - Name: method_copyReturnType + NullabilityOfRet: U + Nullability: [U] + - Name: method_copyArgumentType + NullabilityOfRet: U + Nullability: [U, U] + - Name: method_getReturnType + Nullability: [U, U, U] + - Name: method_getArgumentType + Nullability: [U, U, U, U] + - Name: method_getDescription + NullabilityOfRet: U + Nullability: [U] + - Name: method_setImplementation + NullabilityOfRet: U + Nullability: [U, U] + - Name: method_exchangeImplementations + Nullability: [U, U] + - Name: ivar_getName + NullabilityOfRet: U + Nullability: [U] + - Name: ivar_getTypeEncoding + NullabilityOfRet: U + Nullability: [U] + - Name: ivar_getOffset + Nullability: [U] + - Name: property_getName + NullabilityOfRet: U + Nullability: [U] + - Name: property_getAttributes + NullabilityOfRet: U + Nullability: [U] + - Name: property_copyAttributeList + NullabilityOfRet: U + Nullability: [U, U] + - Name: property_copyAttributeValue + NullabilityOfRet: U + Nullability: [U, U] + - Name: objc_getProtocol + NullabilityOfRet: U + Nullability: [U] + - Name: objc_copyProtocolList +# fixme ResultType: + NullabilityOfRet: U + Nullability: [U] + - Name: protocol_conformsToProtocol + Nullability: [U, U] + - Name: protocol_isEqual + Nullability: [U, U] + - Name: protocol_getName + NullabilityOfRet: U + Nullability: [U] + - Name: protocol_getMethodDescription + Nullability: [U, U, U, U] + - Name: protocol_copyMethodDescriptionList + NullabilityOfRet: U + Nullability: [U, U, U, U] + - Name: protocol_getProperty + NullabilityOfRet: U + Nullability: [U, U, U, U] + - Name: protocol_copyPropertyList + ResultType: "objc_property_t _Nullable * _Null_unspecified" + Nullability: [U, U] + - Name: protocol_copyPropertyList2 + ResultType: "objc_property_t _Nullable * _Null_unspecified" + Nullability: [U, U, U, U] + - Name: protocol_copyProtocolList +# fixme ResultType: + NullabilityOfRet: U + Nullability: [U, U] + - Name: objc_allocateProtocol + NullabilityOfRet: U + Nullability: [U] + - Name: objc_registerProtocol + Nullability: [U] + - Name: protocol_addMethodDescription + Nullability: [U, U, U, U, U] + - Name: protocol_addProtocol + Nullability: [U, U] + - Name: protocol_addProperty + Nullability: [U, U, U, U, U, U] + - Name: objc_copyImageNames + ResultType: "const char * _Nullable * _Null_unspecified" + Nullability: [U] + - Name: class_getImageName + NullabilityOfRet: U + Nullability: [U] + - Name: objc_copyClassNamesForImage + ResultType: "const char * _Nullable * _Null_unspecified" + Nullability: [U, U] + - Name: sel_getName + NullabilityOfRet: U + Nullability: [U] + - Name: sel_registerName + NullabilityOfRet: U + Nullability: [U] + - Name: sel_isEqual + Nullability: [U, U] + - Name: objc_enumerationMutation + Nullability: [U] + - Name: objc_setEnumerationMutationHandler + Nullability: [U] + - Name: objc_setForwardHandler + Nullability: [U, U] + - Name: imp_implementationWithBlock + NullabilityOfRet: U + Nullability: [U] + - Name: imp_getBlock + NullabilityOfRet: U + Nullability: [U] + - Name: imp_removeBlock + Nullability: [U] + - Name: objc_loadWeak + NullabilityOfRet: U + Nullability: [U] + - Name: objc_storeWeak + NullabilityOfRet: U + Nullability: [U, U] + - Name: objc_setAssociatedObject + Nullability: [U, U, U, U] + - Name: objc_getAssociatedObject + NullabilityOfRet: U + Nullability: [U, U] + - Name: objc_removeAssociatedObjects + Nullability: [U] diff --git a/runtime/Module/module.modulemap b/runtime/Module/module.modulemap new file mode 100644 index 0000000..74f7d64 --- /dev/null +++ b/runtime/Module/module.modulemap @@ -0,0 +1,40 @@ +module ObjectiveC [system] [extern_c] { + umbrella "." + export * + module * { + export * + } + + module NSObject { + requires objc + header "NSObject.h" + export * + } + +#if defined(BUILD_FOR_OSX) + module List { + // Uses @defs, which does not work in ObjC++ or non-ARC. + requires objc, !objc_arc, !cplusplus + header "List.h" + export * + } + + module Object { + requires objc + header "Object.h" + export * + } + + module Protocol { + requires objc + header "Protocol.h" + export * + } +#endif + +#if !defined(BUILD_FOR_OSX) + // These file are not available outside macOS. + exclude header "hashtable.h" + exclude header "hashtable2.h" +#endif +} diff --git a/runtime/NSObject.h b/runtime/NSObject.h index 7bd8611..2172aba 100644 --- a/runtime/NSObject.h +++ b/runtime/NSObject.h @@ -47,11 +47,14 @@ @end -OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0) +OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0) OBJC_ROOT_CLASS OBJC_EXPORT @interface NSObject { +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wobjc-interface-ivars" Class isa OBJC_ISA_AVAILABILITY; +#pragma clang diagnostic pop } + (void)load; @@ -82,7 +85,7 @@ OBJC_EXPORT + (IMP)instanceMethodForSelector:(SEL)aSelector; - (void)doesNotRecognizeSelector:(SEL)aSelector; -- (id)forwardingTargetForSelector:(SEL)aSelector OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0); +- (id)forwardingTargetForSelector:(SEL)aSelector OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0); - (void)forwardInvocation:(NSInvocation *)anInvocation OBJC_SWIFT_UNAVAILABLE(""); - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector OBJC_SWIFT_UNAVAILABLE(""); @@ -93,8 +96,8 @@ OBJC_EXPORT + (BOOL)isSubclassOfClass:(Class)aClass; -+ (BOOL)resolveClassMethod:(SEL)sel OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0); -+ (BOOL)resolveInstanceMethod:(SEL)sel OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0); ++ (BOOL)resolveClassMethod:(SEL)sel OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0); ++ (BOOL)resolveInstanceMethod:(SEL)sel OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0); + (NSUInteger)hash; + (Class)superclass; diff --git a/runtime/NSObject.mm b/runtime/NSObject.mm index 7a7b391..ec528a0 100644 --- a/runtime/NSObject.mm +++ b/runtime/NSObject.mm @@ -251,6 +251,22 @@ void SideTableLocksSucceedLock(const void *oldlock) { SideTables().succeedLock(oldlock); } +void SideTableLocksPrecedeLocks(StripedMap& newlocks) { + int i = 0; + const void *newlock; + while ((newlock = newlocks.getLock(i++))) { + SideTables().precedeLock(newlock); + } +} + +void SideTableLocksSucceedLocks(StripedMap& oldlocks) { + int i = 0; + const void *oldlock; + while ((oldlock = oldlocks.getLock(i++))) { + SideTables().succeedLock(oldlock); + } +} + // // The -fobjc-arc flag causes the compiler to issue calls to objc_{retain/release/autorelease/retain_block} // @@ -1070,7 +1086,7 @@ static void badPop(void *token) // Error. For bincompat purposes this is not // fatal in executables built with old SDKs. - if (DebugPoolAllocation || sdkIsAtLeast(10_12, 10_0, 10_0, 3_0)) { + if (DebugPoolAllocation || sdkIsAtLeast(10_12, 10_0, 10_0, 3_0, 2_0)) { // OBJC_DEBUG_POOL_ALLOCATION or new SDK. Bad pop is fatal. _objc_fatal ("Invalid or prematurely-freed autorelease pool %p.", token); diff --git a/runtime/Object.h b/runtime/Object.h index 5c857ac..86ce416 100644 --- a/runtime/Object.h +++ b/runtime/Object.h @@ -38,7 +38,8 @@ #if __OBJC__ && !__OBJC2__ __OSX_AVAILABLE(10.0) -__IOS_UNAVAILABLE __TVOS_UNAVAILABLE __WATCHOS_UNAVAILABLE +__IOS_UNAVAILABLE __TVOS_UNAVAILABLE +__WATCHOS_UNAVAILABLE __BRIDGEOS_UNAVAILABLE OBJC_ROOT_CLASS @interface Object { diff --git a/runtime/Object.mm b/runtime/Object.mm index 3ec14be..d5721aa 100644 --- a/runtime/Object.mm +++ b/runtime/Object.mm @@ -36,7 +36,8 @@ #if __OBJC2__ __OSX_AVAILABLE(10.0) -__IOS_UNAVAILABLE __TVOS_UNAVAILABLE __WATCHOS_UNAVAILABLE +__IOS_UNAVAILABLE __TVOS_UNAVAILABLE +__WATCHOS_UNAVAILABLE __BRIDGEOS_UNAVAILABLE OBJC_ROOT_CLASS @interface Object { Class isa; diff --git a/runtime/Protocol.h b/runtime/Protocol.h index 1f2a7b5..f5169be 100644 --- a/runtime/Protocol.h +++ b/runtime/Protocol.h @@ -41,7 +41,7 @@ // All methods of class Protocol are unavailable. // Use the functions in objc/runtime.h instead. -OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0) +OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0) @interface Protocol : NSObject @end @@ -50,7 +50,7 @@ OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0) #include -OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0) +OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0) @interface Protocol : Object { @private @@ -74,12 +74,14 @@ OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0) __OSX_DEPRECATED(10.0, 10.5, "use protocol_getMethodDescription instead") __IOS_DEPRECATED(2.0, 2.0, "use protocol_getMethodDescription instead") __TVOS_DEPRECATED(9.0, 9.0, "use protocol_getMethodDescription instead") - __WATCHOS_DEPRECATED(1.0, 1.0, "use protocol_getMethodDescription instead"); + __WATCHOS_DEPRECATED(1.0, 1.0, "use protocol_getMethodDescription instead") + __BRIDGEOS_DEPRECATED(2.0, 2.0, "use protocol_getMethodDescription instead"); - (struct objc_method_description *) descriptionForClassMethod:(SEL)aSel __OSX_DEPRECATED(10.0, 10.5, "use protocol_getMethodDescription instead") __IOS_DEPRECATED(2.0, 2.0, "use protocol_getMethodDescription instead") __TVOS_DEPRECATED(9.0, 9.0, "use protocol_getMethodDescription instead") - __WATCHOS_DEPRECATED(1.0, 1.0, "use protocol_getMethodDescription instead"); + __WATCHOS_DEPRECATED(1.0, 1.0, "use protocol_getMethodDescription instead") + __BRIDGEOS_DEPRECATED(2.0, 2.0, "use protocol_getMethodDescription instead"); @end diff --git a/runtime/hashtable2.h b/runtime/hashtable2.h index 197a17f..a7dd3c8 100644 --- a/runtime/hashtable2.h +++ b/runtime/hashtable2.h @@ -32,7 +32,8 @@ #ifndef _OBJC_PRIVATE_H_ # define OBJC_HASH_AVAILABILITY \ __OSX_DEPRECATED(10.0, 10.1, "NXHashTable is deprecated") \ - __IOS_UNAVAILABLE __TVOS_UNAVAILABLE __WATCHOS_UNAVAILABLE + __IOS_UNAVAILABLE __TVOS_UNAVAILABLE \ + __WATCHOS_UNAVAILABLE __BRIDGEOS_UNAVAILABLE #else # define OBJC_HASH_AVAILABILITY #endif @@ -52,9 +53,13 @@ The objective C class HashTable is preferred when dealing with (key, values) ass As well-behaved scalable data structures, hash tables double in size when they start becoming full, thus guaranteeing both average constant time access and linear size. */ typedef struct { - uintptr_t (*hash)(const void *info, const void *data); - int (*isEqual)(const void *info, const void *data1, const void *data2); - void (*free)(const void *info, void *data); + uintptr_t (* _Nonnull hash)(const void * _Nullable info, + const void * _Nullable data); + int (* _Nonnull isEqual)(const void * _Nullable info, + const void * _Nullable data1, + const void * _Nullable data2); + void (* _Nonnull free)(const void * _Nullable info, + void * _Nullable data); int style; /* reserved for future expansion; currently 0 */ } NXHashTablePrototype; @@ -67,41 +72,63 @@ typedef struct { */ typedef struct { - const NXHashTablePrototype *prototype OBJC_HASH_AVAILABILITY; + const NXHashTablePrototype * _Nonnull prototype OBJC_HASH_AVAILABILITY; unsigned count OBJC_HASH_AVAILABILITY; unsigned nbBuckets OBJC_HASH_AVAILABILITY; - void *buckets OBJC_HASH_AVAILABILITY; - const void *info OBJC_HASH_AVAILABILITY; + void * _Nullable buckets OBJC_HASH_AVAILABILITY; + const void * _Nullable info OBJC_HASH_AVAILABILITY; } NXHashTable OBJC_HASH_AVAILABILITY; /* private data structure; may change */ -OBJC_EXPORT NXHashTable *NXCreateHashTableFromZone (NXHashTablePrototype prototype, unsigned capacity, const void *info, void *z) OBJC_HASH_AVAILABILITY; -OBJC_EXPORT NXHashTable *NXCreateHashTable (NXHashTablePrototype prototype, unsigned capacity, const void *info) OBJC_HASH_AVAILABILITY; +OBJC_EXPORT NXHashTable * _Nonnull +NXCreateHashTableFromZone (NXHashTablePrototype prototype, unsigned capacity, + const void * _Nullable info, void * _Nullable z) + OBJC_HASH_AVAILABILITY; + +OBJC_EXPORT NXHashTable * _Nonnull +NXCreateHashTable (NXHashTablePrototype prototype, unsigned capacity, + const void * _Nullable info) + OBJC_HASH_AVAILABILITY; /* if hash is 0, pointer hash is assumed */ /* if isEqual is 0, pointer equality is assumed */ /* if free is 0, elements are not freed */ /* capacity is only a hint; 0 creates a small table */ /* info allows call backs to be very general */ -OBJC_EXPORT void NXFreeHashTable (NXHashTable *table) OBJC_HASH_AVAILABILITY; +OBJC_EXPORT void +NXFreeHashTable (NXHashTable * _Nonnull table) + OBJC_HASH_AVAILABILITY; /* calls free for each data, and recovers table */ -OBJC_EXPORT void NXEmptyHashTable (NXHashTable *table) OBJC_HASH_AVAILABILITY; +OBJC_EXPORT void +NXEmptyHashTable (NXHashTable * _Nonnull table) + OBJC_HASH_AVAILABILITY; /* does not deallocate table nor data; keeps current capacity */ -OBJC_EXPORT void NXResetHashTable (NXHashTable *table) OBJC_HASH_AVAILABILITY; +OBJC_EXPORT void +NXResetHashTable (NXHashTable * _Nonnull table) + OBJC_HASH_AVAILABILITY; /* frees each entry; keeps current capacity */ -OBJC_EXPORT BOOL NXCompareHashTables (NXHashTable *table1, NXHashTable *table2) OBJC_HASH_AVAILABILITY; +OBJC_EXPORT BOOL +NXCompareHashTables (NXHashTable * _Nonnull table1, + NXHashTable * _Nonnull table2) + OBJC_HASH_AVAILABILITY; /* Returns YES if the two sets are equal (each member of table1 in table2, and table have same size) */ -OBJC_EXPORT NXHashTable *NXCopyHashTable (NXHashTable *table) OBJC_HASH_AVAILABILITY; +OBJC_EXPORT NXHashTable * _Nonnull +NXCopyHashTable (NXHashTable * _Nonnull table) + OBJC_HASH_AVAILABILITY; /* makes a fresh table, copying data pointers, not data itself. */ -OBJC_EXPORT unsigned NXCountHashTable (NXHashTable *table) OBJC_HASH_AVAILABILITY; +OBJC_EXPORT unsigned +NXCountHashTable (NXHashTable * _Nonnull table) + OBJC_HASH_AVAILABILITY; /* current number of data in table */ -OBJC_EXPORT int NXHashMember (NXHashTable *table, const void *data) OBJC_HASH_AVAILABILITY; +OBJC_EXPORT int +NXHashMember (NXHashTable * _Nonnull table, const void * _Nullable data) + OBJC_HASH_AVAILABILITY; /* returns non-0 iff data is present in table. Example of use when the hashed data is a struct containing the key, and when the callee only has a key: @@ -110,7 +137,9 @@ OBJC_EXPORT int NXHashMember (NXHashTable *table, const void *data) OBJC_HASH_AV return NXHashMember (myTable, &pseudo) */ -OBJC_EXPORT void *NXHashGet (NXHashTable *table, const void *data) OBJC_HASH_AVAILABILITY; +OBJC_EXPORT void * _Nullable +NXHashGet (NXHashTable * _Nonnull table, const void * _Nullable data) + OBJC_HASH_AVAILABILITY; /* return original table data or NULL. Example of use when the hashed data is a struct containing the key, and when the callee only has a key: @@ -120,14 +149,20 @@ OBJC_EXPORT void *NXHashGet (NXHashTable *table, const void *data) OBJC_HASH_AVA original = NXHashGet (myTable, &pseudo) */ -OBJC_EXPORT void *NXHashInsert (NXHashTable *table, const void *data) OBJC_HASH_AVAILABILITY; +OBJC_EXPORT void * _Nullable +NXHashInsert (NXHashTable * _Nonnull table, const void * _Nullable data) + OBJC_HASH_AVAILABILITY; /* previous data or NULL is returned. */ -OBJC_EXPORT void *NXHashInsertIfAbsent (NXHashTable *table, const void *data) OBJC_HASH_AVAILABILITY; +OBJC_EXPORT void * _Nullable +NXHashInsertIfAbsent (NXHashTable * _Nonnull table, const void * _Nullable data) + OBJC_HASH_AVAILABILITY; /* If data already in table, returns the one in table else adds argument to table and returns argument. */ -OBJC_EXPORT void *NXHashRemove (NXHashTable *table, const void *data) OBJC_HASH_AVAILABILITY; +OBJC_EXPORT void * _Nullable +NXHashRemove (NXHashTable * _Nonnull table, const void * _Nullable data) + OBJC_HASH_AVAILABILITY; /* previous data or NULL is returned */ /* Iteration over all elements of a table consists in setting up an iteration state and then to progress until all entries have been visited. An example of use for counting elements in a table is: @@ -142,9 +177,13 @@ OBJC_EXPORT void *NXHashRemove (NXHashTable *table, const void *data) OBJC_HASH_ typedef struct {int i; int j;} NXHashState OBJC_HASH_AVAILABILITY; /* callers should not rely on actual contents of the struct */ -OBJC_EXPORT NXHashState NXInitHashState(NXHashTable *table) OBJC_HASH_AVAILABILITY; +OBJC_EXPORT NXHashState +NXInitHashState(NXHashTable * _Nonnull table) + OBJC_HASH_AVAILABILITY; -OBJC_EXPORT int NXNextHashState(NXHashTable *table, NXHashState *state, void **data) OBJC_HASH_AVAILABILITY; +OBJC_EXPORT int +NXNextHashState(NXHashTable * _Nonnull table, NXHashState * _Nonnull state, + void * _Nullable * _Nonnull data) OBJC_HASH_AVAILABILITY; /* returns 0 when all elements have been visited */ /************************************************************************* @@ -152,23 +191,45 @@ OBJC_EXPORT int NXNextHashState(NXHashTable *table, NXHashState *state, void **d * and common prototypes *************************************************************************/ -OBJC_EXPORT uintptr_t NXPtrHash(const void *info, const void *data) OBJC_HASH_AVAILABILITY; +OBJC_EXPORT uintptr_t +NXPtrHash(const void * _Nullable info, const void * _Nullable data) + OBJC_HASH_AVAILABILITY; /* scrambles the address bits; info unused */ -OBJC_EXPORT uintptr_t NXStrHash(const void *info, const void *data) OBJC_HASH_AVAILABILITY; + +OBJC_EXPORT uintptr_t +NXStrHash(const void * _Nullable info, const void * _Nullable data) + OBJC_HASH_AVAILABILITY; /* string hashing; info unused */ -OBJC_EXPORT int NXPtrIsEqual(const void *info, const void *data1, const void *data2) OBJC_HASH_AVAILABILITY; + +OBJC_EXPORT int +NXPtrIsEqual(const void * _Nullable info, const void * _Nullable data1, + const void * _Nullable data2) + OBJC_HASH_AVAILABILITY; /* pointer comparison; info unused */ -OBJC_EXPORT int NXStrIsEqual(const void *info, const void *data1, const void *data2) OBJC_HASH_AVAILABILITY; + +OBJC_EXPORT int +NXStrIsEqual(const void * _Nullable info, const void * _Nullable data1, + const void * _Nullable data2) + OBJC_HASH_AVAILABILITY; /* string comparison; NULL ok; info unused */ -OBJC_EXPORT void NXNoEffectFree(const void *info, void *data) OBJC_HASH_AVAILABILITY; + +OBJC_EXPORT void +NXNoEffectFree(const void * _Nullable info, void * _Nullable data) + OBJC_HASH_AVAILABILITY; /* no effect; info unused */ -OBJC_EXPORT void NXReallyFree(const void *info, void *data) OBJC_HASH_AVAILABILITY; + +OBJC_EXPORT void +NXReallyFree(const void * _Nullable info, void * _Nullable data) + OBJC_HASH_AVAILABILITY; /* frees it; info unused */ /* The two following prototypes are useful for manipulating set of pointers or set of strings; For them free is defined as NXNoEffectFree */ -OBJC_EXPORT const NXHashTablePrototype NXPtrPrototype OBJC_HASH_AVAILABILITY; +OBJC_EXPORT const NXHashTablePrototype NXPtrPrototype + OBJC_HASH_AVAILABILITY; /* prototype when data is a pointer (void *) */ -OBJC_EXPORT const NXHashTablePrototype NXStrPrototype OBJC_HASH_AVAILABILITY; + +OBJC_EXPORT const NXHashTablePrototype NXStrPrototype + OBJC_HASH_AVAILABILITY; /* prototype when data is a string (char *) */ /* following prototypes help describe mappings where the key is the first element of a struct and is either a pointer or a string. @@ -181,8 +242,10 @@ For example NXStrStructKeyPrototype can be used to hash pointers to Example, whe For the following prototypes, free is defined as NXReallyFree. */ -OBJC_EXPORT const NXHashTablePrototype NXPtrStructKeyPrototype OBJC_HASH_AVAILABILITY; -OBJC_EXPORT const NXHashTablePrototype NXStrStructKeyPrototype OBJC_HASH_AVAILABILITY; +OBJC_EXPORT const NXHashTablePrototype NXPtrStructKeyPrototype + OBJC_HASH_AVAILABILITY; +OBJC_EXPORT const NXHashTablePrototype NXStrStructKeyPrototype + OBJC_HASH_AVAILABILITY; #if !__OBJC2__ && !TARGET_OS_WIN32 @@ -196,29 +259,39 @@ A unique string is a string that is allocated once for all (never de-allocated) typedef const char *NXAtom OBJC_HASH_AVAILABILITY; -OBJC_EXPORT NXAtom NXUniqueString(const char *buffer) OBJC_HASH_AVAILABILITY; +OBJC_EXPORT NXAtom _Nullable +NXUniqueString(const char * _Nullable buffer) + OBJC_HASH_AVAILABILITY; /* assumes that buffer is \0 terminated, and returns a previously created string or a new string that is a copy of buffer. If NULL is passed returns NULL. Returned string should never be modified. To ensure this invariant, allocations are made in a special read only zone. */ -OBJC_EXPORT NXAtom NXUniqueStringWithLength(const char *buffer, int length) OBJC_HASH_AVAILABILITY; +OBJC_EXPORT NXAtom _Nonnull +NXUniqueStringWithLength(const char * _Nullable buffer, int length) + OBJC_HASH_AVAILABILITY; /* assumes that buffer is a non NULL buffer of at least length characters. Returns a previously created string or a new string that is a copy of buffer. If buffer contains \0, string will be truncated. As for NXUniqueString, returned string should never be modified. */ -OBJC_EXPORT NXAtom NXUniqueStringNoCopy(const char *string) OBJC_HASH_AVAILABILITY; +OBJC_EXPORT NXAtom _Nullable +NXUniqueStringNoCopy(const char * _Nullable string) + OBJC_HASH_AVAILABILITY; /* If there is already a unique string equal to string, returns the original. Otherwise, string is entered in the table, without making a copy. Argument should then never be modified. */ -OBJC_EXPORT char *NXCopyStringBuffer(const char *buffer) OBJC_HASH_AVAILABILITY; +OBJC_EXPORT char * _Nullable +NXCopyStringBuffer(const char * _Nullable buffer) + OBJC_HASH_AVAILABILITY; /* given a buffer, allocates a new string copy of buffer. Buffer should be \0 terminated; returned string is \0 terminated. */ -OBJC_EXPORT char *NXCopyStringBufferFromZone(const char *buffer, void *z) OBJC_HASH_AVAILABILITY; +OBJC_EXPORT char * _Nullable +NXCopyStringBufferFromZone(const char * _Nullable buffer, void * _Nullable z) + OBJC_HASH_AVAILABILITY; /* given a buffer, allocates a new string copy of buffer. Buffer should be \0 terminated; returned string is \0 terminated. */ diff --git a/runtime/maptable.h b/runtime/maptable.h index e43da78..248c18f 100644 --- a/runtime/maptable.h +++ b/runtime/maptable.h @@ -32,7 +32,8 @@ #ifndef _OBJC_PRIVATE_H_ # define OBJC_MAP_AVAILABILITY \ __OSX_DEPRECATED(10.0, 10.1, "NXMapTable is deprecated") \ - __IOS_UNAVAILABLE __TVOS_UNAVAILABLE __WATCHOS_UNAVAILABLE + __IOS_UNAVAILABLE __TVOS_UNAVAILABLE \ + __WATCHOS_UNAVAILABLE __BRIDGEOS_UNAVAILABLE #else # define OBJC_MAP_AVAILABILITY #endif @@ -49,16 +50,21 @@ __BEGIN_DECLS typedef struct _NXMapTable { /* private data structure; may change */ - const struct _NXMapTablePrototype *prototype; + const struct _NXMapTablePrototype * _Nonnull prototype; unsigned count; unsigned nbBucketsMinusOne; - void *buckets; + void * _Nullable buckets; } NXMapTable OBJC_MAP_AVAILABILITY; typedef struct _NXMapTablePrototype { - unsigned (*hash)(NXMapTable *, const void *key); - int (*isEqual)(NXMapTable *, const void *key1, const void *key2); - void (*free)(NXMapTable *, void *key, void *value); + unsigned (* _Nonnull hash)(NXMapTable * _Nonnull, + const void * _Nullable key); + int (* _Nonnull isEqual)(NXMapTable * _Nonnull, + const void * _Nullable key1, + const void * _Nullable key2); + void (* _Nonnull free)(NXMapTable * _Nonnull, + void * _Nullable key, + void * _Nullable value); int style; /* reserved for future expansion; currently 0 */ } NXMapTablePrototype OBJC_MAP_AVAILABILITY; @@ -70,36 +76,59 @@ typedef struct _NXMapTablePrototype { C - isEqual(key1, key2) => key1 == key2 */ -#define NX_MAPNOTAKEY ((void *)(-1)) +#define NX_MAPNOTAKEY ((void * _Nonnull)(-1)) /*************** Functions ***************/ -OBJC_EXPORT NXMapTable *NXCreateMapTableFromZone(NXMapTablePrototype prototype, unsigned capacity, void *z) OBJC_MAP_AVAILABILITY; -OBJC_EXPORT NXMapTable *NXCreateMapTable(NXMapTablePrototype prototype, unsigned capacity) OBJC_MAP_AVAILABILITY; +OBJC_EXPORT NXMapTable * _Nonnull +NXCreateMapTableFromZone(NXMapTablePrototype prototype, + unsigned capacity, void * _Nullable z) + OBJC_MAP_AVAILABILITY; + +OBJC_EXPORT NXMapTable * _Nonnull +NXCreateMapTable(NXMapTablePrototype prototype, unsigned capacity) + OBJC_MAP_AVAILABILITY; /* capacity is only a hint; 0 creates a small table */ -OBJC_EXPORT void NXFreeMapTable(NXMapTable *table) OBJC_MAP_AVAILABILITY; +OBJC_EXPORT void +NXFreeMapTable(NXMapTable * _Nonnull table) + OBJC_MAP_AVAILABILITY; /* call free for each pair, and recovers table */ -OBJC_EXPORT void NXResetMapTable(NXMapTable *table) OBJC_MAP_AVAILABILITY; +OBJC_EXPORT void +NXResetMapTable(NXMapTable * _Nonnull table) + OBJC_MAP_AVAILABILITY; /* free each pair; keep current capacity */ -OBJC_EXPORT BOOL NXCompareMapTables(NXMapTable *table1, NXMapTable *table2) OBJC_MAP_AVAILABILITY; +OBJC_EXPORT BOOL +NXCompareMapTables(NXMapTable * _Nonnull table1, NXMapTable * _Nonnull table2) + OBJC_MAP_AVAILABILITY; /* Returns YES if the two sets are equal (each member of table1 in table2, and table have same size) */ -OBJC_EXPORT unsigned NXCountMapTable(NXMapTable *table) OBJC_MAP_AVAILABILITY; +OBJC_EXPORT unsigned +NXCountMapTable(NXMapTable * _Nonnull table) + OBJC_MAP_AVAILABILITY; /* current number of data in table */ -OBJC_EXPORT void *NXMapMember(NXMapTable *table, const void *key, void **value) OBJC_MAP_AVAILABILITY; +OBJC_EXPORT void * _Nullable +NXMapMember(NXMapTable * _Nonnull table, const void * _Nullable key, + void * _Nullable * _Nonnull value) OBJC_MAP_AVAILABILITY; /* return original table key or NX_MAPNOTAKEY. If key is found, value is set */ -OBJC_EXPORT void *NXMapGet(NXMapTable *table, const void *key) OBJC_MAP_AVAILABILITY; +OBJC_EXPORT void * _Nullable +NXMapGet(NXMapTable * _Nonnull table, const void * _Nullable key) + OBJC_MAP_AVAILABILITY; /* return original corresponding value or NULL. When NULL need be stored as value, NXMapMember can be used to test for presence */ -OBJC_EXPORT void *NXMapInsert(NXMapTable *table, const void *key, const void *value) OBJC_MAP_AVAILABILITY; +OBJC_EXPORT void * _Nullable +NXMapInsert(NXMapTable * _Nonnull table, const void * _Nullable key, + const void * _Nullable value) + OBJC_MAP_AVAILABILITY; /* override preexisting pair; Return previous value or NULL. */ -OBJC_EXPORT void *NXMapRemove(NXMapTable *table, const void *key) OBJC_MAP_AVAILABILITY; +OBJC_EXPORT void * _Nullable +NXMapRemove(NXMapTable * _Nonnull table, const void * _Nullable key) + OBJC_MAP_AVAILABILITY; /* previous value or NULL is returned */ /* Iteration over all elements of a table consists in setting up an iteration state and then to progress until all entries have been visited. An example of use for counting elements in a table is: @@ -115,22 +144,31 @@ OBJC_EXPORT void *NXMapRemove(NXMapTable *table, const void *key) OBJC_MAP_AVAIL typedef struct {int index;} NXMapState OBJC_MAP_AVAILABILITY; /* callers should not rely on actual contents of the struct */ -OBJC_EXPORT NXMapState NXInitMapState(NXMapTable *table) OBJC_MAP_AVAILABILITY; +OBJC_EXPORT NXMapState +NXInitMapState(NXMapTable * _Nonnull table) + OBJC_MAP_AVAILABILITY; -OBJC_EXPORT int NXNextMapState(NXMapTable *table, NXMapState *state, const void **key, const void **value) OBJC_MAP_AVAILABILITY; +OBJC_EXPORT int +NXNextMapState(NXMapTable * _Nonnull table, NXMapState * _Nonnull state, + const void * _Nullable * _Nonnull key, + const void * _Nullable * _Nonnull value) + OBJC_MAP_AVAILABILITY; /* returns 0 when all elements have been visited */ /*************** Conveniences ***************/ -OBJC_EXPORT const NXMapTablePrototype NXPtrValueMapPrototype OBJC_MAP_AVAILABILITY; +OBJC_EXPORT const NXMapTablePrototype NXPtrValueMapPrototype + OBJC_MAP_AVAILABILITY; /* hashing is pointer/integer hashing; isEqual is identity; free is no-op. */ -OBJC_EXPORT const NXMapTablePrototype NXStrValueMapPrototype OBJC_MAP_AVAILABILITY; +OBJC_EXPORT const NXMapTablePrototype NXStrValueMapPrototype + OBJC_MAP_AVAILABILITY; /* hashing is string hashing; isEqual is strcmp; free is no-op. */ -OBJC_EXPORT const NXMapTablePrototype NXObjectMapPrototype OBJC2_UNAVAILABLE; +OBJC_EXPORT const NXMapTablePrototype NXObjectMapPrototype + OBJC2_UNAVAILABLE; /* for objects; uses methods: hash, isEqual:, free, all for key. */ __END_DECLS diff --git a/runtime/message.h b/runtime/message.h index 725e912..a53b430 100644 --- a/runtime/message.h +++ b/runtime/message.h @@ -24,27 +24,23 @@ #ifndef _OBJC_MESSAGE_H #define _OBJC_MESSAGE_H -#pragma GCC system_header - #include #include -#pragma GCC system_header - #ifndef OBJC_SUPER #define OBJC_SUPER /// Specifies the superclass of an instance. struct objc_super { /// Specifies an instance of a class. - __unsafe_unretained id receiver; + __unsafe_unretained _Nonnull id receiver; /// Specifies the particular superclass of the instance to message. #if !defined(__cplusplus) && !__OBJC2__ /* For compatibility with old objc-runtime.h header */ - __unsafe_unretained Class class; + __unsafe_unretained _Nonnull Class class; #else - __unsafe_unretained Class super_class; + __unsafe_unretained _Nonnull Class super_class; #endif /* super_class is the first class to search */ }; @@ -61,10 +57,13 @@ struct objc_super { * before being called. */ #if !OBJC_OLD_DISPATCH_PROTOTYPES -OBJC_EXPORT void objc_msgSend(void /* id self, SEL op, ... */ ) - OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0); -OBJC_EXPORT void objc_msgSendSuper(void /* struct objc_super *super, SEL op, ... */ ) - OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0); +OBJC_EXPORT void +objc_msgSend(void /* id self, SEL op, ... */ ) + OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0); + +OBJC_EXPORT void +objc_msgSendSuper(void /* struct objc_super *super, SEL op, ... */ ) + OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0); #else /** * Sends a message with a simple return value to an instance of a class. @@ -82,8 +81,9 @@ OBJC_EXPORT void objc_msgSendSuper(void /* struct objc_super *super, SEL op, ... * other messages are sent using \c objc_msgSend. Methods that have data structures as return values * are sent using \c objc_msgSendSuper_stret and \c objc_msgSend_stret. */ -OBJC_EXPORT id objc_msgSend(id self, SEL op, ...) - OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0); +OBJC_EXPORT id _Nullable +objc_msgSend(id _Nullable self, SEL _Nonnull op, ...) + OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0); /** * Sends a message with a simple return value to the superclass of an instance of a class. * @@ -98,8 +98,9 @@ OBJC_EXPORT id objc_msgSend(id self, SEL op, ...) * * @see objc_msgSend */ -OBJC_EXPORT id objc_msgSendSuper(struct objc_super *super, SEL op, ...) - OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0); +OBJC_EXPORT id _Nullable +objc_msgSendSuper(struct objc_super * _Nonnull super, SEL _Nonnull op, ...) + OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0); #endif @@ -113,11 +114,14 @@ OBJC_EXPORT id objc_msgSendSuper(struct objc_super *super, SEL op, ...) * before being called. */ #if !OBJC_OLD_DISPATCH_PROTOTYPES -OBJC_EXPORT void objc_msgSend_stret(void /* id self, SEL op, ... */ ) - OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0) +OBJC_EXPORT void +objc_msgSend_stret(void /* id self, SEL op, ... */ ) + OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0) OBJC_ARM64_UNAVAILABLE; -OBJC_EXPORT void objc_msgSendSuper_stret(void /* struct objc_super *super, SEL op, ... */ ) - OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0) + +OBJC_EXPORT void +objc_msgSendSuper_stret(void /* struct objc_super *super, SEL op, ... */ ) + OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0) OBJC_ARM64_UNAVAILABLE; #else /** @@ -125,8 +129,9 @@ OBJC_EXPORT void objc_msgSendSuper_stret(void /* struct objc_super *super, SEL o * * @see objc_msgSend */ -OBJC_EXPORT void objc_msgSend_stret(id self, SEL op, ...) - OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0) +OBJC_EXPORT void +objc_msgSend_stret(id _Nullable self, SEL _Nonnull op, ...) + OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0) OBJC_ARM64_UNAVAILABLE; /** @@ -134,8 +139,10 @@ OBJC_EXPORT void objc_msgSend_stret(id self, SEL op, ...) * * @see objc_msgSendSuper */ -OBJC_EXPORT void objc_msgSendSuper_stret(struct objc_super *super, SEL op, ...) - OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0) +OBJC_EXPORT void +objc_msgSendSuper_stret(struct objc_super * _Nonnull super, + SEL _Nonnull op, ...) + OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0) OBJC_ARM64_UNAVAILABLE; #endif @@ -161,15 +168,19 @@ OBJC_EXPORT void objc_msgSendSuper_stret(struct objc_super *super, SEL op, ...) # if defined(__i386__) -OBJC_EXPORT void objc_msgSend_fpret(void /* id self, SEL op, ... */ ) - OBJC_AVAILABLE(10.4, 2.0, 9.0, 1.0); +OBJC_EXPORT void +objc_msgSend_fpret(void /* id self, SEL op, ... */ ) + OBJC_AVAILABLE(10.4, 2.0, 9.0, 1.0, 2.0); # elif defined(__x86_64__) -OBJC_EXPORT void objc_msgSend_fpret(void /* id self, SEL op, ... */ ) - OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0); -OBJC_EXPORT void objc_msgSend_fp2ret(void /* id self, SEL op, ... */ ) - OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0); +OBJC_EXPORT void +objc_msgSend_fpret(void /* id self, SEL op, ... */ ) + OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0); + +OBJC_EXPORT void +objc_msgSend_fp2ret(void /* id self, SEL op, ... */ ) + OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0); # endif @@ -187,8 +198,9 @@ OBJC_EXPORT void objc_msgSend_fp2ret(void /* id self, SEL op, ... */ ) * you must use \c objc_msgSend_fpret for functions returning non-integral type. For \c float or * \c long \c double return types, cast the function to an appropriate function pointer type first. */ -OBJC_EXPORT double objc_msgSend_fpret(id self, SEL op, ...) - OBJC_AVAILABLE(10.4, 2.0, 9.0, 1.0); +OBJC_EXPORT double +objc_msgSend_fpret(id _Nullable self, SEL _Nonnull op, ...) + OBJC_AVAILABLE(10.4, 2.0, 9.0, 1.0, 2.0); /* Use objc_msgSendSuper() for fp-returning messages to super. */ /* See also objc_msgSendv_fpret() below. */ @@ -199,15 +211,17 @@ OBJC_EXPORT double objc_msgSend_fpret(id self, SEL op, ...) * * @see objc_msgSend */ -OBJC_EXPORT long double objc_msgSend_fpret(id self, SEL op, ...) - OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0); +OBJC_EXPORT long double +objc_msgSend_fpret(id _Nullable self, SEL _Nonnull op, ...) + OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0); # if __STDC_VERSION__ >= 199901L -OBJC_EXPORT _Complex long double objc_msgSend_fp2ret(id self, SEL op, ...) - OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0); +OBJC_EXPORT _Complex long double +objc_msgSend_fp2ret(id _Nullable self, SEL _Nonnull op, ...) + OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0); # else -OBJC_EXPORT void objc_msgSend_fp2ret(id self, SEL op, ...) - OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0); +OBJC_EXPORT void objc_msgSend_fp2ret(id _Nullable self, SEL _Nonnull op, ...) + OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0); # endif /* Use objc_msgSendSuper() for fp-returning messages to super. */ @@ -229,16 +243,22 @@ OBJC_EXPORT void objc_msgSend_fp2ret(id self, SEL op, ...) * before being called. */ #if !OBJC_OLD_DISPATCH_PROTOTYPES -OBJC_EXPORT void method_invoke(void /* id receiver, Method m, ... */ ) - OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0); -OBJC_EXPORT void method_invoke_stret(void /* id receiver, Method m, ... */ ) - OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0) +OBJC_EXPORT void +method_invoke(void /* id receiver, Method m, ... */ ) + OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0); + +OBJC_EXPORT void +method_invoke_stret(void /* id receiver, Method m, ... */ ) + OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0) OBJC_ARM64_UNAVAILABLE; #else -OBJC_EXPORT id method_invoke(id receiver, Method m, ...) - OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0); -OBJC_EXPORT void method_invoke_stret(id receiver, Method m, ...) - OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0) +OBJC_EXPORT id _Nullable +method_invoke(id _Nullable receiver, Method _Nonnull m, ...) + OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0); + +OBJC_EXPORT void +method_invoke_stret(id _Nullable receiver, Method _Nonnull m, ...) + OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0) OBJC_ARM64_UNAVAILABLE; #endif @@ -259,16 +279,22 @@ OBJC_EXPORT void method_invoke_stret(id receiver, Method m, ...) * but may be compared to other IMP values. */ #if !OBJC_OLD_DISPATCH_PROTOTYPES -OBJC_EXPORT void _objc_msgForward(void /* id receiver, SEL sel, ... */ ) - OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0); -OBJC_EXPORT void _objc_msgForward_stret(void /* id receiver, SEL sel, ... */ ) - OBJC_AVAILABLE(10.6, 3.0, 9.0, 1.0) +OBJC_EXPORT void +_objc_msgForward(void /* id receiver, SEL sel, ... */ ) + OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0); + +OBJC_EXPORT void +_objc_msgForward_stret(void /* id receiver, SEL sel, ... */ ) + OBJC_AVAILABLE(10.6, 3.0, 9.0, 1.0, 2.0) OBJC_ARM64_UNAVAILABLE; #else -OBJC_EXPORT id _objc_msgForward(id receiver, SEL sel, ...) - OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0); -OBJC_EXPORT void _objc_msgForward_stret(id receiver, SEL sel, ...) - OBJC_AVAILABLE(10.6, 3.0, 9.0, 1.0) +OBJC_EXPORT id _Nullable +_objc_msgForward(id _Nonnull receiver, SEL _Nonnull sel, ...) + OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0); + +OBJC_EXPORT void +_objc_msgForward_stret(id _Nonnull receiver, SEL _Nonnull sel, ...) + OBJC_AVAILABLE(10.6, 3.0, 9.0, 1.0, 2.0) OBJC_ARM64_UNAVAILABLE; #endif @@ -288,14 +314,25 @@ OBJC_EXPORT void _objc_msgForward_stret(id receiver, SEL sel, ...) typedef void* marg_list; -OBJC_EXPORT id objc_msgSendv(id self, SEL op, size_t arg_size, marg_list arg_frame) OBJC2_UNAVAILABLE; -OBJC_EXPORT void objc_msgSendv_stret(void *stretAddr, id self, SEL op, size_t arg_size, marg_list arg_frame) OBJC2_UNAVAILABLE; +OBJC_EXPORT id _Nullable +objc_msgSendv(id _Nullable self, SEL _Nonnull op, size_t arg_size, + marg_list _Nonnull arg_frame) + OBJC2_UNAVAILABLE; + +OBJC_EXPORT void +objc_msgSendv_stret(void * _Nonnull stretAddr, id _Nullable self, + SEL _Nonnull op, size_t arg_size, + marg_list _Nullable arg_frame) + OBJC2_UNAVAILABLE; /* Note that objc_msgSendv_stret() does not return a structure type, * and should not be cast to do so. This is unlike objc_msgSend_stret() * and objc_msgSendSuper_stret(). */ #if defined(__i386__) -OBJC_EXPORT double objc_msgSendv_fpret(id self, SEL op, unsigned arg_size, marg_list arg_frame) OBJC2_UNAVAILABLE; +OBJC_EXPORT double +objc_msgSendv_fpret(id _Nullable self, SEL _Nonnull op, + unsigned arg_size, marg_list _Nullable arg_frame) + OBJC2_UNAVAILABLE; #endif diff --git a/runtime/objc-abi.h b/runtime/objc-abi.h index 7d9e89e..8d861ef 100644 --- a/runtime/objc-abi.h +++ b/runtime/objc-abi.h @@ -46,8 +46,9 @@ /* Runtime startup. */ // Old static initializer. Used by old crt1.o and old bug workarounds. -OBJC_EXPORT void _objcInit(void) - OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0); +OBJC_EXPORT void +_objcInit(void) + OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0); /* Images */ @@ -122,52 +123,78 @@ typedef struct objc_image_info { /* Properties */ // Read or write an object property. Not all object properties use these. -OBJC_EXPORT id objc_getProperty(id self, SEL _cmd, ptrdiff_t offset, BOOL atomic) - OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0); -OBJC_EXPORT void objc_setProperty(id self, SEL _cmd, ptrdiff_t offset, id newValue, BOOL atomic, signed char shouldCopy) - OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0); +OBJC_EXPORT id _Nullable +objc_getProperty(id _Nullable self, SEL _Nonnull _cmd, + ptrdiff_t offset, BOOL atomic) + OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0); -OBJC_EXPORT void objc_setProperty_atomic(id self, SEL _cmd, id newValue, ptrdiff_t offset) - OBJC_AVAILABLE(10.8, 6.0, 9.0, 1.0); -OBJC_EXPORT void objc_setProperty_nonatomic(id self, SEL _cmd, id newValue, ptrdiff_t offset) - OBJC_AVAILABLE(10.8, 6.0, 9.0, 1.0); -OBJC_EXPORT void objc_setProperty_atomic_copy(id self, SEL _cmd, id newValue, ptrdiff_t offset) - OBJC_AVAILABLE(10.8, 6.0, 9.0, 1.0); -OBJC_EXPORT void objc_setProperty_nonatomic_copy(id self, SEL _cmd, id newValue, ptrdiff_t offset) - OBJC_AVAILABLE(10.8, 6.0, 9.0, 1.0); +OBJC_EXPORT void +objc_setProperty(id _Nullable self, SEL _Nonnull _cmd, ptrdiff_t offset, + id _Nullable newValue, BOOL atomic, signed char shouldCopy) + OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0); + +OBJC_EXPORT void +objc_setProperty_atomic(id _Nullable self, SEL _Nonnull _cmd, + id _Nullable newValue, ptrdiff_t offset) + OBJC_AVAILABLE(10.8, 6.0, 9.0, 1.0, 2.0); + +OBJC_EXPORT void +objc_setProperty_nonatomic(id _Nullable self, SEL _Nonnull _cmd, + id _Nullable newValue, ptrdiff_t offset) + OBJC_AVAILABLE(10.8, 6.0, 9.0, 1.0, 2.0); + +OBJC_EXPORT void +objc_setProperty_atomic_copy(id _Nullable self, SEL _Nonnull _cmd, + id _Nullable newValue, ptrdiff_t offset) + OBJC_AVAILABLE(10.8, 6.0, 9.0, 1.0, 2.0); + +OBJC_EXPORT void +objc_setProperty_nonatomic_copy(id _Nullable self, SEL _Nonnull _cmd, + id _Nullable newValue, ptrdiff_t offset) + OBJC_AVAILABLE(10.8, 6.0, 9.0, 1.0, 2.0); // Read or write a non-object property. Not all uses are C structs, // and not all C struct properties use this. -OBJC_EXPORT void objc_copyStruct(void *dest, const void *src, ptrdiff_t size, BOOL atomic, BOOL hasStrong) - OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0); +OBJC_EXPORT void +objc_copyStruct(void * _Nonnull dest, const void * _Nonnull src, + ptrdiff_t size, BOOL atomic, BOOL hasStrong) + OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0); // Perform a copy of a C++ object using striped locks. Used by non-POD C++ typed atomic properties. -OBJC_EXPORT void objc_copyCppObjectAtomic(void *dest, const void *src, void (*copyHelper) (void *dest, const void *source)) - OBJC_AVAILABLE(10.8, 6.0, 9.0, 1.0); +OBJC_EXPORT void +objc_copyCppObjectAtomic(void * _Nonnull dest, const void * _Nonnull src, + void (* _Nonnull copyHelper) + (void * _Nonnull dest, const void * _Nonnull source)) + OBJC_AVAILABLE(10.8, 6.0, 9.0, 1.0, 2.0); /* Classes. */ #if __OBJC2__ -OBJC_EXPORT IMP _objc_empty_vtable - OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0); +OBJC_EXPORT IMP _Nonnull _objc_empty_vtable + OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0); #endif OBJC_EXPORT struct objc_cache _objc_empty_cache - OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0); + OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0); /* Messages */ #if __OBJC2__ // objc_msgSendSuper2() takes the current search class, not its superclass. -OBJC_EXPORT id objc_msgSendSuper2(struct objc_super *super, SEL op, ...) - OBJC_AVAILABLE(10.6, 2.0, 9.0, 1.0); -OBJC_EXPORT void objc_msgSendSuper2_stret(struct objc_super *super, SEL op,...) - OBJC_AVAILABLE(10.6, 2.0, 9.0, 1.0) +OBJC_EXPORT id _Nullable +objc_msgSendSuper2(struct objc_super * _Nonnull super, SEL _Nonnull op, ...) + OBJC_AVAILABLE(10.6, 2.0, 9.0, 1.0, 2.0); + +OBJC_EXPORT void +objc_msgSendSuper2_stret(struct objc_super * _Nonnull super, + SEL _Nonnull op,...) + OBJC_AVAILABLE(10.6, 2.0, 9.0, 1.0, 2.0) OBJC_ARM64_UNAVAILABLE; // objc_msgSend_noarg() may be faster for methods with no additional arguments. -OBJC_EXPORT id objc_msgSend_noarg(id self, SEL _cmd) - OBJC_AVAILABLE(10.7, 5.0, 9.0, 1.0); +OBJC_EXPORT id _Nullable +objc_msgSend_noarg(id _Nullable self, SEL _Nonnull _cmd) + OBJC_AVAILABLE(10.7, 5.0, 9.0, 1.0, 2.0); #endif #if __OBJC2__ @@ -175,29 +202,42 @@ OBJC_EXPORT id objc_msgSend_noarg(id self, SEL _cmd) // may perform extra sanity checking. // Old objc_msgSendSuper() does not have a debug version; this is OBJC2 only. // *_fixup() do not have debug versions; use non-fixup only for debug mode. -OBJC_EXPORT id objc_msgSend_debug(id self, SEL op, ...) - OBJC_AVAILABLE(10.7, 5.0, 9.0, 1.0); -OBJC_EXPORT id objc_msgSendSuper2_debug(struct objc_super *super, SEL op, ...) - OBJC_AVAILABLE(10.7, 5.0, 9.0, 1.0); -OBJC_EXPORT void objc_msgSend_stret_debug(id self, SEL op, ...) - OBJC_AVAILABLE(10.7, 5.0, 9.0, 1.0) +OBJC_EXPORT id _Nullable +objc_msgSend_debug(id _Nullable self, SEL _Nonnull op, ...) + OBJC_AVAILABLE(10.7, 5.0, 9.0, 1.0, 2.0); + +OBJC_EXPORT id _Nullable +objc_msgSendSuper2_debug(struct objc_super * _Nonnull super, + SEL _Nonnull op, ...) + OBJC_AVAILABLE(10.7, 5.0, 9.0, 1.0, 2.0); + +OBJC_EXPORT void +objc_msgSend_stret_debug(id _Nullable self, SEL _Nonnull op, ...) + OBJC_AVAILABLE(10.7, 5.0, 9.0, 1.0, 2.0) OBJC_ARM64_UNAVAILABLE; -OBJC_EXPORT void objc_msgSendSuper2_stret_debug(struct objc_super *super, SEL op,...) - OBJC_AVAILABLE(10.7, 5.0, 9.0, 1.0) + +OBJC_EXPORT void +objc_msgSendSuper2_stret_debug(struct objc_super * _Nonnull super, + SEL _Nonnull op,...) + OBJC_AVAILABLE(10.7, 5.0, 9.0, 1.0, 2.0) OBJC_ARM64_UNAVAILABLE; # if defined(__i386__) -OBJC_EXPORT double objc_msgSend_fpret_debug(id self, SEL op, ...) - OBJC_AVAILABLE(10.7, 5.0, 9.0, 1.0); +OBJC_EXPORT double +objc_msgSend_fpret_debug(id _Nullable self, SEL _Nonnull op, ...) + OBJC_AVAILABLE(10.7, 5.0, 9.0, 1.0, 2.0); # elif defined(__x86_64__) -OBJC_EXPORT long double objc_msgSend_fpret_debug(id self, SEL op, ...) - OBJC_AVAILABLE(10.7, 5.0, 9.0, 1.0); +OBJC_EXPORT long double +objc_msgSend_fpret_debug(id _Nullable self, SEL _Nonnull op, ...) + OBJC_AVAILABLE(10.7, 5.0, 9.0, 1.0, 2.0); # if __STDC_VERSION__ >= 199901L -OBJC_EXPORT _Complex long double objc_msgSend_fp2ret_debug(id self, SEL op, ...) - OBJC_AVAILABLE(10.7, 5.0, 9.0, 1.0); +OBJC_EXPORT _Complex long double +objc_msgSend_fp2ret_debug(id _Nullable self, SEL _Nonnull op, ...) + OBJC_AVAILABLE(10.7, 5.0, 9.0, 1.0, 2.0); # else -OBJC_EXPORT void objc_msgSend_fp2ret_debug(id self, SEL op, ...) - OBJC_AVAILABLE(10.7, 5.0, 9.0, 1.0); +OBJC_EXPORT void +objc_msgSend_fp2ret_debug(id _Nullable self, SEL _Nonnull op, ...) + OBJC_AVAILABLE(10.7, 5.0, 9.0, 1.0, 2.0); # endif # endif @@ -219,49 +259,78 @@ OBJC_EXPORT void objc_msgSend_fp2ret_debug(id self, SEL op, ...) // - Red zone is not preserved. // See each architecture's implementation for details. -OBJC_EXPORT void objc_msgLookup(void) - OBJC_AVAILABLE(10.12, 10.0, 10.0, 3.0); -OBJC_EXPORT void objc_msgLookupSuper2(void) - OBJC_AVAILABLE(10.12, 10.0, 10.0, 3.0); -OBJC_EXPORT void objc_msgLookup_stret(void) - OBJC_AVAILABLE(10.12, 10.0, 10.0, 3.0) +OBJC_EXPORT void +objc_msgLookup(void) + OBJC_AVAILABLE(10.12, 10.0, 10.0, 3.0, 2.0); + +OBJC_EXPORT void +objc_msgLookupSuper2(void) + OBJC_AVAILABLE(10.12, 10.0, 10.0, 3.0, 2.0); + +OBJC_EXPORT void +objc_msgLookup_stret(void) + OBJC_AVAILABLE(10.12, 10.0, 10.0, 3.0, 2.0) OBJC_ARM64_UNAVAILABLE; -OBJC_EXPORT void objc_msgLookupSuper2_stret(void) - OBJC_AVAILABLE(10.12, 10.0, 10.0, 3.0) + +OBJC_EXPORT void +objc_msgLookupSuper2_stret(void) + OBJC_AVAILABLE(10.12, 10.0, 10.0, 3.0, 2.0) OBJC_ARM64_UNAVAILABLE; # if defined(__i386__) -OBJC_EXPORT void objc_msgLookup_fpret(void) - OBJC_AVAILABLE(10.12, 10.0, 10.0, 3.0); +OBJC_EXPORT void +objc_msgLookup_fpret(void) + OBJC_AVAILABLE(10.12, 10.0, 10.0, 3.0, 2.0); + # elif defined(__x86_64__) -OBJC_EXPORT void objc_msgLookup_fpret(void) - OBJC_AVAILABLE(10.12, 10.0, 10.0, 3.0); -OBJC_EXPORT void objc_msgLookup_fp2ret(void) - OBJC_AVAILABLE(10.12, 10.0, 10.0, 3.0); +OBJC_EXPORT void +objc_msgLookup_fpret(void) + OBJC_AVAILABLE(10.12, 10.0, 10.0, 3.0, 2.0); + +OBJC_EXPORT void +objc_msgLookup_fp2ret(void) + OBJC_AVAILABLE(10.12, 10.0, 10.0, 3.0, 2.0); # endif #endif #if TARGET_OS_OSX && defined(__x86_64__) // objc_msgSend_fixup() is used for vtable-dispatchable call sites. -OBJC_EXPORT void objc_msgSend_fixup(void) +OBJC_EXPORT void +objc_msgSend_fixup(void) __OSX_DEPRECATED(10.5, 10.8, "fixup dispatch is no longer optimized") - __IOS_UNAVAILABLE __TVOS_UNAVAILABLE __WATCHOS_UNAVAILABLE; -OBJC_EXPORT void objc_msgSend_stret_fixup(void) + __IOS_UNAVAILABLE __TVOS_UNAVAILABLE + __WATCHOS_UNAVAILABLE __BRIDGEOS_UNAVAILABLE; + +OBJC_EXPORT void +objc_msgSend_stret_fixup(void) __OSX_DEPRECATED(10.5, 10.8, "fixup dispatch is no longer optimized") - __IOS_UNAVAILABLE __TVOS_UNAVAILABLE __WATCHOS_UNAVAILABLE; -OBJC_EXPORT void objc_msgSendSuper2_fixup(void) + __IOS_UNAVAILABLE __TVOS_UNAVAILABLE + __WATCHOS_UNAVAILABLE __BRIDGEOS_UNAVAILABLE; + +OBJC_EXPORT void +objc_msgSendSuper2_fixup(void) __OSX_DEPRECATED(10.5, 10.8, "fixup dispatch is no longer optimized") - __IOS_UNAVAILABLE __TVOS_UNAVAILABLE __WATCHOS_UNAVAILABLE; -OBJC_EXPORT void objc_msgSendSuper2_stret_fixup(void) + __IOS_UNAVAILABLE __TVOS_UNAVAILABLE + __WATCHOS_UNAVAILABLE __BRIDGEOS_UNAVAILABLE; + +OBJC_EXPORT void +objc_msgSendSuper2_stret_fixup(void) __OSX_DEPRECATED(10.5, 10.8, "fixup dispatch is no longer optimized") - __IOS_UNAVAILABLE __TVOS_UNAVAILABLE __WATCHOS_UNAVAILABLE; -OBJC_EXPORT void objc_msgSend_fpret_fixup(void) + __IOS_UNAVAILABLE __TVOS_UNAVAILABLE + __WATCHOS_UNAVAILABLE __BRIDGEOS_UNAVAILABLE; + +OBJC_EXPORT void +objc_msgSend_fpret_fixup(void) __OSX_DEPRECATED(10.5, 10.8, "fixup dispatch is no longer optimized") - __IOS_UNAVAILABLE __TVOS_UNAVAILABLE __WATCHOS_UNAVAILABLE; -OBJC_EXPORT void objc_msgSend_fp2ret_fixup(void) + __IOS_UNAVAILABLE __TVOS_UNAVAILABLE + __WATCHOS_UNAVAILABLE __BRIDGEOS_UNAVAILABLE; + +OBJC_EXPORT void +objc_msgSend_fp2ret_fixup(void) __OSX_DEPRECATED(10.5, 10.8, "fixup dispatch is no longer optimized") - __IOS_UNAVAILABLE __TVOS_UNAVAILABLE __WATCHOS_UNAVAILABLE; + __IOS_UNAVAILABLE __TVOS_UNAVAILABLE + __WATCHOS_UNAVAILABLE __BRIDGEOS_UNAVAILABLE; #endif /* C++-compatible exception handling. */ @@ -271,12 +340,12 @@ OBJC_EXPORT void objc_msgSend_fp2ret_fixup(void) #if !defined(__cplusplus) // Vtable for C++ exception typeinfo for Objective-C types. -OBJC_EXPORT const void *objc_ehtype_vtable[] - OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0); +OBJC_EXPORT const void * _Nullableobjc_ehtype_vtable[] + OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0); // C++ exception typeinfo for type `id`. OBJC_EXPORT struct objc_typeinfo OBJC_EHTYPE_id - OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0); + OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0); #endif @@ -287,16 +356,17 @@ OBJC_EXPORT int __objc_personality_v0(int version, int actions, uint64_t exceptionClass, - struct _Unwind_Exception *exceptionObject, - struct _Unwind_Context *context) - OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0); + struct _Unwind_Exception * _Nonnull exceptionObject, + struct _Unwind_Context * _Nonnull context) + OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0); #endif /* ARC */ -OBJC_EXPORT id objc_retainBlock(id) - OBJC_AVAILABLE(10.7, 5.0, 9.0, 1.0); +OBJC_EXPORT id _Nullable +objc_retainBlock(id _Nullable) + OBJC_AVAILABLE(10.7, 5.0, 9.0, 1.0, 2.0); /* Non-pointer isa */ @@ -315,7 +385,7 @@ OBJC_EXPORT id objc_retainBlock(id) // Packed-isa version. This one is used directly by Swift code. // (Class)(isa & (uintptr_t)&objc_absolute_packed_isa_class_mask) == class ptr OBJC_EXPORT const struct { char c; } objc_absolute_packed_isa_class_mask - OBJC_AVAILABLE(10.12, 10.0, 10.0, 3.0); + OBJC_AVAILABLE(10.12, 10.0, 10.0, 3.0, 2.0); #elif __ARM_ARCH_7K__ >= 2 # define OBJC_HAVE_NONPOINTER_ISA 1 @@ -329,13 +399,13 @@ OBJC_EXPORT const struct { char c; } objc_absolute_packed_isa_class_mask // cls = (Class)isa; // } OBJC_EXPORT const struct { char c; } objc_absolute_indexed_isa_magic_mask - OBJC_AVAILABLE(10.12, 10.0, 10.0, 3.0); + OBJC_AVAILABLE(10.12, 10.0, 10.0, 3.0, 2.0); OBJC_EXPORT const struct { char c; } objc_absolute_indexed_isa_magic_value - OBJC_AVAILABLE(10.12, 10.0, 10.0, 3.0); + OBJC_AVAILABLE(10.12, 10.0, 10.0, 3.0, 2.0); OBJC_EXPORT const struct { char c; } objc_absolute_indexed_isa_index_mask - OBJC_AVAILABLE(10.12, 10.0, 10.0, 3.0); + OBJC_AVAILABLE(10.12, 10.0, 10.0, 3.0, 2.0); OBJC_EXPORT const struct { char c; } objc_absolute_indexed_isa_index_shift - OBJC_AVAILABLE(10.12, 10.0, 10.0, 3.0); + OBJC_AVAILABLE(10.12, 10.0, 10.0, 3.0, 2.0); #endif diff --git a/runtime/objc-api.h b/runtime/objc-api.h index 42f88e7..fb0e9ea 100644 --- a/runtime/objc-api.h +++ b/runtime/objc-api.h @@ -28,6 +28,7 @@ #include #include #include +#include #ifndef __has_feature # define __has_feature(x) 0 @@ -41,6 +42,27 @@ # define __has_attribute(x) 0 #endif +#if !__has_feature(nullability) +# ifndef _Nullable +# define _Nullable +# endif +# ifndef _Nonnull +# define _Nonnull +# endif +# ifndef _Null_unspecified +# define _Null_unspecified +# endif +#endif + +#ifndef __BRIDGEOS_AVAILABLE +# define __BRIDGEOS_AVAILABLE(v) +#endif +#ifndef __BRIDGEOS_DEPRECATED +# define __BRIDGEOS_DEPRECATED(v1, v2, m) +#endif +#ifndef __BRIDGEOS_UNAVAILABLE +# define __BRIDGEOS_UNAVAILABLE +#endif /* * OBJC_API_VERSION 0 or undef: Tiger and earlier API only @@ -92,9 +114,9 @@ /* OBJC_AVAILABLE: shorthand for all-OS availability */ #if !defined(OBJC_AVAILABLE) -# define OBJC_AVAILABLE(x, i, t, w) \ - __OSX_AVAILABLE(x) __IOS_AVAILABLE(i) \ - __TVOS_AVAILABLE(t) __WATCHOS_AVAILABLE(w) +# define OBJC_AVAILABLE(x, i, t, w, b) \ + __OSX_AVAILABLE(x) __IOS_AVAILABLE(i) __TVOS_AVAILABLE(t) \ + __WATCHOS_AVAILABLE(w) __BRIDGEOS_AVAILABLE(b) #endif @@ -118,7 +140,7 @@ # define OBJC2_UNAVAILABLE \ __OSX_DEPRECATED(10.5, 10.5, "not available in __OBJC2__") \ __IOS_DEPRECATED(2.0, 2.0, "not available in __OBJC2__") \ - __TVOS_UNAVAILABLE __WATCHOS_UNAVAILABLE + __TVOS_UNAVAILABLE __WATCHOS_UNAVAILABLE __BRIDGEOS_UNAVAILABLE # endif #endif @@ -229,4 +251,12 @@ #define OBJC_OPTIONS(_type, _name) _type _name; enum #endif +#if !defined(OBJC_RETURNS_RETAINED) +# if __OBJC__ && __has_attribute(ns_returns_retained) +# define OBJC_RETURNS_RETAINED __attribute__((ns_returns_retained)) +# else +# define OBJC_RETURNS_RETAINED +# endif +#endif + #endif diff --git a/runtime/objc-auto.h b/runtime/objc-auto.h index 3624af5..37da077 100644 --- a/runtime/objc-auto.h +++ b/runtime/objc-auto.h @@ -24,8 +24,6 @@ #ifndef _OBJC_AUTO_H_ #define _OBJC_AUTO_H_ -#pragma GCC system_header - #include #include #include @@ -57,7 +55,7 @@ enum { OBJC_EXHAUSTIVE_COLLECTION = (3 << 0), OBJC_COLLECT_IF_NEEDED = (1 << 3), - OBJC_WAIT_UNTIL_DONE = (1 << 4), + OBJC_WAIT_UNTIL_DONE = (1 << 4) }; enum { @@ -71,65 +69,65 @@ enum { /* Out-of-line declarations */ OBJC_EXPORT void objc_collect(unsigned long options) - __OSX_DEPRECATED(10.6, 10.8, "it does nothing") __IOS_UNAVAILABLE __TVOS_UNAVAILABLE __WATCHOS_UNAVAILABLE; + __OSX_DEPRECATED(10.6, 10.8, "it does nothing") __IOS_UNAVAILABLE __TVOS_UNAVAILABLE __WATCHOS_UNAVAILABLE __BRIDGEOS_UNAVAILABLE; OBJC_EXPORT BOOL objc_collectingEnabled(void) - __OSX_DEPRECATED(10.5, 10.8, "it always returns NO") __IOS_UNAVAILABLE __TVOS_UNAVAILABLE __WATCHOS_UNAVAILABLE; + __OSX_DEPRECATED(10.5, 10.8, "it always returns NO") __IOS_UNAVAILABLE __TVOS_UNAVAILABLE __WATCHOS_UNAVAILABLE __BRIDGEOS_UNAVAILABLE; OBJC_EXPORT malloc_zone_t *objc_collectableZone(void) - __OSX_DEPRECATED(10.7, 10.8, "it always returns nil") __IOS_UNAVAILABLE __TVOS_UNAVAILABLE __WATCHOS_UNAVAILABLE; + __OSX_DEPRECATED(10.7, 10.8, "it always returns nil") __IOS_UNAVAILABLE __TVOS_UNAVAILABLE __WATCHOS_UNAVAILABLE __BRIDGEOS_UNAVAILABLE; OBJC_EXPORT void objc_setCollectionThreshold(size_t threshold) - __OSX_DEPRECATED(10.5, 10.8, "it does nothing") __IOS_UNAVAILABLE __TVOS_UNAVAILABLE __WATCHOS_UNAVAILABLE; + __OSX_DEPRECATED(10.5, 10.8, "it does nothing") __IOS_UNAVAILABLE __TVOS_UNAVAILABLE __WATCHOS_UNAVAILABLE __BRIDGEOS_UNAVAILABLE; OBJC_EXPORT void objc_setCollectionRatio(size_t ratio) - __OSX_DEPRECATED(10.5, 10.8, "it does nothing") __IOS_UNAVAILABLE __TVOS_UNAVAILABLE __WATCHOS_UNAVAILABLE; + __OSX_DEPRECATED(10.5, 10.8, "it does nothing") __IOS_UNAVAILABLE __TVOS_UNAVAILABLE __WATCHOS_UNAVAILABLE __BRIDGEOS_UNAVAILABLE; OBJC_EXPORT BOOL objc_atomicCompareAndSwapPtr(id predicate, id replacement, volatile id *objectLocation) - __OSX_DEPRECATED(10.6, 10.8, "use OSAtomicCompareAndSwapPtr instead") __IOS_UNAVAILABLE __TVOS_UNAVAILABLE __WATCHOS_UNAVAILABLE OBJC_ARC_UNAVAILABLE; + __OSX_DEPRECATED(10.6, 10.8, "use OSAtomicCompareAndSwapPtr instead") __IOS_UNAVAILABLE __TVOS_UNAVAILABLE __WATCHOS_UNAVAILABLE __BRIDGEOS_UNAVAILABLE OBJC_ARC_UNAVAILABLE; OBJC_EXPORT BOOL objc_atomicCompareAndSwapPtrBarrier(id predicate, id replacement, volatile id *objectLocation) - __OSX_DEPRECATED(10.6, 10.8, "use OSAtomicCompareAndSwapPtrBarrier instead") __IOS_UNAVAILABLE __TVOS_UNAVAILABLE __WATCHOS_UNAVAILABLE OBJC_ARC_UNAVAILABLE; + __OSX_DEPRECATED(10.6, 10.8, "use OSAtomicCompareAndSwapPtrBarrier instead") __IOS_UNAVAILABLE __TVOS_UNAVAILABLE __WATCHOS_UNAVAILABLE __BRIDGEOS_UNAVAILABLE OBJC_ARC_UNAVAILABLE; OBJC_EXPORT BOOL objc_atomicCompareAndSwapGlobal(id predicate, id replacement, volatile id *objectLocation) - __OSX_DEPRECATED(10.6, 10.8, "use OSAtomicCompareAndSwapPtr instead") __IOS_UNAVAILABLE __TVOS_UNAVAILABLE __WATCHOS_UNAVAILABLE OBJC_ARC_UNAVAILABLE; + __OSX_DEPRECATED(10.6, 10.8, "use OSAtomicCompareAndSwapPtr instead") __IOS_UNAVAILABLE __TVOS_UNAVAILABLE __WATCHOS_UNAVAILABLE __BRIDGEOS_UNAVAILABLE OBJC_ARC_UNAVAILABLE; OBJC_EXPORT BOOL objc_atomicCompareAndSwapGlobalBarrier(id predicate, id replacement, volatile id *objectLocation) - __OSX_DEPRECATED(10.6, 10.8, "use OSAtomicCompareAndSwapPtrBarrier instead") __IOS_UNAVAILABLE __TVOS_UNAVAILABLE __WATCHOS_UNAVAILABLE OBJC_ARC_UNAVAILABLE; + __OSX_DEPRECATED(10.6, 10.8, "use OSAtomicCompareAndSwapPtrBarrier instead") __IOS_UNAVAILABLE __TVOS_UNAVAILABLE __WATCHOS_UNAVAILABLE __BRIDGEOS_UNAVAILABLE OBJC_ARC_UNAVAILABLE; OBJC_EXPORT BOOL objc_atomicCompareAndSwapInstanceVariable(id predicate, id replacement, volatile id *objectLocation) - __OSX_DEPRECATED(10.6, 10.8, "use OSAtomicCompareAndSwapPtr instead") __IOS_UNAVAILABLE __TVOS_UNAVAILABLE __WATCHOS_UNAVAILABLE OBJC_ARC_UNAVAILABLE; + __OSX_DEPRECATED(10.6, 10.8, "use OSAtomicCompareAndSwapPtr instead") __IOS_UNAVAILABLE __TVOS_UNAVAILABLE __WATCHOS_UNAVAILABLE __BRIDGEOS_UNAVAILABLE OBJC_ARC_UNAVAILABLE; OBJC_EXPORT BOOL objc_atomicCompareAndSwapInstanceVariableBarrier(id predicate, id replacement, volatile id *objectLocation) - __OSX_DEPRECATED(10.6, 10.8, "use OSAtomicCompareAndSwapPtrBarrier instead") __IOS_UNAVAILABLE __TVOS_UNAVAILABLE __WATCHOS_UNAVAILABLE OBJC_ARC_UNAVAILABLE; + __OSX_DEPRECATED(10.6, 10.8, "use OSAtomicCompareAndSwapPtrBarrier instead") __IOS_UNAVAILABLE __TVOS_UNAVAILABLE __WATCHOS_UNAVAILABLE __BRIDGEOS_UNAVAILABLE OBJC_ARC_UNAVAILABLE; OBJC_EXPORT id objc_assign_strongCast(id val, id *dest) - __OSX_DEPRECATED(10.4, 10.8, "use a simple assignment instead") __IOS_UNAVAILABLE __TVOS_UNAVAILABLE __WATCHOS_UNAVAILABLE; + __OSX_DEPRECATED(10.4, 10.8, "use a simple assignment instead") __IOS_UNAVAILABLE __TVOS_UNAVAILABLE __WATCHOS_UNAVAILABLE __BRIDGEOS_UNAVAILABLE; OBJC_EXPORT id objc_assign_global(id val, id *dest) - __OSX_DEPRECATED(10.4, 10.8, "use a simple assignment instead") __IOS_UNAVAILABLE __TVOS_UNAVAILABLE __WATCHOS_UNAVAILABLE; + __OSX_DEPRECATED(10.4, 10.8, "use a simple assignment instead") __IOS_UNAVAILABLE __TVOS_UNAVAILABLE __WATCHOS_UNAVAILABLE __BRIDGEOS_UNAVAILABLE; OBJC_EXPORT id objc_assign_threadlocal(id val, id *dest) - __OSX_DEPRECATED(10.7, 10.8, "use a simple assignment instead") __IOS_UNAVAILABLE __TVOS_UNAVAILABLE __WATCHOS_UNAVAILABLE; + __OSX_DEPRECATED(10.7, 10.8, "use a simple assignment instead") __IOS_UNAVAILABLE __TVOS_UNAVAILABLE __WATCHOS_UNAVAILABLE __BRIDGEOS_UNAVAILABLE; OBJC_EXPORT id objc_assign_ivar(id value, id dest, ptrdiff_t offset) - __OSX_DEPRECATED(10.4, 10.8, "use a simple assignment instead") __IOS_UNAVAILABLE __TVOS_UNAVAILABLE __WATCHOS_UNAVAILABLE; + __OSX_DEPRECATED(10.4, 10.8, "use a simple assignment instead") __IOS_UNAVAILABLE __TVOS_UNAVAILABLE __WATCHOS_UNAVAILABLE __BRIDGEOS_UNAVAILABLE; OBJC_EXPORT void *objc_memmove_collectable(void *dst, const void *src, size_t size) - __OSX_DEPRECATED(10.4, 10.8, "use memmove instead") __IOS_UNAVAILABLE __TVOS_UNAVAILABLE __WATCHOS_UNAVAILABLE; + __OSX_DEPRECATED(10.4, 10.8, "use memmove instead") __IOS_UNAVAILABLE __TVOS_UNAVAILABLE __WATCHOS_UNAVAILABLE __BRIDGEOS_UNAVAILABLE; OBJC_EXPORT id objc_read_weak(id *location) - __OSX_DEPRECATED(10.5, 10.8, "use a simple read instead, or convert to zeroing __weak") __IOS_UNAVAILABLE __TVOS_UNAVAILABLE __WATCHOS_UNAVAILABLE; + __OSX_DEPRECATED(10.5, 10.8, "use a simple read instead, or convert to zeroing __weak") __IOS_UNAVAILABLE __TVOS_UNAVAILABLE __WATCHOS_UNAVAILABLE __BRIDGEOS_UNAVAILABLE; OBJC_EXPORT id objc_assign_weak(id value, id *location) - __OSX_DEPRECATED(10.5, 10.8, "use a simple assignment instead, or convert to zeroing __weak") __IOS_UNAVAILABLE __TVOS_UNAVAILABLE __WATCHOS_UNAVAILABLE; + __OSX_DEPRECATED(10.5, 10.8, "use a simple assignment instead, or convert to zeroing __weak") __IOS_UNAVAILABLE __TVOS_UNAVAILABLE __WATCHOS_UNAVAILABLE __BRIDGEOS_UNAVAILABLE; OBJC_EXPORT void objc_registerThreadWithCollector(void) - __OSX_DEPRECATED(10.6, 10.8, "it does nothing") __IOS_UNAVAILABLE __TVOS_UNAVAILABLE __WATCHOS_UNAVAILABLE; + __OSX_DEPRECATED(10.6, 10.8, "it does nothing") __IOS_UNAVAILABLE __TVOS_UNAVAILABLE __WATCHOS_UNAVAILABLE __BRIDGEOS_UNAVAILABLE; OBJC_EXPORT void objc_unregisterThreadWithCollector(void) - __OSX_DEPRECATED(10.6, 10.8, "it does nothing") __IOS_UNAVAILABLE __TVOS_UNAVAILABLE __WATCHOS_UNAVAILABLE; + __OSX_DEPRECATED(10.6, 10.8, "it does nothing") __IOS_UNAVAILABLE __TVOS_UNAVAILABLE __WATCHOS_UNAVAILABLE __BRIDGEOS_UNAVAILABLE; OBJC_EXPORT void objc_assertRegisteredThreadWithCollector(void) - __OSX_DEPRECATED(10.6, 10.8, "it does nothing") __IOS_UNAVAILABLE __TVOS_UNAVAILABLE __WATCHOS_UNAVAILABLE; + __OSX_DEPRECATED(10.6, 10.8, "it does nothing") __IOS_UNAVAILABLE __TVOS_UNAVAILABLE __WATCHOS_UNAVAILABLE __BRIDGEOS_UNAVAILABLE; OBJC_EXPORT void objc_clear_stack(unsigned long options) - __OSX_DEPRECATED(10.5, 10.8, "it does nothing") __IOS_UNAVAILABLE __TVOS_UNAVAILABLE __WATCHOS_UNAVAILABLE; + __OSX_DEPRECATED(10.5, 10.8, "it does nothing") __IOS_UNAVAILABLE __TVOS_UNAVAILABLE __WATCHOS_UNAVAILABLE __BRIDGEOS_UNAVAILABLE; OBJC_EXPORT BOOL objc_is_finalized(void *ptr) - __OSX_DEPRECATED(10.4, 10.8, "it always returns NO") __IOS_UNAVAILABLE __TVOS_UNAVAILABLE __WATCHOS_UNAVAILABLE; + __OSX_DEPRECATED(10.4, 10.8, "it always returns NO") __IOS_UNAVAILABLE __TVOS_UNAVAILABLE __WATCHOS_UNAVAILABLE __BRIDGEOS_UNAVAILABLE; OBJC_EXPORT void objc_finalizeOnMainThread(Class cls) - __OSX_DEPRECATED(10.5, 10.5, "it does nothing") __IOS_UNAVAILABLE __TVOS_UNAVAILABLE __WATCHOS_UNAVAILABLE; + __OSX_DEPRECATED(10.5, 10.5, "it does nothing") __IOS_UNAVAILABLE __TVOS_UNAVAILABLE __WATCHOS_UNAVAILABLE __BRIDGEOS_UNAVAILABLE; OBJC_EXPORT BOOL objc_collecting_enabled(void) - __OSX_DEPRECATED(10.4, 10.5, "it always returns NO") __IOS_UNAVAILABLE __TVOS_UNAVAILABLE __WATCHOS_UNAVAILABLE; + __OSX_DEPRECATED(10.4, 10.5, "it always returns NO") __IOS_UNAVAILABLE __TVOS_UNAVAILABLE __WATCHOS_UNAVAILABLE __BRIDGEOS_UNAVAILABLE; OBJC_EXPORT void objc_set_collection_threshold(size_t threshold) - __OSX_DEPRECATED(10.4, 10.5, "it does nothing") __IOS_UNAVAILABLE __TVOS_UNAVAILABLE __WATCHOS_UNAVAILABLE; + __OSX_DEPRECATED(10.4, 10.5, "it does nothing") __IOS_UNAVAILABLE __TVOS_UNAVAILABLE __WATCHOS_UNAVAILABLE __BRIDGEOS_UNAVAILABLE; OBJC_EXPORT void objc_set_collection_ratio(size_t ratio) - __OSX_DEPRECATED(10.4, 10.5, "it does nothing") __IOS_UNAVAILABLE __TVOS_UNAVAILABLE __WATCHOS_UNAVAILABLE; + __OSX_DEPRECATED(10.4, 10.5, "it does nothing") __IOS_UNAVAILABLE __TVOS_UNAVAILABLE __WATCHOS_UNAVAILABLE __BRIDGEOS_UNAVAILABLE; OBJC_EXPORT void objc_start_collector_thread(void) - __OSX_DEPRECATED(10.4, 10.5, "it does nothing") __IOS_UNAVAILABLE __TVOS_UNAVAILABLE __WATCHOS_UNAVAILABLE; + __OSX_DEPRECATED(10.4, 10.5, "it does nothing") __IOS_UNAVAILABLE __TVOS_UNAVAILABLE __WATCHOS_UNAVAILABLE __BRIDGEOS_UNAVAILABLE; OBJC_EXPORT void objc_startCollectorThread(void) - __OSX_DEPRECATED(10.5, 10.7, "it does nothing") __IOS_UNAVAILABLE __TVOS_UNAVAILABLE __WATCHOS_UNAVAILABLE; + __OSX_DEPRECATED(10.5, 10.7, "it does nothing") __IOS_UNAVAILABLE __TVOS_UNAVAILABLE __WATCHOS_UNAVAILABLE __BRIDGEOS_UNAVAILABLE; OBJC_EXPORT id objc_allocate_object(Class cls, int extra) - __OSX_DEPRECATED(10.4, 10.4, "use class_createInstance instead") __IOS_UNAVAILABLE __TVOS_UNAVAILABLE __WATCHOS_UNAVAILABLE; + __OSX_DEPRECATED(10.4, 10.4, "use class_createInstance instead") __IOS_UNAVAILABLE __TVOS_UNAVAILABLE __WATCHOS_UNAVAILABLE __BRIDGEOS_UNAVAILABLE; /* !defined(OBJC_NO_GC) */ @@ -199,7 +197,7 @@ static OBJC_INLINE id objc_assign_threadlocal(id val, id *dest) OBJC_GC_DEPRECATED("use a simple assignment instead") static OBJC_INLINE id objc_assign_ivar(id val, id dest, ptrdiff_t offset) - { return (*(id*)((char *)dest+offset) = val); } + { return (*(id*)((intptr_t)(char *)dest+offset) = val); } OBJC_GC_DEPRECATED("use a simple read instead, or convert to zeroing __weak") static OBJC_INLINE id objc_read_weak(id *location) @@ -235,10 +233,10 @@ static OBJC_INLINE void objc_start_collector_thread(void) { } extern id objc_allocate_object(Class cls, int extra) UNAVAILABLE_ATTRIBUTE; #else OBJC_EXPORT id class_createInstance(Class cls, size_t extraBytes) - OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0); + OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0); OBJC_GC_DEPRECATED("use class_createInstance instead") static OBJC_INLINE id objc_allocate_object(Class cls, int extra) - { return class_createInstance(cls, extra); } + { return class_createInstance(cls, (size_t)extra); } #endif OBJC_GC_DEPRECATED("it does nothing") diff --git a/runtime/objc-block-trampolines.mm b/runtime/objc-block-trampolines.mm index ccde02a..6ef5fcf 100644 --- a/runtime/objc-block-trampolines.mm +++ b/runtime/objc-block-trampolines.mm @@ -217,59 +217,40 @@ static inline void _assert_locked() { TrampolineBlockPagePair *headPagePair = headPagePairs[aMode]; - if (headPagePair) { - assert(headPagePair->nextAvailablePage == nil); - } + assert(headPagePair == nil || headPagePair->nextAvailablePage == nil); kern_return_t result; - for (int i = 0; i < 5; i++) { - result = vm_allocate(mach_task_self(), &dataAddress, - PAGE_MAX_SIZE * 2, - TRUE | VM_MAKE_TAG(VM_MEMORY_FOUNDATION)); - if (result != KERN_SUCCESS) { - mach_error("vm_allocate failed", result); - return nil; - } + result = vm_allocate(mach_task_self(), &dataAddress, PAGE_MAX_SIZE * 2, + VM_FLAGS_ANYWHERE | VM_MAKE_TAG(VM_MEMORY_FOUNDATION)); + if (result != KERN_SUCCESS) { + _objc_fatal("vm_allocate trampolines failed (%d)", result); + } - vm_address_t codeAddress = dataAddress + PAGE_MAX_SIZE; - result = vm_deallocate(mach_task_self(), codeAddress, PAGE_MAX_SIZE); - if (result != KERN_SUCCESS) { - mach_error("vm_deallocate failed", result); - return nil; - } + vm_address_t codeAddress = dataAddress + PAGE_MAX_SIZE; - uintptr_t codePage; - switch(aMode) { - case ReturnValueInRegisterArgumentMode: - codePage = a1a2_tramphead(); - break; + uintptr_t codePage; + switch(aMode) { + case ReturnValueInRegisterArgumentMode: + codePage = a1a2_tramphead(); + break; #if SUPPORT_STRET - case ReturnValueOnStackArgumentMode: - codePage = a2a3_tramphead(); - break; + case ReturnValueOnStackArgumentMode: + codePage = a2a3_tramphead(); + break; #endif - default: - _objc_fatal("unknown return mode %d", (int)aMode); - break; - } - vm_prot_t currentProtection, maxProtection; - result = vm_remap(mach_task_self(), &codeAddress, PAGE_MAX_SIZE, - 0, FALSE, mach_task_self(), codePage, TRUE, - ¤tProtection, &maxProtection, VM_INHERIT_SHARE); - if (result != KERN_SUCCESS) { - result = vm_deallocate(mach_task_self(), - dataAddress, PAGE_MAX_SIZE); - if (result != KERN_SUCCESS) { - mach_error("vm_deallocate for retry failed.", result); - return nil; - } - } else { - break; - } + default: + _objc_fatal("unknown return mode %d", (int)aMode); + break; } + vm_prot_t currentProtection, maxProtection; + result = vm_remap(mach_task_self(), &codeAddress, PAGE_MAX_SIZE, + 0, VM_FLAGS_FIXED | VM_FLAGS_OVERWRITE, + mach_task_self(), codePage, TRUE, + ¤tProtection, &maxProtection, VM_INHERIT_SHARE); if (result != KERN_SUCCESS) { - return nil; + // vm_deallocate(mach_task_self(), dataAddress, PAGE_MAX_SIZE * 2); + _objc_fatal("vm_remap trampolines failed (%d)", result); } TrampolineBlockPagePair *pagePair = (TrampolineBlockPagePair *) dataAddress; @@ -279,9 +260,9 @@ static inline void _assert_locked() { if (headPagePair) { TrampolineBlockPagePair *lastPagePair = headPagePair; - while(lastPagePair->nextPagePair) + while(lastPagePair->nextPagePair) { lastPagePair = lastPagePair->nextPagePair; - + } lastPagePair->nextPagePair = pagePair; headPagePairs[aMode]->nextAvailablePage = pagePair; } else { diff --git a/runtime/objc-env.h b/runtime/objc-env.h index 7eb0afa..5ff3382 100644 --- a/runtime/objc-env.h +++ b/runtime/objc-env.h @@ -40,3 +40,4 @@ OPTION( DisableVtables, OBJC_DISABLE_VTABLES, "disable vtab OPTION( DisablePreopt, OBJC_DISABLE_PREOPTIMIZATION, "disable preoptimization courtesy of dyld shared cache") OPTION( DisableTaggedPointers, OBJC_DISABLE_TAGGED_POINTERS, "disable tagged pointer optimization of NSNumber et al.") OPTION( DisableNonpointerIsa, OBJC_DISABLE_NONPOINTER_ISA, "disable non-pointer isa fields") +OPTION( DisableInitializeForkSafety, OBJC_DISABLE_INITIALIZE_FORK_SAFETY, "disable safety checks for +initialize after fork") diff --git a/runtime/objc-exception.h b/runtime/objc-exception.h index c67b92a..d6cbb7e 100644 --- a/runtime/objc-exception.h +++ b/runtime/objc-exception.h @@ -31,51 +31,71 @@ // compiler reserves a setjmp buffer + 4 words as localExceptionData -OBJC_EXPORT void objc_exception_throw(id exception) +OBJC_EXPORT void +objc_exception_throw(id _Nonnull exception) __OSX_AVAILABLE(10.3) - __IOS_UNAVAILABLE __TVOS_UNAVAILABLE __WATCHOS_UNAVAILABLE; -OBJC_EXPORT void objc_exception_try_enter(void *localExceptionData) + __IOS_UNAVAILABLE __TVOS_UNAVAILABLE + __WATCHOS_UNAVAILABLE __BRIDGEOS_UNAVAILABLE; + +OBJC_EXPORT void +objc_exception_try_enter(void * _Nonnull localExceptionData) __OSX_AVAILABLE(10.3) - __IOS_UNAVAILABLE __TVOS_UNAVAILABLE __WATCHOS_UNAVAILABLE; -OBJC_EXPORT void objc_exception_try_exit(void *localExceptionData) + __IOS_UNAVAILABLE __TVOS_UNAVAILABLE + __WATCHOS_UNAVAILABLE __BRIDGEOS_UNAVAILABLE; + +OBJC_EXPORT void +objc_exception_try_exit(void * _Nonnull localExceptionData) __OSX_AVAILABLE(10.3) - __IOS_UNAVAILABLE __TVOS_UNAVAILABLE __WATCHOS_UNAVAILABLE; -OBJC_EXPORT id objc_exception_extract(void *localExceptionData) + __IOS_UNAVAILABLE __TVOS_UNAVAILABLE + __WATCHOS_UNAVAILABLE __BRIDGEOS_UNAVAILABLE; + +OBJC_EXPORT id _Nonnull +objc_exception_extract(void * _Nonnull localExceptionData) __OSX_AVAILABLE(10.3) - __IOS_UNAVAILABLE __TVOS_UNAVAILABLE __WATCHOS_UNAVAILABLE; -OBJC_EXPORT int objc_exception_match(Class exceptionClass, id exception) + __IOS_UNAVAILABLE __TVOS_UNAVAILABLE + __WATCHOS_UNAVAILABLE __BRIDGEOS_UNAVAILABLE; + +OBJC_EXPORT int objc_exception_match(Class _Nonnull exceptionClass, + id _Nonnull exception) __OSX_AVAILABLE(10.3) - __IOS_UNAVAILABLE __TVOS_UNAVAILABLE __WATCHOS_UNAVAILABLE; + __IOS_UNAVAILABLE __TVOS_UNAVAILABLE + __WATCHOS_UNAVAILABLE __BRIDGEOS_UNAVAILABLE; typedef struct { int version; - void (*throw_exc)(id); // version 0 - void (*try_enter)(void *); // version 0 - void (*try_exit)(void *); // version 0 - id (*extract)(void *); // version 0 - int (*match)(Class, id); // version 0 + void (* _Nonnull throw_exc)(id _Nonnull); // version 0 + void (* _Nonnull try_enter)(void * _Nonnull); // version 0 + void (* _Nonnull try_exit)(void * _Nonnull); // version 0 + id _Nonnull (* _Nonnull extract)(void * _Nonnull); // version 0 + int (* _Nonnull match)(Class _Nonnull, id _Nonnull); // version 0 } objc_exception_functions_t; // get table; version tells how many -OBJC_EXPORT void objc_exception_get_functions(objc_exception_functions_t *table) +OBJC_EXPORT void +objc_exception_get_functions(objc_exception_functions_t * _Nullable table) __OSX_AVAILABLE(10.3) - __IOS_UNAVAILABLE __TVOS_UNAVAILABLE __WATCHOS_UNAVAILABLE; + __IOS_UNAVAILABLE __TVOS_UNAVAILABLE + __WATCHOS_UNAVAILABLE __BRIDGEOS_UNAVAILABLE; // set table -OBJC_EXPORT void objc_exception_set_functions(objc_exception_functions_t *table) +OBJC_EXPORT void +objc_exception_set_functions(objc_exception_functions_t * _Nullable table) __OSX_AVAILABLE(10.3) - __IOS_UNAVAILABLE __TVOS_UNAVAILABLE __WATCHOS_UNAVAILABLE; + __IOS_UNAVAILABLE __TVOS_UNAVAILABLE + __WATCHOS_UNAVAILABLE __BRIDGEOS_UNAVAILABLE; // !__OBJC2__ #else // __OBJC2__ -typedef id (*objc_exception_preprocessor)(id exception); -typedef int (*objc_exception_matcher)(Class catch_type, id exception); -typedef void (*objc_uncaught_exception_handler)(id exception); -typedef void (*objc_exception_handler)(id unused, void *context); +typedef id _Nonnull (*objc_exception_preprocessor)(id _Nonnull exception); +typedef int (*objc_exception_matcher)(Class _Nonnull catch_type, + id _Nonnull exception); +typedef void (*objc_uncaught_exception_handler)(id _Null_unspecified /* _Nonnull */ exception); +typedef void (*objc_exception_handler)(id _Nullable unused, + void * _Nullable context); /** * Throw a runtime exception. This function is inserted by the compiler @@ -83,31 +103,51 @@ typedef void (*objc_exception_handler)(id unused, void *context); * * @param exception The exception to be thrown. */ -OBJC_EXPORT void objc_exception_throw(id exception) - OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0); -OBJC_EXPORT void objc_exception_rethrow(void) - OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0); -OBJC_EXPORT id objc_begin_catch(void *exc_buf) - OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0); -OBJC_EXPORT void objc_end_catch(void) - OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0); -OBJC_EXPORT void objc_terminate(void) - OBJC_AVAILABLE(10.8, 6.0, 9.0, 1.0); - -OBJC_EXPORT objc_exception_preprocessor objc_setExceptionPreprocessor(objc_exception_preprocessor fn) - OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0); -OBJC_EXPORT objc_exception_matcher objc_setExceptionMatcher(objc_exception_matcher fn) - OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0); -OBJC_EXPORT objc_uncaught_exception_handler objc_setUncaughtExceptionHandler(objc_uncaught_exception_handler fn) - OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0); +OBJC_EXPORT void +objc_exception_throw(id _Nonnull exception) + OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0); + +OBJC_EXPORT void +objc_exception_rethrow(void) + OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0); + +OBJC_EXPORT id _Nonnull +objc_begin_catch(void * _Nonnull exc_buf) + OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0); + +OBJC_EXPORT void +objc_end_catch(void) + OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0); + +OBJC_EXPORT void +objc_terminate(void) + OBJC_AVAILABLE(10.8, 6.0, 9.0, 1.0, 2.0); + +OBJC_EXPORT objc_exception_preprocessor _Nonnull +objc_setExceptionPreprocessor(objc_exception_preprocessor _Nonnull fn) + OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0); + +OBJC_EXPORT objc_exception_matcher _Nonnull +objc_setExceptionMatcher(objc_exception_matcher _Nonnull fn) + OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0); + +OBJC_EXPORT objc_uncaught_exception_handler _Nonnull +objc_setUncaughtExceptionHandler(objc_uncaught_exception_handler _Nonnull fn) + OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0); // Not for iOS. -OBJC_EXPORT uintptr_t objc_addExceptionHandler(objc_exception_handler fn, void *context) +OBJC_EXPORT uintptr_t +objc_addExceptionHandler(objc_exception_handler _Nonnull fn, + void * _Nullable context) __OSX_AVAILABLE(10.5) - __IOS_UNAVAILABLE __TVOS_UNAVAILABLE __WATCHOS_UNAVAILABLE; -OBJC_EXPORT void objc_removeExceptionHandler(uintptr_t token) + __IOS_UNAVAILABLE __TVOS_UNAVAILABLE + __WATCHOS_UNAVAILABLE __BRIDGEOS_UNAVAILABLE; + +OBJC_EXPORT void +objc_removeExceptionHandler(uintptr_t token) __OSX_AVAILABLE(10.5) - __IOS_UNAVAILABLE __TVOS_UNAVAILABLE __WATCHOS_UNAVAILABLE; + __IOS_UNAVAILABLE __TVOS_UNAVAILABLE + __WATCHOS_UNAVAILABLE __BRIDGEOS_UNAVAILABLE; // __OBJC2__ #endif diff --git a/runtime/objc-gdb.h b/runtime/objc-gdb.h index 0c01590..9177775 100644 --- a/runtime/objc-gdb.h +++ b/runtime/objc-gdb.h @@ -47,16 +47,17 @@ __BEGIN_DECLS **********************************************************************/ // Return cls if it's a valid class, or crash. -OBJC_EXPORT Class gdb_class_getClass(Class cls) +OBJC_EXPORT Class _Nonnull +gdb_class_getClass(Class _Nonnull cls) #if __OBJC2__ - OBJC_AVAILABLE(10.6, 3.1, 9.0, 1.0); + OBJC_AVAILABLE(10.6, 3.1, 9.0, 1.0, 2.0); #else - OBJC_AVAILABLE(10.7, 3.1, 9.0, 1.0); + OBJC_AVAILABLE(10.7, 3.1, 9.0, 1.0, 2.0); #endif // Same as gdb_class_getClass(object_getClass(cls)). -OBJC_EXPORT Class gdb_object_getClass(id obj) - OBJC_AVAILABLE(10.7, 4.3, 9.0, 1.0); +OBJC_EXPORT Class _Nonnull gdb_object_getClass(id _Nullable obj) + OBJC_AVAILABLE(10.7, 4.3, 9.0, 1.0, 2.0); /*********************************************************************** @@ -66,15 +67,16 @@ OBJC_EXPORT Class gdb_object_getClass(id obj) #if __OBJC2__ // Maps class name to Class, for in-use classes only. NXStrValueMapPrototype. -OBJC_EXPORT NXMapTable *gdb_objc_realized_classes - OBJC_AVAILABLE(10.6, 3.1, 9.0, 1.0); +OBJC_EXPORT NXMapTable * _Nullable gdb_objc_realized_classes + OBJC_AVAILABLE(10.6, 3.1, 9.0, 1.0, 2.0); #else // Hashes Classes, for all known classes. Custom prototype. -OBJC_EXPORT NXHashTable *_objc_debug_class_hash +OBJC_EXPORT NXHashTable * _Nullable _objc_debug_class_hash __OSX_AVAILABLE(10.2) - __IOS_UNAVAILABLE __TVOS_UNAVAILABLE __WATCHOS_UNAVAILABLE; + __IOS_UNAVAILABLE __TVOS_UNAVAILABLE + __WATCHOS_UNAVAILABLE __BRIDGEOS_UNAVAILABLE; #endif @@ -88,14 +90,14 @@ OBJC_EXPORT NXHashTable *_objc_debug_class_hash // Extract isa pointer from an isa field. // (Class)(isa & mask) == class pointer OBJC_EXPORT const uintptr_t objc_debug_isa_class_mask - OBJC_AVAILABLE(10.10, 7.0, 9.0, 1.0); + OBJC_AVAILABLE(10.10, 7.0, 9.0, 1.0, 2.0); // Extract magic cookie from an isa field. // (isa & magic_mask) == magic_value OBJC_EXPORT const uintptr_t objc_debug_isa_magic_mask - OBJC_AVAILABLE(10.10, 7.0, 9.0, 1.0); + OBJC_AVAILABLE(10.10, 7.0, 9.0, 1.0, 2.0); OBJC_EXPORT const uintptr_t objc_debug_isa_magic_value - OBJC_AVAILABLE(10.10, 7.0, 9.0, 1.0); + OBJC_AVAILABLE(10.10, 7.0, 9.0, 1.0, 2.0); // Use indexed ISAs for targets which store index of the class in the ISA. // This index can be used to index the array of classes. @@ -109,7 +111,7 @@ OBJC_EXPORT const uintptr_t objc_debug_indexed_isa_index_shift; // And then we can use that index to get the class from this array. Note // the size is provided so that clients can ensure the index they get is in // bounds and not read off the end of the array. -OBJC_EXPORT Class objc_indexed_classes[]; +OBJC_EXPORT Class _Nullable objc_indexed_classes[]; // When we don't have enough bits to store a class*, we can instead store an // index in to this array. Classes are added here when they are realized. @@ -121,6 +123,20 @@ OBJC_EXPORT uintptr_t objc_indexed_classes_count; #endif +/*********************************************************************** +* Class structure decoding +**********************************************************************/ +#if __OBJC2__ + +// Mask for the pointer from class struct to class rw data. +// Other bits may be used for flags. +// Use 0x00007ffffffffff8UL or 0xfffffffcUL when this variable is unavailable. +OBJC_EXPORT const uintptr_t objc_debug_class_rw_data_mask + OBJC_AVAILABLE(10.13, 11.0, 11.0, 4.0, 2.0); + +#endif + + /*********************************************************************** * Tagged pointer decoding **********************************************************************/ @@ -130,24 +146,24 @@ OBJC_EXPORT uintptr_t objc_indexed_classes_count; // if (obj & mask) obj is a tagged pointer object OBJC_EXPORT uintptr_t objc_debug_taggedpointer_mask - OBJC_AVAILABLE(10.9, 7.0, 9.0, 1.0); + OBJC_AVAILABLE(10.9, 7.0, 9.0, 1.0, 2.0); // tag_slot = (obj >> slot_shift) & slot_mask OBJC_EXPORT unsigned int objc_debug_taggedpointer_slot_shift - OBJC_AVAILABLE(10.9, 7.0, 9.0, 1.0); + OBJC_AVAILABLE(10.9, 7.0, 9.0, 1.0, 2.0); OBJC_EXPORT uintptr_t objc_debug_taggedpointer_slot_mask - OBJC_AVAILABLE(10.9, 7.0, 9.0, 1.0); + OBJC_AVAILABLE(10.9, 7.0, 9.0, 1.0, 2.0); // class = classes[tag_slot] -OBJC_EXPORT Class objc_debug_taggedpointer_classes[] - OBJC_AVAILABLE(10.9, 7.0, 9.0, 1.0); +OBJC_EXPORT Class _Nullable objc_debug_taggedpointer_classes[] + OBJC_AVAILABLE(10.9, 7.0, 9.0, 1.0, 2.0); // payload = (obj << payload_lshift) >> payload_rshift // Payload signedness is determined by the signedness of the right-shift. OBJC_EXPORT unsigned int objc_debug_taggedpointer_payload_lshift - OBJC_AVAILABLE(10.9, 7.0, 9.0, 1.0); + OBJC_AVAILABLE(10.9, 7.0, 9.0, 1.0, 2.0); OBJC_EXPORT unsigned int objc_debug_taggedpointer_payload_rshift - OBJC_AVAILABLE(10.9, 7.0, 9.0, 1.0); + OBJC_AVAILABLE(10.9, 7.0, 9.0, 1.0, 2.0); // Extended tagged pointers (255 classes, 52-bit payload). @@ -159,24 +175,24 @@ OBJC_EXPORT unsigned int objc_debug_taggedpointer_payload_rshift // if (ext_mask != 0 && (obj & ext_mask) == ext_mask) // obj is a ext tagged pointer object OBJC_EXPORT uintptr_t objc_debug_taggedpointer_ext_mask - OBJC_AVAILABLE(10.12, 10.0, 10.0, 3.0); + OBJC_AVAILABLE(10.12, 10.0, 10.0, 3.0, 2.0); // ext_tag_slot = (obj >> ext_slot_shift) & ext_slot_mask OBJC_EXPORT unsigned int objc_debug_taggedpointer_ext_slot_shift - OBJC_AVAILABLE(10.12, 10.0, 10.0, 3.0); + OBJC_AVAILABLE(10.12, 10.0, 10.0, 3.0, 2.0); OBJC_EXPORT uintptr_t objc_debug_taggedpointer_ext_slot_mask - OBJC_AVAILABLE(10.12, 10.0, 10.0, 3.0); + OBJC_AVAILABLE(10.12, 10.0, 10.0, 3.0, 2.0); // class = ext_classes[ext_tag_slot] -OBJC_EXPORT Class objc_debug_taggedpointer_ext_classes[] - OBJC_AVAILABLE(10.12, 10.0, 10.0, 3.0); +OBJC_EXPORT Class _Nullable objc_debug_taggedpointer_ext_classes[] + OBJC_AVAILABLE(10.12, 10.0, 10.0, 3.0, 2.0); // payload = (obj << ext_payload_lshift) >> ext_payload_rshift // Payload signedness is determined by the signedness of the right-shift. OBJC_EXPORT unsigned int objc_debug_taggedpointer_ext_payload_lshift - OBJC_AVAILABLE(10.12, 10.0, 10.0, 3.0); + OBJC_AVAILABLE(10.12, 10.0, 10.0, 3.0, 2.0); OBJC_EXPORT unsigned int objc_debug_taggedpointer_ext_payload_rshift - OBJC_AVAILABLE(10.12, 10.0, 10.0, 3.0); + OBJC_AVAILABLE(10.12, 10.0, 10.0, 3.0, 2.0); #endif @@ -211,7 +227,7 @@ struct objc_messenger_breakpoint { OBJC_EXPORT struct objc_messenger_breakpoint gdb_objc_messenger_breakpoints[] - OBJC_AVAILABLE(10.9, 7.0, 9.0, 1.0); + OBJC_AVAILABLE(10.9, 7.0, 9.0, 1.0, 2.0); #endif diff --git a/runtime/objc-initialize.mm b/runtime/objc-initialize.mm index 0f410b6..80491bb 100644 --- a/runtime/objc-initialize.mm +++ b/runtime/objc-initialize.mm @@ -280,8 +280,8 @@ static void _finishInitializing(Class cls, Class supercls) assert(!supercls || supercls->isInitialized()); if (PrintInitializing) { - _objc_inform("INITIALIZE: %s is fully +initialized", - cls->nameForLogging()); + _objc_inform("INITIALIZE: thread %p: %s is fully +initialized", + pthread_self(), cls->nameForLogging()); } // mark this class as fully +initialized @@ -323,8 +323,10 @@ static void _finishInitializingAfter(Class cls, Class supercls) classInitLock.assertLocked(); if (PrintInitializing) { - _objc_inform("INITIALIZE: %s waiting for superclass +[%s initialize]", - cls->nameForLogging(), supercls->nameForLogging()); + _objc_inform("INITIALIZE: thread %p: class %s will be marked as fully " + "+initialized after superclass +[%s initialize] completes", + pthread_self(), cls->nameForLogging(), + supercls->nameForLogging()); } if (!pendingInitializeMap) { @@ -352,6 +354,11 @@ void callInitialize(Class cls) void waitForInitializeToComplete(Class cls) { + if (PrintInitializing) { + _objc_inform("INITIALIZE: thread %p: blocking until +[%s initialize] " + "completes", pthread_self(), cls->nameForLogging()); + } + monitor_locker_t lock(classInitLock); while (!cls->isInitialized()) { classInitLock.wait(); @@ -367,6 +374,109 @@ void callInitialize(Class cls) } +/*********************************************************************** +* classHasTrivialInitialize +* Returns true if the class has no +initialize implementation or +* has a +initialize implementation that looks empty. +* Any root class +initialize implemetation is assumed to be trivial. +**********************************************************************/ +static bool classHasTrivialInitialize(Class cls) +{ + if (cls->isRootClass() || cls->isRootMetaclass()) return true; + + Class rootCls = cls->ISA()->ISA()->superclass; + + IMP rootImp = lookUpImpOrNil(rootCls->ISA(), SEL_initialize, rootCls, + NO/*initialize*/, YES/*cache*/, NO/*resolver*/); + IMP imp = lookUpImpOrNil(cls->ISA(), SEL_initialize, cls, + NO/*initialize*/, YES/*cache*/, NO/*resolver*/); + return (imp == nil || imp == (IMP)&objc_noop_imp || imp == rootImp); +} + + +/*********************************************************************** +* lockAndFinishInitializing +* Mark a class as finished initializing and notify waiters, or queue for later. +* If the superclass is also done initializing, then update +* the info bits and notify waiting threads. +* If not, update them later. (This can happen if this +initialize +* was itself triggered from inside a superclass +initialize.) +**********************************************************************/ +static void lockAndFinishInitializing(Class cls, Class supercls) +{ + monitor_locker_t lock(classInitLock); + if (!supercls || supercls->isInitialized()) { + _finishInitializing(cls, supercls); + } else { + _finishInitializingAfter(cls, supercls); + } +} + + +/*********************************************************************** +* performForkChildInitialize +* +initialize after fork() is problematic. It's possible for the +* fork child process to call some +initialize that would deadlock waiting +* for another +initialize in the parent process. +* We wouldn't know how much progress it made therein, so we can't +* act as if +initialize completed nor can we restart +initialize +* from scratch. +* +* Instead we proceed introspectively. If the class has some +* +initialize implementation, we halt. If the class has no +* +initialize implementation of its own, we continue. Root +* class +initialize is assumed to be empty if it exists. +* +* We apply this rule even if the child's +initialize does not appear +* to be blocked by anything. This prevents races wherein the +initialize +* deadlock only rarely hits. Instead we disallow it even when we "won" +* the race. +* +* Exception: processes that are single-threaded when fork() is called +* have no restrictions on +initialize in the child. Examples: sshd and httpd. +* +* Classes that wish to implement +initialize and be callable after +* fork() must use an atfork() handler to provoke +initialize in fork prepare. +**********************************************************************/ + +// Called before halting when some +initialize +// method can't be called after fork(). +BREAKPOINT_FUNCTION( + void objc_initializeAfterForkError(Class cls) +); + +void performForkChildInitialize(Class cls, Class supercls) +{ + if (classHasTrivialInitialize(cls)) { + if (PrintInitializing) { + _objc_inform("INITIALIZE: thread %p: skipping trivial +[%s " + "initialize] in fork() child process", + pthread_self(), cls->nameForLogging()); + } + lockAndFinishInitializing(cls, supercls); + } + else { + if (PrintInitializing) { + _objc_inform("INITIALIZE: thread %p: refusing to call +[%s " + "initialize] in fork() child process because " + "it may have been in progress when fork() was called", + pthread_self(), cls->nameForLogging()); + } + _objc_inform_now_and_on_crash + ("+[%s initialize] may have been in progress in another thread " + "when fork() was called.", + cls->nameForLogging()); + objc_initializeAfterForkError(cls); + _objc_fatal + ("+[%s initialize] may have been in progress in another thread " + "when fork() was called. We cannot safely call it or " + "ignore it in the fork() child process. Crashing instead. " + "Set a breakpoint on objc_initializeAfterForkError to debug.", + cls->nameForLogging()); + } +} + + /*********************************************************************** * class_initialize. Send the '+initialize' message on demand to any * uninitialized class. Force initialization of superclasses first. @@ -399,44 +509,52 @@ void _class_initialize(Class cls) // Record that we're initializing this class so we can message it. _setThisThreadIsInitializingClass(cls); + + if (MultithreadedForkChild) { + // LOL JK we don't really call +initialize methods after fork(). + performForkChildInitialize(cls, supercls); + return; + } // Send the +initialize message. // Note that +initialize is sent to the superclass (again) if // this class doesn't implement +initialize. 2157218 if (PrintInitializing) { - _objc_inform("INITIALIZE: calling +[%s initialize]", - cls->nameForLogging()); + _objc_inform("INITIALIZE: thread %p: calling +[%s initialize]", + pthread_self(), cls->nameForLogging()); } // Exceptions: A +initialize call that throws an exception // is deemed to be a complete and successful +initialize. - @try { + // + // Only __OBJC2__ adds these handlers. !__OBJC2__ has a + // bootstrapping problem of this versus CF's call to + // objc_exception_set_functions(). +#if __OBJC2__ + @try +#endif + { callInitialize(cls); if (PrintInitializing) { - _objc_inform("INITIALIZE: finished +[%s initialize]", - cls->nameForLogging()); + _objc_inform("INITIALIZE: thread %p: finished +[%s initialize]", + pthread_self(), cls->nameForLogging()); } } +#if __OBJC2__ @catch (...) { if (PrintInitializing) { - _objc_inform("INITIALIZE: +[%s initialize] threw an exception", - cls->nameForLogging()); + _objc_inform("INITIALIZE: thread %p: +[%s initialize] " + "threw an exception", + pthread_self(), cls->nameForLogging()); } @throw; } - @finally { - // Done initializing. - // If the superclass is also done initializing, then update - // the info bits and notify waiting threads. - // If not, update them later. (This can happen if this +initialize - // was itself triggered from inside a superclass +initialize.) - monitor_locker_t lock(classInitLock); - if (!supercls || supercls->isInitialized()) { - _finishInitializing(cls, supercls); - } else { - _finishInitializingAfter(cls, supercls); - } + @finally +#endif + { + // Done initializing. + lockAndFinishInitializing(cls, supercls); } return; } @@ -450,9 +568,14 @@ void _class_initialize(Class cls) // before blocking. if (_thisThreadIsInitializingClass(cls)) { return; - } else { + } else if (!MultithreadedForkChild) { waitForInitializeToComplete(cls); return; + } else { + // We're on the child side of fork(), facing a class that + // was initializing by some other thread when fork() was called. + _setThisThreadIsInitializingClass(cls); + performForkChildInitialize(cls, supercls); } } diff --git a/runtime/objc-internal.h b/runtime/objc-internal.h index 6a28c59..fbd8820 100644 --- a/runtime/objc-internal.h +++ b/runtime/objc-internal.h @@ -59,8 +59,10 @@ __BEGIN_DECLS // Returns nil if a class with the same name already exists. // Returns nil if the superclass is under construction. // Call objc_registerClassPair() when you are done. -OBJC_EXPORT Class objc_initializeClassPair(Class superclass, const char *name, Class cls, Class metacls) - OBJC_AVAILABLE(10.6, 3.0, 9.0, 1.0); +OBJC_EXPORT Class _Nullable +objc_initializeClassPair(Class _Nullable superclass, const char * _Nonnull name, + Class _Nonnull cls, Class _Nonnull metacls) + OBJC_AVAILABLE(10.6, 3.0, 9.0, 1.0, 2.0); // Class and metaclass construction from a compiler-generated memory image. // cls and cls->isa must each be OBJC_MAX_CLASS_SIZE bytes. @@ -72,90 +74,122 @@ OBJC_EXPORT Class objc_initializeClassPair(Class superclass, const char *name, C // Do not call objc_registerClassPair(). #if __OBJC2__ struct objc_image_info; -OBJC_EXPORT Class objc_readClassPair(Class cls, - const struct objc_image_info *info) - OBJC_AVAILABLE(10.10, 8.0, 9.0, 1.0); +OBJC_EXPORT Class _Nullable +objc_readClassPair(Class _Nonnull cls, + const struct objc_image_info * _Nonnull info) + OBJC_AVAILABLE(10.10, 8.0, 9.0, 1.0, 2.0); #endif // Batch object allocation using malloc_zone_batch_malloc(). -OBJC_EXPORT unsigned class_createInstances(Class cls, size_t extraBytes, - id *results, unsigned num_requested) - OBJC_AVAILABLE(10.7, 4.3, 9.0, 1.0) +OBJC_EXPORT unsigned +class_createInstances(Class _Nullable cls, size_t extraBytes, + id _Nonnull * _Nonnull results, unsigned num_requested) + OBJC_AVAILABLE(10.7, 4.3, 9.0, 1.0, 2.0) OBJC_ARC_UNAVAILABLE; // Get the isa pointer written into objects just before being freed. -OBJC_EXPORT Class _objc_getFreedObjectClass(void) - OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0); +OBJC_EXPORT Class _Nonnull +_objc_getFreedObjectClass(void) + OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0); // env NSObjCMessageLoggingEnabled -OBJC_EXPORT void instrumentObjcMessageSends(BOOL flag) - OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0); +OBJC_EXPORT void +instrumentObjcMessageSends(BOOL flag) + OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0); // Initializer called by libSystem -OBJC_EXPORT void _objc_init(void) +OBJC_EXPORT void +_objc_init(void) #if __OBJC2__ - OBJC_AVAILABLE(10.8, 6.0, 9.0, 1.0); + OBJC_AVAILABLE(10.8, 6.0, 9.0, 1.0, 2.0); #else - OBJC_AVAILABLE(10.12, 10.0, 10.0, 3.0); + OBJC_AVAILABLE(10.12, 10.0, 10.0, 3.0, 2.0); #endif // fork() safety called by libSystem -OBJC_EXPORT void _objc_atfork_prepare(void) - OBJC_AVAILABLE(10.12, 10.0, 10.0, 3.0); -OBJC_EXPORT void _objc_atfork_parent(void) - OBJC_AVAILABLE(10.12, 10.0, 10.0, 3.0); -OBJC_EXPORT void _objc_atfork_child(void) - OBJC_AVAILABLE(10.12, 10.0, 10.0, 3.0); +OBJC_EXPORT void +_objc_atfork_prepare(void) + OBJC_AVAILABLE(10.12, 10.0, 10.0, 3.0, 2.0); + +OBJC_EXPORT void +_objc_atfork_parent(void) + OBJC_AVAILABLE(10.12, 10.0, 10.0, 3.0, 2.0); + +OBJC_EXPORT void +_objc_atfork_child(void) + OBJC_AVAILABLE(10.12, 10.0, 10.0, 3.0, 2.0); // Return YES if GC is on and `object` is a GC allocation. -OBJC_EXPORT BOOL objc_isAuto(id object) +OBJC_EXPORT BOOL +objc_isAuto(id _Nullable object) __OSX_DEPRECATED(10.4, 10.8, "it always returns NO") - __IOS_UNAVAILABLE __TVOS_UNAVAILABLE __WATCHOS_UNAVAILABLE; + __IOS_UNAVAILABLE __TVOS_UNAVAILABLE + __WATCHOS_UNAVAILABLE __BRIDGEOS_UNAVAILABLE; // GC startup callback from Foundation -OBJC_EXPORT malloc_zone_t *objc_collect_init(int (*callback)(void)) +OBJC_EXPORT malloc_zone_t * _Nullable +objc_collect_init(int (* _Nonnull callback)(void)) __OSX_DEPRECATED(10.4, 10.8, "it does nothing") - __IOS_UNAVAILABLE __TVOS_UNAVAILABLE __WATCHOS_UNAVAILABLE; + __IOS_UNAVAILABLE __TVOS_UNAVAILABLE + __WATCHOS_UNAVAILABLE __BRIDGEOS_UNAVAILABLE; // Plainly-implemented GC barriers. Rosetta used to use these. -OBJC_EXPORT id objc_assign_strongCast_generic(id value, id *dest) +OBJC_EXPORT id _Nullable +objc_assign_strongCast_generic(id _Nullable value, id _Nullable * _Nonnull dest) UNAVAILABLE_ATTRIBUTE; -OBJC_EXPORT id objc_assign_global_generic(id value, id *dest) + +OBJC_EXPORT id _Nullable +objc_assign_global_generic(id _Nullable value, id _Nullable * _Nonnull dest) UNAVAILABLE_ATTRIBUTE; -OBJC_EXPORT id objc_assign_threadlocal_generic(id value, id *dest) + +OBJC_EXPORT id _Nullable +objc_assign_threadlocal_generic(id _Nullable value, + id _Nullable * _Nonnull dest) UNAVAILABLE_ATTRIBUTE; -OBJC_EXPORT id objc_assign_ivar_generic(id value, id dest, ptrdiff_t offset) + +OBJC_EXPORT id _Nullable +objc_assign_ivar_generic(id _Nullable value, id _Nonnull dest, ptrdiff_t offset) UNAVAILABLE_ATTRIBUTE; // GC preflight for an app executable. // 1: some slice requires GC // 0: no slice requires GC // -1: I/O or file format error -OBJC_EXPORT int objc_appRequiresGC(int fd) +OBJC_EXPORT int +objc_appRequiresGC(int fd) __OSX_AVAILABLE(10.11) - __IOS_UNAVAILABLE __TVOS_UNAVAILABLE __WATCHOS_UNAVAILABLE; + __IOS_UNAVAILABLE __TVOS_UNAVAILABLE + __WATCHOS_UNAVAILABLE __BRIDGEOS_UNAVAILABLE; // Install missing-class callback. Used by the late unlamented ZeroLink. -OBJC_EXPORT void _objc_setClassLoader(BOOL (*newClassLoader)(const char *)) OBJC2_UNAVAILABLE; +OBJC_EXPORT void +_objc_setClassLoader(BOOL (* _Nonnull newClassLoader)(const char * _Nonnull)) + OBJC2_UNAVAILABLE; // Install handler for allocation failures. // Handler may abort, or throw, or provide an object to return. -OBJC_EXPORT void _objc_setBadAllocHandler(id (*newHandler)(Class isa)) - OBJC_AVAILABLE(10.8, 6.0, 9.0, 1.0); +OBJC_EXPORT void +_objc_setBadAllocHandler(id _Nullable (* _Nonnull newHandler) + (Class _Nullable isa)) + OBJC_AVAILABLE(10.8, 6.0, 9.0, 1.0, 2.0); // This can go away when AppKit stops calling it (rdar://7811851) #if __OBJC2__ -OBJC_EXPORT void objc_setMultithreaded (BOOL flag) +OBJC_EXPORT void +objc_setMultithreaded (BOOL flag) __OSX_DEPRECATED(10.0, 10.5, "multithreading is always available") - __IOS_UNAVAILABLE __TVOS_UNAVAILABLE __WATCHOS_UNAVAILABLE; + __IOS_UNAVAILABLE __TVOS_UNAVAILABLE + __WATCHOS_UNAVAILABLE __BRIDGEOS_UNAVAILABLE; #endif // Used by ExceptionHandling.framework #if !__OBJC2__ -OBJC_EXPORT void _objc_error(id rcv, const char *fmt, va_list args) +OBJC_EXPORT void +_objc_error(id _Nullable rcv, const char * _Nonnull fmt, va_list args) __attribute__((noreturn)) __OSX_DEPRECATED(10.0, 10.5, "use other logging facilities instead") - __IOS_UNAVAILABLE __TVOS_UNAVAILABLE __WATCHOS_UNAVAILABLE; + __IOS_UNAVAILABLE __TVOS_UNAVAILABLE + __WATCHOS_UNAVAILABLE __BRIDGEOS_UNAVAILABLE; #endif @@ -210,44 +244,46 @@ _objc_taggedPointersEnabled(void); // Register a class for a tagged pointer tag. // Aborts if the tag is invalid or already in use. -OBJC_EXPORT void _objc_registerTaggedPointerClass(objc_tag_index_t tag, Class cls) - OBJC_AVAILABLE(10.9, 7.0, 9.0, 1.0); +OBJC_EXPORT void +_objc_registerTaggedPointerClass(objc_tag_index_t tag, Class _Nonnull cls) + OBJC_AVAILABLE(10.9, 7.0, 9.0, 1.0, 2.0); // Returns the registered class for the given tag. // Returns nil if the tag is valid but has no registered class. // Aborts if the tag is invalid. -OBJC_EXPORT Class _objc_getClassForTag(objc_tag_index_t tag) - OBJC_AVAILABLE(10.9, 7.0, 9.0, 1.0); +OBJC_EXPORT Class _Nullable +_objc_getClassForTag(objc_tag_index_t tag) + OBJC_AVAILABLE(10.9, 7.0, 9.0, 1.0, 2.0); // Create a tagged pointer object with the given tag and payload. // Assumes the tag is valid. // Assumes tagged pointers are enabled. // The payload will be silently truncated to fit. -static inline void * +static inline void * _Nonnull _objc_makeTaggedPointer(objc_tag_index_t tag, uintptr_t payload); // Return true if ptr is a tagged pointer object. // Does not check the validity of ptr's class. static inline bool -_objc_isTaggedPointer(const void *ptr); +_objc_isTaggedPointer(const void * _Nullable ptr); // Extract the tag value from the given tagged pointer object. // Assumes ptr is a valid tagged pointer object. // Does not check the validity of ptr's tag. static inline objc_tag_index_t -_objc_getTaggedPointerTag(const void *ptr); +_objc_getTaggedPointerTag(const void * _Nullable ptr); // Extract the payload from the given tagged pointer object. // Assumes ptr is a valid tagged pointer object. // The payload value is zero-extended. static inline uintptr_t -_objc_getTaggedPointerValue(const void *ptr); +_objc_getTaggedPointerValue(const void * _Nullable ptr); // Extract the payload from the given tagged pointer object. // Assumes ptr is a valid tagged pointer object. // The payload value is sign-extended. static inline intptr_t -_objc_getTaggedPointerSignedValue(const void *ptr); +_objc_getTaggedPointerSignedValue(const void * _Nullable ptr); // Don't use the values below. Use the declarations above. @@ -270,23 +306,23 @@ _objc_getTaggedPointerSignedValue(const void *ptr); #define _OBJC_TAG_EXT_SLOT_MASK 0xff #if OBJC_MSB_TAGGED_POINTERS -# define _OBJC_TAG_MASK (1ULL<<63) +# define _OBJC_TAG_MASK (1UL<<63) # define _OBJC_TAG_INDEX_SHIFT 60 # define _OBJC_TAG_SLOT_SHIFT 60 # define _OBJC_TAG_PAYLOAD_LSHIFT 4 # define _OBJC_TAG_PAYLOAD_RSHIFT 4 -# define _OBJC_TAG_EXT_MASK (0xfULL<<60) +# define _OBJC_TAG_EXT_MASK (0xfUL<<60) # define _OBJC_TAG_EXT_INDEX_SHIFT 52 # define _OBJC_TAG_EXT_SLOT_SHIFT 52 # define _OBJC_TAG_EXT_PAYLOAD_LSHIFT 12 # define _OBJC_TAG_EXT_PAYLOAD_RSHIFT 12 #else -# define _OBJC_TAG_MASK 1 +# define _OBJC_TAG_MASK 1UL # define _OBJC_TAG_INDEX_SHIFT 1 # define _OBJC_TAG_SLOT_SHIFT 0 # define _OBJC_TAG_PAYLOAD_LSHIFT 0 # define _OBJC_TAG_PAYLOAD_RSHIFT 4 -# define _OBJC_TAG_EXT_MASK 0xfULL +# define _OBJC_TAG_EXT_MASK 0xfUL # define _OBJC_TAG_EXT_INDEX_SHIFT 4 # define _OBJC_TAG_EXT_SLOT_SHIFT 4 # define _OBJC_TAG_EXT_PAYLOAD_LSHIFT 0 @@ -300,7 +336,7 @@ _objc_taggedPointersEnabled(void) return (objc_debug_taggedpointer_mask != 0); } -static inline void * +static inline void * _Nonnull _objc_makeTaggedPointer(objc_tag_index_t tag, uintptr_t value) { // PAYLOAD_LSHIFT and PAYLOAD_RSHIFT are the payload extraction shifts. @@ -309,7 +345,7 @@ _objc_makeTaggedPointer(objc_tag_index_t tag, uintptr_t value) // assert(_objc_taggedPointersEnabled()); if (tag <= OBJC_TAG_Last60BitPayload) { // assert(((value << _OBJC_TAG_PAYLOAD_RSHIFT) >> _OBJC_TAG_PAYLOAD_LSHIFT) == value); - return (void*) + return (void *) (_OBJC_TAG_MASK | ((uintptr_t)tag << _OBJC_TAG_INDEX_SHIFT) | ((value << _OBJC_TAG_PAYLOAD_RSHIFT) >> _OBJC_TAG_PAYLOAD_LSHIFT)); @@ -317,7 +353,7 @@ _objc_makeTaggedPointer(objc_tag_index_t tag, uintptr_t value) // assert(tag >= OBJC_TAG_First52BitPayload); // assert(tag <= OBJC_TAG_Last52BitPayload); // assert(((value << _OBJC_TAG_EXT_PAYLOAD_RSHIFT) >> _OBJC_TAG_EXT_PAYLOAD_LSHIFT) == value); - return (void*) + return (void *) (_OBJC_TAG_EXT_MASK | ((uintptr_t)(tag - OBJC_TAG_First52BitPayload) << _OBJC_TAG_EXT_INDEX_SHIFT) | ((value << _OBJC_TAG_EXT_PAYLOAD_RSHIFT) >> _OBJC_TAG_EXT_PAYLOAD_LSHIFT)); @@ -325,13 +361,13 @@ _objc_makeTaggedPointer(objc_tag_index_t tag, uintptr_t value) } static inline bool -_objc_isTaggedPointer(const void *ptr) +_objc_isTaggedPointer(const void * _Nullable ptr) { - return ((intptr_t)ptr & _OBJC_TAG_MASK) == _OBJC_TAG_MASK; + return ((uintptr_t)ptr & _OBJC_TAG_MASK) == _OBJC_TAG_MASK; } static inline objc_tag_index_t -_objc_getTaggedPointerTag(const void *ptr) +_objc_getTaggedPointerTag(const void * _Nullable ptr) { // assert(_objc_isTaggedPointer(ptr)); uintptr_t basicTag = ((uintptr_t)ptr >> _OBJC_TAG_INDEX_SHIFT) & _OBJC_TAG_INDEX_MASK; @@ -344,7 +380,7 @@ _objc_getTaggedPointerTag(const void *ptr) } static inline uintptr_t -_objc_getTaggedPointerValue(const void *ptr) +_objc_getTaggedPointerValue(const void * _Nullable ptr) { // assert(_objc_isTaggedPointer(ptr)); uintptr_t basicTag = ((uintptr_t)ptr >> _OBJC_TAG_INDEX_SHIFT) & _OBJC_TAG_INDEX_MASK; @@ -356,7 +392,7 @@ _objc_getTaggedPointerValue(const void *ptr) } static inline intptr_t -_objc_getTaggedPointerSignedValue(const void *ptr) +_objc_getTaggedPointerSignedValue(const void * _Nullable ptr) { // assert(_objc_isTaggedPointer(ptr)); uintptr_t basicTag = ((uintptr_t)ptr >> _OBJC_TAG_INDEX_SHIFT) & _OBJC_TAG_INDEX_MASK; @@ -384,22 +420,31 @@ _objc_getTaggedPointerSignedValue(const void *ptr) * * class_getMethodImplementation(object_getClass(obj), name); */ -OBJC_EXPORT IMP object_getMethodImplementation(id obj, SEL name) - OBJC_AVAILABLE(10.9, 7.0, 9.0, 1.0); +OBJC_EXPORT IMP _Nonnull +object_getMethodImplementation(id _Nullable obj, SEL _Nonnull name) + OBJC_AVAILABLE(10.9, 7.0, 9.0, 1.0, 2.0); -OBJC_EXPORT IMP object_getMethodImplementation_stret(id obj, SEL name) - OBJC_AVAILABLE(10.9, 7.0, 9.0, 1.0) +OBJC_EXPORT IMP _Nonnull +object_getMethodImplementation_stret(id _Nullable obj, SEL _Nonnull name) + OBJC_AVAILABLE(10.9, 7.0, 9.0, 1.0, 2.0) OBJC_ARM64_UNAVAILABLE; // Instance-specific instance variable layout. -OBJC_EXPORT void _class_setIvarLayoutAccessor(Class cls_gen, const uint8_t* (*accessor) (id object)) +OBJC_EXPORT void +_class_setIvarLayoutAccessor(Class _Nullable cls, + const uint8_t* _Nullable (* _Nonnull accessor) + (id _Nullable object)) __OSX_AVAILABLE(10.7) - __IOS_UNAVAILABLE __TVOS_UNAVAILABLE __WATCHOS_UNAVAILABLE; -OBJC_EXPORT const uint8_t *_object_getIvarLayout(Class cls_gen, id object) + __IOS_UNAVAILABLE __TVOS_UNAVAILABLE + __WATCHOS_UNAVAILABLE __BRIDGEOS_UNAVAILABLE; + +OBJC_EXPORT const uint8_t * _Nullable +_object_getIvarLayout(Class _Nullable cls, id _Nullable object) __OSX_AVAILABLE(10.7) - __IOS_UNAVAILABLE __TVOS_UNAVAILABLE __WATCHOS_UNAVAILABLE; + __IOS_UNAVAILABLE __TVOS_UNAVAILABLE + __WATCHOS_UNAVAILABLE __BRIDGEOS_UNAVAILABLE; /* "Unknown" includes non-object ivars and non-ARC non-__weak ivars @@ -414,230 +459,224 @@ typedef enum { objc_ivar_memoryUnretained // direct access / direct access } objc_ivar_memory_management_t; -OBJC_EXPORT objc_ivar_memory_management_t _class_getIvarMemoryManagement(Class cls, Ivar ivar) - OBJC_AVAILABLE(10.12, 10.0, 10.0, 3.0); +OBJC_EXPORT objc_ivar_memory_management_t +_class_getIvarMemoryManagement(Class _Nullable cls, Ivar _Nonnull ivar) + OBJC_AVAILABLE(10.12, 10.0, 10.0, 3.0, 2.0); -OBJC_EXPORT BOOL _class_isFutureClass(Class cls) - OBJC_AVAILABLE(10.9, 7.0, 9.0, 1.0); +OBJC_EXPORT BOOL _class_isFutureClass(Class _Nullable cls) + OBJC_AVAILABLE(10.9, 7.0, 9.0, 1.0, 2.0); // API to only be called by root classes like NSObject or NSProxy OBJC_EXPORT -id -_objc_rootRetain(id obj) - OBJC_AVAILABLE(10.7, 5.0, 9.0, 1.0); +id _Nonnull +_objc_rootRetain(id _Nonnull obj) + OBJC_AVAILABLE(10.7, 5.0, 9.0, 1.0, 2.0); OBJC_EXPORT void -_objc_rootRelease(id obj) - OBJC_AVAILABLE(10.7, 5.0, 9.0, 1.0); +_objc_rootRelease(id _Nonnull obj) + OBJC_AVAILABLE(10.7, 5.0, 9.0, 1.0, 2.0); OBJC_EXPORT bool -_objc_rootReleaseWasZero(id obj) - OBJC_AVAILABLE(10.7, 5.0, 9.0, 1.0); +_objc_rootReleaseWasZero(id _Nonnull obj) + OBJC_AVAILABLE(10.7, 5.0, 9.0, 1.0, 2.0); OBJC_EXPORT bool -_objc_rootTryRetain(id obj) - OBJC_AVAILABLE(10.7, 5.0, 9.0, 1.0); +_objc_rootTryRetain(id _Nonnull obj) + OBJC_AVAILABLE(10.7, 5.0, 9.0, 1.0, 2.0); OBJC_EXPORT bool -_objc_rootIsDeallocating(id obj) - OBJC_AVAILABLE(10.7, 5.0, 9.0, 1.0); +_objc_rootIsDeallocating(id _Nonnull obj) + OBJC_AVAILABLE(10.7, 5.0, 9.0, 1.0, 2.0); OBJC_EXPORT -id -_objc_rootAutorelease(id obj) - OBJC_AVAILABLE(10.7, 5.0, 9.0, 1.0); +id _Nonnull +_objc_rootAutorelease(id _Nonnull obj) + OBJC_AVAILABLE(10.7, 5.0, 9.0, 1.0, 2.0); OBJC_EXPORT uintptr_t -_objc_rootRetainCount(id obj) - OBJC_AVAILABLE(10.7, 5.0, 9.0, 1.0); +_objc_rootRetainCount(id _Nonnull obj) + OBJC_AVAILABLE(10.7, 5.0, 9.0, 1.0, 2.0); OBJC_EXPORT -id -_objc_rootInit(id obj) - OBJC_AVAILABLE(10.7, 5.0, 9.0, 1.0); +id _Nonnull +_objc_rootInit(id _Nonnull obj) + OBJC_AVAILABLE(10.7, 5.0, 9.0, 1.0, 2.0); OBJC_EXPORT -id -_objc_rootAllocWithZone(Class cls, malloc_zone_t *zone) - OBJC_AVAILABLE(10.7, 5.0, 9.0, 1.0); +id _Nullable +_objc_rootAllocWithZone(Class _Nonnull cls, malloc_zone_t * _Nullable zone) + OBJC_AVAILABLE(10.7, 5.0, 9.0, 1.0, 2.0); OBJC_EXPORT -id -_objc_rootAlloc(Class cls) - OBJC_AVAILABLE(10.7, 5.0, 9.0, 1.0); +id _Nullable +_objc_rootAlloc(Class _Nonnull cls) + OBJC_AVAILABLE(10.7, 5.0, 9.0, 1.0, 2.0); OBJC_EXPORT void -_objc_rootDealloc(id obj) - OBJC_AVAILABLE(10.7, 5.0, 9.0, 1.0); +_objc_rootDealloc(id _Nonnull obj) + OBJC_AVAILABLE(10.7, 5.0, 9.0, 1.0, 2.0); OBJC_EXPORT void -_objc_rootFinalize(id obj) - OBJC_AVAILABLE(10.7, 5.0, 9.0, 1.0); +_objc_rootFinalize(id _Nonnull obj) + OBJC_AVAILABLE(10.7, 5.0, 9.0, 1.0, 2.0); OBJC_EXPORT -malloc_zone_t * -_objc_rootZone(id obj) - OBJC_AVAILABLE(10.7, 5.0, 9.0, 1.0); +malloc_zone_t * _Nonnull +_objc_rootZone(id _Nonnull obj) + OBJC_AVAILABLE(10.7, 5.0, 9.0, 1.0, 2.0); OBJC_EXPORT uintptr_t -_objc_rootHash(id obj) - OBJC_AVAILABLE(10.7, 5.0, 9.0, 1.0); +_objc_rootHash(id _Nonnull obj) + OBJC_AVAILABLE(10.7, 5.0, 9.0, 1.0, 2.0); OBJC_EXPORT -void * +void * _Nonnull objc_autoreleasePoolPush(void) - OBJC_AVAILABLE(10.7, 5.0, 9.0, 1.0); + OBJC_AVAILABLE(10.7, 5.0, 9.0, 1.0, 2.0); OBJC_EXPORT void -objc_autoreleasePoolPop(void *context) - OBJC_AVAILABLE(10.7, 5.0, 9.0, 1.0); +objc_autoreleasePoolPop(void * _Nonnull context) + OBJC_AVAILABLE(10.7, 5.0, 9.0, 1.0, 2.0); -OBJC_EXPORT id objc_alloc(Class cls) - OBJC_AVAILABLE(10.9, 7.0, 9.0, 1.0); +OBJC_EXPORT id _Nullable +objc_alloc(Class _Nullable cls) + OBJC_AVAILABLE(10.9, 7.0, 9.0, 1.0, 2.0); -OBJC_EXPORT id objc_allocWithZone(Class cls) - OBJC_AVAILABLE(10.9, 7.0, 9.0, 1.0); +OBJC_EXPORT id _Nullable +objc_allocWithZone(Class _Nullable cls) + OBJC_AVAILABLE(10.9, 7.0, 9.0, 1.0, 2.0); -OBJC_EXPORT id objc_retain(id obj) +OBJC_EXPORT id _Nullable +objc_retain(id _Nullable obj) __asm__("_objc_retain") - OBJC_AVAILABLE(10.7, 5.0, 9.0, 1.0); + OBJC_AVAILABLE(10.7, 5.0, 9.0, 1.0, 2.0); -OBJC_EXPORT void objc_release(id obj) +OBJC_EXPORT void +objc_release(id _Nullable obj) __asm__("_objc_release") - OBJC_AVAILABLE(10.7, 5.0, 9.0, 1.0); + OBJC_AVAILABLE(10.7, 5.0, 9.0, 1.0, 2.0); -OBJC_EXPORT id objc_autorelease(id obj) +OBJC_EXPORT id _Nullable +objc_autorelease(id _Nullable obj) __asm__("_objc_autorelease") - OBJC_AVAILABLE(10.7, 5.0, 9.0, 1.0); + OBJC_AVAILABLE(10.7, 5.0, 9.0, 1.0, 2.0); // Prepare a value at +1 for return through a +0 autoreleasing convention. -OBJC_EXPORT -id -objc_autoreleaseReturnValue(id obj) - OBJC_AVAILABLE(10.7, 5.0, 9.0, 1.0); +OBJC_EXPORT id _Nullable +objc_autoreleaseReturnValue(id _Nullable obj) + OBJC_AVAILABLE(10.7, 5.0, 9.0, 1.0, 2.0); // Prepare a value at +0 for return through a +0 autoreleasing convention. -OBJC_EXPORT -id -objc_retainAutoreleaseReturnValue(id obj) - OBJC_AVAILABLE(10.7, 5.0, 9.0, 1.0); +OBJC_EXPORT id _Nullable +objc_retainAutoreleaseReturnValue(id _Nullable obj) + OBJC_AVAILABLE(10.7, 5.0, 9.0, 1.0, 2.0); // Accept a value returned through a +0 autoreleasing convention for use at +1. -OBJC_EXPORT -id -objc_retainAutoreleasedReturnValue(id obj) - OBJC_AVAILABLE(10.7, 5.0, 9.0, 1.0); +OBJC_EXPORT id _Nullable +objc_retainAutoreleasedReturnValue(id _Nullable obj) + OBJC_AVAILABLE(10.7, 5.0, 9.0, 1.0, 2.0); // Accept a value returned through a +0 autoreleasing convention for use at +0. -OBJC_EXPORT -id -objc_unsafeClaimAutoreleasedReturnValue(id obj) - OBJC_AVAILABLE(10.11, 9.0, 9.0, 1.0); +OBJC_EXPORT id _Nullable +objc_unsafeClaimAutoreleasedReturnValue(id _Nullable obj) + OBJC_AVAILABLE(10.11, 9.0, 9.0, 1.0, 2.0); -OBJC_EXPORT -void -objc_storeStrong(id *location, id obj) - OBJC_AVAILABLE(10.7, 5.0, 9.0, 1.0); +OBJC_EXPORT void +objc_storeStrong(id _Nullable * _Nonnull location, id _Nullable obj) + OBJC_AVAILABLE(10.7, 5.0, 9.0, 1.0, 2.0); -OBJC_EXPORT -id -objc_retainAutorelease(id obj) - OBJC_AVAILABLE(10.7, 5.0, 9.0, 1.0); +OBJC_EXPORT id _Nullable +objc_retainAutorelease(id _Nullable obj) + OBJC_AVAILABLE(10.7, 5.0, 9.0, 1.0, 2.0); // obsolete. -OBJC_EXPORT id objc_retain_autorelease(id obj) - OBJC_AVAILABLE(10.7, 5.0, 9.0, 1.0); +OBJC_EXPORT id _Nullable +objc_retain_autorelease(id _Nullable obj) + OBJC_AVAILABLE(10.7, 5.0, 9.0, 1.0, 2.0); -OBJC_EXPORT -id -objc_loadWeakRetained(id *location) - OBJC_AVAILABLE(10.7, 5.0, 9.0, 1.0); +OBJC_EXPORT id _Nullable +objc_loadWeakRetained(id _Nullable * _Nonnull location) + OBJC_AVAILABLE(10.7, 5.0, 9.0, 1.0, 2.0); -OBJC_EXPORT -id -objc_initWeak(id *location, id val) - OBJC_AVAILABLE(10.7, 5.0, 9.0, 1.0); +OBJC_EXPORT id _Nullable +objc_initWeak(id _Nullable * _Nonnull location, id _Nullable val) + OBJC_AVAILABLE(10.7, 5.0, 9.0, 1.0, 2.0); // Like objc_storeWeak, but stores nil if the new object is deallocating // or the new object's class does not support weak references. // Returns the value stored (either the new object or nil). -OBJC_EXPORT -id -objc_storeWeakOrNil(id *location, id obj) - OBJC_AVAILABLE(10.11, 9.0, 9.0, 1.0); +OBJC_EXPORT id _Nullable +objc_storeWeakOrNil(id _Nullable * _Nonnull location, id _Nullable obj) + OBJC_AVAILABLE(10.11, 9.0, 9.0, 1.0, 2.0); // Like objc_initWeak, but stores nil if the new object is deallocating // or the new object's class does not support weak references. // Returns the value stored (either the new object or nil). -OBJC_EXPORT -id -objc_initWeakOrNil(id *location, id val) - OBJC_AVAILABLE(10.11, 9.0, 9.0, 1.0); +OBJC_EXPORT id _Nullable +objc_initWeakOrNil(id _Nullable * _Nonnull location, id _Nullable val) + OBJC_AVAILABLE(10.11, 9.0, 9.0, 1.0, 2.0); -OBJC_EXPORT -void -objc_destroyWeak(id *location) - OBJC_AVAILABLE(10.7, 5.0, 9.0, 1.0); +OBJC_EXPORT void +objc_destroyWeak(id _Nullable * _Nonnull location) + OBJC_AVAILABLE(10.7, 5.0, 9.0, 1.0, 2.0); -OBJC_EXPORT -void -objc_copyWeak(id *to, id *from) - OBJC_AVAILABLE(10.7, 5.0, 9.0, 1.0); +OBJC_EXPORT void +objc_copyWeak(id _Nullable * _Nonnull to, id _Nullable * _Nonnull from) + OBJC_AVAILABLE(10.7, 5.0, 9.0, 1.0, 2.0); -OBJC_EXPORT -void -objc_moveWeak(id *to, id *from) - OBJC_AVAILABLE(10.7, 5.0, 9.0, 1.0); +OBJC_EXPORT void +objc_moveWeak(id _Nullable * _Nonnull to, id _Nullable * _Nonnull from) + OBJC_AVAILABLE(10.7, 5.0, 9.0, 1.0, 2.0); -OBJC_EXPORT -void +OBJC_EXPORT void _objc_autoreleasePoolPrint(void) - OBJC_AVAILABLE(10.7, 5.0, 9.0, 1.0); + OBJC_AVAILABLE(10.7, 5.0, 9.0, 1.0, 2.0); -OBJC_EXPORT BOOL objc_should_deallocate(id object) - OBJC_AVAILABLE(10.7, 5.0, 9.0, 1.0); +OBJC_EXPORT BOOL +objc_should_deallocate(id _Nonnull object) + OBJC_AVAILABLE(10.7, 5.0, 9.0, 1.0, 2.0); -OBJC_EXPORT void objc_clear_deallocating(id object) - OBJC_AVAILABLE(10.7, 5.0, 9.0, 1.0); +OBJC_EXPORT void +objc_clear_deallocating(id _Nonnull object) + OBJC_AVAILABLE(10.7, 5.0, 9.0, 1.0, 2.0); // to make CF link for now -OBJC_EXPORT -void * +OBJC_EXPORT void * _Nonnull _objc_autoreleasePoolPush(void) - OBJC_AVAILABLE(10.7, 5.0, 9.0, 1.0); + OBJC_AVAILABLE(10.7, 5.0, 9.0, 1.0, 2.0); -OBJC_EXPORT -void -_objc_autoreleasePoolPop(void *context) - OBJC_AVAILABLE(10.7, 5.0, 9.0, 1.0); +OBJC_EXPORT void +_objc_autoreleasePoolPop(void * _Nonnull context) + OBJC_AVAILABLE(10.7, 5.0, 9.0, 1.0, 2.0); // Extra @encode data for XPC, or NULL -OBJC_EXPORT const char *_protocol_getMethodTypeEncoding(Protocol *p, SEL sel, BOOL isRequiredMethod, BOOL isInstanceMethod) - OBJC_AVAILABLE(10.8, 6.0, 9.0, 1.0); +OBJC_EXPORT const char * _Nullable +_protocol_getMethodTypeEncoding(Protocol * _Nonnull proto, SEL _Nonnull sel, + BOOL isRequiredMethod, BOOL isInstanceMethod) + OBJC_AVAILABLE(10.8, 6.0, 9.0, 1.0, 2.0); // API to only be called by classes that provide their own reference count storage -OBJC_EXPORT -void -_objc_deallocOnMainThreadHelper(void *context) - OBJC_AVAILABLE(10.7, 5.0, 9.0, 1.0); +OBJC_EXPORT void +_objc_deallocOnMainThreadHelper(void * _Nullable context) + OBJC_AVAILABLE(10.7, 5.0, 9.0, 1.0, 2.0); // On async versus sync deallocation and the _dealloc2main flag // diff --git a/runtime/objc-load.h b/runtime/objc-load.h index 8ec1fa2..0e334a2 100644 --- a/runtime/objc-load.h +++ b/runtime/objc-load.h @@ -35,20 +35,21 @@ /* dynamically loading Mach-O object files that contain Objective-C code */ OBJC_EXPORT long objc_loadModules ( - char *modlist[], - void *errStream, - void (*class_callback) (Class, Category), - /*headerType*/ struct mach_header **hdr_addr, - char *debug_file + char * _Nullable modlist[_Nullable], + void * _Nullable errStream, + void (* _Nullable class_callback) (Class _Nullable, Category _Nullable), + /*headerType*/ struct mach_header * _Nullable * _Nullable hdr_addr, + char * _Nullable debug_file ) OBJC2_UNAVAILABLE; + OBJC_EXPORT int objc_loadModule ( - char * moduleName, - void (*class_callback) (Class, Category), - int * errorCode + char * _Nullable moduleName, + void (* _Nullable class_callback) (Class _Nullable, Category _Nullable), + int * _Nullable errorCode ) OBJC2_UNAVAILABLE; OBJC_EXPORT long objc_unloadModules( - void *errorStream, /* input (optional) */ - void (*unloadCallback)(Class, Category) /* input (optional) */ + void * _Nullable errorStream, /* input (optional) */ + void (* _Nullable unloadCallback)(Class _Nullable, Category _Nullable) /* input (optional) */ ) OBJC2_UNAVAILABLE; #endif /* _OBJC_LOAD_H_ */ diff --git a/runtime/objc-lockdebug.h b/runtime/objc-lockdebug.h index 211b7eb..0fdee6f 100644 --- a/runtime/objc-lockdebug.h +++ b/runtime/objc-lockdebug.h @@ -21,7 +21,7 @@ * @APPLE_LICENSE_HEADER_END@ */ -#if DEBUG +#if LOCKDEBUG extern void lockdebug_assert_all_locks_locked(); extern void lockdebug_assert_no_locks_locked(); extern void lockdebug_setInForkPrepare(bool); diff --git a/runtime/objc-lockdebug.mm b/runtime/objc-lockdebug.mm index 7b38de6..c1ac7a4 100644 --- a/runtime/objc-lockdebug.mm +++ b/runtime/objc-lockdebug.mm @@ -28,7 +28,7 @@ #include "objc-private.h" -#if DEBUG && !TARGET_OS_WIN32 +#if LOCKDEBUG && !TARGET_OS_WIN32 #include @@ -65,23 +65,41 @@ struct lockorder { const void *l; std::vector predecessors; + + mutable std::unordered_map memo; + + lockorder(const void *newl) : l(newl) { } }; -static std::unordered_map lockOrderList; +static std::unordered_map lockOrderList; +// not mutex_t because we don't want lock debugging on this lock +static mutex_tt lockOrderLock; static bool -lockPrecedesLock(const lockorder& oldlock, const lockorder& newlock) +lockPrecedesLock(const lockorder *oldlock, const lockorder *newlock) { - for (const auto *pre : newlock.predecessors) { - if (&oldlock == pre) return true; - if (lockPrecedesLock(oldlock, *pre)) return true; + auto memoed = newlock->memo.find(oldlock); + if (memoed != newlock->memo.end()) { + return memoed->second; } - return false; + + bool result = false; + for (const auto *pre : newlock->predecessors) { + if (oldlock == pre || lockPrecedesLock(oldlock, pre)) { + result = true; + break; + } + } + + newlock->memo[oldlock] = result; + return result; } static bool lockPrecedesLock(const void *oldlock, const void *newlock) { + mutex_tt::locker lock(lockOrderLock); + auto oldorder = lockOrderList.find(oldlock); auto neworder = lockOrderList.find(newlock); if (neworder == lockOrderList.end() || oldorder == lockOrderList.end()) { @@ -93,6 +111,8 @@ static bool lockUnorderedWithLock(const void *oldlock, const void *newlock) { + mutex_tt::locker lock(lockOrderLock); + auto oldorder = lockOrderList.find(oldlock); auto neworder = lockOrderList.find(newlock); if (neworder == lockOrderList.end() || oldorder == lockOrderList.end()) { @@ -114,18 +134,20 @@ void lockdebug_lock_precedes_lock(const void *oldlock, const void *newlock) _objc_fatal("contradiction in lock order declaration"); } + mutex_tt::locker lock(lockOrderLock); + auto oldorder = lockOrderList.find(oldlock); auto neworder = lockOrderList.find(newlock); if (oldorder == lockOrderList.end()) { - lockOrderList[oldlock] = lockorder{oldlock, {}}; + lockOrderList[oldlock] = new lockorder(oldlock); oldorder = lockOrderList.find(oldlock); } if (neworder == lockOrderList.end()) { - lockOrderList[newlock] = lockorder{newlock, {}}; + lockOrderList[newlock] = new lockorder(newlock); neworder = lockOrderList.find(newlock); } - neworder->second.predecessors.push_back(&oldorder->second); + neworder->second->predecessors.push_back(oldorder->second); } @@ -220,6 +242,11 @@ void lockdebug_lock_precedes_lock(const void *oldlock, const void *newlock) // Locks not in AllLocks are exempt (i.e. @synchronize locks) if (&locks != &AllLocks() && AllLocks().find(lock) != AllLocks().end()) { for (auto& oldlock : locks) { + if (AllLocks().find(oldlock.first) == AllLocks().end()) { + // oldlock is exempt + continue; + } + if (lockPrecedesLock(lock, oldlock.first)) { _objc_fatal("lock %p (%s) incorrectly acquired before %p (%s)", oldlock.first, sym(oldlock.first), lock, sym(lock)); diff --git a/runtime/objc-locks.h b/runtime/objc-locks.h index 05bd22f..00654eb 100644 --- a/runtime/objc-locks.h +++ b/runtime/objc-locks.h @@ -54,6 +54,8 @@ extern void SideTableForceResetAll(); extern void SideTableDefineLockOrder(); extern void SideTableLocksPrecedeLock(const void *newlock); extern void SideTableLocksSucceedLock(const void *oldlock); +extern void SideTableLocksPrecedeLocks(StripedMap& newlocks); +extern void SideTableLocksSucceedLocks(StripedMap& oldlocks); #if __OBJC2__ #include "objc-locks-new.h" diff --git a/runtime/objc-os.h b/runtime/objc-os.h index 7785d4a..0104d7d 100644 --- a/runtime/objc-os.h +++ b/runtime/objc-os.h @@ -690,50 +690,6 @@ static bool is_valid_direct_key(tls_key_t k) { } #endif -#if __arm__ - -// rdar://9162780 _pthread_get/setspecific_direct are inefficient -// copied from libdispatch - -__attribute__((const)) -static ALWAYS_INLINE void** -tls_base(void) -{ - uintptr_t p; -#if defined(__arm__) && defined(_ARM_ARCH_6) - __asm__("mrc p15, 0, %[p], c13, c0, 3" : [p] "=&r" (p)); - return (void**)(p & ~0x3ul); -#else -#error tls_base not implemented -#endif -} - - -static ALWAYS_INLINE void -tls_set_direct(void **tsdb, tls_key_t k, void *v) -{ - assert(is_valid_direct_key(k)); - - tsdb[k] = v; -} -#define tls_set_direct(k, v) \ - tls_set_direct(tls_base(), (k), (v)) - - -static ALWAYS_INLINE void * -tls_get_direct(void **tsdb, tls_key_t k) -{ - assert(is_valid_direct_key(k)); - - return tsdb[k]; -} -#define tls_get_direct(k) \ - tls_get_direct(tls_base(), (k)) - -// arm -#else -// not arm - static inline void *tls_get_direct(tls_key_t k) { assert(is_valid_direct_key(k)); @@ -755,9 +711,6 @@ static inline void tls_set_direct(tls_key_t k, void *value) } } -// not arm -#endif - // SUPPORT_DIRECT_THREAD_KEYS #endif @@ -789,11 +742,17 @@ template class monitor_tt; template class rwlock_tt; template class recursive_mutex_tt; -using spinlock_t = mutex_tt; -using mutex_t = mutex_tt; -using monitor_t = monitor_tt; -using rwlock_t = rwlock_tt; -using recursive_mutex_t = recursive_mutex_tt; +#if DEBUG +# define LOCKDEBUG 1 +#else +# define LOCKDEBUG 0 +#endif + +using spinlock_t = mutex_tt; +using mutex_t = mutex_tt; +using monitor_t = monitor_tt; +using rwlock_t = rwlock_tt; +using recursive_mutex_t = recursive_mutex_tt; // Use fork_unsafe_lock to get a lock that isn't // acquired and released around fork(). @@ -858,8 +817,19 @@ class mutex_tt : nocopy_t { lock1->unlock(); if (lock2 != lock1) lock2->unlock(); } + + // Scoped lock and unlock + class locker : nocopy_t { + mutex_tt& lock; + public: + locker(mutex_tt& newLock) + : lock(newLock) { lock.lock(); } + ~locker() { lock.unlock(); } + }; }; +using mutex_locker_t = mutex_tt::locker; + template class recursive_mutex_tt : nocopy_t { @@ -1237,27 +1207,35 @@ ustrdupMaybeNil(const uint8_t *str) // OS version checking: // // sdkVersion() -// DYLD_OS_VERSION(mac, ios, tv, watch) -// sdkIsOlderThan(mac, ios, tv, watch) -// sdkIsAtLeast(mac, ios, tv, watch) +// DYLD_OS_VERSION(mac, ios, tv, watch, bridge) +// sdkIsOlderThan(mac, ios, tv, watch, bridge) +// sdkIsAtLeast(mac, ios, tv, watch, bridge) // // This version order matches OBJC_AVAILABLE. #if TARGET_OS_OSX -# define DYLD_OS_VERSION(x, i, t, w) DYLD_MACOSX_VERSION_##x +# define DYLD_OS_VERSION(x, i, t, w, b) DYLD_MACOSX_VERSION_##x # define sdkVersion() dyld_get_program_sdk_version() #elif TARGET_OS_IOS -# define DYLD_OS_VERSION(x, i, t, w) DYLD_IOS_VERSION_##i +# define DYLD_OS_VERSION(x, i, t, w, b) DYLD_IOS_VERSION_##i # define sdkVersion() dyld_get_program_sdk_version() #elif TARGET_OS_TV // dyld does not currently have distinct constants for tvOS -# define DYLD_OS_VERSION(x, i, t, w) DYLD_IOS_VERSION_##t +# define DYLD_OS_VERSION(x, i, t, w, b) DYLD_IOS_VERSION_##t # define sdkVersion() dyld_get_program_sdk_version() +#elif TARGET_OS_BRIDGE +# if TARGET_OS_WATCH +# error bridgeOS 1.0 not supported +# endif + // fixme don't need bridgeOS versioning yet +# define DYLD_OS_VERSION(x, i, t, w, b) DYLD_IOS_VERSION_##t +# define sdkVersion() dyld_get_program_sdk_bridge_os_version() + #elif TARGET_OS_WATCH -# define DYLD_OS_VERSION(x, i, t, w) DYLD_WATCHOS_VERSION_##w +# define DYLD_OS_VERSION(x, i, t, w, b) DYLD_WATCHOS_VERSION_##w // watchOS has its own API for compatibility reasons # define sdkVersion() dyld_get_program_sdk_watch_os_version() @@ -1266,16 +1244,17 @@ ustrdupMaybeNil(const uint8_t *str) #endif -#define sdkIsOlderThan(x, i, t, w) \ - (sdkVersion() < DYLD_OS_VERSION(x, i, t, w)) -#define sdkIsAtLeast(x, i, t, w) \ - (sdkVersion() >= DYLD_OS_VERSION(x, i, t, w)) +#define sdkIsOlderThan(x, i, t, w, b) \ + (sdkVersion() < DYLD_OS_VERSION(x, i, t, w, b)) +#define sdkIsAtLeast(x, i, t, w, b) \ + (sdkVersion() >= DYLD_OS_VERSION(x, i, t, w, b)) // Allow bare 0 to be used in DYLD_OS_VERSION() and sdkIsOlderThan() #define DYLD_MACOSX_VERSION_0 0 #define DYLD_IOS_VERSION_0 0 #define DYLD_TVOS_VERSION_0 0 #define DYLD_WATCHOS_VERSION_0 0 +#define DYLD_BRIDGEOS_VERSION_0 0 // Pretty-print a DYLD_*_VERSION_* constant. #define SDK_FORMAT "%hu.%hhu.%hhu" diff --git a/runtime/objc-os.mm b/runtime/objc-os.mm index 3049901..b2fc8ac 100644 --- a/runtime/objc-os.mm +++ b/runtime/objc-os.mm @@ -538,6 +538,39 @@ static bool shouldRejectGCImage(const headerType *mhdr) } } #endif + +#if TARGET_OS_OSX + // Disable +initialize fork safety if the app is too old (< 10.13). + // Disable +initialize fork safety if the app has a + // __DATA,__objc_fork_ok section. + + if (dyld_get_program_sdk_version() < DYLD_MACOSX_VERSION_10_13) { + DisableInitializeForkSafety = true; + if (PrintInitializing) { + _objc_inform("INITIALIZE: disabling +initialize fork " + "safety enforcement because the app is " + "too old (SDK version " SDK_FORMAT ")", + FORMAT_SDK(dyld_get_program_sdk_version())); + } + } + + for (uint32_t i = 0; i < hCount; i++) { + auto hi = hList[i]; + auto mh = hi->mhdr(); + if (mh->filetype != MH_EXECUTE) continue; + unsigned long size; + if (getsectiondata(hi->mhdr(), "__DATA", "__objc_fork_ok", &size)) { + DisableInitializeForkSafety = true; + if (PrintInitializing) { + _objc_inform("INITIALIZE: disabling +initialize fork " + "safety enforcement because the app has " + "a __DATA,__objc_fork_ok section"); + } + } + break; // assume only one MH_EXECUTE image + } +#endif + } if (hCount > 0) { @@ -618,7 +651,7 @@ static void static_init() **********************************************************************/ // Declare lock ordering. -#if DEBUG +#if LOCKDEBUG __attribute__((constructor)) static void defineLockOrder() { @@ -667,37 +700,36 @@ static void defineLockOrder() StructLocks.succeedLock(&loadMethodLock); CppObjectLocks.succeedLock(&loadMethodLock); - // PropertyLocks and CppObjectLocks precede everything - // because they are held while objc_retain() or C++ copy are called. + // PropertyLocks and CppObjectLocks and AssociationManagerLock + // precede everything because they are held while objc_retain() + // or C++ copy are called. // (StructLocks do not precede everything because it calls memmove only.) - PropertyLocks.precedeLock(&classInitLock); - CppObjectLocks.precedeLock(&classInitLock); + auto PropertyAndCppObjectAndAssocLocksPrecedeLock = [&](const void *lock) { + PropertyLocks.precedeLock(lock); + CppObjectLocks.precedeLock(lock); + lockdebug_lock_precedes_lock(&AssociationsManagerLock, lock); + }; #if __OBJC2__ - PropertyLocks.precedeLock(&runtimeLock); - CppObjectLocks.precedeLock(&runtimeLock); - PropertyLocks.precedeLock(&DemangleCacheLock); - CppObjectLocks.precedeLock(&DemangleCacheLock); + PropertyAndCppObjectAndAssocLocksPrecedeLock(&runtimeLock); + PropertyAndCppObjectAndAssocLocksPrecedeLock(&DemangleCacheLock); #else - PropertyLocks.precedeLock(&methodListLock); - CppObjectLocks.precedeLock(&methodListLock); - PropertyLocks.precedeLock(&classLock); - CppObjectLocks.precedeLock(&classLock); - PropertyLocks.precedeLock(&NXUniqueStringLock); - CppObjectLocks.precedeLock(&NXUniqueStringLock); - PropertyLocks.precedeLock(&impLock); - CppObjectLocks.precedeLock(&impLock); + PropertyAndCppObjectAndAssocLocksPrecedeLock(&methodListLock); + PropertyAndCppObjectAndAssocLocksPrecedeLock(&classLock); + PropertyAndCppObjectAndAssocLocksPrecedeLock(&NXUniqueStringLock); + PropertyAndCppObjectAndAssocLocksPrecedeLock(&impLock); #endif - PropertyLocks.precedeLock(&selLock); - CppObjectLocks.precedeLock(&selLock); - PropertyLocks.precedeLock(&cacheUpdateLock); - CppObjectLocks.precedeLock(&cacheUpdateLock); - PropertyLocks.precedeLock(&objcMsgLogLock); - CppObjectLocks.precedeLock(&objcMsgLogLock); - PropertyLocks.precedeLock(&AltHandlerDebugLock); - CppObjectLocks.precedeLock(&AltHandlerDebugLock); + PropertyAndCppObjectAndAssocLocksPrecedeLock(&classInitLock); + PropertyAndCppObjectAndAssocLocksPrecedeLock(&selLock); + PropertyAndCppObjectAndAssocLocksPrecedeLock(&cacheUpdateLock); + PropertyAndCppObjectAndAssocLocksPrecedeLock(&objcMsgLogLock); + PropertyAndCppObjectAndAssocLocksPrecedeLock(&AltHandlerDebugLock); + + SideTableLocksSucceedLocks(PropertyLocks); + SideTableLocksSucceedLocks(CppObjectLocks); + SideTableLocksSucceedLock(&AssociationsManagerLock); + PropertyLocks.precedeLock(&AssociationsManagerLock); CppObjectLocks.precedeLock(&AssociationsManagerLock); - // fixme side table #if __OBJC2__ lockdebug_lock_precedes_lock(&classInitLock, &runtimeLock); @@ -707,6 +739,7 @@ static void defineLockOrder() // Runtime operations may occur inside SideTable locks // (such as storeWeak calling getMethodImplementation) SideTableLocksPrecedeLock(&runtimeLock); + SideTableLocksPrecedeLock(&classInitLock); // Some operations may occur inside runtimeLock. lockdebug_lock_precedes_lock(&runtimeLock, &selLock); lockdebug_lock_precedes_lock(&runtimeLock, &cacheUpdateLock); @@ -715,6 +748,7 @@ static void defineLockOrder() // Runtime operations may occur inside SideTable locks // (such as storeWeak calling getMethodImplementation) SideTableLocksPrecedeLock(&methodListLock); + SideTableLocksPrecedeLock(&classInitLock); // Method lookup and fixup. lockdebug_lock_precedes_lock(&methodListLock, &classLock); lockdebug_lock_precedes_lock(&methodListLock, &selLock); @@ -730,11 +764,15 @@ static void defineLockOrder() StructLocks.defineLockOrder(); CppObjectLocks.defineLockOrder(); } -// DEBUG +// LOCKDEBUG #endif +static bool ForkIsMultithreaded; void _objc_atfork_prepare() { + // Save threaded-ness for the child's use. + ForkIsMultithreaded = pthread_is_threaded_np(); + lockdebug_assert_no_locks_locked(); lockdebug_setInForkPrepare(true); @@ -795,6 +833,11 @@ void _objc_atfork_parent() void _objc_atfork_child() { + // Turn on +initialize fork safety enforcement if applicable. + if (ForkIsMultithreaded && !DisableInitializeForkSafety) { + MultithreadedForkChild = true; + } + lockdebug_assert_all_locks_locked(); CppObjectLocks.forceResetAll(); diff --git a/runtime/objc-private.h b/runtime/objc-private.h index b34b591..aac2d51 100644 --- a/runtime/objc-private.h +++ b/runtime/objc-private.h @@ -603,14 +603,6 @@ class monitor_locker_t : nocopy_t { ~monitor_locker_t() { lock.leave(); } }; -class mutex_locker_t : nocopy_t { - mutex_t& lock; - public: - mutex_locker_t(mutex_t& newLock) - : lock(newLock) { lock.lock(); } - ~mutex_locker_t() { lock.unlock(); } -}; - class recursive_mutex_locker_t : nocopy_t { recursive_mutex_t& lock; public: @@ -715,6 +707,8 @@ extern void layout_bitmap_print(layout_bitmap bits); // fixme runtime +extern bool MultithreadedForkChild; +extern id objc_noop_imp(id self, SEL _cmd); extern Class look_up_class(const char *aClassName, bool includeUnconnected, bool includeClassHandler); extern "C" void map_images(unsigned count, const char * const paths[], const struct mach_header * const mhdrs[]); @@ -920,6 +914,11 @@ class StripedMap { // assumes defineLockOrder is also called lockdebug_lock_precedes_lock(oldlock, &array[0].value); } + + const void *getLock(int i) { + if (i < StripeCount) return &array[i].value; + else return nil; + } #if DEBUG StripedMap() { diff --git a/runtime/objc-references.mm b/runtime/objc-references.mm index 20bf807..c1119f0 100644 --- a/runtime/objc-references.mm +++ b/runtime/objc-references.mm @@ -234,12 +234,14 @@ id _object_get_associative_reference(id object, void *key) { ObjcAssociation &entry = j->second; value = entry.value(); policy = entry.policy(); - if (policy & OBJC_ASSOCIATION_GETTER_RETAIN) ((id(*)(id, SEL))objc_msgSend)(value, SEL_retain); + if (policy & OBJC_ASSOCIATION_GETTER_RETAIN) { + objc_retain(value); + } } } } if (value && (policy & OBJC_ASSOCIATION_GETTER_AUTORELEASE)) { - ((id(*)(id, SEL))objc_msgSend)(value, SEL_autorelease); + objc_autorelease(value); } return value; } @@ -247,7 +249,7 @@ id _object_get_associative_reference(id object, void *key) { static id acquireValue(id value, uintptr_t policy) { switch (policy & 0xFF) { case OBJC_ASSOCIATION_SETTER_RETAIN: - return ((id(*)(id, SEL))objc_msgSend)(value, SEL_retain); + return objc_retain(value); case OBJC_ASSOCIATION_SETTER_COPY: return ((id(*)(id, SEL))objc_msgSend)(value, SEL_copy); } @@ -256,7 +258,7 @@ static id acquireValue(id value, uintptr_t policy) { static void releaseValue(id value, uintptr_t policy) { if (policy & OBJC_ASSOCIATION_SETTER_RETAIN) { - ((id(*)(id, SEL))objc_msgSend)(value, SEL_release); + return objc_release(value); } } diff --git a/runtime/objc-runtime-new.mm b/runtime/objc-runtime-new.mm index 37b2f6f..8ad820c 100644 --- a/runtime/objc-runtime-new.mm +++ b/runtime/objc-runtime-new.mm @@ -60,10 +60,6 @@ static bool MetaclassNSObjectAWZSwizzled; static bool ClassNSObjectRRSwizzled; -id objc_noop_imp(id self, SEL _cmd __unused) { - return self; -} - /*********************************************************************** * Lock management @@ -96,6 +92,13 @@ void lock_init(void) } +/*********************************************************************** +* Class structure decoding +**********************************************************************/ + +const uintptr_t objc_debug_class_rw_data_mask = FAST_DATA_MASK; + + /*********************************************************************** * Non-pointer isa decoding **********************************************************************/ @@ -4654,7 +4657,7 @@ IMP lookUpImpOrForward(Class cls, SEL sel, id inst, // Try superclass caches and method lists. { unsigned attempts = unreasonableClassCount(); - for (Class curClass = cls; + for (Class curClass = cls->superclass; curClass != nil; curClass = curClass->superclass) { diff --git a/runtime/objc-runtime-old.h b/runtime/objc-runtime-old.h index 5f0ff02..d8f966b 100644 --- a/runtime/objc-runtime-old.h +++ b/runtime/objc-runtime-old.h @@ -300,6 +300,14 @@ struct objc_class : objc_object { const char *mangledName() { return name; } const char *demangledName() { return name; } const char *nameForLogging() { return name; } + + bool isRootClass() { + return superclass == nil; + } + + bool isRootMetaclass() { + return ISA() == (Class)this; + } bool isMetaClass() { return info & CLS_META; diff --git a/runtime/objc-runtime.mm b/runtime/objc-runtime.mm index e9efe55..ba1b38d 100644 --- a/runtime/objc-runtime.mm +++ b/runtime/objc-runtime.mm @@ -94,6 +94,19 @@ int HeaderCount = 0; +// Set to true on the child side of fork() +// if the parent process was multithreaded when fork() was called. +bool MultithreadedForkChild = false; + + +/*********************************************************************** +* objc_noop_imp. Used when we need to install a do-nothing method somewhere. +**********************************************************************/ +id objc_noop_imp(id self, SEL _cmd __unused) { + return self; +} + + /*********************************************************************** * objc_getClass. Return the id of the named class. If the class does * not exist, call _objc_classLoader and then objc_classHandler, either of diff --git a/runtime/objc-sel-table.s b/runtime/objc-sel-table.s index 359df9b..c4dd405 100644 --- a/runtime/objc-sel-table.s +++ b/runtime/objc-sel-table.s @@ -21,24 +21,31 @@ __objc_opt_data: .space PAGE_MAX_SIZE-28 /* space for selopt, smax/capacity=524288, blen/mask=262143+1 */ -.space 262144 /* mask tab */ -.space 524288 /* checkbytes */ -.space 524288*4 /* offsets */ +.space 4*(8+256) /* header and scramble */ +.space 262144 /* mask tab */ +.space 524288 /* checkbytes */ +.space 524288*4 /* offsets */ /* space for clsopt, smax/capacity=65536, blen/mask=16383+1 */ +.space 4*(8+256) /* header and scramble */ .space 16384 /* mask tab */ .space 65536 /* checkbytes */ .space 65536*12 /* offsets to name and class and header_info */ -.space PAGE_MAX_SIZE /* some duplicate classes */ +.space 512*8 /* some duplicate classes */ -/* space for protocolopt, smax/capacity=8192, blen/mask=4095+1 */ -.space 4096 /* mask tab */ -.space 8192 /* checkbytes */ -.space 8192*4 /* offsets */ +/* space for some demangled protocol names */ +.space 1024 + +/* space for protocolopt, smax/capacity=16384, blen/mask=8191+1 */ +.space 4*(8+256) /* header and scramble */ +.space 8192 /* mask tab */ +.space 16384 /* checkbytes */ +.space 16384*8 /* offsets */ /* space for header_info (RO) structures */ .space 16384 + .section __DATA,__objc_opt_rw .align 3 .private_extern __objc_opt_rw_data @@ -46,11 +53,11 @@ __objc_opt_rw_data: /* space for header_info (RW) structures */ .space 16384 -/* space for 8192 protocols */ +/* space for 16384 protocols */ #if __LP64__ -.space 8192 * 11 * 8 +.space 16384 * 12 * 8 #else -.space 8192 * 11 * 4 +.space 16384 * 12 * 4 #endif diff --git a/runtime/objc-sync.h b/runtime/objc-sync.h index 93a96fc..e9ab64f 100644 --- a/runtime/objc-sync.h +++ b/runtime/objc-sync.h @@ -35,33 +35,25 @@ * * @return OBJC_SYNC_SUCCESS once lock is acquired. */ -OBJC_EXPORT int objc_sync_enter(id obj) - OBJC_AVAILABLE(10.3, 2.0, 9.0, 1.0); +OBJC_EXPORT int +objc_sync_enter(id _Nonnull obj) + OBJC_AVAILABLE(10.3, 2.0, 9.0, 1.0, 2.0); /** * End synchronizing on 'obj'. * - * @param obj The objet to end synchronizing on. + * @param obj The object to end synchronizing on. * * @return OBJC_SYNC_SUCCESS or OBJC_SYNC_NOT_OWNING_THREAD_ERROR */ -OBJC_EXPORT int objc_sync_exit(id obj) - OBJC_AVAILABLE(10.3, 2.0, 9.0, 1.0); - -// The wait/notify functions have never worked correctly and no longer exist. -OBJC_EXPORT int objc_sync_wait(id obj, long long milliSecondsMaxWait) - UNAVAILABLE_ATTRIBUTE; -OBJC_EXPORT int objc_sync_notify(id obj) - UNAVAILABLE_ATTRIBUTE; -OBJC_EXPORT int objc_sync_notifyAll(id obj) - UNAVAILABLE_ATTRIBUTE; +OBJC_EXPORT int +objc_sync_exit(id _Nonnull obj) + OBJC_AVAILABLE(10.3, 2.0, 9.0, 1.0, 2.0); enum { - OBJC_SYNC_SUCCESS = 0, - OBJC_SYNC_NOT_OWNING_THREAD_ERROR = -1, - OBJC_SYNC_TIMED_OUT = -2, - OBJC_SYNC_NOT_INITIALIZED = -3 + OBJC_SYNC_SUCCESS = 0, + OBJC_SYNC_NOT_OWNING_THREAD_ERROR = -1 }; -#endif // __OBJC_SNYC_H_ +#endif // __OBJC_SYNC_H_ diff --git a/runtime/objc.h b/runtime/objc.h index 6cf776b..5064016 100644 --- a/runtime/objc.h +++ b/runtime/objc.h @@ -39,7 +39,7 @@ typedef struct objc_class *Class; /// Represents an instance of a class. struct objc_object { - Class isa OBJC_ISA_AVAILABILITY; + Class _Nonnull isa OBJC_ISA_AVAILABILITY; }; /// A pointer to an instance of a class. @@ -53,7 +53,7 @@ typedef struct objc_selector *SEL; #if !OBJC_OLD_DISPATCH_PROTOTYPES typedef void (*IMP)(void /* id, SEL, ... */ ); #else -typedef id (*IMP)(id, SEL, ...); +typedef id _Nullable (*IMP)(id _Nonnull, SEL _Nonnull, ...); #endif /// Type to represent a boolean value. @@ -135,8 +135,8 @@ typedef id (*IMP)(id, SEL, ...); * * @return A C string indicating the name of the selector. */ -OBJC_EXPORT const char *sel_getName(SEL sel) - OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0); +OBJC_EXPORT const char * _Nonnull sel_getName(SEL _Nonnull sel) + OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0); /** * Registers a method with the Objective-C runtime system, maps the method @@ -150,8 +150,8 @@ OBJC_EXPORT const char *sel_getName(SEL sel) * method’s selector before you can add the method to a class definition. If the method name * has already been registered, this function simply returns the selector. */ -OBJC_EXPORT SEL sel_registerName(const char *str) - OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0); +OBJC_EXPORT SEL _Nonnull sel_registerName(const char * _Nonnull str) + OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0); /** * Returns the class name of a given object. @@ -160,8 +160,8 @@ OBJC_EXPORT SEL sel_registerName(const char *str) * * @return The name of the class of which \e obj is an instance. */ -OBJC_EXPORT const char *object_getClassName(id obj) - OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0); +OBJC_EXPORT const char * _Nonnull object_getClassName(id _Nullable obj) + OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0); /** * Returns a pointer to any extra bytes allocated with an instance given object. @@ -179,8 +179,8 @@ OBJC_EXPORT const char *object_getClassName(id obj) * guaranteed, even if the area following the object's last ivar is more aligned than that. * @note In a garbage-collected environment, the memory is scanned conservatively. */ -OBJC_EXPORT void *object_getIndexedIvars(id obj) - OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0) +OBJC_EXPORT void * _Nullable object_getIndexedIvars(id _Nullable obj) + OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0) OBJC_ARC_UNAVAILABLE; /** @@ -193,8 +193,8 @@ OBJC_EXPORT void *object_getIndexedIvars(id obj) * @warning On some platforms, an invalid reference (to invalid memory addresses) can cause * a crash. */ -OBJC_EXPORT BOOL sel_isMapped(SEL sel) - OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0); +OBJC_EXPORT BOOL sel_isMapped(SEL _Nonnull sel) + OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0); /** * Registers a method name with the Objective-C runtime system. @@ -208,19 +208,19 @@ OBJC_EXPORT BOOL sel_isMapped(SEL sel) * and returned \c NULL if the selector was not found. This was changed for safety, because it was * observed that many of the callers of this function did not check the return value for \c NULL. */ -OBJC_EXPORT SEL sel_getUid(const char *str) - OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0); +OBJC_EXPORT SEL _Nonnull sel_getUid(const char * _Nonnull str) + OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0); typedef const void* objc_objectptr_t; // Obsolete ARC conversions. -OBJC_EXPORT id objc_retainedObject(objc_objectptr_t obj) +OBJC_EXPORT id _Nullable objc_retainedObject(objc_objectptr_t _Nullable obj) OBJC_UNAVAILABLE("use CFBridgingRelease() or a (__bridge_transfer id) cast instead"); -OBJC_EXPORT id objc_unretainedObject(objc_objectptr_t obj) +OBJC_EXPORT id _Nullable objc_unretainedObject(objc_objectptr_t _Nullable obj) OBJC_UNAVAILABLE("use a (__bridge id) cast instead"); -OBJC_EXPORT objc_objectptr_t objc_unretainedPointer(id obj) +OBJC_EXPORT objc_objectptr_t _Nullable objc_unretainedPointer(id _Nullable obj) OBJC_UNAVAILABLE("use a __bridge cast instead"); diff --git a/runtime/runtime.h b/runtime/runtime.h index 8ed2f43..ad68c90 100644 --- a/runtime/runtime.h +++ b/runtime/runtime.h @@ -53,18 +53,18 @@ typedef struct objc_category *Category; typedef struct objc_property *objc_property_t; struct objc_class { - Class isa OBJC_ISA_AVAILABILITY; + Class _Nonnull isa OBJC_ISA_AVAILABILITY; #if !__OBJC2__ - Class super_class OBJC2_UNAVAILABLE; - const char *name OBJC2_UNAVAILABLE; + Class _Nullable super_class OBJC2_UNAVAILABLE; + const char * _Nonnull name OBJC2_UNAVAILABLE; long version OBJC2_UNAVAILABLE; long info OBJC2_UNAVAILABLE; long instance_size OBJC2_UNAVAILABLE; - struct objc_ivar_list *ivars OBJC2_UNAVAILABLE; - struct objc_method_list **methodLists OBJC2_UNAVAILABLE; - struct objc_cache *cache OBJC2_UNAVAILABLE; - struct objc_protocol_list *protocols OBJC2_UNAVAILABLE; + struct objc_ivar_list * _Nullable ivars OBJC2_UNAVAILABLE; + struct objc_method_list * _Nullable * _Nullable methodLists OBJC2_UNAVAILABLE; + struct objc_cache * _Nonnull cache OBJC2_UNAVAILABLE; + struct objc_protocol_list * _Nullable protocols OBJC2_UNAVAILABLE; #endif } OBJC2_UNAVAILABLE; @@ -80,14 +80,14 @@ typedef struct objc_object Protocol; /// Defines a method struct objc_method_description { - SEL name; /**< The name of the method */ - char *types; /**< The types of the method arguments */ + SEL _Nullable name; /**< The name of the method */ + char * _Nullable types; /**< The types of the method arguments */ }; /// Defines a property attribute typedef struct { - const char *name; /**< The name of the attribute */ - const char *value; /**< The value of the attribute (usually empty) */ + const char * _Nonnull name; /**< The name of the attribute */ + const char * _Nonnull value; /**< The value of the attribute (usually empty) */ } objc_property_attribute_t; @@ -103,8 +103,8 @@ typedef struct { * * @return A copy of \e obj. */ -OBJC_EXPORT id object_copy(id obj, size_t size) - OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0) +OBJC_EXPORT id _Nullable object_copy(id _Nullable obj, size_t size) + OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0) OBJC_ARC_UNAVAILABLE; /** @@ -114,8 +114,9 @@ OBJC_EXPORT id object_copy(id obj, size_t size) * * @return nil */ -OBJC_EXPORT id object_dispose(id obj) - OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0) +OBJC_EXPORT id _Nullable +object_dispose(id _Nullable obj) + OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0) OBJC_ARC_UNAVAILABLE; /** @@ -126,8 +127,9 @@ OBJC_EXPORT id object_dispose(id obj) * @return The class object of which \e object is an instance, * or \c Nil if \e object is \c nil. */ -OBJC_EXPORT Class object_getClass(id obj) - OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0); +OBJC_EXPORT Class _Nullable +object_getClass(id _Nullable obj) + OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0); /** * Sets the class of an object. @@ -137,8 +139,9 @@ OBJC_EXPORT Class object_getClass(id obj) * * @return The previous value of \e object's class, or \c Nil if \e object is \c nil. */ -OBJC_EXPORT Class object_setClass(id obj, Class cls) - OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0); +OBJC_EXPORT Class _Nullable +object_setClass(id _Nullable obj, Class _Nonnull cls) + OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0); /** @@ -148,8 +151,9 @@ OBJC_EXPORT Class object_setClass(id obj, Class cls) * * @return true if the object is a class or metaclass, false otherwise. */ -OBJC_EXPORT BOOL object_isClass(id obj) - OBJC_AVAILABLE(10.10, 8.0, 9.0, 1.0); +OBJC_EXPORT BOOL +object_isClass(id _Nullable obj) + OBJC_AVAILABLE(10.10, 8.0, 9.0, 1.0, 2.0); /** @@ -163,8 +167,9 @@ OBJC_EXPORT BOOL object_isClass(id obj) * @note \c object_getIvar is faster than \c object_getInstanceVariable if the Ivar * for the instance variable is already known. */ -OBJC_EXPORT id object_getIvar(id obj, Ivar ivar) - OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0); +OBJC_EXPORT id _Nullable +object_getIvar(id _Nullable obj, Ivar _Nonnull ivar) + OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0); /** * Sets the value of an instance variable in an object. @@ -179,8 +184,9 @@ OBJC_EXPORT id object_getIvar(id obj, Ivar ivar) * @note \c object_setIvar is faster than \c object_setInstanceVariable if the Ivar * for the instance variable is already known. */ -OBJC_EXPORT void object_setIvar(id obj, Ivar ivar, id value) - OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0); +OBJC_EXPORT void +object_setIvar(id _Nullable obj, Ivar _Nonnull ivar, id _Nullable value) + OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0); /** * Sets the value of an instance variable in an object. @@ -195,8 +201,10 @@ OBJC_EXPORT void object_setIvar(id obj, Ivar ivar, id value) * @note \c object_setIvar is faster than \c object_setInstanceVariable if the Ivar * for the instance variable is already known. */ -OBJC_EXPORT void object_setIvarWithStrongDefault(id obj, Ivar ivar, id value) - OBJC_AVAILABLE(10.12, 10.0, 10.0, 3.0); +OBJC_EXPORT void +object_setIvarWithStrongDefault(id _Nullable obj, Ivar _Nonnull ivar, + id _Nullable value) + OBJC_AVAILABLE(10.12, 10.0, 10.0, 3.0, 2.0); /** * Changes the value of an instance variable of a class instance. @@ -213,8 +221,10 @@ OBJC_EXPORT void object_setIvarWithStrongDefault(id obj, Ivar ivar, id value) * use that memory management. Instance variables with unknown memory management * are assigned as if they were unsafe_unretained. */ -OBJC_EXPORT Ivar object_setInstanceVariable(id obj, const char *name, void *value) - OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0) +OBJC_EXPORT Ivar _Nullable +object_setInstanceVariable(id _Nullable obj, const char * _Nonnull name, + void * _Nullable value) + OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0) OBJC_ARC_UNAVAILABLE; /** @@ -232,8 +242,11 @@ OBJC_EXPORT Ivar object_setInstanceVariable(id obj, const char *name, void *valu * use that memory management. Instance variables with unknown memory management * are assigned as if they were strong. */ -OBJC_EXPORT Ivar object_setInstanceVariableWithStrongDefault(id obj, const char *name, void *value) - OBJC_AVAILABLE(10.12, 10.0, 10.0, 3.0) +OBJC_EXPORT Ivar _Nullable +object_setInstanceVariableWithStrongDefault(id _Nullable obj, + const char * _Nonnull name, + void * _Nullable value) + OBJC_AVAILABLE(10.12, 10.0, 10.0, 3.0, 2.0) OBJC_ARC_UNAVAILABLE; /** @@ -247,8 +260,10 @@ OBJC_EXPORT Ivar object_setInstanceVariableWithStrongDefault(id obj, const char * @return A pointer to the \c Ivar data structure that defines the type and name of * the instance variable specified by \e name. */ -OBJC_EXPORT Ivar object_getInstanceVariable(id obj, const char *name, void **outValue) - OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0) +OBJC_EXPORT Ivar _Nullable +object_getInstanceVariable(id _Nullable obj, const char * _Nonnull name, + void * _Nullable * _Nullable outValue) + OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0) OBJC_ARC_UNAVAILABLE; @@ -270,8 +285,9 @@ OBJC_EXPORT Ivar object_getInstanceVariable(id obj, const char *name, void **out * @warning Earlier implementations of this function (prior to OS X v10.0) * terminate the program if the class does not exist. */ -OBJC_EXPORT Class objc_getClass(const char *name) - OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0); +OBJC_EXPORT Class _Nullable +objc_getClass(const char * _Nonnull name) + OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0); /** * Returns the metaclass definition of a specified class. @@ -286,8 +302,9 @@ OBJC_EXPORT Class objc_getClass(const char *name) * definition must have a valid metaclass definition, and so the metaclass definition is always returned, * whether it’s valid or not. */ -OBJC_EXPORT Class objc_getMetaClass(const char *name) - OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0); +OBJC_EXPORT Class _Nullable +objc_getMetaClass(const char * _Nonnull name) + OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0); /** * Returns the class definition of a specified class. @@ -301,8 +318,9 @@ OBJC_EXPORT Class objc_getMetaClass(const char *name) * registered, \c objc_getClass calls the class handler callback and then checks a second * time to see whether the class is registered. This function does not call the class handler callback. */ -OBJC_EXPORT Class objc_lookUpClass(const char *name) - OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0); +OBJC_EXPORT Class _Nullable +objc_lookUpClass(const char * _Nonnull name) + OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0); /** * Returns the class definition of a specified class. @@ -314,8 +332,9 @@ OBJC_EXPORT Class objc_lookUpClass(const char *name) * @note This function is the same as \c objc_getClass, but kills the process if the class is not found. * @note This function is used by ZeroLink, where failing to find a class would be a compile-time link error without ZeroLink. */ -OBJC_EXPORT Class objc_getRequiredClass(const char *name) - OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0); +OBJC_EXPORT Class _Nonnull +objc_getRequiredClass(const char * _Nonnull name) + OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0); /** * Obtains the list of registered class definitions. @@ -336,8 +355,9 @@ OBJC_EXPORT Class objc_getRequiredClass(const char *name) * @warning You cannot assume that class objects you get from this function are classes that inherit from \c NSObject, * so you cannot safely call any methods on such classes without detecting that the method is implemented first. */ -OBJC_EXPORT int objc_getClassList(Class *buffer, int bufferCount) - OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0); +OBJC_EXPORT int +objc_getClassList(Class _Nonnull * _Nullable buffer, int bufferCount) + OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0); /** * Creates and returns a list of pointers to all registered class definitions. @@ -349,8 +369,9 @@ OBJC_EXPORT int objc_getClassList(Class *buffer, int bufferCount) * * @see objc_getClassList */ -OBJC_EXPORT Class *objc_copyClassList(unsigned int *outCount) - OBJC_AVAILABLE(10.7, 3.1, 9.0, 1.0); +OBJC_EXPORT Class _Nonnull * _Nullable +objc_copyClassList(unsigned int * _Nullable outCount) + OBJC_AVAILABLE(10.7, 3.1, 9.0, 1.0, 2.0); /* Working with Classes */ @@ -362,8 +383,9 @@ OBJC_EXPORT Class *objc_copyClassList(unsigned int *outCount) * * @return The name of the class, or the empty string if \e cls is \c Nil. */ -OBJC_EXPORT const char *class_getName(Class cls) - OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0); +OBJC_EXPORT const char * _Nonnull +class_getName(Class _Nullable cls) + OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0); /** * Returns a Boolean value that indicates whether a class object is a metaclass. @@ -373,8 +395,9 @@ OBJC_EXPORT const char *class_getName(Class cls) * @return \c YES if \e cls is a metaclass, \c NO if \e cls is a non-meta class, * \c NO if \e cls is \c Nil. */ -OBJC_EXPORT BOOL class_isMetaClass(Class cls) - OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0); +OBJC_EXPORT BOOL +class_isMetaClass(Class _Nullable cls) + OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0); /** * Returns the superclass of a class. @@ -386,8 +409,9 @@ OBJC_EXPORT BOOL class_isMetaClass(Class cls) * * @note You should usually use \c NSObject's \c superclass method instead of this function. */ -OBJC_EXPORT Class class_getSuperclass(Class cls) - OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0); +OBJC_EXPORT Class _Nullable +class_getSuperclass(Class _Nullable cls) + OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0); /** * Sets the superclass of a given class. @@ -399,11 +423,13 @@ OBJC_EXPORT Class class_getSuperclass(Class cls) * * @warning You should not use this function. */ -OBJC_EXPORT Class class_setSuperclass(Class cls, Class newSuper) +OBJC_EXPORT Class _Nonnull +class_setSuperclass(Class _Nonnull cls, Class _Nonnull newSuper) __OSX_DEPRECATED(10.5, 10.5, "not recommended") __IOS_DEPRECATED(2.0, 2.0, "not recommended") __TVOS_DEPRECATED(9.0, 9.0, "not recommended") - __WATCHOS_DEPRECATED(1.0, 1.0, "not recommended"); + __WATCHOS_DEPRECATED(1.0, 1.0, "not recommended") + __BRIDGEOS_DEPRECATED(2.0, 2.0, "not recommended"); /** * Returns the version number of a class definition. @@ -415,8 +441,9 @@ OBJC_EXPORT Class class_setSuperclass(Class cls, Class newSuper) * * @see class_setVersion */ -OBJC_EXPORT int class_getVersion(Class cls) - OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0); +OBJC_EXPORT int +class_getVersion(Class _Nullable cls) + OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0); /** * Sets the version number of a class definition. @@ -432,8 +459,9 @@ OBJC_EXPORT int class_getVersion(Class cls) * @note Classes derived from the Foundation framework \c NSObject class can set the class-definition * version number using the \c setVersion: class method, which is implemented using the \c class_setVersion function. */ -OBJC_EXPORT void class_setVersion(Class cls, int version) - OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0); +OBJC_EXPORT void +class_setVersion(Class _Nullable cls, int version) + OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0); /** * Returns the size of instances of a class. @@ -442,8 +470,9 @@ OBJC_EXPORT void class_setVersion(Class cls, int version) * * @return The size in bytes of instances of the class \e cls, or \c 0 if \e cls is \c Nil. */ -OBJC_EXPORT size_t class_getInstanceSize(Class cls) - OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0); +OBJC_EXPORT size_t +class_getInstanceSize(Class _Nullable cls) + OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0); /** * Returns the \c Ivar for a specified instance variable of a given class. @@ -454,8 +483,9 @@ OBJC_EXPORT size_t class_getInstanceSize(Class cls) * @return A pointer to an \c Ivar data structure containing information about * the instance variable specified by \e name. */ -OBJC_EXPORT Ivar class_getInstanceVariable(Class cls, const char *name) - OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0); +OBJC_EXPORT Ivar _Nullable +class_getInstanceVariable(Class _Nullable cls, const char * _Nonnull name) + OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0); /** * Returns the Ivar for a specified class variable of a given class. @@ -465,8 +495,9 @@ OBJC_EXPORT Ivar class_getInstanceVariable(Class cls, const char *name) * * @return A pointer to an \c Ivar data structure containing information about the class variable specified by \e name. */ -OBJC_EXPORT Ivar class_getClassVariable(Class cls, const char *name) - OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0); +OBJC_EXPORT Ivar _Nullable +class_getClassVariable(Class _Nullable cls, const char * _Nonnull name) + OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0); /** * Describes the instance variables declared by a class. @@ -481,8 +512,9 @@ OBJC_EXPORT Ivar class_getClassVariable(Class cls, const char *name) * * If the class declares no instance variables, or cls is Nil, NULL is returned and *outCount is 0. */ -OBJC_EXPORT Ivar *class_copyIvarList(Class cls, unsigned int *outCount) - OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0); +OBJC_EXPORT Ivar _Nonnull * _Nullable +class_copyIvarList(Class _Nullable cls, unsigned int * _Nullable outCount) + OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0); /** * Returns a specified instance method for a given class. @@ -496,8 +528,9 @@ OBJC_EXPORT Ivar *class_copyIvarList(Class cls, unsigned int *outCount) * * @note This function searches superclasses for implementations, whereas \c class_copyMethodList does not. */ -OBJC_EXPORT Method class_getInstanceMethod(Class cls, SEL name) - OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0); +OBJC_EXPORT Method _Nullable +class_getInstanceMethod(Class _Nullable cls, SEL _Nonnull name) + OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0); /** * Returns a pointer to the data structure describing a given class method for a given class. @@ -512,8 +545,9 @@ OBJC_EXPORT Method class_getInstanceMethod(Class cls, SEL name) * @note Note that this function searches superclasses for implementations, * whereas \c class_copyMethodList does not. */ -OBJC_EXPORT Method class_getClassMethod(Class cls, SEL name) - OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0); +OBJC_EXPORT Method _Nullable +class_getClassMethod(Class _Nullable cls, SEL _Nonnull name) + OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0); /** * Returns the function pointer that would be called if a @@ -530,8 +564,9 @@ OBJC_EXPORT Method class_getClassMethod(Class cls, SEL name) * an actual method implementation. For example, if instances of the class do not respond to * the selector, the function pointer returned will be part of the runtime's message forwarding machinery. */ -OBJC_EXPORT IMP class_getMethodImplementation(Class cls, SEL name) - OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0); +OBJC_EXPORT IMP _Nullable +class_getMethodImplementation(Class _Nullable cls, SEL _Nonnull name) + OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0); /** * Returns the function pointer that would be called if a particular @@ -543,8 +578,9 @@ OBJC_EXPORT IMP class_getMethodImplementation(Class cls, SEL name) * @return The function pointer that would be called if \c [object name] were called * with an instance of the class, or \c NULL if \e cls is \c Nil. */ -OBJC_EXPORT IMP class_getMethodImplementation_stret(Class cls, SEL name) - OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0) +OBJC_EXPORT IMP _Nullable +class_getMethodImplementation_stret(Class _Nullable cls, SEL _Nonnull name) + OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0) OBJC_ARM64_UNAVAILABLE; /** @@ -558,8 +594,9 @@ OBJC_EXPORT IMP class_getMethodImplementation_stret(Class cls, SEL name) * @note You should usually use \c NSObject's \c respondsToSelector: or \c instancesRespondToSelector: * methods instead of this function. */ -OBJC_EXPORT BOOL class_respondsToSelector(Class cls, SEL sel) - OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0); +OBJC_EXPORT BOOL +class_respondsToSelector(Class _Nullable cls, SEL _Nonnull sel) + OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0); /** * Describes the instance methods implemented by a class. @@ -578,8 +615,9 @@ OBJC_EXPORT BOOL class_respondsToSelector(Class cls, SEL sel) * @note To get the implementations of methods that may be implemented by superclasses, * use \c class_getInstanceMethod or \c class_getClassMethod. */ -OBJC_EXPORT Method *class_copyMethodList(Class cls, unsigned int *outCount) - OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0); +OBJC_EXPORT Method _Nonnull * _Nullable +class_copyMethodList(Class _Nullable cls, unsigned int * _Nullable outCount) + OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0); /** * Returns a Boolean value that indicates whether a class conforms to a given protocol. @@ -591,8 +629,9 @@ OBJC_EXPORT Method *class_copyMethodList(Class cls, unsigned int *outCount) * * @note You should usually use NSObject's conformsToProtocol: method instead of this function. */ -OBJC_EXPORT BOOL class_conformsToProtocol(Class cls, Protocol *protocol) - OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0); +OBJC_EXPORT BOOL +class_conformsToProtocol(Class _Nullable cls, Protocol * _Nullable protocol) + OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0); /** * Describes the protocols adopted by a class. @@ -607,8 +646,9 @@ OBJC_EXPORT BOOL class_conformsToProtocol(Class cls, Protocol *protocol) * * If cls adopts no protocols, or cls is Nil, returns NULL and *outCount is 0. */ -OBJC_EXPORT Protocol * __unsafe_unretained *class_copyProtocolList(Class cls, unsigned int *outCount) - OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0); +OBJC_EXPORT Protocol * __unsafe_unretained _Nonnull * _Nullable +class_copyProtocolList(Class _Nullable cls, unsigned int * _Nullable outCount) + OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0); /** * Returns a property with a given name of a given class. @@ -620,8 +660,9 @@ OBJC_EXPORT Protocol * __unsafe_unretained *class_copyProtocolList(Class cls, un * \c NULL if the class does not declare a property with that name, * or \c NULL if \e cls is \c Nil. */ -OBJC_EXPORT objc_property_t class_getProperty(Class cls, const char *name) - OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0); +OBJC_EXPORT objc_property_t _Nullable +class_getProperty(Class _Nullable cls, const char * _Nonnull name) + OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0); /** * Describes the properties declared by a class. @@ -636,8 +677,9 @@ OBJC_EXPORT objc_property_t class_getProperty(Class cls, const char *name) * * If \e cls declares no properties, or \e cls is \c Nil, returns \c NULL and \c *outCount is \c 0. */ -OBJC_EXPORT objc_property_t *class_copyPropertyList(Class cls, unsigned int *outCount) - OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0); +OBJC_EXPORT objc_property_t _Nonnull * _Nullable +class_copyPropertyList(Class _Nullable cls, unsigned int * _Nullable outCount) + OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0); /** * Returns a description of the \c Ivar layout for a given class. @@ -646,8 +688,9 @@ OBJC_EXPORT objc_property_t *class_copyPropertyList(Class cls, unsigned int *out * * @return A description of the \c Ivar layout for \e cls. */ -OBJC_EXPORT const uint8_t *class_getIvarLayout(Class cls) - OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0); +OBJC_EXPORT const uint8_t * _Nullable +class_getIvarLayout(Class _Nullable cls) + OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0); /** * Returns a description of the layout of weak Ivars for a given class. @@ -656,8 +699,9 @@ OBJC_EXPORT const uint8_t *class_getIvarLayout(Class cls) * * @return A description of the layout of the weak \c Ivars for \e cls. */ -OBJC_EXPORT const uint8_t *class_getWeakIvarLayout(Class cls) - OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0); +OBJC_EXPORT const uint8_t * _Nullable +class_getWeakIvarLayout(Class _Nullable cls) + OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0); /** * Adds a new method to a class with a given name and implementation. @@ -674,9 +718,10 @@ OBJC_EXPORT const uint8_t *class_getWeakIvarLayout(Class cls) * but will not replace an existing implementation in this class. * To change an existing implementation, use method_setImplementation. */ -OBJC_EXPORT BOOL class_addMethod(Class cls, SEL name, IMP imp, - const char *types) - OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0); +OBJC_EXPORT BOOL +class_addMethod(Class _Nullable cls, SEL _Nonnull name, IMP _Nonnull imp, + const char * _Nullable types) + OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0); /** * Replaces the implementation of a method for a given class. @@ -696,9 +741,10 @@ OBJC_EXPORT BOOL class_addMethod(Class cls, SEL name, IMP imp, * - If the method identified by \e name does exist, its \c IMP is replaced as if \c method_setImplementation were called. * The type encoding specified by \e types is ignored. */ -OBJC_EXPORT IMP class_replaceMethod(Class cls, SEL name, IMP imp, - const char *types) - OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0); +OBJC_EXPORT IMP _Nullable +class_replaceMethod(Class _Nullable cls, SEL _Nonnull name, IMP _Nonnull imp, + const char * _Nullable types) + OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0); /** * Adds a new instance variable to a class. @@ -713,9 +759,10 @@ OBJC_EXPORT IMP class_replaceMethod(Class cls, SEL name, IMP imp, * variable depends on the ivar's type and the machine architecture. * For variables of any pointer type, pass log2(sizeof(pointer_type)). */ -OBJC_EXPORT BOOL class_addIvar(Class cls, const char *name, size_t size, - uint8_t alignment, const char *types) - OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0); +OBJC_EXPORT BOOL +class_addIvar(Class _Nullable cls, const char * _Nonnull name, size_t size, + uint8_t alignment, const char * _Nullable types) + OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0); /** * Adds a protocol to a class. @@ -726,8 +773,9 @@ OBJC_EXPORT BOOL class_addIvar(Class cls, const char *name, size_t size, * @return \c YES if the method was added successfully, otherwise \c NO * (for example, the class already conforms to that protocol). */ -OBJC_EXPORT BOOL class_addProtocol(Class cls, Protocol *protocol) - OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0); +OBJC_EXPORT BOOL +class_addProtocol(Class _Nullable cls, Protocol * _Nonnull protocol) + OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0); /** * Adds a property to a class. @@ -740,8 +788,11 @@ OBJC_EXPORT BOOL class_addProtocol(Class cls, Protocol *protocol) * @return \c YES if the property was added successfully, otherwise \c NO * (for example, the class already has that property). */ -OBJC_EXPORT BOOL class_addProperty(Class cls, const char *name, const objc_property_attribute_t *attributes, unsigned int attributeCount) - OBJC_AVAILABLE(10.7, 4.3, 9.0, 1.0); +OBJC_EXPORT BOOL +class_addProperty(Class _Nullable cls, const char * _Nonnull name, + const objc_property_attribute_t * _Nullable attributes, + unsigned int attributeCount) + OBJC_AVAILABLE(10.7, 4.3, 9.0, 1.0, 2.0); /** * Replace a property of a class. @@ -751,8 +802,11 @@ OBJC_EXPORT BOOL class_addProperty(Class cls, const char *name, const objc_prope * @param attributes An array of property attributes. * @param attributeCount The number of attributes in \e attributes. */ -OBJC_EXPORT void class_replaceProperty(Class cls, const char *name, const objc_property_attribute_t *attributes, unsigned int attributeCount) - OBJC_AVAILABLE(10.7, 4.3, 9.0, 1.0); +OBJC_EXPORT void +class_replaceProperty(Class _Nullable cls, const char * _Nonnull name, + const objc_property_attribute_t * _Nullable attributes, + unsigned int attributeCount) + OBJC_AVAILABLE(10.7, 4.3, 9.0, 1.0, 2.0); /** * Sets the Ivar layout for a given class. @@ -760,8 +814,9 @@ OBJC_EXPORT void class_replaceProperty(Class cls, const char *name, const objc_p * @param cls The class to modify. * @param layout The layout of the \c Ivars for \e cls. */ -OBJC_EXPORT void class_setIvarLayout(Class cls, const uint8_t *layout) - OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0); +OBJC_EXPORT void +class_setIvarLayout(Class _Nullable cls, const uint8_t * _Nullable layout) + OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0); /** * Sets the layout for weak Ivars for a given class. @@ -769,8 +824,9 @@ OBJC_EXPORT void class_setIvarLayout(Class cls, const uint8_t *layout) * @param cls The class to modify. * @param layout The layout of the weak Ivars for \e cls. */ -OBJC_EXPORT void class_setWeakIvarLayout(Class cls, const uint8_t *layout) - OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0); +OBJC_EXPORT void +class_setWeakIvarLayout(Class _Nullable cls, const uint8_t * _Nullable layout) + OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0); /** * Used by CoreFoundation's toll-free bridging. @@ -782,8 +838,9 @@ OBJC_EXPORT void class_setWeakIvarLayout(Class cls, const uint8_t *layout) * * @warning Do not call this function yourself. */ -OBJC_EXPORT Class objc_getFutureClass(const char *name) - OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0) +OBJC_EXPORT Class _Nonnull +objc_getFutureClass(const char * _Nonnull name) + OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0) OBJC_ARC_UNAVAILABLE; @@ -800,9 +857,10 @@ OBJC_EXPORT Class objc_getFutureClass(const char *name) * * @return An instance of the class \e cls. */ -OBJC_EXPORT id class_createInstance(Class cls, size_t extraBytes) - OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0) - OBJC_ARC_UNAVAILABLE; +OBJC_EXPORT id _Nullable +class_createInstance(Class _Nullable cls, size_t extraBytes) + OBJC_RETURNS_RETAINED + OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0); /** * Creates an instance of a class at the specific location provided. @@ -817,8 +875,9 @@ OBJC_EXPORT id class_createInstance(Class cls, size_t extraBytes) * * @see class_createInstance */ -OBJC_EXPORT id objc_constructInstance(Class cls, void *bytes) - OBJC_AVAILABLE(10.6, 3.0, 9.0, 1.0) +OBJC_EXPORT id _Nullable +objc_constructInstance(Class _Nullable cls, void * _Nullable bytes) + OBJC_AVAILABLE(10.6, 3.0, 9.0, 1.0, 2.0) OBJC_ARC_UNAVAILABLE; /** @@ -831,8 +890,8 @@ OBJC_EXPORT id objc_constructInstance(Class cls, void *bytes) * * @note CF and other clients do call this under GC. */ -OBJC_EXPORT void *objc_destructInstance(id obj) - OBJC_AVAILABLE(10.6, 3.0, 9.0, 1.0) +OBJC_EXPORT void * _Nullable objc_destructInstance(id _Nullable obj) + OBJC_AVAILABLE(10.6, 3.0, 9.0, 1.0, 2.0) OBJC_ARC_UNAVAILABLE; @@ -855,25 +914,29 @@ OBJC_EXPORT void *objc_destructInstance(id obj) * @note Instance methods and instance variables should be added to the class itself. * Class methods should be added to the metaclass. */ -OBJC_EXPORT Class objc_allocateClassPair(Class superclass, const char *name, - size_t extraBytes) - OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0); +OBJC_EXPORT Class _Nullable +objc_allocateClassPair(Class _Nullable superclass, const char * _Nonnull name, + size_t extraBytes) + OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0); /** * Registers a class that was allocated using \c objc_allocateClassPair. * * @param cls The class you want to register. */ -OBJC_EXPORT void objc_registerClassPair(Class cls) - OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0); +OBJC_EXPORT void +objc_registerClassPair(Class _Nonnull cls) + OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0); /** * Used by Foundation's Key-Value Observing. * * @warning Do not call this function yourself. */ -OBJC_EXPORT Class objc_duplicateClass(Class original, const char *name, size_t extraBytes) - OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0); +OBJC_EXPORT Class _Nonnull +objc_duplicateClass(Class _Nonnull original, const char * _Nonnull name, + size_t extraBytes) + OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0); /** * Destroy a class and its associated metaclass. @@ -883,8 +946,9 @@ OBJC_EXPORT Class objc_duplicateClass(Class original, const char *name, size_t e * * @warning Do not call if instances of this class or a subclass exist. */ -OBJC_EXPORT void objc_disposeClassPair(Class cls) - OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0); +OBJC_EXPORT void +objc_disposeClassPair(Class _Nonnull cls) + OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0); /* Working with Methods */ @@ -898,8 +962,9 @@ OBJC_EXPORT void objc_disposeClassPair(Class cls) * * @note To get the method name as a C string, call \c sel_getName(method_getName(method)). */ -OBJC_EXPORT SEL method_getName(Method m) - OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0); +OBJC_EXPORT SEL _Nonnull +method_getName(Method _Nonnull m) + OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0); /** * Returns the implementation of a method. @@ -908,8 +973,9 @@ OBJC_EXPORT SEL method_getName(Method m) * * @return A function pointer of type IMP. */ -OBJC_EXPORT IMP method_getImplementation(Method m) - OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0); +OBJC_EXPORT IMP _Nonnull +method_getImplementation(Method _Nonnull m) + OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0); /** * Returns a string describing a method's parameter and return types. @@ -918,8 +984,9 @@ OBJC_EXPORT IMP method_getImplementation(Method m) * * @return A C string. The string may be \c NULL. */ -OBJC_EXPORT const char *method_getTypeEncoding(Method m) - OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0); +OBJC_EXPORT const char * _Nullable +method_getTypeEncoding(Method _Nonnull m) + OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0); /** * Returns the number of arguments accepted by a method. @@ -928,8 +995,9 @@ OBJC_EXPORT const char *method_getTypeEncoding(Method m) * * @return An integer containing the number of arguments accepted by the given method. */ -OBJC_EXPORT unsigned int method_getNumberOfArguments(Method m) - OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0); +OBJC_EXPORT unsigned int +method_getNumberOfArguments(Method _Nonnull m) + OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0); /** * Returns a string describing a method's return type. @@ -938,8 +1006,9 @@ OBJC_EXPORT unsigned int method_getNumberOfArguments(Method m) * * @return A C string describing the return type. You must free the string with \c free(). */ -OBJC_EXPORT char *method_copyReturnType(Method m) - OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0); +OBJC_EXPORT char * _Nonnull +method_copyReturnType(Method _Nonnull m) + OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0); /** * Returns a string describing a single parameter type of a method. @@ -950,8 +1019,9 @@ OBJC_EXPORT char *method_copyReturnType(Method m) * @return A C string describing the type of the parameter at index \e index, or \c NULL * if method has no parameter index \e index. You must free the string with \c free(). */ -OBJC_EXPORT char *method_copyArgumentType(Method m, unsigned int index) - OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0); +OBJC_EXPORT char * _Nullable +method_copyArgumentType(Method _Nonnull m, unsigned int index) + OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0); /** * Returns by reference a string describing a method's return type. @@ -963,8 +1033,9 @@ OBJC_EXPORT char *method_copyArgumentType(Method m, unsigned int index) * @note The method's return type string is copied to \e dst. * \e dst is filled as if \c strncpy(dst, parameter_type, dst_len) were called. */ -OBJC_EXPORT void method_getReturnType(Method m, char *dst, size_t dst_len) - OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0); +OBJC_EXPORT void +method_getReturnType(Method _Nonnull m, char * _Nonnull dst, size_t dst_len) + OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0); /** * Returns by reference a string describing a single parameter type of a method. @@ -978,11 +1049,14 @@ OBJC_EXPORT void method_getReturnType(Method m, char *dst, size_t dst_len) * were called. If the method contains no parameter with that index, \e dst is filled as * if \c strncpy(dst, "", dst_len) were called. */ -OBJC_EXPORT void method_getArgumentType(Method m, unsigned int index, - char *dst, size_t dst_len) - OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0); -OBJC_EXPORT struct objc_method_description *method_getDescription(Method m) - OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0); +OBJC_EXPORT void +method_getArgumentType(Method _Nonnull m, unsigned int index, + char * _Nullable dst, size_t dst_len) + OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0); + +OBJC_EXPORT struct objc_method_description * _Nonnull +method_getDescription(Method _Nonnull m) + OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0); /** * Sets the implementation of a method. @@ -992,8 +1066,9 @@ OBJC_EXPORT struct objc_method_description *method_getDescription(Method m) * * @return The previous implementation of the method. */ -OBJC_EXPORT IMP method_setImplementation(Method m, IMP imp) - OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0); +OBJC_EXPORT IMP _Nonnull +method_setImplementation(Method _Nonnull m, IMP _Nonnull imp) + OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0); /** * Exchanges the implementations of two methods. @@ -1009,8 +1084,9 @@ OBJC_EXPORT IMP method_setImplementation(Method m, IMP imp) * method_setImplementation(m2, imp1); * \endcode */ -OBJC_EXPORT void method_exchangeImplementations(Method m1, Method m2) - OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0); +OBJC_EXPORT void +method_exchangeImplementations(Method _Nonnull m1, Method _Nonnull m2) + OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0); /* Working with Instance Variables */ @@ -1022,8 +1098,9 @@ OBJC_EXPORT void method_exchangeImplementations(Method m1, Method m2) * * @return A C string containing the instance variable's name. */ -OBJC_EXPORT const char *ivar_getName(Ivar v) - OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0); +OBJC_EXPORT const char * _Nullable +ivar_getName(Ivar _Nonnull v) + OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0); /** * Returns the type string of an instance variable. @@ -1034,8 +1111,9 @@ OBJC_EXPORT const char *ivar_getName(Ivar v) * * @note For possible values, see Objective-C Runtime Programming Guide > Type Encodings. */ -OBJC_EXPORT const char *ivar_getTypeEncoding(Ivar v) - OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0); +OBJC_EXPORT const char * _Nullable +ivar_getTypeEncoding(Ivar _Nonnull v) + OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0); /** * Returns the offset of an instance variable. @@ -1047,8 +1125,9 @@ OBJC_EXPORT const char *ivar_getTypeEncoding(Ivar v) * @note For instance variables of type \c id or other object types, call \c object_getIvar * and \c object_setIvar instead of using this offset to access the instance variable data directly. */ -OBJC_EXPORT ptrdiff_t ivar_getOffset(Ivar v) - OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0); +OBJC_EXPORT ptrdiff_t +ivar_getOffset(Ivar _Nonnull v) + OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0); /* Working with Properties */ @@ -1060,8 +1139,9 @@ OBJC_EXPORT ptrdiff_t ivar_getOffset(Ivar v) * * @return A C string containing the property's name. */ -OBJC_EXPORT const char *property_getName(objc_property_t property) - OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0); +OBJC_EXPORT const char * _Nonnull +property_getName(objc_property_t _Nonnull property) + OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0); /** * Returns the attribute string of a property. @@ -1072,8 +1152,9 @@ OBJC_EXPORT const char *property_getName(objc_property_t property) * * @note The format of the attribute string is described in Declared Properties in Objective-C Runtime Programming Guide. */ -OBJC_EXPORT const char *property_getAttributes(objc_property_t property) - OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0); +OBJC_EXPORT const char * _Nullable +property_getAttributes(objc_property_t _Nonnull property) + OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0); /** * Returns an array of property attributes for a property. @@ -1083,8 +1164,10 @@ OBJC_EXPORT const char *property_getAttributes(objc_property_t property) * * @return An array of property attributes; must be free'd() by the caller. */ -OBJC_EXPORT objc_property_attribute_t *property_copyAttributeList(objc_property_t property, unsigned int *outCount) - OBJC_AVAILABLE(10.7, 4.3, 9.0, 1.0); +OBJC_EXPORT objc_property_attribute_t * _Nullable +property_copyAttributeList(objc_property_t _Nonnull property, + unsigned int * _Nullable outCount) + OBJC_AVAILABLE(10.7, 4.3, 9.0, 1.0, 2.0); /** * Returns the value of a property attribute given the attribute name. @@ -1095,8 +1178,10 @@ OBJC_EXPORT objc_property_attribute_t *property_copyAttributeList(objc_property_ * @return The value string of the attribute \e attributeName if it exists in * \e property, \c nil otherwise. */ -OBJC_EXPORT char *property_copyAttributeValue(objc_property_t property, const char *attributeName) - OBJC_AVAILABLE(10.7, 4.3, 9.0, 1.0); +OBJC_EXPORT char * _Nullable +property_copyAttributeValue(objc_property_t _Nonnull property, + const char * _Nonnull attributeName) + OBJC_AVAILABLE(10.7, 4.3, 9.0, 1.0, 2.0); /* Working with Protocols */ @@ -1110,8 +1195,9 @@ OBJC_EXPORT char *property_copyAttributeValue(objc_property_t property, const ch * * @note This function acquires the runtime lock. */ -OBJC_EXPORT Protocol *objc_getProtocol(const char *name) - OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0); +OBJC_EXPORT Protocol * _Nullable +objc_getProtocol(const char * _Nonnull name) + OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0); /** * Returns an array of all the protocols known to the runtime. @@ -1123,8 +1209,9 @@ OBJC_EXPORT Protocol *objc_getProtocol(const char *name) * * @note This function acquires the runtime lock. */ -OBJC_EXPORT Protocol * __unsafe_unretained *objc_copyProtocolList(unsigned int *outCount) - OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0); +OBJC_EXPORT Protocol * __unsafe_unretained _Nonnull * _Nullable +objc_copyProtocolList(unsigned int * _Nullable outCount) + OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0); /** * Returns a Boolean value that indicates whether one protocol conforms to another protocol. @@ -1141,8 +1228,10 @@ OBJC_EXPORT Protocol * __unsafe_unretained *objc_copyProtocolList(unsigned int * * \endcode * All the protocols listed between angle brackets are considered part of the ProtocolName protocol. */ -OBJC_EXPORT BOOL protocol_conformsToProtocol(Protocol *proto, Protocol *other) - OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0); +OBJC_EXPORT BOOL +protocol_conformsToProtocol(Protocol * _Nullable proto, + Protocol * _Nullable other) + OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0); /** * Returns a Boolean value that indicates whether two protocols are equal. @@ -1152,8 +1241,9 @@ OBJC_EXPORT BOOL protocol_conformsToProtocol(Protocol *proto, Protocol *other) * * @return \c YES if \e proto is the same as \e other, otherwise \c NO. */ -OBJC_EXPORT BOOL protocol_isEqual(Protocol *proto, Protocol *other) - OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0); +OBJC_EXPORT BOOL +protocol_isEqual(Protocol * _Nullable proto, Protocol * _Nullable other) + OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0); /** * Returns the name of a protocol. @@ -1162,8 +1252,9 @@ OBJC_EXPORT BOOL protocol_isEqual(Protocol *proto, Protocol *other) * * @return The name of the protocol \e p as a C string. */ -OBJC_EXPORT const char *protocol_getName(Protocol *p) - OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0); +OBJC_EXPORT const char * _Nonnull +protocol_getName(Protocol * _Nonnull proto) + OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0); /** * Returns a method description structure for a specified method of a given protocol. @@ -1180,8 +1271,10 @@ OBJC_EXPORT const char *protocol_getName(Protocol *p) * * @note This function recursively searches any protocols that this protocol conforms to. */ -OBJC_EXPORT struct objc_method_description protocol_getMethodDescription(Protocol *p, SEL aSel, BOOL isRequiredMethod, BOOL isInstanceMethod) - OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0); +OBJC_EXPORT struct objc_method_description +protocol_getMethodDescription(Protocol * _Nonnull proto, SEL _Nonnull aSel, + BOOL isRequiredMethod, BOOL isInstanceMethod) + OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0); /** * Returns an array of method descriptions of methods meeting a given specification for a given protocol. @@ -1200,8 +1293,12 @@ OBJC_EXPORT struct objc_method_description protocol_getMethodDescription(Protoco * * @note Methods in other protocols adopted by this protocol are not included. */ -OBJC_EXPORT struct objc_method_description *protocol_copyMethodDescriptionList(Protocol *p, BOOL isRequiredMethod, BOOL isInstanceMethod, unsigned int *outCount) - OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0); +OBJC_EXPORT struct objc_method_description * _Nullable +protocol_copyMethodDescriptionList(Protocol * _Nonnull proto, + BOOL isRequiredMethod, + BOOL isInstanceMethod, + unsigned int * _Nullable outCount) + OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0); /** * Returns the specified property of a given protocol. @@ -1214,8 +1311,11 @@ OBJC_EXPORT struct objc_method_description *protocol_copyMethodDescriptionList(P * @return The property specified by \e name, \e isRequiredProperty, and \e isInstanceProperty for \e proto, * or \c NULL if none of \e proto's properties meets the specification. */ -OBJC_EXPORT objc_property_t protocol_getProperty(Protocol *proto, const char *name, BOOL isRequiredProperty, BOOL isInstanceProperty) - OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0); +OBJC_EXPORT objc_property_t _Nullable +protocol_getProperty(Protocol * _Nonnull proto, + const char * _Nonnull name, + BOOL isRequiredProperty, BOOL isInstanceProperty) + OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0); /** * Returns an array of the required instance properties declared by a protocol. @@ -1225,8 +1325,10 @@ OBJC_EXPORT objc_property_t protocol_getProperty(Protocol *proto, const char *na * protocol_copyPropertyList2(proto, outCount, YES, YES); * \endcode */ -OBJC_EXPORT objc_property_t *protocol_copyPropertyList(Protocol *proto, unsigned int *outCount) - OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0); +OBJC_EXPORT objc_property_t _Nonnull * _Nullable +protocol_copyPropertyList(Protocol * _Nonnull proto, + unsigned int * _Nullable outCount) + OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0); /** * Returns an array of properties declared by a protocol. @@ -1241,8 +1343,11 @@ OBJC_EXPORT objc_property_t *protocol_copyPropertyList(Protocol *proto, unsigned * \c *outCount pointers followed by a \c NULL terminator. You must free the array with \c free(). * If the protocol declares no matching properties, \c NULL is returned and \c *outCount is \c 0. */ -OBJC_EXPORT objc_property_t *protocol_copyPropertyList2(Protocol *proto, unsigned int *outCount, BOOL isRequiredProperty, BOOL isInstanceProperty) - OBJC_AVAILABLE(10.12, 10.0, 10.0, 3.0); +OBJC_EXPORT objc_property_t _Nonnull * _Nullable +protocol_copyPropertyList2(Protocol * _Nonnull proto, + unsigned int * _Nullable outCount, + BOOL isRequiredProperty, BOOL isInstanceProperty) + OBJC_AVAILABLE(10.12, 10.0, 10.0, 3.0, 2.0); /** * Returns an array of the protocols adopted by a protocol. @@ -1254,8 +1359,10 @@ OBJC_EXPORT objc_property_t *protocol_copyPropertyList2(Protocol *proto, unsigne * followed by a \c NULL terminator. You must free the array with \c free(). * If the protocol declares no properties, \c NULL is returned and \c *outCount is \c 0. */ -OBJC_EXPORT Protocol * __unsafe_unretained *protocol_copyProtocolList(Protocol *proto, unsigned int *outCount) - OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0); +OBJC_EXPORT Protocol * __unsafe_unretained _Nonnull * _Nullable +protocol_copyProtocolList(Protocol * _Nonnull proto, + unsigned int * _Nullable outCount) + OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0); /** * Creates a new protocol instance that cannot be used until registered with @@ -1267,8 +1374,9 @@ OBJC_EXPORT Protocol * __unsafe_unretained *protocol_copyProtocolList(Protocol * * with the same name already exists. * @note There is no dispose method for this. */ -OBJC_EXPORT Protocol *objc_allocateProtocol(const char *name) - OBJC_AVAILABLE(10.7, 4.3, 9.0, 1.0); +OBJC_EXPORT Protocol * _Nullable +objc_allocateProtocol(const char * _Nonnull name) + OBJC_AVAILABLE(10.7, 4.3, 9.0, 1.0, 2.0); /** * Registers a newly constructed protocol with the runtime. The protocol @@ -1276,8 +1384,9 @@ OBJC_EXPORT Protocol *objc_allocateProtocol(const char *name) * * @param proto The protocol you want to register. */ -OBJC_EXPORT void objc_registerProtocol(Protocol *proto) - OBJC_AVAILABLE(10.7, 4.3, 9.0, 1.0); +OBJC_EXPORT void +objc_registerProtocol(Protocol * _Nonnull proto) + OBJC_AVAILABLE(10.7, 4.3, 9.0, 1.0, 2.0); /** * Adds a method to a protocol. The protocol must be under construction. @@ -1288,8 +1397,11 @@ OBJC_EXPORT void objc_registerProtocol(Protocol *proto) * @param isRequiredMethod YES if the method is not an optional method. * @param isInstanceMethod YES if the method is an instance method. */ -OBJC_EXPORT void protocol_addMethodDescription(Protocol *proto, SEL name, const char *types, BOOL isRequiredMethod, BOOL isInstanceMethod) - OBJC_AVAILABLE(10.7, 4.3, 9.0, 1.0); +OBJC_EXPORT void +protocol_addMethodDescription(Protocol * _Nonnull proto, SEL _Nonnull name, + const char * _Nullable types, + BOOL isRequiredMethod, BOOL isInstanceMethod) + OBJC_AVAILABLE(10.7, 4.3, 9.0, 1.0, 2.0); /** * Adds an incorporated protocol to another protocol. The protocol being @@ -1299,8 +1411,9 @@ OBJC_EXPORT void protocol_addMethodDescription(Protocol *proto, SEL name, const * @param proto The protocol you want to add to, it must be under construction. * @param addition The protocol you want to incorporate into \e proto, it must be registered. */ -OBJC_EXPORT void protocol_addProtocol(Protocol *proto, Protocol *addition) - OBJC_AVAILABLE(10.7, 4.3, 9.0, 1.0); +OBJC_EXPORT void +protocol_addProtocol(Protocol * _Nonnull proto, Protocol * _Nonnull addition) + OBJC_AVAILABLE(10.7, 4.3, 9.0, 1.0, 2.0); /** * Adds a property to a protocol. The protocol must be under construction. @@ -1314,8 +1427,12 @@ OBJC_EXPORT void protocol_addProtocol(Protocol *proto, Protocol *addition) * This is the only case allowed fo a property, as a result, setting this to NO will * not add the property to the protocol at all. */ -OBJC_EXPORT void protocol_addProperty(Protocol *proto, const char *name, const objc_property_attribute_t *attributes, unsigned int attributeCount, BOOL isRequiredProperty, BOOL isInstanceProperty) - OBJC_AVAILABLE(10.7, 4.3, 9.0, 1.0); +OBJC_EXPORT void +protocol_addProperty(Protocol * _Nonnull proto, const char * _Nonnull name, + const objc_property_attribute_t * _Nullable attributes, + unsigned int attributeCount, + BOOL isRequiredProperty, BOOL isInstanceProperty) + OBJC_AVAILABLE(10.7, 4.3, 9.0, 1.0, 2.0); /* Working with Libraries */ @@ -1328,8 +1445,9 @@ OBJC_EXPORT void protocol_addProperty(Protocol *proto, const char *name, const o * * @return An array of C strings of names. Must be free()'d by caller. */ -OBJC_EXPORT const char **objc_copyImageNames(unsigned int *outCount) - OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0); +OBJC_EXPORT const char * _Nonnull * _Nonnull +objc_copyImageNames(unsigned int * _Nullable outCount) + OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0); /** * Returns the dynamic library name a class originated from. @@ -1338,8 +1456,9 @@ OBJC_EXPORT const char **objc_copyImageNames(unsigned int *outCount) * * @return The name of the library containing this class. */ -OBJC_EXPORT const char *class_getImageName(Class cls) - OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0); +OBJC_EXPORT const char * _Nullable +class_getImageName(Class _Nullable cls) + OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0); /** * Returns the names of all the classes within a library. @@ -1349,9 +1468,10 @@ OBJC_EXPORT const char *class_getImageName(Class cls) * * @return An array of C strings representing the class names. */ -OBJC_EXPORT const char **objc_copyClassNamesForImage(const char *image, - unsigned int *outCount) - OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0); +OBJC_EXPORT const char * _Nonnull * _Nullable +objc_copyClassNamesForImage(const char * _Nonnull image, + unsigned int * _Nullable outCount) + OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0); /* Working with Selectors */ @@ -1363,8 +1483,9 @@ OBJC_EXPORT const char **objc_copyClassNamesForImage(const char *image, * * @return A C string indicating the name of the selector. */ -OBJC_EXPORT const char *sel_getName(SEL sel) - OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0); +OBJC_EXPORT const char * _Nonnull +sel_getName(SEL _Nonnull sel) + OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0); /** @@ -1379,8 +1500,9 @@ OBJC_EXPORT const char *sel_getName(SEL sel) * method’s selector before you can add the method to a class definition. If the method name * has already been registered, this function simply returns the selector. */ -OBJC_EXPORT SEL sel_registerName(const char *str) - OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0); +OBJC_EXPORT SEL _Nonnull +sel_registerName(const char * _Nonnull str) + OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0); /** * Returns a Boolean value that indicates whether two selectors are equal. @@ -1392,8 +1514,9 @@ OBJC_EXPORT SEL sel_registerName(const char *str) * * @note sel_isEqual is equivalent to ==. */ -OBJC_EXPORT BOOL sel_isEqual(SEL lhs, SEL rhs) - OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0); +OBJC_EXPORT BOOL +sel_isEqual(SEL _Nonnull lhs, SEL _Nonnull rhs) + OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0); /* Objective-C Language Features */ @@ -1407,16 +1530,18 @@ OBJC_EXPORT BOOL sel_isEqual(SEL lhs, SEL rhs) * @param obj The object being mutated. * */ -OBJC_EXPORT void objc_enumerationMutation(id obj) - OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0); +OBJC_EXPORT void +objc_enumerationMutation(id _Nonnull obj) + OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0); /** * Sets the current mutation handler. * * @param handler Function pointer to the new mutation handler. */ -OBJC_EXPORT void objc_setEnumerationMutationHandler(void (*handler)(id)) - OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0); +OBJC_EXPORT void +objc_setEnumerationMutationHandler(void (*_Nullable handler)(id _Nonnull )) + OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0); /** * Set the function to be called by objc_msgForward. @@ -1426,8 +1551,9 @@ OBJC_EXPORT void objc_setEnumerationMutationHandler(void (*handler)(id)) * * @see message.h::_objc_msgForward */ -OBJC_EXPORT void objc_setForwardHandler(void *fwd, void *fwd_stret) - OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0); +OBJC_EXPORT void +objc_setForwardHandler(void * _Nonnull fwd, void * _Nonnull fwd_stret) + OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0); /** * Creates a pointer to a function that will call the block @@ -1441,8 +1567,9 @@ OBJC_EXPORT void objc_setForwardHandler(void *fwd, void *fwd_stret) * @return The IMP that calls this block. Must be disposed of with * \c imp_removeBlock. */ -OBJC_EXPORT IMP imp_implementationWithBlock(id block) - OBJC_AVAILABLE(10.7, 4.3, 9.0, 1.0); +OBJC_EXPORT IMP _Nonnull +imp_implementationWithBlock(id _Nonnull block) + OBJC_AVAILABLE(10.7, 4.3, 9.0, 1.0, 2.0); /** * Return the block associated with an IMP that was created using @@ -1452,8 +1579,9 @@ OBJC_EXPORT IMP imp_implementationWithBlock(id block) * * @return The block called by \e anImp. */ -OBJC_EXPORT id imp_getBlock(IMP anImp) - OBJC_AVAILABLE(10.7, 4.3, 9.0, 1.0); +OBJC_EXPORT id _Nullable +imp_getBlock(IMP _Nonnull anImp) + OBJC_AVAILABLE(10.7, 4.3, 9.0, 1.0, 2.0); /** * Disassociates a block from an IMP that was created using @@ -1465,8 +1593,9 @@ OBJC_EXPORT id imp_getBlock(IMP anImp) * @return YES if the block was released successfully, NO otherwise. * (For example, the block might not have been used to create an IMP previously). */ -OBJC_EXPORT BOOL imp_removeBlock(IMP anImp) - OBJC_AVAILABLE(10.7, 4.3, 9.0, 1.0); +OBJC_EXPORT BOOL +imp_removeBlock(IMP _Nonnull anImp) + OBJC_AVAILABLE(10.7, 4.3, 9.0, 1.0, 2.0); /** * This loads the object referenced by a weak pointer and returns it, after @@ -1476,10 +1605,11 @@ OBJC_EXPORT BOOL imp_removeBlock(IMP anImp) * * @param location The weak pointer address * - * @return The object pointed to by \e location, or \c nil if \e location is \c nil. + * @return The object pointed to by \e location, or \c nil if \e *location is \c nil. */ -OBJC_EXPORT id objc_loadWeak(id *location) - OBJC_AVAILABLE(10.7, 5.0, 9.0, 1.0); +OBJC_EXPORT id _Nullable +objc_loadWeak(id _Nullable * _Nonnull location) + OBJC_AVAILABLE(10.7, 5.0, 9.0, 1.0, 2.0); /** * This function stores a new value into a __weak variable. It would @@ -1490,8 +1620,9 @@ OBJC_EXPORT id objc_loadWeak(id *location) * * @return The value stored into \e location, i.e. \e obj */ -OBJC_EXPORT id objc_storeWeak(id *location, id obj) - OBJC_AVAILABLE(10.7, 5.0, 9.0, 1.0); +OBJC_EXPORT id _Nullable +objc_storeWeak(id _Nullable * _Nonnull location, id _Nullable obj) + OBJC_AVAILABLE(10.7, 5.0, 9.0, 1.0, 2.0); /* Associative References */ @@ -1523,8 +1654,10 @@ typedef OBJC_ENUM(uintptr_t, objc_AssociationPolicy) { * @see objc_setAssociatedObject * @see objc_removeAssociatedObjects */ -OBJC_EXPORT void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy) - OBJC_AVAILABLE(10.6, 3.1, 9.0, 1.0); +OBJC_EXPORT void +objc_setAssociatedObject(id _Nonnull object, const void * _Nonnull key, + id _Nullable value, objc_AssociationPolicy policy) + OBJC_AVAILABLE(10.6, 3.1, 9.0, 1.0, 2.0); /** * Returns the value associated with a given object for a given key. @@ -1536,8 +1669,9 @@ OBJC_EXPORT void objc_setAssociatedObject(id object, const void *key, id value, * * @see objc_setAssociatedObject */ -OBJC_EXPORT id objc_getAssociatedObject(id object, const void *key) - OBJC_AVAILABLE(10.6, 3.1, 9.0, 1.0); +OBJC_EXPORT id _Nullable +objc_getAssociatedObject(id _Nonnull object, const void * _Nonnull key) + OBJC_AVAILABLE(10.6, 3.1, 9.0, 1.0, 2.0); /** * Removes all associations for a given object. @@ -1553,8 +1687,9 @@ OBJC_EXPORT id objc_getAssociatedObject(id object, const void *key) * @see objc_setAssociatedObject * @see objc_getAssociatedObject */ -OBJC_EXPORT void objc_removeAssociatedObjects(id object) - OBJC_AVAILABLE(10.6, 3.1, 9.0, 1.0); +OBJC_EXPORT void +objc_removeAssociatedObjects(id _Nonnull object) + OBJC_AVAILABLE(10.6, 3.1, 9.0, 1.0, 2.0); #define _C_ID '@' @@ -1634,30 +1769,30 @@ OBJC_EXPORT void objc_removeAssociatedObjects(id object) struct objc_method_description_list { - int count; - struct objc_method_description list[1]; + int count; + struct objc_method_description list[1]; }; struct objc_protocol_list { - struct objc_protocol_list *next; + struct objc_protocol_list * _Nullable next; long count; - __unsafe_unretained Protocol *list[1]; + __unsafe_unretained Protocol * _Nullable list[1]; }; struct objc_category { - char *category_name OBJC2_UNAVAILABLE; - char *class_name OBJC2_UNAVAILABLE; - struct objc_method_list *instance_methods OBJC2_UNAVAILABLE; - struct objc_method_list *class_methods OBJC2_UNAVAILABLE; - struct objc_protocol_list *protocols OBJC2_UNAVAILABLE; + char * _Nonnull category_name OBJC2_UNAVAILABLE; + char * _Nonnull class_name OBJC2_UNAVAILABLE; + struct objc_method_list * _Nullable instance_methods OBJC2_UNAVAILABLE; + struct objc_method_list * _Nullable class_methods OBJC2_UNAVAILABLE; + struct objc_protocol_list * _Nullable protocols OBJC2_UNAVAILABLE; } OBJC2_UNAVAILABLE; struct objc_ivar { - char *ivar_name OBJC2_UNAVAILABLE; - char *ivar_type OBJC2_UNAVAILABLE; + char * _Nullable ivar_name OBJC2_UNAVAILABLE; + char * _Nullable ivar_type OBJC2_UNAVAILABLE; int ivar_offset OBJC2_UNAVAILABLE; #ifdef __LP64__ int space OBJC2_UNAVAILABLE; @@ -1675,13 +1810,13 @@ struct objc_ivar_list { struct objc_method { - SEL method_name OBJC2_UNAVAILABLE; - char *method_types OBJC2_UNAVAILABLE; - IMP method_imp OBJC2_UNAVAILABLE; + SEL _Nonnull method_name OBJC2_UNAVAILABLE; + char * _Nullable method_types OBJC2_UNAVAILABLE; + IMP _Nonnull method_imp OBJC2_UNAVAILABLE; } OBJC2_UNAVAILABLE; struct objc_method_list { - struct objc_method_list *obsolete OBJC2_UNAVAILABLE; + struct objc_method_list * _Nullable obsolete OBJC2_UNAVAILABLE; int method_count OBJC2_UNAVAILABLE; #ifdef __LP64__ @@ -1696,10 +1831,10 @@ typedef struct objc_symtab *Symtab OBJC2_UNAVAILABLE; struct objc_symtab { unsigned long sel_ref_cnt OBJC2_UNAVAILABLE; - SEL *refs OBJC2_UNAVAILABLE; + SEL _Nonnull * _Nullable refs OBJC2_UNAVAILABLE; unsigned short cls_def_cnt OBJC2_UNAVAILABLE; unsigned short cat_def_cnt OBJC2_UNAVAILABLE; - void *defs[1] /* variable size */ OBJC2_UNAVAILABLE; + void * _Nullable defs[1] /* variable size */ OBJC2_UNAVAILABLE; } OBJC2_UNAVAILABLE; @@ -1716,7 +1851,7 @@ typedef struct objc_cache *Cache OBJC2_UNAVAILABLE; struct objc_cache { unsigned int mask /* total = mask + 1 */ OBJC2_UNAVAILABLE; unsigned int occupied OBJC2_UNAVAILABLE; - Method buckets[1] OBJC2_UNAVAILABLE; + Method _Nullable buckets[1] OBJC2_UNAVAILABLE; }; @@ -1725,8 +1860,8 @@ typedef struct objc_module *Module OBJC2_UNAVAILABLE; struct objc_module { unsigned long version OBJC2_UNAVAILABLE; unsigned long size OBJC2_UNAVAILABLE; - const char *name OBJC2_UNAVAILABLE; - Symtab symtab OBJC2_UNAVAILABLE; + const char * _Nullable name OBJC2_UNAVAILABLE; + Symtab _Nullable symtab OBJC2_UNAVAILABLE; } OBJC2_UNAVAILABLE; #else @@ -1738,52 +1873,103 @@ struct objc_method_list; /* Obsolete functions */ -OBJC_EXPORT IMP class_lookupMethod(Class cls, SEL sel) +OBJC_EXPORT IMP _Nullable +class_lookupMethod(Class _Nullable cls, SEL _Nonnull sel) __OSX_DEPRECATED(10.0, 10.5, "use class_getMethodImplementation instead") __IOS_DEPRECATED(2.0, 2.0, "use class_getMethodImplementation instead") __TVOS_DEPRECATED(9.0, 9.0, "use class_getMethodImplementation instead") - __WATCHOS_DEPRECATED(1.0, 1.0, "use class_getMethodImplementation instead"); -OBJC_EXPORT BOOL class_respondsToMethod(Class cls, SEL sel) + __WATCHOS_DEPRECATED(1.0, 1.0, "use class_getMethodImplementation instead") + __BRIDGEOS_DEPRECATED(2.0, 2.0, "use class_getMethodImplementation instead"); +OBJC_EXPORT BOOL +class_respondsToMethod(Class _Nullable cls, SEL _Nonnull sel) __OSX_DEPRECATED(10.0, 10.5, "use class_respondsToSelector instead") __IOS_DEPRECATED(2.0, 2.0, "use class_respondsToSelector instead") __TVOS_DEPRECATED(9.0, 9.0, "use class_respondsToSelector instead") - __WATCHOS_DEPRECATED(1.0, 1.0, "use class_respondsToSelector instead"); -OBJC_EXPORT void _objc_flush_caches(Class cls) + __WATCHOS_DEPRECATED(1.0, 1.0, "use class_respondsToSelector instead") + __BRIDGEOS_DEPRECATED(2.0, 2.0, "use class_respondsToSelector instead"); + +OBJC_EXPORT void +_objc_flush_caches(Class _Nullable cls) __OSX_DEPRECATED(10.0, 10.5, "not recommended") __IOS_DEPRECATED(2.0, 2.0, "not recommended") __TVOS_DEPRECATED(9.0, 9.0, "not recommended") - __WATCHOS_DEPRECATED(1.0, 1.0, "not recommended"); + __WATCHOS_DEPRECATED(1.0, 1.0, "not recommended") + __BRIDGEOS_DEPRECATED(2.0, 2.0, "not recommended"); -OBJC_EXPORT id object_copyFromZone(id anObject, size_t nBytes, void *z) +OBJC_EXPORT id _Nullable +object_copyFromZone(id _Nullable anObject, size_t nBytes, void * _Nullable z) __OSX_DEPRECATED(10.0, 10.5, "use object_copy instead") - __IOS_UNAVAILABLE __TVOS_UNAVAILABLE __WATCHOS_UNAVAILABLE + __IOS_UNAVAILABLE __TVOS_UNAVAILABLE + __WATCHOS_UNAVAILABLE __BRIDGEOS_UNAVAILABLE OBJC_ARC_UNAVAILABLE; -OBJC_EXPORT id object_realloc(id anObject, size_t nBytes) OBJC2_UNAVAILABLE; -OBJC_EXPORT id object_reallocFromZone(id anObject, size_t nBytes, void *z) OBJC2_UNAVAILABLE; + +OBJC_EXPORT id _Nullable +object_realloc(id _Nullable anObject, size_t nBytes) + OBJC2_UNAVAILABLE; + +OBJC_EXPORT id _Nullable +object_reallocFromZone(id _Nullable anObject, size_t nBytes, void * _Nullable z) + OBJC2_UNAVAILABLE; #define OBSOLETE_OBJC_GETCLASSES 1 -OBJC_EXPORT void *objc_getClasses(void) OBJC2_UNAVAILABLE; -OBJC_EXPORT void objc_addClass(Class myClass) OBJC2_UNAVAILABLE; -OBJC_EXPORT void objc_setClassHandler(int (*)(const char *)) OBJC2_UNAVAILABLE; -OBJC_EXPORT void objc_setMultithreaded (BOOL flag) OBJC2_UNAVAILABLE; +OBJC_EXPORT void * _Nonnull +objc_getClasses(void) + OBJC2_UNAVAILABLE; + +OBJC_EXPORT void +objc_addClass(Class _Nonnull myClass) + OBJC2_UNAVAILABLE; -OBJC_EXPORT id class_createInstanceFromZone(Class, size_t idxIvars, void *z) +OBJC_EXPORT void +objc_setClassHandler(int (* _Nullable )(const char * _Nonnull)) + OBJC2_UNAVAILABLE; + +OBJC_EXPORT void +objc_setMultithreaded(BOOL flag) + OBJC2_UNAVAILABLE; + +OBJC_EXPORT id _Nullable +class_createInstanceFromZone(Class _Nullable, size_t idxIvars, + void * _Nullable z) __OSX_DEPRECATED(10.0, 10.5, "use class_createInstance instead") - __IOS_UNAVAILABLE __TVOS_UNAVAILABLE __WATCHOS_UNAVAILABLE + __IOS_UNAVAILABLE __TVOS_UNAVAILABLE + __WATCHOS_UNAVAILABLE __BRIDGEOS_UNAVAILABLE OBJC_ARC_UNAVAILABLE; -OBJC_EXPORT void class_addMethods(Class, struct objc_method_list *) OBJC2_UNAVAILABLE; -OBJC_EXPORT void class_removeMethods(Class, struct objc_method_list *) OBJC2_UNAVAILABLE; -OBJC_EXPORT void _objc_resolve_categories_for_class(Class cls) OBJC2_UNAVAILABLE; +OBJC_EXPORT void +class_addMethods(Class _Nullable, struct objc_method_list * _Nonnull) + OBJC2_UNAVAILABLE; + +OBJC_EXPORT void +class_removeMethods(Class _Nullable, struct objc_method_list * _Nonnull) + OBJC2_UNAVAILABLE; + +OBJC_EXPORT void +_objc_resolve_categories_for_class(Class _Nonnull cls) + OBJC2_UNAVAILABLE; + +OBJC_EXPORT Class _Nonnull +class_poseAs(Class _Nonnull imposter, Class _Nonnull original) + OBJC2_UNAVAILABLE; + +OBJC_EXPORT unsigned int +method_getSizeOfArguments(Method _Nonnull m) + OBJC2_UNAVAILABLE; -OBJC_EXPORT Class class_poseAs(Class imposter, Class original) OBJC2_UNAVAILABLE; +OBJC_EXPORT unsigned +method_getArgumentInfo(struct objc_method * _Nonnull m, int arg, + const char * _Nullable * _Nonnull type, + int * _Nonnull offset) + OBJC2_UNAVAILABLE; -OBJC_EXPORT unsigned int method_getSizeOfArguments(Method m) OBJC2_UNAVAILABLE; -OBJC_EXPORT unsigned method_getArgumentInfo(struct objc_method *m, int arg, const char **type, int *offset) OBJC2_UNAVAILABLE; +OBJC_EXPORT Class _Nullable +objc_getOrigClass(const char * _Nonnull name) + OBJC2_UNAVAILABLE; -OBJC_EXPORT Class objc_getOrigClass(const char *name) OBJC2_UNAVAILABLE; #define OBJC_NEXT_METHOD_LIST 1 -OBJC_EXPORT struct objc_method_list *class_nextMethodList(Class, void **) OBJC2_UNAVAILABLE; +OBJC_EXPORT struct objc_method_list * _Nullable +class_nextMethodList(Class _Nullable, void * _Nullable * _Nullable) + OBJC2_UNAVAILABLE; // usage for nextMethodList // // void *iterator = 0; @@ -1791,13 +1977,36 @@ OBJC_EXPORT struct objc_method_list *class_nextMethodList(Class, void **) OBJC2_ // while ( mlist = class_nextMethodList( cls, &iterator ) ) // ; -OBJC_EXPORT id (*_alloc)(Class, size_t) OBJC2_UNAVAILABLE; -OBJC_EXPORT id (*_copy)(id, size_t) OBJC2_UNAVAILABLE; -OBJC_EXPORT id (*_realloc)(id, size_t) OBJC2_UNAVAILABLE; -OBJC_EXPORT id (*_dealloc)(id) OBJC2_UNAVAILABLE; -OBJC_EXPORT id (*_zoneAlloc)(Class, size_t, void *) OBJC2_UNAVAILABLE; -OBJC_EXPORT id (*_zoneRealloc)(id, size_t, void *) OBJC2_UNAVAILABLE; -OBJC_EXPORT id (*_zoneCopy)(id, size_t, void *) OBJC2_UNAVAILABLE; -OBJC_EXPORT void (*_error)(id, const char *, va_list) OBJC2_UNAVAILABLE; +OBJC_EXPORT id _Nullable +(* _Nonnull _alloc)(Class _Nullable, size_t) + OBJC2_UNAVAILABLE; + +OBJC_EXPORT id _Nullable +(* _Nonnull _copy)(id _Nullable, size_t) + OBJC2_UNAVAILABLE; + +OBJC_EXPORT id _Nullable +(* _Nonnull _realloc)(id _Nullable, size_t) + OBJC2_UNAVAILABLE; + +OBJC_EXPORT id _Nullable +(* _Nonnull _dealloc)(id _Nullable) + OBJC2_UNAVAILABLE; + +OBJC_EXPORT id _Nullable +(* _Nonnull _zoneAlloc)(Class _Nullable, size_t, void * _Nullable) + OBJC2_UNAVAILABLE; + +OBJC_EXPORT id _Nullable +(* _Nonnull _zoneRealloc)(id _Nullable, size_t, void * _Nullable) + OBJC2_UNAVAILABLE; + +OBJC_EXPORT id _Nullable +(* _Nonnull _zoneCopy)(id _Nullable, size_t, void * _Nullable) + OBJC2_UNAVAILABLE; + +OBJC_EXPORT void +(* _Nonnull _error)(id _Nullable, const char * _Nonnull, va_list) + OBJC2_UNAVAILABLE; #endif From 4439591e755cb9e468082b36869eb4033d4dac3e Mon Sep 17 00:00:00 2001 From: shrekwzw Date: Mon, 10 Dec 2018 10:25:01 +0800 Subject: [PATCH 05/12] Version 750 --- libobjc.order | 2 - objc.suo | Bin 24576 -> 0 bytes objc.xcodeproj/project.pbxproj | 228 +++- runtime/Messengers.subproj/objc-msg-arm.s | 119 +- runtime/Messengers.subproj/objc-msg-arm64.s | 289 ++--- runtime/Messengers.subproj/objc-msg-i386.s | 76 -- .../objc-msg-simulator-i386.s | 143 +-- .../objc-msg-simulator-x86_64.s | 117 +- runtime/Messengers.subproj/objc-msg-x86_64.s | 121 +- runtime/NSObjCRuntime.h | 2 +- runtime/NSObject.mm | 51 - runtime/Object.mm | 9 - runtime/a1a2-blocktramps-arm.s | 148 --- runtime/a1a2-blocktramps-i386.s | 566 --------- runtime/a1a2-blocktramps-x86_64.s | 564 --------- runtime/a2a3-blocktramps-arm.s | 148 --- runtime/a2a3-blocktramps-i386.s | 566 --------- runtime/a2a3-blocktramps-x86_64.s | 565 --------- runtime/arm64-asm.h | 166 +++ runtime/isa.h | 134 ++ runtime/maptable.mm | 99 +- runtime/objc-abi.h | 103 +- runtime/objc-api.h | 38 +- runtime/objc-auto.h | 3 +- runtime/objc-auto.mm | 73 +- runtime/objc-block-trampolines.h | 64 + runtime/objc-block-trampolines.mm | 539 ++++---- runtime/objc-blocktramps-arm.s | 286 +++++ ...ramps-arm64.s => objc-blocktramps-arm64.s} | 44 +- runtime/objc-blocktramps-i386.s | 1099 +++++++++++++++++ runtime/objc-blocktramps-x86_64.s | 1096 ++++++++++++++++ runtime/objc-cache-old.mm | 4 +- runtime/objc-cache.mm | 75 +- runtime/objc-class-old.mm | 40 +- runtime/objc-class.mm | 12 - runtime/objc-config.h | 29 +- runtime/objc-env.h | 1 + runtime/objc-errors.mm | 2 - runtime/objc-exception.mm | 118 +- runtime/objc-file-old.h | 4 +- runtime/objc-file-old.mm | 2 +- runtime/objc-file.h | 50 +- runtime/objc-file.mm | 47 +- runtime/objc-gdb.h | 54 +- runtime/objc-internal.h | 161 ++- runtime/objc-lockdebug.h | 67 +- runtime/objc-lockdebug.mm | 128 -- runtime/objc-locks-new.h | 2 +- runtime/objc-locks.h | 2 +- runtime/objc-object.h | 3 +- runtime/objc-opt.mm | 29 +- runtime/objc-os.h | 252 +--- runtime/objc-os.mm | 10 +- runtime/objc-private.h | 177 +-- runtime/objc-ptrauth.h | 84 ++ runtime/objc-runtime-new.h | 117 +- runtime/objc-runtime-new.mm | 844 +++++++++---- runtime/objc-runtime-old.h | 4 +- runtime/objc-runtime-old.mm | 119 +- runtime/objc-runtime.mm | 149 +-- runtime/objc-sel-old.mm | 34 +- runtime/objc-sel-set.mm | 2 +- runtime/objc-sel-table.s | 25 +- runtime/objc-sel.mm | 34 +- runtime/objc-sync.mm | 14 +- runtime/objc.h | 17 +- runtime/runtime.h | 44 +- 67 files changed, 5408 insertions(+), 4806 deletions(-) delete mode 100755 objc.suo delete mode 100644 runtime/a1a2-blocktramps-arm.s delete mode 100755 runtime/a1a2-blocktramps-i386.s delete mode 100755 runtime/a1a2-blocktramps-x86_64.s delete mode 100644 runtime/a2a3-blocktramps-arm.s delete mode 100755 runtime/a2a3-blocktramps-i386.s delete mode 100755 runtime/a2a3-blocktramps-x86_64.s create mode 100644 runtime/arm64-asm.h create mode 100644 runtime/isa.h create mode 100644 runtime/objc-block-trampolines.h create mode 100644 runtime/objc-blocktramps-arm.s rename runtime/{a1a2-blocktramps-arm64.s => objc-blocktramps-arm64.s} (69%) create mode 100755 runtime/objc-blocktramps-i386.s create mode 100755 runtime/objc-blocktramps-x86_64.s create mode 100644 runtime/objc-ptrauth.h diff --git a/libobjc.order b/libobjc.order index c2f2c6e..fc60cf4 100644 --- a/libobjc.order +++ b/libobjc.order @@ -17,12 +17,10 @@ _rtp_init _gc_fixup_barrier_stubs __objc_update_stubs_in_mach_header _sel_init -_sel_lock ___sel_registerName __objc_search_builtins __ZNK8objc_opt13objc_selopt_t3getEPKc __ZNK8objc_opt13objc_selopt_t4hashEPKc -_sel_unlock _sel_registerName _arr_init __ZN4objc8DenseMapIP11objc_objectmLb1ENS_12DenseMapInfoIS2_EENS3_ImEEE4initEj diff --git a/objc.suo b/objc.suo deleted file mode 100755 index 7f8180c631918658823f234ec88fd2017eac6078..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 24576 zcmeI433wLOy~YPn5cdUaL5o2)5fa%E5FrpiFf0-QEm4FJAZ$SrKm`PC)hn(QDpso2 ziel}>>aAKITd#_(f*aKKwraJlZLM}wYrU;qB=`NzyhBbx^5qLq+WR~ne82Pme&7Gh zIdkT0b7ta>?MMCm&IjAR8Py7>N6n(0RV}0D0dFE3#o@>(YAOTmtg5O?U=+#xB>X?B zfsa+z19b7)N(V|@?}MZk(jihy>0s$l=`iVV=_u(4NlhR+S~^xbMrtLsmOdqYM*6hW zMmkQSjU2DIqiw#|74r0ZUA#7Hm!)6rbf0{$SpyTJa{a4}a-tE@LdA=ti&eAPhhx=h z(j#*HGle5J514q@DKVw=`1fEA^A+N&}?<66psiK1~`doh}WLhD!O;Flo3{AdQglr{+H|jrR959gkH! zP8u&wkj|1ON=4Gy(j;lJG(|c`I#>FvG*v2=&XdlUrb!n_)1?_wiA26%QanqVEzOb2 zq{}3Jo3D6*v`{LS7D*RMi>1q@N@>a?#fih#j5{u)px0^*{bX5 zs^@ue-z*lNp%zx6)|mFAy(~t&j-tH{SF0^n%VJcVtSu|gHSLnB~vPW+;`%W2|k(z zc4}tJQvYmX)g^~z8e$4!BD}w8`=(klS5-A>8Z{|i>xzz|2d1uVvQ^&`(1cdNdRtB+ z^arg=(RbQvr&|0jeScUwN*b${GCyvw^nAuC`u`%2BFuz?TiD+t39wHVb-ZXsQtz5jP?+duav+2+rE4RbYt{$8m(7pvDXdRC9L>B_WJeWEfRK^comYmG<)l|RQL zZ$f;~20m(@f4PpoMCY6wIXveHs)q{IL8g(Yhw^7#mgJsY{?yfv-5Cm$M}7IHYvF=; zPMBWjS>-8zT5@CZKV1HOd^E#xk{mW%ahSx=X_S zM_TvUo`2Z>dTF)XTkDt}%6FuOv0Po5{u&=f$U0TO4A;M0t*ZOUqrWslUc)cTGZt!1 zo%Gn=x(mrue(CCIn6B$g)lp@<3g_%-_0^-@?x_bwL*=nQOFL_~@`l$ef1j6TqS`mB zSlWAOBWjNEs{XWt;(NJ$TW|H}mZ!0)_Ek=5O_R0e*CG47)~ee~_HB(BsqS=x;yd7d zTU&MKRDVsWaJyC_MN!8?KF;rU-BV{g_}YEjkGXrw5rxs=+uF|mN6QZ5Z>+lNqwQNZ zj=;Y#db?Gpm%q`W!>C8zdUIu$84EmtoR!0UcCuP+{M}onW}8ag2c`FOdAcJi(dd=z zNhQ18|GcAfUTvc@;vO+mpNpgLPLE&NE1@lNr&(R$|ca24|7==qU@-Qk@ z>icA^4y(^ES8TD4v{3g~2~Afytb-P*ce3)LB*|#aT$nke^G|EJWjp_LkngQ@7llbG z3%O2LnJIT#o@8FAZ`GHi<#SKw-*EmrL-jN>&U>NGdA>?BQl(++rWUfwzX|JhM)z#b zKg@r8y!M%|QY?%$JE-{r5%zX61=atqz4&bN@1g_t@WutZ7{$tkHo;0O+3!gEL$0n1 zYqN6AZR|#I=eSxaXl+R^>8R0=9prgRne32Ms1;AvN}-3=2YK;lrt+V&UAIbm5ti;n z+vnT~tY5p%8CYCCV{S=q<>Dm^=f!5Rtje+lr3v?!JT~B>=BF)OGJih%h*8v~p_C=l zOXigBo-Q{|SG~u>nm66!Ss=Ui9#4hZ8jC>9E_}udOGk5R3{Vo|;Tr_9;;?kK@$|~nf9=nHw z$G|-~Ojx23x<@-Tz84#yyRv@qT^qYQ1@WC+58b)-kd?2yx?$@7x#Gj+-&ekUswM=DB3gm7}n)g@ti#c@TnqDOqcS&-G8WD~6#^L5nax(nUA zvkr63wAQb^(hu+Fvb!tUUnR}vu4G@9r9eF|d2X;T?@IPZ3G1sV+UuZr?8w(B!di&Y zWw^$y+4_Y$_=c`{&3pgd?+WWWyPCZx9Ir~^If3UeNuOedUAWt7C)3)k=CQ+$7e&`^ z>$HY5&NTnAsohXWpa1Y&mpP~*e|~3IZKfSF<^*;Z&sHDt9D4&!T*|d(Dko;<5f!J#n`UGu{wouSBaQR z*+Yf##D|fHdoehST>zd!!e|XJeCU-pAb#jHeL;CCb8X7>qIulPJ${DfJs^Hp+~Z8@-eGOf$}HoJ2g{ zjPsr-5f_<}u2|wp^m%5K?|g|moMy&(&yXnJEHlbiCK1myBP}C8e)G&I!$Px*%y`>` zw=6ifRc54JEpaWblsI4N?`kPWS|h>myGA1IwGxantd-hJ>m=AQ(l;cI@qh%QEZ>p1 zK95T&d(!MFGwSFuiSV?9ANBElse|;g1ZyR|Byqk!kYJSM=Tdv=mlBLJ|4O3FZ%Z() z`OhTU#ye8#_Z!(KNWV9uEI*aV;}2#W?_H^r^hY!D{a=Z8^=C8u{vvUGKQimC_(O@~ z{WV}!vgr>LgtFk*R0iw-Gx|Vt+3eo5FvG8cb(KvXxq)8~+4PrQX4FA%+4PsbW~3Qxd$?or9c6p0W6u37 z+mjrVc8cw(0Xxt3bjOr=j%?b^Trqi^g|eF}t_b{=*uKayZF__5 zFFR%&I8oBqC}p&vmJ;E1{gCafm!zoux%-ML`@K`{ARFhM{c0^)^KqX4+!->{`eQ)t z%Bti0o~(aZ`(5f|Wy-%_9r@Kptbgc9w3KXHLWO(}ZI&Db62{EHh{WvIQGziNc9Iw!nL%LPq+SxEZf^<3>`0r=k!V|KTCOwc z>2I(Dq|+q=YxR_kiAk~=f)OT3tWu^(DO)DHKw2I!Rv}#L{|MO4vX@Bf%_!4XCBj`2 z$19iamI#|9*iz|U=^p885{wnfMk!>}%Y72%VD*CEV(Gsm!b1}5V(DRt@GS|pLV8pp zuzG=gPWrY)_-_feH_yHr_w1w3sB7W@kzB)&P0OIwbe2d<%gB>HC7mL{Xc@GY94T!r z)LtKnmeE(jkCrh|B5;LMHZms3+8IVTOQK~INhzz4O}b?PyIeLc(hiC6eF;Xx`5%e!BMCNCdPO3lI%uF;?&$U1R$?#kbpL4*P;cYoVpAlfEcX+V#>c(rpsy zHz@wH?ev&m@%OJfmfI&`Ki5F|281p_9MU(=vz>oy?!JWkLN$L$*0Z$dnon%$K~E%E z$Gs2_dg&H3dLfKn`aLsxA&g$S)r?*UqnAE!mi9>Uc*!j7k@V6ZN>sOh+M}p(Z(Hm> zs(yb&3DP$_BG$H;dl0g_SG9kJlU@9CA6OEZ@DT#eUHkLRVK4v z_4|f{{8o2TMr|iB~y%_lIFncxdd)@4< z!0#Qi_X58U%>Ewu?KC@3LqD?&hirHy_-w?CHz;C?S z)WGk2v)O^)T(b)Uze=-}f!}Jgs{+3@W;X|ZUozVm_-!(~Kk$3d>|24~V`f_ezvs(3@`&*}NW=7gJvdOot8EKC* zJKl_Zo69EO&zO<6pX|Ph`qQ4y1;L} z*R=m-Sw43a=*^E3MFnidH@;xh?J(jIzly95ab7maxQQ4I5c{9?!Dm!2C zYi6YViP`IBok~X5?|8J}6&v#c5k>VRn!i$NNBO$fKp1 zqUbaFpnUDjDBlTYIcAjaP}!tyZ>A{fqYv)h`kIlppILu1MbQ|23{`xl8GeOkW6h2i z8z`GF-i$JLG@EF~*BSa4rMSe5#lPWlwa0L>W@nO7;RZ@l7kolAXnX_3@dFU1_2dhKgA z#0in?y}% z%QZsBJ-NsJx2ogXH+6CUn;Eo|*={H0K|5J&wleTrZMG)xyUuKV;CHLpU1qia8ll&m z3)$}9)nC*4Ze!L}@yQZ>B+sma;=U4NRzI`$idoa}8#{>f5Bl0r$N7q3$4ZQ`gC)jp z7<=jCB#t*;O8w4}-Ay{zjP}5;BYkwL8TJ&3aje)(QT!%)GsUH5_|22rOY_YX#s3Ev z{3-*sOg4;N3d+J5vr1|!t(IWa$LDNc=~z+ppAu>Bm+;f@7(abewCfoP_Cr!L9WDH8 zp8fH60H0j{f2Zr8JY}Vw5T1}|J6k0f^Xp3z{c5|E`u##Ses7s+nu^~AQxv^thW);T z-=CzkEq*AQ@HYvjC1b>fP9F&Xqn|aAK_6{qMjn5c2+hrol+C+aghS1+50g#4tpnD< zjQ6qhUmUudp8U2xWve+@57O>H^ z7-@Oesz7o2E*15%NGw-z`u-H-*=!$YnPZ{oV%e1CvVdJ-wkBZLncWbu^=7vOY?Il& z0sE%eLuQ=YW3m}DzimdDzhm~eSr@Sse7(Oeg8n&K9Uh2wCTY|4B@z*?Cd z8?ZKJ#|7+kv;2UKFdG%HF=mAUJKJn>z*d^AGGpEtCYx|W;CG{J=F|-V+a#OwxX+9> z`yJUiiXRWyoA!IhOg~4zH~T}t{${q*jCN2Zn|wPeqqP3I$R^*eX4J^Lg7> z1%6lC?^?4~^1EI(@eO7?W4J{&YlsbIIkMN=zRhuau{&))60mRCe!($q{6*QPDBf;H zUF|S?*-R+uA=+jQft0ZS&W~_)(U=vS~{L0yfa> zw15pZ8yc{Dvk_(-r;lvTXQUZzqnGVbj_Io>%I>c?Wiq2NVw`WG{a_Q!ip&^urpoTD zxY&&R&Xe6;@%d(4!*gw4;P}&GlVo#Wmok}Ai5U5o+7DJ{HqVSQFPB}YctyZg$sR4X zI$&4JW?s71OlEY8Z1UJ(c8u)XW%p2gN8tBW`)xGi8a*VN@R%9r`)#wQ%=rDgvdQBa zGs^s|?dJl&7wq@48G9fE7ltYu%d{YK#TGu!VwW?k?{+uV@RuDIq0*lrcD*0wu2 z=G?l-X1>WaQxxTy^)VBQ&XUc!6`65vlVx{Qd~V=ZY`^o(m~$?$T^jh!wqKbU{bQc( z#R03ZeTieryj(VO=%r?gqE%*>n^CSSY+nm2xXHR~Dp^)VY1_?>PxGVmL1 zHX-mUGMgIsoo`ke_{}j}6!=w`T@v`MG`lMBTVu91@VnXU%Yok=W*Y;)O=g<|zX!}7 z4*VW9dm`}LV)ks{_ng^|!0!iUuLOQSG5bZ}_m5|7J`DW+V)l=~ugdH|?>*2C z4w5Y#9cD&5IMS?5;CH-P$H4EOu>Tg9Wq6x7e@Y Q{U`o^AN>8~o^Hqf9~E!sJ^%m! diff --git a/objc.xcodeproj/project.pbxproj b/objc.xcodeproj/project.pbxproj index 1cd3e32..a7cc501 100644 --- a/objc.xcodeproj/project.pbxproj +++ b/objc.xcodeproj/project.pbxproj @@ -25,6 +25,11 @@ 393CEAC60DC69E67000B69DE /* objc-references.h in Headers */ = {isa = PBXBuildFile; fileRef = 393CEAC50DC69E67000B69DE /* objc-references.h */; }; 39ABD72312F0B61800D1054C /* objc-weak.h in Headers */ = {isa = PBXBuildFile; fileRef = 39ABD71F12F0B61800D1054C /* objc-weak.h */; }; 39ABD72412F0B61800D1054C /* objc-weak.mm in Sources */ = {isa = PBXBuildFile; fileRef = 39ABD72012F0B61800D1054C /* objc-weak.mm */; }; + 7593EC58202248E50046AB96 /* objc-object.h in Headers */ = {isa = PBXBuildFile; fileRef = 7593EC57202248DF0046AB96 /* objc-object.h */; }; + 75A9504F202BAA0600D7D56F /* objc-locks-new.h in Headers */ = {isa = PBXBuildFile; fileRef = 75A9504E202BAA0300D7D56F /* objc-locks-new.h */; }; + 75A95051202BAA9A00D7D56F /* objc-locks.h in Headers */ = {isa = PBXBuildFile; fileRef = 75A95050202BAA9A00D7D56F /* objc-locks.h */; }; + 75A95053202BAC4100D7D56F /* objc-lockdebug.h in Headers */ = {isa = PBXBuildFile; fileRef = 75A95052202BAC4100D7D56F /* objc-lockdebug.h */; }; + 8306440920D24A5D00E356D2 /* objc-block-trampolines.h in Headers */ = {isa = PBXBuildFile; fileRef = 8306440620D24A3E00E356D2 /* objc-block-trampolines.h */; settings = {ATTRIBUTES = (Private, ); }; }; 830F2A740D737FB800392440 /* objc-msg-arm.s in Sources */ = {isa = PBXBuildFile; fileRef = 830F2A690D737FB800392440 /* objc-msg-arm.s */; }; 830F2A750D737FB900392440 /* objc-msg-i386.s in Sources */ = {isa = PBXBuildFile; fileRef = 830F2A6A0D737FB800392440 /* objc-msg-i386.s */; }; 830F2A7D0D737FBB00392440 /* objc-msg-x86_64.s in Sources */ = {isa = PBXBuildFile; fileRef = 830F2A720D737FB800392440 /* objc-msg-x86_64.s */; }; @@ -37,9 +42,6 @@ 834DF8B715993EE1002F2BC9 /* objc-sel-old.mm in Sources */ = {isa = PBXBuildFile; fileRef = 834DF8B615993EE1002F2BC9 /* objc-sel-old.mm */; }; 834EC0A411614167009B2563 /* objc-abi.h in Headers */ = {isa = PBXBuildFile; fileRef = 834EC0A311614167009B2563 /* objc-abi.h */; settings = {ATTRIBUTES = (Private, ); }; }; 83725F4A14CA5BFA0014370E /* objc-opt.mm in Sources */ = {isa = PBXBuildFile; fileRef = 83725F4914CA5BFA0014370E /* objc-opt.mm */; }; - 8379996E13CBAF6F007C2B5F /* a1a2-blocktramps-arm64.s in Sources */ = {isa = PBXBuildFile; fileRef = 8379996D13CBAF6F007C2B5F /* a1a2-blocktramps-arm64.s */; }; - 8383A3A3122600E9009290B8 /* a1a2-blocktramps-arm.s in Sources */ = {isa = PBXBuildFile; fileRef = 8383A3A1122600E9009290B8 /* a1a2-blocktramps-arm.s */; }; - 8383A3A4122600E9009290B8 /* a2a3-blocktramps-arm.s in Sources */ = {isa = PBXBuildFile; fileRef = 8383A3A2122600E9009290B8 /* a2a3-blocktramps-arm.s */; }; 838485BF0D6D687300CEA253 /* hashtable2.h in Headers */ = {isa = PBXBuildFile; fileRef = 838485B70D6D687300CEA253 /* hashtable2.h */; settings = {ATTRIBUTES = (Public, ); }; }; 838485C00D6D687300CEA253 /* hashtable2.mm in Sources */ = {isa = PBXBuildFile; fileRef = 838485B80D6D687300CEA253 /* hashtable2.mm */; }; 838485C30D6D687300CEA253 /* maptable.h in Headers */ = {isa = PBXBuildFile; fileRef = 838485BB0D6D687300CEA253 /* maptable.h */; settings = {ATTRIBUTES = (Private, ); }; }; @@ -95,16 +97,16 @@ 83C9C3391668B50E00F4E544 /* objc-msg-simulator-x86_64.s in Sources */ = {isa = PBXBuildFile; fileRef = 83C9C3381668B50E00F4E544 /* objc-msg-simulator-x86_64.s */; }; 83D49E4F13C7C84F0057F1DD /* objc-msg-arm64.s in Sources */ = {isa = PBXBuildFile; fileRef = 83D49E4E13C7C84F0057F1DD /* objc-msg-arm64.s */; }; 83EB007B121C9EC200B92C16 /* objc-sel-table.s in Sources */ = {isa = PBXBuildFile; fileRef = 83EB007A121C9EC200B92C16 /* objc-sel-table.s */; }; + 83EF5E9820D2298400F486A4 /* objc-blocktramps-i386.s in Sources */ = {isa = PBXBuildFile; fileRef = E8923D9C116AB2820071B552 /* objc-blocktramps-i386.s */; }; + 83EF5E9920D2298400F486A4 /* objc-blocktramps-x86_64.s in Sources */ = {isa = PBXBuildFile; fileRef = E8923D9D116AB2820071B552 /* objc-blocktramps-x86_64.s */; }; + 83EF5E9C20D2299E00F486A4 /* objc-blocktramps-arm.s in Sources */ = {isa = PBXBuildFile; fileRef = 8383A3A1122600E9009290B8 /* objc-blocktramps-arm.s */; }; 83F4B52815E843B100E0926F /* NSObjCRuntime.h in Headers */ = {isa = PBXBuildFile; fileRef = 83F4B52615E843B100E0926F /* NSObjCRuntime.h */; settings = {ATTRIBUTES = (Public, ); }; }; 83F4B52915E843B100E0926F /* NSObject.h in Headers */ = {isa = PBXBuildFile; fileRef = 83F4B52715E843B100E0926F /* NSObject.h */; settings = {ATTRIBUTES = (Public, ); }; }; 83F550E0155E030800E95D3B /* objc-cache-old.mm in Sources */ = {isa = PBXBuildFile; fileRef = 83F550DF155E030800E95D3B /* objc-cache-old.mm */; }; 87BB4EA70EC39854005D08E1 /* objc-probes.d in Sources */ = {isa = PBXBuildFile; fileRef = 87BB4E900EC39633005D08E1 /* objc-probes.d */; }; 9672F7EE14D5F488007CEC96 /* NSObject.mm in Sources */ = {isa = PBXBuildFile; fileRef = 9672F7ED14D5F488007CEC96 /* NSObject.mm */; }; - E8923DA1116AB2820071B552 /* a1a2-blocktramps-i386.s in Sources */ = {isa = PBXBuildFile; fileRef = E8923D9C116AB2820071B552 /* a1a2-blocktramps-i386.s */; }; - E8923DA2116AB2820071B552 /* a1a2-blocktramps-x86_64.s in Sources */ = {isa = PBXBuildFile; fileRef = E8923D9D116AB2820071B552 /* a1a2-blocktramps-x86_64.s */; }; - E8923DA3116AB2820071B552 /* a2a3-blocktramps-i386.s in Sources */ = {isa = PBXBuildFile; fileRef = E8923D9E116AB2820071B552 /* a2a3-blocktramps-i386.s */; }; - E8923DA4116AB2820071B552 /* a2a3-blocktramps-x86_64.s in Sources */ = {isa = PBXBuildFile; fileRef = E8923D9F116AB2820071B552 /* a2a3-blocktramps-x86_64.s */; }; E8923DA5116AB2820071B552 /* objc-block-trampolines.mm in Sources */ = {isa = PBXBuildFile; fileRef = E8923DA0116AB2820071B552 /* objc-block-trampolines.mm */; }; + F9BCC71B205C68E800DD9AFC /* objc-blocktramps-arm64.s in Sources */ = {isa = PBXBuildFile; fileRef = 8379996D13CBAF6F007C2B5F /* objc-blocktramps-arm64.s */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -115,6 +117,13 @@ remoteGlobalIDString = D2AAC0620554660B00DB518D; remoteInfo = objc; }; + F9BCC728205C6A0900DD9AFC /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 08FB7793FE84155DC02AAC07 /* Project object */; + proxyType = 1; + remoteGlobalIDString = F9BCC6CA205C68E800DD9AFC; + remoteInfo = "objc-trampolines"; + }; /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ @@ -122,6 +131,11 @@ 393CEAC50DC69E67000B69DE /* objc-references.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "objc-references.h"; path = "runtime/objc-references.h"; sourceTree = ""; }; 39ABD71F12F0B61800D1054C /* objc-weak.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "objc-weak.h"; path = "runtime/objc-weak.h"; sourceTree = ""; }; 39ABD72012F0B61800D1054C /* objc-weak.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = "objc-weak.mm"; path = "runtime/objc-weak.mm"; sourceTree = ""; }; + 7593EC57202248DF0046AB96 /* objc-object.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "objc-object.h"; path = "runtime/objc-object.h"; sourceTree = ""; }; + 75A9504E202BAA0300D7D56F /* objc-locks-new.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "objc-locks-new.h"; path = "runtime/objc-locks-new.h"; sourceTree = ""; }; + 75A95050202BAA9A00D7D56F /* objc-locks.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "objc-locks.h"; path = "runtime/objc-locks.h"; sourceTree = ""; }; + 75A95052202BAC4100D7D56F /* objc-lockdebug.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "objc-lockdebug.h"; path = "runtime/objc-lockdebug.h"; sourceTree = ""; }; + 8306440620D24A3E00E356D2 /* objc-block-trampolines.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "objc-block-trampolines.h"; path = "runtime/objc-block-trampolines.h"; sourceTree = ""; }; 830F2A690D737FB800392440 /* objc-msg-arm.s */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.asm; name = "objc-msg-arm.s"; path = "runtime/Messengers.subproj/objc-msg-arm.s"; sourceTree = ""; }; 830F2A6A0D737FB800392440 /* objc-msg-i386.s */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.asm; name = "objc-msg-i386.s"; path = "runtime/Messengers.subproj/objc-msg-i386.s"; sourceTree = ""; }; 830F2A720D737FB800392440 /* objc-msg-x86_64.s */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.asm; name = "objc-msg-x86_64.s"; path = "runtime/Messengers.subproj/objc-msg-x86_64.s"; sourceTree = ""; tabWidth = 8; usesTabs = 1; }; @@ -135,9 +149,8 @@ 834DF8B615993EE1002F2BC9 /* objc-sel-old.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = "objc-sel-old.mm"; path = "runtime/objc-sel-old.mm"; sourceTree = ""; }; 834EC0A311614167009B2563 /* objc-abi.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "objc-abi.h"; path = "runtime/objc-abi.h"; sourceTree = ""; }; 83725F4914CA5BFA0014370E /* objc-opt.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = "objc-opt.mm"; path = "runtime/objc-opt.mm"; sourceTree = ""; }; - 8379996D13CBAF6F007C2B5F /* a1a2-blocktramps-arm64.s */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.asm; name = "a1a2-blocktramps-arm64.s"; path = "runtime/a1a2-blocktramps-arm64.s"; sourceTree = ""; }; - 8383A3A1122600E9009290B8 /* a1a2-blocktramps-arm.s */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.asm; name = "a1a2-blocktramps-arm.s"; path = "runtime/a1a2-blocktramps-arm.s"; sourceTree = ""; }; - 8383A3A2122600E9009290B8 /* a2a3-blocktramps-arm.s */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.asm; name = "a2a3-blocktramps-arm.s"; path = "runtime/a2a3-blocktramps-arm.s"; sourceTree = ""; }; + 8379996D13CBAF6F007C2B5F /* objc-blocktramps-arm64.s */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.asm; name = "objc-blocktramps-arm64.s"; path = "runtime/objc-blocktramps-arm64.s"; sourceTree = ""; }; + 8383A3A1122600E9009290B8 /* objc-blocktramps-arm.s */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.asm; name = "objc-blocktramps-arm.s"; path = "runtime/objc-blocktramps-arm.s"; sourceTree = ""; }; 838485B30D6D682B00CEA253 /* libobjc.order */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = libobjc.order; sourceTree = ""; }; 838485B40D6D683300CEA253 /* APPLE_LICENSE */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = APPLE_LICENSE; sourceTree = ""; }; 838485B50D6D683300CEA253 /* ReleaseNotes.rtf */ = {isa = PBXFileReference; lastKnownFileType = text.rtf; path = ReleaseNotes.rtf; sourceTree = ""; }; @@ -204,11 +217,10 @@ 9672F7ED14D5F488007CEC96 /* NSObject.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = NSObject.mm; path = runtime/NSObject.mm; sourceTree = ""; }; BC8B5D1212D3D48100C78A5B /* libauto.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libauto.dylib; path = /usr/lib/libauto.dylib; sourceTree = ""; }; D2AAC0630554660B00DB518D /* libobjc.A.dylib */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.dylib"; includeInIndex = 0; path = libobjc.A.dylib; sourceTree = BUILT_PRODUCTS_DIR; }; - E8923D9C116AB2820071B552 /* a1a2-blocktramps-i386.s */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.asm; name = "a1a2-blocktramps-i386.s"; path = "runtime/a1a2-blocktramps-i386.s"; sourceTree = ""; }; - E8923D9D116AB2820071B552 /* a1a2-blocktramps-x86_64.s */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.asm; name = "a1a2-blocktramps-x86_64.s"; path = "runtime/a1a2-blocktramps-x86_64.s"; sourceTree = ""; }; - E8923D9E116AB2820071B552 /* a2a3-blocktramps-i386.s */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.asm; name = "a2a3-blocktramps-i386.s"; path = "runtime/a2a3-blocktramps-i386.s"; sourceTree = ""; }; - E8923D9F116AB2820071B552 /* a2a3-blocktramps-x86_64.s */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.asm; name = "a2a3-blocktramps-x86_64.s"; path = "runtime/a2a3-blocktramps-x86_64.s"; sourceTree = ""; }; + E8923D9C116AB2820071B552 /* objc-blocktramps-i386.s */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.asm; name = "objc-blocktramps-i386.s"; path = "runtime/objc-blocktramps-i386.s"; sourceTree = ""; }; + E8923D9D116AB2820071B552 /* objc-blocktramps-x86_64.s */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.asm; name = "objc-blocktramps-x86_64.s"; path = "runtime/objc-blocktramps-x86_64.s"; sourceTree = ""; }; E8923DA0116AB2820071B552 /* objc-block-trampolines.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = "objc-block-trampolines.mm"; path = "runtime/objc-block-trampolines.mm"; sourceTree = ""; }; + F9BCC727205C68E800DD9AFC /* libobjc-trampolines.dylib */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.dylib"; includeInIndex = 0; path = "libobjc-trampolines.dylib"; sourceTree = BUILT_PRODUCTS_DIR; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -219,6 +231,13 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + F9BCC721205C68E800DD9AFC /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ @@ -234,6 +253,7 @@ 08FB7795FE84155DC02AAC07 /* Source */, 838485B20D6D67F900CEA253 /* Other */, 1AB674ADFE9D54B511CA2CBB /* Products */, + F9BCC72A205C6A1600DD9AFC /* Frameworks */, ); name = objc; sourceTree = ""; @@ -241,8 +261,6 @@ 08FB7795FE84155DC02AAC07 /* Source */ = { isa = PBXGroup; children = ( - 8383A3A1122600E9009290B8 /* a1a2-blocktramps-arm.s */, - 8383A3A2122600E9009290B8 /* a2a3-blocktramps-arm.s */, 838485B80D6D687300CEA253 /* hashtable2.mm */, 838485BC0D6D687300CEA253 /* maptable.mm */, 9672F7ED14D5F488007CEC96 /* NSObject.mm */, @@ -276,11 +294,10 @@ 834DF8B615993EE1002F2BC9 /* objc-sel-old.mm */, 838485EA0D6D68A200CEA253 /* objc-sync.mm */, 838485EB0D6D68A200CEA253 /* objc-typeencoding.mm */, - 8379996D13CBAF6F007C2B5F /* a1a2-blocktramps-arm64.s */, - E8923D9C116AB2820071B552 /* a1a2-blocktramps-i386.s */, - E8923D9D116AB2820071B552 /* a1a2-blocktramps-x86_64.s */, - E8923D9E116AB2820071B552 /* a2a3-blocktramps-i386.s */, - E8923D9F116AB2820071B552 /* a2a3-blocktramps-x86_64.s */, + 8383A3A1122600E9009290B8 /* objc-blocktramps-arm.s */, + 8379996D13CBAF6F007C2B5F /* objc-blocktramps-arm64.s */, + E8923D9C116AB2820071B552 /* objc-blocktramps-i386.s */, + E8923D9D116AB2820071B552 /* objc-blocktramps-x86_64.s */, 830F2A690D737FB800392440 /* objc-msg-arm.s */, 83D49E4E13C7C84F0057F1DD /* objc-msg-arm64.s */, 830F2A6A0D737FB800392440 /* objc-msg-i386.s */, @@ -296,6 +313,7 @@ isa = PBXGroup; children = ( D2AAC0630554660B00DB518D /* libobjc.A.dylib */, + F9BCC727205C68E800DD9AFC /* libobjc-trampolines.dylib */, ); name = Products; sourceTree = ""; @@ -337,6 +355,7 @@ 834EC0A311614167009B2563 /* objc-abi.h */, 838485BB0D6D687300CEA253 /* maptable.h */, 834266D70E665A8B002E4DA2 /* objc-gdb.h */, + 8306440620D24A3E00E356D2 /* objc-block-trampolines.h */, ); name = "Private Headers"; sourceTree = ""; @@ -369,10 +388,14 @@ isa = PBXGroup; children = ( 838485CF0D6D68A200CEA253 /* objc-config.h */, - 83BE02E60FCCB24D00661494 /* objc-file.h */, 83BE02E50FCCB24D00661494 /* objc-file-old.h */, + 83BE02E60FCCB24D00661494 /* objc-file.h */, 838485D40D6D68A200CEA253 /* objc-initialize.h */, 838485D90D6D68A200CEA253 /* objc-loadmethod.h */, + 75A9504E202BAA0300D7D56F /* objc-locks-new.h */, + 75A95052202BAC4100D7D56F /* objc-lockdebug.h */, + 75A95050202BAA9A00D7D56F /* objc-locks.h */, + 7593EC57202248DF0046AB96 /* objc-object.h */, 831C85D30E10CF850066E64C /* objc-os.h */, 838485DC0D6D68A200CEA253 /* objc-private.h */, 393CEAC50DC69E67000B69DE /* objc-references.h */, @@ -384,14 +407,30 @@ name = "Project Headers"; sourceTree = ""; }; + F9BCC72A205C6A1600DD9AFC /* Frameworks */ = { + isa = PBXGroup; + children = ( + ); + name = Frameworks; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXHeadersBuildPhase section */ + 8306440820D24A5300E356D2 /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + 8306440920D24A5D00E356D2 /* objc-block-trampolines.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; D2AAC0600554660B00DB518D /* Headers */ = { isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( 83A4AEDE1EA08C7200ACADDE /* ObjectiveC.apinotes in Headers */, + 75A95051202BAA9A00D7D56F /* objc-locks.h in Headers */, 83A4AEDC1EA0840800ACADDE /* module.modulemap in Headers */, 830F2A980D738DC200392440 /* hashtable.h in Headers */, 838485BF0D6D687300CEA253 /* hashtable2.h in Headers */, @@ -406,11 +445,14 @@ 838485F80D6D68A200CEA253 /* objc-exception.h in Headers */, 83BE02E80FCCB24D00661494 /* objc-file-old.h in Headers */, 83BE02E90FCCB24D00661494 /* objc-file.h in Headers */, + 75A9504F202BAA0600D7D56F /* objc-locks-new.h in Headers */, 834266D80E665A8B002E4DA2 /* objc-gdb.h in Headers */, 838485FB0D6D68A200CEA253 /* objc-initialize.h in Headers */, + 7593EC58202248E50046AB96 /* objc-object.h in Headers */, 83112ED40F00599600A5FBAF /* objc-internal.h in Headers */, 838485FE0D6D68A200CEA253 /* objc-load.h in Headers */, 838486000D6D68A200CEA253 /* objc-loadmethod.h in Headers */, + 75A95053202BAC4100D7D56F /* objc-lockdebug.h in Headers */, 831C85D50E10CF850066E64C /* objc-os.h in Headers */, 838486030D6D68A200CEA253 /* objc-private.h in Headers */, 393CEAC60DC69E67000B69DE /* objc-references.h in Headers */, @@ -445,12 +487,30 @@ buildRules = ( ); dependencies = ( + F9BCC729205C6A0900DD9AFC /* PBXTargetDependency */, ); name = objc; productName = objc; productReference = D2AAC0630554660B00DB518D /* libobjc.A.dylib */; productType = "com.apple.product-type.library.dynamic"; }; + F9BCC6CA205C68E800DD9AFC /* objc-trampolines */ = { + isa = PBXNativeTarget; + buildConfigurationList = F9BCC724205C68E800DD9AFC /* Build configuration list for PBXNativeTarget "objc-trampolines" */; + buildPhases = ( + 8306440820D24A5300E356D2 /* Headers */, + F9BCC6EF205C68E800DD9AFC /* Sources */, + F9BCC721205C68E800DD9AFC /* Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = "objc-trampolines"; + productName = objc; + productReference = F9BCC727205C68E800DD9AFC /* libobjc-trampolines.dylib */; + productType = "com.apple.product-type.library.dynamic"; + }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ @@ -481,6 +541,7 @@ targets = ( D2AAC0620554660B00DB518D /* objc */, 837F67A81A771F63004D34FA /* objc-simulator */, + F9BCC6CA205C68E800DD9AFC /* objc-trampolines */, ); }; /* End PBXProject section */ @@ -499,7 +560,7 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "set -x\n/usr/bin/xcrun -sdk macosx clang++ -Wall -mmacosx-version-min=10.9 -arch x86_64 -std=c++11 \"${SRCROOT}/markgc.cpp\" -o \"${BUILT_PRODUCTS_DIR}/markgc\"\n\"${BUILT_PRODUCTS_DIR}/markgc\" \"${BUILT_PRODUCTS_DIR}/libobjc.A.dylib\""; + shellScript = "set -x\n/usr/bin/xcrun -sdk macosx.internal clang++ -Wall -mmacosx-version-min=10.12 -arch x86_64 -std=c++11 \"${SRCROOT}/markgc.cpp\" -o \"${BUILT_PRODUCTS_DIR}/markgc\"\n\"${BUILT_PRODUCTS_DIR}/markgc\" \"${BUILT_PRODUCTS_DIR}/libobjc.A.dylib\""; }; 830F2AFA0D73BC5800392440 /* Run Script (symlink) */ = { isa = PBXShellScriptBuildPhase; @@ -554,18 +615,11 @@ 831C85D60E10CF850066E64C /* objc-os.mm in Sources */, 87BB4EA70EC39854005D08E1 /* objc-probes.d in Sources */, 83BE02E40FCCB23400661494 /* objc-file-old.mm in Sources */, - E8923DA1116AB2820071B552 /* a1a2-blocktramps-i386.s in Sources */, - E8923DA2116AB2820071B552 /* a1a2-blocktramps-x86_64.s in Sources */, - E8923DA3116AB2820071B552 /* a2a3-blocktramps-i386.s in Sources */, - E8923DA4116AB2820071B552 /* a2a3-blocktramps-x86_64.s in Sources */, E8923DA5116AB2820071B552 /* objc-block-trampolines.mm in Sources */, 83B1A8BE0FF1AC0D0019EA5B /* objc-msg-simulator-i386.s in Sources */, 83EB007B121C9EC200B92C16 /* objc-sel-table.s in Sources */, - 8383A3A3122600E9009290B8 /* a1a2-blocktramps-arm.s in Sources */, - 8383A3A4122600E9009290B8 /* a2a3-blocktramps-arm.s in Sources */, 39ABD72412F0B61800D1054C /* objc-weak.mm in Sources */, 83D49E4F13C7C84F0057F1DD /* objc-msg-arm64.s in Sources */, - 8379996E13CBAF6F007C2B5F /* a1a2-blocktramps-arm64.s in Sources */, 9672F7EE14D5F488007CEC96 /* NSObject.mm in Sources */, 83725F4A14CA5BFA0014370E /* objc-opt.mm in Sources */, 83F550E0155E030800E95D3B /* objc-cache-old.mm in Sources */, @@ -574,6 +628,17 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + F9BCC6EF205C68E800DD9AFC /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 83EF5E9C20D2299E00F486A4 /* objc-blocktramps-arm.s in Sources */, + 83EF5E9820D2298400F486A4 /* objc-blocktramps-i386.s in Sources */, + 83EF5E9920D2298400F486A4 /* objc-blocktramps-x86_64.s in Sources */, + F9BCC71B205C68E800DD9AFC /* objc-blocktramps-arm64.s in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ @@ -582,6 +647,11 @@ target = D2AAC0620554660B00DB518D /* objc */; targetProxy = 837F67AC1A771F6E004D34FA /* PBXContainerItemProxy */; }; + F9BCC729205C6A0900DD9AFC /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = F9BCC6CA205C68E800DD9AFC /* objc-trampolines */; + targetProxy = F9BCC728205C6A0900DD9AFC /* PBXContainerItemProxy */; + }; /* End PBXTargetDependency section */ /* Begin XCBuildConfiguration section */ @@ -606,6 +676,7 @@ /System/Library/Frameworks/System.framework/PrivateHeaders, ); INSTALL_PATH = /usr/lib; + IS_ZIPPERED = YES; ORDER_FILE = "$(SDKROOT)/AppleInternal/OrderFiles/libobjc.order"; "ORDER_FILE[sdk=iphonesimulator*]" = ""; OTHER_CFLAGS = ( @@ -651,9 +722,12 @@ "-Xlinker", interposable.txt, ); + OTHER_TAPI_FLAGS = "-exclude-public-header $(DSTROOT)/usr/include/objc/ObjectiveC.apinotes -exclude-public-header $(DSTROOT)/usr/include/objc/module.modulemap -Xparser -Wno-deprecated-declarations -Xparser -Wno-unavailable-declarations -Xparser -D_OBJC_PRIVATE_H_=1 -DOBJC_DECLARE_SYMBOLS=1"; PRIVATE_HEADERS_FOLDER_PATH = /usr/local/include/objc; PRODUCT_NAME = objc.A; PUBLIC_HEADERS_FOLDER_PATH = /usr/include/objc; + SUPPORTS_TEXT_BASED_API = YES; + TAPI_VERIFY_MODE = Pedantic; UNEXPORTED_SYMBOLS_FILE = unexported_symbols; }; name = Debug; @@ -676,6 +750,7 @@ /System/Library/Frameworks/System.framework/PrivateHeaders, ); INSTALL_PATH = /usr/lib; + IS_ZIPPERED = YES; ORDER_FILE = "$(SDKROOT)/AppleInternal/OrderFiles/libobjc.order"; "ORDER_FILE[sdk=iphonesimulator*]" = ""; OTHER_CFLAGS = ( @@ -721,17 +796,24 @@ "-Xlinker", interposable.txt, ); + OTHER_TAPI_FLAGS = "-exclude-public-header $(DSTROOT)/usr/include/objc/ObjectiveC.apinotes -exclude-public-header $(DSTROOT)/usr/include/objc/module.modulemap -Xparser -Wno-deprecated-declarations -Xparser -Wno-unavailable-declarations -Xparser -D_OBJC_PRIVATE_H_=1 -DOBJC_DECLARE_SYMBOLS=1"; PRIVATE_HEADERS_FOLDER_PATH = /usr/local/include/objc; PRODUCT_NAME = objc.A; PUBLIC_HEADERS_FOLDER_PATH = /usr/include/objc; + SUPPORTS_TEXT_BASED_API = YES; + TAPI_VERIFY_MODE = Pedantic; UNEXPORTED_SYMBOLS_FILE = unexported_symbols; + WARNING_CFLAGS = ( + "$(inherited)", + "-Wglobal-constructors", + ); }; name = Release; }; 1DEB914F08733D8E0010E9CD /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { - CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; CLANG_CXX_LIBRARY = "libc++"; CLANG_LINK_OBJC_RUNTIME = NO; CLANG_OBJC_RUNTIME = NO; @@ -772,7 +854,7 @@ 1DEB915008733D8E0010E9CD /* Release */ = { isa = XCBuildConfiguration; buildSettings = { - CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; CLANG_CXX_LIBRARY = "libc++"; CLANG_LINK_OBJC_RUNTIME = NO; CLANG_OBJC_RUNTIME = NO; @@ -829,6 +911,79 @@ }; name = Release; }; + F9BCC725205C68E800DD9AFC /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ARCHS = "$(ARCHS_STANDARD_32_64_BIT)"; + COPY_HEADERS_RUN_UNIFDEF = YES; + COPY_HEADERS_UNIFDEF_FLAGS = "-UBUILD_FOR_OSX"; + "COPY_HEADERS_UNIFDEF_FLAGS[sdk=macosx*]" = "-DBUILD_FOR_OSX"; + COPY_PHASE_STRIP = NO; + DYLIB_CURRENT_VERSION = 228; + EXECUTABLE_PREFIX = lib; + GCC_CW_ASM_SYNTAX = NO; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_WARN_ABOUT_DEPRECATED_FUNCTIONS = NO; + HEADER_SEARCH_PATHS = ( + "$(DSTROOT)/usr/include/**", + "$(DSTROOT)/usr/local/include/**", + "$(CONFIGURATION_BUILD_DIR)/usr/include/**", + "$(CONFIGURATION_BUILD_DIR)/usr/local/include/**", + /System/Library/Frameworks/System.framework/PrivateHeaders, + ); + INSTALL_PATH = /usr/lib; + IS_ZIPPERED = YES; + OTHER_CFLAGS = ( + "-fdollars-in-identifiers", + "$(OTHER_CFLAGS)", + ); + OTHER_LDFLAGS = ( + "-Xlinker", + "-not_for_dyld_shared_cache", + ); + PRIVATE_HEADERS_FOLDER_PATH = /usr/local/include/objc; + PRODUCT_NAME = "$(TARGET_NAME)"; + PUBLIC_HEADERS_FOLDER_PATH = /usr/include/objc; + SUPPORTS_TEXT_BASED_API = YES; + TAPI_VERIFY_MODE = Pedantic; + }; + name = Debug; + }; + F9BCC726205C68E800DD9AFC /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + COPY_HEADERS_RUN_UNIFDEF = YES; + COPY_HEADERS_UNIFDEF_FLAGS = "-UBUILD_FOR_OSX"; + "COPY_HEADERS_UNIFDEF_FLAGS[sdk=macosx*]" = "-DBUILD_FOR_OSX"; + DYLIB_CURRENT_VERSION = 228; + EXECUTABLE_PREFIX = lib; + GCC_CW_ASM_SYNTAX = NO; + GCC_WARN_ABOUT_DEPRECATED_FUNCTIONS = NO; + HEADER_SEARCH_PATHS = ( + "$(DSTROOT)/usr/include/**", + "$(DSTROOT)/usr/local/include/**", + "$(CONFIGURATION_BUILD_DIR)/usr/include/**", + "$(CONFIGURATION_BUILD_DIR)/usr/local/include/**", + /System/Library/Frameworks/System.framework/PrivateHeaders, + ); + INSTALL_PATH = /usr/lib; + IS_ZIPPERED = YES; + OTHER_CFLAGS = ( + "-fdollars-in-identifiers", + "$(OTHER_CFLAGS)", + ); + OTHER_LDFLAGS = ( + "-Xlinker", + "-not_for_dyld_shared_cache", + ); + PRIVATE_HEADERS_FOLDER_PATH = /usr/local/include/objc; + PRODUCT_NAME = "$(TARGET_NAME)"; + PUBLIC_HEADERS_FOLDER_PATH = /usr/include/objc; + SUPPORTS_TEXT_BASED_API = YES; + TAPI_VERIFY_MODE = Pedantic; + }; + name = Release; + }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ @@ -859,6 +1014,15 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; + F9BCC724205C68E800DD9AFC /* Build configuration list for PBXNativeTarget "objc-trampolines" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + F9BCC725205C68E800DD9AFC /* Debug */, + F9BCC726205C68E800DD9AFC /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; /* End XCConfigurationList section */ }; rootObject = 08FB7793FE84155DC02AAC07 /* Project object */; diff --git a/runtime/Messengers.subproj/objc-msg-arm.s b/runtime/Messengers.subproj/objc-msg-arm.s index bd88c6a..01419dc 100644 --- a/runtime/Messengers.subproj/objc-msg-arm.s +++ b/runtime/Messengers.subproj/objc-msg-arm.s @@ -29,6 +29,8 @@ #ifdef __arm__ #include +#include "objc-config.h" +#include "isa.h" #ifndef _ARM_ARCH_7 # error requires armv7 @@ -67,25 +69,6 @@ #endif -// Define SUPPORT_INDEXED_ISA for targets which store the class in the ISA as -// an index in to a class table. -// Note, keep this in sync with objc-config.h. -// FIXME: Remove this duplication. We should get this from objc-config.h. -#if __ARM_ARCH_7K__ >= 2 -# define SUPPORT_INDEXED_ISA 1 -#else -# define SUPPORT_INDEXED_ISA 0 -#endif - -// Note, keep these in sync with objc-private.h -#define ISA_INDEX_IS_NPI 1 -#define ISA_INDEX_MASK 0x0001FFFC -#define ISA_INDEX_SHIFT 2 -#define ISA_INDEX_BITS 15 -#define ISA_INDEX_COUNT (1 << ISA_INDEX_BITS) -#define ISA_INDEX_MAGIC_MASK 0x001E0001 -#define ISA_INDEX_MAGIC_VALUE 0x001C0001 - .syntax unified #define MI_EXTERN(var) \ @@ -155,62 +138,6 @@ _objc_exitPoints: .long LExit_objc_msgLookupSuper2_stret .long 0 - -/******************************************************************** -* List every exit insn from every messenger for debugger use. -* Format: -* ( -* 1 word instruction's address -* 1 word type (ENTER or FAST_EXIT or SLOW_EXIT or NIL_EXIT) -* ) -* 1 word zero -* -* ENTER is the start of a dispatcher -* FAST_EXIT is method dispatch -* SLOW_EXIT is uncached method lookup -* NIL_EXIT is returning zero from a message sent to nil -* These must match objc-gdb.h. -********************************************************************/ - -#define ENTER 1 -#define FAST_EXIT 2 -#define SLOW_EXIT 3 -#define NIL_EXIT 4 - -.section __DATA,__objc_msg_break -.globl _gdb_objc_messenger_breakpoints -_gdb_objc_messenger_breakpoints: -// contents populated by the macros below - -.macro MESSENGER_START -7: - .section __DATA,__objc_msg_break - .long 7b - .long ENTER - .text -.endmacro -.macro MESSENGER_END_FAST -7: - .section __DATA,__objc_msg_break - .long 7b - .long FAST_EXIT - .text -.endmacro -.macro MESSENGER_END_SLOW -7: - .section __DATA,__objc_msg_break - .long 7b - .long SLOW_EXIT - .text -.endmacro -.macro MESSENGER_END_NIL -7: - .section __DATA,__objc_msg_break - .long 7b - .long NIL_EXIT - .text -.endmacro - /******************************************************************** * Names for relative labels @@ -218,7 +145,7 @@ _gdb_objc_messenger_breakpoints: * Reserved labels: 6: 7: 8: 9: ********************************************************************/ // 6: used by CacheLookup -// 7: used by MI_GET_ADDRESS etc and MESSENGER_START etc +// 7: used by MI_GET_ADDRESS etc // 8: used by CacheLookup #define LNilReceiver 9 #define LNilReceiver_f 9f @@ -249,6 +176,10 @@ _gdb_objc_messenger_breakpoints: #define CACHE 8 #define CACHE_MASK 12 +/* Field offsets in method cache bucket */ +#define CACHED_SEL 0 +#define CACHED_IMP 4 + /* Selected field offsets in method structure */ #define METHOD_NAME 0 #define METHOD_TYPES 4 @@ -327,7 +258,7 @@ LExit$0: and r12, r12, r1 // r12 = index = SEL & mask .endif add r9, r9, r12, LSL #3 // r9 = bucket = buckets+index*8 - ldr r12, [r9] // r12 = bucket->sel + ldr r12, [r9, #CACHED_SEL] // r12 = bucket->sel 6: .if $0 == STRET teq r12, r2 @@ -335,7 +266,7 @@ LExit$0: teq r12, r1 .endif bne 8f - ldr r12, [r9, #4] // r12 = bucket->imp + ldr r12, [r9, #CACHED_IMP] // r12 = bucket->imp .if $0 == STRET tst r12, r12 // set ne for stret forwarding @@ -346,12 +277,14 @@ LExit$0: .endmacro .macro CacheLookup2 - +#if CACHED_SEL != 0 +# error this code requires that SEL be at offset 0 +#endif 8: cmp r12, #1 blo 8f // if (bucket->sel == 0) cache miss it eq // if (bucket->sel == 1) cache wrap - ldreq r9, [r9, #4] // bucket->imp is before first bucket + ldreq r9, [r9, #CACHED_IMP] // bucket->imp is before first bucket ldr r12, [r9, #8]! // r12 = (++bucket)->sel b 6b 8: @@ -377,7 +310,7 @@ LExit$0: // Note: We are doing a little wasted work here to load values we might not // need. Branching turns out to be even worse when performance was measured. MI_GET_ADDRESS(r12, _objc_indexed_classes) - tst.w r9, #ISA_INDEX_IS_NPI + tst.w r9, #ISA_INDEX_IS_NPI_MASK itt ne ubfxne r9, r9, #ISA_INDEX_SHIFT, #ISA_INDEX_BITS ldrne.w r9, [r12, r9, lsl #2] @@ -425,7 +358,6 @@ LExit$0: ********************************************************************/ ENTRY _objc_msgSend - MESSENGER_START cbz r0, LNilReceiver_f @@ -433,14 +365,12 @@ LExit$0: GetClassFromIsa // r9 = class CacheLookup NORMAL // cache hit, IMP in r12, eq already set for nonstret forwarding - MESSENGER_END_FAST bx r12 // call imp CacheLookup2 NORMAL // cache miss ldr r9, [r0] // r9 = self->isa GetClassFromIsa // r9 = class - MESSENGER_END_SLOW b __objc_msgSend_uncached LNilReceiver: @@ -449,7 +379,6 @@ LNilReceiver: mov r2, #0 mov r3, #0 FP_RETURN_ZERO - MESSENGER_END_NIL bx lr END_ENTRY _objc_msgSend @@ -504,7 +433,6 @@ LNilReceiver: ********************************************************************/ ENTRY _objc_msgSend_stret - MESSENGER_START cbz r1, LNilReceiver_f @@ -512,18 +440,15 @@ LNilReceiver: GetClassFromIsa // r9 = class CacheLookup STRET // cache hit, IMP in r12, ne already set for stret forwarding - MESSENGER_END_FAST bx r12 CacheLookup2 STRET // cache miss ldr r9, [r1] // r9 = self->isa GetClassFromIsa // r9 = class - MESSENGER_END_SLOW b __objc_msgSend_stret_uncached LNilReceiver: - MESSENGER_END_NIL bx lr END_ENTRY _objc_msgSend_stret @@ -569,20 +494,17 @@ LNilReceiver: ********************************************************************/ ENTRY _objc_msgSendSuper - MESSENGER_START ldr r9, [r0, #CLASS] // r9 = struct super->class CacheLookup NORMAL // cache hit, IMP in r12, eq already set for nonstret forwarding ldr r0, [r0, #RECEIVER] // load real receiver - MESSENGER_END_FAST bx r12 // call imp CacheLookup2 NORMAL // cache miss ldr r9, [r0, #CLASS] // r9 = struct super->class ldr r0, [r0, #RECEIVER] // load real receiver - MESSENGER_END_SLOW b __objc_msgSend_uncached END_ENTRY _objc_msgSendSuper @@ -598,14 +520,12 @@ LNilReceiver: ********************************************************************/ ENTRY _objc_msgSendSuper2 - MESSENGER_START ldr r9, [r0, #CLASS] // class = struct super->class ldr r9, [r9, #SUPERCLASS] // class = class->superclass CacheLookup NORMAL // cache hit, IMP in r12, eq already set for nonstret forwarding ldr r0, [r0, #RECEIVER] // load real receiver - MESSENGER_END_FAST bx r12 // call imp CacheLookup2 NORMAL @@ -613,7 +533,6 @@ LNilReceiver: ldr r9, [r0, #CLASS] // class = struct super->class ldr r9, [r9, #SUPERCLASS] // class = class->superclass ldr r0, [r0, #RECEIVER] // load real receiver - MESSENGER_END_SLOW b __objc_msgSend_uncached END_ENTRY _objc_msgSendSuper2 @@ -651,20 +570,17 @@ LNilReceiver: ********************************************************************/ ENTRY _objc_msgSendSuper_stret - MESSENGER_START ldr r9, [r1, #CLASS] // r9 = struct super->class CacheLookup STRET // cache hit, IMP in r12, ne already set for stret forwarding ldr r1, [r1, #RECEIVER] // load real receiver - MESSENGER_END_FAST bx r12 // call imp CacheLookup2 STRET // cache miss ldr r9, [r1, #CLASS] // r9 = struct super->class ldr r1, [r1, #RECEIVER] // load real receiver - MESSENGER_END_SLOW b __objc_msgSend_stret_uncached END_ENTRY _objc_msgSendSuper_stret @@ -675,14 +591,12 @@ LNilReceiver: ********************************************************************/ ENTRY _objc_msgSendSuper2_stret - MESSENGER_START ldr r9, [r1, #CLASS] // class = struct super->class ldr r9, [r9, #SUPERCLASS] // class = class->superclass CacheLookup STRET // cache hit, IMP in r12, ne already set for stret forwarding ldr r1, [r1, #RECEIVER] // load real receiver - MESSENGER_END_FAST bx r12 // call imp CacheLookup2 STRET @@ -690,7 +604,6 @@ LNilReceiver: ldr r9, [r1, #CLASS] // class = struct super->class ldr r9, [r9, #SUPERCLASS] // class = class->superclass ldr r1, [r1, #RECEIVER] // load real receiver - MESSENGER_END_SLOW b __objc_msgSend_stret_uncached END_ENTRY _objc_msgSendSuper2_stret @@ -837,10 +750,6 @@ LNilReceiver: // THIS IS NOT A CALLABLE C FUNCTION // Out-of-band Z is 0 (EQ) for normal, 1 (NE) for stret - MESSENGER_START - nop - MESSENGER_END_SLOW - beq __objc_msgForward b __objc_msgForward_stret diff --git a/runtime/Messengers.subproj/objc-msg-arm64.s b/runtime/Messengers.subproj/objc-msg-arm64.s index c24de97..8cef3e1 100755 --- a/runtime/Messengers.subproj/objc-msg-arm64.s +++ b/runtime/Messengers.subproj/objc-msg-arm64.s @@ -27,9 +27,10 @@ ********************************************************************/ #ifdef __arm64__ - -#include +#include +#include "isa.h" +#include "arm64-asm.h" .data @@ -41,96 +42,84 @@ .align 4 .private_extern _objc_entryPoints _objc_entryPoints: - .quad _cache_getImp - .quad _objc_msgSend - .quad _objc_msgSendSuper - .quad _objc_msgSendSuper2 - .quad _objc_msgLookup - .quad _objc_msgLookupSuper2 - .quad 0 + PTR _cache_getImp + PTR _objc_msgSend + PTR _objc_msgSendSuper + PTR _objc_msgSendSuper2 + PTR _objc_msgLookup + PTR _objc_msgLookupSuper2 + PTR 0 .private_extern _objc_exitPoints _objc_exitPoints: - .quad LExit_cache_getImp - .quad LExit_objc_msgSend - .quad LExit_objc_msgSendSuper - .quad LExit_objc_msgSendSuper2 - .quad LExit_objc_msgLookup - .quad LExit_objc_msgLookupSuper2 - .quad 0 - - -/******************************************************************** -* List every exit insn from every messenger for debugger use. -* Format: -* ( -* 1 word instruction's address -* 1 word type (ENTER or FAST_EXIT or SLOW_EXIT or NIL_EXIT) -* ) -* 1 word zero -* -* ENTER is the start of a dispatcher -* FAST_EXIT is method dispatch -* SLOW_EXIT is uncached method lookup -* NIL_EXIT is returning zero from a message sent to nil -* These must match objc-gdb.h. -********************************************************************/ - -#define ENTER 1 -#define FAST_EXIT 2 -#define SLOW_EXIT 3 -#define NIL_EXIT 4 - -.section __DATA,__objc_msg_break -.globl _gdb_objc_messenger_breakpoints -_gdb_objc_messenger_breakpoints: -// contents populated by the macros below - -.macro MESSENGER_START -4: - .section __DATA,__objc_msg_break - .quad 4b - .quad ENTER - .text -.endmacro -.macro MESSENGER_END_FAST -4: - .section __DATA,__objc_msg_break - .quad 4b - .quad FAST_EXIT - .text -.endmacro -.macro MESSENGER_END_SLOW -4: - .section __DATA,__objc_msg_break - .quad 4b - .quad SLOW_EXIT - .text -.endmacro -.macro MESSENGER_END_NIL -4: - .section __DATA,__objc_msg_break - .quad 4b - .quad NIL_EXIT - .text -.endmacro + PTR LExit_cache_getImp + PTR LExit_objc_msgSend + PTR LExit_objc_msgSendSuper + PTR LExit_objc_msgSendSuper2 + PTR LExit_objc_msgLookup + PTR LExit_objc_msgLookupSuper2 + PTR 0 /* objc_super parameter to sendSuper */ #define RECEIVER 0 -#define CLASS 8 +#define CLASS __SIZEOF_POINTER__ /* Selected field offsets in class structure */ -#define SUPERCLASS 8 -#define CACHE 16 - -/* Selected field offsets in isa field */ -#define ISA_MASK 0x0000000ffffffff8 +#define SUPERCLASS __SIZEOF_POINTER__ +#define CACHE (2 * __SIZEOF_POINTER__) /* Selected field offsets in method structure */ #define METHOD_NAME 0 -#define METHOD_TYPES 8 -#define METHOD_IMP 16 +#define METHOD_TYPES __SIZEOF_POINTER__ +#define METHOD_IMP (2 * __SIZEOF_POINTER__) + +#define BUCKET_SIZE (2 * __SIZEOF_POINTER__) + + +/******************************************************************** + * GetClassFromIsa_p16 src + * src is a raw isa field. Sets p16 to the corresponding class pointer. + * The raw isa might be an indexed isa to be decoded, or a + * packed isa that needs to be masked. + * + * On exit: + * $0 is unchanged + * p16 is a class pointer + * x10 is clobbered + ********************************************************************/ + +#if SUPPORT_INDEXED_ISA + .align 3 + .globl _objc_indexed_classes +_objc_indexed_classes: + .fill ISA_INDEX_COUNT, PTRSIZE, 0 +#endif + +.macro GetClassFromIsa_p16 /* src */ + +#if SUPPORT_INDEXED_ISA + // Indexed isa + mov p16, $0 // optimistically set dst = src + tbz p16, #ISA_INDEX_IS_NPI_BIT, 1f // done if not non-pointer isa + // isa in p16 is indexed + adrp x10, _objc_indexed_classes@PAGE + add x10, x10, _objc_indexed_classes@PAGEOFF + ubfx p16, p16, #ISA_INDEX_SHIFT, #ISA_INDEX_BITS // extract index + ldr p16, [x10, p16, UXTP #PTRSHIFT] // load class from array +1: + +#elif __LP64__ + // 64-bit packed isa + and p16, $0, #ISA_MASK + +#else + // 32-bit raw isa + mov p16, $0 + +#endif + +.endmacro /******************************************************************** @@ -164,12 +153,12 @@ LExit$0: ********************************************************************/ .macro UNWIND .section __LD,__compact_unwind,regular,debug - .quad $0 + PTR $0 .set LUnwind$0, LExit$0 - $0 .long LUnwind$0 .long $1 - .quad 0 /* no personality */ - .quad 0 /* no LSDA */ + PTR 0 /* no personality */ + PTR 0 /* no LSDA */ .text .endmacro @@ -200,14 +189,16 @@ LExit$0: #define GETIMP 1 #define LOOKUP 2 +// CacheHit: x17 = cached IMP, x12 = address of cached IMP .macro CacheHit .if $0 == NORMAL - MESSENGER_END_FAST - br x17 // call imp + TailCallCachedImp x17, x12 // authenticate and call imp .elseif $0 == GETIMP - mov x0, x17 // return imp - ret + mov p0, p17 + AuthAndResignAsIMP x0, x12 // authenticate imp and re-sign as IMP + ret // return IMP .elseif $0 == LOOKUP + AuthAndResignAsIMP x17, x12 // authenticate imp and re-sign as IMP ret // return imp via x17 .else .abort oops @@ -217,11 +208,11 @@ LExit$0: .macro CheckMiss // miss if bucket->sel == 0 .if $0 == GETIMP - cbz x9, LGetImpMiss + cbz p9, LGetImpMiss .elseif $0 == NORMAL - cbz x9, __objc_msgSend_uncached + cbz p9, __objc_msgSend_uncached .elseif $0 == LOOKUP - cbz x9, __objc_msgLookup_uncached + cbz p9, __objc_msgLookup_uncached .else .abort oops .endif @@ -240,39 +231,44 @@ LExit$0: .endmacro .macro CacheLookup - // x1 = SEL, x16 = isa - ldp x10, x11, [x16, #CACHE] // x10 = buckets, x11 = occupied|mask + // p1 = SEL, p16 = isa + ldp p10, p11, [x16, #CACHE] // p10 = buckets, p11 = occupied|mask +#if !__LP64__ + and w11, w11, 0xffff // p11 = mask +#endif and w12, w1, w11 // x12 = _cmd & mask - add x12, x10, x12, LSL #4 // x12 = buckets + ((_cmd & mask)<<4) + add p12, p10, p12, LSL #(1+PTRSHIFT) + // p12 = buckets + ((_cmd & mask) << (1+PTRSHIFT)) - ldp x9, x17, [x12] // {x9, x17} = *bucket -1: cmp x9, x1 // if (bucket->sel != _cmd) + ldp p17, p9, [x12] // {imp, sel} = *bucket +1: cmp p9, p1 // if (bucket->sel != _cmd) b.ne 2f // scan more CacheHit $0 // call or return imp -2: // not hit: x12 = not-hit bucket +2: // not hit: p12 = not-hit bucket CheckMiss $0 // miss if bucket->sel == 0 - cmp x12, x10 // wrap if bucket == buckets + cmp p12, p10 // wrap if bucket == buckets b.eq 3f - ldp x9, x17, [x12, #-16]! // {x9, x17} = *--bucket + ldp p17, p9, [x12, #-BUCKET_SIZE]! // {imp, sel} = *--bucket b 1b // loop -3: // wrap: x12 = first bucket, w11 = mask - add x12, x12, w11, UXTW #4 // x12 = buckets+(mask<<4) +3: // wrap: p12 = first bucket, w11 = mask + add p12, p12, w11, UXTW #(1+PTRSHIFT) + // p12 = buckets + (mask << 1+PTRSHIFT) // Clone scanning loop to miss instead of hang when cache is corrupt. // The slow path may detect any corruption and halt later. - ldp x9, x17, [x12] // {x9, x17} = *bucket -1: cmp x9, x1 // if (bucket->sel != _cmd) + ldp p17, p9, [x12] // {imp, sel} = *bucket +1: cmp p9, p1 // if (bucket->sel != _cmd) b.ne 2f // scan more CacheHit $0 // call or return imp -2: // not hit: x12 = not-hit bucket +2: // not hit: p12 = not-hit bucket CheckMiss $0 // miss if bucket->sel == 0 - cmp x12, x10 // wrap if bucket == buckets + cmp p12, p10 // wrap if bucket == buckets b.eq 3f - ldp x9, x17, [x12, #-16]! // {x9, x17} = *--bucket + ldp p17, p9, [x12, #-BUCKET_SIZE]! // {imp, sel} = *--bucket b 1b // loop 3: // double wrap @@ -292,6 +288,7 @@ LExit$0: * ********************************************************************/ +#if SUPPORT_TAGGED_POINTERS .data .align 3 .globl _objc_debug_taggedpointer_classes @@ -300,39 +297,45 @@ _objc_debug_taggedpointer_classes: .globl _objc_debug_taggedpointer_ext_classes _objc_debug_taggedpointer_ext_classes: .fill 256, 8, 0 +#endif ENTRY _objc_msgSend UNWIND _objc_msgSend, NoFrame - MESSENGER_START - cmp x0, #0 // nil check and tagged pointer check + cmp p0, #0 // nil check and tagged pointer check +#if SUPPORT_TAGGED_POINTERS b.le LNilOrTagged // (MSB tagged pointer looks negative) - ldr x13, [x0] // x13 = isa - and x16, x13, #ISA_MASK // x16 = class +#else + b.eq LReturnZero +#endif + ldr p13, [x0] // p13 = isa + GetClassFromIsa_p16 p13 // p16 = class LGetIsaDone: CacheLookup NORMAL // calls imp or objc_msgSend_uncached +#if SUPPORT_TAGGED_POINTERS LNilOrTagged: b.eq LReturnZero // nil check // tagged - mov x10, #0xf000000000000000 - cmp x0, x10 - b.hs LExtTag adrp x10, _objc_debug_taggedpointer_classes@PAGE add x10, x10, _objc_debug_taggedpointer_classes@PAGEOFF ubfx x11, x0, #60, #4 ldr x16, [x10, x11, LSL #3] - b LGetIsaDone + adrp x10, _OBJC_CLASS_$___NSUnrecognizedTaggedPointer@PAGE + add x10, x10, _OBJC_CLASS_$___NSUnrecognizedTaggedPointer@PAGEOFF + cmp x10, x16 + b.ne LGetIsaDone -LExtTag: // ext tagged adrp x10, _objc_debug_taggedpointer_ext_classes@PAGE add x10, x10, _objc_debug_taggedpointer_ext_classes@PAGEOFF ubfx x11, x0, #52, #8 ldr x16, [x10, x11, LSL #3] b LGetIsaDone - +// SUPPORT_TAGGED_POINTERS +#endif + LReturnZero: // x0 is already zero mov x1, #0 @@ -340,7 +343,6 @@ LReturnZero: movi d1, #0 movi d2, #0 movi d3, #0 - MESSENGER_END_NIL ret END_ENTRY _objc_msgSend @@ -348,14 +350,18 @@ LReturnZero: ENTRY _objc_msgLookup UNWIND _objc_msgLookup, NoFrame - - cmp x0, #0 // nil check and tagged pointer check + cmp p0, #0 // nil check and tagged pointer check +#if SUPPORT_TAGGED_POINTERS b.le LLookup_NilOrTagged // (MSB tagged pointer looks negative) - ldr x13, [x0] // x13 = isa - and x16, x13, #ISA_MASK // x16 = class +#else + b.eq LLookup_Nil +#endif + ldr p13, [x0] // p13 = isa + GetClassFromIsa_p16 p13 // p16 = class LLookup_GetIsaDone: CacheLookup LOOKUP // returns imp +#if SUPPORT_TAGGED_POINTERS LLookup_NilOrTagged: b.eq LLookup_Nil // nil check @@ -375,6 +381,8 @@ LLookup_ExtTag: ubfx x11, x0, #52, #8 ldr x16, [x10, x11, LSL #3] b LLookup_GetIsaDone +// SUPPORT_TAGGED_POINTERS +#endif LLookup_Nil: adrp x17, __objc_msgNil@PAGE @@ -399,9 +407,8 @@ LLookup_Nil: ENTRY _objc_msgSendSuper UNWIND _objc_msgSendSuper, NoFrame - MESSENGER_START - ldp x0, x16, [x0] // x0 = real receiver, x16 = class + ldp p0, p16, [x0] // p0 = real receiver, p16 = class CacheLookup NORMAL // calls imp or objc_msgSend_uncached END_ENTRY _objc_msgSendSuper @@ -410,10 +417,9 @@ LLookup_Nil: ENTRY _objc_msgSendSuper2 UNWIND _objc_msgSendSuper2, NoFrame - MESSENGER_START - ldp x0, x16, [x0] // x0 = real receiver, x16 = class - ldr x16, [x16, #SUPERCLASS] // x16 = class->superclass + ldp p0, p16, [x0] // p0 = real receiver, p16 = class + ldr p16, [x16, #SUPERCLASS] // p16 = class->superclass CacheLookup NORMAL END_ENTRY _objc_msgSendSuper2 @@ -422,8 +428,8 @@ LLookup_Nil: ENTRY _objc_msgLookupSuper2 UNWIND _objc_msgLookupSuper2, NoFrame - ldp x0, x16, [x0] // x0 = real receiver, x16 = class - ldr x16, [x16, #SUPERCLASS] // x16 = class->superclass + ldp p0, p16, [x0] // p0 = real receiver, p16 = class + ldr p16, [x16, #SUPERCLASS] // p16 = class->superclass CacheLookup LOOKUP END_ENTRY _objc_msgLookupSuper2 @@ -432,6 +438,7 @@ LLookup_Nil: .macro MethodTableLookup // push frame + SignLR stp fp, lr, [sp, #-16]! mov fp, sp @@ -451,7 +458,7 @@ LLookup_Nil: mov x2, x16 bl __class_lookupMethodAndLoadCache3 - // imp in x0 + // IMP in x0 mov x17, x0 // restore registers and return @@ -467,6 +474,7 @@ LLookup_Nil: mov sp, fp ldp fp, lr, [sp], #16 + AuthenticateLR .endmacro @@ -474,10 +482,10 @@ LLookup_Nil: UNWIND __objc_msgSend_uncached, FrameWithNoSaves // THIS IS NOT A CALLABLE C FUNCTION - // Out-of-band x16 is the class to search + // Out-of-band p16 is the class to search MethodTableLookup - br x17 + TailCallFunctionPointer x17 END_ENTRY __objc_msgSend_uncached @@ -486,7 +494,7 @@ LLookup_Nil: UNWIND __objc_msgLookup_uncached, FrameWithNoSaves // THIS IS NOT A CALLABLE C FUNCTION - // Out-of-band x16 is the class to search + // Out-of-band p16 is the class to search MethodTableLookup ret @@ -496,11 +504,11 @@ LLookup_Nil: STATIC_ENTRY _cache_getImp - and x16, x0, #ISA_MASK + GetClassFromIsa_p16 p0 CacheLookup GETIMP LGetImpMiss: - mov x0, #0 + mov p0, #0 ret END_ENTRY _cache_getImp @@ -519,10 +527,6 @@ LGetImpMiss: STATIC_ENTRY __objc_msgForward_impcache - MESSENGER_START - nop - MESSENGER_END_SLOW - // No stret specialization. b __objc_msgForward @@ -532,8 +536,8 @@ LGetImpMiss: ENTRY __objc_msgForward adrp x17, __objc_forward_handler@PAGE - ldr x17, [x17, __objc_forward_handler@PAGEOFF] - br x17 + ldr p17, [x17, __objc_forward_handler@PAGEOFF] + TailCallFunctionPointer x17 END_ENTRY __objc_msgForward @@ -553,9 +557,10 @@ LGetImpMiss: ENTRY _method_invoke // x1 is method triplet instead of SEL - ldr x17, [x1, #METHOD_IMP] - ldr x1, [x1, #METHOD_NAME] - br x17 + add p16, p1, #METHOD_IMP + ldr p17, [x16] + ldr p1, [x1, #METHOD_NAME] + TailCallMethodListImp x17, x16 END_ENTRY _method_invoke #endif diff --git a/runtime/Messengers.subproj/objc-msg-i386.s b/runtime/Messengers.subproj/objc-msg-i386.s index 8a64ba0..6f71a51 100644 --- a/runtime/Messengers.subproj/objc-msg-i386.s +++ b/runtime/Messengers.subproj/objc-msg-i386.s @@ -68,62 +68,6 @@ _objc_exitPoints: .long 0 -/******************************************************************** -* List every exit insn from every messenger for debugger use. -* Format: -* ( -* 1 word instruction's address -* 1 word type (ENTER or FAST_EXIT or SLOW_EXIT or NIL_EXIT) -* ) -* 1 word zero -* -* ENTER is the start of a dispatcher -* FAST_EXIT is method dispatch -* SLOW_EXIT is uncached method lookup -* NIL_EXIT is returning zero from a message sent to nil -* These must match objc-gdb.h. -********************************************************************/ - -#define ENTER 1 -#define FAST_EXIT 2 -#define SLOW_EXIT 3 -#define NIL_EXIT 4 - -.section __DATA,__objc_msg_break -.globl _gdb_objc_messenger_breakpoints -_gdb_objc_messenger_breakpoints: -// contents populated by the macros below - -.macro MESSENGER_START -4: - .section __DATA,__objc_msg_break - .long 4b - .long ENTER - .text -.endmacro -.macro MESSENGER_END_FAST -4: - .section __DATA,__objc_msg_break - .long 4b - .long FAST_EXIT - .text -.endmacro -.macro MESSENGER_END_SLOW -4: - .section __DATA,__objc_msg_break - .long 4b - .long SLOW_EXIT - .text -.endmacro -.macro MESSENGER_END_NIL -4: - .section __DATA,__objc_msg_break - .long 4b - .long NIL_EXIT - .text -.endmacro - - /******************************************************************** * * Common offsets. @@ -492,8 +436,6 @@ LMsgSendHitInstrumentDone_$0_$1_$2: ///////////////////////////////////////////////////////////////////// .macro MethodTableLookup - MESSENGER_END_SLOW - // stack has return address and nothing else subl $$(12+5*16), %esp @@ -593,7 +535,6 @@ LGetImpExit: ********************************************************************/ ENTRY _objc_msgSend - MESSENGER_START CALL_MCOUNTER // load receiver and selector @@ -609,7 +550,6 @@ LMsgSendReceiverOk: movl isa(%eax), %edx // class = self->isa CacheLookup WORD_RETURN, MSG_SEND, LMsgSendCacheMiss xor %edx, %edx // set nonstret for msgForward_internal - MESSENGER_END_FAST jmp *%eax // cache miss: go search the method lists @@ -624,7 +564,6 @@ LMsgSendNilSelf: movl $0,%edx xorps %xmm0, %xmm0 LMsgSendDone: - MESSENGER_END_NIL ret // guaranteed non-nil entry point (disabled for now) @@ -647,7 +586,6 @@ LMsgSendExit: ********************************************************************/ ENTRY _objc_msgSendSuper - MESSENGER_START CALL_MCOUNTER // load selector and class to search @@ -658,7 +596,6 @@ LMsgSendExit: // search the cache (class in %edx) CacheLookup WORD_RETURN, MSG_SENDSUPER, LMsgSendSuperCacheMiss xor %edx, %edx // set nonstret for msgForward_internal - MESSENGER_END_FAST jmp *%eax // goto *imp // cache miss: go search the method lists @@ -671,7 +608,6 @@ LMsgSendSuperCacheMiss: LMsgSendSuperIgnored: movl super(%esp), %eax movl receiver(%eax), %eax - MESSENGER_END_NIL ret LMsgSendSuperExit: @@ -736,7 +672,6 @@ LMsgSendvArgsOK: ********************************************************************/ ENTRY _objc_msgSend_fpret - MESSENGER_START CALL_MCOUNTER // load receiver and selector @@ -752,7 +687,6 @@ LMsgSendFpretReceiverOk: movl isa(%eax), %edx // class = self->isa CacheLookup WORD_RETURN, MSG_SEND, LMsgSendFpretCacheMiss xor %edx, %edx // set nonstret for msgForward_internal - MESSENGER_END_FAST jmp *%eax // goto *imp // cache miss: go search the method lists @@ -766,7 +700,6 @@ LMsgSendFpretNilSelf: // %eax is already zero fldz LMsgSendFpretDone: - MESSENGER_END_NIL ret LMsgSendFpretExit: @@ -839,7 +772,6 @@ LMsgSendvFpretArgsOK: ********************************************************************/ ENTRY _objc_msgSend_stret - MESSENGER_START CALL_MCOUNTER // load receiver and selector @@ -855,7 +787,6 @@ LMsgSendStretReceiverOk: movl isa(%eax), %edx // class = self->isa CacheLookup STRUCT_RETURN, MSG_SEND, LMsgSendStretCacheMiss movl $1, %edx // set stret for objc_msgForward - MESSENGER_END_FAST jmp *%eax // goto *imp // cache miss: go search the method lists @@ -866,7 +797,6 @@ LMsgSendStretCacheMiss: // message sent to nil: redirect to nil receiver, if any LMsgSendStretNilSelf: - MESSENGER_END_NIL ret $4 // pop struct return address (#2995932) // guaranteed non-nil entry point (disabled for now) @@ -899,7 +829,6 @@ LMsgSendStretExit: ********************************************************************/ ENTRY _objc_msgSendSuper_stret - MESSENGER_START CALL_MCOUNTER // load selector and class to search @@ -910,7 +839,6 @@ LMsgSendStretExit: // search the cache (class in %edx) CacheLookup STRUCT_RETURN, MSG_SENDSUPER, LMsgSendSuperStretCacheMiss movl $1, %edx // set stret for objc_msgForward - MESSENGER_END_FAST jmp *%eax // goto *imp // cache miss: go search the method lists @@ -1012,10 +940,6 @@ L_forward_stret_handler: // THIS IS NOT A CALLABLE C FUNCTION // Out-of-band register %edx is nonzero for stret, zero otherwise - - MESSENGER_START - nop - MESSENGER_END_SLOW // Check return type (stret or not) testl %edx, %edx diff --git a/runtime/Messengers.subproj/objc-msg-simulator-i386.s b/runtime/Messengers.subproj/objc-msg-simulator-i386.s index beb7ac5..63631fa 100644 --- a/runtime/Messengers.subproj/objc-msg-simulator-i386.s +++ b/runtime/Messengers.subproj/objc-msg-simulator-i386.s @@ -68,62 +68,6 @@ _objc_exitPoints: .long 0 -/******************************************************************** -* List every exit insn from every messenger for debugger use. -* Format: -* ( -* 1 word instruction's address -* 1 word type (ENTER or FAST_EXIT or SLOW_EXIT or NIL_EXIT) -* ) -* 1 word zero -* -* ENTER is the start of a dispatcher -* FAST_EXIT is method dispatch -* SLOW_EXIT is uncached method lookup -* NIL_EXIT is returning zero from a message sent to nil -* These must match objc-gdb.h. -********************************************************************/ - -#define ENTER 1 -#define FAST_EXIT 2 -#define SLOW_EXIT 3 -#define NIL_EXIT 4 - -.section __DATA,__objc_msg_break -.globl _gdb_objc_messenger_breakpoints -_gdb_objc_messenger_breakpoints: -// contents populated by the macros below - -.macro MESSENGER_START -4: - .section __DATA,__objc_msg_break - .long 4b - .long ENTER - .text -.endmacro -.macro MESSENGER_END_FAST -4: - .section __DATA,__objc_msg_break - .long 4b - .long FAST_EXIT - .text -.endmacro -.macro MESSENGER_END_SLOW -4: - .section __DATA,__objc_msg_break - .long 4b - .long SLOW_EXIT - .text -.endmacro -.macro MESSENGER_END_NIL -4: - .section __DATA,__objc_msg_break - .long 4b - .long NIL_EXIT - .text -.endmacro - - /******************************************************************** * Names for relative labels * DO NOT USE THESE LABELS ELSEWHERE @@ -188,6 +132,12 @@ _gdb_objc_messenger_breakpoints: // Selected field offsets in class structure #define isa 0 #define superclass 4 +#define cache_buckets 8 +#define cache_mask 12 + +// Method cache +#define cached_sel 0 +#define cached_imp 4 // Method descriptor #define method_name 0 @@ -277,7 +227,7 @@ LExit$0: // eax = found bucket .if $1 == GETIMP - movl 4(%eax), %eax // return imp + movl cached_imp(%eax), %eax // return imp ret .else @@ -289,11 +239,10 @@ LExit$0: .endif .if $1 == CALL - MESSENGER_END_FAST - jmp *4(%eax) // call imp + jmp *cached_imp(%eax) // call imp .elseif $1 == LOOKUP - movl 4(%eax), %eax // return imp + movl cached_imp(%eax), %eax // return imp ret .else @@ -307,23 +256,23 @@ LExit$0: .macro CacheLookup - movzwl 12(%edx), %eax // eax = mask + movzwl cache_mask(%edx), %eax // eax = mask andl %ecx, %eax // eax = SEL & mask shll $$3, %eax // eax = offset = (SEL & mask) * 8 - addl 8(%edx), %eax // eax = bucket = cache->buckets+offset - cmpl (%eax), %ecx // if (bucket->sel != SEL) + addl cache_buckets(%edx), %eax // eax = bucket = buckets+offset + cmpl cached_sel(%eax), %ecx // if (bucket->sel != SEL) jne 1f // scan more // The `jne` above sets flags for CacheHit CacheHit $0, $1 // call or return imp 1: // loop - cmpl $$1, (%eax) + cmpl $$1, cached_sel(%eax) jbe 3f // if (bucket->sel <= 1) wrap or miss addl $$8, %eax // bucket++ 2: - cmpl (%eax), %ecx // if (bucket->sel != sel) + cmpl cached_sel(%eax), %ecx // if (bucket->sel != sel) jne 1b // scan more // The `jne` above sets flags for CacheHit CacheHit $0, $1 // call or return imp @@ -332,7 +281,7 @@ LExit$0: // wrap or miss jb LCacheMiss_f // if (bucket->sel < 1) cache miss // wrap - movl 4(%eax), %eax // bucket->imp is really first bucket + movl cached_imp(%eax), %eax // bucket->imp is really first bucket jmp 2f // Clone scanning loop to miss instead of hang when cache is corrupt. @@ -340,12 +289,12 @@ LExit$0: 1: // loop - cmpq $$1, (%eax) + cmpl $$1, cached_sel(%eax) jbe 3f // if (bucket->sel <= 1) wrap or miss addl $$8, %eax // bucket++ 2: - cmpl (%eax), %ecx // if (bucket->sel != sel) + cmpl cached_sel(%eax), %ecx // if (bucket->sel != sel) jne 1b // scan more // The `jne` above sets flags for CacheHit CacheHit $0, $1 // call or return imp @@ -482,15 +431,12 @@ LNilTestSlow: .if $0 == NORMAL ZeroReturn - MESSENGER_END_NIL ret .elseif $0 == FPRET ZeroReturnFPRET - MESSENGER_END_NIL ret .elseif $0 == STRET ZeroReturnSTRET - MESSENGER_END_NIL ret $$4 .else .abort oops @@ -553,7 +499,6 @@ LCacheMiss: ENTRY _objc_msgSend UNWIND _objc_msgSend, NoFrame - MESSENGER_START movl selector(%esp), %ecx movl self(%esp), %eax @@ -567,7 +512,6 @@ LCacheMiss: LCacheMiss: // isa still in edx - MESSENGER_END_SLOW jmp __objc_msgSend_uncached END_ENTRY _objc_msgSend @@ -596,13 +540,11 @@ LCacheMiss: /******************************************************************** * * id objc_msgSendSuper(struct objc_super *super, SEL _cmd, ...); - * IMP objc_msgLookupSuper(struct objc_super *super, SEL _cmd, ...); * ********************************************************************/ ENTRY _objc_msgSendSuper UNWIND _objc_msgSendSuper, NoFrame - MESSENGER_START movl selector(%esp), %ecx movl super(%esp), %eax // struct objc_super @@ -613,30 +555,11 @@ LCacheMiss: LCacheMiss: // class still in edx - MESSENGER_END_SLOW jmp __objc_msgSend_uncached END_ENTRY _objc_msgSendSuper - - ENTRY _objc_msgLookupSuper - UNWIND _objc_msgLookupSuper, NoFrame - - movl selector(%esp), %ecx - movl super(%esp), %eax // struct objc_super - movl class(%eax), %edx // struct objc_super->class - movl receiver(%eax), %eax // struct objc_super->receiver - movl %eax, super(%esp) // replace super arg with receiver - CacheLookup NORMAL, LOOKUP // returns IMP on success - -LCacheMiss: - // class still in edx - jmp __objc_msgLookup_uncached - - END_ENTRY _objc_msgLookupSuper - - /******************************************************************** * * id objc_msgSendSuper2(struct objc_super *super, SEL _cmd, ...); @@ -646,7 +569,6 @@ LCacheMiss: ENTRY _objc_msgSendSuper2 UNWIND _objc_msgSendSuper2, NoFrame - MESSENGER_START movl selector(%esp), %ecx movl super(%esp), %eax // struct objc_super @@ -658,7 +580,6 @@ LCacheMiss: LCacheMiss: // class still in edx - MESSENGER_END_SLOW jmp __objc_msgSend_uncached END_ENTRY _objc_msgSendSuper2 @@ -691,7 +612,6 @@ LCacheMiss: ENTRY _objc_msgSend_fpret UNWIND _objc_msgSend_fpret, NoFrame - MESSENGER_START movl selector(%esp), %ecx movl self(%esp), %eax @@ -705,7 +625,6 @@ LCacheMiss: LCacheMiss: // class still in edx - MESSENGER_END_SLOW jmp __objc_msgSend_uncached END_ENTRY _objc_msgSend_fpret @@ -740,7 +659,6 @@ LCacheMiss: ENTRY _objc_msgSend_stret UNWIND _objc_msgSend_stret, NoFrame - MESSENGER_START movl selector_stret(%esp), %ecx movl self_stret(%esp), %eax @@ -754,7 +672,6 @@ LCacheMiss: LCacheMiss: // class still in edx - MESSENGER_END_SLOW jmp __objc_msgSend_stret_uncached END_ENTRY _objc_msgSend_stret @@ -783,13 +700,11 @@ LCacheMiss: /******************************************************************** * * void objc_msgSendSuper_stret(void *st_addr, struct objc_super *super, SEL _cmd, ...); - * IMP objc_msgLookupSuper_stret(void *st_addr, struct objc_super *super, SEL _cmd, ...); * ********************************************************************/ ENTRY _objc_msgSendSuper_stret UNWIND _objc_msgSendSuper_stret, NoFrame - MESSENGER_START movl selector_stret(%esp), %ecx movl super_stret(%esp), %eax // struct objc_super @@ -800,29 +715,11 @@ LCacheMiss: LCacheMiss: // class still in edx - MESSENGER_END_SLOW jmp __objc_msgSend_stret_uncached END_ENTRY _objc_msgSendSuper_stret - ENTRY _objc_msgLookupSuper_stret - UNWIND _objc_msgLookupSuper_stret, NoFrame - - movl selector_stret(%esp), %ecx - movl super_stret(%esp), %eax // struct objc_super - movl class(%eax), %edx // struct objc_super->class - movl receiver(%eax), %eax // struct objc_super->receiver - movl %eax, super_stret(%esp) // replace super arg with receiver - CacheLookup STRET, LOOKUP // returns IMP on success - -LCacheMiss: - // class still in edx - jmp __objc_msgLookup_stret_uncached - - END_ENTRY _objc_msgLookupSuper_stret - - /******************************************************************** * * void objc_msgSendSuper2_stret(void *st_addr, struct objc_super *super, SEL _cmd, ...); @@ -832,7 +729,6 @@ LCacheMiss: ENTRY _objc_msgSendSuper2_stret UNWIND _objc_msgSendSuper2_stret, NoFrame - MESSENGER_START movl selector_stret(%esp), %ecx movl super_stret(%esp), %eax // struct objc_super @@ -845,7 +741,6 @@ LCacheMiss: // cache miss: go search the method lists LCacheMiss: // class still in edx - MESSENGER_END_SLOW jmp __objc_msgSend_stret_uncached END_ENTRY _objc_msgSendSuper2_stret @@ -958,10 +853,6 @@ L_forward_stret_handler: // THIS IS NOT A CALLABLE C FUNCTION // Out-of-band condition register is NE for stret, EQ otherwise. - MESSENGER_START - nop - MESSENGER_END_SLOW - jne __objc_msgForward_stret jmp __objc_msgForward diff --git a/runtime/Messengers.subproj/objc-msg-simulator-x86_64.s b/runtime/Messengers.subproj/objc-msg-simulator-x86_64.s index 41b5aaa..c1ef837 100644 --- a/runtime/Messengers.subproj/objc-msg-simulator-x86_64.s +++ b/runtime/Messengers.subproj/objc-msg-simulator-x86_64.s @@ -22,7 +22,7 @@ */ #include -#if __x86_64__ && TARGET_OS_SIMULATOR +#if __x86_64__ && TARGET_OS_SIMULATOR && !TARGET_OS_IOSMAC /******************************************************************** ******************************************************************** @@ -78,62 +78,6 @@ _objc_exitPoints: .quad 0 -/******************************************************************** -* List every exit insn from every messenger for debugger use. -* Format: -* ( -* 1 word instruction's address -* 1 word type (ENTER or FAST_EXIT or SLOW_EXIT or NIL_EXIT) -* ) -* 1 word zero -* -* ENTER is the start of a dispatcher -* FAST_EXIT is method dispatch -* SLOW_EXIT is uncached method lookup -* NIL_EXIT is returning zero from a message sent to nil -* These must match objc-gdb.h. -********************************************************************/ - -#define ENTER 1 -#define FAST_EXIT 2 -#define SLOW_EXIT 3 -#define NIL_EXIT 4 - -.section __DATA,__objc_msg_break -.globl _gdb_objc_messenger_breakpoints -_gdb_objc_messenger_breakpoints: -// contents populated by the macros below - -.macro MESSENGER_START -4: - .section __DATA,__objc_msg_break - .quad 4b - .quad ENTER - .text -.endmacro -.macro MESSENGER_END_FAST -4: - .section __DATA,__objc_msg_break - .quad 4b - .quad FAST_EXIT - .text -.endmacro -.macro MESSENGER_END_SLOW -4: - .section __DATA,__objc_msg_break - .quad 4b - .quad SLOW_EXIT - .text -.endmacro -.macro MESSENGER_END_NIL -4: - .section __DATA,__objc_msg_break - .quad 4b - .quad NIL_EXIT - .text -.endmacro - - /******************************************************************** * Recommended multi-byte NOP instructions * (Intel 64 and IA-32 Architectures Software Developer's Manual Volume 2B) @@ -218,6 +162,10 @@ _gdb_objc_messenger_breakpoints: #define method_name 0 #define method_imp 16 +// Method cache +#define cached_sel 0 +#define cached_imp 8 + ////////////////////////////////////////////////////////////////////// // @@ -303,7 +251,7 @@ LExit$0: // r11 = found bucket .if $1 == GETIMP - movq 8(%r11), %rax // return imp + movq cached_imp(%r11), %rax // return imp ret .else @@ -315,11 +263,10 @@ LExit$0: .endif .if $1 == CALL - MESSENGER_END_FAST - jmp *8(%r11) // call imp + jmp *cached_imp(%r11) // call imp .elseif $1 == LOOKUP - movq 8(%r11), %r11 // return imp + movq cached_imp(%r11), %r11 // return imp ret .else @@ -342,9 +289,9 @@ LExit$0: addq 16(%r10), %r11 // r11 = class->cache.buckets + offset .if $0 != STRET - cmpq (%r11), %a2 // if (bucket->sel != _cmd) + cmpq cached_sel(%r11), %a2 // if (bucket->sel != _cmd) .else - cmpq (%r11), %a3 // if (bucket->sel != _cmd) + cmpq cached_sel(%r11), %a3 // if (bucket->sel != _cmd) .endif jne 1f // scan more // CacheHit must always be preceded by a not-taken `jne` instruction @@ -352,15 +299,15 @@ LExit$0: 1: // loop - cmpq $$1, (%r11) + cmpq $$1, cached_sel(%r11) jbe 3f // if (bucket->sel <= 1) wrap or miss addq $$16, %r11 // bucket++ 2: .if $0 != STRET - cmpq (%r11), %a2 // if (bucket->sel != _cmd) + cmpq cached_sel(%r11), %a2 // if (bucket->sel != _cmd) .else - cmpq (%r11), %a3 // if (bucket->sel != _cmd) + cmpq cached_sel(%r11), %a3 // if (bucket->sel != _cmd) .endif jne 1b // scan more // CacheHit must always be preceded by a not-taken `jne` instruction @@ -370,7 +317,7 @@ LExit$0: // wrap or miss jb LCacheMiss_f // if (bucket->sel < 1) cache miss // wrap - movq 8(%r11), %r11 // bucket->imp is really first bucket + movq cached_imp(%r11), %r11 // bucket->imp is really first bucket jmp 2f // Clone scanning loop to miss instead of hang when cache is corrupt. @@ -378,15 +325,15 @@ LExit$0: 1: // loop - cmpq $$1, (%r11) + cmpq $$1, cached_sel(%r11) jbe 3f // if (bucket->sel <= 1) wrap or miss addq $$16, %r11 // bucket++ 2: .if $0 != STRET - cmpq (%r11), %a2 // if (bucket->sel != _cmd) + cmpq cached_sel(%r11), %a2 // if (bucket->sel != _cmd) .else - cmpq (%r11), %a3 // if (bucket->sel != _cmd) + cmpq cached_sel(%r11), %a3 // if (bucket->sel != _cmd) .endif jne 1b // scan more // CacheHit must always be preceded by a not-taken `jne` instruction @@ -567,14 +514,13 @@ LNilOrTagged: .else movq %a2, %r11 .endif - shrq $$60, %r11 - cmpl $$0xf, %r11d - je 1f // basic tagged + shrq $$60, %r11 leaq _objc_debug_taggedpointer_classes(%rip), %r10 movq (%r10, %r11, 8), %r10 // read isa from table - jmp LGetIsaDone_b -1: + leaq _OBJC_CLASS_$___NSUnrecognizedTaggedPointer(%rip), %r11 + cmp %r10, %r11 + jne LGetIsaDone_b // ext tagged .if $0 != STRET movq %a1, %r11 @@ -602,7 +548,6 @@ LNil: .else .abort oops .endif - MESSENGER_END_NIL ret .endmacro @@ -671,7 +616,6 @@ _objc_debug_taggedpointer_ext_classes: ENTRY _objc_msgSend UNWIND _objc_msgSend, NoFrame - MESSENGER_START GetIsaCheckNil NORMAL // r10 = self->isa, or return zero CacheLookup NORMAL, CALL // calls IMP on success @@ -682,7 +626,6 @@ _objc_debug_taggedpointer_ext_classes: // cache miss: go search the method lists LCacheMiss: // isa still in r10 - MESSENGER_END_SLOW jmp __objc_msgSend_uncached END_ENTRY _objc_msgSend @@ -728,7 +671,6 @@ LCacheMiss: ENTRY _objc_msgSendSuper UNWIND _objc_msgSendSuper, NoFrame - MESSENGER_START // search the cache (objc_super in %a1) movq class(%a1), %r10 // class = objc_super->class @@ -738,7 +680,6 @@ LCacheMiss: // cache miss: go search the method lists LCacheMiss: // class still in r10 - MESSENGER_END_SLOW jmp __objc_msgSend_uncached END_ENTRY _objc_msgSendSuper @@ -750,7 +691,6 @@ LCacheMiss: ENTRY _objc_msgSendSuper2 UNWIND _objc_msgSendSuper2, NoFrame - MESSENGER_START // objc_super->class is superclass of class to search @@ -763,7 +703,6 @@ LCacheMiss: // cache miss: go search the method lists LCacheMiss: // superclass still in r10 - MESSENGER_END_SLOW jmp __objc_msgSend_uncached END_ENTRY _objc_msgSendSuper2 @@ -808,7 +747,6 @@ LCacheMiss: ENTRY _objc_msgSend_fpret UNWIND _objc_msgSend_fpret, NoFrame - MESSENGER_START GetIsaCheckNil FPRET // r10 = self->isa, or return zero CacheLookup FPRET, CALL // calls IMP on success @@ -819,7 +757,6 @@ LCacheMiss: // cache miss: go search the method lists LCacheMiss: // isa still in r10 - MESSENGER_END_SLOW jmp __objc_msgSend_uncached END_ENTRY _objc_msgSend_fpret @@ -862,7 +799,6 @@ LCacheMiss: ENTRY _objc_msgSend_fp2ret UNWIND _objc_msgSend_fp2ret, NoFrame - MESSENGER_START GetIsaCheckNil FP2RET // r10 = self->isa, or return zero CacheLookup FP2RET, CALL // calls IMP on success @@ -873,7 +809,6 @@ LCacheMiss: // cache miss: go search the method lists LCacheMiss: // isa still in r10 - MESSENGER_END_SLOW jmp __objc_msgSend_uncached END_ENTRY _objc_msgSend_fp2ret @@ -922,7 +857,6 @@ LCacheMiss: ENTRY _objc_msgSend_stret UNWIND _objc_msgSend_stret, NoFrame - MESSENGER_START GetIsaCheckNil STRET // r10 = self->isa, or return zero CacheLookup STRET, CALL // calls IMP on success @@ -933,7 +867,6 @@ LCacheMiss: // cache miss: go search the method lists LCacheMiss: // isa still in r10 - MESSENGER_END_SLOW jmp __objc_msgSend_stret_uncached END_ENTRY _objc_msgSend_stret @@ -988,7 +921,6 @@ LCacheMiss: ENTRY _objc_msgSendSuper_stret UNWIND _objc_msgSendSuper_stret, NoFrame - MESSENGER_START // search the cache (objc_super in %a2) movq class(%a2), %r10 // class = objc_super->class @@ -998,7 +930,6 @@ LCacheMiss: // cache miss: go search the method lists LCacheMiss: // class still in r10 - MESSENGER_END_SLOW jmp __objc_msgSend_stret_uncached END_ENTRY _objc_msgSendSuper_stret @@ -1010,7 +941,6 @@ LCacheMiss: ENTRY _objc_msgSendSuper2_stret UNWIND _objc_msgSendSuper2_stret, NoFrame - MESSENGER_START // search the cache (objc_super in %a2) movq class(%a2), %r10 // class = objc_super->class @@ -1021,7 +951,6 @@ LCacheMiss: // cache miss: go search the method lists LCacheMiss: // superclass still in r10 - MESSENGER_END_SLOW jmp __objc_msgSend_stret_uncached END_ENTRY _objc_msgSendSuper2_stret @@ -1132,10 +1061,6 @@ LCacheMiss: // THIS IS NOT A CALLABLE C FUNCTION // Out-of-band condition register is NE for stret, EQ otherwise. - MESSENGER_START - nop - MESSENGER_END_SLOW - jne __objc_msgForward_stret jmp __objc_msgForward diff --git a/runtime/Messengers.subproj/objc-msg-x86_64.s b/runtime/Messengers.subproj/objc-msg-x86_64.s index 8670555..9427306 100644 --- a/runtime/Messengers.subproj/objc-msg-x86_64.s +++ b/runtime/Messengers.subproj/objc-msg-x86_64.s @@ -22,7 +22,9 @@ */ #include -#if __x86_64__ && !TARGET_OS_SIMULATOR +#if __x86_64__ && !(TARGET_OS_SIMULATOR && !TARGET_OS_IOSMAC) + +#include "isa.h" /******************************************************************** ******************************************************************** @@ -78,62 +80,6 @@ _objc_exitPoints: .quad 0 -/******************************************************************** -* List every exit insn from every messenger for debugger use. -* Format: -* ( -* 1 word instruction's address -* 1 word type (ENTER or FAST_EXIT or SLOW_EXIT or NIL_EXIT) -* ) -* 1 word zero -* -* ENTER is the start of a dispatcher -* FAST_EXIT is method dispatch -* SLOW_EXIT is uncached method lookup -* NIL_EXIT is returning zero from a message sent to nil -* These must match objc-gdb.h. -********************************************************************/ - -#define ENTER 1 -#define FAST_EXIT 2 -#define SLOW_EXIT 3 -#define NIL_EXIT 4 - -.section __DATA,__objc_msg_break -.globl _gdb_objc_messenger_breakpoints -_gdb_objc_messenger_breakpoints: -// contents populated by the macros below - -.macro MESSENGER_START -4: - .section __DATA,__objc_msg_break - .quad 4b - .quad ENTER - .text -.endmacro -.macro MESSENGER_END_FAST -4: - .section __DATA,__objc_msg_break - .quad 4b - .quad FAST_EXIT - .text -.endmacro -.macro MESSENGER_END_SLOW -4: - .section __DATA,__objc_msg_break - .quad 4b - .quad SLOW_EXIT - .text -.endmacro -.macro MESSENGER_END_NIL -4: - .section __DATA,__objc_msg_break - .quad 4b - .quad NIL_EXIT - .text -.endmacro - - /******************************************************************** * Recommended multi-byte NOP instructions * (Intel 64 and IA-32 Architectures Software Developer's Manual Volume 2B) @@ -225,6 +171,10 @@ _gdb_objc_messenger_breakpoints: #define method_name 0 #define method_imp 16 +// Method cache +#define cached_sel 0 +#define cached_imp 8 + ////////////////////////////////////////////////////////////////////// // @@ -310,7 +260,7 @@ LExit$0: // r11 = found bucket .if $1 == GETIMP - movq 8(%r11), %rax // return imp + movq cached_imp(%r11), %rax // return imp ret .else @@ -322,11 +272,10 @@ LExit$0: .endif .if $1 == CALL - MESSENGER_END_FAST - jmp *8(%r11) // call imp + jmp *cached_imp(%r11) // call imp .elseif $1 == LOOKUP - movq 8(%r11), %r11 // return imp + movq cached_imp(%r11), %r11 // return imp ret .else @@ -349,9 +298,9 @@ LExit$0: addq 16(%r10), %r11 // r11 = class->cache.buckets + offset .if $0 != STRET - cmpq (%r11), %a2 // if (bucket->sel != _cmd) + cmpq cached_sel(%r11), %a2 // if (bucket->sel != _cmd) .else - cmpq (%r11), %a3 // if (bucket->sel != _cmd) + cmpq cached_sel(%r11), %a3 // if (bucket->sel != _cmd) .endif jne 1f // scan more // CacheHit must always be preceded by a not-taken `jne` instruction @@ -359,15 +308,15 @@ LExit$0: 1: // loop - cmpq $$1, (%r11) + cmpq $$1, cached_sel(%r11) jbe 3f // if (bucket->sel <= 1) wrap or miss addq $$16, %r11 // bucket++ 2: .if $0 != STRET - cmpq (%r11), %a2 // if (bucket->sel != _cmd) + cmpq cached_sel(%r11), %a2 // if (bucket->sel != _cmd) .else - cmpq (%r11), %a3 // if (bucket->sel != _cmd) + cmpq cached_sel(%r11), %a3 // if (bucket->sel != _cmd) .endif jne 1b // scan more // CacheHit must always be preceded by a not-taken `jne` instruction @@ -377,7 +326,7 @@ LExit$0: // wrap or miss jb LCacheMiss_f // if (bucket->sel < 1) cache miss // wrap - movq 8(%r11), %r11 // bucket->imp is really first bucket + movq cached_imp(%r11), %r11 // bucket->imp is really first bucket jmp 2f // Clone scanning loop to miss instead of hang when cache is corrupt. @@ -385,15 +334,15 @@ LExit$0: 1: // loop - cmpq $$1, (%r11) + cmpq $$1, cached_sel(%r11) jbe 3f // if (bucket->sel <= 1) wrap or miss addq $$16, %r11 // bucket++ 2: .if $0 != STRET - cmpq (%r11), %a2 // if (bucket->sel != _cmd) + cmpq cached_sel(%r11), %a2 // if (bucket->sel != _cmd) .else - cmpq (%r11), %a3 // if (bucket->sel != _cmd) + cmpq cached_sel(%r11), %a3 // if (bucket->sel != _cmd) .endif jne 1b // scan more // CacheHit must always be preceded by a not-taken `jne` instruction @@ -503,13 +452,13 @@ LExit$0: testb $$1, %a1b PN jnz LGetIsaSlow_f - movq $$0x00007ffffffffff8, %r10 + movq $$ ISA_MASK, %r10 andq (%a1), %r10 .else testb $$1, %a2b PN jnz LGetIsaSlow_f - movq $$0x00007ffffffffff8, %r10 + movq $$ ISA_MASK, %r10 andq (%a2), %r10 .endif LGetIsaDone: @@ -523,13 +472,12 @@ LGetIsaSlow: movl %a2d, %r11d .endif andl $$0xF, %r11d - cmp $$0xF, %r11d - je 1f // basic tagged leaq _objc_debug_taggedpointer_classes(%rip), %r10 movq (%r10, %r11, 8), %r10 // read isa from table - jmp LGetIsaDone_b -1: + leaq _OBJC_CLASS_$___NSUnrecognizedTaggedPointer(%rip), %r11 + cmp %r10, %r11 + jne LGetIsaDone_b // extended tagged .if $0 != STRET movl %a1d, %r11d @@ -642,7 +590,6 @@ LNilTestSlow: .else .abort oops .endif - MESSENGER_END_NIL ret .endmacro @@ -713,7 +660,6 @@ _objc_debug_taggedpointer_ext_classes: ENTRY _objc_msgSend UNWIND _objc_msgSend, NoFrame - MESSENGER_START NilTest NORMAL @@ -727,7 +673,6 @@ _objc_debug_taggedpointer_ext_classes: // cache miss: go search the method lists LCacheMiss: // isa still in r10 - MESSENGER_END_SLOW jmp __objc_msgSend_uncached END_ENTRY _objc_msgSend @@ -776,7 +721,6 @@ LCacheMiss: ENTRY _objc_msgSendSuper UNWIND _objc_msgSendSuper, NoFrame - MESSENGER_START // search the cache (objc_super in %a1) movq class(%a1), %r10 // class = objc_super->class @@ -786,7 +730,6 @@ LCacheMiss: // cache miss: go search the method lists LCacheMiss: // class still in r10 - MESSENGER_END_SLOW jmp __objc_msgSend_uncached END_ENTRY _objc_msgSendSuper @@ -798,7 +741,6 @@ LCacheMiss: ENTRY _objc_msgSendSuper2 UNWIND _objc_msgSendSuper2, NoFrame - MESSENGER_START // objc_super->class is superclass of class to search @@ -811,7 +753,6 @@ LCacheMiss: // cache miss: go search the method lists LCacheMiss: // superclass still in r10 - MESSENGER_END_SLOW jmp __objc_msgSend_uncached END_ENTRY _objc_msgSendSuper2 @@ -856,7 +797,6 @@ LCacheMiss: ENTRY _objc_msgSend_fpret UNWIND _objc_msgSend_fpret, NoFrame - MESSENGER_START NilTest FPRET @@ -870,7 +810,6 @@ LCacheMiss: // cache miss: go search the method lists LCacheMiss: // isa still in r10 - MESSENGER_END_SLOW jmp __objc_msgSend_uncached END_ENTRY _objc_msgSend_fpret @@ -916,7 +855,6 @@ LCacheMiss: ENTRY _objc_msgSend_fp2ret UNWIND _objc_msgSend_fp2ret, NoFrame - MESSENGER_START NilTest FP2RET @@ -930,7 +868,6 @@ LCacheMiss: // cache miss: go search the method lists LCacheMiss: // isa still in r10 - MESSENGER_END_SLOW jmp __objc_msgSend_uncached END_ENTRY _objc_msgSend_fp2ret @@ -982,7 +919,6 @@ LCacheMiss: ENTRY _objc_msgSend_stret UNWIND _objc_msgSend_stret, NoFrame - MESSENGER_START NilTest STRET @@ -996,7 +932,6 @@ LCacheMiss: // cache miss: go search the method lists LCacheMiss: // isa still in r10 - MESSENGER_END_SLOW jmp __objc_msgSend_stret_uncached END_ENTRY _objc_msgSend_stret @@ -1054,7 +989,6 @@ LCacheMiss: ENTRY _objc_msgSendSuper_stret UNWIND _objc_msgSendSuper_stret, NoFrame - MESSENGER_START // search the cache (objc_super in %a2) movq class(%a2), %r10 // class = objc_super->class @@ -1064,7 +998,6 @@ LCacheMiss: // cache miss: go search the method lists LCacheMiss: // class still in r10 - MESSENGER_END_SLOW jmp __objc_msgSend_stret_uncached END_ENTRY _objc_msgSendSuper_stret @@ -1076,7 +1009,6 @@ LCacheMiss: ENTRY _objc_msgSendSuper2_stret UNWIND _objc_msgSendSuper2_stret, NoFrame - MESSENGER_START // search the cache (objc_super in %a2) movq class(%a2), %r10 // class = objc_super->class @@ -1087,7 +1019,6 @@ LCacheMiss: // cache miss: go search the method lists LCacheMiss: // superclass still in r10 - MESSENGER_END_SLOW jmp __objc_msgSend_stret_uncached END_ENTRY _objc_msgSendSuper2_stret @@ -1200,10 +1131,6 @@ LCacheMiss: // THIS IS NOT A CALLABLE C FUNCTION // Out-of-band condition register is NE for stret, EQ otherwise. - - MESSENGER_START - nop - MESSENGER_END_SLOW jne __objc_msgForward_stret jmp __objc_msgForward diff --git a/runtime/NSObjCRuntime.h b/runtime/NSObjCRuntime.h index d111e0e..1310f03 100644 --- a/runtime/NSObjCRuntime.h +++ b/runtime/NSObjCRuntime.h @@ -8,7 +8,7 @@ #include #include -#if __LP64__ || (TARGET_OS_EMBEDDED && !TARGET_OS_IPHONE) || TARGET_OS_WIN32 || NS_BUILD_32_LIKE_64 +#if __LP64__ || TARGET_OS_WIN32 || NS_BUILD_32_LIKE_64 typedef long NSInteger; typedef unsigned long NSUInteger; #else diff --git a/runtime/NSObject.mm b/runtime/NSObject.mm index ec528a0..33d7267 100644 --- a/runtime/NSObject.mm +++ b/runtime/NSObject.mm @@ -46,57 +46,6 @@ - (SEL)selector; @end -#if TARGET_OS_MAC - -// NSObject used to be in Foundation/CoreFoundation. - -#define SYMBOL_ELSEWHERE_IN_3(sym, vers, n) \ - OBJC_EXPORT const char elsewhere_ ##n __asm__("$ld$hide$os" #vers "$" #sym); const char elsewhere_ ##n = 0 -#define SYMBOL_ELSEWHERE_IN_2(sym, vers, n) \ - SYMBOL_ELSEWHERE_IN_3(sym, vers, n) -#define SYMBOL_ELSEWHERE_IN(sym, vers) \ - SYMBOL_ELSEWHERE_IN_2(sym, vers, __COUNTER__) - -#if __OBJC2__ -# define NSOBJECT_ELSEWHERE_IN(vers) \ - SYMBOL_ELSEWHERE_IN(_OBJC_CLASS_$_NSObject, vers); \ - SYMBOL_ELSEWHERE_IN(_OBJC_METACLASS_$_NSObject, vers); \ - SYMBOL_ELSEWHERE_IN(_OBJC_IVAR_$_NSObject.isa, vers) -#else -# define NSOBJECT_ELSEWHERE_IN(vers) \ - SYMBOL_ELSEWHERE_IN(.objc_class_name_NSObject, vers) -#endif - -#if TARGET_OS_IOS - NSOBJECT_ELSEWHERE_IN(5.1); - NSOBJECT_ELSEWHERE_IN(5.0); - NSOBJECT_ELSEWHERE_IN(4.3); - NSOBJECT_ELSEWHERE_IN(4.2); - NSOBJECT_ELSEWHERE_IN(4.1); - NSOBJECT_ELSEWHERE_IN(4.0); - NSOBJECT_ELSEWHERE_IN(3.2); - NSOBJECT_ELSEWHERE_IN(3.1); - NSOBJECT_ELSEWHERE_IN(3.0); - NSOBJECT_ELSEWHERE_IN(2.2); - NSOBJECT_ELSEWHERE_IN(2.1); - NSOBJECT_ELSEWHERE_IN(2.0); -#elif TARGET_OS_OSX - NSOBJECT_ELSEWHERE_IN(10.7); - NSOBJECT_ELSEWHERE_IN(10.6); - NSOBJECT_ELSEWHERE_IN(10.5); - NSOBJECT_ELSEWHERE_IN(10.4); - NSOBJECT_ELSEWHERE_IN(10.3); - NSOBJECT_ELSEWHERE_IN(10.2); - NSOBJECT_ELSEWHERE_IN(10.1); - NSOBJECT_ELSEWHERE_IN(10.0); -#else - // NSObject has always been in libobjc on these platforms. -#endif - -// TARGET_OS_MAC -#endif - - /*********************************************************************** * Weak ivar support **********************************************************************/ diff --git a/runtime/Object.mm b/runtime/Object.mm index d5721aa..e03b274 100644 --- a/runtime/Object.mm +++ b/runtime/Object.mm @@ -35,15 +35,6 @@ #if __OBJC2__ -__OSX_AVAILABLE(10.0) -__IOS_UNAVAILABLE __TVOS_UNAVAILABLE -__WATCHOS_UNAVAILABLE __BRIDGEOS_UNAVAILABLE -OBJC_ROOT_CLASS -@interface Object { - Class isa; -} -@end - @implementation Object + (id)initialize diff --git a/runtime/a1a2-blocktramps-arm.s b/runtime/a1a2-blocktramps-arm.s deleted file mode 100644 index 9e54078..0000000 --- a/runtime/a1a2-blocktramps-arm.s +++ /dev/null @@ -1,148 +0,0 @@ -#if __arm__ - -#include -#include - -.syntax unified - -.text - - .private_extern __a1a2_tramphead - .private_extern __a1a2_firsttramp - .private_extern __a1a2_trampend - -// Trampoline machinery assumes the trampolines are Thumb function pointers -#if !__thumb2__ -# error sorry -#endif - -.thumb -.thumb_func __a1a2_tramphead -.thumb_func __a1a2_firsttramp -.thumb_func __a1a2_trampend - -.align PAGE_MAX_SHIFT -__a1a2_tramphead: - /* - r0 == self - r12 == pc of trampoline's first instruction + PC bias - lr == original return address - */ - - mov r1, r0 // _cmd = self - - // Trampoline's data is one page before the trampoline text. - // Also correct PC bias of 4 bytes. - sub r12, #PAGE_MAX_SIZE - ldr r0, [r12, #-4] // self = block object - ldr pc, [r0, #12] // tail call block->invoke - // not reached - - // Align trampolines to 8 bytes -.align 3 - -.macro TrampolineEntry - mov r12, pc - b __a1a2_tramphead -.align 3 -.endmacro - -.macro TrampolineEntryX16 - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry -.endmacro - -.macro TrampolineEntryX256 - TrampolineEntryX16 - TrampolineEntryX16 - TrampolineEntryX16 - TrampolineEntryX16 - - TrampolineEntryX16 - TrampolineEntryX16 - TrampolineEntryX16 - TrampolineEntryX16 - - TrampolineEntryX16 - TrampolineEntryX16 - TrampolineEntryX16 - TrampolineEntryX16 - - TrampolineEntryX16 - TrampolineEntryX16 - TrampolineEntryX16 - TrampolineEntryX16 -.endmacro - -.private_extern __a1a2_firsttramp -__a1a2_firsttramp: - // 2048-2 trampolines to fill 16K page - TrampolineEntryX256 - TrampolineEntryX256 - TrampolineEntryX256 - TrampolineEntryX256 - - TrampolineEntryX256 - TrampolineEntryX256 - TrampolineEntryX256 - - TrampolineEntryX16 - TrampolineEntryX16 - TrampolineEntryX16 - TrampolineEntryX16 - - TrampolineEntryX16 - TrampolineEntryX16 - TrampolineEntryX16 - TrampolineEntryX16 - - TrampolineEntryX16 - TrampolineEntryX16 - TrampolineEntryX16 - TrampolineEntryX16 - - TrampolineEntryX16 - TrampolineEntryX16 - TrampolineEntryX16 - - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - - TrampolineEntry - TrampolineEntry - // TrampolineEntry - // TrampolineEntry - -.private_extern __a1a2_trampend -__a1a2_trampend: - -#endif diff --git a/runtime/a1a2-blocktramps-i386.s b/runtime/a1a2-blocktramps-i386.s deleted file mode 100755 index e4579c0..0000000 --- a/runtime/a1a2-blocktramps-i386.s +++ /dev/null @@ -1,566 +0,0 @@ -/* - * Copyright (c) 1999-2007 Apple Inc. All Rights Reserved. - * - * @APPLE_LICENSE_HEADER_START@ - * - * This file contains Original Code and/or Modifications of Original Code - * as defined in and that are subject to the Apple Public Source License - * Version 2.0 (the 'License'). You may not use this file except in - * compliance with the License. Please obtain a copy of the License at - * http://www.opensource.apple.com/apsl/ and read it before using this - * file. - * - * The Original Code and all software distributed under the License are - * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER - * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, - * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. - * Please see the License for the specific language governing rights and - * limitations under the License. - * - * @APPLE_LICENSE_HEADER_END@ - */ - -#ifdef __i386__ - -#include - -.text - .private_extern __a1a2_tramphead - .private_extern __a1a2_firsttramp - .private_extern __a1a2_nexttramp - .private_extern __a1a2_trampend - -.align PAGE_SHIFT -__a1a2_tramphead: - popl %eax - andl $0xFFFFFFF8, %eax - subl $ PAGE_SIZE, %eax - movl 4(%esp), %ecx // self -> ecx - movl %ecx, 8(%esp) // ecx -> _cmd - movl (%eax), %ecx // blockPtr -> ecx - movl %ecx, 4(%esp) // ecx -> self - jmp *12(%ecx) // tail to block->invoke - -.macro TrampolineEntry - call __a1a2_tramphead - nop - nop - nop -.endmacro - -.align 5 -__a1a2_firsttramp: - TrampolineEntry -__a1a2_nexttramp: // used to calculate size of each trampoline - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - -__a1a2_trampend: - -#endif diff --git a/runtime/a1a2-blocktramps-x86_64.s b/runtime/a1a2-blocktramps-x86_64.s deleted file mode 100755 index 696eb59..0000000 --- a/runtime/a1a2-blocktramps-x86_64.s +++ /dev/null @@ -1,564 +0,0 @@ -/* - * Copyright (c) 1999-2007 Apple Inc. All Rights Reserved. - * - * @APPLE_LICENSE_HEADER_START@ - * - * This file contains Original Code and/or Modifications of Original Code - * as defined in and that are subject to the Apple Public Source License - * Version 2.0 (the 'License'). You may not use this file except in - * compliance with the License. Please obtain a copy of the License at - * http://www.opensource.apple.com/apsl/ and read it before using this - * file. - * - * The Original Code and all software distributed under the License are - * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER - * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, - * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. - * Please see the License for the specific language governing rights and - * limitations under the License. - * - * @APPLE_LICENSE_HEADER_END@ - */ - -#ifdef __x86_64__ - -#include - - .text - .private_extern __a1a2_tramphead - .private_extern __a1a2_firsttramp - .private_extern __a1a2_nexttramp - .private_extern __a1a2_trampend - -.align PAGE_SHIFT -__a1a2_tramphead: - popq %r10 - andq $0xFFFFFFFFFFFFFFF8, %r10 - subq $ PAGE_SIZE, %r10 - movq %rdi, %rsi // arg1 -> arg2 - movq (%r10), %rdi // block -> arg1 - jmp *16(%rdi) - -.macro TrampolineEntry - callq __a1a2_tramphead - nop - nop - nop -.endmacro - -.align 5 -__a1a2_firsttramp: - TrampolineEntry -__a1a2_nexttramp: - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - -__a1a2_trampend: - -#endif diff --git a/runtime/a2a3-blocktramps-arm.s b/runtime/a2a3-blocktramps-arm.s deleted file mode 100644 index ac0ce72..0000000 --- a/runtime/a2a3-blocktramps-arm.s +++ /dev/null @@ -1,148 +0,0 @@ -#if __arm__ - -#include -#include - -.syntax unified - -.text - - .private_extern __a2a3_tramphead - .private_extern __a2a3_firsttramp - .private_extern __a2a3_trampend - -// Trampoline machinery assumes the trampolines are Thumb function pointers -#if !__thumb2__ -# error sorry -#endif - -.thumb -.thumb_func __a2a3_tramphead -.thumb_func __a2a3_firsttramp -.thumb_func __a2a3_trampend - -.align PAGE_MAX_SHIFT -__a2a3_tramphead: - /* - r1 == self - r12 == pc of trampoline's first instruction + PC bias - lr == original return address - */ - - mov r2, r1 // _cmd = self - - // Trampoline's data is one page before the trampoline text. - // Also correct PC bias of 4 bytes. - sub r12, #PAGE_MAX_SIZE - ldr r1, [r12, #-4] // self = block object - ldr pc, [r1, #12] // tail call block->invoke - // not reached - - // Align trampolines to 8 bytes -.align 3 - -.macro TrampolineEntry - mov r12, pc - b __a2a3_tramphead -.align 3 -.endmacro - -.macro TrampolineEntryX16 - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry -.endmacro - -.macro TrampolineEntryX256 - TrampolineEntryX16 - TrampolineEntryX16 - TrampolineEntryX16 - TrampolineEntryX16 - - TrampolineEntryX16 - TrampolineEntryX16 - TrampolineEntryX16 - TrampolineEntryX16 - - TrampolineEntryX16 - TrampolineEntryX16 - TrampolineEntryX16 - TrampolineEntryX16 - - TrampolineEntryX16 - TrampolineEntryX16 - TrampolineEntryX16 - TrampolineEntryX16 -.endmacro - -.private_extern __a2a3_firsttramp -__a2a3_firsttramp: - // 2048-2 trampolines to fill 16K page - TrampolineEntryX256 - TrampolineEntryX256 - TrampolineEntryX256 - TrampolineEntryX256 - - TrampolineEntryX256 - TrampolineEntryX256 - TrampolineEntryX256 - - TrampolineEntryX16 - TrampolineEntryX16 - TrampolineEntryX16 - TrampolineEntryX16 - - TrampolineEntryX16 - TrampolineEntryX16 - TrampolineEntryX16 - TrampolineEntryX16 - - TrampolineEntryX16 - TrampolineEntryX16 - TrampolineEntryX16 - TrampolineEntryX16 - - TrampolineEntryX16 - TrampolineEntryX16 - TrampolineEntryX16 - - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - - TrampolineEntry - TrampolineEntry - // TrampolineEntry - // TrampolineEntry - -.private_extern __a2a3_trampend -__a2a3_trampend: - -#endif diff --git a/runtime/a2a3-blocktramps-i386.s b/runtime/a2a3-blocktramps-i386.s deleted file mode 100755 index d9932f6..0000000 --- a/runtime/a2a3-blocktramps-i386.s +++ /dev/null @@ -1,566 +0,0 @@ -/* - * Copyright (c) 1999-2007 Apple Inc. All Rights Reserved. - * - * @APPLE_LICENSE_HEADER_START@ - * - * This file contains Original Code and/or Modifications of Original Code - * as defined in and that are subject to the Apple Public Source License - * Version 2.0 (the 'License'). You may not use this file except in - * compliance with the License. Please obtain a copy of the License at - * http://www.opensource.apple.com/apsl/ and read it before using this - * file. - * - * The Original Code and all software distributed under the License are - * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER - * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, - * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. - * Please see the License for the specific language governing rights and - * limitations under the License. - * - * @APPLE_LICENSE_HEADER_END@ - */ - -#ifdef __i386__ - -#include - -.text - .private_extern __a2a3_tramphead - .private_extern __a2a3_firsttramp - .private_extern __a2a3_nexttramp - .private_extern __a2a3_trampend - -.align PAGE_SHIFT -__a2a3_tramphead: - popl %eax - andl $0xFFFFFFF8, %eax - subl $ PAGE_SIZE, %eax - movl 8(%esp), %ecx // self -> ecx - movl %ecx, 12(%esp) // ecx -> _cmd - movl (%eax), %ecx // blockPtr -> ecx - movl %ecx, 8(%esp) // ecx -> self - jmp *12(%ecx) // tail to block->invoke - -.macro TrampolineEntry - call __a2a3_tramphead - nop - nop - nop -.endmacro - -.align 5 -__a2a3_firsttramp: - TrampolineEntry -__a2a3_nexttramp: - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - -__a2a3_trampend: - -#endif diff --git a/runtime/a2a3-blocktramps-x86_64.s b/runtime/a2a3-blocktramps-x86_64.s deleted file mode 100755 index 4904ac4..0000000 --- a/runtime/a2a3-blocktramps-x86_64.s +++ /dev/null @@ -1,565 +0,0 @@ -/* - * Copyright (c) 1999-2007 Apple Inc. All Rights Reserved. - * - * @APPLE_LICENSE_HEADER_START@ - * - * This file contains Original Code and/or Modifications of Original Code - * as defined in and that are subject to the Apple Public Source License - * Version 2.0 (the 'License'). You may not use this file except in - * compliance with the License. Please obtain a copy of the License at - * http://www.opensource.apple.com/apsl/ and read it before using this - * file. - * - * The Original Code and all software distributed under the License are - * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER - * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, - * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. - * Please see the License for the specific language governing rights and - * limitations under the License. - * - * @APPLE_LICENSE_HEADER_END@ - */ - -#ifdef __x86_64__ - -#include - - .text - .private_extern __a2a3_tramphead - .private_extern __a2a3_firsttramp - .private_extern __a2a3_nexttramp - .private_extern __a2a3_trampend - -.align PAGE_SHIFT -__a2a3_tramphead: - popq %r10 - andq $0xFFFFFFFFFFFFFFF8, %r10 - subq $ PAGE_SIZE, %r10 - // %rdi -- first arg -- is address of return value's space. Don't mess with it. - movq %rsi, %rdx // arg2 -> arg3 - movq (%r10), %rsi // block -> arg2 - jmp *16(%rsi) - -.macro TrampolineEntry - callq __a2a3_tramphead - nop - nop - nop -.endmacro - -.align 5 -__a2a3_firsttramp: - TrampolineEntry -__a2a3_nexttramp: - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - TrampolineEntry - -__a2a3_trampend: - -#endif diff --git a/runtime/arm64-asm.h b/runtime/arm64-asm.h new file mode 100644 index 0000000..0bbed2f --- /dev/null +++ b/runtime/arm64-asm.h @@ -0,0 +1,166 @@ +/* + * @APPLE_LICENSE_HEADER_START@ + * + * Copyright (c) 2018 Apple Inc. All Rights Reserved. + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPLE_LICENSE_HEADER_END@ + */ +/******************************************************************** + * + * arm64-asm.h - asm tools for arm64/arm64_32 and ROP/JOP + * + ********************************************************************/ + +#if __arm64__ + +#if __LP64__ +// true arm64 + +#define SUPPORT_TAGGED_POINTERS 1 +#define PTR .quad +#define PTRSIZE 8 +#define PTRSHIFT 3 // 1<= 2 || (__arm64__ && !__LP64__) + // armv7k or arm64_32 + +# define ISA_INDEX_IS_NPI_BIT 0 +# define ISA_INDEX_IS_NPI_MASK 0x00000001 +# define ISA_INDEX_MASK 0x0001FFFC +# define ISA_INDEX_SHIFT 2 +# define ISA_INDEX_BITS 15 +# define ISA_INDEX_COUNT (1 << ISA_INDEX_BITS) +# define ISA_INDEX_MAGIC_MASK 0x001E0001 +# define ISA_INDEX_MAGIC_VALUE 0x001C0001 +# define ISA_BITFIELD \ + uintptr_t nonpointer : 1; \ + uintptr_t has_assoc : 1; \ + uintptr_t indexcls : 15; \ + uintptr_t magic : 4; \ + uintptr_t has_cxx_dtor : 1; \ + uintptr_t weakly_referenced : 1; \ + uintptr_t deallocating : 1; \ + uintptr_t has_sidetable_rc : 1; \ + uintptr_t extra_rc : 7 +# define RC_ONE (1ULL<<25) +# define RC_HALF (1ULL<<6) + +# else +# error unknown architecture for indexed isa +# endif + +// SUPPORT_INDEXED_ISA +#endif + + +// _OBJC_ISA_H_ +#endif diff --git a/runtime/maptable.mm b/runtime/maptable.mm index 0413ecd..a59b00b 100644 --- a/runtime/maptable.mm +++ b/runtime/maptable.mm @@ -161,11 +161,107 @@ BOOL NXCompareMapTables(NXMapTable *table1, NXMapTable *table2) { unsigned NXCountMapTable(NXMapTable *table) { return table->count; } +#if __x86_64__ +extern "C" void __NXMAPTABLE_CORRUPTED__ +(const void *table, const void *buckets, uint64_t count, + uint64_t nbBucketsMinusOne, uint64_t badkeys, uint64_t index, + uint64_t index2, uint64_t pairIndexes, const void *key1, + const void *value1, const void *key2, const void *value2, + const void *key3, const void *value3); + +static int _mapStrIsEqual(NXMapTable *table, const void *key1, const void *key2); + +asm("\n .text" + "\n .private_extern ___NXMAPTABLE_CORRUPTED__" + "\n ___NXMAPTABLE_CORRUPTED__:" + // push a frame for the unwinder to see + "\n pushq %rbp" + "\n mov %rsp, %rbp" + // push register parameters to the stack in reverse order + "\n pushq %r9" + "\n pushq %r8" + "\n pushq %rcx" + "\n pushq %rdx" + "\n pushq %rsi" + "\n pushq %rdi" + // pop the pushed register parameters into their destinations + "\n popq %rax" // table + "\n popq %rbx" // buckets + "\n popq %rcx" // count + "\n popq %rdx" // nbBucketsMinusOne + "\n popq %rdi" // badkeys + "\n popq %rsi" // index + // read stack parameters into their destinations + "\n mov 0*8+16(%rbp), %r8" // index2 + "\n mov 1*8+16(%rbp), %r9" // pairIndexes + "\n mov 2*8+16(%rbp), %r10" // key1 + "\n mov 3*8+16(%rbp), %r11" // value1 + "\n mov 4*8+16(%rbp), %r12" // key2 + "\n mov 5*8+16(%rbp), %r13" // value2 + "\n mov 6*8+16(%rbp), %r14" // key3 + "\n mov 7*8+16(%rbp), %r15" // value3 + "\n ud2"); +#endif + +// Look for a particular case of data corruption (rdar://36373000) +// and investigate it further before crashing. +static void validateKey(NXMapTable *table, MapPair *pair, + unsigned index, unsigned index2) +{ +#if __x86_64__ +# define BADKEY ((void * _Nonnull)(0xfffffffffffffffeULL)) + if (pair->key != BADKEY || + table->prototype->isEqual != _mapStrIsEqual) + { + return; + } + + _objc_inform_now_and_on_crash + ("NXMapTable %p (%p) has invalid key/value pair %p->%p (%p)", + table, table->buckets, pair->key, pair->value, pair); + _objc_inform_now_and_on_crash + ("table %p, buckets %p, count %u, nbNucketsMinusOne %u, " + "prototype %p (hash %p, isEqual %p, free %p)", + table, table->buckets, table->count, table->nbBucketsMinusOne, + table->prototype, table->prototype->hash, table->prototype->isEqual, + table->prototype->free); + + // Count the number of bad keys in the table. + MapPair *pairs = (MapPair *)table->buckets; + unsigned badKeys = 0; + for (unsigned i = 0; i < table->nbBucketsMinusOne+1; i++) { + if (pairs[i].key == BADKEY) badKeys++; + } + + _objc_inform_now_and_on_crash("%u invalid keys in table", badKeys); + + // Record some additional key pairs for posterity. + unsigned pair2Index = nextIndex(table, index); + unsigned pair3Index = nextIndex(table, pair2Index); + MapPair *pair2 = pairs + pair2Index; + MapPair *pair3 = pairs + pair3Index; + uint64_t pairIndexes = ((uint64_t)pair2Index << 32) | pair3Index; + + // Save a bunch of values to registers so we can see them in the crash log. + __NXMAPTABLE_CORRUPTED__ + (// rax, rbx, rcx, rdx + table, table->buckets, table->count, table->nbBucketsMinusOne, + // rdi, rsi, skip rbp, skip rsp + badKeys, index, + // r8, r9, r10, r11 + index2, pairIndexes, pair->key, pair->value, + // r12, r13, r14, r15 + pair2->key, pair2->value, pair3->key, pair3->value); +#endif +} + static INLINE void *_NXMapMember(NXMapTable *table, const void *key, void **value) { MapPair *pairs = (MapPair *)table->buckets; unsigned index = bucketOf(table, key); MapPair *pair = pairs + index; if (pair->key == NX_MAPNOTAKEY) return NX_MAPNOTAKEY; + validateKey(table, pair, index, index); + if (isEqual(table, pair->key, key)) { *value = (void *)pair->value; return (void *)pair->key; @@ -174,6 +270,7 @@ BOOL NXCompareMapTables(NXMapTable *table1, NXMapTable *table2) { while ((index2 = nextIndex(table, index2)) != index) { pair = pairs + index2; if (pair->key == NX_MAPNOTAKEY) return NX_MAPNOTAKEY; + validateKey(table, pair, index, index2); if (isEqual(table, pair->key, key)) { *value = (void *)pair->value; return (void *)pair->key; @@ -244,7 +341,7 @@ static void _NXMapRehash(NXMapTable *table) { while ((index2 = nextIndex(table, index2)) != index) { pair = pairs + index2; if (pair->key == NX_MAPNOTAKEY) { - pair->key = key; pair->value = value; + pair->key = key; pair->value = value; table->count++; if (table->count * 4 > numBuckets * 3) _NXMapRehash(table); return NULL; diff --git a/runtime/objc-abi.h b/runtime/objc-abi.h index 8d861ef..ddab3b8 100644 --- a/runtime/objc-abi.h +++ b/runtime/objc-abi.h @@ -43,6 +43,45 @@ #include #include +/* Linker metadata symbols */ + +// NSObject was in Foundation/CF on macOS < 10.8. +#if TARGET_OS_OSX +#if __OBJC2__ + +OBJC_EXPORT const char __objc_nsobject_class_10_5 + __asm__("$ld$hide$os10.5$_OBJC_CLASS_$_NSObject"); +OBJC_EXPORT const char __objc_nsobject_class_10_6 + __asm__("$ld$hide$os10.6$_OBJC_CLASS_$_NSObject"); +OBJC_EXPORT const char __objc_nsobject_class_10_7 + __asm__("$ld$hide$os10.7$_OBJC_CLASS_$_NSObject"); + +OBJC_EXPORT const char __objc_nsobject_metaclass_10_5 + __asm__("$ld$hide$os10.5$_OBJC_METACLASS_$_NSObject"); +OBJC_EXPORT const char __objc_nsobject_metaclass_10_6 + __asm__("$ld$hide$os10.6$_OBJC_METACLASS_$_NSObject"); +OBJC_EXPORT const char __objc_nsobject_metaclass_10_7 + __asm__("$ld$hide$os10.7$_OBJC_METACLASS_$_NSObject"); + +OBJC_EXPORT const char __objc_nsobject_isa_10_5 + __asm__("$ld$hide$os10.5$_OBJC_IVAR_$_NSObject.isa"); +OBJC_EXPORT const char __objc_nsobject_isa_10_6 + __asm__("$ld$hide$os10.6$_OBJC_IVAR_$_NSObject.isa"); +OBJC_EXPORT const char __objc_nsobject_isa_10_7 + __asm__("$ld$hide$os10.7$_OBJC_IVAR_$_NSObject.isa"); + +#else + +OBJC_EXPORT const char __objc_nsobject_class_10_5 + __asm__("$ld$hide$os10.5$.objc_class_name_NSObject"); +OBJC_EXPORT const char __objc_nsobject_class_10_6 + __asm__("$ld$hide$os10.6$.objc_class_name_NSObject"); +OBJC_EXPORT const char __objc_nsobject_class_10_7 + __asm__("$ld$hide$os10.7$.objc_class_name_NSObject"); + +#endif +#endif + /* Runtime startup. */ // Old static initializer. Used by old crt1.o and old bug workarounds. @@ -294,61 +333,46 @@ objc_msgLookup_fp2ret(void) #endif -#if TARGET_OS_OSX && defined(__x86_64__) -// objc_msgSend_fixup() is used for vtable-dispatchable call sites. +#if (TARGET_OS_OSX || TARGET_OS_SIMULATOR) && defined(__x86_64__) +// objc_msgSend_fixup() was used for vtable-dispatchable call sites. +// The symbols remain exported on macOS for binary compatibility. +// The symbols can probably be removed from iOS simulator but we haven't tried. OBJC_EXPORT void objc_msgSend_fixup(void) - __OSX_DEPRECATED(10.5, 10.8, "fixup dispatch is no longer optimized") - __IOS_UNAVAILABLE __TVOS_UNAVAILABLE - __WATCHOS_UNAVAILABLE __BRIDGEOS_UNAVAILABLE; + __OSX_DEPRECATED(10.5, 10.8, "fixup dispatch is no longer optimized"); OBJC_EXPORT void objc_msgSend_stret_fixup(void) - __OSX_DEPRECATED(10.5, 10.8, "fixup dispatch is no longer optimized") - __IOS_UNAVAILABLE __TVOS_UNAVAILABLE - __WATCHOS_UNAVAILABLE __BRIDGEOS_UNAVAILABLE; + __OSX_DEPRECATED(10.5, 10.8, "fixup dispatch is no longer optimized"); OBJC_EXPORT void objc_msgSendSuper2_fixup(void) - __OSX_DEPRECATED(10.5, 10.8, "fixup dispatch is no longer optimized") - __IOS_UNAVAILABLE __TVOS_UNAVAILABLE - __WATCHOS_UNAVAILABLE __BRIDGEOS_UNAVAILABLE; + __OSX_DEPRECATED(10.5, 10.8, "fixup dispatch is no longer optimized"); OBJC_EXPORT void objc_msgSendSuper2_stret_fixup(void) - __OSX_DEPRECATED(10.5, 10.8, "fixup dispatch is no longer optimized") - __IOS_UNAVAILABLE __TVOS_UNAVAILABLE - __WATCHOS_UNAVAILABLE __BRIDGEOS_UNAVAILABLE; + __OSX_DEPRECATED(10.5, 10.8, "fixup dispatch is no longer optimized"); OBJC_EXPORT void objc_msgSend_fpret_fixup(void) - __OSX_DEPRECATED(10.5, 10.8, "fixup dispatch is no longer optimized") - __IOS_UNAVAILABLE __TVOS_UNAVAILABLE - __WATCHOS_UNAVAILABLE __BRIDGEOS_UNAVAILABLE; + __OSX_DEPRECATED(10.5, 10.8, "fixup dispatch is no longer optimized"); OBJC_EXPORT void objc_msgSend_fp2ret_fixup(void) - __OSX_DEPRECATED(10.5, 10.8, "fixup dispatch is no longer optimized") - __IOS_UNAVAILABLE __TVOS_UNAVAILABLE - __WATCHOS_UNAVAILABLE __BRIDGEOS_UNAVAILABLE; + __OSX_DEPRECATED(10.5, 10.8, "fixup dispatch is no longer optimized"); #endif /* C++-compatible exception handling. */ #if __OBJC2__ -// fixme these conflict with C++ compiler's internal definitions -#if !defined(__cplusplus) - // Vtable for C++ exception typeinfo for Objective-C types. -OBJC_EXPORT const void * _Nullableobjc_ehtype_vtable[] +OBJC_EXPORT const void * _Nullable objc_ehtype_vtable[] OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0); // C++ exception typeinfo for type `id`. OBJC_EXPORT struct objc_typeinfo OBJC_EHTYPE_id OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0); -#endif - // Exception personality function for Objective-C and Objective-C++ code. struct _Unwind_Exception; struct _Unwind_Context; @@ -375,7 +399,7 @@ objc_retainBlock(id _Nullable) // Extract class pointer from an isa field. -#if TARGET_OS_SIMULATOR +#if TARGET_OS_SIMULATOR && !TARGET_OS_IOSMAC // No simulators use nonpointer isa yet. #elif __LP64__ @@ -387,7 +411,7 @@ objc_retainBlock(id _Nullable) OBJC_EXPORT const struct { char c; } objc_absolute_packed_isa_class_mask OBJC_AVAILABLE(10.12, 10.0, 10.0, 3.0, 2.0); -#elif __ARM_ARCH_7K__ >= 2 +#elif (__ARM_ARCH_7K__ >= 2 || (__arm64__ && !__LP64__)) # define OBJC_HAVE_NONPOINTER_ISA 1 # define OBJC_HAVE_INDEXED_NONPOINTER_ISA 1 @@ -409,8 +433,29 @@ OBJC_EXPORT const struct { char c; } objc_absolute_indexed_isa_index_shift #endif -// OBJC2 #endif + +/* Object class */ + +// This symbol might be required for binary compatibility, so we +// declare it here where TAPI will see it. +#if __OBJC__ && __OBJC2__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wobjc-interface-ivars" +#if !defined(OBJC_DECLARE_SYMBOLS) +__OSX_AVAILABLE(10.0) +__IOS_UNAVAILABLE __TVOS_UNAVAILABLE +__WATCHOS_UNAVAILABLE __BRIDGEOS_UNAVAILABLE +#endif +OBJC_ROOT_CLASS +@interface Object { + Class isa; +} +@end +#pragma clang diagnostic pop +#endif + + // _OBJC_ABI_H #endif diff --git a/runtime/objc-api.h b/runtime/objc-api.h index fb0e9ea..7a7e397 100644 --- a/runtime/objc-api.h +++ b/runtime/objc-api.h @@ -54,14 +54,28 @@ # endif #endif -#ifndef __BRIDGEOS_AVAILABLE -# define __BRIDGEOS_AVAILABLE(v) -#endif -#ifndef __BRIDGEOS_DEPRECATED -# define __BRIDGEOS_DEPRECATED(v1, v2, m) -#endif -#ifndef __BRIDGEOS_UNAVAILABLE -# define __BRIDGEOS_UNAVAILABLE +#ifndef __APPLE_BLEACH_SDK__ +# if __has_feature(attribute_availability_bridgeos) +# ifndef __BRIDGEOS_AVAILABLE +# define __BRIDGEOS_AVAILABLE(_vers) __OS_AVAILABILITY(bridgeos,introduced=_vers) +# endif +# ifndef __BRIDGEOS_DEPRECATED +# define __BRIDGEOS_DEPRECATED(_start, _dep, _msg) __BRIDGEOS_AVAILABLE(_start) __OS_AVAILABILITY_MSG(bridgeos,deprecated=_dep,_msg) +# endif +# ifndef __BRIDGEOS_UNAVAILABLE +# define __BRIDGEOS_UNAVAILABLE __OS_AVAILABILITY(bridgeos,unavailable) +# endif +# else +# ifndef __BRIDGEOS_AVAILABLE +# define __BRIDGEOS_AVAILABLE(_vers) +# endif +# ifndef __BRIDGEOS_DEPRECATED +# define __BRIDGEOS_DEPRECATED(_start, _dep, _msg) +# endif +# ifndef __BRIDGEOS_UNAVAILABLE +# define __BRIDGEOS_UNAVAILABLE +# endif +# endif #endif /* @@ -108,7 +122,13 @@ /* OBJC_OLD_DISPATCH_PROTOTYPES == 0 enforces the rule that the dispatch * functions must be cast to an appropriate function pointer type. */ #if !defined(OBJC_OLD_DISPATCH_PROTOTYPES) -# define OBJC_OLD_DISPATCH_PROTOTYPES 1 +# if __swift__ + // Existing Swift code expects IMP to be Comparable. + // Variadic IMP is comparable via OpaquePointer; non-variadic IMP isn't. +# define OBJC_OLD_DISPATCH_PROTOTYPES 1 +# else +# define OBJC_OLD_DISPATCH_PROTOTYPES 1 +# endif #endif diff --git a/runtime/objc-auto.h b/runtime/objc-auto.h index 37da077..81c107e 100644 --- a/runtime/objc-auto.h +++ b/runtime/objc-auto.h @@ -63,7 +63,8 @@ enum { }; -#ifndef OBJC_NO_GC +#if !defined(OBJC_NO_GC) || \ + (OBJC_DECLARE_SYMBOLS && !defined(OBJC_NO_GC_API)) /* Out-of-line declarations */ diff --git a/runtime/objc-auto.mm b/runtime/objc-auto.mm index bf7dceb..e8e704b 100644 --- a/runtime/objc-auto.mm +++ b/runtime/objc-auto.mm @@ -21,7 +21,9 @@ * @APPLE_LICENSE_HEADER_END@ */ +#define OBJC_DECLARE_SYMBOLS 1 #include "objc-private.h" +#include "objc-auto.h" // GC is no longer supported. @@ -35,86 +37,85 @@ #else // No GC but we do need to export GC symbols. -// These are mostly the same as the OBJC_NO_GC inline versions in objc-auto.h. # if !SUPPORT_GC_COMPAT # error inconsistent config settings # endif -OBJC_EXPORT void objc_collect(unsigned long options __unused) { } -OBJC_EXPORT BOOL objc_collectingEnabled(void) { return NO; } -OBJC_EXPORT void objc_setCollectionThreshold(size_t threshold __unused) { } -OBJC_EXPORT void objc_setCollectionRatio(size_t ratio __unused) { } -OBJC_EXPORT void objc_startCollectorThread(void) { } +void objc_collect(unsigned long options __unused) { } +BOOL objc_collectingEnabled(void) { return NO; } +void objc_setCollectionThreshold(size_t threshold __unused) { } +void objc_setCollectionRatio(size_t ratio __unused) { } +void objc_startCollectorThread(void) { } #if TARGET_OS_WIN32 -OBJC_EXPORT BOOL objc_atomicCompareAndSwapPtr(id predicate, id replacement, volatile id *objectLocation) +BOOL objc_atomicCompareAndSwapPtr(id predicate, id replacement, volatile id *objectLocation) { void *original = InterlockedCompareExchangePointer((void * volatile *)objectLocation, (void *)replacement, (void *)predicate); return (original == predicate); } -OBJC_EXPORT BOOL objc_atomicCompareAndSwapPtrBarrier(id predicate, id replacement, volatile id *objectLocation) +BOOL objc_atomicCompareAndSwapPtrBarrier(id predicate, id replacement, volatile id *objectLocation) { void *original = InterlockedCompareExchangePointer((void * volatile *)objectLocation, (void *)replacement, (void *)predicate); return (original == predicate); } #else -OBJC_EXPORT BOOL objc_atomicCompareAndSwapPtr(id predicate, id replacement, volatile id *objectLocation) +BOOL objc_atomicCompareAndSwapPtr(id predicate, id replacement, volatile id *objectLocation) { return OSAtomicCompareAndSwapPtr((void *)predicate, (void *)replacement, (void * volatile *)objectLocation); } -OBJC_EXPORT BOOL objc_atomicCompareAndSwapPtrBarrier(id predicate, id replacement, volatile id *objectLocation) +BOOL objc_atomicCompareAndSwapPtrBarrier(id predicate, id replacement, volatile id *objectLocation) { return OSAtomicCompareAndSwapPtrBarrier((void *)predicate, (void *)replacement, (void * volatile *)objectLocation); } #endif -OBJC_EXPORT BOOL objc_atomicCompareAndSwapGlobal(id predicate, id replacement, volatile id *objectLocation) +BOOL objc_atomicCompareAndSwapGlobal(id predicate, id replacement, volatile id *objectLocation) { return objc_atomicCompareAndSwapPtr(predicate, replacement, objectLocation); } -OBJC_EXPORT BOOL objc_atomicCompareAndSwapGlobalBarrier(id predicate, id replacement, volatile id *objectLocation) +BOOL objc_atomicCompareAndSwapGlobalBarrier(id predicate, id replacement, volatile id *objectLocation) { return objc_atomicCompareAndSwapPtrBarrier(predicate, replacement, objectLocation); } -OBJC_EXPORT BOOL objc_atomicCompareAndSwapInstanceVariable(id predicate, id replacement, volatile id *objectLocation) +BOOL objc_atomicCompareAndSwapInstanceVariable(id predicate, id replacement, volatile id *objectLocation) { return objc_atomicCompareAndSwapPtr(predicate, replacement, objectLocation); } -OBJC_EXPORT BOOL objc_atomicCompareAndSwapInstanceVariableBarrier(id predicate, id replacement, volatile id *objectLocation) +BOOL objc_atomicCompareAndSwapInstanceVariableBarrier(id predicate, id replacement, volatile id *objectLocation) { return objc_atomicCompareAndSwapPtrBarrier(predicate, replacement, objectLocation); } -OBJC_EXPORT id objc_assign_strongCast(id val, id *dest) +id objc_assign_strongCast(id val, id *dest) { return (*dest = val); } -OBJC_EXPORT id objc_assign_global(id val, id *dest) +id objc_assign_global(id val, id *dest) { return (*dest = val); } -OBJC_EXPORT id objc_assign_threadlocal(id val, id *dest) +id objc_assign_threadlocal(id val, id *dest) { return (*dest = val); } -OBJC_EXPORT id objc_assign_ivar(id val, id dest, ptrdiff_t offset) +id objc_assign_ivar(id val, id dest, ptrdiff_t offset) { return (*(id*)((char *)dest+offset) = val); } -OBJC_EXPORT id objc_read_weak(id *location) +id objc_read_weak(id *location) { return *location; } -OBJC_EXPORT id objc_assign_weak(id value, id *location) +id objc_assign_weak(id value, id *location) { return (*location = value); } -OBJC_EXPORT void *objc_memmove_collectable(void *dst, const void *src, size_t size) +void *objc_memmove_collectable(void *dst, const void *src, size_t size) { return memmove(dst, src, size); } -OBJC_EXPORT void objc_finalizeOnMainThread(Class cls __unused) { } -OBJC_EXPORT BOOL objc_is_finalized(void *ptr __unused) { return NO; } -OBJC_EXPORT void objc_clear_stack(unsigned long options __unused) { } +void objc_finalizeOnMainThread(Class cls __unused) { } +BOOL objc_is_finalized(void *ptr __unused) { return NO; } +void objc_clear_stack(unsigned long options __unused) { } -OBJC_EXPORT BOOL objc_collecting_enabled(void) { return NO; } -OBJC_EXPORT void objc_set_collection_threshold(size_t threshold __unused) { } -OBJC_EXPORT void objc_set_collection_ratio(size_t ratio __unused) { } -OBJC_EXPORT void objc_start_collector_thread(void) { } +BOOL objc_collecting_enabled(void) { return NO; } +void objc_set_collection_threshold(size_t threshold __unused) { } +void objc_set_collection_ratio(size_t ratio __unused) { } +void objc_start_collector_thread(void) { } -OBJC_EXPORT id objc_allocate_object(Class cls, int extra) +id objc_allocate_object(Class cls, int extra) { return class_createInstance(cls, extra); } -OBJC_EXPORT void objc_registerThreadWithCollector() { } -OBJC_EXPORT void objc_unregisterThreadWithCollector() { } -OBJC_EXPORT void objc_assertRegisteredThreadWithCollector() { } +void objc_registerThreadWithCollector() { } +void objc_unregisterThreadWithCollector() { } +void objc_assertRegisteredThreadWithCollector() { } -OBJC_EXPORT malloc_zone_t* objc_collect_init(int(*callback)() __unused) { return nil; } -OBJC_EXPORT void* objc_collectableZone() { return nil; } +malloc_zone_t* objc_collect_init(int(*callback)() __unused) { return nil; } +malloc_zone_t* objc_collectableZone() { return nil; } -OBJC_EXPORT BOOL objc_isAuto(id object __unused) { return NO; } -OBJC_EXPORT BOOL objc_dumpHeap(char *filename __unused, unsigned long length __unused) +BOOL objc_isAuto(id object __unused) { return NO; } +BOOL objc_dumpHeap(char *filename __unused, unsigned long length __unused) { return NO; } // not OBJC_NO_GC_API diff --git a/runtime/objc-block-trampolines.h b/runtime/objc-block-trampolines.h new file mode 100644 index 0000000..f5ec10a --- /dev/null +++ b/runtime/objc-block-trampolines.h @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2018 Apple Inc. All Rights Reserved. + * + * @APPLE_LICENSE_HEADER_START@ + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPLE_LICENSE_HEADER_END@ + */ + + +#ifndef _OBJC_TRAMPOLINES_H +#define _OBJC_TRAMPOLINES_H + +/* + * WARNING DANGER HAZARD BEWARE EEK + * + * Everything in this file is for Apple Internal use only. + * These will change in arbitrary OS updates and in unpredictable ways. + * When your program breaks, you get to keep both pieces. + */ + +/* + * objc-block-trampolines.h: Symbols for IMP block trampolines + */ + +#include + +OBJC_EXPORT const char _objc_blockTrampolineImpl + OBJC_AVAILABLE(10.14, 12.0, 12.0, 5.0, 3.0); + +OBJC_EXPORT const char _objc_blockTrampolineStart + OBJC_AVAILABLE(10.14, 12.0, 12.0, 5.0, 3.0); + +OBJC_EXPORT const char _objc_blockTrampolineLast + OBJC_AVAILABLE(10.14, 12.0, 12.0, 5.0, 3.0); + + +OBJC_EXPORT const char _objc_blockTrampolineImpl_stret +OBJC_AVAILABLE(10.14, 12.0, 12.0, 5.0, 3.0) + OBJC_ARM64_UNAVAILABLE; + +OBJC_EXPORT const char _objc_blockTrampolineStart_stret +OBJC_AVAILABLE(10.14, 12.0, 12.0, 5.0, 3.0) + OBJC_ARM64_UNAVAILABLE; + +OBJC_EXPORT const char _objc_blockTrampolineLast_stret +OBJC_AVAILABLE(10.14, 12.0, 12.0, 5.0, 3.0) + OBJC_ARM64_UNAVAILABLE; + +#endif diff --git a/runtime/objc-block-trampolines.mm b/runtime/objc-block-trampolines.mm index 6ef5fcf..c11423b 100644 --- a/runtime/objc-block-trampolines.mm +++ b/runtime/objc-block-trampolines.mm @@ -35,25 +35,153 @@ #include #include #include +#include -// symbols defined in assembly files -// Don't use the symbols directly; they're thumb-biased on some ARM archs. -#define TRAMP(tramp) \ - static inline __unused uintptr_t tramp(void) { \ - extern void *_##tramp; \ - return ((uintptr_t)&_##tramp) & ~1UL; \ - } -// Scalar return -TRAMP(a1a2_tramphead); // trampoline header code -TRAMP(a1a2_firsttramp); // first trampoline -TRAMP(a1a2_trampend); // after the last trampoline +// fixme C++ compilers don't implemement memory_order_consume efficiently. +// Use memory_order_relaxed and cross our fingers. +#define MEMORY_ORDER_CONSUME std::memory_order_relaxed -#if SUPPORT_STRET -// Struct return -TRAMP(a2a3_tramphead); -TRAMP(a2a3_firsttramp); -TRAMP(a2a3_trampend); +// 8 bytes of text and data per trampoline on all architectures. +#define SLOT_SIZE 8 + +// The trampolines are defined in assembly files in libobjc-trampolines.dylib. +// We can't link to libobjc-trampolines.dylib directly because +// for security reasons it isn't in the dyld shared cache. + +// Trampoline addresses are lazily looked up. +// All of them are hidden behind a single atomic pointer for lock-free init. + +#ifdef __PTRAUTH_INTRINSICS__ +# define TrampolinePtrauth __ptrauth(ptrauth_key_function_pointer, 1, 0x3af1) +#else +# define TrampolinePtrauth +#endif + +class TrampolinePointerWrapper { + struct TrampolinePointers { + class TrampolineAddress { + const void * TrampolinePtrauth storage; + + public: + TrampolineAddress(void *dylib, const char *name) { +#define PREFIX "_objc_blockTrampoline" + char symbol[strlen(PREFIX) + strlen(name) + 1]; + strcpy(symbol, PREFIX); + strcat(symbol, name); + // dlsym() from a text segment returns a signed pointer + // Authenticate it manually and let the compiler re-sign it. + storage = ptrauth_auth_data(dlsym(dylib, symbol), + ptrauth_key_function_pointer, 0); + if (!storage) { + _objc_fatal("couldn't dlsym %s", symbol); + } + } + + uintptr_t address() { + return (uintptr_t)(void*)storage; + } + }; + + TrampolineAddress impl; // trampoline header code + TrampolineAddress start; // first trampoline +#if DEBUG + // These symbols are only used in assertions. + // fixme might be able to move the assertions to libobjc-trampolines itself + TrampolineAddress last; // start of the last trampoline + // We don't use the address after the last trampoline because that + // address might be in a different section, and then dlsym() would not + // sign it as a function pointer. +# if SUPPORT_STRET + TrampolineAddress impl_stret; + TrampolineAddress start_stret; + TrampolineAddress last_stret; +# endif +#endif + + uintptr_t textSegment; + uintptr_t textSegmentSize; + + void check() { +#if DEBUG + assert(impl.address() == textSegment + PAGE_MAX_SIZE); + assert(impl.address() % PAGE_SIZE == 0); // not PAGE_MAX_SIZE + assert(impl.address() + PAGE_MAX_SIZE == + last.address() + SLOT_SIZE); + assert(last.address()+8 < textSegment + textSegmentSize); + assert((last.address() - start.address()) % SLOT_SIZE == 0); +# if SUPPORT_STRET + assert(impl_stret.address() == textSegment + 2*PAGE_MAX_SIZE); + assert(impl_stret.address() % PAGE_SIZE == 0); // not PAGE_MAX_SIZE + assert(impl_stret.address() + PAGE_MAX_SIZE == + last_stret.address() + SLOT_SIZE); + assert(start.address() - impl.address() == + start_stret.address() - impl_stret.address()); + assert(last_stret.address() + SLOT_SIZE < + textSegment + textSegmentSize); + assert((last_stret.address() - start_stret.address()) + % SLOT_SIZE == 0); +# endif +#endif + } + + + TrampolinePointers(void *dylib) + : impl(dylib, "Impl") + , start(dylib, "Start") +#if DEBUG + , last(dylib, "Last") +# if SUPPORT_STRET + , impl_stret(dylib, "Impl_stret") + , start_stret(dylib, "Start_stret") + , last_stret(dylib, "Last_stret") +# endif #endif + { + const auto *mh = + dyld_image_header_containing_address((void *)impl.address()); + unsigned long size = 0; + textSegment = (uintptr_t) + getsegmentdata((headerType *)mh, "__TEXT", &size); + textSegmentSize = size; + + check(); + } + }; + + std::atomic trampolines{nil}; + + TrampolinePointers *get() { + return trampolines.load(MEMORY_ORDER_CONSUME); + } + +public: + void Initialize() { + if (get()) return; + + // This code may be called concurrently. + // In the worst case we perform extra dyld operations. + void *dylib = dlopen("/usr/lib/libobjc-trampolines.dylib", + RTLD_NOW | RTLD_LOCAL | RTLD_FIRST); + if (!dylib) { + _objc_fatal("couldn't dlopen libobjc-trampolines.dylib"); + } + + auto t = new TrampolinePointers(dylib); + TrampolinePointers *old = nil; + if (! trampolines.compare_exchange_strong(old, t, memory_order_release)) + { + delete t; // Lost an initialization race. + } + } + + uintptr_t textSegment() { return get()->textSegment; } + uintptr_t textSegmentSize() { return get()->textSegmentSize; } + + uintptr_t impl() { return get()->impl.address(); } + uintptr_t start() { return get()->start.address(); } +}; + +static TrampolinePointerWrapper Trampolines; // argument mode identifier typedef enum { @@ -65,34 +193,37 @@ static inline __unused uintptr_t tramp(void) { \ ArgumentModeCount } ArgumentMode; - // We must take care with our data layout on architectures that support // multiple page sizes. // // The trampoline template in __TEXT is sized and aligned with PAGE_MAX_SIZE. // On some platforms this requires additional linker flags. // -// When we allocate a page pair, we use PAGE_MAX_SIZE size. +// When we allocate a page group, we use PAGE_MAX_SIZE size. // This allows trampoline code to find its data by subtracting PAGE_MAX_SIZE. // -// When we allocate a page pair, we use the process's page alignment. +// When we allocate a page group, we use the process's page alignment. // This simplifies allocation because we don't need to force greater than // default alignment when running with small pages, but it also means // the trampoline code MUST NOT look for its data by masking with PAGE_MAX_MASK. -struct TrampolineBlockPagePair +struct TrampolineBlockPageGroup { - TrampolineBlockPagePair *nextPagePair; // linked list of all pages - TrampolineBlockPagePair *nextAvailablePage; // linked list of pages with available slots + TrampolineBlockPageGroup *nextPageGroup; // linked list of all pages + TrampolineBlockPageGroup *nextAvailablePage; // linked list of pages with available slots uintptr_t nextAvailable; // index of next available slot, endIndex() if no more available // Payload data: block pointers and free list. // Bytes parallel with trampoline header code are the fields above or unused - // uint8_t blocks[ PAGE_MAX_SIZE - sizeof(TrampolineBlockPagePair) ] - - // Code: trampoline header followed by trampolines. - // uint8_t trampolines[PAGE_MAX_SIZE]; + // uint8_t payloads[PAGE_MAX_SIZE - sizeof(TrampolineBlockPageGroup)] + + // Code: Mach-O header, then trampoline header followed by trampolines. + // On platforms with struct return we have non-stret trampolines and + // stret trampolines. The stret and non-stret trampolines at a given + // index share the same data page. + // uint8_t macho[PAGE_MAX_SIZE]; + // uint8_t trampolines[ArgumentModeCount][PAGE_MAX_SIZE]; // Per-trampoline block data format: // initial value is 0 while page data is filled sequentially @@ -105,11 +236,11 @@ static inline __unused uintptr_t tramp(void) { \ }; static uintptr_t headerSize() { - return (uintptr_t) (a1a2_firsttramp() - a1a2_tramphead()); + return (uintptr_t) (Trampolines.start() - Trampolines.impl()); } static uintptr_t slotSize() { - return 8; + return SLOT_SIZE; } static uintptr_t startIndex() { @@ -130,185 +261,158 @@ static bool validIndex(uintptr_t index) { return (Payload *)((char *)this + index*slotSize()); } - IMP trampoline(uintptr_t index) { + uintptr_t trampolinesForMode(int aMode) { + // Skip over data page and Mach-O page. + return (uintptr_t)this + PAGE_MAX_SIZE * (2 + aMode); + } + + IMP trampoline(int aMode, uintptr_t index) { assert(validIndex(index)); - char *imp = (char *)this + index*slotSize() + PAGE_MAX_SIZE; + char *base = (char *)trampolinesForMode(aMode); + char *imp = base + index*slotSize(); #if __arm__ imp++; // trampoline is Thumb instructions +#endif +#if __has_feature(ptrauth_calls) + imp = ptrauth_sign_unauthenticated(imp, + ptrauth_key_function_pointer, 0); #endif return (IMP)imp; } - uintptr_t indexForTrampoline(IMP tramp) { - uintptr_t tramp0 = (uintptr_t)this + PAGE_MAX_SIZE; - uintptr_t start = tramp0 + headerSize(); - uintptr_t end = tramp0 + PAGE_MAX_SIZE; - uintptr_t address = (uintptr_t)tramp; - if (address >= start && address < end) { - return (uintptr_t)(address - tramp0) / slotSize(); + uintptr_t indexForTrampoline(uintptr_t tramp) { + for (int aMode = 0; aMode < ArgumentModeCount; aMode++) { + uintptr_t base = trampolinesForMode(aMode); + uintptr_t start = base + startIndex() * slotSize(); + uintptr_t end = base + endIndex() * slotSize(); + if (tramp >= start && tramp < end) { + return (uintptr_t)(tramp - base) / slotSize(); + } } return 0; } static void check() { - assert(TrampolineBlockPagePair::slotSize() == 8); - assert(TrampolineBlockPagePair::headerSize() >= sizeof(TrampolineBlockPagePair)); - assert(TrampolineBlockPagePair::headerSize() % TrampolineBlockPagePair::slotSize() == 0); - - // _objc_inform("%p %p %p", a1a2_tramphead(), a1a2_firsttramp(), - // a1a2_trampend()); - assert(a1a2_tramphead() % PAGE_SIZE == 0); // not PAGE_MAX_SIZE - assert(a1a2_tramphead() + PAGE_MAX_SIZE == a1a2_trampend()); -#if SUPPORT_STRET - // _objc_inform("%p %p %p", a2a3_tramphead(), a2a3_firsttramp(), - // a2a3_trampend()); - assert(a2a3_tramphead() % PAGE_SIZE == 0); // not PAGE_MAX_SIZE - assert(a2a3_tramphead() + PAGE_MAX_SIZE == a2a3_trampend()); -#endif - -#if __arm__ - // make sure trampolines are Thumb - extern void *_a1a2_firsttramp; - extern void *_a2a3_firsttramp; - assert(((uintptr_t)&_a1a2_firsttramp) % 2 == 1); - assert(((uintptr_t)&_a2a3_firsttramp) % 2 == 1); -#endif + assert(TrampolineBlockPageGroup::headerSize() >= sizeof(TrampolineBlockPageGroup)); + assert(TrampolineBlockPageGroup::headerSize() % TrampolineBlockPageGroup::slotSize() == 0); } }; -// two sets of trampoline pages; one for stack returns and one for register returns -static TrampolineBlockPagePair *headPagePairs[ArgumentModeCount]; +static TrampolineBlockPageGroup *HeadPageGroup; #pragma mark Utility Functions -static inline void _lock() { -#if __OBJC2__ - runtimeLock.write(); -#else - classLock.lock(); -#endif -} - -static inline void _unlock() { -#if __OBJC2__ - runtimeLock.unlockWrite(); -#else - classLock.unlock(); -#endif -} - -static inline void _assert_locked() { -#if __OBJC2__ - runtimeLock.assertWriting(); -#else - classLock.assertLocked(); +#if !__OBJC2__ +#define runtimeLock classLock #endif -} #pragma mark Trampoline Management Functions -static TrampolineBlockPagePair *_allocateTrampolinesAndData(ArgumentMode aMode) +static TrampolineBlockPageGroup *_allocateTrampolinesAndData() { - _assert_locked(); + runtimeLock.assertLocked(); vm_address_t dataAddress; - TrampolineBlockPagePair::check(); - - TrampolineBlockPagePair *headPagePair = headPagePairs[aMode]; - - assert(headPagePair == nil || headPagePair->nextAvailablePage == nil); - + TrampolineBlockPageGroup::check(); + + // Our final mapping will look roughly like this: + // r/w data + // r/o text mapped from libobjc-trampolines.dylib + // with fixed offsets from the text to the data embedded in the text. + // + // More precisely it will look like this: + // 1 page r/w data + // 1 page libobjc-trampolines.dylib Mach-O header + // N pages trampoline code, one for each ArgumentMode + // M pages for the rest of libobjc-trampolines' TEXT segment. + // The kernel requires that we remap the entire TEXT segment every time. + // We assume that our code begins on the second TEXT page, but are robust + // against other additions to the end of the TEXT segment. + + assert(HeadPageGroup == nil || HeadPageGroup->nextAvailablePage == nil); + + auto textSource = Trampolines.textSegment(); + auto textSourceSize = Trampolines.textSegmentSize(); + auto dataSize = PAGE_MAX_SIZE; + + // Allocate a single contiguous region big enough to hold data+text. kern_return_t result; - result = vm_allocate(mach_task_self(), &dataAddress, PAGE_MAX_SIZE * 2, + result = vm_allocate(mach_task_self(), &dataAddress, + dataSize + textSourceSize, VM_FLAGS_ANYWHERE | VM_MAKE_TAG(VM_MEMORY_FOUNDATION)); if (result != KERN_SUCCESS) { _objc_fatal("vm_allocate trampolines failed (%d)", result); } - vm_address_t codeAddress = dataAddress + PAGE_MAX_SIZE; - - uintptr_t codePage; - switch(aMode) { - case ReturnValueInRegisterArgumentMode: - codePage = a1a2_tramphead(); - break; -#if SUPPORT_STRET - case ReturnValueOnStackArgumentMode: - codePage = a2a3_tramphead(); - break; -#endif - default: - _objc_fatal("unknown return mode %d", (int)aMode); - break; - } - + // Remap libobjc-trampolines' TEXT segment atop all + // but the first of the pages we just allocated: + vm_address_t textDest = dataAddress + dataSize; vm_prot_t currentProtection, maxProtection; - result = vm_remap(mach_task_self(), &codeAddress, PAGE_MAX_SIZE, + result = vm_remap(mach_task_self(), &textDest, + textSourceSize, 0, VM_FLAGS_FIXED | VM_FLAGS_OVERWRITE, - mach_task_self(), codePage, TRUE, + mach_task_self(), textSource, TRUE, ¤tProtection, &maxProtection, VM_INHERIT_SHARE); if (result != KERN_SUCCESS) { - // vm_deallocate(mach_task_self(), dataAddress, PAGE_MAX_SIZE * 2); _objc_fatal("vm_remap trampolines failed (%d)", result); } + + TrampolineBlockPageGroup *pageGroup = (TrampolineBlockPageGroup *) dataAddress; + pageGroup->nextAvailable = pageGroup->startIndex(); + pageGroup->nextPageGroup = nil; + pageGroup->nextAvailablePage = nil; - TrampolineBlockPagePair *pagePair = (TrampolineBlockPagePair *) dataAddress; - pagePair->nextAvailable = pagePair->startIndex(); - pagePair->nextPagePair = nil; - pagePair->nextAvailablePage = nil; - - if (headPagePair) { - TrampolineBlockPagePair *lastPagePair = headPagePair; - while(lastPagePair->nextPagePair) { - lastPagePair = lastPagePair->nextPagePair; + if (HeadPageGroup) { + TrampolineBlockPageGroup *lastPageGroup = HeadPageGroup; + while(lastPageGroup->nextPageGroup) { + lastPageGroup = lastPageGroup->nextPageGroup; } - lastPagePair->nextPagePair = pagePair; - headPagePairs[aMode]->nextAvailablePage = pagePair; + lastPageGroup->nextPageGroup = pageGroup; + HeadPageGroup->nextAvailablePage = pageGroup; } else { - headPagePairs[aMode] = pagePair; + HeadPageGroup = pageGroup; } - return pagePair; + return pageGroup; } -static TrampolineBlockPagePair * -_getOrAllocatePagePairWithNextAvailable(ArgumentMode aMode) +static TrampolineBlockPageGroup * +getOrAllocatePageGroupWithNextAvailable() { - _assert_locked(); + runtimeLock.assertLocked(); - TrampolineBlockPagePair *headPagePair = headPagePairs[aMode]; - - if (!headPagePair) - return _allocateTrampolinesAndData(aMode); + if (!HeadPageGroup) + return _allocateTrampolinesAndData(); // make sure head page is filled first - if (headPagePair->nextAvailable != headPagePair->endIndex()) - return headPagePair; + if (HeadPageGroup->nextAvailable != HeadPageGroup->endIndex()) + return HeadPageGroup; - if (headPagePair->nextAvailablePage) // check if there is a page w/a hole - return headPagePair->nextAvailablePage; + if (HeadPageGroup->nextAvailablePage) // check if there is a page w/a hole + return HeadPageGroup->nextAvailablePage; - return _allocateTrampolinesAndData(aMode); // tack on a new one + return _allocateTrampolinesAndData(); // tack on a new one } -static TrampolineBlockPagePair * -_pageAndIndexContainingIMP(IMP anImp, uintptr_t *outIndex, - TrampolineBlockPagePair **outHeadPagePair) +static TrampolineBlockPageGroup * +pageAndIndexContainingIMP(IMP anImp, uintptr_t *outIndex) { - _assert_locked(); + runtimeLock.assertLocked(); - for (int arg = 0; arg < ArgumentModeCount; arg++) { - for (TrampolineBlockPagePair *pagePair = headPagePairs[arg]; - pagePair; - pagePair = pagePair->nextPagePair) - { - uintptr_t index = pagePair->indexForTrampoline(anImp); - if (index) { - if (outIndex) *outIndex = index; - if (outHeadPagePair) *outHeadPagePair = headPagePairs[arg]; - return pagePair; - } + // Authenticate as a function pointer, returning an un-signed address. + uintptr_t trampAddress = + (uintptr_t)ptrauth_auth_data((const char *)anImp, + ptrauth_key_function_pointer, 0); + + for (TrampolineBlockPageGroup *pageGroup = HeadPageGroup; + pageGroup; + pageGroup = pageGroup->nextPageGroup) + { + uintptr_t index = pageGroup->indexForTrampoline(trampAddress); + if (index) { + if (outIndex) *outIndex = index; + return pageGroup; } } @@ -317,7 +421,7 @@ static inline void _assert_locked() { static ArgumentMode -_argumentModeForBlock(id block) +argumentModeForBlock(id block) { ArgumentMode aMode = ReturnValueInRegisterArgumentMode; @@ -336,18 +440,14 @@ static inline void _assert_locked() { IMP _imp_implementationWithBlockNoCopy(id block) { - _assert_locked(); + runtimeLock.assertLocked(); - ArgumentMode aMode = _argumentModeForBlock(block); + TrampolineBlockPageGroup *pageGroup = + getOrAllocatePageGroupWithNextAvailable(); - TrampolineBlockPagePair *pagePair = - _getOrAllocatePagePairWithNextAvailable(aMode); - if (!headPagePairs[aMode]) - headPagePairs[aMode] = pagePair; - - uintptr_t index = pagePair->nextAvailable; - assert(index >= pagePair->startIndex() && index < pagePair->endIndex()); - TrampolineBlockPagePair::Payload *payload = pagePair->payload(index); + uintptr_t index = pageGroup->nextAvailable; + assert(index >= pageGroup->startIndex() && index < pageGroup->endIndex()); + TrampolineBlockPageGroup::Payload *payload = pageGroup->payload(index); uintptr_t nextAvailableIndex = payload->nextAvailable; if (nextAvailableIndex == 0) { @@ -355,104 +455,109 @@ static inline void _assert_locked() { // If the page is now full this will now be endIndex(), handled below. nextAvailableIndex = index + 1; } - pagePair->nextAvailable = nextAvailableIndex; - if (nextAvailableIndex == pagePair->endIndex()) { - // PagePair is now full (free list or wilderness exhausted) + pageGroup->nextAvailable = nextAvailableIndex; + if (nextAvailableIndex == pageGroup->endIndex()) { + // PageGroup is now full (free list or wilderness exhausted) // Remove from available page linked list - TrampolineBlockPagePair *iterator = headPagePairs[aMode]; - while(iterator && (iterator->nextAvailablePage != pagePair)) { + TrampolineBlockPageGroup *iterator = HeadPageGroup; + while(iterator && (iterator->nextAvailablePage != pageGroup)) { iterator = iterator->nextAvailablePage; } if (iterator) { - iterator->nextAvailablePage = pagePair->nextAvailablePage; - pagePair->nextAvailablePage = nil; + iterator->nextAvailablePage = pageGroup->nextAvailablePage; + pageGroup->nextAvailablePage = nil; } } payload->block = block; - return pagePair->trampoline(index); + return pageGroup->trampoline(argumentModeForBlock(block), index); } #pragma mark Public API IMP imp_implementationWithBlock(id block) { + // Block object must be copied outside runtimeLock + // because it performs arbitrary work. block = Block_copy(block); - _lock(); - IMP returnIMP = _imp_implementationWithBlockNoCopy(block); - _unlock(); - return returnIMP; + + // Trampolines must be initialized outside runtimeLock + // because it calls dlopen(). + Trampolines.Initialize(); + + mutex_locker_t lock(runtimeLock); + + return _imp_implementationWithBlockNoCopy(block); } id imp_getBlock(IMP anImp) { uintptr_t index; - TrampolineBlockPagePair *pagePair; + TrampolineBlockPageGroup *pageGroup; if (!anImp) return nil; - _lock(); + mutex_locker_t lock(runtimeLock); - pagePair = _pageAndIndexContainingIMP(anImp, &index, nil); + pageGroup = pageAndIndexContainingIMP(anImp, &index); - if (!pagePair) { - _unlock(); + if (!pageGroup) { return nil; } - TrampolineBlockPagePair::Payload *payload = pagePair->payload(index); + TrampolineBlockPageGroup::Payload *payload = pageGroup->payload(index); - if (payload->nextAvailable <= TrampolineBlockPagePair::endIndex()) { + if (payload->nextAvailable <= TrampolineBlockPageGroup::endIndex()) { // unallocated - _unlock(); return nil; } - _unlock(); - return payload->block; } BOOL imp_removeBlock(IMP anImp) { - TrampolineBlockPagePair *pagePair; - TrampolineBlockPagePair *headPagePair; - uintptr_t index; if (!anImp) return NO; - - _lock(); - pagePair = _pageAndIndexContainingIMP(anImp, &index, &headPagePair); - - if (!pagePair) { - _unlock(); - return NO; - } - TrampolineBlockPagePair::Payload *payload = pagePair->payload(index); - id block = payload->block; - // block is released below + id block; - payload->nextAvailable = pagePair->nextAvailable; - pagePair->nextAvailable = index; - - // make sure this page is on available linked list - TrampolineBlockPagePair *pagePairIterator = headPagePair; - - // see if page is the next available page for any existing pages - while (pagePairIterator->nextAvailablePage && - pagePairIterator->nextAvailablePage != pagePair) { - pagePairIterator = pagePairIterator->nextAvailablePage; - } + mutex_locker_t lock(runtimeLock); - if (! pagePairIterator->nextAvailablePage) { - // if iteration stopped because nextAvail was nil - // add to end of list. - pagePairIterator->nextAvailablePage = pagePair; - pagePair->nextAvailablePage = nil; + uintptr_t index; + TrampolineBlockPageGroup *pageGroup = + pageAndIndexContainingIMP(anImp, &index); + + if (!pageGroup) { + return NO; + } + + TrampolineBlockPageGroup::Payload *payload = pageGroup->payload(index); + block = payload->block; + // block is released below, outside the lock + + payload->nextAvailable = pageGroup->nextAvailable; + pageGroup->nextAvailable = index; + + // make sure this page is on available linked list + TrampolineBlockPageGroup *pageGroupIterator = HeadPageGroup; + + // see if page is the next available page for any existing pages + while (pageGroupIterator->nextAvailablePage && + pageGroupIterator->nextAvailablePage != pageGroup) + { + pageGroupIterator = pageGroupIterator->nextAvailablePage; + } + + if (! pageGroupIterator->nextAvailablePage) { + // if iteration stopped because nextAvail was nil + // add to end of list. + pageGroupIterator->nextAvailablePage = pageGroup; + pageGroup->nextAvailablePage = nil; + } } - - _unlock(); + + // do this AFTER dropping the lock Block_release(block); return YES; } diff --git a/runtime/objc-blocktramps-arm.s b/runtime/objc-blocktramps-arm.s new file mode 100644 index 0000000..bbbe1cf --- /dev/null +++ b/runtime/objc-blocktramps-arm.s @@ -0,0 +1,286 @@ +#if __arm__ + +#include +#include + +.syntax unified + +.text +.globl __objc_blockTrampolineImpl +.globl __objc_blockTrampolineStart +.globl __objc_blockTrampolineLast + +// Trampoline machinery assumes the trampolines are Thumb function pointers +#if !__thumb2__ +# error sorry +#endif + +.thumb + +// Exported symbols are not marked as functions. +// The trampoline construction code assumes that the Thumb bit is not set. +.thumb_func L__objc_blockTrampolineImpl_func + +.align PAGE_MAX_SHIFT +__objc_blockTrampolineImpl: +L__objc_blockTrampolineImpl_func: + /* + r0 == self + r12 == pc of trampoline's first instruction + PC bias + lr == original return address + */ + + mov r1, r0 // _cmd = self + + // Trampoline's data is one page before the trampoline text. + // Also correct PC bias of 4 bytes. + sub r12, # 2*PAGE_MAX_SIZE + ldr r0, [r12, #-4] // self = block object + ldr pc, [r0, #12] // tail call block->invoke + // not reached + + // Align trampolines to 8 bytes +.align 3 + +.macro TrampolineEntry + mov r12, pc + b L__objc_blockTrampolineImpl_func +.align 3 +.endmacro + +.macro TrampolineEntryX16 + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry +.endmacro + +.macro TrampolineEntryX256 + TrampolineEntryX16 + TrampolineEntryX16 + TrampolineEntryX16 + TrampolineEntryX16 + + TrampolineEntryX16 + TrampolineEntryX16 + TrampolineEntryX16 + TrampolineEntryX16 + + TrampolineEntryX16 + TrampolineEntryX16 + TrampolineEntryX16 + TrampolineEntryX16 + + TrampolineEntryX16 + TrampolineEntryX16 + TrampolineEntryX16 + TrampolineEntryX16 +.endmacro + +__objc_blockTrampolineStart: + // 2048-2 trampolines to fill 16K page + TrampolineEntryX256 + TrampolineEntryX256 + TrampolineEntryX256 + TrampolineEntryX256 + + TrampolineEntryX256 + TrampolineEntryX256 + TrampolineEntryX256 + + TrampolineEntryX16 + TrampolineEntryX16 + TrampolineEntryX16 + TrampolineEntryX16 + + TrampolineEntryX16 + TrampolineEntryX16 + TrampolineEntryX16 + TrampolineEntryX16 + + TrampolineEntryX16 + TrampolineEntryX16 + TrampolineEntryX16 + TrampolineEntryX16 + + TrampolineEntryX16 + TrampolineEntryX16 + TrampolineEntryX16 + + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + + TrampolineEntry +__objc_blockTrampolineLast: + TrampolineEntry + // TrampolineEntry + // TrampolineEntry + + + +.text +.globl __objc_blockTrampolineImpl_stret +.globl __objc_blockTrampolineStart_stret +.globl __objc_blockTrampolineLast_stret + +// Trampoline machinery assumes the trampolines are Thumb function pointers +#if !__thumb2__ +# error sorry +#endif + +.thumb + +// Exported symbols are not marked as functions. +// The trampoline construction code assumes that the Thumb bit is not set. +.thumb_func L__objc_blockTrampolineImpl_stret_func + +.align PAGE_MAX_SHIFT +__objc_blockTrampolineImpl_stret: +L__objc_blockTrampolineImpl_stret_func: + /* + r1 == self + r12 == pc of trampoline's first instruction + PC bias + lr == original return address + */ + + mov r2, r1 // _cmd = self + + // Trampoline's data is one page before the trampoline text. + // Also correct PC bias of 4 bytes. + sub r12, # 3*PAGE_MAX_SIZE + ldr r1, [r12, #-4] // self = block object + ldr pc, [r1, #12] // tail call block->invoke + // not reached + + // Align trampolines to 8 bytes +.align 3 + +.macro TrampolineEntry_stret + mov r12, pc + b L__objc_blockTrampolineImpl_stret_func +.align 3 +.endmacro + +.macro TrampolineEntryX16_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret +.endmacro + +.macro TrampolineEntryX256_stret + TrampolineEntryX16_stret + TrampolineEntryX16_stret + TrampolineEntryX16_stret + TrampolineEntryX16_stret + + TrampolineEntryX16_stret + TrampolineEntryX16_stret + TrampolineEntryX16_stret + TrampolineEntryX16_stret + + TrampolineEntryX16_stret + TrampolineEntryX16_stret + TrampolineEntryX16_stret + TrampolineEntryX16_stret + + TrampolineEntryX16_stret + TrampolineEntryX16_stret + TrampolineEntryX16_stret + TrampolineEntryX16_stret +.endmacro + +__objc_blockTrampolineStart_stret: + // 2048-2 trampolines to fill 16K page + TrampolineEntryX256_stret + TrampolineEntryX256_stret + TrampolineEntryX256_stret + TrampolineEntryX256_stret + + TrampolineEntryX256_stret + TrampolineEntryX256_stret + TrampolineEntryX256_stret + + TrampolineEntryX16_stret + TrampolineEntryX16_stret + TrampolineEntryX16_stret + TrampolineEntryX16_stret + + TrampolineEntryX16_stret + TrampolineEntryX16_stret + TrampolineEntryX16_stret + TrampolineEntryX16_stret + + TrampolineEntryX16_stret + TrampolineEntryX16_stret + TrampolineEntryX16_stret + TrampolineEntryX16_stret + + TrampolineEntryX16_stret + TrampolineEntryX16_stret + TrampolineEntryX16_stret + + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + + TrampolineEntry_stret +__objc_blockTrampolineLast_stret: + TrampolineEntry_stret + // TrampolineEntry_stret + // TrampolineEntry_stret + +#endif diff --git a/runtime/a1a2-blocktramps-arm64.s b/runtime/objc-blocktramps-arm64.s similarity index 69% rename from runtime/a1a2-blocktramps-arm64.s rename to runtime/objc-blocktramps-arm64.s index 139df99..a79a031 100644 --- a/runtime/a1a2-blocktramps-arm64.s +++ b/runtime/objc-blocktramps-arm64.s @@ -1,35 +1,44 @@ #if __arm64__ #include +#include "arm64-asm.h" + +// Offset of block->invoke field. +#if __LP64__ + // true arm64 +# define BLOCK_INVOKE 16 +#else + // arm64_32 +# define BLOCK_INVOKE 12 +#endif .text - - .private_extern __a1a2_tramphead - .private_extern __a1a2_firsttramp - .private_extern __a1a2_trampend +.globl __objc_blockTrampolineImpl +.globl __objc_blockTrampolineStart +.globl __objc_blockTrampolineLast .align PAGE_MAX_SHIFT -__a1a2_tramphead: -L_a1a2_tramphead: +__objc_blockTrampolineImpl: +L_objc_blockTrampolineImpl: /* x0 == self - x17 == address of called trampoline's data (1 page before its code) + x17 == address of called trampoline's data (2 pages before its code) lr == original return address */ mov x1, x0 // _cmd = self - ldr x0, [x17] // self = block object - ldr x16, [x0, #16] // tail call block->invoke - br x16 + ldr p0, [x17] // self = block object + add p15, p0, #BLOCK_INVOKE // x15 = &block->invoke + ldr p16, [x15] // x16 = block->invoke + TailCallBlockInvoke x16, x15 // pad up to TrampolineBlockPagePair header size nop - nop .macro TrampolineEntry - // load address of trampoline data (one page before this instruction) - adr x17, -PAGE_MAX_SIZE - b L_a1a2_tramphead + // load address of trampoline data (two pages before this instruction) + adr x17, -2*PAGE_MAX_SIZE + b L_objc_blockTrampolineImpl .endmacro .macro TrampolineEntryX16 @@ -77,8 +86,7 @@ L_a1a2_tramphead: .endmacro .align 3 -.private_extern __a1a2_firsttramp -__a1a2_firsttramp: +__objc_blockTrampolineStart: // 2048-3 trampolines to fill 16K page TrampolineEntryX256 TrampolineEntryX256 @@ -123,12 +131,10 @@ __a1a2_firsttramp: TrampolineEntry TrampolineEntry +__objc_blockTrampolineLast: TrampolineEntry // TrampolineEntry // TrampolineEntry // TrampolineEntry - -.private_extern __a1a2_trampend -__a1a2_trampend: #endif diff --git a/runtime/objc-blocktramps-i386.s b/runtime/objc-blocktramps-i386.s new file mode 100755 index 0000000..f2a1ace --- /dev/null +++ b/runtime/objc-blocktramps-i386.s @@ -0,0 +1,1099 @@ +/* + * Copyright (c) 1999-2007 Apple Inc. All Rights Reserved. + * + * @APPLE_LICENSE_HEADER_START@ + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPLE_LICENSE_HEADER_END@ + */ + +#ifdef __i386__ + +#include + +.text +.globl __objc_blockTrampolineImpl +.globl __objc_blockTrampolineStart +.globl __objc_blockTrampolineLast + +.align PAGE_SHIFT +__objc_blockTrampolineImpl: + popl %eax + andl $0xFFFFFFF8, %eax + subl $ 2*PAGE_SIZE, %eax + movl 4(%esp), %ecx // self -> ecx + movl %ecx, 8(%esp) // ecx -> _cmd + movl (%eax), %ecx // blockPtr -> ecx + movl %ecx, 4(%esp) // ecx -> self + jmp *12(%ecx) // tail to block->invoke + +.macro TrampolineEntry + call __objc_blockTrampolineImpl + nop + nop + nop +.endmacro + +.align 5 +__objc_blockTrampolineStart: + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry +__objc_blockTrampolineLast: + TrampolineEntry + + +.text +.globl __objc_blockTrampolineImpl_stret +.globl __objc_blockTrampolineStart_stret +.globl __objc_blockTrampolineLast_stret + +.align PAGE_SHIFT +__objc_blockTrampolineImpl_stret: + popl %eax + andl $0xFFFFFFF8, %eax + subl $ 3*PAGE_SIZE, %eax + movl 8(%esp), %ecx // self -> ecx + movl %ecx, 12(%esp) // ecx -> _cmd + movl (%eax), %ecx // blockPtr -> ecx + movl %ecx, 8(%esp) // ecx -> self + jmp *12(%ecx) // tail to block->invoke + +.macro TrampolineEntry_stret + call __objc_blockTrampolineImpl_stret + nop + nop + nop +.endmacro + +.align 5 +__objc_blockTrampolineStart_stret: + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret +__objc_blockTrampolineLast_stret: + TrampolineEntry_stret + +#endif diff --git a/runtime/objc-blocktramps-x86_64.s b/runtime/objc-blocktramps-x86_64.s new file mode 100755 index 0000000..4423859 --- /dev/null +++ b/runtime/objc-blocktramps-x86_64.s @@ -0,0 +1,1096 @@ +/* + * Copyright (c) 1999-2007 Apple Inc. All Rights Reserved. + * + * @APPLE_LICENSE_HEADER_START@ + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPLE_LICENSE_HEADER_END@ + */ + +#ifdef __x86_64__ + +#include + +.text +.globl __objc_blockTrampolineImpl +.globl __objc_blockTrampolineStart +.globl __objc_blockTrampolineLast + +.align PAGE_SHIFT +__objc_blockTrampolineImpl: + popq %r10 + andq $0xFFFFFFFFFFFFFFF8, %r10 + subq $ 2*PAGE_SIZE, %r10 + movq %rdi, %rsi // arg1 -> arg2 + movq (%r10), %rdi // block -> arg1 + jmp *16(%rdi) + +.macro TrampolineEntry + callq __objc_blockTrampolineImpl + nop + nop + nop +.endmacro + +.align 5 +__objc_blockTrampolineStart: + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry +__objc_blockTrampolineLast: + TrampolineEntry + + +.text +.globl __objc_blockTrampolineImpl_stret +.globl __objc_blockTrampolineStart_stret +.globl __objc_blockTrampolineLast_stret + +.align PAGE_SHIFT +__objc_blockTrampolineImpl_stret: + popq %r10 + andq $0xFFFFFFFFFFFFFFF8, %r10 + subq $ 3*PAGE_SIZE, %r10 + // %rdi -- first arg -- is address of return value's space. Don't mess with it. + movq %rsi, %rdx // arg2 -> arg3 + movq (%r10), %rsi // block -> arg2 + jmp *16(%rsi) + +.macro TrampolineEntry_stret + callq __objc_blockTrampolineImpl_stret + nop + nop + nop +.endmacro + +.align 5 +__objc_blockTrampolineStart_stret: + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret +__objc_blockTrampolineLast_stret: + TrampolineEntry_stret + +#endif diff --git a/runtime/objc-cache-old.mm b/runtime/objc-cache-old.mm index 12baf36..b785a6a 100644 --- a/runtime/objc-cache-old.mm +++ b/runtime/objc-cache-old.mm @@ -693,8 +693,8 @@ static uintptr_t _get_pc_for_thread(thread_t thread) * reading function is in progress because it might still be using * the garbage memory. **********************************************************************/ -OBJC_EXPORT uintptr_t objc_entryPoints[]; -OBJC_EXPORT uintptr_t objc_exitPoints[]; +extern "C" uintptr_t objc_entryPoints[]; +extern "C" uintptr_t objc_exitPoints[]; static int _collecting_in_critical(void) { diff --git a/runtime/objc-cache.mm b/runtime/objc-cache.mm index a0c6545..73b172c 100644 --- a/runtime/objc-cache.mm +++ b/runtime/objc-cache.mm @@ -201,27 +201,42 @@ __asm__ __volatile__( \ #if __arm64__ +// Pointer-size register prefix for inline asm +# if __LP64__ +# define p "x" // true arm64 +# else +# define p "w" // arm64_32 +# endif + // Use atomic double-word instructions to update cache entries. // This requires cache buckets not cross cache line boundaries. -#define stp(onep, twop, destp) \ - __asm__ ("stp %[one], %[two], [%[dest]]" \ - : "=m" (((uint64_t *)(destp))[0]), \ - "=m" (((uint64_t *)(destp))[1]) \ - : [one] "r" (onep), \ - [two] "r" (twop), \ - [dest] "r" (destp) \ - : /* no clobbers */ \ - ) -#define ldp(onep, twop, srcp) \ - __asm__ ("ldp %[one], %[two], [%[src]]" \ - : [one] "=r" (onep), \ - [two] "=r" (twop) \ - : "m" (((uint64_t *)(srcp))[0]), \ - "m" (((uint64_t *)(srcp))[1]), \ - [src] "r" (srcp) \ - : /* no clobbers */ \ - ) +static ALWAYS_INLINE void +stp(uintptr_t onep, uintptr_t twop, void *destp) +{ + __asm__ ("stp %" p "[one], %" p "[two], [%x[dest]]" + : "=m" (((uintptr_t *)(destp))[0]), + "=m" (((uintptr_t *)(destp))[1]) + : [one] "r" (onep), + [two] "r" (twop), + [dest] "r" (destp) + : /* no clobbers */ + ); +} + +static ALWAYS_INLINE void __unused +ldp(uintptr_t& onep, uintptr_t& twop, const void *srcp) +{ + __asm__ ("ldp %" p "[one], %" p "[two], [%x[src]]" + : [one] "=r" (onep), + [two] "=r" (twop) + : "m" (((const uintptr_t *)(srcp))[0]), + "m" (((const uintptr_t *)(srcp))[1]), + [src] "r" (srcp) + : /* no clobbers */ + ); +} +#undef p #endif @@ -251,9 +266,21 @@ cache_key_t getKey(SEL sel) { assert(_key == 0 || _key == newKey); - // LDP/STP guarantees that all observers get - // either key/imp or newKey/newImp - stp(newKey, newImp, this); + static_assert(offsetof(bucket_t,_imp) == 0 && offsetof(bucket_t,_key) == sizeof(void *), + "bucket_t doesn't match arm64 bucket_t::set()"); + +#if __has_feature(ptrauth_calls) + // Authenticate as a C function pointer and re-sign for the cache bucket. + uintptr_t signedImp = _imp.prepareWrite(newImp); +#else + // No function pointer signing. + uintptr_t signedImp = (uintptr_t)newImp; +#endif + + // Write to the bucket. + // LDP/STP guarantees that all observers get + // either imp/key or newImp/newKey + stp(signedImp, newKey, this); } #else @@ -648,7 +675,7 @@ static uintptr_t _get_pc_for_thread(thread_t thread) arm_thread_state64_t state; unsigned int count = ARM_THREAD_STATE64_COUNT; kern_return_t okay = thread_get_state (thread, ARM_THREAD_STATE64, (thread_state_t)&state, &count); - return (okay == KERN_SUCCESS) ? state.__pc : PC_SENTINEL; + return (okay == KERN_SUCCESS) ? arm_thread_state64_get_pc(state) : PC_SENTINEL; } #else { @@ -665,8 +692,8 @@ static uintptr_t _get_pc_for_thread(thread_t thread) * reading function is in progress because it might still be using * the garbage memory. **********************************************************************/ -OBJC_EXPORT uintptr_t objc_entryPoints[]; -OBJC_EXPORT uintptr_t objc_exitPoints[]; +extern "C" uintptr_t objc_entryPoints[]; +extern "C" uintptr_t objc_exitPoints[]; static int _collecting_in_critical(void) { diff --git a/runtime/objc-class-old.mm b/runtime/objc-class-old.mm index 2fe27d8..dd13e22 100644 --- a/runtime/objc-class-old.mm +++ b/runtime/objc-class-old.mm @@ -163,13 +163,12 @@ void disableSharedCacheOptimizations(void) // Mach-O bundles are fixed up in place. // This prevents leaks when a bundle is unloaded. } - sel_lock(); + mutex_locker_t lock(selLock); for ( i = 0; i < mlist->method_count; i += 1 ) { method = &mlist->method_list[i]; method->method_name = sel_registerNameNoLock((const char *)method->method_name, isBundle); // Always copy selector data from bundles. } - sel_unlock(); mlist->obsolete = fixed_up_method_list; } return mlist; @@ -683,33 +682,6 @@ void class_setIvarLayout(Class cls, const uint8_t *layout) cls->ivar_layout = ustrdupMaybeNil(layout); } -// SPI: Instance-specific object layout. - -void _class_setIvarLayoutAccessor(Class cls, const uint8_t* (*accessor) (id object)) { - if (!cls) return; - - if (! (cls->info & CLS_EXT)) { - _objc_inform("class '%s' needs to be recompiled", cls->name); - return; - } - - // fixme leak - cls->ivar_layout = (const uint8_t *)accessor; - cls->setInfo(CLS_HAS_INSTANCE_SPECIFIC_LAYOUT); -} - -const uint8_t *_object_getIvarLayout(Class cls, id object) { - if (cls && (cls->info & CLS_EXT)) { - const uint8_t* layout = cls->ivar_layout; - if (cls->info & CLS_HAS_INSTANCE_SPECIFIC_LAYOUT) { - const uint8_t* (*accessor) (id object) = (const uint8_t* (*)(id))layout; - layout = accessor(object); - } - return layout; - } else { - return nil; - } -} /*********************************************************************** * class_setWeakIvarLayout @@ -837,7 +809,7 @@ IMP _category_getLoadMethod(Category cat) * If methods are removed between calls to class_nextMethodList(), it may * omit surviving method lists or simply crash. **********************************************************************/ -OBJC_EXPORT struct objc_method_list *class_nextMethodList(Class cls, void **it) +struct objc_method_list *class_nextMethodList(Class cls, void **it) { OBJC_WARN_DEPRECATED; @@ -851,7 +823,7 @@ IMP _category_getLoadMethod(Category cat) * * Formerly class_addInstanceMethods () **********************************************************************/ -OBJC_EXPORT void class_addMethods(Class cls, struct objc_method_list *meths) +void class_addMethods(Class cls, struct objc_method_list *meths) { OBJC_WARN_DEPRECATED; @@ -871,7 +843,7 @@ OBJC_EXPORT void class_addMethods(Class cls, struct objc_method_list *meths) /*********************************************************************** * class_removeMethods. **********************************************************************/ -OBJC_EXPORT void class_removeMethods(Class cls, struct objc_method_list *meths) +void class_removeMethods(Class cls, struct objc_method_list *meths) { OBJC_WARN_DEPRECATED; @@ -1494,6 +1466,9 @@ unsigned int method_getSizeOfArguments(Method m) return encoding_getSizeOfArguments(method_getTypeEncoding(m)); } +// This function was accidentally un-exported beginning in macOS 10.9. +// As of macOS 10.13 nobody had complained. +/* unsigned int method_getArgumentInfo(Method m, int arg, const char **type, int *offset) { @@ -1502,6 +1477,7 @@ unsigned int method_getArgumentInfo(Method m, int arg, return encoding_getArgumentInfo(method_getTypeEncoding(m), arg, type, offset); } +*/ spinlock_t impLock; diff --git a/runtime/objc-class.mm b/runtime/objc-class.mm index 9e96b6d..bb9ceae 100644 --- a/runtime/objc-class.mm +++ b/runtime/objc-class.mm @@ -160,18 +160,6 @@ #include "objc-abi.h" #include - -/* overriding the default object allocation and error handling routines */ - -OBJC_EXPORT id (*_alloc)(Class, size_t); -OBJC_EXPORT id (*_copy)(id, size_t); -OBJC_EXPORT id (*_realloc)(id, size_t); -OBJC_EXPORT id (*_dealloc)(id); -OBJC_EXPORT id (*_zoneAlloc)(Class, size_t, void *); -OBJC_EXPORT id (*_zoneRealloc)(id, size_t, void *); -OBJC_EXPORT id (*_zoneCopy)(id, size_t, void *); - - /*********************************************************************** * Information about multi-thread support: * diff --git a/runtime/objc-config.h b/runtime/objc-config.h index 979b467..72799c7 100644 --- a/runtime/objc-config.h +++ b/runtime/objc-config.h @@ -26,6 +26,15 @@ #include +// Define __OBJC2__ for the benefit of our asm files. +#ifndef __OBJC2__ +# if TARGET_OS_OSX && !TARGET_OS_IOSMAC && __i386__ + // old ABI +# else +# define __OBJC2__ 1 +# endif +#endif + // Avoid the !NDEBUG double negative. #if !NDEBUG # define DEBUG 1 @@ -42,7 +51,7 @@ #endif // Define SUPPORT_ZONES=1 to enable malloc zone support in NXHashTable. -#if !TARGET_OS_OSX +#if !(TARGET_OS_OSX || TARGET_OS_IOSMAC) # define SUPPORT_ZONES 0 #else # define SUPPORT_ZONES 1 @@ -73,7 +82,7 @@ // Define SUPPORT_MSB_TAGGED_POINTERS to use the MSB // as the tagged pointer marker instead of the LSB. // Be sure to edit tagged pointer SPI in objc-internal.h as well. -#if !SUPPORT_TAGGED_POINTERS || !TARGET_OS_IPHONE +#if !SUPPORT_TAGGED_POINTERS || (TARGET_OS_OSX || TARGET_OS_IOSMAC) # define SUPPORT_MSB_TAGGED_POINTERS 0 #else # define SUPPORT_MSB_TAGGED_POINTERS 1 @@ -83,7 +92,7 @@ // field as an index into a class table. // Note, keep this in sync with any .s files which also define it. // Be sure to edit objc-abi.h as well. -#if __ARM_ARCH_7K__ >= 2 +#if __ARM_ARCH_7K__ >= 2 || (__arm64__ && !__LP64__) # define SUPPORT_INDEXED_ISA 1 #else # define SUPPORT_INDEXED_ISA 0 @@ -91,7 +100,8 @@ // Define SUPPORT_PACKED_ISA=1 on platforms that store the class in the isa // field as a maskable pointer with other data around it. -#if (!__LP64__ || TARGET_OS_WIN32 || TARGET_OS_SIMULATOR) +#if (!__LP64__ || TARGET_OS_WIN32 || \ + (TARGET_OS_SIMULATOR && !TARGET_OS_IOSMAC)) # define SUPPORT_PACKED_ISA 0 #else # define SUPPORT_PACKED_ISA 1 @@ -125,7 +135,7 @@ // Define SUPPORT_ALT_HANDLERS if you're using zero-cost exceptions // but also need to support AppKit's alt-handler scheme // Be sure to edit objc-exception.h as well (objc_add/removeExceptionHandler) -#if !SUPPORT_ZEROCOST_EXCEPTIONS || TARGET_OS_IPHONE || TARGET_OS_EMBEDDED +#if !SUPPORT_ZEROCOST_EXCEPTIONS || !TARGET_OS_OSX # define SUPPORT_ALT_HANDLERS 0 #else # define SUPPORT_ALT_HANDLERS 1 @@ -146,19 +156,12 @@ #endif // Define SUPPORT_MESSAGE_LOGGING to enable NSObjCMessageLoggingEnabled -#if TARGET_OS_WIN32 || TARGET_OS_EMBEDDED +#if !TARGET_OS_OSX # define SUPPORT_MESSAGE_LOGGING 0 #else # define SUPPORT_MESSAGE_LOGGING 1 #endif -// Define SUPPORT_QOS_HACK to work around deadlocks due to QoS bugs. -#if !__OBJC2__ || TARGET_OS_WIN32 -# define SUPPORT_QOS_HACK 0 -#else -# define SUPPORT_QOS_HACK 1 -#endif - // OBJC_INSTRUMENTED controls whether message dispatching is dynamically // monitored. Monitoring introduces substantial overhead. // NOTE: To define this condition, do so in the build command, NOT by diff --git a/runtime/objc-env.h b/runtime/objc-env.h index 5ff3382..07090d6 100644 --- a/runtime/objc-env.h +++ b/runtime/objc-env.h @@ -39,5 +39,6 @@ OPTION( DebugDontCrash, OBJC_DEBUG_DONT_CRASH, "halt the pro OPTION( DisableVtables, OBJC_DISABLE_VTABLES, "disable vtable dispatch") OPTION( DisablePreopt, OBJC_DISABLE_PREOPTIMIZATION, "disable preoptimization courtesy of dyld shared cache") OPTION( DisableTaggedPointers, OBJC_DISABLE_TAGGED_POINTERS, "disable tagged pointer optimization of NSNumber et al.") +OPTION( DisableTaggedPointerObfuscation, OBJC_DISABLE_TAG_OBFUSCATION, "disable obfuscation of tagged pointers") OPTION( DisableNonpointerIsa, OBJC_DISABLE_NONPOINTER_ISA, "disable non-pointer isa fields") OPTION( DisableInitializeForkSafety, OBJC_DISABLE_INITIALIZE_FORK_SAFETY, "disable safety checks for +initialize after fork") diff --git a/runtime/objc-errors.mm b/runtime/objc-errors.mm index 6d65ca2..159cd86 100644 --- a/runtime/objc-errors.mm +++ b/runtime/objc-errors.mm @@ -76,8 +76,6 @@ void _objc_error(id rcv, const char *fmt, va_list args) #include <_simple.h> -OBJC_EXPORT void (*_error)(id, const char *, va_list); - // Return true if c is a UTF8 continuation byte static bool isUTF8Continuation(char c) { diff --git a/runtime/objc-exception.mm b/runtime/objc-exception.mm index d510d23..c93b8e7 100644 --- a/runtime/objc-exception.mm +++ b/runtime/objc-exception.mm @@ -238,6 +238,7 @@ void _destroyAltHandlerList(struct alt_handler_list *list) **********************************************************************/ #include "objc-private.h" +#include #include #include #include @@ -310,7 +311,7 @@ void _destroyAltHandlerList(struct alt_handler_list *list) struct objc_typeinfo { // Position of vtable and name fields must match C++ typeinfo object - const void **vtable; // always objc_ehtype_vtable+2 + const void ** __ptrauth_cxx_vtable_pointer vtable; // objc_ehtype_vtable+2 const char *name; // c++ typeinfo string Class cls_unremapped; @@ -322,53 +323,88 @@ void _destroyAltHandlerList(struct alt_handler_list *list) }; -static void _objc_exception_noop(void) { } -static bool _objc_exception_false(void) { return 0; } -// static bool _objc_exception_true(void) { return 1; } -static void _objc_exception_abort1(void) { +extern "C" void _objc_exception_noop(void) { } +extern "C" bool _objc_exception_false(void) { return 0; } +// extern "C" bool _objc_exception_true(void) { return 1; } +extern "C" void _objc_exception_abort1(void) { _objc_fatal("unexpected call into objc exception typeinfo vtable %d", 1); } -static void _objc_exception_abort2(void) { +extern "C" void _objc_exception_abort2(void) { _objc_fatal("unexpected call into objc exception typeinfo vtable %d", 2); } -static void _objc_exception_abort3(void) { +extern "C" void _objc_exception_abort3(void) { _objc_fatal("unexpected call into objc exception typeinfo vtable %d", 3); } -static void _objc_exception_abort4(void) { +extern "C" void _objc_exception_abort4(void) { _objc_fatal("unexpected call into objc exception typeinfo vtable %d", 4); } -static bool _objc_exception_do_catch(struct objc_typeinfo *catch_tinfo, - struct objc_typeinfo *throw_tinfo, - void **throw_obj_p, - unsigned outer); +extern "C" bool _objc_exception_do_catch(struct objc_typeinfo *catch_tinfo, + struct objc_typeinfo *throw_tinfo, + void **throw_obj_p, + unsigned outer); + +// C++ pointers to vtables are signed with no extra data. +// C++ vtable entries are signed with a number derived from the function name. +// For this fake vtable, we hardcode number as deciphered from the +// assembly output during libc++abi's build. +#if __has_feature(ptrauth_calls) +# define VTABLE_PTR_AUTH "@AUTH(da, 0)" +# define VTABLE_ENTRY_AUTH(x) "@AUTH(ia," #x ",addr)" +#else +# define VTABLE_PTR_AUTH "" +# define VTABLE_ENTRY_AUTH(x) "" +#endif -// forward declaration -OBJC_EXPORT struct objc_typeinfo OBJC_EHTYPE_id; - -OBJC_EXPORT -const void *objc_ehtype_vtable[] = { - nil, // typeinfo's vtable? - fixme - (void*)&OBJC_EHTYPE_id, // typeinfo's typeinfo - hack - (void*)_objc_exception_noop, // in-place destructor? - (void*)_objc_exception_noop, // destructor? - (void*)_objc_exception_false, // OLD __is_pointer_p - (void*)_objc_exception_false, // OLD __is_function_p - (void*)_objc_exception_do_catch, // OLD __do_catch, NEW can_catch - (void*)_objc_exception_false, // OLD __do_upcast, NEW search_above_dst - (void*)_objc_exception_false, // NEW search_below_dst - (void*)_objc_exception_abort1, // paranoia: blow up if libc++abi - (void*)_objc_exception_abort2, // adds something new - (void*)_objc_exception_abort3, - (void*)_objc_exception_abort4, -}; +#if __LP64__ +# define PTR ".quad " +# define TWOPTRSIZE "16" +#else +# define PTR ".long " +# define TWOPTRSIZE "8" +#endif -OBJC_EXPORT -struct objc_typeinfo OBJC_EHTYPE_id = { - objc_ehtype_vtable+2, - "id", - nil -}; +// Hand-built vtable for objc exception typeinfo. +// "OLD" is GNU libcpp, "NEW" is libc++abi. + +asm( + "\n .cstring" + "\n l_.id_str: .asciz \"id\"" + + "\n .section __DATA,__const" + "\n .globl _OBJC_EHTYPE_id" + "\n .globl _objc_ehtype_vtable" + "\n .p2align 4" + + "\n _OBJC_EHTYPE_id:" + "\n " PTR "(_objc_ehtype_vtable+" TWOPTRSIZE ") " VTABLE_PTR_AUTH + "\n " PTR "l_.id_str" + "\n " PTR "0" + + "\n _objc_ehtype_vtable:" + "\n " PTR "0" + // typeinfo's typeinfo - fixme hack + "\n " PTR "_OBJC_EHTYPE_id" + // destructor and in-place destructor + "\n " PTR "__objc_exception_noop" VTABLE_ENTRY_AUTH(52634) + "\n " PTR "__objc_exception_noop" VTABLE_ENTRY_AUTH(10344) + // OLD __is_pointer_p + "\n " PTR "__objc_exception_noop" VTABLE_ENTRY_AUTH(6889) + // OLD __is_function_p + "\n " PTR "__objc_exception_noop" VTABLE_ENTRY_AUTH(23080) + // OLD __do_catch, NEW can_catch + "\n " PTR "__objc_exception_do_catch" VTABLE_ENTRY_AUTH(27434) + // OLD __do_upcast, NEW search_above_dst + "\n " PTR "__objc_exception_false" VTABLE_ENTRY_AUTH(48481) + // NEW search_below_dst + "\n " PTR "__objc_exception_false" VTABLE_ENTRY_AUTH(41165) + // NEW has_unambiguous_public_base (fixme need this?) + "\n " PTR "__objc_exception_abort1" VTABLE_ENTRY_AUTH(14357) + // paranoia: die if libcxxabi adds anything else + "\n " PTR "__objc_exception_abort2" + "\n " PTR "__objc_exception_abort3" + "\n " PTR "__objc_exception_abort4" + ); @@ -583,10 +619,10 @@ void objc_end_catch(void) // `outer` is not passed by the new libcxxabi -static bool _objc_exception_do_catch(struct objc_typeinfo *catch_tinfo, - struct objc_typeinfo *throw_tinfo, - void **throw_obj_p, - unsigned outer UNAVAILABLE_ATTRIBUTE) +bool _objc_exception_do_catch(struct objc_typeinfo *catch_tinfo, + struct objc_typeinfo *throw_tinfo, + void **throw_obj_p, + unsigned outer UNAVAILABLE_ATTRIBUTE) { id exception; diff --git a/runtime/objc-file-old.h b/runtime/objc-file-old.h index 3feb82b..9799c5e 100644 --- a/runtime/objc-file-old.h +++ b/runtime/objc-file-old.h @@ -40,8 +40,8 @@ extern struct old_protocol **_getObjcProtocols(const header_info *hi, size_t *np extern Class *_getObjcClassRefs(const header_info *hi, size_t *nclasses); extern const char *_getObjcClassNames(const header_info *hi, size_t *size); -using Initializer = void(*)(void); -extern Initializer* getLibobjcInitializers(const headerType *mhdr, size_t *count); +using UnsignedInitializer = void(*)(void); +extern UnsignedInitializer* getLibobjcInitializers(const headerType *mhdr, size_t *count); __END_DECLS diff --git a/runtime/objc-file-old.mm b/runtime/objc-file-old.mm index 8e170d4..67889ec 100644 --- a/runtime/objc-file-old.mm +++ b/runtime/objc-file-old.mm @@ -88,7 +88,7 @@ GETSECT(_getObjcClassRefs, Class, "__OBJC", "__cls_refs"); GETSECT(_getObjcClassNames, const char, "__OBJC", "__class_names"); // __OBJC,__class_names section only emitted by CodeWarrior rdar://4951638 -GETSECT(getLibobjcInitializers, Initializer, "__DATA", "__objc_init_func"); +GETSECT(getLibobjcInitializers, UnsignedInitializer, "__DATA", "__objc_init_func"); objc_image_info * diff --git a/runtime/objc-file.h b/runtime/objc-file.h index 5e78b61..47b7108 100644 --- a/runtime/objc-file.h +++ b/runtime/objc-file.h @@ -40,12 +40,56 @@ extern category_t **_getObjc2CategoryList(const header_info *hi, size_t *count); extern category_t **_getObjc2NonlazyCategoryList(const header_info *hi, size_t *count); extern protocol_t **_getObjc2ProtocolList(const header_info *hi, size_t *count); extern protocol_t **_getObjc2ProtocolRefs(const header_info *hi, size_t *count); -using Initializer = void(*)(void); -extern Initializer* getLibobjcInitializers(const header_info *hi, size_t *count); + +// FIXME: rdar://29241917&33734254 clang doesn't sign static initializers. +struct UnsignedInitializer { +private: + uintptr_t storage; +public: + void operator () () const { + using Initializer = void(*)(); + Initializer init = + ptrauth_sign_unauthenticated((Initializer)storage, + ptrauth_key_function_pointer, 0); + init(); + } +}; + +extern UnsignedInitializer *getLibobjcInitializers(const header_info *hi, size_t *count); extern classref_t *_getObjc2NonlazyClassList(const headerType *mhdr, size_t *count); extern category_t **_getObjc2NonlazyCategoryList(const headerType *mhdr, size_t *count); -extern Initializer* getLibobjcInitializers(const headerType *mhdr, size_t *count); +extern UnsignedInitializer *getLibobjcInitializers(const headerType *mhdr, size_t *count); + +static inline void +foreach_data_segment(const headerType *mhdr, + std::function code) +{ + intptr_t slide = 0; + + // compute VM slide + const segmentType *seg = (const segmentType *) (mhdr + 1); + for (unsigned long i = 0; i < mhdr->ncmds; i++) { + if (seg->cmd == SEGMENT_CMD && + segnameEquals(seg->segname, "__TEXT")) + { + slide = (char *)mhdr - (char *)seg->vmaddr; + break; + } + seg = (const segmentType *)((char *)seg + seg->cmdsize); + } + + // enumerate __DATA* segments + seg = (const segmentType *) (mhdr + 1); + for (unsigned long i = 0; i < mhdr->ncmds; i++) { + if (seg->cmd == SEGMENT_CMD && + segnameStartsWith(seg->segname, "__DATA")) + { + code(seg, slide); + } + seg = (const segmentType *)((char *)seg + seg->cmdsize); + } +} #endif diff --git a/runtime/objc-file.mm b/runtime/objc-file.mm index c9ec260..917b2bc 100644 --- a/runtime/objc-file.mm +++ b/runtime/objc-file.mm @@ -65,7 +65,7 @@ GETSECT(_getObjc2NonlazyCategoryList, category_t *, "__objc_nlcatlist"); GETSECT(_getObjc2ProtocolList, protocol_t *, "__objc_protolist"); GETSECT(_getObjc2ProtocolRefs, protocol_t *, "__objc_protorefs"); -GETSECT(getLibobjcInitializers, Initializer, "__objc_init_func"); +GETSECT(getLibobjcInitializers, UnsignedInitializer, "__objc_init_func"); objc_image_info * @@ -75,31 +75,15 @@ outBytes, nil); } - -static const segmentType * -getsegbynamefromheader(const headerType *mhdr, const char *segname) -{ - const segmentType *seg = (const segmentType *) (mhdr + 1); - for (unsigned long i = 0; i < mhdr->ncmds; i++){ - if (seg->cmd == SEGMENT_CMD && segnameEquals(seg->segname, segname)) { - return seg; - } - seg = (const segmentType *)((char *)seg + seg->cmdsize); - } - return nil; -} - // Look for an __objc* section other than __objc_imageinfo static bool segmentHasObjcContents(const segmentType *seg) { - if (seg) { - for (uint32_t i = 0; i < seg->nsects; i++) { - const sectionType *sect = ((const sectionType *)(seg+1))+i; - if (sectnameStartsWith(sect->sectname, "__objc_") && - !sectnameEquals(sect->sectname, "__objc_imageinfo")) - { - return true; - } + for (uint32_t i = 0; i < seg->nsects; i++) { + const sectionType *sect = ((const sectionType *)(seg+1))+i; + if (sectnameStartsWith(sect->sectname, "__objc_") && + !sectnameEquals(sect->sectname, "__objc_imageinfo")) + { + return true; } } @@ -110,16 +94,15 @@ static bool segmentHasObjcContents(const segmentType *seg) bool _hasObjcContents(const header_info *hi) { - const segmentType *data = - getsegbynamefromheader(hi->mhdr(), "__DATA"); - const segmentType *data_const = - getsegbynamefromheader(hi->mhdr(), "__DATA_CONST"); - const segmentType *data_dirty = - getsegbynamefromheader(hi->mhdr(), "__DATA_DIRTY"); + bool foundObjC = false; + + foreach_data_segment(hi->mhdr(), [&](const segmentType *seg, intptr_t slide) + { + if (segmentHasObjcContents(seg)) foundObjC = true; + }); + + return foundObjC; - return segmentHasObjcContents(data) - || segmentHasObjcContents(data_const) - || segmentHasObjcContents(data_dirty); } diff --git a/runtime/objc-gdb.h b/runtime/objc-gdb.h index 9177775..88dc2a8 100644 --- a/runtime/objc-gdb.h +++ b/runtime/objc-gdb.h @@ -34,7 +34,9 @@ #ifdef __APPLE_API_PRIVATE -#define _OBJC_PRIVATE_H_ +#ifndef _OBJC_PRIVATE_H_ +# define _OBJC_PRIVATE_H_ +#endif #include #include #include @@ -148,6 +150,12 @@ OBJC_EXPORT const uintptr_t objc_debug_class_rw_data_mask OBJC_EXPORT uintptr_t objc_debug_taggedpointer_mask OBJC_AVAILABLE(10.9, 7.0, 9.0, 1.0, 2.0); +// tagged pointers are obfuscated by XORing with a random value +// decoded_obj = (obj ^ obfuscator) +OBJC_EXPORT uintptr_t objc_debug_taggedpointer_obfuscator + OBJC_AVAILABLE(10.14, 12.0, 12.0, 5.0, 3.0); + + // tag_slot = (obj >> slot_shift) & slot_mask OBJC_EXPORT unsigned int objc_debug_taggedpointer_slot_shift OBJC_AVAILABLE(10.9, 7.0, 9.0, 1.0, 2.0); @@ -158,7 +166,7 @@ OBJC_EXPORT uintptr_t objc_debug_taggedpointer_slot_mask OBJC_EXPORT Class _Nullable objc_debug_taggedpointer_classes[] OBJC_AVAILABLE(10.9, 7.0, 9.0, 1.0, 2.0); -// payload = (obj << payload_lshift) >> payload_rshift +// payload = (decoded_obj << payload_lshift) >> payload_rshift // Payload signedness is determined by the signedness of the right-shift. OBJC_EXPORT unsigned int objc_debug_taggedpointer_payload_lshift OBJC_AVAILABLE(10.9, 7.0, 9.0, 1.0, 2.0); @@ -172,7 +180,7 @@ OBJC_EXPORT unsigned int objc_debug_taggedpointer_payload_rshift // tagged pointer scheme alone, it will appear to have an isa // that is either nil or class __NSUnrecognizedTaggedPointer. -// if (ext_mask != 0 && (obj & ext_mask) == ext_mask) +// if (ext_mask != 0 && (decoded_obj & ext_mask) == ext_mask) // obj is a ext tagged pointer object OBJC_EXPORT uintptr_t objc_debug_taggedpointer_ext_mask OBJC_AVAILABLE(10.12, 10.0, 10.0, 3.0, 2.0); @@ -187,7 +195,7 @@ OBJC_EXPORT uintptr_t objc_debug_taggedpointer_ext_slot_mask OBJC_EXPORT Class _Nullable objc_debug_taggedpointer_ext_classes[] OBJC_AVAILABLE(10.12, 10.0, 10.0, 3.0, 2.0); -// payload = (obj << ext_payload_lshift) >> ext_payload_rshift +// payload = (decoded_obj << ext_payload_lshift) >> ext_payload_rshift // Payload signedness is determined by the signedness of the right-shift. OBJC_EXPORT unsigned int objc_debug_taggedpointer_ext_payload_lshift OBJC_AVAILABLE(10.12, 10.0, 10.0, 3.0, 2.0); @@ -196,44 +204,10 @@ OBJC_EXPORT unsigned int objc_debug_taggedpointer_ext_payload_rshift #endif - -/*********************************************************************** -* Breakpoints in objc_msgSend for debugger stepping. -* The array is a {0,0} terminated list of addresses. -* Each address is one of the following: -* OBJC_MESSENGER_START: Address is the start of a messenger function. -* OBJC_MESSENGER_END_FAST: Address is a jump insn that calls an IMP. -* OBJC_MESSENGER_END_SLOW: Address is some insn in the slow lookup path. -* OBJC_MESSENGER_END_NIL: Address is a return insn for messages to nil. -* -* Every path from OBJC_MESSENGER_START should reach some OBJC_MESSENGER_END. -* At all ENDs, the stack and parameter register state is the same as START. -* -* In some cases, the END_FAST case jumps to something other than the -* method's implementation. In those cases the jump's destination will -* be another function that is marked OBJC_MESSENGER_START. -**********************************************************************/ -#if __OBJC2__ - -#define OBJC_MESSENGER_START 1 -#define OBJC_MESSENGER_END_FAST 2 -#define OBJC_MESSENGER_END_SLOW 3 -#define OBJC_MESSENGER_END_NIL 4 - -struct objc_messenger_breakpoint { - uintptr_t address; - uintptr_t kind; -}; - -OBJC_EXPORT struct objc_messenger_breakpoint -gdb_objc_messenger_breakpoints[] - OBJC_AVAILABLE(10.9, 7.0, 9.0, 1.0, 2.0); - -#endif - - __END_DECLS +// APPLE_API_PRIVATE #endif +// _OBJC_GDB_H #endif diff --git a/runtime/objc-internal.h b/runtime/objc-internal.h index fbd8820..44e89ff 100644 --- a/runtime/objc-internal.h +++ b/runtime/objc-internal.h @@ -41,9 +41,9 @@ #include #include #include +#include #include -__BEGIN_DECLS // Termination reasons in the OS_REASON_OBJC namespace. #define OBJC_EXIT_REASON_UNSPECIFIED 1 @@ -54,6 +54,9 @@ __BEGIN_DECLS // The runtime's class structure will never grow beyond this. #define OBJC_MAX_CLASS_SIZE (32*sizeof(void*)) + +__BEGIN_DECLS + // In-place construction of an Objective-C class. // cls and metacls must each be OBJC_MAX_CLASS_SIZE bytes. // Returns nil if a class with the same name already exists. @@ -126,6 +129,13 @@ objc_isAuto(id _Nullable object) __IOS_UNAVAILABLE __TVOS_UNAVAILABLE __WATCHOS_UNAVAILABLE __BRIDGEOS_UNAVAILABLE; +// GC debugging +OBJC_EXPORT BOOL +objc_dumpHeap(char * _Nonnull filename, unsigned long length) + __OSX_DEPRECATED(10.4, 10.8, "it always returns NO") + __IOS_UNAVAILABLE __TVOS_UNAVAILABLE + __WATCHOS_UNAVAILABLE __BRIDGEOS_UNAVAILABLE; + // GC startup callback from Foundation OBJC_EXPORT malloc_zone_t * _Nullable objc_collect_init(int (* _Nonnull callback)(void)) @@ -166,6 +176,14 @@ OBJC_EXPORT void _objc_setClassLoader(BOOL (* _Nonnull newClassLoader)(const char * _Nonnull)) OBJC2_UNAVAILABLE; +#if !(TARGET_OS_OSX && !TARGET_OS_IOSMAC && __i386__) +OBJC_EXPORT void +_objc_setClassCopyFixupHandler(void (* _Nonnull newFixupHandler) + (Class _Nonnull oldClass, Class _Nonnull newClass)); +// fixme work around bug in Swift +// OBJC_AVAILABLE(10.14, 12.0, 12.0, 5.0, 3.0) +#endif + // Install handler for allocation failures. // Handler may abort, or throw, or provide an object to return. OBJC_EXPORT void @@ -173,15 +191,6 @@ _objc_setBadAllocHandler(id _Nullable (* _Nonnull newHandler) (Class _Nullable isa)) OBJC_AVAILABLE(10.8, 6.0, 9.0, 1.0, 2.0); -// This can go away when AppKit stops calling it (rdar://7811851) -#if __OBJC2__ -OBJC_EXPORT void -objc_setMultithreaded (BOOL flag) - __OSX_DEPRECATED(10.0, 10.5, "multithreading is always available") - __IOS_UNAVAILABLE __TVOS_UNAVAILABLE - __WATCHOS_UNAVAILABLE __BRIDGEOS_UNAVAILABLE; -#endif - // Used by ExceptionHandling.framework #if !__OBJC2__ OBJC_EXPORT void @@ -194,6 +203,19 @@ _objc_error(id _Nullable rcv, const char * _Nonnull fmt, va_list args) #endif +/** + * Returns the names of all the classes within a library. + * + * @param image The mach header for library or framework you are inquiring about. + * @param outCount The number of class names returned. + * + * @return An array of C strings representing the class names. + */ +OBJC_EXPORT const char * _Nonnull * _Nullable +objc_copyClassNamesForImageHeader(const struct mach_header * _Nonnull mh, + unsigned int * _Nullable outCount) + OBJC_AVAILABLE(10.14, 12.0, 12.0, 5.0, 3.0); + // Tagged pointer objects. #if __LP64__ @@ -216,15 +238,28 @@ typedef uint16_t objc_tag_index_t; enum #endif { + // 60-bit payloads OBJC_TAG_NSAtom = 0, OBJC_TAG_1 = 1, OBJC_TAG_NSString = 2, OBJC_TAG_NSNumber = 3, OBJC_TAG_NSIndexPath = 4, OBJC_TAG_NSManagedObjectID = 5, - OBJC_TAG_NSDate = 6, + OBJC_TAG_NSDate = 6, + + // 60-bit reserved OBJC_TAG_RESERVED_7 = 7, + // 52-bit payloads + OBJC_TAG_Photos_1 = 8, + OBJC_TAG_Photos_2 = 9, + OBJC_TAG_Photos_3 = 10, + OBJC_TAG_Photos_4 = 11, + OBJC_TAG_XPC_1 = 12, + OBJC_TAG_XPC_2 = 13, + OBJC_TAG_XPC_3 = 14, + OBJC_TAG_XPC_4 = 15, + OBJC_TAG_First60BitPayload = 0, OBJC_TAG_Last60BitPayload = 6, OBJC_TAG_First52BitPayload = 8, @@ -287,7 +322,7 @@ _objc_getTaggedPointerSignedValue(const void * _Nullable ptr); // Don't use the values below. Use the declarations above. -#if TARGET_OS_OSX && __x86_64__ +#if (TARGET_OS_OSX || TARGET_OS_IOSMAC) && __x86_64__ // 64-bit Mac - tag bit is LSB # define OBJC_MSB_TAGGED_POINTERS 0 #else @@ -329,6 +364,20 @@ _objc_getTaggedPointerSignedValue(const void * _Nullable ptr); # define _OBJC_TAG_EXT_PAYLOAD_RSHIFT 12 #endif +extern uintptr_t objc_debug_taggedpointer_obfuscator; + +static inline void * _Nonnull +_objc_encodeTaggedPointer(uintptr_t ptr) +{ + return (void *)(objc_debug_taggedpointer_obfuscator ^ ptr); +} + +static inline uintptr_t +_objc_decodeTaggedPointer(const void * _Nullable ptr) +{ + return (uintptr_t)ptr ^ objc_debug_taggedpointer_obfuscator; +} + static inline bool _objc_taggedPointersEnabled(void) { @@ -345,23 +394,25 @@ _objc_makeTaggedPointer(objc_tag_index_t tag, uintptr_t value) // assert(_objc_taggedPointersEnabled()); if (tag <= OBJC_TAG_Last60BitPayload) { // assert(((value << _OBJC_TAG_PAYLOAD_RSHIFT) >> _OBJC_TAG_PAYLOAD_LSHIFT) == value); - return (void *) + uintptr_t result = (_OBJC_TAG_MASK | ((uintptr_t)tag << _OBJC_TAG_INDEX_SHIFT) | ((value << _OBJC_TAG_PAYLOAD_RSHIFT) >> _OBJC_TAG_PAYLOAD_LSHIFT)); + return _objc_encodeTaggedPointer(result); } else { // assert(tag >= OBJC_TAG_First52BitPayload); // assert(tag <= OBJC_TAG_Last52BitPayload); // assert(((value << _OBJC_TAG_EXT_PAYLOAD_RSHIFT) >> _OBJC_TAG_EXT_PAYLOAD_LSHIFT) == value); - return (void *) + uintptr_t result = (_OBJC_TAG_EXT_MASK | ((uintptr_t)(tag - OBJC_TAG_First52BitPayload) << _OBJC_TAG_EXT_INDEX_SHIFT) | ((value << _OBJC_TAG_EXT_PAYLOAD_RSHIFT) >> _OBJC_TAG_EXT_PAYLOAD_LSHIFT)); + return _objc_encodeTaggedPointer(result); } } static inline bool -_objc_isTaggedPointer(const void * _Nullable ptr) +_objc_isTaggedPointer(const void * _Nullable ptr) { return ((uintptr_t)ptr & _OBJC_TAG_MASK) == _OBJC_TAG_MASK; } @@ -370,8 +421,9 @@ static inline objc_tag_index_t _objc_getTaggedPointerTag(const void * _Nullable ptr) { // assert(_objc_isTaggedPointer(ptr)); - uintptr_t basicTag = ((uintptr_t)ptr >> _OBJC_TAG_INDEX_SHIFT) & _OBJC_TAG_INDEX_MASK; - uintptr_t extTag = ((uintptr_t)ptr >> _OBJC_TAG_EXT_INDEX_SHIFT) & _OBJC_TAG_EXT_INDEX_MASK; + uintptr_t value = _objc_decodeTaggedPointer(ptr); + uintptr_t basicTag = (value >> _OBJC_TAG_INDEX_SHIFT) & _OBJC_TAG_INDEX_MASK; + uintptr_t extTag = (value >> _OBJC_TAG_EXT_INDEX_SHIFT) & _OBJC_TAG_EXT_INDEX_MASK; if (basicTag == _OBJC_TAG_INDEX_MASK) { return (objc_tag_index_t)(extTag + OBJC_TAG_First52BitPayload); } else { @@ -383,11 +435,12 @@ static inline uintptr_t _objc_getTaggedPointerValue(const void * _Nullable ptr) { // assert(_objc_isTaggedPointer(ptr)); - uintptr_t basicTag = ((uintptr_t)ptr >> _OBJC_TAG_INDEX_SHIFT) & _OBJC_TAG_INDEX_MASK; + uintptr_t value = _objc_decodeTaggedPointer(ptr); + uintptr_t basicTag = (value >> _OBJC_TAG_INDEX_SHIFT) & _OBJC_TAG_INDEX_MASK; if (basicTag == _OBJC_TAG_INDEX_MASK) { - return ((uintptr_t)ptr << _OBJC_TAG_EXT_PAYLOAD_LSHIFT) >> _OBJC_TAG_EXT_PAYLOAD_RSHIFT; + return (value << _OBJC_TAG_EXT_PAYLOAD_LSHIFT) >> _OBJC_TAG_EXT_PAYLOAD_RSHIFT; } else { - return ((uintptr_t)ptr << _OBJC_TAG_PAYLOAD_LSHIFT) >> _OBJC_TAG_PAYLOAD_RSHIFT; + return (value << _OBJC_TAG_PAYLOAD_LSHIFT) >> _OBJC_TAG_PAYLOAD_RSHIFT; } } @@ -395,11 +448,12 @@ static inline intptr_t _objc_getTaggedPointerSignedValue(const void * _Nullable ptr) { // assert(_objc_isTaggedPointer(ptr)); - uintptr_t basicTag = ((uintptr_t)ptr >> _OBJC_TAG_INDEX_SHIFT) & _OBJC_TAG_INDEX_MASK; + uintptr_t value = _objc_decodeTaggedPointer(ptr); + uintptr_t basicTag = (value >> _OBJC_TAG_INDEX_SHIFT) & _OBJC_TAG_INDEX_MASK; if (basicTag == _OBJC_TAG_INDEX_MASK) { - return ((intptr_t)ptr << _OBJC_TAG_EXT_PAYLOAD_LSHIFT) >> _OBJC_TAG_EXT_PAYLOAD_RSHIFT; + return ((intptr_t)value << _OBJC_TAG_EXT_PAYLOAD_LSHIFT) >> _OBJC_TAG_EXT_PAYLOAD_RSHIFT; } else { - return ((intptr_t)ptr << _OBJC_TAG_PAYLOAD_LSHIFT) >> _OBJC_TAG_PAYLOAD_RSHIFT; + return ((intptr_t)value << _OBJC_TAG_PAYLOAD_LSHIFT) >> _OBJC_TAG_PAYLOAD_RSHIFT; } } @@ -430,21 +484,68 @@ object_getMethodImplementation_stret(id _Nullable obj, SEL _Nonnull name) OBJC_ARM64_UNAVAILABLE; -// Instance-specific instance variable layout. +/** + * Adds multiple methods to a class in bulk. This amortizes overhead that can be + * expensive when adding methods one by one with class_addMethod. + * + * @param cls The class to which to add the methods. + * @param names An array of selectors for the methods to add. + * @param imps An array of functions which implement the new methods. + * @param types An array of strings that describe the types of each method's + * arguments. + * @param count The number of items in the names, imps, and types arrays. + * @param outFiledCount Upon return, contains the number of failed selectors in + * the returned array. + * + * @return A NULL-terminated C array of selectors which could not be added. A + * method cannot be added when a method of that name already exists on that + * class. When no failures occur, the return value is \c NULL. When a non-NULL + * value is returned, the caller must free the array with \c free(). + * + */ +#if __OBJC2__ +OBJC_EXPORT _Nullable SEL * _Nullable +class_addMethodsBulk(_Nullable Class cls, _Nonnull const SEL * _Nonnull names, + _Nonnull const IMP * _Nonnull imps, + const char * _Nonnull * _Nonnull types, uint32_t count, + uint32_t * _Nullable outFailedCount) + OBJC_AVAILABLE(10.14, 12.0, 12.0, 5.0, 3.0); +#endif + +/** + * Replaces multiple methods in a class in bulk. This amortizes overhead that + * can be expensive when adding methods one by one with class_replaceMethod. + * + * @param cls The class to modify. + * @param names An array of selectors for the methods to replace. + * @param imps An array of functions will be the new method implementantations. + * @param types An array of strings that describe the types of each method's + * arguments. + * @param count The number of items in the names, imps, and types arrays. + */ +#if __OBJC2__ +OBJC_EXPORT void +class_replaceMethodsBulk(_Nullable Class cls, + _Nonnull const SEL * _Nonnull names, + _Nonnull const IMP * _Nonnull imps, + const char * _Nonnull * _Nonnull types, + uint32_t count) + OBJC_AVAILABLE(10.14, 12.0, 12.0, 5.0, 3.0); +#endif + + +// Instance-specific instance variable layout. This is no longer implemented. OBJC_EXPORT void _class_setIvarLayoutAccessor(Class _Nullable cls, const uint8_t* _Nullable (* _Nonnull accessor) (id _Nullable object)) - __OSX_AVAILABLE(10.7) - __IOS_UNAVAILABLE __TVOS_UNAVAILABLE - __WATCHOS_UNAVAILABLE __BRIDGEOS_UNAVAILABLE; + UNAVAILABLE_ATTRIBUTE; OBJC_EXPORT const uint8_t * _Nullable _object_getIvarLayout(Class _Nullable cls, id _Nullable object) - __OSX_AVAILABLE(10.7) - __IOS_UNAVAILABLE __TVOS_UNAVAILABLE - __WATCHOS_UNAVAILABLE __BRIDGEOS_UNAVAILABLE; + UNAVAILABLE_ATTRIBUTE; + /* "Unknown" includes non-object ivars and non-ARC non-__weak ivars diff --git a/runtime/objc-lockdebug.h b/runtime/objc-lockdebug.h index 0fdee6f..a3048b1 100644 --- a/runtime/objc-lockdebug.h +++ b/runtime/objc-lockdebug.h @@ -27,10 +27,10 @@ extern void lockdebug_assert_no_locks_locked(); extern void lockdebug_setInForkPrepare(bool); extern void lockdebug_lock_precedes_lock(const void *oldlock, const void *newlock); #else -static inline void lockdebug_assert_all_locks_locked() { } -static inline void lockdebug_assert_no_locks_locked() { } -static inline void lockdebug_setInForkPrepare(bool) { } -static inline void lockdebug_lock_precedes_lock(const void *, const void *) { } +static constexpr inline void lockdebug_assert_all_locks_locked() { } +static constexpr inline void lockdebug_assert_no_locks_locked() { } +static constexpr inline void lockdebug_setInForkPrepare(bool) { } +static constexpr inline void lockdebug_lock_precedes_lock(const void *, const void *) { } #endif extern void lockdebug_remember_mutex(mutex_tt *lock); @@ -40,12 +40,12 @@ extern void lockdebug_mutex_unlock(mutex_tt *lock); extern void lockdebug_mutex_assert_locked(mutex_tt *lock); extern void lockdebug_mutex_assert_unlocked(mutex_tt *lock); -static inline void lockdebug_remember_mutex(mutex_tt *lock) { } -static inline void lockdebug_mutex_lock(mutex_tt *lock) { } -static inline void lockdebug_mutex_try_lock(mutex_tt *lock) { } -static inline void lockdebug_mutex_unlock(mutex_tt *lock) { } -static inline void lockdebug_mutex_assert_locked(mutex_tt *lock) { } -static inline void lockdebug_mutex_assert_unlocked(mutex_tt *lock) { } +static constexpr inline void lockdebug_remember_mutex(mutex_tt *lock) { } +static constexpr inline void lockdebug_mutex_lock(mutex_tt *lock) { } +static constexpr inline void lockdebug_mutex_try_lock(mutex_tt *lock) { } +static constexpr inline void lockdebug_mutex_unlock(mutex_tt *lock) { } +static constexpr inline void lockdebug_mutex_assert_locked(mutex_tt *lock) { } +static constexpr inline void lockdebug_mutex_assert_unlocked(mutex_tt *lock) { } extern void lockdebug_remember_monitor(monitor_tt *lock); @@ -55,12 +55,12 @@ extern void lockdebug_monitor_wait(monitor_tt *lock); extern void lockdebug_monitor_assert_locked(monitor_tt *lock); extern void lockdebug_monitor_assert_unlocked(monitor_tt *lock); -static inline void lockdebug_remember_monitor(monitor_tt *lock) { } -static inline void lockdebug_monitor_enter(monitor_tt *lock) { } -static inline void lockdebug_monitor_leave(monitor_tt *lock) { } -static inline void lockdebug_monitor_wait(monitor_tt *lock) { } -static inline void lockdebug_monitor_assert_locked(monitor_tt *lock) { } -static inline void lockdebug_monitor_assert_unlocked(monitor_tt *lock) {} +static constexpr inline void lockdebug_remember_monitor(monitor_tt *lock) { } +static constexpr inline void lockdebug_monitor_enter(monitor_tt *lock) { } +static constexpr inline void lockdebug_monitor_leave(monitor_tt *lock) { } +static constexpr inline void lockdebug_monitor_wait(monitor_tt *lock) { } +static constexpr inline void lockdebug_monitor_assert_locked(monitor_tt *lock) { } +static constexpr inline void lockdebug_monitor_assert_unlocked(monitor_tt *lock) {} extern void @@ -74,38 +74,13 @@ lockdebug_recursive_mutex_assert_locked(recursive_mutex_tt *lock); extern void lockdebug_recursive_mutex_assert_unlocked(recursive_mutex_tt *lock); -static inline void +static constexpr inline void lockdebug_remember_recursive_mutex(recursive_mutex_tt *lock) { } -static inline void +static constexpr inline void lockdebug_recursive_mutex_lock(recursive_mutex_tt *lock) { } -static inline void +static constexpr inline void lockdebug_recursive_mutex_unlock(recursive_mutex_tt *lock) { } -static inline void +static constexpr inline void lockdebug_recursive_mutex_assert_locked(recursive_mutex_tt *lock) { } -static inline void +static constexpr inline void lockdebug_recursive_mutex_assert_unlocked(recursive_mutex_tt *lock) { } - - -extern void lockdebug_remember_rwlock(rwlock_tt *lock); -extern void lockdebug_rwlock_read(rwlock_tt *lock); -extern void lockdebug_rwlock_try_read_success(rwlock_tt *lock); -extern void lockdebug_rwlock_unlock_read(rwlock_tt *lock); -extern void lockdebug_rwlock_write(rwlock_tt *lock); -extern void lockdebug_rwlock_try_write_success(rwlock_tt *lock); -extern void lockdebug_rwlock_unlock_write(rwlock_tt *lock); -extern void lockdebug_rwlock_assert_reading(rwlock_tt *lock); -extern void lockdebug_rwlock_assert_writing(rwlock_tt *lock); -extern void lockdebug_rwlock_assert_locked(rwlock_tt *lock); -extern void lockdebug_rwlock_assert_unlocked(rwlock_tt *lock); - -static inline void lockdebug_remember_rwlock(rwlock_tt *) { } -static inline void lockdebug_rwlock_read(rwlock_tt *) { } -static inline void lockdebug_rwlock_try_read_success(rwlock_tt *) { } -static inline void lockdebug_rwlock_unlock_read(rwlock_tt *) { } -static inline void lockdebug_rwlock_write(rwlock_tt *) { } -static inline void lockdebug_rwlock_try_write_success(rwlock_tt *) { } -static inline void lockdebug_rwlock_unlock_write(rwlock_tt *) { } -static inline void lockdebug_rwlock_assert_reading(rwlock_tt *) { } -static inline void lockdebug_rwlock_assert_writing(rwlock_tt *) { } -static inline void lockdebug_rwlock_assert_locked(rwlock_tt *) { } -static inline void lockdebug_rwlock_assert_unlocked(rwlock_tt *) { } diff --git a/runtime/objc-lockdebug.mm b/runtime/objc-lockdebug.mm index c1ac7a4..f182a27 100644 --- a/runtime/objc-lockdebug.mm +++ b/runtime/objc-lockdebug.mm @@ -306,12 +306,6 @@ void lockdebug_lock_precedes_lock(const void *oldlock, const void *newlock) setLock(AllLocks(), lock, MONITOR); } -void -lockdebug_remember_rwlock(rwlock_t *lock) -{ - setLock(AllLocks(), lock, WRLOCK); -} - void lockdebug_assert_all_locks_locked() { @@ -497,126 +491,4 @@ void lockdebug_lock_precedes_lock(const void *oldlock, const void *newlock) } } - -/*********************************************************************** -* rwlock checking -**********************************************************************/ - -void -lockdebug_rwlock_read(rwlock_t *lock) -{ - auto& locks = ownedLocks(); - - if (hasLock(locks, lock, RDLOCK)) { - // Recursive rwlock read is bad (may deadlock vs pending writer) - _objc_fatal("recursive rwlock read"); - } - if (hasLock(locks, lock, WRLOCK)) { - _objc_fatal("deadlock: read after write for rwlock"); - } - setLock(locks, lock, RDLOCK); -} - -// try-read success is the only case with lockdebug effects. -// try-read when already reading is OK (won't deadlock) -// try-read when already writing is OK (will fail) -// try-read failure does nothing. -void -lockdebug_rwlock_try_read_success(rwlock_t *lock) -{ - auto& locks = ownedLocks(); - setLock(locks, lock, RDLOCK); -} - -void -lockdebug_rwlock_unlock_read(rwlock_t *lock) -{ - auto& locks = ownedLocks(); - - if (!hasLock(locks, lock, RDLOCK)) { - _objc_fatal("un-reading unowned rwlock"); - } - clearLock(locks, lock, RDLOCK); -} - - -void -lockdebug_rwlock_write(rwlock_t *lock) -{ - auto& locks = ownedLocks(); - - if (hasLock(locks, lock, RDLOCK)) { - // Lock promotion not allowed (may deadlock) - _objc_fatal("deadlock: write after read for rwlock"); - } - if (hasLock(locks, lock, WRLOCK)) { - _objc_fatal("recursive rwlock write"); - } - setLock(locks, lock, WRLOCK); -} - -// try-write success is the only case with lockdebug effects. -// try-write when already reading is OK (will fail) -// try-write when already writing is OK (will fail) -// try-write failure does nothing. -void -lockdebug_rwlock_try_write_success(rwlock_t *lock) -{ - auto& locks = ownedLocks(); - setLock(locks, lock, WRLOCK); -} - -void -lockdebug_rwlock_unlock_write(rwlock_t *lock) -{ - auto& locks = ownedLocks(); - - if (!hasLock(locks, lock, WRLOCK)) { - _objc_fatal("un-writing unowned rwlock"); - } - clearLock(locks, lock, WRLOCK); -} - - -void -lockdebug_rwlock_assert_reading(rwlock_t *lock) -{ - auto& locks = ownedLocks(); - - if (!hasLock(locks, lock, RDLOCK)) { - _objc_fatal("rwlock incorrectly not reading"); - } -} - -void -lockdebug_rwlock_assert_writing(rwlock_t *lock) -{ - auto& locks = ownedLocks(); - - if (!hasLock(locks, lock, WRLOCK)) { - _objc_fatal("rwlock incorrectly not writing"); - } -} - -void -lockdebug_rwlock_assert_locked(rwlock_t *lock) -{ - auto& locks = ownedLocks(); - - if (!hasLock(locks, lock, RDLOCK) && !hasLock(locks, lock, WRLOCK)) { - _objc_fatal("rwlock incorrectly neither reading nor writing"); - } -} - -void -lockdebug_rwlock_assert_unlocked(rwlock_t *lock) -{ - auto& locks = ownedLocks(); - - if (hasLock(locks, lock, RDLOCK) || hasLock(locks, lock, WRLOCK)) { - _objc_fatal("rwlock incorrectly not unlocked"); - } -} - - #endif diff --git a/runtime/objc-locks-new.h b/runtime/objc-locks-new.h index 73e3dd0..cd3ace3 100644 --- a/runtime/objc-locks-new.h +++ b/runtime/objc-locks-new.h @@ -32,7 +32,7 @@ // fork() safety requires careful tracking of all locks used in the runtime. // Thou shalt not declare any locks outside this file. -extern rwlock_t runtimeLock; +extern mutex_t runtimeLock; extern mutex_t DemangleCacheLock; #endif diff --git a/runtime/objc-locks.h b/runtime/objc-locks.h index 00654eb..8cb7907 100644 --- a/runtime/objc-locks.h +++ b/runtime/objc-locks.h @@ -36,7 +36,7 @@ // and is enforced by lockdebug. extern monitor_t classInitLock; -extern rwlock_t selLock; +extern mutex_t selLock; extern mutex_t cacheUpdateLock; extern recursive_mutex_t loadMethodLock; extern mutex_t crashlog_lock; diff --git a/runtime/objc-object.h b/runtime/objc-object.h index 821b0a7..177ea10 100644 --- a/runtime/objc-object.h +++ b/runtime/objc-object.h @@ -106,7 +106,8 @@ objc_object::isBasicTaggedPointer() inline bool objc_object::isExtTaggedPointer() { - return ((uintptr_t)this & _OBJC_TAG_EXT_MASK) == _OBJC_TAG_EXT_MASK; + uintptr_t ptr = _objc_decodeTaggedPointer(this); + return (ptr & _OBJC_TAG_EXT_MASK) == _OBJC_TAG_EXT_MASK; } diff --git a/runtime/objc-opt.mm b/runtime/objc-opt.mm index 45c13b3..498c349 100644 --- a/runtime/objc-opt.mm +++ b/runtime/objc-opt.mm @@ -75,6 +75,11 @@ Class getPreoptimizedClass(const char *name) return nil; } +bool sharedRegionContains(const void *ptr) +{ + return false; +} + header_info *preoptimizedHinfoForHeader(const headerType *mhdr) { return nil; @@ -116,6 +121,8 @@ void preopt_init(void) // opt is initialized to ~0 to detect incorrect use before preopt_init() static const objc_opt_t *opt = (objc_opt_t *)~0; +static uintptr_t shared_cache_start; +static uintptr_t shared_cache_end; static bool preoptimized; extern const objc_opt_t _objc_opt_data; // in __TEXT, __objc_opt_ro @@ -251,6 +258,16 @@ Class getPreoptimizedClass(const char *name) return nil; } +/*********************************************************************** +* Return YES if the given pointer lies within the shared cache. +* If the shared cache is not set up or is not valid, +**********************************************************************/ +bool sharedRegionContains(const void *ptr) +{ + uintptr_t address = (uintptr_t)ptr; + return shared_cache_start <= address && address < shared_cache_end; +} + namespace objc_opt { struct objc_headeropt_ro_t { uint32_t count; @@ -327,6 +344,16 @@ Class getPreoptimizedClass(const char *name) void preopt_init(void) { + // Get the memory region occupied by the shared cache. + size_t length; + const void *start = _dyld_get_shared_cache_range(&length); + if (start) { + shared_cache_start = (uintptr_t)start; + shared_cache_end = shared_cache_start + length; + } else { + shared_cache_start = shared_cache_end = 0; + } + // `opt` not set at compile time in order to detect too-early usage const char *failure = nil; opt = &_objc_opt_data; @@ -347,7 +374,7 @@ void preopt_init(void) // One of the tables is missing. failure = "(dyld shared cache is absent or out of date)"; } - + if (failure) { // All preoptimized selector references are invalid. preoptimized = NO; diff --git a/runtime/objc-os.h b/runtime/objc-os.h index 0104d7d..24addca 100644 --- a/runtime/objc-os.h +++ b/runtime/objc-os.h @@ -56,8 +56,8 @@ class nocopy_t { nocopy_t(const nocopy_t&) = delete; const nocopy_t& operator=(const nocopy_t&) = delete; protected: - nocopy_t() { } - ~nocopy_t() { } + constexpr nocopy_t() = default; + ~nocopy_t() = default; }; @@ -135,12 +135,19 @@ subc(uintptr_t lhs, uintptr_t rhs, uintptr_t carryin, uintptr_t *carryout) #if __arm64__ +// Pointer-size register prefix for inline asm +# if __LP64__ +# define p "x" // true arm64 +# else +# define p "w" // arm64_32 +# endif + static ALWAYS_INLINE uintptr_t LoadExclusive(uintptr_t *src) { uintptr_t result; - asm("ldxr %x0, [%x1]" + asm("ldxr %" p "0, [%x1]" : "=r" (result) : "r" (src), "m" (*src)); return result; @@ -151,8 +158,8 @@ bool StoreExclusive(uintptr_t *dst, uintptr_t oldvalue __unused, uintptr_t value) { uint32_t result; - asm("stxr %w0, %x2, [%x3]" - : "=r" (result), "=m" (*dst) + asm("stxr %w0, %" p "2, [%x3]" + : "=&r" (result), "=m" (*dst) : "r" (value), "r" (dst)); return !result; } @@ -163,8 +170,8 @@ bool StoreReleaseExclusive(uintptr_t *dst, uintptr_t oldvalue __unused, uintptr_t value) { uint32_t result; - asm("stlxr %w0, %x2, [%x3]" - : "=r" (result), "=m" (*dst) + asm("stlxr %w0, %" p "2, [%x3]" + : "=&r" (result), "=m" (*dst) : "r" (value), "r" (dst)); return !result; } @@ -177,6 +184,7 @@ ClearExclusive(uintptr_t *dst) asm("clrex" : "=m" (*dst)); } +#undef p #elif __arm__ @@ -372,9 +380,6 @@ extern void _objc_fatal_with_reason(uint64_t reason, uint64_t flags, # if SUPPORT_RETURN_AUTORELEASE # define RETURN_DISPOSITION_KEY ((tls_key_t)__PTK_FRAMEWORK_OBJC_KEY4) # endif -# if SUPPORT_QOS_HACK -# define QOS_KEY ((tls_key_t)__PTK_FRAMEWORK_OBJC_KEY5) -# endif #else # define SUPPORT_DIRECT_THREAD_KEYS 0 #endif @@ -611,9 +616,6 @@ static inline int monitor_notifyAll(monitor_t *c) { } -// fixme no rwlock yet - - typedef IMAGE_DOS_HEADER headerType; // fixme YES bundle? NO bundle? sometimes? #define headerIsBundle(hi) YES @@ -682,9 +684,6 @@ static bool is_valid_direct_key(tls_key_t k) { || k == AUTORELEASE_POOL_KEY # if SUPPORT_RETURN_AUTORELEASE || k == RETURN_DISPOSITION_KEY -# endif -# if SUPPORT_QOS_HACK - || k == QOS_KEY # endif ); } @@ -727,19 +726,9 @@ static inline mach_port_t mach_thread_self_direct() _pthread_getspecific_direct(_PTHREAD_TSD_SLOT_MACH_THREAD_SELF); } -#if SUPPORT_QOS_HACK -static inline pthread_priority_t pthread_self_priority_direct() -{ - pthread_priority_t pri = (pthread_priority_t) - _pthread_getspecific_direct(_PTHREAD_TSD_SLOT_PTHREAD_QOS_CLASS); - return pri & ~_PTHREAD_PRIORITY_FLAGS_MASK; -} -#endif - template class mutex_tt; template class monitor_tt; -template class rwlock_tt; template class recursive_mutex_tt; #if DEBUG @@ -751,13 +740,14 @@ template class recursive_mutex_tt; using spinlock_t = mutex_tt; using mutex_t = mutex_tt; using monitor_t = monitor_tt; -using rwlock_t = rwlock_tt; using recursive_mutex_t = recursive_mutex_tt; // Use fork_unsafe_lock to get a lock that isn't // acquired and released around fork(). // All fork-safe locks are checked in debug builds. -struct fork_unsafe_lock_t { }; +struct fork_unsafe_lock_t { + constexpr fork_unsafe_lock_t() = default; +}; extern const fork_unsafe_lock_t fork_unsafe_lock; #include "objc-lockdebug.h" @@ -766,11 +756,11 @@ template class mutex_tt : nocopy_t { os_unfair_lock mLock; public: - mutex_tt() : mLock(OS_UNFAIR_LOCK_INIT) { + constexpr mutex_tt() : mLock(OS_UNFAIR_LOCK_INIT) { lockdebug_remember_mutex(this); } - mutex_tt(const fork_unsafe_lock_t unsafe) : mLock(OS_UNFAIR_LOCK_INIT) { } + constexpr mutex_tt(const fork_unsafe_lock_t unsafe) : mLock(OS_UNFAIR_LOCK_INIT) { } void lock() { lockdebug_mutex_lock(this); @@ -826,38 +816,49 @@ class mutex_tt : nocopy_t { : lock(newLock) { lock.lock(); } ~locker() { lock.unlock(); } }; + + // Either scoped lock and unlock, or NOP. + class conditional_locker : nocopy_t { + mutex_tt& lock; + bool didLock; + public: + conditional_locker(mutex_tt& newLock, bool shouldLock) + : lock(newLock), didLock(shouldLock) + { + if (shouldLock) lock.lock(); + } + ~conditional_locker() { if (didLock) lock.unlock(); } + }; }; using mutex_locker_t = mutex_tt::locker; +using conditional_mutex_locker_t = mutex_tt::conditional_locker; template class recursive_mutex_tt : nocopy_t { - pthread_mutex_t mLock; + os_unfair_recursive_lock mLock; public: - recursive_mutex_tt() : mLock(PTHREAD_RECURSIVE_MUTEX_INITIALIZER) { + constexpr recursive_mutex_tt() : mLock(OS_UNFAIR_RECURSIVE_LOCK_INIT) { lockdebug_remember_recursive_mutex(this); } - recursive_mutex_tt(const fork_unsafe_lock_t unsafe) - : mLock(PTHREAD_RECURSIVE_MUTEX_INITIALIZER) + constexpr recursive_mutex_tt(const fork_unsafe_lock_t unsafe) + : mLock(OS_UNFAIR_RECURSIVE_LOCK_INIT) { } void lock() { lockdebug_recursive_mutex_lock(this); - - int err = pthread_mutex_lock(&mLock); - if (err) _objc_fatal("pthread_mutex_lock failed (%d)", err); + os_unfair_recursive_lock_lock(&mLock); } void unlock() { lockdebug_recursive_mutex_unlock(this); - int err = pthread_mutex_unlock(&mLock); - if (err) _objc_fatal("pthread_mutex_unlock failed (%d)", err); + os_unfair_recursive_lock_unlock(&mLock); } void forceReset() @@ -865,23 +866,18 @@ class recursive_mutex_tt : nocopy_t { lockdebug_recursive_mutex_unlock(this); bzero(&mLock, sizeof(mLock)); - mLock = pthread_mutex_t PTHREAD_RECURSIVE_MUTEX_INITIALIZER; + mLock = os_unfair_recursive_lock OS_UNFAIR_RECURSIVE_LOCK_INIT; } bool tryUnlock() { - int err = pthread_mutex_unlock(&mLock); - if (err == 0) { + if (os_unfair_recursive_lock_tryunlock4objc(&mLock)) { lockdebug_recursive_mutex_unlock(this); return true; - } else if (err == EPERM) { - return false; - } else { - _objc_fatal("pthread_mutex_unlock failed (%d)", err); } + return false; } - void assertLocked() { lockdebug_recursive_mutex_assert_locked(this); } @@ -898,7 +894,7 @@ class monitor_tt { pthread_cond_t cond; public: - monitor_tt() + constexpr monitor_tt() : mutex(PTHREAD_MUTEX_INITIALIZER), cond(PTHREAD_COND_INITIALIZER) { lockdebug_remember_monitor(this); @@ -977,166 +973,6 @@ static inline semaphore_t create_semaphore(void) } -#if SUPPORT_QOS_HACK -// Override QOS class to avoid priority inversion in rwlocks -// do a qos override before taking rw lock in objc - -#include -extern pthread_priority_t BackgroundPriority; -extern pthread_priority_t MainPriority; - -static inline void qosStartOverride() -{ - uintptr_t overrideRefCount = (uintptr_t)tls_get_direct(QOS_KEY); - if (overrideRefCount > 0) { - // If there is a qos override, increment the refcount and continue - tls_set_direct(QOS_KEY, (void *)(overrideRefCount + 1)); - } - else { - pthread_priority_t currentPriority = pthread_self_priority_direct(); - // Check if override is needed. Only override if we are background qos - if (currentPriority != 0 && currentPriority <= BackgroundPriority) { - int res __unused = _pthread_override_qos_class_start_direct(mach_thread_self_direct(), MainPriority); - assert(res == 0); - // Once we override, we set the reference count in the tsd - // to know when to end the override - tls_set_direct(QOS_KEY, (void *)1); - } - } -} - -static inline void qosEndOverride() -{ - uintptr_t overrideRefCount = (uintptr_t)tls_get_direct(QOS_KEY); - if (overrideRefCount == 0) return; - - if (overrideRefCount == 1) { - // end the override - int res __unused = _pthread_override_qos_class_end_direct(mach_thread_self_direct()); - assert(res == 0); - } - - // decrement refcount - tls_set_direct(QOS_KEY, (void *)(overrideRefCount - 1)); -} - -// SUPPORT_QOS_HACK -#else -// not SUPPORT_QOS_HACK - -static inline void qosStartOverride() { } -static inline void qosEndOverride() { } - -// not SUPPORT_QOS_HACK -#endif - - -template -class rwlock_tt : nocopy_t { - pthread_rwlock_t mLock; - - public: - rwlock_tt() : mLock(PTHREAD_RWLOCK_INITIALIZER) { - lockdebug_remember_rwlock(this); - } - - rwlock_tt(const fork_unsafe_lock_t unsafe) - : mLock(PTHREAD_RWLOCK_INITIALIZER) - { } - - void read() - { - lockdebug_rwlock_read(this); - - qosStartOverride(); - int err = pthread_rwlock_rdlock(&mLock); - if (err) _objc_fatal("pthread_rwlock_rdlock failed (%d)", err); - } - - void unlockRead() - { - lockdebug_rwlock_unlock_read(this); - - int err = pthread_rwlock_unlock(&mLock); - if (err) _objc_fatal("pthread_rwlock_unlock failed (%d)", err); - qosEndOverride(); - } - - bool tryRead() - { - qosStartOverride(); - int err = pthread_rwlock_tryrdlock(&mLock); - if (err == 0) { - lockdebug_rwlock_try_read_success(this); - return true; - } else if (err == EBUSY) { - qosEndOverride(); - return false; - } else { - _objc_fatal("pthread_rwlock_tryrdlock failed (%d)", err); - } - } - - void write() - { - lockdebug_rwlock_write(this); - - qosStartOverride(); - int err = pthread_rwlock_wrlock(&mLock); - if (err) _objc_fatal("pthread_rwlock_wrlock failed (%d)", err); - } - - void unlockWrite() - { - lockdebug_rwlock_unlock_write(this); - - int err = pthread_rwlock_unlock(&mLock); - if (err) _objc_fatal("pthread_rwlock_unlock failed (%d)", err); - qosEndOverride(); - } - - bool tryWrite() - { - qosStartOverride(); - int err = pthread_rwlock_trywrlock(&mLock); - if (err == 0) { - lockdebug_rwlock_try_write_success(this); - return true; - } else if (err == EBUSY) { - qosEndOverride(); - return false; - } else { - _objc_fatal("pthread_rwlock_trywrlock failed (%d)", err); - } - } - - void forceReset() - { - lockdebug_rwlock_unlock_write(this); - - bzero(&mLock, sizeof(mLock)); - mLock = pthread_rwlock_t PTHREAD_RWLOCK_INITIALIZER; - } - - - void assertReading() { - lockdebug_rwlock_assert_reading(this); - } - - void assertWriting() { - lockdebug_rwlock_assert_writing(this); - } - - void assertLocked() { - lockdebug_rwlock_assert_locked(this); - } - - void assertUnlocked() { - lockdebug_rwlock_assert_unlocked(this); - } -}; - - #ifndef __LP64__ typedef struct mach_header headerType; typedef struct segment_command segmentType; diff --git a/runtime/objc-os.mm b/runtime/objc-os.mm index b2fc8ac..9731e15 100644 --- a/runtime/objc-os.mm +++ b/runtime/objc-os.mm @@ -631,7 +631,7 @@ static bool shouldRejectGCImage(const headerType *mhdr) static void static_init() { size_t count; - Initializer *inits = getLibobjcInitializers(&_mh_dylib_header, &count); + auto inits = getLibobjcInitializers(&_mh_dylib_header, &count); for (size_t i = 0; i < count; i++) { inits[i](); } @@ -783,7 +783,7 @@ void _objc_atfork_prepare() SideTableLockAll(); classInitLock.enter(); #if __OBJC2__ - runtimeLock.write(); + runtimeLock.lock(); DemangleCacheLock.lock(); #else methodListLock.lock(); @@ -791,7 +791,7 @@ void _objc_atfork_prepare() NXUniqueStringLock.lock(); impLock.lock(); #endif - selLock.write(); + selLock.lock(); cacheUpdateLock.lock(); objcMsgLogLock.lock(); AltHandlerDebugLock.lock(); @@ -815,11 +815,11 @@ void _objc_atfork_parent() crashlog_lock.unlock(); loadMethodLock.unlock(); cacheUpdateLock.unlock(); - selLock.unlockWrite(); + selLock.unlock(); SideTableUnlockAll(); #if __OBJC2__ DemangleCacheLock.unlock(); - runtimeLock.unlockWrite(); + runtimeLock.unlock(); #else impLock.unlock(); NXUniqueStringLock.unlock(); diff --git a/runtime/objc-private.h b/runtime/objc-private.h index aac2d51..66994f6 100644 --- a/runtime/objc-private.h +++ b/runtime/objc-private.h @@ -21,8 +21,8 @@ * @APPLE_LICENSE_HEADER_END@ */ /* - * objc-private.h - * Copyright 1988-1996, NeXT Software, Inc. + * objc-private.h + * Copyright 1988-1996, NeXT Software, Inc. */ #ifndef _OBJC_PRIVATE_H_ @@ -39,6 +39,7 @@ #endif #define OBJC_TYPES_DEFINED 1 +#undef OBJC_OLD_DISPATCH_PROTOTYPES #define OBJC_OLD_DISPATCH_PROTOTYPES 0 #include // for nullptr_t @@ -55,113 +56,19 @@ namespace { struct SideTable; }; +#include "isa.h" -#if (!SUPPORT_NONPOINTER_ISA && !SUPPORT_PACKED_ISA && !SUPPORT_INDEXED_ISA) ||\ - ( SUPPORT_NONPOINTER_ISA && SUPPORT_PACKED_ISA && !SUPPORT_INDEXED_ISA) ||\ - ( SUPPORT_NONPOINTER_ISA && !SUPPORT_PACKED_ISA && SUPPORT_INDEXED_ISA) - // good config -#else -# error bad config -#endif - - -union isa_t -{ +union isa_t { isa_t() { } isa_t(uintptr_t value) : bits(value) { } Class cls; uintptr_t bits; - -#if SUPPORT_PACKED_ISA - - // extra_rc must be the MSB-most field (so it matches carry/overflow flags) - // nonpointer must be the LSB (fixme or get rid of it) - // shiftcls must occupy the same bits that a real class pointer would - // bits + RC_ONE is equivalent to extra_rc + 1 - // RC_HALF is the high bit of extra_rc (i.e. half of its range) - - // future expansion: - // uintptr_t fast_rr : 1; // no r/r overrides - // uintptr_t lock : 2; // lock for atomic property, @synch - // uintptr_t extraBytes : 1; // allocated with extra bytes - -# if __arm64__ -# define ISA_MASK 0x0000000ffffffff8ULL -# define ISA_MAGIC_MASK 0x000003f000000001ULL -# define ISA_MAGIC_VALUE 0x000001a000000001ULL - struct { - uintptr_t nonpointer : 1; - uintptr_t has_assoc : 1; - uintptr_t has_cxx_dtor : 1; - uintptr_t shiftcls : 33; // MACH_VM_MAX_ADDRESS 0x1000000000 - uintptr_t magic : 6; - uintptr_t weakly_referenced : 1; - uintptr_t deallocating : 1; - uintptr_t has_sidetable_rc : 1; - uintptr_t extra_rc : 19; -# define RC_ONE (1ULL<<45) -# define RC_HALF (1ULL<<18) - }; - -# elif __x86_64__ -# define ISA_MASK 0x00007ffffffffff8ULL -# define ISA_MAGIC_MASK 0x001f800000000001ULL -# define ISA_MAGIC_VALUE 0x001d800000000001ULL +#if defined(ISA_BITFIELD) struct { - uintptr_t nonpointer : 1; - uintptr_t has_assoc : 1; - uintptr_t has_cxx_dtor : 1; - uintptr_t shiftcls : 44; // MACH_VM_MAX_ADDRESS 0x7fffffe00000 - uintptr_t magic : 6; - uintptr_t weakly_referenced : 1; - uintptr_t deallocating : 1; - uintptr_t has_sidetable_rc : 1; - uintptr_t extra_rc : 8; -# define RC_ONE (1ULL<<56) -# define RC_HALF (1ULL<<7) + ISA_BITFIELD; // defined in isa.h }; - -# else -# error unknown architecture for packed isa -# endif - -// SUPPORT_PACKED_ISA -#endif - - -#if SUPPORT_INDEXED_ISA - -# if __ARM_ARCH_7K__ >= 2 - -# define ISA_INDEX_IS_NPI 1 -# define ISA_INDEX_MASK 0x0001FFFC -# define ISA_INDEX_SHIFT 2 -# define ISA_INDEX_BITS 15 -# define ISA_INDEX_COUNT (1 << ISA_INDEX_BITS) -# define ISA_INDEX_MAGIC_MASK 0x001E0001 -# define ISA_INDEX_MAGIC_VALUE 0x001C0001 - struct { - uintptr_t nonpointer : 1; - uintptr_t has_assoc : 1; - uintptr_t indexcls : 15; - uintptr_t magic : 4; - uintptr_t has_cxx_dtor : 1; - uintptr_t weakly_referenced : 1; - uintptr_t deallocating : 1; - uintptr_t has_sidetable_rc : 1; - uintptr_t extra_rc : 7; -# define RC_ONE (1ULL<<25) -# define RC_HALF (1ULL<<6) - }; - -# else -# error unknown architecture for indexed isa -# endif - -// SUPPORT_INDEXED_ISA #endif - }; @@ -309,6 +216,8 @@ typedef struct old_property *objc_property_t; // Private headers +#include "objc-ptrauth.h" + #if __OBJC2__ #include "objc-runtime-new.h" #else @@ -506,8 +415,6 @@ static inline bool sectnameStartsWith(const char *sectname, const char *prefix){ /* selectors */ extern void sel_init(size_t selrefCount); extern SEL sel_registerNameNoLock(const char *str, bool copy); -extern void sel_lock(void); -extern void sel_unlock(void); extern SEL SEL_load; extern SEL SEL_initialize; @@ -545,6 +452,8 @@ extern unsigned getPreoptimizedClassUnreasonableCount(); extern Class getPreoptimizedClass(const char *name); extern Class* copyPreoptimizedClasses(const char *name, int *outCount); +extern bool sharedRegionContains(const void *ptr); + extern Class _calloc_class(size_t size); /* method lookup */ @@ -611,20 +520,6 @@ class recursive_mutex_locker_t : nocopy_t { ~recursive_mutex_locker_t() { lock.unlock(); } }; -class rwlock_reader_t : nocopy_t { - rwlock_t& lock; - public: - rwlock_reader_t(rwlock_t& newLock) : lock(newLock) { lock.read(); } - ~rwlock_reader_t() { lock.unlockRead(); } -}; - -class rwlock_writer_t : nocopy_t { - rwlock_t& lock; - public: - rwlock_writer_t(rwlock_t& newLock) : lock(newLock) { lock.write(); } - ~rwlock_writer_t() { lock.unlockWrite(); } -}; - /* Exceptions */ struct alt_handler_list; @@ -719,8 +614,6 @@ extern void unmap_image(const char *path, const struct mach_header *mh); extern void unmap_image_nolock(const struct mach_header *mh); extern void _read_images(header_info **hList, uint32_t hCount, int totalClasses, int unoptimizedTotalClass); extern void _unload_image(header_info *hi); -extern const char ** _objc_copyClassNamesForImage(header_info *hi, unsigned int *outCount); - extern const header_info *_headerForClass(Class cls); @@ -772,9 +665,9 @@ __END_DECLS static __inline uint32_t _objc_strhash(const char *s) { uint32_t hash = 0; for (;;) { - int a = *s++; - if (0 == a) break; - hash += (hash << 8) + a; + int a = *s++; + if (0 == a) break; + hash += (hash << 8) + a; } return hash; } @@ -845,6 +738,7 @@ class TimeLogger { } }; +enum { CacheLineSize = 64 }; // StripedMap is a map of void* -> T, sized appropriately // for cache-friendly lock striping. @@ -852,10 +746,7 @@ class TimeLogger { // or as StripedMap where SomeStruct stores a spin lock. template class StripedMap { - - enum { CacheLineSize = 64 }; - -#if TARGET_OS_EMBEDDED +#if TARGET_OS_IPHONE && !TARGET_OS_SIMULATOR enum { StripeCount = 8 }; #else enum { StripeCount = 64 }; @@ -928,6 +819,8 @@ class StripedMap { assert(delta % CacheLineSize == 0); assert(base % CacheLineSize == 0); } +#else + constexpr StripedMap() {} #endif }; @@ -991,6 +884,40 @@ static inline bool operator != (DisguisedPtr lhs, id rhs) { } +// Storage for a thread-safe chained hook function. +// get() returns the value for calling. +// set() installs a new function and returns the old one for chaining. +// More precisely, set() writes the old value to a variable supplied by +// the caller. get() and set() use appropriate barriers so that the +// old value is safely written to the variable before the new value is +// called to use it. +// +// T1: store to old variable; store-release to hook variable +// T2: load-acquire from hook variable; call it; called hook loads old variable + +template +class ChainedHookFunction { + std::atomic hook{nil}; + +public: + ChainedHookFunction(Fn f) : hook{f} { }; + + Fn get() { + return hook.load(std::memory_order_acquire); + } + + void set(Fn newValue, Fn *oldVariable) + { + Fn oldValue = hook.load(std::memory_order_relaxed); + do { + *oldVariable = oldValue; + } while (!hook.compare_exchange_weak(oldValue, newValue, + std::memory_order_release, + std::memory_order_relaxed)); + } +}; + + // Pointer hash function. // This is not a terrific hash, but it is fast // and not outrageously flawed for our purposes. diff --git a/runtime/objc-ptrauth.h b/runtime/objc-ptrauth.h new file mode 100644 index 0000000..f766d26 --- /dev/null +++ b/runtime/objc-ptrauth.h @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2017 Apple Inc. All Rights Reserved. + * + * @APPLE_LICENSE_HEADER_START@ + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPLE_LICENSE_HEADER_END@ + */ + +#ifndef _OBJC_PTRAUTH_H_ +#define _OBJC_PTRAUTH_H_ + +#include + +// On some architectures, method lists and method caches store signed IMPs. + +// StorageSignedFunctionPointer is declared by libclosure. +#include + +// fixme simply include ptrauth.h once all build trains have it +#if __has_include () +#include +#else +#define ptrauth_strip(__value, __key) __value +#define ptrauth_blend_discriminator(__pointer, __integer) ((uintptr_t)0) +#define ptrauth_sign_constant(__value, __key, __data) __value +#define ptrauth_sign_unauthenticated(__value, __key, __data) __value +#define ptrauth_auth_and_resign(__value, __old_key, __old_data, __new_key, __new_data) __value +#define ptrauth_auth_function(__value, __old_key, __old_data) __value +#define ptrauth_auth_data(__value, __old_key, __old_data) __value +#define ptrauth_string_discriminator(__string) ((int)0) +#define ptrauth_sign_generic_data(__value, __data) ((ptrauth_generic_signature_t)0) + +#define __ptrauth_function_pointer +#define __ptrauth_return_address +#define __ptrauth_block_invocation_pointer +#define __ptrauth_block_copy_helper +#define __ptrauth_block_destroy_helper +#define __ptrauth_block_byref_copy_helper +#define __ptrauth_block_byref_destroy_helper +#define __ptrauth_objc_method_list_imp +#define __ptrauth_cxx_vtable_pointer +#define __ptrauth_cxx_vtt_vtable_pointer +#define __ptrauth_swift_heap_object_destructor +#define __ptrauth_cxx_virtual_function_pointer(__declkey) +#define __ptrauth_swift_function_pointer(__typekey) +#define __ptrauth_swift_class_method_pointer(__declkey) +#define __ptrauth_swift_protocol_witness_function_pointer(__declkey) +#define __ptrauth_swift_value_witness_function_pointer(__key) +#endif + + +#if __has_feature(ptrauth_calls) + +// Method lists use process-independent signature for compatibility. +// Method caches use process-dependent signature for extra protection. +// (fixme not yet __ptrauth(...) because of `stp` inline asm in objc-cache.mm) +using MethodListIMP = IMP __ptrauth_objc_method_list_imp; +using MethodCacheIMP = + StorageSignedFunctionPointer; + +#else + +using MethodListIMP = IMP; +using MethodCacheIMP = IMP; + +#endif + +// _OBJC_PTRAUTH_H_ +#endif diff --git a/runtime/objc-runtime-new.h b/runtime/objc-runtime-new.h index dff7c10..04a4c65 100644 --- a/runtime/objc-runtime-new.h +++ b/runtime/objc-runtime-new.h @@ -36,8 +36,15 @@ struct swift_class_t; struct bucket_t { private: + // IMP-first is better for arm64e ptrauth and no worse for arm64. + // SEL-first is better for armv7* and i386 and x86_64. +#if __arm64__ + MethodCacheIMP _imp; cache_key_t _key; - IMP _imp; +#else + cache_key_t _key; + MethodCacheIMP _imp; +#endif public: inline cache_key_t key() const { return _key; } @@ -112,11 +119,19 @@ struct entsize_list_tt { } size_t byteSize() const { - return sizeof(*this) + (count-1)*entsize(); + return byteSize(entsize(), count); + } + + static size_t byteSize(uint32_t entsize, uint32_t count) { + return sizeof(entsize_list_tt) + (count-1)*entsize; } List *duplicate() const { - return (List *)memdup(this, this->byteSize()); + auto *dup = (List *)calloc(this->byteSize(), 1); + dup->entsizeAndFlags = this->entsizeAndFlags; + dup->count = this->count; + std::copy(begin(), end(), dup->begin()); + return dup; } struct iterator; @@ -207,7 +222,7 @@ struct entsize_list_tt { struct method_t { SEL name; const char *types; - IMP imp; + MethodListIMP imp; struct SortBySELAddress : public std::binary_functionhasCxxDtor to save an instruction. -#define FAST_HAS_CXX_DTOR (1UL<<2) +// class is a Swift class from the pre-stable Swift ABI +#define FAST_IS_SWIFT_LEGACY (1UL<<0) +// class is a Swift class from the stable Swift ABI +#define FAST_IS_SWIFT_STABLE (1UL<<1) +// summary bit for fast alloc path: !hasCxxCtor and +// !instancesRequireRawIsa and instanceSize fits into shiftedSize +#define FAST_ALLOC (1UL<<2) // data pointer #define FAST_DATA_MASK 0x00007ffffffffff8UL // class or superclass has .cxx_construct implementation @@ -505,13 +524,15 @@ struct locstamped_category_list_t { // class or superclass has default retain/release/autorelease/retainCount/ // _tryRetain/_isDeallocating/retainWeakReference/allowsWeakReference #define FAST_HAS_DEFAULT_RR (1UL<<49) -// summary bit for fast alloc path: !hasCxxCtor and -// !instancesRequireRawIsa and instanceSize fits into shiftedSize -#define FAST_ALLOC (1UL<<50) +// class's instances requires raw isa +// This bit is aligned with isa_t->hasCxxDtor to save an instruction. +#define FAST_REQUIRES_RAW_ISA (1UL<<50) +// class or superclass has .cxx_destruct implementation +#define FAST_HAS_CXX_DTOR (1UL<<51) // instance size in units of 16 bytes // or 0 if the instance size is too big in this field // This field must be LAST -#define FAST_SHIFTED_SIZE_SHIFT 51 +#define FAST_SHIFTED_SIZE_SHIFT 52 // FAST_ALLOC means // FAST_HAS_CXX_CTOR is set @@ -524,6 +545,10 @@ struct locstamped_category_list_t { #endif +// The Swift ABI requires that these bits be defined like this on all platforms. +static_assert(FAST_IS_SWIFT_LEGACY == 1, "resistance is futile"); +static_assert(FAST_IS_SWIFT_STABLE == 2, "resistance is futile"); + struct class_ro_t { uint32_t flags; @@ -908,6 +933,7 @@ struct class_data_bits_t { bits = newBits; } +#if FAST_HAS_DEFAULT_RR bool hasDefaultRR() { return getBit(FAST_HAS_DEFAULT_RR); } @@ -917,6 +943,17 @@ struct class_data_bits_t { void setHasCustomRR() { clearBits(FAST_HAS_DEFAULT_RR); } +#else + bool hasDefaultRR() { + return data()->flags & RW_HAS_DEFAULT_RR; + } + void setHasDefaultRR() { + data()->setFlags(RW_HAS_DEFAULT_RR); + } + void setHasCustomRR() { + data()->clearFlags(RW_HAS_DEFAULT_RR); + } +#endif #if FAST_HAS_DEFAULT_AWZ bool hasDefaultAWZ() { @@ -1051,12 +1088,22 @@ struct class_data_bits_t { #endif } - bool isSwift() { - return getBit(FAST_IS_SWIFT); + bool isAnySwift() { + return isSwiftStable() || isSwiftLegacy(); } - void setIsSwift() { - setBits(FAST_IS_SWIFT); + bool isSwiftStable() { + return getBit(FAST_IS_SWIFT_STABLE); + } + void setIsSwiftStable() { + setBits(FAST_IS_SWIFT_STABLE); + } + + bool isSwiftLegacy() { + return getBit(FAST_IS_SWIFT_LEGACY); + } + void setIsSwiftLegacy() { + setBits(FAST_IS_SWIFT_LEGACY); } }; @@ -1146,8 +1193,16 @@ struct objc_class : objc_object { } - bool isSwift() { - return bits.isSwift(); + bool isSwiftStable() { + return bits.isSwiftStable(); + } + + bool isSwiftLegacy() { + return bits.isSwiftLegacy(); + } + + bool isAnySwift() { + return bits.isAnySwift(); } @@ -1358,7 +1413,7 @@ static inline void foreach_realized_class_and_subclass_2(Class top, unsigned& count, std::function code) { - // runtimeLock.assertWriting(); + // runtimeLock.assertLocked(); assert(top); Class cls = top; while (1) { diff --git a/runtime/objc-runtime-new.mm b/runtime/objc-runtime-new.mm index 8ad820c..b52aaa0 100644 --- a/runtime/objc-runtime-new.mm +++ b/runtime/objc-runtime-new.mm @@ -53,6 +53,7 @@ static void updateCustomRR_AWZ(Class cls, method_t *meth); static method_t *search_method_list(const method_list_t *mlist, SEL sel); static void flushCaches(Class cls); +static void initializeTaggedPointerObfuscator(void); #if SUPPORT_FIXUP static void fixupMessageRef(message_ref_t *msg); #endif @@ -64,31 +65,13 @@ /*********************************************************************** * Lock management **********************************************************************/ -rwlock_t runtimeLock; -rwlock_t selLock; +mutex_t runtimeLock; +mutex_t selLock; mutex_t cacheUpdateLock; recursive_mutex_t loadMethodLock; -#if SUPPORT_QOS_HACK -pthread_priority_t BackgroundPriority = 0; -pthread_priority_t MainPriority = 0; -# if DEBUG -static __unused void destroyQOSKey(void *arg) { - _objc_fatal("QoS override level at thread exit is %zu instead of zero", - (size_t)(uintptr_t)arg); -} -# endif -#endif - void lock_init(void) { -#if SUPPORT_QOS_HACK - BackgroundPriority = _pthread_qos_class_encode(QOS_CLASS_BACKGROUND, 0, 0); - MainPriority = _pthread_qos_class_encode(qos_class_main(), 0, 0); -# if DEBUG - pthread_key_init_np(QOS_KEY, &destroyQOSKey); -# endif -#endif } @@ -191,6 +174,14 @@ void lock_init(void) #endif +/*********************************************************************** +* allocatedClasses +* A table of all classes (and metaclasses) which have been allocated +* with objc_allocateClassPair. +**********************************************************************/ +static NXHashTable *allocatedClasses = nil; + + typedef locstamped_category_list_t category_list; @@ -231,7 +222,7 @@ void lock_init(void) } void method_list_t::setFixedUp() { - runtimeLock.assertWriting(); + runtimeLock.assertLocked(); assert(!isFixedUp()); entsizeAndFlags = entsize() | fixed_up_method_list; } @@ -241,7 +232,7 @@ void lock_init(void) } void protocol_t::setFixedUp() { - runtimeLock.assertWriting(); + runtimeLock.assertLocked(); assert(!isFixedUp()); flags = (flags & ~PROTOCOL_FIXED_UP_MASK) | fixed_up_protocol; } @@ -282,10 +273,18 @@ static void try_free(const void *p) } +static void (*classCopyFixupHandler)(Class _Nonnull oldClass, + Class _Nonnull newClass); + +void _objc_setClassCopyFixupHandler(void (* _Nonnull newFixupHandler) + (Class _Nonnull oldClass, Class _Nonnull newClass)) { + classCopyFixupHandler = newFixupHandler; +} + static Class alloc_class_for_subclass(Class supercls, size_t extraBytes) { - if (!supercls || !supercls->isSwift()) { + if (!supercls || !supercls->isAnySwift()) { return _calloc_class(sizeof(objc_class) + extraBytes); } @@ -307,8 +306,17 @@ static void try_free(const void *p) bzero(swcls, sizeof(objc_class)); swcls->description = nil; + if (classCopyFixupHandler) { + classCopyFixupHandler(supercls, (Class)swcls); + } + // Mark this class as Swift-enhanced. - swcls->bits.setIsSwift(); + if (supercls->isSwiftStable()) { + swcls->bits.setIsSwiftStable(); + } + if (supercls->isSwiftLegacy()) { + swcls->bits.setIsSwiftLegacy(); + } return (Class)swcls; } @@ -327,7 +335,7 @@ static void try_free(const void *p) if (!obj->isClass()) return base + obj->ISA()->alignedInstanceSize(); Class cls = (Class)obj; - if (!cls->isSwift()) return base + sizeof(objc_class); + if (!cls->isAnySwift()) return base + sizeof(objc_class); swift_class_t *swcls = (swift_class_t *)cls; return base - swcls->classAddressOffset + word_align(swcls->classSize); @@ -341,7 +349,7 @@ static void try_free(const void *p) **********************************************************************/ static class_ro_t *make_ro_writeable(class_rw_t *rw) { - runtimeLock.assertWriting(); + runtimeLock.assertLocked(); if (rw->flags & RW_COPIED_RO) { // already writeable, do nothing @@ -362,7 +370,7 @@ static void try_free(const void *p) **********************************************************************/ static NXMapTable *unattachedCategories(void) { - runtimeLock.assertWriting(); + runtimeLock.assertLocked(); static NXMapTable *category_map = nil; @@ -375,6 +383,139 @@ static void try_free(const void *p) } +/*********************************************************************** +* dataSegmentsContain +* Returns true if the given address lies within a data segment in any +* loaded image. +* +* This is optimized for use where the return value is expected to be +* true. A call where the return value is false always results in a +* slow linear search of all loaded images. A call where the return +* value is fast will often be fast due to caching. +**********************************************************************/ +static bool dataSegmentsContain(const void *ptr) { + struct Range { + uintptr_t start, end; + bool contains(uintptr_t ptr) { + return start <= ptr && ptr <= end; + } + }; + + // This is a really simple linear searched cache. On a cache hit, + // the hit entry is moved to the front of the array. On a cache + // miss where a range is successfully found on the slow path, the + // found range is inserted at the beginning of the cache. This gives + // us fast access to the most recently used elements, and LRU + // eviction. + enum { cacheCount = 16 }; + static Range cache[cacheCount]; + + uintptr_t addr = (uintptr_t)ptr; + + // Special case a hit on the first entry of the cache. No + // bookkeeping is required at all in this case. + if (cache[0].contains(addr)) { + return true; + } + + // Search the rest of the cache. + for (unsigned i = 1; i < cacheCount; i++) { + if (cache[i].contains(addr)) { + // Cache hit. Move all preceding entries down one element, + // then place this entry at the front. + Range r = cache[i]; + memmove(&cache[1], &cache[0], i * sizeof(cache[0])); + cache[0] = r; + return true; + } + } + + // Cache miss. Find the image header containing the given address. + // If there isn't one, then we're definitely not in any image, + // so return false. + Range found = { 0, 0 }; + auto *h = (headerType *)dyld_image_header_containing_address(ptr); + if (h == nullptr) + return false; + + // Iterate over the data segments in the found image. If the address + // lies within one, note the data segment range in `found`. + // TODO: this is more work than we'd like to do. All we really need + // is the full range of the image. Addresses within the TEXT segment + // would also be acceptable for our use case. If possible, we should + // change this to work with the full address range of the found + // image header. Another possibility would be to use the range + // from `h` to the end of the page containing `addr`. + foreach_data_segment(h, [&](const segmentType *seg, intptr_t slide) { + Range r; + r.start = seg->vmaddr + slide; + r.end = r.start + seg->vmsize; + if (r.contains(addr)) + found = r; + }); + + if (found.start != 0) { + memmove(&cache[1], &cache[0], (cacheCount - 1) * sizeof(cache[0])); + cache[0] = found; + return true; + } + + return false; +} + + +/*********************************************************************** +* isKnownClass +* Return true if the class is known to the runtime (located within the +* shared cache, within the data segment of a loaded image, or has been +* allocated with obj_allocateClassPair). +**********************************************************************/ +static bool isKnownClass(Class cls) { + // The order of conditionals here is important for speed. We want to + // put the most common cases first, but also the fastest cases + // first. Checking the shared region is both fast and common. + // Checking allocatedClasses is fast, but may not be common, + // depending on what the program is doing. Checking if data segments + // contain the address is slow, so do it last. + return (sharedRegionContains(cls) || + NXHashMember(allocatedClasses, cls) || + dataSegmentsContain(cls)); +} + + +/*********************************************************************** +* addClassTableEntry +* Add a class to the table of all classes. If addMeta is true, +* automatically adds the metaclass of the class as well. +* Locking: runtimeLock must be held by the caller. +**********************************************************************/ +static void addClassTableEntry(Class cls, bool addMeta = true) { + runtimeLock.assertLocked(); + + // This class is allowed to be a known class via the shared cache or via + // data segments, but it is not allowed to be in the dynamic table already. + assert(!NXHashMember(allocatedClasses, cls)); + + if (!isKnownClass(cls)) + NXHashInsert(allocatedClasses, cls); + if (addMeta) + addClassTableEntry(cls->ISA(), false); +} + + +/*********************************************************************** +* checkIsKnownClass +* Checks the given class against the list of all known classes. Dies +* with a fatal error if the class is not known. +* Locking: runtimeLock must be held by the caller. +**********************************************************************/ +static void checkIsKnownClass(Class cls) +{ + if (!isKnownClass(cls)) + _objc_fatal("Attempt to use unknown class %p.", cls); +} + + /*********************************************************************** * addUnattachedCategoryForClass * Records an unattached category. @@ -383,7 +524,7 @@ static void try_free(const void *p) static void addUnattachedCategoryForClass(category_t *cat, Class cls, header_info *catHeader) { - runtimeLock.assertWriting(); + runtimeLock.assertLocked(); // DO NOT use cat->cls! cls may be cat->cls->isa instead NXMapTable *cats = unattachedCategories(); @@ -409,7 +550,7 @@ static void addUnattachedCategoryForClass(category_t *cat, Class cls, **********************************************************************/ static void removeUnattachedCategoryForClass(category_t *cat, Class cls) { - runtimeLock.assertWriting(); + runtimeLock.assertLocked(); // DO NOT use cat->cls! cls may be cat->cls->isa instead NXMapTable *cats = unattachedCategories(); @@ -441,7 +582,7 @@ static void removeUnattachedCategoryForClass(category_t *cat, Class cls) static category_list * unattachedCategoriesForClass(Class cls, bool realizing) { - runtimeLock.assertWriting(); + runtimeLock.assertLocked(); return (category_list *)NXMapRemove(unattachedCategories(), cls); } @@ -453,7 +594,7 @@ static void removeUnattachedCategoryForClass(category_t *cat, Class cls) **********************************************************************/ static void removeAllUnattachedCategoriesForClass(Class cls) { - runtimeLock.assertWriting(); + runtimeLock.assertLocked(); void *list = NXMapRemove(unattachedCategories(), cls); if (list) free(list); @@ -544,19 +685,19 @@ static bool isBundleClass(Class cls) static void fixupMethodList(method_list_t *mlist, bool bundleCopy, bool sort) { - runtimeLock.assertWriting(); + runtimeLock.assertLocked(); assert(!mlist->isFixedUp()); // fixme lock less in attachMethodLists ? - sel_lock(); + { + mutex_locker_t lock(selLock); - // Unique selectors in list. - for (auto& meth : *mlist) { - const char *name = sel_cname(meth.name); - meth.name = sel_registerNameNoLock(name, bundleCopy); + // Unique selectors in list. + for (auto& meth : *mlist) { + const char *name = sel_cname(meth.name); + meth.name = sel_registerNameNoLock(name, bundleCopy); + } } - - sel_unlock(); // Sort by selector address. if (sort) { @@ -573,7 +714,7 @@ static bool isBundleClass(Class cls) prepareMethodLists(Class cls, method_list_t **addedLists, int addedCount, bool baseMethods, bool methodsFromBundle) { - runtimeLock.assertWriting(); + runtimeLock.assertLocked(); if (addedCount == 0) return; @@ -684,7 +825,7 @@ static bool isBundleClass(Class cls) **********************************************************************/ static void methodizeClass(Class cls) { - runtimeLock.assertWriting(); + runtimeLock.assertLocked(); bool isMeta = cls->isMetaClass(); auto rw = cls->data(); @@ -761,7 +902,7 @@ static void remethodizeClass(Class cls) category_list *cats; bool isMeta; - runtimeLock.assertWriting(); + runtimeLock.assertLocked(); isMeta = cls->isMetaClass(); @@ -815,7 +956,7 @@ static void remethodizeClass(Class cls) **********************************************************************/ static void addNonMetaClass(Class cls) { - runtimeLock.assertWriting(); + runtimeLock.assertLocked(); void *old; old = NXMapInsert(nonMetaClasses(), cls->ISA(), cls); @@ -827,7 +968,7 @@ static void addNonMetaClass(Class cls) static void removeNonMetaClass(Class cls) { - runtimeLock.assertWriting(); + runtimeLock.assertLocked(); NXMapRemove(nonMetaClasses(), cls->ISA()); } @@ -1001,7 +1142,7 @@ static Class getClass(const char *name) **********************************************************************/ static void addNamedClass(Class cls, const char *name, Class replacing = nil) { - runtimeLock.assertWriting(); + runtimeLock.assertLocked(); Class old; if ((old = getClass(name)) && old != replacing) { inform_duplicate(name, old, cls); @@ -1026,7 +1167,7 @@ static void addNamedClass(Class cls, const char *name, Class replacing = nil) **********************************************************************/ static void removeNamedClass(Class cls, const char *name) { - runtimeLock.assertWriting(); + runtimeLock.assertLocked(); assert(!(cls->data()->flags & RO_META)); if (cls == NXMapGet(gdb_objc_realized_classes, name)) { NXMapRemove(gdb_objc_realized_classes, name); @@ -1065,7 +1206,7 @@ unsigned unreasonableClassCount() static NXMapTable *future_named_class_map = nil; static NXMapTable *futureNamedClasses() { - runtimeLock.assertWriting(); + runtimeLock.assertLocked(); if (future_named_class_map) return future_named_class_map; @@ -1091,7 +1232,7 @@ static void addFutureNamedClass(const char *name, Class cls) { void *old; - runtimeLock.assertWriting(); + runtimeLock.assertLocked(); if (PrintFuture) { _objc_inform("FUTURE: reserving %p for %s", (void*)cls, name); @@ -1118,7 +1259,7 @@ static void addFutureNamedClass(const char *name, Class cls) **********************************************************************/ static Class popFutureNamedClass(const char *name) { - runtimeLock.assertWriting(); + runtimeLock.assertLocked(); Class cls = nil; @@ -1185,7 +1326,7 @@ static bool noClassesRemapped(void) **********************************************************************/ static void addRemappedClass(Class oldcls, Class newcls) { - runtimeLock.assertWriting(); + runtimeLock.assertLocked(); if (PrintFuture) { _objc_inform("FUTURE: using %p instead of %p for %s", @@ -1228,7 +1369,7 @@ static Class remapClass(classref_t cls) Class _class_remap(Class cls) { - rwlock_reader_t lock(runtimeLock); + mutex_locker_t lock(runtimeLock); return remapClass(cls); } @@ -1374,7 +1515,7 @@ static Class getNonMetaClass(Class metacls, id inst) **********************************************************************/ Class _class_getNonMetaClass(Class cls, id obj) { - rwlock_writer_t lock(runtimeLock); + mutex_locker_t lock(runtimeLock); cls = getNonMetaClass(cls, obj); assert(cls->isRealized()); return cls; @@ -1395,7 +1536,7 @@ Class firstRealizedClass() static void addRootClass(Class cls) { - runtimeLock.assertWriting(); + runtimeLock.assertLocked(); assert(cls->isRealized()); cls->data()->nextSiblingClass = _firstRealizedClass; @@ -1404,7 +1545,7 @@ static void addRootClass(Class cls) static void removeRootClass(Class cls) { - runtimeLock.assertWriting(); + runtimeLock.assertLocked(); Class *classp; for (classp = &_firstRealizedClass; @@ -1423,7 +1564,7 @@ static void removeRootClass(Class cls) **********************************************************************/ static void addSubclass(Class supercls, Class subcls) { - runtimeLock.assertWriting(); + runtimeLock.assertLocked(); if (supercls && subcls) { assert(supercls->isRealized()); @@ -1463,7 +1604,7 @@ static void addSubclass(Class supercls, Class subcls) **********************************************************************/ static void removeSubclass(Class supercls, Class subcls) { - runtimeLock.assertWriting(); + runtimeLock.assertLocked(); assert(supercls->isRealized()); assert(subcls->isRealized()); assert(subcls->superclass == supercls); @@ -1564,7 +1705,7 @@ static void remapProtocolRef(protocol_t **protoref) **********************************************************************/ static void moveIvars(class_ro_t *ro, uint32_t superSize) { - runtimeLock.assertWriting(); + runtimeLock.assertLocked(); uint32_t diff; @@ -1716,7 +1857,7 @@ static void reconcileInstanceVariables(Class cls, Class supercls, const class_ro **********************************************************************/ static Class realizeClass(Class cls) { - runtimeLock.assertWriting(); + runtimeLock.assertLocked(); const class_ro_t *ro; class_rw_t *rw; @@ -1864,7 +2005,7 @@ static Class realizeClass(Class cls) **********************************************************************/ static void realizeAllClassesInImage(header_info *hi) { - runtimeLock.assertWriting(); + runtimeLock.assertLocked(); size_t count, i; classref_t *classlist; @@ -1888,7 +2029,7 @@ static void realizeAllClassesInImage(header_info *hi) **********************************************************************/ static void realizeAllClasses(void) { - runtimeLock.assertWriting(); + runtimeLock.assertLocked(); header_info *hi; for (hi = FirstHeader; hi; hi = hi->getNext()) { @@ -1906,7 +2047,7 @@ static void realizeAllClasses(void) **********************************************************************/ Class _objc_allocateFutureClass(const char *name) { - rwlock_writer_t lock(runtimeLock); + mutex_locker_t lock(runtimeLock); Class cls; NXMapTable *map = futureNamedClasses(); @@ -1968,7 +2109,7 @@ BOOL _class_isFutureClass(Class cls) **********************************************************************/ static void flushCaches(Class cls) { - runtimeLock.assertWriting(); + runtimeLock.assertLocked(); mutex_locker_t lock(cacheUpdateLock); @@ -1988,7 +2129,7 @@ static void flushCaches(Class cls) void _objc_flush_caches(Class cls) { { - rwlock_writer_t lock(runtimeLock); + mutex_locker_t lock(runtimeLock); flushCaches(cls); if (cls && cls->superclass && cls != cls->getIsa()) { flushCaches(cls->getIsa()); @@ -2017,7 +2158,7 @@ void _objc_flush_caches(Class cls) map_images(unsigned count, const char * const paths[], const struct mach_header * const mhdrs[]) { - rwlock_writer_t lock(runtimeLock); + mutex_locker_t lock(runtimeLock); return map_images_nolock(count, paths, mhdrs); } @@ -2041,7 +2182,7 @@ void _objc_flush_caches(Class cls) // Discover load methods { - rwlock_writer_t lock2(runtimeLock); + mutex_locker_t lock2(runtimeLock); prepare_load_methods((const headerType *)mh); } @@ -2060,7 +2201,7 @@ void _objc_flush_caches(Class cls) unmap_image(const char *path __unused, const struct mach_header *mh) { recursive_mutex_locker_t lock(loadMethodLock); - rwlock_writer_t lock2(runtimeLock); + mutex_locker_t lock2(runtimeLock); unmap_image_nolock(mh); } @@ -2165,7 +2306,7 @@ Class readClass(Class cls, bool headerIsBundle, bool headerIsPreoptimized) // Copy objc_class to future class's struct. // Preserve future's rw data block. - if (newCls->isSwift()) { + if (newCls->isAnySwift()) { _objc_fatal("Can't complete future class request for '%s' " "because the real class is too big.", cls->nameForLogging()); @@ -2192,8 +2333,9 @@ Class readClass(Class cls, bool headerIsBundle, bool headerIsPreoptimized) assert(getClass(mangledName)); } else { addNamedClass(cls, mangledName, replacing); + addClassTableEntry(cls); } - + // for future reference: shared cache never contains MH_BUNDLEs if (headerIsBundle) { cls->data()->flags |= RO_FROM_BUNDLE; @@ -2314,7 +2456,7 @@ void _read_images(header_info **hList, uint32_t hCount, int totalClasses, int un static bool doneOnce; TimeLogger ts(PrintImageTimes); - runtimeLock.assertWriting(); + runtimeLock.assertLocked(); #define EACH_HEADER \ hIndex = 0; \ @@ -2378,6 +2520,8 @@ void _read_images(header_info **hList, uint32_t hCount, int totalClasses, int un disableTaggedPointers(); } + initializeTaggedPointerObfuscator(); + if (PrintConnecting) { _objc_inform("CLASS: found %d classes during launch", totalClasses); } @@ -2389,7 +2533,9 @@ void _read_images(header_info **hList, uint32_t hCount, int totalClasses, int un (isPreoptimized() ? unoptimizedTotalClasses : totalClasses) * 4 / 3; gdb_objc_realized_classes = NXCreateMapTable(NXStrValueMapPrototype, namedClassesSize); - + + allocatedClasses = NXCreateHashTable(NXPtrPrototype, 0, nil); + ts.log("IMAGE TIMES: first time tasks"); } @@ -2397,6 +2543,8 @@ void _read_images(header_info **hList, uint32_t hCount, int totalClasses, int un // Discover classes. Fix up unresolved future classes. Mark bundle classes. for (EACH_HEADER) { + classref_t *classlist = _getObjc2ClassList(hi, &count); + if (! mustReadClasses(hi)) { // Image is sufficiently optimized that we need not call readClass() continue; @@ -2405,7 +2553,6 @@ void _read_images(header_info **hList, uint32_t hCount, int totalClasses, int un bool headerIsBundle = hi->isBundle(); bool headerIsPreoptimized = hi->isPreoptimized(); - classref_t *classlist = _getObjc2ClassList(hi, &count); for (i = 0; i < count; i++) { Class cls = (Class)classlist[i]; Class newCls = readClass(cls, headerIsBundle, headerIsPreoptimized); @@ -2446,19 +2593,20 @@ void _read_images(header_info **hList, uint32_t hCount, int totalClasses, int un // Fix up @selector references static size_t UnfixedSelectors; - sel_lock(); - for (EACH_HEADER) { - if (hi->isPreoptimized()) continue; - - bool isBundle = hi->isBundle(); - SEL *sels = _getObjc2SelectorRefs(hi, &count); - UnfixedSelectors += count; - for (i = 0; i < count; i++) { - const char *name = sel_cname(sels[i]); - sels[i] = sel_registerNameNoLock(name, isBundle); + { + mutex_locker_t lock(selLock); + for (EACH_HEADER) { + if (hi->isPreoptimized()) continue; + + bool isBundle = hi->isBundle(); + SEL *sels = _getObjc2SelectorRefs(hi, &count); + UnfixedSelectors += count; + for (i = 0; i < count; i++) { + const char *name = sel_cname(sels[i]); + sels[i] = sel_registerNameNoLock(name, isBundle); + } } } - sel_unlock(); ts.log("IMAGE TIMES: fix up selector references"); @@ -2533,7 +2681,8 @@ void _read_images(header_info **hList, uint32_t hCount, int totalClasses, int un cls->ISA()->cache._occupied = 0; } #endif - + + addClassTableEntry(cls); realizeClass(cls); } } @@ -2718,7 +2867,7 @@ void prepare_load_methods(const headerType *mhdr) { size_t count, i; - runtimeLock.assertWriting(); + runtimeLock.assertLocked(); classref_t *classlist = _getObjc2NonlazyClassList(mhdr, &count); @@ -2748,7 +2897,7 @@ void _unload_image(header_info *hi) size_t count, i; loadMethodLock.assertLocked(); - runtimeLock.assertWriting(); + runtimeLock.assertLocked(); // Unload unattached categories and categories waiting for +load. @@ -2875,7 +3024,7 @@ void _unload_image(header_info *hi) static IMP _method_setImplementation(Class cls, method_t *m, IMP imp) { - runtimeLock.assertWriting(); + runtimeLock.assertLocked(); if (!m) return nil; if (!imp) return nil; @@ -2899,7 +3048,7 @@ void _unload_image(header_info *hi) { // Don't know the class - will be slow if RR/AWZ are affected // fixme build list of classes whose Methods are known externally? - rwlock_writer_t lock(runtimeLock); + mutex_locker_t lock(runtimeLock); return _method_setImplementation(Nil, m, imp); } @@ -2908,7 +3057,7 @@ void method_exchangeImplementations(Method m1, Method m2) { if (!m1 || !m2) return; - rwlock_writer_t lock(runtimeLock); + mutex_locker_t lock(runtimeLock); IMP m1_imp = m1->imp; m1->imp = m2->imp; @@ -2984,7 +3133,7 @@ void method_exchangeImplementations(Method m1, Method m2) return nil; } - rwlock_reader_t lock(runtimeLock); + mutex_locker_t lock(runtimeLock); return copyPropertyAttributeList(prop->attributes,outCount); } @@ -2992,7 +3141,7 @@ void method_exchangeImplementations(Method m1, Method m2) { if (!prop || !name || *name == '\0') return nil; - rwlock_reader_t lock(runtimeLock); + mutex_locker_t lock(runtimeLock); return copyPropertyAttributeValue(prop->attributes, name); } @@ -3064,7 +3213,7 @@ static uint32_t getExtendedTypesIndexForMethod(protocol_t *proto, const method_t fixupProtocolMethodList(protocol_t *proto, method_list_t *mlist, bool required, bool instance) { - runtimeLock.assertWriting(); + runtimeLock.assertLocked(); if (!mlist) return; if (mlist->isFixedUp()) return; @@ -3103,7 +3252,7 @@ static uint32_t getExtendedTypesIndexForMethod(protocol_t *proto, const method_t static void fixupProtocol(protocol_t *proto) { - runtimeLock.assertWriting(); + runtimeLock.assertLocked(); if (proto->protocols) { for (uintptr_t i = 0; i < proto->protocols->count; i++) { @@ -3134,7 +3283,7 @@ static uint32_t getExtendedTypesIndexForMethod(protocol_t *proto, const method_t assert(proto); if (!proto->isFixedUp()) { - rwlock_writer_t lock(runtimeLock); + mutex_locker_t lock(runtimeLock); fixupProtocol(proto); } } @@ -3210,7 +3359,7 @@ static uint32_t getExtendedTypesIndexForMethod(protocol_t *proto, const method_t if (!proto) return nil; fixupProtocolIfNeeded(proto); - rwlock_reader_t lock(runtimeLock); + mutex_locker_t lock(runtimeLock); return protocol_getMethod_nolock(proto, sel, isRequiredMethod, isInstanceMethod, recursive); } @@ -3220,7 +3369,7 @@ static uint32_t getExtendedTypesIndexForMethod(protocol_t *proto, const method_t * protocol_getMethodTypeEncoding_nolock * Return the @encode string for the requested protocol method. * Returns nil if the compiler did not emit any extended @encode data. -* Locking: runtimeLock must be held for writing by the caller +* Locking: runtimeLock must be held by the caller **********************************************************************/ const char * protocol_getMethodTypeEncoding_nolock(protocol_t *proto, SEL sel, @@ -3271,7 +3420,7 @@ static uint32_t getExtendedTypesIndexForMethod(protocol_t *proto, const method_t if (!proto) return nil; fixupProtocolIfNeeded(proto); - rwlock_reader_t lock(runtimeLock); + mutex_locker_t lock(runtimeLock); return protocol_getMethodTypeEncoding_nolock(proto, sel, isRequiredMethod, isInstanceMethod); @@ -3373,7 +3522,7 @@ static uint32_t getExtendedTypesIndexForMethod(protocol_t *proto, const method_t **********************************************************************/ BOOL protocol_conformsToProtocol(Protocol *self, Protocol *other) { - rwlock_reader_t lock(runtimeLock); + mutex_locker_t lock(runtimeLock); return protocol_conformsToProtocol_nolock(newprotocol(self), newprotocol(other)); } @@ -3417,7 +3566,7 @@ BOOL protocol_isEqual(Protocol *self, Protocol *other) fixupProtocolIfNeeded(proto); - rwlock_reader_t lock(runtimeLock); + mutex_locker_t lock(runtimeLock); method_list_t *mlist = getProtocolMethodList(proto, isRequiredMethod, isInstanceMethod); @@ -3483,7 +3632,7 @@ objc_property_t protocol_getProperty(Protocol *p, const char *name, { if (!p || !name) return nil; - rwlock_reader_t lock(runtimeLock); + mutex_locker_t lock(runtimeLock); return (objc_property_t) protocol_getProperty_nolock(newprotocol(p), name, isRequiredProperty, isInstanceProperty); @@ -3530,7 +3679,7 @@ objc_property_t protocol_getProperty(Protocol *p, const char *name, return nil; } - rwlock_reader_t lock(runtimeLock); + mutex_locker_t lock(runtimeLock); property_list_t *plist = isInstanceProperty ? newprotocol(proto)->instanceProperties @@ -3564,7 +3713,7 @@ objc_property_t protocol_getProperty(Protocol *p, const char *name, return nil; } - rwlock_reader_t lock(runtimeLock); + mutex_locker_t lock(runtimeLock); if (proto->protocols) { count = (unsigned int)proto->protocols->count; @@ -3594,7 +3743,7 @@ objc_property_t protocol_getProperty(Protocol *p, const char *name, Protocol * objc_allocateProtocol(const char *name) { - rwlock_writer_t lock(runtimeLock); + mutex_locker_t lock(runtimeLock); if (getProtocol(name)) { return nil; @@ -3625,7 +3774,7 @@ void objc_registerProtocol(Protocol *proto_gen) { protocol_t *proto = newprotocol(proto_gen); - rwlock_writer_t lock(runtimeLock); + mutex_locker_t lock(runtimeLock); extern objc_class OBJC_CLASS_$___IncompleteProtocol; Class oldcls = (Class)&OBJC_CLASS_$___IncompleteProtocol; @@ -3670,7 +3819,7 @@ void objc_registerProtocol(Protocol *proto_gen) if (!proto_gen) return; if (!addition_gen) return; - rwlock_writer_t lock(runtimeLock); + mutex_locker_t lock(runtimeLock); if (proto->ISA() != cls) { _objc_inform("protocol_addProtocol: modified protocol '%s' is not " @@ -3733,7 +3882,7 @@ void objc_registerProtocol(Protocol *proto_gen) if (!proto_gen) return; - rwlock_writer_t lock(runtimeLock); + mutex_locker_t lock(runtimeLock); if (proto->ISA() != cls) { _objc_inform("protocol_addMethodDescription: protocol '%s' is not " @@ -3791,7 +3940,7 @@ void objc_registerProtocol(Protocol *proto_gen) if (!proto) return; if (!name) return; - rwlock_writer_t lock(runtimeLock); + mutex_locker_t lock(runtimeLock); if (proto->ISA() != cls) { _objc_inform("protocol_addProperty: protocol '%s' is not " @@ -3823,7 +3972,7 @@ void objc_registerProtocol(Protocol *proto_gen) int objc_getClassList(Class *buffer, int bufferLen) { - rwlock_writer_t lock(runtimeLock); + mutex_locker_t lock(runtimeLock); realizeAllClasses(); @@ -3858,7 +4007,7 @@ void objc_registerProtocol(Protocol *proto_gen) Class * objc_copyClassList(unsigned int *outCount) { - rwlock_writer_t lock(runtimeLock); + mutex_locker_t lock(runtimeLock); realizeAllClasses(); @@ -3893,7 +4042,7 @@ void objc_registerProtocol(Protocol *proto_gen) Protocol * __unsafe_unretained * objc_copyProtocolList(unsigned int *outCount) { - rwlock_reader_t lock(runtimeLock); + mutex_locker_t lock(runtimeLock); NXMapTable *protocol_map = protocols(); @@ -3930,7 +4079,7 @@ void objc_registerProtocol(Protocol *proto_gen) **********************************************************************/ Protocol *objc_getProtocol(const char *name) { - rwlock_reader_t lock(runtimeLock); + mutex_locker_t lock(runtimeLock); return getProtocol(name); } @@ -3951,7 +4100,7 @@ void objc_registerProtocol(Protocol *proto_gen) return nil; } - rwlock_reader_t lock(runtimeLock); + mutex_locker_t lock(runtimeLock); assert(cls->isRealized()); @@ -3989,7 +4138,7 @@ void objc_registerProtocol(Protocol *proto_gen) return nil; } - rwlock_reader_t lock(runtimeLock); + mutex_locker_t lock(runtimeLock); assert(cls->isRealized()); @@ -4023,9 +4172,11 @@ void objc_registerProtocol(Protocol *proto_gen) return nil; } - rwlock_reader_t lock(runtimeLock); + mutex_locker_t lock(runtimeLock); + checkIsKnownClass(cls); assert(cls->isRealized()); + auto rw = cls->data(); property_t **result = nil; @@ -4113,7 +4264,7 @@ void objc_registerProtocol(Protocol *proto_gen) Class _category_getClass(Category cat) { - rwlock_reader_t lock(runtimeLock); + mutex_locker_t lock(runtimeLock); Class result = remapClass(cat->cls); assert(result->isRealized()); // ok for call_category_loads' usage return result; @@ -4177,7 +4328,9 @@ void objc_registerProtocol(Protocol *proto_gen) return nil; } - rwlock_reader_t lock(runtimeLock); + mutex_locker_t lock(runtimeLock); + + checkIsKnownClass(cls); assert(cls->isRealized()); @@ -4199,25 +4352,70 @@ void objc_registerProtocol(Protocol *proto_gen) /*********************************************************************** -* _objc_copyClassNamesForImage -* fixme -* Locking: write-locks runtimeLock +* objc_copyImageNames +* Copies names of loaded images with ObjC contents. +* +* Locking: acquires runtimeLock **********************************************************************/ -const char ** -_objc_copyClassNamesForImage(header_info *hi, unsigned int *outCount) +const char **objc_copyImageNames(unsigned int *outCount) { - size_t count, i, shift; - classref_t *classlist; - const char **names; + mutex_locker_t lock(runtimeLock); - // Need to write-lock in case demangledName() needs to realize a class. - rwlock_writer_t lock(runtimeLock); - - classlist = _getObjc2ClassList(hi, &count); - names = (const char **)malloc((count+1) * sizeof(const char *)); +#if TARGET_OS_WIN32 + const TCHAR **names = (const TCHAR **) + malloc((HeaderCount+1) * sizeof(TCHAR *)); +#else + const char **names = (const char **) + malloc((HeaderCount+1) * sizeof(char *)); +#endif + + unsigned int count = 0; + for (header_info *hi = FirstHeader; hi != nil; hi = hi->getNext()) { +#if TARGET_OS_WIN32 + if (hi->moduleName) { + names[count++] = hi->moduleName; + } +#else + const char *fname = hi->fname(); + if (fname) { + names[count++] = fname; + } +#endif + } + names[count] = nil; - shift = 0; - for (i = 0; i < count; i++) { + if (count == 0) { + // Return nil instead of empty list if there are no images + free((void *)names); + names = nil; + } + + if (outCount) *outCount = count; + return names; +} + + +/*********************************************************************** +* copyClassNamesForImage_nolock +* Copies class names from the given image. +* Missing weak-import classes are omitted. +* Swift class names are demangled. +* +* Locking: runtimeLock must be held by the caller +**********************************************************************/ +const char ** +copyClassNamesForImage_nolock(header_info *hi, unsigned int *outCount) +{ + runtimeLock.assertLocked(); + assert(hi); + + size_t count; + classref_t *classlist = _getObjc2ClassList(hi, &count); + const char **names = (const char **) + malloc((count+1) * sizeof(const char *)); + + size_t shift = 0; + for (size_t i = 0; i < count; i++) { Class cls = remapClass(classlist[i]); if (cls) { names[i-shift] = cls->demangledName(true/*realize*/); @@ -4233,6 +4431,78 @@ void objc_registerProtocol(Protocol *proto_gen) } + +/*********************************************************************** +* objc_copyClassNamesForImage +* Copies class names from the named image. +* The image name must be identical to dladdr's dli_fname value. +* Missing weak-import classes are omitted. +* Swift class names are demangled. +* +* Locking: acquires runtimeLock +**********************************************************************/ +const char ** +objc_copyClassNamesForImage(const char *image, unsigned int *outCount) +{ + if (!image) { + if (outCount) *outCount = 0; + return nil; + } + + mutex_locker_t lock(runtimeLock); + + // Find the image. + header_info *hi; + for (hi = FirstHeader; hi != nil; hi = hi->getNext()) { +#if TARGET_OS_WIN32 + if (0 == wcscmp((TCHAR *)image, hi->moduleName)) break; +#else + if (0 == strcmp(image, hi->fname())) break; +#endif + } + + if (!hi) { + if (outCount) *outCount = 0; + return nil; + } + + return copyClassNamesForImage_nolock(hi, outCount); +} + + +/*********************************************************************** +* objc_copyClassNamesForImageHeader +* Copies class names from the given image. +* Missing weak-import classes are omitted. +* Swift class names are demangled. +* +* Locking: acquires runtimeLock +**********************************************************************/ +const char ** +objc_copyClassNamesForImageHeader(const struct mach_header *mh, unsigned int *outCount) +{ + if (!mh) { + if (outCount) *outCount = 0; + return nil; + } + + mutex_locker_t lock(runtimeLock); + + // Find the image. + header_info *hi; + for (hi = FirstHeader; hi != nil; hi = hi->getNext()) { + if (hi->mhdr() == (const headerType *)mh) break; + } + + if (!hi) { + if (outCount) *outCount = 0; + return nil; + } + + return copyClassNamesForImage_nolock(hi, outCount); +} + + /*********************************************************************** * saveTemporaryString * Save a string in a thread-local FIFO buffer. @@ -4284,7 +4554,7 @@ void objc_registerProtocol(Protocol *proto_gen) /*********************************************************************** * objc_class::demangledName * If realize=false, the class must already be realized or future. -* Locking: If realize=true, runtimeLock must be held for writing by the caller. +* Locking: If realize=true, runtimeLock must be held by the caller. **********************************************************************/ mutex_t DemangleCacheLock; static NXHashTable *DemangleCache; @@ -4302,7 +4572,7 @@ void objc_registerProtocol(Protocol *proto_gen) if (isRealized() || isFuture()) { // Class is already realized or future. // Save demangling result in rw data. - // We may not own rwlock for writing so use an atomic operation instead. + // We may not own runtimeLock so use an atomic operation instead. if (! OSAtomicCompareAndSwapPtrBarrier(nil, (void*)(de ?: mangled), (void**)&data()->demangledName)) { @@ -4322,11 +4592,11 @@ void objc_registerProtocol(Protocol *proto_gen) // fixme lldb's calls to class_getName() can also get here when // interrogating the dyld shared cache. (rdar://27258517) - // fixme runtimeLock.assertWriting(); + // fixme runtimeLock.assertLocked(); // fixme assert(realize); if (realize) { - runtimeLock.assertWriting(); + runtimeLock.assertLocked(); realizeClass((Class)this); data()->demangledName = de; return de; @@ -4510,7 +4780,7 @@ void objc_registerProtocol(Protocol *proto_gen) **********************************************************************/ static Method _class_getMethod(Class cls, SEL sel) { - rwlock_reader_t lock(runtimeLock); + mutex_locker_t lock(runtimeLock); return getMethod_nolock(cls, sel); } @@ -4610,25 +4880,17 @@ IMP lookUpImpOrForward(Class cls, SEL sel, id inst, // the cache was re-filled with the old value after the cache flush on // behalf of the category. - runtimeLock.read(); + runtimeLock.lock(); + checkIsKnownClass(cls); if (!cls->isRealized()) { - // Drop the read-lock and acquire the write-lock. - // realizeClass() checks isRealized() again to prevent - // a race while the lock is down. - runtimeLock.unlockRead(); - runtimeLock.write(); - realizeClass(cls); - - runtimeLock.unlockWrite(); - runtimeLock.read(); } if (initialize && !cls->isInitialized()) { - runtimeLock.unlockRead(); + runtimeLock.unlock(); _class_initialize (_class_getNonMetaClass(cls, inst)); - runtimeLock.read(); + runtimeLock.lock(); // If sel == initialize, _class_initialize will send +initialize and // then the messenger will send +initialize again after this // procedure finishes. Of course, if this is not being called @@ -4637,7 +4899,7 @@ IMP lookUpImpOrForward(Class cls, SEL sel, id inst, retry: - runtimeLock.assertReading(); + runtimeLock.assertLocked(); // Try this class's cache. @@ -4695,9 +4957,9 @@ IMP lookUpImpOrForward(Class cls, SEL sel, id inst, // No implementation found. Try method resolver once. if (resolver && !triedResolver) { - runtimeLock.unlockRead(); + runtimeLock.unlock(); _class_resolveMethod(cls, sel, inst); - runtimeLock.read(); + runtimeLock.lock(); // Don't cache the result; we don't hold the lock so it may have // changed already. Re-do the search from scratch instead. triedResolver = YES; @@ -4711,7 +4973,7 @@ IMP lookUpImpOrForward(Class cls, SEL sel, id inst, cache_fill(cls, sel, imp, inst); done: - runtimeLock.unlockRead(); + runtimeLock.unlock(); return imp; } @@ -4750,7 +5012,7 @@ IMP lookupMethodInClassAndLoadCache(Class cls, SEL sel) // Cache miss. Search method list. - rwlock_reader_t lock(runtimeLock); + mutex_locker_t lock(runtimeLock); meth = getMethodNoSuper_nolock(cls, sel); @@ -4775,8 +5037,10 @@ objc_property_t class_getProperty(Class cls, const char *name) { if (!cls || !name) return nil; - rwlock_reader_t lock(runtimeLock); + mutex_locker_t lock(runtimeLock); + checkIsKnownClass(cls); + assert(cls->isRealized()); for ( ; cls; cls = cls->superclass) { @@ -4824,7 +5088,7 @@ Class gdb_object_getClass(id obj) cls = (Class)this; metacls = cls->ISA(); - rwlock_reader_t lock(runtimeLock); + mutex_locker_t lock(runtimeLock); // Scan metaclass for custom AWZ. // Scan metaclass for custom RR. @@ -5031,7 +5295,7 @@ Class gdb_object_getClass(id obj) void objc_class::setHasCustomRR(bool inherited) { Class cls = (Class)this; - runtimeLock.assertWriting(); + runtimeLock.assertLocked(); if (hasCustomRR()) return; @@ -5059,7 +5323,7 @@ Class gdb_object_getClass(id obj) void objc_class::setHasCustomAWZ(bool inherited) { Class cls = (Class)this; - runtimeLock.assertWriting(); + runtimeLock.assertLocked(); if (hasCustomAWZ()) return; @@ -5087,7 +5351,7 @@ Class gdb_object_getClass(id obj) void objc_class::setInstancesRequireRawIsa(bool inherited) { Class cls = (Class)this; - runtimeLock.assertWriting(); + runtimeLock.assertLocked(); if (instancesRequireRawIsa()) return; @@ -5112,7 +5376,7 @@ Class gdb_object_getClass(id obj) { #if SUPPORT_INDEXED_ISA Class cls = (Class)this; - runtimeLock.assertWriting(); + runtimeLock.assertLocked(); if (objc_indexed_classes_count >= ISA_INDEX_COUNT) { // No more indexes available. @@ -5260,8 +5524,10 @@ Class gdb_object_getClass(id obj) { if (!cls) return; - rwlock_writer_t lock(runtimeLock); + mutex_locker_t lock(runtimeLock); + checkIsKnownClass(cls); + // Can only change layout of in-construction classes. // note: if modifications to post-construction classes were // allowed, there would be a race below (us vs. concurrent object_setIvar) @@ -5277,35 +5543,6 @@ Class gdb_object_getClass(id obj) ro_w->ivarLayout = ustrdupMaybeNil(layout); } -// SPI: Instance-specific object layout. - -void -_class_setIvarLayoutAccessor(Class cls, const uint8_t* (*accessor) (id object)) { - if (!cls) return; - - rwlock_writer_t lock(runtimeLock); - - class_ro_t *ro_w = make_ro_writeable(cls->data()); - - // FIXME: this really isn't safe to free if there are instances of this class already. - if (!(cls->data()->flags & RW_HAS_INSTANCE_SPECIFIC_LAYOUT)) try_free(ro_w->ivarLayout); - ro_w->ivarLayout = (uint8_t *)accessor; - cls->setInfo(RW_HAS_INSTANCE_SPECIFIC_LAYOUT); -} - -const uint8_t * -_object_getIvarLayout(Class cls, id object) -{ - if (cls) { - const uint8_t* layout = cls->data()->ro->ivarLayout; - if (cls->data()->flags & RW_HAS_INSTANCE_SPECIFIC_LAYOUT) { - const uint8_t* (*accessor) (id object) = (const uint8_t* (*)(id))layout; - layout = accessor(object); - } - return layout; - } - return nil; -} /*********************************************************************** * class_setWeakIvarLayout @@ -5321,8 +5558,10 @@ Class gdb_object_getClass(id obj) { if (!cls) return; - rwlock_writer_t lock(runtimeLock); + mutex_locker_t lock(runtimeLock); + checkIsKnownClass(cls); + // Can only change layout of in-construction classes. // note: if modifications to post-construction classes were // allowed, there would be a race below (us vs. concurrent object_setIvar) @@ -5372,7 +5611,7 @@ Class gdb_object_getClass(id obj) **********************************************************************/ Class _class_getClassForIvar(Class cls, Ivar ivar) { - rwlock_reader_t lock(runtimeLock); + mutex_locker_t lock(runtimeLock); for ( ; cls; cls = cls->superclass) { if (auto ivars = cls->data()->ro->ivars) { @@ -5394,7 +5633,7 @@ Class _class_getClassForIvar(Class cls, Ivar ivar) Ivar _class_getVariable(Class cls, const char *name) { - rwlock_reader_t lock(runtimeLock); + mutex_locker_t lock(runtimeLock); for ( ; cls; cls = cls->superclass) { ivar_t *ivar = getIvar(cls, name); @@ -5419,10 +5658,12 @@ BOOL class_conformsToProtocol(Class cls, Protocol *proto_gen) if (!cls) return NO; if (!proto_gen) return NO; - rwlock_reader_t lock(runtimeLock); + mutex_locker_t lock(runtimeLock); + checkIsKnownClass(cls); + assert(cls->isRealized()); - + for (const auto& proto_ref : cls->data()->protocols) { protocol_t *p = remapProtocol(proto_ref); if (p == proto || protocol_conformsToProtocol_nolock(p, proto)) { @@ -5444,8 +5685,10 @@ BOOL class_conformsToProtocol(Class cls, Protocol *proto_gen) { IMP result = nil; - runtimeLock.assertWriting(); + runtimeLock.assertLocked(); + checkIsKnownClass(cls); + assert(types); assert(cls->isRealized()); @@ -5478,13 +5721,91 @@ BOOL class_conformsToProtocol(Class cls, Protocol *proto_gen) return result; } +/********************************************************************** +* addMethods +* Add the given methods to a class in bulk. +* Returns the selectors which could not be added, when replace == NO and a +* method already exists. The returned selectors are NULL terminated and must be +* freed by the caller. They are NULL if no failures occurred. +* Locking: runtimeLock must be held by the caller +**********************************************************************/ +static SEL * +addMethods(Class cls, const SEL *names, const IMP *imps, const char **types, + uint32_t count, bool replace, uint32_t *outFailedCount) +{ + runtimeLock.assertLocked(); + + assert(names); + assert(imps); + assert(types); + assert(cls->isRealized()); + + method_list_t *newlist; + size_t newlistSize = method_list_t::byteSize(sizeof(method_t), count); + newlist = (method_list_t *)calloc(newlistSize, 1); + newlist->entsizeAndFlags = + (uint32_t)sizeof(method_t) | fixed_up_method_list; + newlist->count = 0; + + method_t *newlistMethods = &newlist->first; + + SEL *failedNames = nil; + uint32_t failedCount = 0; + + for (uint32_t i = 0; i < count; i++) { + method_t *m; + if ((m = getMethodNoSuper_nolock(cls, names[i]))) { + // already exists + if (!replace) { + // report failure + if (failedNames == nil) { + // allocate an extra entry for a trailing NULL in case + // every method fails + failedNames = (SEL *)calloc(sizeof(*failedNames), + count + 1); + } + failedNames[failedCount] = m->name; + failedCount++; + } else { + _method_setImplementation(cls, m, imps[i]); + } + } else { + method_t *newmethod = &newlistMethods[newlist->count]; + newmethod->name = names[i]; + newmethod->types = strdupIfMutable(types[i]); + newmethod->imp = imps[i]; + newlist->count++; + } + } + + if (newlist->count > 0) { + // fixme resize newlist because it may have been over-allocated above. + // Note that realloc() alone doesn't work due to ptrauth. + + method_t::SortBySELAddress sorter; + std::stable_sort(newlist->begin(), newlist->end(), sorter); + + prepareMethodLists(cls, &newlist, 1, NO, NO); + cls->data()->methods.attachLists(&newlist, 1); + flushCaches(cls); + } else { + // Attaching the method list to the class consumes it. If we don't + // do that, we have to free the memory ourselves. + free(newlist); + } + + if (outFailedCount) *outFailedCount = failedCount; + + return failedNames; +} + BOOL class_addMethod(Class cls, SEL name, IMP imp, const char *types) { if (!cls) return NO; - rwlock_writer_t lock(runtimeLock); + mutex_locker_t lock(runtimeLock); return ! addMethod(cls, name, imp, types ?: "", NO); } @@ -5494,11 +5815,36 @@ BOOL class_conformsToProtocol(Class cls, Protocol *proto_gen) { if (!cls) return nil; - rwlock_writer_t lock(runtimeLock); + mutex_locker_t lock(runtimeLock); return addMethod(cls, name, imp, types ?: "", YES); } +SEL * +class_addMethodsBulk(Class cls, const SEL *names, const IMP *imps, + const char **types, uint32_t count, + uint32_t *outFailedCount) +{ + if (!cls) { + if (outFailedCount) *outFailedCount = count; + return (SEL *)memdup(names, count * sizeof(*names)); + } + + mutex_locker_t lock(runtimeLock); + return addMethods(cls, names, imps, types, count, NO, outFailedCount); +} + +void +class_replaceMethodsBulk(Class cls, const SEL *names, const IMP *imps, + const char **types, uint32_t count) +{ + if (!cls) return; + + mutex_locker_t lock(runtimeLock); + addMethods(cls, names, imps, types, count, YES, nil); +} + + /*********************************************************************** * class_addIvar * Adds an ivar to a class. @@ -5513,8 +5859,9 @@ BOOL class_conformsToProtocol(Class cls, Protocol *proto_gen) if (!type) type = ""; if (name && 0 == strcmp(name, "")) name = nil; - rwlock_writer_t lock(runtimeLock); + mutex_locker_t lock(runtimeLock); + checkIsKnownClass(cls); assert(cls->isRealized()); // No class variables @@ -5588,7 +5935,7 @@ BOOL class_addProtocol(Class cls, Protocol *protocol_gen) if (!cls) return NO; if (class_conformsToProtocol(cls, protocol_gen)) return NO; - rwlock_writer_t lock(runtimeLock); + mutex_locker_t lock(runtimeLock); assert(cls->isRealized()); @@ -5626,13 +5973,13 @@ BOOL class_addProtocol(Class cls, Protocol *protocol_gen) } else if (prop) { // replace existing - rwlock_writer_t lock(runtimeLock); + mutex_locker_t lock(runtimeLock); try_free(prop->attributes); prop->attributes = copyPropertyAttributeString(attrs, count); return YES; } else { - rwlock_writer_t lock(runtimeLock); + mutex_locker_t lock(runtimeLock); assert(cls->isRealized()); @@ -5679,12 +6026,12 @@ bool includeClassHandler __attribute__((unused))) Class result; bool unrealized; { - rwlock_reader_t lock(runtimeLock); + mutex_locker_t lock(runtimeLock); result = getClass(name); unrealized = result && !result->isRealized(); } if (unrealized) { - rwlock_writer_t lock(runtimeLock); + mutex_locker_t lock(runtimeLock); realizeClass(result); } return result; @@ -5702,7 +6049,9 @@ bool includeClassHandler __attribute__((unused))) { Class duplicate; - rwlock_writer_t lock(runtimeLock); + mutex_locker_t lock(runtimeLock); + + checkIsKnownClass(original); assert(original->isRealized()); assert(!original->isMetaClass()); @@ -5745,7 +6094,8 @@ bool includeClassHandler __attribute__((unused))) // Don't methodize class - construction above is correct addNamedClass(duplicate, duplicate->data()->ro->name); - + addClassTableEntry(duplicate, /*addMeta=*/false); + if (PrintConnecting) { _objc_inform("CLASS: realizing class '%s' (duplicate of %s) %p %p", name, original->nameForLogging(), @@ -5767,7 +6117,7 @@ bool includeClassHandler __attribute__((unused))) static void objc_initializeClassPair_internal(Class superclass, const char *name, Class cls, Class meta) { - runtimeLock.assertWriting(); + runtimeLock.assertLocked(); class_ro_t *cls_ro_w, *meta_ro_w; @@ -5830,6 +6180,8 @@ static void objc_initializeClassPair_internal(Class superclass, const char *name cls->cache.initializeToEmpty(); meta->cache.initializeToEmpty(); + + addClassTableEntry(cls); } @@ -5863,7 +6215,7 @@ static void objc_initializeClassPair_internal(Class superclass, const char *name **********************************************************************/ Class objc_initializeClassPair(Class superclass, const char *name, Class cls, Class meta) { - rwlock_writer_t lock(runtimeLock); + mutex_locker_t lock(runtimeLock); // Fail if the class name is in use. // Fail if the superclass isn't kosher. @@ -5887,7 +6239,7 @@ Class objc_allocateClassPair(Class superclass, const char *name, { Class cls, meta; - rwlock_writer_t lock(runtimeLock); + mutex_locker_t lock(runtimeLock); // Fail if the class name is in use. // Fail if the superclass isn't kosher. @@ -5913,9 +6265,11 @@ Class objc_allocateClassPair(Class superclass, const char *name, **********************************************************************/ void objc_registerClassPair(Class cls) { - rwlock_writer_t lock(runtimeLock); + mutex_locker_t lock(runtimeLock); - if ((cls->data()->flags & RW_CONSTRUCTED) || + checkIsKnownClass(cls); + + if ((cls->data()->flags & RW_CONSTRUCTED) || (cls->ISA()->data()->flags & RW_CONSTRUCTED)) { _objc_inform("objc_registerClassPair: class '%s' was already " @@ -5953,19 +6307,20 @@ void objc_registerClassPair(Class cls) **********************************************************************/ Class objc_readClassPair(Class bits, const struct objc_image_info *info) { - rwlock_writer_t lock(runtimeLock); + mutex_locker_t lock(runtimeLock); // No info bits are significant yet. (void)info; - // Fail if the class name is in use. // Fail if the superclass isn't kosher. - const char *name = bits->mangledName(); bool rootOK = bits->data()->flags & RO_ROOT; - if (getClass(name) || !verifySuperclass(bits->superclass, rootOK)){ + if (!verifySuperclass(bits->superclass, rootOK)){ return nil; } + // Duplicate classes are allowed, just like they are for image loading. + // readClass will complain about the duplicate. + Class cls = readClass(bits, false/*bundle*/, false/*shared cache*/); if (cls != bits) { // This function isn't allowed to remap anything. @@ -5987,7 +6342,7 @@ Class objc_readClassPair(Class bits, const struct objc_image_info *info) **********************************************************************/ static void detach_class(Class cls, bool isMeta) { - runtimeLock.assertWriting(); + runtimeLock.assertLocked(); // categories not yet attached to this class removeAllUnattachedCategoriesForClass(cls); @@ -6006,6 +6361,7 @@ static void detach_class(Class cls, bool isMeta) if (!isMeta) { removeNamedClass(cls, cls->mangledName()); } + NXHashRemove(allocatedClasses, cls); } @@ -6017,7 +6373,7 @@ static void detach_class(Class cls, bool isMeta) **********************************************************************/ static void free_class(Class cls) { - runtimeLock.assertWriting(); + runtimeLock.assertLocked(); if (! cls->isRealized()) return; @@ -6060,9 +6416,11 @@ static void free_class(Class cls) void objc_disposeClassPair(Class cls) { - rwlock_writer_t lock(runtimeLock); + mutex_locker_t lock(runtimeLock); - if (!(cls->data()->flags & (RW_CONSTRUCTED|RW_CONSTRUCTING)) || + checkIsKnownClass(cls); + + if (!(cls->data()->flags & (RW_CONSTRUCTED|RW_CONSTRUCTING)) || !(cls->ISA()->data()->flags & (RW_CONSTRUCTED|RW_CONSTRUCTING))) { // class not allocated with objc_allocateClassPair @@ -6252,7 +6610,7 @@ static __attribute__((always_inline)) } -#if !(TARGET_OS_EMBEDDED || TARGET_OS_IPHONE) +#if SUPPORT_ZONES /*********************************************************************** * class_createInstanceFromZone @@ -6366,6 +6724,7 @@ Class _objc_getFreedObjectClass (void) #if !SUPPORT_TAGGED_POINTERS // These variables are always provided for debuggers. +uintptr_t objc_debug_taggedpointer_obfuscator = 0; uintptr_t objc_debug_taggedpointer_mask = 0; unsigned objc_debug_taggedpointer_slot_shift = 0; uintptr_t objc_debug_taggedpointer_slot_mask = 0; @@ -6383,12 +6742,16 @@ Class _objc_getFreedObjectClass (void) static void disableTaggedPointers() { } +static void +initializeTaggedPointerObfuscator(void) { } + #else // The "slot" used in the class table and given to the debugger // includes the is-tagged bit. This makes objc_msgSend faster. // The "ext" representation doesn't do that. +uintptr_t objc_debug_taggedpointer_obfuscator; uintptr_t objc_debug_taggedpointer_mask = _OBJC_TAG_MASK; unsigned objc_debug_taggedpointer_slot_shift = _OBJC_TAG_SLOT_SHIFT; uintptr_t objc_debug_taggedpointer_slot_mask = _OBJC_TAG_SLOT_MASK; @@ -6425,11 +6788,15 @@ Class _objc_getFreedObjectClass (void) static Class * classSlotForBasicTagIndex(objc_tag_index_t tag) { + uintptr_t tagObfuscator = ((objc_debug_taggedpointer_obfuscator + >> _OBJC_TAG_INDEX_SHIFT) + & _OBJC_TAG_INDEX_MASK); + uintptr_t obfuscatedTag = tag ^ tagObfuscator; // Array index in objc_tag_classes includes the tagged bit itself #if SUPPORT_MSB_TAGGED_POINTERS - return &objc_tag_classes[0x8 | tag]; + return &objc_tag_classes[0x8 | obfuscatedTag]; #else - return &objc_tag_classes[(tag << 1) | 1]; + return &objc_tag_classes[(obfuscatedTag << 1) | 1]; #endif } @@ -6444,12 +6811,43 @@ Class _objc_getFreedObjectClass (void) } if (tag >= OBJC_TAG_First52BitPayload && tag <= OBJC_TAG_Last52BitPayload) { - return &objc_tag_ext_classes[tag - OBJC_TAG_First52BitPayload]; + int index = tag - OBJC_TAG_First52BitPayload; + uintptr_t tagObfuscator = ((objc_debug_taggedpointer_obfuscator + >> _OBJC_TAG_EXT_INDEX_SHIFT) + & _OBJC_TAG_EXT_INDEX_MASK); + return &objc_tag_ext_classes[index ^ tagObfuscator]; } return nil; } +/*********************************************************************** +* initializeTaggedPointerObfuscator +* Initialize objc_debug_taggedpointer_obfuscator with randomness. +* +* The tagged pointer obfuscator is intended to make it more difficult +* for an attacker to construct a particular object as a tagged pointer, +* in the presence of a buffer overflow or other write control over some +* memory. The obfuscator is XORed with the tagged pointers when setting +* or retrieving payload values. They are filled with randomness on first +* use. +**********************************************************************/ +static void +initializeTaggedPointerObfuscator(void) +{ + if (sdkIsOlderThan(10_14, 12_0, 12_0, 5_0, 3_0) || + // Set the obfuscator to zero for apps linked against older SDKs, + // in case they're relying on the tagged pointer representation. + DisableTaggedPointerObfuscation) { + objc_debug_taggedpointer_obfuscator = 0; + } else { + // Pull random data into the variable, then shift away all non-payload bits. + arc4random_buf(&objc_debug_taggedpointer_obfuscator, + sizeof(objc_debug_taggedpointer_obfuscator)); + objc_debug_taggedpointer_obfuscator &= ~_OBJC_TAG_MASK; + } +} + /*********************************************************************** * _objc_registerTaggedPointerClass @@ -6589,7 +6987,7 @@ static Class setSuperclass(Class cls, Class newSuper) { Class oldSuper; - runtimeLock.assertWriting(); + runtimeLock.assertLocked(); assert(cls->isRealized()); assert(newSuper->isRealized()); @@ -6613,7 +7011,7 @@ static Class setSuperclass(Class cls, Class newSuper) Class class_setSuperclass(Class cls, Class newSuper) { - rwlock_writer_t lock(runtimeLock); + mutex_locker_t lock(runtimeLock); return setSuperclass(cls, newSuper); } diff --git a/runtime/objc-runtime-old.h b/runtime/objc-runtime-old.h index d8f966b..664cf4b 100644 --- a/runtime/objc-runtime-old.h +++ b/runtime/objc-runtime-old.h @@ -65,8 +65,8 @@ #define CLS_LEAF 0x800000 // class instances may have associative references #define CLS_INSTANCES_HAVE_ASSOCIATED_OBJECTS 0x1000000 -// class has instance-specific GC layout -#define CLS_HAS_INSTANCE_SPECIFIC_LAYOUT 0x2000000 +// available for use; was CLS_HAS_INSTANCE_SPECIFIC_LAYOUT +#define CLS_2000000 0x2000000 // class compiled with ARC #define CLS_IS_ARC 0x4000000 // class is not ARC but has ARC-style weak ivar layout diff --git a/runtime/objc-runtime-old.mm b/runtime/objc-runtime-old.mm index 7c95bd7..e881d71 100644 --- a/runtime/objc-runtime-old.mm +++ b/runtime/objc-runtime-old.mm @@ -1337,7 +1337,7 @@ static inline void map_selrefs(SEL *sels, size_t count, bool copy) if (!sels) return; - sel_lock(); + mutex_locker_t lock(selLock); // Process each selector for (index = 0; index < count; index += 1) @@ -1353,8 +1353,6 @@ static inline void map_selrefs(SEL *sels, size_t count, bool copy) sels[index] = sel; } } - - sel_unlock(); } @@ -1371,7 +1369,7 @@ static void map_method_descs (struct objc_method_description_list * methods, bo if (!methods) return; - sel_lock(); + mutex_locker_t lock(selLock); // Process each method for (index = 0; index < methods->count; index += 1) @@ -1390,8 +1388,6 @@ static void map_method_descs (struct objc_method_description_list * methods, bo if (method->name != sel) method->name = sel; } - - sel_unlock(); } @@ -3153,7 +3149,43 @@ static bool _objc_register_category(old_category *cat, int version) } -const char ** +const char **objc_copyImageNames(unsigned int *outCount) +{ + header_info *hi; + int count = 0; + int max = HeaderCount; +#if TARGET_OS_WIN32 + const TCHAR **names = (const TCHAR **)calloc(max+1, sizeof(TCHAR *)); +#else + const char **names = (const char **)calloc(max+1, sizeof(char *)); +#endif + + for (hi = FirstHeader; hi != NULL && count < max; hi = hi->getNext()) { +#if TARGET_OS_WIN32 + if (hi->moduleName) { + names[count++] = hi->moduleName; + } +#else + const char *fname = hi->fname(); + if (fname) { + names[count++] = fname; + } +#endif + } + names[count] = NULL; + + if (count == 0) { + // Return NULL instead of empty list if there are no images + free((void *)names); + names = NULL; + } + + if (outCount) *outCount = count; + return names; +} + + +static const char ** _objc_copyClassNamesForImage(header_info *hi, unsigned int *outCount) { Module mods; @@ -3200,6 +3232,66 @@ static bool _objc_register_category(old_category *cat, int version) return list; } + +/********************************************************************** +* +**********************************************************************/ +const char ** +objc_copyClassNamesForImage(const char *image, unsigned int *outCount) +{ + header_info *hi; + + if (!image) { + if (outCount) *outCount = 0; + return NULL; + } + + // Find the image. + for (hi = FirstHeader; hi != NULL; hi = hi->getNext()) { +#if TARGET_OS_WIN32 + if (0 == wcscmp((TCHAR *)image, hi->moduleName)) break; +#else + if (0 == strcmp(image, hi->fname())) break; +#endif + } + + if (!hi) { + if (outCount) *outCount = 0; + return NULL; + } + + return _objc_copyClassNamesForImage(hi, outCount); +} + + + +/********************************************************************** +* +**********************************************************************/ +const char ** +objc_copyClassNamesForImageHeader(const struct mach_header *mh, unsigned int *outCount) +{ + header_info *hi; + + if (!mh) { + if (outCount) *outCount = 0; + return NULL; + } + + // Find the image. + for (hi = FirstHeader; hi != NULL; hi = hi->getNext()) { + if (hi->mhdr() == (const headerType *)mh) break; + } + + if (!hi) { + if (outCount) *outCount = 0; + return NULL; + } + + return _objc_copyClassNamesForImage(hi, outCount); +} + + Class gdb_class_getClass(Class cls) { const char *className = cls->name; @@ -3216,10 +3308,21 @@ Class gdb_object_getClass(id obj) } +/*********************************************************************** +* objc_setMultithreaded. +**********************************************************************/ +void objc_setMultithreaded (BOOL flag) +{ + OBJC_WARN_DEPRECATED; + + // Nothing here. Thread synchronization in the runtime is always active. +} + + /*********************************************************************** * Lock management **********************************************************************/ -rwlock_t selLock; +mutex_t selLock; mutex_t classLock; mutex_t methodListLock; mutex_t cacheUpdateLock; diff --git a/runtime/objc-runtime.mm b/runtime/objc-runtime.mm index ba1b38d..4d94b64 100644 --- a/runtime/objc-runtime.mm +++ b/runtime/objc-runtime.mm @@ -37,13 +37,37 @@ #include "objc-loadmethod.h" #include "message.h" -OBJC_EXPORT Class getOriginalClassForPosingClass(Class); - - /*********************************************************************** * Exports. **********************************************************************/ +/* Linker metadata symbols */ + +// NSObject was in Foundation/CF on macOS < 10.8. +#if TARGET_OS_OSX +#if __OBJC2__ + +const char __objc_nsobject_class_10_5 = 0; +const char __objc_nsobject_class_10_6 = 0; +const char __objc_nsobject_class_10_7 = 0; + +const char __objc_nsobject_metaclass_10_5 = 0; +const char __objc_nsobject_metaclass_10_6 = 0; +const char __objc_nsobject_metaclass_10_7 = 0; + +const char __objc_nsobject_isa_10_5 = 0; +const char __objc_nsobject_isa_10_6 = 0; +const char __objc_nsobject_isa_10_7 = 0; + +#else + +const char __objc_nsobject_class_10_5 = 0; +const char __objc_nsobject_class_10_6 = 0; +const char __objc_nsobject_class_10_7 = 0; + +#endif +#endif + // Settings from environment variables #define OPTION(var, env, help) bool var = false; #include "objc-env.h" @@ -357,18 +381,6 @@ void environ_init(void) } - -/*********************************************************************** -* objc_setMultithreaded. -**********************************************************************/ -void objc_setMultithreaded (BOOL flag) -{ - OBJC_WARN_DEPRECATED; - - // Nothing here. Thread synchronization in the runtime is always active. -} - - /*********************************************************************** * _objc_fetch_pthread_data * Fetch objc's pthread data for this thread. @@ -487,109 +499,36 @@ void objc_setForwardHandler(void *fwd, void *fwd_stret) // GrP fixme extern "C" Class _objc_getOrigClass(const char *name); #endif -const char *class_getImageName(Class cls) -{ -#if TARGET_OS_WIN32 - TCHAR *szFileName; - DWORD charactersCopied; - Class origCls; - HMODULE classModule; - bool res; -#endif - if (!cls) return NULL; +static BOOL internal_class_getImageName(Class cls, const char **outName) +{ #if !__OBJC2__ cls = _objc_getOrigClass(cls->demangledName()); #endif -#if TARGET_OS_WIN32 - charactersCopied = 0; - szFileName = malloc(MAX_PATH * sizeof(TCHAR)); - - origCls = objc_getOrigClass(cls->demangledName()); - classModule = NULL; - res = GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS, (LPCTSTR)origCls, &classModule); - if (res && classModule) { - charactersCopied = GetModuleFileName(classModule, szFileName, MAX_PATH * sizeof(TCHAR)); - } - if (classModule) FreeLibrary(classModule); - if (charactersCopied) { - return (const char *)szFileName; - } else { - free(szFileName); - } - return NULL; -#else - return dyld_image_path_containing_address(cls); -#endif + auto result = dyld_image_path_containing_address(cls); + *outName = result; + return (result != nil); } -const char **objc_copyImageNames(unsigned int *outCount) -{ - header_info *hi; - int count = 0; - int max = HeaderCount; -#if TARGET_OS_WIN32 - const TCHAR **names = (const TCHAR **)calloc(max+1, sizeof(TCHAR *)); -#else - const char **names = (const char **)calloc(max+1, sizeof(char *)); -#endif - - for (hi = FirstHeader; hi != NULL && count < max; hi = hi->getNext()) { -#if TARGET_OS_WIN32 - if (hi->moduleName) { - names[count++] = hi->moduleName; - } -#else - const char *fname = hi->fname(); - if (fname) { - names[count++] = fname; - } -#endif - } - names[count] = NULL; - - if (count == 0) { - // Return NULL instead of empty list if there are no images - free((void *)names); - names = NULL; - } +static ChainedHookFunction +GetImageNameHook{internal_class_getImageName}; - if (outCount) *outCount = count; - return names; +void objc_setHook_getImageName(objc_hook_getImageName newValue, + objc_hook_getImageName *outOldValue) +{ + GetImageNameHook.set(newValue, outOldValue); } - -/********************************************************************** -* -**********************************************************************/ -const char ** -objc_copyClassNamesForImage(const char *image, unsigned int *outCount) +const char *class_getImageName(Class cls) { - header_info *hi; - - if (!image) { - if (outCount) *outCount = 0; - return NULL; - } + if (!cls) return nil; - // Find the image. - for (hi = FirstHeader; hi != NULL; hi = hi->getNext()) { -#if TARGET_OS_WIN32 - if (0 == wcscmp((TCHAR *)image, hi->moduleName)) break; -#else - if (0 == strcmp(image, hi->fname())) break; -#endif - } - - if (!hi) { - if (outCount) *outCount = 0; - return NULL; - } - - return _objc_copyClassNamesForImage(hi, outCount); + const char *name; + if (GetImageNameHook.get()(cls, &name)) return name; + else return nil; } - + /********************************************************************** * Fast Enumeration Support diff --git a/runtime/objc-sel-old.mm b/runtime/objc-sel-old.mm index c956494..942ddf5 100644 --- a/runtime/objc-sel-old.mm +++ b/runtime/objc-sel-old.mm @@ -77,42 +77,35 @@ BOOL sel_isMapped(SEL name) sel = _objc_search_builtins((const char *)name); if (sel) return YES; - rwlock_reader_t lock(selLock); + mutex_locker_t lock(selLock); if (_objc_selectors) { sel = __objc_sel_set_get(_objc_selectors, name); } return bool(sel); } -static SEL __sel_registerName(const char *name, int lock, int copy) +static SEL __sel_registerName(const char *name, bool shouldLock, bool copy) { SEL result = 0; - if (lock) selLock.assertUnlocked(); - else selLock.assertWriting(); + if (shouldLock) selLock.assertUnlocked(); + else selLock.assertLocked(); if (!name) return (SEL)0; result = _objc_search_builtins(name); if (result) return result; - - if (lock) selLock.read(); + + conditional_mutex_locker_t lock(selLock, shouldLock); if (_objc_selectors) { result = __objc_sel_set_get(_objc_selectors, (SEL)name); } - if (lock) selLock.unlockRead(); if (result) return result; // No match. Insert. - if (lock) selLock.write(); - if (!_objc_selectors) { _objc_selectors = __objc_sel_set_create(SelrefCount); } - if (lock) { - // Rescan in case it was added while we dropped the lock - result = __objc_sel_set_get(_objc_selectors, (SEL)name); - } if (!result) { result = (SEL)(copy ? strdup(name) : name); __objc_sel_set_add(_objc_selectors, result); @@ -121,7 +114,6 @@ static SEL __sel_registerName(const char *name, int lock, int copy) #endif } - if (lock) selLock.unlockWrite(); return result; } @@ -134,16 +126,6 @@ SEL sel_registerNameNoLock(const char *name, bool copy) { return __sel_registerName(name, 0, copy); // NO lock, maybe copy } -void sel_lock(void) -{ - selLock.write(); -} - -void sel_unlock(void) -{ - selLock.unlockWrite(); -} - // 2001/1/24 // the majority of uses of this function (which used to return NULL if not found) @@ -178,7 +160,7 @@ void sel_init(size_t selrefCount) #define s(x) SEL_##x = sel_registerNameNoLock(#x, NO) #define t(x,y) SEL_##y = sel_registerNameNoLock(#x, NO) - sel_lock(); + mutex_locker_t lock(selLock); s(load); s(initialize); @@ -204,8 +186,6 @@ void sel_init(size_t selrefCount) extern SEL FwdSel; FwdSel = sel_registerNameNoLock("forward::", NO); - sel_unlock(); - #undef s #undef t } diff --git a/runtime/objc-sel-set.mm b/runtime/objc-sel-set.mm index 79ca591..ab21b00 100644 --- a/runtime/objc-sel-set.mm +++ b/runtime/objc-sel-set.mm @@ -120,7 +120,7 @@ static struct __objc_sel_set_finds __objc_sel_set_findBuckets(struct __objc_sel_ sset->_count = 0; // heuristic to convert executable's selrefs count to table size -#if TARGET_OS_IPHONE +#if TARGET_OS_IPHONE && !TARGET_OS_IOSMAC for (idx = 0; __objc_sel_set_capacities[idx] < selrefs; idx++); if (idx > 0 && selrefs < 1536) idx--; #else diff --git a/runtime/objc-sel-table.s b/runtime/objc-sel-table.s index c4dd405..05b5532 100644 --- a/runtime/objc-sel-table.s +++ b/runtime/objc-sel-table.s @@ -20,17 +20,17 @@ __objc_opt_data: .long 0 /* table.headeropt_rw_offset */ .space PAGE_MAX_SIZE-28 -/* space for selopt, smax/capacity=524288, blen/mask=262143+1 */ +/* space for selopt, smax/capacity=1048576, blen/mask=524287+1 */ .space 4*(8+256) /* header and scramble */ -.space 262144 /* mask tab */ -.space 524288 /* checkbytes */ -.space 524288*4 /* offsets */ +.space 524288 /* mask tab */ +.space 1048576 /* checkbytes */ +.space 1048576*4 /* offsets */ -/* space for clsopt, smax/capacity=65536, blen/mask=16383+1 */ +/* space for clsopt, smax/capacity=131072, blen/mask=32767+1 */ .space 4*(8+256) /* header and scramble */ -.space 16384 /* mask tab */ -.space 65536 /* checkbytes */ -.space 65536*12 /* offsets to name and class and header_info */ +.space 32768 /* mask tab */ +.space 131072 /* checkbytes */ +.space 131072*12 /* offsets to name and class and header_info */ .space 512*8 /* some duplicate classes */ /* space for some demangled protocol names */ @@ -42,16 +42,17 @@ __objc_opt_data: .space 16384 /* checkbytes */ .space 16384*8 /* offsets */ -/* space for header_info (RO) structures */ -.space 16384 +/* space for 2048 header_info (RO) structures */ +.space 8 + (2048*16) .section __DATA,__objc_opt_rw .align 3 .private_extern __objc_opt_rw_data __objc_opt_rw_data: -/* space for header_info (RW) structures */ -.space 16384 + +/* space for 2048 header_info (RW) structures */ +.space 8 + (2048*8) /* space for 16384 protocols */ #if __LP64__ diff --git a/runtime/objc-sel.mm b/runtime/objc-sel.mm index 4c7fd29..55d57eb 100644 --- a/runtime/objc-sel.mm +++ b/runtime/objc-sel.mm @@ -67,7 +67,7 @@ void sel_init(size_t selrefCount) #define s(x) SEL_##x = sel_registerNameNoLock(#x, NO) #define t(x,y) SEL_##y = sel_registerNameNoLock(#x, NO) - sel_lock(); + mutex_locker_t lock(selLock); s(load); s(initialize); @@ -90,8 +90,6 @@ void sel_init(size_t selrefCount) s(retainWeakReference); s(allowsWeakReference); - sel_unlock(); - #undef s #undef t } @@ -99,7 +97,7 @@ void sel_init(size_t selrefCount) static SEL sel_alloc(const char *name, bool copy) { - selLock.assertWriting(); + selLock.assertLocked(); return (SEL)(copy ? strdupIfMutable(name) : name); } @@ -119,7 +117,7 @@ BOOL sel_isMapped(SEL sel) if (sel == search_builtins(name)) return YES; - rwlock_reader_t lock(selLock); + mutex_locker_t lock(selLock); if (namedSelectors) { return (sel == (SEL)NXMapGet(namedSelectors, name)); } @@ -136,44 +134,36 @@ static SEL search_builtins(const char *name) } -static SEL __sel_registerName(const char *name, int lock, int copy) +static SEL __sel_registerName(const char *name, bool shouldLock, bool copy) { SEL result = 0; - if (lock) selLock.assertUnlocked(); - else selLock.assertWriting(); + if (shouldLock) selLock.assertUnlocked(); + else selLock.assertLocked(); if (!name) return (SEL)0; result = search_builtins(name); if (result) return result; - if (lock) selLock.read(); + conditional_mutex_locker_t lock(selLock, shouldLock); if (namedSelectors) { result = (SEL)NXMapGet(namedSelectors, name); } - if (lock) selLock.unlockRead(); if (result) return result; // No match. Insert. - if (lock) selLock.write(); - if (!namedSelectors) { namedSelectors = NXCreateMapTable(NXStrValueMapPrototype, (unsigned)SelrefCount); } - if (lock) { - // Rescan in case it was added while we dropped the lock - result = (SEL)NXMapGet(namedSelectors, name); - } if (!result) { result = sel_alloc(name, copy); // fixme choose a better container (hash not map for starters) NXMapInsert(namedSelectors, sel_getName(result), result); } - if (lock) selLock.unlockWrite(); return result; } @@ -186,16 +176,6 @@ SEL sel_registerNameNoLock(const char *name, bool copy) { return __sel_registerName(name, 0, copy); // NO lock, maybe copy } -void sel_lock(void) -{ - selLock.write(); -} - -void sel_unlock(void) -{ - selLock.unlockWrite(); -} - // 2001/1/24 // the majority of uses of this function (which used to return NULL if not found) diff --git a/runtime/objc-sync.mm b/runtime/objc-sync.mm index daf743d..d5dd019 100644 --- a/runtime/objc-sync.mm +++ b/runtime/objc-sync.mm @@ -30,7 +30,7 @@ // -typedef struct SyncData { +typedef struct alignas(CacheLineSize) SyncData { struct SyncData* nextData; DisguisedPtr object; int32_t threadCount; // number of THREADS using this block @@ -60,7 +60,7 @@ SyncData *data; spinlock_t lock; - SyncList() : data(nil), lock(fork_unsafe_lock) { } + constexpr SyncList() : data(nil), lock(fork_unsafe_lock) { } }; // Use multiple parallel lists to decrease contention among unrelated objects. @@ -228,11 +228,11 @@ void _destroySyncCache(struct SyncCache *cache) } } - // malloc a new SyncData and add to list. - // XXX calling malloc with a global lock held is bad practice, - // might be worth releasing the lock, mallocing, and searching again. - // But since we never free these guys we won't be stuck in malloc very often. - result = (SyncData*)calloc(sizeof(SyncData), 1); + // Allocate a new SyncData and add to list. + // XXX allocating memory with a global lock held is bad practice, + // might be worth releasing the lock, allocating, and searching again. + // But since we never free these guys we won't be stuck in allocation very often. + posix_memalign((void **)&result, alignof(SyncData), sizeof(SyncData)); result->object = (objc_object *)object; result->threadCount = 1; new (&result->mutex) recursive_mutex_t(fork_unsafe_lock); diff --git a/runtime/objc.h b/runtime/objc.h index 5064016..ab6f0dd 100644 --- a/runtime/objc.h +++ b/runtime/objc.h @@ -67,7 +67,7 @@ typedef id _Nullable (*IMP)(id _Nonnull, SEL _Nonnull, ...); # endif #else // __OBJC_BOOL_IS_BOOL not set. -# if TARGET_OS_OSX || (TARGET_OS_IOS && !__LP64__ && !__ARM_ARCH_7K) +# if TARGET_OS_OSX || TARGET_OS_IOSMAC || (TARGET_OS_IOS && !__LP64__ && !__ARM_ARCH_7K) # define OBJC_BOOL_IS_BOOL 0 # else # define OBJC_BOOL_IS_BOOL 1 @@ -217,11 +217,20 @@ typedef const void* objc_objectptr_t; // Obsolete ARC conversions. OBJC_EXPORT id _Nullable objc_retainedObject(objc_objectptr_t _Nullable obj) - OBJC_UNAVAILABLE("use CFBridgingRelease() or a (__bridge_transfer id) cast instead"); +#if !OBJC_DECLARE_SYMBOLS + OBJC_UNAVAILABLE("use CFBridgingRelease() or a (__bridge_transfer id) cast instead") +#endif + ; OBJC_EXPORT id _Nullable objc_unretainedObject(objc_objectptr_t _Nullable obj) - OBJC_UNAVAILABLE("use a (__bridge id) cast instead"); +#if !OBJC_DECLARE_SYMBOLS + OBJC_UNAVAILABLE("use a (__bridge id) cast instead") +#endif + ; OBJC_EXPORT objc_objectptr_t _Nullable objc_unretainedPointer(id _Nullable obj) - OBJC_UNAVAILABLE("use a __bridge cast instead"); +#if !OBJC_DECLARE_SYMBOLS + OBJC_UNAVAILABLE("use a __bridge cast instead") +#endif + ; #if !__OBJC2__ diff --git a/runtime/runtime.h b/runtime/runtime.h index ad68c90..2e99cf0 100644 --- a/runtime/runtime.h +++ b/runtime/runtime.h @@ -1248,7 +1248,7 @@ protocol_isEqual(Protocol * _Nullable proto, Protocol * _Nullable other) /** * Returns the name of a protocol. * - * @param p A protocol. + * @param proto A protocol. * * @return The name of the protocol \e p as a C string. */ @@ -1259,7 +1259,7 @@ protocol_getName(Protocol * _Nonnull proto) /** * Returns a method description structure for a specified method of a given protocol. * - * @param p A protocol. + * @param proto A protocol. * @param aSel A selector. * @param isRequiredMethod A Boolean value that indicates whether aSel is a required method. * @param isInstanceMethod A Boolean value that indicates whether aSel is an instance method. @@ -1279,7 +1279,7 @@ protocol_getMethodDescription(Protocol * _Nonnull proto, SEL _Nonnull aSel, /** * Returns an array of method descriptions of methods meeting a given specification for a given protocol. * - * @param p A protocol. + * @param proto A protocol. * @param isRequiredMethod A Boolean value that indicates whether returned methods should * be required methods (pass YES to specify required methods). * @param isInstanceMethod A Boolean value that indicates whether returned methods should @@ -1692,6 +1692,43 @@ objc_removeAssociatedObjects(id _Nonnull object) OBJC_AVAILABLE(10.6, 3.1, 9.0, 1.0, 2.0); +/* Hooks for Swift */ + +/** + * Function type for a hook that intercepts class_getImageName(). + * + * @param cls The class whose image name is being looked up. + * @param outImageName On return, the result of the image name lookup. + * @return YES if an image name for this class was found, NO otherwise. + * + * @see class_getImageName + * @see objc_setHook_getImageName + */ +typedef BOOL (*objc_hook_getImageName)(Class _Nonnull cls, const char * _Nullable * _Nonnull outImageName); + +/** + * Install a hook for class_getImageName(). + * + * @param newValue The hook function to install. + * @param outOldValue The address of a function pointer variable. On return, + * the old hook function is stored in the variable. + * + * @note The store to *outOldValue is thread-safe: the variable will be + * updated before class_getImageName() calls your new hook to read it, + * even if your new hook is called from another thread before this + * setter completes. + * @note The first hook in the chain is the native implementation of + * class_getImageName(). Your hook should call the previous hook for + * classes that you do not recognize. + * + * @see class_getImageName + * @see objc_hook_getImageName + */ +OBJC_EXPORT void objc_setHook_getImageName(objc_hook_getImageName _Nonnull newValue, + objc_hook_getImageName _Nullable * _Nonnull outOldValue) + OBJC_AVAILABLE(10.14, 12.0, 12.0, 5.0, 3.0); + + #define _C_ID '@' #define _C_CLASS '#' #define _C_SEL ':' @@ -1960,6 +1997,7 @@ OBJC_EXPORT unsigned method_getArgumentInfo(struct objc_method * _Nonnull m, int arg, const char * _Nullable * _Nonnull type, int * _Nonnull offset) + UNAVAILABLE_ATTRIBUTE // This function was accidentally deleted in 10.9. OBJC2_UNAVAILABLE; OBJC_EXPORT Class _Nullable From 4da97f4217a6fa1aa153b51b7ac0cae749993ac9 Mon Sep 17 00:00:00 2001 From: Apple Open Source Date: Tue, 15 Jun 2021 22:33:59 +0200 Subject: [PATCH 06/12] Version 750.1 --- objc.xcodeproj/project.pbxproj | 62 ++++++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) diff --git a/objc.xcodeproj/project.pbxproj b/objc.xcodeproj/project.pbxproj index a7cc501..715d179 100644 --- a/objc.xcodeproj/project.pbxproj +++ b/objc.xcodeproj/project.pbxproj @@ -7,6 +7,17 @@ objects = { /* Begin PBXAggregateTarget section */ + 834F9B01212E560100F95A54 /* objc4_tests */ = { + isa = PBXAggregateTarget; + buildConfigurationList = 834F9B04212E560200F95A54 /* Build configuration list for PBXAggregateTarget "objc4_tests" */; + buildPhases = ( + 834F9B05212E561400F95A54 /* Run Script (mkdir) */, + ); + dependencies = ( + ); + name = objc4_tests; + productName = objc4_tests; + }; 837F67A81A771F63004D34FA /* objc-simulator */ = { isa = PBXAggregateTarget; buildConfigurationList = 837F67A91A771F63004D34FA /* Build configuration list for PBXAggregateTarget "objc-simulator" */; @@ -520,6 +531,11 @@ BuildIndependentTargetsInParallel = NO; LastUpgradeCheck = 0440; TargetAttributes = { + 834F9B01212E560100F95A54 = { + CreatedOnToolsVersion = 10.0; + DevelopmentTeam = 59GAB85EFG; + ProvisioningStyle = Automatic; + }; 837F67A81A771F63004D34FA = { CreatedOnToolsVersion = 6.3; }; @@ -542,6 +558,7 @@ D2AAC0620554660B00DB518D /* objc */, 837F67A81A771F63004D34FA /* objc-simulator */, F9BCC6CA205C68E800DD9AFC /* objc-trampolines */, + 834F9B01212E560100F95A54 /* objc4_tests */, ); }; /* End PBXProject section */ @@ -576,6 +593,24 @@ shellPath = /bin/sh; shellScript = "cd \"${INSTALL_DIR}\"\n/bin/ln -s libobjc.A.dylib libobjc.dylib\n\nTBD_UPPER=`echo ${GENERATE_TEXT_BASED_STUBS} | tr a-z A-Z`\n\nif [ ${TBD_UPPER} = \"YES\" ] || [ ${TBD_UPPER} = \"TRUE\" ] || [ ${TBD_UPPER} = \"1\" ]; then\nGENERATE_TBD=1\nfi\n\nif [ ${GENERATE_TBD} ]; then\n /bin/ln -s libobjc.A.tbd libobjc.tbd\nfi\n"; }; + 834F9B05212E561400F95A54 /* Run Script (mkdir) */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + name = "Run Script (mkdir)"; + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "mkdir -p \"$DSTROOT/AppleInternal\"\n"; + }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ @@ -897,6 +932,24 @@ }; name = Release; }; + 834F9B02212E560200F95A54 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = 59GAB85EFG; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Debug; + }; + 834F9B03212E560200F95A54 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = 59GAB85EFG; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Release; + }; 837F67AA1A771F63004D34FA /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { @@ -1005,6 +1058,15 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; + 834F9B04212E560200F95A54 /* Build configuration list for PBXAggregateTarget "objc4_tests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 834F9B02212E560200F95A54 /* Debug */, + 834F9B03212E560200F95A54 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; 837F67A91A771F63004D34FA /* Build configuration list for PBXAggregateTarget "objc-simulator" */ = { isa = XCConfigurationList; buildConfigurations = ( From 82215b01f0c2b075f7f8686769de9a5e3cb73b03 Mon Sep 17 00:00:00 2001 From: Apple Open Source Date: Tue, 15 Jun 2021 22:36:03 +0200 Subject: [PATCH 07/12] Version 756.2 --- objc.xcodeproj/project.pbxproj | 23 +- runtime/Messengers.subproj/objc-msg-arm64.s | 13 +- runtime/NSObject.mm | 18 +- runtime/arm64-asm.h | 11 +- runtime/message.h | 3 + runtime/objc-abi.h | 24 +- runtime/objc-block-trampolines.h | 7 + runtime/objc-block-trampolines.mm | 34 +- runtime/objc-blocktramps-arm.s | 28 +- runtime/objc-blocktramps-arm64.s | 8 +- runtime/objc-blocktramps-i386.s | 49 +- runtime/objc-blocktramps-x86_64.s | 39 +- runtime/objc-cache.mm | 90 +- runtime/objc-class-old.mm | 129 +- runtime/objc-class.mm | 116 +- runtime/objc-gdb.h | 9 + runtime/objc-initialize.h | 2 +- runtime/objc-initialize.mm | 4 +- runtime/objc-internal.h | 10 + runtime/objc-object.h | 2 +- runtime/objc-os.h | 96 +- runtime/objc-private.h | 7 +- runtime/objc-ptrauth.h | 12 +- runtime/objc-references.mm | 10 + runtime/objc-runtime-new.h | 194 +- runtime/objc-runtime-new.mm | 662 ++++- runtime/objc-runtime-old.h | 5 + runtime/objc-runtime.mm | 17 + runtime/runtime.h | 59 + test/ARCBase.h | 21 + test/ARCBase.m | 30 + test/ARCLayouts.m | 217 ++ test/ARCLayoutsWithoutWeak.m | 12 + test/ARCMRC.h | 13 + test/ARCMRC.m | 11 + test/MRCARC.h | 13 + test/MRCARC.m | 11 + test/MRCBase.h | 28 + test/MRCBase.m | 46 + test/accessors.m | 79 + test/accessors2.m | 143 + test/addMethod.m | 121 + test/addMethods.m | 323 +++ test/addProtocol.m | 255 ++ test/applescriptobjc.m | 13 + test/arr-cast.m | 20 + test/arr-weak.m | 329 +++ test/asm-placeholder.s | 47 + test/association-cf.m | 40 + test/association.m | 127 + test/associationForbidden.h | 50 + test/associationForbidden.m | 16 + test/associationForbidden2.m | 19 + test/associationForbidden3.m | 21 + test/associationForbidden4.m | 18 + test/atomicProperty.mm | 41 + test/badAltHandler.m | 81 + test/badCache.m | 89 + test/badPool.m | 34 + test/badPoolCompat-ios-tvos.m | 14 + test/badPoolCompat-macos.m | 14 + test/badPoolCompat-watchos.m | 14 + test/badSuperclass.m | 37 + test/badSuperclass2.m | 13 + test/badTagClass.m | 48 + test/badTagIndex.m | 33 + test/bigrc.m | 139 + test/blocksAsImps.m | 252 ++ test/bool.c | 45 + test/cacheflush.h | 7 + test/cacheflush.m | 61 + test/cacheflush0.m | 7 + test/cacheflush2.m | 6 + test/cacheflush3.m | 6 + test/category.m | 169 ++ test/cdtors.mm | 306 +++ test/classgetclass.m | 17 + test/classname.m | 39 + test/classpair.m | 299 +++ test/classversion.m | 19 + test/concurrentcat.m | 127 + test/concurrentcat_category.m | 70 + test/copyIvarList.m | 115 + test/copyMethodList.m | 155 ++ test/copyPropertyList.m | 167 ++ test/createInstance.m | 66 + test/customrr-cat1.m | 7 + test/customrr-cat2.m | 7 + test/customrr-nsobject-awz.m | 16 + test/customrr-nsobject-none.m | 15 + test/customrr-nsobject-rr.m | 16 + test/customrr-nsobject-rrawz.m | 17 + test/customrr-nsobject.m | 177 ++ test/customrr.m | 889 ++++++ test/customrr2.m | 9 + test/definitions.c | 54 + test/designatedinit.m | 26 + test/duplicateClass.m | 150 ++ test/duplicatedClasses.m | 26 + test/evil-category-0.m | 18 + test/evil-category-00.m | 24 + test/evil-category-000.m | 18 + test/evil-category-1.m | 24 + test/evil-category-2.m | 24 + test/evil-category-3.m | 24 + test/evil-category-4.m | 24 + test/evil-category-def.m | 73 + test/evil-class-0.m | 22 + test/evil-class-00.m | 28 + test/evil-class-000.m | 22 + test/evil-class-1.m | 28 + test/evil-class-2.m | 28 + test/evil-class-3.m | 28 + test/evil-class-4.m | 28 + test/evil-class-5.m | 30 + test/evil-class-def.m | 321 +++ test/evil-main.m | 15 + test/exc.m | 879 ++++++ test/exchangeImp.m | 106 + test/foreach.m | 227 ++ test/fork.m | 54 + test/forkInitialize.m | 159 ++ test/forkInitializeDisabled.m | 21 + test/forkInitializeSingleThreaded.m | 8 + test/forward.m | 543 ++++ test/forwardDefault.m | 24 + test/forwardDefaultStret.m | 24 + test/future.h | 15 + test/future.m | 82 + test/future0.m | 2 + test/future2.m | 17 + test/gc-main.m | 10 + test/gc.c | 1 + test/gc.m | 8 + test/gcenforcer-app-aso.m | 12 + test/gcenforcer-app-gc.m | 14 + test/gcenforcer-app-gcaso.m | 14 + test/gcenforcer-app-gcaso2.m | 14 + test/gcenforcer-app-gconly.m | 14 + test/gcenforcer-app-nogc.m | 12 + test/gcenforcer-app-noobjc.m | 12 + test/gcenforcer-dylib-nogc.m | 11 + test/gcenforcer-dylib-noobjc.m | 9 + test/gcenforcer-dylib-requiresgc.m | 21 + test/gcenforcer-dylib-supportsgc.m | 9 + test/gcenforcer-preflight.m | 88 + test/gcfiles/evil1 | Bin 0 -> 441 bytes test/gcfiles/i386-aso | Bin 0 -> 12624 bytes test/gcfiles/i386-aso--x86_64-aso | Bin 0 -> 29060 bytes test/gcfiles/i386-broken | Bin 0 -> 12608 bytes test/gcfiles/i386-broken--x86_64-gc | Bin 0 -> 29056 bytes test/gcfiles/i386-broken--x86_64-nogc | Bin 0 -> 29056 bytes test/gcfiles/i386-gc | Bin 0 -> 12608 bytes test/gcfiles/i386-gc--x86_64-broken | Bin 0 -> 29056 bytes test/gcfiles/i386-gc--x86_64-gc | Bin 0 -> 29056 bytes test/gcfiles/i386-gc--x86_64-nogc | Bin 0 -> 29056 bytes test/gcfiles/i386-gcaso | Bin 0 -> 12716 bytes test/gcfiles/i386-gcaso2 | Bin 0 -> 12644 bytes test/gcfiles/i386-gconly | Bin 0 -> 12608 bytes test/gcfiles/i386-nogc | Bin 0 -> 12608 bytes test/gcfiles/i386-nogc--x86_64-broken | Bin 0 -> 29056 bytes test/gcfiles/i386-nogc--x86_64-gc | Bin 0 -> 29056 bytes test/gcfiles/i386-nogc--x86_64-nogc | Bin 0 -> 29056 bytes test/gcfiles/i386-noobjc | Bin 0 -> 4228 bytes test/gcfiles/libnogc.dylib | Bin 0 -> 74696 bytes test/gcfiles/libnoobjc.dylib | Bin 0 -> 41640 bytes test/gcfiles/librequiresgc.dylib | Bin 0 -> 74696 bytes test/gcfiles/librequiresgc.fake.dylib | Bin 0 -> 74696 bytes test/gcfiles/libsupportsgc.dylib | Bin 0 -> 74696 bytes test/gcfiles/x86_64-aso | Bin 0 -> 8580 bytes test/gcfiles/x86_64-broken | Bin 0 -> 8576 bytes test/gcfiles/x86_64-gc | Bin 0 -> 8576 bytes test/gcfiles/x86_64-gcaso | Bin 0 -> 8920 bytes test/gcfiles/x86_64-gcaso2 | Bin 0 -> 8640 bytes test/gcfiles/x86_64-gconly | Bin 0 -> 8576 bytes test/gcfiles/x86_64-nogc | Bin 0 -> 8576 bytes test/gcfiles/x86_64-noobjc | Bin 0 -> 4248 bytes test/gdb.m | 34 + test/getClassHook.m | 128 + test/getImageNameHook.m | 78 + test/getMethod.m | 139 + test/get_task_allow_entitlement.plist | 8 + test/headers.c | 19 + test/headers.sh | 38 + test/imageorder.h | 20 + test/imageorder.m | 41 + test/imageorder1.m | 52 + test/imageorder2.m | 33 + test/imageorder3.m | 33 + test/imports.c | 37 + test/include-warnings.c | 19 + test/includes-objc2.c | 13 + test/includes.c | 47 + test/initialize.m | 323 +++ test/initializeVersusWeak.m | 187 ++ test/instanceSize.m | 59 + test/isaValidation.m | 267 ++ test/ismeta.m | 13 + test/ivar.m | 133 + test/ivarSlide.h | 110 + test/ivarSlide.m | 499 ++++ test/ivarSlide1.m | 21 + test/literals.m | 55 + test/load-noobjc.m | 38 + test/load-noobjc2.m | 13 + test/load-noobjc3.m | 9 + test/load-order.m | 18 + test/load-order1.m | 15 + test/load-order2.m | 15 + test/load-order3.m | 12 + test/load-parallel.m | 63 + test/load-parallel0.m | 48 + test/load-parallel00.m | 2 + test/load-reentrant.m | 36 + test/load-reentrant2.m | 23 + test/load.m | 99 + test/methodArgs.m | 180 ++ test/methodListSize.m | 56 + test/msgSend-performance.m | 176 ++ test/msgSend.m | 2673 +++++++++++++++++++ test/nilAPIArgs.m | 18 + test/nonpointerisa.m | 262 ++ test/nopool.m | 49 + test/nscdtors.mm | 6 + test/nsexc.m | 7 + test/nsobject.m | 114 + test/nsprotocol.m | 17 + test/objectCopy.m | 38 + test/property.m | 157 ++ test/propertyDesc.m | 334 +++ test/protocol.m | 281 ++ test/protocol_copyMethodList.m | 154 ++ test/protocol_copyPropertyList.m | 207 ++ test/rawisa.m | 30 + test/readClassPair.m | 82 + test/release-workaround.m | 34 + test/resolve.m | 298 +++ test/rr-autorelease-fast.m | 357 +++ test/rr-autorelease-fastarc.m | 204 ++ test/rr-autorelease-stacklogging.m | 13 + test/rr-autorelease.m | 9 + test/rr-autorelease2.m | 384 +++ test/rr-nsautorelease.m | 7 + test/rr-sidetable.m | 59 + test/runtime.m | 212 ++ test/sel.m | 21 + test/setSuper.m | 44 + test/subscripting.m | 139 + test/super.m | 21 + test/swift-class-def.m | 291 ++ test/swiftMetadataInitializer.m | 70 + test/synchronized-counter.m | 88 + test/synchronized-grid.m | 112 + test/synchronized.m | 102 + test/taggedNSPointers.m | 80 + test/taggedPointers.m | 356 +++ test/taggedPointersAllClasses.m | 86 + test/taggedPointersDisabled.m | 39 + test/taggedPointersTagObfuscationDisabled.m | 21 + test/tbi.c | 14 + test/test.h | 469 ++++ test/test.pl | 1715 ++++++++++++ test/testroot.i | 220 ++ test/timeout.pl | 9 + test/unload.h | 6 + test/unload.m | 179 ++ test/unload2.m | 26 + test/unload3.c | 10 + test/unload4.m | 7 + test/unwind.m | 81 + test/weak.h | 67 + test/weak.m | 316 +++ test/weak2.m | 82 + test/weakcopy.m | 62 + test/weakframework-missing.m | 14 + test/weakframework-not-missing.m | 11 + test/weakimport-missing.m | 13 + test/weakimport-not-missing.m | 11 + test/weakrace.m | 76 + test/zone.m | 40 + 280 files changed, 24426 insertions(+), 479 deletions(-) create mode 100644 test/ARCBase.h create mode 100644 test/ARCBase.m create mode 100644 test/ARCLayouts.m create mode 100644 test/ARCLayoutsWithoutWeak.m create mode 100644 test/ARCMRC.h create mode 100644 test/ARCMRC.m create mode 100644 test/MRCARC.h create mode 100644 test/MRCARC.m create mode 100644 test/MRCBase.h create mode 100644 test/MRCBase.m create mode 100644 test/accessors.m create mode 100644 test/accessors2.m create mode 100644 test/addMethod.m create mode 100644 test/addMethods.m create mode 100644 test/addProtocol.m create mode 100644 test/applescriptobjc.m create mode 100644 test/arr-cast.m create mode 100644 test/arr-weak.m create mode 100644 test/asm-placeholder.s create mode 100644 test/association-cf.m create mode 100644 test/association.m create mode 100644 test/associationForbidden.h create mode 100644 test/associationForbidden.m create mode 100644 test/associationForbidden2.m create mode 100644 test/associationForbidden3.m create mode 100644 test/associationForbidden4.m create mode 100644 test/atomicProperty.mm create mode 100644 test/badAltHandler.m create mode 100644 test/badCache.m create mode 100644 test/badPool.m create mode 100644 test/badPoolCompat-ios-tvos.m create mode 100644 test/badPoolCompat-macos.m create mode 100644 test/badPoolCompat-watchos.m create mode 100644 test/badSuperclass.m create mode 100644 test/badSuperclass2.m create mode 100644 test/badTagClass.m create mode 100644 test/badTagIndex.m create mode 100644 test/bigrc.m create mode 100644 test/blocksAsImps.m create mode 100644 test/bool.c create mode 100644 test/cacheflush.h create mode 100644 test/cacheflush.m create mode 100644 test/cacheflush0.m create mode 100644 test/cacheflush2.m create mode 100644 test/cacheflush3.m create mode 100644 test/category.m create mode 100644 test/cdtors.mm create mode 100644 test/classgetclass.m create mode 100644 test/classname.m create mode 100644 test/classpair.m create mode 100644 test/classversion.m create mode 100644 test/concurrentcat.m create mode 100644 test/concurrentcat_category.m create mode 100644 test/copyIvarList.m create mode 100644 test/copyMethodList.m create mode 100644 test/copyPropertyList.m create mode 100644 test/createInstance.m create mode 100644 test/customrr-cat1.m create mode 100644 test/customrr-cat2.m create mode 100644 test/customrr-nsobject-awz.m create mode 100644 test/customrr-nsobject-none.m create mode 100644 test/customrr-nsobject-rr.m create mode 100644 test/customrr-nsobject-rrawz.m create mode 100644 test/customrr-nsobject.m create mode 100644 test/customrr.m create mode 100644 test/customrr2.m create mode 100644 test/definitions.c create mode 100644 test/designatedinit.m create mode 100644 test/duplicateClass.m create mode 100644 test/duplicatedClasses.m create mode 100644 test/evil-category-0.m create mode 100644 test/evil-category-00.m create mode 100644 test/evil-category-000.m create mode 100644 test/evil-category-1.m create mode 100644 test/evil-category-2.m create mode 100644 test/evil-category-3.m create mode 100644 test/evil-category-4.m create mode 100644 test/evil-category-def.m create mode 100644 test/evil-class-0.m create mode 100644 test/evil-class-00.m create mode 100644 test/evil-class-000.m create mode 100644 test/evil-class-1.m create mode 100644 test/evil-class-2.m create mode 100644 test/evil-class-3.m create mode 100644 test/evil-class-4.m create mode 100644 test/evil-class-5.m create mode 100644 test/evil-class-def.m create mode 100644 test/evil-main.m create mode 100644 test/exc.m create mode 100644 test/exchangeImp.m create mode 100644 test/foreach.m create mode 100644 test/fork.m create mode 100644 test/forkInitialize.m create mode 100644 test/forkInitializeDisabled.m create mode 100644 test/forkInitializeSingleThreaded.m create mode 100644 test/forward.m create mode 100644 test/forwardDefault.m create mode 100644 test/forwardDefaultStret.m create mode 100644 test/future.h create mode 100644 test/future.m create mode 100644 test/future0.m create mode 100644 test/future2.m create mode 100644 test/gc-main.m create mode 100644 test/gc.c create mode 100644 test/gc.m create mode 100644 test/gcenforcer-app-aso.m create mode 100644 test/gcenforcer-app-gc.m create mode 100644 test/gcenforcer-app-gcaso.m create mode 100644 test/gcenforcer-app-gcaso2.m create mode 100644 test/gcenforcer-app-gconly.m create mode 100644 test/gcenforcer-app-nogc.m create mode 100644 test/gcenforcer-app-noobjc.m create mode 100644 test/gcenforcer-dylib-nogc.m create mode 100644 test/gcenforcer-dylib-noobjc.m create mode 100644 test/gcenforcer-dylib-requiresgc.m create mode 100644 test/gcenforcer-dylib-supportsgc.m create mode 100644 test/gcenforcer-preflight.m create mode 100644 test/gcfiles/evil1 create mode 100755 test/gcfiles/i386-aso create mode 100755 test/gcfiles/i386-aso--x86_64-aso create mode 100755 test/gcfiles/i386-broken create mode 100755 test/gcfiles/i386-broken--x86_64-gc create mode 100755 test/gcfiles/i386-broken--x86_64-nogc create mode 100755 test/gcfiles/i386-gc create mode 100755 test/gcfiles/i386-gc--x86_64-broken create mode 100755 test/gcfiles/i386-gc--x86_64-gc create mode 100755 test/gcfiles/i386-gc--x86_64-nogc create mode 100755 test/gcfiles/i386-gcaso create mode 100755 test/gcfiles/i386-gcaso2 create mode 100755 test/gcfiles/i386-gconly create mode 100755 test/gcfiles/i386-nogc create mode 100755 test/gcfiles/i386-nogc--x86_64-broken create mode 100755 test/gcfiles/i386-nogc--x86_64-gc create mode 100755 test/gcfiles/i386-nogc--x86_64-nogc create mode 100755 test/gcfiles/i386-noobjc create mode 100755 test/gcfiles/libnogc.dylib create mode 100755 test/gcfiles/libnoobjc.dylib create mode 100755 test/gcfiles/librequiresgc.dylib create mode 100755 test/gcfiles/librequiresgc.fake.dylib create mode 100755 test/gcfiles/libsupportsgc.dylib create mode 100755 test/gcfiles/x86_64-aso create mode 100755 test/gcfiles/x86_64-broken create mode 100755 test/gcfiles/x86_64-gc create mode 100755 test/gcfiles/x86_64-gcaso create mode 100755 test/gcfiles/x86_64-gcaso2 create mode 100755 test/gcfiles/x86_64-gconly create mode 100755 test/gcfiles/x86_64-nogc create mode 100755 test/gcfiles/x86_64-noobjc create mode 100644 test/gdb.m create mode 100644 test/getClassHook.m create mode 100644 test/getImageNameHook.m create mode 100644 test/getMethod.m create mode 100644 test/get_task_allow_entitlement.plist create mode 100644 test/headers.c create mode 100755 test/headers.sh create mode 100644 test/imageorder.h create mode 100644 test/imageorder.m create mode 100644 test/imageorder1.m create mode 100644 test/imageorder2.m create mode 100644 test/imageorder3.m create mode 100644 test/imports.c create mode 100644 test/include-warnings.c create mode 100644 test/includes-objc2.c create mode 100644 test/includes.c create mode 100644 test/initialize.m create mode 100644 test/initializeVersusWeak.m create mode 100644 test/instanceSize.m create mode 100644 test/isaValidation.m create mode 100644 test/ismeta.m create mode 100644 test/ivar.m create mode 100644 test/ivarSlide.h create mode 100644 test/ivarSlide.m create mode 100644 test/ivarSlide1.m create mode 100644 test/literals.m create mode 100644 test/load-noobjc.m create mode 100644 test/load-noobjc2.m create mode 100644 test/load-noobjc3.m create mode 100644 test/load-order.m create mode 100644 test/load-order1.m create mode 100644 test/load-order2.m create mode 100644 test/load-order3.m create mode 100644 test/load-parallel.m create mode 100644 test/load-parallel0.m create mode 100644 test/load-parallel00.m create mode 100644 test/load-reentrant.m create mode 100644 test/load-reentrant2.m create mode 100644 test/load.m create mode 100644 test/methodArgs.m create mode 100644 test/methodListSize.m create mode 100644 test/msgSend-performance.m create mode 100644 test/msgSend.m create mode 100644 test/nilAPIArgs.m create mode 100644 test/nonpointerisa.m create mode 100644 test/nopool.m create mode 100644 test/nscdtors.mm create mode 100644 test/nsexc.m create mode 100644 test/nsobject.m create mode 100644 test/nsprotocol.m create mode 100644 test/objectCopy.m create mode 100644 test/property.m create mode 100644 test/propertyDesc.m create mode 100644 test/protocol.m create mode 100644 test/protocol_copyMethodList.m create mode 100644 test/protocol_copyPropertyList.m create mode 100644 test/rawisa.m create mode 100644 test/readClassPair.m create mode 100644 test/release-workaround.m create mode 100644 test/resolve.m create mode 100644 test/rr-autorelease-fast.m create mode 100644 test/rr-autorelease-fastarc.m create mode 100644 test/rr-autorelease-stacklogging.m create mode 100644 test/rr-autorelease.m create mode 100644 test/rr-autorelease2.m create mode 100644 test/rr-nsautorelease.m create mode 100644 test/rr-sidetable.m create mode 100644 test/runtime.m create mode 100644 test/sel.m create mode 100644 test/setSuper.m create mode 100644 test/subscripting.m create mode 100644 test/super.m create mode 100644 test/swift-class-def.m create mode 100644 test/swiftMetadataInitializer.m create mode 100644 test/synchronized-counter.m create mode 100644 test/synchronized-grid.m create mode 100644 test/synchronized.m create mode 100644 test/taggedNSPointers.m create mode 100644 test/taggedPointers.m create mode 100644 test/taggedPointersAllClasses.m create mode 100644 test/taggedPointersDisabled.m create mode 100644 test/taggedPointersTagObfuscationDisabled.m create mode 100644 test/tbi.c create mode 100644 test/test.h create mode 100755 test/test.pl create mode 100644 test/testroot.i create mode 100755 test/timeout.pl create mode 100644 test/unload.h create mode 100644 test/unload.m create mode 100644 test/unload2.m create mode 100644 test/unload3.c create mode 100644 test/unload4.m create mode 100644 test/unwind.m create mode 100644 test/weak.h create mode 100644 test/weak.m create mode 100644 test/weak2.m create mode 100644 test/weakcopy.m create mode 100644 test/weakframework-missing.m create mode 100644 test/weakframework-not-missing.m create mode 100644 test/weakimport-missing.m create mode 100644 test/weakimport-not-missing.m create mode 100644 test/weakrace.m create mode 100644 test/zone.m diff --git a/objc.xcodeproj/project.pbxproj b/objc.xcodeproj/project.pbxproj index 715d179..9325b98 100644 --- a/objc.xcodeproj/project.pbxproj +++ b/objc.xcodeproj/project.pbxproj @@ -11,7 +11,7 @@ isa = PBXAggregateTarget; buildConfigurationList = 834F9B04212E560200F95A54 /* Build configuration list for PBXAggregateTarget "objc4_tests" */; buildPhases = ( - 834F9B05212E561400F95A54 /* Run Script (mkdir) */, + 834F9B05212E561400F95A54 /* Run Script (build tests) */, ); dependencies = ( ); @@ -107,6 +107,8 @@ 83BE02EA0FCCB24D00661494 /* objc-runtime-old.h in Headers */ = {isa = PBXBuildFile; fileRef = 83BE02E70FCCB24D00661494 /* objc-runtime-old.h */; }; 83C9C3391668B50E00F4E544 /* objc-msg-simulator-x86_64.s in Sources */ = {isa = PBXBuildFile; fileRef = 83C9C3381668B50E00F4E544 /* objc-msg-simulator-x86_64.s */; }; 83D49E4F13C7C84F0057F1DD /* objc-msg-arm64.s in Sources */ = {isa = PBXBuildFile; fileRef = 83D49E4E13C7C84F0057F1DD /* objc-msg-arm64.s */; }; + 83D92696212254CF00299F69 /* isa.h in Headers */ = {isa = PBXBuildFile; fileRef = 83D92695212254CF00299F69 /* isa.h */; }; + 83D9269821225A7400299F69 /* arm64-asm.h in Headers */ = {isa = PBXBuildFile; fileRef = 83D9269721225A7400299F69 /* arm64-asm.h */; }; 83EB007B121C9EC200B92C16 /* objc-sel-table.s in Sources */ = {isa = PBXBuildFile; fileRef = 83EB007A121C9EC200B92C16 /* objc-sel-table.s */; }; 83EF5E9820D2298400F486A4 /* objc-blocktramps-i386.s in Sources */ = {isa = PBXBuildFile; fileRef = E8923D9C116AB2820071B552 /* objc-blocktramps-i386.s */; }; 83EF5E9920D2298400F486A4 /* objc-blocktramps-x86_64.s in Sources */ = {isa = PBXBuildFile; fileRef = E8923D9D116AB2820071B552 /* objc-blocktramps-x86_64.s */; }; @@ -220,6 +222,8 @@ 83C9C3381668B50E00F4E544 /* objc-msg-simulator-x86_64.s */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.asm; name = "objc-msg-simulator-x86_64.s"; path = "runtime/Messengers.subproj/objc-msg-simulator-x86_64.s"; sourceTree = ""; }; 83CE671D1E6E76B60095A33E /* interposable.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = interposable.txt; sourceTree = ""; }; 83D49E4E13C7C84F0057F1DD /* objc-msg-arm64.s */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.asm; name = "objc-msg-arm64.s"; path = "runtime/Messengers.subproj/objc-msg-arm64.s"; sourceTree = ""; }; + 83D92695212254CF00299F69 /* isa.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = isa.h; path = runtime/isa.h; sourceTree = ""; }; + 83D9269721225A7400299F69 /* arm64-asm.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "arm64-asm.h"; path = "runtime/arm64-asm.h"; sourceTree = ""; }; 83EB007A121C9EC200B92C16 /* objc-sel-table.s */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.asm; name = "objc-sel-table.s"; path = "runtime/objc-sel-table.s"; sourceTree = ""; }; 83F4B52615E843B100E0926F /* NSObjCRuntime.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = NSObjCRuntime.h; path = runtime/NSObjCRuntime.h; sourceTree = ""; }; 83F4B52715E843B100E0926F /* NSObject.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = NSObject.h; path = runtime/NSObject.h; sourceTree = ""; }; @@ -398,6 +402,8 @@ 8384862A0D6D6ABC00CEA253 /* Project Headers */ = { isa = PBXGroup; children = ( + 83D9269721225A7400299F69 /* arm64-asm.h */, + 83D92695212254CF00299F69 /* isa.h */, 838485CF0D6D68A200CEA253 /* objc-config.h */, 83BE02E50FCCB24D00661494 /* objc-file-old.h */, 83BE02E60FCCB24D00661494 /* objc-file.h */, @@ -472,7 +478,9 @@ 8384860A0D6D68A200CEA253 /* objc-runtime.h in Headers */, 8384860C0D6D68A200CEA253 /* objc-sel-set.h in Headers */, 838486100D6D68A200CEA253 /* objc-sync.h in Headers */, + 83D92696212254CF00299F69 /* isa.h in Headers */, 838486130D6D68A200CEA253 /* objc.h in Headers */, + 83D9269821225A7400299F69 /* arm64-asm.h in Headers */, 838486140D6D68A200CEA253 /* Object.h in Headers */, 8384861E0D6D68A800CEA253 /* Protocol.h in Headers */, 838486200D6D68A800CEA253 /* runtime.h in Headers */, @@ -593,7 +601,7 @@ shellPath = /bin/sh; shellScript = "cd \"${INSTALL_DIR}\"\n/bin/ln -s libobjc.A.dylib libobjc.dylib\n\nTBD_UPPER=`echo ${GENERATE_TEXT_BASED_STUBS} | tr a-z A-Z`\n\nif [ ${TBD_UPPER} = \"YES\" ] || [ ${TBD_UPPER} = \"TRUE\" ] || [ ${TBD_UPPER} = \"1\" ]; then\nGENERATE_TBD=1\nfi\n\nif [ ${GENERATE_TBD} ]; then\n /bin/ln -s libobjc.A.tbd libobjc.tbd\nfi\n"; }; - 834F9B05212E561400F95A54 /* Run Script (mkdir) */ = { + 834F9B05212E561400F95A54 /* Run Script (build tests) */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -602,14 +610,14 @@ ); inputPaths = ( ); - name = "Run Script (mkdir)"; + name = "Run Script (build tests)"; outputFileListPaths = ( ); outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "mkdir -p \"$DSTROOT/AppleInternal\"\n"; + shellScript = "set -x\nset -e\n\n# Set this to empty, or a space-separated list of tests to run.\ntestfiles=\"\"\n\n# Location inside DSTROOT of our test files and our BATS config file.\ntestdir=/AppleInternal/CoreOS/tests/objc4\nconfigdir=/AppleInternal/CoreOS/BATS/unit_tests\n\nmkdir -p ${DSTROOT}${testdir}\nmkdir -p ${DSTROOT}${configdir}\n\n# Common test.pl args for building and running.\ntestargs=\"ARCH=`echo ${ARCHS} | tr ' ' ','` OS=${PLATFORM_NAME} MEM=mrc,arc LANGUAGE=c,c++,objc,objc++ RUN=0 VERBOSE=1 BATS=1 ${testfiles}\"\n\n# Build the tests and BATS plist into DSTROOT.\nperl ${SRCROOT}/test/test.pl $testargs BUILD=1 RUN=0\n\n# Move the BATS plist where BATS expects it, and convert it to binary format.\nmv ${DSTROOT}${testdir}/objc4.plist ${DSTROOT}${configdir}\nplutil -convert binary1 ${DSTROOT}${configdir}/objc4.plist\n\n# Copy test sources to DSTROOT; running the test requires reading them again.\ncp -R ${SRCROOT}/test ${DSTROOT}${testdir}/test\n\n# Don't copy gcfiles because XBS chokes on them.\n# Don't copy other cruft because verifiers dislike them. (This doesn't matter for submissions but does affect local buildit builds.)\nrm -rf ${DSTROOT}${testdir}/test/gcfiles\nrm -rf ${DSTROOT}${testdir}/test/*~\nrm -rf ${DSTROOT}${testdir}/test/\\#*\\#\nrm -rf ${DSTROOT}${testdir}/test/.\\#*\n"; }; /* End PBXShellScriptBuildPhase section */ @@ -853,12 +861,14 @@ CLANG_LINK_OBJC_RUNTIME = NO; CLANG_OBJC_RUNTIME = NO; DEBUG_INFORMATION_FORMAT = dwarf; - EXCLUDED_INSTALLSRC_SUBDIRECTORY_PATTERNS = "$(inherited) test"; GCC_ENABLE_CPP_EXCEPTIONS = NO; GCC_ENABLE_CPP_RTTI = NO; GCC_INLINES_ARE_PRIVATE_EXTERN = YES; GCC_OPTIMIZATION_LEVEL = 0; - GCC_PREPROCESSOR_DEFINITIONS = "OS_OBJECT_USE_OBJC=0"; + GCC_PREPROCESSOR_DEFINITIONS = ( + "OS_OBJECT_USE_OBJC=0", + "OBJC_IS_DEBUG_BUILD=1", + ); GCC_STRICT_ALIASING = YES; GCC_SYMBOLS_PRIVATE_EXTERN = YES; GCC_VERSION = com.apple.compilers.llvm.clang.1_0; @@ -894,7 +904,6 @@ CLANG_LINK_OBJC_RUNTIME = NO; CLANG_OBJC_RUNTIME = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - EXCLUDED_INSTALLSRC_SUBDIRECTORY_PATTERNS = "$(inherited) test"; GCC_ENABLE_CPP_EXCEPTIONS = NO; GCC_ENABLE_CPP_RTTI = NO; GCC_INLINES_ARE_PRIVATE_EXTERN = YES; diff --git a/runtime/Messengers.subproj/objc-msg-arm64.s b/runtime/Messengers.subproj/objc-msg-arm64.s index 8cef3e1..89975d0 100755 --- a/runtime/Messengers.subproj/objc-msg-arm64.s +++ b/runtime/Messengers.subproj/objc-msg-arm64.s @@ -189,16 +189,19 @@ LExit$0: #define GETIMP 1 #define LOOKUP 2 -// CacheHit: x17 = cached IMP, x12 = address of cached IMP +// CacheHit: x17 = cached IMP, x12 = address of cached IMP, x1 = SEL .macro CacheHit .if $0 == NORMAL - TailCallCachedImp x17, x12 // authenticate and call imp + TailCallCachedImp x17, x12, x1 // authenticate and call imp .elseif $0 == GETIMP mov p0, p17 - AuthAndResignAsIMP x0, x12 // authenticate imp and re-sign as IMP - ret // return IMP + cbz p0, 9f // don't ptrauth a nil imp + AuthAndResignAsIMP x0, x12, x1 // authenticate imp and re-sign as IMP +9: ret // return IMP .elseif $0 == LOOKUP - AuthAndResignAsIMP x17, x12 // authenticate imp and re-sign as IMP + // No nil check for ptrauth: the caller would crash anyway when they + // jump to a nil IMP. We don't care if that jump also fails ptrauth. + AuthAndResignAsIMP x17, x12, x1 // authenticate imp and re-sign as IMP ret // return imp via x17 .else .abort oops diff --git a/runtime/NSObject.mm b/runtime/NSObject.mm index 33d7267..8953faa 100644 --- a/runtime/NSObject.mm +++ b/runtime/NSObject.mm @@ -308,7 +308,7 @@ BOOL objc_should_deallocate(id object) { !((objc_class *)cls)->isInitialized()) { SideTable::unlockTwo(oldTable, newTable); - _class_initialize(_class_getNonMetaClass(cls, (id)newObj)); + class_initialize(cls, (id)newObj); // If this class is finished with +initialize then we're good. // If this class is still running +initialize on this thread @@ -506,7 +506,7 @@ BOOL objc_should_deallocate(id object) { } else { table->unlock(); - _class_initialize(cls); + class_initialize(cls, obj); goto retry; } } @@ -614,7 +614,12 @@ BOOL objc_should_deallocate(id object) { } ~magic_t() { - m[0] = m[1] = m[2] = m[3] = 0; + // Clear magic before deallocation. + // This prevents some false positives in memory debugging tools. + // fixme semantically this should be memset_s(), but the + // compiler doesn't optimize that at all (rdar://44856676). + volatile uint64_t *p = (volatile uint64_t *)m; + p[0] = 0; p[1] = 0; } bool check() const { @@ -1782,6 +1787,13 @@ void objc_overrelease_during_dealloc_error(void) return callAlloc(cls, true/*checkNil*/, true/*allocWithZone*/); } +// Calls [[cls alloc] init]. +id +objc_alloc_init(Class cls) +{ + return [callAlloc(cls, true/*checkNil*/, false/*allocWithZone*/) init]; +} + void _objc_rootDealloc(id obj) diff --git a/runtime/arm64-asm.h b/runtime/arm64-asm.h index 0bbed2f..281bb7f 100644 --- a/runtime/arm64-asm.h +++ b/runtime/arm64-asm.h @@ -108,7 +108,8 @@ .endmacro .macro TailCallCachedImp - // $0 = cached imp, $1 = address of cached imp + // $0 = cached imp, $1 = address of cached imp, $2 = SEL + eor $1, $1, $2 // mix SEL into ptrauth modifier brab $0, $1 .endmacro @@ -123,8 +124,11 @@ .endmacro .macro AuthAndResignAsIMP - // $0 = cached imp, $1 = address of cached imp + // $0 = cached imp, $1 = address of cached imp, $2 = SEL + // note: assumes the imp is not nil + eor $1, $1, $2 // mix SEL into ptrauth modifier autib $0, $1 // authenticate cached imp + ldr xzr, [$0] // crash if authentication failed paciza $0 // resign cached imp as IMP .endmacro @@ -138,7 +142,7 @@ .endmacro .macro TailCallCachedImp - // $0 = cached imp, $1 = address of cached imp + // $0 = cached imp, $1 = address of cached imp, $2 = SEL br $0 .endmacro @@ -153,6 +157,7 @@ .endmacro .macro AuthAndResignAsIMP + // $0 = cached imp, $1 = address of cached imp, $2 = SEL // empty .endmacro diff --git a/runtime/message.h b/runtime/message.h index a53b430..20f698f 100644 --- a/runtime/message.h +++ b/runtime/message.h @@ -198,9 +198,12 @@ objc_msgSend_fp2ret(void /* id self, SEL op, ... */ ) * you must use \c objc_msgSend_fpret for functions returning non-integral type. For \c float or * \c long \c double return types, cast the function to an appropriate function pointer type first. */ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wincompatible-library-redeclaration" OBJC_EXPORT double objc_msgSend_fpret(id _Nullable self, SEL _Nonnull op, ...) OBJC_AVAILABLE(10.4, 2.0, 9.0, 1.0, 2.0); +#pragma clang diagnostic pop /* Use objc_msgSendSuper() for fp-returning messages to super. */ /* See also objc_msgSendv_fpret() below. */ diff --git a/runtime/objc-abi.h b/runtime/objc-abi.h index ddab3b8..bacf2e4 100644 --- a/runtime/objc-abi.h +++ b/runtime/objc-abi.h @@ -100,6 +100,7 @@ typedef struct objc_image_info { #if __cplusplus >= 201103L private: enum : uint32_t { + // 1 byte assorted flags IsReplacement = 1<<0, // used for Fix&Continue, now ignored SupportsGC = 1<<1, // image supports GC RequiresGC = 1<<2, // image requires GC @@ -107,17 +108,28 @@ typedef struct objc_image_info { CorrectedSynthesize = 1<<4, // used for an old workaround, now ignored IsSimulated = 1<<5, // image compiled for a simulator platform HasCategoryClassProperties = 1<<6, // class properties in category_t + // not yet used = 1<<7 - SwiftVersionMaskShift = 8, - SwiftVersionMask = 0xff << SwiftVersionMaskShift // Swift ABI version + // 1 byte Swift unstable ABI version number + SwiftUnstableVersionMaskShift = 8, + SwiftUnstableVersionMask = 0xff << SwiftUnstableVersionMaskShift, + // 2 byte Swift stable ABI version number + SwiftStableVersionMaskShift = 16, + SwiftStableVersionMask = 0xffffUL << SwiftStableVersionMaskShift }; - public: + public: enum : uint32_t { + // Values for SwiftUnstableVersion + // All stable ABIs store SwiftVersion5 here. SwiftVersion1 = 1, SwiftVersion1_2 = 2, SwiftVersion2 = 3, - SwiftVersion3 = 4 + SwiftVersion3 = 4, + SwiftVersion4 = 5, + SwiftVersion4_1 = 6, + SwiftVersion4_2 = 6, // [sic] + SwiftVersion5 = 7 }; public: @@ -126,8 +138,8 @@ typedef struct objc_image_info { bool requiresGC() const { return flags & RequiresGC; } bool optimizedByDyld() const { return flags & OptimizedByDyld; } bool hasCategoryClassProperties() const { return flags & HasCategoryClassProperties; } - bool containsSwift() const { return (flags & SwiftVersionMask) != 0; } - uint32_t swiftVersion() const { return (flags & SwiftVersionMask) >> SwiftVersionMaskShift; } + bool containsSwift() const { return (flags & SwiftUnstableVersionMask) != 0; } + uint32_t swiftUnstableVersion() const { return (flags & SwiftUnstableVersionMask) >> SwiftUnstableVersionMaskShift; } #endif } objc_image_info; diff --git a/runtime/objc-block-trampolines.h b/runtime/objc-block-trampolines.h index f5ec10a..c65eeef 100644 --- a/runtime/objc-block-trampolines.h +++ b/runtime/objc-block-trampolines.h @@ -37,6 +37,13 @@ * objc-block-trampolines.h: Symbols for IMP block trampolines */ +// WARNING: remapped code and dtrace do not play well together. Dtrace +// will place trap instructions to instrument the code, which then get +// remapped along with everything else. The remapped traps are not +// recognized by dtrace and the process crashes. To avoid this, dtrace +// blacklists this library by name. Do not change the name of this +// library. rdar://problem/42627391 + #include OBJC_EXPORT const char _objc_blockTrampolineImpl diff --git a/runtime/objc-block-trampolines.mm b/runtime/objc-block-trampolines.mm index c11423b..21879b7 100644 --- a/runtime/objc-block-trampolines.mm +++ b/runtime/objc-block-trampolines.mm @@ -163,7 +163,8 @@ void Initialize() { void *dylib = dlopen("/usr/lib/libobjc-trampolines.dylib", RTLD_NOW | RTLD_LOCAL | RTLD_FIRST); if (!dylib) { - _objc_fatal("couldn't dlopen libobjc-trampolines.dylib"); + _objc_fatal("couldn't dlopen libobjc-trampolines.dylib: %s", + dlerror()); } auto t = new TrampolinePointers(dylib); @@ -177,6 +178,9 @@ void Initialize() { uintptr_t textSegment() { return get()->textSegment; } uintptr_t textSegmentSize() { return get()->textSegmentSize; } + // See comments below about PAGE_SIZE and PAGE_MAX_SIZE. + uintptr_t dataSize() { return PAGE_MAX_SIZE; } + uintptr_t impl() { return get()->impl.address(); } uintptr_t start() { return get()->start.address(); } }; @@ -184,6 +188,8 @@ void Initialize() { static TrampolinePointerWrapper Trampolines; // argument mode identifier +// Some calculations assume that these modes are sequential starting from 0. +// This order must match the order of the trampoline's assembly code. typedef enum { ReturnValueInRegisterArgumentMode, #if SUPPORT_STRET @@ -211,8 +217,17 @@ void Initialize() { { TrampolineBlockPageGroup *nextPageGroup; // linked list of all pages TrampolineBlockPageGroup *nextAvailablePage; // linked list of pages with available slots - + uintptr_t nextAvailable; // index of next available slot, endIndex() if no more available + + const void * TrampolinePtrauth const text; // text VM region; stored only for the benefit of the leaks tool + + TrampolineBlockPageGroup() + : nextPageGroup(nil) + , nextAvailablePage(nil) + , nextAvailable(startIndex()) + , text((const void *)((uintptr_t)this + Trampolines.dataSize())) + { } // Payload data: block pointers and free list. // Bytes parallel with trampoline header code are the fields above or unused @@ -249,7 +264,7 @@ static uintptr_t startIndex() { } static uintptr_t endIndex() { - return (uintptr_t)PAGE_MAX_SIZE / slotSize(); + return (uintptr_t)Trampolines.dataSize() / slotSize(); } static bool validIndex(uintptr_t index) { @@ -262,8 +277,10 @@ static bool validIndex(uintptr_t index) { } uintptr_t trampolinesForMode(int aMode) { - // Skip over data page and Mach-O page. - return (uintptr_t)this + PAGE_MAX_SIZE * (2 + aMode); + // Skip over the data area, one page of Mach-O headers, + // and one text page for each mode before this one. + return (uintptr_t)this + Trampolines.dataSize() + + PAGE_MAX_SIZE * (1 + aMode); } IMP trampoline(int aMode, uintptr_t index) { @@ -334,7 +351,7 @@ static void check() { auto textSource = Trampolines.textSegment(); auto textSourceSize = Trampolines.textSegmentSize(); - auto dataSize = PAGE_MAX_SIZE; + auto dataSize = Trampolines.dataSize(); // Allocate a single contiguous region big enough to hold data+text. kern_return_t result; @@ -358,10 +375,7 @@ static void check() { _objc_fatal("vm_remap trampolines failed (%d)", result); } - TrampolineBlockPageGroup *pageGroup = (TrampolineBlockPageGroup *) dataAddress; - pageGroup->nextAvailable = pageGroup->startIndex(); - pageGroup->nextPageGroup = nil; - pageGroup->nextAvailablePage = nil; + auto *pageGroup = new ((void*)dataAddress) TrampolineBlockPageGroup; if (HeadPageGroup) { TrampolineBlockPageGroup *lastPageGroup = HeadPageGroup; diff --git a/runtime/objc-blocktramps-arm.s b/runtime/objc-blocktramps-arm.s index bbbe1cf..de80a43 100644 --- a/runtime/objc-blocktramps-arm.s +++ b/runtime/objc-blocktramps-arm.s @@ -32,16 +32,13 @@ L__objc_blockTrampolineImpl_func: mov r1, r0 // _cmd = self - // Trampoline's data is one page before the trampoline text. + // Trampoline's data is two pages before the trampoline text. // Also correct PC bias of 4 bytes. sub r12, # 2*PAGE_MAX_SIZE ldr r0, [r12, #-4] // self = block object ldr pc, [r0, #12] // tail call block->invoke // not reached - // Align trampolines to 8 bytes -.align 3 - .macro TrampolineEntry mov r12, pc b L__objc_blockTrampolineImpl_func @@ -92,8 +89,9 @@ L__objc_blockTrampolineImpl_func: TrampolineEntryX16 .endmacro +.align 5 __objc_blockTrampolineStart: - // 2048-2 trampolines to fill 16K page + // 2048-4 trampolines to fill 16K page TrampolineEntryX256 TrampolineEntryX256 TrampolineEntryX256 @@ -135,11 +133,11 @@ __objc_blockTrampolineStart: TrampolineEntry TrampolineEntry TrampolineEntry - TrampolineEntry - - TrampolineEntry __objc_blockTrampolineLast: TrampolineEntry + + // TrampolineEntry + // TrampolineEntry // TrampolineEntry // TrampolineEntry @@ -172,15 +170,12 @@ L__objc_blockTrampolineImpl_stret_func: mov r2, r1 // _cmd = self - // Trampoline's data is one page before the trampoline text. + // Trampoline's data is three pages before the trampoline text. // Also correct PC bias of 4 bytes. sub r12, # 3*PAGE_MAX_SIZE ldr r1, [r12, #-4] // self = block object ldr pc, [r1, #12] // tail call block->invoke // not reached - - // Align trampolines to 8 bytes -.align 3 .macro TrampolineEntry_stret mov r12, pc @@ -232,8 +227,9 @@ L__objc_blockTrampolineImpl_stret_func: TrampolineEntryX16_stret .endmacro +.align 5 __objc_blockTrampolineStart_stret: - // 2048-2 trampolines to fill 16K page + // 2048-4 trampolines to fill 16K page TrampolineEntryX256_stret TrampolineEntryX256_stret TrampolineEntryX256_stret @@ -275,11 +271,11 @@ __objc_blockTrampolineStart_stret: TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret - TrampolineEntry_stret - - TrampolineEntry_stret __objc_blockTrampolineLast_stret: TrampolineEntry_stret + + // TrampolineEntry_stret + // TrampolineEntry_stret // TrampolineEntry_stret // TrampolineEntry_stret diff --git a/runtime/objc-blocktramps-arm64.s b/runtime/objc-blocktramps-arm64.s index a79a031..d7e32a5 100644 --- a/runtime/objc-blocktramps-arm64.s +++ b/runtime/objc-blocktramps-arm64.s @@ -34,6 +34,8 @@ L_objc_blockTrampolineImpl: // pad up to TrampolineBlockPagePair header size nop + nop + nop .macro TrampolineEntry // load address of trampoline data (two pages before this instruction) @@ -87,7 +89,7 @@ L_objc_blockTrampolineImpl: .align 3 __objc_blockTrampolineStart: - // 2048-3 trampolines to fill 16K page + // 2048-4 trampolines to fill 16K page TrampolineEntryX256 TrampolineEntryX256 TrampolineEntryX256 @@ -129,10 +131,10 @@ __objc_blockTrampolineStart: TrampolineEntry TrampolineEntry TrampolineEntry - TrampolineEntry - __objc_blockTrampolineLast: TrampolineEntry + + // TrampolineEntry // TrampolineEntry // TrampolineEntry // TrampolineEntry diff --git a/runtime/objc-blocktramps-i386.s b/runtime/objc-blocktramps-i386.s index f2a1ace..d4f1eb8 100755 --- a/runtime/objc-blocktramps-i386.s +++ b/runtime/objc-blocktramps-i386.s @@ -32,20 +32,21 @@ .align PAGE_SHIFT __objc_blockTrampolineImpl: - popl %eax - andl $0xFFFFFFF8, %eax - subl $ 2*PAGE_SIZE, %eax - movl 4(%esp), %ecx // self -> ecx - movl %ecx, 8(%esp) // ecx -> _cmd - movl (%eax), %ecx // blockPtr -> ecx - movl %ecx, 4(%esp) // ecx -> self - jmp *12(%ecx) // tail to block->invoke + movl (%esp), %eax // return address pushed by trampoline + // 4(%esp) is return address pushed by the call site + movl 8(%esp), %ecx // self -> ecx + movl %ecx, 12(%esp) // ecx -> _cmd + movl -2*PAGE_SIZE-5(%eax), %ecx // block object pointer -> ecx + // trampoline is -5 bytes from the return address + // data is -2 pages from the trampoline + movl %ecx, 8(%esp) // ecx -> self + ret // back to TrampolineEntry to preserve CPU's return stack .macro TrampolineEntry - call __objc_blockTrampolineImpl - nop - nop - nop + // This trampoline is 8 bytes long. + // This callq is 5 bytes long. + calll __objc_blockTrampolineImpl + jmp *12(%ecx) // tail call block->invoke .endmacro .align 5 @@ -568,20 +569,22 @@ __objc_blockTrampolineLast: .align PAGE_SHIFT __objc_blockTrampolineImpl_stret: - popl %eax - andl $0xFFFFFFF8, %eax - subl $ 3*PAGE_SIZE, %eax - movl 8(%esp), %ecx // self -> ecx - movl %ecx, 12(%esp) // ecx -> _cmd - movl (%eax), %ecx // blockPtr -> ecx - movl %ecx, 8(%esp) // ecx -> self - jmp *12(%ecx) // tail to block->invoke + movl (%esp), %eax // return address pushed by trampoline + // 4(%esp) is return address pushed by the call site + // 8(%esp) is struct-return address + movl 12(%esp), %ecx // self -> ecx + movl %ecx, 16(%esp) // ecx -> _cmd + movl -3*PAGE_SIZE-5(%eax), %ecx // block object pointer -> ecx + // trampoline is -5 bytes from the return address + // data is -3 pages from the trampoline + movl %ecx, 12(%esp) // ecx -> self + ret .macro TrampolineEntry_stret + // This trampoline is 8 bytes long. + // This callq is 5 bytes long. call __objc_blockTrampolineImpl_stret - nop - nop - nop + jmp *12(%ecx) // tail to block->invoke .endmacro .align 5 diff --git a/runtime/objc-blocktramps-x86_64.s b/runtime/objc-blocktramps-x86_64.s index 4423859..5f377f0 100755 --- a/runtime/objc-blocktramps-x86_64.s +++ b/runtime/objc-blocktramps-x86_64.s @@ -32,18 +32,18 @@ .align PAGE_SHIFT __objc_blockTrampolineImpl: - popq %r10 - andq $0xFFFFFFFFFFFFFFF8, %r10 - subq $ 2*PAGE_SIZE, %r10 - movq %rdi, %rsi // arg1 -> arg2 - movq (%r10), %rdi // block -> arg1 - jmp *16(%rdi) + movq (%rsp), %r10 // read return address pushed by TrampolineEntry's callq + movq %rdi, %rsi // arg1 -> arg2 + movq -2*PAGE_SIZE-5(%r10), %rdi // block object pointer -> arg1 + // trampoline is -5 bytes from the return address + // data is -2 pages from the trampoline + ret // back to TrampolineEntry to preserve CPU's return stack .macro TrampolineEntry + // This trampoline is 8 bytes long. + // This callq is 5 bytes long. callq __objc_blockTrampolineImpl - nop - nop - nop + jmp *16(%rdi) .endmacro .align 5 @@ -566,19 +566,20 @@ __objc_blockTrampolineLast: .align PAGE_SHIFT __objc_blockTrampolineImpl_stret: - popq %r10 - andq $0xFFFFFFFFFFFFFFF8, %r10 - subq $ 3*PAGE_SIZE, %r10 - // %rdi -- first arg -- is address of return value's space. Don't mess with it. - movq %rsi, %rdx // arg2 -> arg3 - movq (%r10), %rsi // block -> arg2 - jmp *16(%rsi) + + // %rdi -- arg1 -- is address of return value's space. Don't mess with it. + movq (%rsp), %r10 // read return address pushed by TrampolineEntry's callq + movq %rsi, %rdx // arg2 -> arg3 + movq -3*PAGE_SIZE-5(%r10), %rsi // block object pointer -> arg2 + // trampoline is -5 bytes from the return address + // data is -3 pages from the trampoline + ret // back to TrampolineEntry to preserve CPU's return stack .macro TrampolineEntry_stret + // This trampoline is 8 bytes long. + // This callq is 5 bytes long. callq __objc_blockTrampolineImpl_stret - nop - nop - nop + jmp *16(%rsi) .endmacro .align 5 diff --git a/runtime/objc-cache.mm b/runtime/objc-cache.mm index 73b172c..7602fe0 100644 --- a/runtime/objc-cache.mm +++ b/runtime/objc-cache.mm @@ -243,9 +243,9 @@ __asm__ __volatile__( \ // Class points to cache. SEL is key. Cache buckets store SEL+IMP. // Caches are never built in the dyld shared cache. -static inline mask_t cache_hash(cache_key_t key, mask_t mask) +static inline mask_t cache_hash(SEL sel, mask_t mask) { - return (mask_t)(key & mask); + return (mask_t)(uintptr_t)sel & mask; } cache_t *getCache(Class cls) @@ -254,52 +254,49 @@ static inline mask_t cache_hash(cache_key_t key, mask_t mask) return &cls->cache; } -cache_key_t getKey(SEL sel) -{ - assert(sel); - return (cache_key_t)sel; -} - #if __arm64__ -void bucket_t::set(cache_key_t newKey, IMP newImp) +template +void bucket_t::set(SEL newSel, IMP newImp) { - assert(_key == 0 || _key == newKey); + assert(_sel == 0 || _sel == newSel); - static_assert(offsetof(bucket_t,_imp) == 0 && offsetof(bucket_t,_key) == sizeof(void *), - "bucket_t doesn't match arm64 bucket_t::set()"); + static_assert(offsetof(bucket_t,_imp) == 0 && + offsetof(bucket_t,_sel) == sizeof(void *), + "bucket_t layout doesn't match arm64 bucket_t::set()"); -#if __has_feature(ptrauth_calls) - // Authenticate as a C function pointer and re-sign for the cache bucket. - uintptr_t signedImp = _imp.prepareWrite(newImp); -#else - // No function pointer signing. - uintptr_t signedImp = (uintptr_t)newImp; -#endif + uintptr_t signedImp = signIMP(newImp, newSel); - // Write to the bucket. - // LDP/STP guarantees that all observers get - // either imp/key or newImp/newKey - stp(signedImp, newKey, this); + if (atomicity == Atomic) { + // LDP/STP guarantees that all observers get + // either imp/sel or newImp/newSel + stp(signedImp, (uintptr_t)newSel, this); + } else { + _sel = newSel; + _imp = signedImp; + } } #else -void bucket_t::set(cache_key_t newKey, IMP newImp) +template +void bucket_t::set(SEL newSel, IMP newImp) { - assert(_key == 0 || _key == newKey); + assert(_sel == 0 || _sel == newSel); - // objc_msgSend uses key and imp with no locks. - // It is safe for objc_msgSend to see new imp but NULL key + // objc_msgSend uses sel and imp with no locks. + // It is safe for objc_msgSend to see new imp but NULL sel // (It will get a cache miss but not dispatch to the wrong place.) - // It is unsafe for objc_msgSend to see old imp and new key. - // Therefore we write new imp, wait a lot, then write new key. + // It is unsafe for objc_msgSend to see old imp and new sel. + // Therefore we write new imp, wait a lot, then write new sel. - _imp = newImp; + _imp = (uintptr_t)newImp; - if (_key != newKey) { - mega_barrier(); - _key = newKey; + if (_sel != newSel) { + if (atomicity == Atomic) { + mega_barrier(); + } + _sel = newSel; } } @@ -385,14 +382,12 @@ cache_key_t getKey(SEL sel) bucket_t *end = cache_t::endMarker(newBuckets, newCapacity); #if __arm__ - // End marker's key is 1 and imp points BEFORE the first bucket. + // End marker's sel is 1 and imp points BEFORE the first bucket. // This saves an instruction in objc_msgSend. - end->setKey((cache_key_t)(uintptr_t)1); - end->setImp((IMP)(newBuckets - 1)); + end->set((SEL)(uintptr_t)1, (IMP)(newBuckets - 1)); #else - // End marker's key is 1 and imp points to the first bucket. - end->setKey((cache_key_t)(uintptr_t)1); - end->setImp((IMP)newBuckets); + // End marker's sel is 1 and imp points to the first bucket. + end->set((SEL)(uintptr_t)1, (IMP)newBuckets); #endif if (PrintCaches) recordNewCache(newCapacity); @@ -521,23 +516,23 @@ cache_key_t getKey(SEL sel) } -bucket_t * cache_t::find(cache_key_t k, id receiver) +bucket_t * cache_t::find(SEL s, id receiver) { - assert(k != 0); + assert(s != 0); bucket_t *b = buckets(); mask_t m = mask(); - mask_t begin = cache_hash(k, m); + mask_t begin = cache_hash(s, m); mask_t i = begin; do { - if (b[i].key() == 0 || b[i].key() == k) { + if (b[i].sel() == 0 || b[i].sel() == s) { return &b[i]; } } while ((i = cache_next(i, m)) != begin); // hack Class cls = (Class)((uintptr_t)this - offsetof(objc_class, cache)); - cache_t::bad_cache(receiver, (SEL)k, cls); + cache_t::bad_cache(receiver, (SEL)s, cls); } @@ -570,7 +565,6 @@ static void cache_fill_nolock(Class cls, SEL sel, IMP imp, id receiver) if (cache_getImp(cls, sel)) return; cache_t *cache = getCache(cls); - cache_key_t key = getKey(sel); // Use the cache as-is if it is less than 3/4 full mask_t newOccupied = cache->occupied() + 1; @@ -590,9 +584,9 @@ static void cache_fill_nolock(Class cls, SEL sel, IMP imp, id receiver) // Scan for the first unused slot and insert there. // There is guaranteed to be an empty slot because the // minimum size is 4 and we resized at 3/4 full. - bucket_t *bucket = cache->find(key, receiver); - if (bucket->key() == 0) cache->incrementOccupied(); - bucket->set(key, imp); + bucket_t *bucket = cache->find(sel, receiver); + if (bucket->sel() == 0) cache->incrementOccupied(); + bucket->set(sel, imp); } void cache_fill(Class cls, SEL sel, IMP imp, id receiver) diff --git a/runtime/objc-class-old.mm b/runtime/objc-class-old.mm index dd13e22..e23718f 100644 --- a/runtime/objc-class-old.mm +++ b/runtime/objc-class-old.mm @@ -36,6 +36,7 @@ static Method _class_getMethod(Class cls, SEL sel); static Method _class_getMethodNoSuper(Class cls, SEL sel); static Method _class_getMethodNoSuper_nolock(Class cls, SEL sel); +static Class _class_getNonMetaClass(Class cls, id obj); static void flush_caches(Class cls, bool flush_meta); @@ -324,6 +325,118 @@ static void _freedHandler(id obj, SEL sel) } +/*********************************************************************** +* _class_resolveClassMethod +* Call +resolveClassMethod, looking for a method to be added to class cls. +* cls should be a metaclass. +* Does not check if the method already exists. +**********************************************************************/ +static void _class_resolveClassMethod(Class cls, SEL sel, id inst) +{ + assert(cls->isMetaClass()); + + if (! lookUpImpOrNil(cls, SEL_resolveClassMethod, inst, + NO/*initialize*/, YES/*cache*/, NO/*resolver*/)) + { + // Resolver not implemented. + return; + } + + BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend; + bool resolved = msg(_class_getNonMetaClass(cls, inst), + SEL_resolveClassMethod, sel); + + // Cache the result (good or bad) so the resolver doesn't fire next time. + // +resolveClassMethod adds to self->ISA() a.k.a. cls + IMP imp = lookUpImpOrNil(cls, sel, inst, + NO/*initialize*/, YES/*cache*/, NO/*resolver*/); + + if (resolved && PrintResolving) { + if (imp) { + _objc_inform("RESOLVE: method %c[%s %s] " + "dynamically resolved to %p", + cls->isMetaClass() ? '+' : '-', + cls->nameForLogging(), sel_getName(sel), imp); + } + else { + // Method resolver didn't add anything? + _objc_inform("RESOLVE: +[%s resolveClassMethod:%s] returned YES" + ", but no new implementation of %c[%s %s] was found", + cls->nameForLogging(), sel_getName(sel), + cls->isMetaClass() ? '+' : '-', + cls->nameForLogging(), sel_getName(sel)); + } + } +} + + +/*********************************************************************** +* _class_resolveInstanceMethod +* Call +resolveInstanceMethod, looking for a method to be added to class cls. +* cls may be a metaclass or a non-meta class. +* Does not check if the method already exists. +**********************************************************************/ +static void _class_resolveInstanceMethod(Class cls, SEL sel, id inst) +{ + if (! lookUpImpOrNil(cls->ISA(), SEL_resolveInstanceMethod, cls, + NO/*initialize*/, YES/*cache*/, NO/*resolver*/)) + { + // Resolver not implemented. + return; + } + + BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend; + bool resolved = msg(cls, SEL_resolveInstanceMethod, sel); + + // Cache the result (good or bad) so the resolver doesn't fire next time. + // +resolveInstanceMethod adds to self a.k.a. cls + IMP imp = lookUpImpOrNil(cls, sel, inst, + NO/*initialize*/, YES/*cache*/, NO/*resolver*/); + + if (resolved && PrintResolving) { + if (imp) { + _objc_inform("RESOLVE: method %c[%s %s] " + "dynamically resolved to %p", + cls->isMetaClass() ? '+' : '-', + cls->nameForLogging(), sel_getName(sel), imp); + } + else { + // Method resolver didn't add anything? + _objc_inform("RESOLVE: +[%s resolveInstanceMethod:%s] returned YES" + ", but no new implementation of %c[%s %s] was found", + cls->nameForLogging(), sel_getName(sel), + cls->isMetaClass() ? '+' : '-', + cls->nameForLogging(), sel_getName(sel)); + } + } +} + + +/*********************************************************************** +* _class_resolveMethod +* Call +resolveClassMethod or +resolveInstanceMethod. +* Returns nothing; any result would be potentially out-of-date already. +* Does not check if the method already exists. +**********************************************************************/ +void _class_resolveMethod(Class cls, SEL sel, id inst) +{ + if (! cls->isMetaClass()) { + // try [cls resolveInstanceMethod:sel] + _class_resolveInstanceMethod(cls, sel, inst); + } + else { + // try [nonMetaClass resolveClassMethod:sel] + // and [cls resolveInstanceMethod:sel] + _class_resolveClassMethod(cls, sel, inst); + if (!lookUpImpOrNil(cls, sel, inst, + NO/*initialize*/, YES/*cache*/, NO/*resolver*/)) + { + _class_resolveInstanceMethod(cls, sel, inst); + } + } +} + + /*********************************************************************** * log_and_fill_cache * Log this method call. If the logger permits it, fill the method cache. @@ -393,9 +506,9 @@ IMP lookUpImpOrForward(Class cls, SEL sel, id inst, // Check for +initialize if (initialize && !cls->isInitialized()) { - _class_initialize (_class_getNonMetaClass(cls, inst)); - // If sel == initialize, _class_initialize will send +initialize and - // then the messenger will send +initialize again after this + initializeNonMetaClass (_class_getNonMetaClass(cls, inst)); + // If sel == initialize, initializeNonMetaClass will send +initialize + // and then the messenger will send +initialize again after this // procedure finishes. Of course, if this is not being called // from the messenger then it won't happen. 2778172 } @@ -734,7 +847,7 @@ int class_getVersion(Class cls) * Return the ordinary class for this class or metaclass. * Used by +initialize. **********************************************************************/ -Class _class_getNonMetaClass(Class cls, id obj) +static Class _class_getNonMetaClass(Class cls, id obj) { // fixme ick if (cls->isMetaClass()) { @@ -759,6 +872,14 @@ Class _class_getNonMetaClass(Class cls, id obj) } +Class class_initialize(Class cls, id inst) { + if (!cls->isInitialized()) { + initializeNonMetaClass (_class_getNonMetaClass(cls, inst)); + } + return cls; +} + + Cache _class_getCache(Class cls) { return cls->cache; diff --git a/runtime/objc-class.mm b/runtime/objc-class.mm index bb9ceae..67b731e 100644 --- a/runtime/objc-class.mm +++ b/runtime/objc-class.mm @@ -195,7 +195,10 @@ Class object_setClass(id obj, Class cls) // weakly-referenced object has an un-+initialized isa. // Unresolved future classes are not so protected. if (!cls->isFuture() && !cls->isInitialized()) { - _class_initialize(_class_getNonMetaClass(cls, nil)); + // use lookUpImpOrForward to indirectly provoke +initialize + // to avoid duplicating the code to actually send +initialize + lookUpImpOrForward(cls, SEL_initialize, nil, + YES/*initialize*/, YES/*cache*/, NO/*resolver*/); } return obj->changeIsa(cls); @@ -564,117 +567,6 @@ void fixupCopiedIvars(id newObject, id oldObject) } -/*********************************************************************** -* _class_resolveClassMethod -* Call +resolveClassMethod, looking for a method to be added to class cls. -* cls should be a metaclass. -* Does not check if the method already exists. -**********************************************************************/ -static void _class_resolveClassMethod(Class cls, SEL sel, id inst) -{ - assert(cls->isMetaClass()); - - if (! lookUpImpOrNil(cls, SEL_resolveClassMethod, inst, - NO/*initialize*/, YES/*cache*/, NO/*resolver*/)) - { - // Resolver not implemented. - return; - } - - BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend; - bool resolved = msg(_class_getNonMetaClass(cls, inst), - SEL_resolveClassMethod, sel); - - // Cache the result (good or bad) so the resolver doesn't fire next time. - // +resolveClassMethod adds to self->ISA() a.k.a. cls - IMP imp = lookUpImpOrNil(cls, sel, inst, - NO/*initialize*/, YES/*cache*/, NO/*resolver*/); - - if (resolved && PrintResolving) { - if (imp) { - _objc_inform("RESOLVE: method %c[%s %s] " - "dynamically resolved to %p", - cls->isMetaClass() ? '+' : '-', - cls->nameForLogging(), sel_getName(sel), imp); - } - else { - // Method resolver didn't add anything? - _objc_inform("RESOLVE: +[%s resolveClassMethod:%s] returned YES" - ", but no new implementation of %c[%s %s] was found", - cls->nameForLogging(), sel_getName(sel), - cls->isMetaClass() ? '+' : '-', - cls->nameForLogging(), sel_getName(sel)); - } - } -} - - -/*********************************************************************** -* _class_resolveInstanceMethod -* Call +resolveInstanceMethod, looking for a method to be added to class cls. -* cls may be a metaclass or a non-meta class. -* Does not check if the method already exists. -**********************************************************************/ -static void _class_resolveInstanceMethod(Class cls, SEL sel, id inst) -{ - if (! lookUpImpOrNil(cls->ISA(), SEL_resolveInstanceMethod, cls, - NO/*initialize*/, YES/*cache*/, NO/*resolver*/)) - { - // Resolver not implemented. - return; - } - - BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend; - bool resolved = msg(cls, SEL_resolveInstanceMethod, sel); - - // Cache the result (good or bad) so the resolver doesn't fire next time. - // +resolveInstanceMethod adds to self a.k.a. cls - IMP imp = lookUpImpOrNil(cls, sel, inst, - NO/*initialize*/, YES/*cache*/, NO/*resolver*/); - - if (resolved && PrintResolving) { - if (imp) { - _objc_inform("RESOLVE: method %c[%s %s] " - "dynamically resolved to %p", - cls->isMetaClass() ? '+' : '-', - cls->nameForLogging(), sel_getName(sel), imp); - } - else { - // Method resolver didn't add anything? - _objc_inform("RESOLVE: +[%s resolveInstanceMethod:%s] returned YES" - ", but no new implementation of %c[%s %s] was found", - cls->nameForLogging(), sel_getName(sel), - cls->isMetaClass() ? '+' : '-', - cls->nameForLogging(), sel_getName(sel)); - } - } -} - - -/*********************************************************************** -* _class_resolveMethod -* Call +resolveClassMethod or +resolveInstanceMethod. -* Returns nothing; any result would be potentially out-of-date already. -* Does not check if the method already exists. -**********************************************************************/ -void _class_resolveMethod(Class cls, SEL sel, id inst) -{ - if (! cls->isMetaClass()) { - // try [cls resolveInstanceMethod:sel] - _class_resolveInstanceMethod(cls, sel, inst); - } - else { - // try [nonMetaClass resolveClassMethod:sel] - // and [cls resolveInstanceMethod:sel] - _class_resolveClassMethod(cls, sel, inst); - if (!lookUpImpOrNil(cls, sel, inst, - NO/*initialize*/, YES/*cache*/, NO/*resolver*/)) - { - _class_resolveInstanceMethod(cls, sel, inst); - } - } -} - /*********************************************************************** * class_getClassMethod. Return the class method for the specified diff --git a/runtime/objc-gdb.h b/runtime/objc-gdb.h index 88dc2a8..67ce2cb 100644 --- a/runtime/objc-gdb.h +++ b/runtime/objc-gdb.h @@ -204,6 +204,15 @@ OBJC_EXPORT unsigned int objc_debug_taggedpointer_ext_payload_rshift #endif + +/*********************************************************************** +* Swift marker bits +**********************************************************************/ +#if __OBJC2__ +OBJC_EXPORT const uintptr_t objc_debug_swift_stable_abi_bit +OBJC_AVAILABLE(10.14, 12.0, 12.0, 5.0, 3.0); +#endif + __END_DECLS // APPLE_API_PRIVATE diff --git a/runtime/objc-initialize.h b/runtime/objc-initialize.h index 9ec99b5..c4695d5 100644 --- a/runtime/objc-initialize.h +++ b/runtime/objc-initialize.h @@ -30,7 +30,7 @@ __BEGIN_DECLS struct _objc_initializing_classes; -extern void _class_initialize(Class cls); +extern void initializeNonMetaClass(Class cls); extern void _destroyInitializingClassList(struct _objc_initializing_classes *list); diff --git a/runtime/objc-initialize.mm b/runtime/objc-initialize.mm index 80491bb..8f962aa 100644 --- a/runtime/objc-initialize.mm +++ b/runtime/objc-initialize.mm @@ -481,7 +481,7 @@ void performForkChildInitialize(Class cls, Class supercls) * class_initialize. Send the '+initialize' message on demand to any * uninitialized class. Force initialization of superclasses first. **********************************************************************/ -void _class_initialize(Class cls) +void initializeNonMetaClass(Class cls) { assert(!cls->isMetaClass()); @@ -492,7 +492,7 @@ void _class_initialize(Class cls) // See note about deadlock above. supercls = cls->superclass; if (supercls && !supercls->isInitialized()) { - _class_initialize(supercls); + initializeNonMetaClass(supercls); } // Try to atomically set CLS_INITIALIZING. diff --git a/runtime/objc-internal.h b/runtime/objc-internal.h index 44e89ff..a3e0fee 100644 --- a/runtime/objc-internal.h +++ b/runtime/objc-internal.h @@ -57,6 +57,11 @@ __BEGIN_DECLS +// This symbol is exported only from debug builds of libobjc itself. +#if defined(OBJC_IS_DEBUG_BUILD) +OBJC_EXPORT void _objc_isDebugBuild(void); +#endif + // In-place construction of an Objective-C class. // cls and metacls must each be OBJC_MAX_CLASS_SIZE bytes. // Returns nil if a class with the same name already exists. @@ -659,6 +664,11 @@ OBJC_EXPORT id _Nullable objc_allocWithZone(Class _Nullable cls) OBJC_AVAILABLE(10.9, 7.0, 9.0, 1.0, 2.0); +OBJC_EXPORT id _Nullable +objc_alloc_init(Class _Nullable cls) + OBJC_AVAILABLE(10.14, 12.0, 12.0, 5.0, 3.0); +// rdar://44986431 fixme correct availability for objc_alloc_init() + OBJC_EXPORT id _Nullable objc_retain(id _Nullable obj) __asm__("_objc_retain") diff --git a/runtime/objc-object.h b/runtime/objc-object.h index 177ea10..4c68e24 100644 --- a/runtime/objc-object.h +++ b/runtime/objc-object.h @@ -43,7 +43,7 @@ bool prepareOptimizedReturn(ReturnDisposition disposition); #if SUPPORT_TAGGED_POINTERS extern "C" { - extern Class objc_debug_taggedpointer_classes[_OBJC_TAG_SLOT_COUNT*2]; + extern Class objc_debug_taggedpointer_classes[_OBJC_TAG_SLOT_COUNT]; extern Class objc_debug_taggedpointer_ext_classes[_OBJC_TAG_EXT_SLOT_COUNT]; } #define objc_tag_classes objc_debug_taggedpointer_classes diff --git a/runtime/objc-os.h b/runtime/objc-os.h index 24addca..ae29020 100644 --- a/runtime/objc-os.h +++ b/runtime/objc-os.h @@ -132,125 +132,67 @@ subc(uintptr_t lhs, uintptr_t rhs, uintptr_t carryin, uintptr_t *carryout) return __builtin_subcl(lhs, rhs, carryin, carryout); } - -#if __arm64__ - -// Pointer-size register prefix for inline asm -# if __LP64__ -# define p "x" // true arm64 -# else -# define p "w" // arm64_32 -# endif +#if __arm64__ && !__arm64e__ static ALWAYS_INLINE -uintptr_t +uintptr_t LoadExclusive(uintptr_t *src) { - uintptr_t result; - asm("ldxr %" p "0, [%x1]" - : "=r" (result) - : "r" (src), "m" (*src)); - return result; + return __builtin_arm_ldrex(src); } static ALWAYS_INLINE -bool +bool StoreExclusive(uintptr_t *dst, uintptr_t oldvalue __unused, uintptr_t value) { - uint32_t result; - asm("stxr %w0, %" p "2, [%x3]" - : "=&r" (result), "=m" (*dst) - : "r" (value), "r" (dst)); - return !result; + return !__builtin_arm_strex(value, dst); } static ALWAYS_INLINE -bool +bool StoreReleaseExclusive(uintptr_t *dst, uintptr_t oldvalue __unused, uintptr_t value) { - uint32_t result; - asm("stlxr %w0, %" p "2, [%x3]" - : "=&r" (result), "=m" (*dst) - : "r" (value), "r" (dst)); - return !result; -} - -static ALWAYS_INLINE -void -ClearExclusive(uintptr_t *dst) -{ - // pretend it writes to *dst for instruction ordering purposes - asm("clrex" : "=m" (*dst)); -} - -#undef p - -#elif __arm__ - -static ALWAYS_INLINE -uintptr_t -LoadExclusive(uintptr_t *src) -{ - return *src; + return !__builtin_arm_stlex(value, dst); } static ALWAYS_INLINE -bool -StoreExclusive(uintptr_t *dst, uintptr_t oldvalue, uintptr_t value) -{ - return OSAtomicCompareAndSwapPtr((void *)oldvalue, (void *)value, - (void **)dst); -} - -static ALWAYS_INLINE -bool -StoreReleaseExclusive(uintptr_t *dst, uintptr_t oldvalue, uintptr_t value) -{ - return OSAtomicCompareAndSwapPtrBarrier((void *)oldvalue, (void *)value, - (void **)dst); -} - -static ALWAYS_INLINE -void +void ClearExclusive(uintptr_t *dst __unused) { + __builtin_arm_clrex(); } - -#elif __x86_64__ || __i386__ +#else static ALWAYS_INLINE -uintptr_t +uintptr_t LoadExclusive(uintptr_t *src) { - return *src; + return __c11_atomic_load((_Atomic(uintptr_t) *)src, __ATOMIC_RELAXED); } static ALWAYS_INLINE -bool +bool StoreExclusive(uintptr_t *dst, uintptr_t oldvalue, uintptr_t value) { - - return __sync_bool_compare_and_swap((void **)dst, (void *)oldvalue, (void *)value); + return __c11_atomic_compare_exchange_weak((_Atomic(uintptr_t) *)dst, &oldvalue, value, __ATOMIC_RELAXED, __ATOMIC_RELAXED); } + static ALWAYS_INLINE -bool +bool StoreReleaseExclusive(uintptr_t *dst, uintptr_t oldvalue, uintptr_t value) { - return StoreExclusive(dst, oldvalue, value); + return __c11_atomic_compare_exchange_weak((_Atomic(uintptr_t) *)dst, &oldvalue, value, __ATOMIC_RELEASE, __ATOMIC_RELAXED); } static ALWAYS_INLINE -void +void ClearExclusive(uintptr_t *dst __unused) { } - -#else -# error unknown architecture #endif @@ -646,7 +588,7 @@ OBJC_EXTERN IMAGE_DOS_HEADER __ImageBase; // OS compatibility static inline uint64_t nanoseconds() { - return mach_absolute_time(); + return clock_gettime_nsec_np(CLOCK_MONOTONIC_RAW); } // Internal data types diff --git a/runtime/objc-private.h b/runtime/objc-private.h index 66994f6..f28cd38 100644 --- a/runtime/objc-private.h +++ b/runtime/objc-private.h @@ -462,6 +462,7 @@ extern IMP lookUpImpOrForward(Class, SEL, id obj, bool initialize, bool cache, b extern IMP lookupMethodInClassAndLoadCache(Class cls, SEL sel); extern bool class_respondsToSelector_inst(Class cls, SEL sel, id inst); +extern Class class_initialize(Class cls, id inst); extern bool objcMsgLogEnabled; extern bool logMessageSend(bool isClassMethod, @@ -551,6 +552,9 @@ typedef struct { struct SyncCache *syncCache; // for @synchronize struct alt_handler_list *handlerList; // for exception alt handlers char *printableNames[4]; // temporary demangled names for logging + const char **classNameLookups; // for objc_getClass() hooks + unsigned classNameLookupsAllocated; + unsigned classNameLookupsUsed; // If you add new fields here, don't forget to update // _objc_pthread_destroyspecific() @@ -618,7 +622,6 @@ extern void _unload_image(header_info *hi); extern const header_info *_headerForClass(Class cls); extern Class _class_remap(Class cls); -extern Class _class_getNonMetaClass(Class cls, id obj); extern Ivar _class_getVariable(Class cls, const char *name); extern unsigned _class_createInstancesFromZone(Class cls, size_t extraBytes, void *zone, id *results, unsigned num_requested); @@ -632,8 +635,6 @@ extern IMP _category_getLoadMethod(Category cat); extern id object_cxxConstructFromClass(id obj, Class cls); extern void object_cxxDestruct(id obj); -extern void _class_resolveMethod(Class cls, SEL sel, id inst); - extern void fixupCopiedIvars(id newObject, id oldObject); extern Class _class_getClassForIvar(Class cls, Ivar ivar); diff --git a/runtime/objc-ptrauth.h b/runtime/objc-ptrauth.h index f766d26..e275dca 100644 --- a/runtime/objc-ptrauth.h +++ b/runtime/objc-ptrauth.h @@ -28,9 +28,6 @@ // On some architectures, method lists and method caches store signed IMPs. -// StorageSignedFunctionPointer is declared by libclosure. -#include - // fixme simply include ptrauth.h once all build trains have it #if __has_include () #include @@ -66,17 +63,16 @@ #if __has_feature(ptrauth_calls) +#if !__arm64__ +#error ptrauth other than arm64e is unimplemented +#endif + // Method lists use process-independent signature for compatibility. -// Method caches use process-dependent signature for extra protection. -// (fixme not yet __ptrauth(...) because of `stp` inline asm in objc-cache.mm) using MethodListIMP = IMP __ptrauth_objc_method_list_imp; -using MethodCacheIMP = - StorageSignedFunctionPointer; #else using MethodListIMP = IMP; -using MethodCacheIMP = IMP; #endif diff --git a/runtime/objc-references.mm b/runtime/objc-references.mm index c1119f0..7de8187 100644 --- a/runtime/objc-references.mm +++ b/runtime/objc-references.mm @@ -269,6 +269,16 @@ void operator() (ObjcAssociation &association) { }; void _object_set_associative_reference(id object, void *key, id value, uintptr_t policy) { + // This code used to work when nil was passed for object and key. Some code + // probably relies on that to not crash. Check and handle it explicitly. + // rdar://problem/44094390 + if (!object && !value) return; + + assert(object); + + if (object->getIsa()->forbidsAssociatedObjects()) + _objc_fatal("objc_setAssociatedObject called on instance (%p) of class %s which does not allow associated objects", object, object_getClassName(object)); + // retain the new value (if any) outside the lock. ObjcAssociation old_association(0, nil); id new_value = value ? acquireValue(value, policy) : nil; diff --git a/runtime/objc-runtime-new.h b/runtime/objc-runtime-new.h index 04a4c65..19258f6 100644 --- a/runtime/objc-runtime-new.h +++ b/runtime/objc-runtime-new.h @@ -29,30 +29,53 @@ typedef uint32_t mask_t; // x86_64 & arm64 asm are less efficient with 16-bits #else typedef uint16_t mask_t; #endif -typedef uintptr_t cache_key_t; +typedef uintptr_t SEL; struct swift_class_t; +enum Atomicity { Atomic = true, NotAtomic = false }; struct bucket_t { private: // IMP-first is better for arm64e ptrauth and no worse for arm64. // SEL-first is better for armv7* and i386 and x86_64. #if __arm64__ - MethodCacheIMP _imp; - cache_key_t _key; + uintptr_t _imp; + SEL _sel; #else - cache_key_t _key; - MethodCacheIMP _imp; + SEL _sel; + uintptr_t _imp; #endif + // Compute the ptrauth signing modifier from &_imp and newSel + uintptr_t modifierForSEL(SEL newSel) const { + return (uintptr_t)&_imp ^ (uintptr_t)newSel; + } + + // Sign newImp, with &_imp and newSel as modifiers. + uintptr_t signIMP(IMP newImp, SEL newSel) const { + if (!newImp) return 0; + return (uintptr_t) + ptrauth_auth_and_resign(newImp, + ptrauth_key_function_pointer, 0, + ptrauth_key_process_dependent_code, + modifierForSEL(newSel)); + } + public: - inline cache_key_t key() const { return _key; } - inline IMP imp() const { return (IMP)_imp; } - inline void setKey(cache_key_t newKey) { _key = newKey; } - inline void setImp(IMP newImp) { _imp = newImp; } + inline SEL sel() const { return _sel; } + + inline IMP imp() const { + if (!_imp) return nil; + return (IMP) + ptrauth_auth_and_resign((const void *)_imp, + ptrauth_key_process_dependent_code, + modifierForSEL(_sel), + ptrauth_key_function_pointer, 0); + } - void set(cache_key_t newKey, IMP newImp); + template + void set(SEL newSel, IMP newImp); }; @@ -78,7 +101,7 @@ struct cache_t { void expand(); void reallocate(mask_t oldCapacity, mask_t newCapacity); - struct bucket_t * find(cache_key_t key, id receiver); + struct bucket_t * find(SEL sel, id receiver); static void bad_cache(id receiver, SEL sel, Class isa) __attribute__((noreturn)); }; @@ -402,14 +425,16 @@ struct locstamped_category_list_t { #define RO_HIDDEN (1<<4) // class has attribute(objc_exception): OBJC_EHTYPE_$_ThisClass is non-weak #define RO_EXCEPTION (1<<5) -// this bit is available for reassignment -// #define RO_REUSE_ME (1<<6) +// class has ro field for Swift metadata initializer callback +#define RO_HAS_SWIFT_INITIALIZER (1<<6) // class compiled with ARC #define RO_IS_ARC (1<<7) // class has .cxx_destruct but no .cxx_construct (with RO_HAS_CXX_STRUCTORS) #define RO_HAS_CXX_DTOR_ONLY (1<<8) // class is not ARC but has ARC-style weak ivar layout #define RO_HAS_WEAK_WITHOUT_ARC (1<<9) +// class does not allow associated objects on instances +#define RO_FORBIDS_ASSOCIATED_OBJECTS (1<<10) // class is in an unloadable bundle - must never be set by compiler #define RO_FROM_BUNDLE (1<<29) @@ -445,8 +470,8 @@ struct locstamped_category_list_t { #endif // class has instance-specific GC layout #define RW_HAS_INSTANCE_SPECIFIC_LAYOUT (1 << 21) -// available for use -// #define RW_20 (1<<20) +// class does not allow associated objects on its instances +#define RW_FORBIDS_ASSOCIATED_OBJECTS (1<<20) // class has started realizing but not yet completed it #define RW_REALIZING (1<<19) @@ -568,9 +593,33 @@ struct class_ro_t { const uint8_t * weakIvarLayout; property_list_t *baseProperties; + // This field exists only when RO_HAS_SWIFT_INITIALIZER is set. + _objc_swiftMetadataInitializer __ptrauth_objc_method_list_imp _swiftMetadataInitializer_NEVER_USE[0]; + + _objc_swiftMetadataInitializer swiftMetadataInitializer() const { + if (flags & RO_HAS_SWIFT_INITIALIZER) { + return _swiftMetadataInitializer_NEVER_USE[0]; + } else { + return nil; + } + } + method_list_t *baseMethods() const { return baseMethodList; } + + class_ro_t *duplicate() const { + if (flags & RO_HAS_SWIFT_INITIALIZER) { + size_t size = sizeof(*this) + sizeof(_swiftMetadataInitializer_NEVER_USE[0]); + class_ro_t *ro = (class_ro_t *)memdup(this, size); + ro->_swiftMetadataInitializer_NEVER_USE[0] = this->_swiftMetadataInitializer_NEVER_USE[0]; + return ro; + } else { + size_t size = sizeof(*this); + class_ro_t *ro = (class_ro_t *)memdup(this, size); + return ro; + } + } }; @@ -878,43 +927,47 @@ struct class_data_bits_t { } #if FAST_ALLOC - static uintptr_t updateFastAlloc(uintptr_t oldBits, uintptr_t change) + // On entry, `newBits` is a bits value after setting and/or clearing + // the bits in `change`. Fix the fast-alloc parts of newBits if necessary + // and return the updated value. + static uintptr_t updateFastAlloc(uintptr_t newBits, uintptr_t change) { if (change & FAST_ALLOC_MASK) { - if (((oldBits & FAST_ALLOC_MASK) == FAST_ALLOC_VALUE) && - ((oldBits >> FAST_SHIFTED_SIZE_SHIFT) != 0)) + if (((newBits & FAST_ALLOC_MASK) == FAST_ALLOC_VALUE) && + ((newBits >> FAST_SHIFTED_SIZE_SHIFT) != 0)) { - oldBits |= FAST_ALLOC; + newBits |= FAST_ALLOC; } else { - oldBits &= ~FAST_ALLOC; + newBits &= ~FAST_ALLOC; } } - return oldBits; + return newBits; } #else - static uintptr_t updateFastAlloc(uintptr_t oldBits, uintptr_t change) { - return oldBits; + static uintptr_t updateFastAlloc(uintptr_t newBits, uintptr_t change) { + return newBits; } #endif - void setBits(uintptr_t set) + // Atomically set the bits in `set` and clear the bits in `clear`. + // set and clear must not overlap. + void setAndClearBits(uintptr_t set, uintptr_t clear) { + assert((set & clear) == 0); uintptr_t oldBits; uintptr_t newBits; do { oldBits = LoadExclusive(&bits); - newBits = updateFastAlloc(oldBits | set, set); + newBits = updateFastAlloc((oldBits | set) & ~clear, set | clear); } while (!StoreReleaseExclusive(&bits, oldBits, newBits)); } - void clearBits(uintptr_t clear) - { - uintptr_t oldBits; - uintptr_t newBits; - do { - oldBits = LoadExclusive(&bits); - newBits = updateFastAlloc(oldBits & ~clear, clear); - } while (!StoreReleaseExclusive(&bits, oldBits, newBits)); + void setBits(uintptr_t set) { + setAndClearBits(set, 0); + } + + void clearBits(uintptr_t clear) { + setAndClearBits(0, clear); } public: @@ -933,6 +986,20 @@ struct class_data_bits_t { bits = newBits; } + // Get the class's ro data, even in the presence of concurrent realization. + // fixme this isn't really safe without a compiler barrier at least + // and probably a memory barrier when realizeClass changes the data field + const class_ro_t *safe_ro() { + class_rw_t *maybe_rw = data(); + if (maybe_rw->flags & RW_REALIZED) { + // maybe_rw is rw + return maybe_rw->ro; + } else { + // maybe_rw is actually ro + return (class_ro_t *)maybe_rw; + } + } + #if FAST_HAS_DEFAULT_RR bool hasDefaultRR() { return getBit(FAST_HAS_DEFAULT_RR); @@ -1096,14 +1163,26 @@ struct class_data_bits_t { return getBit(FAST_IS_SWIFT_STABLE); } void setIsSwiftStable() { - setBits(FAST_IS_SWIFT_STABLE); + setAndClearBits(FAST_IS_SWIFT_STABLE, FAST_IS_SWIFT_LEGACY); } bool isSwiftLegacy() { return getBit(FAST_IS_SWIFT_LEGACY); } void setIsSwiftLegacy() { - setBits(FAST_IS_SWIFT_LEGACY); + setAndClearBits(FAST_IS_SWIFT_LEGACY, FAST_IS_SWIFT_STABLE); + } + + // fixme remove this once the Swift runtime uses the stable bits + bool isSwiftStable_ButAllowLegacyForNow() { + return isAnySwift(); + } + + _objc_swiftMetadataInitializer swiftMetadataInitializer() { + // This function is called on un-realized classes without + // holding any locks. + // Beware of races with other realizers. + return safe_ro()->swiftMetadataInitializer(); } }; @@ -1205,6 +1284,40 @@ struct objc_class : objc_object { return bits.isAnySwift(); } + bool isSwiftStable_ButAllowLegacyForNow() { + return bits.isSwiftStable_ButAllowLegacyForNow(); + } + + // Swift stable ABI built for old deployment targets looks weird. + // The is-legacy bit is set for compatibility with old libobjc. + // We are on a "new" deployment target so we need to rewrite that bit. + // These stable-with-legacy-bit classes are distinguished from real + // legacy classes using another bit in the Swift data + // (ClassFlags::IsSwiftPreStableABI) + + bool isUnfixedBackwardDeployingStableSwift() { + // Only classes marked as Swift legacy need apply. + if (!bits.isSwiftLegacy()) return false; + + // Check the true legacy vs stable distinguisher. + // The low bit of Swift's ClassFlags is SET for true legacy + // and UNSET for stable pretending to be legacy. + uint32_t swiftClassFlags = *(uint32_t *)(&bits + 1); + bool isActuallySwiftLegacy = bool(swiftClassFlags & 1); + return !isActuallySwiftLegacy; + } + + void fixupBackwardDeployingStableSwift() { + if (isUnfixedBackwardDeployingStableSwift()) { + // Class really is stable Swift, pretending to be pre-stable. + // Fix its lie. + bits.setIsSwiftStable(); + } + } + + _objc_swiftMetadataInitializer swiftMetadataInitializer() { + return bits.swiftMetadataInitializer(); + } // Return YES if the class's ivars are managed by ARC, // or the class is MRC but has ARC-style weak ivars. @@ -1218,6 +1331,10 @@ struct objc_class : objc_object { } + bool forbidsAssociatedObjects() { + return (data()->flags & RW_FORBIDS_ASSOCIATED_OBJECTS); + } + #if SUPPORT_NONPOINTER_ISA // Tracked in non-pointer isas; not tracked otherwise #else @@ -1281,6 +1398,11 @@ struct objc_class : objc_object { return data()->ro->flags & RO_META; } + // Like isMetaClass, but also valid on un-realized classes + bool isMetaClassMaybeUnrealized() { + return bits.safe_ro()->flags & RO_META; + } + // NOT identical to this->ISA when this is a metaclass Class getMeta() { if (isMetaClass()) return (Class)this; @@ -1305,7 +1427,7 @@ struct objc_class : objc_object { } } - const char *demangledName(bool realize = false); + const char *demangledName(); const char *nameForLogging(); // May be unaligned depending on class's ivars. diff --git a/runtime/objc-runtime-new.mm b/runtime/objc-runtime-new.mm index b52aaa0..f7d09c9 100644 --- a/runtime/objc-runtime-new.mm +++ b/runtime/objc-runtime-new.mm @@ -42,7 +42,6 @@ static void detach_class(Class cls, bool isMeta); static void free_class(Class cls); static Class setSuperclass(Class cls, Class newSuper); -static Class realizeClass(Class cls); static method_t *getMethodNoSuper_nolock(Class cls, SEL sel); static method_t *getMethod_nolock(Class cls, SEL sel); static IMP addMethod(Class cls, SEL name, IMP imp, const char *types, bool replace); @@ -57,6 +56,8 @@ #if SUPPORT_FIXUP static void fixupMessageRef(message_ref_t *msg); #endif +static Class realizeClassMaybeSwiftAndUnlock(Class cls, mutex_t& lock); +static Class readClass(Class cls, bool headerIsBundle, bool headerIsPreoptimized); static bool MetaclassNSObjectAWZSwizzled; static bool ClassNSObjectRRSwizzled; @@ -174,6 +175,12 @@ void lock_init(void) #endif +/*********************************************************************** +* Swift marker bits +**********************************************************************/ +const uintptr_t objc_debug_swift_stable_abi_bit = FAST_IS_SWIFT_STABLE; + + /*********************************************************************** * allocatedClasses * A table of all classes (and metaclasses) which have been allocated @@ -354,9 +361,7 @@ void _objc_setClassCopyFixupHandler(void (* _Nonnull newFixupHandler) if (rw->flags & RW_COPIED_RO) { // already writeable, do nothing } else { - class_ro_t *ro = (class_ro_t *) - memdup(rw->ro, sizeof(*rw->ro)); - rw->ro = ro; + rw->ro = rw->ro->duplicate(); rw->flags |= RW_COPIED_RO; } return (class_ro_t *)rw->ro; @@ -930,7 +935,8 @@ static void remethodizeClass(Class cls) * Classes with no duplicates are not included. * Classes in the preoptimized named-class table are not included. * Classes whose duplicates are in the preoptimized table are not included. -* Most code should use getNonMetaClass() instead of reading this table. +* Most code should use getMaybeUnrealizedNonMetaClass() +* instead of reading this table. * Locking: runtimeLock must be read- or write-locked by the caller **********************************************************************/ static NXMapTable *nonmeta_class_map = nil; @@ -960,8 +966,8 @@ static void addNonMetaClass(Class cls) void *old; old = NXMapInsert(nonMetaClasses(), cls->ISA(), cls); - assert(!cls->isMetaClass()); - assert(cls->ISA()->isMetaClass()); + assert(!cls->isMetaClassMaybeUnrealized()); + assert(cls->ISA()->isMetaClassMaybeUnrealized()); assert(!old); } @@ -1090,9 +1096,12 @@ static bool scanMangledField(const char *&string, const char *end, /*********************************************************************** -* getClass +* getClassExceptSomeSwift * Looks up a class by name. The class MIGHT NOT be realized. * Demangled Swift names are recognized. +* Classes known to the Swift runtime but not yet used are NOT recognized. +* (such as subclasses of un-instantiated generics) +* Use look_up_class() to find them as well. * Locking: runtimeLock must be read- or write-locked by the caller. **********************************************************************/ @@ -1115,7 +1124,7 @@ static Class getClass_impl(const char *name) return getPreoptimizedClass(name); } -static Class getClass(const char *name) +static Class getClassExceptSomeSwift(const char *name) { runtimeLock.assertLocked(); @@ -1144,11 +1153,12 @@ static void addNamedClass(Class cls, const char *name, Class replacing = nil) { runtimeLock.assertLocked(); Class old; - if ((old = getClass(name)) && old != replacing) { + if ((old = getClassExceptSomeSwift(name)) && old != replacing) { inform_duplicate(name, old, cls); - // getNonMetaClass uses name lookups. Classes not found by name - // lookup must be in the secondary meta->nonmeta table. + // getMaybeUnrealizedNonMetaClass uses name lookups. + // Classes not found by name lookup must be in the + // secondary meta->nonmeta table. addNonMetaClass(cls); } else { NXMapInsert(gdb_objc_realized_classes, name, cls); @@ -1389,19 +1399,19 @@ static void remapClassRef(Class *clsref) /*********************************************************************** -* getNonMetaClass +* getMaybeUnrealizedNonMetaClass * Return the ordinary class for this class or metaclass. * `inst` is an instance of `cls` or a subclass thereof, or nil. * Non-nil inst is faster. +* The result may be unrealized. * Used by +initialize. * Locking: runtimeLock must be read- or write-locked by the caller **********************************************************************/ -static Class getNonMetaClass(Class metacls, id inst) +static Class getMaybeUnrealizedNonMetaClass(Class metacls, id inst) { static int total, named, secondary, sharedcache; runtimeLock.assertLocked(); - - realizeClass(metacls); + assert(metacls->isRealized()); total++; @@ -1409,6 +1419,7 @@ static Class getNonMetaClass(Class metacls, id inst) if (!metacls->isMetaClass()) return metacls; // metacls really is a metaclass + // which means inst (if any) is a class // special case for root metaclass // where inst == inst->ISA() == metacls is possible @@ -1422,17 +1433,16 @@ static Class getNonMetaClass(Class metacls, id inst) // use inst if available if (inst) { - Class cls = (Class)inst; - realizeClass(cls); + Class cls = remapClass((Class)inst); // cls may be a subclass - find the real class for metacls - while (cls && cls->ISA() != metacls) { + // fixme this probably stops working once Swift starts + // reallocating classes if cls is unrealized. + while (cls) { + if (cls->ISA() == metacls) { + assert(!cls->isMetaClassMaybeUnrealized()); + return cls; + } cls = cls->superclass; - realizeClass(cls); - } - if (cls) { - assert(!cls->isMetaClass()); - assert(cls->ISA() == metacls); - return cls; } #if DEBUG _objc_fatal("cls is not an instance of metacls"); @@ -1443,7 +1453,7 @@ static Class getNonMetaClass(Class metacls, id inst) // try name lookup { - Class cls = getClass(metacls->mangledName()); + Class cls = getClassExceptSomeSwift(metacls->mangledName()); if (cls->ISA() == metacls) { named++; if (PrintInitializing) { @@ -1451,8 +1461,6 @@ static Class getNonMetaClass(Class metacls, id inst) "successful by-name metaclass lookups", named, total, named*100.0/total); } - - realizeClass(cls); return cls; } } @@ -1469,7 +1477,6 @@ static Class getNonMetaClass(Class metacls, id inst) } assert(cls->ISA() == metacls); - realizeClass(cls); return cls; } } @@ -1498,7 +1505,6 @@ static Class getNonMetaClass(Class metacls, id inst) sharedcache, total, sharedcache*100.0/total); } - realizeClass(cls); return cls; } } @@ -1508,19 +1514,66 @@ static Class getNonMetaClass(Class metacls, id inst) /*********************************************************************** -* _class_getNonMetaClass -* Return the ordinary class for this class or metaclass. -* Used by +initialize. -* Locking: acquires runtimeLock +* class_initialize. Send the '+initialize' message on demand to any +* uninitialized class. Force initialization of superclasses first. +* inst is an instance of cls, or nil. Non-nil is better for performance. +* Returns the class pointer. If the class was unrealized then +* it may be reallocated. +* Locking: +* runtimeLock must be held by the caller +* This function may drop the lock. +* On exit the lock is re-acquired or dropped as requested by leaveLocked. **********************************************************************/ -Class _class_getNonMetaClass(Class cls, id obj) +static Class initializeAndMaybeRelock(Class cls, id inst, + mutex_t& lock, bool leaveLocked) { - mutex_locker_t lock(runtimeLock); - cls = getNonMetaClass(cls, obj); + lock.assertLocked(); assert(cls->isRealized()); + + if (cls->isInitialized()) { + if (!leaveLocked) lock.unlock(); + return cls; + } + + // Find the non-meta class for cls, if it is not already one. + // The +initialize message is sent to the non-meta class object. + Class nonmeta = getMaybeUnrealizedNonMetaClass(cls, inst); + + // Realize the non-meta class if necessary. + if (nonmeta->isRealized()) { + // nonmeta is cls, which was already realized + // OR nonmeta is distinct, but is already realized + // - nothing else to do + lock.unlock(); + } else { + nonmeta = realizeClassMaybeSwiftAndUnlock(nonmeta, lock); + // runtimeLock is now unlocked + // fixme Swift can't relocate the class today, + // but someday it will: + cls = object_getClass(nonmeta); + } + + // runtimeLock is now unlocked, for +initialize dispatch + assert(nonmeta->isRealized()); + initializeNonMetaClass(nonmeta); + + if (leaveLocked) runtimeLock.lock(); return cls; } +// Locking: acquires runtimeLock +Class class_initialize(Class cls, id obj) +{ + runtimeLock.lock(); + return initializeAndMaybeRelock(cls, obj, runtimeLock, false); +} + +// Locking: caller must hold runtimeLock; this may drop and re-acquire it +static Class initializeAndLeaveLocked(Class cls, id obj, mutex_t& lock) +{ + return initializeAndMaybeRelock(cls, obj, lock, true); +} + /*********************************************************************** * addRootClass @@ -1849,13 +1902,14 @@ static void reconcileInstanceVariables(Class cls, Class supercls, const class_ro /*********************************************************************** -* realizeClass +* realizeClassWithoutSwift * Performs first-time initialization on class cls, * including allocating its read-write data. +* Does not perform any Swift-side initialization. * Returns the real class structure for the class. * Locking: runtimeLock must be write-locked by the caller **********************************************************************/ -static Class realizeClass(Class cls) +static Class realizeClassWithoutSwift(Class cls) { runtimeLock.assertLocked(); @@ -1895,16 +1949,22 @@ static Class realizeClass(Class cls) cls->chooseClassArrayIndex(); if (PrintConnecting) { - _objc_inform("CLASS: realizing class '%s'%s %p %p #%u", + _objc_inform("CLASS: realizing class '%s'%s %p %p #%u %s%s", cls->nameForLogging(), isMeta ? " (meta)" : "", - (void*)cls, ro, cls->classArrayIndex()); + (void*)cls, ro, cls->classArrayIndex(), + cls->isSwiftStable() ? "(swift)" : "", + cls->isSwiftLegacy() ? "(pre-stable swift)" : ""); } // Realize superclass and metaclass, if they aren't already. // This needs to be done after RW_REALIZED is set above, for root classes. // This needs to be done after class index is chosen, for root metaclasses. - supercls = realizeClass(remapClass(cls->superclass)); - metacls = realizeClass(remapClass(cls->ISA())); + // This assumes that none of those classes have Swift contents, + // or that Swift's initializers have already been called. + // fixme that assumption will be wrong if we add support + // for ObjC subclasses of Swift classes. + supercls = realizeClassWithoutSwift(remapClass(cls->superclass)); + metacls = realizeClassWithoutSwift(remapClass(cls->ISA())); #if SUPPORT_NONPOINTER_ISA // Disable non-pointer isa for some classes and/or platforms. @@ -1959,6 +2019,14 @@ static Class realizeClass(Class cls) cls->setHasCxxCtor(); } } + + // Propagate the associated objects forbidden flag from ro or from + // the superclass. + if ((ro->flags & RO_FORBIDS_ASSOCIATED_OBJECTS) || + (supercls && supercls->forbidsAssociatedObjects())) + { + rw->flags |= RW_FORBIDS_ASSOCIATED_OBJECTS; + } // Connect this class to its superclass's subclass lists if (supercls) { @@ -1974,6 +2042,172 @@ static Class realizeClass(Class cls) } +/*********************************************************************** +* _objc_realizeClassFromSwift +* Called by Swift when it needs the ObjC part of a class to be realized. +* There are four cases: +* 1. cls != nil; previously == cls +* Class cls is being realized in place +* 2. cls != nil; previously == nil +* Class cls is being constructed at runtime +* 3. cls != nil; previously != cls +* The class that was at previously has been reallocated to cls +* 4. cls == nil, previously != nil +* The class at previously is hereby disavowed +* +* Only variants #1 and #2 are supported today. +* +* Locking: acquires runtimeLock +**********************************************************************/ +Class _objc_realizeClassFromSwift(Class cls, void *previously) +{ + if (cls) { + if (previously && previously != (void*)cls) { + // #3: relocation + // In the future this will mean remapping the old address + // to the new class, and installing dispatch forwarding + // machinery at the old address + _objc_fatal("Swift requested that class %p be reallocated, " + "but libobjc does not support that.", previously); + } else { + // #1 and #2: realization in place, or new class + mutex_locker_t lock(runtimeLock); + + if (!previously) { + // #2: new class + cls = readClass(cls, false/*bundle*/, false/*shared cache*/); + } + + // #1 and #2: realization in place, or new class + // We ignore the Swift metadata initializer callback. + // We assume that's all handled since we're being called from Swift. + return realizeClassWithoutSwift(cls); + } + } + else { + // #4: disavowal + // In the future this will mean remapping the old address to nil + // and if necessary removing the old address from any other tables. + _objc_fatal("Swift requested that class %p be ignored, " + "but libobjc does not support that.", previously); + } +} + +/*********************************************************************** +* realizeSwiftClass +* Performs first-time initialization on class cls, +* including allocating its read-write data, +* and any Swift-side initialization. +* Returns the real class structure for the class. +* Locking: acquires runtimeLock indirectly +**********************************************************************/ +static Class realizeSwiftClass(Class cls) +{ + runtimeLock.assertUnlocked(); + + // Some assumptions: + // * Metaclasses never have a Swift initializer. + // * Root classes never have a Swift initializer. + // (These two together avoid initialization order problems at the root.) + // * Unrealized non-Swift classes have no Swift ancestry. + // * Unrealized Swift classes with no initializer have no ancestry that + // does have the initializer. + // (These two together mean we don't need to scan superclasses here + // and we don't need to worry about Swift superclasses inside + // realizeClassWithoutSwift()). + + // fixme some of these assumptions will be wrong + // if we add support for ObjC sublasses of Swift classes. + +#if DEBUG + runtimeLock.lock(); + assert(remapClass(cls) == cls); + assert(cls->isSwiftStable_ButAllowLegacyForNow()); + assert(!cls->isMetaClassMaybeUnrealized()); + assert(cls->superclass); + runtimeLock.unlock(); +#endif + + // Look for a Swift metadata initialization function + // installed on the class. If it is present we call it. + // That function in turn initializes the Swift metadata, + // prepares the "compiler-generated" ObjC metadata if not + // already present, and calls _objc_realizeSwiftClass() to finish + // our own initialization. + + if (auto init = cls->swiftMetadataInitializer()) { + if (PrintConnecting) { + _objc_inform("CLASS: calling Swift metadata initializer " + "for class '%s' (%p)", cls->nameForLogging(), cls); + } + + Class newcls = init(cls, nil); + + // fixme someday Swift will need to relocate classes at this point, + // but we don't accept that yet. + if (cls != newcls) { + _objc_fatal("Swift metadata initializer moved a class " + "from %p to %p, but libobjc does not yet allow that.", + cls, newcls); + } + + return newcls; + } + else { + // No Swift-side initialization callback. + // Perform our own realization directly. + mutex_locker_t lock(runtimeLock); + return realizeClassWithoutSwift(cls); + } +} + + +/*********************************************************************** +* realizeClassMaybeSwift (MaybeRelock / AndUnlock / AndLeaveLocked) +* Realize a class that might be a Swift class. +* Returns the real class structure for the class. +* Locking: +* runtimeLock must be held on entry +* runtimeLock may be dropped during execution +* ...AndUnlock function leaves runtimeLock unlocked on exit +* ...AndLeaveLocked re-acquires runtimeLock if it was dropped +* This complication avoids repeated lock transitions in some cases. +**********************************************************************/ +static Class +realizeClassMaybeSwiftMaybeRelock(Class cls, mutex_t& lock, bool leaveLocked) +{ + lock.assertLocked(); + + if (!cls->isSwiftStable_ButAllowLegacyForNow()) { + // Non-Swift class. Realize it now with the lock still held. + // fixme wrong in the future for objc subclasses of swift classes + realizeClassWithoutSwift(cls); + if (!leaveLocked) lock.unlock(); + } else { + // Swift class. We need to drop locks and call the Swift + // runtime to initialize it. + lock.unlock(); + cls = realizeSwiftClass(cls); + assert(cls->isRealized()); // callback must have provoked realization + if (leaveLocked) lock.lock(); + } + + return cls; +} + +static Class +realizeClassMaybeSwiftAndUnlock(Class cls, mutex_t& lock) +{ + return realizeClassMaybeSwiftMaybeRelock(cls, lock, false); +} + +static Class +realizeClassMaybeSwiftAndLeaveLocked(Class cls, mutex_t& lock) +{ + return realizeClassMaybeSwiftMaybeRelock(cls, lock, true); +} + + /*********************************************************************** * missingWeakSuperclass * Return YES if some superclass of cls was weak-linked and is missing. @@ -2002,6 +2236,7 @@ static Class realizeClass(Class cls) * realizeAllClassesInImage * Non-lazily realizes all unrealized classes in the given image. * Locking: runtimeLock must be held by the caller. +* Locking: this function may drop and re-acquire the lock. **********************************************************************/ static void realizeAllClassesInImage(header_info *hi) { @@ -2015,7 +2250,10 @@ static void realizeAllClassesInImage(header_info *hi) classlist = _getObjc2ClassList(hi, &count); for (i = 0; i < count; i++) { - realizeClass(remapClass(classlist[i])); + Class cls = remapClass(classlist[i]); + if (cls) { + realizeClassMaybeSwiftAndLeaveLocked(cls, runtimeLock); + } } hi->setAllClassesRealized(YES); @@ -2026,6 +2264,11 @@ static void realizeAllClassesInImage(header_info *hi) * realizeAllClasses * Non-lazily realizes all unrealized classes in all known images. * Locking: runtimeLock must be held by the caller. +* Locking: this function may drop and re-acquire the lock. +* Dropping the lock makes this function thread-unsafe with respect +* to concurrent image unload, but the callers of this function +* already ultimately do something that is also thread-unsafe with +* respect to image unload (such as using the list of all classes). **********************************************************************/ static void realizeAllClasses(void) { @@ -2033,7 +2276,7 @@ static void realizeAllClasses(void) header_info *hi; for (hi = FirstHeader; hi; hi = hi->getNext()) { - realizeAllClassesInImage(hi); + realizeAllClassesInImage(hi); // may drop and re-acquire runtimeLock } } @@ -2242,6 +2485,20 @@ bool mustReadClasses(header_info *hi) goto readthem; } + // readClass() rewrites bits in backward-deploying Swift stable ABI code. + // The assumption here is there there are no such classes + // in the dyld shared cache. +#if DEBUG + { + size_t count; + classref_t *classlist = _getObjc2ClassList(hi, &count); + for (size_t i = 0; i < count; i++) { + Class cls = remapClass(classlist[i]); + assert(!cls->isUnfixedBackwardDeployingStableSwift()); + } + } +#endif + // readClass() does not need to do anything. return NO; @@ -2300,6 +2557,8 @@ Class readClass(Class cls, bool headerIsBundle, bool headerIsPreoptimized) if (cls->ISA()->cache._occupied) cls->ISA()->cache._occupied = 0; #endif + cls->fixupBackwardDeployingStableSwift(); + Class replacing = nil; if (Class newCls = popFutureNamedClass(mangledName)) { // This name was previously allocated as a future class. @@ -2330,7 +2589,7 @@ Class readClass(Class cls, bool headerIsBundle, bool headerIsPreoptimized) // class list built in shared cache // fixme strict assert doesn't work because of duplicates // assert(cls == getClass(name)); - assert(getClass(mangledName)); + assert(getClassExceptSomeSwift(mangledName)); } else { addNamedClass(cls, mangledName, replacing); addClassTableEntry(cls); @@ -2473,7 +2732,7 @@ void _read_images(header_info **hList, uint32_t hCount, int totalClasses, int un // Disable nonpointer isa if any image contains old Swift code for (EACH_HEADER) { if (hi->info()->containsSwift() && - hi->info()->swiftVersion() < objc_image_info::SwiftVersion3) + hi->info()->swiftUnstableVersion() < objc_image_info::SwiftVersion3) { DisableNonpointerIsa = true; if (PrintRawIsa) { @@ -2683,7 +2942,18 @@ void _read_images(header_info **hList, uint32_t hCount, int totalClasses, int un #endif addClassTableEntry(cls); - realizeClass(cls); + + if (cls->isSwiftStable()) { + if (cls->swiftMetadataInitializer()) { + _objc_fatal("Swift class %s with a metadata initializer " + "is not allowed to be non-lazy", + cls->nameForLogging()); + } + // fixme also disallow relocatable classes + // We can't disallow all Swift classes because of + // classes like Swift.__EmptyArrayStorage + } + realizeClassWithoutSwift(cls); } } @@ -2692,8 +2962,12 @@ void _read_images(header_info **hList, uint32_t hCount, int totalClasses, int un // Realize newly-resolved future classes, in case CF manipulates them if (resolvedFutureClasses) { for (i = 0; i < resolvedFutureClassCount; i++) { - realizeClass(resolvedFutureClasses[i]); - resolvedFutureClasses[i]->setInstancesRequireRawIsa(false/*inherited*/); + Class cls = resolvedFutureClasses[i]; + if (cls->isSwiftStable()) { + _objc_fatal("Swift class is not allowed to be future"); + } + realizeClassWithoutSwift(cls); + cls->setInstancesRequireRawIsa(false/*inherited*/); } free(resolvedFutureClasses); } @@ -2880,7 +3154,11 @@ void prepare_load_methods(const headerType *mhdr) category_t *cat = categorylist[i]; Class cls = remapClass(cat->cls); if (!cls) continue; // category for ignored weak-linked class - realizeClass(cls); + if (cls->isSwiftStable()) { + _objc_fatal("Swift class extensions and categories on Swift " + "classes are not allowed to have +load methods"); + } + realizeClassWithoutSwift(cls); assert(cls->ISA()->isRealized()); add_category_to_loadable_list(cat); } @@ -4418,7 +4696,7 @@ void objc_registerProtocol(Protocol *proto_gen) for (size_t i = 0; i < count; i++) { Class cls = remapClass(classlist[i]); if (cls) { - names[i-shift] = cls->demangledName(true/*realize*/); + names[i-shift] = cls->demangledName(); } else { shift++; // ignored weak-linked class } @@ -4554,12 +4832,12 @@ void objc_registerProtocol(Protocol *proto_gen) /*********************************************************************** * objc_class::demangledName * If realize=false, the class must already be realized or future. -* Locking: If realize=true, runtimeLock must be held by the caller. +* Locking: runtimeLock may or may not be held by the caller. **********************************************************************/ mutex_t DemangleCacheLock; static NXHashTable *DemangleCache; const char * -objc_class::demangledName(bool realize) +objc_class::demangledName() { // Return previously demangled name if available. if (isRealized() || isFuture()) { @@ -4587,33 +4865,30 @@ void objc_registerProtocol(Protocol *proto_gen) return mangled; } - // Class is not yet realized and name is mangled. Realize the class. + // Class is not yet realized and name is mangled. + // Allocate the name but don't save it in the class. + // Save the name in a side cache instead to prevent leaks. + // When the class is actually realized we may allocate a second + // copy of the name, but we don't care. + // (Previously we would try to realize the class now and save the + // name there, but realization is more complicated for Swift classes.) + // Only objc_copyClassNamesForImage() should get here. - // fixme lldb's calls to class_getName() can also get here when // interrogating the dyld shared cache. (rdar://27258517) // fixme runtimeLock.assertLocked(); // fixme assert(realize); - - if (realize) { - runtimeLock.assertLocked(); - realizeClass((Class)this); - data()->demangledName = de; - return de; - } - else { - // Save the string to avoid leaks. - char *cached; - { - mutex_locker_t lock(DemangleCacheLock); - if (!DemangleCache) { - DemangleCache = NXCreateHashTable(NXStrPrototype, 0, nil); - } - cached = (char *)NXHashInsertIfAbsent(DemangleCache, de); + + char *cached; + { + mutex_locker_t lock(DemangleCacheLock); + if (!DemangleCache) { + DemangleCache = NXCreateHashTable(NXStrPrototype, 0, nil); } - if (cached != de) free(de); - return cached; + cached = (char *)NXHashInsertIfAbsent(DemangleCache, de); } + if (cached != de) free(de); + return cached; } @@ -4810,6 +5085,135 @@ Method class_getInstanceMethod(Class cls, SEL sel) } +/*********************************************************************** +* resolveClassMethod +* Call +resolveClassMethod, looking for a method to be added to class cls. +* cls should be a metaclass. +* Does not check if the method already exists. +**********************************************************************/ +static void resolveClassMethod(Class cls, SEL sel, id inst) +{ + runtimeLock.assertUnlocked(); + assert(cls->isRealized()); + assert(cls->isMetaClass()); + + if (! lookUpImpOrNil(cls, SEL_resolveClassMethod, inst, + NO/*initialize*/, YES/*cache*/, NO/*resolver*/)) + { + // Resolver not implemented. + return; + } + + Class nonmeta; + { + mutex_locker_t lock(runtimeLock); + nonmeta = getMaybeUnrealizedNonMetaClass(cls, inst); + // +initialize path should have realized nonmeta already + if (!nonmeta->isRealized()) { + _objc_fatal("nonmeta class %s (%p) unexpectedly not realized", + nonmeta->nameForLogging(), nonmeta); + } + } + BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend; + bool resolved = msg(nonmeta, SEL_resolveClassMethod, sel); + + // Cache the result (good or bad) so the resolver doesn't fire next time. + // +resolveClassMethod adds to self->ISA() a.k.a. cls + IMP imp = lookUpImpOrNil(cls, sel, inst, + NO/*initialize*/, YES/*cache*/, NO/*resolver*/); + + if (resolved && PrintResolving) { + if (imp) { + _objc_inform("RESOLVE: method %c[%s %s] " + "dynamically resolved to %p", + cls->isMetaClass() ? '+' : '-', + cls->nameForLogging(), sel_getName(sel), imp); + } + else { + // Method resolver didn't add anything? + _objc_inform("RESOLVE: +[%s resolveClassMethod:%s] returned YES" + ", but no new implementation of %c[%s %s] was found", + cls->nameForLogging(), sel_getName(sel), + cls->isMetaClass() ? '+' : '-', + cls->nameForLogging(), sel_getName(sel)); + } + } +} + + +/*********************************************************************** +* resolveInstanceMethod +* Call +resolveInstanceMethod, looking for a method to be added to class cls. +* cls may be a metaclass or a non-meta class. +* Does not check if the method already exists. +**********************************************************************/ +static void resolveInstanceMethod(Class cls, SEL sel, id inst) +{ + runtimeLock.assertUnlocked(); + assert(cls->isRealized()); + + if (! lookUpImpOrNil(cls->ISA(), SEL_resolveInstanceMethod, cls, + NO/*initialize*/, YES/*cache*/, NO/*resolver*/)) + { + // Resolver not implemented. + return; + } + + BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend; + bool resolved = msg(cls, SEL_resolveInstanceMethod, sel); + + // Cache the result (good or bad) so the resolver doesn't fire next time. + // +resolveInstanceMethod adds to self a.k.a. cls + IMP imp = lookUpImpOrNil(cls, sel, inst, + NO/*initialize*/, YES/*cache*/, NO/*resolver*/); + + if (resolved && PrintResolving) { + if (imp) { + _objc_inform("RESOLVE: method %c[%s %s] " + "dynamically resolved to %p", + cls->isMetaClass() ? '+' : '-', + cls->nameForLogging(), sel_getName(sel), imp); + } + else { + // Method resolver didn't add anything? + _objc_inform("RESOLVE: +[%s resolveInstanceMethod:%s] returned YES" + ", but no new implementation of %c[%s %s] was found", + cls->nameForLogging(), sel_getName(sel), + cls->isMetaClass() ? '+' : '-', + cls->nameForLogging(), sel_getName(sel)); + } + } +} + + +/*********************************************************************** +* resolveMethod +* Call +resolveClassMethod or +resolveInstanceMethod. +* Returns nothing; any result would be potentially out-of-date already. +* Does not check if the method already exists. +**********************************************************************/ +static void resolveMethod(Class cls, SEL sel, id inst) +{ + runtimeLock.assertUnlocked(); + assert(cls->isRealized()); + + if (! cls->isMetaClass()) { + // try [cls resolveInstanceMethod:sel] + resolveInstanceMethod(cls, sel, inst); + } + else { + // try [nonMetaClass resolveClassMethod:sel] + // and [cls resolveInstanceMethod:sel] + resolveClassMethod(cls, sel, inst); + if (!lookUpImpOrNil(cls, sel, inst, + NO/*initialize*/, YES/*cache*/, NO/*resolver*/)) + { + resolveInstanceMethod(cls, sel, inst); + } + } +} + + /*********************************************************************** * log_and_fill_cache * Log this method call. If the logger permits it, fill the method cache. @@ -4884,20 +5288,21 @@ IMP lookUpImpOrForward(Class cls, SEL sel, id inst, checkIsKnownClass(cls); if (!cls->isRealized()) { - realizeClass(cls); + cls = realizeClassMaybeSwiftAndLeaveLocked(cls, runtimeLock); + // runtimeLock may have been dropped but is now locked again } - if (initialize && !cls->isInitialized()) { - runtimeLock.unlock(); - _class_initialize (_class_getNonMetaClass(cls, inst)); - runtimeLock.lock(); - // If sel == initialize, _class_initialize will send +initialize and + if (initialize && !cls->isInitialized()) { + cls = initializeAndLeaveLocked(cls, inst, runtimeLock); + // runtimeLock may have been dropped but is now locked again + + // If sel == initialize, class_initialize will send +initialize and // then the messenger will send +initialize again after this // procedure finishes. Of course, if this is not being called // from the messenger then it won't happen. 2778172 } - + retry: runtimeLock.assertLocked(); @@ -4958,7 +5363,7 @@ IMP lookUpImpOrForward(Class cls, SEL sel, id inst, if (resolver && !triedResolver) { runtimeLock.unlock(); - _class_resolveMethod(cls, sel, inst); + resolveMethod(cls, sel, inst); runtimeLock.lock(); // Don't cache the result; we don't hold the lock so it may have // changed already. Re-do the search from scratch instead. @@ -6016,6 +6421,20 @@ BOOL class_addProtocol(Class cls, Protocol *protocol_gen) * Look up a class by name, and realize it. * Locking: acquires runtimeLock **********************************************************************/ +static BOOL empty_getClass(const char *name, Class *outClass) +{ + *outClass = nil; + return NO; +} + +static ChainedHookFunction GetClassHook{empty_getClass}; + +void objc_setHook_getClass(objc_hook_getClass newValue, + objc_hook_getClass *outOldValue) +{ + GetClassHook.set(newValue, outOldValue); +} + Class look_up_class(const char *name, bool includeUnconnected __attribute__((unused)), @@ -6026,14 +6445,58 @@ bool includeClassHandler __attribute__((unused))) Class result; bool unrealized; { - mutex_locker_t lock(runtimeLock); - result = getClass(name); + runtimeLock.lock(); + result = getClassExceptSomeSwift(name); unrealized = result && !result->isRealized(); + if (unrealized) { + result = realizeClassMaybeSwiftAndUnlock(result, runtimeLock); + // runtimeLock is now unlocked + } else { + runtimeLock.unlock(); + } } - if (unrealized) { - mutex_locker_t lock(runtimeLock); - realizeClass(result); + + if (!result) { + // Ask Swift about its un-instantiated classes. + + // We use thread-local storage to prevent infinite recursion + // if the hook function provokes another lookup of the same name + // (for example, if the hook calls objc_allocateClassPair) + + auto *tls = _objc_fetch_pthread_data(true); + + // Stop if this thread is already looking up this name. + for (unsigned i = 0; i < tls->classNameLookupsUsed; i++) { + if (0 == strcmp(name, tls->classNameLookups[i])) { + return nil; + } + } + + // Save this lookup in tls. + if (tls->classNameLookupsUsed == tls->classNameLookupsAllocated) { + tls->classNameLookupsAllocated = + (tls->classNameLookupsAllocated * 2 ?: 1); + size_t size = tls->classNameLookupsAllocated * + sizeof(tls->classNameLookups[0]); + tls->classNameLookups = (const char **) + realloc(tls->classNameLookups, size); + } + tls->classNameLookups[tls->classNameLookupsUsed++] = name; + + // Call the hook. + Class swiftcls = nil; + if (GetClassHook.get()(name, &swiftcls)) { + assert(swiftcls->isRealized()); + result = swiftcls; + } + + // Erase the name from tls. + unsigned slot = --tls->classNameLookupsUsed; + assert(slot >= 0 && slot < tls->classNameLookupsAllocated); + assert(name == tls->classNameLookups[slot]); + tls->classNameLookups[slot] = nil; } + return result; } @@ -6072,8 +6535,7 @@ bool includeClassHandler __attribute__((unused))) duplicate->bits = original->bits; duplicate->setData(rw); - rw->ro = (class_ro_t *) - memdup(original->data()->ro, sizeof(*original->data()->ro)); + rw->ro = original->data()->ro->duplicate(); *(char **)&rw->ro->name = strdupIfMutable(name); rw->methods = original->data()->methods.duplicate(); @@ -6142,6 +6604,8 @@ static void objc_initializeClassPair_internal(Class superclass, const char *name meta_ro_w->flags |= RO_ROOT; } if (superclass) { + uint32_t flagsToCopy = RW_FORBIDS_ASSOCIATED_OBJECTS; + cls->data()->flags |= superclass->data()->flags & flagsToCopy; cls_ro_w->instanceStart = superclass->unalignedInstanceSize(); meta_ro_w->instanceStart = superclass->ISA()->unalignedInstanceSize(); cls->setInstanceSize(cls_ro_w->instanceStart); @@ -6215,11 +6679,16 @@ static void objc_initializeClassPair_internal(Class superclass, const char *name **********************************************************************/ Class objc_initializeClassPair(Class superclass, const char *name, Class cls, Class meta) { + // Fail if the class name is in use. + if (look_up_class(name, NO, NO)) return nil; + mutex_locker_t lock(runtimeLock); // Fail if the class name is in use. // Fail if the superclass isn't kosher. - if (getClass(name) || !verifySuperclass(superclass, true/*rootOK*/)) { + if (getClassExceptSomeSwift(name) || + !verifySuperclass(superclass, true/*rootOK*/)) + { return nil; } @@ -6239,11 +6708,16 @@ Class objc_allocateClassPair(Class superclass, const char *name, { Class cls, meta; + // Fail if the class name is in use. + if (look_up_class(name, NO, NO)) return nil; + mutex_locker_t lock(runtimeLock); // Fail if the class name is in use. // Fail if the superclass isn't kosher. - if (getClass(name) || !verifySuperclass(superclass, true/*rootOK*/)) { + if (getClassExceptSomeSwift(name) || + !verifySuperclass(superclass, true/*rootOK*/)) + { return nil; } @@ -6327,7 +6801,11 @@ Class objc_readClassPair(Class bits, const struct objc_image_info *info) _objc_fatal("objc_readClassPair for class %s changed %p to %p", cls->nameForLogging(), bits, cls); } - realizeClass(cls); + + // The only client of this function is old Swift. + // Stable Swift won't use it. + // fixme once Swift in the OS settles we can assert(!cls->isSwiftStable()). + cls = realizeClassWithoutSwift(cls); return cls; } diff --git a/runtime/objc-runtime-old.h b/runtime/objc-runtime-old.h index 664cf4b..b367c09 100644 --- a/runtime/objc-runtime-old.h +++ b/runtime/objc-runtime-old.h @@ -248,6 +248,11 @@ struct objc_class : objc_object { void setHasDefaultAWZ() { } void printCustomAWZ(bool) { } + bool forbidsAssociatedObjects() { + // Old runtime doesn't support forbidding associated objects. + return false; + } + bool instancesHaveAssociatedObjects() { return info & CLS_INSTANCES_HAVE_ASSOCIATED_OBJECTS; } diff --git a/runtime/objc-runtime.mm b/runtime/objc-runtime.mm index 4d94b64..176d341 100644 --- a/runtime/objc-runtime.mm +++ b/runtime/objc-runtime.mm @@ -131,6 +131,22 @@ id objc_noop_imp(id self, SEL _cmd __unused) { } +/*********************************************************************** +* _objc_isDebugBuild. Defined in debug builds only. +* Some test code looks for the presence of this symbol. +**********************************************************************/ +#if DEBUG != OBJC_IS_DEBUG_BUILD +#error mismatch in debug-ness macros +// DEBUG is used in our code. OBJC_IS_DEBUG_BUILD is used in the +// header declaration of _objc_isDebugBuild() because that header +// is visible to other clients who might have their own DEBUG macro. +#endif + +#if OBJC_IS_DEBUG_BUILD +void _objc_isDebugBuild(void) { } +#endif + + /*********************************************************************** * objc_getClass. Return the id of the named class. If the class does * not exist, call _objc_classLoader and then objc_classHandler, either of @@ -420,6 +436,7 @@ void _objc_pthread_destroyspecific(void *arg) free(data->printableNames[i]); } } + free(data->classNameLookups); // add further cleanup here... diff --git a/runtime/runtime.h b/runtime/runtime.h index 2e99cf0..1542d03 100644 --- a/runtime/runtime.h +++ b/runtime/runtime.h @@ -1728,6 +1728,65 @@ OBJC_EXPORT void objc_setHook_getImageName(objc_hook_getImageName _Nonnull newVa objc_hook_getImageName _Nullable * _Nonnull outOldValue) OBJC_AVAILABLE(10.14, 12.0, 12.0, 5.0, 3.0); +/** + * Function type for a hook that assists objc_getClass() and related functions. + * + * @param name The class name to look up. + * @param outClass On return, the result of the class lookup. + * @return YES if a class with this name was found, NO otherwise. + * + * @see objc_getClass + * @see objc_setHook_getClass + */ +typedef BOOL (*objc_hook_getClass)(const char * _Nonnull name, Class _Nullable * _Nonnull outClass); + +/** + * Install a hook for objc_getClass() and related functions. + * + * @param newValue The hook function to install. + * @param outOldValue The address of a function pointer variable. On return, + * the old hook function is stored in the variable. + * + * @note The store to *outOldValue is thread-safe: the variable will be + * updated before objc_getClass() calls your new hook to read it, + * even if your new hook is called from another thread before this + * setter completes. + * @note Your hook should call the previous hook for class names + * that you do not recognize. + * + * @see objc_getClass + * @see objc_hook_getClass + */ +#if !(TARGET_OS_OSX && __i386__) +#define OBJC_GETCLASSHOOK_DEFINED 1 +OBJC_EXPORT void objc_setHook_getClass(objc_hook_getClass _Nonnull newValue, + objc_hook_getClass _Nullable * _Nonnull outOldValue) + OBJC_AVAILABLE(10.14, 12.0, 12.0, 5.0, 3.0); +// rdar://44986431 fixme correct availability for _objc_realizeClassFromSwift +#endif + +/** + * Callback from Objective-C to Swift to perform Swift class initialization. + */ +#if !(TARGET_OS_OSX && __i386__) +typedef Class _Nullable +(*_objc_swiftMetadataInitializer)(Class _Nonnull cls, void * _Nullable arg); +#endif + + +/** + * Perform Objective-C initialization of a Swift class. + * Do not call this function. It is provided for the Swift runtime's use only + * and will change without notice or mercy. + */ +#if !(TARGET_OS_OSX && __i386__) +#define OBJC_REALIZECLASSFROMSWIFT_DEFINED 1 +OBJC_EXPORT Class _Nullable +_objc_realizeClassFromSwift(Class _Nullable cls, void * _Nullable previously) + OBJC_AVAILABLE(10.14, 12.0, 12.0, 5.0, 3.0); +// rdar://44986431 fixme correct availability for _objc_realizeClassFromSwift +#endif + #define _C_ID '@' #define _C_CLASS '#' diff --git a/test/ARCBase.h b/test/ARCBase.h new file mode 100644 index 0000000..55722cf --- /dev/null +++ b/test/ARCBase.h @@ -0,0 +1,21 @@ +// +// ARCBase.h +// TestARCLayouts +// +// Created by Patrick Beard on 3/8/11. +// Copyright 2011 __MyCompanyName__. All rights reserved. +// + +#import + +@interface ARCMisalign : NSObject { + char misalign1; +} +@end + +@interface ARCBase : ARCMisalign +@property long number; +@property(retain) id object; +@property void *pointer; +@property(weak) __weak id delegate; +@end diff --git a/test/ARCBase.m b/test/ARCBase.m new file mode 100644 index 0000000..37f91c5 --- /dev/null +++ b/test/ARCBase.m @@ -0,0 +1,30 @@ +// +// ARCBase.m +// TestARCLayouts +// +// Created by Patrick Beard on 3/8/11. +// Copyright 2011 __MyCompanyName__. All rights reserved. +// + +#import "ARCBase.h" + +// ARCMisalign->misalign1 and ARCBase->misalign2 together cause +// ARCBase's instanceStart to be misaligned, which exercises handling +// of storage that is not represented in the class's ivar layout bitmaps. + +@implementation ARCMisalign +@end + +@interface ARCBase () { +@private + char misalign2; + long number; + id object; + void *pointer; + __weak id delegate; +} +@end + +@implementation ARCBase +@synthesize number, object, pointer, delegate; +@end diff --git a/test/ARCLayouts.m b/test/ARCLayouts.m new file mode 100644 index 0000000..516c837 --- /dev/null +++ b/test/ARCLayouts.m @@ -0,0 +1,217 @@ +// Note that test ARCLayoutsWithoutWeak uses the same files +// with different build options. +/* +TEST_CONFIG MEM=arc +TEST_BUILD + mkdir -p $T{OBJDIR} + $C{COMPILE_NOLINK_NOMEM} -c $DIR/MRCBase.m -o $T{OBJDIR}/MRCBase.o + $C{COMPILE_NOLINK_NOMEM} -c $DIR/MRCARC.m -o $T{OBJDIR}/MRCARC.o + $C{COMPILE_NOLINK} -c $DIR/ARCBase.m -o $T{OBJDIR}/ARCBase.o + $C{COMPILE_NOLINK} -c $DIR/ARCMRC.m -o $T{OBJDIR}/ARCMRC.o + $C{COMPILE} '-DNAME=\"ARCLayouts.m\"' -fobjc-arc $DIR/ARCLayouts.m -x none $T{OBJDIR}/MRCBase.o $T{OBJDIR}/MRCARC.o $T{OBJDIR}/ARCBase.o $T{OBJDIR}/ARCMRC.o -framework Foundation -o ARCLayouts.exe +END +*/ + +#include "test.h" +#import +#import +#import + +#import "ARCMRC.h" +#import "MRCARC.h" + +@interface NSObject (Layouts) ++ (const char *)strongLayout; ++ (const char *)weakLayout; +@end + +void printlayout(const char *name, const uint8_t *layout) +{ + if (!testverbose()) return; + + testprintf("%s: ", name); + + // these use fprintf() to avoid repeated VERBOSE: in the middle of the line + if (!layout) { + fprintf(stderr, "NULL\n"); + return; + } + + const uint8_t *c; + for (c = layout; *c; c++) { + fprintf(stderr, "%02x ", *c); + } + + fprintf(stderr, "00\n"); +} + +@implementation NSObject (Layouts) + ++ (const char *)strongLayout { + const uint8_t *layout = class_getIvarLayout(self); + printlayout("strong", layout); + return (const char *)layout; +} + ++ (const char *)weakLayout { + const uint8_t *weakLayout = class_getWeakIvarLayout(self); + printlayout("weak", weakLayout); + return (const char *)weakLayout; +} + ++ (Ivar)instanceVariable:(const char *)name { + return class_getInstanceVariable(self, name); +} + +@end + +void checkMM(Class cls, const char *ivarName, + objc_ivar_memory_management_t mmExpected) +{ + Ivar ivar = [cls instanceVariable:ivarName]; + objc_ivar_memory_management_t mm = _class_getIvarMemoryManagement(cls,ivar); + testprintf("%s->%s want %d, got %d\n", + class_getName(cls), ivarName, mmExpected, mm); + testassert(mmExpected == mm); +} + +int main (int argc __unused, const char * argv[] __unused) { + + testprintf("ARCMRC\n"); + testassert(strcmp([ARCMRC strongLayout], "\x01") == 0); + testassert([ARCMRC weakLayout] == NULL); + + + // Verify that ARCBase->misalign and MRCBase->alignment did their thing. + testassert(ivar_getOffset(class_getInstanceVariable([ARCBase class], "misalign2")) & 1); + testassert(ivar_getOffset(class_getInstanceVariable([MRCBase class], "alignment")) > (ptrdiff_t)sizeof(void*)); + + testprintf("ARCMisalign\n"); + testassert([ARCMisalign strongLayout] == NULL); + testassert([ARCMisalign weakLayout] == NULL); + + testprintf("ARCBase\n"); + if (strcmp([ARCBase strongLayout], "\x11\x30") == 0) { + testwarn("1130 layout is a compiler flaw but doesn't fail"); + } else { + testassert(strcmp([ARCBase strongLayout], "\x11") == 0); + } + testassert(strcmp([ARCBase weakLayout], "\x31") == 0); + + testprintf("MRCARC\n"); + testassert([MRCARC strongLayout] == NULL); + testassert([MRCARC weakLayout] == NULL); + + testprintf("MRCBase\n"); + // MRC marks __weak only. + testassert([MRCBase strongLayout] == NULL); + if (supportsMRCWeak) { + testassert(strcmp([MRCBase weakLayout], "\x71") == 0); + } else { + testassert([MRCBase weakLayout] == nil); + } + + // now check consistency between dynamic accessors and KVC, etc. + ARCMRC *am = [ARCMRC new]; + MRCARC *ma = [MRCARC new]; + + NSString *amValue = [[NSString alloc] initWithFormat:@"%s %p", "ARCMRC", am]; + NSString *amValue2 = [[NSString alloc] initWithFormat:@"%s %p", "ARCMRC", am]; + NSString *maValue = [[NSString alloc] initWithFormat:@"%s %p", "MRCARC", ma]; + NSString *maValue2 = [[NSString alloc] initWithFormat:@"%s %p", "MRCARC", ma]; + + am.number = M_PI; + + object_setIvar(am, [ARCMRC instanceVariable:"object"], amValue); + testassert(CFGetRetainCount((__bridge CFTypeRef)amValue) == 1); + testassert(am.object == amValue); + + object_setIvarWithStrongDefault(am, [ARCMRC instanceVariable:"object"], amValue2); + testassert(CFGetRetainCount((__bridge CFTypeRef)amValue2) == 2); + testassert(am.object == amValue2); + + am.pointer = @selector(ARCMRC); + + object_setIvar(am, [ARCMRC instanceVariable:"delegate"], ma); + testassert(CFGetRetainCount((__bridge CFTypeRef)ma) == 1); + testassert(am.delegate == ma); + + object_setIvarWithStrongDefault(am, [ARCMRC instanceVariable:"delegate"], ma); + testassert(CFGetRetainCount((__bridge CFTypeRef)ma) == 1); + testassert(am.delegate == ma); + + + ma.number = M_E; + + object_setIvar(ma, [MRCARC instanceVariable:"object"], maValue); + testassert(CFGetRetainCount((__bridge CFTypeRef)maValue) == 2); + @autoreleasepool { + testassert(ma.object == maValue); + } + + object_setIvarWithStrongDefault(ma, [MRCARC instanceVariable:"object"], maValue2); + testassert(CFGetRetainCount((__bridge CFTypeRef)maValue2) == 2); + @autoreleasepool { + testassert(ma.object == maValue2); + } + + ma.pointer = @selector(MRCARC); + + ma.delegate = am; + object_setIvar(ma, [MRCARC instanceVariable:"delegate"], am); + testassert(CFGetRetainCount((__bridge CFTypeRef)am) == 1); + @autoreleasepool { + testassert(ma.delegate == am); + } + + object_setIvarWithStrongDefault(ma, [MRCARC instanceVariable:"delegate"], am); + testassert(CFGetRetainCount((__bridge CFTypeRef)am) == 1); + @autoreleasepool { + testassert(ma.delegate == am); + } + + + // Verify that object_copy() handles ARC variables correctly. + + MRCARC *ma2 = docopy(ma); + testassert(ma2); + testassert(ma2 != ma); + testassert(CFGetRetainCount((__bridge CFTypeRef)maValue2) == 3); + testassert(CFGetRetainCount((__bridge CFTypeRef)am) == 1); + testassert(ma2.number == ma.number); + testassert(ma2.object == ma.object); + @autoreleasepool { + testassert(ma2.delegate == ma.delegate); + } + testassert(ma2.pointer == ma.pointer); + + + // Test _class_getIvarMemoryManagement() SPI + + objc_ivar_memory_management_t memoryMRCWeak = + supportsMRCWeak ? objc_ivar_memoryWeak : objc_ivar_memoryUnknown; + checkMM([ARCMRC class], "number", objc_ivar_memoryUnknown); + checkMM([ARCMRC class], "object", objc_ivar_memoryUnknown); + checkMM([ARCMRC class], "pointer", objc_ivar_memoryUnknown); + checkMM([ARCMRC class], "delegate", memoryMRCWeak); + checkMM([ARCMRC class], "dataSource", objc_ivar_memoryStrong); + + checkMM([MRCARC class], "number", objc_ivar_memoryUnretained); + checkMM([MRCARC class], "object", objc_ivar_memoryStrong); + checkMM([MRCARC class], "pointer", objc_ivar_memoryUnretained); + checkMM([MRCARC class], "delegate", objc_ivar_memoryWeak); + checkMM([MRCARC class], "dataSource", objc_ivar_memoryUnknown); + + checkMM([ARCBase class], "number", objc_ivar_memoryUnretained); + checkMM([ARCBase class], "object", objc_ivar_memoryStrong); + checkMM([ARCBase class], "pointer", objc_ivar_memoryUnretained); + checkMM([ARCBase class], "delegate", objc_ivar_memoryWeak); + + checkMM([MRCBase class], "number", objc_ivar_memoryUnknown); + checkMM([MRCBase class], "object", objc_ivar_memoryUnknown); + checkMM([MRCBase class], "pointer", objc_ivar_memoryUnknown); + checkMM([MRCBase class], "delegate", memoryMRCWeak); + + succeed(NAME); + return 0; +} diff --git a/test/ARCLayoutsWithoutWeak.m b/test/ARCLayoutsWithoutWeak.m new file mode 100644 index 0000000..6e81ac6 --- /dev/null +++ b/test/ARCLayoutsWithoutWeak.m @@ -0,0 +1,12 @@ +// Same as test ARCLayouts but with MRC __weak support disabled. +/* +TEST_CONFIG MEM=arc +TEST_BUILD + mkdir -p $T{OBJDIR} + $C{COMPILE_NOLINK_NOMEM} -c $DIR/MRCBase.m -o $T{OBJDIR}/MRCBase.o -fno-objc-weak + $C{COMPILE_NOLINK_NOMEM} -c $DIR/MRCARC.m -o $T{OBJDIR}/MRCARC.o -fno-objc-weak + $C{COMPILE_NOLINK} -c $DIR/ARCBase.m -o $T{OBJDIR}/ARCBase.o + $C{COMPILE_NOLINK} -c $DIR/ARCMRC.m -o $T{OBJDIR}/ARCMRC.o + $C{COMPILE} '-DNAME=\"ARCLayoutsWithoutWeak.m\"' -fobjc-arc $DIR/ARCLayouts.m -x none $T{OBJDIR}/MRCBase.o $T{OBJDIR}/MRCARC.o $T{OBJDIR}/ARCBase.o $T{OBJDIR}/ARCMRC.o -framework Foundation -o ARCLayoutsWithoutWeak.exe +END +*/ diff --git a/test/ARCMRC.h b/test/ARCMRC.h new file mode 100644 index 0000000..a37422e --- /dev/null +++ b/test/ARCMRC.h @@ -0,0 +1,13 @@ +// +// ARCMRC.h +// TestARCLayouts +// +// Created by Patrick Beard on 3/8/11. +// Copyright 2011 __MyCompanyName__. All rights reserved. +// + +#import "MRCBase.h" + +@interface ARCMRC : MRCBase +@property(retain) id dataSource; +@end diff --git a/test/ARCMRC.m b/test/ARCMRC.m new file mode 100644 index 0000000..eed9fce --- /dev/null +++ b/test/ARCMRC.m @@ -0,0 +1,11 @@ +// +// ARCMRC.m +// + +#import "ARCMRC.h" + +@implementation ARCMRC + +@synthesize dataSource; + +@end diff --git a/test/MRCARC.h b/test/MRCARC.h new file mode 100644 index 0000000..9443ea9 --- /dev/null +++ b/test/MRCARC.h @@ -0,0 +1,13 @@ +// +// MRCARC.h +// TestARCLayouts +// +// Created by Patrick Beard on 3/8/11. +// Copyright 2011 __MyCompanyName__. All rights reserved. +// + +#import "ARCBase.h" + +@interface MRCARC : ARCBase +@property(retain) id dataSource; +@end diff --git a/test/MRCARC.m b/test/MRCARC.m new file mode 100644 index 0000000..c2882f3 --- /dev/null +++ b/test/MRCARC.m @@ -0,0 +1,11 @@ +// +// MRCARC.m +// + +#import "MRCARC.h" + +@implementation MRCARC + +@synthesize dataSource; + +@end diff --git a/test/MRCBase.h b/test/MRCBase.h new file mode 100644 index 0000000..050f43f --- /dev/null +++ b/test/MRCBase.h @@ -0,0 +1,28 @@ +// +// MRCBase.h +// TestARCLayouts +// +// Created by Patrick Beard on 3/8/11. +// Copyright 2011 __MyCompanyName__. All rights reserved. +// + +#import + +// YES if MRC compiler supports ARC-style weak +extern bool supportsMRCWeak; + +#if __LP64__ +#define DOUBLEWORD_ALIGNED __attribute__((aligned(16))) +#else +#define DOUBLEWORD_ALIGNED __attribute__((aligned(8))) +#endif + +@interface MRCBase : NSObject +@property double number; +@property(retain) id object; +@property void *pointer; +@property(weak) __weak id delegate; +@end + +// Call object_copy from MRC. +extern id __attribute__((ns_returns_retained)) docopy(id obj); diff --git a/test/MRCBase.m b/test/MRCBase.m new file mode 100644 index 0000000..e95cc2e --- /dev/null +++ b/test/MRCBase.m @@ -0,0 +1,46 @@ +// +// MRCBase.m +// TestARCLayouts +// +// Created by Patrick Beard on 3/8/11. +// Copyright 2011 __MyCompanyName__. All rights reserved. +// + +#include "MRCBase.h" +#include "test.h" + +// MRCBase->alignment ensures that there is a gap between the end of +// NSObject's ivars and the start of MRCBase's ivars, which exercises +// handling of storage that is not represented in any class's ivar +// layout bitmaps. + +#if __has_feature(objc_arc_weak) +bool supportsMRCWeak = true; +#else +bool supportsMRCWeak = false; +#endif + +@interface MRCBase () { +@private + double DOUBLEWORD_ALIGNED alignment; + uintptr_t pad[3]; // historically this made OBJC2 layout bitmaps match OBJC1 + double number; + id object; + void *pointer; +#if __has_feature(objc_arc_weak) + __weak +#endif + id delegate; +} +@end + +@implementation MRCBase +@synthesize number, object, pointer, delegate; +@end + +// Call object_copy from MRC. +extern id __attribute__((ns_returns_retained)) +docopy(id obj) +{ + return object_copy(obj, 0); +} diff --git a/test/accessors.m b/test/accessors.m new file mode 100644 index 0000000..9111e64 --- /dev/null +++ b/test/accessors.m @@ -0,0 +1,79 @@ +// TEST_CFLAGS -framework Foundation + +#import +#import +#import +#include "test.h" + +@interface Test : NSObject { + NSString *_value; + // _object is at the last optimized property offset + id _object __attribute__((aligned(64))); +} +@property(readonly) Class cls; +@property(copy) NSString *value; +@property(assign) id object; +@end + +typedef struct { + void *isa; + void *_value; + // _object is at the last optimized property offset + void *_object __attribute__((aligned(64))); +} TestDefs; + +@implementation Test + +// Question: why can't this code be automatically generated? + +#if !__has_feature(objc_arc) +- (void)dealloc { + self.value = nil; + self.object = nil; + [super dealloc]; +} +#endif + +- (Class)cls { return objc_getProperty(self, _cmd, 0, YES); } + +- (NSString*)value { return (NSString*) objc_getProperty(self, _cmd, offsetof(TestDefs, _value), YES); } +- (void)setValue:(NSString*)inValue { objc_setProperty(self, _cmd, offsetof(TestDefs, _value), inValue, YES, YES); } + +- (id)object { return objc_getProperty(self, _cmd, offsetof(TestDefs, _object), YES); } +- (void)setObject:(id)inObject { objc_setProperty(self, _cmd, offsetof(TestDefs, _object), inObject, YES, NO); } + +- (NSString *)description { + return [NSString stringWithFormat:@"value = %@, object = %@", self.value, self.object]; +} + +@end + +int main() { + PUSH_POOL { + + NSMutableString *value = [NSMutableString stringWithUTF8String:"test"]; + id object = [NSNumber numberWithInt:11]; + Test *t = AUTORELEASE([Test new]); + t.value = value; + [value setString:@"yuck"]; // mutate the string. + testassert(t.value != value); // must copy, since it was mutable. + testassert([t.value isEqualToString:@"test"]); + + Class testClass = [Test class]; + Class cls = t.cls; + testassert(testClass == cls); + cls = t.cls; + testassert(testClass == cls); + + t.object = object; + t.object = object; + + // NSLog(@"t.object = %@, t.value = %@", t.object, t.value); + // NSLog(@"t.object = %@, t.value = %@", t.object, t.value); // second call will optimized getters. + + } POP_POOL; + + succeed(__FILE__); + + return 0; +} diff --git a/test/accessors2.m b/test/accessors2.m new file mode 100644 index 0000000..4b3db6d --- /dev/null +++ b/test/accessors2.m @@ -0,0 +1,143 @@ +// TEST_CONFIG + +#include "test.h" +#include +#include + +#include "testroot.i" + +@interface Base : TestRoot { + @public + id ivar; +} +@end +@implementation Base @end + +int main() +{ + SEL _cmd = @selector(foo); + Base *o = [Base new]; + ptrdiff_t offset = ivar_getOffset(class_getInstanceVariable([Base class], "ivar")); + testassert(offset == sizeof(id)); + + TestRoot *value = [TestRoot new]; + + // fixme test atomicity + + // Original setter API + + testprintf("original nonatomic retain\n"); + o->ivar = nil; + TestRootRetain = 0; + TestRootCopyWithZone = 0; + TestRootMutableCopyWithZone = 0; + objc_setProperty(o, _cmd, offset, value, NO/*atomic*/, NO/*copy*/); + testassert(TestRootRetain == 1); + testassert(TestRootCopyWithZone == 0); + testassert(TestRootMutableCopyWithZone == 0); + testassert(o->ivar == value); + + testprintf("original atomic retain\n"); + o->ivar = nil; + TestRootRetain = 0; + TestRootCopyWithZone = 0; + TestRootMutableCopyWithZone = 0; + objc_setProperty(o, _cmd, offset, value, YES/*atomic*/, NO/*copy*/); + testassert(TestRootRetain == 1); + testassert(TestRootCopyWithZone == 0); + testassert(TestRootMutableCopyWithZone == 0); + testassert(o->ivar == value); + + testprintf("original nonatomic copy\n"); + o->ivar = nil; + TestRootRetain = 0; + TestRootCopyWithZone = 0; + TestRootMutableCopyWithZone = 0; + objc_setProperty(o, _cmd, offset, value, NO/*atomic*/, YES/*copy*/); + testassert(TestRootRetain == 0); + testassert(TestRootCopyWithZone == 1); + testassert(TestRootMutableCopyWithZone == 0); + testassert(o->ivar && o->ivar != value); + + testprintf("original atomic copy\n"); + o->ivar = nil; + TestRootRetain = 0; + TestRootCopyWithZone = 0; + TestRootMutableCopyWithZone = 0; + objc_setProperty(o, _cmd, offset, value, YES/*atomic*/, YES/*copy*/); + testassert(TestRootRetain == 0); + testassert(TestRootCopyWithZone == 1); + testassert(TestRootMutableCopyWithZone == 0); + testassert(o->ivar && o->ivar != value); + + testprintf("original nonatomic mutablecopy\n"); + o->ivar = nil; + TestRootRetain = 0; + TestRootCopyWithZone = 0; + TestRootMutableCopyWithZone = 0; + objc_setProperty(o, _cmd, offset, value, NO/*atomic*/, 2/*copy*/); + testassert(TestRootRetain == 0); + testassert(TestRootCopyWithZone == 0); + testassert(TestRootMutableCopyWithZone == 1); + testassert(o->ivar && o->ivar != value); + + testprintf("original atomic mutablecopy\n"); + o->ivar = nil; + TestRootRetain = 0; + TestRootCopyWithZone = 0; + TestRootMutableCopyWithZone = 0; + objc_setProperty(o, _cmd, offset, value, YES/*atomic*/, 2/*copy*/); + testassert(TestRootRetain == 0); + testassert(TestRootCopyWithZone == 0); + testassert(TestRootMutableCopyWithZone == 1); + testassert(o->ivar && o->ivar != value); + + + // Optimized setter API + + testprintf("optimized nonatomic retain\n"); + o->ivar = nil; + TestRootRetain = 0; + TestRootCopyWithZone = 0; + TestRootMutableCopyWithZone = 0; + objc_setProperty_nonatomic(o, _cmd, value, offset); + testassert(TestRootRetain == 1); + testassert(TestRootCopyWithZone == 0); + testassert(TestRootMutableCopyWithZone == 0); + testassert(o->ivar == value); + + testprintf("optimized atomic retain\n"); + o->ivar = nil; + TestRootRetain = 0; + TestRootCopyWithZone = 0; + TestRootMutableCopyWithZone = 0; + objc_setProperty_atomic(o, _cmd, value, offset); + testassert(TestRootRetain == 1); + testassert(TestRootCopyWithZone == 0); + testassert(TestRootMutableCopyWithZone == 0); + testassert(o->ivar == value); + + testprintf("optimized nonatomic copy\n"); + o->ivar = nil; + TestRootRetain = 0; + TestRootCopyWithZone = 0; + TestRootMutableCopyWithZone = 0; + objc_setProperty_nonatomic_copy(o, _cmd, value, offset); + testassert(TestRootRetain == 0); + testassert(TestRootCopyWithZone == 1); + testassert(TestRootMutableCopyWithZone == 0); + testassert(o->ivar && o->ivar != value); + + testprintf("optimized atomic copy\n"); + o->ivar = nil; + TestRootRetain = 0; + TestRootCopyWithZone = 0; + TestRootMutableCopyWithZone = 0; + objc_setProperty_atomic_copy(o, _cmd, value, offset); + testassert(TestRootRetain == 0); + testassert(TestRootCopyWithZone == 1); + testassert(TestRootMutableCopyWithZone == 0); + testassert(o->ivar && o->ivar != value); + + succeed(__FILE__); +} diff --git a/test/addMethod.m b/test/addMethod.m new file mode 100644 index 0000000..8e693d9 --- /dev/null +++ b/test/addMethod.m @@ -0,0 +1,121 @@ +// TEST_CONFIG + +#include "test.h" +#include "testroot.i" +#include + +@interface Super : TestRoot @end +@implementation Super +-(int)superMethod { return 0; } +-(int)superMethod2 { return 0; } +-(int)bothMethod { return 0; } +@end + +@interface Sub : Super @end +@implementation Sub +-(int)subMethod { return 0; } +-(int)bothMethod { return 0; } +@end + +@interface Sub2 : Super @end +@implementation Sub2 +-(int)subMethod { return 0; } +-(int)bothMethod { return 0; } +@end + + +id fn(id self __attribute__((unused)), SEL cmd __attribute__((unused)), ...) { return nil; } + +#define TWICE(x) x; x + +int main() +{ + IMP superMethodFromSuper = class_getMethodImplementation([Super class], @selector(superMethod)); + IMP superMethod2FromSuper = class_getMethodImplementation([Super class], @selector(superMethod2)); + IMP bothMethodFromSuper = class_getMethodImplementation([Super class], @selector(bothMethod)); + IMP subMethodFromSub = class_getMethodImplementation([Sub class], @selector(subMethod)); + IMP bothMethodFromSub = class_getMethodImplementation([Sub class], @selector(bothMethod)); + IMP subMethodFromSub2 = class_getMethodImplementation([Sub2 class], @selector(subMethod)); + IMP bothMethodFromSub2 = class_getMethodImplementation([Sub2 class], @selector(bothMethod)); + + testassert(superMethodFromSuper); + testassert(superMethod2FromSuper); + testassert(bothMethodFromSuper); + testassert(subMethodFromSub); + testassert(bothMethodFromSub); + testassert(subMethodFromSub2); + testassert(bothMethodFromSub2); + + BOOL ok; + IMP imp; + + // Each method lookup below is performed twice, with the intent + // that at least one of them will be a cache lookup. + + // class_addMethod doesn't replace existing implementations + ok = class_addMethod([Super class], @selector(superMethod), (IMP)fn, NULL); + testassert(!ok); + TWICE(testassert(class_getMethodImplementation([Super class], @selector(superMethod)) == superMethodFromSuper)); + + // class_addMethod does override superclass implementations + ok = class_addMethod([Sub class], @selector(superMethod), (IMP)fn, NULL); + testassert(ok); + TWICE(testassert(class_getMethodImplementation([Sub class], @selector(superMethod)) == (IMP)fn)); + + // class_addMethod does add root implementations + ok = class_addMethod([Super class], @selector(superMethodNew2), (IMP)fn, NULL); + testassert(ok); + TWICE(testassert(class_getMethodImplementation([Super class], @selector(superMethodNew2)) == (IMP)fn)); + TWICE(testassert(class_getMethodImplementation([Sub class], @selector(superMethodNew2)) == (IMP)fn)); + + // bincompat: some apps call class_addMethod() and class_replaceMethod() with a nil IMP + // This has the effect of covering the superclass implementation. + // fixme change this, possibly behind a linked-on-or-after check + #pragma clang diagnostic push + #pragma clang diagnostic ignored "-Wnonnull" + ok = class_addMethod([Sub class], @selector(superMethod2), nil, NULL); + #pragma clang diagnostic pop + testassert(ok); + TWICE(testassert(class_getMethodImplementation([Sub class], @selector(superMethod2)) == (IMP)_objc_msgForward)); + + // class_replaceMethod does add new implementations, + // returning NULL if super has an implementation + imp = class_replaceMethod([Sub2 class], @selector(superMethod), (IMP)fn, NULL); + testassert(imp == NULL); + TWICE(testassert(class_getMethodImplementation([Sub2 class], @selector(superMethod)) == (IMP)fn)); + + // class_replaceMethod does add new implementations, + // returning NULL if super has no implementation + imp = class_replaceMethod([Sub2 class], @selector(subMethodNew), (IMP)fn, NULL); + testassert(imp == NULL); + TWICE(testassert(class_getMethodImplementation([Sub2 class], @selector(subMethodNew)) == (IMP)fn)); + + // class_replaceMethod does add new implemetations + // returning NULL if there is no super class + imp = class_replaceMethod([Super class], @selector(superMethodNew), (IMP)fn, NULL); + testassert(imp == NULL); + TWICE(testassert(class_getMethodImplementation([Super class], @selector(superMethodNew)) == (IMP)fn)); + + + // class_replaceMethod does replace existing implementations, + // returning existing implementation (regardless of super) + imp = class_replaceMethod([Sub2 class], @selector(subMethod), (IMP)fn, NULL); + testassert(imp == subMethodFromSub2); + TWICE(testassert(class_getMethodImplementation([Sub2 class], @selector(subMethod)) == (IMP)fn)); + + // class_replaceMethod does replace existing implemetations, + // returning existing implementation (regardless of super) + imp = class_replaceMethod([Sub2 class], @selector(bothMethod), (IMP)fn, NULL); + testassert(imp == bothMethodFromSub2); + TWICE(testassert(class_getMethodImplementation([Sub2 class], @selector(bothMethod)) == (IMP)fn)); + + // class_replaceMethod does replace existing implemetations, + // returning existing implementation (regardless of super) + imp = class_replaceMethod([Super class], @selector(superMethod), (IMP)fn, NULL); + testassert(imp == superMethodFromSuper); + TWICE(testassert(class_getMethodImplementation([Super class], @selector(superMethod)) == (IMP)fn)); + + // fixme actually try calling them + + succeed(__FILE__); +} diff --git a/test/addMethods.m b/test/addMethods.m new file mode 100644 index 0000000..953ada4 --- /dev/null +++ b/test/addMethods.m @@ -0,0 +1,323 @@ +// TEST_CONFIG + +#include "test.h" +#include "testroot.i" +#include +#include + +// Macros for array construction. +// ten IMPs +#define IMPS10 fn0, fn1, fn2, fn3, fn4, fn5, fn6, fn7, fn8, fn9 +// ten method types +#define TYPES10 "", "", "", "", "", "", "", "", "", "" +// ten selectors of the form name0..name9 +#define SELS10(name) \ + @selector(name##0), @selector(name##1), @selector(name##2), \ + @selector(name##3), @selector(name##4), @selector(name##5), \ + @selector(name##6), @selector(name##7), @selector(name##8), \ + @selector(name##9) + + +@interface Super : TestRoot @end +@implementation Super +-(int)superMethod0 { return 0; } +-(int)superMethod1 { return 0; } +-(int)superMethod2 { return 0; } +-(int)superMethod3 { return 0; } +-(int)superMethod4 { return 0; } +-(int)superMethod5 { return 0; } +-(int)superMethod6 { return 0; } +-(int)superMethod7 { return 0; } +-(int)superMethod8 { return 0; } +-(int)superMethod9 { return 0; } +-(int)bothMethod0 { return 0; } +-(int)bothMethod1 { return 0; } +-(int)bothMethod2 { return 0; } +-(int)bothMethod3 { return 0; } +-(int)bothMethod4 { return 0; } +-(int)bothMethod5 { return 0; } +-(int)bothMethod6 { return 0; } +-(int)bothMethod7 { return 0; } +-(int)bothMethod8 { return 0; } +-(int)bothMethod9 { return 0; } +@end + +@interface Sub : Super @end +@implementation Sub +-(int)subMethod0 { return 0; } +-(int)subMethod1 { return 0; } +-(int)subMethod2 { return 0; } +-(int)subMethod3 { return 0; } +-(int)subMethod4 { return 0; } +-(int)subMethod5 { return 0; } +-(int)subMethod6 { return 0; } +-(int)subMethod7 { return 0; } +-(int)subMethod8 { return 0; } +-(int)subMethod9 { return 0; } +-(int)bothMethod0 { return 0; } +-(int)bothMethod1 { return 0; } +-(int)bothMethod2 { return 0; } +-(int)bothMethod3 { return 0; } +-(int)bothMethod4 { return 0; } +-(int)bothMethod5 { return 0; } +-(int)bothMethod6 { return 0; } +-(int)bothMethod7 { return 0; } +-(int)bothMethod8 { return 0; } +-(int)bothMethod9 { return 0; } +@end + +@interface Sub2 : Super @end +@implementation Sub2 +-(int)subMethod0 { return 0; } +-(int)subMethod1 { return 0; } +-(int)subMethod2 { return 0; } +-(int)subMethod3 { return 0; } +-(int)subMethod4 { return 0; } +-(int)subMethod5 { return 0; } +-(int)subMethod6 { return 0; } +-(int)subMethod7 { return 0; } +-(int)subMethod8 { return 0; } +-(int)subMethod9 { return 0; } +-(int)bothMethod0 { return 0; } +-(int)bothMethod1 { return 0; } +-(int)bothMethod2 { return 0; } +-(int)bothMethod3 { return 0; } +-(int)bothMethod4 { return 0; } +-(int)bothMethod5 { return 0; } +-(int)bothMethod6 { return 0; } +-(int)bothMethod7 { return 0; } +-(int)bothMethod8 { return 0; } +-(int)bothMethod9 { return 0; } +@end + + +id fn0(id self __attribute__((unused)), SEL cmd __attribute__((unused)), ...) { + return nil; +} +id fn1(id self __attribute__((unused)), SEL cmd __attribute__((unused)), ...) { + return nil; +} +id fn2(id self __attribute__((unused)), SEL cmd __attribute__((unused)), ...) { + return nil; +} +id fn3(id self __attribute__((unused)), SEL cmd __attribute__((unused)), ...) { + return nil; +} +id fn4(id self __attribute__((unused)), SEL cmd __attribute__((unused)), ...) { + return nil; +} +id fn5(id self __attribute__((unused)), SEL cmd __attribute__((unused)), ...) { + return nil; +} +id fn6(id self __attribute__((unused)), SEL cmd __attribute__((unused)), ...) { + return nil; +} +id fn7(id self __attribute__((unused)), SEL cmd __attribute__((unused)), ...) { + return nil; +} +id fn8(id self __attribute__((unused)), SEL cmd __attribute__((unused)), ...) { + return nil; +} +id fn9(id self __attribute__((unused)), SEL cmd __attribute__((unused)), ...) { + return nil; +} + +void testBulkMemoryOnce(void) +{ + Class c = objc_allocateClassPair([TestRoot class], "c", 0); + objc_registerClassPair(c); + + SEL sels[10] = { + SELS10(method) + }; + IMP imps[10] = { + IMPS10 + }; + const char *types[10] = { + TYPES10 + }; + + uint32_t failureCount = 0; + SEL *failed; + + // Test all successes. + failed = class_addMethodsBulk(c, sels, imps, types, 4, &failureCount); + testassert(failed == NULL); + testassert(failureCount == 0); + + // Test mixed success and failure (this overlaps the previous one, so there + // will be one of each). + failed = class_addMethodsBulk(c, sels + 3, imps + 3, types + 3, 2, + &failureCount); + testassert(failed != NULL); + testassert(failureCount == 1); + testassert(failed[0] == sels[3]); + free(failed); + + // Test total failure. + failed = class_addMethodsBulk(c, sels, imps, types, 5, &failureCount); + testassert(failed != NULL); + testassert(failureCount == 5); + for(int i = 0; i < 5; i++) { + testassert(failed[i] == sels[i]); + } + free(failed); + + class_replaceMethodsBulk(c, sels, imps, types, 10); + + for(int i = 0; i < 10; i++) { + testassert(class_getMethodImplementation(c, sels[i]) == imps[i]); + } + + objc_disposeClassPair(c); +} + +int main() +{ + IMP dummyIMPs[130] = { + IMPS10, IMPS10, IMPS10, IMPS10, IMPS10, + IMPS10, IMPS10, IMPS10, IMPS10, IMPS10, + IMPS10, IMPS10, IMPS10, + }; + + // similar to dummyIMPs but with different values in each slot + IMP dummyIMPs2[130] = { + fn5, fn6, fn7, fn8, fn9, + IMPS10, IMPS10, IMPS10, IMPS10, IMPS10, + IMPS10, IMPS10, IMPS10, IMPS10, IMPS10, + IMPS10, IMPS10, + fn0, fn1, fn2, fn3, fn4, + }; + + const char *dummyTypes[130] = { + TYPES10, TYPES10, TYPES10, TYPES10, TYPES10, + TYPES10, TYPES10, TYPES10, TYPES10, TYPES10, + TYPES10, TYPES10, TYPES10, + }; + + SEL addSELs[20] = { + SELS10(superMethod), + SELS10(superMethodAddNew) + }; + + uint32_t failedCount = 0; + SEL *failed; + + failed = class_addMethodsBulk([Super class], addSELs, dummyIMPs, dummyTypes, + 20, &failedCount); + + // class_addMethodsBulk reports failures for all methods that already exist + testassert(failed != NULL); + testassert(failedCount == 10); + + // class_addMethodsBulk failed for existing implementations + for(int i = 0; i < 10; i++) { + testassert(failed[i] == addSELs[i]); + testassert(class_getMethodImplementation([Super class], addSELs[i]) + != dummyIMPs[i]); + } + + free(failed); + + // class_addMethodsBulk does add root implementations + for(int i = 10; i < 20; i++) { + testassert(class_getMethodImplementation([Super class], addSELs[i]) + == dummyIMPs[i]); + } + + // class_addMethod does override superclass implementations + failed = class_addMethodsBulk([Sub class], addSELs, dummyIMPs, dummyTypes, + 10, &failedCount); + testassert(failedCount == 0); + testassert(failed == NULL); + for(int i = 0; i < 10; i++) { + testassert(class_getMethodImplementation([Sub class], addSELs[i]) + == dummyIMPs[i]); + } + + SEL subReplaceSELs[40] = { + SELS10(superMethod), + SELS10(subMethodNew), + SELS10(subMethod), + SELS10(bothMethod), + }; + + // class_replaceMethodsBulk adds new implementations or replaces existing + // ones for methods that exist on the superclass, the subclass, both, or + // neither + class_replaceMethodsBulk([Sub2 class], subReplaceSELs, dummyIMPs, + dummyTypes, 40); + for(int i = 0; i < 40; i++) { + IMP newIMP = class_getMethodImplementation([Sub2 class], + subReplaceSELs[i]); + testassert(newIMP == dummyIMPs[i]); + } + + SEL superReplaceSELs[20] = { + SELS10(superMethod), + SELS10(superMethodNew), + }; + + // class_replaceMethodsBulk adds new implementations or replaces existing + // ones in the superclass + class_replaceMethodsBulk([Super class], superReplaceSELs, dummyIMPs, + dummyTypes, 20); + for(int i = 0; i < 20; i++) { + IMP newIMP = class_getMethodImplementation([Super class], + superReplaceSELs[i]); + testassert(newIMP == dummyIMPs[i]); + } + + + // class_addMethodsBulk, where almost all of the requested additions + // already exist and thus can't be added. (They were already added + // above by class_replaceMethodsBulk([Sub2 class], subReplaceSELs, ...).) + + // This list is large in the hope of provoking any realloc() of the + // new method list inside addMethods(). + // The runtime doesn't care that the list contains lots of duplicates. + SEL subAddMostlyExistingSELs[130] = { + SELS10(superMethod), SELS10(subMethodNew), SELS10(subMethod), + SELS10(superMethod), SELS10(subMethodNew), SELS10(subMethod), + SELS10(superMethod), SELS10(subMethodNew), SELS10(subMethod), + SELS10(superMethod), SELS10(subMethodNew), SELS10(subMethod), + SELS10(bothMethod), + }; + subAddMostlyExistingSELs[16] = @selector(INDEX_16_IS_DIFFERENT); + + failed = class_addMethodsBulk([Sub2 class], subAddMostlyExistingSELs, + dummyIMPs2, dummyTypes, 130, &failedCount); + testassert(failedCount == 129); + testassert(failed != NULL); + + for(int i = 0; i < 130; i++) { + IMP newIMP = class_getMethodImplementation([Sub2 class], + subAddMostlyExistingSELs[i]); + if (i == 16) { + // the only one that was actually added + testassert(newIMP != dummyIMPs[i]); + testassert(newIMP == dummyIMPs2[i]); + } else { + // the others should all have failed + testassert(newIMP == dummyIMPs[i]); + testassert(newIMP != dummyIMPs2[i]); + } + } + for (uint32_t i = 0; i < failedCount; i++) { + testassert(failed[i] != NULL); + testassert(failed[i] != subAddMostlyExistingSELs[16]); + } + + + // fixme actually try calling them + + // make sure the Bulk functions aren't leaking + testBulkMemoryOnce(); + leak_mark(); + for(int i = 0; i < 10; i++) { + testBulkMemoryOnce(); + } + leak_check(0); + + succeed(__FILE__); +} diff --git a/test/addProtocol.m b/test/addProtocol.m new file mode 100644 index 0000000..7f639b1 --- /dev/null +++ b/test/addProtocol.m @@ -0,0 +1,255 @@ +/* +TEST_CFLAGS -framework Foundation +TEST_BUILD_OUTPUT +.*addProtocol.m:\d+:\d+: warning: null passed to a callee that requires a non-null argument \[-Wnonnull\](\n.* note: expanded from macro 'testassert')? +.*addProtocol.m:\d+:\d+: warning: null passed to a callee that requires a non-null argument \[-Wnonnull\](\n.* note: expanded from macro 'testassert')? +.*addProtocol.m:\d+:\d+: warning: null passed to a callee that requires a non-null argument \[-Wnonnull\](\n.* note: expanded from macro 'testassert')? +.*addProtocol.m:\d+:\d+: warning: null passed to a callee that requires a non-null argument \[-Wnonnull\](\n.* note: expanded from macro 'testassert')? +.*addProtocol.m:\d+:\d+: warning: null passed to a callee that requires a non-null argument \[-Wnonnull\](\n.* note: expanded from macro 'testassert')? +END +TEST_RUN_OUTPUT +objc\[\d+\]: protocol_addProtocol: added protocol 'EmptyProto' is still under construction! +objc\[\d+\]: objc_registerProtocol: protocol 'Proto1' was already registered! +objc\[\d+\]: protocol_addProtocol: modified protocol 'Proto1' is not under construction! +objc\[\d+\]: protocol_addMethodDescription: protocol 'Proto1' is not under construction! +objc\[\d+\]: objc_registerProtocol: protocol 'SuperProto' was already registered! +objc\[\d+\]: protocol_addProtocol: modified protocol 'SuperProto' is not under construction! +objc\[\d+\]: protocol_addMethodDescription: protocol 'SuperProto' is not under construction! +OK: addProtocol.m +END +*/ + +#include "test.h" + +#include +#include + +@protocol SuperProto @end +@protocol SuperProto2 @end +@protocol UnrelatedProto @end + + +void Crash(id self, SEL _cmd) +{ + fail("%c[%s %s] called unexpectedly", + class_isMetaClass(object_getClass(self)) ? '+' : '-', + object_getClassName(self), sel_getName(_cmd)); +} + + +int main() +{ + // old-ABI implementation of [Protocol retain] + // is added by +[NSObject(NSObject) load] in CF. + [NSObject class]; + + Protocol *proto, *proto2; + Protocol * __unsafe_unretained *protolist; + struct objc_method_description *desclist; + objc_property_t *proplist; + unsigned int count; + + // If objc_registerProtocol() fails to preserve the retain count + // then ARC will deallocate Protocol objects too early. + class_replaceMethod(objc_getClass("Protocol"), + sel_registerName("dealloc"), (IMP)Crash, "v@:"); + class_replaceMethod(objc_getClass("__IncompleteProtocol"), + sel_registerName("dealloc"), (IMP)Crash, "v@:"); + + // make sure binary contains hard copies of these protocols + proto = @protocol(SuperProto); + proto = @protocol(SuperProto2); + + // Adding a protocol + + char *name = strdup("Proto1"); + proto = objc_allocateProtocol(name); + testassert(proto); + testassert(!objc_getProtocol(name)); + + protocol_addProtocol(proto, @protocol(SuperProto)); + protocol_addProtocol(proto, @protocol(SuperProto2)); + // no inheritance cycles + proto2 = objc_allocateProtocol("EmptyProto"); + protocol_addProtocol(proto, proto2); // fails + objc_registerProtocol(proto2); + protocol_addProtocol(proto, proto2); // succeeds + + char *types = strdup("@:"); + protocol_addMethodDescription(proto, @selector(ReqInst0), types, YES, YES); + protocol_addMethodDescription(proto, @selector(ReqInst1), types, YES, YES); + protocol_addMethodDescription(proto, @selector(ReqInst2), types, YES, YES); + protocol_addMethodDescription(proto, @selector(ReqInst3), types, YES, YES); + + protocol_addMethodDescription(proto, @selector(ReqClas0), types, YES, NO); + protocol_addMethodDescription(proto, @selector(ReqClas1), types, YES, NO); + protocol_addMethodDescription(proto, @selector(ReqClas2), types, YES, NO); + protocol_addMethodDescription(proto, @selector(ReqClas3), types, YES, NO); + + protocol_addMethodDescription(proto, @selector(OptInst0), types, NO, YES); + protocol_addMethodDescription(proto, @selector(OptInst1), types, NO, YES); + protocol_addMethodDescription(proto, @selector(OptInst2), types, NO, YES); + protocol_addMethodDescription(proto, @selector(OptInst3), types, NO, YES); + + protocol_addMethodDescription(proto, @selector(OptClas0), types, NO, NO); + protocol_addMethodDescription(proto, @selector(OptClas1), types, NO, NO); + protocol_addMethodDescription(proto, @selector(OptClas2), types, NO, NO); + protocol_addMethodDescription(proto, @selector(OptClas3), types, NO, NO); + + char *name0 = strdup("ReqInst0"); + char *name1 = strdup("ReqInst1"); + char *name2 = strdup("ReqInst2"); + char *name3 = strdup("ReqInst3"); + char *name4 = strdup("ReqClass0"); + char *name5 = strdup("ReqClass1"); + char *name6 = strdup("ReqClass2"); + char *name7 = strdup("ReqClass3"); + char *attrname = strdup("T"); + char *attrvalue = strdup("i"); + objc_property_attribute_t attrs[] = {{attrname, attrvalue}}; + int attrcount = sizeof(attrs) / sizeof(attrs[0]); + protocol_addProperty(proto, name0, attrs, attrcount, YES, YES); + protocol_addProperty(proto, name1, attrs, attrcount, YES, YES); + protocol_addProperty(proto, name2, attrs, attrcount, YES, YES); + protocol_addProperty(proto, name3, attrs, attrcount, YES, YES); + protocol_addProperty(proto, name4, attrs, attrcount, YES, NO); + protocol_addProperty(proto, name5, attrs, attrcount, YES, NO); + protocol_addProperty(proto, name6, attrs, attrcount, YES, NO); + protocol_addProperty(proto, name7, attrs, attrcount, YES, NO); + + objc_registerProtocol(proto); + testassert(0 == strcmp(protocol_getName(proto), "Proto1")); + + // Use of added protocols + + testassert(proto == objc_getProtocol("Proto1")); + strcpy(name, "XXXXXX"); // name is copied + testassert(0 == strcmp(protocol_getName(proto), "Proto1")); + + protolist = protocol_copyProtocolList(proto, &count); + testassert(protolist); + testassert(count == 3); + // note this order is not required + testassert(protolist[0] == @protocol(SuperProto) && + protolist[1] == @protocol(SuperProto2) && + protolist[2] == proto2); + free(protolist); + + testassert(protocol_conformsToProtocol(proto, proto2)); + testassert(protocol_conformsToProtocol(proto, @protocol(SuperProto))); + testassert(!protocol_conformsToProtocol(proto, @protocol(UnrelatedProto))); + + strcpy(types, "XX"); // types is copied + desclist = protocol_copyMethodDescriptionList(proto, YES, YES, &count); + testassert(desclist && count == 4); + testprintf("%p %p\n", desclist[0].name, @selector(ReqInst0)); + // testassert(desclist[0].name == @selector(ReqInst0)); + testassert(0 == strcmp(desclist[0].types, "@:")); + free(desclist); + desclist = protocol_copyMethodDescriptionList(proto, YES, NO, &count); + testassert(desclist && count == 4); + testassert(desclist[1].name == @selector(ReqClas1)); + testassert(0 == strcmp(desclist[1].types, "@:")); + free(desclist); + desclist = protocol_copyMethodDescriptionList(proto, NO, YES, &count); + testassert(desclist && count == 4); + testassert(desclist[2].name == @selector(OptInst2)); + testassert(0 == strcmp(desclist[2].types, "@:")); + free(desclist); + desclist = protocol_copyMethodDescriptionList(proto, NO, NO, &count); + testassert(desclist && count == 4); + testassert(desclist[3].name == @selector(OptClas3)); + testassert(0 == strcmp(desclist[3].types, "@:")); + free(desclist); + + strcpy(name0, "XXXXXXXX"); // name is copied + strcpy(name1, "XXXXXXXX"); // name is copied + strcpy(name2, "XXXXXXXX"); // name is copied + strcpy(name3, "XXXXXXXX"); // name is copied + strcpy(name4, "XXXXXXXXX"); // name is copied + strcpy(name5, "XXXXXXXXX"); // name is copied + strcpy(name6, "XXXXXXXXX"); // name is copied + strcpy(name7, "XXXXXXXXX"); // name is copied + strcpy(attrname, "X"); // description is copied + strcpy(attrvalue, "X"); // description is copied + memset(attrs, 'X', sizeof(attrs)); // description is copied + + // instance properties + count = 100; + proplist = protocol_copyPropertyList(proto, &count); + testassert(proplist); + testassert(count == 4); + // note this order is not required + testassert(0 == strcmp(property_getName(proplist[0]), "ReqInst0")); + testassert(0 == strcmp(property_getName(proplist[1]), "ReqInst1")); + testassert(0 == strcmp(property_getName(proplist[2]), "ReqInst2")); + testassert(0 == strcmp(property_getName(proplist[3]), "ReqInst3")); + testassert(0 == strcmp(property_getAttributes(proplist[0]), "Ti")); + testassert(0 == strcmp(property_getAttributes(proplist[1]), "Ti")); + testassert(0 == strcmp(property_getAttributes(proplist[2]), "Ti")); + testassert(0 == strcmp(property_getAttributes(proplist[3]), "Ti")); + free(proplist); + + // class properties + count = 100; + proplist = protocol_copyPropertyList2(proto, &count, YES, NO); + testassert(proplist); + testassert(count == 4); + // note this order is not required + testassert(0 == strcmp(property_getName(proplist[0]), "ReqClass0")); + testassert(0 == strcmp(property_getName(proplist[1]), "ReqClass1")); + testassert(0 == strcmp(property_getName(proplist[2]), "ReqClass2")); + testassert(0 == strcmp(property_getName(proplist[3]), "ReqClass3")); + testassert(0 == strcmp(property_getAttributes(proplist[0]), "Ti")); + testassert(0 == strcmp(property_getAttributes(proplist[1]), "Ti")); + testassert(0 == strcmp(property_getAttributes(proplist[2]), "Ti")); + testassert(0 == strcmp(property_getAttributes(proplist[3]), "Ti")); + free(proplist); + + + testassert(proto2 == objc_getProtocol("EmptyProto")); + testassert(0 == strcmp(protocol_getName(proto2), "EmptyProto")); + + protolist = protocol_copyProtocolList(proto2, &count); + testassert(!protolist); + testassert(count == 0); + + testassert(!protocol_conformsToProtocol(proto2, proto)); + testassert(!protocol_conformsToProtocol(proto2,@protocol(SuperProto))); + testassert(!protocol_conformsToProtocol(proto2,@protocol(UnrelatedProto))); + + desclist = protocol_copyMethodDescriptionList(proto2, YES, YES, &count); + testassert(!desclist && count == 0); + desclist = protocol_copyMethodDescriptionList(proto2, YES, NO, &count); + testassert(!desclist && count == 0); + desclist = protocol_copyMethodDescriptionList(proto2, NO, YES, &count); + testassert(!desclist && count == 0); + desclist = protocol_copyMethodDescriptionList(proto2, NO, NO, &count); + testassert(!desclist && count == 0); + + // Immutability of existing protocols + + objc_registerProtocol(proto); + protocol_addProtocol(proto, @protocol(SuperProto2)); + protocol_addMethodDescription(proto, @selector(foo), "", YES, YES); + + objc_registerProtocol(@protocol(SuperProto)); + protocol_addProtocol(@protocol(SuperProto), @protocol(SuperProto2)); + protocol_addMethodDescription(@protocol(SuperProto), @selector(foo), "", YES, YES); + + // No duplicates + + proto = objc_allocateProtocol("SuperProto"); + testassert(!proto); + proto = objc_allocateProtocol("Proto1"); + testassert(!proto); + + // NULL protocols ignored + + protocol_addProtocol((__bridge Protocol *)((void*)1), NULL); + protocol_addProtocol(NULL, (__bridge Protocol *)((void*)1)); + protocol_addProtocol(NULL, NULL); + protocol_addMethodDescription(NULL, @selector(foo), "", YES, YES); + + succeed(__FILE__); +} diff --git a/test/applescriptobjc.m b/test/applescriptobjc.m new file mode 100644 index 0000000..dd4370e --- /dev/null +++ b/test/applescriptobjc.m @@ -0,0 +1,13 @@ +// TEST_CONFIG OS=macosx +// TEST_CFLAGS -framework AppleScriptObjC -framework Foundation + +// Verify that trivial AppleScriptObjC apps run with GC off. + +#include +#include "test.h" + +int main() +{ + [NSBundle class]; + succeed(__FILE__); +} diff --git a/test/arr-cast.m b/test/arr-cast.m new file mode 100644 index 0000000..ddb6e21 --- /dev/null +++ b/test/arr-cast.m @@ -0,0 +1,20 @@ +// TEST_CONFIG + +#include "test.h" + +// objc.h redefines these calls into bridge casts. +// This test verifies that the function implementations are exported. +__BEGIN_DECLS +extern void *retainedObject(void *arg) __asm__("_objc_retainedObject"); +extern void *unretainedObject(void *arg) __asm__("_objc_unretainedObject"); +extern void *unretainedPointer(void *arg) __asm__("_objc_unretainedPointer"); +__END_DECLS + +int main() +{ + void *p = (void*)&main; + testassert(p == retainedObject(p)); + testassert(p == unretainedObject(p)); + testassert(p == unretainedPointer(p)); + succeed(__FILE__); +} diff --git a/test/arr-weak.m b/test/arr-weak.m new file mode 100644 index 0000000..a38077f --- /dev/null +++ b/test/arr-weak.m @@ -0,0 +1,329 @@ +// TEST_CONFIG MEM=mrc +// TEST_CRASHES +/* +TEST_RUN_OUTPUT +objc\[\d+\]: Cannot form weak reference to instance \(0x[0-9a-f]+\) of class Crash. It is possible that this object was over-released, or is in the process of deallocation. +objc\[\d+\]: HALTED +END +*/ + +#include "test.h" + +#include + +static id weak; +static id weak2; +static id weak3; +static id weak4; +static bool did_dealloc; + +static int state; + +@interface NSObject (WeakInternals) +-(BOOL)_tryRetain; +-(BOOL)_isDeallocating; +@end + +@interface Test : NSObject @end +@implementation Test +-(void)dealloc { + testprintf("Weak storeOrNil does not crash while deallocating\n"); + weak4 = (id)0x100; // old value must not be used + id result = objc_initWeakOrNil(&weak4, self); + testassert(result == nil); + testassert(weak4 == nil); + result = objc_storeWeakOrNil(&weak4, self); + testassert(result == nil); + testassert(weak4 == nil); + + // The value returned by objc_loadWeak() is now nil, + // but the storage is not yet cleared. + testassert(weak == self); + testassert(weak2 == self); + + // objc_loadWeak() does not eagerly clear the storage. + testassert(objc_loadWeakRetained(&weak) == nil); + testassert(weak != nil); + + // dealloc clears the storage. + testprintf("Weak references clear during super dealloc\n"); + testassert(weak2 != nil); + [super dealloc]; + testassert(weak == nil); + testassert(weak2 == nil); + + did_dealloc = true; +} +@end + +@interface CustomTryRetain : Test @end +@implementation CustomTryRetain +-(BOOL)_tryRetain { state++; return [super _tryRetain]; } +@end + +@interface CustomIsDeallocating : Test @end +@implementation CustomIsDeallocating +-(BOOL)_isDeallocating { state++; return [super _isDeallocating]; } +@end + +@interface CustomAllowsWeakReference : Test @end +@implementation CustomAllowsWeakReference +-(BOOL)allowsWeakReference { state++; return [super allowsWeakReference]; } +@end + +@interface CustomRetainWeakReference : Test @end +@implementation CustomRetainWeakReference +-(BOOL)retainWeakReference { state++; return [super retainWeakReference]; } +@end + +@interface Crash : NSObject @end +@implementation Crash +-(void)dealloc { + testassert(weak == self); + testassert(weak2 == self); + testassert(objc_loadWeakRetained(&weak) == nil); + testassert(objc_loadWeakRetained(&weak2) == nil); + + testprintf("Weak storeOrNil does not crash while deallocating\n"); + id result = objc_storeWeakOrNil(&weak, self); + testassert(result == nil); + + testprintf("Weak store crashes while deallocating\n"); + objc_storeWeak(&weak, self); + fail("objc_storeWeak of deallocating value should have crashed"); + [super dealloc]; +} +@end + + +void cycle(Class cls, Test *obj, Test *obj2, bool storeOrNil) +{ + testprintf("Cycling class %s\n", class_getName(cls)); + + id result; + + id (*storeWeak)(id *location, id obj); + id (*initWeak)(id *location, id obj); + if (storeOrNil) { + testprintf("Using objc_storeWeakOrNil\n"); + storeWeak = objc_storeWeakOrNil; + initWeak = objc_initWeakOrNil; + } else { + testprintf("Using objc_storeWeak\n"); + storeWeak = objc_storeWeak; + initWeak = objc_initWeak; + } + + // state counts calls to custom weak methods + // Difference test classes have different expected values. + int storeTarget; + int loadTarget; + if (cls == [Test class]) { + storeTarget = 0; + loadTarget = 0; + } + else if (cls == [CustomTryRetain class] || + cls == [CustomRetainWeakReference class]) + { + storeTarget = 0; + loadTarget = 1; + } + else if (cls == [CustomIsDeallocating class] || + cls == [CustomAllowsWeakReference class]) + { + storeTarget = 1; + loadTarget = 0; + } + else fail("wut"); + + testprintf("Weak assignment\n"); + state = 0; + result = storeWeak(&weak, obj); + testassert(state == storeTarget); + testassert(result == obj); + testassert(weak == obj); + + testprintf("Weak assignment to the same value\n"); + state = 0; + result = storeWeak(&weak, obj); + testassert(state == storeTarget); + testassert(result == obj); + testassert(weak == obj); + + testprintf("Weak load\n"); + state = 0; + result = objc_loadWeakRetained(&weak); + if (state != loadTarget) testprintf("state %d target %d\n", state, loadTarget); + testassert(state == loadTarget); + testassert(result == obj); + testassert(result == weak); + [result release]; + + testprintf("Weak assignment to different value\n"); + state = 0; + result = storeWeak(&weak, obj2); + testassert(state == storeTarget); + testassert(result == obj2); + testassert(weak == obj2); + + testprintf("Weak assignment to NULL\n"); + state = 0; + result = storeWeak(&weak, NULL); + testassert(state == 0); + testassert(result == NULL); + testassert(weak == NULL); + + testprintf("Weak re-assignment to NULL\n"); + state = 0; + result = storeWeak(&weak, NULL); + testassert(state == 0); + testassert(result == NULL); + testassert(weak == NULL); + + testprintf("Weak move\n"); + state = 0; + result = storeWeak(&weak, obj); + testassert(state == storeTarget); + testassert(result == obj); + testassert(weak == obj); + weak2 = (id)(PAGE_MAX_SIZE-16); + objc_moveWeak(&weak2, &weak); + testassert(weak == nil); + testassert(weak2 == obj); + storeWeak(&weak2, NULL); + + testprintf("Weak copy\n"); + state = 0; + result = storeWeak(&weak, obj); + testassert(state == storeTarget); + testassert(result == obj); + testassert(weak == obj); + weak2 = (id)(PAGE_MAX_SIZE-16); + objc_copyWeak(&weak2, &weak); + testassert(weak == obj); + testassert(weak2 == obj); + storeWeak(&weak, NULL); + storeWeak(&weak2, NULL); + + testprintf("Weak clear\n"); + + id obj3 = [cls new]; + + state = 0; + result = storeWeak(&weak, obj3); + testassert(state == storeTarget); + testassert(result == obj3); + testassert(weak == obj3); + + state = 0; + result = storeWeak(&weak2, obj3); + testassert(state == storeTarget); + testassert(result == obj3); + testassert(weak2 == obj3); + + did_dealloc = false; + [obj3 release]; + testassert(did_dealloc); + testassert(weak == NULL); + testassert(weak2 == NULL); + + + testprintf("Weak init and destroy\n"); + + id obj4 = [cls new]; + + state = 0; + weak = (id)0x100; // old value must not be used + result = initWeak(&weak, obj4); + testassert(state == storeTarget); + testassert(result == obj4); + testassert(weak == obj4); + + state = 0; + weak2 = (id)0x100; // old value must not be used + result = initWeak(&weak2, obj4); + testassert(state == storeTarget); + testassert(result == obj4); + testassert(weak2 == obj4); + + state = 0; + weak3 = (id)0x100; // old value must not be used + result = initWeak(&weak3, obj4); + testassert(state == storeTarget); + testassert(result == obj4); + testassert(weak3 == obj4); + + state = 0; + objc_destroyWeak(&weak3); + testassert(state == 0); + testassert(weak3 == obj4); // storage is unchanged + + did_dealloc = false; + [obj4 release]; + testassert(did_dealloc); + testassert(weak == NULL); // not destroyed earlier so cleared now + testassert(weak2 == NULL); // not destroyed earlier so cleared now + testassert(weak3 == obj4); // destroyed earlier so not cleared now + + objc_destroyWeak(&weak); + objc_destroyWeak(&weak2); +} + + +void test_class(Class cls) +{ + // prime strong and weak side tables before leak checking + Test *prime[256] = {nil}; + for (size_t i = 0; i < sizeof(prime)/sizeof(prime[0]); i++) { + objc_storeWeak(&prime[i], [cls new]); + } + + Test *obj = [cls new]; + Test *obj2 = [cls new]; + + for (int i = 0; i < 100000; i++) { + cycle(cls, obj, obj2, false); + cycle(cls, obj, obj2, true); + } + leak_mark(); + for (int i = 0; i < 100000; i++) { + cycle(cls, obj, obj2, false); + cycle(cls, obj, obj2, true); + } + // allow some slop for side table expansion + // 5120 is common with this configuration + leak_check(6000); + + // rdar://14105994 + id weaks[8]; + for (size_t i = 0; i < sizeof(weaks)/sizeof(weaks[0]); i++) { + objc_storeWeak(&weaks[i], obj); + } + for (size_t i = 0; i < sizeof(weaks)/sizeof(weaks[0]); i++) { + objc_storeWeak(&weaks[i], nil); + } +} + +int main() +{ + test_class([Test class]); + test_class([CustomTryRetain class]); + test_class([CustomIsDeallocating class]); + test_class([CustomAllowsWeakReference class]); + test_class([CustomRetainWeakReference class]); + + + id result; + + Crash *obj3 = [Crash new]; + result = objc_storeWeak(&weak, obj3); + testassert(result == obj3); + testassert(weak == obj3); + + result = objc_storeWeak(&weak2, obj3); + testassert(result == obj3); + testassert(weak2 == obj3); + + [obj3 release]; + fail("should have crashed in -[Crash dealloc]"); +} diff --git a/test/asm-placeholder.s b/test/asm-placeholder.s new file mode 100644 index 0000000..f222644 --- /dev/null +++ b/test/asm-placeholder.s @@ -0,0 +1,47 @@ +.macro NOP16 + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop +.endmacro + +.macro NOP256 + NOP16 + NOP16 + NOP16 + NOP16 + NOP16 + NOP16 + NOP16 + NOP16 + NOP16 + NOP16 + NOP16 + NOP16 + NOP16 + NOP16 + NOP16 + NOP16 +.endmacro + +.text + .globl _main + .align 14 +_main: + // at least 1024 instruction bytes on all architectures + NOP256 + NOP256 + NOP256 + NOP256 diff --git a/test/association-cf.m b/test/association-cf.m new file mode 100644 index 0000000..4ffa2f4 --- /dev/null +++ b/test/association-cf.m @@ -0,0 +1,40 @@ +// TEST_CFLAGS -framework CoreFoundation +// TEST_CONFIG MEM=mrc +// not for ARC because ARC memory management doesn't +// work on CF types whose ObjC side is not yet loaded + +#include +#include + +#include "test.h" + +#if __has_feature(objc_arc) + +int main() +{ + testwarn("rdar://11368528 confused by Foundation"); + succeed(__FILE__); +} + +#else + +int main() +{ + // rdar://6164781 setAssociatedObject on unresolved future class crashes + + id mp = (id)CFMachPortCreate(0, 0, 0, 0); + testassert(mp); + + testassert(! objc_getClass("NSMachPort")); + + objc_setAssociatedObject(mp, (void*)1, mp, OBJC_ASSOCIATION_ASSIGN); + + id obj = objc_getAssociatedObject(mp, (void*)1); + testassert(obj == mp); + + CFRelease((CFTypeRef)mp); + + succeed(__FILE__); +} + +#endif diff --git a/test/association.m b/test/association.m new file mode 100644 index 0000000..c297421 --- /dev/null +++ b/test/association.m @@ -0,0 +1,127 @@ +// TEST_CONFIG + +#include "test.h" +#include +#include + +static int values; +static int supers; +static int subs; + +static const char *key = "key"; + + +@interface Value : NSObject @end +@interface Super : NSObject @end +@interface Sub : NSObject @end + +@interface Super2 : NSObject @end +@interface Sub2 : NSObject @end + +@implementation Super +-(id) init +{ + // rdar://8270243 don't lose associations after isa swizzling + + id value = [Value new]; + objc_setAssociatedObject(self, &key, value, OBJC_ASSOCIATION_RETAIN); + RELEASE_VAR(value); + + object_setClass(self, [Sub class]); + + return self; +} + +-(void) dealloc +{ + supers++; + SUPER_DEALLOC(); +} + +@end + +@implementation Sub +-(void) dealloc +{ + subs++; + SUPER_DEALLOC(); +} +@end + +@implementation Super2 +-(id) init +{ + // rdar://9617109 don't lose associations after isa swizzling + + id value = [Value new]; + object_setClass(self, [Sub2 class]); + objc_setAssociatedObject(self, &key, value, OBJC_ASSOCIATION_RETAIN); + RELEASE_VAR(value); + object_setClass(self, [Super2 class]); + + return self; +} + +-(void) dealloc +{ + supers++; + SUPER_DEALLOC(); +} + +@end + +@implementation Sub2 +-(void) dealloc +{ + subs++; + SUPER_DEALLOC(); +} +@end + +@implementation Value +-(void) dealloc { + values++; + SUPER_DEALLOC(); +} +@end + + +int main() +{ + testonthread(^{ + int i; + for (i = 0; i < 100; i++) { + RELEASE_VALUE([[Super alloc] init]); + } + }); + testcollect(); + + testassert(supers == 0); + testassert(subs > 0); + testassert(subs == values); + + + supers = 0; + subs = 0; + values = 0; + + testonthread(^{ + int i; + for (i = 0; i < 100; i++) { + RELEASE_VALUE([[Super2 alloc] init]); + } + }); + testcollect(); + + testassert(supers > 0); + testassert(subs == 0); + testassert(supers == values); + + // rdar://44094390 tolerate nil object and nil value +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wnonnull" + objc_setAssociatedObject(nil, &key, nil, 0); +#pragma clang diagnostic pop + + succeed(__FILE__); +} diff --git a/test/associationForbidden.h b/test/associationForbidden.h new file mode 100644 index 0000000..2ec4de0 --- /dev/null +++ b/test/associationForbidden.h @@ -0,0 +1,50 @@ +#include "testroot.i" + +@interface Normal : TestRoot +@end +@implementation Normal +@end + +@interface Forbidden : TestRoot +@end +@implementation Forbidden +@end + +struct minimal_unrealized_class { + void *isa; + void *superclass; + void *cachePtr; + uintptr_t maskAndOccupied; + struct minimal_class_ro *ro; +}; + +struct minimal_class_ro { + uint32_t flags; +}; + +extern struct minimal_unrealized_class OBJC_CLASS_$_Forbidden; + +#define RO_FORBIDS_ASSOCIATED_OBJECTS (1<<10) + +static void *key = &key; + +static void test(void); + +int main() +{ + struct minimal_unrealized_class *localForbidden = &OBJC_CLASS_$_Forbidden; + localForbidden->ro->flags |= RO_FORBIDS_ASSOCIATED_OBJECTS; + test(); +} + +static inline void ShouldSucceed(id obj) { + objc_setAssociatedObject(obj, key, obj, OBJC_ASSOCIATION_ASSIGN); + id assoc = objc_getAssociatedObject(obj, key); + fprintf(stderr, "Associated object is %p\n", assoc); + testassert(obj == assoc); +} + +static inline void ShouldFail(id obj) { + objc_setAssociatedObject(obj, key, obj, OBJC_ASSOCIATION_ASSIGN); + fail("should have crashed trying to set the associated object"); +} diff --git a/test/associationForbidden.m b/test/associationForbidden.m new file mode 100644 index 0000000..1afac35 --- /dev/null +++ b/test/associationForbidden.m @@ -0,0 +1,16 @@ +// TEST_CRASHES +/* +TEST_RUN_OUTPUT +Associated object is 0x[0-9a-fA-F]+ +objc\[\d+\]: objc_setAssociatedObject called on instance \(0x[0-9a-fA-F]+\) of class Forbidden which does not allow associated objects +objc\[\d+\]: HALTED +END +*/ + +#include "associationForbidden.h" + +void test(void) +{ + ShouldSucceed([Normal alloc]); + ShouldFail([Forbidden alloc]); +} diff --git a/test/associationForbidden2.m b/test/associationForbidden2.m new file mode 100644 index 0000000..9d71ee0 --- /dev/null +++ b/test/associationForbidden2.m @@ -0,0 +1,19 @@ +// TEST_CRASHES +/* +TEST_RUN_OUTPUT +Associated object is 0x[0-9a-fA-F]+ +objc\[\d+\]: objc_setAssociatedObject called on instance \(0x[0-9a-fA-F]+\) of class ForbiddenSubclass which does not allow associated objects +objc\[\d+\]: HALTED +END +*/ + +#include "associationForbidden.h" + +void test(void) +{ + ShouldSucceed([Normal alloc]); + Class ForbiddenSubclass = objc_allocateClassPair([Forbidden class], + "ForbiddenSubclass", 0); + objc_registerClassPair(ForbiddenSubclass); + ShouldFail([ForbiddenSubclass alloc]); +} diff --git a/test/associationForbidden3.m b/test/associationForbidden3.m new file mode 100644 index 0000000..f96ba90 --- /dev/null +++ b/test/associationForbidden3.m @@ -0,0 +1,21 @@ +// TEST_CRASHES +/* +TEST_RUN_OUTPUT +Associated object is 0x[0-9a-fA-F]+ +objc\[\d+\]: objc_setAssociatedObject called on instance \(0x[0-9a-fA-F]+\) of class ForbiddenSubclass which does not allow associated objects +objc\[\d+\]: HALTED +END +*/ + +#include "associationForbidden.h" + +@interface ForbiddenSubclass : Forbidden +@end +@implementation ForbiddenSubclass +@end + +void test(void) +{ + ShouldSucceed([Normal alloc]); + ShouldSucceed([ForbiddenSubclass alloc]); +} diff --git a/test/associationForbidden4.m b/test/associationForbidden4.m new file mode 100644 index 0000000..05370fe --- /dev/null +++ b/test/associationForbidden4.m @@ -0,0 +1,18 @@ +// TEST_CRASHES +/* +TEST_RUN_OUTPUT +Associated object is 0x[0-9a-fA-F]+ +objc\[\d+\]: objc_setAssociatedObject called on instance \(0x[0-9a-fA-F]+\) of class ForbiddenDuplicate which does not allow associated objects +objc\[\d+\]: HALTED +END +*/ + +#include "associationForbidden.h" + +void test(void) +{ + ShouldSucceed([Normal alloc]); + Class ForbiddenDuplicate = objc_duplicateClass([Forbidden class], + "ForbiddenDuplicate", 0); + ShouldFail([ForbiddenDuplicate alloc]); +} diff --git a/test/atomicProperty.mm b/test/atomicProperty.mm new file mode 100644 index 0000000..2625e76 --- /dev/null +++ b/test/atomicProperty.mm @@ -0,0 +1,41 @@ +// TEST_CONFIG + +#include "test.h" +#include +#include +#import + +class SerialNumber { + size_t _number; +public: + SerialNumber() : _number(42) {} + SerialNumber(const SerialNumber &number) : _number(number._number + 1) {} + SerialNumber &operator=(const SerialNumber &number) { _number = number._number + 1; return *this; } + + int operator==(const SerialNumber &number) { return _number == number._number; } + int operator!=(const SerialNumber &number) { return _number != number._number; } +}; + +@interface TestAtomicProperty : NSObject { + SerialNumber number; +} +@property(atomic) SerialNumber number; +@end + +@implementation TestAtomicProperty + +@synthesize number; + +@end + +int main() +{ + PUSH_POOL { + SerialNumber number; + TestAtomicProperty *test = [TestAtomicProperty new]; + test.number = number; + testassert(test.number != number); + } POP_POOL; + + succeed(__FILE__); +} diff --git a/test/badAltHandler.m b/test/badAltHandler.m new file mode 100644 index 0000000..6ea4c76 --- /dev/null +++ b/test/badAltHandler.m @@ -0,0 +1,81 @@ +/* TEST_CONFIG OS=macosx + TEST_CRASHES + +TEST_RUN_OUTPUT +objc\[\d+\]: objc_removeExceptionHandler\(\) called with unknown alt handler; this is probably a bug in multithreaded AppKit use. Set environment variable OBJC_DEBUG_ALT_HANDLERS=YES or break in objc_alt_handler_error\(\) to debug. +objc\[\d+\]: objc_removeExceptionHandler\(\) called with unknown alt handler; this is probably a bug in multithreaded AppKit use. +objc\[\d+\]: HALTED +END +*/ + +#include "test.h" + +#include + +/* + rdar://6888838 + Mail installs an alt handler on one thread and deletes it on another. + This confuses the alt handler machinery, which halts the process. +*/ + +uintptr_t Token; + +void handler(id unused __unused, void *context __unused) +{ +} + +int main() +{ +#if __clang__ && __cplusplus + // alt handlers need the objc personality + // catch (id) workaround forces the objc personality + @try { + testwarn("rdar://9183014 clang uses wrong exception personality"); + } @catch (id e __unused) { + } +#endif + + @try { + // Install 4 alt handlers + uintptr_t t1, t2, t3, t4; + t1 = objc_addExceptionHandler(&handler, NULL); + t2 = objc_addExceptionHandler(&handler, NULL); + t3 = objc_addExceptionHandler(&handler, NULL); + t4 = objc_addExceptionHandler(&handler, NULL); + + // Remove 3 of them. + objc_removeExceptionHandler(t1); + objc_removeExceptionHandler(t2); + objc_removeExceptionHandler(t3); + + // Create an alt handler on another thread + // that collides with one of the removed handlers + testonthread(^{ + @try { + Token = objc_addExceptionHandler(&handler, NULL); + } @catch (...) { + } + }); + + // Incorrectly remove the other thread's handler + objc_removeExceptionHandler(Token); + // Remove the 4th handler + objc_removeExceptionHandler(t4); + + // Install 8 more handlers. + // If the other thread's handler was not ignored, + // this will fail. + objc_addExceptionHandler(&handler, NULL); + objc_addExceptionHandler(&handler, NULL); + objc_addExceptionHandler(&handler, NULL); + objc_addExceptionHandler(&handler, NULL); + objc_addExceptionHandler(&handler, NULL); + objc_addExceptionHandler(&handler, NULL); + objc_addExceptionHandler(&handler, NULL); + objc_addExceptionHandler(&handler, NULL); + } @catch (...) { + } + + // This should have crashed earlier. + fail(__FILE__); +} diff --git a/test/badCache.m b/test/badCache.m new file mode 100644 index 0000000..08daf77 --- /dev/null +++ b/test/badCache.m @@ -0,0 +1,89 @@ +/* +TEST_CRASHES +TEST_RUN_OUTPUT +arm +OK: badCache.m +OR +crash now +objc\[\d+\]: Method cache corrupted.* +objc\[\d+\]: .* +objc\[\d+\]: .* +objc\[\d+\]: .* +objc\[\d+\]: .* +objc\[\d+\]: Method cache corrupted.* +objc\[\d+\]: HALTED +END +*/ + + +#include "test.h" + +// Test objc_msgSend's detection of infinite loops during cache scan. + +#if __arm__ + +int main() +{ + testwarn("objc_msgSend on arm doesn't detect infinite loops"); + fprintf(stderr, "arm\n"); + succeed(__FILE__); +} + +#else + +#include "testroot.i" + +#if __LP64__ +typedef uint32_t mask_t; +#else +typedef uint16_t mask_t; +#endif + +struct bucket_t { + uintptr_t sel; + uintptr_t imp; +}; + +struct cache_t { + struct bucket_t *buckets; + mask_t mask; + mask_t occupied; +}; + +struct class_t { + void *isa; + void *supercls; + struct cache_t cache; +}; + +@interface Subclass : TestRoot @end +@implementation Subclass @end + +int main() +{ + Class cls = [TestRoot class]; + id obj = [cls new]; + [obj self]; + + struct cache_t *cache = &((__bridge struct class_t *)cls)->cache; + +# define COUNT 4 + struct bucket_t *buckets = (struct bucket_t *)calloc(sizeof(struct bucket_t), COUNT+1); + for (int i = 0; i < COUNT; i++) { + buckets[i].sel = ~0; + buckets[i].imp = ~0; + } + buckets[COUNT].sel = 1; + buckets[COUNT].imp = (uintptr_t)buckets; + + cache->mask = COUNT-1; + cache->occupied = 0; + cache->buckets = buckets; + + fprintf(stderr, "crash now\n"); + [obj self]; + + fail("should have crashed"); +} + +#endif diff --git a/test/badPool.m b/test/badPool.m new file mode 100644 index 0000000..fa47e8e --- /dev/null +++ b/test/badPool.m @@ -0,0 +1,34 @@ +// TEST_CONFIG MEM=mrc +// TEST_CRASHES + +// Test badPoolCompat also uses this file. + +/* +TEST_RUN_OUTPUT +objc\[\d+\]: [Ii]nvalid or prematurely-freed autorelease pool 0x[0-9a-fA-F]+\.? +objc\[\d+\]: HALTED +END +*/ + +#include "test.h" + +int main() +{ + void *outer = objc_autoreleasePoolPush(); + void *inner = objc_autoreleasePoolPush(); + objc_autoreleasePoolPop(outer); + objc_autoreleasePoolPop(inner); + +#if !OLD + fail("should have crashed already with new SDK"); +#else + // should only warn once + outer = objc_autoreleasePoolPush(); + inner = objc_autoreleasePoolPush(); + objc_autoreleasePoolPop(outer); + objc_autoreleasePoolPop(inner); + + succeed(__FILE__); +#endif +} + diff --git a/test/badPoolCompat-ios-tvos.m b/test/badPoolCompat-ios-tvos.m new file mode 100644 index 0000000..5f1b92c --- /dev/null +++ b/test/badPoolCompat-ios-tvos.m @@ -0,0 +1,14 @@ +// Run test badPool as if it were built with an old SDK. + +// TEST_CONFIG MEM=mrc OS=iphoneos,iphonesimulator,appletvos,appletvsimulator +// TEST_CRASHES +// TEST_CFLAGS -DOLD=1 -Xlinker -sdk_version -Xlinker 9.0 + +/* +TEST_RUN_OUTPUT +objc\[\d+\]: Invalid or prematurely-freed autorelease pool 0x[0-9a-fA-f]+\. Set a breakpoint .* Proceeding anyway .* +OK: badPool.m +END +*/ + +#include "badPool.m" diff --git a/test/badPoolCompat-macos.m b/test/badPoolCompat-macos.m new file mode 100644 index 0000000..afd2117 --- /dev/null +++ b/test/badPoolCompat-macos.m @@ -0,0 +1,14 @@ +// Run test badPool as if it were built with an old SDK. + +// TEST_CONFIG MEM=mrc OS=macosx +// TEST_CRASHES +// TEST_CFLAGS -DOLD=1 -Xlinker -sdk_version -Xlinker 10.11 + +/* +TEST_RUN_OUTPUT +objc\[\d+\]: Invalid or prematurely-freed autorelease pool 0x[0-9a-fA-f]+\. Set a breakpoint .* Proceeding anyway .* +OK: badPool.m +END +*/ + +#include "badPool.m" diff --git a/test/badPoolCompat-watchos.m b/test/badPoolCompat-watchos.m new file mode 100644 index 0000000..6e89e44 --- /dev/null +++ b/test/badPoolCompat-watchos.m @@ -0,0 +1,14 @@ +// Run test badPool as if it were built with an old SDK. + +// TEST_CONFIG MEM=mrc OS=watchos,watchsimulator +// TEST_CRASHES +// TEST_CFLAGS -DOLD=1 -Xlinker -sdk_version -Xlinker 2.0 + +/* +TEST_RUN_OUTPUT +objc\[\d+\]: Invalid or prematurely-freed autorelease pool 0x[0-9a-fA-f]+\. Set a breakpoint .* Proceeding anyway .* +OK: badPool.m +END +*/ + +#include "badPool.m" diff --git a/test/badSuperclass.m b/test/badSuperclass.m new file mode 100644 index 0000000..2fa0bc7 --- /dev/null +++ b/test/badSuperclass.m @@ -0,0 +1,37 @@ +// TEST_CRASHES +/* +TEST_RUN_OUTPUT +objc\[\d+\]: Memory corruption in class list\. +objc\[\d+\]: HALTED +END +*/ + +#include "test.h" +#include "testroot.i" + +@interface Super : TestRoot @end +@implementation Super @end + +@interface Sub : Super @end +@implementation Sub @end + +int main() +{ + alarm(10); + + Class supercls = [Super class]; + Class subcls = [Sub class]; + id subobj __unused = [Sub alloc]; + + // Create a cycle in a superclass chain (Sub->supercls == Sub) + // then attempt to walk that chain. Runtime should halt eventually. + _objc_flush_caches(supercls); + ((Class *)(__bridge void *)subcls)[1] = subcls; +#ifdef CACHE_FLUSH + _objc_flush_caches(supercls); +#else + [subobj class]; +#endif + + fail("should have crashed"); +} diff --git a/test/badSuperclass2.m b/test/badSuperclass2.m new file mode 100644 index 0000000..a75f053 --- /dev/null +++ b/test/badSuperclass2.m @@ -0,0 +1,13 @@ +// TEST_CRASHES +/* +TEST_RUN_OUTPUT +objc\[\d+\]: Memory corruption in class list\. +objc\[\d+\]: HALTED +OR +old abi +OK: badSuperclass\.m +END +*/ + +#define CACHE_FLUSH +#include "badSuperclass.m" diff --git a/test/badTagClass.m b/test/badTagClass.m new file mode 100644 index 0000000..2a08ebd --- /dev/null +++ b/test/badTagClass.m @@ -0,0 +1,48 @@ +/* +TEST_CRASHES +TEST_BUILD_OUTPUT +.*badTagClass.m:\d+:\d+: warning: null passed to a callee that requires a non-null argument \[-Wnonnull\] +END +TEST_RUN_OUTPUT +objc\[\d+\]: tag index 1 used for two different classes \(was 0x[0-9a-fA-F]+ NSObject, now 0x[0-9a-fA-F]+ TestRoot\) +objc\[\d+\]: HALTED +OR +no tagged pointers +OK: badTagClass.m +END +*/ + +#include "test.h" +#include "testroot.i" + +#include +#include + +#if OBJC_HAVE_TAGGED_POINTERS + +int main() +{ + // re-registration and nil registration allowed + _objc_registerTaggedPointerClass(OBJC_TAG_1, [NSObject class]); + _objc_registerTaggedPointerClass(OBJC_TAG_1, [NSObject class]); + _objc_registerTaggedPointerClass(OBJC_TAG_1, nil); + _objc_registerTaggedPointerClass(OBJC_TAG_1, [NSObject class]); + + // colliding registration disallowed + _objc_registerTaggedPointerClass(OBJC_TAG_1, [TestRoot class]); + + fail(__FILE__); +} + +#else + +int main() +{ + // provoke the same nullability warning as the real test + objc_getClass(nil); + + fprintf(stderr, "no tagged pointers\n"); + succeed(__FILE__); +} + +#endif diff --git a/test/badTagIndex.m b/test/badTagIndex.m new file mode 100644 index 0000000..c4bfd33 --- /dev/null +++ b/test/badTagIndex.m @@ -0,0 +1,33 @@ +/* +TEST_CRASHES +TEST_RUN_OUTPUT +objc\[\d+\]: tag index 264 is invalid +objc\[\d+\]: HALTED +OR +no tagged pointers +OK: badTagIndex.m +END +*/ + +#include "test.h" + +#include +#include + +#if OBJC_HAVE_TAGGED_POINTERS + +int main() +{ + _objc_registerTaggedPointerClass((objc_tag_index_t)(OBJC_TAG_Last52BitPayload+1), [NSObject class]); + fail(__FILE__); +} + +#else + +int main() +{ + fprintf(stderr, "no tagged pointers\n"); + succeed(__FILE__); +} + +#endif diff --git a/test/bigrc.m b/test/bigrc.m new file mode 100644 index 0000000..419bbb6 --- /dev/null +++ b/test/bigrc.m @@ -0,0 +1,139 @@ +// TEST_CONFIG MEM=mrc +/* +TEST_RUN_OUTPUT +objc\[\d+\]: Deallocator object 0x[0-9a-fA-F]+ overreleased while already deallocating; break on objc_overrelease_during_dealloc_error to debug +OK: bigrc.m +OR +no overrelease enforcement +OK: bigrc.m +END + */ + +#include "test.h" +#include "testroot.i" + +static size_t LOTS; + +@interface Deallocator : TestRoot @end +@implementation Deallocator + +-(void)dealloc +{ + id o = self; + size_t rc = 1; + + + testprintf("Retain a lot during dealloc\n"); + + testassert(rc == 1); + testassert([o retainCount] == rc); + do { + [o retain]; + if (rc % 0x100000 == 0) testprintf("%zx/%zx ++\n", rc, LOTS); + } while (++rc < LOTS); + + testassert([o retainCount] == rc); + + do { + [o release]; + if (rc % 0x100000 == 0) testprintf("%zx/%zx --\n", rc, LOTS); + } while (--rc > 1); + + testassert(rc == 1); + testassert([o retainCount] == rc); + + + testprintf("Overrelease during dealloc\n"); + + // Not all architectures enforce this. +#if !SUPPORT_NONPOINTER_ISA + testwarn("no overrelease enforcement"); + fprintf(stderr, "no overrelease enforcement\n"); +#endif + [o release]; + + [super dealloc]; +} + +@end + +size_t clz(uintptr_t isa) { + if (sizeof(uintptr_t) == 4) + return __builtin_clzl(isa); + testassert(sizeof(uintptr_t) == 8); + return __builtin_clzll(isa); +} + +int main() +{ + Deallocator *o = [Deallocator new]; + size_t rc = 1; + + [o retain]; + + uintptr_t isa = *(uintptr_t *)o; + if (isa & 1) { + // Assume refcount in high bits. + LOTS = 1 << (4 + clz(isa)); + testprintf("LOTS %zu via cntlzw\n", LOTS); + } else { + LOTS = 0x1000000; + testprintf("LOTS %zu via guess\n", LOTS); + } + + [o release]; + + + testprintf("Retain a lot\n"); + + testassert(rc == 1); + testassert([o retainCount] == rc); + do { + [o retain]; + if (rc % 0x100000 == 0) testprintf("%zx/%zx ++\n", rc, LOTS); + } while (++rc < LOTS); + + testassert([o retainCount] == rc); + + do { + [o release]; + if (rc % 0x100000 == 0) testprintf("%zx/%zx --\n", rc, LOTS); + } while (--rc > 1); + + testassert(rc == 1); + testassert([o retainCount] == rc); + + + testprintf("tryRetain a lot\n"); + + id w; + objc_storeWeak(&w, o); + testassert(w == o); + + testassert(rc == 1); + testassert([o retainCount] == rc); + do { + objc_loadWeakRetained(&w); + if (rc % 0x100000 == 0) testprintf("%zx/%zx ++\n", rc, LOTS); + } while (++rc < LOTS); + + testassert([o retainCount] == rc); + + do { + [o release]; + if (rc % 0x100000 == 0) testprintf("%zx/%zx --\n", rc, LOTS); + } while (--rc > 1); + + testassert(rc == 1); + testassert([o retainCount] == rc); + + testprintf("dealloc\n"); + + testassert(TestRootDealloc == 0); + testassert(w != nil); + [o release]; + testassert(TestRootDealloc == 1); + testassert(w == nil); + + succeed(__FILE__); +} diff --git a/test/blocksAsImps.m b/test/blocksAsImps.m new file mode 100644 index 0000000..2ba2580 --- /dev/null +++ b/test/blocksAsImps.m @@ -0,0 +1,252 @@ +// TEST_CFLAGS -framework Foundation + +#include "test.h" +#include +#import + +#include + +#if !__has_feature(objc_arc) +# define __bridge +#endif + +#if __arm64__ + // stret supported, but is identical to non-stret +# define STRET_SPECIAL 0 +#else + // stret supported and distinct from non-stret +# define STRET_SPECIAL 1 +#endif + +typedef struct BigStruct { + uintptr_t datums[200]; +} BigStruct; + +@interface Foo:NSObject +@end +@implementation Foo +- (BigStruct) methodThatReturnsBigStruct: (BigStruct) b +{ + return b; +} +@end + +@interface Foo(bar) +- (int) boo: (int) a; +- (BigStruct) structThatIsBig: (BigStruct) b; +- (BigStruct) methodThatReturnsBigStruct: (BigStruct) b; +- (float) methodThatReturnsFloat: (float) aFloat; +@end + +// This is void* instead of id to prevent interference from ARC. +typedef uintptr_t (*FuncPtr)(void *, SEL); +typedef BigStruct (*BigStructFuncPtr)(id, SEL, BigStruct); +typedef float (*FloatFuncPtr)(id, SEL, float); + +BigStruct bigfunc(BigStruct a) { + return a; +} + +@interface Deallocator : NSObject @end +@implementation Deallocator +-(void) methodThatNobodyElseCalls1 { } +-(void) methodThatNobodyElseCalls2 { } +id retain_imp(Deallocator *self, SEL _cmd) { + _objc_flush_caches([Deallocator class]); + [self methodThatNobodyElseCalls1]; + struct objc_super sup = { self, [[Deallocator class] superclass] }; + return ((id(*)(struct objc_super *, SEL))objc_msgSendSuper)(&sup, _cmd); +} +void dealloc_imp(Deallocator *self, SEL _cmd) { + _objc_flush_caches([Deallocator class]); + [self methodThatNobodyElseCalls2]; + struct objc_super sup = { self, [[Deallocator class] superclass] }; + ((void(*)(struct objc_super *, SEL))objc_msgSendSuper)(&sup, _cmd); +} ++(void) load { + class_addMethod(self, sel_registerName("retain"), (IMP)retain_imp, ""); + class_addMethod(self, sel_registerName("dealloc"), (IMP)dealloc_imp, ""); +} +@end + +/* Code copied from objc-block-trampolines.m to test Block innards */ +typedef enum { + ReturnValueInRegisterArgumentMode, +#if STRET_SPECIAL + ReturnValueOnStackArgumentMode, +#endif + + ArgumentModeMax +} ArgumentMode; + +static ArgumentMode _argumentModeForBlock(id block) { + ArgumentMode aMode = ReturnValueInRegisterArgumentMode; +#if STRET_SPECIAL + if ( _Block_use_stret((__bridge void *)block) ) + aMode = ReturnValueOnStackArgumentMode; +#else + testassert(!_Block_use_stret((__bridge void *)block)); +#endif + + return aMode; +} +/* End copied code */ + +int main () { + // make sure the bits are in place + int (^registerReturn)() = ^(){ return 42; }; + ArgumentMode aMode; + + aMode = _argumentModeForBlock(registerReturn); + testassert(aMode == ReturnValueInRegisterArgumentMode); + + BigStruct (^stackReturn)() = ^() { BigStruct k = {{0}}; return k; }; + aMode = _argumentModeForBlock(stackReturn); +#if STRET_SPECIAL + testassert(aMode == ReturnValueOnStackArgumentMode); +#else + testassert(aMode == ReturnValueInRegisterArgumentMode); +#endif + + uintptr_t TEST_QUANTITY = is_guardmalloc() ? 1000 : 100000; + FuncPtr funcArray[TEST_QUANTITY]; + + for(uintptr_t i = 0; i < TEST_QUANTITY; i++) { + uintptr_t (^block)(void *self) = ^uintptr_t(void *self) { + testassert(i == (uintptr_t)self); + return i; + }; + block = (__bridge id)_Block_copy((__bridge void *)block); + + funcArray[i] = (FuncPtr) imp_implementationWithBlock(block); + + testassert(block((void *)i) == i); + + id blockFromIMPResult = imp_getBlock((IMP)funcArray[i]); + testassert(blockFromIMPResult == (id)block); + + _Block_release((__bridge void *)block); + } + + for(uintptr_t i = 0; i < TEST_QUANTITY; i++) { + uintptr_t result = funcArray[i]((void *)i, 0); + testassert(i == result); + } + + for(uintptr_t i = 0; i < TEST_QUANTITY; i = i + 3) { + imp_removeBlock((IMP)funcArray[i]); + id shouldBeNull = imp_getBlock((IMP)funcArray[i]); + testassert(shouldBeNull == NULL); + } + + for(uintptr_t i = 0; i < TEST_QUANTITY; i = i + 3) { + uintptr_t j = i * i; + + uintptr_t (^block)(void *) = ^uintptr_t(void *self) { + testassert(j == (uintptr_t)self); + return j; + }; + funcArray[i] = (FuncPtr) imp_implementationWithBlock(block); + + testassert(block((void *)j) == j); + testassert(funcArray[i]((void *)j, 0) == j); + } + + for(uintptr_t i = 0; i < TEST_QUANTITY; i = i + 3) { + uintptr_t j = i * i; + uintptr_t result = funcArray[i]((void *)j, 0); + testassert(j == result); + } + + int (^implBlock)(id, int); + + implBlock = ^(id self __attribute__((unused)), int a){ + return -1 * a; + }; + + PUSH_POOL { + + IMP methodImp = imp_implementationWithBlock(implBlock); + + BOOL success = class_addMethod([Foo class], @selector(boo:), methodImp, "i@:i"); + testassert(success); + + Foo *f = [Foo new]; + int (*impF)(id self, SEL _cmd, int x) = (int(*)(id, SEL, int)) [Foo instanceMethodForSelector: @selector(boo:)]; + + int x = impF(f, @selector(boo:), -42); + + testassert(x == 42); + testassert([f boo: -42] == 42); + + BigStruct a; + for (uintptr_t i = 0; i < 200; i++) { + a.datums[i] = i; + } + + // slightly more straightforward here + __block unsigned int state = 0; + BigStruct (^structBlock)(id, BigStruct) = ^BigStruct(id self __attribute__((unused)), BigStruct c) { + state++; + return c; + }; + BigStruct blockDirect = structBlock(nil, a); + testassert(!memcmp(&a, &blockDirect, sizeof(BigStruct))); + testassert(state==1); + + IMP bigStructIMP = imp_implementationWithBlock(structBlock); + + class_addMethod([Foo class], @selector(structThatIsBig:), bigStructIMP, "oh, type strings, how I hate thee. Fortunately, the runtime doesn't generally care."); + + BigStruct b; + + BigStructFuncPtr bFunc; + + b = bigfunc(a); + testassert(!memcmp(&a, &b, sizeof(BigStruct))); + b = bigfunc(a); + testassert(!memcmp(&a, &b, sizeof(BigStruct))); + + bFunc = (BigStructFuncPtr) [Foo instanceMethodForSelector: @selector(methodThatReturnsBigStruct:)]; + + b = bFunc(f, @selector(methodThatReturnsBigStruct:), a); + testassert(!memcmp(&a, &b, sizeof(BigStruct))); + + b = [f methodThatReturnsBigStruct: a]; + testassert(!memcmp(&a, &b, sizeof(BigStruct))); + + bFunc = (BigStructFuncPtr) [Foo instanceMethodForSelector: @selector(structThatIsBig:)]; + + b = bFunc(f, @selector(structThatIsBig:), a); + testassert(!memcmp(&a, &b, sizeof(BigStruct))); + testassert(state==2); + + b = [f structThatIsBig: a]; + testassert(!memcmp(&a, &b, sizeof(BigStruct))); + testassert(state==3); + + + IMP floatIMP = imp_implementationWithBlock(^float (id self __attribute__((unused)), float aFloat ) { + return aFloat; + }); + class_addMethod([Foo class], @selector(methodThatReturnsFloat:), floatIMP, "ooh.. type string unspecified again... oh noe... runtime might punish. not."); + + float e = (float)0.001; + float retF = (float)[f methodThatReturnsFloat: 37.1212f]; + testassert( ((retF - e) < 37.1212) && ((retF + e) > 37.1212) ); + + +#if !__has_feature(objc_arc) + // Make sure imp_implementationWithBlock() and imp_removeBlock() + // don't deadlock while calling Block_copy() and Block_release() + Deallocator *dead = [[Deallocator alloc] init]; + IMP deadlockImp = imp_implementationWithBlock(^{ [dead self]; }); + [dead release]; + imp_removeBlock(deadlockImp); +#endif + + } POP_POOL; + + succeed(__FILE__); +} + diff --git a/test/bool.c b/test/bool.c new file mode 100644 index 0000000..edfe1fa --- /dev/null +++ b/test/bool.c @@ -0,0 +1,45 @@ +// TEST_CFLAGS -funsigned-char +// (verify -funsigned-char doesn't change the definition of BOOL) + +#include "test.h" +#include + +#if TARGET_OS_OSX +# define RealBool 0 +#elif TARGET_OS_IOS +# if (__arm__ && !__armv7k__) || __i386__ +# define RealBool 0 +# else +# define RealBool 1 +# endif +#else +# define RealBool 1 +#endif + +#if __OBJC__ && !defined(__OBJC_BOOL_IS_BOOL) +# error no __OBJC_BOOL_IS_BOOL +#endif + +#if RealBool != OBJC_BOOL_IS_BOOL +# error wrong OBJC_BOOL_IS_BOOL +#endif + +#if RealBool == OBJC_BOOL_IS_CHAR +# error wrong OBJC_BOOL_IS_CHAR +#endif + +int main() +{ + const char *expected __unused = +#if RealBool + "B" +#else + "c" +#endif + ; +#if __OBJC__ + const char *enc = @encode(BOOL); + testassert(0 == strcmp(enc, expected)); +#endif + succeed(__FILE__); +} diff --git a/test/cacheflush.h b/test/cacheflush.h new file mode 100644 index 0000000..5553dbb --- /dev/null +++ b/test/cacheflush.h @@ -0,0 +1,7 @@ +#include +#include "test.h" + +@interface TestRoot(cat) ++(int)classMethod; +-(int)instanceMethod; +@end diff --git a/test/cacheflush.m b/test/cacheflush.m new file mode 100644 index 0000000..2bc21ae --- /dev/null +++ b/test/cacheflush.m @@ -0,0 +1,61 @@ +/* +TEST_BUILD + $C{COMPILE} $DIR/cacheflush0.m -o cacheflush0.dylib -dynamiclib + $C{COMPILE} $DIR/cacheflush2.m -x none cacheflush0.dylib -o cacheflush2.dylib -dynamiclib + $C{COMPILE} $DIR/cacheflush3.m -x none cacheflush0.dylib -o cacheflush3.dylib -dynamiclib + $C{COMPILE} $DIR/cacheflush.m -x none cacheflush0.dylib -o cacheflush.exe +END +*/ + +#include "test.h" +#include +#include + +#include "cacheflush.h" + +@interface Sub : TestRoot @end +@implementation Sub @end + + +int main() +{ + TestRoot *sup = [TestRoot new]; + Sub *sub = [Sub new]; + + // Fill method cache + testassert(1 == [TestRoot classMethod]); + testassert(1 == [sup instanceMethod]); + testassert(1 == [TestRoot classMethod]); + testassert(1 == [sup instanceMethod]); + + testassert(1 == [Sub classMethod]); + testassert(1 == [sub instanceMethod]); + testassert(1 == [Sub classMethod]); + testassert(1 == [sub instanceMethod]); + + // Dynamically load a category + dlopen("cacheflush2.dylib", 0); + + // Make sure old cache results are gone + testassert(2 == [TestRoot classMethod]); + testassert(2 == [sup instanceMethod]); + + testassert(2 == [Sub classMethod]); + testassert(2 == [sub instanceMethod]); + + // Dynamically load another category + dlopen("cacheflush3.dylib", 0); + + // Make sure old cache results are gone + testassert(3 == [TestRoot classMethod]); + testassert(3 == [sup instanceMethod]); + + testassert(3 == [Sub classMethod]); + testassert(3 == [sub instanceMethod]); + + // fixme test subclasses + + // fixme test objc_flush_caches(), class_addMethod(), class_addMethods() + + succeed(__FILE__); +} diff --git a/test/cacheflush0.m b/test/cacheflush0.m new file mode 100644 index 0000000..8536071 --- /dev/null +++ b/test/cacheflush0.m @@ -0,0 +1,7 @@ +#include "cacheflush.h" +#include "testroot.i" + +@implementation TestRoot(cat) ++(int)classMethod { return 1; } +-(int)instanceMethod { return 1; } +@end diff --git a/test/cacheflush2.m b/test/cacheflush2.m new file mode 100644 index 0000000..0337e3f --- /dev/null +++ b/test/cacheflush2.m @@ -0,0 +1,6 @@ +#include "cacheflush.h" + +@implementation TestRoot (Category2) ++(int)classMethod { return 2; } +-(int)instanceMethod { return 2; } +@end diff --git a/test/cacheflush3.m b/test/cacheflush3.m new file mode 100644 index 0000000..c180e98 --- /dev/null +++ b/test/cacheflush3.m @@ -0,0 +1,6 @@ +#include "cacheflush.h" + +@implementation TestRoot (Category3) ++(int)classMethod { return 3; } +-(int)instanceMethod { return 3; } +@end diff --git a/test/category.m b/test/category.m new file mode 100644 index 0000000..f8cd3a9 --- /dev/null +++ b/test/category.m @@ -0,0 +1,169 @@ +// TEST_CFLAGS -Wl,-no_objc_category_merging + +#include "test.h" +#include "testroot.i" +#include +#include + +static int state = 0; + +@interface Super : TestRoot @end +@implementation Super +-(void)instancemethod { fail("-instancemethod not overridden by category"); } ++(void)method { fail("+method not overridden by category"); } +@end + +@interface Super (Category) @end +@implementation Super (Category) ++(void)method { + testprintf("in [Super(Category) method]\n"); + testassert(self == [Super class]); + testassert(state == 0); + state = 1; +} +-(void)instancemethod { + testprintf("in [Super(Category) instancemethod]\n"); + testassert(object_getClass(self) == [Super class]); + testassert(state == 1); + state = 2; +} +@end + +@interface Super (PropertyCategory) +@property int i; +@property(class) int i; +@end +@implementation Super (PropertyCategory) +- (int)i { return 0; } +- (void)setI:(int)value { (void)value; } ++ (int)i { return 0; } ++ (void)setI:(int)value { (void)value; } +@end + +// rdar://5086110 memory smasher in category with class method and property +@interface Super (r5086110) +@property int property5086110; +@end +@implementation Super (r5086110) ++(void)method5086110 { + fail("method method5086110 called!"); +} +- (int)property5086110 { fail("property5086110 called!"); return 0; } +- (void)setProperty5086110:(int)value { fail("setProperty5086110 called!"); (void)value; } +@end + +// rdar://25605427 incorrect handling of class properties in 10.11 and earlier +@interface Super25605427 : TestRoot +@property(class, readonly) int i; +@end +@implementation Super25605427 ++(int)i { return 0; } +@end + +@interface Super25605427 (r25605427a) +@property(readonly) int r25605427a1; +@end +@implementation Super25605427 (r25605427a) +-(int)r25605427a1 { return 0; } ++(int)r25605427a2 { return 0; } +@end + +@interface Super25605427 (r25605427b) +@property(readonly) int r25605427b1; +@end +@implementation Super25605427 (r25605427b) +-(int)r25605427b1 { return 0; } ++(int)r25605427b2 { return 0; } +@end + +@interface Super25605427 (r25605427c) +@property(readonly) int r25605427c1; +@end +@implementation Super25605427 (r25605427c) +-(int)r25605427c1 { return 0; } ++(int)r25605427c2 { return 0; } +@end + +@interface Super25605427 (r25605427d) +@property(readonly) int r25605427d1; +@end +@implementation Super25605427 (r25605427d) +-(int)r25605427d1 { return 0; } ++(int)r25605427d2 { return 0; } +@end + + +@interface PropertyClass : Super { + int q; +} +@property(readonly) int q; +@end +@implementation PropertyClass +@synthesize q; +@end + +@interface PropertyClass (PropertyCategory) +@property int q; +@end +@implementation PropertyClass (PropertyCategory) +@dynamic q; +@end + + +int main() +{ + { + // rdar://25605427 bugs in 10.11 and earlier when metaclass + // has a property and category has metaclass additions. + // Memory smasher in buildPropertyList (caught by guard malloc) + Class cls = [Super25605427 class]; + // Incorrect attachment of instance properties from category to metacls + testassert(class_getProperty(cls, "r25605427d1")); + testassert(! class_getProperty(object_getClass(cls), "r25605427d1")); + } + + // methods introduced by category + state = 0; + [Super method]; + [[Super new] instancemethod]; + testassert(state == 2); + + // property introduced by category + objc_property_t p = class_getProperty([Super class], "i"); + testassert(p); + testassert(0 == strcmp(property_getName(p), "i")); + testassert(property_getAttributes(p)); + + objc_property_t p2 = class_getProperty(object_getClass([Super class]), "i"); + testassert(p2); + testassert(p != p2); + testassert(0 == strcmp(property_getName(p2), "i")); + testassert(property_getAttributes(p2)); + + // methods introduced by category's property + Method m; + m = class_getInstanceMethod([Super class], @selector(i)); + testassert(m); + m = class_getInstanceMethod([Super class], @selector(setI:)); + testassert(m); + + m = class_getClassMethod([Super class], @selector(i)); + testassert(m); + m = class_getClassMethod([Super class], @selector(setI:)); + testassert(m); + + // class's property shadowed by category's property + objc_property_t *plist = class_copyPropertyList([PropertyClass class], NULL); + testassert(plist); + testassert(plist[0]); + testassert(0 == strcmp(property_getName(plist[0]), "q")); + testassert(0 == strcmp(property_getAttributes(plist[0]), "Ti,D")); + testassert(plist[1]); + testassert(0 == strcmp(property_getName(plist[1]), "q")); + testassert(0 == strcmp(property_getAttributes(plist[1]), "Ti,R,Vq")); + testassert(!plist[2]); + free(plist); + + succeed(__FILE__); +} + diff --git a/test/cdtors.mm b/test/cdtors.mm new file mode 100644 index 0000000..be2a7d1 --- /dev/null +++ b/test/cdtors.mm @@ -0,0 +1,306 @@ +// TEST_CONFIG + +#if USE_FOUNDATION +#include +#define SUPERCLASS NSObject +#define FILENAME "nscdtors.mm" +#else +#define SUPERCLASS TestRoot +#define FILENAME "cdtors.mm" +#endif + +#include "test.h" + +#include +#include "objc/objc-internal.h" +#include "testroot.i" + +static unsigned ctors1 = 0; +static unsigned dtors1 = 0; +static unsigned ctors2 = 0; +static unsigned dtors2 = 0; + +class cxx1 { + unsigned & ctors; + unsigned& dtors; + + public: + cxx1() : ctors(ctors1), dtors(dtors1) { ctors++; } + + ~cxx1() { dtors++; } +}; +class cxx2 { + unsigned& ctors; + unsigned& dtors; + + public: + cxx2() : ctors(ctors2), dtors(dtors2) { ctors++; } + + ~cxx2() { dtors++; } +}; + +/* + Class hierarchy: + TestRoot + CXXBase + NoCXXSub + CXXSub + + This has two cxx-wielding classes, and a class in between without cxx. +*/ + + +@interface CXXBase : SUPERCLASS { + cxx1 baseIvar; +} +@end +@implementation CXXBase @end + +@interface NoCXXSub : CXXBase { + int nocxxIvar; +} +@end +@implementation NoCXXSub @end + +@interface CXXSub : NoCXXSub { + cxx2 subIvar; +} +@end +@implementation CXXSub @end + + +void test_single(void) +{ + // Single allocation + + ctors1 = dtors1 = ctors2 = dtors2 = 0; + testonthread(^{ + id o = [TestRoot new]; + testassert(ctors1 == 0 && dtors1 == 0 && + ctors2 == 0 && dtors2 == 0); + testassert([o class] == [TestRoot class]); + RELEASE_VAR(o); + }); + testcollect(); + testassert(ctors1 == 0 && dtors1 == 0 && + ctors2 == 0 && dtors2 == 0); + + ctors1 = dtors1 = ctors2 = dtors2 = 0; + testonthread(^{ + id o = [CXXBase new]; + testassert(ctors1 == 1 && dtors1 == 0 && + ctors2 == 0 && dtors2 == 0); + testassert([o class] == [CXXBase class]); + RELEASE_VAR(o); + }); + testcollect(); + testassert(ctors1 == 1 && dtors1 == 1 && + ctors2 == 0 && dtors2 == 0); + + ctors1 = dtors1 = ctors2 = dtors2 = 0; + testonthread(^{ + id o = [NoCXXSub new]; + testassert(ctors1 == 1 && dtors1 == 0 && + ctors2 == 0 && dtors2 == 0); + testassert([o class] == [NoCXXSub class]); + RELEASE_VAR(o); + }); + testcollect(); + testassert(ctors1 == 1 && dtors1 == 1 && + ctors2 == 0 && dtors2 == 0); + + ctors1 = dtors1 = ctors2 = dtors2 = 0; + testonthread(^{ + id o = [CXXSub new]; + testassert(ctors1 == 1 && dtors1 == 0 && + ctors2 == 1 && dtors2 == 0); + testassert([o class] == [CXXSub class]); + RELEASE_VAR(o); + }); + testcollect(); + testassert(ctors1 == 1 && dtors1 == 1 && + ctors2 == 1 && dtors2 == 1); +} + +void test_inplace(void) +{ + __unsafe_unretained volatile id o; + char o2[64]; + + id (*objc_constructInstance_fn)(Class, void*) = (id(*)(Class, void*))dlsym(RTLD_DEFAULT, "objc_constructInstance"); + void (*objc_destructInstance_fn)(id) = (void(*)(id))dlsym(RTLD_DEFAULT, "objc_destructInstance"); + + // In-place allocation + + ctors1 = dtors1 = ctors2 = dtors2 = 0; + o = objc_constructInstance_fn([TestRoot class], o2); + testassert(ctors1 == 0 && dtors1 == 0 && + ctors2 == 0 && dtors2 == 0); + testassert([o class] == [TestRoot class]); + objc_destructInstance_fn(o), o = nil; + testcollect(); + testassert(ctors1 == 0 && dtors1 == 0 && + ctors2 == 0 && dtors2 == 0); + + ctors1 = dtors1 = ctors2 = dtors2 = 0; + o = objc_constructInstance_fn([CXXBase class], o2); + testassert(ctors1 == 1 && dtors1 == 0 && + ctors2 == 0 && dtors2 == 0); + testassert([o class] == [CXXBase class]); + objc_destructInstance_fn(o), o = nil; + testcollect(); + testassert(ctors1 == 1 && dtors1 == 1 && + ctors2 == 0 && dtors2 == 0); + + ctors1 = dtors1 = ctors2 = dtors2 = 0; + o = objc_constructInstance_fn([NoCXXSub class], o2); + testassert(ctors1 == 1 && dtors1 == 0 && + ctors2 == 0 && dtors2 == 0); + testassert([o class] == [NoCXXSub class]); + objc_destructInstance_fn(o), o = nil; + testcollect(); + testassert(ctors1 == 1 && dtors1 == 1 && + ctors2 == 0 && dtors2 == 0); + + ctors1 = dtors1 = ctors2 = dtors2 = 0; + o = objc_constructInstance_fn([CXXSub class], o2); + testassert(ctors1 == 1 && dtors1 == 0 && + ctors2 == 1 && dtors2 == 0); + testassert([o class] == [CXXSub class]); + objc_destructInstance_fn(o), o = nil; + testcollect(); + testassert(ctors1 == 1 && dtors1 == 1 && + ctors2 == 1 && dtors2 == 1); +} + + +#if __has_feature(objc_arc) + +void test_batch(void) +{ + // not converted to ARC yet + return; +} + +#else + +// Like class_createInstances(), but refuses to accept zero allocations +static unsigned +reallyCreateInstances(Class cls, size_t extraBytes, id *dst, unsigned want) +{ + unsigned count; + while (0 == (count = class_createInstances(cls, extraBytes, dst, want))) { + testprintf("class_createInstances created nothing; retrying\n"); + RELEASE_VALUE([[TestRoot alloc] init]); + } + return count; +} + +void test_batch(void) +{ + id o2[100]; + unsigned int count, i; + + // Batch allocation + + for (i = 0; i < 100; i++) { + o2[i] = (id)malloc(class_getInstanceSize([TestRoot class])); + } + for (i = 0; i < 100; i++) { + free(o2[i]); + } + + ctors1 = dtors1 = ctors2 = dtors2 = 0; + count = reallyCreateInstances([TestRoot class], 0, o2, 10); + testassert(count > 0); + testassert(ctors1 == 0 && dtors1 == 0 && + ctors2 == 0 && dtors2 == 0); + for (i = 0; i < count; i++) testassert([o2[i] class] == [TestRoot class]); + for (i = 0; i < count; i++) object_dispose(o2[i]), o2[i] = nil; + testcollect(); + testassert(ctors1 == 0 && dtors1 == 0 && + ctors2 == 0 && dtors2 == 0); + + for (i = 0; i < 100; i++) { + // prime batch allocator + free(malloc(class_getInstanceSize([TestRoot class]))); + } + + ctors1 = dtors1 = ctors2 = dtors2 = 0; + count = reallyCreateInstances([CXXBase class], 0, o2, 10); + testassert(count > 0); + testassert(ctors1 == count && dtors1 == 0 && + ctors2 == 0 && dtors2 == 0); + for (i = 0; i < count; i++) testassert([o2[i] class] == [CXXBase class]); + for (i = 0; i < count; i++) object_dispose(o2[i]), o2[i] = nil; + testcollect(); + testassert(ctors1 == count && dtors1 == count && + ctors2 == 0 && dtors2 == 0); + + for (i = 0; i < 100; i++) { + // prime batch allocator + free(malloc(class_getInstanceSize([TestRoot class]))); + } + + ctors1 = dtors1 = ctors2 = dtors2 = 0; + count = reallyCreateInstances([NoCXXSub class], 0, o2, 10); + testassert(count > 0); + testassert(ctors1 == count && dtors1 == 0 && + ctors2 == 0 && dtors2 == 0); + for (i = 0; i < count; i++) testassert([o2[i] class] == [NoCXXSub class]); + for (i = 0; i < count; i++) object_dispose(o2[i]), o2[i] = nil; + testcollect(); + testassert(ctors1 == count && dtors1 == count && + ctors2 == 0 && dtors2 == 0); + + for (i = 0; i < 100; i++) { + // prime batch allocator + free(malloc(class_getInstanceSize([TestRoot class]))); + } + + ctors1 = dtors1 = ctors2 = dtors2 = 0; + count = reallyCreateInstances([CXXSub class], 0, o2, 10); + testassert(count > 0); + testassert(ctors1 == count && dtors1 == 0 && + ctors2 == count && dtors2 == 0); + for (i = 0; i < count; i++) testassert([o2[i] class] == [CXXSub class]); + for (i = 0; i < count; i++) object_dispose(o2[i]), o2[i] = nil; + testcollect(); + testassert(ctors1 == count && dtors1 == count && + ctors2 == count && dtors2 == count); +} + +// not ARC +#endif + + +int main() +{ + for (int i = 0; i < 1000; i++) { + testonthread(^{ test_single(); }); + testonthread(^{ test_inplace(); }); + testonthread(^{ test_batch(); }); + } + + testonthread(^{ test_single(); }); + testonthread(^{ test_inplace(); }); + testonthread(^{ test_batch(); }); + + leak_mark(); + + for (int i = 0; i < 1000; i++) { + testonthread(^{ test_single(); }); + testonthread(^{ test_inplace(); }); + testonthread(^{ test_batch(); }); + } + + leak_check(0); + + // fixme ctor exceptions aren't caught inside .cxx_construct ? + // Single allocation, ctors fail + // In-place allocation, ctors fail + // Batch allocation, ctors fail for every object + // Batch allocation, ctors fail for every other object + + succeed(FILENAME); +} diff --git a/test/classgetclass.m b/test/classgetclass.m new file mode 100644 index 0000000..b73243b --- /dev/null +++ b/test/classgetclass.m @@ -0,0 +1,17 @@ +// TEST_CONFIG + +#include "test.h" +#include +#include +#import + +@interface Foo:NSObject +@end +@implementation Foo +@end + +int main() +{ + testassert(gdb_class_getClass([Foo class]) == [Foo class]); + succeed(__FILE__); +} diff --git a/test/classname.m b/test/classname.m new file mode 100644 index 0000000..8d4b993 --- /dev/null +++ b/test/classname.m @@ -0,0 +1,39 @@ +// TEST_CONFIG + +#include "test.h" +#include "testroot.i" +#include +#include + +@interface Fake : TestRoot @end +@implementation Fake @end + +int main() +{ + TestRoot *obj = [TestRoot new]; + Class __unsafe_unretained * buf = (Class *)(__bridge void *)(obj); + *buf = [Fake class]; + + testassert(object_getClass(obj) == [Fake class]); + testassert(object_setClass(obj, [TestRoot class]) == [Fake class]); + testassert(object_getClass(obj) == [TestRoot class]); + testassert(object_setClass(nil, [TestRoot class]) == nil); + + testassert(malloc_size(buf) >= sizeof(id)); + bzero(buf, malloc_size(buf)); + testassert(object_setClass(obj, [TestRoot class]) == nil); + + testassert(object_getClass(obj) == [TestRoot class]); + testassert(object_getClass([TestRoot class]) == object_getClass([TestRoot class])); + testassert(object_getClass(nil) == Nil); + + testassert(0 == strcmp(object_getClassName(obj), "TestRoot")); + testassert(0 == strcmp(object_getClassName([TestRoot class]), "TestRoot")); + testassert(0 == strcmp(object_getClassName(nil), "nil")); + + testassert(0 == strcmp(class_getName([TestRoot class]), "TestRoot")); + testassert(0 == strcmp(class_getName(object_getClass([TestRoot class])), "TestRoot")); + testassert(0 == strcmp(class_getName(nil), "nil")); + + succeed(__FILE__); +} diff --git a/test/classpair.m b/test/classpair.m new file mode 100644 index 0000000..d1346f1 --- /dev/null +++ b/test/classpair.m @@ -0,0 +1,299 @@ +// TEST_CONFIG + +#include "test.h" + +#include "testroot.i" +#include +#include + +@protocol Proto +-(void) instanceMethod; ++(void) classMethod; +@optional +-(void) instanceMethod2; ++(void) classMethod2; +@end + +@protocol Proto2 +-(void) instanceMethod; ++(void) classMethod; +@optional +-(void) instanceMethod2; ++(void) classMethod_that_does_not_exist; +@end + +@protocol Proto3 +-(void) instanceMethod; ++(void) classMethod_that_does_not_exist; +@optional +-(void) instanceMethod2; ++(void) classMethod2; +@end + +static int super_initialize; + +@interface Super : TestRoot +@property int superProp; +@end +@implementation Super +@dynamic superProp; ++(void)initialize { super_initialize++; } + ++(void) classMethod { fail("+[Super classMethod] called"); } ++(void) classMethod2 { fail("+[Super classMethod2] called"); } +-(void) instanceMethod { fail("-[Super instanceMethod] called"); } +-(void) instanceMethod2 { fail("-[Super instanceMethod2] called"); } +@end + +static int state; + +static void instance_fn(id self, SEL _cmd __attribute__((unused))) +{ + testassert(!class_isMetaClass(object_getClass(self))); + state++; +} + +static void class_fn(id self, SEL _cmd __attribute__((unused))) +{ + testassert(class_isMetaClass(object_getClass(self))); + state++; +} + +static void fail_fn(id self __attribute__((unused)), SEL _cmd) +{ + fail("fail_fn '%s' called", sel_getName(_cmd)); +} + + +static void cycle(void) +{ + Class cls; + BOOL ok; + objc_property_t prop; + char namebuf[256]; + + testassert(!objc_getClass("Sub")); + testassert([Super class]); + + // Test subclass with bells and whistles + + cls = objc_allocateClassPair([Super class], "Sub", 0); + testassert(cls); + + class_addMethod(cls, @selector(instanceMethod), + (IMP)&instance_fn, "v@:"); + class_addMethod(object_getClass(cls), @selector(classMethod), + (IMP)&class_fn, "v@:"); + class_addMethod(object_getClass(cls), @selector(initialize), + (IMP)&class_fn, "v@:"); + class_addMethod(object_getClass(cls), @selector(load), + (IMP)&fail_fn, "v@:"); + + ok = class_addProtocol(cls, @protocol(Proto)); + testassert(ok); + ok = class_addProtocol(cls, @protocol(Proto)); + testassert(!ok); + + char attrname[2]; + char attrvalue[2]; + objc_property_attribute_t attrs[1]; + unsigned int attrcount = sizeof(attrs) / sizeof(attrs[0]); + + attrs[0].name = attrname; + attrs[0].value = attrvalue; + strcpy(attrname, "T"); + strcpy(attrvalue, "x"); + + strcpy(namebuf, "subProp"); + ok = class_addProperty(cls, namebuf, attrs, attrcount); + testassert(ok); + strcpy(namebuf, "subProp"); + ok = class_addProperty(cls, namebuf, attrs, attrcount); + testassert(!ok); + strcpy(attrvalue, "i"); + class_replaceProperty(cls, namebuf, attrs, attrcount); + strcpy(namebuf, "superProp"); + ok = class_addProperty(cls, namebuf, attrs, attrcount); + testassert(!ok); + bzero(namebuf, sizeof(namebuf)); + bzero(attrs, sizeof(attrs)); + bzero(attrname, sizeof(attrname)); + bzero(attrvalue, sizeof(attrvalue)); + +#ifndef __LP64__ +# define size 4 +# define align 2 +#else +#define size 8 +# define align 3 +#endif + + /* + { + int ivar; + id ivarid; + id* ivaridstar; + Block_t ivarblock; + } + */ + ok = class_addIvar(cls, "ivar", 4, 2, "i"); + testassert(ok); + ok = class_addIvar(cls, "ivarid", size, align, "@"); + testassert(ok); + ok = class_addIvar(cls, "ivaridstar", size, align, "^@"); + testassert(ok); + ok = class_addIvar(cls, "ivarblock", size, align, "@?"); + testassert(ok); + + ok = class_addIvar(cls, "ivar", 4, 2, "i"); + testassert(!ok); + ok = class_addIvar(object_getClass(cls), "classvar", 4, 2, "i"); + testassert(!ok); + + objc_registerClassPair(cls); + + // should call cls's +initialize, not super's + // Provoke +initialize using class_getMethodImplementation(class method) + // in order to test getNonMetaClass's slow case + super_initialize = 0; + state = 0; + class_getMethodImplementation(object_getClass(cls), @selector(class)); + testassert(super_initialize == 0); + testassert(state == 1); + + testassert(cls == [cls class]); + testassert(cls == objc_getClass("Sub")); + + testassert(!class_isMetaClass(cls)); + testassert(class_isMetaClass(object_getClass(cls))); + + testassert(class_getSuperclass(cls) == [Super class]); + testassert(class_getSuperclass(object_getClass(cls)) == object_getClass([Super class])); + + testassert(class_getInstanceSize(cls) >= sizeof(Class) + 4 + 3*size); + testassert(class_conformsToProtocol(cls, @protocol(Proto))); + + class_addMethod(cls, @selector(instanceMethod2), + (IMP)&instance_fn, "v@:"); + class_addMethod(object_getClass(cls), @selector(classMethod2), + (IMP)&class_fn, "v@:"); + + ok = class_addIvar(cls, "ivar2", 4, 4, "i"); + testassert(!ok); + ok = class_addIvar(object_getClass(cls), "classvar2", 4, 4, "i"); + testassert(!ok); + + ok = class_addProtocol(cls, @protocol(Proto2)); + testassert(ok); + ok = class_addProtocol(cls, @protocol(Proto2)); + testassert(!ok); + ok = class_addProtocol(cls, @protocol(Proto)); + testassert(!ok); + + attrs[0].name = attrname; + attrs[0].value = attrvalue; + strcpy(attrname, "T"); + strcpy(attrvalue, "i"); + + strcpy(namebuf, "subProp2"); + ok = class_addProperty(cls, namebuf, attrs, attrcount); + testassert(ok); + strcpy(namebuf, "subProp"); + ok = class_addProperty(cls, namebuf, attrs, attrcount); + testassert(!ok); + strcpy(namebuf, "superProp"); + ok = class_addProperty(cls, namebuf, attrs, attrcount); + testassert(!ok); + bzero(namebuf, sizeof(namebuf)); + bzero(attrs, sizeof(attrs)); + bzero(attrname, sizeof(attrname)); + bzero(attrvalue, sizeof(attrvalue)); + + prop = class_getProperty(cls, "subProp"); + testassert(prop); + testassert(0 == strcmp(property_getName(prop), "subProp")); + testassert(0 == strcmp(property_getAttributes(prop), "Ti")); + prop = class_getProperty(cls, "subProp2"); + testassert(prop); + testassert(0 == strcmp(property_getName(prop), "subProp2")); + testassert(0 == strcmp(property_getAttributes(prop), "Ti")); + + // note: adding more methods here causes a false leak check failure + state = 0; + [cls classMethod]; + [cls classMethod2]; + testassert(state == 2); + + // put instance tests on a separate thread so they + // are reliably deallocated before class destruction + testonthread(^{ + id obj = [cls new]; + state = 0; + [obj instanceMethod]; + [obj instanceMethod2]; + testassert(state == 2); + RELEASE_VAR(obj); + }); + + // Test ivar layouts of sub-subclass + Class cls2 = objc_allocateClassPair(cls, "SubSub", 0); + testassert(cls2); + + /* + { + id ivarid2; + id idarray[16]; + void* ptrarray[16]; + char a; + char b; + char c; + } + */ + ok = class_addIvar(cls2, "ivarid2", size, align, "@"); + testassert(ok); + ok = class_addIvar(cls2, "idarray", 16*sizeof(id), align, "[16@]"); + testassert(ok); + ok = class_addIvar(cls2, "ptrarray", 16*sizeof(void*), align, "[16^]"); + testassert(ok); + ok = class_addIvar(cls2, "a", 1, 0, "c"); + testassert(ok); + ok = class_addIvar(cls2, "b", 1, 0, "c"); + testassert(ok); + ok = class_addIvar(cls2, "c", 1, 0, "c"); + testassert(ok); + + objc_registerClassPair(cls2); + + // 1-byte ivars should be well packed + testassert(ivar_getOffset(class_getInstanceVariable(cls2, "b")) == + ivar_getOffset(class_getInstanceVariable(cls2, "a")) + 1); + testassert(ivar_getOffset(class_getInstanceVariable(cls2, "c")) == + ivar_getOffset(class_getInstanceVariable(cls2, "b")) + 1); + + objc_disposeClassPair(cls2); + objc_disposeClassPair(cls); + + testassert(!objc_getClass("Sub")); + + // fixme test layout setters +} + +int main() +{ + int count = 5000; + + // fixme even with this long warmup we still + // suffer false 4096-byte leaks occasionally. + for (int i = 0; i < 500; i++) { + testonthread(^{ cycle(); }); + } + + leak_mark(); + while (count--) { + testonthread(^{ cycle(); }); + } + leak_check(4096); + + succeed(__FILE__); +} + diff --git a/test/classversion.m b/test/classversion.m new file mode 100644 index 0000000..c34b98e --- /dev/null +++ b/test/classversion.m @@ -0,0 +1,19 @@ +// TEST_CONFIG + +#include "test.h" +#include "testroot.i" +#include + +int main() +{ + Class cls = [TestRoot class]; + testassert(class_getVersion(cls) == 0); + testassert(class_getVersion(object_getClass(cls)) > 5); + class_setVersion(cls, 100); + testassert(class_getVersion(cls) == 100); + + testassert(class_getVersion(Nil) == 0); + class_setVersion(Nil, 100); + + succeed(__FILE__); +} diff --git a/test/concurrentcat.m b/test/concurrentcat.m new file mode 100644 index 0000000..f4bb145 --- /dev/null +++ b/test/concurrentcat.m @@ -0,0 +1,127 @@ +/* +TEST_BUILD + $C{COMPILE} $DIR/concurrentcat.m -o concurrentcat.exe -framework Foundation + + $C{COMPILE} -bundle -bundle_loader concurrentcat.exe -framework Foundation $DIR/concurrentcat_category.m -DTN=cc1 -o cc1.bundle + $C{COMPILE} -bundle -bundle_loader concurrentcat.exe -framework Foundation $DIR/concurrentcat_category.m -DTN=cc2 -o cc2.bundle + $C{COMPILE} -bundle -bundle_loader concurrentcat.exe -framework Foundation $DIR/concurrentcat_category.m -DTN=cc3 -o cc3.bundle + $C{COMPILE} -bundle -bundle_loader concurrentcat.exe -framework Foundation $DIR/concurrentcat_category.m -DTN=cc4 -o cc4.bundle + $C{COMPILE} -bundle -bundle_loader concurrentcat.exe -framework Foundation $DIR/concurrentcat_category.m -DTN=cc5 -o cc5.bundle + $C{COMPILE} -bundle -bundle_loader concurrentcat.exe -framework Foundation $DIR/concurrentcat_category.m -DTN=cc6 -o cc6.bundle + $C{COMPILE} -bundle -bundle_loader concurrentcat.exe -framework Foundation $DIR/concurrentcat_category.m -DTN=cc7 -o cc7.bundle + $C{COMPILE} -bundle -bundle_loader concurrentcat.exe -framework Foundation $DIR/concurrentcat_category.m -DTN=cc8 -o cc8.bundle + $C{COMPILE} -bundle -bundle_loader concurrentcat.exe -framework Foundation $DIR/concurrentcat_category.m -DTN=cc9 -o cc9.bundle + $C{COMPILE} -bundle -bundle_loader concurrentcat.exe -framework Foundation $DIR/concurrentcat_category.m -DTN=cc10 -o cc10.bundle + $C{COMPILE} -bundle -bundle_loader concurrentcat.exe -framework Foundation $DIR/concurrentcat_category.m -DTN=cc11 -o cc11.bundle + $C{COMPILE} -bundle -bundle_loader concurrentcat.exe -framework Foundation $DIR/concurrentcat_category.m -DTN=cc12 -o cc12.bundle + $C{COMPILE} -bundle -bundle_loader concurrentcat.exe -framework Foundation $DIR/concurrentcat_category.m -DTN=cc13 -o cc13.bundle + $C{COMPILE} -bundle -bundle_loader concurrentcat.exe -framework Foundation $DIR/concurrentcat_category.m -DTN=cc14 -o cc14.bundle + $C{COMPILE} -bundle -bundle_loader concurrentcat.exe -framework Foundation $DIR/concurrentcat_category.m -DTN=cc15 -o cc15.bundle +END +*/ + +#include "test.h" +#include +#include +#include +#include +#include +#include + +@interface TargetClass : NSObject +@end + +@interface TargetClass(LoadedMethods) +- (void) m0; +- (void) m1; +- (void) m2; +- (void) m3; +- (void) m4; +- (void) m5; +- (void) m6; +- (void) m7; +- (void) m8; +- (void) m9; +- (void) m10; +- (void) m11; +- (void) m12; +- (void) m13; +- (void) m14; +- (void) m15; +@end + +@implementation TargetClass +- (void) m0 { fail("shoulda been loaded!"); } +- (void) m1 { fail("shoulda been loaded!"); } +- (void) m2 { fail("shoulda been loaded!"); } +- (void) m3 { fail("shoulda been loaded!"); } +- (void) m4 { fail("shoulda been loaded!"); } +- (void) m5 { fail("shoulda been loaded!"); } +- (void) m6 { fail("shoulda been loaded!"); } +@end + +void *threadFun(void *aTargetClassName) { + const char *className = (const char *)aTargetClassName; + + PUSH_POOL { + + Class targetSubclass = objc_getClass(className); + testassert(targetSubclass); + + id target = [targetSubclass new]; + testassert(target); + + while(1) { + [target m0]; + RETAIN(target); + [target addObserver: target forKeyPath: @"m3" options: 0 context: NULL]; + [target addObserver: target forKeyPath: @"m4" options: 0 context: NULL]; + [target m1]; + RELEASE_VALUE(target); + [target m2]; + AUTORELEASE(target); + [target m3]; + RETAIN(target); + [target removeObserver: target forKeyPath: @"m4"]; + [target addObserver: target forKeyPath: @"m5" options: 0 context: NULL]; + [target m4]; + RETAIN(target); + [target m5]; + AUTORELEASE(target); + [target m6]; + [target m7]; + [target m8]; + [target m9]; + [target m10]; + [target m11]; + [target m12]; + [target m13]; + [target m14]; + [target m15]; + [target removeObserver: target forKeyPath: @"m3"]; + [target removeObserver: target forKeyPath: @"m5"]; + } + } POP_POOL; + return NULL; +} + +int main() +{ + int i; + + void *dylib; + + for(i=1; i<16; i++) { + pthread_t t; + char dlName[100]; + sprintf(dlName, "cc%d.bundle", i); + dylib = dlopen(dlName, RTLD_LAZY); + char className[100]; + sprintf(className, "cc%d", i); + pthread_create(&t, NULL, threadFun, strdup(className)); + testassert(dylib); + } + sleep(1); + + succeed(__FILE__); +} diff --git a/test/concurrentcat_category.m b/test/concurrentcat_category.m new file mode 100644 index 0000000..c59f663 --- /dev/null +++ b/test/concurrentcat_category.m @@ -0,0 +1,70 @@ +#include +#include +#import + +@interface TargetClass : NSObject +@end + +@interface TargetClass(LoadedMethods) +- (void) m0; +- (void) m1; +- (void) m2; +- (void) m3; +- (void) m4; +- (void) m5; +- (void) m6; +- (void) m7; +- (void) m8; +- (void) m9; +- (void) m10; +- (void) m11; +- (void) m12; +- (void) m13; +- (void) m14; +- (void) m15; +@end + +@interface TN:TargetClass +@end + +@implementation TN +- (void) m1 { [super m1]; } +- (void) m3 { [self m1]; } + +- (void) m2 +{ + [self willChangeValueForKey: @"m4"]; + [self didChangeValueForKey: @"m4"]; +} + +- (void)observeValueForKeyPath:(NSString *) keyPath + ofObject:(id)object + change:(NSDictionary *)change + context:(void *)context +{ + // suppress warning + keyPath = nil; + object = nil; + change = nil; + context = NULL; +} +@end + +@implementation TargetClass(LoadedMethods) +- (void) m0 { ; } +- (void) m1 { ; } +- (void) m2 { ; } +- (void) m3 { ; } +- (void) m4 { ; } +- (void) m5 { ; } +- (void) m6 { ; } +- (void) m7 { ; } +- (void) m8 { ; } +- (void) m9 { ; } +- (void) m10 { ; } +- (void) m11 { ; } +- (void) m12 { ; } +- (void) m13 { ; } +- (void) m14 { ; } +- (void) m15 { ; } +@end diff --git a/test/copyIvarList.m b/test/copyIvarList.m new file mode 100644 index 0000000..b924108 --- /dev/null +++ b/test/copyIvarList.m @@ -0,0 +1,115 @@ +// TEST_CONFIG + +#include "test.h" +#include +#include +#include + +OBJC_ROOT_CLASS +@interface SuperIvars { + id isa; + int ivar1; + int ivar2; +} @end +@implementation SuperIvars @end + +@interface SubIvars : SuperIvars { + int ivar3; + int ivar4; +} @end +@implementation SubIvars @end + +OBJC_ROOT_CLASS +@interface FourIvars { + int ivar1; + int ivar2; + int ivar3; + int ivar4; +} @end +@implementation FourIvars @end + +OBJC_ROOT_CLASS +@interface NoIvars { } @end +@implementation NoIvars @end + +static int isNamed(Ivar iv, const char *name) +{ + return (0 == strcmp(name, ivar_getName(iv))); +} + +int main() +{ + Ivar *ivars; + unsigned int count; + Class cls; + + cls = objc_getClass("SubIvars"); + testassert(cls); + + count = 100; + ivars = class_copyIvarList(cls, &count); + testassert(ivars); + testassert(count == 2); + testassert(isNamed(ivars[0], "ivar3")); + testassert(isNamed(ivars[1], "ivar4")); + // ivars[] should be null-terminated + testassert(ivars[2] == NULL); + free(ivars); + + cls = objc_getClass("SuperIvars"); + testassert(cls); + + count = 100; + ivars = class_copyIvarList(cls, &count); + testassert(ivars); + testassert(count == 3); + testassert(isNamed(ivars[0], "isa")); + testassert(isNamed(ivars[1], "ivar1")); + testassert(isNamed(ivars[2], "ivar2")); + // ivars[] should be null-terminated + testassert(ivars[3] == NULL); + free(ivars); + + // Check null-termination - this ivar list block would be 16 bytes + // if it weren't for the terminator + cls = objc_getClass("FourIvars"); + testassert(cls); + + count = 100; + ivars = class_copyIvarList(cls, &count); + testassert(ivars); + testassert(count == 4); + testassert(malloc_size(ivars) >= 5 * sizeof(Ivar)); + testassert(ivars[3] != NULL); + testassert(ivars[4] == NULL); + free(ivars); + + // Check NULL count parameter + ivars = class_copyIvarList(cls, NULL); + testassert(ivars); + testassert(ivars[4] == NULL); + testassert(ivars[3] != NULL); + free(ivars); + + // Check NULL class parameter + count = 100; + ivars = class_copyIvarList(NULL, &count); + testassert(!ivars); + testassert(count == 0); + + // Check NULL class and count + ivars = class_copyIvarList(NULL, NULL); + testassert(!ivars); + + // Check class with no ivars + cls = objc_getClass("NoIvars"); + testassert(cls); + + count = 100; + ivars = class_copyIvarList(cls, &count); + testassert(!ivars); + testassert(count == 0); + + succeed(__FILE__); + return 0; +} diff --git a/test/copyMethodList.m b/test/copyMethodList.m new file mode 100644 index 0000000..18a0d6c --- /dev/null +++ b/test/copyMethodList.m @@ -0,0 +1,155 @@ +// TEST_CFLAGS -Wl,-no_objc_category_merging + +#include "test.h" +#include "testroot.i" +#include +#include + +@interface SuperMethods : TestRoot { } @end +@implementation SuperMethods ++(BOOL)SuperMethodClass { return NO; } ++(BOOL)SuperMethodClass2 { return NO; } +-(BOOL)SuperMethodInstance { return NO; } +-(BOOL)SuperMethodInstance2 { return NO; } +@end + +@interface SubMethods : SuperMethods { } @end +@implementation SubMethods ++(BOOL)SubMethodClass { return NO; } ++(BOOL)SubMethodClass2 { return NO; } +-(BOOL)SubMethodInstance { return NO; } +-(BOOL)SubMethodInstance2 { return NO; } +@end + +@interface SuperMethods (Category) @end +@implementation SuperMethods (Category) ++(BOOL)SuperMethodClass { return YES; } ++(BOOL)SuperMethodClass2 { return YES; } +-(BOOL)SuperMethodInstance { return YES; } +-(BOOL)SuperMethodInstance2 { return YES; } +@end + +@interface SubMethods (Category) @end +@implementation SubMethods (Category) ++(BOOL)SubMethodClass { return YES; } ++(BOOL)SubMethodClass2 { return YES; } +-(BOOL)SubMethodInstance { return YES; } +-(BOOL)SubMethodInstance2 { return YES; } +@end + + +@interface FourMethods : TestRoot @end +@implementation FourMethods +-(void)one { } +-(void)two { } +-(void)three { } +-(void)four { } +@end + +@interface NoMethods : TestRoot @end +@implementation NoMethods @end + +static void checkReplacement(Method *list, const char *name) +{ + Method first = NULL, second = NULL; + SEL sel = sel_registerName(name); + int i; + + testassert(list); + + // Find the methods. There should be two. + for (i = 0; list[i]; i++) { + if (method_getName(list[i]) == sel) { + if (!first) first = list[i]; + else if (!second) second = list[i]; + else testassert(0); + } + } + + // Call the methods. The first should be the category (returns YES). + BOOL isCat; + isCat = ((BOOL(*)(id, Method))method_invoke)(NULL, first); + testassert(isCat); + isCat = ((BOOL(*)(id, Method))method_invoke)(NULL, second); + testassert(! isCat); +} + +int main() +{ + // Class SubMethods has not yet been touched, so runtime must attach + // the lazy categories + Method *methods; + unsigned int count; + Class cls; + + cls = objc_getClass("SubMethods"); + testassert(cls); + + testprintf("calling class_copyMethodList(SubMethods) (should be unmethodized)\n"); + + count = 100; + methods = class_copyMethodList(cls, &count); + testassert(methods); + testassert(count == 4); + // methods[] should be null-terminated + testassert(methods[4] == NULL); + // Class and category methods may be mixed in the method list thanks + // to linker / shared cache sorting, but a category's replacement should + // always precede the class's implementation. + checkReplacement(methods, "SubMethodInstance"); + checkReplacement(methods, "SubMethodInstance2"); + free(methods); + + testprintf("calling class_copyMethodList(SubMethods(meta)) (should be unmethodized)\n"); + + count = 100; + methods = class_copyMethodList(object_getClass(cls), &count); + testassert(methods); + testassert(count == 4); + // methods[] should be null-terminated + testassert(methods[4] == NULL); + // Class and category methods may be mixed in the method list thanks + // to linker / shared cache sorting, but a category's replacement should + // always precede the class's implementation. + checkReplacement(methods, "SubMethodClass"); + checkReplacement(methods, "SubMethodClass2"); + free(methods); + + // Check null-termination - this method list block would be 16 bytes + // if it weren't for the terminator + count = 100; + cls = objc_getClass("FourMethods"); + methods = class_copyMethodList(cls, &count); + testassert(methods); + testassert(count == 4); + testassert(malloc_size(methods) >= (4+1) * sizeof(Method)); + testassert(methods[3] != NULL); + testassert(methods[4] == NULL); + free(methods); + + // Check NULL count parameter + methods = class_copyMethodList(cls, NULL); + testassert(methods); + testassert(methods[4] == NULL); + testassert(methods[3] != NULL); + free(methods); + + // Check NULL class parameter + count = 100; + methods = class_copyMethodList(NULL, &count); + testassert(!methods); + testassert(count == 0); + + // Check NULL class and count + methods = class_copyMethodList(NULL, NULL); + testassert(!methods); + + // Check class with no methods + count = 100; + cls = objc_getClass("NoMethods"); + methods = class_copyMethodList(cls, &count); + testassert(!methods); + testassert(count == 0); + + succeed(__FILE__); +} diff --git a/test/copyPropertyList.m b/test/copyPropertyList.m new file mode 100644 index 0000000..e3b2632 --- /dev/null +++ b/test/copyPropertyList.m @@ -0,0 +1,167 @@ +// TEST_CONFIG + +#include "test.h" +#include +#include +#include + +OBJC_ROOT_CLASS +@interface SuperProps { id isa; int prop1; int prop2; } +@property int prop1; +@property int prop2; +@end +@implementation SuperProps +@synthesize prop1; +@synthesize prop2; +@end + +@interface SubProps : SuperProps { int prop3; int prop4; } +@property int prop3; +@property int prop4; +@end +@implementation SubProps +@synthesize prop3; +@synthesize prop4; +@end + +OBJC_ROOT_CLASS +@interface FourProps { int prop1; int prop2; int prop3; int prop4; } +@property int prop1; +@property int prop2; +@property int prop3; +@property int prop4; +@end +@implementation FourProps +@synthesize prop1; +@synthesize prop2; +@synthesize prop3; +@synthesize prop4; +@end + +OBJC_ROOT_CLASS +@interface NoProps @end +@implementation NoProps @end + +OBJC_ROOT_CLASS +@interface ClassProps +@property(readonly,class) int prop1; +@property(readonly,class) int prop2; +@property(readonly) int prop2; +@property(readonly) int prop3; +@end +@implementation ClassProps ++(int)prop1 { return 0; } ++(int)prop2 { return 0; } +-(int)prop2 { return 0; } +-(int)prop3 { return 0; } +@end + +static int isNamed(objc_property_t p, const char *name) +{ + return (0 == strcmp(name, property_getName(p))); +} + +int main() +{ + objc_property_t *props; + unsigned int count; + Class cls; + + cls = objc_getClass("SubProps"); + testassert(cls); + + count = 100; + props = class_copyPropertyList(cls, &count); + testassert(props); + testassert(count == 2); + testassert((isNamed(props[0], "prop3") && isNamed(props[1], "prop4")) || + (isNamed(props[1], "prop3") && isNamed(props[0], "prop4"))); + // props[] should be null-terminated + testassert(props[2] == NULL); + free(props); + + cls = objc_getClass("SuperProps"); + testassert(cls); + + count = 100; + props = class_copyPropertyList(cls, &count); + testassert(props); + testassert(count == 2); + testassert((isNamed(props[0], "prop1") && isNamed(props[1], "prop2")) || + (isNamed(props[1], "prop1") && isNamed(props[0], "prop2"))); + // props[] should be null-terminated + testassert(props[2] == NULL); + free(props); + + // Check null-termination - this property list block would be 16 bytes + // if it weren't for the terminator + cls = objc_getClass("FourProps"); + testassert(cls); + + count = 100; + props = class_copyPropertyList(cls, &count); + testassert(props); + testassert(count == 4); + testassert(malloc_size(props) >= 5 * sizeof(objc_property_t)); + testassert(props[3] != NULL); + testassert(props[4] == NULL); + free(props); + + // Check NULL count parameter + props = class_copyPropertyList(cls, NULL); + testassert(props); + testassert(props[4] == NULL); + testassert(props[3] != NULL); + free(props); + + // Check NULL class parameter + count = 100; + props = class_copyPropertyList(NULL, &count); + testassert(!props); + testassert(count == 0); + + // Check NULL class and count + props = class_copyPropertyList(NULL, NULL); + testassert(!props); + + // Check class with no properties + cls = objc_getClass("NoProps"); + testassert(cls); + + count = 100; + props = class_copyPropertyList(cls, &count); + testassert(!props); + testassert(count == 0); + + // Check class properties + + cls = objc_getClass("ClassProps"); + testassert(cls); + + count = 100; + props = class_copyPropertyList(cls, &count); + testassert(props); + testassert(count == 2); + testassert((isNamed(props[0], "prop2") && isNamed(props[1], "prop3")) || + (isNamed(props[1], "prop2") && isNamed(props[0], "prop3"))); + // props[] should be null-terminated + testassert(props[2] == NULL); + free(props); + + cls = object_getClass(objc_getClass("ClassProps")); + testassert(cls); + + count = 100; + props = class_copyPropertyList(cls, &count); + testassert(props); + testassert(count == 2); + testassert((isNamed(props[0], "prop1") && isNamed(props[1], "prop2")) || + (isNamed(props[1], "prop1") && isNamed(props[0], "prop2"))); + // props[] should be null-terminated + testassert(props[2] == NULL); + free(props); + + + succeed(__FILE__); + return 0; +} diff --git a/test/createInstance.m b/test/createInstance.m new file mode 100644 index 0000000..c1c156c --- /dev/null +++ b/test/createInstance.m @@ -0,0 +1,66 @@ +// TEST_CONFIG + +#import +#import +#include "test.h" +#include "testroot.i" + +@interface Super : TestRoot @end +@implementation Super @end + +@interface Sub : Super { int array[128]; } @end +@implementation Sub @end + +#if __has_feature(objc_arc) +#define object_dispose(x) do {} while (0) +#endif + +int main() +{ + Super *s; + + s = class_createInstance([Super class], 0); + testassert(s); + testassert(object_getClass(s) == [Super class]); + testassert(malloc_size((__bridge const void *)s) >= class_getInstanceSize([Super class])); + + object_dispose(s); + + s = class_createInstance([Sub class], 0); + testassert(s); + testassert(object_getClass(s) == [Sub class]); + testassert(malloc_size((__bridge const void *)s) >= class_getInstanceSize([Sub class])); + + object_dispose(s); + + s = class_createInstance([Super class], 100); + testassert(s); + testassert(object_getClass(s) == [Super class]); + testassert(malloc_size((__bridge const void *)s) >= class_getInstanceSize([Super class]) + 100); + + object_dispose(s); + + s = class_createInstance([Sub class], 100); + testassert(s); + testassert(object_getClass(s) == [Sub class]); + testassert(malloc_size((__bridge const void *)s) >= class_getInstanceSize([Sub class]) + 100); + + object_dispose(s); + + s = class_createInstance(Nil, 0); + testassert(!s); + + testassert(TestRootAlloc == 0); + +#if __has_feature(objc_arc) + // ARC version didn't use object_dispose() + // and should have called -dealloc on 4 objects + testassert(TestRootDealloc == 4); +#else + // MRC version used object_dispose() + // which doesn't call -dealloc + testassert(TestRootDealloc == 0); +#endif + + succeed(__FILE__); +} diff --git a/test/customrr-cat1.m b/test/customrr-cat1.m new file mode 100644 index 0000000..823238d --- /dev/null +++ b/test/customrr-cat1.m @@ -0,0 +1,7 @@ +@interface InheritingSubCat @end + +@interface InheritingSubCat (NonClobberingCategory) @end + +@implementation InheritingSubCat (NonClobberingCategory) +-(id) unrelatedMethod { return self; } +@end diff --git a/test/customrr-cat2.m b/test/customrr-cat2.m new file mode 100644 index 0000000..55c96df --- /dev/null +++ b/test/customrr-cat2.m @@ -0,0 +1,7 @@ +@interface InheritingSubCat @end + +@interface InheritingSubCat (ClobberingCategory) @end + +@implementation InheritingSubCat (ClobberingCategory) +-(int) retainCount { return 1; } +@end diff --git a/test/customrr-nsobject-awz.m b/test/customrr-nsobject-awz.m new file mode 100644 index 0000000..bda0316 --- /dev/null +++ b/test/customrr-nsobject-awz.m @@ -0,0 +1,16 @@ +/* + +TEST_CONFIG MEM=mrc +TEST_ENV OBJC_PRINT_CUSTOM_RR=YES OBJC_PRINT_CUSTOM_AWZ=YES + +TEST_BUILD + $C{COMPILE} $DIR/customrr-nsobject.m -o customrr-nsobject-awz.exe -DSWIZZLE_AWZ=1 +END + +TEST_RUN_OUTPUT +objc\[\d+\]: CUSTOM AWZ: NSObject \(meta\) +OK: customrr-nsobject-awz.exe +END + +*/ + diff --git a/test/customrr-nsobject-none.m b/test/customrr-nsobject-none.m new file mode 100644 index 0000000..60bb6ce --- /dev/null +++ b/test/customrr-nsobject-none.m @@ -0,0 +1,15 @@ +/* + +TEST_CONFIG MEM=mrc +TEST_ENV OBJC_PRINT_CUSTOM_RR=YES OBJC_PRINT_CUSTOM_AWZ=YES + +TEST_BUILD + $C{COMPILE} $DIR/customrr-nsobject.m -o customrr-nsobject-none.exe +END + +TEST_RUN_OUTPUT +OK: customrr-nsobject-none.exe +END + +*/ + diff --git a/test/customrr-nsobject-rr.m b/test/customrr-nsobject-rr.m new file mode 100644 index 0000000..94418f8 --- /dev/null +++ b/test/customrr-nsobject-rr.m @@ -0,0 +1,16 @@ +/* + +TEST_CONFIG MEM=mrc +TEST_ENV OBJC_PRINT_CUSTOM_RR=YES OBJC_PRINT_CUSTOM_AWZ=YES + +TEST_BUILD + $C{COMPILE} $DIR/customrr-nsobject.m -o customrr-nsobject-rr.exe -DSWIZZLE_RELEASE=1 +END + +TEST_RUN_OUTPUT +objc\[\d+\]: CUSTOM RR: NSObject +OK: customrr-nsobject-rr.exe +END + +*/ + diff --git a/test/customrr-nsobject-rrawz.m b/test/customrr-nsobject-rrawz.m new file mode 100644 index 0000000..413d973 --- /dev/null +++ b/test/customrr-nsobject-rrawz.m @@ -0,0 +1,17 @@ +/* + +TEST_CONFIG MEM=mrc +TEST_ENV OBJC_PRINT_CUSTOM_RR=YES OBJC_PRINT_CUSTOM_AWZ=YES + +TEST_BUILD + $C{COMPILE} $DIR/customrr-nsobject.m -o customrr-nsobject-rrawz.exe -DSWIZZLE_RELEASE=1 -DSWIZZLE_AWZ=1 +END + +TEST_RUN_OUTPUT +objc\[\d+\]: CUSTOM AWZ: NSObject \(meta\) +objc\[\d+\]: CUSTOM RR: NSObject +OK: customrr-nsobject-rrawz.exe +END + +*/ + diff --git a/test/customrr-nsobject.m b/test/customrr-nsobject.m new file mode 100644 index 0000000..57e5b5a --- /dev/null +++ b/test/customrr-nsobject.m @@ -0,0 +1,177 @@ +// This file is used in the customrr-nsobject-*.m tests + +#include "test.h" +#include + +#if __has_feature(ptrauth_calls) +typedef IMP __ptrauth_objc_method_list_imp MethodListIMP; +#else +typedef IMP MethodListIMP; +#endif + +static int Retains; +static int Releases; +static int Autoreleases; +static int PlusInitializes; +static int Allocs; +static int AllocWithZones; +static int Inits; + +id (*RealRetain)(id self, SEL _cmd); +void (*RealRelease)(id self, SEL _cmd); +id (*RealAutorelease)(id self, SEL _cmd); +id (*RealAlloc)(id self, SEL _cmd); +id (*RealAllocWithZone)(id self, SEL _cmd, void *zone); + +id HackRetain(id self, SEL _cmd) { Retains++; return RealRetain(self, _cmd); } +void HackRelease(id self, SEL _cmd) { Releases++; return RealRelease(self, _cmd); } +id HackAutorelease(id self, SEL _cmd) { Autoreleases++; return RealAutorelease(self, _cmd); } + +id HackAlloc(Class self, SEL _cmd) { Allocs++; return RealAlloc(self, _cmd); } +id HackAllocWithZone(Class self, SEL _cmd, void *zone) { AllocWithZones++; return RealAllocWithZone(self, _cmd, zone); } + +void HackPlusInitialize(id self __unused, SEL _cmd __unused) { PlusInitializes++; } + +id HackInit(id self, SEL _cmd __unused) { Inits++; return self; } + + +int main(int argc __unused, char **argv) +{ + Class cls = objc_getClass("NSObject"); + Method meth; + + meth = class_getClassMethod(cls, @selector(initialize)); + method_setImplementation(meth, (IMP)HackPlusInitialize); + + // We either swizzle the method normally (testing that it properly + // disables optimizations), or we hack the implementation into place + // behind objc's back (so we can see whether it got called with the + // optimizations still enabled). + + meth = class_getClassMethod(cls, @selector(allocWithZone:)); + RealAllocWithZone = (typeof(RealAllocWithZone))method_getImplementation(meth); +#if SWIZZLE_AWZ + method_setImplementation(meth, (IMP)HackAllocWithZone); +#else + ((MethodListIMP *)meth)[2] = (IMP)HackAllocWithZone; +#endif + + meth = class_getInstanceMethod(cls, @selector(release)); + RealRelease = (typeof(RealRelease))method_getImplementation(meth); +#if SWIZZLE_RELEASE + method_setImplementation(meth, (IMP)HackRelease); +#else + ((MethodListIMP *)meth)[2] = (IMP)HackRelease; +#endif + + // These other methods get hacked for counting purposes only + + meth = class_getInstanceMethod(cls, @selector(retain)); + RealRetain = (typeof(RealRetain))method_getImplementation(meth); + ((MethodListIMP *)meth)[2] = (IMP)HackRetain; + + meth = class_getInstanceMethod(cls, @selector(autorelease)); + RealAutorelease = (typeof(RealAutorelease))method_getImplementation(meth); + ((MethodListIMP *)meth)[2] = (IMP)HackAutorelease; + + meth = class_getClassMethod(cls, @selector(alloc)); + RealAlloc = (typeof(RealAlloc))method_getImplementation(meth); + ((MethodListIMP *)meth)[2] = (IMP)HackAlloc; + + meth = class_getInstanceMethod(cls, @selector(init)); + ((MethodListIMP *)meth)[2] = (IMP)HackInit; + + // Verify that the swizzles occurred before +initialize by provoking it now + testassert(PlusInitializes == 0); + [NSObject self]; + testassert(PlusInitializes == 1); + + id obj; + id result; + + Allocs = 0; + AllocWithZones = 0; + Inits = 0; + obj = objc_alloc(cls); +#if SWIZZLE_AWZ + testprintf("swizzled AWZ should be called\n"); + testassert(Allocs == 1); + testassert(AllocWithZones == 1); + testassert(Inits == 0); +#else + testprintf("unswizzled AWZ should be bypassed\n"); + testassert(Allocs == 0); + testassert(AllocWithZones == 0); + testassert(Inits == 0); +#endif + testassert([obj isKindOfClass:[NSObject class]]); + + Allocs = 0; + AllocWithZones = 0; + Inits = 0; + obj = [NSObject alloc]; +#if SWIZZLE_AWZ + testprintf("swizzled AWZ should be called\n"); + testassert(Allocs == 1); + testassert(AllocWithZones == 1); + testassert(Inits == 0); +#else + testprintf("unswizzled AWZ should be bypassed\n"); + testassert(Allocs == 1); + testassert(AllocWithZones == 0); + testassert(Inits == 0); +#endif + testassert([obj isKindOfClass:[NSObject class]]); + + Allocs = 0; + AllocWithZones = 0; + Inits = 0; + obj = objc_alloc_init(cls); +#if SWIZZLE_AWZ + testprintf("swizzled AWZ should be called\n"); + testassert(Allocs == 1); + testassert(AllocWithZones == 1); + testassert(Inits == 1); +#else + testprintf("unswizzled AWZ should be bypassed\n"); + testassert(Allocs == 0); + testassert(AllocWithZones == 0); + testassert(Inits == 1); // swizzled init is still called +#endif + testassert([obj isKindOfClass:[NSObject class]]); + + Retains = 0; + result = objc_retain(obj); +#if SWIZZLE_RELEASE + testprintf("swizzled release should force retain\n"); + testassert(Retains == 1); +#else + testprintf("unswizzled release should bypass retain\n"); + testassert(Retains == 0); +#endif + testassert(result == obj); + + Releases = 0; + Autoreleases = 0; + PUSH_POOL { + result = objc_autorelease(obj); +#if SWIZZLE_RELEASE + testprintf("swizzled release should force autorelease\n"); + testassert(Autoreleases == 1); +#else + testprintf("unswizzled release should bypass autorelease\n"); + testassert(Autoreleases == 0); +#endif + testassert(result == obj); + } POP_POOL + +#if SWIZZLE_RELEASE + testprintf("swizzled release should be called\n"); + testassert(Releases == 1); +#else + testprintf("unswizzled release should be bypassed\n"); + testassert(Releases == 0); +#endif + + succeed(basename(argv[0])); +} diff --git a/test/customrr.m b/test/customrr.m new file mode 100644 index 0000000..8ad8a47 --- /dev/null +++ b/test/customrr.m @@ -0,0 +1,889 @@ +// These options must match customrr2.m +// TEST_CONFIG MEM=mrc +/* +TEST_BUILD + $C{COMPILE} $DIR/customrr.m -fvisibility=default -o customrr.exe + $C{COMPILE} -bundle -bundle_loader customrr.exe $DIR/customrr-cat1.m -o customrr-cat1.bundle + $C{COMPILE} -bundle -bundle_loader customrr.exe $DIR/customrr-cat2.m -o customrr-cat2.bundle +END +*/ + + +#include "test.h" +#include +#include + +static int Retains; +static int Releases; +static int Autoreleases; +static int RetainCounts; +static int PlusRetains; +static int PlusReleases; +static int PlusAutoreleases; +static int PlusRetainCounts; +static int Allocs; +static int AllocWithZones; + +static int SubRetains; +static int SubReleases; +static int SubAutoreleases; +static int SubRetainCounts; +static int SubPlusRetains; +static int SubPlusReleases; +static int SubPlusAutoreleases; +static int SubPlusRetainCounts; +static int SubAllocs; +static int SubAllocWithZones; + +static int Imps; + +static id imp_fn(id self, SEL _cmd __unused, ...) +{ + Imps++; + return self; +} + +static void zero(void) { + Retains = 0; + Releases = 0; + Autoreleases = 0; + RetainCounts = 0; + PlusRetains = 0; + PlusReleases = 0; + PlusAutoreleases = 0; + PlusRetainCounts = 0; + Allocs = 0; + AllocWithZones = 0; + + SubRetains = 0; + SubReleases = 0; + SubAutoreleases = 0; + SubRetainCounts = 0; + SubPlusRetains = 0; + SubPlusReleases = 0; + SubPlusAutoreleases = 0; + SubPlusRetainCounts = 0; + SubAllocs = 0; + SubAllocWithZones = 0; + + Imps = 0; +} + + +id HackRetain(id self, SEL _cmd __unused) { Retains++; return self; } +void HackRelease(id self __unused, SEL _cmd __unused) { Releases++; } +id HackAutorelease(id self, SEL _cmd __unused) { Autoreleases++; return self; } +NSUInteger HackRetainCount(id self __unused, SEL _cmd __unused) { RetainCounts++; return 1; } +id HackPlusRetain(id self, SEL _cmd __unused) { PlusRetains++; return self; } +void HackPlusRelease(id self __unused, SEL _cmd __unused) { PlusReleases++; } +id HackPlusAutorelease(id self, SEL _cmd __unused) { PlusAutoreleases++; return self; } +NSUInteger HackPlusRetainCount(id self __unused, SEL _cmd __unused) { PlusRetainCounts++; return 1; } +id HackAlloc(Class self, SEL _cmd __unused) { Allocs++; return class_createInstance(self, 0); } +id HackAllocWithZone(Class self, SEL _cmd __unused) { AllocWithZones++; return class_createInstance(self, 0); } + + +@interface OverridingSub : NSObject @end +@implementation OverridingSub + +-(id) retain { SubRetains++; return self; } ++(id) retain { SubPlusRetains++; return self; } +-(oneway void) release { SubReleases++; } ++(oneway void) release { SubPlusReleases++; } +-(id) autorelease { SubAutoreleases++; return self; } ++(id) autorelease { SubPlusAutoreleases++; return self; } +-(NSUInteger) retainCount { SubRetainCounts++; return 1; } ++(NSUInteger) retainCount { SubPlusRetainCounts++; return 1; } + +@end + +@interface OverridingASub : NSObject @end +@implementation OverridingASub ++(id) alloc { SubAllocs++; return class_createInstance(self, 0); } +@end + +@interface OverridingAWZSub : NSObject @end +@implementation OverridingAWZSub ++(id) allocWithZone:(NSZone * __unused)z { SubAllocWithZones++; return class_createInstance(self, 0); } +@end + +@interface OverridingAAWZSub : NSObject @end +@implementation OverridingAAWZSub ++(id) alloc { SubAllocs++; return class_createInstance(self, 0); } ++(id) allocWithZone:(NSZone * __unused)z { SubAllocWithZones++; return class_createInstance(self, 0); } +@end + + +@interface InheritingSub : NSObject @end +@implementation InheritingSub @end + +@interface InheritingSub2 : NSObject @end +@implementation InheritingSub2 @end +@interface InheritingSub2_2 : InheritingSub2 @end +@implementation InheritingSub2_2 @end + +@interface InheritingSub3 : NSObject @end +@implementation InheritingSub3 @end +@interface InheritingSub3_2 : InheritingSub3 @end +@implementation InheritingSub3_2 @end + +@interface InheritingSub4 : NSObject @end +@implementation InheritingSub4 @end +@interface InheritingSub4_2 : InheritingSub4 @end +@implementation InheritingSub4_2 @end + +@interface InheritingSub5 : NSObject @end +@implementation InheritingSub5 @end +@interface InheritingSub5_2 : InheritingSub5 @end +@implementation InheritingSub5_2 @end + +@interface InheritingSub6 : NSObject @end +@implementation InheritingSub6 @end +@interface InheritingSub6_2 : InheritingSub6 @end +@implementation InheritingSub6_2 @end + +@interface InheritingSub7 : NSObject @end +@implementation InheritingSub7 @end +@interface InheritingSub7_2 : InheritingSub7 @end +@implementation InheritingSub7_2 @end + +@interface InheritingSubCat : NSObject @end +@implementation InheritingSubCat @end +@interface InheritingSubCat_2 : InheritingSubCat @end +@implementation InheritingSubCat_2 @end + + +extern uintptr_t OBJC_CLASS_$_UnrealizedSubA1; +@interface UnrealizedSubA1 : NSObject @end +@implementation UnrealizedSubA1 @end +extern uintptr_t OBJC_CLASS_$_UnrealizedSubA2; +@interface UnrealizedSubA2 : NSObject @end +@implementation UnrealizedSubA2 @end +extern uintptr_t OBJC_CLASS_$_UnrealizedSubA3; +@interface UnrealizedSubA3 : NSObject @end +@implementation UnrealizedSubA3 @end + +extern uintptr_t OBJC_CLASS_$_UnrealizedSubB1; +@interface UnrealizedSubB1 : NSObject @end +@implementation UnrealizedSubB1 @end +extern uintptr_t OBJC_CLASS_$_UnrealizedSubB2; +@interface UnrealizedSubB2 : NSObject @end +@implementation UnrealizedSubB2 @end +extern uintptr_t OBJC_CLASS_$_UnrealizedSubB3; +@interface UnrealizedSubB3 : NSObject @end +@implementation UnrealizedSubB3 @end + +extern uintptr_t OBJC_CLASS_$_UnrealizedSubC1; +@interface UnrealizedSubC1 : NSObject @end +@implementation UnrealizedSubC1 @end +extern uintptr_t OBJC_CLASS_$_UnrealizedSubC2; +@interface UnrealizedSubC2 : NSObject @end +@implementation UnrealizedSubC2 @end +extern uintptr_t OBJC_CLASS_$_UnrealizedSubC3; +@interface UnrealizedSubC3 : NSObject @end +@implementation UnrealizedSubC3 @end + + +int main(int argc __unused, char **argv) +{ + objc_autoreleasePoolPush(); + + // Hack NSObject's RR methods. + // Don't use runtime functions to do this - + // we want the runtime to think that these are NSObject's real code + { +#if __has_feature(ptrauth_calls) + typedef IMP __ptrauth_objc_method_list_imp MethodListIMP; +#else + typedef IMP MethodListIMP; +#endif + + Class cls = [NSObject class]; + IMP imp = class_getMethodImplementation(cls, @selector(retain)); + MethodListIMP *m = (MethodListIMP *) + class_getInstanceMethod(cls, @selector(retain)); + testassert(m[2] == imp); // verify Method struct is as we expect + + m = (MethodListIMP *)class_getInstanceMethod(cls, @selector(retain)); + m[2] = (IMP)HackRetain; + m = (MethodListIMP *)class_getInstanceMethod(cls, @selector(release)); + m[2] = (IMP)HackRelease; + m = (MethodListIMP *)class_getInstanceMethod(cls, @selector(autorelease)); + m[2] = (IMP)HackAutorelease; + m = (MethodListIMP *)class_getInstanceMethod(cls, @selector(retainCount)); + m[2] = (IMP)HackRetainCount; + m = (MethodListIMP *)class_getClassMethod(cls, @selector(retain)); + m[2] = (IMP)HackPlusRetain; + m = (MethodListIMP *)class_getClassMethod(cls, @selector(release)); + m[2] = (IMP)HackPlusRelease; + m = (MethodListIMP *)class_getClassMethod(cls, @selector(autorelease)); + m[2] = (IMP)HackPlusAutorelease; + m = (MethodListIMP *)class_getClassMethod(cls, @selector(retainCount)); + m[2] = (IMP)HackPlusRetainCount; + m = (MethodListIMP *)class_getClassMethod(cls, @selector(alloc)); + m[2] = (IMP)HackAlloc; + m = (MethodListIMP *)class_getClassMethod(cls, @selector(allocWithZone:)); + m[2] = (IMP)HackAllocWithZone; + + _objc_flush_caches(cls); + + imp = class_getMethodImplementation(cls, @selector(retain)); + testassert(imp == (IMP)HackRetain); // verify hack worked + } + + Class cls = [NSObject class]; + Class icl = [InheritingSub class]; + Class ocl = [OverridingSub class]; + /* + Class oa1 = [OverridingASub class]; + Class oa2 = [OverridingAWZSub class]; + Class oa3 = [OverridingAAWZSub class]; + */ + NSObject *obj = [NSObject new]; + InheritingSub *inh = [InheritingSub new]; + OverridingSub *ovr = [OverridingSub new]; + + Class ccc; + id ooo; + Class cc2; + id oo2; + + void *dlh; + + +#if __x86_64__ + // vtable dispatch can introduce bypass just like the ARC entrypoints +#else + testprintf("method dispatch does not bypass\n"); + zero(); + + [obj retain]; + testassert(Retains == 1); + [obj release]; + testassert(Releases == 1); + [obj autorelease]; + testassert(Autoreleases == 1); + + [cls retain]; + testassert(PlusRetains == 1); + [cls release]; + testassert(PlusReleases == 1); + [cls autorelease]; + testassert(PlusAutoreleases == 1); + + [inh retain]; + testassert(Retains == 2); + [inh release]; + testassert(Releases == 2); + [inh autorelease]; + testassert(Autoreleases == 2); + + [icl retain]; + testassert(PlusRetains == 2); + [icl release]; + testassert(PlusReleases == 2); + [icl autorelease]; + testassert(PlusAutoreleases == 2); + + [ovr retain]; + testassert(SubRetains == 1); + [ovr release]; + testassert(SubReleases == 1); + [ovr autorelease]; + testassert(SubAutoreleases == 1); + + [ocl retain]; + testassert(SubPlusRetains == 1); + [ocl release]; + testassert(SubPlusReleases == 1); + [ocl autorelease]; + testassert(SubPlusAutoreleases == 1); + + [UnrealizedSubA1 retain]; + testassert(PlusRetains == 3); + [UnrealizedSubA2 release]; + testassert(PlusReleases == 3); + [UnrealizedSubA3 autorelease]; + testassert(PlusAutoreleases == 3); +#endif + + + testprintf("objc_msgSend() does not bypass\n"); + zero(); + + id (*retain_fn)(id, SEL) = (id(*)(id, SEL))objc_msgSend; + void (*release_fn)(id, SEL) = (void(*)(id, SEL))objc_msgSend; + id (*autorelease_fn)(id, SEL) = (id(*)(id, SEL))objc_msgSend; + + retain_fn(obj, @selector(retain)); + testassert(Retains == 1); + release_fn(obj, @selector(release)); + testassert(Releases == 1); + autorelease_fn(obj, @selector(autorelease)); + testassert(Autoreleases == 1); + + retain_fn(cls, @selector(retain)); + testassert(PlusRetains == 1); + release_fn(cls, @selector(release)); + testassert(PlusReleases == 1); + autorelease_fn(cls, @selector(autorelease)); + testassert(PlusAutoreleases == 1); + + retain_fn(inh, @selector(retain)); + testassert(Retains == 2); + release_fn(inh, @selector(release)); + testassert(Releases == 2); + autorelease_fn(inh, @selector(autorelease)); + testassert(Autoreleases == 2); + + retain_fn(icl, @selector(retain)); + testassert(PlusRetains == 2); + release_fn(icl, @selector(release)); + testassert(PlusReleases == 2); + autorelease_fn(icl, @selector(autorelease)); + testassert(PlusAutoreleases == 2); + + retain_fn(ovr, @selector(retain)); + testassert(SubRetains == 1); + release_fn(ovr, @selector(release)); + testassert(SubReleases == 1); + autorelease_fn(ovr, @selector(autorelease)); + testassert(SubAutoreleases == 1); + + retain_fn(ocl, @selector(retain)); + testassert(SubPlusRetains == 1); + release_fn(ocl, @selector(release)); + testassert(SubPlusReleases == 1); + autorelease_fn(ocl, @selector(autorelease)); + testassert(SubPlusAutoreleases == 1); + + retain_fn((Class)&OBJC_CLASS_$_UnrealizedSubB1, @selector(retain)); + testassert(PlusRetains == 3); + release_fn((Class)&OBJC_CLASS_$_UnrealizedSubB2, @selector(release)); + testassert(PlusReleases == 3); + autorelease_fn((Class)&OBJC_CLASS_$_UnrealizedSubB3, @selector(autorelease)); + testassert(PlusAutoreleases == 3); + + + testprintf("arc function bypasses instance but not class or override\n"); + zero(); + + objc_retain(obj); + testassert(Retains == 0); + objc_release(obj); + testassert(Releases == 0); + objc_autorelease(obj); + testassert(Autoreleases == 0); + + objc_retain(cls); + testassert(PlusRetains == 1); + objc_release(cls); + testassert(PlusReleases == 1); + objc_autorelease(cls); + testassert(PlusAutoreleases == 1); + + objc_retain(inh); + testassert(Retains == 0); + objc_release(inh); + testassert(Releases == 0); + objc_autorelease(inh); + testassert(Autoreleases == 0); + + objc_retain(icl); + testassert(PlusRetains == 2); + objc_release(icl); + testassert(PlusReleases == 2); + objc_autorelease(icl); + testassert(PlusAutoreleases == 2); + + objc_retain(ovr); + testassert(SubRetains == 1); + objc_release(ovr); + testassert(SubReleases == 1); + objc_autorelease(ovr); + testassert(SubAutoreleases == 1); + + objc_retain(ocl); + testassert(SubPlusRetains == 1); + objc_release(ocl); + testassert(SubPlusReleases == 1); + objc_autorelease(ocl); + testassert(SubPlusAutoreleases == 1); + + objc_retain((Class)&OBJC_CLASS_$_UnrealizedSubC1); + testassert(PlusRetains == 3); + objc_release((Class)&OBJC_CLASS_$_UnrealizedSubC2); + testassert(PlusReleases == 3); + objc_autorelease((Class)&OBJC_CLASS_$_UnrealizedSubC3); + testassert(PlusAutoreleases == 3); + + + testprintf("unrelated addMethod does not clobber\n"); + zero(); + + class_addMethod(cls, @selector(unrelatedMethod), (IMP)imp_fn, ""); + + objc_retain(obj); + testassert(Retains == 0); + objc_release(obj); + testassert(Releases == 0); + objc_autorelease(obj); + testassert(Autoreleases == 0); + + + testprintf("add class method does not clobber\n"); + zero(); + + objc_retain(obj); + testassert(Retains == 0); + objc_release(obj); + testassert(Releases == 0); + objc_autorelease(obj); + testassert(Autoreleases == 0); + + class_addMethod(object_getClass(cls), @selector(retain), (IMP)imp_fn, ""); + + objc_retain(obj); + testassert(Retains == 0); + objc_release(obj); + testassert(Releases == 0); + objc_autorelease(obj); + testassert(Autoreleases == 0); + + + testprintf("addMethod clobbers (InheritingSub2, retain)\n"); + zero(); + + ccc = [InheritingSub2 class]; + ooo = [ccc new]; + cc2 = [InheritingSub2_2 class]; + oo2 = [cc2 new]; + + objc_retain(ooo); + testassert(Retains == 0); + objc_release(ooo); + testassert(Releases == 0); + objc_autorelease(ooo); + testassert(Autoreleases == 0); + + objc_retain(oo2); + testassert(Retains == 0); + objc_release(oo2); + testassert(Releases == 0); + objc_autorelease(oo2); + testassert(Autoreleases == 0); + + class_addMethod(ccc, @selector(retain), (IMP)imp_fn, ""); + + objc_retain(ooo); + testassert(Retains == 0); + testassert(Imps == 1); + objc_release(ooo); + testassert(Releases == 1); + objc_autorelease(ooo); + testassert(Autoreleases == 1); + + objc_retain(oo2); + testassert(Retains == 0); + testassert(Imps == 2); + objc_release(oo2); + testassert(Releases == 2); + objc_autorelease(oo2); + testassert(Autoreleases == 2); + + + testprintf("addMethod clobbers (InheritingSub3, release)\n"); + zero(); + + ccc = [InheritingSub3 class]; + ooo = [ccc new]; + cc2 = [InheritingSub3_2 class]; + oo2 = [cc2 new]; + + objc_retain(ooo); + testassert(Retains == 0); + objc_release(ooo); + testassert(Releases == 0); + objc_autorelease(ooo); + testassert(Autoreleases == 0); + + objc_retain(oo2); + testassert(Retains == 0); + objc_release(oo2); + testassert(Releases == 0); + objc_autorelease(oo2); + testassert(Autoreleases == 0); + + class_addMethod(ccc, @selector(release), (IMP)imp_fn, ""); + + objc_retain(ooo); + testassert(Retains == 1); + objc_release(ooo); + testassert(Releases == 0); + testassert(Imps == 1); + objc_autorelease(ooo); + testassert(Autoreleases == 1); + + objc_retain(oo2); + testassert(Retains == 2); + objc_release(oo2); + testassert(Releases == 0); + testassert(Imps == 2); + objc_autorelease(oo2); + testassert(Autoreleases == 2); + + + testprintf("addMethod clobbers (InheritingSub4, autorelease)\n"); + zero(); + + ccc = [InheritingSub4 class]; + ooo = [ccc new]; + cc2 = [InheritingSub4_2 class]; + oo2 = [cc2 new]; + + objc_retain(ooo); + testassert(Retains == 0); + objc_release(ooo); + testassert(Releases == 0); + objc_autorelease(ooo); + testassert(Autoreleases == 0); + + objc_retain(oo2); + testassert(Retains == 0); + objc_release(oo2); + testassert(Releases == 0); + objc_autorelease(oo2); + testassert(Autoreleases == 0); + + class_addMethod(ccc, @selector(autorelease), (IMP)imp_fn, ""); + + objc_retain(ooo); + testassert(Retains == 1); + objc_release(ooo); + testassert(Releases == 1); + objc_autorelease(ooo); + testassert(Autoreleases == 0); + testassert(Imps == 1); + + objc_retain(oo2); + testassert(Retains == 2); + objc_release(oo2); + testassert(Releases == 2); + objc_autorelease(oo2); + testassert(Autoreleases == 0); + testassert(Imps == 2); + + + testprintf("addMethod clobbers (InheritingSub5, retainCount)\n"); + zero(); + + ccc = [InheritingSub5 class]; + ooo = [ccc new]; + cc2 = [InheritingSub5_2 class]; + oo2 = [cc2 new]; + + objc_retain(ooo); + testassert(Retains == 0); + objc_release(ooo); + testassert(Releases == 0); + objc_autorelease(ooo); + testassert(Autoreleases == 0); + + objc_retain(oo2); + testassert(Retains == 0); + objc_release(oo2); + testassert(Releases == 0); + objc_autorelease(oo2); + testassert(Autoreleases == 0); + + class_addMethod(ccc, @selector(retainCount), (IMP)imp_fn, ""); + + objc_retain(ooo); + testassert(Retains == 1); + objc_release(ooo); + testassert(Releases == 1); + objc_autorelease(ooo); + testassert(Autoreleases == 1); + // no bypassing call for -retainCount + + objc_retain(oo2); + testassert(Retains == 2); + objc_release(oo2); + testassert(Releases == 2); + objc_autorelease(oo2); + testassert(Autoreleases == 2); + // no bypassing call for -retainCount + + + testprintf("setSuperclass to clean super does not clobber (InheritingSub6)\n"); + zero(); + + ccc = [InheritingSub6 class]; + ooo = [ccc new]; + cc2 = [InheritingSub6_2 class]; + oo2 = [cc2 new]; + + objc_retain(ooo); + testassert(Retains == 0); + objc_release(ooo); + testassert(Releases == 0); + objc_autorelease(ooo); + testassert(Autoreleases == 0); + + objc_retain(oo2); + testassert(Retains == 0); + objc_release(oo2); + testassert(Releases == 0); + objc_autorelease(oo2); + testassert(Autoreleases == 0); + + class_setSuperclass(ccc, [InheritingSub class]); + + objc_retain(ooo); + testassert(Retains == 0); + objc_release(ooo); + testassert(Releases == 0); + objc_autorelease(ooo); + testassert(Autoreleases == 0); + + objc_retain(oo2); + testassert(Retains == 0); + objc_release(oo2); + testassert(Releases == 0); + objc_autorelease(oo2); + testassert(Autoreleases == 0); + + + testprintf("setSuperclass to dirty super clobbers (InheritingSub7)\n"); + zero(); + + ccc = [InheritingSub7 class]; + ooo = [ccc new]; + cc2 = [InheritingSub7_2 class]; + oo2 = [cc2 new]; + + objc_retain(ooo); + testassert(Retains == 0); + objc_release(ooo); + testassert(Releases == 0); + objc_autorelease(ooo); + testassert(Autoreleases == 0); + + objc_retain(oo2); + testassert(Retains == 0); + objc_release(oo2); + testassert(Releases == 0); + objc_autorelease(oo2); + testassert(Autoreleases == 0); + + class_setSuperclass(ccc, [OverridingSub class]); + + objc_retain(ooo); + testassert(SubRetains == 1); + objc_release(ooo); + testassert(SubReleases == 1); + objc_autorelease(ooo); + testassert(SubAutoreleases == 1); + + objc_retain(oo2); + testassert(SubRetains == 2); + objc_release(oo2); + testassert(SubReleases == 2); + objc_autorelease(oo2); + testassert(SubAutoreleases == 2); + + + testprintf("category replacement of unrelated method does not clobber (InheritingSubCat)\n"); + zero(); + + ccc = [InheritingSubCat class]; + ooo = [ccc new]; + cc2 = [InheritingSubCat_2 class]; + oo2 = [cc2 new]; + + objc_retain(ooo); + testassert(Retains == 0); + objc_release(ooo); + testassert(Releases == 0); + objc_autorelease(ooo); + testassert(Autoreleases == 0); + + objc_retain(oo2); + testassert(Retains == 0); + objc_release(oo2); + testassert(Releases == 0); + objc_autorelease(oo2); + testassert(Autoreleases == 0); + + dlh = dlopen("customrr-cat1.bundle", RTLD_LAZY); + testassert(dlh); + + objc_retain(ooo); + testassert(Retains == 0); + objc_release(ooo); + testassert(Releases == 0); + objc_autorelease(ooo); + testassert(Autoreleases == 0); + + objc_retain(oo2); + testassert(Retains == 0); + objc_release(oo2); + testassert(Releases == 0); + objc_autorelease(oo2); + testassert(Autoreleases == 0); + + + testprintf("category replacement clobbers (InheritingSubCat)\n"); + zero(); + + ccc = [InheritingSubCat class]; + ooo = [ccc new]; + cc2 = [InheritingSubCat_2 class]; + oo2 = [cc2 new]; + + objc_retain(ooo); + testassert(Retains == 0); + objc_release(ooo); + testassert(Releases == 0); + objc_autorelease(ooo); + testassert(Autoreleases == 0); + + objc_retain(oo2); + testassert(Retains == 0); + objc_release(oo2); + testassert(Releases == 0); + objc_autorelease(oo2); + testassert(Autoreleases == 0); + + dlh = dlopen("customrr-cat2.bundle", RTLD_LAZY); + testassert(dlh); + + objc_retain(ooo); + testassert(Retains == 1); + objc_release(ooo); + testassert(Releases == 1); + objc_autorelease(ooo); + testassert(Autoreleases == 1); + + objc_retain(oo2); + testassert(Retains == 2); + objc_release(oo2); + testassert(Releases == 2); + objc_autorelease(oo2); + testassert(Autoreleases == 2); + + + testprintf("allocateClassPair with clean super does not clobber\n"); + zero(); + + objc_retain(inh); + testassert(Retains == 0); + objc_release(inh); + testassert(Releases == 0); + objc_autorelease(inh); + testassert(Autoreleases == 0); + + ccc = objc_allocateClassPair([InheritingSub class], "CleanClassPair", 0); + objc_registerClassPair(ccc); + ooo = [ccc new]; + + objc_retain(inh); + testassert(Retains == 0); + objc_release(inh); + testassert(Releases == 0); + objc_autorelease(inh); + testassert(Autoreleases == 0); + + objc_retain(ooo); + testassert(Retains == 0); + objc_release(ooo); + testassert(Releases == 0); + objc_autorelease(ooo); + testassert(Autoreleases == 0); + + + testprintf("allocateClassPair with clobbered super clobbers\n"); + zero(); + + ccc = objc_allocateClassPair([OverridingSub class], "DirtyClassPair", 0); + objc_registerClassPair(ccc); + ooo = [ccc new]; + + objc_retain(ooo); + testassert(SubRetains == 1); + objc_release(ooo); + testassert(SubReleases == 1); + objc_autorelease(ooo); + testassert(SubAutoreleases == 1); + + + testprintf("allocateClassPair with clean super and override clobbers\n"); + zero(); + + ccc = objc_allocateClassPair([InheritingSub class], "Dirty2ClassPair", 0); + class_addMethod(ccc, @selector(autorelease), (IMP)imp_fn, ""); + objc_registerClassPair(ccc); + ooo = [ccc new]; + + objc_retain(ooo); + testassert(Retains == 1); + objc_release(ooo); + testassert(Releases == 1); + objc_autorelease(ooo); + testassert(Autoreleases == 0); + testassert(Imps == 1); + + + // method_setImplementation and method_exchangeImplementations only + // clobber when manipulating NSObject. We can only test one at a time. + // To test both, we need two tests: customrr and customrr2. + + // These tests also check recursive clobber. + +#if TEST_EXCHANGEIMPLEMENTATIONS + testprintf("exchangeImplementations clobbers (recursive)\n"); +#else + testprintf("setImplementation clobbers (recursive)\n"); +#endif + zero(); + + objc_retain(obj); + testassert(Retains == 0); + objc_release(obj); + testassert(Releases == 0); + objc_autorelease(obj); + testassert(Autoreleases == 0); + + objc_retain(inh); + testassert(Retains == 0); + objc_release(inh); + testassert(Releases == 0); + objc_autorelease(inh); + testassert(Autoreleases == 0); + + Method meth = class_getInstanceMethod(cls, @selector(retainCount)); + testassert(meth); +#if TEST_EXCHANGEIMPLEMENTATIONS + method_exchangeImplementations(meth, meth); +#else + method_setImplementation(meth, (IMP)imp_fn); +#endif + + objc_retain(obj); + testassert(Retains == 1); + objc_release(obj); + testassert(Releases == 1); + objc_autorelease(obj); + testassert(Autoreleases == 1); + + objc_retain(inh); + testassert(Retains == 2); + objc_release(inh); + testassert(Releases == 2); + objc_autorelease(inh); + testassert(Autoreleases == 2); + + + // do not add more tests here - the recursive test must be LAST + + succeed(basename(argv[0])); +} diff --git a/test/customrr2.m b/test/customrr2.m new file mode 100644 index 0000000..935aae6 --- /dev/null +++ b/test/customrr2.m @@ -0,0 +1,9 @@ +// These options must match customrr.m +// TEST_CONFIG MEM=mrc +/* +TEST_BUILD + $C{COMPILE} $DIR/customrr.m -fvisibility=default -o customrr2.exe -DTEST_EXCHANGEIMPLEMENTATIONS=1 + $C{COMPILE} -bundle -bundle_loader customrr2.exe $DIR/customrr-cat1.m -o customrr-cat1.bundle + $C{COMPILE} -bundle -bundle_loader customrr2.exe $DIR/customrr-cat2.m -o customrr-cat2.bundle +END +*/ diff --git a/test/definitions.c b/test/definitions.c new file mode 100644 index 0000000..570abb0 --- /dev/null +++ b/test/definitions.c @@ -0,0 +1,54 @@ +// TEST_CONFIG + +// DO NOT include anything else here +#include +// DO NOT include anything else here +Class c = Nil; +SEL s; +IMP i; +id o = nil; +BOOL b = YES; +BOOL b2 = NO; +#if !__has_feature(objc_arc) +__strong void *p; +#endif +id __unsafe_unretained u; +#if __has_feature(objc_arc_weak) +id __weak w; +#endif + +void fn(void) __unused; +void fn(void) { + id __autoreleasing a __unused; +} + +// check type inference for blocks returning YES and NO (rdar://10118972) +BOOL (^block1)(void) = ^{ return YES; }; +BOOL (^block2)(void) = ^{ return NO; }; + +#include "test.h" + +int main() +{ + testassert(YES); + testassert(!NO); +#if __cplusplus + testwarn("rdar://12371870 -Wnull-conversion"); + testassert(!(bool)nil); + testassert(!(bool)Nil); +#else + testassert(!nil); + testassert(!Nil); +#endif + +#if __has_feature(objc_bool) + // YES[array] is disallowed for objc just as true[array] is for C++ +#else + // this will fail if YES and NO do not have enough parentheses + int array[2] = { 888, 999 }; + testassert(NO[array] == 888); + testassert(YES[array] == 999); +#endif + + succeed(__FILE__); +} diff --git a/test/designatedinit.m b/test/designatedinit.m new file mode 100644 index 0000000..232625f --- /dev/null +++ b/test/designatedinit.m @@ -0,0 +1,26 @@ +// TEST_CONFIG +/* TEST_BUILD_OUTPUT +.*designatedinit.m:\d+:\d+: warning: designated initializer should only invoke a designated initializer on 'super'.* +.*designatedinit.m:\d+:\d+: note: .* +.*designatedinit.m:\d+:\d+: warning: method override for the designated initializer of the superclass '-init' not found.* +.*NSObject.h:\d+:\d+: note: .* +END */ + +#define NS_ENFORCE_NSOBJECT_DESIGNATED_INITIALIZER 1 +#include "test.h" +#include + +@interface C : NSObject +-(id) initWithInt:(int)i NS_DESIGNATED_INITIALIZER; +@end + +@implementation C +-(id) initWithInt:(int)__unused i { + return [self init]; +} +@end + +int main() +{ + succeed(__FILE__); +} diff --git a/test/duplicateClass.m b/test/duplicateClass.m new file mode 100644 index 0000000..7c44f07 --- /dev/null +++ b/test/duplicateClass.m @@ -0,0 +1,150 @@ +// TEST_CFLAGS -Wno-deprecated-declarations -Wl,-no_objc_category_merging + +#include "test.h" +#include "testroot.i" +#include + +static int state; + +@protocol Proto ++(void)classMethod; +-(void)instanceMethod; +@end + +@interface Super : TestRoot { + int i; +} +@property int i; +@end + +@implementation Super +@synthesize i; + ++(void)classMethod { + state = 1; +} + +-(void)instanceMethod { + state = 3; +} + +@end + + +#if __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wobjc-protocol-method-implementation" +#endif + +@implementation Super (Category) + ++(void)classMethod { + state = 2; +} + +-(void)instanceMethod { + state = 4; +} + +@end + +#if __clang__ +#pragma clang diagnostic pop +#endif + + +int main() +{ + Class clone; + Class cls; + Method *m1, *m2; + int i; + + cls = [Super class]; + clone = objc_duplicateClass(cls, "Super_copy", 0); + + testassert(clone != cls); + testassert(object_getClass(clone) == object_getClass(cls)); + testassert(class_getSuperclass(clone) == class_getSuperclass(cls)); + testassert(class_getVersion(clone) == class_getVersion(cls)); + testassert(class_isMetaClass(clone) == class_isMetaClass(cls)); + testassert(class_getIvarLayout(clone) == class_getIvarLayout(cls)); + testassert(class_getWeakIvarLayout(clone) == class_getWeakIvarLayout(cls)); + + // Check method list + + m1 = class_copyMethodList(cls, NULL); + m2 = class_copyMethodList(clone, NULL); + testassert(m1); + testassert(m2); + for (i = 0; m1[i] && m2[i]; i++) { + testassert(m1[i] != m2[i]); // method list must be deep-copied + testassert(method_getName(m1[i]) == method_getName(m2[i])); + testassert(method_getImplementation(m1[i]) == method_getImplementation(m2[i])); + testassert(method_getTypeEncoding(m1[i]) == method_getTypeEncoding(m2[i])); + } + testassert(m1[i] == NULL && m2[i] == NULL); + free(m1); + free(m2); + + // Check ivar list + Ivar *i1 = class_copyIvarList(cls, NULL); + Ivar *i2 = class_copyIvarList(clone, NULL); + testassert(i1); + testassert(i2); + for (i = 0; i1[i] && i2[i]; i++) { + testassert(i1[i] == i2[i]); // ivars are not deep-copied + } + testassert(i1[i] == NULL && i2[i] == NULL); + free(i1); + free(i2); + + // Check protocol list + Protocol * __unsafe_unretained *p1 = class_copyProtocolList(cls, NULL); + Protocol * __unsafe_unretained *p2 = class_copyProtocolList(clone, NULL); + testassert(p1); + testassert(p2); + for (i = 0; p1[i] && p2[i]; i++) { + testassert(p1[i] == p2[i]); // protocols are not deep-copied + } + testassert(p1[i] == NULL && p2[i] == NULL); + free(p1); + free(p2); + + // Check property list + objc_property_t *o1 = class_copyPropertyList(cls, NULL); + objc_property_t *o2 = class_copyPropertyList(clone, NULL); + testassert(o1); + testassert(o2); + for (i = 0; o1[i] && o2[i]; i++) { + testassert(o1[i] == o2[i]); // properties are not deep-copied + } + testassert(o1[i] == NULL && o2[i] == NULL); + free(o1); + free(o2); + + // Check method calls + + state = 0; + [cls classMethod]; + testassert(state == 2); + state = 0; + [clone classMethod]; + testassert(state == 2); + + // #4511660 Make sure category implementation is still the preferred one + id obj; + obj = [cls new]; + state = 0; + [obj instanceMethod]; + testassert(state == 4); + RELEASE_VAR(obj); + + obj = [clone new]; + state = 0; + [obj instanceMethod]; + testassert(state == 4); + RELEASE_VAR(obj); + + succeed(__FILE__); +} diff --git a/test/duplicatedClasses.m b/test/duplicatedClasses.m new file mode 100644 index 0000000..f5acb89 --- /dev/null +++ b/test/duplicatedClasses.m @@ -0,0 +1,26 @@ +// fixme rdar://24624435 duplicate class warning fails with the shared cache +// OBJC_DISABLE_PREOPTIMIZATION=YES works around that problem. + +// TEST_ENV OBJC_DEBUG_DUPLICATE_CLASSES=YES OBJC_DISABLE_PREOPTIMIZATION=YES +// TEST_CRASHES +/* +TEST_RUN_OUTPUT +objc\[\d+\]: Class [^\s]+ is implemented in both .+ \(0x[0-9a-f]+\) and .+ \(0x[0-9a-f]+\)\. One of the two will be used\. Which one is undefined\. +objc\[\d+\]: HALTED +OR +OK: duplicatedClasses.m +END + */ + +#include "test.h" +#include "testroot.i" + +@interface WKWebView : TestRoot @end +@implementation WKWebView @end + +int main() +{ + void *dl = dlopen("/System/Library/Frameworks/WebKit.framework/WebKit", RTLD_LAZY); + if (!dl) fail("couldn't open WebKit"); + fail("should have crashed already"); +} diff --git a/test/evil-category-0.m b/test/evil-category-0.m new file mode 100644 index 0000000..a7cc36b --- /dev/null +++ b/test/evil-category-0.m @@ -0,0 +1,18 @@ +/* +rdar://8553305 + +TEST_BUILD + $C{COMPILE} $DIR/evil-category-0.m -dynamiclib -o libevil.dylib + $C{COMPILE} $DIR/evil-main.m -x none -DNOT_EVIL libevil.dylib -o evil-category-0.exe +END +*/ + +// NOT EVIL version + +#define EVIL_INSTANCE_METHOD 0 +#define EVIL_CLASS_METHOD 0 + +#define OMIT_CAT 0 +#define OMIT_NL_CAT 0 + +#include "evil-category-def.m" diff --git a/test/evil-category-00.m b/test/evil-category-00.m new file mode 100644 index 0000000..0b1e842 --- /dev/null +++ b/test/evil-category-00.m @@ -0,0 +1,24 @@ +/* +rdar://8553305 + +TEST_CONFIG OS=iphoneos +TEST_CRASHES + +TEST_BUILD + $C{COMPILE} $DIR/evil-category-00.m $DIR/evil-main.m -o evil-category-00.exe +END + +TEST_RUN_OUTPUT +CRASHED: SIGABRT +END +*/ + +// NOT EVIL version: apps are allowed through (then crash in +load) + +#define EVIL_INSTANCE_METHOD 1 +#define EVIL_CLASS_METHOD 1 + +#define OMIT_CAT 0 +#define OMIT_NL_CAT 0 + +#include "evil-category-def.m" diff --git a/test/evil-category-000.m b/test/evil-category-000.m new file mode 100644 index 0000000..9e599f9 --- /dev/null +++ b/test/evil-category-000.m @@ -0,0 +1,18 @@ +/* +rdar://8553305 + +TEST_BUILD + $C{COMPILE} $DIR/evil-category-000.m -dynamiclib -o libevil.dylib + $C{COMPILE} $DIR/evil-main.m -x none -DNOT_EVIL libevil.dylib -o evil-category-000.exe +END +*/ + +// NOT EVIL version: category omitted from all lists + +#define EVIL_INSTANCE_METHOD 1 +#define EVIL_CLASS_METHOD 1 + +#define OMIT_CAT 1 +#define OMIT_NL_CAT 1 + +#include "evil-category-def.m" diff --git a/test/evil-category-1.m b/test/evil-category-1.m new file mode 100644 index 0000000..7907a88 --- /dev/null +++ b/test/evil-category-1.m @@ -0,0 +1,24 @@ +/* +rdar://8553305 + +TEST_CONFIG OS=iphoneos +TEST_CRASHES + +TEST_BUILD + $C{COMPILE} $DIR/evil-category-1.m -dynamiclib -o libevil.dylib + $C{COMPILE} $DIR/evil-main.m -x none libevil.dylib -o evil-category-1.exe +END + +TEST_RUN_OUTPUT +objc\[\d+\]: bad method implementation \(0x[0-9a-f]+ at 0x[0-9a-f]+\) +objc\[\d+\]: HALTED +END +*/ + +#define EVIL_INSTANCE_METHOD 1 +#define EVIL_CLASS_METHOD 0 + +#define OMIT_CAT 0 +#define OMIT_NL_CAT 0 + +#include "evil-category-def.m" diff --git a/test/evil-category-2.m b/test/evil-category-2.m new file mode 100644 index 0000000..c719402 --- /dev/null +++ b/test/evil-category-2.m @@ -0,0 +1,24 @@ +/* +rdar://8553305 + +TEST_CONFIG OS=iphoneos +TEST_CRASHES + +TEST_BUILD + $C{COMPILE} $DIR/evil-category-2.m -dynamiclib -o libevil.dylib + $C{COMPILE} $DIR/evil-main.m -x none libevil.dylib -o evil-category-2.exe +END + +TEST_RUN_OUTPUT +objc\[\d+\]: bad method implementation \(0x[0-9a-f]+ at 0x[0-9a-f]+\) +objc\[\d+\]: HALTED +END +*/ + +#define EVIL_INSTANCE_METHOD 0 +#define EVIL_CLASS_METHOD 1 + +#define OMIT_CAT 0 +#define OMIT_NL_CAT 0 + +#include "evil-category-def.m" diff --git a/test/evil-category-3.m b/test/evil-category-3.m new file mode 100644 index 0000000..3a7b510 --- /dev/null +++ b/test/evil-category-3.m @@ -0,0 +1,24 @@ +/* +rdar://8553305 + +TEST_CONFIG OS=iphoneos +TEST_CRASHES + +TEST_BUILD + $C{COMPILE} $DIR/evil-category-3.m -dynamiclib -o libevil.dylib + $C{COMPILE} $DIR/evil-main.m -x none libevil.dylib -o evil-category-3.exe +END + +TEST_RUN_OUTPUT +objc\[\d+\]: bad method implementation \(0x[0-9a-f]+ at 0x[0-9a-f]+\) +objc\[\d+\]: HALTED +END +*/ + +#define EVIL_INSTANCE_METHOD 0 +#define EVIL_CLASS_METHOD 1 + +#define OMIT_CAT 1 +#define OMIT_NL_CAT 0 + +#include "evil-category-def.m" diff --git a/test/evil-category-4.m b/test/evil-category-4.m new file mode 100644 index 0000000..12c10fa --- /dev/null +++ b/test/evil-category-4.m @@ -0,0 +1,24 @@ +/* +rdar://8553305 + +TEST_CONFIG OS=iphoneos +TEST_CRASHES + +TEST_BUILD + $C{COMPILE} $DIR/evil-category-4.m -dynamiclib -o libevil.dylib + $C{COMPILE} $DIR/evil-main.m -x none libevil.dylib -o evil-category-4.exe +END + +TEST_RUN_OUTPUT +objc\[\d+\]: bad method implementation \(0x[0-9a-f]+ at 0x[0-9a-f]+\) +objc\[\d+\]: HALTED +END +*/ + +#define EVIL_INSTANCE_METHOD 0 +#define EVIL_CLASS_METHOD 1 + +#define OMIT_CAT 0 +#define OMIT_NL_CAT 1 + +#include "evil-category-def.m" diff --git a/test/evil-category-def.m b/test/evil-category-def.m new file mode 100644 index 0000000..6d0f1e0 --- /dev/null +++ b/test/evil-category-def.m @@ -0,0 +1,73 @@ +#include + +#if __LP64__ +# define PTR " .quad " +#else +# define PTR " .long " +#endif + +#if __has_feature(ptrauth_calls) +# define SIGNED_METHOD_LIST_IMP "@AUTH(ia,0,addr) " +#else +# define SIGNED_METHOD_LIST_IMP +#endif + +#define str(x) #x +#define str2(x) str(x) + +__BEGIN_DECLS +void nop(void) { } +__END_DECLS + +asm( + ".section __DATA,__objc_data \n" + ".align 3 \n" + "L_category: \n" + PTR "L_cat_name \n" + PTR "_OBJC_CLASS_$_NSObject \n" +#if EVIL_INSTANCE_METHOD + PTR "L_evil_methods \n" +#else + PTR "L_good_methods \n" +#endif +#if EVIL_CLASS_METHOD + PTR "L_evil_methods \n" +#else + PTR "L_good_methods \n" +#endif + PTR "0 \n" + PTR "0 \n" + + "L_evil_methods: \n" + ".long 24 \n" + ".long 1 \n" + PTR "L_load \n" + PTR "L_load \n" + PTR "_abort" SIGNED_METHOD_LIST_IMP "\n" + // assumes that abort is inside the dyld shared cache + + "L_good_methods: \n" + ".long 24 \n" + ".long 1 \n" + PTR "L_load \n" + PTR "L_load \n" + PTR "_nop" SIGNED_METHOD_LIST_IMP "\n" + + ".cstring \n" + "L_cat_name: .ascii \"Evil\\0\" \n" + "L_load: .ascii \"load\\0\" \n" + + ".section __DATA,__objc_catlist \n" +#if !OMIT_CAT + PTR "L_category \n" +#endif + + ".section __DATA,__objc_nlcatlist \n" +#if !OMIT_NL_CAT + PTR "L_category \n" +#endif + + ".text \n" + ); + +void fn(void) { } diff --git a/test/evil-class-0.m b/test/evil-class-0.m new file mode 100644 index 0000000..cd71806 --- /dev/null +++ b/test/evil-class-0.m @@ -0,0 +1,22 @@ +/* +rdar://8553305 + +TEST_BUILD + $C{COMPILE} $DIR/evil-class-0.m -dynamiclib -o libevil.dylib + $C{COMPILE} $DIR/evil-main.m -x none -DNOT_EVIL libevil.dylib -o evil-class-0.exe +END +*/ + +// NOT EVIL version + +#define EVIL_SUPER 0 +#define EVIL_SUPER_META 0 +#define EVIL_SUB 0 +#define EVIL_SUB_META 0 + +#define OMIT_SUPER 0 +#define OMIT_NL_SUPER 0 +#define OMIT_SUB 0 +#define OMIT_NL_SUB 0 + +#include "evil-class-def.m" diff --git a/test/evil-class-00.m b/test/evil-class-00.m new file mode 100644 index 0000000..1b39407 --- /dev/null +++ b/test/evil-class-00.m @@ -0,0 +1,28 @@ +/* +rdar://8553305 + +TEST_CONFIG OS=iphoneos +TEST_CRASHES + +TEST_BUILD + $C{COMPILE} $DIR/evil-class-00.m $DIR/evil-main.m -o evil-class-00.exe +END + +TEST_RUN_OUTPUT +CRASHED: SIGABRT +END +*/ + +// NOT EVIL version: apps are allowed through (then crash in +load) + +#define EVIL_SUPER 0 +#define EVIL_SUPER_META 1 +#define EVIL_SUB 0 +#define EVIL_SUB_META 0 + +#define OMIT_SUPER 1 +#define OMIT_NL_SUPER 1 +#define OMIT_SUB 1 +#define OMIT_NL_SUB 0 + +#include "evil-class-def.m" diff --git a/test/evil-class-000.m b/test/evil-class-000.m new file mode 100644 index 0000000..fbf1ce4 --- /dev/null +++ b/test/evil-class-000.m @@ -0,0 +1,22 @@ +/* +rdar://8553305 + +TEST_BUILD + $C{COMPILE} $DIR/evil-class-000.m -dynamiclib -o libevil.dylib + $C{COMPILE} $DIR/evil-main.m -x none -DNOT_EVIL libevil.dylib -o evil-class-000.exe +END +*/ + +// NOT EVIL version: all classes omitted from all lists + +#define EVIL_SUPER 1 +#define EVIL_SUPER_META 1 +#define EVIL_SUB 1 +#define EVIL_SUB_META 1 + +#define OMIT_SUPER 1 +#define OMIT_NL_SUPER 1 +#define OMIT_SUB 1 +#define OMIT_NL_SUB 1 + +#include "evil-class-def.m" diff --git a/test/evil-class-1.m b/test/evil-class-1.m new file mode 100644 index 0000000..f8785ee --- /dev/null +++ b/test/evil-class-1.m @@ -0,0 +1,28 @@ +/* +rdar://8553305 + +TEST_CONFIG OS=iphoneos +TEST_CRASHES + +TEST_BUILD + $C{COMPILE} $DIR/evil-class-1.m -dynamiclib -o libevil.dylib + $C{COMPILE} $DIR/evil-main.m -x none libevil.dylib -o evil-class-1.exe +END + +TEST_RUN_OUTPUT +objc\[\d+\]: bad method implementation \(0x[0-9a-f]+ at 0x[0-9a-f]+\) +objc\[\d+\]: HALTED +END +*/ + +#define EVIL_SUPER 1 +#define EVIL_SUPER_META 0 +#define EVIL_SUB 0 +#define EVIL_SUB_META 0 + +#define OMIT_SUPER 0 +#define OMIT_NL_SUPER 0 +#define OMIT_SUB 0 +#define OMIT_NL_SUB 0 + +#include "evil-class-def.m" diff --git a/test/evil-class-2.m b/test/evil-class-2.m new file mode 100644 index 0000000..9175eb3 --- /dev/null +++ b/test/evil-class-2.m @@ -0,0 +1,28 @@ +/* +rdar://8553305 + +TEST_CONFIG OS=iphoneos +TEST_CRASHES + +TEST_BUILD + $C{COMPILE} $DIR/evil-class-2.m -dynamiclib -o libevil.dylib + $C{COMPILE} $DIR/evil-main.m -x none libevil.dylib -o evil-class-2.exe +END + +TEST_RUN_OUTPUT +objc\[\d+\]: bad method implementation \(0x[0-9a-f]+ at 0x[0-9a-f]+\) +objc\[\d+\]: HALTED +END +*/ + +#define EVIL_SUPER 0 +#define EVIL_SUPER_META 1 +#define EVIL_SUB 0 +#define EVIL_SUB_META 0 + +#define OMIT_SUPER 0 +#define OMIT_NL_SUPER 0 +#define OMIT_SUB 0 +#define OMIT_NL_SUB 0 + +#include "evil-class-def.m" diff --git a/test/evil-class-3.m b/test/evil-class-3.m new file mode 100644 index 0000000..c068b34 --- /dev/null +++ b/test/evil-class-3.m @@ -0,0 +1,28 @@ +/* +rdar://8553305 + +TEST_CONFIG OS=iphoneos +TEST_CRASHES + +TEST_BUILD + $C{COMPILE} $DIR/evil-class-3.m -dynamiclib -o libevil.dylib + $C{COMPILE} $DIR/evil-main.m -x none libevil.dylib -o evil-class-3.exe +END + +TEST_RUN_OUTPUT +objc\[\d+\]: bad method implementation \(0x[0-9a-f]+ at 0x[0-9a-f]+\) +objc\[\d+\]: HALTED +END +*/ + +#define EVIL_SUPER 0 +#define EVIL_SUPER_META 0 +#define EVIL_SUB 1 +#define EVIL_SUB_META 0 + +#define OMIT_SUPER 0 +#define OMIT_NL_SUPER 0 +#define OMIT_SUB 0 +#define OMIT_NL_SUB 0 + +#include "evil-class-def.m" diff --git a/test/evil-class-4.m b/test/evil-class-4.m new file mode 100644 index 0000000..5189da8 --- /dev/null +++ b/test/evil-class-4.m @@ -0,0 +1,28 @@ +/* +rdar://8553305 + +TEST_CONFIG OS=iphoneos +TEST_CRASHES + +TEST_BUILD + $C{COMPILE} $DIR/evil-class-4.m -dynamiclib -o libevil.dylib + $C{COMPILE} $DIR/evil-main.m -x none libevil.dylib -o evil-class-4.exe +END + +TEST_RUN_OUTPUT +objc\[\d+\]: bad method implementation \(0x[0-9a-f]+ at 0x[0-9a-f]+\) +objc\[\d+\]: HALTED +END +*/ + +#define EVIL_SUPER 0 +#define EVIL_SUPER_META 0 +#define EVIL_SUB 0 +#define EVIL_SUB_META 1 + +#define OMIT_SUPER 0 +#define OMIT_NL_SUPER 0 +#define OMIT_SUB 0 +#define OMIT_NL_SUB 0 + +#include "evil-class-def.m" diff --git a/test/evil-class-5.m b/test/evil-class-5.m new file mode 100644 index 0000000..9a78ab4 --- /dev/null +++ b/test/evil-class-5.m @@ -0,0 +1,30 @@ +/* +rdar://8553305 + +TEST_DISABLED rdar://19200100 + +TEST_CONFIG OS=iphoneos +TEST_CRASHES + +TEST_BUILD + $C{COMPILE} $DIR/evil-class-5.m -dynamiclib -o libevil.dylib + $C{COMPILE} $DIR/evil-main.m -x none libevil.dylib -o evil-class-5.exe +END + +TEST_RUN_OUTPUT +objc\[\d+\]: bad method implementation \(0x[0-9a-f]+ at 0x[0-9a-f]+\) +objc\[\d+\]: HALTED +END +*/ + +#define EVIL_SUPER 0 +#define EVIL_SUPER_META 1 +#define EVIL_SUB 0 +#define EVIL_SUB_META 0 + +#define OMIT_SUPER 1 +#define OMIT_NL_SUPER 1 +#define OMIT_SUB 1 +#define OMIT_NL_SUB 0 + +#include "evil-class-def.m" diff --git a/test/evil-class-def.m b/test/evil-class-def.m new file mode 100644 index 0000000..6c9f25c --- /dev/null +++ b/test/evil-class-def.m @@ -0,0 +1,321 @@ +#include + +#if __LP64__ +# define PTR " .quad " +# define PTRSIZE "8" +# define LOGPTRSIZE "3" +#else +# define PTR " .long " +# define PTRSIZE "4" +# define LOGPTRSIZE "2" +#endif + +#if __has_feature(ptrauth_calls) +# define SIGNED_METHOD_LIST_IMP "@AUTH(ia,0,addr) " +#else +# define SIGNED_METHOD_LIST_IMP +#endif + +#define str(x) #x +#define str2(x) str(x) + +__BEGIN_DECLS +// not id to avoid ARC operations because the class doesn't implement RR methods +void* nop(void* self) { return self; } +__END_DECLS + +asm( + ".globl _OBJC_CLASS_$_Super \n" + ".section __DATA,__objc_data \n" + ".align 3 \n" + "_OBJC_CLASS_$_Super: \n" + PTR "_OBJC_METACLASS_$_Super \n" + PTR "0 \n" + PTR "__objc_empty_cache \n" + PTR "0 \n" + PTR "L_ro \n" + // pad to OBJC_MAX_CLASS_SIZE + PTR "0 \n" + PTR "0 \n" + PTR "0 \n" + PTR "0 \n" + PTR "0 \n" + PTR "0 \n" + PTR "0 \n" + PTR "0 \n" + PTR "0 \n" + PTR "0 \n" + PTR "0 \n" + PTR "0 \n" + PTR "0 \n" + PTR "0 \n" + PTR "0 \n" + PTR "0 \n" + PTR "0 \n" + PTR "0 \n" + PTR "0 \n" + PTR "0 \n" + PTR "0 \n" + PTR "0 \n" + PTR "0 \n" + PTR "0 \n" + PTR "0 \n" + PTR "0 \n" + PTR "0 \n" + "" + "_OBJC_METACLASS_$_Super: \n" + PTR "_OBJC_METACLASS_$_Super \n" + PTR "_OBJC_CLASS_$_Super \n" + PTR "__objc_empty_cache \n" + PTR "0 \n" + PTR "L_meta_ro \n" + // pad to OBJC_MAX_CLASS_SIZE + PTR "0 \n" + PTR "0 \n" + PTR "0 \n" + PTR "0 \n" + PTR "0 \n" + PTR "0 \n" + PTR "0 \n" + PTR "0 \n" + PTR "0 \n" + PTR "0 \n" + PTR "0 \n" + PTR "0 \n" + PTR "0 \n" + PTR "0 \n" + PTR "0 \n" + PTR "0 \n" + PTR "0 \n" + PTR "0 \n" + PTR "0 \n" + PTR "0 \n" + PTR "0 \n" + PTR "0 \n" + PTR "0 \n" + PTR "0 \n" + PTR "0 \n" + PTR "0 \n" + PTR "0 \n" + "" + "L_ro: \n" + ".long 2 \n" + ".long 0 \n" + ".long "PTRSIZE" \n" +#if __LP64__ + ".long 0 \n" +#endif + PTR "0 \n" + PTR "L_super_name \n" +#if EVIL_SUPER + PTR "L_evil_methods \n" +#else + PTR "L_good_methods \n" +#endif + PTR "0 \n" + PTR "L_super_ivars \n" + PTR "0 \n" + PTR "0 \n" + "" + "L_meta_ro: \n" + ".long 3 \n" + ".long 40 \n" + ".long 40 \n" +#if __LP64__ + ".long 0 \n" +#endif + PTR "0 \n" + PTR "L_super_name \n" +#if EVIL_SUPER_META + PTR "L_evil_methods \n" +#else + PTR "L_good_methods \n" +#endif + PTR "0 \n" + PTR "0 \n" + PTR "0 \n" + PTR "0 \n" + + ".globl _OBJC_CLASS_$_Sub \n" + ".section __DATA,__objc_data \n" + ".align 3 \n" + "_OBJC_CLASS_$_Sub: \n" + PTR "_OBJC_METACLASS_$_Sub \n" + PTR "_OBJC_CLASS_$_Super \n" + PTR "__objc_empty_cache \n" + PTR "0 \n" + PTR "L_sub_ro \n" + // pad to OBJC_MAX_CLASS_SIZE + PTR "0 \n" + PTR "0 \n" + PTR "0 \n" + PTR "0 \n" + PTR "0 \n" + PTR "0 \n" + PTR "0 \n" + PTR "0 \n" + PTR "0 \n" + PTR "0 \n" + PTR "0 \n" + PTR "0 \n" + PTR "0 \n" + PTR "0 \n" + PTR "0 \n" + PTR "0 \n" + PTR "0 \n" + PTR "0 \n" + PTR "0 \n" + PTR "0 \n" + PTR "0 \n" + PTR "0 \n" + PTR "0 \n" + PTR "0 \n" + PTR "0 \n" + PTR "0 \n" + PTR "0 \n" + "" + "_OBJC_METACLASS_$_Sub: \n" + PTR "_OBJC_METACLASS_$_Super \n" + PTR "_OBJC_METACLASS_$_Super \n" + PTR "__objc_empty_cache \n" + PTR "0 \n" + PTR "L_sub_meta_ro \n" + // pad to OBJC_MAX_CLASS_SIZE + PTR "0 \n" + PTR "0 \n" + PTR "0 \n" + PTR "0 \n" + PTR "0 \n" + PTR "0 \n" + PTR "0 \n" + PTR "0 \n" + PTR "0 \n" + PTR "0 \n" + PTR "0 \n" + PTR "0 \n" + PTR "0 \n" + PTR "0 \n" + PTR "0 \n" + PTR "0 \n" + PTR "0 \n" + PTR "0 \n" + PTR "0 \n" + PTR "0 \n" + PTR "0 \n" + PTR "0 \n" + PTR "0 \n" + PTR "0 \n" + PTR "0 \n" + PTR "0 \n" + PTR "0 \n" + "" + "L_sub_ro: \n" + ".long 2 \n" + ".long 0 \n" + ".long "PTRSIZE" \n" +#if __LP64__ + ".long 0 \n" +#endif + PTR "0 \n" + PTR "L_sub_name \n" +#if EVIL_SUB + PTR "L_evil_methods \n" +#else + PTR "L_good_methods \n" +#endif + PTR "0 \n" + PTR "L_sub_ivars \n" + PTR "0 \n" + PTR "0 \n" + "" + "L_sub_meta_ro: \n" + ".long 3 \n" + ".long 40 \n" + ".long 40 \n" +#if __LP64__ + ".long 0 \n" +#endif + PTR "0 \n" + PTR "L_sub_name \n" +#if EVIL_SUB_META + PTR "L_evil_methods \n" +#else + PTR "L_good_methods \n" +#endif + PTR "0 \n" + PTR "0 \n" + PTR "0 \n" + PTR "0 \n" + + "L_evil_methods: \n" + ".long 3*"PTRSIZE" \n" + ".long 1 \n" + PTR "L_load \n" + PTR "L_load \n" + PTR "_abort" SIGNED_METHOD_LIST_IMP "\n" + // assumes that abort is inside the dyld shared cache + + "L_good_methods: \n" + ".long 3*"PTRSIZE" \n" + ".long 2 \n" + PTR "L_load \n" + PTR "L_load \n" + PTR "_nop" SIGNED_METHOD_LIST_IMP "\n" + PTR "L_self \n" + PTR "L_self \n" + PTR "_nop" SIGNED_METHOD_LIST_IMP "\n" + + "L_super_ivars: \n" + ".long 4*"PTRSIZE" \n" + ".long 1 \n" + PTR "L_super_ivar_offset \n" + PTR "L_super_ivar_name \n" + PTR "L_super_ivar_type \n" + ".long "LOGPTRSIZE" \n" + ".long "PTRSIZE" \n" + + "L_sub_ivars: \n" + ".long 4*"PTRSIZE" \n" + ".long 1 \n" + PTR "L_sub_ivar_offset \n" + PTR "L_sub_ivar_name \n" + PTR "L_sub_ivar_type \n" + ".long "LOGPTRSIZE" \n" + ".long "PTRSIZE" \n" + + "L_super_ivar_offset: \n" + ".long 0 \n" + "L_sub_ivar_offset: \n" + ".long "PTRSIZE" \n" + + ".cstring \n" + "L_super_name: .ascii \"Super\\0\" \n" + "L_sub_name: .ascii \"Sub\\0\" \n" + "L_load: .ascii \"load\\0\" \n" + "L_self: .ascii \"self\\0\" \n" + "L_super_ivar_name: .ascii \"super_ivar\\0\" \n" + "L_super_ivar_type: .ascii \"c\\0\" \n" + "L_sub_ivar_name: .ascii \"sub_ivar\\0\" \n" + "L_sub_ivar_type: .ascii \"@\\0\" \n" + + + ".section __DATA,__objc_classlist \n" +#if !OMIT_SUPER + PTR "_OBJC_CLASS_$_Super \n" +#endif +#if !OMIT_SUB + PTR "_OBJC_CLASS_$_Sub \n" +#endif + + ".section __DATA,__objc_nlclslist \n" +#if !OMIT_NL_SUPER + PTR "_OBJC_CLASS_$_Super \n" +#endif +#if !OMIT_NL_SUB + PTR "_OBJC_CLASS_$_Sub \n" +#endif + + ".text \n" +); + +void fn(void) { } diff --git a/test/evil-main.m b/test/evil-main.m new file mode 100644 index 0000000..1e17fb8 --- /dev/null +++ b/test/evil-main.m @@ -0,0 +1,15 @@ +#include "test.h" + +extern void fn(void); + +int main(int argc __unused, char **argv) +{ + fn(); + +#if TARGET_OS_IPHONE && !TARGET_OS_SIMULATOR && !defined(NOT_EVIL) +#pragma unused (argv) + fail("All that is necessary for the triumph of evil is that good men do nothing."); +#else + succeed(basename(argv[0])); +#endif +} diff --git a/test/exc.m b/test/exc.m new file mode 100644 index 0000000..8c7435a --- /dev/null +++ b/test/exc.m @@ -0,0 +1,879 @@ +/* +need exception-safe ARC for exception deallocation tests +TEST_CFLAGS -fobjc-arc-exceptions -framework Foundation +*/ + +#include "test.h" +#include "testroot.i" +#include +#include + +static volatile int state = 0; +static volatile int dealloced = 0; +#define BAD 1000000 + +#if defined(USE_FOUNDATION) + +#include + +@interface Super : NSException @end +@implementation Super ++(id)exception { return AUTORELEASE([[self alloc] initWithName:@"Super" reason:@"reason" userInfo:nil]); } +-(void)check { state++; } ++(void)check { testassert(!"caught class object, not instance"); } +-(void)dealloc { dealloced++; SUPER_DEALLOC(); } +@end + +#define FILENAME "nsexc.m" + +#else + +@interface Super : TestRoot @end +@implementation Super ++(id)exception { return AUTORELEASE([self new]); } +-(void)check { state++; } ++(void)check { testassert(!"caught class object, not instance"); } +-(void)dealloc { dealloced++; SUPER_DEALLOC(); } +@end + +#define FILENAME "exc.m" + +#endif + +@interface Sub : Super @end +@implementation Sub +@end + + +#if TARGET_OS_OSX +void altHandlerFail(id unused __unused, void *context __unused) +{ + fail("altHandlerFail called"); +} + +#define ALT_HANDLER(n) \ + void altHandler##n(id unused __unused, void *context) \ + { \ + testassert(context == (void*)&altHandler##n); \ + testassert(state == n); \ + state++; \ + } + +ALT_HANDLER(1) +ALT_HANDLER(2) +ALT_HANDLER(3) +ALT_HANDLER(4) +ALT_HANDLER(5) +ALT_HANDLER(6) +ALT_HANDLER(7) + + +static void throwWithAltHandler(void) __attribute__((noinline, used)); +static void throwWithAltHandler(void) +{ + @try { + state++; + uintptr_t token = objc_addExceptionHandler(altHandler3, (void*)altHandler3); + // state++ inside alt handler + @throw [Super exception]; + state = BAD; + objc_removeExceptionHandler(token); + } + @catch (Sub *e) { + state = BAD; + } + state = BAD; +} + + +static void throwWithAltHandlerAndRethrow(void) __attribute__((noinline, used)); +static void throwWithAltHandlerAndRethrow(void) +{ + @try { + state++; + uintptr_t token = objc_addExceptionHandler(altHandler3, (void*)altHandler3); + // state++ inside alt handler + @throw [Super exception]; + state = BAD; + objc_removeExceptionHandler(token); + } + @catch (...) { + testassert(state == 4); + state++; + @throw; + } + state = BAD; +} + +#endif + +#if __cplusplus +#include +void terminator() { + succeed(FILENAME); +} +#endif + + +#define TEST(code) \ + do { \ + testonthread(^{ PUSH_POOL { code } POP_POOL; }); \ + testcollect(); \ + } while (0) + + + +int main() +{ + testprintf("try-catch-finally, exception caught exactly\n"); + + TEST({ + state = 0; + dealloced = 0; + @try { + state++; + @try { + state++; + @throw [Super exception]; + state = BAD; + } + @catch (Super *e) { + state++; + [e check]; // state++ + } + @finally { + state++; + } + state++; + } + @catch (...) { + state = BAD; + } + }); + testassert(state == 6); + testassert(dealloced == 1); + + + testprintf("try-finally, no exception thrown\n"); + + TEST({ + state = 0; + dealloced = 0; + @try { + state++; + @try { + state++; + } + @finally { + state++; + } + state++; + } + @catch (...) { + state = BAD; + } + }); + testassert(state == 4); + testassert(dealloced == 0); + + + testprintf("try-finally, with exception\n"); + + TEST({ + state = 0; + dealloced = 0; + @try { + state++; + @try { + state++; + @throw [Super exception]; + state = BAD; + } + @finally { + state++; + } + state = BAD; + } + @catch (id e) { + state++; + [e check]; // state++ + } + }); + testassert(state == 5); + testassert(dealloced == 1); + + + testprintf("try-finally, with autorelease pool pop during unwind\n"); + // Popping an autorelease pool during unwind used to deallocate the + // exception object, but now we retain them while in flight. + + // This use-after-free is undetected without MallocScribble or guardmalloc. + if (!getenv("MallocScribble") && + (!getenv("DYLD_INSERT_LIBRARIES") || + !strstr(getenv("DYLD_INSERT_LIBRARIES"), "libgmalloc"))) + { + testwarn("MallocScribble not set"); + } + + TEST({ + state = 0; + dealloced = 0; + @try { + void *pool2 = objc_autoreleasePoolPush(); + state++; + @try { + state++; + @throw [Super exception]; + state = BAD; + } + @finally { + state++; + objc_autoreleasePoolPop(pool2); + } + state = BAD; + } + @catch (id e) { + state++; + [e check]; // state++ + } + }); + testassert(state == 5); + testassert(dealloced == 1); + + + testprintf("try-catch-finally, no exception\n"); + + TEST({ + state = 0; + dealloced = 0; + @try { + state++; + @try { + state++; + } + @catch (...) { + state = BAD; + } + @finally { + state++; + } + state++; + } @catch (...) { + state = BAD; + } + }); + testassert(state == 4); + testassert(dealloced == 0); + + + testprintf("try-catch-finally, exception not caught\n"); + + TEST({ + state = 0; + dealloced = 0; + @try { + state++; + @try { + state++; + @throw [Super exception]; + state = BAD; + } + @catch (Sub *e) { + state = BAD; + } + @finally { + state++; + } + state = BAD; + } + @catch (id e) { + state++; + [e check]; // state++ + } + }); + testassert(state == 5); + testassert(dealloced == 1); + + + testprintf("try-catch-finally, exception caught exactly, rethrown\n"); + + TEST({ + state = 0; + dealloced = 0; + @try { + state++; + @try { + state++; + @throw [Super exception]; + state = BAD; + } + @catch (Super *e) { + state++; + [e check]; // state++ + @throw; + state = BAD; + } + @finally { + state++; + } + state = BAD; + } + @catch (id e) { + state++; + [e check]; // state++ + } + }); + testassert(state == 7); + testassert(dealloced == 1); + + + testprintf("try-catch, no exception\n"); + + TEST({ + state = 0; + dealloced = 0; + @try { + state++; + @try { + state++; + } + @catch (...) { + state = BAD; + } + state++; + } @catch (...) { + state = BAD; + } + }); + testassert(state == 3); + testassert(dealloced == 0); + + + testprintf("try-catch, exception not caught\n"); + + TEST({ + state = 0; + dealloced = 0; + @try { + state++; + @try { + state++; + @throw [Super exception]; + state = BAD; + } + @catch (Sub *e) { + state = BAD; + } + state = BAD; + } + @catch (id e) { + state++; + [e check]; // state++ + } + }); + testassert(state == 4); + testassert(dealloced == 1); + + + testprintf("try-catch, exception caught exactly\n"); + + TEST({ + state = 0; + dealloced = 0; + @try { + state++; + @try { + state++; + @throw [Super exception]; + state = BAD; + } + @catch (Super *e) { + state++; + [e check]; // state++ + } + state++; + } + @catch (...) { + state = BAD; + } + }); + testassert(state == 5); + testassert(dealloced == 1); + + + testprintf("try-catch, exception caught exactly, rethrown\n"); + + TEST({ + state = 0; + dealloced = 0; + @try { + state++; + @try { + state++; + @throw [Super exception]; + state = BAD; + } + @catch (Super *e) { + state++; + [e check]; // state++ + @throw; + state = BAD; + } + state = BAD; + } + @catch (id e) { + state++; + [e check]; // state++ + } + }); + testassert(state == 6); + testassert(dealloced == 1); + + + testprintf("try-catch, exception caught exactly, thrown again explicitly\n"); + + TEST({ + state = 0; + dealloced = 0; + @try { + state++; + @try { + state++; + @throw [Super exception]; + state = BAD; + } + @catch (Super *e) { + state++; + [e check]; // state++ + @throw e; + state = BAD; + } + state = BAD; + } + @catch (id e) { + state++; + [e check]; // state++ + } + }); + testassert(state == 6); + testassert(dealloced == 1); + + + testprintf("try-catch, default catch, rethrown\n"); + + TEST({ + state = 0; + dealloced = 0; + @try { + state++; + @try { + state++; + @throw [Super exception]; + state = BAD; + } + @catch (...) { + state++; + @throw; + state = BAD; + } + state = BAD; + } + @catch (id e) { + state++; + [e check]; // state++ + } + }); + testassert(state == 5); + testassert(dealloced == 1); + + + testprintf("try-catch, default catch, rethrown and caught inside nested handler\n"); + + TEST({ + state = 0; + dealloced = 0; + @try { + state++; + @try { + state++; + @throw [Super exception]; + state = BAD; + } + @catch (...) { + state++; + + @try { + state++; + @throw; + state = BAD; + } @catch (Sub *e) { + state = BAD; + } @catch (Super *e) { + state++; + [e check]; // state++ + } @catch (...) { + state = BAD; + } @finally { + state++; + } + + state++; + } + state++; + } + @catch (...) { + state = BAD; + } + }); + testassert(state == 9); + testassert(dealloced == 1); + + + testprintf("try-catch, default catch, rethrown inside nested handler but not caught\n"); + + TEST({ + state = 0; + dealloced = 0; + @try { + state++; + @try { + state++; + @throw [Super exception]; + state = BAD; + } + @catch (...) { + state++; + + @try { + state++; + @throw; + state = BAD; + } + @catch (Sub *e) { + state = BAD; + } + @finally { + state++; + } + + state = BAD; + } + state = BAD; + } + @catch (id e) { + state++; + [e check]; // state++ + } + }); + testassert(state == 7); + testassert(dealloced == 1); + + +#if __cplusplus + testprintf("C++ try/catch, Objective-C exception superclass\n"); + + TEST({ + state = 0; + dealloced = 0; + try { + state++; + try { + state++; + try { + state++; + @throw [Super exception]; + state = BAD; + } catch (...) { + state++; + throw; + state = BAD; + } + state = BAD; + } catch (void *e) { + state = BAD; + } catch (int e) { + state = BAD; + } catch (Sub *e) { + state = BAD; + } catch (Super *e) { + state++; + [e check]; // state++ + throw; + } catch (...) { + state = BAD; + } + } catch (id e) { + state++; + [e check]; // state++; + } + }); + testassert(state == 8); + testassert(dealloced == 1); + + + testprintf("C++ try/catch, Objective-C exception subclass\n"); + + TEST({ + state = 0; + dealloced = 0; + try { + state++; + try { + state++; + try { + state++; + @throw [Sub exception]; + state = BAD; + } catch (...) { + state++; + throw; + state = BAD; + } + state = BAD; + } catch (void *e) { + state = BAD; + } catch (int e) { + state = BAD; + } catch (Super *e) { + state++; + [e check]; // state++ + throw; + } catch (Sub *e) { + state = BAD; + } catch (...) { + state = BAD; + } + } catch (id e) { + state++; + [e check]; // state++; + } + }); + testassert(state == 8); + testassert(dealloced == 1); + +#endif + + +#if !TARGET_OS_OSX + // alt handlers are for macOS only +#else + { + // alt handlers + // run a lot to catch failed unregistration (runtime complains at 1000) +#define ALT_HANDLER_REPEAT 2000 + + testprintf("alt handler, no exception\n"); + + TEST({ + dealloced = 0; + for (int i = 0; i < ALT_HANDLER_REPEAT; i++) { + state = 0; + @try { + state++; + @try { + uintptr_t token = objc_addExceptionHandler(altHandlerFail, 0); + state++; + objc_removeExceptionHandler(token); + } + @catch (...) { + state = BAD; + } + state++; + } @catch (...) { + state = BAD; + } + testassert(state == 3); + } + }); + testassert(dealloced == 0); + + + testprintf("alt handler, exception thrown through\n"); + + TEST({ + dealloced = 0; + for (int i = 0; i < ALT_HANDLER_REPEAT; i++) { + state = 0; + @try { + state++; + @try { + state++; + uintptr_t token = objc_addExceptionHandler(altHandler2, (void*)altHandler2); + // state++ inside alt handler + @throw [Super exception]; + state = BAD; + objc_removeExceptionHandler(token); + } + @catch (Sub *e) { + state = BAD; + } + state = BAD; + } + @catch (id e) { + testassert(state == 3); + state++; + [e check]; // state++ + } + testassert(state == 5); + } + }); + testassert(dealloced == ALT_HANDLER_REPEAT); + + + testprintf("alt handler, nested\n"); +#if 1 + testwarn("fixme compiler no longer cooperative for local nested?"); + // Nested alt handlers inside the same function require that each + // catch group have its own landing pad descriptor. The compiler is + // probably not doing that anymore. For now we assume that the + // combination of nested exception handlers and alt handlers is + // rare enough that nobody cares. +#else + TEST({ + dealloced = 0; + for (int i = 0; i < ALT_HANDLER_REPEAT; i++) { + state = 0; + @try { + state++; + @try { + state++; + // same-level handlers called in FIFO order (not stack-like) + uintptr_t token = objc_addExceptionHandler(altHandler4, (void*)altHandler4); + // state++ inside alt handler + uintptr_t token2 = objc_addExceptionHandler(altHandler5, (void*)altHandler5); + // state++ inside alt handler + throwWithAltHandler(); // state += 2 inside + state = BAD; + objc_removeExceptionHandler(token); + objc_removeExceptionHandler(token2); + } + @catch (id e) { + testassert(state == 6); + state++; + [e check]; // state++; + } + state++; + } + @catch (...) { + state = BAD; + } + testassert(state == 9); + } + }); + testassert(dealloced == ALT_HANDLER_REPEAT); +#endif + + testprintf("alt handler, nested, rethrows in between\n"); +#if 1 + testwarn("fixme compiler no longer cooperative for local nested?"); + // See above. +#else + TEST({ + dealloced = 0; + for (int i = 0; i < ALT_HANDLER_REPEAT; i++) { + state = 0; + @try { + state++; + @try { + state++; + // same-level handlers called in FIFO order (not stack-like) + uintptr_t token = objc_addExceptionHandler(altHandler5, (void*)altHandler5); + // state++ inside alt handler + uintptr_t token2 = objc_addExceptionHandler(altHandler6, (void*)altHandler6); + // state++ inside alt handler + throwWithAltHandlerAndRethrow(); // state += 3 inside + state = BAD; + objc_removeExceptionHandler(token); + objc_removeExceptionHandler(token2); + } + @catch (...) { + testassert(state == 7); + state++; + @throw; + } + state = BAD; + } + @catch (id e) { + testassert(state == 8); + state++; + [e check]; // state++ + } + testassert(state == 10); + } + }); + testassert(dealloced == ALT_HANDLER_REPEAT); +#endif + + testprintf("alt handler, exception thrown and caught inside\n"); + + TEST({ + dealloced = 0; + for (int i = 0; i < ALT_HANDLER_REPEAT; i++) { + state = 0; + @try { + state++; + uintptr_t token = objc_addExceptionHandler(altHandlerFail, 0); + @try { + state++; + @throw [Super exception]; + state = BAD; + } + @catch (Super *e) { + state++; + [e check]; // state++ + } + state++; + objc_removeExceptionHandler(token); + } + @catch (...) { + state = BAD; + } + testassert(state == 5); + } + }); + testassert(dealloced == ALT_HANDLER_REPEAT); + + +#if defined(USE_FOUNDATION) + testprintf("alt handler, rdar://10055775\n"); + + TEST({ + dealloced = 0; + for (int i = 0; i < ALT_HANDLER_REPEAT; i++) { + state = 0; + @try { + uintptr_t token = objc_addExceptionHandler(altHandler1, (void*)altHandler1); + { + id x = [NSArray array]; + x = [NSArray array]; + } + state++; + // state++ inside alt handler + [Super raise:@"foo" format:@"bar"]; + state = BAD; + objc_removeExceptionHandler(token); + } @catch (id e) { + state++; + testassert(state == 3); + } + testassert(state == 3); + } + }); + testassert(dealloced == ALT_HANDLER_REPEAT); + +// defined(USE_FOUNDATION) +#endif + + } +// alt handlers +#endif + +#if __cplusplus + std::set_terminate(terminator); + objc_terminate(); + fail("should not have returned from objc_terminate()"); +#else + succeed(FILENAME); +#endif +} + diff --git a/test/exchangeImp.m b/test/exchangeImp.m new file mode 100644 index 0000000..da84f94 --- /dev/null +++ b/test/exchangeImp.m @@ -0,0 +1,106 @@ +/* +TEST_BUILD_OUTPUT +.*exchangeImp.m:\d+:\d+: warning: null passed to a callee that requires a non-null argument \[-Wnonnull\](\n.* note: expanded from macro 'checkExchange')? +.*exchangeImp.m:\d+:\d+: warning: null passed to a callee that requires a non-null argument \[-Wnonnull\](\n.* note: expanded from macro 'checkExchange')? +.*exchangeImp.m:\d+:\d+: warning: null passed to a callee that requires a non-null argument \[-Wnonnull\](\n.* note: expanded from macro 'checkExchange')? +.*exchangeImp.m:\d+:\d+: warning: null passed to a callee that requires a non-null argument \[-Wnonnull\](\n.* note: expanded from macro 'checkExchange')? +.*exchangeImp.m:\d+:\d+: warning: null passed to a callee that requires a non-null argument \[-Wnonnull\](\n.* note: expanded from macro 'checkExchange')? +.*exchangeImp.m:\d+:\d+: warning: null passed to a callee that requires a non-null argument \[-Wnonnull\](\n.* note: expanded from macro 'checkExchange')? +.*exchangeImp.m:\d+:\d+: warning: null passed to a callee that requires a non-null argument \[-Wnonnull\](\n.* note: expanded from macro 'checkExchange')? +.*exchangeImp.m:\d+:\d+: warning: null passed to a callee that requires a non-null argument \[-Wnonnull\](\n.* note: expanded from macro 'checkExchange')? +.*exchangeImp.m:\d+:\d+: warning: null passed to a callee that requires a non-null argument \[-Wnonnull\](\n.* note: expanded from macro 'checkExchange')? +.*exchangeImp.m:\d+:\d+: warning: null passed to a callee that requires a non-null argument \[-Wnonnull\](\n.* note: expanded from macro 'checkExchange')? +.*exchangeImp.m:\d+:\d+: warning: null passed to a callee that requires a non-null argument \[-Wnonnull\](\n.* note: expanded from macro 'checkExchange')? +.*exchangeImp.m:\d+:\d+: warning: null passed to a callee that requires a non-null argument \[-Wnonnull\](\n.* note: expanded from macro 'checkExchange')? +.*exchangeImp.m:\d+:\d+: warning: null passed to a callee that requires a non-null argument \[-Wnonnull\](\n.* note: expanded from macro 'checkExchange')? +.*exchangeImp.m:\d+:\d+: warning: null passed to a callee that requires a non-null argument \[-Wnonnull\](\n.* note: expanded from macro 'checkExchange')? +.*exchangeImp.m:\d+:\d+: warning: null passed to a callee that requires a non-null argument \[-Wnonnull\](\n.* note: expanded from macro 'checkExchange')? +.*exchangeImp.m:\d+:\d+: warning: null passed to a callee that requires a non-null argument \[-Wnonnull\](\n.* note: expanded from macro 'checkExchange')? +END +*/ + +#include "test.h" +#include "testroot.i" +#include + +static int state; + +#define ONE 1 +#define TWO 2 +#define LENGTH 3 +#define COUNT 4 + +@interface Super : TestRoot @end +@implementation Super ++(void) one { state = ONE; } ++(void) two { state = TWO; } ++(void) length { state = LENGTH; } ++(void) count { state = COUNT; } +@end + +#define checkExchange(s1, v1, s2, v2) \ + do { \ + Method m1, m2; \ + \ + testprintf("Check unexchanged version\n"); \ + state = 0; \ + [Super s1]; \ + testassert(state == v1); \ + state = 0; \ + [Super s2]; \ + testassert(state == v2); \ + \ + testprintf("Exchange\n"); \ + m1 = class_getClassMethod([Super class], @selector(s1)); \ + m2 = class_getClassMethod([Super class], @selector(s2)); \ + testassert(m1); \ + testassert(m2); \ + method_exchangeImplementations(m1, m2); \ + \ + testprintf("Check exchanged version\n"); \ + state = 0; \ + [Super s1]; \ + testassert(state == v2); \ + state = 0; \ + [Super s2]; \ + testassert(state == v1); \ + \ + testprintf("NULL should do nothing\n"); \ + method_exchangeImplementations(m1, NULL); \ + method_exchangeImplementations(NULL, m2); \ + method_exchangeImplementations(NULL, NULL); \ + \ + testprintf("Make sure NULL did nothing\n"); \ + state = 0; \ + [Super s1]; \ + testassert(state == v2); \ + state = 0; \ + [Super s2]; \ + testassert(state == v1); \ + \ + testprintf("Put them back\n"); \ + method_exchangeImplementations(m1, m2); \ + \ + testprintf("Check restored version\n"); \ + state = 0; \ + [Super s1]; \ + testassert(state == v1); \ + state = 0; \ + [Super s2]; \ + testassert(state == v2); \ + } while (0) + +int main() +{ + // Check ordinary selectors + checkExchange(one, ONE, two, TWO); + + // Check vtable selectors + checkExchange(length, LENGTH, count, COUNT); + + // Check ordinary<->vtable and vtable<->ordinary + checkExchange(count, COUNT, one, ONE); + checkExchange(two, TWO, length, LENGTH); + + succeed(__FILE__); +} diff --git a/test/foreach.m b/test/foreach.m new file mode 100644 index 0000000..4386db3 --- /dev/null +++ b/test/foreach.m @@ -0,0 +1,227 @@ +// TEST_CFLAGS -framework Foundation + +#include "test.h" +#import + +/* foreach tester */ + +int Errors = 0; + +bool testHandwritten(const char *style, const char *test, const char *message, id collection, NSSet *reference) { + unsigned int counter = 0; + bool result = true; + testprintf("testing: %s %s %s\n", style, test, message); +/* + for (id elem in collection) + if ([reference member:elem]) ++counter; + */ + NSFastEnumerationState state; + id __unsafe_unretained buffer[4]; + state.state = 0; + NSUInteger limit = [collection countByEnumeratingWithState:&state objects:buffer count:4]; + if (limit != 0) { + unsigned long mutationsPtr = *state.mutationsPtr; + do { + unsigned long innerCounter = 0; + do { + if (mutationsPtr != *state.mutationsPtr) objc_enumerationMutation(collection); + id elem = state.itemsPtr[innerCounter++]; + + if ([reference member:elem]) ++counter; + + } while (innerCounter < limit); + } while ((limit = [collection countByEnumeratingWithState:&state objects:buffer count:4])); + } + + + + if (counter == [reference count]) { + testprintf("success: %s %s %s\n", style, test, message); + } + else { + result = false; + printf("** failed: %s %s %s (%d vs %d)\n", style, test, message, counter, (int)[reference count]); + ++Errors; + } + return result; +} + +bool testCompiler(const char *style, const char *test, const char *message, id collection, NSSet *reference) { + unsigned int counter = 0; + bool result = true; + testprintf("testing: %s %s %s\n", style, test, message); + for (id elem in collection) + if ([reference member:elem]) ++counter; + if (counter == [reference count]) { + testprintf("success: %s %s %s\n", style, test, message); + } + else { + result = false; + printf("** failed: %s %s %s (%d vs %d)\n", style, test, message, counter, (int)[reference count]); + ++Errors; + } + return result; +} + +void testContinue(NSArray *array) { + bool broken = false; + testprintf("testing: continue statements\n"); + for (id __unused elem in array) { + if ([array count]) + continue; + broken = true; + } + if (broken) { + printf("** continue statement did not work\n"); + ++Errors; + } +} + + +// array is filled with NSNumbers, in order, from 0 - N +bool testBreak(unsigned int where, NSArray *array) { + PUSH_POOL { + unsigned int counter = 0; + id enumerator = [array objectEnumerator]; + for (id __unused elem in enumerator) { + if (++counter == where) + break; + } + if (counter != where) { + ++Errors; + printf("*** break at %d didn't work (actual was %d)\n", where, counter); + return false; + } + for (id __unused elem in enumerator) + ++counter; + if (counter != [array count]) { + ++Errors; + printf("*** break at %d didn't finish (actual was %d)\n", where, counter); + return false; + } + } POP_POOL; + return true; +} + +bool testBreaks(NSArray *array) { + bool result = true; + testprintf("testing breaks\n"); + unsigned int counter = 0; + for (counter = 1; counter < [array count]; ++counter) { + result = testBreak(counter, array) && result; + } + return result; +} + +bool testCompleteness(const char *test, const char *message, id collection, NSSet *reference) { + bool result = true; + result = result && testHandwritten("handwritten", test, message, collection, reference); + result = result && testCompiler("compiler", test, message, collection, reference); + return result; +} + +bool testEnumerator(const char *test, const char *message, id collection, NSSet *reference) { + bool result = true; + result = result && testHandwritten("handwritten", test, message, [collection objectEnumerator], reference); + result = result && testCompiler("compiler", test, message, [collection objectEnumerator], reference); + return result; +} + +NSMutableSet *ReferenceSet = nil; +NSMutableArray *ReferenceArray = nil; + +void makeReferences(int n) { + if (!ReferenceSet) { + int i; + ReferenceSet = [[NSMutableSet alloc] init]; + ReferenceArray = [[NSMutableArray alloc] init]; + for (i = 0; i < n; ++i) { + NSNumber *number = [[NSNumber alloc] initWithInt:i]; + [ReferenceSet addObject:number]; + [ReferenceArray addObject:number]; + RELEASE_VAR(number); + } + } +} + +void testCollections(const char *test, NSArray *array, NSSet *set) { + PUSH_POOL { + id collection; + collection = [NSMutableArray arrayWithArray:array]; + testCompleteness(test, "mutable array", collection, set); + testEnumerator(test, "mutable array enumerator", collection, set); + collection = [NSArray arrayWithArray:array]; + testCompleteness(test, "immutable array", collection, set); + testEnumerator(test, "immutable array enumerator", collection, set); + collection = set; + testCompleteness(test, "immutable set", collection, set); + testEnumerator(test, "immutable set enumerator", collection, set); + collection = [NSMutableSet setWithArray:array]; + testCompleteness(test, "mutable set", collection, set); + testEnumerator(test, "mutable set enumerator", collection, set); + } POP_POOL; +} + +void testInnerDecl(const char *test, const char *message, id collection) { + unsigned int counter = 0; + for (id __unused x in collection) + ++counter; + if (counter != [collection count]) { + printf("** failed: %s %s\n", test, message); + ++Errors; + } +} + + +void testOuterDecl(const char *test, const char *message, id collection) { + unsigned int counter = 0; + id x; + for (x in collection) + ++counter; + if (counter != [collection count]) { + printf("** failed: %s %s\n", test, message); + ++Errors; + } +} +void testInnerExpression(const char *test, const char *message, id collection) { + unsigned int counter = 0; + for (id __unused x in [collection self]) + ++counter; + if (counter != [collection count]) { + printf("** failed: %s %s\n", test, message); + ++Errors; + } +} +void testOuterExpression(const char *test, const char *message, id collection) { + unsigned int counter = 0; + id x; + for (x in [collection self]) + ++counter; + if (counter != [collection count]) { + printf("** failed: %s %s\n", test, message); + ++Errors; + } +} + +void testExpressions(const char *message, id collection) { + testInnerDecl("inner", message, collection); + testOuterDecl("outer", message, collection); + testInnerExpression("outer expression", message, collection); + testOuterExpression("outer expression", message, collection); +} + + +int main() { + PUSH_POOL { + testCollections("nil", nil, nil); + testCollections("empty", [NSArray array], [NSSet set]); + makeReferences(100); + testCollections("100 item", ReferenceArray, ReferenceSet); + testExpressions("array", ReferenceArray); + testBreaks(ReferenceArray); + testContinue(ReferenceArray); + if (Errors == 0) succeed(__FILE__); + else fail("foreach %d errors detected\n", Errors); + } POP_POOL; + exit(Errors); +} diff --git a/test/fork.m b/test/fork.m new file mode 100644 index 0000000..f3677df --- /dev/null +++ b/test/fork.m @@ -0,0 +1,54 @@ +// TEST_CONFIG + +#include "test.h" + +void *flushthread(void *arg __unused) +{ + while (1) { + _objc_flush_caches(nil); + } +} + +int main() +{ + pthread_t th; + pthread_create(&th, nil, &flushthread, nil); + + alarm(120); + + [NSObject self]; + [NSObject self]; + + int max = is_guardmalloc() ? 10: 100; + + for (int i = 0; i < max; i++) { + pid_t child; + switch ((child = fork())) { + case -1: + abort(); + case 0: + // child + alarm(10); + [NSObject self]; + _exit(0); + default: { + // parent + int result = 0; + while (waitpid(child, &result, 0) < 0) { + if (errno != EINTR) { + fail("waitpid failed (errno %d %s)", + errno, strerror(errno)); + } + } + if (!WIFEXITED(result)) { + fail("child crashed (waitpid result %d)", result); + } + + [NSObject self]; + break; + } + } + } + + succeed(__FILE__ " parent"); +} diff --git a/test/forkInitialize.m b/test/forkInitialize.m new file mode 100644 index 0000000..b9e9a3a --- /dev/null +++ b/test/forkInitialize.m @@ -0,0 +1,159 @@ +/* +TEST_CRASHES +TEST_RUN_OUTPUT +objc\[\d+\]: \+\[BlockingSub initialize\] may have been in progress in another thread when fork\(\) was called\. +objc\[\d+\]: \+\[BlockingSub initialize\] may have been in progress in another thread when fork\(\) was called\. We cannot safely call it or ignore it in the fork\(\) child process\. Crashing instead\. Set a breakpoint on objc_initializeAfterForkError to debug\. +objc\[\d+\]: HALTED +OK: forkInitialize\.m +END +*/ + +#include "test.h" + +static void *retain_fn(void *self, SEL _cmd __unused) { return self; } +static void release_fn(void *self __unused, SEL _cmd __unused) { } + +OBJC_ROOT_CLASS +@interface BlockingRootClass @end +@implementation BlockingRootClass ++(id)self { return self; } ++(void)initialize { + class_addMethod(self, sel_registerName("retain"), (IMP)retain_fn, ""); + class_addMethod(self, sel_registerName("release"), (IMP)release_fn, ""); + + if (self == [BlockingRootClass self]) { + while (1) sleep(1); + } +} +@end + +@interface BlockingRootSub : BlockingRootClass @end +@implementation BlockingRootSub +@end + +OBJC_ROOT_CLASS +@interface BlockingSubRoot @end +@implementation BlockingSubRoot ++(id)self { return self; } ++(void)initialize { + class_addMethod(self, sel_registerName("retain"), (IMP)retain_fn, ""); + class_addMethod(self, sel_registerName("release"), (IMP)release_fn, ""); +} +@end + +@interface BlockingSub : BlockingSubRoot @end +@implementation BlockingSub ++(void)initialize { + class_addMethod(self, sel_registerName("retain"), (IMP)retain_fn, ""); + class_addMethod(self, sel_registerName("release"), (IMP)release_fn, ""); + + while (1) sleep(1); +} +@end + +OBJC_ROOT_CLASS +@interface AnotherRootClass @end + +@interface BoringSub : AnotherRootClass @end +@implementation BoringSub +// can't implement +initialize here +@end + +@implementation AnotherRootClass + +void doFork() +{ + testprintf("FORK\n"); + + pid_t child; + switch((child = fork())) { + case -1: + fail("fork failed"); + case 0: + // child + // This one succeeds even though we're nested inside it's + // superclass's +initialize, because ordinary +initialize nesting + // still works across fork(). + // This falls in the isInitializing() case in _class_initialize. + [BoringSub self]; + +#if !SINGLETHREADED + // This one succeeds even though another thread is in its + // superclass's +initialize, because that superclass is a root class + // so we assume that +initialize is empty and therefore this one + // is safe to call. + // This falls in the reallyInitialize case in _class_initialize. + [BlockingRootSub self]; + + // This one aborts without deadlocking because it was in progress + // when fork() was called. + // This falls in the isInitializing() case in _class_initialize. + [BlockingSub self]; + + fail("should have crashed"); +#endif + break; + default: { + // parent + int result = 0; + while (waitpid(child, &result, 0) < 0) { + if (errno != EINTR) { + fail("waitpid failed (errno %d %s)", + errno, strerror(errno)); + } + } + if (!WIFEXITED(result)) { + fail("child crashed (waitpid result %d)", result); + } + break; + } + } +} + ++(void)initialize { + class_addMethod(self, sel_registerName("retain"), (IMP)retain_fn, ""); + class_addMethod(self, sel_registerName("release"), (IMP)release_fn, ""); + + if (self == [AnotherRootClass self]) { + static bool called = false; + if (!called) { + doFork(); + called = true; + } else { + fail("+[AnotherRootClass initialize] called again"); + } + } +} + ++(id)self { + return self; +} +@end + + +void *blocker(void *arg __unused) +{ + [BlockingSub self]; + return nil; +} + +void *blocker2(void *arg __unused) +{ + [BlockingRootClass self]; + return nil; +} + +int main() +{ +#if !SINGLETHREADED + pthread_t th; + pthread_create(&th, nil, blocker, nil); + pthread_detach(th); + pthread_create(&th, nil, blocker2, nil); + pthread_detach(th); + sleep(1); +#endif + + [AnotherRootClass self]; + succeed(__FILE__); +} diff --git a/test/forkInitializeDisabled.m b/test/forkInitializeDisabled.m new file mode 100644 index 0000000..747d1b8 --- /dev/null +++ b/test/forkInitializeDisabled.m @@ -0,0 +1,21 @@ +/* +TEST_CONFIG OS=macosx MEM=mrc ARCH=x86_64 +(confused by ARC which loads Foundation which provokes more +initialize logs) +(also confused by i386 OS_object +load workaround) + +TEST_ENV OBJC_PRINT_INITIALIZE_METHODS=YES + +TEST_RUN_OUTPUT +objc\[\d+\]: INITIALIZE: disabling \+initialize fork safety enforcement because the app has a __DATA,__objc_fork_ok section +OK: forkInitializeDisabled\.m +END +*/ + +#include "test.h" + +asm(".section __DATA, __objc_fork_ok\n.long 0\n"); + +int main() +{ + succeed(__FILE__); +} diff --git a/test/forkInitializeSingleThreaded.m b/test/forkInitializeSingleThreaded.m new file mode 100644 index 0000000..575c6f3 --- /dev/null +++ b/test/forkInitializeSingleThreaded.m @@ -0,0 +1,8 @@ +/* +TEST_RUN_OUTPUT +OK: forkInitialize\.m +OK: forkInitialize\.m +END +*/ +#define SINGLETHREADED 1 +#include "forkInitialize.m" diff --git a/test/forward.m b/test/forward.m new file mode 100644 index 0000000..517f5e2 --- /dev/null +++ b/test/forward.m @@ -0,0 +1,543 @@ +// TEST_CONFIG MEM=mrc + +#include "test.h" + +#include +#include + +id ID_RESULT = (id)0x12345678; +long long LL_RESULT = __LONG_LONG_MAX__ - 2LL*__INT_MAX__; +double FP_RESULT = __DBL_MIN__ + __DBL_EPSILON__; +long double LFP_RESULT = __LDBL_MIN__ + __LDBL_EPSILON__; +// STRET_RESULT in test.h + + +static int state = 0; +static id receiver; + +OBJC_ROOT_CLASS +@interface Super { id isa; } @end + +@interface Super (Forwarded) ++(id)idret: + (long)i1 :(long)i2 :(long)i3 :(long)i4 :(long)i5 :(long)i6 :(long)i7 :(long)i8 :(long)i9 :(long)i10 :(long)i11 :(long)i12 :(long)i13 :(double)f1 :(double)f2 :(double)f3 :(double)f4 :(double)f5 :(double)f6 :(double)f7 :(double)f8 :(double)f9 :(double)f10 :(double)f11 :(double)f12 :(double)f13 :(double)f14 :(double)f15; + ++(id)idre2: + (long)i1 :(long)i2 :(long)i3 :(long)i4 :(long)i5 :(long)i6 :(long)i7 :(long)i8 :(long)i9 :(long)i10 :(long)i11 :(long)i12 :(long)i13 :(double)f1 :(double)f2 :(double)f3 :(double)f4 :(double)f5 :(double)f6 :(double)f7 :(double)f8 :(double)f9 :(double)f10 :(double)f11 :(double)f12 :(double)f13 :(double)f14 :(double)f15; + ++(id)idre3: + (long)i1 :(long)i2 :(long)i3 :(long)i4 :(long)i5 :(long)i6 :(long)i7 :(long)i8 :(long)i9 :(long)i10 :(long)i11 :(long)i12 :(long)i13 :(double)f1 :(double)f2 :(double)f3 :(double)f4 :(double)f5 :(double)f6 :(double)f7 :(double)f8 :(double)f9 :(double)f10 :(double)f11 :(double)f12 :(double)f13 :(double)f14 :(double)f15; + ++(long long)llret: + (long)i1 :(long)i2 :(long)i3 :(long)i4 :(long)i5 :(long)i6 :(long)i7 :(long)i8 :(long)i9 :(long)i10 :(long)i11 :(long)i12 :(long)i13 :(double)f1 :(double)f2 :(double)f3 :(double)f4 :(double)f5 :(double)f6 :(double)f7 :(double)f8 :(double)f9 :(double)f10 :(double)f11 :(double)f12 :(double)f13 :(double)f14 :(double)f15; + ++(long long)llre2: + (long)i1 :(long)i2 :(long)i3 :(long)i4 :(long)i5 :(long)i6 :(long)i7 :(long)i8 :(long)i9 :(long)i10 :(long)i11 :(long)i12 :(long)i13 :(double)f1 :(double)f2 :(double)f3 :(double)f4 :(double)f5 :(double)f6 :(double)f7 :(double)f8 :(double)f9 :(double)f10 :(double)f11 :(double)f12 :(double)f13 :(double)f14 :(double)f15; + ++(long long)llre3: + (long)i1 :(long)i2 :(long)i3 :(long)i4 :(long)i5 :(long)i6 :(long)i7 :(long)i8 :(long)i9 :(long)i10 :(long)i11 :(long)i12 :(long)i13 :(double)f1 :(double)f2 :(double)f3 :(double)f4 :(double)f5 :(double)f6 :(double)f7 :(double)f8 :(double)f9 :(double)f10 :(double)f11 :(double)f12 :(double)f13 :(double)f14 :(double)f15; + ++(struct stret)stret: + (long)i1 :(long)i2 :(long)i3 :(long)i4 :(long)i5 :(long)i6 :(long)i7 :(long)i8 :(long)i9 :(long)i10 :(long)i11 :(long)i12 :(long)i13 :(double)f1 :(double)f2 :(double)f3 :(double)f4 :(double)f5 :(double)f6 :(double)f7 :(double)f8 :(double)f9 :(double)f10 :(double)f11 :(double)f12 :(double)f13 :(double)f14 :(double)f15; + ++(struct stret)stre2: + (long)i1 :(long)i2 :(long)i3 :(long)i4 :(long)i5 :(long)i6 :(long)i7 :(long)i8 :(long)i9 :(long)i10 :(long)i11 :(long)i12 :(long)i13 :(double)f1 :(double)f2 :(double)f3 :(double)f4 :(double)f5 :(double)f6 :(double)f7 :(double)f8 :(double)f9 :(double)f10 :(double)f11 :(double)f12 :(double)f13 :(double)f14 :(double)f15; + ++(struct stret)stre3: + (long)i1 :(long)i2 :(long)i3 :(long)i4 :(long)i5 :(long)i6 :(long)i7 :(long)i8 :(long)i9 :(long)i10 :(long)i11 :(long)i12 :(long)i13 :(double)f1 :(double)f2 :(double)f3 :(double)f4 :(double)f5 :(double)f6 :(double)f7 :(double)f8 :(double)f9 :(double)f10 :(double)f11 :(double)f12 :(double)f13 :(double)f14 :(double)f15; + ++(double)fpret: + (long)i1 :(long)i2 :(long)i3 :(long)i4 :(long)i5 :(long)i6 :(long)i7 :(long)i8 :(long)i9 :(long)i10 :(long)i11 :(long)i12 :(long)i13 :(double)f1 :(double)f2 :(double)f3 :(double)f4 :(double)f5 :(double)f6 :(double)f7 :(double)f8 :(double)f9 :(double)f10 :(double)f11 :(double)f12 :(double)f13 :(double)f14 :(double)f15; + ++(double)fpre2: + (long)i1 :(long)i2 :(long)i3 :(long)i4 :(long)i5 :(long)i6 :(long)i7 :(long)i8 :(long)i9 :(long)i10 :(long)i11 :(long)i12 :(long)i13 :(double)f1 :(double)f2 :(double)f3 :(double)f4 :(double)f5 :(double)f6 :(double)f7 :(double)f8 :(double)f9 :(double)f10 :(double)f11 :(double)f12 :(double)f13 :(double)f14 :(double)f15; + ++(double)fpre3: + (long)i1 :(long)i2 :(long)i3 :(long)i4 :(long)i5 :(long)i6 :(long)i7 :(long)i8 :(long)i9 :(long)i10 :(long)i11 :(long)i12 :(long)i13 :(double)f1 :(double)f2 :(double)f3 :(double)f4 :(double)f5 :(double)f6 :(double)f7 :(double)f8 :(double)f9 :(double)f10 :(double)f11 :(double)f12 :(double)f13 :(double)f14 :(double)f15; + +@end + + +long long forward_handler(id self, SEL _cmd, long i1, long i2, long i3, long i4, long i5, long i6, long i7, long i8, long i9, long i10, long i11, long i12, long i13, double f1, double f2, double f3, double f4, double f5, double f6, double f7, double f8, double f9, double f10, double f11, double f12, double f13, double f14, double f15) +{ +#if __arm64__ +# if __LP64__ +# define p "x" // true arm64 +# else +# define p "w" // arm64_32 +# endif + void *struct_addr; + __asm__ volatile("mov %"p"0, "p"8" : "=r" (struct_addr) : : p"8"); +#endif + + testassert(self == receiver); + + testassert(i1 == 1); + testassert(i2 == 2); + testassert(i3 == 3); + testassert(i4 == 4); + testassert(i5 == 5); + testassert(i6 == 6); + testassert(i7 == 7); + testassert(i8 == 8); + testassert(i9 == 9); + testassert(i10 == 10); + testassert(i11 == 11); + testassert(i12 == 12); + testassert(i13 == 13); + + testassert(f1 == 1.0); + testassert(f2 == 2.0); + testassert(f3 == 3.0); + testassert(f4 == 4.0); + testassert(f5 == 5.0); + testassert(f6 == 6.0); + testassert(f7 == 7.0); + testassert(f8 == 8.0); + testassert(f9 == 9.0); + testassert(f10 == 10.0); + testassert(f11 == 11.0); + testassert(f12 == 12.0); + testassert(f13 == 13.0); + testassert(f14 == 14.0); + testassert(f15 == 15.0); + + if (_cmd == @selector(idret::::::::::::::::::::::::::::) || + _cmd == @selector(idre2::::::::::::::::::::::::::::) || + _cmd == @selector(idre3::::::::::::::::::::::::::::)) + { + union { + id idval; + long long llval; + } result; + testassert(state == 11); + state = 12; + result.idval = ID_RESULT; + return result.llval; + } + else if (_cmd == @selector(llret::::::::::::::::::::::::::::) || + _cmd == @selector(llre2::::::::::::::::::::::::::::) || + _cmd == @selector(llre3::::::::::::::::::::::::::::)) + { + testassert(state == 13); + state = 14; + return LL_RESULT; + } + else if (_cmd == @selector(fpret::::::::::::::::::::::::::::) || + _cmd == @selector(fpre2::::::::::::::::::::::::::::) || + _cmd == @selector(fpre3::::::::::::::::::::::::::::)) + { + testassert(state == 15); + state = 16; +#if defined(__i386__) + __asm__ volatile("fldl %0" : : "m" (FP_RESULT)); +#elif defined(__x86_64__) + __asm__ volatile("movsd %0, %%xmm0" : : "m" (FP_RESULT)); +#elif defined(__arm64__) + __asm__ volatile("ldr d0, %0" : : "m" (FP_RESULT)); +#elif defined(__arm__) && __ARM_ARCH_7K__ + __asm__ volatile("vld1.64 {d0}, %0" : : "m" (FP_RESULT)); +#elif defined(__arm__) + union { + double fpval; + long long llval; + } result; + result.fpval = FP_RESULT; + return result.llval; +#else +# error unknown architecture +#endif + return 0; + } + else if (_cmd == @selector(stret::::::::::::::::::::::::::::) || + _cmd == @selector(stre2::::::::::::::::::::::::::::) || + _cmd == @selector(stre3::::::::::::::::::::::::::::)) + { +#if __i386__ || __x86_64__ || __arm__ + fail("stret message sent to non-stret forward_handler"); +#elif __arm64_32__ || __arm64__ + testassert(state == 17); + state = 18; + memcpy(struct_addr, &STRET_RESULT, sizeof(STRET_RESULT)); + return 0; +#else +# error unknown architecture +#endif + } + else { + fail("unknown selector %s in forward_handler", sel_getName(_cmd)); + } +} + + +struct stret forward_stret_handler(id self, SEL _cmd, long i1, long i2, long i3, long i4, long i5, long i6, long i7, long i8, long i9, long i10, long i11, long i12, long i13, double f1, double f2, double f3, double f4, double f5, double f6, double f7, double f8, double f9, double f10, double f11, double f12, double f13, double f14, double f15) +{ + testassert(self == receiver); + + testassert(i1 == 1); + testassert(i2 == 2); + testassert(i3 == 3); + testassert(i4 == 4); + testassert(i5 == 5); + testassert(i6 == 6); + testassert(i7 == 7); + testassert(i8 == 8); + testassert(i9 == 9); + testassert(i10 == 10); + testassert(i11 == 11); + testassert(i12 == 12); + testassert(i13 == 13); + + testassert(f1 == 1.0); + testassert(f2 == 2.0); + testassert(f3 == 3.0); + testassert(f4 == 4.0); + testassert(f5 == 5.0); + testassert(f6 == 6.0); + testassert(f7 == 7.0); + testassert(f8 == 8.0); + testassert(f9 == 9.0); + testassert(f10 == 10.0); + testassert(f11 == 11.0); + testassert(f12 == 12.0); + testassert(f13 == 13.0); + testassert(f14 == 14.0); + testassert(f15 == 15.0); + + if (_cmd == @selector(idret::::::::::::::::::::::::::::) || + _cmd == @selector(idre2::::::::::::::::::::::::::::) || + _cmd == @selector(idre3::::::::::::::::::::::::::::) || + _cmd == @selector(llret::::::::::::::::::::::::::::) || + _cmd == @selector(llre2::::::::::::::::::::::::::::) || + _cmd == @selector(llre3::::::::::::::::::::::::::::) || + _cmd == @selector(fpret::::::::::::::::::::::::::::) || + _cmd == @selector(fpre2::::::::::::::::::::::::::::) || + _cmd == @selector(fpre3::::::::::::::::::::::::::::)) + { + fail("non-stret selector %s sent to forward_stret_handler", sel_getName(_cmd)); + } + else if (_cmd == @selector(stret::::::::::::::::::::::::::::) || + _cmd == @selector(stre2::::::::::::::::::::::::::::) || + _cmd == @selector(stre3::::::::::::::::::::::::::::)) + { + testassert(state == 17); + state = 18; + return STRET_RESULT; + } + else { + fail("unknown selector %s in forward_stret_handler", sel_getName(_cmd)); + } + +} + + +@implementation Super ++(void)initialize { } ++(id)class { return self; } +@end + +typedef id (*id_fn_t)(id self, SEL _cmd, long i1, long i2, long i3, long i4, long i5, long i6, long i7, long i8, long i9, long i10, long i11, long i12, long i13, double f1, double f2, double f3, double f4, double f5, double f6, double f7, double f8, double f9, double f10, double f11, double f12, double f13, double f14, double f15); + +typedef long long (*ll_fn_t)(id self, SEL _cmd, long i1, long i2, long i3, long i4, long i5, long i6, long i7, long i8, long i9, long i10, long i11, long i12, long i13, double f1, double f2, double f3, double f4, double f5, double f6, double f7, double f8, double f9, double f10, double f11, double f12, double f13, double f14, double f15); + +typedef double (*fp_fn_t)(id self, SEL _cmd, long i1, long i2, long i3, long i4, long i5, long i6, long i7, long i8, long i9, long i10, long i11, long i12, long i13, double f1, double f2, double f3, double f4, double f5, double f6, double f7, double f8, double f9, double f10, double f11, double f12, double f13, double f14, double f15); + +typedef struct stret (*st_fn_t)(id self, SEL _cmd, long i1, long i2, long i3, long i4, long i5, long i6, long i7, long i8, long i9, long i10, long i11, long i12, long i13, double f1, double f2, double f3, double f4, double f5, double f6, double f7, double f8, double f9, double f10, double f11, double f12, double f13, double f14, double f15); + +#if __x86_64__ +typedef struct stret * (*fake_st_fn_t)(struct stret *, id self, SEL _cmd, long i1, long i2, long i3, long i4, long i5, long i6, long i7, long i8, long i9, long i10, long i11, long i12, long i13, double f1, double f2, double f3, double f4, double f5, double f6, double f7, double f8, double f9, double f10, double f11, double f12, double f13, double f14, double f15); +#endif + +__BEGIN_DECLS +extern void *getSP(void); +__END_DECLS + +#if defined(__x86_64__) + asm(".text \n _getSP: movq %rsp, %rax \n retq \n"); +#elif defined(__i386__) + asm(".text \n _getSP: movl %esp, %eax \n ret \n"); +#elif defined(__arm__) + asm(".text \n .thumb \n .thumb_func _getSP \n " + "_getSP: mov r0, sp \n bx lr \n"); +#elif defined(__arm64__) + asm(".text \n _getSP: mov x0, sp \n ret \n"); +#else +# error unknown architecture +#endif + +int main() +{ + id idval; + long long llval; + struct stret stval; +#if __x86_64__ + struct stret *stptr; +#endif + double fpval; + void *sp1 = (void*)1; + void *sp2 = (void*)2; + + st_fn_t stret_fwd; +#if __arm64__ + stret_fwd = (st_fn_t)_objc_msgForward; +#else + stret_fwd = (st_fn_t)_objc_msgForward_stret; +#endif + + receiver = [Super class]; + + // Test user-defined forward handler + + objc_setForwardHandler((void*)&forward_handler, (void*)&forward_stret_handler); + + state = 11; + sp1 = getSP(); + idval = [Super idre3:1:2:3:4:5:6:7:8:9:10:11:12:13:1.0:2.0:3.0:4.0:5.0:6.0:7.0:8.0:9.0:10.0:11.0:12.0:13.0:14.0:15.0]; + sp2 = getSP(); + testassert(sp1 == sp2); + testassert(state == 12); + testassert(idval == ID_RESULT); + + state = 13; + sp1 = getSP(); + llval = [Super llre3:1:2:3:4:5:6:7:8:9:10:11:12:13:1.0:2.0:3.0:4.0:5.0:6.0:7.0:8.0:9.0:10.0:11.0:12.0:13.0:14.0:15.0]; + sp2 = getSP(); + testassert(sp1 == sp2); + testassert(state == 14); + testassert(llval == LL_RESULT); + + state = 15; + sp1 = getSP(); + fpval = [Super fpre3:1:2:3:4:5:6:7:8:9:10:11:12:13:1.0:2.0:3.0:4.0:5.0:6.0:7.0:8.0:9.0:10.0:11.0:12.0:13.0:14.0:15.0]; + sp2 = getSP(); + testassert(sp1 == sp2); + testassert(state == 16); + testassert(fpval == FP_RESULT); + + state = 17; + sp1 = getSP(); + stval = [Super stre3:1:2:3:4:5:6:7:8:9:10:11:12:13:1.0:2.0:3.0:4.0:5.0:6.0:7.0:8.0:9.0:10.0:11.0:12.0:13.0:14.0:15.0]; + sp2 = getSP(); + testassert(sp1 == sp2); + testassert(state == 18); + testassert(stret_equal(stval, STRET_RESULT)); + +#if __x86_64__ + // check stret return register + state = 17; + sp1 = getSP(); + stptr = ((fake_st_fn_t)objc_msgSend_stret)(&stval, [Super class], @selector(stre3::::::::::::::::::::::::::::), 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0); + sp2 = getSP(); + testassert(sp1 == sp2); + testassert(state == 18); + testassert(stret_equal(stval, STRET_RESULT)); + testassert(stptr == &stval); +#endif + + + // Test user-defined forward handler, cached + + state = 11; + sp1 = getSP(); + idval = [Super idre3:1:2:3:4:5:6:7:8:9:10:11:12:13:1.0:2.0:3.0:4.0:5.0:6.0:7.0:8.0:9.0:10.0:11.0:12.0:13.0:14.0:15.0]; + sp2 = getSP(); + testassert(sp1 == sp2); + testassert(state == 12); + testassert(idval == ID_RESULT); + + state = 13; + sp1 = getSP(); + llval = [Super llre3:1:2:3:4:5:6:7:8:9:10:11:12:13:1.0:2.0:3.0:4.0:5.0:6.0:7.0:8.0:9.0:10.0:11.0:12.0:13.0:14.0:15.0]; + sp2 = getSP(); + testassert(sp1 == sp2); + testassert(state == 14); + testassert(llval == LL_RESULT); + + state = 15; + sp1 = getSP(); + fpval = [Super fpre3:1:2:3:4:5:6:7:8:9:10:11:12:13:1.0:2.0:3.0:4.0:5.0:6.0:7.0:8.0:9.0:10.0:11.0:12.0:13.0:14.0:15.0]; + sp2 = getSP(); + testassert(sp1 == sp2); + testassert(state == 16); + testassert(fpval == FP_RESULT); + + state = 17; + sp1 = getSP(); + stval = [Super stre3:1:2:3:4:5:6:7:8:9:10:11:12:13:1.0:2.0:3.0:4.0:5.0:6.0:7.0:8.0:9.0:10.0:11.0:12.0:13.0:14.0:15.0]; + sp2 = getSP(); + testassert(sp1 == sp2); + testassert(state == 18); + testassert(stret_equal(stval, STRET_RESULT)); + +#if __x86_64__ + // check stret return register + state = 17; + sp1 = getSP(); + stptr = ((fake_st_fn_t)objc_msgSend_stret)(&stval, [Super class], @selector(stre3::::::::::::::::::::::::::::), 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0); + sp2 = getSP(); + testassert(sp1 == sp2); + testassert(state == 18); + testassert(stret_equal(stval, STRET_RESULT)); + testassert(stptr == &stval); +#endif + + + // Test user-defined forward handler, uncached but fixed-up + + _objc_flush_caches(nil); + + state = 11; + sp1 = getSP(); + idval = [Super idre3:1:2:3:4:5:6:7:8:9:10:11:12:13:1.0:2.0:3.0:4.0:5.0:6.0:7.0:8.0:9.0:10.0:11.0:12.0:13.0:14.0:15.0]; + sp2 = getSP(); + testassert(sp1 == sp2); + testassert(state == 12); + testassert(idval == ID_RESULT); + + state = 13; + sp1 = getSP(); + llval = [Super llre3:1:2:3:4:5:6:7:8:9:10:11:12:13:1.0:2.0:3.0:4.0:5.0:6.0:7.0:8.0:9.0:10.0:11.0:12.0:13.0:14.0:15.0]; + sp2 = getSP(); + testassert(sp1 == sp2); + testassert(state == 14); + testassert(llval == LL_RESULT); + + state = 15; + sp1 = getSP(); + fpval = [Super fpre3:1:2:3:4:5:6:7:8:9:10:11:12:13:1.0:2.0:3.0:4.0:5.0:6.0:7.0:8.0:9.0:10.0:11.0:12.0:13.0:14.0:15.0]; + sp2 = getSP(); + testassert(sp1 == sp2); + testassert(state == 16); + testassert(fpval == FP_RESULT); + + state = 17; + sp1 = getSP(); + stval = [Super stre3:1:2:3:4:5:6:7:8:9:10:11:12:13:1.0:2.0:3.0:4.0:5.0:6.0:7.0:8.0:9.0:10.0:11.0:12.0:13.0:14.0:15.0]; + sp2 = getSP(); + testassert(sp1 == sp2); + testassert(state == 18); + testassert(stret_equal(stval, STRET_RESULT)); + +#if __x86_64__ + // check stret return register + state = 17; + sp1 = getSP(); + stptr = ((fake_st_fn_t)objc_msgSend_stret)(&stval, [Super class], @selector(stre3::::::::::::::::::::::::::::), 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0); + sp2 = getSP(); + testassert(sp1 == sp2); + testassert(state == 18); + testassert(stret_equal(stval, STRET_RESULT)); + testassert(stptr == &stval); +#endif + + + + // Test user-defined forward handler, manual forwarding + + state = 11; + sp1 = getSP(); + idval = ((id_fn_t)_objc_msgForward)(receiver, @selector(idre2::::::::::::::::::::::::::::), 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0); + sp2 = getSP(); + testassert(sp1 == sp2); + testassert(state == 12); + testassert(idval == ID_RESULT); + + state = 13; + sp1 = getSP(); + llval = ((ll_fn_t)_objc_msgForward)(receiver, @selector(llre2::::::::::::::::::::::::::::), 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0); + sp2 = getSP(); + testassert(sp1 == sp2); + testassert(state == 14); + testassert(llval == LL_RESULT); + + state = 15; + sp1 = getSP(); + fpval = ((fp_fn_t)_objc_msgForward)(receiver, @selector(fpre2::::::::::::::::::::::::::::), 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0); + sp2 = getSP(); + testassert(sp1 == sp2); + testassert(state == 16); + testassert(fpval == FP_RESULT); + + state = 17; + sp1 = getSP(); + stval = stret_fwd(receiver, @selector(stre2::::::::::::::::::::::::::::), 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0); + sp2 = getSP(); + testassert(sp1 == sp2); + testassert(state == 18); + testassert(stret_equal(stval, STRET_RESULT)); + + + // Test user-defined forward handler, manual forwarding, cached + + state = 11; + sp1 = getSP(); + idval = ((id_fn_t)_objc_msgForward)(receiver, @selector(idre2::::::::::::::::::::::::::::), 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0); + sp2 = getSP(); + testassert(sp1 == sp2); + testassert(state == 12); + testassert(idval == ID_RESULT); + + state = 13; + sp1 = getSP(); + llval = ((ll_fn_t)_objc_msgForward)(receiver, @selector(llre2::::::::::::::::::::::::::::), 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0); + sp2 = getSP(); + testassert(sp1 == sp2); + testassert(state == 14); + testassert(llval == LL_RESULT); + + state = 15; + sp1 = getSP(); + fpval = ((fp_fn_t)_objc_msgForward)(receiver, @selector(fpre2::::::::::::::::::::::::::::), 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0); + sp2 = getSP(); + testassert(sp1 == sp2); + testassert(state == 16); + testassert(fpval == FP_RESULT); + + state = 17; + sp1 = getSP(); + stval = stret_fwd(receiver, @selector(stre2::::::::::::::::::::::::::::), 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0); + sp2 = getSP(); + testassert(sp1 == sp2); + testassert(state == 18); + testassert(stret_equal(stval, STRET_RESULT)); + + + // Test user-defined forward handler, manual forwarding, uncached but fixed-up + + _objc_flush_caches(nil); + + state = 11; + sp1 = getSP(); + idval = ((id_fn_t)_objc_msgForward)(receiver, @selector(idre2::::::::::::::::::::::::::::), 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0); + sp2 = getSP(); + testassert(sp1 == sp2); + testassert(state == 12); + testassert(idval == ID_RESULT); + + state = 13; + sp1 = getSP(); + llval = ((ll_fn_t)_objc_msgForward)(receiver, @selector(llre2::::::::::::::::::::::::::::), 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0); + sp2 = getSP(); + testassert(sp1 == sp2); + testassert(state == 14); + testassert(llval == LL_RESULT); + + state = 15; + sp1 = getSP(); + fpval = ((fp_fn_t)_objc_msgForward)(receiver, @selector(fpre2::::::::::::::::::::::::::::), 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0); + sp2 = getSP(); + testassert(sp1 == sp2); + testassert(state == 16); + testassert(fpval == FP_RESULT); + + state = 17; + sp1 = getSP(); + stval = stret_fwd(receiver, @selector(stre2::::::::::::::::::::::::::::), 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0); + sp2 = getSP(); + testassert(sp1 == sp2); + testassert(state == 18); + testassert(stret_equal(stval, STRET_RESULT)); + + + succeed(__FILE__); +} diff --git a/test/forwardDefault.m b/test/forwardDefault.m new file mode 100644 index 0000000..0ba1d6c --- /dev/null +++ b/test/forwardDefault.m @@ -0,0 +1,24 @@ +/* +no arc, rdar://11368528 confused by Foundation +TEST_CONFIG MEM=mrc +TEST_CRASHES +TEST_RUN_OUTPUT +objc\[\d+\]: \+\[NSObject fakeorama\]: unrecognized selector sent to instance 0x[0-9a-fA-F]+ \(no message forward handler is installed\) +objc\[\d+\]: HALTED +END +*/ + +#include "test.h" + +#include + +@interface NSObject (Fake) +-(void)fakeorama; +@end + +int main() +{ + [NSObject fakeorama]; + fail("should have crashed"); +} + diff --git a/test/forwardDefaultStret.m b/test/forwardDefaultStret.m new file mode 100644 index 0000000..979f535 --- /dev/null +++ b/test/forwardDefaultStret.m @@ -0,0 +1,24 @@ +/* +no arc, rdar://11368528 confused by Foundation +TEST_CONFIG MEM=mrc +TEST_CRASHES +TEST_RUN_OUTPUT +objc\[\d+\]: \+\[NSObject fakeorama\]: unrecognized selector sent to instance 0x[0-9a-fA-F]+ \(no message forward handler is installed\) +objc\[\d+\]: HALTED +END +*/ + +#include "test.h" + +#include + +@interface NSObject (Fake) +-(struct stret)fakeorama; +@end + +int main() +{ + [NSObject fakeorama]; + fail("should have crashed"); +} + diff --git a/test/future.h b/test/future.h new file mode 100644 index 0000000..a48dc9a --- /dev/null +++ b/test/future.h @@ -0,0 +1,15 @@ +#include "test.h" + +@interface Sub1 : TestRoot ++(int)method; ++(Class)classref; +@end + +@interface Sub2 : TestRoot ++(int)method; ++(Class)classref; +@end + +@interface SubSub1 : Sub1 @end + +@interface SubSub2 : Sub2 @end diff --git a/test/future.m b/test/future.m new file mode 100644 index 0000000..9714f66 --- /dev/null +++ b/test/future.m @@ -0,0 +1,82 @@ +/* +TEST_BUILD + $C{COMPILE} $DIR/future0.m -o future0.dylib -dynamiclib + $C{COMPILE} $DIR/future2.m -x none future0.dylib -o future2.dylib -dynamiclib + $C{COMPILE} $DIR/future.m -x none future0.dylib -o future.exe +END +*/ + +#include "test.h" + +#if __has_feature(objc_arc) + +int main() +{ + testwarn("rdar://10041403 future class API is not ARC-compatible"); + succeed(__FILE__); +} + + +#else + +#include +#include +#include +#include +#include "future.h" + +@implementation Sub2 ++(int)method { + return 2; +} ++(Class)classref { + return [Sub2 class]; +} +@end + +@implementation SubSub2 ++(int)method { + return 1 + [super method]; +} +@end + +int main() +{ + Class oldTestRoot; + Class oldSub1; + Class newSub1; + + // objc_getFutureClass with existing class + oldTestRoot = objc_getFutureClass("TestRoot"); + testassert(oldTestRoot == [TestRoot class]); + testassert(! _class_isFutureClass(oldTestRoot)); + + // objc_getFutureClass with missing class + oldSub1 = objc_getFutureClass("Sub1"); + testassert(oldSub1); + testassert(malloc_size((__bridge void*)oldSub1) > 0); + testassert(objc_getClass("Sub1") == Nil); + testassert(_class_isFutureClass(oldSub1)); + testassert(0 == strcmp(class_getName(oldSub1), "Sub1")); + testassert(object_getClass(oldSub1) == Nil); // CF expects this + + // objc_getFutureClass a second time + testassert(oldSub1 == objc_getFutureClass("Sub1")); + + // Load class Sub1 + dlopen("future2.dylib", 0); + + // Verify use of future class + newSub1 = objc_getClass("Sub1"); + testassert(oldSub1 == newSub1); + testassert(newSub1 == [newSub1 classref]); + testassert(newSub1 == class_getSuperclass(objc_getClass("SubSub1"))); + testassert(! _class_isFutureClass(newSub1)); + + testassert(1 == [oldSub1 method]); + testassert(1 == [newSub1 method]); + + succeed(__FILE__); +} + +#endif diff --git a/test/future0.m b/test/future0.m new file mode 100644 index 0000000..4dd3146 --- /dev/null +++ b/test/future0.m @@ -0,0 +1,2 @@ +#include "future.h" +#include "testroot.i" diff --git a/test/future2.m b/test/future2.m new file mode 100644 index 0000000..c5ebb58 --- /dev/null +++ b/test/future2.m @@ -0,0 +1,17 @@ +#include "future.h" + + +@implementation Sub1 ++(Class)classref { + return [Sub1 class]; +} ++(int)method { + return 1; +} +@end + +@implementation SubSub1 ++(int)method { + return 1 + [super method]; +} +@end diff --git a/test/gc-main.m b/test/gc-main.m new file mode 100644 index 0000000..44f7476 --- /dev/null +++ b/test/gc-main.m @@ -0,0 +1,10 @@ +#include "test.h" + +OBJC_ROOT_CLASS +@interface Main @end +@implementation Main @end + +int main(int argc __attribute__((unused)), char **argv) +{ + succeed(basename(argv[0])); +} diff --git a/test/gc.c b/test/gc.c new file mode 100644 index 0000000..dab0f7b --- /dev/null +++ b/test/gc.c @@ -0,0 +1 @@ +int GC(void) { return 42; } diff --git a/test/gc.m b/test/gc.m new file mode 100644 index 0000000..65ba5f9 --- /dev/null +++ b/test/gc.m @@ -0,0 +1,8 @@ +#import + +OBJC_ROOT_CLASS +@interface GC @end +@implementation GC @end + +// silence "no debug symbols in executable" warning +void foo(void) { } diff --git a/test/gcenforcer-app-aso.m b/test/gcenforcer-app-aso.m new file mode 100644 index 0000000..8507a62 --- /dev/null +++ b/test/gcenforcer-app-aso.m @@ -0,0 +1,12 @@ +/* +fixme disabled in BATS because of gcfiles +TEST_CONFIG OS=macosx BATS=0 + +TEST_BUILD + cp $DIR/gcfiles/$C{ARCH}-aso gcenforcer-app-aso.exe +END + +TEST_RUN_OUTPUT +.*No Info\.plist file in application bundle or no NSPrincipalClass in the Info\.plist file, exiting +END +*/ diff --git a/test/gcenforcer-app-gc.m b/test/gcenforcer-app-gc.m new file mode 100644 index 0000000..a8ff65b --- /dev/null +++ b/test/gcenforcer-app-gc.m @@ -0,0 +1,14 @@ +/* +fixme disabled in BATS because of gcfiles +TEST_CONFIG OS=macosx BATS=0 + +TEST_BUILD + cp $DIR/gcfiles/$C{ARCH}-gc gcenforcer-app-gc.exe +END + +TEST_CRASHES +TEST_RUN_OUTPUT +objc\[\d+\]: Objective-C garbage collection is no longer supported\. +objc\[\d+\]: HALTED +END +*/ diff --git a/test/gcenforcer-app-gcaso.m b/test/gcenforcer-app-gcaso.m new file mode 100644 index 0000000..2094937 --- /dev/null +++ b/test/gcenforcer-app-gcaso.m @@ -0,0 +1,14 @@ +/* +fixme disabled in BATS because of gcfiles +TEST_CONFIG OS=macosx BATS=0 + +TEST_BUILD + cp $DIR/gcfiles/$C{ARCH}-gcaso gcenforcer-app-gcaso.exe +END + +TEST_CRASHES +TEST_RUN_OUTPUT +objc\[\d+\]: Objective-C garbage collection is no longer supported\. +objc\[\d+\]: HALTED +END +*/ diff --git a/test/gcenforcer-app-gcaso2.m b/test/gcenforcer-app-gcaso2.m new file mode 100644 index 0000000..8231993 --- /dev/null +++ b/test/gcenforcer-app-gcaso2.m @@ -0,0 +1,14 @@ +/* +fixme disabled in BATS because of gcfiles +TEST_CONFIG OS=macosx BATS=0 + +TEST_BUILD + cp $DIR/gcfiles/$C{ARCH}-gcaso2 gcenforcer-app-gcaso2.exe +END + +TEST_CRASHES +TEST_RUN_OUTPUT +objc\[\d+\]: Objective-C garbage collection is no longer supported\. +objc\[\d+\]: HALTED +END +*/ diff --git a/test/gcenforcer-app-gconly.m b/test/gcenforcer-app-gconly.m new file mode 100644 index 0000000..1b8e6a6 --- /dev/null +++ b/test/gcenforcer-app-gconly.m @@ -0,0 +1,14 @@ +/* +fixme disabled in BATS because of gcfiles +TEST_CONFIG OS=macosx BATS=0 + +TEST_BUILD + cp $DIR/gcfiles/$C{ARCH}-gconly gcenforcer-app-gconly.exe +END + +TEST_CRASHES +TEST_RUN_OUTPUT +objc\[\d+\]: Objective-C garbage collection is no longer supported\. +objc\[\d+\]: HALTED +END +*/ diff --git a/test/gcenforcer-app-nogc.m b/test/gcenforcer-app-nogc.m new file mode 100644 index 0000000..d99db0f --- /dev/null +++ b/test/gcenforcer-app-nogc.m @@ -0,0 +1,12 @@ +/* +fixme disabled in BATS because of gcfiles +TEST_CONFIG OS=macosx BATS=0 + +TEST_BUILD + cp $DIR/gcfiles/$C{ARCH}-nogc gcenforcer-app-nogc.exe +END + +TEST_RUN_OUTPUT +running +END +*/ diff --git a/test/gcenforcer-app-noobjc.m b/test/gcenforcer-app-noobjc.m new file mode 100644 index 0000000..ad746c3 --- /dev/null +++ b/test/gcenforcer-app-noobjc.m @@ -0,0 +1,12 @@ +/* +fixme disabled in BATS because of gcfiles +TEST_CONFIG OS=macosx BATS=0 + +TEST_BUILD + cp $DIR/gcfiles/$C{ARCH}-noobjc gcenforcer-app-noobjc.exe +END + +TEST_RUN_OUTPUT + +END +*/ diff --git a/test/gcenforcer-dylib-nogc.m b/test/gcenforcer-dylib-nogc.m new file mode 100644 index 0000000..b10fbe1 --- /dev/null +++ b/test/gcenforcer-dylib-nogc.m @@ -0,0 +1,11 @@ +// gc-off app loading gc-off dylib: should work + +/* +fixme disabled in BATS because of gcfiles +TEST_CONFIG OS=macosx BATS=0 + +TEST_BUILD + cp $DIR/gcfiles/libnogc.dylib . + $C{COMPILE} $DIR/gc-main.m -x none libnogc.dylib -o gcenforcer-dylib-nogc.exe +END +*/ diff --git a/test/gcenforcer-dylib-noobjc.m b/test/gcenforcer-dylib-noobjc.m new file mode 100644 index 0000000..a06fa54 --- /dev/null +++ b/test/gcenforcer-dylib-noobjc.m @@ -0,0 +1,9 @@ +/* +fixme disabled in BATS because of gcfiles +TEST_CONFIG OS=macosx BATS=0 + +TEST_BUILD + cp $DIR/gcfiles/libnoobjc.dylib . + $C{COMPILE} $DIR/gc-main.m -x none libnoobjc.dylib -o gcenforcer-dylib-noobjc.exe +END +*/ diff --git a/test/gcenforcer-dylib-requiresgc.m b/test/gcenforcer-dylib-requiresgc.m new file mode 100644 index 0000000..67ef3ce --- /dev/null +++ b/test/gcenforcer-dylib-requiresgc.m @@ -0,0 +1,21 @@ +// gc-off app loading gc-required dylib: should crash +// linker sees librequiresgc.fake.dylib, runtime uses librequiresgc.dylib + +/* +fixme disabled in BATS because of gcfiles +TEST_CONFIG OS=macosx BATS=0 +TEST_CRASHES + +TEST_RUN_OUTPUT +dyld: Library not loaded: librequiresgc\.dylib + Referenced from: .*gcenforcer-dylib-requiresgc.exe + Reason: no suitable image found\. Did find: + .*librequiresgc\.dylib: cannot load '.*librequiresgc\.dylib' because Objective-C garbage collection is not supported + librequiresgc.dylib: cannot load 'librequiresgc\.dylib' because Objective-C garbage collection is not supported +END + +TEST_BUILD + cp $DIR/gcfiles/librequiresgc.dylib . + $C{COMPILE} $DIR/gc-main.m -x none $DIR/gcfiles/librequiresgc.fake.dylib -o gcenforcer-dylib-requiresgc.exe +END +*/ diff --git a/test/gcenforcer-dylib-supportsgc.m b/test/gcenforcer-dylib-supportsgc.m new file mode 100644 index 0000000..d8ce9e3 --- /dev/null +++ b/test/gcenforcer-dylib-supportsgc.m @@ -0,0 +1,9 @@ +/* +fixme disabled in BATS because of gcfiles +TEST_CONFIG OS=macosx BATS=0 + +TEST_BUILD + cp $DIR/gcfiles/libsupportsgc.dylib . + $C{COMPILE} $DIR/gc-main.m -x none libsupportsgc.dylib -o gcenforcer-dylib-supportsgc.exe +END +*/ diff --git a/test/gcenforcer-preflight.m b/test/gcenforcer-preflight.m new file mode 100644 index 0000000..828cc33 --- /dev/null +++ b/test/gcenforcer-preflight.m @@ -0,0 +1,88 @@ +#pragma clang diagnostic ignored "-Wcomment" +/* +fixme disabled in BATS because of gcfiles +TEST_CONFIG OS=macosx BATS=0 + +TEST_BUILD + cp $DIR/gcfiles/* . + $C{COMPILE} $DIR/gcenforcer-preflight.m -o gcenforcer-preflight.exe +END +*/ + +#include "test.h" +#include + +void check(int expected, const char *name) +{ + int fd = open(name, O_RDONLY); + testassert(fd >= 0); + + int result = objc_appRequiresGC(fd); + + close(fd); + testprintf("want %2d got %2d for %s\n", expected, result, name); + if (result != expected) { + fail("want %2d got %2d for %s\n", expected, result, name); + } + testassert(result == expected); +} + +int main() +{ + int i; + for (i = 0; i < 1000; i++) { + // dlopen_preflight + + testassert(dlopen_preflight("libsupportsgc.dylib")); + testassert(dlopen_preflight("libnoobjc.dylib")); + testassert(! dlopen_preflight("librequiresgc.dylib")); + testassert(dlopen_preflight("libnogc.dylib")); + + // objc_appRequiresGC + + // noobjc: no ObjC content + // nogc: ordinary not GC + // aso: trivial AppleScriptObjC wrapper that can run without GC + // gc: -fobjc-gc + // gconly: -fobjc-gc-only + // gcaso: non-trivial AppleScriptObjC with too many classrefs + // gcaso2: non-trivial AppleScriptObjC with too many class impls + + check(0, "x86_64-noobjc"); + check(0, "x86_64-nogc"); + check(0, "x86_64-aso"); + check(1, "x86_64-gc"); + check(1, "x86_64-gconly"); + check(1, "x86_64-gcaso"); + check(1, "x86_64-gcaso2"); + + check(0, "i386-noobjc"); + check(0, "i386-nogc"); + check(0, "i386-aso"); + check(1, "i386-gc"); + check(1, "i386-gconly"); + check(1, "i386-gcaso"); + check(1, "i386-gcaso2"); + + // fat files + check(0, "i386-aso--x86_64-aso"); + check(0, "i386-nogc--x86_64-nogc"); + check(1, "i386-gc--x86_64-gc"); + check(1, "i386-gc--x86_64-nogc"); + check(1, "i386-nogc--x86_64-gc"); + + // broken files + check(-1, "x86_64-broken"); + check(-1, "i386-broken"); + check(-1, "i386-broken--x86_64-gc"); + check(-1, "i386-broken--x86_64-nogc"); + check(-1, "i386-gc--x86_64-broken"); + check(-1, "i386-nogc--x86_64-broken"); + + // evil files + // evil1: claims to have 4 billion load commands of size 0 + check(-1, "evil1"); + } + + succeed(__FILE__); +} diff --git a/test/gcfiles/evil1 b/test/gcfiles/evil1 new file mode 100644 index 0000000000000000000000000000000000000000..88bd337c1adf4cf57147abe815ff7740f598744d GIT binary patch literal 441 zcmZXQOGuOf5XV17T)Rl1hl0HfB=cB&uz1j!F8KfjK~Z5*I+;i)Dq0eR1%YmfDEQbt zb@1dt6!awc_z+S|4_ZDJQ8Z8qIxN*eMUA!3tIlD5fBwu|ew$bEgwJu3GK;Sdk{!o+ zxIUL~J#Kov2j}ta@}_sP$u%NYys55Ha#L4_UVkS1LKK0REeIgxTLE!h4DA%VNdsG| z9asE@p_alc@U8QLGm08S3A);4gr>2vOz5q$Qqhut{QH4M!I$>SK4G-)yi%$PW~Gn_ zF()&k3hyNSkLpy`&+Wqj2JYJDqvkVh+1(N0ZR7(DKpV+&qn(F&Y=4nTsh-qA_V?p@ zi6#@7@Q{B`lt-SzPLSpLxrMYkz`DS~;+rV>QhBpML6RVa;=_82latp(xQkG2f`lWS%_}U=k@dMlhCc-m_7S?$D`cy l7;nX(76WE4j&8^}P0t*`S_&9>@?Y?WAVon{CHMjS2}H#c4}C#MeL_6-0i;H15by&K!kn`ojlHqVGcZ@VJ2UsroVjy$ zHkw`EJAeH4*CRrRhoEE7C!t9gJ`veO0`)hbQm*FTF1=Sezk)_26H(=G$#I|IB4wp? zsnX}?m}neI&O06|dr9dTTOMnH#K%uQCqy2u$)iz^uuO{W$RefNZB|>BbiGa$#yCa= z7-`rmz+jw_q{ah`hhYE)2Fr$FwGGE_53XY!7+KiYfkDn5NQl98_D0Lc9E{@@FwVe!D;`HUCkw9eTd0($@)eR7NfwppeuVcT z+5_w&r40@PD%+kjbbN{(F}6Qu@)xzGwDeIJsjLvhNX0O?FOpCS<3EFolq-dIioPkh zyov(jL^dcnhVe(^z`x~6$JlUTgbW++_st%}%YN98MD<0=rs2B!rV--AF_MUZ@h7t* zF+a#4s? zI)TBGJhBj0Kj*%mfo%dR zjy&q?iKtE6*FwC6wQ^ki|BL_s0(Sa>y?qR|gwV=e*E5>hl38_hr>mWI^rmsG<$UOB z#a6AQ=QbjPcF}NLvt_$lK5+SQHwG}X|Ad)uw=JVwbIi84QoT|fjvnje&okdQPa^Rh z=tk@jXW%VZMy`+pe`Yn{^itm#AwI%m@ye+6V%l@2H!P&07WjZi9@htO?;)1C#0VZw zw>oxR_dwln>;r#@-@jHN?!h{^)>idOEtij0D#VNE!9B5seTHDf%VC@QSBYXkF`yVw z3@8Q^1OFQaF6``Gy#8zEJ{Np>_tT$oJlMUlhwIMLOmZhVbAK9+-5dLT7F5Oc+nLKB z>~m0WmTxw_S)S<|d^zaN3S4jUyeIa5>GgVzXIT3QwdHaFCmhQVP2IG^s@2l#gXcJ- z=9yQGVsQBL-)MM#=J}V>U-Ep)^DECm)C=MJCF^`YO5;2<0ga1=cgd?zC5i#XfMP%~ zpcqgLC^Ql=&`_7U)PHTa?VKludWDY}j?!C<$axH>9y;)HZjxKLO3nhJ=xtB*1`1*i9=Gh`c0Xe5aydKo)7Nwx>f08H%b*3~t$ouvO x2(dVr2Yxt$oF8h<(p^{DNZG}j!pw^ys~cR`Az~c8?!2jKCl_9uKRJ(J{|1kHi{$_S literal 0 HcmV?d00001 diff --git a/test/gcfiles/i386-aso--x86_64-aso b/test/gcfiles/i386-aso--x86_64-aso new file mode 100755 index 0000000000000000000000000000000000000000..93b331649cdfb041bf5fa300f34f51b8b1d3aa67 GIT binary patch literal 29060 zcmeHPU2I&%6`mU$Sb+r&S*QzvZj1wI>UyC{qKF^KYh!SNNgS_}Mo=(|<_wL#z z?F%XA$a81T{CxM!cW14+b7#&c-+b_)Qfd>zH3(NJ)r07s30%58+9)gH4 zAPfit!hkR!3JkBv?q3U!Xl1ZxP}y^M?2Q=`YGLY3IYwL{GNZnlB7v~KwG4WM;CnNUpEP~b#W~NAr!q#j6_N)VkBZ192Z>(6xu(Ai`IvR4~&?s;Bq%2v{R`dW*ge?+JXO4UDy1Rb!0+1 zwKsKBtM;-U#v@UUXg%xK>$y|D#ff8d!3Nsjm0B00lAkO2tIsgnk8WV_hTSm?WQFV~ z&e94y4BGm#&DybT9Srgq{FcY}AADr=!Ts)cIi%DC3xUCyJW_Sihark0Q=UYWgwpE@ z5f4CT`7dzUhM3Qu<OdwE*?*i+)XU4L)oU=Ib{Oggy`|-D&o~zf= zrQ&otzfj6UzZHQtBnSHc@OOXj!kJwcKKS(C1HbM^UJru0?phOeIh}d$DYX-QWxM$Q z7xVuD`78^@_RUCnDxF=ZH~iW3cyYRxt1YA-s^w<=rz^Ez*3%=EnM!W(q{~Pj^=tKF zrCd*Ef+AygA%dCO2s2Zymi+8YtypawntpPmGkUC&xzFyC&<$SII>N;bu zmRtOAUzIwK+-P4b(@)L}W?WB|x)U`xCgw5DV2t{))j57egaKhd7!U@80b$^K!@!Z| zg`=lGPhMn)k1ze|FPI)Iy?O!H=6HWsv#bAN9}1RUy%fqpQk_1RJpRNbHd@)iH=C6m zoYNQhaS=BkR&^o2p)Q)8VZ{+(;X2f}h6zb8Lb?TvN-cmPiii zgIR7(D9)HonU7-A0t>ot9@5-dsb}k_vVJ*_Jc@@M)CKL&`!jP5AN%QZg!+NmLi^h7 zO65`IeuUxC+-B~*yurij1OB+r7CRKM=Txbm1!)$m(I9P>B(g!8TN+yXzD)zAhNAnx z+btM0rp}ad^|~%&%P#t4cV2YpU9?|q#ME;6+P&%Yp1t?%-m{y=iU)k$tjeG=Li}Mb|qs0sTf?w0}|3UR!h77vjT>S6ovn{`E|+~S!!wc@=L8myKytyLg_u>EE-AtscFABsH)U6y#nIbiB zV{*o^d#^rQ>PML&Y6Bk!@&AjwPxQ?H|L)^}b`rOMK+b~kmO^S>-wDy9ZVe7ung#=G-(!0{|13$_?&;v zyUI(l5aK;Esl53fuc!N(#4^Fxkb-@0V=?Es?mhGQQ2rWCC0iH}2801&Ko}4PgaKhd z7!U@80bxKG5C*Y}j6n#$JVo}qE5FsN_nU#Zjo59WYIr>K%$Fw(;W+TU9?hKfz$;HR8^RBX8f9oUHSvcz3QEJ?|bi_ zx$n$4dNXr-^TD70JfoD_2OWZrK=1EY>T_jfGO*u+YCSi3y8Ll@;T#+z!^RkF8TSAyf{^?oTYW-tVgsG>5sQwRq8Z`tiwuW%_O#2wP{?f!;MC@>FF?P zSJByzQ3pmI{dHh4Pb0bg3C00*00Y6g?szRHi2c3mh=5_CKLQ3hdn7#!`dY7qQQ-P( zy}|xGt^ngDj9mdnTGDu0xBZK*U(>F?+Qg)OjH|#ngZ``id87cj#$TXXPfspQrUoq7 z=6dw5g}DbfOKTq-CRDbf;IZ*JPDE@vXYyx!t-Z>VFmjes#K`qw@V+qDm-(N;LF;o< z@1KNYa8^TfLLL_%&$V{*At#Aw#qp0j%m_pT!c44#l&7~bGgQhB%yX3@J12G&vl#{F)$Td@8cgFGg`<(ady zAC#xhn%`xqcP3zzFo`^Ju90I(ZDL3%97fTMqAi#~v=i{h(9WTqK+837oI$)7ZlPU9 zdk{JYO$+Bz%{5Dp8T)z<$M~i_1mhJbf6I0|3~bM>+O-X@2LEL!bGT+5KlIb`iAz7< z^lu-S{oxztISf??pN;h`lVuoPe-5rdj=Tr8sFKlsb+XT>m9-iW8_edwz0(;fM$W!hkR!3b*(i&=IT0`?AB^J^qMetSBpQ8wV zK2X~#$c>bA3q|eGD!u#*CCw63-3Zqf9lwS?NJgWEdPq0w+F5s2+L41g`U)YAHY%>K z#`#iVpZfj?8Xo8#&fv`h{=FF6sAIr82-Y^B)*&uMemAjM#-OGXJAO4Wld@UQ$_f1c m1dx*PEmTVn=Az?VigEUFk%jhAx~m@*UhG|$S@QrZIMKgpur(k6 literal 0 HcmV?d00001 diff --git a/test/gcfiles/i386-broken--x86_64-gc b/test/gcfiles/i386-broken--x86_64-gc new file mode 100755 index 0000000000000000000000000000000000000000..b22be31828f6f0905f149b02e06526076f2099e8 GIT binary patch literal 29056 zcmeHPTWnNC7@jRfR;jQcF*Pb4u;mugg`kEPk}XhJv;u9x5EC7GIb~P3+h%tgis7LN z!MIjTP0+*#6CO12ff$~AF!7Q|BKUw0UV;e^Jb)<#6JvZJN~qsAXJ&i$Y+Jx5!2eI@ z{PWM{J2T&$)0~-|f4;x@#aBwHI)r%$bCgOztxw_75JTFiN!tx`8KSBIK|~l32801& zKo}4PgaKhd7!U@80bxKG5C-ls2G0Hc`%OFXH;Vv^(f&W?Ds@zOG0gwfMg*;Qw{CBH zxvg_osB>HXX-yX+vy}eH^6uR3BW*Qo$+%+e!3gDPW|L zek$6I5Fq>b4T9ENTlchvGCaiHc-XPP;!mYNTIa!GK;=-WFm3%f;ZQf+X7cB3t#jS8 zVZ=S9h!Kxq@VRi~%l4;n(R$bB=eNLcvJMf{Q(mB1hwXRuz<;i&3x0nQiIC~)o3h?i zdsz<8BTVCM-3NS&6UC@U8;DWw&5AMDH=OgQ#<02{abWO<xc4$;?*xn#3Xw;ab+TNkQDiBF%Mz$1(2Xt= zdM)fG=-tq3q1gxKvl5?$i_rU_=OgS!2n)+HxG@Xox%e52<7}rFAo2tP-{s_Rv5?GV zdy;*Ja(%ELM_?OHdn*=vxqtoPug~Q#J+SlRciGMaf|~!332U6qG{`D6oAvnrB`mzh zegx*Ry-Hm|lh?+QU5AP#e<0bO?J0B@4kfo0x(ED&gN4_N$!&wf`M&N_b}--4?^2U5 z`Gq1UrYHJ4r3`0a15C((+VL%uV2801&KK8=n4ifg%{ zsl2DDzT9?WVkPp%Uc7Pay%9vpOPb1U*C!rEbp6?K+h64~M@~1s^6mI!Gr#aBn;US= zaKbRz%&E(GMnSGzot&J^@RRn+MZ|V?ZN}GQ&R4y;?qX3DhVyw$B=OA(;kP-zdnsYf z?^1rZ@;j1&^IHnzoEJ5qX2R5FYORc7F8MZsh%g`w2m``^Fdz&F1HynXAPfit!hkR! z4BUSV@OS8s5cs$?B?EIkqigaKhd7!U@80bxKG5C((+VL%uV2L5jh zELtA#d|GvG?pVC+>e8XQq1aGe$Kn(>(gvI0d$9i;o8UVNYFriKi&J_Co7zkLv>F~s zX>Kvq1H}Vfe!dTBkeJ3E>N-7;(f)|vJ6!UykG`7_%Lcl$dDYBCg*ocPQYc<%E{tL2 z0e_F0IQB7M4T5_cu-CzCMSd=ba~p%&8pO@gM0+UC{j4lNe*#F#*&cRF*S1BQwzQbm zn@tuvkFC49vhkw2U#`zJtOWOxt1uu82m``^Fdz&F1HynXAPfit!hkSv*E8@#;Qt@P z+O~0W;kXO`f75pQsHYL8Jp;g$u#2k7fexz*VLIg8g6 z6j!v(VurQhm^jZ_@xY&C~Ei{w|MUUw8K(`ZSZD4m*4}7_n-x#9hO?W+0Q6A zVe$A3QD{dof}G&0wV%<(W!l-*<1u0J=qpX3Z5D_Bk;J1@N4JXn#G67q=B? zhuhuPIr+(d){VL%uV2801&Kp2>ffxYSS<@AwjiS*c9o`;tn zd-hKhPnW-&;1@>v*rpC+wp~A^Ciu-}o&M8DPA81l|4F1xHh+ezQtii{d>^W{YqI$$ zwDtY}t>XKC0kNpdKkixO=MU4^{Dunq`*U7OnZe&bwj{__bKdAwv5`b9(J(JQPVk5v zw)3kut!6gw<>r}@;$jK|!hkR!3V&<(KA@AFcKSQHw%d>-+;zxeFIqV4*UaH+&BtAa}C1y y2|#n%qT(xH*D1pO0?1Yyf^wCg5L*MAFh+LZ3Y?0~-%?rlPW%UEVC;^Z2mS?nVR-8R literal 0 HcmV?d00001 diff --git a/test/gcfiles/i386-broken--x86_64-nogc b/test/gcfiles/i386-broken--x86_64-nogc new file mode 100755 index 0000000000000000000000000000000000000000..a401acce90c1eea84e67f306c0b47ef70137b3ff GIT binary patch literal 29056 zcmeHPU2IfE6rP0wD^%E4Vro>pV2fZ#7yJtkl5By(q7`ThhM36E(pz?QyKQ#2p%@-& z2*$NyYJ$F*@Zf_F#PDWf;!h%ppb0*ipz(nR&_XaV#s{K=`h9a}ws-Hg1$+WLC!Krd z%+GgczPY!#GdpL#yYcy#N~t*r3lQcim4I58!lgcjv{93`6XtS6RXu`;Fdz&F1HynX zAPfit!hkR!3$rN*yf1EL0!wLK@<&RH1rn|AJ*L!FWO+-Js&j>? z>(r<-wJshS=pD@IVrjS+iBXIUFt~`30S4RYBt9>|n2!Wtpt8>Rxgoz`?CO4$fZ-v% z1Pm^mRK#M?)_Q-jRLJHJ*vx1+9q z+Kvz)`}j42)|=aQwS_V~#NBwFS%Z-c);8 z4$mV|jc7gK7mGaye2Wvss6!iwQRmHyG1xbp^QXqJx*u_1@P_2V@dlF;YDac}t+0}Kel&}*RC2j;U9pM?w1`=A#g>_i9)%QCnz3+K7`8H?j=rxzpg2m;^bC% zeTQ;=updTX8%}#G7k{yD-Qlm!OdJ2b&4`j!Qp&gPboW?Z|!%f$(Q{? z5h=ywrcgf6i2n?N!;i(z0zWp@FZ3LeTgaKhd7!U@80bxMxE?+uwwP}J(>=}FeM|?hwjsJvexxTr) ztGTY+etqHzh?JK$m)oyR+>hwGv*q@`%4d$8ZhG~b@yQl`;ZL^Ie|@Zxcthpp*cgbp*fw6DQ=_1{e++Zy zQBJBG5YEGK7ykdI?etMkBTRb+fGJ@o={7!-BF+eI5cOeac{9o9=7X?UoBI+tr>Cae z^o#)bJrHk$DXnHbD+p`Dg>rA=X*#S9=*BlgA zw9R6Mwc(gM>Y~)zE=BmI43{Ws_#3x)?*6pHQiW~sWP~J(}Gv@-RA?US8Bxii~K7q|K^_DBO5Bk5JZxBa)_wE9wB6J4x z5cq#5dcFaYDrX#g|K{H3n>ReQ^|OWlw9Nhax0U?AVu-o}&p23*wvjt>cq?ZdpqS}@ zxZbUtbHI8l5y-;{FBsR=*5=f1jP$V$oyKgxc1lg~o6S1?r;nUY7_a}6NSkc=6j!A>jy?JTR9p9C%TZ|S z`~Pdj_x~5fqAvfqXO*8nOk?vKD(vsic`0QEfB)FhAY09Sqf5m`60t=6g7`SWBXZcz zuimoSWxIK%vqcL7!hkR!3cPNJN^SRFm_wc1OEbwId#UXeI(dH{abMt| z^-}55QfizrCJrsni&MqQSz1T#dPFOc{&@RUrOsl=KCD#6Ok$f+o5s~T+-Ovqbsa|S z3Of5SYQV^$zXlBEX(YQp!8m{pU?5o6+_zSb*Y6nOqx zZ?Hd)YruF3W7mL@mNcH$ZU3_8SGDJ_HZiFm<2o?Tq5pb+9w|Vs@h7O()00b+sR0|d zxgNc1VeSFW(%J`y36-rVcx-%@6A_!vnfw`F>w5V~7+G5>Vr2U;cwd<7%lyybp!J2R zcTdAH@+J(#DLZKy!~9Jg_^*dLa92ZgLLL_%&$V~+@WU$T6ihF%*xFq8WvgH-l&=;E$o5Lpy<%Yv4G8crV;RyNvcA zbPk#p&ZU}bmL4Ns5w9$_N(Q{Yx;#%)9YMxgAhHTb2>E+G~&MnHTafw3;5RVy0Et7RE4Pm z*639RV~`UN_Qo_TAFmW9O@va%F@x*B0#0!Pb)x4d7Z{F+Fdz&F1HynXAPfit!hrgq z^V`+m^E+hX($;6cVEeSS{VR^0+*oI6EYm4H*m)geTkk*k^6PaNozbyQ>F=HAV4k|w zDgD*CdG$vAqo23CR2ZfWn`fi=50h23M_RV(#! z7^J7%>(|u7~800z&i-mHlWraE=7Jjv028TrV~4UH8GR2Si@@FrH(2uhV{SN2&eV#*6nRC zw{`9cHICZ^GYHGQgNN38+V<@UWnvju4nE(DR~g4#T9^Eh5<`JR%X?2LwH-;`LZ#|m zCi?Yi)Tvq*4-NDT=5(<%+=Iv{Mg|z%#K-`H^>h-S8(=(u2wwNS@fgyM10yV9JgtZG2ebJ;oz3?TB2yIO6fn|= zKNYP<2#{_322Sg(t$SKS2_C|(KWtxM^QY1tt@Ge8pmL~Gn6`YJa3~wDGx>A6*17K4 zFyfw4#E3^QI4)fOvi@m2wBEJ(`7KbKtV00hlo$9c!}_~&;6GQ?1;4+DNXT^MO92k5dxp2HerG)B{9bhf& zFsNxuBQVHgqc<}Kc?`bg_8mK4XxqBOeU~XD@J+x-ArpCIUMI_y8by*xUjlClltz~b ze=YPT_`Bh+h0iuHorO3SF2dgre?Hu9xUeuUo$Ir3nhT$?I8Jx|0tB9bY}XFZZuM{Pnr~r3ZF?{4VR6fK&4yGH#9283$Q~YO@^wzl4SF z*bm1v)>o-ZsPfuavg=T>3 z$iem>2B&y|T5a{9gQ18B1HynXAPfit!hkR!45+>3%SWy?PLPRxV{iS8>C@QwuXvUl zn#y~c>dS36CRQSC?8O_$-Wx%nyrikzc75Vu1lOM}xBXQg42PhEv)r7ZKXowHdR=oUeLw-Nm9R4CnJ$NMgK zyp;1+&Lio#zNIkCbx{LKCQNC@*2*Z>l5fL_2m``^Fdz&F1HynXAPfit!hkR!3XHaE*+{HiVf9uEKcztZEy&_2j|am2)?7B##JG{IHiYhsJ+xrt>KZB z<`GjpP(0A(=lc)`iD{gnuG0ex6@M!CAjl>OpwXOfF7LPW%6sHFo?5^$I1GIIf-0JRtoW*Mj zvMcIlGsEgI%pG+>`r2+q_@xZDC~Ei{w|MUT)WcGRZSZElm*4}7_n-x#9+q0X+3zSg zVevSIDAc1EK}zt{+RtcXGVS#0{+O_Mw3Vh%HycBNTJz+%JC6ZTbN>OxSJk+4<1aMs zLiaE6gSwyDFYZP!n!3C`K9(SQ2L>4dTRKZ&@>=Fjj{s{PoL@55{D znruD_-`f8FRS z6Fee^?fU9X>ob|}a_h`Uc2R`^VL%uV2801&Ko}4PgaKhd7!U@80b$@CU|`YmL}x4Z zFcziE1?zfCduvyhZkjp@-%(gMK8-=Z=oxHQ7>SLunT14^Z9wC+wgJp|2lfHXZVUzB za}UDU3Bc#JMa5RY?o)*A1(2*Z1or;5t#}GGa;yR!%Lr~Sji3kJ2fG{8o z2m``^Fdz&F1HynXAPfit!oWSoz`4JFziAi#W)olu>i@?)rH(2uhV{SN2&Z*N>-M&n z+q!mz8pmyd8HDBD!9(laZF_fzGO>&+2cPf7tBhkVtxNt$iJ?HE<-Mnr+Kwb|ky3Ro z6a6|h>Qt?ZhX#5FbGld>?nPu2BLfU>Vq}29dOC^E3osr)1TauoXZ+ldUod)gJ4(Rt z5MKfYH%=;IF{o?3zgQ|{^9O8Vv>wNS@fgyM10yV9JgtZG2ebJ;oz3?TB2yIO6fn|= zKNYP<2#{_322Sg(t-D)82_C|(KWtxM^QY1tt@Ge8pmL~Gn6`YJa3~wDGx>A6*14Y9 zFyfw4#E3^QI4)fOvi@m2wBEJ(`7KbKtV00hlo$9c!}_~&;6GQ?1;4+DNXT^MO92k5dxp2HerG)B{9bhf& zFsNxuBQVHgqc<}Kc?`bg_8mK4XxqBOeU~XD@J+x-ArpCIUMI_y8by*xZ-CbTrO_qA zUkkkneh2)u@Yx2Yvk=F^Mfm&RFM#WS3k&noxjqZ0x$qf_<8Vj-E! z_9pud<@%sM4#zs2_Es$Xa^L#HU!TigdSK_r@3Ni=IJMv*|nm7-^C_h z@(V@86q8#*=|Cg?GjI++7CQ_4*i^gFw%}DQn_JMvY;SED)CmaHWd?g++uzdaN+`7g zIoSTg;1n-VtF0b%FccACKo}4PgaKhd7!U@80kx-m`N*}#2{N&F?5&?MeHt7870+^g zQ+an&UAgVX#7d-%y?Eo;dm{*x8=A^(*C!rEaQ)eG+h64~M@~1s^6mI!GiUgd&GmR@ zxL}xU=F(+6qaatVPEJl{IHkRE5uu&kn=yOL`D%Zzr&v^l;d~woNz7RxoSSprO9}g& zmvY|9c_bazw-koCE~-b#gelF~S{cP!@@+T~VL%uV2801&Ko}4PgaKhd7!U@80bxKG zxc?a7@6aE?@pnJ|Hgqzy2N3+$b^obe#5B%O&(Q-J?T`5Thf6-r(f1Hy*+5S=ubR23Fjt*e3Xcz}i(=S$ zz~7@LjB^awgW%Z)oOLiqk)I2~JjS5524S-`(H;u(JS#KMo&b_^x`)%!wRO>^E-j|^ z=8%QXS?%X?*K44>>}NUXYz*qPrf@_G0mELP{a1g`1n zQ*L=j0PG%!x5?yIv)&aD;L+CK8i^bHYFqzTEgo%jDNYYG*j?MZ2Waa|xz*hPIg8g6 zWLMP9W`@;am^jvsfVQs+u+T9FTn>E??DSfJuJ0&v)@s0 z!s2lZQK&~Tf|TH?wV%<(WZLP~{V`$jXe&*jZZ?JjwdTojcOC;F-{KYhTyTd%6mL}ok9xR%_+}9^B^J+Ywh=Lkw=RN5J&XsWD!i%xOOg8wyhy)7 zr;M7;p5=Z6FStX2R$3|KcarwZy#Q(m{yIdGGmgDap|elDWlHUX|6lJnh@-swb^%in z8iRRo{J#^u-vCjSI}Sei@xtoQf7-C}!!`f>a&mBO7yqvqqVB>w4mP5057kfG{8o2m``^Fdz)f#=xF*`EvTmwM2Ss9`D0T zk3IV*vZu@6O>l;hKDMdTsBPCzsR_>6tkHk^$mxW!`ag-d$>z`SRI2^hlkdZ8?VfBt z3g6oP|5ma6zkpEGvh#;(bk3o|_WoR#Qf9FCk2M6zYVI3dDmIdcCFfG{8o2m``^Fdz&F1HynXAPfit!hkSv4=}KJd7`Todl-vT z=7M#-rM;&L*+oEDCVD~A)_5w&&8-je5pAcIEoiIjr;0c_H&fij*`A+NyW?<}&j0gS&1-Exw literal 0 HcmV?d00001 diff --git a/test/gcfiles/i386-gc--x86_64-nogc b/test/gcfiles/i386-gc--x86_64-nogc new file mode 100755 index 0000000000000000000000000000000000000000..3a237e19cb2e19990cce1a370e23ab68f735fcb2 GIT binary patch literal 29056 zcmeHPTWnNC7@ma!D^%DbF*Pb4uthMW3*N$mBwL`cXocP|#6*Uc9@y3Gw%Of=VtA+_ z7}tua3HoNjgAYCs!<&hTmqZdl6MQm3;{y+%g>71E= zF5j8?W==CRJO6xlhbKo}4PgaKhd z7!U@80bxKG5C((+VL%wT%NRKK_wP6C!ryEHEJgkQn5WcH<;Af6R~z88?r7cC_DWmV z&QRmHO)!J7+`D*a-P5+aCzOe0TsioBFJ5IFb7@`jM@kF@5-smNrqnhhc}tY4bD8MZ zsZpnDT|6|>N)$J$&!$W)t z7~D9ih{d3;^}b@Mkj)>kiP3r-2gXB4I}VJngz>Z<&L7O?`*k+Ie-N3X7^i@dM*OL0 zJwkwN00M{XTykl zN)aO-!Qi-X{mc5N@z8qbrWZCtak35plv7^dvkdF+%7On}Q5XFEMMOfTD{u08Q}ty& zj7Opx(R#oy7JCo)7AJ~PhdL0W&YKluuzxt`PxWDSJL16L3(1A!4Jsv6kL&^JTIb19xbJ@OR z|Djwz^oQYChtuAw#b4}QfB36&`HS;+eEc5knSfJ^78>`Y(-{X@jcT(T|G$KV@7N2+ zG}c$Ci>UJ2ShD+2vE&aV+p~Rz-ol~e)b0{5X#D50P;m2ZUfghV{7upuQs%29P+L-ODErU7%p}Ndq-)s9?T3rdHRv`!5 ze;AzN1!|4egARrwA`A!v!hkR!3cu1`FHw6T}2AA5fUf%3AZa@)0u`w?7!w%qnt`OJ~ijjw()KH1C}{$z7Ko*6C} zCY!l*8P6!lv@SxUzg`&QNSEAGkkUM2 zst1Y(y8V1V;vg}NGt_hRKt}r`{=VUok8||Bgjg}qo6W0cZYs=GCziwGgX)qPb{_Ee zpb6s~1NI!aUE)474YJq@3>Iv~+DtaYpchs0};wn?*hkAB4r~JeR;VJ$=ee z?+Ae11MxPR+-lal0s=hR`dcG$gI{gyzsKUyMwjCBK!e@2y?cPR&Xk+o9gwqlO+j`= z-E3x99frB1E=XV7tq8xA;TA;=f8!RBS0 zj`J;E(a!~UC`9pANAReJ>xXX^F;imk%w`)Aqj>8gc+|sqK&rx<`o9#p&%lfHD|E`J z>FinVH}HZx6lkTDLVhP{&)f^3hTyM5Bst^Q`vf}s)LWs{Uikm@euFs5yKfgT6`?Vh z2gmLeTgaKhd7!U@8f!P??l`daOAGw-HkImzKcbKQ|{ zLT5aVE5LXLzAM0pN)%7ZmUXFSRbf^O z&m(*mY7MZLlomJ)sI)z2-|;DS#Mo%eq%uMfBNfBox=_!T@ect|%Cl3i zOrxS?0tUwU4+15}Fn*R4DI2C&l;wKSb%T2MctUWzikDRgQGLNPRKvr1C&mN-h%u4r zjp0hiT=t{rp2rdZIM<~Z2L3N1aWqWVEv}d$PMnKMh*9Z@5j@N=j4nQ_z~Bw5F$^?C z<7ie|b@QL~*vZFgh_Tueqt@U|!X@1m#|ALCCN_Fta7_g7rPH%>ujWtAs&`Bl4n_-% zYv{zaz`jb332_-N-#>z41eIasM0o=BF_eoa`G3WALI0PiGp3PAA+}K-hMt2)g=>$> z_t`;`#CZOH#N~c8Fdn{_jF#&d^;*fOtko;1KMZ9IKU{eGlixm_nB3}Le{cAU#_NnT z4Hbv)^_A}uzs@$uQ7=v zlbjLjx>z31smO$Q0zLTrKA#BQ2TzA}u01W90nLDBKr^5j&oWw zmp575+{hmhVsm>I*Y?0zdtofu&fngB628qdx37NChS44xYv=#iy$|N(^>+UE_IK;w z4!`-$_TKSSMu@%R1GrX`zA13s=H5u$`gw0}ugY!Tt)F1c6|nCy%bv)udQK6Wn7P6f zwp?{n1iwF=mSy3WBsO}%ewKShO0?u&j(a`sJE;79OQFqu=>TBU(1XOrZ^?I{S~LTi z0nLDBKr^5j&Gy|Fe&46Y=GoTsx|1rSt(A2BpZ-1U6^!(%iXS6?06z~zHOFj(o zK0^6fXtNXh^Sdd(&84AV!N+fpl;C?*VC$=9Kr^5j&Gy|Fe&46Y=GoTsJ3}^;4 z1OFZaM~h@ z+DLBqmRTEkFe{nPC>!od!L%xVj;!dD;*oK`*-v7S7V=4Dkl-){XQL|3RkPgkOyt=Y z3H@TDShK`SJOt<$+oQPbWRWm28k`FZ-V>Y~JOuY(0w)Lfd(O9E;tb(=*f?A86CBP2 zwtp zxELscaVkuqxD$kK1#uyCSV3hn#osx#ym9 z&-uL%?t3}E{`JQ{PYNLpfycm;;M-3K@tKH6BIv&jmU21$ZsxJkJ^6S_0l((W1# zQm$n#u7&ip(ROIL?%^(TmXyA|;jAH?htb$w8lvf%k{#ZmDp zx!1=H_?X7{4fqIC*q@Y5=SsyX%8FB}V^V)Ex8UP6#&7k<5pu{JZ-b>gm0nAS6mj(R z`RJYteGPDylnyWqpltZ=Q2Qt+!ZsW;@zb)D)!Zl_(YO%w5$*HA>q4Jj#(x?IDVOHn zn}?w3b#!1S;z3Ir#;@&wf7O$&UGgvp8fLHJj~~R#dAJ_wRgsi6+w*ekcGpk8k0@f` zddx%TOv64O^KLyOzvHyeMy%|n=i4>wTqW=3+)Zo2 z&DHEHb@vm`TBtXjV$QGBon%RmS{H2B!-!|ihxG8Ko=0d8{Kp|h#0dZCMx$zH3vQ+1 zFXb=K4_hQ{^-WUiyct?261jssu#UzL7zQaN=aRs(k{=e^E^NLU72AB>wMXZlwtD9v zCSnkd4HyR)gkr1bFBg(&9hnd>V+POXr?^HClz6o(2Nx^^Lk0nZfI+|@U=T0}7zBnO zaK5#B;p$$ZP1VKi&mUqxvAy#*j;&atwU!ufW%k<(&~3lJzkPk5+V7QiGSTv>eb~mX zk=ie>e}hg7564?3Mr znMU`2LvJxVhuve<7KLig^Tc8nTRgkq3%BVwc%#JTHQ1wb|4RzDYJZCTEB8`lzUPwI z=lf9%`@M;t9X_?#_~Kpi3$P)BfI+|@U=T0}7z7Lg1_6VBLBJqj5HJWB1Rg&G_&@Yb z@Yi7eUxzOo;)l?8fZ+eG$1iV_kwL&9U=T0}7z7Lg1_6VBLBJqj5HJWB1pZG5JTo0# zeMw~JR-Qfi;6!7r5owIA980l~HYkG6qy8L4@GGv^5pMKYN;Xi`?w2UVo=ZuVWXhVi zp0%AK#y}B?dfqWvD@%LBE;M}`b@Vy9I9bb895KTW3Wvnj2{b%VJQ~4Q2mFq!J`VW) zz?ufsA*)j4w}L)PJXH-b235)MrC^+84Wbn4Q_0EQy5W?TF literal 0 HcmV?d00001 diff --git a/test/gcfiles/i386-gconly b/test/gcfiles/i386-gconly new file mode 100755 index 0000000000000000000000000000000000000000..910274770f9e14fcd5b663d0236e3ec37f77fd7f GIT binary patch literal 12608 zcmeHO&u<$=6rN38u&ANIM92tKwk6@0A}=6>I67(E9}wF7DpW{lYR8*+!CqUtYb6mq zM5>T#0#cB2LPFxii9`PcBsjDePH-rv9*UG!RN}w^p$hYTyPjram)=3|sc+}KnR(y5 z`F0)6+OI$Ub?5Jgl~M&RV?XeBZjZ$GQlNetPCmCBe&Y%^-pxLSuB&1$Qm!>Ch5 z=O9KM7&-LUfx$eDWDh1751|7X2-bDC(RPE_-@lIt7&iJNV34zW(#N2$^>P>mp1;~3 z9L!?}7*Anr2N-Ed<7wUT*F3+bJ%6QzNrMh^9RI$gg=<`Za*X*ZbrUI1TF&A|7`aVn)~g1UV8b~P zRGRKuEBGjMPP96HtrB@Hzqn$0op;?JL{I1(PmKeO_^(3^zGYnlzO}b6>@8WUI9HiyQ0jTi;QlXyQ!Jof>ifwJh9e>j2m``^Fdz&F1HynXpw4xF zz4TjQmrR`By8JVaPg~p9aqZ^DyG!GlZu#!+3mDsa=kDiUti$Mzjd#m`?mi0h)ob1I zAKj~$ekgqK{dRAHC;Z+-4%a$g7+W(Va#2TLA;i&U#q-q!3l$Ei zuaBVNh3;Vs-#p;old+9D27Cv>+6L4*#HGk@BsR+!)J$SCOwFcj*0XW~|33kwWPBUd g(!;svIG19aeOzRreU$F%dxaPK_ht4x#0nPr2ZBa1l>h($ literal 0 HcmV?d00001 diff --git a/test/gcfiles/i386-nogc b/test/gcfiles/i386-nogc new file mode 100755 index 0000000000000000000000000000000000000000..4349810f0d0eaa39d8ac541048bc09bcd48d49f3 GIT binary patch literal 12608 zcmeHO--{bn6uz@fZLra9w~IuSI&F7bl{KRv>SK1h=?`7o{bebF+$NjZO>{B|GZVTC zLKll*T!j_-D)P@#R%Cm($4gTl6`^q~(@it+nqa=Wuh?H^Fjp=a(n_uli} zbHAA^caqbe?*94D9;MVS=n!-SdV9B0A1m9kV7~#?dUo<;>D|)&SvW?9jWO6V?lWAp zUM!toOpPNWFTA!VI z^EezMFT+5bvXhoE%-_U;e=pR5yBwku@}T&5uDzWv=izxIYAjke+%PP!xILVHj0|!h zM#g?9Mzh-X-0gd~b01k?a6ximc!NtxB=D+9zQ=3*~3oX@`O1)hkYQ&8xzH2Fe^hdg(}W{?hB;ezX6; zUqAnN={EBmhN``f$NHl2*#~(ZX)}(0U(&)gE6cNgU~tAZ2Q%6RB!r)Wz*}NbAu2)p>sSn4m9Gw3N`qabshND_P(&UWU0bb z0eh@h24j#D5O&5iEAKBACQXD=M=^u@{}7yF0rf)9Pi`)+6L4*#HGluB{s_#)O2DqOwFWh*0XW~|33kwXnY&h g(u29^IF~}4eOzRreU$F%`-K-f_ht4xzzP=n7j?!l%K!iX literal 0 HcmV?d00001 diff --git a/test/gcfiles/i386-nogc--x86_64-broken b/test/gcfiles/i386-nogc--x86_64-broken new file mode 100755 index 0000000000000000000000000000000000000000..6570d398af036d501a9c0b9d6060fb2afadeab28 GIT binary patch literal 29056 zcmeHPTWDNG7@loYyV__su}DO%r)^SgiP=cKy$GAO(XGasn<9cvljfwmy4el88=8pF zV5qppLIf3j5c(qcP^3>jh^U}q>w|spf|76b0 zKbP;!d^0D^%+5cb{Pp&`N~tw)>*3Zam4J6^3XjGZ;-*bpFVrmvszx{wVL%uV2801& zKo}4PgaKhd7!U@80bxKGxWpLv^!K0svI~E+3DAuCpI@icQRT(3{#SRwY2Dj(Z}+3! z{SSs3$8CaTgymkqL+iorM+QTgSjLru&-db0#xa-HWq-2FP$1Fr-i=D#izIKOQVlK> z{RTDdRIN)>W5b14NGKR50djb7W1GB7;E zmw~~Jld4z@>RKNxm5bT@A)6Sj$4kJt7HKa5BP?M&ttaw_v-uI7&5st4DT;9t7-__x zjMgIr$Tq$Yr}f^h!LCq(hp_7p+ZWjUskTS!JU9%f94{A_EFUKv%7*Jq{+zCLZfGTp zxTh2`;t>pv3)jD_e;N<1AKZQ49w<&a5kNWR1wPBL{;nMO&y{r1A1xsgvQ&AK*ITSF z^I<#^)ri((eyKEc$hSCAj0V(!7!BTv7=@9EoWIzIwe5%lgD)f(jyI^3P(89^tc4v0 zb!}+@26=4pmd7BE!MEJAZ~y(>d-u8TGKB=b2^cA4B9F}LWUErsNHXc0;BA7^;u7Jv zLvMxO3%?yc+rV@d;#fEb{{Z|AaJ_J0VO~1dXW=v#K4o#7?)<9|xE_w*a&n?nOy;t~ z$&snt2=wdVSchkB*;eR3a_4*RG+q7knU9W~XFU^eYQq)A?Q}ZhAUC7hEXV&ZVc|Ot zz%h;WRq7j5xjmK~m@1Y1v1CtnxHwdtO5Rf(8uJeqicgl3_Y@}bBSYnEA>T3TVv`U1 z#S&sl$vvTTpb`HzIENpLod$kvv0Z3e@T!j89cW{AxV{YP1ccf$h2bX-c67NCN^M6D zw*P5xiWjIIRu4KDiij{E3tz~i$>-rjgaKhd7!U@80bxKG5C((+VL%uV2801& z;J?QJe}{ezj=%fyx1kfV2k_tPWNDEwAPfit!hkR!3tK!|-w}j)j6v-U!YrnGLSdd~Wd_<4KvGWka9X;)F51+k!_?j!ve0=P z-PP5D7q#tjZLVV_c$Ykd0bxKG5C((+VL%uV2801&Ko}4Pgn^5mfir>qe++BqX)dap z;J$+4F6{qJ-D#tqhFkIu0F%Qm(rtJ-k2sTfLDYtw`OPAqhY!MHb)HM$nw~yo)jI-U z_dvY6O>VW~T>$|eZT+p0xWTWr^?%&r(MFfz^gx5%wY__Qw$7B*?heRVyw)JQqHZ=b ztPaE6X&0og?N)?e&TxyOj=wpJ=iW~}EH&5$Z{>RlUa@$WTM+7DsmELSj)LPBk7I~J zJ&F;e1W&E~j5a3IPOt5cd5cF|X$p0-F%+nEPma5xXX^F^gjH%w`)Aqj;SWJnCUQAl2Y4{$GmRXW&Kp6*^_w zboL7O8+gGT3bfKnA-|KfXYK`1{j;+>-uiskO|S0!x{*6__)qRQKsM9< zaJheS&jHJAha(Tiy`W!rSe^YQA`A!v!hkR!3}HUb@3>CU$Jv;34?&?Q`oF95}RW)i-{WBfR-g~1DNpw>;stH7z)7W9)z(I xfX{7PFA>aH=|gOJ(K@u^(85u?sRD_#1(3d3yi= literal 0 HcmV?d00001 diff --git a/test/gcfiles/i386-nogc--x86_64-gc b/test/gcfiles/i386-nogc--x86_64-gc new file mode 100755 index 0000000000000000000000000000000000000000..3f3b82c3832ef9857d316ee902c278b6577eb445 GIT binary patch literal 29056 zcmeHPTWnNC7@mb9t5(@!V`@}9V9Ui|x5gVUBwL`eC<<-C5EC6*ddjYDcbna9Yz+yG z4aT*S)C5g@FyY0-2V!{g!5B3WBlsW>UV;e^Jcua-jWIqLHP-K&GqXK=wiodU_5ag3 zGyhz^GxN=yW@dK&`TWoKKU7LBfm;r@RH+2K`%-u`#t=7d;&ww_ji73T6A=c40bxKG z5C((+VL%uV2801&Ko}4Pgn@q<17H01!=HBHZ#Dr|qW(WEQ|gHFVp#vHO>kQ8?%dw> zR9DZgP~*5wFpsd@t9WR=r)%GyP$rgf<>2$Zc$IO?rFGdKEi)8Iw7hqhQrnT_tx&4L zWuo7p#+|BlX>6#!kkh5|NIxQ@7#U!26C(o**3(IRS%7gJB7lLyI^*Yt{i4xp+ffFF zhxjruxN%Yyi$PuM1Eq2?n?Gn1qxCojjN6cQ3>aYv<7qvTe?FTZ(AoT80hyv0CxMYh z{K;rNLV#@J$8cJ2>)g{BO7IYN{bBn8n?Kd|Xq^X#0hPn$;+*B-{4eFuGu6UAsi9f;B3Er?MV7|HpweOTL$I57A^a^ZM`N(t2?JH%Sp zVNlnW7GRLa7H@tG@)&%}J9j+sSl6~4?z>DOfo}pv3Yo|w^Ez3h)Hsq%dNaIcC@n4# z{zmAn@OQ)C2%l|WIty_uT!6nH{*7?E;ljeabgs|BX)b));yB&;HzRNd9KYq{NU502 zW&4u@W4Qt7x5KdxFW!51q37_!AAQht%a3P2J^T~vnSfI_-eBB4PG=lsJ*v%e{QnXb zzGFWe(^y}nzC)Ea#*)2brLsSi+?nk!_7%sHj}-fc{O1eB=Ss;(3M2V}zH+vZZy$8A z$tV3{2{EPQ)=)aoi2neb!;i(z06#X{F0?IpRr{89v@zRXUj}soLT#Bs|FZ|$J6#E- z)*=Vne+Zo71!{xUgARrwA`A!v!hkR!366z8~k$aR|P*og$2Z?E%pH4~8QS3wJTlkKG;}(x& zh(bMz5u^l9t^JHPCeu!@?T=}TM_XwMb+a)PsC7?{yK@*2b@v}&eASFQH~vE7F0`M7 z@{8PuVDTIb_p`$FV*iBOz1ZCdi3lF`Fdn}L?r}M>0HMRjQbJ_+l<5k!PFuO4nfX_V$VijL0nXkrvU>?S<%6Q-}P#<^e literal 0 HcmV?d00001 diff --git a/test/gcfiles/i386-nogc--x86_64-nogc b/test/gcfiles/i386-nogc--x86_64-nogc new file mode 100755 index 0000000000000000000000000000000000000000..48dce6ab2d6a3f3ccae5a3ad72116e0df59fa9c3 GIT binary patch literal 29056 zcmeHPU2IfE7@dV8D^%G6F*Pb)phYm)1%G*vWD67)MW8i(FbC9GciU@#0Z+mlL;Chcn~cF4KY3tCDe1~&TQ}AZ43AW_MzT;I!V}x~c7@wvKI~ z#&Mfq3Sqf-@X)%eZD&^~6U(@A@cCZ6$~flIy5tX)7z!j>-g{W7O-S+v2i?nl~tzz9niPwT<_{%pQiXY+gektvFC0vKt;pNQ5Y z1jsgi38(dj)~?o2f`_o{58D^m{He4@>pVCNs2nI2CM_Q)9Lk34O#Ym%b*_6ljJT&1 zG2#&njtkentbZC0t+%avZaowys}MjrP(b_|Fw}!QWd%BxJJkCa*V9U*^Mj zB&rdu`}|_Dd!KJ{q8N3k12O8nX)*eH2Xp>JA6B;`4h+7KTsYpKQbP5}_OTXr7}T_- z5g6pL(VH5BJOyUkGm@ltz~be22jEzTHy&Hs-*M=fFFtEn@W+|24_#(G6L4zo9OE8!I^!VAQEisv|Cg}v9lPO} z#`-Gt3#z;_mh3!GEct!OE!mzzci}*CW1+jx-``(&wV2%4KbY_BE@k`kEqh&T@iI|3vCNt)v~SyZOr!6mO-6>P+g|K=aoGzt*(Sp%aDWZ zKL}3o0=2^GK?g$-5e9?-VL%uV2801&Kp0Rv%I6PXY8)dIJ4fF70n?|E(I4?F*Ef~B zn(E4JSH~Vf+QJqWHoU2glUeCqJY#+Sbu9dG6gf4sRK&kPp~ z z1NdDYB=3NzKQMez8bIxmKu2mIYOpwXOdyi$@z>iqiuPcGvdq0opoKu6K7p&f+x%*%fuOnPGJp z<_^0ceQmcQ{8ENn6gB*fT0HlD>S3wEHh9zDOYnikyW4_L4@)iH^mh~-vv?ds6zWlo zASHNe?Ps(xnRa@0e~ei?+DcQXn~kABt$A|XoyCBtx&Hv;t7_c2@fR9*q5UM3zs`LK z7SF+OKPy}>_D{Io*Si}b5y7J##^d+EJuWA9oS2IOcuzpIy`II3+XB?X^>H}Pw|GTA z7u=x`#akZ1qaLmwzFEXfh{ZFTZA6UXt%~4L590x;3UA{7Qsh1ZFVe5jDZ{3-r@7z2 z3+_;$l~xM*ouoZ;FMt|=zY3A$jAQR3=nLquujQ>{*QMce72WwC_az_qtH>^Bi%Ko}4PgaKhd7!U@gV_-+Rd_H~nQX)Mvi}&HBN1pl<+0*53 z#yG=BA6?sS)V3=p)EMV%*62Td_+-LZ{U1l%c=N}2;`i#K54{hswR61r2z+b%|7*qe z|7V1vCjWS6m7PCSqjL@ww)f|{lrn|Ae{5lptY*H}p<+XcSfYM*e3al3IcV2cZ(83a zyLG11#TN#I0bxKG5C((+VL%uV2801&Ko}4Pgn`?KfrcfCj#lhpG^ET0>w5i`*3M4d zG;tKZy|8L@5`%!@Q`oF95*uY}3yCV*fW}E}1DNq9>;stH7z)7WeuuFWfX{7e|y@GoBXb>;v7 literal 0 HcmV?d00001 diff --git a/test/gcfiles/i386-noobjc b/test/gcfiles/i386-noobjc new file mode 100755 index 0000000000000000000000000000000000000000..8f01860aba841a005c4accf46f4d7cb404973b38 GIT binary patch literal 4228 zcmeHKK`TU26u!@pLRd*rh7i)sD9K8Z7aAc(Nx03MdGa*#)XaMsyID}|tX(<3SJvGEQfADJOyrz3stzmrHUR8PlhU&*Ug(GM_xkIeu;#i=i2AQI z22~YG>AQ!Ho0pEedw|WKGt_X$Joan2L-kd)YW;fm;=)qeoLx|Fh$G|U4aaafKXaA! z2~ka{;I9>TjN+XAJAo8p@}I%V`etASh#GT=Xus(BUp~KSH_!qk{cGy|Fe&46Y=GoTsxuMDi8 zJ&)aP-WB288sL|olB!njoAV%)*e-nhJ*&Mlf^x0SL~ec*>ca$=IxT` zEoL284DiC3p!5#@7`7P@X83gkez?yWFjF8;<51nd?~}oOl%2Bd`IfYx($NO?4%XCP ADB(pKjom4d zM%9cQ`=TgK!Zy%DZatN3dni2=HixtgrIbQ?Fx|Gh6be1GkV8^ROP~}=VGm2czxgA} zV>@gPg(iF-e)Im!dvD%+W_-}VoB7TCpZ`)Rbqetdh!aXpBl*?`*i0oxbvIFbZC~mT z87M#j3Q&Lo6rcbFC_n)UP=Epy_>2ks{@%y;!yo?Y4Ca0oaW|>d8+ZfN>n;+YgMSd(shT$c_9wb`n>ZcskjKP@}5M~*EIVEh1ssC!sGJVrXJl#C&1Z2n_oxO?q})AA>Qwpt$^ z<0bT!#*&^qM%!ujTh@W&;W4`57?PemhTX1jkA^rhk0JU_Dy1YnX$-j*ONd&R*2>Qn z3v1CmIYea^5$Ol$EZ0KXM&S`;E+Nh!j@P&BM#tGUQ_Vf(uve> z;ZXjV&!8;F_!H7eR6dOSRYVC7g;Cnd!?W?@qxeAKQM8h|-}%SWKYqKEx$ukgubsYg zJOAA|)cNzA!ch>PjLJwQ$nh2trLP=csSgno%ILd21E(y}X?(*>rP3Q&Lo6rcbFC_n)U zP=Epypa2CZKmiI+fC3bt00k&O0SZun0u-PC1t>rP3Q&Lo6rcbFC_n)UP=Epypa2CZ zKmiI+fC3bt00lln0-JAs^y2#~ScfjFa?ghoA5UeSudJ(=b-xmS60VPzysXckLeF4j zKMDxZ`uQl8{<8k}#E=d4MC~7RJ+rivb?UMrP3Q&Lo6rcbFC_n)UP=Epypa2CZKmiI+fC3bt00k&O0SZun0u-PC1t>rP z3Q&Lo6rcbFC_n)UP=Epypa2DwI`^3BCcATp>F(qPL{8}~$59tj@?r4c`}4X}Z(ABK z9k@85ZoQ(C>YZUiGX6Kkn<3(Dq*4AZ@>>%L_17E4YqIV?g^j$E;Co2t!u$`Az8>~P zR?Q)vMwEOR8_Dk@okc1^>YK7b_%qV8Xdv$s_^0F%W&D35#h<+9K+P(n+p~A-o@IFL zuHjj(w-Ec>`9h;_x0*(7Bfpxlzp`{?kk4n#R=u-5C={=jDle=T%QaogZLAb)nak7uPfGo;j#gGwr(RxVwXm&2ltK z)7q-{Ti&2rE0orjGnkJ#KR>8dDy95tZmpa_b7R}II$KW9w0fyvehOcxZ+~vi1}Ebv z{}wZ@Ijw%%a&et3({}oopYOAsed*%krK-`3>*?7)PqLXYG2(gdskj{C<}pbC3{~gA%q$H5}!U(fZ&4h5vja zHoxe-htct)ax{j#=Wsk|-S51NAC20^uW#&#&+ol(eyd?8DaXecA5YxA?Yk9nwcOaA zY?wbG6*rkPE`cA$&$DH_p4P{YHyO59*#q;iN?x}(3gLKmyS{D7sm8~f%mz)P@ud7o z=NZk<>A3#YjgL1Sj`vnDtdeqcO~R@}NjP4!?$!PJ_;@$bU&7TOQBwY-@#H$lHPgDZ zR(`HnSc~rAAu1(g{jG}buKjQ@xemB|qwolFmk?(V$LsM7r{myB1s*-v4eYy!k%GADyD1AO{coZGvI6wN&&h1|p zo}Rz)_Sa_rUb^?kIn>E<)zm|Nd@?Fa9l{ji5~B1Cj{6~U31#%%o`F-ADF2D6+=u}C zgMMkG2mM6taKC!rbD~~Hk8?K}Z(rSWJ*%Bsj{3-P9!HerP3Q&Lo6rcbFC_n)UP=Epypa2CZKmiI+fC3bt z00k&O0SZun0u-PC1t>rP3Q&Lo6!;$tY_7if(TnfnU3{{d_I!A-@l@96%G&uwq!ND` zu3MM9tRJ32&uPCm3Rqx!GwY|LjkJ+<&JxE%eEt0RzT@&Itx^88rP3Q&Lo6rcbFC_n)UP=Epypa2CZKmiI+fC3bt00k&O0SZun z0u-PC1t>rP3Q&Lo6rcbFC_n)UP=Epypa2CZKmiI+fC3bt00k&O0Sf#-37mUOb!WTT z?xpTbb@_Rv&Sk?duv_h}x2Mgz`E^S@|IK$2@4gjGJE7HSb!y9T)R!vD&*rt7nNYW0 z!N3*( zZ+U}itx#H9&R|C7{QRI+sg&}oxwUcz&5do->TEeZ)9R&y8LDkF)&3lv4^GEXT5fgY zn$zmHEf?3yGHs{t8s?5`$an7-{YwT5oVoV@A;#|epd$9q@PMu4u*du>HuqXh?Rx!& NZrGis)r;=S{{kfq;2!`0 literal 0 HcmV?d00001 diff --git a/test/gcfiles/libnoobjc.dylib b/test/gcfiles/libnoobjc.dylib new file mode 100755 index 0000000000000000000000000000000000000000..56167e2988e548060e5b67bab17adf9af211be26 GIT binary patch literal 41640 zcmeI4U1%It7>3W9YW<1W%lZR~#-f(&BsGK}EZxoSk_0!qCD|0ckTE+ui7WdPc4uRH zGYwb~F9mNEEEIdwURMPbyihFmN4ymDrlkm83%v-w@64RAEfQ1EQp5X^Z@%-LIcMga zXL7R#zW0Z%FTOIy+y~zeA2Q|u!t@$0!>R4QH`#ZVeQ=owh=2%)fCz|y2#A0Ph=2%) zfCz}d|BS$;n}2L2Km5%DSUZ2<7jPQz9?k*q``>&6cin37wJI8emQ4~w&c7edLj;=Z z2L476x8wZQ`R9)s^Du5O-eUXArp;Zq-+rgjuDgx)N(Y^{ui*mb&Z3{uzSqvRk?Ws= zyKZT&{A#fK}$^>~L5ht*1@l%LJbl`|Obta?7w z3c8-(O~>mqt6tjMGrPfNI)?u)&hbvO-|~C-OupCX^m~rC-gEeUCZewe5ZE?HLMWl` z_JV%RtwGGe@5UcqR}l~a5fA|p5CIVo0TB=Z5fFj9PT+E^|Ci#~fYtxo#32Ip|7efH zZ@_QY-{ZZ=vPrZd$?sGX@LY0EK`lI*&EpK$0c}9`?M5Gv4@05-5dUa#;HP%^!O_ zw!`L7XwvTw{oe2W-n`%Mz4^@OFaz(+Z|?r$mrAKq(9c3gl$wC~#=9uSQu}Q;;eK;Z z+MpB?AOR8}0TLhq5+DH*AOR8}0TLjAPZ)teeE7lL@WEew26G>Wem$+!t9TE9=YN$( zp>?fvwU#)seCWeU{Anm&9{8fQXYF{Q1Cxxu^;FPb>>)|3VdQIVx?a!jY=sTU9B(2P z(^O*PA}=x`(YoJx(e5<0-Pv@|n9Ok-u}f$d8;^_P#R`LO`wXh{my59@dDLSXF%%9W zTDL8)uFY26bp!usd{z$RkBluZV0;0CC_MBJ=g8)jk{n_a^N;0lciRo8otN_T(IPyS}xb;>bLPh#gf*i9IQYT#GrV*5&2O)1`&w z=$;&+F%N}(7r}BZq;Dkmqw+=QBsAXMvKu|?THo$j?v|Np?!s5+kVTkEu6>B&CuCx% z)OobYTqW<5@MWC0VMoz;2K7~_$bF$oUwMHRp99=`k_XXC#`#t42S2&o{c`We&wb}B zpC5nyN7HEY=Q)N^ppQpBtcZ*^3zb+IU#a(?DP{ECo`Dkw!Jvj6(7>sjs0d=&F2GRnN?hQa^%vCtQe6cQi-5+DH*AOR8}0TLhq z5+DH*AOR8}0TLhq5+DH*AOR8}0TLhq5+DH*AOR8}0TLhq5+DH*AOR8}0TLhq5+DH* zAOR8}0TLhq5+DH*AOR8}0TTFx5ZHL_{pa6W#5#0Yk^4|M>A00OzOt@f*8Gb8G+ZAq zby=T3hR9%LKO9K3em=4iFKd5Klzea`>VL0mnWdksQ!I5(nhy`<9WjbTbE{`bV2A-vzhM)f;Xh z$h!Xw3fYt3b=c{!{(ab2!dO(*H1sr7>J=1H--Dfk6_NI)6p(jd$I(Id6Zogpp_2dK zu=vR~2WmSy_v+P&Sx5ZyVW!bYsICU{iV4}gJLmfw(6a&!9r=Z zTzz(>RH^A&VQsNg%U#T7E+$gLqtQ5-SYB84`lnWI!ES7RWWtpD6ZOxd2mC{PC z-7^Q(^<2AdI_}P3ZKD!pX~T8 zo@wSN&c3l==evIXqNrT`OI!!AVL!(GdEw&!0TQ`w!!=T6@-xhe{~PeDeG*672;T z`9Uknv>NW$QPKL~1%>~9A~C;c-@|D9$d7W!K8JD9y5D&b4~^Q!(>D$z=XX1t-%=PP zelkZqpK$w@&uyVlE5wfE!}tEeuzKJm3jUcTOKe{Gi(;*Y)Yu3HG-=55O6Y(Oe zfl=arR6e;5a?P|ZFIS!}Ei6a(@DPn6dB0TA-L)4clk0%Xw=egjmW572+Rj7#6MP-Vg zgG$WDl?M?Z$ z8GW~B;G{+6ADJn{AfSiDWuXUgLho=~z3(|u)X{P7q?7&EcU{kFXXc|ABvg+=W&S%u zrR3gyEcAsZg#<`|1W14cNPq-LfCNZ@1W14cNPq-LfCNZ@1W14cNPq-LfCNZ@1W14c zNPq-LfCNZ@1W14cNPq-LfCNZ@1W14cNPq-LfCNZ@1W14cNPq-LfCTMbJ_k4G9ef}Ax&gR1}uv_h}x2w&% z`Bh6jbM1}Pn>T_fr?fh)PHj4ldZ0Rgxv16Th`RL>J_=qe7uMJHw8rKHmsU!(Ld<_M zt!}=YQfak^G7t$7k$nQ1ek~||pYZ<#e{&?I{&Br@MS4hl28HZ#Ao4xbr(^yPQGX>k zH$Qd-Hid*-0Jo@>`l$K4sMZB(KxO>48>Z+V0D+Cq7GK8G2Z7cLBH)oQu8R9LR$ z(B0TFtJwhJ5dS*1u$6 pfiu_sA0l?&2Mvio!#(zrVUPJMHg{W1?Rx!&ZrGis)r;=S{{U2%Gzo*_IPZE z&7shw-v>SK^FHsq&-1?XnbBbe-gn-=^XuO#rA|S=0v%Oq0_K$uQH+o5w_St#gFR`3 zQb>RVNPq-LfCNZ@1W14cNPq-LfCRo^1paXQlRM#qzdD1tpM`!urPOP94}j-?l|rF) zt$3xDII&#l!%F;VC|(};qP1u3c%cK6jKBV^pugBdl2XIS*V=Twp556B87X&0<2}SKqg`w~E{Yc`41VM@s4P5Hh#kqH9@B`Sa1hbD zZFzNVw(71M_($V2av*nPYCa09-5Sy5PEQh<>Za6J}66mY-;T+E+ zRyvD4IY--R_FL8g$HO_gVGgk;=dj!Lt^E{7<}pOBe@Ti$DxzZczerk^sK9WyJxvuX1cixU!6x5VJf-yA&Q@niJ?*# z&?a-0yidZHasC23hQ@nQUxSL=6RPx;7ijT0z`ZMZ0KH_Kw|?@cPrviKZ~o>N)xzvw zZ>RUB(dN%{9HT%Vi+orS8E+0Mu`<3=A3;Zy(RX_WPFU1we9uhhLjX7`F~Z;=MrM3C zrr!6QDB|cCw^GUe>$|RJwbKhx%p=Gs^PV3D|KmqPUpP`ofCNZ@1W14cNPq-LfCNZ@ z1W14cNPq-LfCNZ@1W14cNPq-LfCNZ@1W14cNPq-LfCNZ@1W14cNPq-LfCNZ@1W14c zNPq-LfCNZ@1W14cNPq-L;0r=vyRn3I=&~aB!En-XD{FjZUA?UN75!znK3?jw zK7SmM!ODI(kZApUWF=nK{+=kg;7HW}Zr3tPKUt?PYqMqD@2@bg=i!S4>ym>yOA;Uf z5+DH*AOR8}0TLhq5+DH*AOR8}0TLhq5+DH*AOR8}0TLhq5+DH*AOR8}0TLhq5+DH* zAOR8}0TLhq5+DH*AOR8}0TLhq5+DH*AOR8}0TLhq5+DH*_!I6tbczob&??cs=2{B`@y5ZVW@QT-O`o1-J@AFmfL z%ewzG3fYt3$FS33{b#VRhOwxsY3OOF)XONOz6U!6D-n zsq*YH$T3pSx zd*-0Bo^97n$K4rJH_B0#rnOn`x4gl6ZLzenki~q=ix&sAN~Kg-&aafS=x%J8R%g@c znN}|y%unIl^zARr+2CXx<=8? zFQwFL{w{q=eGUFR9;&sj6|dCbBqf(<2z}Y-PoClZhjCb~J!{89C6r`7d43m>Ifz7l z;Gs;Z;eH(ztq)#M`0pnY^NaR9jK+`rC`WS7L9P3p7xB=jZ9IMZ`46A(d*S?+!vx|d zb40nt3b!Bn+!ph-eC$Xrte;?oo6H%Pz?b-Wwrtna`uKcfVSkl7U>-Kfc8mKG=Cj-N zElW-{nQtr?bdBN7p25I%L9p&AM0j+mrdOAzoxH zFiQN-$|u)Bu9?=QmGaZY#g*tD9->hs=a(wFyY|9lavgB__T@g*GSEqAydCdwde+sx z-LqW0ufPikyWz*2M@H!{c_&~dV5eY{k{Jf`JB0(1dkA0p4_`(*8t+AY4JsmaQJLab zp%U|X+xOwx&H@^GIcNj zQAXeG88~TC`A4SnF$m}(aT(}AoX|TQSMPgH6m@i*Td8FK^f0mlyctY+M61*4)TZO8`zs4i6||ZhRo7p_N5KoF{QA0{*4Uij(rU4mkNHog z)U}sKR7&lk3`9aiWS@YhUki$x3IEUVH%CX*KVL6imL3wHMj?9~i2M}w>6rf(>aPZe z{1UF3hMo!A#4n?W{NJNK6&@4w_k<5uHKhpUU#OqO$Kj)W21aoL;z#=rOp1>_(fl5Q z4|hL9Ch)_r%*)tDw`XtHJvuksZz*h2Bmx%4NJu=zR+%&p1p0& zn1j{gYPQ`o2bJ|~yKXw}&Y-$cjYRgQwzeMMQ({YrRTiv+qwEAt!#kI0b+v&T8x$PSAz56-;l7R)z nT>F2B*nJ-~B>oI{*-M5!=C9b?Z8f#)^&7fjcbZl&x-b6&uxRCk literal 0 HcmV?d00001 diff --git a/test/gcfiles/libsupportsgc.dylib b/test/gcfiles/libsupportsgc.dylib new file mode 100755 index 0000000000000000000000000000000000000000..8a1efdac309cb57c33805b544ce9ff7e6ab022ee GIT binary patch literal 74696 zcmeI5-)|#V700hP-X&_YY!(CxYAdCysM>ak?Omc(Bx+&DF-cngNE0X8K2*2%c$^Nh zJ;pOm6A8IqXtk}x!~O$E3lH$PeOOAx11}X)1T8EG2_8V|LyLHz0wH)Q(u(hyAIW5r zRNDt6TE0g<_uO;mo^$VfX8bTC-8;Xy_tT##rA|RV107Rp66V|QqL@hRx7~vKojqxT zQb>RVNPq-LfCNZ@1W14cNPq-LfCN5a1b+452ls*pe{}|PKL`C*QmI$)9stk(Dv3ht zTJc&fc4E1}hn4u#P`o_wL~GaFaRUb?9)J65et)rtB&kNBueIU0eXF|_G{keguZlEc~UH0-uF3G~(aaE|8@ zE1kujoTFnm2W|6!y7gxf2 za)ibl6!u*N%e9ccp`1nK%g`xkw7qRLoI$T=_g!bpNHuris|&~?OeNPoMDb%XIa2B( z+GMVh_euCN&L3dM(fA>Z=Mqv1^Ptj!-~jwb5Mzu@s)ZHno#M1(@*1sMeW8nj8r}VfTI#43=U#s#)o6- z1J@2Cj*f9R8SlTo>$qkowGhTUhKw@r`H}xWemwAnBZUM=fCNZ@1W14cNPq-LfCNZ@ z1W14cNPq-LfCNZ@1W14cNPq-LfCNZ@1W14cNPq-LfCNZ@1W14cNPq-LfCNZ@1W14c zNPq-LfCNZ@1W14cNPq-BAp|yFd;ht&maq<8R^&b$Ogd_1jjyb$mo>klKMvN%OI_CI zPax7?*$)R2uAdLB#LL>>6D8*#3H#sgT4w1d>(pg!wygX83Fh@Yd=Y)oy4VsW)p zeYRYz)O0Pswp6TTFK1GhuMC$q@@tFbe5q6|WHZB3zJi9OViu@O!?m`}Sz}l(mb0C{ zF|4j(?#}EB5M|{sgtq0u~@zAI(Jbiol51;Rw!Tgqk1meeY zgt^5Ew^u!Gi}_kUawHejPq4yG=8Q|=N%TD1mg8!De7^CZzsemj51M4V#eE6#S)KZp zDW@9GH=gsmhWW(*sPhcxXLlX%>c;a;2KjFKX_fflH3^yynIK=Y?$*8bc)nYR7g_a< z691#}$#sxxrgdqh@^o==CA^16XcWnLr3&w^y&#!f2VA~=Ig45bIt7ik;~kDO==JQr z>)?F_UO-q4FXjR=N`J{a2{Q>h4I7ut$e-US9FW{2_|kuL9qnj*5cO54h}1=8il2o_ z%*U075Fq3HzC8AuH|~7(=huJqxwHTL`tLSp&?e)mi3dD=BJ`yVG69{3N~}Nbd#EK; zdf@caIB8M&N2c--22gN3!Xa-Q`HiIqMV1N9n%BEEh; zdhEEmzef1zk&l~aFLyLYwB=fGJnR=epJ=~u9eT8lbrK)}5+DH*AOR8}0TLhq5+DH* zAOR8}0TLhq5+DH*AOR8}0TLhq5+DH*AOR8}0TLhq5+DH*AOR8}0TLhq5+DH*AOR8} z0TLhq5+DH*AOR8}0TLhq5+DH*_+KG#{!!JN?&W$HdsFKR&nR_17kq);?DX7SZPbky zO!dq+Ur*e*=}$SK)oFFAW!viW)rF@DT1}0q+b`jx;Du6teO=FJY)){sT&(3I{*y^{ z>v}>Z)gDS;Bt%5^321sXzj!C+{}z60ETR5#qqr$OBtC^g_BasvKI$_O|A(l*>>u(< zxM~J^CTJ7Cf+F6EN6G-uj;FF!$KizwCmli z;bL*MRDHHwtkiTZzqVAYWiMw^m#++$Hu7tW<$S4BEo3vpQoe$QrD7IeXg3Yl+BRp6 zVYyh&cKXJ!x}NRS4cplnu5DDpEKRdjAGF=!dTp_^vXI4$j7yh>wQ99gSkA9hvgn@P zGR$tv?i*%5<7%Q6rQDpT5Uv)2*v!$R4oK3SwIP@pq0FKU7Xf5Ar2)ff?;iM;w|eR?XGD8 zsuT%0xB2MV;=++jLG*y&R3VTs-`kzBch^qNNX(PQGjHC^H}8Ej zJD%P7{;%KuIl!2}Ok1opaCcR=r_W z5^q`H$?>HwJER1jw1;@SVL2VkMWZC%s=#|#0FX|+4`s~1cJ*?NH!Z)>GMm;Io-6Q9 z2msQf62pdp^KR>k-KulDwbl;w_q zWwcJt7%N7k8P-E=oyhePSto=$nuqmvWSJoD$#^L_1aP8$5l%3kuyfvWxVPD?wjJK_ z-7&nB@K3r$dJ-?nBj=8}7hXo-N&iTX;?Z-lU;2)m^I)CWO>^C%sZGY4P2iCp^(O)q z6a}7VIj*&a$0Lb1Bk+&~LVDCcJhRdi#j81{=MiEO@1($!@sb|J14s;Sv@ewxE}So( zS&(~5Hk`MQ<>Wypkvj1vO;mpr``+_Z$TCJ_r;slpm$I4-Ol(8s#2KyS9q6=1rk`M} zgZvPZ>__-AQTymTL5&Og)Bcu}dr^@^nnKdMo~t`{Rj+S4b?79IAdw%}>QDXt>3662 z&MRM?`|{G&o5xU>Mq-B!2I(28Q{C6tgAp(FOYVy_&MFew!gX;3C5`EoP0zQQdfBeJ zrn{-1rAPfr+kMZ|3+-CloDH6Rs?aZ5u4lJfo^FID^bh@SARpmoq3wxlbUKbzskwH? zU#wm(OdcW8DB$U2WE@TV7PMm=$sPC$Lz&Ibf=9cG#*p#2Kg4uAw^!U4`n>H=a;u<+ zHi7#t@=@Hk;Lizd4?fSfyREwEgTl$!BfG$l#>?0()W+kb9bwjpM$6cfXhrMe^K7`@ zpBDNKbR~)b#eiZ!F`yXu|1t1JsrR$-rg5>dw9vcL`@6J#I)g)8X?tEnR^C4S11K>D zKceDKluEsuP(2^X>PoLU{V2w^EGF9;LpJXijG|I>XH&r&K|igvxYoo$++ow&F5u$yt%(RWPmYzD0>J#`b(h%=$WTtbje9ycZso$lms#Xjr1{4E|0mXn~ zKrx^gPz)#r6a$I@#eibqKgPg)M>Cz&GGq7W=yEBz4Chtc599LsbvM<2AjcQ;=L)<~ zHYycX&avsOSFBc@ZQa1vjX<6Hu;nfowU3)xiX(Bs?lrNgWH5o2Z*kLWW;R?QP(ESQNV^DP< z&*B&Q@JcNo>huXCkR|-WKD=6_0jaa-kdqEf@@_&ryRZ`{W{g?ath+Dk`us~T%+1fe G$o>UZ$ed*W literal 0 HcmV?d00001 diff --git a/test/gcfiles/x86_64-broken b/test/gcfiles/x86_64-broken new file mode 100755 index 0000000000000000000000000000000000000000..4346f8ac435152adab696f59ce65004d50094f49 GIT binary patch literal 8576 zcmeHM&x;&I7_Cl>8I#4#Mv;g==!C2$A?&O~!AoFfHxp-d$&Z~?M9|92PVdfWPtVdl zLw3c(K!~t}z+4o(?M3hq#FPJkg2~B)B01!!vH=AV52E1sUUl_sb&s37P#;uRy{fNX zfAy-msqXsruV4S!FNCvKi2fcS#3a()hlRK_cKsld|ircjI!5(|qnB3JMIL16G|C% zd-Z5km5uKm%{QSv5}b6%X+B7%EasC<&tLOG)MWX5&39b;6I1t*jm6igzq>5kUbh+4 z+TIx7K=UQ{CpIm`jRq-~qi)y=R<%<$zK=EEBf6c~v~j*(aJ3aQWGh(dz%k3WrTMsq zXvA5LxFx^_5z8$@(+a9eKI- zXi^5gj5gZ!jxX1`;jVlM4cD(37w5+ux0HVEL40}iXM71CvB}5h!Cco;s&&$8*H%4F zMRvTd=KF0J8?hM==u)QnBF_)K6+9kUz7v`+r+X2b@j*A#?3m_T_G?i@#w_1VhL70f z15=7`=e@Krf8n+Ax%uRs;%4R*nATU59lu9(QPm$q(+o?l5FASmc@cSH6<3H80BgwU zN6onQJ_F{Qx<`e$g8U%TB_z}6%Vf@z+0Jqf{gY?cYIMdEv>(2K%?->MfH8-4?r z_7oE1`1HGbC%^pu%=4d|`um4lozshauPC7o9zakB#Hj=$r(rC8_TzieG~VY565ANF z5I-X9(@vqf5p}(GVWCwIYvD%WJRi-go$$S=aK6(E8nte#6HKcImbHa9yf8vdRG2f` z!AAae5>i%X^gW~hchhO4{@P3-H$7Ox@7he;yHLxhb=dr*OMA^ z-mF;#tO8a6tAJI&Dqt0OkOHq)Hh-#I`z2qwvCnbvDSYFl-{D@_{AQawjLOZkmlZ7k z`Gwf#p6!J@o8>V{LF<0{0$&1XJBR;muCt|K2bS$Y3wY{;AJ zdjB3uYJ)7oUJ&3L4O=+eQ#?&Sn3;2r#y#AN$eH+EN@JP(<_Yx7tKO^?&f_XW+xv^c zS5lJ-%i+x6aFHiZ>ZDxOaoCHK?fz|j zkT`S(9C4UGT$F1#SnjTffe4R0R*VlM+tF&(3mT}?=182W?vd@L^wz!QUf07}d5w%m z+qG68ruegAuh@E4yC(%!A9B>mmij;Ogu+7o14;goARqnFID>Y8(Gg-P#-vd;k1N_o h7>7~HS)cb76YS#Tb39h5V%_!nS2NTC1# literal 0 HcmV?d00001 diff --git a/test/gcfiles/x86_64-gc b/test/gcfiles/x86_64-gc new file mode 100755 index 0000000000000000000000000000000000000000..23246c22b53107dab8a66a5c06b88481390e5dfc GIT binary patch literal 8576 zcmeHM&x;&I7_Cl>8I#4#Mv;g==!C2$A?&O~!AoFfHxp-d$&Z~?M9|92PVdfWPtVfb zLw3c(K!~t}z+4o(?M3hq#FPJkg2~B)B01z3WdnK<52E1sUUl_MO|P4~P#;uRzpuV} z{ne}Lrn~F=zkd5?zYxw|A$og+5R)i(9~I)daB(KY(1IJ%7y$;mPv(n(w%dC#LR0+mTEzQR@ zL?g~}BrVBNO0OwbLJn0~mTz109nqbLv37SguDiQf5K^8iEtQNgtgo?km##l!-I14T zk0xc{%V?ur@Az^p4tM2CX}EsPyf{DRxTW-K597XvAhdiFb6=e39pe-U@DyEZ>O?AF-Jqbc1JPns3>!MG+aZd@~t7Vv`R{ zV|=6M(!%_OH_GSc(`SmCnO9(1Pfd369MPxBe+*4CJadJhHaXNq)TvcmAx;3Sp=KO4 z{Or`wEz4nBo%yz)E3E1TbK^9`eN^Xz2>%YS|) zw)xKX(w)unADg!;*S^ia`%Ql^bql`1)R#C4v2gRbPf(SrgQ@GN`%O9@{4y8}n(X@E z9?sMYvIx6DfNwOs!r_|YS;oQ4n(t_QhkF?{3%^TgJm-7!1bXIGZ+eCEq^i*N;i7QX z^G<$Zf38pRjC}+ix6|f3e1*`&A0jq#jz$#!BunJfOtOEZp z1tyQ>7fbkuF9cd$F_KyN#a} z%A7t&9Oe%fWIWog zwE{84pACD(*7G_%J)!%Mqjt8`|B0s*7U~~J`i}(l@Q=nBv;z#c5K9Rrjk0+h(LTi3 gj8egRzqgoT7dxLjbCoLBT@)K1cq+;c1(Q7f1%l~7zyJUM literal 0 HcmV?d00001 diff --git a/test/gcfiles/x86_64-gcaso b/test/gcfiles/x86_64-gcaso new file mode 100755 index 0000000000000000000000000000000000000000..9a58c23c2353415bf3e89506ea46fe2663d39895 GIT binary patch literal 8920 zcmeHNO>7%Q6rLrN28ifZDX54aIaPuJs^V5EQZAM3I4&-Pq={1m6=-5Ro7iQ&wszNT z!UaneKT*}n9QY9@4shTWAugbTkU%1pkjSwI4jfvPN+5y6A>vT;z1g2P z3OV=uM$zl2biQf9Hy~O_EIafIzI%isE(w&0WVA*@O-U&-^`inA;l>GoX}eD{ia z!m9N0HJpnTXOUN&r5YOQd>;rtib)c}I!9O&{&H@W`I1Xh4MgYrS@0beoe1Mv*j2i5 zJei(PE6otEkvNv|ti&<#%5~!0vAMTeE!J#a_g&0$XFf?o*Mn*ot&ciy&h5fpd?xyn zeUgu`%176kdYyjusXkhMEQ~S z)f_Ll8|Zw;1Yb+^A*|V_uyyS7EZen~a68cXz7c$KzJygi+@qlBinNceTVmAtHZ(rM zDj%43;#(~EgnCUPoE+ffXJ;*k=KxtshB z9(k`)+dKe}K%)D0vf;T&yHZRpuG)*hB#$7G9kcKMVZLRZNMHTp^@XX;uYMSY4z7bi z_XX)GiAe{+T$uOh0NQ^Q)e3Rg+VdqFZoRlc(rbbg|~X>LpLr z8V=U8Qgg)AhP-5gU9|eL>{gcxcMzG&Ys- z4iqG09g*Lm9mT&W&~um)$(v%l|H>o3S3{P6{*~Va@_l)?9xKYccWV>tV-Lh0h&>Q{ zAof7)f!G7F2VxJz9*8{MiFjXAwFf z2FL#f`gpa>tre@(@GYFl7s%kzYN6t=qroiMaoW@9W39(=YNJ{fZp)mNhKBH5AKM&4 z>+K=Eu*F1@k~qPl>mJTH)b%=s@8DM+_%+JXi5^`o*1ea1 VIxnljbyZ^^y3#TVdLlBI*xyyL45|PC literal 0 HcmV?d00001 diff --git a/test/gcfiles/x86_64-gcaso2 b/test/gcfiles/x86_64-gcaso2 new file mode 100755 index 0000000000000000000000000000000000000000..3ac79a2f133918ea56a660a31c210729f126b194 GIT binary patch literal 8640 zcmeHMO^6&t6t2#XS(C*~+$a%$=v}fBf7o8pfS16`P9}EZl1+A2Sp}(`pWdC0Jv~Er z57`xkkr2tS1jd5_ZyrTFcu?>dR4_St_24m)2`G3|R2;uoT{Y9wlU?vA)Pw4(_v+PG zufKlX)6?&VKY#t_0At2p#@c%rV^e6K!f=(D7%}!3nvKT!h1@HJHw(*4WE~Y_RK2f8 zIGfmj^OeHt%BYK2pBc3=sUsA&AB}nv&Ld|tipQ~@!aIzUAwT5;eV-arzmK((D(7L; zDu)A8I=(fDHz_@ot`RN-|es`;h5 zGltidcxr#rlQM#h3C=5F6uAC|bV|o}L*hLs=Sfe>I9|)Y?D|#i`s)oirtvF=5}?E9(&E|kuNF?9RnHVNbzPy8+!Pl(evZhYu+QMRH%}vzG2&(d{VIATYbIkS zpj<;we#9B&-c9Iq4eV(=*U%qAyMUGy@};J7C^@Ib^H{I$U1g*GAv6<>o;y}63@pzr zTh%SE3Z3L}G>YS!YhTTL*gEyWr=VP0uM-0=F40l`rM*w@J=QIf;EXe6CGy|Fe&46Y=GoTsxZw$O}y|uk?yHmK+xn*DZ&V1|JZh!VO*!r`d z=xsEaxsVb|lM}7S82)elb@p^5gt` zaRJpG&tVsX`R-JfDo>(P&dVtFqO#q-g^v|NjPB8~wW!xPl?au!ZjhPE@+Jz-(K_q0 z;MgPCxKLRSH;Rs5g-wdVQ77BO>ox9dI+a%BpoU(eou=z0*Jrcz5n?ZEKPBDOfXs&t zQSlPrEgls*9fsdd@I7PjrQ=0RK|yUNmUFQ_DABFP`k?f9`NqusTd zAP$zQB894wFXfyg2ZZT&f2?c(mT?8((KHe`R466 zZ)R7sJKz2F%Rfhiunr5+J0ygdLfU;;h?~MjNr)$qDo9dZDZN&Hr@VZbwP~}`@IMiG zHgN+fSITd%q+L}1e7a6(jM&5k5_6iAp|crA)^osXbyGcgSfzNTw{}lTF9naD3Ec`CQF+R{IlE_o0o&*Qvk1D%(!j^lEKq zjIXcx;`TkF6v%eSNXxQ1xN zS&pbBx=QIZ<(ki-D$DZiX}(js6EW8AfyQ;Wiv=O&h0;nX;fD2?nddDxQxqavH|cV?VwZlg9g8Lt-0a7UD;Q z{eo4fZUv#!E-ba`e$C%1T;!v9z2kon6fSnUUZWPaI^K+WU|Cyu%kcx$1cik}JJ`to z3R28(S@*zijmC!YMYo!npTRg=bvVPa{SX&w)BU*eXG+OlLOcsE>ab%+^ZJ~|oHt`e z0i%FXz$jo8FbWt29;CpVmF@eL8$ahOw?Qi$E!>HUoe^tTqpI?eS z?%C$gC%qB(YS|u2{{wLOKB`~-#m$)dDWY>!aAukw0*cNtj)ZY zpFEQ5lRRS|frr%}>zvKppT^&r#Kw#QMggOMQNSo*6fg=H1&jhl0i%FXz$jo8_$aaim;qut{ItB+giWJ~>@cwAwj{(;2*NRSWzXuODafZ-8hDZ->twumd*hZu)Z e%2}WH7GrGVY}T7@ln?HEBehf>043+E#H@5Q@W>#DPe5QnwC7{fGlXNOWq)o7k{kN4sk^ zK^%;#B1KgtU*Mc02ZXrs13;n`2d)+3$VV!{1#v(iVV-Ap*4bT?-jU{&W@n#w-g)+& zXJ%KsJKz2F+doHyuqK4)9}+@LquhN^h#SJjnGlboR8ge7R6bvMyRveT-n3b1_@9V8 zTeyIftChD_(;=#VKJ614BR6pvg*8pe(Af&3^9ZN;>|+=V8s>Ef@dNwe1pOqGG6;K( zVC2f?cSZ9}>4+G|9deovk|~S$WXo|k9Uq=7pR4(1bUZP2AK6%b-Nw6Xvg3p;uikOS z_y(FUzCLl%Qru{eaxDn`wzr{!viW_Y`5w~k#7P_H>v`AOUQ@Qc^)3Rle7l;DYludi z<%n9Mqm)iduKOISvMk@8<~yM~5o7HhXk2%9u^^;8TV5?E!mz$l>n>h@iFHR_u05KR zo-2dxPNVC}&CoxPFQ(!8P3Fb? zcx}y>Lq8gEGM~u1f7E<|mDcR2R9Qw!iZnEuR02_tRonkNWi*CD7Y;LNX z%;Vb+ue>@l|HAn%C;u)?eE-X7K39}bM<&YLl# zfKk9GU=%P47zK<1_fz1F>dsHq>%Zo!HxFADK80_-{5!&{JKyZ_4WoK%{<4CVKfe%r zd}n+9&Q9fzo!iyxU+3TXYA`H(2H&voIgUas-+Jm}ROQ;Ra0B(AMd!Vrhr?luUGM#X zGxdTj{GR9G8x5~;xTbiLaj;~~cQn4ky@Z;D-=#F3^SyZrJ@cwJy~27_RcL#EMOa&T zD?fE4Hz0Y&J^&BhAL%-7j=wWwY-*!`QNSo*6fg=H1&jhl0i%FXz$jo8FbWt2{yz## zpUSV4@egCVBrh(!wkQ{u%e9)Ekr!$U*xhj*aoL|694~R_N$r%&1~z-Kv)#XqpB2ii z0Y~fSkC)^oHkQM6F%bTVM@rF#WGC3DIbIV!ZI;BI>LJ-_NoUJh>xB;X%Ijo2*{QcZ zQQ(h_39C~YF*hxJq?6E+w9{Bz7&e-? z>k=3K1pk056W!oOegYd;#y{YKaL)AA&eZC{^}ETP_s)HH&b{x@tgyYsXwpm)4n^e2BT;py@N_4c61|^E#>8RtCYRu7 zDKgd2wnGQ74Dxj*aT&zNT?+ANKb_zx4pm$A7eT$EL4EH42ZMaaj*lA9h^f=xOx0F( zuTiSmZpI+rnd7_W2#9-k8^+n!e`9TPz3^bIrycZ3=;rh~b|lm-I(K*$-*;(Pfx#Gf zn8!P^$2>6mtGf6E_Ci=Y2%&;Kn8Ps4MqW+S&BlyjBC{R%McI-GrcIsN+M zR&n{!$KW9K>dlAuw-G~2A`_P*U+7`RU~{-H-{bd>8|&oZJd1iZC-^-~zc%VWK5T}z zws?OLeFWw1+jp_B-Vz@Cx8@zDKHj_nUIDLwSHLUa74Qmp1-t@Y0k6RSDDY(U?f2zR z&p&gPy5R`oD`ytYH21L5-?{mQd(LTT%rcq&Bxa@%qorPMi)m*}COtOZi4ST +#include + +int main() +{ + // Class hashes + Class result; + + // Class should not be realized yet + // fixme not true during class hash rearrangement + // result = NXMapGet(gdb_objc_realized_classes, "TestRoot"); + // testassert(!result); + + [TestRoot class]; + // Now class should be realized + + result = (__bridge Class)(NXMapGet(gdb_objc_realized_classes, "TestRoot")); + testassert(result); + testassert(result == [TestRoot class]); + + result = (__bridge Class)(NXMapGet(gdb_objc_realized_classes, "DoesNotExist")); + testassert(!result); + + // Class structure decoding + + uintptr_t *maskp = (uintptr_t *)dlsym(RTLD_DEFAULT, "objc_debug_class_rw_data_mask"); + testassert(maskp); + + succeed(__FILE__); +} diff --git a/test/getClassHook.m b/test/getClassHook.m new file mode 100644 index 0000000..1d671d9 --- /dev/null +++ b/test/getClassHook.m @@ -0,0 +1,128 @@ +// TEST_CONFIG + +#include "test.h" +#include "testroot.i" + +@interface OrdinaryClass : TestRoot @end +@implementation OrdinaryClass @end + +objc_hook_getClass OnePreviousHook; +static int HookOneCalls = 0; +BOOL GetClassHookOne(const char *name, Class *outClass) +{ + HookOneCalls++; + if (0 == strcmp(name, "TwoClass")) { + fail("other hook should have handled this already"); + } else if (0 == strcmp(name, "OrdinaryClass")) { + fail("runtime should have handled this already"); + } else if (0 == strcmp(name, "OneClass")) { + Class cls = objc_allocateClassPair([OrdinaryClass class], "OneClass", 0); + objc_registerClassPair(cls); + *outClass = cls; + return YES; + } else { + return OnePreviousHook(name, outClass); + } +} + +objc_hook_getClass TwoPreviousHook; +static int HookTwoCalls = 0; +BOOL GetClassHookTwo(const char *name, Class *outClass) +{ + HookTwoCalls++; + if (0 == strcmp(name, "OrdinaryClass")) { + fail("runtime should have handled this already"); + } else if (0 == strcmp(name, "TwoClass")) { + Class cls = objc_allocateClassPair([OrdinaryClass class], "TwoClass", 0); + objc_registerClassPair(cls); + *outClass = cls; + return YES; + } else { + return TwoPreviousHook(name, outClass); + } +} + + +objc_hook_getClass ThreePreviousHook; +static int HookThreeCalls = 0; +#define MAXDEPTH 100 +BOOL GetClassHookThree(const char *name, Class *outClass) +{ + // Re-entrant hook test. + // libobjc must prevent re-entrancy when a getClass + // hook provokes another getClass call. + + static int depth = 0; + static char *names[MAXDEPTH] = {0}; + + if (depth < MAXDEPTH) { + // Re-entrantly call objc_getClass() with a new class name. + if (!names[depth]) asprintf(&names[depth], "Reentrant%d", depth); + const char *reentrantName = names[depth]; + depth++; + (void)objc_getClass(reentrantName); + depth--; + } else if (depth == MAXDEPTH) { + // We now have maxdepth getClass hooks stacked up. + // Call objc_getClass() on all of those names a second time. + // None of those lookups should call this hook again. + HookThreeCalls++; + depth = -1; + for (int i = 0; i < MAXDEPTH; i++) { + testassert(!objc_getClass(names[i])); + } + depth = MAXDEPTH; + } else { + fail("reentrancy protection failed"); + } + + // Chain to the previous hook after all of the reentrancy is unwound. + if (depth == 0) { + return ThreePreviousHook(name, outClass); + } else { + return NO; + } +} + + +void testLookup(const char *name, int expectedHookOneCalls, + int expectedHookTwoCalls, int expectedHookThreeCalls) +{ + HookOneCalls = HookTwoCalls = HookThreeCalls = 0; + Class cls = objc_getClass(name); + testassert(HookOneCalls == expectedHookOneCalls && + HookTwoCalls == expectedHookTwoCalls && + HookThreeCalls == expectedHookThreeCalls); + testassert(cls); + testassert(0 == strcmp(class_getName(cls), name)); + testassert(cls == [cls self]); +} + +int main() +{ + testassert(objc_getClass("OrdinaryClass")); + testassert(!objc_getClass("OneClass")); + testassert(!objc_getClass("TwoClass")); + testassert(!objc_getClass("NoSuchClass")); + + objc_setHook_getClass(GetClassHookOne, &OnePreviousHook); + objc_setHook_getClass(GetClassHookTwo, &TwoPreviousHook); + objc_setHook_getClass(GetClassHookThree, &ThreePreviousHook); + // invocation order: HookThree -> Hook Two -> Hook One + + HookOneCalls = HookTwoCalls = HookThreeCalls = 0; + testassert(!objc_getClass("NoSuchClass")); + testassert(HookOneCalls == 1 && HookTwoCalls == 1 && HookThreeCalls == 1); + + testLookup("OneClass", 1, 1, 1); + testLookup("TwoClass", 0, 1, 1); + testLookup("OrdinaryClass", 0, 0, 0); + + // Check again. No hooks should be needed this time. + + testLookup("OneClass", 0, 0, 0); + testLookup("TwoClass", 0, 0, 0); + testLookup("OrdinaryClass", 0, 0, 0); + + succeed(__FILE__); +} diff --git a/test/getImageNameHook.m b/test/getImageNameHook.m new file mode 100644 index 0000000..87c72fe --- /dev/null +++ b/test/getImageNameHook.m @@ -0,0 +1,78 @@ +// TEST_CONFIG + +#include "test.h" +#include "testroot.i" + +@interface One : TestRoot @end +@implementation One @end + +@interface Two : TestRoot @end +@implementation Two @end + +@interface Both : TestRoot @end +@implementation Both @end + +@interface None : TestRoot @end +@implementation None @end + + +objc_hook_getImageName OnePreviousHook; +BOOL GetImageNameHookOne(Class cls, const char **outName) +{ + if (0 == strcmp(class_getName(cls), "One")) { + *outName = "Image One"; + return YES; + } else if (0 == strcmp(class_getName(cls), "Both")) { + *outName = "Image Both via One"; + return YES; + } else { + return OnePreviousHook(cls, outName); + } +} + +objc_hook_getImageName TwoPreviousHook; +BOOL GetImageNameHookTwo(Class cls, const char **outName) +{ + if (0 == strcmp(class_getName(cls), "Two")) { + *outName = "Image Two"; + return YES; + } else if (0 == strcmp(class_getName(cls), "Both")) { + *outName = "Image Both via Two"; + return YES; + } else { + return TwoPreviousHook(cls, outName); + } +} + +int main() +{ + + // before hooks: main executable is the image name for four classes + testassert(strstr(class_getImageName([One class]), "getImageNameHook")); + testassert(strstr(class_getImageName([Two class]), "getImageNameHook")); + testassert(strstr(class_getImageName([Both class]), "getImageNameHook")); + testassert(strstr(class_getImageName([None class]), "getImageNameHook")); + testassert(strstr(class_getImageName([NSObject class]), "libobjc")); + + // install hook One + objc_setHook_getImageName(GetImageNameHookOne, &OnePreviousHook); + + // two classes are in Image One with hook One in place + testassert(strstr(class_getImageName([One class]), "Image One")); + testassert(strstr(class_getImageName([Two class]), "getImageNameHook")); + testassert(strstr(class_getImageName([Both class]), "Image Both via One")); + testassert(strstr(class_getImageName([None class]), "getImageNameHook")); + testassert(strstr(class_getImageName([NSObject class]), "libobjc")); + + // install hook Two which chains to One + objc_setHook_getImageName(GetImageNameHookTwo, &TwoPreviousHook); + + // two classes are in Image Two and one in One with both hooks in place + testassert(strstr(class_getImageName([One class]), "Image One")); + testassert(strstr(class_getImageName([Two class]), "Image Two")); + testassert(strstr(class_getImageName([Both class]), "Image Both via Two")); + testassert(strstr(class_getImageName([None class]), "getImageNameHook")); + testassert(strstr(class_getImageName([NSObject class]), "libobjc")); + + succeed(__FILE__); +} diff --git a/test/getMethod.m b/test/getMethod.m new file mode 100644 index 0000000..3777329 --- /dev/null +++ b/test/getMethod.m @@ -0,0 +1,139 @@ +/* +TEST_BUILD_OUTPUT +.*getMethod.m:\d+:\d+: warning: null passed to a callee that requires a non-null argument \[-Wnonnull\](\n.* note: expanded from macro 'testassert')? +.*getMethod.m:\d+:\d+: warning: null passed to a callee that requires a non-null argument \[-Wnonnull\](\n.* note: expanded from macro 'testassert')? +.*getMethod.m:\d+:\d+: warning: null passed to a callee that requires a non-null argument \[-Wnonnull\](\n.* note: expanded from macro 'testassert')? +.*getMethod.m:\d+:\d+: warning: null passed to a callee that requires a non-null argument \[-Wnonnull\](\n.* note: expanded from macro 'testassert')? +END +*/ + +#include "test.h" +#include "testroot.i" +#include +#include + +static int state = 0; + +@interface Super : TestRoot @end +@implementation Super ++(void)classMethod { state = 1; } +-(void)instanceMethod { state = 4; } ++(void)classMethodSuperOnly { state = 3; } +-(void)instanceMethodSuperOnly { state = 6; } +@end + +@interface Sub : Super @end +@implementation Sub ++(void)classMethod { state = 2; } +-(void)instanceMethod { state = 5; } +@end + +typedef void (*imp_t)(id, SEL); + +int main() +{ + Class Super_cls, Sub_cls; + Class buf[10]; + Method m; + SEL sel; + IMP imp; + + id bufobj = (__bridge_transfer id)(void*)buf; + + // don't use [Super class] to check laziness handing + Super_cls = objc_getClass("Super"); + Sub_cls = objc_getClass("Sub"); + + sel = sel_registerName("classMethod"); + m = class_getClassMethod(Super_cls, sel); + testassert(m); + testassert(sel == method_getName(m)); + imp = method_getImplementation(m); + testassert(imp == class_getMethodImplementation(object_getClass(Super_cls), sel)); + testassert(imp == object_getMethodImplementation(Super_cls, sel)); + state = 0; + (*(imp_t)imp)(Super_cls, sel); + testassert(state == 1); + + sel = sel_registerName("classMethod"); + m = class_getClassMethod(Sub_cls, sel); + testassert(m); + testassert(sel == method_getName(m)); + imp = method_getImplementation(m); + testassert(imp == class_getMethodImplementation(object_getClass(Sub_cls), sel)); + testassert(imp == object_getMethodImplementation(Sub_cls, sel)); + state = 0; + (*(imp_t)imp)(Sub_cls, sel); + testassert(state == 2); + + sel = sel_registerName("classMethodSuperOnly"); + m = class_getClassMethod(Sub_cls, sel); + testassert(m); + testassert(sel == method_getName(m)); + imp = method_getImplementation(m); + testassert(imp == class_getMethodImplementation(object_getClass(Sub_cls), sel)); + testassert(imp == object_getMethodImplementation(Sub_cls, sel)); + state = 0; + (*(imp_t)imp)(Sub_cls, sel); + testassert(state == 3); + + sel = sel_registerName("instanceMethod"); + m = class_getInstanceMethod(Super_cls, sel); + testassert(m); + testassert(sel == method_getName(m)); + imp = method_getImplementation(m); + testassert(imp == class_getMethodImplementation(Super_cls, sel)); + buf[0] = Super_cls; + testassert(imp == object_getMethodImplementation(bufobj, sel)); + state = 0; + (*(imp_t)imp)(bufobj, sel); + testassert(state == 4); + + sel = sel_registerName("instanceMethod"); + m = class_getInstanceMethod(Sub_cls, sel); + testassert(m); + testassert(sel == method_getName(m)); + imp = method_getImplementation(m); + testassert(imp == class_getMethodImplementation(Sub_cls, sel)); + buf[0] = Sub_cls; + testassert(imp == object_getMethodImplementation(bufobj, sel)); + state = 0; + (*(imp_t)imp)(bufobj, sel); + testassert(state == 5); + + sel = sel_registerName("instanceMethodSuperOnly"); + m = class_getInstanceMethod(Sub_cls, sel); + testassert(m); + testassert(sel == method_getName(m)); + imp = method_getImplementation(m); + testassert(imp == class_getMethodImplementation(Sub_cls, sel)); + buf[0] = Sub_cls; + testassert(imp == object_getMethodImplementation(bufobj, sel)); + state = 0; + (*(imp_t)imp)(bufobj, sel); + testassert(state == 6); + + // check class_getClassMethod(cls) == class_getInstanceMethod(cls->isa) + sel = sel_registerName("classMethod"); + testassert(class_getClassMethod(Sub_cls, sel) == class_getInstanceMethod(object_getClass(Sub_cls), sel)); + + sel = sel_registerName("nonexistent"); + testassert(! class_getInstanceMethod(Sub_cls, sel)); + testassert(! class_getClassMethod(Sub_cls, sel)); + testassert(class_getMethodImplementation(Sub_cls, sel) == (IMP)&_objc_msgForward); + buf[0] = Sub_cls; + testassert(object_getMethodImplementation(bufobj, sel) == (IMP)&_objc_msgForward); +#if !__arm64__ + testassert(class_getMethodImplementation_stret(Sub_cls, sel) == (IMP)&_objc_msgForward_stret); + testassert(object_getMethodImplementation_stret(bufobj, sel) == (IMP)&_objc_msgForward_stret); +#endif + + testassert(! class_getInstanceMethod(NULL, NULL)); + testassert(! class_getInstanceMethod(NULL, sel)); + testassert(! class_getInstanceMethod(Sub_cls, NULL)); + testassert(! class_getClassMethod(NULL, NULL)); + testassert(! class_getClassMethod(NULL, sel)); + testassert(! class_getClassMethod(Sub_cls, NULL)); + + succeed(__FILE__); +} diff --git a/test/get_task_allow_entitlement.plist b/test/get_task_allow_entitlement.plist new file mode 100644 index 0000000..bd10085 --- /dev/null +++ b/test/get_task_allow_entitlement.plist @@ -0,0 +1,8 @@ + + + + + get-task-allow + + + diff --git a/test/headers.c b/test/headers.c new file mode 100644 index 0000000..9fbc1ac --- /dev/null +++ b/test/headers.c @@ -0,0 +1,19 @@ +/* +TEST_BUILD +$DIR/headers.sh '$C{TESTINCLUDEDIR}' '$C{TESTLOCALINCLUDEDIR}' '$C{COMPILE_C}' '$C{COMPILE_CXX}' '$C{COMPILE_M}' '$C{COMPILE_MM}' '$VERBOSE' +$C{COMPILE_C} $DIR/headers.c -o headers.exe +END + +allow `sh -x` output from headers.sh +TEST_BUILD_OUTPUT +(\+ .*\n)*(\+ .*)?done +END + */ + + +#include "test.h" + +int main() +{ + succeed(__FILE__); +} diff --git a/test/headers.sh b/test/headers.sh new file mode 100755 index 0000000..b6306a4 --- /dev/null +++ b/test/headers.sh @@ -0,0 +1,38 @@ +#!/bin/sh + +# Compile every exported ObjC header as if it were a file in every language. +# This script is executed by test headers.c's TEST_BUILD command. + +TESTINCLUDEDIR=$1; shift +TESTLOCALINCLUDEDIR=$1; shift +COMPILE_C=$1; shift +COMPILE_CXX=$1; shift +COMPILE_M=$1; shift +COMPILE_MM=$1; shift +VERBOSE=$1; shift + +# stop after any command error +set -e + +# echo commands when verbose +if [ "$VERBOSE" != "0" ]; then + set -x +fi + +FILES="$TESTINCLUDEDIR/objc/*.h $TESTLOCALINCLUDEDIR/objc/*.h" +CFLAGS='-fsyntax-only -Wno-unused-function -D_OBJC_PRIVATE_H_' + +$COMPILE_C $CFLAGS $FILES +$COMPILE_CXX $CFLAGS $FILES +$COMPILE_M $CFLAGS $FILES +$COMPILE_MM $CFLAGS $FILES +for STDC in '99' '11' ; do + $COMPILE_C $CFLAGS $FILES -std=c$STDC + $COMPILE_M $CFLAGS $FILES -std=c$STDC +done +for STDCXX in '98' '03' '11' '14' '17' ; do + $COMPILE_CXX $CFLAGS $FILES -std=c++$STDCXX + $COMPILE_MM $CFLAGS $FILES -std=c++$STDCXX +done + +echo done diff --git a/test/imageorder.h b/test/imageorder.h new file mode 100644 index 0000000..654e48e --- /dev/null +++ b/test/imageorder.h @@ -0,0 +1,20 @@ +extern int state; +extern int cstate; + +OBJC_ROOT_CLASS +@interface Super { id isa; } ++(void) method; ++(void) method0; +@end + +@interface Super (cat1) ++(void) method1; +@end + +@interface Super (cat2) ++(void) method2; +@end + +@interface Super (cat3) ++(void) method3; +@end diff --git a/test/imageorder.m b/test/imageorder.m new file mode 100644 index 0000000..ec7b629 --- /dev/null +++ b/test/imageorder.m @@ -0,0 +1,41 @@ +/* +TEST_BUILD + $C{COMPILE} $DIR/imageorder1.m -o imageorder1.dylib -dynamiclib + $C{COMPILE} $DIR/imageorder2.m -x none imageorder1.dylib -o imageorder2.dylib -dynamiclib + $C{COMPILE} $DIR/imageorder3.m -x none imageorder2.dylib imageorder1.dylib -o imageorder3.dylib -dynamiclib + $C{COMPILE} $DIR/imageorder.m -x none imageorder3.dylib imageorder2.dylib imageorder1.dylib -o imageorder.exe +END +*/ + +#include "test.h" +#include "imageorder.h" +#include +#include + +int main() +{ + // +load methods and C static initializers + testassert(state == 3); + testassert(cstate == 3); + + Class cls = objc_getClass("Super"); + testassert(cls); + + // make sure all categories arrived + state = -1; + [Super method0]; + testassert(state == 0); + [Super method1]; + testassert(state == 1); + [Super method2]; + testassert(state == 2); + [Super method3]; + testassert(state == 3); + + // make sure imageorder3.dylib is the last category to attach + state = 0; + [Super method]; + testassert(state == 3); + + succeed(__FILE__); +} diff --git a/test/imageorder1.m b/test/imageorder1.m new file mode 100644 index 0000000..2cc1d80 --- /dev/null +++ b/test/imageorder1.m @@ -0,0 +1,52 @@ +#include "test.h" +#include "imageorder.h" + +int state = -1; +int cstate = 0; + +static void c1(void) __attribute__((constructor)); +static void c1(void) +{ + testassert(state == 1); // +load before C/C++ + testassert(cstate == 0); + cstate = 1; +} + + +#if __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wobjc-protocol-method-implementation" +#endif + +@implementation Super (cat1) ++(void) method { + fail("+[Super(cat1) method] not replaced!"); +} ++(void) method1 { + state = 1; +} ++(void) load { + testassert(state == 0); + state = 1; +} +@end + +#if __clang__ +#pragma clang diagnostic pop +#endif + + +@implementation Super ++(void) initialize { } ++(void) method { + fail("+[Super method] not replaced!"); +} ++(void) method0 { + state = 0; +} ++(void) load { + testassert(state == -1); + state = 0; +} +@end + diff --git a/test/imageorder2.m b/test/imageorder2.m new file mode 100644 index 0000000..e4d690b --- /dev/null +++ b/test/imageorder2.m @@ -0,0 +1,33 @@ +#include "test.h" +#include "imageorder.h" + +static void c2(void) __attribute__((constructor)); +static void c2(void) +{ + testassert(state == 2); // +load before C/C++ + testassert(cstate == 1); + cstate = 2; +} + + +#if __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wobjc-protocol-method-implementation" +#endif + +@implementation Super (cat2) ++(void) method { + fail("+[Super(cat2) method] not replaced!"); +} ++(void) method2 { + state = 2; +} ++(void) load { + testassert(state == 1); + state = 2; +} +@end + +#if __clang__ +#pragma clang diagnostic pop +#endif diff --git a/test/imageorder3.m b/test/imageorder3.m new file mode 100644 index 0000000..217130f --- /dev/null +++ b/test/imageorder3.m @@ -0,0 +1,33 @@ +#include "test.h" +#include "imageorder.h" + +static void c3(void) __attribute__((constructor)); +static void c3(void) +{ + testassert(state == 3); // +load before C/C++ + testassert(cstate == 2); + cstate = 3; +} + + +#if __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wobjc-protocol-method-implementation" +#endif + +@implementation Super (cat3) ++(void) method { + state = 3; +} ++(void) method3 { + state = 3; +} ++(void) load { + testassert(state == 2); + state = 3; +} +@end + +#if __clang__ +#pragma clang diagnostic pop +#endif diff --git a/test/imports.c b/test/imports.c new file mode 100644 index 0000000..99e46e7 --- /dev/null +++ b/test/imports.c @@ -0,0 +1,37 @@ +/* +Disallow some imports into and exports from libobjc.A.dylib. + +To debug, re-run libobjc's link command with + -Xlinker -dead_strip -Xlinker -why_live -Xlinker SYMBOL_NAME_HERE + +Disallowed imports (nm -u): +___cxa_guard_acquire (C++ function-scope static initializer) +___cxa_guard_release (C++ function-scope static initializer) +___cxa_atexit (C++ static destructor) +weak external (any weak externals, including operators new and delete) + +Disallowed exports (nm -U): +__Z* (any C++-mangled export) +weak external (any weak externals, including operators new and delete) + +fixme rdar://13354718 should disallow anything from libc++ (i.e. not libc++abi) +*/ + +/* +TEST_BUILD +echo $C{XCRUN} nm -m -arch $C{ARCH} $C{TESTLIB} +$C{XCRUN} nm -u -m -arch $C{ARCH} $C{TESTLIB} | egrep '(weak external| external (___cxa_atexit|___cxa_guard_acquire|___cxa_guard_release))' || true +$C{XCRUN} nm -U -m -arch $C{ARCH} $C{TESTLIB} | egrep '(weak external| external __Z)' || true +$C{COMPILE_C} $DIR/imports.c -o imports.exe +END + +TEST_BUILD_OUTPUT +.*libobjc.A.dylib +END + */ + +#include "test.h" +int main() +{ + succeed(__FILE__); +} diff --git a/test/include-warnings.c b/test/include-warnings.c new file mode 100644 index 0000000..72990e2 --- /dev/null +++ b/test/include-warnings.c @@ -0,0 +1,19 @@ +/* +TEST_BUILD + $C{COMPILE} $DIR/include-warnings.c -o include-warnings.exe -Wsystem-headers -Weverything -Wno-undef -Wno-old-style-cast -Wno-nullability-extension 2>&1 | grep -v 'In file' | grep objc || true +END + +TEST_RUN_OUTPUT +OK: includes.c +END +*/ + +// Detect warnings inside any header. +// The build command above filters out warnings inside non-objc headers +// (which are noisy with -Weverything). +// -Wno-undef suppresses warnings about `#if __cplusplus` and the like. +// -Wno-old-style-cast is tough to avoid in mixed C/C++ code. +// -Wno-nullability-extension disables a warning about non-portable +// _Nullable etc which we already handle correctly in objc-abi.h. + +#include "includes.c" diff --git a/test/includes-objc2.c b/test/includes-objc2.c new file mode 100644 index 0000000..6fb80ab --- /dev/null +++ b/test/includes-objc2.c @@ -0,0 +1,13 @@ +// TEST_CFLAGS -D__OBJC2__ + +// Verify that all headers can be included in any language, even if +// the client is C code that defined __OBJC2__. + +// This is the definition that Instruments uses in its build. +#if defined(__OBJC2__) +#undef __OBJC2__ +#endif +#define __OBJC2__ 1 + +#define NAME "includes-objc2.c" +#include "includes.c" diff --git a/test/includes.c b/test/includes.c new file mode 100644 index 0000000..1abab68 --- /dev/null +++ b/test/includes.c @@ -0,0 +1,47 @@ +// TEST_CONFIG + +// Verify that all headers can be included in any language. +// See also test/include-warnings.c which checks for warnings in these headers. +// See also test/includes-objc2.c which checks for safety even if +// the client is C code that defined __OBJC2__. + +#ifndef NAME +#define NAME "includes.c" +#endif + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#if TARGET_OS_OSX +#include +#include +#include +#endif + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Weverything" +#include "test.h" +#pragma clang diagnostic pop + +int main() +{ + succeed(NAME); +} diff --git a/test/initialize.m b/test/initialize.m new file mode 100644 index 0000000..d32b4df --- /dev/null +++ b/test/initialize.m @@ -0,0 +1,323 @@ +// TEST_CONFIG + +// initialize.m +// Test basic +initialize behavior +// * +initialize before class method +// * superclass +initialize before subclass +initialize +// * subclass inheritance of superclass implementation +// * messaging during +initialize +// * +initialize provoked by class_getMethodImplementation +// * +initialize not provoked by objc_getClass +#include "test.h" +#include "testroot.i" + +int state = 0; + +@interface Super0 : TestRoot @end +@implementation Super0 ++(void)initialize { + fail("objc_getClass() must not trigger +initialize"); +} +@end + +@interface Super : TestRoot @end +@implementation Super ++(void)initialize { + testprintf("in [Super initialize]\n"); + testassert(state == 0); + state = 1; +} ++(void)method { + fail("[Super method] shouldn't be called"); +} +@end + +@interface Sub : Super @end +@implementation Sub ++(void)initialize { + testprintf("in [Sub initialize]\n"); + testassert(state == 1); + state = 2; +} ++(void)method { + testprintf("in [Sub method]\n"); + testassert(state == 2); + state = 3; +} +@end + + +@interface Super2 : TestRoot @end +@interface Sub2 : Super2 @end + +@implementation Super2 ++(void)initialize { + if (self == objc_getClass("Sub2")) { + testprintf("in [Super2 initialize] of Sub2\n"); + testassert(state == 1); + state = 2; + } else if (self == objc_getClass("Super2")) { + testprintf("in [Super2 initialize] of Super2\n"); + testassert(state == 0); + state = 1; + } else { + fail("in [Super2 initialize] of unknown class"); + } +} ++(void)method { + testprintf("in [Super2 method]\n"); + testassert(state == 2); + state = 3; +} +@end + +@implementation Sub2 +// nothing here +@end + + +@interface Super3 : TestRoot @end +@interface Sub3 : Super3 @end + +@implementation Super3 ++(void)initialize { + if (self == [Sub3 class]) { // this message triggers [Sub3 initialize] + testprintf("in [Super3 initialize] of Sub3\n"); + testassert(state == 0); + state = 1; + } else if (self == [Super3 class]) { + testprintf("in [Super3 initialize] of Super3\n"); + testassert(state == 1); + state = 2; + } else { + fail("in [Super3 initialize] of unknown class"); + } +} ++(void)method { + testprintf("in [Super3 method]\n"); + testassert(state == 2); + state = 3; +} +@end + +@implementation Sub3 +// nothing here +@end + + +@interface Super4 : TestRoot @end +@implementation Super4 +-(void)instanceMethod { + testassert(state == 1); + state = 2; +} ++(void)initialize { + testprintf("in [Super4 initialize]\n"); + testassert(state == 0); + state = 1; + id x = [[self alloc] init]; + [x instanceMethod]; + RELEASE_VALUE(x); +} +@end + + +@interface Super5 : TestRoot @end +@implementation Super5 +-(void)instanceMethod { +} ++(void)classMethod { + testassert(state == 1); + state = 2; +} ++(void)initialize { + testprintf("in [Super5 initialize]\n"); + testassert(state == 0); + state = 1; + class_getMethodImplementation(self, @selector(instanceMethod)); + // this is the "memoized" case for getNonMetaClass + class_getMethodImplementation(object_getClass(self), @selector(classMethod)); + [self classMethod]; +} +@end + + +@interface Super6 : TestRoot @end +@interface Sub6 : Super6 @end +@implementation Super6 ++(void)initialize { + static bool once; + bool wasOnce; + testprintf("in [Super6 initialize] (#%d)\n", 1+(int)once); + if (!once) { + once = true; + wasOnce = true; + testassert(state == 0); + state = 1; + } else { + wasOnce = false; + testassert(state == 2); + state = 3; + } + [Sub6 class]; + if (wasOnce) { + testassert(state == 5); + state = 6; + } else { + testassert(state == 3); + state = 4; + } +} +@end +@implementation Sub6 ++(void)initialize { + testprintf("in [Sub6 initialize]\n"); + testassert(state == 1); + state = 2; + [super initialize]; + testassert(state == 4); + state = 5; +} +@end + + +@interface Super7 : TestRoot @end +@interface Sub7 : Super7 @end +@implementation Super7 ++(void)initialize { + static bool once; + bool wasOnce; + testprintf("in [Super7 initialize] (#%d)\n", 1+(int)once); + if (!once) { + once = true; + wasOnce = true; + testassert(state == 0); + state = 1; + } else { + wasOnce = false; + testassert(state == 2); + state = 3; + } + [Sub7 class]; + if (wasOnce) { + testassert(state == 5); + state = 6; + } else { + testassert(state == 3); + state = 4; + } +} +@end +@implementation Sub7 ++(void)initialize { + testprintf("in [Sub7 initialize]\n"); + testassert(state == 1); + state = 2; + [super initialize]; + testassert(state == 4); + state = 5; +} +@end + + + +@interface SuperThrower : TestRoot @end +@implementation SuperThrower ++(void)initialize { + testprintf("in [SuperThrower initialize]\n"); + testassert(state == 0); + state = 10; + @throw AUTORELEASE([TestRoot new]); + fail("@throw didn't throw"); +} +@end + +@interface SubThrower : SuperThrower @end +@implementation SubThrower ++(void)initialize { + testprintf("in [SubThrower initialize]\n"); + testassert(state == 0); + state = 20; +} +@end + +int main() +{ + Class cls; + + // objc_getClass() must not +initialize anything + state = 0; + objc_getClass("Super0"); + testassert(state == 0); + + // initialize superclass, then subclass + state = 0; + [Sub method]; + testassert(state == 3); + + // check subclass's inheritance of superclass initialize + state = 0; + [Sub2 method]; + testassert(state == 3); + + // check subclass method called from superclass initialize + state = 0; + [Sub3 method]; + testassert(state == 3); + + // check class_getMethodImplementation (instance method) + state = 0; + cls = objc_getClass("Super4"); + testassert(state == 0); + class_getMethodImplementation(cls, @selector(classMethod)); + testassert(state == 2); + + // check class_getMethodImplementation (class method) + // this is the "slow" case for getNonMetaClass + state = 0; + cls = objc_getClass("Super5"); + testassert(state == 0); + class_getMethodImplementation(object_getClass(cls), @selector(instanceMethod)); + testassert(state == 2); + + // check +initialize cycles + // this is the "cls is a subclass" case for getNonMetaClass + state = 0; + [Super6 class]; + testassert(state == 6); + + // check +initialize cycles + // this is the "cls is a subclass" case for getNonMetaClass + state = 0; + [Sub7 class]; + testassert(state == 6); + + // exception from +initialize must be handled cleanly + PUSH_POOL { + alarm(3); + testonthread( ^{ + @try { + state = 0; + [SuperThrower class]; + fail("where's the beef^Wexception?"); + } @catch (...) { + testassert(state == 10); + state = 11; + } + testassert(state == 11); + }); + @try { + state = 0; + [SuperThrower class]; + testassert(state == 0); + [SubThrower class]; + testassert(state == 20); + } @catch (...) { + fail("+initialize called again after exception"); + } + } POP_POOL; + + succeed(__FILE__); + + return 0; +} diff --git a/test/initializeVersusWeak.m b/test/initializeVersusWeak.m new file mode 100644 index 0000000..e9c1580 --- /dev/null +++ b/test/initializeVersusWeak.m @@ -0,0 +1,187 @@ +// TEST_CONFIG MEM=arc +// TEST_CFLAGS -framework Foundation + +// Problem: If weak reference operations provoke +initialize, the runtime +// can deadlock (recursive weak lock, or lock inversion between weak lock +// and +initialize lock). +// Solution: object_setClass() and objc_storeWeak() perform +initialize +// if needed so that no weakly-referenced object can ever have an +// un-+initialized isa. + +#include +#include +#include "test.h" + +#pragma clang diagnostic ignored "-Warc-unsafe-retained-assign" + +// This is StripedMap's pointer hash +#if TARGET_OS_IPHONE && !TARGET_OS_SIMULATOR + enum { StripeCount = 8 }; +#else + enum { StripeCount = 64 }; +#endif +uintptr_t stripehash(id obj) { + uintptr_t addr = (uintptr_t)obj; + return ((addr >> 4) ^ (addr >> 9)) % StripeCount; +} + +bool sameAlignment(id o1, id o2) +{ + return stripehash(o1) == stripehash(o2); +} + +// Return a new non-tagged object that uses the same striped weak locks as `obj` +NSObject *newAlignedObject(id obj) +{ + // Use immutable arrays because their contents are stored inline, + // which prevents Guard Malloc from using the same alignment for all of them + NSArray *result = [NSArray new]; + while (!sameAlignment(obj, result)) { + result = [result arrayByAddingObject:result]; + } + return result; +} + + +__weak NSObject *weak1; +__weak NSObject *weak2; +NSObject *strong2; + +@interface A : NSObject @end +@implementation A ++(void)initialize { + weak2 = strong2; // weak store #2 + strong2 = nil; +} +@end + +void testA() +{ + // Weak store #1 provokes +initialize which performs weak store #2. + // Solution: weak store #1 runs +initialize if needed + // without holding locks. + @autoreleasepool { + A *obj = [A new]; + strong2 = newAlignedObject(obj); + [obj addObserver:obj forKeyPath:@"foo" options:0 context:0]; + weak1 = obj; // weak store #1 + [obj removeObserver:obj forKeyPath:@"foo"]; + obj = nil; + } +} + + +__weak NSObject *weak3; +__weak NSObject *weak4; +NSObject *strong4; + +@interface B : NSObject @end +@implementation B ++(void)initialize { + weak4 = strong4; // weak store #4 + strong4 = nil; +} +@end + + +void testB() +{ + // Weak load #3 provokes +initialize which performs weak store #4. + // Solution: object_setClass() runs +initialize if needed + // without holding locks. + @autoreleasepool { + B *obj = [B new]; + strong4 = newAlignedObject(obj); + weak3 = obj; + [obj addObserver:obj forKeyPath:@"foo" options:0 context:0]; + [weak3 self]; // weak load #3 + [obj removeObserver:obj forKeyPath:@"foo"]; + obj = nil; + } +} + + +__weak id weak5; + +@interface C : NSObject @end +@implementation C ++(void)initialize { + weak5 = [self new]; +} +@end + +void testC() +{ + // +initialize performs a weak store of itself. + // Make sure the retry in objc_storeWeak() doesn't spin. + @autoreleasepool { + [C self]; + } +} + + +__weak id weak6; +NSObject *strong6; +semaphore_t Dgo; +semaphore_t Ddone; + +void *Dthread(void *arg __unused) +{ + @autoreleasepool { + semaphore_wait(Dgo); + for (int i = 0; i < 1000; i++) { + id x = weak6; + testassert(x == strong6); + } + return nil; + } +} + +@interface D : NSObject @end +@implementation D ++(void)initialize { + strong6 = [self new]; + weak6 = strong6; + semaphore_signal(Dgo); + for (int i = 0; i < 1000; i++) { + id x = weak6; + testassert(x == strong6); + } +} +@end + +void testD() +{ + // +initialize performs a weak store of itself, then another thread + // tries to load that weak variable before +initialize completes. + // Deadlock occurs if the +initialize thread tries to acquire the + // sidetable lock for another operation and the second thread holds + // the sidetable lock while waiting for +initialize. + + @autoreleasepool { + semaphore_create(mach_task_self(), &Dgo, 0, 0); + semaphore_create(mach_task_self(), &Ddone, 0, 0); + pthread_t th; + pthread_create(&th, nil, Dthread, nil); + [D self]; + pthread_join(th, nil); + } +} + +int main() +{ + if (is_guardmalloc() && getenv("MALLOC_PROTECT_BEFORE")) { + testwarn("fixme malloc guard before breaks this with debug libobjc"); + } + else { + alarm(10); // replace hangs with crashes + + testA(); + testB(); + testC(); + testD(); + } + + succeed(__FILE__); +} + diff --git a/test/instanceSize.m b/test/instanceSize.m new file mode 100644 index 0000000..8034a5c --- /dev/null +++ b/test/instanceSize.m @@ -0,0 +1,59 @@ +// TEST_CONFIG + +#include "test.h" +#include "testroot.i" +#include + + +@interface Sub1 : TestRoot { + // id isa; // 0..4 + BOOL b; // 4..5 +} +@end + +@implementation Sub1 @end + +@interface Sub2 : Sub1 { + // id isa // 0..4 0..8 + // BOOL b // 4..5 8..9 + BOOL b2; // 5..6 9..10 + id o; // 8..12 16..24 +} +@end +@implementation Sub2 @end + +@interface Sub3 : Sub1 { + // id isa; // 0..4 0..8 + // BOOL b; // 4..5 8..9 + id o; // 8..12 16..24 + BOOL b2; // 12..13 24..25 +} +@end +@implementation Sub3 @end + +int main() +{ + testassert(sizeof(id) == class_getInstanceSize([TestRoot class])); + testassert(2*sizeof(id) == class_getInstanceSize([Sub1 class])); + testassert(3*sizeof(id) == class_getInstanceSize([Sub2 class])); + testassert(4*sizeof(id) == class_getInstanceSize([Sub3 class])); + +#if !__has_feature(objc_arc) + id o; + + o = [TestRoot new]; + testassert(object_getIndexedIvars(o) == (char *)o + class_getInstanceSize(object_getClass(o))); + RELEASE_VAR(o); + o = [Sub1 new]; + testassert(object_getIndexedIvars(o) == (char *)o + class_getInstanceSize(object_getClass(o))); + RELEASE_VAR(o); + o = [Sub2 new]; + testassert(object_getIndexedIvars(o) == (char *)o + class_getInstanceSize(object_getClass(o))); + RELEASE_VAR(o); + o = [Sub3 new]; + testassert(object_getIndexedIvars(o) == (char *)o + class_getInstanceSize(object_getClass(o))); + RELEASE_VAR(o); +#endif + + succeed(__FILE__); +} diff --git a/test/isaValidation.m b/test/isaValidation.m new file mode 100644 index 0000000..3a00a47 --- /dev/null +++ b/test/isaValidation.m @@ -0,0 +1,267 @@ +// TEST_CRASHES +// TEST_CONFIG MEM=mrc +/* +TEST_RUN_OUTPUT +Testing object_getMethodImplementation +Completed test on good classes. +objc\[\d+\]: Attempt to use unknown class 0x[0-9a-f]+. +objc\[\d+\]: HALTED +Testing class_getInstanceMethod +Completed test on good classes. +objc\[\d+\]: Attempt to use unknown class 0x[0-9a-f]+. +objc\[\d+\]: HALTED +Testing class_getMethodImplementation +Completed test on good classes. +objc\[\d+\]: Attempt to use unknown class 0x[0-9a-f]+. +objc\[\d+\]: HALTED +Testing class_respondsToSelector +Completed test on good classes. +objc\[\d+\]: Attempt to use unknown class 0x[0-9a-f]+. +objc\[\d+\]: HALTED +Testing class_conformsToProtocol +Completed test on good classes. +objc\[\d+\]: Attempt to use unknown class 0x[0-9a-f]+. +objc\[\d+\]: HALTED +Testing class_copyProtocolList +Completed test on good classes. +objc\[\d+\]: Attempt to use unknown class 0x[0-9a-f]+. +objc\[\d+\]: HALTED +Testing class_getProperty +Completed test on good classes. +objc\[\d+\]: Attempt to use unknown class 0x[0-9a-f]+. +objc\[\d+\]: HALTED +Testing class_copyPropertyList +Completed test on good classes. +objc\[\d+\]: Attempt to use unknown class 0x[0-9a-f]+. +objc\[\d+\]: HALTED +Testing class_addMethod +Completed test on good classes. +objc\[\d+\]: Attempt to use unknown class 0x[0-9a-f]+. +objc\[\d+\]: HALTED +Testing class_replaceMethod +Completed test on good classes. +objc\[\d+\]: Attempt to use unknown class 0x[0-9a-f]+. +objc\[\d+\]: HALTED +Testing class_addIvar +Completed test on good classes. +objc\[\d+\]: Attempt to use unknown class 0x[0-9a-f]+. +objc\[\d+\]: HALTED +Testing class_addProtocol +Completed test on good classes. +objc\[\d+\]: Attempt to use unknown class 0x[0-9a-f]+. +objc\[\d+\]: HALTED +Testing class_addProperty +Completed test on good classes. +objc\[\d+\]: Attempt to use unknown class 0x[0-9a-f]+. +objc\[\d+\]: HALTED +Testing class_replaceProperty +Completed test on good classes. +objc\[\d+\]: Attempt to use unknown class 0x[0-9a-f]+. +objc\[\d+\]: HALTED +Testing class_setIvarLayout +objc\[\d+\]: \*\*\* Can't set ivar layout for already-registered class 'TestRoot' +objc\[\d+\]: \*\*\* Can't set ivar layout for already-registered class 'TestRoot' +objc\[\d+\]: \*\*\* Can't set ivar layout for already-registered class 'NSObject' +objc\[\d+\]: \*\*\* Can't set ivar layout for already-registered class 'NSObject' +objc\[\d+\]: \*\*\* Can't set ivar layout for already-registered class 'AllocatedTestClass2' +objc\[\d+\]: \*\*\* Can't set ivar layout for already-registered class 'AllocatedTestClass2' +objc\[\d+\]: \*\*\* Can't set ivar layout for already-registered class 'TestRoot' +objc\[\d+\]: \*\*\* Can't set ivar layout for already-registered class 'DuplicateClass' +Completed test on good classes. +objc\[\d+\]: Attempt to use unknown class 0x[0-9a-f]+. +objc\[\d+\]: HALTED +Testing class_setWeakIvarLayout +objc\[\d+\]: \*\*\* Can't set weak ivar layout for already-registered class 'TestRoot' +objc\[\d+\]: \*\*\* Can't set weak ivar layout for already-registered class 'TestRoot' +objc\[\d+\]: \*\*\* Can't set weak ivar layout for already-registered class 'NSObject' +objc\[\d+\]: \*\*\* Can't set weak ivar layout for already-registered class 'NSObject' +objc\[\d+\]: \*\*\* Can't set weak ivar layout for already-registered class 'AllocatedTestClass2' +objc\[\d+\]: \*\*\* Can't set weak ivar layout for already-registered class 'AllocatedTestClass2' +objc\[\d+\]: \*\*\* Can't set weak ivar layout for already-registered class 'TestRoot' +objc\[\d+\]: \*\*\* Can't set weak ivar layout for already-registered class 'DuplicateClass' +Completed test on good classes. +objc\[\d+\]: Attempt to use unknown class 0x[0-9a-f]+. +objc\[\d+\]: HALTED +Testing objc_registerClassPair +objc\[\d+\]: objc_registerClassPair: class 'TestRoot' was not allocated with objc_allocateClassPair! +objc\[\d+\]: objc_registerClassPair: class 'NSObject' was not allocated with objc_allocateClassPair! +objc\[\d+\]: objc_registerClassPair: class 'AllocatedTestClass2' was already registered! +objc\[\d+\]: objc_registerClassPair: class 'DuplicateClass' was not allocated with objc_allocateClassPair! +Completed test on good classes. +objc\[\d+\]: Attempt to use unknown class 0x[0-9a-f]+. +objc\[\d+\]: HALTED +Testing objc_duplicateClass +Completed test on good classes. +objc\[\d+\]: Attempt to use unknown class 0x[0-9a-f]+. +objc\[\d+\]: HALTED +Testing objc_disposeClassPair +objc\[\d+\]: objc_disposeClassPair: class 'TestRoot' was not allocated with objc_allocateClassPair! +objc\[\d+\]: objc_disposeClassPair: class 'NSObject' was not allocated with objc_allocateClassPair! +objc\[\d+\]: objc_disposeClassPair: class 'DuplicateClass' was not allocated with objc_allocateClassPair! +Completed test on good classes. +objc\[\d+\]: Attempt to use unknown class 0x[0-9a-f]+. +objc\[\d+\]: HALTED +Completed! +END + */ + +#include "test.h" +#include "testroot.i" +#include + +@protocol P +@end + +extern char **environ; + +id dummyIMP(id self, SEL _cmd, ...) { (void)_cmd; return self; } + +char *dupeName(Class cls) { + char *name; + asprintf(&name, "%sDuplicate", class_getName(cls)); + return name; +} + +typedef void (^TestBlock)(Class); +struct TestCase { + const char *name; + TestBlock block; +}; + +#define NAMED_TESTCASE(name, ...) { name, ^(Class cls) { __VA_ARGS__; } } +#define TESTCASE(...) NAMED_TESTCASE(#__VA_ARGS__, __VA_ARGS__) +#define TESTCASE_NOMETA(...) \ + NAMED_TESTCASE( #__VA_ARGS__, if(class_isMetaClass(cls)) return; __VA_ARGS__; ) +#define TESTCASE_OBJ(...) NAMED_TESTCASE( \ + #__VA_ARGS__, \ + if(class_isMetaClass(cls)) return; \ + id obj = [TestRoot alloc]; \ + *(Class *)obj = cls; \ + __VA_ARGS__; \ +) + +struct TestCase TestCases[] = { + TESTCASE_OBJ(object_getMethodImplementation(obj, @selector(init))), + + TESTCASE(class_getInstanceMethod(cls, @selector(init))), + TESTCASE(class_getMethodImplementation(cls, @selector(init))), + TESTCASE(class_respondsToSelector(cls, @selector(init))), + TESTCASE(class_conformsToProtocol(cls, @protocol(P))), + TESTCASE(free(class_copyProtocolList(cls, NULL))), + TESTCASE(class_getProperty(cls, "x")), + TESTCASE(free(class_copyPropertyList(cls, NULL))), + TESTCASE(class_addMethod(cls, @selector(nop), dummyIMP, "v@:")), + TESTCASE(class_replaceMethod(cls, @selector(nop), dummyIMP, "v@:")), + TESTCASE(class_addIvar(cls, "x", sizeof(int), sizeof(int), @encode(int))), + TESTCASE(class_addProtocol(cls, @protocol(P))), + TESTCASE(class_addProperty(cls, "x", NULL, 0)), + TESTCASE(class_replaceProperty(cls, "x", NULL, 0)), + TESTCASE(class_setIvarLayout(cls, NULL)), + TESTCASE(class_setWeakIvarLayout(cls, NULL)), + TESTCASE_NOMETA(objc_registerClassPair(cls)), + TESTCASE_NOMETA(objc_duplicateClass(cls, dupeName(cls), 0)), + TESTCASE_NOMETA(objc_disposeClassPair(cls)), +}; + +void parent(char *argv0) +{ + int testCount = sizeof(TestCases) / sizeof(*TestCases); + for (int i = 0; i < testCount; i++) { + char *testIndex; + asprintf(&testIndex, "%d", i); + char *argvSpawn[] = { + argv0, + testIndex, + NULL + }; + pid_t pid; + int result = posix_spawn(&pid, argv0, NULL, NULL, argvSpawn, environ); + if (result != 0) { + fprintf(stderr, "Could not spawn child process: (%d) %s\n", + errno, strerror(errno)); + exit(1); + } + + free(testIndex); + + result = waitpid(pid, NULL, 0); + if (result == -1) { + fprintf(stderr, "Error waiting for termination of child process: (%d) %s\n", + errno, strerror(errno)); + exit(1); + } + } + fprintf(stderr, "Completed!\n"); +} + +void child(char *argv1) +{ + long index = strtol(argv1, NULL, 10); + struct TestCase testCase = TestCases[index]; + TestBlock block = testCase.block; + + const char *name = testCase.name; + if (strncmp(name, "free(", 5) == 0) + name += 5; + const char *paren = strchr(name, '('); + long len = paren != NULL ? paren - name : strlen(name); + fprintf(stderr, "Testing %.*s\n", (int)len, name); + + // Make sure plain classes work. + block([TestRoot class]); + block(object_getClass([TestRoot class])); + + // And framework classes. + block([NSObject class]); + block(object_getClass([NSObject class])); + + // Test a constructed, unregistered class. + Class allocatedClass = objc_allocateClassPair([TestRoot class], + "AllocatedTestClass", + 0); + class_getMethodImplementation(allocatedClass, @selector(self)); + block(object_getClass(allocatedClass)); + block(allocatedClass); + + // Test a constructed, registered class. (Do this separately so + // test cases can dispose of the class if needed.) + allocatedClass = objc_allocateClassPair([TestRoot class], + "AllocatedTestClass2", + 0); + objc_registerClassPair(allocatedClass); + block(object_getClass(allocatedClass)); + block(allocatedClass); + + // Test a duplicated class. + + Class duplicatedClass = objc_duplicateClass([TestRoot class], + "DuplicateClass", + 0); + block(object_getClass(duplicatedClass)); + block(duplicatedClass); + + fprintf(stderr, "Completed test on good classes.\n"); + + // Test a fake class. + Class templateClass = objc_allocateClassPair([TestRoot class], + "TemplateClass", + 0); + void *fakeClass = malloc(malloc_size(templateClass)); + memcpy(fakeClass, templateClass, malloc_size(templateClass)); + block((Class)fakeClass); + fail("Should have died on the fake class"); +} + +int main(int argc, char **argv) +{ + // We want to run a bunch of tests, all of which end in _objc_fatal + // (at least if they succeed). Spawn one subprocess per test and + // have the parent process manage it all. The test will begin by + // running parent(), which will repeatedly re-spawn this program to + // call child() with the index of the test to run. + if (argc == 1) { + parent(argv[0]); + } else { + child(argv[1]); + } +} diff --git a/test/ismeta.m b/test/ismeta.m new file mode 100644 index 0000000..d0e580d --- /dev/null +++ b/test/ismeta.m @@ -0,0 +1,13 @@ +// TEST_CONFIG + +#include "test.h" +#include "testroot.i" +#include + +int main() +{ + testassert(!class_isMetaClass([TestRoot class])); + testassert(class_isMetaClass(object_getClass([TestRoot class]))); + testassert(!class_isMetaClass(nil)); + succeed(__FILE__); +} diff --git a/test/ivar.m b/test/ivar.m new file mode 100644 index 0000000..871dbf0 --- /dev/null +++ b/test/ivar.m @@ -0,0 +1,133 @@ +/* +TEST_BUILD_OUTPUT +.*ivar.m:\d+:\d+: warning: null passed to a callee that requires a non-null argument \[-Wnonnull\](\n.* note: expanded from macro 'testassert')? +.*ivar.m:\d+:\d+: warning: null passed to a callee that requires a non-null argument \[-Wnonnull\](\n.* note: expanded from macro 'testassert')? +.*ivar.m:\d+:\d+: warning: null passed to a callee that requires a non-null argument \[-Wnonnull\](\n.* note: expanded from macro 'testassert')? +.*ivar.m:\d+:\d+: warning: null passed to a callee that requires a non-null argument \[-Wnonnull\](\n.* note: expanded from macro 'testassert')? +.*ivar.m:\d+:\d+: warning: null passed to a callee that requires a non-null argument \[-Wnonnull\] +.*ivar.m:\d+:\d+: warning: null passed to a callee that requires a non-null argument \[-Wnonnull\] +.*ivar.m:\d+:\d+: warning: null passed to a callee that requires a non-null argument \[-Wnonnull\](\n.* note: expanded from macro 'testassert')? +.*ivar.m:\d+:\d+: warning: null passed to a callee that requires a non-null argument \[-Wnonnull\](\n.* note: expanded from macro 'testassert')? +.*ivar.m:\d+:\d+: warning: null passed to a callee that requires a non-null argument \[-Wnonnull\](\n.* note: expanded from macro 'testassert')? +.*ivar.m:\d+:\d+: warning: null passed to a callee that requires a non-null argument \[-Wnonnull\](\n.* note: expanded from macro 'testassert')? +.*ivar.m:\d+:\d+: warning: null passed to a callee that requires a non-null argument \[-Wnonnull\](\n.* note: expanded from macro 'testassert')? +.*ivar.m:\d+:\d+: warning: null passed to a callee that requires a non-null argument \[-Wnonnull\](\n.* note: expanded from macro 'testassert')? +END +*/ + +#include "test.h" +#include "testroot.i" +#include +#include +#include + +@interface Super : TestRoot { + @public + char superIvar; +} +@end + +@interface Sub : Super { + @public + id subIvar; +} +@end + +@implementation Super @end +@implementation Sub @end + + +int main() +{ + /* + Runtime layout of Sub: + [0] isa + [1] superIvar + [2] subIvar + */ + + Ivar ivar; + Sub *sub = [Sub new]; + sub->subIvar = [Sub class]; + testassert(((Class *)(__bridge void *)sub)[2] == [Sub class]); + + ivar = class_getInstanceVariable([Sub class], "subIvar"); + testassert(ivar); + testassert(2*sizeof(intptr_t) == (size_t)ivar_getOffset(ivar)); + testassert(0 == strcmp(ivar_getName(ivar), "subIvar")); + testassert(0 == strcmp(ivar_getTypeEncoding(ivar), "@")); + + ivar = class_getInstanceVariable([Super class], "superIvar"); + testassert(ivar); + testassert(sizeof(intptr_t) == (size_t)ivar_getOffset(ivar)); + testassert(0 == strcmp(ivar_getName(ivar), "superIvar")); + testassert(0 == strcmp(ivar_getTypeEncoding(ivar), "c")); + testassert(ivar == class_getInstanceVariable([Sub class], "superIvar")); + + ivar = class_getInstanceVariable([Super class], "subIvar"); + testassert(!ivar); + + ivar = class_getInstanceVariable(object_getClass([Sub class]), "subIvar"); + testassert(!ivar); + + ivar = class_getInstanceVariable([Sub class], "subIvar"); + object_setIvar(sub, ivar, sub); + testassert(sub->subIvar == sub); + testassert(sub == object_getIvar(sub, ivar)); + + testassert(NULL == class_getInstanceVariable(NULL, "foo")); + testassert(NULL == class_getInstanceVariable([Sub class], NULL)); + testassert(NULL == class_getInstanceVariable(NULL, NULL)); + + testassert(NULL == object_getIvar(sub, NULL)); + testassert(NULL == object_getIvar(NULL, ivar)); + testassert(NULL == object_getIvar(NULL, NULL)); + + object_setIvar(sub, NULL, NULL); + object_setIvar(NULL, ivar, NULL); + object_setIvar(NULL, NULL, NULL); + +#if !__has_feature(objc_arc) + + uintptr_t value; + + sub->subIvar = (id)10; + value = 0; + object_getInstanceVariable(sub, "subIvar", (void **)&value); + testassert(value == 10); + + object_setInstanceVariable(sub, "subIvar", (id)11); + testassert(sub->subIvar == (id)11); + + ivar = class_getInstanceVariable([Sub class], "subIvar"); + testassert(ivar == object_getInstanceVariable(sub, "subIvar", NULL)); + + testassert(NULL == object_getInstanceVariable(sub, NULL, NULL)); + testassert(NULL == object_getInstanceVariable(NULL, "foo", NULL)); + testassert(NULL == object_getInstanceVariable(NULL, NULL, NULL)); + value = 10; + testassert(NULL == object_getInstanceVariable(sub, NULL, (void **)&value)); + testassert(value == 0); + value = 10; + testassert(NULL == object_getInstanceVariable(NULL, "foo", (void **)&value)); + testassert(value == 0); + value = 10; + testassert(NULL == object_getInstanceVariable(NULL, NULL, (void **)&value)); + testassert(value == 0); + + testassert(NULL == object_setInstanceVariable(sub, NULL, NULL)); + testassert(NULL == object_setInstanceVariable(NULL, "foo", NULL)); + testassert(NULL == object_setInstanceVariable(NULL, NULL, NULL)); +#else + // provoke the same nullability warnings as the real test + objc_getClass(nil); + objc_getClass(nil); + objc_getClass(nil); + objc_getClass(nil); + objc_getClass(nil); + objc_getClass(nil); +#endif + + succeed(__FILE__); + return 0; +} diff --git a/test/ivarSlide.h b/test/ivarSlide.h new file mode 100644 index 0000000..d4e89ca --- /dev/null +++ b/test/ivarSlide.h @@ -0,0 +1,110 @@ +@interface Super : TestRoot { + @public +#if OLD + // nothing +#else + char superIvar; +#endif +} +@end + + +@interface ShrinkingSuper : TestRoot { + @public +#if OLD + id superIvar[5]; + __weak id superIvar2[5]; +#else + // nothing +#endif +} +@end; + + +@interface MoreStrongSuper : TestRoot { + @public +#if OLD + void *superIvar; +#else + id superIvar; +#endif +} +@end; + + +@interface MoreWeakSuper : TestRoot { + @public +#if OLD + id superIvar; +#else + __weak id superIvar; +#endif +} +@end; + +@interface MoreWeak2Super : TestRoot { + @public +#if OLD + void *superIvar; +#else + __weak id superIvar; +#endif +} +@end; + +@interface LessStrongSuper : TestRoot { + @public +#if OLD + id superIvar; +#else + void *superIvar; +#endif +} +@end; + +@interface LessWeakSuper : TestRoot { + @public +#if OLD + __weak id superIvar; +#else + id superIvar; +#endif +} +@end; + +@interface LessWeak2Super : TestRoot { + @public +#if OLD + __weak id superIvar; +#else + void *superIvar; +#endif +} +@end; + +@interface NoGCChangeSuper : TestRoot { + @public + intptr_t d; + char superc1; +#if OLD + // nothing +#else + char superc2; +#endif +} +@end + +@interface RunsOf15 : TestRoot { + @public + id scan1; + intptr_t skip15[15]; + id scan15[15]; + intptr_t skip15_2[15]; + id scan15_2[15]; +#if OLD + // nothing +#else + intptr_t skip1; +#endif +} +@end diff --git a/test/ivarSlide.m b/test/ivarSlide.m new file mode 100644 index 0000000..dcc0bcc --- /dev/null +++ b/test/ivarSlide.m @@ -0,0 +1,499 @@ +/* +TEST_BUILD + $C{COMPILE} -fobjc-weak $DIR/ivarSlide1.m $DIR/ivarSlide.m -o ivarSlide.exe +END +*/ + +#include "test.h" +#include +#include +#include +#include + +// fixme should check ARC layout handling +// current test checks GC layout handling which is dead +#define FIXME_CHECK_ARC_LAYOUTS 0 + +// ARC doesn't like __strong void* or __weak void* +#define gc_weak +#define gc_strong + +#define OLD 1 +#include "ivarSlide.h" + +#define ustrcmp(a, b) strcmp((char *)a, (char *)b) + +#ifdef __cplusplus +class CXX { + public: + static uintptr_t count; + uintptr_t magic; + CXX() : magic(1) { } + ~CXX() { count += magic; } +}; +uintptr_t CXX::count; +#endif + +@interface Bitfields : Super { + uint8_t uint8_ivar; + uint8_t uint8_bitfield1 :7; + uint8_t uint8_bitfield2 :1; + + id id_ivar; + + uintptr_t uintptr_ivar; + uintptr_t /*uintptr_bitfield1*/ :31; // anonymous (rdar://5723893) + uintptr_t uintptr_bitfield2 :1; + + id id_ivar2; +} +@end + +@implementation Bitfields @end + + +@interface Sub : Super { + @public + uintptr_t subIvar; + gc_strong void* subIvar2; + gc_weak void* subIvar3; +#ifdef __cplusplus + CXX cxx; +#else + // same layout as cxx + uintptr_t cxx_magic; +#endif +} +@end + +@implementation Sub @end + + +@interface Sub2 : ShrinkingSuper { + @public + gc_weak void* subIvar; + gc_strong void* subIvar2; +} +@end + +@implementation Sub2 @end + +@interface MoreStrongSub : MoreStrongSuper { id subIvar; } @end +@interface LessStrongSub : LessStrongSuper { id subIvar; } @end +@interface MoreWeakSub : MoreWeakSuper { id subIvar; } @end +@interface MoreWeak2Sub : MoreWeak2Super { id subIvar; } @end +@interface LessWeakSub : LessWeakSuper { id subIvar; } @end +@interface LessWeak2Sub : LessWeak2Super { id subIvar; } @end + +@implementation MoreStrongSub @end +@implementation LessStrongSub @end +@implementation MoreWeakSub @end +@implementation MoreWeak2Sub @end +@implementation LessWeakSub @end +@implementation LessWeak2Sub @end + +@interface NoGCChangeSub : NoGCChangeSuper { + @public + char subc3; +} +@end +@implementation NoGCChangeSub @end + +@interface RunsOf15Sub : RunsOf15 { + @public + char sub; +} +@end +@implementation RunsOf15Sub @end + + +int main(int argc __attribute__((unused)), char **argv) +{ +#if __has_feature(objc_arc) + testwarn("fixme check ARC layouts too"); +#endif + + /* + Bitfield ivars. + rdar://5723893 anonymous bitfield ivars crash when slid + rdar://5724385 bitfield ivar alignment incorrect + + Compile-time layout of Bitfields: + [0 scan] isa + [1 skip] uint8_ivar, uint8_bitfield + [2 scan] id_ivar + [3 skip] uintptr_ivar + [4 skip] uintptr_bitfield + [5 scan] id_ivar2 + + Runtime layout of Bitfields: + [0 scan] isa + [1 skip] superIvar + [2 skip] uint8_ivar, uint8_bitfield + [3 scan] id_ivar + [4 skip] uintptr_ivar + [5 skip] uintptr_bitfield + [6 scan] id_ivar2 + */ + + [Bitfields class]; + + testassert(class_getInstanceSize([Bitfields class]) == 7*sizeof(void*)); + + if (FIXME_CHECK_ARC_LAYOUTS) { + const uint8_t *bitfieldlayout; + bitfieldlayout = class_getIvarLayout([Bitfields class]); + testassert(0 == ustrcmp(bitfieldlayout, "\x01\x21\x21")); + + bitfieldlayout = class_getWeakIvarLayout([Bitfields class]); + testassert(bitfieldlayout == NULL); + } + + /* + Compile-time layout of Sub: + [0 scan] isa + [1 skip] subIvar + [2 scan] subIvar2 + [3 weak] subIvar3 + [6 skip] cxx + + Runtime layout of Sub: + [0 scan] isa + [1 skip] superIvar + [2 skip] subIvar + [3 scan] subIvar2 + [4 weak] subIvar3 + [6 skip] cxx + + Also, superIvar is only one byte, so subIvar's alignment must + be handled correctly. + + fixme test more layouts + */ + + Ivar ivar; + static Sub * volatile sub; + sub = [Sub new]; + sub->subIvar = 10; + uintptr_t *subwords = (uintptr_t *)(__bridge void*)sub; + testassert(subwords[2] == 10); + +#ifdef __cplusplus + testassert(subwords[5] == 1); + testassert(sub->cxx.magic == 1); + sub->cxx.magic++; + testassert(subwords[5] == 2); + testassert(sub->cxx.magic == 2); +# if __has_feature(objc_arc) + sub = nil; +# else + [sub dealloc]; +# endif + testassert(CXX::count == 2); +#endif + + testassert(class_getInstanceSize([Sub class]) == 6*sizeof(void*)); + + ivar = class_getInstanceVariable([Sub class], "subIvar"); + testassert(ivar); + testassert(2*sizeof(void*) == (size_t)ivar_getOffset(ivar)); + testassert(0 == strcmp(ivar_getName(ivar), "subIvar")); + testassert(0 == strcmp(ivar_getTypeEncoding(ivar), +#if __LP64__ + "Q" +#else + "L" +#endif + )); + +#ifdef __cplusplus + ivar = class_getInstanceVariable([Sub class], "cxx"); + testassert(ivar); +#endif + + ivar = class_getInstanceVariable([Super class], "superIvar"); + testassert(ivar); + testassert(sizeof(void*) == (size_t)ivar_getOffset(ivar)); + testassert(0 == strcmp(ivar_getName(ivar), "superIvar")); + testassert(0 == strcmp(ivar_getTypeEncoding(ivar), "c")); + + ivar = class_getInstanceVariable([Super class], "subIvar"); + testassert(!ivar); + + if (FIXME_CHECK_ARC_LAYOUTS) { + const uint8_t *superlayout; + const uint8_t *sublayout; + superlayout = class_getIvarLayout([Super class]); + sublayout = class_getIvarLayout([Sub class]); + testassert(0 == ustrcmp(superlayout, "\x01\x10")); + testassert(0 == ustrcmp(sublayout, "\x01\x21\x20")); + + superlayout = class_getWeakIvarLayout([Super class]); + sublayout = class_getWeakIvarLayout([Sub class]); + testassert(superlayout == NULL); + testassert(0 == ustrcmp(sublayout, "\x41\x10")); + } + + /* + Shrinking superclass. + Subclass ivars do not compact, but the GC layout needs to + update, including the gap that the superclass no longer spans. + + Compile-time layout of Sub2: + [0 scan] isa + [1-5 scan] superIvar + [6-10 weak] superIvar2 + [11 weak] subIvar + [12 scan] subIvar2 + + Runtime layout of Sub2: + [0 scan] isa + [1-10 skip] was superIvar + [11 weak] subIvar + [12 scan] subIvar2 + */ + + Sub2 *sub2 = [Sub2 new]; + uintptr_t *sub2words = (uintptr_t *)(__bridge void*)sub2; + sub2->subIvar = (void *)10; + testassert(sub2words[11] == 10); + + testassert(class_getInstanceSize([Sub2 class]) == 13*sizeof(void*)); + + ivar = class_getInstanceVariable([Sub2 class], "subIvar"); + testassert(ivar); + testassert(11*sizeof(void*) == (size_t)ivar_getOffset(ivar)); + testassert(0 == strcmp(ivar_getName(ivar), "subIvar")); + + ivar = class_getInstanceVariable([ShrinkingSuper class], "superIvar"); + testassert(!ivar); + + if (FIXME_CHECK_ARC_LAYOUTS) { + const uint8_t *superlayout; + const uint8_t *sublayout; + superlayout = class_getIvarLayout([ShrinkingSuper class]); + sublayout = class_getIvarLayout([Sub2 class]); + // only `isa` is left; superIvar[] and superIvar2[] are gone + testassert(superlayout == NULL || 0 == ustrcmp(superlayout, "\x01")); + testassert(0 == ustrcmp(sublayout, "\x01\xb1")); + + superlayout = class_getWeakIvarLayout([ShrinkingSuper class]); + sublayout = class_getWeakIvarLayout([Sub2 class]); + testassert(superlayout == NULL); + testassert(0 == ustrcmp(sublayout, "\xb1\x10")); + } + + /* + Ivars slide but GC layouts stay the same + Here, the last word of the superclass is misaligned, but + its GC layout includes a bit for that whole word. + Additionally, all of the subclass ivars fit into that word too, + both before and after sliding. + The runtime will try to slide the GC layout and must not be + confused (rdar://6851700). Note that the second skip-word may or may + not actually be included, because it crosses the end of the object. + + + Compile-time layout of NoGCChangeSub: + [0 scan] isa + [1 skip] d + [2 skip] superc1, subc3 + + Runtime layout of NoGCChangeSub: + [0 scan] isa + [1 skip] d + [2 skip] superc1, superc2, subc3 + */ + if (FIXME_CHECK_ARC_LAYOUTS) { + Ivar ivar1 = class_getInstanceVariable([NoGCChangeSub class], "superc1"); + testassert(ivar1); + Ivar ivar2 = class_getInstanceVariable([NoGCChangeSub class], "superc2"); + testassert(ivar2); + Ivar ivar3 = class_getInstanceVariable([NoGCChangeSub class], "subc3"); + testassert(ivar3); + testassert(ivar_getOffset(ivar1) != ivar_getOffset(ivar2) && + ivar_getOffset(ivar1) != ivar_getOffset(ivar3) && + ivar_getOffset(ivar2) != ivar_getOffset(ivar3)); + } + + /* Ivar layout includes runs of 15 words. + rdar://6859875 this would generate a truncated GC layout. + */ + if (FIXME_CHECK_ARC_LAYOUTS) { + const uint8_t *layout = + class_getIvarLayout(objc_getClass("RunsOf15Sub")); + testassert(layout); + int totalSkip = 0; + int totalScan = 0; + // should find 30+ each of skip and scan + uint8_t c; + while ((c = *layout++)) { + totalSkip += c>>4; + totalScan += c&0xf; + } + testassert(totalSkip >= 30); + testassert(totalScan >= 30); + } + + + /* + Non-strong -> strong + Classes do not change size, but GC layouts must be updated. + Both new and old ABI detect this case (rdar://5774578) + + Compile-time layout of MoreStrongSub: + [0 scan] isa + [1 skip] superIvar + [2 scan] subIvar + + Runtime layout of MoreStrongSub: + [0 scan] isa + [1 scan] superIvar + [2 scan] subIvar + */ + testassert(class_getInstanceSize([MoreStrongSub class]) == 3*sizeof(void*)); + if (FIXME_CHECK_ARC_LAYOUTS) { + const uint8_t *layout; + layout = class_getIvarLayout([MoreStrongSub class]); + testassert(layout == NULL); + + layout = class_getWeakIvarLayout([MoreStrongSub class]); + testassert(layout == NULL); + } + + + /* + Strong -> weak + Classes do not change size, but GC layouts must be updated. + Old ABI intentionally does not detect this case (rdar://5774578) + + Compile-time layout of MoreWeakSub: + [0 scan] isa + [1 scan] superIvar + [2 scan] subIvar + + Runtime layout of MoreWeakSub: + [0 scan] isa + [1 weak] superIvar + [2 scan] subIvar + */ + testassert(class_getInstanceSize([MoreWeakSub class]) == 3*sizeof(void*)); + if (FIXME_CHECK_ARC_LAYOUTS) { + const uint8_t *layout; + layout = class_getIvarLayout([MoreWeakSub class]); + testassert(0 == ustrcmp(layout, "\x01\x11")); + + layout = class_getWeakIvarLayout([MoreWeakSub class]); + testassert(0 == ustrcmp(layout, "\x11\x10")); + } + + + /* + Non-strong -> weak + Classes do not change size, but GC layouts must be updated. + Old ABI intentionally does not detect this case (rdar://5774578) + + Compile-time layout of MoreWeak2Sub: + [0 scan] isa + [1 skip] superIvar + [2 scan] subIvar + + Runtime layout of MoreWeak2Sub: + [0 scan] isa + [1 weak] superIvar + [2 scan] subIvar + */ + testassert(class_getInstanceSize([MoreWeak2Sub class]) == 3*sizeof(void*)); + if (FIXME_CHECK_ARC_LAYOUTS) { + const uint8_t *layout; + layout = class_getIvarLayout([MoreWeak2Sub class]); + testassert(0 == ustrcmp(layout, "\x01\x11") || + 0 == ustrcmp(layout, "\x01\x10\x01")); + + layout = class_getWeakIvarLayout([MoreWeak2Sub class]); + testassert(0 == ustrcmp(layout, "\x11\x10")); + } + + + /* + Strong -> non-strong + Classes do not change size, but GC layouts must be updated. + Old ABI intentionally does not detect this case (rdar://5774578) + + Compile-time layout of LessStrongSub: + [0 scan] isa + [1 scan] superIvar + [2 scan] subIvar + + Runtime layout of LessStrongSub: + [0 scan] isa + [1 skip] superIvar + [2 scan] subIvar + */ + testassert(class_getInstanceSize([LessStrongSub class]) == 3*sizeof(void*)); + if (FIXME_CHECK_ARC_LAYOUTS) { + const uint8_t *layout; + layout = class_getIvarLayout([LessStrongSub class]); + testassert(0 == ustrcmp(layout, "\x01\x11")); + + layout = class_getWeakIvarLayout([LessStrongSub class]); + testassert(layout == NULL); + } + + + /* + Weak -> strong + Classes do not change size, but GC layouts must be updated. + Both new and old ABI detect this case (rdar://5774578 rdar://6924114) + + Compile-time layout of LessWeakSub: + [0 scan] isa + [1 weak] superIvar + [2 scan] subIvar + + Runtime layout of LessWeakSub: + [0 scan] isa + [1 scan] superIvar + [2 scan] subIvar + */ + testassert(class_getInstanceSize([LessWeakSub class]) == 3*sizeof(void*)); + if (FIXME_CHECK_ARC_LAYOUTS) { + const uint8_t *layout; + layout = class_getIvarLayout([LessWeakSub class]); + testassert(layout == NULL); + + layout = class_getWeakIvarLayout([LessWeakSub class]); + testassert(layout == NULL); + } + + + /* + Weak -> non-strong + Classes do not change size, but GC layouts must be updated. + Old ABI intentionally does not detect this case (rdar://5774578) + + Compile-time layout of LessWeak2Sub: + [0 scan] isa + [1 weak] superIvar + [2 scan] subIvar + + Runtime layout of LessWeak2Sub: + [0 scan] isa + [1 skip] superIvar + [2 scan] subIvar + */ + testassert(class_getInstanceSize([LessWeak2Sub class]) == 3*sizeof(void*)); + if (FIXME_CHECK_ARC_LAYOUTS) { + const uint8_t *layout; + layout = class_getIvarLayout([LessWeak2Sub class]); + testassert(0 == ustrcmp(layout, "\x01\x11") || + 0 == ustrcmp(layout, "\x01\x10\x01")); + + layout = class_getWeakIvarLayout([LessWeak2Sub class]); + testassert(layout == NULL); + } + + + succeed(basename(argv[0])); + return 0; +} diff --git a/test/ivarSlide1.m b/test/ivarSlide1.m new file mode 100644 index 0000000..47b2e63 --- /dev/null +++ b/test/ivarSlide1.m @@ -0,0 +1,21 @@ +#include "test.h" +#include +#include + +#define OLD 0 +#include "ivarSlide.h" + +#include "testroot.i" + +@implementation Super @end + +@implementation ShrinkingSuper @end + +@implementation MoreStrongSuper @end +@implementation LessStrongSuper @end +@implementation MoreWeakSuper @end +@implementation MoreWeak2Super @end +@implementation LessWeakSuper @end +@implementation LessWeak2Super @end +@implementation NoGCChangeSuper @end +@implementation RunsOf15 @end diff --git a/test/literals.m b/test/literals.m new file mode 100644 index 0000000..e43673a --- /dev/null +++ b/test/literals.m @@ -0,0 +1,55 @@ +// TEST_CFLAGS -framework Foundation + +#import +#import +#import +#import +#import +#include "test.h" + +int main() { + PUSH_POOL { + +#if __has_feature(objc_bool) // placeholder until we get a more precise macro. + NSArray *array = @[ @1, @2, @YES, @NO, @"Hello", @"World" ]; + testassert([array count] == 6); + NSDictionary *dict = @{ @"Name" : @"John Q. Public", @"Age" : @42 }; + testassert([dict count] == 2); + NSDictionary *numbers = @{ @"π" : @M_PI, @"e" : @M_E }; + testassert([[numbers objectForKey:@"π"] doubleValue] == M_PI); + testassert([[numbers objectForKey:@"e"] doubleValue] == M_E); + + BOOL yesBool = YES; + BOOL noBool = NO; + array = @[ + @(true), + @(YES), + [NSNumber numberWithBool:YES], + @YES, + @(yesBool), + @((BOOL)YES), + + @(false), + @(NO), + [NSNumber numberWithBool:NO], + @NO, + @(noBool), + @((BOOL)NO), + ]; + NSData * jsonData = [NSJSONSerialization dataWithJSONObject:array options:0 error:nil]; + NSString * string = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding]; +#if __cplusplus + testassert([string isEqualToString:@"[true,true,true,true,true,true,false,false,false,false,false,false]"]); +#else + // C99 @(true) and @(false) evaluate to @(1) and @(0). + testassert([string isEqualToString:@"[1,true,true,true,true,true,0,false,false,false,false,false]"]); +#endif + +#endif + + } POP_POOL; + + succeed(__FILE__); + + return 0; +} diff --git a/test/load-noobjc.m b/test/load-noobjc.m new file mode 100644 index 0000000..4dd9f86 --- /dev/null +++ b/test/load-noobjc.m @@ -0,0 +1,38 @@ +/* +TEST_BUILD + $C{COMPILE} $DIR/load-noobjc.m -o load-noobjc.exe + $C{COMPILE} $DIR/load-noobjc2.m -o libload-noobjc2.dylib -bundle -bundle_loader load-noobjc.exe + $C{COMPILE} $DIR/load-noobjc3.m -o libload-noobjc3.dylib -bundle -bundle_loader load-noobjc.exe +END +*/ + +#include "test.h" +#include + +int state = 0; +semaphore_t go; + +void *thread(void *arg __unused) +{ + dlopen("libload-noobjc2.dylib", RTLD_LAZY); + fail("dlopen should not have returned"); +} + +int main() +{ + semaphore_create(mach_task_self(), &go, SYNC_POLICY_FIFO, 0); + + pthread_t th; + pthread_create(&th, nil, &thread, nil); + + // Wait for thread to stop in libload-noobjc2's +load method. + semaphore_wait(go); + + // run nooobjc3's constructor function. + // There's no objc code here so it shouldn't require the +load lock. + void *dlh = dlopen("libload-noobjc3.dylib", RTLD_LAZY); + testassert(dlh); + testassert(state == 1); + + succeed(__FILE__); +} diff --git a/test/load-noobjc2.m b/test/load-noobjc2.m new file mode 100644 index 0000000..bcca510 --- /dev/null +++ b/test/load-noobjc2.m @@ -0,0 +1,13 @@ +#include "test.h" + +extern semaphore_t go; + +OBJC_ROOT_CLASS +@interface noobjc @end +@implementation noobjc ++(void)load +{ + semaphore_signal(go); + while (1) sleep(1); +} +@end diff --git a/test/load-noobjc3.m b/test/load-noobjc3.m new file mode 100644 index 0000000..98e4071 --- /dev/null +++ b/test/load-noobjc3.m @@ -0,0 +1,9 @@ +#include "test.h" + +extern int state; + +__attribute__((constructor)) +static void ctor(void) +{ + state = 1; +} diff --git a/test/load-order.m b/test/load-order.m new file mode 100644 index 0000000..a0eb8f4 --- /dev/null +++ b/test/load-order.m @@ -0,0 +1,18 @@ +/* +TEST_BUILD + $C{COMPILE} $DIR/load-order3.m -o load-order3.dylib -dynamiclib + $C{COMPILE} $DIR/load-order2.m -o load-order2.dylib -x none load-order3.dylib -dynamiclib + $C{COMPILE} $DIR/load-order1.m -o load-order1.dylib -x none load-order3.dylib load-order2.dylib -dynamiclib + $C{COMPILE} $DIR/load-order.m -o load-order.exe -x none load-order3.dylib load-order2.dylib load-order1.dylib +END +*/ + +#include "test.h" + +extern int state1, state2, state3; + +int main() +{ + testassert(state1 == 1 && state2 == 2 && state3 == 3); + succeed(__FILE__); +} diff --git a/test/load-order1.m b/test/load-order1.m new file mode 100644 index 0000000..cdfa075 --- /dev/null +++ b/test/load-order1.m @@ -0,0 +1,15 @@ +#include "test.h" + +extern int state2, state3; + +int state1 = 0; + +OBJC_ROOT_CLASS +@interface One @end +@implementation One ++(void)load +{ + testassert(state2 == 2 && state3 == 3); + state1 = 1; +} +@end diff --git a/test/load-order2.m b/test/load-order2.m new file mode 100644 index 0000000..7537754 --- /dev/null +++ b/test/load-order2.m @@ -0,0 +1,15 @@ +#include "test.h" + +extern int state3; + +int state2 = 0; + +OBJC_ROOT_CLASS +@interface Two @end +@implementation Two ++(void)load +{ + testassert(state3 == 3); + state2 = 2; +} +@end diff --git a/test/load-order3.m b/test/load-order3.m new file mode 100644 index 0000000..7c34d5a --- /dev/null +++ b/test/load-order3.m @@ -0,0 +1,12 @@ +#include "test.h" + +int state3 = 0; + +OBJC_ROOT_CLASS +@interface Three @end +@implementation Three ++(void)load +{ + state3 = 3; +} +@end diff --git a/test/load-parallel.m b/test/load-parallel.m new file mode 100644 index 0000000..5b5bd94 --- /dev/null +++ b/test/load-parallel.m @@ -0,0 +1,63 @@ +/* +TEST_BUILD + $C{COMPILE} $DIR/load-parallel00.m -o load-parallel00.dylib -dynamiclib + $C{COMPILE} $DIR/load-parallel.m -x none load-parallel00.dylib -o load-parallel.exe -DCOUNT=10 + + $C{COMPILE} $DIR/load-parallel0.m -x none load-parallel00.dylib -o load-parallel0.dylib -dynamiclib -DN=0 + $C{COMPILE} $DIR/load-parallel0.m -x none load-parallel00.dylib -o load-parallel1.dylib -dynamiclib -DN=1 + $C{COMPILE} $DIR/load-parallel0.m -x none load-parallel00.dylib -o load-parallel2.dylib -dynamiclib -DN=2 + $C{COMPILE} $DIR/load-parallel0.m -x none load-parallel00.dylib -o load-parallel3.dylib -dynamiclib -DN=3 + $C{COMPILE} $DIR/load-parallel0.m -x none load-parallel00.dylib -o load-parallel4.dylib -dynamiclib -DN=4 + $C{COMPILE} $DIR/load-parallel0.m -x none load-parallel00.dylib -o load-parallel5.dylib -dynamiclib -DN=5 + $C{COMPILE} $DIR/load-parallel0.m -x none load-parallel00.dylib -o load-parallel6.dylib -dynamiclib -DN=6 + $C{COMPILE} $DIR/load-parallel0.m -x none load-parallel00.dylib -o load-parallel7.dylib -dynamiclib -DN=7 + $C{COMPILE} $DIR/load-parallel0.m -x none load-parallel00.dylib -o load-parallel8.dylib -dynamiclib -DN=8 + $C{COMPILE} $DIR/load-parallel0.m -x none load-parallel00.dylib -o load-parallel9.dylib -dynamiclib -DN=9 +END +*/ + +#include "test.h" + +#include +#include + +#ifndef COUNT +#error -DCOUNT=c missing +#endif + +extern atomic_int state; + +void *thread(void *arg) +{ + uintptr_t num = (uintptr_t)arg; + char *buf; + + asprintf(&buf, "load-parallel%lu.dylib", (unsigned long)num); + testprintf("%s\n", buf); + void *dlh = dlopen(buf, RTLD_LAZY); + if (!dlh) { + fail("dlopen failed: %s", dlerror()); + } + free(buf); + + return NULL; +} + +int main() +{ + pthread_t t[COUNT]; + uintptr_t i; + + for (i = 0; i < COUNT; i++) { + pthread_create(&t[i], NULL, thread, (void *)i); + } + + for (i = 0; i < COUNT; i++) { + pthread_join(t[i], NULL); + } + + testprintf("loaded %d/%d\n", (int)state, COUNT*26); + testassert(state == COUNT*26); + + succeed(__FILE__); +} diff --git a/test/load-parallel0.m b/test/load-parallel0.m new file mode 100644 index 0000000..2e135e9 --- /dev/null +++ b/test/load-parallel0.m @@ -0,0 +1,48 @@ +#ifndef N +#error -DN=n missing +#endif + +#import +#include +#include +#include +#include "test.h" +extern atomic_int state; + +#define CLASS0(n,nn) \ + OBJC_ROOT_CLASS \ + @interface C_##n##_##nn @end \ + @implementation C_##n##_##nn \ + +(void)load { \ + atomic_fetch_add_explicit(&state, 1, memory_order_relaxed); \ + usleep(10); } \ + @end + +#define CLASS(n,nn) CLASS0(n,nn) + +CLASS(a,N) +CLASS(b,N) +CLASS(c,N) +CLASS(d,N) +CLASS(e,N) +CLASS(f,N) +CLASS(g,N) +CLASS(h,N) +CLASS(i,N) +CLASS(j,N) +CLASS(k,N) +CLASS(l,N) +CLASS(m,N) +CLASS(n,N) +CLASS(o,N) +CLASS(p,N) +CLASS(q,N) +CLASS(r,N) +CLASS(s,N) +CLASS(t,N) +CLASS(u,N) +CLASS(v,N) +CLASS(w,N) +CLASS(x,N) +CLASS(y,N) +CLASS(z,N) diff --git a/test/load-parallel00.m b/test/load-parallel00.m new file mode 100644 index 0000000..9df43b4 --- /dev/null +++ b/test/load-parallel00.m @@ -0,0 +1,2 @@ +#include "test.h" +atomic_int state; diff --git a/test/load-reentrant.m b/test/load-reentrant.m new file mode 100644 index 0000000..1dac2f2 --- /dev/null +++ b/test/load-reentrant.m @@ -0,0 +1,36 @@ +/* +TEST_BUILD + $C{COMPILE} $DIR/load-reentrant.m -o load-reentrant.exe + $C{COMPILE} $DIR/load-reentrant2.m -o libload-reentrant2.dylib -bundle -bundle_loader load-reentrant.exe +END +*/ + +#include "test.h" +#include + +int state1 = 0; +int *state2_p; + +OBJC_ROOT_CLASS +@interface One @end +@implementation One ++(void)load +{ + state1 = 111; + + // Re-entrant +load doesn't get to complete until we do + void *dlh = dlopen("libload-reentrant2.dylib", RTLD_LAZY); + testassert(dlh); + state2_p = (int *)dlsym(dlh, "state2"); + testassert(state2_p); + testassert(*state2_p == 0); + + state1 = 1; +} +@end + +int main() +{ + testassert(state1 == 1 && state2_p && *state2_p == 2); + succeed(__FILE__); +} diff --git a/test/load-reentrant2.m b/test/load-reentrant2.m new file mode 100644 index 0000000..0cc6a40 --- /dev/null +++ b/test/load-reentrant2.m @@ -0,0 +1,23 @@ +#include "test.h" + +int state2 = 0; +extern int state1; + +static void ctor(void) __attribute__((constructor)); +static void ctor(void) +{ + // should be called during One's dlopen(), before Two's +load + testassert(state1 == 111); + testassert(state2 == 0); +} + +OBJC_ROOT_CLASS +@interface Two @end +@implementation Two ++(void) load +{ + // Does not run until One's +load completes + testassert(state1 == 1); + state2 = 2; +} +@end diff --git a/test/load.m b/test/load.m new file mode 100644 index 0000000..9e72870 --- /dev/null +++ b/test/load.m @@ -0,0 +1,99 @@ +// TEST_CONFIG + +#include "test.h" +#include "testroot.i" + +int state = 0; +int catstate = 0; +int deallocstate = 0; + +@interface Deallocator : TestRoot @end +@implementation Deallocator +-(void)dealloc { + deallocstate = 1; + SUPER_DEALLOC(); +} +@end + + +@interface Super : TestRoot @end +@implementation Super ++(void)initialize { + if (self == [Super class]) { + testprintf("in +[Super initialize]\n"); + testassert(state == 2); + state = 3; + } else { + testprintf("in +[Super initialize] on behalf of Sub\n"); + testassert(state == 3); + state = 4; + } +} +-(void)load { fail("-[Super load] called!"); } ++(void)load { + testprintf("in +[Super load]\n"); + testassert(state == 0); + state = 1; +} +@end + +@interface Sub : Super { } @end +@implementation Sub ++(void)load { + testprintf("in +[Sub load]\n"); + testassert(state == 1); + state = 2; +} +-(void)load { fail("-[Sub load] called!"); } +@end + +@interface SubNoLoad : Super { } @end +@implementation SubNoLoad @end + +@interface Super (Category) @end +@implementation Super (Category) +-(void)load { fail("-[Super(Category) load called!"); } ++(void)load { + testprintf("in +[Super(Category) load]\n"); + testassert(state >= 1); + catstate++; +} +@end + + +@interface Sub (Category) @end +@implementation Sub (Category) +-(void)load { fail("-[Sub(Category) load called!"); } ++(void)load { + testprintf("in +[Sub(Category) load]\n"); + testassert(state >= 2); + catstate++; + + // test autorelease pool + __autoreleasing id x; + x = AUTORELEASE([Deallocator new]); +} +@end + + +@interface SubNoLoad (Category) @end +@implementation SubNoLoad (Category) +-(void)load { fail("-[SubNoLoad(Category) load called!"); } ++(void)load { + testprintf("in +[SubNoLoad(Category) load]\n"); + testassert(state >= 1); + catstate++; +} +@end + +int main() +{ + testassert(state == 2); + testassert(catstate == 3); + testassert(deallocstate == 1); + [Sub class]; + testassert(state == 4); + testassert(catstate == 3); + + succeed(__FILE__); +} diff --git a/test/methodArgs.m b/test/methodArgs.m new file mode 100644 index 0000000..91af094 --- /dev/null +++ b/test/methodArgs.m @@ -0,0 +1,180 @@ +/* +TEST_CFLAGS -Wno-deprecated-declarations +TEST_BUILD_OUTPUT +.*methodArgs.m:\d+:\d+: warning: null passed to a callee that requires a non-null argument \[-Wnonnull\](\n.* note: expanded from macro 'testassert')? +.*methodArgs.m:\d+:\d+: warning: null passed to a callee that requires a non-null argument \[-Wnonnull\](\n.* note: expanded from macro 'testassert')? +.*methodArgs.m:\d+:\d+: warning: null passed to a callee that requires a non-null argument \[-Wnonnull\](\n.* note: expanded from macro 'testassert')? +.*methodArgs.m:\d+:\d+: warning: null passed to a callee that requires a non-null argument \[-Wnonnull\](\n.* note: expanded from macro 'testassert')? +.*methodArgs.m:\d+:\d+: warning: null passed to a callee that requires a non-null argument \[-Wnonnull\](\n.* note: expanded from macro 'testassert')? +.*methodArgs.m:\d+:\d+: warning: null passed to a callee that requires a non-null argument \[-Wnonnull\] +.*methodArgs.m:\d+:\d+: warning: null passed to a callee that requires a non-null argument \[-Wnonnull\] +.*methodArgs.m:\d+:\d+: warning: null passed to a callee that requires a non-null argument \[-Wnonnull\] +.*methodArgs.m:\d+:\d+: warning: null passed to a callee that requires a non-null argument \[-Wnonnull\] +.*methodArgs.m:\d+:\d+: warning: null passed to a callee that requires a non-null argument \[-Wnonnull\] +.*methodArgs.m:\d+:\d+: warning: null passed to a callee that requires a non-null argument \[-Wnonnull\] +.*methodArgs.m:\d+:\d+: warning: null passed to a callee that requires a non-null argument \[-Wnonnull\] +.*methodArgs.m:\d+:\d+: warning: null passed to a callee that requires a non-null argument \[-Wnonnull\] +.*methodArgs.m:\d+:\d+: warning: null passed to a callee that requires a non-null argument \[-Wnonnull\] +.*methodArgs.m:\d+:\d+: warning: null passed to a callee that requires a non-null argument \[-Wnonnull\] +.*methodArgs.m:\d+:\d+: warning: null passed to a callee that requires a non-null argument \[-Wnonnull\] +END +*/ + +#include "test.h" +#include "testroot.i" +#include +#include + +@interface Super : TestRoot @end +@implementation Super ++(id)method:(int)__unused arg :(void(^)(void)) __unused arg2 { + return 0; +} +@end + + +int main() +{ + char buf[128]; + char *arg; + struct objc_method_description *desc; + Method m = class_getClassMethod([Super class], sel_registerName("method::")); + testassert(m); + + testassert(method_getNumberOfArguments(m) == 4); + + arg = method_copyArgumentType(m, 0); + testassert(arg); + testassert(0 == strcmp(arg, "@")); + memset(buf, 1, 128); + method_getArgumentType(m, 0, buf, 1+strlen(arg)); + testassert(0 == strcmp(arg, buf)); + testassert(buf[1+strlen(arg)] == 1); + memset(buf, 1, 128); + method_getArgumentType(m, 0, buf, 2); + testassert(0 == strncmp(arg, buf, 2)); + testassert(buf[2] == 1); + free(arg); + + arg = method_copyArgumentType(m, 1); + testassert(arg); + testassert(0 == strcmp(arg, ":")); + memset(buf, 1, 128); + method_getArgumentType(m, 1, buf, 1+strlen(arg)); + testassert(0 == strcmp(arg, buf)); + testassert(buf[1+strlen(arg)] == 1); + memset(buf, 1, 128); + method_getArgumentType(m, 1, buf, 2); + testassert(0 == strncmp(arg, buf, 2)); + testassert(buf[2] == 1); + free(arg); + + arg = method_copyArgumentType(m, 2); + testassert(arg); + testassert(0 == strcmp(arg, "i")); + memset(buf, 1, 128); + method_getArgumentType(m, 2, buf, 1+strlen(arg)); + testassert(0 == strcmp(arg, buf)); + testassert(buf[1+strlen(arg)] == 1); + memset(buf, 1, 128); + method_getArgumentType(m, 2, buf, 2); + testassert(0 == strncmp(arg, buf, 2)); + testassert(buf[2] == 1); + free(arg); + + arg = method_copyArgumentType(m, 3); + testassert(arg); + testassert(0 == strcmp(arg, "@?")); + memset(buf, 1, 128); + method_getArgumentType(m, 3, buf, 1+strlen(arg)); + testassert(0 == strcmp(arg, buf)); + testassert(buf[1+strlen(arg)] == 1); + memset(buf, 1, 128); + method_getArgumentType(m, 3, buf, 2); + testassert(0 == strncmp(arg, buf, 2)); + testassert(buf[2] == 1); + memset(buf, 1, 128); + method_getArgumentType(m, 3, buf, 3); + testassert(0 == strncmp(arg, buf, 3)); + testassert(buf[3] == 1); + free(arg); + + arg = method_copyArgumentType(m, 4); + testassert(!arg); + + arg = method_copyArgumentType(m, -1); + testassert(!arg); + + memset(buf, 1, 128); + method_getArgumentType(m, 4, buf, 127); + testassert(buf[0] == 0); + testassert(buf[1] == 0); + testassert(buf[127] == 1); + + memset(buf, 1, 128); + method_getArgumentType(m, -1, buf, 127); + testassert(buf[0] == 0); + testassert(buf[1] == 0); + testassert(buf[127] == 1); + + arg = method_copyReturnType(m); + testassert(arg); + testassert(0 == strcmp(arg, "@")); + memset(buf, 1, 128); + method_getReturnType(m, buf, 1+strlen(arg)); + testassert(0 == strcmp(arg, buf)); + testassert(buf[1+strlen(arg)] == 1); + memset(buf, 1, 128); + method_getReturnType(m, buf, 2); + testassert(0 == strncmp(arg, buf, 2)); + testassert(buf[2] == 1); + free(arg); + + desc = method_getDescription(m); + testassert(desc); + testassert(desc->name == sel_registerName("method::")); +#if __LP64__ + testassert(0 == strcmp(desc->types, "@28@0:8i16@?20")); +#else + testassert(0 == strcmp(desc->types, "@16@0:4i8@?12")); +#endif + + testassert(0 == method_getNumberOfArguments(NULL)); + testassert(NULL == method_copyArgumentType(NULL, 10)); + testassert(NULL == method_copyReturnType(NULL)); + testassert(NULL == method_getDescription(NULL)); + + memset(buf, 1, 128); + method_getArgumentType(NULL, 1, buf, 127); + testassert(buf[0] == 0); + testassert(buf[1] == 0); + testassert(buf[127] == 1); + + memset(buf, 1, 128); + method_getArgumentType(NULL, 1, buf, 0); + testassert(buf[0] == 1); + testassert(buf[1] == 1); + + method_getArgumentType(m, 1, NULL, 128); + method_getArgumentType(m, 1, NULL, 0); + method_getArgumentType(NULL, 1, NULL, 128); + method_getArgumentType(NULL, 1, NULL, 0); + + memset(buf, 1, 128); + method_getReturnType(NULL, buf, 127); + testassert(buf[0] == 0); + testassert(buf[1] == 0); + testassert(buf[127] == 1); + + memset(buf, 1, 128); + method_getReturnType(NULL, buf, 0); + testassert(buf[0] == 1); + testassert(buf[1] == 1); + + method_getReturnType(m, NULL, 128); + method_getReturnType(m, NULL, 0); + method_getReturnType(NULL, NULL, 128); + method_getReturnType(NULL, NULL, 0); + + succeed(__FILE__); +} diff --git a/test/methodListSize.m b/test/methodListSize.m new file mode 100644 index 0000000..3821844 --- /dev/null +++ b/test/methodListSize.m @@ -0,0 +1,56 @@ +// TEST_CONFIG +// rdar://8052003 rdar://8077031 + +#include "test.h" + +#include +#include + +// add SELCOUNT methods to each of CLASSCOUNT classes +#define CLASSCOUNT 100 +#define SELCOUNT 200 + +int main() +{ + int i, j; + malloc_statistics_t start, end; + + Class root; + root = objc_allocateClassPair(NULL, "Root", 0); + objc_registerClassPair(root); + + Class classes[CLASSCOUNT]; + for (i = 0; i < CLASSCOUNT; i++) { + char *classname; + asprintf(&classname, "GrP_class_%d", i); + classes[i] = objc_allocateClassPair(root, classname, 0); + objc_registerClassPair(classes[i]); + free(classname); + } + + SEL selectors[SELCOUNT]; + for (i = 0; i < SELCOUNT; i++) { + char *selname; + asprintf(&selname, "GrP_sel_%d", i); + selectors[i] = sel_registerName(selname); + free(selname); + } + + malloc_zone_statistics(NULL, &start); + + for (i = 0; i < CLASSCOUNT; i++) { + for (j = 0; j < SELCOUNT; j++) { + class_addMethod(classes[i], selectors[j], (IMP)main, ""); + } + } + + malloc_zone_statistics(NULL, &end); + + // expected: 3-word method struct plus two other words + ssize_t expected = (sizeof(void*) * (3+2)) * SELCOUNT * CLASSCOUNT; + ssize_t actual = end.size_in_use - start.size_in_use; + testassert(actual < expected * 3); // allow generous fudge factor + + succeed(__FILE__); +} + diff --git a/test/msgSend-performance.m b/test/msgSend-performance.m new file mode 100644 index 0000000..30e1716 --- /dev/null +++ b/test/msgSend-performance.m @@ -0,0 +1,176 @@ +// TEST_CONFIG + +#include "test.h" +#include "testroot.i" +#include + +#if defined(__arm__) +// rdar://8331406 +# define ALIGN_() +#else +# define ALIGN_() asm(".align 4"); +#endif + + +@interface Super : TestRoot @end + +@implementation Super + +-(void)voidret_nop +{ + return; +} + +-(void)voidret_nop2 +{ + return; +} + +-(id)idret_nop +{ + return nil; +} + +-(long long)llret_nop +{ + return 0; +} + +-(struct stret)stret_nop +{ + return STRET_RESULT; +} + +-(double)fpret_nop +{ + return 0; +} + +-(long double)lfpret_nop +{ + return 0; +} + +-(vector_ulong2)vecret_nop +{ + return (vector_ulong2){0x1234567890abcdefULL, 0xfedcba0987654321ULL}; +} + +@end + + +@interface Sub : Super @end + +@implementation Sub @end + + +int main() +{ + + // cached message performance + // catches failure to cache or (abi=2) failure to fixup (#5584187) + // fixme unless they all fail + + uint64_t startTime; + uint64_t totalTime; + uint64_t targetTime; + + Sub *sub = [Sub new]; + + // fill cache first + + [sub voidret_nop]; + [sub voidret_nop2]; + [sub llret_nop]; + [sub stret_nop]; + [sub fpret_nop]; + [sub lfpret_nop]; + [sub vecret_nop]; + [sub voidret_nop]; + [sub voidret_nop2]; + [sub llret_nop]; + [sub stret_nop]; + [sub fpret_nop]; + [sub lfpret_nop]; + [sub vecret_nop]; + [sub voidret_nop]; + [sub voidret_nop2]; + [sub llret_nop]; + [sub stret_nop]; + [sub fpret_nop]; + [sub lfpret_nop]; + [sub vecret_nop]; + + // Some of these times have high variance on some compilers. + // The errors we're trying to catch should be catastrophically slow, + // so the margins here are generous to avoid false failures. + + // Use voidret because id return is too slow for perf test with ARC. + + // Pick smallest of voidret_nop and voidret_nop2 time + // in the hopes that one of them didn't collide in the method cache. + + // ALIGN_ matches loop alignment to make -O0 work + +#define COUNT 1000000 + + startTime = mach_absolute_time(); + ALIGN_(); + for (int i = 0; i < COUNT; i++) { + [sub voidret_nop]; + } + totalTime = mach_absolute_time() - startTime; + testprintf("time: voidret %llu\n", totalTime); + targetTime = totalTime; + + startTime = mach_absolute_time(); + ALIGN_(); + for (int i = 0; i < COUNT; i++) { + [sub voidret_nop2]; + } + totalTime = mach_absolute_time() - startTime; + testprintf("time: voidret2 %llu\n", totalTime); + if (totalTime < targetTime) targetTime = totalTime; + + startTime = mach_absolute_time(); + ALIGN_(); + for (int i = 0; i < COUNT; i++) { + [sub llret_nop]; + } + totalTime = mach_absolute_time() - startTime; + timecheck("llret ", totalTime, targetTime * 0.65, targetTime * 2.0); + + startTime = mach_absolute_time(); + ALIGN_(); + for (int i = 0; i < COUNT; i++) { + [sub stret_nop]; + } + totalTime = mach_absolute_time() - startTime; + timecheck("stret ", totalTime, targetTime * 0.65, targetTime * 5.0); + + startTime = mach_absolute_time(); + ALIGN_(); + for (int i = 0; i < COUNT; i++) { + [sub fpret_nop]; + } + totalTime = mach_absolute_time() - startTime; + timecheck("fpret ", totalTime, targetTime * 0.65, targetTime * 4.0); + + startTime = mach_absolute_time(); + ALIGN_(); + for (int i = 0; i < COUNT; i++) { + [sub lfpret_nop]; + } + totalTime = mach_absolute_time() - startTime; + timecheck("lfpret", totalTime, targetTime * 0.65, targetTime * 4.0); + + startTime = mach_absolute_time(); + ALIGN_(); + for (int i = 0; i < COUNT; i++) { + [sub vecret_nop]; + } + totalTime = mach_absolute_time() - startTime; + timecheck("vecret", totalTime, targetTime * 0.65, targetTime * 4.0); + + succeed(__FILE__); +} diff --git a/test/msgSend.m b/test/msgSend.m new file mode 100644 index 0000000..590dcf0 --- /dev/null +++ b/test/msgSend.m @@ -0,0 +1,2673 @@ +/* +asm-placeholder.exe is used below to disassemble objc_msgSend + +TEST_BUILD + $C{COMPILE} -x assembler $DIR/asm-placeholder.s -o asm-placeholder.exe + $C{COMPILE} $DIR/msgSend.m -o msgSend.exe -Wno-unused-parameter -Wundeclared-selector -D__DARWIN_OPAQUE_ARM_THREAD_STATE64=1 +END +*/ + +#include "test.h" +#include "testroot.i" + +#include +#include +#include +#include +#include +#include +#include + +// rdar://21694990 simd.h should have a vector_equal(a, b) function +static bool vector_equal(vector_ulong2 lhs, vector_ulong2 rhs) { + return vector_all(lhs == rhs); +} + +#if __arm64__ + // no stret dispatchers +# define SUPPORT_STRET 0 +# define objc_msgSend_stret objc_msgSend +# define objc_msgSendSuper2_stret objc_msgSendSuper2 +# define objc_msgSend_stret_debug objc_msgSend_debug +# define objc_msgSendSuper2_stret_debug objc_msgSendSuper2_debug +# define objc_msgLookup_stret objc_msgLookup +# define objc_msgLookupSuper2_stret objc_msgLookupSuper2 +# define method_invoke_stret method_invoke +#else +# define SUPPORT_STRET 1 +#endif + + +#if defined(__arm__) +// rdar://8331406 +# define ALIGN_() +#else +# define ALIGN_() asm(".align 4"); +#endif + +@interface Super : TestRoot @end + +@interface Sub : Super @end + +static int state = 0; + +static id SELF; + +// for typeof() shorthand only +id (*idmsg0)(id, SEL) __attribute__((unused)); +long long (*llmsg0)(id, SEL) __attribute__((unused)); +// struct stret (*stretmsg0)(id, SEL) __attribute__((unused)); +double (*fpmsg0)(id, SEL) __attribute__((unused)); +long double (*lfpmsg0)(id, SEL) __attribute__((unused)); +vector_ulong2 (*vecmsg0)(id, SEL) __attribute__((unused)); + +#define VEC1 ((vector_ulong2){1, 1}) +#define VEC2 ((vector_ulong2){2, 2}) +#define VEC3 ((vector_ulong2){3, 3}) +#define VEC4 ((vector_ulong2){4, 4}) +#define VEC5 ((vector_ulong2){5, 5}) +#define VEC6 ((vector_ulong2){6, 6}) +#define VEC7 ((vector_ulong2){7, 7}) +#define VEC8 ((vector_ulong2){8, 8}) + +#define CHECK_ARGS(sel) \ +do { \ + testassert(self == SELF); \ + testassert(_cmd == sel_registerName(#sel "::::::::::::::::::::::::::::::::::::"));\ + testassert(i1 == 1); \ + testassert(i2 == 2); \ + testassert(i3 == 3); \ + testassert(i4 == 4); \ + testassert(i5 == 5); \ + testassert(i6 == 6); \ + testassert(i7 == 7); \ + testassert(i8 == 8); \ + testassert(i9 == 9); \ + testassert(i10 == 10); \ + testassert(i11 == 11); \ + testassert(i12 == 12); \ + testassert(i13 == 13); \ + testassert(f1 == 1.0); \ + testassert(f2 == 2.0); \ + testassert(f3 == 3.0); \ + testassert(f4 == 4.0); \ + testassert(f5 == 5.0); \ + testassert(f6 == 6.0); \ + testassert(f7 == 7.0); \ + testassert(f8 == 8.0); \ + testassert(f9 == 9.0); \ + testassert(f10 == 10.0); \ + testassert(f11 == 11.0); \ + testassert(f12 == 12.0); \ + testassert(f13 == 13.0); \ + testassert(f14 == 14.0); \ + testassert(f15 == 15.0); \ + testassert(vector_all(v1 == 1)); \ + testassert(vector_all(v2 == 2)); \ + testassert(vector_all(v3 == 3)); \ + testassert(vector_all(v4 == 4)); \ + testassert(vector_all(v5 == 5)); \ + testassert(vector_all(v6 == 6)); \ + testassert(vector_all(v7 == 7)); \ + testassert(vector_all(v8 == 8)); \ +} while (0) + +#define CHECK_ARGS_NOARG(sel) \ +do { \ + testassert(self == SELF); \ + testassert(_cmd == sel_registerName(#sel "_noarg"));\ +} while (0) + +id NIL_RECEIVER; +id ID_RESULT; +long long LL_RESULT = __LONG_LONG_MAX__ - 2LL*__INT_MAX__; +double FP_RESULT = __DBL_MIN__ + __DBL_EPSILON__; +long double LFP_RESULT = __LDBL_MIN__ + __LDBL_EPSILON__; +vector_ulong2 VEC_RESULT = { 0x1234567890abcdefULL, 0xfedcba0987654321ULL }; +// STRET_RESULT in test.h + +static struct stret zero; + +struct stret_i1 { + uintptr_t i1; +}; +struct stret_i2 { + uintptr_t i1; + uintptr_t i2; +}; +struct stret_i3 { + uintptr_t i1; + uintptr_t i2; + uintptr_t i3; +}; +struct stret_i4 { + uintptr_t i1; + uintptr_t i2; + uintptr_t i3; +}; +struct stret_i5 { + uintptr_t i1; + uintptr_t i2; + uintptr_t i3; + uintptr_t i4; + uintptr_t i5; +}; +struct stret_i6 { + uintptr_t i1; + uintptr_t i2; + uintptr_t i3; + uintptr_t i4; + uintptr_t i5; + uintptr_t i6; +}; +struct stret_i7 { + uintptr_t i1; + uintptr_t i2; + uintptr_t i3; + uintptr_t i4; + uintptr_t i5; + uintptr_t i6; + uintptr_t i7; +}; +struct stret_i8 { + uintptr_t i1; + uintptr_t i2; + uintptr_t i3; + uintptr_t i4; + uintptr_t i5; + uintptr_t i8; + uintptr_t i9; +}; +struct stret_i9 { + uintptr_t i1; + uintptr_t i2; + uintptr_t i3; + uintptr_t i4; + uintptr_t i5; + uintptr_t i6; + uintptr_t i7; + uintptr_t i8; + uintptr_t i9; +}; + +struct stret_d1 { + double d1; +}; +struct stret_d2 { + double d1; + double d2; +}; +struct stret_d3 { + double d1; + double d2; + double d3; +}; +struct stret_d4 { + double d1; + double d2; + double d3; +}; +struct stret_d5 { + double d1; + double d2; + double d3; + double d4; + double d5; +}; +struct stret_d6 { + double d1; + double d2; + double d3; + double d4; + double d5; + double d6; +}; +struct stret_d7 { + double d1; + double d2; + double d3; + double d4; + double d5; + double d6; + double d7; +}; +struct stret_d8 { + double d1; + double d2; + double d3; + double d4; + double d5; + double d8; + double d9; +}; +struct stret_d9 { + double d1; + double d2; + double d3; + double d4; + double d5; + double d6; + double d7; + double d8; + double d9; +}; + + +@interface Super (Prototypes) + +// Method prototypes to pacify -Wundeclared-selector. + +-(id)idret: + (vector_ulong2)v1 :(vector_ulong2)v2 :(vector_ulong2)v3 :(vector_ulong2)v4 :(vector_ulong2)v5 :(vector_ulong2)v6 :(vector_ulong2)v7 :(vector_ulong2)v8 :(int)i1 :(int)i2 :(int)i3 :(int)i4 :(int)i5 :(int)i6 :(int)i7 :(int)i8 :(int)i9 :(int)i10 :(int)i11 :(int)i12 :(int)i13 :(double)f1 :(double)f2 :(double)f3 :(double)f4 :(double)f5 :(double)f6 :(double)f7 :(double)f8 :(double)f9 :(double)f10 :(double)f11 :(double)f12 :(double)f13 :(double)f14 :(double)f15; + +-(long long)llret: + (vector_ulong2)v1 :(vector_ulong2)v2 :(vector_ulong2)v3 :(vector_ulong2)v4 :(vector_ulong2)v5 :(vector_ulong2)v6 :(vector_ulong2)v7 :(vector_ulong2)v8 :(int)i1 :(int)i2 :(int)i3 :(int)i4 :(int)i5 :(int)i6 :(int)i7 :(int)i8 :(int)i9 :(int)i10 :(int)i11 :(int)i12 :(int)i13 :(double)f1 :(double)f2 :(double)f3 :(double)f4 :(double)f5 :(double)f6 :(double)f7 :(double)f8 :(double)f9 :(double)f10 :(double)f11 :(double)f12 :(double)f13 :(double)f14 :(double)f15; + +-(struct stret)stret: + (vector_ulong2)v1 :(vector_ulong2)v2 :(vector_ulong2)v3 :(vector_ulong2)v4 :(vector_ulong2)v5 :(vector_ulong2)v6 :(vector_ulong2)v7 :(vector_ulong2)v8 :(int)i1 :(int)i2 :(int)i3 :(int)i4 :(int)i5 :(int)i6 :(int)i7 :(int)i8 :(int)i9 :(int)i10 :(int)i11 :(int)i12 :(int)i13 :(double)f1 :(double)f2 :(double)f3 :(double)f4 :(double)f5 :(double)f6 :(double)f7 :(double)f8 :(double)f9 :(double)f10 :(double)f11 :(double)f12 :(double)f13 :(double)f14 :(double)f15; + +-(double)fpret: + (vector_ulong2)v1 :(vector_ulong2)v2 :(vector_ulong2)v3 :(vector_ulong2)v4 :(vector_ulong2)v5 :(vector_ulong2)v6 :(vector_ulong2)v7 :(vector_ulong2)v8 :(int)i1 :(int)i2 :(int)i3 :(int)i4 :(int)i5 :(int)i6 :(int)i7 :(int)i8 :(int)i9 :(int)i10 :(int)i11 :(int)i12 :(int)i13 :(double)f1 :(double)f2 :(double)f3 :(double)f4 :(double)f5 :(double)f6 :(double)f7 :(double)f8 :(double)f9 :(double)f10 :(double)f11 :(double)f12 :(double)f13 :(double)f14 :(double)f15; + +-(long double)lfpret: + (vector_ulong2)v1 :(vector_ulong2)v2 :(vector_ulong2)v3 :(vector_ulong2)v4 :(vector_ulong2)v5 :(vector_ulong2)v6 :(vector_ulong2)v7 :(vector_ulong2)v8 :(int)i1 :(int)i2 :(int)i3 :(int)i4 :(int)i5 :(int)i6 :(int)i7 :(int)i8 :(int)i9 :(int)i10 :(int)i11 :(int)i12 :(int)i13 :(double)f1 :(double)f2 :(double)f3 :(double)f4 :(double)f5 :(double)f6 :(double)f7 :(double)f8 :(double)f9 :(double)f10 :(double)f11 :(double)f12 :(double)f13 :(double)f14 :(double)f15; + +-(vector_ulong2)vecret: + (vector_ulong2)v1 :(vector_ulong2)v2 :(vector_ulong2)v3 :(vector_ulong2)v4 :(vector_ulong2)v5 :(vector_ulong2)v6 :(vector_ulong2)v7 :(vector_ulong2)v8 :(int)i1 :(int)i2 :(int)i3 :(int)i4 :(int)i5 :(int)i6 :(int)i7 :(int)i8 :(int)i9 :(int)i10 :(int)i11 :(int)i12 :(int)i13 :(double)f1 :(double)f2 :(double)f3 :(double)f4 :(double)f5 :(double)f6 :(double)f7 :(double)f8 :(double)f9 :(double)f10 :(double)f11 :(double)f12 :(double)f13 :(double)f14 :(double)f15; + +@end + + +// Zero all volatile registers. +#if __cplusplus +extern "C" +#endif +void stomp(void); + +#if __x86_64__ +asm("\n .text" + "\n .globl _stomp" + "\n _stomp:" + "\n mov $0, %rax" + "\n mov $0, %rcx" + "\n mov $0, %rdx" + "\n mov $0, %rsi" + "\n mov $0, %rdi" + "\n mov $0, %r8" + "\n mov $0, %r9" + "\n mov $0, %r10" + "\n mov $0, %r11" + "\n xorps %xmm0, %xmm0" + "\n xorps %xmm1, %xmm1" + "\n xorps %xmm2, %xmm2" + "\n xorps %xmm3, %xmm3" + "\n xorps %xmm4, %xmm4" + "\n xorps %xmm5, %xmm5" + "\n xorps %xmm6, %xmm6" + "\n xorps %xmm7, %xmm7" + "\n xorps %xmm8, %xmm8" + "\n xorps %xmm9, %xmm9" + "\n xorps %xmm10, %xmm10" + "\n xorps %xmm11, %xmm11" + "\n xorps %xmm12, %xmm12" + "\n xorps %xmm13, %xmm13" + "\n xorps %xmm14, %xmm14" + "\n xorps %xmm15, %xmm15" + "\n ret"); + +#elif __i386__ +asm("\n .text" + "\n .globl _stomp" + "\n _stomp:" + "\n mov $0, %eax" + "\n mov $0, %ecx" + "\n mov $0, %edx" + "\n xorps %xmm0, %xmm0" + "\n xorps %xmm1, %xmm1" + "\n xorps %xmm2, %xmm2" + "\n xorps %xmm3, %xmm3" + "\n xorps %xmm4, %xmm4" + "\n xorps %xmm5, %xmm5" + "\n xorps %xmm6, %xmm6" + "\n xorps %xmm7, %xmm7" + "\n ret"); + +#elif __arm64__ +asm("\n .text" + "\n .globl _stomp" + "\n _stomp:" + "\n mov x0, #0" + "\n mov x1, #0" + "\n mov x2, #0" + "\n mov x3, #0" + "\n mov x4, #0" + "\n mov x5, #0" + "\n mov x6, #0" + "\n mov x7, #0" + "\n mov x8, #0" + "\n mov x9, #0" + "\n mov x10, #0" + "\n mov x11, #0" + "\n mov x12, #0" + "\n mov x13, #0" + "\n mov x14, #0" + "\n mov x15, #0" + "\n mov x16, #0" + "\n mov x17, #0" + "\n movi d0, #0" + "\n movi d1, #0" + "\n movi d2, #0" + "\n movi d3, #0" + "\n movi d4, #0" + "\n movi d5, #0" + "\n movi d6, #0" + "\n movi d7, #0" + "\n ret" + ); + +#elif __arm__ +asm("\n .text" + "\n .globl _stomp" + "\n .thumb_func _stomp" + "\n _stomp:" + "\n mov r0, #0" + "\n mov r1, #0" + "\n mov r2, #0" + "\n mov r3, #0" + "\n mov r9, #0" + "\n mov r12, #0" + "\n vmov.i32 q0, #0" + "\n vmov.i32 q1, #0" + "\n vmov.i32 q2, #0" + "\n vmov.i32 q3, #0" + "\n vmov.i32 q8, #0" + "\n vmov.i32 q9, #0" + "\n vmov.i32 q10, #0" + "\n vmov.i32 q11, #0" + "\n vmov.i32 q12, #0" + "\n vmov.i32 q13, #0" + "\n vmov.i32 q14, #0" + "\n vmov.i32 q15, #0" + "\n bx lr" + ); + +#else +# error unknown architecture +#endif + + +@implementation Super +-(struct stret)stret { return STRET_RESULT; } + +// The IMPL_ methods are not called directly. Instead the non IMPL_ name is +// called. The resolver function installs the real method. This allows +// the resolver function to stomp on registers to help test register +// preservation in the uncached path. + ++(BOOL) resolveInstanceMethod:(SEL)sel +{ + const char *name = sel_getName(sel); + if (! strstr(name, "::::::::")) return false; + + testprintf("resolving %s\n", name); + + stomp(); + char *realName; + asprintf(&realName, "IMPL_%s", name); + SEL realSel = sel_registerName(realName); + free(realName); + + IMP imp = class_getMethodImplementation(self, realSel); + if (imp == &_objc_msgForward) return false; + return class_addMethod(self, sel, imp, ""); +} + +-(id)IMPL_idret: +(vector_ulong2)v1 :(vector_ulong2)v2 :(vector_ulong2)v3 :(vector_ulong2)v4 :(vector_ulong2)v5 :(vector_ulong2)v6 :(vector_ulong2)v7 :(vector_ulong2)v8 :(int)i1 :(int)i2 :(int)i3 :(int)i4 :(int)i5 :(int)i6 :(int)i7 :(int)i8 :(int)i9 :(int)i10 :(int)i11 :(int)i12 :(int)i13 :(double)f1 :(double)f2 :(double)f3 :(double)f4 :(double)f5 :(double)f6 :(double)f7 :(double)f8 :(double)f9 :(double)f10 :(double)f11 :(double)f12 :(double)f13 :(double)f14 :(double)f15 +{ + CHECK_ARGS(idret); + state = 1; + return ID_RESULT; +} + +-(long long)IMPL_llret: + (vector_ulong2)v1 :(vector_ulong2)v2 :(vector_ulong2)v3 :(vector_ulong2)v4 :(vector_ulong2)v5 :(vector_ulong2)v6 :(vector_ulong2)v7 :(vector_ulong2)v8 :(int)i1 :(int)i2 :(int)i3 :(int)i4 :(int)i5 :(int)i6 :(int)i7 :(int)i8 :(int)i9 :(int)i10 :(int)i11 :(int)i12 :(int)i13 :(double)f1 :(double)f2 :(double)f3 :(double)f4 :(double)f5 :(double)f6 :(double)f7 :(double)f8 :(double)f9 :(double)f10 :(double)f11 :(double)f12 :(double)f13 :(double)f14 :(double)f15 +{ + CHECK_ARGS(llret); + state = 2; + return LL_RESULT; +} + +-(struct stret)IMPL_stret: + (vector_ulong2)v1 :(vector_ulong2)v2 :(vector_ulong2)v3 :(vector_ulong2)v4 :(vector_ulong2)v5 :(vector_ulong2)v6 :(vector_ulong2)v7 :(vector_ulong2)v8 :(int)i1 :(int)i2 :(int)i3 :(int)i4 :(int)i5 :(int)i6 :(int)i7 :(int)i8 :(int)i9 :(int)i10 :(int)i11 :(int)i12 :(int)i13 :(double)f1 :(double)f2 :(double)f3 :(double)f4 :(double)f5 :(double)f6 :(double)f7 :(double)f8 :(double)f9 :(double)f10 :(double)f11 :(double)f12 :(double)f13 :(double)f14 :(double)f15 +{ + CHECK_ARGS(stret); + state = 3; + return STRET_RESULT; +} + +-(double)IMPL_fpret: + (vector_ulong2)v1 :(vector_ulong2)v2 :(vector_ulong2)v3 :(vector_ulong2)v4 :(vector_ulong2)v5 :(vector_ulong2)v6 :(vector_ulong2)v7 :(vector_ulong2)v8 :(int)i1 :(int)i2 :(int)i3 :(int)i4 :(int)i5 :(int)i6 :(int)i7 :(int)i8 :(int)i9 :(int)i10 :(int)i11 :(int)i12 :(int)i13 :(double)f1 :(double)f2 :(double)f3 :(double)f4 :(double)f5 :(double)f6 :(double)f7 :(double)f8 :(double)f9 :(double)f10 :(double)f11 :(double)f12 :(double)f13 :(double)f14 :(double)f15 +{ + CHECK_ARGS(fpret); + state = 4; + return FP_RESULT; +} + +-(long double)IMPL_lfpret: + (vector_ulong2)v1 :(vector_ulong2)v2 :(vector_ulong2)v3 :(vector_ulong2)v4 :(vector_ulong2)v5 :(vector_ulong2)v6 :(vector_ulong2)v7 :(vector_ulong2)v8 :(int)i1 :(int)i2 :(int)i3 :(int)i4 :(int)i5 :(int)i6 :(int)i7 :(int)i8 :(int)i9 :(int)i10 :(int)i11 :(int)i12 :(int)i13 :(double)f1 :(double)f2 :(double)f3 :(double)f4 :(double)f5 :(double)f6 :(double)f7 :(double)f8 :(double)f9 :(double)f10 :(double)f11 :(double)f12 :(double)f13 :(double)f14 :(double)f15 +{ + CHECK_ARGS(lfpret); + state = 5; + return LFP_RESULT; +} + +-(vector_ulong2)IMPL_vecret: + (vector_ulong2)v1 :(vector_ulong2)v2 :(vector_ulong2)v3 :(vector_ulong2)v4 :(vector_ulong2)v5 :(vector_ulong2)v6 :(vector_ulong2)v7 :(vector_ulong2)v8 :(int)i1 :(int)i2 :(int)i3 :(int)i4 :(int)i5 :(int)i6 :(int)i7 :(int)i8 :(int)i9 :(int)i10 :(int)i11 :(int)i12 :(int)i13 :(double)f1 :(double)f2 :(double)f3 :(double)f4 :(double)f5 :(double)f6 :(double)f7 :(double)f8 :(double)f9 :(double)f10 :(double)f11 :(double)f12 :(double)f13 :(double)f14 :(double)f15 +{ + CHECK_ARGS(vecret); + state = 6; + return VEC_RESULT; +} + + +-(id)idret_noarg +{ + CHECK_ARGS_NOARG(idret); + state = 11; + return ID_RESULT; +} + +-(long long)llret_noarg +{ + CHECK_ARGS_NOARG(llret); + state = 12; + return LL_RESULT; +} + +-(struct stret)stret_noarg +{ + CHECK_ARGS_NOARG(stret); + state = 13; + return STRET_RESULT; +} + +-(double)fpret_noarg +{ + CHECK_ARGS_NOARG(fpret); + state = 14; + return FP_RESULT; +} + +-(long double)lfpret_noarg +{ + CHECK_ARGS_NOARG(lfpret); + state = 15; + return LFP_RESULT; +} + +-(vector_ulong2)vecret_noarg +{ + CHECK_ARGS_NOARG(vecret); + state = 16; + return VEC_RESULT; +} + + +-(struct stret)stret_nop +{ + return STRET_RESULT; +} + + +#define STRET_IMP(n) \ ++(struct stret_##n)stret_##n##_zero \ +{ \ + struct stret_##n ret; \ + bzero(&ret, sizeof(ret)); \ + return ret; \ +} \ ++(struct stret_##n)stret_##n##_nonzero \ +{ \ + struct stret_##n ret; \ + memset(&ret, 0xff, sizeof(ret)); \ + return ret; \ +} + +STRET_IMP(i1) +STRET_IMP(i2) +STRET_IMP(i3) +STRET_IMP(i4) +STRET_IMP(i5) +STRET_IMP(i6) +STRET_IMP(i7) +STRET_IMP(i8) +STRET_IMP(i9) + +STRET_IMP(d1) +STRET_IMP(d2) +STRET_IMP(d3) +STRET_IMP(d4) +STRET_IMP(d5) +STRET_IMP(d6) +STRET_IMP(d7) +STRET_IMP(d8) +STRET_IMP(d9) + + ++(id)idret: + (vector_ulong2)v1 :(vector_ulong2)v2 :(vector_ulong2)v3 :(vector_ulong2)v4 :(vector_ulong2)v5 :(vector_ulong2)v6 :(vector_ulong2)v7 :(vector_ulong2)v8 :(int)i1 :(int)i2 :(int)i3 :(int)i4 :(int)i5 :(int)i6 :(int)i7 :(int)i8 :(int)i9 :(int)i10 :(int)i11 :(int)i12 :(int)i13 :(double)f1 :(double)f2 :(double)f3 :(double)f4 :(double)f5 :(double)f6 :(double)f7 :(double)f8 :(double)f9 :(double)f10 :(double)f11 :(double)f12 :(double)f13 :(double)f14 :(double)f15 +{ + fail("+idret called instead of -idret"); + CHECK_ARGS(idret); +} + ++(long long)llret: + (vector_ulong2)v1 :(vector_ulong2)v2 :(vector_ulong2)v3 :(vector_ulong2)v4 :(vector_ulong2)v5 :(vector_ulong2)v6 :(vector_ulong2)v7 :(vector_ulong2)v8 :(int)i1 :(int)i2 :(int)i3 :(int)i4 :(int)i5 :(int)i6 :(int)i7 :(int)i8 :(int)i9 :(int)i10 :(int)i11 :(int)i12 :(int)i13 :(double)f1 :(double)f2 :(double)f3 :(double)f4 :(double)f5 :(double)f6 :(double)f7 :(double)f8 :(double)f9 :(double)f10 :(double)f11 :(double)f12 :(double)f13 :(double)f14 :(double)f15 +{ + fail("+llret called instead of -llret"); + CHECK_ARGS(llret); +} + ++(struct stret)stret: + (vector_ulong2)v1 :(vector_ulong2)v2 :(vector_ulong2)v3 :(vector_ulong2)v4 :(vector_ulong2)v5 :(vector_ulong2)v6 :(vector_ulong2)v7 :(vector_ulong2)v8 :(int)i1 :(int)i2 :(int)i3 :(int)i4 :(int)i5 :(int)i6 :(int)i7 :(int)i8 :(int)i9 :(int)i10 :(int)i11 :(int)i12 :(int)i13 :(double)f1 :(double)f2 :(double)f3 :(double)f4 :(double)f5 :(double)f6 :(double)f7 :(double)f8 :(double)f9 :(double)f10 :(double)f11 :(double)f12 :(double)f13 :(double)f14 :(double)f15 +{ + fail("+stret called instead of -stret"); + CHECK_ARGS(stret); +} + ++(double)fpret: + (vector_ulong2)v1 :(vector_ulong2)v2 :(vector_ulong2)v3 :(vector_ulong2)v4 :(vector_ulong2)v5 :(vector_ulong2)v6 :(vector_ulong2)v7 :(vector_ulong2)v8 :(int)i1 :(int)i2 :(int)i3 :(int)i4 :(int)i5 :(int)i6 :(int)i7 :(int)i8 :(int)i9 :(int)i10 :(int)i11 :(int)i12 :(int)i13 :(double)f1 :(double)f2 :(double)f3 :(double)f4 :(double)f5 :(double)f6 :(double)f7 :(double)f8 :(double)f9 :(double)f10 :(double)f11 :(double)f12 :(double)f13 :(double)f14 :(double)f15 +{ + fail("+fpret called instead of -fpret"); + CHECK_ARGS(fpret); +} + ++(long double)lfpret: + (vector_ulong2)v1 :(vector_ulong2)v2 :(vector_ulong2)v3 :(vector_ulong2)v4 :(vector_ulong2)v5 :(vector_ulong2)v6 :(vector_ulong2)v7 :(vector_ulong2)v8 :(int)i1 :(int)i2 :(int)i3 :(int)i4 :(int)i5 :(int)i6 :(int)i7 :(int)i8 :(int)i9 :(int)i10 :(int)i11 :(int)i12 :(int)i13 :(double)f1 :(double)f2 :(double)f3 :(double)f4 :(double)f5 :(double)f6 :(double)f7 :(double)f8 :(double)f9 :(double)f10 :(double)f11 :(double)f12 :(double)f13 :(double)f14 :(double)f15 +{ + fail("+lfpret called instead of -lfpret"); + CHECK_ARGS(lfpret); +} + ++(id)idret_noarg +{ + fail("+idret_noarg called instead of -idret_noarg"); + CHECK_ARGS_NOARG(idret); +} + ++(long long)llret_noarg +{ + fail("+llret_noarg called instead of -llret_noarg"); + CHECK_ARGS_NOARG(llret); +} + ++(struct stret)stret_noarg +{ + fail("+stret_noarg called instead of -stret_noarg"); + CHECK_ARGS_NOARG(stret); +} + ++(double)fpret_noarg +{ + fail("+fpret_noarg called instead of -fpret_noarg"); + CHECK_ARGS_NOARG(fpret); +} + ++(long double)lfpret_noarg +{ + fail("+lfpret_noarg called instead of -lfpret_noarg"); + CHECK_ARGS_NOARG(lfpret); +} + ++(vector_ulong2)vecret_noarg +{ + fail("+vecret_noarg called instead of -vecret_noarg"); + CHECK_ARGS_NOARG(vecret); +} + +@end + + +@implementation Sub + +-(id)IMPL_idret: + (vector_ulong2)v1 :(vector_ulong2)v2 :(vector_ulong2)v3 :(vector_ulong2)v4 :(vector_ulong2)v5 :(vector_ulong2)v6 :(vector_ulong2)v7 :(vector_ulong2)v8 :(int)i1 :(int)i2 :(int)i3 :(int)i4 :(int)i5 :(int)i6 :(int)i7 :(int)i8 :(int)i9 :(int)i10 :(int)i11 :(int)i12 :(int)i13 :(double)f1 :(double)f2 :(double)f3 :(double)f4 :(double)f5 :(double)f6 :(double)f7 :(double)f8 :(double)f9 :(double)f10 :(double)f11 :(double)f12 :(double)f13 :(double)f14 :(double)f15 +{ + id result; + CHECK_ARGS(idret); + state = 100; + result = [super idret:v1:v2:v3:v4:v5:v6:v7:v8:i1:i2:i3:i4:i5:i6:i7:i8:i9:i10:i11:i12:i13:f1:f2:f3:f4:f5:f6:f7:f8:f9:f10:f11:f12:f13:f14:f15]; + testassert(state == 1); + testassert(result == ID_RESULT); + state = 101; + return result; +} + +-(long long)IMPL_llret: + (vector_ulong2)v1 :(vector_ulong2)v2 :(vector_ulong2)v3 :(vector_ulong2)v4 :(vector_ulong2)v5 :(vector_ulong2)v6 :(vector_ulong2)v7 :(vector_ulong2)v8 :(int)i1 :(int)i2 :(int)i3 :(int)i4 :(int)i5 :(int)i6 :(int)i7 :(int)i8 :(int)i9 :(int)i10 :(int)i11 :(int)i12 :(int)i13 :(double)f1 :(double)f2 :(double)f3 :(double)f4 :(double)f5 :(double)f6 :(double)f7 :(double)f8 :(double)f9 :(double)f10 :(double)f11 :(double)f12 :(double)f13 :(double)f14 :(double)f15 +{ + long long result; + CHECK_ARGS(llret); + state = 100; + result = [super llret:v1:v2:v3:v4:v5:v6:v7:v8:i1:i2:i3:i4:i5:i6:i7:i8:i9:i10:i11:i12:i13:f1:f2:f3:f4:f5:f6:f7:f8:f9:f10:f11:f12:f13:f14:f15]; + testassert(state == 2); + testassert(result == LL_RESULT); + state = 102; + return result; +} + +-(struct stret)IMPL_stret: + (vector_ulong2)v1 :(vector_ulong2)v2 :(vector_ulong2)v3 :(vector_ulong2)v4 :(vector_ulong2)v5 :(vector_ulong2)v6 :(vector_ulong2)v7 :(vector_ulong2)v8 :(int)i1 :(int)i2 :(int)i3 :(int)i4 :(int)i5 :(int)i6 :(int)i7 :(int)i8 :(int)i9 :(int)i10 :(int)i11 :(int)i12 :(int)i13 :(double)f1 :(double)f2 :(double)f3 :(double)f4 :(double)f5 :(double)f6 :(double)f7 :(double)f8 :(double)f9 :(double)f10 :(double)f11 :(double)f12 :(double)f13 :(double)f14 :(double)f15 +{ + struct stret result; + CHECK_ARGS(stret); + state = 100; + result = [super stret:v1:v2:v3:v4:v5:v6:v7:v8:i1:i2:i3:i4:i5:i6:i7:i8:i9:i10:i11:i12:i13:f1:f2:f3:f4:f5:f6:f7:f8:f9:f10:f11:f12:f13:f14:f15]; + testassert(state == 3); + testassert(stret_equal(result, STRET_RESULT)); + state = 103; + return result; +} + +-(double)IMPL_fpret: + (vector_ulong2)v1 :(vector_ulong2)v2 :(vector_ulong2)v3 :(vector_ulong2)v4 :(vector_ulong2)v5 :(vector_ulong2)v6 :(vector_ulong2)v7 :(vector_ulong2)v8 :(int)i1 :(int)i2 :(int)i3 :(int)i4 :(int)i5 :(int)i6 :(int)i7 :(int)i8 :(int)i9 :(int)i10 :(int)i11 :(int)i12 :(int)i13 :(double)f1 :(double)f2 :(double)f3 :(double)f4 :(double)f5 :(double)f6 :(double)f7 :(double)f8 :(double)f9 :(double)f10 :(double)f11 :(double)f12 :(double)f13 :(double)f14 :(double)f15 +{ + double result; + CHECK_ARGS(fpret); + state = 100; + result = [super fpret:v1:v2:v3:v4:v5:v6:v7:v8:i1:i2:i3:i4:i5:i6:i7:i8:i9:i10:i11:i12:i13:f1:f2:f3:f4:f5:f6:f7:f8:f9:f10:f11:f12:f13:f14:f15]; + testassert(state == 4); + testassert(result == FP_RESULT); + state = 104; + return result; +} + +-(long double)IMPL_lfpret: + (vector_ulong2)v1 :(vector_ulong2)v2 :(vector_ulong2)v3 :(vector_ulong2)v4 :(vector_ulong2)v5 :(vector_ulong2)v6 :(vector_ulong2)v7 :(vector_ulong2)v8 :(int)i1 :(int)i2 :(int)i3 :(int)i4 :(int)i5 :(int)i6 :(int)i7 :(int)i8 :(int)i9 :(int)i10 :(int)i11 :(int)i12 :(int)i13 :(double)f1 :(double)f2 :(double)f3 :(double)f4 :(double)f5 :(double)f6 :(double)f7 :(double)f8 :(double)f9 :(double)f10 :(double)f11 :(double)f12 :(double)f13 :(double)f14 :(double)f15 +{ + long double result; + CHECK_ARGS(lfpret); + state = 100; + result = [super lfpret:v1:v2:v3:v4:v5:v6:v7:v8:i1:i2:i3:i4:i5:i6:i7:i8:i9:i10:i11:i12:i13:f1:f2:f3:f4:f5:f6:f7:f8:f9:f10:f11:f12:f13:f14:f15]; + testassert(state == 5); + testassert(result == LFP_RESULT); + state = 105; + return result; +} + +-(vector_ulong2)IMPL_vecret: + (vector_ulong2)v1 :(vector_ulong2)v2 :(vector_ulong2)v3 :(vector_ulong2)v4 :(vector_ulong2)v5 :(vector_ulong2)v6 :(vector_ulong2)v7 :(vector_ulong2)v8 :(int)i1 :(int)i2 :(int)i3 :(int)i4 :(int)i5 :(int)i6 :(int)i7 :(int)i8 :(int)i9 :(int)i10 :(int)i11 :(int)i12 :(int)i13 :(double)f1 :(double)f2 :(double)f3 :(double)f4 :(double)f5 :(double)f6 :(double)f7 :(double)f8 :(double)f9 :(double)f10 :(double)f11 :(double)f12 :(double)f13 :(double)f14 :(double)f15 +{ + vector_ulong2 result; + CHECK_ARGS(vecret); + state = 100; + result = [super vecret:v1:v2:v3:v4:v5:v6:v7:v8:i1:i2:i3:i4:i5:i6:i7:i8:i9:i10:i11:i12:i13:f1:f2:f3:f4:f5:f6:f7:f8:f9:f10:f11:f12:f13:f14:f15]; + testassert(state == 6); + testassert(vector_equal(result, VEC_RESULT)); + state = 106; + return result; +} + + +-(id)idret_noarg +{ + id result; + CHECK_ARGS_NOARG(idret); + state = 100; + result = [super idret_noarg]; + testassert(state == 11); + testassert(result == ID_RESULT); + state = 111; + return result; +} + +-(long long)llret_noarg +{ + long long result; + CHECK_ARGS_NOARG(llret); + state = 100; + result = [super llret_noarg]; + testassert(state == 12); + testassert(result == LL_RESULT); + state = 112; + return result; +} + +-(struct stret)stret_noarg +{ + struct stret result; + CHECK_ARGS_NOARG(stret); + state = 100; + result = [super stret_noarg]; + testassert(state == 13); + testassert(stret_equal(result, STRET_RESULT)); + state = 113; + return result; +} + +-(double)fpret_noarg +{ + double result; + CHECK_ARGS_NOARG(fpret); + state = 100; + result = [super fpret_noarg]; + testassert(state == 14); + testassert(result == FP_RESULT); + state = 114; + return result; +} + +-(long double)lfpret_noarg +{ + long double result; + CHECK_ARGS_NOARG(lfpret); + state = 100; + result = [super lfpret_noarg]; + testassert(state == 15); + testassert(result == LFP_RESULT); + state = 115; + return result; +} + +-(vector_ulong2)vecret_noarg +{ + vector_ulong2 result; + CHECK_ARGS_NOARG(vecret); + state = 100; + result = [super vecret_noarg]; + testassert(state == 16); + testassert(vector_equal(result, VEC_RESULT)); + state = 116; + return result; +} + +@end + + +#if OBJC_HAVE_TAGGED_POINTERS + +@interface TaggedSub : Sub @end + +@implementation TaggedSub : Sub + ++(void)initialize +{ + _objc_registerTaggedPointerClass(OBJC_TAG_1, self); +} + +@end + +@interface ExtTaggedSub : Sub @end + +@implementation ExtTaggedSub : Sub + ++(void)initialize +{ + _objc_registerTaggedPointerClass(OBJC_TAG_First52BitPayload, self); +} + +@end + +#endif + + +// DWARF checking machinery + +#if TARGET_OS_WIN32 +// unimplemented on this platform +#define NO_DWARF_REASON "(windows)" + +#elif TARGET_OS_WATCH +// fixme unimplemented - ucontext not passed to signal handlers +#define NO_DWARF_REASON "(watchOS)" + +#elif __has_feature(objc_arc) +// ARC's extra RR calls hit the traps at the wrong times +#define NO_DWARF_REASON "(ARC)" + +#else + +#define TEST_DWARF 1 + +// Classes with no implementations and no cache contents from elsewhere. +@interface SuperDW : TestRoot @end +@implementation SuperDW @end + +@interface Sub0DW : SuperDW @end +@implementation Sub0DW @end + +@interface SubDW : Sub0DW @end +@implementation SubDW @end + +#include +#include +#include +#include + +bool caught = false; +uintptr_t clobbered; + +__BEGIN_DECLS +extern void callit(void *obj, void *sel, void *fn); +extern struct stret callit_stret(void *obj, void *sel, void *fn); +__END_DECLS + +#if __x86_64__ + +typedef uint8_t insn_t; +typedef insn_t clobbered_insn_t; +#define BREAK_INSN ((insn_t)0x06) // undefined +#define BREAK_SIGNAL SIGILL + +uintptr_t r12 = 0; +uintptr_t r13 = 0; +uintptr_t r14 = 0; +uintptr_t r15 = 0; +uintptr_t rbx = 0; +uintptr_t rbp = 0; +uintptr_t rsp = 0; +uintptr_t rip = 0; + +void handle_exception(x86_thread_state64_t *state) +{ + unw_cursor_t curs; + unw_word_t reg; + int err; + int step; + + err = unw_init_local(&curs, (unw_context_t *)state); + testassert(!err); + + step = unw_step(&curs); + testassert(step > 0); + + err = unw_get_reg(&curs, UNW_X86_64_R12, ®); + testassert(!err); + testassert(reg == r12); + + err = unw_get_reg(&curs, UNW_X86_64_R13, ®); + testassert(!err); + testassert(reg == r13); + + err = unw_get_reg(&curs, UNW_X86_64_R14, ®); + testassert(!err); + testassert(reg == r14); + + err = unw_get_reg(&curs, UNW_X86_64_R15, ®); + testassert(!err); + testassert(reg == r15); + + err = unw_get_reg(&curs, UNW_X86_64_RBX, ®); + testassert(!err); + testassert(reg == rbx); + + err = unw_get_reg(&curs, UNW_X86_64_RBP, ®); + testassert(!err); + testassert(reg == rbp); + + err = unw_get_reg(&curs, UNW_X86_64_RSP, ®); + testassert(!err); + testassert(reg == rsp); + + err = unw_get_reg(&curs, UNW_REG_IP, ®); + testassert(!err); + testassert(reg == rip); + + + // set thread state to unwound state + state->__r12 = r12; + state->__r13 = r13; + state->__r14 = r14; + state->__r15 = r15; + state->__rbx = rbx; + state->__rbp = rbp; + state->__rsp = rsp; + state->__rip = rip; + + caught = true; +} + + +void break_handler(int sig, siginfo_t *info, void *cc) +{ + ucontext_t *uc = (ucontext_t *)cc; + mcontext_t mc = (mcontext_t)uc->uc_mcontext; + + testprintf(" handled\n"); + + testassert(sig == BREAK_SIGNAL); + testassert((uintptr_t)info->si_addr == clobbered); + + handle_exception(&mc->__ss); + // handle_exception changed register state for continuation +} + +__asm__( +"\n .text" +"\n .globl _callit" +"\n _callit:" +// save sp and return address to variables +"\n movq (%rsp), %r10" +"\n movq %r10, _rip(%rip)" +"\n movq %rsp, _rsp(%rip)" +"\n addq $8, _rsp(%rip)" // rewind to pre-call value +// save other non-volatile registers to variables +"\n movq %rbx, _rbx(%rip)" +"\n movq %rbp, _rbp(%rip)" +"\n movq %r12, _r12(%rip)" +"\n movq %r13, _r13(%rip)" +"\n movq %r14, _r14(%rip)" +"\n movq %r15, _r15(%rip)" +"\n jmpq *%rdx" + ); + +__asm__( +"\n .text" +"\n .globl _callit_stret" +"\n _callit_stret:" +// save sp and return address to variables +"\n movq (%rsp), %r10" +"\n movq %r10, _rip(%rip)" +"\n movq %rsp, _rsp(%rip)" +"\n addq $8, _rsp(%rip)" // rewind to pre-call value +// save other non-volatile registers to variables +"\n movq %rbx, _rbx(%rip)" +"\n movq %rbp, _rbp(%rip)" +"\n movq %r12, _r12(%rip)" +"\n movq %r13, _r13(%rip)" +"\n movq %r14, _r14(%rip)" +"\n movq %r15, _r15(%rip)" +"\n jmpq *%rcx" + ); + + +// x86_64 + +#elif __i386__ + +typedef uint8_t insn_t; +typedef insn_t clobbered_insn_t; +#define BREAK_INSN ((insn_t)0xcc) // int3 +#define BREAK_SIGNAL SIGTRAP + +uintptr_t eip = 0; +uintptr_t esp = 0; +uintptr_t ebx = 0; +uintptr_t ebp = 0; +uintptr_t edi = 0; +uintptr_t esi = 0; +uintptr_t espfix = 0; + +void handle_exception(i386_thread_state_t *state) +{ + unw_cursor_t curs; + unw_word_t reg; + int err; + int step; + + err = unw_init_local(&curs, (unw_context_t *)state); + testassert(!err); + + step = unw_step(&curs); + testassert(step > 0); + + err = unw_get_reg(&curs, UNW_REG_IP, ®); + testassert(!err); + testassert(reg == eip); + + err = unw_get_reg(&curs, UNW_X86_ESP, ®); + testassert(!err); + testassert(reg == esp); + + err = unw_get_reg(&curs, UNW_X86_EBX, ®); + testassert(!err); + testassert(reg == ebx); + + err = unw_get_reg(&curs, UNW_X86_EBP, ®); + testassert(!err); + testassert(reg == ebp); + + err = unw_get_reg(&curs, UNW_X86_EDI, ®); + testassert(!err); + testassert(reg == edi); + + err = unw_get_reg(&curs, UNW_X86_ESI, ®); + testassert(!err); + testassert(reg == esi); + + + // set thread state to unwound state + state->__eip = eip; + state->__esp = esp + espfix; + state->__ebx = ebx; + state->__ebp = ebp; + state->__edi = edi; + state->__esi = esi; + + caught = true; +} + + +void break_handler(int sig, siginfo_t *info, void *cc) +{ + ucontext_t *uc = (ucontext_t *)cc; + mcontext_t mc = (mcontext_t)uc->uc_mcontext; + + testprintf(" handled\n"); + + testassert(sig == BREAK_SIGNAL); + testassert((uintptr_t)info->si_addr-1 == clobbered); + + handle_exception(&mc->__ss); + // handle_exception changed register state for continuation +} + +__asm__( +"\n .text" +"\n .globl _callit" +"\n _callit:" +// save sp and return address to variables +"\n call 1f" +"\n 1: popl %edx" +"\n movl (%esp), %eax" +"\n movl %eax, _eip-1b(%edx)" +"\n movl %esp, _esp-1b(%edx)" +"\n addl $4, _esp-1b(%edx)" // rewind to pre-call value +"\n movl $0, _espfix-1b(%edx)" +// save other non-volatile registers to variables +"\n movl %ebx, _ebx-1b(%edx)" +"\n movl %ebp, _ebp-1b(%edx)" +"\n movl %edi, _edi-1b(%edx)" +"\n movl %esi, _esi-1b(%edx)" +"\n jmpl *12(%esp)" + ); + +__asm__( +"\n .text" +"\n .globl _callit_stret" +"\n _callit_stret:" +// save sp and return address to variables +"\n call 1f" +"\n 1: popl %edx" +"\n movl (%esp), %eax" +"\n movl %eax, _eip-1b(%edx)" +"\n movl %esp, _esp-1b(%edx)" +"\n addl $4, _esp-1b(%edx)" // rewind to pre-call value +"\n movl $4, _espfix-1b(%edx)" +// save other non-volatile registers to variables +"\n movl %ebx, _ebx-1b(%edx)" +"\n movl %ebp, _ebp-1b(%edx)" +"\n movl %edi, _edi-1b(%edx)" +"\n movl %esi, _esi-1b(%edx)" +"\n jmpl *16(%esp)" + ); + + +// i386 +#elif __arm64__ + +#include + +typedef uint32_t insn_t; +typedef insn_t clobbered_insn_t; +#define BREAK_INSN ((insn_t)0xd4200020) // brk #1 +#define BREAK_SIGNAL SIGTRAP + +uintptr_t x19 = 0; +uintptr_t x20 = 0; +uintptr_t x21 = 0; +uintptr_t x22 = 0; +uintptr_t x23 = 0; +uintptr_t x24 = 0; +uintptr_t x25 = 0; +uintptr_t x26 = 0; +uintptr_t x27 = 0; +uintptr_t x28 = 0; +uintptr_t fp = 0; +uintptr_t sp = 0; +uintptr_t pc = 0; + +void handle_exception(arm_thread_state64_t *state) +{ + unw_cursor_t curs; + unw_word_t reg; + int err; + int step; + + // libunwind layout differs from mcontext layout + // GPRs are the same but vector registers are not + unw_context_t unwstate; + unw_getcontext(&unwstate); + memcpy(&unwstate, state, sizeof(*state)); + + // libunwind and xnu sign some pointers differently + // xnu: not signed (fixme this may change?) + // libunwind: PC and LR both signed with return address key and SP + void **pcp = &((arm_thread_state64_t *)&unwstate)->__opaque_pc; + *pcp = ptrauth_sign_unauthenticated((void*)__darwin_arm_thread_state64_get_pc(*state), + ptrauth_key_return_address, + (ptrauth_extra_data_t)__darwin_arm_thread_state64_get_sp(*state)); + void **lrp = &((arm_thread_state64_t *)&unwstate)->__opaque_lr; + *lrp = ptrauth_sign_unauthenticated((void*)__darwin_arm_thread_state64_get_lr(*state), + ptrauth_key_return_address, + (ptrauth_extra_data_t)__darwin_arm_thread_state64_get_sp(*state)); + + err = unw_init_local(&curs, &unwstate); + testassert(!err); + + step = unw_step(&curs); + testassert(step > 0); + + err = unw_get_reg(&curs, UNW_ARM64_X19, ®); + testassert(!err); + testassert(reg == x19); + + err = unw_get_reg(&curs, UNW_ARM64_X20, ®); + testassert(!err); + testassert(reg == x20); + + err = unw_get_reg(&curs, UNW_ARM64_X21, ®); + testassert(!err); + testassert(reg == x21); + + err = unw_get_reg(&curs, UNW_ARM64_X22, ®); + testassert(!err); + testassert(reg == x22); + + err = unw_get_reg(&curs, UNW_ARM64_X23, ®); + testassert(!err); + testassert(reg == x23); + + err = unw_get_reg(&curs, UNW_ARM64_X24, ®); + testassert(!err); + testassert(reg == x24); + + err = unw_get_reg(&curs, UNW_ARM64_X25, ®); + testassert(!err); + testassert(reg == x25); + + err = unw_get_reg(&curs, UNW_ARM64_X26, ®); + testassert(!err); + testassert(reg == x26); + + err = unw_get_reg(&curs, UNW_ARM64_X27, ®); + testassert(!err); + testassert(reg == x27); + + err = unw_get_reg(&curs, UNW_ARM64_X28, ®); + testassert(!err); + testassert(reg == x28); + + err = unw_get_reg(&curs, UNW_ARM64_FP, ®); + testassert(!err); + testassert(reg == fp); + + err = unw_get_reg(&curs, UNW_ARM64_SP, ®); + testassert(!err); + testassert(reg == sp); + + err = unw_get_reg(&curs, UNW_REG_IP, ®); + testassert(!err); + // libunwind's return is signed but our value is not + reg = (uintptr_t)ptrauth_strip((void *)reg, ptrauth_key_return_address); + testassert(reg == pc); + + // libunwind restores PC into LR and doesn't track LR + // err = unw_get_reg(&curs, UNW_ARM64_LR, ®); + // testassert(!err); + // testassert(reg == lr); + + // set signal handler's thread state to unwound state + state->__x[19] = x19; + state->__x[20] = x20; + state->__x[21] = x21; + state->__x[22] = x22; + state->__x[23] = x23; + state->__x[24] = x24; + state->__x[25] = x25; + state->__x[26] = x26; + state->__x[27] = x27; + state->__x[28] = x28; + state->__opaque_fp = (void *)fp; + state->__opaque_lr = (void *)pc; // libunwind restores PC into LR + state->__opaque_sp = (void *)sp; + state->__opaque_pc = (void *)pc; + + caught = true; +} + + +void break_handler(int sig, siginfo_t *info, void *cc) +{ + ucontext_t *uc = (ucontext_t *)cc; + struct __darwin_mcontext64 *mc = (struct __darwin_mcontext64 *)uc->uc_mcontext; + + testprintf(" handled\n"); + + testassert(sig == BREAK_SIGNAL); + testassert((uintptr_t)info->si_addr == clobbered); + + handle_exception(&mc->__ss); + // handle_exception changed register state for continuation +} + + +__asm__( +"\n .text" +"\n .globl _callit" +"\n _callit:" +// save sp and return address to variables +"\n mov x16, sp" +"\n adrp x17, _sp@PAGE" +"\n str x16, [x17, _sp@PAGEOFF]" +"\n adrp x17, _pc@PAGE" +"\n str lr, [x17, _pc@PAGEOFF]" +// save other non-volatile registers to variables +"\n adrp x17, _x19@PAGE" +"\n str x19, [x17, _x19@PAGEOFF]" +"\n adrp x17, _x19@PAGE" +"\n str x20, [x17, _x20@PAGEOFF]" +"\n adrp x17, _x19@PAGE" +"\n str x21, [x17, _x21@PAGEOFF]" +"\n adrp x17, _x19@PAGE" +"\n str x22, [x17, _x22@PAGEOFF]" +"\n adrp x17, _x19@PAGE" +"\n str x23, [x17, _x23@PAGEOFF]" +"\n adrp x17, _x19@PAGE" +"\n str x24, [x17, _x24@PAGEOFF]" +"\n adrp x17, _x19@PAGE" +"\n str x25, [x17, _x25@PAGEOFF]" +"\n adrp x17, _x19@PAGE" +"\n str x26, [x17, _x26@PAGEOFF]" +"\n adrp x17, _x19@PAGE" +"\n str x27, [x17, _x27@PAGEOFF]" +"\n adrp x17, _x19@PAGE" +"\n str x28, [x17, _x28@PAGEOFF]" +"\n adrp x17, _x19@PAGE" +"\n str fp, [x17, _fp@PAGEOFF]" +"\n br x2" + ); + + +// arm64 +#elif __arm__ + +#include + +typedef uint16_t insn_t; +typedef struct { + insn_t first; + insn_t second; + bool thirty_two; +} clobbered_insn_t; +#define BREAK_INSN ((insn_t)0xdefe) // trap +#define BREAK_SIGNAL SIGILL +#define BREAK_SIGNAL2 SIGTRAP + +uintptr_t r4 = 0; +uintptr_t r5 = 0; +uintptr_t r6 = 0; +uintptr_t r7 = 0; +uintptr_t r8 = 0; +uintptr_t r10 = 0; +uintptr_t r11 = 0; +uintptr_t sp = 0; +uintptr_t pc = 0; + +void handle_exception(arm_thread_state_t *state) +{ + // No unwind tables on this architecture so no libunwind checks. + // We run the test anyway to verify instruction-level coverage. + + // set thread state to unwound state + state->__r[4] = r4; + state->__r[5] = r5; + state->__r[6] = r6; + state->__r[7] = r7; + state->__r[8] = r8; + state->__r[10] = r10; + state->__r[11] = r11; + state->__sp = sp; + state->__pc = pc; + // clear IT... bits so caller doesn't act on them + state->__cpsr &= ~0x0600fc00; + + caught = true; +} + + +void break_handler(int sig, siginfo_t *info, void *cc) +{ + ucontext_t *uc = (ucontext_t *)cc; + struct __darwin_mcontext32 *mc = (struct __darwin_mcontext32 *)uc->uc_mcontext; + + testprintf(" handled\n"); + + testassert(sig == BREAK_SIGNAL || sig == BREAK_SIGNAL2); + testassert((uintptr_t)info->si_addr == clobbered); + + handle_exception(&mc->__ss); + // handle_exception changed register state for continuation +} + + +__asm__( +"\n .text" +"\n .syntax unified" +"\n .code 16" +"\n .align 5" +"\n .globl _callit" +"\n .thumb_func" +"\n _callit:" +// save sp and return address to variables +"\n movw r12, :lower16:(_sp-1f-4)" +"\n movt r12, :upper16:(_sp-1f-4)" +"\n 1: add r12, pc" +"\n str sp, [r12]" +"\n movw r12, :lower16:(_pc-1f-4)" +"\n movt r12, :upper16:(_pc-1f-4)" +"\n 1: add r12, pc" +"\n str lr, [r12]" +// save other non-volatile registers to variables +"\n movw r12, :lower16:(_r4-1f-4)" +"\n movt r12, :upper16:(_r4-1f-4)" +"\n 1: add r12, pc" +"\n str r4, [r12]" +"\n movw r12, :lower16:(_r5-1f-4)" +"\n movt r12, :upper16:(_r5-1f-4)" +"\n 1: add r12, pc" +"\n str r5, [r12]" +"\n movw r12, :lower16:(_r6-1f-4)" +"\n movt r12, :upper16:(_r6-1f-4)" +"\n 1: add r12, pc" +"\n str r6, [r12]" +"\n movw r12, :lower16:(_r7-1f-4)" +"\n movt r12, :upper16:(_r7-1f-4)" +"\n 1: add r12, pc" +"\n str r7, [r12]" +"\n movw r12, :lower16:(_r8-1f-4)" +"\n movt r12, :upper16:(_r8-1f-4)" +"\n 1: add r12, pc" +"\n str r8, [r12]" +"\n movw r12, :lower16:(_r10-1f-4)" +"\n movt r12, :upper16:(_r10-1f-4)" +"\n 1: add r12, pc" +"\n str r10, [r12]" +"\n movw r12, :lower16:(_r11-1f-4)" +"\n movt r12, :upper16:(_r11-1f-4)" +"\n 1: add r12, pc" +"\n str r11, [r12]" +"\n bx r2" + ); + +__asm__( +"\n .text" +"\n .syntax unified" +"\n .code 16" +"\n .align 5" +"\n .globl _callit_stret" +"\n .thumb_func" +"\n _callit_stret:" +// save sp and return address to variables +"\n movw r12, :lower16:(_sp-1f-4)" +"\n movt r12, :upper16:(_sp-1f-4)" +"\n 1: add r12, pc" +"\n str sp, [r12]" +"\n movw r12, :lower16:(_pc-1f-4)" +"\n movt r12, :upper16:(_pc-1f-4)" +"\n 1: add r12, pc" +"\n str lr, [r12]" +// save other non-volatile registers to variables +"\n movw r12, :lower16:(_r4-1f-4)" +"\n movt r12, :upper16:(_r4-1f-4)" +"\n 1: add r12, pc" +"\n str r4, [r12]" +"\n movw r12, :lower16:(_r5-1f-4)" +"\n movt r12, :upper16:(_r5-1f-4)" +"\n 1: add r12, pc" +"\n str r5, [r12]" +"\n movw r12, :lower16:(_r6-1f-4)" +"\n movt r12, :upper16:(_r6-1f-4)" +"\n 1: add r12, pc" +"\n str r6, [r12]" +"\n movw r12, :lower16:(_r7-1f-4)" +"\n movt r12, :upper16:(_r7-1f-4)" +"\n 1: add r12, pc" +"\n str r7, [r12]" +"\n movw r12, :lower16:(_r8-1f-4)" +"\n movt r12, :upper16:(_r8-1f-4)" +"\n 1: add r12, pc" +"\n str r8, [r12]" +"\n movw r12, :lower16:(_r10-1f-4)" +"\n movt r12, :upper16:(_r10-1f-4)" +"\n 1: add r12, pc" +"\n str r10, [r12]" +"\n movw r12, :lower16:(_r11-1f-4)" +"\n movt r12, :upper16:(_r11-1f-4)" +"\n 1: add r12, pc" +"\n str r11, [r12]" +"\n bx r3" + ); + + +// arm +#else + +#error unknown architecture + +#endif + + +#if __arm__ +uintptr_t fnaddr(void *fn) { return (uintptr_t)fn & ~(uintptr_t)1; } +#else +uintptr_t fnaddr(void *fn) { return (uintptr_t)fn; } +#endif + +insn_t set(uintptr_t dst, insn_t newvalue) +{ + uintptr_t start = dst & ~(PAGE_MAX_SIZE-1); + int err = mprotect((void*)start, PAGE_MAX_SIZE, PROT_READ|PROT_WRITE); + if (err) fail("mprotect(%p, RW-) failed (%d)", start, errno); + insn_t oldvalue = *(insn_t *)dst; + *(insn_t *)dst = newvalue; + err = mprotect((void*)start, PAGE_MAX_SIZE, PROT_READ|PROT_EXEC); + if (err) fail("mprotect(%p, R-X) failed (%d)", start, errno); + return oldvalue; +} + +clobbered_insn_t clobber(void *fn, uintptr_t offset) +{ + clobbered = fnaddr(fn) + offset; + insn_t oldInsn = set(fnaddr(fn) + offset, BREAK_INSN); +#if __arm__ + // Need to clobber 32-bit Thumb instructions with another 32-bit instruction + // to preserve the behavior of IT... blocks. + clobbered_insn_t result = {oldInsn, 0, false}; + if (((oldInsn & 0xf000) == 0xf000) || + ((oldInsn & 0xf800) == 0xe800)) + { + testprintf("clobbering thumb-32 at offset %zu\n", offset); + // Old insn was 32-bit. Clobber all of it. + // First unclobber. + set(fnaddr(fn) + offset, oldInsn); + // f7f0 a0f0 is a "permanently undefined" Thumb-2 instruction. + // Clobber the first half last so `clobbered` gets the right value. + result.second = set(fnaddr(fn) + offset + 2, 0xa0f0); + result.first = set(fnaddr(fn) + offset, 0xf7f0); + result.thirty_two = true; + } + return result; +#else + return oldInsn; +#endif +} + +void unclobber(void *fn, uintptr_t offset, clobbered_insn_t oldvalue) +{ +#if __arm__ + if (oldvalue.thirty_two) { + set(fnaddr(fn) + offset + 2, oldvalue.second); + } + set(fnaddr(fn) + offset, oldvalue.first); +#else + set(fnaddr(fn) + offset, oldvalue); +#endif +} + + +// terminator for the list of instruction offsets +#define END_OFFSETS ~0UL + +// Disassemble instructions symbol.. offsets) *p-- = END_OFFSETS; +#endif + + return p; +} + + +uintptr_t *getOffsets(const char *symname, uintptr_t *outBase) +{ + // Find the start of our function. + uintptr_t symbol = (uintptr_t)dlsym(RTLD_NEXT, symname); + if (!symbol) return nil; +#if __has_feature(ptrauth_calls) + symbol = (uintptr_t) + ptrauth_strip((void*)symbol, ptrauth_key_function_pointer); +#endif + + if (outBase) *outBase = symbol; + + // Find the end of our function by finding the start + // of the next symbol after our target symbol. + + const int insnIncrement = +#if __arm64__ + 4; +#elif __arm__ + 2; // in case of thumb or thumb-2 +#elif __i386__ || __x86_64__ + 1; +#else +#error unknown architecture +#endif + + uintptr_t symbolEnd; + Dl_info dli; + int ok; + for (symbolEnd = symbol + insnIncrement; + ((ok = dladdr((void*)symbolEnd, &dli))) && dli.dli_saddr == (void*)symbol; + symbolEnd += insnIncrement) + ; + + testprintf("found %s at %p..<%p %d %p %s\n", + symname, (void*)symbol, (void*)symbolEnd, ok, dli.dli_saddr, dli.dli_sname); + + // Record the offset to each non-NOP instruction. + uintptr_t *result = (uintptr_t *)malloc(1000 * sizeof(uintptr_t)); + uintptr_t *end = result + 1000; + uintptr_t *p = result; + + p = disassemble(symbol, symbolEnd, p, end); + + // Also record the offsets in _objc_msgSend_uncached when present + // (which is the slow path and has a frame to unwind) + if (!strstr(symname, "_uncached")) { + const char *uncached_symname = strstr(symname, "stret") + ? "_objc_msgSend_stret_uncached" : "_objc_msgSend_uncached"; + uintptr_t uncached_symbol; + uintptr_t *uncached_offsets = + getOffsets(uncached_symname, &uncached_symbol); + if (uncached_offsets) { + uintptr_t *q = uncached_offsets; + // Skip prologue and epilogue of objc_msgSend_uncached + // because it's imprecisely modeled in compact unwind + int prologueInstructions, epilogueInstructions; +#if __arm64e__ + prologueInstructions = 3; + epilogueInstructions = 2; +#elif __arm64__ || __x86_64__ || __i386__ || __arm__ + prologueInstructions = 2; + epilogueInstructions = 1; +#else +#error unknown architecture +#endif + // skip past prologue + for (int i = 0; i < prologueInstructions; i++) { + testassert(*q != END_OFFSETS); + q++; + } + + // copy instructions + while (*q != END_OFFSETS) *p++ = *q++ + uncached_symbol - symbol; + + // rewind past epilogue + for (int i = 0; i < epilogueInstructions; i++) { + testassert(p > result); + p--; + } + + free(uncached_offsets); + } + } + + // Terminate the list of offsets and return. + testassert(p > result); + testassert(p < end); + *p = END_OFFSETS; + + return result; +} + + +void CALLIT(void *o, void *sel_arg, SEL s, void *f, bool stret) __attribute__((noinline)); +void CALLIT(void *o, void *sel_arg, SEL s, void *f, bool stret) +{ + uintptr_t message_ref[2]; + if (sel_arg != s) { + // fixup dispatch + // copy to a local buffer to keep sel_arg un-fixed-up + memcpy(message_ref, sel_arg, sizeof(message_ref)); + sel_arg = message_ref; + } + if (!stret) callit(o, sel_arg, f); +#if SUPPORT_STRET + else callit_stret(o, sel_arg, f); +#else + else fail("stret?"); +#endif +} + +void test_dw_forward(void) +{ + return; +} + +struct stret test_dw_forward_stret(void) +{ + return zero; +} + +// sub = ordinary receiver object +// tagged = tagged receiver object +// SEL = selector to send +// sub_arg = arg to pass in receiver register (may be objc_super struct) +// tagged_arg = arg to pass in receiver register (may be objc_super struct) +// sel_arg = arg to pass in sel register (may be message_ref) +// uncaughtAllowed is the number of acceptable unreachable instructions +// (for example, the ones that handle the corrupt-cache-error case) +void test_dw(const char *name, id sub, id tagged, id exttagged, bool stret, + int uncaughtAllowed) +{ + + testprintf("DWARF FOR %s%s\n", name, stret ? " (stret)" : ""); + + // We need 2 SELs of each alignment so we can generate hash collisions. + // sel_registerName() never returns those alignments because they + // differ from malloc's alignment. So we create lots of compiled-in + // SELs here and hope something fits. +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wundeclared-selector" + SEL sel = @selector(a); + SEL lotsOfSels[] = { + @selector(a1), @selector(a2), @selector(a3), @selector(a4), + @selector(a5), @selector(a6), @selector(a7), @selector(a8), + @selector(aa), @selector(ab), @selector(ac), @selector(ad), + @selector(ae), @selector(af), @selector(ag), @selector(ah), + @selector(A1), @selector(A2), @selector(A3), @selector(A4), + @selector(A5), @selector(A6), @selector(A7), @selector(A8), + @selector(AA), @selector(Ab), @selector(Ac), @selector(Ad), + @selector(Ae), @selector(Af), @selector(Ag), @selector(Ah), + @selector(bb1), @selector(bb2), @selector(bb3), @selector(bb4), + @selector(bb5), @selector(bb6), @selector(bb7), @selector(bb8), + @selector(bba), @selector(bbb), @selector(bbc), @selector(bbd), + @selector(bbe), @selector(bbf), @selector(bbg), @selector(bbh), + @selector(BB1), @selector(BB2), @selector(BB3), @selector(BB4), + @selector(BB5), @selector(BB6), @selector(BB7), @selector(BB8), + @selector(BBa), @selector(BBb), @selector(BBc), @selector(BBd), + @selector(BBe), @selector(BBf), @selector(BBg), @selector(BBh), + @selector(ccc1), @selector(ccc2), @selector(ccc3), @selector(ccc4), + @selector(ccc5), @selector(ccc6), @selector(ccc7), @selector(ccc8), + @selector(ccca), @selector(cccb), @selector(cccc), @selector(cccd), + @selector(ccce), @selector(cccf), @selector(cccg), @selector(ccch), + @selector(CCC1), @selector(CCC2), @selector(CCC3), @selector(CCC4), + @selector(CCC5), @selector(CCC6), @selector(CCC7), @selector(CCC8), + @selector(CCCa), @selector(CCCb), @selector(CCCc), @selector(CCCd), + @selector(CCCe), @selector(CCCf), @selector(CCCg), @selector(CCCh), + }; +#pragma clang diagnostic pop + + { + IMP imp = stret ? (IMP)test_dw_forward_stret : (IMP)test_dw_forward; + Class cls = object_getClass(sub); + Class tagcls = object_getClass(tagged); + Class exttagcls = object_getClass(exttagged); + class_replaceMethod(cls, sel, imp, ""); + class_replaceMethod(tagcls, sel, imp, ""); + class_replaceMethod(exttagcls, sel, imp, ""); + for (size_t i = 0; i < sizeof(lotsOfSels)/sizeof(lotsOfSels[0]); i++) { + class_replaceMethod(cls, lotsOfSels[i], imp, ""); + class_replaceMethod(tagcls, lotsOfSels[i], imp, ""); + class_replaceMethod(exttagcls, lotsOfSels[i], imp, ""); + } + } + + #define ALIGNCOUNT 16 + SEL sels[ALIGNCOUNT][2] = {{0}}; + for (int align = 0; align < ALIGNCOUNT; align++) { + for (size_t i = 0; i < sizeof(lotsOfSels)/sizeof(lotsOfSels[0]); i++) { + if ((uintptr_t)(void*)lotsOfSels[i] % ALIGNCOUNT == align) { + if (sels[align][0]) { + sels[align][1] = lotsOfSels[i]; + } else { + sels[align][0] = lotsOfSels[i]; + } + } + } + if (!sels[align][0]) fail("no SEL with alignment %d", align); + if (!sels[align][1]) fail("only one SEL with alignment %d", align); + } + + void *fn = dlsym(RTLD_DEFAULT, name); +#if __has_feature(ptrauth_calls) + fn = ptrauth_strip(fn, ptrauth_key_function_pointer); +#endif + testassert(fn); + + // argument substitutions + + void *sub_arg = (__bridge void*)sub; + void *tagged_arg = (__bridge void*)tagged; + void *exttagged_arg = (__bridge void*)exttagged; + void *sel_arg = (void*)sel; + + struct objc_super sup_st = { sub, object_getClass(sub) }; + struct objc_super tagged_sup_st = { tagged, object_getClass(tagged) }; + struct objc_super exttagged_sup_st = { exttagged, object_getClass(exttagged) }; + struct { void *imp; SEL sel; } message_ref = { fn, sel }; + + Class cache_cls = object_getClass(sub); + Class tagged_cache_cls = object_getClass(tagged); + Class exttagged_cache_cls = object_getClass(exttagged); + + if (strstr(name, "Super")) { + // super version - replace receiver with objc_super + // clear caches of superclass + cache_cls = class_getSuperclass(cache_cls); + tagged_cache_cls = class_getSuperclass(tagged_cache_cls); + exttagged_cache_cls = class_getSuperclass(exttagged_cache_cls); + sub_arg = &sup_st; + tagged_arg = &tagged_sup_st; + exttagged_arg = &exttagged_sup_st; + } + + if (strstr(name, "_fixup")) { + // fixup version - replace sel with message_ref + sel_arg = &message_ref; + } + + + uintptr_t *insnOffsets = getOffsets(name, nil); + testassert(insnOffsets); + uintptr_t offset; + int uncaughtCount = 0; + for (int oo = 0; insnOffsets[oo] != ~0UL; oo++) { + offset = insnOffsets[oo]; + testprintf("OFFSET %lu\n", offset); + + clobbered_insn_t saved_insn = clobber(fn, offset); + caught = false; + + // nil + if ((__bridge void*)sub == sub_arg) { + SELF = nil; + testprintf(" nil\n"); + CALLIT(nil, sel_arg, sel, fn, stret); + CALLIT(nil, sel_arg, sel, fn, stret); + } + + // uncached + SELF = sub; + testprintf(" uncached\n"); + _objc_flush_caches(cache_cls); + CALLIT(sub_arg, sel_arg, sel, fn, stret); + _objc_flush_caches(cache_cls); + CALLIT(sub_arg, sel_arg, sel, fn, stret); + + // cached + SELF = sub; + testprintf(" cached\n"); + CALLIT(sub_arg, sel_arg, sel, fn, stret); + CALLIT(sub_arg, sel_arg, sel, fn, stret); + + // uncached,tagged + SELF = tagged; + testprintf(" uncached,tagged\n"); + _objc_flush_caches(tagged_cache_cls); + CALLIT(tagged_arg, sel_arg, sel, fn, stret); + _objc_flush_caches(tagged_cache_cls); + CALLIT(tagged_arg, sel_arg, sel, fn, stret); + _objc_flush_caches(exttagged_cache_cls); + CALLIT(exttagged_arg, sel_arg, sel, fn, stret); + _objc_flush_caches(exttagged_cache_cls); + CALLIT(exttagged_arg, sel_arg, sel, fn, stret); + + // cached,tagged + SELF = tagged; + testprintf(" cached,tagged\n"); + CALLIT(tagged_arg, sel_arg, sel, fn, stret); + CALLIT(tagged_arg, sel_arg, sel, fn, stret); + CALLIT(exttagged_arg, sel_arg, sel, fn, stret); + CALLIT(exttagged_arg, sel_arg, sel, fn, stret); + + // multiple SEL alignments, collisions, wraps + SELF = sub; + for (int a = 0; a < ALIGNCOUNT; a++) { + testprintf(" cached and uncached, SEL alignment %d\n", a); + + // Count both up and down to be independent of + // implementation's cache scan direction + + _objc_flush_caches(cache_cls); + for (int x2 = 0; x2 < 8; x2++) { + for (int s = 0; s < 4; s++) { + int align = (a+s) % ALIGNCOUNT; + CALLIT(sub_arg, sels[align][0], sels[align][0], fn, stret); + CALLIT(sub_arg, sels[align][1], sels[align][1], fn, stret); + } + } + + _objc_flush_caches(cache_cls); + for (int x2 = 0; x2 < 8; x2++) { + for (int s = 0; s < 4; s++) { + int align = abs(a-s) % ALIGNCOUNT; + CALLIT(sub_arg, sels[align][0], sels[align][0], fn, stret); + CALLIT(sub_arg, sels[align][1], sels[align][1], fn, stret); + } + } + } + + unclobber(fn, offset, saved_insn); + + // remember offsets that were caught by none of the above + if (caught) { + insnOffsets[oo] = 0; + } else { + uncaughtCount++; + testprintf("offset %s+%lu not caught (%d/%d)\n", + name, offset, uncaughtCount, uncaughtAllowed); + } + } + + // Complain if too many offsets went uncaught. + // Acceptably-uncaught offsets include the corrupt-cache-error handler. + if (uncaughtCount != uncaughtAllowed) { + for (int oo = 0; insnOffsets[oo] != ~0UL; oo++) { + if (insnOffsets[oo]) { + fprintf(stderr, "BAD: offset %s+%lu not caught\n", + name, insnOffsets[oo]); + } + } + fail("wrong instructions not reached for %s (missed %d, expected %d)", + name, uncaughtCount, uncaughtAllowed); + } + + free(insnOffsets); +} + + +// TEST_DWARF +#endif + + +void test_basic(id receiver) +{ + id idval; + long long llval; + struct stret stretval; + double fpval; + long double lfpval; + vector_ulong2 vecval; + + // message uncached + // message uncached long long + // message uncached stret + // message uncached fpret + // message uncached fpret long double + // message uncached noarg (as above) + // message cached + // message cached long long + // message cached stret + // message cached fpret + // message cached fpret long double + // message cached noarg (as above) + // fixme verify that uncached lookup didn't happen the 2nd time? + SELF = receiver; + _objc_flush_caches(object_getClass(receiver)); + for (int i = 0; i < 5; i++) { + testprintf("idret\n"); + state = 0; + idval = nil; + idval = [receiver idret :VEC1:VEC2:VEC3:VEC4:VEC5:VEC6:VEC7:VEC8:1:2:3:4:5:6:7:8:9:10:11:12:13:1.0:2.0:3.0:4.0:5.0:6.0:7.0:8.0:9.0:10.0:11.0:12.0:13.0:14.0:15.0]; + testassert(state == 101); + testassert(idval == ID_RESULT); + + testprintf("llret\n"); + llval = 0; + llval = [receiver llret :VEC1:VEC2:VEC3:VEC4:VEC5:VEC6:VEC7:VEC8:1:2:3:4:5:6:7:8:9:10:11:12:13:1.0:2.0:3.0:4.0:5.0:6.0:7.0:8.0:9.0:10.0:11.0:12.0:13.0:14.0:15.0]; + testassert(state == 102); + testassert(llval == LL_RESULT); + + testprintf("stret\n"); + stretval = zero; + stretval = [receiver stret :VEC1:VEC2:VEC3:VEC4:VEC5:VEC6:VEC7:VEC8:1:2:3:4:5:6:7:8:9:10:11:12:13:1.0:2.0:3.0:4.0:5.0:6.0:7.0:8.0:9.0:10.0:11.0:12.0:13.0:14.0:15.0]; + testassert(state == 103); + testassert(stret_equal(stretval, STRET_RESULT)); + + testprintf("fpret\n"); + fpval = 0; + fpval = [receiver fpret :VEC1:VEC2:VEC3:VEC4:VEC5:VEC6:VEC7:VEC8:1:2:3:4:5:6:7:8:9:10:11:12:13:1.0:2.0:3.0:4.0:5.0:6.0:7.0:8.0:9.0:10.0:11.0:12.0:13.0:14.0:15.0]; + testassert(state == 104); + testassert(fpval == FP_RESULT); + + testprintf("lfpret\n"); + lfpval = 0; + lfpval = [receiver lfpret :VEC1:VEC2:VEC3:VEC4:VEC5:VEC6:VEC7:VEC8:1:2:3:4:5:6:7:8:9:10:11:12:13:1.0:2.0:3.0:4.0:5.0:6.0:7.0:8.0:9.0:10.0:11.0:12.0:13.0:14.0:15.0]; + testassert(state == 105); + testassert(lfpval == LFP_RESULT); + + testprintf("vecret\n"); + vecval = 0; + vecval = [receiver vecret :VEC1:VEC2:VEC3:VEC4:VEC5:VEC6:VEC7:VEC8:1:2:3:4:5:6:7:8:9:10:11:12:13:1.0:2.0:3.0:4.0:5.0:6.0:7.0:8.0:9.0:10.0:11.0:12.0:13.0:14.0:15.0]; + testassert(state == 106); + testassert(vector_equal(vecval, VEC_RESULT)); + + // explicitly call noarg messenger, even if compiler doesn't emit it + state = 0; + testprintf("idret noarg\n"); + idval = nil; + idval = ((typeof(idmsg0))objc_msgSend_noarg)(receiver, @selector(idret_noarg)); + testassert(state == 111); + testassert(idval == ID_RESULT); + + testprintf("llret noarg\n"); + llval = 0; + llval = ((typeof(llmsg0))objc_msgSend_noarg)(receiver, @selector(llret_noarg)); + testassert(state == 112); + testassert(llval == LL_RESULT); + /* + no objc_msgSend_stret_noarg + stretval = zero; + stretval = ((typeof(stretmsg0))objc_msgSend_stret_noarg)(receiver, @selector(stret_noarg)); + stretval = [receiver stret :VEC1:VEC2:VEC3:VEC4:VEC5:VEC6:VEC7:VEC8:1:2:3:4:5:6:7:8:9:10:11:12:13:1.0:2.0:3.0:4.0:5.0:6.0:7.0:8.0:9.0:10.0:11.0:12.0:13.0:14.0:15.0]; + testassert(state == 113); + testassert(stret_equal(stretval, STRET_RESULT)); + */ +#if !__i386__ + testprintf("fpret noarg\n"); + fpval = 0; + fpval = ((typeof(fpmsg0))objc_msgSend_noarg)(receiver, @selector(fpret_noarg)); + testassert(state == 114); + testassert(fpval == FP_RESULT); + + testprintf("vecret noarg\n"); + vecval = 0; + vecval = ((typeof(vecmsg0))objc_msgSend_noarg)(receiver, @selector(vecret_noarg)); + testassert(state == 116); + testassert(vector_equal(vecval, VEC_RESULT)); +#endif +#if !__i386__ && !__x86_64__ + testprintf("lfpret noarg\n"); + lfpval = 0; + lfpval = ((typeof(lfpmsg0))objc_msgSend_noarg)(receiver, @selector(lfpret_noarg)); + testassert(state == 115); + testassert(lfpval == LFP_RESULT); +#endif + } + + testprintf("basic done\n"); +} + +int main() +{ + PUSH_POOL { + id idval; + long long llval; + struct stret stretval; + double fpval; + long double lfpval; + vector_ulong2 vecval; + +#if __x86_64__ + struct stret *stretptr; +#endif + + Method idmethod; + Method llmethod; + Method stretmethod; + Method fpmethod; + Method lfpmethod; + Method vecmethod; + + id (*idfn)(id, Method, vector_ulong2, vector_ulong2, vector_ulong2, vector_ulong2, vector_ulong2, vector_ulong2, vector_ulong2, vector_ulong2, int, int, int, int, int, int, int, int, int, int, int, int, int, double, double, double, double, double, double, double, double, double, double, double, double, double, double, double); + long long (*llfn)(id, Method, vector_ulong2, vector_ulong2, vector_ulong2, vector_ulong2, vector_ulong2, vector_ulong2, vector_ulong2, vector_ulong2, int, int, int, int, int, int, int, int, int, int, int, int, int, double, double, double, double, double, double, double, double, double, double, double, double, double, double, double); + struct stret (*stretfn)(id, Method, vector_ulong2, vector_ulong2, vector_ulong2, vector_ulong2, vector_ulong2, vector_ulong2, vector_ulong2, vector_ulong2, int, int, int, int, int, int, int, int, int, int, int, int, int, double, double, double, double, double, double, double, double, double, double, double, double, double, double, double); + double (*fpfn)(id, Method, vector_ulong2, vector_ulong2, vector_ulong2, vector_ulong2, vector_ulong2, vector_ulong2, vector_ulong2, vector_ulong2, int, int, int, int, int, int, int, int, int, int, int, int, int, double, double, double, double, double, double, double, double, double, double, double, double, double, double, double); + long double (*lfpfn)(id, Method, vector_ulong2, vector_ulong2, vector_ulong2, vector_ulong2, vector_ulong2, vector_ulong2, vector_ulong2, vector_ulong2, int, int, int, int, int, int, int, int, int, int, int, int, int, double, double, double, double, double, double, double, double, double, double, double, double, double, double, double); + vector_ulong2 (*vecfn)(id, Method, vector_ulong2, vector_ulong2, vector_ulong2, vector_ulong2, vector_ulong2, vector_ulong2, vector_ulong2, vector_ulong2, int, int, int, int, int, int, int, int, int, int, int, int, int, double, double, double, double, double, double, double, double, double, double, double, double, double, double, double); + + id (*idmsg)(id, SEL, vector_ulong2, vector_ulong2, vector_ulong2, vector_ulong2, vector_ulong2, vector_ulong2, vector_ulong2, vector_ulong2, int, int, int, int, int, int, int, int, int, int, int, int, int, double, double, double, double, double, double, double, double, double, double, double, double, double, double, double) __attribute__((unused)); + id (*idmsgsuper)(struct objc_super *, SEL, vector_ulong2, vector_ulong2, vector_ulong2, vector_ulong2, vector_ulong2, vector_ulong2, vector_ulong2, vector_ulong2, int, int, int, int, int, int, int, int, int, int, int, int, int, double, double, double, double, double, double, double, double, double, double, double, double, double, double, double) __attribute__((unused)); + long long (*llmsg)(id, SEL, vector_ulong2, vector_ulong2, vector_ulong2, vector_ulong2, vector_ulong2, vector_ulong2, vector_ulong2, vector_ulong2, int, int, int, int, int, int, int, int, int, int, int, int, int, double, double, double, double, double, double, double, double, double, double, double, double, double, double, double) __attribute__((unused)); + struct stret (*stretmsg)(id, SEL, vector_ulong2, vector_ulong2, vector_ulong2, vector_ulong2, vector_ulong2, vector_ulong2, vector_ulong2, vector_ulong2, int, int, int, int, int, int, int, int, int, int, int, int, int, double, double, double, double, double, double, double, double, double, double, double, double, double, double, double) __attribute__((unused)); + struct stret (*stretmsgsuper)(struct objc_super *, SEL, vector_ulong2, vector_ulong2, vector_ulong2, vector_ulong2, vector_ulong2, vector_ulong2, vector_ulong2, vector_ulong2, int, int, int, int, int, int, int, int, int, int, int, int, int, double, double, double, double, double, double, double, double, double, double, double, double, double, double, double) __attribute__((unused)); + double (*fpmsg)(id, SEL, vector_ulong2, vector_ulong2, vector_ulong2, vector_ulong2, vector_ulong2, vector_ulong2, vector_ulong2, vector_ulong2, int, int, int, int, int, int, int, int, int, int, int, int, int, double, double, double, double, double, double, double, double, double, double, double, double, double, double, double) __attribute__((unused)); + long double (*lfpmsg)(id, SEL, vector_ulong2, vector_ulong2, vector_ulong2, vector_ulong2, vector_ulong2, vector_ulong2, vector_ulong2, vector_ulong2, int, int, int, int, int, int, int, int, int, int, int, int, int, double, double, double, double, double, double, double, double, double, double, double, double, double, double, double) __attribute__((unused)); + vector_ulong2 (*vecmsg)(id, SEL, vector_ulong2, vector_ulong2, vector_ulong2, vector_ulong2, vector_ulong2, vector_ulong2, vector_ulong2, vector_ulong2, int, int, int, int, int, int, int, int, int, int, int, int, int, double, double, double, double, double, double, double, double, double, double, double, double, double, double, double) __attribute__((unused)); + + // get +initialize out of the way + [Sub class]; +#if OBJC_HAVE_TAGGED_POINTERS + [TaggedSub class]; + [ExtTaggedSub class]; +#endif + + ID_RESULT = [Super new]; + + Sub *sub = [Sub new]; + Super *sup = [Super new]; +#if OBJC_HAVE_TAGGED_POINTERS + TaggedSub *tagged = (__bridge id)_objc_makeTaggedPointer(OBJC_TAG_1, 999); + ExtTaggedSub *exttagged = (__bridge id)_objc_makeTaggedPointer(OBJC_TAG_First52BitPayload, 999); +#endif + + // Basic cached and uncached dispatch. + // Do this first before anything below caches stuff. + testprintf("basic\n"); + test_basic(sub); +#if OBJC_HAVE_TAGGED_POINTERS + testprintf("basic tagged\n"); + test_basic(tagged); + testprintf("basic ext tagged\n"); + test_basic(exttagged); +#endif + + idmethod = class_getInstanceMethod([Super class], @selector(idret::::::::::::::::::::::::::::::::::::)); + testassert(idmethod); + llmethod = class_getInstanceMethod([Super class], @selector(llret::::::::::::::::::::::::::::::::::::)); + testassert(llmethod); + stretmethod = class_getInstanceMethod([Super class], @selector(stret::::::::::::::::::::::::::::::::::::)); + testassert(stretmethod); + fpmethod = class_getInstanceMethod([Super class], @selector(fpret::::::::::::::::::::::::::::::::::::)); + testassert(fpmethod); + lfpmethod = class_getInstanceMethod([Super class], @selector(lfpret::::::::::::::::::::::::::::::::::::)); + testassert(lfpmethod); + vecmethod = class_getInstanceMethod([Super class], @selector(vecret::::::::::::::::::::::::::::::::::::)); + testassert(vecmethod); + + idfn = (id (*)(id, Method, vector_ulong2, vector_ulong2, vector_ulong2, vector_ulong2, vector_ulong2, vector_ulong2, vector_ulong2, vector_ulong2, int, int, int, int, int, int, int, int, int, int, int, int, int, double, double, double, double, double, double, double, double, double, double, double, double, double, double, double)) method_invoke; + llfn = (long long (*)(id, Method, vector_ulong2, vector_ulong2, vector_ulong2, vector_ulong2, vector_ulong2, vector_ulong2, vector_ulong2, vector_ulong2, int, int, int, int, int, int, int, int, int, int, int, int, int, double, double, double, double, double, double, double, double, double, double, double, double, double, double, double)) method_invoke; + stretfn = (struct stret (*)(id, Method, vector_ulong2, vector_ulong2, vector_ulong2, vector_ulong2, vector_ulong2, vector_ulong2, vector_ulong2, vector_ulong2, int, int, int, int, int, int, int, int, int, int, int, int, int, double, double, double, double, double, double, double, double, double, double, double, double, double, double, double)) method_invoke_stret; + fpfn = (double (*)(id, Method, vector_ulong2, vector_ulong2, vector_ulong2, vector_ulong2, vector_ulong2, vector_ulong2, vector_ulong2, vector_ulong2, int, int, int, int, int, int, int, int, int, int, int, int, int, double, double, double, double, double, double, double, double, double, double, double, double, double, double, double)) method_invoke; + lfpfn = (long double (*)(id, Method, vector_ulong2, vector_ulong2, vector_ulong2, vector_ulong2, vector_ulong2, vector_ulong2, vector_ulong2, vector_ulong2, int, int, int, int, int, int, int, int, int, int, int, int, int, double, double, double, double, double, double, double, double, double, double, double, double, double, double, double)) method_invoke; + vecfn = (vector_ulong2 (*)(id, Method, vector_ulong2, vector_ulong2, vector_ulong2, vector_ulong2, vector_ulong2, vector_ulong2, vector_ulong2, vector_ulong2, int, int, int, int, int, int, int, int, int, int, int, int, int, double, double, double, double, double, double, double, double, double, double, double, double, double, double, double)) method_invoke; + + // method_invoke + // method_invoke long long + // method_invoke_stret stret + // method_invoke_stret fpret + // method_invoke fpret long double + testprintf("method_invoke\n"); + + SELF = sup; + + state = 0; + idval = nil; + idval = (*idfn)(sup, idmethod, VEC1, VEC2, VEC3, VEC4, VEC5, VEC6, VEC7, VEC8, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0); + testassert(state == 1); + testassert(idval == ID_RESULT); + + llval = 0; + llval = (*llfn)(sup, llmethod, VEC1, VEC2, VEC3, VEC4, VEC5, VEC6, VEC7, VEC8, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0); + testassert(state == 2); + testassert(llval == LL_RESULT); + + stretval = zero; + stretval = (*stretfn)(sup, stretmethod, VEC1, VEC2, VEC3, VEC4, VEC5, VEC6, VEC7, VEC8, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0); + testassert(state == 3); + testassert(stret_equal(stretval, STRET_RESULT)); + + fpval = 0; + fpval = (*fpfn)(sup, fpmethod, VEC1, VEC2, VEC3, VEC4, VEC5, VEC6, VEC7, VEC8, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0); + testassert(state == 4); + testassert(fpval == FP_RESULT); + + lfpval = 0; + lfpval = (*lfpfn)(sup, lfpmethod, VEC1, VEC2, VEC3, VEC4, VEC5, VEC6, VEC7, VEC8, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0); + testassert(state == 5); + testassert(lfpval == LFP_RESULT); + + vecval = 0; + vecval = (*vecfn)(sup, vecmethod, VEC1, VEC2, VEC3, VEC4, VEC5, VEC6, VEC7, VEC8, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0); + testassert(state == 6); + testassert(vector_equal(vecval, VEC_RESULT)); + + + // message to nil + // message to nil long long + // message to nil stret + // message to nil fpret + // message to nil fpret long double + // Use NIL_RECEIVER to avoid compiler optimizations. + testprintf("message to nil\n"); + + state = 0; + idval = ID_RESULT; + idval = [(id)NIL_RECEIVER idret :VEC1:VEC2:VEC3:VEC4:VEC5:VEC6:VEC7:VEC8:1:2:3:4:5:6:7:8:9:10:11:12:13:1.0:2.0:3.0:4.0:5.0:6.0:7.0:8.0:9.0:10.0:11.0:12.0:13.0:14.0:15.0]; + testassert(state == 0); + testassert(idval == nil); + + state = 0; + llval = LL_RESULT; + llval = [(id)NIL_RECEIVER llret :VEC1:VEC2:VEC3:VEC4:VEC5:VEC6:VEC7:VEC8:1:2:3:4:5:6:7:8:9:10:11:12:13:1.0:2.0:3.0:4.0:5.0:6.0:7.0:8.0:9.0:10.0:11.0:12.0:13.0:14.0:15.0]; + testassert(state == 0); + testassert(llval == 0LL); + + state = 0; + stretval = zero; + stretval = [(id)NIL_RECEIVER stret :VEC1:VEC2:VEC3:VEC4:VEC5:VEC6:VEC7:VEC8:1:2:3:4:5:6:7:8:9:10:11:12:13:1.0:2.0:3.0:4.0:5.0:6.0:7.0:8.0:9.0:10.0:11.0:12.0:13.0:14.0:15.0]; + testassert(state == 0); + testassert(0 == memcmp(&stretval, &zero, sizeof(stretval))); + +#if __x86_64__ + // check stret return register + state = 0; + stretval = zero; + stretptr = ((struct stret *(*)(struct stret *, id, SEL))objc_msgSend_stret) + (&stretval, nil, @selector(stret_nop)); + testassert(stretptr == &stretval); + testassert(state == 0); + // no stret result guarantee for hand-written calls +#endif + +#if __i386__ + // check struct-return address stack pop + for (int i = 0; i < 10000000; i++) { + state = 0; + ((struct stret (*)(id, SEL))objc_msgSend_stret) + (nil, @selector(stret_nop)); + } +#endif + + state = 0; + fpval = FP_RESULT; + fpval = [(id)NIL_RECEIVER fpret :VEC1:VEC2:VEC3:VEC4:VEC5:VEC6:VEC7:VEC8:1:2:3:4:5:6:7:8:9:10:11:12:13:1.0:2.0:3.0:4.0:5.0:6.0:7.0:8.0:9.0:10.0:11.0:12.0:13.0:14.0:15.0]; + testassert(state == 0); + testassert(fpval == 0.0); + + state = 0; + lfpval = LFP_RESULT; + lfpval = [(id)NIL_RECEIVER lfpret :VEC1:VEC2:VEC3:VEC4:VEC5:VEC6:VEC7:VEC8:1:2:3:4:5:6:7:8:9:10:11:12:13:1.0:2.0:3.0:4.0:5.0:6.0:7.0:8.0:9.0:10.0:11.0:12.0:13.0:14.0:15.0]; + testassert(state == 0); + testassert(lfpval == 0.0); + + state = 0; + vecval = VEC_RESULT; + vecval = [(id)NIL_RECEIVER vecret :VEC1:VEC2:VEC3:VEC4:VEC5:VEC6:VEC7:VEC8:1:2:3:4:5:6:7:8:9:10:11:12:13:1.0:2.0:3.0:4.0:5.0:6.0:7.0:8.0:9.0:10.0:11.0:12.0:13.0:14.0:15.0]; + testassert(state == 0); + testassert(vector_all(vecval == 0)); + + // message to nil, different struct types + // This verifies that ordinary objc_msgSend() erases enough registers + // for structs that return in registers. +#define TEST_NIL_STRUCT(i,n) \ + do { \ + struct stret_##i##n z; \ + bzero(&z, sizeof(z)); \ + [Super stret_i##n##_nonzero]; \ + [Super stret_d##n##_nonzero]; \ + struct stret_##i##n val = [(id)NIL_RECEIVER stret_##i##n##_zero]; \ + testassert(0 == memcmp(&z, &val, sizeof(val))); \ + } while (0) + + TEST_NIL_STRUCT(i,1); + TEST_NIL_STRUCT(i,2); + TEST_NIL_STRUCT(i,3); + TEST_NIL_STRUCT(i,4); + TEST_NIL_STRUCT(i,5); + TEST_NIL_STRUCT(i,6); + TEST_NIL_STRUCT(i,7); + TEST_NIL_STRUCT(i,8); + TEST_NIL_STRUCT(i,9); + +#if __i386__ + testwarn("rdar://16267205 i386 struct{float} and struct{double}"); +#else + TEST_NIL_STRUCT(d,1); +#endif + TEST_NIL_STRUCT(d,2); + TEST_NIL_STRUCT(d,3); + TEST_NIL_STRUCT(d,4); + TEST_NIL_STRUCT(d,5); + TEST_NIL_STRUCT(d,6); + TEST_NIL_STRUCT(d,7); + TEST_NIL_STRUCT(d,8); + TEST_NIL_STRUCT(d,9); + + + // message to nil noarg + // explicitly call noarg messenger, even if compiler doesn't emit it + state = 0; + idval = ID_RESULT; + idval = ((typeof(idmsg0))objc_msgSend_noarg)(nil, @selector(idret_noarg)); + testassert(state == 0); + testassert(idval == nil); + + state = 0; + llval = LL_RESULT; + llval = ((typeof(llmsg0))objc_msgSend_noarg)(nil, @selector(llret_noarg)); + testassert(state == 0); + testassert(llval == 0LL); + + // no stret_noarg messenger + +#if !__i386__ + state = 0; + fpval = FP_RESULT; + fpval = ((typeof(fpmsg0))objc_msgSend_noarg)(nil, @selector(fpret_noarg)); + testassert(state == 0); + testassert(fpval == 0.0); + + state = 0; + vecval = VEC_RESULT; + vecval = ((typeof(vecmsg0))objc_msgSend_noarg)(nil, @selector(vecret_noarg)); + testassert(state == 0); + testassert(vector_all(vecval == 0)); +#endif +#if !__i386__ && !__x86_64__ + state = 0; + lfpval = LFP_RESULT; + lfpval = ((typeof(lfpmsg0))objc_msgSend_noarg)(nil, @selector(lfpret_noarg)); + testassert(state == 0); + testassert(lfpval == 0.0); +#endif + + + // rdar://8271364 objc_msgSendSuper2 must not change objc_super + testprintf("super struct\n"); + struct objc_super sup_st = { + sub, + object_getClass(sub), + }; + + SELF = sub; + + state = 100; + idval = nil; + idval = ((id(*)(struct objc_super *, SEL, vector_ulong2,vector_ulong2,vector_ulong2,vector_ulong2,vector_ulong2,vector_ulong2,vector_ulong2,vector_ulong2, int,int,int,int,int,int,int,int,int,int,int,int,int, double,double,double,double,double,double,double,double,double,double,double,double,double,double,double))objc_msgSendSuper2) (&sup_st, @selector(idret::::::::::::::::::::::::::::::::::::), VEC1,VEC2,VEC3,VEC4,VEC5,VEC6,VEC7,VEC8, 1,2,3,4,5,6,7,8,9,10,11,12,13, 1.0,2.0,3.0,4.0,5.0,6.0,7.0,8.0,9.0,10.0,11.0,12.0,13.0,14.0,15.0); + testassert(state == 1); + testassert(idval == ID_RESULT); + testassert(sup_st.receiver == sub); + testassert(sup_st.super_class == object_getClass(sub)); + + state = 100; + stretval = zero; + stretval = ((struct stret(*)(struct objc_super *, SEL, vector_ulong2,vector_ulong2,vector_ulong2,vector_ulong2,vector_ulong2,vector_ulong2,vector_ulong2,vector_ulong2, int,int,int,int,int,int,int,int,int,int,int,int,int, double,double,double,double,double,double,double,double,double,double,double,double,double,double,double))objc_msgSendSuper2_stret) (&sup_st, @selector(stret::::::::::::::::::::::::::::::::::::), VEC1,VEC2,VEC3,VEC4,VEC5,VEC6,VEC7,VEC8, 1,2,3,4,5,6,7,8,9,10,11,12,13, 1.0,2.0,3.0,4.0,5.0,6.0,7.0,8.0,9.0,10.0,11.0,12.0,13.0,14.0,15.0); + testassert(state == 3); + testassert(stret_equal(stretval, STRET_RESULT)); + testassert(sup_st.receiver == sub); + testassert(sup_st.super_class == object_getClass(sub)); + +#if !__arm64__ + // Debug messengers. + testprintf("debug messengers\n"); + + state = 0; + idmsg = (typeof(idmsg))objc_msgSend_debug; + idval = nil; + idval = (*idmsg)(sub, @selector(idret::::::::::::::::::::::::::::::::::::), VEC1, VEC2, VEC3, VEC4, VEC5, VEC6, VEC7, VEC8, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0); + testassert(state == 101); + testassert(idval == ID_RESULT); + + state = 0; + llmsg = (typeof(llmsg))objc_msgSend_debug; + llval = 0; + llval = (*llmsg)(sub, @selector(llret::::::::::::::::::::::::::::::::::::), VEC1, VEC2, VEC3, VEC4, VEC5, VEC6, VEC7, VEC8, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0); + testassert(state == 102); + testassert(llval == LL_RESULT); + + state = 0; + stretmsg = (typeof(stretmsg))objc_msgSend_stret_debug; + stretval = zero; + stretval = (*stretmsg)(sub, @selector(stret::::::::::::::::::::::::::::::::::::), VEC1, VEC2, VEC3, VEC4, VEC5, VEC6, VEC7, VEC8, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0); + testassert(state == 103); + testassert(stret_equal(stretval, STRET_RESULT)); + + state = 100; + sup_st.receiver = sub; + sup_st.super_class = object_getClass(sub); + idmsgsuper = (typeof(idmsgsuper))objc_msgSendSuper2_debug; + idval = nil; + idval = (*idmsgsuper)(&sup_st, @selector(idret::::::::::::::::::::::::::::::::::::), VEC1, VEC2, VEC3, VEC4, VEC5, VEC6, VEC7, VEC8, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0); + testassert(state == 1); + testassert(idval == ID_RESULT); + + state = 100; + sup_st.receiver = sub; + sup_st.super_class = object_getClass(sub); + stretmsgsuper = (typeof(stretmsgsuper))objc_msgSendSuper2_stret_debug; + stretval = zero; + stretval = (*stretmsgsuper)(&sup_st, @selector(stret::::::::::::::::::::::::::::::::::::), VEC1, VEC2, VEC3, VEC4, VEC5, VEC6, VEC7, VEC8, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0); + testassert(state == 3); + testassert(stret_equal(stretval, STRET_RESULT)); + +#if __i386__ + state = 0; + fpmsg = (typeof(fpmsg))objc_msgSend_fpret_debug; + fpval = 0; + fpval = (*fpmsg)(sub, @selector(fpret::::::::::::::::::::::::::::::::::::), VEC1, VEC2, VEC3, VEC4, VEC5, VEC6, VEC7, VEC8, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0); + testassert(state == 104); + testassert(fpval == FP_RESULT); +#endif +#if __x86_64__ + state = 0; + lfpmsg = (typeof(lfpmsg))objc_msgSend_fpret_debug; + lfpval = 0; + lfpval = (*lfpmsg)(sub, @selector(lfpret::::::::::::::::::::::::::::::::::::), VEC1, VEC2, VEC3, VEC4, VEC5, VEC6, VEC7, VEC8, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0); + testassert(state == 105); + testassert(lfpval == LFP_RESULT); + + // fixme fp2ret +#endif + + // debug messengers +#endif + + + // objc_msgLookup + +#if 1 + // fixme objc_msgLookup test hack stopped working after a compiler update + +#elif __has_feature(objc_arc) + // ARC interferes with objc_msgLookup test hacks + +#elif __i386__ && TARGET_OS_SIMULATOR + testwarn("fixme msgLookup hack doesn't work"); + +#else + // fixme hack: call the looked-up method +# if __arm64__ +# define CALL_LOOKUP(ret) \ + asm volatile ("blr x17 \n mov %x0, x0" : "=r" (ret)) +# define CALL_LOOKUP_STRET(ret) \ + asm volatile ("mov x8, %x1 \n blr x17 \n" : "=m" (ret) : "r" (&ret)) + +# elif __arm__ +# define CALL_LOOKUP(ret) \ + asm volatile ("blx r12 \n mov %0, r0" : "=r" (ret)) +# define CALL_LOOKUP_STRET(ret) \ + asm volatile ("mov r0, %1 \n blx r12 \n" : "=m" (ret) : "r" (&ret)) + +# elif __x86_64__ +# define CALL_LOOKUP(ret) \ + asm volatile ("call *%%r11 \n mov %%rax, %0" : "=r" (ret)) +# define CALL_LOOKUP_STRET(ret) \ + asm volatile ("mov %1, %%rdi \n call *%%r11 \n" : "=m" (ret) : "r" (&ret)) + +# elif __i386__ +# define CALL_LOOKUP(ret) \ + asm volatile ("call *%%eax \n mov %%eax, %0" : "=r" (ret)) +# define CALL_LOOKUP_STRET(ret) \ + asm volatile ("add $4, %%esp \n mov %1, (%%esp) \n call *%%eax \n sub $4, %%esp \n" : "=m" (ret) : "d" (&ret)) + +# else +# error unknown architecture +# endif + + // msgLookup uncached + // msgLookup uncached super + // msgLookup uncached stret + // msgLookup uncached super stret + // msgLookup uncached fpret + // msgLookup uncached fpret long double + // msgLookup cached + // msgLookup cached stret + // msgLookup cached super + // msgLookup cached super stret + // msgLookup cached fpret + // msgLookup cached fpret long double + // fixme verify that uncached lookup didn't happen the 2nd time? + SELF = sub; + _objc_flush_caches(object_getClass(sub)); + for (int i = 0; i < 5; i++) { + testprintf("objc_msgLookup\n"); + state = 0; + idmsg = (typeof(idmsg))objc_msgLookup; + idval = nil; + (*idmsg)(sub, @selector(idret::::::::::::::::::::::::::::::::::::), VEC1, VEC2, VEC3, VEC4, VEC5, VEC6, VEC7, VEC8, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0); + CALL_LOOKUP(idval); + testassert(state == 101); + testassert(idval == ID_RESULT); + + testprintf("objc_msgLookup_stret\n"); + state = 0; + stretmsg = (typeof(stretmsg))objc_msgLookup_stret; + stretval = zero; + (*stretmsg)(sub, @selector(stret::::::::::::::::::::::::::::::::::::), VEC1, VEC2, VEC3, VEC4, VEC5, VEC6, VEC7, VEC8, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0); + CALL_LOOKUP_STRET(stretval); + testassert(state == 103); + testassert(stret_equal(stretval, STRET_RESULT)); + + testprintf("objc_msgLookupSuper2\n"); + state = 100; + sup_st.receiver = sub; + sup_st.super_class = object_getClass(sub); + idmsgsuper = (typeof(idmsgsuper))objc_msgLookupSuper2; + idval = nil; + idval = (*idmsgsuper)(&sup_st, @selector(idret::::::::::::::::::::::::::::::::::::), VEC1, VEC2, VEC3, VEC4, VEC5, VEC6, VEC7, VEC8, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0); + CALL_LOOKUP(idval); + testassert(state == 1); + testassert(idval == ID_RESULT); + + testprintf("objc_msgLookupSuper2_stret\n"); + state = 100; + sup_st.receiver = sub; + sup_st.super_class = object_getClass(sub); + stretmsgsuper = (typeof(stretmsgsuper))objc_msgLookupSuper2_stret; + stretval = zero; + (*stretmsgsuper)(&sup_st, @selector(stret::::::::::::::::::::::::::::::::::::), VEC1, VEC2, VEC3, VEC4, VEC5, VEC6, VEC7, VEC8, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0); + CALL_LOOKUP_STRET(stretval); + testassert(state == 3); + testassert(stret_equal(stretval, STRET_RESULT)); + +#if __i386__ + // fixme fpret, can't test FP stack properly +#endif +#if __x86_64__ + // fixme fpret, can't test FP stack properly + // fixme fp2ret, can't test FP stack properly +#endif + + } + + // msgLookup to nil + // msgLookup to nil stret + // fixme msgLookup to nil long long + // fixme msgLookup to nil fpret + // fixme msgLookup to nil fp2ret + + testprintf("objc_msgLookup to nil\n"); + state = 0; + idmsg = (typeof(idmsg))objc_msgLookup; + idval = nil; + (*idmsg)(NIL_RECEIVER, @selector(idret::::::::::::::::::::::::::::::::::::), VEC1, VEC2, VEC3, VEC4, VEC5, VEC6, VEC7, VEC8, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0); + CALL_LOOKUP(idval); + testassert(state == 0); + testassert(idval == nil); + + testprintf("objc_msgLookup_stret to nil\n"); + state = 0; + stretmsg = (typeof(stretmsg))objc_msgLookup_stret; + stretval = zero; + (*stretmsg)(NIL_RECEIVER, @selector(stret::::::::::::::::::::::::::::::::::::), VEC1, VEC2, VEC3, VEC4, VEC5, VEC6, VEC7, VEC8, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0); + CALL_LOOKUP_STRET(stretval); + testassert(state == 0); + // no stret result guarantee + +#if __i386__ + // fixme fpret, can't test FP stack properly +#endif +#if __x86_64__ + // fixme fpret, can't test FP stack properly + // fixme fp2ret, can't test FP stack properly +#endif + + // objc_msgLookup +#endif + + + +#if !TEST_DWARF + testwarn("no unwind tables in this configuration " NO_DWARF_REASON); +#else + // DWARF unwind tables + testprintf("unwind tables\n"); + + // Clear simulator-related environment variables. + // Disassembly will run llvm-objdump which is not a simulator executable. + unsetenv("DYLD_ROOT_PATH"); + unsetenv("DYLD_FALLBACK_LIBRARY_PATH"); + unsetenv("DYLD_FALLBACK_FRAMEWORK_PATH"); + + // Check mprotect() of objc_msgSend. + // It doesn't work when running on a device with no libobjc root. + // In that case we skip this part of the test without failing. + // fixme make this work + // fixme now it doesn't work even with a libobjc root in place? + int err1 = mprotect((void *)((uintptr_t)&objc_msgSend & ~(PAGE_MAX_SIZE-1)), + PAGE_MAX_SIZE, PROT_READ | PROT_WRITE); + int errno1 = errno; + int err2 = mprotect((void *)((uintptr_t)&objc_msgSend & ~(PAGE_MAX_SIZE-1)), + PAGE_MAX_SIZE, PROT_READ | PROT_EXEC); + int errno2 = errno; + if (err1 || err2) { + testwarn("can't mprotect() objc_msgSend (%d, %d). " + "Skipping unwind table test.", + err1, errno1, err2, errno2); + } + else { + // install exception handler + struct sigaction act; + act.sa_sigaction = break_handler; + act.sa_mask = 0; + act.sa_flags = SA_SIGINFO; + sigaction(BREAK_SIGNAL, &act, nil); +#if defined(BREAK_SIGNAL2) + sigaction(BREAK_SIGNAL2, &act, nil); +#endif + + SubDW *dw = [[SubDW alloc] init]; + + objc_setForwardHandler((void*)test_dw_forward, (void*)test_dw_forward_stret); + +# if __x86_64__ + test_dw("objc_msgSend", dw, tagged, exttagged, false, 0); + test_dw("objc_msgSend_stret", dw, tagged, exttagged, true, 0); + test_dw("objc_msgSend_fpret", dw, tagged, exttagged, false, 0); + test_dw("objc_msgSend_fp2ret", dw, tagged, exttagged, false, 0); + test_dw("objc_msgSendSuper", dw, tagged, exttagged, false, 0); + test_dw("objc_msgSendSuper2", dw, tagged, exttagged, false, 0); + test_dw("objc_msgSendSuper_stret", dw, tagged, exttagged, true, 0); + test_dw("objc_msgSendSuper2_stret", dw, tagged, exttagged, true, 0); +# elif __i386__ + test_dw("objc_msgSend", dw, dw, dw, false, 0); + test_dw("objc_msgSend_stret", dw, dw, dw, true, 0); + test_dw("objc_msgSend_fpret", dw, dw, dw, false, 0); + test_dw("objc_msgSendSuper", dw, dw, dw, false, 0); + test_dw("objc_msgSendSuper2", dw, dw, dw, false, 0); + test_dw("objc_msgSendSuper_stret", dw, dw, dw, true, 0); + test_dw("objc_msgSendSuper2_stret", dw, dw, dw, true, 0); +# elif __arm64__ + test_dw("objc_msgSend", dw, tagged, exttagged, false, 1); + test_dw("objc_msgSendSuper", dw, tagged, exttagged, false, 1); + test_dw("objc_msgSendSuper2", dw, tagged, exttagged, false, 1); +# elif __arm__ + test_dw("objc_msgSend", dw, dw, dw, false, 0); + test_dw("objc_msgSend_stret", dw, dw, dw, true, 0); + test_dw("objc_msgSendSuper", dw, dw, dw, false, 0); + test_dw("objc_msgSendSuper2", dw, dw, dw, false, 0); + test_dw("objc_msgSendSuper_stret", dw, dw, dw, true, 0); + test_dw("objc_msgSendSuper2_stret", dw, dw, dw, true, 0); +# else +# error unknown architecture +# endif + } + + // end DWARF unwind test +#endif + + } POP_POOL; + succeed(__FILE__); +} diff --git a/test/nilAPIArgs.m b/test/nilAPIArgs.m new file mode 100644 index 0000000..29eee12 --- /dev/null +++ b/test/nilAPIArgs.m @@ -0,0 +1,18 @@ +/* +TEST_BUILD_OUTPUT +.*nilAPIArgs.m:\d+:\d+: warning: null passed to a callee that requires a non-null argument \[-Wnonnull\] +.*nilAPIArgs.m:\d+:\d+: warning: null passed to a callee that requires a non-null argument \[-Wnonnull\] +END +*/ + +#include "test.h" + +#import + +int main() { + // ensure various bits of API don't crash when tossed nil parameters + class_conformsToProtocol(nil, nil); + method_setImplementation(nil, NULL); + + succeed(__FILE__); +} diff --git a/test/nonpointerisa.m b/test/nonpointerisa.m new file mode 100644 index 0000000..673220d --- /dev/null +++ b/test/nonpointerisa.m @@ -0,0 +1,262 @@ +// TEST_CFLAGS -framework Foundation +// TEST_CONFIG MEM=mrc + +#include "test.h" +#include + +#include +#include + +#define ISA(x) (*((uintptr_t *)(x))) +#define NONPOINTER(x) (ISA(x) & 1) + +#if SUPPORT_NONPOINTER_ISA +# if __x86_64__ +# define RC_ONE (1ULL<<56) +# elif __arm64__ && __LP64__ +# define RC_ONE (1ULL<<45) +# elif __ARM_ARCH_7K__ >= 2 || (__arm64__ && !__LP64__) +# define RC_ONE (1ULL<<25) +# else +# error unknown architecture +# endif +#endif + + +void check_raw_pointer(id obj, Class cls) +{ + testassert(object_getClass(obj) == cls); + testassert(!NONPOINTER(obj)); + + uintptr_t isa = ISA(obj); + testassert((Class)isa == cls); + testassert((Class)(isa & objc_debug_isa_class_mask) == cls); + testassert((Class)(isa & ~objc_debug_isa_class_mask) == 0); + + CFRetain(obj); + testassert(ISA(obj) == isa); + testassert([obj retainCount] == 2); + [obj retain]; + testassert(ISA(obj) == isa); + testassert([obj retainCount] == 3); + CFRelease(obj); + testassert(ISA(obj) == isa); + testassert([obj retainCount] == 2); + [obj release]; + testassert(ISA(obj) == isa); + testassert([obj retainCount] == 1); +} + + +#if ! SUPPORT_NONPOINTER_ISA + +int main() +{ +#if OBJC_HAVE_NONPOINTER_ISA || OBJC_HAVE_PACKED_NONPOINTER_ISA || OBJC_HAVE_INDEXED_NONPOINTER_ISA +# error wrong +#endif + + testprintf("Isa with index\n"); + id index_o = [NSObject new]; + check_raw_pointer(index_o, [NSObject class]); + + // These variables DO NOT exist without non-pointer isa support. + testassert(!dlsym(RTLD_DEFAULT, "objc_absolute_packed_isa_class_mask")); + testassert(!dlsym(RTLD_DEFAULT, "objc_absolute_indexed_isa_magic_mask")); + testassert(!dlsym(RTLD_DEFAULT, "objc_absolute_indexed_isa_magic_value")); + testassert(!dlsym(RTLD_DEFAULT, "objc_absolute_indexed_isa_index_mask")); + testassert(!dlsym(RTLD_DEFAULT, "objc_absolute_indexed_isa_index_shift")); + + // These variables DO exist even without non-pointer isa support. + testassert(dlsym(RTLD_DEFAULT, "objc_debug_isa_class_mask")); + testassert(dlsym(RTLD_DEFAULT, "objc_debug_isa_magic_mask")); + testassert(dlsym(RTLD_DEFAULT, "objc_debug_isa_magic_value")); + + succeed(__FILE__); +} + +#else +// SUPPORT_NONPOINTER_ISA + +void check_nonpointer(id obj, Class cls) +{ + testassert(object_getClass(obj) == cls); + testassert(NONPOINTER(obj)); + + uintptr_t isa = ISA(obj); + + if (objc_debug_indexed_isa_magic_mask != 0) { + // Indexed isa. + testassert((isa & objc_debug_indexed_isa_magic_mask) == objc_debug_indexed_isa_magic_value); + testassert((isa & ~objc_debug_indexed_isa_index_mask) != 0); + uintptr_t index = (isa & objc_debug_indexed_isa_index_mask) >> objc_debug_indexed_isa_index_shift; + testassert(index < objc_indexed_classes_count); + testassert(objc_indexed_classes[index] == cls); + } else { + // Packed isa. + testassert((Class)(isa & objc_debug_isa_class_mask) == cls); + testassert((Class)(isa & ~objc_debug_isa_class_mask) != 0); + testassert((isa & objc_debug_isa_magic_mask) == objc_debug_isa_magic_value); + } + + CFRetain(obj); + testassert(ISA(obj) == isa + RC_ONE); + testassert([obj retainCount] == 2); + [obj retain]; + testassert(ISA(obj) == isa + RC_ONE*2); + testassert([obj retainCount] == 3); + CFRelease(obj); + testassert(ISA(obj) == isa + RC_ONE); + testassert([obj retainCount] == 2); + [obj release]; + testassert(ISA(obj) == isa); + testassert([obj retainCount] == 1); +} + + +@interface OS_object ++(id)alloc; +@end + +@interface Fake_OS_object : NSObject { + int refcnt; + int xref_cnt; +} +@end + +@implementation Fake_OS_object ++(void)initialize { + static bool initialized; + if (!initialized) { + initialized = true; + testprintf("Nonpointer during +initialize\n"); + testassert(NONPOINTER(self)); + id o = [Fake_OS_object new]; + check_nonpointer(o, self); + [o release]; + } +} +@end + +@interface Sub_OS_object : OS_object @end + +@implementation Sub_OS_object +@end + + + +int main() +{ + uintptr_t isa; + +#if SUPPORT_PACKED_ISA +# if !OBJC_HAVE_NONPOINTER_ISA || !OBJC_HAVE_PACKED_NONPOINTER_ISA || OBJC_HAVE_INDEXED_NONPOINTER_ISA +# error wrong +# endif + testassert(objc_debug_isa_class_mask == (uintptr_t)&objc_absolute_packed_isa_class_mask); + + // Indexed isa variables DO NOT exist on packed-isa platforms + testassert(!dlsym(RTLD_DEFAULT, "objc_absolute_indexed_isa_magic_mask")); + testassert(!dlsym(RTLD_DEFAULT, "objc_absolute_indexed_isa_magic_value")); + testassert(!dlsym(RTLD_DEFAULT, "objc_absolute_indexed_isa_index_mask")); + testassert(!dlsym(RTLD_DEFAULT, "objc_absolute_indexed_isa_index_shift")); + +#elif SUPPORT_INDEXED_ISA +# if !OBJC_HAVE_NONPOINTER_ISA || OBJC_HAVE_PACKED_NONPOINTER_ISA || !OBJC_HAVE_INDEXED_NONPOINTER_ISA +# error wrong +# endif + testassert(objc_debug_indexed_isa_magic_mask == (uintptr_t)&objc_absolute_indexed_isa_magic_mask); + testassert(objc_debug_indexed_isa_magic_value == (uintptr_t)&objc_absolute_indexed_isa_magic_value); + testassert(objc_debug_indexed_isa_index_mask == (uintptr_t)&objc_absolute_indexed_isa_index_mask); + testassert(objc_debug_indexed_isa_index_shift == (uintptr_t)&objc_absolute_indexed_isa_index_shift); + + // Packed isa variable DOES NOT exist on indexed-isa platforms. + testassert(!dlsym(RTLD_DEFAULT, "objc_absolute_packed_isa_class_mask")); + +#else +# error unknown nonpointer isa format +#endif + + testprintf("Isa with index\n"); + id index_o = [Fake_OS_object new]; + check_nonpointer(index_o, [Fake_OS_object class]); + + testprintf("Weakly referenced\n"); + isa = ISA(index_o); + id weak; + objc_storeWeak(&weak, index_o); + testassert(__builtin_popcountl(isa ^ ISA(index_o)) == 1); + + testprintf("Has associated references\n"); + id assoc = @"thing"; + isa = ISA(index_o); + objc_setAssociatedObject(index_o, assoc, assoc, OBJC_ASSOCIATION_ASSIGN); + testassert(__builtin_popcountl(isa ^ ISA(index_o)) == 1); + + + testprintf("Isa without index\n"); + id raw_o = [OS_object alloc]; + check_raw_pointer(raw_o, [OS_object class]); + + + id buf[4]; + id bufo = (id)buf; + + testprintf("Change isa 0 -> raw pointer\n"); + bzero(buf, sizeof(buf)); + object_setClass(bufo, [OS_object class]); + check_raw_pointer(bufo, [OS_object class]); + + testprintf("Change isa 0 -> nonpointer\n"); + bzero(buf, sizeof(buf)); + object_setClass(bufo, [NSObject class]); + check_nonpointer(bufo, [NSObject class]); + + testprintf("Change isa nonpointer -> nonpointer\n"); + testassert(NONPOINTER(bufo)); + _objc_rootRetain(bufo); + testassert(_objc_rootRetainCount(bufo) == 2); + object_setClass(bufo, [Fake_OS_object class]); + testassert(_objc_rootRetainCount(bufo) == 2); + _objc_rootRelease(bufo); + testassert(_objc_rootRetainCount(bufo) == 1); + check_nonpointer(bufo, [Fake_OS_object class]); + + testprintf("Change isa nonpointer -> raw pointer\n"); + // Retain count must be preserved. + // Use root* to avoid OS_object's overrides. + testassert(NONPOINTER(bufo)); + _objc_rootRetain(bufo); + testassert(_objc_rootRetainCount(bufo) == 2); + object_setClass(bufo, [OS_object class]); + testassert(_objc_rootRetainCount(bufo) == 2); + _objc_rootRelease(bufo); + testassert(_objc_rootRetainCount(bufo) == 1); + check_raw_pointer(bufo, [OS_object class]); + + testprintf("Change isa raw pointer -> nonpointer (doesn't happen)\n"); + testassert(!NONPOINTER(bufo)); + _objc_rootRetain(bufo); + testassert(_objc_rootRetainCount(bufo) == 2); + object_setClass(bufo, [Fake_OS_object class]); + testassert(_objc_rootRetainCount(bufo) == 2); + _objc_rootRelease(bufo); + testassert(_objc_rootRetainCount(bufo) == 1); + check_raw_pointer(bufo, [Fake_OS_object class]); + + testprintf("Change isa raw pointer -> raw pointer\n"); + testassert(!NONPOINTER(bufo)); + _objc_rootRetain(bufo); + testassert(_objc_rootRetainCount(bufo) == 2); + object_setClass(bufo, [Sub_OS_object class]); + testassert(_objc_rootRetainCount(bufo) == 2); + _objc_rootRelease(bufo); + testassert(_objc_rootRetainCount(bufo) == 1); + check_raw_pointer(bufo, [Sub_OS_object class]); + + + succeed(__FILE__); +} + +// SUPPORT_NONPOINTER_ISA +#endif diff --git a/test/nopool.m b/test/nopool.m new file mode 100644 index 0000000..f3493de --- /dev/null +++ b/test/nopool.m @@ -0,0 +1,49 @@ +// TEST_CONFIG MEM=mrc + +#include "test.h" +#include "testroot.i" + +@implementation TestRoot (Loader) ++(void)load +{ + [[TestRoot new] autorelease]; + testassert(TestRootAutorelease == 1); + testassert(TestRootDealloc == 0); +} +@end + +int main() +{ + // +load's autoreleased object should have deallocated + testassert(TestRootDealloc == 1); + + [[TestRoot new] autorelease]; + testassert(TestRootAutorelease == 2); + + + objc_autoreleasePoolPop(objc_autoreleasePoolPush()); + [[TestRoot new] autorelease]; + testassert(TestRootAutorelease == 3); + + + testonthread(^{ + [[TestRoot new] autorelease]; + testassert(TestRootAutorelease == 4); + testassert(TestRootDealloc == 1); + }); + // thread's autoreleased object should have deallocated + testassert(TestRootDealloc == 2); + + + // Test no-pool autorelease after a pool was pushed and popped. + // The simplest POOL_SENTINEL check during pop gets this wrong. + testonthread(^{ + objc_autoreleasePoolPop(objc_autoreleasePoolPush()); + [[TestRoot new] autorelease]; + testassert(TestRootAutorelease == 5); + testassert(TestRootDealloc == 2); + }); + testassert(TestRootDealloc == 3 +); + succeed(__FILE__); +} diff --git a/test/nscdtors.mm b/test/nscdtors.mm new file mode 100644 index 0000000..876f6b0 --- /dev/null +++ b/test/nscdtors.mm @@ -0,0 +1,6 @@ +// TEST_CONFIG +// test cdtors, with NSObject instead of TestRoot as the root class + +#define USE_FOUNDATION 1 +#include "cdtors.mm" + diff --git a/test/nsexc.m b/test/nsexc.m new file mode 100644 index 0000000..cf4ac43 --- /dev/null +++ b/test/nsexc.m @@ -0,0 +1,7 @@ +/* +need exception-safe ARC for exception deallocation tests +TEST_CFLAGS -fobjc-arc-exceptions -framework Foundation +*/ + +#define USE_FOUNDATION 1 +#include "exc.m" diff --git a/test/nsobject.m b/test/nsobject.m new file mode 100644 index 0000000..dd64bd0 --- /dev/null +++ b/test/nsobject.m @@ -0,0 +1,114 @@ +// TEST_CONFIG MEM=mrc + +#include "test.h" + +#import + +@interface Sub : NSObject @end +@implementation Sub ++(id)allocWithZone:(NSZone *)zone { + testprintf("in +[Sub alloc]\n"); + return [super allocWithZone:zone]; + } +-(void)dealloc { + testprintf("in -[Sub dealloc]\n"); + [super dealloc]; +} +@end + + +// These declarations and definitions can be used +// to check the compile-time type of an object. +@interface NSObject (Checker) +// fixme this isn't actually enforced ++(void)NSObjectInstance __attribute__((unavailable)); +@end +@implementation NSObject (Checker) +-(void)NSObjectInstance { } ++(void)NSObjectClass { } +@end +@interface Sub (Checker) +-(void)NSObjectInstance __attribute__((unavailable)); ++(void)NSObjectClass __attribute__((unavailable)); +@end +@implementation Sub (Checker) +-(void)SubInstance { } ++(void)SubClass { } +@end + +int main() +{ + PUSH_POOL { + [[Sub new] autorelease]; + } POP_POOL; + + // Verify that dot syntax on class objects works with some instance methods + // (void)NSObject.self; fixme + (void)NSObject.class; + (void)NSObject.superclass; + (void)NSObject.hash; + (void)NSObject.description; + (void)NSObject.debugDescription; + + // Verify that some methods return the correct type. + Class cls; + NSObject *nsobject = nil; + Sub *subobject = nil; + + cls = [NSObject self]; + cls = [Sub self]; + nsobject = [nsobject self]; + subobject = [subobject self]; + [[NSObject self] NSObjectClass]; + [[nsobject self] NSObjectInstance]; + [[Sub self] SubClass]; + [[subobject self] SubInstance]; + + // fixme + // cls = NSObject.self; + // cls = Sub.self; + // [NSObject.self NSObjectClass]; + // [nsobject.self NSObjectInstance]; + // [Sub.self SubClass]; + // [subobject.self SubInstance]; + + cls = [NSObject class]; + cls = [nsobject class]; + cls = [Sub class]; + cls = [subobject class]; + [[NSObject class] NSObjectClass]; + [[nsobject class] NSObjectClass]; + [[Sub class] SubClass]; + [[subobject class] SubClass]; + + cls = NSObject.class; + cls = nsobject.class; + cls = Sub.class; + cls = subobject.class; + [NSObject.class NSObjectClass]; + [nsobject.class NSObjectClass]; + [Sub.class SubClass]; + [subobject.class SubClass]; + + + cls = [NSObject superclass]; + cls = [nsobject superclass]; + cls = [Sub superclass]; + cls = [subobject superclass]; + [[NSObject superclass] NSObjectClass]; + [[nsobject superclass] NSObjectClass]; + [[Sub superclass] NSObjectClass]; + [[subobject superclass] NSObjectClass]; + + cls = NSObject.superclass; + cls = nsobject.superclass; + cls = Sub.superclass; + cls = subobject.superclass; + [NSObject.superclass NSObjectClass]; + [nsobject.superclass NSObjectClass]; + [Sub.superclass NSObjectClass]; + [subobject.superclass NSObjectClass]; + + + succeed(__FILE__); +} diff --git a/test/nsprotocol.m b/test/nsprotocol.m new file mode 100644 index 0000000..5315371 --- /dev/null +++ b/test/nsprotocol.m @@ -0,0 +1,17 @@ +// TEST_CONFIG + +#include "test.h" +#include + +int main() +{ + // Class Protocol is always a subclass of NSObject + + testassert(objc_getClass("NSObject")); + + Class cls = objc_getClass("Protocol"); + testassert(class_getInstanceMethod(cls, sel_registerName("isProxy"))); + testassert(class_getSuperclass(cls) == objc_getClass("NSObject")); + + succeed(__FILE__); +} diff --git a/test/objectCopy.m b/test/objectCopy.m new file mode 100644 index 0000000..973ac4d --- /dev/null +++ b/test/objectCopy.m @@ -0,0 +1,38 @@ +// TEST_CONFIG MEM=mrc + +#include "test.h" +#include + +@interface Test : NSObject { +@public + char bytes[32-sizeof(void*)]; +} +@end +@implementation Test +@end + + +int main() +{ + Test *o0 = [Test new]; + [o0 retain]; + Test *o1 = class_createInstance([Test class], 32); + [o1 retain]; + id o2 = object_copy(o0, 0); + id o3 = object_copy(o1, 0); + id o4 = object_copy(o1, 32); + + testassert(malloc_size(o0) == 32); + testassert(malloc_size(o1) == 64); + testassert(malloc_size(o2) == 32); + testassert(malloc_size(o3) == 32); + testassert(malloc_size(o4) == 64); + + testassert([o0 retainCount] == 2); + testassert([o1 retainCount] == 2); + testassert([o2 retainCount] == 1); + testassert([o3 retainCount] == 1); + testassert([o4 retainCount] == 1); + + succeed(__FILE__); +} diff --git a/test/property.m b/test/property.m new file mode 100644 index 0000000..0c2de1e --- /dev/null +++ b/test/property.m @@ -0,0 +1,157 @@ +/* +TEST_BUILD_OUTPUT +.*property.m:\d+:\d+: warning: null passed to a callee that requires a non-null argument \[-Wnonnull\](\n.* note: expanded from macro 'testassert')? +.*property.m:\d+:\d+: warning: null passed to a callee that requires a non-null argument \[-Wnonnull\](\n.* note: expanded from macro 'testassert')? +.*property.m:\d+:\d+: warning: null passed to a callee that requires a non-null argument \[-Wnonnull\](\n.* note: expanded from macro 'testassert')? +.*property.m:\d+:\d+: warning: null passed to a callee that requires a non-null argument \[-Wnonnull\](\n.* note: expanded from macro 'testassert')? +.*property.m:\d+:\d+: warning: null passed to a callee that requires a non-null argument \[-Wnonnull\](\n.* note: expanded from macro 'testassert')? +.*property.m:\d+:\d+: warning: null passed to a callee that requires a non-null argument \[-Wnonnull\](\n.* note: expanded from macro 'testassert')? +END +*/ + +#include "test.h" +#include "testroot.i" +#include +#include +#include + + +@protocol SuperProto +@property(readonly) char superProtoProp; +@property(class,readonly) char superProtoProp; +@end + +@protocol SubProto +@property(readonly) uintptr_t subProtoProp; +@property(class,readonly) uintptr_t subProtoProp; +@property(readonly) uintptr_t subInstanceOnlyProtoProp; +@property(class,readonly) uintptr_t subClassOnlyProtoProp; +@end + +@interface Super : TestRoot { + @public + char superIvar; +} + +@property(readonly) char superProp; +@property(class,readonly) char superProp; +@end + +@implementation Super +@synthesize superProp = superIvar; ++(char)superProp { return 'a'; } + +-(char)superProtoProp { return 'a'; } ++(char)superProtoProp { return 'a'; } +@end + + +@interface Sub : Super { + @public + uintptr_t subIvar; +} +@property(readonly) uintptr_t subProp; +@property(class,readonly) uintptr_t subProp; +@property(readonly) uintptr_t subInstanceOnlyProp; +@property(class,readonly) uintptr_t subClassOnlyProp; +@end + +@implementation Sub +@synthesize subProp = subIvar; ++(uintptr_t)subProp { return 'a'; } ++(uintptr_t)subClassOnlyProp { return 'a'; } +-(uintptr_t)subInstanceOnlyProp { return 'a'; } + +-(uintptr_t)subProtoProp { return 'a'; } ++(uintptr_t)subProtoProp { return 'a'; } ++(uintptr_t)subClassOnlyProtoProp { return 'a'; } +-(uintptr_t)subInstanceOnlyProtoProp { return 'a'; } +@end + + +void test(Class subcls) +{ + objc_property_t prop; + + Class supercls = class_getSuperclass(subcls); + + prop = class_getProperty(subcls, "subProp"); + testassert(prop); + + prop = class_getProperty(subcls, "subProtoProp"); + testassert(prop); + + prop = class_getProperty(supercls, "superProp"); + testassert(prop); + testassert(prop == class_getProperty(subcls, "superProp")); + + prop = class_getProperty(supercls, "superProtoProp"); + testassert(prop); + // These are distinct because Sub adopts SuperProto itself + // in addition to Super's adoption of SuperProto. + testassert(prop != class_getProperty(subcls, "superProtoProp")); + + prop = class_getProperty(supercls, "subProp"); + testassert(!prop); + + prop = class_getProperty(supercls, "subProtoProp"); + testassert(!prop); + + testassert(nil == class_getProperty(nil, "foo")); + testassert(nil == class_getProperty(subcls, nil)); + testassert(nil == class_getProperty(nil, nil)); +} + + +int main() +{ + Class subcls = [Sub class]; + Class submeta = object_getClass(subcls); + objc_property_t prop; + + // instance properties + test(subcls); + + // class properties + test(submeta); + + // properties must not appear on the wrong side + testassert(nil == class_getProperty(subcls, "subClassOnlyProp")); + testassert(nil == class_getProperty(submeta, "subInstanceOnlyProp")); + testassert(nil == class_getProperty(subcls, "subClassOnlyProtoProp")); + testassert(nil == class_getProperty(submeta, "subInstanceOnlyProtoProp")); + + // properties with the same name on both sides are distinct + testassert(class_getProperty(subcls, "subProp") != class_getProperty(submeta, "subProp")); + testassert(class_getProperty(subcls, "superProp") != class_getProperty(submeta, "superProp")); + testassert(class_getProperty(subcls, "subProtoProp") != class_getProperty(submeta, "subProtoProp")); + testassert(class_getProperty(subcls, "superProtoProp") != class_getProperty(submeta, "superProtoProp")); + + // protocol properties + + prop = protocol_getProperty(@protocol(SubProto), "subProtoProp", YES, YES); + testassert(prop); + + prop = protocol_getProperty(@protocol(SuperProto), "superProtoProp", YES, YES); + testassert(prop == protocol_getProperty(@protocol(SubProto), "superProtoProp", YES, YES)); + + prop = protocol_getProperty(@protocol(SuperProto), "subProtoProp", YES, YES); + testassert(!prop); + + // protocol properties must not appear on the wrong side + testassert(nil == protocol_getProperty(@protocol(SubProto), "subClassOnlyProtoProp", YES, YES)); + testassert(nil == protocol_getProperty(@protocol(SubProto), "subInstanceOnlyProtoProp", YES, NO)); + + // protocol properties with the same name on both sides are distinct + testassert(protocol_getProperty(@protocol(SubProto), "subProtoProp", YES, YES) != protocol_getProperty(@protocol(SubProto), "subProtoProp", YES, NO)); + testassert(protocol_getProperty(@protocol(SubProto), "superProtoProp", YES, YES) != protocol_getProperty(@protocol(SubProto), "superProtoProp", YES, NO)); + + testassert(nil == protocol_getProperty(nil, "foo", YES, YES)); + testassert(nil == protocol_getProperty(@protocol(SuperProto), nil, YES, YES)); + testassert(nil == protocol_getProperty(nil, nil, YES, YES)); + testassert(nil == protocol_getProperty(@protocol(SuperProto), "superProtoProp", NO, YES)); + testassert(nil == protocol_getProperty(@protocol(SuperProto), "superProtoProp", NO, NO)); + + succeed(__FILE__); + return 0; +} diff --git a/test/propertyDesc.m b/test/propertyDesc.m new file mode 100644 index 0000000..87d3e65 --- /dev/null +++ b/test/propertyDesc.m @@ -0,0 +1,334 @@ +/* +TEST_BUILD_OUTPUT +.*propertyDesc.m:\d+:\d+: warning: null passed to a callee that requires a non-null argument \[-Wnonnull\] +.*propertyDesc.m:\d+:\d+: warning: null passed to a callee that requires a non-null argument \[-Wnonnull\] +.*propertyDesc.m:\d+:\d+: warning: null passed to a callee that requires a non-null argument \[-Wnonnull\] +.*propertyDesc.m:\d+:\d+: warning: null passed to a callee that requires a non-null argument \[-Wnonnull\] +.*propertyDesc.m:\d+:\d+: warning: null passed to a callee that requires a non-null argument \[-Wnonnull\] +.*propertyDesc.m:\d+:\d+: warning: null passed to a callee that requires a non-null argument \[-Wnonnull\] +.*propertyDesc.m:\d+:\d+: warning: null passed to a callee that requires a non-null argument \[-Wnonnull\] +.*propertyDesc.m:\d+:\d+: warning: null passed to a callee that requires a non-null argument \[-Wnonnull\] +END +*/ + +#include "test.h" +#include "testroot.i" +#include +#include +#include + +struct objc_property { + const char *name; + const char *attr; +}; + +#define checkattrlist(attrs, attrcount, target) \ + do { \ + if (target > 0) { \ + testassert(attrs); \ + testassert(attrcount == target); \ + testassert(malloc_size(attrs) >= \ + (1+target) * sizeof(objc_property_attribute_t)); \ + testassert(attrs[target].name == NULL); \ + testassert(attrs[target].value == NULL); \ + } else { \ + testassert(!attrs); \ + testassert(attrcount == 0); \ + } \ + } while (0) + +#define checkattr(attrs, i, n, v) \ + do { \ + char *attrsstart = (char *)attrs; \ + char *attrsend = (char *)attrs + malloc_size(attrs); \ + testassert((char*)(attrs+i+1) <= attrsend); \ + testassert(attrs[i].name >= attrsstart); \ + testassert(attrs[i].value >= attrsstart); \ + testassert(attrs[i].name + strlen(attrs[i].name) + 1 <= attrsend); \ + testassert(attrs[i].value + strlen(attrs[i].value) + 1 <= attrsend); \ + if (n) testassert(0 == strcmp(attrs[i].name, n)); \ + else testassert(attrs[i].name == NULL); \ + if (v) testassert(0 == strcmp(attrs[i].value, v)); \ + else testassert(attrs[i].value == NULL); \ + } while (0) + +int main() +{ + char *value; + objc_property_attribute_t *attrs; + unsigned int attrcount; + + // STRING TO ATTRIBUTE LIST (property_copyAttributeList) + + struct objc_property prop; + prop.name = "test"; + + // null property + attrcount = 42; + attrs = property_copyAttributeList(NULL, &attrcount); + testassert(!attrs); + testassert(attrcount == 0); + attrs = property_copyAttributeList(NULL, NULL); + testassert(!attrs); + + // null attributes + attrcount = 42; + prop.attr = NULL; + attrs = property_copyAttributeList(&prop, &attrcount); + checkattrlist(attrs, attrcount, 0); + attrs = property_copyAttributeList(&prop, NULL); + testassert(!attrs); + + // empty attributes + attrcount = 42; + prop.attr = ""; + attrs = property_copyAttributeList(&prop, &attrcount); + checkattrlist(attrs, attrcount, 0); + attrs = property_copyAttributeList(&prop, NULL); + testassert(!attrs); + + // commas only + attrcount = 42; + prop.attr = ",,,"; + attrs = property_copyAttributeList(&prop, &attrcount); + checkattrlist(attrs, attrcount, 0); + attrs = property_copyAttributeList(&prop, NULL); + testassert(!attrs); + + // long and short names, with and without values + attrcount = 42; + prop.attr = "?XX,',\"?!?!\"YY,\"''''\""; + attrs = property_copyAttributeList(&prop, &attrcount); + checkattrlist(attrs, attrcount, 4); + checkattr(attrs, 0, "?", "XX"); + checkattr(attrs, 1, "'", ""); + checkattr(attrs, 2, "?!?!", "YY"); + checkattr(attrs, 3, "''''", ""); + free(attrs); + + // all recognized attributes simultaneously, values with quotes + attrcount = 42; + prop.attr = "T11,V2222,S333333\",G\"44444444,W,P,D,R,N,C,&"; + attrs = property_copyAttributeList(&prop, &attrcount); + checkattrlist(attrs, attrcount, 11); + checkattr(attrs, 0, "T", "11"); + checkattr(attrs, 1, "V", "2222"); + checkattr(attrs, 2, "S", "333333\""); + checkattr(attrs, 3, "G", "\"44444444"); + checkattr(attrs, 4, "W", ""); + checkattr(attrs, 5, "P", ""); + checkattr(attrs, 6, "D", ""); + checkattr(attrs, 7, "R", ""); + checkattr(attrs, 8, "N", ""); + checkattr(attrs, 9, "C", ""); + checkattr(attrs,10, "&", ""); + free(attrs); + + // kitchen sink + attrcount = 42; + prop.attr = "W,T11,P,?XX,D,V2222,R,',N,S333333\",C,\"?!?!\"YY,&,G\"44444444,\"''''\""; + attrs = property_copyAttributeList(&prop, &attrcount); + checkattrlist(attrs, attrcount, 15); + checkattr(attrs, 0, "W", ""); + checkattr(attrs, 1, "T", "11"); + checkattr(attrs, 2, "P", ""); + checkattr(attrs, 3, "?", "XX"); + checkattr(attrs, 4, "D", ""); + checkattr(attrs, 5, "V", "2222"); + checkattr(attrs, 6, "R", ""); + checkattr(attrs, 7, "'", ""); + checkattr(attrs, 8, "N", ""); + checkattr(attrs, 9, "S", "333333\""); + checkattr(attrs,10, "C", ""); + checkattr(attrs,11, "?!?!", "YY"); + checkattr(attrs,12, "&", ""); + checkattr(attrs,13, "G", "\"44444444"); + checkattr(attrs,14, "''''", ""); + free(attrs); + + // SEARCH ATTRIBUTE LIST (property_copyAttributeValue) + + // null property, null name, empty name + value = property_copyAttributeValue(NULL, NULL); + testassert(!value); + value = property_copyAttributeValue(NULL, "foo"); + testassert(!value); + value = property_copyAttributeValue(NULL, ""); + testassert(!value); + value = property_copyAttributeValue(&prop, NULL); + testassert(!value); + value = property_copyAttributeValue(&prop, ""); + testassert(!value); + + // null attributes, empty attributes + prop.attr = NULL; + value = property_copyAttributeValue(&prop, "foo"); + testassert(!value); + prop.attr = ""; + value = property_copyAttributeValue(&prop, "foo"); + testassert(!value); + + // long and short names, with and without values + prop.attr = "?XX,',\"?!?!\"YY,\"''''\""; + value = property_copyAttributeValue(&prop, "missing"); + testassert(!value); + value = property_copyAttributeValue(&prop, "X"); + testassert(!value); + value = property_copyAttributeValue(&prop, "\""); + testassert(!value); + value = property_copyAttributeValue(&prop, "'''"); + testassert(!value); + value = property_copyAttributeValue(&prop, "'''''"); + testassert(!value); + + value = property_copyAttributeValue(&prop, "?"); + testassert(0 == strcmp(value, "XX")); + testassert(malloc_size(value) >= 1 + strlen(value)); + free(value); + value = property_copyAttributeValue(&prop, "'"); + testassert(0 == strcmp(value, "")); + testassert(malloc_size(value) >= 1 + strlen(value)); + free(value); + value = property_copyAttributeValue(&prop, "?!?!"); + testassert(0 == strcmp(value, "YY")); + testassert(malloc_size(value) >= 1 + strlen(value)); + free(value); + value = property_copyAttributeValue(&prop, "''''"); + testassert(0 == strcmp(value, "")); + testassert(malloc_size(value) >= 1 + strlen(value)); + free(value); + + // all recognized attributes simultaneously, values with quotes + prop.attr = "T11,V2222,S333333\",G\"44444444,W,P,D,R,N,C,&"; + value = property_copyAttributeValue(&prop, "T"); + testassert(0 == strcmp(value, "11")); + testassert(malloc_size(value) >= 1 + strlen(value)); + free(value); + value = property_copyAttributeValue(&prop, "V"); + testassert(0 == strcmp(value, "2222")); + testassert(malloc_size(value) >= 1 + strlen(value)); + free(value); + value = property_copyAttributeValue(&prop, "S"); + testassert(0 == strcmp(value, "333333\"")); + testassert(malloc_size(value) >= 1 + strlen(value)); + free(value); + value = property_copyAttributeValue(&prop, "G"); + testassert(0 == strcmp(value, "\"44444444")); + testassert(malloc_size(value) >= 1 + strlen(value)); + free(value); + value = property_copyAttributeValue(&prop, "W"); + testassert(0 == strcmp(value, "")); + testassert(malloc_size(value) >= 1 + strlen(value)); + free(value); + value = property_copyAttributeValue(&prop, "P"); + testassert(0 == strcmp(value, "")); + testassert(malloc_size(value) >= 1 + strlen(value)); + free(value); + value = property_copyAttributeValue(&prop, "D"); + testassert(0 == strcmp(value, "")); + testassert(malloc_size(value) >= 1 + strlen(value)); + free(value); + value = property_copyAttributeValue(&prop, "R"); + testassert(0 == strcmp(value, "")); + testassert(malloc_size(value) >= 1 + strlen(value)); + free(value); + value = property_copyAttributeValue(&prop, "N"); + testassert(0 == strcmp(value, "")); + testassert(malloc_size(value) >= 1 + strlen(value)); + free(value); + value = property_copyAttributeValue(&prop, "C"); + testassert(0 == strcmp(value, "")); + testassert(malloc_size(value) >= 1 + strlen(value)); + free(value); + value = property_copyAttributeValue(&prop, "&"); + testassert(0 == strcmp(value, "")); + testassert(malloc_size(value) >= 1 + strlen(value)); + free(value); + + // ATTRIBUTE LIST TO STRING (class_addProperty) + + BOOL ok; + objc_property_t prop2; + + // null name + ok = class_addProperty([TestRoot class], NULL, (objc_property_attribute_t *)1, 1); + testassert(!ok); + + // null description + ok = class_addProperty([TestRoot class], "test-null-desc", NULL, 0); + testassert(ok); + prop2 = class_getProperty([TestRoot class], "test-null-desc"); + testassert(prop2); + testassert(0 == strcmp(property_getAttributes(prop2), "")); + + // empty description + ok = class_addProperty([TestRoot class], "test-empty-desc", (objc_property_attribute_t*)1, 0); + testassert(ok); + prop2 = class_getProperty([TestRoot class], "test-empty-desc"); + testassert(prop2); + testassert(0 == strcmp(property_getAttributes(prop2), "")); + + // long and short names, with and without values + objc_property_attribute_t attrs2[] = { + { "!", NULL }, + { "?", "XX" }, + { "'", "" }, + { "?!?!", "YY" }, + { "''''", "" } + }; + ok = class_addProperty([TestRoot class], "test-unrecognized", attrs2, 5); + testassert(ok); + prop2 = class_getProperty([TestRoot class], "test-unrecognized"); + testassert(prop2); + testassert(0 == strcmp(property_getAttributes(prop2), "?XX,',\"?!?!\"YY,\"''''\"")); + + // all recognized attributes simultaneously, values with quotes + objc_property_attribute_t attrs3[] = { + { "&", "" }, + { "C", "" }, + { "N", "" }, + { "R", "" }, + { "D", "" }, + { "P", "" }, + { "W", "" }, + { "G", "\"44444444" }, + { "S", "333333\"" }, + { "V", "2222" }, + { "T", "11" }, + }; + ok = class_addProperty([TestRoot class], "test-recognized", attrs3, 11); + testassert(ok); + prop2 = class_getProperty([TestRoot class], "test-recognized"); + testassert(prop2); + testassert(0 == strcmp(property_getAttributes(prop2), + "&,C,N,R,D,P,W,G\"44444444,S333333\",V2222,T11")); + + // kitchen sink + objc_property_attribute_t attrs4[] = { + { "&", "" }, + { "C", "" }, + { "N", "" }, + { "R", "" }, + { "D", "" }, + { "P", "" }, + { "W", "" }, + { "!", NULL }, + { "G", "\"44444444" }, + { "S", "333333\"" }, + { "V", "2222" }, + { "T", "11" }, + { "?", "XX" }, + { "'", "" }, + { "?!?!", "YY" }, + { "''''", "" } + }; + ok = class_addProperty([TestRoot class], "test-sink", attrs4, 16); + testassert(ok); + prop2 = class_getProperty([TestRoot class], "test-sink"); + testassert(prop2); + testassert(0 == strcmp(property_getAttributes(prop2), + "&,C,N,R,D,P,W,G\"44444444,S333333\",V2222,T11," + "?XX,',\"?!?!\"YY,\"''''\"")); + + succeed(__FILE__); +} diff --git a/test/protocol.m b/test/protocol.m new file mode 100644 index 0000000..1e24ad2 --- /dev/null +++ b/test/protocol.m @@ -0,0 +1,281 @@ +// TEST_CFLAGS -framework Foundation -Wno-deprecated-declarations +// need Foundation to get NSObject compatibility additions for class Protocol +// because ARC calls [protocol retain] +/* +TEST_BUILD_OUTPUT +.*protocol.m:\d+:\d+: warning: null passed to a callee that requires a non-null argument \[-Wnonnull\](\n.* note: expanded from macro 'testassert')? +END +*/ + +#include "test.h" +#include "testroot.i" +#include +#include +#include + +@protocol Proto1 ++(id)proto1ClassMethod; +-(id)proto1InstanceMethod; +@end + +@protocol Proto2 ++(id)proto2ClassMethod; +-(id)proto2InstanceMethod; +@end + +@protocol Proto3 ++(id)proto3ClassMethod; +-(id)proto3InstanceMethod; +@end + +@protocol Proto4 +@property int i; +@end + +// Force some of Proto5's selectors out of address order rdar://10582325 +SEL fn(int x) { if (x) return @selector(m12:); else return @selector(m22:); } + +// This declaration order deliberately looks weird because it determines the +// selector address order on some architectures rdar://10582325 +@protocol Proto5 +-(id)m11:(id)a; +-(void)m12:(id)a; +-(int)m13:(id)a; ++(void)m22:(TestRoot*)a; ++(int)m23:(TestRoot*)a; ++(TestRoot*)m21:(TestRoot*)a; +@optional +-(id(^)(id))m31:(id(^)(id))a; +-(void)m32:(id(^)(id))a; +-(int)m33:(id(^)(id))a; ++(void)m42:(TestRoot*(^)(TestRoot*))a; ++(int)m43:(TestRoot*(^)(TestRoot*))a; ++(TestRoot*(^)(TestRoot*))m41:(TestRoot*(^)(TestRoot*))a; +@end + +@protocol Proto6 +@optional ++(TestRoot*(^)(TestRoot*))n41:(TestRoot*(^)(TestRoot*))a; +@end + +@protocol ProtoEmpty +@end + +#define SwiftV1MangledName "_TtP6Module15SwiftV1Protocol_" + +__attribute__((objc_runtime_name(SwiftV1MangledName))) +@protocol SwiftV1Protocol +@end + +@interface Super : TestRoot @end +@implementation Super ++(id)proto1ClassMethod { return self; } +-(id)proto1InstanceMethod { return self; } +@end + +@interface SubNoProtocols : Super @end +@implementation SubNoProtocols @end + +@interface SuperNoProtocols : TestRoot @end +@implementation SuperNoProtocols +@end + +@interface SubProp : Super { int i; } @end +@implementation SubProp +@synthesize i; +@end + + +int main() +{ + Class cls; + Protocol * __unsafe_unretained *list; + Protocol *protocol, *empty; + struct objc_method_description desc2; + objc_property_t *proplist; + unsigned int count; + + protocol = @protocol(Proto3); + empty = @protocol(ProtoEmpty); + testassert(protocol); + testassert(empty); + + testassert(0 == strcmp(protocol_getName(protocol), "Proto3")); + testassert(0 == strcmp(protocol_getName(empty), "ProtoEmpty")); + + testassert(class_conformsToProtocol([Super class], @protocol(Proto1))); + testassert(!class_conformsToProtocol([SubProp class], @protocol(Proto1))); + testassert(class_conformsToProtocol([SubProp class], @protocol(Proto4))); + testassert(!class_conformsToProtocol([SubProp class], @protocol(Proto3))); + testassert(!class_conformsToProtocol([Super class], @protocol(Proto3))); + + testassert(!protocol_conformsToProtocol(@protocol(Proto1), @protocol(Proto2))); + testassert(protocol_conformsToProtocol(@protocol(Proto3), @protocol(Proto2))); + testassert(!protocol_conformsToProtocol(@protocol(Proto2), @protocol(Proto3))); + + testassert(protocol_isEqual(@protocol(Proto1), @protocol(Proto1))); + testassert(! protocol_isEqual(@protocol(Proto1), @protocol(Proto2))); + + desc2 = protocol_getMethodDescription(protocol, @selector(proto3InstanceMethod), YES, YES); + testassert(desc2.name && desc2.types); + testassert(desc2.name == @selector(proto3InstanceMethod)); + desc2 = protocol_getMethodDescription(protocol, @selector(proto3ClassMethod), YES, NO); + testassert(desc2.name && desc2.types); + testassert(desc2.name == @selector(proto3ClassMethod)); + desc2 = protocol_getMethodDescription(protocol, @selector(proto2ClassMethod), YES, NO); + testassert(desc2.name && desc2.types); + testassert(desc2.name == @selector(proto2ClassMethod)); + + desc2 = protocol_getMethodDescription(protocol, @selector(proto3ClassMethod), YES, YES); + testassert(!desc2.name && !desc2.types); + desc2 = protocol_getMethodDescription(protocol, @selector(proto3InstanceMethod), YES, NO); + testassert(!desc2.name && !desc2.types); + desc2 = protocol_getMethodDescription(empty, @selector(proto3ClassMethod), YES, YES); + testassert(!desc2.name && !desc2.types); + desc2 = protocol_getMethodDescription(empty, @selector(proto3InstanceMethod), YES, NO); + testassert(!desc2.name && !desc2.types); + + count = 100; + list = protocol_copyProtocolList(@protocol(Proto2), &count); + testassert(!list); + testassert(count == 0); + count = 100; + list = protocol_copyProtocolList(@protocol(Proto3), &count); + testassert(list); + testassert(count == 1); + testassert(protocol_isEqual(list[0], @protocol(Proto2))); + testassert(!list[1]); + free(list); + + count = 100; + cls = objc_getClass("Super"); + testassert(cls); + list = class_copyProtocolList(cls, &count); + testassert(list); + testassert(list[count] == NULL); + testassert(count == 1); + testassert(0 == strcmp(protocol_getName(list[0]), "Proto1")); + free(list); + + count = 100; + cls = objc_getClass("SuperNoProtocols"); + testassert(cls); + list = class_copyProtocolList(cls, &count); + testassert(!list); + testassert(count == 0); + + count = 100; + cls = objc_getClass("SubNoProtocols"); + testassert(cls); + list = class_copyProtocolList(cls, &count); + testassert(!list); + testassert(count == 0); + + + cls = objc_getClass("SuperNoProtocols"); + testassert(cls); + list = class_copyProtocolList(cls, NULL); + testassert(!list); + + cls = objc_getClass("Super"); + testassert(cls); + list = class_copyProtocolList(cls, NULL); + testassert(list); + free(list); + + count = 100; + list = class_copyProtocolList(NULL, &count); + testassert(!list); + testassert(count == 0); + + + // Check property added by protocol + cls = objc_getClass("SubProp"); + testassert(cls); + + count = 100; + list = class_copyProtocolList(cls, &count); + testassert(list); + testassert(count == 1); + testassert(0 == strcmp(protocol_getName(list[0]), "Proto4")); + testassert(list[1] == NULL); + free(list); + + count = 100; + proplist = class_copyPropertyList(cls, &count); + testassert(proplist); + testassert(count == 1); + testassert(0 == strcmp(property_getName(proplist[0]), "i")); + testassert(proplist[1] == NULL); + free(proplist); + + // Check extended type encodings + testassert(_protocol_getMethodTypeEncoding(@protocol(Proto5), @selector(DoesNotExist), true, true) == NULL); + testassert(_protocol_getMethodTypeEncoding(NULL, @selector(m11), true, true) == NULL); + testassert(_protocol_getMethodTypeEncoding(@protocol(Proto5), @selector(m11), true, false) == NULL); + testassert(_protocol_getMethodTypeEncoding(@protocol(Proto5), @selector(m11), false, false) == NULL); + testassert(_protocol_getMethodTypeEncoding(@protocol(Proto5), @selector(m11), false, true) == NULL); + testassert(_protocol_getMethodTypeEncoding(@protocol(Proto5), @selector(m21), true, true) == NULL); +#if __LP64__ + const char *types11 = "@24@0:8@\"\"16"; + const char *types12 = "v24@0:8@\"\"16"; + const char *types13 = "i24@0:8@\"\"16"; + const char *types21 = "@\"TestRoot\"24@0:8@\"TestRoot\"16"; + const char *types22 = "v24@0:8@\"TestRoot\"16"; + const char *types23 = "i24@0:8@\"TestRoot\"16"; + const char *types31 = "@?<@@?@>24@0:8@?<@\"\"@?@\"\">16"; + const char *types32 = "v24@0:8@?<@\"\"@?@\"\">16"; + const char *types33 = "i24@0:8@?<@\"\"@?@\"\">16"; + const char *types41 = "@?<@\"TestRoot\"@?@\"TestRoot\">24@0:8@?<@\"TestRoot\"@?@\"TestRoot\">16"; + const char *types42 = "v24@0:8@?<@\"TestRoot\"@?@\"TestRoot\">16"; + const char *types43 = "i24@0:8@?<@\"TestRoot\"@?@\"TestRoot\">16"; +#else + const char *types11 = "@12@0:4@\"\"8"; + const char *types12 = "v12@0:4@\"\"8"; + const char *types13 = "i12@0:4@\"\"8"; + const char *types21 = "@\"TestRoot\"12@0:4@\"TestRoot\"8"; + const char *types22 = "v12@0:4@\"TestRoot\"8"; + const char *types23 = "i12@0:4@\"TestRoot\"8"; + const char *types31 = "@?<@@?@>12@0:4@?<@\"\"@?@\"\">8"; + const char *types32 = "v12@0:4@?<@\"\"@?@\"\">8"; + const char *types33 = "i12@0:4@?<@\"\"@?@\"\">8"; + const char *types41 = "@?<@\"TestRoot\"@?@\"TestRoot\">12@0:4@?<@\"TestRoot\"@?@\"TestRoot\">8"; + const char *types42 = "v12@0:4@?<@\"TestRoot\"@?@\"TestRoot\">8"; + const char *types43 = "i12@0:4@?<@\"TestRoot\"@?@\"TestRoot\">8"; +#endif + + // Make sure some of Proto5's selectors are out of order rdar://10582325 + // These comparisons deliberately look weird because they determine the + // selector order on some architectures. + testassert(sel_registerName("m11:") > sel_registerName("m12:") || + sel_registerName("m21:") > sel_registerName("m22:") || + sel_registerName("m32:") < sel_registerName("m31:") || + sel_registerName("m42:") < sel_registerName("m41:") ); + + if (!_protocol_getMethodTypeEncoding(@protocol(Proto5), @selector(m11:), true, true)) { + fail("rdar://10492418 extended type encodings not present (is compiler old?)"); + } else { + testassert(0 == strcmp(_protocol_getMethodTypeEncoding(@protocol(Proto5), @selector(m11:), true, true), types11)); + testassert(0 == strcmp(_protocol_getMethodTypeEncoding(@protocol(Proto5), @selector(m12:), true, true), types12)); + testassert(0 == strcmp(_protocol_getMethodTypeEncoding(@protocol(Proto5), @selector(m13:), true, true), types13)); + testassert(0 == strcmp(_protocol_getMethodTypeEncoding(@protocol(Proto5), @selector(m21:), true, false), types21)); + testassert(0 == strcmp(_protocol_getMethodTypeEncoding(@protocol(Proto5), @selector(m22:), true, false), types22)); + testassert(0 == strcmp(_protocol_getMethodTypeEncoding(@protocol(Proto5), @selector(m23:), true, false), types23)); + testassert(0 == strcmp(_protocol_getMethodTypeEncoding(@protocol(Proto5), @selector(m31:), false, true), types31)); + testassert(0 == strcmp(_protocol_getMethodTypeEncoding(@protocol(Proto5), @selector(m32:), false, true), types32)); + testassert(0 == strcmp(_protocol_getMethodTypeEncoding(@protocol(Proto5), @selector(m33:), false, true), types33)); + testassert(0 == strcmp(_protocol_getMethodTypeEncoding(@protocol(Proto5), @selector(m41:), false, false), types41)); + testassert(0 == strcmp(_protocol_getMethodTypeEncoding(@protocol(Proto5), @selector(m42:), false, false), types42)); + testassert(0 == strcmp(_protocol_getMethodTypeEncoding(@protocol(Proto5), @selector(m43:), false, false), types43)); + + testassert(0 == strcmp(_protocol_getMethodTypeEncoding(@protocol(Proto6), @selector(n41:), false, false), types41)); + testassert(0 == strcmp(_protocol_getMethodTypeEncoding(@protocol(Proto6), @selector(m41:), false, false), types41)); + } + + testassert(@protocol(SwiftV1Protocol) == objc_getProtocol("Module.SwiftV1Protocol")); + testassert(@protocol(SwiftV1Protocol) == objc_getProtocol(SwiftV1MangledName)); + testassert(0 == strcmp(protocol_getName(@protocol(SwiftV1Protocol)), "Module.SwiftV1Protocol")); + testassert(!objc_getProtocol("SwiftV1Protocol")); + + succeed(__FILE__); +} diff --git a/test/protocol_copyMethodList.m b/test/protocol_copyMethodList.m new file mode 100644 index 0000000..2b5e089 --- /dev/null +++ b/test/protocol_copyMethodList.m @@ -0,0 +1,154 @@ +// TEST_CFLAGS -framework Foundation +// need Foundation to get NSObject compatibility additions for class Protocol +// because ARC calls [protocol retain] +/* +TEST_BUILD_OUTPUT +.*protocol_copyMethodList.m:\d+:\d+: warning: null passed to a callee that requires a non-null argument \[-Wnonnull\] +.*protocol_copyMethodList.m:\d+:\d+: warning: null passed to a callee that requires a non-null argument \[-Wnonnull\] +.*protocol_copyMethodList.m:\d+:\d+: warning: null passed to a callee that requires a non-null argument \[-Wnonnull\] +.*protocol_copyMethodList.m:\d+:\d+: warning: null passed to a callee that requires a non-null argument \[-Wnonnull\] +END +*/ + + +#include "test.h" +#include +#include + +@protocol SuperMethods ++(void)SuperMethodClass; ++(void)SuperMethodClass2; +-(void)SuperMethodInstance; +-(void)SuperMethodInstance2; +@end + +@protocol SubMethods ++(void)SubMethodClass; ++(void)SubMethodClass2; +-(void)SubMethodInstance; +-(void)SubMethodInstance2; +@end + +@protocol SuperOptionalMethods +@optional ++(void)SuperOptMethodClass; ++(void)SuperOptMethodClass2; +-(void)SuperOptMethodInstance; +-(void)SuperOptMethodInstance2; +@end + +@protocol SubOptionalMethods +@optional ++(void)SubOptMethodClass; ++(void)SubOptMethodClass2; +-(void)SubOptMethodInstance; +-(void)SubOptMethodInstance2; +@end + +@protocol NoMethods @end + +static int isNamed(struct objc_method_description m, const char *name) +{ + return (m.name == sel_registerName(name)); +} + +int main() +{ + struct objc_method_description *methods; + unsigned int count; + Protocol *proto; + + proto = @protocol(SubMethods); + testassert(proto); + + // Check required methods + count = 999; + methods = protocol_copyMethodDescriptionList(proto, YES, YES, &count); + testassert(methods); + testassert(count == 2); + testassert((isNamed(methods[0], "SubMethodInstance") && + isNamed(methods[1], "SubMethodInstance2")) + || + (isNamed(methods[1], "SubMethodInstance") && + isNamed(methods[0], "SubMethodInstance2"))); + free(methods); + + count = 999; + methods = protocol_copyMethodDescriptionList(proto, YES, NO, &count); + testassert(methods); + testassert(count == 2); + testassert((isNamed(methods[0], "SubMethodClass") && + isNamed(methods[1], "SubMethodClass2")) + || + (isNamed(methods[1], "SubMethodClass") && + isNamed(methods[0], "SubMethodClass2"))); + free(methods); + + // Check lack of optional methods + count = 999; + methods = protocol_copyMethodDescriptionList(proto, NO, YES, &count); + testassert(!methods); + testassert(count == 0); + count = 999; + methods = protocol_copyMethodDescriptionList(proto, NO, NO, &count); + testassert(!methods); + testassert(count == 0); + + + proto = @protocol(SubOptionalMethods); + testassert(proto); + + // Check optional methods + count = 999; + methods = protocol_copyMethodDescriptionList(proto, NO, YES, &count); + testassert(methods); + testassert(count == 2); + testassert((isNamed(methods[0], "SubOptMethodInstance") && + isNamed(methods[1], "SubOptMethodInstance2")) + || + (isNamed(methods[1], "SubOptMethodInstance") && + isNamed(methods[0], "SubOptMethodInstance2"))); + free(methods); + + count = 999; + methods = protocol_copyMethodDescriptionList(proto, NO, NO, &count); + testassert(methods); + testassert(count == 2); + testassert((isNamed(methods[0], "SubOptMethodClass") && + isNamed(methods[1], "SubOptMethodClass2")) + || + (isNamed(methods[1], "SubOptMethodClass") && + isNamed(methods[0], "SubOptMethodClass2"))); + free(methods); + + // Check lack of required methods + count = 999; + methods = protocol_copyMethodDescriptionList(proto, YES, YES, &count); + testassert(!methods); + testassert(count == 0); + count = 999; + methods = protocol_copyMethodDescriptionList(proto, YES, NO, &count); + testassert(!methods); + testassert(count == 0); + + + // Check NULL protocol parameter + count = 999; + methods = protocol_copyMethodDescriptionList(NULL, YES, YES, &count); + testassert(!methods); + testassert(count == 0); + count = 999; + methods = protocol_copyMethodDescriptionList(NULL, YES, NO, &count); + testassert(!methods); + testassert(count == 0); + count = 999; + methods = protocol_copyMethodDescriptionList(NULL, NO, YES, &count); + testassert(!methods); + testassert(count == 0); + count = 999; + methods = protocol_copyMethodDescriptionList(NULL, NO, NO, &count); + testassert(!methods); + testassert(count == 0); + + succeed(__FILE__); +} diff --git a/test/protocol_copyPropertyList.m b/test/protocol_copyPropertyList.m new file mode 100644 index 0000000..a9ecd22 --- /dev/null +++ b/test/protocol_copyPropertyList.m @@ -0,0 +1,207 @@ +// TEST_CFLAGS -framework Foundation +// need Foundation to get NSObject compatibility additions for class Protocol +// because ARC calls [protocol retain] +/* +TEST_BUILD_OUTPUT +.*protocol_copyPropertyList.m:\d+:\d+: warning: null passed to a callee that requires a non-null argument \[-Wnonnull\] +.*protocol_copyPropertyList.m:\d+:\d+: warning: null passed to a callee that requires a non-null argument \[-Wnonnull\] +.*protocol_copyPropertyList.m:\d+:\d+: warning: null passed to a callee that requires a non-null argument \[-Wnonnull\] +.*protocol_copyPropertyList.m:\d+:\d+: warning: null passed to a callee that requires a non-null argument \[-Wnonnull\] +END +*/ + +#include "test.h" +#include +#include +#include + +@protocol SuperProps +@property int prop1; +@property int prop2; +@property(class) int prop1; +@property(class) int prop2; +@end + +@protocol SubProps +@property int prop3; +@property int prop4; +@property(class) int prop3; +@property(class) int prop4; +@end + + +@protocol FourProps +@property int prop1; +@property int prop2; +@property int prop3; +@property int prop4; + +@property(class) int prop1; +@property(class) int prop2; +@property(class) int prop3; +@property(class) int prop4; +@end + +@protocol NoProps @end + +@protocol OneProp +@property int instanceProp; +@property(class) int classProp; +@end + + +static int isNamed(objc_property_t p, const char *name) +{ + return (0 == strcmp(name, property_getName(p))); +} + +void testfn(objc_property_t *(*copyPropertyList_fn)(Protocol*, unsigned int *), + const char *onePropName) +{ + objc_property_t *props; + unsigned int count; + Protocol *proto; + + proto = @protocol(SubProps); + testassert(proto); + + count = 100; + props = copyPropertyList_fn(proto, &count); + testassert(props); + testassert(count == 2); + testassert((isNamed(props[0], "prop4") && isNamed(props[1], "prop3")) || + (isNamed(props[0], "prop3") && isNamed(props[1], "prop4"))); + // props[] should be null-terminated + testassert(props[2] == NULL); + free(props); + + proto = @protocol(SuperProps); + testassert(proto); + + count = 100; + props = copyPropertyList_fn(proto, &count); + testassert(props); + testassert(count == 2); + testassert((isNamed(props[0], "prop1") && isNamed(props[1], "prop2")) || + (isNamed(props[0], "prop2") && isNamed(props[1], "prop1"))); + // props[] should be null-terminated + testassert(props[2] == NULL); + free(props); + + // Check null-termination - this property list block would be 16 bytes + // if it weren't for the terminator + proto = @protocol(FourProps); + testassert(proto); + + count = 100; + props = copyPropertyList_fn(proto, &count); + testassert(props); + testassert(count == 4); + testassert(malloc_size(props) >= 5 * sizeof(objc_property_t)); + testassert(props[3] != NULL); + testassert(props[4] == NULL); + free(props); + + // Check NULL count parameter + props = copyPropertyList_fn(proto, NULL); + testassert(props); + testassert(props[4] == NULL); + testassert(props[3] != NULL); + free(props); + + // Check NULL protocol parameter + count = 100; + props = copyPropertyList_fn(NULL, &count); + testassert(!props); + testassert(count == 0); + + // Check NULL protocol and count + props = copyPropertyList_fn(NULL, NULL); + testassert(!props); + + // Check protocol with no properties + proto = @protocol(NoProps); + testassert(proto); + + count = 100; + props = copyPropertyList_fn(proto, &count); + testassert(!props); + testassert(count == 0); + + // Check instance vs class properties + proto = @protocol(OneProp); + testassert(proto); + + count = 100; + props = copyPropertyList_fn(proto, &count); + testassert(props); + testassert(count == 1); + testassert(0 == strcmp(property_getName(props[0]), onePropName)); + free(props); +} + +objc_property_t *protocol_copyPropertyList2_YES_YES(Protocol *proto, unsigned int *outCount) +{ + return protocol_copyPropertyList2(proto, outCount, YES, YES); +} + +objc_property_t *protocol_copyPropertyList2_YES_NO(Protocol *proto, unsigned int *outCount) +{ + return protocol_copyPropertyList2(proto, outCount, YES, NO); +} + +int main() +{ + // protocol_copyPropertyList(...) is identical to + // protocol_copyPropertyList2(..., YES, YES) + testfn(protocol_copyPropertyList, "instanceProp"); + testfn(protocol_copyPropertyList2_YES_YES, "instanceProp"); + + // protocol_copyPropertyList2(..., YES, NO) is also identical + // with the protocol definitions above, except for protocol OneProp. + testfn(protocol_copyPropertyList2_YES_NO, "classProp"); + + // Check non-functionality of optional properties + + unsigned int count; + objc_property_t *props; + + count = 100; + props = protocol_copyPropertyList2(@protocol(FourProps), &count, NO, YES); + testassert(!props); + testassert(count == 0); + + count = 100; + props = protocol_copyPropertyList2(@protocol(FourProps), &count, NO, NO); + testassert(!props); + testassert(count == 0); + + // Check nil count parameter + props = protocol_copyPropertyList2(@protocol(FourProps), nil, NO, YES); + testassert(!props); + + props = protocol_copyPropertyList2(@protocol(FourProps), nil, NO, NO); + testassert(!props); + + // Check nil protocol parameter + count = 100; + props = protocol_copyPropertyList2(nil, &count, NO, YES); + testassert(!props); + testassert(count == 0); + + count = 100; + props = protocol_copyPropertyList2(nil, &count, NO, NO); + testassert(!props); + testassert(count == 0); + + // Check nil protocol and count + props = protocol_copyPropertyList2(nil, nil, NO, YES); + testassert(!props); + + props = protocol_copyPropertyList2(nil, nil, NO, NO); + testassert(!props); + + + succeed(__FILE__); + return 0; +} diff --git a/test/rawisa.m b/test/rawisa.m new file mode 100644 index 0000000..4e30cb3 --- /dev/null +++ b/test/rawisa.m @@ -0,0 +1,30 @@ +/* +TEST_CFLAGS -Xlinker -sectcreate -Xlinker __DATA -Xlinker __objc_rawisa -Xlinker /dev/null +TEST_ENV OBJC_PRINT_RAW_ISA=YES + +TEST_RUN_OUTPUT +objc\[\d+\]: RAW ISA: disabling non-pointer isa because the app has a __DATA,__objc_rawisa section +(.* RAW ISA: .*\n)* +OK: rawisa.m(\n.* RAW ISA: .*)* +OR +(.* RAW ISA: .*\n)* +no __DATA,__rawisa support +OK: rawisa.m(\n.* RAW ISA: .*)* +END + +"RAW ISA" is allowed after "OK" because of static destructors +that provoke class realization. +*/ + +#include "test.h" + +int main() +{ + fprintf(stderr, "\n"); +#if ! (SUPPORT_NONPOINTER_ISA && TARGET_OS_OSX) + // only 64-bit Mac supports this + fprintf(stderr, "no __DATA,__rawisa support\n"); +#endif + succeed(__FILE__); +} + diff --git a/test/readClassPair.m b/test/readClassPair.m new file mode 100644 index 0000000..80313b2 --- /dev/null +++ b/test/readClassPair.m @@ -0,0 +1,82 @@ +/* +TEST_RUN_OUTPUT +objc\[\d+\]: Class Sub is implemented in both [^\s]+ \(0x[0-9a-f]+\) and [^\s]+ \(0x[0-9a-f]+\)\. One of the two will be used\. Which one is undefined\. +OK: readClassPair.m +END + */ + +#include "test.h" +#include + +// Reuse evil-class-def.m as a non-evil class definition. + +#define EVIL_SUPER 0 +#define EVIL_SUPER_META 0 +#define EVIL_SUB 0 +#define EVIL_SUB_META 0 + +#define OMIT_SUPER 1 +#define OMIT_NL_SUPER 1 +#define OMIT_SUB 1 +#define OMIT_NL_SUB 1 + +#include "evil-class-def.m" + +int main() +{ + // This definition is ABI and is never allowed to change. + testassert(OBJC_MAX_CLASS_SIZE == 32*sizeof(void*)); + + struct objc_image_info ii = { 0, 0 }; + + // Read a root class. + testassert(!objc_getClass("Super")); + + extern intptr_t OBJC_CLASS_$_Super[OBJC_MAX_CLASS_SIZE/sizeof(void*)]; + Class Super = objc_readClassPair((__bridge Class)(void*)&OBJC_CLASS_$_Super, &ii); + testassert(Super); + + testassert(objc_getClass("Super") == Super); + testassert(0 == strcmp(class_getName(Super), "Super")); + testassert(class_getSuperclass(Super) == nil); + testassert(class_getClassMethod(Super, @selector(load))); + testassert(class_getInstanceMethod(Super, @selector(load))); + testassert(class_getInstanceVariable(Super, "super_ivar")); + testassert(class_getInstanceSize(Super) == sizeof(void*)); + [Super load]; + + // Read a non-root class. + testassert(!objc_getClass("Sub")); + + extern intptr_t OBJC_CLASS_$_Sub[OBJC_MAX_CLASS_SIZE/sizeof(void*)]; + // Make a duplicate of class Sub for use later. + intptr_t Sub2_buf[OBJC_MAX_CLASS_SIZE/sizeof(void*)]; + memcpy(Sub2_buf, &OBJC_CLASS_$_Sub, sizeof(Sub2_buf)); + Class Sub = objc_readClassPair((__bridge Class)(void*)&OBJC_CLASS_$_Sub, &ii); + testassert(Sub); + + testassert(0 == strcmp(class_getName(Sub), "Sub")); + testassert(objc_getClass("Sub") == Sub); + testassert(class_getSuperclass(Sub) == Super); + testassert(class_getClassMethod(Sub, @selector(load))); + testassert(class_getInstanceMethod(Sub, @selector(load))); + testassert(class_getInstanceVariable(Sub, "sub_ivar")); + testassert(class_getInstanceSize(Sub) == 2*sizeof(void*)); + [Sub load]; + + // Reading a class whose name already exists succeeds + // with a duplicate warning. + Class Sub2 = objc_readClassPair((__bridge Class)(void*)Sub2_buf, &ii); + testassert(Sub2); + testassert(Sub2 != Sub); + testassert(objc_getClass("Sub") == Sub); // didn't change + testassert(0 == strcmp(class_getName(Sub2), "Sub")); + testassert(class_getSuperclass(Sub2) == Super); + testassert(class_getClassMethod(Sub2, @selector(load))); + testassert(class_getInstanceMethod(Sub2, @selector(load))); + testassert(class_getInstanceVariable(Sub2, "sub_ivar")); + testassert(class_getInstanceSize(Sub2) == 2*sizeof(void*)); + [Sub2 load]; + + succeed(__FILE__); +} diff --git a/test/release-workaround.m b/test/release-workaround.m new file mode 100644 index 0000000..5e3bfa3 --- /dev/null +++ b/test/release-workaround.m @@ -0,0 +1,34 @@ +// TEST_CONFIG ARCH=x86_64 MEM=mrc +// TEST_CFLAGS -framework Foundation + +// rdar://20206767 + +#include +#include "test.h" + + +@interface Test : NSObject @end +@implementation Test +@end + + +int main() +{ + id buf[1]; + buf[0] = [Test class]; + id obj = (id)buf; + [obj retain]; + [obj retain]; + + uintptr_t rax; + + [obj release]; + asm("mov %%rax, %0" : "=r" (rax)); + testassert(rax == 0); + + objc_release(obj); + asm("mov %%rax, %0" : "=r" (rax)); + testassert(rax == 0); + + succeed(__FILE__); +} diff --git a/test/resolve.m b/test/resolve.m new file mode 100644 index 0000000..913a686 --- /dev/null +++ b/test/resolve.m @@ -0,0 +1,298 @@ +/* resolve.m + * Test +resolveClassMethod: and +resolveInstanceMethod: + */ + +// TEST_CFLAGS -Wno-deprecated-declarations + +#include "test.h" +#include "testroot.i" +#include +#include +#include + +#if __has_feature(objc_arc) + +int main() +{ + testwarn("rdar://11368528 confused by Foundation"); + succeed(__FILE__); +} + +#else + +static int state = 0; + +@interface Super : TestRoot @end +@interface Sub : Super @end + + +@implementation Super ++(void)initialize { + if (self == [Super class]) { + testassert(state == 1); + state = 2; + } +} +@end + +static id forward_handler(id self, SEL sel) +{ + if (class_isMetaClass(object_getClass(self))) { + // self is a class object + if (sel == @selector(missingClassMethod)) { + testassert(state == 21 || state == 25 || state == 80); + if (state == 21) state = 22; + if (state == 25) state = 26; + if (state == 80) state = 81;; + return nil; + } else if (sel == @selector(lyingClassMethod)) { + testassert(state == 31 || state == 35); + if (state == 31) state = 32; + if (state == 35) state = 36; + return nil; + } + fail("+forward:: shouldn't be called with sel %s", sel_getName(sel)); + return nil; + } + else { + // self is not a class object + if (sel == @selector(missingInstanceMethod)) { + testassert(state == 61 || state == 65); + if (state == 61) state = 62; + if (state == 65) state = 66; + return nil; + } else if (sel == @selector(lyingInstanceMethod)) { + testassert(state == 71 || state == 75); + if (state == 71) state = 72; + if (state == 75) state = 76; + return nil; + } + fail("-forward:: shouldn't be called with sel %s", sel_getName(sel)); + return nil; + } +} + + +static id classMethod_c(id __unused self, SEL __unused sel) +{ + testassert(state == 4 || state == 10); + if (state == 4) state = 5; + if (state == 10) state = 11; + return [Super class]; +} + +static id instanceMethod_c(id __unused self, SEL __unused sel) +{ + testassert(state == 41 || state == 50); + if (state == 41) state = 42; + if (state == 50) state = 51; + return [Sub class]; +} + + +@implementation Sub + ++(void)method2 { } ++(void)method3 { } ++(void)method4 { } ++(void)method5 { } + ++(void)initialize { + if (self == [Sub class]) { + testassert(state == 2); + state = 3; + } +} + ++(BOOL)resolveClassMethod:(SEL)sel +{ + if (sel == @selector(classMethod)) { + testassert(state == 3); + state = 4; + class_addMethod(object_getClass(self), sel, (IMP)&classMethod_c, ""); + return YES; + } else if (sel == @selector(missingClassMethod)) { + testassert(state == 20); + state = 21; + return NO; + } else if (sel == @selector(lyingClassMethod)) { + testassert(state == 30); + state = 31; + return YES; // lie + } else { + fail("+resolveClassMethod: called incorrectly (sel %s)", + sel_getName(sel)); + return NO; + } +} + ++(BOOL)resolveInstanceMethod:(SEL)sel +{ + if (sel == @selector(instanceMethod)) { + testassert(state == 40); + state = 41; + class_addMethod(self, sel, (IMP)instanceMethod_c, ""); + return YES; + } else if (sel == @selector(missingInstanceMethod)) { + testassert(state == 60); + state = 61; + return NO; + } else if (sel == @selector(lyingInstanceMethod)) { + testassert(state == 70); + state = 71; + return YES; // lie + } else { + fail("+resolveInstanceMethod: called incorrectly (sel %s)", + sel_getName(sel)); + return NO; + } +} + +@end + +@interface Super (MissingMethods) ++(id)missingClassMethod; +@end + +@interface Sub (ResolvedMethods) ++(id)classMethod; +-(id)instanceMethod; ++(id)missingClassMethod; +-(id)missingInstanceMethod; ++(id)lyingClassMethod; +-(id)lyingInstanceMethod; +@end + + +int main() +{ + Sub *s; + id ret; + + objc_setForwardHandler((void*)&forward_handler, (void*)&abort); + + // Be ready for ARC to retain the class object and call +initialize early + state = 1; + + Class dup = objc_duplicateClass(objc_getClass("Sub"), "Sub_copy", 0); + + // Resolve a class method + // +initialize should fire first (if it hasn't already) + ret = [Sub classMethod]; + testassert(state == 5); + testassert(ret == [Super class]); + + // Call it again, cached + // Resolver shouldn't be called again. + state = 10; + ret = [Sub classMethod]; + testassert(state == 11); + testassert(ret == [Super class]); + + _objc_flush_caches(object_getClass([Sub class])); + + // Call a method that won't get resolved + state = 20; + ret = [Sub missingClassMethod]; + testassert(state == 22); + testassert(ret == nil); + + // Call it again, cached + // Resolver shouldn't be called again. + state = 25; + ret = [Sub missingClassMethod]; + testassert(state == 26); + testassert(ret == nil); + + _objc_flush_caches(object_getClass([Sub class])); + + // Call a method that won't get resolved but the resolver lies about it + state = 30; + ret = [Sub lyingClassMethod]; + testassert(state == 32); + testassert(ret == nil); + + // Call it again, cached + // Resolver shouldn't be called again. + state = 35; + ret = [Sub lyingClassMethod]; + testassert(state == 36); + testassert(ret == nil); + + _objc_flush_caches(object_getClass([Sub class])); + + + // Resolve an instance method + s = [Sub new]; + state = 40; + ret = [s instanceMethod]; + testassert(state == 42); + testassert(ret == [Sub class]); + + // Call it again, cached + // Resolver shouldn't be called again. + state = 50; + ret = [s instanceMethod]; + testassert(state == 51); + testassert(ret == [Sub class]); + + _objc_flush_caches([Sub class]); + + // Call a method that won't get resolved + state = 60; + ret = [s missingInstanceMethod]; + testassert(state == 62); + testassert(ret == nil); + + // Call it again, cached + // Resolver shouldn't be called again. + state = 65; + ret = [s missingInstanceMethod]; + testassert(state == 66); + testassert(ret == nil); + + _objc_flush_caches([Sub class]); + + // Call a method that won't get resolved but the resolver lies about it + state = 70; + ret = [s lyingInstanceMethod]; + testassert(state == 72); + testassert(ret == nil); + + // Call it again, cached + // Resolver shouldn't be called again. + state = 75; + ret = [s lyingInstanceMethod]; + testassert(state == 76); + testassert(ret == nil); + + _objc_flush_caches([Sub class]); + + // Call a missing method on a class that doesn't support resolving + state = 80; + ret = [Super missingClassMethod]; + testassert(state == 81); + testassert(ret == nil); + RELEASE_VAR(s); + + // Resolve an instance method on a class duplicated before resolving + s = [dup new]; + state = 40; + ret = [s instanceMethod]; + testassert(state == 42); + testassert(ret == [Sub class]); + + // Call it again, cached + // Resolver shouldn't be called again. + state = 50; + ret = [s instanceMethod]; + testassert(state == 51); + testassert(ret == [Sub class]); + RELEASE_VAR(s); + + succeed(__FILE__); + return 0; +} + +#endif + diff --git a/test/rr-autorelease-fast.m b/test/rr-autorelease-fast.m new file mode 100644 index 0000000..0de2b2b --- /dev/null +++ b/test/rr-autorelease-fast.m @@ -0,0 +1,357 @@ +// TEST_CONFIG MEM=mrc +// TEST_CFLAGS -Os + +#include "test.h" +#include "testroot.i" + +#include +#include +#include + +@interface TestObject : TestRoot @end +@implementation TestObject @end + + +// MAGIC and NOT_MAGIC each call two functions +// with or without the magic instruction sequence, respectively. +// +// tmp = first(obj); +// magic, or not; +// tmp = second(tmp); + +#if __arm__ + +#define NOT_MAGIC(first, second) \ + tmp = first(obj); \ + asm volatile("mov r8, r8"); \ + tmp = second(tmp); + +#define MAGIC(first, second) \ + tmp = first(obj); \ + asm volatile("mov r7, r7"); \ + tmp = second(tmp); + +// arm +#elif __arm64__ + +#define NOT_MAGIC(first, second) \ + tmp = first(obj); \ + asm volatile("mov x28, x28"); \ + tmp = second(tmp); + +#define MAGIC(first, second) \ + tmp = first(obj); \ + asm volatile("mov x29, x29"); \ + tmp = second(tmp); + +// arm64 +#elif __x86_64__ + +#define NOT_MAGIC(first, second) \ + tmp = first(obj); \ + asm volatile("nop"); \ + tmp = second(tmp); + +#define MAGIC(first, second) \ + tmp = first(obj); \ + tmp = second(tmp); + +// x86_64 +#elif __i386__ + +#define NOT_MAGIC(first, second) \ + tmp = first(obj); \ + tmp = second(tmp); + +#define MAGIC(first, second) \ + asm volatile("\n subl $16, %%esp" \ + "\n movl %[obj], (%%esp)" \ + "\n call _" #first \ + "\n" \ + "\n movl %%ebp, %%ebp" \ + "\n" \ + "\n movl %%eax, (%%esp)" \ + "\n call _" #second \ + "\n movl %%eax, %[tmp]" \ + "\n addl $16, %%esp" \ + : [tmp] "=r" (tmp) \ + : [obj] "r" (obj) \ + : "eax", "edx", "ecx", "cc", "memory") + +// i386 +#else + +#error unknown architecture + +#endif + + +int +main() +{ + TestObject *tmp, *obj; + +#ifdef __x86_64__ + // need to get DYLD to resolve the stubs on x86 + PUSH_POOL { + TestObject *warm_up = [[TestObject alloc] init]; + testassert(warm_up); + warm_up = objc_retainAutoreleasedReturnValue(warm_up); + warm_up = objc_unsafeClaimAutoreleasedReturnValue(warm_up); + [warm_up release]; + warm_up = nil; + } POP_POOL; +#endif + + testprintf(" Successful +1 -> +1 handshake\n"); + + PUSH_POOL { + obj = [[TestObject alloc] init]; + testassert(obj); + + TestRootRetain = 0; + TestRootRelease = 0; + TestRootAutorelease = 0; + TestRootDealloc = 0; + + MAGIC(objc_autoreleaseReturnValue, + objc_retainAutoreleasedReturnValue); + + testassert(TestRootDealloc == 0); + testassert(TestRootRetain == 0); + testassert(TestRootRelease == 0); + testassert(TestRootAutorelease == 0); + + [tmp release]; + testassert(TestRootDealloc == 1); + testassert(TestRootRetain == 0); + testassert(TestRootRelease == 1); + testassert(TestRootAutorelease == 0); + + } POP_POOL; + + testprintf("Unsuccessful +1 -> +1 handshake\n"); + + PUSH_POOL { + obj = [[TestObject alloc] init]; + testassert(obj); + + TestRootRetain = 0; + TestRootRelease = 0; + TestRootAutorelease = 0; + TestRootDealloc = 0; + + NOT_MAGIC(objc_autoreleaseReturnValue, + objc_retainAutoreleasedReturnValue); + + testassert(TestRootDealloc == 0); + testassert(TestRootRetain == 1); + testassert(TestRootRelease == 0); + testassert(TestRootAutorelease == 1); + + [tmp release]; + testassert(TestRootDealloc == 0); + testassert(TestRootRetain == 1); + testassert(TestRootRelease == 1); + testassert(TestRootAutorelease == 1); + + } POP_POOL; + testassert(TestRootDealloc == 1); + testassert(TestRootRetain == 1); + testassert(TestRootRelease == 2); + testassert(TestRootAutorelease == 1); + + + testprintf(" Successful +0 -> +1 handshake\n"); + + PUSH_POOL { + obj = [[TestObject alloc] init]; + testassert(obj); + + TestRootRetain = 0; + TestRootRelease = 0; + TestRootAutorelease = 0; + TestRootDealloc = 0; + + MAGIC(objc_retainAutoreleaseReturnValue, + objc_retainAutoreleasedReturnValue); + + testassert(TestRootDealloc == 0); + testassert(TestRootRetain == 1); + testassert(TestRootRelease == 0); + testassert(TestRootAutorelease == 0); + + [tmp release]; + testassert(TestRootDealloc == 0); + testassert(TestRootRetain == 1); + testassert(TestRootRelease == 1); + testassert(TestRootAutorelease == 0); + + [tmp release]; + testassert(TestRootDealloc == 1); + testassert(TestRootRetain == 1); + testassert(TestRootRelease == 2); + testassert(TestRootAutorelease == 0); + + } POP_POOL; + + testprintf("Unsuccessful +0 -> +1 handshake\n"); + + PUSH_POOL { + obj = [[TestObject alloc] init]; + testassert(obj); + + TestRootRetain = 0; + TestRootRelease = 0; + TestRootAutorelease = 0; + TestRootDealloc = 0; + + NOT_MAGIC(objc_retainAutoreleaseReturnValue, + objc_retainAutoreleasedReturnValue); + + testassert(TestRootDealloc == 0); + testassert(TestRootRetain == 2); + testassert(TestRootRelease == 0); + testassert(TestRootAutorelease == 1); + + [tmp release]; + testassert(TestRootDealloc == 0); + testassert(TestRootRetain == 2); + testassert(TestRootRelease == 1); + testassert(TestRootAutorelease == 1); + + [tmp release]; + testassert(TestRootDealloc == 0); + testassert(TestRootRetain == 2); + testassert(TestRootRelease == 2); + testassert(TestRootAutorelease == 1); + + } POP_POOL; + testassert(TestRootDealloc == 1); + testassert(TestRootRetain == 2); + testassert(TestRootRelease == 3); + testassert(TestRootAutorelease == 1); + + + testprintf(" Successful +1 -> +0 handshake\n"); + + PUSH_POOL { + obj = [[[TestObject alloc] init] retain]; + testassert(obj); + + TestRootRetain = 0; + TestRootRelease = 0; + TestRootAutorelease = 0; + TestRootDealloc = 0; + + MAGIC(objc_autoreleaseReturnValue, + objc_unsafeClaimAutoreleasedReturnValue); + + testassert(TestRootDealloc == 0); + testassert(TestRootRetain == 0); + testassert(TestRootRelease == 1); + testassert(TestRootAutorelease == 0); + + [tmp release]; + testassert(TestRootDealloc == 1); + testassert(TestRootRetain == 0); + testassert(TestRootRelease == 2); + testassert(TestRootAutorelease == 0); + + } POP_POOL; + + testprintf("Unsuccessful +1 -> +0 handshake\n"); + + PUSH_POOL { + obj = [[[TestObject alloc] init] retain]; + testassert(obj); + + TestRootRetain = 0; + TestRootRelease = 0; + TestRootAutorelease = 0; + TestRootDealloc = 0; + + NOT_MAGIC(objc_autoreleaseReturnValue, + objc_unsafeClaimAutoreleasedReturnValue); + + testassert(TestRootDealloc == 0); + testassert(TestRootRetain == 0); + testassert(TestRootRelease == 0); + testassert(TestRootAutorelease == 1); + + [tmp release]; + testassert(TestRootDealloc == 0); + testassert(TestRootRetain == 0); + testassert(TestRootRelease == 1); + testassert(TestRootAutorelease == 1); + + } POP_POOL; + testassert(TestRootDealloc == 1); + testassert(TestRootRetain == 0); + testassert(TestRootRelease == 2); + testassert(TestRootAutorelease == 1); + + + testprintf(" Successful +0 -> +0 handshake\n"); + + PUSH_POOL { + obj = [[TestObject alloc] init]; + testassert(obj); + + TestRootRetain = 0; + TestRootRelease = 0; + TestRootAutorelease = 0; + TestRootDealloc = 0; + + MAGIC(objc_retainAutoreleaseReturnValue, + objc_unsafeClaimAutoreleasedReturnValue); + + testassert(TestRootDealloc == 0); + testassert(TestRootRetain == 0); + testassert(TestRootRelease == 0); + testassert(TestRootAutorelease == 0); + + [tmp release]; + testassert(TestRootDealloc == 1); + testassert(TestRootRetain == 0); + testassert(TestRootRelease == 1); + testassert(TestRootAutorelease == 0); + + } POP_POOL; + + testprintf("Unsuccessful +0 -> +0 handshake\n"); + + PUSH_POOL { + obj = [[TestObject alloc] init]; + testassert(obj); + + TestRootRetain = 0; + TestRootRelease = 0; + TestRootAutorelease = 0; + TestRootDealloc = 0; + + NOT_MAGIC(objc_retainAutoreleaseReturnValue, + objc_unsafeClaimAutoreleasedReturnValue); + + testassert(TestRootDealloc == 0); + testassert(TestRootRetain == 1); + testassert(TestRootRelease == 0); + testassert(TestRootAutorelease == 1); + + [tmp release]; + testassert(TestRootDealloc == 0); + testassert(TestRootRetain == 1); + testassert(TestRootRelease == 1); + testassert(TestRootAutorelease == 1); + + } POP_POOL; + testassert(TestRootDealloc == 1); + testassert(TestRootRetain == 1); + testassert(TestRootRelease == 2); + testassert(TestRootAutorelease == 1); + + succeed(__FILE__); + + return 0; +} + diff --git a/test/rr-autorelease-fastarc.m b/test/rr-autorelease-fastarc.m new file mode 100644 index 0000000..da536f1 --- /dev/null +++ b/test/rr-autorelease-fastarc.m @@ -0,0 +1,204 @@ +// TEST_CFLAGS -Os -framework Foundation +// TEST_DISABLED pending clang support for rdar://20530049 + +#include "test.h" +#include "testroot.i" + +#include +#include +#include + +@interface TestObject : TestRoot @end +@implementation TestObject @end + +@interface Tester : NSObject @end +@implementation Tester { +@public + id ivar; +} + +-(id) return0 { + return ivar; +} +-(id) return1 { + id x = ivar; + [x self]; + return x; +} + +@end + +OBJC_EXPORT +id +objc_retainAutoreleasedReturnValue(id obj); + +// Accept a value returned through a +0 autoreleasing convention for use at +0. +OBJC_EXPORT +id +objc_unsafeClaimAutoreleasedReturnValue(id obj); + + +int +main() +{ + TestObject *obj; + Tester *tt = [Tester new]; + +#ifdef __x86_64__ + // need to get DYLD to resolve the stubs on x86 + PUSH_POOL { + TestObject *warm_up = [[TestObject alloc] init]; + testassert(warm_up); + warm_up = objc_retainAutoreleasedReturnValue(warm_up); + warm_up = objc_unsafeClaimAutoreleasedReturnValue(warm_up); + warm_up = nil; + } POP_POOL; +#endif + + testprintf(" Successful +1 -> +1 handshake\n"); + + PUSH_POOL { + obj = [[TestObject alloc] init]; + testassert(obj); + tt->ivar = obj; + obj = nil; + + TestRootRetain = 0; + TestRootRelease = 0; + TestRootAutorelease = 0; + TestRootDealloc = 0; + + TestObject *tmp = [tt return1]; + + testassert(TestRootDealloc == 0); + testassert(TestRootRetain == 1); + testassert(TestRootRelease == 0); + testassert(TestRootAutorelease == 0); + + tt->ivar = nil; + testassert(TestRootDealloc == 0); + testassert(TestRootRetain == 1); + testassert(TestRootRelease == 1); + testassert(TestRootAutorelease == 0); + + tmp = nil; + testassert(TestRootDealloc == 1); + testassert(TestRootRetain == 1); + testassert(TestRootRelease == 2); + testassert(TestRootAutorelease == 0); + + } POP_POOL; + + testprintf(" Successful +0 -> +0 handshake\n"); + + PUSH_POOL { + obj = [[TestObject alloc] init]; + testassert(obj); + tt->ivar = obj; + obj = nil; + + TestRootRetain = 0; + TestRootRelease = 0; + TestRootAutorelease = 0; + TestRootDealloc = 0; + + __unsafe_unretained TestObject *tmp = [tt return0]; + + testassert(TestRootDealloc == 0); + testassert(TestRootRetain == 0); + testassert(TestRootRelease == 0); + testassert(TestRootAutorelease == 0); + + tmp = nil; + testassert(TestRootDealloc == 0); + testassert(TestRootRetain == 0); + testassert(TestRootRelease == 0); + testassert(TestRootAutorelease == 0); + + tt->ivar = nil; + testassert(TestRootDealloc == 1); + testassert(TestRootRetain == 0); + testassert(TestRootRelease == 1); + testassert(TestRootAutorelease == 0); + + } POP_POOL; + + + testprintf(" Successful +1 -> +0 handshake\n"); + + PUSH_POOL { + obj = [[TestObject alloc] init]; + testassert(obj); + tt->ivar = obj; + obj = nil; + + TestRootRetain = 0; + TestRootRelease = 0; + TestRootAutorelease = 0; + TestRootDealloc = 0; + + __unsafe_unretained TestObject *tmp = [tt return1]; + + testassert(TestRootDealloc == 0); + testassert(TestRootRetain == 1); + testassert(TestRootRelease == 1); + testassert(TestRootAutorelease == 0); + + tmp = nil; + testassert(TestRootDealloc == 0); + testassert(TestRootRetain == 1); + testassert(TestRootRelease == 1); + testassert(TestRootAutorelease == 0); + + tt->ivar = nil; + testassert(TestRootDealloc == 1); + testassert(TestRootRetain == 1); + testassert(TestRootRelease == 2); + testassert(TestRootAutorelease == 0); + + } POP_POOL; + + + testprintf(" Successful +0 -> +1 handshake\n"); + + PUSH_POOL { + obj = [[TestObject alloc] init]; + testassert(obj); + tt->ivar = obj; + obj = nil; + + TestRootRetain = 0; + TestRootRelease = 0; + TestRootAutorelease = 0; + TestRootDealloc = 0; + + TestObject *tmp = [tt return0]; + + testassert(TestRootDealloc == 0); + testassert(TestRootRetain == 1); + testassert(TestRootRelease == 0); + testassert(TestRootAutorelease == 0); + + tmp = nil; + testassert(TestRootDealloc == 0); + testassert(TestRootRetain == 1); + testassert(TestRootRelease == 1); + testassert(TestRootAutorelease == 0); + + tt->ivar = nil; + testassert(TestRootDealloc == 1); + testassert(TestRootRetain == 1); + testassert(TestRootRelease == 2); + testassert(TestRootAutorelease == 0); + + } POP_POOL; + + + + succeed(__FILE__); + + return 0; +} + + +#endif diff --git a/test/rr-autorelease-stacklogging.m b/test/rr-autorelease-stacklogging.m new file mode 100644 index 0000000..6347364 --- /dev/null +++ b/test/rr-autorelease-stacklogging.m @@ -0,0 +1,13 @@ +// Test OBJC_DEBUG_POOL_ALLOCATION (which is also enabled by MallocStackLogging) + +// TEST_ENV OBJC_DEBUG_POOL_ALLOCATION=YES +// TEST_CFLAGS -framework Foundation +// TEST_CONFIG MEM=mrc + +#include "test.h" + +#define FOUNDATION 0 +#define NAME "rr-autorelease-stacklogging" +#define DEBUG_POOL_ALLOCATION 1 + +#include "rr-autorelease2.m" diff --git a/test/rr-autorelease.m b/test/rr-autorelease.m new file mode 100644 index 0000000..01eb89e --- /dev/null +++ b/test/rr-autorelease.m @@ -0,0 +1,9 @@ +// TEST_CFLAGS -framework Foundation +// TEST_CONFIG MEM=mrc + +#include "test.h" + +#define FOUNDATION 0 +#define NAME "rr-autorelease" + +#include "rr-autorelease2.m" diff --git a/test/rr-autorelease2.m b/test/rr-autorelease2.m new file mode 100644 index 0000000..f242082 --- /dev/null +++ b/test/rr-autorelease2.m @@ -0,0 +1,384 @@ +// Define FOUNDATION=1 for NSObject and NSAutoreleasePool +// Define FOUNDATION=0 for _objc_root* and _objc_autoreleasePool* + +#include "test.h" + +#if FOUNDATION +# define RR_PUSH() [[NSAutoreleasePool alloc] init] +# define RR_POP(p) [(id)p release] +# define RR_RETAIN(o) [o retain] +# define RR_RELEASE(o) [o release] +# define RR_AUTORELEASE(o) [o autorelease] +# define RR_RETAINCOUNT(o) [o retainCount] +#else +# define RR_PUSH() _objc_autoreleasePoolPush() +# define RR_POP(p) _objc_autoreleasePoolPop(p) +# define RR_RETAIN(o) _objc_rootRetain((id)o) +# define RR_RELEASE(o) _objc_rootRelease((id)o) +# define RR_AUTORELEASE(o) _objc_rootAutorelease((id)o) +# define RR_RETAINCOUNT(o) _objc_rootRetainCount((id)o) +#endif + +#include +#include + +static int state; +static pthread_attr_t smallstack; + +#define NESTED_COUNT 8 + +@interface Deallocator : NSObject @end +@implementation Deallocator +-(void) dealloc +{ + // testprintf("-[Deallocator %p dealloc]\n", self); + state++; + [super dealloc]; +} +@end + +@interface AutoreleaseDuringDealloc : NSObject @end +@implementation AutoreleaseDuringDealloc +-(void) dealloc +{ + state++; + RR_AUTORELEASE([[Deallocator alloc] init]); + [super dealloc]; +} +@end + +@interface AutoreleasePoolDuringDealloc : NSObject @end +@implementation AutoreleasePoolDuringDealloc +-(void) dealloc +{ + // caller's pool + for (int i = 0; i < NESTED_COUNT; i++) { + RR_AUTORELEASE([[Deallocator alloc] init]); + } + + // local pool, popped + void *pool = RR_PUSH(); + for (int i = 0; i < NESTED_COUNT; i++) { + RR_AUTORELEASE([[Deallocator alloc] init]); + } + RR_POP(pool); + + // caller's pool again + for (int i = 0; i < NESTED_COUNT; i++) { + RR_AUTORELEASE([[Deallocator alloc] init]); + } + +#if FOUNDATION + { + static bool warned; + if (!warned) testwarn("rdar://7138159 NSAutoreleasePool leaks"); + warned = true; + } + state += NESTED_COUNT; +#else + // local pool, not popped + RR_PUSH(); + for (int i = 0; i < NESTED_COUNT; i++) { + RR_AUTORELEASE([[Deallocator alloc] init]); + } +#endif + + [super dealloc]; +} +@end + +void *autorelease_lots_fn(void *singlePool) +{ + // Enough to blow out the stack if AutoreleasePoolPage is recursive. + const int COUNT = 1024*1024; + state = 0; + + int p = 0; + void **pools = (void**)malloc((COUNT+1) * sizeof(void*)); + pools[p++] = RR_PUSH(); + + id obj = RR_AUTORELEASE([[Deallocator alloc] init]); + + // last pool has only 1 autorelease in it + pools[p++] = RR_PUSH(); + + for (int i = 0; i < COUNT; i++) { + if (rand() % 1000 == 0 && !singlePool) { + pools[p++] = RR_PUSH(); + } else { + RR_AUTORELEASE(RR_RETAIN(obj)); + } + } + + testassert(state == 0); + while (--p) { + RR_POP(pools[p]); + } + testassert(state == 0); + testassert(RR_RETAINCOUNT(obj) == 1); + RR_POP(pools[0]); + testassert(state == 1); + free(pools); + + return NULL; +} + +void *nsthread_fn(void *arg __unused) +{ + [NSThread currentThread]; + void *pool = RR_PUSH(); + RR_AUTORELEASE([[Deallocator alloc] init]); + RR_POP(pool); + return NULL; +} + +void cycle(void) +{ + // Normal autorelease. + testprintf("-- Normal autorelease.\n"); + { + void *pool = RR_PUSH(); + state = 0; + RR_AUTORELEASE([[Deallocator alloc] init]); + testassert(state == 0); + RR_POP(pool); + testassert(state == 1); + } + + // Autorelease during dealloc during autoreleasepool-pop. + // That autorelease is handled by the popping pool, not the one above it. + testprintf("-- Autorelease during dealloc during autoreleasepool-pop.\n"); + { + void *pool = RR_PUSH(); + state = 0; + RR_AUTORELEASE([[AutoreleaseDuringDealloc alloc] init]); + testassert(state == 0); + RR_POP(pool); + testassert(state == 2); + } + + // Autorelease pool during dealloc during autoreleasepool-pop. + testprintf("-- Autorelease pool during dealloc during autoreleasepool-pop.\n"); + { + void *pool = RR_PUSH(); + state = 0; + RR_AUTORELEASE([[AutoreleasePoolDuringDealloc alloc] init]); + testassert(state == 0); + RR_POP(pool); + testassert(state == 4 * NESTED_COUNT); + } + + // Top-level thread pool popped normally. + // Check twice - once for empty placeholder, once without. +# if DEBUG_POOL_ALLOCATION || FOUNDATION + // DebugPoolAllocation disables the empty placeholder pool. + // Guard Malloc disables the empty placeholder pool (checked at runtime) + // Foundation makes RR_PUSH return an NSAutoreleasePool not the raw token. +# define CHECK_PLACEHOLDER 0 +# else +# define CHECK_PLACEHOLDER 1 +# endif + testprintf("-- Thread-level pool popped normally.\n"); + { + state = 0; + testonthread(^{ + void *pool = RR_PUSH(); +#if CHECK_PLACEHOLDER + if (!is_guardmalloc()) { + testassert(pool == (void*)1); + } +#endif + RR_AUTORELEASE([[Deallocator alloc] init]); + RR_POP(pool); + pool = RR_PUSH(); +#if CHECK_PLACEHOLDER + if (!is_guardmalloc()) { + testassert(pool != (void*)1); + } +#endif + RR_AUTORELEASE([[Deallocator alloc] init]); + RR_POP(pool); + }); + testassert(state == 2); + } + + + // Autorelease with no pool. + testprintf("-- Autorelease with no pool.\n"); + { + state = 0; + testonthread(^{ + RR_AUTORELEASE([[Deallocator alloc] init]); + }); + testassert(state == 1); + } + + // Autorelease with no pool after popping the top-level pool. + testprintf("-- Autorelease with no pool after popping the last pool.\n"); + { + state = 0; + testonthread(^{ + void *pool = RR_PUSH(); + RR_AUTORELEASE([[Deallocator alloc] init]); + RR_POP(pool); + RR_AUTORELEASE([[Deallocator alloc] init]); + }); + testassert(state == 2); + } + + // Top-level thread pool not popped. + // The runtime should clean it up. +#if FOUNDATION + { + static bool warned; + if (!warned) testwarn("rdar://7138159 NSAutoreleasePool leaks"); + warned = true; + } +#else + testprintf("-- Thread-level pool not popped.\n"); + { + state = 0; + testonthread(^{ + RR_PUSH(); + RR_AUTORELEASE([[Deallocator alloc] init]); + // pool not popped + }); + testassert(state == 1); + } +#endif + + // Intermediate pool not popped. + // Popping the containing pool should clean up the skipped pool first. +#if FOUNDATION + { + static bool warned; + if (!warned) testwarn("rdar://7138159 NSAutoreleasePool leaks"); + warned = true; + } +#else + testprintf("-- Intermediate pool not popped.\n"); + { + void *pool = RR_PUSH(); + void *pool2 = RR_PUSH(); + RR_AUTORELEASE([[Deallocator alloc] init]); + state = 0; + (void)pool2; // pool2 not popped + RR_POP(pool); + testassert(state == 1); + } +#endif +} + + +static void +slow_cycle(void) +{ + // Large autorelease stack. + // Do this only once because it's slow. + testprintf("-- Large autorelease stack.\n"); + { + // limit stack size: autorelease pop should not be recursive + pthread_t th; + pthread_create(&th, &smallstack, &autorelease_lots_fn, NULL); + pthread_join(th, NULL); + } + + // Single large autorelease pool. + // Do this only once because it's slow. + testprintf("-- Large autorelease pool.\n"); + { + // limit stack size: autorelease pop should not be recursive + pthread_t th; + pthread_create(&th, &smallstack, &autorelease_lots_fn, (void*)1); + pthread_join(th, NULL); + } +} + + +int main() +{ + pthread_attr_init(&smallstack); + pthread_attr_setstacksize(&smallstack, 32768); + + // inflate the refcount side table so it doesn't show up in leak checks + { + int count = 10000; + id *objs = (id *)malloc(count*sizeof(id)); + for (int i = 0; i < count; i++) { + objs[i] = RR_RETAIN([NSObject new]); + } + for (int i = 0; i < count; i++) { + RR_RELEASE(objs[i]); + RR_RELEASE(objs[i]); + } + free(objs); + } + +#if FOUNDATION + // inflate NSAutoreleasePool's instance cache + { + int count = 32; + id *objs = (id *)malloc(count * sizeof(id)); + for (int i = 0; i < count; i++) { + objs[i] = [[NSAutoreleasePool alloc] init]; + } + for (int i = 0; i < count; i++) { + [objs[count-i-1] release]; + } + + free(objs); + } +#endif + + // preheat + { + for (int i = 0; i < 100; i++) { + cycle(); + } + + slow_cycle(); + } + + // check for leaks using top-level pools + { + leak_mark(); + + for (int i = 0; i < 1000; i++) { + cycle(); + } + + leak_check(0); + + slow_cycle(); + + leak_check(0); + } + + // check for leaks using pools not at top level + // fixme for FOUNDATION this leak mark/check needs + // to be outside the autorelease pool for some reason + leak_mark(); + void *pool = RR_PUSH(); + { + for (int i = 0; i < 1000; i++) { + cycle(); + } + + slow_cycle(); + } + RR_POP(pool); + leak_check(0); + + // NSThread. + // Can't leak check this because it's too noisy. + testprintf("-- NSThread.\n"); + { + pthread_t th; + pthread_create(&th, &smallstack, &nsthread_fn, 0); + pthread_join(th, NULL); + } + + // NO LEAK CHECK HERE + + succeed(NAME); +} diff --git a/test/rr-nsautorelease.m b/test/rr-nsautorelease.m new file mode 100644 index 0000000..095ec36 --- /dev/null +++ b/test/rr-nsautorelease.m @@ -0,0 +1,7 @@ +// TEST_CFLAGS -framework Foundation +// TEST_CONFIG MEM=mrc + +#define FOUNDATION 1 +#define NAME "rr-nsautorelease" + +#include "rr-autorelease2.m" diff --git a/test/rr-sidetable.m b/test/rr-sidetable.m new file mode 100644 index 0000000..daa4090 --- /dev/null +++ b/test/rr-sidetable.m @@ -0,0 +1,59 @@ +// TEST_CFLAGS -framework Foundation +// TEST_CONFIG MEM=mrc ARCH=x86_64 + +// Stress-test nonpointer isa's side table retain count transfers. + +// x86_64 only. arm64's side table limit is high enough that bugs +// are harder to reproduce. + +#include "test.h" +#import + +#define OBJECTS 1 +#define LOOPS 256 +#define THREADS 16 +#if __x86_64__ +# define RC_HALF (1ULL<<7) +#else +# error sorry +#endif +#define RC_DELTA RC_HALF + +static bool Deallocated = false; +@interface Deallocator : NSObject @end +@implementation Deallocator +-(void)dealloc { + Deallocated = true; + [super dealloc]; +} +@end + +// This is global to avoid extra retains by the dispatch block objects. +static Deallocator *obj; + +int main() { + dispatch_queue_t queue = + dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); + + for (size_t i = 0; i < OBJECTS; i++) { + obj = [Deallocator new]; + + dispatch_apply(THREADS, queue, ^(size_t i __unused) { + for (size_t a = 0; a < LOOPS; a++) { + for (size_t b = 0; b < RC_DELTA; b++) { + [obj retain]; + } + for (size_t b = 0; b < RC_DELTA; b++) { + [obj release]; + } + } + }); + + testassert(!Deallocated); + [obj release]; + testassert(Deallocated); + Deallocated = false; + } + + succeed(__FILE__); +} diff --git a/test/runtime.m b/test/runtime.m new file mode 100644 index 0000000..c70620b --- /dev/null +++ b/test/runtime.m @@ -0,0 +1,212 @@ +/* +TEST_BUILD_OUTPUT +.*runtime.m:\d+:\d+: warning: null passed to a callee that requires a non-null argument \[-Wnonnull\](\n.* note: expanded from macro 'testassert')? +.*runtime.m:\d+:\d+: warning: null passed to a callee that requires a non-null argument \[-Wnonnull\](\n.* note: expanded from macro 'testassert')? +.*runtime.m:\d+:\d+: warning: null passed to a callee that requires a non-null argument \[-Wnonnull\](\n.* note: expanded from macro 'testassert')? +END + +TEST_RUN_OUTPUT +objc\[\d+\]: class `SwiftV1Class\' not linked into application +objc\[\d+\]: class `DoesNotExist\' not linked into application +OK: runtime.m +OR +confused by Foundation +OK: runtime.m +END +*/ + + +#include "test.h" +#include "testroot.i" +#include +#include +#include +#include + +#if __has_feature(objc_arc) + +int main() +{ + // provoke the same nullability warnings as the real test + objc_getClass(nil); + objc_getClass(nil); + objc_getClass(nil); + + testwarn("rdar://11368528 confused by Foundation"); + fprintf(stderr, "confused by Foundation\n"); + succeed(__FILE__); +} + +#else + +@interface Sub : TestRoot @end +@implementation Sub @end + +#define SwiftV1MangledName "_TtC6Module12SwiftV1Class" +#define SwiftV1MangledName2 "_TtC2Sw13SwiftV1Class2" +#define SwiftV1MangledName3 "_TtCs13SwiftV1Class3" +#define SwiftV1MangledName4 "_TtC6Swiftt13SwiftV1Class4" + +__attribute__((objc_runtime_name(SwiftV1MangledName))) +@interface SwiftV1Class : TestRoot @end +@implementation SwiftV1Class @end + +__attribute__((objc_runtime_name(SwiftV1MangledName2))) +@interface SwiftV1Class2 : TestRoot @end +@implementation SwiftV1Class2 @end + +__attribute__((objc_runtime_name(SwiftV1MangledName3))) +@interface SwiftV1Class3 : TestRoot @end +@implementation SwiftV1Class3 @end + +__attribute__((objc_runtime_name(SwiftV1MangledName4))) +@interface SwiftV1Class4 : TestRoot @end +@implementation SwiftV1Class4 @end + + +int main() +{ + Class list[100]; + Class *list2; + unsigned int count, count0, count2; + unsigned int i; + int foundTestRoot; + int foundSub; + int foundSwiftV1; + int foundSwiftV1class2; + int foundSwiftV1class3; + int foundSwiftV1class4; + const char **names; + const char **namesFromHeader; + Dl_info info; + + [TestRoot class]; + + // This shouldn't touch any classes. + dladdr(&_mh_execute_header, &info); + names = objc_copyClassNamesForImage(info.dli_fname, &count); + testassert(names); + testassert(count == 6); + testassert(names[count] == NULL); + foundTestRoot = 0; + foundSub = 0; + foundSwiftV1 = 0; + foundSwiftV1class2 = 0; + foundSwiftV1class3 = 0; + foundSwiftV1class4 = 0; + for (i = 0; i < count; i++) { + if (0 == strcmp(names[i], "TestRoot")) foundTestRoot++; + if (0 == strcmp(names[i], "Sub")) foundSub++; + if (0 == strcmp(names[i], "Module.SwiftV1Class")) foundSwiftV1++; + if (0 == strcmp(names[i], "Sw.SwiftV1Class2")) foundSwiftV1class2++; + if (0 == strcmp(names[i], "Swift.SwiftV1Class3")) foundSwiftV1class3++; + if (0 == strcmp(names[i], "Swiftt.SwiftV1Class4")) foundSwiftV1class4++; + } + testassert(foundTestRoot == 1); + testassert(foundSub == 1); + testassert(foundSwiftV1 == 1); + testassert(foundSwiftV1class2 == 1); + testassert(foundSwiftV1class3 == 1); + testassert(foundSwiftV1class4 == 1); + + // Getting the names using the header should give us the same list. + namesFromHeader = objc_copyClassNamesForImage(info.dli_fname, &count0); + testassert(namesFromHeader); + testassert(count == count0); + for (i = 0; i < count; i++) { + testassert(!strcmp(names[i], namesFromHeader[i])); + } + + + // class Sub hasn't been touched - make sure it's in the class list too + count0 = objc_getClassList(NULL, 0); + testassert(count0 >= 2 && count0 < 100); + + list[count0-1] = NULL; + count = objc_getClassList(list, count0-1); + testassert(list[count0-1] == NULL); + testassert(count == count0); + + count = objc_getClassList(list, count0); + testassert(count == count0); + + for (i = 0; i < count; i++) { + testprintf("%s\n", class_getName(list[i])); + } + + foundTestRoot = 0; + foundSub = 0; + foundSwiftV1 = 0; + foundSwiftV1class2 = 0; + foundSwiftV1class3 = 0; + foundSwiftV1class4 = 0; + for (i = 0; i < count; i++) { + if (0 == strcmp(class_getName(list[i]), "TestRoot")) foundTestRoot++; + if (0 == strcmp(class_getName(list[i]), "Sub")) foundSub++; + if (0 == strcmp(class_getName(list[i]), "Module.SwiftV1Class")) foundSwiftV1++; + if (0 == strcmp(class_getName(list[i]), "Sw.SwiftV1Class2")) foundSwiftV1class2++; + if (0 == strcmp(class_getName(list[i]), "Swift.SwiftV1Class3")) foundSwiftV1class3++; + if (0 == strcmp(class_getName(list[i]), "Swiftt.SwiftV1Class4")) foundSwiftV1class4++; + // list should be non-meta classes only + testassert(!class_isMetaClass(list[i])); + } + testassert(foundTestRoot == 1); + testassert(foundSub == 1); + testassert(foundSwiftV1 == 1); + testassert(foundSwiftV1class2 == 1); + testassert(foundSwiftV1class3 == 1); + testassert(foundSwiftV1class4 == 1); + + // fixme check class handler + testassert(objc_getClass("TestRoot") == [TestRoot class]); + testassert(objc_getClass("Module.SwiftV1Class") == [SwiftV1Class class]); + testassert(objc_getClass(SwiftV1MangledName) == [SwiftV1Class class]); + testassert(objc_getClass("Sw.SwiftV1Class2") == [SwiftV1Class2 class]); + testassert(objc_getClass(SwiftV1MangledName2) == [SwiftV1Class2 class]); + testassert(objc_getClass("Swift.SwiftV1Class3") == [SwiftV1Class3 class]); + testassert(objc_getClass(SwiftV1MangledName3) == [SwiftV1Class3 class]); + testassert(objc_getClass("Swiftt.SwiftV1Class4") == [SwiftV1Class4 class]); + testassert(objc_getClass(SwiftV1MangledName4) == [SwiftV1Class4 class]); + testassert(objc_getClass("SwiftV1Class") == nil); + testassert(objc_getClass("DoesNotExist") == nil); + testassert(objc_getClass(NULL) == nil); + + testassert(objc_getMetaClass("TestRoot") == object_getClass([TestRoot class])); + testassert(objc_getMetaClass("Module.SwiftV1Class") == object_getClass([SwiftV1Class class])); + testassert(objc_getMetaClass(SwiftV1MangledName) == object_getClass([SwiftV1Class class])); + testassert(objc_getMetaClass("SwiftV1Class") == nil); + testassert(objc_getMetaClass("DoesNotExist") == nil); + testassert(objc_getMetaClass(NULL) == nil); + + // fixme check class no handler + testassert(objc_lookUpClass("TestRoot") == [TestRoot class]); + testassert(objc_lookUpClass("Module.SwiftV1Class") == [SwiftV1Class class]); + testassert(objc_lookUpClass(SwiftV1MangledName) == [SwiftV1Class class]); + testassert(objc_lookUpClass("SwiftV1Class") == nil); + testassert(objc_lookUpClass("DoesNotExist") == nil); + testassert(objc_lookUpClass(NULL) == nil); + + testassert(! object_isClass(nil)); + testassert(! object_isClass([TestRoot new])); + testassert(object_isClass([TestRoot class])); + testassert(object_isClass(object_getClass([TestRoot class]))); + testassert(object_isClass([Sub class])); + testassert(object_isClass(object_getClass([Sub class]))); + testassert(object_isClass([SwiftV1Class class])); + testassert(object_isClass(object_getClass([SwiftV1Class class]))); + + list2 = objc_copyClassList(&count2); + testassert(count2 == count); + testassert(list2); + testassert(malloc_size(list2) >= (1+count2) * sizeof(Class)); + for (i = 0; i < count; i++) { + testassert(list[i] == list2[i]); + } + testassert(list2[count] == NULL); + free(list2); + free(objc_copyClassList(NULL)); + + succeed(__FILE__); +} + +#endif diff --git a/test/sel.m b/test/sel.m new file mode 100644 index 0000000..a4c4dd3 --- /dev/null +++ b/test/sel.m @@ -0,0 +1,21 @@ +/* +TEST_BUILD_OUTPUT +.*sel.m:\d+:\d+: warning: null passed to a callee that requires a non-null argument \[-Wnonnull\](\n.* note: expanded from macro 'testassert')? +END +*/ + +#include "test.h" +#include +#include +#include + +int main() +{ + // Make sure @selector values are correctly fixed up + testassert(@selector(foo) == sel_registerName("foo")); + + // sel_getName recognizes the zero SEL + testassert(0 == strcmp("", sel_getName(0))); + + succeed(__FILE__); +} diff --git a/test/setSuper.m b/test/setSuper.m new file mode 100644 index 0000000..5f77840 --- /dev/null +++ b/test/setSuper.m @@ -0,0 +1,44 @@ +// TEST_CONFIG + +#include "test.h" +#include "testroot.i" +#include + +@interface Super1 : TestRoot @end +@implementation Super1 ++(int)classMethod { return 1; } +-(int)instanceMethod { return 10000; } +@end + +@interface Super2 : TestRoot @end +@implementation Super2 ++(int)classMethod { return 2; } +-(int)instanceMethod { return 20000; } +@end + +@interface Sub : Super1 @end +@implementation Sub ++(int)classMethod { return [super classMethod] + 100; } +-(int)instanceMethod { + return [super instanceMethod] + 1000000; +} +@end + +int main() +{ + Class cls; + Sub *obj = [Sub new]; + + testassert(101 == [[Sub class] classMethod]); + testassert(1010000 == [obj instanceMethod]); + + cls = class_setSuperclass([Sub class], [Super2 class]); + + testassert(cls == [Super1 class]); + testassert(object_getClass(cls) == object_getClass([Super1 class])); + + testassert(102 == [[Sub class] classMethod]); + testassert(1020000 == [obj instanceMethod]); + + succeed(__FILE__); +} diff --git a/test/subscripting.m b/test/subscripting.m new file mode 100644 index 0000000..ecc2b13 --- /dev/null +++ b/test/subscripting.m @@ -0,0 +1,139 @@ +// TEST_CFLAGS -framework Foundation + +#import +#import +#import +#import +#include "test.h" + +@interface TestIndexed : NSObject { + NSMutableArray *indexedValues; +} +@property(readonly) NSUInteger count; +- (id)objectAtIndexedSubscript:(NSUInteger)index; +- (void)setObject:(id)object atIndexedSubscript:(NSUInteger)index; +@end + +@implementation TestIndexed + +- (id)init { + if ((self = [super init])) { + indexedValues = [NSMutableArray new]; + } + return self; +} + +#if !__has_feature(objc_arc) +- (void)dealloc { + [indexedValues release]; + [super dealloc]; +} +#endif + +- (NSUInteger)count { return [indexedValues count]; } +- (id)objectAtIndexedSubscript:(NSUInteger)index { return [indexedValues objectAtIndex:index]; } +- (void)setObject:(id)object atIndexedSubscript:(NSUInteger)index { + if (index == NSNotFound) + [indexedValues addObject:object]; + else + [indexedValues replaceObjectAtIndex:index withObject:object]; +} + +- (NSString *)description { + return [NSString stringWithFormat:@"indexedValues = %@", indexedValues]; +} + +- (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state objects:(id __unsafe_unretained [])buffer count:(NSUInteger)len { + return [indexedValues countByEnumeratingWithState:state objects:buffer count:len]; +} + + +@end + +@interface TestKeyed : NSObject { + NSMutableDictionary *keyedValues; +} +@property(readonly) NSUInteger count; +- (id)objectForKeyedSubscript:(id)key; +- (void)setObject:(id)object forKeyedSubscript:(id)key; +@end + +@implementation TestKeyed + +- (id)init { + if ((self = [super init])) { + keyedValues = [NSMutableDictionary new]; + } + return self; +} + +#if !__has_feature(objc_arc) +- (void)dealloc { + [keyedValues release]; + [super dealloc]; +} +#endif + +- (NSUInteger)count { return [keyedValues count]; } +- (id)objectForKeyedSubscript:(id)key { return [keyedValues objectForKey:key]; } +- (void)setObject:(id)object forKeyedSubscript:(id)key { + [keyedValues setObject:object forKey:key]; +} + +- (NSString *)description { + return [NSString stringWithFormat:@"keyedValues = %@", keyedValues]; +} + +- (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state objects:(id __unsafe_unretained [])buffer count:(NSUInteger)len { + return [keyedValues countByEnumeratingWithState:state objects:buffer count:len]; +} + +@end + +int main() { + PUSH_POOL { + +#if __has_feature(objc_bool) // placeholder until we get a more precise macro. + TestIndexed *testIndexed = [TestIndexed new]; + id objects[] = { @1, @2, @3, @4, @5 }; + size_t i, count = sizeof(objects) / sizeof(id); + for (i = 0; i < count; ++i) { + testIndexed[NSNotFound] = objects[i]; + } + for (i = 0; i < count; ++i) { + id object = testIndexed[i]; + testassert(object == objects[i]); + } + if (testverbose()) { + i = 0; + for (id object in testIndexed) { + NSString *message = [NSString stringWithFormat:@"testIndexed[%zu] = %@\n", i++, object]; + testprintf([message UTF8String]); + } + } + + TestKeyed *testKeyed = [TestKeyed new]; + id keys[] = { @"One", @"Two", @"Three", @"Four", @"Five" }; + for (i = 0; i < count; ++i) { + id key = keys[i]; + testKeyed[key] = objects[i]; + } + for (i = 0; i < count; ++i) { + id key = keys[i]; + id object = testKeyed[key]; + testassert(object == objects[i]); + } + if (testverbose()) { + for (id key in testKeyed) { + NSString *message = [NSString stringWithFormat:@"testKeyed[@\"%@\"] = %@\n", key, testKeyed[key]]; + testprintf([message UTF8String]); + } + } +#endif + + } POP_POOL; + + succeed(__FILE__); + + return 0; +} diff --git a/test/super.m b/test/super.m new file mode 100644 index 0000000..ff169f7 --- /dev/null +++ b/test/super.m @@ -0,0 +1,21 @@ +// TEST_CONFIG + +#include "test.h" +#include "testroot.i" +#include + +@interface Sub : TestRoot @end +@implementation Sub @end + +int main() +{ + // [super ...] messages are tested in msgSend.m + + testassert(class_getSuperclass([Sub class]) == [TestRoot class]); + testassert(class_getSuperclass(object_getClass([Sub class])) == object_getClass([TestRoot class])); + testassert(class_getSuperclass([TestRoot class]) == Nil); + testassert(class_getSuperclass(object_getClass([TestRoot class])) == [TestRoot class]); + testassert(class_getSuperclass(Nil) == Nil); + + succeed(__FILE__); +} diff --git a/test/swift-class-def.m b/test/swift-class-def.m new file mode 100644 index 0000000..3288ad7 --- /dev/null +++ b/test/swift-class-def.m @@ -0,0 +1,291 @@ +#include + +#if __LP64__ +# define PTR " .quad " +# define PTRSIZE "8" +# define LOGPTRSIZE "3" +#else +# define PTR " .long " +# define PTRSIZE "4" +# define LOGPTRSIZE "2" +#endif + +#if __has_feature(ptrauth_calls) +# define SIGNED_METHOD_LIST_IMP "@AUTH(ia,0,addr) " +#else +# define SIGNED_METHOD_LIST_IMP +#endif + +#define str(x) #x +#define str2(x) str(x) + +// Swift metadata initializers. Define these in the test. +EXTERN_C Class initSuper(Class cls, void *arg); +EXTERN_C Class initSub(Class cls, void *arg); + +@interface SwiftSuper : NSObject @end +@interface SwiftSub : SwiftSuper @end + +__BEGIN_DECLS +// not id to avoid ARC operations because the class doesn't implement RR methods +void* nop(void* self) { return self; } +__END_DECLS + +asm( + ".globl _OBJC_CLASS_$_SwiftSuper \n" + ".section __DATA,__objc_data \n" + ".align 3 \n" + "_OBJC_CLASS_$_SwiftSuper: \n" + PTR "_OBJC_METACLASS_$_SwiftSuper \n" + PTR "_OBJC_CLASS_$_NSObject \n" + PTR "__objc_empty_cache \n" + PTR "0 \n" + PTR "L_ro + 2 \n" + // pad to OBJC_MAX_CLASS_SIZE + PTR "0 \n" + PTR "0 \n" + PTR "0 \n" + PTR "0 \n" + PTR "0 \n" + PTR "0 \n" + PTR "0 \n" + PTR "0 \n" + PTR "0 \n" + PTR "0 \n" + PTR "0 \n" + PTR "0 \n" + PTR "0 \n" + PTR "0 \n" + PTR "0 \n" + PTR "0 \n" + PTR "0 \n" + PTR "0 \n" + PTR "0 \n" + PTR "0 \n" + PTR "0 \n" + PTR "0 \n" + PTR "0 \n" + PTR "0 \n" + PTR "0 \n" + PTR "0 \n" + PTR "0 \n" + "" + "_OBJC_METACLASS_$_SwiftSuper: \n" + PTR "_OBJC_METACLASS_$_NSObject \n" + PTR "_OBJC_METACLASS_$_NSObject \n" + PTR "__objc_empty_cache \n" + PTR "0 \n" + PTR "L_meta_ro \n" + // pad to OBJC_MAX_CLASS_SIZE + PTR "0 \n" + PTR "0 \n" + PTR "0 \n" + PTR "0 \n" + PTR "0 \n" + PTR "0 \n" + PTR "0 \n" + PTR "0 \n" + PTR "0 \n" + PTR "0 \n" + PTR "0 \n" + PTR "0 \n" + PTR "0 \n" + PTR "0 \n" + PTR "0 \n" + PTR "0 \n" + PTR "0 \n" + PTR "0 \n" + PTR "0 \n" + PTR "0 \n" + PTR "0 \n" + PTR "0 \n" + PTR "0 \n" + PTR "0 \n" + PTR "0 \n" + PTR "0 \n" + PTR "0 \n" + "" + "L_ro: \n" + ".long (1<<6)\n" + ".long 0 \n" + ".long "PTRSIZE" \n" +#if __LP64__ + ".long 0 \n" +#endif + PTR "0 \n" + PTR "L_super_name \n" + PTR "L_good_methods \n" + PTR "0 \n" + PTR "L_super_ivars \n" + PTR "0 \n" + PTR "0 \n" + PTR "_initSuper" SIGNED_METHOD_LIST_IMP "\n" + "" + "L_meta_ro: \n" + ".long 1 \n" + ".long 40 \n" + ".long 40 \n" +#if __LP64__ + ".long 0 \n" +#endif + PTR "0 \n" + PTR "L_super_name \n" + PTR "L_good_methods \n" + PTR "0 \n" + PTR "0 \n" + PTR "0 \n" + PTR "0 \n" + + ".globl _OBJC_CLASS_$_SwiftSub \n" + ".section __DATA,__objc_data \n" + ".align 3 \n" + "_OBJC_CLASS_$_SwiftSub: \n" + PTR "_OBJC_METACLASS_$_SwiftSub \n" + PTR "_OBJC_CLASS_$_SwiftSuper \n" + PTR "__objc_empty_cache \n" + PTR "0 \n" + PTR "L_sub_ro + 2 \n" + // pad to OBJC_MAX_CLASS_SIZE + PTR "0 \n" + PTR "0 \n" + PTR "0 \n" + PTR "0 \n" + PTR "0 \n" + PTR "0 \n" + PTR "0 \n" + PTR "0 \n" + PTR "0 \n" + PTR "0 \n" + PTR "0 \n" + PTR "0 \n" + PTR "0 \n" + PTR "0 \n" + PTR "0 \n" + PTR "0 \n" + PTR "0 \n" + PTR "0 \n" + PTR "0 \n" + PTR "0 \n" + PTR "0 \n" + PTR "0 \n" + PTR "0 \n" + PTR "0 \n" + PTR "0 \n" + PTR "0 \n" + PTR "0 \n" + "" + "_OBJC_METACLASS_$_SwiftSub: \n" + PTR "_OBJC_METACLASS_$_NSObject \n" + PTR "_OBJC_METACLASS_$_SwiftSuper \n" + PTR "__objc_empty_cache \n" + PTR "0 \n" + PTR "L_sub_meta_ro \n" + // pad to OBJC_MAX_CLASS_SIZE + PTR "0 \n" + PTR "0 \n" + PTR "0 \n" + PTR "0 \n" + PTR "0 \n" + PTR "0 \n" + PTR "0 \n" + PTR "0 \n" + PTR "0 \n" + PTR "0 \n" + PTR "0 \n" + PTR "0 \n" + PTR "0 \n" + PTR "0 \n" + PTR "0 \n" + PTR "0 \n" + PTR "0 \n" + PTR "0 \n" + PTR "0 \n" + PTR "0 \n" + PTR "0 \n" + PTR "0 \n" + PTR "0 \n" + PTR "0 \n" + PTR "0 \n" + PTR "0 \n" + PTR "0 \n" + "" + "L_sub_ro: \n" + ".long (1<<6)\n" + ".long 0 \n" + ".long "PTRSIZE" \n" +#if __LP64__ + ".long 0 \n" +#endif + PTR "0 \n" + PTR "L_sub_name \n" + PTR "L_good_methods \n" + PTR "0 \n" + PTR "L_sub_ivars \n" + PTR "0 \n" + PTR "0 \n" + PTR "_initSub" SIGNED_METHOD_LIST_IMP "\n" + "" + "L_sub_meta_ro: \n" + ".long 1 \n" + ".long 40 \n" + ".long 40 \n" +#if __LP64__ + ".long 0 \n" +#endif + PTR "0 \n" + PTR "L_sub_name \n" + PTR "L_good_methods \n" + PTR "0 \n" + PTR "0 \n" + PTR "0 \n" + PTR "0 \n" + + "L_good_methods: \n" + ".long 3*"PTRSIZE" \n" + ".long 1 \n" + PTR "L_self \n" + PTR "L_self \n" + PTR "_nop" SIGNED_METHOD_LIST_IMP "\n" + + "L_super_ivars: \n" + ".long 4*"PTRSIZE" \n" + ".long 1 \n" + PTR "L_super_ivar_offset \n" + PTR "L_super_ivar_name \n" + PTR "L_super_ivar_type \n" + ".long "LOGPTRSIZE" \n" + ".long "PTRSIZE" \n" + + "L_sub_ivars: \n" + ".long 4*"PTRSIZE" \n" + ".long 1 \n" + PTR "L_sub_ivar_offset \n" + PTR "L_sub_ivar_name \n" + PTR "L_sub_ivar_type \n" + ".long "LOGPTRSIZE" \n" + ".long "PTRSIZE" \n" + + "L_super_ivar_offset: \n" + ".long 0 \n" + "L_sub_ivar_offset: \n" + ".long "PTRSIZE" \n" + + ".cstring \n" + "L_super_name: .ascii \"SwiftSuper\\0\" \n" + "L_sub_name: .ascii \"SwiftSub\\0\" \n" + "L_load: .ascii \"load\\0\" \n" + "L_self: .ascii \"self\\0\" \n" + "L_super_ivar_name: .ascii \"super_ivar\\0\" \n" + "L_super_ivar_type: .ascii \"c\\0\" \n" + "L_sub_ivar_name: .ascii \"sub_ivar\\0\" \n" + "L_sub_ivar_type: .ascii \"@\\0\" \n" + + + ".section __DATA,__objc_classlist \n" + PTR "_OBJC_CLASS_$_SwiftSuper \n" + PTR "_OBJC_CLASS_$_SwiftSub \n" + + ".text \n" +); + +void fn(void) { } diff --git a/test/swiftMetadataInitializer.m b/test/swiftMetadataInitializer.m new file mode 100644 index 0000000..bfa54a8 --- /dev/null +++ b/test/swiftMetadataInitializer.m @@ -0,0 +1,70 @@ +// TEST_CONFIG MEM=mrc + +#include "test.h" +#include "swift-class-def.m" + + +// _objc_swiftMetadataInitializer hooks for the classes in swift-class-def.m + +Class initSuper(Class cls __unused, void *arg __unused) +{ + // This test provokes objc's callback out of superclass order. + // SwiftSub's init is first. SwiftSuper's init is never called. + + fail("SwiftSuper's init should not have been called"); +} + +bool isRealized(Class cls) +{ + // check the is-realized bits directly + +#if __LP64__ +# define mask (~(uintptr_t)7) +#else +# define mask (~(uintptr_t)3) +#endif +#define RW_REALIZED (1<<31) + + uintptr_t rw = ((uintptr_t *)cls)[4] & mask; // class_t->data + return ((uint32_t *)rw)[0] & RW_REALIZED; // class_rw_t->flags +} + +static int SubInits = 0; +Class initSub(Class cls, void *arg) +{ + testprintf("initSub callback\n"); + + extern uintptr_t OBJC_CLASS_$_SwiftSuper; + extern uintptr_t OBJC_CLASS_$_SwiftSub; + Class RawSwiftSuper = (Class)&OBJC_CLASS_$_SwiftSuper; + Class RawSwiftSub = (Class)&OBJC_CLASS_$_SwiftSub; + + testassert(SubInits == 0); + SubInits++; + testassert(arg == nil); + testassert(0 == strcmp(class_getName(cls), "SwiftSub")); + testassert(cls == RawSwiftSub); + testassert(!isRealized(RawSwiftSuper)); + testassert(!isRealized(RawSwiftSub)); + + testprintf("initSub beginning _objc_realizeClassFromSwift\n"); + _objc_realizeClassFromSwift(cls, cls); + testprintf("initSub finished _objc_realizeClassFromSwift\n"); + + testassert(isRealized(RawSwiftSuper)); + testassert(isRealized(RawSwiftSub)); + + return cls; +} + + +int main() +{ + testassert(SubInits == 0); + testprintf("calling [SwiftSub class]\n"); + [SwiftSub class]; + testprintf("finished [SwiftSub class]\n"); + testassert(SubInits == 1); + [SwiftSuper class]; + succeed(__FILE__); +} diff --git a/test/synchronized-counter.m b/test/synchronized-counter.m new file mode 100644 index 0000000..7d3fd2d --- /dev/null +++ b/test/synchronized-counter.m @@ -0,0 +1,88 @@ +// TEST_CONFIG + +#include "test.h" + +#include +#include +#include +#include +#include +#include + +// synchronized stress test +// Single locked counter incremented by many threads. + +#if defined(__arm__) +#define THREADS 16 +#define COUNT 1024*24 +#else +// 64 / 1024*24 test takes about 20s on 4x2.6GHz Mac Pro +#define THREADS 64 +#define COUNT 1024*24 +#endif + +static id lock; +static int count; + +static void *threadfn(void *arg) +{ + int n, d; + int depth = 1 + (int)(intptr_t)arg % 4; + + for (n = 0; n < COUNT; n++) { + // Lock + for (d = 0; d < depth; d++) { + int err = objc_sync_enter(lock); + testassert(err == OBJC_SYNC_SUCCESS); + } + + // Increment + count++; + + // Unlock + for (d = 0; d < depth; d++) { + int err = objc_sync_exit(lock); + testassert(err == OBJC_SYNC_SUCCESS); + } + } + + // Verify lack of objc pthread data (should have used sync fast cache) +#ifdef __PTK_FRAMEWORK_OBJC_KEY0 + testassert(! pthread_getspecific(__PTK_FRAMEWORK_OBJC_KEY0)); +#endif + + return NULL; +} + +int main() +{ + pthread_t threads[THREADS]; + int t; + int err; + + lock = [[NSObject alloc] init]; + + // Verify objc pthread data on this thread (from +initialize) + // Worker threads shouldn't have any because of sync fast cache. +#ifdef __PTK_FRAMEWORK_OBJC_KEY0 + testassert(pthread_getspecific(__PTK_FRAMEWORK_OBJC_KEY0)); +#endif + + // Start the threads + for (t = 0; t < THREADS; t++) { + pthread_create(&threads[t], NULL, &threadfn, (void*)(intptr_t)t); + } + + // Wait for threads to finish + for (t = 0; t < THREADS; t++) { + pthread_join(threads[t], NULL); + } + + // Verify lock: should be available + // Verify count: should be THREADS*COUNT + err = objc_sync_enter(lock); + testassert(err == OBJC_SYNC_SUCCESS); + testassert(count == THREADS*COUNT); + + succeed(__FILE__); +} diff --git a/test/synchronized-grid.m b/test/synchronized-grid.m new file mode 100644 index 0000000..47f7ccc --- /dev/null +++ b/test/synchronized-grid.m @@ -0,0 +1,112 @@ +// TEST_CONFIG + +#include "test.h" + +#include +#include +#include +#include +#include + +// synchronized stress test +// 2-D grid of counters and locks. +// Each thread increments all counters some number of times. +// To increment: +// * thread picks a target [row][col] +// * thread locks all locks [row][0] to [row][col], possibly recursively +// * thread increments counter [row][col] +// * thread unlocks all of the locks + +#if defined(__arm__) +// 16 / 4 / 3 / 1024*8 test takes about 30s on 2nd gen iPod touch +#define THREADS 16 +#define ROWS 4 +#define COLS 3 +#define COUNT 1024*8 +#else +// 64 / 4 / 3 / 1024*8 test takes about 20s on 4x2.6GHz Mac Pro +#define THREADS 64 +#define ROWS 4 +#define COLS 3 +#define COUNT 1024*8 +#endif + +static id locks[ROWS][COLS]; +static int counts[ROWS][COLS]; + + +static void *threadfn(void *arg) +{ + int n, d; + int depth = 1 + (int)(intptr_t)arg % 4; + + for (n = 0; n < COUNT; n++) { + int rrr = rand() % ROWS; + int ccc = rand() % COLS; + int rr, cc; + for (rr = 0; rr < ROWS; rr++) { + int r = (rrr+rr) % ROWS; + for (cc = 0; cc < COLS; cc++) { + int c = (ccc+cc) % COLS; + int l; + + // Lock [r][0..c] + // ... in that order to prevent deadlock + for (l = 0; l <= c; l++) { + for (d = 0; d < depth; d++) { + int err = objc_sync_enter(locks[r][l]); + testassert(err == OBJC_SYNC_SUCCESS); + } + } + + // Increment count [r][c] + counts[r][c]++; + + // Unlock [r][0..c] + // ... in that order to increase contention + for (l = 0; l <= c; l++) { + for (d = 0; d < depth; d++) { + int err = objc_sync_exit(locks[r][l]); + testassert(err == OBJC_SYNC_SUCCESS); + } + } + } + } + } + + return NULL; +} + +int main() +{ + pthread_t threads[THREADS]; + int r, c, t; + + for (r = 0; r < ROWS; r++) { + for (c = 0; c < COLS; c++) { + locks[r][c] = [[NSObject alloc] init]; + } + } + + // Start the threads + for (t = 0; t < THREADS; t++) { + pthread_create(&threads[t], NULL, &threadfn, (void*)(intptr_t)t); + } + + // Wait for threads to finish + for (t = 0; t < THREADS; t++) { + pthread_join(threads[t], NULL); + } + + // Verify locks: all should be available + // Verify counts: all should be THREADS*COUNT + for (r = 0; r < ROWS; r++) { + for (c = 0; c < COLS; c++) { + int err = objc_sync_enter(locks[r][c]); + testassert(err == OBJC_SYNC_SUCCESS); + testassert(counts[r][c] == THREADS*COUNT); + } + } + + succeed(__FILE__); +} diff --git a/test/synchronized.m b/test/synchronized.m new file mode 100644 index 0000000..cab6dc0 --- /dev/null +++ b/test/synchronized.m @@ -0,0 +1,102 @@ +// TEST_CONFIG + +#include "test.h" + +#include +#include +#include +#include +#include +#include + +// Basic @synchronized tests. + + +#define WAIT_SEC 3 + +static id obj; +static semaphore_t go; +static semaphore_t stop; + +void *thread(void *arg __unused) +{ + int err; + + // non-blocking sync_enter + err = objc_sync_enter(obj); + testassert(err == OBJC_SYNC_SUCCESS); + + semaphore_signal(go); + // main thread: sync_exit of object locked on some other thread + semaphore_wait(stop); + + err = objc_sync_exit(obj); + testassert(err == OBJC_SYNC_SUCCESS); + err = objc_sync_enter(obj); + testassert(err == OBJC_SYNC_SUCCESS); + + semaphore_signal(go); + // main thread: blocking sync_enter + testassert(WAIT_SEC/3*3 == WAIT_SEC); + sleep(WAIT_SEC/3); + // recursive enter while someone waits + err = objc_sync_enter(obj); + testassert(err == OBJC_SYNC_SUCCESS); + sleep(WAIT_SEC/3); + // recursive exit while someone waits + err = objc_sync_exit(obj); + testassert(err == OBJC_SYNC_SUCCESS); + sleep(WAIT_SEC/3); + // sync_exit while someone waits + err = objc_sync_exit(obj); + testassert(err == OBJC_SYNC_SUCCESS); + + return NULL; +} + +int main() +{ + pthread_t th; + int err; + struct timeval start, end; + + obj = [[NSObject alloc] init]; + + // sync_exit of never-locked object + err = objc_sync_exit(obj); + testassert(err == OBJC_SYNC_NOT_OWNING_THREAD_ERROR); + + semaphore_create(mach_task_self(), &go, 0, 0); + semaphore_create(mach_task_self(), &stop, 0, 0); + pthread_create(&th, NULL, &thread, NULL); + semaphore_wait(go); + + // sync_exit of object locked on some other thread + err = objc_sync_exit(obj); + testassert(err == OBJC_SYNC_NOT_OWNING_THREAD_ERROR); + + semaphore_signal(stop); + semaphore_wait(go); + + // blocking sync_enter + gettimeofday(&start, NULL); + err = objc_sync_enter(obj); + gettimeofday(&end, NULL); + testassert(err == OBJC_SYNC_SUCCESS); + // should have waited more than WAIT_SEC but less than WAIT_SEC+1 + // fixme hack: sleep(1) is ending 500 usec too early on x86_64 buildbot + // (rdar://6456975) + testassert(end.tv_sec*1000000LL+end.tv_usec >= + start.tv_sec*1000000LL+start.tv_usec + WAIT_SEC*1000000LL + - 3*500 /*hack*/); + testassert(end.tv_sec*1000000LL+end.tv_usec < + start.tv_sec*1000000LL+start.tv_usec + (1+WAIT_SEC)*1000000LL); + + err = objc_sync_exit(obj); + testassert(err == OBJC_SYNC_SUCCESS); + + err = objc_sync_exit(obj); + testassert(err == OBJC_SYNC_NOT_OWNING_THREAD_ERROR); + + succeed(__FILE__); +} diff --git a/test/taggedNSPointers.m b/test/taggedNSPointers.m new file mode 100644 index 0000000..86efb90 --- /dev/null +++ b/test/taggedNSPointers.m @@ -0,0 +1,80 @@ +// TEST_CFLAGS -framework Foundation + +#include "test.h" +#include +#import + +#if OBJC_HAVE_TAGGED_POINTERS + +void testTaggedNumber() +{ + NSNumber *taggedNS = [NSNumber numberWithInt: 1234]; + CFNumberRef taggedCF = (__bridge CFNumberRef)taggedNS; + int result; + + testassert( CFGetTypeID(taggedCF) == CFNumberGetTypeID() ); + testassert(_objc_getClassForTag(OBJC_TAG_NSNumber) == [taggedNS class]); + + CFNumberGetValue(taggedCF, kCFNumberIntType, &result); + testassert(result == 1234); + + testassert(_objc_isTaggedPointer(taggedCF)); + testassert(_objc_getTaggedPointerTag(taggedCF) == OBJC_TAG_NSNumber); + testassert(_objc_makeTaggedPointer(_objc_getTaggedPointerTag(taggedCF), _objc_getTaggedPointerValue(taggedCF)) == taggedCF); + + // do some generic object-y things to the taggedPointer instance + CFRetain(taggedCF); + CFRelease(taggedCF); + + NSMutableDictionary *dict = [NSMutableDictionary dictionary]; + [dict setObject: taggedNS forKey: @"fred"]; + testassert(taggedNS == [dict objectForKey: @"fred"]); + [dict setObject: @"bob" forKey: taggedNS]; + testassert([@"bob" isEqualToString: [dict objectForKey: taggedNS]]); + + NSNumber *iM88 = [NSNumber numberWithInt:-88]; + NSNumber *i12346 = [NSNumber numberWithInt: 12346]; + NSNumber *i12347 = [NSNumber numberWithInt: 12347]; + + NSArray *anArray = [NSArray arrayWithObjects: iM88, i12346, i12347, nil]; + testassert([anArray count] == 3); + testassert([anArray indexOfObject: i12346] == 1); + + NSSet *aSet = [NSSet setWithObjects: iM88, i12346, i12347, nil]; + testassert([aSet count] == 3); + testassert([aSet containsObject: i12346]); + + [taggedNS performSelector: @selector(intValue)]; + testassert(![taggedNS isProxy]); + testassert([taggedNS isKindOfClass: [NSNumber class]]); + testassert([taggedNS respondsToSelector: @selector(intValue)]); + + (void)[taggedNS description]; +} + +int main() +{ + PUSH_POOL { + testTaggedNumber(); // should be tested by CF... our tests are wrong, wrong, wrong. + } POP_POOL; + + succeed(__FILE__); +} + +// OBJC_HAVE_TAGGED_POINTERS +#else +// not OBJC_HAVE_TAGGED_POINTERS + +// Tagged pointers not supported. Crash if an NSNumber actually +// is a tagged pointer (which means this test is out of date). + +int main() +{ + PUSH_POOL { + testassert(*(void **)(__bridge void *)[NSNumber numberWithInt:1234]); + } POP_POOL; + + succeed(__FILE__); +} + +#endif diff --git a/test/taggedPointers.m b/test/taggedPointers.m new file mode 100644 index 0000000..76f1617 --- /dev/null +++ b/test/taggedPointers.m @@ -0,0 +1,356 @@ +// TEST_CFLAGS -fobjc-weak + +#include "test.h" +#include +#include +#include +#include +#import + +#if OBJC_HAVE_TAGGED_POINTERS + +#if !__x86_64__ && !__arm64__ +#error wrong architecture for tagged pointers +#endif + +static BOOL didIt; + +@interface WeakContainer : NSObject +{ + @public + __weak id weaks[10000]; +} +@end +@implementation WeakContainer +-(void) dealloc { + for (unsigned int i = 0; i < sizeof(weaks)/sizeof(weaks[0]); i++) { + testassert(weaks[i] == nil); + } + SUPER_DEALLOC(); +} +@end + +OBJC_ROOT_CLASS +@interface TaggedBaseClass60 +@end + +@implementation TaggedBaseClass60 +-(id) self { return self; } + ++ (void) initialize { +} + +- (void) instanceMethod { + didIt = YES; +} + +- (uintptr_t) taggedValue { + return _objc_getTaggedPointerValue((__bridge void*)self); +} + +- (struct stret) stret: (struct stret) aStruct { + return aStruct; +} + +- (long double) fpret: (long double) aValue { + return aValue; +} + + +-(void) dealloc { + fail("TaggedBaseClass60 dealloc called!"); +} + +static void * +retain_fn(void *self, SEL _cmd __unused) { + void * (*fn)(void *) = (typeof(fn))_objc_rootRetain; + return fn(self); +} + +static void +release_fn(void *self, SEL _cmd __unused) { + void (*fn)(void *) = (typeof(fn))_objc_rootRelease; + fn(self); +} + +static void * +autorelease_fn(void *self, SEL _cmd __unused) { + void * (*fn)(void *) = (typeof(fn))_objc_rootAutorelease; + return fn(self); +} + +static unsigned long +retaincount_fn(void *self, SEL _cmd __unused) { + unsigned long (*fn)(void *) = (typeof(fn))_objc_rootRetainCount; + return fn(self); +} + ++(void) load { + class_addMethod(self, sel_registerName("retain"), (IMP)retain_fn, ""); + class_addMethod(self, sel_registerName("release"), (IMP)release_fn, ""); + class_addMethod(self, sel_registerName("autorelease"), (IMP)autorelease_fn, ""); + class_addMethod(self, sel_registerName("retainCount"), (IMP)retaincount_fn, ""); +} + +@end + +@interface TaggedSubclass52: TaggedBaseClass60 +@end + +@implementation TaggedSubclass52 + +- (void) instanceMethod { + return [super instanceMethod]; +} + +- (uintptr_t) taggedValue { + return [super taggedValue]; +} + +- (struct stret) stret: (struct stret) aStruct { + return [super stret: aStruct]; +} + +- (long double) fpret: (long double) aValue { + return [super fpret: aValue]; +} +@end + +@interface TaggedNSObjectSubclass : NSObject +@end + +@implementation TaggedNSObjectSubclass + +- (void) instanceMethod { + didIt = YES; +} + +- (uintptr_t) taggedValue { + return _objc_getTaggedPointerValue((__bridge void*)self); +} + +- (struct stret) stret: (struct stret) aStruct { + return aStruct; +} + +- (long double) fpret: (long double) aValue { + return aValue; +} +@end + +void testTaggedPointerValue(Class cls, objc_tag_index_t tag, uintptr_t value) +{ + void *taggedAddress = _objc_makeTaggedPointer(tag, value); + testprintf("obj %p, tag %p, value %p\n", + taggedAddress, (void*)tag, (void*)value); + + bool ext = (tag >= OBJC_TAG_First52BitPayload); + + // _objc_makeTaggedPointer must quietly mask out of range values for now + if (ext) { + value = (value << 12) >> 12; + } else { + value = (value << 4) >> 4; + } + + testassert(_objc_isTaggedPointer(taggedAddress)); + testassert(_objc_getTaggedPointerTag(taggedAddress) == tag); + testassert(_objc_getTaggedPointerValue(taggedAddress) == value); + testassert(objc_debug_taggedpointer_obfuscator != 0); + + if (ext) { + uintptr_t slot = ((uintptr_t)taggedAddress >> objc_debug_taggedpointer_ext_slot_shift) & objc_debug_taggedpointer_ext_slot_mask; + testassert(objc_debug_taggedpointer_ext_classes[slot] == cls); + uintptr_t deobfuscated = (uintptr_t)taggedAddress ^ objc_debug_taggedpointer_obfuscator; + testassert(((deobfuscated << objc_debug_taggedpointer_ext_payload_lshift) >> objc_debug_taggedpointer_ext_payload_rshift) == value); + } + else { + testassert(((uintptr_t)taggedAddress & objc_debug_taggedpointer_mask) == objc_debug_taggedpointer_mask); + uintptr_t slot = ((uintptr_t)taggedAddress >> objc_debug_taggedpointer_slot_shift) & objc_debug_taggedpointer_slot_mask; + testassert(objc_debug_taggedpointer_classes[slot] == cls); + uintptr_t deobfuscated = (uintptr_t)taggedAddress ^ objc_debug_taggedpointer_obfuscator; + testassert(((deobfuscated << objc_debug_taggedpointer_payload_lshift) >> objc_debug_taggedpointer_payload_rshift) == value); + } + + id taggedPointer = (__bridge id)taggedAddress; + testassert(!object_isClass(taggedPointer)); + testassert(object_getClass(taggedPointer) == cls); + testassert([taggedPointer taggedValue] == value); + + didIt = NO; + [taggedPointer instanceMethod]; + testassert(didIt); + + struct stret orig = STRET_RESULT; + testassert(stret_equal(orig, [taggedPointer stret: orig])); + + long double dblvalue = 3.14156789; + testassert(dblvalue == [taggedPointer fpret: dblvalue]); + + objc_setAssociatedObject(taggedPointer, (__bridge void *)taggedPointer, taggedPointer, OBJC_ASSOCIATION_RETAIN); + testassert(objc_getAssociatedObject(taggedPointer, (__bridge void *)taggedPointer) == taggedPointer); + objc_setAssociatedObject(taggedPointer, (__bridge void *)taggedPointer, nil, OBJC_ASSOCIATION_RETAIN); + testassert(objc_getAssociatedObject(taggedPointer, (__bridge void *)taggedPointer) == nil); +} + +void testGenericTaggedPointer(objc_tag_index_t tag, Class cls) +{ + testassert(cls); + testprintf("%s\n", class_getName(cls)); + + testTaggedPointerValue(cls, tag, 0); + testTaggedPointerValue(cls, tag, 1UL << 0); + testTaggedPointerValue(cls, tag, 1UL << 1); + testTaggedPointerValue(cls, tag, 1UL << 50); + testTaggedPointerValue(cls, tag, 1UL << 51); + testTaggedPointerValue(cls, tag, 1UL << 52); + testTaggedPointerValue(cls, tag, 1UL << 58); + testTaggedPointerValue(cls, tag, 1UL << 59); + testTaggedPointerValue(cls, tag, ~0UL >> 4); + testTaggedPointerValue(cls, tag, ~0UL); + + // Tagged pointers should bypass refcount tables and autorelease pools + // and weak reference tables + WeakContainer *w = [WeakContainer new]; + + // force sidetable retain of the WeakContainer before leak checking + objc_retain(w); +#if !__has_feature(objc_arc) + // prime method caches before leak checking + id taggedPointer = (id)_objc_makeTaggedPointer(tag, 1234); + [taggedPointer retain]; + [taggedPointer release]; + [taggedPointer autorelease]; +#endif + // prime is_debug() before leak checking + (void)is_debug(); + + leak_mark(); + testonthread(^(void) { + for (uintptr_t i = 0; i < sizeof(w->weaks)/sizeof(w->weaks[0]); i++) { + id o = (__bridge id)_objc_makeTaggedPointer(tag, i); + testassert(object_getClass(o) == cls); + + id result = WEAK_STORE(w->weaks[i], o); + testassert(result == o); + testassert(w->weaks[i] == o); + + result = WEAK_LOAD(w->weaks[i]); + testassert(result == o); + + uintptr_t rc = _objc_rootRetainCount(o); + testassert(rc != 0); + _objc_rootRelease(o); testassert(_objc_rootRetainCount(o) == rc); + _objc_rootRelease(o); testassert(_objc_rootRetainCount(o) == rc); + _objc_rootRetain(o); testassert(_objc_rootRetainCount(o) == rc); + _objc_rootRetain(o); testassert(_objc_rootRetainCount(o) == rc); + _objc_rootRetain(o); testassert(_objc_rootRetainCount(o) == rc); +#if !__has_feature(objc_arc) + [o release]; testassert(_objc_rootRetainCount(o) == rc); + [o release]; testassert(_objc_rootRetainCount(o) == rc); + [o retain]; testassert(_objc_rootRetainCount(o) == rc); + [o retain]; testassert(_objc_rootRetainCount(o) == rc); + [o retain]; testassert(_objc_rootRetainCount(o) == rc); + objc_release(o); testassert(_objc_rootRetainCount(o) == rc); + objc_release(o); testassert(_objc_rootRetainCount(o) == rc); + objc_retain(o); testassert(_objc_rootRetainCount(o) == rc); + objc_retain(o); testassert(_objc_rootRetainCount(o) == rc); + objc_retain(o); testassert(_objc_rootRetainCount(o) == rc); +#endif + PUSH_POOL { + testassert(_objc_rootRetainCount(o) == rc); + _objc_rootAutorelease(o); + testassert(_objc_rootRetainCount(o) == rc); +#if !__has_feature(objc_arc) + [o autorelease]; + testassert(_objc_rootRetainCount(o) == rc); + objc_autorelease(o); + testassert(_objc_rootRetainCount(o) == rc); + objc_retainAutorelease(o); + testassert(_objc_rootRetainCount(o) == rc); + objc_autoreleaseReturnValue(o); + testassert(_objc_rootRetainCount(o) == rc); + objc_retainAutoreleaseReturnValue(o); + testassert(_objc_rootRetainCount(o) == rc); + objc_retainAutoreleasedReturnValue(o); + testassert(_objc_rootRetainCount(o) == rc); +#endif + } POP_POOL; + testassert(_objc_rootRetainCount(o) == rc); + } + }); + if (is_debug()) { + // libobjc's debug lock checking makes this leak check fail + testwarn("skipping leak check with debug libobjc build"); + } else { + leak_check(0); + } + for (uintptr_t i = 0; i < 10000; i++) { + testassert(w->weaks[i] != NULL); + WEAK_STORE(w->weaks[i], NULL); + testassert(w->weaks[i] == NULL); + testassert(WEAK_LOAD(w->weaks[i]) == NULL); + } + objc_release(w); + RELEASE_VAR(w); +} + +int main() +{ + testassert(objc_debug_taggedpointer_mask != 0); + testassert(_objc_taggedPointersEnabled()); + + PUSH_POOL { + // Avoid CF's tagged pointer tags because of rdar://11368528 + + // Reserved slot should be nil until the + // first extended tag is registered. + // This test no longer works because XPC now uses extended tags. +#define HAVE_XPC_TAGS 1 + + uintptr_t extSlot = (~objc_debug_taggedpointer_obfuscator >> objc_debug_taggedpointer_slot_shift) & objc_debug_taggedpointer_slot_mask; + Class extPlaceholder = objc_getClass("__NSUnrecognizedTaggedPointer"); + testassert(extPlaceholder != nil); + +#if !HAVE_XPC_TAGS + testassert(objc_debug_taggedpointer_classes[extSlot] == nil); +#endif + + _objc_registerTaggedPointerClass(OBJC_TAG_1, + objc_getClass("TaggedBaseClass60")); + testGenericTaggedPointer(OBJC_TAG_1, + objc_getClass("TaggedBaseClass60")); + +#if !HAVE_XPC_TAGS + testassert(objc_debug_taggedpointer_classes[extSlot] == nil); +#endif + + _objc_registerTaggedPointerClass(OBJC_TAG_First52BitPayload, + objc_getClass("TaggedSubclass52")); + testGenericTaggedPointer(OBJC_TAG_First52BitPayload, + objc_getClass("TaggedSubclass52")); + + testassert(objc_debug_taggedpointer_classes[extSlot] == extPlaceholder); + + _objc_registerTaggedPointerClass(OBJC_TAG_NSManagedObjectID, + objc_getClass("TaggedNSObjectSubclass")); + testGenericTaggedPointer(OBJC_TAG_NSManagedObjectID, + objc_getClass("TaggedNSObjectSubclass")); + } POP_POOL; + + succeed(__FILE__); +} + +// OBJC_HAVE_TAGGED_POINTERS +#else +// not OBJC_HAVE_TAGGED_POINTERS + +// Tagged pointers not supported. + +int main() +{ + testassert(objc_debug_taggedpointer_mask == 0); + succeed(__FILE__); +} + +#endif diff --git a/test/taggedPointersAllClasses.m b/test/taggedPointersAllClasses.m new file mode 100644 index 0000000..9c02840 --- /dev/null +++ b/test/taggedPointersAllClasses.m @@ -0,0 +1,86 @@ +// TEST_CONFIG + +#include "test.h" +#include "testroot.i" +#include +#include + +#if OBJC_HAVE_TAGGED_POINTERS + +@interface TagSuperclass: TestRoot + +- (void)test; + +@end + +@implementation TagSuperclass + +- (void)test {} + +@end + +int main() +{ + Class classes[OBJC_TAG_Last52BitPayload + 1] = {}; + + __block uintptr_t expectedPayload; + __block uintptr_t sawPayload; + __block int sawTag; + + for (int i = 0; i <= OBJC_TAG_Last52BitPayload; i++) { + objc_tag_index_t tag = (objc_tag_index_t)i; + if (i > OBJC_TAG_Last60BitPayload && i < OBJC_TAG_First52BitPayload) + continue; + if (_objc_getClassForTag(tag) != nil) + continue; + + char *name; + asprintf(&name, "Tag%d", i); + classes[i] = objc_allocateClassPair([TagSuperclass class], name, 0); + free(name); + + IMP testIMP = imp_implementationWithBlock(^(void *self) { + testassert(i == _objc_getTaggedPointerTag(self)); + testassert(expectedPayload == _objc_getTaggedPointerValue(self)); + sawPayload = _objc_getTaggedPointerValue(self); + sawTag = i; + }); + class_addMethod(classes[i], @selector(test), testIMP, "v@@"); + + objc_registerClassPair(classes[i]); + _objc_registerTaggedPointerClass(tag, classes[i]); + } + + for (int i = 0; i <= OBJC_TAG_Last52BitPayload; i++) { + objc_tag_index_t tag = (objc_tag_index_t)i; + if (classes[i] == nil) + continue; + + for (int byte = 0; byte <= 0xff; byte++) { + uintptr_t payload; + memset(&payload, byte, sizeof(payload)); + + if (i <= OBJC_TAG_Last60BitPayload) + payload >>= _OBJC_TAG_PAYLOAD_RSHIFT; + else + payload >>= _OBJC_TAG_EXT_PAYLOAD_RSHIFT; + + expectedPayload = payload; + id obj = (__bridge id)_objc_makeTaggedPointer(tag, payload); + [obj test]; + testassert(sawPayload == payload); + testassert(sawTag == i); + } + } + + succeed(__FILE__); +} + +#else + +int main() +{ + succeed(__FILE__); +} + +#endif diff --git a/test/taggedPointersDisabled.m b/test/taggedPointersDisabled.m new file mode 100644 index 0000000..958cc7d --- /dev/null +++ b/test/taggedPointersDisabled.m @@ -0,0 +1,39 @@ +/* +TEST_ENV OBJC_DISABLE_TAGGED_POINTERS=YES +TEST_CRASHES + +TEST_BUILD_OUTPUT +.*taggedPointersDisabled.m:\d+:\d+: warning: null passed to a callee that requires a non-null argument \[-Wnonnull\] +END + +TEST_RUN_OUTPUT +objc\[\d+\]: tagged pointers are disabled +objc\[\d+\]: HALTED +OR +OK: taggedPointersDisabled.m +END +*/ + +#include "test.h" +#include + +#if !OBJC_HAVE_TAGGED_POINTERS + +int main() +{ + // provoke the same nullability warning as the real test + objc_getClass(nil); + + succeed(__FILE__); +} + +#else + +int main() +{ + testassert(!_objc_taggedPointersEnabled()); + _objc_registerTaggedPointerClass((objc_tag_index_t)0, nil); + fail("should have crashed in _objc_registerTaggedPointerClass()"); +} + +#endif diff --git a/test/taggedPointersTagObfuscationDisabled.m b/test/taggedPointersTagObfuscationDisabled.m new file mode 100644 index 0000000..a3aad8b --- /dev/null +++ b/test/taggedPointersTagObfuscationDisabled.m @@ -0,0 +1,21 @@ +// TEST_ENV OBJC_DISABLE_TAG_OBFUSCATION=YES + +#include "test.h" +#include + +#if !OBJC_HAVE_TAGGED_POINTERS + +int main() +{ + succeed(__FILE__); +} + +#else + +int main() +{ + testassert(_objc_getTaggedPointerTag((void *)1) == 0); + succeed(__FILE__); +} + +#endif diff --git a/test/tbi.c b/test/tbi.c new file mode 100644 index 0000000..93c2022 --- /dev/null +++ b/test/tbi.c @@ -0,0 +1,14 @@ +// TEST_CONFIG OS=iphoneos ARCH=arm64 + +#include "test.h" + +#ifndef __arm64__ +#error wrong architecture for TBI hardware feature +#endif + +volatile int x = 123456; + +int main(void) { + testassert(*(int *)((unsigned long)&x | 0xFF00000000000000ul) == 123456); + succeed(__FILE__); +} diff --git a/test/test.h b/test/test.h new file mode 100644 index 0000000..f4332a1 --- /dev/null +++ b/test/test.h @@ -0,0 +1,469 @@ +// test.h +// Common definitions for trivial test harness + + +#ifndef TEST_H +#define TEST_H + +#include +#include +#include +#include +#include +#include +#include +#include +#if __cplusplus +#include +using namespace std; +#else +#include +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if __has_include() +# include +#endif + +#include "../runtime/isa.h" + +#if __cplusplus +# define EXTERN_C extern "C" +#else +# define EXTERN_C /*empty*/ +#endif + + +// Test output + +static inline void succeed(const char *name) __attribute__((noreturn)); +static inline void succeed(const char *name) +{ + if (name) { + char path[MAXPATHLEN+1]; + strcpy(path, name); + fprintf(stderr, "OK: %s\n", basename(path)); + } else { + fprintf(stderr, "OK\n"); + } + exit(0); +} + +static inline void fail(const char *msg, ...) __attribute__((noreturn)); +static inline void fail(const char *msg, ...) +{ + if (msg) { + char *msg2; + asprintf(&msg2, "BAD: %s\n", msg); + va_list v; + va_start(v, msg); + vfprintf(stderr, msg2, v); + va_end(v); + free(msg2); + } else { + fprintf(stderr, "BAD\n"); + } + exit(1); +} + +#define testassert(cond) \ + ((void) (((cond) != 0) ? (void)0 : __testassert(#cond, __FILE__, __LINE__))) +#define __testassert(cond, file, line) \ + (fail("failed assertion '%s' at %s:%u", cond, __FILE__, __LINE__)) + +/* time-sensitive assertion, disabled under valgrind */ +#define timecheck(name, time, fast, slow) \ + if (getenv("VALGRIND") && 0 != strcmp(getenv("VALGRIND"), "NO")) { \ + /* valgrind; do nothing */ \ + } else if (time > slow) { \ + fprintf(stderr, "SLOW: %s %llu, expected %llu..%llu\n", \ + name, (uint64_t)(time), (uint64_t)(fast), (uint64_t)(slow)); \ + } else if (time < fast) { \ + fprintf(stderr, "FAST: %s %llu, expected %llu..%llu\n", \ + name, (uint64_t)(time), (uint64_t)(fast), (uint64_t)(slow)); \ + } else { \ + testprintf("time: %s %llu, expected %llu..%llu\n", \ + name, (uint64_t)(time), (uint64_t)(fast), (uint64_t)(slow)); \ + } + + +// Return true if testprintf() output is enabled. +static inline bool testverbose(void) +{ + static int verbose = -1; + if (verbose < 0) verbose = atoi(getenv("VERBOSE") ?: "0"); + + // VERBOSE=1 prints test harness info only + // VERBOSE=2 prints test info + return verbose >= 2; +} + +// Print debugging info when VERBOSE=2 is set, +// without disturbing the test's expected output. +static inline void testprintf(const char *msg, ...) +{ + if (msg && testverbose()) { + char *msg2; + asprintf(&msg2, "VERBOSE: %s", msg); + va_list v; + va_start(v, msg); + vfprintf(stderr, msg2, v); + va_end(v); + free(msg2); + } +} + +// complain to output, but don't fail the test +// Use when warning that some test is being temporarily skipped +// because of something like a compiler bug. +static inline void testwarn(const char *msg, ...) +{ + if (msg) { + char *msg2; + asprintf(&msg2, "WARN: %s\n", msg); + va_list v; + va_start(v, msg); + vfprintf(stderr, msg2, v); + va_end(v); + free(msg2); + } +} + +static inline void testnoop() { } + +// Prevent deprecation warnings from some runtime functions. + +static inline void test_objc_flush_caches(Class cls) +{ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" + _objc_flush_caches(cls); +#pragma clang diagnostic pop +} +#define _objc_flush_caches(c) test_objc_flush_caches(c) + + +static inline Class test_class_setSuperclass(Class cls, Class supercls) +{ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" + return class_setSuperclass(cls, supercls); +#pragma clang diagnostic pop +} +#define class_setSuperclass(c, s) test_class_setSuperclass(c, s) + + +static inline void testcollect() +{ + _objc_flush_caches(nil); +} + + +// Synchronously run test code on another thread. + +// The block object is unsafe_unretained because we must not allow +// ARC to retain them in non-Foundation tests +typedef void(^testblock_t)(void); +static __unsafe_unretained testblock_t testcodehack; +static inline void *_testthread(void *arg __unused) +{ + testcodehack(); + return NULL; +} +static inline void testonthread(__unsafe_unretained testblock_t code) +{ + pthread_t th; + testcodehack = code; // force GC not-thread-local, avoid ARC void* casts + pthread_create(&th, NULL, _testthread, NULL); + pthread_join(th, NULL); +} + +/* Make sure libobjc does not call global operator new. + Any test that DOES need to call global operator new must + `#define TEST_CALLS_OPERATOR_NEW` before including test.h. + */ +#if __cplusplus && !defined(TEST_CALLS_OPERATOR_NEW) +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Winline-new-delete" +#import +inline void* operator new(std::size_t) throw (std::bad_alloc) { fail("called global operator new"); } +inline void* operator new[](std::size_t) throw (std::bad_alloc) { fail("called global operator new[]"); } +inline void* operator new(std::size_t, const std::nothrow_t&) throw() { fail("called global operator new(nothrow)"); } +inline void* operator new[](std::size_t, const std::nothrow_t&) throw() { fail("called global operator new[](nothrow)"); } +inline void operator delete(void*) throw() { fail("called global operator delete"); } +inline void operator delete[](void*) throw() { fail("called global operator delete[]"); } +inline void operator delete(void*, const std::nothrow_t&) throw() { fail("called global operator delete(nothrow)"); } +inline void operator delete[](void*, const std::nothrow_t&) throw() { fail("called global operator delete[](nothrow)"); } +#pragma clang diagnostic pop +#endif + + +/* Leak checking + Fails if total malloc memory in use at leak_check(n) + is more than n bytes above that at leak_mark(). +*/ + +static inline void leak_recorder(task_t task __unused, void *ctx, unsigned type __unused, vm_range_t *ranges, unsigned count) +{ + size_t *inuse = (size_t *)ctx; + while (count--) { + *inuse += ranges[count].size; + } +} + +static inline size_t leak_inuse(void) +{ + size_t total = 0; + vm_address_t *zones; + unsigned count; + malloc_get_all_zones(mach_task_self(), NULL, &zones, &count); + for (unsigned i = 0; i < count; i++) { + size_t inuse = 0; + malloc_zone_t *zone = (malloc_zone_t *)zones[i]; + if (!zone->introspect || !zone->introspect->enumerator) continue; + + // skip DispatchContinuations because it sometimes claims to be + // using lots of memory that then goes away later + if (0 == strcmp(zone->zone_name, "DispatchContinuations")) continue; + + zone->introspect->enumerator(mach_task_self(), &inuse, MALLOC_PTR_IN_USE_RANGE_TYPE, (vm_address_t)zone, NULL, leak_recorder); + // fprintf(stderr, "%zu in use for zone %s\n", inuse, zone->zone_name); + total += inuse; + } + + return total; +} + + +static inline void leak_dump_heap(const char *msg) +{ + fprintf(stderr, "%s\n", msg); + + // Make `heap` write to stderr + int outfd = dup(STDOUT_FILENO); + dup2(STDERR_FILENO, STDOUT_FILENO); + pid_t pid = getpid(); + char cmd[256]; + // environment variables reset for iOS simulator use + sprintf(cmd, "DYLD_LIBRARY_PATH= DYLD_ROOT_PATH= /usr/bin/heap -addresses all %d", (int)pid); + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" + system(cmd); +#pragma clang diagnostic pop + + dup2(outfd, STDOUT_FILENO); + close(outfd); +} + +static size_t _leak_start; +static inline void leak_mark(void) +{ + testcollect(); + if (getenv("LEAK_HEAP")) { + leak_dump_heap("HEAP AT leak_mark"); + } + _leak_start = leak_inuse(); +} + +#define leak_check(n) \ + do { \ + const char *_check = getenv("LEAK_CHECK"); \ + size_t inuse; \ + if (_check && 0 == strcmp(_check, "NO")) break; \ + testcollect(); \ + if (getenv("LEAK_HEAP")) { \ + leak_dump_heap("HEAP AT leak_check"); \ + } \ + inuse = leak_inuse(); \ + if (inuse > _leak_start + n) { \ + fprintf(stderr, "BAD: %zu bytes leaked at %s:%u " \ + "(try LEAK_HEAP and HANG_ON_LEAK to debug)\n", \ + inuse - _leak_start, __FILE__, __LINE__); \ + if (getenv("HANG_ON_LEAK")) { \ + fprintf(stderr, "Hanging after leaks detected. " \ + "Leaks command:\n"); \ + fprintf(stderr, "leaks %d\n", getpid()); \ + while (1) sleep(1); \ + } \ + } \ + } while (0) + +// true when running under Guard Malloc +static inline bool is_guardmalloc(void) +{ + const char *env = getenv("GUARDMALLOC"); + return (env && 0 == strcmp(env, "1")); +} + +// true when running a debug build of libobjc +static inline bool is_debug(void) +{ + static int debugness = -1; + if (debugness == -1) { + debugness = dlsym(RTLD_DEFAULT, "_objc_isDebugBuild") ? 1 : 0; + } + return (bool)debugness; +} + + +/* Memory management compatibility macros */ + +static id self_fn(id x) __attribute__((used)); +static id self_fn(id x) { return x; } + +#if __has_feature(objc_arc_weak) + // __weak +# define WEAK_STORE(dst, val) (dst = (val)) +# define WEAK_LOAD(src) (src) +#else + // no __weak +# define WEAK_STORE(dst, val) objc_storeWeak((id *)&dst, val) +# define WEAK_LOAD(src) objc_loadWeak((id *)&src) +#endif + +#if __has_feature(objc_arc) + // ARC +# define RELEASE_VAR(x) x = nil +# define SUPER_DEALLOC() +# define RETAIN(x) (self_fn(x)) +# define RELEASE_VALUE(x) ((void)self_fn(x)) +# define AUTORELEASE(x) (self_fn(x)) + +#else + // MRC +# define RELEASE_VAR(x) do { [x release]; x = nil; } while (0) +# define SUPER_DEALLOC() [super dealloc] +# define RETAIN(x) [x retain] +# define RELEASE_VALUE(x) [x release] +# define AUTORELEASE(x) [x autorelease] +#endif + +/* gcc compatibility macros */ +/* @autoreleasepool should generate objc_autoreleasePoolPush/Pop on 10.7/5.0 */ +//#if !defined(__clang__) +# define PUSH_POOL { void *pool = objc_autoreleasePoolPush(); +# define POP_POOL objc_autoreleasePoolPop(pool); } +//#else +//# define PUSH_POOL @autoreleasepool +//# define POP_POOL +//#endif + +#if __OBJC__ + +/* General purpose root class */ + +OBJC_ROOT_CLASS +@interface TestRoot { + @public + Class isa; +} + ++(void) load; ++(void) initialize; + +-(id) self; +-(Class) class; +-(Class) superclass; + ++(id) new; ++(id) alloc; ++(id) allocWithZone:(void*)zone; +-(id) copy; +-(id) mutableCopy; +-(id) init; +-(void) dealloc; +@end +@interface TestRoot (RR) +-(id) retain; +-(oneway void) release; +-(id) autorelease; +-(unsigned long) retainCount; +-(id) copyWithZone:(void *)zone; +-(id) mutableCopyWithZone:(void*)zone; +@end + +// incremented for each call of TestRoot's methods +extern atomic_int TestRootLoad; +extern atomic_int TestRootInitialize; +extern atomic_int TestRootAlloc; +extern atomic_int TestRootAllocWithZone; +extern atomic_int TestRootCopy; +extern atomic_int TestRootCopyWithZone; +extern atomic_int TestRootMutableCopy; +extern atomic_int TestRootMutableCopyWithZone; +extern atomic_int TestRootInit; +extern atomic_int TestRootDealloc; +extern atomic_int TestRootRetain; +extern atomic_int TestRootRelease; +extern atomic_int TestRootAutorelease; +extern atomic_int TestRootRetainCount; +extern atomic_int TestRootTryRetain; +extern atomic_int TestRootIsDeallocating; +extern atomic_int TestRootPlusRetain; +extern atomic_int TestRootPlusRelease; +extern atomic_int TestRootPlusAutorelease; +extern atomic_int TestRootPlusRetainCount; + +#endif + + +// Struct that does not return in registers on any architecture + +struct stret { + int a; + int b; + int c; + int d; + int e; + int f; + int g; + int h; + int i; + int j; +}; + +static inline BOOL stret_equal(struct stret a, struct stret b) +{ + return (a.a == b.a && + a.b == b.b && + a.c == b.c && + a.d == b.d && + a.e == b.e && + a.f == b.f && + a.g == b.g && + a.h == b.h && + a.i == b.i && + a.j == b.j); +} + +static struct stret STRET_RESULT __attribute__((used)) = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; + + +#if TARGET_OS_SIMULATOR +// Force cwd to executable's directory during launch. +// sim used to do this but simctl does not. +#include + __attribute__((constructor)) +static void hack_cwd(void) +{ + if (!getenv("HACKED_CWD")) { + chdir(dirname((*_NSGetArgv())[0])); + setenv("HACKED_CWD", "1", 1); + } +} +#endif + +#endif diff --git a/test/test.pl b/test/test.pl new file mode 100755 index 0000000..a72a6a7 --- /dev/null +++ b/test/test.pl @@ -0,0 +1,1715 @@ +#!/usr/bin/perl + +# test.pl +# Run unit tests. + +use strict; +use File::Basename; + +# We use encode_json() to write BATS plist files. +# JSON::PP does not exist on iOS devices, but we need not write plists there. +# So we simply load JSON:PP if it exists. +if (eval { require JSON::PP; 1; }) { + JSON::PP->import(); +} + + +chdir dirname $0; +chomp (my $DIR = `pwd`); + +if (scalar(@ARGV) == 1) { + my $arg = $ARGV[0]; + if ($arg eq "-h" || $arg eq "-H" || $arg eq "-help" || $arg eq "help") { + print(< + OS=[sdk version][-[-]] + ROOT=/path/to/project.roots/ + + CC= + + LANGUAGE=c,c++,objective-c,objective-c++,swift + MEM=mrc,arc + GUARDMALLOC=0|1|before|after + + BUILD=0|1 (build the tests?) + RUN=0|1 (run the tests?) + VERBOSE=0|1|2 (0=quieter 1=print commands executed 2=full test output) + BATS=0|1 (build for and/or run in BATS?) + +examples: + + test installed library, x86_64 + $0 + + test buildit-built root, i386 and x86_64, MRC and ARC, clang compiler + $0 ARCH=i386,x86_64 ROOT=/tmp/objc4.roots MEM=mrc,arc CC=clang + + test buildit-built root with iOS simulator, deploy to iOS 7, run on iOS 8 + $0 ARCH=x86_64 ROOT=/tmp/objc4.roots OS=iphonesimulator-7.0-8.0 + + test buildit-built root on attached iOS device + $0 ARCH=arm64 ROOT=/tmp/objc4.roots OS=iphoneos +END + exit 0; + } +} + +######################################################################### +## Tests + +# Maps test name => test's filename extension. +# ex: "msgSend" => "m" +# `keys %ALL_TESTS` is also used as the list of all tests found on disk. +my %ALL_TESTS; + +######################################################################### +## Variables for use in complex build and run rules + +# variable # example value + +# things you can multiplex on the command line +# ARCH=i386,x86_64,armv6,armv7 +# OS=macosx,iphoneos,iphonesimulator (plus sdk/deployment/run versions) +# LANGUAGE=c,c++,objective-c,objective-c++,swift +# CC=clang +# MEM=mrc,arc +# GUARDMALLOC=0,1,before,after + +# things you can set once on the command line +# ROOT=/path/to/project.roots +# BUILD=0|1 +# RUN=0|1 +# VERBOSE=0|1|2 +# BATS=0|1 + +# environment variables from the command line +# DSTROOT +# OBJROOT +# (SRCROOT is ignored; test sources are assumed to +# be in the same directory as the test script itself.) +# fixme SYMROOT for dsymutil output? + + +# Some arguments as read from the command line. +my %args; +my $BUILD; +my $RUN; +my $VERBOSE; +my $BATS; + +my @TESTLIBNAMES = ("libobjc.A.dylib", "libobjc-trampolines.dylib"); +my $TESTLIBDIR = "/usr/lib"; + +# Top level directory for intermediate and final build products. +# Intermediate files must be kept separate for XBS BATS builds. +my $OBJROOT = $ENV{OBJROOT} || ""; +my $DSTROOT = $ENV{DSTROOT} || ""; + +# Build product directory inside DSTROOT and OBJROOT. +# Each test config gets its own build directory inside this. +my $BUILDDIR; + +# Local top-level directory. +# This is the default value for $BUILDDIR. +my $LOCALBASE = "/tmp/test-$TESTLIBNAMES[0]-build"; + +# Device-side top-level directory. +# This replaces $DSTROOT$BUILDDIR/ for on-device execution. +my $REMOTEBASE = "/AppleInternal/objctest"; + +# BATS top-level directory. +# This replaces $DSTROOT$BUILDDIR/ for BATS execution. +my $BATSBASE = "/AppleInternal/CoreOS/tests/objc4"; + + +my $crashcatch = <<'END'; +// interpose-able code to catch crashes, print, and exit cleanly +#include +#include +#include + +// from dyld-interposing.h +#define DYLD_INTERPOSE(_replacement,_replacee) __attribute__((used)) static struct{ const void* replacement; const void* replacee; } _interpose_##_replacee __attribute__ ((section ("__DATA,__interpose"))) = { (const void*)(unsigned long)&_replacement, (const void*)(unsigned long)&_replacee }; + +static void catchcrash(int sig) +{ + const char *msg; + switch (sig) { + case SIGILL: msg = "CRASHED: SIGILL"; break; + case SIGBUS: msg = "CRASHED: SIGBUS"; break; + case SIGSYS: msg = "CRASHED: SIGSYS"; break; + case SIGSEGV: msg = "CRASHED: SIGSEGV"; break; + case SIGTRAP: msg = "CRASHED: SIGTRAP"; break; + case SIGABRT: msg = "CRASHED: SIGABRT"; break; + default: msg = "unknown signal"; break; + } + write(STDERR_FILENO, msg, strlen(msg)); + + // avoid backslash-n newline due to escaping differences somewhere + // in BATS versus local execution (perhaps different perl versions?) + char newline = 0xa; + write(STDERR_FILENO, &newline, 1); + + _exit(1); +} + +static void setupcrash(void) __attribute__((constructor)); +static void setupcrash(void) +{ + signal(SIGILL, &catchcrash); + signal(SIGBUS, &catchcrash); + signal(SIGSYS, &catchcrash); + signal(SIGSEGV, &catchcrash); + signal(SIGTRAP, &catchcrash); + signal(SIGABRT, &catchcrash); +} + + +static int hacked = 0; +ssize_t hacked_write(int fildes, const void *buf, size_t nbyte) +{ + if (!hacked) { + setupcrash(); + hacked = 1; + } + return write(fildes, buf, nbyte); +} + +DYLD_INTERPOSE(hacked_write, write); + +END + + +######################################################################### +## Harness + + +# map language to buildable extensions for that language +my %extensions_for_language = ( + "c" => ["c"], + "objective-c" => ["c", "m"], + "c++" => ["c", "cc", "cp", "cpp", "cxx", "c++"], + "objective-c++" => ["c", "m", "cc", "cp", "cpp", "cxx", "c++", "mm"], + "swift" => ["swift"], + + "any" => ["c", "m", "cc", "cp", "cpp", "cxx", "c++", "mm", "swift"], + ); + +# map extension to languages +my %languages_for_extension = ( + "c" => ["c", "objective-c", "c++", "objective-c++"], + "m" => ["objective-c", "objective-c++"], + "mm" => ["objective-c++"], + "cc" => ["c++", "objective-c++"], + "cp" => ["c++", "objective-c++"], + "cpp" => ["c++", "objective-c++"], + "cxx" => ["c++", "objective-c++"], + "c++" => ["c++", "objective-c++"], + "swift" => ["swift"], + ); + +# Run some newline-separated commands like `make` would, stopping if any fail +# run("cmd1 \n cmd2 \n cmd3") +sub make { + my $output = ""; + my @cmds = split("\n", $_[0]); + die if scalar(@cmds) == 0; + $? = 0; + foreach my $cmd (@cmds) { + chomp $cmd; + next if $cmd =~ /^\s*$/; + $cmd .= " 2>&1"; + print "$cmd\n" if $VERBOSE; + $output .= `$cmd`; + last if $?; + } + print "$output\n" if $VERBOSE; + return $output; +} + +sub chdir_verbose { + my $dir = shift || die; + print "cd $dir\n" if $VERBOSE; + chdir $dir || die "couldn't cd $dir"; +} + +sub rm_rf_verbose { + my $dir = shift || die; + print "mkdir -p $dir\n" if $VERBOSE; + `rm -rf '$dir'`; + die "couldn't rm -rf $dir" if $?; +} + +sub mkdir_verbose { + my $dir = shift || die; + print "mkdir -p $dir\n" if $VERBOSE; + `mkdir -p '$dir'`; + die "couldn't mkdir $dir" if $?; +} + + +# xterm colors +my $red = "\e[41;37m"; +my $yellow = "\e[43;30m"; +my $nocolor = "\e[0m"; +if (! -t STDIN) { + # Not isatty. Don't use colors. + $red = ""; + $yellow = ""; + $nocolor = ""; +} + +# print text with a colored prefix on each line +# fixme some callers pass an array of lines and some don't +sub colorprefix { + my $color = shift; + while (defined(my $lines = shift)) { + $lines = "\n" if ($lines eq ""); + for my $line (split(/^/, $lines)) { + chomp $line; + print "$color $nocolor$line\n"; + } + } +} + +# print text colored +# fixme some callers pass an array of lines and some don't +sub colorprint { + my $color = shift; + while (defined(my $lines = shift)) { + $lines = "\n" if ($lines eq ""); + for my $line (split(/^/, $lines)) { + chomp $line; + print "$color$line$nocolor\n"; + } + } +} + +# Return test names from the command line. +# Returns all tests if no tests were named. +sub gettests { + my @tests; + + foreach my $arg (@ARGV) { + push @tests, $arg if ($arg !~ /=/ && $arg !~ /^-/); + } + + opendir(my $dir, $DIR) || die; + while (my $file = readdir($dir)) { + my ($name, $ext) = ($file =~ /^([^.]+)\.([^.]+)$/); + next if ! $languages_for_extension{$ext}; + + open(my $in, "< $file") || die "$file"; + my $contents = join "", <$in>; + if (defined $ALL_TESTS{$name}) { + colorprint $yellow, "SKIP: multiple tests named '$name'; skipping file '$file'."; + } else { + $ALL_TESTS{$name} = $ext if ($contents =~ m#^[/*\s]*TEST_#m); + } + close($in); + } + closedir($dir); + + if (scalar(@tests) == 0) { + @tests = keys %ALL_TESTS; + } + + @tests = sort @tests; + + return @tests; +} + + +# Turn a C compiler name into a C++ compiler name. +sub cplusplus { + my ($c) = @_; + if ($c =~ /cc/) { + $c =~ s/cc/\+\+/; + return $c; + } + return $c . "++"; # e.g. clang => clang++ +} + +# Turn a C compiler name into a Swift compiler name +sub swift { + my ($c) = @_; + $c =~ s#[^/]*$#swift#; + return $c; +} + +# Returns an array of all sdks from `xcodebuild -showsdks` +my @sdks_memo; +sub getsdks { + if (!@sdks_memo) { + @sdks_memo = (`xcodebuild -showsdks` =~ /-sdk (.+)$/mg); + } + return @sdks_memo; +} + +my %sdk_path_memo = {}; +sub getsdkpath { + my ($sdk) = @_; + if (!defined $sdk_path_memo{$sdk}) { + ($sdk_path_memo{$sdk}) = (`xcodebuild -version -sdk '$sdk' Path` =~ /^\s*(.+?)\s*$/); + } + return $sdk_path_memo{$sdk}; +} + +# Extract a version number from a string. +# Ignore trailing "internal". +sub versionsuffix { + my ($str) = @_; + my ($vers) = ($str =~ /([0-9]+\.[0-9]+)(?:\.?internal)?$/); + return $vers; +} +sub majorversionsuffix { + my ($str) = @_; + my ($vers) = ($str =~ /([0-9]+)\.[0-9]+(?:\.?internal)?$/); + return $vers; +} +sub minorversionsuffix { + my ($str) = @_; + my ($vers) = ($str =~ /[0-9]+\.([0-9]+)(?:\.?internal)?$/); + return $vers; +} + +# Compares two SDK names and returns the newer one. +# Assumes the two SDKs are the same OS. +sub newersdk { + my ($lhs, $rhs) = @_; + + # Major version wins. + my $lhsMajor = majorversionsuffix($lhs); + my $rhsMajor = majorversionsuffix($rhs); + if ($lhsMajor > $rhsMajor) { return $lhs; } + if ($lhsMajor < $rhsMajor) { return $rhs; } + + # Minor version wins. + my $lhsMinor = minorversionsuffix($lhs); + my $rhsMinor = minorversionsuffix($rhs); + if ($lhsMinor > $rhsMinor) { return $lhs; } + if ($lhsMinor < $rhsMinor) { return $rhs; } + + # Lexically-last wins (i.e. internal is better than not internal) + if ($lhs gt $rhs) { return $lhs; } + return $rhs; +} + +sub rewind { + seek($_[0], 0, 0); +} + +# parse name=value,value pairs +sub readconditions { + my ($conditionstring) = @_; + + my %results; + my @conditions = ($conditionstring =~ /\w+=(?:[^\s,]+,?)+/g); + for my $condition (@conditions) { + my ($name, $values) = ($condition =~ /(\w+)=(.+)/); + $results{$name} = [split ',', $values]; + } + + return %results; +} + +sub check_output { + my %C = %{shift()}; + my $name = shift; + my @output = @_; + + my %T = %{$C{"TEST_$name"}}; + + # Quietly strip MallocScribble before saving the "original" output + # because it is distracting. + filter_malloc(\@output); + + my @original_output = @output; + + # Run result-checking passes, reducing @output each time + my $xit = 1; + my $bad = ""; + my $warn = ""; + my $runerror = $T{TEST_RUN_OUTPUT}; + filter_hax(\@output); + filter_verbose(\@output); + filter_simulator(\@output); + $warn = filter_warn(\@output); + $bad |= filter_guardmalloc(\@output) if ($C{GUARDMALLOC}); + $bad |= filter_valgrind(\@output) if ($C{VALGRIND}); + $bad = filter_expected(\@output, \%C, $name) if ($bad eq ""); + $bad = filter_bad(\@output) if ($bad eq ""); + + # OK line should be the only one left + $bad = "(output not 'OK: $name')" if ($bad eq "" && (scalar(@output) != 1 || $output[0] !~ /^OK: $name/)); + + if ($bad ne "") { + colorprint $red, "FAIL: /// test '$name' \\\\\\"; + colorprefix $red, @original_output; + colorprint $red, "FAIL: \\\\\\ test '$name' ///"; + colorprint $red, "FAIL: $name: $bad"; + $xit = 0; + } + elsif ($warn ne "") { + colorprint $yellow, "PASS: /// test '$name' \\\\\\"; + colorprefix $yellow, @original_output; + colorprint $yellow, "PASS: \\\\\\ test '$name' ///"; + print "PASS: $name (with warnings)\n"; + } + else { + print "PASS: $name\n"; + } + return $xit; +} + +sub filter_expected +{ + my $outputref = shift; + my %C = %{shift()}; + my $name = shift; + + my %T = %{$C{"TEST_$name"}}; + my $runerror = $T{TEST_RUN_OUTPUT} || return ""; + + my $bad = ""; + + my $output = join("\n", @$outputref) . "\n"; + if ($output !~ /$runerror/) { + $bad = "(run output does not match TEST_RUN_OUTPUT)"; + @$outputref = ("FAIL: $name"); + } else { + @$outputref = ("OK: $name"); # pacify later filter + } + + return $bad; +} + +sub filter_bad +{ + my $outputref = shift; + my $bad = ""; + + my @new_output; + for my $line (@$outputref) { + if ($line =~ /^BAD: (.*)/) { + $bad = "(failed)"; + } else { + push @new_output, $line; + } + } + + @$outputref = @new_output; + return $bad; +} + +sub filter_warn +{ + my $outputref = shift; + my $warn = ""; + + my @new_output; + for my $line (@$outputref) { + if ($line !~ /^WARN: (.*)/) { + push @new_output, $line; + } else { + $warn = "(warned)"; + } + } + + @$outputref = @new_output; + return $warn; +} + +sub filter_verbose +{ + my $outputref = shift; + + my @new_output; + for my $line (@$outputref) { + if ($line !~ /^VERBOSE: (.*)/) { + push @new_output, $line; + } + } + + @$outputref = @new_output; +} + +sub filter_simulator +{ + my $outputref = shift; + + my @new_output; + for my $line (@$outputref) { + if (($line !~ /No simulator devices appear to be running/) && + ($line !~ /CoreSimulator is attempting to unload a stale CoreSimulatorService job/) && + ($line !~ /Failed to locate a valid instance of CoreSimulatorService/)) + { + push @new_output, $line; + } + } + + @$outputref = @new_output; +} + +sub filter_hax +{ + my $outputref = shift; + + my @new_output; + for my $line (@$outputref) { + if ($line !~ /Class OS_tcp_/) { + push @new_output, $line; + } + } + + @$outputref = @new_output; +} + +sub filter_valgrind +{ + my $outputref = shift; + my $errors = 0; + my $leaks = 0; + + my @new_output; + for my $line (@$outputref) { + if ($line =~ /^Approx: do_origins_Dirty\([RW]\): missed \d bytes$/) { + # --track-origins warning (harmless) + next; + } + if ($line =~ /^UNKNOWN __disable_threadsignal is unsupported. This warning will not be repeated.$/) { + # signals unsupported (harmless) + next; + } + if ($line =~ /^UNKNOWN __pthread_sigmask is unsupported. This warning will not be repeated.$/) { + # signals unsupported (harmless) + next; + } + if ($line !~ /^^\.*==\d+==/) { + # not valgrind output + push @new_output, $line; + next; + } + + my ($errcount) = ($line =~ /==\d+== ERROR SUMMARY: (\d+) errors/); + if (defined $errcount && $errcount > 0) { + $errors = 1; + } + + (my $leakcount) = ($line =~ /==\d+==\s+(?:definitely|possibly) lost:\s+([0-9,]+)/); + if (defined $leakcount && $leakcount > 0) { + $leaks = 1; + } + } + + @$outputref = @new_output; + + my $bad = ""; + $bad .= "(valgrind errors)" if ($errors); + $bad .= "(valgrind leaks)" if ($leaks); + return $bad; +} + + + +sub filter_malloc +{ + my $outputref = shift; + my $errors = 0; + + my @new_output; + my $count = 0; + for my $line (@$outputref) { + # Ignore MallocScribble prologue. + # Ignore MallocStackLogging prologue. + if ($line =~ /malloc: enabling scribbling to detect mods to free/ || + $line =~ /Deleted objects will be dirtied by the collector/ || + $line =~ /malloc: stack logs being written into/ || + $line =~ /malloc: stack logs deleted from/ || + $line =~ /malloc: process \d+ no longer exists/ || + $line =~ /malloc: recording malloc and VM allocation stacks/) + { + next; + } + + # not malloc output + push @new_output, $line; + + } + + @$outputref = @new_output; +} + +sub filter_guardmalloc +{ + my $outputref = shift; + my $errors = 0; + + my @new_output; + my $count = 0; + for my $line (@$outputref) { + if ($line !~ /^GuardMalloc\[[^\]]+\]: /) { + # not guardmalloc output + push @new_output, $line; + next; + } + + # Ignore 4 lines of guardmalloc prologue. + # Anything further is a guardmalloc error. + if (++$count > 4) { + $errors = 1; + } + } + + @$outputref = @new_output; + + my $bad = ""; + $bad .= "(guardmalloc errors)" if ($errors); + return $bad; +} + +# TEST_SOMETHING +# text +# text +# END +sub extract_multiline { + my ($flag, $contents, $name) = @_; + if ($contents =~ /$flag\n/) { + my ($output) = ($contents =~ /$flag\n(.*?\n)END[ *\/]*\n/s); + die "$name used $flag without END\n" if !defined($output); + return $output; + } + return undef; +} + + +# TEST_SOMETHING +# text +# OR +# text +# END +sub extract_multiple_multiline { + my ($flag, $contents, $name) = @_; + if ($contents =~ /$flag\n/) { + my ($output) = ($contents =~ /$flag\n(.*?\n)END[ *\/]*\n/s); + die "$name used $flag without END\n" if !defined($output); + + $output =~ s/\nOR\n/\n|/sg; + $output = "^(" . $output . ")\$"; + return $output; + } + return undef; +} + + +sub gather_simple { + my $CREF = shift; + my %C = %{$CREF}; + my $name = shift; + chdir_verbose $DIR; + + my $ext = $ALL_TESTS{$name}; + my $file = "$name.$ext"; + return 0 if !$file; + + # search file for 'TEST_CONFIG' or '#include "test.h"' + # also collect other values: + # TEST_DISABLED disable test with an optional message + # TEST_CRASHES test is expected to crash + # TEST_CONFIG test conditions + # TEST_ENV environment prefix + # TEST_CFLAGS compile flags + # TEST_BUILD build instructions + # TEST_BUILD_OUTPUT expected build stdout/stderr + # TEST_RUN_OUTPUT expected run stdout/stderr + open(my $in, "< $file") || die; + my $contents = join "", <$in>; + + my $test_h = ($contents =~ /^\s*#\s*(include|import)\s*"test\.h"/m); + my ($disabled) = ($contents =~ /\b(TEST_DISABLED\b.*)$/m); + my $crashes = ($contents =~ /\bTEST_CRASHES\b/m); + my ($conditionstring) = ($contents =~ /\bTEST_CONFIG\b(.*)$/m); + my ($envstring) = ($contents =~ /\bTEST_ENV\b(.*)$/m); + my ($cflags) = ($contents =~ /\bTEST_CFLAGS\b(.*)$/m); + my ($buildcmd) = extract_multiline("TEST_BUILD", $contents, $name); + my ($builderror) = extract_multiple_multiline("TEST_BUILD_OUTPUT", $contents, $name); + my ($runerror) = extract_multiple_multiline("TEST_RUN_OUTPUT", $contents, $name); + + return 0 if !$test_h && !$disabled && !$crashes && !defined($conditionstring) && !defined($envstring) && !defined($cflags) && !defined($buildcmd) && !defined($builderror) && !defined($runerror); + + if ($disabled) { + colorprint $yellow, "SKIP: $name (disabled by $disabled)"; + return 0; + } + + # check test conditions + + my $run = 1; + my %conditions = readconditions($conditionstring); + if (! $conditions{LANGUAGE}) { + # implicit language restriction from file extension + $conditions{LANGUAGE} = $languages_for_extension{$ext}; + } + for my $condkey (keys %conditions) { + my @condvalues = @{$conditions{$condkey}}; + + # special case: RUN=0 does not affect build + if ($condkey eq "RUN" && @condvalues == 1 && $condvalues[0] == 0) { + $run = 0; + next; + } + + my $testvalue = $C{$condkey}; + next if !defined($testvalue); + # testvalue is the configuration being run now + # condvalues are the allowed values for this test + + my $ok = 0; + for my $condvalue (@condvalues) { + + # special case: objc and objc++ + if ($condkey eq "LANGUAGE") { + $condvalue = "objective-c" if $condvalue eq "objc"; + $condvalue = "objective-c++" if $condvalue eq "objc++"; + } + + $ok = 1 if ($testvalue eq $condvalue); + + # special case: CC and CXX allow substring matches + if ($condkey eq "CC" || $condkey eq "CXX") { + $ok = 1 if ($testvalue =~ /$condvalue/); + } + + last if $ok; + } + + if (!$ok) { + my $plural = (@condvalues > 1) ? "one of: " : ""; + print "SKIP: $name ($condkey=$testvalue, but test requires $plural", join(' ', @condvalues), ")\n"; + return 0; + } + } + + # save some results for build and run phases + $$CREF{"TEST_$name"} = { + TEST_BUILD => $buildcmd, + TEST_BUILD_OUTPUT => $builderror, + TEST_CRASHES => $crashes, + TEST_RUN_OUTPUT => $runerror, + TEST_CFLAGS => $cflags, + TEST_ENV => $envstring, + TEST_RUN => $run, + DSTDIR => "$C{DSTDIR}/$name.build", + OBJDIR => "$C{OBJDIR}/$name.build", + }; + + return 1; +} + + +# Test description plist to write when building for BATS execution. +my %bats_plist; +$bats_plist{'Project'} = "objc4"; +$bats_plist{'Tests'} = []; # populated by append_bats_test() + +# Saves run instructions for a single test in all configurations as a BATS test. +sub append_bats_test { + my $name = shift; + + my $arch = join(',', @{$args{ARCH}}); + my $os = join(',', @{$args{OSVERSION}}); + my $mem = join(',', @{$args{MEM}}); + my $language = join(',', @{$args{LANGUAGE}}); + + push @{$bats_plist{'Tests'}}, { + "TestName" => "$name", + "Command" => [ + "/usr/bin/perl", + "$BATSBASE/test/test.pl", + $name, + "ARCH=$arch", + "OS=$os", + "MEM=$mem", + "LANGUAGE=$language", + "BUILD=0", + "RUN=1", + "VERBOSE=1", + "BATS=1", + ] + }; +} + + +# Builds a simple test +sub build_simple { + my %C = %{shift()}; + my $name = shift; + my %T = %{$C{"TEST_$name"}}; + + mkdir_verbose $T{DSTDIR}; + chdir_verbose $T{DSTDIR}; + # we don't mkdir $T{OBJDIR} because most tests don't use it + + my $ext = $ALL_TESTS{$name}; + my $file = "$DIR/$name.$ext"; + + if ($T{TEST_CRASHES}) { + `echo '$crashcatch' > crashcatch.c`; + make("$C{COMPILE_C} -dynamiclib -o libcrashcatch.dylib -x c crashcatch.c"); + die "$?" if $?; + } + + my $cmd = $T{TEST_BUILD} ? eval "return \"$T{TEST_BUILD}\"" : "$C{COMPILE} $T{TEST_CFLAGS} $file -o $name.exe"; + + my $output = make($cmd); + + # ignore out-of-date text-based stubs (caused by ditto into SDK) + $output =~ s/ld: warning: text-based stub file.*\n//g; + # rdar://10163155 + $output =~ s/ld: warning: could not create compact unwind for [^\n]+: does not use standard frame\n//g; + # rdar://37937122 + $output =~ s/^warning: Cannot lower [^\n]+\n//g; + $output =~ s/^warning: key: [^\n]+\n//g; + $output =~ s/^warning: discriminator: [^\n]+\n//g; + $output =~ s/^warning: callee: [^\n]+\n//g; + + my $ok; + if (my $builderror = $T{TEST_BUILD_OUTPUT}) { + # check for expected output and ignore $? + if ($output =~ /$builderror/) { + $ok = 1; + } else { + colorprint $red, "FAIL: /// test '$name' \\\\\\"; + colorprefix $red, $output; + colorprint $red, "FAIL: \\\\\\ test '$name' ///"; + colorprint $red, "FAIL: $name (build output does not match TEST_BUILD_OUTPUT)"; + $ok = 0; + } + } elsif ($?) { + colorprint $red, "FAIL: /// test '$name' \\\\\\"; + colorprefix $red, $output; + colorprint $red, "FAIL: \\\\\\ test '$name' ///"; + colorprint $red, "FAIL: $name (build failed)"; + $ok = 0; + } elsif ($output ne "") { + colorprint $red, "FAIL: /// test '$name' \\\\\\"; + colorprefix $red, $output; + colorprint $red, "FAIL: \\\\\\ test '$name' ///"; + colorprint $red, "FAIL: $name (unexpected build output)"; + $ok = 0; + } else { + $ok = 1; + } + + if ($ok) { + foreach my $file (glob("*.exe *.dylib *.bundle")) { + if (!$BATS) { + # not for BATS to save space and build time + # fixme use SYMROOT? + make("xcrun dsymutil $file"); + } + if ($C{OS} eq "macosx" || $C{OS} =~ /simulator/) { + # setting any entitlements disables dyld environment variables + } else { + # get-task-allow entitlement is required + # to enable dyld environment variables + make("xcrun codesign -s - --entitlements $DIR/get_task_allow_entitlement.plist $file"); + die "$?" if $?; + } + } + } + + return $ok; +} + +# Run a simple test (testname.exe, with error checking of stdout and stderr) +sub run_simple { + my %C = %{shift()}; + my $name = shift; + my %T = %{$C{"TEST_$name"}}; + + if (! $T{TEST_RUN}) { + print "PASS: $name (build only)\n"; + return 1; + } + + my $testdir = $T{DSTDIR}; + chdir_verbose $testdir; + + my $env = "$C{ENV} $T{TEST_ENV}"; + + if ($T{TEST_CRASHES}) { + $env .= " OBJC_DEBUG_DONT_CRASH=YES"; + } + + my $output; + + if ($C{ARCH} =~ /^arm/ && `uname -p` !~ /^arm/) { + # run on iOS or watchos or tvos device + # fixme device selection and verification + my $remotedir = "$REMOTEBASE/" . basename($C{DSTDIR}) . "/$name.build"; + + # Add test dir and libobjc's dir to DYLD_LIBRARY_PATH. + # Insert libcrashcatch.dylib if necessary. + $env .= " DYLD_LIBRARY_PATH=$remotedir"; + $env .= ":$REMOTEBASE" if ($C{TESTLIBDIR} ne $TESTLIBDIR); + if ($T{TEST_CRASHES}) { + $env .= " DYLD_INSERT_LIBRARIES=$remotedir/libcrashcatch.dylib"; + } + + my $cmd = "ssh iphone 'cd $remotedir && env $env ./$name.exe'"; + $output = make("$cmd"); + } + elsif ($C{OS} =~ /simulator/) { + # run locally in a simulator + # fixme selection of simulated OS version + my $simdevice; + if ($C{OS} =~ /iphonesimulator/) { + $simdevice = 'iPhone 6'; + } elsif ($C{OS} =~ /watchsimulator/) { + $simdevice = 'Apple Watch Series 4 - 40mm'; + } elsif ($C{OS} =~ /tvsimulator/) { + $simdevice = 'Apple TV 1080p'; + } else { + die "unknown simulator $C{OS}\n"; + } + my $sim = "xcrun -sdk iphonesimulator simctl spawn '$simdevice'"; + # Add test dir and libobjc's dir to DYLD_LIBRARY_PATH. + # Insert libcrashcatch.dylib if necessary. + $env .= " DYLD_LIBRARY_PATH=$testdir"; + $env .= ":" . $C{TESTLIBDIR} if ($C{TESTLIBDIR} ne $TESTLIBDIR); + if ($T{TEST_CRASHES}) { + $env .= " DYLD_INSERT_LIBRARIES=$testdir/libcrashcatch.dylib"; + } + + my $simenv = ""; + foreach my $keyvalue (split(' ', $env)) { + $simenv .= "SIMCTL_CHILD_$keyvalue "; + } + # Use the full path here so hack_cwd in test.h works. + $output = make("env $simenv $sim $testdir/$name.exe"); + } + else { + # run locally + + # Add test dir and libobjc's dir to DYLD_LIBRARY_PATH. + # Insert libcrashcatch.dylib if necessary. + $env .= " DYLD_LIBRARY_PATH=$testdir"; + $env .= ":" . $C{TESTLIBDIR} if ($C{TESTLIBDIR} ne $TESTLIBDIR); + if ($T{TEST_CRASHES}) { + $env .= " DYLD_INSERT_LIBRARIES=$testdir/libcrashcatch.dylib"; + } + + $output = make("sh -c '$env ./$name.exe'"); + } + + return check_output(\%C, $name, split("\n", $output)); +} + + +my %compiler_memo; +sub find_compiler { + my ($cc, $toolchain, $sdk_path) = @_; + + # memoize + my $key = $cc . ':' . $toolchain; + my $result = $compiler_memo{$key}; + return $result if defined $result; + + $result = make("xcrun -toolchain $toolchain -find $cc 2>/dev/null"); + + chomp $result; + $compiler_memo{$key} = $result; + return $result; +} + +sub dirContainsAllTestLibs { + my $dir = shift; + + foreach my $testlib (@TESTLIBNAMES) { + my $found = (-e "$dir/$testlib"); + my $foundstr = ($found ? "found" : "didn't find"); + print "note: $foundstr $testlib in $dir\n" if ($VERBOSE); + return 0 if (!$found); + } + + return 1; +} + +sub make_one_config { + my $configref = shift; + my $root = shift; + my %C = %{$configref}; + + # Aliases + $C{LANGUAGE} = "objective-c" if $C{LANGUAGE} eq "objc"; + $C{LANGUAGE} = "objective-c++" if $C{LANGUAGE} eq "objc++"; + + # Interpret OS version string from command line. + my ($sdk_arg, $deployment_arg, $run_arg, undef) = split('-', $C{OSVERSION}); + delete $C{OSVERSION}; + my ($os_arg) = ($sdk_arg =~ /^([^\.0-9]+)/); + $deployment_arg = "default" if !defined($deployment_arg); + $run_arg = "default" if !defined($run_arg); + + my %allowed_os_args = ( + "macosx" => "macosx", "osx" => "macosx", "macos" => "macosx", + "iphoneos" => "iphoneos", "ios" => "iphoneos", + "iphonesimulator" => "iphonesimulator", "iossimulator" => "iphonesimulator", + "watchos" => "watchos", + "watchsimulator" => "watchsimulator", "watchossimulator" => "watchsimulator", + "appletvos" => "appletvos", "tvos" => "appletvos", + "appletvsimulator" => "appletvsimulator", "tvsimulator" => "appletvsimulator", + "bridgeos" => "bridgeos", + ); + + $C{OS} = $allowed_os_args{$os_arg} || die "unknown OS '$os_arg' (expected " . join(', ', sort keys %allowed_os_args) . ")\n"; + + # set the config name now, after massaging the language and OS versions, + # but before adding other settings + my $configname = config_name(%C); + die if ($configname =~ /'/); + die if ($configname =~ / /); + ($C{NAME} = $configname) =~ s/~/ /g; + (my $configdir = $configname) =~ s#/##g; + $C{DSTDIR} = "$DSTROOT$BUILDDIR/$configdir"; + $C{OBJDIR} = "$OBJROOT$BUILDDIR/$configdir"; + + # Allow tests to see BATS-edness in TEST_CONFIG. + $C{BATS} = $BATS; + + if ($C{OS} eq "iphoneos" || $C{OS} eq "iphonesimulator") { + $C{TOOLCHAIN} = "ios"; + } elsif ($C{OS} eq "watchos" || $C{OS} eq "watchsimulator") { + $C{TOOLCHAIN} = "watchos"; + } elsif ($C{OS} eq "appletvos" || $C{OS} eq "appletvsimulator") { + $C{TOOLCHAIN} = "appletvos"; + } elsif ($C{OS} eq "bridgeos") { + $C{TOOLCHAIN} = "bridgeos"; + } elsif ($C{OS} eq "macosx") { + $C{TOOLCHAIN} = "osx"; + } else { + colorprint $yellow, "WARN: don't know toolchain for OS $C{OS}"; + $C{TOOLCHAIN} = "default"; + } + + if ($BUILD) { + # Look up SDK. + # Try exact match first. + # Then try lexically-last prefix match + # (so "macosx" => "macosx10.7internal") + + $sdk_arg =~ s/$os_arg/$C{OS}/; + + my @sdks = getsdks(); + if ($VERBOSE) { + print "note: Installed SDKs: @sdks\n"; + } + my $exactsdk = undef; + my $prefixsdk = undef; + foreach my $sdk (@sdks) { + $exactsdk = $sdk if ($sdk eq $sdk_arg); + $prefixsdk = newersdk($sdk, $prefixsdk) if ($sdk =~ /^$sdk_arg/); + } + + my $sdk; + if ($exactsdk) { + $sdk = $exactsdk; + } elsif ($prefixsdk) { + $sdk = $prefixsdk; + } else { + die "unknown SDK '$sdk_arg'\nInstalled SDKs: @sdks\n"; + } + + # Set deployment target. + # fixme can't enforce version when run_arg eq "default" + # because we don't know it yet + $deployment_arg = versionsuffix($sdk) if $deployment_arg eq "default"; + if ($run_arg ne "default") { + die "Deployment target '$deployment_arg' is newer than run target '$run_arg'\n" if $deployment_arg > $run_arg; + } + $C{DEPLOYMENT_TARGET} = $deployment_arg; + $C{SDK_PATH} = getsdkpath($sdk); + } else { + # not $BUILD + $C{DEPLOYMENT_TARGET} = "unknown_deployment_target"; + $C{SDK_PATH} = "/unknown/sdk"; + } + + # Set run target. + $C{RUN_TARGET} = $run_arg; + + # Look up test library (possible in root or SDK_PATH) + + my $rootarg = $root; + my $symroot; + my @sympaths = ( (glob "$root/*~sym")[0], + (glob "$root/BuildRecords/*_install/Symbols")[0], + "$root/Symbols" ); + my @dstpaths = ( (glob "$root/*~dst")[0], + (glob "$root/BuildRecords/*_install/Root")[0], + "$root/Root" ); + for(my $i = 0; $i < scalar(@sympaths); $i++) { + if (-e $sympaths[$i] && -e $dstpaths[$i]) { + $symroot = $sympaths[$i]; + $root = $dstpaths[$i]; + last; + } + } + + if ($root ne "") { + # Root specified. Require that it contain our dylibs. + if (dirContainsAllTestLibs("$root$C{SDK_PATH}$TESTLIBDIR")) { + $C{TESTLIBDIR} = "$root$C{SDK_PATH}$TESTLIBDIR"; + } elsif (dirContainsAllTestLibs("$root$TESTLIBDIR")) { + $C{TESTLIBDIR} = "$root$TESTLIBDIR"; + } elsif (dirContainsAllTestLibs($root)) { + $C{TESTLIBDIR} = "$root"; + } else { + die "Didn't find some libs in root '$rootarg' for sdk '$C{SDK_PATH}'\n"; + } + } + else { + # No root specified. Use the SDK or / for our dylibs. + if (dirContainsAllTestLibs("$C{SDK_PATH}$TESTLIBDIR")) { + $C{TESTLIBDIR} = "$C{SDK_PATH}$TESTLIBDIR"; + } else { + # We don't actually check in / because on devices + # there are no dylib files there. + $C{TESTLIBDIR} = $TESTLIBDIR; + } + } + + @{$C{TESTLIBS}} = map { "$C{TESTLIBDIR}/$_" } @TESTLIBNAMES; + # convenience for tests that want libobjc.dylib's path + $C{TESTLIB} = @{$C{TESTLIBS}}[0]; + + foreach my $testlibname (@TESTLIBNAMES) { + if (-e "$symroot/$testlibname.dSYM") { + push(@{$C{TESTDSYMS}}, "$symroot/$testlibname.dSYM"); + } + } + + if ($VERBOSE) { + foreach my $testlib (@{$C{TESTLIBS}}) { + my @uuids = `/usr/bin/dwarfdump -u '$testlib'`; + while (my $uuid = shift @uuids) { + print "note: $uuid"; + } + } + } + + # Look up compilers + my $cc = $C{CC}; + my $cxx = cplusplus($C{CC}); + my $swift = swift($C{CC}); + if (! $BUILD) { + $C{CC} = $cc; + $C{CXX} = $cxx; + $C{SWIFT} = $swift + } else { + $C{CC} = find_compiler($cc, $C{TOOLCHAIN}, $C{SDK_PATH}); + $C{CXX} = find_compiler($cxx, $C{TOOLCHAIN}, $C{SDK_PATH}); + $C{SWIFT} = find_compiler($swift, $C{TOOLCHAIN}, $C{SDK_PATH}); + + die "No C compiler '$cc' ('$C{CC}') in toolchain '$C{TOOLCHAIN}'\n" if !-e $C{CC}; + die "No C++ compiler '$cxx' ('$C{CXX}') in toolchain '$C{TOOLCHAIN}'\n" if !-e $C{CXX}; + die "No Swift compiler '$swift' ('$C{SWIFT}') in toolchain '$C{TOOLCHAIN}'\n" if !-e $C{SWIFT}; + } + + if ($C{ARCH} eq "i386" && $C{OS} eq "macosx") { + # libarclite no longer available on i386 + # fixme need an archived copy for bincompat testing + $C{FORCE_LOAD_ARCLITE} = ""; + } else { + $C{FORCE_LOAD_ARCLITE} = "-Xlinker -force_load -Xlinker " . dirname($C{CC}) . "/../lib/arc/libarclite_$C{OS}.a"; + } + + # Populate cflags + + my $cflags = "-I$DIR -W -Wall -Wno-objc-weak-compat -Wno-arc-bridge-casts-disallowed-in-nonarc -Wshorten-64-to-32 -Qunused-arguments -fno-caret-diagnostics -Os -arch $C{ARCH} "; + if (!$BATS) { + # save-temps so dsymutil works so debug info works. + # Disabled in BATS to save disk space. + # rdar://45656803 -save-temps causes bad -Wstdlibcxx-not-found warnings + $cflags .= "-g -save-temps -Wno-stdlibcxx-not-found"; + } + my $objcflags = ""; + my $swiftflags = "-g "; + + $cflags .= " -isysroot '$C{SDK_PATH}'"; + $cflags .= " '-Wl,-syslibroot,$C{SDK_PATH}'"; + $swiftflags .= " -sdk '$C{SDK_PATH}'"; + + # Set deployment target cflags + my $target = undef; + die "No deployment target" if $C{DEPLOYMENT_TARGET} eq ""; + if ($C{OS} eq "iphoneos") { + $cflags .= " -mios-version-min=$C{DEPLOYMENT_TARGET}"; + $target = "$C{ARCH}-apple-ios$C{DEPLOYMENT_TARGET}"; + } + elsif ($C{OS} eq "iphonesimulator") { + $cflags .= " -mios-simulator-version-min=$C{DEPLOYMENT_TARGET}"; + $target = "$C{ARCH}-apple-ios$C{DEPLOYMENT_TARGET}"; + } + elsif ($C{OS} eq "watchos") { + $cflags .= " -mwatchos-version-min=$C{DEPLOYMENT_TARGET}"; + $target = "$C{ARCH}-apple-watchos$C{DEPLOYMENT_TARGET}"; + } + elsif ($C{OS} eq "watchsimulator") { + $cflags .= " -mwatchos-simulator-version-min=$C{DEPLOYMENT_TARGET}"; + $target = "$C{ARCH}-apple-watchos$C{DEPLOYMENT_TARGET}"; + } + elsif ($C{OS} eq "appletvos") { + $cflags .= " -mtvos-version-min=$C{DEPLOYMENT_TARGET}"; + $target = "$C{ARCH}-apple-tvos$C{DEPLOYMENT_TARGET}"; + } + elsif ($C{OS} eq "appletvsimulator") { + $cflags .= " -mtvos-simulator-version-min=$C{DEPLOYMENT_TARGET}"; + $target = "$C{ARCH}-apple-tvos$C{DEPLOYMENT_TARGET}"; + } + elsif ($C{OS} eq "bridgeos") { + $cflags .= " -mbridgeos-version-min=$C{DEPLOYMENT_TARGET}"; + $target = "$C{ARCH}-apple-bridgeos$C{DEPLOYMENT_TARGET}"; + } + else { + $cflags .= " -mmacosx-version-min=$C{DEPLOYMENT_TARGET}"; + $target = "$C{ARCH}-apple-macosx$C{DEPLOYMENT_TARGET}"; + } + $swiftflags .= " -target $target"; + + $C{TESTINCLUDEDIR} = "$C{SDK_PATH}/usr/include"; + $C{TESTLOCALINCLUDEDIR} = "$C{SDK_PATH}/usr/local/include"; + if ($root ne "") { + if ($C{SDK_PATH} ne "/") { + $cflags .= " -isystem '$root$C{SDK_PATH}/usr/include'"; + $cflags .= " -isystem '$root$C{SDK_PATH}/usr/local/include'"; + } + + my $library_path = $C{TESTLIBDIR}; + $cflags .= " -L$library_path"; + # fixme Root vs SDKContentRoot + $C{TESTINCLUDEDIR} = "$root/../SDKContentRoot/usr/include"; + $C{TESTLOCALINCLUDEDIR} = "$root/../SDKContentRoot/usr/local/include"; + $cflags .= " -isystem '$C{TESTINCLUDEDIR}'"; + $cflags .= " -isystem '$C{TESTLOCALINCLUDEDIR}'"; + } + + + # Populate objcflags + + $objcflags .= " -lobjc"; + if ($C{MEM} eq "arc") { + $objcflags .= " -fobjc-arc"; + } + elsif ($C{MEM} eq "mrc") { + # nothing + } + else { + die "unrecognized MEM '$C{MEM}'\n"; + } + + # Populate ENV_PREFIX + $C{ENV} = "LANG=C MallocScribble=1"; + $C{ENV} .= " VERBOSE=$VERBOSE" if $VERBOSE; + if ($root ne "") { + die "no spaces allowed in root" if $C{TESTLIBDIR} =~ /\s+/; + } + if ($C{GUARDMALLOC}) { + $C{ENV} .= " GUARDMALLOC=1"; # checked by tests and errcheck.pl + $C{ENV} .= " DYLD_INSERT_LIBRARIES=/usr/lib/libgmalloc.dylib"; + if ($C{GUARDMALLOC} eq "before") { + $C{ENV} .= " MALLOC_PROTECT_BEFORE=1"; + } elsif ($C{GUARDMALLOC} eq "after") { + # protect after is the default + } else { + die "Unknown guard malloc mode '$C{GUARDMALLOC}'\n"; + } + } + + # Populate compiler commands + $C{XCRUN} = "env LANG=C /usr/bin/xcrun -toolchain '$C{TOOLCHAIN}'"; + + $C{COMPILE_C} = "$C{XCRUN} '$C{CC}' $cflags -x c -std=gnu99"; + $C{COMPILE_CXX} = "$C{XCRUN} '$C{CXX}' $cflags -x c++"; + $C{COMPILE_M} = "$C{XCRUN} '$C{CC}' $cflags $objcflags -x objective-c -std=gnu99"; + $C{COMPILE_MM} = "$C{XCRUN} '$C{CXX}' $cflags $objcflags -x objective-c++"; + $C{COMPILE_SWIFT} = "$C{XCRUN} '$C{SWIFT}' $swiftflags"; + + $C{COMPILE} = $C{COMPILE_C} if $C{LANGUAGE} eq "c"; + $C{COMPILE} = $C{COMPILE_CXX} if $C{LANGUAGE} eq "c++"; + $C{COMPILE} = $C{COMPILE_M} if $C{LANGUAGE} eq "objective-c"; + $C{COMPILE} = $C{COMPILE_MM} if $C{LANGUAGE} eq "objective-c++"; + $C{COMPILE} = $C{COMPILE_SWIFT} if $C{LANGUAGE} eq "swift"; + die "unknown language '$C{LANGUAGE}'\n" if !defined $C{COMPILE}; + + ($C{COMPILE_NOMEM} = $C{COMPILE}) =~ s/ -fobjc-arc\S*//g; + ($C{COMPILE_NOLINK} = $C{COMPILE}) =~ s/ '?-(?:Wl,|l)\S*//g; + ($C{COMPILE_NOLINK_NOMEM} = $C{COMPILE_NOMEM}) =~ s/ '?-(?:Wl,|l)\S*//g; + + + # Reject some self-inconsistent and disallowed configurations + if ($C{MEM} !~ /^(mrc|arc)$/) { + die "unknown MEM=$C{MEM} (expected one of mrc arc)\n"; + } + + if ($C{MEM} eq "arc" && $C{CC} !~ /clang/) { + print "note: skipping configuration $C{NAME}\n"; + print "note: because CC=$C{CC} does not support MEM=$C{MEM}\n"; + return 0; + } + + if ($C{ARCH} eq "i386" && $C{OS} eq "macosx") { + colorprint $yellow, "WARN: skipping configuration $C{NAME}\n"; + colorprint $yellow, "WARN: because 32-bit Mac is dead\n"; + return 0; + } + + # fixme + if ($C{LANGUAGE} eq "swift" && $C{ARCH} =~ /^arm/) { + print "note: skipping configuration $C{NAME}\n"; + print "note: because ARCH=$C{ARCH} does not support LANGUAGE=SWIFT\n"; + return 0; + } + + # fixme unimplemented run targets + if ($C{RUN_TARGET} ne "default" && $C{OS} !~ /simulator/) { + colorprint $yellow, "WARN: skipping configuration $C{NAME}"; + colorprint $yellow, "WARN: because OS=$C{OS} does not yet implement RUN_TARGET=$C{RUN_TARGET}"; + } + + %$configref = %C; +} + +sub make_configs { + my ($root, %args) = @_; + + my @results = ({}); # start with one empty config + + for my $key (keys %args) { + my @newresults; + my @values = @{$args{$key}}; + for my $configref (@results) { + my %config = %{$configref}; + for my $value (@values) { + my %newconfig = %config; + $newconfig{$key} = $value; + push @newresults, \%newconfig; + } + } + @results = @newresults; + } + + my @newresults; + for my $configref(@results) { + if (make_one_config($configref, $root)) { + push @newresults, $configref; + } + } + + return @newresults; +} + +sub config_name { + my %config = @_; + my $name = ""; + for my $key (sort keys %config) { + $name .= '~' if $name ne ""; + $name .= "$key=$config{$key}"; + } + return $name; +} + +sub rsync_ios { + my ($src, $timeout) = @_; + for (my $i = 0; $i < 10; $i++) { + make("$DIR/timeout.pl $timeout env RSYNC_PASSWORD=alpine rsync -av $src rsync://root\@localhost:10873/root/$REMOTEBASE/"); + return if $? == 0; + colorprint $yellow, "WARN: RETRY\n" if $VERBOSE; + } + die "Couldn't rsync tests to device. Check: device is connected; tcprelay is running; device trusts your Mac; device is unlocked; filesystem is mounted r/w\n"; +} + +sub build_and_run_one_config { + my %C = %{shift()}; + my @tests = @_; + + # Build and run + my $testcount = 0; + my $failcount = 0; + my $skipconfig = 0; + + my @gathertests; + foreach my $test (@tests) { + if ($VERBOSE) { + print "\nGATHER $test\n"; + } + + if ($ALL_TESTS{$test}) { + gather_simple(\%C, $test) || next; # not pass, not fail + push @gathertests, $test; + } else { + die "No test named '$test'\n"; + } + } + + my @builttests; + if (!$BUILD) { + @builttests = @gathertests; + $testcount = scalar(@gathertests); + } else { + foreach my $test (@gathertests) { + if ($VERBOSE) { + print "\nBUILD $test\n"; + } + + if ($ALL_TESTS{$test}) { + $testcount++; + if (!build_simple(\%C, $test)) { + $failcount++; + } else { + push @builttests, $test; + } + } else { + die "No test named '$test'\n"; + } + } + } + + if (!$RUN || !scalar(@builttests)) { + # nothing to do + } + else { + if ($C{ARCH} =~ /^arm/ && `uname -p` !~ /^arm/) { + # upload timeout - longer for slow watch devices + my $timeout = ($C{OS} =~ /watch/) ? 120 : 20; + + # upload all tests to iOS device + rsync_ios($C{DSTDIR}, $timeout); + + # upload library to iOS device + if ($C{TESTLIBDIR} ne $TESTLIBDIR) { + foreach my $thing (@{$C{TESTLIBS}}, @{$C{TESTDSYMS}}) { + rsync_ios($thing, $timeout); + } + } + } + elsif ($C{OS} =~ /simulator/) { + # run locally in a simulator + } + else { + # run locally + if ($BATS) { + # BATS execution tries to run architectures that + # aren't supported by the device. Skip those configs here. + my $machine = `machine`; + chomp $machine; + # unsupported: + # running arm64e on non-arm64e device + # running arm64 on non-arm64* device + # running armv7k on non-armv7k device + # running arm64_32 on armv7k device + # We don't need to handle all mismatches here, + # only mismatches that arise within a single OS. + $skipconfig = + (($C{ARCH} eq "arm64e" && $machine ne "arm64e") || + ($C{ARCH} eq "arm64" && $machine !~ /^arm64/) || + ($C{ARCH} eq "armv7k" && $machine ne "armv7k") || + ($C{ARCH} eq "arm64_32" && $machine eq "armv7k")); + if ($skipconfig) { + print "note: skipping configuration $C{NAME}\n"; + print "note: because test arch $C{ARCH} is not " . + "supported on device arch $machine\n"; + $testcount = 0; + } + } + } + + if (!$skipconfig) { + foreach my $test (@builttests) { + print "\nRUN $test\n" if ($VERBOSE); + + if ($ALL_TESTS{$test}) { + if (!run_simple(\%C, $test)) { + $failcount++; + } + } else { + die "No test named '$test'\n"; + } + } + } + } + + return ($testcount, $failcount, $skipconfig); +} + + + +# Return value if set by "$argname=value" on the command line +# Return $default if not set. +sub getargs { + my ($argname, $default) = @_; + + foreach my $arg (@ARGV) { + my ($value) = ($arg =~ /^$argname=(.+)$/); + return [split ',', $value] if defined $value; + } + + return [split ',', $default]; +} + +# Return 1 or 0 if set by "$argname=1" or "$argname=0" on the +# command line. Return $default if not set. +sub getbools { + my ($argname, $default) = @_; + + my @values = @{getargs($argname, $default)}; + return [( map { ($_ eq "0") ? 0 : 1 } @values )]; +} + +# Return an integer if set by "$argname=value" on the +# command line. Return $default if not set. +sub getints { + my ($argname, $default) = @_; + + my @values = @{getargs($argname, $default)}; + return [( map { int($_) } @values )]; +} + +sub getarg { + my ($argname, $default) = @_; + my @values = @{getargs($argname, $default)}; + die "Only one value allowed for $argname\n" if @values > 1; + return $values[0]; +} + +sub getbool { + my ($argname, $default) = @_; + my @values = @{getbools($argname, $default)}; + die "Only one value allowed for $argname\n" if @values > 1; + return $values[0]; +} + +sub getint { + my ($argname, $default) = @_; + my @values = @{getints($argname, $default)}; + die "Only one value allowed for $argname\n" if @values > 1; + return $values[0]; +} + + +my $default_arch = "x86_64"; +$args{ARCH} = getargs("ARCH", 0); +$args{ARCH} = getargs("ARCHS", $default_arch) if !@{$args{ARCH}}[0]; + +$args{OSVERSION} = getargs("OS", "macosx-default-default"); + +$args{MEM} = getargs("MEM", "mrc"); +$args{LANGUAGE} = [ map { lc($_) } @{getargs("LANGUAGE", "objective-c")} ]; + +$args{CC} = getargs("CC", "clang"); + +{ + my $guardmalloc = getargs("GUARDMALLOC", 0); + # GUARDMALLOC=1 is the same as GUARDMALLOC=before,after + my @guardmalloc2 = (); + for my $arg (@$guardmalloc) { + if ($arg == 1) { push @guardmalloc2, "before"; + push @guardmalloc2, "after"; } + else { push @guardmalloc2, $arg } + } + $args{GUARDMALLOC} = \@guardmalloc2; +} + +$BUILD = getbool("BUILD", 1); +$RUN = getbool("RUN", 1); +$VERBOSE = getint("VERBOSE", 0); +$BATS = getbool("BATS", 0); +$BUILDDIR = getarg("BUILDDIR", $BATS ? $BATSBASE : $LOCALBASE); + +my $root = getarg("ROOT", ""); +$root =~ s#/*$##; + +my @tests = gettests(); + +if ($BUILD) { + rm_rf_verbose "$DSTROOT$BUILDDIR"; + rm_rf_verbose "$OBJROOT$BUILDDIR"; +} + +print "note: -----\n"; +print "note: testing root '$root'\n"; + +my @configs = make_configs($root, %args); + +print "note: -----\n"; +print "note: testing ", scalar(@configs), " configurations:\n"; +for my $configref (@configs) { + my $configname = $$configref{NAME}; + print "note: configuration $configname\n"; +} + +my $failed = 0; + +my $testconfigs = @configs; +my $failconfigs = 0; +my $skipconfigs = 0; +my $testcount = 0; +my $failcount = 0; +for my $configref (@configs) { + my $configname = $$configref{NAME}; + print "note: -----\n"; + print "note: \nnote: $configname\nnote: \n"; + + (my $t, my $f, my $skipconfig) = + eval { build_and_run_one_config($configref, @tests); }; + $skipconfigs += $skipconfig; + if ($@) { + chomp $@; + colorprint $red, "FAIL: $configname"; + colorprint $red, "FAIL: $@"; + $failconfigs++; + } else { + my $color = ($f ? $red : ""); + print "note:\n"; + colorprint $color, "note: $configname\n"; + colorprint $color, "note: $t tests, $f failures"; + $testcount += $t; + $failcount += $f; + $failconfigs++ if ($f); + } +} + +print "note: -----\n"; +my $color = ($failconfigs ? $red : ""); +colorprint $color, "note: $testconfigs configurations, " . + "$failconfigs with failures, $skipconfigs skipped"; +colorprint $color, "note: $testcount tests, $failcount failures"; + +$failed = ($failconfigs ? 1 : 0); + + +if ($BUILD && $BATS && !$failed) { + # Collect BATS execution instructions for all tests. + # Each BATS "test" is all configurations together of one of our tests. + for my $testname (@tests) { + append_bats_test($testname); + } + + # Write the BATS plist to disk. + my $json = encode_json(\%bats_plist); + my $filename = "$DSTROOT$BATSBASE/objc4.plist"; + print "note: writing BATS config to $filename\n"; + open(my $file, '>', $filename); + print $file $json; + close $file; +} + +exit ($failed ? 1 : 0); diff --git a/test/testroot.i b/test/testroot.i new file mode 100644 index 0000000..99fbec8 --- /dev/null +++ b/test/testroot.i @@ -0,0 +1,220 @@ +// testroot.i +// Implementation of class TestRoot +// Include this file into your main test file to use it. + +#include "test.h" +#include +#include + +atomic_int TestRootLoad; +atomic_int TestRootInitialize; +atomic_int TestRootAlloc; +atomic_int TestRootAllocWithZone; +atomic_int TestRootCopy; +atomic_int TestRootCopyWithZone; +atomic_int TestRootMutableCopy; +atomic_int TestRootMutableCopyWithZone; +atomic_int TestRootInit; +atomic_int TestRootDealloc; +atomic_int TestRootRetain; +atomic_int TestRootRelease; +atomic_int TestRootAutorelease; +atomic_int TestRootRetainCount; +atomic_int TestRootTryRetain; +atomic_int TestRootIsDeallocating; +atomic_int TestRootPlusRetain; +atomic_int TestRootPlusRelease; +atomic_int TestRootPlusAutorelease; +atomic_int TestRootPlusRetainCount; + + +@implementation TestRoot + +// These all use void* pending rdar://9310005. + +static void * +retain_fn(void *self, SEL _cmd __unused) { + atomic_fetch_add_explicit(&TestRootRetain, 1, memory_order_relaxed); + void * (*fn)(void *) = (typeof(fn))_objc_rootRetain; + return fn(self); +} + +static void +release_fn(void *self, SEL _cmd __unused) { + atomic_fetch_add_explicit(&TestRootRelease, 1, memory_order_relaxed); + void (*fn)(void *) = (typeof(fn))_objc_rootRelease; + fn(self); +} + +static void * +autorelease_fn(void *self, SEL _cmd __unused) { + atomic_fetch_add_explicit(&TestRootAutorelease, 1, memory_order_relaxed); + void * (*fn)(void *) = (typeof(fn))_objc_rootAutorelease; + return fn(self); +} + +static unsigned long +retaincount_fn(void *self, SEL _cmd __unused) { + atomic_fetch_add_explicit(&TestRootRetainCount, 1, memory_order_relaxed); + unsigned long (*fn)(void *) = (typeof(fn))_objc_rootRetainCount; + return fn(self); +} + +static void * +copywithzone_fn(void *self, SEL _cmd __unused, void *zone) { + atomic_fetch_add_explicit(&TestRootCopyWithZone, 1, memory_order_relaxed); + void * (*fn)(void *, void *) = + (typeof(fn))dlsym(RTLD_DEFAULT, "object_copy"); + return fn(self, zone); +} + +static void * +plusretain_fn(void *self __unused, SEL _cmd __unused) { + atomic_fetch_add_explicit(&TestRootPlusRetain, 1, memory_order_relaxed); + return self; +} + +static void +plusrelease_fn(void *self __unused, SEL _cmd __unused) { + atomic_fetch_add_explicit(&TestRootPlusRelease, 1, memory_order_relaxed); +} + +static void * +plusautorelease_fn(void *self, SEL _cmd __unused) { + atomic_fetch_add_explicit(&TestRootPlusAutorelease, 1, memory_order_relaxed); + return self; +} + +static unsigned long +plusretaincount_fn(void *self __unused, SEL _cmd __unused) { + atomic_fetch_add_explicit(&TestRootPlusRetainCount, 1, memory_order_relaxed); + return ULONG_MAX; +} + ++(void) load { + atomic_fetch_add_explicit(&TestRootLoad, 1, memory_order_relaxed); + + // install methods that ARC refuses to compile + class_addMethod(self, sel_registerName("retain"), (IMP)retain_fn, ""); + class_addMethod(self, sel_registerName("release"), (IMP)release_fn, ""); + class_addMethod(self, sel_registerName("autorelease"), (IMP)autorelease_fn, ""); + class_addMethod(self, sel_registerName("retainCount"), (IMP)retaincount_fn, ""); + class_addMethod(self, sel_registerName("copyWithZone:"), (IMP)copywithzone_fn, ""); + + class_addMethod(object_getClass(self), sel_registerName("retain"), (IMP)plusretain_fn, ""); + class_addMethod(object_getClass(self), sel_registerName("release"), (IMP)plusrelease_fn, ""); + class_addMethod(object_getClass(self), sel_registerName("autorelease"), (IMP)plusautorelease_fn, ""); + class_addMethod(object_getClass(self), sel_registerName("retainCount"), (IMP)plusretaincount_fn, ""); +} + + ++(void) initialize { + atomic_fetch_add_explicit(&TestRootInitialize, 1, memory_order_relaxed); +} + +-(id) self { + return self; +} + ++(Class) class { + return self; +} + +-(Class) class { + return object_getClass(self); +} + ++(Class) superclass { + return class_getSuperclass(self); +} + +-(Class) superclass { + return class_getSuperclass([self class]); +} + ++(id) new { + return [[self alloc] init]; +} + ++(id) alloc { + atomic_fetch_add_explicit(&TestRootAlloc, 1, memory_order_relaxed); + void * (*fn)(id __unsafe_unretained) = (typeof(fn))_objc_rootAlloc; + return (__bridge_transfer id)(fn(self)); +} + ++(id) allocWithZone:(void *)zone { + atomic_fetch_add_explicit(&TestRootAllocWithZone, 1, memory_order_relaxed); + void * (*fn)(id __unsafe_unretained, void *) = (typeof(fn))_objc_rootAllocWithZone; + return (__bridge_transfer id)(fn(self, zone)); +} + ++(id) copy { + return self; +} + ++(id) copyWithZone:(void *) __unused zone { + return self; +} + +-(id) copy { + atomic_fetch_add_explicit(&TestRootCopy, 1, memory_order_relaxed); + return [self copyWithZone:NULL]; +} + ++(id) mutableCopyWithZone:(void *) __unused zone { + fail("+mutableCopyWithZone: called"); +} + +-(id) mutableCopy { + atomic_fetch_add_explicit(&TestRootMutableCopy, 1, memory_order_relaxed); + return [self mutableCopyWithZone:NULL]; +} + +-(id) mutableCopyWithZone:(void *) __unused zone { + atomic_fetch_add_explicit(&TestRootMutableCopyWithZone, 1, memory_order_relaxed); + void * (*fn)(id __unsafe_unretained) = (typeof(fn))_objc_rootAlloc; + return (__bridge_transfer id)(fn(object_getClass(self))); +} + +-(id) init { + atomic_fetch_add_explicit(&TestRootInit, 1, memory_order_relaxed); + return _objc_rootInit(self); +} + ++(void) dealloc { + fail("+dealloc called"); +} + +-(void) dealloc { + atomic_fetch_add_explicit(&TestRootDealloc, 1, memory_order_relaxed); + _objc_rootDealloc(self); +} + ++(BOOL) _tryRetain { + return YES; +} + +-(BOOL) _tryRetain { + atomic_fetch_add_explicit(&TestRootTryRetain, 1, memory_order_relaxed); + return _objc_rootTryRetain(self); +} + ++(BOOL) _isDeallocating { + return NO; +} + +-(BOOL) _isDeallocating { + atomic_fetch_add_explicit(&TestRootIsDeallocating, 1, memory_order_relaxed); + return _objc_rootIsDeallocating(self); +} + +-(BOOL) allowsWeakReference { + return ! [self _isDeallocating]; +} + +-(BOOL) retainWeakReference { + return [self _tryRetain]; +} + + +@end diff --git a/test/timeout.pl b/test/timeout.pl new file mode 100755 index 0000000..750b21e --- /dev/null +++ b/test/timeout.pl @@ -0,0 +1,9 @@ +#!/usr/bin/perl -w + +use strict; + +my $usage = "timeout \n"; +my $timeout = shift || die $usage; +alarm($timeout); +exec @ARGV; +die "exec failed: @ARGV"; diff --git a/test/unload.h b/test/unload.h new file mode 100644 index 0000000..5287fe9 --- /dev/null +++ b/test/unload.h @@ -0,0 +1,6 @@ +#include "test.h" + +@interface SmallClass : TestRoot @end + +@interface BigClass : TestRoot @end + diff --git a/test/unload.m b/test/unload.m new file mode 100644 index 0000000..33593d9 --- /dev/null +++ b/test/unload.m @@ -0,0 +1,179 @@ +// xpc leaks memory in dlopen(). Disable it. +// TEST_ENV XPC_SERVICES_UNAVAILABLE=1 +/* +TEST_BUILD + $C{COMPILE} $DIR/unload4.m -o unload4.dylib -dynamiclib + $C{COMPILE_C} $DIR/unload3.c -o unload3.dylib -dynamiclib + $C{COMPILE} $DIR/unload2.m -o unload2.bundle -bundle $C{FORCE_LOAD_ARCLITE} + $C{COMPILE} $DIR/unload.m -o unload.exe -framework Foundation +END +*/ + +/* +i386 Mac doesn't have libarclite +TEST_BUILD_OUTPUT +ld: warning: ignoring file .* which is not the architecture being linked \(i386\).* +OR +END + */ + +#include "test.h" +#include +#include +#include + +#include "unload.h" + +#if __has_feature(objc_arc) + +int main() +{ + testwarn("rdar://11368528 confused by Foundation"); + succeed(__FILE__); +} + +#else + +static id forward_handler(void) +{ + return 0; +} + +static BOOL hasName(const char * const *names, const char *query) +{ + const char *name; + while ((name = *names++)) { + if (strstr(name, query)) return YES; + } + + return NO; +} + +void cycle(void) +{ + int i; + char buf[100]; + unsigned int imageCount, imageCount0; + const char **names; + const char *name; + + names = objc_copyImageNames(&imageCount0); + testassert(names); + free(names); + + void *bundle = dlopen("unload2.bundle", RTLD_LAZY); + testassert(bundle); + + names = objc_copyImageNames(&imageCount); + testassert(names); + testassert(imageCount == imageCount0 + 1); + testassert(hasName(names, "unload2.bundle")); + free(names); + + Class small = objc_getClass("SmallClass"); + Class big = objc_getClass("BigClass"); + testassert(small); + testassert(big); + + name = class_getImageName(small); + testassert(name); + testassert(strstr(name, "unload2.bundle")); + name = class_getImageName(big); + testassert(name); + testassert(strstr(name, "unload2.bundle")); + + id o1 = [small new]; + id o2 = [big new]; + testassert(o1); + testassert(o2); + + // give BigClass and BigClass->isa large method caches (4692641) + // Flush caches part way through to test large empty caches. + for (i = 0; i < 3000; i++) { + sprintf(buf, "method_%d", i); + SEL sel = sel_registerName(buf); + ((void(*)(id, SEL))objc_msgSend)(o2, sel); + ((void(*)(id, SEL))objc_msgSend)(object_getClass(o2), sel); + } + _objc_flush_caches(object_getClass(o2)); + for (i = 0; i < 17000; i++) { + sprintf(buf, "method_%d", i); + SEL sel = sel_registerName(buf); + ((void(*)(id, SEL))objc_msgSend)(o2, sel); + ((void(*)(id, SEL))objc_msgSend)(object_getClass(o2), sel); + } + + RELEASE_VAR(o1); + RELEASE_VAR(o2); + + testcollect(); + + int err = dlclose(bundle); + testassert(err == 0); + err = dlclose(bundle); + testassert(err == -1); // already closed + + _objc_flush_caches(nil); + + testassert(objc_getClass("SmallClass") == NULL); + testassert(objc_getClass("BigClass") == NULL); + + names = objc_copyImageNames(&imageCount); + testassert(names); + testassert(imageCount == imageCount0); + testassert(! hasName(names, "unload2.bundle")); + free(names); + + // these selectors came from the bundle + testassert(0 == strcmp("unload2_instance_method", sel_getName(sel_registerName("unload2_instance_method")))); + testassert(0 == strcmp("unload2_category_method", sel_getName(sel_registerName("unload2_category_method")))); + + // This protocol came from the bundle. + // It isn't unloaded cleanly (rdar://20664713), but neither + // may it cause the protocol table to crash after unloading. + testassert(objc_getProtocol("SmallProtocol")); +} + + +int main() +{ + objc_setForwardHandler((void*)&forward_handler, (void*)&forward_handler); + +#if defined(__arm__) || defined(__arm64__) + int count = 10; +#else + int count = is_guardmalloc() ? 10 : 100; +#endif + + cycle(); +#if __LP64__ + // fixme heap use goes up 512 bytes after the 2nd cycle only - bad or not? + cycle(); +#endif + + leak_mark(); + while (count--) { + cycle(); + } + leak_check(0); + + // 5359412 Make sure dylibs with nothing other than image_info can close + void *dylib = dlopen("unload3.dylib", RTLD_LAZY); + testassert(dylib); + int err = dlclose(dylib); + testassert(err == 0); + err = dlclose(dylib); + testassert(err == -1); // already closed + + // Make sure dylibs with real objc content cannot close + dylib = dlopen("unload4.dylib", RTLD_LAZY); + testassert(dylib); + err = dlclose(dylib); + testassert(err == 0); + err = dlclose(dylib); + testassert(err == -1); // already closed + + succeed(__FILE__); +} + +#endif diff --git a/test/unload2.m b/test/unload2.m new file mode 100644 index 0000000..16ac0ca --- /dev/null +++ b/test/unload2.m @@ -0,0 +1,26 @@ +#include "unload.h" +#include "testroot.i" +#import + +@implementation SmallClass : TestRoot +-(void)unload2_instance_method { } +@end + + +@implementation BigClass : TestRoot +@end + +OBJC_ROOT_CLASS +@interface UnusedClass { id isa; } @end +@implementation UnusedClass @end + + +@protocol SmallProtocol +-(void)unload2_category_method; +@end + +@interface SmallClass (Category) @end + +@implementation SmallClass (Category) +-(void)unload2_category_method { } +@end diff --git a/test/unload3.c b/test/unload3.c new file mode 100644 index 0000000..caf0473 --- /dev/null +++ b/test/unload3.c @@ -0,0 +1,10 @@ +// unload3: contains imageinfo but no other objc metadata +// libobjc must not keep it open + +#include + +int fake[2] __attribute__((section("__DATA,__objc_imageinfo"))) + = { 0, TARGET_OS_SIMULATOR ? (1<<5) : 0 }; + +// silence "no debug symbols in executable" warning +void fn(void) { } diff --git a/test/unload4.m b/test/unload4.m new file mode 100644 index 0000000..9608e74 --- /dev/null +++ b/test/unload4.m @@ -0,0 +1,7 @@ +// unload4: contains some objc metadata other than imageinfo +// libobjc must keep it open + +int fake2 __attribute__((section("__DATA,__objc_foo"))) = 0; + +// getsectiondata() falls over if __TEXT has no contents +const char *unload4 = "unload4"; diff --git a/test/unwind.m b/test/unwind.m new file mode 100644 index 0000000..3759147 --- /dev/null +++ b/test/unwind.m @@ -0,0 +1,81 @@ +// TEST_CONFIG + +#include "test.h" +#include +#include + +static int state; + +@interface Foo : NSObject @end +@interface Bar : NSObject @end + +@interface Foo (Unimplemented) ++(void)method; +@end + +@implementation Bar @end + +@implementation Foo + +-(void)check { state++; } ++(void)check { testassert(!"caught class object, not instance"); } + +static id exc; + +static void handler(id unused, void *ctx) __attribute__((used)); +static void handler(id unused __unused, void *ctx __unused) +{ + testassert(state == 3); state++; +} + ++(BOOL) resolveClassMethod:(SEL)__unused name +{ + testassert(state == 1); state++; +#if TARGET_OS_OSX + objc_addExceptionHandler(&handler, 0); + testassert(state == 2); +#else + state++; // handler would have done this +#endif + state++; + exc = [Foo new]; + @throw exc; +} + + +@end + +int main() +{ + // unwind exception and alt handler through objc_msgSend() + + PUSH_POOL { + + const int count = is_guardmalloc() ? 1000 : 100000; + state = 0; + for (int i = 0; i < count; i++) { + @try { + testassert(state == 0); state++; + [Foo method]; + testassert(0); + } @catch (Bar *e) { + testassert(0); + } @catch (Foo *e) { + testassert(e == exc); + testassert(state == 4); state++; + testassert(state == 5); [e check]; // state++ + RELEASE_VAR(exc); + } @catch (id e) { + testassert(0); + } @catch (...) { + testassert(0); + } @finally { + testassert(state == 6); state++; + } + testassert(state == 7); state = 0; + } + + } POP_POOL; + + succeed(__FILE__); +} diff --git a/test/weak.h b/test/weak.h new file mode 100644 index 0000000..6bae410 --- /dev/null +++ b/test/weak.h @@ -0,0 +1,67 @@ +/* + To test -weak-l or -weak-framework: + * -DWEAK_IMPORT= + * -DWEAK_FRAMEWORK=1 + * -UEMPTY when building the weak-not-missing library + * -DEMPTY= when building the weak-missing library + + To test attribute((weak_import)): + * -DWEAK_IMPORT=__attribute__((weak_import)) + * -UWEAK_FRAMEWORK + * -UEMPTY when building the weak-not-missing library + * -DEMPTY= when building the weak-missing library + +*/ + +#include "test.h" +#include + +extern int state; + +WEAK_IMPORT OBJC_ROOT_CLASS +@interface MissingRoot { + id isa; +} ++(void) initialize; ++(Class) class; ++(id) alloc; +-(id) init; +-(void) dealloc; ++(int) method; +@end + +@interface MissingRoot (RR) +-(id) retain; +-(void) release; +@end + +WEAK_IMPORT +@interface MissingSuper : MissingRoot { + @public + int ivar; +} +@end + +OBJC_ROOT_CLASS +@interface NotMissingRoot { + id isa; +} ++(void) initialize; ++(Class) class; ++(id) alloc; +-(id) init; +-(void) dealloc; ++(int) method; +@end + +@interface NotMissingRoot (RR) +-(id) retain; +-(void) release; +@end + +@interface NotMissingSuper : NotMissingRoot { + @public + int unused[100]; + int ivar; +} +@end diff --git a/test/weak.m b/test/weak.m new file mode 100644 index 0000000..77edef6 --- /dev/null +++ b/test/weak.m @@ -0,0 +1,316 @@ +// See instructions in weak.h + +#include "test.h" +#include "weak.h" + +// Subclass of superclass that isn't there +@interface MyMissingSuper : MissingSuper ++(int) method; +@end +@implementation MyMissingSuper ++(int) method { return 1+[super method]; } ++(void) load { state++; } +@end + +// Subclass of subclass of superclass that isn't there +@interface MyMissingSub : MyMissingSuper ++(int) method; +@end +@implementation MyMissingSub ++(int) method { return 1+[super method]; } ++(void) load { state++; } +@end + +// Subclass of real superclass +@interface MyNotMissingSuper : NotMissingSuper ++(int) method; +@end +@implementation MyNotMissingSuper ++(int) method { return 1+[super method]; } ++(void) load { state++; } +@end + +// Subclass of subclass of superclass that isn't there +@interface MyNotMissingSub : MyNotMissingSuper ++(int) method; +@end +@implementation MyNotMissingSub ++(int) method { return 1+[super method]; } ++(void) load { state++; } +@end + +// Categories on all of the above +@interface MissingRoot (MissingRootExtras) ++(void)load; ++(int) cat_method; +@end +@implementation MissingRoot (MissingRootExtras) ++(void)load { state++; } ++(int) cat_method { return 40; } +@end + +@interface MissingSuper (MissingSuperExtras) ++(void)load; ++(int) cat_method; +@end +@implementation MissingSuper (MissingSuperExtras) ++(void)load { state++; } ++(int) cat_method { return 1+[super cat_method]; } +@end + +@interface MyMissingSuper (MyMissingSuperExtras) ++(void)load; ++(int) cat_method; +@end +@implementation MyMissingSuper (MyMissingSuperExtras) ++(void)load { state++; } ++(int) cat_method { return 1+[super cat_method]; } +@end + +@interface MyMissingSub (MyMissingSubExtras) ++(void)load; ++(int) cat_method; +@end +@implementation MyMissingSub (MyMissingSubExtras) ++(void)load { state++; } ++(int) cat_method { return 1+[super cat_method]; } +@end + + +@interface NotMissingRoot (NotMissingRootExtras) ++(void)load; ++(int) cat_method; +@end +@implementation NotMissingRoot (NotMissingRootExtras) ++(void)load { state++; } ++(int) cat_method { return 30; } +@end + +@interface NotMissingSuper (NotMissingSuperExtras) ++(void)load; ++(int) cat_method; +@end +@implementation NotMissingSuper (NotMissingSuperExtras) ++(void)load { state++; } ++(int) cat_method { return 1+[super cat_method]; } +@end + +@interface MyNotMissingSuper (MyNotMissingSuperExtras) ++(void)load; ++(int) cat_method; +@end +@implementation MyNotMissingSuper (MyNotMissingSuperExtras) ++(void)load { state++; } ++(int) cat_method { return 1+[super cat_method]; } +@end + +@interface MyNotMissingSub (MyNotMissingSubExtras) ++(void)load; ++(int) cat_method; +@end +@implementation MyNotMissingSub (MyNotMissingSubExtras) ++(void)load { state++; } ++(int) cat_method { return 1+[super cat_method]; } +@end + + +#if WEAK_FRAMEWORK +# define TESTIVAR(cond) testassert(cond) +#else +# define TESTIVAR(cond) /* rdar */ +#endif + +static BOOL classInList(__unsafe_unretained Class classes[], const char *name) +{ + for (int i = 0; classes[i] != nil; i++) { + if (0 == strcmp(class_getName(classes[i]), name)) return YES; + } + return NO; +} + +static BOOL classInNameList(const char **names, const char *name) +{ + const char **cp; + for (cp = names; *cp; cp++) { + if (0 == strcmp(*cp, name)) return YES; + } + return NO; +} + +int main(int argc __unused, char **argv) +{ + BOOL weakMissing; + if (strstr(argv[0], "-not-missing.exe")) { + weakMissing = NO; + } else if (strstr(argv[0], "-missing.exe")) { + weakMissing = YES; + } else { + fail("executable name must be weak*-missing.exe or weak*-not-missing.exe"); + } + + // class and category +load methods + if (weakMissing) testassert(state == 8); + else testassert(state == 16); + state = 0; + + // classes + testassert([NotMissingRoot class]); + testassert([NotMissingSuper class]); + testassert([MyNotMissingSuper class]); + testassert([MyNotMissingSub class]); + if (weakMissing) { + testassert([MissingRoot class] == nil); + testassert([MissingSuper class] == nil); + testassert([MyMissingSuper class] == nil); + testassert([MyMissingSub class] == nil); + } else { + testassert([MissingRoot class]); + testassert([MissingSuper class]); + testassert([MyMissingSuper class]); + testassert([MyMissingSub class]); + } + + // objc_getClass + testassert(objc_getClass("NotMissingRoot")); + testassert(objc_getClass("NotMissingSuper")); + testassert(objc_getClass("MyNotMissingSuper")); + testassert(objc_getClass("MyNotMissingSub")); + if (weakMissing) { + testassert(objc_getClass("MissingRoot") == nil); + testassert(objc_getClass("MissingSuper") == nil); + testassert(objc_getClass("MyMissingSuper") == nil); + testassert(objc_getClass("MyMissingSub") == nil); + } else { + testassert(objc_getClass("MissingRoot")); + testassert(objc_getClass("MissingSuper")); + testassert(objc_getClass("MyMissingSuper")); + testassert(objc_getClass("MyMissingSub")); + } + + // class list + union { + Class *c; + void *v; + } classes; + classes.c = objc_copyClassList(NULL); + testassert(classInList(classes.c, "NotMissingRoot")); + testassert(classInList(classes.c, "NotMissingSuper")); + testassert(classInList(classes.c, "MyNotMissingSuper")); + testassert(classInList(classes.c, "MyNotMissingSub")); + if (weakMissing) { + testassert(! classInList(classes.c, "MissingRoot")); + testassert(! classInList(classes.c, "MissingSuper")); + testassert(! classInList(classes.c, "MyMissingSuper")); + testassert(! classInList(classes.c, "MyMissingSub")); + } else { + testassert(classInList(classes.c, "MissingRoot")); + testassert(classInList(classes.c, "MissingSuper")); + testassert(classInList(classes.c, "MyMissingSuper")); + testassert(classInList(classes.c, "MyMissingSub")); + } + free(classes.v); + + // class name list + const char *image = class_getImageName(objc_getClass("NotMissingRoot")); + testassert(image); + const char **names = objc_copyClassNamesForImage(image, NULL); + testassert(names); + testassert(classInNameList(names, "NotMissingRoot")); + testassert(classInNameList(names, "NotMissingSuper")); + if (weakMissing) { + testassert(! classInNameList(names, "MissingRoot")); + testassert(! classInNameList(names, "MissingSuper")); + } else { + testassert(classInNameList(names, "MissingRoot")); + testassert(classInNameList(names, "MissingSuper")); + } + free(names); + + image = class_getImageName(objc_getClass("MyNotMissingSub")); + testassert(image); + names = objc_copyClassNamesForImage(image, NULL); + testassert(names); + testassert(classInNameList(names, "MyNotMissingSuper")); + testassert(classInNameList(names, "MyNotMissingSub")); + if (weakMissing) { + testassert(! classInNameList(names, "MyMissingSuper")); + testassert(! classInNameList(names, "MyMissingSub")); + } else { + testassert(classInNameList(names, "MyMissingSuper")); + testassert(classInNameList(names, "MyMissingSub")); + } + free(names); + + // methods + testassert(20 == [NotMissingRoot method]); + testassert(21 == [NotMissingSuper method]); + testassert(22 == [MyNotMissingSuper method]); + testassert(23 == [MyNotMissingSub method]); + if (weakMissing) { + testassert(0 == [MissingRoot method]); + testassert(0 == [MissingSuper method]); + testassert(0 == [MyMissingSuper method]); + testassert(0 == [MyMissingSub method]); + } else { + testassert(10 == [MissingRoot method]); + testassert(11 == [MissingSuper method]); + testassert(12 == [MyMissingSuper method]); + testassert(13 == [MyMissingSub method]); + } + + // category methods + testassert(30 == [NotMissingRoot cat_method]); + testassert(31 == [NotMissingSuper cat_method]); + testassert(32 == [MyNotMissingSuper cat_method]); + testassert(33 == [MyNotMissingSub cat_method]); + if (weakMissing) { + testassert(0 == [MissingRoot cat_method]); + testassert(0 == [MissingSuper cat_method]); + testassert(0 == [MyMissingSuper cat_method]); + testassert(0 == [MyMissingSub cat_method]); + } else { + testassert(40 == [MissingRoot cat_method]); + testassert(41 == [MissingSuper cat_method]); + testassert(42 == [MyMissingSuper cat_method]); + testassert(43 == [MyMissingSub cat_method]); + } + + // allocations and ivars + id obj; + NotMissingSuper *obj2; + MissingSuper *obj3; + testassert((obj = [[NotMissingRoot alloc] init])); + RELEASE_VAR(obj); + testassert((obj2 = [[NotMissingSuper alloc] init])); + TESTIVAR(obj2->ivar == 200); + RELEASE_VAR(obj2); + testassert((obj2 = [[MyNotMissingSuper alloc] init])); + TESTIVAR(obj2->ivar == 200); + RELEASE_VAR(obj2); + testassert((obj2 = [[MyNotMissingSub alloc] init])); + TESTIVAR(obj2->ivar == 200); + RELEASE_VAR(obj2); + if (weakMissing) { + testassert([[MissingRoot alloc] init] == nil); + testassert([[MissingSuper alloc] init] == nil); + testassert([[MyMissingSuper alloc] init] == nil); + testassert([[MyMissingSub alloc] init] == nil); + } else { + testassert((obj = [[MissingRoot alloc] init])); + RELEASE_VAR(obj); + testassert((obj3 = [[MissingSuper alloc] init])); + TESTIVAR(obj3->ivar == 100); + RELEASE_VAR(obj3); + testassert((obj3 = [[MyMissingSuper alloc] init])); + TESTIVAR(obj3->ivar == 100); + RELEASE_VAR(obj3); + testassert((obj3 = [[MyMissingSub alloc] init])); + TESTIVAR(obj3->ivar == 100); + RELEASE_VAR(obj3); + } + + *strrchr(argv[0], '.') = 0; + succeed(basename(argv[0])); + return 0; +} + diff --git a/test/weak2.m b/test/weak2.m new file mode 100644 index 0000000..394363f --- /dev/null +++ b/test/weak2.m @@ -0,0 +1,82 @@ +// See instructions in weak.h + +#include "test.h" +#include "weak.h" +#include + +int state = 0; + +static void *noop_fn(void *self, SEL _cmd __unused) { + return self; +} +static void *retain_fn(void *self, SEL _cmd __unused) { + void * (*fn)(void *) = (typeof(fn))_objc_rootRetain; + return fn(self); +} +static void release_fn(void *self, SEL _cmd __unused) { + void (*fn)(void *) = (typeof(fn))_objc_rootRelease; + fn(self); +} +static void *autorelease_fn(void *self, SEL _cmd __unused) { + void * (*fn)(void *) = (typeof(fn))_objc_rootAutorelease; + return fn(self); +} + +#if !defined(EMPTY) + +@implementation MissingRoot ++(void) initialize { } ++(Class) class { return self; } ++(id) alloc { return _objc_rootAlloc(self); } ++(id) allocWithZone:(void*)zone { return _objc_rootAllocWithZone(self, (malloc_zone_t *)zone); } +-(id) init { return self; } +-(void) dealloc { _objc_rootDealloc(self); } ++(int) method { return 10; } ++(void) load { + class_addMethod(self, sel_registerName("retain"), (IMP)retain_fn, ""); + class_addMethod(self, sel_registerName("release"), (IMP)release_fn, ""); + class_addMethod(self, sel_registerName("autorelease"), (IMP)autorelease_fn, ""); + + class_addMethod(object_getClass(self), sel_registerName("retain"), (IMP)noop_fn, ""); + class_addMethod(object_getClass(self), sel_registerName("release"), (IMP)noop_fn, ""); + class_addMethod(object_getClass(self), sel_registerName("autorelease"), (IMP)noop_fn, ""); + + state++; +} +@end + +@implementation MissingSuper ++(int) method { return 1+[super method]; } +-(id) init { self = [super init]; ivar = 100; return self; } ++(void) load { state++; } +@end + +#endif + +@implementation NotMissingRoot ++(void) initialize { } ++(Class) class { return self; } ++(id) alloc { return _objc_rootAlloc(self); } ++(id) allocWithZone:(void*)zone { return _objc_rootAllocWithZone(self, (malloc_zone_t *)zone); } +-(id) init { return self; } +-(void) dealloc { _objc_rootDealloc(self); } ++(int) method { return 20; } ++(void) load { + class_addMethod(self, sel_registerName("retain"), (IMP)retain_fn, ""); + class_addMethod(self, sel_registerName("release"), (IMP)release_fn, ""); + class_addMethod(self, sel_registerName("autorelease"), (IMP)autorelease_fn, ""); + + class_addMethod(object_getClass(self), sel_registerName("retain"), (IMP)noop_fn, ""); + class_addMethod(object_getClass(self), sel_registerName("release"), (IMP)noop_fn, ""); + class_addMethod(object_getClass(self), sel_registerName("autorelease"), (IMP)noop_fn, ""); + + state++; +} +@end + +@implementation NotMissingSuper ++(int) method { return 1+[super method]; } +-(id) init { self = [super init]; ivar = 200; return self; } ++(void) load { state++; } +@end + diff --git a/test/weakcopy.m b/test/weakcopy.m new file mode 100644 index 0000000..778e36a --- /dev/null +++ b/test/weakcopy.m @@ -0,0 +1,62 @@ +// TEST_CFLAGS -fobjc-weak + +#include "test.h" + +#include "testroot.i" +#include +#include +#include + +@interface Weak : TestRoot { + @public + __weak id value; +} +@end +@implementation Weak +@end + +Weak *oldObject; +Weak *newObject; + +int main() +{ + testonthread(^{ + TestRoot *value; + + PUSH_POOL { + value = [TestRoot new]; + testassert(value); + oldObject = [Weak new]; + testassert(oldObject); + + oldObject->value = value; + testassert(oldObject->value == value); + + newObject = [oldObject copy]; + testassert(newObject); + testassert(newObject->value == oldObject->value); + + newObject->value = nil; + testassert(newObject->value == nil); + testassert(oldObject->value == value); + } POP_POOL; + + testcollect(); + TestRootDealloc = 0; + RELEASE_VAR(value); + }); + + testcollect(); + testassert(TestRootDealloc); + +#if __has_feature(objc_arc_weak) + testassert(oldObject->value == nil); +#endif + testassert(newObject->value == nil); + + RELEASE_VAR(newObject); + RELEASE_VAR(oldObject); + + succeed(__FILE__); + return 0; +} diff --git a/test/weakframework-missing.m b/test/weakframework-missing.m new file mode 100644 index 0000000..1d92433 --- /dev/null +++ b/test/weakframework-missing.m @@ -0,0 +1,14 @@ +/* +TEST_BUILD + $C{COMPILE} $DIR/weak2.m -DWEAK_FRAMEWORK=1 -DWEAK_IMPORT= -UEMPTY -dynamiclib -o libweakframework.dylib + + $C{COMPILE} $DIR/weakframework-missing.m -L. -weak-lweakframework -o weakframework-missing.exe + + $C{COMPILE} $DIR/weak2.m -DWEAK_FRAMEWORK=1 -DWEAK_IMPORT= -DEMPTY= -dynamiclib -o libweakframework.dylib + +END +*/ + +#define WEAK_FRAMEWORK 1 +#define WEAK_IMPORT +#include "weak.m" diff --git a/test/weakframework-not-missing.m b/test/weakframework-not-missing.m new file mode 100644 index 0000000..c348ba9 --- /dev/null +++ b/test/weakframework-not-missing.m @@ -0,0 +1,11 @@ +/* +TEST_BUILD + $C{COMPILE} $DIR/weak2.m -DWEAK_FRAMEWORK=1 -DWEAK_IMPORT= -UEMPTY -dynamiclib -o libweakframework.dylib + + $C{COMPILE} $DIR/weakframework-not-missing.m -L. -weak-lweakframework -o weakframework-not-missing.exe +END +*/ + +#define WEAK_FRAMEWORK 1 +#define WEAK_IMPORT +#include "weak.m" diff --git a/test/weakimport-missing.m b/test/weakimport-missing.m new file mode 100644 index 0000000..2fcdf83 --- /dev/null +++ b/test/weakimport-missing.m @@ -0,0 +1,13 @@ +/* +TEST_BUILD + $C{COMPILE} $DIR/weak2.m -UWEAK_FRAMEWORK -DWEAK_IMPORT=__attribute__\\(\\(weak_import\\)\\) -UEMPTY -dynamiclib -o libweakimport.dylib + + $C{COMPILE} $DIR/weakimport-missing.m -L. -weak-lweakimport -o weakimport-missing.exe + + $C{COMPILE} $DIR/weak2.m -UWEAK_FRAMEWORK -DWEAK_IMPORT=__attribute__\\(\\(weak_import\\)\\) -DEMPTY= -dynamiclib -o libweakimport.dylib +END +*/ + +// #define WEAK_FRAMEWORK +#define WEAK_IMPORT __attribute__((weak_import)) +#include "weak.m" diff --git a/test/weakimport-not-missing.m b/test/weakimport-not-missing.m new file mode 100644 index 0000000..6f5e18c --- /dev/null +++ b/test/weakimport-not-missing.m @@ -0,0 +1,11 @@ +/* +TEST_BUILD + $C{COMPILE} $DIR/weak2.m -UWEAK_FRAMEWORK -DWEAK_IMPORT=__attribute__\\(\\(weak_import\\)\\) -UEMPTY -dynamiclib -o libweakimport.dylib + + $C{COMPILE} $DIR/weakimport-not-missing.m -L. -weak-lweakimport -o weakimport-not-missing.exe +END +*/ + +// #define WEAK_FRAMEWORK +#define WEAK_IMPORT __attribute__((weak_import)) +#include "weak.m" diff --git a/test/weakrace.m b/test/weakrace.m new file mode 100644 index 0000000..2ff2ea9 --- /dev/null +++ b/test/weakrace.m @@ -0,0 +1,76 @@ +// TEST_CONFIG MEM=mrc + +#include "test.h" +#include + +static semaphore_t go1; +static semaphore_t go2; +static semaphore_t done; + +#define VARCOUNT 100000 +static id obj; +static id vars[VARCOUNT]; + + +void *destroyer(void *arg __unused) +{ + while (1) { + semaphore_wait(go1); + for (int i = 0; i < VARCOUNT; i++) { + objc_destroyWeak(&vars[i]); + } + semaphore_signal(done); + } +} + + +void *deallocator(void *arg __unused) +{ + while (1) { + semaphore_wait(go2); + [obj release]; + semaphore_signal(done); + } +} + + +void cycle(void) +{ + // rdar://12896779 objc_destroyWeak() versus weak clear in dealloc + + // Clean up from previous cycle - objc_destroyWeak() doesn't set var to nil + for (int i = 0; i < VARCOUNT; i++) { + vars[i] = nil; + } + + obj = [NSObject new]; + for (int i = 0; i < VARCOUNT; i++) { + objc_storeWeak(&vars[i], obj); + } + + // let destroyer start before deallocator runs + semaphore_signal(go1); + sched_yield(); + semaphore_signal(go2); + + semaphore_wait(done); + semaphore_wait(done); +} + + +int main() +{ + semaphore_create(mach_task_self(), &go1, 0, 0); + semaphore_create(mach_task_self(), &go2, 0, 0); + semaphore_create(mach_task_self(), &done, 0, 0); + + pthread_t th[2]; + pthread_create(&th[1], NULL, deallocator, NULL); + pthread_create(&th[1], NULL, destroyer, NULL); + + for (int i = 0; i < 100; i++) { + cycle(); + } + + succeed(__FILE__); +} diff --git a/test/zone.m b/test/zone.m new file mode 100644 index 0000000..46ec5ea --- /dev/null +++ b/test/zone.m @@ -0,0 +1,40 @@ +// TEST_CONFIG + +#include "test.h" +#include +#include + +// Look for malloc zone "ObjC" iff OBJC_USE_INTERNAL_ZONE is set. +// This fails if objc tries to allocate before checking its own +// environment variables (rdar://6688423) + +int main() +{ + if (is_guardmalloc()) { + // guard malloc confuses this test + succeed(__FILE__); + } + + kern_return_t kr; + vm_address_t *zones; + unsigned int count, i; + BOOL has_objc = NO, want_objc = NO; + + want_objc = (getenv("OBJC_USE_INTERNAL_ZONE") != NULL) ? YES : NO; + testprintf("want objc %s\n", want_objc ? "YES" : "NO"); + + kr = malloc_get_all_zones(mach_task_self(), NULL, &zones, &count); + testassert(!kr); + for (i = 0; i < count; i++) { + const char *name = malloc_get_zone_name((malloc_zone_t *)zones[i]); + if (name) { + BOOL is_objc = (0 == strcmp(name, "ObjC_Internal")) ? YES : NO; + if (is_objc) has_objc = YES; + testprintf("zone %s\n", name); + } + } + + testassert(want_objc == has_objc); + + succeed(__FILE__); +} From af7906cbc37e5301a43f795e4a5c844a76a1b6bf Mon Sep 17 00:00:00 2001 From: Apple Open Source Date: Tue, 15 Jun 2021 22:37:23 +0200 Subject: [PATCH 08/12] Version 779.1 --- libobjc.order | 5 +- objc.xcodeproj/project.pbxproj | 47 +- runtime/DenseMapExtras.h | 86 + runtime/Messengers.subproj/objc-msg-arm.s | 141 +- runtime/Messengers.subproj/objc-msg-arm64.s | 146 +- runtime/Messengers.subproj/objc-msg-i386.s | 43 +- .../objc-msg-simulator-i386.s | 83 +- .../objc-msg-simulator-x86_64.s | 103 +- runtime/Messengers.subproj/objc-msg-win32.m | 15 +- runtime/Messengers.subproj/objc-msg-x86_64.s | 216 +- runtime/NSObject-internal.h | 161 + runtime/NSObject.mm | 575 ++-- runtime/Protocol.h | 10 +- runtime/Protocol.mm | 16 +- runtime/arm64-asm.h | 11 +- runtime/hashtable2.h | 6 +- runtime/llvm-DenseMap.h | 765 +++-- runtime/llvm-DenseMapInfo.h | 16 + runtime/llvm-DenseSet.h | 293 ++ runtime/llvm-type_traits.h | 325 +- runtime/maptable.h | 6 +- runtime/maptable.mm | 3 - runtime/message.h | 15 + runtime/objc-abi.h | 8 +- runtime/objc-api.h | 56 +- runtime/objc-auto.h | 60 +- runtime/objc-block-trampolines.mm | 51 +- runtime/objc-cache-old.h | 1 + runtime/objc-cache-old.mm | 22 +- runtime/objc-cache.h | 2 + runtime/objc-cache.mm | 444 ++- runtime/objc-class-old.mm | 115 +- runtime/objc-class.mm | 85 +- runtime/objc-config.h | 49 +- runtime/objc-env.h | 5 +- runtime/objc-errors.mm | 5 +- runtime/objc-exception.h | 36 +- runtime/objc-exception.mm | 58 +- runtime/objc-file.h | 18 +- runtime/objc-file.mm | 11 +- runtime/objc-gdb.h | 36 +- runtime/objc-initialize.mm | 109 +- runtime/objc-internal.h | 147 +- runtime/objc-layout.mm | 4 +- runtime/objc-loadmethod.mm | 4 +- runtime/objc-locks.h | 2 + runtime/objc-object.h | 118 +- runtime/objc-opt.mm | 134 +- runtime/objc-os.h | 97 +- runtime/objc-os.mm | 60 +- runtime/objc-private.h | 208 +- runtime/objc-references.h | 5 +- runtime/objc-references.mm | 403 +-- runtime/objc-runtime-new.h | 1014 +++--- runtime/objc-runtime-new.mm | 2917 ++++++++++------- runtime/objc-runtime-old.h | 12 +- runtime/objc-runtime-old.mm | 21 +- runtime/objc-runtime.mm | 146 +- runtime/objc-sel-old.mm | 28 +- runtime/objc-sel-table.s | 44 +- runtime/objc-sel.mm | 96 +- runtime/objc-sync.mm | 21 +- runtime/objc-weak.mm | 12 +- runtime/objc.h | 2 +- runtime/runtime.h | 126 +- test/addMethods.m | 7 +- test/association.m | 2 +- test/badCache.m | 37 +- test/bool.c | 2 +- test/category.m | 60 + test/{classpair.m => classpair.mm} | 22 +- test/copyFixupHandler.mm | 89 + test/copyProtocolList.m | 69 + test/customrr-nsobject-awz.m | 8 +- test/customrr-nsobject-core.m | 17 + test/customrr-nsobject-none.m | 6 +- test/customrr-nsobject-rr.m | 8 +- test/customrr-nsobject-rrawz.m | 10 +- test/customrr-nsobject.m | 54 + test/customrr.m | 30 +- test/customrr2.m | 2 +- test/duplicateProtocols.m | 42 + test/evil-category-0.m | 18 - test/evil-category-00.m | 24 - test/evil-category-000.m | 18 - test/evil-category-1.m | 24 - test/evil-category-2.m | 24 - test/evil-category-3.m | 24 - test/evil-category-4.m | 24 - test/evil-category-def.m | 73 - test/evil-class-0.m | 22 - test/evil-class-00.m | 28 - test/evil-class-000.m | 22 - test/evil-class-1.m | 28 - test/evil-class-2.m | 28 - test/evil-class-3.m | 28 - test/evil-class-4.m | 28 - test/evil-class-5.m | 30 - test/evil-class-def.m | 22 +- test/evil-main.m | 15 - test/gcenforcer-dylib-requiresgc.m | 5 +- test/gdb.m | 10 + test/imports.c | 5 +- test/include-warnings.c | 4 +- test/isaValidation.m | 6 +- test/load-image-notification-dylib.m | 7 + test/load-image-notification.m | 76 + test/methodCacheLeaks.m | 65 + test/msgSend.m | 13 +- test/nonpointerisa.m | 2 +- test/realizedClassGenerationCount.m | 29 + test/restartableRangesSynchronizeStress.m | 78 + test/setAssociatedObjectHook.m | 47 + test/swift-class-def.m | 422 +-- test/swiftMetadataInitializer.m | 10 +- test/swiftMetadataInitializerRealloc-dylib1.m | 64 + test/swiftMetadataInitializerRealloc-dylib2.m | 40 + test/swiftMetadataInitializerRealloc.m | 176 + test/synchronized.m | 12 + test/test.pl | 33 +- test/unload.m | 7 +- test/unload2.m | 16 + test/willInitializeClassFunc.m | 109 + 123 files changed, 7372 insertions(+), 4688 deletions(-) create mode 100644 runtime/DenseMapExtras.h create mode 100644 runtime/NSObject-internal.h create mode 100644 runtime/llvm-DenseSet.h rename test/{classpair.m => classpair.mm} (94%) create mode 100644 test/copyFixupHandler.mm create mode 100644 test/copyProtocolList.m create mode 100644 test/customrr-nsobject-core.m create mode 100644 test/duplicateProtocols.m delete mode 100644 test/evil-category-0.m delete mode 100644 test/evil-category-00.m delete mode 100644 test/evil-category-000.m delete mode 100644 test/evil-category-1.m delete mode 100644 test/evil-category-2.m delete mode 100644 test/evil-category-3.m delete mode 100644 test/evil-category-4.m delete mode 100644 test/evil-category-def.m delete mode 100644 test/evil-class-0.m delete mode 100644 test/evil-class-00.m delete mode 100644 test/evil-class-000.m delete mode 100644 test/evil-class-1.m delete mode 100644 test/evil-class-2.m delete mode 100644 test/evil-class-3.m delete mode 100644 test/evil-class-4.m delete mode 100644 test/evil-class-5.m delete mode 100644 test/evil-main.m create mode 100644 test/load-image-notification-dylib.m create mode 100644 test/load-image-notification.m create mode 100644 test/methodCacheLeaks.m create mode 100644 test/realizedClassGenerationCount.m create mode 100644 test/restartableRangesSynchronizeStress.m create mode 100644 test/setAssociatedObjectHook.m create mode 100644 test/swiftMetadataInitializerRealloc-dylib1.m create mode 100644 test/swiftMetadataInitializerRealloc-dylib2.m create mode 100644 test/swiftMetadataInitializerRealloc.m create mode 100644 test/willInitializeClassFunc.m diff --git a/libobjc.order b/libobjc.order index fc60cf4..c7415fc 100644 --- a/libobjc.order +++ b/libobjc.order @@ -1,7 +1,7 @@ __objc_init _environ_init _tls_init -_lock_init +_runtime_init _recursive_mutex_init _exception_init _map_images @@ -96,6 +96,7 @@ __ZNSt3__122__merge_move_constructIRN8method_t16SortBySELAddressEN13method_list_ __ZNSt3__119__merge_move_assignIRN8method_t16SortBySELAddressEPS1_S4_N13method_list_t15method_iteratorEEEvT0_S7_T1_S8_T2_T_ _NXPtrIsEqual __getObjc2CategoryList +__getObjc2CategoryList2 __Z29addUnattachedCategoryForClassP10category_tP7class_tP12_header_info __Z16remethodizeClassP7class_t __Z11flushCachesP7class_t @@ -143,7 +144,6 @@ __objc_insert_tagged_isa _objc_msgSend_fixup __objc_fixupMessageRef _objc_msgSend -__class_lookupMethodAndLoadCache3 _lookUpMethod _prepareForMethodLookup __class_initialize @@ -330,7 +330,6 @@ _objc_atomicCompareAndSwapGlobalBarrier _sel_getUid __ZN12_GLOBAL__N_119AutoreleasePoolPage11tls_deallocEPv __ZN12_GLOBAL__N_119AutoreleasePoolPage4killEv -__objc_constructOrFree _object_cxxConstruct _object_cxxConstructFromClass __class_hasCxxStructors diff --git a/objc.xcodeproj/project.pbxproj b/objc.xcodeproj/project.pbxproj index 9325b98..3c3853f 100644 --- a/objc.xcodeproj/project.pbxproj +++ b/objc.xcodeproj/project.pbxproj @@ -36,6 +36,13 @@ 393CEAC60DC69E67000B69DE /* objc-references.h in Headers */ = {isa = PBXBuildFile; fileRef = 393CEAC50DC69E67000B69DE /* objc-references.h */; }; 39ABD72312F0B61800D1054C /* objc-weak.h in Headers */ = {isa = PBXBuildFile; fileRef = 39ABD71F12F0B61800D1054C /* objc-weak.h */; }; 39ABD72412F0B61800D1054C /* objc-weak.mm in Sources */ = {isa = PBXBuildFile; fileRef = 39ABD72012F0B61800D1054C /* objc-weak.mm */; }; + 6E1475EA21DFDB1B001357EA /* llvm-AlignOf.h in Headers */ = {isa = PBXBuildFile; fileRef = 6E1475E521DFDB1A001357EA /* llvm-AlignOf.h */; }; + 6E1475EB21DFDB1B001357EA /* llvm-DenseMap.h in Headers */ = {isa = PBXBuildFile; fileRef = 6E1475E621DFDB1B001357EA /* llvm-DenseMap.h */; }; + 6E1475EC21DFDB1B001357EA /* llvm-DenseMapInfo.h in Headers */ = {isa = PBXBuildFile; fileRef = 6E1475E721DFDB1B001357EA /* llvm-DenseMapInfo.h */; }; + 6E1475ED21DFDB1B001357EA /* llvm-type_traits.h in Headers */ = {isa = PBXBuildFile; fileRef = 6E1475E821DFDB1B001357EA /* llvm-type_traits.h */; }; + 6E1475EE21DFDB1B001357EA /* llvm-MathExtras.h in Headers */ = {isa = PBXBuildFile; fileRef = 6E1475E921DFDB1B001357EA /* llvm-MathExtras.h */; }; + 6ECD0B1F2244999E00910D88 /* llvm-DenseSet.h in Headers */ = {isa = PBXBuildFile; fileRef = 6ECD0B1E2244999E00910D88 /* llvm-DenseSet.h */; }; + 7213C36321FA7C730090A271 /* NSObject-internal.h in Headers */ = {isa = PBXBuildFile; fileRef = 7213C36221FA7C730090A271 /* NSObject-internal.h */; settings = {ATTRIBUTES = (Private, ); }; }; 7593EC58202248E50046AB96 /* objc-object.h in Headers */ = {isa = PBXBuildFile; fileRef = 7593EC57202248DF0046AB96 /* objc-object.h */; }; 75A9504F202BAA0600D7D56F /* objc-locks-new.h in Headers */ = {isa = PBXBuildFile; fileRef = 75A9504E202BAA0300D7D56F /* objc-locks-new.h */; }; 75A95051202BAA9A00D7D56F /* objc-locks.h in Headers */ = {isa = PBXBuildFile; fileRef = 75A95050202BAA9A00D7D56F /* objc-locks.h */; }; @@ -118,6 +125,7 @@ 83F550E0155E030800E95D3B /* objc-cache-old.mm in Sources */ = {isa = PBXBuildFile; fileRef = 83F550DF155E030800E95D3B /* objc-cache-old.mm */; }; 87BB4EA70EC39854005D08E1 /* objc-probes.d in Sources */ = {isa = PBXBuildFile; fileRef = 87BB4E900EC39633005D08E1 /* objc-probes.d */; }; 9672F7EE14D5F488007CEC96 /* NSObject.mm in Sources */ = {isa = PBXBuildFile; fileRef = 9672F7ED14D5F488007CEC96 /* NSObject.mm */; }; + C2E6D3FC2225DCF00059DFAA /* DenseMapExtras.h in Headers */ = {isa = PBXBuildFile; fileRef = C2E6D3FB2225DCF00059DFAA /* DenseMapExtras.h */; }; E8923DA5116AB2820071B552 /* objc-block-trampolines.mm in Sources */ = {isa = PBXBuildFile; fileRef = E8923DA0116AB2820071B552 /* objc-block-trampolines.mm */; }; F9BCC71B205C68E800DD9AFC /* objc-blocktramps-arm64.s in Sources */ = {isa = PBXBuildFile; fileRef = 8379996D13CBAF6F007C2B5F /* objc-blocktramps-arm64.s */; }; /* End PBXBuildFile section */ @@ -144,6 +152,13 @@ 393CEAC50DC69E67000B69DE /* objc-references.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "objc-references.h"; path = "runtime/objc-references.h"; sourceTree = ""; }; 39ABD71F12F0B61800D1054C /* objc-weak.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "objc-weak.h"; path = "runtime/objc-weak.h"; sourceTree = ""; }; 39ABD72012F0B61800D1054C /* objc-weak.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = "objc-weak.mm"; path = "runtime/objc-weak.mm"; sourceTree = ""; }; + 6E1475E521DFDB1A001357EA /* llvm-AlignOf.h */ = {isa = PBXFileReference; fileEncoding = 4; indentWidth = 2; lastKnownFileType = sourcecode.c.h; name = "llvm-AlignOf.h"; path = "runtime/llvm-AlignOf.h"; sourceTree = ""; tabWidth = 2; }; + 6E1475E621DFDB1B001357EA /* llvm-DenseMap.h */ = {isa = PBXFileReference; fileEncoding = 4; indentWidth = 2; lastKnownFileType = sourcecode.c.h; name = "llvm-DenseMap.h"; path = "runtime/llvm-DenseMap.h"; sourceTree = ""; tabWidth = 2; }; + 6E1475E721DFDB1B001357EA /* llvm-DenseMapInfo.h */ = {isa = PBXFileReference; fileEncoding = 4; indentWidth = 2; lastKnownFileType = sourcecode.c.h; name = "llvm-DenseMapInfo.h"; path = "runtime/llvm-DenseMapInfo.h"; sourceTree = ""; tabWidth = 2; }; + 6E1475E821DFDB1B001357EA /* llvm-type_traits.h */ = {isa = PBXFileReference; fileEncoding = 4; indentWidth = 2; lastKnownFileType = sourcecode.c.h; name = "llvm-type_traits.h"; path = "runtime/llvm-type_traits.h"; sourceTree = ""; tabWidth = 2; }; + 6E1475E921DFDB1B001357EA /* llvm-MathExtras.h */ = {isa = PBXFileReference; fileEncoding = 4; indentWidth = 2; lastKnownFileType = sourcecode.c.h; name = "llvm-MathExtras.h"; path = "runtime/llvm-MathExtras.h"; sourceTree = ""; tabWidth = 2; }; + 6ECD0B1E2244999E00910D88 /* llvm-DenseSet.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "llvm-DenseSet.h"; path = "runtime/llvm-DenseSet.h"; sourceTree = ""; }; + 7213C36221FA7C730090A271 /* NSObject-internal.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "NSObject-internal.h"; path = "runtime/NSObject-internal.h"; sourceTree = ""; }; 7593EC57202248DF0046AB96 /* objc-object.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "objc-object.h"; path = "runtime/objc-object.h"; sourceTree = ""; }; 75A9504E202BAA0300D7D56F /* objc-locks-new.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "objc-locks-new.h"; path = "runtime/objc-locks-new.h"; sourceTree = ""; }; 75A95050202BAA9A00D7D56F /* objc-locks.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "objc-locks.h"; path = "runtime/objc-locks.h"; sourceTree = ""; }; @@ -231,6 +246,7 @@ 87BB4E900EC39633005D08E1 /* objc-probes.d */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.dtrace; name = "objc-probes.d"; path = "runtime/objc-probes.d"; sourceTree = ""; }; 9672F7ED14D5F488007CEC96 /* NSObject.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = NSObject.mm; path = runtime/NSObject.mm; sourceTree = ""; }; BC8B5D1212D3D48100C78A5B /* libauto.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libauto.dylib; path = /usr/lib/libauto.dylib; sourceTree = ""; }; + C2E6D3FB2225DCF00059DFAA /* DenseMapExtras.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = DenseMapExtras.h; path = runtime/DenseMapExtras.h; sourceTree = ""; }; D2AAC0630554660B00DB518D /* libobjc.A.dylib */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.dylib"; includeInIndex = 0; path = libobjc.A.dylib; sourceTree = BUILT_PRODUCTS_DIR; }; E8923D9C116AB2820071B552 /* objc-blocktramps-i386.s */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.asm; name = "objc-blocktramps-i386.s"; path = "runtime/objc-blocktramps-i386.s"; sourceTree = ""; }; E8923D9D116AB2820071B552 /* objc-blocktramps-x86_64.s */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.asm; name = "objc-blocktramps-x86_64.s"; path = "runtime/objc-blocktramps-x86_64.s"; sourceTree = ""; }; @@ -366,6 +382,7 @@ 838485C70D6D688200CEA253 /* Private Headers */ = { isa = PBXGroup; children = ( + 7213C36221FA7C730090A271 /* NSObject-internal.h */, 83112ED30F00599600A5FBAF /* objc-internal.h */, 834EC0A311614167009B2563 /* objc-abi.h */, 838485BB0D6D687300CEA253 /* maptable.h */, @@ -402,6 +419,13 @@ 8384862A0D6D6ABC00CEA253 /* Project Headers */ = { isa = PBXGroup; children = ( + 6E1475E521DFDB1A001357EA /* llvm-AlignOf.h */, + 6E1475E621DFDB1B001357EA /* llvm-DenseMap.h */, + 6E1475E721DFDB1B001357EA /* llvm-DenseMapInfo.h */, + 6ECD0B1E2244999E00910D88 /* llvm-DenseSet.h */, + 6E1475E921DFDB1B001357EA /* llvm-MathExtras.h */, + 6E1475E821DFDB1B001357EA /* llvm-type_traits.h */, + C2E6D3FB2225DCF00059DFAA /* DenseMapExtras.h */, 83D9269721225A7400299F69 /* arm64-asm.h */, 83D92695212254CF00299F69 /* isa.h */, 838485CF0D6D68A200CEA253 /* objc-config.h */, @@ -448,9 +472,13 @@ files = ( 83A4AEDE1EA08C7200ACADDE /* ObjectiveC.apinotes in Headers */, 75A95051202BAA9A00D7D56F /* objc-locks.h in Headers */, + 6E1475ED21DFDB1B001357EA /* llvm-type_traits.h in Headers */, 83A4AEDC1EA0840800ACADDE /* module.modulemap in Headers */, 830F2A980D738DC200392440 /* hashtable.h in Headers */, + 6E1475EA21DFDB1B001357EA /* llvm-AlignOf.h in Headers */, 838485BF0D6D687300CEA253 /* hashtable2.h in Headers */, + 6E1475EC21DFDB1B001357EA /* llvm-DenseMapInfo.h in Headers */, + C2E6D3FC2225DCF00059DFAA /* DenseMapExtras.h in Headers */, 838486260D6D68F000CEA253 /* List.h in Headers */, 838485C30D6D687300CEA253 /* maptable.h in Headers */, 838486280D6D6A2400CEA253 /* message.h in Headers */, @@ -459,6 +487,7 @@ 838485F00D6D68A200CEA253 /* objc-auto.h in Headers */, 838485F40D6D68A200CEA253 /* objc-class.h in Headers */, 838485F60D6D68A200CEA253 /* objc-config.h in Headers */, + 6E1475EE21DFDB1B001357EA /* llvm-MathExtras.h in Headers */, 838485F80D6D68A200CEA253 /* objc-exception.h in Headers */, 83BE02E80FCCB24D00661494 /* objc-file-old.h in Headers */, 83BE02E90FCCB24D00661494 /* objc-file.h in Headers */, @@ -473,10 +502,12 @@ 831C85D50E10CF850066E64C /* objc-os.h in Headers */, 838486030D6D68A200CEA253 /* objc-private.h in Headers */, 393CEAC60DC69E67000B69DE /* objc-references.h in Headers */, + 6E1475EB21DFDB1B001357EA /* llvm-DenseMap.h in Headers */, 838486070D6D68A200CEA253 /* objc-runtime-new.h in Headers */, 83BE02EA0FCCB24D00661494 /* objc-runtime-old.h in Headers */, 8384860A0D6D68A200CEA253 /* objc-runtime.h in Headers */, 8384860C0D6D68A200CEA253 /* objc-sel-set.h in Headers */, + 7213C36321FA7C730090A271 /* NSObject-internal.h in Headers */, 838486100D6D68A200CEA253 /* objc-sync.h in Headers */, 83D92696212254CF00299F69 /* isa.h in Headers */, 838486130D6D68A200CEA253 /* objc.h in Headers */, @@ -486,6 +517,7 @@ 838486200D6D68A800CEA253 /* runtime.h in Headers */, 39ABD72312F0B61800D1054C /* objc-weak.h in Headers */, 83F4B52815E843B100E0926F /* NSObjCRuntime.h in Headers */, + 6ECD0B1F2244999E00910D88 /* llvm-DenseSet.h in Headers */, 83F4B52915E843B100E0926F /* NSObject.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; @@ -701,11 +733,11 @@ 1DEB914B08733D8E0010E9CD /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { - ARCHS = "$(ARCHS_STANDARD_32_64_BIT)"; COPY_HEADERS_RUN_UNIFDEF = YES; COPY_HEADERS_UNIFDEF_FLAGS = "-UBUILD_FOR_OSX"; "COPY_HEADERS_UNIFDEF_FLAGS[sdk=macosx*]" = "-DBUILD_FOR_OSX"; COPY_PHASE_STRIP = NO; + DEPLOYMENT_LOCATION = YES; DYLIB_CURRENT_VERSION = 228; EXECUTABLE_PREFIX = lib; GCC_CW_ASM_SYNTAX = NO; @@ -720,10 +752,12 @@ ); INSTALL_PATH = /usr/lib; IS_ZIPPERED = YES; + LLVM_LTO = NO; ORDER_FILE = "$(SDKROOT)/AppleInternal/OrderFiles/libobjc.order"; "ORDER_FILE[sdk=iphonesimulator*]" = ""; OTHER_CFLAGS = ( "-fdollars-in-identifiers", + "-fno-objc-convert-messages-to-runtime-calls", "$(OTHER_CFLAGS)", ); "OTHER_LDFLAGS[sdk=iphoneos*][arch=*]" = ( @@ -781,6 +815,7 @@ COPY_HEADERS_RUN_UNIFDEF = YES; COPY_HEADERS_UNIFDEF_FLAGS = "-UBUILD_FOR_OSX"; "COPY_HEADERS_UNIFDEF_FLAGS[sdk=macosx*]" = "-DBUILD_FOR_OSX"; + DEPLOYMENT_LOCATION = YES; DYLIB_CURRENT_VERSION = 228; EXECUTABLE_PREFIX = lib; GCC_CW_ASM_SYNTAX = NO; @@ -798,6 +833,7 @@ "ORDER_FILE[sdk=iphonesimulator*]" = ""; OTHER_CFLAGS = ( "-fdollars-in-identifiers", + "-fno-objc-convert-messages-to-runtime-calls", "$(OTHER_CFLAGS)", ); "OTHER_LDFLAGS[sdk=iphoneos*][arch=*]" = ( @@ -878,12 +914,15 @@ GCC_WARN_ABOUT_RETURN_TYPE = YES; GCC_WARN_SHADOW = YES; GCC_WARN_UNUSED_VARIABLE = YES; + LLVM_LTO = YES; OTHER_CFLAGS = ""; "OTHER_CFLAGS[arch=x86_64]" = "-fobjc-legacy-dispatch"; OTHER_CPLUSPLUSFLAGS = ( "$(OTHER_CFLAGS)", "-D_LIBCPP_VISIBLE=\"\"", ); + SDKROOT = macosx.internal; + SUPPORTED_PLATFORMS = "macosx iphoneos"; WARNING_CFLAGS = ( "-Wall", "-Wextra", @@ -920,7 +959,8 @@ GCC_WARN_ABOUT_RETURN_TYPE = YES; GCC_WARN_SHADOW = YES; GCC_WARN_UNUSED_VARIABLE = YES; - "OTHER_CFLAGS[arch=i386]" = "-momit-leaf-frame-pointer"; + LLVM_LTO = YES; + OTHER_CFLAGS = "-momit-leaf-frame-pointer"; "OTHER_CFLAGS[arch=x86_64]" = ( "-momit-leaf-frame-pointer", "-fobjc-legacy-dispatch", @@ -929,6 +969,8 @@ "$(OTHER_CFLAGS)", "-D_LIBCPP_VISIBLE=\"\"", ); + SDKROOT = macosx.internal; + SUPPORTED_PLATFORMS = "macosx iphoneos"; WARNING_CFLAGS = ( "-Wall", "-Wextra", @@ -976,7 +1018,6 @@ F9BCC725205C68E800DD9AFC /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { - ARCHS = "$(ARCHS_STANDARD_32_64_BIT)"; COPY_HEADERS_RUN_UNIFDEF = YES; COPY_HEADERS_UNIFDEF_FLAGS = "-UBUILD_FOR_OSX"; "COPY_HEADERS_UNIFDEF_FLAGS[sdk=macosx*]" = "-DBUILD_FOR_OSX"; diff --git a/runtime/DenseMapExtras.h b/runtime/DenseMapExtras.h new file mode 100644 index 0000000..19dc4ee --- /dev/null +++ b/runtime/DenseMapExtras.h @@ -0,0 +1,86 @@ +/* + * Copyright (c) 2019 Apple Inc. All Rights Reserved. + * + * @APPLE_LICENSE_HEADER_START@ + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPLE_LICENSE_HEADER_END@ + */ + +#ifndef DENSEMAPEXTRAS_H +#define DENSEMAPEXTRAS_H + +#include "llvm-DenseMap.h" +#include "llvm-DenseSet.h" + +namespace objc { + +// We cannot use a C++ static initializer to initialize certain globals because +// libc calls us before our C++ initializers run. We also don't want a global +// pointer to some globals because of the extra indirection. +// +// ExplicitInit / LazyInit wrap doing it the hard way. +template +class ExplicitInit { + alignas(Type) uint8_t _storage[sizeof(Type)]; + +public: + template + void init(Ts &&... Args) { + new (_storage) Type(std::forward(Args)...); + } + + Type &get() { + return *reinterpret_cast(_storage); + } +}; + +template +class LazyInit { + alignas(Type) uint8_t _storage[sizeof(Type)]; + bool _didInit; + +public: + template + Type *get(bool allowCreate, Ts &&... Args) { + if (!_didInit) { + if (!allowCreate) { + return nullptr; + } + new (_storage) Type(std::forward(Args)...); + _didInit = true; + } + return reinterpret_cast(_storage); + } +}; + +// Convenience class for Dense Maps & Sets +template +class ExplicitInitDenseMap : public ExplicitInit> { }; + +template +class LazyInitDenseMap : public LazyInit> { }; + +template +class ExplicitInitDenseSet : public ExplicitInit> { }; + +template +class LazyInitDenseSet : public LazyInit> { }; + +} // namespace objc + +#endif /* DENSEMAPEXTRAS_H */ diff --git a/runtime/Messengers.subproj/objc-msg-arm.s b/runtime/Messengers.subproj/objc-msg-arm.s index 01419dc..b1a9aec 100644 --- a/runtime/Messengers.subproj/objc-msg-arm.s +++ b/runtime/Messengers.subproj/objc-msg-arm.s @@ -102,41 +102,34 @@ _objc_indexed_classes: -// _objc_entryPoints and _objc_exitPoints are used by method dispatch +// _objc_restartableRanges is used by method dispatch // caching code to figure out whether any threads are actively // in the cache for dispatching. The labels surround the asm code // that do cache lookups. The tables are zero-terminated. -.align 2 -.private_extern _objc_entryPoints -_objc_entryPoints: - .long _cache_getImp - .long _objc_msgSend - .long _objc_msgSend_stret - .long _objc_msgSendSuper - .long _objc_msgSendSuper_stret - .long _objc_msgSendSuper2 - .long _objc_msgSendSuper2_stret - .long _objc_msgLookup - .long _objc_msgLookup_stret - .long _objc_msgLookupSuper2 - .long _objc_msgLookupSuper2_stret - .long 0 - -.private_extern _objc_exitPoints -_objc_exitPoints: - .long LExit_cache_getImp - .long LExit_objc_msgSend - .long LExit_objc_msgSend_stret - .long LExit_objc_msgSendSuper - .long LExit_objc_msgSendSuper_stret - .long LExit_objc_msgSendSuper2 - .long LExit_objc_msgSendSuper2_stret - .long LExit_objc_msgLookup - .long LExit_objc_msgLookup_stret - .long LExit_objc_msgLookupSuper2 - .long LExit_objc_msgLookupSuper2_stret - .long 0 +.macro RestartableEntry + .long LLookupStart$0 + .long 0 + .short LLookupEnd$0 - LLookupStart$0 + .short 0xffff // poor ol' armv7 doesn't support kernel based recovery + .long 0 +.endmacro + + .align 4 + .private_extern _objc_restartableRanges +_objc_restartableRanges: + RestartableEntry _cache_getImp + RestartableEntry _objc_msgSend + RestartableEntry _objc_msgSend_stret + RestartableEntry _objc_msgSendSuper + RestartableEntry _objc_msgSendSuper_stret + RestartableEntry _objc_msgSendSuper2 + RestartableEntry _objc_msgSendSuper2_stret + RestartableEntry _objc_msgLookup + RestartableEntry _objc_msgLookup_stret + RestartableEntry _objc_msgLookupSuper2 + RestartableEntry _objc_msgLookupSuper2_stret + .fill 16, 1, 0 /******************************************************************** @@ -231,8 +224,8 @@ LExit$0: ///////////////////////////////////////////////////////////////////// // -// CacheLookup NORMAL|STRET -// CacheLookup2 NORMAL|STRET +// CacheLookup NORMAL|STRET +// CacheLookup2 NORMAL|STRET // // Locate the implementation for a selector in a class's method cache. // @@ -249,7 +242,32 @@ LExit$0: ///////////////////////////////////////////////////////////////////// .macro CacheLookup - + // + // Restart protocol: + // + // As soon as we're past the LLookupStart$1 label we may have loaded + // an invalid cache pointer or mask. + // + // When task_restartable_ranges_synchronize() is called, + // (or when a signal hits us) before we're past LLookupEnd$1, + // then our PC will be reset to LCacheMiss$1 which forcefully + // jumps to the cache-miss codepath. + // + // It is assumed that the CacheMiss codepath starts right at the end + // of CacheLookup2 and will re-setup the registers to meet the cache-miss + // requirements: + // + // GETIMP: + // The cache-miss is just returning NULL (setting r9 to 0) + // + // NORMAL and STRET: + // - r0 or r1 (STRET) contains the receiver + // - r1 or r2 (STRET) contains the selector + // - r9 contains the isa (reloaded from r0/r1) + // - other registers are set as per calling conventions + // +LLookupStart$1: + ldrh r12, [r9, #CACHE_MASK] // r12 = mask ldr r9, [r9, #CACHE] // r9 = buckets .if $0 == STRET @@ -282,12 +300,14 @@ LExit$0: #endif 8: cmp r12, #1 - blo 8f // if (bucket->sel == 0) cache miss + blo LCacheMiss$1 // if (bucket->sel == 0) cache miss it eq // if (bucket->sel == 1) cache wrap ldreq r9, [r9, #CACHED_IMP] // bucket->imp is before first bucket ldr r12, [r9, #8]! // r12 = (++bucket)->sel b 6b -8: + +LLookupEnd$1: +LCacheMiss$1: .endmacro @@ -332,12 +352,12 @@ LExit$0: STATIC_ENTRY _cache_getImp mov r9, r0 - CacheLookup NORMAL + CacheLookup NORMAL, _cache_getImp // cache hit, IMP in r12 mov r0, r12 bx lr // return imp - CacheLookup2 GETIMP + CacheLookup2 GETIMP, _cache_getImp // cache miss, return nil mov r0, #0 bx lr @@ -363,11 +383,11 @@ LExit$0: ldr r9, [r0] // r9 = self->isa GetClassFromIsa // r9 = class - CacheLookup NORMAL + CacheLookup NORMAL, _objc_msgSend // cache hit, IMP in r12, eq already set for nonstret forwarding bx r12 // call imp - CacheLookup2 NORMAL + CacheLookup2 NORMAL, _objc_msgSend // cache miss ldr r9, [r0] // r9 = self->isa GetClassFromIsa // r9 = class @@ -390,11 +410,11 @@ LNilReceiver: ldr r9, [r0] // r9 = self->isa GetClassFromIsa // r9 = class - CacheLookup NORMAL + CacheLookup NORMAL, _objc_msgLookup // cache hit, IMP in r12, eq already set for nonstret forwarding bx lr - CacheLookup2 NORMAL + CacheLookup2 NORMAL, _objc_msgLookup // cache miss ldr r9, [r0] // r9 = self->isa GetClassFromIsa // r9 = class @@ -438,11 +458,11 @@ LNilReceiver: ldr r9, [r1] // r9 = self->isa GetClassFromIsa // r9 = class - CacheLookup STRET + CacheLookup STRET, _objc_msgSend_stret // cache hit, IMP in r12, ne already set for stret forwarding bx r12 - CacheLookup2 STRET + CacheLookup2 STRET, _objc_msgSend_stret // cache miss ldr r9, [r1] // r9 = self->isa GetClassFromIsa // r9 = class @@ -460,11 +480,11 @@ LNilReceiver: ldr r9, [r1] // r9 = self->isa GetClassFromIsa // r9 = class - CacheLookup STRET + CacheLookup STRET, _objc_msgLookup_stret // cache hit, IMP in r12, ne already set for stret forwarding bx lr - CacheLookup2 STRET + CacheLookup2 STRET, _objc_msgLookup_stret // cache miss ldr r9, [r1] // r9 = self->isa GetClassFromIsa // r9 = class @@ -496,12 +516,12 @@ LNilReceiver: ENTRY _objc_msgSendSuper ldr r9, [r0, #CLASS] // r9 = struct super->class - CacheLookup NORMAL + CacheLookup NORMAL, _objc_msgSendSuper // cache hit, IMP in r12, eq already set for nonstret forwarding ldr r0, [r0, #RECEIVER] // load real receiver bx r12 // call imp - CacheLookup2 NORMAL + CacheLookup2 NORMAL, _objc_msgSendSuper // cache miss ldr r9, [r0, #CLASS] // r9 = struct super->class ldr r0, [r0, #RECEIVER] // load real receiver @@ -523,12 +543,12 @@ LNilReceiver: ldr r9, [r0, #CLASS] // class = struct super->class ldr r9, [r9, #SUPERCLASS] // class = class->superclass - CacheLookup NORMAL + CacheLookup NORMAL, _objc_msgSendSuper2 // cache hit, IMP in r12, eq already set for nonstret forwarding ldr r0, [r0, #RECEIVER] // load real receiver bx r12 // call imp - CacheLookup2 NORMAL + CacheLookup2 NORMAL, _objc_msgSendSuper2 // cache miss ldr r9, [r0, #CLASS] // class = struct super->class ldr r9, [r9, #SUPERCLASS] // class = class->superclass @@ -542,12 +562,12 @@ LNilReceiver: ldr r9, [r0, #CLASS] // class = struct super->class ldr r9, [r9, #SUPERCLASS] // class = class->superclass - CacheLookup NORMAL + CacheLookup NORMAL, _objc_msgLookupSuper2 // cache hit, IMP in r12, eq already set for nonstret forwarding ldr r0, [r0, #RECEIVER] // load real receiver bx lr - CacheLookup2 NORMAL + CacheLookup2 NORMAL, _objc_msgLookupSuper2 // cache miss ldr r9, [r0, #CLASS] ldr r9, [r9, #SUPERCLASS] // r9 = class to search @@ -572,12 +592,12 @@ LNilReceiver: ENTRY _objc_msgSendSuper_stret ldr r9, [r1, #CLASS] // r9 = struct super->class - CacheLookup STRET + CacheLookup STRET, _objc_msgSendSuper_stret // cache hit, IMP in r12, ne already set for stret forwarding ldr r1, [r1, #RECEIVER] // load real receiver bx r12 // call imp - CacheLookup2 STRET + CacheLookup2 STRET, _objc_msgSendSuper_stret // cache miss ldr r9, [r1, #CLASS] // r9 = struct super->class ldr r1, [r1, #RECEIVER] // load real receiver @@ -594,12 +614,12 @@ LNilReceiver: ldr r9, [r1, #CLASS] // class = struct super->class ldr r9, [r9, #SUPERCLASS] // class = class->superclass - CacheLookup STRET + CacheLookup STRET, _objc_msgSendSuper2_stret // cache hit, IMP in r12, ne already set for stret forwarding ldr r1, [r1, #RECEIVER] // load real receiver bx r12 // call imp - CacheLookup2 STRET + CacheLookup2 STRET, _objc_msgSendSuper2_stret // cache miss ldr r9, [r1, #CLASS] // class = struct super->class ldr r9, [r9, #SUPERCLASS] // class = class->superclass @@ -613,12 +633,12 @@ LNilReceiver: ldr r9, [r1, #CLASS] // class = struct super->class ldr r9, [r9, #SUPERCLASS] // class = class->superclass - CacheLookup STRET + CacheLookup STRET, _objc_msgLookupSuper2_stret // cache hit, IMP in r12, ne already set for stret forwarding ldr r1, [r1, #RECEIVER] // load real receiver bx lr - CacheLookup2 STRET + CacheLookup2 STRET, _objc_msgLookupSuper2_stret // cache miss ldr r9, [r1, #CLASS] ldr r9, [r9, #SUPERCLASS] // r9 = class to search @@ -651,6 +671,7 @@ LNilReceiver: sub sp, #8 // align stack FP_SAVE + // lookUpImpOrForward(obj, sel, cls, LOOKUP_INITIALIZE | LOOKUP_RESOLVER) .if $0 == NORMAL // receiver already in r0 // selector already in r1 @@ -659,8 +680,8 @@ LNilReceiver: mov r1, r2 // selector .endif mov r2, r9 // class to search - - blx __class_lookupMethodAndLoadCache3 + mov r3, #3 // LOOKUP_INITIALIZE | LOOKUP_INITIALIZE + blx _lookUpImpOrForward mov r12, r0 // r12 = IMP .if $0 == NORMAL diff --git a/runtime/Messengers.subproj/objc-msg-arm64.s b/runtime/Messengers.subproj/objc-msg-arm64.s index 89975d0..6bf3f29 100755 --- a/runtime/Messengers.subproj/objc-msg-arm64.s +++ b/runtime/Messengers.subproj/objc-msg-arm64.s @@ -31,34 +31,37 @@ #include #include "isa.h" #include "arm64-asm.h" +#include "objc-config.h" .data -// _objc_entryPoints and _objc_exitPoints are used by method dispatch +// _objc_restartableRanges is used by method dispatch // caching code to figure out whether any threads are actively // in the cache for dispatching. The labels surround the asm code // that do cache lookups. The tables are zero-terminated. -.align 4 -.private_extern _objc_entryPoints -_objc_entryPoints: - PTR _cache_getImp - PTR _objc_msgSend - PTR _objc_msgSendSuper - PTR _objc_msgSendSuper2 - PTR _objc_msgLookup - PTR _objc_msgLookupSuper2 - PTR 0 - -.private_extern _objc_exitPoints -_objc_exitPoints: - PTR LExit_cache_getImp - PTR LExit_objc_msgSend - PTR LExit_objc_msgSendSuper - PTR LExit_objc_msgSendSuper2 - PTR LExit_objc_msgLookup - PTR LExit_objc_msgLookupSuper2 - PTR 0 +.macro RestartableEntry +#if __LP64__ + .quad LLookupStart$0 +#else + .long LLookupStart$0 + .long 0 +#endif + .short LLookupEnd$0 - LLookupStart$0 + .short LLookupRecover$0 - LLookupStart$0 + .long 0 +.endmacro + + .align 4 + .private_extern _objc_restartableRanges +_objc_restartableRanges: + RestartableEntry _cache_getImp + RestartableEntry _objc_msgSend + RestartableEntry _objc_msgSendSuper + RestartableEntry _objc_msgSendSuper2 + RestartableEntry _objc_msgLookup + RestartableEntry _objc_msgLookupSuper2 + .fill 16, 1, 0 /* objc_super parameter to sendSuper */ @@ -168,10 +171,15 @@ LExit$0: /******************************************************************** * - * CacheLookup NORMAL|GETIMP|LOOKUP - * + * CacheLookup NORMAL|GETIMP|LOOKUP + * * Locate the implementation for a selector in a class method cache. * + * When this is used in a function that doesn't hold the runtime lock, + * this represents the critical section that may access dead memory. + * If the kernel causes one of these functions to go down the recovery + * path, we pretend the lookup failed by jumping the JumpMiss branch. + * * Takes: * x1 = selector * x16 = class to be searched @@ -189,19 +197,19 @@ LExit$0: #define GETIMP 1 #define LOOKUP 2 -// CacheHit: x17 = cached IMP, x12 = address of cached IMP, x1 = SEL +// CacheHit: x17 = cached IMP, x12 = address of cached IMP, x1 = SEL, x16 = isa .macro CacheHit .if $0 == NORMAL - TailCallCachedImp x17, x12, x1 // authenticate and call imp + TailCallCachedImp x17, x12, x1, x16 // authenticate and call imp .elseif $0 == GETIMP mov p0, p17 cbz p0, 9f // don't ptrauth a nil imp - AuthAndResignAsIMP x0, x12, x1 // authenticate imp and re-sign as IMP + AuthAndResignAsIMP x0, x12, x1, x16 // authenticate imp and re-sign as IMP 9: ret // return IMP .elseif $0 == LOOKUP // No nil check for ptrauth: the caller would crash anyway when they // jump to a nil IMP. We don't care if that jump also fails ptrauth. - AuthAndResignAsIMP x17, x12, x1 // authenticate imp and re-sign as IMP + AuthAndResignAsIMP x17, x12, x1, x16 // authenticate imp and re-sign as IMP ret // return imp via x17 .else .abort oops @@ -234,12 +242,46 @@ LExit$0: .endmacro .macro CacheLookup + // + // Restart protocol: + // + // As soon as we're past the LLookupStart$1 label we may have loaded + // an invalid cache pointer or mask. + // + // When task_restartable_ranges_synchronize() is called, + // (or when a signal hits us) before we're past LLookupEnd$1, + // then our PC will be reset to LLookupRecover$1 which forcefully + // jumps to the cache-miss codepath which have the following + // requirements: + // + // GETIMP: + // The cache-miss is just returning NULL (setting x0 to 0) + // + // NORMAL and LOOKUP: + // - x0 contains the receiver + // - x1 contains the selector + // - x16 contains the isa + // - other registers are set as per calling conventions + // +LLookupStart$1: + // p1 = SEL, p16 = isa - ldp p10, p11, [x16, #CACHE] // p10 = buckets, p11 = occupied|mask -#if !__LP64__ - and w11, w11, 0xffff // p11 = mask + ldr p11, [x16, #CACHE] // p11 = mask|buckets + +#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16 + and p10, p11, #0x0000ffffffffffff // p10 = buckets + and p12, p1, p11, LSR #48 // x12 = _cmd & mask +#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_LOW_4 + and p10, p11, #~0xf // p10 = buckets + and p11, p11, #0xf // p11 = maskShift + mov p12, #0xffff + lsr p11, p12, p11 // p11 = mask = 0xffff >> p11 + and p12, p1, p11 // x12 = _cmd & mask +#else +#error Unsupported cache mask storage for ARM64. #endif - and w12, w1, w11 // x12 = _cmd & mask + + add p12, p10, p12, LSL #(1+PTRSHIFT) // p12 = buckets + ((_cmd & mask) << (1+PTRSHIFT)) @@ -256,8 +298,15 @@ LExit$0: b 1b // loop 3: // wrap: p12 = first bucket, w11 = mask - add p12, p12, w11, UXTW #(1+PTRSHIFT) - // p12 = buckets + (mask << 1+PTRSHIFT) +#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16 + add p12, p12, p11, LSR #(48 - (1+PTRSHIFT)) + // p12 = buckets + (mask << 1+PTRSHIFT) +#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_LOW_4 + add p12, p12, p11, LSL #(1+PTRSHIFT) + // p12 = buckets + (mask << 1+PTRSHIFT) +#else +#error Unsupported cache mask storage for ARM64. +#endif // Clone scanning loop to miss instead of hang when cache is corrupt. // The slow path may detect any corruption and halt later. @@ -274,9 +323,11 @@ LExit$0: ldp p17, p9, [x12, #-BUCKET_SIZE]! // {imp, sel} = *--bucket b 1b // loop +LLookupEnd$1: +LLookupRecover$1: 3: // double wrap JumpMiss $0 - + .endmacro @@ -314,7 +365,8 @@ _objc_debug_taggedpointer_ext_classes: ldr p13, [x0] // p13 = isa GetClassFromIsa_p16 p13 // p16 = class LGetIsaDone: - CacheLookup NORMAL // calls imp or objc_msgSend_uncached + // calls imp or objc_msgSend_uncached + CacheLookup NORMAL, _objc_msgSend #if SUPPORT_TAGGED_POINTERS LNilOrTagged: @@ -362,21 +414,22 @@ LReturnZero: ldr p13, [x0] // p13 = isa GetClassFromIsa_p16 p13 // p16 = class LLookup_GetIsaDone: - CacheLookup LOOKUP // returns imp + // returns imp + CacheLookup LOOKUP, _objc_msgLookup #if SUPPORT_TAGGED_POINTERS LLookup_NilOrTagged: b.eq LLookup_Nil // nil check // tagged - mov x10, #0xf000000000000000 - cmp x0, x10 - b.hs LLookup_ExtTag adrp x10, _objc_debug_taggedpointer_classes@PAGE add x10, x10, _objc_debug_taggedpointer_classes@PAGEOFF ubfx x11, x0, #60, #4 ldr x16, [x10, x11, LSL #3] - b LLookup_GetIsaDone + adrp x10, _OBJC_CLASS_$___NSUnrecognizedTaggedPointer@PAGE + add x10, x10, _OBJC_CLASS_$___NSUnrecognizedTaggedPointer@PAGEOFF + cmp x10, x16 + b.ne LLookup_GetIsaDone LLookup_ExtTag: adrp x10, _objc_debug_taggedpointer_ext_classes@PAGE @@ -412,7 +465,8 @@ LLookup_Nil: UNWIND _objc_msgSendSuper, NoFrame ldp p0, p16, [x0] // p0 = real receiver, p16 = class - CacheLookup NORMAL // calls imp or objc_msgSend_uncached + // calls imp or objc_msgSend_uncached + CacheLookup NORMAL, _objc_msgSendSuper END_ENTRY _objc_msgSendSuper @@ -423,7 +477,7 @@ LLookup_Nil: ldp p0, p16, [x0] // p0 = real receiver, p16 = class ldr p16, [x16, #SUPERCLASS] // p16 = class->superclass - CacheLookup NORMAL + CacheLookup NORMAL, _objc_msgSendSuper2 END_ENTRY _objc_msgSendSuper2 @@ -433,7 +487,7 @@ LLookup_Nil: ldp p0, p16, [x0] // p0 = real receiver, p16 = class ldr p16, [x16, #SUPERCLASS] // p16 = class->superclass - CacheLookup LOOKUP + CacheLookup LOOKUP, _objc_msgLookupSuper2 END_ENTRY _objc_msgLookupSuper2 @@ -457,9 +511,11 @@ LLookup_Nil: stp x6, x7, [sp, #(8*16+6*8)] str x8, [sp, #(8*16+8*8)] + // lookUpImpOrForward(obj, sel, cls, LOOKUP_INITIALIZE | LOOKUP_RESOLVER) // receiver and selector already in x0 and x1 mov x2, x16 - bl __class_lookupMethodAndLoadCache3 + mov x3, #3 + bl _lookUpImpOrForward // IMP in x0 mov x17, x0 @@ -508,7 +564,7 @@ LLookup_Nil: STATIC_ENTRY _cache_getImp GetClassFromIsa_p16 p0 - CacheLookup GETIMP + CacheLookup GETIMP, _cache_getImp LGetImpMiss: mov p0, #0 diff --git a/runtime/Messengers.subproj/objc-msg-i386.s b/runtime/Messengers.subproj/objc-msg-i386.s index 6f71a51..7473227 100644 --- a/runtime/Messengers.subproj/objc-msg-i386.s +++ b/runtime/Messengers.subproj/objc-msg-i386.s @@ -40,32 +40,29 @@ .data -// _objc_entryPoints and _objc_exitPoints are used by objc +// _objc_restartableRanges is used by method dispatch // to get the critical regions for which method caches // cannot be garbage collected. -.align 2 -.private_extern _objc_entryPoints -_objc_entryPoints: - .long __cache_getImp - .long __cache_getMethod - .long _objc_msgSend - .long _objc_msgSend_fpret - .long _objc_msgSend_stret - .long _objc_msgSendSuper - .long _objc_msgSendSuper_stret +.macro RestartableEntry + .long $0 .long 0 - -.private_extern _objc_exitPoints -_objc_exitPoints: - .long LGetImpExit - .long LGetMethodExit - .long LMsgSendExit - .long LMsgSendFpretExit - .long LMsgSendStretExit - .long LMsgSendSuperExit - .long LMsgSendSuperStretExit + .short $1 - $0 + .short 0xffff // The old runtime doesn't support kernel based recovery .long 0 +.endmacro + + .align 4 + .private_extern _objc_restartableRanges +_objc_restartableRanges: + RestartableEntry __cache_getImp, LGetImpExit + RestartableEntry __cache_getMethod, LGetMethodExit + RestartableEntry _objc_msgSend, LMsgSendExit + RestartableEntry _objc_msgSend_fpret, LMsgSendFpretExit + RestartableEntry _objc_msgSend_stret, LMsgSendStretExit + RestartableEntry _objc_msgSendSuper, LMsgSendSuperExit + RestartableEntry _objc_msgSendSuper_stret, LMsgSendSuperStretExit + .fill 16, 1, 0 /******************************************************************** @@ -444,10 +441,12 @@ LMsgSendHitInstrumentDone_$0_$1_$2: movdqa %xmm1, 2*16(%esp) movdqa %xmm0, 1*16(%esp) + // lookUpImpOrForward(obj, sel, cls, LOOKUP_INITIALIZE | LOOKUP_RESOLVER) + movl $$3, 12(%esp) // LOOKUP_INITIALIZE | LOOKUP_RESOLVER movl %eax, 8(%esp) // class movl %ecx, 4(%esp) // selector movl %edx, 0(%esp) // receiver - call __class_lookupMethodAndLoadCache3 + call _lookUpImpOrForward movdqa 4*16(%esp), %xmm3 movdqa 3*16(%esp), %xmm2 diff --git a/runtime/Messengers.subproj/objc-msg-simulator-i386.s b/runtime/Messengers.subproj/objc-msg-simulator-i386.s index 63631fa..727b983 100644 --- a/runtime/Messengers.subproj/objc-msg-simulator-i386.s +++ b/runtime/Messengers.subproj/objc-msg-simulator-i386.s @@ -28,44 +28,35 @@ .data -// _objc_entryPoints and _objc_exitPoints are used by objc +// _objc_restartableRanges is used by method dispatch // to get the critical regions for which method caches // cannot be garbage collected. -.align 2 -.private_extern _objc_entryPoints -_objc_entryPoints: - .long _cache_getImp - .long _objc_msgSend - .long _objc_msgSend_fpret - .long _objc_msgSend_stret - .long _objc_msgSendSuper - .long _objc_msgSendSuper2 - .long _objc_msgSendSuper_stret - .long _objc_msgSendSuper2_stret - .long _objc_msgLookup - .long _objc_msgLookup_fpret - .long _objc_msgLookup_stret - .long _objc_msgLookupSuper2 - .long _objc_msgLookupSuper2_stret +.macro RestartableEntry + .long $0 .long 0 - -.private_extern _objc_exitPoints -_objc_exitPoints: - .long LExit_cache_getImp - .long LExit_objc_msgSend - .long LExit_objc_msgSend_fpret - .long LExit_objc_msgSend_stret - .long LExit_objc_msgSendSuper - .long LExit_objc_msgSendSuper2 - .long LExit_objc_msgSendSuper_stret - .long LExit_objc_msgSendSuper2_stret - .long LExit_objc_msgLookup - .long LExit_objc_msgLookup_fpret - .long LExit_objc_msgLookup_stret - .long LExit_objc_msgLookupSuper2 - .long LExit_objc_msgLookupSuper2_stret + .short LExit$0 - $0 + .short 0xffff // The simulator doesn't support kernel based recovery .long 0 +.endmacro + + .align 4 + .private_extern _objc_restartableRanges +_objc_restartableRanges: + RestartableEntry _cache_getImp + RestartableEntry _objc_msgSend + RestartableEntry _objc_msgSend_fpret + RestartableEntry _objc_msgSend_stret + RestartableEntry _objc_msgSendSuper + RestartableEntry _objc_msgSendSuper2 + RestartableEntry _objc_msgSendSuper_stret + RestartableEntry _objc_msgSendSuper2_stret + RestartableEntry _objc_msgLookup + RestartableEntry _objc_msgLookup_fpret + RestartableEntry _objc_msgLookup_stret + RestartableEntry _objc_msgLookupSuper2 + RestartableEntry _objc_msgLookupSuper2_stret + .fill 16, 1, 0 /******************************************************************** @@ -228,21 +219,25 @@ LExit$0: .if $1 == GETIMP movl cached_imp(%eax), %eax // return imp - ret + cmpl $$0, %eax + jz 9f // don't xor a nil imp + xorl %edx, %eax // xor the isa with the imp +9: ret .else +.if $1 == CALL + xorl cached_imp(%eax), %edx // xor imp and isa .if $0 != STRET - // eq already set for forwarding by `jne` + // ne already set for forwarding by `xor` .else - test %eax, %eax // set ne for stret forwarding + cmp %eax, %eax // set eq for stret forwarding .endif - -.if $1 == CALL - jmp *cached_imp(%eax) // call imp + jmp *%edx // call imp .elseif $1 == LOOKUP movl cached_imp(%eax), %eax // return imp + xorl %edx, %eax // xor isa into imp ret .else @@ -337,10 +332,12 @@ LExit$0: movdqa %xmm1, 2*16(%esp) movdqa %xmm0, 1*16(%esp) + // lookUpImpOrForward(obj, sel, cls, LOOKUP_INITIALIZE | LOOKUP_RESOLVER) + movl $$3, 12(%esp) // LOOKUP_INITIALIZE | LOOKUP_RESOLVER movl %edx, 8(%esp) // class movl %ecx, 4(%esp) // selector movl %eax, 0(%esp) // receiver - call __class_lookupMethodAndLoadCache3 + call _lookUpImpOrForward // imp in eax @@ -350,9 +347,9 @@ LExit$0: movdqa 1*16(%esp), %xmm0 .if $0 == NORMAL - cmp %eax, %eax // set eq for nonstret forwarding -.else test %eax, %eax // set ne for stret forwarding +.else + cmp %eax, %eax // set eq for nonstret forwarding .endif leave @@ -853,7 +850,7 @@ L_forward_stret_handler: // THIS IS NOT A CALLABLE C FUNCTION // Out-of-band condition register is NE for stret, EQ otherwise. - jne __objc_msgForward_stret + je __objc_msgForward_stret jmp __objc_msgForward END_ENTRY _objc_msgForward_impcache diff --git a/runtime/Messengers.subproj/objc-msg-simulator-x86_64.s b/runtime/Messengers.subproj/objc-msg-simulator-x86_64.s index c1ef837..a5410c4 100644 --- a/runtime/Messengers.subproj/objc-msg-simulator-x86_64.s +++ b/runtime/Messengers.subproj/objc-msg-simulator-x86_64.s @@ -34,48 +34,36 @@ .data -// _objc_entryPoints and _objc_exitPoints are used by objc +// _objc_restartableRanges is used by method dispatch // to get the critical regions for which method caches // cannot be garbage collected. -.align 4 -.private_extern _objc_entryPoints -_objc_entryPoints: - .quad _cache_getImp - .quad _objc_msgSend - .quad _objc_msgSend_fpret - .quad _objc_msgSend_fp2ret - .quad _objc_msgSend_stret - .quad _objc_msgSendSuper - .quad _objc_msgSendSuper_stret - .quad _objc_msgSendSuper2 - .quad _objc_msgSendSuper2_stret - .quad _objc_msgLookup - .quad _objc_msgLookup_fpret - .quad _objc_msgLookup_fp2ret - .quad _objc_msgLookup_stret - .quad _objc_msgLookupSuper2 - .quad _objc_msgLookupSuper2_stret - .quad 0 - -.private_extern _objc_exitPoints -_objc_exitPoints: - .quad LExit_cache_getImp - .quad LExit_objc_msgSend - .quad LExit_objc_msgSend_fpret - .quad LExit_objc_msgSend_fp2ret - .quad LExit_objc_msgSend_stret - .quad LExit_objc_msgSendSuper - .quad LExit_objc_msgSendSuper_stret - .quad LExit_objc_msgSendSuper2 - .quad LExit_objc_msgSendSuper2_stret - .quad LExit_objc_msgLookup - .quad LExit_objc_msgLookup_fpret - .quad LExit_objc_msgLookup_fp2ret - .quad LExit_objc_msgLookup_stret - .quad LExit_objc_msgLookupSuper2 - .quad LExit_objc_msgLookupSuper2_stret - .quad 0 +.macro RestartableEntry + .quad $0 + .short LExit$0 - $0 + .short 0xffff // The simulator doesn't support kernel based recovery + .long 0 +.endmacro + + .align 4 + .private_extern _objc_restartableRanges +_objc_restartableRanges: + RestartableEntry _cache_getImp + RestartableEntry _objc_msgSend + RestartableEntry _objc_msgSend_fpret + RestartableEntry _objc_msgSend_fp2ret + RestartableEntry _objc_msgSend_stret + RestartableEntry _objc_msgSendSuper + RestartableEntry _objc_msgSendSuper_stret + RestartableEntry _objc_msgSendSuper2 + RestartableEntry _objc_msgSendSuper2_stret + RestartableEntry _objc_msgLookup + RestartableEntry _objc_msgLookup_fpret + RestartableEntry _objc_msgLookup_fp2ret + RestartableEntry _objc_msgLookup_stret + RestartableEntry _objc_msgLookupSuper2 + RestartableEntry _objc_msgLookupSuper2_stret + .fill 16, 1, 0 /******************************************************************** @@ -245,28 +233,30 @@ LExit$0: .macro CacheHit - // CacheHit must always be preceded by a not-taken `jne` instruction - // in order to set the correct flags for _objc_msgForward_impcache. - // r11 = found bucket .if $1 == GETIMP movq cached_imp(%r11), %rax // return imp - ret + cmpq $$0, %rax + jz 9f // don't xor a nil imp + xorq %r10, %rax // xor the isa with the imp +9: ret .else +.if $1 == CALL + movq cached_imp(%r11), %r11 // load imp + xorq %r10, %r11 // xor imp and isa .if $0 != STRET - // eq already set for forwarding by `jne` + // ne already set for forwarding by `xor` .else - test %r11, %r11 // set ne for stret forwarding + cmp %r11, %r11 // set eq for stret forwarding .endif + jmp *%r11 // call imp -.if $1 == CALL - jmp *cached_imp(%r11) // call imp - .elseif $1 == LOOKUP - movq cached_imp(%r11), %r11 // return imp + movq cached_imp(%r11), %r11 + xorq %r10, %r11 // return imp ^ isa ret .else @@ -294,7 +284,6 @@ LExit$0: cmpq cached_sel(%r11), %a3 // if (bucket->sel != _cmd) .endif jne 1f // scan more - // CacheHit must always be preceded by a not-taken `jne` instruction CacheHit $0, $1 // call or return imp 1: @@ -310,7 +299,6 @@ LExit$0: cmpq cached_sel(%r11), %a3 // if (bucket->sel != _cmd) .endif jne 1b // scan more - // CacheHit must always be preceded by a not-taken `jne` instruction CacheHit $0, $1 // call or return imp 3: @@ -336,7 +324,6 @@ LExit$0: cmpq cached_sel(%r11), %a3 // if (bucket->sel != _cmd) .endif jne 1b // scan more - // CacheHit must always be preceded by a not-taken `jne` instruction CacheHit $0, $1 // call or return imp 3: @@ -381,8 +368,7 @@ LExit$0: push %a6 movdqa %xmm7, -0x10(%rbp) - // _class_lookupMethodAndLoadCache3(receiver, selector, class) - + // lookUpImpOrForward(obj, sel, cls, LOOKUP_INITIALIZE | LOOKUP_RESOLVER) .if $0 == NORMAL // receiver already in a1 // selector already in a2 @@ -391,7 +377,8 @@ LExit$0: movq %a3, %a2 .endif movq %r10, %a3 - call __class_lookupMethodAndLoadCache3 + movl $$3, %a4d + call _lookUpImpOrForward // IMP is now in %rax movq %rax, %r11 @@ -413,9 +400,9 @@ LExit$0: movdqa -0x10(%rbp), %xmm7 .if $0 == NORMAL - cmp %r11, %r11 // set eq for nonstret forwarding -.else test %r11, %r11 // set ne for stret forwarding +.else + cmp %r11, %r11 // set eq for nonstret forwarding .endif leave @@ -1061,7 +1048,7 @@ LCacheMiss: // THIS IS NOT A CALLABLE C FUNCTION // Out-of-band condition register is NE for stret, EQ otherwise. - jne __objc_msgForward_stret + je __objc_msgForward_stret jmp __objc_msgForward END_ENTRY __objc_msgForward_impcache diff --git a/runtime/Messengers.subproj/objc-msg-win32.m b/runtime/Messengers.subproj/objc-msg-win32.m index 8821b0a..432d661 100644 --- a/runtime/Messengers.subproj/objc-msg-win32.m +++ b/runtime/Messengers.subproj/objc-msg-win32.m @@ -175,10 +175,11 @@ OBJC_EXPORT __declspec(naked) id objc_msgSend(id a, SEL b, ...) mov eax, isa[edx] // MethodTableLookup WORD_RETURN, MSG_SEND + push $3 push eax push ecx push edx - call _class_lookupMethodAndLoadCache3 + call lookUpImpOrFoward mov edx, kFwdMsgSend leave @@ -244,10 +245,11 @@ OBJC_EXPORT __declspec(naked) double objc_msgSend_fpret(id a, SEL b, ...) mov eax, isa[edx] // MethodTableLookup WORD_RETURN, MSG_SEND + push $3 push eax push ecx push edx - call _class_lookupMethodAndLoadCache3 + call lookUpImpOrFoward mov edx, kFwdMsgSend leave @@ -313,10 +315,11 @@ OBJC_EXPORT __declspec(naked) id objc_msgSendSuper(struct objc_super *a, SEL b, mov eax, super_class[eax] // MethodTableLookup WORD_RETURN, MSG_SENDSUPER + push $3 push eax push ecx push edx - call _class_lookupMethodAndLoadCache3 + call lookUpImpOrFoward mov edx, kFwdMsgSend leave @@ -375,10 +378,11 @@ OBJC_EXPORT __declspec(naked) void objc_msgSend_stret(void) mov eax, isa[edx] // MethodTableLookup WORD_RETURN, MSG_SEND + push $3 push eax push ecx push edx - call _class_lookupMethodAndLoadCache3 + call lookUpImpOrFoward mov edx, kFwdMsgSendStret leave @@ -445,10 +449,11 @@ OBJC_EXPORT __declspec(naked) id objc_msgSendSuper_stret(struct objc_super *a, S mov eax, super_class[eax] // MethodTableLookup WORD_RETURN, MSG_SENDSUPER + push $3 push eax push ecx push edx - call _class_lookupMethodAndLoadCache3 + call lookUpImpOrFoward mov edx, kFwdMsgSendStret leave diff --git a/runtime/Messengers.subproj/objc-msg-x86_64.s b/runtime/Messengers.subproj/objc-msg-x86_64.s index 9427306..8fc6d48 100644 --- a/runtime/Messengers.subproj/objc-msg-x86_64.s +++ b/runtime/Messengers.subproj/objc-msg-x86_64.s @@ -36,48 +36,36 @@ .data -// _objc_entryPoints and _objc_exitPoints are used by objc +// _objc_restartableRanges is used by method dispatch // to get the critical regions for which method caches // cannot be garbage collected. -.align 4 -.private_extern _objc_entryPoints -_objc_entryPoints: - .quad _cache_getImp - .quad _objc_msgSend - .quad _objc_msgSend_fpret - .quad _objc_msgSend_fp2ret - .quad _objc_msgSend_stret - .quad _objc_msgSendSuper - .quad _objc_msgSendSuper_stret - .quad _objc_msgSendSuper2 - .quad _objc_msgSendSuper2_stret - .quad _objc_msgLookup - .quad _objc_msgLookup_fpret - .quad _objc_msgLookup_fp2ret - .quad _objc_msgLookup_stret - .quad _objc_msgLookupSuper2 - .quad _objc_msgLookupSuper2_stret - .quad 0 - -.private_extern _objc_exitPoints -_objc_exitPoints: - .quad LExit_cache_getImp - .quad LExit_objc_msgSend - .quad LExit_objc_msgSend_fpret - .quad LExit_objc_msgSend_fp2ret - .quad LExit_objc_msgSend_stret - .quad LExit_objc_msgSendSuper - .quad LExit_objc_msgSendSuper_stret - .quad LExit_objc_msgSendSuper2 - .quad LExit_objc_msgSendSuper2_stret - .quad LExit_objc_msgLookup - .quad LExit_objc_msgLookup_fpret - .quad LExit_objc_msgLookup_fp2ret - .quad LExit_objc_msgLookup_stret - .quad LExit_objc_msgLookupSuper2 - .quad LExit_objc_msgLookupSuper2_stret - .quad 0 +.macro RestartableEntry + .quad LLookupStart$0 + .short LLookupEnd$0 - LLookupStart$0 + .short LCacheMiss$0 - LLookupStart$0 + .long 0 +.endmacro + + .align 4 + .private_extern _objc_restartableRanges +_objc_restartableRanges: + RestartableEntry _cache_getImp + RestartableEntry _objc_msgSend + RestartableEntry _objc_msgSend_fpret + RestartableEntry _objc_msgSend_fp2ret + RestartableEntry _objc_msgSend_stret + RestartableEntry _objc_msgSendSuper + RestartableEntry _objc_msgSendSuper_stret + RestartableEntry _objc_msgSendSuper2 + RestartableEntry _objc_msgSendSuper2_stret + RestartableEntry _objc_msgLookup + RestartableEntry _objc_msgLookup_fpret + RestartableEntry _objc_msgLookup_fp2ret + RestartableEntry _objc_msgLookup_stret + RestartableEntry _objc_msgLookupSuper2 + RestartableEntry _objc_msgLookupSuper2_stret + .fill 16, 1, 0 /******************************************************************** @@ -127,9 +115,6 @@ _objc_exitPoints: * DO NOT USE THESE LABELS ELSEWHERE * Reserved labels: 6: 7: 8: 9: ********************************************************************/ -#define LCacheMiss 6 -#define LCacheMiss_f 6f -#define LCacheMiss_b 6b #define LNilTestSlow 7 #define LNilTestSlow_f 7f #define LNilTestSlow_b 7b @@ -235,10 +220,15 @@ LExit$0: ///////////////////////////////////////////////////////////////////// // -// CacheLookup return-type, caller +// CacheLookup return-type, caller, function // // Locate the implementation for a class in a selector's method cache. // +// When this is used in a function that doesn't hold the runtime lock, +// this represents the critical section that may access dead memory. +// If the kernel causes one of these functions to go down the recovery +// path, we pretend the lookup failed by jumping the JumpMiss branch. +// // Takes: // $0 = NORMAL, FPRET, FP2RET, STRET // $1 = CALL, LOOKUP, GETIMP @@ -254,28 +244,30 @@ LExit$0: .macro CacheHit - // CacheHit must always be preceded by a not-taken `jne` instruction - // in order to set the correct flags for _objc_msgForward_impcache. - // r11 = found bucket .if $1 == GETIMP movq cached_imp(%r11), %rax // return imp - ret + cmpq $$0, %rax + jz 9f // don't xor a nil imp + xorq %r10, %rax // xor the isa with the imp +9: ret .else +.if $1 == CALL + movq cached_imp(%r11), %r11 // load imp + xorq %r10, %r11 // xor imp and isa .if $0 != STRET - // eq already set for forwarding by `jne` + // ne already set for forwarding by `xor` .else - test %r11, %r11 // set ne for stret forwarding + cmp %r11, %r11 // set eq for stret forwarding .endif + jmp *%r11 // call imp -.if $1 == CALL - jmp *cached_imp(%r11) // call imp - .elseif $1 == LOOKUP - movq cached_imp(%r11), %r11 // return imp + movq cached_imp(%r11), %r11 + xorq %r10, %r11 // return imp ^ isa ret .else @@ -288,6 +280,29 @@ LExit$0: .macro CacheLookup + // + // Restart protocol: + // + // As soon as we're past the LLookupStart$1 label we may have loaded + // an invalid cache pointer or mask. + // + // When task_restartable_ranges_synchronize() is called, + // (or when a signal hits us) before we're past LLookupEnd$1, + // then our PC will be reset to LCacheMiss$1 which forcefully + // jumps to the cache-miss codepath which have the following + // requirements: + // + // GETIMP: + // The cache-miss is just returning NULL (setting %rax to 0) + // + // NORMAL and STRET: + // - a1 or a2 (STRET) contains the receiver + // - a2 or a3 (STRET) contains the selector + // - r10 contains the isa + // - other registers are set as per calling conventions + // +LLookupStart$2: + .if $0 != STRET movq %a2, %r11 // r11 = _cmd .else @@ -303,7 +318,6 @@ LExit$0: cmpq cached_sel(%r11), %a3 // if (bucket->sel != _cmd) .endif jne 1f // scan more - // CacheHit must always be preceded by a not-taken `jne` instruction CacheHit $0, $1 // call or return imp 1: @@ -319,12 +333,11 @@ LExit$0: cmpq cached_sel(%r11), %a3 // if (bucket->sel != _cmd) .endif jne 1b // scan more - // CacheHit must always be preceded by a not-taken `jne` instruction CacheHit $0, $1 // call or return imp 3: // wrap or miss - jb LCacheMiss_f // if (bucket->sel < 1) cache miss + jb LCacheMiss$2 // if (bucket->sel < 1) cache miss // wrap movq cached_imp(%r11), %r11 // bucket->imp is really first bucket jmp 2f @@ -345,13 +358,13 @@ LExit$0: cmpq cached_sel(%r11), %a3 // if (bucket->sel != _cmd) .endif jne 1b // scan more - // CacheHit must always be preceded by a not-taken `jne` instruction CacheHit $0, $1 // call or return imp 3: // double wrap or miss - jmp LCacheMiss_f + jmp LCacheMiss$2 +LLookupEnd$2: .endmacro @@ -390,8 +403,7 @@ LExit$0: push %a6 movdqa %xmm7, -0x10(%rbp) - // _class_lookupMethodAndLoadCache3(receiver, selector, class) - + // lookUpImpOrForward(obj, sel, cls, LOOKUP_INITIALIZE | LOOKUP_RESOLVER) .if $0 == NORMAL // receiver already in a1 // selector already in a2 @@ -400,7 +412,8 @@ LExit$0: movq %a3, %a2 .endif movq %r10, %a3 - call __class_lookupMethodAndLoadCache3 + movl $$3, %a4d + call _lookUpImpOrForward // IMP is now in %rax movq %rax, %r11 @@ -422,9 +435,9 @@ LExit$0: movdqa -0x10(%rbp), %xmm7 .if $0 == NORMAL - cmp %r11, %r11 // set eq for nonstret forwarding + test %r11, %r11 // set ne for nonstret forwarding .else - test %r11, %r11 // set ne for stret forwarding + cmp %r11, %r11 // set eq for stret forwarding .endif leave @@ -627,9 +640,10 @@ LNilTestSlow: // do lookup movq %a1, %r10 // move class to r10 for CacheLookup - CacheLookup NORMAL, GETIMP // returns IMP on success + // returns IMP on success + CacheLookup NORMAL, GETIMP, _cache_getImp -LCacheMiss: +LCacheMiss_cache_getImp: // cache miss, return nil xorl %eax, %eax ret @@ -664,14 +678,15 @@ _objc_debug_taggedpointer_ext_classes: NilTest NORMAL GetIsaFast NORMAL // r10 = self->isa - CacheLookup NORMAL, CALL // calls IMP on success + // calls IMP on success + CacheLookup NORMAL, CALL, _objc_msgSend NilTestReturnZero NORMAL GetIsaSupport NORMAL // cache miss: go search the method lists -LCacheMiss: +LCacheMiss_objc_msgSend: // isa still in r10 jmp __objc_msgSend_uncached @@ -683,14 +698,15 @@ LCacheMiss: NilTest NORMAL GetIsaFast NORMAL // r10 = self->isa - CacheLookup NORMAL, LOOKUP // returns IMP on success + // returns IMP on success + CacheLookup NORMAL, LOOKUP, _objc_msgLookup NilTestReturnIMP NORMAL GetIsaSupport NORMAL // cache miss: go search the method lists -LCacheMiss: +LCacheMiss_objc_msgLookup: // isa still in r10 jmp __objc_msgLookup_uncached @@ -725,10 +741,11 @@ LCacheMiss: // search the cache (objc_super in %a1) movq class(%a1), %r10 // class = objc_super->class movq receiver(%a1), %a1 // load real receiver - CacheLookup NORMAL, CALL // calls IMP on success + // calls IMP on success + CacheLookup NORMAL, CALL, _objc_msgSendSuper // cache miss: go search the method lists -LCacheMiss: +LCacheMiss_objc_msgSendSuper: // class still in r10 jmp __objc_msgSend_uncached @@ -748,10 +765,11 @@ LCacheMiss: movq class(%a1), %r10 // cls = objc_super->class movq receiver(%a1), %a1 // load real receiver movq 8(%r10), %r10 // cls = class->superclass - CacheLookup NORMAL, CALL // calls IMP on success + // calls IMP on success + CacheLookup NORMAL, CALL, _objc_msgSendSuper2 // cache miss: go search the method lists -LCacheMiss: +LCacheMiss_objc_msgSendSuper2: // superclass still in r10 jmp __objc_msgSend_uncached @@ -766,10 +784,11 @@ LCacheMiss: movq class(%a1), %r10 // cls = objc_super->class movq receiver(%a1), %a1 // load real receiver movq 8(%r10), %r10 // cls = class->superclass - CacheLookup NORMAL, LOOKUP // returns IMP on success + // returns IMP on success + CacheLookup NORMAL, LOOKUP, _objc_msgLookupSuper2 // cache miss: go search the method lists -LCacheMiss: +LCacheMiss_objc_msgLookupSuper2: // superclass still in r10 jmp __objc_msgLookup_uncached @@ -801,14 +820,15 @@ LCacheMiss: NilTest FPRET GetIsaFast FPRET // r10 = self->isa - CacheLookup FPRET, CALL // calls IMP on success + // calls IMP on success + CacheLookup FPRET, CALL, _objc_msgSend_fpret NilTestReturnZero FPRET GetIsaSupport FPRET // cache miss: go search the method lists -LCacheMiss: +LCacheMiss_objc_msgSend_fpret: // isa still in r10 jmp __objc_msgSend_uncached @@ -820,14 +840,15 @@ LCacheMiss: NilTest FPRET GetIsaFast FPRET // r10 = self->isa - CacheLookup FPRET, LOOKUP // returns IMP on success + // returns IMP on success + CacheLookup FPRET, LOOKUP, _objc_msgLookup_fpret NilTestReturnIMP FPRET GetIsaSupport FPRET // cache miss: go search the method lists -LCacheMiss: +LCacheMiss_objc_msgLookup_fpret: // isa still in r10 jmp __objc_msgLookup_uncached @@ -859,14 +880,15 @@ LCacheMiss: NilTest FP2RET GetIsaFast FP2RET // r10 = self->isa - CacheLookup FP2RET, CALL // calls IMP on success + // calls IMP on success + CacheLookup FP2RET, CALL, _objc_msgSend_fp2ret NilTestReturnZero FP2RET GetIsaSupport FP2RET // cache miss: go search the method lists -LCacheMiss: +LCacheMiss_objc_msgSend_fp2ret: // isa still in r10 jmp __objc_msgSend_uncached @@ -878,14 +900,15 @@ LCacheMiss: NilTest FP2RET GetIsaFast FP2RET // r10 = self->isa - CacheLookup FP2RET, LOOKUP // returns IMP on success + // returns IMP on success + CacheLookup FP2RET, LOOKUP, _objc_msgLookup_fp2ret NilTestReturnIMP FP2RET GetIsaSupport FP2RET // cache miss: go search the method lists -LCacheMiss: +LCacheMiss_objc_msgLookup_fp2ret: // isa still in r10 jmp __objc_msgLookup_uncached @@ -923,14 +946,15 @@ LCacheMiss: NilTest STRET GetIsaFast STRET // r10 = self->isa - CacheLookup STRET, CALL // calls IMP on success + // calls IMP on success + CacheLookup STRET, CALL, _objc_msgSend_stret NilTestReturnZero STRET GetIsaSupport STRET // cache miss: go search the method lists -LCacheMiss: +LCacheMiss_objc_msgSend_stret: // isa still in r10 jmp __objc_msgSend_stret_uncached @@ -942,14 +966,15 @@ LCacheMiss: NilTest STRET GetIsaFast STRET // r10 = self->isa - CacheLookup STRET, LOOKUP // returns IMP on success + // returns IMP on success + CacheLookup STRET, LOOKUP, _objc_msgLookup_stret NilTestReturnIMP STRET GetIsaSupport STRET // cache miss: go search the method lists -LCacheMiss: +LCacheMiss_objc_msgLookup_stret: // isa still in r10 jmp __objc_msgLookup_stret_uncached @@ -993,10 +1018,11 @@ LCacheMiss: // search the cache (objc_super in %a2) movq class(%a2), %r10 // class = objc_super->class movq receiver(%a2), %a2 // load real receiver - CacheLookup STRET, CALL // calls IMP on success + // calls IMP on success + CacheLookup STRET, CALL, _objc_msgSendSuper_stret // cache miss: go search the method lists -LCacheMiss: +LCacheMiss_objc_msgSendSuper_stret: // class still in r10 jmp __objc_msgSend_stret_uncached @@ -1014,10 +1040,11 @@ LCacheMiss: movq class(%a2), %r10 // class = objc_super->class movq receiver(%a2), %a2 // load real receiver movq 8(%r10), %r10 // class = class->superclass - CacheLookup STRET, CALL // calls IMP on success + // calls IMP on success + CacheLookup STRET, CALL, _objc_msgSendSuper2_stret // cache miss: go search the method lists -LCacheMiss: +LCacheMiss_objc_msgSendSuper2_stret: // superclass still in r10 jmp __objc_msgSend_stret_uncached @@ -1030,10 +1057,11 @@ LCacheMiss: movq class(%a2), %r10 // class = objc_super->class movq receiver(%a2), %a2 // load real receiver movq 8(%r10), %r10 // class = class->superclass - CacheLookup STRET, LOOKUP // returns IMP on success + // returns IMP on success + CacheLookup STRET, LOOKUP, _objc_msgLookupSuper2_stret // cache miss: go search the method lists -LCacheMiss: +LCacheMiss_objc_msgLookupSuper2_stret: // superclass still in r10 jmp __objc_msgLookup_stret_uncached @@ -1132,7 +1160,7 @@ LCacheMiss: // THIS IS NOT A CALLABLE C FUNCTION // Out-of-band condition register is NE for stret, EQ otherwise. - jne __objc_msgForward_stret + je __objc_msgForward_stret jmp __objc_msgForward END_ENTRY __objc_msgForward_impcache diff --git a/runtime/NSObject-internal.h b/runtime/NSObject-internal.h new file mode 100644 index 0000000..c23fbc2 --- /dev/null +++ b/runtime/NSObject-internal.h @@ -0,0 +1,161 @@ +/* + * Copyright (c) 2019 Apple Inc. All Rights Reserved. + * + * @APPLE_LICENSE_HEADER_START@ + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPLE_LICENSE_HEADER_END@ + */ + +#ifndef _NSOBJECT_INTERNAL_H +#define _NSOBJECT_INTERNAL_H + +/* + * WARNING DANGER HAZARD BEWARE EEK + * + * Everything in this file is for Apple Internal use only. + * These will change in arbitrary OS updates and in unpredictable ways. + * When your program breaks, you get to keep both pieces. + */ + +/* + * NSObject-internal.h: Private SPI for use by other system frameworks. + */ + +/*********************************************************************** + Autorelease pool implementation + + A thread's autorelease pool is a stack of pointers. + Each pointer is either an object to release, or POOL_BOUNDARY which is + an autorelease pool boundary. + A pool token is a pointer to the POOL_BOUNDARY for that pool. When + the pool is popped, every object hotter than the sentinel is released. + The stack is divided into a doubly-linked list of pages. Pages are added + and deleted as necessary. + Thread-local storage points to the hot page, where newly autoreleased + objects are stored. +**********************************************************************/ + +// structure version number. Only bump if ABI compatability is broken +#define AUTORELEASEPOOL_VERSION 1 + +// Set this to 1 to mprotect() autorelease pool contents +#define PROTECT_AUTORELEASEPOOL 0 + +// Set this to 1 to validate the entire autorelease pool header all the time +// (i.e. use check() instead of fastcheck() everywhere) +#define CHECK_AUTORELEASEPOOL (DEBUG) + +#ifdef __cplusplus +#include +#include +#include +#include + + +#ifndef C_ASSERT + #if __has_feature(cxx_static_assert) + #define C_ASSERT(expr) static_assert(expr, "(" #expr ")!") + #elif __has_feature(c_static_assert) + #define C_ASSERT(expr) _Static_assert(expr, "(" #expr ")!") + #else + #define C_ASSERT(expr) + #endif +#endif + +// Make ASSERT work when objc-private.h hasn't been included. +#ifndef ASSERT +#define ASSERT(x) assert(x) +#endif + +struct magic_t { + static const uint32_t M0 = 0xA1A1A1A1; +# define M1 "AUTORELEASE!" + static const size_t M1_len = 12; + uint32_t m[4]; + + magic_t() { + ASSERT(M1_len == strlen(M1)); + ASSERT(M1_len == 3 * sizeof(m[1])); + + m[0] = M0; + strncpy((char *)&m[1], M1, M1_len); + } + + ~magic_t() { + // Clear magic before deallocation. + // This prevents some false positives in memory debugging tools. + // fixme semantically this should be memset_s(), but the + // compiler doesn't optimize that at all (rdar://44856676). + volatile uint64_t *p = (volatile uint64_t *)m; + p[0] = 0; p[1] = 0; + } + + bool check() const { + return (m[0] == M0 && 0 == strncmp((char *)&m[1], M1, M1_len)); + } + + bool fastcheck() const { +#if CHECK_AUTORELEASEPOOL + return check(); +#else + return (m[0] == M0); +#endif + } + +# undef M1 +}; + +class AutoreleasePoolPage; +struct AutoreleasePoolPageData +{ + magic_t const magic; + __unsafe_unretained id *next; + pthread_t const thread; + AutoreleasePoolPage * const parent; + AutoreleasePoolPage *child; + uint32_t const depth; + uint32_t hiwat; + + AutoreleasePoolPageData(__unsafe_unretained id* _next, pthread_t _thread, AutoreleasePoolPage* _parent, uint32_t _depth, uint32_t _hiwat) + : magic(), next(_next), thread(_thread), + parent(_parent), child(nil), + depth(_depth), hiwat(_hiwat) + { + } +}; + + +struct thread_data_t +{ +#ifdef __LP64__ + pthread_t const thread; + uint32_t const hiwat; + uint32_t const depth; +#else + pthread_t const thread; + uint32_t const hiwat; + uint32_t const depth; + uint32_t padding; +#endif +}; +C_ASSERT(sizeof(thread_data_t) == 16); + +#undef C_ASSERT + +#endif +#endif diff --git a/runtime/NSObject.mm b/runtime/NSObject.mm index 8953faa..6a56ec6 100644 --- a/runtime/NSObject.mm +++ b/runtime/NSObject.mm @@ -25,8 +25,7 @@ #include "NSObject.h" #include "objc-weak.h" -#include "llvm-DenseMap.h" -#include "NSObject.h" +#include "DenseMapExtras.h" #include #include @@ -36,15 +35,22 @@ #include #include #include -#include #include #include #include +#include "NSObject-internal.h" @interface NSInvocation - (SEL)selector; @end +OBJC_EXTERN const uint32_t objc_debug_autoreleasepoolpage_magic_offset = __builtin_offsetof(AutoreleasePoolPageData, magic); +OBJC_EXTERN const uint32_t objc_debug_autoreleasepoolpage_next_offset = __builtin_offsetof(AutoreleasePoolPageData, next); +OBJC_EXTERN const uint32_t objc_debug_autoreleasepoolpage_thread_offset = __builtin_offsetof(AutoreleasePoolPageData, thread); +OBJC_EXTERN const uint32_t objc_debug_autoreleasepoolpage_parent_offset = __builtin_offsetof(AutoreleasePoolPageData, parent); +OBJC_EXTERN const uint32_t objc_debug_autoreleasepoolpage_child_offset = __builtin_offsetof(AutoreleasePoolPageData, child); +OBJC_EXTERN const uint32_t objc_debug_autoreleasepoolpage_depth_offset = __builtin_offsetof(AutoreleasePoolPageData, depth); +OBJC_EXTERN const uint32_t objc_debug_autoreleasepoolpage_hiwat_offset = __builtin_offsetof(AutoreleasePoolPageData, hiwat); /*********************************************************************** * Weak ivar support @@ -56,9 +62,9 @@ static id defaultBadAllocHandler(Class cls) cls->nameForLogging()); } -static id(*badAllocHandler)(Class) = &defaultBadAllocHandler; +id(*badAllocHandler)(Class) = &defaultBadAllocHandler; -static id callBadAllocHandler(Class cls) +id _objc_callBadAllocHandler(Class cls) { // fixme add re-entrancy protection in case allocation fails inside handler return (*badAllocHandler)(cls); @@ -81,9 +87,15 @@ void _objc_setBadAllocHandler(id(*newHandler)(Class)) #define SIDE_TABLE_RC_SHIFT 2 #define SIDE_TABLE_FLAG_MASK (SIDE_TABLE_RC_ONE-1) +struct RefcountMapValuePurgeable { + static inline bool isPurgeable(size_t x) { + return x == 0; + } +}; + // RefcountMap disguises its pointers because we // don't want the table to act as a root for `leaks`. -typedef objc::DenseMap,size_t,true> RefcountMap; +typedef objc::DenseMap,size_t,RefcountMapValuePurgeable> RefcountMap; // Template parameters. enum HaveOld { DontHaveOld = false, DoHaveOld = true }; @@ -157,20 +169,10 @@ void _objc_setBadAllocHandler(id(*newHandler)(Class)) lock2->unlock(); } - -// We cannot use a C++ static initializer to initialize SideTables because -// libc calls us before our C++ initializers run. We also don't want a global -// pointer to this struct because of the extra indirection. -// Do it the hard way. -alignas(StripedMap) static uint8_t - SideTableBuf[sizeof(StripedMap)]; - -static void SideTableInit() { - new (SideTableBuf) StripedMap(); -} +static objc::ExplicitInit> SideTablesMap; static StripedMap& SideTables() { - return *reinterpret_cast*>(SideTableBuf); + return SideTablesMap.get(); } // anonymous namespace @@ -268,8 +270,8 @@ BOOL objc_should_deallocate(id object) { static id storeWeak(id *location, objc_object *newObj) { - assert(haveOld || haveNew); - if (!haveNew) assert(newObj == nil); + ASSERT(haveOld || haveNew); + if (!haveNew) ASSERT(newObj == nil); Class previouslyInitializedClass = nil; id oldObj; @@ -486,7 +488,7 @@ BOOL objc_should_deallocate(id object) { if (! cls->hasCustomRR()) { // Fast case. We know +initialize is complete because // default-RR can never be set before then. - assert(cls->isInitialized()); + ASSERT(cls->isInitialized()); if (! obj->rootTryRetain()) { result = nil; } @@ -496,11 +498,11 @@ BOOL objc_should_deallocate(id object) { // the lock if necessary in order to avoid deadlocks. if (cls->isInitialized() || _thisThreadIsInitializingClass(cls)) { BOOL (*tryRetain)(id, SEL) = (BOOL(*)(id, SEL)) - class_getMethodImplementation(cls, SEL_retainWeakReference); + class_getMethodImplementation(cls, @selector(retainWeakReference)); if ((IMP)tryRetain == _objc_msgForward) { result = nil; } - else if (! (*tryRetain)(obj, SEL_retainWeakReference)) { + else if (! (*tryRetain)(obj, @selector(retainWeakReference))) { result = nil; } } @@ -587,59 +589,26 @@ BOOL objc_should_deallocate(id object) { objects are stored. **********************************************************************/ -// Set this to 1 to mprotect() autorelease pool contents -#define PROTECT_AUTORELEASEPOOL 0 - -// Set this to 1 to validate the entire autorelease pool header all the time -// (i.e. use check() instead of fastcheck() everywhere) -#define CHECK_AUTORELEASEPOOL (DEBUG) - BREAKPOINT_FUNCTION(void objc_autoreleaseNoPool(id obj)); BREAKPOINT_FUNCTION(void objc_autoreleasePoolInvalid(const void *token)); -namespace { - -struct magic_t { - static const uint32_t M0 = 0xA1A1A1A1; -# define M1 "AUTORELEASE!" - static const size_t M1_len = 12; - uint32_t m[4]; - - magic_t() { - assert(M1_len == strlen(M1)); - assert(M1_len == 3 * sizeof(m[1])); - - m[0] = M0; - strncpy((char *)&m[1], M1, M1_len); - } - - ~magic_t() { - // Clear magic before deallocation. - // This prevents some false positives in memory debugging tools. - // fixme semantically this should be memset_s(), but the - // compiler doesn't optimize that at all (rdar://44856676). - volatile uint64_t *p = (volatile uint64_t *)m; - p[0] = 0; p[1] = 0; - } - - bool check() const { - return (m[0] == M0 && 0 == strncmp((char *)&m[1], M1, M1_len)); - } +class AutoreleasePoolPage : private AutoreleasePoolPageData +{ + friend struct thread_data_t; - bool fastcheck() const { -#if CHECK_AUTORELEASEPOOL - return check(); +public: + static size_t const SIZE = +#if PROTECT_AUTORELEASEPOOL + PAGE_MAX_SIZE; // must be multiple of vm page size #else - return (m[0] == M0); + PAGE_MIN_SIZE; // size and alignment, power of 2 #endif - } - -# undef M1 -}; +private: + static pthread_key_t const key = AUTORELEASE_POOL_KEY; + static uint8_t const SCRIBBLE = 0xA3; // 0xA3A3A3A3 after releasing + static size_t const COUNT = SIZE / sizeof(id); -class AutoreleasePoolPage -{ // EMPTY_POOL_PLACEHOLDER is stored in TLS when exactly one pool is // pushed and it has never contained any objects. This saves memory // when the top level (i.e. libdispatch) pushes and pops pools but @@ -647,23 +616,6 @@ bool fastcheck() const { # define EMPTY_POOL_PLACEHOLDER ((id*)1) # define POOL_BOUNDARY nil - static pthread_key_t const key = AUTORELEASE_POOL_KEY; - static uint8_t const SCRIBBLE = 0xA3; // 0xA3A3A3A3 after releasing - static size_t const SIZE = -#if PROTECT_AUTORELEASEPOOL - PAGE_MAX_SIZE; // must be multiple of vm page size -#else - PAGE_MAX_SIZE; // size and alignment, power of 2 -#endif - static size_t const COUNT = SIZE / sizeof(id); - - magic_t const magic; - id *next; - pthread_t const thread; - AutoreleasePoolPage * const parent; - AutoreleasePoolPage *child; - uint32_t const depth; - uint32_t hiwat; // SIZE-sizeof(*this) bytes of contents follow @@ -688,15 +640,16 @@ inline void unprotect() { #endif } - AutoreleasePoolPage(AutoreleasePoolPage *newParent) - : magic(), next(begin()), thread(pthread_self()), - parent(newParent), child(nil), - depth(parent ? 1+parent->depth : 0), - hiwat(parent ? parent->hiwat : 0) + AutoreleasePoolPage(AutoreleasePoolPage *newParent) : + AutoreleasePoolPageData(begin(), + objc_thread_self(), + newParent, + newParent ? 1+newParent->depth : 0, + newParent ? newParent->hiwat : 0) { if (parent) { parent->check(); - assert(!parent->child); + ASSERT(!parent->child); parent->unprotect(); parent->child = this; parent->protect(); @@ -708,19 +661,19 @@ inline void unprotect() { { check(); unprotect(); - assert(empty()); + ASSERT(empty()); // Not recursive: we don't want to blow out the stack // if a thread accumulates a stupendous amount of garbage - assert(!child); + ASSERT(!child); } - - void busted(bool die = true) + template + void + busted(Fn log) const { magic_t right; - (die ? _objc_fatal : _objc_inform) - ("autorelease pool page %p corrupted\n" + log("autorelease pool page %p corrupted\n" " magic 0x%08x 0x%08x 0x%08x 0x%08x\n" " should be 0x%08x 0x%08x 0x%08x 0x%08x\n" " pthread %p\n" @@ -728,23 +681,37 @@ void busted(bool die = true) this, magic.m[0], magic.m[1], magic.m[2], magic.m[3], right.m[0], right.m[1], right.m[2], right.m[3], - this->thread, pthread_self()); + this->thread, objc_thread_self()); } - void check(bool die = true) + __attribute__((noinline, cold, noreturn)) + void + busted_die() const { - if (!magic.check() || !pthread_equal(thread, pthread_self())) { - busted(die); + busted(_objc_fatal); + __builtin_unreachable(); + } + + inline void + check(bool die = true) const + { + if (!magic.check() || thread != objc_thread_self()) { + if (die) { + busted_die(); + } else { + busted(_objc_inform); + } } } - void fastcheck(bool die = true) + inline void + fastcheck() const { #if CHECK_AUTORELEASEPOOL - check(die); + check(); #else if (! magic.fastcheck()) { - busted(die); + busted_die(); } #endif } @@ -772,7 +739,7 @@ bool lessThanHalfFull() { id *add(id obj) { - assert(!full()); + ASSERT(!full()); unprotect(); id *ret = next; // faster than `return next-1` because of aliasing *next++ = obj; @@ -816,7 +783,7 @@ void releaseUntil(id *stop) #if DEBUG // we expect any children to be completely empty for (AutoreleasePoolPage *page = child; page; page = page->child) { - assert(page->empty()); + ASSERT(page->empty()); } #endif } @@ -852,8 +819,8 @@ static void tls_dealloc(void *p) setHotPage((AutoreleasePoolPage *)p); if (AutoreleasePoolPage *page = coldPage()) { - if (!page->empty()) pop(page->begin()); // pop all of the pools - if (DebugMissingPools || DebugPoolAllocation) { + if (!page->empty()) objc_autoreleasePoolPop(page->begin()); // pop all of the pools + if (slowpath(DebugMissingPools || DebugPoolAllocation)) { // pop() killed the pages already } else { page->kill(); // free all of the pages @@ -874,7 +841,7 @@ static void tls_dealloc(void *p) AutoreleasePoolPage *result; uintptr_t offset = p % SIZE; - assert(offset >= sizeof(AutoreleasePoolPage)); + ASSERT(offset >= sizeof(AutoreleasePoolPage)); result = (AutoreleasePoolPage *)(p - offset); result->fastcheck(); @@ -891,7 +858,7 @@ static inline bool haveEmptyPoolPlaceholder() static inline id* setEmptyPoolPlaceholder() { - assert(tls_get_direct(key) == nil); + ASSERT(tls_get_direct(key) == nil); tls_set_direct(key, (void *)EMPTY_POOL_PLACEHOLDER); return EMPTY_POOL_PLACEHOLDER; } @@ -942,8 +909,8 @@ static __attribute__((noinline)) // The hot page is full. // Step to the next non-full page, adding a new page if necessary. // Then add the object to that page. - assert(page == hotPage()); - assert(page->full() || DebugPoolAllocation); + ASSERT(page == hotPage()); + ASSERT(page->full() || DebugPoolAllocation); do { if (page->child) page = page->child; @@ -959,7 +926,7 @@ static __attribute__((noinline)) { // "No page" could mean no pool has been pushed // or an empty placeholder pool has been pushed and has no contents yet - assert(!hotPage()); + ASSERT(!hotPage()); bool pushExtraBoundary = false; if (haveEmptyPoolPlaceholder()) { @@ -976,7 +943,7 @@ static __attribute__((noinline)) "autoreleased with no pool in place - " "just leaking - break on " "objc_autoreleaseNoPool() to debug", - pthread_self(), (void*)obj, object_getClassName(obj)); + objc_thread_self(), (void*)obj, object_getClassName(obj)); objc_autoreleaseNoPool(obj); return nil; } @@ -1014,10 +981,10 @@ static __attribute__((noinline)) public: static inline id autorelease(id obj) { - assert(obj); - assert(!obj->isTaggedPointer()); + ASSERT(obj); + ASSERT(!obj->isTaggedPointer()); id *dest __unused = autoreleaseFast(obj); - assert(!dest || dest == EMPTY_POOL_PLACEHOLDER || *dest == obj); + ASSERT(!dest || dest == EMPTY_POOL_PLACEHOLDER || *dest == obj); return obj; } @@ -1025,16 +992,17 @@ static inline id autorelease(id obj) static inline void *push() { id *dest; - if (DebugPoolAllocation) { + if (slowpath(DebugPoolAllocation)) { // Each autorelease pool starts on a new pool page. dest = autoreleaseNewPage(POOL_BOUNDARY); } else { dest = autoreleaseFast(POOL_BOUNDARY); } - assert(dest == EMPTY_POOL_PLACEHOLDER || *dest == POOL_BOUNDARY); + ASSERT(dest == EMPTY_POOL_PLACEHOLDER || *dest == POOL_BOUNDARY); return dest; } + __attribute__((noinline, cold)) static void badPop(void *token) { // Error. For bincompat purposes this is not @@ -1059,26 +1027,64 @@ static void badPop(void *token) } objc_autoreleasePoolInvalid(token); } - - static inline void pop(void *token) + + template + static void + popPage(void *token, AutoreleasePoolPage *page, id *stop) + { + if (allowDebug && PrintPoolHiwat) printHiwat(); + + page->releaseUntil(stop); + + // memory: delete empty children + if (allowDebug && DebugPoolAllocation && page->empty()) { + // special case: delete everything during page-per-pool debugging + AutoreleasePoolPage *parent = page->parent; + page->kill(); + setHotPage(parent); + } else if (allowDebug && DebugMissingPools && page->empty() && !page->parent) { + // special case: delete everything for pop(top) + // when debugging missing autorelease pools + page->kill(); + setHotPage(nil); + } else if (page->child) { + // hysteresis: keep one empty child if page is more than half full + if (page->lessThanHalfFull()) { + page->child->kill(); + } + else if (page->child->child) { + page->child->child->kill(); + } + } + } + + __attribute__((noinline, cold)) + static void + popPageDebug(void *token, AutoreleasePoolPage *page, id *stop) + { + popPage(token, page, stop); + } + + static inline void + pop(void *token) { AutoreleasePoolPage *page; id *stop; - if (token == (void*)EMPTY_POOL_PLACEHOLDER) { // Popping the top-level placeholder pool. - if (hotPage()) { - // Pool was used. Pop its contents normally. - // Pool pages remain allocated for re-use as usual. - pop(coldPage()->begin()); - } else { + page = hotPage(); + if (!page) { // Pool was never used. Clear the placeholder. - setHotPage(nil); + return setHotPage(nil); } - return; + // Pool was used. Pop its contents normally. + // Pool pages remain allocated for re-use as usual. + page = coldPage(); + token = page->begin(); + } else { + page = pageForPointer(token); } - page = pageForPointer(token); stop = (id *)token; if (*stop != POOL_BOUNDARY) { if (stop == page->begin() && !page->parent) { @@ -1092,41 +1098,22 @@ static inline void pop(void *token) } } - if (PrintPoolHiwat) printHiwat(); - - page->releaseUntil(stop); - - // memory: delete empty children - if (DebugPoolAllocation && page->empty()) { - // special case: delete everything during page-per-pool debugging - AutoreleasePoolPage *parent = page->parent; - page->kill(); - setHotPage(parent); - } else if (DebugMissingPools && page->empty() && !page->parent) { - // special case: delete everything for pop(top) - // when debugging missing autorelease pools - page->kill(); - setHotPage(nil); - } - else if (page->child) { - // hysteresis: keep one empty child if page is more than half full - if (page->lessThanHalfFull()) { - page->child->kill(); - } - else if (page->child->child) { - page->child->child->kill(); - } + if (slowpath(PrintPoolHiwat || DebugPoolAllocation || DebugMissingPools)) { + return popPageDebug(token, page, stop); } + + return popPage(token, page, stop); } static void init() { int r __unused = pthread_key_init_np(AutoreleasePoolPage::key, AutoreleasePoolPage::tls_dealloc); - assert(r == 0); + ASSERT(r == 0); } - void print() + __attribute__((noinline, cold)) + void print() { _objc_inform("[%p] ................ PAGE %s %s %s", this, full() ? "(full)" : "", @@ -1143,10 +1130,11 @@ void print() } } + __attribute__((noinline, cold)) static void printAll() - { + { _objc_inform("##############"); - _objc_inform("AUTORELEASE POOLS for thread %p", pthread_self()); + _objc_inform("AUTORELEASE POOLS for thread %p", objc_thread_self()); AutoreleasePoolPage *page; ptrdiff_t objects = 0; @@ -1170,6 +1158,7 @@ static void printAll() _objc_inform("##############"); } + __attribute__((noinline, cold)) static void printHiwat() { // Check and propagate high water mark @@ -1182,11 +1171,11 @@ static void printHiwat() p->hiwat = mark; p->protect(); } - + _objc_inform("POOL HIGHWATER: new high water mark of %u " - "pending releases for thread %p:", - mark, pthread_self()); - + "pending releases for thread %p:", + mark, objc_thread_self()); + void *stack[128]; int count = backtrace(stack, sizeof(stack)/sizeof(stack[0])); char **sym = backtrace_symbols(stack, count); @@ -1200,10 +1189,6 @@ static void printHiwat() #undef POOL_BOUNDARY }; -// anonymous namespace -}; - - /*********************************************************************** * Slow paths for inline control **********************************************************************/ @@ -1217,7 +1202,7 @@ static void printHiwat() } -NEVER_INLINE bool +NEVER_INLINE uintptr_t objc_object::rootRelease_underflow(bool performDealloc) { return rootRelease(performDealloc, true); @@ -1231,7 +1216,7 @@ static void printHiwat() NEVER_INLINE void objc_object::clearDeallocating_slow() { - assert(isa.nonpointer && (isa.weakly_referenced || isa.has_sidetable_rc)); + ASSERT(isa.nonpointer && (isa.weakly_referenced || isa.has_sidetable_rc)); SideTable& table = SideTables()[this]; table.lock(); @@ -1250,7 +1235,7 @@ static void printHiwat() id objc_object::rootAutorelease2() { - assert(!isTaggedPointer()); + ASSERT(!isTaggedPointer()); return AutoreleasePoolPage::autorelease((id)this); } @@ -1260,13 +1245,12 @@ void objc_overrelease_during_dealloc_error(void) ); -NEVER_INLINE -bool +NEVER_INLINE uintptr_t objc_object::overrelease_error() { _objc_inform_now_and_on_crash("%s object %p overreleased while already deallocating; break on objc_overrelease_during_dealloc_error to debug", object_getClassName((id)this), this); objc_overrelease_during_dealloc_error(); - return false; // allow rootRelease() to tail-call this + return 0; // allow rootRelease() to tail-call this } @@ -1320,14 +1304,14 @@ void objc_overrelease_during_dealloc_error(void) bool isDeallocating, bool weaklyReferenced) { - assert(!isa.nonpointer); // should already be changed to raw pointer + ASSERT(!isa.nonpointer); // should already be changed to raw pointer SideTable& table = SideTables()[this]; size_t& refcntStorage = table.refcnts[this]; size_t oldRefcnt = refcntStorage; // not deallocating - that was in the isa - assert((oldRefcnt & SIDE_TABLE_DEALLOCATING) == 0); - assert((oldRefcnt & SIDE_TABLE_WEAKLY_REFERENCED) == 0); + ASSERT((oldRefcnt & SIDE_TABLE_DEALLOCATING) == 0); + ASSERT((oldRefcnt & SIDE_TABLE_WEAKLY_REFERENCED) == 0); uintptr_t carry; size_t refcnt = addc(oldRefcnt, extra_rc << SIDE_TABLE_RC_SHIFT, 0, &carry); @@ -1344,14 +1328,14 @@ void objc_overrelease_during_dealloc_error(void) bool objc_object::sidetable_addExtraRC_nolock(size_t delta_rc) { - assert(isa.nonpointer); + ASSERT(isa.nonpointer); SideTable& table = SideTables()[this]; size_t& refcntStorage = table.refcnts[this]; size_t oldRefcnt = refcntStorage; // isa-side bits should not be set here - assert((oldRefcnt & SIDE_TABLE_DEALLOCATING) == 0); - assert((oldRefcnt & SIDE_TABLE_WEAKLY_REFERENCED) == 0); + ASSERT((oldRefcnt & SIDE_TABLE_DEALLOCATING) == 0); + ASSERT((oldRefcnt & SIDE_TABLE_WEAKLY_REFERENCED) == 0); if (oldRefcnt & SIDE_TABLE_RC_PINNED) return true; @@ -1375,7 +1359,7 @@ void objc_overrelease_during_dealloc_error(void) size_t objc_object::sidetable_subExtraRC_nolock(size_t delta_rc) { - assert(isa.nonpointer); + ASSERT(isa.nonpointer); SideTable& table = SideTables()[this]; RefcountMap::iterator it = table.refcnts.find(this); @@ -1386,11 +1370,11 @@ void objc_overrelease_during_dealloc_error(void) size_t oldRefcnt = it->second; // isa-side bits should not be set here - assert((oldRefcnt & SIDE_TABLE_DEALLOCATING) == 0); - assert((oldRefcnt & SIDE_TABLE_WEAKLY_REFERENCED) == 0); + ASSERT((oldRefcnt & SIDE_TABLE_DEALLOCATING) == 0); + ASSERT((oldRefcnt & SIDE_TABLE_WEAKLY_REFERENCED) == 0); size_t newRefcnt = oldRefcnt - (delta_rc << SIDE_TABLE_RC_SHIFT); - assert(oldRefcnt > newRefcnt); // shouldn't underflow + ASSERT(oldRefcnt > newRefcnt); // shouldn't underflow it->second = newRefcnt; return delta_rc; } @@ -1399,7 +1383,7 @@ void objc_overrelease_during_dealloc_error(void) size_t objc_object::sidetable_getExtraRC_nolock() { - assert(isa.nonpointer); + ASSERT(isa.nonpointer); SideTable& table = SideTables()[this]; RefcountMap::iterator it = table.refcnts.find(this); if (it == table.refcnts.end()) return 0; @@ -1415,7 +1399,7 @@ void objc_overrelease_during_dealloc_error(void) objc_object::sidetable_retain() { #if SUPPORT_NONPOINTER_ISA - assert(!isa.nonpointer); + ASSERT(!isa.nonpointer); #endif SideTable& table = SideTables()[this]; @@ -1434,7 +1418,7 @@ void objc_overrelease_during_dealloc_error(void) objc_object::sidetable_tryRetain() { #if SUPPORT_NONPOINTER_ISA - assert(!isa.nonpointer); + ASSERT(!isa.nonpointer); #endif SideTable& table = SideTables()[this]; @@ -1448,13 +1432,14 @@ void objc_overrelease_during_dealloc_error(void) // } bool result = true; - RefcountMap::iterator it = table.refcnts.find(this); - if (it == table.refcnts.end()) { - table.refcnts[this] = SIDE_TABLE_RC_ONE; - } else if (it->second & SIDE_TABLE_DEALLOCATING) { + auto it = table.refcnts.try_emplace(this, SIDE_TABLE_RC_ONE); + auto &refcnt = it.first->second; + if (it.second) { + // there was no entry + } else if (refcnt & SIDE_TABLE_DEALLOCATING) { result = false; - } else if (! (it->second & SIDE_TABLE_RC_PINNED)) { - it->second += SIDE_TABLE_RC_ONE; + } else if (! (refcnt & SIDE_TABLE_RC_PINNED)) { + refcnt += SIDE_TABLE_RC_ONE; } return result; @@ -1522,7 +1507,7 @@ void objc_overrelease_during_dealloc_error(void) objc_object::sidetable_setWeaklyReferenced_nolock() { #if SUPPORT_NONPOINTER_ISA - assert(!isa.nonpointer); + ASSERT(!isa.nonpointer); #endif SideTable& table = SideTables()[this]; @@ -1538,27 +1523,27 @@ void objc_overrelease_during_dealloc_error(void) objc_object::sidetable_release(bool performDealloc) { #if SUPPORT_NONPOINTER_ISA - assert(!isa.nonpointer); + ASSERT(!isa.nonpointer); #endif SideTable& table = SideTables()[this]; bool do_dealloc = false; table.lock(); - RefcountMap::iterator it = table.refcnts.find(this); - if (it == table.refcnts.end()) { + auto it = table.refcnts.try_emplace(this, SIDE_TABLE_DEALLOCATING); + auto &refcnt = it.first->second; + if (it.second) { do_dealloc = true; - table.refcnts[this] = SIDE_TABLE_DEALLOCATING; - } else if (it->second < SIDE_TABLE_DEALLOCATING) { + } else if (refcnt < SIDE_TABLE_DEALLOCATING) { // SIDE_TABLE_WEAKLY_REFERENCED may be set. Don't change it. do_dealloc = true; - it->second |= SIDE_TABLE_DEALLOCATING; - } else if (! (it->second & SIDE_TABLE_RC_PINNED)) { - it->second -= SIDE_TABLE_RC_ONE; + refcnt |= SIDE_TABLE_DEALLOCATING; + } else if (! (refcnt & SIDE_TABLE_RC_PINNED)) { + refcnt -= SIDE_TABLE_RC_ONE; } table.unlock(); if (do_dealloc && performDealloc) { - ((void(*)(objc_object *, SEL))objc_msgSend)(this, SEL_dealloc); + ((void(*)(objc_object *, SEL))objc_msgSend)(this, @selector(dealloc)); } return do_dealloc; } @@ -1591,7 +1576,7 @@ void objc_overrelease_during_dealloc_error(void) #if __OBJC2__ -__attribute__((aligned(16))) +__attribute__((aligned(16), flatten, noinline)) id objc_retain(id obj) { @@ -1601,7 +1586,7 @@ void objc_overrelease_during_dealloc_error(void) } -__attribute__((aligned(16))) +__attribute__((aligned(16), flatten, noinline)) void objc_release(id obj) { @@ -1611,7 +1596,7 @@ void objc_overrelease_during_dealloc_error(void) } -__attribute__((aligned(16))) +__attribute__((aligned(16), flatten, noinline)) id objc_autorelease(id obj) { @@ -1641,7 +1626,7 @@ void objc_overrelease_during_dealloc_error(void) bool _objc_rootTryRetain(id obj) { - assert(obj); + ASSERT(obj); return obj->rootTryRetain(); } @@ -1649,7 +1634,7 @@ void objc_overrelease_during_dealloc_error(void) bool _objc_rootIsDeallocating(id obj) { - assert(obj); + ASSERT(obj); return obj->rootIsDeallocating(); } @@ -1658,7 +1643,7 @@ void objc_overrelease_during_dealloc_error(void) void objc_clear_deallocating(id obj) { - assert(obj); + ASSERT(obj); if (obj->isTaggedPointer()) return; obj->clearDeallocating(); @@ -1668,100 +1653,62 @@ void objc_overrelease_during_dealloc_error(void) bool _objc_rootReleaseWasZero(id obj) { - assert(obj); + ASSERT(obj); return obj->rootReleaseShouldDealloc(); } -id +NEVER_INLINE id _objc_rootAutorelease(id obj) { - assert(obj); + ASSERT(obj); return obj->rootAutorelease(); } uintptr_t _objc_rootRetainCount(id obj) { - assert(obj); + ASSERT(obj); return obj->rootRetainCount(); } -id +NEVER_INLINE id _objc_rootRetain(id obj) { - assert(obj); + ASSERT(obj); return obj->rootRetain(); } -void +NEVER_INLINE void _objc_rootRelease(id obj) { - assert(obj); + ASSERT(obj); obj->rootRelease(); } -id -_objc_rootAllocWithZone(Class cls, malloc_zone_t *zone) -{ - id obj; - -#if __OBJC2__ - // allocWithZone under __OBJC2__ ignores the zone parameter - (void)zone; - obj = class_createInstance(cls, 0); -#else - if (!zone) { - obj = class_createInstance(cls, 0); - } - else { - obj = class_createInstanceFromZone(cls, 0, zone); - } -#endif - - if (slowpath(!obj)) obj = callBadAllocHandler(cls); - return obj; -} - - // Call [cls alloc] or [cls allocWithZone:nil], with appropriate // shortcutting optimizations. static ALWAYS_INLINE id callAlloc(Class cls, bool checkNil, bool allocWithZone=false) { - if (slowpath(checkNil && !cls)) return nil; - #if __OBJC2__ + if (slowpath(checkNil && !cls)) return nil; if (fastpath(!cls->ISA()->hasCustomAWZ())) { - // No alloc/allocWithZone implementation. Go straight to the allocator. - // fixme store hasCustomAWZ in the non-meta class and - // add it to canAllocFast's summary - if (fastpath(cls->canAllocFast())) { - // No ctors, raw isa, etc. Go straight to the metal. - bool dtor = cls->hasCxxDtor(); - id obj = (id)calloc(1, cls->bits.fastInstanceSize()); - if (slowpath(!obj)) return callBadAllocHandler(cls); - obj->initInstanceIsa(cls, dtor); - return obj; - } - else { - // Has ctor or raw isa or something. Use the slower path. - id obj = class_createInstance(cls, 0); - if (slowpath(!obj)) return callBadAllocHandler(cls); - return obj; - } + return _objc_rootAllocWithZone(cls, nil); } #endif // No shortcuts available. - if (allocWithZone) return [cls allocWithZone:nil]; - return [cls alloc]; + if (allocWithZone) { + return ((id(*)(id, SEL, struct _NSZone *))objc_msgSend)(cls, @selector(allocWithZone:), nil); + } + return ((id(*)(id, SEL))objc_msgSend)(cls, @selector(alloc)); } @@ -1794,11 +1741,79 @@ void objc_overrelease_during_dealloc_error(void) return [callAlloc(cls, true/*checkNil*/, false/*allocWithZone*/) init]; } +// Calls [cls new] +id +objc_opt_new(Class cls) +{ +#if __OBJC2__ + if (fastpath(cls && !cls->ISA()->hasCustomCore())) { + return [callAlloc(cls, false/*checkNil*/, true/*allocWithZone*/) init]; + } +#endif + return ((id(*)(id, SEL))objc_msgSend)(cls, @selector(new)); +} + +// Calls [obj self] +id +objc_opt_self(id obj) +{ +#if __OBJC2__ + if (fastpath(!obj || obj->isTaggedPointer() || !obj->ISA()->hasCustomCore())) { + return obj; + } +#endif + return ((id(*)(id, SEL))objc_msgSend)(obj, @selector(self)); +} + +// Calls [obj class] +Class +objc_opt_class(id obj) +{ +#if __OBJC2__ + if (slowpath(!obj)) return nil; + Class cls = obj->getIsa(); + if (fastpath(!cls->hasCustomCore())) { + return cls->isMetaClass() ? obj : cls; + } +#endif + return ((Class(*)(id, SEL))objc_msgSend)(obj, @selector(class)); +} + +// Calls [obj isKindOfClass] +BOOL +objc_opt_isKindOfClass(id obj, Class otherClass) +{ +#if __OBJC2__ + if (slowpath(!obj)) return NO; + Class cls = obj->getIsa(); + if (fastpath(!cls->hasCustomCore())) { + for (Class tcls = cls; tcls; tcls = tcls->superclass) { + if (tcls == otherClass) return YES; + } + return NO; + } +#endif + return ((BOOL(*)(id, SEL, Class))objc_msgSend)(obj, @selector(isKindOfClass:), otherClass); +} + +// Calls [obj respondsToSelector] +BOOL +objc_opt_respondsToSelector(id obj, SEL sel) +{ +#if __OBJC2__ + if (slowpath(!obj)) return NO; + Class cls = obj->getIsa(); + if (fastpath(!cls->hasCustomCore())) { + return class_respondsToSelector_inst(obj, sel, cls); + } +#endif + return ((BOOL(*)(id, SEL, SEL))objc_msgSend)(obj, @selector(respondsToSelector:), sel); +} void _objc_rootDealloc(id obj) { - assert(obj); + ASSERT(obj); obj->rootDealloc(); } @@ -1806,7 +1821,7 @@ void objc_overrelease_during_dealloc_error(void) void _objc_rootFinalize(id obj __unused) { - assert(obj); + ASSERT(obj); _objc_fatal("_objc_rootFinalize called with garbage collection off"); } @@ -1845,6 +1860,7 @@ void objc_overrelease_during_dealloc_error(void) return AutoreleasePoolPage::push(); } +NEVER_INLINE void objc_autoreleasePoolPop(void *ctxt) { @@ -1955,7 +1971,8 @@ void objc_overrelease_during_dealloc_error(void) void arr_init(void) { AutoreleasePoolPage::init(); - SideTableInit(); + SideTablesMap.init(); + _objc_associations_init(); } @@ -1967,8 +1984,8 @@ void arr_init(void) @interface __NSUnrecognizedTaggedPointer : NSObject @end +__attribute__((objc_nonlazy_class)) @implementation __NSUnrecognizedTaggedPointer -+(void) load { } -(id) retain { return self; } -(oneway void) release { } -(id) autorelease { return self; } @@ -1976,12 +1993,9 @@ -(id) autorelease { return self; } #endif - +__attribute__((objc_nonlazy_class)) @implementation NSObject -+ (void)load { -} - + (void)initialize { } @@ -2010,7 +2024,7 @@ - (Class)superclass { } + (BOOL)isMemberOfClass:(Class)cls { - return object_getClass((id)self) == cls; + return self->ISA() == cls; } - (BOOL)isMemberOfClass:(Class)cls { @@ -2018,7 +2032,7 @@ - (BOOL)isMemberOfClass:(Class)cls { } + (BOOL)isKindOfClass:(Class)cls { - for (Class tcls = object_getClass((id)self); tcls; tcls = tcls->superclass) { + for (Class tcls = self->ISA(); tcls; tcls = tcls->superclass) { if (tcls == cls) return YES; } return NO; @@ -2046,18 +2060,15 @@ + (BOOL)isAncestorOfObject:(NSObject *)obj { } + (BOOL)instancesRespondToSelector:(SEL)sel { - if (!sel) return NO; - return class_respondsToSelector(self, sel); + return class_respondsToSelector_inst(nil, sel, self); } + (BOOL)respondsToSelector:(SEL)sel { - if (!sel) return NO; - return class_respondsToSelector_inst(object_getClass(self), sel, self); + return class_respondsToSelector_inst(self, sel, self->ISA()); } - (BOOL)respondsToSelector:(SEL)sel { - if (!sel) return NO; - return class_respondsToSelector_inst([self class], sel, self); + return class_respondsToSelector_inst(self, sel, [self class]); } + (BOOL)conformsToProtocol:(Protocol *)protocol { @@ -2241,7 +2252,7 @@ + (id)retain { // Replaced by ObjectAlloc - (id)retain { - return ((id)self)->rootRetain(); + return _objc_rootRetain(self); } @@ -2251,7 +2262,7 @@ + (BOOL)_tryRetain { // Replaced by ObjectAlloc - (BOOL)_tryRetain { - return ((id)self)->rootTryRetain(); + return _objc_rootTryRetain(self); } + (BOOL)_isDeallocating { @@ -2259,14 +2270,14 @@ + (BOOL)_isDeallocating { } - (BOOL)_isDeallocating { - return ((id)self)->rootIsDeallocating(); + return _objc_rootIsDeallocating(self); } + (BOOL)allowsWeakReference { return YES; } -+ (BOOL)retainWeakReference { ++ (BOOL)retainWeakReference { return YES; } @@ -2283,7 +2294,7 @@ + (oneway void)release { // Replaced by ObjectAlloc - (oneway void)release { - ((id)self)->rootRelease(); + _objc_rootRelease(self); } + (id)autorelease { @@ -2292,7 +2303,7 @@ + (id)autorelease { // Replaced by ObjectAlloc - (id)autorelease { - return ((id)self)->rootAutorelease(); + return _objc_rootAutorelease(self); } + (NSUInteger)retainCount { @@ -2300,7 +2311,7 @@ + (NSUInteger)retainCount { } - (NSUInteger)retainCount { - return ((id)self)->rootRetainCount(); + return _objc_rootRetainCount(self); } + (id)alloc { diff --git a/runtime/Protocol.h b/runtime/Protocol.h index f5169be..953b366 100644 --- a/runtime/Protocol.h +++ b/runtime/Protocol.h @@ -75,13 +75,19 @@ OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0) __IOS_DEPRECATED(2.0, 2.0, "use protocol_getMethodDescription instead") __TVOS_DEPRECATED(9.0, 9.0, "use protocol_getMethodDescription instead") __WATCHOS_DEPRECATED(1.0, 1.0, "use protocol_getMethodDescription instead") - __BRIDGEOS_DEPRECATED(2.0, 2.0, "use protocol_getMethodDescription instead"); +#ifndef __APPLE_BLEACH_SDK__ + __BRIDGEOS_DEPRECATED(2.0, 2.0, "use protocol_getMethodDescription instead") +#endif +; - (struct objc_method_description *) descriptionForClassMethod:(SEL)aSel __OSX_DEPRECATED(10.0, 10.5, "use protocol_getMethodDescription instead") __IOS_DEPRECATED(2.0, 2.0, "use protocol_getMethodDescription instead") __TVOS_DEPRECATED(9.0, 9.0, "use protocol_getMethodDescription instead") __WATCHOS_DEPRECATED(1.0, 1.0, "use protocol_getMethodDescription instead") - __BRIDGEOS_DEPRECATED(2.0, 2.0, "use protocol_getMethodDescription instead"); +#ifndef __APPLE_BLEACH_SDK__ + __BRIDGEOS_DEPRECATED(2.0, 2.0, "use protocol_getMethodDescription instead") +#endif +; @end diff --git a/runtime/Protocol.mm b/runtime/Protocol.mm index 2a2b3da..9e97419 100644 --- a/runtime/Protocol.mm +++ b/runtime/Protocol.mm @@ -45,22 +45,20 @@ // by CF, so __IncompleteProtocol would be left without an R/R implementation // otherwise, which would break ARC. -@interface __IncompleteProtocol : NSObject @end -@implementation __IncompleteProtocol +@interface __IncompleteProtocol : NSObject +@end + #if __OBJC2__ -// fixme hack - make __IncompleteProtocol a non-lazy class -+ (void) load { } +__attribute__((objc_nonlazy_class)) #endif +@implementation __IncompleteProtocol @end -@implementation Protocol - #if __OBJC2__ -// fixme hack - make Protocol a non-lazy class -+ (void) load { } +__attribute__((objc_nonlazy_class)) #endif - +@implementation Protocol - (BOOL) conformsTo: (Protocol *)aProtocolObj { diff --git a/runtime/arm64-asm.h b/runtime/arm64-asm.h index 281bb7f..fb15e5e 100644 --- a/runtime/arm64-asm.h +++ b/runtime/arm64-asm.h @@ -108,8 +108,9 @@ .endmacro .macro TailCallCachedImp - // $0 = cached imp, $1 = address of cached imp, $2 = SEL + // $0 = cached imp, $1 = address of cached imp, $2 = SEL, $3 = isa eor $1, $1, $2 // mix SEL into ptrauth modifier + eor $1, $1, $3 // mix isa into ptrauth modifier brab $0, $1 .endmacro @@ -124,9 +125,10 @@ .endmacro .macro AuthAndResignAsIMP - // $0 = cached imp, $1 = address of cached imp, $2 = SEL + // $0 = cached imp, $1 = address of cached imp, $2 = SEL, $3 = isa // note: assumes the imp is not nil eor $1, $1, $2 // mix SEL into ptrauth modifier + eor $1, $1, $3 // mix isa into ptrauth modifier autib $0, $1 // authenticate cached imp ldr xzr, [$0] // crash if authentication failed paciza $0 // resign cached imp as IMP @@ -142,7 +144,8 @@ .endmacro .macro TailCallCachedImp - // $0 = cached imp, $1 = address of cached imp, $2 = SEL + // $0 = cached imp, $1 = address of cached imp, $2 = SEL, $3 = isa + eor $0, $0, $3 br $0 .endmacro @@ -158,7 +161,7 @@ .macro AuthAndResignAsIMP // $0 = cached imp, $1 = address of cached imp, $2 = SEL - // empty + eor $0, $0, $3 .endmacro // not JOP diff --git a/runtime/hashtable2.h b/runtime/hashtable2.h index a7dd3c8..3cd0960 100644 --- a/runtime/hashtable2.h +++ b/runtime/hashtable2.h @@ -30,10 +30,8 @@ #define _OBJC_LITTLE_HASHTABLE_H_ #ifndef _OBJC_PRIVATE_H_ -# define OBJC_HASH_AVAILABILITY \ - __OSX_DEPRECATED(10.0, 10.1, "NXHashTable is deprecated") \ - __IOS_UNAVAILABLE __TVOS_UNAVAILABLE \ - __WATCHOS_UNAVAILABLE __BRIDGEOS_UNAVAILABLE +# define OBJC_HASH_AVAILABILITY \ + OBJC_OSX_DEPRECATED_OTHERS_UNAVAILABLE(10.0, 10.1, "NXHashTable is deprecated") #else # define OBJC_HASH_AVAILABILITY #endif diff --git a/runtime/llvm-DenseMap.h b/runtime/llvm-DenseMap.h index 6fe1382..893e84e 100644 --- a/runtime/llvm-DenseMap.h +++ b/runtime/llvm-DenseMap.h @@ -11,7 +11,7 @@ // //===----------------------------------------------------------------------===// -// Taken from llvmCore-3425.0.31. +// Taken from clang-1100.247.11.10.9 #ifndef LLVM_ADT_DENSEMAP_H #define LLVM_ADT_DENSEMAP_H @@ -21,119 +21,179 @@ #include "llvm-AlignOf.h" #include "llvm-DenseMapInfo.h" #include -#include -#include -#include #include -#include #include #include +#include +#include +#include +#include #include #include "objc-private.h" -// From llvm/Support/Compiler.h -#define LLVM_USE_RVALUE_REFERENCES 1 -#define llvm_move(value) (::std::move(value)) - #define MIN_BUCKETS 4 #define MIN_COMPACT 1024 +#define LLVM_UNLIKELY slowpath +#define LLVM_LIKELY fastpath namespace objc { -template, - bool IsConst = false> +namespace detail { + +// We extend a pair to allow users to override the bucket type with their own +// implementation without requiring two members. +template +struct DenseMapPair : public std::pair { + + // FIXME: Switch to inheriting constructors when we drop support for older + // clang versions. + // NOTE: This default constructor is declared with '{}' rather than + // '= default' to work around a separate bug in clang-3.8. This can + // also go when we switch to inheriting constructors. + DenseMapPair() {} + + DenseMapPair(const KeyT &Key, const ValueT &Value) + : std::pair(Key, Value) {} + + DenseMapPair(KeyT &&Key, ValueT &&Value) + : std::pair(std::move(Key), std::move(Value)) {} + + template + DenseMapPair(AltKeyT &&AltKey, AltValueT &&AltValue, + typename std::enable_if< + std::is_convertible::value && + std::is_convertible::value>::type * = 0) + : std::pair(std::forward(AltKey), + std::forward(AltValue)) {} + + template + DenseMapPair(AltPairT &&AltPair, + typename std::enable_if>::value>::type * = 0) + : std::pair(std::forward(AltPair)) {} + + KeyT &getFirst() { return std::pair::first; } + const KeyT &getFirst() const { return std::pair::first; } + ValueT &getSecond() { return std::pair::second; } + const ValueT &getSecond() const { return std::pair::second; } +}; + +} // end namespace detail + +template < + typename KeyT, typename ValueT, + typename ValueInfoT = DenseMapValueInfo, + typename KeyInfoT = DenseMapInfo, + typename Bucket = detail::DenseMapPair, + bool IsConst = false> class DenseMapIterator; -// ZeroValuesArePurgeable=true is used by the refcount table. +// ValueInfoT is used by the refcount table. // A key/value pair with value==0 is not required to be stored // in the refcount table; it could correctly be erased instead. // For performance, we do keep zero values in the table when the // true refcount decreases to 1: this makes any future retain faster. // For memory size, we allow rehashes and table insertions to // remove a zero value as if it were a tombstone. - -template +template class DenseMapBase { -protected: - typedef std::pair BucketT; + template + using const_arg_type_t = typename const_pointer_or_const_ref::type; public: - typedef KeyT key_type; - typedef ValueT mapped_type; - typedef BucketT value_type; + using size_type = unsigned; + using key_type = KeyT; + using mapped_type = ValueT; + using value_type = BucketT; + + using iterator = DenseMapIterator; + using const_iterator = + DenseMapIterator; - typedef DenseMapIterator iterator; - typedef DenseMapIterator const_iterator; inline iterator begin() { - // When the map is empty, avoid the overhead of AdvancePastEmptyBuckets(). - return empty() ? end() : iterator(getBuckets(), getBucketsEnd()); + // When the map is empty, avoid the overhead of advancing/retreating past + // empty buckets. + if (empty()) + return end(); + return makeIterator(getBuckets(), getBucketsEnd()); } inline iterator end() { - return iterator(getBucketsEnd(), getBucketsEnd(), true); + return makeIterator(getBucketsEnd(), getBucketsEnd(), true); } inline const_iterator begin() const { - return empty() ? end() : const_iterator(getBuckets(), getBucketsEnd()); + if (empty()) + return end(); + return makeConstIterator(getBuckets(), getBucketsEnd()); } inline const_iterator end() const { - return const_iterator(getBucketsEnd(), getBucketsEnd(), true); + return makeConstIterator(getBucketsEnd(), getBucketsEnd(), true); } - bool empty() const { return getNumEntries() == 0; } + bool empty() const { + return getNumEntries() == 0; + } unsigned size() const { return getNumEntries(); } - /// Grow the densemap so that it has at least Size buckets. Does not shrink - void resize(size_t Size) { - if (Size > getNumBuckets()) - grow(Size); + /// Grow the densemap so that it can contain at least \p NumEntries items + /// before resizing again. + void reserve(size_type NumEntries) { + auto NumBuckets = getMinBucketToReserveForEntries(NumEntries); + if (NumBuckets > getNumBuckets()) + grow(NumBuckets); } void clear() { if (getNumEntries() == 0 && getNumTombstones() == 0) return; - + // If the capacity of the array is huge, and the # elements used is small, // shrink the array. - if (getNumEntries() * 4 < getNumBuckets() && - getNumBuckets() > MIN_BUCKETS) { + if (getNumEntries() * 4 < getNumBuckets() && getNumBuckets() > MIN_BUCKETS) { shrink_and_clear(); return; } const KeyT EmptyKey = getEmptyKey(), TombstoneKey = getTombstoneKey(); + if (is_trivially_copyable::value && + is_trivially_copyable::value) { + // Use a simpler loop when these are trivial types. + for (BucketT *P = getBuckets(), *E = getBucketsEnd(); P != E; ++P) + P->getFirst() = EmptyKey; + } else { + unsigned NumEntries = getNumEntries(); for (BucketT *P = getBuckets(), *E = getBucketsEnd(); P != E; ++P) { - if (!KeyInfoT::isEqual(P->first, EmptyKey)) { - if (!KeyInfoT::isEqual(P->first, TombstoneKey)) { - P->second.~ValueT(); - decrementNumEntries(); + if (!KeyInfoT::isEqual(P->getFirst(), EmptyKey)) { + if (!KeyInfoT::isEqual(P->getFirst(), TombstoneKey)) { + P->getSecond().~ValueT(); + --NumEntries; + } + P->getFirst() = EmptyKey; } - P->first = EmptyKey; } + ASSERT(NumEntries == 0 && "Node count imbalance!"); } - assert(getNumEntries() == 0 && "Node count imbalance!"); + setNumEntries(0); setNumTombstones(0); } - /// count - Return true if the specified key is in the map. - bool count(const KeyT &Val) const { + /// Return 1 if the specified key is in the map, 0 otherwise. + size_type count(const_arg_type_t Val) const { const BucketT *TheBucket; - return LookupBucketFor(Val, TheBucket); + return LookupBucketFor(Val, TheBucket) ? 1 : 0; } - iterator find(const KeyT &Val) { + iterator find(const_arg_type_t Val) { BucketT *TheBucket; if (LookupBucketFor(Val, TheBucket)) - return iterator(TheBucket, getBucketsEnd(), true); + return makeIterator(TheBucket, getBucketsEnd(), true); return end(); } - const_iterator find(const KeyT &Val) const { + const_iterator find(const_arg_type_t Val) const { const BucketT *TheBucket; if (LookupBucketFor(Val, TheBucket)) - return const_iterator(TheBucket, getBucketsEnd(), true); + return makeConstIterator(TheBucket, getBucketsEnd(), true); return end(); } @@ -146,23 +206,23 @@ class DenseMapBase { iterator find_as(const LookupKeyT &Val) { BucketT *TheBucket; if (LookupBucketFor(Val, TheBucket)) - return iterator(TheBucket, getBucketsEnd(), true); + return makeIterator(TheBucket, getBucketsEnd(), true); return end(); } template const_iterator find_as(const LookupKeyT &Val) const { const BucketT *TheBucket; if (LookupBucketFor(Val, TheBucket)) - return const_iterator(TheBucket, getBucketsEnd(), true); + return makeConstIterator(TheBucket, getBucketsEnd(), true); return end(); } /// lookup - Return the entry for the specified key, or a default /// constructed value if no such entry exists. - ValueT lookup(const KeyT &Val) const { + ValueT lookup(const_arg_type_t Val) const { const BucketT *TheBucket; if (LookupBucketFor(Val, TheBucket)) - return TheBucket->second; + return TheBucket->getSecond(); return ValueT(); } @@ -170,14 +230,73 @@ class DenseMapBase { // If the key is already in the map, it returns false and doesn't update the // value. std::pair insert(const std::pair &KV) { + return try_emplace(KV.first, KV.second); + } + + // Inserts key,value pair into the map if the key isn't already in the map. + // If the key is already in the map, it returns false and doesn't update the + // value. + std::pair insert(std::pair &&KV) { + return try_emplace(std::move(KV.first), std::move(KV.second)); + } + + // Inserts key,value pair into the map if the key isn't already in the map. + // The value is constructed in-place if the key is not in the map, otherwise + // it is not moved. + template + std::pair try_emplace(KeyT &&Key, Ts &&... Args) { + BucketT *TheBucket; + if (LookupBucketFor(Key, TheBucket)) + return std::make_pair( + makeIterator(TheBucket, getBucketsEnd(), true), + false); // Already in map. + + // Otherwise, insert the new element. + TheBucket = + InsertIntoBucket(TheBucket, std::move(Key), std::forward(Args)...); + return std::make_pair( + makeIterator(TheBucket, getBucketsEnd(), true), + true); + } + + // Inserts key,value pair into the map if the key isn't already in the map. + // The value is constructed in-place if the key is not in the map, otherwise + // it is not moved. + template + std::pair try_emplace(const KeyT &Key, Ts &&... Args) { + BucketT *TheBucket; + if (LookupBucketFor(Key, TheBucket)) + return std::make_pair( + makeIterator(TheBucket, getBucketsEnd(), true), + false); // Already in map. + + // Otherwise, insert the new element. + TheBucket = InsertIntoBucket(TheBucket, Key, std::forward(Args)...); + return std::make_pair( + makeIterator(TheBucket, getBucketsEnd(), true), + true); + } + + /// Alternate version of insert() which allows a different, and possibly + /// less expensive, key type. + /// The DenseMapInfo is responsible for supplying methods + /// getHashValue(LookupKeyT) and isEqual(LookupKeyT, KeyT) for each key + /// type used. + template + std::pair insert_as(std::pair &&KV, + const LookupKeyT &Val) { BucketT *TheBucket; - if (LookupBucketFor(KV.first, TheBucket)) - return std::make_pair(iterator(TheBucket, getBucketsEnd(), true), - false); // Already in map. + if (LookupBucketFor(Val, TheBucket)) + return std::make_pair( + makeIterator(TheBucket, getBucketsEnd(), *this, true), + false); // Already in map. // Otherwise, insert the new element. - TheBucket = InsertIntoBucket(KV.first, KV.second, TheBucket); - return std::make_pair(iterator(TheBucket, getBucketsEnd(), true), true); + TheBucket = InsertIntoBucketWithLookup(TheBucket, std::move(KV.first), + std::move(KV.second), Val); + return std::make_pair( + makeIterator(TheBucket, getBucketsEnd(), *this, true), + true); } /// insert - Range insertion of pairs. @@ -192,9 +311,9 @@ class DenseMapBase { void compact() { if (getNumEntries() == 0) { shrink_and_clear(); - } - else if (getNumBuckets() / 16 > getNumEntries() && - getNumBuckets() > MIN_COMPACT) + } + else if (getNumBuckets() / 16 > getNumEntries() && + getNumBuckets() > MIN_COMPACT) { grow(getNumEntries() * 2); } @@ -205,8 +324,8 @@ class DenseMapBase { if (!LookupBucketFor(Val, TheBucket)) return false; // not in map. - TheBucket->second.~ValueT(); - TheBucket->first = getTombstoneKey(); + TheBucket->getSecond().~ValueT(); + TheBucket->getFirst() = getTombstoneKey(); decrementNumEntries(); incrementNumTombstones(); compact(); @@ -214,8 +333,8 @@ class DenseMapBase { } void erase(iterator I) { BucketT *TheBucket = &*I; - TheBucket->second.~ValueT(); - TheBucket->first = getTombstoneKey(); + TheBucket->getSecond().~ValueT(); + TheBucket->getFirst() = getTombstoneKey(); decrementNumEntries(); incrementNumTombstones(); compact(); @@ -226,26 +345,24 @@ class DenseMapBase { if (LookupBucketFor(Key, TheBucket)) return *TheBucket; - return *InsertIntoBucket(Key, ValueT(), TheBucket); + return *InsertIntoBucket(TheBucket, Key); } ValueT &operator[](const KeyT &Key) { return FindAndConstruct(Key).second; } -#if LLVM_USE_RVALUE_REFERENCES value_type& FindAndConstruct(KeyT &&Key) { BucketT *TheBucket; if (LookupBucketFor(Key, TheBucket)) return *TheBucket; - return *InsertIntoBucket(Key, ValueT(), TheBucket); + return *InsertIntoBucket(TheBucket, std::move(Key)); } ValueT &operator[](KeyT &&Key) { - return FindAndConstruct(Key).second; + return FindAndConstruct(std::move(Key)).second; } -#endif /// isPointerIntoBucketsArray - Return true if the specified pointer points /// somewhere into the DenseMap's array of buckets (i.e. either to a key or @@ -260,7 +377,7 @@ class DenseMapBase { const void *getPointerIntoBucketsArray() const { return getBuckets(); } protected: - DenseMapBase() {} + DenseMapBase() = default; void destroyAll() { if (getNumBuckets() == 0) // Nothing to do. @@ -268,26 +385,33 @@ class DenseMapBase { const KeyT EmptyKey = getEmptyKey(), TombstoneKey = getTombstoneKey(); for (BucketT *P = getBuckets(), *E = getBucketsEnd(); P != E; ++P) { - if (!KeyInfoT::isEqual(P->first, EmptyKey) && - !KeyInfoT::isEqual(P->first, TombstoneKey)) - P->second.~ValueT(); - P->first.~KeyT(); - } - -#ifndef NDEBUG - memset((void*)getBuckets(), 0x5a, sizeof(BucketT)*getNumBuckets()); -#endif + if (!KeyInfoT::isEqual(P->getFirst(), EmptyKey) && + !KeyInfoT::isEqual(P->getFirst(), TombstoneKey)) + P->getSecond().~ValueT(); + P->getFirst().~KeyT(); } + } void initEmpty() { setNumEntries(0); setNumTombstones(0); - assert((getNumBuckets() & (getNumBuckets()-1)) == 0 && + ASSERT((getNumBuckets() & (getNumBuckets()-1)) == 0 && "# initial buckets must be a power of two!"); const KeyT EmptyKey = getEmptyKey(); for (BucketT *B = getBuckets(), *E = getBucketsEnd(); B != E; ++B) - new (&B->first) KeyT(EmptyKey); + ::new (&B->getFirst()) KeyT(EmptyKey); + } + + /// Returns the number of buckets to allocate to ensure that the DenseMap can + /// accommodate \p NumEntries without need to grow(). + unsigned getMinBucketToReserveForEntries(unsigned NumEntries) { + // Ensure that "NumEntries * 4 < NumBuckets * 3" + if (NumEntries == 0) + return 0; + // +1 is required because of the strict equality. + // For example if NumEntries is 48, we need to return 401. + return NextPowerOf2(NumEntries * 4 / 3 + 1); } void moveFromOldBuckets(BucketT *OldBucketsBegin, BucketT *OldBucketsEnd) { @@ -297,106 +421,129 @@ class DenseMapBase { const KeyT EmptyKey = getEmptyKey(); const KeyT TombstoneKey = getTombstoneKey(); for (BucketT *B = OldBucketsBegin, *E = OldBucketsEnd; B != E; ++B) { - if (!KeyInfoT::isEqual(B->first, EmptyKey) && - !KeyInfoT::isEqual(B->first, TombstoneKey) && - !(ZeroValuesArePurgeable && B->second == 0)) { + if (ValueInfoT::isPurgeable(B->getSecond())) { + // Free the value. + B->getSecond().~ValueT(); + } else if (!KeyInfoT::isEqual(B->getFirst(), EmptyKey) && + !KeyInfoT::isEqual(B->getFirst(), TombstoneKey)) { // Insert the key/value into the new table. BucketT *DestBucket; - bool FoundVal = LookupBucketFor(B->first, DestBucket); + bool FoundVal = LookupBucketFor(B->getFirst(), DestBucket); (void)FoundVal; // silence warning. - assert(!FoundVal && "Key already in new map?"); - DestBucket->first = llvm_move(B->first); - new (&DestBucket->second) ValueT(llvm_move(B->second)); + ASSERT(!FoundVal && "Key already in new map?"); + DestBucket->getFirst() = std::move(B->getFirst()); + ::new (&DestBucket->getSecond()) ValueT(std::move(B->getSecond())); incrementNumEntries(); - + // Free the value. - B->second.~ValueT(); + B->getSecond().~ValueT(); } - B->first.~KeyT(); + B->getFirst().~KeyT(); } - -#ifndef NDEBUG - if (OldBucketsBegin != OldBucketsEnd) - memset((void*)OldBucketsBegin, 0x5a, - sizeof(BucketT) * (OldBucketsEnd - OldBucketsBegin)); -#endif } template - void copyFrom(const DenseMapBase& other) { - assert(getNumBuckets() == other.getNumBuckets()); + void copyFrom( + const DenseMapBase &other) { + ASSERT(&other != this); + ASSERT(getNumBuckets() == other.getNumBuckets()); setNumEntries(other.getNumEntries()); setNumTombstones(other.getNumTombstones()); - if (isPodLike::value && isPodLike::value) - memcpy(getBuckets(), other.getBuckets(), + if (is_trivially_copyable::value && + is_trivially_copyable::value) + memcpy(reinterpret_cast(getBuckets()), other.getBuckets(), getNumBuckets() * sizeof(BucketT)); else for (size_t i = 0; i < getNumBuckets(); ++i) { - new (&getBuckets()[i].first) KeyT(other.getBuckets()[i].first); - if (!KeyInfoT::isEqual(getBuckets()[i].first, getEmptyKey()) && - !KeyInfoT::isEqual(getBuckets()[i].first, getTombstoneKey())) - new (&getBuckets()[i].second) ValueT(other.getBuckets()[i].second); + ::new (&getBuckets()[i].getFirst()) + KeyT(other.getBuckets()[i].getFirst()); + if (!KeyInfoT::isEqual(getBuckets()[i].getFirst(), getEmptyKey()) && + !KeyInfoT::isEqual(getBuckets()[i].getFirst(), getTombstoneKey())) + ::new (&getBuckets()[i].getSecond()) + ValueT(other.getBuckets()[i].getSecond()); } } - void swap(DenseMapBase& RHS) { - std::swap(getNumEntries(), RHS.getNumEntries()); - std::swap(getNumTombstones(), RHS.getNumTombstones()); - } - static unsigned getHashValue(const KeyT &Val) { return KeyInfoT::getHashValue(Val); } + template static unsigned getHashValue(const LookupKeyT &Val) { return KeyInfoT::getHashValue(Val); } + static const KeyT getEmptyKey() { + static_assert(std::is_base_of::value, + "Must pass the derived type to this template!"); return KeyInfoT::getEmptyKey(); } + static const KeyT getTombstoneKey() { return KeyInfoT::getTombstoneKey(); } private: + iterator makeIterator(BucketT *P, BucketT *E, + bool NoAdvance=false) { + return iterator(P, E, NoAdvance); + } + + const_iterator makeConstIterator(const BucketT *P, const BucketT *E, + const bool NoAdvance=false) const { + return const_iterator(P, E, NoAdvance); + } + unsigned getNumEntries() const { return static_cast(this)->getNumEntries(); } + void setNumEntries(unsigned Num) { static_cast(this)->setNumEntries(Num); } + void incrementNumEntries() { setNumEntries(getNumEntries() + 1); } + void decrementNumEntries() { setNumEntries(getNumEntries() - 1); } + unsigned getNumTombstones() const { return static_cast(this)->getNumTombstones(); } + void setNumTombstones(unsigned Num) { static_cast(this)->setNumTombstones(Num); } + void incrementNumTombstones() { setNumTombstones(getNumTombstones() + 1); } + void decrementNumTombstones() { setNumTombstones(getNumTombstones() - 1); } + const BucketT *getBuckets() const { return static_cast(this)->getBuckets(); } + BucketT *getBuckets() { return static_cast(this)->getBuckets(); } + unsigned getNumBuckets() const { return static_cast(this)->getNumBuckets(); } + BucketT *getBucketsEnd() { return getBuckets() + getNumBuckets(); } + const BucketT *getBucketsEnd() const { return getBuckets() + getNumBuckets(); } @@ -409,39 +556,32 @@ class DenseMapBase { static_cast(this)->shrink_and_clear(); } + template + BucketT *InsertIntoBucket(BucketT *TheBucket, KeyArg &&Key, + ValueArgs &&... Values) { + TheBucket = InsertIntoBucketImpl(Key, Key, TheBucket); - BucketT *InsertIntoBucket(const KeyT &Key, const ValueT &Value, - BucketT *TheBucket) { - TheBucket = InsertIntoBucketImpl(Key, TheBucket); - - TheBucket->first = Key; - new (&TheBucket->second) ValueT(Value); + TheBucket->getFirst() = std::forward(Key); + ::new (&TheBucket->getSecond()) ValueT(std::forward(Values)...); return TheBucket; } -#if LLVM_USE_RVALUE_REFERENCES - BucketT *InsertIntoBucket(const KeyT &Key, ValueT &&Value, - BucketT *TheBucket) { - TheBucket = InsertIntoBucketImpl(Key, TheBucket); - - TheBucket->first = Key; - new (&TheBucket->second) ValueT(std::move(Value)); - return TheBucket; - } - - BucketT *InsertIntoBucket(KeyT &&Key, ValueT &&Value, BucketT *TheBucket) { - TheBucket = InsertIntoBucketImpl(Key, TheBucket); + template + BucketT *InsertIntoBucketWithLookup(BucketT *TheBucket, KeyT &&Key, + ValueT &&Value, LookupKeyT &Lookup) { + TheBucket = InsertIntoBucketImpl(Key, Lookup, TheBucket); - TheBucket->first = std::move(Key); - new (&TheBucket->second) ValueT(std::move(Value)); + TheBucket->getFirst() = std::move(Key); + ::new (&TheBucket->getSecond()) ValueT(std::move(Value)); return TheBucket; } -#endif - BucketT *InsertIntoBucketImpl(const KeyT &Key, BucketT *TheBucket) { - // If the load of the hash table is more than 3/4, grow the table. - // If fewer than 1/8 of the buckets are empty (meaning that many are - // filled with tombstones), rehash the table without growing. + template + BucketT *InsertIntoBucketImpl(const KeyT &Key, const LookupKeyT &Lookup, + BucketT *TheBucket) { + // If the load of the hash table is more than 3/4, or if fewer than 1/8 of + // the buckets are empty (meaning that many are filled with tombstones), + // grow the table. // // The later case is tricky. For example, if we had one empty bucket with // tons of tombstones, failing lookups (e.g. for insertion) would have to @@ -450,43 +590,53 @@ class DenseMapBase { // causing infinite loops in lookup. unsigned NewNumEntries = getNumEntries() + 1; unsigned NumBuckets = getNumBuckets(); - if (NewNumEntries*4 >= NumBuckets*3) { + if (LLVM_UNLIKELY(NewNumEntries * 4 >= NumBuckets * 3)) { this->grow(NumBuckets * 2); - LookupBucketFor(Key, TheBucket); + LookupBucketFor(Lookup, TheBucket); NumBuckets = getNumBuckets(); - } - if (NumBuckets-(NewNumEntries+getNumTombstones()) <= NumBuckets/8) { + } else if (LLVM_UNLIKELY(NumBuckets-(NewNumEntries+getNumTombstones()) <= + NumBuckets/8)) { this->grow(NumBuckets); - LookupBucketFor(Key, TheBucket); + LookupBucketFor(Lookup, TheBucket); } - assert(TheBucket); + ASSERT(TheBucket); // Only update the state after we've grown our bucket space appropriately // so that when growing buckets we have self-consistent entry count. // If we are writing over a tombstone or zero value, remember this. - if (KeyInfoT::isEqual(TheBucket->first, getEmptyKey())) { + if (KeyInfoT::isEqual(TheBucket->getFirst(), getEmptyKey())) { // Replacing an empty bucket. - incrementNumEntries(); - } - else if (KeyInfoT::isEqual(TheBucket->first, getTombstoneKey())) { + incrementNumEntries(); + } else if (KeyInfoT::isEqual(TheBucket->getFirst(), getTombstoneKey())) { // Replacing a tombstone. incrementNumEntries(); decrementNumTombstones(); - } - else if (ZeroValuesArePurgeable && TheBucket->second == 0) { - // Purging a zero. No accounting changes. - TheBucket->second.~ValueT(); } else { - // Updating an existing entry. No accounting changes. + // we should be purging a zero. No accounting changes. + ASSERT(ValueInfoT::isPurgeable(TheBucket->getSecond())); + TheBucket->getSecond().~ValueT(); } return TheBucket; } + __attribute__((noinline, noreturn, cold)) + void FatalCorruptHashTables(const BucketT *BucketsPtr, unsigned NumBuckets) const + { + _objc_fatal("Hash table corrupted. This is probably a memory error " + "somewhere. (table at %p, buckets at %p (%zu bytes), " + "%u buckets, %u entries, %u tombstones, " + "data %p %p %p %p)", + this, BucketsPtr, malloc_size(BucketsPtr), + NumBuckets, getNumEntries(), getNumTombstones(), + ((void**)BucketsPtr)[0], ((void**)BucketsPtr)[1], + ((void**)BucketsPtr)[2], ((void**)BucketsPtr)[3]); + } + /// LookupBucketFor - Lookup the appropriate bucket for Val, returning it in /// FoundBucket. If the bucket contains the key and a value, this returns - /// true, otherwise it returns a bucket with an empty marker or tombstone - /// or zero value and returns false. + /// true, otherwise it returns a bucket with an empty marker or tombstone and + /// returns false. template bool LookupBucketFor(const LookupKeyT &Val, const BucketT *&FoundBucket) const { @@ -494,12 +644,12 @@ class DenseMapBase { const unsigned NumBuckets = getNumBuckets(); if (NumBuckets == 0) { - FoundBucket = 0; + FoundBucket = nullptr; return false; } - // FoundTombstone - Keep track of whether we find a tombstone or zero value while probing. - const BucketT *FoundTombstone = 0; + // FoundTombstone - Keep track of whether we find a tombstone while probing. + const BucketT *FoundTombstone = nullptr; const KeyT EmptyKey = getEmptyKey(); const KeyT TombstoneKey = getTombstoneKey(); assert(!KeyInfoT::isEqual(Val, EmptyKey) && @@ -508,20 +658,19 @@ class DenseMapBase { unsigned BucketNo = getHashValue(Val) & (NumBuckets-1); unsigned ProbeAmt = 1; - while (1) { + while (true) { const BucketT *ThisBucket = BucketsPtr + BucketNo; // Found Val's bucket? If so, return it. - if (KeyInfoT::isEqual(Val, ThisBucket->first)) { + if (LLVM_LIKELY(KeyInfoT::isEqual(Val, ThisBucket->getFirst()))) { FoundBucket = ThisBucket; return true; } // If we found an empty bucket, the key doesn't exist in the set. // Insert it and return the default value. - if (KeyInfoT::isEqual(ThisBucket->first, EmptyKey)) { + if (LLVM_LIKELY(KeyInfoT::isEqual(ThisBucket->getFirst(), EmptyKey))) { // If we've already seen a tombstone while probing, fill it in instead // of the empty bucket we eventually probed to. - if (FoundTombstone) ThisBucket = FoundTombstone; FoundBucket = FoundTombstone ? FoundTombstone : ThisBucket; return false; } @@ -529,27 +678,19 @@ class DenseMapBase { // If this is a tombstone, remember it. If Val ends up not in the map, we // prefer to return it than something that would require more probing. // Ditto for zero values. - if (KeyInfoT::isEqual(ThisBucket->first, TombstoneKey) && !FoundTombstone) + if (KeyInfoT::isEqual(ThisBucket->getFirst(), TombstoneKey) && + !FoundTombstone) FoundTombstone = ThisBucket; // Remember the first tombstone found. - if (ZeroValuesArePurgeable && - ThisBucket->second == 0 && !FoundTombstone) + if (ValueInfoT::isPurgeable(ThisBucket->getSecond()) && !FoundTombstone) FoundTombstone = ThisBucket; // Otherwise, it's a hash collision or a tombstone, continue quadratic // probing. if (ProbeAmt > NumBuckets) { - // No empty buckets in table. Die. - _objc_fatal("Hash table corrupted. This is probably a memory error " - "somewhere. (table at %p, buckets at %p (%zu bytes), " - "%u buckets, %u entries, %u tombstones, " - "data %p %p %p %p)", - this, BucketsPtr, malloc_size(BucketsPtr), - NumBuckets, getNumEntries(), getNumTombstones(), - ((void**)BucketsPtr)[0], ((void**)BucketsPtr)[1], - ((void**)BucketsPtr)[2], ((void**)BucketsPtr)[3]); + FatalCorruptHashTables(BucketsPtr, NumBuckets); } BucketNo += ProbeAmt++; - BucketNo&= (NumBuckets-1); + BucketNo &= (NumBuckets-1); } } @@ -572,17 +713,51 @@ class DenseMapBase { } }; -template > -class DenseMap - : public DenseMapBase, - KeyT, ValueT, KeyInfoT, ZeroValuesArePurgeable> { +/// Equality comparison for DenseMap. +/// +/// Iterates over elements of LHS confirming that each (key, value) pair in LHS +/// is also in RHS, and that no additional pairs are in RHS. +/// Equivalent to N calls to RHS.find and N value comparisons. Amortized +/// complexity is linear, worst case is O(N^2) (if every hash collides). +template +bool operator==( + const DenseMapBase &LHS, + const DenseMapBase &RHS) { + if (LHS.size() != RHS.size()) + return false; + + for (auto &KV : LHS) { + auto I = RHS.find(KV.first); + if (I == RHS.end() || I->second != KV.second) + return false; + } + + return true; +} + +/// Inequality comparison for DenseMap. +/// +/// Equivalent to !(LHS == RHS). See operator== for performance notes. +template +bool operator!=( + const DenseMapBase &LHS, + const DenseMapBase &RHS) { + return !(LHS == RHS); +} + +template , + typename KeyInfoT = DenseMapInfo, + typename BucketT = detail::DenseMapPair> +class DenseMap : public DenseMapBase, + KeyT, ValueT, ValueInfoT, KeyInfoT, BucketT> { + friend class DenseMapBase; + // Lift some types from the dependent base class into this class for // simplicity of referring to them. - typedef DenseMapBase BaseT; - typedef typename BaseT::BucketT BucketT; - friend class DenseMapBase; + using BaseT = DenseMapBase; BucketT *Buckets; unsigned NumEntries; @@ -590,28 +765,31 @@ class DenseMap unsigned NumBuckets; public: - explicit DenseMap(unsigned NumInitBuckets = 0) { - init(NumInitBuckets); - } + /// Create a DenseMap wth an optional \p InitialReserve that guarantee that + /// this number of elements can be inserted in the map without grow() + explicit DenseMap(unsigned InitialReserve = 0) { init(InitialReserve); } - DenseMap(const DenseMap &other) { + DenseMap(const DenseMap &other) : BaseT() { init(0); copyFrom(other); } -#if LLVM_USE_RVALUE_REFERENCES - DenseMap(DenseMap &&other) { + DenseMap(DenseMap &&other) : BaseT() { init(0); swap(other); } -#endif template DenseMap(const InputIt &I, const InputIt &E) { - init(NextPowerOf2(std::distance(I, E))); + init(std::distance(I, E)); this->insert(I, E); } + DenseMap(std::initializer_list Vals) { + init(Vals.size()); + this->insert(Vals.begin(), Vals.end()); + } + ~DenseMap() { this->destroyAll(); operator delete(Buckets); @@ -625,11 +803,11 @@ class DenseMap } DenseMap& operator=(const DenseMap& other) { - copyFrom(other); + if (&other != this) + copyFrom(other); return *this; } -#if LLVM_USE_RVALUE_REFERENCES DenseMap& operator=(DenseMap &&other) { this->destroyAll(); operator delete(Buckets); @@ -637,7 +815,6 @@ class DenseMap swap(other); return *this; } -#endif void copyFrom(const DenseMap& other) { this->destroyAll(); @@ -650,7 +827,8 @@ class DenseMap } } - void init(unsigned InitBuckets) { + void init(unsigned InitNumEntries) { + auto InitBuckets = BaseT::getMinBucketToReserveForEntries(InitNumEntries); if (allocateBuckets(InitBuckets)) { this->BaseT::initEmpty(); } else { @@ -663,8 +841,8 @@ class DenseMap unsigned OldNumBuckets = NumBuckets; BucketT *OldBuckets = Buckets; - allocateBuckets(std::max(MIN_BUCKETS, NextPowerOf2(AtLeast))); - assert(Buckets); + allocateBuckets(std::max(MIN_BUCKETS, static_cast(NextPowerOf2(AtLeast-1)))); + ASSERT(Buckets); if (!OldBuckets) { this->BaseT::initEmpty(); return; @@ -697,6 +875,7 @@ class DenseMap unsigned getNumEntries() const { return NumEntries; } + void setNumEntries(unsigned Num) { NumEntries = Num; } @@ -704,6 +883,7 @@ class DenseMap unsigned getNumTombstones() const { return NumTombstones; } + void setNumTombstones(unsigned Num) { NumTombstones = Num; } @@ -719,27 +899,31 @@ class DenseMap bool allocateBuckets(unsigned Num) { NumBuckets = Num; if (NumBuckets == 0) { - Buckets = 0; + Buckets = nullptr; return false; } - Buckets = static_cast(operator new(sizeof(BucketT)*NumBuckets)); + Buckets = static_cast(operator new(sizeof(BucketT) * NumBuckets)); return true; } }; -template > +template , + typename KeyInfoT = DenseMapInfo, + typename BucketT = detail::DenseMapPair> class SmallDenseMap - : public DenseMapBase, - KeyT, ValueT, KeyInfoT, ZeroValuesArePurgeable> { + : public DenseMapBase< + SmallDenseMap, KeyT, + ValueT, ValueInfoT, KeyInfoT, BucketT> { + friend class DenseMapBase; + // Lift some types from the dependent base class into this class for // simplicity of referring to them. - typedef DenseMapBase BaseT; - typedef typename BaseT::BucketT BucketT; - friend class DenseMapBase; + using BaseT = DenseMapBase; + + static_assert(powerof2(InlineBuckets), + "InlineBuckets must be a power of 2."); unsigned Small : 1; unsigned NumEntries : 31; @@ -759,17 +943,15 @@ class SmallDenseMap init(NumInitBuckets); } - SmallDenseMap(const SmallDenseMap &other) { + SmallDenseMap(const SmallDenseMap &other) : BaseT() { init(0); copyFrom(other); } -#if LLVM_USE_RVALUE_REFERENCES - SmallDenseMap(SmallDenseMap &&other) { + SmallDenseMap(SmallDenseMap &&other) : BaseT() { init(0); swap(other); } -#endif template SmallDenseMap(const InputIt &I, const InputIt &E) { @@ -798,23 +980,23 @@ class SmallDenseMap for (unsigned i = 0, e = InlineBuckets; i != e; ++i) { BucketT *LHSB = &getInlineBuckets()[i], *RHSB = &RHS.getInlineBuckets()[i]; - bool hasLHSValue = (!KeyInfoT::isEqual(LHSB->first, EmptyKey) && - !KeyInfoT::isEqual(LHSB->first, TombstoneKey)); - bool hasRHSValue = (!KeyInfoT::isEqual(RHSB->first, EmptyKey) && - !KeyInfoT::isEqual(RHSB->first, TombstoneKey)); + bool hasLHSValue = (!KeyInfoT::isEqual(LHSB->getFirst(), EmptyKey) && + !KeyInfoT::isEqual(LHSB->getFirst(), TombstoneKey)); + bool hasRHSValue = (!KeyInfoT::isEqual(RHSB->getFirst(), EmptyKey) && + !KeyInfoT::isEqual(RHSB->getFirst(), TombstoneKey)); if (hasLHSValue && hasRHSValue) { // Swap together if we can... std::swap(*LHSB, *RHSB); continue; } // Swap separately and handle any assymetry. - std::swap(LHSB->first, RHSB->first); + std::swap(LHSB->getFirst(), RHSB->getFirst()); if (hasLHSValue) { - new (&RHSB->second) ValueT(llvm_move(LHSB->second)); - LHSB->second.~ValueT(); + ::new (&RHSB->getSecond()) ValueT(std::move(LHSB->getSecond())); + LHSB->getSecond().~ValueT(); } else if (hasRHSValue) { - new (&LHSB->second) ValueT(llvm_move(RHSB->second)); - RHSB->second.~ValueT(); + ::new (&LHSB->getSecond()) ValueT(std::move(RHSB->getSecond())); + RHSB->getSecond().~ValueT(); } } return; @@ -829,7 +1011,7 @@ class SmallDenseMap SmallDenseMap &LargeSide = Small ? RHS : *this; // First stash the large side's rep and move the small side across. - LargeRep TmpRep = llvm_move(*LargeSide.getLargeRep()); + LargeRep TmpRep = std::move(*LargeSide.getLargeRep()); LargeSide.getLargeRep()->~LargeRep(); LargeSide.Small = true; // This is similar to the standard move-from-old-buckets, but the bucket @@ -839,27 +1021,27 @@ class SmallDenseMap for (unsigned i = 0, e = InlineBuckets; i != e; ++i) { BucketT *NewB = &LargeSide.getInlineBuckets()[i], *OldB = &SmallSide.getInlineBuckets()[i]; - new (&NewB->first) KeyT(llvm_move(OldB->first)); - OldB->first.~KeyT(); - if (!KeyInfoT::isEqual(NewB->first, EmptyKey) && - !KeyInfoT::isEqual(NewB->first, TombstoneKey)) { - new (&NewB->second) ValueT(llvm_move(OldB->second)); - OldB->second.~ValueT(); + ::new (&NewB->getFirst()) KeyT(std::move(OldB->getFirst())); + OldB->getFirst().~KeyT(); + if (!KeyInfoT::isEqual(NewB->getFirst(), EmptyKey) && + !KeyInfoT::isEqual(NewB->getFirst(), TombstoneKey)) { + ::new (&NewB->getSecond()) ValueT(std::move(OldB->getSecond())); + OldB->getSecond().~ValueT(); } } // The hard part of moving the small buckets across is done, just move // the TmpRep into its new home. SmallSide.Small = false; - new (SmallSide.getLargeRep()) LargeRep(llvm_move(TmpRep)); + new (SmallSide.getLargeRep()) LargeRep(std::move(TmpRep)); } SmallDenseMap& operator=(const SmallDenseMap& other) { - copyFrom(other); + if (&other != this) + copyFrom(other); return *this; } -#if LLVM_USE_RVALUE_REFERENCES SmallDenseMap& operator=(SmallDenseMap &&other) { this->destroyAll(); deallocateBuckets(); @@ -867,7 +1049,6 @@ class SmallDenseMap swap(other); return *this; } -#endif void copyFrom(const SmallDenseMap& other) { this->destroyAll(); @@ -875,7 +1056,7 @@ class SmallDenseMap Small = true; if (other.getNumBuckets() > InlineBuckets) { Small = false; - allocateBuckets(other.getNumBuckets()); + new (getLargeRep()) LargeRep(allocateBuckets(other.getNumBuckets())); } this->BaseT::copyFrom(other); } @@ -890,11 +1071,11 @@ class SmallDenseMap } void grow(unsigned AtLeast) { - if (AtLeast > InlineBuckets) + if (AtLeast >= InlineBuckets) AtLeast = std::max(MIN_BUCKETS, NextPowerOf2(AtLeast)); if (Small) { - if (AtLeast <= InlineBuckets) + if (AtLeast < InlineBuckets) return; // Nothing to do. // First move the inline buckets into a temporary storage. @@ -907,16 +1088,16 @@ class SmallDenseMap const KeyT EmptyKey = this->getEmptyKey(); const KeyT TombstoneKey = this->getTombstoneKey(); for (BucketT *P = getBuckets(), *E = P + InlineBuckets; P != E; ++P) { - if (!KeyInfoT::isEqual(P->first, EmptyKey) && - !KeyInfoT::isEqual(P->first, TombstoneKey)) { + if (!KeyInfoT::isEqual(P->getFirst(), EmptyKey) && + !KeyInfoT::isEqual(P->getFirst(), TombstoneKey)) { assert(size_t(TmpEnd - TmpBegin) < InlineBuckets && "Too many inline buckets!"); - new (&TmpEnd->first) KeyT(llvm_move(P->first)); - new (&TmpEnd->second) ValueT(llvm_move(P->second)); + ::new (&TmpEnd->getFirst()) KeyT(std::move(P->getFirst())); + ::new (&TmpEnd->getSecond()) ValueT(std::move(P->getSecond())); ++TmpEnd; - P->second.~ValueT(); + P->getSecond().~ValueT(); } - P->first.~KeyT(); + P->getFirst().~KeyT(); } // Now make this map use the large rep, and move all the entries back @@ -927,7 +1108,7 @@ class SmallDenseMap return; } - LargeRep OldRep = llvm_move(*getLargeRep()); + LargeRep OldRep = std::move(*getLargeRep()); getLargeRep()->~LargeRep(); if (AtLeast <= InlineBuckets) { Small = true; @@ -966,34 +1147,40 @@ class SmallDenseMap unsigned getNumEntries() const { return NumEntries; } + void setNumEntries(unsigned Num) { - assert(Num < INT_MAX && "Cannot support more than INT_MAX entries"); + // NumEntries is hardcoded to be 31 bits wide. + ASSERT(Num < (1U << 31) && "Cannot support more than 1<<31 entries"); NumEntries = Num; } unsigned getNumTombstones() const { return NumTombstones; } + void setNumTombstones(unsigned Num) { NumTombstones = Num; } const BucketT *getInlineBuckets() const { - assert(Small); + ASSERT(Small); // Note that this cast does not violate aliasing rules as we assert that // the memory's dynamic type is the small, inline bucket buffer, and the // 'storage.buffer' static type is 'char *'. return reinterpret_cast(storage.buffer); } + BucketT *getInlineBuckets() { return const_cast( const_cast(this)->getInlineBuckets()); } + const LargeRep *getLargeRep() const { - assert(!Small); + ASSERT(!Small); // Note, same rule about aliasing as with getInlineBuckets. return reinterpret_cast(storage.buffer); } + LargeRep *getLargeRep() { return const_cast( const_cast(this)->getLargeRep()); @@ -1002,10 +1189,12 @@ class SmallDenseMap const BucketT *getBuckets() const { return Small ? getInlineBuckets() : getLargeRep()->Buckets; } + BucketT *getBuckets() { return const_cast( const_cast(this)->getBuckets()); } + unsigned getNumBuckets() const { return Small ? InlineBuckets : getLargeRep()->NumBuckets; } @@ -1019,43 +1208,52 @@ class SmallDenseMap } LargeRep allocateBuckets(unsigned Num) { - assert(Num > InlineBuckets && "Must allocate more buckets than are inline"); + ASSERT(Num > InlineBuckets && "Must allocate more buckets than are inline"); LargeRep Rep = { static_cast(operator new(sizeof(BucketT) * Num)), Num -}; + }; return Rep; } }; -template +template class DenseMapIterator { - typedef std::pair Bucket; - typedef DenseMapIterator ConstIterator; - friend class DenseMapIterator; + friend class DenseMapIterator; + friend class DenseMapIterator; + + using ConstIterator = DenseMapIterator; + public: - typedef ptrdiff_t difference_type; - typedef typename conditional::type value_type; - typedef value_type *pointer; - typedef value_type &reference; - typedef std::forward_iterator_tag iterator_category; + using difference_type = ptrdiff_t; + using value_type = + typename std::conditional::type; + using pointer = value_type *; + using reference = value_type &; + using iterator_category = std::forward_iterator_tag; + private: - pointer Ptr, End; + pointer Ptr = nullptr; + pointer End = nullptr; + public: - DenseMapIterator() : Ptr(0), End(0) {} + DenseMapIterator() = default; - DenseMapIterator(pointer Pos, pointer E, bool NoAdvance = false) - : Ptr(Pos), End(E) { - if (!NoAdvance) AdvancePastEmptyBuckets(); + DenseMapIterator(pointer Pos, pointer E, + bool NoAdvance = false) + : Ptr(Pos), End(E) { + if (NoAdvance) return; + AdvancePastEmptyBuckets(); } - // If IsConst is true this is a converting constructor from iterator to - // const_iterator and the default copy constructor is used. - // Otherwise this is a copy constructor for iterator. - DenseMapIterator(const DenseMapIterator& I) - : Ptr(I.Ptr), End(I.End) {} + // Converting ctor from non-const iterators to const iterators. SFINAE'd out + // for const iterator destinations so it doesn't end up as a user defined copy + // constructor. + template ::type> + DenseMapIterator( + const DenseMapIterator &I) + : Ptr(I.Ptr), End(I.End) {} reference operator*() const { return *Ptr; @@ -1065,10 +1263,10 @@ class DenseMapIterator { } bool operator==(const ConstIterator &RHS) const { - return Ptr == RHS.operator->(); + return Ptr == RHS.Ptr; } bool operator!=(const ConstIterator &RHS) const { - return Ptr != RHS.operator->(); + return Ptr != RHS.Ptr; } inline DenseMapIterator& operator++() { // Preincrement @@ -1082,16 +1280,31 @@ class DenseMapIterator { private: void AdvancePastEmptyBuckets() { + ASSERT(Ptr <= End); const KeyT Empty = KeyInfoT::getEmptyKey(); const KeyT Tombstone = KeyInfoT::getTombstoneKey(); - while (Ptr != End && - (KeyInfoT::isEqual(Ptr->first, Empty) || - KeyInfoT::isEqual(Ptr->first, Tombstone))) + while (Ptr != End && (KeyInfoT::isEqual(Ptr->getFirst(), Empty) || + KeyInfoT::isEqual(Ptr->getFirst(), Tombstone))) ++Ptr; } + + void RetreatPastEmptyBuckets() { + ASSERT(Ptr >= End); + const KeyT Empty = KeyInfoT::getEmptyKey(); + const KeyT Tombstone = KeyInfoT::getTombstoneKey(); + + while (Ptr != End && (KeyInfoT::isEqual(Ptr[-1].getFirst(), Empty) || + KeyInfoT::isEqual(Ptr[-1].getFirst(), Tombstone))) + --Ptr; + } }; +template +inline size_t capacity_in_bytes(const DenseMap &X) { + return X.getMemorySize(); +} + } // end namespace objc -#endif +#endif // LLVM_ADT_DENSEMAP_H diff --git a/runtime/llvm-DenseMapInfo.h b/runtime/llvm-DenseMapInfo.h index 4b7869f..a82c701 100644 --- a/runtime/llvm-DenseMapInfo.h +++ b/runtime/llvm-DenseMapInfo.h @@ -75,6 +75,15 @@ template<> struct DenseMapInfo { return _objc_strhash(Val); } static bool isEqual(const char* const &LHS, const char* const &RHS) { + if (LHS == RHS) { + return true; + } + if (LHS == getEmptyKey() || RHS == getEmptyKey()) { + return false; + } + if (LHS == getTombstoneKey() || RHS == getTombstoneKey()) { + return false; + } return 0 == strcmp(LHS, RHS); } }; @@ -195,6 +204,13 @@ struct DenseMapInfo > { } }; +template +struct DenseMapValueInfo { + static inline bool isPurgeable(const T &value) { + return false; + } +}; + } // end namespace objc #endif diff --git a/runtime/llvm-DenseSet.h b/runtime/llvm-DenseSet.h new file mode 100644 index 0000000..45ea107 --- /dev/null +++ b/runtime/llvm-DenseSet.h @@ -0,0 +1,293 @@ +//===- llvm/ADT/DenseSet.h - Dense probed hash table ------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file defines the DenseSet and SmallDenseSet classes. +// +//===----------------------------------------------------------------------===// + +// Taken from clang-1100.247.11.10.9 + +#ifndef LLVM_ADT_DENSESET_H +#define LLVM_ADT_DENSESET_H + +#include "llvm-DenseMap.h" +#include "llvm-DenseMapInfo.h" +#include "llvm-type_traits.h" +#include +#include +#include +#include +#include +#include + +#include "objc-private.h" + +namespace objc { + +namespace detail { + +struct DenseSetEmpty {}; + +// Use the empty base class trick so we can create a DenseMap where the buckets +// contain only a single item. +template class DenseSetPair : public DenseSetEmpty { + KeyT key; + +public: + KeyT &getFirst() { return key; } + const KeyT &getFirst() const { return key; } + DenseSetEmpty &getSecond() { return *this; } + const DenseSetEmpty &getSecond() const { return *this; } +}; + +/// Base class for DenseSet and DenseSmallSet. +/// +/// MapTy should be either +/// +/// DenseMap, +/// ValueInfoT, detail::DenseSetPair> +/// +/// or the equivalent SmallDenseMap type. ValueInfoT must implement the +/// DenseMapInfo "concept". +template +class DenseSetImpl { + static_assert(sizeof(typename MapTy::value_type) == sizeof(ValueT), + "DenseMap buckets unexpectedly large!"); + MapTy TheMap; + + template + using const_arg_type_t = typename const_pointer_or_const_ref::type; + +public: + using key_type = ValueT; + using value_type = ValueT; + using size_type = unsigned; + + explicit DenseSetImpl(unsigned InitialReserve = 0) : TheMap(InitialReserve) {} + + DenseSetImpl(std::initializer_list Elems) + : DenseSetImpl(PowerOf2Ceil(Elems.size())) { + insert(Elems.begin(), Elems.end()); + } + + bool empty() const { return TheMap.empty(); } + size_type size() const { return TheMap.size(); } + size_t getMemorySize() const { return TheMap.getMemorySize(); } + + /// Grow the DenseSet so that it has at least Size buckets. Will not shrink + /// the Size of the set. + void resize(size_t Size) { TheMap.resize(Size); } + + /// Grow the DenseSet so that it can contain at least \p NumEntries items + /// before resizing again. + void reserve(size_t Size) { TheMap.reserve(Size); } + + void clear() { + TheMap.clear(); + } + + /// Return 1 if the specified key is in the set, 0 otherwise. + size_type count(const_arg_type_t V) const { + return TheMap.count(V); + } + + bool erase(const ValueT &V) { + return TheMap.erase(V); + } + + void swap(DenseSetImpl &RHS) { TheMap.swap(RHS.TheMap); } + + // Iterators. + + class ConstIterator; + + class Iterator { + typename MapTy::iterator I; + friend class DenseSetImpl; + friend class ConstIterator; + + public: + using difference_type = typename MapTy::iterator::difference_type; + using value_type = ValueT; + using pointer = value_type *; + using reference = value_type &; + using iterator_category = std::forward_iterator_tag; + + Iterator() = default; + Iterator(const typename MapTy::iterator &i) : I(i) {} + + ValueT &operator*() { return I->getFirst(); } + const ValueT &operator*() const { return I->getFirst(); } + ValueT *operator->() { return &I->getFirst(); } + const ValueT *operator->() const { return &I->getFirst(); } + + Iterator& operator++() { ++I; return *this; } + Iterator operator++(int) { auto T = *this; ++I; return T; } + bool operator==(const ConstIterator& X) const { return I == X.I; } + bool operator!=(const ConstIterator& X) const { return I != X.I; } + }; + + class ConstIterator { + typename MapTy::const_iterator I; + friend class DenseSet; + friend class Iterator; + + public: + using difference_type = typename MapTy::const_iterator::difference_type; + using value_type = ValueT; + using pointer = const value_type *; + using reference = const value_type &; + using iterator_category = std::forward_iterator_tag; + + ConstIterator() = default; + ConstIterator(const Iterator &B) : I(B.I) {} + ConstIterator(const typename MapTy::const_iterator &i) : I(i) {} + + const ValueT &operator*() const { return I->getFirst(); } + const ValueT *operator->() const { return &I->getFirst(); } + + ConstIterator& operator++() { ++I; return *this; } + ConstIterator operator++(int) { auto T = *this; ++I; return T; } + bool operator==(const ConstIterator& X) const { return I == X.I; } + bool operator!=(const ConstIterator& X) const { return I != X.I; } + }; + + using iterator = Iterator; + using const_iterator = ConstIterator; + + iterator begin() { return Iterator(TheMap.begin()); } + iterator end() { return Iterator(TheMap.end()); } + + const_iterator begin() const { return ConstIterator(TheMap.begin()); } + const_iterator end() const { return ConstIterator(TheMap.end()); } + + iterator find(const_arg_type_t V) { return Iterator(TheMap.find(V)); } + const_iterator find(const_arg_type_t V) const { + return ConstIterator(TheMap.find(V)); + } + + /// Alternative version of find() which allows a different, and possibly less + /// expensive, key type. + /// The DenseMapInfo is responsible for supplying methods + /// getHashValue(LookupKeyT) and isEqual(LookupKeyT, KeyT) for each key type + /// used. + template + iterator find_as(const LookupKeyT &Val) { + return Iterator(TheMap.find_as(Val)); + } + template + const_iterator find_as(const LookupKeyT &Val) const { + return ConstIterator(TheMap.find_as(Val)); + } + + void erase(Iterator I) { return TheMap.erase(I.I); } + void erase(ConstIterator CI) { return TheMap.erase(CI.I); } + + std::pair insert(const ValueT &V) { + detail::DenseSetEmpty Empty; + return TheMap.try_emplace(V, Empty); + } + + std::pair insert(ValueT &&V) { + detail::DenseSetEmpty Empty; + return TheMap.try_emplace(std::move(V), Empty); + } + + /// Alternative version of insert that uses a different (and possibly less + /// expensive) key type. + template + std::pair insert_as(const ValueT &V, + const LookupKeyT &LookupKey) { + return TheMap.insert_as({V, detail::DenseSetEmpty()}, LookupKey); + } + template + std::pair insert_as(ValueT &&V, const LookupKeyT &LookupKey) { + return TheMap.insert_as({std::move(V), detail::DenseSetEmpty()}, LookupKey); + } + + // Range insertion of values. + template + void insert(InputIt I, InputIt E) { + for (; I != E; ++I) + insert(*I); + } +}; + +/// Equality comparison for DenseSet. +/// +/// Iterates over elements of LHS confirming that each element is also a member +/// of RHS, and that RHS contains no additional values. +/// Equivalent to N calls to RHS.count. Amortized complexity is linear, worst +/// case is O(N^2) (if every hash collides). +template +bool operator==(const DenseSetImpl &LHS, + const DenseSetImpl &RHS) { + if (LHS.size() != RHS.size()) + return false; + + for (auto &E : LHS) + if (!RHS.count(E)) + return false; + + return true; +} + +/// Inequality comparison for DenseSet. +/// +/// Equivalent to !(LHS == RHS). See operator== for performance notes. +template +bool operator!=(const DenseSetImpl &LHS, + const DenseSetImpl &RHS) { + return !(LHS == RHS); +} + +} // end namespace detail + +/// Implements a dense probed hash-table based set. +template > +class DenseSet : public detail::DenseSetImpl< + ValueT, DenseMap, + ValueInfoT, detail::DenseSetPair>, + ValueInfoT> { + using BaseT = + detail::DenseSetImpl, + ValueInfoT, detail::DenseSetPair>, + ValueInfoT>; + +public: + using BaseT::BaseT; +}; + +/// Implements a dense probed hash-table based set with some number of buckets +/// stored inline. +template > +class SmallDenseSet + : public detail::DenseSetImpl< + ValueT, SmallDenseMap, + ValueInfoT, detail::DenseSetPair>, + ValueInfoT> { + using BaseT = detail::DenseSetImpl< + ValueT, SmallDenseMap, + ValueInfoT, detail::DenseSetPair>, + ValueInfoT>; + +public: + using BaseT::BaseT; +}; + +} // end namespace objc + +#endif // LLVM_ADT_DENSESET_H diff --git a/runtime/llvm-type_traits.h b/runtime/llvm-type_traits.h index bbe38d4..abcce7e 100644 --- a/runtime/llvm-type_traits.h +++ b/runtime/llvm-type_traits.h @@ -1,24 +1,22 @@ //===- llvm/Support/type_traits.h - Simplfied type traits -------*- C++ -*-===// // -// The LLVM Compiler Infrastructure -// -// This file is distributed under the University of Illinois Open Source -// License. See LICENSE.TXT for details. +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===----------------------------------------------------------------------===// // -// This file provides a template class that determines if a type is a class or -// not. The basic mechanism, based on using the pointer to member function of -// a zero argument to a function was "boosted" from the boost type_traits -// library. See http://www.boost.org/ for all the gory details. +// This file provides useful additions to the standard type_traits library. // //===----------------------------------------------------------------------===// -// Taken from llvmCore-3425.0.31. +// Taken from clang-1100.247.11.10.9 #ifndef LLVM_SUPPORT_TYPE_TRAITS_H #define LLVM_SUPPORT_TYPE_TRAITS_H +#define HAVE_STD_IS_TRIVIALLY_COPYABLE 1 + #include #include @@ -27,195 +25,180 @@ #define __has_feature(x) 0 #endif -// This is actually the conforming implementation which works with abstract -// classes. However, enough compilers have trouble with it that most will use -// the one in boost/type_traits/object_traits.hpp. This implementation actually -// works with VC7.0, but other interactions seem to fail when we use it. - namespace objc { - -namespace dont_use -{ - // These two functions should never be used. They are helpers to - // the is_class template below. They cannot be located inside - // is_class because doing so causes at least GCC to think that - // the value of the "value" enumerator is not constant. Placing - // them out here (for some strange reason) allows the sizeof - // operator against them to magically be constant. This is - // important to make the is_class::value idiom zero cost. it - // evaluates to a constant 1 or 0 depending on whether the - // parameter T is a class or not (respectively). - template char is_class_helper(void(T::*)()); - template double is_class_helper(...); -} -template -struct is_class -{ - // is_class<> metafunction due to Paul Mensonides (leavings@attbi.com). For - // more details: - // http://groups.google.com/groups?hl=en&selm=000001c1cc83%24e154d5e0%247772e50c%40c161550a&rnum=1 + +/// Metafunction that determines whether the given type is either an +/// integral type or an enumeration type, including enum classes. +/// +/// Note that this accepts potentially more integral types than is_integral +/// because it is based on being implicitly convertible to an integral type. +/// Also note that enum classes aren't implicitly convertible to integral types, +/// the value may therefore need to be explicitly converted before being used. +template class is_integral_or_enum { + using UnderlyingT = typename std::remove_reference::type; + public: static const bool value = - sizeof(char) == sizeof(dont_use::is_class_helper(0)); -}; - - -/// isPodLike - This is a type trait that is used to determine whether a given -/// type can be copied around with memcpy instead of running ctors etc. -template -struct isPodLike { -#if __has_feature(is_trivially_copyable) - // If the compiler supports the is_trivially_copyable trait use it, as it - // matches the definition of isPodLike closely. - static const bool value = __is_trivially_copyable(T); -#else - // If we don't know anything else, we can (at least) assume that all non-class - // types are PODs. - static const bool value = !is_class::value; -#endif + !std::is_class::value && // Filter conversion operators. + !std::is_pointer::value && + !std::is_floating_point::value && + (std::is_enum::value || + std::is_convertible::value); }; -// std::pair's are pod-like if their elements are. -template -struct isPodLike > { - static const bool value = isPodLike::value && isPodLike::value; -}; - - -template -struct integral_constant { - typedef T value_type; - static const value_type value = v; - typedef integral_constant type; - operator value_type() { return value; } +/// If T is a pointer, just return it. If it is not, return T&. +template +struct add_lvalue_reference_if_not_pointer { using type = T &; }; + +template +struct add_lvalue_reference_if_not_pointer< + T, typename std::enable_if::value>::type> { + using type = T; }; -typedef integral_constant true_type; -typedef integral_constant false_type; +/// If T is a pointer to X, return a pointer to const X. If it is not, +/// return const T. +template +struct add_const_past_pointer { using type = const T; }; -/// \brief Metafunction that determines whether the two given types are -/// equivalent. -template struct is_same : public false_type {}; -template struct is_same : public true_type {}; +template +struct add_const_past_pointer< + T, typename std::enable_if::value>::type> { + using type = const typename std::remove_pointer::type *; +}; -/// \brief Metafunction that removes const qualification from a type. -template struct remove_const { typedef T type; }; -template struct remove_const { typedef T type; }; +template +struct const_pointer_or_const_ref { + using type = const T &; +}; +template +struct const_pointer_or_const_ref< + T, typename std::enable_if::value>::type> { + using type = typename add_const_past_pointer::type; +}; -/// \brief Metafunction that removes volatile qualification from a type. -template struct remove_volatile { typedef T type; }; -template struct remove_volatile { typedef T type; }; +namespace detail { +/// Internal utility to detect trivial copy construction. +template union copy_construction_triviality_helper { + T t; + copy_construction_triviality_helper() = default; + copy_construction_triviality_helper(const copy_construction_triviality_helper&) = default; + ~copy_construction_triviality_helper() = default; +}; +/// Internal utility to detect trivial move construction. +template union move_construction_triviality_helper { + T t; + move_construction_triviality_helper() = default; + move_construction_triviality_helper(move_construction_triviality_helper&&) = default; + ~move_construction_triviality_helper() = default; +}; -/// \brief Metafunction that removes both const and volatile qualification from -/// a type. -template struct remove_cv { - typedef typename remove_const::type>::type type; +template +union trivial_helper { + T t; }; -/// \brief Helper to implement is_integral metafunction. -template struct is_integral_impl : false_type {}; -template <> struct is_integral_impl< bool> : true_type {}; -template <> struct is_integral_impl< char> : true_type {}; -template <> struct is_integral_impl< signed char> : true_type {}; -template <> struct is_integral_impl : true_type {}; -template <> struct is_integral_impl< wchar_t> : true_type {}; -template <> struct is_integral_impl< short> : true_type {}; -template <> struct is_integral_impl : true_type {}; -template <> struct is_integral_impl< int> : true_type {}; -template <> struct is_integral_impl : true_type {}; -template <> struct is_integral_impl< long> : true_type {}; -template <> struct is_integral_impl : true_type {}; -template <> struct is_integral_impl< long long> : true_type {}; -template <> struct is_integral_impl : true_type {}; - -/// \brief Metafunction that determines whether the given type is an integral -/// type. +} // end namespace detail + +/// An implementation of `std::is_trivially_copy_constructible` since we have +/// users with STLs that don't yet include it. template -struct is_integral : is_integral_impl {}; - -/// \brief Metafunction to remove reference from a type. -template struct remove_reference { typedef T type; }; -template struct remove_reference { typedef T type; }; - -/// \brief Metafunction that determines whether the given type is a pointer -/// type. -template struct is_pointer : false_type {}; -template struct is_pointer : true_type {}; -template struct is_pointer : true_type {}; -template struct is_pointer : true_type {}; -template struct is_pointer : true_type {}; - -/// \brief Metafunction that determines whether the given type is either an -/// integral type or an enumeration type. -/// -/// Note that this accepts potentially more integral types than we whitelist -/// above for is_integral because it is based on merely being convertible -/// implicitly to an integral type. -template class is_integral_or_enum { - // Provide an overload which can be called with anything implicitly - // convertible to an unsigned long long. This should catch integer types and - // enumeration types at least. We blacklist classes with conversion operators - // below. - static double check_int_convertible(unsigned long long); - static char check_int_convertible(...); +struct is_trivially_copy_constructible + : std::is_copy_constructible< + ::objc::detail::copy_construction_triviality_helper> {}; +template +struct is_trivially_copy_constructible : std::true_type {}; +template +struct is_trivially_copy_constructible : std::false_type {}; - typedef typename remove_reference::type UnderlyingT; - static UnderlyingT &nonce_instance; +/// An implementation of `std::is_trivially_move_constructible` since we have +/// users with STLs that don't yet include it. +template +struct is_trivially_move_constructible + : std::is_move_constructible< + ::objc::detail::move_construction_triviality_helper> {}; +template +struct is_trivially_move_constructible : std::true_type {}; +template +struct is_trivially_move_constructible : std::true_type {}; -public: - static const bool - value = (!is_class::value && !is_pointer::value && - !is_same::value && - !is_same::value && - sizeof(char) != sizeof(check_int_convertible(nonce_instance))); -}; -// enable_if_c - Enable/disable a template based on a metafunction -template -struct enable_if_c { - typedef T type; +template +struct is_copy_assignable { + template + static auto get(F*) -> decltype(std::declval() = std::declval(), std::true_type{}); + static std::false_type get(...); + static constexpr bool value = decltype(get((T*)nullptr))::value; }; -template struct enable_if_c { }; - -// enable_if - Enable/disable a template based on a metafunction -template -struct enable_if : public enable_if_c { }; - -namespace dont_use { - template char base_of_helper(const volatile Base*); - template double base_of_helper(...); -} - -/// is_base_of - Metafunction to determine whether one type is a base class of -/// (or identical to) another type. -template -struct is_base_of { - static const bool value - = is_class::value && is_class::value && - sizeof(char) == sizeof(dont_use::base_of_helper((Derived*)0)); +template +struct is_move_assignable { + template + static auto get(F*) -> decltype(std::declval() = std::declval(), std::true_type{}); + static std::false_type get(...); + static constexpr bool value = decltype(get((T*)nullptr))::value; }; -// remove_pointer - Metafunction to turn Foo* into Foo. Defined in -// C++0x [meta.trans.ptr]. -template struct remove_pointer { typedef T type; }; -template struct remove_pointer { typedef T type; }; -template struct remove_pointer { typedef T type; }; -template struct remove_pointer { typedef T type; }; -template struct remove_pointer { - typedef T type; }; -template -struct conditional { typedef T type; }; +// An implementation of `std::is_trivially_copyable` since STL version +// is not equally supported by all compilers, especially GCC 4.9. +// Uniform implementation of this trait is important for ABI compatibility +// as it has an impact on SmallVector's ABI (among others). +template +class is_trivially_copyable { + + // copy constructors + static constexpr bool has_trivial_copy_constructor = + std::is_copy_constructible>::value; + static constexpr bool has_deleted_copy_constructor = + !std::is_copy_constructible::value; + + // move constructors + static constexpr bool has_trivial_move_constructor = + std::is_move_constructible>::value; + static constexpr bool has_deleted_move_constructor = + !std::is_move_constructible::value; + + // copy assign + static constexpr bool has_trivial_copy_assign = + is_copy_assignable>::value; + static constexpr bool has_deleted_copy_assign = + !is_copy_assignable::value; + + // move assign + static constexpr bool has_trivial_move_assign = + is_move_assignable>::value; + static constexpr bool has_deleted_move_assign = + !is_move_assignable::value; + + // destructor + static constexpr bool has_trivial_destructor = + std::is_destructible>::value; + + public: + + static constexpr bool value = + has_trivial_destructor && + (has_deleted_move_assign || has_trivial_move_assign) && + (has_deleted_move_constructor || has_trivial_move_constructor) && + (has_deleted_copy_assign || has_trivial_copy_assign) && + (has_deleted_copy_constructor || has_trivial_copy_constructor); + +#ifdef HAVE_STD_IS_TRIVIALLY_COPYABLE + static_assert(value == std::is_trivially_copyable::value, + "inconsistent behavior between llvm:: and std:: implementation of is_trivially_copyable"); +#endif +}; +template +class is_trivially_copyable : public std::true_type { +}; -template -struct conditional { typedef F type; }; -} +} // end namespace llvm #ifdef LLVM_DEFINED_HAS_FEATURE #undef __has_feature #endif -#endif +#endif // LLVM_SUPPORT_TYPE_TRAITS_H diff --git a/runtime/maptable.h b/runtime/maptable.h index 248c18f..e0f2af9 100644 --- a/runtime/maptable.h +++ b/runtime/maptable.h @@ -30,10 +30,8 @@ #define _OBJC_MAPTABLE_H_ #ifndef _OBJC_PRIVATE_H_ -# define OBJC_MAP_AVAILABILITY \ - __OSX_DEPRECATED(10.0, 10.1, "NXMapTable is deprecated") \ - __IOS_UNAVAILABLE __TVOS_UNAVAILABLE \ - __WATCHOS_UNAVAILABLE __BRIDGEOS_UNAVAILABLE +# define OBJC_MAP_AVAILABILITY \ + OBJC_OSX_DEPRECATED_OTHERS_UNAVAILABLE(10.0, 10.1, "NXMapTable is deprecated") #else # define OBJC_MAP_AVAILABILITY #endif diff --git a/runtime/maptable.mm b/runtime/maptable.mm index a59b00b..25594a0 100644 --- a/runtime/maptable.mm +++ b/runtime/maptable.mm @@ -358,8 +358,6 @@ static void _NXMapRehash(NXMapTable *table) { } } -static int mapRemove = 0; - void *NXMapRemove(NXMapTable *table, const void *key) { MapPair *pairs = (MapPair *)table->buckets; unsigned index = bucketOf(table, key); @@ -368,7 +366,6 @@ static void _NXMapRehash(NXMapTable *table) { int found = 0; const void *old = NULL; if (pair->key == NX_MAPNOTAKEY) return NULL; - mapRemove ++; /* compute chain */ { unsigned index2 = index; diff --git a/runtime/message.h b/runtime/message.h index 20f698f..31bd544 100644 --- a/runtime/message.h +++ b/runtime/message.h @@ -57,6 +57,8 @@ struct objc_super { * before being called. */ #if !OBJC_OLD_DISPATCH_PROTOTYPES +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wincompatible-library-redeclaration" OBJC_EXPORT void objc_msgSend(void /* id self, SEL op, ... */ ) OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0); @@ -64,6 +66,7 @@ objc_msgSend(void /* id self, SEL op, ... */ ) OBJC_EXPORT void objc_msgSendSuper(void /* struct objc_super *super, SEL op, ... */ ) OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0); +#pragma clang diagnostic pop #else /** * Sends a message with a simple return value to an instance of a class. @@ -114,6 +117,8 @@ objc_msgSendSuper(struct objc_super * _Nonnull super, SEL _Nonnull op, ...) * before being called. */ #if !OBJC_OLD_DISPATCH_PROTOTYPES +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wincompatible-library-redeclaration" OBJC_EXPORT void objc_msgSend_stret(void /* id self, SEL op, ... */ ) OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0) @@ -123,6 +128,7 @@ OBJC_EXPORT void objc_msgSendSuper_stret(void /* struct objc_super *super, SEL op, ... */ ) OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0) OBJC_ARM64_UNAVAILABLE; +#pragma clang diagnostic pop #else /** * Sends a message with a data-structure return value to an instance of a class. @@ -165,6 +171,8 @@ objc_msgSendSuper_stret(struct objc_super * _Nonnull super, * before being called. */ #if !OBJC_OLD_DISPATCH_PROTOTYPES +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wincompatible-library-redeclaration" # if defined(__i386__) @@ -182,6 +190,7 @@ OBJC_EXPORT void objc_msgSend_fp2ret(void /* id self, SEL op, ... */ ) OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0); +#pragma clang diagnostic pop # endif // !OBJC_OLD_DISPATCH_PROTOTYPES @@ -246,6 +255,8 @@ OBJC_EXPORT void objc_msgSend_fp2ret(id _Nullable self, SEL _Nonnull op, ...) * before being called. */ #if !OBJC_OLD_DISPATCH_PROTOTYPES +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wincompatible-library-redeclaration" OBJC_EXPORT void method_invoke(void /* id receiver, Method m, ... */ ) OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0); @@ -254,6 +265,7 @@ OBJC_EXPORT void method_invoke_stret(void /* id receiver, Method m, ... */ ) OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0) OBJC_ARM64_UNAVAILABLE; +#pragma clang diagnostic pop #else OBJC_EXPORT id _Nullable method_invoke(id _Nullable receiver, Method _Nonnull m, ...) @@ -282,6 +294,8 @@ method_invoke_stret(id _Nullable receiver, Method _Nonnull m, ...) * but may be compared to other IMP values. */ #if !OBJC_OLD_DISPATCH_PROTOTYPES +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wincompatible-library-redeclaration" OBJC_EXPORT void _objc_msgForward(void /* id receiver, SEL sel, ... */ ) OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0); @@ -290,6 +304,7 @@ OBJC_EXPORT void _objc_msgForward_stret(void /* id receiver, SEL sel, ... */ ) OBJC_AVAILABLE(10.6, 3.0, 9.0, 1.0, 2.0) OBJC_ARM64_UNAVAILABLE; +#pragma clang diagnostic pop #else OBJC_EXPORT id _Nullable _objc_msgForward(id _Nonnull receiver, SEL _Nonnull sel, ...) diff --git a/runtime/objc-abi.h b/runtime/objc-abi.h index bacf2e4..18430df 100644 --- a/runtime/objc-abi.h +++ b/runtime/objc-abi.h @@ -108,7 +108,7 @@ typedef struct objc_image_info { CorrectedSynthesize = 1<<4, // used for an old workaround, now ignored IsSimulated = 1<<5, // image compiled for a simulator platform HasCategoryClassProperties = 1<<6, // class properties in category_t - // not yet used = 1<<7 + OptimizedByDyldClosure = 1 << 7, // dyld (not the shared cache) optimized this. // 1 byte Swift unstable ABI version number SwiftUnstableVersionMaskShift = 8, @@ -138,6 +138,7 @@ typedef struct objc_image_info { bool requiresGC() const { return flags & RequiresGC; } bool optimizedByDyld() const { return flags & OptimizedByDyld; } bool hasCategoryClassProperties() const { return flags & HasCategoryClassProperties; } + bool optimizedByDyldClosure() const { return flags & OptimizedByDyldClosure; } bool containsSwift() const { return (flags & SwiftUnstableVersionMask) != 0; } uint32_t swiftUnstableVersion() const { return (flags & SwiftUnstableVersionMask) >> SwiftUnstableVersionMaskShift; } #endif @@ -458,7 +459,10 @@ OBJC_EXPORT const struct { char c; } objc_absolute_indexed_isa_index_shift #if !defined(OBJC_DECLARE_SYMBOLS) __OSX_AVAILABLE(10.0) __IOS_UNAVAILABLE __TVOS_UNAVAILABLE -__WATCHOS_UNAVAILABLE __BRIDGEOS_UNAVAILABLE +__WATCHOS_UNAVAILABLE +# ifndef __APPLE_BLEACH_SDK__ +__BRIDGEOS_UNAVAILABLE +# endif #endif OBJC_ROOT_CLASS @interface Object { diff --git a/runtime/objc-api.h b/runtime/objc-api.h index 7a7e397..284f24f 100644 --- a/runtime/objc-api.h +++ b/runtime/objc-api.h @@ -127,16 +127,62 @@ // Variadic IMP is comparable via OpaquePointer; non-variadic IMP isn't. # define OBJC_OLD_DISPATCH_PROTOTYPES 1 # else -# define OBJC_OLD_DISPATCH_PROTOTYPES 1 +# define OBJC_OLD_DISPATCH_PROTOTYPES 0 # endif #endif /* OBJC_AVAILABLE: shorthand for all-OS availability */ -#if !defined(OBJC_AVAILABLE) -# define OBJC_AVAILABLE(x, i, t, w, b) \ - __OSX_AVAILABLE(x) __IOS_AVAILABLE(i) __TVOS_AVAILABLE(t) \ - __WATCHOS_AVAILABLE(w) __BRIDGEOS_AVAILABLE(b) +#ifndef __APPLE_BLEACH_SDK__ +# if !defined(OBJC_AVAILABLE) +# define OBJC_AVAILABLE(x, i, t, w, b) \ + __OSX_AVAILABLE(x) __IOS_AVAILABLE(i) __TVOS_AVAILABLE(t) \ + __WATCHOS_AVAILABLE(w) __BRIDGEOS_AVAILABLE(b) +# endif +#else +# if !defined(OBJC_AVAILABLE) +# define OBJC_AVAILABLE(x, i, t, w, b) \ + __OSX_AVAILABLE(x) __IOS_AVAILABLE(i) __TVOS_AVAILABLE(t) \ + __WATCHOS_AVAILABLE(w) +# endif +#endif + + +/* OBJC_OSX_DEPRECATED_OTHERS_UNAVAILABLE: Deprecated on OS X, + * unavailable everywhere else. */ +#ifndef __APPLE_BLEACH_SDK__ +# if !defined(OBJC_OSX_DEPRECATED_OTHERS_UNAVAILABLE) +# define OBJC_OSX_DEPRECATED_OTHERS_UNAVAILABLE(_start, _dep, _msg) \ + __OSX_DEPRECATED(_start, _dep, _msg) \ + __IOS_UNAVAILABLE __TVOS_UNAVAILABLE \ + __WATCHOS_UNAVAILABLE __BRIDGEOS_UNAVAILABLE +# endif +#else +# if !defined(OBJC_OSX_DEPRECATED_OTHERS_UNAVAILABLE) +# define OBJC_OSX_DEPRECATED_OTHERS_UNAVAILABLE(_start, _dep, _msg) \ + __OSX_DEPRECATED(_start, _dep, _msg) \ + __IOS_UNAVAILABLE __TVOS_UNAVAILABLE \ + __WATCHOS_UNAVAILABLE +# endif +#endif + + +/* OBJC_OSX_AVAILABLE_OTHERS_UNAVAILABLE: Available on OS X, + * unavailable everywhere else. */ +#ifndef __APPLE_BLEACH_SDK__ +# if !defined(OBJC_OSX_AVAILABLE_OTHERS_UNAVAILABLE) +# define OBJC_OSX_AVAILABLE_OTHERS_UNAVAILABLE(vers) \ + __OSX_AVAILABLE(vers) \ + __IOS_UNAVAILABLE __TVOS_UNAVAILABLE \ + __WATCHOS_UNAVAILABLE __BRIDGEOS_UNAVAILABLE +# endif +#else +# if !defined(OBJC_OSX_AVAILABLE_OTHERS_UNAVAILABLE) +# define OBJC_OSX_AVAILABLE_OTHERS_UNAVAILABLE(vers) \ + __OSX_AVAILABLE(vers) \ + __IOS_UNAVAILABLE __TVOS_UNAVAILABLE \ + __WATCHOS_UNAVAILABLE +# endif #endif diff --git a/runtime/objc-auto.h b/runtime/objc-auto.h index 81c107e..364590f 100644 --- a/runtime/objc-auto.h +++ b/runtime/objc-auto.h @@ -70,65 +70,65 @@ enum { /* Out-of-line declarations */ OBJC_EXPORT void objc_collect(unsigned long options) - __OSX_DEPRECATED(10.6, 10.8, "it does nothing") __IOS_UNAVAILABLE __TVOS_UNAVAILABLE __WATCHOS_UNAVAILABLE __BRIDGEOS_UNAVAILABLE; + OBJC_OSX_DEPRECATED_OTHERS_UNAVAILABLE(10.6, 10.8, "it does nothing"); OBJC_EXPORT BOOL objc_collectingEnabled(void) - __OSX_DEPRECATED(10.5, 10.8, "it always returns NO") __IOS_UNAVAILABLE __TVOS_UNAVAILABLE __WATCHOS_UNAVAILABLE __BRIDGEOS_UNAVAILABLE; + OBJC_OSX_DEPRECATED_OTHERS_UNAVAILABLE(10.5, 10.8, "it always returns NO"); OBJC_EXPORT malloc_zone_t *objc_collectableZone(void) - __OSX_DEPRECATED(10.7, 10.8, "it always returns nil") __IOS_UNAVAILABLE __TVOS_UNAVAILABLE __WATCHOS_UNAVAILABLE __BRIDGEOS_UNAVAILABLE; + OBJC_OSX_DEPRECATED_OTHERS_UNAVAILABLE(10.7, 10.8, "it always returns nil"); OBJC_EXPORT void objc_setCollectionThreshold(size_t threshold) - __OSX_DEPRECATED(10.5, 10.8, "it does nothing") __IOS_UNAVAILABLE __TVOS_UNAVAILABLE __WATCHOS_UNAVAILABLE __BRIDGEOS_UNAVAILABLE; + OBJC_OSX_DEPRECATED_OTHERS_UNAVAILABLE(10.5, 10.8, "it does nothing"); OBJC_EXPORT void objc_setCollectionRatio(size_t ratio) - __OSX_DEPRECATED(10.5, 10.8, "it does nothing") __IOS_UNAVAILABLE __TVOS_UNAVAILABLE __WATCHOS_UNAVAILABLE __BRIDGEOS_UNAVAILABLE; + OBJC_OSX_DEPRECATED_OTHERS_UNAVAILABLE(10.5, 10.8, "it does nothing"); OBJC_EXPORT BOOL objc_atomicCompareAndSwapPtr(id predicate, id replacement, volatile id *objectLocation) - __OSX_DEPRECATED(10.6, 10.8, "use OSAtomicCompareAndSwapPtr instead") __IOS_UNAVAILABLE __TVOS_UNAVAILABLE __WATCHOS_UNAVAILABLE __BRIDGEOS_UNAVAILABLE OBJC_ARC_UNAVAILABLE; + OBJC_OSX_DEPRECATED_OTHERS_UNAVAILABLE(10.6, 10.8, "use OSAtomicCompareAndSwapPtr instead"); OBJC_EXPORT BOOL objc_atomicCompareAndSwapPtrBarrier(id predicate, id replacement, volatile id *objectLocation) - __OSX_DEPRECATED(10.6, 10.8, "use OSAtomicCompareAndSwapPtrBarrier instead") __IOS_UNAVAILABLE __TVOS_UNAVAILABLE __WATCHOS_UNAVAILABLE __BRIDGEOS_UNAVAILABLE OBJC_ARC_UNAVAILABLE; + OBJC_OSX_DEPRECATED_OTHERS_UNAVAILABLE(10.6, 10.8, "use OSAtomicCompareAndSwapPtrBarrier instead"); OBJC_EXPORT BOOL objc_atomicCompareAndSwapGlobal(id predicate, id replacement, volatile id *objectLocation) - __OSX_DEPRECATED(10.6, 10.8, "use OSAtomicCompareAndSwapPtr instead") __IOS_UNAVAILABLE __TVOS_UNAVAILABLE __WATCHOS_UNAVAILABLE __BRIDGEOS_UNAVAILABLE OBJC_ARC_UNAVAILABLE; + OBJC_OSX_DEPRECATED_OTHERS_UNAVAILABLE(10.6, 10.8, "use OSAtomicCompareAndSwapPtr instead"); OBJC_EXPORT BOOL objc_atomicCompareAndSwapGlobalBarrier(id predicate, id replacement, volatile id *objectLocation) - __OSX_DEPRECATED(10.6, 10.8, "use OSAtomicCompareAndSwapPtrBarrier instead") __IOS_UNAVAILABLE __TVOS_UNAVAILABLE __WATCHOS_UNAVAILABLE __BRIDGEOS_UNAVAILABLE OBJC_ARC_UNAVAILABLE; + OBJC_OSX_DEPRECATED_OTHERS_UNAVAILABLE(10.6, 10.8, "use OSAtomicCompareAndSwapPtrBarrier instead"); OBJC_EXPORT BOOL objc_atomicCompareAndSwapInstanceVariable(id predicate, id replacement, volatile id *objectLocation) - __OSX_DEPRECATED(10.6, 10.8, "use OSAtomicCompareAndSwapPtr instead") __IOS_UNAVAILABLE __TVOS_UNAVAILABLE __WATCHOS_UNAVAILABLE __BRIDGEOS_UNAVAILABLE OBJC_ARC_UNAVAILABLE; + OBJC_OSX_DEPRECATED_OTHERS_UNAVAILABLE(10.6, 10.8, "use OSAtomicCompareAndSwapPtr instead"); OBJC_EXPORT BOOL objc_atomicCompareAndSwapInstanceVariableBarrier(id predicate, id replacement, volatile id *objectLocation) - __OSX_DEPRECATED(10.6, 10.8, "use OSAtomicCompareAndSwapPtrBarrier instead") __IOS_UNAVAILABLE __TVOS_UNAVAILABLE __WATCHOS_UNAVAILABLE __BRIDGEOS_UNAVAILABLE OBJC_ARC_UNAVAILABLE; + OBJC_OSX_DEPRECATED_OTHERS_UNAVAILABLE(10.6, 10.8, "use OSAtomicCompareAndSwapPtrBarrier instead"); OBJC_EXPORT id objc_assign_strongCast(id val, id *dest) - __OSX_DEPRECATED(10.4, 10.8, "use a simple assignment instead") __IOS_UNAVAILABLE __TVOS_UNAVAILABLE __WATCHOS_UNAVAILABLE __BRIDGEOS_UNAVAILABLE; + OBJC_OSX_DEPRECATED_OTHERS_UNAVAILABLE(10.4, 10.8, "use a simple assignment instead"); OBJC_EXPORT id objc_assign_global(id val, id *dest) - __OSX_DEPRECATED(10.4, 10.8, "use a simple assignment instead") __IOS_UNAVAILABLE __TVOS_UNAVAILABLE __WATCHOS_UNAVAILABLE __BRIDGEOS_UNAVAILABLE; + OBJC_OSX_DEPRECATED_OTHERS_UNAVAILABLE(10.4, 10.8, "use a simple assignment instead"); OBJC_EXPORT id objc_assign_threadlocal(id val, id *dest) - __OSX_DEPRECATED(10.7, 10.8, "use a simple assignment instead") __IOS_UNAVAILABLE __TVOS_UNAVAILABLE __WATCHOS_UNAVAILABLE __BRIDGEOS_UNAVAILABLE; + OBJC_OSX_DEPRECATED_OTHERS_UNAVAILABLE(10.7, 10.8, "use a simple assignment instead"); OBJC_EXPORT id objc_assign_ivar(id value, id dest, ptrdiff_t offset) - __OSX_DEPRECATED(10.4, 10.8, "use a simple assignment instead") __IOS_UNAVAILABLE __TVOS_UNAVAILABLE __WATCHOS_UNAVAILABLE __BRIDGEOS_UNAVAILABLE; + OBJC_OSX_DEPRECATED_OTHERS_UNAVAILABLE(10.4, 10.8, "use a simple assignment instead"); OBJC_EXPORT void *objc_memmove_collectable(void *dst, const void *src, size_t size) - __OSX_DEPRECATED(10.4, 10.8, "use memmove instead") __IOS_UNAVAILABLE __TVOS_UNAVAILABLE __WATCHOS_UNAVAILABLE __BRIDGEOS_UNAVAILABLE; + OBJC_OSX_DEPRECATED_OTHERS_UNAVAILABLE(10.4, 10.8, "use memmove instead"); OBJC_EXPORT id objc_read_weak(id *location) - __OSX_DEPRECATED(10.5, 10.8, "use a simple read instead, or convert to zeroing __weak") __IOS_UNAVAILABLE __TVOS_UNAVAILABLE __WATCHOS_UNAVAILABLE __BRIDGEOS_UNAVAILABLE; + OBJC_OSX_DEPRECATED_OTHERS_UNAVAILABLE(10.5, 10.8, "use a simple read instead, or convert to zeroing __weak"); OBJC_EXPORT id objc_assign_weak(id value, id *location) - __OSX_DEPRECATED(10.5, 10.8, "use a simple assignment instead, or convert to zeroing __weak") __IOS_UNAVAILABLE __TVOS_UNAVAILABLE __WATCHOS_UNAVAILABLE __BRIDGEOS_UNAVAILABLE; + OBJC_OSX_DEPRECATED_OTHERS_UNAVAILABLE(10.5, 10.8, "use a simple assignment instead, or convert to zeroing __weak"); OBJC_EXPORT void objc_registerThreadWithCollector(void) - __OSX_DEPRECATED(10.6, 10.8, "it does nothing") __IOS_UNAVAILABLE __TVOS_UNAVAILABLE __WATCHOS_UNAVAILABLE __BRIDGEOS_UNAVAILABLE; + OBJC_OSX_DEPRECATED_OTHERS_UNAVAILABLE(10.6, 10.8, "it does nothing"); OBJC_EXPORT void objc_unregisterThreadWithCollector(void) - __OSX_DEPRECATED(10.6, 10.8, "it does nothing") __IOS_UNAVAILABLE __TVOS_UNAVAILABLE __WATCHOS_UNAVAILABLE __BRIDGEOS_UNAVAILABLE; + OBJC_OSX_DEPRECATED_OTHERS_UNAVAILABLE(10.6, 10.8, "it does nothing"); OBJC_EXPORT void objc_assertRegisteredThreadWithCollector(void) - __OSX_DEPRECATED(10.6, 10.8, "it does nothing") __IOS_UNAVAILABLE __TVOS_UNAVAILABLE __WATCHOS_UNAVAILABLE __BRIDGEOS_UNAVAILABLE; + OBJC_OSX_DEPRECATED_OTHERS_UNAVAILABLE(10.6, 10.8, "it does nothing"); OBJC_EXPORT void objc_clear_stack(unsigned long options) - __OSX_DEPRECATED(10.5, 10.8, "it does nothing") __IOS_UNAVAILABLE __TVOS_UNAVAILABLE __WATCHOS_UNAVAILABLE __BRIDGEOS_UNAVAILABLE; + OBJC_OSX_DEPRECATED_OTHERS_UNAVAILABLE(10.5, 10.8, "it does nothing"); OBJC_EXPORT BOOL objc_is_finalized(void *ptr) - __OSX_DEPRECATED(10.4, 10.8, "it always returns NO") __IOS_UNAVAILABLE __TVOS_UNAVAILABLE __WATCHOS_UNAVAILABLE __BRIDGEOS_UNAVAILABLE; + OBJC_OSX_DEPRECATED_OTHERS_UNAVAILABLE(10.4, 10.8, "it always returns NO"); OBJC_EXPORT void objc_finalizeOnMainThread(Class cls) - __OSX_DEPRECATED(10.5, 10.5, "it does nothing") __IOS_UNAVAILABLE __TVOS_UNAVAILABLE __WATCHOS_UNAVAILABLE __BRIDGEOS_UNAVAILABLE; + OBJC_OSX_DEPRECATED_OTHERS_UNAVAILABLE(10.5, 10.5, "it does nothing"); OBJC_EXPORT BOOL objc_collecting_enabled(void) - __OSX_DEPRECATED(10.4, 10.5, "it always returns NO") __IOS_UNAVAILABLE __TVOS_UNAVAILABLE __WATCHOS_UNAVAILABLE __BRIDGEOS_UNAVAILABLE; + OBJC_OSX_DEPRECATED_OTHERS_UNAVAILABLE(10.4, 10.5, "it always returns NO"); OBJC_EXPORT void objc_set_collection_threshold(size_t threshold) - __OSX_DEPRECATED(10.4, 10.5, "it does nothing") __IOS_UNAVAILABLE __TVOS_UNAVAILABLE __WATCHOS_UNAVAILABLE __BRIDGEOS_UNAVAILABLE; + OBJC_OSX_DEPRECATED_OTHERS_UNAVAILABLE(10.4, 10.5, "it does nothing"); OBJC_EXPORT void objc_set_collection_ratio(size_t ratio) - __OSX_DEPRECATED(10.4, 10.5, "it does nothing") __IOS_UNAVAILABLE __TVOS_UNAVAILABLE __WATCHOS_UNAVAILABLE __BRIDGEOS_UNAVAILABLE; + OBJC_OSX_DEPRECATED_OTHERS_UNAVAILABLE(10.4, 10.5, "it does nothing"); OBJC_EXPORT void objc_start_collector_thread(void) - __OSX_DEPRECATED(10.4, 10.5, "it does nothing") __IOS_UNAVAILABLE __TVOS_UNAVAILABLE __WATCHOS_UNAVAILABLE __BRIDGEOS_UNAVAILABLE; + OBJC_OSX_DEPRECATED_OTHERS_UNAVAILABLE(10.4, 10.5, "it does nothing"); OBJC_EXPORT void objc_startCollectorThread(void) - __OSX_DEPRECATED(10.5, 10.7, "it does nothing") __IOS_UNAVAILABLE __TVOS_UNAVAILABLE __WATCHOS_UNAVAILABLE __BRIDGEOS_UNAVAILABLE; + OBJC_OSX_DEPRECATED_OTHERS_UNAVAILABLE(10.5, 10.7, "it does nothing"); OBJC_EXPORT id objc_allocate_object(Class cls, int extra) - __OSX_DEPRECATED(10.4, 10.4, "use class_createInstance instead") __IOS_UNAVAILABLE __TVOS_UNAVAILABLE __WATCHOS_UNAVAILABLE __BRIDGEOS_UNAVAILABLE; + OBJC_OSX_DEPRECATED_OTHERS_UNAVAILABLE(10.4, 10.4, "use class_createInstance instead"); /* !defined(OBJC_NO_GC) */ diff --git a/runtime/objc-block-trampolines.mm b/runtime/objc-block-trampolines.mm index 21879b7..9dea652 100644 --- a/runtime/objc-block-trampolines.mm +++ b/runtime/objc-block-trampolines.mm @@ -103,15 +103,15 @@ uintptr_t address() { void check() { #if DEBUG - assert(impl.address() == textSegment + PAGE_MAX_SIZE); - assert(impl.address() % PAGE_SIZE == 0); // not PAGE_MAX_SIZE + ASSERT(impl.address() == textSegment + PAGE_MAX_SIZE); + ASSERT(impl.address() % PAGE_SIZE == 0); // not PAGE_MAX_SIZE assert(impl.address() + PAGE_MAX_SIZE == last.address() + SLOT_SIZE); - assert(last.address()+8 < textSegment + textSegmentSize); - assert((last.address() - start.address()) % SLOT_SIZE == 0); + ASSERT(last.address()+8 < textSegment + textSegmentSize); + ASSERT((last.address() - start.address()) % SLOT_SIZE == 0); # if SUPPORT_STRET - assert(impl_stret.address() == textSegment + 2*PAGE_MAX_SIZE); - assert(impl_stret.address() % PAGE_SIZE == 0); // not PAGE_MAX_SIZE + ASSERT(impl_stret.address() == textSegment + 2*PAGE_MAX_SIZE); + ASSERT(impl_stret.address() % PAGE_SIZE == 0); // not PAGE_MAX_SIZE assert(impl_stret.address() + PAGE_MAX_SIZE == last_stret.address() + SLOT_SIZE); assert(start.address() - impl.address() == @@ -272,7 +272,7 @@ static bool validIndex(uintptr_t index) { } Payload *payload(uintptr_t index) { - assert(validIndex(index)); + ASSERT(validIndex(index)); return (Payload *)((char *)this + index*slotSize()); } @@ -284,7 +284,7 @@ uintptr_t trampolinesForMode(int aMode) { } IMP trampoline(int aMode, uintptr_t index) { - assert(validIndex(index)); + ASSERT(validIndex(index)); char *base = (char *)trampolinesForMode(aMode); char *imp = base + index*slotSize(); #if __arm__ @@ -310,8 +310,8 @@ uintptr_t indexForTrampoline(uintptr_t tramp) { } static void check() { - assert(TrampolineBlockPageGroup::headerSize() >= sizeof(TrampolineBlockPageGroup)); - assert(TrampolineBlockPageGroup::headerSize() % TrampolineBlockPageGroup::slotSize() == 0); + ASSERT(TrampolineBlockPageGroup::headerSize() >= sizeof(TrampolineBlockPageGroup)); + ASSERT(TrampolineBlockPageGroup::headerSize() % TrampolineBlockPageGroup::slotSize() == 0); } }; @@ -347,7 +347,7 @@ static void check() { // We assume that our code begins on the second TEXT page, but are robust // against other additions to the end of the TEXT segment. - assert(HeadPageGroup == nil || HeadPageGroup->nextAvailablePage == nil); + ASSERT(HeadPageGroup == nil || HeadPageGroup->nextAvailablePage == nil); auto textSource = Trampolines.textSegment(); auto textSourceSize = Trampolines.textSegmentSize(); @@ -443,12 +443,37 @@ static void check() { if (_Block_has_signature(block) && _Block_use_stret(block)) aMode = ReturnValueOnStackArgumentMode; #else - assert(! (_Block_has_signature(block) && _Block_use_stret(block))); + ASSERT(! (_Block_has_signature(block) && _Block_use_stret(block))); #endif return aMode; } +/// Initialize the trampoline machinery. Normally this does nothing, as +/// everything is initialized lazily, but for certain processes we eagerly load +/// the trampolines dylib. +void +_imp_implementationWithBlock_init(void) +{ +#if TARGET_OS_OSX + // Eagerly load libobjc-trampolines.dylib in certain processes. Some + // programs (most notably QtWebEngineProcess used by older versions of + // embedded Chromium) enable a highly restrictive sandbox profile which + // blocks access to that dylib. If anything calls + // imp_implementationWithBlock (as AppKit has started doing) then we'll + // crash trying to load it. Loading it here sets it up before the sandbox + // profile is enabled and blocks it. + // + // This fixes EA Origin (rdar://problem/50813789) + // and Steam (rdar://problem/55286131) + if (__progname && + (strcmp(__progname, "QtWebEngineProcess") == 0 || + strcmp(__progname, "Steam Helper") == 0)) { + Trampolines.Initialize(); + } +#endif +} + // `block` must already have been copied IMP @@ -460,7 +485,7 @@ static void check() { getOrAllocatePageGroupWithNextAvailable(); uintptr_t index = pageGroup->nextAvailable; - assert(index >= pageGroup->startIndex() && index < pageGroup->endIndex()); + ASSERT(index >= pageGroup->startIndex() && index < pageGroup->endIndex()); TrampolineBlockPageGroup::Payload *payload = pageGroup->payload(index); uintptr_t nextAvailableIndex = payload->nextAvailable; diff --git a/runtime/objc-cache-old.h b/runtime/objc-cache-old.h index 7f0245e..995e2e3 100644 --- a/runtime/objc-cache-old.h +++ b/runtime/objc-cache-old.h @@ -31,6 +31,7 @@ __BEGIN_DECLS extern IMP _cache_getImp(Class cls, SEL sel); extern Method _cache_getMethod(Class cls, SEL sel, IMP objc_msgForward_internal_imp); +extern void cache_init(void); extern void flush_cache(Class cls); extern bool _cache_fill(Class cls, Method meth, SEL sel); extern void _cache_addForwardEntry(Class cls, SEL sel); diff --git a/runtime/objc-cache-old.mm b/runtime/objc-cache-old.mm index b785a6a..fed884a 100644 --- a/runtime/objc-cache-old.mm +++ b/runtime/objc-cache-old.mm @@ -693,8 +693,14 @@ static uintptr_t _get_pc_for_thread(thread_t thread) * reading function is in progress because it might still be using * the garbage memory. **********************************************************************/ -extern "C" uintptr_t objc_entryPoints[]; -extern "C" uintptr_t objc_exitPoints[]; +typedef struct { + uint64_t location; + unsigned short length; + unsigned short recovery_offs; + unsigned int flags; +} task_restartable_range_t; + +extern "C" task_restartable_range_t objc_restartableRanges[]; static int _collecting_in_critical(void) { @@ -707,7 +713,7 @@ static int _collecting_in_critical(void) kern_return_t ret; int result; - mach_port_t mythread = pthread_mach_thread_np(pthread_self()); + mach_port_t mythread = pthread_mach_thread_np(objc_thread_self()); // Get a list of all the threads in the current task ret = task_threads (mach_task_self (), &threads, &number); @@ -738,10 +744,11 @@ static int _collecting_in_critical(void) } // Check whether it is in the cache lookup code - for (region = 0; objc_entryPoints[region] != 0; region++) + for (region = 0; objc_restartableRanges[region].location != 0; region++) { - if ((pc >= objc_entryPoints[region]) && - (pc <= objc_exitPoints[region])) + uint32_t loc = (uint32_t)objc_restartableRanges[region].location; + if ((pc > loc) && + (pc - loc) < objc_restartableRanges[region].length) { result = TRUE; goto done; @@ -1788,6 +1795,9 @@ void _class_printMethodCacheStatistics(void) #endif +void cache_init() +{ +} // !__OBJC2__ #endif diff --git a/runtime/objc-cache.h b/runtime/objc-cache.h index de348a0..e0448e7 100644 --- a/runtime/objc-cache.h +++ b/runtime/objc-cache.h @@ -6,6 +6,8 @@ __BEGIN_DECLS +extern void cache_init(void); + extern IMP cache_getImp(Class cls, SEL sel); extern void cache_fill(Class cls, SEL sel, IMP imp, id receiver); diff --git a/runtime/objc-cache.mm b/runtime/objc-cache.mm index 7602fe0..4735a2d 100644 --- a/runtime/objc-cache.mm +++ b/runtime/objc-cache.mm @@ -90,7 +90,9 @@ /* Initial cache bucket count. INIT_CACHE_SIZE must be a power of two. */ enum { INIT_CACHE_SIZE_LOG2 = 2, - INIT_CACHE_SIZE = (1 << INIT_CACHE_SIZE_LOG2) + INIT_CACHE_SIZE = (1 << INIT_CACHE_SIZE_LOG2), + MAX_CACHE_SIZE_LOG2 = 16, + MAX_CACHE_SIZE = (1 << MAX_CACHE_SIZE_LOG2), }; static void cache_collect_free(struct bucket_t *data, mask_t capacity); @@ -147,8 +149,14 @@ static void recordDeadCache(mask_t capacity) "\n .globl __objc_empty_vtable" "\n .set __objc_empty_vtable, 0" "\n .globl __objc_empty_cache" +#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_LOW_4 + "\n .align 4" + "\n L__objc_empty_cache: .space " stringize2(EMPTY_BYTES) + "\n .set __objc_empty_cache, L__objc_empty_cache + 0xf" +#else "\n .align 3" "\n __objc_empty_cache: .space " stringize2(EMPTY_BYTES) +#endif ); @@ -173,30 +181,14 @@ static inline mask_t cache_next(mask_t i, mask_t mask) { #endif -// copied from dispatch_atomic_maximally_synchronizing_barrier -// fixme verify that this barrier hack does in fact work here -#if __x86_64__ -#define mega_barrier() \ - do { unsigned long _clbr; __asm__ __volatile__( \ - "cpuid" \ - : "=a" (_clbr) : "0" (0) : "rbx", "rcx", "rdx", "cc", "memory" \ - ); } while(0) - -#elif __i386__ -#define mega_barrier() \ - do { unsigned long _clbr; __asm__ __volatile__( \ - "cpuid" \ - : "=a" (_clbr) : "0" (0) : "ebx", "ecx", "edx", "cc", "memory" \ - ); } while(0) - -#elif __arm__ || __arm64__ +// mega_barrier doesn't really work, but it works enough on ARM that +// we leave well enough alone and keep using it there. +#if __arm__ #define mega_barrier() \ __asm__ __volatile__( \ "dsb ish" \ : : : "memory") -#else -#error unknown architecture #endif #if __arm64__ @@ -250,39 +242,38 @@ static inline mask_t cache_hash(SEL sel, mask_t mask) cache_t *getCache(Class cls) { - assert(cls); + ASSERT(cls); return &cls->cache; } #if __arm64__ -template -void bucket_t::set(SEL newSel, IMP newImp) +template +void bucket_t::set(SEL newSel, IMP newImp, Class cls) { - assert(_sel == 0 || _sel == newSel); + ASSERT(_sel.load(memory_order::memory_order_relaxed) == 0 || + _sel.load(memory_order::memory_order_relaxed) == newSel); static_assert(offsetof(bucket_t,_imp) == 0 && offsetof(bucket_t,_sel) == sizeof(void *), "bucket_t layout doesn't match arm64 bucket_t::set()"); - uintptr_t signedImp = signIMP(newImp, newSel); + uintptr_t encodedImp = (impEncoding == Encoded + ? encodeImp(newImp, newSel, cls) + : (uintptr_t)newImp); - if (atomicity == Atomic) { - // LDP/STP guarantees that all observers get - // either imp/sel or newImp/newSel - stp(signedImp, (uintptr_t)newSel, this); - } else { - _sel = newSel; - _imp = signedImp; - } + // LDP/STP guarantees that all observers get + // either imp/sel or newImp/newSel + stp(encodedImp, (uintptr_t)newSel, this); } #else -template -void bucket_t::set(SEL newSel, IMP newImp) +template +void bucket_t::set(SEL newSel, IMP newImp, Class cls) { - assert(_sel == 0 || _sel == newSel); + ASSERT(_sel.load(memory_order::memory_order_relaxed) == 0 || + _sel.load(memory_order::memory_order_relaxed) == newSel); // objc_msgSend uses sel and imp with no locks. // It is safe for objc_msgSend to see new imp but NULL sel @@ -290,18 +281,33 @@ static inline mask_t cache_hash(SEL sel, mask_t mask) // It is unsafe for objc_msgSend to see old imp and new sel. // Therefore we write new imp, wait a lot, then write new sel. - _imp = (uintptr_t)newImp; - - if (_sel != newSel) { - if (atomicity == Atomic) { + uintptr_t newIMP = (impEncoding == Encoded + ? encodeImp(newImp, newSel, cls) + : (uintptr_t)newImp); + + if (atomicity == Atomic) { + _imp.store(newIMP, memory_order::memory_order_relaxed); + + if (_sel.load(memory_order::memory_order_relaxed) != newSel) { +#ifdef __arm__ mega_barrier(); + _sel.store(newSel, memory_order::memory_order_relaxed); +#elif __x86_64__ || __i386__ + _sel.store(newSel, memory_order::memory_order_release); +#else +#error Don't know how to do bucket_t::set on this architecture. +#endif } - _sel = newSel; + } else { + _imp.store(newIMP, memory_order::memory_order_relaxed); + _sel.store(newSel, memory_order::memory_order_relaxed); } } #endif +#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_OUTLINED + void cache_t::setBucketsAndMask(struct bucket_t *newBuckets, mask_t newMask) { // objc_msgSend uses mask and buckets with no locks. @@ -311,47 +317,149 @@ static inline mask_t cache_hash(SEL sel, mask_t mask) // Therefore we write new buckets, wait a lot, then write new mask. // objc_msgSend reads mask first, then buckets. +#ifdef __arm__ // ensure other threads see buckets contents before buckets pointer mega_barrier(); - _buckets = newBuckets; + _buckets.store(newBuckets, memory_order::memory_order_relaxed); // ensure other threads see new buckets before new mask mega_barrier(); - _mask = newMask; + _mask.store(newMask, memory_order::memory_order_relaxed); _occupied = 0; +#elif __x86_64__ || i386 + // ensure other threads see buckets contents before buckets pointer + _buckets.store(newBuckets, memory_order::memory_order_release); + + // ensure other threads see new buckets before new mask + _mask.store(newMask, memory_order::memory_order_release); + _occupied = 0; +#else +#error Don't know how to do setBucketsAndMask on this architecture. +#endif } +struct bucket_t *cache_t::emptyBuckets() +{ + return (bucket_t *)&_objc_empty_cache; +} struct bucket_t *cache_t::buckets() { - return _buckets; + return _buckets.load(memory_order::memory_order_relaxed); } mask_t cache_t::mask() { - return _mask; + return _mask.load(memory_order::memory_order_relaxed); } -mask_t cache_t::occupied() +void cache_t::initializeToEmpty() { - return _occupied; + bzero(this, sizeof(*this)); + _buckets.store((bucket_t *)&_objc_empty_cache, memory_order::memory_order_relaxed); } -void cache_t::incrementOccupied() +#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16 + +void cache_t::setBucketsAndMask(struct bucket_t *newBuckets, mask_t newMask) { - _occupied++; + uintptr_t buckets = (uintptr_t)newBuckets; + uintptr_t mask = (uintptr_t)newMask; + + ASSERT(buckets <= bucketsMask); + ASSERT(mask <= maxMask); + + _maskAndBuckets.store(((uintptr_t)newMask << maskShift) | (uintptr_t)newBuckets, std::memory_order_relaxed); + _occupied = 0; +} + +struct bucket_t *cache_t::emptyBuckets() +{ + return (bucket_t *)&_objc_empty_cache; +} + +struct bucket_t *cache_t::buckets() +{ + uintptr_t maskAndBuckets = _maskAndBuckets.load(memory_order::memory_order_relaxed); + return (bucket_t *)(maskAndBuckets & bucketsMask); +} + +mask_t cache_t::mask() +{ + uintptr_t maskAndBuckets = _maskAndBuckets.load(memory_order::memory_order_relaxed); + return maskAndBuckets >> maskShift; } void cache_t::initializeToEmpty() { bzero(this, sizeof(*this)); - _buckets = (bucket_t *)&_objc_empty_cache; + _maskAndBuckets.store((uintptr_t)&_objc_empty_cache, std::memory_order_relaxed); +} + +#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_LOW_4 + +void cache_t::setBucketsAndMask(struct bucket_t *newBuckets, mask_t newMask) +{ + uintptr_t buckets = (uintptr_t)newBuckets; + unsigned mask = (unsigned)newMask; + + ASSERT(buckets == (buckets & bucketsMask)); + ASSERT(mask <= 0xffff); + + // The shift amount is equal to the number of leading zeroes in + // the last 16 bits of mask. Count all the leading zeroes, then + // subtract to ignore the top half. + uintptr_t maskShift = __builtin_clz(mask) - (sizeof(mask) * CHAR_BIT - 16); + ASSERT(mask == (0xffff >> maskShift)); + + _maskAndBuckets.store(buckets | maskShift, memory_order::memory_order_relaxed); + _occupied = 0; + + ASSERT(this->buckets() == newBuckets); + ASSERT(this->mask() == newMask); +} + +struct bucket_t *cache_t::emptyBuckets() +{ + return (bucket_t *)((uintptr_t)&_objc_empty_cache & bucketsMask); +} + +struct bucket_t *cache_t::buckets() +{ + uintptr_t maskAndBuckets = _maskAndBuckets.load(memory_order::memory_order_relaxed); + return (bucket_t *)(maskAndBuckets & bucketsMask); +} + +mask_t cache_t::mask() +{ + uintptr_t maskAndBuckets = _maskAndBuckets.load(memory_order::memory_order_relaxed); + uintptr_t maskShift = (maskAndBuckets & maskMask); + return 0xffff >> maskShift; +} + +void cache_t::initializeToEmpty() +{ + bzero(this, sizeof(*this)); + _maskAndBuckets.store((uintptr_t)&_objc_empty_cache, std::memory_order_relaxed); +} + +#else +#error Unknown cache mask storage type. +#endif + +mask_t cache_t::occupied() +{ + return _occupied; } +void cache_t::incrementOccupied() +{ + _occupied++; +} -mask_t cache_t::capacity() +unsigned cache_t::capacity() { return mask() ? mask()+1 : 0; } @@ -384,10 +492,10 @@ static inline mask_t cache_hash(SEL sel, mask_t mask) #if __arm__ // End marker's sel is 1 and imp points BEFORE the first bucket. // This saves an instruction in objc_msgSend. - end->set((SEL)(uintptr_t)1, (IMP)(newBuckets - 1)); + end->set((SEL)(uintptr_t)1, (IMP)(newBuckets - 1), nil); #else // End marker's sel is 1 and imp points to the first bucket. - end->set((SEL)(uintptr_t)1, (IMP)newBuckets); + end->set((SEL)(uintptr_t)1, (IMP)newBuckets, nil); #endif if (PrintCaches) recordNewCache(newCapacity); @@ -414,13 +522,17 @@ static inline mask_t cache_hash(SEL sel, mask_t mask) bucket_t *emptyBucketsForCapacity(mask_t capacity, bool allocate = true) { +#if CONFIG_USE_CACHE_LOCK cacheUpdateLock.assertLocked(); +#else + runtimeLock.assertLocked(); +#endif size_t bytes = cache_t::bytesForCapacity(capacity); // Use _objc_empty_cache if the buckets is small enough. if (bytes <= EMPTY_BYTES) { - return (bucket_t *)&_objc_empty_cache; + return cache_t::emptyBuckets(); } // Use shared empty buckets allocated on the heap. @@ -465,11 +577,9 @@ static inline mask_t cache_hash(SEL sel, mask_t mask) return !isConstantEmptyCache(); } - -void cache_t::reallocate(mask_t oldCapacity, mask_t newCapacity) +ALWAYS_INLINE +void cache_t::reallocate(mask_t oldCapacity, mask_t newCapacity, bool freeOld) { - bool freeOld = canBeFreed(); - bucket_t *oldBuckets = buckets(); bucket_t *newBuckets = allocateBuckets(newCapacity); @@ -477,14 +587,13 @@ static inline mask_t cache_hash(SEL sel, mask_t mask) // This is thought to save cache memory at the cost of extra cache fills. // fixme re-measure this - assert(newCapacity > 0); - assert((uintptr_t)(mask_t)(newCapacity-1) == newCapacity-1); + ASSERT(newCapacity > 0); + ASSERT((uintptr_t)(mask_t)(newCapacity-1) == newCapacity-1); setBucketsAndMask(newBuckets, newCapacity - 1); if (freeOld) { cache_collect_free(oldBuckets, oldCapacity); - cache_collect(false); } } @@ -496,16 +605,35 @@ static inline mask_t cache_hash(SEL sel, mask_t mask) ("Method cache corrupted. This may be a message to an " "invalid object, or a memory error somewhere else."); cache_t *cache = &isa->cache; +#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_OUTLINED + bucket_t *buckets = cache->_buckets.load(memory_order::memory_order_relaxed); _objc_inform_now_and_on_crash ("%s %p, SEL %p, isa %p, cache %p, buckets %p, " "mask 0x%x, occupied 0x%x", receiver ? "receiver" : "unused", receiver, - sel, isa, cache, cache->_buckets, - cache->_mask, cache->_occupied); + sel, isa, cache, buckets, + cache->_mask.load(memory_order::memory_order_relaxed), + cache->_occupied); _objc_inform_now_and_on_crash ("%s %zu bytes, buckets %zu bytes", receiver ? "receiver" : "unused", malloc_size(receiver), - malloc_size(cache->_buckets)); + malloc_size(buckets)); +#elif (CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16 || \ + CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_LOW_4) + uintptr_t maskAndBuckets = cache->_maskAndBuckets.load(memory_order::memory_order_relaxed); + _objc_inform_now_and_on_crash + ("%s %p, SEL %p, isa %p, cache %p, buckets and mask 0x%lx, " + "occupied 0x%x", + receiver ? "receiver" : "unused", receiver, + sel, isa, cache, maskAndBuckets, + cache->_occupied); + _objc_inform_now_and_on_crash + ("%s %zu bytes, buckets %zu bytes", + receiver ? "receiver" : "unused", malloc_size(receiver), + malloc_size(cache->buckets())); +#else +#error Unknown cache mask storage type. +#endif _objc_inform_now_and_on_crash ("selector '%s'", sel_getName(sel)); _objc_inform_now_and_on_crash @@ -515,88 +643,75 @@ static inline mask_t cache_hash(SEL sel, mask_t mask) "invalid object, or a memory error somewhere else."); } - -bucket_t * cache_t::find(SEL s, id receiver) -{ - assert(s != 0); - - bucket_t *b = buckets(); - mask_t m = mask(); - mask_t begin = cache_hash(s, m); - mask_t i = begin; - do { - if (b[i].sel() == 0 || b[i].sel() == s) { - return &b[i]; - } - } while ((i = cache_next(i, m)) != begin); - - // hack - Class cls = (Class)((uintptr_t)this - offsetof(objc_class, cache)); - cache_t::bad_cache(receiver, (SEL)s, cls); -} - - -void cache_t::expand() -{ - cacheUpdateLock.assertLocked(); - - uint32_t oldCapacity = capacity(); - uint32_t newCapacity = oldCapacity ? oldCapacity*2 : INIT_CACHE_SIZE; - - if ((uint32_t)(mask_t)newCapacity != newCapacity) { - // mask overflow - can't grow further - // fixme this wastes one bit of mask - newCapacity = oldCapacity; - } - - reallocate(oldCapacity, newCapacity); -} - - -static void cache_fill_nolock(Class cls, SEL sel, IMP imp, id receiver) +ALWAYS_INLINE +void cache_t::insert(Class cls, SEL sel, IMP imp, id receiver) { +#if CONFIG_USE_CACHE_LOCK cacheUpdateLock.assertLocked(); +#else + runtimeLock.assertLocked(); +#endif - // Never cache before +initialize is done - if (!cls->isInitialized()) return; - - // Make sure the entry wasn't added to the cache by some other thread - // before we grabbed the cacheUpdateLock. - if (cache_getImp(cls, sel)) return; - - cache_t *cache = getCache(cls); + ASSERT(sel != 0 && cls->isInitialized()); // Use the cache as-is if it is less than 3/4 full - mask_t newOccupied = cache->occupied() + 1; - mask_t capacity = cache->capacity(); - if (cache->isConstantEmptyCache()) { + mask_t newOccupied = occupied() + 1; + unsigned oldCapacity = capacity(), capacity = oldCapacity; + if (slowpath(isConstantEmptyCache())) { // Cache is read-only. Replace it. - cache->reallocate(capacity, capacity ?: INIT_CACHE_SIZE); + if (!capacity) capacity = INIT_CACHE_SIZE; + reallocate(oldCapacity, capacity, /* freeOld */false); } - else if (newOccupied <= capacity / 4 * 3) { + else if (fastpath(newOccupied <= capacity / 4 * 3)) { // Cache is less than 3/4 full. Use it as-is. } else { - // Cache is too full. Expand it. - cache->expand(); + capacity = capacity ? capacity * 2 : INIT_CACHE_SIZE; + if (capacity > MAX_CACHE_SIZE) { + capacity = MAX_CACHE_SIZE; + } + reallocate(oldCapacity, capacity, true); } + bucket_t *b = buckets(); + mask_t m = capacity - 1; + mask_t begin = cache_hash(sel, m); + mask_t i = begin; + // Scan for the first unused slot and insert there. - // There is guaranteed to be an empty slot because the + // There is guaranteed to be an empty slot because the // minimum size is 4 and we resized at 3/4 full. - bucket_t *bucket = cache->find(sel, receiver); - if (bucket->sel() == 0) cache->incrementOccupied(); - bucket->set(sel, imp); + do { + if (fastpath(b[i].sel() == 0)) { + incrementOccupied(); + b[i].set(sel, imp, cls); + return; + } + if (b[i].sel() == sel) { + // The entry was added to the cache by some other thread + // before we grabbed the cacheUpdateLock. + return; + } + } while (fastpath((i = cache_next(i, m)) != begin)); + + cache_t::bad_cache(receiver, (SEL)sel, cls); } void cache_fill(Class cls, SEL sel, IMP imp, id receiver) { + runtimeLock.assertLocked(); + #if !DEBUG_TASK_THREADS - mutex_locker_t lock(cacheUpdateLock); - cache_fill_nolock(cls, sel, imp, receiver); + // Never cache before +initialize is done + if (cls->isInitialized()) { + cache_t *cache = getCache(cls); +#if CONFIG_USE_CACHE_LOCK + mutex_locker_t lock(cacheUpdateLock); +#endif + cache->insert(cls, sel, imp, receiver); + } #else _collecting_in_critical(); - return; #endif } @@ -605,7 +720,11 @@ void cache_fill(Class cls, SEL sel, IMP imp, id receiver) // This must not shrink the cache - that breaks the lock-free scheme. void cache_erase_nolock(Class cls) { +#if CONFIG_USE_CACHE_LOCK cacheUpdateLock.assertLocked(); +#else + runtimeLock.assertLocked(); +#endif cache_t *cache = getCache(cls); @@ -616,14 +735,17 @@ void cache_erase_nolock(Class cls) cache->setBucketsAndMask(buckets, capacity - 1); // also clears occupied cache_collect_free(oldBuckets, capacity); - cache_collect(false); } } void cache_delete(Class cls) { +#if CONFIG_USE_CACHE_LOCK mutex_locker_t lock(cacheUpdateLock); +#else + runtimeLock.assertLocked(); +#endif if (cls->cache.canBeFreed()) { if (PrintCaches) recordDeadCache(cls->cache.capacity()); free(cls->cache.buckets()); @@ -669,7 +791,7 @@ static uintptr_t _get_pc_for_thread(thread_t thread) arm_thread_state64_t state; unsigned int count = ARM_THREAD_STATE64_COUNT; kern_return_t okay = thread_get_state (thread, ARM_THREAD_STATE64, (thread_state_t)&state, &count); - return (okay == KERN_SUCCESS) ? arm_thread_state64_get_pc(state) : PC_SENTINEL; + return (okay == KERN_SUCCESS) ? (uintptr_t)arm_thread_state64_get_pc(state) : PC_SENTINEL; } #else { @@ -686,21 +808,64 @@ static uintptr_t _get_pc_for_thread(thread_t thread) * reading function is in progress because it might still be using * the garbage memory. **********************************************************************/ -extern "C" uintptr_t objc_entryPoints[]; -extern "C" uintptr_t objc_exitPoints[]; +#if HAVE_TASK_RESTARTABLE_RANGES +#include +#else +typedef struct { + uint64_t location; + unsigned short length; + unsigned short recovery_offs; + unsigned int flags; +} task_restartable_range_t; +#endif + +extern "C" task_restartable_range_t objc_restartableRanges[]; + +#if HAVE_TASK_RESTARTABLE_RANGES +static bool shouldUseRestartableRanges = true; +#endif + +void cache_init() +{ +#if HAVE_TASK_RESTARTABLE_RANGES + mach_msg_type_number_t count = 0; + kern_return_t kr; + + while (objc_restartableRanges[count].location) { + count++; + } + + kr = task_restartable_ranges_register(mach_task_self(), + objc_restartableRanges, count); + if (kr == KERN_SUCCESS) return; + _objc_fatal("task_restartable_ranges_register failed (result 0x%x: %s)", + kr, mach_error_string(kr)); +#endif // HAVE_TASK_RESTARTABLE_RANGES +} static int _collecting_in_critical(void) { #if TARGET_OS_WIN32 return TRUE; -#else +#elif HAVE_TASK_RESTARTABLE_RANGES + // Only use restartable ranges if we registered them earlier. + if (shouldUseRestartableRanges) { + kern_return_t kr = task_restartable_ranges_synchronize(mach_task_self()); + if (kr == KERN_SUCCESS) return FALSE; + _objc_fatal("task_restartable_ranges_synchronize failed (result 0x%x: %s)", + kr, mach_error_string(kr)); + } +#endif // !HAVE_TASK_RESTARTABLE_RANGES + + // Fallthrough if we didn't use restartable ranges. + thread_act_port_array_t threads; unsigned number; unsigned count; kern_return_t ret; int result; - mach_port_t mythread = pthread_mach_thread_np(pthread_self()); + mach_port_t mythread = pthread_mach_thread_np(objc_thread_self()); // Get a list of all the threads in the current task #if !DEBUG_TASK_THREADS @@ -736,10 +901,11 @@ static int _collecting_in_critical(void) } // Check whether it is in the cache lookup code - for (region = 0; objc_entryPoints[region] != 0; region++) + for (region = 0; objc_restartableRanges[region].location != 0; region++) { - if ((pc >= objc_entryPoints[region]) && - (pc <= objc_exitPoints[region])) + uint64_t loc = objc_restartableRanges[region].location; + if ((pc > loc) && + (pc - loc < (uint64_t)objc_restartableRanges[region].length)) { result = TRUE; goto done; @@ -758,7 +924,6 @@ static int _collecting_in_critical(void) // Return our finding return result; -#endif } @@ -819,13 +984,18 @@ static void _garbage_make_room(void) **********************************************************************/ static void cache_collect_free(bucket_t *data, mask_t capacity) { +#if CONFIG_USE_CACHE_LOCK cacheUpdateLock.assertLocked(); +#else + runtimeLock.assertLocked(); +#endif if (PrintCaches) recordDeadCache(capacity); _garbage_make_room (); garbage_byte_size += cache_t::bytesForCapacity(capacity); garbage_refs[garbage_count++] = data; + cache_collect(false); } @@ -836,7 +1006,11 @@ static void cache_collect_free(bucket_t *data, mask_t capacity) **********************************************************************/ void cache_collect(bool collectALot) { +#if CONFIG_USE_CACHE_LOCK cacheUpdateLock.assertLocked(); +#else + runtimeLock.assertLocked(); +#endif // Done if the garbage is not full if (garbage_byte_size < garbage_threshold && !collectALot) { diff --git a/runtime/objc-class-old.mm b/runtime/objc-class-old.mm index e23718f..acc269e 100644 --- a/runtime/objc-class-old.mm +++ b/runtime/objc-class-old.mm @@ -331,26 +331,22 @@ static void _freedHandler(id obj, SEL sel) * cls should be a metaclass. * Does not check if the method already exists. **********************************************************************/ -static void _class_resolveClassMethod(Class cls, SEL sel, id inst) +static void _class_resolveClassMethod(id inst, SEL sel, Class cls) { - assert(cls->isMetaClass()); + ASSERT(cls->isMetaClass()); + SEL resolve_sel = @selector(resolveClassMethod:); - if (! lookUpImpOrNil(cls, SEL_resolveClassMethod, inst, - NO/*initialize*/, YES/*cache*/, NO/*resolver*/)) - { + if (!lookUpImpOrNil(inst, resolve_sel, cls)) { // Resolver not implemented. return; } BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend; - bool resolved = msg(_class_getNonMetaClass(cls, inst), - SEL_resolveClassMethod, sel); + bool resolved = msg(_class_getNonMetaClass(cls, inst), resolve_sel, sel); // Cache the result (good or bad) so the resolver doesn't fire next time. // +resolveClassMethod adds to self->ISA() a.k.a. cls - IMP imp = lookUpImpOrNil(cls, sel, inst, - NO/*initialize*/, YES/*cache*/, NO/*resolver*/); - + IMP imp = lookUpImpOrNil(inst, sel, cls); if (resolved && PrintResolving) { if (imp) { _objc_inform("RESOLVE: method %c[%s %s] " @@ -376,22 +372,21 @@ static void _class_resolveClassMethod(Class cls, SEL sel, id inst) * cls may be a metaclass or a non-meta class. * Does not check if the method already exists. **********************************************************************/ -static void _class_resolveInstanceMethod(Class cls, SEL sel, id inst) +static void _class_resolveInstanceMethod(id inst, SEL sel, Class cls) { - if (! lookUpImpOrNil(cls->ISA(), SEL_resolveInstanceMethod, cls, - NO/*initialize*/, YES/*cache*/, NO/*resolver*/)) - { + SEL resolve_sel = @selector(resolveInstanceMethod:); + + if (! lookUpImpOrNil(cls, resolve_sel, cls->ISA())) { // Resolver not implemented. return; } BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend; - bool resolved = msg(cls, SEL_resolveInstanceMethod, sel); + bool resolved = msg(cls, resolve_sel, sel); // Cache the result (good or bad) so the resolver doesn't fire next time. // +resolveInstanceMethod adds to self a.k.a. cls - IMP imp = lookUpImpOrNil(cls, sel, inst, - NO/*initialize*/, YES/*cache*/, NO/*resolver*/); + IMP imp = lookUpImpOrNil(inst, sel, cls); if (resolved && PrintResolving) { if (imp) { @@ -418,20 +413,19 @@ static void _class_resolveInstanceMethod(Class cls, SEL sel, id inst) * Returns nothing; any result would be potentially out-of-date already. * Does not check if the method already exists. **********************************************************************/ -void _class_resolveMethod(Class cls, SEL sel, id inst) +static void +_class_resolveMethod(id inst, SEL sel, Class cls) { if (! cls->isMetaClass()) { // try [cls resolveInstanceMethod:sel] - _class_resolveInstanceMethod(cls, sel, inst); + _class_resolveInstanceMethod(inst, sel, cls); } else { // try [nonMetaClass resolveClassMethod:sel] // and [cls resolveInstanceMethod:sel] - _class_resolveClassMethod(cls, sel, inst); - if (!lookUpImpOrNil(cls, sel, inst, - NO/*initialize*/, YES/*cache*/, NO/*resolver*/)) - { - _class_resolveInstanceMethod(cls, sel, inst); + _class_resolveClassMethod(inst, sel, cls); + if (!lookUpImpOrNil(inst, sel, cls)) { + _class_resolveInstanceMethod(inst, sel, cls); } } } @@ -459,33 +453,19 @@ void _class_resolveMethod(Class cls, SEL sel, id inst) } -/*********************************************************************** -* _class_lookupMethodAndLoadCache. -* Method lookup for dispatchers ONLY. OTHER CODE SHOULD USE lookUpImp(). -* This lookup avoids optimistic cache scan because the dispatcher -* already tried that. -**********************************************************************/ -IMP _class_lookupMethodAndLoadCache3(id obj, SEL sel, Class cls) -{ - return lookUpImpOrForward(cls, sel, obj, - YES/*initialize*/, NO/*cache*/, YES/*resolver*/); -} - - /*********************************************************************** * lookUpImpOrForward. * The standard IMP lookup. -* initialize==NO tries to avoid +initialize (but sometimes fails) -* cache==NO skips optimistic unlocked lookup (but uses cache elsewhere) -* Most callers should use initialize==YES and cache==YES. -* inst is an instance of cls or a subclass thereof, or nil if none is known. +* Without LOOKUP_INITIALIZE: tries to avoid +initialize (but sometimes fails) +* Without LOOKUP_CACHE: skips optimistic unlocked lookup (but uses cache elsewhere) +* Most callers should use LOOKUP_INITIALIZE and LOOKUP_CACHE +* inst is an instance of cls or a subclass thereof, or nil if none is known. * If cls is an un-initialized metaclass then a non-nil inst is faster. * May return _objc_msgForward_impcache. IMPs destined for external use * must be converted to _objc_msgForward or _objc_msgForward_stret. -* If you don't want forwarding at all, use lookUpImpOrNil() instead. +* If you don't want forwarding at all, use LOOKUP_NIL. **********************************************************************/ -IMP lookUpImpOrForward(Class cls, SEL sel, id inst, - bool initialize, bool cache, bool resolver) +IMP lookUpImpOrForward(id inst, SEL sel, Class cls, int behavior) { Class curClass; IMP methodPC = nil; @@ -495,9 +475,9 @@ IMP lookUpImpOrForward(Class cls, SEL sel, id inst, methodListLock.assertUnlocked(); // Optimistic cache lookup - if (cache) { + if (behavior & LOOKUP_CACHE) { methodPC = _cache_getImp(cls, sel); - if (methodPC) return methodPC; + if (methodPC) goto out_nolock; } // Check for freed class @@ -505,7 +485,7 @@ IMP lookUpImpOrForward(Class cls, SEL sel, id inst, return (IMP) _freedHandler; // Check for +initialize - if (initialize && !cls->isInitialized()) { + if ((behavior & LOOKUP_INITIALIZE) && !cls->isInitialized()) { initializeNonMetaClass (_class_getNonMetaClass(cls, inst)); // If sel == initialize, initializeNonMetaClass will send +initialize // and then the messenger will send +initialize again after this @@ -566,7 +546,7 @@ IMP lookUpImpOrForward(Class cls, SEL sel, id inst, // No implementation found. Try method resolver once. - if (resolver && !triedResolver) { + if ((behavior & LOOKUP_RESOLVER) && !triedResolver) { methodListLock.unlock(); _class_resolveMethod(cls, sel, inst); triedResolver = YES; @@ -582,23 +562,14 @@ IMP lookUpImpOrForward(Class cls, SEL sel, id inst, done: methodListLock.unlock(); + out_nolock: + if ((behavior & LOOKUP_NIL) && methodPC == (IMP)_objc_msgForward_impcache) { + return nil; + } return methodPC; } -/*********************************************************************** -* lookUpImpOrNil. -* Like lookUpImpOrForward, but returns nil instead of _objc_msgForward_impcache -**********************************************************************/ -IMP lookUpImpOrNil(Class cls, SEL sel, id inst, - bool initialize, bool cache, bool resolver) -{ - IMP imp = lookUpImpOrForward(cls, sel, inst, initialize, cache, resolver); - if (imp == _objc_msgForward_impcache) return nil; - else return imp; -} - - /*********************************************************************** * lookupMethodInClassAndLoadCache. * Like _class_lookupMethodAndLoadCache, but does not search superclasses. @@ -865,7 +836,7 @@ static Class _class_getNonMetaClass(Class cls, id obj) else { cls = objc_getClass(cls->name); } - assert(cls); + ASSERT(cls); } return cls; @@ -1035,8 +1006,7 @@ Method class_getInstanceMethod(Class cls, SEL sel) } // Search method lists, try method resolver, etc. - lookUpImpOrNil(cls, sel, nil, - NO/*initialize*/, NO/*cache*/, YES/*resolver*/); + lookUpImpOrForward(nil, sel, cls, LOOKUP_INITIALIZE | LOOKUP_RESOLVER); meth = _cache_getMethod(cls, sel, _objc_msgForward_impcache); if (meth == (Method)1) { @@ -2386,7 +2356,7 @@ void objc_disposeClassPair(Class cls) obj->initIsa(cls); if (cls->hasCxxCtor()) { - return object_cxxConstructFromClass(obj, cls); + return object_cxxConstructFromClass(obj, cls, OBJECT_CONSTRUCT_NONE); } else { return obj; } @@ -2561,6 +2531,21 @@ id class_createInstanceFromZone(Class cls, size_t extraBytes, void *z) return (*_zoneAlloc)(cls, extraBytes, z); } +id +_objc_rootAllocWithZone(Class cls, malloc_zone_t *zone) +{ + id obj; + + if (fastpath(!zone)) { + obj = class_createInstance(cls, 0); + } else { + obj = class_createInstanceFromZone(cls, 0, zone); + } + + if (slowpath(!obj)) obj = _objc_callBadAllocHandler(cls); + return obj; +} + unsigned class_createInstances(Class cls, size_t extraBytes, id *results, unsigned num_requested) { diff --git a/runtime/objc-class.mm b/runtime/objc-class.mm index 67b731e..776f3fa 100644 --- a/runtime/objc-class.mm +++ b/runtime/objc-class.mm @@ -195,10 +195,9 @@ Class object_setClass(id obj, Class cls) // weakly-referenced object has an un-+initialized isa. // Unresolved future classes are not so protected. if (!cls->isFuture() && !cls->isInitialized()) { - // use lookUpImpOrForward to indirectly provoke +initialize + // use lookUpImpOrNil to indirectly provoke +initialize // to avoid duplicating the code to actually send +initialize - lookUpImpOrForward(cls, SEL_initialize, nil, - YES/*initialize*/, YES/*cache*/, NO/*resolver*/); + lookUpImpOrNil(nil, @selector(initialize), cls, LOOKUP_INITIALIZE); } return obj->changeIsa(cls); @@ -485,9 +484,9 @@ void object_cxxDestruct(id obj) * return nil: construction failed because a C++ constructor threw an exception **********************************************************************/ id -object_cxxConstructFromClass(id obj, Class cls) +object_cxxConstructFromClass(id obj, Class cls, int flags) { - assert(cls->hasCxxCtor()); // required for performance, not correctness + ASSERT(cls->hasCxxCtor()); // required for performance, not correctness id (*ctor)(id); Class supercls; @@ -496,8 +495,8 @@ void object_cxxDestruct(id obj) // Call superclasses' ctors first, if any. if (supercls && supercls->hasCxxCtor()) { - bool ok = object_cxxConstructFromClass(obj, supercls); - if (!ok) return nil; // some superclass's ctor failed - give up + bool ok = object_cxxConstructFromClass(obj, supercls, flags); + if (slowpath(!ok)) return nil; // some superclass's ctor failed - give up } // Find this class's ctor, if any. @@ -509,11 +508,17 @@ void object_cxxDestruct(id obj) _objc_inform("CXX: calling C++ constructors for class %s", cls->nameForLogging()); } - if ((*ctor)(obj)) return obj; // ctor called and succeeded - ok + if (fastpath((*ctor)(obj))) return obj; // ctor called and succeeded - ok - // This class's ctor was called and failed. + supercls = cls->superclass; // this reload avoids a spill on the stack + + // This class's ctor was called and failed. // Call superclasses's dtors to clean up. if (supercls) object_cxxDestructFromClass(obj, supercls); + if (flags & OBJECT_CONSTRUCT_FREE_ONFAILURE) free(obj); + if (flags & OBJECT_CONSTRUCT_CALL_BADALLOC) { + return _objc_callBadAllocHandler(cls); + } return nil; } @@ -625,23 +630,18 @@ BOOL class_respondsToMethod(Class cls, SEL sel) BOOL class_respondsToSelector(Class cls, SEL sel) { - return class_respondsToSelector_inst(cls, sel, nil); + return class_respondsToSelector_inst(nil, sel, cls); } // inst is an instance of cls or a subclass thereof, or nil if none is known. // Non-nil inst is faster in some cases. See lookUpImpOrForward() for details. -bool class_respondsToSelector_inst(Class cls, SEL sel, id inst) +NEVER_INLINE BOOL +class_respondsToSelector_inst(id inst, SEL sel, Class cls) { - IMP imp; - - if (!sel || !cls) return NO; - // Avoids +initialize because it historically did so. // We're not returning a callable IMP anyway. - imp = lookUpImpOrNil(cls, sel, inst, - NO/*initialize*/, YES/*cache*/, YES/*resolver*/); - return bool(imp); + return sel && cls && lookUpImpOrNil(inst, sel, cls, LOOKUP_RESOLVER); } @@ -668,8 +668,7 @@ IMP class_getMethodImplementation(Class cls, SEL sel) if (!cls || !sel) return nil; - imp = lookUpImpOrNil(cls, sel, nil, - YES/*initialize*/, YES/*cache*/, YES/*resolver*/); + imp = lookUpImpOrNil(nil, sel, cls, LOOKUP_INITIALIZE | LOOKUP_RESOLVER); // Translate forwarding function to C-callable external version if (!imp) { @@ -828,26 +827,6 @@ void method_getArgumentType(Method m, unsigned int index, return encoding_copyArgumentType(method_getTypeEncoding(m), index); } - -/*********************************************************************** -* _objc_constructOrFree -* Call C++ constructors, and free() if they fail. -* bytes->isa must already be set. -* cls must have cxx constructors. -* Returns the object, or nil. -**********************************************************************/ -id -_objc_constructOrFree(id bytes, Class cls) -{ - assert(cls->hasCxxCtor()); // for performance, not correctness - - id obj = object_cxxConstructFromClass(bytes, cls); - if (!obj) free(bytes); - - return obj; -} - - /*********************************************************************** * _class_createInstancesFromZone * Batch-allocating version of _class_createInstanceFromZone. @@ -878,8 +857,10 @@ void method_getArgumentType(Method m, unsigned int index, for (unsigned i = 0; i < num_allocated; i++) { id obj = results[i]; obj->initIsa(cls); // fixme allow nonpointer - if (ctor) obj = _objc_constructOrFree(obj, cls); - + if (ctor) { + obj = object_cxxConstructFromClass(obj, cls, + OBJECT_CONSTRUCT_FREE_ONFAILURE); + } if (obj) { results[i-shift] = obj; } else { @@ -925,11 +906,11 @@ void method_getArgumentType(Method m, unsigned int index, #if DEBUG // debug build: sanitize input for (i = 0; i < count; i++) { - assert(attrs[i].name); - assert(strlen(attrs[i].name) > 0); - assert(! strchr(attrs[i].name, ',')); - assert(! strchr(attrs[i].name, '"')); - if (attrs[i].value) assert(! strchr(attrs[i].value, ',')); + ASSERT(attrs[i].name); + ASSERT(strlen(attrs[i].name) > 0); + ASSERT(! strchr(attrs[i].name, ',')); + ASSERT(! strchr(attrs[i].name, '"')); + if (attrs[i].value) ASSERT(! strchr(attrs[i].value, ',')); } #endif @@ -1015,8 +996,8 @@ void method_getArgumentType(Method m, unsigned int index, const char *nameStart; const char *nameEnd; - assert(start < end); - assert(*start); + ASSERT(start < end); + ASSERT(*start); if (*start != '\"') { // single-char short name nameStart = start; @@ -1036,7 +1017,7 @@ void method_getArgumentType(Method m, unsigned int index, const char *valueStart; const char *valueEnd; - assert(start <= end); + ASSERT(start <= end); valueStart = start; valueEnd = end; @@ -1113,8 +1094,8 @@ void method_getArgumentType(Method m, unsigned int index, attrcount = iteratePropertyAttributes(attrs, copyOneAttribute, &ra, &rs); - assert((uint8_t *)(ra+1) <= (uint8_t *)result+size); - assert((uint8_t *)rs <= (uint8_t *)result+size); + ASSERT((uint8_t *)(ra+1) <= (uint8_t *)result+size); + ASSERT((uint8_t *)rs <= (uint8_t *)result+size); if (attrcount == 0) { free(result); diff --git a/runtime/objc-config.h b/runtime/objc-config.h index 72799c7..9bc9fc1 100644 --- a/runtime/objc-config.h +++ b/runtime/objc-config.h @@ -65,7 +65,7 @@ #endif // Define SUPPORT_PREOPT=1 to enable dyld shared cache optimizations -#if TARGET_OS_WIN32 || TARGET_OS_SIMULATOR +#if TARGET_OS_WIN32 # define SUPPORT_PREOPT 0 #else # define SUPPORT_PREOPT 1 @@ -162,6 +162,14 @@ # define SUPPORT_MESSAGE_LOGGING 1 #endif +// Define HAVE_TASK_RESTARTABLE_RANGES to enable usage of +// task_restartable_ranges_synchronize() +#if TARGET_OS_SIMULATOR || defined(__i386__) || defined(__arm__) || !TARGET_OS_MAC +# define HAVE_TASK_RESTARTABLE_RANGES 0 +#else +# define HAVE_TASK_RESTARTABLE_RANGES 1 +#endif + // OBJC_INSTRUMENTED controls whether message dispatching is dynamically // monitored. Monitoring introduces substantial overhead. // NOTE: To define this condition, do so in the build command, NOT by @@ -170,4 +178,43 @@ // because objc-class.h is public and objc-config.h is not. //#define OBJC_INSTRUMENTED +// In __OBJC2__, the runtimeLock is a mutex always held +// hence the cache lock is redundant and can be elided. +// +// If the runtime lock ever becomes a rwlock again, +// the cache lock would need to be used again +#if __OBJC2__ +#define CONFIG_USE_CACHE_LOCK 0 +#else +#define CONFIG_USE_CACHE_LOCK 1 +#endif + +// Determine how the method cache stores IMPs. +#define CACHE_IMP_ENCODING_NONE 1 // Method cache contains raw IMP. +#define CACHE_IMP_ENCODING_ISA_XOR 2 // Method cache contains ISA ^ IMP. +#define CACHE_IMP_ENCODING_PTRAUTH 3 // Method cache contains ptrauth'd IMP. + +#if __PTRAUTH_INTRINSICS__ +// Always use ptrauth when it's supported. +#define CACHE_IMP_ENCODING CACHE_IMP_ENCODING_PTRAUTH +#elif defined(__arm__) +// 32-bit ARM uses no encoding. +#define CACHE_IMP_ENCODING CACHE_IMP_ENCODING_NONE +#else +// Everything else uses ISA ^ IMP. +#define CACHE_IMP_ENCODING CACHE_IMP_ENCODING_ISA_XOR +#endif + +#define CACHE_MASK_STORAGE_OUTLINED 1 +#define CACHE_MASK_STORAGE_HIGH_16 2 +#define CACHE_MASK_STORAGE_LOW_4 3 + +#if defined(__arm64__) && __LP64__ +#define CACHE_MASK_STORAGE CACHE_MASK_STORAGE_HIGH_16 +#elif defined(__arm64__) && !__LP64__ +#define CACHE_MASK_STORAGE CACHE_MASK_STORAGE_LOW_4 +#else +#define CACHE_MASK_STORAGE CACHE_MASK_STORAGE_OUTLINED +#endif + #endif diff --git a/runtime/objc-env.h b/runtime/objc-env.h index 07090d6..ccdceb6 100644 --- a/runtime/objc-env.h +++ b/runtime/objc-env.h @@ -22,8 +22,9 @@ OPTION( PrintAltHandlers, OBJC_PRINT_ALT_HANDLERS, "log processi OPTION( PrintReplacedMethods, OBJC_PRINT_REPLACED_METHODS, "log methods replaced by category implementations") OPTION( PrintDeprecation, OBJC_PRINT_DEPRECATION_WARNINGS, "warn about calls to deprecated runtime functions") OPTION( PrintPoolHiwat, OBJC_PRINT_POOL_HIGHWATER, "log high-water marks for autorelease pools") -OPTION( PrintCustomRR, OBJC_PRINT_CUSTOM_RR, "log classes with un-optimized custom retain/release methods") -OPTION( PrintCustomAWZ, OBJC_PRINT_CUSTOM_AWZ, "log classes with un-optimized custom allocWithZone methods") +OPTION( PrintCustomCore, OBJC_PRINT_CUSTOM_CORE, "log classes with custom core methods") +OPTION( PrintCustomRR, OBJC_PRINT_CUSTOM_RR, "log classes with custom retain/release methods") +OPTION( PrintCustomAWZ, OBJC_PRINT_CUSTOM_AWZ, "log classes with custom allocWithZone methods") OPTION( PrintRawIsa, OBJC_PRINT_RAW_ISA, "log classes that require raw pointer isa fields") OPTION( DebugUnload, OBJC_DEBUG_UNLOAD, "warn about poorly-behaving bundles when unloaded") diff --git a/runtime/objc-errors.mm b/runtime/objc-errors.mm index 159cd86..17fdda2 100644 --- a/runtime/objc-errors.mm +++ b/runtime/objc-errors.mm @@ -158,7 +158,7 @@ static void _objc_syslog(const char *message) #if !__OBJC2__ // used by ExceptionHandling.framework #endif -__attribute__((noreturn)) +__attribute__((noreturn, cold)) void _objc_error(id self, const char *fmt, va_list ap) { char *buf; @@ -181,7 +181,7 @@ void __objc_error(id rcv, const char *fmt, ...) va_end(vp); } -static __attribute__((noreturn)) +static __attribute__((noreturn, cold)) void _objc_fatalv(uint64_t reason, uint64_t flags, const char *fmt, va_list ap) { char *buf1; @@ -198,6 +198,7 @@ void _objc_fatalv(uint64_t reason, uint64_t flags, const char *fmt, va_list ap) _Exit(1); } else { + _objc_crashlog(buf1); abort_with_reason(OS_REASON_OBJC, reason, buf1, flags); } } diff --git a/runtime/objc-exception.h b/runtime/objc-exception.h index d6cbb7e..c93b47b 100644 --- a/runtime/objc-exception.h +++ b/runtime/objc-exception.h @@ -33,33 +33,23 @@ OBJC_EXPORT void objc_exception_throw(id _Nonnull exception) - __OSX_AVAILABLE(10.3) - __IOS_UNAVAILABLE __TVOS_UNAVAILABLE - __WATCHOS_UNAVAILABLE __BRIDGEOS_UNAVAILABLE; + OBJC_OSX_AVAILABLE_OTHERS_UNAVAILABLE(10.3); OBJC_EXPORT void objc_exception_try_enter(void * _Nonnull localExceptionData) - __OSX_AVAILABLE(10.3) - __IOS_UNAVAILABLE __TVOS_UNAVAILABLE - __WATCHOS_UNAVAILABLE __BRIDGEOS_UNAVAILABLE; + OBJC_OSX_AVAILABLE_OTHERS_UNAVAILABLE(10.3); OBJC_EXPORT void objc_exception_try_exit(void * _Nonnull localExceptionData) - __OSX_AVAILABLE(10.3) - __IOS_UNAVAILABLE __TVOS_UNAVAILABLE - __WATCHOS_UNAVAILABLE __BRIDGEOS_UNAVAILABLE; + OBJC_OSX_AVAILABLE_OTHERS_UNAVAILABLE(10.3); OBJC_EXPORT id _Nonnull objc_exception_extract(void * _Nonnull localExceptionData) - __OSX_AVAILABLE(10.3) - __IOS_UNAVAILABLE __TVOS_UNAVAILABLE - __WATCHOS_UNAVAILABLE __BRIDGEOS_UNAVAILABLE; + OBJC_OSX_AVAILABLE_OTHERS_UNAVAILABLE(10.3); OBJC_EXPORT int objc_exception_match(Class _Nonnull exceptionClass, id _Nonnull exception) - __OSX_AVAILABLE(10.3) - __IOS_UNAVAILABLE __TVOS_UNAVAILABLE - __WATCHOS_UNAVAILABLE __BRIDGEOS_UNAVAILABLE; + OBJC_OSX_AVAILABLE_OTHERS_UNAVAILABLE(10.3); typedef struct { @@ -74,16 +64,12 @@ typedef struct { // get table; version tells how many OBJC_EXPORT void objc_exception_get_functions(objc_exception_functions_t * _Nullable table) - __OSX_AVAILABLE(10.3) - __IOS_UNAVAILABLE __TVOS_UNAVAILABLE - __WATCHOS_UNAVAILABLE __BRIDGEOS_UNAVAILABLE; + OBJC_OSX_AVAILABLE_OTHERS_UNAVAILABLE(10.3); // set table OBJC_EXPORT void objc_exception_set_functions(objc_exception_functions_t * _Nullable table) - __OSX_AVAILABLE(10.3) - __IOS_UNAVAILABLE __TVOS_UNAVAILABLE - __WATCHOS_UNAVAILABLE __BRIDGEOS_UNAVAILABLE; + OBJC_OSX_AVAILABLE_OTHERS_UNAVAILABLE(10.3); // !__OBJC2__ @@ -139,15 +125,11 @@ objc_setUncaughtExceptionHandler(objc_uncaught_exception_handler _Nonnull fn) OBJC_EXPORT uintptr_t objc_addExceptionHandler(objc_exception_handler _Nonnull fn, void * _Nullable context) - __OSX_AVAILABLE(10.5) - __IOS_UNAVAILABLE __TVOS_UNAVAILABLE - __WATCHOS_UNAVAILABLE __BRIDGEOS_UNAVAILABLE; + OBJC_OSX_AVAILABLE_OTHERS_UNAVAILABLE(10.5); OBJC_EXPORT void objc_removeExceptionHandler(uintptr_t token) - __OSX_AVAILABLE(10.5) - __IOS_UNAVAILABLE __TVOS_UNAVAILABLE - __WATCHOS_UNAVAILABLE __BRIDGEOS_UNAVAILABLE; + OBJC_OSX_AVAILABLE_OTHERS_UNAVAILABLE(10.5); // __OBJC2__ #endif diff --git a/runtime/objc-exception.mm b/runtime/objc-exception.mm index c93b8e7..6c318c6 100644 --- a/runtime/objc-exception.mm +++ b/runtime/objc-exception.mm @@ -133,8 +133,8 @@ int objc_exception_match(Class exceptionClass, id exception) { static ThreadChainLink_t ThreadChainLink; static ThreadChainLink_t *getChainLink() { - // follow links until thread_self() found (someday) XXX - objc_thread_t self = thread_self(); + // follow links until objc_thread_self() found (someday) XXX + objc_thread_t self = objc_thread_self(); ThreadChainLink_t *walker = &ThreadChainLink; while (walker->perThreadID != self) { if (walker->next != nil) { @@ -322,27 +322,35 @@ void _destroyAltHandlerList(struct alt_handler_list *list) struct objc_typeinfo tinfo; }; +extern "C" { -extern "C" void _objc_exception_noop(void) { } -extern "C" bool _objc_exception_false(void) { return 0; } -// extern "C" bool _objc_exception_true(void) { return 1; } -extern "C" void _objc_exception_abort1(void) { - _objc_fatal("unexpected call into objc exception typeinfo vtable %d", 1); -} -extern "C" void _objc_exception_abort2(void) { - _objc_fatal("unexpected call into objc exception typeinfo vtable %d", 2); -} -extern "C" void _objc_exception_abort3(void) { - _objc_fatal("unexpected call into objc exception typeinfo vtable %d", 3); -} -extern "C" void _objc_exception_abort4(void) { - _objc_fatal("unexpected call into objc exception typeinfo vtable %d", 4); -} - -extern "C" bool _objc_exception_do_catch(struct objc_typeinfo *catch_tinfo, - struct objc_typeinfo *throw_tinfo, - void **throw_obj_p, - unsigned outer); +__attribute__((used)) +void _objc_exception_noop(void) { } +__attribute__((used)) +bool _objc_exception_false(void) { return 0; } +// bool _objc_exception_true(void) { return 1; } +__attribute__((used)) +void _objc_exception_abort1(void) { + _objc_fatal("unexpected call into objc exception typeinfo vtable %d", 1); +} +__attribute__((used)) +void _objc_exception_abort2(void) { + _objc_fatal("unexpected call into objc exception typeinfo vtable %d", 2); +} +__attribute__((used)) +void _objc_exception_abort3(void) { + _objc_fatal("unexpected call into objc exception typeinfo vtable %d", 3); +} +__attribute__((used)) +void _objc_exception_abort4(void) { + _objc_fatal("unexpected call into objc exception typeinfo vtable %d", 4); +} +__attribute__((used)) +bool _objc_exception_do_catch(struct objc_typeinfo *catch_tinfo, + struct objc_typeinfo *throw_tinfo, + void **throw_obj_p, + unsigned outer); +} // C++ pointers to vtables are signed with no extra data. // C++ vtable entries are signed with a number derived from the function name. @@ -1207,9 +1215,9 @@ uintptr_t objc_addExceptionHandler(objc_exception_handler fn, void *context) bzero(data->debug, sizeof(*data->debug)); } - pthread_getname_np(pthread_self(), data->debug->thread, THREADNAME_COUNT); - strlcpy(data->debug->queue, - dispatch_queue_get_label(dispatch_get_current_queue()), + pthread_getname_np(objc_thread_self(), data->debug->thread, THREADNAME_COUNT); + strlcpy(data->debug->queue, + dispatch_queue_get_label(DISPATCH_CURRENT_QUEUE_LABEL), THREADNAME_COUNT); data->debug->backtraceSize = backtrace(data->debug->backtrace, BACKTRACE_COUNT); diff --git a/runtime/objc-file.h b/runtime/objc-file.h index 47b7108..23c0da1 100644 --- a/runtime/objc-file.h +++ b/runtime/objc-file.h @@ -30,15 +30,19 @@ // classref_t is not fixed up at launch; use remapClass() to convert +// classlist and catlist and protolist sections are marked const here +// because those sections may be in read-only __DATA_CONST segments. + extern SEL *_getObjc2SelectorRefs(const header_info *hi, size_t *count); extern message_ref_t *_getObjc2MessageRefs(const header_info *hi, size_t *count); extern Class*_getObjc2ClassRefs(const header_info *hi, size_t *count); extern Class*_getObjc2SuperRefs(const header_info *hi, size_t *count); -extern classref_t *_getObjc2ClassList(const header_info *hi, size_t *count); -extern classref_t *_getObjc2NonlazyClassList(const header_info *hi, size_t *count); -extern category_t **_getObjc2CategoryList(const header_info *hi, size_t *count); -extern category_t **_getObjc2NonlazyCategoryList(const header_info *hi, size_t *count); -extern protocol_t **_getObjc2ProtocolList(const header_info *hi, size_t *count); +extern classref_t const *_getObjc2ClassList(const header_info *hi, size_t *count); +extern classref_t const *_getObjc2NonlazyClassList(const header_info *hi, size_t *count); +extern category_t * const *_getObjc2CategoryList(const header_info *hi, size_t *count); +extern category_t * const *_getObjc2CategoryList2(const header_info *hi, size_t *count); +extern category_t * const *_getObjc2NonlazyCategoryList(const header_info *hi, size_t *count); +extern protocol_t * const *_getObjc2ProtocolList(const header_info *hi, size_t *count); extern protocol_t **_getObjc2ProtocolRefs(const header_info *hi, size_t *count); // FIXME: rdar://29241917&33734254 clang doesn't sign static initializers. @@ -57,8 +61,8 @@ struct UnsignedInitializer { extern UnsignedInitializer *getLibobjcInitializers(const header_info *hi, size_t *count); -extern classref_t *_getObjc2NonlazyClassList(const headerType *mhdr, size_t *count); -extern category_t **_getObjc2NonlazyCategoryList(const headerType *mhdr, size_t *count); +extern classref_t const *_getObjc2NonlazyClassList(const headerType *mhdr, size_t *count); +extern category_t * const *_getObjc2NonlazyCategoryList(const headerType *mhdr, size_t *count); extern UnsignedInitializer *getLibobjcInitializers(const headerType *mhdr, size_t *count); static inline void diff --git a/runtime/objc-file.mm b/runtime/objc-file.mm index 917b2bc..ffde2fd 100644 --- a/runtime/objc-file.mm +++ b/runtime/objc-file.mm @@ -59,11 +59,12 @@ GETSECT(_getObjc2MessageRefs, message_ref_t, "__objc_msgrefs"); GETSECT(_getObjc2ClassRefs, Class, "__objc_classrefs"); GETSECT(_getObjc2SuperRefs, Class, "__objc_superrefs"); -GETSECT(_getObjc2ClassList, classref_t, "__objc_classlist"); -GETSECT(_getObjc2NonlazyClassList, classref_t, "__objc_nlclslist"); -GETSECT(_getObjc2CategoryList, category_t *, "__objc_catlist"); -GETSECT(_getObjc2NonlazyCategoryList, category_t *, "__objc_nlcatlist"); -GETSECT(_getObjc2ProtocolList, protocol_t *, "__objc_protolist"); +GETSECT(_getObjc2ClassList, classref_t const, "__objc_classlist"); +GETSECT(_getObjc2NonlazyClassList, classref_t const, "__objc_nlclslist"); +GETSECT(_getObjc2CategoryList, category_t * const, "__objc_catlist"); +GETSECT(_getObjc2CategoryList2, category_t * const, "__objc_catlist2"); +GETSECT(_getObjc2NonlazyCategoryList, category_t * const, "__objc_nlcatlist"); +GETSECT(_getObjc2ProtocolList, protocol_t * const, "__objc_protolist"); GETSECT(_getObjc2ProtocolRefs, protocol_t *, "__objc_protorefs"); GETSECT(getLibobjcInitializers, UnsignedInitializer, "__objc_init_func"); diff --git a/runtime/objc-gdb.h b/runtime/objc-gdb.h index 67ce2cb..f1e4a3c 100644 --- a/runtime/objc-gdb.h +++ b/runtime/objc-gdb.h @@ -61,6 +61,18 @@ gdb_class_getClass(Class _Nonnull cls) OBJC_EXPORT Class _Nonnull gdb_object_getClass(id _Nullable obj) OBJC_AVAILABLE(10.7, 4.3, 9.0, 1.0, 2.0); +/*********************************************************************** +* Class inspection +**********************************************************************/ + +#if __OBJC2__ + +// Return the raw, mangled name of cls. +OBJC_EXPORT const char * _Nonnull +objc_debug_class_getNameRaw(Class _Nullable cls) +OBJC_AVAILABLE(10.15, 13.0, 13.0, 6.0, 5.0); + +#endif /*********************************************************************** * Class lists for heap. @@ -72,6 +84,15 @@ OBJC_EXPORT Class _Nonnull gdb_object_getClass(id _Nullable obj) OBJC_EXPORT NXMapTable * _Nullable gdb_objc_realized_classes OBJC_AVAILABLE(10.6, 3.1, 9.0, 1.0, 2.0); +// A generation count of realized classes. Increments when new classes +// are realized. This is NOT an exact count of the number of classes. +// It's not guaranteed by how much it increases when classes are +// realized, only that it increases by something. When classes are +// removed (unloading bundles or destroying dynamically allocated +// classes) the number will also increase to signal that there has +// been a change. +OBJC_EXPORT uintptr_t objc_debug_realized_class_generation_count; + #else // Hashes Classes, for all known classes. Custom prototype. @@ -210,9 +231,22 @@ OBJC_EXPORT unsigned int objc_debug_taggedpointer_ext_payload_rshift **********************************************************************/ #if __OBJC2__ OBJC_EXPORT const uintptr_t objc_debug_swift_stable_abi_bit -OBJC_AVAILABLE(10.14, 12.0, 12.0, 5.0, 3.0); +OBJC_AVAILABLE(10.14.4, 12.2, 12.2, 5.2, 3.2); #endif + + +/*********************************************************************** +* AutoreleasePoolPage +**********************************************************************/ +OBJC_EXTERN const uint32_t objc_debug_autoreleasepoolpage_magic_offset OBJC_AVAILABLE(10.15, 13.0, 13.0, 6.0, 5.0); +OBJC_EXTERN const uint32_t objc_debug_autoreleasepoolpage_next_offset OBJC_AVAILABLE(10.15, 13.0, 13.0, 6.0, 5.0); +OBJC_EXTERN const uint32_t objc_debug_autoreleasepoolpage_thread_offset OBJC_AVAILABLE(10.15, 13.0, 13.0, 6.0, 5.0); +OBJC_EXTERN const uint32_t objc_debug_autoreleasepoolpage_parent_offset OBJC_AVAILABLE(10.15, 13.0, 13.0, 6.0, 5.0); +OBJC_EXTERN const uint32_t objc_debug_autoreleasepoolpage_child_offset OBJC_AVAILABLE(10.15, 13.0, 13.0, 6.0, 5.0); +OBJC_EXTERN const uint32_t objc_debug_autoreleasepoolpage_depth_offset OBJC_AVAILABLE(10.15, 13.0, 13.0, 6.0, 5.0); +OBJC_EXTERN const uint32_t objc_debug_autoreleasepoolpage_hiwat_offset OBJC_AVAILABLE(10.15, 13.0, 13.0, 6.0, 5.0); + __END_DECLS // APPLE_API_PRIVATE diff --git a/runtime/objc-initialize.mm b/runtime/objc-initialize.mm index 8f962aa..4713325 100644 --- a/runtime/objc-initialize.mm +++ b/runtime/objc-initialize.mm @@ -95,6 +95,7 @@ #include "objc-private.h" #include "message.h" #include "objc-initialize.h" +#include "DenseMapExtras.h" /* classInitLock protects CLS_INITIALIZED and CLS_INITIALIZING, and * is signalled when any class is done initializing. @@ -102,6 +103,13 @@ monitor_t classInitLock; +struct _objc_willInitializeClassCallback { + _objc_func_willInitializeClass f; + void *context; +}; +static GlobalSmallVector<_objc_willInitializeClassCallback, 1> willInitializeFuncs; + + /*********************************************************************** * struct _objc_initializing_classes * Per-thread list of classes currently being initialized by that thread. @@ -262,9 +270,12 @@ static void _setThisThreadIsNotInitializingClass(Class cls) typedef struct PendingInitialize { Class subclass; struct PendingInitialize *next; + + PendingInitialize(Class cls) : subclass(cls), next(nullptr) { } } PendingInitialize; -static NXMapTable *pendingInitializeMap; +typedef objc::DenseMap PendingInitializeMap; +static PendingInitializeMap *pendingInitializeMap; /*********************************************************************** * _finishInitializing @@ -277,11 +288,11 @@ static void _finishInitializing(Class cls, Class supercls) PendingInitialize *pending; classInitLock.assertLocked(); - assert(!supercls || supercls->isInitialized()); + ASSERT(!supercls || supercls->isInitialized()); if (PrintInitializing) { _objc_inform("INITIALIZE: thread %p: %s is fully +initialized", - pthread_self(), cls->nameForLogging()); + objc_thread_self(), cls->nameForLogging()); } // mark this class as fully +initialized @@ -291,21 +302,23 @@ static void _finishInitializing(Class cls, Class supercls) // mark any subclasses that were merely waiting for this class if (!pendingInitializeMap) return; - pending = (PendingInitialize *)NXMapGet(pendingInitializeMap, cls); - if (!pending) return; - NXMapRemove(pendingInitializeMap, cls); - + auto it = pendingInitializeMap->find(cls); + if (it == pendingInitializeMap->end()) return; + + pending = it->second; + pendingInitializeMap->erase(it); + // Destroy the pending table if it's now empty, to save memory. - if (NXCountMapTable(pendingInitializeMap) == 0) { - NXFreeMapTable(pendingInitializeMap); + if (pendingInitializeMap->size() == 0) { + delete pendingInitializeMap; pendingInitializeMap = nil; } while (pending) { PendingInitialize *next = pending->next; if (pending->subclass) _finishInitializing(pending->subclass, cls); - free(pending); + delete pending; pending = next; } } @@ -318,28 +331,27 @@ static void _finishInitializing(Class cls, Class supercls) **********************************************************************/ static void _finishInitializingAfter(Class cls, Class supercls) { - PendingInitialize *pending; classInitLock.assertLocked(); if (PrintInitializing) { _objc_inform("INITIALIZE: thread %p: class %s will be marked as fully " "+initialized after superclass +[%s initialize] completes", - pthread_self(), cls->nameForLogging(), + objc_thread_self(), cls->nameForLogging(), supercls->nameForLogging()); } if (!pendingInitializeMap) { - pendingInitializeMap = - NXCreateMapTable(NXPtrValueMapPrototype, 10); + pendingInitializeMap = new PendingInitializeMap{10}; // fixme pre-size this table for CF/NSObject +initialize } - pending = (PendingInitialize *)malloc(sizeof(*pending)); - pending->subclass = cls; - pending->next = (PendingInitialize *) - NXMapGet(pendingInitializeMap, supercls); - NXMapInsert(pendingInitializeMap, supercls, pending); + PendingInitialize *pending = new PendingInitialize{cls}; + auto result = pendingInitializeMap->try_emplace(supercls, pending); + if (!result.second) { + pending->next = result.first->second; + result.first->second = pending; + } } @@ -356,7 +368,7 @@ void waitForInitializeToComplete(Class cls) { if (PrintInitializing) { _objc_inform("INITIALIZE: thread %p: blocking until +[%s initialize] " - "completes", pthread_self(), cls->nameForLogging()); + "completes", objc_thread_self(), cls->nameForLogging()); } monitor_locker_t lock(classInitLock); @@ -369,7 +381,7 @@ void waitForInitializeToComplete(Class cls) void callInitialize(Class cls) { - ((void(*)(Class, SEL))objc_msgSend)(cls, SEL_initialize); + ((void(*)(Class, SEL))objc_msgSend)(cls, @selector(initialize)); asm(""); } @@ -386,10 +398,8 @@ static bool classHasTrivialInitialize(Class cls) Class rootCls = cls->ISA()->ISA()->superclass; - IMP rootImp = lookUpImpOrNil(rootCls->ISA(), SEL_initialize, rootCls, - NO/*initialize*/, YES/*cache*/, NO/*resolver*/); - IMP imp = lookUpImpOrNil(cls->ISA(), SEL_initialize, cls, - NO/*initialize*/, YES/*cache*/, NO/*resolver*/); + IMP rootImp = lookUpImpOrNil(rootCls, @selector(initialize), rootCls->ISA()); + IMP imp = lookUpImpOrNil(cls, @selector(initialize), cls->ISA()); return (imp == nil || imp == (IMP)&objc_noop_imp || imp == rootImp); } @@ -451,7 +461,7 @@ void performForkChildInitialize(Class cls, Class supercls) if (PrintInitializing) { _objc_inform("INITIALIZE: thread %p: skipping trivial +[%s " "initialize] in fork() child process", - pthread_self(), cls->nameForLogging()); + objc_thread_self(), cls->nameForLogging()); } lockAndFinishInitializing(cls, supercls); } @@ -460,7 +470,7 @@ void performForkChildInitialize(Class cls, Class supercls) _objc_inform("INITIALIZE: thread %p: refusing to call +[%s " "initialize] in fork() child process because " "it may have been in progress when fork() was called", - pthread_self(), cls->nameForLogging()); + objc_thread_self(), cls->nameForLogging()); } _objc_inform_now_and_on_crash ("+[%s initialize] may have been in progress in another thread " @@ -483,7 +493,7 @@ void performForkChildInitialize(Class cls, Class supercls) **********************************************************************/ void initializeNonMetaClass(Class cls) { - assert(!cls->isMetaClass()); + ASSERT(!cls->isMetaClass()); Class supercls; bool reallyInitialize = NO; @@ -496,11 +506,15 @@ void initializeNonMetaClass(Class cls) } // Try to atomically set CLS_INITIALIZING. + SmallVector<_objc_willInitializeClassCallback, 1> localWillInitializeFuncs; { monitor_locker_t lock(classInitLock); if (!cls->isInitialized() && !cls->isInitializing()) { cls->setInitializing(); reallyInitialize = YES; + + // Grab a copy of the will-initialize funcs with the lock held. + localWillInitializeFuncs.initFrom(willInitializeFuncs); } } @@ -516,12 +530,15 @@ void initializeNonMetaClass(Class cls) return; } + for (auto callback : localWillInitializeFuncs) + callback.f(callback.context, cls); + // Send the +initialize message. // Note that +initialize is sent to the superclass (again) if // this class doesn't implement +initialize. 2157218 if (PrintInitializing) { _objc_inform("INITIALIZE: thread %p: calling +[%s initialize]", - pthread_self(), cls->nameForLogging()); + objc_thread_self(), cls->nameForLogging()); } // Exceptions: A +initialize call that throws an exception @@ -538,7 +555,7 @@ void initializeNonMetaClass(Class cls) if (PrintInitializing) { _objc_inform("INITIALIZE: thread %p: finished +[%s initialize]", - pthread_self(), cls->nameForLogging()); + objc_thread_self(), cls->nameForLogging()); } } #if __OBJC2__ @@ -546,7 +563,7 @@ void initializeNonMetaClass(Class cls) if (PrintInitializing) { _objc_inform("INITIALIZE: thread %p: +[%s initialize] " "threw an exception", - pthread_self(), cls->nameForLogging()); + objc_thread_self(), cls->nameForLogging()); } @throw; } @@ -595,3 +612,33 @@ void initializeNonMetaClass(Class cls) _objc_fatal("thread-safe class init in objc runtime is buggy!"); } } + +void _objc_addWillInitializeClassFunc(_objc_func_willInitializeClass _Nonnull func, void * _Nullable context) { +#if __OBJC2__ + unsigned count; + Class *realizedClasses; + + // Fetch all currently initialized classes. Do this with classInitLock held + // so we don't race with setting those flags. + { + monitor_locker_t initLock(classInitLock); + realizedClasses = objc_copyRealizedClassList(&count); + for (unsigned i = 0; i < count; i++) { + // Remove uninitialized classes from the array. + if (!realizedClasses[i]->isInitializing() && !realizedClasses[i]->isInitialized()) + realizedClasses[i] = Nil; + } + + willInitializeFuncs.append({func, context}); + } + + // Invoke the callback for all realized classes that weren't cleared out. + for (unsigned i = 0; i < count; i++) { + if (Class cls = realizedClasses[i]) { + func(context, cls); + } + } + + free(realizedClasses); +#endif +} diff --git a/runtime/objc-internal.h b/runtime/objc-internal.h index a3e0fee..afc82f5 100644 --- a/runtime/objc-internal.h +++ b/runtime/objc-internal.h @@ -130,23 +130,37 @@ _objc_atfork_child(void) // Return YES if GC is on and `object` is a GC allocation. OBJC_EXPORT BOOL objc_isAuto(id _Nullable object) - __OSX_DEPRECATED(10.4, 10.8, "it always returns NO") - __IOS_UNAVAILABLE __TVOS_UNAVAILABLE - __WATCHOS_UNAVAILABLE __BRIDGEOS_UNAVAILABLE; + OBJC_OSX_DEPRECATED_OTHERS_UNAVAILABLE(10.4, 10.8, "it always returns NO"); // GC debugging OBJC_EXPORT BOOL objc_dumpHeap(char * _Nonnull filename, unsigned long length) - __OSX_DEPRECATED(10.4, 10.8, "it always returns NO") - __IOS_UNAVAILABLE __TVOS_UNAVAILABLE - __WATCHOS_UNAVAILABLE __BRIDGEOS_UNAVAILABLE; + OBJC_OSX_DEPRECATED_OTHERS_UNAVAILABLE(10.4, 10.8, "it always returns NO"); // GC startup callback from Foundation OBJC_EXPORT malloc_zone_t * _Nullable objc_collect_init(int (* _Nonnull callback)(void)) - __OSX_DEPRECATED(10.4, 10.8, "it does nothing") - __IOS_UNAVAILABLE __TVOS_UNAVAILABLE - __WATCHOS_UNAVAILABLE __BRIDGEOS_UNAVAILABLE; + OBJC_OSX_DEPRECATED_OTHERS_UNAVAILABLE(10.4, 10.8, "it does nothing"); + +#if __OBJC2__ +// Copies the list of currently realized classes +// intended for introspection only +// most users will want objc_copyClassList instead. +OBJC_EXPORT +Class _Nonnull * _Nullable +objc_copyRealizedClassList(unsigned int *_Nullable outCount) + OBJC_AVAILABLE(10.15, 13.0, 13.0, 6.0, 5.0); + +typedef struct objc_imp_cache_entry { + SEL _Nonnull sel; + IMP _Nonnull imp; +} objc_imp_cache_entry; + +OBJC_EXPORT +objc_imp_cache_entry *_Nullable +class_copyImpCache(Class _Nonnull cls, int * _Nullable outCount) + OBJC_AVAILABLE(10.15, 13.0, 13.0, 6.0, 5.0); +#endif // Plainly-implemented GC barriers. Rosetta used to use these. OBJC_EXPORT id _Nullable @@ -174,7 +188,11 @@ OBJC_EXPORT int objc_appRequiresGC(int fd) __OSX_AVAILABLE(10.11) __IOS_UNAVAILABLE __TVOS_UNAVAILABLE - __WATCHOS_UNAVAILABLE __BRIDGEOS_UNAVAILABLE; + __WATCHOS_UNAVAILABLE +#ifndef __APPLE_BLEACH_SDK__ + __BRIDGEOS_UNAVAILABLE +#endif +; // Install missing-class callback. Used by the late unlamented ZeroLink. OBJC_EXPORT void @@ -182,11 +200,14 @@ _objc_setClassLoader(BOOL (* _Nonnull newClassLoader)(const char * _Nonnull)) OBJC2_UNAVAILABLE; #if !(TARGET_OS_OSX && !TARGET_OS_IOSMAC && __i386__) +// Add a class copy fixup handler. The name is a misnomer, as +// multiple calls will install multiple handlers. Older versions +// of the Swift runtime call it by name, and it's only used by Swift +// so it's not worth deprecating this name in favor of a better one. OBJC_EXPORT void _objc_setClassCopyFixupHandler(void (* _Nonnull newFixupHandler) - (Class _Nonnull oldClass, Class _Nonnull newClass)); -// fixme work around bug in Swift -// OBJC_AVAILABLE(10.14, 12.0, 12.0, 5.0, 3.0) + (Class _Nonnull oldClass, Class _Nonnull newClass)) + OBJC_AVAILABLE(10.14, 12.0, 12.0, 5.0, 3.0); #endif // Install handler for allocation failures. @@ -200,10 +221,8 @@ _objc_setBadAllocHandler(id _Nullable (* _Nonnull newHandler) #if !__OBJC2__ OBJC_EXPORT void _objc_error(id _Nullable rcv, const char * _Nonnull fmt, va_list args) - __attribute__((noreturn)) - __OSX_DEPRECATED(10.0, 10.5, "use other logging facilities instead") - __IOS_UNAVAILABLE __TVOS_UNAVAILABLE - __WATCHOS_UNAVAILABLE __BRIDGEOS_UNAVAILABLE; + __attribute__((noreturn, cold)) + OBJC_OSX_DEPRECATED_OTHERS_UNAVAILABLE(10.0, 10.5, "use other logging facilities instead"); #endif @@ -264,6 +283,10 @@ enum OBJC_TAG_XPC_2 = 13, OBJC_TAG_XPC_3 = 14, OBJC_TAG_XPC_4 = 15, + OBJC_TAG_NSColor = 16, + OBJC_TAG_UIColor = 17, + OBJC_TAG_CGColor = 18, + OBJC_TAG_NSIndexSet = 19, OBJC_TAG_First60BitPayload = 0, OBJC_TAG_Last60BitPayload = 6, @@ -396,18 +419,18 @@ _objc_makeTaggedPointer(objc_tag_index_t tag, uintptr_t value) // PAYLOAD_LSHIFT and PAYLOAD_RSHIFT are the payload extraction shifts. // They are reversed here for payload insertion. - // assert(_objc_taggedPointersEnabled()); + // ASSERT(_objc_taggedPointersEnabled()); if (tag <= OBJC_TAG_Last60BitPayload) { - // assert(((value << _OBJC_TAG_PAYLOAD_RSHIFT) >> _OBJC_TAG_PAYLOAD_LSHIFT) == value); + // ASSERT(((value << _OBJC_TAG_PAYLOAD_RSHIFT) >> _OBJC_TAG_PAYLOAD_LSHIFT) == value); uintptr_t result = (_OBJC_TAG_MASK | ((uintptr_t)tag << _OBJC_TAG_INDEX_SHIFT) | ((value << _OBJC_TAG_PAYLOAD_RSHIFT) >> _OBJC_TAG_PAYLOAD_LSHIFT)); return _objc_encodeTaggedPointer(result); } else { - // assert(tag >= OBJC_TAG_First52BitPayload); - // assert(tag <= OBJC_TAG_Last52BitPayload); - // assert(((value << _OBJC_TAG_EXT_PAYLOAD_RSHIFT) >> _OBJC_TAG_EXT_PAYLOAD_LSHIFT) == value); + // ASSERT(tag >= OBJC_TAG_First52BitPayload); + // ASSERT(tag <= OBJC_TAG_Last52BitPayload); + // ASSERT(((value << _OBJC_TAG_EXT_PAYLOAD_RSHIFT) >> _OBJC_TAG_EXT_PAYLOAD_LSHIFT) == value); uintptr_t result = (_OBJC_TAG_EXT_MASK | ((uintptr_t)(tag - OBJC_TAG_First52BitPayload) << _OBJC_TAG_EXT_INDEX_SHIFT) | @@ -425,7 +448,7 @@ _objc_isTaggedPointer(const void * _Nullable ptr) static inline objc_tag_index_t _objc_getTaggedPointerTag(const void * _Nullable ptr) { - // assert(_objc_isTaggedPointer(ptr)); + // ASSERT(_objc_isTaggedPointer(ptr)); uintptr_t value = _objc_decodeTaggedPointer(ptr); uintptr_t basicTag = (value >> _OBJC_TAG_INDEX_SHIFT) & _OBJC_TAG_INDEX_MASK; uintptr_t extTag = (value >> _OBJC_TAG_EXT_INDEX_SHIFT) & _OBJC_TAG_EXT_INDEX_MASK; @@ -439,7 +462,7 @@ _objc_getTaggedPointerTag(const void * _Nullable ptr) static inline uintptr_t _objc_getTaggedPointerValue(const void * _Nullable ptr) { - // assert(_objc_isTaggedPointer(ptr)); + // ASSERT(_objc_isTaggedPointer(ptr)); uintptr_t value = _objc_decodeTaggedPointer(ptr); uintptr_t basicTag = (value >> _OBJC_TAG_INDEX_SHIFT) & _OBJC_TAG_INDEX_MASK; if (basicTag == _OBJC_TAG_INDEX_MASK) { @@ -452,7 +475,7 @@ _objc_getTaggedPointerValue(const void * _Nullable ptr) static inline intptr_t _objc_getTaggedPointerSignedValue(const void * _Nullable ptr) { - // assert(_objc_isTaggedPointer(ptr)); + // ASSERT(_objc_isTaggedPointer(ptr)); uintptr_t value = _objc_decodeTaggedPointer(ptr); uintptr_t basicTag = (value >> _OBJC_TAG_INDEX_SHIFT) & _OBJC_TAG_INDEX_MASK; if (basicTag == _OBJC_TAG_INDEX_MASK) { @@ -666,8 +689,32 @@ objc_allocWithZone(Class _Nullable cls) OBJC_EXPORT id _Nullable objc_alloc_init(Class _Nullable cls) - OBJC_AVAILABLE(10.14, 12.0, 12.0, 5.0, 3.0); -// rdar://44986431 fixme correct availability for objc_alloc_init() + OBJC_AVAILABLE(10.14.4, 12.2, 12.2, 5.2, 3.2); + +OBJC_EXPORT id _Nullable +objc_opt_new(Class _Nullable cls) + OBJC_AVAILABLE(10.15, 13.0, 13.0, 6.0, 5.0); + +OBJC_EXPORT id _Nullable +objc_opt_self(id _Nullable obj) + OBJC_AVAILABLE(10.15, 13.0, 13.0, 6.0, 5.0); + +OBJC_EXPORT Class _Nullable +objc_opt_class(id _Nullable obj) + OBJC_AVAILABLE(10.15, 13.0, 13.0, 6.0, 5.0); + +OBJC_EXPORT BOOL +objc_opt_respondsToSelector(id _Nullable obj, SEL _Nullable sel) + OBJC_AVAILABLE(10.15, 13.0, 13.0, 6.0, 5.0); + +OBJC_EXPORT BOOL +objc_opt_isKindOfClass(id _Nullable obj, Class _Nullable cls) + OBJC_AVAILABLE(10.15, 13.0, 13.0, 6.0, 5.0); + + +OBJC_EXPORT BOOL +objc_sync_try_enter(id _Nonnull obj) + OBJC_AVAILABLE(10.15, 13.0, 13.0, 6.0, 5.0); OBJC_EXPORT id _Nullable objc_retain(id _Nullable obj) @@ -776,6 +823,26 @@ _objc_autoreleasePoolPop(void * _Nonnull context) OBJC_AVAILABLE(10.7, 5.0, 9.0, 1.0, 2.0); +/** + * Load a classref, which is a chunk of data containing a class + * pointer. May perform initialization and rewrite the classref to + * point to a new object, if needed. Returns the loaded Class. + * + * In particular, if the classref points to a stub class (indicated + * by setting the bottom bit of the class pointer to 1), then this + * will call the stub's initializer and then replace the classref + * value with the value returned by the initializer. + * + * @param clsref The classref to load. + * @return The loaded Class pointer. + */ +#if __OBJC2__ +OBJC_EXPORT _Nullable Class +objc_loadClassref(_Nullable Class * _Nonnull clsref) + OBJC_AVAILABLE(10.15, 13.0, 13.0, 6.0, 5.0); +#endif + + // Extra @encode data for XPC, or NULL OBJC_EXPORT const char * _Nullable _protocol_getMethodTypeEncoding(Protocol * _Nonnull proto, SEL _Nonnull sel, @@ -783,6 +850,32 @@ _protocol_getMethodTypeEncoding(Protocol * _Nonnull proto, SEL _Nonnull sel, OBJC_AVAILABLE(10.8, 6.0, 9.0, 1.0, 2.0); +/** + * Function type for a function that is called when a realized class + * is about to be initialized. + * + * @param context The context pointer the function was registered with. + * @param cls The class that's about to be initialized. + */ +struct mach_header; +typedef void (*_objc_func_willInitializeClass)(void * _Nullable context, Class _Nonnull cls); + +/** + * Add a function to be called when a realized class is about to be + * initialized. The class can be queried and manipulated using runtime + * functions. Don't message it. + * + * When adding a new function, that function is immediately called with all + * realized classes that are already initialized or are in the process + * of initialization. + * + * @param func The function to add. + * @param context A context pointer that will be passed to the function when called. + */ +#define OBJC_WILLINITIALIZECLASSFUNC_DEFINED 1 +OBJC_EXPORT void _objc_addWillInitializeClassFunc(_objc_func_willInitializeClass _Nonnull func, void * _Nullable context) + OBJC_AVAILABLE(10.15, 13.0, 13.0, 6.0, 4.0); + // API to only be called by classes that provide their own reference count storage OBJC_EXPORT void diff --git a/runtime/objc-layout.mm b/runtime/objc-layout.mm index d125f08..47993ea 100644 --- a/runtime/objc-layout.mm +++ b/runtime/objc-layout.mm @@ -338,8 +338,8 @@ static void decompress_layout(const unsigned char *layout_string, layout_bitmap realloc(bits->bits, (newAllocated+7) / 8); bits->bitsAllocated = newAllocated; } - assert(bits->bitsAllocated >= bits->bitCount); - assert(bits->bitsAllocated >= newCount); + ASSERT(bits->bitsAllocated >= bits->bitCount); + ASSERT(bits->bitsAllocated >= newCount); } diff --git a/runtime/objc-loadmethod.mm b/runtime/objc-loadmethod.mm index 55ef00b..d095cee 100644 --- a/runtime/objc-loadmethod.mm +++ b/runtime/objc-loadmethod.mm @@ -201,7 +201,7 @@ static void call_class_loads(void) if (PrintLoading) { _objc_inform("LOAD: +[%s load]\n", cls->nameForLogging()); } - (*load_method)(cls, SEL_load); + (*load_method)(cls, @selector(load)); } // Destroy the detached list. @@ -248,7 +248,7 @@ static bool call_category_loads(void) cls->nameForLogging(), _category_getName(cat)); } - (*load_method)(cls, SEL_load); + (*load_method)(cls, @selector(load)); cats[i].cat = nil; } } diff --git a/runtime/objc-locks.h b/runtime/objc-locks.h index 8cb7907..cd4a419 100644 --- a/runtime/objc-locks.h +++ b/runtime/objc-locks.h @@ -37,7 +37,9 @@ extern monitor_t classInitLock; extern mutex_t selLock; +#if CONFIG_USE_CACHE_LOCK extern mutex_t cacheUpdateLock; +#endif extern recursive_mutex_t loadMethodLock; extern mutex_t crashlog_lock; extern spinlock_t objcMsgLogLock; diff --git a/runtime/objc-object.h b/runtime/objc-object.h index 4c68e24..2c17c94 100644 --- a/runtime/objc-object.h +++ b/runtime/objc-object.h @@ -55,8 +55,8 @@ extern "C" { ALWAYS_INLINE Class & classForIndex(uintptr_t index) { - assert(index > 0); - assert(index < (uintptr_t)objc_indexed_classes_count); + ASSERT(index > 0); + ASSERT(index < (uintptr_t)objc_indexed_classes_count); return objc_indexed_classes[index]; } @@ -76,20 +76,26 @@ objc_object::isClass() inline Class objc_object::getIsa() { - if (!isTaggedPointer()) return ISA(); + if (fastpath(!isTaggedPointer())) return ISA(); - uintptr_t ptr = (uintptr_t)this; - if (isExtTaggedPointer()) { - uintptr_t slot = - (ptr >> _OBJC_TAG_EXT_SLOT_SHIFT) & _OBJC_TAG_EXT_SLOT_MASK; - return objc_tag_ext_classes[slot]; - } else { - uintptr_t slot = - (ptr >> _OBJC_TAG_SLOT_SHIFT) & _OBJC_TAG_SLOT_MASK; - return objc_tag_classes[slot]; + extern objc_class OBJC_CLASS_$___NSUnrecognizedTaggedPointer; + uintptr_t slot, ptr = (uintptr_t)this; + Class cls; + + slot = (ptr >> _OBJC_TAG_SLOT_SHIFT) & _OBJC_TAG_SLOT_MASK; + cls = objc_tag_classes[slot]; + if (slowpath(cls == (Class)&OBJC_CLASS_$___NSUnrecognizedTaggedPointer)) { + slot = (ptr >> _OBJC_TAG_EXT_SLOT_SHIFT) & _OBJC_TAG_EXT_SLOT_MASK; + cls = objc_tag_ext_classes[slot]; } + return cls; } +inline uintptr_t +objc_object::isaBits() const +{ + return isa.bits; +} inline bool objc_object::isTaggedPointer() @@ -122,6 +128,12 @@ objc_object::getIsa() return ISA(); } +inline uintptr_t +objc_object::isaBits() const +{ + return isa.bits; +} + inline bool objc_object::isTaggedPointer() @@ -151,7 +163,7 @@ objc_object::isExtTaggedPointer() inline Class objc_object::ISA() { - assert(!isTaggedPointer()); + ASSERT(!isTaggedPointer()); #if SUPPORT_INDEXED_ISA if (isa.nonpointer) { uintptr_t slot = isa.indexcls; @@ -163,6 +175,12 @@ objc_object::ISA() #endif } +inline Class +objc_object::rawISA() +{ + ASSERT(!isTaggedPointer() && !isa.nonpointer); + return (Class)isa.bits; +} inline bool objc_object::hasNonpointerIsa() @@ -196,8 +214,8 @@ objc_object::initProtocolIsa(Class cls) inline void objc_object::initInstanceIsa(Class cls, bool hasCxxDtor) { - assert(!cls->instancesRequireRawIsa()); - assert(hasCxxDtor == cls->hasCxxDtor()); + ASSERT(!cls->instancesRequireRawIsa()); + ASSERT(hasCxxDtor == cls->hasCxxDtor()); initIsa(cls, true, hasCxxDtor); } @@ -205,18 +223,18 @@ objc_object::initInstanceIsa(Class cls, bool hasCxxDtor) inline void objc_object::initIsa(Class cls, bool nonpointer, bool hasCxxDtor) { - assert(!isTaggedPointer()); + ASSERT(!isTaggedPointer()); if (!nonpointer) { - isa.cls = cls; + isa = isa_t((uintptr_t)cls); } else { - assert(!DisableNonpointerIsa); - assert(!cls->instancesRequireRawIsa()); + ASSERT(!DisableNonpointerIsa); + ASSERT(!cls->instancesRequireRawIsa()); isa_t newisa(0); #if SUPPORT_INDEXED_ISA - assert(cls->classArrayIndex() > 0); + ASSERT(cls->classArrayIndex() > 0); newisa.bits = ISA_INDEX_MAGIC_VALUE; // isa.magic is part of ISA_MAGIC_VALUE // isa.nonpointer is part of ISA_MAGIC_VALUE @@ -249,7 +267,7 @@ objc_object::changeIsa(Class newCls) // assert(newCls->isFuture() || // newCls->isInitializing() || newCls->isInitialized()); - assert(!isTaggedPointer()); + ASSERT(!isTaggedPointer()); isa_t oldisa; isa_t newisa; @@ -271,7 +289,7 @@ objc_object::changeIsa(Class newCls) // isa.magic is part of ISA_MAGIC_VALUE // isa.nonpointer is part of ISA_MAGIC_VALUE newisa.has_cxx_dtor = newCls->hasCxxDtor(); - assert(newCls->classArrayIndex() > 0); + ASSERT(newCls->classArrayIndex() > 0); newisa.indexcls = (uintptr_t)newCls->classArrayIndex(); #else if (oldisa.bits == 0) newisa.bits = ISA_MAGIC_VALUE; @@ -351,7 +369,7 @@ objc_object::setHasAssociatedObjects() inline bool objc_object::isWeaklyReferenced() { - assert(!isTaggedPointer()); + ASSERT(!isTaggedPointer()); if (isa.nonpointer) return isa.weakly_referenced; else return sidetable_isWeaklyReferenced(); } @@ -380,7 +398,7 @@ objc_object::setWeaklyReferenced_nolock() inline bool objc_object::hasCxxDtor() { - assert(!isTaggedPointer()); + ASSERT(!isTaggedPointer()); if (isa.nonpointer) return isa.has_cxx_dtor; else return isa.cls->hasCxxDtor(); } @@ -436,13 +454,13 @@ objc_object::rootDealloc() inline id objc_object::retain() { - assert(!isTaggedPointer()); + ASSERT(!isTaggedPointer()); if (fastpath(!ISA()->hasCustomRR())) { return rootRetain(); } - return ((id(*)(objc_object *, SEL))objc_msgSend)(this, SEL_retain); + return ((id(*)(objc_object *, SEL))objc_msgSend)(this, @selector(retain)); } @@ -484,6 +502,7 @@ objc_object::rootRetain(bool tryRetain, bool handleOverflow) newisa = oldisa; if (slowpath(!newisa.nonpointer)) { ClearExclusive(&isa.bits); + if (rawISA()->isMetaClass()) return (id)this; if (!tryRetain && sideTableLocked) sidetable_unlock(); if (tryRetain) return sidetable_tryRetain() ? (id)this : nil; else return sidetable_retain(); @@ -527,14 +546,14 @@ objc_object::rootRetain(bool tryRetain, bool handleOverflow) inline void objc_object::release() { - assert(!isTaggedPointer()); + ASSERT(!isTaggedPointer()); if (fastpath(!ISA()->hasCustomRR())) { rootRelease(); return; } - ((void(*)(objc_object *, SEL))objc_msgSend)(this, SEL_release); + ((void(*)(objc_object *, SEL))objc_msgSend)(this, @selector(release)); } @@ -576,6 +595,7 @@ objc_object::rootRelease(bool performDealloc, bool handleUnderflow) newisa = oldisa; if (slowpath(!newisa.nonpointer)) { ClearExclusive(&isa.bits); + if (rawISA()->isMetaClass()) return false; if (sideTableLocked) sidetable_unlock(); return sidetable_release(performDealloc); } @@ -677,9 +697,10 @@ objc_object::rootRelease(bool performDealloc, bool handleUnderflow) if (slowpath(sideTableLocked)) sidetable_unlock(); - __sync_synchronize(); + __c11_atomic_thread_fence(__ATOMIC_ACQUIRE); + if (performDealloc) { - ((void(*)(objc_object *, SEL))objc_msgSend)(this, SEL_dealloc); + ((void(*)(objc_object *, SEL))objc_msgSend)(this, @selector(dealloc)); } return true; } @@ -689,10 +710,12 @@ objc_object::rootRelease(bool performDealloc, bool handleUnderflow) inline id objc_object::autorelease() { - if (isTaggedPointer()) return (id)this; - if (fastpath(!ISA()->hasCustomRR())) return rootAutorelease(); + ASSERT(!isTaggedPointer()); + if (fastpath(!ISA()->hasCustomRR())) { + return rootAutorelease(); + } - return ((id(*)(objc_object *, SEL))objc_msgSend)(this, SEL_autorelease); + return ((id(*)(objc_object *, SEL))objc_msgSend)(this, @selector(autorelease)); } @@ -737,10 +760,15 @@ objc_object::rootRetainCount() inline Class objc_object::ISA() { - assert(!isTaggedPointer()); + ASSERT(!isTaggedPointer()); return isa.cls; } +inline Class +objc_object::rawISA() +{ + return ISA(); +} inline bool objc_object::hasNonpointerIsa() @@ -752,7 +780,7 @@ objc_object::hasNonpointerIsa() inline void objc_object::initIsa(Class cls) { - assert(!isTaggedPointer()); + ASSERT(!isTaggedPointer()); isa = (uintptr_t)cls; } @@ -793,7 +821,7 @@ objc_object::changeIsa(Class cls) // assert(cls->isFuture() || // cls->isInitializing() || cls->isInitialized()); - assert(!isTaggedPointer()); + ASSERT(!isTaggedPointer()); isa_t oldisa, newisa; newisa.cls = cls; @@ -826,7 +854,7 @@ objc_object::setHasAssociatedObjects() inline bool objc_object::isWeaklyReferenced() { - assert(!isTaggedPointer()); + ASSERT(!isTaggedPointer()); return sidetable_isWeaklyReferenced(); } @@ -835,7 +863,7 @@ objc_object::isWeaklyReferenced() inline void objc_object::setWeaklyReferenced_nolock() { - assert(!isTaggedPointer()); + ASSERT(!isTaggedPointer()); sidetable_setWeaklyReferenced_nolock(); } @@ -844,7 +872,7 @@ objc_object::setWeaklyReferenced_nolock() inline bool objc_object::hasCxxDtor() { - assert(!isTaggedPointer()); + ASSERT(!isTaggedPointer()); return isa.cls->hasCxxDtor(); } @@ -876,13 +904,13 @@ objc_object::rootDealloc() inline id objc_object::retain() { - assert(!isTaggedPointer()); + ASSERT(!isTaggedPointer()); if (fastpath(!ISA()->hasCustomRR())) { return sidetable_retain(); } - return ((id(*)(objc_object *, SEL))objc_msgSend)(this, SEL_retain); + return ((id(*)(objc_object *, SEL))objc_msgSend)(this, @selector(retain)); } @@ -901,14 +929,14 @@ objc_object::rootRetain() inline void objc_object::release() { - assert(!isTaggedPointer()); + ASSERT(!isTaggedPointer()); if (fastpath(!ISA()->hasCustomRR())) { sidetable_release(); return; } - ((void(*)(objc_object *, SEL))objc_msgSend)(this, SEL_release); + ((void(*)(objc_object *, SEL))objc_msgSend)(this, @selector(release)); } @@ -939,7 +967,7 @@ objc_object::autorelease() if (isTaggedPointer()) return (id)this; if (fastpath(!ISA()->hasCustomRR())) return rootAutorelease(); - return ((id(*)(objc_object *, SEL))objc_msgSend)(this, SEL_autorelease); + return ((id(*)(objc_object *, SEL))objc_msgSend)(this, @selector(autorelease)); } @@ -1168,7 +1196,7 @@ setReturnDisposition(ReturnDisposition disposition) static ALWAYS_INLINE bool prepareOptimizedReturn(ReturnDisposition disposition) { - assert(getReturnDisposition() == ReturnAtPlus0); + ASSERT(getReturnDisposition() == ReturnAtPlus0); if (callerAcceptsOptimizedReturn(__builtin_return_address(0))) { if (disposition) setReturnDisposition(disposition); diff --git a/runtime/objc-opt.mm b/runtime/objc-opt.mm index 498c349..f4afb20 100644 --- a/runtime/objc-opt.mm +++ b/runtime/objc-opt.mm @@ -49,11 +49,31 @@ bool noMissingWeakSuperclasses(void) return false; } +bool header_info::hasPreoptimizedSelectors() const +{ + return false; +} + +bool header_info::hasPreoptimizedClasses() const +{ + return false; +} + +bool header_info::hasPreoptimizedProtocols() const +{ + return false; +} + objc_selopt_t *preoptimizedSelectors(void) { return nil; } +bool sharedCacheSupportsProtocolRoots(void) +{ + return false; +} + Protocol *getPreoptimizedProtocol(const char *name) { return nil; @@ -75,11 +95,6 @@ Class getPreoptimizedClass(const char *name) return nil; } -bool sharedRegionContains(const void *ptr) -{ - return false; -} - header_info *preoptimizedHinfoForHeader(const headerType *mhdr) { return nil; @@ -109,6 +124,7 @@ void preopt_init(void) using objc_opt::objc_stringhash_offset_t; using objc_opt::objc_protocolopt_t; +using objc_opt::objc_protocolopt2_t; using objc_opt::objc_clsopt_t; using objc_opt::objc_headeropt_ro_t; using objc_opt::objc_headeropt_rw_t; @@ -121,8 +137,6 @@ void preopt_init(void) // opt is initialized to ~0 to detect incorrect use before preopt_init() static const objc_opt_t *opt = (objc_opt_t *)~0; -static uintptr_t shared_cache_start; -static uintptr_t shared_cache_end; static bool preoptimized; extern const objc_opt_t _objc_opt_data; // in __TEXT, __objc_opt_ro @@ -161,15 +175,53 @@ bool noMissingWeakSuperclasses(void) return YES; } +bool header_info::hasPreoptimizedSelectors() const +{ + // preoptimization disabled for some reason + if (!preoptimized) return NO; + + return info()->optimizedByDyld() || info()->optimizedByDyldClosure(); +} + +bool header_info::hasPreoptimizedClasses() const +{ + // preoptimization disabled for some reason + if (!preoptimized) return NO; + + return info()->optimizedByDyld() || info()->optimizedByDyldClosure(); +} + +bool header_info::hasPreoptimizedProtocols() const +{ + // preoptimization disabled for some reason + if (!preoptimized) return NO; + + return info()->optimizedByDyld() || info()->optimizedByDyldClosure(); +} + objc_selopt_t *preoptimizedSelectors(void) { return opt ? opt->selopt() : nil; } +bool sharedCacheSupportsProtocolRoots(void) +{ + return (opt != nil) && (opt->protocolopt2() != nil); +} -Protocol *getPreoptimizedProtocol(const char *name) + +Protocol *getSharedCachePreoptimizedProtocol(const char *name) { + // Look in the new table if we have it + if (objc_protocolopt2_t *protocols2 = opt ? opt->protocolopt2() : nil) { + // Note, we have to pass the lambda directly here as otherwise we would try + // message copy and autorelease. + return (Protocol *)protocols2->getProtocol(name, [](const void* hi) -> bool { + return ((header_info *)hi)->isLoaded(); + }); + } + objc_protocolopt_t *protocols = opt ? opt->protocolopt() : nil; if (!protocols) return nil; @@ -177,6 +229,31 @@ bool noMissingWeakSuperclasses(void) } +Protocol *getPreoptimizedProtocol(const char *name) +{ + // Try table from dyld closure first. It was built to ignore the dupes it + // knows will come from the cache, so anything left in here was there when + // we launched + Protocol *result = nil; + // Note, we have to pass the lambda directly here as otherwise we would try + // message copy and autorelease. + _dyld_for_each_objc_protocol(name, [&result](void* protocolPtr, bool isLoaded, bool* stop) { + // Skip images which aren't loaded. This supports the case where dyld + // might soft link an image from the main binary so its possibly not + // loaded yet. + if (!isLoaded) + return; + + // Found a loaded image with this class name, so stop the search + result = (Protocol *)protocolPtr; + *stop = true; + }); + if (result) return result; + + return getSharedCachePreoptimizedProtocol(name); +} + + unsigned int getPreoptimizedClassUnreasonableCount() { objc_clsopt_t *classes = opt ? opt->clsopt() : nil; @@ -193,6 +270,25 @@ Class getPreoptimizedClass(const char *name) objc_clsopt_t *classes = opt ? opt->clsopt() : nil; if (!classes) return nil; + // Try table from dyld closure first. It was built to ignore the dupes it + // knows will come from the cache, so anything left in here was there when + // we launched + Class result = nil; + // Note, we have to pass the lambda directly here as otherwise we would try + // message copy and autorelease. + _dyld_for_each_objc_class(name, [&result](void* classPtr, bool isLoaded, bool* stop) { + // Skip images which aren't loaded. This supports the case where dyld + // might soft link an image from the main binary so its possibly not + // loaded yet. + if (!isLoaded) + return; + + // Found a loaded image with this class name, so stop the search + result = (Class)classPtr; + *stop = true; + }); + if (result) return result; + void *cls; void *hi; uint32_t count = classes->getClassAndHeader(name, cls, hi); @@ -258,16 +354,6 @@ Class getPreoptimizedClass(const char *name) return nil; } -/*********************************************************************** -* Return YES if the given pointer lies within the shared cache. -* If the shared cache is not set up or is not valid, -**********************************************************************/ -bool sharedRegionContains(const void *ptr) -{ - uintptr_t address = (uintptr_t)ptr; - return shared_cache_start <= address && address < shared_cache_end; -} - namespace objc_opt { struct objc_headeropt_ro_t { uint32_t count; @@ -276,7 +362,7 @@ bool sharedRegionContains(const void *ptr) header_info *get(const headerType *mhdr) { - assert(entsize == sizeof(header_info)); + ASSERT(entsize == sizeof(header_info)); int32_t start = 0; int32_t end = count; @@ -337,7 +423,7 @@ bool sharedRegionContains(const void *ptr) hdr->fname(), hdr, hinfoRO, hinfoRW); } int32_t index = (int32_t)(hdr - hinfoRO->headers); - assert(hinfoRW->entsize == sizeof(header_info_rw)); + ASSERT(hinfoRW->entsize == sizeof(header_info_rw)); return &hinfoRW->headers[index]; } @@ -346,12 +432,10 @@ void preopt_init(void) { // Get the memory region occupied by the shared cache. size_t length; - const void *start = _dyld_get_shared_cache_range(&length); + const uintptr_t start = (uintptr_t)_dyld_get_shared_cache_range(&length); + if (start) { - shared_cache_start = (uintptr_t)start; - shared_cache_end = shared_cache_start + length; - } else { - shared_cache_start = shared_cache_end = 0; + objc::dataSegmentsRanges.add(start, start + length); } // `opt` not set at compile time in order to detect too-early usage diff --git a/runtime/objc-os.h b/runtime/objc-os.h index ae29020..c28ba05 100644 --- a/runtime/objc-os.h +++ b/runtime/objc-os.h @@ -29,8 +29,10 @@ #ifndef _OBJC_OS_H #define _OBJC_OS_H +#include #include #include "objc-config.h" +#include "objc-private.h" #ifdef __LP64__ # define WORD_SHIFT 3UL @@ -48,7 +50,9 @@ static inline uint32_t word_align(uint32_t x) { static inline size_t word_align(size_t x) { return (x + WORD_MASK) & ~WORD_MASK; } - +static inline size_t align16(size_t x) { + return (x + size_t(15)) & ~size_t(15); +} // Mix-in for classes that must not be copied. class nocopy_t { @@ -60,6 +64,34 @@ class nocopy_t { ~nocopy_t() = default; }; +// Version of std::atomic that does not allow implicit conversions +// to/from the wrapped type, and requires an explicit memory order +// be passed to load() and store(). +template +struct explicit_atomic : public std::atomic { + explicit explicit_atomic(T initial) noexcept : std::atomic(std::move(initial)) {} + operator T() const = delete; + + T load(std::memory_order order) const noexcept { + return std::atomic::load(order); + } + void store(T desired, std::memory_order order) noexcept { + std::atomic::store(desired, order); + } + + // Convert a normal pointer to an atomic pointer. This is a + // somewhat dodgy thing to do, but if the atomic type is lock + // free and the same size as the non-atomic type, we know the + // representations are the same, and the compiler generates good + // code. + static explicit_atomic *from_pointer(T *ptr) { + static_assert(sizeof(explicit_atomic *) == sizeof(T *), + "Size of atomic must match size of original"); + explicit_atomic *atomic = (explicit_atomic *)ptr; + ASSERT(atomic->is_lock_free()); + return atomic; + } +}; #if TARGET_OS_MAC @@ -114,7 +146,7 @@ void vsyslog(int, const char *, va_list) UNAVAILABLE_ATTRIBUTE; #define ALWAYS_INLINE inline __attribute__((always_inline)) -#define NEVER_INLINE inline __attribute__((noinline)) +#define NEVER_INLINE __attribute__((noinline)) #define fastpath(x) (__builtin_expect(bool(x), 1)) #define slowpath(x) (__builtin_expect(bool(x), 0)) @@ -280,10 +312,10 @@ ClearExclusive(uintptr_t *dst __unused) #include extern void _objc_fatal(const char *fmt, ...) - __attribute__((noreturn, format (printf, 1, 2))); + __attribute__((noreturn, cold, format (printf, 1, 2))); extern void _objc_fatal_with_reason(uint64_t reason, uint64_t flags, const char *fmt, ...) - __attribute__((noreturn, format (printf, 3, 4))); + __attribute__((noreturn, cold, format (printf, 3, 4))); #define INIT_ONCE_PTR(var, create, delete) \ do { \ @@ -393,7 +425,7 @@ typedef DWORD objc_thread_t; // thread ID static __inline int thread_equal(objc_thread_t t1, objc_thread_t t2) { return t1 == t2; } -static __inline objc_thread_t thread_self(void) { +static __inline objc_thread_t objc_thread_self(void) { return GetCurrentThreadId(); } @@ -455,15 +487,15 @@ typedef struct { #define RECURSIVE_MUTEX_NOT_LOCKED 1 extern void recursive_mutex_init(recursive_mutex_t *m); static __inline int _recursive_mutex_lock_nodebug(recursive_mutex_t *m) { - assert(m->mutex); + ASSERT(m->mutex); return WaitForSingleObject(m->mutex, INFINITE); } static __inline bool _recursive_mutex_try_lock_nodebug(recursive_mutex_t *m) { - assert(m->mutex); + ASSERT(m->mutex); return (WAIT_OBJECT_0 == WaitForSingleObject(m->mutex, 0)); } static __inline int _recursive_mutex_unlock_nodebug(recursive_mutex_t *m) { - assert(m->mutex); + ASSERT(m->mutex); return ReleaseMutex(m->mutex) ? 0 : RECURSIVE_MUTEX_NOT_LOCKED; } @@ -598,10 +630,6 @@ typedef pthread_t objc_thread_t; static __inline int thread_equal(objc_thread_t t1, objc_thread_t t2) { return pthread_equal(t1, t2); } -static __inline objc_thread_t thread_self(void) { - return pthread_self(); -} - typedef pthread_key_t tls_key_t; @@ -611,7 +639,7 @@ static inline tls_key_t tls_create(void (*dtor)(void*)) { return k; } static inline void *tls_get(tls_key_t k) { - return pthread_getspecific(k); + return pthread_getspecific(k); } static inline void tls_set(tls_key_t k, void *value) { pthread_setspecific(k, value); @@ -619,21 +647,20 @@ static inline void tls_set(tls_key_t k, void *value) { #if SUPPORT_DIRECT_THREAD_KEYS -#if DEBUG -static bool is_valid_direct_key(tls_key_t k) { +static inline bool is_valid_direct_key(tls_key_t k) { return ( k == SYNC_DATA_DIRECT_KEY || k == SYNC_COUNT_DIRECT_KEY || k == AUTORELEASE_POOL_KEY + || k == _PTHREAD_TSD_SLOT_PTHREAD_SELF # if SUPPORT_RETURN_AUTORELEASE || k == RETURN_DISPOSITION_KEY # endif ); } -#endif -static inline void *tls_get_direct(tls_key_t k) +static inline void *tls_get_direct(tls_key_t k) { - assert(is_valid_direct_key(k)); + ASSERT(is_valid_direct_key(k)); if (_pthread_has_direct_tsd()) { return _pthread_getspecific_direct(k); @@ -643,7 +670,7 @@ static inline void *tls_get_direct(tls_key_t k) } static inline void tls_set_direct(tls_key_t k, void *value) { - assert(is_valid_direct_key(k)); + ASSERT(is_valid_direct_key(k)); if (_pthread_has_direct_tsd()) { _pthread_setspecific_direct(k, value); @@ -652,21 +679,18 @@ static inline void tls_set_direct(tls_key_t k, void *value) } } -// SUPPORT_DIRECT_THREAD_KEYS -#endif - - -static inline pthread_t pthread_self_direct() +__attribute__((const)) +static inline pthread_t objc_thread_self() { - return (pthread_t) - _pthread_getspecific_direct(_PTHREAD_TSD_SLOT_PTHREAD_SELF); + return (pthread_t)tls_get_direct(_PTHREAD_TSD_SLOT_PTHREAD_SELF); } - -static inline mach_port_t mach_thread_self_direct() +#else +__attribute__((const)) +static inline pthread_t objc_thread_self() { - return (mach_port_t)(uintptr_t) - _pthread_getspecific_direct(_PTHREAD_TSD_SLOT_MACH_THREAD_SELF); + return pthread_self(); } +#endif // SUPPORT_DIRECT_THREAD_KEYS template class mutex_tt; @@ -707,8 +731,10 @@ class mutex_tt : nocopy_t { void lock() { lockdebug_mutex_lock(this); + // + uint32_t opts = OS_UNFAIR_LOCK_DATA_SYNCHRONIZATION | OS_UNFAIR_LOCK_ADAPTIVE_SPIN; os_unfair_lock_lock_with_options_inline - (&mLock, OS_UNFAIR_LOCK_DATA_SYNCHRONIZATION); + (&mLock, (os_unfair_lock_options_t)opts); } void unlock() { @@ -811,6 +837,15 @@ class recursive_mutex_tt : nocopy_t { mLock = os_unfair_recursive_lock OS_UNFAIR_RECURSIVE_LOCK_INIT; } + bool tryLock() + { + if (os_unfair_recursive_lock_trylock(&mLock)) { + lockdebug_recursive_mutex_lock(this); + return true; + } + return false; + } + bool tryUnlock() { if (os_unfair_recursive_lock_tryunlock4objc(&mLock)) { diff --git a/runtime/objc-os.mm b/runtime/objc-os.mm index 9731e15..e2f65f2 100644 --- a/runtime/objc-os.mm +++ b/runtime/objc-os.mm @@ -28,6 +28,7 @@ #include "objc-private.h" #include "objc-loadmethod.h" +#include "objc-cache.h" #if TARGET_OS_WIN32 @@ -102,7 +103,7 @@ WINBOOL APIENTRY DllMain( HMODULE hModule, case DLL_PROCESS_ATTACH: environ_init(); tls_init(); - lock_init(); + runtime_init(); sel_init(3500); // old selector heuristic exception_init(); break; @@ -253,18 +254,13 @@ bool bad_magic(const headerType *mhdr) // Verify image_info size_t info_size = 0; const objc_image_info *image_info = _getObjcImageInfo(mhdr,&info_size); - assert(image_info == hi->info()); + ASSERT(image_info == hi->info()); #endif } else { // Didn't find an hinfo in the dyld shared cache. - // Weed out duplicates - for (hi = FirstHeader; hi; hi = hi->getNext()) { - if (mhdr == hi->mhdr()) return NULL; - } - // Locate the __OBJC segment size_t info_size = 0; unsigned long seg_size; @@ -342,7 +338,7 @@ bool bad_magic(const headerType *mhdr) **********************************************************************/ static bool shouldRejectGCApp(const header_info *hi) { - assert(hi->mhdr()->filetype == MH_EXECUTE); + ASSERT(hi->mhdr()->filetype == MH_EXECUTE); if (!hi->info()->supportsGC()) { // App does not use GC. Don't reject it. @@ -386,7 +382,7 @@ static bool shouldRejectGCApp(const header_info *hi) **********************************************************************/ static bool shouldRejectGCImage(const headerType *mhdr) { - assert(mhdr->filetype != MH_EXECUTE); + ASSERT(mhdr->filetype != MH_EXECUTE); objc_image_info *image_info; size_t size; @@ -419,6 +415,25 @@ static bool shouldRejectGCImage(const headerType *mhdr) #endif +// Swift currently adds 4 callbacks. +static GlobalSmallVector loadImageFuncs; + +void objc_addLoadImageFunc(objc_func_loadImage _Nonnull func) { + // Not supported on the old runtime. Not that the old runtime is supported anyway. +#if __OBJC2__ + mutex_locker_t lock(runtimeLock); + + // Call it with all the existing images first. + for (auto header = FirstHeader; header; header = header->getNext()) { + func((struct mach_header *)header->mhdr()); + } + + // Add it to the vector for future loads. + loadImageFuncs.append(func); +#endif +} + + /*********************************************************************** * map_images_nolock * Process the given images which are being mapped in by dyld. @@ -578,6 +593,13 @@ static bool shouldRejectGCImage(const headerType *mhdr) } firstTime = NO; + + // Call image load funcs after everything is set up. + for (auto func : loadImageFuncs) { + for (uint32_t i = 0; i < mhCount; i++) { + func(mhdrs[i]); + } + } } @@ -669,7 +691,9 @@ static void defineLockOrder() lockdebug_lock_precedes_lock(&impLock, &crashlog_lock); #endif lockdebug_lock_precedes_lock(&selLock, &crashlog_lock); +#if CONFIG_USE_CACHE_LOCK lockdebug_lock_precedes_lock(&cacheUpdateLock, &crashlog_lock); +#endif lockdebug_lock_precedes_lock(&objcMsgLogLock, &crashlog_lock); lockdebug_lock_precedes_lock(&AltHandlerDebugLock, &crashlog_lock); lockdebug_lock_precedes_lock(&AssociationsManagerLock, &crashlog_lock); @@ -691,7 +715,9 @@ static void defineLockOrder() lockdebug_lock_precedes_lock(&loadMethodLock, &impLock); #endif lockdebug_lock_precedes_lock(&loadMethodLock, &selLock); +#if CONFIG_USE_CACHE_LOCK lockdebug_lock_precedes_lock(&loadMethodLock, &cacheUpdateLock); +#endif lockdebug_lock_precedes_lock(&loadMethodLock, &objcMsgLogLock); lockdebug_lock_precedes_lock(&loadMethodLock, &AltHandlerDebugLock); lockdebug_lock_precedes_lock(&loadMethodLock, &AssociationsManagerLock); @@ -720,7 +746,9 @@ static void defineLockOrder() #endif PropertyAndCppObjectAndAssocLocksPrecedeLock(&classInitLock); PropertyAndCppObjectAndAssocLocksPrecedeLock(&selLock); +#if CONFIG_USE_CACHE_LOCK PropertyAndCppObjectAndAssocLocksPrecedeLock(&cacheUpdateLock); +#endif PropertyAndCppObjectAndAssocLocksPrecedeLock(&objcMsgLogLock); PropertyAndCppObjectAndAssocLocksPrecedeLock(&AltHandlerDebugLock); @@ -742,7 +770,9 @@ static void defineLockOrder() SideTableLocksPrecedeLock(&classInitLock); // Some operations may occur inside runtimeLock. lockdebug_lock_precedes_lock(&runtimeLock, &selLock); +#if CONFIG_USE_CACHE_LOCK lockdebug_lock_precedes_lock(&runtimeLock, &cacheUpdateLock); +#endif lockdebug_lock_precedes_lock(&runtimeLock, &DemangleCacheLock); #else // Runtime operations may occur inside SideTable locks @@ -752,7 +782,9 @@ static void defineLockOrder() // Method lookup and fixup. lockdebug_lock_precedes_lock(&methodListLock, &classLock); lockdebug_lock_precedes_lock(&methodListLock, &selLock); +#if CONFIG_USE_CACHE_LOCK lockdebug_lock_precedes_lock(&methodListLock, &cacheUpdateLock); +#endif lockdebug_lock_precedes_lock(&methodListLock, &impLock); lockdebug_lock_precedes_lock(&classLock, &selLock); lockdebug_lock_precedes_lock(&classLock, &cacheUpdateLock); @@ -792,7 +824,9 @@ void _objc_atfork_prepare() impLock.lock(); #endif selLock.lock(); +#if CONFIG_USE_CACHE_LOCK cacheUpdateLock.lock(); +#endif objcMsgLogLock.lock(); AltHandlerDebugLock.lock(); StructLocks.lockAll(); @@ -814,7 +848,9 @@ void _objc_atfork_parent() objcMsgLogLock.unlock(); crashlog_lock.unlock(); loadMethodLock.unlock(); +#if CONFIG_USE_CACHE_LOCK cacheUpdateLock.unlock(); +#endif selLock.unlock(); SideTableUnlockAll(); #if __OBJC2__ @@ -848,7 +884,9 @@ void _objc_atfork_child() objcMsgLogLock.forceReset(); crashlog_lock.forceReset(); loadMethodLock.forceReset(); +#if CONFIG_USE_CACHE_LOCK cacheUpdateLock.forceReset(); +#endif selLock.forceReset(); SideTableForceResetAll(); #if __OBJC2__ @@ -882,8 +920,10 @@ void _objc_init(void) environ_init(); tls_init(); static_init(); - lock_init(); + runtime_init(); exception_init(); + cache_init(); + _imp_implementationWithBlock_init(); _dyld_objc_notify_register(&map_images, load_images, unmap_image); } diff --git a/runtime/objc-private.h b/runtime/objc-private.h index f28cd38..fbeaeb5 100644 --- a/runtime/objc-private.h +++ b/runtime/objc-private.h @@ -46,6 +46,13 @@ #include #include +// An assert that's disabled for release builds but still ensures the expression compiles. +#ifdef NDEBUG +#define ASSERT(x) (void)sizeof(!(x)) +#else +#define ASSERT(x) assert(x) +#endif + struct objc_class; struct objc_object; @@ -81,8 +88,13 @@ struct objc_object { // ISA() assumes this is NOT a tagged pointer object Class ISA(); + // rawISA() assumes this is NOT a tagged pointer object or a non pointer ISA + Class rawISA(); + // getIsa() allows this to be a tagged pointer object Class getIsa(); + + uintptr_t isaBits() const; // initIsa() should be used to init the isa of new objects only. // If this object already has an isa, use changeIsa() for correctness. @@ -139,14 +151,14 @@ struct objc_object { // Slow paths for inline control id rootAutorelease2(); - bool overrelease_error(); + uintptr_t overrelease_error(); #if SUPPORT_NONPOINTER_ISA // Unified retain count manipulation for nonpointer isa id rootRetain(bool tryRetain, bool handleOverflow); bool rootRelease(bool performDealloc, bool handleUnderflow); id rootRetain_overflow(bool tryRetain); - bool rootRelease_underflow(bool performDealloc); + uintptr_t rootRelease_underflow(bool performDealloc); void clearDeallocating_slow(); @@ -242,6 +254,55 @@ struct objc_selopt_t; __BEGIN_DECLS +namespace objc { + +struct SafeRanges { +private: + struct Range { + uintptr_t start; + uintptr_t end; + + inline bool contains(uintptr_t ptr) const { + uintptr_t m_start, m_end; +#if __arm64__ + // Force the compiler to use ldp + // we really don't want 2 loads and 2 jumps. + __asm__( +# if __LP64__ + "ldp %x[one], %x[two], [%x[src]]" +# else + "ldp %w[one], %w[two], [%x[src]]" +# endif + : [one] "=r" (m_start), [two] "=r" (m_end) + : [src] "r" (this) + ); +#else + m_start = start; + m_end = end; +#endif + return m_start <= ptr && ptr < m_end; + } + }; + + struct Range *ranges; + uint32_t count; + uint32_t size : 31; + uint32_t sorted : 1; + +public: + inline bool contains(uint16_t witness, uintptr_t ptr) const { + return witness < count && ranges[witness].contains(ptr); + } + + bool find(uintptr_t ptr, uint32_t &pos); + void add(uintptr_t start, uintptr_t end); + void remove(uintptr_t start, uintptr_t end); +}; + +extern struct SafeRanges dataSegmentsRanges; + +} // objc + struct header_info; // Split out the rw data from header info. For now put it in a huge array @@ -357,6 +418,12 @@ typedef struct header_info { bool isPreoptimized() const; + bool hasPreoptimizedSelectors() const; + + bool hasPreoptimizedClasses() const; + + bool hasPreoptimizedProtocols() const; + #if !__OBJC2__ struct old_protocol **proto_refs; struct objc_module *mod_ptr; @@ -384,7 +451,6 @@ typedef struct header_info { extern header_info *FirstHeader; extern header_info *LastHeader; -extern int HeaderCount; extern void appendHeader(header_info *hi); extern void removeHeader(header_info *hi); @@ -416,26 +482,8 @@ static inline bool sectnameStartsWith(const char *sectname, const char *prefix){ extern void sel_init(size_t selrefCount); extern SEL sel_registerNameNoLock(const char *str, bool copy); -extern SEL SEL_load; -extern SEL SEL_initialize; -extern SEL SEL_resolveClassMethod; -extern SEL SEL_resolveInstanceMethod; extern SEL SEL_cxx_construct; extern SEL SEL_cxx_destruct; -extern SEL SEL_retain; -extern SEL SEL_release; -extern SEL SEL_autorelease; -extern SEL SEL_retainCount; -extern SEL SEL_alloc; -extern SEL SEL_allocWithZone; -extern SEL SEL_dealloc; -extern SEL SEL_copy; -extern SEL SEL_new; -extern SEL SEL_forwardInvocation; -extern SEL SEL_tryRetain; -extern SEL SEL_isDeallocating; -extern SEL SEL_retainWeakReference; -extern SEL SEL_allowsWeakReference; /* preoptimization */ extern void preopt_init(void); @@ -446,22 +494,33 @@ extern header_info *preoptimizedHinfoForHeader(const headerType *mhdr); extern objc_selopt_t *preoptimizedSelectors(void); +extern bool sharedCacheSupportsProtocolRoots(void); extern Protocol *getPreoptimizedProtocol(const char *name); +extern Protocol *getSharedCachePreoptimizedProtocol(const char *name); extern unsigned getPreoptimizedClassUnreasonableCount(); extern Class getPreoptimizedClass(const char *name); extern Class* copyPreoptimizedClasses(const char *name, int *outCount); -extern bool sharedRegionContains(const void *ptr); - extern Class _calloc_class(size_t size); /* method lookup */ -extern IMP lookUpImpOrNil(Class, SEL, id obj, bool initialize, bool cache, bool resolver); -extern IMP lookUpImpOrForward(Class, SEL, id obj, bool initialize, bool cache, bool resolver); +enum { + LOOKUP_INITIALIZE = 1, + LOOKUP_RESOLVER = 2, + LOOKUP_CACHE = 4, + LOOKUP_NIL = 8, +}; +extern IMP lookUpImpOrForward(id obj, SEL, Class cls, int behavior); + +static inline IMP +lookUpImpOrNil(id obj, SEL sel, Class cls, int behavior = 0) +{ + return lookUpImpOrForward(obj, sel, cls, behavior | LOOKUP_CACHE | LOOKUP_NIL); +} extern IMP lookupMethodInClassAndLoadCache(Class cls, SEL sel); -extern bool class_respondsToSelector_inst(Class cls, SEL sel, id inst); +extern BOOL class_respondsToSelector_inst(id inst, SEL sel, Class cls); extern Class class_initialize(Class cls, id inst); extern bool objcMsgLogEnabled; @@ -471,7 +530,6 @@ extern bool logMessageSend(bool isClassMethod, SEL selector); /* message dispatcher */ -extern IMP _class_lookupMethodAndLoadCache3(id, SEL, Class); #if !OBJC_OLD_DISPATCH_PROTOTYPES extern void _objc_msgForward_impcache(void); @@ -480,11 +538,13 @@ extern id _objc_msgForward_impcache(id, SEL, ...); #endif /* errors */ -extern void __objc_error(id, const char *, ...) __attribute__((format (printf, 2, 3), noreturn)); -extern void _objc_inform(const char *fmt, ...) __attribute__((format (printf, 1, 2))); -extern void _objc_inform_on_crash(const char *fmt, ...) __attribute__((format (printf, 1, 2))); -extern void _objc_inform_now_and_on_crash(const char *fmt, ...) __attribute__((format (printf, 1, 2))); -extern void _objc_inform_deprecated(const char *oldname, const char *newname) __attribute__((noinline)); +extern id(*badAllocHandler)(Class); +extern id _objc_callBadAllocHandler(Class cls) __attribute__((cold, noinline)); +extern void __objc_error(id, const char *, ...) __attribute__((cold, format (printf, 2, 3), noreturn)); +extern void _objc_inform(const char *fmt, ...) __attribute__((cold, format(printf, 1, 2))); +extern void _objc_inform_on_crash(const char *fmt, ...) __attribute__((cold, format (printf, 1, 2))); +extern void _objc_inform_now_and_on_crash(const char *fmt, ...) __attribute__((cold, format (printf, 1, 2))); +extern void _objc_inform_deprecated(const char *oldname, const char *newname) __attribute__((cold, noinline)); extern void inform_duplicate(const char *name, Class oldCls, Class cls); /* magic */ @@ -504,7 +564,6 @@ extern objc_property_attribute_t *copyPropertyAttributeList(const char *attrs, u extern char *copyPropertyAttributeValue(const char *attrs, const char *name); /* locking */ -extern void lock_init(void); class monitor_locker_t : nocopy_t { monitor_t& lock; @@ -542,6 +601,7 @@ extern void gdb_objc_class_changed(Class cls, unsigned long changes, const char #undef OPTION extern void environ_init(void); +extern void runtime_init(void); extern void logReplacedMethod(const char *className, SEL s, bool isMeta, const char *catName, IMP oldImp, IMP newImp); @@ -581,6 +641,7 @@ extern void arr_init(void); extern id objc_autoreleaseReturnValue(id obj); // block trampolines +extern void _imp_implementationWithBlock_init(void); extern IMP _imp_implementationWithBlockNoCopy(id block); // layout.h @@ -625,14 +686,18 @@ extern Class _class_remap(Class cls); extern Ivar _class_getVariable(Class cls, const char *name); extern unsigned _class_createInstancesFromZone(Class cls, size_t extraBytes, void *zone, id *results, unsigned num_requested); -extern id _objc_constructOrFree(id bytes, Class cls); extern const char *_category_getName(Category cat); extern const char *_category_getClassName(Category cat); extern Class _category_getClass(Category cat); extern IMP _category_getLoadMethod(Category cat); -extern id object_cxxConstructFromClass(id obj, Class cls); +enum { + OBJECT_CONSTRUCT_NONE = 0, + OBJECT_CONSTRUCT_FREE_ONFAILURE = 1, + OBJECT_CONSTRUCT_CALL_BADALLOC = 2, +}; +extern id object_cxxConstructFromClass(id obj, Class cls, int flags); extern void object_cxxDestruct(id obj); extern void fixupCopiedIvars(id newObject, id oldObject); @@ -817,8 +882,8 @@ class StripedMap { // Verify alignment expectations. uintptr_t base = (uintptr_t)&array[0].value; uintptr_t delta = (uintptr_t)&array[1].value - base; - assert(delta % CacheLineSize == 0); - assert(base % CacheLineSize == 0); + ASSERT(delta % CacheLineSize == 0); + ASSERT(base % CacheLineSize == 0); } #else constexpr StripedMap() {} @@ -919,6 +984,75 @@ class ChainedHookFunction { }; +// A small vector for use as a global variable. Only supports appending and +// iteration. Stores a single element inline, and multiple elements in a heap +// allocation. There is no attempt to amortize reallocation cost; this is +// intended to be used in situation where zero or one element is common, two +// might happen, and three or more is very rare. +// +// This does not clean up its allocation, and thus cannot be used as a local +// variable or member of something with limited lifetime. + +template +class GlobalSmallVector { + static_assert(std::is_pod::value, "SmallVector requires POD types"); + +protected: + unsigned count{0}; + union { + T inlineElements[InlineCount]; + T *elements; + }; + +public: + void append(const T &val) { + if (count < InlineCount) { + // We have space. Store the new value inline. + inlineElements[count] = val; + } else if (count == InlineCount) { + // Inline storage is full. Switch to a heap allocation. + T *newElements = (T *)malloc((count + 1) * sizeof(T)); + memcpy(newElements, inlineElements, count * sizeof(T)); + newElements[count] = val; + elements = newElements; + } else { + // Resize the heap allocation and append. + elements = (T *)realloc(elements, (count + 1) * sizeof(T)); + elements[count] = val; + } + count++; + } + + const T *begin() const { + return count <= InlineCount ? inlineElements : elements; + } + + const T *end() const { + return begin() + count; + } +}; + +// A small vector that cleans up its internal memory allocation when destroyed. +template +class SmallVector: public GlobalSmallVector { +public: + ~SmallVector() { + if (this->count > InlineCount) + free(this->elements); + } + + template + void initFrom(const GlobalSmallVector &other) { + ASSERT(this->count == 0); + this->count = (unsigned)(other.end() - other.begin()); + if (this->count > InlineCount) { + this->elements = (T *)memdup(other.begin(), this->count * sizeof(T)); + } else { + memcpy(this->inlineElements, other.begin(), this->count * sizeof(T)); + } + } +}; + // Pointer hash function. // This is not a terrific hash, but it is fast // and not outrageously flawed for our purposes. diff --git a/runtime/objc-references.h b/runtime/objc-references.h index 1e49f7c..8c79405 100644 --- a/runtime/objc-references.h +++ b/runtime/objc-references.h @@ -32,8 +32,9 @@ __BEGIN_DECLS -extern void _object_set_associative_reference(id object, void *key, id value, uintptr_t policy); -extern id _object_get_associative_reference(id object, void *key); +extern void _objc_associations_init(); +extern void _object_set_associative_reference(id object, const void *key, id value, uintptr_t policy); +extern id _object_get_associative_reference(id object, const void *key); extern void _object_remove_assocations(id object); __END_DECLS diff --git a/runtime/objc-references.mm b/runtime/objc-references.mm index 7de8187..caa8910 100644 --- a/runtime/objc-references.mm +++ b/runtime/objc-references.mm @@ -28,320 +28,209 @@ #include "objc-private.h" #include #include +#include "DenseMapExtras.h" -#if _LIBCPP_VERSION -# include -#else -# include - using namespace tr1; -#endif +// expanded policy bits. +enum { + OBJC_ASSOCIATION_SETTER_ASSIGN = 0, + OBJC_ASSOCIATION_SETTER_RETAIN = 1, + OBJC_ASSOCIATION_SETTER_COPY = 3, // NOTE: both bits are set, so we can simply test 1 bit in releaseValue below. + OBJC_ASSOCIATION_GETTER_READ = (0 << 8), + OBJC_ASSOCIATION_GETTER_RETAIN = (1 << 8), + OBJC_ASSOCIATION_GETTER_AUTORELEASE = (2 << 8) +}; -// wrap all the murky C++ details in a namespace to get them out of the way. +spinlock_t AssociationsManagerLock; -namespace objc_references_support { - struct DisguisedPointerEqual { - bool operator()(uintptr_t p1, uintptr_t p2) const { - return p1 == p2; - } - }; - - struct DisguisedPointerHash { - uintptr_t operator()(uintptr_t k) const { - // borrowed from CFSet.c -#if __LP64__ - uintptr_t a = 0x4368726973746F70ULL; - uintptr_t b = 0x686572204B616E65ULL; -#else - uintptr_t a = 0x4B616E65UL; - uintptr_t b = 0x4B616E65UL; -#endif - uintptr_t c = 1; - a += k; -#if __LP64__ - a -= b; a -= c; a ^= (c >> 43); - b -= c; b -= a; b ^= (a << 9); - c -= a; c -= b; c ^= (b >> 8); - a -= b; a -= c; a ^= (c >> 38); - b -= c; b -= a; b ^= (a << 23); - c -= a; c -= b; c ^= (b >> 5); - a -= b; a -= c; a ^= (c >> 35); - b -= c; b -= a; b ^= (a << 49); - c -= a; c -= b; c ^= (b >> 11); - a -= b; a -= c; a ^= (c >> 12); - b -= c; b -= a; b ^= (a << 18); - c -= a; c -= b; c ^= (b >> 22); -#else - a -= b; a -= c; a ^= (c >> 13); - b -= c; b -= a; b ^= (a << 8); - c -= a; c -= b; c ^= (b >> 13); - a -= b; a -= c; a ^= (c >> 12); - b -= c; b -= a; b ^= (a << 16); - c -= a; c -= b; c ^= (b >> 5); - a -= b; a -= c; a ^= (c >> 3); - b -= c; b -= a; b ^= (a << 10); - c -= a; c -= b; c ^= (b >> 15); -#endif - return c; - } - }; - - struct ObjectPointerLess { - bool operator()(const void *p1, const void *p2) const { - return p1 < p2; - } - }; - - struct ObjcPointerHash { - uintptr_t operator()(void *p) const { - return DisguisedPointerHash()(uintptr_t(p)); - } - }; - - // STL allocator that uses the runtime's internal allocator. - - template struct ObjcAllocator { - typedef T value_type; - typedef value_type* pointer; - typedef const value_type *const_pointer; - typedef value_type& reference; - typedef const value_type& const_reference; - typedef size_t size_type; - typedef ptrdiff_t difference_type; - - template struct rebind { typedef ObjcAllocator other; }; - - template ObjcAllocator(const ObjcAllocator&) {} - ObjcAllocator() {} - ObjcAllocator(const ObjcAllocator&) {} - ~ObjcAllocator() {} - - pointer address(reference x) const { return &x; } - const_pointer address(const_reference x) const { - return x; - } +namespace objc { - pointer allocate(size_type n, const_pointer = 0) { - return static_cast(::malloc(n * sizeof(T))); - } +class ObjcAssociation { + uintptr_t _policy; + id _value; +public: + ObjcAssociation(uintptr_t policy, id value) : _policy(policy), _value(value) {} + ObjcAssociation() : _policy(0), _value(nil) {} + ObjcAssociation(const ObjcAssociation &other) = default; + ObjcAssociation &operator=(const ObjcAssociation &other) = default; + ObjcAssociation(ObjcAssociation &&other) : ObjcAssociation() { + swap(other); + } + + inline void swap(ObjcAssociation &other) { + std::swap(_policy, other._policy); + std::swap(_value, other._value); + } - void deallocate(pointer p, size_type) { ::free(p); } + inline uintptr_t policy() const { return _policy; } + inline id value() const { return _value; } + + inline void acquireValue() { + if (_value) { + switch (_policy & 0xFF) { + case OBJC_ASSOCIATION_SETTER_RETAIN: + _value = objc_retain(_value); + break; + case OBJC_ASSOCIATION_SETTER_COPY: + _value = ((id(*)(id, SEL))objc_msgSend)(_value, @selector(copy)); + break; + } + } + } - size_type max_size() const { - return static_cast(-1) / sizeof(T); + inline void releaseHeldValue() { + if (_value && (_policy & OBJC_ASSOCIATION_SETTER_RETAIN)) { + objc_release(_value); } + } - void construct(pointer p, const value_type& x) { - new(p) value_type(x); + inline void retainReturnedValue() { + if (_value && (_policy & OBJC_ASSOCIATION_GETTER_RETAIN)) { + objc_retain(_value); } + } - void destroy(pointer p) { p->~value_type(); } - - void operator=(const ObjcAllocator&); - - }; - - template<> struct ObjcAllocator { - typedef void value_type; - typedef void* pointer; - typedef const void *const_pointer; - template struct rebind { typedef ObjcAllocator other; }; - }; - - typedef uintptr_t disguised_ptr_t; - inline disguised_ptr_t DISGUISE(id value) { return ~uintptr_t(value); } - inline id UNDISGUISE(disguised_ptr_t dptr) { return id(~dptr); } - - class ObjcAssociation { - uintptr_t _policy; - id _value; - public: - ObjcAssociation(uintptr_t policy, id value) : _policy(policy), _value(value) {} - ObjcAssociation() : _policy(0), _value(nil) {} - - uintptr_t policy() const { return _policy; } - id value() const { return _value; } - - bool hasValue() { return _value != nil; } - }; - -#if TARGET_OS_WIN32 - typedef hash_map ObjectAssociationMap; - typedef hash_map AssociationsHashMap; -#else - typedef ObjcAllocator > ObjectAssociationMapAllocator; - class ObjectAssociationMap : public std::map { - public: - void *operator new(size_t n) { return ::malloc(n); } - void operator delete(void *ptr) { ::free(ptr); } - }; - typedef ObjcAllocator > AssociationsHashMapAllocator; - class AssociationsHashMap : public unordered_map { - public: - void *operator new(size_t n) { return ::malloc(n); } - void operator delete(void *ptr) { ::free(ptr); } - }; -#endif -} + inline id autoreleaseReturnedValue() { + if (slowpath(_value && (_policy & OBJC_ASSOCIATION_GETTER_AUTORELEASE))) { + return objc_autorelease(_value); + } + return _value; + } +}; -using namespace objc_references_support; +typedef DenseMap ObjectAssociationMap; +typedef DenseMap, ObjectAssociationMap> AssociationsHashMap; // class AssociationsManager manages a lock / hash table singleton pair. -// Allocating an instance acquires the lock, and calling its assocations() -// method lazily allocates the hash table. - -spinlock_t AssociationsManagerLock; +// Allocating an instance acquires the lock class AssociationsManager { - // associative references: object pointer -> PtrPtrHashMap. - static AssociationsHashMap *_map; + using Storage = ExplicitInitDenseMap, ObjectAssociationMap>; + static Storage _mapStorage; + public: AssociationsManager() { AssociationsManagerLock.lock(); } ~AssociationsManager() { AssociationsManagerLock.unlock(); } - - AssociationsHashMap &associations() { - if (_map == NULL) - _map = new AssociationsHashMap(); - return *_map; + + AssociationsHashMap &get() { + return _mapStorage.get(); + } + + static void init() { + _mapStorage.init(); } }; -AssociationsHashMap *AssociationsManager::_map = NULL; +AssociationsManager::Storage AssociationsManager::_mapStorage; -// expanded policy bits. +} // namespace objc -enum { - OBJC_ASSOCIATION_SETTER_ASSIGN = 0, - OBJC_ASSOCIATION_SETTER_RETAIN = 1, - OBJC_ASSOCIATION_SETTER_COPY = 3, // NOTE: both bits are set, so we can simply test 1 bit in releaseValue below. - OBJC_ASSOCIATION_GETTER_READ = (0 << 8), - OBJC_ASSOCIATION_GETTER_RETAIN = (1 << 8), - OBJC_ASSOCIATION_GETTER_AUTORELEASE = (2 << 8) -}; +using namespace objc; + +void +_objc_associations_init() +{ + AssociationsManager::init(); +} + +id +_object_get_associative_reference(id object, const void *key) +{ + ObjcAssociation association{}; -id _object_get_associative_reference(id object, void *key) { - id value = nil; - uintptr_t policy = OBJC_ASSOCIATION_ASSIGN; { AssociationsManager manager; - AssociationsHashMap &associations(manager.associations()); - disguised_ptr_t disguised_object = DISGUISE(object); - AssociationsHashMap::iterator i = associations.find(disguised_object); + AssociationsHashMap &associations(manager.get()); + AssociationsHashMap::iterator i = associations.find((objc_object *)object); if (i != associations.end()) { - ObjectAssociationMap *refs = i->second; - ObjectAssociationMap::iterator j = refs->find(key); - if (j != refs->end()) { - ObjcAssociation &entry = j->second; - value = entry.value(); - policy = entry.policy(); - if (policy & OBJC_ASSOCIATION_GETTER_RETAIN) { - objc_retain(value); - } + ObjectAssociationMap &refs = i->second; + ObjectAssociationMap::iterator j = refs.find(key); + if (j != refs.end()) { + association = j->second; + association.retainReturnedValue(); } } } - if (value && (policy & OBJC_ASSOCIATION_GETTER_AUTORELEASE)) { - objc_autorelease(value); - } - return value; -} - -static id acquireValue(id value, uintptr_t policy) { - switch (policy & 0xFF) { - case OBJC_ASSOCIATION_SETTER_RETAIN: - return objc_retain(value); - case OBJC_ASSOCIATION_SETTER_COPY: - return ((id(*)(id, SEL))objc_msgSend)(value, SEL_copy); - } - return value; -} -static void releaseValue(id value, uintptr_t policy) { - if (policy & OBJC_ASSOCIATION_SETTER_RETAIN) { - return objc_release(value); - } + return association.autoreleaseReturnedValue(); } -struct ReleaseValue { - void operator() (ObjcAssociation &association) { - releaseValue(association.value(), association.policy()); - } -}; - -void _object_set_associative_reference(id object, void *key, id value, uintptr_t policy) { +void +_object_set_associative_reference(id object, const void *key, id value, uintptr_t policy) +{ // This code used to work when nil was passed for object and key. Some code // probably relies on that to not crash. Check and handle it explicitly. // rdar://problem/44094390 if (!object && !value) return; - - assert(object); - + if (object->getIsa()->forbidsAssociatedObjects()) _objc_fatal("objc_setAssociatedObject called on instance (%p) of class %s which does not allow associated objects", object, object_getClassName(object)); - + + DisguisedPtr disguised{(objc_object *)object}; + ObjcAssociation association{policy, value}; + // retain the new value (if any) outside the lock. - ObjcAssociation old_association(0, nil); - id new_value = value ? acquireValue(value, policy) : nil; + association.acquireValue(); + { AssociationsManager manager; - AssociationsHashMap &associations(manager.associations()); - disguised_ptr_t disguised_object = DISGUISE(object); - if (new_value) { - // break any existing association. - AssociationsHashMap::iterator i = associations.find(disguised_object); - if (i != associations.end()) { - // secondary table exists - ObjectAssociationMap *refs = i->second; - ObjectAssociationMap::iterator j = refs->find(key); - if (j != refs->end()) { - old_association = j->second; - j->second = ObjcAssociation(policy, new_value); - } else { - (*refs)[key] = ObjcAssociation(policy, new_value); - } - } else { - // create the new association (first time). - ObjectAssociationMap *refs = new ObjectAssociationMap; - associations[disguised_object] = refs; - (*refs)[key] = ObjcAssociation(policy, new_value); + AssociationsHashMap &associations(manager.get()); + + if (value) { + auto refs_result = associations.try_emplace(disguised, ObjectAssociationMap{}); + if (refs_result.second) { + /* it's the first association we make */ object->setHasAssociatedObjects(); } + + /* establish or replace the association */ + auto &refs = refs_result.first->second; + auto result = refs.try_emplace(key, std::move(association)); + if (!result.second) { + association.swap(result.first->second); + } } else { - // setting the association to nil breaks the association. - AssociationsHashMap::iterator i = associations.find(disguised_object); - if (i != associations.end()) { - ObjectAssociationMap *refs = i->second; - ObjectAssociationMap::iterator j = refs->find(key); - if (j != refs->end()) { - old_association = j->second; - refs->erase(j); + auto refs_it = associations.find(disguised); + if (refs_it != associations.end()) { + auto &refs = refs_it->second; + auto it = refs.find(key); + if (it != refs.end()) { + association.swap(it->second); + refs.erase(it); + if (refs.size() == 0) { + associations.erase(refs_it); + + } } } } } + // release the old value (outside of the lock). - if (old_association.hasValue()) ReleaseValue()(old_association); + association.releaseHeldValue(); } -void _object_remove_assocations(id object) { - vector< ObjcAssociation,ObjcAllocator > elements; +// Unlike setting/getting an associated reference, +// this function is performance sensitive because of +// raw isa objects (such as OS Objects) that can't track +// whether they have associated objects. +void +_object_remove_assocations(id object) +{ + ObjectAssociationMap refs{}; + { AssociationsManager manager; - AssociationsHashMap &associations(manager.associations()); - if (associations.size() == 0) return; - disguised_ptr_t disguised_object = DISGUISE(object); - AssociationsHashMap::iterator i = associations.find(disguised_object); + AssociationsHashMap &associations(manager.get()); + AssociationsHashMap::iterator i = associations.find((objc_object *)object); if (i != associations.end()) { - // copy all of the associations that need to be removed. - ObjectAssociationMap *refs = i->second; - for (ObjectAssociationMap::iterator j = refs->begin(), end = refs->end(); j != end; ++j) { - elements.push_back(j->second); - } - // remove the secondary table. - delete refs; + refs.swap(i->second); associations.erase(i); } } - // the calls to releaseValue() happen outside of the lock. - for_each(elements.begin(), elements.end(), ReleaseValue()); + + // release everything (outside of the lock). + for (auto &i: refs) { + i.second.releaseHeldValue(); + } } diff --git a/runtime/objc-runtime-new.h b/runtime/objc-runtime-new.h index 19258f6..532a353 100644 --- a/runtime/objc-runtime-new.h +++ b/runtime/objc-runtime-new.h @@ -24,6 +24,166 @@ #ifndef _OBJC_RUNTIME_NEW_H #define _OBJC_RUNTIME_NEW_H +// class_data_bits_t is the class_t->data field (class_rw_t pointer plus flags) +// The extra bits are optimized for the retain/release and alloc/dealloc paths. + +// Values for class_ro_t->flags +// These are emitted by the compiler and are part of the ABI. +// Note: See CGObjCNonFragileABIMac::BuildClassRoTInitializer in clang +// class is a metaclass +#define RO_META (1<<0) +// class is a root class +#define RO_ROOT (1<<1) +// class has .cxx_construct/destruct implementations +#define RO_HAS_CXX_STRUCTORS (1<<2) +// class has +load implementation +// #define RO_HAS_LOAD_METHOD (1<<3) +// class has visibility=hidden set +#define RO_HIDDEN (1<<4) +// class has attribute(objc_exception): OBJC_EHTYPE_$_ThisClass is non-weak +#define RO_EXCEPTION (1<<5) +// class has ro field for Swift metadata initializer callback +#define RO_HAS_SWIFT_INITIALIZER (1<<6) +// class compiled with ARC +#define RO_IS_ARC (1<<7) +// class has .cxx_destruct but no .cxx_construct (with RO_HAS_CXX_STRUCTORS) +#define RO_HAS_CXX_DTOR_ONLY (1<<8) +// class is not ARC but has ARC-style weak ivar layout +#define RO_HAS_WEAK_WITHOUT_ARC (1<<9) +// class does not allow associated objects on instances +#define RO_FORBIDS_ASSOCIATED_OBJECTS (1<<10) + +// class is in an unloadable bundle - must never be set by compiler +#define RO_FROM_BUNDLE (1<<29) +// class is unrealized future class - must never be set by compiler +#define RO_FUTURE (1<<30) +// class is realized - must never be set by compiler +#define RO_REALIZED (1<<31) + +// Values for class_rw_t->flags +// These are not emitted by the compiler and are never used in class_ro_t. +// Their presence should be considered in future ABI versions. +// class_t->data is class_rw_t, not class_ro_t +#define RW_REALIZED (1<<31) +// class is unresolved future class +#define RW_FUTURE (1<<30) +// class is initialized +#define RW_INITIALIZED (1<<29) +// class is initializing +#define RW_INITIALIZING (1<<28) +// class_rw_t->ro is heap copy of class_ro_t +#define RW_COPIED_RO (1<<27) +// class allocated but not yet registered +#define RW_CONSTRUCTING (1<<26) +// class allocated and registered +#define RW_CONSTRUCTED (1<<25) +// available for use; was RW_FINALIZE_ON_MAIN_THREAD +// #define RW_24 (1<<24) +// class +load has been called +#define RW_LOADED (1<<23) +#if !SUPPORT_NONPOINTER_ISA +// class instances may have associative references +#define RW_INSTANCES_HAVE_ASSOCIATED_OBJECTS (1<<22) +#endif +// class has instance-specific GC layout +#define RW_HAS_INSTANCE_SPECIFIC_LAYOUT (1 << 21) +// class does not allow associated objects on its instances +#define RW_FORBIDS_ASSOCIATED_OBJECTS (1<<20) +// class has started realizing but not yet completed it +#define RW_REALIZING (1<<19) + +// NOTE: MORE RW_ FLAGS DEFINED BELOW + + +// Values for class_rw_t->flags (RW_*), cache_t->_flags (FAST_CACHE_*), +// or class_t->bits (FAST_*). +// +// FAST_* and FAST_CACHE_* are stored on the class, reducing pointer indirection. + +#if __LP64__ + +// class is a Swift class from the pre-stable Swift ABI +#define FAST_IS_SWIFT_LEGACY (1UL<<0) +// class is a Swift class from the stable Swift ABI +#define FAST_IS_SWIFT_STABLE (1UL<<1) +// class or superclass has default retain/release/autorelease/retainCount/ +// _tryRetain/_isDeallocating/retainWeakReference/allowsWeakReference +#define FAST_HAS_DEFAULT_RR (1UL<<2) +// data pointer +#define FAST_DATA_MASK 0x00007ffffffffff8UL + +#if __arm64__ +// class or superclass has .cxx_construct/.cxx_destruct implementation +// FAST_CACHE_HAS_CXX_DTOR is the first bit so that setting it in +// isa_t::has_cxx_dtor is a single bfi +#define FAST_CACHE_HAS_CXX_DTOR (1<<0) +#define FAST_CACHE_HAS_CXX_CTOR (1<<1) +// Denormalized RO_META to avoid an indirection +#define FAST_CACHE_META (1<<2) +#else +// Denormalized RO_META to avoid an indirection +#define FAST_CACHE_META (1<<0) +// class or superclass has .cxx_construct/.cxx_destruct implementation +// FAST_CACHE_HAS_CXX_DTOR is chosen to alias with isa_t::has_cxx_dtor +#define FAST_CACHE_HAS_CXX_CTOR (1<<1) +#define FAST_CACHE_HAS_CXX_DTOR (1<<2) +#endif + +// Fast Alloc fields: +// This stores the word-aligned size of instances + "ALLOC_DELTA16", +// or 0 if the instance size doesn't fit. +// +// These bits occupy the same bits than in the instance size, so that +// the size can be extracted with a simple mask operation. +// +// FAST_CACHE_ALLOC_MASK16 allows to extract the instance size rounded +// rounded up to the next 16 byte boundary, which is a fastpath for +// _objc_rootAllocWithZone() +#define FAST_CACHE_ALLOC_MASK 0x1ff8 +#define FAST_CACHE_ALLOC_MASK16 0x1ff0 +#define FAST_CACHE_ALLOC_DELTA16 0x0008 + +// class's instances requires raw isa +#define FAST_CACHE_REQUIRES_RAW_ISA (1<<13) +// class or superclass has default alloc/allocWithZone: implementation +// Note this is is stored in the metaclass. +#define FAST_CACHE_HAS_DEFAULT_AWZ (1<<14) +// class or superclass has default new/self/class/respondsToSelector/isKindOfClass +#define FAST_CACHE_HAS_DEFAULT_CORE (1<<15) + +#else + +// class or superclass has .cxx_construct implementation +#define RW_HAS_CXX_CTOR (1<<18) +// class or superclass has .cxx_destruct implementation +#define RW_HAS_CXX_DTOR (1<<17) +// class or superclass has default alloc/allocWithZone: implementation +// Note this is is stored in the metaclass. +#define RW_HAS_DEFAULT_AWZ (1<<16) +// class's instances requires raw isa +#if SUPPORT_NONPOINTER_ISA +#define RW_REQUIRES_RAW_ISA (1<<15) +#endif +// class or superclass has default retain/release/autorelease/retainCount/ +// _tryRetain/_isDeallocating/retainWeakReference/allowsWeakReference +#define RW_HAS_DEFAULT_RR (1<<14) +// class or superclass has default new/self/class/respondsToSelector/isKindOfClass +#define RW_HAS_DEFAULT_CORE (1<<13) + +// class is a Swift class from the pre-stable Swift ABI +#define FAST_IS_SWIFT_LEGACY (1UL<<0) +// class is a Swift class from the stable Swift ABI +#define FAST_IS_SWIFT_STABLE (1UL<<1) +// data pointer +#define FAST_DATA_MASK 0xfffffffcUL + +#endif // __LP64__ + +// The Swift ABI requires that these bits be defined like this on all platforms. +static_assert(FAST_IS_SWIFT_LEGACY == 1, "resistance is futile"); +static_assert(FAST_IS_SWIFT_STABLE == 2, "resistance is futile"); + + #if __LP64__ typedef uint32_t mask_t; // x86_64 & arm64 asm are less efficient with 16-bits #else @@ -34,57 +194,117 @@ typedef uintptr_t SEL; struct swift_class_t; enum Atomicity { Atomic = true, NotAtomic = false }; +enum IMPEncoding { Encoded = true, Raw = false }; struct bucket_t { private: // IMP-first is better for arm64e ptrauth and no worse for arm64. // SEL-first is better for armv7* and i386 and x86_64. #if __arm64__ - uintptr_t _imp; - SEL _sel; + explicit_atomic _imp; + explicit_atomic _sel; #else - SEL _sel; - uintptr_t _imp; + explicit_atomic _sel; + explicit_atomic _imp; #endif - // Compute the ptrauth signing modifier from &_imp and newSel - uintptr_t modifierForSEL(SEL newSel) const { - return (uintptr_t)&_imp ^ (uintptr_t)newSel; + // Compute the ptrauth signing modifier from &_imp, newSel, and cls. + uintptr_t modifierForSEL(SEL newSel, Class cls) const { + return (uintptr_t)&_imp ^ (uintptr_t)newSel ^ (uintptr_t)cls; } - // Sign newImp, with &_imp and newSel as modifiers. - uintptr_t signIMP(IMP newImp, SEL newSel) const { + // Sign newImp, with &_imp, newSel, and cls as modifiers. + uintptr_t encodeImp(IMP newImp, SEL newSel, Class cls) const { if (!newImp) return 0; +#if CACHE_IMP_ENCODING == CACHE_IMP_ENCODING_PTRAUTH return (uintptr_t) ptrauth_auth_and_resign(newImp, ptrauth_key_function_pointer, 0, ptrauth_key_process_dependent_code, - modifierForSEL(newSel)); + modifierForSEL(newSel, cls)); +#elif CACHE_IMP_ENCODING == CACHE_IMP_ENCODING_ISA_XOR + return (uintptr_t)newImp ^ (uintptr_t)cls; +#elif CACHE_IMP_ENCODING == CACHE_IMP_ENCODING_NONE + return (uintptr_t)newImp; +#else +#error Unknown method cache IMP encoding. +#endif } public: - inline SEL sel() const { return _sel; } + inline SEL sel() const { return _sel.load(memory_order::memory_order_relaxed); } - inline IMP imp() const { - if (!_imp) return nil; + inline IMP imp(Class cls) const { + uintptr_t imp = _imp.load(memory_order::memory_order_relaxed); + if (!imp) return nil; +#if CACHE_IMP_ENCODING == CACHE_IMP_ENCODING_PTRAUTH + SEL sel = _sel.load(memory_order::memory_order_relaxed); return (IMP) - ptrauth_auth_and_resign((const void *)_imp, + ptrauth_auth_and_resign((const void *)imp, ptrauth_key_process_dependent_code, - modifierForSEL(_sel), + modifierForSEL(sel, cls), ptrauth_key_function_pointer, 0); +#elif CACHE_IMP_ENCODING == CACHE_IMP_ENCODING_ISA_XOR + return (IMP)(imp ^ (uintptr_t)cls); +#elif CACHE_IMP_ENCODING == CACHE_IMP_ENCODING_NONE + return (IMP)imp; +#else +#error Unknown method cache IMP encoding. +#endif } - template - void set(SEL newSel, IMP newImp); + template + void set(SEL newSel, IMP newImp, Class cls); }; struct cache_t { - struct bucket_t *_buckets; - mask_t _mask; - mask_t _occupied; +#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_OUTLINED + explicit_atomic _buckets; + explicit_atomic _mask; +#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16 + explicit_atomic _maskAndBuckets; + mask_t _mask_unused; + + // How much the mask is shifted by. + static constexpr uintptr_t maskShift = 48; + + // Additional bits after the mask which must be zero. msgSend + // takes advantage of these additional bits to construct the value + // `mask << 4` from `_maskAndBuckets` in a single instruction. + static constexpr uintptr_t maskZeroBits = 4; + + // The largest mask value we can store. + static constexpr uintptr_t maxMask = ((uintptr_t)1 << (64 - maskShift)) - 1; + + // The mask applied to `_maskAndBuckets` to retrieve the buckets pointer. + static constexpr uintptr_t bucketsMask = ((uintptr_t)1 << (maskShift - maskZeroBits)) - 1; + + // Ensure we have enough bits for the buckets pointer. + static_assert(bucketsMask >= MACH_VM_MAX_ADDRESS, "Bucket field doesn't have enough bits for arbitrary pointers."); +#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_LOW_4 + // _maskAndBuckets stores the mask shift in the low 4 bits, and + // the buckets pointer in the remainder of the value. The mask + // shift is the value where (0xffff >> shift) produces the correct + // mask. This is equal to 16 - log2(cache_size). + explicit_atomic _maskAndBuckets; + mask_t _mask_unused; + + static constexpr uintptr_t maskBits = 4; + static constexpr uintptr_t maskMask = (1 << maskBits) - 1; + static constexpr uintptr_t bucketsMask = ~maskMask; +#else +#error Unknown cache mask storage type. +#endif + +#if __LP64__ + uint16_t _flags; +#endif + uint16_t _occupied; public: + static bucket_t *emptyBuckets(); + struct bucket_t *buckets(); mask_t mask(); mask_t occupied(); @@ -92,24 +312,96 @@ struct cache_t { void setBucketsAndMask(struct bucket_t *newBuckets, mask_t newMask); void initializeToEmpty(); - mask_t capacity(); + unsigned capacity(); bool isConstantEmptyCache(); bool canBeFreed(); +#if __LP64__ + bool getBit(uint16_t flags) const { + return _flags & flags; + } + void setBit(uint16_t set) { + __c11_atomic_fetch_or((_Atomic(uint16_t) *)&_flags, set, __ATOMIC_RELAXED); + } + void clearBit(uint16_t clear) { + __c11_atomic_fetch_and((_Atomic(uint16_t) *)&_flags, ~clear, __ATOMIC_RELAXED); + } +#endif + +#if FAST_CACHE_ALLOC_MASK + bool hasFastInstanceSize(size_t extra) const + { + if (__builtin_constant_p(extra) && extra == 0) { + return _flags & FAST_CACHE_ALLOC_MASK16; + } + return _flags & FAST_CACHE_ALLOC_MASK; + } + + size_t fastInstanceSize(size_t extra) const + { + ASSERT(hasFastInstanceSize(extra)); + + if (__builtin_constant_p(extra) && extra == 0) { + return _flags & FAST_CACHE_ALLOC_MASK16; + } else { + size_t size = _flags & FAST_CACHE_ALLOC_MASK; + // remove the FAST_CACHE_ALLOC_DELTA16 that was added + // by setFastInstanceSize + return align16(size + extra - FAST_CACHE_ALLOC_DELTA16); + } + } + + void setFastInstanceSize(size_t newSize) + { + // Set during realization or construction only. No locking needed. + uint16_t newBits = _flags & ~FAST_CACHE_ALLOC_MASK; + uint16_t sizeBits; + + // Adding FAST_CACHE_ALLOC_DELTA16 allows for FAST_CACHE_ALLOC_MASK16 + // to yield the proper 16byte aligned allocation size with a single mask + sizeBits = word_align(newSize) + FAST_CACHE_ALLOC_DELTA16; + sizeBits &= FAST_CACHE_ALLOC_MASK; + if (newSize <= sizeBits) { + newBits |= sizeBits; + } + _flags = newBits; + } +#else + bool hasFastInstanceSize(size_t extra) const { + return false; + } + size_t fastInstanceSize(size_t extra) const { + abort(); + } + void setFastInstanceSize(size_t extra) { + // nothing + } +#endif + static size_t bytesForCapacity(uint32_t cap); static struct bucket_t * endMarker(struct bucket_t *b, uint32_t cap); - void expand(); - void reallocate(mask_t oldCapacity, mask_t newCapacity); - struct bucket_t * find(SEL sel, id receiver); + void reallocate(mask_t oldCapacity, mask_t newCapacity, bool freeOld); + void insert(Class cls, SEL sel, IMP imp, id receiver); - static void bad_cache(id receiver, SEL sel, Class isa) __attribute__((noreturn)); + static void bad_cache(id receiver, SEL sel, Class isa) __attribute__((noreturn, cold)); }; // classref_t is unremapped class_t* typedef struct classref * classref_t; + +#ifdef __PTRAUTH_INTRINSICS__ +# define StubClassInitializerPtrauth __ptrauth(ptrauth_key_function_pointer, 1, 0xc671) +#else +# define StubClassInitializerPtrauth +#endif +struct stub_class_t { + uintptr_t isa; + _objc_swiftMetadataInitializer StubClassInitializerPtrauth initializer; +}; + /*********************************************************************** * entsize_list_tt * Generic implementation of an array of non-fragile structs. @@ -133,11 +425,11 @@ struct entsize_list_tt { } Element& getOrEnd(uint32_t i) const { - assert(i <= count); + ASSERT(i <= count); return *(Element *)((uint8_t *)&first + i*entsize()); } Element& get(uint32_t i) const { - assert(i < count); + ASSERT(i < count); return getOrEnd(i); } @@ -286,13 +578,14 @@ struct property_t { // Two bits of entsize are used for fixup markers. struct method_list_t : entsize_list_tt { + bool isUniqued() const; bool isFixedUp() const; void setFixedUp(); uint32_t indexOfMethod(const method_t *meth) const { uint32_t i = (uint32_t)(((uintptr_t)meth - (uintptr_t)this) / entsize()); - assert(i < count); + ASSERT(i < count); return i; } }; @@ -310,8 +603,9 @@ struct property_list_t : entsize_list_tt { typedef uintptr_t protocol_ref_t; // protocol_t *, but unremapped // Values for protocol_t->flags -#define PROTOCOL_FIXED_UP_2 (1<<31) // must never be set by compiler -#define PROTOCOL_FIXED_UP_1 (1<<30) // must never be set by compiler +#define PROTOCOL_FIXED_UP_2 (1<<31) // must never be set by compiler +#define PROTOCOL_FIXED_UP_1 (1<<30) // must never be set by compiler +#define PROTOCOL_IS_CANONICAL (1<<29) // must never be set by compiler // Bits 0..15 are reserved for Swift's use. #define PROTOCOL_FIXED_UP_MASK (PROTOCOL_FIXED_UP_1 | PROTOCOL_FIXED_UP_2) @@ -340,6 +634,9 @@ struct protocol_t : objc_object { bool isFixedUp() const; void setFixedUp(); + bool isCanonical() const; + void clearIsCanonical(); + # define HAS_FIELD(f) (size >= offsetof(protocol_t, f) + sizeof(f)) bool hasExtendedMethodTypesField() const { @@ -364,7 +661,7 @@ struct protocol_t : objc_object { }; struct protocol_list_t { - // count is 64-bit by accident. + // count is pointer-sized by accident. uintptr_t count; protocol_ref_t list[0]; // variable-size @@ -393,188 +690,6 @@ struct protocol_list_t { } }; -struct locstamped_category_t { - category_t *cat; - struct header_info *hi; -}; - -struct locstamped_category_list_t { - uint32_t count; -#if __LP64__ - uint32_t reserved; -#endif - locstamped_category_t list[0]; -}; - - -// class_data_bits_t is the class_t->data field (class_rw_t pointer plus flags) -// The extra bits are optimized for the retain/release and alloc/dealloc paths. - -// Values for class_ro_t->flags -// These are emitted by the compiler and are part of the ABI. -// Note: See CGObjCNonFragileABIMac::BuildClassRoTInitializer in clang -// class is a metaclass -#define RO_META (1<<0) -// class is a root class -#define RO_ROOT (1<<1) -// class has .cxx_construct/destruct implementations -#define RO_HAS_CXX_STRUCTORS (1<<2) -// class has +load implementation -// #define RO_HAS_LOAD_METHOD (1<<3) -// class has visibility=hidden set -#define RO_HIDDEN (1<<4) -// class has attribute(objc_exception): OBJC_EHTYPE_$_ThisClass is non-weak -#define RO_EXCEPTION (1<<5) -// class has ro field for Swift metadata initializer callback -#define RO_HAS_SWIFT_INITIALIZER (1<<6) -// class compiled with ARC -#define RO_IS_ARC (1<<7) -// class has .cxx_destruct but no .cxx_construct (with RO_HAS_CXX_STRUCTORS) -#define RO_HAS_CXX_DTOR_ONLY (1<<8) -// class is not ARC but has ARC-style weak ivar layout -#define RO_HAS_WEAK_WITHOUT_ARC (1<<9) -// class does not allow associated objects on instances -#define RO_FORBIDS_ASSOCIATED_OBJECTS (1<<10) - -// class is in an unloadable bundle - must never be set by compiler -#define RO_FROM_BUNDLE (1<<29) -// class is unrealized future class - must never be set by compiler -#define RO_FUTURE (1<<30) -// class is realized - must never be set by compiler -#define RO_REALIZED (1<<31) - -// Values for class_rw_t->flags -// These are not emitted by the compiler and are never used in class_ro_t. -// Their presence should be considered in future ABI versions. -// class_t->data is class_rw_t, not class_ro_t -#define RW_REALIZED (1<<31) -// class is unresolved future class -#define RW_FUTURE (1<<30) -// class is initialized -#define RW_INITIALIZED (1<<29) -// class is initializing -#define RW_INITIALIZING (1<<28) -// class_rw_t->ro is heap copy of class_ro_t -#define RW_COPIED_RO (1<<27) -// class allocated but not yet registered -#define RW_CONSTRUCTING (1<<26) -// class allocated and registered -#define RW_CONSTRUCTED (1<<25) -// available for use; was RW_FINALIZE_ON_MAIN_THREAD -// #define RW_24 (1<<24) -// class +load has been called -#define RW_LOADED (1<<23) -#if !SUPPORT_NONPOINTER_ISA -// class instances may have associative references -#define RW_INSTANCES_HAVE_ASSOCIATED_OBJECTS (1<<22) -#endif -// class has instance-specific GC layout -#define RW_HAS_INSTANCE_SPECIFIC_LAYOUT (1 << 21) -// class does not allow associated objects on its instances -#define RW_FORBIDS_ASSOCIATED_OBJECTS (1<<20) -// class has started realizing but not yet completed it -#define RW_REALIZING (1<<19) - -// NOTE: MORE RW_ FLAGS DEFINED BELOW - - -// Values for class_rw_t->flags or class_t->bits -// These flags are optimized for retain/release and alloc/dealloc -// 64-bit stores more of them in class_t->bits to reduce pointer indirection. - -#if !__LP64__ - -// class or superclass has .cxx_construct implementation -#define RW_HAS_CXX_CTOR (1<<18) -// class or superclass has .cxx_destruct implementation -#define RW_HAS_CXX_DTOR (1<<17) -// class or superclass has default alloc/allocWithZone: implementation -// Note this is is stored in the metaclass. -#define RW_HAS_DEFAULT_AWZ (1<<16) -// class's instances requires raw isa -#if SUPPORT_NONPOINTER_ISA -#define RW_REQUIRES_RAW_ISA (1<<15) -#endif -// class or superclass has default retain/release/autorelease/retainCount/ -// _tryRetain/_isDeallocating/retainWeakReference/allowsWeakReference -#define RW_HAS_DEFAULT_RR (1<<14) - -// class is a Swift class from the pre-stable Swift ABI -#define FAST_IS_SWIFT_LEGACY (1UL<<0) -// class is a Swift class from the stable Swift ABI -#define FAST_IS_SWIFT_STABLE (1UL<<1) -// data pointer -#define FAST_DATA_MASK 0xfffffffcUL - -#elif 1 -// Leaks-compatible version that steals low bits only. - -// class or superclass has .cxx_construct implementation -#define RW_HAS_CXX_CTOR (1<<18) -// class or superclass has .cxx_destruct implementation -#define RW_HAS_CXX_DTOR (1<<17) -// class or superclass has default alloc/allocWithZone: implementation -// Note this is is stored in the metaclass. -#define RW_HAS_DEFAULT_AWZ (1<<16) -// class's instances requires raw isa -#define RW_REQUIRES_RAW_ISA (1<<15) - -// class is a Swift class from the pre-stable Swift ABI -#define FAST_IS_SWIFT_LEGACY (1UL<<0) -// class is a Swift class from the stable Swift ABI -#define FAST_IS_SWIFT_STABLE (1UL<<1) -// class or superclass has default retain/release/autorelease/retainCount/ -// _tryRetain/_isDeallocating/retainWeakReference/allowsWeakReference -#define FAST_HAS_DEFAULT_RR (1UL<<2) -// data pointer -#define FAST_DATA_MASK 0x00007ffffffffff8UL - -#else -// Leaks-incompatible version that steals lots of bits. - -// class is a Swift class from the pre-stable Swift ABI -#define FAST_IS_SWIFT_LEGACY (1UL<<0) -// class is a Swift class from the stable Swift ABI -#define FAST_IS_SWIFT_STABLE (1UL<<1) -// summary bit for fast alloc path: !hasCxxCtor and -// !instancesRequireRawIsa and instanceSize fits into shiftedSize -#define FAST_ALLOC (1UL<<2) -// data pointer -#define FAST_DATA_MASK 0x00007ffffffffff8UL -// class or superclass has .cxx_construct implementation -#define FAST_HAS_CXX_CTOR (1UL<<47) -// class or superclass has default alloc/allocWithZone: implementation -// Note this is is stored in the metaclass. -#define FAST_HAS_DEFAULT_AWZ (1UL<<48) -// class or superclass has default retain/release/autorelease/retainCount/ -// _tryRetain/_isDeallocating/retainWeakReference/allowsWeakReference -#define FAST_HAS_DEFAULT_RR (1UL<<49) -// class's instances requires raw isa -// This bit is aligned with isa_t->hasCxxDtor to save an instruction. -#define FAST_REQUIRES_RAW_ISA (1UL<<50) -// class or superclass has .cxx_destruct implementation -#define FAST_HAS_CXX_DTOR (1UL<<51) -// instance size in units of 16 bytes -// or 0 if the instance size is too big in this field -// This field must be LAST -#define FAST_SHIFTED_SIZE_SHIFT 52 - -// FAST_ALLOC means -// FAST_HAS_CXX_CTOR is set -// FAST_REQUIRES_RAW_ISA is not set -// FAST_SHIFTED_SIZE is not zero -// FAST_ALLOC does NOT check FAST_HAS_DEFAULT_AWZ because that -// bit is stored on the metaclass. -#define FAST_ALLOC_MASK (FAST_HAS_CXX_CTOR | FAST_REQUIRES_RAW_ISA) -#define FAST_ALLOC_VALUE (0) - -#endif - -// The Swift ABI requires that these bits be defined like this on all platforms. -static_assert(FAST_IS_SWIFT_LEGACY == 1, "resistance is futile"); -static_assert(FAST_IS_SWIFT_STABLE == 2, "resistance is futile"); - - struct class_ro_t { uint32_t flags; uint32_t instanceStart; @@ -683,10 +798,10 @@ class list_array_tt { } const iterator& operator ++ () { - assert(m != mEnd); + ASSERT(m != mEnd); m++; if (m == mEnd) { - assert(lists != listsEnd); + ASSERT(lists != listsEnd); lists++; if (lists != listsEnd) { m = (*lists)->begin(); @@ -875,7 +990,8 @@ class protocol_array_t : struct class_rw_t { // Be warned that Symbolication knows the layout of this structure. uint32_t flags; - uint32_t version; + uint16_t version; + uint16_t witness; const class_ro_t *ro; @@ -894,18 +1010,18 @@ struct class_rw_t { void setFlags(uint32_t set) { - OSAtomicOr32Barrier(set, &flags); + __c11_atomic_fetch_or((_Atomic(uint32_t) *)&flags, set, __ATOMIC_RELAXED); } void clearFlags(uint32_t clear) { - OSAtomicXor32Barrier(clear, &flags); + __c11_atomic_fetch_and((_Atomic(uint32_t) *)&flags, ~clear, __ATOMIC_RELAXED); } // set and clear must not overlap void changeFlags(uint32_t set, uint32_t clear) { - assert((set & clear) == 0); + ASSERT((set & clear) == 0); uint32_t oldf, newf; do { @@ -917,67 +1033,45 @@ struct class_rw_t { struct class_data_bits_t { + friend objc_class; // Values are the FAST_ flags above. uintptr_t bits; private: - bool getBit(uintptr_t bit) + bool getBit(uintptr_t bit) const { return bits & bit; } -#if FAST_ALLOC - // On entry, `newBits` is a bits value after setting and/or clearing - // the bits in `change`. Fix the fast-alloc parts of newBits if necessary - // and return the updated value. - static uintptr_t updateFastAlloc(uintptr_t newBits, uintptr_t change) - { - if (change & FAST_ALLOC_MASK) { - if (((newBits & FAST_ALLOC_MASK) == FAST_ALLOC_VALUE) && - ((newBits >> FAST_SHIFTED_SIZE_SHIFT) != 0)) - { - newBits |= FAST_ALLOC; - } else { - newBits &= ~FAST_ALLOC; - } - } - return newBits; - } -#else - static uintptr_t updateFastAlloc(uintptr_t newBits, uintptr_t change) { - return newBits; - } -#endif - // Atomically set the bits in `set` and clear the bits in `clear`. // set and clear must not overlap. void setAndClearBits(uintptr_t set, uintptr_t clear) { - assert((set & clear) == 0); + ASSERT((set & clear) == 0); uintptr_t oldBits; uintptr_t newBits; do { oldBits = LoadExclusive(&bits); - newBits = updateFastAlloc((oldBits | set) & ~clear, set | clear); + newBits = (oldBits | set) & ~clear; } while (!StoreReleaseExclusive(&bits, oldBits, newBits)); } void setBits(uintptr_t set) { - setAndClearBits(set, 0); + __c11_atomic_fetch_or((_Atomic(uintptr_t) *)&bits, set, __ATOMIC_RELAXED); } void clearBits(uintptr_t clear) { - setAndClearBits(0, clear); + __c11_atomic_fetch_and((_Atomic(uintptr_t) *)&bits, ~clear, __ATOMIC_RELAXED); } public: - class_rw_t* data() { + class_rw_t* data() const { return (class_rw_t *)(bits & FAST_DATA_MASK); } void setData(class_rw_t *newData) { - assert(!data() || (newData->flags & (RW_REALIZING | RW_FUTURE))); + ASSERT(!data() || (newData->flags & (RW_REALIZING | RW_FUTURE))); // Set during realization or construction only. No locking needed. // Use a store-release fence because there may be concurrent // readers of data and data's contents. @@ -1000,149 +1094,10 @@ struct class_data_bits_t { } } -#if FAST_HAS_DEFAULT_RR - bool hasDefaultRR() { - return getBit(FAST_HAS_DEFAULT_RR); - } - void setHasDefaultRR() { - setBits(FAST_HAS_DEFAULT_RR); - } - void setHasCustomRR() { - clearBits(FAST_HAS_DEFAULT_RR); - } -#else - bool hasDefaultRR() { - return data()->flags & RW_HAS_DEFAULT_RR; - } - void setHasDefaultRR() { - data()->setFlags(RW_HAS_DEFAULT_RR); - } - void setHasCustomRR() { - data()->clearFlags(RW_HAS_DEFAULT_RR); - } -#endif - -#if FAST_HAS_DEFAULT_AWZ - bool hasDefaultAWZ() { - return getBit(FAST_HAS_DEFAULT_AWZ); - } - void setHasDefaultAWZ() { - setBits(FAST_HAS_DEFAULT_AWZ); - } - void setHasCustomAWZ() { - clearBits(FAST_HAS_DEFAULT_AWZ); - } -#else - bool hasDefaultAWZ() { - return data()->flags & RW_HAS_DEFAULT_AWZ; - } - void setHasDefaultAWZ() { - data()->setFlags(RW_HAS_DEFAULT_AWZ); - } - void setHasCustomAWZ() { - data()->clearFlags(RW_HAS_DEFAULT_AWZ); - } -#endif - -#if FAST_HAS_CXX_CTOR - bool hasCxxCtor() { - return getBit(FAST_HAS_CXX_CTOR); - } - void setHasCxxCtor() { - setBits(FAST_HAS_CXX_CTOR); - } -#else - bool hasCxxCtor() { - return data()->flags & RW_HAS_CXX_CTOR; - } - void setHasCxxCtor() { - data()->setFlags(RW_HAS_CXX_CTOR); - } -#endif - -#if FAST_HAS_CXX_DTOR - bool hasCxxDtor() { - return getBit(FAST_HAS_CXX_DTOR); - } - void setHasCxxDtor() { - setBits(FAST_HAS_CXX_DTOR); - } -#else - bool hasCxxDtor() { - return data()->flags & RW_HAS_CXX_DTOR; - } - void setHasCxxDtor() { - data()->setFlags(RW_HAS_CXX_DTOR); - } -#endif - -#if FAST_REQUIRES_RAW_ISA - bool instancesRequireRawIsa() { - return getBit(FAST_REQUIRES_RAW_ISA); - } - void setInstancesRequireRawIsa() { - setBits(FAST_REQUIRES_RAW_ISA); - } -#elif SUPPORT_NONPOINTER_ISA - bool instancesRequireRawIsa() { - return data()->flags & RW_REQUIRES_RAW_ISA; - } - void setInstancesRequireRawIsa() { - data()->setFlags(RW_REQUIRES_RAW_ISA); - } -#else - bool instancesRequireRawIsa() { - return true; - } - void setInstancesRequireRawIsa() { - // nothing - } -#endif - -#if FAST_ALLOC - size_t fastInstanceSize() - { - assert(bits & FAST_ALLOC); - return (bits >> FAST_SHIFTED_SIZE_SHIFT) * 16; - } - void setFastInstanceSize(size_t newSize) - { - // Set during realization or construction only. No locking needed. - assert(data()->flags & RW_REALIZING); - - // Round up to 16-byte boundary, then divide to get 16-byte units - newSize = ((newSize + 15) & ~15) / 16; - - uintptr_t newBits = newSize << FAST_SHIFTED_SIZE_SHIFT; - if ((newBits >> FAST_SHIFTED_SIZE_SHIFT) == newSize) { - int shift = WORD_BITS - FAST_SHIFTED_SIZE_SHIFT; - uintptr_t oldBits = (bits << shift) >> shift; - if ((oldBits & FAST_ALLOC_MASK) == FAST_ALLOC_VALUE) { - newBits |= FAST_ALLOC; - } - bits = oldBits | newBits; - } - } - - bool canAllocFast() { - return bits & FAST_ALLOC; - } -#else - size_t fastInstanceSize() { - abort(); - } - void setFastInstanceSize(size_t) { - // nothing - } - bool canAllocFast() { - return false; - } -#endif - void setClassArrayIndex(unsigned Idx) { #if SUPPORT_INDEXED_ISA // 0 is unused as then we can rely on zero-initialisation from calloc. - assert(Idx > 0); + ASSERT(Idx > 0); data()->index = Idx; #endif } @@ -1193,7 +1148,7 @@ struct objc_class : objc_object { cache_t cache; // formerly cache pointer and vtable class_data_bits_t bits; // class_rw_t * plus custom rr/alloc flags - class_rw_t *data() { + class_rw_t *data() const { return bits.data(); } void setData(class_rw_t *newData) { @@ -1201,76 +1156,153 @@ struct objc_class : objc_object { } void setInfo(uint32_t set) { - assert(isFuture() || isRealized()); + ASSERT(isFuture() || isRealized()); data()->setFlags(set); } void clearInfo(uint32_t clear) { - assert(isFuture() || isRealized()); + ASSERT(isFuture() || isRealized()); data()->clearFlags(clear); } // set and clear must not overlap void changeInfo(uint32_t set, uint32_t clear) { - assert(isFuture() || isRealized()); - assert((set & clear) == 0); + ASSERT(isFuture() || isRealized()); + ASSERT((set & clear) == 0); data()->changeFlags(set, clear); } - bool hasCustomRR() { - return ! bits.hasDefaultRR(); +#if FAST_HAS_DEFAULT_RR + bool hasCustomRR() const { + return !bits.getBit(FAST_HAS_DEFAULT_RR); } void setHasDefaultRR() { - assert(isInitializing()); - bits.setHasDefaultRR(); + bits.setBits(FAST_HAS_DEFAULT_RR); } - void setHasCustomRR(bool inherited = false); - void printCustomRR(bool inherited); + void setHasCustomRR() { + bits.clearBits(FAST_HAS_DEFAULT_RR); + } +#else + bool hasCustomRR() const { + return !(bits.data()->flags & RW_HAS_DEFAULT_RR); + } + void setHasDefaultRR() { + bits.data()->setFlags(RW_HAS_DEFAULT_RR); + } + void setHasCustomRR() { + bits.data()->clearFlags(RW_HAS_DEFAULT_RR); + } +#endif - bool hasCustomAWZ() { - return ! bits.hasDefaultAWZ(); +#if FAST_CACHE_HAS_DEFAULT_AWZ + bool hasCustomAWZ() const { + return !cache.getBit(FAST_CACHE_HAS_DEFAULT_AWZ); } void setHasDefaultAWZ() { - assert(isInitializing()); - bits.setHasDefaultAWZ(); + cache.setBit(FAST_CACHE_HAS_DEFAULT_AWZ); } - void setHasCustomAWZ(bool inherited = false); - void printCustomAWZ(bool inherited); - - bool instancesRequireRawIsa() { - return bits.instancesRequireRawIsa(); + void setHasCustomAWZ() { + cache.clearBit(FAST_CACHE_HAS_DEFAULT_AWZ); } - void setInstancesRequireRawIsa(bool inherited = false); - void printInstancesRequireRawIsa(bool inherited); - - bool canAllocNonpointer() { - assert(!isFuture()); - return !instancesRequireRawIsa(); +#else + bool hasCustomAWZ() const { + return !(bits.data()->flags & RW_HAS_DEFAULT_AWZ); + } + void setHasDefaultAWZ() { + bits.data()->setFlags(RW_HAS_DEFAULT_AWZ); } - bool canAllocFast() { - assert(!isFuture()); - return bits.canAllocFast(); + void setHasCustomAWZ() { + bits.data()->clearFlags(RW_HAS_DEFAULT_AWZ); } +#endif +#if FAST_CACHE_HAS_DEFAULT_CORE + bool hasCustomCore() const { + return !cache.getBit(FAST_CACHE_HAS_DEFAULT_CORE); + } + void setHasDefaultCore() { + return cache.setBit(FAST_CACHE_HAS_DEFAULT_CORE); + } + void setHasCustomCore() { + return cache.clearBit(FAST_CACHE_HAS_DEFAULT_CORE); + } +#else + bool hasCustomCore() const { + return !(bits.data()->flags & RW_HAS_DEFAULT_CORE); + } + void setHasDefaultCore() { + bits.data()->setFlags(RW_HAS_DEFAULT_CORE); + } + void setHasCustomCore() { + bits.data()->clearFlags(RW_HAS_DEFAULT_CORE); + } +#endif +#if FAST_CACHE_HAS_CXX_CTOR + bool hasCxxCtor() { + ASSERT(isRealized()); + return cache.getBit(FAST_CACHE_HAS_CXX_CTOR); + } + void setHasCxxCtor() { + cache.setBit(FAST_CACHE_HAS_CXX_CTOR); + } +#else bool hasCxxCtor() { - // addSubclass() propagates this flag from the superclass. - assert(isRealized()); - return bits.hasCxxCtor(); + ASSERT(isRealized()); + return bits.data()->flags & RW_HAS_CXX_CTOR; } - void setHasCxxCtor() { - bits.setHasCxxCtor(); + void setHasCxxCtor() { + bits.data()->setFlags(RW_HAS_CXX_CTOR); } +#endif +#if FAST_CACHE_HAS_CXX_DTOR + bool hasCxxDtor() { + ASSERT(isRealized()); + return cache.getBit(FAST_CACHE_HAS_CXX_DTOR); + } + void setHasCxxDtor() { + cache.setBit(FAST_CACHE_HAS_CXX_DTOR); + } +#else bool hasCxxDtor() { - // addSubclass() propagates this flag from the superclass. - assert(isRealized()); - return bits.hasCxxDtor(); + ASSERT(isRealized()); + return bits.data()->flags & RW_HAS_CXX_DTOR; } - void setHasCxxDtor() { - bits.setHasCxxDtor(); + void setHasCxxDtor() { + bits.data()->setFlags(RW_HAS_CXX_DTOR); } +#endif +#if FAST_CACHE_REQUIRES_RAW_ISA + bool instancesRequireRawIsa() { + return cache.getBit(FAST_CACHE_REQUIRES_RAW_ISA); + } + void setInstancesRequireRawIsa() { + cache.setBit(FAST_CACHE_REQUIRES_RAW_ISA); + } +#elif SUPPORT_NONPOINTER_ISA + bool instancesRequireRawIsa() { + return bits.data()->flags & RW_REQUIRES_RAW_ISA; + } + void setInstancesRequireRawIsa() { + bits.data()->setFlags(RW_REQUIRES_RAW_ISA); + } +#else + bool instancesRequireRawIsa() { + return true; + } + void setInstancesRequireRawIsa() { + // nothing + } +#endif + void setInstancesRequireRawIsaRecursively(bool inherited = false); + void printInstancesRequireRawIsa(bool inherited); + + bool canAllocNonpointer() { + ASSERT(!isFuture()); + return !instancesRequireRawIsa(); + } bool isSwiftStable() { return bits.isSwiftStable(); @@ -1288,6 +1320,11 @@ struct objc_class : objc_object { return bits.isSwiftStable_ButAllowLegacyForNow(); } + bool isStubClass() const { + uintptr_t isa = (uintptr_t)isaBits(); + return 1 <= isa && isa < 16; + } + // Swift stable ABI built for old deployment targets looks weird. // The is-legacy bit is set for compatibility with old libobjc. // We are on a "new" deployment target so we need to rewrite that bit. @@ -1340,13 +1377,13 @@ struct objc_class : objc_object { #else bool instancesHaveAssociatedObjects() { // this may be an unrealized future class in the CF-bridged case - assert(isFuture() || isRealized()); + ASSERT(isFuture() || isRealized()); return data()->flags & RW_INSTANCES_HAVE_ASSOCIATED_OBJECTS; } void setInstancesHaveAssociatedObjects() { // this may be an unrealized future class in the CF-bridged case - assert(isFuture() || isRealized()); + ASSERT(isFuture() || isRealized()); setInfo(RW_INSTANCES_HAVE_ASSOCIATED_OBJECTS); } #endif @@ -1364,7 +1401,7 @@ struct objc_class : objc_object { } void setInitializing() { - assert(!isMetaClass()); + ASSERT(!isMetaClass()); ISA()->setInfo(RW_INITIALIZING); } @@ -1375,27 +1412,31 @@ struct objc_class : objc_object { void setInitialized(); bool isLoadable() { - assert(isRealized()); + ASSERT(isRealized()); return true; // any class registered for +load is definitely loadable } IMP getLoadMethod(); // Locking: To prevent concurrent realization, hold runtimeLock. - bool isRealized() { - return data()->flags & RW_REALIZED; + bool isRealized() const { + return !isStubClass() && (data()->flags & RW_REALIZED); } // Returns true if this is an unrealized future class. // Locking: To prevent concurrent realization, hold runtimeLock. - bool isFuture() { + bool isFuture() const { return data()->flags & RW_FUTURE; } bool isMetaClass() { - assert(this); - assert(isRealized()); + ASSERT(this); + ASSERT(isRealized()); +#if FAST_CACHE_META + return cache.getBit(FAST_CACHE_META); +#else return data()->ro->flags & RO_META; +#endif } // Like isMetaClass, but also valid on un-realized classes @@ -1418,7 +1459,7 @@ struct objc_class : objc_object { const char *mangledName() { // fixme can't assert locks here - assert(this); + ASSERT(this); if (isRealized() || isFuture()) { return data()->ro->name; @@ -1431,29 +1472,33 @@ struct objc_class : objc_object { const char *nameForLogging(); // May be unaligned depending on class's ivars. - uint32_t unalignedInstanceStart() { - assert(isRealized()); + uint32_t unalignedInstanceStart() const { + ASSERT(isRealized()); return data()->ro->instanceStart; } // Class's instance start rounded up to a pointer-size boundary. // This is used for ARC layout bitmaps. - uint32_t alignedInstanceStart() { + uint32_t alignedInstanceStart() const { return word_align(unalignedInstanceStart()); } // May be unaligned depending on class's ivars. - uint32_t unalignedInstanceSize() { - assert(isRealized()); + uint32_t unalignedInstanceSize() const { + ASSERT(isRealized()); return data()->ro->instanceSize; } // Class's ivar size rounded up to a pointer-size boundary. - uint32_t alignedInstanceSize() { + uint32_t alignedInstanceSize() const { return word_align(unalignedInstanceSize()); } - size_t instanceSize(size_t extraBytes) { + size_t instanceSize(size_t extraBytes) const { + if (fastpath(cache.hasFastInstanceSize(extraBytes))) { + return cache.fastInstanceSize(extraBytes); + } + size_t size = alignedInstanceSize() + extraBytes; // CF requires all objects be at least 16 bytes. if (size < 16) size = 16; @@ -1461,12 +1506,13 @@ struct objc_class : objc_object { } void setInstanceSize(uint32_t newSize) { - assert(isRealized()); + ASSERT(isRealized()); + ASSERT(data()->flags & RW_REALIZING); if (newSize != data()->ro->instanceSize) { - assert(data()->flags & RW_COPIED_RO); + ASSERT(data()->flags & RW_COPIED_RO); *const_cast(&data()->ro->instanceSize) = newSize; } - bits.setFastInstanceSize(newSize); + cache.setFastInstanceSize(newSize); } void chooseClassArrayIndex(); @@ -1478,7 +1524,6 @@ struct objc_class : objc_object { unsigned classArrayIndex() { return bits.classArrayIndex(); } - }; @@ -1516,6 +1561,11 @@ struct category_t { } property_list_t *propertiesForMeta(bool isMeta, struct header_info *hi); + + protocol_list_t *protocolsForMeta(bool isMeta) { + if (isMeta) return nullptr; + else return protocols; + } }; struct objc_super2 { @@ -1531,70 +1581,4 @@ struct message_ref_t { extern Method protocol_getMethod(protocol_t *p, SEL sel, bool isRequiredMethod, bool isInstanceMethod, bool recursive); -static inline void -foreach_realized_class_and_subclass_2(Class top, unsigned& count, - std::function code) -{ - // runtimeLock.assertLocked(); - assert(top); - Class cls = top; - while (1) { - if (--count == 0) { - _objc_fatal("Memory corruption in class list."); - } - if (!code(cls)) break; - - if (cls->data()->firstSubclass) { - cls = cls->data()->firstSubclass; - } else { - while (!cls->data()->nextSiblingClass && cls != top) { - cls = cls->superclass; - if (--count == 0) { - _objc_fatal("Memory corruption in class list."); - } - } - if (cls == top) break; - cls = cls->data()->nextSiblingClass; - } - } -} - -extern Class firstRealizedClass(); -extern unsigned int unreasonableClassCount(); - -// Enumerates a class and all of its realized subclasses. -static inline void -foreach_realized_class_and_subclass(Class top, - std::function code) -{ - unsigned int count = unreasonableClassCount(); - - foreach_realized_class_and_subclass_2(top, count, - [&code](Class cls) -> bool - { - code(cls); - return true; - }); -} - -// Enumerates all realized classes and metaclasses. -static inline void -foreach_realized_class_and_metaclass(std::function code) -{ - unsigned int count = unreasonableClassCount(); - - for (Class top = firstRealizedClass(); - top != nil; - top = top->data()->nextSiblingClass) - { - foreach_realized_class_and_subclass_2(top, count, - [&code](Class cls) -> bool - { - code(cls); - return true; - }); - } - -} - #endif diff --git a/runtime/objc-runtime-new.mm b/runtime/objc-runtime-new.mm index f7d09c9..be70319 100644 --- a/runtime/objc-runtime-new.mm +++ b/runtime/objc-runtime-new.mm @@ -28,6 +28,7 @@ #if __OBJC2__ +#include "DenseMapExtras.h" #include "objc-private.h" #include "objc-runtime-new.h" #include "objc-file.h" @@ -41,16 +42,11 @@ static void disableTaggedPointers(); static void detach_class(Class cls, bool isMeta); static void free_class(Class cls); -static Class setSuperclass(Class cls, Class newSuper); -static method_t *getMethodNoSuper_nolock(Class cls, SEL sel); -static method_t *getMethod_nolock(Class cls, SEL sel); static IMP addMethod(Class cls, SEL name, IMP imp, const char *types, bool replace); -static bool isRRSelector(SEL sel); -static bool isAWZSelector(SEL sel); -static bool methodListImplementsRR(const method_list_t *mlist); -static bool methodListImplementsAWZ(const method_list_t *mlist); -static void updateCustomRR_AWZ(Class cls, method_t *meth); +static void adjustCustomFlagsForMethodChange(Class cls, method_t *meth); static method_t *search_method_list(const method_list_t *mlist, SEL sel); +static bool method_lists_contains_any(method_list_t **mlists, method_list_t **end, + SEL sels[], size_t selcount); static void flushCaches(Class cls); static void initializeTaggedPointerObfuscator(void); #if SUPPORT_FIXUP @@ -59,8 +55,17 @@ static Class realizeClassMaybeSwiftAndUnlock(Class cls, mutex_t& lock); static Class readClass(Class cls, bool headerIsBundle, bool headerIsPreoptimized); -static bool MetaclassNSObjectAWZSwizzled; -static bool ClassNSObjectRRSwizzled; +struct locstamped_category_t { + category_t *cat; + struct header_info *hi; +}; +enum { + ATTACH_CLASS = 1 << 0, + ATTACH_METACLASS = 1 << 1, + ATTACH_CLASS_AND_METACLASS = 1 << 2, + ATTACH_EXISTING = 1 << 3, +}; +static void attachCategories(Class cls, const struct locstamped_category_t *cats_list, uint32_t cats_count, int flags); /*********************************************************************** @@ -68,14 +73,11 @@ **********************************************************************/ mutex_t runtimeLock; mutex_t selLock; +#if CONFIG_USE_CACHE_LOCK mutex_t cacheUpdateLock; +#endif recursive_mutex_t loadMethodLock; -void lock_init(void) -{ -} - - /*********************************************************************** * Class structure decoding **********************************************************************/ @@ -186,11 +188,15 @@ void lock_init(void) * A table of all classes (and metaclasses) which have been allocated * with objc_allocateClassPair. **********************************************************************/ -static NXHashTable *allocatedClasses = nil; - - -typedef locstamped_category_list_t category_list; +namespace objc { +static ExplicitInitDenseSet allocatedClasses; +} +/*********************************************************************** +* _firstRealizedClass +* The root of all realized classes +**********************************************************************/ +static Class _firstRealizedClass = nil; /* Low two bits of mlist->entsize is used as the fixed-up marker. @@ -215,13 +221,26 @@ void lock_init(void) */ static uint32_t fixed_up_method_list = 3; +static uint32_t uniqued_method_list = 1; static uint32_t fixed_up_protocol = PROTOCOL_FIXED_UP_1; +static uint32_t canonical_protocol = PROTOCOL_IS_CANONICAL; void disableSharedCacheOptimizations(void) { fixed_up_method_list = 2; + // It is safe to set uniqued method lists to 0 as we'll never call it unless + // the method list was already in need of being fixed up + uniqued_method_list = 0; fixed_up_protocol = PROTOCOL_FIXED_UP_1 | PROTOCOL_FIXED_UP_2; + // Its safe to just set canonical protocol to 0 as we'll never call + // clearIsCanonical() unless isCanonical() returned true, which can't happen + // with a 0 mask + canonical_protocol = 0; +} + +bool method_list_t::isUniqued() const { + return (flags() & uniqued_method_list) != 0; } bool method_list_t::isFixedUp() const { @@ -230,7 +249,7 @@ void lock_init(void) void method_list_t::setFixedUp() { runtimeLock.assertLocked(); - assert(!isFixedUp()); + ASSERT(!isFixedUp()); entsizeAndFlags = entsize() | fixed_up_method_list; } @@ -240,10 +259,20 @@ void lock_init(void) void protocol_t::setFixedUp() { runtimeLock.assertLocked(); - assert(!isFixedUp()); + ASSERT(!isFixedUp()); flags = (flags & ~PROTOCOL_FIXED_UP_MASK) | fixed_up_protocol; } +bool protocol_t::isCanonical() const { + return (flags & canonical_protocol) != 0; +} + +void protocol_t::clearIsCanonical() { + runtimeLock.assertLocked(); + ASSERT(isCanonical()); + flags = flags & ~canonical_protocol; +} + method_list_t **method_array_t::endCategoryMethodLists(Class cls) { @@ -280,12 +309,16 @@ static void try_free(const void *p) } -static void (*classCopyFixupHandler)(Class _Nonnull oldClass, - Class _Nonnull newClass); +using ClassCopyFixupHandler = void (*)(Class _Nonnull oldClass, + Class _Nonnull newClass); +// Normally there's only one handler registered. +static GlobalSmallVector classCopyFixupHandlers; void _objc_setClassCopyFixupHandler(void (* _Nonnull newFixupHandler) (Class _Nonnull oldClass, Class _Nonnull newClass)) { - classCopyFixupHandler = newFixupHandler; + mutex_locker_t lock(runtimeLock); + + classCopyFixupHandlers.append(newFixupHandler); } static Class @@ -313,8 +346,8 @@ void _objc_setClassCopyFixupHandler(void (* _Nonnull newFixupHandler) bzero(swcls, sizeof(objc_class)); swcls->description = nil; - if (classCopyFixupHandler) { - classCopyFixupHandler(supercls, (Class)swcls); + for (auto handler : classCopyFixupHandlers) { + handler(supercls, (Class)swcls); } // Mark this class as Swift-enhanced. @@ -368,103 +401,24 @@ void _objc_setClassCopyFixupHandler(void (* _Nonnull newFixupHandler) } -/*********************************************************************** -* unattachedCategories -* Returns the class => categories map of unattached categories. -* Locking: runtimeLock must be held by the caller. -**********************************************************************/ -static NXMapTable *unattachedCategories(void) -{ - runtimeLock.assertLocked(); - - static NXMapTable *category_map = nil; - - if (category_map) return category_map; - - // fixme initial map size - category_map = NXCreateMapTable(NXPtrValueMapPrototype, 16); - - return category_map; -} - - /*********************************************************************** * dataSegmentsContain * Returns true if the given address lies within a data segment in any * loaded image. -* -* This is optimized for use where the return value is expected to be -* true. A call where the return value is false always results in a -* slow linear search of all loaded images. A call where the return -* value is fast will often be fast due to caching. **********************************************************************/ -static bool dataSegmentsContain(const void *ptr) { - struct Range { - uintptr_t start, end; - bool contains(uintptr_t ptr) { - return start <= ptr && ptr <= end; - } - }; - - // This is a really simple linear searched cache. On a cache hit, - // the hit entry is moved to the front of the array. On a cache - // miss where a range is successfully found on the slow path, the - // found range is inserted at the beginning of the cache. This gives - // us fast access to the most recently used elements, and LRU - // eviction. - enum { cacheCount = 16 }; - static Range cache[cacheCount]; - - uintptr_t addr = (uintptr_t)ptr; - - // Special case a hit on the first entry of the cache. No - // bookkeeping is required at all in this case. - if (cache[0].contains(addr)) { - return true; - } - - // Search the rest of the cache. - for (unsigned i = 1; i < cacheCount; i++) { - if (cache[i].contains(addr)) { - // Cache hit. Move all preceding entries down one element, - // then place this entry at the front. - Range r = cache[i]; - memmove(&cache[1], &cache[0], i * sizeof(cache[0])); - cache[0] = r; - return true; +NEVER_INLINE +static bool +dataSegmentsContain(Class cls) +{ + uint32_t index; + if (objc::dataSegmentsRanges.find((uintptr_t)cls, index)) { + // if the class is realized (hence has a class_rw_t), + // memorize where we found the range + if (cls->isRealized()) { + cls->data()->witness = (uint16_t)index; } - } - - // Cache miss. Find the image header containing the given address. - // If there isn't one, then we're definitely not in any image, - // so return false. - Range found = { 0, 0 }; - auto *h = (headerType *)dyld_image_header_containing_address(ptr); - if (h == nullptr) - return false; - - // Iterate over the data segments in the found image. If the address - // lies within one, note the data segment range in `found`. - // TODO: this is more work than we'd like to do. All we really need - // is the full range of the image. Addresses within the TEXT segment - // would also be acceptable for our use case. If possible, we should - // change this to work with the full address range of the found - // image header. Another possibility would be to use the range - // from `h` to the end of the page containing `addr`. - foreach_data_segment(h, [&](const segmentType *seg, intptr_t slide) { - Range r; - r.start = seg->vmaddr + slide; - r.end = r.start + seg->vmsize; - if (r.contains(addr)) - found = r; - }); - - if (found.start != 0) { - memmove(&cache[1], &cache[0], (cacheCount - 1) * sizeof(cache[0])); - cache[0] = found; return true; } - return false; } @@ -474,17 +428,19 @@ bool contains(uintptr_t ptr) { * Return true if the class is known to the runtime (located within the * shared cache, within the data segment of a loaded image, or has been * allocated with obj_allocateClassPair). +* +* The result of this operation is cached on the class in a "witness" +* value that is cheaply checked in the fastpath. **********************************************************************/ -static bool isKnownClass(Class cls) { - // The order of conditionals here is important for speed. We want to - // put the most common cases first, but also the fastest cases - // first. Checking the shared region is both fast and common. - // Checking allocatedClasses is fast, but may not be common, - // depending on what the program is doing. Checking if data segments - // contain the address is slow, so do it last. - return (sharedRegionContains(cls) || - NXHashMember(allocatedClasses, cls) || - dataSegmentsContain(cls)); +ALWAYS_INLINE +static bool +isKnownClass(Class cls) +{ + if (fastpath(objc::dataSegmentsRanges.contains(cls->data()->witness, (uintptr_t)cls))) { + return true; + } + auto &set = objc::allocatedClasses.get(); + return set.find(cls) != set.end() || dataSegmentsContain(cls); } @@ -494,15 +450,19 @@ static bool isKnownClass(Class cls) { * automatically adds the metaclass of the class as well. * Locking: runtimeLock must be held by the caller. **********************************************************************/ -static void addClassTableEntry(Class cls, bool addMeta = true) { +static void +addClassTableEntry(Class cls, bool addMeta = true) +{ runtimeLock.assertLocked(); // This class is allowed to be a known class via the shared cache or via // data segments, but it is not allowed to be in the dynamic table already. - assert(!NXHashMember(allocatedClasses, cls)); + auto &set = objc::allocatedClasses.get(); + + ASSERT(set.find(cls) == set.end()); if (!isKnownClass(cls)) - NXHashInsert(allocatedClasses, cls); + set.insert(cls); if (addMeta) addClassTableEntry(cls->ISA(), false); } @@ -514,98 +474,15 @@ static void addClassTableEntry(Class cls, bool addMeta = true) { * with a fatal error if the class is not known. * Locking: runtimeLock must be held by the caller. **********************************************************************/ -static void checkIsKnownClass(Class cls) +ALWAYS_INLINE +static void +checkIsKnownClass(Class cls) { - if (!isKnownClass(cls)) + if (slowpath(!isKnownClass(cls))) { _objc_fatal("Attempt to use unknown class %p.", cls); -} - - -/*********************************************************************** -* addUnattachedCategoryForClass -* Records an unattached category. -* Locking: runtimeLock must be held by the caller. -**********************************************************************/ -static void addUnattachedCategoryForClass(category_t *cat, Class cls, - header_info *catHeader) -{ - runtimeLock.assertLocked(); - - // DO NOT use cat->cls! cls may be cat->cls->isa instead - NXMapTable *cats = unattachedCategories(); - category_list *list; - - list = (category_list *)NXMapGet(cats, cls); - if (!list) { - list = (category_list *) - calloc(sizeof(*list) + sizeof(list->list[0]), 1); - } else { - list = (category_list *) - realloc(list, sizeof(*list) + sizeof(list->list[0]) * (list->count + 1)); } - list->list[list->count++] = (locstamped_category_t){cat, catHeader}; - NXMapInsert(cats, cls, list); } - -/*********************************************************************** -* removeUnattachedCategoryForClass -* Removes an unattached category. -* Locking: runtimeLock must be held by the caller. -**********************************************************************/ -static void removeUnattachedCategoryForClass(category_t *cat, Class cls) -{ - runtimeLock.assertLocked(); - - // DO NOT use cat->cls! cls may be cat->cls->isa instead - NXMapTable *cats = unattachedCategories(); - category_list *list; - - list = (category_list *)NXMapGet(cats, cls); - if (!list) return; - - uint32_t i; - for (i = 0; i < list->count; i++) { - if (list->list[i].cat == cat) { - // shift entries to preserve list order - memmove(&list->list[i], &list->list[i+1], - (list->count-i-1) * sizeof(list->list[i])); - list->count--; - return; - } - } -} - - -/*********************************************************************** -* unattachedCategoriesForClass -* Returns the list of unattached categories for a class, and -* deletes them from the list. -* The result must be freed by the caller. -* Locking: runtimeLock must be held by the caller. -**********************************************************************/ -static category_list * -unattachedCategoriesForClass(Class cls, bool realizing) -{ - runtimeLock.assertLocked(); - return (category_list *)NXMapRemove(unattachedCategories(), cls); -} - - -/*********************************************************************** -* removeAllUnattachedCategoriesForClass -* Deletes all unattached categories (loaded or not) for a class. -* Locking: runtimeLock must be held by the caller. -**********************************************************************/ -static void removeAllUnattachedCategoriesForClass(Class cls) -{ - runtimeLock.assertLocked(); - - void *list = NXMapRemove(unattachedCategories(), cls); - if (list) free(list); -} - - /*********************************************************************** * classNSObject * Returns class NSObject. @@ -617,6 +494,11 @@ static Class classNSObject(void) return (Class)&OBJC_CLASS_$_NSObject; } +static Class metaclassNSObject(void) +{ + extern objc_class OBJC_METACLASS_$_NSObject; + return (Class)&OBJC_METACLASS_$_NSObject; +} /*********************************************************************** * printReplacements @@ -624,17 +506,17 @@ static Class classNSObject(void) * Warn about methods from cats that override other methods in cats or cls. * Assumes no methods from cats have been added to cls yet. **********************************************************************/ -static void printReplacements(Class cls, category_list *cats) +__attribute__((cold, noinline)) +static void +printReplacements(Class cls, const locstamped_category_t *cats_list, uint32_t cats_count) { uint32_t c; bool isMeta = cls->isMetaClass(); - if (!cats) return; - // Newest categories are LAST in cats // Later categories override earlier ones. - for (c = 0; c < cats->count; c++) { - category_t *cat = cats->list[c].cat; + for (c = 0; c < cats_count; c++) { + category_t *cat = cats_list[c].cat; method_list_t *mlist = cat->methodsForMeta(isMeta); if (!mlist) continue; @@ -647,7 +529,7 @@ static void printReplacements(Class cls, category_list *cats) // Look for method in earlier categories for (uint32_t c2 = 0; c2 < c; c2++) { - category_t *cat2 = cats->list[c2].cat; + category_t *cat2 = cats_list[c2].cat; const method_list_t *mlist2 = cat2->methodsForMeta(isMeta); if (!mlist2) continue; @@ -681,6 +563,603 @@ static void printReplacements(Class cls, category_list *cats) } +/*********************************************************************** +* unreasonableClassCount +* Provides an upper bound for any iteration of classes, +* to prevent spins when runtime metadata is corrupted. +**********************************************************************/ +static unsigned unreasonableClassCount() +{ + runtimeLock.assertLocked(); + + int base = NXCountMapTable(gdb_objc_realized_classes) + + getPreoptimizedClassUnreasonableCount(); + + // Provide lots of slack here. Some iterations touch metaclasses too. + // Some iterations backtrack (like realized class iteration). + // We don't need an efficient bound, merely one that prevents spins. + return (base + 1) * 16; +} + + +/*********************************************************************** +* Class enumerators +* The passed in block returns `false` if subclasses can be skipped +* Locking: runtimeLock must be held by the caller. +**********************************************************************/ +static inline void +foreach_realized_class_and_subclass_2(Class top, unsigned &count, + bool skip_metaclass, + bool (^code)(Class) __attribute((noescape))) +{ + Class cls = top; + + runtimeLock.assertLocked(); + ASSERT(top); + + while (1) { + if (--count == 0) { + _objc_fatal("Memory corruption in class list."); + } + + bool skip_subclasses; + + if (skip_metaclass && cls->isMetaClass()) { + skip_subclasses = true; + } else { + skip_subclasses = !code(cls); + } + + if (!skip_subclasses && cls->data()->firstSubclass) { + cls = cls->data()->firstSubclass; + } else { + while (!cls->data()->nextSiblingClass && cls != top) { + cls = cls->superclass; + if (--count == 0) { + _objc_fatal("Memory corruption in class list."); + } + } + if (cls == top) break; + cls = cls->data()->nextSiblingClass; + } + } +} + +// Enumerates a class and all of its realized subclasses. +static void +foreach_realized_class_and_subclass(Class top, bool (^code)(Class) __attribute((noescape))) +{ + unsigned int count = unreasonableClassCount(); + + foreach_realized_class_and_subclass_2(top, count, false, code); +} + +// Enumerates all realized classes and metaclasses. +static void +foreach_realized_class_and_metaclass(bool (^code)(Class) __attribute((noescape))) +{ + unsigned int count = unreasonableClassCount(); + + for (Class top = _firstRealizedClass; + top != nil; + top = top->data()->nextSiblingClass) + { + foreach_realized_class_and_subclass_2(top, count, false, code); + } +} + +// Enumerates all realized classes (ignoring metaclasses). +static void +foreach_realized_class(bool (^code)(Class) __attribute((noescape))) +{ + unsigned int count = unreasonableClassCount(); + + for (Class top = _firstRealizedClass; + top != nil; + top = top->data()->nextSiblingClass) + { + foreach_realized_class_and_subclass_2(top, count, true, code); + } +} + + +/*********************************************************************** + * Method Scanners / Optimization tracking + * Implementation of scanning for various implementations of methods. + **********************************************************************/ + +namespace objc { + +enum SelectorBundle { + AWZ, + RR, + Core, +}; + +namespace scanner { + +// The current state of NSObject swizzling for every scanner +// +// It allows for cheap checks of global swizzles, and also lets +// things like IMP Swizzling before NSObject has been initialized +// to be remembered, as setInitialized() would miss these. +// +// Every pair of bits describes a SelectorBundle. +// even bits: is NSObject class swizzled for this bundle +// odd bits: is NSObject meta class swizzled for this bundle +static uintptr_t NSObjectSwizzledMask; + +static ALWAYS_INLINE uintptr_t +swizzlingBit(SelectorBundle bundle, bool isMeta) +{ + return 1UL << (2 * bundle + isMeta); +} + +static void __attribute__((cold, noinline)) +printCustom(Class cls, SelectorBundle bundle, bool inherited) +{ + static char const * const SelectorBundleName[] = { + [AWZ] = "CUSTOM AWZ", + [RR] = "CUSTOM RR", + [Core] = "CUSTOM Core", + }; + + _objc_inform("%s: %s%s%s", SelectorBundleName[bundle], + cls->nameForLogging(), + cls->isMetaClass() ? " (meta)" : "", + inherited ? " (inherited)" : ""); +} + +enum class Scope { Instances, Classes, Both }; + +template +class Mixin { + + // work around compiler being broken with templates using Class/objc_class, + // probably some weird confusion with Class being builtin + ALWAYS_INLINE static objc_class *as_objc_class(Class cls) { + return (objc_class *)cls; + } + + static void + setCustomRecursively(Class cls, bool inherited = false) + { + foreach_realized_class_and_subclass(cls, [=](Class c){ + if (c != cls && !as_objc_class(c)->isInitialized()) { + // Subclass not yet initialized. Wait for setInitialized() to do it + return false; + } + if (Traits::isCustom(c)) { + return false; + } + Traits::setCustom(c); + if (ShouldPrint) { + printCustom(cls, Bundle, inherited || c != cls); + } + return true; + }); + } + + static bool + isNSObjectSwizzled(bool isMeta) + { + return NSObjectSwizzledMask & swizzlingBit(Bundle, isMeta); + } + + static void + setNSObjectSwizzled(Class NSOClass, bool isMeta) + { + NSObjectSwizzledMask |= swizzlingBit(Bundle, isMeta); + if (as_objc_class(NSOClass)->isInitialized()) { + setCustomRecursively(NSOClass); + } + } + + static void + scanChangedMethodForUnknownClass(const method_t *meth) + { + Class cls; + + cls = classNSObject(); + if (Domain != Scope::Classes && !isNSObjectSwizzled(NO)) { + for (const auto &meth2: as_objc_class(cls)->data()->methods) { + if (meth == &meth2) { + setNSObjectSwizzled(cls, NO); + break; + } + } + } + + cls = metaclassNSObject(); + if (Domain != Scope::Instances && !isNSObjectSwizzled(YES)) { + for (const auto &meth2: as_objc_class(cls)->data()->methods) { + if (meth == &meth2) { + setNSObjectSwizzled(cls, YES); + break; + } + } + } + } + + static void + scanAddedClassImpl(Class cls, bool isMeta) + { + Class NSOClass = (isMeta ? metaclassNSObject() : classNSObject()); + bool setCustom = NO, inherited = NO; + + if (isNSObjectSwizzled(isMeta)) { + setCustom = YES; + } else if (cls == NSOClass) { + // NSObject is default but we need to check categories + auto &methods = as_objc_class(cls)->data()->methods; + setCustom = Traits::scanMethodLists(methods.beginCategoryMethodLists(), + methods.endCategoryMethodLists(cls)); + } else if (!isMeta && !as_objc_class(cls)->superclass) { + // Custom Root class + setCustom = YES; + } else if (Traits::isCustom(as_objc_class(cls)->superclass)) { + // Superclass is custom, therefore we are too. + setCustom = YES; + inherited = YES; + } else { + // Not NSObject. + auto &methods = as_objc_class(cls)->data()->methods; + setCustom = Traits::scanMethodLists(methods.beginLists(), + methods.endLists()); + } + if (slowpath(setCustom)) { + if (ShouldPrint) printCustom(cls, Bundle, inherited); + } else { + Traits::setDefault(cls); + } + } + +public: + // Scan a class that is about to be marked Initialized for particular + // bundles of selectors, and mark the class and its children + // accordingly. + // + // This also handles inheriting properties from its superclass. + // + // Caller: objc_class::setInitialized() + static void + scanInitializedClass(Class cls, Class metacls) + { + if (Domain != Scope::Classes) { + scanAddedClassImpl(cls, false); + } + if (Domain != Scope::Instances) { + scanAddedClassImpl(metacls, true); + } + } + + // Inherit various properties from the superclass when a class + // is being added to the graph. + // + // Caller: addSubclass() + static void + scanAddedSubClass(Class subcls, Class supercls) + { + if (slowpath(Traits::isCustom(supercls) && !Traits::isCustom(subcls))) { + setCustomRecursively(subcls, true); + } + } + + // Scan Method lists for selectors that would override things + // in a Bundle. + // + // This is used to detect when categories override problematic selectors + // are injected in a class after it has been initialized. + // + // Caller: prepareMethodLists() + static void + scanAddedMethodLists(Class cls, method_list_t **mlists, int count) + { + if (slowpath(Traits::isCustom(cls))) { + return; + } + if (slowpath(Traits::scanMethodLists(mlists, mlists + count))) { + setCustomRecursively(cls); + } + } + + // Handle IMP Swizzling (the IMP for an exisiting method being changed). + // + // In almost all cases, IMP swizzling does not affect custom bits. + // Custom search will already find the method whether or not + // it is swizzled, so it does not transition from non-custom to custom. + // + // The only cases where IMP swizzling can affect the custom bits is + // if the swizzled method is one of the methods that is assumed to be + // non-custom. These special cases are listed in setInitialized(). + // We look for such cases here. + // + // Caller: Swizzling methods via adjustCustomFlagsForMethodChange() + static void + scanChangedMethod(Class cls, const method_t *meth) + { + if (fastpath(!Traits::isInterestingSelector(meth->name))) { + return; + } + + if (cls) { + bool isMeta = as_objc_class(cls)->isMetaClass(); + if (isMeta && Domain != Scope::Instances) { + if (cls == metaclassNSObject() && !isNSObjectSwizzled(isMeta)) { + setNSObjectSwizzled(cls, isMeta); + } + } + if (!isMeta && Domain != Scope::Classes) { + if (cls == classNSObject() && !isNSObjectSwizzled(isMeta)) { + setNSObjectSwizzled(cls, isMeta); + } + } + } else { + // We're called from method_exchangeImplementations, only NSObject + // class and metaclass may be problematic (exchanging the default + // builtin IMP of an interesting seleector, is a swizzling that, + // may flip our scanned property. For other classes, the previous + // value had already flipped the property). + // + // However, as we don't know the class, we need to scan all of + // NSObject class and metaclass methods (this is SLOW). + scanChangedMethodForUnknownClass(meth); + } + } +}; + +} // namespace scanner + +// AWZ methods: +alloc / +allocWithZone: +struct AWZScanner : scanner::Mixin { + static bool isCustom(Class cls) { + return cls->hasCustomAWZ(); + } + static void setCustom(Class cls) { + cls->setHasCustomAWZ(); + } + static void setDefault(Class cls) { + cls->setHasDefaultAWZ(); + } + static bool isInterestingSelector(SEL sel) { + return sel == @selector(alloc) || sel == @selector(allocWithZone:); + } + static bool scanMethodLists(method_list_t **mlists, method_list_t **end) { + SEL sels[2] = { @selector(alloc), @selector(allocWithZone:), }; + return method_lists_contains_any(mlists, end, sels, 2); + } +}; + +// Retain/Release methods that are extremely rarely overridden +// +// retain/release/autorelease/retainCount/ +// _tryRetain/_isDeallocating/retainWeakReference/allowsWeakReference +struct RRScanner : scanner::Mixin { + static bool isCustom(Class cls) { + return cls->hasCustomRR(); + } + static void setCustom(Class cls) { + cls->setHasCustomRR(); + } + static void setDefault(Class cls) { + cls->setHasDefaultRR(); + } + static bool isInterestingSelector(SEL sel) { + return sel == @selector(retain) || + sel == @selector(release) || + sel == @selector(autorelease) || + sel == @selector(_tryRetain) || + sel == @selector(_isDeallocating) || + sel == @selector(retainCount) || + sel == @selector(allowsWeakReference) || + sel == @selector(retainWeakReference); + } + static bool scanMethodLists(method_list_t **mlists, method_list_t **end) { + SEL sels[8] = { + @selector(retain), + @selector(release), + @selector(autorelease), + @selector(_tryRetain), + @selector(_isDeallocating), + @selector(retainCount), + @selector(allowsWeakReference), + @selector(retainWeakReference), + }; + return method_lists_contains_any(mlists, end, sels, 8); + } +}; + +// Core NSObject methods that are extremely rarely overridden +// +// +new, ±class, ±self, ±isKindOfClass:, ±respondsToSelector +struct CoreScanner : scanner::Mixin { + static bool isCustom(Class cls) { + return cls->hasCustomCore(); + } + static void setCustom(Class cls) { + cls->setHasCustomCore(); + } + static void setDefault(Class cls) { + cls->setHasDefaultCore(); + } + static bool isInterestingSelector(SEL sel) { + return sel == @selector(new) || + sel == @selector(self) || + sel == @selector(class) || + sel == @selector(isKindOfClass:) || + sel == @selector(respondsToSelector:); + } + static bool scanMethodLists(method_list_t **mlists, method_list_t **end) { + SEL sels[5] = { + @selector(new), + @selector(self), + @selector(class), + @selector(isKindOfClass:), + @selector(respondsToSelector:) + }; + return method_lists_contains_any(mlists, end, sels, 5); + } +}; + +class category_list : nocopy_t { + union { + locstamped_category_t lc; + struct { + locstamped_category_t *array; + // this aliases with locstamped_category_t::hi + // which is an aliased pointer + uint32_t is_array : 1; + uint32_t count : 31; + uint32_t size : 32; + }; + } _u; + +public: + category_list() : _u{{nullptr, nullptr}} { } + category_list(locstamped_category_t lc) : _u{{lc}} { } + category_list(category_list &&other) : category_list() { + std::swap(_u, other._u); + } + ~category_list() + { + if (_u.is_array) { + free(_u.array); + } + } + + uint32_t count() const + { + if (_u.is_array) return _u.count; + return _u.lc.cat ? 1 : 0; + } + + uint32_t arrayByteSize(uint32_t size) const + { + return sizeof(locstamped_category_t) * size; + } + + const locstamped_category_t *array() const + { + return _u.is_array ? _u.array : &_u.lc; + } + + void append(locstamped_category_t lc) + { + if (_u.is_array) { + if (_u.count == _u.size) { + // Have a typical malloc growth: + // - size <= 8: grow by 2 + // - size <= 16: grow by 4 + // - size <= 32: grow by 8 + // ... etc + _u.size += _u.size < 8 ? 2 : 1 << (fls(_u.size) - 2); + _u.array = (locstamped_category_t *)reallocf(_u.array, arrayByteSize(_u.size)); + } + _u.array[_u.count++] = lc; + } else if (_u.lc.cat == NULL) { + _u.lc = lc; + } else { + locstamped_category_t *arr = (locstamped_category_t *)malloc(arrayByteSize(2)); + arr[0] = _u.lc; + arr[1] = lc; + + _u.array = arr; + _u.is_array = true; + _u.count = 2; + _u.size = 2; + } + } + + void erase(category_t *cat) + { + if (_u.is_array) { + for (int i = 0; i < _u.count; i++) { + if (_u.array[i].cat == cat) { + // shift entries to preserve list order + memmove(&_u.array[i], &_u.array[i+1], arrayByteSize(_u.count - i - 1)); + return; + } + } + } else if (_u.lc.cat == cat) { + _u.lc.cat = NULL; + _u.lc.hi = NULL; + } + } +}; + +class UnattachedCategories : public ExplicitInitDenseMap +{ +public: + void addForClass(locstamped_category_t lc, Class cls) + { + runtimeLock.assertLocked(); + + if (slowpath(PrintConnecting)) { + _objc_inform("CLASS: found category %c%s(%s)", + cls->isMetaClass() ? '+' : '-', + cls->nameForLogging(), lc.cat->name); + } + + auto result = get().try_emplace(cls, lc); + if (!result.second) { + result.first->second.append(lc); + } + } + + void attachToClass(Class cls, Class previously, int flags) + { + runtimeLock.assertLocked(); + ASSERT((flags & ATTACH_CLASS) || + (flags & ATTACH_METACLASS) || + (flags & ATTACH_CLASS_AND_METACLASS)); + + auto &map = get(); + auto it = map.find(previously); + + if (it != map.end()) { + category_list &list = it->second; + if (flags & ATTACH_CLASS_AND_METACLASS) { + int otherFlags = flags & ~ATTACH_CLASS_AND_METACLASS; + attachCategories(cls, list.array(), list.count(), otherFlags | ATTACH_CLASS); + attachCategories(cls->ISA(), list.array(), list.count(), otherFlags | ATTACH_METACLASS); + } else { + attachCategories(cls, list.array(), list.count(), flags); + } + map.erase(it); + } + } + + void eraseCategoryForClass(category_t *cat, Class cls) + { + runtimeLock.assertLocked(); + + auto &map = get(); + auto it = map.find(cls); + if (it != map.end()) { + category_list &list = it->second; + list.erase(cat); + if (list.count() == 0) { + map.erase(it); + } + } + } + + void eraseClass(Class cls) + { + runtimeLock.assertLocked(); + + get().erase(cls); + } +}; + +static UnattachedCategories unattachedCategories; + +} // namespace objc + static bool isBundleClass(Class cls) { return cls->data()->ro->flags & RO_FROM_BUNDLE; @@ -691,10 +1170,11 @@ static bool isBundleClass(Class cls) fixupMethodList(method_list_t *mlist, bool bundleCopy, bool sort) { runtimeLock.assertLocked(); - assert(!mlist->isFixedUp()); + ASSERT(!mlist->isFixedUp()); // fixme lock less in attachMethodLists ? - { + // dyld3 may have already uniqued, but not sorted, the list + if (!mlist->isUniqued()) { mutex_locker_t lock(selLock); // Unique selectors in list. @@ -716,23 +1196,19 @@ static bool isBundleClass(Class cls) static void -prepareMethodLists(Class cls, method_list_t **addedLists, int addedCount, +prepareMethodLists(Class cls, method_list_t **addedLists, int addedCount, bool baseMethods, bool methodsFromBundle) { runtimeLock.assertLocked(); if (addedCount == 0) return; - // Don't scan redundantly - bool scanForCustomRR = !cls->hasCustomRR(); - bool scanForCustomAWZ = !cls->hasCustomAWZ(); - - // There exist RR/AWZ special cases for some class's base methods. - // But this code should never need to scan base methods for RR/AWZ: - // default RR/AWZ cannot be set before setInitialized(). + // There exist RR/AWZ/Core special cases for some class's base methods. + // But this code should never need to scan base methods for RR/AWZ/Core: + // default RR/AWZ/Core cannot be set before setInitialized(). // Therefore we need not handle any special cases here. if (baseMethods) { - assert(!scanForCustomRR && !scanForCustomAWZ); + ASSERT(cls->hasCustomAWZ() && cls->hasCustomRR() && cls->hasCustomCore()); } // Add method lists to array. @@ -741,22 +1217,21 @@ static bool isBundleClass(Class cls) for (int i = 0; i < addedCount; i++) { method_list_t *mlist = addedLists[i]; - assert(mlist); + ASSERT(mlist); // Fixup selectors if necessary if (!mlist->isFixedUp()) { fixupMethodList(mlist, methodsFromBundle, true/*sort*/); } + } - // Scan for method implementations tracked by the class's flags - if (scanForCustomRR && methodListImplementsRR(mlist)) { - cls->setHasCustomRR(); - scanForCustomRR = false; - } - if (scanForCustomAWZ && methodListImplementsAWZ(mlist)) { - cls->setHasCustomAWZ(); - scanForCustomAWZ = false; - } + // If the class is initialized, then scan for method implementations + // tracked by the class's flags. If it's not initialized yet, + // then objc_class::setInitialized() will take care of it. + if (cls->isInitialized()) { + objc::AWZScanner::scanAddedMethodLists(cls, addedLists, addedCount); + objc::RRScanner::scanAddedMethodLists(cls, addedLists, addedCount); + objc::CoreScanner::scanAddedMethodLists(cls, addedLists, addedCount); } } @@ -764,61 +1239,84 @@ static bool isBundleClass(Class cls) // Attach method lists and properties and protocols from categories to a class. // Assumes the categories in cats are all loaded and sorted by load order, // oldest categories first. -static void -attachCategories(Class cls, category_list *cats, bool flush_caches) -{ - if (!cats) return; - if (PrintReplacedMethods) printReplacements(cls, cats); - - bool isMeta = cls->isMetaClass(); - - // fixme rearrange to remove these intermediate allocations - method_list_t **mlists = (method_list_t **) - malloc(cats->count * sizeof(*mlists)); - property_list_t **proplists = (property_list_t **) - malloc(cats->count * sizeof(*proplists)); - protocol_list_t **protolists = (protocol_list_t **) - malloc(cats->count * sizeof(*protolists)); - - // Count backwards through cats to get newest categories first - int mcount = 0; - int propcount = 0; - int protocount = 0; - int i = cats->count; +static void +attachCategories(Class cls, const locstamped_category_t *cats_list, uint32_t cats_count, + int flags) +{ + if (slowpath(PrintReplacedMethods)) { + printReplacements(cls, cats_list, cats_count); + } + if (slowpath(PrintConnecting)) { + _objc_inform("CLASS: attaching %d categories to%s class '%s'%s", + cats_count, (flags & ATTACH_EXISTING) ? " existing" : "", + cls->nameForLogging(), (flags & ATTACH_METACLASS) ? " (meta)" : ""); + } + + /* + * Only a few classes have more than 64 categories during launch. + * This uses a little stack, and avoids malloc. + * + * Categories must be added in the proper order, which is back + * to front. To do that with the chunking, we iterate cats_list + * from front to back, build up the local buffers backwards, + * and call attachLists on the chunks. attachLists prepends the + * lists, so the final result is in the expected order. + */ + constexpr uint32_t ATTACH_BUFSIZ = 64; + method_list_t *mlists[ATTACH_BUFSIZ]; + property_list_t *proplists[ATTACH_BUFSIZ]; + protocol_list_t *protolists[ATTACH_BUFSIZ]; + + uint32_t mcount = 0; + uint32_t propcount = 0; + uint32_t protocount = 0; bool fromBundle = NO; - while (i--) { - auto& entry = cats->list[i]; + bool isMeta = (flags & ATTACH_METACLASS); + auto rw = cls->data(); + + for (uint32_t i = 0; i < cats_count; i++) { + auto& entry = cats_list[i]; method_list_t *mlist = entry.cat->methodsForMeta(isMeta); if (mlist) { - mlists[mcount++] = mlist; + if (mcount == ATTACH_BUFSIZ) { + prepareMethodLists(cls, mlists, mcount, NO, fromBundle); + rw->methods.attachLists(mlists, mcount); + mcount = 0; + } + mlists[ATTACH_BUFSIZ - ++mcount] = mlist; fromBundle |= entry.hi->isBundle(); } - property_list_t *proplist = + property_list_t *proplist = entry.cat->propertiesForMeta(isMeta, entry.hi); if (proplist) { - proplists[propcount++] = proplist; + if (propcount == ATTACH_BUFSIZ) { + rw->properties.attachLists(proplists, propcount); + propcount = 0; + } + proplists[ATTACH_BUFSIZ - ++propcount] = proplist; } - protocol_list_t *protolist = entry.cat->protocols; + protocol_list_t *protolist = entry.cat->protocolsForMeta(isMeta); if (protolist) { - protolists[protocount++] = protolist; + if (protocount == ATTACH_BUFSIZ) { + rw->protocols.attachLists(protolists, protocount); + protocount = 0; + } + protolists[ATTACH_BUFSIZ - ++protocount] = protolist; } } - auto rw = cls->data(); - - prepareMethodLists(cls, mlists, mcount, NO, fromBundle); - rw->methods.attachLists(mlists, mcount); - free(mlists); - if (flush_caches && mcount > 0) flushCaches(cls); + if (mcount > 0) { + prepareMethodLists(cls, mlists + ATTACH_BUFSIZ - mcount, mcount, NO, fromBundle); + rw->methods.attachLists(mlists + ATTACH_BUFSIZ - mcount, mcount); + if (flags & ATTACH_EXISTING) flushCaches(cls); + } - rw->properties.attachLists(proplists, propcount); - free(proplists); + rw->properties.attachLists(proplists + ATTACH_BUFSIZ - propcount, propcount); - rw->protocols.attachLists(protolists, protocount); - free(protolists); + rw->protocols.attachLists(protolists + ATTACH_BUFSIZ - protocount, protocount); } @@ -828,7 +1326,7 @@ static bool isBundleClass(Class cls) * Attaches any outstanding categories. * Locking: runtimeLock must be held by the caller **********************************************************************/ -static void methodizeClass(Class cls) +static void methodizeClass(Class cls, Class previously) { runtimeLock.assertLocked(); @@ -863,24 +1361,24 @@ static void methodizeClass(Class cls) // them already. These apply before category replacements. if (cls->isRootMetaclass()) { // root metaclass - addMethod(cls, SEL_initialize, (IMP)&objc_noop_imp, "", NO); + addMethod(cls, @selector(initialize), (IMP)&objc_noop_imp, "", NO); } // Attach categories. - category_list *cats = unattachedCategoriesForClass(cls, true /*realizing*/); - attachCategories(cls, cats, false /*don't flush caches*/); - - if (PrintConnecting) { - if (cats) { - for (uint32_t i = 0; i < cats->count; i++) { - _objc_inform("CLASS: attached category %c%s(%s)", - isMeta ? '+' : '-', - cls->nameForLogging(), cats->list[i].cat->name); - } + if (previously) { + if (isMeta) { + objc::unattachedCategories.attachToClass(cls, previously, + ATTACH_METACLASS); + } else { + // When a class relocates, categories with class methods + // may be registered on the class itself rather than on + // the metaclass. Tell attachToClass to look for those. + objc::unattachedCategories.attachToClass(cls, previously, + ATTACH_CLASS_AND_METACLASS); } } - - if (cats) free(cats); + objc::unattachedCategories.attachToClass(cls, cls, + isMeta ? ATTACH_METACLASS : ATTACH_CLASS); #if DEBUG // Debug: sanity-check all SELs; log method list contents @@ -889,41 +1387,12 @@ static void methodizeClass(Class cls) _objc_inform("METHOD %c[%s %s]", isMeta ? '+' : '-', cls->nameForLogging(), sel_getName(meth.name)); } - assert(sel_registerName(sel_getName(meth.name)) == meth.name); + ASSERT(sel_registerName(sel_getName(meth.name)) == meth.name); } #endif } -/*********************************************************************** -* remethodizeClass -* Attach outstanding categories to an existing class. -* Fixes up cls's method list, protocol list, and property list. -* Updates method caches for cls and its subclasses. -* Locking: runtimeLock must be held by the caller -**********************************************************************/ -static void remethodizeClass(Class cls) -{ - category_list *cats; - bool isMeta; - - runtimeLock.assertLocked(); - - isMeta = cls->isMetaClass(); - - // Re-methodizing: check for more categories - if ((cats = unattachedCategoriesForClass(cls, false/*not realizing*/))) { - if (PrintConnecting) { - _objc_inform("CLASS: attaching categories to class '%s' %s", - cls->nameForLogging(), isMeta ? "(meta)" : ""); - } - - attachCategories(cls, cats, true /*flush caches*/); - free(cats); - } -} - - /*********************************************************************** * nonMetaClasses * Returns the secondary metaclass => class map @@ -966,9 +1435,9 @@ static void addNonMetaClass(Class cls) void *old; old = NXMapInsert(nonMetaClasses(), cls->ISA(), cls); - assert(!cls->isMetaClassMaybeUnrealized()); - assert(cls->ISA()->isMetaClassMaybeUnrealized()); - assert(!old); + ASSERT(!cls->isMetaClassMaybeUnrealized()); + ASSERT(cls->ISA()->isMetaClassMaybeUnrealized()); + ASSERT(!old); } @@ -1108,19 +1577,25 @@ static bool scanMangledField(const char *&string, const char *end, // This is a misnomer: gdb_objc_realized_classes is actually a list of // named classes not in the dyld shared cache, whether realized or not. NXMapTable *gdb_objc_realized_classes; // exported for debuggers in objc-gdb.h +uintptr_t objc_debug_realized_class_generation_count; static Class getClass_impl(const char *name) { runtimeLock.assertLocked(); // allocated in _read_images - assert(gdb_objc_realized_classes); + ASSERT(gdb_objc_realized_classes); // Try runtime-allocated table Class result = (Class)NXMapGet(gdb_objc_realized_classes, name); if (result) return result; - // Try table from dyld shared cache + // Try table from dyld shared cache. + // Note we do this last to handle the case where we dlopen'ed a shared cache + // dylib with duplicates of classes already present in the main executable. + // In that case, we put the class from the main executable in + // gdb_objc_realized_classes and want to check that before considering any + // newly loaded shared cache binaries. return getPreoptimizedClass(name); } @@ -1163,10 +1638,10 @@ static void addNamedClass(Class cls, const char *name, Class replacing = nil) } else { NXMapInsert(gdb_objc_realized_classes, name, cls); } - assert(!(cls->data()->flags & RO_META)); + ASSERT(!(cls->data()->flags & RO_META)); // wrong: constructed classes are already realized when they get here - // assert(!cls->isRealized()); + // ASSERT(!cls->isRealized()); } @@ -1178,7 +1653,7 @@ static void addNamedClass(Class cls, const char *name, Class replacing = nil) static void removeNamedClass(Class cls, const char *name) { runtimeLock.assertLocked(); - assert(!(cls->data()->flags & RO_META)); + ASSERT(!(cls->data()->flags & RO_META)); if (cls == NXMapGet(gdb_objc_realized_classes, name)) { NXMapRemove(gdb_objc_realized_classes, name); } else { @@ -1189,25 +1664,6 @@ static void removeNamedClass(Class cls, const char *name) } -/*********************************************************************** -* unreasonableClassCount -* Provides an upper bound for any iteration of classes, -* to prevent spins when runtime metadata is corrupted. -**********************************************************************/ -unsigned unreasonableClassCount() -{ - runtimeLock.assertLocked(); - - int base = NXCountMapTable(gdb_objc_realized_classes) + - getPreoptimizedClassUnreasonableCount(); - - // Provide lots of slack here. Some iterations touch metaclasses too. - // Some iterations backtrack (like realized class iteration). - // We don't need an efficient bound, merely one that prevents spins. - return (base + 1) * 16; -} - - /*********************************************************************** * futureNamedClasses * Returns the classname => future class map for unrealized future classes. @@ -1256,7 +1712,7 @@ static void addFutureNamedClass(const char *name, Class cls) cls->data()->flags = RO_FUTURE; old = NXMapKeyCopyingInsert(futureNamedClasses(), name, cls); - assert(!old); + ASSERT(!old); } @@ -1291,21 +1747,14 @@ static Class popFutureNamedClass(const char *name) * Returns the oldClass => nil map for ignored weak-linked classes. * Locking: runtimeLock must be read- or write-locked by the caller **********************************************************************/ -static NXMapTable *remappedClasses(bool create) +static objc::DenseMap *remappedClasses(bool create) { - static NXMapTable *remapped_class_map = nil; + static objc::LazyInitDenseMap remapped_class_map; runtimeLock.assertLocked(); - if (remapped_class_map) return remapped_class_map; - if (!create) return nil; - - // remapped_class_map is big enough to hold CF's classes and a few others - INIT_ONCE_PTR(remapped_class_map, - NXCreateMapTable(NXPtrValueMapPrototype, 32), - NXFreeMapTable(v)); - - return remapped_class_map; + // start big enough to hold CF's classes and a few others + return remapped_class_map.get(create, 32); } @@ -1321,8 +1770,8 @@ static bool noClassesRemapped(void) bool result = (remappedClasses(NO) == nil); #if DEBUG // Catch construction of an empty table, which defeats optimization. - NXMapTable *map = remappedClasses(NO); - if (map) assert(NXCountMapTable(map) > 0); + auto *map = remappedClasses(NO); + if (map) ASSERT(map->size() > 0); #endif return result; } @@ -1343,9 +1792,18 @@ static void addRemappedClass(Class oldcls, Class newcls) (void*)newcls, (void*)oldcls, oldcls->nameForLogging()); } - void *old; - old = NXMapInsert(remappedClasses(YES), oldcls, newcls); - assert(!old); + auto result = remappedClasses(YES)->insert({ oldcls, newcls }); +#if DEBUG + if (!std::get<1>(result)) { + // An existing mapping was overwritten. This is not allowed + // unless it was to nil. + auto iterator = std::get<0>(result); + auto value = std::get<1>(*iterator); + ASSERT(value == nil); + } +#else + (void)result; +#endif } @@ -1360,16 +1818,16 @@ static Class remapClass(Class cls) { runtimeLock.assertLocked(); - Class c2; - if (!cls) return nil; - NXMapTable *map = remappedClasses(NO); - if (!map || NXMapMember(map, cls, (void**)&c2) == NX_MAPNOTAKEY) { + auto *map = remappedClasses(NO); + if (!map) return cls; - } else { - return c2; - } + + auto iterator = map->find(cls); + if (iterator == map->end()) + return cls; + return std::get<1>(*iterator); } static Class remapClass(classref_t cls) @@ -1398,6 +1856,22 @@ static void remapClassRef(Class *clsref) } +_Nullable Class +objc_loadClassref(_Nullable Class * _Nonnull clsref) +{ + auto *atomicClsref = explicit_atomic::from_pointer((uintptr_t *)clsref); + + uintptr_t cls = atomicClsref->load(std::memory_order_relaxed); + if (fastpath((cls & 1) == 0)) + return (Class)cls; + + auto stub = (stub_class_t *)(cls & ~1ULL); + Class initialized = stub->initializer((Class)stub, nil); + atomicClsref->store((uintptr_t)initialized, std::memory_order_relaxed); + return initialized; +} + + /*********************************************************************** * getMaybeUnrealizedNonMetaClass * Return the ordinary class for this class or metaclass. @@ -1409,9 +1883,9 @@ static void remapClassRef(Class *clsref) **********************************************************************/ static Class getMaybeUnrealizedNonMetaClass(Class metacls, id inst) { - static int total, named, secondary, sharedcache; + static int total, named, secondary, sharedcache, dyld3; runtimeLock.assertLocked(); - assert(metacls->isRealized()); + ASSERT(metacls->isRealized()); total++; @@ -1425,9 +1899,9 @@ static Class getMaybeUnrealizedNonMetaClass(Class metacls, id inst) // where inst == inst->ISA() == metacls is possible if (metacls->ISA() == metacls) { Class cls = metacls->superclass; - assert(cls->isRealized()); - assert(!cls->isMetaClass()); - assert(cls->ISA() == metacls); + ASSERT(cls->isRealized()); + ASSERT(!cls->isMetaClass()); + ASSERT(cls->ISA() == metacls); if (cls->ISA() == metacls) return cls; } @@ -1439,7 +1913,7 @@ static Class getMaybeUnrealizedNonMetaClass(Class metacls, id inst) // reallocating classes if cls is unrealized. while (cls) { if (cls->ISA() == metacls) { - assert(!cls->isMetaClassMaybeUnrealized()); + ASSERT(!cls->isMetaClassMaybeUnrealized()); return cls; } cls = cls->superclass; @@ -1454,7 +1928,7 @@ static Class getMaybeUnrealizedNonMetaClass(Class metacls, id inst) // try name lookup { Class cls = getClassExceptSomeSwift(metacls->mangledName()); - if (cls->ISA() == metacls) { + if (cls && cls->ISA() == metacls) { named++; if (PrintInitializing) { _objc_inform("INITIALIZE: %d/%d (%g%%) " @@ -1476,7 +1950,44 @@ static Class getMaybeUnrealizedNonMetaClass(Class metacls, id inst) secondary, total, secondary*100.0/total); } - assert(cls->ISA() == metacls); + ASSERT(cls->ISA() == metacls); + return cls; + } + } + + // try the dyld closure table + if (isPreoptimized()) + { + // Try table from dyld closure first. It was built to ignore the dupes it + // knows will come from the cache, so anything left in here was there when + // we launched + Class cls = nil; + // Note, we have to pass the lambda directly here as otherwise we would try + // message copy and autorelease. + _dyld_for_each_objc_class(metacls->mangledName(), + [&cls, metacls](void* classPtr, bool isLoaded, bool* stop) { + // Skip images which aren't loaded. This supports the case where dyld + // might soft link an image from the main binary so its possibly not + // loaded yet. + if (!isLoaded) + return; + + // Found a loaded image with this class name, so check if its the right one + Class result = (Class)classPtr; + if (result->ISA() == metacls) { + cls = result; + *stop = true; + } + }); + + if (cls) { + dyld3++; + if (PrintInitializing) { + _objc_inform("INITIALIZE: %d/%d (%g%%) " + "successful dyld closure metaclass lookups", + dyld3, total, dyld3*100.0/total); + } + return cls; } } @@ -1528,7 +2039,7 @@ static Class initializeAndMaybeRelock(Class cls, id inst, mutex_t& lock, bool leaveLocked) { lock.assertLocked(); - assert(cls->isRealized()); + ASSERT(cls->isRealized()); if (cls->isInitialized()) { if (!leaveLocked) lock.unlock(); @@ -1554,7 +2065,7 @@ static Class initializeAndMaybeRelock(Class cls, id inst, } // runtimeLock is now unlocked, for +initialize dispatch - assert(nonmeta->isRealized()); + ASSERT(nonmeta->isRealized()); initializeNonMetaClass(nonmeta); if (leaveLocked) runtimeLock.lock(); @@ -1580,18 +2091,14 @@ static Class initializeAndLeaveLocked(Class cls, id obj, mutex_t& lock) * Adds cls as a new realized root class. * Locking: runtimeLock must be held by the caller. **********************************************************************/ -static Class _firstRealizedClass = nil; -Class firstRealizedClass() -{ - runtimeLock.assertLocked(); - return _firstRealizedClass; -} - static void addRootClass(Class cls) { runtimeLock.assertLocked(); - assert(cls->isRealized()); + ASSERT(cls->isRealized()); + + objc_debug_realized_class_generation_count++; + cls->data()->nextSiblingClass = _firstRealizedClass; _firstRealizedClass = cls; } @@ -1600,6 +2107,8 @@ static void removeRootClass(Class cls) { runtimeLock.assertLocked(); + objc_debug_realized_class_generation_count++; + Class *classp; for (classp = &_firstRealizedClass; *classp != cls; @@ -1620,8 +2129,11 @@ static void addSubclass(Class supercls, Class subcls) runtimeLock.assertLocked(); if (supercls && subcls) { - assert(supercls->isRealized()); - assert(subcls->isRealized()); + ASSERT(supercls->isRealized()); + ASSERT(subcls->isRealized()); + + objc_debug_realized_class_generation_count++; + subcls->data()->nextSiblingClass = supercls->data()->firstSubclass; supercls->data()->firstSubclass = subcls; @@ -1633,18 +2145,14 @@ static void addSubclass(Class supercls, Class subcls) subcls->setHasCxxDtor(); } - if (supercls->hasCustomRR()) { - subcls->setHasCustomRR(true); - } - - if (supercls->hasCustomAWZ()) { - subcls->setHasCustomAWZ(true); - } + objc::AWZScanner::scanAddedSubClass(subcls, supercls); + objc::RRScanner::scanAddedSubClass(subcls, supercls); + objc::CoreScanner::scanAddedSubClass(subcls, supercls); - // Special case: instancesRequireRawIsa does not propagate + // Special case: instancesRequireRawIsa does not propagate // from root class to root metaclass if (supercls->instancesRequireRawIsa() && supercls->superclass) { - subcls->setInstancesRequireRawIsa(true); + subcls->setInstancesRequireRawIsaRecursively(true); } } } @@ -1658,16 +2166,18 @@ static void addSubclass(Class supercls, Class subcls) static void removeSubclass(Class supercls, Class subcls) { runtimeLock.assertLocked(); - assert(supercls->isRealized()); - assert(subcls->isRealized()); - assert(subcls->superclass == supercls); + ASSERT(supercls->isRealized()); + ASSERT(subcls->isRealized()); + ASSERT(subcls->superclass == supercls); + objc_debug_realized_class_generation_count++; + Class *cp; for (cp = &supercls->data()->firstSubclass; *cp && *cp != subcls; cp = &(*cp)->data()->nextSiblingClass) ; - assert(*cp == subcls); + ASSERT(*cp == subcls); *cp = subcls->data()->nextSiblingClass; } @@ -1697,7 +2207,7 @@ static void removeSubclass(Class supercls, Class subcls) * Looks up a protocol by name. Demangled Swift names are recognized. * Locking: runtimeLock must be read- or write-locked by the caller. **********************************************************************/ -static Protocol *getProtocol(const char *name) +static NEVER_INLINE Protocol *getProtocol(const char *name) { runtimeLock.assertLocked(); @@ -1709,7 +2219,16 @@ static void removeSubclass(Class supercls, Class subcls) if (char *swName = copySwiftV1MangledName(name, true/*isProtocol*/)) { result = (Protocol *)NXMapGet(protocols(), swName); free(swName); - return result; + if (result) return result; + } + + // Try table from dyld shared cache + // Temporarily check that we are using the new table. Eventually this check + // will always be true. + // FIXME: Remove this check when we can + if (sharedCacheSupportsProtocolRoots()) { + result = getPreoptimizedProtocol(name); + if (result) return result; } return nil; @@ -1722,10 +2241,15 @@ static void removeSubclass(Class supercls, Class subcls) * a protocol struct that has been reallocated. * Locking: runtimeLock must be read- or write-locked by the caller **********************************************************************/ -static protocol_t *remapProtocol(protocol_ref_t proto) +static ALWAYS_INLINE protocol_t *remapProtocol(protocol_ref_t proto) { runtimeLock.assertLocked(); + // Protocols in shared cache images have a canonical bit to mark that they + // are the definition we should use + if (((protocol_t *)proto)->isCanonical()) + return (protocol_t *)proto; + protocol_t *newproto = (protocol_t *) getProtocol(((protocol_t *)proto)->mangledName); return newproto ? newproto : (protocol_t *)proto; @@ -1762,7 +2286,7 @@ static void moveIvars(class_ro_t *ro, uint32_t superSize) uint32_t diff; - assert(superSize > ro->instanceStart); + ASSERT(superSize > ro->instanceStart); diff = superSize - ro->instanceStart; if (ro->ivars) { @@ -1805,8 +2329,8 @@ static void reconcileInstanceVariables(Class cls, Class supercls, const class_ro { class_rw_t *rw = cls->data(); - assert(supercls); - assert(!cls->isMetaClass()); + ASSERT(supercls); + ASSERT(!cls->isMetaClass()); /* debug: print them all before sliding if (ro->ivars) { @@ -1909,7 +2433,7 @@ static void reconcileInstanceVariables(Class cls, Class supercls, const class_ro * Returns the real class structure for the class. * Locking: runtimeLock must be write-locked by the caller **********************************************************************/ -static Class realizeClassWithoutSwift(Class cls) +static Class realizeClassWithoutSwift(Class cls, Class previously) { runtimeLock.assertLocked(); @@ -1921,7 +2445,7 @@ static Class realizeClassWithoutSwift(Class cls) if (!cls) return nil; if (cls->isRealized()) return cls; - assert(cls == remapClass(cls)); + ASSERT(cls == remapClass(cls)); // fixme verify class is not in an un-dlopened part of the shared cache? @@ -1940,7 +2464,9 @@ static Class realizeClassWithoutSwift(Class cls) } isMeta = ro->flags & RO_META; - +#if FAST_CACHE_META + if (isMeta) cls->cache.setBit(FAST_CACHE_META); +#endif rw->version = isMeta ? 7 : 0; // old runtime went up to 6 @@ -1963,40 +2489,45 @@ static Class realizeClassWithoutSwift(Class cls) // or that Swift's initializers have already been called. // fixme that assumption will be wrong if we add support // for ObjC subclasses of Swift classes. - supercls = realizeClassWithoutSwift(remapClass(cls->superclass)); - metacls = realizeClassWithoutSwift(remapClass(cls->ISA())); + supercls = realizeClassWithoutSwift(remapClass(cls->superclass), nil); + metacls = realizeClassWithoutSwift(remapClass(cls->ISA()), nil); #if SUPPORT_NONPOINTER_ISA - // Disable non-pointer isa for some classes and/or platforms. - // Set instancesRequireRawIsa. - bool instancesRequireRawIsa = cls->instancesRequireRawIsa(); - bool rawIsaIsInherited = false; - static bool hackedDispatch = false; - - if (DisableNonpointerIsa) { - // Non-pointer isa disabled by environment or app SDK version - instancesRequireRawIsa = true; - } - else if (!hackedDispatch && !(ro->flags & RO_META) && - 0 == strcmp(ro->name, "OS_object")) - { - // hack for libdispatch et al - isa also acts as vtable pointer - hackedDispatch = true; - instancesRequireRawIsa = true; - } - else if (supercls && supercls->superclass && - supercls->instancesRequireRawIsa()) - { - // This is also propagated by addSubclass() - // but nonpointer isa setup needs it earlier. - // Special case: instancesRequireRawIsa does not propagate - // from root class to root metaclass - instancesRequireRawIsa = true; - rawIsaIsInherited = true; - } - - if (instancesRequireRawIsa) { - cls->setInstancesRequireRawIsa(rawIsaIsInherited); + if (isMeta) { + // Metaclasses do not need any features from non pointer ISA + // This allows for a faspath for classes in objc_retain/objc_release. + cls->setInstancesRequireRawIsa(); + } else { + // Disable non-pointer isa for some classes and/or platforms. + // Set instancesRequireRawIsa. + bool instancesRequireRawIsa = cls->instancesRequireRawIsa(); + bool rawIsaIsInherited = false; + static bool hackedDispatch = false; + + if (DisableNonpointerIsa) { + // Non-pointer isa disabled by environment or app SDK version + instancesRequireRawIsa = true; + } + else if (!hackedDispatch && 0 == strcmp(ro->name, "OS_object")) + { + // hack for libdispatch et al - isa also acts as vtable pointer + hackedDispatch = true; + instancesRequireRawIsa = true; + } + else if (supercls && supercls->superclass && + supercls->instancesRequireRawIsa()) + { + // This is also propagated by addSubclass() + // but nonpointer isa setup needs it earlier. + // Special case: instancesRequireRawIsa does not propagate + // from root class to root metaclass + instancesRequireRawIsa = true; + rawIsaIsInherited = true; + } + + if (instancesRequireRawIsa) { + cls->setInstancesRequireRawIsaRecursively(rawIsaIsInherited); + } } // SUPPORT_NONPOINTER_ISA #endif @@ -2036,7 +2567,7 @@ static Class realizeClassWithoutSwift(Class cls) } // Attach categories - methodizeClass(cls); + methodizeClass(cls, previously); return cls; } @@ -2064,11 +2595,11 @@ Class _objc_realizeClassFromSwift(Class cls, void *previously) if (cls) { if (previously && previously != (void*)cls) { // #3: relocation - // In the future this will mean remapping the old address - // to the new class, and installing dispatch forwarding - // machinery at the old address - _objc_fatal("Swift requested that class %p be reallocated, " - "but libobjc does not support that.", previously); + mutex_locker_t lock(runtimeLock); + addRemappedClass((Class)previously, cls); + addClassTableEntry(cls); + addNamedClass(cls, cls->mangledName(), /*replacing*/nil); + return realizeClassWithoutSwift(cls, (Class)previously); } else { // #1 and #2: realization in place, or new class mutex_locker_t lock(runtimeLock); @@ -2081,7 +2612,7 @@ Class _objc_realizeClassFromSwift(Class cls, void *previously) // #1 and #2: realization in place, or new class // We ignore the Swift metadata initializer callback. // We assume that's all handled since we're being called from Swift. - return realizeClassWithoutSwift(cls); + return realizeClassWithoutSwift(cls, nil); } } else { @@ -2121,10 +2652,10 @@ static Class realizeSwiftClass(Class cls) #if DEBUG runtimeLock.lock(); - assert(remapClass(cls) == cls); - assert(cls->isSwiftStable_ButAllowLegacyForNow()); - assert(!cls->isMetaClassMaybeUnrealized()); - assert(cls->superclass); + ASSERT(remapClass(cls) == cls); + ASSERT(cls->isSwiftStable_ButAllowLegacyForNow()); + ASSERT(!cls->isMetaClassMaybeUnrealized()); + ASSERT(cls->superclass); runtimeLock.unlock(); #endif @@ -2146,9 +2677,8 @@ static Class realizeSwiftClass(Class cls) // fixme someday Swift will need to relocate classes at this point, // but we don't accept that yet. if (cls != newcls) { - _objc_fatal("Swift metadata initializer moved a class " - "from %p to %p, but libobjc does not yet allow that.", - cls, newcls); + mutex_locker_t lock(runtimeLock); + addRemappedClass(cls, newcls); } return newcls; @@ -2157,7 +2687,7 @@ static Class realizeSwiftClass(Class cls) // No Swift-side initialization callback. // Perform our own realization directly. mutex_locker_t lock(runtimeLock); - return realizeClassWithoutSwift(cls); + return realizeClassWithoutSwift(cls, nil); } } @@ -2181,14 +2711,14 @@ static Class realizeSwiftClass(Class cls) if (!cls->isSwiftStable_ButAllowLegacyForNow()) { // Non-Swift class. Realize it now with the lock still held. // fixme wrong in the future for objc subclasses of swift classes - realizeClassWithoutSwift(cls); + realizeClassWithoutSwift(cls, nil); if (!leaveLocked) lock.unlock(); } else { // Swift class. We need to drop locks and call the Swift // runtime to initialize it. lock.unlock(); cls = realizeSwiftClass(cls); - assert(cls->isRealized()); // callback must have provoked realization + ASSERT(cls->isRealized()); // callback must have provoked realization if (leaveLocked) lock.lock(); } @@ -2215,7 +2745,7 @@ static Class realizeSwiftClass(Class cls) static bool missingWeakSuperclass(Class cls) { - assert(!cls->isRealized()); + ASSERT(!cls->isRealized()); if (!cls->superclass) { // superclass nil. This is normal for root classes only. @@ -2223,8 +2753,8 @@ static Class realizeSwiftClass(Class cls) } else { // superclass not nil. Check if a higher superclass is missing. Class supercls = remapClass(cls->superclass); - assert(cls != cls->superclass); - assert(cls != supercls); + ASSERT(cls != cls->superclass); + ASSERT(cls != supercls); if (!supercls) return YES; if (supercls->isRealized()) return NO; return missingWeakSuperclass(supercls); @@ -2243,7 +2773,7 @@ static void realizeAllClassesInImage(header_info *hi) runtimeLock.assertLocked(); size_t count, i; - classref_t *classlist; + classref_t const *classlist; if (hi->areAllClassesRealized()) return; @@ -2353,17 +2883,20 @@ BOOL _class_isFutureClass(Class cls) static void flushCaches(Class cls) { runtimeLock.assertLocked(); - +#if CONFIG_USE_CACHE_LOCK mutex_locker_t lock(cacheUpdateLock); +#endif if (cls) { - foreach_realized_class_and_subclass(cls, ^(Class c){ + foreach_realized_class_and_subclass(cls, [](Class c){ cache_erase_nolock(c); + return true; }); } else { - foreach_realized_class_and_metaclass(^(Class c){ + foreach_realized_class_and_metaclass([](Class c){ cache_erase_nolock(c); + return true; }); } } @@ -2384,7 +2917,11 @@ void _objc_flush_caches(Class cls) if (!cls) { // collectALot if cls==nil +#if CONFIG_USE_CACHE_LOCK mutex_locker_t lock(cacheUpdateLock); +#else + mutex_locker_t lock(runtimeLock); +#endif cache_collect(true); } } @@ -2449,18 +2986,16 @@ void _objc_flush_caches(Class cls) } - - /*********************************************************************** * mustReadClasses * Preflight check in advance of readClass() from an image. **********************************************************************/ -bool mustReadClasses(header_info *hi) +bool mustReadClasses(header_info *hi, bool hasDyldRoots) { const char *reason; // If the image is not preoptimized then we must read classes. - if (!hi->isPreoptimized()) { + if (!hi->hasPreoptimizedClasses()) { reason = nil; // Don't log this one because it is noisy. goto readthem; } @@ -2471,10 +3006,10 @@ bool mustReadClasses(header_info *hi) goto readthem; #endif - assert(!hi->isBundle()); // no MH_BUNDLE in shared cache + ASSERT(!hi->isBundle()); // no MH_BUNDLE in shared cache // If the image may have missing weak superclasses then we must read classes - if (!noMissingWeakSuperclasses()) { + if (!noMissingWeakSuperclasses() || hasDyldRoots) { reason = "the image may contain classes with missing weak superclasses"; goto readthem; } @@ -2491,10 +3026,10 @@ bool mustReadClasses(header_info *hi) #if DEBUG { size_t count; - classref_t *classlist = _getObjc2ClassList(hi, &count); + classref_t const *classlist = _getObjc2ClassList(hi, &count); for (size_t i = 0; i < count; i++) { Class cls = remapClass(classlist[i]); - assert(!cls->isUnfixedBackwardDeployingStableSwift()); + ASSERT(!cls->isUnfixedBackwardDeployingStableSwift()); } } #endif @@ -2541,22 +3076,6 @@ Class readClass(Class cls, bool headerIsBundle, bool headerIsPreoptimized) return nil; } - // Note: Class __ARCLite__'s hack does not go through here. - // Class structure fixups that apply to it also need to be - // performed in non-lazy realization below. - - // These fields should be set to zero because of the - // binding of _objc_empty_vtable, but OS X 10.8's dyld - // does not bind shared cache absolute symbols as expected. - // This (and the __ARCLite__ hack below) can be removed - // once the simulator drops 10.8 support. -#if TARGET_OS_SIMULATOR - if (cls->cache._mask) cls->cache._mask = 0; - if (cls->cache._occupied) cls->cache._occupied = 0; - if (cls->ISA()->cache._mask) cls->ISA()->cache._mask = 0; - if (cls->ISA()->cache._occupied) cls->ISA()->cache._occupied = 0; -#endif - cls->fixupBackwardDeployingStableSwift(); Class replacing = nil; @@ -2588,8 +3107,8 @@ Class readClass(Class cls, bool headerIsBundle, bool headerIsPreoptimized) if (headerIsPreoptimized && !replacing) { // class list built in shared cache // fixme strict assert doesn't work because of duplicates - // assert(cls == getClass(name)); - assert(getClassExceptSomeSwift(mangledName)); + // ASSERT(cls == getClass(name)); + ASSERT(getClassExceptSomeSwift(mangledName)); } else { addNamedClass(cls, mangledName, replacing); addClassTableEntry(cls); @@ -2621,11 +3140,29 @@ Class readClass(Class cls, bool headerIsBundle, bool headerIsPreoptimized) protocol_t *oldproto = (protocol_t *)getProtocol(newproto->mangledName); if (oldproto) { - // Some other definition already won. - if (PrintProtocols) { - _objc_inform("PROTOCOLS: protocol at %p is %s " - "(duplicate of %p)", - newproto, oldproto->nameForLogging(), oldproto); + if (oldproto != newproto) { + // Some other definition already won. + if (PrintProtocols) { + _objc_inform("PROTOCOLS: protocol at %p is %s " + "(duplicate of %p)", + newproto, oldproto->nameForLogging(), oldproto); + } + + // If we are a shared cache binary then we have a definition of this + // protocol, but if another one was chosen then we need to clear our + // isCanonical bit so that no-one trusts it. + // Note, if getProtocol returned a shared cache protocol then the + // canonical definition is already in the shared cache and we don't + // need to do anything. + if (headerIsPreoptimized && !oldproto->isCanonical()) { + // Note newproto is an entry in our __objc_protolist section which + // for shared cache binaries points to the original protocol in + // that binary, not the shared cache uniqued one. + auto cacheproto = (protocol_t *) + getSharedCachePreoptimizedProtocol(newproto->mangledName); + if (cacheproto && cacheproto->isCanonical()) + cacheproto->clearIsCanonical(); + } } } else if (headerIsPreoptimized) { @@ -2646,8 +3183,8 @@ Class readClass(Class cls, bool headerIsBundle, bool headerIsPreoptimized) installedproto = newproto; } - assert(installedproto->getIsa() == protocol_class); - assert(installedproto->size >= sizeof(protocol_t)); + ASSERT(installedproto->getIsa() == protocol_class); + ASSERT(installedproto->size >= sizeof(protocol_t)); insertFn(protocol_map, installedproto->mangledName, installedproto); @@ -2713,6 +3250,7 @@ void _read_images(header_info **hList, uint32_t hCount, int totalClasses, int un Class *resolvedFutureClasses = nil; size_t resolvedFutureClassCount = 0; static bool doneOnce; + bool launchTime = NO; TimeLogger ts(PrintImageTimes); runtimeLock.assertLocked(); @@ -2724,6 +3262,7 @@ void _read_images(header_info **hList, uint32_t hCount, int totalClasses, int un if (!doneOnce) { doneOnce = YES; + launchTime = YES; #if SUPPORT_NONPOINTER_ISA // Disable non-pointer isa under some conditions. @@ -2792,25 +3331,45 @@ void _read_images(header_info **hList, uint32_t hCount, int totalClasses, int un (isPreoptimized() ? unoptimizedTotalClasses : totalClasses) * 4 / 3; gdb_objc_realized_classes = NXCreateMapTable(NXStrValueMapPrototype, namedClassesSize); - - allocatedClasses = NXCreateHashTable(NXPtrPrototype, 0, nil); - + ts.log("IMAGE TIMES: first time tasks"); } + // Fix up @selector references + static size_t UnfixedSelectors; + { + mutex_locker_t lock(selLock); + for (EACH_HEADER) { + if (hi->hasPreoptimizedSelectors()) continue; + + bool isBundle = hi->isBundle(); + SEL *sels = _getObjc2SelectorRefs(hi, &count); + UnfixedSelectors += count; + for (i = 0; i < count; i++) { + const char *name = sel_cname(sels[i]); + SEL sel = sel_registerNameNoLock(name, isBundle); + if (sels[i] != sel) { + sels[i] = sel; + } + } + } + } + + ts.log("IMAGE TIMES: fix up selector references"); // Discover classes. Fix up unresolved future classes. Mark bundle classes. + bool hasDyldRoots = dyld_shared_cache_some_image_overridden(); for (EACH_HEADER) { - classref_t *classlist = _getObjc2ClassList(hi, &count); - - if (! mustReadClasses(hi)) { + if (! mustReadClasses(hi, hasDyldRoots)) { // Image is sufficiently optimized that we need not call readClass() continue; } + classref_t const *classlist = _getObjc2ClassList(hi, &count); + bool headerIsBundle = hi->isBundle(); - bool headerIsPreoptimized = hi->isPreoptimized(); + bool headerIsPreoptimized = hi->hasPreoptimizedClasses(); for (i = 0; i < count; i++) { Class cls = (Class)classlist[i]; @@ -2850,25 +3409,6 @@ void _read_images(header_info **hList, uint32_t hCount, int totalClasses, int un ts.log("IMAGE TIMES: remap classes"); - // Fix up @selector references - static size_t UnfixedSelectors; - { - mutex_locker_t lock(selLock); - for (EACH_HEADER) { - if (hi->isPreoptimized()) continue; - - bool isBundle = hi->isBundle(); - SEL *sels = _getObjc2SelectorRefs(hi, &count); - UnfixedSelectors += count; - for (i = 0; i < count; i++) { - const char *name = sel_cname(sels[i]); - sels[i] = sel_registerNameNoLock(name, isBundle); - } - } - } - - ts.log("IMAGE TIMES: fix up selector references"); - #if SUPPORT_FIXUP // Fix up old objc_msgSend_fixup call sites for (EACH_HEADER) { @@ -2887,16 +3427,33 @@ void _read_images(header_info **hList, uint32_t hCount, int totalClasses, int un ts.log("IMAGE TIMES: fix up objc_msgSend_fixup"); #endif + bool cacheSupportsProtocolRoots = sharedCacheSupportsProtocolRoots(); + // Discover protocols. Fix up protocol refs. for (EACH_HEADER) { extern objc_class OBJC_CLASS_$_Protocol; Class cls = (Class)&OBJC_CLASS_$_Protocol; - assert(cls); + ASSERT(cls); NXMapTable *protocol_map = protocols(); - bool isPreoptimized = hi->isPreoptimized(); + bool isPreoptimized = hi->hasPreoptimizedProtocols(); + + // Skip reading protocols if this is an image from the shared cache + // and we support roots + // Note, after launch we do need to walk the protocol as the protocol + // in the shared cache is marked with isCanonical() and that may not + // be true if some non-shared cache binary was chosen as the canonical + // definition + if (launchTime && isPreoptimized && cacheSupportsProtocolRoots) { + if (PrintProtocols) { + _objc_inform("PROTOCOLS: Skipping reading protocols in image: %s", + hi->fname()); + } + continue; + } + bool isBundle = hi->isBundle(); - protocol_t **protolist = _getObjc2ProtocolList(hi, &count); + protocol_t * const *protolist = _getObjc2ProtocolList(hi, &count); for (i = 0; i < count; i++) { readProtocol(protolist[i], cls, protocol_map, isPreoptimized, isBundle); @@ -2909,6 +3466,12 @@ void _read_images(header_info **hList, uint32_t hCount, int totalClasses, int un // Preoptimized images may have the right // answer already but we don't know for sure. for (EACH_HEADER) { + // At launch time, we know preoptimized image refs are pointing at the + // shared cache definition of a protocol. We can skip the check on + // launch, but have to visit @protocol refs for shared cache images + // loaded later. + if (launchTime && cacheSupportsProtocolRoots && hi->isPreoptimized()) + continue; protocol_t **protolist = _getObjc2ProtocolRefs(hi, &count); for (i = 0; i < count; i++) { remapProtocolRef(&protolist[i]); @@ -2917,30 +3480,90 @@ void _read_images(header_info **hList, uint32_t hCount, int totalClasses, int un ts.log("IMAGE TIMES: fix up @protocol references"); + // Discover categories. + for (EACH_HEADER) { + bool hasClassProperties = hi->info()->hasCategoryClassProperties(); + + auto processCatlist = [&](category_t * const *catlist) { + for (i = 0; i < count; i++) { + category_t *cat = catlist[i]; + Class cls = remapClass(cat->cls); + locstamped_category_t lc{cat, hi}; + + if (!cls) { + // Category's target class is missing (probably weak-linked). + // Ignore the category. + if (PrintConnecting) { + _objc_inform("CLASS: IGNORING category \?\?\?(%s) %p with " + "missing weak-linked target class", + cat->name, cat); + } + continue; + } + + // Process this category. + if (cls->isStubClass()) { + // Stub classes are never realized. Stub classes + // don't know their metaclass until they're + // initialized, so we have to add categories with + // class methods or properties to the stub itself. + // methodizeClass() will find them and add them to + // the metaclass as appropriate. + if (cat->instanceMethods || + cat->protocols || + cat->instanceProperties || + cat->classMethods || + cat->protocols || + (hasClassProperties && cat->_classProperties)) + { + objc::unattachedCategories.addForClass(lc, cls); + } + } else { + // First, register the category with its target class. + // Then, rebuild the class's method lists (etc) if + // the class is realized. + if (cat->instanceMethods || cat->protocols + || cat->instanceProperties) + { + if (cls->isRealized()) { + attachCategories(cls, &lc, 1, ATTACH_EXISTING); + } else { + objc::unattachedCategories.addForClass(lc, cls); + } + } + + if (cat->classMethods || cat->protocols + || (hasClassProperties && cat->_classProperties)) + { + if (cls->ISA()->isRealized()) { + attachCategories(cls->ISA(), &lc, 1, ATTACH_EXISTING | ATTACH_METACLASS); + } else { + objc::unattachedCategories.addForClass(lc, cls->ISA()); + } + } + } + } + }; + processCatlist(_getObjc2CategoryList(hi, &count)); + processCatlist(_getObjc2CategoryList2(hi, &count)); + } + + ts.log("IMAGE TIMES: discover categories"); + + // Category discovery MUST BE Late to avoid potential races + // when other threads call the new category code before + // this thread finishes its fixups. + + // +load handled by prepare_load_methods() + // Realize non-lazy classes (for +load methods and static instances) for (EACH_HEADER) { - classref_t *classlist = + classref_t const *classlist = _getObjc2NonlazyClassList(hi, &count); for (i = 0; i < count; i++) { Class cls = remapClass(classlist[i]); if (!cls) continue; - // hack for class __ARCLite__, which didn't get this above -#if TARGET_OS_SIMULATOR - if (cls->cache._buckets == (void*)&_objc_empty_cache && - (cls->cache._mask || cls->cache._occupied)) - { - cls->cache._mask = 0; - cls->cache._occupied = 0; - } - if (cls->ISA()->cache._buckets == (void*)&_objc_empty_cache && - (cls->ISA()->cache._mask || cls->ISA()->cache._occupied)) - { - cls->ISA()->cache._mask = 0; - cls->ISA()->cache._occupied = 0; - } -#endif - addClassTableEntry(cls); if (cls->isSwiftStable()) { @@ -2953,7 +3576,7 @@ void _read_images(header_info **hList, uint32_t hCount, int totalClasses, int un // We can't disallow all Swift classes because of // classes like Swift.__EmptyArrayStorage } - realizeClassWithoutSwift(cls); + realizeClassWithoutSwift(cls, nil); } } @@ -2966,78 +3589,13 @@ void _read_images(header_info **hList, uint32_t hCount, int totalClasses, int un if (cls->isSwiftStable()) { _objc_fatal("Swift class is not allowed to be future"); } - realizeClassWithoutSwift(cls); - cls->setInstancesRequireRawIsa(false/*inherited*/); + realizeClassWithoutSwift(cls, nil); + cls->setInstancesRequireRawIsaRecursively(false/*inherited*/); } free(resolvedFutureClasses); - } - - ts.log("IMAGE TIMES: realize future classes"); - - // Discover categories. - for (EACH_HEADER) { - category_t **catlist = - _getObjc2CategoryList(hi, &count); - bool hasClassProperties = hi->info()->hasCategoryClassProperties(); - - for (i = 0; i < count; i++) { - category_t *cat = catlist[i]; - Class cls = remapClass(cat->cls); - - if (!cls) { - // Category's target class is missing (probably weak-linked). - // Disavow any knowledge of this category. - catlist[i] = nil; - if (PrintConnecting) { - _objc_inform("CLASS: IGNORING category \?\?\?(%s) %p with " - "missing weak-linked target class", - cat->name, cat); - } - continue; - } - - // Process this category. - // First, register the category with its target class. - // Then, rebuild the class's method lists (etc) if - // the class is realized. - bool classExists = NO; - if (cat->instanceMethods || cat->protocols - || cat->instanceProperties) - { - addUnattachedCategoryForClass(cat, cls, hi); - if (cls->isRealized()) { - remethodizeClass(cls); - classExists = YES; - } - if (PrintConnecting) { - _objc_inform("CLASS: found category -%s(%s) %s", - cls->nameForLogging(), cat->name, - classExists ? "on existing class" : ""); - } - } - - if (cat->classMethods || cat->protocols - || (hasClassProperties && cat->_classProperties)) - { - addUnattachedCategoryForClass(cat, cls->ISA(), hi); - if (cls->ISA()->isRealized()) { - remethodizeClass(cls->ISA()); - } - if (PrintConnecting) { - _objc_inform("CLASS: found category +%s(%s)", - cls->nameForLogging(), cat->name); - } - } - } } - ts.log("IMAGE TIMES: discover categories"); - - // Category discovery MUST BE LAST to avoid potential races - // when other threads call the new category code before - // this thread finishes its fixups. - - // +load handled by prepare_load_methods() + ts.log("IMAGE TIMES: realize future classes"); if (DebugNonFragileIvars) { realizeAllClasses(); @@ -3052,7 +3610,7 @@ void _read_images(header_info **hList, uint32_t hCount, int totalClasses, int un static unsigned int PreoptOptimizedClasses; for (EACH_HEADER) { - if (hi->isPreoptimized()) { + if (hi->hasPreoptimizedSelectors()) { _objc_inform("PREOPTIMIZATION: honoring preoptimized selectors " "in %s", hi->fname()); } @@ -3061,13 +3619,13 @@ void _read_images(header_info **hList, uint32_t hCount, int totalClasses, int un "in %s", hi->fname()); } - classref_t *classlist = _getObjc2ClassList(hi, &count); + classref_t const *classlist = _getObjc2ClassList(hi, &count); for (i = 0; i < count; i++) { Class cls = remapClass(classlist[i]); if (!cls) continue; PreoptTotalClasses++; - if (hi->isPreoptimized()) { + if (hi->hasPreoptimizedClasses()) { PreoptOptimizedClasses++; } @@ -3117,7 +3675,7 @@ void _read_images(header_info **hList, uint32_t hCount, int totalClasses, int un static void schedule_class_load(Class cls) { if (!cls) return; - assert(cls->isRealized()); // _read_images should realize + ASSERT(cls->isRealized()); // _read_images should realize if (cls->data()->flags & RW_LOADED) return; @@ -3143,13 +3701,13 @@ void prepare_load_methods(const headerType *mhdr) runtimeLock.assertLocked(); - classref_t *classlist = + classref_t const *classlist = _getObjc2NonlazyClassList(mhdr, &count); for (i = 0; i < count; i++) { schedule_class_load(remapClass(classlist[i])); } - category_t **categorylist = _getObjc2NonlazyCategoryList(mhdr, &count); + category_t * const *categorylist = _getObjc2NonlazyCategoryList(mhdr, &count); for (i = 0; i < count; i++) { category_t *cat = categorylist[i]; Class cls = remapClass(cat->cls); @@ -3158,8 +3716,8 @@ void prepare_load_methods(const headerType *mhdr) _objc_fatal("Swift class extensions and categories on Swift " "classes are not allowed to have +load methods"); } - realizeClassWithoutSwift(cls); - assert(cls->ISA()->isRealized()); + realizeClassWithoutSwift(cls, nil); + ASSERT(cls->ISA()->isRealized()); add_category_to_loadable_list(cat); } } @@ -3179,17 +3737,18 @@ void _unload_image(header_info *hi) // Unload unattached categories and categories waiting for +load. - category_t **catlist = _getObjc2CategoryList(hi, &count); + // Ignore __objc_catlist2. We don't support unloading Swift + // and we never will. + category_t * const *catlist = _getObjc2CategoryList(hi, &count); for (i = 0; i < count; i++) { category_t *cat = catlist[i]; - if (!cat) continue; // category for ignored weak-linked class Class cls = remapClass(cat->cls); - assert(cls); // shouldn't have live category for dead class + if (!cls) continue; // category for ignored weak-linked class // fixme for MH_DYLIB cat's class may have been unloaded already // unattached list - removeUnattachedCategoryForClass(cat, cls); + objc::unattachedCategories.eraseCategoryForClass(cat, cls); // +load queue remove_category_from_loadable_list(cat); @@ -3201,41 +3760,34 @@ void _unload_image(header_info *hi) // and __DATA,__objc_nlclslist. arclite's hack puts a class in the latter // only, and we need to unload that class if we unload an arclite image. - NXHashTable *classes = NXCreateHashTable(NXPtrPrototype, 0, nil); - classref_t *classlist; + objc::DenseSet classes{}; + classref_t const *classlist; classlist = _getObjc2ClassList(hi, &count); for (i = 0; i < count; i++) { Class cls = remapClass(classlist[i]); - if (cls) NXHashInsert(classes, cls); + if (cls) classes.insert(cls); } classlist = _getObjc2NonlazyClassList(hi, &count); for (i = 0; i < count; i++) { Class cls = remapClass(classlist[i]); - if (cls) NXHashInsert(classes, cls); + if (cls) classes.insert(cls); } // First detach classes from each other. Then free each class. // This avoid bugs where this loop unloads a subclass before its superclass - NXHashState hs; - Class cls; - - hs = NXInitHashState(classes); - while (NXNextHashState(classes, &hs, (void**)&cls)) { + for (Class cls: classes) { remove_class_from_loadable_list(cls); detach_class(cls->ISA(), YES); detach_class(cls, NO); } - hs = NXInitHashState(classes); - while (NXNextHashState(classes, &hs, (void**)&cls)) { + for (Class cls: classes) { free_class(cls->ISA()); free_class(cls); } - NXFreeHashTable(classes); - // XXX FIXME -- Clean up protocols: // Support unloading protocols at dylib/image unload time @@ -3275,7 +3827,7 @@ void _unload_image(header_info *hi) { if (!m) return nil; - assert(m->name == sel_registerName(sel_getName(m->name))); + ASSERT(m->name == sel_registerName(sel_getName(m->name))); return m->name; } @@ -3316,7 +3868,7 @@ void _unload_image(header_info *hi) flushCaches(cls); - updateCustomRR_AWZ(cls, m); + adjustCustomFlagsForMethodChange(cls, m); return old; } @@ -3348,8 +3900,8 @@ void method_exchangeImplementations(Method m1, Method m2) flushCaches(nil); - updateCustomRR_AWZ(nil, m1); - updateCustomRR_AWZ(nil, m2); + adjustCustomFlagsForMethodChange(nil, m1); + adjustCustomFlagsForMethodChange(nil, m2); } @@ -3558,7 +4110,7 @@ static uint32_t getExtendedTypesIndexForMethod(protocol_t *proto, const method_t fixupProtocolIfNeeded(protocol_t *proto) { runtimeLock.assertUnlocked(); - assert(proto); + ASSERT(proto); if (!proto->isFixedUp()) { mutex_locker_t lock(runtimeLock); @@ -3602,7 +4154,7 @@ static uint32_t getExtendedTypesIndexForMethod(protocol_t *proto, const method_t if (!proto || !sel) return nil; - assert(proto->isFixedUp()); + ASSERT(proto->isFixedUp()); method_list_t *mlist = getProtocolMethodList(proto, isRequiredMethod, isInstanceMethod); @@ -3659,7 +4211,7 @@ static uint32_t getExtendedTypesIndexForMethod(protocol_t *proto, const method_t if (!proto) return nil; if (!proto->extendedMethodTypes()) return nil; - assert(proto->isFixedUp()); + ASSERT(proto->isFixedUp()); method_t *m = protocol_getMethod_nolock(proto, sel, @@ -3713,7 +4265,7 @@ static uint32_t getExtendedTypesIndexForMethod(protocol_t *proto, const method_t const char * protocol_t::demangledName() { - assert(hasDemangledNameField()); + ASSERT(hasDemangledNameField()); if (! _demangledName) { char *de = copySwiftV1DemangledName(mangledName, true/*isProtocol*/); @@ -3780,6 +4332,9 @@ static uint32_t getExtendedTypesIndexForMethod(protocol_t *proto, const method_t uintptr_t i; for (i = 0; i < self->protocols->count; i++) { protocol_t *proto = remapProtocol(self->protocols->list[i]); + if (other == proto) { + return YES; + } if (0 == strcmp(other->mangledName, proto->mangledName)) { return YES; } @@ -4074,7 +4629,11 @@ void objc_registerProtocol(Protocol *proto_gen) // have been retained and we must preserve that count. proto->changeIsa(cls); - NXMapKeyCopyingInsert(protocols(), proto->mangledName, proto); + // Don't add this protocol if we already have it. + // Should we warn on duplicates? + if (getProtocol(proto->mangledName) == nil) { + NXMapKeyCopyingInsert(protocols(), proto->mangledName, proto); + } } @@ -4240,35 +4799,105 @@ void objc_registerProtocol(Protocol *proto_gen) //} } +static int +objc_getRealizedClassList_nolock(Class *buffer, int bufferLen) +{ + int count = 0; + + if (buffer) { + int c = 0; + foreach_realized_class([=, &count, &c](Class cls) { + count++; + if (c < bufferLen) { + buffer[c++] = cls; + } + return true; + }); + } else { + foreach_realized_class([&count](Class cls) { + count++; + return true; + }); + } + + return count; +} + +static Class * +objc_copyRealizedClassList_nolock(unsigned int *outCount) +{ + Class *result = nil; + unsigned int count = 0; + + foreach_realized_class([&count](Class cls) { + count++; + return true; + }); + + if (count > 0) { + unsigned int c = 0; + + result = (Class *)malloc((1+count) * sizeof(Class)); + foreach_realized_class([=, &c](Class cls) { + result[c++] = cls; + return true; + }); + result[c] = nil; + } + + if (outCount) *outCount = count; + return result; +} + +static void +class_getImpCache_nolock(Class cls, cache_t &cache, objc_imp_cache_entry *buffer, int len) +{ + bucket_t *buckets = cache.buckets(); + + uintptr_t count = cache.capacity(); + uintptr_t index; + int wpos = 0; + + for (index = 0; index < count && wpos < len; index += 1) { + if (buckets[index].sel()) { + buffer[wpos].imp = buckets[index].imp(cls); + buffer[wpos].sel = buckets[index].sel(); + wpos++; + } + } +} /*********************************************************************** -* objc_getClassList -* Returns pointers to all classes. -* This requires all classes be realized, which is regretfully non-lazy. -* Locking: acquires runtimeLock -**********************************************************************/ -int -objc_getClassList(Class *buffer, int bufferLen) + * objc_getClassList + * Returns pointers to all classes. + * This requires all classes be realized, which is regretfully non-lazy. + * Locking: acquires runtimeLock + **********************************************************************/ +int +objc_getClassList(Class *buffer, int bufferLen) { mutex_locker_t lock(runtimeLock); realizeAllClasses(); - __block int count = 0; - foreach_realized_class_and_metaclass(^(Class cls) { - if (!cls->isMetaClass()) count++; - }); + return objc_getRealizedClassList_nolock(buffer, bufferLen); +} - if (buffer) { - __block int c = 0; - foreach_realized_class_and_metaclass(^(Class cls) { - if (c < bufferLen && !cls->isMetaClass()) { - buffer[c++] = cls; - } - }); - } +/*********************************************************************** + * objc_copyClassList + * Returns pointers to Realized classes. + * + * outCount may be nil. *outCount is the number of classes returned. + * If the returned array is not nil, it is nil-terminated and must be + * freed with free(). + * Locking: write-locks runtimeLock + **********************************************************************/ +Class * +objc_copyRealizedClassList(unsigned int *outCount) +{ + mutex_locker_t lock(runtimeLock); - return count; + return objc_copyRealizedClassList_nolock(outCount); } @@ -4289,26 +4918,39 @@ void objc_registerProtocol(Protocol *proto_gen) realizeAllClasses(); - Class *result = nil; + return objc_copyRealizedClassList_nolock(outCount); +} - __block unsigned int count = 0; - foreach_realized_class_and_metaclass(^(Class cls) { - if (!cls->isMetaClass()) count++; - }); +/*********************************************************************** + * class_copyImpCache + * Returns the current content of the Class IMP Cache + * + * outCount may be nil. *outCount is the number of entries returned. + * If the returned array is not nil, it is nil-terminated and must be + * freed with free(). + * Locking: write-locks cacheUpdateLock + **********************************************************************/ +objc_imp_cache_entry * +class_copyImpCache(Class cls, int *outCount) +{ + objc_imp_cache_entry *buffer = nullptr; - if (count > 0) { - result = (Class *)malloc((1+count) * sizeof(Class)); - __block unsigned int c = 0; - foreach_realized_class_and_metaclass(^(Class cls) { - if (!cls->isMetaClass()) { - result[c++] = cls; - } - }); - result[c] = nil; +#if CONFIG_USE_CACHE_LOCK + mutex_locker_t lock(cacheUpdateLock); +#else + mutex_locker_t lock(runtimeLock); +#endif + + cache_t &cache = cls->cache; + int count = (int)cache.occupied(); + + if (count) { + buffer = (objc_imp_cache_entry *)calloc(1+count, sizeof(objc_imp_cache_entry)); + class_getImpCache_nolock(cls, cache, buffer, count); } if (outCount) *outCount = count; - return result; + return buffer; } @@ -4324,7 +4966,36 @@ void objc_registerProtocol(Protocol *proto_gen) NXMapTable *protocol_map = protocols(); - unsigned int count = NXCountMapTable(protocol_map); + // Find all the protocols from the pre-optimized images. These protocols + // won't be in the protocol map. + objc::DenseMap preoptimizedProtocols; + if (sharedCacheSupportsProtocolRoots()) { + header_info *hi; + for (hi = FirstHeader; hi; hi = hi->getNext()) { + if (!hi->hasPreoptimizedProtocols()) + continue; + + size_t count, i; + const protocol_t * const *protolist = _getObjc2ProtocolList(hi, &count); + for (i = 0; i < count; i++) { + const protocol_t* protocol = protolist[i]; + + // Skip protocols we have in the run time map. These likely + // correspond to protocols added dynamically which have the same + // name as a protocol found later in a dlopen'ed shared cache image. + if (NXMapGet(protocol_map, protocol->mangledName) != nil) + continue; + + // The protocols in the shared cache protolist point to their + // original on-disk object, not the optimized one. We can use the name + // to find the optimized one. + Protocol* optimizedProto = getPreoptimizedProtocol(protocol->mangledName); + preoptimizedProtocols.insert({ protocol->mangledName, optimizedProto }); + } + } + } + + unsigned int count = NXCountMapTable(protocol_map) + (unsigned int)preoptimizedProtocols.size(); if (count == 0) { if (outCount) *outCount = 0; return nil; @@ -4341,9 +5012,14 @@ void objc_registerProtocol(Protocol *proto_gen) { result[i++] = proto; } + + // Add any protocols found in the pre-optimized table + for (auto it : preoptimizedProtocols) { + result[i++] = it.second; + } result[i++] = nil; - assert(i == count+1); + ASSERT(i == count+1); if (outCount) *outCount = count; return result; @@ -4380,7 +5056,7 @@ void objc_registerProtocol(Protocol *proto_gen) mutex_locker_t lock(runtimeLock); - assert(cls->isRealized()); + ASSERT(cls->isRealized()); count = cls->data()->methods.count(); @@ -4418,7 +5094,7 @@ void objc_registerProtocol(Protocol *proto_gen) mutex_locker_t lock(runtimeLock); - assert(cls->isRealized()); + ASSERT(cls->isRealized()); if ((ivars = cls->data()->ro->ivars) && ivars->count) { result = (Ivar *)malloc((ivars->count+1) * sizeof(Ivar)); @@ -4453,7 +5129,7 @@ void objc_registerProtocol(Protocol *proto_gen) mutex_locker_t lock(runtimeLock); checkIsKnownClass(cls); - assert(cls->isRealized()); + ASSERT(cls->isRealized()); auto rw = cls->data(); @@ -4487,10 +5163,10 @@ void objc_registerProtocol(Protocol *proto_gen) const method_list_t *mlist; - assert(isRealized()); - assert(ISA()->isRealized()); - assert(!isMetaClass()); - assert(ISA()->isMetaClass()); + ASSERT(isRealized()); + ASSERT(ISA()->isRealized()); + ASSERT(!isMetaClass()); + ASSERT(ISA()->isMetaClass()); mlist = ISA()->data()->ro->baseMethods(); if (mlist) { @@ -4544,7 +5220,7 @@ void objc_registerProtocol(Protocol *proto_gen) { mutex_locker_t lock(runtimeLock); Class result = remapClass(cat->cls); - assert(result->isRealized()); // ok for call_category_loads' usage + ASSERT(result->isRealized()); // ok for call_category_loads' usage return result; } @@ -4610,7 +5286,7 @@ void objc_registerProtocol(Protocol *proto_gen) checkIsKnownClass(cls); - assert(cls->isRealized()); + ASSERT(cls->isRealized()); count = cls->data()->protocols.count(); @@ -4638,7 +5314,12 @@ void objc_registerProtocol(Protocol *proto_gen) const char **objc_copyImageNames(unsigned int *outCount) { mutex_locker_t lock(runtimeLock); - + + int HeaderCount = 0; + for (header_info *hi = FirstHeader; hi != nil; hi = hi->getNext()) { + HeaderCount++; + } + #if TARGET_OS_WIN32 const TCHAR **names = (const TCHAR **) malloc((HeaderCount+1) * sizeof(TCHAR *)); @@ -4685,10 +5366,10 @@ void objc_registerProtocol(Protocol *proto_gen) copyClassNamesForImage_nolock(header_info *hi, unsigned int *outCount) { runtimeLock.assertLocked(); - assert(hi); + ASSERT(hi); size_t count; - classref_t *classlist = _getObjc2ClassList(hi, &count); + classref_t const *classlist = _getObjc2ClassList(hi, &count); const char **names = (const char **) malloc((count+1) * sizeof(const char *)); @@ -4835,7 +5516,7 @@ void objc_registerProtocol(Protocol *proto_gen) * Locking: runtimeLock may or may not be held by the caller. **********************************************************************/ mutex_t DemangleCacheLock; -static NXHashTable *DemangleCache; +static objc::DenseSet *DemangleCache; const char * objc_class::demangledName() { @@ -4877,15 +5558,15 @@ void objc_registerProtocol(Protocol *proto_gen) // fixme lldb's calls to class_getName() can also get here when // interrogating the dyld shared cache. (rdar://27258517) // fixme runtimeLock.assertLocked(); - // fixme assert(realize); + // fixme ASSERT(realize); - char *cached; + const char *cached; { mutex_locker_t lock(DemangleCacheLock); if (!DemangleCache) { - DemangleCache = NXCreateHashTable(NXStrPrototype, 0, nil); + DemangleCache = new objc::DenseSet{}; } - cached = (char *)NXHashInsertIfAbsent(DemangleCache, de); + cached = *DemangleCache->insert(de).first; } if (cached != de) free(de); return cached; @@ -4895,16 +5576,27 @@ void objc_registerProtocol(Protocol *proto_gen) /*********************************************************************** * class_getName * fixme -* Locking: acquires runtimeLock +* Locking: may acquire DemangleCacheLock **********************************************************************/ const char *class_getName(Class cls) { if (!cls) return "nil"; // fixme lldb calls class_getName() on unrealized classes (rdar://27258517) - // assert(cls->isRealized() || cls->isFuture()); + // ASSERT(cls->isRealized() || cls->isFuture()); return cls->demangledName(); } +/*********************************************************************** +* objc_debug_class_getNameRaw +* fixme +* Locking: none +**********************************************************************/ +const char *objc_debug_class_getNameRaw(Class cls) +{ + if (!cls) return "nil"; + return cls->mangledName(); +} + /*********************************************************************** * class_getVersion @@ -4915,7 +5607,7 @@ void objc_registerProtocol(Protocol *proto_gen) class_getVersion(Class cls) { if (!cls) return 0; - assert(cls->isRealized()); + ASSERT(cls->isRealized()); return cls->data()->version; } @@ -4929,14 +5621,17 @@ void objc_registerProtocol(Protocol *proto_gen) class_setVersion(Class cls, int version) { if (!cls) return; - assert(cls->isRealized()); + ASSERT(cls->isRealized()); cls->data()->version = version; } - -static method_t *findMethodInSortedMethodList(SEL key, const method_list_t *list) +/*********************************************************************** + * search_method_list_inline + **********************************************************************/ +ALWAYS_INLINE static method_t * +findMethodInSortedMethodList(SEL key, const method_list_t *list) { - assert(list); + ASSERT(list); const method_t * const first = &list->first; const method_t *base = first; @@ -4968,17 +5663,13 @@ void objc_registerProtocol(Protocol *proto_gen) return nil; } -/*********************************************************************** -* getMethodNoSuper_nolock -* fixme -* Locking: runtimeLock must be read- or write-locked by the caller -**********************************************************************/ -static method_t *search_method_list(const method_list_t *mlist, SEL sel) +ALWAYS_INLINE static method_t * +search_method_list_inline(const method_list_t *mlist, SEL sel) { int methodListIsFixedUp = mlist->isFixedUp(); int methodListHasExpectedSize = mlist->entsize() == sizeof(method_t); - if (__builtin_expect(methodListIsFixedUp && methodListHasExpectedSize, 1)) { + if (fastpath(methodListIsFixedUp && methodListHasExpectedSize)) { return findMethodInSortedMethodList(sel, mlist); } else { // Linear search of unsorted method list @@ -5001,12 +5692,54 @@ void objc_registerProtocol(Protocol *proto_gen) return nil; } +NEVER_INLINE static method_t * +search_method_list(const method_list_t *mlist, SEL sel) +{ + return search_method_list_inline(mlist, sel); +} + +/*********************************************************************** + * method_lists_contains_any + **********************************************************************/ +static NEVER_INLINE bool +method_lists_contains_any(method_list_t **mlists, method_list_t **end, + SEL sels[], size_t selcount) +{ + while (mlists < end) { + const method_list_t *mlist = *mlists++; + int methodListIsFixedUp = mlist->isFixedUp(); + int methodListHasExpectedSize = mlist->entsize() == sizeof(method_t); + + if (fastpath(methodListIsFixedUp && methodListHasExpectedSize)) { + for (size_t i = 0; i < selcount; i++) { + if (findMethodInSortedMethodList(sels[i], mlist)) { + return true; + } + } + } else { + for (auto& meth : *mlist) { + for (size_t i = 0; i < selcount; i++) { + if (meth.name == sels[i]) { + return true; + } + } + } + } + } + return false; +} + +/*********************************************************************** + * getMethodNoSuper_nolock + * fixme + * Locking: runtimeLock must be read- or write-locked by the caller + **********************************************************************/ static method_t * getMethodNoSuper_nolock(Class cls, SEL sel) { runtimeLock.assertLocked(); - assert(cls->isRealized()); + ASSERT(cls->isRealized()); // fixme nil cls? // fixme nil sel? @@ -5015,7 +5748,11 @@ void objc_registerProtocol(Protocol *proto_gen) mlists != end; ++mlists) { - method_t *m = search_method_list(*mlists, sel); + // getMethodNoSuper_nolock is the hottest + // caller of search_method_list, inlining it turns + // getMethodNoSuper_nolock into a frame-less function and eliminates + // any store from this codepath. + method_t *m = search_method_list_inline(*mlists, sel); if (m) return m; } @@ -5038,7 +5775,7 @@ void objc_registerProtocol(Protocol *proto_gen) // fixme nil cls? // fixme nil sel? - assert(cls->isRealized()); + ASSERT(cls->isRealized()); while (cls && ((m = getMethodNoSuper_nolock(cls, sel))) == nil) { cls = cls->superclass; @@ -5076,8 +5813,7 @@ Method class_getInstanceMethod(Class cls, SEL sel) #warning fixme build and search caches // Search method lists, try method resolver, etc. - lookUpImpOrNil(cls, sel, nil, - NO/*initialize*/, NO/*cache*/, YES/*resolver*/); + lookUpImpOrForward(nil, sel, cls, LOOKUP_RESOLVER); #warning fixme build and search caches @@ -5091,15 +5827,13 @@ Method class_getInstanceMethod(Class cls, SEL sel) * cls should be a metaclass. * Does not check if the method already exists. **********************************************************************/ -static void resolveClassMethod(Class cls, SEL sel, id inst) +static void resolveClassMethod(id inst, SEL sel, Class cls) { runtimeLock.assertUnlocked(); - assert(cls->isRealized()); - assert(cls->isMetaClass()); + ASSERT(cls->isRealized()); + ASSERT(cls->isMetaClass()); - if (! lookUpImpOrNil(cls, SEL_resolveClassMethod, inst, - NO/*initialize*/, YES/*cache*/, NO/*resolver*/)) - { + if (!lookUpImpOrNil(inst, @selector(resolveClassMethod:), cls)) { // Resolver not implemented. return; } @@ -5115,12 +5849,11 @@ static void resolveClassMethod(Class cls, SEL sel, id inst) } } BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend; - bool resolved = msg(nonmeta, SEL_resolveClassMethod, sel); + bool resolved = msg(nonmeta, @selector(resolveClassMethod:), sel); // Cache the result (good or bad) so the resolver doesn't fire next time. // +resolveClassMethod adds to self->ISA() a.k.a. cls - IMP imp = lookUpImpOrNil(cls, sel, inst, - NO/*initialize*/, YES/*cache*/, NO/*resolver*/); + IMP imp = lookUpImpOrNil(inst, sel, cls); if (resolved && PrintResolving) { if (imp) { @@ -5147,25 +5880,23 @@ static void resolveClassMethod(Class cls, SEL sel, id inst) * cls may be a metaclass or a non-meta class. * Does not check if the method already exists. **********************************************************************/ -static void resolveInstanceMethod(Class cls, SEL sel, id inst) +static void resolveInstanceMethod(id inst, SEL sel, Class cls) { runtimeLock.assertUnlocked(); - assert(cls->isRealized()); + ASSERT(cls->isRealized()); + SEL resolve_sel = @selector(resolveInstanceMethod:); - if (! lookUpImpOrNil(cls->ISA(), SEL_resolveInstanceMethod, cls, - NO/*initialize*/, YES/*cache*/, NO/*resolver*/)) - { + if (!lookUpImpOrNil(cls, resolve_sel, cls->ISA())) { // Resolver not implemented. return; } BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend; - bool resolved = msg(cls, SEL_resolveInstanceMethod, sel); + bool resolved = msg(cls, resolve_sel, sel); // Cache the result (good or bad) so the resolver doesn't fire next time. // +resolveInstanceMethod adds to self a.k.a. cls - IMP imp = lookUpImpOrNil(cls, sel, inst, - NO/*initialize*/, YES/*cache*/, NO/*resolver*/); + IMP imp = lookUpImpOrNil(inst, sel, cls); if (resolved && PrintResolving) { if (imp) { @@ -5187,30 +5918,36 @@ static void resolveInstanceMethod(Class cls, SEL sel, id inst) /*********************************************************************** -* resolveMethod +* resolveMethod_locked * Call +resolveClassMethod or +resolveInstanceMethod. -* Returns nothing; any result would be potentially out-of-date already. -* Does not check if the method already exists. +* +* Called with the runtimeLock held to avoid pressure in the caller +* Tail calls into lookUpImpOrForward, also to avoid pressure in the callerb **********************************************************************/ -static void resolveMethod(Class cls, SEL sel, id inst) +static NEVER_INLINE IMP +resolveMethod_locked(id inst, SEL sel, Class cls, int behavior) { - runtimeLock.assertUnlocked(); - assert(cls->isRealized()); + runtimeLock.assertLocked(); + ASSERT(cls->isRealized()); + + runtimeLock.unlock(); if (! cls->isMetaClass()) { // try [cls resolveInstanceMethod:sel] - resolveInstanceMethod(cls, sel, inst); + resolveInstanceMethod(inst, sel, cls); } else { // try [nonMetaClass resolveClassMethod:sel] // and [cls resolveInstanceMethod:sel] - resolveClassMethod(cls, sel, inst); - if (!lookUpImpOrNil(cls, sel, inst, - NO/*initialize*/, YES/*cache*/, NO/*resolver*/)) - { - resolveInstanceMethod(cls, sel, inst); + resolveClassMethod(inst, sel, cls); + if (!lookUpImpOrNil(inst, sel, cls)) { + resolveInstanceMethod(inst, sel, cls); } } + + // chances are that calling the resolver have populated the cache + // so attempt using it + return lookUpImpOrForward(inst, sel, cls, behavior | LOOKUP_CACHE); } @@ -5224,7 +5961,7 @@ static void resolveMethod(Class cls, SEL sel, id inst) log_and_fill_cache(Class cls, IMP imp, SEL sel, id receiver, Class implementer) { #if SUPPORT_MESSAGE_LOGGING - if (objcMsgLogEnabled) { + if (slowpath(objcMsgLogEnabled && implementer)) { bool cacheIt = logMessageSend(implementer->isMetaClass(), cls->nameForLogging(), implementer->nameForLogging(), @@ -5232,47 +5969,34 @@ static void resolveMethod(Class cls, SEL sel, id inst) if (!cacheIt) return; } #endif - cache_fill (cls, sel, imp, receiver); -} - - -/*********************************************************************** -* _class_lookupMethodAndLoadCache. -* Method lookup for dispatchers ONLY. OTHER CODE SHOULD USE lookUpImp(). -* This lookup avoids optimistic cache scan because the dispatcher -* already tried that. -**********************************************************************/ -IMP _class_lookupMethodAndLoadCache3(id obj, SEL sel, Class cls) -{ - return lookUpImpOrForward(cls, sel, obj, - YES/*initialize*/, NO/*cache*/, YES/*resolver*/); + cache_fill(cls, sel, imp, receiver); } /*********************************************************************** * lookUpImpOrForward. * The standard IMP lookup. -* initialize==NO tries to avoid +initialize (but sometimes fails) -* cache==NO skips optimistic unlocked lookup (but uses cache elsewhere) -* Most callers should use initialize==YES and cache==YES. +* Without LOOKUP_INITIALIZE: tries to avoid +initialize (but sometimes fails) +* Without LOOKUP_CACHE: skips optimistic unlocked lookup (but uses cache elsewhere) +* Most callers should use LOOKUP_INITIALIZE and LOOKUP_CACHE * inst is an instance of cls or a subclass thereof, or nil if none is known. * If cls is an un-initialized metaclass then a non-nil inst is faster. * May return _objc_msgForward_impcache. IMPs destined for external use * must be converted to _objc_msgForward or _objc_msgForward_stret. -* If you don't want forwarding at all, use lookUpImpOrNil() instead. +* If you don't want forwarding at all, use LOOKUP_NIL. **********************************************************************/ -IMP lookUpImpOrForward(Class cls, SEL sel, id inst, - bool initialize, bool cache, bool resolver) +IMP lookUpImpOrForward(id inst, SEL sel, Class cls, int behavior) { + const IMP forward_imp = (IMP)_objc_msgForward_impcache; IMP imp = nil; - bool triedResolver = NO; + Class curClass; runtimeLock.assertUnlocked(); // Optimistic cache lookup - if (cache) { + if (fastpath(behavior & LOOKUP_CACHE)) { imp = cache_getImp(cls, sel); - if (imp) return imp; + if (imp) goto done_nolock; } // runtimeLock is held during isRealized and isInitialized checking @@ -5285,14 +6009,23 @@ IMP lookUpImpOrForward(Class cls, SEL sel, id inst, // behalf of the category. runtimeLock.lock(); + + // We don't want people to be able to craft a binary blob that looks like + // a class but really isn't one and do a CFI attack. + // + // To make these harder we want to make sure this is a class that was + // either built into the binary or legitimately registered through + // objc_duplicateClass, objc_initializeClassPair or objc_allocateClassPair. + // + // TODO: this check is quite costly during process startup. checkIsKnownClass(cls); - if (!cls->isRealized()) { + if (slowpath(!cls->isRealized())) { cls = realizeClassMaybeSwiftAndLeaveLocked(cls, runtimeLock); // runtimeLock may have been dropped but is now locked again } - if (initialize && !cls->isInitialized()) { + if (slowpath((behavior & LOOKUP_INITIALIZE) && !cls->isInitialized())) { cls = initializeAndLeaveLocked(cls, inst, runtimeLock); // runtimeLock may have been dropped but is now locked again @@ -5302,104 +6035,70 @@ IMP lookUpImpOrForward(Class cls, SEL sel, id inst, // from the messenger then it won't happen. 2778172 } - - retry: runtimeLock.assertLocked(); - - // Try this class's cache. - - imp = cache_getImp(cls, sel); - if (imp) goto done; - - // Try this class's method lists. - { - Method meth = getMethodNoSuper_nolock(cls, sel); + curClass = cls; + + // The code used to lookpu the class's cache again right after + // we take the lock but for the vast majority of the cases + // evidence shows this is a miss most of the time, hence a time loss. + // + // The only codepath calling into this without having performed some + // kind of cache lookup is class_getInstanceMethod(). + + for (unsigned attempts = unreasonableClassCount();;) { + // curClass method list. + Method meth = getMethodNoSuper_nolock(curClass, sel); if (meth) { - log_and_fill_cache(cls, meth->imp, sel, inst, cls); imp = meth->imp; goto done; } - } - // Try superclass caches and method lists. - { - unsigned attempts = unreasonableClassCount(); - for (Class curClass = cls->superclass; - curClass != nil; - curClass = curClass->superclass) - { - // Halt if there is a cycle in the superclass chain. - if (--attempts == 0) { - _objc_fatal("Memory corruption in class list."); - } - - // Superclass cache. - imp = cache_getImp(curClass, sel); - if (imp) { - if (imp != (IMP)_objc_msgForward_impcache) { - // Found the method in a superclass. Cache it in this class. - log_and_fill_cache(cls, imp, sel, inst, curClass); - goto done; - } - else { - // Found a forward:: entry in a superclass. - // Stop searching, but don't cache yet; call method - // resolver for this class first. - break; - } - } - - // Superclass method list. - Method meth = getMethodNoSuper_nolock(curClass, sel); - if (meth) { - log_and_fill_cache(cls, meth->imp, sel, inst, curClass); - imp = meth->imp; - goto done; - } + if (slowpath((curClass = curClass->superclass) == nil)) { + // No implementation found, and method resolver didn't help. + // Use forwarding. + imp = forward_imp; + break; } - } - // No implementation found. Try method resolver once. + // Halt if there is a cycle in the superclass chain. + if (slowpath(--attempts == 0)) { + _objc_fatal("Memory corruption in class list."); + } - if (resolver && !triedResolver) { - runtimeLock.unlock(); - resolveMethod(cls, sel, inst); - runtimeLock.lock(); - // Don't cache the result; we don't hold the lock so it may have - // changed already. Re-do the search from scratch instead. - triedResolver = YES; - goto retry; + // Superclass cache. + imp = cache_getImp(curClass, sel); + if (slowpath(imp == forward_imp)) { + // Found a forward:: entry in a superclass. + // Stop searching, but don't cache yet; call method + // resolver for this class first. + break; + } + if (fastpath(imp)) { + // Found the method in a superclass. Cache it in this class. + goto done; + } } - // No implementation found, and method resolver didn't help. - // Use forwarding. + // No implementation found. Try method resolver once. - imp = (IMP)_objc_msgForward_impcache; - cache_fill(cls, sel, imp, inst); + if (slowpath(behavior & LOOKUP_RESOLVER)) { + behavior ^= LOOKUP_RESOLVER; + return resolveMethod_locked(inst, sel, cls, behavior); + } done: + log_and_fill_cache(cls, imp, sel, inst, curClass); runtimeLock.unlock(); - + done_nolock: + if (slowpath((behavior & LOOKUP_NIL) && imp == forward_imp)) { + return nil; + } return imp; } - -/*********************************************************************** -* lookUpImpOrNil. -* Like lookUpImpOrForward, but returns nil instead of _objc_msgForward_impcache -**********************************************************************/ -IMP lookUpImpOrNil(Class cls, SEL sel, id inst, - bool initialize, bool cache, bool resolver) -{ - IMP imp = lookUpImpOrForward(cls, sel, inst, initialize, cache, resolver); - if (imp == _objc_msgForward_impcache) return nil; - else return imp; -} - - /*********************************************************************** * lookupMethodInClassAndLoadCache. -* Like _class_lookupMethodAndLoadCache, but does not search superclasses. +* Like lookUpImpOrForward, but does not search superclasses. * Caches and returns objc_msgForward if the method is not found in the class. **********************************************************************/ IMP lookupMethodInClassAndLoadCache(Class cls, SEL sel) @@ -5409,7 +6108,7 @@ IMP lookupMethodInClassAndLoadCache(Class cls, SEL sel) // fixme this is incomplete - no resolver, +initialize - // but it's only used for .cxx_construct/destruct so we don't care - assert(sel == SEL_cxx_construct || sel == SEL_cxx_destruct); + ASSERT(sel == SEL_cxx_construct || sel == SEL_cxx_destruct); // Search cache first. imp = cache_getImp(cls, sel); @@ -5446,7 +6145,7 @@ objc_property_t class_getProperty(Class cls, const char *name) checkIsKnownClass(cls); - assert(cls->isRealized()); + ASSERT(cls->isRealized()); for ( ; cls; cls = cls->superclass) { for (auto& prop : cls->data()->properties) { @@ -5488,122 +6187,23 @@ Class gdb_object_getClass(id obj) Class metacls; Class cls; - assert(!isMetaClass()); + ASSERT(!isMetaClass()); cls = (Class)this; metacls = cls->ISA(); mutex_locker_t lock(runtimeLock); - // Scan metaclass for custom AWZ. - // Scan metaclass for custom RR. - // Scan class for custom RR. - // Also print custom RR/AWZ because we probably haven't done it yet. - // Special cases: - // NSObject AWZ class methods are default. - // NSObject RR instance methods are default. - // updateCustomRR_AWZ() also knows these special cases. + // - NSObject AWZ class methods are default. + // - NSObject RR class and instance methods are default. + // - NSObject Core class and instance methods are default. + // adjustCustomFlagsForMethodChange() also knows these special cases. // attachMethodLists() also knows these special cases. - bool inherited; - bool metaCustomAWZ = NO; - if (MetaclassNSObjectAWZSwizzled) { - // Somebody already swizzled NSObject's methods - metaCustomAWZ = YES; - inherited = NO; - } - else if (metacls == classNSObject()->ISA()) { - // NSObject's metaclass AWZ is default, but we still need to check cats - auto& methods = metacls->data()->methods; - for (auto mlists = methods.beginCategoryMethodLists(), - end = methods.endCategoryMethodLists(metacls); - mlists != end; - ++mlists) - { - if (methodListImplementsAWZ(*mlists)) { - metaCustomAWZ = YES; - inherited = NO; - break; - } - } - } - else if (metacls->superclass->hasCustomAWZ()) { - // Superclass is custom AWZ, therefore we are too. - metaCustomAWZ = YES; - inherited = YES; - } - else { - // Not metaclass NSObject. - auto& methods = metacls->data()->methods; - for (auto mlists = methods.beginLists(), - end = methods.endLists(); - mlists != end; - ++mlists) - { - if (methodListImplementsAWZ(*mlists)) { - metaCustomAWZ = YES; - inherited = NO; - break; - } - } - } - if (!metaCustomAWZ) metacls->setHasDefaultAWZ(); - - if (PrintCustomAWZ && metaCustomAWZ) metacls->printCustomAWZ(inherited); - // metacls->printCustomRR(); - - - bool clsCustomRR = NO; - if (ClassNSObjectRRSwizzled) { - // Somebody already swizzled NSObject's methods - clsCustomRR = YES; - inherited = NO; - } - if (cls == classNSObject()) { - // NSObject's RR is default, but we still need to check categories - auto& methods = cls->data()->methods; - for (auto mlists = methods.beginCategoryMethodLists(), - end = methods.endCategoryMethodLists(cls); - mlists != end; - ++mlists) - { - if (methodListImplementsRR(*mlists)) { - clsCustomRR = YES; - inherited = NO; - break; - } - } - } - else if (!cls->superclass) { - // Custom root class - clsCustomRR = YES; - inherited = NO; - } - else if (cls->superclass->hasCustomRR()) { - // Superclass is custom RR, therefore we are too. - clsCustomRR = YES; - inherited = YES; - } - else { - // Not class NSObject. - auto& methods = cls->data()->methods; - for (auto mlists = methods.beginLists(), - end = methods.endLists(); - mlists != end; - ++mlists) - { - if (methodListImplementsRR(*mlists)) { - clsCustomRR = YES; - inherited = NO; - break; - } - } - } - if (!clsCustomRR) cls->setHasDefaultRR(); - - // cls->printCustomAWZ(); - if (PrintCustomRR && clsCustomRR) cls->printCustomRR(inherited); + objc::AWZScanner::scanInitializedClass(cls, metacls); + objc::RRScanner::scanInitializedClass(cls, metacls); + objc::CoreScanner::scanInitializedClass(cls, metacls); // Update the +initialize flags. // Do this last. @@ -5611,164 +6211,35 @@ Class gdb_object_getClass(id obj) } -/*********************************************************************** -* Return YES if sel is used by retain/release implementors -**********************************************************************/ -static bool -isRRSelector(SEL sel) -{ - return (sel == SEL_retain || sel == SEL_release || - sel == SEL_autorelease || sel == SEL_retainCount || - sel == SEL_tryRetain || sel == SEL_retainWeakReference || - sel == SEL_isDeallocating || sel == SEL_allowsWeakReference); -} - - -/*********************************************************************** -* Return YES if mlist implements one of the isRRSelector() methods -**********************************************************************/ -static bool -methodListImplementsRR(const method_list_t *mlist) -{ - return (search_method_list(mlist, SEL_retain) || - search_method_list(mlist, SEL_release) || - search_method_list(mlist, SEL_autorelease) || - search_method_list(mlist, SEL_retainCount) || - search_method_list(mlist, SEL_tryRetain) || - search_method_list(mlist, SEL_isDeallocating) || - search_method_list(mlist, SEL_retainWeakReference) || - search_method_list(mlist, SEL_allowsWeakReference)); -} - - -/*********************************************************************** -* Return YES if sel is used by alloc or allocWithZone implementors -**********************************************************************/ -static bool -isAWZSelector(SEL sel) -{ - return (sel == SEL_allocWithZone || sel == SEL_alloc); -} - - -/*********************************************************************** -* Return YES if mlist implements one of the isAWZSelector() methods -**********************************************************************/ -static bool -methodListImplementsAWZ(const method_list_t *mlist) -{ - return (search_method_list(mlist, SEL_allocWithZone) || - search_method_list(mlist, SEL_alloc)); -} - - -void -objc_class::printCustomRR(bool inherited) -{ - assert(PrintCustomRR); - assert(hasCustomRR()); - _objc_inform("CUSTOM RR: %s%s%s", nameForLogging(), - isMetaClass() ? " (meta)" : "", - inherited ? " (inherited)" : ""); -} - -void -objc_class::printCustomAWZ(bool inherited) -{ - assert(PrintCustomAWZ); - assert(hasCustomAWZ()); - _objc_inform("CUSTOM AWZ: %s%s%s", nameForLogging(), - isMetaClass() ? " (meta)" : "", - inherited ? " (inherited)" : ""); -} - -void +void objc_class::printInstancesRequireRawIsa(bool inherited) { - assert(PrintRawIsa); - assert(instancesRequireRawIsa()); + ASSERT(PrintRawIsa); + ASSERT(instancesRequireRawIsa()); _objc_inform("RAW ISA: %s%s%s", nameForLogging(), isMetaClass() ? " (meta)" : "", inherited ? " (inherited)" : ""); } - -/*********************************************************************** -* Mark this class and all of its subclasses as implementors or -* inheritors of custom RR (retain/release/autorelease/retainCount) -**********************************************************************/ -void objc_class::setHasCustomRR(bool inherited) -{ - Class cls = (Class)this; - runtimeLock.assertLocked(); - - if (hasCustomRR()) return; - - foreach_realized_class_and_subclass(cls, ^(Class c){ - if (c != cls && !c->isInitialized()) { - // Subclass not yet initialized. Wait for setInitialized() to do it - // fixme short circuit recursion? - return; - } - if (c->hasCustomRR()) { - // fixme short circuit recursion? - return; - } - - c->bits.setHasCustomRR(); - - if (PrintCustomRR) c->printCustomRR(inherited || c != cls); - }); -} - -/*********************************************************************** -* Mark this class and all of its subclasses as implementors or -* inheritors of custom alloc/allocWithZone: -**********************************************************************/ -void objc_class::setHasCustomAWZ(bool inherited) -{ - Class cls = (Class)this; - runtimeLock.assertLocked(); - - if (hasCustomAWZ()) return; - - foreach_realized_class_and_subclass(cls, ^(Class c){ - if (c != cls && !c->isInitialized()) { - // Subclass not yet initialized. Wait for setInitialized() to do it - // fixme short circuit recursion? - return; - } - if (c->hasCustomAWZ()) { - // fixme short circuit recursion? - return; - } - - c->bits.setHasCustomAWZ(); - - if (PrintCustomAWZ) c->printCustomAWZ(inherited || c != cls); - }); -} - - /*********************************************************************** * Mark this class and all of its subclasses as requiring raw isa pointers **********************************************************************/ -void objc_class::setInstancesRequireRawIsa(bool inherited) +void objc_class::setInstancesRequireRawIsaRecursively(bool inherited) { Class cls = (Class)this; runtimeLock.assertLocked(); if (instancesRequireRawIsa()) return; - foreach_realized_class_and_subclass(cls, ^(Class c){ + foreach_realized_class_and_subclass(cls, [=](Class c){ if (c->instancesRequireRawIsa()) { - // fixme short circuit recursion? - return; + return false; } - c->bits.setInstancesRequireRawIsa(); + c->setInstancesRequireRawIsa(); if (PrintRawIsa) c->printInstancesRequireRawIsa(inherited || c != cls); + return true; }); } @@ -5785,8 +6256,8 @@ Class gdb_object_getClass(id obj) if (objc_indexed_classes_count >= ISA_INDEX_COUNT) { // No more indexes available. - assert(cls->classArrayIndex() == 0); - cls->setInstancesRequireRawIsa(false/*not inherited*/); + ASSERT(cls->classArrayIndex() == 0); + cls->setInstancesRequireRawIsaRecursively(false/*not inherited*/); return; } @@ -5802,88 +6273,11 @@ Class gdb_object_getClass(id obj) * Update custom RR and AWZ when a method changes its IMP **********************************************************************/ static void -updateCustomRR_AWZ(Class cls, method_t *meth) +adjustCustomFlagsForMethodChange(Class cls, method_t *meth) { - // In almost all cases, IMP swizzling does not affect custom RR/AWZ bits. - // Custom RR/AWZ search will already find the method whether or not - // it is swizzled, so it does not transition from non-custom to custom. - // - // The only cases where IMP swizzling can affect the RR/AWZ bits is - // if the swizzled method is one of the methods that is assumed to be - // non-custom. These special cases are listed in setInitialized(). - // We look for such cases here. - - if (isRRSelector(meth->name)) { - - if ((classNSObject()->isInitialized() && - classNSObject()->hasCustomRR()) - || - ClassNSObjectRRSwizzled) - { - // already custom, nothing would change - return; - } - - bool swizzlingNSObject = NO; - if (cls == classNSObject()) { - swizzlingNSObject = YES; - } else { - // Don't know the class. - // The only special case is class NSObject. - for (const auto& meth2 : classNSObject()->data()->methods) { - if (meth == &meth2) { - swizzlingNSObject = YES; - break; - } - } - } - if (swizzlingNSObject) { - if (classNSObject()->isInitialized()) { - classNSObject()->setHasCustomRR(); - } else { - // NSObject not yet +initialized, so custom RR has not yet - // been checked, and setInitialized() will not notice the - // swizzle. - ClassNSObjectRRSwizzled = YES; - } - } - } - else if (isAWZSelector(meth->name)) { - Class metaclassNSObject = classNSObject()->ISA(); - - if ((metaclassNSObject->isInitialized() && - metaclassNSObject->hasCustomAWZ()) - || - MetaclassNSObjectAWZSwizzled) - { - // already custom, nothing would change - return; - } - - bool swizzlingNSObject = NO; - if (cls == metaclassNSObject) { - swizzlingNSObject = YES; - } else { - // Don't know the class. - // The only special case is metaclass NSObject. - for (const auto& meth2 : metaclassNSObject->data()->methods) { - if (meth == &meth2) { - swizzlingNSObject = YES; - break; - } - } - } - if (swizzlingNSObject) { - if (metaclassNSObject->isInitialized()) { - metaclassNSObject->setHasCustomAWZ(); - } else { - // NSObject not yet +initialized, so custom RR has not yet - // been checked, and setInitialized() will not notice the - // swizzle. - MetaclassNSObjectAWZSwizzled = YES; - } - } - } + objc::AWZScanner::scanChangedMethod(cls, meth); + objc::RRScanner::scanChangedMethod(cls, meth); + objc::CoreScanner::scanChangedMethod(cls, meth); } @@ -5993,7 +6387,7 @@ Class gdb_object_getClass(id obj) runtimeLock.assertLocked(); const ivar_list_t *ivars; - assert(cls->isRealized()); + ASSERT(cls->isRealized()); if ((ivars = cls->data()->ro->ivars)) { for (auto& ivar : *ivars) { if (!ivar.offset) continue; // anonymous bitfield @@ -6067,7 +6461,7 @@ BOOL class_conformsToProtocol(Class cls, Protocol *proto_gen) checkIsKnownClass(cls); - assert(cls->isRealized()); + ASSERT(cls->isRealized()); for (const auto& proto_ref : cls->data()->protocols) { protocol_t *p = remapProtocol(proto_ref); @@ -6094,8 +6488,8 @@ BOOL class_conformsToProtocol(Class cls, Protocol *proto_gen) checkIsKnownClass(cls); - assert(types); - assert(cls->isRealized()); + ASSERT(types); + ASSERT(cls->isRealized()); method_t *m; if ((m = getMethodNoSuper_nolock(cls, name))) { @@ -6140,10 +6534,10 @@ BOOL class_conformsToProtocol(Class cls, Protocol *proto_gen) { runtimeLock.assertLocked(); - assert(names); - assert(imps); - assert(types); - assert(cls->isRealized()); + ASSERT(names); + ASSERT(imps); + ASSERT(types); + ASSERT(cls->isRealized()); method_list_t *newlist; size_t newlistSize = method_list_t::byteSize(sizeof(method_t), count); @@ -6267,7 +6661,7 @@ BOOL class_conformsToProtocol(Class cls, Protocol *proto_gen) mutex_locker_t lock(runtimeLock); checkIsKnownClass(cls); - assert(cls->isRealized()); + ASSERT(cls->isRealized()); // No class variables if (cls->isMetaClass()) { @@ -6342,7 +6736,7 @@ BOOL class_addProtocol(Class cls, Protocol *protocol_gen) mutex_locker_t lock(runtimeLock); - assert(cls->isRealized()); + ASSERT(cls->isRealized()); // fixme optimize protocol_list_t *protolist = (protocol_list_t *) @@ -6386,7 +6780,7 @@ BOOL class_addProtocol(Class cls, Protocol *protocol_gen) else { mutex_locker_t lock(runtimeLock); - assert(cls->isRealized()); + ASSERT(cls->isRealized()); property_list_t *proplist = (property_list_t *) malloc(sizeof(*proplist)); @@ -6486,14 +6880,14 @@ bool includeClassHandler __attribute__((unused))) // Call the hook. Class swiftcls = nil; if (GetClassHook.get()(name, &swiftcls)) { - assert(swiftcls->isRealized()); + ASSERT(swiftcls->isRealized()); result = swiftcls; } // Erase the name from tls. unsigned slot = --tls->classNameLookupsUsed; - assert(slot >= 0 && slot < tls->classNameLookupsAllocated); - assert(name == tls->classNameLookups[slot]); + ASSERT(slot >= 0 && slot < tls->classNameLookupsAllocated); + ASSERT(name == tls->classNameLookups[slot]); tls->classNameLookups[slot] = nil; } @@ -6516,8 +6910,8 @@ bool includeClassHandler __attribute__((unused))) checkIsKnownClass(original); - assert(original->isRealized()); - assert(!original->isMetaClass()); + ASSERT(original->isRealized()); + ASSERT(!original->isMetaClass()); duplicate = alloc_class_for_subclass(original, extraBytes); @@ -6626,8 +7020,19 @@ static void objc_initializeClassPair_internal(Class superclass, const char *name meta->chooseClassArrayIndex(); cls->chooseClassArrayIndex(); + // This absolutely needs to be done before addSubclass + // as initializeToEmpty() clobbers the FAST_CACHE bits + cls->cache.initializeToEmpty(); + meta->cache.initializeToEmpty(); + +#if FAST_CACHE_META + meta->cache.setBit(FAST_CACHE_META); +#endif + meta->setInstancesRequireRawIsa(); + // Connect to superclasses and metaclasses cls->initClassIsa(meta); + if (superclass) { meta->initClassIsa(superclass->ISA()->ISA()); cls->superclass = superclass; @@ -6642,9 +7047,6 @@ static void objc_initializeClassPair_internal(Class superclass, const char *name addSubclass(cls, meta); } - cls->cache.initializeToEmpty(); - meta->cache.initializeToEmpty(); - addClassTableEntry(cls); } @@ -6805,7 +7207,7 @@ Class objc_readClassPair(Class bits, const struct objc_image_info *info) // The only client of this function is old Swift. // Stable Swift won't use it. // fixme once Swift in the OS settles we can assert(!cls->isSwiftStable()). - cls = realizeClassWithoutSwift(cls); + cls = realizeClassWithoutSwift(cls, nil); return cls; } @@ -6823,7 +7225,7 @@ static void detach_class(Class cls, bool isMeta) runtimeLock.assertLocked(); // categories not yet attached to this class - removeAllUnattachedCategoriesForClass(cls); + objc::unattachedCategories.eraseClass(cls); // superclass's subclass list if (cls->isRealized()) { @@ -6839,7 +7241,7 @@ static void detach_class(Class cls, bool isMeta) if (!isMeta) { removeNamedClass(cls, cls->mangledName()); } - NXHashRemove(allocatedClasses, cls); + objc::allocatedClasses.get().erase(cls); } @@ -6947,7 +7349,7 @@ void objc_disposeClassPair(Class cls) * Note: class_createInstance() and class_createInstances() preflight this. **********************************************************************/ id -objc_constructInstance(Class cls, void *bytes) +objc_constructInstance(Class cls, void *bytes) { if (!cls || !bytes) return nil; @@ -6965,7 +7367,7 @@ void objc_disposeClassPair(Class cls) } if (hasCxxCtor) { - return object_cxxConstructFromClass(obj, cls); + return object_cxxConstructFromClass(obj, cls, OBJECT_CONSTRUCT_NONE); } else { return obj; } @@ -6976,59 +7378,71 @@ void objc_disposeClassPair(Class cls) * class_createInstance * fixme * Locking: none +* +* Note: this function has been carefully written so that the fastpath +* takes no branch. **********************************************************************/ - -static __attribute__((always_inline)) -id -_class_createInstanceFromZone(Class cls, size_t extraBytes, void *zone, - bool cxxConstruct = true, +static ALWAYS_INLINE id +_class_createInstanceFromZone(Class cls, size_t extraBytes, void *zone, + int construct_flags = OBJECT_CONSTRUCT_NONE, + bool cxxConstruct = true, size_t *outAllocatedSize = nil) { - if (!cls) return nil; - - assert(cls->isRealized()); + ASSERT(cls->isRealized()); // Read class's info bits all at once for performance - bool hasCxxCtor = cls->hasCxxCtor(); + bool hasCxxCtor = cxxConstruct && cls->hasCxxCtor(); bool hasCxxDtor = cls->hasCxxDtor(); bool fast = cls->canAllocNonpointer(); + size_t size; - size_t size = cls->instanceSize(extraBytes); + size = cls->instanceSize(extraBytes); if (outAllocatedSize) *outAllocatedSize = size; id obj; - if (!zone && fast) { + if (zone) { + obj = (id)malloc_zone_calloc((malloc_zone_t *)zone, 1, size); + } else { obj = (id)calloc(1, size); - if (!obj) return nil; - obj->initInstanceIsa(cls, hasCxxDtor); - } - else { - if (zone) { - obj = (id)malloc_zone_calloc ((malloc_zone_t *)zone, 1, size); - } else { - obj = (id)calloc(1, size); + } + if (slowpath(!obj)) { + if (construct_flags & OBJECT_CONSTRUCT_CALL_BADALLOC) { + return _objc_callBadAllocHandler(cls); } - if (!obj) return nil; + return nil; + } - // Use raw pointer isa on the assumption that they might be + if (!zone && fast) { + obj->initInstanceIsa(cls, hasCxxDtor); + } else { + // Use raw pointer isa on the assumption that they might be // doing something weird with the zone or RR. obj->initIsa(cls); } - if (cxxConstruct && hasCxxCtor) { - obj = _objc_constructOrFree(obj, cls); + if (fastpath(!hasCxxCtor)) { + return obj; } - return obj; + construct_flags |= OBJECT_CONSTRUCT_FREE_ONFAILURE; + return object_cxxConstructFromClass(obj, cls, construct_flags); } - -id +id class_createInstance(Class cls, size_t extraBytes) { + if (!cls) return nil; return _class_createInstanceFromZone(cls, extraBytes, nil); } +NEVER_INLINE +id +_objc_rootAllocWithZone(Class cls, malloc_zone_t *zone __unused) +{ + // allocWithZone under __OBJC2__ ignores the zone parameter + return _class_createInstanceFromZone(cls, 0, nil, + OBJECT_CONSTRUCT_CALL_BADALLOC); +} /*********************************************************************** * class_createInstances @@ -7061,7 +7475,8 @@ static __attribute__((always_inline)) Class cls = oldObj->ISA(); size_t size; - id obj = _class_createInstanceFromZone(cls, extraBytes, zone, false, &size); + id obj = _class_createInstanceFromZone(cls, extraBytes, zone, + OBJECT_CONSTRUCT_NONE, false, &size); if (!obj) return nil; // Copy everything except the isa, which was already set above. @@ -7098,6 +7513,7 @@ static __attribute__((always_inline)) id class_createInstanceFromZone(Class cls, size_t extraBytes, void *zone) { + if (!cls) return nil; return _class_createInstanceFromZone(cls, extraBytes, zone); } @@ -7421,15 +7837,15 @@ Class _objc_getFreedObjectClass (void) msg->sel = sel_registerName((const char *)msg->sel); if (msg->imp == &objc_msgSend_fixup) { - if (msg->sel == SEL_alloc) { + if (msg->sel == @selector(alloc)) { msg->imp = (IMP)&objc_alloc; - } else if (msg->sel == SEL_allocWithZone) { + } else if (msg->sel == @selector(allocWithZone:)) { msg->imp = (IMP)&objc_allocWithZone; - } else if (msg->sel == SEL_retain) { + } else if (msg->sel == @selector(retain)) { msg->imp = (IMP)&objc_retain; - } else if (msg->sel == SEL_release) { + } else if (msg->sel == @selector(release)) { msg->imp = (IMP)&objc_release; - } else if (msg->sel == SEL_autorelease) { + } else if (msg->sel == @selector(autorelease)) { msg->imp = (IMP)&objc_autorelease; } else { msg->imp = &objc_msgSend_fixedup; @@ -7467,8 +7883,8 @@ static Class setSuperclass(Class cls, Class newSuper) runtimeLock.assertLocked(); - assert(cls->isRealized()); - assert(newSuper->isRealized()); + ASSERT(cls->isRealized()); + ASSERT(newSuper->isRealized()); oldSuper = cls->superclass; removeSubclass(oldSuper, cls); @@ -7493,6 +7909,11 @@ Class class_setSuperclass(Class cls, Class newSuper) return setSuperclass(cls, newSuper); } +void runtime_init(void) +{ + objc::unattachedCategories.init(32); + objc::allocatedClasses.init(); +} // __OBJC2__ #endif diff --git a/runtime/objc-runtime-old.h b/runtime/objc-runtime-old.h index b367c09..1467210 100644 --- a/runtime/objc-runtime-old.h +++ b/runtime/objc-runtime-old.h @@ -205,7 +205,7 @@ struct objc_class : objc_object { // set and clear must not overlap void changeInfo(uint32_t set, uint32_t clear) { - assert((set & clear) == 0); + ASSERT((set & clear) == 0); uint32_t oldf, newf; do { @@ -234,19 +234,13 @@ struct objc_class : objc_object { return info & CLS_IS_ARC; } - bool hasCustomRR() { + bool hasCustomRR() { return true; } - void setHasCustomRR(bool = false) { } - void setHasDefaultRR() { } - void printCustomRR(bool) { } - bool hasCustomAWZ() { + bool hasCustomAWZ() { return true; } - void setHasCustomAWZ(bool = false) { } - void setHasDefaultAWZ() { } - void printCustomAWZ(bool) { } bool forbidsAssociatedObjects() { // Old runtime doesn't support forbidding associated objects. diff --git a/runtime/objc-runtime-old.mm b/runtime/objc-runtime-old.mm index e881d71..3d8c0b2 100644 --- a/runtime/objc-runtime-old.mm +++ b/runtime/objc-runtime-old.mm @@ -365,7 +365,7 @@ int objc_getClassList(Class *buffer, int bufferLen) } result[i++] = nil; - assert(i == count+1); + ASSERT(i == count+1); if (outCount) *outCount = count; return result; @@ -879,7 +879,7 @@ static void really_connect_class(Class cls, // No duplicate classes allowed. // Duplicates should have been rejected by _objc_read_classes_from_image - assert(!oldCls); + ASSERT(!oldCls); } // Fix up pended class refs to this class, if any @@ -1780,7 +1780,7 @@ BOOL protocol_isEqual(Protocol *self, Protocol *other) objc_allocateProtocol(const char *name) { Class cls = objc_getClass("__IncompleteProtocol"); - assert(cls); + ASSERT(cls); mutex_locker_t lock(classLock); @@ -2196,6 +2196,11 @@ void _read_images(header_info **hList, uint32_t hCount, int totalClasses, int un // Parts of this order are important for correctness or performance. + // Fix up selector refs from all images. + for (i = 0; i < hCount; i++) { + _objc_fixup_selector_refs(hList[i]); + } + // Read classes from all images. for (i = 0; i < hCount; i++) { _objc_read_classes_from_image(hList[i]); @@ -2218,10 +2223,9 @@ void _read_images(header_info **hList, uint32_t hCount, int totalClasses, int un _objc_connect_classes_from_image(hList[i]); } - // Fix up class refs, selector refs, and protocol objects from all images. + // Fix up class refs, and protocol objects from all images. for (i = 0; i < hCount; i++) { _objc_map_class_refs_for_image(hList[i]); - _objc_fixup_selector_refs(hList[i]); _objc_fixup_protocol_objects_for_image(hList[i]); } @@ -3153,7 +3157,10 @@ static bool _objc_register_category(old_category *cat, int version) { header_info *hi; int count = 0; - int max = HeaderCount; + int max = 0; + for (hi = FirstHeader; hi != nil; hi = hi->getNext()) { + max++; + } #if TARGET_OS_WIN32 const TCHAR **names = (const TCHAR **)calloc(max+1, sizeof(TCHAR *)); #else @@ -3328,7 +3335,7 @@ void objc_setMultithreaded (BOOL flag) mutex_t cacheUpdateLock; recursive_mutex_t loadMethodLock; -void lock_init(void) +void runtime_init(void) { } diff --git a/runtime/objc-runtime.mm b/runtime/objc-runtime.mm index 176d341..08a1b77 100644 --- a/runtime/objc-runtime.mm +++ b/runtime/objc-runtime.mm @@ -35,6 +35,7 @@ #include "objc-private.h" #include "objc-loadmethod.h" +#include "objc-file.h" #include "message.h" /*********************************************************************** @@ -88,35 +89,19 @@ // objc's key for pthread_getspecific +#if SUPPORT_DIRECT_THREAD_KEYS +#define _objc_pthread_key TLS_DIRECT_KEY +#else static tls_key_t _objc_pthread_key; +#endif // Selectors -SEL SEL_load = NULL; -SEL SEL_initialize = NULL; -SEL SEL_resolveInstanceMethod = NULL; -SEL SEL_resolveClassMethod = NULL; SEL SEL_cxx_construct = NULL; SEL SEL_cxx_destruct = NULL; -SEL SEL_retain = NULL; -SEL SEL_release = NULL; -SEL SEL_autorelease = NULL; -SEL SEL_retainCount = NULL; -SEL SEL_alloc = NULL; -SEL SEL_allocWithZone = NULL; -SEL SEL_dealloc = NULL; -SEL SEL_copy = NULL; -SEL SEL_new = NULL; -SEL SEL_forwardInvocation = NULL; -SEL SEL_tryRetain = NULL; -SEL SEL_isDeallocating = NULL; -SEL SEL_retainWeakReference = NULL; -SEL SEL_allowsWeakReference = NULL; - +struct objc::SafeRanges objc::dataSegmentsRanges; header_info *FirstHeader = 0; // NULL means empty list header_info *LastHeader = 0; // NULL means invalid; recompute it -int HeaderCount = 0; - // Set to true on the child side of fork() // if the parent process was multithreaded when fork() was called. @@ -212,6 +197,72 @@ Class objc_getMetaClass(const char *aClassName) return cls->ISA(); } +/*********************************************************************** + * objc::SafeRanges::find. Find an image data segment that contains address + **********************************************************************/ +bool +objc::SafeRanges::find(uintptr_t ptr, uint32_t &pos) +{ + if (!sorted) { + std::sort(ranges, ranges + count, [](const Range &s1, const Range &s2){ + return s1.start < s2.start; + }); + sorted = true; + } + + uint32_t l = 0, r = count; + while (l < r) { + uint32_t i = (l + r) / 2; + + if (ptr < ranges[i].start) { + r = i; + } else if (ptr >= ranges[i].end) { + l = i + 1; + } else { + pos = i; + return true; + } + } + + pos = UINT32_MAX; + return false; +} + +/*********************************************************************** + * objc::SafeRanges::add. Register a new well known data segment. + **********************************************************************/ +void +objc::SafeRanges::add(uintptr_t start, uintptr_t end) +{ + if (count == size) { + // Have a typical malloc growth: + // - size <= 32: grow by 4 + // - size <= 64: grow by 8 + // - size <= 128: grow by 16 + // ... etc + size += size < 16 ? 4 : 1 << (fls(size) - 3); + ranges = (Range *)realloc(ranges, sizeof(Range) * size); + } + ranges[count++] = Range{ start, end }; + sorted = false; +} + +/*********************************************************************** + * objc::SafeRanges::remove. Remove a previously known data segment. + **********************************************************************/ +void +objc::SafeRanges::remove(uintptr_t start, uintptr_t end) +{ + uint32_t pos; + + if (!find(start, pos) || ranges[pos].end != end) { + _objc_fatal("Cannot find range %#lx..%#lx", start, end); + } + if (pos < --count) { + ranges[pos] = ranges[count]; + sorted = false; + } +} /*********************************************************************** * appendHeader. Add a newly-constructed header_info to the list. @@ -220,7 +271,6 @@ void appendHeader(header_info *hi) { // Add the header to the header list. // The header is appended to the list, to preserve the bottom-up order. - HeaderCount++; hi->setNext(NULL); if (!FirstHeader) { // list is empty @@ -235,6 +285,15 @@ void appendHeader(header_info *hi) LastHeader->setNext(hi); LastHeader = hi; } + +#if __OBJC2__ + if ((hi->mhdr()->flags & MH_DYLIB_IN_CACHE) == 0) { + foreach_data_segment(hi->mhdr(), [](const segmentType *seg, intptr_t slide) { + uintptr_t start = (uintptr_t)seg->vmaddr + slide; + objc::dataSegmentsRanges.add(start, start + seg->vmsize); + }); + } +#endif } @@ -264,12 +323,19 @@ void removeHeader(header_info *hi) if (LastHeader == deadHead) { LastHeader = NULL; // will be recomputed next time it's used } - - HeaderCount--; break; } prev = current; } + +#if __OBJC2__ + if ((hi->mhdr()->flags & MH_DYLIB_IN_CACHE) == 0) { + foreach_data_segment(hi->mhdr(), [](const segmentType *seg, intptr_t slide) { + uintptr_t start = (uintptr_t)seg->vmaddr + slide; + objc::dataSegmentsRanges.remove(start, start + seg->vmsize); + }); + } +#endif } @@ -379,7 +445,7 @@ void environ_init(void) const char *newImage = "??"; // Silently ignore +load replacement because category +load is special - if (s == SEL_load) return; + if (s == @selector(load)) return; #if TARGET_OS_WIN32 // don't know dladdr()/dli_fname equivalent @@ -448,7 +514,6 @@ void _objc_pthread_destroyspecific(void *arg) void tls_init(void) { #if SUPPORT_DIRECT_THREAD_KEYS - _objc_pthread_key = TLS_DIRECT_KEY; pthread_key_init_np(TLS_DIRECT_KEY, &_objc_pthread_destroyspecific); #else _objc_pthread_key = tls_create(&_objc_pthread_destroyspecific); @@ -481,7 +546,7 @@ void _objcInit(void) #else // Default forward handler halts the process. -__attribute__((noreturn)) void +__attribute__((noreturn, cold)) void objc_defaultForwardHandler(id self, SEL sel) { _objc_fatal("%c[%s %s]: unrecognized selector sent to instance %p " @@ -493,7 +558,7 @@ void _objcInit(void) #if SUPPORT_STRET struct stret { int i[100]; }; -__attribute__((noreturn)) struct stret +__attribute__((noreturn, cold)) struct stret objc_defaultForwardStretHandler(id self, SEL sel) { objc_defaultForwardHandler(self, sel); @@ -578,13 +643,30 @@ void objc_setEnumerationMutationHandler(void (*handler)(id)) { * Associative Reference Support **********************************************************************/ -id objc_getAssociatedObject(id object, const void *key) { - return _object_get_associative_reference(object, (void *)key); +id +objc_getAssociatedObject(id object, const void *key) +{ + return _object_get_associative_reference(object, key); +} + +static void +_base_objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy) +{ + _object_set_associative_reference(object, key, value, policy); } +static ChainedHookFunction SetAssocHook{_base_objc_setAssociatedObject}; -void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy) { - _object_set_associative_reference(object, (void *)key, value, policy); +void +objc_setHook_setAssociatedObject(objc_hook_setAssociatedObject _Nonnull newValue, + objc_hook_setAssociatedObject _Nullable * _Nonnull outOldValue) { + SetAssocHook.set(newValue, outOldValue); +} + +void +objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy) +{ + SetAssocHook.get()(object, key, value, policy); } diff --git a/runtime/objc-sel-old.mm b/runtime/objc-sel-old.mm index 942ddf5..2a3a242 100644 --- a/runtime/objc-sel-old.mm +++ b/runtime/objc-sel-old.mm @@ -157,37 +157,13 @@ void sel_init(size_t selrefCount) // Register selectors used by libobjc -#define s(x) SEL_##x = sel_registerNameNoLock(#x, NO) -#define t(x,y) SEL_##y = sel_registerNameNoLock(#x, NO) - mutex_locker_t lock(selLock); - s(load); - s(initialize); - t(resolveInstanceMethod:, resolveInstanceMethod); - t(resolveClassMethod:, resolveClassMethod); - t(.cxx_construct, cxx_construct); - t(.cxx_destruct, cxx_destruct); - s(retain); - s(release); - s(autorelease); - s(retainCount); - s(alloc); - t(allocWithZone:, allocWithZone); - s(dealloc); - s(copy); - s(new); - t(forwardInvocation:, forwardInvocation); - t(_tryRetain, tryRetain); - t(_isDeallocating, isDeallocating); - s(retainWeakReference); - s(allowsWeakReference); + SEL_cxx_construct = sel_registerNameNoLock(".cxx_construct", NO); + SEL_cxx_destruct = sel_registerNameNoLock(".cxx_destruct", NO); extern SEL FwdSel; FwdSel = sel_registerNameNoLock("forward::", NO); - -#undef s -#undef t } __END_DECLS diff --git a/runtime/objc-sel-table.s b/runtime/objc-sel-table.s index 05b5532..6d9710d 100644 --- a/runtime/objc-sel-table.s +++ b/runtime/objc-sel-table.s @@ -7,6 +7,9 @@ # define PTR(x) .long x #endif +// These offsets are populated by the dyld shared cache builder. +// They point to memory allocatd elsewhere in the shared cache. + .section __TEXT,__objc_opt_ro .align 3 .private_extern __objc_opt_data @@ -20,47 +23,6 @@ __objc_opt_data: .long 0 /* table.headeropt_rw_offset */ .space PAGE_MAX_SIZE-28 -/* space for selopt, smax/capacity=1048576, blen/mask=524287+1 */ -.space 4*(8+256) /* header and scramble */ -.space 524288 /* mask tab */ -.space 1048576 /* checkbytes */ -.space 1048576*4 /* offsets */ - -/* space for clsopt, smax/capacity=131072, blen/mask=32767+1 */ -.space 4*(8+256) /* header and scramble */ -.space 32768 /* mask tab */ -.space 131072 /* checkbytes */ -.space 131072*12 /* offsets to name and class and header_info */ -.space 512*8 /* some duplicate classes */ - -/* space for some demangled protocol names */ -.space 1024 - -/* space for protocolopt, smax/capacity=16384, blen/mask=8191+1 */ -.space 4*(8+256) /* header and scramble */ -.space 8192 /* mask tab */ -.space 16384 /* checkbytes */ -.space 16384*8 /* offsets */ - -/* space for 2048 header_info (RO) structures */ -.space 8 + (2048*16) - - -.section __DATA,__objc_opt_rw -.align 3 -.private_extern __objc_opt_rw_data -__objc_opt_rw_data: - -/* space for 2048 header_info (RW) structures */ -.space 8 + (2048*8) - -/* space for 16384 protocols */ -#if __LP64__ -.space 16384 * 12 * 8 -#else -.space 16384 * 12 * 4 -#endif - /* section of pointers that the shared cache optimizer wants to know about */ .section __DATA,__objc_opt_ptrs diff --git a/runtime/objc-sel.mm b/runtime/objc-sel.mm index 55d57eb..27ee356 100644 --- a/runtime/objc-sel.mm +++ b/runtime/objc-sel.mm @@ -25,16 +25,15 @@ #include "objc-private.h" #include "objc-cache.h" +#include "DenseMapExtras.h" #if SUPPORT_PREOPT static const objc_selopt_t *builtins = NULL; +static bool useDyldSelectorLookup = false; #endif -static size_t SelrefCount = 0; - -static NXMapTable *namedSelectors; - +static objc::ExplicitInitDenseSet namedSelectors; static SEL search_builtins(const char *key); @@ -44,11 +43,17 @@ **********************************************************************/ void sel_init(size_t selrefCount) { - // save this value for later - SelrefCount = selrefCount; - #if SUPPORT_PREOPT - builtins = preoptimizedSelectors(); + // If dyld finds a known shared cache selector, then it must be also looking + // in the shared cache table. + if (_dyld_get_objc_selector("retain") != nil) + useDyldSelectorLookup = true; + else + builtins = preoptimizedSelectors(); + + if (PrintPreopt && useDyldSelectorLookup) { + _objc_inform("PREOPTIMIZATION: using dyld selector opt"); + } if (PrintPreopt && builtins) { uint32_t occupied = builtins->occupied; @@ -59,39 +64,18 @@ void sel_init(size_t selrefCount) _objc_inform("PREOPTIMIZATION: %u/%u (%u%%) hash table occupancy", occupied, capacity, (unsigned)(occupied/(double)capacity*100)); - } + } + namedSelectors.init(useDyldSelectorLookup ? 0 : (unsigned)selrefCount); +#else + namedSelectors.init((unsigned)selrefCount); #endif // Register selectors used by libobjc -#define s(x) SEL_##x = sel_registerNameNoLock(#x, NO) -#define t(x,y) SEL_##y = sel_registerNameNoLock(#x, NO) - mutex_locker_t lock(selLock); - s(load); - s(initialize); - t(resolveInstanceMethod:, resolveInstanceMethod); - t(resolveClassMethod:, resolveClassMethod); - t(.cxx_construct, cxx_construct); - t(.cxx_destruct, cxx_destruct); - s(retain); - s(release); - s(autorelease); - s(retainCount); - s(alloc); - t(allocWithZone:, allocWithZone); - s(dealloc); - s(copy); - s(new); - t(forwardInvocation:, forwardInvocation); - t(_tryRetain, tryRetain); - t(_isDeallocating, isDeallocating); - s(retainWeakReference); - s(allowsWeakReference); - -#undef s -#undef t + SEL_cxx_construct = sel_registerNameNoLock(".cxx_construct", NO); + SEL_cxx_destruct = sel_registerNameNoLock(".cxx_destruct", NO); } @@ -118,17 +102,25 @@ BOOL sel_isMapped(SEL sel) if (sel == search_builtins(name)) return YES; mutex_locker_t lock(selLock); - if (namedSelectors) { - return (sel == (SEL)NXMapGet(namedSelectors, name)); - } - return false; + auto it = namedSelectors.get().find(name); + return it != namedSelectors.get().end() && (SEL)*it == sel; } static SEL search_builtins(const char *name) { #if SUPPORT_PREOPT - if (builtins) return (SEL)builtins->get(name); + if (builtins) { + SEL result = 0; + if ((result = (SEL)builtins->get(name))) + return result; + + if ((result = (SEL)_dyld_get_objc_selector(name))) + return result; + } else if (useDyldSelectorLookup) { + if (SEL result = (SEL)_dyld_get_objc_selector(name)) + return result; + } #endif return nil; } @@ -147,24 +139,12 @@ static SEL __sel_registerName(const char *name, bool shouldLock, bool copy) if (result) return result; conditional_mutex_locker_t lock(selLock, shouldLock); - if (namedSelectors) { - result = (SEL)NXMapGet(namedSelectors, name); - } - if (result) return result; - - // No match. Insert. - - if (!namedSelectors) { - namedSelectors = NXCreateMapTable(NXStrValueMapPrototype, - (unsigned)SelrefCount); - } - if (!result) { - result = sel_alloc(name, copy); - // fixme choose a better container (hash not map for starters) - NXMapInsert(namedSelectors, sel_getName(result), result); - } - - return result; + auto it = namedSelectors.get().insert(name); + if (it.second) { + // No match. Insert. + *it.first = (const char *)sel_alloc(name, copy); + } + return (SEL)*it.first; } diff --git a/runtime/objc-sync.mm b/runtime/objc-sync.mm index d5dd019..d29a822 100644 --- a/runtime/objc-sync.mm +++ b/runtime/objc-sync.mm @@ -287,7 +287,7 @@ int objc_sync_enter(id obj) if (obj) { SyncData* data = id2data(obj, ACQUIRE); - assert(data); + ASSERT(data); data->mutex.lock(); } else { // @synchronized(nil) does nothing @@ -300,6 +300,25 @@ int objc_sync_enter(id obj) return result; } +BOOL objc_sync_try_enter(id obj) +{ + BOOL result = YES; + + if (obj) { + SyncData* data = id2data(obj, ACQUIRE); + ASSERT(data); + result = data->mutex.tryLock(); + } else { + // @synchronized(nil) does nothing + if (DebugNilSync) { + _objc_inform("NIL SYNC DEBUG: @synchronized(nil); set a breakpoint on objc_sync_nil to debug"); + } + objc_sync_nil(); + } + + return result; +} + // End synchronizing on 'obj'. // Returns OBJC_SYNC_SUCCESS or OBJC_SYNC_NOT_OWNING_THREAD_ERROR diff --git a/runtime/objc-weak.mm b/runtime/objc-weak.mm index 3dd6d0a..4d9c43d 100644 --- a/runtime/objc-weak.mm +++ b/runtime/objc-weak.mm @@ -76,7 +76,7 @@ static inline uintptr_t w_hash_pointer(objc_object **key) { static void grow_refs_and_insert(weak_entry_t *entry, objc_object **new_referrer) { - assert(entry->out_of_line()); + ASSERT(entry->out_of_line()); size_t old_size = TABLE_SIZE(entry); size_t new_size = old_size ? old_size * 2 : 8; @@ -135,7 +135,7 @@ static void append_referrer(weak_entry_t *entry, objc_object **new_referrer) entry->max_hash_displacement = 0; } - assert(entry->out_of_line()); + ASSERT(entry->out_of_line()); if (entry->num_refs >= TABLE_SIZE(entry) * 3/4) { return grow_refs_and_insert(entry, new_referrer); @@ -211,7 +211,7 @@ static void remove_referrer(weak_entry_t *entry, objc_object **old_referrer) static void weak_entry_insert(weak_table_t *weak_table, weak_entry_t *new_entry) { weak_entry_t *weak_entries = weak_table->weak_entries; - assert(weak_entries != nil); + ASSERT(weak_entries != nil); size_t begin = hash_pointer(new_entry->referent) & (weak_table->mask); size_t index = begin; @@ -308,7 +308,7 @@ static void weak_entry_remove(weak_table_t *weak_table, weak_entry_t *entry) static weak_entry_t * weak_entry_for_referent(weak_table_t *weak_table, objc_object *referent) { - assert(referent); + ASSERT(referent); weak_entry_t *weak_entries = weak_table->weak_entries; @@ -405,12 +405,12 @@ static void weak_entry_remove(weak_table_t *weak_table, weak_entry_t *entry) BOOL (*allowsWeakReference)(objc_object *, SEL) = (BOOL(*)(objc_object *, SEL)) object_getMethodImplementation((id)referent, - SEL_allowsWeakReference); + @selector(allowsWeakReference)); if ((IMP)allowsWeakReference == _objc_msgForward) { return nil; } deallocating = - ! (*allowsWeakReference)(referent, SEL_allowsWeakReference); + ! (*allowsWeakReference)(referent, @selector(allowsWeakReference)); } if (deallocating) { diff --git a/runtime/objc.h b/runtime/objc.h index ab6f0dd..6a73568 100644 --- a/runtime/objc.h +++ b/runtime/objc.h @@ -67,7 +67,7 @@ typedef id _Nullable (*IMP)(id _Nonnull, SEL _Nonnull, ...); # endif #else // __OBJC_BOOL_IS_BOOL not set. -# if TARGET_OS_OSX || TARGET_OS_IOSMAC || (TARGET_OS_IOS && !__LP64__ && !__ARM_ARCH_7K) +# if TARGET_OS_OSX || TARGET_OS_IOSMAC || ((TARGET_OS_IOS || TARGET_OS_BRIDGE) && !__LP64__ && !__ARM_ARCH_7K) # define OBJC_BOOL_IS_BOOL 0 # else # define OBJC_BOOL_IS_BOOL 1 diff --git a/runtime/runtime.h b/runtime/runtime.h index 1542d03..c97129b 100644 --- a/runtime/runtime.h +++ b/runtime/runtime.h @@ -425,11 +425,14 @@ class_getSuperclass(Class _Nullable cls) */ OBJC_EXPORT Class _Nonnull class_setSuperclass(Class _Nonnull cls, Class _Nonnull newSuper) - __OSX_DEPRECATED(10.5, 10.5, "not recommended") - __IOS_DEPRECATED(2.0, 2.0, "not recommended") - __TVOS_DEPRECATED(9.0, 9.0, "not recommended") + __OSX_DEPRECATED(10.5, 10.5, "not recommended") + __IOS_DEPRECATED(2.0, 2.0, "not recommended") + __TVOS_DEPRECATED(9.0, 9.0, "not recommended") __WATCHOS_DEPRECATED(1.0, 1.0, "not recommended") - __BRIDGEOS_DEPRECATED(2.0, 2.0, "not recommended"); +#ifndef __APPLE_BLEACH_SDK__ + __BRIDGEOS_DEPRECATED(2.0, 2.0, "not recommended") +#endif +; /** * Returns the version number of a class definition. @@ -1357,7 +1360,7 @@ protocol_copyPropertyList2(Protocol * _Nonnull proto, * * @return A C array of protocols adopted by \e proto. The array contains \e *outCount pointers * followed by a \c NULL terminator. You must free the array with \c free(). - * If the protocol declares no properties, \c NULL is returned and \c *outCount is \c 0. + * If the protocol adopts no other protocols, \c NULL is returned and \c *outCount is \c 0. */ OBJC_EXPORT Protocol * __unsafe_unretained _Nonnull * _Nullable protocol_copyProtocolList(Protocol * _Nonnull proto, @@ -1761,10 +1764,73 @@ typedef BOOL (*objc_hook_getClass)(const char * _Nonnull name, Class _Nullable * #define OBJC_GETCLASSHOOK_DEFINED 1 OBJC_EXPORT void objc_setHook_getClass(objc_hook_getClass _Nonnull newValue, objc_hook_getClass _Nullable * _Nonnull outOldValue) - OBJC_AVAILABLE(10.14, 12.0, 12.0, 5.0, 3.0); -// rdar://44986431 fixme correct availability for _objc_realizeClassFromSwift + OBJC_AVAILABLE(10.14.4, 12.2, 12.2, 5.2, 3.2); #endif +/** + * Function type for a hook that assists objc_setAssociatedObject(). + * + * @param object The source object for the association. + * @param key The key for the association. + * @param value The value to associate with the key key for object. Pass nil to clear an existing association. + * @param policy The policy for the association. For possible values, see “Associative Object Behaviors.” + * + * @see objc_setAssociatedObject + * @see objc_setHook_setAssociatedObject + */ +typedef void (*objc_hook_setAssociatedObject)(id _Nonnull object, const void * _Nonnull key, + id _Nullable value, objc_AssociationPolicy policy); + +/** + * Install a hook for objc_setAssociatedObject(). + * + * @param newValue The hook function to install. + * @param outOldValue The address of a function pointer variable. On return, + * the old hook function is stored in the variable. + * + * @note The store to *outOldValue is thread-safe: the variable will be + * updated before objc_setAssociatedObject() calls your new hook to read it, + * even if your new hook is called from another thread before this + * setter completes. + * @note Your hook should always call the previous hook. + * + * @see objc_setAssociatedObject + * @see objc_hook_setAssociatedObject + */ +#if !(TARGET_OS_OSX && __i386__) +#define OBJC_SETASSOCIATEDOBJECTHOOK_DEFINED 1 +OBJC_EXPORT void objc_setHook_setAssociatedObject(objc_hook_setAssociatedObject _Nonnull newValue, + objc_hook_setAssociatedObject _Nullable * _Nonnull outOldValue) + OBJC_AVAILABLE(10.15, 13.0, 13.0, 6.0, 4.0); +#endif + +/** + * Function type for a function that is called when an image is loaded. + * + * @param header The newly loaded header. + */ +struct mach_header; +typedef void (*objc_func_loadImage)(const struct mach_header * _Nonnull header); + +/** + * Add a function to be called when a new image is loaded. The function is + * called after ObjC has scanned and fixed up the image. It is called + * BEFORE +load methods are invoked. + * + * When adding a new function, that function is immediately called with all + * images that are currently loaded. It is then called as needed for images + * that are loaded afterwards. + * + * Note: the function is called with ObjC's internal runtime lock held. + * Be VERY careful with what the function does to avoid deadlocks or + * poor performance. + * + * @param func The function to add. + */ +#define OBJC_ADDLOADIMAGEFUNC_DEFINED 1 +OBJC_EXPORT void objc_addLoadImageFunc(objc_func_loadImage _Nonnull func) + OBJC_AVAILABLE(10.15, 13.0, 13.0, 6.0, 4.0); + /** * Callback from Objective-C to Swift to perform Swift class initialization. */ @@ -1783,8 +1849,7 @@ typedef Class _Nullable #define OBJC_REALIZECLASSFROMSWIFT_DEFINED 1 OBJC_EXPORT Class _Nullable _objc_realizeClassFromSwift(Class _Nullable cls, void * _Nullable previously) - OBJC_AVAILABLE(10.14, 12.0, 12.0, 5.0, 3.0); -// rdar://44986431 fixme correct availability for _objc_realizeClassFromSwift + OBJC_AVAILABLE(10.14.4, 12.2, 12.2, 5.2, 3.2); #endif @@ -1971,33 +2036,39 @@ struct objc_method_list; OBJC_EXPORT IMP _Nullable class_lookupMethod(Class _Nullable cls, SEL _Nonnull sel) - __OSX_DEPRECATED(10.0, 10.5, "use class_getMethodImplementation instead") - __IOS_DEPRECATED(2.0, 2.0, "use class_getMethodImplementation instead") - __TVOS_DEPRECATED(9.0, 9.0, "use class_getMethodImplementation instead") + __OSX_DEPRECATED(10.0, 10.5, "use class_getMethodImplementation instead") + __IOS_DEPRECATED(2.0, 2.0, "use class_getMethodImplementation instead") + __TVOS_DEPRECATED(9.0, 9.0, "use class_getMethodImplementation instead") __WATCHOS_DEPRECATED(1.0, 1.0, "use class_getMethodImplementation instead") - __BRIDGEOS_DEPRECATED(2.0, 2.0, "use class_getMethodImplementation instead"); +#ifndef __APPLE_BLEACH_SDK__ + __BRIDGEOS_DEPRECATED(2.0, 2.0, "use class_getMethodImplementation instead") +#endif +; OBJC_EXPORT BOOL class_respondsToMethod(Class _Nullable cls, SEL _Nonnull sel) - __OSX_DEPRECATED(10.0, 10.5, "use class_respondsToSelector instead") - __IOS_DEPRECATED(2.0, 2.0, "use class_respondsToSelector instead") - __TVOS_DEPRECATED(9.0, 9.0, "use class_respondsToSelector instead") + __OSX_DEPRECATED(10.0, 10.5, "use class_respondsToSelector instead") + __IOS_DEPRECATED(2.0, 2.0, "use class_respondsToSelector instead") + __TVOS_DEPRECATED(9.0, 9.0, "use class_respondsToSelector instead") __WATCHOS_DEPRECATED(1.0, 1.0, "use class_respondsToSelector instead") - __BRIDGEOS_DEPRECATED(2.0, 2.0, "use class_respondsToSelector instead"); +#ifndef __APPLE_BLEACH_SDK__ + __BRIDGEOS_DEPRECATED(2.0, 2.0, "use class_respondsToSelector instead") +#endif +; OBJC_EXPORT void _objc_flush_caches(Class _Nullable cls) - __OSX_DEPRECATED(10.0, 10.5, "not recommended") - __IOS_DEPRECATED(2.0, 2.0, "not recommended") - __TVOS_DEPRECATED(9.0, 9.0, "not recommended") + __OSX_DEPRECATED(10.0, 10.5, "not recommended") + __IOS_DEPRECATED(2.0, 2.0, "not recommended") + __TVOS_DEPRECATED(9.0, 9.0, "not recommended") __WATCHOS_DEPRECATED(1.0, 1.0, "not recommended") - __BRIDGEOS_DEPRECATED(2.0, 2.0, "not recommended"); +#ifndef __APPLE_BLEACH_SDK__ + __BRIDGEOS_DEPRECATED(2.0, 2.0, "not recommended") +#endif +; OBJC_EXPORT id _Nullable object_copyFromZone(id _Nullable anObject, size_t nBytes, void * _Nullable z) - __OSX_DEPRECATED(10.0, 10.5, "use object_copy instead") - __IOS_UNAVAILABLE __TVOS_UNAVAILABLE - __WATCHOS_UNAVAILABLE __BRIDGEOS_UNAVAILABLE - OBJC_ARC_UNAVAILABLE; + OBJC_OSX_DEPRECATED_OTHERS_UNAVAILABLE(10.0, 10.5, "use object_copy instead"); OBJC_EXPORT id _Nullable object_realloc(id _Nullable anObject, size_t nBytes) @@ -2027,10 +2098,7 @@ objc_setMultithreaded(BOOL flag) OBJC_EXPORT id _Nullable class_createInstanceFromZone(Class _Nullable, size_t idxIvars, void * _Nullable z) - __OSX_DEPRECATED(10.0, 10.5, "use class_createInstance instead") - __IOS_UNAVAILABLE __TVOS_UNAVAILABLE - __WATCHOS_UNAVAILABLE __BRIDGEOS_UNAVAILABLE - OBJC_ARC_UNAVAILABLE; + OBJC_OSX_DEPRECATED_OTHERS_UNAVAILABLE(10.0, 10.5, "use class_createInstance instead"); OBJC_EXPORT void class_addMethods(Class _Nullable, struct objc_method_list * _Nonnull) diff --git a/test/addMethods.m b/test/addMethods.m index 953ada4..022ee95 100644 --- a/test/addMethods.m +++ b/test/addMethods.m @@ -7,7 +7,8 @@ // Macros for array construction. // ten IMPs -#define IMPS10 fn0, fn1, fn2, fn3, fn4, fn5, fn6, fn7, fn8, fn9 +#define IMPS10 (IMP)fn0, (IMP)fn1, (IMP)fn2, (IMP)fn3, (IMP)fn4, \ + (IMP)fn5, (IMP)fn6, (IMP)fn7, (IMP)fn8, (IMP)fn9 // ten method types #define TYPES10 "", "", "", "", "", "", "", "", "", "" // ten selectors of the form name0..name9 @@ -182,11 +183,11 @@ int main() // similar to dummyIMPs but with different values in each slot IMP dummyIMPs2[130] = { - fn5, fn6, fn7, fn8, fn9, + (IMP)fn5, (IMP)fn6, (IMP)fn7, (IMP)fn8, (IMP)fn9, IMPS10, IMPS10, IMPS10, IMPS10, IMPS10, IMPS10, IMPS10, IMPS10, IMPS10, IMPS10, IMPS10, IMPS10, - fn0, fn1, fn2, fn3, fn4, + (IMP)fn0, (IMP)fn1, (IMP)fn2, (IMP)fn3, (IMP)fn4, }; const char *dummyTypes[130] = { diff --git a/test/association.m b/test/association.m index c297421..e148fc5 100644 --- a/test/association.m +++ b/test/association.m @@ -120,7 +120,7 @@ int main() // rdar://44094390 tolerate nil object and nil value #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wnonnull" - objc_setAssociatedObject(nil, &key, nil, 0); + objc_setAssociatedObject(nil, &key, nil, OBJC_ASSOCIATION_ASSIGN); #pragma clang diagnostic pop succeed(__FILE__); diff --git a/test/badCache.m b/test/badCache.m index 08daf77..8b7aa2a 100644 --- a/test/badCache.m +++ b/test/badCache.m @@ -45,7 +45,7 @@ int main() }; struct cache_t { - struct bucket_t *buckets; + uintptr_t buckets; mask_t mask; mask_t occupied; }; @@ -66,8 +66,26 @@ int main() [obj self]; struct cache_t *cache = &((__bridge struct class_t *)cls)->cache; - + + // Figure out which cache mask scheme is in use by examining the existing bits. + int low4 = 0; +#if __LP64__ + int top16 = 0; +#endif + int outlined = 0; + + if (cache->buckets & 0xf) { + low4 = 1; +#if __LP64__ + } else if ((cache->buckets & (0xffffULL << 48))) { + top16 = 1; +#endif + } else { + outlined = 1; + } + # define COUNT 4 +# define COUNTSHIFT 14 struct bucket_t *buckets = (struct bucket_t *)calloc(sizeof(struct bucket_t), COUNT+1); for (int i = 0; i < COUNT; i++) { buckets[i].sel = ~0; @@ -76,10 +94,19 @@ int main() buckets[COUNT].sel = 1; buckets[COUNT].imp = (uintptr_t)buckets; - cache->mask = COUNT-1; - cache->occupied = 0; - cache->buckets = buckets; + if (low4) { + cache->buckets = (uintptr_t)buckets | COUNTSHIFT; +#if __LP64__ + } else if (top16) { + cache->buckets = ((uintptr_t)(COUNT - 1) << 48) | (uintptr_t)buckets; +#endif + } else if (outlined) { + cache->mask = COUNT-1; + cache->buckets = (uintptr_t)buckets; + } + cache->occupied = 0; + fprintf(stderr, "crash now\n"); [obj self]; diff --git a/test/bool.c b/test/bool.c index edfe1fa..c12cc32 100644 --- a/test/bool.c +++ b/test/bool.c @@ -6,7 +6,7 @@ #if TARGET_OS_OSX # define RealBool 0 -#elif TARGET_OS_IOS +#elif TARGET_OS_IOS || TARGET_OS_BRIDGE # if (__arm__ && !__armv7k__) || __i386__ # define RealBool 0 # else diff --git a/test/category.m b/test/category.m index f8cd3a9..80795e2 100644 --- a/test/category.m +++ b/test/category.m @@ -5,6 +5,12 @@ #include #include +#if __LP64__ +# define PTR " .quad " +#else +# define PTR " .long " +#endif + static int state = 0; @interface Super : TestRoot @end @@ -110,6 +116,57 @@ @implementation PropertyClass (PropertyCategory) @end +// Manually build a category that goes in __objc_catlist2. +#if __has_feature(ptrauth_calls) +#define SIGNED_CATEGORY_IMP "@AUTH(ia,0,addr)" +#else +#define SIGNED_CATEGORY_IMP +#endif +asm( +" .section __DATA,__objc_const \n" +"L_catlist2CategoryName: \n" +" .asciz \"Category_catlist2\" \n" +"L_catlist2MethodString: \n" +" .asciz \"catlist2Method\" \n" +"L_catlist2MethodTypes: \n" +" .asciz \"i16@0:8\" \n" + +" .p2align 3 \n" +"l_OBJC_$_CATEGORY_INSTANCE_METHODS_Super_$_Category_catlist2: \n" +" .long 24 \n" +" .long 1 \n" +" "PTR" L_catlist2MethodString \n" +" "PTR" L_catlist2MethodTypes \n" +" "PTR" _catlist2MethodImplementation"SIGNED_CATEGORY_IMP" \n" + +" .p2align 3 \n" +"l_OBJC_$_CATEGORY_Super_$_Category_catlist2: \n" +" "PTR" L_catlist2CategoryName \n" +" "PTR" _OBJC_CLASS_$_Super \n" +" "PTR" l_OBJC_$_CATEGORY_INSTANCE_METHODS_Super_$_Category_catlist2 \n" +" "PTR" 0 \n" +" "PTR" 0 \n" +" "PTR" 0 \n" +" "PTR" 0 \n" +" .long 64 \n" +" .space 4 \n" + +" .section __DATA,__objc_catlist2 \n" +" .p2align 3 \n" +" "PTR" l_OBJC_$_CATEGORY_Super_$_Category_catlist2 \n" + +" .text \n" +); + +@interface Super (Category_catlist2) +- (int)catlist2Method; +@end + +EXTERN_C int catlist2MethodImplementation(id self __unused, SEL _cmd __unused) { + return 0; +} + + int main() { { @@ -164,6 +221,9 @@ int main() testassert(!plist[2]); free(plist); + // method introduced by category in catlist2 + testassert([[Super new] catlist2Method] == 0); + succeed(__FILE__); } diff --git a/test/classpair.m b/test/classpair.mm similarity index 94% rename from test/classpair.m rename to test/classpair.mm index d1346f1..1ce76ac 100644 --- a/test/classpair.m +++ b/test/classpair.mm @@ -31,11 +31,25 @@ +(void) classMethod2; @end static int super_initialize; +static int super_cxxctor; +static int super_cxxdtor; + +struct super_cxx { + int foo; + super_cxx() : foo(0) { + super_cxxctor++; + } + ~super_cxx() { + super_cxxdtor++; + } +}; @interface Super : TestRoot @property int superProp; @end -@implementation Super +@implementation Super { + super_cxx _foo; +} @dynamic superProp; +(void)initialize { super_initialize++; } @@ -227,12 +241,18 @@ static void cycle(void) // put instance tests on a separate thread so they // are reliably deallocated before class destruction testonthread(^{ + super_cxxctor = 0; + super_cxxdtor = 0; id obj = [cls new]; + testassert(super_cxxctor == 1); + testassert(super_cxxdtor == 0); state = 0; [obj instanceMethod]; [obj instanceMethod2]; testassert(state == 2); RELEASE_VAR(obj); + testassert(super_cxxctor == 1); + testassert(super_cxxdtor == 1); }); // Test ivar layouts of sub-subclass diff --git a/test/copyFixupHandler.mm b/test/copyFixupHandler.mm new file mode 100644 index 0000000..c2c5c64 --- /dev/null +++ b/test/copyFixupHandler.mm @@ -0,0 +1,89 @@ +// TEST_CONFIG MEM=mrc + +#define TEST_CALLS_OPERATOR_NEW +#include "test.h" +#include "testroot.i" +#include "swift-class-def.m" + +#include + +#include + +static Class expectedOldClass; + +static std::vector observedNewClasses1; +static void handler1(Class _Nonnull oldClass, Class _Nonnull newClass) { + testprintf("%s(%p, %p)", __func__, oldClass, newClass); + testassert(oldClass == expectedOldClass); + observedNewClasses1.push_back(newClass); +} + +static std::vector observedNewClasses2; +static void handler2(Class _Nonnull oldClass, Class _Nonnull newClass) { + testprintf("%s(%p, %p)", __func__, oldClass, newClass); + testassert(oldClass == expectedOldClass); + observedNewClasses2.push_back(newClass); +} + +static std::vector observedNewClasses3; +static void handler3(Class _Nonnull oldClass, Class _Nonnull newClass) { + testprintf("%s(%p, %p)", __func__, oldClass, newClass); + testassert(oldClass == expectedOldClass); + observedNewClasses3.push_back(newClass); +} + +EXTERN_C Class _objc_realizeClassFromSwift(Class, void *); + +EXTERN_C Class init(Class cls, void *arg) { + (void)arg; + _objc_realizeClassFromSwift(cls, cls); + return cls; +} + +@interface SwiftRoot: TestRoot @end +SWIFT_CLASS(SwiftRoot, TestRoot, init); + +int main() +{ + expectedOldClass = [SwiftRoot class]; + Class A = objc_allocateClassPair([RawSwiftRoot class], "A", 0); + objc_registerClassPair(A); + testassert(observedNewClasses1.size() == 0); + testassert(observedNewClasses2.size() == 0); + testassert(observedNewClasses3.size() == 0); + + _objc_setClassCopyFixupHandler(handler1); + + expectedOldClass = A; + Class B = objc_allocateClassPair(A, "B", 0); + objc_registerClassPair(B); + testassert(observedNewClasses1.size() == 2); + testassert(observedNewClasses2.size() == 0); + testassert(observedNewClasses3.size() == 0); + testassert(observedNewClasses1[0] == B); + + _objc_setClassCopyFixupHandler(handler2); + + expectedOldClass = B; + Class C = objc_allocateClassPair(B, "C", 0); + objc_registerClassPair(C); + testassert(observedNewClasses1.size() == 4); + testassert(observedNewClasses2.size() == 2); + testassert(observedNewClasses3.size() == 0); + testassert(observedNewClasses1[2] == C); + testassert(observedNewClasses2[0] == C); + + _objc_setClassCopyFixupHandler(handler3); + + expectedOldClass = C; + Class D = objc_allocateClassPair(C, "D", 0); + objc_registerClassPair(D); + testassert(observedNewClasses1.size() == 6); + testassert(observedNewClasses2.size() == 4); + testassert(observedNewClasses3.size() == 2); + testassert(observedNewClasses1[4] == D); + testassert(observedNewClasses2[2] == D); + testassert(observedNewClasses3[0] == D); + + succeed(__FILE__); +} diff --git a/test/copyProtocolList.m b/test/copyProtocolList.m new file mode 100644 index 0000000..5543e50 --- /dev/null +++ b/test/copyProtocolList.m @@ -0,0 +1,69 @@ +// TEST_CONFIG + +#include "test.h" +#include +#include +#include + +@protocol Proto1 ++(id)proto1ClassMethod; +-(id)proto1InstanceMethod; +@end + +void noNullEntries(Protocol * _Nonnull __unsafe_unretained * _Nullable protolist, + unsigned int count) +{ + for (unsigned int i = 0; i != count; ++i) { + testassert(protolist[i]); + testassert(protocol_getName(protolist[i])); + testprintf("Protocol[%d/%d]: %p %s\n", i, count, protolist[i], protocol_getName(protolist[i])); + } +} + +Protocol* getProtocol(Protocol * _Nonnull __unsafe_unretained * _Nullable protolist, + unsigned int count, const char* name) { + for (unsigned int i = 0; i != count; ++i) { + if (!strcmp(protocol_getName(protolist[i]), name)) + return protolist[i]; + } + return nil; +} + +int main() +{ + Protocol * _Nonnull __unsafe_unretained * _Nullable protolist; + unsigned int count; + + count = 100; + protolist = objc_copyProtocolList(&count); + testassert(protolist); + testassert(count != 0); + testassert(malloc_size(protolist) >= (count * sizeof(Protocol*))); + noNullEntries(protolist, count); + testassert(protolist[count] == nil); + // Check for a shared cache protocol, ie, the one we know comes from libobjc + testassert(getProtocol(protolist, count, "NSObject")); + // Test for a protocol we know isn't in the cache + testassert(getProtocol(protolist, count, "Proto1") == @protocol(Proto1)); + // Test for a protocol we know isn't there + testassert(!getProtocol(protolist, count, "Proto2")); + free(protolist); + + // Now add it + Protocol* newproto = objc_allocateProtocol("Proto2"); + objc_registerProtocol(newproto); + + Protocol * _Nonnull __unsafe_unretained * _Nullable newProtolist; + unsigned int newCount; + + newCount = 100; + newProtolist = objc_copyProtocolList(&newCount); + testassert(newProtolist); + testassert(newCount == (count + 1)); + testassert(getProtocol(newProtolist, newCount, "Proto2")); + free(newProtolist); + + + succeed(__FILE__); + return 0; +} diff --git a/test/customrr-nsobject-awz.m b/test/customrr-nsobject-awz.m index bda0316..258ce9e 100644 --- a/test/customrr-nsobject-awz.m +++ b/test/customrr-nsobject-awz.m @@ -1,14 +1,14 @@ -/* +/* TEST_CONFIG MEM=mrc -TEST_ENV OBJC_PRINT_CUSTOM_RR=YES OBJC_PRINT_CUSTOM_AWZ=YES +TEST_ENV OBJC_PRINT_CUSTOM_RR=YES OBJC_PRINT_CUSTOM_AWZ=YES OBJC_PRINT_CUSTOM_CORE=YES TEST_BUILD - $C{COMPILE} $DIR/customrr-nsobject.m -o customrr-nsobject-awz.exe -DSWIZZLE_AWZ=1 + $C{COMPILE} $DIR/customrr-nsobject.m -o customrr-nsobject-awz.exe -DSWIZZLE_AWZ=1 -fno-objc-convert-messages-to-runtime-calls END TEST_RUN_OUTPUT -objc\[\d+\]: CUSTOM AWZ: NSObject \(meta\) +objc\[\d+\]: CUSTOM AWZ: NSObject \(meta\) OK: customrr-nsobject-awz.exe END diff --git a/test/customrr-nsobject-core.m b/test/customrr-nsobject-core.m new file mode 100644 index 0000000..f1442f8 --- /dev/null +++ b/test/customrr-nsobject-core.m @@ -0,0 +1,17 @@ +/* + +TEST_CONFIG MEM=mrc +TEST_ENV OBJC_PRINT_CUSTOM_RR=YES OBJC_PRINT_CUSTOM_AWZ=YES OBJC_PRINT_CUSTOM_CORE=YES + +TEST_BUILD + $C{COMPILE} $DIR/customrr-nsobject.m -o customrr-nsobject-core.exe -DSWIZZLE_CORE=1 -fno-objc-convert-messages-to-runtime-calls +END + +TEST_RUN_OUTPUT +objc\[\d+\]: CUSTOM Core: NSObject +objc\[\d+\]: CUSTOM Core: NSObject \(meta\) +OK: customrr-nsobject-core.exe +END + +*/ + diff --git a/test/customrr-nsobject-none.m b/test/customrr-nsobject-none.m index 60bb6ce..90be7fe 100644 --- a/test/customrr-nsobject-none.m +++ b/test/customrr-nsobject-none.m @@ -1,10 +1,10 @@ -/* +/* TEST_CONFIG MEM=mrc -TEST_ENV OBJC_PRINT_CUSTOM_RR=YES OBJC_PRINT_CUSTOM_AWZ=YES +TEST_ENV OBJC_PRINT_CUSTOM_RR=YES OBJC_PRINT_CUSTOM_AWZ=YES OBJC_PRINT_CUSTOM_CORE=YES TEST_BUILD - $C{COMPILE} $DIR/customrr-nsobject.m -o customrr-nsobject-none.exe + $C{COMPILE} $DIR/customrr-nsobject.m -o customrr-nsobject-none.exe -fno-objc-convert-messages-to-runtime-calls END TEST_RUN_OUTPUT diff --git a/test/customrr-nsobject-rr.m b/test/customrr-nsobject-rr.m index 94418f8..2021591 100644 --- a/test/customrr-nsobject-rr.m +++ b/test/customrr-nsobject-rr.m @@ -1,14 +1,14 @@ -/* +/* TEST_CONFIG MEM=mrc -TEST_ENV OBJC_PRINT_CUSTOM_RR=YES OBJC_PRINT_CUSTOM_AWZ=YES +TEST_ENV OBJC_PRINT_CUSTOM_RR=YES OBJC_PRINT_CUSTOM_AWZ=YES OBJC_PRINT_CUSTOM_CORE=YES TEST_BUILD - $C{COMPILE} $DIR/customrr-nsobject.m -o customrr-nsobject-rr.exe -DSWIZZLE_RELEASE=1 + $C{COMPILE} $DIR/customrr-nsobject.m -o customrr-nsobject-rr.exe -DSWIZZLE_RELEASE=1 -fno-objc-convert-messages-to-runtime-calls END TEST_RUN_OUTPUT -objc\[\d+\]: CUSTOM RR: NSObject +objc\[\d+\]: CUSTOM RR: NSObject OK: customrr-nsobject-rr.exe END diff --git a/test/customrr-nsobject-rrawz.m b/test/customrr-nsobject-rrawz.m index 413d973..5e0fa2a 100644 --- a/test/customrr-nsobject-rrawz.m +++ b/test/customrr-nsobject-rrawz.m @@ -1,15 +1,15 @@ -/* +/* TEST_CONFIG MEM=mrc -TEST_ENV OBJC_PRINT_CUSTOM_RR=YES OBJC_PRINT_CUSTOM_AWZ=YES +TEST_ENV OBJC_PRINT_CUSTOM_RR=YES OBJC_PRINT_CUSTOM_AWZ=YES OBJC_PRINT_CUSTOM_CORE=YES TEST_BUILD - $C{COMPILE} $DIR/customrr-nsobject.m -o customrr-nsobject-rrawz.exe -DSWIZZLE_RELEASE=1 -DSWIZZLE_AWZ=1 + $C{COMPILE} $DIR/customrr-nsobject.m -o customrr-nsobject-rrawz.exe -DSWIZZLE_RELEASE=1 -DSWIZZLE_AWZ=1 -fno-objc-convert-messages-to-runtime-calls END TEST_RUN_OUTPUT -objc\[\d+\]: CUSTOM AWZ: NSObject \(meta\) -objc\[\d+\]: CUSTOM RR: NSObject +objc\[\d+\]: CUSTOM AWZ: NSObject \(meta\) +objc\[\d+\]: CUSTOM RR: NSObject OK: customrr-nsobject-rrawz.exe END diff --git a/test/customrr-nsobject.m b/test/customrr-nsobject.m index 57e5b5a..912f414 100644 --- a/test/customrr-nsobject.m +++ b/test/customrr-nsobject.m @@ -2,6 +2,7 @@ #include "test.h" #include +#include #if __has_feature(ptrauth_calls) typedef IMP __ptrauth_objc_method_list_imp MethodListIMP; @@ -16,12 +17,18 @@ static int Allocs; static int AllocWithZones; static int Inits; +static int PlusNew; +static int Self; +static int PlusSelf; id (*RealRetain)(id self, SEL _cmd); void (*RealRelease)(id self, SEL _cmd); id (*RealAutorelease)(id self, SEL _cmd); id (*RealAlloc)(id self, SEL _cmd); id (*RealAllocWithZone)(id self, SEL _cmd, void *zone); +id (*RealPlusNew)(id self, SEL _cmd); +id (*RealSelf)(id self); +id (*RealPlusSelf)(id self); id HackRetain(id self, SEL _cmd) { Retains++; return RealRetain(self, _cmd); } void HackRelease(id self, SEL _cmd) { Releases++; return RealRelease(self, _cmd); } @@ -34,6 +41,10 @@ id HackInit(id self, SEL _cmd __unused) { Inits++; return self; } +id HackPlusNew(id self, SEL _cmd __unused) { PlusNew++; return RealPlusNew(self, _cmd); } +id HackSelf(id self) { Self++; return RealSelf(self); } +id HackPlusSelf(id self) { PlusSelf++; return RealPlusSelf(self); } + int main(int argc __unused, char **argv) { @@ -56,6 +67,30 @@ int main(int argc __unused, char **argv) ((MethodListIMP *)meth)[2] = (IMP)HackAllocWithZone; #endif + meth = class_getClassMethod(cls, @selector(new)); + RealPlusNew = (typeof(RealPlusNew))method_getImplementation(meth); +#if SWIZZLE_CORE + method_setImplementation(meth, (IMP)HackPlusNew); +#else + ((MethodListIMP *)meth)[2] = (IMP)HackPlusNew; +#endif + + meth = class_getClassMethod(cls, @selector(self)); + RealPlusSelf = (typeof(RealPlusSelf))method_getImplementation(meth); +#if SWIZZLE_CORE + method_setImplementation(meth, (IMP)HackPlusSelf); +#else + ((MethodListIMP *)meth)[2] = (IMP)HackPlusSelf; +#endif + + meth = class_getInstanceMethod(cls, @selector(self)); + RealSelf = (typeof(RealSelf))method_getImplementation(meth); +#if SWIZZLE_CORE + method_setImplementation(meth, (IMP)HackSelf); +#else + ((MethodListIMP *)meth)[2] = (IMP)HackSelf; +#endif + meth = class_getInstanceMethod(cls, @selector(release)); RealRelease = (typeof(RealRelease))method_getImplementation(meth); #if SWIZZLE_RELEASE @@ -173,5 +208,24 @@ int main(int argc __unused, char **argv) testassert(Releases == 0); #endif + PlusNew = 0; + Self = 0; + PlusSelf = 0; + Class nso = objc_opt_self([NSObject class]); + obj = objc_opt_new(nso); + obj = objc_opt_self(obj); +#if SWIZZLE_CORE + testprintf("swizzled Core should be called\n"); + testassert(PlusNew == 1); + testassert(Self == 1); + testassert(PlusSelf == 1); +#else + testprintf("unswizzled CORE should be bypassed\n"); + testassert(PlusNew == 0); + testassert(Self == 0); + testassert(PlusSelf == 0); +#endif + testassert([obj isKindOfClass:nso]); + succeed(basename(argv[0])); } diff --git a/test/customrr.m b/test/customrr.m index 8ad8a47..4ebcece 100644 --- a/test/customrr.m +++ b/test/customrr.m @@ -2,7 +2,7 @@ // TEST_CONFIG MEM=mrc /* TEST_BUILD - $C{COMPILE} $DIR/customrr.m -fvisibility=default -o customrr.exe + $C{COMPILE} $DIR/customrr.m -fvisibility=default -o customrr.exe -fno-objc-convert-messages-to-runtime-calls $C{COMPILE} -bundle -bundle_loader customrr.exe $DIR/customrr-cat1.m -o customrr-cat1.bundle $C{COMPILE} -bundle -bundle_loader customrr.exe $DIR/customrr-cat2.m -o customrr-cat2.bundle END @@ -374,12 +374,21 @@ int main(int argc __unused, char **argv) objc_autorelease(obj); testassert(Autoreleases == 0); +#if SUPPORT_NONPOINTER_ISA + objc_retain(cls); + testassert(PlusRetains == 0); + objc_release(cls); + testassert(PlusReleases == 0); + objc_autorelease(cls); + testassert(PlusAutoreleases == 0); +#else objc_retain(cls); testassert(PlusRetains == 1); objc_release(cls); testassert(PlusReleases == 1); objc_autorelease(cls); testassert(PlusAutoreleases == 1); +#endif objc_retain(inh); testassert(Retains == 0); @@ -388,12 +397,21 @@ int main(int argc __unused, char **argv) objc_autorelease(inh); testassert(Autoreleases == 0); +#if SUPPORT_NONPOINTER_ISA + objc_retain(icl); + testassert(PlusRetains == 0); + objc_release(icl); + testassert(PlusReleases == 0); + objc_autorelease(icl); + testassert(PlusAutoreleases == 0); +#else objc_retain(icl); testassert(PlusRetains == 2); objc_release(icl); testassert(PlusReleases == 2); objc_autorelease(icl); testassert(PlusAutoreleases == 2); +#endif objc_retain(ovr); testassert(SubRetains == 1); @@ -409,13 +427,21 @@ int main(int argc __unused, char **argv) objc_autorelease(ocl); testassert(SubPlusAutoreleases == 1); +#if SUPPORT_NONPOINTER_ISA + objc_retain((Class)&OBJC_CLASS_$_UnrealizedSubC1); + testassert(PlusRetains == 1); + objc_release((Class)&OBJC_CLASS_$_UnrealizedSubC2); + testassert(PlusReleases == 1); + objc_autorelease((Class)&OBJC_CLASS_$_UnrealizedSubC3); + testassert(PlusAutoreleases == 1); +#else objc_retain((Class)&OBJC_CLASS_$_UnrealizedSubC1); testassert(PlusRetains == 3); objc_release((Class)&OBJC_CLASS_$_UnrealizedSubC2); testassert(PlusReleases == 3); objc_autorelease((Class)&OBJC_CLASS_$_UnrealizedSubC3); testassert(PlusAutoreleases == 3); - +#endif testprintf("unrelated addMethod does not clobber\n"); zero(); diff --git a/test/customrr2.m b/test/customrr2.m index 935aae6..3f28493 100644 --- a/test/customrr2.m +++ b/test/customrr2.m @@ -2,7 +2,7 @@ // TEST_CONFIG MEM=mrc /* TEST_BUILD - $C{COMPILE} $DIR/customrr.m -fvisibility=default -o customrr2.exe -DTEST_EXCHANGEIMPLEMENTATIONS=1 + $C{COMPILE} $DIR/customrr.m -fvisibility=default -o customrr2.exe -DTEST_EXCHANGEIMPLEMENTATIONS=1 -fno-objc-convert-messages-to-runtime-calls $C{COMPILE} -bundle -bundle_loader customrr2.exe $DIR/customrr-cat1.m -o customrr-cat1.bundle $C{COMPILE} -bundle -bundle_loader customrr2.exe $DIR/customrr-cat2.m -o customrr-cat2.bundle END diff --git a/test/duplicateProtocols.m b/test/duplicateProtocols.m new file mode 100644 index 0000000..1cb5a76 --- /dev/null +++ b/test/duplicateProtocols.m @@ -0,0 +1,42 @@ + +#include "test.h" + +// This test assumes +// - that on launch we don't have NSCoding, NSSecureCoding, or NSDictionary, ie, libSystem doesn't contain those +// - that after dlopening CF, we get NSDictionary and it conforms to NSSecureCoding which conforms to NSCoding +// - that our NSCoding will be used if we ask either NSSecureCoding or NSDictionary if they conform to our test protocol + +@protocol NewNSCodingSuperProto +@end + +@protocol NSCoding +@end + +int main() +{ + // Before we dlopen, make sure we are using our NSCoding, not the shared cache version + Protocol* codingSuperProto = objc_getProtocol("NewNSCodingSuperProto"); + Protocol* codingProto = objc_getProtocol("NSCoding"); + if (@protocol(NewNSCodingSuperProto) != codingSuperProto) fail("Protocol mismatch"); + if (@protocol(NSCoding) != codingProto) fail("Protocol mismatch"); + if (!protocol_conformsToProtocol(codingProto, codingSuperProto)) fail("Our NSCoding should conform to NewNSCodingSuperProto"); + + // Also make sure we don't yet have an NSSecureCoding or NSDictionary + if (objc_getProtocol("NSSecureCoding")) fail("Test assumes we don't have NSSecureCoding yet"); + if (objc_getClass("NSDictionary")) fail("Test assumes we don't have NSDictionary yet"); + + void *dl = dlopen("/System/Library/Frameworks/CoreFoundation.framework/CoreFoundation", RTLD_LAZY); + if (!dl) fail("couldn't open CoreFoundation"); + + // We should now have NSSecureCoding and NSDictionary + Protocol* secureCodingProto = objc_getProtocol("NSSecureCoding"); + id dictionaryClass = objc_getClass("NSDictionary"); + if (!secureCodingProto) fail("Should have got NSSecureCoding from CoreFoundation"); + if (!dictionaryClass) fail("Should have got NSDictionary from CoreFoundation"); + + // Now make sure that NSDictionary and NSSecureCoding find our new protocols + if (!protocol_conformsToProtocol(secureCodingProto, codingProto)) fail("NSSecureCoding should conform to our NSCoding"); + if (!protocol_conformsToProtocol(secureCodingProto, codingSuperProto)) fail("NSSecureCoding should conform to our NewNSCodingSuperProto"); + if (!class_conformsToProtocol(dictionaryClass, codingProto)) fail("NSDictionary should conform to our NSCoding"); + if (!class_conformsToProtocol(dictionaryClass, codingSuperProto)) fail("NSDictionary should conform to our NewNSCodingSuperProto"); +} diff --git a/test/evil-category-0.m b/test/evil-category-0.m deleted file mode 100644 index a7cc36b..0000000 --- a/test/evil-category-0.m +++ /dev/null @@ -1,18 +0,0 @@ -/* -rdar://8553305 - -TEST_BUILD - $C{COMPILE} $DIR/evil-category-0.m -dynamiclib -o libevil.dylib - $C{COMPILE} $DIR/evil-main.m -x none -DNOT_EVIL libevil.dylib -o evil-category-0.exe -END -*/ - -// NOT EVIL version - -#define EVIL_INSTANCE_METHOD 0 -#define EVIL_CLASS_METHOD 0 - -#define OMIT_CAT 0 -#define OMIT_NL_CAT 0 - -#include "evil-category-def.m" diff --git a/test/evil-category-00.m b/test/evil-category-00.m deleted file mode 100644 index 0b1e842..0000000 --- a/test/evil-category-00.m +++ /dev/null @@ -1,24 +0,0 @@ -/* -rdar://8553305 - -TEST_CONFIG OS=iphoneos -TEST_CRASHES - -TEST_BUILD - $C{COMPILE} $DIR/evil-category-00.m $DIR/evil-main.m -o evil-category-00.exe -END - -TEST_RUN_OUTPUT -CRASHED: SIGABRT -END -*/ - -// NOT EVIL version: apps are allowed through (then crash in +load) - -#define EVIL_INSTANCE_METHOD 1 -#define EVIL_CLASS_METHOD 1 - -#define OMIT_CAT 0 -#define OMIT_NL_CAT 0 - -#include "evil-category-def.m" diff --git a/test/evil-category-000.m b/test/evil-category-000.m deleted file mode 100644 index 9e599f9..0000000 --- a/test/evil-category-000.m +++ /dev/null @@ -1,18 +0,0 @@ -/* -rdar://8553305 - -TEST_BUILD - $C{COMPILE} $DIR/evil-category-000.m -dynamiclib -o libevil.dylib - $C{COMPILE} $DIR/evil-main.m -x none -DNOT_EVIL libevil.dylib -o evil-category-000.exe -END -*/ - -// NOT EVIL version: category omitted from all lists - -#define EVIL_INSTANCE_METHOD 1 -#define EVIL_CLASS_METHOD 1 - -#define OMIT_CAT 1 -#define OMIT_NL_CAT 1 - -#include "evil-category-def.m" diff --git a/test/evil-category-1.m b/test/evil-category-1.m deleted file mode 100644 index 7907a88..0000000 --- a/test/evil-category-1.m +++ /dev/null @@ -1,24 +0,0 @@ -/* -rdar://8553305 - -TEST_CONFIG OS=iphoneos -TEST_CRASHES - -TEST_BUILD - $C{COMPILE} $DIR/evil-category-1.m -dynamiclib -o libevil.dylib - $C{COMPILE} $DIR/evil-main.m -x none libevil.dylib -o evil-category-1.exe -END - -TEST_RUN_OUTPUT -objc\[\d+\]: bad method implementation \(0x[0-9a-f]+ at 0x[0-9a-f]+\) -objc\[\d+\]: HALTED -END -*/ - -#define EVIL_INSTANCE_METHOD 1 -#define EVIL_CLASS_METHOD 0 - -#define OMIT_CAT 0 -#define OMIT_NL_CAT 0 - -#include "evil-category-def.m" diff --git a/test/evil-category-2.m b/test/evil-category-2.m deleted file mode 100644 index c719402..0000000 --- a/test/evil-category-2.m +++ /dev/null @@ -1,24 +0,0 @@ -/* -rdar://8553305 - -TEST_CONFIG OS=iphoneos -TEST_CRASHES - -TEST_BUILD - $C{COMPILE} $DIR/evil-category-2.m -dynamiclib -o libevil.dylib - $C{COMPILE} $DIR/evil-main.m -x none libevil.dylib -o evil-category-2.exe -END - -TEST_RUN_OUTPUT -objc\[\d+\]: bad method implementation \(0x[0-9a-f]+ at 0x[0-9a-f]+\) -objc\[\d+\]: HALTED -END -*/ - -#define EVIL_INSTANCE_METHOD 0 -#define EVIL_CLASS_METHOD 1 - -#define OMIT_CAT 0 -#define OMIT_NL_CAT 0 - -#include "evil-category-def.m" diff --git a/test/evil-category-3.m b/test/evil-category-3.m deleted file mode 100644 index 3a7b510..0000000 --- a/test/evil-category-3.m +++ /dev/null @@ -1,24 +0,0 @@ -/* -rdar://8553305 - -TEST_CONFIG OS=iphoneos -TEST_CRASHES - -TEST_BUILD - $C{COMPILE} $DIR/evil-category-3.m -dynamiclib -o libevil.dylib - $C{COMPILE} $DIR/evil-main.m -x none libevil.dylib -o evil-category-3.exe -END - -TEST_RUN_OUTPUT -objc\[\d+\]: bad method implementation \(0x[0-9a-f]+ at 0x[0-9a-f]+\) -objc\[\d+\]: HALTED -END -*/ - -#define EVIL_INSTANCE_METHOD 0 -#define EVIL_CLASS_METHOD 1 - -#define OMIT_CAT 1 -#define OMIT_NL_CAT 0 - -#include "evil-category-def.m" diff --git a/test/evil-category-4.m b/test/evil-category-4.m deleted file mode 100644 index 12c10fa..0000000 --- a/test/evil-category-4.m +++ /dev/null @@ -1,24 +0,0 @@ -/* -rdar://8553305 - -TEST_CONFIG OS=iphoneos -TEST_CRASHES - -TEST_BUILD - $C{COMPILE} $DIR/evil-category-4.m -dynamiclib -o libevil.dylib - $C{COMPILE} $DIR/evil-main.m -x none libevil.dylib -o evil-category-4.exe -END - -TEST_RUN_OUTPUT -objc\[\d+\]: bad method implementation \(0x[0-9a-f]+ at 0x[0-9a-f]+\) -objc\[\d+\]: HALTED -END -*/ - -#define EVIL_INSTANCE_METHOD 0 -#define EVIL_CLASS_METHOD 1 - -#define OMIT_CAT 0 -#define OMIT_NL_CAT 1 - -#include "evil-category-def.m" diff --git a/test/evil-category-def.m b/test/evil-category-def.m deleted file mode 100644 index 6d0f1e0..0000000 --- a/test/evil-category-def.m +++ /dev/null @@ -1,73 +0,0 @@ -#include - -#if __LP64__ -# define PTR " .quad " -#else -# define PTR " .long " -#endif - -#if __has_feature(ptrauth_calls) -# define SIGNED_METHOD_LIST_IMP "@AUTH(ia,0,addr) " -#else -# define SIGNED_METHOD_LIST_IMP -#endif - -#define str(x) #x -#define str2(x) str(x) - -__BEGIN_DECLS -void nop(void) { } -__END_DECLS - -asm( - ".section __DATA,__objc_data \n" - ".align 3 \n" - "L_category: \n" - PTR "L_cat_name \n" - PTR "_OBJC_CLASS_$_NSObject \n" -#if EVIL_INSTANCE_METHOD - PTR "L_evil_methods \n" -#else - PTR "L_good_methods \n" -#endif -#if EVIL_CLASS_METHOD - PTR "L_evil_methods \n" -#else - PTR "L_good_methods \n" -#endif - PTR "0 \n" - PTR "0 \n" - - "L_evil_methods: \n" - ".long 24 \n" - ".long 1 \n" - PTR "L_load \n" - PTR "L_load \n" - PTR "_abort" SIGNED_METHOD_LIST_IMP "\n" - // assumes that abort is inside the dyld shared cache - - "L_good_methods: \n" - ".long 24 \n" - ".long 1 \n" - PTR "L_load \n" - PTR "L_load \n" - PTR "_nop" SIGNED_METHOD_LIST_IMP "\n" - - ".cstring \n" - "L_cat_name: .ascii \"Evil\\0\" \n" - "L_load: .ascii \"load\\0\" \n" - - ".section __DATA,__objc_catlist \n" -#if !OMIT_CAT - PTR "L_category \n" -#endif - - ".section __DATA,__objc_nlcatlist \n" -#if !OMIT_NL_CAT - PTR "L_category \n" -#endif - - ".text \n" - ); - -void fn(void) { } diff --git a/test/evil-class-0.m b/test/evil-class-0.m deleted file mode 100644 index cd71806..0000000 --- a/test/evil-class-0.m +++ /dev/null @@ -1,22 +0,0 @@ -/* -rdar://8553305 - -TEST_BUILD - $C{COMPILE} $DIR/evil-class-0.m -dynamiclib -o libevil.dylib - $C{COMPILE} $DIR/evil-main.m -x none -DNOT_EVIL libevil.dylib -o evil-class-0.exe -END -*/ - -// NOT EVIL version - -#define EVIL_SUPER 0 -#define EVIL_SUPER_META 0 -#define EVIL_SUB 0 -#define EVIL_SUB_META 0 - -#define OMIT_SUPER 0 -#define OMIT_NL_SUPER 0 -#define OMIT_SUB 0 -#define OMIT_NL_SUB 0 - -#include "evil-class-def.m" diff --git a/test/evil-class-00.m b/test/evil-class-00.m deleted file mode 100644 index 1b39407..0000000 --- a/test/evil-class-00.m +++ /dev/null @@ -1,28 +0,0 @@ -/* -rdar://8553305 - -TEST_CONFIG OS=iphoneos -TEST_CRASHES - -TEST_BUILD - $C{COMPILE} $DIR/evil-class-00.m $DIR/evil-main.m -o evil-class-00.exe -END - -TEST_RUN_OUTPUT -CRASHED: SIGABRT -END -*/ - -// NOT EVIL version: apps are allowed through (then crash in +load) - -#define EVIL_SUPER 0 -#define EVIL_SUPER_META 1 -#define EVIL_SUB 0 -#define EVIL_SUB_META 0 - -#define OMIT_SUPER 1 -#define OMIT_NL_SUPER 1 -#define OMIT_SUB 1 -#define OMIT_NL_SUB 0 - -#include "evil-class-def.m" diff --git a/test/evil-class-000.m b/test/evil-class-000.m deleted file mode 100644 index fbf1ce4..0000000 --- a/test/evil-class-000.m +++ /dev/null @@ -1,22 +0,0 @@ -/* -rdar://8553305 - -TEST_BUILD - $C{COMPILE} $DIR/evil-class-000.m -dynamiclib -o libevil.dylib - $C{COMPILE} $DIR/evil-main.m -x none -DNOT_EVIL libevil.dylib -o evil-class-000.exe -END -*/ - -// NOT EVIL version: all classes omitted from all lists - -#define EVIL_SUPER 1 -#define EVIL_SUPER_META 1 -#define EVIL_SUB 1 -#define EVIL_SUB_META 1 - -#define OMIT_SUPER 1 -#define OMIT_NL_SUPER 1 -#define OMIT_SUB 1 -#define OMIT_NL_SUB 1 - -#include "evil-class-def.m" diff --git a/test/evil-class-1.m b/test/evil-class-1.m deleted file mode 100644 index f8785ee..0000000 --- a/test/evil-class-1.m +++ /dev/null @@ -1,28 +0,0 @@ -/* -rdar://8553305 - -TEST_CONFIG OS=iphoneos -TEST_CRASHES - -TEST_BUILD - $C{COMPILE} $DIR/evil-class-1.m -dynamiclib -o libevil.dylib - $C{COMPILE} $DIR/evil-main.m -x none libevil.dylib -o evil-class-1.exe -END - -TEST_RUN_OUTPUT -objc\[\d+\]: bad method implementation \(0x[0-9a-f]+ at 0x[0-9a-f]+\) -objc\[\d+\]: HALTED -END -*/ - -#define EVIL_SUPER 1 -#define EVIL_SUPER_META 0 -#define EVIL_SUB 0 -#define EVIL_SUB_META 0 - -#define OMIT_SUPER 0 -#define OMIT_NL_SUPER 0 -#define OMIT_SUB 0 -#define OMIT_NL_SUB 0 - -#include "evil-class-def.m" diff --git a/test/evil-class-2.m b/test/evil-class-2.m deleted file mode 100644 index 9175eb3..0000000 --- a/test/evil-class-2.m +++ /dev/null @@ -1,28 +0,0 @@ -/* -rdar://8553305 - -TEST_CONFIG OS=iphoneos -TEST_CRASHES - -TEST_BUILD - $C{COMPILE} $DIR/evil-class-2.m -dynamiclib -o libevil.dylib - $C{COMPILE} $DIR/evil-main.m -x none libevil.dylib -o evil-class-2.exe -END - -TEST_RUN_OUTPUT -objc\[\d+\]: bad method implementation \(0x[0-9a-f]+ at 0x[0-9a-f]+\) -objc\[\d+\]: HALTED -END -*/ - -#define EVIL_SUPER 0 -#define EVIL_SUPER_META 1 -#define EVIL_SUB 0 -#define EVIL_SUB_META 0 - -#define OMIT_SUPER 0 -#define OMIT_NL_SUPER 0 -#define OMIT_SUB 0 -#define OMIT_NL_SUB 0 - -#include "evil-class-def.m" diff --git a/test/evil-class-3.m b/test/evil-class-3.m deleted file mode 100644 index c068b34..0000000 --- a/test/evil-class-3.m +++ /dev/null @@ -1,28 +0,0 @@ -/* -rdar://8553305 - -TEST_CONFIG OS=iphoneos -TEST_CRASHES - -TEST_BUILD - $C{COMPILE} $DIR/evil-class-3.m -dynamiclib -o libevil.dylib - $C{COMPILE} $DIR/evil-main.m -x none libevil.dylib -o evil-class-3.exe -END - -TEST_RUN_OUTPUT -objc\[\d+\]: bad method implementation \(0x[0-9a-f]+ at 0x[0-9a-f]+\) -objc\[\d+\]: HALTED -END -*/ - -#define EVIL_SUPER 0 -#define EVIL_SUPER_META 0 -#define EVIL_SUB 1 -#define EVIL_SUB_META 0 - -#define OMIT_SUPER 0 -#define OMIT_NL_SUPER 0 -#define OMIT_SUB 0 -#define OMIT_NL_SUB 0 - -#include "evil-class-def.m" diff --git a/test/evil-class-4.m b/test/evil-class-4.m deleted file mode 100644 index 5189da8..0000000 --- a/test/evil-class-4.m +++ /dev/null @@ -1,28 +0,0 @@ -/* -rdar://8553305 - -TEST_CONFIG OS=iphoneos -TEST_CRASHES - -TEST_BUILD - $C{COMPILE} $DIR/evil-class-4.m -dynamiclib -o libevil.dylib - $C{COMPILE} $DIR/evil-main.m -x none libevil.dylib -o evil-class-4.exe -END - -TEST_RUN_OUTPUT -objc\[\d+\]: bad method implementation \(0x[0-9a-f]+ at 0x[0-9a-f]+\) -objc\[\d+\]: HALTED -END -*/ - -#define EVIL_SUPER 0 -#define EVIL_SUPER_META 0 -#define EVIL_SUB 0 -#define EVIL_SUB_META 1 - -#define OMIT_SUPER 0 -#define OMIT_NL_SUPER 0 -#define OMIT_SUB 0 -#define OMIT_NL_SUB 0 - -#include "evil-class-def.m" diff --git a/test/evil-class-5.m b/test/evil-class-5.m deleted file mode 100644 index 9a78ab4..0000000 --- a/test/evil-class-5.m +++ /dev/null @@ -1,30 +0,0 @@ -/* -rdar://8553305 - -TEST_DISABLED rdar://19200100 - -TEST_CONFIG OS=iphoneos -TEST_CRASHES - -TEST_BUILD - $C{COMPILE} $DIR/evil-class-5.m -dynamiclib -o libevil.dylib - $C{COMPILE} $DIR/evil-main.m -x none libevil.dylib -o evil-class-5.exe -END - -TEST_RUN_OUTPUT -objc\[\d+\]: bad method implementation \(0x[0-9a-f]+ at 0x[0-9a-f]+\) -objc\[\d+\]: HALTED -END -*/ - -#define EVIL_SUPER 0 -#define EVIL_SUPER_META 1 -#define EVIL_SUB 0 -#define EVIL_SUB_META 0 - -#define OMIT_SUPER 1 -#define OMIT_NL_SUPER 1 -#define OMIT_SUB 1 -#define OMIT_NL_SUB 0 - -#include "evil-class-def.m" diff --git a/test/evil-class-def.m b/test/evil-class-def.m index 6c9f25c..c49bda8 100644 --- a/test/evil-class-def.m +++ b/test/evil-class-def.m @@ -101,7 +101,7 @@ "L_ro: \n" ".long 2 \n" ".long 0 \n" - ".long "PTRSIZE" \n" + ".long " PTRSIZE " \n" #if __LP64__ ".long 0 \n" #endif @@ -212,7 +212,7 @@ "L_sub_ro: \n" ".long 2 \n" ".long 0 \n" - ".long "PTRSIZE" \n" + ".long " PTRSIZE " \n" #if __LP64__ ".long 0 \n" #endif @@ -248,7 +248,7 @@ PTR "0 \n" "L_evil_methods: \n" - ".long 3*"PTRSIZE" \n" + ".long 3*" PTRSIZE " \n" ".long 1 \n" PTR "L_load \n" PTR "L_load \n" @@ -256,7 +256,7 @@ // assumes that abort is inside the dyld shared cache "L_good_methods: \n" - ".long 3*"PTRSIZE" \n" + ".long 3*" PTRSIZE " \n" ".long 2 \n" PTR "L_load \n" PTR "L_load \n" @@ -266,27 +266,27 @@ PTR "_nop" SIGNED_METHOD_LIST_IMP "\n" "L_super_ivars: \n" - ".long 4*"PTRSIZE" \n" + ".long 4*" PTRSIZE " \n" ".long 1 \n" PTR "L_super_ivar_offset \n" PTR "L_super_ivar_name \n" PTR "L_super_ivar_type \n" - ".long "LOGPTRSIZE" \n" - ".long "PTRSIZE" \n" + ".long " LOGPTRSIZE " \n" + ".long " PTRSIZE " \n" "L_sub_ivars: \n" - ".long 4*"PTRSIZE" \n" + ".long 4*" PTRSIZE " \n" ".long 1 \n" PTR "L_sub_ivar_offset \n" PTR "L_sub_ivar_name \n" PTR "L_sub_ivar_type \n" - ".long "LOGPTRSIZE" \n" - ".long "PTRSIZE" \n" + ".long " LOGPTRSIZE " \n" + ".long " PTRSIZE " \n" "L_super_ivar_offset: \n" ".long 0 \n" "L_sub_ivar_offset: \n" - ".long "PTRSIZE" \n" + ".long " PTRSIZE " \n" ".cstring \n" "L_super_name: .ascii \"Super\\0\" \n" diff --git a/test/evil-main.m b/test/evil-main.m deleted file mode 100644 index 1e17fb8..0000000 --- a/test/evil-main.m +++ /dev/null @@ -1,15 +0,0 @@ -#include "test.h" - -extern void fn(void); - -int main(int argc __unused, char **argv) -{ - fn(); - -#if TARGET_OS_IPHONE && !TARGET_OS_SIMULATOR && !defined(NOT_EVIL) -#pragma unused (argv) - fail("All that is necessary for the triumph of evil is that good men do nothing."); -#else - succeed(basename(argv[0])); -#endif -} diff --git a/test/gcenforcer-dylib-requiresgc.m b/test/gcenforcer-dylib-requiresgc.m index 67ef3ce..69a4d25 100644 --- a/test/gcenforcer-dylib-requiresgc.m +++ b/test/gcenforcer-dylib-requiresgc.m @@ -10,8 +10,9 @@ dyld: Library not loaded: librequiresgc\.dylib Referenced from: .*gcenforcer-dylib-requiresgc.exe Reason: no suitable image found\. Did find: - .*librequiresgc\.dylib: cannot load '.*librequiresgc\.dylib' because Objective-C garbage collection is not supported - librequiresgc.dylib: cannot load 'librequiresgc\.dylib' because Objective-C garbage collection is not supported + (.*librequiresgc\.dylib: cannot load '.*librequiresgc\.dylib' because Objective-C garbage collection is not supported(\n)?)+ + librequiresgc.dylib: cannot load 'librequiresgc\.dylib' because Objective-C garbage collection is not supported( + .*librequiresgc\.dylib: cannot load '.*librequiresgc\.dylib' because Objective-C garbage collection is not supported(\n)?)* END TEST_BUILD diff --git a/test/gdb.m b/test/gdb.m index dc74635..e2c8b7d 100644 --- a/test/gdb.m +++ b/test/gdb.m @@ -5,6 +5,11 @@ #include #include +#define SwiftV1MangledName4 "_TtC6Swiftt13SwiftV1Class4" +__attribute__((objc_runtime_name(SwiftV1MangledName4))) +@interface SwiftV1Class4 : TestRoot @end +@implementation SwiftV1Class4 @end + int main() { // Class hashes @@ -29,6 +34,11 @@ int main() uintptr_t *maskp = (uintptr_t *)dlsym(RTLD_DEFAULT, "objc_debug_class_rw_data_mask"); testassert(maskp); + + // Raw class names + testassert(strcmp(objc_debug_class_getNameRaw([SwiftV1Class4 class]), SwiftV1MangledName4) == 0); + testassert(strcmp(objc_debug_class_getNameRaw([TestRoot class]), "TestRoot") == 0); + succeed(__FILE__); } diff --git a/test/imports.c b/test/imports.c index 99e46e7..86588fc 100644 --- a/test/imports.c +++ b/test/imports.c @@ -10,6 +10,9 @@ ___cxa_guard_release (C++ function-scope static initializer) ___cxa_atexit (C++ static destructor) weak external (any weak externals, including operators new and delete) +Whitelisted imports: +weak external ____chkstk_darwin (from libSystem) + Disallowed exports (nm -U): __Z* (any C++-mangled export) weak external (any weak externals, including operators new and delete) @@ -20,7 +23,7 @@ fixme rdar://13354718 should disallow anything from libc++ (i.e. not libc++abi) /* TEST_BUILD echo $C{XCRUN} nm -m -arch $C{ARCH} $C{TESTLIB} -$C{XCRUN} nm -u -m -arch $C{ARCH} $C{TESTLIB} | egrep '(weak external| external (___cxa_atexit|___cxa_guard_acquire|___cxa_guard_release))' || true +$C{XCRUN} nm -u -m -arch $C{ARCH} $C{TESTLIB} | grep -v 'weak external ____chkstk_darwin \(from libSystem\)' | egrep '(weak external| external (___cxa_atexit|___cxa_guard_acquire|___cxa_guard_release))' || true $C{XCRUN} nm -U -m -arch $C{ARCH} $C{TESTLIB} | egrep '(weak external| external __Z)' || true $C{COMPILE_C} $DIR/imports.c -o imports.exe END diff --git a/test/include-warnings.c b/test/include-warnings.c index 72990e2..4afdb5f 100644 --- a/test/include-warnings.c +++ b/test/include-warnings.c @@ -1,6 +1,6 @@ /* TEST_BUILD - $C{COMPILE} $DIR/include-warnings.c -o include-warnings.exe -Wsystem-headers -Weverything -Wno-undef -Wno-old-style-cast -Wno-nullability-extension 2>&1 | grep -v 'In file' | grep objc || true + $C{COMPILE} $DIR/include-warnings.c -o include-warnings.exe -Wsystem-headers -Weverything -Wno-undef -Wno-old-style-cast -Wno-nullability-extension -Wno-c++98-compat 2>&1 | grep -v 'In file' | grep objc || true END TEST_RUN_OUTPUT @@ -15,5 +15,7 @@ END // -Wno-old-style-cast is tough to avoid in mixed C/C++ code. // -Wno-nullability-extension disables a warning about non-portable // _Nullable etc which we already handle correctly in objc-abi.h. +// -Wno-c++98-compat disables warnings about things that already +// have guards against C++98. #include "includes.c" diff --git a/test/isaValidation.m b/test/isaValidation.m index 3a00a47..bb26808 100644 --- a/test/isaValidation.m +++ b/test/isaValidation.m @@ -114,7 +114,7 @@ @protocol P extern char **environ; -id dummyIMP(id self, SEL _cmd, ...) { (void)_cmd; return self; } +id dummyIMP(id self, SEL _cmd) { (void)_cmd; return self; } char *dupeName(Class cls) { char *name; @@ -150,8 +150,8 @@ @protocol P TESTCASE(free(class_copyProtocolList(cls, NULL))), TESTCASE(class_getProperty(cls, "x")), TESTCASE(free(class_copyPropertyList(cls, NULL))), - TESTCASE(class_addMethod(cls, @selector(nop), dummyIMP, "v@:")), - TESTCASE(class_replaceMethod(cls, @selector(nop), dummyIMP, "v@:")), + TESTCASE(class_addMethod(cls, @selector(nop), (IMP)dummyIMP, "v@:")), + TESTCASE(class_replaceMethod(cls, @selector(nop), (IMP)dummyIMP, "v@:")), TESTCASE(class_addIvar(cls, "x", sizeof(int), sizeof(int), @encode(int))), TESTCASE(class_addProtocol(cls, @protocol(P))), TESTCASE(class_addProperty(cls, "x", NULL, 0)), diff --git a/test/load-image-notification-dylib.m b/test/load-image-notification-dylib.m new file mode 100644 index 0000000..1495966 --- /dev/null +++ b/test/load-image-notification-dylib.m @@ -0,0 +1,7 @@ +#import "test.h" + +#import + +@interface CLASSNAME: NSObject @end +@implementation CLASSNAME @end + diff --git a/test/load-image-notification.m b/test/load-image-notification.m new file mode 100644 index 0000000..3b0fa72 --- /dev/null +++ b/test/load-image-notification.m @@ -0,0 +1,76 @@ +/* +TEST_BUILD + $C{COMPILE} -DCLASSNAME=Class1 $DIR/load-image-notification-dylib.m -o load-image-notification1.dylib -dynamiclib + $C{COMPILE} -DCLASSNAME=Class2 $DIR/load-image-notification-dylib.m -o load-image-notification2.dylib -dynamiclib + $C{COMPILE} -DCLASSNAME=Class3 $DIR/load-image-notification-dylib.m -o load-image-notification3.dylib -dynamiclib + $C{COMPILE} -DCLASSNAME=Class4 $DIR/load-image-notification-dylib.m -o load-image-notification4.dylib -dynamiclib + $C{COMPILE} -DCLASSNAME=Class5 $DIR/load-image-notification-dylib.m -o load-image-notification5.dylib -dynamiclib + $C{COMPILE} $DIR/load-image-notification.m -o load-image-notification.exe +END +*/ + +#include "test.h" + +#include + +#define ADD_IMAGE_CALLBACK(n) \ +int called ## n = 0; \ +static void add_image ## n(const struct mach_header * mh __unused) { \ + called ## n++; \ +} + +ADD_IMAGE_CALLBACK(1) +ADD_IMAGE_CALLBACK(2) +ADD_IMAGE_CALLBACK(3) +ADD_IMAGE_CALLBACK(4) +ADD_IMAGE_CALLBACK(5) + +int main() +{ + objc_addLoadImageFunc(add_image1); + testassert(called1 > 0); + int oldcalled = called1; + void *handle = dlopen("load-image-notification1.dylib", RTLD_LAZY); + testassert(handle); + testassert(called1 > oldcalled); + + objc_addLoadImageFunc(add_image2); + testassert(called2 == called1); + oldcalled = called1; + handle = dlopen("load-image-notification2.dylib", RTLD_LAZY); + testassert(handle); + testassert(called1 > oldcalled); + testassert(called2 == called1); + + objc_addLoadImageFunc(add_image3); + testassert(called3 == called1); + oldcalled = called1; + handle = dlopen("load-image-notification3.dylib", RTLD_LAZY); + testassert(handle); + testassert(called1 > oldcalled); + testassert(called2 == called1); + testassert(called3 == called1); + + objc_addLoadImageFunc(add_image4); + testassert(called4 == called1); + oldcalled = called1; + handle = dlopen("load-image-notification4.dylib", RTLD_LAZY); + testassert(handle); + testassert(called1 > oldcalled); + testassert(called2 == called1); + testassert(called3 == called1); + testassert(called4 == called1); + + objc_addLoadImageFunc(add_image5); + testassert(called5 == called1); + oldcalled = called1; + handle = dlopen("load-image-notification5.dylib", RTLD_LAZY); + testassert(handle); + testassert(called1 > oldcalled); + testassert(called2 == called1); + testassert(called3 == called1); + testassert(called4 == called1); + testassert(called5 == called1); + + succeed(__FILE__); +} diff --git a/test/methodCacheLeaks.m b/test/methodCacheLeaks.m new file mode 100644 index 0000000..968bf5a --- /dev/null +++ b/test/methodCacheLeaks.m @@ -0,0 +1,65 @@ +// TEST_CONFIG MEM=mrc LANGUAGE=objective-c +/* +TEST_RUN_OUTPUT +[\S\s]*0 leaks for 0 total leaked bytes[\S\s]* +END +*/ + +#include "test.h" +#include "testroot.i" + +#include +#include + +void noopIMP(id self __unused, SEL _cmd __unused) {} + +id test(int n, int methodCount) { + char *name; + asprintf(&name, "TestClass%d", n); + Class c = objc_allocateClassPair([TestRoot class], name, 0); + free(name); + + SEL *sels = malloc(methodCount * sizeof(*sels)); + for(int i = 0; i < methodCount; i++) { + asprintf(&name, "selector%d", i); + sels[i] = sel_getUid(name); + free(name); + } + + for(int i = 0; i < methodCount; i++) { + class_addMethod(c, sels[i], (IMP)noopIMP, "v@:"); + } + + objc_registerClassPair(c); + + id obj = [[c alloc] init]; + for (int i = 0; i < methodCount; i++) { + ((void (*)(id, SEL))objc_msgSend)(obj, sels[i]); + } + free(sels); + return obj; +} + +int main() +{ + int classCount = 16; + id *objs = malloc(classCount * sizeof(*objs)); + for (int i = 0; i < classCount; i++) { + objs[i] = test(i, 1 << i); + } + + char *pidstr; + int result = asprintf(&pidstr, "%u", getpid()); + testassert(result); + + extern char **environ; + char *argv[] = { "/usr/bin/leaks", pidstr, NULL }; + pid_t pid; + result = posix_spawn(&pid, "/usr/bin/leaks", NULL, NULL, argv, environ); + if (result) { + perror("posix_spawn"); + exit(1); + } + wait4(pid, NULL, 0, NULL); + printf("objs=%p\n", objs); +} diff --git a/test/msgSend.m b/test/msgSend.m index 590dcf0..37ba2d8 100644 --- a/test/msgSend.m +++ b/test/msgSend.m @@ -10,6 +10,7 @@ #include "test.h" #include "testroot.i" +#include #include #include #include @@ -1465,6 +1466,10 @@ void break_handler(int sig, siginfo_t *info, void *cc) uintptr_t fnaddr(void *fn) { return (uintptr_t)fn; } #endif +void flushICache(uintptr_t addr) { + sys_icache_invalidate((void *)addr, sizeof(insn_t)); +} + insn_t set(uintptr_t dst, insn_t newvalue) { uintptr_t start = dst & ~(PAGE_MAX_SIZE-1); @@ -1474,6 +1479,7 @@ insn_t set(uintptr_t dst, insn_t newvalue) *(insn_t *)dst = newvalue; err = mprotect((void*)start, PAGE_MAX_SIZE, PROT_READ|PROT_EXEC); if (err) fail("mprotect(%p, R-X) failed (%d)", start, errno); + flushICache(dst); return oldvalue; } @@ -1552,11 +1558,12 @@ void unclobber(void *fn, uintptr_t offset, clobbered_insn_t oldvalue) if (fstat(placeholder, &st) < 0) { fail("couldn't stat asm-placeholder.exe (%d)", errno); } - char *buf = (char *)malloc(st.st_size); - if (pread(placeholder, buf, st.st_size, 0) != st.st_size) { + ssize_t sz = (ssize_t)st.st_size; + char *buf = (char *)malloc(sz); + if (pread(placeholder, buf, sz, 0) != sz) { fail("couldn't read asm-placeholder.exe (%d)", errno); } - if (pwrite(fd, buf, st.st_size, 0) != st.st_size) { + if (pwrite(fd, buf, sz, 0) != sz) { fail("couldn't write asm temp file %s (%d)", tempname, errno); } free(buf); diff --git a/test/nonpointerisa.m b/test/nonpointerisa.m index 673220d..dbe222c 100644 --- a/test/nonpointerisa.m +++ b/test/nonpointerisa.m @@ -130,7 +130,7 @@ +(void)initialize { if (!initialized) { initialized = true; testprintf("Nonpointer during +initialize\n"); - testassert(NONPOINTER(self)); + testassert(!NONPOINTER(self)); id o = [Fake_OS_object new]; check_nonpointer(o, self); [o release]; diff --git a/test/realizedClassGenerationCount.m b/test/realizedClassGenerationCount.m new file mode 100644 index 0000000..72f0882 --- /dev/null +++ b/test/realizedClassGenerationCount.m @@ -0,0 +1,29 @@ +// TEST_CONFIG + +#include "test.h" +#include "testroot.i" + +#include + +extern uintptr_t objc_debug_realized_class_generation_count; + +int main() +{ + testassert(objc_debug_realized_class_generation_count > 0); + uintptr_t prev = objc_debug_realized_class_generation_count; + + void *handle = dlopen("/System/Library/Frameworks/Foundation.framework/Foundation", RTLD_LAZY); + testassert(handle); + Class c = objc_getClass("NSFileManager"); + testassert(c); + testassert(objc_debug_realized_class_generation_count > prev); + + prev = objc_debug_realized_class_generation_count; + c = objc_allocateClassPair([TestRoot class], "Dynamic", 0); + testassert(objc_debug_realized_class_generation_count > prev); + prev = objc_debug_realized_class_generation_count; + objc_registerClassPair(c); + testassert(objc_debug_realized_class_generation_count == prev); + + succeed(__FILE__); +} \ No newline at end of file diff --git a/test/restartableRangesSynchronizeStress.m b/test/restartableRangesSynchronizeStress.m new file mode 100644 index 0000000..0efc7f6 --- /dev/null +++ b/test/restartableRangesSynchronizeStress.m @@ -0,0 +1,78 @@ +// TEST_CONFIG OS=macosx,iphoneos,tvos,watchos + +// This test checks that objc_msgSend's recovery path works correctly. +// It continuously runs msgSend on some background threads, then +// triggers the recovery path constantly as a stress test. + +#include "test.h" +#include "testroot.i" +#include + +struct Big { + uintptr_t a, b, c, d, e, f, g; +}; + +@interface C1: TestRoot +@end +@implementation C1 +- (id)idret { return nil; } +- (double)fpret { return 0.0; } +- (long double)lfpret { return 0.0; } +- (struct Big)stret { return (struct Big){}; } +@end + +@interface C2: C1 +@end +@implementation C2 +- (id)idret { return [super idret]; } +- (double)fpret { return [super fpret]; } +- (long double)lfpret { return [super lfpret]; } +- (struct Big)stret { return [super stret]; } +@end + +EXTERN_C kern_return_t task_restartable_ranges_synchronize(task_t task); + +EXTERN_C void sendWithMsgLookup(id self, SEL _cmd); + +#if defined(__arm64__) && !__has_feature(ptrauth_calls) +asm( +"_sendWithMsgLookup: \n" +" stp fp, lr, [sp, #-16]! \n" +" mov fp, sp \n" +" bl _objc_msgLookup \n" +" mov sp, fp \n" +" ldp fp, lr, [sp], #16 \n" +" br x17 \n" +); +#elif defined(__x86_64__) +asm( +"_sendWithMsgLookup: \n" +" pushq %rbp \n" +" movq %rsp, %rbp \n" +" callq _objc_msgLookup \n" +" popq %rbp \n" +" jmpq *%r11 \n" +); +#else +// Just skip it. +void sendWithMsgLookup(id self __unused, SEL _cmd __unused) {} +#endif + +int main() { + id obj = [C2 new]; + for(int i = 0; i < 2; i++) { + dispatch_async(dispatch_get_global_queue(0, 0), ^{ + while(1) { + [obj idret]; + [obj fpret]; + [obj lfpret]; + [obj stret]; + sendWithMsgLookup(obj, @selector(idret)); + } + }); + } + for(int i = 0; i < 1000000; i++) { + task_restartable_ranges_synchronize(mach_task_self());; + } + succeed(__FILE__); +} diff --git a/test/setAssociatedObjectHook.m b/test/setAssociatedObjectHook.m new file mode 100644 index 0000000..e244d5c --- /dev/null +++ b/test/setAssociatedObjectHook.m @@ -0,0 +1,47 @@ +// TEST_CONFIG + +#include "test.h" +#include "testroot.i" + +id sawObject; +const void *sawKey; +id sawValue; +objc_AssociationPolicy sawPolicy; + +objc_hook_setAssociatedObject originalSetAssociatedObject; + +void hook(id _Nonnull object, const void * _Nonnull key, id _Nullable value, objc_AssociationPolicy policy) { + sawObject = object; + sawKey = key; + sawValue = value; + sawPolicy = policy; + originalSetAssociatedObject(object, key, value, policy); +} + +int main() { + id obj = [TestRoot new]; + id value = [TestRoot new]; + const void *key = "key"; + objc_setAssociatedObject(obj, key, value, OBJC_ASSOCIATION_RETAIN); + testassert(sawObject == nil); + testassert(sawKey == nil); + testassert(sawValue == nil); + testassert(sawPolicy == 0); + + id out = objc_getAssociatedObject(obj, key); + testassert(out == value); + + objc_setHook_setAssociatedObject(hook, &originalSetAssociatedObject); + + key = "key2"; + objc_setAssociatedObject(obj, key, value, OBJC_ASSOCIATION_RETAIN); + testassert(sawObject == obj); + testassert(sawKey == key); + testassert(sawValue == value); + testassert(sawPolicy == OBJC_ASSOCIATION_RETAIN); + + out = objc_getAssociatedObject(obj, key); + testassert(out == value); + + succeed(__FILE__); +} \ No newline at end of file diff --git a/test/swift-class-def.m b/test/swift-class-def.m index 3288ad7..6bc2d05 100644 --- a/test/swift-class-def.m +++ b/test/swift-class-def.m @@ -4,16 +4,20 @@ # define PTR " .quad " # define PTRSIZE "8" # define LOGPTRSIZE "3" +# define ONLY_LP64(x) x #else # define PTR " .long " # define PTRSIZE "4" # define LOGPTRSIZE "2" +# define ONLY_LP64(x) #endif #if __has_feature(ptrauth_calls) # define SIGNED_METHOD_LIST_IMP "@AUTH(ia,0,addr) " +# define SIGNED_STUB_INITIALIZER "@AUTH(ia,0xc671,addr) " #else # define SIGNED_METHOD_LIST_IMP +# define SIGNED_STUB_INITIALIZER #endif #define str(x) #x @@ -31,261 +35,167 @@ @interface SwiftSub : SwiftSuper @end void* nop(void* self) { return self; } __END_DECLS -asm( - ".globl _OBJC_CLASS_$_SwiftSuper \n" - ".section __DATA,__objc_data \n" - ".align 3 \n" - "_OBJC_CLASS_$_SwiftSuper: \n" - PTR "_OBJC_METACLASS_$_SwiftSuper \n" - PTR "_OBJC_CLASS_$_NSObject \n" - PTR "__objc_empty_cache \n" - PTR "0 \n" - PTR "L_ro + 2 \n" - // pad to OBJC_MAX_CLASS_SIZE - PTR "0 \n" - PTR "0 \n" - PTR "0 \n" - PTR "0 \n" - PTR "0 \n" - PTR "0 \n" - PTR "0 \n" - PTR "0 \n" - PTR "0 \n" - PTR "0 \n" - PTR "0 \n" - PTR "0 \n" - PTR "0 \n" - PTR "0 \n" - PTR "0 \n" - PTR "0 \n" - PTR "0 \n" - PTR "0 \n" - PTR "0 \n" - PTR "0 \n" - PTR "0 \n" - PTR "0 \n" - PTR "0 \n" - PTR "0 \n" - PTR "0 \n" - PTR "0 \n" - PTR "0 \n" - "" - "_OBJC_METACLASS_$_SwiftSuper: \n" - PTR "_OBJC_METACLASS_$_NSObject \n" - PTR "_OBJC_METACLASS_$_NSObject \n" - PTR "__objc_empty_cache \n" - PTR "0 \n" - PTR "L_meta_ro \n" - // pad to OBJC_MAX_CLASS_SIZE - PTR "0 \n" - PTR "0 \n" - PTR "0 \n" - PTR "0 \n" - PTR "0 \n" - PTR "0 \n" - PTR "0 \n" - PTR "0 \n" - PTR "0 \n" - PTR "0 \n" - PTR "0 \n" - PTR "0 \n" - PTR "0 \n" - PTR "0 \n" - PTR "0 \n" - PTR "0 \n" - PTR "0 \n" - PTR "0 \n" - PTR "0 \n" - PTR "0 \n" - PTR "0 \n" - PTR "0 \n" - PTR "0 \n" - PTR "0 \n" - PTR "0 \n" - PTR "0 \n" - PTR "0 \n" - "" - "L_ro: \n" - ".long (1<<6)\n" - ".long 0 \n" - ".long "PTRSIZE" \n" -#if __LP64__ - ".long 0 \n" -#endif - PTR "0 \n" - PTR "L_super_name \n" - PTR "L_good_methods \n" - PTR "0 \n" - PTR "L_super_ivars \n" - PTR "0 \n" - PTR "0 \n" - PTR "_initSuper" SIGNED_METHOD_LIST_IMP "\n" - "" - "L_meta_ro: \n" - ".long 1 \n" - ".long 40 \n" - ".long 40 \n" -#if __LP64__ - ".long 0 \n" -#endif - PTR "0 \n" - PTR "L_super_name \n" - PTR "L_good_methods \n" - PTR "0 \n" - PTR "0 \n" - PTR "0 \n" - PTR "0 \n" - - ".globl _OBJC_CLASS_$_SwiftSub \n" - ".section __DATA,__objc_data \n" - ".align 3 \n" - "_OBJC_CLASS_$_SwiftSub: \n" - PTR "_OBJC_METACLASS_$_SwiftSub \n" - PTR "_OBJC_CLASS_$_SwiftSuper \n" - PTR "__objc_empty_cache \n" - PTR "0 \n" - PTR "L_sub_ro + 2 \n" - // pad to OBJC_MAX_CLASS_SIZE - PTR "0 \n" - PTR "0 \n" - PTR "0 \n" - PTR "0 \n" - PTR "0 \n" - PTR "0 \n" - PTR "0 \n" - PTR "0 \n" - PTR "0 \n" - PTR "0 \n" - PTR "0 \n" - PTR "0 \n" - PTR "0 \n" - PTR "0 \n" - PTR "0 \n" - PTR "0 \n" - PTR "0 \n" - PTR "0 \n" - PTR "0 \n" - PTR "0 \n" - PTR "0 \n" - PTR "0 \n" - PTR "0 \n" - PTR "0 \n" - PTR "0 \n" - PTR "0 \n" - PTR "0 \n" - "" - "_OBJC_METACLASS_$_SwiftSub: \n" - PTR "_OBJC_METACLASS_$_NSObject \n" - PTR "_OBJC_METACLASS_$_SwiftSuper \n" - PTR "__objc_empty_cache \n" - PTR "0 \n" - PTR "L_sub_meta_ro \n" - // pad to OBJC_MAX_CLASS_SIZE - PTR "0 \n" - PTR "0 \n" - PTR "0 \n" - PTR "0 \n" - PTR "0 \n" - PTR "0 \n" - PTR "0 \n" - PTR "0 \n" - PTR "0 \n" - PTR "0 \n" - PTR "0 \n" - PTR "0 \n" - PTR "0 \n" - PTR "0 \n" - PTR "0 \n" - PTR "0 \n" - PTR "0 \n" - PTR "0 \n" - PTR "0 \n" - PTR "0 \n" - PTR "0 \n" - PTR "0 \n" - PTR "0 \n" - PTR "0 \n" - PTR "0 \n" - PTR "0 \n" - PTR "0 \n" - "" - "L_sub_ro: \n" - ".long (1<<6)\n" - ".long 0 \n" - ".long "PTRSIZE" \n" -#if __LP64__ - ".long 0 \n" -#endif - PTR "0 \n" - PTR "L_sub_name \n" - PTR "L_good_methods \n" - PTR "0 \n" - PTR "L_sub_ivars \n" - PTR "0 \n" - PTR "0 \n" - PTR "_initSub" SIGNED_METHOD_LIST_IMP "\n" - "" - "L_sub_meta_ro: \n" - ".long 1 \n" - ".long 40 \n" - ".long 40 \n" -#if __LP64__ - ".long 0 \n" -#endif - PTR "0 \n" - PTR "L_sub_name \n" - PTR "L_good_methods \n" - PTR "0 \n" - PTR "0 \n" - PTR "0 \n" - PTR "0 \n" - - "L_good_methods: \n" - ".long 3*"PTRSIZE" \n" - ".long 1 \n" - PTR "L_self \n" - PTR "L_self \n" - PTR "_nop" SIGNED_METHOD_LIST_IMP "\n" - - "L_super_ivars: \n" - ".long 4*"PTRSIZE" \n" - ".long 1 \n" - PTR "L_super_ivar_offset \n" - PTR "L_super_ivar_name \n" - PTR "L_super_ivar_type \n" - ".long "LOGPTRSIZE" \n" - ".long "PTRSIZE" \n" - - "L_sub_ivars: \n" - ".long 4*"PTRSIZE" \n" - ".long 1 \n" - PTR "L_sub_ivar_offset \n" - PTR "L_sub_ivar_name \n" - PTR "L_sub_ivar_type \n" - ".long "LOGPTRSIZE" \n" - ".long "PTRSIZE" \n" - - "L_super_ivar_offset: \n" - ".long 0 \n" - "L_sub_ivar_offset: \n" - ".long "PTRSIZE" \n" - - ".cstring \n" - "L_super_name: .ascii \"SwiftSuper\\0\" \n" - "L_sub_name: .ascii \"SwiftSub\\0\" \n" - "L_load: .ascii \"load\\0\" \n" - "L_self: .ascii \"self\\0\" \n" - "L_super_ivar_name: .ascii \"super_ivar\\0\" \n" - "L_super_ivar_type: .ascii \"c\\0\" \n" - "L_sub_ivar_name: .ascii \"sub_ivar\\0\" \n" - "L_sub_ivar_type: .ascii \"@\\0\" \n" - - - ".section __DATA,__objc_classlist \n" - PTR "_OBJC_CLASS_$_SwiftSuper \n" - PTR "_OBJC_CLASS_$_SwiftSub \n" - - ".text \n" -); +#define SWIFT_CLASS(name, superclass, swiftInit) \ +asm( \ + ".globl _OBJC_CLASS_$_" #name "\n" \ + ".section __DATA,__objc_data \n" \ + ".align 3 \n" \ + "_OBJC_CLASS_$_" #name ": \n" \ + PTR "_OBJC_METACLASS_$_" #name "\n" \ + PTR "_OBJC_CLASS_$_" #superclass "\n" \ + PTR "__objc_empty_cache \n" \ + PTR "0 \n" \ + PTR "L_" #name "_ro + 2 \n" \ + /* Swift class fields. */ \ + ".long 0 \n" /* flags */ \ + ".long 0 \n" /* instanceAddressOffset */ \ + ".long 16 \n" /* instanceSize */ \ + ".short 15 \n" /* instanceAlignMask */ \ + ".short 0 \n" /* reserved */ \ + ".long 256 \n" /* classSize */ \ + ".long 0 \n" /* classAddressOffset */ \ + PTR "0 \n" /* description */ \ + /* pad to OBJC_MAX_CLASS_SIZE */ \ + PTR "0 \n" \ + PTR "0 \n" \ + PTR "0 \n" \ + PTR "0 \n" \ + PTR "0 \n" \ + PTR "0 \n" \ + PTR "0 \n" \ + PTR "0 \n" \ + PTR "0 \n" \ + PTR "0 \n" \ + PTR "0 \n" \ + PTR "0 \n" \ + PTR "0 \n" \ + PTR "0 \n" \ + PTR "0 \n" \ + PTR "0 \n" \ + PTR "0 \n" \ + PTR "0 \n" \ + PTR "0 \n" \ + PTR "0 \n" \ + PTR "0 \n" \ + PTR "0 \n" \ + PTR "0 \n" \ + PTR "0 \n" \ + \ + "_OBJC_METACLASS_$_" #name ": \n" \ + PTR "_OBJC_METACLASS_$_" #superclass "\n" \ + PTR "_OBJC_METACLASS_$_" #superclass "\n" \ + PTR "__objc_empty_cache \n" \ + PTR "0 \n" \ + PTR "L_" #name "_meta_ro \n" \ + /* pad to OBJC_MAX_CLASS_SIZE */ \ + PTR "0 \n" \ + PTR "0 \n" \ + PTR "0 \n" \ + PTR "0 \n" \ + PTR "0 \n" \ + PTR "0 \n" \ + PTR "0 \n" \ + PTR "0 \n" \ + PTR "0 \n" \ + PTR "0 \n" \ + PTR "0 \n" \ + PTR "0 \n" \ + PTR "0 \n" \ + PTR "0 \n" \ + PTR "0 \n" \ + PTR "0 \n" \ + PTR "0 \n" \ + PTR "0 \n" \ + PTR "0 \n" \ + PTR "0 \n" \ + PTR "0 \n" \ + PTR "0 \n" \ + PTR "0 \n" \ + PTR "0 \n" \ + PTR "0 \n" \ + PTR "0 \n" \ + PTR "0 \n" \ + \ + "L_" #name "_ro: \n" \ + ".long (1<<6) \n" \ + ".long 0 \n" \ + ".long " PTRSIZE " \n" \ + ONLY_LP64(".long 0 \n") \ + PTR "0 \n" \ + PTR "L_" #name "_name \n" \ + PTR "L_" #name "_methods \n" \ + PTR "0 \n" \ + PTR "L_" #name "_ivars \n" \ + PTR "0 \n" \ + PTR "0 \n" \ + PTR "_" #swiftInit SIGNED_METHOD_LIST_IMP "\n" \ + \ + "L_" #name "_meta_ro: \n" \ + ".long 1 \n" \ + ".long 40 \n" \ + ".long 40 \n" \ + ONLY_LP64(".long 0 \n") \ + PTR "0 \n" \ + PTR "L_" #name "_name \n" \ + PTR "L_" #name "_meta_methods \n" \ + PTR "0 \n" \ + PTR "0 \n" \ + PTR "0 \n" \ + PTR "0 \n" \ + \ + "L_" #name "_methods: \n" \ + "L_" #name "_meta_methods: \n" \ + ".long 3*" PTRSIZE "\n" \ + ".long 1 \n" \ + PTR "L_" #name "_self \n" \ + PTR "L_" #name "_self \n" \ + PTR "_nop" SIGNED_METHOD_LIST_IMP "\n" \ + \ + "L_" #name "_ivars: \n" \ + ".long 4*" PTRSIZE " \n" \ + ".long 1 \n" \ + PTR "L_" #name "_ivar_offset \n" \ + PTR "L_" #name "_ivar_name \n" \ + PTR "L_" #name "_ivar_type \n" \ + ".long " LOGPTRSIZE "\n" \ + ".long " PTRSIZE "\n" \ + \ + "L_" #name "_ivar_offset: \n" \ + ".long 0 \n" \ + \ + ".cstring \n" \ + "L_" #name "_name: .ascii \"" #name "\\0\" \n" \ + "L_" #name "_self: .ascii \"self\\0\" \n" \ + "L_" #name "_ivar_name: " \ + " .ascii \"" #name "_ivar\\0\" \n" \ + "L_" #name "_ivar_type: .ascii \"c\\0\" \n" \ + \ + \ + ".text \n" \ +); \ +extern char OBJC_CLASS_$_ ## name; \ +Class Raw ## name = (Class)&OBJC_CLASS_$_ ## name + +#define SWIFT_STUB_CLASSREF(name) \ +extern char OBJC_CLASS_$_ ## name; \ +static Class name ## Classref = (Class)(&OBJC_CLASS_$_ ## name + 1) + +#define SWIFT_STUB_CLASS(name, initializer) \ +asm( \ + ".globl _OBJC_CLASS_$_" #name "\n" \ + ".section __DATA,__objc_data \n" \ + ".align 3 \n" \ + "_dummy" #name ": \n" \ + PTR "0 \n" \ + ".alt_entry _OBJC_CLASS_$_" #name "\n" \ + "_OBJC_CLASS_$_" #name ": \n" \ + PTR "1 \n" \ + PTR "_" #initializer SIGNED_STUB_INITIALIZER "\n" \ + ".text" \ +); \ +extern char OBJC_CLASS_$_ ## name; \ +Class Raw ## name = (Class)&OBJC_CLASS_$_ ## name; \ +SWIFT_STUB_CLASSREF(name) + void fn(void) { } diff --git a/test/swiftMetadataInitializer.m b/test/swiftMetadataInitializer.m index bfa54a8..7900344 100644 --- a/test/swiftMetadataInitializer.m +++ b/test/swiftMetadataInitializer.m @@ -4,7 +4,10 @@ #include "swift-class-def.m" -// _objc_swiftMetadataInitializer hooks for the classes in swift-class-def.m +SWIFT_CLASS(SwiftSuper, NSObject, initSuper); +SWIFT_CLASS(SwiftSub, SwiftSuper, initSub); + +// _objc_swiftMetadataInitializer hooks for the fake Swift classes Class initSuper(Class cls __unused, void *arg __unused) { @@ -34,11 +37,6 @@ Class initSub(Class cls, void *arg) { testprintf("initSub callback\n"); - extern uintptr_t OBJC_CLASS_$_SwiftSuper; - extern uintptr_t OBJC_CLASS_$_SwiftSub; - Class RawSwiftSuper = (Class)&OBJC_CLASS_$_SwiftSuper; - Class RawSwiftSub = (Class)&OBJC_CLASS_$_SwiftSub; - testassert(SubInits == 0); SubInits++; testassert(arg == nil); diff --git a/test/swiftMetadataInitializerRealloc-dylib1.m b/test/swiftMetadataInitializerRealloc-dylib1.m new file mode 100644 index 0000000..2fbecd2 --- /dev/null +++ b/test/swiftMetadataInitializerRealloc-dylib1.m @@ -0,0 +1,64 @@ +#include "test.h" +#include "swift-class-def.m" + +SWIFT_CLASS(RealSwiftDylib1A, NSObject, nop); +SWIFT_STUB_CLASS(SwiftDylib1A, initSwiftDylib1A); + +SWIFT_CLASS(RealSwiftDylib1B, NSObject, nop); +SWIFT_STUB_CLASS(SwiftDylib1B, initSwiftDylib1B); + +int Dylib1AInits = 0; + +@interface SwiftDylib1A: NSObject @end +@interface SwiftDylib1B: NSObject @end + +@implementation SwiftDylib1A (Category) +- (const char *)dylib1ACategoryInSameDylib { return "dylib1ACategoryInSameDylib"; } +@end +@implementation SwiftDylib1B (Category) +- (const char *)dylib1BCategoryInSameDylib { return "dylib1BCategoryInSameDylib"; } +@end + +EXTERN_C Class initSwiftDylib1A(Class cls, void *arg) +{ + Dylib1AInits++; + testassert(arg == nil); + testassert(cls == RawSwiftDylib1A); + + if (Dylib1AInits == 1) + _objc_realizeClassFromSwift(RawRealSwiftDylib1A, cls); + + return RawRealSwiftDylib1A; +} + +int Dylib1BInits = 0; + +EXTERN_C Class initSwiftDylib1B(Class cls, void *arg) +{ + Dylib1BInits++; + testassert(arg == nil); + testassert(cls == RawSwiftDylib1B); + + if (Dylib1BInits == 1) + _objc_realizeClassFromSwift(RawRealSwiftDylib1B, cls); + + return RawRealSwiftDylib1B; +} + +EXTERN_C Class objc_loadClassref(_Nullable Class * _Nonnull clsref); + +void Dylib1Test(void) { + testassert((uintptr_t)SwiftDylib1AClassref & 1); + Class SwiftDylib1A = objc_loadClassref(&SwiftDylib1AClassref); + testassert(((uintptr_t)SwiftDylib1AClassref & 1) == 0); + testassert(SwiftDylib1A == [SwiftDylib1A class]); + testassert(SwiftDylib1A == SwiftDylib1AClassref); + testassert(Dylib1AInits == 2); + + testassert((uintptr_t)SwiftDylib1BClassref & 1); + Class SwiftDylib1B = objc_loadClassref(&SwiftDylib1BClassref); + testassert(((uintptr_t)SwiftDylib1BClassref & 1) == 0); + testassert(SwiftDylib1B == [SwiftDylib1B class]); + testassert(SwiftDylib1B == SwiftDylib1BClassref); + testassert(Dylib1BInits == 2); +} diff --git a/test/swiftMetadataInitializerRealloc-dylib2.m b/test/swiftMetadataInitializerRealloc-dylib2.m new file mode 100644 index 0000000..71670c0 --- /dev/null +++ b/test/swiftMetadataInitializerRealloc-dylib2.m @@ -0,0 +1,40 @@ +#include "test.h" +#include "swift-class-def.m" + +@interface SwiftDylib1A: NSObject @end +@interface SwiftDylib1B: NSObject @end + +@interface NSObject (DylibCategories) +- (char *)dylib1ACategoryInSameDylib; +- (char *)dylib1BCategoryInSameDylib; +- (char *)dylib1ACategoryInOtherDylib; +- (char *)dylib1BCategoryInOtherDylib; +- (char *)dylib1ACategoryInApp; +- (char *)dylib1BCategoryInApp; ++ (void)testFromOtherDylib; +@end + +@implementation SwiftDylib1A (Category) +- (const char *)dylib1ACategoryInOtherDylib { return "dylib1ACategoryInOtherDylib"; } +@end +@implementation SwiftDylib1B (Category) +- (const char *)dylib1BCategoryInOtherDylib { return "dylib1BCategoryInOtherDylib"; } +@end + +SWIFT_STUB_CLASSREF(SwiftDylib1A); +SWIFT_STUB_CLASSREF(SwiftDylib1B); + +Class objc_loadClassref(_Nullable Class * _Nonnull clsref); + +@implementation SwiftDylib1A (Test) ++ (void)testFromOtherDylib { + Class SwiftDylib1A = objc_loadClassref(&SwiftDylib1AClassref); + Class SwiftDylib1B = objc_loadClassref(&SwiftDylib1BClassref); + testassert(strcmp([[SwiftDylib1A new] dylib1ACategoryInSameDylib], "dylib1ACategoryInSameDylib") == 0); + testassert(strcmp([[SwiftDylib1B new] dylib1BCategoryInSameDylib], "dylib1BCategoryInSameDylib") == 0); + testassert(strcmp([[SwiftDylib1A new] dylib1ACategoryInApp], "dylib1ACategoryInApp") == 0); + testassert(strcmp([[SwiftDylib1B new] dylib1BCategoryInApp], "dylib1BCategoryInApp") == 0); + testassert(strcmp([[SwiftDylib1A new] dylib1ACategoryInOtherDylib], "dylib1ACategoryInOtherDylib") == 0); + testassert(strcmp([[SwiftDylib1B new] dylib1BCategoryInOtherDylib], "dylib1BCategoryInOtherDylib") == 0); +} +@end diff --git a/test/swiftMetadataInitializerRealloc.m b/test/swiftMetadataInitializerRealloc.m new file mode 100644 index 0000000..c50d1dc --- /dev/null +++ b/test/swiftMetadataInitializerRealloc.m @@ -0,0 +1,176 @@ +/* +TEST_CONFIG MEM=mrc +TEST_BUILD + $C{COMPILE} $DIR/swiftMetadataInitializerRealloc-dylib1.m -o libswiftMetadataInitializerRealloc-dylib1.dylib -dynamiclib -Wno-deprecated-objc-pointer-introspection + $C{COMPILE} $DIR/swiftMetadataInitializerRealloc-dylib2.m -o libswiftMetadataInitializerRealloc-dylib2.dylib -dynamiclib -L. -lswiftMetadataInitializerRealloc-dylib1 + $C{COMPILE} $DIR/swiftMetadataInitializerRealloc.m -o swiftMetadataInitializerRealloc.exe -L. -lswiftMetadataInitializerRealloc-dylib1 -Wno-deprecated-objc-pointer-introspection +END +*/ + +#include "test.h" +#include "swift-class-def.m" + + +// _objc_swiftMetadataInitializer hooks for the classes in swift-class-def.m + +Class initSuper(Class cls __unused, void *arg __unused) +{ + // This test provokes objc's callback out of superclass order. + // SwiftSub's init is first. SwiftSuper's init is never called. + + fail("SwiftSuper's init should not have been called"); +} + +bool isRealized(Class cls) +{ + // check the is-realized bits directly + +#if __LP64__ +# define mask (~(uintptr_t)7) +#else +# define mask (~(uintptr_t)3) +#endif +#define RW_REALIZED (1<<31) + + uintptr_t rw = ((uintptr_t *)cls)[4] & mask; // class_t->data + return ((uint32_t *)rw)[0] & RW_REALIZED; // class_rw_t->flags +} + +SWIFT_CLASS(SwiftSuper, NSObject, initSuper); +SWIFT_CLASS(RealSwiftSub, SwiftSuper, initSub); + +SWIFT_STUB_CLASS(SwiftSub, initSub); + +OBJC_EXPORT _Nullable Class +objc_loadClassref(_Nullable Class * _Nonnull clsref); + +static int SubInits = 0; +Class initSub(Class cls, void *arg) +{ + testprintf("initSub callback\n"); + + testassert(SubInits == 0); + SubInits++; + testassert(arg == nil); + testassert(cls == RawSwiftSub); + testassert(!isRealized(RawSwiftSuper)); + + // Copy the class to the heap to ensure they're registered properly. + // Classes in the data segment are automatically "known" even if not + // added as a known class. Swift dynamically allocates classes from + // a statically allocated space in the dylib, then allocates from + // the heap after it runs out of room there. Code that only works + // when the class is in a dylib can fail a long time down the road + // when something finally exceeds the capacity of that space. + // Example: rdar://problem/50707074 + Class HeapSwiftSub = (Class)malloc(OBJC_MAX_CLASS_SIZE); + memcpy(HeapSwiftSub, RawRealSwiftSub, OBJC_MAX_CLASS_SIZE); + + testprintf("initSub beginning _objc_realizeClassFromSwift\n"); + _objc_realizeClassFromSwift(HeapSwiftSub, cls); + testprintf("initSub finished _objc_realizeClassFromSwift\n"); + + testassert(isRealized(RawSwiftSuper)); + testassert(isRealized(HeapSwiftSub)); + + testprintf("Returning reallocated class %p\n", HeapSwiftSub); + + return HeapSwiftSub; +} + + +@interface SwiftSub (Addition) +- (int)number; +@end +@implementation SwiftSub (Addition) +- (int)number { return 42; } +@end + +@interface NSObject (DylibCategories) +- (const char *)dylib1ACategoryInSameDylib; +- (const char *)dylib1BCategoryInSameDylib; +- (const char *)dylib1ACategoryInOtherDylib; +- (const char *)dylib1BCategoryInOtherDylib; +- (const char *)dylib1ACategoryInApp; +- (const char *)dylib1BCategoryInApp; ++ (const char *)dylib1ACategoryInAppClassMethod; ++ (const char *)dylib1BCategoryInAppClassMethod; ++ (void)testFromOtherDylib; +@end + +extern int Dylib1AInits; +extern int Dylib1BInits; +SWIFT_STUB_CLASSREF(SwiftDylib1A); +SWIFT_STUB_CLASSREF(SwiftDylib1B); +void Dylib1Test(void); + +@interface SwiftDylib1A: NSObject @end +@interface SwiftDylib1B: NSObject @end + +@implementation SwiftDylib1A (Category) +- (const char *)dylib1ACategoryInApp { return "dylib1ACategoryInApp"; } ++ (const char *)dylib1ACategoryInAppClassMethod { return "dylib1ACategoryInAppClassMethod"; } +@end +@implementation SwiftDylib1B (Category) +- (const char *)dylib1BCategoryInApp { return "dylib1BCategoryInApp"; } ++ (const char *)dylib1BCategoryInAppClassMethod { return "dylib1BCategoryInAppClassMethod"; } +@end + + +int main() +{ +#define LOG(fmt, expr) testprintf(#expr " is " #fmt "\n", expr); + LOG(%p, SwiftSubClassref); + Class loadedSwiftSub = objc_loadClassref(&SwiftSubClassref); + LOG(%p, SwiftSubClassref); + LOG(%p, loadedSwiftSub); + LOG(%p, [loadedSwiftSub class]); + LOG(%p, [loadedSwiftSub superclass]); + LOG(%p, [RawSwiftSuper class]); + + id obj = [[loadedSwiftSub alloc] init]; + LOG(%p, obj); + LOG(%d, [obj number]); + + LOG(%p, SwiftDylib1AClassref); + testassert(Dylib1AInits == 0); + testassert((uintptr_t)SwiftDylib1AClassref & 1); + Class SwiftDylib1A = objc_loadClassref(&SwiftDylib1AClassref); + testassert(((uintptr_t)SwiftDylib1AClassref & 1) == 0); + testassert(SwiftDylib1A == [SwiftDylib1A class]); + testassert(SwiftDylib1A == SwiftDylib1AClassref); + testassert(Dylib1AInits == 1); + LOG(%p, SwiftDylib1A); + + LOG(%p, SwiftDylib1BClassref); + testassert(Dylib1BInits == 0); + testassert((uintptr_t)SwiftDylib1BClassref & 1); + Class SwiftDylib1B = objc_loadClassref(&SwiftDylib1BClassref); + testassert(((uintptr_t)SwiftDylib1BClassref & 1) == 0); + testassert(SwiftDylib1B == [SwiftDylib1B class]); + testassert(SwiftDylib1B == SwiftDylib1BClassref); + testassert(Dylib1BInits == 1); + LOG(%p, SwiftDylib1B); + + Dylib1Test(); + + testassert(strcmp([[SwiftDylib1A new] dylib1ACategoryInSameDylib], "dylib1ACategoryInSameDylib") == 0); + testassert(strcmp([[SwiftDylib1B new] dylib1BCategoryInSameDylib], "dylib1BCategoryInSameDylib") == 0); + testassert(strcmp([[SwiftDylib1A new] dylib1ACategoryInApp], "dylib1ACategoryInApp") == 0); + testassert(strcmp([[SwiftDylib1B new] dylib1BCategoryInApp], "dylib1BCategoryInApp") == 0); + + void *handle = dlopen("libswiftMetadataInitializerRealloc-dylib2.dylib", RTLD_LAZY); + testassert(handle); + + testassert(strcmp([[SwiftDylib1A new] dylib1ACategoryInOtherDylib], "dylib1ACategoryInOtherDylib") == 0); + testassert(strcmp([[SwiftDylib1B new] dylib1BCategoryInOtherDylib], "dylib1BCategoryInOtherDylib") == 0); + testassert(strcmp([SwiftDylib1A dylib1ACategoryInAppClassMethod], "dylib1ACategoryInAppClassMethod") == 0); + testassert(strcmp([SwiftDylib1B dylib1BCategoryInAppClassMethod], "dylib1BCategoryInAppClassMethod") == 0); + [SwiftDylib1A testFromOtherDylib]; + + testassert(objc_getClass("RealSwiftSub")); + testassert(objc_getClass("RealSwiftDylib1A")); + testassert(objc_getClass("RealSwiftDylib1B")); + + succeed(__FILE__); +} diff --git a/test/synchronized.m b/test/synchronized.m index cab6dc0..2de5406 100644 --- a/test/synchronized.m +++ b/test/synchronized.m @@ -21,11 +21,18 @@ void *thread(void *arg __unused) { int err; + BOOL locked; // non-blocking sync_enter err = objc_sync_enter(obj); testassert(err == OBJC_SYNC_SUCCESS); + // recursive try_sync_enter + locked = objc_sync_try_enter(obj); + testassert(locked); + err = objc_sync_exit(obj); + testassert(err == OBJC_SYNC_SUCCESS); + semaphore_signal(go); // main thread: sync_exit of object locked on some other thread semaphore_wait(stop); @@ -59,6 +66,7 @@ int main() pthread_t th; int err; struct timeval start, end; + BOOL locked; obj = [[NSObject alloc] init]; @@ -78,6 +86,10 @@ int main() semaphore_signal(stop); semaphore_wait(go); + // contended try_sync_enter + locked = objc_sync_try_enter(obj); + testassert(!locked); + // blocking sync_enter gettimeofday(&start, NULL); err = objc_sync_enter(obj); diff --git a/test/test.pl b/test/test.pl index a72a6a7..0d0886b 100755 --- a/test/test.pl +++ b/test/test.pl @@ -104,6 +104,9 @@ END my $VERBOSE; my $BATS; +my $HOST; +my $PORT; + my @TESTLIBNAMES = ("libobjc.A.dylib", "libobjc-trampolines.dylib"); my $TESTLIBDIR = "/usr/lib"; @@ -879,12 +882,26 @@ sub build_simple { $output =~ s/^warning: key: [^\n]+\n//g; $output =~ s/^warning: discriminator: [^\n]+\n//g; $output =~ s/^warning: callee: [^\n]+\n//g; + # rdar://38710948 + $output =~ s/ld: warning: ignoring file [^\n]*libclang_rt\.bridgeos\.a[^\n]*\n//g; + # ignore compiler logging of CCC_OVERRIDE_OPTIONS effects + if (defined $ENV{CCC_OVERRIDE_OPTIONS}) { + $output =~ s/### (CCC_OVERRIDE_OPTIONS:|Adding argument|Deleting argument|Replacing) [^\n]*\n//g; + } my $ok; if (my $builderror = $T{TEST_BUILD_OUTPUT}) { # check for expected output and ignore $? if ($output =~ /$builderror/) { $ok = 1; + } elsif (defined $ENV{CCC_OVERRIDE_OPTIONS} && $builderror =~ /warning:/) { + # CCC_OVERRIDE_OPTIONS manipulates compiler diagnostics. + # Don't enforce any TEST_BUILD_OUTPUT that looks for warnings. + colorprint $yellow, "WARN: /// test '$name' \\\\\\"; + colorprefix $yellow, $output; + colorprint $yellow, "WARN: \\\\\\ test '$name' ///"; + colorprint $yellow, "WARN: $name (build output does not match TEST_BUILD_OUTPUT; not fatal because CCC_OVERRIDE_OPTIONS is set)"; + $ok = 1; } else { colorprint $red, "FAIL: /// test '$name' \\\\\\"; colorprefix $red, $output; @@ -964,7 +981,7 @@ sub run_simple { $env .= " DYLD_INSERT_LIBRARIES=$remotedir/libcrashcatch.dylib"; } - my $cmd = "ssh iphone 'cd $remotedir && env $env ./$name.exe'"; + my $cmd = "ssh -p $PORT $HOST 'cd $remotedir && env $env ./$name.exe'"; $output = make("$cmd"); } elsif ($C{OS} =~ /simulator/) { @@ -972,7 +989,7 @@ sub run_simple { # fixme selection of simulated OS version my $simdevice; if ($C{OS} =~ /iphonesimulator/) { - $simdevice = 'iPhone 6'; + $simdevice = 'iPhone X'; } elsif ($C{OS} =~ /watchsimulator/) { $simdevice = 'Apple Watch Series 4 - 40mm'; } elsif ($C{OS} =~ /tvsimulator/) { @@ -1228,6 +1245,9 @@ sub make_one_config { # libarclite no longer available on i386 # fixme need an archived copy for bincompat testing $C{FORCE_LOAD_ARCLITE} = ""; + } elsif ($C{OS} eq "bridgeos") { + # no libarclite on bridgeOS + $C{FORCE_LOAD_ARCLITE} = ""; } else { $C{FORCE_LOAD_ARCLITE} = "-Xlinker -force_load -Xlinker " . dirname($C{CC}) . "/../lib/arc/libarclite_$C{OS}.a"; } @@ -1430,7 +1450,7 @@ sub config_name { sub rsync_ios { my ($src, $timeout) = @_; for (my $i = 0; $i < 10; $i++) { - make("$DIR/timeout.pl $timeout env RSYNC_PASSWORD=alpine rsync -av $src rsync://root\@localhost:10873/root/$REMOTEBASE/"); + make("$DIR/timeout.pl $timeout rsync -e 'ssh -p $PORT' -av $src $HOST:/$REMOTEBASE/"); return if $? == 0; colorprint $yellow, "WARN: RETRY\n" if $VERBOSE; } @@ -1611,11 +1631,14 @@ sub getint { $args{OSVERSION} = getargs("OS", "macosx-default-default"); -$args{MEM} = getargs("MEM", "mrc"); -$args{LANGUAGE} = [ map { lc($_) } @{getargs("LANGUAGE", "objective-c")} ]; +$args{MEM} = getargs("MEM", "mrc,arc"); +$args{LANGUAGE} = [ map { lc($_) } @{getargs("LANGUAGE", "c,objective-c,c++,objective-c++")} ]; $args{CC} = getargs("CC", "clang"); +$HOST = getarg("HOST", "iphone"); +$PORT = getarg("PORT", "10022"); + { my $guardmalloc = getargs("GUARDMALLOC", 0); # GUARDMALLOC=1 is the same as GUARDMALLOC=before,after diff --git a/test/unload.m b/test/unload.m index 33593d9..ccd99b7 100644 --- a/test/unload.m +++ b/test/unload.m @@ -4,15 +4,14 @@ TEST_BUILD $C{COMPILE} $DIR/unload4.m -o unload4.dylib -dynamiclib $C{COMPILE_C} $DIR/unload3.c -o unload3.dylib -dynamiclib - $C{COMPILE} $DIR/unload2.m -o unload2.bundle -bundle $C{FORCE_LOAD_ARCLITE} + $C{COMPILE} $DIR/unload2.m -o unload2.bundle -bundle $C{FORCE_LOAD_ARCLITE} -Xlinker -undefined -Xlinker dynamic_lookup $C{COMPILE} $DIR/unload.m -o unload.exe -framework Foundation END */ /* -i386 Mac doesn't have libarclite TEST_BUILD_OUTPUT -ld: warning: ignoring file .* which is not the architecture being linked \(i386\).* +ld: warning: -undefined dynamic_lookup is deprecated on .* OR END */ @@ -72,8 +71,10 @@ void cycle(void) Class small = objc_getClass("SmallClass"); Class big = objc_getClass("BigClass"); + Class missing = objc_getClass("SubclassOfMissingWeakImport"); testassert(small); testassert(big); + testassert(!missing); name = class_getImageName(small); testassert(name); diff --git a/test/unload2.m b/test/unload2.m index 16ac0ca..b16fd63 100644 --- a/test/unload2.m +++ b/test/unload2.m @@ -24,3 +24,19 @@ @interface SmallClass (Category) @end @implementation SmallClass (Category) -(void)unload2_category_method { } @end + +// This isn't really weak-import: we link with `-undefined dynamic_lookup` +// instead of providing a valid definition at link time. +// But it looks the same to the runtime. +__attribute__((weak_import)) +@interface ClassThatIsWeakImportAndMissing : TestRoot @end + +@interface SubclassOfMissingWeakImport : ClassThatIsWeakImportAndMissing @end +@implementation SubclassOfMissingWeakImport +-(void)unload2_category_method { } +@end + +@interface ClassThatIsWeakImportAndMissing (Category) @end +@implementation ClassThatIsWeakImportAndMissing (Category) +-(void)unload2_category_method { } +@end diff --git a/test/willInitializeClassFunc.m b/test/willInitializeClassFunc.m new file mode 100644 index 0000000..4fef00e --- /dev/null +++ b/test/willInitializeClassFunc.m @@ -0,0 +1,109 @@ +// TEST_CONFIG MEM=mrc + +#import "test.h" +#import "testroot.i" + +#import + +#import + +char dummy; + +Class *seenClasses; +size_t seenClassesCount; + +static void clear(void) { + free(seenClasses); + seenClasses = NULL; + seenClassesCount = 0; +} + +static void willInitializeClass(void *context, Class cls) { + testprintf("Will initialize %s\n", class_getName(cls)); + seenClassesCount++; + seenClasses = (Class *)realloc(seenClasses, seenClassesCount * sizeof(*seenClasses)); + seenClasses[seenClassesCount - 1] = cls; + testassert(context == &dummy); +} + +int initializedC; +@interface C: TestRoot @end +@implementation C ++ (void)initialize { + testprintf("C initialize\n"); + initializedC = 1; +} +@end + +int initializedD; +@interface D: TestRoot @end +@implementation D ++ (void)initialize { + testprintf("D initialize\n"); + initializedD = 1; +} +@end + +int initializedE; +@interface E: TestRoot @end +@implementation E ++ (void)initialize { + testprintf("E initialize\n"); + initializedE = 1; +} +@end + +int main() +{ + _objc_addWillInitializeClassFunc(willInitializeClass, &dummy); + + // Merely getting a class should not trigger the callback. + clear(); + size_t oldCount = seenClassesCount; + Class c = objc_getClass("C"); + testassert(seenClassesCount == oldCount); + testassert(initializedC == 0); + + // Sending a message to C should trigger the callback and the superclass's callback. + [c class]; + testassert(seenClassesCount == oldCount + 2); + testassert(seenClasses[seenClassesCount - 2] == [TestRoot class]); + testassert(seenClasses[seenClassesCount - 1] == [C class]); + + // Sending a message to D should trigger the callback only for D, since the + // superclass is already initialized. + oldCount = seenClassesCount; + [D class]; + testassert(seenClassesCount == oldCount + 1); + testassert(seenClasses[seenClassesCount - 1] == [D class]); + + // Registering a second callback should inform us of all three exactly once. + clear(); + _objc_addWillInitializeClassFunc(willInitializeClass, &dummy); + testassert(seenClassesCount == 3); + + int foundRoot = 0; + int foundC = 0; + int foundD = 0; + for (size_t i = 0; i < seenClassesCount; i++) { + if (seenClasses[i] == [TestRoot class]) + foundRoot++; + if (seenClasses[i] == [C class]) + foundC++; + if (seenClasses[i] == [D class]) + foundD++; + } + testassert(foundRoot == 1); + testassert(foundC == 1); + testassert(foundD == 1); + + // Both callbacks should fire when sending a message to E. + clear(); + [E class]; + testassert(initializedE); + testassert(seenClassesCount == 2); + testassert(seenClasses[0] == [E class]); + testassert(seenClasses[1] == [E class]); + + succeed(__FILE__); +} From 3b4e8fcfd6f7656d6790921ce31ef0d7b5d814ff Mon Sep 17 00:00:00 2001 From: Apple Open Source Date: Tue, 15 Jun 2021 22:38:17 +0200 Subject: [PATCH 09/12] Version 781 --- objc.xcodeproj/project.pbxproj | 16 +- runtime/NSObject.mm | 3 + runtime/PointerUnion.h | 182 ++++++++++++ runtime/objc-cache.mm | 20 +- runtime/objc-gdb.h | 43 +++ runtime/objc-os.mm | 4 + runtime/objc-private.h | 5 + runtime/objc-runtime-new.h | 170 ++++++++--- runtime/objc-runtime-new.mm | 509 +++++++++++++++++++++------------ runtime/objc-zalloc.h | 125 ++++++++ runtime/objc-zalloc.mm | 125 ++++++++ test/runtime.m | 9 +- 12 files changed, 970 insertions(+), 241 deletions(-) create mode 100644 runtime/PointerUnion.h create mode 100644 runtime/objc-zalloc.h create mode 100644 runtime/objc-zalloc.mm diff --git a/objc.xcodeproj/project.pbxproj b/objc.xcodeproj/project.pbxproj index 3c3853f..80c47fb 100644 --- a/objc.xcodeproj/project.pbxproj +++ b/objc.xcodeproj/project.pbxproj @@ -41,6 +41,9 @@ 6E1475EC21DFDB1B001357EA /* llvm-DenseMapInfo.h in Headers */ = {isa = PBXBuildFile; fileRef = 6E1475E721DFDB1B001357EA /* llvm-DenseMapInfo.h */; }; 6E1475ED21DFDB1B001357EA /* llvm-type_traits.h in Headers */ = {isa = PBXBuildFile; fileRef = 6E1475E821DFDB1B001357EA /* llvm-type_traits.h */; }; 6E1475EE21DFDB1B001357EA /* llvm-MathExtras.h in Headers */ = {isa = PBXBuildFile; fileRef = 6E1475E921DFDB1B001357EA /* llvm-MathExtras.h */; }; + 6E7B0862232DE7CA00689009 /* PointerUnion.h in Headers */ = {isa = PBXBuildFile; fileRef = 6E7B0861232DE7CA00689009 /* PointerUnion.h */; }; + 6EACB842232C97A400CE9176 /* objc-zalloc.h in Headers */ = {isa = PBXBuildFile; fileRef = 6EACB841232C97A400CE9176 /* objc-zalloc.h */; }; + 6EACB844232C97B900CE9176 /* objc-zalloc.mm in Sources */ = {isa = PBXBuildFile; fileRef = 6EACB843232C97B900CE9176 /* objc-zalloc.mm */; }; 6ECD0B1F2244999E00910D88 /* llvm-DenseSet.h in Headers */ = {isa = PBXBuildFile; fileRef = 6ECD0B1E2244999E00910D88 /* llvm-DenseSet.h */; }; 7213C36321FA7C730090A271 /* NSObject-internal.h in Headers */ = {isa = PBXBuildFile; fileRef = 7213C36221FA7C730090A271 /* NSObject-internal.h */; settings = {ATTRIBUTES = (Private, ); }; }; 7593EC58202248E50046AB96 /* objc-object.h in Headers */ = {isa = PBXBuildFile; fileRef = 7593EC57202248DF0046AB96 /* objc-object.h */; }; @@ -157,6 +160,9 @@ 6E1475E721DFDB1B001357EA /* llvm-DenseMapInfo.h */ = {isa = PBXFileReference; fileEncoding = 4; indentWidth = 2; lastKnownFileType = sourcecode.c.h; name = "llvm-DenseMapInfo.h"; path = "runtime/llvm-DenseMapInfo.h"; sourceTree = ""; tabWidth = 2; }; 6E1475E821DFDB1B001357EA /* llvm-type_traits.h */ = {isa = PBXFileReference; fileEncoding = 4; indentWidth = 2; lastKnownFileType = sourcecode.c.h; name = "llvm-type_traits.h"; path = "runtime/llvm-type_traits.h"; sourceTree = ""; tabWidth = 2; }; 6E1475E921DFDB1B001357EA /* llvm-MathExtras.h */ = {isa = PBXFileReference; fileEncoding = 4; indentWidth = 2; lastKnownFileType = sourcecode.c.h; name = "llvm-MathExtras.h"; path = "runtime/llvm-MathExtras.h"; sourceTree = ""; tabWidth = 2; }; + 6E7B0861232DE7CA00689009 /* PointerUnion.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = PointerUnion.h; path = runtime/PointerUnion.h; sourceTree = ""; }; + 6EACB841232C97A400CE9176 /* objc-zalloc.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "objc-zalloc.h"; path = "runtime/objc-zalloc.h"; sourceTree = ""; }; + 6EACB843232C97B900CE9176 /* objc-zalloc.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = "objc-zalloc.mm"; path = "runtime/objc-zalloc.mm"; sourceTree = ""; }; 6ECD0B1E2244999E00910D88 /* llvm-DenseSet.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "llvm-DenseSet.h"; path = "runtime/llvm-DenseSet.h"; sourceTree = ""; }; 7213C36221FA7C730090A271 /* NSObject-internal.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "NSObject-internal.h"; path = "runtime/NSObject-internal.h"; sourceTree = ""; }; 7593EC57202248DF0046AB96 /* objc-object.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "objc-object.h"; path = "runtime/objc-object.h"; sourceTree = ""; }; @@ -299,6 +305,7 @@ 830F2A930D73876100392440 /* objc-accessors.mm */, 838485CA0D6D68A200CEA253 /* objc-auto.mm */, 39ABD72012F0B61800D1054C /* objc-weak.mm */, + 6EACB843232C97B900CE9176 /* objc-zalloc.mm */, E8923DA0116AB2820071B552 /* objc-block-trampolines.mm */, 838485CB0D6D68A200CEA253 /* objc-cache.mm */, 83F550DF155E030800E95D3B /* objc-cache-old.mm */, @@ -426,6 +433,7 @@ 6E1475E921DFDB1B001357EA /* llvm-MathExtras.h */, 6E1475E821DFDB1B001357EA /* llvm-type_traits.h */, C2E6D3FB2225DCF00059DFAA /* DenseMapExtras.h */, + 6E7B0861232DE7CA00689009 /* PointerUnion.h */, 83D9269721225A7400299F69 /* arm64-asm.h */, 83D92695212254CF00299F69 /* isa.h */, 838485CF0D6D68A200CEA253 /* objc-config.h */, @@ -444,6 +452,7 @@ 83BE02E70FCCB24D00661494 /* objc-runtime-old.h */, 838485E50D6D68A200CEA253 /* objc-sel-set.h */, 39ABD71F12F0B61800D1054C /* objc-weak.h */, + 6EACB841232C97A400CE9176 /* objc-zalloc.h */, ); name = "Project Headers"; sourceTree = ""; @@ -475,6 +484,7 @@ 6E1475ED21DFDB1B001357EA /* llvm-type_traits.h in Headers */, 83A4AEDC1EA0840800ACADDE /* module.modulemap in Headers */, 830F2A980D738DC200392440 /* hashtable.h in Headers */, + 6EACB842232C97A400CE9176 /* objc-zalloc.h in Headers */, 6E1475EA21DFDB1B001357EA /* llvm-AlignOf.h in Headers */, 838485BF0D6D687300CEA253 /* hashtable2.h in Headers */, 6E1475EC21DFDB1B001357EA /* llvm-DenseMapInfo.h in Headers */, @@ -507,6 +517,7 @@ 83BE02EA0FCCB24D00661494 /* objc-runtime-old.h in Headers */, 8384860A0D6D68A200CEA253 /* objc-runtime.h in Headers */, 8384860C0D6D68A200CEA253 /* objc-sel-set.h in Headers */, + 6E7B0862232DE7CA00689009 /* PointerUnion.h in Headers */, 7213C36321FA7C730090A271 /* NSObject-internal.h in Headers */, 838486100D6D68A200CEA253 /* objc-sync.h in Headers */, 83D92696212254CF00299F69 /* isa.h in Headers */, @@ -697,6 +708,7 @@ 83D49E4F13C7C84F0057F1DD /* objc-msg-arm64.s in Sources */, 9672F7EE14D5F488007CEC96 /* NSObject.mm in Sources */, 83725F4A14CA5BFA0014370E /* objc-opt.mm in Sources */, + 6EACB844232C97B900CE9176 /* objc-zalloc.mm in Sources */, 83F550E0155E030800E95D3B /* objc-cache-old.mm in Sources */, 834DF8B715993EE1002F2BC9 /* objc-sel-old.mm in Sources */, 83C9C3391668B50E00F4E544 /* objc-msg-simulator-x86_64.s in Sources */, @@ -922,7 +934,7 @@ "-D_LIBCPP_VISIBLE=\"\"", ); SDKROOT = macosx.internal; - SUPPORTED_PLATFORMS = "macosx iphoneos"; + SUPPORTED_PLATFORMS = "macosx iphoneos appletvos watchos"; WARNING_CFLAGS = ( "-Wall", "-Wextra", @@ -970,7 +982,7 @@ "-D_LIBCPP_VISIBLE=\"\"", ); SDKROOT = macosx.internal; - SUPPORTED_PLATFORMS = "macosx iphoneos"; + SUPPORTED_PLATFORMS = "macosx iphoneos appletvos watchos"; WARNING_CFLAGS = ( "-Wall", "-Wextra", diff --git a/runtime/NSObject.mm b/runtime/NSObject.mm index 6a56ec6..f672f4c 100644 --- a/runtime/NSObject.mm +++ b/runtime/NSObject.mm @@ -51,6 +51,9 @@ - (SEL)selector; OBJC_EXTERN const uint32_t objc_debug_autoreleasepoolpage_child_offset = __builtin_offsetof(AutoreleasePoolPageData, child); OBJC_EXTERN const uint32_t objc_debug_autoreleasepoolpage_depth_offset = __builtin_offsetof(AutoreleasePoolPageData, depth); OBJC_EXTERN const uint32_t objc_debug_autoreleasepoolpage_hiwat_offset = __builtin_offsetof(AutoreleasePoolPageData, hiwat); +#if __OBJC2__ +OBJC_EXTERN const uint32_t objc_class_abi_version = OBJC_CLASS_ABI_VERSION_MAX; +#endif /*********************************************************************** * Weak ivar support diff --git a/runtime/PointerUnion.h b/runtime/PointerUnion.h new file mode 100644 index 0000000..85b7846 --- /dev/null +++ b/runtime/PointerUnion.h @@ -0,0 +1,182 @@ +/* + * Copyright (c) 2019 Apple Inc. All Rights Reserved. + * + * @APPLE_LICENSE_HEADER_START@ + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPLE_LICENSE_HEADER_END@ + */ + +#ifndef POINTERUNION_H +#define POINTERUNION_H + +#include +#include + +namespace objc { + +template struct PointerUnionTypeSelectorReturn { + using Return = T; +}; + +/// Get a type based on whether two types are the same or not. +/// +/// For: +/// +/// \code +/// using Ret = typename PointerUnionTypeSelector::Return; +/// \endcode +/// +/// Ret will be EQ type if T1 is same as T2 or NE type otherwise. +template +struct PointerUnionTypeSelector { + using Return = typename PointerUnionTypeSelectorReturn::Return; +}; + +template +struct PointerUnionTypeSelector { + using Return = typename PointerUnionTypeSelectorReturn::Return; +}; + +template +struct PointerUnionTypeSelectorReturn< + PointerUnionTypeSelector> { + using Return = + typename PointerUnionTypeSelector::Return; +}; + +template +class PointerUnion { + uintptr_t _value; + + static_assert(alignof(PT1) >= 2, "alignment requirement"); + static_assert(alignof(PT2) >= 2, "alignment requirement"); + + struct IsPT1 { + static const uintptr_t Num = 0; + }; + struct IsPT2 { + static const uintptr_t Num = 1; + }; + template struct UNION_DOESNT_CONTAIN_TYPE {}; + + uintptr_t getPointer() const { + return _value & ~1; + } + uintptr_t getTag() const { + return _value & 1; + } + +public: + explicit PointerUnion(const std::atomic &raw) + : _value(raw.load(std::memory_order_relaxed)) + { } + PointerUnion(PT1 t) : _value((uintptr_t)t) { } + PointerUnion(PT2 t) : _value((uintptr_t)t | 1) { } + + void storeAt(std::atomic &raw, std::memory_order order) const { + raw.store(_value, order); + } + + template + bool is() const { + using Ty = typename PointerUnionTypeSelector>>::Return; + return getTag() == Ty::Num; + } + + template T get() const { + ASSERT(is() && "Invalid accessor called"); + return reinterpret_cast(getPointer()); + } + + template T dyn_cast() const { + if (is()) + return get(); + return T(); + } +}; + +template +class PointerUnion4 { + uintptr_t _value; + + static_assert(alignof(PT1) >= 4, "alignment requirement"); + static_assert(alignof(PT2) >= 4, "alignment requirement"); + static_assert(alignof(PT3) >= 4, "alignment requirement"); + static_assert(alignof(PT4) >= 4, "alignment requirement"); + + struct IsPT1 { + static const uintptr_t Num = 0; + }; + struct IsPT2 { + static const uintptr_t Num = 1; + }; + struct IsPT3 { + static const uintptr_t Num = 2; + }; + struct IsPT4 { + static const uintptr_t Num = 3; + }; + template struct UNION_DOESNT_CONTAIN_TYPE {}; + + uintptr_t getPointer() const { + return _value & ~3; + } + uintptr_t getTag() const { + return _value & 3; + } + +public: + explicit PointerUnion4(const std::atomic &raw) + : _value(raw.load(std::memory_order_relaxed)) + { } + PointerUnion4(PT1 t) : _value((uintptr_t)t) { } + PointerUnion4(PT2 t) : _value((uintptr_t)t | 1) { } + PointerUnion4(PT3 t) : _value((uintptr_t)t | 2) { } + PointerUnion4(PT4 t) : _value((uintptr_t)t | 3) { } + + void storeAt(std::atomic &raw, std::memory_order order) const { + raw.store(_value, order); + } + + template + bool is() const { + using Ty = typename PointerUnionTypeSelector>>>>::Return; + return getTag() == Ty::Num; + } + + template T get() const { + ASSERT(is() && "Invalid accessor called"); + return reinterpret_cast(getPointer()); + } + + template T dyn_cast() const { + if (is()) + return get(); + return T(); + } +}; + +} // namespace objc + +#endif /* DENSEMAPEXTRAS_H */ diff --git a/runtime/objc-cache.mm b/runtime/objc-cache.mm index 4735a2d..4e6ca11 100644 --- a/runtime/objc-cache.mm +++ b/runtime/objc-cache.mm @@ -465,17 +465,15 @@ static inline mask_t cache_hash(SEL sel, mask_t mask) } -#if CACHE_END_MARKER - -size_t cache_t::bytesForCapacity(uint32_t cap) +size_t cache_t::bytesForCapacity(uint32_t cap) { - // fixme put end marker inline when capacity+1 malloc is inefficient - return sizeof(bucket_t) * (cap + 1); + return sizeof(bucket_t) * cap; } -bucket_t *cache_t::endMarker(struct bucket_t *b, uint32_t cap) +#if CACHE_END_MARKER + +bucket_t *cache_t::endMarker(struct bucket_t *b, uint32_t cap) { - // bytesForCapacity() chooses whether the end marker is inline or not return (bucket_t *)((uintptr_t)b + bytesForCapacity(cap)) - 1; } @@ -483,7 +481,6 @@ static inline mask_t cache_hash(SEL sel, mask_t mask) { // Allocate one extra bucket to mark the end of the list. // This can't overflow mask_t because newCapacity is a power of 2. - // fixme instead put the end mark inline when +1 is malloc-inefficient bucket_t *newBuckets = (bucket_t *) calloc(cache_t::bytesForCapacity(newCapacity), 1); @@ -505,11 +502,6 @@ static inline mask_t cache_hash(SEL sel, mask_t mask) #else -size_t cache_t::bytesForCapacity(uint32_t cap) -{ - return sizeof(bucket_t) * cap; -} - bucket_t *allocateBuckets(mask_t newCapacity) { if (PrintCaches) recordNewCache(newCapacity); @@ -662,7 +654,7 @@ static inline mask_t cache_hash(SEL sel, mask_t mask) if (!capacity) capacity = INIT_CACHE_SIZE; reallocate(oldCapacity, capacity, /* freeOld */false); } - else if (fastpath(newOccupied <= capacity / 4 * 3)) { + else if (fastpath(newOccupied + CACHE_END_MARKER <= capacity / 4 * 3)) { // Cache is less than 3/4 full. Use it as-is. } else { diff --git a/runtime/objc-gdb.h b/runtime/objc-gdb.h index f1e4a3c..9cab4a3 100644 --- a/runtime/objc-gdb.h +++ b/runtime/objc-gdb.h @@ -157,6 +157,49 @@ OBJC_EXPORT uintptr_t objc_indexed_classes_count; OBJC_EXPORT const uintptr_t objc_debug_class_rw_data_mask OBJC_AVAILABLE(10.13, 11.0, 11.0, 4.0, 2.0); +// The ABI version for the internal runtime representations +// lldb, CoreSymbolication and debugging tools need to know +OBJC_EXTERN const uint32_t objc_class_abi_version + OBJC_AVAILABLE(10.15, 13.0, 13.0, 6.0, 5.0); + +// the maximum ABI version in existence for now +#define OBJC_CLASS_ABI_VERSION_MAX 1 + +// Used when objc_class_abi_version is absent or 0 +struct class_rw_v0_t { + uint32_t flags; + uint32_t version; + uintptr_t ro; // class_ro_t + uintptr_t methods; // method_array_t + uintptr_t properties; // property_array_t + uintptr_t protocols; // protocol_array_t + Class _Nullable firstSubclass; + Class _Nullable nextSiblingClass; + char *_Nullable demangledName; + + // uint32_t index; // only when indexed-isa is used +}; + +// Used when objc_class_abi_version is 1 +struct class_rw_v1_t { + uint32_t flags; + uint16_t witness; + uint16_t index; // only when indexed-isa is used + uintptr_t ro_or_rw_ext; // tagged union based on the low bit: + // 0: class_ro_t 1: class_rw_ext_t + Class _Nullable firstSubclass; + Class _Nullable nextSiblingClass; +}; + +struct class_rw_ext_v1_t { + uintptr_t ro; // class_ro_t + uintptr_t methods; // method_array_t + uintptr_t properties; // property_array_t + uintptr_t protocols; // protocol_array_t + char *_Nullable demangledName; + uint32_t version; +}; + #endif diff --git a/runtime/objc-os.mm b/runtime/objc-os.mm index e2f65f2..7d600ef 100644 --- a/runtime/objc-os.mm +++ b/runtime/objc-os.mm @@ -926,6 +926,10 @@ void _objc_init(void) _imp_implementationWithBlock_init(); _dyld_objc_notify_register(&map_images, load_images, unmap_image); + +#if __OBJC2__ + didCallDyldNotifyRegister = true; +#endif } diff --git a/runtime/objc-private.h b/runtime/objc-private.h index fbeaeb5..4d7aab2 100644 --- a/runtime/objc-private.h +++ b/runtime/objc-private.h @@ -478,6 +478,11 @@ static inline bool sectnameStartsWith(const char *sectname, const char *prefix){ } +#if __OBJC2__ +extern bool didCallDyldNotifyRegister; +#endif + + /* selectors */ extern void sel_init(size_t selrefCount); extern SEL sel_registerNameNoLock(const char *str, bool copy); diff --git a/runtime/objc-runtime-new.h b/runtime/objc-runtime-new.h index 532a353..d3541cf 100644 --- a/runtime/objc-runtime-new.h +++ b/runtime/objc-runtime-new.h @@ -24,6 +24,8 @@ #ifndef _OBJC_RUNTIME_NEW_H #define _OBJC_RUNTIME_NEW_H +#include "PointerUnion.h" + // class_data_bits_t is the class_t->data field (class_rw_t pointer plus flags) // The extra bits are optimized for the retain/release and alloc/dealloc paths. @@ -92,6 +94,10 @@ // class has started realizing but not yet completed it #define RW_REALIZING (1<<19) +// class is a metaclass (copied from ro) +#define RW_META RO_META // (1<<0) + + // NOTE: MORE RW_ FLAGS DEFINED BELOW @@ -769,12 +775,12 @@ class list_array_tt { protected: class iterator { - List **lists; - List **listsEnd; + List * const *lists; + List * const *listsEnd; typename List::iterator m, mEnd; public: - iterator(List **begin, List **end) + iterator(List *const *begin, List *const *end) : lists(begin), listsEnd(end) { if (begin != end) { @@ -822,7 +828,7 @@ class list_array_tt { return arrayAndFlag & 1; } - array_t *array() { + array_t *array() const { return (array_t *)(arrayAndFlag & ~1); } @@ -831,8 +837,10 @@ class list_array_tt { } public: + list_array_tt() : list(nullptr) { } + list_array_tt(List *l) : list(l) { } - uint32_t count() { + uint32_t count() const { uint32_t result = 0; for (auto lists = beginLists(), end = endLists(); lists != end; @@ -843,12 +851,12 @@ class list_array_tt { return result; } - iterator begin() { + iterator begin() const { return iterator(beginLists(), endLists()); } - iterator end() { - List **e = endLists(); + iterator end() const { + List * const *e = endLists(); return iterator(e, e); } @@ -863,7 +871,7 @@ class list_array_tt { } } - List** beginLists() { + List* const * beginLists() const { if (hasArray()) { return array()->lists; } else { @@ -871,7 +879,7 @@ class list_array_tt { } } - List** endLists() { + List* const * endLists() const { if (hasArray()) { return array()->lists + array()->count; } else if (list) { @@ -951,11 +959,14 @@ class method_array_t : typedef list_array_tt Super; public: - method_list_t **beginCategoryMethodLists() { + method_array_t() : Super() { } + method_array_t(method_list_t *l) : Super(l) { } + + method_list_t * const *beginCategoryMethodLists() const { return beginLists(); } - method_list_t **endCategoryMethodLists(Class cls); + method_list_t * const *endCategoryMethodLists(Class cls) const; method_array_t duplicate() { return Super::duplicate(); @@ -969,6 +980,9 @@ class property_array_t : typedef list_array_tt Super; public: + property_array_t() : Super() { } + property_array_t(property_list_t *l) : Super(l) { } + property_array_t duplicate() { return Super::duplicate(); } @@ -981,34 +995,58 @@ class protocol_array_t : typedef list_array_tt Super; public: + protocol_array_t() : Super() { } + protocol_array_t(protocol_list_t *l) : Super(l) { } + protocol_array_t duplicate() { return Super::duplicate(); } }; +struct class_rw_ext_t { + const class_ro_t *ro; + method_array_t methods; + property_array_t properties; + protocol_array_t protocols; + char *demangledName; + uint32_t version; +}; struct class_rw_t { // Be warned that Symbolication knows the layout of this structure. uint32_t flags; - uint16_t version; uint16_t witness; +#if SUPPORT_INDEXED_ISA + uint16_t index; +#endif - const class_ro_t *ro; - - method_array_t methods; - property_array_t properties; - protocol_array_t protocols; + explicit_atomic ro_or_rw_ext; Class firstSubclass; Class nextSiblingClass; - char *demangledName; +private: + using ro_or_rw_ext_t = objc::PointerUnion; -#if SUPPORT_INDEXED_ISA - uint32_t index; -#endif + const ro_or_rw_ext_t get_ro_or_rwe() const { + return ro_or_rw_ext_t{ro_or_rw_ext}; + } + + void set_ro_or_rwe(const class_ro_t *ro) { + ro_or_rw_ext_t{ro}.storeAt(ro_or_rw_ext, memory_order_relaxed); + } + + void set_ro_or_rwe(class_rw_ext_t *rwe, const class_ro_t *ro) { + // the release barrier is so that the class_rw_ext_t::ro initialization + // is visible to lockless readers + rwe->ro = ro; + ro_or_rw_ext_t{rwe}.storeAt(ro_or_rw_ext, memory_order_release); + } - void setFlags(uint32_t set) + class_rw_ext_t *extAlloc(const class_ro_t *ro, bool deep = false); + +public: + void setFlags(uint32_t set) { __c11_atomic_fetch_or((_Atomic(uint32_t) *)&flags, set, __ATOMIC_RELAXED); } @@ -1029,6 +1067,67 @@ struct class_rw_t { newf = (oldf | set) & ~clear; } while (!OSAtomicCompareAndSwap32Barrier(oldf, newf, (volatile int32_t *)&flags)); } + + class_rw_ext_t *ext() const { + return get_ro_or_rwe().dyn_cast(); + } + + class_rw_ext_t *extAllocIfNeeded() { + auto v = get_ro_or_rwe(); + if (fastpath(v.is())) { + return v.get(); + } else { + return extAlloc(v.get()); + } + } + + class_rw_ext_t *deepCopy(const class_ro_t *ro) { + return extAlloc(ro, true); + } + + const class_ro_t *ro() const { + auto v = get_ro_or_rwe(); + if (slowpath(v.is())) { + return v.get()->ro; + } + return v.get(); + } + + void set_ro(const class_ro_t *ro) { + auto v = get_ro_or_rwe(); + if (v.is()) { + v.get()->ro = ro; + } else { + set_ro_or_rwe(ro); + } + } + + const method_array_t methods() const { + auto v = get_ro_or_rwe(); + if (v.is()) { + return v.get()->methods; + } else { + return method_array_t{v.get()->baseMethods()}; + } + } + + const property_array_t properties() const { + auto v = get_ro_or_rwe(); + if (v.is()) { + return v.get()->properties; + } else { + return property_array_t{v.get()->baseProperties}; + } + } + + const protocol_array_t protocols() const { + auto v = get_ro_or_rwe(); + if (v.is()) { + return v.get()->protocols; + } else { + return protocol_array_t{v.get()->baseProtocols}; + } + } }; @@ -1087,7 +1186,7 @@ struct class_data_bits_t { class_rw_t *maybe_rw = data(); if (maybe_rw->flags & RW_REALIZED) { // maybe_rw is rw - return maybe_rw->ro; + return maybe_rw->ro(); } else { // maybe_rw is actually ro return (class_ro_t *)maybe_rw; @@ -1359,12 +1458,12 @@ struct objc_class : objc_object { // Return YES if the class's ivars are managed by ARC, // or the class is MRC but has ARC-style weak ivars. bool hasAutomaticIvars() { - return data()->ro->flags & (RO_IS_ARC | RO_HAS_WEAK_WITHOUT_ARC); + return data()->ro()->flags & (RO_IS_ARC | RO_HAS_WEAK_WITHOUT_ARC); } // Return YES if the class's ivars are managed by ARC. bool isARC() { - return data()->ro->flags & RO_IS_ARC; + return data()->ro()->flags & RO_IS_ARC; } @@ -1435,13 +1534,15 @@ struct objc_class : objc_object { #if FAST_CACHE_META return cache.getBit(FAST_CACHE_META); #else - return data()->ro->flags & RO_META; + return data()->flags & RW_META; #endif } // Like isMetaClass, but also valid on un-realized classes bool isMetaClassMaybeUnrealized() { - return bits.safe_ro()->flags & RO_META; + static_assert(offsetof(class_rw_t, flags) == offsetof(class_ro_t, flags), "flags alias"); + static_assert(RO_META == RW_META, "flags alias"); + return data()->flags & RW_META; } // NOT identical to this->ISA when this is a metaclass @@ -1462,19 +1563,19 @@ struct objc_class : objc_object { ASSERT(this); if (isRealized() || isFuture()) { - return data()->ro->name; + return data()->ro()->name; } else { return ((const class_ro_t *)data())->name; } } - const char *demangledName(); + const char *demangledName(bool needsLock); const char *nameForLogging(); // May be unaligned depending on class's ivars. uint32_t unalignedInstanceStart() const { ASSERT(isRealized()); - return data()->ro->instanceStart; + return data()->ro()->instanceStart; } // Class's instance start rounded up to a pointer-size boundary. @@ -1486,7 +1587,7 @@ struct objc_class : objc_object { // May be unaligned depending on class's ivars. uint32_t unalignedInstanceSize() const { ASSERT(isRealized()); - return data()->ro->instanceSize; + return data()->ro()->instanceSize; } // Class's ivar size rounded up to a pointer-size boundary. @@ -1508,9 +1609,10 @@ struct objc_class : objc_object { void setInstanceSize(uint32_t newSize) { ASSERT(isRealized()); ASSERT(data()->flags & RW_REALIZING); - if (newSize != data()->ro->instanceSize) { + auto ro = data()->ro(); + if (newSize != ro->instanceSize) { ASSERT(data()->flags & RW_COPIED_RO); - *const_cast(&data()->ro->instanceSize) = newSize; + *const_cast(&ro->instanceSize) = newSize; } cache.setFastInstanceSize(newSize); } diff --git a/runtime/objc-runtime-new.mm b/runtime/objc-runtime-new.mm index be70319..ee12127 100644 --- a/runtime/objc-runtime-new.mm +++ b/runtime/objc-runtime-new.mm @@ -33,6 +33,7 @@ #include "objc-runtime-new.h" #include "objc-file.h" #include "objc-cache.h" +#include "objc-zalloc.h" #include #include #include @@ -45,7 +46,7 @@ static IMP addMethod(Class cls, SEL name, IMP imp, const char *types, bool replace); static void adjustCustomFlagsForMethodChange(Class cls, method_t *meth); static method_t *search_method_list(const method_list_t *mlist, SEL sel); -static bool method_lists_contains_any(method_list_t **mlists, method_list_t **end, +static bool method_lists_contains_any(method_list_t * const *mlists, method_list_t * const *end, SEL sels[], size_t selcount); static void flushCaches(Class cls); static void initializeTaggedPointerObfuscator(void); @@ -198,6 +199,19 @@ static bool method_lists_contains_any(method_list_t **mlists, method_list_t **en **********************************************************************/ static Class _firstRealizedClass = nil; +/*********************************************************************** +* didInitialAttachCategories +* Whether the initial attachment of categories present at startup has +* been done. +**********************************************************************/ +static bool didInitialAttachCategories = false; + +/*********************************************************************** +* didCallDyldNotifyRegister +* Whether the call to _dyld_objc_notify_register has completed. +**********************************************************************/ +bool didCallDyldNotifyRegister = false; + /* Low two bits of mlist->entsize is used as the fixed-up marker. PREOPTIMIZED VERSION: @@ -274,12 +288,12 @@ static bool method_lists_contains_any(method_list_t **mlists, method_list_t **en } -method_list_t **method_array_t::endCategoryMethodLists(Class cls) +method_list_t * const *method_array_t::endCategoryMethodLists(Class cls) const { - method_list_t **mlists = beginLists(); - method_list_t **mlistsEnd = endLists(); + auto mlists = beginLists(); + auto mlistsEnd = endLists(); - if (mlists == mlistsEnd || !cls->data()->ro->baseMethods()) + if (mlists == mlistsEnd || !cls->data()->ro()->baseMethods()) { // No methods, or no base methods. // Everything here is a category method. @@ -394,10 +408,10 @@ void _objc_setClassCopyFixupHandler(void (* _Nonnull newFixupHandler) if (rw->flags & RW_COPIED_RO) { // already writeable, do nothing } else { - rw->ro = rw->ro->duplicate(); + rw->set_ro(rw->ro()->duplicate()); rw->flags |= RW_COPIED_RO; } - return (class_ro_t *)rw->ro; + return const_cast(rw->ro()); } @@ -546,7 +560,7 @@ static Class metaclassNSObject(void) } // Look for method in cls - for (const auto& meth2 : cls->data()->methods) { + for (const auto& meth2 : cls->data()->methods()) { SEL s2 = sel_registerName(sel_cname(meth2.name)); if (s == s2) { logReplacedMethod(cls->nameForLogging(), s, @@ -762,7 +776,7 @@ static void __attribute__((cold, noinline)) cls = classNSObject(); if (Domain != Scope::Classes && !isNSObjectSwizzled(NO)) { - for (const auto &meth2: as_objc_class(cls)->data()->methods) { + for (const auto &meth2: as_objc_class(cls)->data()->methods()) { if (meth == &meth2) { setNSObjectSwizzled(cls, NO); break; @@ -772,7 +786,7 @@ static void __attribute__((cold, noinline)) cls = metaclassNSObject(); if (Domain != Scope::Instances && !isNSObjectSwizzled(YES)) { - for (const auto &meth2: as_objc_class(cls)->data()->methods) { + for (const auto &meth2: as_objc_class(cls)->data()->methods()) { if (meth == &meth2) { setNSObjectSwizzled(cls, YES); break; @@ -791,7 +805,7 @@ static void __attribute__((cold, noinline)) setCustom = YES; } else if (cls == NSOClass) { // NSObject is default but we need to check categories - auto &methods = as_objc_class(cls)->data()->methods; + auto &methods = as_objc_class(cls)->data()->methods(); setCustom = Traits::scanMethodLists(methods.beginCategoryMethodLists(), methods.endCategoryMethodLists(cls)); } else if (!isMeta && !as_objc_class(cls)->superclass) { @@ -803,7 +817,7 @@ static void __attribute__((cold, noinline)) inherited = YES; } else { // Not NSObject. - auto &methods = as_objc_class(cls)->data()->methods; + auto &methods = as_objc_class(cls)->data()->methods(); setCustom = Traits::scanMethodLists(methods.beginLists(), methods.endLists()); } @@ -924,7 +938,7 @@ static void setDefault(Class cls) { static bool isInterestingSelector(SEL sel) { return sel == @selector(alloc) || sel == @selector(allocWithZone:); } - static bool scanMethodLists(method_list_t **mlists, method_list_t **end) { + static bool scanMethodLists(method_list_t * const *mlists, method_list_t * const *end) { SEL sels[2] = { @selector(alloc), @selector(allocWithZone:), }; return method_lists_contains_any(mlists, end, sels, 2); } @@ -958,7 +972,7 @@ static bool isInterestingSelector(SEL sel) { sel == @selector(allowsWeakReference) || sel == @selector(retainWeakReference); } - static bool scanMethodLists(method_list_t **mlists, method_list_t **end) { + static bool scanMethodLists(method_list_t * const *mlists, method_list_t * const *end) { SEL sels[8] = { @selector(retain), @selector(release), @@ -993,7 +1007,7 @@ static bool isInterestingSelector(SEL sel) { sel == @selector(isKindOfClass:) || sel == @selector(respondsToSelector:); } - static bool scanMethodLists(method_list_t **mlists, method_list_t **end) { + static bool scanMethodLists(method_list_t * const *mlists, method_list_t * const *end) { SEL sels[5] = { @selector(new), @selector(self), @@ -1162,7 +1176,7 @@ void eraseClass(Class cls) static bool isBundleClass(Class cls) { - return cls->data()->ro->flags & RO_FROM_BUNDLE; + return cls->data()->ro()->flags & RO_FROM_BUNDLE; } @@ -1235,6 +1249,39 @@ static bool isBundleClass(Class cls) } } +class_rw_ext_t * +class_rw_t::extAlloc(const class_ro_t *ro, bool deepCopy) +{ + runtimeLock.assertLocked(); + + auto rwe = objc::zalloc(); + + rwe->version = (ro->flags & RO_META) ? 7 : 0; + + method_list_t *list = ro->baseMethods(); + if (list) { + if (deepCopy) list = list->duplicate(); + rwe->methods.attachLists(&list, 1); + } + + // See comments in objc_duplicateClass + // property lists and protocol lists historically + // have not been deep-copied + // + // This is probably wrong and ought to be fixed some day + property_list_t *proplist = ro->baseProperties; + if (proplist) { + rwe->properties.attachLists(&proplist, 1); + } + + protocol_list_t *protolist = ro->baseProtocols; + if (protolist) { + rwe->protocols.attachLists(&protolist, 1); + } + + set_ro_or_rwe(rwe, ro); + return rwe; +} // Attach method lists and properties and protocols from categories to a class. // Assumes the categories in cats are all loaded and sorted by load order, @@ -1272,7 +1319,7 @@ static bool isBundleClass(Class cls) uint32_t protocount = 0; bool fromBundle = NO; bool isMeta = (flags & ATTACH_METACLASS); - auto rw = cls->data(); + auto rwe = cls->data()->extAllocIfNeeded(); for (uint32_t i = 0; i < cats_count; i++) { auto& entry = cats_list[i]; @@ -1281,7 +1328,7 @@ static bool isBundleClass(Class cls) if (mlist) { if (mcount == ATTACH_BUFSIZ) { prepareMethodLists(cls, mlists, mcount, NO, fromBundle); - rw->methods.attachLists(mlists, mcount); + rwe->methods.attachLists(mlists, mcount); mcount = 0; } mlists[ATTACH_BUFSIZ - ++mcount] = mlist; @@ -1292,7 +1339,7 @@ static bool isBundleClass(Class cls) entry.cat->propertiesForMeta(isMeta, entry.hi); if (proplist) { if (propcount == ATTACH_BUFSIZ) { - rw->properties.attachLists(proplists, propcount); + rwe->properties.attachLists(proplists, propcount); propcount = 0; } proplists[ATTACH_BUFSIZ - ++propcount] = proplist; @@ -1301,7 +1348,7 @@ static bool isBundleClass(Class cls) protocol_list_t *protolist = entry.cat->protocolsForMeta(isMeta); if (protolist) { if (protocount == ATTACH_BUFSIZ) { - rw->protocols.attachLists(protolists, protocount); + rwe->protocols.attachLists(protolists, protocount); protocount = 0; } protolists[ATTACH_BUFSIZ - ++protocount] = protolist; @@ -1310,13 +1357,13 @@ static bool isBundleClass(Class cls) if (mcount > 0) { prepareMethodLists(cls, mlists + ATTACH_BUFSIZ - mcount, mcount, NO, fromBundle); - rw->methods.attachLists(mlists + ATTACH_BUFSIZ - mcount, mcount); + rwe->methods.attachLists(mlists + ATTACH_BUFSIZ - mcount, mcount); if (flags & ATTACH_EXISTING) flushCaches(cls); } - rw->properties.attachLists(proplists + ATTACH_BUFSIZ - propcount, propcount); + rwe->properties.attachLists(proplists + ATTACH_BUFSIZ - propcount, propcount); - rw->protocols.attachLists(protolists + ATTACH_BUFSIZ - protocount, protocount); + rwe->protocols.attachLists(protolists + ATTACH_BUFSIZ - protocount, protocount); } @@ -1332,7 +1379,8 @@ static void methodizeClass(Class cls, Class previously) bool isMeta = cls->isMetaClass(); auto rw = cls->data(); - auto ro = rw->ro; + auto ro = rw->ro(); + auto rwe = rw->ext(); // Methodizing for the first time if (PrintConnecting) { @@ -1344,17 +1392,17 @@ static void methodizeClass(Class cls, Class previously) method_list_t *list = ro->baseMethods(); if (list) { prepareMethodLists(cls, &list, 1, YES, isBundleClass(cls)); - rw->methods.attachLists(&list, 1); + if (rwe) rwe->methods.attachLists(&list, 1); } property_list_t *proplist = ro->baseProperties; - if (proplist) { - rw->properties.attachLists(&proplist, 1); + if (rwe && proplist) { + rwe->properties.attachLists(&proplist, 1); } protocol_list_t *protolist = ro->baseProtocols; - if (protolist) { - rw->protocols.attachLists(&protolist, 1); + if (rwe && protolist) { + rwe->protocols.attachLists(&protolist, 1); } // Root classes get bonus method implementations if they don't have @@ -1382,7 +1430,7 @@ static void methodizeClass(Class cls, Class previously) #if DEBUG // Debug: sanity-check all SELs; log method list contents - for (const auto& meth : rw->methods) { + for (const auto& meth : rw->methods()) { if (PrintConnecting) { _objc_inform("METHOD %c[%s %s]", isMeta ? '+' : '-', cls->nameForLogging(), sel_getName(meth.name)); @@ -1704,10 +1752,10 @@ static void addFutureNamedClass(const char *name, Class cls) _objc_inform("FUTURE: reserving %p for %s", (void*)cls, name); } - class_rw_t *rw = (class_rw_t *)calloc(sizeof(class_rw_t), 1); + class_rw_t *rw = objc::zalloc(); class_ro_t *ro = (class_ro_t *)calloc(sizeof(class_ro_t), 1); ro->name = strdupIfMutable(name); - rw->ro = ro; + rw->set_ro(ro); cls->setData(rw); cls->data()->flags = RO_FUTURE; @@ -2345,7 +2393,7 @@ static void reconcileInstanceVariables(Class cls, Class supercls, const class_ro */ // Non-fragile ivars - reconcile this class with its superclass - const class_ro_t *super_ro = supercls->data()->ro; + const class_ro_t *super_ro = supercls->data()->ro(); if (DebugNonFragileIvars) { // Debugging: Force non-fragile ivars to slide. @@ -2367,7 +2415,7 @@ static void reconcileInstanceVariables(Class cls, Class supercls, const class_ro { uint32_t oldStart = ro->instanceStart; class_ro_t *ro_w = make_ro_writeable(rw); - ro = rw->ro; + ro = rw->ro(); // Find max ivar alignment in class. // default to word size to simplify ivar update @@ -2418,7 +2466,7 @@ static void reconcileInstanceVariables(Class cls, Class supercls, const class_ro super_ro->instanceSize); } class_ro_t *ro_w = make_ro_writeable(rw); - ro = rw->ro; + ro = rw->ro(); moveIvars(ro_w, super_ro->instanceSize); gdb_objc_class_changed(cls, OBJC_CLASS_IVARS_CHANGED, ro->name); } @@ -2437,11 +2485,9 @@ static Class realizeClassWithoutSwift(Class cls, Class previously) { runtimeLock.assertLocked(); - const class_ro_t *ro; class_rw_t *rw; Class supercls; Class metacls; - bool isMeta; if (!cls) return nil; if (cls->isRealized()) return cls; @@ -2449,26 +2495,25 @@ static Class realizeClassWithoutSwift(Class cls, Class previously) // fixme verify class is not in an un-dlopened part of the shared cache? - ro = (const class_ro_t *)cls->data(); + auto ro = (const class_ro_t *)cls->data(); + auto isMeta = ro->flags & RO_META; if (ro->flags & RO_FUTURE) { // This was a future class. rw data is already allocated. rw = cls->data(); - ro = cls->data()->ro; + ro = cls->data()->ro(); + ASSERT(!isMeta); cls->changeInfo(RW_REALIZED|RW_REALIZING, RW_FUTURE); } else { // Normal class. Allocate writeable class data. - rw = (class_rw_t *)calloc(sizeof(class_rw_t), 1); - rw->ro = ro; - rw->flags = RW_REALIZED|RW_REALIZING; + rw = objc::zalloc(); + rw->set_ro(ro); + rw->flags = RW_REALIZED|RW_REALIZING|isMeta; cls->setData(rw); } - isMeta = ro->flags & RO_META; #if FAST_CACHE_META if (isMeta) cls->cache.setBit(FAST_CACHE_META); #endif - rw->version = isMeta ? 7 : 0; // old runtime went up to 6 - // Choose an index for this class. // Sets cls->instancesRequireRawIsa if indexes no more indexes are available @@ -2943,6 +2988,83 @@ void _objc_flush_caches(Class cls) } +static void load_categories_nolock(header_info *hi) { + bool hasClassProperties = hi->info()->hasCategoryClassProperties(); + + size_t count; + auto processCatlist = [&](category_t * const *catlist) { + for (unsigned i = 0; i < count; i++) { + category_t *cat = catlist[i]; + Class cls = remapClass(cat->cls); + locstamped_category_t lc{cat, hi}; + + if (!cls) { + // Category's target class is missing (probably weak-linked). + // Ignore the category. + if (PrintConnecting) { + _objc_inform("CLASS: IGNORING category \?\?\?(%s) %p with " + "missing weak-linked target class", + cat->name, cat); + } + continue; + } + + // Process this category. + if (cls->isStubClass()) { + // Stub classes are never realized. Stub classes + // don't know their metaclass until they're + // initialized, so we have to add categories with + // class methods or properties to the stub itself. + // methodizeClass() will find them and add them to + // the metaclass as appropriate. + if (cat->instanceMethods || + cat->protocols || + cat->instanceProperties || + cat->classMethods || + cat->protocols || + (hasClassProperties && cat->_classProperties)) + { + objc::unattachedCategories.addForClass(lc, cls); + } + } else { + // First, register the category with its target class. + // Then, rebuild the class's method lists (etc) if + // the class is realized. + if (cat->instanceMethods || cat->protocols + || cat->instanceProperties) + { + if (cls->isRealized()) { + attachCategories(cls, &lc, 1, ATTACH_EXISTING); + } else { + objc::unattachedCategories.addForClass(lc, cls); + } + } + + if (cat->classMethods || cat->protocols + || (hasClassProperties && cat->_classProperties)) + { + if (cls->ISA()->isRealized()) { + attachCategories(cls->ISA(), &lc, 1, ATTACH_EXISTING | ATTACH_METACLASS); + } else { + objc::unattachedCategories.addForClass(lc, cls->ISA()); + } + } + } + } + }; + + processCatlist(_getObjc2CategoryList(hi, &count)); + processCatlist(_getObjc2CategoryList2(hi, &count)); +} + +static void loadAllCategories() { + mutex_locker_t lock(runtimeLock); + + for (auto *hi = FirstHeader; hi != NULL; hi = hi->getNext()) { + load_categories_nolock(hi); + } +} + /*********************************************************************** * load_images * Process +load in the given images which are being mapped in by dyld. @@ -2955,6 +3077,11 @@ void _objc_flush_caches(Class cls) void load_images(const char *path __unused, const struct mach_header *mh) { + if (!didInitialAttachCategories && didCallDyldNotifyRegister) { + didInitialAttachCategories = true; + loadAllCategories(); + } + // Return without taking locks if there are no +load methods here. if (!hasLoadMethods((const headerType *)mh)) return; @@ -3091,9 +3218,9 @@ Class readClass(Class cls, bool headerIsBundle, bool headerIsPreoptimized) } class_rw_t *rw = newCls->data(); - const class_ro_t *old_ro = rw->ro; + const class_ro_t *old_ro = rw->ro(); memcpy(newCls, cls, sizeof(objc_class)); - rw->ro = (class_ro_t *)newCls->data(); + rw->set_ro((class_ro_t *)newCls->data()); newCls->setData(rw); freeIfMutable((char *)old_ro->name); free((void *)old_ro); @@ -3480,72 +3607,14 @@ void _read_images(header_info **hList, uint32_t hCount, int totalClasses, int un ts.log("IMAGE TIMES: fix up @protocol references"); - // Discover categories. - for (EACH_HEADER) { - bool hasClassProperties = hi->info()->hasCategoryClassProperties(); - - auto processCatlist = [&](category_t * const *catlist) { - for (i = 0; i < count; i++) { - category_t *cat = catlist[i]; - Class cls = remapClass(cat->cls); - locstamped_category_t lc{cat, hi}; - - if (!cls) { - // Category's target class is missing (probably weak-linked). - // Ignore the category. - if (PrintConnecting) { - _objc_inform("CLASS: IGNORING category \?\?\?(%s) %p with " - "missing weak-linked target class", - cat->name, cat); - } - continue; - } - - // Process this category. - if (cls->isStubClass()) { - // Stub classes are never realized. Stub classes - // don't know their metaclass until they're - // initialized, so we have to add categories with - // class methods or properties to the stub itself. - // methodizeClass() will find them and add them to - // the metaclass as appropriate. - if (cat->instanceMethods || - cat->protocols || - cat->instanceProperties || - cat->classMethods || - cat->protocols || - (hasClassProperties && cat->_classProperties)) - { - objc::unattachedCategories.addForClass(lc, cls); - } - } else { - // First, register the category with its target class. - // Then, rebuild the class's method lists (etc) if - // the class is realized. - if (cat->instanceMethods || cat->protocols - || cat->instanceProperties) - { - if (cls->isRealized()) { - attachCategories(cls, &lc, 1, ATTACH_EXISTING); - } else { - objc::unattachedCategories.addForClass(lc, cls); - } - } - - if (cat->classMethods || cat->protocols - || (hasClassProperties && cat->_classProperties)) - { - if (cls->ISA()->isRealized()) { - attachCategories(cls->ISA(), &lc, 1, ATTACH_EXISTING | ATTACH_METACLASS); - } else { - objc::unattachedCategories.addForClass(lc, cls->ISA()); - } - } - } - } - }; - processCatlist(_getObjc2CategoryList(hi, &count)); - processCatlist(_getObjc2CategoryList2(hi, &count)); + // Discover categories. Only do this after the initial category + // attachment has been done. For categories present at startup, + // discovery is deferred until the first load_images call after + // the call to _dyld_objc_notify_register completes. rdar://problem/53119145 + if (didInitialAttachCategories) { + for (EACH_HEADER) { + load_categories_nolock(hi); + } } ts.log("IMAGE TIMES: discover categories"); @@ -5055,16 +5124,17 @@ void objc_registerProtocol(Protocol *proto_gen) } mutex_locker_t lock(runtimeLock); + const auto methods = cls->data()->methods(); ASSERT(cls->isRealized()); - count = cls->data()->methods.count(); + count = methods.count(); if (count > 0) { result = (Method *)malloc((count + 1) * sizeof(Method)); count = 0; - for (auto& meth : cls->data()->methods) { + for (auto& meth : methods) { result[count++] = &meth; } result[count] = nil; @@ -5096,7 +5166,7 @@ void objc_registerProtocol(Protocol *proto_gen) ASSERT(cls->isRealized()); - if ((ivars = cls->data()->ro->ivars) && ivars->count) { + if ((ivars = cls->data()->ro()->ivars) && ivars->count) { result = (Ivar *)malloc((ivars->count+1) * sizeof(Ivar)); for (auto& ivar : *ivars) { @@ -5134,12 +5204,13 @@ void objc_registerProtocol(Protocol *proto_gen) auto rw = cls->data(); property_t **result = nil; - unsigned int count = rw->properties.count(); + auto const properties = rw->properties(); + unsigned int count = properties.count(); if (count > 0) { result = (property_t **)malloc((count + 1) * sizeof(property_t *)); count = 0; - for (auto& prop : rw->properties) { + for (auto& prop : properties) { result[count++] = ∝ } result[count] = nil; @@ -5168,7 +5239,7 @@ void objc_registerProtocol(Protocol *proto_gen) ASSERT(!isMetaClass()); ASSERT(ISA()->isMetaClass()); - mlist = ISA()->data()->ro->baseMethods(); + mlist = ISA()->data()->ro()->baseMethods(); if (mlist) { for (const auto& meth : *mlist) { const char *name = sel_cname(meth.name); @@ -5283,18 +5354,19 @@ void objc_registerProtocol(Protocol *proto_gen) } mutex_locker_t lock(runtimeLock); + const auto protocols = cls->data()->protocols(); checkIsKnownClass(cls); ASSERT(cls->isRealized()); - count = cls->data()->protocols.count(); + count = protocols.count(); if (count > 0) { result = (Protocol **)malloc((count+1) * sizeof(Protocol *)); count = 0; - for (const auto& proto : cls->data()->protocols) { + for (const auto& proto : protocols) { result[count++] = (Protocol *)remapProtocol(proto); } result[count] = nil; @@ -5377,7 +5449,7 @@ void objc_registerProtocol(Protocol *proto_gen) for (size_t i = 0; i < count; i++) { Class cls = remapClass(classlist[i]); if (cls) { - names[i-shift] = cls->demangledName(); + names[i-shift] = cls->demangledName(/* needs lock */false); } else { shift++; // ignored weak-linked class } @@ -5495,7 +5567,13 @@ void objc_registerProtocol(Protocol *proto_gen) { // Handle the easy case directly. if (isRealized() || isFuture()) { - if (data()->demangledName) return data()->demangledName; + if (!isAnySwift()) { + return data()->ro()->name; + } + auto rwe = data()->ext(); + if (rwe && rwe->demangledName) { + return rwe->demangledName; + } } char *result; @@ -5518,26 +5596,44 @@ void objc_registerProtocol(Protocol *proto_gen) mutex_t DemangleCacheLock; static objc::DenseSet *DemangleCache; const char * -objc_class::demangledName() +objc_class::demangledName(bool needsLock) { + if (!needsLock) { + runtimeLock.assertLocked(); + } + // Return previously demangled name if available. if (isRealized() || isFuture()) { - if (data()->demangledName) return data()->demangledName; + if (!isAnySwift()) { + return data()->ro()->name; + } + auto rwe = data()->ext(); + if (rwe && rwe->demangledName) { + return rwe->demangledName; + } } // Try demangling the mangled name. const char *mangled = mangledName(); char *de = copySwiftV1DemangledName(mangled); + class_rw_ext_t *rwe; + if (isRealized() || isFuture()) { - // Class is already realized or future. + if (needsLock) { + mutex_locker_t lock(runtimeLock); + rwe = data()->extAllocIfNeeded(); + } else { + rwe = data()->extAllocIfNeeded(); + } + // Class is already realized or future. // Save demangling result in rw data. // We may not own runtimeLock so use an atomic operation instead. if (! OSAtomicCompareAndSwapPtrBarrier(nil, (void*)(de ?: mangled), - (void**)&data()->demangledName)) + (void**)&rwe->demangledName)) { if (de) free(de); } - return data()->demangledName; + return rwe->demangledName; } // Class is not yet realized. @@ -5557,7 +5653,6 @@ void objc_registerProtocol(Protocol *proto_gen) // Only objc_copyClassNamesForImage() should get here. // fixme lldb's calls to class_getName() can also get here when // interrogating the dyld shared cache. (rdar://27258517) - // fixme runtimeLock.assertLocked(); // fixme ASSERT(realize); const char *cached; @@ -5583,7 +5678,7 @@ void objc_registerProtocol(Protocol *proto_gen) if (!cls) return "nil"; // fixme lldb calls class_getName() on unrealized classes (rdar://27258517) // ASSERT(cls->isRealized() || cls->isFuture()); - return cls->demangledName(); + return cls->demangledName(/* needs lock */true); } /*********************************************************************** @@ -5608,7 +5703,11 @@ void objc_registerProtocol(Protocol *proto_gen) { if (!cls) return 0; ASSERT(cls->isRealized()); - return cls->data()->version; + auto rwe = cls->data()->ext(); + if (rwe) { + return rwe->version; + } + return cls->isMetaClass() ? 7 : 0; } @@ -5622,7 +5721,13 @@ void objc_registerProtocol(Protocol *proto_gen) { if (!cls) return; ASSERT(cls->isRealized()); - cls->data()->version = version; + auto rwe = cls->data()->ext(); + if (!rwe) { + mutex_locker_t lock(runtimeLock); + rwe = cls->data()->extAllocIfNeeded(); + } + + rwe->version = version; } /*********************************************************************** @@ -5702,7 +5807,7 @@ void objc_registerProtocol(Protocol *proto_gen) * method_lists_contains_any **********************************************************************/ static NEVER_INLINE bool -method_lists_contains_any(method_list_t **mlists, method_list_t **end, +method_lists_contains_any(method_list_t * const *mlists, method_list_t * const *end, SEL sels[], size_t selcount) { while (mlists < end) { @@ -5743,8 +5848,9 @@ void objc_registerProtocol(Protocol *proto_gen) // fixme nil cls? // fixme nil sel? - for (auto mlists = cls->data()->methods.beginLists(), - end = cls->data()->methods.endLists(); + auto const methods = cls->data()->methods(); + for (auto mlists = methods.beginLists(), + end = methods.endLists(); mlists != end; ++mlists) { @@ -6148,7 +6254,7 @@ objc_property_t class_getProperty(Class cls, const char *name) ASSERT(cls->isRealized()); for ( ; cls; cls = cls->superclass) { - for (auto& prop : cls->data()->properties) { + for (auto& prop : cls->data()->properties()) { if (0 == strcmp(name, prop.name)) { return (objc_property_t)∝ } @@ -6290,7 +6396,7 @@ Class gdb_object_getClass(id obj) const uint8_t * class_getIvarLayout(Class cls) { - if (cls) return cls->data()->ro->ivarLayout; + if (cls) return cls->data()->ro()->ivarLayout; else return nil; } @@ -6304,7 +6410,7 @@ Class gdb_object_getClass(id obj) const uint8_t * class_getWeakIvarLayout(Class cls) { - if (cls) return cls->data()->ro->weakIvarLayout; + if (cls) return cls->data()->ro()->weakIvarLayout; else return nil; } @@ -6388,7 +6494,7 @@ Class gdb_object_getClass(id obj) const ivar_list_t *ivars; ASSERT(cls->isRealized()); - if ((ivars = cls->data()->ro->ivars)) { + if ((ivars = cls->data()->ro()->ivars)) { for (auto& ivar : *ivars) { if (!ivar.offset) continue; // anonymous bitfield @@ -6413,7 +6519,7 @@ Class _class_getClassForIvar(Class cls, Ivar ivar) mutex_locker_t lock(runtimeLock); for ( ; cls; cls = cls->superclass) { - if (auto ivars = cls->data()->ro->ivars) { + if (auto ivars = cls->data()->ro()->ivars) { if (ivars->containsIvar(ivar)) { return cls; } @@ -6463,7 +6569,7 @@ BOOL class_conformsToProtocol(Class cls, Protocol *proto_gen) ASSERT(cls->isRealized()); - for (const auto& proto_ref : cls->data()->protocols) { + for (const auto& proto_ref : cls->data()->protocols()) { protocol_t *p = remapProtocol(proto_ref); if (p == proto || protocol_conformsToProtocol_nolock(p, proto)) { return YES; @@ -6500,6 +6606,8 @@ BOOL class_conformsToProtocol(Class cls, Protocol *proto_gen) result = _method_setImplementation(cls, m, imp); } } else { + auto rwe = cls->data()->extAllocIfNeeded(); + // fixme optimize method_list_t *newlist; newlist = (method_list_t *)calloc(sizeof(*newlist), 1); @@ -6511,7 +6619,7 @@ BOOL class_conformsToProtocol(Class cls, Protocol *proto_gen) newlist->first.imp = imp; prepareMethodLists(cls, &newlist, 1, NO, NO); - cls->data()->methods.attachLists(&newlist, 1); + rwe->methods.attachLists(&newlist, 1); flushCaches(cls); result = nil; @@ -6578,6 +6686,8 @@ BOOL class_conformsToProtocol(Class cls, Protocol *proto_gen) } if (newlist->count > 0) { + auto rwe = cls->data()->extAllocIfNeeded(); + // fixme resize newlist because it may have been over-allocated above. // Note that realloc() alone doesn't work due to ptrauth. @@ -6585,7 +6695,7 @@ BOOL class_conformsToProtocol(Class cls, Protocol *proto_gen) std::stable_sort(newlist->begin(), newlist->end(), sorter); prepareMethodLists(cls, &newlist, 1, NO, NO); - cls->data()->methods.attachLists(&newlist, 1); + rwe->methods.attachLists(&newlist, 1); flushCaches(cls); } else { // Attaching the method list to the class consumes it. If we don't @@ -6685,7 +6795,7 @@ BOOL class_conformsToProtocol(Class cls, Protocol *proto_gen) // fixme allocate less memory here ivar_list_t *oldlist, *newlist; - if ((oldlist = (ivar_list_t *)cls->data()->ro->ivars)) { + if ((oldlist = (ivar_list_t *)cls->data()->ro()->ivars)) { size_t oldsize = oldlist->byteSize(); newlist = (ivar_list_t *)calloc(oldsize + oldlist->entsize(), 1); memcpy(newlist, oldlist, oldsize); @@ -6735,6 +6845,7 @@ BOOL class_addProtocol(Class cls, Protocol *protocol_gen) if (class_conformsToProtocol(cls, protocol_gen)) return NO; mutex_locker_t lock(runtimeLock); + auto rwe = cls->data()->extAllocIfNeeded(); ASSERT(cls->isRealized()); @@ -6744,7 +6855,7 @@ BOOL class_addProtocol(Class cls, Protocol *protocol_gen) protolist->count = 1; protolist->list[0] = (protocol_ref_t)protocol; - cls->data()->protocols.attachLists(&protolist, 1); + rwe->protocols.attachLists(&protolist, 1); // fixme metaclass? @@ -6779,6 +6890,7 @@ BOOL class_addProtocol(Class cls, Protocol *protocol_gen) } else { mutex_locker_t lock(runtimeLock); + auto rwe = cls->data()->extAllocIfNeeded(); ASSERT(cls->isRealized()); @@ -6789,7 +6901,7 @@ BOOL class_addProtocol(Class cls, Protocol *protocol_gen) proplist->first.name = strdupIfMutable(name); proplist->first.attributes = copyPropertyAttributeString(attrs, count); - cls->data()->properties.attachLists(&proplist, 1); + rwe->properties.attachLists(&proplist, 1); return YES; } @@ -6910,6 +7022,10 @@ bool includeClassHandler __attribute__((unused))) checkIsKnownClass(original); + auto orig_rw = original->data(); + auto orig_rwe = orig_rw->ext(); + auto orig_ro = orig_rw->ro(); + ASSERT(original->isRealized()); ASSERT(!original->isMetaClass()); @@ -6920,23 +7036,31 @@ bool includeClassHandler __attribute__((unused))) duplicate->cache.initializeToEmpty(); - class_rw_t *rw = (class_rw_t *)calloc(sizeof(*original->data()), 1); - rw->flags = (original->data()->flags | RW_COPIED_RO | RW_REALIZING); - rw->version = original->data()->version; + class_rw_t *rw = objc::zalloc(); + rw->flags = (orig_rw->flags | RW_COPIED_RO | RW_REALIZING); rw->firstSubclass = nil; rw->nextSiblingClass = nil; duplicate->bits = original->bits; duplicate->setData(rw); - rw->ro = original->data()->ro->duplicate(); - *(char **)&rw->ro->name = strdupIfMutable(name); + auto ro = orig_ro->duplicate(); + *(char **)&ro->name = strdupIfMutable(name); + rw->set_ro(ro); - rw->methods = original->data()->methods.duplicate(); + if (orig_rwe) { + auto rwe = rw->extAllocIfNeeded(); + rwe->version = orig_rwe->version; + rwe->methods = orig_rwe->methods.duplicate(); - // fixme dies when categories are added to the base - rw->properties = original->data()->properties; - rw->protocols = original->data()->protocols; + // fixme dies when categories are added to the base + rwe->properties = orig_rwe->properties; + rwe->protocols = orig_rwe->protocols; + } else if (ro->baseMethods()) { + // if we have base methods, we need to make a deep copy + // which requires a class_rw_ext_t to be allocated + rw->deepCopy(ro); + } duplicate->chooseClassArrayIndex(); @@ -6949,13 +7073,12 @@ bool includeClassHandler __attribute__((unused))) // Don't methodize class - construction above is correct - addNamedClass(duplicate, duplicate->data()->ro->name); + addNamedClass(duplicate, ro->name); addClassTableEntry(duplicate, /*addMeta=*/false); if (PrintConnecting) { _objc_inform("CLASS: realizing class '%s' (duplicate of %s) %p %p", - name, original->nameForLogging(), - (void*)duplicate, duplicate->data()->ro); + name, original->nameForLogging(), (void*)duplicate, ro); } duplicate->clearInfo(RW_REALIZING); @@ -6976,35 +7099,35 @@ static void objc_initializeClassPair_internal(Class superclass, const char *name runtimeLock.assertLocked(); class_ro_t *cls_ro_w, *meta_ro_w; + class_rw_t *cls_rw_w, *meta_rw_w; - cls->setData((class_rw_t *)calloc(sizeof(class_rw_t), 1)); - meta->setData((class_rw_t *)calloc(sizeof(class_rw_t), 1)); + cls_rw_w = objc::zalloc(); + meta_rw_w = objc::zalloc(); cls_ro_w = (class_ro_t *)calloc(sizeof(class_ro_t), 1); meta_ro_w = (class_ro_t *)calloc(sizeof(class_ro_t), 1); - cls->data()->ro = cls_ro_w; - meta->data()->ro = meta_ro_w; + + cls->setData(cls_rw_w); + cls_rw_w->set_ro(cls_ro_w); + meta->setData(meta_rw_w); + meta_rw_w->set_ro(meta_ro_w); // Set basic info - cls->data()->flags = RW_CONSTRUCTING | RW_COPIED_RO | RW_REALIZED | RW_REALIZING; - meta->data()->flags = RW_CONSTRUCTING | RW_COPIED_RO | RW_REALIZED | RW_REALIZING; - cls->data()->version = 0; - meta->data()->version = 7; + cls_rw_w->flags = RW_CONSTRUCTING | RW_COPIED_RO | RW_REALIZED | RW_REALIZING; + meta_rw_w->flags = RW_CONSTRUCTING | RW_COPIED_RO | RW_REALIZED | RW_REALIZING | RW_META; cls_ro_w->flags = 0; meta_ro_w->flags = RO_META; - if (!superclass) { - cls_ro_w->flags |= RO_ROOT; - meta_ro_w->flags |= RO_ROOT; - } if (superclass) { uint32_t flagsToCopy = RW_FORBIDS_ASSOCIATED_OBJECTS; - cls->data()->flags |= superclass->data()->flags & flagsToCopy; + cls_rw_w->flags |= superclass->data()->flags & flagsToCopy; cls_ro_w->instanceStart = superclass->unalignedInstanceSize(); meta_ro_w->instanceStart = superclass->ISA()->unalignedInstanceSize(); cls->setInstanceSize(cls_ro_w->instanceStart); meta->setInstanceSize(meta_ro_w->instanceStart); } else { + cls_ro_w->flags |= RO_ROOT; + meta_ro_w->flags |= RO_ROOT; cls_ro_w->instanceStart = 0; meta_ro_w->instanceStart = (uint32_t)sizeof(objc_class); cls->setInstanceSize((uint32_t)sizeof(id)); // just an isa @@ -7149,7 +7272,7 @@ void objc_registerClassPair(Class cls) (cls->ISA()->data()->flags & RW_CONSTRUCTED)) { _objc_inform("objc_registerClassPair: class '%s' was already " - "registered!", cls->data()->ro->name); + "registered!", cls->data()->ro()->name); return; } @@ -7158,7 +7281,7 @@ void objc_registerClassPair(Class cls) { _objc_inform("objc_registerClassPair: class '%s' was not " "allocated with objc_allocateClassPair!", - cls->data()->ro->name); + cls->data()->ro()->name); return; } @@ -7167,7 +7290,7 @@ void objc_registerClassPair(Class cls) cls->changeInfo(RW_CONSTRUCTED, RW_CONSTRUCTING | RW_REALIZING); // Add to named class table. - addNamedClass(cls, cls->data()->ro->name); + addNamedClass(cls, cls->data()->ro()->name); } @@ -7258,14 +7381,17 @@ static void free_class(Class cls) if (! cls->isRealized()) return; auto rw = cls->data(); - auto ro = rw->ro; + auto rwe = rw->ext(); + auto ro = rw->ro(); cache_delete(cls); - - for (auto& meth : rw->methods) { - try_free(meth.types); + + if (rwe) { + for (auto& meth : rwe->methods) { + try_free(meth.types); + } + rwe->methods.tryFree(); } - rw->methods.tryFree(); const ivar_list_t *ivars = ro->ivars; if (ivars) { @@ -7277,19 +7403,22 @@ static void free_class(Class cls) try_free(ivars); } - for (auto& prop : rw->properties) { - try_free(prop.name); - try_free(prop.attributes); - } - rw->properties.tryFree(); + if (rwe) { + for (auto& prop : rwe->properties) { + try_free(prop.name); + try_free(prop.attributes); + } + rwe->properties.tryFree(); - rw->protocols.tryFree(); + rwe->protocols.tryFree(); + } try_free(ro->ivarLayout); try_free(ro->weakIvarLayout); try_free(ro->name); try_free(ro); - try_free(rw); + objc::zfree(rwe); + objc::zfree(rw); try_free(cls); } @@ -7307,25 +7436,25 @@ void objc_disposeClassPair(Class cls) // disposing still-unregistered class is OK! _objc_inform("objc_disposeClassPair: class '%s' was not " "allocated with objc_allocateClassPair!", - cls->data()->ro->name); + cls->data()->ro()->name); return; } if (cls->isMetaClass()) { _objc_inform("objc_disposeClassPair: class '%s' is a metaclass, " - "not a class!", cls->data()->ro->name); + "not a class!", cls->data()->ro()->name); return; } // Shouldn't have any live subclasses. if (cls->data()->firstSubclass) { _objc_inform("objc_disposeClassPair: class '%s' still has subclasses, " - "including '%s'!", cls->data()->ro->name, + "including '%s'!", cls->data()->ro()->name, cls->data()->firstSubclass->nameForLogging()); } if (cls->ISA()->data()->firstSubclass) { _objc_inform("objc_disposeClassPair: class '%s' still has subclasses, " - "including '%s'!", cls->data()->ro->name, + "including '%s'!", cls->data()->ro()->name, cls->ISA()->data()->firstSubclass->nameForLogging()); } diff --git a/runtime/objc-zalloc.h b/runtime/objc-zalloc.h new file mode 100644 index 0000000..ef8203c --- /dev/null +++ b/runtime/objc-zalloc.h @@ -0,0 +1,125 @@ +/* + * Copyright (c) 2019 Apple Inc. All Rights Reserved. + * + * @APPLE_LICENSE_HEADER_START@ + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPLE_LICENSE_HEADER_END@ + */ + +/** + * @file objc-zalloc.h + * + * "zone allocator" for objc. + * + * Provides packed allocation for data structures the runtime + * almost never frees. + */ + +#ifndef _OBJC_ZALLOC_H +#define _OBJC_ZALLOC_H + +#include +#include +#include + +namespace objc { + +// Darwin malloc always aligns to 16 bytes +#define MALLOC_ALIGNMENT 16 + +class AtomicQueue { +#if __LP64__ + using pair_t = __int128_t; +#else + using pair_t = uint64_t; +#endif + static constexpr auto relaxed = std::memory_order_relaxed; + static constexpr auto release = std::memory_order_release; + + struct Entry { + struct Entry *next; + }; + + union { + struct { + Entry *head; + unsigned long gen; + }; + std::atomic atomic_pair; + pair_t pair; + }; + +public: + void *pop(); + void push_list(void *_head, void *_tail); + inline void push(void *head) + { + push_list(head, head); + } +}; + +template +class Zone { +}; + +template +class Zone { + struct Element { + Element *next; + char buf[sizeof(T) - sizeof(void *)]; + } __attribute__((packed)); + + static AtomicQueue _freelist; + static T *alloc_slow(); + +public: + static T *alloc(); + static void free(T *); +}; + +template +class Zone { +public: + static inline T *alloc() { + return reinterpret_cast(::calloc(sizeof(T), 1)); + } + static inline void free(T *ptr) { + ::free(ptr); + } +}; + +/* + * This allocator returns always zeroed memory, + * and the template needs to be instantiated in objc-zalloc.mm + */ + +template +T *zalloc() +{ + return Zone::alloc(); +} + +template +void zfree(T *e) +{ + Zone::free(e); +} + +}; + +#endif diff --git a/runtime/objc-zalloc.mm b/runtime/objc-zalloc.mm new file mode 100644 index 0000000..cdfd043 --- /dev/null +++ b/runtime/objc-zalloc.mm @@ -0,0 +1,125 @@ +/* + * Copyright (c) 2007 Apple Inc. All Rights Reserved. + * + * @APPLE_LICENSE_HEADER_START@ + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPLE_LICENSE_HEADER_END@ + */ + +/** + * @file objc-zalloc.h + * + * "zone allocator" for objc. + * + * Provides packed allocation for data structures the runtime + * almost never frees. + */ + +#include "objc-private.h" +#include "objc-zalloc.h" + +namespace objc { + +void *AtomicQueue::pop() +{ + AtomicQueue l1, l2; + + l1.pair = pair; // non atomic on purpose + + do { + if (l1.head == nullptr) { + return nullptr; + } + l2.head = l1.head->next; + l2.gen = l1.gen + 1; + } while (!atomic_pair.compare_exchange_weak(l1.pair, l2.pair, relaxed, relaxed)); + + return reinterpret_cast(l1.head); +} + +void AtomicQueue::push_list(void *_head, void *_tail) +{ + Entry *head = reinterpret_cast(_head); + Entry *tail = reinterpret_cast(_tail); + AtomicQueue l1, l2; + + l1.pair = pair; // non atomic load on purpose + do { + tail->next = l1.head; + l2.head = head; + l2.gen = l1.gen + 1; + } while (!atomic_pair.compare_exchange_weak(l1.pair, l2.pair, release, relaxed)); +} + +template +constexpr inline +T gcd(T a, T b) +{ + return b == 0 ? a : gcd(b, a % b); +} + +template +AtomicQueue Zone::_freelist; + +template +T *Zone::alloc_slow() +{ + // our malloc aligns to 16 bytes and this code should be used for sizes + // small enough that this should always be an actual malloc bucket. + // + // The point of this code is *NOT* speed but optimal density + constexpr size_t n_elem = MALLOC_ALIGNMENT / gcd(sizeof(T), size_t{MALLOC_ALIGNMENT}); + Element *slab = reinterpret_cast(::calloc(n_elem, sizeof(T))); + for (size_t i = 1; i < n_elem - 1; i++) { + slab[i].next = &slab[i + 1]; + } + _freelist.push_list(reinterpret_cast(&slab[1]), + reinterpret_cast(&slab[n_elem - 1])); + return reinterpret_cast(&slab[0]); +} + +template +T *Zone::alloc() +{ + void *e = _freelist.pop(); + if (e) { + __builtin_bzero(e, sizeof(void *)); + return reinterpret_cast(e); + } + return alloc_slow(); +} + +template +void Zone::free(T *ptr) +{ + if (ptr) { + Element *e = reinterpret_cast(ptr); + __builtin_bzero(e->buf, sizeof(e->buf)); + _freelist.push(e); + } +} + +#if __OBJC2__ +#define ZoneInstantiate(type) \ + template class Zone + +ZoneInstantiate(class_rw_t); +ZoneInstantiate(class_rw_ext_t); +#endif + +} diff --git a/test/runtime.m b/test/runtime.m index c70620b..ec8fda8 100644 --- a/test/runtime.m +++ b/test/runtime.m @@ -126,7 +126,14 @@ int main() count = objc_getClassList(list, count0-1); testassert(list[count0-1] == NULL); testassert(count == count0); - + + // So that demangling works, fake what would have happened with Swift + // and force the "Swift" bit on the class + ((uintptr_t *)objc_getClass(SwiftV1MangledName))[4] |= 2; + ((uintptr_t *)objc_getClass(SwiftV1MangledName2))[4] |= 2; + ((uintptr_t *)objc_getClass(SwiftV1MangledName3))[4] |= 2; + ((uintptr_t *)objc_getClass(SwiftV1MangledName4))[4] |= 2; + count = objc_getClassList(list, count0); testassert(count == count0); From 150008e621e6b236f4aed6452e31e7eb7bfc3a7a Mon Sep 17 00:00:00 2001 From: Apple Open Source Date: Tue, 15 Jun 2021 22:39:24 +0200 Subject: [PATCH 10/12] Version 781.2 --- runtime/objc-runtime-new.mm | 4 +++- test/runtime.m | 8 ++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/runtime/objc-runtime-new.mm b/runtime/objc-runtime-new.mm index ee12127..e158375 100644 --- a/runtime/objc-runtime-new.mm +++ b/runtime/objc-runtime-new.mm @@ -5604,7 +5604,9 @@ void objc_registerProtocol(Protocol *proto_gen) // Return previously demangled name if available. if (isRealized() || isFuture()) { - if (!isAnySwift()) { + // Swift metaclasses don't have the is-Swift bit. + // We can't take this shortcut for them. + if (!isMetaClass() && !isAnySwift()) { return data()->ro()->name; } auto rwe = data()->ext(); diff --git a/test/runtime.m b/test/runtime.m index ec8fda8..50bd68c 100644 --- a/test/runtime.m +++ b/test/runtime.m @@ -212,6 +212,14 @@ int main() testassert(list2[count] == NULL); free(list2); free(objc_copyClassList(NULL)); + + // Make sure metaclasses get demangled too. + testassert(strcmp(class_getName([TestRoot class]), class_getName(object_getClass([TestRoot class]))) == 0); + testassert(strcmp(class_getName([Sub class]), class_getName(object_getClass([Sub class]))) == 0); + testassert(strcmp(class_getName([SwiftV1Class class]), class_getName(object_getClass([SwiftV1Class class]))) == 0); + testassert(strcmp(class_getName([SwiftV1Class2 class]), class_getName(object_getClass([SwiftV1Class2 class]))) == 0); + testassert(strcmp(class_getName([SwiftV1Class3 class]), class_getName(object_getClass([SwiftV1Class3 class]))) == 0); + testassert(strcmp(class_getName([SwiftV1Class4 class]), class_getName(object_getClass([SwiftV1Class4 class]))) == 0); succeed(__FILE__); } From 83fa8aa33391a5cc0991a6499ab8a5155f7f3209 Mon Sep 17 00:00:00 2001 From: Apple Open Source Date: Tue, 15 Jun 2021 22:40:08 +0200 Subject: [PATCH 11/12] Version 787.1 --- libobjc.order | 2 - objc.xcodeproj/project.pbxproj | 202 +++++++++- objcdt/json.h | 82 +++++ objcdt/json.mm | 234 ++++++++++++ objcdt/objcdt-entitlements.plist | 10 + objcdt/objcdt.1 | 20 + objcdt/objcdt.mm | 34 ++ runtime/Messengers.subproj/objc-msg-arm.s | 80 +++- runtime/Messengers.subproj/objc-msg-arm64.s | 117 ++++-- .../objc-msg-simulator-i386.s | 92 ++++- .../objc-msg-simulator-x86_64.s | 140 +++++-- runtime/Messengers.subproj/objc-msg-x86_64.s | 140 +++++-- runtime/PointerUnion.h | 32 +- runtime/objc-cache.mm | 12 + runtime/objc-file.h | 14 +- runtime/objc-internal.h | 8 +- runtime/objc-opt.mm | 218 +++++++---- runtime/objc-os.h | 3 +- runtime/objc-os.mm | 15 +- runtime/objc-private.h | 73 +++- runtime/objc-ptrauth.h | 114 ++++++ runtime/objc-runtime-new.h | 346 +++++++++++++----- runtime/objc-runtime-new.mm | 280 ++++++++------ runtime/objc-sel-old.mm | 13 - runtime/objc-sel.mm | 43 +-- runtime/objc.h | 3 +- test/gdb.m | 26 +- test/libraryPath.c | 44 +++ test/load-noobjc.m | 4 + test/methodListSmall.h | 226 ++++++++++++ test/methodListSmall.mm | 89 +++++ test/methodListSmallMutableMemory.mm | 18 + test/nonpointerisa.m | 10 +- test/supported-inline-refcnt.m | 85 +++++ test/test.h | 13 + test/test.pl | 58 ++- 36 files changed, 2391 insertions(+), 509 deletions(-) create mode 100644 objcdt/json.h create mode 100644 objcdt/json.mm create mode 100644 objcdt/objcdt-entitlements.plist create mode 100644 objcdt/objcdt.1 create mode 100644 objcdt/objcdt.mm create mode 100644 test/libraryPath.c create mode 100644 test/methodListSmall.h create mode 100644 test/methodListSmall.mm create mode 100644 test/methodListSmallMutableMemory.mm create mode 100644 test/supported-inline-refcnt.m diff --git a/libobjc.order b/libobjc.order index c7415fc..1d56c59 100644 --- a/libobjc.order +++ b/libobjc.order @@ -19,8 +19,6 @@ __objc_update_stubs_in_mach_header _sel_init ___sel_registerName __objc_search_builtins -__ZNK8objc_opt13objc_selopt_t3getEPKc -__ZNK8objc_opt13objc_selopt_t4hashEPKc _sel_registerName _arr_init __ZN4objc8DenseMapIP11objc_objectmLb1ENS_12DenseMapInfoIS2_EENS3_ImEEE4initEj diff --git a/objc.xcodeproj/project.pbxproj b/objc.xcodeproj/project.pbxproj index 80c47fb..5587470 100644 --- a/objc.xcodeproj/project.pbxproj +++ b/objc.xcodeproj/project.pbxproj @@ -7,6 +7,17 @@ objects = { /* Begin PBXAggregateTarget section */ + 6EF877EF23263D7000963DBB /* objc_executables */ = { + isa = PBXAggregateTarget; + buildConfigurationList = 6EF877F223263D7000963DBB /* Build configuration list for PBXAggregateTarget "objc_executables" */; + buildPhases = ( + ); + dependencies = ( + 6EF877F423263D8000963DBB /* PBXTargetDependency */, + ); + name = objc_executables; + productName = "objc-executables"; + }; 834F9B01212E560100F95A54 /* objc4_tests */ = { isa = PBXAggregateTarget; buildConfigurationList = 834F9B04212E560200F95A54 /* Build configuration list for PBXAggregateTarget "objc4_tests" */; @@ -45,6 +56,14 @@ 6EACB842232C97A400CE9176 /* objc-zalloc.h in Headers */ = {isa = PBXBuildFile; fileRef = 6EACB841232C97A400CE9176 /* objc-zalloc.h */; }; 6EACB844232C97B900CE9176 /* objc-zalloc.mm in Sources */ = {isa = PBXBuildFile; fileRef = 6EACB843232C97B900CE9176 /* objc-zalloc.mm */; }; 6ECD0B1F2244999E00910D88 /* llvm-DenseSet.h in Headers */ = {isa = PBXBuildFile; fileRef = 6ECD0B1E2244999E00910D88 /* llvm-DenseSet.h */; }; + 6EF877DA2325D62600963DBB /* objcdt.mm in Sources */ = {isa = PBXBuildFile; fileRef = 6EF877D92325D62600963DBB /* objcdt.mm */; }; + 6EF877DE2325D79000963DBB /* objc-probes.d in Sources */ = {isa = PBXBuildFile; fileRef = 87BB4E900EC39633005D08E1 /* objc-probes.d */; }; + 6EF877E02325D92E00963DBB /* CoreSymbolication.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6EF877DF2325D92E00963DBB /* CoreSymbolication.framework */; }; + 6EF877E22325D93200963DBB /* Symbolication.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6EF877E12325D93200963DBB /* Symbolication.framework */; }; + 6EF877E52325FAC400963DBB /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6EF877E42325FAC400963DBB /* Foundation.framework */; }; + 6EF877E82326184000963DBB /* json.mm in Sources */ = {isa = PBXBuildFile; fileRef = 6EF877E72326184000963DBB /* json.mm */; }; + 6EF877E923261D3E00963DBB /* objc-cache.mm in Sources */ = {isa = PBXBuildFile; fileRef = 838485CB0D6D68A200CEA253 /* objc-cache.mm */; }; + 6EF877EC232635A700963DBB /* objcdt.1 in Install Manpages */ = {isa = PBXBuildFile; fileRef = 6EF877EA232633CC00963DBB /* objcdt.1 */; }; 7213C36321FA7C730090A271 /* NSObject-internal.h in Headers */ = {isa = PBXBuildFile; fileRef = 7213C36221FA7C730090A271 /* NSObject-internal.h */; settings = {ATTRIBUTES = (Private, ); }; }; 7593EC58202248E50046AB96 /* objc-object.h in Headers */ = {isa = PBXBuildFile; fileRef = 7593EC57202248DF0046AB96 /* objc-object.h */; }; 75A9504F202BAA0600D7D56F /* objc-locks-new.h in Headers */ = {isa = PBXBuildFile; fileRef = 75A9504E202BAA0300D7D56F /* objc-locks-new.h */; }; @@ -128,12 +147,20 @@ 83F550E0155E030800E95D3B /* objc-cache-old.mm in Sources */ = {isa = PBXBuildFile; fileRef = 83F550DF155E030800E95D3B /* objc-cache-old.mm */; }; 87BB4EA70EC39854005D08E1 /* objc-probes.d in Sources */ = {isa = PBXBuildFile; fileRef = 87BB4E900EC39633005D08E1 /* objc-probes.d */; }; 9672F7EE14D5F488007CEC96 /* NSObject.mm in Sources */ = {isa = PBXBuildFile; fileRef = 9672F7ED14D5F488007CEC96 /* NSObject.mm */; }; + C22F5208230EF38B001BFE14 /* objc-ptrauth.h in Headers */ = {isa = PBXBuildFile; fileRef = C22F5207230EF38B001BFE14 /* objc-ptrauth.h */; }; C2E6D3FC2225DCF00059DFAA /* DenseMapExtras.h in Headers */ = {isa = PBXBuildFile; fileRef = C2E6D3FB2225DCF00059DFAA /* DenseMapExtras.h */; }; E8923DA5116AB2820071B552 /* objc-block-trampolines.mm in Sources */ = {isa = PBXBuildFile; fileRef = E8923DA0116AB2820071B552 /* objc-block-trampolines.mm */; }; F9BCC71B205C68E800DD9AFC /* objc-blocktramps-arm64.s in Sources */ = {isa = PBXBuildFile; fileRef = 8379996D13CBAF6F007C2B5F /* objc-blocktramps-arm64.s */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ + 6EF877F323263D8000963DBB /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 08FB7793FE84155DC02AAC07 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 6EF877D62325D62600963DBB; + remoteInfo = objcdt; + }; 837F67AC1A771F6E004D34FA /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 08FB7793FE84155DC02AAC07 /* Project object */; @@ -150,6 +177,20 @@ }; /* End PBXContainerItemProxy section */ +/* Begin PBXCopyFilesBuildPhase section */ + 6EF877D52325D62600963DBB /* Install Manpages */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = /usr/local/share/man/man1/; + dstSubfolderSpec = 0; + files = ( + 6EF877EC232635A700963DBB /* objcdt.1 in Install Manpages */, + ); + name = "Install Manpages"; + runOnlyForDeploymentPostprocessing = 1; + }; +/* End PBXCopyFilesBuildPhase section */ + /* Begin PBXFileReference section */ 393CEABF0DC69E3E000B69DE /* objc-references.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = "objc-references.mm"; path = "runtime/objc-references.mm"; sourceTree = ""; }; 393CEAC50DC69E67000B69DE /* objc-references.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "objc-references.h"; path = "runtime/objc-references.h"; sourceTree = ""; }; @@ -164,6 +205,15 @@ 6EACB841232C97A400CE9176 /* objc-zalloc.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "objc-zalloc.h"; path = "runtime/objc-zalloc.h"; sourceTree = ""; }; 6EACB843232C97B900CE9176 /* objc-zalloc.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = "objc-zalloc.mm"; path = "runtime/objc-zalloc.mm"; sourceTree = ""; }; 6ECD0B1E2244999E00910D88 /* llvm-DenseSet.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "llvm-DenseSet.h"; path = "runtime/llvm-DenseSet.h"; sourceTree = ""; }; + 6EF877D72325D62600963DBB /* objcdt */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = objcdt; sourceTree = BUILT_PRODUCTS_DIR; }; + 6EF877D92325D62600963DBB /* objcdt.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = objcdt.mm; sourceTree = ""; usesTabs = 0; }; + 6EF877DF2325D92E00963DBB /* CoreSymbolication.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreSymbolication.framework; path = System/Library/PrivateFrameworks/CoreSymbolication.framework; sourceTree = SDKROOT; }; + 6EF877E12325D93200963DBB /* Symbolication.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Symbolication.framework; path = System/Library/PrivateFrameworks/Symbolication.framework; sourceTree = SDKROOT; }; + 6EF877E32325D95300963DBB /* objcdt-entitlements.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "objcdt-entitlements.plist"; sourceTree = ""; }; + 6EF877E42325FAC400963DBB /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; + 6EF877E62326184000963DBB /* json.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = json.h; sourceTree = ""; usesTabs = 1; }; + 6EF877E72326184000963DBB /* json.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = json.mm; sourceTree = ""; usesTabs = 1; }; + 6EF877EA232633CC00963DBB /* objcdt.1 */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.man; path = objcdt.1; sourceTree = ""; }; 7213C36221FA7C730090A271 /* NSObject-internal.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "NSObject-internal.h"; path = "runtime/NSObject-internal.h"; sourceTree = ""; }; 7593EC57202248DF0046AB96 /* objc-object.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "objc-object.h"; path = "runtime/objc-object.h"; sourceTree = ""; }; 75A9504E202BAA0300D7D56F /* objc-locks-new.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "objc-locks-new.h"; path = "runtime/objc-locks-new.h"; sourceTree = ""; }; @@ -252,6 +302,8 @@ 87BB4E900EC39633005D08E1 /* objc-probes.d */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.dtrace; name = "objc-probes.d"; path = "runtime/objc-probes.d"; sourceTree = ""; }; 9672F7ED14D5F488007CEC96 /* NSObject.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = NSObject.mm; path = runtime/NSObject.mm; sourceTree = ""; }; BC8B5D1212D3D48100C78A5B /* libauto.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libauto.dylib; path = /usr/lib/libauto.dylib; sourceTree = ""; }; + C217B55222DE556D004369BA /* objc-env.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "objc-env.h"; path = "runtime/objc-env.h"; sourceTree = ""; }; + C22F5207230EF38B001BFE14 /* objc-ptrauth.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "objc-ptrauth.h"; path = "runtime/objc-ptrauth.h"; sourceTree = ""; }; C2E6D3FB2225DCF00059DFAA /* DenseMapExtras.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = DenseMapExtras.h; path = runtime/DenseMapExtras.h; sourceTree = ""; }; D2AAC0630554660B00DB518D /* libobjc.A.dylib */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.dylib"; includeInIndex = 0; path = libobjc.A.dylib; sourceTree = BUILT_PRODUCTS_DIR; }; E8923D9C116AB2820071B552 /* objc-blocktramps-i386.s */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.asm; name = "objc-blocktramps-i386.s"; path = "runtime/objc-blocktramps-i386.s"; sourceTree = ""; }; @@ -261,6 +313,16 @@ /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ + 6EF877D42325D62600963DBB /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 6EF877E22325D93200963DBB /* Symbolication.framework in Frameworks */, + 6EF877E52325FAC400963DBB /* Foundation.framework in Frameworks */, + 6EF877E02325D92E00963DBB /* CoreSymbolication.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; D289988505E68E00004EDB86 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; @@ -289,6 +351,7 @@ 838486270D6D690F00CEA253 /* Obsolete Source */, 08FB7795FE84155DC02AAC07 /* Source */, 838485B20D6D67F900CEA253 /* Other */, + 6EF877D82325D62600963DBB /* objcdt */, 1AB674ADFE9D54B511CA2CBB /* Products */, F9BCC72A205C6A1600DD9AFC /* Frameworks */, ); @@ -352,10 +415,23 @@ children = ( D2AAC0630554660B00DB518D /* libobjc.A.dylib */, F9BCC727205C68E800DD9AFC /* libobjc-trampolines.dylib */, + 6EF877D72325D62600963DBB /* objcdt */, ); name = Products; sourceTree = ""; }; + 6EF877D82325D62600963DBB /* objcdt */ = { + isa = PBXGroup; + children = ( + 6EF877EA232633CC00963DBB /* objcdt.1 */, + 6EF877E62326184000963DBB /* json.h */, + 6EF877E72326184000963DBB /* json.mm */, + 6EF877D92325D62600963DBB /* objcdt.mm */, + 6EF877E32325D95300963DBB /* objcdt-entitlements.plist */, + ); + path = objcdt; + sourceTree = ""; + }; 838485B20D6D67F900CEA253 /* Other */ = { isa = PBXGroup; children = ( @@ -389,12 +465,13 @@ 838485C70D6D688200CEA253 /* Private Headers */ = { isa = PBXGroup; children = ( + 838485BB0D6D687300CEA253 /* maptable.h */, 7213C36221FA7C730090A271 /* NSObject-internal.h */, - 83112ED30F00599600A5FBAF /* objc-internal.h */, 834EC0A311614167009B2563 /* objc-abi.h */, - 838485BB0D6D687300CEA253 /* maptable.h */, - 834266D70E665A8B002E4DA2 /* objc-gdb.h */, 8306440620D24A3E00E356D2 /* objc-block-trampolines.h */, + 834266D70E665A8B002E4DA2 /* objc-gdb.h */, + 83112ED30F00599600A5FBAF /* objc-internal.h */, + C22F5207230EF38B001BFE14 /* objc-ptrauth.h */, ); name = "Private Headers"; sourceTree = ""; @@ -437,6 +514,7 @@ 83D9269721225A7400299F69 /* arm64-asm.h */, 83D92695212254CF00299F69 /* isa.h */, 838485CF0D6D68A200CEA253 /* objc-config.h */, + C217B55222DE556D004369BA /* objc-env.h */, 83BE02E50FCCB24D00661494 /* objc-file-old.h */, 83BE02E60FCCB24D00661494 /* objc-file.h */, 838485D40D6D68A200CEA253 /* objc-initialize.h */, @@ -460,6 +538,9 @@ F9BCC72A205C6A1600DD9AFC /* Frameworks */ = { isa = PBXGroup; children = ( + 6EF877E42325FAC400963DBB /* Foundation.framework */, + 6EF877E12325D93200963DBB /* Symbolication.framework */, + 6EF877DF2325D92E00963DBB /* CoreSymbolication.framework */, ); name = Frameworks; sourceTree = ""; @@ -528,6 +609,7 @@ 838486200D6D68A800CEA253 /* runtime.h in Headers */, 39ABD72312F0B61800D1054C /* objc-weak.h in Headers */, 83F4B52815E843B100E0926F /* NSObjCRuntime.h in Headers */, + C22F5208230EF38B001BFE14 /* objc-ptrauth.h in Headers */, 6ECD0B1F2244999E00910D88 /* llvm-DenseSet.h in Headers */, 83F4B52915E843B100E0926F /* NSObject.h in Headers */, ); @@ -536,6 +618,23 @@ /* End PBXHeadersBuildPhase section */ /* Begin PBXNativeTarget section */ + 6EF877D62325D62600963DBB /* objcdt */ = { + isa = PBXNativeTarget; + buildConfigurationList = 6EF877DD2325D62600963DBB /* Build configuration list for PBXNativeTarget "objcdt" */; + buildPhases = ( + 6EF877D32325D62600963DBB /* Sources */, + 6EF877D42325D62600963DBB /* Frameworks */, + 6EF877D52325D62600963DBB /* Install Manpages */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = objcdt; + productName = objcdt; + productReference = 6EF877D72325D62600963DBB /* objcdt */; + productType = "com.apple.product-type.tool"; + }; D2AAC0620554660B00DB518D /* objc */ = { isa = PBXNativeTarget; buildConfigurationList = 1DEB914A08733D8E0010E9CD /* Build configuration list for PBXNativeTarget "objc" */; @@ -582,6 +681,13 @@ BuildIndependentTargetsInParallel = NO; LastUpgradeCheck = 0440; TargetAttributes = { + 6EF877D62325D62600963DBB = { + CreatedOnToolsVersion = 11.0; + }; + 6EF877EF23263D7000963DBB = { + CreatedOnToolsVersion = 11.0; + ProvisioningStyle = Automatic; + }; 834F9B01212E560100F95A54 = { CreatedOnToolsVersion = 10.0; DevelopmentTeam = 59GAB85EFG; @@ -610,6 +716,8 @@ 837F67A81A771F63004D34FA /* objc-simulator */, F9BCC6CA205C68E800DD9AFC /* objc-trampolines */, 834F9B01212E560100F95A54 /* objc4_tests */, + 6EF877EF23263D7000963DBB /* objc_executables */, + 6EF877D62325D62600963DBB /* objcdt */, ); }; /* End PBXProject section */ @@ -665,6 +773,17 @@ /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ + 6EF877D32325D62600963DBB /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 6EF877E923261D3E00963DBB /* objc-cache.mm in Sources */, + 6EF877E82326184000963DBB /* json.mm in Sources */, + 6EF877DA2325D62600963DBB /* objcdt.mm in Sources */, + 6EF877DE2325D79000963DBB /* objc-probes.d in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; D2AAC0610554660B00DB518D /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; @@ -729,6 +848,11 @@ /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ + 6EF877F423263D8000963DBB /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 6EF877D62325D62600963DBB /* objcdt */; + targetProxy = 6EF877F323263D8000963DBB /* PBXContainerItemProxy */; + }; 837F67AD1A771F6E004D34FA /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = D2AAC0620554660B00DB518D /* objc */; @@ -995,6 +1119,58 @@ }; name = Release; }; + 6EF877DB2325D62600963DBB /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_ENTITLEMENTS = "objcdt/objcdt-entitlements.plist"; + CODE_SIGN_IDENTITY = "-"; + GCC_PREPROCESSOR_DEFINITIONS = ( + "__BUILDING_OBJCDT__=1", + "$(inherited)", + ); + HEADER_SEARCH_PATHS = ( + "$(SRCROOT)/runtime", + /System/Library/Frameworks/System.framework/PrivateHeaders, + ); + PRODUCT_NAME = "$(TARGET_NAME)"; + SYSTEM_FRAMEWORK_SEARCH_PATHS = "$(inherited) $(SDKROOT)$(SYSTEM_LIBRARY_DIR)/PrivateFrameworks"; + }; + name = Debug; + }; + 6EF877DC2325D62600963DBB /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_ENTITLEMENTS = "objcdt/objcdt-entitlements.plist"; + CODE_SIGN_IDENTITY = "-"; + GCC_PREPROCESSOR_DEFINITIONS = ( + "__BUILDING_OBJCDT__=1", + "$(inherited)", + ); + HEADER_SEARCH_PATHS = ( + "$(SRCROOT)/runtime", + /System/Library/Frameworks/System.framework/PrivateHeaders, + ); + PRODUCT_NAME = "$(TARGET_NAME)"; + SYSTEM_FRAMEWORK_SEARCH_PATHS = "$(inherited) $(SDKROOT)$(SYSTEM_LIBRARY_DIR)/PrivateFrameworks"; + }; + name = Release; + }; + 6EF877F023263D7000963DBB /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Debug; + }; + 6EF877F123263D7000963DBB /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Release; + }; 834F9B02212E560200F95A54 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { @@ -1034,6 +1210,7 @@ COPY_HEADERS_UNIFDEF_FLAGS = "-UBUILD_FOR_OSX"; "COPY_HEADERS_UNIFDEF_FLAGS[sdk=macosx*]" = "-DBUILD_FOR_OSX"; COPY_PHASE_STRIP = NO; + DEPLOYMENT_LOCATION = YES; DYLIB_CURRENT_VERSION = 228; EXECUTABLE_PREFIX = lib; GCC_CW_ASM_SYNTAX = NO; @@ -1070,6 +1247,7 @@ COPY_HEADERS_RUN_UNIFDEF = YES; COPY_HEADERS_UNIFDEF_FLAGS = "-UBUILD_FOR_OSX"; "COPY_HEADERS_UNIFDEF_FLAGS[sdk=macosx*]" = "-DBUILD_FOR_OSX"; + DEPLOYMENT_LOCATION = YES; DYLIB_CURRENT_VERSION = 228; EXECUTABLE_PREFIX = lib; GCC_CW_ASM_SYNTAX = NO; @@ -1120,6 +1298,24 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; + 6EF877DD2325D62600963DBB /* Build configuration list for PBXNativeTarget "objcdt" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 6EF877DB2325D62600963DBB /* Debug */, + 6EF877DC2325D62600963DBB /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 6EF877F223263D7000963DBB /* Build configuration list for PBXAggregateTarget "objc_executables" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 6EF877F023263D7000963DBB /* Debug */, + 6EF877F123263D7000963DBB /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; 834F9B04212E560200F95A54 /* Build configuration list for PBXAggregateTarget "objc4_tests" */ = { isa = XCConfigurationList; buildConfigurations = ( diff --git a/objcdt/json.h b/objcdt/json.h new file mode 100644 index 0000000..95dbea2 --- /dev/null +++ b/objcdt/json.h @@ -0,0 +1,82 @@ +/* +* Copyright (c) 2019 Apple Inc. All Rights Reserved. +* +* @APPLE_LICENSE_HEADER_START@ +* +* This file contains Original Code and/or Modifications of Original Code +* as defined in and that are subject to the Apple Public Source License +* Version 2.0 (the 'License'). You may not use this file except in +* compliance with the License. Please obtain a copy of the License at +* http://www.opensource.apple.com/apsl/ and read it before using this +* file. +* +* The Original Code and all software distributed under the License are +* distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER +* EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, +* INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. +* Please see the License for the specific language governing rights and +* limitations under the License. +* +* @APPLE_LICENSE_HEADER_END@ +*/ + +#ifndef _OBJC_OBJCDT_JSON_H_ +#define _OBJC_OBJCDT_JSON_H_ + +#include +#include +#include +#include + +namespace json { + +enum context: uint8_t { + root, + array_value, + object_value, + object_key, + done, +}; + +class writer { +private: + FILE *_file; + context _context; + int _depth; + bool _needs_comma; + + void begin_value(int sep = '\0'); + void advance(context old); + void key(const char *key); + +public: + + writer(FILE *f); + ~writer(); + + void object(std::function); + void object(const char *key, std::function); + + void array(std::function); + void array(const char *key, std::function); + + void boolean(bool value); + void boolean(const char *key, bool value); + + void number(uint64_t value); + void number(const char *key, uint64_t value); + + void string(const char *s); + void string(const char *key, const char *s); + + __printflike(2, 3) + void stringf(const char *fmt, ...); + + __printflike(3, 4) + void stringf(const char *key, const char *fmt, ...); +}; + +} + +#endif /* _OBJC_OBJCDT_JSON_H_ */ diff --git a/objcdt/json.mm b/objcdt/json.mm new file mode 100644 index 0000000..7eb7488 --- /dev/null +++ b/objcdt/json.mm @@ -0,0 +1,234 @@ +/* +* Copyright (c) 2019 Apple Inc. All Rights Reserved. +* +* @APPLE_LICENSE_HEADER_START@ +* +* This file contains Original Code and/or Modifications of Original Code +* as defined in and that are subject to the Apple Public Source License +* Version 2.0 (the 'License'). You may not use this file except in +* compliance with the License. Please obtain a copy of the License at +* http://www.opensource.apple.com/apsl/ and read it before using this +* file. +* +* The Original Code and all software distributed under the License are +* distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER +* EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, +* INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. +* Please see the License for the specific language governing rights and +* limitations under the License. +* +* @APPLE_LICENSE_HEADER_END@ +*/ + +#include +#include "json.h" + +namespace json { + +static bool +context_is_value(context c) +{ + return c == root || c == array_value || c == object_value; +} + +writer::writer(FILE *f) +: _file(f) +, _context(root) +, _depth(0) +, _needs_comma(false) +{ +} + +writer::~writer() +{ + fputc('\n', _file); + fflush(_file); +} + +void +writer::begin_value(int sep) +{ + if (_needs_comma) { + _needs_comma = false; + if (sep) { + fprintf(_file, ", %c\n", sep); + return; + } + fputs(",\n", _file); + } + if (_context == array_value || _context == object_key) { + fprintf(_file, "%*s", _depth * 2, ""); + } + if (sep) { + fprintf(_file, "%c\n", sep); + } +} + +void +writer::advance(context c) +{ + switch (c) { + case root: + _context = done; + _needs_comma = false; + break; + case array_value: + _context = array_value; + _needs_comma = true; + break; + case object_value: + _context = object_key; + _needs_comma = true; + break; + case object_key: + _context = object_value; + _needs_comma = false; + break; + case done: + assert(false); + break; + } +} + +void +writer::key(const char *key) +{ + assert(_context == object_key); + + begin_value(); + fprintf(_file, "\"%s\": ", key); + advance(_context); +} + +void +writer::object(std::function f) +{ + context old = _context; + assert(context_is_value(old)); + + begin_value('{'); + + _depth++; + _context = object_key; + _needs_comma = false; + f(); + + _depth--; + fprintf(_file, "\n%*s}", _depth * 2, ""); + advance(old); +} + +void +writer::object(const char *k, std::function f) +{ + key(k); + object(f); +} + +void +writer::array(std::function f) +{ + context old = _context; + assert(context_is_value(old)); + + begin_value('['); + + _depth++; + _context = array_value; + _needs_comma = false; + f(); + + _depth--; + fprintf(_file, "\n%*s]", _depth * 2, ""); + advance(old); +} + +void +writer::array(const char *k, std::function f) +{ + key(k); + array(f); +} + +void +writer::boolean(bool value) +{ + assert(context_is_value(_context)); + begin_value(); + fputs(value ? "true" : "false", _file); + advance(_context); +} + +void +writer::boolean(const char *k, bool value) +{ + key(k); + boolean(value); +} + +void +writer::number(uint64_t value) +{ + assert(context_is_value(_context)); + begin_value(); + fprintf(_file, "%lld", value); + advance(_context); +} + +void +writer::number(const char *k, uint64_t value) +{ + key(k); + number(value); +} + +void +writer::string(const char *s) +{ + assert(context_is_value(_context)); + begin_value(); + fprintf(_file, "\"%s\"", s); + advance(_context); +} + +void +writer::string(const char *k, const char *s) +{ + key(k); + string(s); +} + +void +writer::stringf(const char *fmt, ...) +{ + va_list ap; + + assert(context_is_value(_context)); + begin_value(); + fputc('"', _file); + va_start(ap, fmt); + vfprintf(_file, fmt, ap); + va_end(ap); + fputc('"', _file); + advance(_context); +} + +void +writer::stringf(const char *k, const char *fmt, ...) +{ + va_list ap; + + key(k); + + assert(context_is_value(_context)); + begin_value(); + fputc('"', _file); + va_start(ap, fmt); + vfprintf(_file, fmt, ap); + va_end(ap); + fputc('"', _file); + advance(_context); +} + +} // json diff --git a/objcdt/objcdt-entitlements.plist b/objcdt/objcdt-entitlements.plist new file mode 100644 index 0000000..b7b4e6c --- /dev/null +++ b/objcdt/objcdt-entitlements.plist @@ -0,0 +1,10 @@ + + + + + task_for_pid-allow + + com.apple.system-task-ports + + + diff --git a/objcdt/objcdt.1 b/objcdt/objcdt.1 new file mode 100644 index 0000000..5522491 --- /dev/null +++ b/objcdt/objcdt.1 @@ -0,0 +1,20 @@ +.\" Copyright (c) 2019, Apple Computer, Inc. All rights reserved. +.\" +.Dd September 9, 2019 \" DATE +.Dt objcdt 1 \" Program name and manual section number +.Os "OS X" +.Sh NAME +.Nm objcdt +.Nd Tool to debug objective-C usage in live processes +.Sh SYNOPSIS +.Nm objcdt +.Sh DESCRIPTION +The +.Nm +utility is a small CLI with embedded help that can dump some information about +the Objective-C runtime state in live processes. +.Pp +Help can be obtained using +.Nm +.Ar help +.Ed diff --git a/objcdt/objcdt.mm b/objcdt/objcdt.mm new file mode 100644 index 0000000..81a5a49 --- /dev/null +++ b/objcdt/objcdt.mm @@ -0,0 +1,34 @@ +/* +* Copyright (c) 2019 Apple Inc. All Rights Reserved. +* +* @APPLE_LICENSE_HEADER_START@ +* +* This file contains Original Code and/or Modifications of Original Code +* as defined in and that are subject to the Apple Public Source License +* Version 2.0 (the 'License'). You may not use this file except in +* compliance with the License. Please obtain a copy of the License at +* http://www.opensource.apple.com/apsl/ and read it before using this +* file. +* +* The Original Code and all software distributed under the License are +* distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER +* EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, +* INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. +* Please see the License for the specific language governing rights and +* limitations under the License. +* +* @APPLE_LICENSE_HEADER_END@ +*/ + +#include "objc-private.h" +#include "objc-ptrauth.h" +#include +#include +#include +#include + +int main(int argc, const char *argv[]) +{ + return EX_UNAVAILABLE; +} diff --git a/runtime/Messengers.subproj/objc-msg-arm.s b/runtime/Messengers.subproj/objc-msg-arm.s index b1a9aec..67317c7 100644 --- a/runtime/Messengers.subproj/objc-msg-arm.s +++ b/runtime/Messengers.subproj/objc-msg-arm.s @@ -222,6 +222,40 @@ LExit$0: .endmacro +////////////////////////////////////////////////////////////////////// +// +// SAVE_REGS +// +// Create a stack frame and save all argument registers in preparation +// for a function call. +////////////////////////////////////////////////////////////////////// + +.macro SAVE_REGS + + stmfd sp!, {r0-r3,r7,lr} + add r7, sp, #16 + sub sp, #8 // align stack + FP_SAVE + +.endmacro + + +////////////////////////////////////////////////////////////////////// +// +// RESTORE_REGS +// +// Restore all argument registers and pop the stack frame created by +// SAVE_REGS. +////////////////////////////////////////////////////////////////////// + +.macro RESTORE_REGS + + FP_RESTORE + add sp, #8 // align stack + ldmfd sp!, {r0-r3,r7,lr} + +.endmacro + ///////////////////////////////////////////////////////////////////// // // CacheLookup NORMAL|STRET @@ -666,10 +700,7 @@ LNilReceiver: .macro MethodTableLookup - stmfd sp!, {r0-r3,r7,lr} - add r7, sp, #16 - sub sp, #8 // align stack - FP_SAVE + SAVE_REGS // lookUpImpOrForward(obj, sel, cls, LOOKUP_INITIALIZE | LOOKUP_RESOLVER) .if $0 == NORMAL @@ -690,9 +721,7 @@ LNilReceiver: tst r12, r12 // set ne for stret forwarding .endif - FP_RESTORE - add sp, #8 // align stack - ldmfd sp!, {r0-r3,r7,lr} + RESTORE_REGS .endmacro @@ -819,18 +848,55 @@ LNilReceiver: ENTRY _method_invoke + + // See if this is a small method. + lsls r12, r1, #31 + bne.w L_method_invoke_small + + // We can directly load the IMP from big methods. // r1 is method triplet instead of SEL ldr r12, [r1, #METHOD_IMP] ldr r1, [r1, #METHOD_NAME] bx r12 + +L_method_invoke_small: + // Small methods require a call to handle swizzling. + SAVE_REGS + mov r0, r1 + bl __method_getImplementationAndName + mov r12, r0 + mov r9, r1 + RESTORE_REGS + mov r1, r9 + bx r12 + + END_ENTRY _method_invoke ENTRY _method_invoke_stret + + // See if this is a small method. + lsls r12, r2, #31 + bne.w L_method_invoke_stret_small + + // We can directly load the IMP from big methods. // r2 is method triplet instead of SEL ldr r12, [r2, #METHOD_IMP] ldr r2, [r2, #METHOD_NAME] bx r12 + +L_method_invoke_stret_small: + // Small methods require a call to handle swizzling. + SAVE_REGS + mov r0, r2 + bl __method_getImplementationAndName + mov r12, r0 + mov r9, r1 + RESTORE_REGS + mov r2, r9 + bx r12 + END_ENTRY _method_invoke_stret diff --git a/runtime/Messengers.subproj/objc-msg-arm64.s b/runtime/Messengers.subproj/objc-msg-arm64.s index 6bf3f29..595b03e 100755 --- a/runtime/Messengers.subproj/objc-msg-arm64.s +++ b/runtime/Messengers.subproj/objc-msg-arm64.s @@ -169,6 +169,63 @@ LExit$0: #define FrameWithNoSaves 0x04000000 // frame, no non-volatile saves +////////////////////////////////////////////////////////////////////// +// +// SAVE_REGS +// +// Create a stack frame and save all argument registers in preparation +// for a function call. +////////////////////////////////////////////////////////////////////// + +.macro SAVE_REGS + + // push frame + SignLR + stp fp, lr, [sp, #-16]! + mov fp, sp + + // save parameter registers: x0..x8, q0..q7 + sub sp, sp, #(10*8 + 8*16) + stp q0, q1, [sp, #(0*16)] + stp q2, q3, [sp, #(2*16)] + stp q4, q5, [sp, #(4*16)] + stp q6, q7, [sp, #(6*16)] + stp x0, x1, [sp, #(8*16+0*8)] + stp x2, x3, [sp, #(8*16+2*8)] + stp x4, x5, [sp, #(8*16+4*8)] + stp x6, x7, [sp, #(8*16+6*8)] + str x8, [sp, #(8*16+8*8)] + +.endmacro + + +////////////////////////////////////////////////////////////////////// +// +// RESTORE_REGS +// +// Restore all argument registers and pop the stack frame created by +// SAVE_REGS. +////////////////////////////////////////////////////////////////////// + +.macro RESTORE_REGS + + ldp q0, q1, [sp, #(0*16)] + ldp q2, q3, [sp, #(2*16)] + ldp q4, q5, [sp, #(4*16)] + ldp q6, q7, [sp, #(6*16)] + ldp x0, x1, [sp, #(8*16+0*8)] + ldp x2, x3, [sp, #(8*16+2*8)] + ldp x4, x5, [sp, #(8*16+4*8)] + ldp x6, x7, [sp, #(8*16+6*8)] + ldr x8, [sp, #(8*16+8*8)] + + mov sp, fp + ldp fp, lr, [sp], #16 + AuthenticateLR + +.endmacro + + /******************************************************************** * * CacheLookup NORMAL|GETIMP|LOOKUP @@ -494,22 +551,7 @@ LLookup_Nil: .macro MethodTableLookup - // push frame - SignLR - stp fp, lr, [sp, #-16]! - mov fp, sp - - // save parameter registers: x0..x8, q0..q7 - sub sp, sp, #(10*8 + 8*16) - stp q0, q1, [sp, #(0*16)] - stp q2, q3, [sp, #(2*16)] - stp q4, q5, [sp, #(4*16)] - stp q6, q7, [sp, #(6*16)] - stp x0, x1, [sp, #(8*16+0*8)] - stp x2, x3, [sp, #(8*16+2*8)] - stp x4, x5, [sp, #(8*16+4*8)] - stp x6, x7, [sp, #(8*16+6*8)] - str x8, [sp, #(8*16+8*8)] + SAVE_REGS // lookUpImpOrForward(obj, sel, cls, LOOKUP_INITIALIZE | LOOKUP_RESOLVER) // receiver and selector already in x0 and x1 @@ -519,21 +561,8 @@ LLookup_Nil: // IMP in x0 mov x17, x0 - - // restore registers and return - ldp q0, q1, [sp, #(0*16)] - ldp q2, q3, [sp, #(2*16)] - ldp q4, q5, [sp, #(4*16)] - ldp q6, q7, [sp, #(6*16)] - ldp x0, x1, [sp, #(8*16+0*8)] - ldp x2, x3, [sp, #(8*16+2*8)] - ldp x4, x5, [sp, #(8*16+4*8)] - ldp x6, x7, [sp, #(8*16+6*8)] - ldr x8, [sp, #(8*16+8*8)] - - mov sp, fp - ldp fp, lr, [sp], #16 - AuthenticateLR + + RESTORE_REGS .endmacro @@ -615,11 +644,37 @@ LGetImpMiss: ENTRY _method_invoke + + // See if this is a small method. + tbnz p1, #0, L_method_invoke_small + + // We can directly load the IMP from big methods. // x1 is method triplet instead of SEL add p16, p1, #METHOD_IMP ldr p17, [x16] ldr p1, [x1, #METHOD_NAME] TailCallMethodListImp x17, x16 + +L_method_invoke_small: + // Small methods require a call to handle swizzling. + SAVE_REGS + mov p0, p1 + bl __method_getImplementationAndName + // ARM64_32 packs both return values into x0, with SEL in the high bits and IMP in the low. + // ARM64 just returns them in x0 and x1. + mov x17, x0 +#if __LP64__ + mov x16, x1 +#endif + RESTORE_REGS +#if __LP64__ + mov x1, x16 +#else + lsr x1, x17, #32 + mov w17, w17 +#endif + TailCallFunctionPointer x17 + END_ENTRY _method_invoke #endif diff --git a/runtime/Messengers.subproj/objc-msg-simulator-i386.s b/runtime/Messengers.subproj/objc-msg-simulator-i386.s index 727b983..914a9ac 100644 --- a/runtime/Messengers.subproj/objc-msg-simulator-i386.s +++ b/runtime/Messengers.subproj/objc-msg-simulator-i386.s @@ -192,6 +192,47 @@ LExit$0: #define FrameWithNoSaves 0x01000000 // frame, no non-volatile saves +////////////////////////////////////////////////////////////////////// +// +// SAVE_REGS +// +// Create a stack frame and save all argument registers in preparation +// for a function call. +////////////////////////////////////////////////////////////////////// + +.macro SAVE_REGS + + pushl %ebp + movl %esp, %ebp + + subl $$(8+5*16), %esp + + movdqa %xmm3, 4*16(%esp) + movdqa %xmm2, 3*16(%esp) + movdqa %xmm1, 2*16(%esp) + movdqa %xmm0, 1*16(%esp) + +.endmacro + + +////////////////////////////////////////////////////////////////////// +// +// RESTORE_REGS +// +// Restore all argument registers and pop the stack frame created by +// SAVE_REGS. +////////////////////////////////////////////////////////////////////// + +.macro RESTORE_REGS + + movdqa 4*16(%esp), %xmm3 + movdqa 3*16(%esp), %xmm2 + movdqa 2*16(%esp), %xmm1 + movdqa 1*16(%esp), %xmm0 + + leave + +.endmacro ///////////////////////////////////////////////////////////////////// // // CacheLookup return-type, caller @@ -314,10 +355,7 @@ LExit$0: ///////////////////////////////////////////////////////////////////// .macro MethodTableLookup - pushl %ebp - movl %esp, %ebp - - subl $$(8+5*16), %esp + SAVE_REGS .if $0 == NORMAL movl self+4(%ebp), %eax @@ -327,11 +365,6 @@ LExit$0: movl selector_stret+4(%ebp), %ecx .endif - movdqa %xmm3, 4*16(%esp) - movdqa %xmm2, 3*16(%esp) - movdqa %xmm1, 2*16(%esp) - movdqa %xmm0, 1*16(%esp) - // lookUpImpOrForward(obj, sel, cls, LOOKUP_INITIALIZE | LOOKUP_RESOLVER) movl $$3, 12(%esp) // LOOKUP_INITIALIZE | LOOKUP_RESOLVER movl %edx, 8(%esp) // class @@ -341,18 +374,13 @@ LExit$0: // imp in eax - movdqa 4*16(%esp), %xmm3 - movdqa 3*16(%esp), %xmm2 - movdqa 2*16(%esp), %xmm1 - movdqa 1*16(%esp), %xmm0 - .if $0 == NORMAL test %eax, %eax // set ne for stret forwarding .else cmp %eax, %eax // set eq for nonstret forwarding .endif - leave + RESTORE_REGS .endmacro @@ -906,23 +934,55 @@ L_forward_stret_handler: ENTRY _method_invoke + // See if this is a small method. + testb $1, selector(%esp) + jnz L_method_invoke_small + + // We can directly load the IMP from big methods. movl selector(%esp), %ecx movl method_name(%ecx), %edx movl method_imp(%ecx), %eax movl %edx, selector(%esp) jmp *%eax - + +L_method_invoke_small: + // Small methods require a call to handle swizzling. + SAVE_REGS + + movl selector+4(%ebp), %eax + movl %eax, 0(%esp) + call __method_getImplementationAndName + RESTORE_REGS + movl %edx, selector(%esp) + jmp *%eax + END_ENTRY _method_invoke ENTRY _method_invoke_stret + // See if this is a small method. + testb $1, selector_stret(%esp) + jnz L_method_invoke_stret_small + + // We can directly load the IMP from big methods. movl selector_stret(%esp), %ecx movl method_name(%ecx), %edx movl method_imp(%ecx), %eax movl %edx, selector_stret(%esp) jmp *%eax +L_method_invoke_stret_small: + // Small methods require a call to handle swizzling. + SAVE_REGS + + movl selector_stret+4(%ebp), %eax + movl %eax, 0(%esp) + call __method_getImplementationAndName + RESTORE_REGS + movl %edx, selector_stret(%esp) + jmp *%eax + END_ENTRY _method_invoke_stret diff --git a/runtime/Messengers.subproj/objc-msg-simulator-x86_64.s b/runtime/Messengers.subproj/objc-msg-simulator-x86_64.s index a5410c4..9186278 100644 --- a/runtime/Messengers.subproj/objc-msg-simulator-x86_64.s +++ b/runtime/Messengers.subproj/objc-msg-simulator-x86_64.s @@ -93,6 +93,7 @@ _objc_restartableRanges: #define a2b sil #define a3 rdx #define a3d edx +#define a3b dl #define a4 rcx #define a4d ecx #define a5 r8 @@ -212,6 +213,70 @@ LExit$0: #define FrameWithNoSaves 0x01000000 // frame, no non-volatile saves +////////////////////////////////////////////////////////////////////// +// +// SAVE_REGS +// +// Create a stack frame and save all argument registers in preparation +// for a function call. +////////////////////////////////////////////////////////////////////// + +.macro SAVE_REGS + + push %rbp + mov %rsp, %rbp + + sub $$0x80+8, %rsp // +8 for alignment + + movdqa %xmm0, -0x80(%rbp) + push %rax // might be xmm parameter count + movdqa %xmm1, -0x70(%rbp) + push %a1 + movdqa %xmm2, -0x60(%rbp) + push %a2 + movdqa %xmm3, -0x50(%rbp) + push %a3 + movdqa %xmm4, -0x40(%rbp) + push %a4 + movdqa %xmm5, -0x30(%rbp) + push %a5 + movdqa %xmm6, -0x20(%rbp) + push %a6 + movdqa %xmm7, -0x10(%rbp) + +.endmacro + + +////////////////////////////////////////////////////////////////////// +// +// RESTORE_REGS +// +// Restore all argument registers and pop the stack frame created by +// SAVE_REGS. +////////////////////////////////////////////////////////////////////// + +.macro RESTORE_REGS + + movdqa -0x80(%rbp), %xmm0 + pop %a6 + movdqa -0x70(%rbp), %xmm1 + pop %a5 + movdqa -0x60(%rbp), %xmm2 + pop %a4 + movdqa -0x50(%rbp), %xmm3 + pop %a3 + movdqa -0x40(%rbp), %xmm4 + pop %a2 + movdqa -0x30(%rbp), %xmm5 + pop %a1 + movdqa -0x20(%rbp), %xmm6 + pop %rax + movdqa -0x10(%rbp), %xmm7 + leave + +.endmacro + + ///////////////////////////////////////////////////////////////////// // // CacheLookup return-type, caller @@ -347,26 +412,7 @@ LExit$0: .macro MethodTableLookup - push %rbp - mov %rsp, %rbp - - sub $$0x80+8, %rsp // +8 for alignment - - movdqa %xmm0, -0x80(%rbp) - push %rax // might be xmm parameter count - movdqa %xmm1, -0x70(%rbp) - push %a1 - movdqa %xmm2, -0x60(%rbp) - push %a2 - movdqa %xmm3, -0x50(%rbp) - push %a3 - movdqa %xmm4, -0x40(%rbp) - push %a4 - movdqa %xmm5, -0x30(%rbp) - push %a5 - movdqa %xmm6, -0x20(%rbp) - push %a6 - movdqa %xmm7, -0x10(%rbp) + SAVE_REGS // lookUpImpOrForward(obj, sel, cls, LOOKUP_INITIALIZE | LOOKUP_RESOLVER) .if $0 == NORMAL @@ -383,29 +429,13 @@ LExit$0: // IMP is now in %rax movq %rax, %r11 - movdqa -0x80(%rbp), %xmm0 - pop %a6 - movdqa -0x70(%rbp), %xmm1 - pop %a5 - movdqa -0x60(%rbp), %xmm2 - pop %a4 - movdqa -0x50(%rbp), %xmm3 - pop %a3 - movdqa -0x40(%rbp), %xmm4 - pop %a2 - movdqa -0x30(%rbp), %xmm5 - pop %a1 - movdqa -0x20(%rbp), %xmm6 - pop %rax - movdqa -0x10(%rbp), %xmm7 + RESTORE_REGS .if $0 == NORMAL test %r11, %r11 // set ne for stret forwarding .else cmp %r11, %r11 // set eq for nonstret forwarding .endif - - leave .endmacro @@ -1104,19 +1134,51 @@ LCacheMiss: ENTRY _method_invoke + // See if this is a small method. + testb $1, %a2b + jnz L_method_invoke_small + + // We can directly load the IMP from big methods. movq method_imp(%a2), %r11 movq method_name(%a2), %a2 jmp *%r11 - + +L_method_invoke_small: + // Small methods require a call to handle swizzling. + SAVE_REGS + movq %a2, %a1 + call __method_getImplementationAndName + movq %rdx, %r10 + movq %rax, %r11 + RESTORE_REGS + movq %r10, %a2 + jmp *%r11 + END_ENTRY _method_invoke ENTRY _method_invoke_stret + // See if this is a small method. + testb $1, %a3b + jnz L_method_invoke_stret_small + + // We can directly load the IMP from big methods. movq method_imp(%a3), %r11 movq method_name(%a3), %a3 jmp *%r11 - + +L_method_invoke_stret_small: + // Small methods require a call to handle swizzling. + SAVE_REGS + movq %a3, %a1 + call __method_getImplementationAndName + movq %rdx, %r10 + movq %rax, %r11 + RESTORE_REGS + movq %r10, %a3 + jmp *%r11 + END_ENTRY _method_invoke_stret diff --git a/runtime/Messengers.subproj/objc-msg-x86_64.s b/runtime/Messengers.subproj/objc-msg-x86_64.s index 8fc6d48..d090995 100644 --- a/runtime/Messengers.subproj/objc-msg-x86_64.s +++ b/runtime/Messengers.subproj/objc-msg-x86_64.s @@ -102,6 +102,7 @@ _objc_restartableRanges: #define a2b sil #define a3 rdx #define a3d edx +#define a3b dl #define a4 rcx #define a4d ecx #define a5 r8 @@ -218,6 +219,70 @@ LExit$0: #define FrameWithNoSaves 0x01000000 // frame, no non-volatile saves +////////////////////////////////////////////////////////////////////// +// +// SAVE_REGS +// +// Create a stack frame and save all argument registers in preparation +// for a function call. +////////////////////////////////////////////////////////////////////// + +.macro SAVE_REGS + + push %rbp + mov %rsp, %rbp + + sub $$0x80+8, %rsp // +8 for alignment + + movdqa %xmm0, -0x80(%rbp) + push %rax // might be xmm parameter count + movdqa %xmm1, -0x70(%rbp) + push %a1 + movdqa %xmm2, -0x60(%rbp) + push %a2 + movdqa %xmm3, -0x50(%rbp) + push %a3 + movdqa %xmm4, -0x40(%rbp) + push %a4 + movdqa %xmm5, -0x30(%rbp) + push %a5 + movdqa %xmm6, -0x20(%rbp) + push %a6 + movdqa %xmm7, -0x10(%rbp) + +.endmacro + + +////////////////////////////////////////////////////////////////////// +// +// RESTORE_REGS +// +// Restore all argument registers and pop the stack frame created by +// SAVE_REGS. +////////////////////////////////////////////////////////////////////// + +.macro RESTORE_REGS + + movdqa -0x80(%rbp), %xmm0 + pop %a6 + movdqa -0x70(%rbp), %xmm1 + pop %a5 + movdqa -0x60(%rbp), %xmm2 + pop %a4 + movdqa -0x50(%rbp), %xmm3 + pop %a3 + movdqa -0x40(%rbp), %xmm4 + pop %a2 + movdqa -0x30(%rbp), %xmm5 + pop %a1 + movdqa -0x20(%rbp), %xmm6 + pop %rax + movdqa -0x10(%rbp), %xmm7 + leave + +.endmacro + + ///////////////////////////////////////////////////////////////////// // // CacheLookup return-type, caller, function @@ -382,26 +447,7 @@ LLookupEnd$2: .macro MethodTableLookup - push %rbp - mov %rsp, %rbp - - sub $$0x80+8, %rsp // +8 for alignment - - movdqa %xmm0, -0x80(%rbp) - push %rax // might be xmm parameter count - movdqa %xmm1, -0x70(%rbp) - push %a1 - movdqa %xmm2, -0x60(%rbp) - push %a2 - movdqa %xmm3, -0x50(%rbp) - push %a3 - movdqa %xmm4, -0x40(%rbp) - push %a4 - movdqa %xmm5, -0x30(%rbp) - push %a5 - movdqa %xmm6, -0x20(%rbp) - push %a6 - movdqa %xmm7, -0x10(%rbp) + SAVE_REGS // lookUpImpOrForward(obj, sel, cls, LOOKUP_INITIALIZE | LOOKUP_RESOLVER) .if $0 == NORMAL @@ -418,29 +464,13 @@ LLookupEnd$2: // IMP is now in %rax movq %rax, %r11 - movdqa -0x80(%rbp), %xmm0 - pop %a6 - movdqa -0x70(%rbp), %xmm1 - pop %a5 - movdqa -0x60(%rbp), %xmm2 - pop %a4 - movdqa -0x50(%rbp), %xmm3 - pop %a3 - movdqa -0x40(%rbp), %xmm4 - pop %a2 - movdqa -0x30(%rbp), %xmm5 - pop %a1 - movdqa -0x20(%rbp), %xmm6 - pop %rax - movdqa -0x10(%rbp), %xmm7 + RESTORE_REGS .if $0 == NORMAL test %r11, %r11 // set ne for nonstret forwarding .else cmp %r11, %r11 // set eq for stret forwarding .endif - - leave .endmacro @@ -1216,19 +1246,51 @@ LCacheMiss_objc_msgLookupSuper2_stret: ENTRY _method_invoke + // See if this is a small method. + testb $1, %a2b + jnz L_method_invoke_small + + // We can directly load the IMP from big methods. movq method_imp(%a2), %r11 movq method_name(%a2), %a2 jmp *%r11 - + +L_method_invoke_small: + // Small methods require a call to handle swizzling. + SAVE_REGS + movq %a2, %a1 + call __method_getImplementationAndName + movq %rdx, %r10 + movq %rax, %r11 + RESTORE_REGS + movq %r10, %a2 + jmp *%r11 + END_ENTRY _method_invoke ENTRY _method_invoke_stret + // See if this is a small method. + testb $1, %a3b + jnz L_method_invoke_stret_small + + // We can directly load the IMP from big methods. movq method_imp(%a3), %r11 movq method_name(%a3), %a3 jmp *%r11 - + +L_method_invoke_stret_small: + // Small methods require a call to handle swizzling. + SAVE_REGS + movq %a3, %a1 + call __method_getImplementationAndName + movq %rdx, %r10 + movq %rax, %r11 + RESTORE_REGS + movq %r10, %a3 + jmp *%r11 + END_ENTRY _method_invoke_stret diff --git a/runtime/PointerUnion.h b/runtime/PointerUnion.h index 85b7846..1108673 100644 --- a/runtime/PointerUnion.h +++ b/runtime/PointerUnion.h @@ -59,12 +59,12 @@ struct PointerUnionTypeSelectorReturn< typename PointerUnionTypeSelector::Return; }; -template +template class PointerUnion { uintptr_t _value; - static_assert(alignof(PT1) >= 2, "alignment requirement"); - static_assert(alignof(PT2) >= 2, "alignment requirement"); + static_assert(alignof(T1) >= 2, "alignment requirement"); + static_assert(alignof(T2) >= 2, "alignment requirement"); struct IsPT1 { static const uintptr_t Num = 0; @@ -85,8 +85,12 @@ class PointerUnion { explicit PointerUnion(const std::atomic &raw) : _value(raw.load(std::memory_order_relaxed)) { } - PointerUnion(PT1 t) : _value((uintptr_t)t) { } - PointerUnion(PT2 t) : _value((uintptr_t)t | 1) { } + PointerUnion(T1 *t, const void *address) { + _value = (uintptr_t)Auth1::sign(t, address); + } + PointerUnion(T2 *t, const void *address) { + _value = (uintptr_t)Auth2::sign(t, address) | 1; + } void storeAt(std::atomic &raw, std::memory_order order) const { raw.store(_value, order); @@ -94,20 +98,24 @@ class PointerUnion { template bool is() const { - using Ty = typename PointerUnionTypeSelector>>::Return; return getTag() == Ty::Num; } - template T get() const { - ASSERT(is() && "Invalid accessor called"); - return reinterpret_cast(getPointer()); + template T get(const void *address) const { + ASSERT(is() && "Invalid accessor called"); + using AuthT = typename PointerUnionTypeSelector>>::Return; + + return AuthT::auth((T)getPointer(), address); } - template T dyn_cast() const { + template T dyn_cast(const void *address) const { if (is()) - return get(); + return get(address); return T(); } }; diff --git a/runtime/objc-cache.mm b/runtime/objc-cache.mm index 4e6ca11..7656391 100644 --- a/runtime/objc-cache.mm +++ b/runtime/objc-cache.mm @@ -99,6 +99,18 @@ static int _collecting_in_critical(void); static void _garbage_make_room(void); +#if DEBUG_TASK_THREADS +static kern_return_t objc_task_threads +( + task_t target_task, + thread_act_array_t *act_list, + mach_msg_type_number_t *act_listCnt +); +#endif + +#if DEBUG_TASK_THREADS +#undef HAVE_TASK_RESTARTABLE_RANGES +#endif /*********************************************************************** * Cache statistics for OBJC_PRINT_CACHE_SETUP diff --git a/runtime/objc-file.h b/runtime/objc-file.h index 23c0da1..3dc54c7 100644 --- a/runtime/objc-file.h +++ b/runtime/objc-file.h @@ -38,10 +38,14 @@ extern message_ref_t *_getObjc2MessageRefs(const header_info *hi, size_t *count) extern Class*_getObjc2ClassRefs(const header_info *hi, size_t *count); extern Class*_getObjc2SuperRefs(const header_info *hi, size_t *count); extern classref_t const *_getObjc2ClassList(const header_info *hi, size_t *count); -extern classref_t const *_getObjc2NonlazyClassList(const header_info *hi, size_t *count); -extern category_t * const *_getObjc2CategoryList(const header_info *hi, size_t *count); -extern category_t * const *_getObjc2CategoryList2(const header_info *hi, size_t *count); -extern category_t * const *_getObjc2NonlazyCategoryList(const header_info *hi, size_t *count); +// Use hi->nlclslist() instead +// extern classref_t const *_getObjc2NonlazyClassList(const header_info *hi, size_t *count); +// Use hi->catlist() instead +// extern category_t * const *_getObjc2CategoryList(const header_info *hi, size_t *count); +// Use hi->catlist2() instead +// extern category_t * const *_getObjc2CategoryList2(const header_info *hi, size_t *count); +// Use hi->nlcatlist() instead +// extern category_t * const *_getObjc2NonlazyCategoryList(const header_info *hi, size_t *count); extern protocol_t * const *_getObjc2ProtocolList(const header_info *hi, size_t *count); extern protocol_t **_getObjc2ProtocolRefs(const header_info *hi, size_t *count); @@ -62,6 +66,8 @@ struct UnsignedInitializer { extern UnsignedInitializer *getLibobjcInitializers(const header_info *hi, size_t *count); extern classref_t const *_getObjc2NonlazyClassList(const headerType *mhdr, size_t *count); +extern category_t * const *_getObjc2CategoryList(const headerType *mhdr, size_t *count); +extern category_t * const *_getObjc2CategoryList2(const headerType *mhdr, size_t *count); extern category_t * const *_getObjc2NonlazyCategoryList(const headerType *mhdr, size_t *count); extern UnsignedInitializer *getLibobjcInitializers(const headerType *mhdr, size_t *count); diff --git a/runtime/objc-internal.h b/runtime/objc-internal.h index afc82f5..112804d 100644 --- a/runtime/objc-internal.h +++ b/runtime/objc-internal.h @@ -287,6 +287,8 @@ enum OBJC_TAG_UIColor = 17, OBJC_TAG_CGColor = 18, OBJC_TAG_NSIndexSet = 19, + OBJC_TAG_NSMethodSignature = 20, + OBJC_TAG_UTTypeRecord = 21, OBJC_TAG_First60BitPayload = 0, OBJC_TAG_Last60BitPayload = 6, @@ -944,7 +946,7 @@ typedef enum { } \ } \ -(NSUInteger)retainCount { \ - return (_rc_ivar + 2) >> 1; \ + return (NSUInteger)(_rc_ivar + 2) >> 1; \ } \ -(BOOL)_tryRetain { \ __typeof__(_rc_ivar) _prev; \ @@ -966,12 +968,12 @@ typedef enum { } else if (_rc_ivar < -2) { \ __builtin_trap(); /* BUG: over-release elsewhere */ \ } \ - return _rc_ivar & 1; \ + return (_rc_ivar & 1) != 0; \ } #define _OBJC_SUPPORTED_INLINE_REFCNT_LOGIC(_rc_ivar, _dealloc2main) \ _OBJC_SUPPORTED_INLINE_REFCNT_LOGIC_BLOCK(_rc_ivar, (^(id _self_ __attribute__((unused))) { \ - if (_dealloc2main && !pthread_main_np()) { \ + if ((_dealloc2main) && !pthread_main_np()) { \ return _OBJC_DEALLOC_OBJECT_LATER; \ } else { \ return _OBJC_DEALLOC_OBJECT_NOW; \ diff --git a/runtime/objc-opt.mm b/runtime/objc-opt.mm index f4afb20..b21869b 100644 --- a/runtime/objc-opt.mm +++ b/runtime/objc-opt.mm @@ -27,13 +27,13 @@ */ #include "objc-private.h" +#include "objc-os.h" +#include "objc-file.h" #if !SUPPORT_PREOPT // Preoptimization not supported on this platform. -struct objc_selopt_t; - bool isPreoptimized(void) { return false; @@ -64,16 +64,6 @@ bool noMissingWeakSuperclasses(void) return false; } -objc_selopt_t *preoptimizedSelectors(void) -{ - return nil; -} - -bool sharedCacheSupportsProtocolRoots(void) -{ - return false; -} - Protocol *getPreoptimizedProtocol(const char *name) { return nil; @@ -123,7 +113,6 @@ void preopt_init(void) #include using objc_opt::objc_stringhash_offset_t; -using objc_opt::objc_protocolopt_t; using objc_opt::objc_protocolopt2_t; using objc_opt::objc_clsopt_t; using objc_opt::objc_headeropt_ro_t; @@ -141,6 +130,62 @@ void preopt_init(void) extern const objc_opt_t _objc_opt_data; // in __TEXT, __objc_opt_ro +namespace objc_opt { +struct objc_headeropt_ro_t { + uint32_t count; + uint32_t entsize; + header_info headers[0]; // sorted by mhdr address + + header_info& getOrEnd(uint32_t i) const { + ASSERT(i <= count); + return *(header_info *)((uint8_t *)&headers + (i * entsize)); + } + + header_info& get(uint32_t i) const { + ASSERT(i < count); + return *(header_info *)((uint8_t *)&headers + (i * entsize)); + } + + uint32_t index(const header_info* hi) const { + const header_info* begin = &get(0); + const header_info* end = &getOrEnd(count); + ASSERT(hi >= begin && hi < end); + return (uint32_t)(((uintptr_t)hi - (uintptr_t)begin) / entsize); + } + + header_info *get(const headerType *mhdr) + { + int32_t start = 0; + int32_t end = count; + while (start <= end) { + int32_t i = (start+end)/2; + header_info &hi = get(i); + if (mhdr == hi.mhdr()) return &hi; + else if (mhdr < hi.mhdr()) end = i-1; + else start = i+1; + } + +#if DEBUG + for (uint32_t i = 0; i < count; i++) { + header_info &hi = get(i); + if (mhdr == hi.mhdr()) { + _objc_fatal("failed to find header %p (%d/%d)", + mhdr, i, count); + } + } +#endif + + return nil; + } +}; + +struct objc_headeropt_rw_t { + uint32_t count; + uint32_t entsize; + header_info_rw headers[0]; // sorted by mhdr address +}; +}; + /*********************************************************************** * Return YES if we have a valid optimized shared cache. **********************************************************************/ @@ -199,38 +244,114 @@ bool noMissingWeakSuperclasses(void) return info()->optimizedByDyld() || info()->optimizedByDyldClosure(); } +bool header_info::hasPreoptimizedSectionLookups() const +{ + objc_opt::objc_headeropt_ro_t *hinfoRO = opt->headeropt_ro(); + if (hinfoRO->entsize == (2 * sizeof(intptr_t))) + return NO; -objc_selopt_t *preoptimizedSelectors(void) + return YES; +} + +const classref_t *header_info::nlclslist(size_t *outCount) const { - return opt ? opt->selopt() : nil; +#if __OBJC2__ + // This field is new, so temporarily be resilient to the shared cache + // not generating it + if (isPreoptimized() && hasPreoptimizedSectionLookups()) { + *outCount = nlclslist_count; + const classref_t *list = (const classref_t *)(((intptr_t)&nlclslist_offset) + nlclslist_offset); + #if DEBUG + size_t debugCount; + assert((list == _getObjc2NonlazyClassList(mhdr(), &debugCount)) && (*outCount == debugCount)); + #endif + return list; + } + return _getObjc2NonlazyClassList(mhdr(), outCount); +#else + return NULL; +#endif } -bool sharedCacheSupportsProtocolRoots(void) +category_t * const *header_info::nlcatlist(size_t *outCount) const { - return (opt != nil) && (opt->protocolopt2() != nil); +#if __OBJC2__ + // This field is new, so temporarily be resilient to the shared cache + // not generating it + if (isPreoptimized() && hasPreoptimizedSectionLookups()) { + *outCount = nlcatlist_count; + category_t * const *list = (category_t * const *)(((intptr_t)&nlcatlist_offset) + nlcatlist_offset); + #if DEBUG + size_t debugCount; + assert((list == _getObjc2NonlazyCategoryList(mhdr(), &debugCount)) && (*outCount == debugCount)); + #endif + return list; + } + return _getObjc2NonlazyCategoryList(mhdr(), outCount); +#else + return NULL; +#endif } +category_t * const *header_info::catlist(size_t *outCount) const +{ +#if __OBJC2__ + // This field is new, so temporarily be resilient to the shared cache + // not generating it + if (isPreoptimized() && hasPreoptimizedSectionLookups()) { + *outCount = catlist_count; + category_t * const *list = (category_t * const *)(((intptr_t)&catlist_offset) + catlist_offset); + #if DEBUG + size_t debugCount; + assert((list == _getObjc2CategoryList(mhdr(), &debugCount)) && (*outCount == debugCount)); + #endif + return list; + } + return _getObjc2CategoryList(mhdr(), outCount); +#else + return NULL; +#endif +} -Protocol *getSharedCachePreoptimizedProtocol(const char *name) +category_t * const *header_info::catlist2(size_t *outCount) const { - // Look in the new table if we have it - if (objc_protocolopt2_t *protocols2 = opt ? opt->protocolopt2() : nil) { - // Note, we have to pass the lambda directly here as otherwise we would try - // message copy and autorelease. - return (Protocol *)protocols2->getProtocol(name, [](const void* hi) -> bool { - return ((header_info *)hi)->isLoaded(); - }); +#if __OBJC2__ + // This field is new, so temporarily be resilient to the shared cache + // not generating it + if (isPreoptimized() && hasPreoptimizedSectionLookups()) { + *outCount = catlist2_count; + category_t * const *list = (category_t * const *)(((intptr_t)&catlist2_offset) + catlist2_offset); + #if DEBUG + size_t debugCount; + assert((list == _getObjc2CategoryList2(mhdr(), &debugCount)) && (*outCount == debugCount)); + #endif + return list; } + return _getObjc2CategoryList2(mhdr(), outCount); +#else + return NULL; +#endif +} + - objc_protocolopt_t *protocols = opt ? opt->protocolopt() : nil; +Protocol *getSharedCachePreoptimizedProtocol(const char *name) +{ + objc_protocolopt2_t *protocols = opt ? opt->protocolopt2() : nil; if (!protocols) return nil; - return (Protocol *)protocols->getProtocol(name); + // Note, we have to pass the lambda directly here as otherwise we would try + // message copy and autorelease. + return (Protocol *)protocols->getProtocol(name, [](const void* hi) -> bool { + return ((header_info *)hi)->isLoaded(); + }); } Protocol *getPreoptimizedProtocol(const char *name) { + objc_protocolopt2_t *protocols = opt ? opt->protocolopt2() : nil; + if (!protocols) return nil; + // Try table from dyld closure first. It was built to ignore the dupes it // knows will come from the cache, so anything left in here was there when // we launched @@ -354,47 +475,6 @@ Class getPreoptimizedClass(const char *name) return nil; } -namespace objc_opt { -struct objc_headeropt_ro_t { - uint32_t count; - uint32_t entsize; - header_info headers[0]; // sorted by mhdr address - - header_info *get(const headerType *mhdr) - { - ASSERT(entsize == sizeof(header_info)); - - int32_t start = 0; - int32_t end = count; - while (start <= end) { - int32_t i = (start+end)/2; - header_info *hi = headers+i; - if (mhdr == hi->mhdr()) return hi; - else if (mhdr < hi->mhdr()) end = i-1; - else start = i+1; - } - -#if DEBUG - for (uint32_t i = 0; i < count; i++) { - header_info *hi = headers+i; - if (mhdr == hi->mhdr()) { - _objc_fatal("failed to find header %p (%d/%d)", - mhdr, i, count); - } - } -#endif - - return nil; - } -}; - -struct objc_headeropt_rw_t { - uint32_t count; - uint32_t entsize; - header_info_rw headers[0]; // sorted by mhdr address -}; -}; - header_info *preoptimizedHinfoForHeader(const headerType *mhdr) { @@ -422,7 +502,7 @@ Class getPreoptimizedClass(const char *name) _objc_fatal("preoptimized header_info missing for %s (%p %p %p)", hdr->fname(), hdr, hinfoRO, hinfoRW); } - int32_t index = (int32_t)(hdr - hinfoRO->headers); + int32_t index = hinfoRO->index(hdr); ASSERT(hinfoRW->entsize == sizeof(header_info_rw)); return &hinfoRW->headers[index]; } diff --git a/runtime/objc-os.h b/runtime/objc-os.h index c28ba05..5a06252 100644 --- a/runtime/objc-os.h +++ b/runtime/objc-os.h @@ -1076,6 +1076,7 @@ ustrdupMaybeNil(const uint8_t *str) (unsigned char)(((uint32_t)(v))>>8), \ (unsigned char)(((uint32_t)(v))>>0) +#ifndef __BUILDING_OBJCDT__ // fork() safety requires careful tracking of all locks. // Our custom lock types check this in debug builds. // Disallow direct use of all other lock types. @@ -1083,6 +1084,6 @@ typedef __darwin_pthread_mutex_t pthread_mutex_t UNAVAILABLE_ATTRIBUTE; typedef __darwin_pthread_rwlock_t pthread_rwlock_t UNAVAILABLE_ATTRIBUTE; typedef int32_t OSSpinLock UNAVAILABLE_ATTRIBUTE; typedef struct os_unfair_lock_s os_unfair_lock UNAVAILABLE_ATTRIBUTE; - +#endif #endif diff --git a/runtime/objc-os.mm b/runtime/objc-os.mm index 7d600ef..db021d0 100644 --- a/runtime/objc-os.mm +++ b/runtime/objc-os.mm @@ -492,11 +492,16 @@ void objc_addLoadImageFunc(objc_func_loadImage _Nonnull func) { if (mhdr->filetype == MH_EXECUTE) { // Size some data structures based on main executable's size #if __OBJC2__ - size_t count; - _getObjc2SelectorRefs(hi, &count); - selrefCount += count; - _getObjc2MessageRefs(hi, &count); - selrefCount += count; + // If dyld3 optimized the main executable, then there shouldn't + // be any selrefs needed in the dynamic map so we can just init + // to a 0 sized map + if ( !hi->hasPreoptimizedSelectors() ) { + size_t count; + _getObjc2SelectorRefs(hi, &count); + selrefCount += count; + _getObjc2MessageRefs(hi, &count); + selrefCount += count; + } #else _getObjcSelectorRefs(hi, &selrefCount); #endif diff --git a/runtime/objc-private.h b/runtime/objc-private.h index 4d7aab2..bf2a8de 100644 --- a/runtime/objc-private.h +++ b/runtime/objc-private.h @@ -55,9 +55,11 @@ struct objc_class; struct objc_object; +struct category_t; typedef struct objc_class *Class; typedef struct objc_object *id; +typedef struct classref *classref_t; namespace { struct SideTable; @@ -241,14 +243,6 @@ typedef struct old_property *objc_property_t; #include "objc-loadmethod.h" -#if SUPPORT_PREOPT && __cplusplus -#include -using objc_selopt_t = const objc_opt::objc_selopt_t; -#else -struct objc_selopt_t; -#endif - - #define STRINGIFY(x) #x #define STRINGIFY2(x) STRINGIFY(x) @@ -358,6 +352,22 @@ typedef struct header_info { // from this location. intptr_t info_offset; + // Offset from this location to the non-lazy class list + intptr_t nlclslist_offset; + uintptr_t nlclslist_count; + + // Offset from this location to the non-lazy category list + intptr_t nlcatlist_offset; + uintptr_t nlcatlist_count; + + // Offset from this location to the category list + intptr_t catlist_offset; + uintptr_t catlist_count; + + // Offset from this location to the category list 2 + intptr_t catlist2_offset; + uintptr_t catlist2_count; + // Do not add fields without editing ObjCModernAbstraction.hpp public: @@ -384,6 +394,30 @@ typedef struct header_info { info_offset = (intptr_t)info - (intptr_t)&info_offset; } + const classref_t *nlclslist(size_t *outCount) const; + + void set_nlclslist(const void *list) { + nlclslist_offset = (intptr_t)list - (intptr_t)&nlclslist_offset; + } + + category_t * const *nlcatlist(size_t *outCount) const; + + void set_nlcatlist(const void *list) { + nlcatlist_offset = (intptr_t)list - (intptr_t)&nlcatlist_offset; + } + + category_t * const *catlist(size_t *outCount) const; + + void set_catlist(const void *list) { + catlist_offset = (intptr_t)list - (intptr_t)&catlist_offset; + } + + category_t * const *catlist2(size_t *outCount) const; + + void set_catlist2(const void *list) { + catlist2_offset = (intptr_t)list - (intptr_t)&catlist2_offset; + } + bool isLoaded() { return getHeaderInfoRW()->getLoaded(); } @@ -424,6 +458,8 @@ typedef struct header_info { bool hasPreoptimizedProtocols() const; + bool hasPreoptimizedSectionLookups() const; + #if !__OBJC2__ struct old_protocol **proto_refs; struct objc_module *mod_ptr; @@ -497,9 +533,6 @@ extern bool isPreoptimized(void); extern bool noMissingWeakSuperclasses(void); extern header_info *preoptimizedHinfoForHeader(const headerType *mhdr); -extern objc_selopt_t *preoptimizedSelectors(void); - -extern bool sharedCacheSupportsProtocolRoots(void); extern Protocol *getPreoptimizedProtocol(const char *name); extern Protocol *getSharedCachePreoptimizedProtocol(const char *name); @@ -525,6 +558,14 @@ lookUpImpOrNil(id obj, SEL sel, Class cls, int behavior = 0) } extern IMP lookupMethodInClassAndLoadCache(Class cls, SEL sel); + +struct IMPAndSEL { + IMP imp; + SEL sel; +}; + +extern IMPAndSEL _method_getImplementationAndName(Method m); + extern BOOL class_respondsToSelector_inst(id inst, SEL sel, Class cls); extern Class class_initialize(Class cls, id inst); @@ -971,7 +1012,7 @@ class ChainedHookFunction { std::atomic hook{nil}; public: - ChainedHookFunction(Fn f) : hook{f} { }; + constexpr ChainedHookFunction(Fn f) : hook{f} { }; Fn get() { return hook.load(std::memory_order_acquire); @@ -990,10 +1031,10 @@ class ChainedHookFunction { // A small vector for use as a global variable. Only supports appending and -// iteration. Stores a single element inline, and multiple elements in a heap +// iteration. Stores up to N elements inline, and multiple elements in a heap // allocation. There is no attempt to amortize reallocation cost; this is -// intended to be used in situation where zero or one element is common, two -// might happen, and three or more is very rare. +// intended to be used in situation where a small number of elements is +// common, more might happen, and significantly more is very rare. // // This does not clean up its allocation, and thus cannot be used as a local // variable or member of something with limited lifetime. @@ -1006,7 +1047,7 @@ class GlobalSmallVector { unsigned count{0}; union { T inlineElements[InlineCount]; - T *elements; + T *elements{nullptr}; }; public: diff --git a/runtime/objc-ptrauth.h b/runtime/objc-ptrauth.h index e275dca..388abe6 100644 --- a/runtime/objc-ptrauth.h +++ b/runtime/objc-ptrauth.h @@ -76,5 +76,119 @@ using MethodListIMP = IMP; #endif +// A struct that wraps a pointer using the provided template. +// The provided Auth parameter is used to sign and authenticate +// the pointer as it is read and written. +template +struct WrappedPtr { +private: + T *ptr; + +public: + WrappedPtr(T *p) { + *this = p; + } + + WrappedPtr(const WrappedPtr &p) { + *this = p; + } + + WrappedPtr &operator =(T *p) { + ptr = Auth::sign(p, &ptr); + return *this; + } + + WrappedPtr &operator =(const WrappedPtr &p) { + *this = (T *)p; + return *this; + } + + operator T*() const { return get(); } + T *operator->() const { return get(); } + + T *get() const { return Auth::auth(ptr, &ptr); } + + // When asserts are enabled, ensure that we can read a byte from + // the underlying pointer. This can be used to catch ptrauth + // errors early for easier debugging. + void validate() const { +#if !NDEBUG + char *p = (char *)get(); + char dummy; + memset_s(&dummy, 1, *p, 1); + ASSERT(dummy == *p); +#endif + } +}; + +// A "ptrauth" struct that just passes pointers through unchanged. +struct PtrauthRaw { + template + static T *sign(T *ptr, const void *address) { + return ptr; + } + + template + static T *auth(T *ptr, const void *address) { + return ptr; + } +}; + +// A ptrauth struct that stores pointers raw, and strips ptrauth +// when reading. +struct PtrauthStrip { + template + static T *sign(T *ptr, const void *address) { + return ptr; + } + + template + static T *auth(T *ptr, const void *address) { + return ptrauth_strip(ptr, ptrauth_key_process_dependent_data); + } +}; + +// A ptrauth struct that signs and authenticates pointers using the +// DB key with the given discriminator and address diversification. +template +struct Ptrauth { + template + static T *sign(T *ptr, const void *address) { + if (!ptr) + return nullptr; + return ptrauth_sign_unauthenticated(ptr, ptrauth_key_process_dependent_data, ptrauth_blend_discriminator(address, discriminator)); + } + + template + static T *auth(T *ptr, const void *address) { + if (!ptr) + return nullptr; + return ptrauth_auth_data(ptr, ptrauth_key_process_dependent_data, ptrauth_blend_discriminator(address, discriminator)); + } +}; + +// A template that produces a WrappedPtr to the given type using a +// plain unauthenticated pointer. +template using RawPtr = WrappedPtr; + +#if __has_feature(ptrauth_calls) +// Get a ptrauth type that uses a string discriminator. +#define PTRAUTH_STR(name) Ptrauth + +// When ptrauth is available, declare a template that wraps a type +// in a WrappedPtr that uses an authenticated pointer using the +// process-dependent data key, address diversification, and a +// discriminator based on the name passed in. +// +// When ptrauth is not available, equivalent to RawPtr. +#define DECLARE_AUTHED_PTR_TEMPLATE(name) \ + template using name ## _authed_ptr \ + = WrappedPtr; +#else +#define PTRAUTH_STR(name) PtrauthRaw +#define DECLARE_AUTHED_PTR_TEMPLATE(name) \ + template using name ## _authed_ptr = RawPtr; +#endif + // _OBJC_PTRAUTH_H_ #endif diff --git a/runtime/objc-runtime-new.h b/runtime/objc-runtime-new.h index d3541cf..d6ce37c 100644 --- a/runtime/objc-runtime-new.h +++ b/runtime/objc-runtime-new.h @@ -240,6 +240,19 @@ struct bucket_t { public: inline SEL sel() const { return _sel.load(memory_order::memory_order_relaxed); } + inline IMP rawImp(objc_class *cls) const { + uintptr_t imp = _imp.load(memory_order::memory_order_relaxed); + if (!imp) return nil; +#if CACHE_IMP_ENCODING == CACHE_IMP_ENCODING_PTRAUTH +#elif CACHE_IMP_ENCODING == CACHE_IMP_ENCODING_ISA_XOR + imp ^= (uintptr_t)cls; +#elif CACHE_IMP_ENCODING == CACHE_IMP_ENCODING_NONE +#else +#error Unknown method cache IMP encoding. +#endif + return (IMP)imp; + } + inline IMP imp(Class cls) const { uintptr_t imp = _imp.load(memory_order::memory_order_relaxed); if (!imp) return nil; @@ -398,6 +411,27 @@ struct cache_t { typedef struct classref * classref_t; +/*********************************************************************** +* RelativePointer +* A pointer stored as an offset from the address of that offset. +* +* The target address is computed by taking the address of this struct +* and adding the offset stored within it. This is a 32-bit signed +* offset giving ±2GB of range. +**********************************************************************/ +template +struct RelativePointer: nocopy_t { + int32_t offset; + + T get() const { + uintptr_t base = (uintptr_t)&offset; + uintptr_t signExtendedOffset = (uintptr_t)(intptr_t)offset; + uintptr_t pointer = base + signExtendedOffset; + return (T)pointer; + } +}; + + #ifdef __PTRAUTH_INTRINSICS__ # define StubClassInitializerPtrauth __ptrauth(ptrauth_key_function_pointer, 1, 0xc671) #else @@ -408,20 +442,27 @@ struct stub_class_t { _objc_swiftMetadataInitializer StubClassInitializerPtrauth initializer; }; +// A pointer modifier that does nothing to the pointer. +struct PointerModifierNop { + template + static T *modify(const ListType &list, T *ptr) { return ptr; } +}; + /*********************************************************************** -* entsize_list_tt +* entsize_list_tt * Generic implementation of an array of non-fragile structs. * * Element is the struct type (e.g. method_t) * List is the specialization of entsize_list_tt (e.g. method_list_t) * FlagMask is used to stash extra bits in the entsize field * (e.g. method list fixup markers) +* PointerModifier is applied to the element pointers retrieved from +* the array. **********************************************************************/ -template +template struct entsize_list_tt { uint32_t entsizeAndFlags; uint32_t count; - Element first; uint32_t entsize() const { return entsizeAndFlags & ~FlagMask; @@ -432,7 +473,7 @@ struct entsize_list_tt { Element& getOrEnd(uint32_t i) const { ASSERT(i <= count); - return *(Element *)((uint8_t *)&first + i*entsize()); + return *PointerModifier::modify(*this, (Element *)((uint8_t *)this + sizeof(*this) + i*entsize())); } Element& get(uint32_t i) const { ASSERT(i < count); @@ -444,15 +485,7 @@ struct entsize_list_tt { } static size_t byteSize(uint32_t entsize, uint32_t count) { - return sizeof(entsize_list_tt) + (count-1)*entsize; - } - - List *duplicate() const { - auto *dup = (List *)calloc(this->byteSize(), 1); - dup->entsizeAndFlags = this->entsizeAndFlags; - dup->count = this->count; - std::copy(begin(), end(), dup->begin()); - return dup; + return sizeof(entsize_list_tt) + count*entsize; } struct iterator; @@ -541,18 +574,109 @@ struct entsize_list_tt { struct method_t { - SEL name; - const char *types; - MethodListIMP imp; + static const uint32_t smallMethodListFlag = 0x80000000; + + method_t(const method_t &other) = delete; + + // The representation of a "big" method. This is the traditional + // representation of three pointers storing the selector, types + // and implementation. + struct big { + SEL name; + const char *types; + MethodListIMP imp; + }; + +private: + bool isSmall() const { + return ((uintptr_t)this & 1) == 1; + } + + // The representation of a "small" method. This stores three + // relative offsets to the name, types, and implementation. + struct small { + RelativePointer name; + RelativePointer types; + RelativePointer imp; + }; + + small &small() const { + ASSERT(isSmall()); + return *(struct small *)((uintptr_t)this & ~(uintptr_t)1); + } + + IMP remappedImp(bool needsLock) const; + void remapImp(IMP imp); + objc_method_description *getSmallDescription() const; + +public: + static const auto bigSize = sizeof(struct big); + static const auto smallSize = sizeof(struct small); + + // The pointer modifier used with method lists. When the method + // list contains small methods, set the bottom bit of the pointer. + // We use that bottom bit elsewhere to distinguish between big + // and small methods. + struct pointer_modifier { + template + static method_t *modify(const ListType &list, method_t *ptr) { + if (list.flags() & smallMethodListFlag) + return (method_t *)((uintptr_t)ptr | 1); + return ptr; + } + }; + + big &big() const { + ASSERT(!isSmall()); + return *(struct big *)this; + } + + SEL &name() const { + return isSmall() ? *small().name.get() : big().name; + } + const char *types() const { + return isSmall() ? small().types.get() : big().types; + } + IMP imp(bool needsLock) const { + if (isSmall()) { + IMP imp = remappedImp(needsLock); + if (!imp) + imp = ptrauth_sign_unauthenticated(small().imp.get(), + ptrauth_key_function_pointer, 0); + return imp; + } + return big().imp; + } + + void setImp(IMP imp) { + if (isSmall()) { + remapImp(imp); + } else { + big().imp = imp; + } + + } + + objc_method_description *getDescription() const { + return isSmall() ? getSmallDescription() : (struct objc_method_description *)this; + } struct SortBySELAddress : - public std::binary_function + public std::binary_function { - bool operator() (const method_t& lhs, - const method_t& rhs) + bool operator() (const struct method_t::big& lhs, + const struct method_t::big& rhs) { return lhs.name < rhs.name; } }; + + method_t &operator=(const method_t &other) { + ASSERT(!isSmall()); + big().name = other.name(); + big().types = other.types(); + big().imp = other.imp(false); + return *this; + } }; struct ivar_t { @@ -583,7 +707,15 @@ struct property_t { }; // Two bits of entsize are used for fixup markers. -struct method_list_t : entsize_list_tt { +// Reserve the top half of entsize for more flags. We never +// need entry sizes anywhere close to 64kB. +// +// Currently there is one flag defined: the small method list flag, +// method_t::smallMethodListFlag. Other flags are currently ignored. +// (NOTE: these bits are only ignored on runtimes that support small +// method lists. Older runtimes will treat them as part of the entry +// size!) +struct method_list_t : entsize_list_tt { bool isUniqued() const; bool isFixedUp() const; void setFixedUp(); @@ -594,6 +726,31 @@ struct method_list_t : entsize_list_tt { ASSERT(i < count); return i; } + + bool isSmallList() const { + return flags() & method_t::smallMethodListFlag; + } + + bool isExpectedSize() const { + if (isSmallList()) + return entsize() == method_t::smallSize; + else + return entsize() == method_t::bigSize; + } + + method_list_t *duplicate() const { + method_list_t *dup; + if (isSmallList()) { + dup = (method_list_t *)calloc(byteSize(method_t::bigSize, count), 1); + dup->entsizeAndFlags = method_t::bigSize; + } else { + dup = (method_list_t *)calloc(this->byteSize(), 1); + dup->entsizeAndFlags = this->entsizeAndFlags; + } + dup->count = this->count; + std::copy(begin(), end(), dup->begin()); + return dup; + } }; struct ivar_list_t : entsize_list_tt { @@ -707,7 +864,7 @@ struct class_ro_t { const uint8_t * ivarLayout; const char * name; - method_list_t * baseMethodList; + WrappedPtr baseMethodList; protocol_list_t * baseProtocols; const ivar_list_t * ivars; @@ -745,11 +902,13 @@ struct class_ro_t { /*********************************************************************** -* list_array_tt +* list_array_tt * Generic implementation for metadata that can be augmented by categories. * * Element is the underlying metadata type (e.g. method_t) * List is the metadata's list type (e.g. method_list_t) +* List is a template applied to Element to make Element*. Useful for +* applying qualifiers to the pointer type. * * A list_array_tt has one of three values: * - empty @@ -759,11 +918,11 @@ struct class_ro_t { * countLists/beginLists/endLists iterate the metadata lists * count/begin/end iterate the underlying metadata elements **********************************************************************/ -template +template class Ptr> class list_array_tt { struct array_t { uint32_t count; - List* lists[0]; + Ptr lists[0]; static size_t byteSize(uint32_t count) { return sizeof(array_t) + count*sizeof(lists[0]); @@ -775,12 +934,12 @@ class list_array_tt { protected: class iterator { - List * const *lists; - List * const *listsEnd; + const Ptr *lists; + const Ptr *listsEnd; typename List::iterator m, mEnd; public: - iterator(List *const *begin, List *const *end) + iterator(const Ptr *begin, const Ptr *end) : lists(begin), listsEnd(end) { if (begin != end) { @@ -820,7 +979,7 @@ class list_array_tt { private: union { - List* list; + Ptr list; uintptr_t arrayAndFlag; }; @@ -836,9 +995,26 @@ class list_array_tt { arrayAndFlag = (uintptr_t)array | 1; } + void validate() { + for (auto cursor = beginLists(), end = endLists(); cursor != end; cursor++) + cursor->validate(); + } + public: list_array_tt() : list(nullptr) { } list_array_tt(List *l) : list(l) { } + list_array_tt(const list_array_tt &other) { + *this = other; + } + + list_array_tt &operator =(const list_array_tt &other) { + if (other.hasArray()) { + arrayAndFlag = other.arrayAndFlag; + } else { + list = other.list; + } + return *this; + } uint32_t count() const { uint32_t result = 0; @@ -856,7 +1032,7 @@ class list_array_tt { } iterator end() const { - List * const *e = endLists(); + auto e = endLists(); return iterator(e, e); } @@ -871,7 +1047,7 @@ class list_array_tt { } } - List* const * beginLists() const { + const Ptr* beginLists() const { if (hasArray()) { return array()->lists; } else { @@ -879,7 +1055,7 @@ class list_array_tt { } } - List* const * endLists() const { + const Ptr* endLists() const { if (hasArray()) { return array()->lists + array()->count; } else if (list) { @@ -896,27 +1072,34 @@ class list_array_tt { // many lists -> many lists uint32_t oldCount = array()->count; uint32_t newCount = oldCount + addedCount; - setArray((array_t *)realloc(array(), array_t::byteSize(newCount))); + array_t *newArray = (array_t *)malloc(array_t::byteSize(newCount)); + newArray->count = newCount; array()->count = newCount; - memmove(array()->lists + addedCount, array()->lists, - oldCount * sizeof(array()->lists[0])); - memcpy(array()->lists, addedLists, - addedCount * sizeof(array()->lists[0])); + + for (int i = oldCount - 1; i >= 0; i--) + newArray->lists[i + addedCount] = array()->lists[i]; + for (unsigned i = 0; i < addedCount; i++) + newArray->lists[i] = addedLists[i]; + free(array()); + setArray(newArray); + validate(); } else if (!list && addedCount == 1) { // 0 lists -> 1 list list = addedLists[0]; + validate(); } else { // 1 list -> many lists - List* oldList = list; + Ptr oldList = list; uint32_t oldCount = oldList ? 1 : 0; uint32_t newCount = oldCount + addedCount; setArray((array_t *)malloc(array_t::byteSize(newCount))); array()->count = newCount; if (oldList) array()->lists[addedCount] = oldList; - memcpy(array()->lists, addedLists, - addedCount * sizeof(array()->lists[0])); + for (unsigned i = 0; i < addedCount; i++) + array()->lists[i] = addedLists[i]; + validate(); } } @@ -932,79 +1115,66 @@ class list_array_tt { } } - template - Result duplicate() { - Result result; - + template + void duplicateInto(Other &other) { if (hasArray()) { array_t *a = array(); - result.setArray((array_t *)memdup(a, a->byteSize())); + other.setArray((array_t *)memdup(a, a->byteSize())); for (uint32_t i = 0; i < a->count; i++) { - result.array()->lists[i] = a->lists[i]->duplicate(); + other.array()->lists[i] = a->lists[i]->duplicate(); } } else if (list) { - result.list = list->duplicate(); + other.list = list->duplicate(); } else { - result.list = nil; + other.list = nil; } - - return result; } }; +DECLARE_AUTHED_PTR_TEMPLATE(method_list_t) + class method_array_t : - public list_array_tt + public list_array_tt { - typedef list_array_tt Super; + typedef list_array_tt Super; public: method_array_t() : Super() { } method_array_t(method_list_t *l) : Super(l) { } - method_list_t * const *beginCategoryMethodLists() const { + const method_list_t_authed_ptr *beginCategoryMethodLists() const { return beginLists(); } - method_list_t * const *endCategoryMethodLists(Class cls) const; - - method_array_t duplicate() { - return Super::duplicate(); - } + const method_list_t_authed_ptr *endCategoryMethodLists(Class cls) const; }; class property_array_t : - public list_array_tt + public list_array_tt { - typedef list_array_tt Super; + typedef list_array_tt Super; public: property_array_t() : Super() { } property_array_t(property_list_t *l) : Super(l) { } - - property_array_t duplicate() { - return Super::duplicate(); - } }; class protocol_array_t : - public list_array_tt + public list_array_tt { - typedef list_array_tt Super; + typedef list_array_tt Super; public: protocol_array_t() : Super() { } protocol_array_t(protocol_list_t *l) : Super(l) { } - - protocol_array_t duplicate() { - return Super::duplicate(); - } }; struct class_rw_ext_t { - const class_ro_t *ro; + DECLARE_AUTHED_PTR_TEMPLATE(class_ro_t) + class_ro_t_authed_ptr ro; method_array_t methods; property_array_t properties; protocol_array_t protocols; @@ -1026,21 +1196,21 @@ struct class_rw_t { Class nextSiblingClass; private: - using ro_or_rw_ext_t = objc::PointerUnion; + using ro_or_rw_ext_t = objc::PointerUnion; const ro_or_rw_ext_t get_ro_or_rwe() const { return ro_or_rw_ext_t{ro_or_rw_ext}; } void set_ro_or_rwe(const class_ro_t *ro) { - ro_or_rw_ext_t{ro}.storeAt(ro_or_rw_ext, memory_order_relaxed); + ro_or_rw_ext_t{ro, &ro_or_rw_ext}.storeAt(ro_or_rw_ext, memory_order_relaxed); } void set_ro_or_rwe(class_rw_ext_t *rwe, const class_ro_t *ro) { // the release barrier is so that the class_rw_ext_t::ro initialization // is visible to lockless readers rwe->ro = ro; - ro_or_rw_ext_t{rwe}.storeAt(ro_or_rw_ext, memory_order_release); + ro_or_rw_ext_t{rwe, &ro_or_rw_ext}.storeAt(ro_or_rw_ext, memory_order_release); } class_rw_ext_t *extAlloc(const class_ro_t *ro, bool deep = false); @@ -1069,15 +1239,15 @@ struct class_rw_t { } class_rw_ext_t *ext() const { - return get_ro_or_rwe().dyn_cast(); + return get_ro_or_rwe().dyn_cast(&ro_or_rw_ext); } class_rw_ext_t *extAllocIfNeeded() { auto v = get_ro_or_rwe(); if (fastpath(v.is())) { - return v.get(); + return v.get(&ro_or_rw_ext); } else { - return extAlloc(v.get()); + return extAlloc(v.get(&ro_or_rw_ext)); } } @@ -1088,15 +1258,15 @@ struct class_rw_t { const class_ro_t *ro() const { auto v = get_ro_or_rwe(); if (slowpath(v.is())) { - return v.get()->ro; + return v.get(&ro_or_rw_ext)->ro; } - return v.get(); + return v.get(&ro_or_rw_ext); } void set_ro(const class_ro_t *ro) { auto v = get_ro_or_rwe(); if (v.is()) { - v.get()->ro = ro; + v.get(&ro_or_rw_ext)->ro = ro; } else { set_ro_or_rwe(ro); } @@ -1105,27 +1275,27 @@ struct class_rw_t { const method_array_t methods() const { auto v = get_ro_or_rwe(); if (v.is()) { - return v.get()->methods; + return v.get(&ro_or_rw_ext)->methods; } else { - return method_array_t{v.get()->baseMethods()}; + return method_array_t{v.get(&ro_or_rw_ext)->baseMethods()}; } } const property_array_t properties() const { auto v = get_ro_or_rwe(); if (v.is()) { - return v.get()->properties; + return v.get(&ro_or_rw_ext)->properties; } else { - return property_array_t{v.get()->baseProperties}; + return property_array_t{v.get(&ro_or_rw_ext)->baseProperties}; } } const protocol_array_t protocols() const { auto v = get_ro_or_rwe(); if (v.is()) { - return v.get()->protocols; + return v.get(&ro_or_rw_ext)->protocols; } else { - return protocol_array_t{v.get()->baseProtocols}; + return protocol_array_t{v.get(&ro_or_rw_ext)->baseProtocols}; } } }; @@ -1650,8 +1820,8 @@ struct swift_class_t : objc_class { struct category_t { const char *name; classref_t cls; - struct method_list_t *instanceMethods; - struct method_list_t *classMethods; + WrappedPtr instanceMethods; + WrappedPtr classMethods; struct protocol_list_t *protocols; struct property_list_t *instanceProperties; // Fields below this point are not always present on disk. diff --git a/runtime/objc-runtime-new.mm b/runtime/objc-runtime-new.mm index e158375..1eabd5c 100644 --- a/runtime/objc-runtime-new.mm +++ b/runtime/objc-runtime-new.mm @@ -46,7 +46,7 @@ static IMP addMethod(Class cls, SEL name, IMP imp, const char *types, bool replace); static void adjustCustomFlagsForMethodChange(Class cls, method_t *meth); static method_t *search_method_list(const method_list_t *mlist, SEL sel); -static bool method_lists_contains_any(method_list_t * const *mlists, method_list_t * const *end, +template static bool method_lists_contains_any(T *mlists, T *end, SEL sels[], size_t selcount); static void flushCaches(Class cls); static void initializeTaggedPointerObfuscator(void); @@ -212,6 +212,59 @@ static bool method_lists_contains_any(method_list_t * const *mlists, method_list **********************************************************************/ bool didCallDyldNotifyRegister = false; + +/*********************************************************************** +* smallMethodIMPMap +* The map from small method pointers to replacement IMPs. +* +* Locking: runtimeLock must be held when accessing this map. +**********************************************************************/ +namespace objc { + static objc::LazyInitDenseMap smallMethodIMPMap; +} + +static IMP method_t_remappedImp_nolock(const method_t *m) { + runtimeLock.assertLocked(); + auto *map = objc::smallMethodIMPMap.get(false); + if (!map) + return nullptr; + auto iter = map->find(m); + if (iter == map->end()) + return nullptr; + return iter->second; +} + +IMP method_t::remappedImp(bool needsLock) const { + ASSERT(isSmall()); + if (needsLock) { + mutex_locker_t guard(runtimeLock); + return method_t_remappedImp_nolock(this); + } else { + return method_t_remappedImp_nolock(this); + } +} + +void method_t::remapImp(IMP imp) { + ASSERT(isSmall()); + runtimeLock.assertLocked(); + auto *map = objc::smallMethodIMPMap.get(true); + (*map)[this] = imp; +} + +objc_method_description *method_t::getSmallDescription() const { + static objc::LazyInitDenseMap map; + + mutex_locker_t guard(runtimeLock); + + auto &ptr = (*map.get(true))[this]; + if (!ptr) { + ptr = (objc_method_description *)malloc(sizeof *ptr); + ptr->name = name(); + ptr->types = (char *)types(); + } + return ptr; +} + /* Low two bits of mlist->entsize is used as the fixed-up marker. PREOPTIMIZED VERSION: @@ -258,7 +311,8 @@ static bool method_lists_contains_any(method_list_t * const *mlists, method_list } bool method_list_t::isFixedUp() const { - return flags() == fixed_up_method_list; + // Ignore any flags in the top bits, just look at the bottom two. + return (flags() & 0x3) == fixed_up_method_list; } void method_list_t::setFixedUp() { @@ -288,11 +342,11 @@ static bool method_lists_contains_any(method_list_t * const *mlists, method_list } -method_list_t * const *method_array_t::endCategoryMethodLists(Class cls) const +const method_list_t_authed_ptr *method_array_t::endCategoryMethodLists(Class cls) const { auto mlists = beginLists(); auto mlistsEnd = endLists(); - + if (mlists == mlistsEnd || !cls->data()->ro()->baseMethods()) { // No methods, or no base methods. @@ -536,7 +590,7 @@ static Class metaclassNSObject(void) if (!mlist) continue; for (const auto& meth : *mlist) { - SEL s = sel_registerName(sel_cname(meth.name)); + SEL s = sel_registerName(sel_cname(meth.name())); // Search for replaced methods in method lookup order. // Complain about the first duplicate only. @@ -549,11 +603,11 @@ static Class metaclassNSObject(void) if (!mlist2) continue; for (const auto& meth2 : *mlist2) { - SEL s2 = sel_registerName(sel_cname(meth2.name)); + SEL s2 = sel_registerName(sel_cname(meth2.name())); if (s == s2) { logReplacedMethod(cls->nameForLogging(), s, cls->isMetaClass(), cat->name, - meth2.imp, meth.imp); + meth2.imp(false), meth.imp(false)); goto complained; } } @@ -561,11 +615,11 @@ static Class metaclassNSObject(void) // Look for method in cls for (const auto& meth2 : cls->data()->methods()) { - SEL s2 = sel_registerName(sel_cname(meth2.name)); + SEL s2 = sel_registerName(sel_cname(meth2.name())); if (s == s2) { logReplacedMethod(cls->nameForLogging(), s, cls->isMetaClass(), cat->name, - meth2.imp, meth.imp); + meth2.imp(false), meth.imp(false)); goto complained; } } @@ -892,7 +946,7 @@ static void __attribute__((cold, noinline)) static void scanChangedMethod(Class cls, const method_t *meth) { - if (fastpath(!Traits::isInterestingSelector(meth->name))) { + if (fastpath(!Traits::isInterestingSelector(meth->name()))) { return; } @@ -938,7 +992,8 @@ static void setDefault(Class cls) { static bool isInterestingSelector(SEL sel) { return sel == @selector(alloc) || sel == @selector(allocWithZone:); } - static bool scanMethodLists(method_list_t * const *mlists, method_list_t * const *end) { + template + static bool scanMethodLists(T *mlists, T *end) { SEL sels[2] = { @selector(alloc), @selector(allocWithZone:), }; return method_lists_contains_any(mlists, end, sels, 2); } @@ -972,7 +1027,8 @@ static bool isInterestingSelector(SEL sel) { sel == @selector(allowsWeakReference) || sel == @selector(retainWeakReference); } - static bool scanMethodLists(method_list_t * const *mlists, method_list_t * const *end) { + template + static bool scanMethodLists(T *mlists, T *end) { SEL sels[8] = { @selector(retain), @selector(release), @@ -1007,7 +1063,8 @@ static bool isInterestingSelector(SEL sel) { sel == @selector(isKindOfClass:) || sel == @selector(respondsToSelector:); } - static bool scanMethodLists(method_list_t * const *mlists, method_list_t * const *end) { + template + static bool scanMethodLists(T *mlists, T *end) { SEL sels[5] = { @selector(new), @selector(self), @@ -1193,19 +1250,25 @@ static bool isBundleClass(Class cls) // Unique selectors in list. for (auto& meth : *mlist) { - const char *name = sel_cname(meth.name); - meth.name = sel_registerNameNoLock(name, bundleCopy); + const char *name = sel_cname(meth.name()); + meth.name() = sel_registerNameNoLock(name, bundleCopy); } } // Sort by selector address. - if (sort) { + // Don't try to sort small lists, as they're immutable. + // Don't try to sort big lists of nonstandard size, as stable_sort + // won't copy the entries properly. + if (sort && !mlist->isSmallList() && mlist->entsize() == method_t::bigSize) { method_t::SortBySELAddress sorter; - std::stable_sort(mlist->begin(), mlist->end(), sorter); + std::stable_sort(&mlist->begin()->big(), &mlist->end()->big(), sorter); } - // Mark method list as uniqued and sorted - mlist->setFixedUp(); + // Mark method list as uniqued and sorted. + // Can't mark small lists, since they're immutable. + if (!mlist->isSmallList()) { + mlist->setFixedUp(); + } } @@ -1391,6 +1454,9 @@ static void methodizeClass(Class cls, Class previously) // Install methods and properties that the class implements itself. method_list_t *list = ro->baseMethods(); if (list) { + if (list->isSmallList() && !_dyld_is_memory_immutable(list, list->byteSize())) + _objc_fatal("CLASS: class '%s' %p small method list %p is not in immutable memory", + cls->nameForLogging(), cls, list); prepareMethodLists(cls, &list, 1, YES, isBundleClass(cls)); if (rwe) rwe->methods.attachLists(&list, 1); } @@ -1433,9 +1499,9 @@ static void methodizeClass(Class cls, Class previously) for (const auto& meth : rw->methods()) { if (PrintConnecting) { _objc_inform("METHOD %c[%s %s]", isMeta ? '+' : '-', - cls->nameForLogging(), sel_getName(meth.name)); + cls->nameForLogging(), sel_getName(meth.name())); } - ASSERT(sel_registerName(sel_getName(meth.name)) == meth.name); + ASSERT(sel_registerName(sel_getName(meth.name())) == meth.name()); } #endif } @@ -2270,16 +2336,8 @@ static void removeSubclass(Class supercls, Class subcls) if (result) return result; } - // Try table from dyld shared cache - // Temporarily check that we are using the new table. Eventually this check - // will always be true. - // FIXME: Remove this check when we can - if (sharedCacheSupportsProtocolRoots()) { - result = getPreoptimizedProtocol(name); - if (result) return result; - } - - return nil; + // Try table from dyld3 closure and dyld shared cache + return getPreoptimizedProtocol(name); } @@ -3053,8 +3111,8 @@ static void load_categories_nolock(header_info *hi) { } }; - processCatlist(_getObjc2CategoryList(hi, &count)); - processCatlist(_getObjc2CategoryList2(hi, &count)); + processCatlist(hi->catlist(&count)); + processCatlist(hi->catlist2(&count)); } static void loadAllCategories() { @@ -3554,7 +3612,6 @@ void _read_images(header_info **hList, uint32_t hCount, int totalClasses, int un ts.log("IMAGE TIMES: fix up objc_msgSend_fixup"); #endif - bool cacheSupportsProtocolRoots = sharedCacheSupportsProtocolRoots(); // Discover protocols. Fix up protocol refs. for (EACH_HEADER) { @@ -3570,7 +3627,7 @@ void _read_images(header_info **hList, uint32_t hCount, int totalClasses, int un // in the shared cache is marked with isCanonical() and that may not // be true if some non-shared cache binary was chosen as the canonical // definition - if (launchTime && isPreoptimized && cacheSupportsProtocolRoots) { + if (launchTime && isPreoptimized) { if (PrintProtocols) { _objc_inform("PROTOCOLS: Skipping reading protocols in image: %s", hi->fname()); @@ -3597,7 +3654,7 @@ void _read_images(header_info **hList, uint32_t hCount, int totalClasses, int un // shared cache definition of a protocol. We can skip the check on // launch, but have to visit @protocol refs for shared cache images // loaded later. - if (launchTime && cacheSupportsProtocolRoots && hi->isPreoptimized()) + if (launchTime && hi->isPreoptimized()) continue; protocol_t **protolist = _getObjc2ProtocolRefs(hi, &count); for (i = 0; i < count; i++) { @@ -3627,8 +3684,7 @@ void _read_images(header_info **hList, uint32_t hCount, int totalClasses, int un // Realize non-lazy classes (for +load methods and static instances) for (EACH_HEADER) { - classref_t const *classlist = - _getObjc2NonlazyClassList(hi, &count); + classref_t const *classlist = hi->nlclslist(&count); for (i = 0; i < count; i++) { Class cls = remapClass(classlist[i]); if (!cls) continue; @@ -3808,7 +3864,7 @@ void _unload_image(header_info *hi) // Ignore __objc_catlist2. We don't support unloading Swift // and we never will. - category_t * const *catlist = _getObjc2CategoryList(hi, &count); + category_t * const *catlist = hi->catlist(&count); for (i = 0; i < count; i++) { category_t *cat = catlist[i]; Class cls = remapClass(cat->cls); @@ -3838,7 +3894,7 @@ void _unload_image(header_info *hi) if (cls) classes.insert(cls); } - classlist = _getObjc2NonlazyClassList(hi, &count); + classlist = hi->nlclslist(&count); for (i = 0; i < count; i++) { Class cls = remapClass(classlist[i]); if (cls) classes.insert(cls); @@ -3873,14 +3929,19 @@ void _unload_image(header_info *hi) method_getDescription(Method m) { if (!m) return nil; - return (struct objc_method_description *)m; + return m->getDescription(); } IMP method_getImplementation(Method m) { - return m ? m->imp : nil; + return m ? m->imp(true) : nil; +} + +IMPAndSEL _method_getImplementationAndName(Method m) +{ + return { m->imp(true), m->name() }; } @@ -3896,8 +3957,8 @@ void _unload_image(header_info *hi) { if (!m) return nil; - ASSERT(m->name == sel_registerName(sel_getName(m->name))); - return m->name; + ASSERT(m->name() == sel_registerName(sel_getName(m->name()))); + return m->name(); } @@ -3911,7 +3972,7 @@ void _unload_image(header_info *hi) method_getTypeEncoding(Method m) { if (!m) return nil; - return m->types; + return m->types(); } @@ -3928,8 +3989,8 @@ void _unload_image(header_info *hi) if (!m) return nil; if (!imp) return nil; - IMP old = m->imp; - m->imp = imp; + IMP old = m->imp(false); + m->setImp(imp); // Cache updates are slow if cls is nil (i.e. unknown) // RR/AWZ updates are slow if cls is nil (i.e. unknown) @@ -3958,9 +4019,9 @@ void method_exchangeImplementations(Method m1, Method m2) mutex_locker_t lock(runtimeLock); - IMP m1_imp = m1->imp; - m1->imp = m2->imp; - m2->imp = m1_imp; + IMP m1_imp = m1->imp(false); + m1->setImp(m2->imp(false)); + m2->setImp(m1_imp); // RR/AWZ updates are slow because class is unknown @@ -4121,7 +4182,7 @@ static uint32_t getExtendedTypesIndexForMethod(protocol_t *proto, const method_t fixupMethodList(mlist, true/*always copy for simplicity*/, !extTypes/*sort if no extended method types*/); - if (extTypes) { + if (extTypes && !mlist->isSmallList()) { // Sort method list and extended method types together. // fixupMethodList() can't do this. // fixme COW stomp @@ -4132,8 +4193,8 @@ static uint32_t getExtendedTypesIndexForMethod(protocol_t *proto, const method_t required, instance, prefix, junk); for (uint32_t i = 0; i < count; i++) { for (uint32_t j = i+1; j < count; j++) { - method_t& mi = mlist->get(i); - method_t& mj = mlist->get(j); + auto& mi = mlist->get(i).big(); + auto& mj = mlist->get(j).big(); if (mi.name > mj.name) { std::swap(mi, mj); std::swap(extTypes[prefix+i], extTypes[prefix+j]); @@ -4372,7 +4433,9 @@ static uint32_t getExtendedTypesIndexForMethod(protocol_t *proto, const method_t Method m = protocol_getMethod(newprotocol(p), aSel, isRequiredMethod, isInstanceMethod, true); - if (m) return *method_getDescription(m); + // method_getDescription is inefficient for small methods. Don't bother + // trying to use it, just make our own. + if (m) return (struct objc_method_description){m->name(), (char *)m->types()}; else return (struct objc_method_description){nil, nil}; } @@ -4477,8 +4540,8 @@ BOOL protocol_isEqual(Protocol *self, Protocol *other) result = (struct objc_method_description *) calloc(mlist->count + 1, sizeof(struct objc_method_description)); for (const auto& meth : *mlist) { - result[count].name = meth.name; - result[count].types = (char *)meth.types; + result[count].name = meth.name(); + result[count].types = (char *)meth.types(); count++; } } @@ -4763,15 +4826,15 @@ void objc_registerProtocol(Protocol *proto_gen) protocol_addMethod_nolock(method_list_t*& list, SEL name, const char *types) { if (!list) { - list = (method_list_t *)calloc(sizeof(method_list_t), 1); - list->entsizeAndFlags = sizeof(list->first); + list = (method_list_t *)calloc(method_list_t::byteSize(sizeof(struct method_t::big), 1), 1); + list->entsizeAndFlags = sizeof(struct method_t::big); list->setFixedUp(); } else { size_t size = list->byteSize() + list->entsize(); list = (method_list_t *)realloc(list, size); } - method_t& meth = list->get(list->count++); + auto &meth = list->get(list->count++).big(); meth.name = name; meth.types = types ? strdupIfMutable(types) : ""; meth.imp = nil; @@ -4819,15 +4882,15 @@ void objc_registerProtocol(Protocol *proto_gen) unsigned int count) { if (!plist) { - plist = (property_list_t *)calloc(sizeof(property_list_t), 1); + plist = (property_list_t *)calloc(property_list_t::byteSize(sizeof(property_t), 1), 1); plist->entsizeAndFlags = sizeof(property_t); + plist->count = 1; } else { - plist = (property_list_t *) - realloc(plist, sizeof(property_list_t) - + plist->count * plist->entsize()); + plist->count++; + plist = (property_list_t *)realloc(plist, plist->byteSize()); } - property_t& prop = plist->get(plist->count++); + property_t& prop = plist->get(plist->count - 1); prop.name = strdupIfMutable(name); prop.attributes = copyPropertyAttributeString(attrs, count); } @@ -5038,7 +5101,7 @@ void objc_registerProtocol(Protocol *proto_gen) // Find all the protocols from the pre-optimized images. These protocols // won't be in the protocol map. objc::DenseMap preoptimizedProtocols; - if (sharedCacheSupportsProtocolRoots()) { + { header_info *hi; for (hi = FirstHeader; hi; hi = hi->getNext()) { if (!hi->hasPreoptimizedProtocols()) @@ -5242,9 +5305,9 @@ void objc_registerProtocol(Protocol *proto_gen) mlist = ISA()->data()->ro()->baseMethods(); if (mlist) { for (const auto& meth : *mlist) { - const char *name = sel_cname(meth.name); + const char *name = sel_cname(meth.name()); if (0 == strcmp(name, "load")) { - return meth.imp; + return meth.imp(false); } } } @@ -5312,9 +5375,9 @@ void objc_registerProtocol(Protocol *proto_gen) mlist = cat->classMethods; if (mlist) { for (const auto& meth : *mlist) { - const char *name = sel_cname(meth.name); + const char *name = sel_cname(meth.name()); if (0 == strcmp(name, "load")) { - return meth.imp; + return meth.imp(false); } } } @@ -5740,25 +5803,26 @@ void objc_registerProtocol(Protocol *proto_gen) { ASSERT(list); - const method_t * const first = &list->first; - const method_t *base = first; - const method_t *probe; + auto first = list->begin(); + auto base = first; + decltype(first) probe; + uintptr_t keyValue = (uintptr_t)key; uint32_t count; for (count = list->count; count != 0; count >>= 1) { probe = base + (count >> 1); - uintptr_t probeValue = (uintptr_t)probe->name; + uintptr_t probeValue = (uintptr_t)probe->name(); if (keyValue == probeValue) { // `probe` is a match. // Rewind looking for the *first* occurrence of this value. // This is required for correct category overrides. - while (probe > first && keyValue == (uintptr_t)probe[-1].name) { + while (probe > first && keyValue == (uintptr_t)(probe - 1)->name()) { probe--; } - return (method_t *)probe; + return &*probe; } if (keyValue > probeValue) { @@ -5774,14 +5838,14 @@ void objc_registerProtocol(Protocol *proto_gen) search_method_list_inline(const method_list_t *mlist, SEL sel) { int methodListIsFixedUp = mlist->isFixedUp(); - int methodListHasExpectedSize = mlist->entsize() == sizeof(method_t); + int methodListHasExpectedSize = mlist->isExpectedSize(); if (fastpath(methodListIsFixedUp && methodListHasExpectedSize)) { return findMethodInSortedMethodList(sel, mlist); } else { // Linear search of unsorted method list for (auto& meth : *mlist) { - if (meth.name == sel) return &meth; + if (meth.name() == sel) return &meth; } } @@ -5789,7 +5853,7 @@ void objc_registerProtocol(Protocol *proto_gen) // sanity-check negative results if (mlist->isFixedUp()) { for (auto& meth : *mlist) { - if (meth.name == sel) { + if (meth.name() == sel) { _objc_fatal("linear search worked when binary search did not"); } } @@ -5808,14 +5872,15 @@ void objc_registerProtocol(Protocol *proto_gen) /*********************************************************************** * method_lists_contains_any **********************************************************************/ +template static NEVER_INLINE bool -method_lists_contains_any(method_list_t * const *mlists, method_list_t * const *end, +method_lists_contains_any(T *mlists, T *end, SEL sels[], size_t selcount) { while (mlists < end) { const method_list_t *mlist = *mlists++; int methodListIsFixedUp = mlist->isFixedUp(); - int methodListHasExpectedSize = mlist->entsize() == sizeof(method_t); + int methodListHasExpectedSize = mlist->entsize() == sizeof(struct method_t::big); if (fastpath(methodListIsFixedUp && methodListHasExpectedSize)) { for (size_t i = 0; i < selcount; i++) { @@ -5826,7 +5891,7 @@ void objc_registerProtocol(Protocol *proto_gen) } else { for (auto& meth : *mlist) { for (size_t i = 0; i < selcount; i++) { - if (meth.name == sels[i]) { + if (meth.name() == sels[i]) { return true; } } @@ -6124,8 +6189,6 @@ IMP lookUpImpOrForward(id inst, SEL sel, Class cls, int behavior) // To make these harder we want to make sure this is a class that was // either built into the binary or legitimately registered through // objc_duplicateClass, objc_initializeClassPair or objc_allocateClassPair. - // - // TODO: this check is quite costly during process startup. checkIsKnownClass(cls); if (slowpath(!cls->isRealized())) { @@ -6157,7 +6220,7 @@ IMP lookUpImpOrForward(id inst, SEL sel, Class cls, int behavior) // curClass method list. Method meth = getMethodNoSuper_nolock(curClass, sel); if (meth) { - imp = meth->imp; + imp = meth->imp(false); goto done; } @@ -6230,8 +6293,8 @@ IMP lookupMethodInClassAndLoadCache(Class cls, SEL sel) if (meth) { // Hit in method list. Cache it. - cache_fill(cls, sel, meth->imp, nil); - return meth->imp; + cache_fill(cls, sel, meth->imp(false), nil); + return meth->imp(false); } else { // Miss in method list. Cache objc_msgForward. cache_fill(cls, sel, _objc_msgForward_impcache, nil); @@ -6603,7 +6666,7 @@ BOOL class_conformsToProtocol(Class cls, Protocol *proto_gen) if ((m = getMethodNoSuper_nolock(cls, name))) { // already exists if (!replace) { - result = m->imp; + result = m->imp(false); } else { result = _method_setImplementation(cls, m, imp); } @@ -6612,13 +6675,14 @@ BOOL class_conformsToProtocol(Class cls, Protocol *proto_gen) // fixme optimize method_list_t *newlist; - newlist = (method_list_t *)calloc(sizeof(*newlist), 1); + newlist = (method_list_t *)calloc(method_list_t::byteSize(method_t::bigSize, 1), 1); newlist->entsizeAndFlags = - (uint32_t)sizeof(method_t) | fixed_up_method_list; + (uint32_t)sizeof(struct method_t::big) | fixed_up_method_list; newlist->count = 1; - newlist->first.name = name; - newlist->first.types = strdupIfMutable(types); - newlist->first.imp = imp; + auto &first = newlist->begin()->big(); + first.name = name; + first.types = strdupIfMutable(types); + first.imp = imp; prepareMethodLists(cls, &newlist, 1, NO, NO); rwe->methods.attachLists(&newlist, 1); @@ -6650,14 +6714,12 @@ BOOL class_conformsToProtocol(Class cls, Protocol *proto_gen) ASSERT(cls->isRealized()); method_list_t *newlist; - size_t newlistSize = method_list_t::byteSize(sizeof(method_t), count); + size_t newlistSize = method_list_t::byteSize(sizeof(struct method_t::big), count); newlist = (method_list_t *)calloc(newlistSize, 1); newlist->entsizeAndFlags = - (uint32_t)sizeof(method_t) | fixed_up_method_list; + (uint32_t)sizeof(struct method_t::big) | fixed_up_method_list; newlist->count = 0; - method_t *newlistMethods = &newlist->first; - SEL *failedNames = nil; uint32_t failedCount = 0; @@ -6673,16 +6735,16 @@ BOOL class_conformsToProtocol(Class cls, Protocol *proto_gen) failedNames = (SEL *)calloc(sizeof(*failedNames), count + 1); } - failedNames[failedCount] = m->name; + failedNames[failedCount] = m->name(); failedCount++; } else { _method_setImplementation(cls, m, imps[i]); } } else { - method_t *newmethod = &newlistMethods[newlist->count]; - newmethod->name = names[i]; - newmethod->types = strdupIfMutable(types[i]); - newmethod->imp = imps[i]; + auto &newmethod = newlist->end()->big(); + newmethod.name = names[i]; + newmethod.types = strdupIfMutable(types[i]); + newmethod.imp = imps[i]; newlist->count++; } } @@ -6694,7 +6756,7 @@ BOOL class_conformsToProtocol(Class cls, Protocol *proto_gen) // Note that realloc() alone doesn't work due to ptrauth. method_t::SortBySELAddress sorter; - std::stable_sort(newlist->begin(), newlist->end(), sorter); + std::stable_sort(&newlist->begin()->big(), &newlist->end()->big(), sorter); prepareMethodLists(cls, &newlist, 1, NO, NO); rwe->methods.attachLists(&newlist, 1); @@ -6803,7 +6865,7 @@ BOOL class_conformsToProtocol(Class cls, Protocol *proto_gen) memcpy(newlist, oldlist, oldsize); free(oldlist); } else { - newlist = (ivar_list_t *)calloc(sizeof(ivar_list_t), 1); + newlist = (ivar_list_t *)calloc(ivar_list_t::byteSize(sizeof(ivar_t), 1), 1); newlist->entsizeAndFlags = (uint32_t)sizeof(ivar_t); } @@ -6897,11 +6959,11 @@ BOOL class_addProtocol(Class cls, Protocol *protocol_gen) ASSERT(cls->isRealized()); property_list_t *proplist = (property_list_t *) - malloc(sizeof(*proplist)); + malloc(property_list_t::byteSize(sizeof(property_t), 1)); proplist->count = 1; - proplist->entsizeAndFlags = sizeof(proplist->first); - proplist->first.name = strdupIfMutable(name); - proplist->first.attributes = copyPropertyAttributeString(attrs, count); + proplist->entsizeAndFlags = sizeof(property_t); + proplist->begin()->name = strdupIfMutable(name); + proplist->begin()->attributes = copyPropertyAttributeString(attrs, count); rwe->properties.attachLists(&proplist, 1); @@ -7053,7 +7115,7 @@ bool includeClassHandler __attribute__((unused))) if (orig_rwe) { auto rwe = rw->extAllocIfNeeded(); rwe->version = orig_rwe->version; - rwe->methods = orig_rwe->methods.duplicate(); + orig_rwe->methods.duplicateInto(rwe->methods); // fixme dies when categories are added to the base rwe->properties = orig_rwe->properties; @@ -7390,7 +7452,7 @@ static void free_class(Class cls) if (rwe) { for (auto& meth : rwe->methods) { - try_free(meth.types); + try_free(meth.types()); } rwe->methods.tryFree(); } diff --git a/runtime/objc-sel-old.mm b/runtime/objc-sel-old.mm index 2a3a242..02fc2b5 100644 --- a/runtime/objc-sel-old.mm +++ b/runtime/objc-sel-old.mm @@ -33,11 +33,6 @@ #include "objc-private.h" #include "objc-sel-set.h" -#if SUPPORT_PREOPT -#include -static const objc_selopt_t *builtins = NULL; -#endif - __BEGIN_DECLS static size_t SelrefCount = 0; @@ -55,10 +50,6 @@ static SEL _objc_search_builtins(const char *key) if (!key) return (SEL)0; if ('\0' == *key) return (SEL)_objc_empty_selector; -#if SUPPORT_PREOPT - if (builtins) return (SEL)builtins->get(key); -#endif - return (SEL)0; } @@ -151,10 +142,6 @@ void sel_init(size_t selrefCount) // save this value for later SelrefCount = selrefCount; -#if SUPPORT_PREOPT - builtins = preoptimizedSelectors(); -#endif - // Register selectors used by libobjc mutex_locker_t lock(selLock); diff --git a/runtime/objc-sel.mm b/runtime/objc-sel.mm index 27ee356..da4c228 100644 --- a/runtime/objc-sel.mm +++ b/runtime/objc-sel.mm @@ -27,11 +27,6 @@ #include "objc-cache.h" #include "DenseMapExtras.h" -#if SUPPORT_PREOPT -static const objc_selopt_t *builtins = NULL; -static bool useDyldSelectorLookup = false; -#endif - static objc::ExplicitInitDenseSet namedSelectors; static SEL search_builtins(const char *key); @@ -44,32 +39,13 @@ void sel_init(size_t selrefCount) { #if SUPPORT_PREOPT - // If dyld finds a known shared cache selector, then it must be also looking - // in the shared cache table. - if (_dyld_get_objc_selector("retain") != nil) - useDyldSelectorLookup = true; - else - builtins = preoptimizedSelectors(); - - if (PrintPreopt && useDyldSelectorLookup) { + if (PrintPreopt) { _objc_inform("PREOPTIMIZATION: using dyld selector opt"); } - - if (PrintPreopt && builtins) { - uint32_t occupied = builtins->occupied; - uint32_t capacity = builtins->capacity; - - _objc_inform("PREOPTIMIZATION: using selopt at %p", builtins); - _objc_inform("PREOPTIMIZATION: %u selectors", occupied); - _objc_inform("PREOPTIMIZATION: %u/%u (%u%%) hash table occupancy", - occupied, capacity, - (unsigned)(occupied/(double)capacity*100)); - } - namedSelectors.init(useDyldSelectorLookup ? 0 : (unsigned)selrefCount); -#else - namedSelectors.init((unsigned)selrefCount); #endif + namedSelectors.init((unsigned)selrefCount); + // Register selectors used by libobjc mutex_locker_t lock(selLock); @@ -110,17 +86,8 @@ BOOL sel_isMapped(SEL sel) static SEL search_builtins(const char *name) { #if SUPPORT_PREOPT - if (builtins) { - SEL result = 0; - if ((result = (SEL)builtins->get(name))) - return result; - - if ((result = (SEL)_dyld_get_objc_selector(name))) - return result; - } else if (useDyldSelectorLookup) { - if (SEL result = (SEL)_dyld_get_objc_selector(name)) - return result; - } + if (SEL result = (SEL)_dyld_get_objc_selector(name)) + return result; #endif return nil; } diff --git a/runtime/objc.h b/runtime/objc.h index 6a73568..6b974a3 100644 --- a/runtime/objc.h +++ b/runtime/objc.h @@ -180,8 +180,7 @@ OBJC_EXPORT const char * _Nonnull object_getClassName(id _Nullable obj) * @note In a garbage-collected environment, the memory is scanned conservatively. */ OBJC_EXPORT void * _Nullable object_getIndexedIvars(id _Nullable obj) - OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0) - OBJC_ARC_UNAVAILABLE; + OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0); /** * Identifies a selector as being valid or invalid. diff --git a/test/gdb.m b/test/gdb.m index e2c8b7d..22e0334 100644 --- a/test/gdb.m +++ b/test/gdb.m @@ -23,9 +23,31 @@ int main() [TestRoot class]; // Now class should be realized - result = (__bridge Class)(NXMapGet(gdb_objc_realized_classes, "TestRoot")); + if (!testdyld3()) { + // In dyld3 mode, the class will be in the launch closure and not in our table. + result = (__bridge Class)(NXMapGet(gdb_objc_realized_classes, "TestRoot")); + testassert(result); + testassert(result == [TestRoot class]); + } + + Class dynamic = objc_allocateClassPair([TestRoot class], "Dynamic", 0); + objc_registerClassPair(dynamic); + result = (__bridge Class)(NXMapGet(gdb_objc_realized_classes, "Dynamic")); testassert(result); - testassert(result == [TestRoot class]); + testassert(result == dynamic); + + Class *realizedClasses = objc_copyRealizedClassList(NULL); + bool foundTestRoot = false; + bool foundDynamic = false; + for (Class *cursor = realizedClasses; *cursor; cursor++) { + if (*cursor == [TestRoot class]) + foundTestRoot = true; + if (*cursor == dynamic) + foundDynamic = true; + } + free(realizedClasses); + testassert(foundTestRoot); + testassert(foundDynamic); result = (__bridge Class)(NXMapGet(gdb_objc_realized_classes, "DoesNotExist")); testassert(!result); diff --git a/test/libraryPath.c b/test/libraryPath.c new file mode 100644 index 0000000..40cf7bd --- /dev/null +++ b/test/libraryPath.c @@ -0,0 +1,44 @@ +// TEST_CFLAGS -lobjc + +#include "test.h" +#include + +// We use DYLD_LIBRARY_PATH to run the tests against a particular copy of +// libobjc. If this fails somehow (path is wrong, codesigning prevents loading, +// etc.) then the typical result is a silent failure and we end up testing +// /usr/lib/libobjc.A.dylib instead. This test detects when DYLD_LIBRARY_PATH is +// set but libobjc isn't loaded from it. +int main() { + char *dyldLibraryPath = getenv("DYLD_LIBRARY_PATH"); + testprintf("DYLD_LIBRARY_PATH is %s\n", dyldLibraryPath); + if (dyldLibraryPath != NULL && strlen(dyldLibraryPath) > 0) { + int foundMatch = 0; + + dyldLibraryPath = strdup(dyldLibraryPath); + + Dl_info info; + int success = dladdr((void *)objc_msgSend, &info); + testassert(success); + + testprintf("libobjc is located at %s\n", info.dli_fname); + + char *cursor = dyldLibraryPath; + char *path; + while ((path = strsep(&cursor, ":"))) { + char *resolved = realpath(path, NULL); + testprintf("Resolved %s to %s\n", path, resolved); + testprintf("Comparing %s and %s\n", resolved, info.dli_fname); + int comparison = strncmp(resolved, info.dli_fname, strlen(resolved)); + free(resolved); + if (comparison == 0) { + testprintf("Found a match!\n"); + foundMatch = 1; + break; + } + } + + testprintf("Finished searching, foundMatch=%d\n", foundMatch); + testassert(foundMatch); + } + succeed(__FILE__); +} diff --git a/test/load-noobjc.m b/test/load-noobjc.m index 4dd9f86..c2be1a0 100644 --- a/test/load-noobjc.m +++ b/test/load-noobjc.m @@ -1,4 +1,8 @@ /* +dyld3 calls the load callback with its own internal lock held, which causes +this test to deadlock. Disable the test in dyld3 mode. If +rdar://problem/53769512 is fixed then remove this. +TEST_CONFIG DYLD=2 TEST_BUILD $C{COMPILE} $DIR/load-noobjc.m -o load-noobjc.exe $C{COMPILE} $DIR/load-noobjc2.m -o libload-noobjc2.dylib -bundle -bundle_loader load-noobjc.exe diff --git a/test/methodListSmall.h b/test/methodListSmall.h new file mode 100644 index 0000000..c6f32e2 --- /dev/null +++ b/test/methodListSmall.h @@ -0,0 +1,226 @@ +#include "test.h" + +struct ObjCClass { + struct ObjCClass *isa; + struct ObjCClass *superclass; + void *cachePtr; + uintptr_t zero; + struct ObjCClass_ro *data; +}; + +struct ObjCClass_ro { + uint32_t flags; + uint32_t instanceStart; + uint32_t instanceSize; +#ifdef __LP64__ + uint32_t reserved; +#endif + + const uint8_t * ivarLayout; + + const char * name; + struct ObjCMethodList * baseMethodList; + struct protocol_list_t * baseProtocols; + const struct ivar_list_t * ivars; + + const uint8_t * weakIvarLayout; + struct property_list_t *baseProperties; +}; + +struct ObjCMethod { + char *name; + char *type; + IMP imp; +}; + +struct ObjCMethodList { + uint32_t sizeAndFlags; + uint32_t count; + struct ObjCMethod methods[]; +}; + +struct ObjCMethodSmall { + int32_t nameOffset; + int32_t typeOffset; + int32_t impOffset; +}; + +struct ObjCMethodListSmall { + uint32_t sizeAndFlags; + uint32_t count; + struct ObjCMethodSmall methods[]; +}; + + +extern struct ObjCClass OBJC_METACLASS_$_NSObject; +extern struct ObjCClass OBJC_CLASS_$_NSObject; + + +struct ObjCClass_ro FooMetaclass_ro = { + .flags = 1, + .instanceStart = 40, + .instanceSize = 40, + .name = "Foo", +}; + +struct ObjCClass FooMetaclass = { + .isa = &OBJC_METACLASS_$_NSObject, + .superclass = &OBJC_METACLASS_$_NSObject, + .cachePtr = &_objc_empty_cache, + .data = &FooMetaclass_ro, +}; + + +int ranMyMethod1; +extern "C" void myMethod1(id self __unused, SEL _cmd) { + testprintf("myMethod1\n"); + testassert(_cmd == @selector(myMethod1)); + ranMyMethod1 = 1; +} + +int ranMyMethod2; +extern "C" void myMethod2(id self __unused, SEL _cmd) { + testprintf("myMethod2\n"); + testassert(_cmd == @selector(myMethod2)); + ranMyMethod2 = 1; +} + +int ranMyMethod3; +extern "C" void myMethod3(id self __unused, SEL _cmd) { + testprintf("myMethod3\n"); + testassert(_cmd == @selector(myMethod3)); + ranMyMethod3 = 1; +} + +int ranMyReplacedMethod1; +extern "C" void myReplacedMethod1(id self __unused, SEL _cmd) { + testprintf("myReplacedMethod1\n"); + testassert(_cmd == @selector(myMethod1)); + ranMyReplacedMethod1 = 1; +} + +int ranMyReplacedMethod2; +extern "C" void myReplacedMethod2(id self __unused, SEL _cmd) { + testprintf("myReplacedMethod2\n"); + testassert(_cmd == @selector(myMethod2)); + ranMyReplacedMethod2 = 1; +} + +struct BigStruct { + uintptr_t a, b, c, d, e, f, g; +}; + +int ranMyMethodStret; +extern "C" BigStruct myMethodStret(id self __unused, SEL _cmd) { + testprintf("myMethodStret\n"); + testassert(_cmd == @selector(myMethodStret)); + ranMyMethodStret = 1; + BigStruct ret = {}; + return ret; +} + +int ranMyReplacedMethodStret; +extern "C" BigStruct myReplacedMethodStret(id self __unused, SEL _cmd) { + testprintf("myReplacedMethodStret\n"); + testassert(_cmd == @selector(myMethodStret)); + ranMyReplacedMethodStret = 1; + BigStruct ret = {}; + return ret; +} + +extern struct ObjCMethodList Foo_methodlistSmall; + +asm(R"ASM( +.section __TEXT,__cstring +_MyMethod1Name: + .asciz "myMethod1" +_MyMethod2Name: + .asciz "myMethod2" +_MyMethod3Name: + .asciz "myMethod3" +_BoringMethodType: + .asciz "v16@0:8" +_MyMethodStretName: + .asciz "myMethodStret" +_StretType: + .asciz "{BigStruct=QQQQQQQ}16@0:8" +)ASM"); + +#if __LP64__ +asm(R"ASM( +.section __DATA,__objc_selrefs,literal_pointers,no_dead_strip +_MyMethod1NameRef: + .quad _MyMethod1Name +_MyMethod2NameRef: + .quad _MyMethod2Name +_MyMethod3NameRef: + .quad _MyMethod3Name +_MyMethodStretNameRef: + .quad _MyMethodStretName +)ASM"); +#else +asm(R"ASM( +.section __DATA,__objc_selrefs,literal_pointers,no_dead_strip +_MyMethod1NameRef: + .long _MyMethod1Name +_MyMethod2NameRef: + .long _MyMethod2Name +_MyMethod3NameRef: + .long _MyMethod3Name +_MyMethodStretNameRef: + .long _MyMethodStretName +)ASM"); +#endif + +#if MUTABLE_METHOD_LIST +asm(".section __DATA,__objc_methlist\n"); +#else +asm(".section __TEXT,__objc_methlist\n"); +#endif + +asm(R"ASM( + .p2align 2 +_Foo_methodlistSmall: + .long 12 | 0x80000000 + .long 4 + + .long _MyMethod1NameRef - . + .long _BoringMethodType - . + .long _myMethod1 - . + + .long _MyMethod2NameRef - . + .long _BoringMethodType - . + .long _myMethod2 - . + + .long _MyMethod3NameRef - . + .long _BoringMethodType - . + .long _myMethod3 - . + + .long _MyMethodStretNameRef - . + .long _StretType - . + .long _myMethodStret - . +)ASM"); + +struct ObjCClass_ro Foo_ro = { + .instanceStart = 8, + .instanceSize = 8, + .name = "Foo", + .baseMethodList = &Foo_methodlistSmall, +}; + +struct ObjCClass FooClass = { + .isa = &FooMetaclass, + .superclass = &OBJC_CLASS_$_NSObject, + .cachePtr = &_objc_empty_cache, + .data = &Foo_ro, +}; + + +@interface Foo: NSObject + +- (void)myMethod1; +- (void)myMethod2; +- (void)myMethod3; +- (BigStruct)myMethodStret; + +@end diff --git a/test/methodListSmall.mm b/test/methodListSmall.mm new file mode 100644 index 0000000..c10f29d --- /dev/null +++ b/test/methodListSmall.mm @@ -0,0 +1,89 @@ +// TEST_CFLAGS -std=c++11 + +#include "methodListSmall.h" + +void testClass(Class c) { + id foo = [c new]; + [foo myMethod1]; + testassert(ranMyMethod1); + [foo myMethod2]; + testassert(ranMyMethod2); + [foo myMethod3]; + testassert(ranMyMethod3); + + Method m1 = class_getInstanceMethod(c, @selector(myMethod1)); + testassert(m1); + testassert(method_getName(m1) == @selector(myMethod1)); + testassert(strcmp(method_getTypeEncoding(m1), "v16@0:8") == 0); + testassert(method_getImplementation(m1) == (IMP)myMethod1); + + method_setImplementation(m1, (IMP)myReplacedMethod1); + testassert(method_getImplementation(m1) == (IMP)myReplacedMethod1); + [foo myMethod1]; + testassert(ranMyReplacedMethod1); + + Method m2 = class_getInstanceMethod(c, @selector(myMethod2)); + auto method_invoke_cast = (void (*)(id, Method))method_invoke; + + ranMyMethod2 = 0; + method_invoke_cast(foo, m2); + testassert(ranMyMethod2); + + method_setImplementation(m2, (IMP)myReplacedMethod2); + method_invoke_cast(foo, m2); + testassert(ranMyReplacedMethod2); + + Method mstret = class_getInstanceMethod(c, @selector(myMethodStret)); +#if __arm64__ + // No _stret variant on ARM64. We'll test struct return through + // method_invoke anyway just to be thorough. + auto method_invoke_stret_cast = (BigStruct (*)(id, Method))method_invoke; +#else + auto method_invoke_stret_cast = (BigStruct (*)(id, Method))method_invoke_stret; +#endif + + [foo myMethodStret]; + testassert(ranMyMethodStret); + + ranMyMethodStret = 0; + method_invoke_stret_cast(foo, mstret); + testassert(ranMyMethodStret); + + method_setImplementation(mstret, (IMP)myReplacedMethodStret); + [foo myMethodStret]; + testassert(ranMyReplacedMethodStret); + + ranMyReplacedMethodStret = 0; + method_invoke_stret_cast(foo, mstret); + testassert(ranMyReplacedMethodStret); + + auto *desc1 = method_getDescription(m1); + testassert(desc1->name == @selector(myMethod1)); + testassert(desc1->types == method_getTypeEncoding(m1)); + + auto *desc2 = method_getDescription(m2); + testassert(desc2->name == @selector(myMethod2)); + testassert(desc2->types == method_getTypeEncoding(m2)); + + auto *descstret = method_getDescription(mstret); + testassert(descstret->name == @selector(myMethodStret)); + testassert(descstret->types == method_getTypeEncoding(mstret)); +} + +int main() { + Class fooClass = (__bridge Class)&FooClass; + + // Make sure this class can be duplicated and works as expected. + // Duplicate it before testClass mucks around with the methods. + // Need to realize fooClass before duplicating it, hence the + // class message. + Class dupedClass = objc_duplicateClass([fooClass class], "FooDup", 0); + + testprintf("Testing class.\n"); + testClass(fooClass); + + testprintf("Testing duplicate class.\n"); + testClass(dupedClass); + + succeed(__FILE__); +} diff --git a/test/methodListSmallMutableMemory.mm b/test/methodListSmallMutableMemory.mm new file mode 100644 index 0000000..9250fea --- /dev/null +++ b/test/methodListSmallMutableMemory.mm @@ -0,0 +1,18 @@ +/* +TEST_CFLAGS -std=c++11 +TEST_CRASHES +TEST_RUN_OUTPUT +objc\[\d+\]: CLASS: class 'Foo' 0x[0-9a-fA-F]+ small method list 0x[0-9a-fA-F]+ is not in immutable memory +objc\[\d+\]: HALTED +END +*/ + +#define MUTABLE_METHOD_LIST 1 + +#include "methodListSmall.h" + +int main() { + Class fooClass = (__bridge Class)&FooClass; + [fooClass new]; + fail("Should have crashed"); +} diff --git a/test/nonpointerisa.m b/test/nonpointerisa.m index dbe222c..d7b007f 100644 --- a/test/nonpointerisa.m +++ b/test/nonpointerisa.m @@ -114,10 +114,6 @@ void check_nonpointer(id obj, Class cls) } -@interface OS_object -+(id)alloc; -@end - @interface Fake_OS_object : NSObject { int refcnt; int xref_cnt; @@ -138,7 +134,7 @@ +(void)initialize { } @end -@interface Sub_OS_object : OS_object @end +@interface Sub_OS_object : NSObject @end @implementation Sub_OS_object @end @@ -147,6 +143,9 @@ @implementation Sub_OS_object int main() { + Class OS_object = objc_getClass("OS_object"); + class_setSuperclass([Sub_OS_object class], OS_object); + uintptr_t isa; #if SUPPORT_PACKED_ISA @@ -193,7 +192,6 @@ int main() objc_setAssociatedObject(index_o, assoc, assoc, OBJC_ASSOCIATION_ASSIGN); testassert(__builtin_popcountl(isa ^ ISA(index_o)) == 1); - testprintf("Isa without index\n"); id raw_o = [OS_object alloc]; check_raw_pointer(raw_o, [OS_object class]); diff --git a/test/supported-inline-refcnt.m b/test/supported-inline-refcnt.m new file mode 100644 index 0000000..bd32ba9 --- /dev/null +++ b/test/supported-inline-refcnt.m @@ -0,0 +1,85 @@ +// TEST_CONFIG MEM=mrc +// TEST_CFLAGS -framework CoreFoundation -Weverything + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Weverything" +#include "test.h" +#pragma clang diagnostic pop +#include +#include +#include + + +// Some warnings just aren't feasible to work around. We'll disable them instead. +#pragma clang diagnostic ignored "-Watomic-implicit-seq-cst" +#pragma clang diagnostic ignored "-Wdirect-ivar-access" +#pragma clang diagnostic ignored "-Wold-style-cast" + +static int deallocCount; +@interface Refcnt: NSObject @end +@implementation Refcnt { + int _rc; +} + +_OBJC_SUPPORTED_INLINE_REFCNT(_rc) + +- (void)dealloc { + deallocCount++; + [super dealloc]; +} + +@end + +@interface MainRefcnt: NSObject @end +@implementation MainRefcnt { + int _rc; +} + +_OBJC_SUPPORTED_INLINE_REFCNT_WITH_DEALLOC2MAIN(_rc) + +- (void)dealloc { + testassert(pthread_main_np()); + deallocCount++; + [super dealloc]; +} + +@end + +int main() +{ + Refcnt *obj = [Refcnt new]; + [obj retain]; + [obj retain]; + [obj retain]; + [obj release]; + [obj release]; + [obj release]; + [obj release]; + testassert(deallocCount == 1); + + MainRefcnt *obj2 = [MainRefcnt new]; + [obj2 retain]; + [obj2 retain]; + [obj2 retain]; + + dispatch_group_t group = dispatch_group_create(); + dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{ + [obj2 release]; + }); + dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{ + [obj2 release]; + }); + dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{ + [obj2 release]; + }); + dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{ + [obj2 release]; + }); + + dispatch_group_notify(group, dispatch_get_main_queue(), ^{ + testassert(deallocCount == 2); + succeed(__FILE__); + }); + + CFRunLoopRun(); +} diff --git a/test/test.h b/test/test.h index f4332a1..4ae8137 100644 --- a/test/test.h +++ b/test/test.h @@ -143,6 +143,19 @@ static inline void testwarn(const char *msg, ...) static inline void testnoop() { } +// Are we running in dyld3 mode? +// Note: checks by looking for the DYLD_USE_CLOSURES environment variable. +// This is is always set by our test script, but this won't give the right +// answer when being run manually unless that variable is set. +static inline bool testdyld3(void) { + static int dyld = 0; + if (dyld == 0) { + const char *useClosures = getenv("DYLD_USE_CLOSURES"); + dyld = useClosures && useClosures[0] == '1' ? 3 : 2; + } + return dyld == 3; +} + // Prevent deprecation warnings from some runtime functions. static inline void test_objc_flush_caches(Class cls) diff --git a/test/test.pl b/test/test.pl index 0d0886b..46c3d03 100755 --- a/test/test.pl +++ b/test/test.pl @@ -42,6 +42,8 @@ RUN=0|1 (run the tests?) VERBOSE=0|1|2 (0=quieter 1=print commands executed 2=full test output) BATS=0|1 (build for and/or run in BATS?) + BUILD_SHARED_CACHE=0|1 (build a dyld shared cache with the root and test against that) + DYLD=2|3 (test in dyld 2 or dyld 3 mode) examples: @@ -230,7 +232,22 @@ sub make { next if $cmd =~ /^\s*$/; $cmd .= " 2>&1"; print "$cmd\n" if $VERBOSE; - $output .= `$cmd`; + eval { + local $SIG{ALRM} = sub { die "alarm\n" }; + # Timeout after 600 seconds so a deadlocked test doesn't wedge the + # entire test suite. Increase to an hour for B&I builds. + if (exists $ENV{"RC_XBS"}) { + alarm 3600; + } else { + alarm 600; + } + $output .= `$cmd`; + alarm 0; + }; + if ($@) { + die unless $@ eq "alarm\n"; + $output .= "\nTIMED OUT"; + } last if $?; } print "$output\n" if $VERBOSE; @@ -966,6 +983,16 @@ sub run_simple { $env .= " OBJC_DEBUG_DONT_CRASH=YES"; } + if ($C{DYLD} eq "2") { + $env .= " DYLD_USE_CLOSURES=0"; + } + elsif ($C{DYLD} eq "3") { + $env .= " DYLD_USE_CLOSURES=1"; + } + else { + die "unknown DYLD setting $C{DYLD}"; + } + my $output; if ($C{ARCH} =~ /^arm/ && `uname -p` !~ /^arm/) { @@ -1060,6 +1087,26 @@ sub dirContainsAllTestLibs { return 1; } +sub findIncludeDir { + my ($root, $includePath) = @_; + + foreach my $candidate ("$root/../SDKContentRoot/$includePath", "$root/$includePath") { + my $found = -e $candidate; + my $foundstr = ($found ? "found" : "didn't find"); + print "note: $foundstr $includePath at $candidate\n" if $VERBOSE; + return $candidate if $found; + } + + die "Unable to find $includePath in $root.\n"; +} + +sub buildSharedCache { + my $Cref = shift; + my %C = %$Cref; + + make("update_dyld_shared_cache -verbose -cache_dir $BUILDDIR -overlay $C{TESTLIBDIR}/../.."); +} + sub make_one_config { my $configref = shift; my $root = shift; @@ -1315,9 +1362,8 @@ sub make_one_config { my $library_path = $C{TESTLIBDIR}; $cflags .= " -L$library_path"; - # fixme Root vs SDKContentRoot - $C{TESTINCLUDEDIR} = "$root/../SDKContentRoot/usr/include"; - $C{TESTLOCALINCLUDEDIR} = "$root/../SDKContentRoot/usr/local/include"; + $C{TESTINCLUDEDIR} = findIncludeDir($root, "usr/include"); + $C{TESTLOCALINCLUDEDIR} = findIncludeDir($root, "usr/local/include"); $cflags .= " -isystem '$C{TESTINCLUDEDIR}'"; $cflags .= " -isystem '$C{TESTLOCALINCLUDEDIR}'"; } @@ -1634,6 +1680,10 @@ sub getint { $args{MEM} = getargs("MEM", "mrc,arc"); $args{LANGUAGE} = [ map { lc($_) } @{getargs("LANGUAGE", "c,objective-c,c++,objective-c++")} ]; +$args{BUILD_SHARED_CACHE} = getargs("BUILD_SHARED_CACHE", 0); + +$args{DYLD} = getargs("DYLD", "2,3"); + $args{CC} = getargs("CC", "clang"); $HOST = getarg("HOST", "iphone"); From 063e3630eb0e9f5574da7b7e09be1dc1fbce5b29 Mon Sep 17 00:00:00 2001 From: Apple Open Source Date: Tue, 15 Jun 2021 22:42:30 +0200 Subject: [PATCH 12/12] Version 818.2 --- markgc.cpp | 8 + objc.xcodeproj/project.pbxproj | 58 +- objc4.plist | 11 + objcdt/objcdt.1 | 1 - objcdt/objcdt.mm | 2 + runtime/Messengers.subproj/objc-msg-arm.s | 2 +- runtime/Messengers.subproj/objc-msg-arm64.s | 458 +++++--- .../objc-msg-simulator-x86_64.s | 48 +- runtime/Messengers.subproj/objc-msg-x86_64.s | 48 +- runtime/NSObject-internal.h | 10 + runtime/NSObject.mm | 321 +++++- runtime/Protocol.mm | 2 +- runtime/arm64-asm.h | 37 +- runtime/dummy-library-mac-i386.c | 356 +++++++ runtime/isa.h | 55 +- runtime/objc-abi.h | 13 +- runtime/objc-api.h | 6 + runtime/objc-block-trampolines.mm | 49 +- runtime/objc-blocktramps-i386.s | 8 +- runtime/objc-blocktramps-x86_64.s | 74 +- runtime/objc-cache-old.mm | 4 - runtime/objc-cache.h | 23 - runtime/objc-cache.mm | 652 ++++++++---- runtime/objc-class-old.mm | 13 +- runtime/objc-class.mm | 48 +- runtime/objc-config.h | 96 +- runtime/objc-env.h | 5 + runtime/objc-exception.mm | 2 +- runtime/objc-file.h | 10 +- runtime/objc-file.mm | 6 + runtime/objc-gdb.h | 10 + runtime/objc-initialize.mm | 8 +- runtime/objc-internal.h | 217 +++- runtime/objc-lockdebug.h | 36 +- runtime/objc-lockdebug.mm | 8 + runtime/objc-object.h | 554 +++++++--- runtime/objc-opt.mm | 2 +- runtime/objc-os.h | 104 +- runtime/objc-os.mm | 16 +- runtime/objc-private.h | 100 +- runtime/objc-ptrauth.h | 22 +- runtime/objc-references.h | 2 +- runtime/objc-references.mm | 43 +- runtime/objc-runtime-new.h | 550 ++++++++-- runtime/objc-runtime-new.mm | 985 +++++++++++++----- runtime/objc-runtime.mm | 56 +- runtime/objc-sel-set.mm | 2 +- runtime/objc-sel-table.s | 5 + runtime/objc-sel.mm | 12 +- runtime/objc-weak.h | 8 +- runtime/objc-weak.mm | 51 +- runtime/objc.h | 2 +- runtime/runtime.h | 71 +- test/association.m | 104 ++ test/badPoolCompat-ios-tvos.m | 14 - test/badPoolCompat-ios.m | 18 + test/badPoolCompat-macos.m | 8 +- test/badPoolCompat-tvos.m | 18 + test/badPoolCompat-watchos.m | 6 +- test/badSuperclass.m | 2 +- test/bigrc.m | 41 +- test/bool.c | 6 +- test/cacheflush-constant.m | 44 + test/category.m | 22 +- test/consolidatePoolPointers.m | 142 +++ test/customrr-nsobject.m | 20 +- test/customrr.m | 53 +- test/evil-class-def.m | 82 +- test/exchangeImp.m | 107 ++ test/fakeRealizedClass.m | 74 ++ test/fakeRealizedClass2.m | 74 ++ test/forward.m | 2 +- test/gc-main.m | 10 - test/gc.c | 1 - test/gc.m | 8 - test/gcenforcer-app-aso.m | 12 - test/gcenforcer-app-gc.m | 14 - test/gcenforcer-app-gcaso.m | 14 - test/gcenforcer-app-gcaso2.m | 14 - test/gcenforcer-app-gconly.m | 14 - test/gcenforcer-app-nogc.m | 12 - test/gcenforcer-app-noobjc.m | 12 - test/gcenforcer-dylib-nogc.m | 11 - test/gcenforcer-dylib-noobjc.m | 9 - test/gcenforcer-dylib-requiresgc.m | 22 - test/gcenforcer-dylib-supportsgc.m | 9 - test/gcenforcer-preflight.m | 88 -- test/gcfiles/evil1 | Bin 441 -> 0 bytes test/gcfiles/i386-aso | Bin 12624 -> 0 bytes test/gcfiles/i386-aso--x86_64-aso | Bin 29060 -> 0 bytes test/gcfiles/i386-broken | Bin 12608 -> 0 bytes test/gcfiles/i386-broken--x86_64-gc | Bin 29056 -> 0 bytes test/gcfiles/i386-broken--x86_64-nogc | Bin 29056 -> 0 bytes test/gcfiles/i386-gc | Bin 12608 -> 0 bytes test/gcfiles/i386-gc--x86_64-broken | Bin 29056 -> 0 bytes test/gcfiles/i386-gc--x86_64-gc | Bin 29056 -> 0 bytes test/gcfiles/i386-gc--x86_64-nogc | Bin 29056 -> 0 bytes test/gcfiles/i386-gcaso | Bin 12716 -> 0 bytes test/gcfiles/i386-gcaso2 | Bin 12644 -> 0 bytes test/gcfiles/i386-gconly | Bin 12608 -> 0 bytes test/gcfiles/i386-nogc | Bin 12608 -> 0 bytes test/gcfiles/i386-nogc--x86_64-broken | Bin 29056 -> 0 bytes test/gcfiles/i386-nogc--x86_64-gc | Bin 29056 -> 0 bytes test/gcfiles/i386-nogc--x86_64-nogc | Bin 29056 -> 0 bytes test/gcfiles/i386-noobjc | Bin 4228 -> 0 bytes test/gcfiles/libnogc.dylib | Bin 74696 -> 0 bytes test/gcfiles/libnoobjc.dylib | Bin 41640 -> 0 bytes test/gcfiles/librequiresgc.dylib | Bin 74696 -> 0 bytes test/gcfiles/librequiresgc.fake.dylib | Bin 74696 -> 0 bytes test/gcfiles/libsupportsgc.dylib | Bin 74696 -> 0 bytes test/gcfiles/x86_64-aso | Bin 8580 -> 0 bytes test/gcfiles/x86_64-broken | Bin 8576 -> 0 bytes test/gcfiles/x86_64-gc | Bin 8576 -> 0 bytes test/gcfiles/x86_64-gcaso | Bin 8920 -> 0 bytes test/gcfiles/x86_64-gcaso2 | Bin 8640 -> 0 bytes test/gcfiles/x86_64-gconly | Bin 8576 -> 0 bytes test/gcfiles/x86_64-nogc | Bin 8576 -> 0 bytes test/gcfiles/x86_64-noobjc | Bin 4248 -> 0 bytes test/isaValidation.m | 6 +- test/ivarSlide.m | 26 +- test/lazyClassName.m | 136 +++ test/libraryPath.c | 17 +- test/methodCacheLeaks.m | 7 +- test/methodListSmall.h | 18 +- test/methodListSmall.mm | 6 + test/methodListSmallMutableMemory.mm | 18 - test/nonpointerisa.m | 49 +- test/preopt-caches.entitlements | 12 + test/preopt-caches.mm | 380 +++++++ test/protocolSmall.m | 91 ++ test/readClassPair.m | 8 +- test/rr-sidetable.m | 2 +- test/runtime.m | 7 + test/setAssociatedObjectHook.m | 45 +- test/swift-class-def.m | 18 +- test/swiftMetadataInitializerRealloc.m | 3 + test/taggedPointers.m | 20 + test/taggedPointersTagObfuscationDisabled.m | 8 +- test/test-defines.h | 1 + test/test.h | 58 +- test/test.pl | 221 +++- test/unload.m | 16 +- test/weakReferenceHook.m | 49 + 143 files changed, 5716 insertions(+), 1883 deletions(-) create mode 100644 objc4.plist create mode 100644 runtime/dummy-library-mac-i386.c delete mode 100644 runtime/objc-cache.h delete mode 100644 test/badPoolCompat-ios-tvos.m create mode 100644 test/badPoolCompat-ios.m create mode 100644 test/badPoolCompat-tvos.m create mode 100644 test/cacheflush-constant.m create mode 100644 test/consolidatePoolPointers.m create mode 100644 test/fakeRealizedClass.m create mode 100644 test/fakeRealizedClass2.m delete mode 100644 test/gc-main.m delete mode 100644 test/gc.c delete mode 100644 test/gc.m delete mode 100644 test/gcenforcer-app-aso.m delete mode 100644 test/gcenforcer-app-gc.m delete mode 100644 test/gcenforcer-app-gcaso.m delete mode 100644 test/gcenforcer-app-gcaso2.m delete mode 100644 test/gcenforcer-app-gconly.m delete mode 100644 test/gcenforcer-app-nogc.m delete mode 100644 test/gcenforcer-app-noobjc.m delete mode 100644 test/gcenforcer-dylib-nogc.m delete mode 100644 test/gcenforcer-dylib-noobjc.m delete mode 100644 test/gcenforcer-dylib-requiresgc.m delete mode 100644 test/gcenforcer-dylib-supportsgc.m delete mode 100644 test/gcenforcer-preflight.m delete mode 100644 test/gcfiles/evil1 delete mode 100755 test/gcfiles/i386-aso delete mode 100755 test/gcfiles/i386-aso--x86_64-aso delete mode 100755 test/gcfiles/i386-broken delete mode 100755 test/gcfiles/i386-broken--x86_64-gc delete mode 100755 test/gcfiles/i386-broken--x86_64-nogc delete mode 100755 test/gcfiles/i386-gc delete mode 100755 test/gcfiles/i386-gc--x86_64-broken delete mode 100755 test/gcfiles/i386-gc--x86_64-gc delete mode 100755 test/gcfiles/i386-gc--x86_64-nogc delete mode 100755 test/gcfiles/i386-gcaso delete mode 100755 test/gcfiles/i386-gcaso2 delete mode 100755 test/gcfiles/i386-gconly delete mode 100755 test/gcfiles/i386-nogc delete mode 100755 test/gcfiles/i386-nogc--x86_64-broken delete mode 100755 test/gcfiles/i386-nogc--x86_64-gc delete mode 100755 test/gcfiles/i386-nogc--x86_64-nogc delete mode 100755 test/gcfiles/i386-noobjc delete mode 100755 test/gcfiles/libnogc.dylib delete mode 100755 test/gcfiles/libnoobjc.dylib delete mode 100755 test/gcfiles/librequiresgc.dylib delete mode 100755 test/gcfiles/librequiresgc.fake.dylib delete mode 100755 test/gcfiles/libsupportsgc.dylib delete mode 100755 test/gcfiles/x86_64-aso delete mode 100755 test/gcfiles/x86_64-broken delete mode 100755 test/gcfiles/x86_64-gc delete mode 100755 test/gcfiles/x86_64-gcaso delete mode 100755 test/gcfiles/x86_64-gcaso2 delete mode 100755 test/gcfiles/x86_64-gconly delete mode 100755 test/gcfiles/x86_64-nogc delete mode 100755 test/gcfiles/x86_64-noobjc create mode 100644 test/lazyClassName.m delete mode 100644 test/methodListSmallMutableMemory.mm create mode 100644 test/preopt-caches.entitlements create mode 100644 test/preopt-caches.mm create mode 100644 test/protocolSmall.m create mode 100644 test/test-defines.h create mode 100644 test/weakReferenceHook.m diff --git a/markgc.cpp b/markgc.cpp index 4543ad6..bed92dd 100644 --- a/markgc.cpp +++ b/markgc.cpp @@ -391,6 +391,14 @@ void dosect(uint8_t *start, macho_section

*sect) sect->set_sectname("__objc_init_func"); if (debug) printf("disabled __mod_init_func section\n"); } + if (segnameStartsWith(sect->segname(), "__TEXT") && + sectnameEquals(sect->sectname(), "__init_offsets")) + { + // section type 0 is S_REGULAR + sect->set_flags(sect->flags() & ~SECTION_TYPE); + sect->set_sectname("__objc_init_offs"); + if (debug) printf("disabled __mod_init_func section\n"); + } if (segnameStartsWith(sect->segname(), "__DATA") && sectnameEquals(sect->sectname(), "__mod_term_func")) { diff --git a/objc.xcodeproj/project.pbxproj b/objc.xcodeproj/project.pbxproj index 5587470..9f3248f 100644 --- a/objc.xcodeproj/project.pbxproj +++ b/objc.xcodeproj/project.pbxproj @@ -62,7 +62,6 @@ 6EF877E22325D93200963DBB /* Symbolication.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6EF877E12325D93200963DBB /* Symbolication.framework */; }; 6EF877E52325FAC400963DBB /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6EF877E42325FAC400963DBB /* Foundation.framework */; }; 6EF877E82326184000963DBB /* json.mm in Sources */ = {isa = PBXBuildFile; fileRef = 6EF877E72326184000963DBB /* json.mm */; }; - 6EF877E923261D3E00963DBB /* objc-cache.mm in Sources */ = {isa = PBXBuildFile; fileRef = 838485CB0D6D68A200CEA253 /* objc-cache.mm */; }; 6EF877EC232635A700963DBB /* objcdt.1 in Install Manpages */ = {isa = PBXBuildFile; fileRef = 6EF877EA232633CC00963DBB /* objcdt.1 */; }; 7213C36321FA7C730090A271 /* NSObject-internal.h in Headers */ = {isa = PBXBuildFile; fileRef = 7213C36221FA7C730090A271 /* NSObject-internal.h */; settings = {ATTRIBUTES = (Private, ); }; }; 7593EC58202248E50046AB96 /* objc-object.h in Headers */ = {isa = PBXBuildFile; fileRef = 7593EC57202248DF0046AB96 /* objc-object.h */; }; @@ -149,7 +148,9 @@ 9672F7EE14D5F488007CEC96 /* NSObject.mm in Sources */ = {isa = PBXBuildFile; fileRef = 9672F7ED14D5F488007CEC96 /* NSObject.mm */; }; C22F5208230EF38B001BFE14 /* objc-ptrauth.h in Headers */ = {isa = PBXBuildFile; fileRef = C22F5207230EF38B001BFE14 /* objc-ptrauth.h */; }; C2E6D3FC2225DCF00059DFAA /* DenseMapExtras.h in Headers */ = {isa = PBXBuildFile; fileRef = C2E6D3FB2225DCF00059DFAA /* DenseMapExtras.h */; }; + C2EB731D23D8A38A0040672B /* dummy-library-mac-i386.c in Sources */ = {isa = PBXBuildFile; fileRef = C2EB731C23D8A38A0040672B /* dummy-library-mac-i386.c */; }; E8923DA5116AB2820071B552 /* objc-block-trampolines.mm in Sources */ = {isa = PBXBuildFile; fileRef = E8923DA0116AB2820071B552 /* objc-block-trampolines.mm */; }; + E934A9F123E996D00088F26F /* objc4.plist in CopyFiles */ = {isa = PBXBuildFile; fileRef = E934A9EF23E9967D0088F26F /* objc4.plist */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; }; F9BCC71B205C68E800DD9AFC /* objc-blocktramps-arm64.s in Sources */ = {isa = PBXBuildFile; fileRef = 8379996D13CBAF6F007C2B5F /* objc-blocktramps-arm64.s */; }; /* End PBXBuildFile section */ @@ -189,6 +190,16 @@ name = "Install Manpages"; runOnlyForDeploymentPostprocessing = 1; }; + E934A9F023E996CC0088F26F /* CopyFiles */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 8; + dstPath = /System/Library/FeatureFlags/Domain; + dstSubfolderSpec = 0; + files = ( + E934A9F123E996D00088F26F /* objc4.plist in CopyFiles */, + ); + runOnlyForDeploymentPostprocessing = 1; + }; /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ @@ -303,12 +314,17 @@ 9672F7ED14D5F488007CEC96 /* NSObject.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = NSObject.mm; path = runtime/NSObject.mm; sourceTree = ""; }; BC8B5D1212D3D48100C78A5B /* libauto.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libauto.dylib; path = /usr/lib/libauto.dylib; sourceTree = ""; }; C217B55222DE556D004369BA /* objc-env.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "objc-env.h"; path = "runtime/objc-env.h"; sourceTree = ""; }; + C2296C682457336C003FAE61 /* objc-bp-assist.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "objc-bp-assist.h"; path = "runtime/objc-bp-assist.h"; sourceTree = ""; }; C22F5207230EF38B001BFE14 /* objc-ptrauth.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "objc-ptrauth.h"; path = "runtime/objc-ptrauth.h"; sourceTree = ""; }; C2E6D3FB2225DCF00059DFAA /* DenseMapExtras.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = DenseMapExtras.h; path = runtime/DenseMapExtras.h; sourceTree = ""; }; + C2EB731C23D8A38A0040672B /* dummy-library-mac-i386.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = "dummy-library-mac-i386.c"; path = "runtime/dummy-library-mac-i386.c"; sourceTree = ""; }; D2AAC0630554660B00DB518D /* libobjc.A.dylib */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.dylib"; includeInIndex = 0; path = libobjc.A.dylib; sourceTree = BUILT_PRODUCTS_DIR; }; E8923D9C116AB2820071B552 /* objc-blocktramps-i386.s */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.asm; name = "objc-blocktramps-i386.s"; path = "runtime/objc-blocktramps-i386.s"; sourceTree = ""; }; E8923D9D116AB2820071B552 /* objc-blocktramps-x86_64.s */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.asm; name = "objc-blocktramps-x86_64.s"; path = "runtime/objc-blocktramps-x86_64.s"; sourceTree = ""; }; E8923DA0116AB2820071B552 /* objc-block-trampolines.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = "objc-block-trampolines.mm"; path = "runtime/objc-block-trampolines.mm"; sourceTree = ""; }; + E934A9EF23E9967D0088F26F /* objc4.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = objc4.plist; sourceTree = ""; }; + E97047552497CC5300781D29 /* check_preopt_caches.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = check_preopt_caches.entitlements; sourceTree = ""; }; + E9AD465924925261002AF1DB /* check_preopt_caches.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = check_preopt_caches.mm; sourceTree = ""; }; F9BCC727205C68E800DD9AFC /* libobjc-trampolines.dylib */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.dylib"; includeInIndex = 0; path = "libobjc-trampolines.dylib"; sourceTree = BUILT_PRODUCTS_DIR; }; /* End PBXFileReference section */ @@ -352,6 +368,7 @@ 08FB7795FE84155DC02AAC07 /* Source */, 838485B20D6D67F900CEA253 /* Other */, 6EF877D82325D62600963DBB /* objcdt */, + E9AD465824925261002AF1DB /* check-preopt-caches */, 1AB674ADFE9D54B511CA2CBB /* Products */, F9BCC72A205C6A1600DD9AFC /* Frameworks */, ); @@ -361,6 +378,7 @@ 08FB7795FE84155DC02AAC07 /* Source */ = { isa = PBXGroup; children = ( + C2EB731C23D8A38A0040672B /* dummy-library-mac-i386.c */, 838485B80D6D687300CEA253 /* hashtable2.mm */, 838485BC0D6D687300CEA253 /* maptable.mm */, 9672F7ED14D5F488007CEC96 /* NSObject.mm */, @@ -439,6 +457,7 @@ 838485B40D6D683300CEA253 /* APPLE_LICENSE */, 838485B50D6D683300CEA253 /* ReleaseNotes.rtf */, 83CE671D1E6E76B60095A33E /* interposable.txt */, + E934A9EF23E9967D0088F26F /* objc4.plist */, 838485B30D6D682B00CEA253 /* libobjc.order */, ); name = Other; @@ -514,6 +533,7 @@ 83D9269721225A7400299F69 /* arm64-asm.h */, 83D92695212254CF00299F69 /* isa.h */, 838485CF0D6D68A200CEA253 /* objc-config.h */, + C2296C682457336C003FAE61 /* objc-bp-assist.h */, C217B55222DE556D004369BA /* objc-env.h */, 83BE02E50FCCB24D00661494 /* objc-file-old.h */, 83BE02E60FCCB24D00661494 /* objc-file.h */, @@ -535,6 +555,15 @@ name = "Project Headers"; sourceTree = ""; }; + E9AD465824925261002AF1DB /* check-preopt-caches */ = { + isa = PBXGroup; + children = ( + E97047552497CC5300781D29 /* check_preopt_caches.entitlements */, + E9AD465924925261002AF1DB /* check_preopt_caches.mm */, + ); + path = "check-preopt-caches"; + sourceTree = ""; + }; F9BCC72A205C6A1600DD9AFC /* Frameworks */ = { isa = PBXGroup; children = ( @@ -644,6 +673,7 @@ D289988505E68E00004EDB86 /* Frameworks */, 830F2AB60D739AB600392440 /* Run Script (markgc) */, 830F2AFA0D73BC5800392440 /* Run Script (symlink) */, + E934A9F023E996CC0088F26F /* CopyFiles */, ); buildRules = ( ); @@ -678,7 +708,6 @@ 08FB7793FE84155DC02AAC07 /* Project object */ = { isa = PBXProject; attributes = { - BuildIndependentTargetsInParallel = NO; LastUpgradeCheck = 0440; TargetAttributes = { 6EF877D62325D62600963DBB = { @@ -777,7 +806,6 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 6EF877E923261D3E00963DBB /* objc-cache.mm in Sources */, 6EF877E82326184000963DBB /* json.mm in Sources */, 6EF877DA2325D62600963DBB /* objcdt.mm in Sources */, 6EF877DE2325D79000963DBB /* objc-probes.d in Sources */, @@ -824,6 +852,7 @@ 83B1A8BE0FF1AC0D0019EA5B /* objc-msg-simulator-i386.s in Sources */, 83EB007B121C9EC200B92C16 /* objc-sel-table.s in Sources */, 39ABD72412F0B61800D1054C /* objc-weak.mm in Sources */, + C2EB731D23D8A38A0040672B /* dummy-library-mac-i386.c in Sources */, 83D49E4F13C7C84F0057F1DD /* objc-msg-arm64.s in Sources */, 9672F7EE14D5F488007CEC96 /* NSObject.mm in Sources */, 83725F4A14CA5BFA0014370E /* objc-opt.mm in Sources */, @@ -875,6 +904,8 @@ COPY_PHASE_STRIP = NO; DEPLOYMENT_LOCATION = YES; DYLIB_CURRENT_VERSION = 228; + EXCLUDED_SOURCE_FILE_NAMES = "dummy-library-mac-i386.c"; + "EXCLUDED_SOURCE_FILE_NAMES[sdk=macosx*][arch=i386]" = "*"; EXECUTABLE_PREFIX = lib; GCC_CW_ASM_SYNTAX = NO; GCC_OPTIMIZATION_LEVEL = 0; @@ -886,6 +917,7 @@ "$(CONFIGURATION_BUILD_DIR)/usr/local/include/**", /System/Library/Frameworks/System.framework/PrivateHeaders, ); + "INCLUDED_SOURCE_FILE_NAMES[sdk=macosx*][arch=i386]" = "dummy-library-mac-i386.c"; INSTALL_PATH = /usr/lib; IS_ZIPPERED = YES; LLVM_LTO = NO; @@ -911,6 +943,10 @@ "-interposable_list", "-Xlinker", interposable.txt, + "-Xlinker", + "-headerpad", + "-Xlinker", + 0x100, ); "OTHER_LDFLAGS[sdk=iphonesimulator*][arch=*]" = ( "-lc++abi", @@ -934,7 +970,9 @@ "-interposable_list", "-Xlinker", interposable.txt, + "-loah", ); + "OTHER_LDFLAGS[sdk=macosx*][arch=i386]" = "-nodefaultlibs"; OTHER_TAPI_FLAGS = "-exclude-public-header $(DSTROOT)/usr/include/objc/ObjectiveC.apinotes -exclude-public-header $(DSTROOT)/usr/include/objc/module.modulemap -Xparser -Wno-deprecated-declarations -Xparser -Wno-unavailable-declarations -Xparser -D_OBJC_PRIVATE_H_=1 -DOBJC_DECLARE_SYMBOLS=1"; PRIVATE_HEADERS_FOLDER_PATH = /usr/local/include/objc; PRODUCT_NAME = objc.A; @@ -953,6 +991,8 @@ "COPY_HEADERS_UNIFDEF_FLAGS[sdk=macosx*]" = "-DBUILD_FOR_OSX"; DEPLOYMENT_LOCATION = YES; DYLIB_CURRENT_VERSION = 228; + EXCLUDED_SOURCE_FILE_NAMES = "dummy-library-mac-i386.c"; + "EXCLUDED_SOURCE_FILE_NAMES[sdk=macosx*][arch=i386]" = "*"; EXECUTABLE_PREFIX = lib; GCC_CW_ASM_SYNTAX = NO; GCC_WARN_ABOUT_DEPRECATED_FUNCTIONS = NO; @@ -963,6 +1003,7 @@ "$(CONFIGURATION_BUILD_DIR)/usr/local/include/**", /System/Library/Frameworks/System.framework/PrivateHeaders, ); + "INCLUDED_SOURCE_FILE_NAMES[sdk=macosx*][arch=i386]" = "dummy-library-mac-i386.c"; INSTALL_PATH = /usr/lib; IS_ZIPPERED = YES; ORDER_FILE = "$(SDKROOT)/AppleInternal/OrderFiles/libobjc.order"; @@ -987,6 +1028,10 @@ "-interposable_list", "-Xlinker", interposable.txt, + "-Xlinker", + "-headerpad", + "-Xlinker", + 0x100, ); "OTHER_LDFLAGS[sdk=iphonesimulator*][arch=*]" = ( "-lc++abi", @@ -1010,7 +1055,9 @@ "-interposable_list", "-Xlinker", interposable.txt, + "-loah", ); + "OTHER_LDFLAGS[sdk=macosx*][arch=i386]" = "-nodefaultlibs"; OTHER_TAPI_FLAGS = "-exclude-public-header $(DSTROOT)/usr/include/objc/ObjectiveC.apinotes -exclude-public-header $(DSTROOT)/usr/include/objc/module.modulemap -Xparser -Wno-deprecated-declarations -Xparser -Wno-unavailable-declarations -Xparser -D_OBJC_PRIVATE_H_=1 -DOBJC_DECLARE_SYMBOLS=1"; PRIVATE_HEADERS_FOLDER_PATH = /usr/local/include/objc; PRODUCT_NAME = objc.A; @@ -1032,6 +1079,7 @@ CLANG_CXX_LIBRARY = "libc++"; CLANG_LINK_OBJC_RUNTIME = NO; CLANG_OBJC_RUNTIME = NO; + CODE_SIGN_IDENTITY = "-"; DEBUG_INFORMATION_FORMAT = dwarf; GCC_ENABLE_CPP_EXCEPTIONS = NO; GCC_ENABLE_CPP_RTTI = NO; @@ -1078,6 +1126,7 @@ CLANG_CXX_LIBRARY = "libc++"; CLANG_LINK_OBJC_RUNTIME = NO; CLANG_OBJC_RUNTIME = NO; + CODE_SIGN_IDENTITY = "-"; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; GCC_ENABLE_CPP_EXCEPTIONS = NO; GCC_ENABLE_CPP_RTTI = NO; @@ -1124,6 +1173,7 @@ buildSettings = { CODE_SIGN_ENTITLEMENTS = "objcdt/objcdt-entitlements.plist"; CODE_SIGN_IDENTITY = "-"; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; GCC_PREPROCESSOR_DEFINITIONS = ( "__BUILDING_OBJCDT__=1", "$(inherited)", @@ -1232,6 +1282,7 @@ OTHER_LDFLAGS = ( "-Xlinker", "-not_for_dyld_shared_cache", + "-nodefaultlibs", ); PRIVATE_HEADERS_FOLDER_PATH = /usr/local/include/objc; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -1268,6 +1319,7 @@ OTHER_LDFLAGS = ( "-Xlinker", "-not_for_dyld_shared_cache", + "-nodefaultlibs", ); PRIVATE_HEADERS_FOLDER_PATH = /usr/local/include/objc; PRODUCT_NAME = "$(TARGET_NAME)"; diff --git a/objc4.plist b/objc4.plist new file mode 100644 index 0000000..157aea8 --- /dev/null +++ b/objc4.plist @@ -0,0 +1,11 @@ + + + + + preoptimizedCaches + + Enabled + + + + diff --git a/objcdt/objcdt.1 b/objcdt/objcdt.1 index 5522491..999a155 100644 --- a/objcdt/objcdt.1 +++ b/objcdt/objcdt.1 @@ -17,4 +17,3 @@ the Objective-C runtime state in live processes. Help can be obtained using .Nm .Ar help -.Ed diff --git a/objcdt/objcdt.mm b/objcdt/objcdt.mm index 81a5a49..eca5fa2 100644 --- a/objcdt/objcdt.mm +++ b/objcdt/objcdt.mm @@ -27,6 +27,8 @@ #include #include #include +#include +#include int main(int argc, const char *argv[]) { diff --git a/runtime/Messengers.subproj/objc-msg-arm.s b/runtime/Messengers.subproj/objc-msg-arm.s index 67317c7..4947209 100644 --- a/runtime/Messengers.subproj/objc-msg-arm.s +++ b/runtime/Messengers.subproj/objc-msg-arm.s @@ -711,7 +711,7 @@ LNilReceiver: mov r1, r2 // selector .endif mov r2, r9 // class to search - mov r3, #3 // LOOKUP_INITIALIZE | LOOKUP_INITIALIZE + mov r3, #3 // LOOKUP_INITIALIZE | LOOKUP_RESOLVER blx _lookUpImpOrForward mov r12, r0 // r12 = IMP diff --git a/runtime/Messengers.subproj/objc-msg-arm64.s b/runtime/Messengers.subproj/objc-msg-arm64.s index 595b03e..7794ad5 100755 --- a/runtime/Messengers.subproj/objc-msg-arm64.s +++ b/runtime/Messengers.subproj/objc-msg-arm64.s @@ -30,8 +30,19 @@ #include #include "isa.h" -#include "arm64-asm.h" #include "objc-config.h" +#include "arm64-asm.h" + +#if TARGET_OS_IPHONE && __LP64__ + .section __TEXT,__objc_methname,cstring_literals +l_MagicSelector: /* the shared cache builder knows about this value */ + .byte 0xf0, 0x9f, 0xa4, 0xaf, 0 + + .section __DATA,__objc_selrefs,literal_pointers,no_dead_strip + .p2align 3 +_MagicSelRef: + .quad l_MagicSelector +#endif .data @@ -57,7 +68,6 @@ _objc_restartableRanges: RestartableEntry _cache_getImp RestartableEntry _objc_msgSend - RestartableEntry _objc_msgSendSuper RestartableEntry _objc_msgSendSuper2 RestartableEntry _objc_msgLookup RestartableEntry _objc_msgLookupSuper2 @@ -81,13 +91,13 @@ _objc_restartableRanges: /******************************************************************** - * GetClassFromIsa_p16 src + * GetClassFromIsa_p16 src, needs_auth, auth_address * src is a raw isa field. Sets p16 to the corresponding class pointer. * The raw isa might be an indexed isa to be decoded, or a * packed isa that needs to be masked. * * On exit: - * $0 is unchanged + * src is unchanged * p16 is a class pointer * x10 is clobbered ********************************************************************/ @@ -99,11 +109,11 @@ _objc_indexed_classes: .fill ISA_INDEX_COUNT, PTRSIZE, 0 #endif -.macro GetClassFromIsa_p16 /* src */ +.macro GetClassFromIsa_p16 src, needs_auth, auth_address /* note: auth_address is not required if !needs_auth */ #if SUPPORT_INDEXED_ISA // Indexed isa - mov p16, $0 // optimistically set dst = src + mov p16, \src // optimistically set dst = src tbz p16, #ISA_INDEX_IS_NPI_BIT, 1f // done if not non-pointer isa // isa in p16 is indexed adrp x10, _objc_indexed_classes@PAGE @@ -113,12 +123,15 @@ _objc_indexed_classes: 1: #elif __LP64__ +.if \needs_auth == 0 // _cache_getImp takes an authed class already + mov p16, \src +.else // 64-bit packed isa - and p16, $0, #ISA_MASK - + ExtractISA p16, \src, \auth_address +.endif #else // 32-bit raw isa - mov p16, $0 + mov p16, \src #endif @@ -169,6 +182,9 @@ LExit$0: #define FrameWithNoSaves 0x04000000 // frame, no non-volatile saves +#define MSGSEND 100 +#define METHOD_INVOKE 101 + ////////////////////////////////////////////////////////////////////// // // SAVE_REGS @@ -177,7 +193,7 @@ LExit$0: // for a function call. ////////////////////////////////////////////////////////////////////// -.macro SAVE_REGS +.macro SAVE_REGS kind // push frame SignLR @@ -185,16 +201,23 @@ LExit$0: mov fp, sp // save parameter registers: x0..x8, q0..q7 - sub sp, sp, #(10*8 + 8*16) - stp q0, q1, [sp, #(0*16)] - stp q2, q3, [sp, #(2*16)] - stp q4, q5, [sp, #(4*16)] - stp q6, q7, [sp, #(6*16)] - stp x0, x1, [sp, #(8*16+0*8)] - stp x2, x3, [sp, #(8*16+2*8)] - stp x4, x5, [sp, #(8*16+4*8)] - stp x6, x7, [sp, #(8*16+6*8)] - str x8, [sp, #(8*16+8*8)] + sub sp, sp, #(10*8 + 8*16) + stp q0, q1, [sp, #(0*16)] + stp q2, q3, [sp, #(2*16)] + stp q4, q5, [sp, #(4*16)] + stp q6, q7, [sp, #(6*16)] + stp x0, x1, [sp, #(8*16+0*8)] + stp x2, x3, [sp, #(8*16+2*8)] + stp x4, x5, [sp, #(8*16+4*8)] + stp x6, x7, [sp, #(8*16+6*8)] +.if \kind == MSGSEND + stp x8, x15, [sp, #(8*16+8*8)] + mov x16, x15 // stashed by CacheLookup, restore to x16 +.elseif \kind == METHOD_INVOKE + str x8, [sp, #(8*16+8*8)] +.else +.abort Unknown kind. +.endif .endmacro @@ -207,17 +230,24 @@ LExit$0: // SAVE_REGS. ////////////////////////////////////////////////////////////////////// -.macro RESTORE_REGS - - ldp q0, q1, [sp, #(0*16)] - ldp q2, q3, [sp, #(2*16)] - ldp q4, q5, [sp, #(4*16)] - ldp q6, q7, [sp, #(6*16)] - ldp x0, x1, [sp, #(8*16+0*8)] - ldp x2, x3, [sp, #(8*16+2*8)] - ldp x4, x5, [sp, #(8*16+4*8)] - ldp x6, x7, [sp, #(8*16+6*8)] - ldr x8, [sp, #(8*16+8*8)] +.macro RESTORE_REGS kind + + ldp q0, q1, [sp, #(0*16)] + ldp q2, q3, [sp, #(2*16)] + ldp q4, q5, [sp, #(4*16)] + ldp q6, q7, [sp, #(6*16)] + ldp x0, x1, [sp, #(8*16+0*8)] + ldp x2, x3, [sp, #(8*16+2*8)] + ldp x4, x5, [sp, #(8*16+4*8)] + ldp x6, x7, [sp, #(8*16+6*8)] +.if \kind == MSGSEND + ldp x8, x16, [sp, #(8*16+8*8)] + orr x16, x16, #2 // for the sake of instrumentations, remember it was the slowpath +.elseif \kind == METHOD_INVOKE + ldr x8, [sp, #(8*16+8*8)] +.else +.abort Unknown kind. +.endif mov sp, fp ldp fp, lr, [sp], #16 @@ -228,7 +258,9 @@ LExit$0: /******************************************************************** * - * CacheLookup NORMAL|GETIMP|LOOKUP + * CacheLookup NORMAL|GETIMP|LOOKUP MissLabelDynamic MissLabelConstant + * + * MissLabelConstant is only used for the GETIMP variant. * * Locate the implementation for a selector in a class method cache. * @@ -242,11 +274,27 @@ LExit$0: * x16 = class to be searched * * Kills: - * x9,x10,x11,x12, x17 + * x9,x10,x11,x12,x13,x15,x17 + * + * Untouched: + * x14 * * On exit: (found) calls or returns IMP * with x16 = class, x17 = IMP + * In LOOKUP mode, the two low bits are set to 0x3 + * if we hit a constant cache (used in objc_trace) * (not found) jumps to LCacheMiss + * with x15 = class + * For constant caches in LOOKUP mode, the low bit + * of x16 is set to 0x1 to indicate we had to fallback. + * In addition, when LCacheMiss is __objc_msgSend_uncached or + * __objc_msgLookup_uncached, 0x2 will be set in x16 + * to remember we took the slowpath. + * So the two low bits of x16 on exit mean: + * 0: dynamic hit + * 1: fallback to the parent class, when there is a preoptimized cache + * 2: slowpath + * 3: preoptimized cache hit * ********************************************************************/ @@ -254,60 +302,37 @@ LExit$0: #define GETIMP 1 #define LOOKUP 2 -// CacheHit: x17 = cached IMP, x12 = address of cached IMP, x1 = SEL, x16 = isa +// CacheHit: x17 = cached IMP, x10 = address of buckets, x1 = SEL, x16 = isa .macro CacheHit .if $0 == NORMAL - TailCallCachedImp x17, x12, x1, x16 // authenticate and call imp + TailCallCachedImp x17, x10, x1, x16 // authenticate and call imp .elseif $0 == GETIMP mov p0, p17 cbz p0, 9f // don't ptrauth a nil imp - AuthAndResignAsIMP x0, x12, x1, x16 // authenticate imp and re-sign as IMP + AuthAndResignAsIMP x0, x10, x1, x16 // authenticate imp and re-sign as IMP 9: ret // return IMP .elseif $0 == LOOKUP // No nil check for ptrauth: the caller would crash anyway when they // jump to a nil IMP. We don't care if that jump also fails ptrauth. - AuthAndResignAsIMP x17, x12, x1, x16 // authenticate imp and re-sign as IMP + AuthAndResignAsIMP x17, x10, x1, x16 // authenticate imp and re-sign as IMP + cmp x16, x15 + cinc x16, x16, ne // x16 += 1 when x15 != x16 (for instrumentation ; fallback to the parent class) ret // return imp via x17 .else .abort oops .endif .endmacro -.macro CheckMiss - // miss if bucket->sel == 0 -.if $0 == GETIMP - cbz p9, LGetImpMiss -.elseif $0 == NORMAL - cbz p9, __objc_msgSend_uncached -.elseif $0 == LOOKUP - cbz p9, __objc_msgLookup_uncached -.else -.abort oops -.endif -.endmacro - -.macro JumpMiss -.if $0 == GETIMP - b LGetImpMiss -.elseif $0 == NORMAL - b __objc_msgSend_uncached -.elseif $0 == LOOKUP - b __objc_msgLookup_uncached -.else -.abort oops -.endif -.endmacro - -.macro CacheLookup +.macro CacheLookup Mode, Function, MissLabelDynamic, MissLabelConstant // // Restart protocol: // - // As soon as we're past the LLookupStart$1 label we may have loaded - // an invalid cache pointer or mask. + // As soon as we're past the LLookupStart\Function label we may have + // loaded an invalid cache pointer or mask. // // When task_restartable_ranges_synchronize() is called, - // (or when a signal hits us) before we're past LLookupEnd$1, - // then our PC will be reset to LLookupRecover$1 which forcefully + // (or when a signal hits us) before we're past LLookupEnd\Function, + // then our PC will be reset to LLookupRecover\Function which forcefully // jumps to the cache-miss codepath which have the following // requirements: // @@ -320,70 +345,158 @@ LExit$0: // - x16 contains the isa // - other registers are set as per calling conventions // -LLookupStart$1: + mov x15, x16 // stash the original isa +LLookupStart\Function: // p1 = SEL, p16 = isa - ldr p11, [x16, #CACHE] // p11 = mask|buckets - -#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16 +#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16_BIG_ADDRS + ldr p10, [x16, #CACHE] // p10 = mask|buckets + lsr p11, p10, #48 // p11 = mask + and p10, p10, #0xffffffffffff // p10 = buckets + and w12, w1, w11 // x12 = _cmd & mask +#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16 + ldr p11, [x16, #CACHE] // p11 = mask|buckets +#if CONFIG_USE_PREOPT_CACHES +#if __has_feature(ptrauth_calls) + tbnz p11, #0, LLookupPreopt\Function + and p10, p11, #0x0000ffffffffffff // p10 = buckets +#else + and p10, p11, #0x0000fffffffffffe // p10 = buckets + tbnz p11, #0, LLookupPreopt\Function +#endif + eor p12, p1, p1, LSR #7 + and p12, p12, p11, LSR #48 // x12 = (_cmd ^ (_cmd >> 7)) & mask +#else and p10, p11, #0x0000ffffffffffff // p10 = buckets and p12, p1, p11, LSR #48 // x12 = _cmd & mask +#endif // CONFIG_USE_PREOPT_CACHES #elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_LOW_4 + ldr p11, [x16, #CACHE] // p11 = mask|buckets and p10, p11, #~0xf // p10 = buckets and p11, p11, #0xf // p11 = maskShift mov p12, #0xffff - lsr p11, p12, p11 // p11 = mask = 0xffff >> p11 - and p12, p1, p11 // x12 = _cmd & mask + lsr p11, p12, p11 // p11 = mask = 0xffff >> p11 + and p12, p1, p11 // x12 = _cmd & mask #else #error Unsupported cache mask storage for ARM64. #endif + add p13, p10, p12, LSL #(1+PTRSHIFT) + // p13 = buckets + ((_cmd & mask) << (1+PTRSHIFT)) + + // do { +1: ldp p17, p9, [x13], #-BUCKET_SIZE // {imp, sel} = *bucket-- + cmp p9, p1 // if (sel != _cmd) { + b.ne 3f // scan more + // } else { +2: CacheHit \Mode // hit: call or return imp + // } +3: cbz p9, \MissLabelDynamic // if (sel == 0) goto Miss; + cmp p13, p10 // } while (bucket >= buckets) + b.hs 1b + + // wrap-around: + // p10 = first bucket + // p11 = mask (and maybe other bits on LP64) + // p12 = _cmd & mask + // + // A full cache can happen with CACHE_ALLOW_FULL_UTILIZATION. + // So stop when we circle back to the first probed bucket + // rather than when hitting the first bucket again. + // + // Note that we might probe the initial bucket twice + // when the first probed slot is the last entry. - add p12, p10, p12, LSL #(1+PTRSHIFT) - // p12 = buckets + ((_cmd & mask) << (1+PTRSHIFT)) - ldp p17, p9, [x12] // {imp, sel} = *bucket -1: cmp p9, p1 // if (bucket->sel != _cmd) - b.ne 2f // scan more - CacheHit $0 // call or return imp - -2: // not hit: p12 = not-hit bucket - CheckMiss $0 // miss if bucket->sel == 0 - cmp p12, p10 // wrap if bucket == buckets - b.eq 3f - ldp p17, p9, [x12, #-BUCKET_SIZE]! // {imp, sel} = *--bucket - b 1b // loop - -3: // wrap: p12 = first bucket, w11 = mask -#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16 - add p12, p12, p11, LSR #(48 - (1+PTRSHIFT)) - // p12 = buckets + (mask << 1+PTRSHIFT) +#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16_BIG_ADDRS + add p13, p10, w11, UXTW #(1+PTRSHIFT) + // p13 = buckets + (mask << 1+PTRSHIFT) +#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16 + add p13, p10, p11, LSR #(48 - (1+PTRSHIFT)) + // p13 = buckets + (mask << 1+PTRSHIFT) + // see comment about maskZeroBits #elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_LOW_4 - add p12, p12, p11, LSL #(1+PTRSHIFT) - // p12 = buckets + (mask << 1+PTRSHIFT) + add p13, p10, p11, LSL #(1+PTRSHIFT) + // p13 = buckets + (mask << 1+PTRSHIFT) #else #error Unsupported cache mask storage for ARM64. +#endif + add p12, p10, p12, LSL #(1+PTRSHIFT) + // p12 = first probed bucket + + // do { +4: ldp p17, p9, [x13], #-BUCKET_SIZE // {imp, sel} = *bucket-- + cmp p9, p1 // if (sel == _cmd) + b.eq 2b // goto hit + cmp p9, #0 // } while (sel != 0 && + ccmp p13, p12, #0, ne // bucket > first_probed) + b.hi 4b + +LLookupEnd\Function: +LLookupRecover\Function: + b \MissLabelDynamic + +#if CONFIG_USE_PREOPT_CACHES +#if CACHE_MASK_STORAGE != CACHE_MASK_STORAGE_HIGH_16 +#error config unsupported +#endif +LLookupPreopt\Function: +#if __has_feature(ptrauth_calls) + and p10, p11, #0x007ffffffffffffe // p10 = buckets + autdb x10, x16 // auth as early as possible #endif - // Clone scanning loop to miss instead of hang when cache is corrupt. - // The slow path may detect any corruption and halt later. + // x12 = (_cmd - first_shared_cache_sel) + adrp x9, _MagicSelRef@PAGE + ldr p9, [x9, _MagicSelRef@PAGEOFF] + sub p12, p1, p9 - ldp p17, p9, [x12] // {imp, sel} = *bucket -1: cmp p9, p1 // if (bucket->sel != _cmd) - b.ne 2f // scan more - CacheHit $0 // call or return imp - -2: // not hit: p12 = not-hit bucket - CheckMiss $0 // miss if bucket->sel == 0 - cmp p12, p10 // wrap if bucket == buckets - b.eq 3f - ldp p17, p9, [x12, #-BUCKET_SIZE]! // {imp, sel} = *--bucket - b 1b // loop - -LLookupEnd$1: -LLookupRecover$1: -3: // double wrap - JumpMiss $0 + // w9 = ((_cmd - first_shared_cache_sel) >> hash_shift & hash_mask) +#if __has_feature(ptrauth_calls) + // bits 63..60 of x11 are the number of bits in hash_mask + // bits 59..55 of x11 is hash_shift + + lsr x17, x11, #55 // w17 = (hash_shift, ...) + lsr w9, w12, w17 // >>= shift + + lsr x17, x11, #60 // w17 = mask_bits + mov x11, #0x7fff + lsr x11, x11, x17 // p11 = mask (0x7fff >> mask_bits) + and x9, x9, x11 // &= mask +#else + // bits 63..53 of x11 is hash_mask + // bits 52..48 of x11 is hash_shift + lsr x17, x11, #48 // w17 = (hash_shift, hash_mask) + lsr w9, w12, w17 // >>= shift + and x9, x9, x11, LSR #53 // &= mask +#endif + + ldr x17, [x10, x9, LSL #3] // x17 == sel_offs | (imp_offs << 32) + cmp x12, w17, uxtw + +.if \Mode == GETIMP + b.ne \MissLabelConstant // cache miss + sub x0, x16, x17, LSR #32 // imp = isa - imp_offs + SignAsImp x0 + ret +.else + b.ne 5f // cache miss + sub x17, x16, x17, LSR #32 // imp = isa - imp_offs +.if \Mode == NORMAL + br x17 +.elseif \Mode == LOOKUP + orr x16, x16, #3 // for instrumentation, note that we hit a constant cache + SignAsImp x17 + ret +.else +.abort unhandled mode \Mode +.endif + +5: ldursw x9, [x10, #-8] // offset -8 is the fallback offset + add x16, x16, x9 // compute the fallback isa + b LLookupStart\Function // lookup again with a new isa +.endif +#endif // CONFIG_USE_PREOPT_CACHES .endmacro @@ -402,12 +515,37 @@ LLookupRecover$1: #if SUPPORT_TAGGED_POINTERS .data .align 3 - .globl _objc_debug_taggedpointer_classes -_objc_debug_taggedpointer_classes: - .fill 16, 8, 0 .globl _objc_debug_taggedpointer_ext_classes _objc_debug_taggedpointer_ext_classes: .fill 256, 8, 0 + +// Dispatch for split tagged pointers take advantage of the fact that +// the extended tag classes array immediately precedes the standard +// tag array. The .alt_entry directive ensures that the two stay +// together. This is harmless when using non-split tagged pointers. + .globl _objc_debug_taggedpointer_classes + .alt_entry _objc_debug_taggedpointer_classes +_objc_debug_taggedpointer_classes: + .fill 16, 8, 0 + +// Look up the class for a tagged pointer in x0, placing it in x16. +.macro GetTaggedClass + + and x10, x0, #0x7 // x10 = small tag + asr x11, x0, #55 // x11 = large tag with 1s filling the top (because bit 63 is 1 on a tagged pointer) + cmp x10, #7 // tag == 7? + csel x12, x11, x10, eq // x12 = index in tagged pointer classes array, negative for extended tags. + // The extended tag array is placed immediately before the basic tag array + // so this looks into the right place either way. The sign extension done + // by the asr instruction produces the value extended_tag - 256, which produces + // the correct index in the extended tagged pointer classes array. + + // x16 = _objc_debug_taggedpointer_classes[x12] + adrp x10, _objc_debug_taggedpointer_classes@PAGE + add x10, x10, _objc_debug_taggedpointer_classes@PAGEOFF + ldr x16, [x10, x12, LSL #3] + +.endmacro #endif ENTRY _objc_msgSend @@ -420,30 +558,15 @@ _objc_debug_taggedpointer_ext_classes: b.eq LReturnZero #endif ldr p13, [x0] // p13 = isa - GetClassFromIsa_p16 p13 // p16 = class + GetClassFromIsa_p16 p13, 1, x0 // p16 = class LGetIsaDone: // calls imp or objc_msgSend_uncached - CacheLookup NORMAL, _objc_msgSend + CacheLookup NORMAL, _objc_msgSend, __objc_msgSend_uncached #if SUPPORT_TAGGED_POINTERS LNilOrTagged: b.eq LReturnZero // nil check - - // tagged - adrp x10, _objc_debug_taggedpointer_classes@PAGE - add x10, x10, _objc_debug_taggedpointer_classes@PAGEOFF - ubfx x11, x0, #60, #4 - ldr x16, [x10, x11, LSL #3] - adrp x10, _OBJC_CLASS_$___NSUnrecognizedTaggedPointer@PAGE - add x10, x10, _OBJC_CLASS_$___NSUnrecognizedTaggedPointer@PAGEOFF - cmp x10, x16 - b.ne LGetIsaDone - - // ext tagged - adrp x10, _objc_debug_taggedpointer_ext_classes@PAGE - add x10, x10, _objc_debug_taggedpointer_ext_classes@PAGEOFF - ubfx x11, x0, #52, #8 - ldr x16, [x10, x11, LSL #3] + GetTaggedClass b LGetIsaDone // SUPPORT_TAGGED_POINTERS #endif @@ -469,37 +592,22 @@ LReturnZero: b.eq LLookup_Nil #endif ldr p13, [x0] // p13 = isa - GetClassFromIsa_p16 p13 // p16 = class + GetClassFromIsa_p16 p13, 1, x0 // p16 = class LLookup_GetIsaDone: // returns imp - CacheLookup LOOKUP, _objc_msgLookup + CacheLookup LOOKUP, _objc_msgLookup, __objc_msgLookup_uncached #if SUPPORT_TAGGED_POINTERS LLookup_NilOrTagged: b.eq LLookup_Nil // nil check - - // tagged - adrp x10, _objc_debug_taggedpointer_classes@PAGE - add x10, x10, _objc_debug_taggedpointer_classes@PAGEOFF - ubfx x11, x0, #60, #4 - ldr x16, [x10, x11, LSL #3] - adrp x10, _OBJC_CLASS_$___NSUnrecognizedTaggedPointer@PAGE - add x10, x10, _OBJC_CLASS_$___NSUnrecognizedTaggedPointer@PAGEOFF - cmp x10, x16 - b.ne LLookup_GetIsaDone - -LLookup_ExtTag: - adrp x10, _objc_debug_taggedpointer_ext_classes@PAGE - add x10, x10, _objc_debug_taggedpointer_ext_classes@PAGEOFF - ubfx x11, x0, #52, #8 - ldr x16, [x10, x11, LSL #3] + GetTaggedClass b LLookup_GetIsaDone // SUPPORT_TAGGED_POINTERS #endif LLookup_Nil: - adrp x17, __objc_msgNil@PAGE - add x17, x17, __objc_msgNil@PAGEOFF + adr x17, __objc_msgNil + SignAsImp x17 ret END_ENTRY _objc_msgLookup @@ -522,8 +630,7 @@ LLookup_Nil: UNWIND _objc_msgSendSuper, NoFrame ldp p0, p16, [x0] // p0 = real receiver, p16 = class - // calls imp or objc_msgSend_uncached - CacheLookup NORMAL, _objc_msgSendSuper + b L_objc_msgSendSuper2_body END_ENTRY _objc_msgSendSuper @@ -532,9 +639,18 @@ LLookup_Nil: ENTRY _objc_msgSendSuper2 UNWIND _objc_msgSendSuper2, NoFrame +#if __has_feature(ptrauth_calls) + ldp x0, x17, [x0] // x0 = real receiver, x17 = class + add x17, x17, #SUPERCLASS // x17 = &class->superclass + ldr x16, [x17] // x16 = class->superclass + AuthISASuper x16, x17, ISA_SIGNING_DISCRIMINATOR_CLASS_SUPERCLASS +LMsgSendSuperResume: +#else ldp p0, p16, [x0] // p0 = real receiver, p16 = class ldr p16, [x16, #SUPERCLASS] // p16 = class->superclass - CacheLookup NORMAL, _objc_msgSendSuper2 +#endif +L_objc_msgSendSuper2_body: + CacheLookup NORMAL, _objc_msgSendSuper2, __objc_msgSend_uncached END_ENTRY _objc_msgSendSuper2 @@ -542,16 +658,24 @@ LLookup_Nil: ENTRY _objc_msgLookupSuper2 UNWIND _objc_msgLookupSuper2, NoFrame +#if __has_feature(ptrauth_calls) + ldp x0, x17, [x0] // x0 = real receiver, x17 = class + add x17, x17, #SUPERCLASS // x17 = &class->superclass + ldr x16, [x17] // x16 = class->superclass + AuthISASuper x16, x17, ISA_SIGNING_DISCRIMINATOR_CLASS_SUPERCLASS +LMsgLookupSuperResume: +#else ldp p0, p16, [x0] // p0 = real receiver, p16 = class ldr p16, [x16, #SUPERCLASS] // p16 = class->superclass - CacheLookup LOOKUP, _objc_msgLookupSuper2 +#endif + CacheLookup LOOKUP, _objc_msgLookupSuper2, __objc_msgLookup_uncached END_ENTRY _objc_msgLookupSuper2 .macro MethodTableLookup - SAVE_REGS + SAVE_REGS MSGSEND // lookUpImpOrForward(obj, sel, cls, LOOKUP_INITIALIZE | LOOKUP_RESOLVER) // receiver and selector already in x0 and x1 @@ -562,7 +686,7 @@ LLookup_Nil: // IMP in x0 mov x17, x0 - RESTORE_REGS + RESTORE_REGS MSGSEND .endmacro @@ -570,7 +694,7 @@ LLookup_Nil: UNWIND __objc_msgSend_uncached, FrameWithNoSaves // THIS IS NOT A CALLABLE C FUNCTION - // Out-of-band p16 is the class to search + // Out-of-band p15 is the class to search MethodTableLookup TailCallFunctionPointer x17 @@ -582,7 +706,7 @@ LLookup_Nil: UNWIND __objc_msgLookup_uncached, FrameWithNoSaves // THIS IS NOT A CALLABLE C FUNCTION - // Out-of-band p16 is the class to search + // Out-of-band p15 is the class to search MethodTableLookup ret @@ -592,13 +716,17 @@ LLookup_Nil: STATIC_ENTRY _cache_getImp - GetClassFromIsa_p16 p0 - CacheLookup GETIMP, _cache_getImp + GetClassFromIsa_p16 p0, 0 + CacheLookup GETIMP, _cache_getImp, LGetImpMissDynamic, LGetImpMissConstant -LGetImpMiss: +LGetImpMissDynamic: mov p0, #0 ret +LGetImpMissConstant: + mov p0, p2 + ret + END_ENTRY _cache_getImp @@ -657,7 +785,7 @@ LGetImpMiss: L_method_invoke_small: // Small methods require a call to handle swizzling. - SAVE_REGS + SAVE_REGS METHOD_INVOKE mov p0, p1 bl __method_getImplementationAndName // ARM64_32 packs both return values into x0, with SEL in the high bits and IMP in the low. @@ -666,7 +794,7 @@ L_method_invoke_small: #if __LP64__ mov x16, x1 #endif - RESTORE_REGS + RESTORE_REGS METHOD_INVOKE #if __LP64__ mov x1, x16 #else diff --git a/runtime/Messengers.subproj/objc-msg-simulator-x86_64.s b/runtime/Messengers.subproj/objc-msg-simulator-x86_64.s index 9186278..402b97d 100644 --- a/runtime/Messengers.subproj/objc-msg-simulator-x86_64.s +++ b/runtime/Messengers.subproj/objc-msg-simulator-x86_64.s @@ -22,7 +22,7 @@ */ #include -#if __x86_64__ && TARGET_OS_SIMULATOR && !TARGET_OS_IOSMAC +#if __x86_64__ && TARGET_OS_SIMULATOR && !TARGET_OS_MACCATALYST /******************************************************************** ******************************************************************** @@ -133,6 +133,10 @@ _objc_restartableRanges: #define GETIMP 101 #define LOOKUP 102 +#define MSGSEND 200 +#define METHOD_INVOKE 201 +#define METHOD_INVOKE_STRET 202 + /******************************************************************** * @@ -221,21 +225,28 @@ LExit$0: // for a function call. ////////////////////////////////////////////////////////////////////// -.macro SAVE_REGS +.macro SAVE_REGS kind +.if \kind != MSGSEND && \kind != METHOD_INVOKE && \kind != METHOD_INVOKE_STRET +.abort Unknown kind. +.endif push %rbp mov %rsp, %rbp - sub $$0x80+8, %rsp // +8 for alignment + sub $0x80, %rsp movdqa %xmm0, -0x80(%rbp) push %rax // might be xmm parameter count movdqa %xmm1, -0x70(%rbp) push %a1 movdqa %xmm2, -0x60(%rbp) +.if \kind == MSGSEND || \kind == METHOD_INVOKE_STRET push %a2 +.endif movdqa %xmm3, -0x50(%rbp) +.if \kind == MSGSEND || \kind == METHOD_INVOKE push %a3 +.endif movdqa %xmm4, -0x40(%rbp) push %a4 movdqa %xmm5, -0x30(%rbp) @@ -243,6 +254,9 @@ LExit$0: movdqa %xmm6, -0x20(%rbp) push %a6 movdqa %xmm7, -0x10(%rbp) +.if \kind == MSGSEND + push %r10 +.endif .endmacro @@ -255,8 +269,12 @@ LExit$0: // SAVE_REGS. ////////////////////////////////////////////////////////////////////// -.macro RESTORE_REGS +.macro RESTORE_REGS kind +.if \kind == MSGSEND + pop %r10 + orq $2, %r10 // for the sake of instrumentations, remember it was the slowpath +.endif movdqa -0x80(%rbp), %xmm0 pop %a6 movdqa -0x70(%rbp), %xmm1 @@ -264,9 +282,13 @@ LExit$0: movdqa -0x60(%rbp), %xmm2 pop %a4 movdqa -0x50(%rbp), %xmm3 +.if \kind == MSGSEND || \kind == METHOD_INVOKE pop %a3 +.endif movdqa -0x40(%rbp), %xmm4 +.if \kind == MSGSEND || \kind == METHOD_INVOKE_STRET pop %a2 +.endif movdqa -0x30(%rbp), %xmm5 pop %a1 movdqa -0x20(%rbp), %xmm6 @@ -412,7 +434,7 @@ LExit$0: .macro MethodTableLookup - SAVE_REGS + SAVE_REGS MSGSEND // lookUpImpOrForward(obj, sel, cls, LOOKUP_INITIALIZE | LOOKUP_RESOLVER) .if $0 == NORMAL @@ -429,7 +451,7 @@ LExit$0: // IMP is now in %rax movq %rax, %r11 - RESTORE_REGS + RESTORE_REGS MSGSEND .if $0 == NORMAL test %r11, %r11 // set ne for stret forwarding @@ -1145,13 +1167,12 @@ LCacheMiss: L_method_invoke_small: // Small methods require a call to handle swizzling. - SAVE_REGS + SAVE_REGS METHOD_INVOKE movq %a2, %a1 call __method_getImplementationAndName - movq %rdx, %r10 + movq %rdx, %a2 movq %rax, %r11 - RESTORE_REGS - movq %r10, %a2 + RESTORE_REGS METHOD_INVOKE jmp *%r11 END_ENTRY _method_invoke @@ -1170,13 +1191,12 @@ L_method_invoke_small: L_method_invoke_stret_small: // Small methods require a call to handle swizzling. - SAVE_REGS + SAVE_REGS METHOD_INVOKE_STRET movq %a3, %a1 call __method_getImplementationAndName - movq %rdx, %r10 + movq %rdx, %a3 movq %rax, %r11 - RESTORE_REGS - movq %r10, %a3 + RESTORE_REGS METHOD_INVOKE_STRET jmp *%r11 END_ENTRY _method_invoke_stret diff --git a/runtime/Messengers.subproj/objc-msg-x86_64.s b/runtime/Messengers.subproj/objc-msg-x86_64.s index d090995..0b8eff7 100644 --- a/runtime/Messengers.subproj/objc-msg-x86_64.s +++ b/runtime/Messengers.subproj/objc-msg-x86_64.s @@ -22,7 +22,7 @@ */ #include -#if __x86_64__ && !(TARGET_OS_SIMULATOR && !TARGET_OS_IOSMAC) +#if __x86_64__ && !(TARGET_OS_SIMULATOR && !TARGET_OS_MACCATALYST) #include "isa.h" @@ -139,6 +139,10 @@ _objc_restartableRanges: #define GETIMP 101 #define LOOKUP 102 +#define MSGSEND 200 +#define METHOD_INVOKE 201 +#define METHOD_INVOKE_STRET 202 + /******************************************************************** * @@ -227,21 +231,28 @@ LExit$0: // for a function call. ////////////////////////////////////////////////////////////////////// -.macro SAVE_REGS +.macro SAVE_REGS kind +.if \kind != MSGSEND && \kind != METHOD_INVOKE && \kind != METHOD_INVOKE_STRET +.abort Unknown kind. +.endif push %rbp mov %rsp, %rbp - sub $$0x80+8, %rsp // +8 for alignment + sub $0x80, %rsp movdqa %xmm0, -0x80(%rbp) push %rax // might be xmm parameter count movdqa %xmm1, -0x70(%rbp) push %a1 movdqa %xmm2, -0x60(%rbp) +.if \kind == MSGSEND || \kind == METHOD_INVOKE_STRET push %a2 +.endif movdqa %xmm3, -0x50(%rbp) +.if \kind == MSGSEND || \kind == METHOD_INVOKE push %a3 +.endif movdqa %xmm4, -0x40(%rbp) push %a4 movdqa %xmm5, -0x30(%rbp) @@ -249,6 +260,9 @@ LExit$0: movdqa %xmm6, -0x20(%rbp) push %a6 movdqa %xmm7, -0x10(%rbp) +.if \kind == MSGSEND + push %r10 +.endif .endmacro @@ -261,8 +275,12 @@ LExit$0: // SAVE_REGS. ////////////////////////////////////////////////////////////////////// -.macro RESTORE_REGS +.macro RESTORE_REGS kind +.if \kind == MSGSEND + pop %r10 + orq $2, %r10 // for the sake of instrumentations, remember it was the slowpath +.endif movdqa -0x80(%rbp), %xmm0 pop %a6 movdqa -0x70(%rbp), %xmm1 @@ -270,9 +288,13 @@ LExit$0: movdqa -0x60(%rbp), %xmm2 pop %a4 movdqa -0x50(%rbp), %xmm3 +.if \kind == MSGSEND || \kind == METHOD_INVOKE pop %a3 +.endif movdqa -0x40(%rbp), %xmm4 +.if \kind == MSGSEND || \kind == METHOD_INVOKE_STRET pop %a2 +.endif movdqa -0x30(%rbp), %xmm5 pop %a1 movdqa -0x20(%rbp), %xmm6 @@ -447,7 +469,7 @@ LLookupEnd$2: .macro MethodTableLookup - SAVE_REGS + SAVE_REGS MSGSEND // lookUpImpOrForward(obj, sel, cls, LOOKUP_INITIALIZE | LOOKUP_RESOLVER) .if $0 == NORMAL @@ -464,7 +486,7 @@ LLookupEnd$2: // IMP is now in %rax movq %rax, %r11 - RESTORE_REGS + RESTORE_REGS MSGSEND .if $0 == NORMAL test %r11, %r11 // set ne for nonstret forwarding @@ -1257,13 +1279,12 @@ LCacheMiss_objc_msgLookupSuper2_stret: L_method_invoke_small: // Small methods require a call to handle swizzling. - SAVE_REGS + SAVE_REGS METHOD_INVOKE movq %a2, %a1 call __method_getImplementationAndName - movq %rdx, %r10 + movq %rdx, %a2 movq %rax, %r11 - RESTORE_REGS - movq %r10, %a2 + RESTORE_REGS METHOD_INVOKE jmp *%r11 END_ENTRY _method_invoke @@ -1282,13 +1303,12 @@ L_method_invoke_small: L_method_invoke_stret_small: // Small methods require a call to handle swizzling. - SAVE_REGS + SAVE_REGS METHOD_INVOKE_STRET movq %a3, %a1 call __method_getImplementationAndName - movq %rdx, %r10 + movq %rdx, %a3 movq %rax, %r11 - RESTORE_REGS - movq %r10, %a3 + RESTORE_REGS METHOD_INVOKE_STRET jmp *%r11 END_ENTRY _method_invoke_stret diff --git a/runtime/NSObject-internal.h b/runtime/NSObject-internal.h index c23fbc2..978799a 100644 --- a/runtime/NSObject-internal.h +++ b/runtime/NSObject-internal.h @@ -123,6 +123,16 @@ struct magic_t { class AutoreleasePoolPage; struct AutoreleasePoolPageData { +#if SUPPORT_AUTORELEASEPOOL_DEDUP_PTRS + struct AutoreleasePoolEntry { + uintptr_t ptr: 48; + uintptr_t count: 16; + + static const uintptr_t maxCount = 65535; // 2^16 - 1 + }; + static_assert((AutoreleasePoolEntry){ .ptr = MACH_VM_MAX_ADDRESS }.ptr == MACH_VM_MAX_ADDRESS, "MACH_VM_MAX_ADDRESS doesn't fit into AutoreleasePoolEntry::ptr!"); +#endif + magic_t const magic; __unsafe_unretained id *next; pthread_t const thread; diff --git a/runtime/NSObject.mm b/runtime/NSObject.mm index f672f4c..6d2e14f 100644 --- a/runtime/NSObject.mm +++ b/runtime/NSObject.mm @@ -39,6 +39,12 @@ #include #include #include "NSObject-internal.h" +#include + +extern "C" { +#include +#include +} @interface NSInvocation - (SEL)selector; @@ -51,7 +57,13 @@ - (SEL)selector; OBJC_EXTERN const uint32_t objc_debug_autoreleasepoolpage_child_offset = __builtin_offsetof(AutoreleasePoolPageData, child); OBJC_EXTERN const uint32_t objc_debug_autoreleasepoolpage_depth_offset = __builtin_offsetof(AutoreleasePoolPageData, depth); OBJC_EXTERN const uint32_t objc_debug_autoreleasepoolpage_hiwat_offset = __builtin_offsetof(AutoreleasePoolPageData, hiwat); +OBJC_EXTERN const uint32_t objc_debug_autoreleasepoolpage_begin_offset = sizeof(AutoreleasePoolPageData); #if __OBJC2__ +#if SUPPORT_AUTORELEASEPOOL_DEDUP_PTRS +OBJC_EXTERN const uintptr_t objc_debug_autoreleasepoolpage_ptr_mask = (AutoreleasePoolPageData::AutoreleasePoolEntry){ .ptr = ~(uintptr_t)0 }.ptr; +#else +OBJC_EXTERN const uintptr_t objc_debug_autoreleasepoolpage_ptr_mask = ~(uintptr_t)0; +#endif OBJC_EXTERN const uint32_t objc_class_abi_version = OBJC_CLASS_ABI_VERSION_MAX; #endif @@ -79,8 +91,42 @@ void _objc_setBadAllocHandler(id(*newHandler)(Class)) } +static id _initializeSwiftRefcountingThenCallRetain(id objc); +static void _initializeSwiftRefcountingThenCallRelease(id objc); + +explicit_atomic swiftRetain{&_initializeSwiftRefcountingThenCallRetain}; +explicit_atomic swiftRelease{&_initializeSwiftRefcountingThenCallRelease}; + +static void _initializeSwiftRefcounting() { + void *const token = dlopen("/usr/lib/swift/libswiftCore.dylib", RTLD_LAZY | RTLD_LOCAL); + ASSERT(token); + swiftRetain.store((id(*)(id))dlsym(token, "swift_retain"), memory_order_relaxed); + ASSERT(swiftRetain.load(memory_order_relaxed)); + swiftRelease.store((void(*)(id))dlsym(token, "swift_release"), memory_order_relaxed); + ASSERT(swiftRelease.load(memory_order_relaxed)); + dlclose(token); +} + +static id _initializeSwiftRefcountingThenCallRetain(id objc) { + _initializeSwiftRefcounting(); + return swiftRetain.load(memory_order_relaxed)(objc); +} + +static void _initializeSwiftRefcountingThenCallRelease(id objc) { + _initializeSwiftRefcounting(); + swiftRelease.load(memory_order_relaxed)(objc); +} + +namespace objc { + extern int PageCountWarning; +} + namespace { +#if TARGET_OS_IPHONE && !TARGET_OS_SIMULATOR +uint32_t numFaults = 0; +#endif + // The order of these bits is important. #define SIDE_TABLE_WEAKLY_REFERENCED (1UL<<0) #define SIDE_TABLE_DEALLOCATING (1UL<<1) // MSB-ward of weak bit @@ -221,6 +267,23 @@ void SideTableLocksSucceedLocks(StripedMap& oldlocks) { } } +// Call out to the _setWeaklyReferenced method on obj, if implemented. +static void callSetWeaklyReferenced(id obj) { + if (!obj) + return; + + Class cls = obj->getIsa(); + + if (slowpath(cls->hasCustomRR() && !object_isClass(obj))) { + ASSERT(((objc_class *)cls)->isInitializing() || ((objc_class *)cls)->isInitialized()); + void (*setWeaklyReferenced)(id, SEL) = (void(*)(id, SEL)) + class_getMethodImplementation(cls, @selector(_setWeaklyReferenced)); + if ((IMP)setWeaklyReferenced != _objc_msgForward) { + (*setWeaklyReferenced)(obj, @selector(_setWeaklyReferenced)); + } + } +} + // // The -fobjc-arc flag causes the compiler to issue calls to objc_{retain/release/autorelease/retain_block} // @@ -269,7 +332,7 @@ BOOL objc_should_deallocate(id object) { DontCrashIfDeallocating = false, DoCrashIfDeallocating = true }; template + enum CrashIfDeallocating crashIfDeallocating> static id storeWeak(id *location, objc_object *newObj) { @@ -336,11 +399,11 @@ BOOL objc_should_deallocate(id object) { if (haveNew) { newObj = (objc_object *) weak_register_no_lock(&newTable->weak_table, (id)newObj, location, - crashIfDeallocating); + crashIfDeallocating ? CrashIfDeallocating : ReturnNilIfDeallocating); // weak_register_no_lock returns nil if weak store should be rejected // Set is-weakly-referenced bit in refcount table. - if (newObj && !newObj->isTaggedPointer()) { + if (!newObj->isTaggedPointerOrNil()) { newObj->setWeaklyReferenced_nolock(); } @@ -353,6 +416,12 @@ BOOL objc_should_deallocate(id object) { SideTable::unlockTwo(oldTable, newTable); + // This must be called without the locks held, as it can invoke + // arbitrary code. In particular, even if _setWeaklyReferenced + // is not implemented, resolveInstanceMethod: may be, and may + // call back into the weak reference machinery. + callSetWeaklyReferenced((id)newObj); + return (id)newObj; } @@ -474,8 +543,7 @@ BOOL objc_should_deallocate(id object) { retry: // fixme std::atomic this load obj = *location; - if (!obj) return nil; - if (obj->isTaggedPointer()) return obj; + if (obj->isTaggedPointerOrNil()) return obj; table = &SideTables()[obj]; @@ -499,9 +567,12 @@ BOOL objc_should_deallocate(id object) { else { // Slow case. We must check for +initialize and call it outside // the lock if necessary in order to avoid deadlocks. + // Use lookUpImpOrForward so we can avoid the assert in + // class_getInstanceMethod, since we intentionally make this + // callout with the lock held. if (cls->isInitialized() || _thisThreadIsInitializingClass(cls)) { BOOL (*tryRetain)(id, SEL) = (BOOL(*)(id, SEL)) - class_getMethodImplementation(cls, @selector(retainWeakReference)); + lookUpImpOrForwardTryCache(obj, @selector(retainWeakReference), cls); if ((IMP)tryRetain == _objc_msgForward) { result = nil; } @@ -572,9 +643,28 @@ BOOL objc_should_deallocate(id object) { void objc_moveWeak(id *dst, id *src) { - objc_copyWeak(dst, src); - objc_destroyWeak(src); + id obj; + SideTable *table; + +retry: + obj = *src; + if (obj == nil) { + *dst = nil; + return; + } + + table = &SideTables()[obj]; + table->lock(); + if (*src != obj) { + table->unlock(); + goto retry; + } + + weak_unregister_no_lock(&table->weak_table, obj, src); + weak_register_no_lock(&table->weak_table, obj, dst, DontCheckDeallocating); + *dst = obj; *src = nil; + table->unlock(); } @@ -611,6 +701,7 @@ BOOL objc_should_deallocate(id object) { static pthread_key_t const key = AUTORELEASE_POOL_KEY; static uint8_t const SCRIBBLE = 0xA3; // 0xA3A3A3A3 after releasing static size_t const COUNT = SIZE / sizeof(id); + static size_t const MAX_FAULTS = 2; // EMPTY_POOL_PLACEHOLDER is stored in TLS when exactly one pool is // pushed and it has never contained any objects. This saves memory @@ -643,13 +734,33 @@ inline void unprotect() { #endif } + void checkTooMuchAutorelease() + { +#if TARGET_OS_IPHONE && !TARGET_OS_SIMULATOR + bool objcModeNoFaults = DisableFaults || getpid() == 1 || + !os_variant_has_internal_diagnostics("com.apple.obj-c"); + if (!objcModeNoFaults) { + if (depth+1 >= (uint32_t)objc::PageCountWarning && numFaults < MAX_FAULTS) { //depth is 0 when first page is allocated + os_fault_with_payload(OS_REASON_LIBSYSTEM, + OS_REASON_LIBSYSTEM_CODE_FAULT, + NULL, 0, "Large Autorelease Pool", 0); + numFaults++; + } + } +#endif + } + AutoreleasePoolPage(AutoreleasePoolPage *newParent) : AutoreleasePoolPageData(begin(), objc_thread_self(), newParent, newParent ? 1+newParent->depth : 0, newParent ? newParent->hiwat : 0) - { + { + if (objc::PageCountWarning != -1) { + checkTooMuchAutorelease(); + } + if (parent) { parent->check(); ASSERT(!parent->child); @@ -744,8 +855,49 @@ bool lessThanHalfFull() { { ASSERT(!full()); unprotect(); - id *ret = next; // faster than `return next-1` because of aliasing + id *ret; + +#if SUPPORT_AUTORELEASEPOOL_DEDUP_PTRS + if (!DisableAutoreleaseCoalescing || !DisableAutoreleaseCoalescingLRU) { + if (!DisableAutoreleaseCoalescingLRU) { + if (!empty() && (obj != POOL_BOUNDARY)) { + AutoreleasePoolEntry *topEntry = (AutoreleasePoolEntry *)next - 1; + for (uintptr_t offset = 0; offset < 4; offset++) { + AutoreleasePoolEntry *offsetEntry = topEntry - offset; + if (offsetEntry <= (AutoreleasePoolEntry*)begin() || *(id *)offsetEntry == POOL_BOUNDARY) { + break; + } + if (offsetEntry->ptr == (uintptr_t)obj && offsetEntry->count < AutoreleasePoolEntry::maxCount) { + if (offset > 0) { + AutoreleasePoolEntry found = *offsetEntry; + memmove(offsetEntry, offsetEntry + 1, offset * sizeof(*offsetEntry)); + *topEntry = found; + } + topEntry->count++; + ret = (id *)topEntry; // need to reset ret + goto done; + } + } + } + } else { + if (!empty() && (obj != POOL_BOUNDARY)) { + AutoreleasePoolEntry *prevEntry = (AutoreleasePoolEntry *)next - 1; + if (prevEntry->ptr == (uintptr_t)obj && prevEntry->count < AutoreleasePoolEntry::maxCount) { + prevEntry->count++; + ret = (id *)prevEntry; // need to reset ret + goto done; + } + } + } + } +#endif + ret = next; // faster than `return next-1` because of aliasing *next++ = obj; +#if SUPPORT_AUTORELEASEPOOL_DEDUP_PTRS + // Make sure obj fits in the bits available for it + ASSERT(((AutoreleasePoolEntry *)ret)->ptr == (uintptr_t)obj); +#endif + done: protect(); return ret; } @@ -772,12 +924,28 @@ void releaseUntil(id *stop) } page->unprotect(); +#if SUPPORT_AUTORELEASEPOOL_DEDUP_PTRS + AutoreleasePoolEntry* entry = (AutoreleasePoolEntry*) --page->next; + + // create an obj with the zeroed out top byte and release that + id obj = (id)entry->ptr; + int count = (int)entry->count; // grab these before memset +#else id obj = *--page->next; +#endif memset((void*)page->next, SCRIBBLE, sizeof(*page->next)); page->protect(); if (obj != POOL_BOUNDARY) { +#if SUPPORT_AUTORELEASEPOOL_DEDUP_PTRS + // release count+1 times since it is count of the additional + // autoreleases beyond the first one + for (int i = 0; i < count + 1; i++) { + objc_release(obj); + } +#else objc_release(obj); +#endif } } @@ -984,10 +1152,13 @@ static __attribute__((noinline)) public: static inline id autorelease(id obj) { - ASSERT(obj); - ASSERT(!obj->isTaggedPointer()); + ASSERT(!obj->isTaggedPointerOrNil()); id *dest __unused = autoreleaseFast(obj); +#if SUPPORT_AUTORELEASEPOOL_DEDUP_PTRS + ASSERT(!dest || dest == EMPTY_POOL_PLACEHOLDER || (id)((AutoreleasePoolEntry *)dest)->ptr == obj); +#else ASSERT(!dest || dest == EMPTY_POOL_PLACEHOLDER || *dest == obj); +#endif return obj; } @@ -1024,9 +1195,9 @@ static void badPop(void *token) _objc_inform_now_and_on_crash ("Invalid or prematurely-freed autorelease pool %p. " "Set a breakpoint on objc_autoreleasePoolInvalid to debug. " - "Proceeding anyway because the app is old " - "(SDK version " SDK_FORMAT "). Memory errors are likely.", - token, FORMAT_SDK(sdkVersion())); + "Proceeding anyway because the app is old. Memory errors " + "are likely.", + token); } objc_autoreleasePoolInvalid(token); } @@ -1127,8 +1298,19 @@ void print() if (*p == POOL_BOUNDARY) { _objc_inform("[%p] ################ POOL %p", p, p); } else { - _objc_inform("[%p] %#16lx %s", +#if SUPPORT_AUTORELEASEPOOL_DEDUP_PTRS + AutoreleasePoolEntry *entry = (AutoreleasePoolEntry *)p; + if (entry->count > 0) { + id obj = (id)entry->ptr; + _objc_inform("[%p] %#16lx %s autorelease count %u", + p, (unsigned long)obj, object_getClassName(obj), + entry->count + 1); + goto done; + } +#endif + _objc_inform("[%p] %#16lx %s", p, (unsigned long)*p, object_getClassName(*p)); + done:; } } } @@ -1161,6 +1343,20 @@ static void printAll() _objc_inform("##############"); } +#if SUPPORT_AUTORELEASEPOOL_DEDUP_PTRS + __attribute__((noinline, cold)) + unsigned sumOfExtraReleases() + { + unsigned sumOfExtraReleases = 0; + for (id *p = begin(); p < next; p++) { + if (*p != POOL_BOUNDARY) { + sumOfExtraReleases += ((AutoreleasePoolEntry *)p)->count; + } + } + return sumOfExtraReleases; + } +#endif + __attribute__((noinline, cold)) static void printHiwat() { @@ -1168,16 +1364,29 @@ static void printHiwat() // Ignore high water marks under 256 to suppress noise. AutoreleasePoolPage *p = hotPage(); uint32_t mark = p->depth*COUNT + (uint32_t)(p->next - p->begin()); - if (mark > p->hiwat && mark > 256) { + if (mark > p->hiwat + 256) { +#if SUPPORT_AUTORELEASEPOOL_DEDUP_PTRS + unsigned sumOfExtraReleases = 0; +#endif for( ; p; p = p->parent) { p->unprotect(); p->hiwat = mark; p->protect(); + +#if SUPPORT_AUTORELEASEPOOL_DEDUP_PTRS + sumOfExtraReleases += p->sumOfExtraReleases(); +#endif } _objc_inform("POOL HIGHWATER: new high water mark of %u " "pending releases for thread %p:", mark, objc_thread_self()); +#if SUPPORT_AUTORELEASEPOOL_DEDUP_PTRS + if (sumOfExtraReleases > 0) { + _objc_inform("POOL HIGHWATER: extra sequential autoreleases of objects: %u", + sumOfExtraReleases); + } +#endif void *stack[128]; int count = backtrace(stack, sizeof(stack)/sizeof(stack[0])); @@ -1201,14 +1410,14 @@ static void printHiwat() NEVER_INLINE id objc_object::rootRetain_overflow(bool tryRetain) { - return rootRetain(tryRetain, true); + return rootRetain(tryRetain, RRVariant::Full); } NEVER_INLINE uintptr_t objc_object::rootRelease_underflow(bool performDealloc) { - return rootRelease(performDealloc, true); + return rootRelease(performDealloc, RRVariant::Full); } @@ -1317,7 +1526,7 @@ void objc_overrelease_during_dealloc_error(void) ASSERT((oldRefcnt & SIDE_TABLE_WEAKLY_REFERENCED) == 0); uintptr_t carry; - size_t refcnt = addc(oldRefcnt, extra_rc << SIDE_TABLE_RC_SHIFT, 0, &carry); + size_t refcnt = addc(oldRefcnt, (extra_rc - 1) << SIDE_TABLE_RC_SHIFT, 0, &carry); if (carry) refcnt = SIDE_TABLE_RC_PINNED; if (isDeallocating) refcnt |= SIDE_TABLE_DEALLOCATING; if (weaklyReferenced) refcnt |= SIDE_TABLE_WEAKLY_REFERENCED; @@ -1359,7 +1568,7 @@ void objc_overrelease_during_dealloc_error(void) // Move some retain counts from the side table to the isa field. // Returns the actual count subtracted, which may be less than the request. -size_t +objc_object::SidetableBorrow objc_object::sidetable_subExtraRC_nolock(size_t delta_rc) { ASSERT(isa.nonpointer); @@ -1368,7 +1577,7 @@ void objc_overrelease_during_dealloc_error(void) RefcountMap::iterator it = table.refcnts.find(this); if (it == table.refcnts.end() || it->second == 0) { // Side table retain count is zero. Can't borrow. - return 0; + return { 0, 0 }; } size_t oldRefcnt = it->second; @@ -1379,7 +1588,7 @@ void objc_overrelease_during_dealloc_error(void) size_t newRefcnt = oldRefcnt - (delta_rc << SIDE_TABLE_RC_SHIFT); ASSERT(oldRefcnt > newRefcnt); // shouldn't underflow it->second = newRefcnt; - return delta_rc; + return { delta_rc, newRefcnt >> SIDE_TABLE_RC_SHIFT }; } @@ -1394,19 +1603,29 @@ void objc_overrelease_during_dealloc_error(void) } +void +objc_object::sidetable_clearExtraRC_nolock() +{ + ASSERT(isa.nonpointer); + SideTable& table = SideTables()[this]; + RefcountMap::iterator it = table.refcnts.find(this); + table.refcnts.erase(it); +} + + // SUPPORT_NONPOINTER_ISA #endif id -objc_object::sidetable_retain() +objc_object::sidetable_retain(bool locked) { #if SUPPORT_NONPOINTER_ISA ASSERT(!isa.nonpointer); #endif SideTable& table = SideTables()[this]; - table.lock(); + if (!locked) table.lock(); size_t& refcntStorage = table.refcnts[this]; if (! (refcntStorage & SIDE_TABLE_RC_PINNED)) { refcntStorage += SIDE_TABLE_RC_ONE; @@ -1505,6 +1724,14 @@ void objc_overrelease_during_dealloc_error(void) return result; } +#if OBJC_WEAK_FORMATION_CALLOUT_DEFINED +//Clients can dlsym() for this symbol to see if an ObjC supporting +//-_setWeaklyReferenced is present +OBJC_EXPORT const uintptr_t _objc_has_weak_formation_callout = 0; +static_assert(SUPPORT_NONPOINTER_ISA, "Weak formation callout must only be defined when nonpointer isa is supported."); +#else +static_assert(!SUPPORT_NONPOINTER_ISA, "If weak callout is not present then we must not support nonpointer isas."); +#endif void objc_object::sidetable_setWeaklyReferenced_nolock() @@ -1512,9 +1739,9 @@ void objc_overrelease_during_dealloc_error(void) #if SUPPORT_NONPOINTER_ISA ASSERT(!isa.nonpointer); #endif - + SideTable& table = SideTables()[this]; - + table.refcnts[this] |= SIDE_TABLE_WEAKLY_REFERENCED; } @@ -1523,7 +1750,7 @@ void objc_overrelease_during_dealloc_error(void) // return uintptr_t instead of bool so that the various raw-isa // -release paths all return zero in eax uintptr_t -objc_object::sidetable_release(bool performDealloc) +objc_object::sidetable_release(bool locked, bool performDealloc) { #if SUPPORT_NONPOINTER_ISA ASSERT(!isa.nonpointer); @@ -1532,7 +1759,7 @@ void objc_overrelease_during_dealloc_error(void) bool do_dealloc = false; - table.lock(); + if (!locked) table.lock(); auto it = table.refcnts.try_emplace(this, SIDE_TABLE_DEALLOCATING); auto &refcnt = it.first->second; if (it.second) { @@ -1583,8 +1810,7 @@ void objc_overrelease_during_dealloc_error(void) id objc_retain(id obj) { - if (!obj) return obj; - if (obj->isTaggedPointer()) return obj; + if (obj->isTaggedPointerOrNil()) return obj; return obj->retain(); } @@ -1593,8 +1819,7 @@ void objc_overrelease_during_dealloc_error(void) void objc_release(id obj) { - if (!obj) return; - if (obj->isTaggedPointer()) return; + if (obj->isTaggedPointerOrNil()) return; return obj->release(); } @@ -1603,8 +1828,7 @@ void objc_overrelease_during_dealloc_error(void) id objc_autorelease(id obj) { - if (!obj) return obj; - if (obj->isTaggedPointer()) return obj; + if (obj->isTaggedPointerOrNil()) return obj; return obj->autorelease(); } @@ -1694,8 +1918,7 @@ void objc_overrelease_during_dealloc_error(void) obj->rootRelease(); } - -// Call [cls alloc] or [cls allocWithZone:nil], with appropriate +// Call [cls alloc] or [cls allocWithZone:nil], with appropriate // shortcutting optimizations. static ALWAYS_INLINE id callAlloc(Class cls, bool checkNil, bool allocWithZone=false) @@ -1731,7 +1954,7 @@ void objc_overrelease_during_dealloc_error(void) } // Calls [cls allocWithZone:nil]. -id +id objc_allocWithZone(Class cls) { return callAlloc(cls, true/*checkNil*/, true/*allocWithZone*/); @@ -1750,7 +1973,7 @@ void objc_overrelease_during_dealloc_error(void) { #if __OBJC2__ if (fastpath(cls && !cls->ISA()->hasCustomCore())) { - return [callAlloc(cls, false/*checkNil*/, true/*allocWithZone*/) init]; + return [callAlloc(cls, false/*checkNil*/) init]; } #endif return ((id(*)(id, SEL))objc_msgSend)(cls, @selector(new)); @@ -1761,7 +1984,7 @@ void objc_overrelease_during_dealloc_error(void) objc_opt_self(id obj) { #if __OBJC2__ - if (fastpath(!obj || obj->isTaggedPointer() || !obj->ISA()->hasCustomCore())) { + if (fastpath(obj->isTaggedPointerOrNil() || !obj->ISA()->hasCustomCore())) { return obj; } #endif @@ -1790,7 +2013,7 @@ void objc_overrelease_during_dealloc_error(void) if (slowpath(!obj)) return NO; Class cls = obj->getIsa(); if (fastpath(!cls->hasCustomCore())) { - for (Class tcls = cls; tcls; tcls = tcls->superclass) { + for (Class tcls = cls; tcls; tcls = tcls->getSuperclass()) { if (tcls == otherClass) return YES; } return NO; @@ -2019,11 +2242,11 @@ - (Class)class { } + (Class)superclass { - return self->superclass; + return self->getSuperclass(); } - (Class)superclass { - return [self class]->superclass; + return [self class]->getSuperclass(); } + (BOOL)isMemberOfClass:(Class)cls { @@ -2035,28 +2258,28 @@ - (BOOL)isMemberOfClass:(Class)cls { } + (BOOL)isKindOfClass:(Class)cls { - for (Class tcls = self->ISA(); tcls; tcls = tcls->superclass) { + for (Class tcls = self->ISA(); tcls; tcls = tcls->getSuperclass()) { if (tcls == cls) return YES; } return NO; } - (BOOL)isKindOfClass:(Class)cls { - for (Class tcls = [self class]; tcls; tcls = tcls->superclass) { + for (Class tcls = [self class]; tcls; tcls = tcls->getSuperclass()) { if (tcls == cls) return YES; } return NO; } + (BOOL)isSubclassOfClass:(Class)cls { - for (Class tcls = self; tcls; tcls = tcls->superclass) { + for (Class tcls = self; tcls; tcls = tcls->getSuperclass()) { if (tcls == cls) return YES; } return NO; } + (BOOL)isAncestorOfObject:(NSObject *)obj { - for (Class tcls = [obj class]; tcls; tcls = tcls->superclass) { + for (Class tcls = [obj class]; tcls; tcls = tcls->getSuperclass()) { if (tcls == self) return YES; } return NO; @@ -2076,7 +2299,7 @@ - (BOOL)respondsToSelector:(SEL)sel { + (BOOL)conformsToProtocol:(Protocol *)protocol { if (!protocol) return NO; - for (Class tcls = self; tcls; tcls = tcls->superclass) { + for (Class tcls = self; tcls; tcls = tcls->getSuperclass()) { if (class_conformsToProtocol(tcls, protocol)) return YES; } return NO; @@ -2084,7 +2307,7 @@ + (BOOL)conformsToProtocol:(Protocol *)protocol { - (BOOL)conformsToProtocol:(Protocol *)protocol { if (!protocol) return NO; - for (Class tcls = [self class]; tcls; tcls = tcls->superclass) { + for (Class tcls = [self class]; tcls; tcls = tcls->getSuperclass()) { if (class_conformsToProtocol(tcls, protocol)) return YES; } return NO; diff --git a/runtime/Protocol.mm b/runtime/Protocol.mm index 9e97419..9432267 100644 --- a/runtime/Protocol.mm +++ b/runtime/Protocol.mm @@ -100,7 +100,7 @@ - (BOOL)isEqual:other // check isKindOf: Class cls; Class protoClass = objc_getClass("Protocol"); - for (cls = object_getClass(other); cls; cls = cls->superclass) { + for (cls = object_getClass(other); cls; cls = cls->getSuperclass()) { if (cls == protoClass) break; } if (!cls) return NO; diff --git a/runtime/arm64-asm.h b/runtime/arm64-asm.h index fb15e5e..a6f7d38 100644 --- a/runtime/arm64-asm.h +++ b/runtime/arm64-asm.h @@ -28,6 +28,8 @@ #if __arm64__ +#include "objc-config.h" + #if __LP64__ // true arm64 @@ -129,11 +131,35 @@ // note: assumes the imp is not nil eor $1, $1, $2 // mix SEL into ptrauth modifier eor $1, $1, $3 // mix isa into ptrauth modifier - autib $0, $1 // authenticate cached imp + autib $0, $1 // authenticate cached imp ldr xzr, [$0] // crash if authentication failed paciza $0 // resign cached imp as IMP .endmacro +.macro ExtractISA + and $0, $1, #ISA_MASK +#if ISA_SIGNING_AUTH_MODE == ISA_SIGNING_STRIP + xpacd $0 +#elif ISA_SIGNING_AUTH_MODE == ISA_SIGNING_AUTH + mov x10, $2 + movk x10, #ISA_SIGNING_DISCRIMINATOR, LSL #48 + autda $0, x10 +#endif +.endmacro + +.macro AuthISASuper dst, addr_mutable, discriminator +#if ISA_SIGNING_AUTH_MODE == ISA_SIGNING_AUTH + movk \addr_mutable, #\discriminator, LSL #48 + autda \dst, \addr_mutable +#elif ISA_SIGNING_AUTH_MODE == ISA_SIGNING_STRIP + xpacd \dst +#endif +.endmacro + +.macro SignAsImp + paciza $0 +.endmacro + // JOP #else // not JOP @@ -162,7 +188,14 @@ .macro AuthAndResignAsIMP // $0 = cached imp, $1 = address of cached imp, $2 = SEL eor $0, $0, $3 -.endmacro +.endmacro + +.macro SignAsImp +.endmacro + +.macro ExtractISA + and $0, $1, #ISA_MASK +.endmacro // not JOP #endif diff --git a/runtime/dummy-library-mac-i386.c b/runtime/dummy-library-mac-i386.c new file mode 100644 index 0000000..a8cd20f --- /dev/null +++ b/runtime/dummy-library-mac-i386.c @@ -0,0 +1,356 @@ +// This file contains stubs matching the sybols previously exported by libobjc +// when i386 Mac was actually supported. These stubs allow us to tease apart the +// dependencies to prepare for removing i386 Mac libobjc entirely. +// +// This file is not built when building for any other arch/OS combination. When +// building for i386 Mac, no other source files are built, just this one. This +// is handled using the Included/Excluded Source File Names settings in Xcode, +// with arch/OS-specific overrides. +// +// rdar://problem/58541885 + +#pragma GCC visibility push(default) +const char ___ld_hide_os10_5__objc_class_name_NSObject __asm__("$ld$hide$os10.5$.objc_class_name_NSObject"); +const char ___ld_hide_os10_6__objc_class_name_NSObject __asm__("$ld$hide$os10.6$.objc_class_name_NSObject"); +const char ___ld_hide_os10_7__objc_class_name_NSObject __asm__("$ld$hide$os10.7$.objc_class_name_NSObject"); +const char ___objc_class_name_List __asm__(".objc_class_name_List"); +const char ___objc_class_name_NSObject __asm__(".objc_class_name_NSObject"); +const char ___objc_class_name_Object __asm__(".objc_class_name_Object"); +const char ___objc_class_name_Protocol __asm__(".objc_class_name_Protocol"); +void NXCompareHashTables(void) {} +void NXCompareMapTables(void) {} +void NXCopyHashTable(void) {} +void NXCopyStringBuffer(void) {} +void NXCopyStringBufferFromZone(void) {} +void NXCountHashTable(void) {} +void NXCountMapTable(void) {} +void NXCreateHashTable(void) {} +void NXCreateHashTableFromZone(void) {} +void NXCreateMapTable(void) {} +void NXCreateMapTableFromZone(void) {} +void NXEmptyHashTable(void) {} +void NXFreeHashTable(void) {} +void NXFreeMapTable(void) {} +void NXHashGet(void) {} +void NXHashInsert(void) {} +void NXHashInsertIfAbsent(void) {} +void NXHashMember(void) {} +void NXHashRemove(void) {} +void NXInitHashState(void) {} +void NXInitMapState(void) {} +void NXMapGet(void) {} +void NXMapInsert(void) {} +void NXMapMember(void) {} +void NXMapRemove(void) {} +void NXNextHashState(void) {} +void NXNextMapState(void) {} +void NXNoEffectFree(void) {} +const char NXObjectMapPrototype; +void NXPtrHash(void) {} +void NXPtrIsEqual(void) {} +const char NXPtrPrototype; +const char NXPtrStructKeyPrototype; +const char NXPtrValueMapPrototype; +void NXReallyFree(void) {} +void NXResetHashTable(void) {} +void NXResetMapTable(void) {} +void NXStrHash(void) {} +void NXStrIsEqual(void) {} +const char NXStrPrototype; +const char NXStrStructKeyPrototype; +const char NXStrValueMapPrototype; +void NXUniqueString(void) {} +void NXUniqueStringNoCopy(void) {} +void NXUniqueStringWithLength(void) {} +char _alloc; +void _class_getIvarMemoryManagement(void) {} +void _class_isFutureClass(void) {} +void _class_isSwift(void) {} +char _copy; +char _dealloc; +char _error; +void _objcInit(void) {} +void _objc_addWillInitializeClassFunc(void) {} +void _objc_atfork_child(void) {} +void _objc_atfork_parent(void) {} +void _objc_atfork_prepare(void) {} +void _objc_autoreleasePoolPop(void) {} +void _objc_autoreleasePoolPrint(void) {} +void _objc_autoreleasePoolPush(void) {} +void _objc_deallocOnMainThreadHelper(void) {} +const char _objc_debug_class_hash; +const char _objc_empty_cache; +void _objc_error(void) {} +void _objc_flush_caches(void) {} +void _objc_getFreedObjectClass(void) {} +void _objc_init(void) {} +void _objc_msgForward(void) {} +void _objc_msgForward_stret(void) {} +void _objc_resolve_categories_for_class(void) {} +void _objc_rootAlloc(void) {} +void _objc_rootAllocWithZone(void) {} +void _objc_rootAutorelease(void) {} +void _objc_rootDealloc(void) {} +void _objc_rootFinalize(void) {} +void _objc_rootHash(void) {} +void _objc_rootInit(void) {} +void _objc_rootIsDeallocating(void) {} +void _objc_rootRelease(void) {} +void _objc_rootReleaseWasZero(void) {} +void _objc_rootRetain(void) {} +void _objc_rootRetainCount(void) {} +void _objc_rootTryRetain(void) {} +void _objc_rootZone(void) {} +void _objc_setBadAllocHandler(void) {} +void _objc_setClassLoader(void) {} +void _protocol_getMethodTypeEncoding(void) {} +char _realloc; +char _zoneAlloc; +char _zoneCopy; +char _zoneRealloc; +void class_addIvar(void) {} +void class_addMethod(void) {} +void class_addMethods(void) {} +void class_addProperty(void) {} +void class_addProtocol(void) {} +void class_conformsToProtocol(void) {} +void class_copyIvarList(void) {} +void class_copyMethodList(void) {} +void class_copyPropertyList(void) {} +void class_copyProtocolList(void) {} +void class_createInstance(void) {} +void class_createInstanceFromZone(void) {} +void class_createInstances(void) {} +void class_getClassMethod(void) {} +void class_getClassVariable(void) {} +void class_getImageName(void) {} +void class_getInstanceMethod(void) {} +void class_getInstanceSize(void) {} +void class_getInstanceVariable(void) {} +void class_getIvarLayout(void) {} +void class_getMethodImplementation(void) {} +void class_getMethodImplementation_stret(void) {} +void class_getName(void) {} +void class_getProperty(void) {} +void class_getSuperclass(void) {} +void class_getVersion(void) {} +void class_getWeakIvarLayout(void) {} +void class_isMetaClass(void) {} +void class_lookupMethod(void) {} +void class_nextMethodList(void) {} +void class_poseAs(void) {} +void class_removeMethods(void) {} +void class_replaceMethod(void) {} +void class_replaceProperty(void) {} +void class_respondsToMethod(void) {} +void class_respondsToSelector(void) {} +void class_setIvarLayout(void) {} +void class_setSuperclass(void) {} +void class_setVersion(void) {} +void class_setWeakIvarLayout(void) {} +void gdb_class_getClass(void) {} +void gdb_object_getClass(void) {} +void imp_getBlock(void) {} +void imp_implementationWithBlock(void) {} +void imp_removeBlock(void) {} +void instrumentObjcMessageSends(void) {} +void ivar_getName(void) {} +void ivar_getOffset(void) {} +void ivar_getTypeEncoding(void) {} +void method_copyArgumentType(void) {} +void method_copyReturnType(void) {} +void method_exchangeImplementations(void) {} +void method_getArgumentType(void) {} +void method_getDescription(void) {} +void method_getImplementation(void) {} +void method_getName(void) {} +void method_getNumberOfArguments(void) {} +void method_getReturnType(void) {} +void method_getSizeOfArguments(void) {} +void method_getTypeEncoding(void) {} +void method_invoke(void) {} +void method_invoke_stret(void) {} +void method_setImplementation(void) {} +void objc_addClass(void) {} +void objc_addLoadImageFunc(void) {} +void objc_alloc(void) {} +void objc_allocWithZone(void) {} +void objc_alloc_init(void) {} +void objc_allocateClassPair(void) {} +void objc_allocateProtocol(void) {} +void objc_allocate_object(void) {} +void objc_appRequiresGC(void) {} +void objc_assertRegisteredThreadWithCollector(void) {} +void objc_assign_global(void) {} +void objc_assign_ivar(void) {} +void objc_assign_strongCast(void) {} +void objc_assign_threadlocal(void) {} +void objc_assign_weak(void) {} +void objc_atomicCompareAndSwapGlobal(void) {} +void objc_atomicCompareAndSwapGlobalBarrier(void) {} +void objc_atomicCompareAndSwapInstanceVariable(void) {} +void objc_atomicCompareAndSwapInstanceVariableBarrier(void) {} +void objc_atomicCompareAndSwapPtr(void) {} +void objc_atomicCompareAndSwapPtrBarrier(void) {} +void objc_autorelease(void) {} +void objc_autoreleasePoolPop(void) {} +void objc_autoreleasePoolPush(void) {} +void objc_autoreleaseReturnValue(void) {} +void objc_clear_deallocating(void) {} +void objc_clear_stack(void) {} +void objc_collect(void) {} +void objc_collect_init(void) {} +void objc_collectableZone(void) {} +void objc_collectingEnabled(void) {} +void objc_collecting_enabled(void) {} +void objc_constructInstance(void) {} +void objc_copyClassList(void) {} +void objc_copyClassNamesForImage(void) {} +void objc_copyClassNamesForImageHeader(void) {} +void objc_copyCppObjectAtomic(void) {} +void objc_copyImageNames(void) {} +void objc_copyProtocolList(void) {} +void objc_copyStruct(void) {} +void objc_copyWeak(void) {} +const char objc_debug_autoreleasepoolpage_child_offset; +const char objc_debug_autoreleasepoolpage_depth_offset; +const char objc_debug_autoreleasepoolpage_hiwat_offset; +const char objc_debug_autoreleasepoolpage_magic_offset; +const char objc_debug_autoreleasepoolpage_next_offset; +const char objc_debug_autoreleasepoolpage_parent_offset; +const char objc_debug_autoreleasepoolpage_thread_offset; +void objc_destroyWeak(void) {} +void objc_destructInstance(void) {} +void objc_disposeClassPair(void) {} +void objc_dumpHeap(void) {} +void objc_duplicateClass(void) {} +void objc_enumerationMutation(void) {} +void objc_exception_extract(void) {} +void objc_exception_get_functions(void) {} +void objc_exception_match(void) {} +void objc_exception_set_functions(void) {} +void objc_exception_throw(void) {} +void objc_exception_try_enter(void) {} +void objc_exception_try_exit(void) {} +void objc_finalizeOnMainThread(void) {} +void objc_getAssociatedObject(void) {} +void objc_getClass(void) {} +void objc_getClassList(void) {} +void objc_getClasses(void) {} +void objc_getFutureClass(void) {} +void objc_getMetaClass(void) {} +void objc_getOrigClass(void) {} +void objc_getProperty(void) {} +void objc_getProtocol(void) {} +void objc_getRequiredClass(void) {} +void objc_initWeak(void) {} +void objc_initWeakOrNil(void) {} +void objc_initializeClassPair(void) {} +void objc_isAuto(void) {} +void objc_is_finalized(void) {} +void objc_loadModule(void) {} +void objc_loadModules(void) {} +void objc_loadWeak(void) {} +void objc_loadWeakRetained(void) {} +void objc_lookUpClass(void) {} +void objc_memmove_collectable(void) {} +void objc_moveWeak(void) {} +void objc_msgSend(void) {} +void objc_msgSendSuper(void) {} +void objc_msgSendSuper_stret(void) {} +void objc_msgSend_fpret(void) {} +void objc_msgSend_stret(void) {} +void objc_msgSendv(void) {} +void objc_msgSendv_fpret(void) {} +void objc_msgSendv_stret(void) {} +void objc_opt_class(void) {} +void objc_opt_isKindOfClass(void) {} +void objc_opt_new(void) {} +void objc_opt_respondsToSelector(void) {} +void objc_opt_self(void) {} +void objc_read_weak(void) {} +void objc_registerClassPair(void) {} +void objc_registerProtocol(void) {} +void objc_registerThreadWithCollector(void) {} +void objc_release(void) {} +void objc_removeAssociatedObjects(void) {} +void objc_retain(void) {} +void objc_retainAutorelease(void) {} +void objc_retainAutoreleaseReturnValue(void) {} +void objc_retainAutoreleasedReturnValue(void) {} +void objc_retainBlock(void) {} +void objc_retain_autorelease(void) {} +void objc_retainedObject(void) {} +void objc_setAssociatedObject(void) {} +void objc_setClassHandler(void) {} +void objc_setCollectionRatio(void) {} +void objc_setCollectionThreshold(void) {} +void objc_setEnumerationMutationHandler(void) {} +void objc_setForwardHandler(void) {} +void objc_setHook_getImageName(void) {} +void objc_setMultithreaded(void) {} +void objc_setProperty(void) {} +void objc_setProperty_atomic(void) {} +void objc_setProperty_atomic_copy(void) {} +void objc_setProperty_nonatomic(void) {} +void objc_setProperty_nonatomic_copy(void) {} +void objc_set_collection_ratio(void) {} +void objc_set_collection_threshold(void) {} +void objc_should_deallocate(void) {} +void objc_startCollectorThread(void) {} +void objc_start_collector_thread(void) {} +void objc_storeStrong(void) {} +void objc_storeWeak(void) {} +void objc_storeWeakOrNil(void) {} +void objc_sync_enter(void) {} +void objc_sync_exit(void) {} +void objc_sync_try_enter(void) {} +void objc_unloadModules(void) {} +void objc_unregisterThreadWithCollector(void) {} +void objc_unretainedObject(void) {} +void objc_unretainedPointer(void) {} +void objc_unsafeClaimAutoreleasedReturnValue(void) {} +void object_copy(void) {} +void object_copyFromZone(void) {} +void object_dispose(void) {} +void object_getClass(void) {} +void object_getClassName(void) {} +void object_getIndexedIvars(void) {} +void object_getInstanceVariable(void) {} +void object_getIvar(void) {} +void object_getMethodImplementation(void) {} +void object_getMethodImplementation_stret(void) {} +void object_isClass(void) {} +void object_realloc(void) {} +void object_reallocFromZone(void) {} +void object_setClass(void) {} +void object_setInstanceVariable(void) {} +void object_setInstanceVariableWithStrongDefault(void) {} +void object_setIvar(void) {} +void object_setIvarWithStrongDefault(void) {} +void property_copyAttributeList(void) {} +void property_copyAttributeValue(void) {} +void property_getAttributes(void) {} +void property_getName(void) {} +void protocol_addMethodDescription(void) {} +void protocol_addProperty(void) {} +void protocol_addProtocol(void) {} +void protocol_conformsToProtocol(void) {} +void protocol_copyMethodDescriptionList(void) {} +void protocol_copyPropertyList(void) {} +void protocol_copyPropertyList2(void) {} +void protocol_copyProtocolList(void) {} +void protocol_getMethodDescription(void) {} +void protocol_getName(void) {} +void protocol_getProperty(void) {} +void protocol_isEqual(void) {} +void sel_getName(void) {} +void sel_getUid(void) {} +void sel_isEqual(void) {} +void sel_isMapped(void) {} +void sel_registerName(void) {} +void objc_cache_buckets(void) {} +void objc_cache_bytesForCapacity(void) {} +void objc_cache_capacity(void) {} +void objc_cache_occupied(void) {} +void objc_copyClassesForImage(void) {} diff --git a/runtime/isa.h b/runtime/isa.h index b4741cb..8b552c2 100644 --- a/runtime/isa.h +++ b/runtime/isa.h @@ -55,26 +55,46 @@ // uintptr_t extraBytes : 1; // allocated with extra bytes # if __arm64__ -# define ISA_MASK 0x0000000ffffffff8ULL -# define ISA_MAGIC_MASK 0x000003f000000001ULL -# define ISA_MAGIC_VALUE 0x000001a000000001ULL -# define ISA_BITFIELD \ - uintptr_t nonpointer : 1; \ - uintptr_t has_assoc : 1; \ - uintptr_t has_cxx_dtor : 1; \ - uintptr_t shiftcls : 33; /*MACH_VM_MAX_ADDRESS 0x1000000000*/ \ - uintptr_t magic : 6; \ - uintptr_t weakly_referenced : 1; \ - uintptr_t deallocating : 1; \ - uintptr_t has_sidetable_rc : 1; \ - uintptr_t extra_rc : 19 -# define RC_ONE (1ULL<<45) -# define RC_HALF (1ULL<<18) +// ARM64 simulators have a larger address space, so use the ARM64e +// scheme even when simulators build for ARM64-not-e. +# if __has_feature(ptrauth_calls) || TARGET_OS_SIMULATOR +# define ISA_MASK 0x007ffffffffffff8ULL +# define ISA_MAGIC_MASK 0x0000000000000001ULL +# define ISA_MAGIC_VALUE 0x0000000000000001ULL +# define ISA_HAS_CXX_DTOR_BIT 0 +# define ISA_BITFIELD \ + uintptr_t nonpointer : 1; \ + uintptr_t has_assoc : 1; \ + uintptr_t weakly_referenced : 1; \ + uintptr_t shiftcls_and_sig : 52; \ + uintptr_t has_sidetable_rc : 1; \ + uintptr_t extra_rc : 8 +# define RC_ONE (1ULL<<56) +# define RC_HALF (1ULL<<7) +# else +# define ISA_MASK 0x0000000ffffffff8ULL +# define ISA_MAGIC_MASK 0x000003f000000001ULL +# define ISA_MAGIC_VALUE 0x000001a000000001ULL +# define ISA_HAS_CXX_DTOR_BIT 1 +# define ISA_BITFIELD \ + uintptr_t nonpointer : 1; \ + uintptr_t has_assoc : 1; \ + uintptr_t has_cxx_dtor : 1; \ + uintptr_t shiftcls : 33; /*MACH_VM_MAX_ADDRESS 0x1000000000*/ \ + uintptr_t magic : 6; \ + uintptr_t weakly_referenced : 1; \ + uintptr_t unused : 1; \ + uintptr_t has_sidetable_rc : 1; \ + uintptr_t extra_rc : 19 +# define RC_ONE (1ULL<<45) +# define RC_HALF (1ULL<<18) +# endif # elif __x86_64__ # define ISA_MASK 0x00007ffffffffff8ULL # define ISA_MAGIC_MASK 0x001f800000000001ULL # define ISA_MAGIC_VALUE 0x001d800000000001ULL +# define ISA_HAS_CXX_DTOR_BIT 1 # define ISA_BITFIELD \ uintptr_t nonpointer : 1; \ uintptr_t has_assoc : 1; \ @@ -82,7 +102,7 @@ uintptr_t shiftcls : 44; /*MACH_VM_MAX_ADDRESS 0x7fffffe00000*/ \ uintptr_t magic : 6; \ uintptr_t weakly_referenced : 1; \ - uintptr_t deallocating : 1; \ + uintptr_t unused : 1; \ uintptr_t has_sidetable_rc : 1; \ uintptr_t extra_rc : 8 # define RC_ONE (1ULL<<56) @@ -109,6 +129,7 @@ # define ISA_INDEX_COUNT (1 << ISA_INDEX_BITS) # define ISA_INDEX_MAGIC_MASK 0x001E0001 # define ISA_INDEX_MAGIC_VALUE 0x001C0001 +# define ISA_HAS_CXX_DTOR_BIT 1 # define ISA_BITFIELD \ uintptr_t nonpointer : 1; \ uintptr_t has_assoc : 1; \ @@ -116,7 +137,7 @@ uintptr_t magic : 4; \ uintptr_t has_cxx_dtor : 1; \ uintptr_t weakly_referenced : 1; \ - uintptr_t deallocating : 1; \ + uintptr_t unused : 1; \ uintptr_t has_sidetable_rc : 1; \ uintptr_t extra_rc : 7 # define RC_ONE (1ULL<<25) diff --git a/runtime/objc-abi.h b/runtime/objc-abi.h index 18430df..937a4be 100644 --- a/runtime/objc-abi.h +++ b/runtime/objc-abi.h @@ -46,7 +46,7 @@ /* Linker metadata symbols */ // NSObject was in Foundation/CF on macOS < 10.8. -#if TARGET_OS_OSX +#if TARGET_OS_OSX && (__x86_64__ || __i386__) #if __OBJC2__ OBJC_EXPORT const char __objc_nsobject_class_10_5 @@ -171,6 +171,15 @@ typedef struct objc_image_info { Old ABI: Set by some compilers. Not used by the runtime. */ +// Description of an expected duplicate class name. +// __DATA,__objc_dupclass stores one of these. Only the main image is +// consulted for these purposes. +typedef struct _objc_duplicate_class { + uint32_t version; + uint32_t flags; + const char name[64]; +} objc_duplicate_class; +#define OBJC_HAS_DUPLICATE_CLASS 1 /* Properties */ @@ -412,7 +421,7 @@ objc_retainBlock(id _Nullable) // Extract class pointer from an isa field. -#if TARGET_OS_SIMULATOR && !TARGET_OS_IOSMAC +#if TARGET_OS_SIMULATOR && !TARGET_OS_MACCATALYST && !__arm64__ // No simulators use nonpointer isa yet. #elif __LP64__ diff --git a/runtime/objc-api.h b/runtime/objc-api.h index 284f24f..26b30bf 100644 --- a/runtime/objc-api.h +++ b/runtime/objc-api.h @@ -118,6 +118,12 @@ # define NS_ENFORCE_NSOBJECT_DESIGNATED_INITIALIZER 1 #endif +/* The arm64 ABI requires proper casting to ensure arguments are passed + * * correctly. */ +#if defined(__arm64__) && !__swift__ +# undef OBJC_OLD_DISPATCH_PROTOTYPES +# define OBJC_OLD_DISPATCH_PROTOTYPES 0 +#endif /* OBJC_OLD_DISPATCH_PROTOTYPES == 0 enforces the rule that the dispatch * functions must be cast to an appropriate function pointer type. */ diff --git a/runtime/objc-block-trampolines.mm b/runtime/objc-block-trampolines.mm index 9dea652..f905d35 100644 --- a/runtime/objc-block-trampolines.mm +++ b/runtime/objc-block-trampolines.mm @@ -57,6 +57,16 @@ # define TrampolinePtrauth #endif +// A page of trampolines is as big as the maximum supported page size +// everywhere except i386. i386 only exists for the watch simulator +// now, and we know it really only has 4kB pages. Also see comments +// below about PAGE_SIZE and PAGE_MAX_SIZE. +#ifdef __i386__ +#define TRAMPOLINE_PAGE_SIZE PAGE_MIN_SIZE +#else +#define TRAMPOLINE_PAGE_SIZE PAGE_MAX_SIZE +#endif + class TrampolinePointerWrapper { struct TrampolinePointers { class TrampolineAddress { @@ -103,22 +113,22 @@ uintptr_t address() { void check() { #if DEBUG - ASSERT(impl.address() == textSegment + PAGE_MAX_SIZE); - ASSERT(impl.address() % PAGE_SIZE == 0); // not PAGE_MAX_SIZE - assert(impl.address() + PAGE_MAX_SIZE == + ASSERT(impl.address() == textSegment + TRAMPOLINE_PAGE_SIZE); + ASSERT(impl.address() % PAGE_SIZE == 0); // not TRAMPOLINE_PAGE_SIZE + ASSERT(impl.address() + TRAMPOLINE_PAGE_SIZE == last.address() + SLOT_SIZE); ASSERT(last.address()+8 < textSegment + textSegmentSize); ASSERT((last.address() - start.address()) % SLOT_SIZE == 0); # if SUPPORT_STRET - ASSERT(impl_stret.address() == textSegment + 2*PAGE_MAX_SIZE); - ASSERT(impl_stret.address() % PAGE_SIZE == 0); // not PAGE_MAX_SIZE - assert(impl_stret.address() + PAGE_MAX_SIZE == + ASSERT(impl_stret.address() == textSegment + 2*TRAMPOLINE_PAGE_SIZE); + ASSERT(impl_stret.address() % PAGE_SIZE == 0); // not TRAMPOLINE_PAGE_SIZE + ASSERT(impl_stret.address() + TRAMPOLINE_PAGE_SIZE == last_stret.address() + SLOT_SIZE); - assert(start.address() - impl.address() == + ASSERT(start.address() - impl.address() == start_stret.address() - impl_stret.address()); - assert(last_stret.address() + SLOT_SIZE < + ASSERT(last_stret.address() + SLOT_SIZE < textSegment + textSegmentSize); - assert((last_stret.address() - start_stret.address()) + ASSERT((last_stret.address() - start_stret.address()) % SLOT_SIZE == 0); # endif #endif @@ -178,8 +188,7 @@ void Initialize() { uintptr_t textSegment() { return get()->textSegment; } uintptr_t textSegmentSize() { return get()->textSegmentSize; } - // See comments below about PAGE_SIZE and PAGE_MAX_SIZE. - uintptr_t dataSize() { return PAGE_MAX_SIZE; } + uintptr_t dataSize() { return TRAMPOLINE_PAGE_SIZE; } uintptr_t impl() { return get()->impl.address(); } uintptr_t start() { return get()->start.address(); } @@ -202,11 +211,13 @@ void Initialize() { // We must take care with our data layout on architectures that support // multiple page sizes. // -// The trampoline template in __TEXT is sized and aligned with PAGE_MAX_SIZE. -// On some platforms this requires additional linker flags. +// The trampoline template in __TEXT is sized and aligned with PAGE_MAX_SIZE, +// except on i386 which is a weird special case that uses PAGE_MIN_SIZE. +// The TRAMPOLINE_PAGE_SIZE macro handles this difference. On some platforms, +// aligning to PAGE_MAX_SIZE requires additional linker flags. // -// When we allocate a page group, we use PAGE_MAX_SIZE size. -// This allows trampoline code to find its data by subtracting PAGE_MAX_SIZE. +// When we allocate a page group, we use TRAMPOLINE_PAGE_SIZE size. +// This allows trampoline code to find its data by subtracting TRAMPOLINE_PAGE_SIZE. // // When we allocate a page group, we use the process's page alignment. // This simplifies allocation because we don't need to force greater than @@ -231,14 +242,14 @@ void Initialize() { // Payload data: block pointers and free list. // Bytes parallel with trampoline header code are the fields above or unused - // uint8_t payloads[PAGE_MAX_SIZE - sizeof(TrampolineBlockPageGroup)] + // uint8_t payloads[TRAMPOLINE_PAGE_SIZE - sizeof(TrampolineBlockPageGroup)] // Code: Mach-O header, then trampoline header followed by trampolines. // On platforms with struct return we have non-stret trampolines and // stret trampolines. The stret and non-stret trampolines at a given // index share the same data page. - // uint8_t macho[PAGE_MAX_SIZE]; - // uint8_t trampolines[ArgumentModeCount][PAGE_MAX_SIZE]; + // uint8_t macho[TRAMPOLINE_PAGE_SIZE]; + // uint8_t trampolines[ArgumentModeCount][TRAMPOLINE_PAGE_SIZE]; // Per-trampoline block data format: // initial value is 0 while page data is filled sequentially @@ -280,7 +291,7 @@ uintptr_t trampolinesForMode(int aMode) { // Skip over the data area, one page of Mach-O headers, // and one text page for each mode before this one. return (uintptr_t)this + Trampolines.dataSize() + - PAGE_MAX_SIZE * (1 + aMode); + TRAMPOLINE_PAGE_SIZE * (1 + aMode); } IMP trampoline(int aMode, uintptr_t index) { diff --git a/runtime/objc-blocktramps-i386.s b/runtime/objc-blocktramps-i386.s index d4f1eb8..cd7c9ce 100755 --- a/runtime/objc-blocktramps-i386.s +++ b/runtime/objc-blocktramps-i386.s @@ -30,13 +30,13 @@ .globl __objc_blockTrampolineStart .globl __objc_blockTrampolineLast -.align PAGE_SHIFT +.align 12 /* PAGE_SHIFT */ __objc_blockTrampolineImpl: movl (%esp), %eax // return address pushed by trampoline // 4(%esp) is return address pushed by the call site movl 8(%esp), %ecx // self -> ecx movl %ecx, 12(%esp) // ecx -> _cmd - movl -2*PAGE_SIZE-5(%eax), %ecx // block object pointer -> ecx + movl -2*4096/*PAGE_SIZE */-5(%eax), %ecx // block object pointer -> ecx // trampoline is -5 bytes from the return address // data is -2 pages from the trampoline movl %ecx, 8(%esp) // ecx -> self @@ -567,14 +567,14 @@ __objc_blockTrampolineLast: .globl __objc_blockTrampolineStart_stret .globl __objc_blockTrampolineLast_stret -.align PAGE_SHIFT +.align 12 /* PAGE_SHIFT */ __objc_blockTrampolineImpl_stret: movl (%esp), %eax // return address pushed by trampoline // 4(%esp) is return address pushed by the call site // 8(%esp) is struct-return address movl 12(%esp), %ecx // self -> ecx movl %ecx, 16(%esp) // ecx -> _cmd - movl -3*PAGE_SIZE-5(%eax), %ecx // block object pointer -> ecx + movl -3*4096/*PAGE_SIZE*/-5(%eax), %ecx // block object pointer -> ecx // trampoline is -5 bytes from the return address // data is -3 pages from the trampoline movl %ecx, 12(%esp) // ecx -> self diff --git a/runtime/objc-blocktramps-x86_64.s b/runtime/objc-blocktramps-x86_64.s index 5f377f0..618e0f1 100755 --- a/runtime/objc-blocktramps-x86_64.s +++ b/runtime/objc-blocktramps-x86_64.s @@ -30,22 +30,37 @@ .globl __objc_blockTrampolineStart .globl __objc_blockTrampolineLast -.align PAGE_SHIFT +.align PAGE_MAX_SHIFT __objc_blockTrampolineImpl: movq (%rsp), %r10 // read return address pushed by TrampolineEntry's callq movq %rdi, %rsi // arg1 -> arg2 - movq -2*PAGE_SIZE-5(%r10), %rdi // block object pointer -> arg1 + movq -2*PAGE_MAX_SIZE-5(%r10), %rdi // block object pointer -> arg1 // trampoline is -5 bytes from the return address // data is -2 pages from the trampoline ret // back to TrampolineEntry to preserve CPU's return stack -.macro TrampolineEntry +.macro TrampolineEntry1 // This trampoline is 8 bytes long. // This callq is 5 bytes long. callq __objc_blockTrampolineImpl jmp *16(%rdi) .endmacro +.macro TrampolineEntry4 + TrampolineEntry1 + TrampolineEntry1 + TrampolineEntry1 + TrampolineEntry1 +.endmacro + +#if PAGE_MAX_SHIFT == 12 +#define TrampolineEntry TrampolineEntry1 +#elif PAGE_MAX_SHIFT == 14 +#define TrampolineEntry TrampolineEntry4 +#else +#error "unknown PAGE_MAX_SHIFT value" +#endif + .align 5 __objc_blockTrampolineStart: TrampolineEntry @@ -555,8 +570,26 @@ __objc_blockTrampolineStart: TrampolineEntry TrampolineEntry TrampolineEntry + +// The above is 507 entries. +#if PAGE_MAX_SHIFT == 14 +// With 16kB pages, we need (4096*4-32)/8 = 2044 single entries, or +// 511 "quad" entries as above. We need 3 more regular entries, then +// 3 more singular entries, and finally a singular entry labeled Last. + TrampolineEntry + TrampolineEntry + TrampolineEntry + TrampolineEntry1 + TrampolineEntry1 + TrampolineEntry1 +__objc_blockTrampolineLast: + TrampolineEntry1 +#else +// With 4kB pages, we need (4096-32)/8 = 508 entries. We have one +// more at the end with the Last label for a total of 508. __objc_blockTrampolineLast: TrampolineEntry +#endif .text @@ -564,24 +597,39 @@ __objc_blockTrampolineLast: .globl __objc_blockTrampolineStart_stret .globl __objc_blockTrampolineLast_stret -.align PAGE_SHIFT +.align PAGE_MAX_SHIFT __objc_blockTrampolineImpl_stret: // %rdi -- arg1 -- is address of return value's space. Don't mess with it. movq (%rsp), %r10 // read return address pushed by TrampolineEntry's callq movq %rsi, %rdx // arg2 -> arg3 - movq -3*PAGE_SIZE-5(%r10), %rsi // block object pointer -> arg2 + movq -3*PAGE_MAX_SIZE-5(%r10), %rsi // block object pointer -> arg2 // trampoline is -5 bytes from the return address // data is -3 pages from the trampoline ret // back to TrampolineEntry to preserve CPU's return stack -.macro TrampolineEntry_stret +.macro TrampolineEntry_stret1 // This trampoline is 8 bytes long. // This callq is 5 bytes long. callq __objc_blockTrampolineImpl_stret jmp *16(%rsi) .endmacro +.macro TrampolineEntry_stret4 + TrampolineEntry_stret1 + TrampolineEntry_stret1 + TrampolineEntry_stret1 + TrampolineEntry_stret1 +.endmacro + +#if PAGE_MAX_SHIFT == 12 +#define TrampolineEntry_stret TrampolineEntry_stret1 +#elif PAGE_MAX_SHIFT == 14 +#define TrampolineEntry_stret TrampolineEntry_stret4 +#else +#error "unknown PAGE_MAX_SHIFT value" +#endif + .align 5 __objc_blockTrampolineStart_stret: TrampolineEntry_stret @@ -1091,7 +1139,21 @@ __objc_blockTrampolineStart_stret: TrampolineEntry_stret TrampolineEntry_stret TrampolineEntry_stret + +// See the comment on non-stret's Last for why we have additional +// entries here. +#if PAGE_MAX_SHIFT == 14 + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret + TrampolineEntry_stret1 + TrampolineEntry_stret1 + TrampolineEntry_stret1 +__objc_blockTrampolineLast_stret: + TrampolineEntry_stret1 +#else __objc_blockTrampolineLast_stret: TrampolineEntry_stret +#endif #endif diff --git a/runtime/objc-cache-old.mm b/runtime/objc-cache-old.mm index fed884a..50fbab0 100644 --- a/runtime/objc-cache-old.mm +++ b/runtime/objc-cache-old.mm @@ -1795,9 +1795,5 @@ void _class_printMethodCacheStatistics(void) #endif -void cache_init() -{ -} - // !__OBJC2__ #endif diff --git a/runtime/objc-cache.h b/runtime/objc-cache.h deleted file mode 100644 index e0448e7..0000000 --- a/runtime/objc-cache.h +++ /dev/null @@ -1,23 +0,0 @@ - -#ifndef _OBJC_CACHE_H -#define _OBJC_CACHE_H - -#include "objc-private.h" - -__BEGIN_DECLS - -extern void cache_init(void); - -extern IMP cache_getImp(Class cls, SEL sel); - -extern void cache_fill(Class cls, SEL sel, IMP imp, id receiver); - -extern void cache_erase_nolock(Class cls); - -extern void cache_delete(Class cls); - -extern void cache_collect(bool collectALot); - -__END_DECLS - -#endif diff --git a/runtime/objc-cache.mm b/runtime/objc-cache.mm index 7656391..213d147 100644 --- a/runtime/objc-cache.mm +++ b/runtime/objc-cache.mm @@ -63,14 +63,12 @@ * objc_msgSend* * cache_getImp * - * Cache writers (hold cacheUpdateLock while reading or writing; not PC-checked) - * cache_fill (acquires lock) - * cache_expand (only called from cache_fill) - * cache_create (only called from cache_expand) - * bcopy (only called from instrumented cache_expand) - * flush_caches (acquires lock) - * cache_flush (only called from cache_fill and flush_caches) - * cache_collect_free (only called from cache_expand and cache_flush) + * Cache readers/writers (hold cacheUpdateLock during access; not PC-checked) + * cache_t::copyCacheNolock (caller must hold the lock) + * cache_t::eraseNolock (caller must hold the lock) + * cache_t::collectNolock (caller must hold the lock) + * cache_t::insert (acquires lock) + * cache_t::destroy (acquires lock) * * UNPROTECTED cache readers (NOT thread-safe; used for debug info only) * cache_print @@ -84,18 +82,81 @@ #if __OBJC2__ #include "objc-private.h" -#include "objc-cache.h" +#if TARGET_OS_OSX +#include +#include +#endif + +#if __arm__ || __x86_64__ || __i386__ + +// objc_msgSend has few registers available. +// Cache scan increments and wraps at special end-marking bucket. +#define CACHE_END_MARKER 1 + +// Historical fill ratio of 75% (since the new objc runtime was introduced). +static inline mask_t cache_fill_ratio(mask_t capacity) { + return capacity * 3 / 4; +} + +#elif __arm64__ && !__LP64__ + +// objc_msgSend has lots of registers available. +// Cache scan decrements. No end marker needed. +#define CACHE_END_MARKER 0 + +// Historical fill ratio of 75% (since the new objc runtime was introduced). +static inline mask_t cache_fill_ratio(mask_t capacity) { + return capacity * 3 / 4; +} + +#elif __arm64__ && __LP64__ + +// objc_msgSend has lots of registers available. +// Cache scan decrements. No end marker needed. +#define CACHE_END_MARKER 0 + +// Allow 87.5% fill ratio in the fast path for all cache sizes. +// Increasing the cache fill ratio reduces the fragmentation and wasted space +// in imp-caches at the cost of potentially increasing the average lookup of +// a selector in imp-caches by increasing collision chains. Another potential +// change is that cache table resizes / resets happen at different moments. +static inline mask_t cache_fill_ratio(mask_t capacity) { + return capacity * 7 / 8; +} + +// Allow 100% cache utilization for smaller cache sizes. This has the same +// advantages and disadvantages as the fill ratio. A very large percentage +// of caches end up with very few entries and the worst case of collision +// chains in small tables is relatively small. +// NOTE: objc_msgSend properly handles a cache lookup with a full cache. +#define CACHE_ALLOW_FULL_UTILIZATION 1 + +#else +#error unknown architecture +#endif /* Initial cache bucket count. INIT_CACHE_SIZE must be a power of two. */ enum { +#if CACHE_END_MARKER || (__arm64__ && !__LP64__) + // When we have a cache end marker it fills a bucket slot, so having a + // initial cache size of 2 buckets would not be efficient when one of the + // slots is always filled with the end marker. So start with a cache size + // 4 buckets. INIT_CACHE_SIZE_LOG2 = 2, +#else + // Allow an initial bucket size of 2 buckets, since a large number of + // classes, especially metaclasses, have very few imps, and we support + // the ability to fill 100% of the cache before resizing. + INIT_CACHE_SIZE_LOG2 = 1, +#endif INIT_CACHE_SIZE = (1 << INIT_CACHE_SIZE_LOG2), MAX_CACHE_SIZE_LOG2 = 16, MAX_CACHE_SIZE = (1 << MAX_CACHE_SIZE_LOG2), + FULL_UTILIZATION_CACHE_SIZE_LOG2 = 3, + FULL_UTILIZATION_CACHE_SIZE = (1 << FULL_UTILIZATION_CACHE_SIZE_LOG2), }; -static void cache_collect_free(struct bucket_t *data, mask_t capacity); static int _collecting_in_critical(void); static void _garbage_make_room(void); @@ -171,25 +232,21 @@ static void recordDeadCache(mask_t capacity) #endif ); +#if CONFIG_USE_PREOPT_CACHES +__attribute__((used, section("__DATA_CONST,__objc_scoffs"))) +uintptr_t objc_opt_offsets[__OBJC_OPT_OFFSETS_COUNT]; +#endif -#if __arm__ || __x86_64__ || __i386__ -// objc_msgSend has few registers available. -// Cache scan increments and wraps at special end-marking bucket. -#define CACHE_END_MARKER 1 +#if CACHE_END_MARKER static inline mask_t cache_next(mask_t i, mask_t mask) { return (i+1) & mask; } - #elif __arm64__ -// objc_msgSend has lots of registers available. -// Cache scan decrements. No end marker needed. -#define CACHE_END_MARKER 0 static inline mask_t cache_next(mask_t i, mask_t mask) { return i ? i-1 : mask; } - #else -#error unknown architecture +#error unexpected configuration #endif @@ -249,29 +306,27 @@ __asm__ __volatile__( \ static inline mask_t cache_hash(SEL sel, mask_t mask) { - return (mask_t)(uintptr_t)sel & mask; -} - -cache_t *getCache(Class cls) -{ - ASSERT(cls); - return &cls->cache; + uintptr_t value = (uintptr_t)sel; +#if CONFIG_USE_PREOPT_CACHES + value ^= value >> 7; +#endif + return (mask_t)(value & mask); } #if __arm64__ template -void bucket_t::set(SEL newSel, IMP newImp, Class cls) +void bucket_t::set(bucket_t *base, SEL newSel, IMP newImp, Class cls) { - ASSERT(_sel.load(memory_order::memory_order_relaxed) == 0 || - _sel.load(memory_order::memory_order_relaxed) == newSel); + ASSERT(_sel.load(memory_order_relaxed) == 0 || + _sel.load(memory_order_relaxed) == newSel); static_assert(offsetof(bucket_t,_imp) == 0 && offsetof(bucket_t,_sel) == sizeof(void *), "bucket_t layout doesn't match arm64 bucket_t::set()"); uintptr_t encodedImp = (impEncoding == Encoded - ? encodeImp(newImp, newSel, cls) + ? encodeImp(base, newImp, newSel, cls) : (uintptr_t)newImp); // LDP/STP guarantees that all observers get @@ -282,10 +337,10 @@ static inline mask_t cache_hash(SEL sel, mask_t mask) #else template -void bucket_t::set(SEL newSel, IMP newImp, Class cls) +void bucket_t::set(bucket_t *base, SEL newSel, IMP newImp, Class cls) { - ASSERT(_sel.load(memory_order::memory_order_relaxed) == 0 || - _sel.load(memory_order::memory_order_relaxed) == newSel); + ASSERT(_sel.load(memory_order_relaxed) == 0 || + _sel.load(memory_order_relaxed) == newSel); // objc_msgSend uses sel and imp with no locks. // It is safe for objc_msgSend to see new imp but NULL sel @@ -294,29 +349,195 @@ static inline mask_t cache_hash(SEL sel, mask_t mask) // Therefore we write new imp, wait a lot, then write new sel. uintptr_t newIMP = (impEncoding == Encoded - ? encodeImp(newImp, newSel, cls) + ? encodeImp(base, newImp, newSel, cls) : (uintptr_t)newImp); if (atomicity == Atomic) { - _imp.store(newIMP, memory_order::memory_order_relaxed); + _imp.store(newIMP, memory_order_relaxed); - if (_sel.load(memory_order::memory_order_relaxed) != newSel) { + if (_sel.load(memory_order_relaxed) != newSel) { #ifdef __arm__ mega_barrier(); - _sel.store(newSel, memory_order::memory_order_relaxed); + _sel.store(newSel, memory_order_relaxed); #elif __x86_64__ || __i386__ - _sel.store(newSel, memory_order::memory_order_release); + _sel.store(newSel, memory_order_release); #else #error Don't know how to do bucket_t::set on this architecture. #endif } } else { - _imp.store(newIMP, memory_order::memory_order_relaxed); - _sel.store(newSel, memory_order::memory_order_relaxed); + _imp.store(newIMP, memory_order_relaxed); + _sel.store(newSel, memory_order_relaxed); + } +} + +#endif + +void cache_t::initializeToEmpty() +{ + _bucketsAndMaybeMask.store((uintptr_t)&_objc_empty_cache, std::memory_order_relaxed); + _originalPreoptCache.store(nullptr, std::memory_order_relaxed); +} + +#if CONFIG_USE_PREOPT_CACHES +/* + * The shared cache builder will sometimes have prebuilt an IMP cache + * for the class and left a `preopt_cache_t` pointer in _originalPreoptCache. + * + * However we have this tension: + * - when the class is realized it has to have a cache that can't resolve any + * selector until the class is properly initialized so that every + * caller falls in the slowpath and synchronizes with the class initializing, + * - we need to remember that cache pointer and we have no space for that. + * + * The caches are designed so that preopt_cache::bit_one is set to 1, + * so we "disguise" the pointer so that it looks like a cache of capacity 1 + * where that bit one aliases with where the top bit of a SEL in the bucket_t + * would live: + * + * +----------------+----------------+ + * | IMP | SEL | << a bucket_t + * +----------------+----------------+--------------... + * preopt_cache_t >>| 1| ... + * +----------------+--------------... + * + * The shared cache guarantees that there's valid memory to read under "IMP" + * + * This lets us encode the original preoptimized cache pointer during + * initialization, and we can reconstruct its original address and install + * it back later. + */ +void cache_t::initializeToPreoptCacheInDisguise(const preopt_cache_t *cache) +{ + // preopt_cache_t::bit_one is 1 which sets the top bit + // and is never set on any valid selector + + uintptr_t value = (uintptr_t)cache + sizeof(preopt_cache_t) - + (bucket_t::offsetOfSel() + sizeof(SEL)); + + _originalPreoptCache.store(nullptr, std::memory_order_relaxed); + setBucketsAndMask((bucket_t *)value, 0); + _occupied = cache->occupied; +} + +void cache_t::maybeConvertToPreoptimized() +{ + const preopt_cache_t *cache = disguised_preopt_cache(); + + if (cache == nil) { + return; } + + if (!cls()->allowsPreoptCaches() || + (cache->has_inlines && !cls()->allowsPreoptInlinedSels())) { + if (PrintCaches) { + _objc_inform("CACHES: %sclass %s: dropping cache (from %s)", + cls()->isMetaClass() ? "meta" : "", + cls()->nameForLogging(), "setInitialized"); + } + return setBucketsAndMask(emptyBuckets(), 0); + } + + uintptr_t value = (uintptr_t)&cache->entries; +#if __has_feature(ptrauth_calls) + value = (uintptr_t)ptrauth_sign_unauthenticated((void *)value, + ptrauth_key_process_dependent_data, (uintptr_t)cls()); +#endif + value |= preoptBucketsHashParams(cache) | preoptBucketsMarker; + _bucketsAndMaybeMask.store(value, memory_order_relaxed); + _occupied = cache->occupied; +} + +void cache_t::initializeToEmptyOrPreoptimizedInDisguise() +{ + if (os_fastpath(!DisablePreoptCaches)) { + if (!objc::dataSegmentsRanges.inSharedCache((uintptr_t)this)) { + if (dyld_shared_cache_some_image_overridden()) { + // If the system has roots, then we must disable preoptimized + // caches completely. If a class in another image has a + // superclass in the root, the offset to the superclass will + // be wrong. rdar://problem/61601961 + cls()->setDisallowPreoptCachesRecursively("roots"); + } + return initializeToEmpty(); + } + + auto cache = _originalPreoptCache.load(memory_order_relaxed); + if (cache) { + return initializeToPreoptCacheInDisguise(cache); + } + } + + return initializeToEmpty(); } +const preopt_cache_t *cache_t::preopt_cache() const +{ + auto addr = _bucketsAndMaybeMask.load(memory_order_relaxed); + addr &= preoptBucketsMask; +#if __has_feature(ptrauth_calls) +#if __BUILDING_OBJCDT__ + addr = (uintptr_t)ptrauth_strip((preopt_cache_entry_t *)addr, + ptrauth_key_process_dependent_data); +#else + addr = (uintptr_t)ptrauth_auth_data((preopt_cache_entry_t *)addr, + ptrauth_key_process_dependent_data, (uintptr_t)cls()); #endif +#endif + return (preopt_cache_t *)(addr - sizeof(preopt_cache_t)); +} + +const preopt_cache_t *cache_t::disguised_preopt_cache() const +{ + bucket_t *b = buckets(); + if ((intptr_t)b->sel() >= 0) return nil; + + uintptr_t value = (uintptr_t)b + bucket_t::offsetOfSel() + sizeof(SEL); + return (preopt_cache_t *)(value - sizeof(preopt_cache_t)); +} + +Class cache_t::preoptFallbackClass() const +{ + return (Class)((uintptr_t)cls() + preopt_cache()->fallback_class_offset); +} + +bool cache_t::isConstantOptimizedCache(bool strict, uintptr_t empty_addr) const +{ + uintptr_t addr = _bucketsAndMaybeMask.load(memory_order_relaxed); + if (addr & preoptBucketsMarker) { + return true; + } + if (strict) { + return false; + } + return mask() == 0 && addr != empty_addr; +} + +bool cache_t::shouldFlush(SEL sel, IMP imp) const +{ + // This test isn't backwards: disguised caches aren't "strict" + // constant optimized caches + if (!isConstantOptimizedCache(/*strict*/true)) { + const preopt_cache_t *cache = disguised_preopt_cache(); + if (cache) { + uintptr_t offs = (uintptr_t)sel - (uintptr_t)@selector(🤯); + uintptr_t slot = ((offs >> cache->shift) & cache->mask); + auto &entry = cache->entries[slot]; + + return entry.sel_offs == offs && + (uintptr_t)cls() - entry.imp_offs == + (uintptr_t)ptrauth_strip(imp, ptrauth_key_function_pointer); + } + } + + return cache_getImp(cls(), sel) == imp; +} + +bool cache_t::isConstantOptimizedCacheWithInlinedSels() const +{ + return isConstantOptimizedCache(/* strict */true) && preopt_cache()->has_inlines; +} +#endif // CONFIG_USE_PREOPT_CACHES #if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_OUTLINED @@ -333,135 +554,85 @@ static inline mask_t cache_hash(SEL sel, mask_t mask) // ensure other threads see buckets contents before buckets pointer mega_barrier(); - _buckets.store(newBuckets, memory_order::memory_order_relaxed); - + _bucketsAndMaybeMask.store((uintptr_t)newBuckets, memory_order_relaxed); + // ensure other threads see new buckets before new mask mega_barrier(); - - _mask.store(newMask, memory_order::memory_order_relaxed); + + _maybeMask.store(newMask, memory_order_relaxed); _occupied = 0; #elif __x86_64__ || i386 // ensure other threads see buckets contents before buckets pointer - _buckets.store(newBuckets, memory_order::memory_order_release); - + _bucketsAndMaybeMask.store((uintptr_t)newBuckets, memory_order_release); + // ensure other threads see new buckets before new mask - _mask.store(newMask, memory_order::memory_order_release); + _maybeMask.store(newMask, memory_order_release); _occupied = 0; #else #error Don't know how to do setBucketsAndMask on this architecture. #endif } -struct bucket_t *cache_t::emptyBuckets() -{ - return (bucket_t *)&_objc_empty_cache; -} - -struct bucket_t *cache_t::buckets() +mask_t cache_t::mask() const { - return _buckets.load(memory_order::memory_order_relaxed); + return _maybeMask.load(memory_order_relaxed); } -mask_t cache_t::mask() -{ - return _mask.load(memory_order::memory_order_relaxed); -} - -void cache_t::initializeToEmpty() -{ - bzero(this, sizeof(*this)); - _buckets.store((bucket_t *)&_objc_empty_cache, memory_order::memory_order_relaxed); -} - -#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16 +#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16 || CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16_BIG_ADDRS void cache_t::setBucketsAndMask(struct bucket_t *newBuckets, mask_t newMask) { uintptr_t buckets = (uintptr_t)newBuckets; uintptr_t mask = (uintptr_t)newMask; - + ASSERT(buckets <= bucketsMask); ASSERT(mask <= maxMask); - - _maskAndBuckets.store(((uintptr_t)newMask << maskShift) | (uintptr_t)newBuckets, std::memory_order_relaxed); - _occupied = 0; -} -struct bucket_t *cache_t::emptyBuckets() -{ - return (bucket_t *)&_objc_empty_cache; -} - -struct bucket_t *cache_t::buckets() -{ - uintptr_t maskAndBuckets = _maskAndBuckets.load(memory_order::memory_order_relaxed); - return (bucket_t *)(maskAndBuckets & bucketsMask); + _bucketsAndMaybeMask.store(((uintptr_t)newMask << maskShift) | (uintptr_t)newBuckets, memory_order_relaxed); + _occupied = 0; } -mask_t cache_t::mask() +mask_t cache_t::mask() const { - uintptr_t maskAndBuckets = _maskAndBuckets.load(memory_order::memory_order_relaxed); + uintptr_t maskAndBuckets = _bucketsAndMaybeMask.load(memory_order_relaxed); return maskAndBuckets >> maskShift; } -void cache_t::initializeToEmpty() -{ - bzero(this, sizeof(*this)); - _maskAndBuckets.store((uintptr_t)&_objc_empty_cache, std::memory_order_relaxed); -} - #elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_LOW_4 void cache_t::setBucketsAndMask(struct bucket_t *newBuckets, mask_t newMask) { uintptr_t buckets = (uintptr_t)newBuckets; unsigned mask = (unsigned)newMask; - + ASSERT(buckets == (buckets & bucketsMask)); ASSERT(mask <= 0xffff); - - // The shift amount is equal to the number of leading zeroes in - // the last 16 bits of mask. Count all the leading zeroes, then - // subtract to ignore the top half. - uintptr_t maskShift = __builtin_clz(mask) - (sizeof(mask) * CHAR_BIT - 16); - ASSERT(mask == (0xffff >> maskShift)); - - _maskAndBuckets.store(buckets | maskShift, memory_order::memory_order_relaxed); + + _bucketsAndMaybeMask.store(buckets | objc::mask16ShiftBits(mask), memory_order_relaxed); _occupied = 0; - + ASSERT(this->buckets() == newBuckets); ASSERT(this->mask() == newMask); } -struct bucket_t *cache_t::emptyBuckets() +mask_t cache_t::mask() const { - return (bucket_t *)((uintptr_t)&_objc_empty_cache & bucketsMask); -} - -struct bucket_t *cache_t::buckets() -{ - uintptr_t maskAndBuckets = _maskAndBuckets.load(memory_order::memory_order_relaxed); - return (bucket_t *)(maskAndBuckets & bucketsMask); -} - -mask_t cache_t::mask() -{ - uintptr_t maskAndBuckets = _maskAndBuckets.load(memory_order::memory_order_relaxed); + uintptr_t maskAndBuckets = _bucketsAndMaybeMask.load(memory_order_relaxed); uintptr_t maskShift = (maskAndBuckets & maskMask); return 0xffff >> maskShift; } -void cache_t::initializeToEmpty() -{ - bzero(this, sizeof(*this)); - _maskAndBuckets.store((uintptr_t)&_objc_empty_cache, std::memory_order_relaxed); -} - #else #error Unknown cache mask storage type. #endif -mask_t cache_t::occupied() +struct bucket_t *cache_t::buckets() const +{ + uintptr_t addr = _bucketsAndMaybeMask.load(memory_order_relaxed); + return (bucket_t *)(addr & bucketsMask); +} + +mask_t cache_t::occupied() const { return _occupied; } @@ -471,11 +642,15 @@ static inline mask_t cache_hash(SEL sel, mask_t mask) _occupied++; } -unsigned cache_t::capacity() +unsigned cache_t::capacity() const { return mask() ? mask()+1 : 0; } +Class cache_t::cls() const +{ + return (Class)((uintptr_t)this - offsetof(objc_class, cache)); +} size_t cache_t::bytesForCapacity(uint32_t cap) { @@ -489,22 +664,21 @@ static inline mask_t cache_hash(SEL sel, mask_t mask) return (bucket_t *)((uintptr_t)b + bytesForCapacity(cap)) - 1; } -bucket_t *allocateBuckets(mask_t newCapacity) +bucket_t *cache_t::allocateBuckets(mask_t newCapacity) { // Allocate one extra bucket to mark the end of the list. // This can't overflow mask_t because newCapacity is a power of 2. - bucket_t *newBuckets = (bucket_t *) - calloc(cache_t::bytesForCapacity(newCapacity), 1); + bucket_t *newBuckets = (bucket_t *)calloc(bytesForCapacity(newCapacity), 1); - bucket_t *end = cache_t::endMarker(newBuckets, newCapacity); + bucket_t *end = endMarker(newBuckets, newCapacity); #if __arm__ // End marker's sel is 1 and imp points BEFORE the first bucket. // This saves an instruction in objc_msgSend. - end->set((SEL)(uintptr_t)1, (IMP)(newBuckets - 1), nil); + end->set(newBuckets, (SEL)(uintptr_t)1, (IMP)(newBuckets - 1), nil); #else // End marker's sel is 1 and imp points to the first bucket. - end->set((SEL)(uintptr_t)1, (IMP)newBuckets, nil); + end->set(newBuckets, (SEL)(uintptr_t)1, (IMP)newBuckets, nil); #endif if (PrintCaches) recordNewCache(newCapacity); @@ -514,17 +688,21 @@ static inline mask_t cache_hash(SEL sel, mask_t mask) #else -bucket_t *allocateBuckets(mask_t newCapacity) +bucket_t *cache_t::allocateBuckets(mask_t newCapacity) { if (PrintCaches) recordNewCache(newCapacity); - return (bucket_t *)calloc(cache_t::bytesForCapacity(newCapacity), 1); + return (bucket_t *)calloc(bytesForCapacity(newCapacity), 1); } #endif +struct bucket_t *cache_t::emptyBuckets() +{ + return (bucket_t *)((uintptr_t)&_objc_empty_cache & bucketsMask); +} -bucket_t *emptyBucketsForCapacity(mask_t capacity, bool allocate = true) +bucket_t *cache_t::emptyBucketsForCapacity(mask_t capacity, bool allocate) { #if CONFIG_USE_CACHE_LOCK cacheUpdateLock.assertLocked(); @@ -532,11 +710,11 @@ static inline mask_t cache_hash(SEL sel, mask_t mask) runtimeLock.assertLocked(); #endif - size_t bytes = cache_t::bytesForCapacity(capacity); + size_t bytes = bytesForCapacity(capacity); // Use _objc_empty_cache if the buckets is small enough. if (bytes <= EMPTY_BYTES) { - return cache_t::emptyBuckets(); + return emptyBuckets(); } // Use shared empty buckets allocated on the heap. @@ -568,17 +746,16 @@ static inline mask_t cache_hash(SEL sel, mask_t mask) return emptyBucketsList[index]; } - -bool cache_t::isConstantEmptyCache() +bool cache_t::isConstantEmptyCache() const { - return - occupied() == 0 && + return + occupied() == 0 && buckets() == emptyBucketsForCapacity(capacity(), false); } -bool cache_t::canBeFreed() +bool cache_t::canBeFreed() const { - return !isConstantEmptyCache(); + return !isConstantEmptyCache() && !isConstantOptimizedCache(); } ALWAYS_INLINE @@ -597,68 +774,79 @@ static inline mask_t cache_hash(SEL sel, mask_t mask) setBucketsAndMask(newBuckets, newCapacity - 1); if (freeOld) { - cache_collect_free(oldBuckets, oldCapacity); + collect_free(oldBuckets, oldCapacity); } } -void cache_t::bad_cache(id receiver, SEL sel, Class isa) +void cache_t::bad_cache(id receiver, SEL sel) { // Log in separate steps in case the logging itself causes a crash. _objc_inform_now_and_on_crash ("Method cache corrupted. This may be a message to an " "invalid object, or a memory error somewhere else."); - cache_t *cache = &isa->cache; #if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_OUTLINED - bucket_t *buckets = cache->_buckets.load(memory_order::memory_order_relaxed); + bucket_t *b = buckets(); _objc_inform_now_and_on_crash ("%s %p, SEL %p, isa %p, cache %p, buckets %p, " "mask 0x%x, occupied 0x%x", receiver ? "receiver" : "unused", receiver, - sel, isa, cache, buckets, - cache->_mask.load(memory_order::memory_order_relaxed), - cache->_occupied); + sel, cls(), this, b, + _maybeMask.load(memory_order_relaxed), + _occupied); _objc_inform_now_and_on_crash ("%s %zu bytes, buckets %zu bytes", receiver ? "receiver" : "unused", malloc_size(receiver), - malloc_size(buckets)); + malloc_size(b)); #elif (CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16 || \ + CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16_BIG_ADDRS || \ CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_LOW_4) - uintptr_t maskAndBuckets = cache->_maskAndBuckets.load(memory_order::memory_order_relaxed); + uintptr_t maskAndBuckets = _bucketsAndMaybeMask.load(memory_order_relaxed); _objc_inform_now_and_on_crash ("%s %p, SEL %p, isa %p, cache %p, buckets and mask 0x%lx, " "occupied 0x%x", receiver ? "receiver" : "unused", receiver, - sel, isa, cache, maskAndBuckets, - cache->_occupied); + sel, cls(), this, maskAndBuckets, _occupied); _objc_inform_now_and_on_crash ("%s %zu bytes, buckets %zu bytes", receiver ? "receiver" : "unused", malloc_size(receiver), - malloc_size(cache->buckets())); + malloc_size(buckets())); #else #error Unknown cache mask storage type. #endif _objc_inform_now_and_on_crash ("selector '%s'", sel_getName(sel)); _objc_inform_now_and_on_crash - ("isa '%s'", isa->nameForLogging()); + ("isa '%s'", cls()->nameForLogging()); _objc_fatal ("Method cache corrupted. This may be a message to an " "invalid object, or a memory error somewhere else."); } -ALWAYS_INLINE -void cache_t::insert(Class cls, SEL sel, IMP imp, id receiver) +void cache_t::insert(SEL sel, IMP imp, id receiver) { -#if CONFIG_USE_CACHE_LOCK - cacheUpdateLock.assertLocked(); -#else runtimeLock.assertLocked(); + + // Never cache before +initialize is done + if (slowpath(!cls()->isInitialized())) { + return; + } + + if (isConstantOptimizedCache()) { + _objc_fatal("cache_t::insert() called with a preoptimized cache for %s", + cls()->nameForLogging()); + } + +#if DEBUG_TASK_THREADS + return _collecting_in_critical(); +#else +#if CONFIG_USE_CACHE_LOCK + mutex_locker_t lock(cacheUpdateLock); #endif - ASSERT(sel != 0 && cls->isInitialized()); + ASSERT(sel != 0 && cls()->isInitialized()); - // Use the cache as-is if it is less than 3/4 full + // Use the cache as-is if until we exceed our expected fill ratio. mask_t newOccupied = occupied() + 1; unsigned oldCapacity = capacity(), capacity = oldCapacity; if (slowpath(isConstantEmptyCache())) { @@ -666,9 +854,14 @@ static inline mask_t cache_hash(SEL sel, mask_t mask) if (!capacity) capacity = INIT_CACHE_SIZE; reallocate(oldCapacity, capacity, /* freeOld */false); } - else if (fastpath(newOccupied + CACHE_END_MARKER <= capacity / 4 * 3)) { - // Cache is less than 3/4 full. Use it as-is. + else if (fastpath(newOccupied + CACHE_END_MARKER <= cache_fill_ratio(capacity))) { + // Cache is less than 3/4 or 7/8 full. Use it as-is. + } +#if CACHE_ALLOW_FULL_UTILIZATION + else if (capacity <= FULL_UTILIZATION_CACHE_SIZE && newOccupied + CACHE_END_MARKER <= capacity) { + // Allow 100% cache utilization for small buckets. Use it as-is. } +#endif else { capacity = capacity ? capacity * 2 : INIT_CACHE_SIZE; if (capacity > MAX_CACHE_SIZE) { @@ -683,12 +876,11 @@ static inline mask_t cache_hash(SEL sel, mask_t mask) mask_t i = begin; // Scan for the first unused slot and insert there. - // There is guaranteed to be an empty slot because the - // minimum size is 4 and we resized at 3/4 full. + // There is guaranteed to be an empty slot. do { if (fastpath(b[i].sel() == 0)) { incrementOccupied(); - b[i].set(sel, imp, cls); + b[i].set(b, sel, imp, cls()); return; } if (b[i].sel() == sel) { @@ -698,31 +890,54 @@ static inline mask_t cache_hash(SEL sel, mask_t mask) } } while (fastpath((i = cache_next(i, m)) != begin)); - cache_t::bad_cache(receiver, (SEL)sel, cls); + bad_cache(receiver, (SEL)sel); +#endif // !DEBUG_TASK_THREADS } -void cache_fill(Class cls, SEL sel, IMP imp, id receiver) +void cache_t::copyCacheNolock(objc_imp_cache_entry *buffer, int len) { - runtimeLock.assertLocked(); - -#if !DEBUG_TASK_THREADS - // Never cache before +initialize is done - if (cls->isInitialized()) { - cache_t *cache = getCache(cls); #if CONFIG_USE_CACHE_LOCK - mutex_locker_t lock(cacheUpdateLock); + cacheUpdateLock.assertLocked(); +#else + runtimeLock.assertLocked(); #endif - cache->insert(cls, sel, imp, receiver); + int wpos = 0; + +#if CONFIG_USE_PREOPT_CACHES + if (isConstantOptimizedCache()) { + auto cache = preopt_cache(); + auto mask = cache->mask; + uintptr_t sel_base = objc_opt_offsets[OBJC_OPT_METHODNAME_START]; + uintptr_t imp_base = (uintptr_t)&cache->entries; + + for (uintptr_t index = 0; index <= mask && wpos < len; index++) { + auto &ent = cache->entries[index]; + if (~ent.sel_offs) { + buffer[wpos].sel = (SEL)(sel_base + ent.sel_offs); + buffer[wpos].imp = (IMP)(imp_base - ent.imp_offs); + wpos++; + } + } + return; } -#else - _collecting_in_critical(); #endif + { + bucket_t *buckets = this->buckets(); + uintptr_t count = capacity(); + + for (uintptr_t index = 0; index < count && wpos < len; index++) { + if (buckets[index].sel()) { + buffer[wpos].imp = buckets[index].imp(buckets, cls()); + buffer[wpos].sel = buckets[index].sel(); + wpos++; + } + } + } } - // Reset this entire cache to the uncached lookup by reallocating it. // This must not shrink the cache - that breaks the lock-free scheme. -void cache_erase_nolock(Class cls) +void cache_t::eraseNolock(const char *func) { #if CONFIG_USE_CACHE_LOCK cacheUpdateLock.assertLocked(); @@ -730,29 +945,36 @@ void cache_erase_nolock(Class cls) runtimeLock.assertLocked(); #endif - cache_t *cache = getCache(cls); - - mask_t capacity = cache->capacity(); - if (capacity > 0 && cache->occupied() > 0) { - auto oldBuckets = cache->buckets(); + if (isConstantOptimizedCache()) { + auto c = cls(); + if (PrintCaches) { + _objc_inform("CACHES: %sclass %s: dropping and disallowing preopt cache (from %s)", + c->isMetaClass() ? "meta" : "", + c->nameForLogging(), func); + } + setBucketsAndMask(emptyBuckets(), 0); + c->setDisallowPreoptCaches(); + } else if (occupied() > 0) { + auto capacity = this->capacity(); + auto oldBuckets = buckets(); auto buckets = emptyBucketsForCapacity(capacity); - cache->setBucketsAndMask(buckets, capacity - 1); // also clears occupied - cache_collect_free(oldBuckets, capacity); + setBucketsAndMask(buckets, capacity - 1); // also clears occupied + collect_free(oldBuckets, capacity); } } -void cache_delete(Class cls) +void cache_t::destroy() { #if CONFIG_USE_CACHE_LOCK mutex_locker_t lock(cacheUpdateLock); #else runtimeLock.assertLocked(); #endif - if (cls->cache.canBeFreed()) { - if (PrintCaches) recordDeadCache(cls->cache.capacity()); - free(cls->cache.buckets()); + if (canBeFreed()) { + if (PrintCaches) recordDeadCache(capacity()); + free(buckets()); } } @@ -829,7 +1051,7 @@ static uintptr_t _get_pc_for_thread(thread_t thread) static bool shouldUseRestartableRanges = true; #endif -void cache_init() +void cache_t::init() { #if HAVE_TASK_RESTARTABLE_RANGES mach_msg_type_number_t count = 0; @@ -895,7 +1117,18 @@ static int _collecting_in_critical(void) continue; // Find out where thread is executing +#if TARGET_OS_OSX + if (oah_is_current_process_translated()) { + kern_return_t ret = objc_thread_get_rip(threads[count], (uint64_t*)&pc); + if (ret != KERN_SUCCESS) { + pc = PC_SENTINEL; + } + } else { + pc = _get_pc_for_thread (threads[count]); + } +#else pc = _get_pc_for_thread (threads[count]); +#endif // Check for bad status, and if so, assume the worse (can't collect) if (pc == PC_SENTINEL) @@ -980,13 +1213,13 @@ static void _garbage_make_room(void) /*********************************************************************** -* cache_collect_free. Add the specified malloc'd memory to the list +* cache_t::collect_free. Add the specified malloc'd memory to the list * of them to free at some later point. * size is used for the collection threshold. It does not have to be * precisely the block's size. * Cache locks: cacheUpdateLock must be held by the caller. **********************************************************************/ -static void cache_collect_free(bucket_t *data, mask_t capacity) +void cache_t::collect_free(bucket_t *data, mask_t capacity) { #if CONFIG_USE_CACHE_LOCK cacheUpdateLock.assertLocked(); @@ -999,7 +1232,7 @@ static void cache_collect_free(bucket_t *data, mask_t capacity) _garbage_make_room (); garbage_byte_size += cache_t::bytesForCapacity(capacity); garbage_refs[garbage_count++] = data; - cache_collect(false); + cache_t::collectNolock(false); } @@ -1008,7 +1241,7 @@ static void cache_collect_free(bucket_t *data, mask_t capacity) * collectALot tries harder to free memory. * Cache locks: cacheUpdateLock must be held by the caller. **********************************************************************/ -void cache_collect(bool collectALot) +void cache_t::collectNolock(bool collectALot) { #if CONFIG_USE_CACHE_LOCK cacheUpdateLock.assertLocked(); @@ -1305,6 +1538,41 @@ mig_internal kern_return_t __MIG_check__Reply__task_threads_t(__Reply__task_thre // DEBUG_TASK_THREADS #endif +OBJC_EXPORT bucket_t * objc_cache_buckets(const cache_t * cache) { + return cache->buckets(); +} + +#if CONFIG_USE_PREOPT_CACHES + +OBJC_EXPORT const preopt_cache_t * _Nonnull objc_cache_preoptCache(const cache_t * _Nonnull cache) { + return cache->preopt_cache(); +} + +OBJC_EXPORT bool objc_cache_isConstantOptimizedCache(const cache_t * _Nonnull cache, bool strict, uintptr_t empty_addr) { + return cache->isConstantOptimizedCache(strict, empty_addr); +} + +OBJC_EXPORT unsigned objc_cache_preoptCapacity(const cache_t * _Nonnull cache) { + return cache->preopt_cache()->capacity(); +} + +OBJC_EXPORT Class _Nonnull objc_cache_preoptFallbackClass(const cache_t * _Nonnull cache) { + return cache->preoptFallbackClass(); +} + +#endif + +OBJC_EXPORT size_t objc_cache_bytesForCapacity(uint32_t cap) { + return cache_t::bytesForCapacity(cap); +} + +OBJC_EXPORT uint32_t objc_cache_occupied(const cache_t * _Nonnull cache) { + return cache->occupied(); +} + +OBJC_EXPORT unsigned objc_cache_capacity(const struct cache_t * _Nonnull cache) { + return cache->capacity(); +} // __OBJC2__ #endif diff --git a/runtime/objc-class-old.mm b/runtime/objc-class-old.mm index acc269e..c0a79a7 100644 --- a/runtime/objc-class-old.mm +++ b/runtime/objc-class-old.mm @@ -336,7 +336,7 @@ static void _class_resolveClassMethod(id inst, SEL sel, Class cls) ASSERT(cls->isMetaClass()); SEL resolve_sel = @selector(resolveClassMethod:); - if (!lookUpImpOrNil(inst, resolve_sel, cls)) { + if (!lookUpImpOrNilTryCache(inst, resolve_sel, cls)) { // Resolver not implemented. return; } @@ -346,7 +346,7 @@ static void _class_resolveClassMethod(id inst, SEL sel, Class cls) // Cache the result (good or bad) so the resolver doesn't fire next time. // +resolveClassMethod adds to self->ISA() a.k.a. cls - IMP imp = lookUpImpOrNil(inst, sel, cls); + IMP imp = lookUpImpOrNilTryCache(inst, sel, cls); if (resolved && PrintResolving) { if (imp) { _objc_inform("RESOLVE: method %c[%s %s] " @@ -376,7 +376,7 @@ static void _class_resolveInstanceMethod(id inst, SEL sel, Class cls) { SEL resolve_sel = @selector(resolveInstanceMethod:); - if (! lookUpImpOrNil(cls, resolve_sel, cls->ISA())) { + if (! lookUpImpOrNilTryCache(cls, resolve_sel, cls->ISA())) { // Resolver not implemented. return; } @@ -386,7 +386,7 @@ static void _class_resolveInstanceMethod(id inst, SEL sel, Class cls) // Cache the result (good or bad) so the resolver doesn't fire next time. // +resolveInstanceMethod adds to self a.k.a. cls - IMP imp = lookUpImpOrNil(inst, sel, cls); + IMP imp = lookUpImpOrNilTryCache(inst, sel, cls); if (resolved && PrintResolving) { if (imp) { @@ -424,7 +424,7 @@ static void _class_resolveInstanceMethod(id inst, SEL sel, Class cls) // try [nonMetaClass resolveClassMethod:sel] // and [cls resolveInstanceMethod:sel] _class_resolveClassMethod(inst, sel, cls); - if (!lookUpImpOrNil(inst, sel, cls)) { + if (!lookUpImpOrNilTryCache(inst, sel, cls)) { _class_resolveInstanceMethod(inst, sel, cls); } } @@ -2593,8 +2593,7 @@ id object_reallocFromZone(id obj, size_t nBytes, void *z) void *object_getIndexedIvars(id obj) { // ivars are tacked onto the end of the object - if (!obj) return nil; - if (obj->isTaggedPointer()) return nil; + if (obj->isTaggedPointerOrNil()) return nil; return ((char *) obj) + obj->ISA()->alignedInstanceSize(); } diff --git a/runtime/objc-class.mm b/runtime/objc-class.mm index 776f3fa..13ea069 100644 --- a/runtime/objc-class.mm +++ b/runtime/objc-class.mm @@ -159,6 +159,9 @@ #include "objc-private.h" #include "objc-abi.h" #include +#if !TARGET_OS_WIN32 +#include +#endif /*********************************************************************** * Information about multi-thread support: @@ -195,9 +198,9 @@ Class object_setClass(id obj, Class cls) // weakly-referenced object has an un-+initialized isa. // Unresolved future classes are not so protected. if (!cls->isFuture() && !cls->isInitialized()) { - // use lookUpImpOrNil to indirectly provoke +initialize + // use lookUpImpOrNilTryCache to indirectly provoke +initialize // to avoid duplicating the code to actually send +initialize - lookUpImpOrNil(nil, @selector(initialize), cls, LOOKUP_INITIALIZE); + lookUpImpOrNilTryCache(nil, @selector(initialize), cls, LOOKUP_INITIALIZE); } return obj->changeIsa(cls); @@ -281,7 +284,7 @@ static bool isScanned(ptrdiff_t ivar_offset, const uint8_t *layout) // Preflight the hasAutomaticIvars check // because _class_getClassForIvar() may need to take locks. bool hasAutomaticIvars = NO; - for (Class c = cls; c; c = c->superclass) { + for (Class c = cls; c; c = c->getSuperclass()) { if (c->hasAutomaticIvars()) { hasAutomaticIvars = YES; break; @@ -337,7 +340,7 @@ static bool isScanned(ptrdiff_t ivar_offset, const uint8_t *layout) static ALWAYS_INLINE void _object_setIvar(id obj, Ivar ivar, id value, bool assumeStrong) { - if (!obj || !ivar || obj->isTaggedPointer()) return; + if (!ivar || obj->isTaggedPointerOrNil()) return; ptrdiff_t offset; objc_ivar_memory_management_t memoryManagement; @@ -371,7 +374,7 @@ void object_setIvarWithStrongDefault(id obj, Ivar ivar, id value) id object_getIvar(id obj, Ivar ivar) { - if (!obj || !ivar || obj->isTaggedPointer()) return nil; + if (!ivar || obj->isTaggedPointerOrNil()) return nil; ptrdiff_t offset; objc_ivar_memory_management_t memoryManagement; @@ -393,7 +396,7 @@ Ivar _object_setInstanceVariable(id obj, const char *name, void *value, { Ivar ivar = nil; - if (obj && name && !obj->isTaggedPointer()) { + if (name && !obj->isTaggedPointerOrNil()) { if ((ivar = _class_getVariable(obj->ISA(), name))) { _object_setIvar(obj, ivar, (id)value, assumeStrong); } @@ -415,7 +418,7 @@ Ivar object_setInstanceVariableWithStrongDefault(id obj, const char *name, Ivar object_getInstanceVariable(id obj, const char *name, void **value) { - if (obj && name && !obj->isTaggedPointer()) { + if (name && !obj->isTaggedPointerOrNil()) { Ivar ivar; if ((ivar = class_getInstanceVariable(obj->ISA(), name))) { if (value) *value = (void *)object_getIvar(obj, ivar); @@ -440,7 +443,7 @@ static void object_cxxDestructFromClass(id obj, Class cls) // Call cls's dtor first, then superclasses's dtors. - for ( ; cls; cls = cls->superclass) { + for ( ; cls; cls = cls->getSuperclass()) { if (!cls->hasCxxDtor()) return; dtor = (void(*)(id)) lookupMethodInClassAndLoadCache(cls, SEL_cxx_destruct); @@ -462,8 +465,7 @@ static void object_cxxDestructFromClass(id obj, Class cls) **********************************************************************/ void object_cxxDestruct(id obj) { - if (!obj) return; - if (obj->isTaggedPointer()) return; + if (obj->isTaggedPointerOrNil()) return; object_cxxDestructFromClass(obj, obj->ISA()); } @@ -491,7 +493,7 @@ void object_cxxDestruct(id obj) id (*ctor)(id); Class supercls; - supercls = cls->superclass; + supercls = cls->getSuperclass(); // Call superclasses' ctors first, if any. if (supercls && supercls->hasCxxCtor()) { @@ -510,7 +512,7 @@ void object_cxxDestruct(id obj) } if (fastpath((*ctor)(obj))) return obj; // ctor called and succeeded - ok - supercls = cls->superclass; // this reload avoids a spill on the stack + supercls = cls->getSuperclass(); // this reload avoids a spill on the stack // This class's ctor was called and failed. // Call superclasses's dtors to clean up. @@ -530,7 +532,7 @@ void object_cxxDestruct(id obj) **********************************************************************/ void fixupCopiedIvars(id newObject, id oldObject) { - for (Class cls = oldObject->ISA(); cls; cls = cls->superclass) { + for (Class cls = oldObject->ISA(); cls; cls = cls->getSuperclass()) { if (cls->hasAutomaticIvars()) { // Use alignedInstanceStart() because unaligned bytes at the start // of this class's ivars are not represented in the layout bitmap. @@ -636,12 +638,12 @@ BOOL class_respondsToSelector(Class cls, SEL sel) // inst is an instance of cls or a subclass thereof, or nil if none is known. // Non-nil inst is faster in some cases. See lookUpImpOrForward() for details. -NEVER_INLINE BOOL +NEVER_INLINE __attribute__((flatten)) BOOL class_respondsToSelector_inst(id inst, SEL sel, Class cls) { // Avoids +initialize because it historically did so. // We're not returning a callable IMP anyway. - return sel && cls && lookUpImpOrNil(inst, sel, cls, LOOKUP_RESOLVER); + return sel && cls && lookUpImpOrNilTryCache(inst, sel, cls, LOOKUP_RESOLVER); } @@ -662,13 +664,16 @@ IMP class_lookupMethod(Class cls, SEL sel) return class_getMethodImplementation(cls, sel); } +__attribute__((flatten)) IMP class_getMethodImplementation(Class cls, SEL sel) { IMP imp; if (!cls || !sel) return nil; - imp = lookUpImpOrNil(nil, sel, cls, LOOKUP_INITIALIZE | LOOKUP_RESOLVER); + lockdebug_assert_no_locks_locked_except({ &loadMethodLock }); + + imp = lookUpImpOrNilTryCache(nil, sel, cls, LOOKUP_INITIALIZE | LOOKUP_RESOLVER); // Translate forwarding function to C-callable external version if (!imp) { @@ -775,7 +780,7 @@ Class _calloc_class(size_t size) Class class_getSuperclass(Class cls) { if (!cls) return nil; - return cls->superclass; + return cls->getSuperclass(); } BOOL class_isMetaClass(Class cls) @@ -886,6 +891,15 @@ void method_getArgumentType(Method m, unsigned int index, const header_info *newHeader = _headerForClass(newCls); const char *oldName = oldHeader ? oldHeader->fname() : "??"; const char *newName = newHeader ? newHeader->fname() : "??"; + const objc_duplicate_class **_dupi = NULL; + + LINKER_SET_FOREACH(_dupi, const objc_duplicate_class **, "__objc_dupclass") { + const objc_duplicate_class *dupi = *_dupi; + + if (strcmp(dupi->name, name) == 0) { + return; + } + } (DebugDuplicateClasses ? _objc_fatal : _objc_inform) ("Class %s is implemented in both %s (%p) and %s (%p). " diff --git a/runtime/objc-config.h b/runtime/objc-config.h index 9bc9fc1..cac827e 100644 --- a/runtime/objc-config.h +++ b/runtime/objc-config.h @@ -26,15 +26,6 @@ #include -// Define __OBJC2__ for the benefit of our asm files. -#ifndef __OBJC2__ -# if TARGET_OS_OSX && !TARGET_OS_IOSMAC && __i386__ - // old ABI -# else -# define __OBJC2__ 1 -# endif -#endif - // Avoid the !NDEBUG double negative. #if !NDEBUG # define DEBUG 1 @@ -51,7 +42,7 @@ #endif // Define SUPPORT_ZONES=1 to enable malloc zone support in NXHashTable. -#if !(TARGET_OS_OSX || TARGET_OS_IOSMAC) +#if !(TARGET_OS_OSX || TARGET_OS_MACCATALYST) # define SUPPORT_ZONES 0 #else # define SUPPORT_ZONES 1 @@ -73,7 +64,7 @@ // Define SUPPORT_TAGGED_POINTERS=1 to enable tagged pointer objects // Be sure to edit tagged pointer SPI in objc-internal.h as well. -#if !(__OBJC2__ && __LP64__) +#if !__LP64__ # define SUPPORT_TAGGED_POINTERS 0 #else # define SUPPORT_TAGGED_POINTERS 1 @@ -82,7 +73,7 @@ // Define SUPPORT_MSB_TAGGED_POINTERS to use the MSB // as the tagged pointer marker instead of the LSB. // Be sure to edit tagged pointer SPI in objc-internal.h as well. -#if !SUPPORT_TAGGED_POINTERS || (TARGET_OS_OSX || TARGET_OS_IOSMAC) +#if !SUPPORT_TAGGED_POINTERS || ((TARGET_OS_OSX || TARGET_OS_MACCATALYST) && __x86_64__) # define SUPPORT_MSB_TAGGED_POINTERS 0 #else # define SUPPORT_MSB_TAGGED_POINTERS 1 @@ -101,7 +92,7 @@ // Define SUPPORT_PACKED_ISA=1 on platforms that store the class in the isa // field as a maskable pointer with other data around it. #if (!__LP64__ || TARGET_OS_WIN32 || \ - (TARGET_OS_SIMULATOR && !TARGET_OS_IOSMAC)) + (TARGET_OS_SIMULATOR && !TARGET_OS_MACCATALYST && !__arm64__)) # define SUPPORT_PACKED_ISA 0 #else # define SUPPORT_PACKED_ISA 1 @@ -126,7 +117,7 @@ // Define SUPPORT_ZEROCOST_EXCEPTIONS to use "zero-cost" exceptions for OBJC2. // Be sure to edit objc-exception.h as well (objc_add/removeExceptionHandler) -#if !__OBJC2__ || (defined(__arm__) && __USING_SJLJ_EXCEPTIONS__) +#if defined(__arm__) && __USING_SJLJ_EXCEPTIONS__ # define SUPPORT_ZEROCOST_EXCEPTIONS 0 #else # define SUPPORT_ZEROCOST_EXCEPTIONS 1 @@ -162,6 +153,13 @@ # define SUPPORT_MESSAGE_LOGGING 1 #endif +// Define SUPPORT_AUTORELEASEPOOL_DEDDUP_PTRS to combine consecutive pointers to the same object in autorelease pools +#if !__LP64__ +# define SUPPORT_AUTORELEASEPOOL_DEDUP_PTRS 0 +#else +# define SUPPORT_AUTORELEASEPOOL_DEDUP_PTRS 1 +#endif + // Define HAVE_TASK_RESTARTABLE_RANGES to enable usage of // task_restartable_ranges_synchronize() #if TARGET_OS_SIMULATOR || defined(__i386__) || defined(__arm__) || !TARGET_OS_MAC @@ -178,16 +176,12 @@ // because objc-class.h is public and objc-config.h is not. //#define OBJC_INSTRUMENTED -// In __OBJC2__, the runtimeLock is a mutex always held -// hence the cache lock is redundant and can be elided. +// The runtimeLock is a mutex always held hence the cache lock is +// redundant and can be elided. // // If the runtime lock ever becomes a rwlock again, // the cache lock would need to be used again -#if __OBJC2__ #define CONFIG_USE_CACHE_LOCK 0 -#else -#define CONFIG_USE_CACHE_LOCK 1 -#endif // Determine how the method cache stores IMPs. #define CACHE_IMP_ENCODING_NONE 1 // Method cache contains raw IMP. @@ -208,13 +202,75 @@ #define CACHE_MASK_STORAGE_OUTLINED 1 #define CACHE_MASK_STORAGE_HIGH_16 2 #define CACHE_MASK_STORAGE_LOW_4 3 +#define CACHE_MASK_STORAGE_HIGH_16_BIG_ADDRS 4 #if defined(__arm64__) && __LP64__ +#if TARGET_OS_OSX || TARGET_OS_SIMULATOR +#define CACHE_MASK_STORAGE CACHE_MASK_STORAGE_HIGH_16_BIG_ADDRS +#else #define CACHE_MASK_STORAGE CACHE_MASK_STORAGE_HIGH_16 +#endif #elif defined(__arm64__) && !__LP64__ #define CACHE_MASK_STORAGE CACHE_MASK_STORAGE_LOW_4 #else #define CACHE_MASK_STORAGE CACHE_MASK_STORAGE_OUTLINED #endif +// Constants used for signing/authing isas. This doesn't quite belong +// here, but the asm files can't import other headers. +#define ISA_SIGNING_DISCRIMINATOR 0x6AE1 +#define ISA_SIGNING_DISCRIMINATOR_CLASS_SUPERCLASS 0xB5AB + +#define ISA_SIGNING_KEY ptrauth_key_process_independent_data + +// ISA signing authentication modes. Set ISA_SIGNING_AUTH_MODE to one +// of these to choose how ISAs are authenticated. +#define ISA_SIGNING_STRIP 1 // Strip the signature whenever reading an ISA. +#define ISA_SIGNING_AUTH 2 // Authenticate the signature on all ISAs. + + +// ISA signing modes. Set ISA_SIGNING_SIGN_MODE to one of these to +// choose how ISAs are signed. +#define ISA_SIGNING_SIGN_NONE 1 // Sign no ISAs. +#define ISA_SIGNING_SIGN_ONLY_SWIFT 2 // Only sign ISAs of Swift objects. +#define ISA_SIGNING_SIGN_ALL 3 // Sign all ISAs. + +#if __has_feature(ptrauth_objc_isa_strips) || __has_feature(ptrauth_objc_isa_signs) || __has_feature(ptrauth_objc_isa_authenticates) +# if __has_feature(ptrauth_objc_isa_authenticates) +# define ISA_SIGNING_AUTH_MODE ISA_SIGNING_AUTH +# else +# define ISA_SIGNING_AUTH_MODE ISA_SIGNING_STRIP +# endif +# if __has_feature(ptrauth_objc_isa_signs) +# define ISA_SIGNING_SIGN_MODE ISA_SIGNING_SIGN_ALL +# else +# define ISA_SIGNING_SIGN_MODE ISA_SIGNING_SIGN_NONE +# endif +#else +# if __has_feature(ptrauth_objc_isa) +# define ISA_SIGNING_AUTH_MODE ISA_SIGNING_AUTH +# define ISA_SIGNING_SIGN_MODE ISA_SIGNING_SIGN_ALL +# else +# define ISA_SIGNING_AUTH_MODE ISA_SIGNING_STRIP +# define ISA_SIGNING_SIGN_MODE ISA_SIGNING_SIGN_NONE +# endif +#endif + +// When set, an unsigned superclass pointer is treated as Nil, which +// will treat the class as if its superclass was weakly linked and +// not loaded, and cause uses of the class to resolve to Nil. +#define SUPERCLASS_SIGNING_TREAT_UNSIGNED_AS_NIL 0 + +#if defined(__arm64__) && TARGET_OS_IOS && !TARGET_OS_SIMULATOR && !TARGET_OS_MACCATALYST +#define CONFIG_USE_PREOPT_CACHES 1 +#else +#define CONFIG_USE_PREOPT_CACHES 0 +#endif + +// When set to 1, small methods in the shared cache have a direct +// offset to a selector. When set to 0, small methods in the shared +// cache have the same format as other small methods, with an offset +// to a selref. +#define CONFIG_SHARED_CACHE_RELATIVE_DIRECT_SELECTORS 1 + #endif diff --git a/runtime/objc-env.h b/runtime/objc-env.h index ccdceb6..7083b3e 100644 --- a/runtime/objc-env.h +++ b/runtime/objc-env.h @@ -36,6 +36,7 @@ OPTION( DebugMissingPools, OBJC_DEBUG_MISSING_POOLS, "warn about a OPTION( DebugPoolAllocation, OBJC_DEBUG_POOL_ALLOCATION, "halt when autorelease pools are popped out of order, and allow heap debuggers to track autorelease pools") OPTION( DebugDuplicateClasses, OBJC_DEBUG_DUPLICATE_CLASSES, "halt when multiple classes with the same name are present") OPTION( DebugDontCrash, OBJC_DEBUG_DONT_CRASH, "halt the process by exiting instead of crashing") +OPTION( DebugPoolDepth, OBJC_DEBUG_POOL_DEPTH, "log fault when at least a set number of autorelease pages has been allocated") OPTION( DisableVtables, OBJC_DISABLE_VTABLES, "disable vtable dispatch") OPTION( DisablePreopt, OBJC_DISABLE_PREOPTIMIZATION, "disable preoptimization courtesy of dyld shared cache") @@ -43,3 +44,7 @@ OPTION( DisableTaggedPointers, OBJC_DISABLE_TAGGED_POINTERS, "disable tagg OPTION( DisableTaggedPointerObfuscation, OBJC_DISABLE_TAG_OBFUSCATION, "disable obfuscation of tagged pointers") OPTION( DisableNonpointerIsa, OBJC_DISABLE_NONPOINTER_ISA, "disable non-pointer isa fields") OPTION( DisableInitializeForkSafety, OBJC_DISABLE_INITIALIZE_FORK_SAFETY, "disable safety checks for +initialize after fork") +OPTION( DisableFaults, OBJC_DISABLE_FAULTS, "disable os faults") +OPTION( DisablePreoptCaches, OBJC_DISABLE_PREOPTIMIZED_CACHES, "disable preoptimized caches") +OPTION( DisableAutoreleaseCoalescing, OBJC_DISABLE_AUTORELEASE_COALESCING, "disable coalescing of autorelease pool pointers") +OPTION( DisableAutoreleaseCoalescingLRU, OBJC_DISABLE_AUTORELEASE_COALESCING_LRU, "disable coalescing of autorelease pool pointers using look back N strategy") diff --git a/runtime/objc-exception.mm b/runtime/objc-exception.mm index 6c318c6..2b794e6 100644 --- a/runtime/objc-exception.mm +++ b/runtime/objc-exception.mm @@ -440,7 +440,7 @@ static int _objc_default_exception_matcher(Class catch_cls, id exception) Class cls; for (cls = exception->getIsa(); cls != nil; - cls = cls->superclass) + cls = cls->getSuperclass()) { if (cls == catch_cls) return 1; } diff --git a/runtime/objc-file.h b/runtime/objc-file.h index 3dc54c7..597fd3b 100644 --- a/runtime/objc-file.h +++ b/runtime/objc-file.h @@ -54,6 +54,10 @@ struct UnsignedInitializer { private: uintptr_t storage; public: + UnsignedInitializer(uint32_t offset) { + storage = (uintptr_t)&_mh_dylib_header + offset; + } + void operator () () const { using Initializer = void(*)(); Initializer init = @@ -70,6 +74,7 @@ extern category_t * const *_getObjc2CategoryList(const headerType *mhdr, size_t extern category_t * const *_getObjc2CategoryList2(const headerType *mhdr, size_t *count); extern category_t * const *_getObjc2NonlazyCategoryList(const headerType *mhdr, size_t *count); extern UnsignedInitializer *getLibobjcInitializers(const headerType *mhdr, size_t *count); +extern uint32_t *getLibobjcInitializerOffsets(const headerType *hi, size_t *count); static inline void foreach_data_segment(const headerType *mhdr, @@ -89,11 +94,12 @@ foreach_data_segment(const headerType *mhdr, seg = (const segmentType *)((char *)seg + seg->cmdsize); } - // enumerate __DATA* segments + // enumerate __DATA* and __AUTH* segments seg = (const segmentType *) (mhdr + 1); for (unsigned long i = 0; i < mhdr->ncmds; i++) { if (seg->cmd == SEGMENT_CMD && - segnameStartsWith(seg->segname, "__DATA")) + (segnameStartsWith(seg->segname, "__DATA") || + segnameStartsWith(seg->segname, "__AUTH"))) { code(seg, slide); } diff --git a/runtime/objc-file.mm b/runtime/objc-file.mm index ffde2fd..c7ff5ca 100644 --- a/runtime/objc-file.mm +++ b/runtime/objc-file.mm @@ -68,6 +68,12 @@ GETSECT(_getObjc2ProtocolRefs, protocol_t *, "__objc_protorefs"); GETSECT(getLibobjcInitializers, UnsignedInitializer, "__objc_init_func"); +uint32_t *getLibobjcInitializerOffsets(const headerType *mhdr, size_t *outCount) { + unsigned long byteCount = 0; + uint32_t *offsets = (uint32_t *)getsectiondata(mhdr, "__TEXT", "__objc_init_offs", &byteCount); + if (outCount) *outCount = byteCount / sizeof(uint32_t); + return offsets; +} objc_image_info * _getObjcImageInfo(const headerType *mhdr, size_t *outBytes) diff --git a/runtime/objc-gdb.h b/runtime/objc-gdb.h index 9cab4a3..99cff42 100644 --- a/runtime/objc-gdb.h +++ b/runtime/objc-gdb.h @@ -219,6 +219,10 @@ OBJC_EXPORT uintptr_t objc_debug_taggedpointer_mask OBJC_EXPORT uintptr_t objc_debug_taggedpointer_obfuscator OBJC_AVAILABLE(10.14, 12.0, 12.0, 5.0, 3.0); +#if OBJC_SPLIT_TAGGED_POINTERS +OBJC_EXPORT uint8_t objc_debug_tag60_permutations[8]; +#endif + // tag_slot = (obj >> slot_shift) & slot_mask OBJC_EXPORT unsigned int objc_debug_taggedpointer_slot_shift @@ -266,6 +270,9 @@ OBJC_EXPORT unsigned int objc_debug_taggedpointer_ext_payload_lshift OBJC_EXPORT unsigned int objc_debug_taggedpointer_ext_payload_rshift OBJC_AVAILABLE(10.12, 10.0, 10.0, 3.0, 2.0); +OBJC_EXPORT uintptr_t objc_debug_constant_cfstring_tag_bits + OBJC_AVAILABLE(10.16, 14.0, 14.0, 7.0, 6.0); + #endif @@ -289,6 +296,9 @@ OBJC_EXTERN const uint32_t objc_debug_autoreleasepoolpage_parent_offset OBJC_AVA OBJC_EXTERN const uint32_t objc_debug_autoreleasepoolpage_child_offset OBJC_AVAILABLE(10.15, 13.0, 13.0, 6.0, 5.0); OBJC_EXTERN const uint32_t objc_debug_autoreleasepoolpage_depth_offset OBJC_AVAILABLE(10.15, 13.0, 13.0, 6.0, 5.0); OBJC_EXTERN const uint32_t objc_debug_autoreleasepoolpage_hiwat_offset OBJC_AVAILABLE(10.15, 13.0, 13.0, 6.0, 5.0); +#if __OBJC2__ +OBJC_EXTERN const uintptr_t objc_debug_autoreleasepoolpage_ptr_mask OBJC_AVAILABLE(10.16, 14.0, 14.0, 7.0, 6.0); +#endif __END_DECLS diff --git a/runtime/objc-initialize.mm b/runtime/objc-initialize.mm index 4713325..8f98cbd 100644 --- a/runtime/objc-initialize.mm +++ b/runtime/objc-initialize.mm @@ -396,10 +396,10 @@ static bool classHasTrivialInitialize(Class cls) { if (cls->isRootClass() || cls->isRootMetaclass()) return true; - Class rootCls = cls->ISA()->ISA()->superclass; + Class rootCls = cls->ISA()->ISA()->getSuperclass(); - IMP rootImp = lookUpImpOrNil(rootCls, @selector(initialize), rootCls->ISA()); - IMP imp = lookUpImpOrNil(cls, @selector(initialize), cls->ISA()); + IMP rootImp = lookUpImpOrNilTryCache(rootCls, @selector(initialize), rootCls->ISA()); + IMP imp = lookUpImpOrNilTryCache(cls, @selector(initialize), cls->ISA()); return (imp == nil || imp == (IMP)&objc_noop_imp || imp == rootImp); } @@ -500,7 +500,7 @@ void initializeNonMetaClass(Class cls) // Make sure super is done initializing BEFORE beginning to initialize cls. // See note about deadlock above. - supercls = cls->superclass; + supercls = cls->getSuperclass(); if (supercls && !supercls->isInitialized()) { initializeNonMetaClass(supercls); } diff --git a/runtime/objc-internal.h b/runtime/objc-internal.h index 112804d..ad40a1c 100644 --- a/runtime/objc-internal.h +++ b/runtime/objc-internal.h @@ -44,6 +44,11 @@ #include #include +// Include NSObject.h only if we're ObjC. Module imports get unhappy +// otherwise. +#if __OBJC__ +#include +#endif // Termination reasons in the OS_REASON_OBJC namespace. #define OBJC_EXIT_REASON_UNSPECIFIED 1 @@ -54,6 +59,18 @@ // The runtime's class structure will never grow beyond this. #define OBJC_MAX_CLASS_SIZE (32*sizeof(void*)) +// Private objc_setAssociatedObject policy modifier. When an object is +// destroyed, associated objects attached to that object that are marked with +// this will be released after all associated objects not so marked. +// +// In addition, such associations are not removed when calling +// objc_removeAssociatedObjects. +// +// NOTE: This should be used sparingly. Performance will be poor when a single +// object has more than a few (deliberately vague) associated objects marked +// with this flag. If you're not sure if you should use this, you should not use +// this! +#define _OBJC_ASSOCIATION_SYSTEM_OBJECT (1 << 16) __BEGIN_DECLS @@ -160,8 +177,14 @@ OBJC_EXPORT objc_imp_cache_entry *_Nullable class_copyImpCache(Class _Nonnull cls, int * _Nullable outCount) OBJC_AVAILABLE(10.15, 13.0, 13.0, 6.0, 5.0); + +OBJC_EXPORT +unsigned long +sel_hash(SEL _Nullable sel) + OBJC_AVAILABLE(10.16, 14.0, 14.0, 7.0, 6.0); #endif + // Plainly-implemented GC barriers. Rosetta used to use these. OBJC_EXPORT id _Nullable objc_assign_strongCast_generic(id _Nullable value, id _Nullable * _Nonnull dest) @@ -199,7 +222,7 @@ OBJC_EXPORT void _objc_setClassLoader(BOOL (* _Nonnull newClassLoader)(const char * _Nonnull)) OBJC2_UNAVAILABLE; -#if !(TARGET_OS_OSX && !TARGET_OS_IOSMAC && __i386__) +#if !(TARGET_OS_OSX && !TARGET_OS_MACCATALYST && __i386__) // Add a class copy fixup handler. The name is a misnomer, as // multiple calls will install multiple handlers. Older versions // of the Swift runtime call it by name, and it's only used by Swift @@ -240,6 +263,21 @@ objc_copyClassNamesForImageHeader(const struct mach_header * _Nonnull mh, unsigned int * _Nullable outCount) OBJC_AVAILABLE(10.14, 12.0, 12.0, 5.0, 3.0); +/** + * Returns the all the classes within a library. + * + * @param image The mach header for library or framework you are inquiring about. + * @param outCount The number of class names returned. + * + * @return An array of Class objects + */ + +OBJC_EXPORT Class _Nonnull * _Nullable +objc_copyClassesForImage(const char * _Nonnull image, + unsigned int * _Nullable outCount) + OBJC_AVAILABLE(10.16, 14.0, 14.0, 7.0, 4.0); + + // Tagged pointer objects. #if __LP64__ @@ -290,10 +328,20 @@ enum OBJC_TAG_NSMethodSignature = 20, OBJC_TAG_UTTypeRecord = 21, + // When using the split tagged pointer representation + // (OBJC_SPLIT_TAGGED_POINTERS), this is the first tag where + // the tag and payload are unobfuscated. All tags from here to + // OBJC_TAG_Last52BitPayload are unobfuscated. The shared cache + // builder is able to construct these as long as the low bit is + // not set (i.e. even-numbered tags). + OBJC_TAG_FirstUnobfuscatedSplitTag = 136, // 128 + 8, first ext tag with high bit set + + OBJC_TAG_Constant_CFString = 136, + OBJC_TAG_First60BitPayload = 0, OBJC_TAG_Last60BitPayload = 6, OBJC_TAG_First52BitPayload = 8, - OBJC_TAG_Last52BitPayload = 263, + OBJC_TAG_Last52BitPayload = 263, OBJC_TAG_RESERVED_264 = 264 }; @@ -352,7 +400,16 @@ _objc_getTaggedPointerSignedValue(const void * _Nullable ptr); // Don't use the values below. Use the declarations above. -#if (TARGET_OS_OSX || TARGET_OS_IOSMAC) && __x86_64__ +#if __arm64__ +// ARM64 uses a new tagged pointer scheme where normal tags are in +// the low bits, extended tags are in the high bits, and half of the +// extended tag space is reserved for unobfuscated payloads. +# define OBJC_SPLIT_TAGGED_POINTERS 1 +#else +# define OBJC_SPLIT_TAGGED_POINTERS 0 +#endif + +#if (TARGET_OS_OSX || TARGET_OS_MACCATALYST) && __x86_64__ // 64-bit Mac - tag bit is LSB # define OBJC_MSB_TAGGED_POINTERS 0 #else @@ -360,17 +417,37 @@ _objc_getTaggedPointerSignedValue(const void * _Nullable ptr); # define OBJC_MSB_TAGGED_POINTERS 1 #endif -#define _OBJC_TAG_INDEX_MASK 0x7 +#define _OBJC_TAG_INDEX_MASK 0x7UL + +#if OBJC_SPLIT_TAGGED_POINTERS +#define _OBJC_TAG_SLOT_COUNT 8 +#define _OBJC_TAG_SLOT_MASK 0x7UL +#else // array slot includes the tag bit itself #define _OBJC_TAG_SLOT_COUNT 16 -#define _OBJC_TAG_SLOT_MASK 0xf +#define _OBJC_TAG_SLOT_MASK 0xfUL +#endif #define _OBJC_TAG_EXT_INDEX_MASK 0xff // array slot has no extra bits #define _OBJC_TAG_EXT_SLOT_COUNT 256 #define _OBJC_TAG_EXT_SLOT_MASK 0xff -#if OBJC_MSB_TAGGED_POINTERS +#if OBJC_SPLIT_TAGGED_POINTERS +# define _OBJC_TAG_MASK (1UL<<63) +# define _OBJC_TAG_INDEX_SHIFT 0 +# define _OBJC_TAG_SLOT_SHIFT 0 +# define _OBJC_TAG_PAYLOAD_LSHIFT 1 +# define _OBJC_TAG_PAYLOAD_RSHIFT 4 +# define _OBJC_TAG_EXT_MASK (_OBJC_TAG_MASK | 0x7UL) +# define _OBJC_TAG_NO_OBFUSCATION_MASK ((1UL<<62) | _OBJC_TAG_EXT_MASK) +# define _OBJC_TAG_CONSTANT_POINTER_MASK \ + ~(_OBJC_TAG_EXT_MASK | ((uintptr_t)_OBJC_TAG_EXT_SLOT_MASK << _OBJC_TAG_EXT_SLOT_SHIFT)) +# define _OBJC_TAG_EXT_INDEX_SHIFT 55 +# define _OBJC_TAG_EXT_SLOT_SHIFT 55 +# define _OBJC_TAG_EXT_PAYLOAD_LSHIFT 9 +# define _OBJC_TAG_EXT_PAYLOAD_RSHIFT 12 +#elif OBJC_MSB_TAGGED_POINTERS # define _OBJC_TAG_MASK (1UL<<63) # define _OBJC_TAG_INDEX_SHIFT 60 # define _OBJC_TAG_SLOT_SHIFT 60 @@ -394,21 +471,64 @@ _objc_getTaggedPointerSignedValue(const void * _Nullable ptr); # define _OBJC_TAG_EXT_PAYLOAD_RSHIFT 12 #endif +// Map of tags to obfuscated tags. extern uintptr_t objc_debug_taggedpointer_obfuscator; +#if OBJC_SPLIT_TAGGED_POINTERS +extern uint8_t objc_debug_tag60_permutations[8]; + +static inline uintptr_t _objc_basicTagToObfuscatedTag(uintptr_t tag) { + return objc_debug_tag60_permutations[tag]; +} + +static inline uintptr_t _objc_obfuscatedTagToBasicTag(uintptr_t tag) { + for (unsigned i = 0; i < 7; i++) + if (objc_debug_tag60_permutations[i] == tag) + return i; + return 7; +} +#endif + static inline void * _Nonnull _objc_encodeTaggedPointer(uintptr_t ptr) { - return (void *)(objc_debug_taggedpointer_obfuscator ^ ptr); + uintptr_t value = (objc_debug_taggedpointer_obfuscator ^ ptr); +#if OBJC_SPLIT_TAGGED_POINTERS + if ((value & _OBJC_TAG_NO_OBFUSCATION_MASK) == _OBJC_TAG_NO_OBFUSCATION_MASK) + return (void *)ptr; + uintptr_t basicTag = (value >> _OBJC_TAG_INDEX_SHIFT) & _OBJC_TAG_INDEX_MASK; + uintptr_t permutedTag = _objc_basicTagToObfuscatedTag(basicTag); + value &= ~(_OBJC_TAG_INDEX_MASK << _OBJC_TAG_INDEX_SHIFT); + value |= permutedTag << _OBJC_TAG_INDEX_SHIFT; +#endif + return (void *)value; +} + +static inline uintptr_t +_objc_decodeTaggedPointer_noPermute(const void * _Nullable ptr) +{ + uintptr_t value = (uintptr_t)ptr; +#if OBJC_SPLIT_TAGGED_POINTERS + if ((value & _OBJC_TAG_NO_OBFUSCATION_MASK) == _OBJC_TAG_NO_OBFUSCATION_MASK) + return value; +#endif + return value ^ objc_debug_taggedpointer_obfuscator; } static inline uintptr_t _objc_decodeTaggedPointer(const void * _Nullable ptr) { - return (uintptr_t)ptr ^ objc_debug_taggedpointer_obfuscator; + uintptr_t value = _objc_decodeTaggedPointer_noPermute(ptr); +#if OBJC_SPLIT_TAGGED_POINTERS + uintptr_t basicTag = (value >> _OBJC_TAG_INDEX_SHIFT) & _OBJC_TAG_INDEX_MASK; + + value &= ~(_OBJC_TAG_INDEX_MASK << _OBJC_TAG_INDEX_SHIFT); + value |= _objc_obfuscatedTagToBasicTag(basicTag) << _OBJC_TAG_INDEX_SHIFT; +#endif + return value; } -static inline bool +static inline bool _objc_taggedPointersEnabled(void) { extern uintptr_t objc_debug_taggedpointer_mask; @@ -447,6 +567,15 @@ _objc_isTaggedPointer(const void * _Nullable ptr) return ((uintptr_t)ptr & _OBJC_TAG_MASK) == _OBJC_TAG_MASK; } +static inline bool +_objc_isTaggedPointerOrNil(const void * _Nullable ptr) +{ + // this function is here so that clang can turn this into + // a comparison with NULL when this is appropriate + // it turns out it's not able to in many cases without this + return !ptr || ((uintptr_t)ptr & _OBJC_TAG_MASK) == _OBJC_TAG_MASK; +} + static inline objc_tag_index_t _objc_getTaggedPointerTag(const void * _Nullable ptr) { @@ -465,7 +594,7 @@ static inline uintptr_t _objc_getTaggedPointerValue(const void * _Nullable ptr) { // ASSERT(_objc_isTaggedPointer(ptr)); - uintptr_t value = _objc_decodeTaggedPointer(ptr); + uintptr_t value = _objc_decodeTaggedPointer_noPermute(ptr); uintptr_t basicTag = (value >> _OBJC_TAG_INDEX_SHIFT) & _OBJC_TAG_INDEX_MASK; if (basicTag == _OBJC_TAG_INDEX_MASK) { return (value << _OBJC_TAG_EXT_PAYLOAD_LSHIFT) >> _OBJC_TAG_EXT_PAYLOAD_RSHIFT; @@ -478,7 +607,7 @@ static inline intptr_t _objc_getTaggedPointerSignedValue(const void * _Nullable ptr) { // ASSERT(_objc_isTaggedPointer(ptr)); - uintptr_t value = _objc_decodeTaggedPointer(ptr); + uintptr_t value = _objc_decodeTaggedPointer_noPermute(ptr); uintptr_t basicTag = (value >> _OBJC_TAG_INDEX_SHIFT) & _OBJC_TAG_INDEX_MASK; if (basicTag == _OBJC_TAG_INDEX_MASK) { return ((intptr_t)value << _OBJC_TAG_EXT_PAYLOAD_LSHIFT) >> _OBJC_TAG_EXT_PAYLOAD_RSHIFT; @@ -487,6 +616,13 @@ _objc_getTaggedPointerSignedValue(const void * _Nullable ptr) } } +# if OBJC_SPLIT_TAGGED_POINTERS +static inline void * _Nullable +_objc_getTaggedPointerRawPointerValue(const void * _Nullable ptr) { + return (void *)((uintptr_t)ptr & _OBJC_TAG_CONSTANT_POINTER_MASK); +} +# endif + // OBJC_HAVE_TAGGED_POINTERS #endif @@ -597,6 +733,11 @@ _class_getIvarMemoryManagement(Class _Nullable cls, Ivar _Nonnull ivar) OBJC_EXPORT BOOL _class_isFutureClass(Class _Nullable cls) OBJC_AVAILABLE(10.9, 7.0, 9.0, 1.0, 2.0); +/// Returns true if the class is an ABI stable Swift class. (Despite +/// the name, this does NOT return true for Swift classes built with +/// Swift versions prior to 5.0.) +OBJC_EXPORT BOOL _class_isSwift(Class _Nullable cls) + OBJC_AVAILABLE(10.16, 14.0, 14.0, 7.0, 5.0); // API to only be called by root classes like NSObject or NSProxy @@ -878,12 +1019,47 @@ typedef void (*_objc_func_willInitializeClass)(void * _Nullable context, Class _ OBJC_EXPORT void _objc_addWillInitializeClassFunc(_objc_func_willInitializeClass _Nonnull func, void * _Nullable context) OBJC_AVAILABLE(10.15, 13.0, 13.0, 6.0, 4.0); +// Replicate the conditionals in objc-config.h for packed isa, indexed isa, and preopt caches +#if __ARM_ARCH_7K__ >= 2 || (__arm64__ && !__LP64__) || \ + !(!__LP64__ || TARGET_OS_WIN32 || \ + (TARGET_OS_SIMULATOR && !TARGET_OS_MACCATALYST && !__arm64__)) +OBJC_EXPORT const uintptr_t _objc_has_weak_formation_callout; +#define OBJC_WEAK_FORMATION_CALLOUT_DEFINED 1 +#else +#define OBJC_WEAK_FORMATION_CALLOUT_DEFINED 0 +#endif + +#if defined(__arm64__) && TARGET_OS_IOS && !TARGET_OS_SIMULATOR && !TARGET_OS_MACCATALYST +#define CONFIG_USE_PREOPT_CACHES 1 +#else +#define CONFIG_USE_PREOPT_CACHES 0 +#endif + + +#if __OBJC2__ +// Helper function for objc4 tests only! Do not call this yourself +// for any reason ever. +OBJC_EXPORT void _method_setImplementationRawUnsafe(Method _Nonnull m, IMP _Nonnull imp) + OBJC_AVAILABLE(10.16, 14.0, 14.0, 7.0, 5.0); +#endif + // API to only be called by classes that provide their own reference count storage OBJC_EXPORT void _objc_deallocOnMainThreadHelper(void * _Nullable context) OBJC_AVAILABLE(10.7, 5.0, 9.0, 1.0, 2.0); +#if __OBJC__ +// Declarations for internal methods used for custom weak reference +// implementations. These declarations ensure that the compiler knows +// to exclude these methods from NS_DIRECT_MEMBERS. Do NOT implement +// these methods unless you really know what you're doing. +@interface NSObject () +- (BOOL)_tryRetain; +- (BOOL)_isDeallocating; +@end +#endif + // On async versus sync deallocation and the _dealloc2main flag // // Theory: @@ -983,6 +1159,25 @@ typedef enum { #define _OBJC_SUPPORTED_INLINE_REFCNT(_rc_ivar) _OBJC_SUPPORTED_INLINE_REFCNT_LOGIC(_rc_ivar, 0) #define _OBJC_SUPPORTED_INLINE_REFCNT_WITH_DEALLOC2MAIN(_rc_ivar) _OBJC_SUPPORTED_INLINE_REFCNT_LOGIC(_rc_ivar, 1) + +// C cache_t wrappers for objcdt and the IMP caches test tool +struct cache_t; +struct bucket_t; +struct preopt_cache_t; +OBJC_EXPORT struct bucket_t * _Nonnull objc_cache_buckets(const struct cache_t * _Nonnull cache); +OBJC_EXPORT size_t objc_cache_bytesForCapacity(uint32_t cap); +OBJC_EXPORT uint32_t objc_cache_occupied(const struct cache_t * _Nonnull cache); +OBJC_EXPORT unsigned objc_cache_capacity(const struct cache_t * _Nonnull cache); + +#if CONFIG_USE_PREOPT_CACHES + +OBJC_EXPORT bool objc_cache_isConstantOptimizedCache(const struct cache_t * _Nonnull cache, bool strict, uintptr_t empty_addr); +OBJC_EXPORT unsigned objc_cache_preoptCapacity(const struct cache_t * _Nonnull cache); +OBJC_EXPORT Class _Nonnull objc_cache_preoptFallbackClass(const struct cache_t * _Nonnull cache); +OBJC_EXPORT const struct preopt_cache_t * _Nonnull objc_cache_preoptCache(const struct cache_t * _Nonnull cache); + +#endif + __END_DECLS #endif diff --git a/runtime/objc-lockdebug.h b/runtime/objc-lockdebug.h index a3048b1..a69ee06 100644 --- a/runtime/objc-lockdebug.h +++ b/runtime/objc-lockdebug.h @@ -24,11 +24,13 @@ #if LOCKDEBUG extern void lockdebug_assert_all_locks_locked(); extern void lockdebug_assert_no_locks_locked(); +extern void lockdebug_assert_no_locks_locked_except(std::initializer_list canBeLocked); extern void lockdebug_setInForkPrepare(bool); extern void lockdebug_lock_precedes_lock(const void *oldlock, const void *newlock); #else static constexpr inline void lockdebug_assert_all_locks_locked() { } static constexpr inline void lockdebug_assert_no_locks_locked() { } +static constexpr inline void lockdebug_assert_no_locks_locked_except(std::initializer_list canBeLocked) { }; static constexpr inline void lockdebug_setInForkPrepare(bool) { } static constexpr inline void lockdebug_lock_precedes_lock(const void *, const void *) { } #endif @@ -40,12 +42,12 @@ extern void lockdebug_mutex_unlock(mutex_tt *lock); extern void lockdebug_mutex_assert_locked(mutex_tt *lock); extern void lockdebug_mutex_assert_unlocked(mutex_tt *lock); -static constexpr inline void lockdebug_remember_mutex(mutex_tt *lock) { } -static constexpr inline void lockdebug_mutex_lock(mutex_tt *lock) { } -static constexpr inline void lockdebug_mutex_try_lock(mutex_tt *lock) { } -static constexpr inline void lockdebug_mutex_unlock(mutex_tt *lock) { } -static constexpr inline void lockdebug_mutex_assert_locked(mutex_tt *lock) { } -static constexpr inline void lockdebug_mutex_assert_unlocked(mutex_tt *lock) { } +static constexpr inline void lockdebug_remember_mutex(__unused mutex_tt *lock) { } +static constexpr inline void lockdebug_mutex_lock(__unused mutex_tt *lock) { } +static constexpr inline void lockdebug_mutex_try_lock(__unused mutex_tt *lock) { } +static constexpr inline void lockdebug_mutex_unlock(__unused mutex_tt *lock) { } +static constexpr inline void lockdebug_mutex_assert_locked(__unused mutex_tt *lock) { } +static constexpr inline void lockdebug_mutex_assert_unlocked(__unused mutex_tt *lock) { } extern void lockdebug_remember_monitor(monitor_tt *lock); @@ -55,12 +57,12 @@ extern void lockdebug_monitor_wait(monitor_tt *lock); extern void lockdebug_monitor_assert_locked(monitor_tt *lock); extern void lockdebug_monitor_assert_unlocked(monitor_tt *lock); -static constexpr inline void lockdebug_remember_monitor(monitor_tt *lock) { } -static constexpr inline void lockdebug_monitor_enter(monitor_tt *lock) { } -static constexpr inline void lockdebug_monitor_leave(monitor_tt *lock) { } -static constexpr inline void lockdebug_monitor_wait(monitor_tt *lock) { } -static constexpr inline void lockdebug_monitor_assert_locked(monitor_tt *lock) { } -static constexpr inline void lockdebug_monitor_assert_unlocked(monitor_tt *lock) {} +static constexpr inline void lockdebug_remember_monitor(__unused monitor_tt *lock) { } +static constexpr inline void lockdebug_monitor_enter(__unused monitor_tt *lock) { } +static constexpr inline void lockdebug_monitor_leave(__unused monitor_tt *lock) { } +static constexpr inline void lockdebug_monitor_wait(__unused monitor_tt *lock) { } +static constexpr inline void lockdebug_monitor_assert_locked(__unused monitor_tt *lock) { } +static constexpr inline void lockdebug_monitor_assert_unlocked(__unused monitor_tt *lock) {} extern void @@ -75,12 +77,12 @@ extern void lockdebug_recursive_mutex_assert_unlocked(recursive_mutex_tt *lock); static constexpr inline void -lockdebug_remember_recursive_mutex(recursive_mutex_tt *lock) { } +lockdebug_remember_recursive_mutex(__unused recursive_mutex_tt *lock) { } static constexpr inline void -lockdebug_recursive_mutex_lock(recursive_mutex_tt *lock) { } +lockdebug_recursive_mutex_lock(__unused recursive_mutex_tt *lock) { } static constexpr inline void -lockdebug_recursive_mutex_unlock(recursive_mutex_tt *lock) { } +lockdebug_recursive_mutex_unlock(__unused recursive_mutex_tt *lock) { } static constexpr inline void -lockdebug_recursive_mutex_assert_locked(recursive_mutex_tt *lock) { } +lockdebug_recursive_mutex_assert_locked(__unused recursive_mutex_tt *lock) { } static constexpr inline void -lockdebug_recursive_mutex_assert_unlocked(recursive_mutex_tt *lock) { } +lockdebug_recursive_mutex_assert_unlocked(__unused recursive_mutex_tt *lock) { } diff --git a/runtime/objc-lockdebug.mm b/runtime/objc-lockdebug.mm index f182a27..1429c2d 100644 --- a/runtime/objc-lockdebug.mm +++ b/runtime/objc-lockdebug.mm @@ -321,10 +321,18 @@ void lockdebug_lock_precedes_lock(const void *oldlock, const void *newlock) void lockdebug_assert_no_locks_locked() +{ + lockdebug_assert_no_locks_locked_except({}); +} + +void lockdebug_assert_no_locks_locked_except(std::initializer_list canBeLocked) { auto& owned = ownedLocks(); for (const auto& l : AllLocks()) { + if (std::find(canBeLocked.begin(), canBeLocked.end(), l.first) != canBeLocked.end()) + continue; + if (hasLock(owned, l.first, l.second.k)) { _objc_fatal("lock %p:%d is incorrectly owned", l.first, l.second.k); } diff --git a/runtime/objc-object.h b/runtime/objc-object.h index 2c17c94..d15d5a8 100644 --- a/runtime/objc-object.h +++ b/runtime/objc-object.h @@ -73,7 +73,7 @@ objc_object::isClass() #if SUPPORT_TAGGED_POINTERS -inline Class +inline Class objc_object::getIsa() { if (fastpath(!isTaggedPointer())) return ISA(); @@ -103,6 +103,12 @@ objc_object::isTaggedPointer() return _objc_isTaggedPointer(this); } +inline bool +objc_object::isTaggedPointerOrNil() +{ + return _objc_isTaggedPointerOrNil(this); +} + inline bool objc_object::isBasicTaggedPointer() { @@ -121,8 +127,7 @@ objc_object::isExtTaggedPointer() #else // not SUPPORT_TAGGED_POINTERS - -inline Class +inline Class objc_object::getIsa() { return ISA(); @@ -141,6 +146,12 @@ objc_object::isTaggedPointer() return false; } +inline bool +objc_object::isTaggedPointerOrNil() +{ + return !this; +} + inline bool objc_object::isBasicTaggedPointer() { @@ -160,21 +171,118 @@ objc_object::isExtTaggedPointer() #if SUPPORT_NONPOINTER_ISA -inline Class -objc_object::ISA() -{ - ASSERT(!isTaggedPointer()); +// Set the class field in an isa. Takes both the class to set and +// a pointer to the object where the isa will ultimately be used. +// This is necessary to get the pointer signing right. +// +// Note: this method does not support setting an indexed isa. When +// indexed isas are in use, it can only be used to set the class of a +// raw isa. +inline void +isa_t::setClass(Class newCls, UNUSED_WITHOUT_PTRAUTH objc_object *obj) +{ + // Match the conditional in isa.h. +#if __has_feature(ptrauth_calls) || TARGET_OS_SIMULATOR +# if ISA_SIGNING_SIGN_MODE == ISA_SIGNING_SIGN_NONE + // No signing, just use the raw pointer. + uintptr_t signedCls = (uintptr_t)newCls; + +# elif ISA_SIGNING_SIGN_MODE == ISA_SIGNING_SIGN_ONLY_SWIFT + // We're only signing Swift classes. Non-Swift classes just use + // the raw pointer + uintptr_t signedCls = (uintptr_t)newCls; + if (newCls->isSwiftStable()) + signedCls = (uintptr_t)ptrauth_sign_unauthenticated((void *)newCls, ISA_SIGNING_KEY, ptrauth_blend_discriminator(obj, ISA_SIGNING_DISCRIMINATOR)); + +# elif ISA_SIGNING_SIGN_MODE == ISA_SIGNING_SIGN_ALL + // We're signing everything + uintptr_t signedCls = (uintptr_t)ptrauth_sign_unauthenticated((void *)newCls, ISA_SIGNING_KEY, ptrauth_blend_discriminator(obj, ISA_SIGNING_DISCRIMINATOR)); + +# else +# error Unknown isa signing mode. +# endif + + shiftcls_and_sig = signedCls >> 3; + +#elif SUPPORT_INDEXED_ISA + // Indexed isa only uses this method to set a raw pointer class. + // Setting an indexed class is handled separately. + cls = newCls; + +#else // Nonpointer isa, no ptrauth + shiftcls = (uintptr_t)newCls >> 3; +#endif +} + +// Get the class pointer out of an isa. When ptrauth is supported, +// this operation is optionally authenticated. Many code paths don't +// need the authentication, so it can be skipped in those cases for +// better performance. +// +// Note: this method does not support retrieving indexed isas. When +// indexed isas are in use, it can only be used to retrieve the class +// of a raw isa. +#if SUPPORT_INDEXED_ISA || (ISA_SIGNING_AUTH_MODE != ISA_SIGNING_AUTH) +#define MAYBE_UNUSED_AUTHENTICATED_PARAM __attribute__((unused)) +#else +#define MAYBE_UNUSED_AUTHENTICATED_PARAM UNUSED_WITHOUT_PTRAUTH +#endif + +inline Class +isa_t::getClass(MAYBE_UNUSED_AUTHENTICATED_PARAM bool authenticated) { #if SUPPORT_INDEXED_ISA - if (isa.nonpointer) { - uintptr_t slot = isa.indexcls; - return classForIndex((unsigned)slot); + return cls; +#else + + uintptr_t clsbits = bits; + +# if __has_feature(ptrauth_calls) +# if ISA_SIGNING_AUTH_MODE == ISA_SIGNING_AUTH + // Most callers aren't security critical, so skip the + // authentication unless they ask for it. Message sending and + // cache filling are protected by the auth code in msgSend. + if (authenticated) { + // Mask off all bits besides the class pointer and signature. + clsbits &= ISA_MASK; + if (clsbits == 0) + return Nil; + clsbits = (uintptr_t)ptrauth_auth_data((void *)clsbits, ISA_SIGNING_KEY, ptrauth_blend_discriminator(this, ISA_SIGNING_DISCRIMINATOR)); + } else { + // If not authenticating, strip using the precomputed class mask. + clsbits &= objc_debug_isa_class_mask; } - return (Class)isa.bits; +# else + // If not authenticating, strip using the precomputed class mask. + clsbits &= objc_debug_isa_class_mask; +# endif + +# else + clsbits &= ISA_MASK; +# endif + + return (Class)clsbits; +#endif +} + +inline Class +isa_t::getDecodedClass(bool authenticated) { +#if SUPPORT_INDEXED_ISA + if (nonpointer) { + return classForIndex(indexcls); + } + return (Class)cls; #else - return (Class)(isa.bits & ISA_MASK); + return getClass(authenticated); #endif } +inline Class +objc_object::ISA(bool authenticated) +{ + ASSERT(!isTaggedPointer()); + return isa.getDecodedClass(authenticated); +} + inline Class objc_object::rawISA() { @@ -220,18 +328,25 @@ objc_object::initInstanceIsa(Class cls, bool hasCxxDtor) initIsa(cls, true, hasCxxDtor); } +#if !SUPPORT_INDEXED_ISA && !ISA_HAS_CXX_DTOR_BIT +#define UNUSED_WITHOUT_INDEXED_ISA_AND_DTOR_BIT __attribute__((unused)) +#else +#define UNUSED_WITHOUT_INDEXED_ISA_AND_DTOR_BIT +#endif + inline void -objc_object::initIsa(Class cls, bool nonpointer, bool hasCxxDtor) +objc_object::initIsa(Class cls, bool nonpointer, UNUSED_WITHOUT_INDEXED_ISA_AND_DTOR_BIT bool hasCxxDtor) { ASSERT(!isTaggedPointer()); + isa_t newisa(0); + if (!nonpointer) { - isa = isa_t((uintptr_t)cls); + newisa.setClass(cls, this); } else { ASSERT(!DisableNonpointerIsa); ASSERT(!cls->instancesRequireRawIsa()); - isa_t newisa(0); #if SUPPORT_INDEXED_ISA ASSERT(cls->classArrayIndex() > 0); @@ -244,18 +359,21 @@ objc_object::initIsa(Class cls, bool nonpointer, bool hasCxxDtor) newisa.bits = ISA_MAGIC_VALUE; // isa.magic is part of ISA_MAGIC_VALUE // isa.nonpointer is part of ISA_MAGIC_VALUE +# if ISA_HAS_CXX_DTOR_BIT newisa.has_cxx_dtor = hasCxxDtor; - newisa.shiftcls = (uintptr_t)cls >> 3; +# endif + newisa.setClass(cls, this); #endif - - // This write must be performed in a single store in some cases - // (for example when realizing a class because other threads - // may simultaneously try to use the class). - // fixme use atomics here to guarantee single-store and to - // guarantee memory order w.r.t. the class index table - // ...but not too atomic because we don't want to hurt instantiation - isa = newisa; + newisa.extra_rc = 1; } + + // This write must be performed in a single store in some cases + // (for example when realizing a class because other threads + // may simultaneously try to use the class). + // fixme use atomics here to guarantee single-store and to + // guarantee memory order w.r.t. the class index table + // ...but not too atomic because we don't want to hurt instantiation + isa = newisa; } @@ -270,34 +388,46 @@ objc_object::changeIsa(Class newCls) ASSERT(!isTaggedPointer()); isa_t oldisa; - isa_t newisa; + isa_t newisa(0); bool sideTableLocked = false; bool transcribeToSideTable = false; + oldisa = LoadExclusive(&isa.bits); + do { transcribeToSideTable = false; - oldisa = LoadExclusive(&isa.bits); if ((oldisa.bits == 0 || oldisa.nonpointer) && !newCls->isFuture() && newCls->canAllocNonpointer()) { // 0 -> nonpointer // nonpointer -> nonpointer #if SUPPORT_INDEXED_ISA - if (oldisa.bits == 0) newisa.bits = ISA_INDEX_MAGIC_VALUE; - else newisa = oldisa; + if (oldisa.bits == 0) { + newisa.bits = ISA_INDEX_MAGIC_VALUE; + newisa.extra_rc = 1; + } else { + newisa = oldisa; + } // isa.magic is part of ISA_MAGIC_VALUE // isa.nonpointer is part of ISA_MAGIC_VALUE newisa.has_cxx_dtor = newCls->hasCxxDtor(); ASSERT(newCls->classArrayIndex() > 0); newisa.indexcls = (uintptr_t)newCls->classArrayIndex(); #else - if (oldisa.bits == 0) newisa.bits = ISA_MAGIC_VALUE; - else newisa = oldisa; + if (oldisa.bits == 0) { + newisa.bits = ISA_MAGIC_VALUE; + newisa.extra_rc = 1; + } + else { + newisa = oldisa; + } // isa.magic is part of ISA_MAGIC_VALUE // isa.nonpointer is part of ISA_MAGIC_VALUE +# if ISA_HAS_CXX_DTOR_BIT newisa.has_cxx_dtor = newCls->hasCxxDtor(); - newisa.shiftcls = (uintptr_t)newCls >> 3; +# endif + newisa.setClass(newCls, this); #endif } else if (oldisa.nonpointer) { @@ -308,38 +438,28 @@ objc_object::changeIsa(Class newCls) if (!sideTableLocked) sidetable_lock(); sideTableLocked = true; transcribeToSideTable = true; - newisa.cls = newCls; + newisa.setClass(newCls, this); } else { // raw pointer -> raw pointer - newisa.cls = newCls; + newisa.setClass(newCls, this); } - } while (!StoreExclusive(&isa.bits, oldisa.bits, newisa.bits)); + } while (slowpath(!StoreExclusive(&isa.bits, &oldisa.bits, newisa.bits))); if (transcribeToSideTable) { // Copy oldisa's retain count et al to side table. // oldisa.has_assoc: nothing to do // oldisa.has_cxx_dtor: nothing to do sidetable_moveExtraRC_nolock(oldisa.extra_rc, - oldisa.deallocating, + oldisa.isDeallocating(), oldisa.weakly_referenced); } if (sideTableLocked) sidetable_unlock(); - if (oldisa.nonpointer) { -#if SUPPORT_INDEXED_ISA - return classForIndex(oldisa.indexcls); -#else - return (Class)((uintptr_t)oldisa.shiftcls << 3); -#endif - } - else { - return oldisa.cls; - } + return oldisa.getDecodedClass(false); } - inline bool objc_object::hasAssociatedObjects() { @@ -354,15 +474,22 @@ objc_object::setHasAssociatedObjects() { if (isTaggedPointer()) return; - retry: - isa_t oldisa = LoadExclusive(&isa.bits); - isa_t newisa = oldisa; - if (!newisa.nonpointer || newisa.has_assoc) { - ClearExclusive(&isa.bits); - return; + if (slowpath(!hasNonpointerIsa() && ISA()->hasCustomRR()) && !ISA()->isFuture() && !ISA()->isMetaClass()) { + void(*setAssoc)(id, SEL) = (void(*)(id, SEL)) object_getMethodImplementation((id)this, @selector(_noteAssociatedObjects)); + if ((IMP)setAssoc != _objc_msgForward) { + (*setAssoc)((id)this, @selector(_noteAssociatedObjects)); + } } - newisa.has_assoc = true; - if (!StoreExclusive(&isa.bits, oldisa.bits, newisa.bits)) goto retry; + + isa_t newisa, oldisa = LoadExclusive(&isa.bits); + do { + newisa = oldisa; + if (!newisa.nonpointer || newisa.has_assoc) { + ClearExclusive(&isa.bits); + return; + } + newisa.has_assoc = true; + } while (slowpath(!StoreExclusive(&isa.bits, &oldisa.bits, newisa.bits))); } @@ -378,20 +505,20 @@ objc_object::isWeaklyReferenced() inline void objc_object::setWeaklyReferenced_nolock() { - retry: - isa_t oldisa = LoadExclusive(&isa.bits); - isa_t newisa = oldisa; - if (slowpath(!newisa.nonpointer)) { - ClearExclusive(&isa.bits); - sidetable_setWeaklyReferenced_nolock(); - return; - } - if (newisa.weakly_referenced) { - ClearExclusive(&isa.bits); - return; - } - newisa.weakly_referenced = true; - if (!StoreExclusive(&isa.bits, oldisa.bits, newisa.bits)) goto retry; + isa_t newisa, oldisa = LoadExclusive(&isa.bits); + do { + newisa = oldisa; + if (slowpath(!newisa.nonpointer)) { + ClearExclusive(&isa.bits); + sidetable_setWeaklyReferenced_nolock(); + return; + } + if (newisa.weakly_referenced) { + ClearExclusive(&isa.bits); + return; + } + newisa.weakly_referenced = true; + } while (slowpath(!StoreExclusive(&isa.bits, &oldisa.bits, newisa.bits))); } @@ -399,8 +526,12 @@ inline bool objc_object::hasCxxDtor() { ASSERT(!isTaggedPointer()); - if (isa.nonpointer) return isa.has_cxx_dtor; - else return isa.cls->hasCxxDtor(); +#if ISA_HAS_CXX_DTOR_BIT + if (isa.nonpointer) + return isa.has_cxx_dtor; + else +#endif + return ISA()->hasCxxDtor(); } @@ -409,7 +540,7 @@ inline bool objc_object::rootIsDeallocating() { if (isTaggedPointer()) return false; - if (isa.nonpointer) return isa.deallocating; + if (isa.nonpointer) return isa.isDeallocating(); return sidetable_isDeallocating(); } @@ -435,10 +566,14 @@ objc_object::rootDealloc() { if (isTaggedPointer()) return; // fixme necessary? - if (fastpath(isa.nonpointer && - !isa.weakly_referenced && - !isa.has_assoc && - !isa.has_cxx_dtor && + if (fastpath(isa.nonpointer && + !isa.weakly_referenced && + !isa.has_assoc && +#if ISA_HAS_CXX_DTOR_BIT + !isa.has_cxx_dtor && +#else + !isa.getClass(false)->hasCxxDtor() && +#endif !isa.has_sidetable_rc)) { assert(!sidetable_present()); @@ -449,6 +584,8 @@ objc_object::rootDealloc() } } +extern explicit_atomic swiftRetain; +extern explicit_atomic swiftRelease; // Equivalent to calling [this retain], with shortcuts if there is no override inline id @@ -456,14 +593,9 @@ objc_object::retain() { ASSERT(!isTaggedPointer()); - if (fastpath(!ISA()->hasCustomRR())) { - return rootRetain(); - } - - return ((id(*)(objc_object *, SEL))objc_msgSend)(this, @selector(retain)); + return rootRetain(false, RRVariant::FastOrMsgSend); } - // Base retain implementation, ignoring overrides. // This does not check isa.fast_rr; if there is an RR override then // it was already called and it chose to call [super retain]. @@ -476,19 +608,19 @@ objc_object::retain() ALWAYS_INLINE id objc_object::rootRetain() { - return rootRetain(false, false); + return rootRetain(false, RRVariant::Fast); } ALWAYS_INLINE bool objc_object::rootTryRetain() { - return rootRetain(true, false) ? true : false; + return rootRetain(true, RRVariant::Fast) ? true : false; } -ALWAYS_INLINE id -objc_object::rootRetain(bool tryRetain, bool handleOverflow) +ALWAYS_INLINE id +objc_object::rootRetain(bool tryRetain, objc_object::RRVariant variant) { - if (isTaggedPointer()) return (id)this; + if (slowpath(isTaggedPointer())) return (id)this; bool sideTableLocked = false; bool transcribeToSideTable = false; @@ -496,29 +628,56 @@ objc_object::rootRetain(bool tryRetain, bool handleOverflow) isa_t oldisa; isa_t newisa; + oldisa = LoadExclusive(&isa.bits); + + if (variant == RRVariant::FastOrMsgSend) { + // These checks are only meaningful for objc_retain() + // They are here so that we avoid a re-load of the isa. + if (slowpath(oldisa.getDecodedClass(false)->hasCustomRR())) { + ClearExclusive(&isa.bits); + if (oldisa.getDecodedClass(false)->canCallSwiftRR()) { + return swiftRetain.load(memory_order_relaxed)((id)this); + } + return ((id(*)(objc_object *, SEL))objc_msgSend)(this, @selector(retain)); + } + } + + if (slowpath(!oldisa.nonpointer)) { + // a Class is a Class forever, so we can perform this check once + // outside of the CAS loop + if (oldisa.getDecodedClass(false)->isMetaClass()) { + ClearExclusive(&isa.bits); + return (id)this; + } + } + do { transcribeToSideTable = false; - oldisa = LoadExclusive(&isa.bits); newisa = oldisa; if (slowpath(!newisa.nonpointer)) { ClearExclusive(&isa.bits); - if (rawISA()->isMetaClass()) return (id)this; - if (!tryRetain && sideTableLocked) sidetable_unlock(); if (tryRetain) return sidetable_tryRetain() ? (id)this : nil; - else return sidetable_retain(); + else return sidetable_retain(sideTableLocked); } // don't check newisa.fast_rr; we already called any RR overrides - if (slowpath(tryRetain && newisa.deallocating)) { + if (slowpath(newisa.isDeallocating())) { ClearExclusive(&isa.bits); - if (!tryRetain && sideTableLocked) sidetable_unlock(); - return nil; + if (sideTableLocked) { + ASSERT(variant == RRVariant::Full); + sidetable_unlock(); + } + if (slowpath(tryRetain)) { + return nil; + } else { + return (id)this; + } } uintptr_t carry; newisa.bits = addc(newisa.bits, RC_ONE, 0, &carry); // extra_rc++ if (slowpath(carry)) { // newisa.extra_rc++ overflowed - if (!handleOverflow) { + if (variant != RRVariant::Full) { ClearExclusive(&isa.bits); return rootRetain_overflow(tryRetain); } @@ -530,14 +689,20 @@ objc_object::rootRetain(bool tryRetain, bool handleOverflow) newisa.extra_rc = RC_HALF; newisa.has_sidetable_rc = true; } - } while (slowpath(!StoreExclusive(&isa.bits, oldisa.bits, newisa.bits))); + } while (slowpath(!StoreExclusive(&isa.bits, &oldisa.bits, newisa.bits))); - if (slowpath(transcribeToSideTable)) { - // Copy the other half of the retain counts to the side table. - sidetable_addExtraRC_nolock(RC_HALF); + if (variant == RRVariant::Full) { + if (slowpath(transcribeToSideTable)) { + // Copy the other half of the retain counts to the side table. + sidetable_addExtraRC_nolock(RC_HALF); + } + + if (slowpath(!tryRetain && sideTableLocked)) sidetable_unlock(); + } else { + ASSERT(!transcribeToSideTable); + ASSERT(!sideTableLocked); } - if (slowpath(!tryRetain && sideTableLocked)) sidetable_unlock(); return (id)this; } @@ -548,12 +713,7 @@ objc_object::release() { ASSERT(!isTaggedPointer()); - if (fastpath(!ISA()->hasCustomRR())) { - rootRelease(); - return; - } - - ((void(*)(objc_object *, SEL))objc_msgSend)(this, @selector(release)); + rootRelease(true, RRVariant::FastOrMsgSend); } @@ -570,35 +730,65 @@ objc_object::release() ALWAYS_INLINE bool objc_object::rootRelease() { - return rootRelease(true, false); + return rootRelease(true, RRVariant::Fast); } ALWAYS_INLINE bool objc_object::rootReleaseShouldDealloc() { - return rootRelease(false, false); + return rootRelease(false, RRVariant::Fast); } -ALWAYS_INLINE bool -objc_object::rootRelease(bool performDealloc, bool handleUnderflow) +ALWAYS_INLINE bool +objc_object::rootRelease(bool performDealloc, objc_object::RRVariant variant) { - if (isTaggedPointer()) return false; + if (slowpath(isTaggedPointer())) return false; bool sideTableLocked = false; - isa_t oldisa; - isa_t newisa; + isa_t newisa, oldisa; + + oldisa = LoadExclusive(&isa.bits); + + if (variant == RRVariant::FastOrMsgSend) { + // These checks are only meaningful for objc_release() + // They are here so that we avoid a re-load of the isa. + if (slowpath(oldisa.getDecodedClass(false)->hasCustomRR())) { + ClearExclusive(&isa.bits); + if (oldisa.getDecodedClass(false)->canCallSwiftRR()) { + swiftRelease.load(memory_order_relaxed)((id)this); + return true; + } + ((void(*)(objc_object *, SEL))objc_msgSend)(this, @selector(release)); + return true; + } + } - retry: + if (slowpath(!oldisa.nonpointer)) { + // a Class is a Class forever, so we can perform this check once + // outside of the CAS loop + if (oldisa.getDecodedClass(false)->isMetaClass()) { + ClearExclusive(&isa.bits); + return false; + } + } + +retry: do { - oldisa = LoadExclusive(&isa.bits); newisa = oldisa; if (slowpath(!newisa.nonpointer)) { ClearExclusive(&isa.bits); - if (rawISA()->isMetaClass()) return false; - if (sideTableLocked) sidetable_unlock(); - return sidetable_release(performDealloc); + return sidetable_release(sideTableLocked, performDealloc); + } + if (slowpath(newisa.isDeallocating())) { + ClearExclusive(&isa.bits); + if (sideTableLocked) { + ASSERT(variant == RRVariant::Full); + sidetable_unlock(); + } + return false; } + // don't check newisa.fast_rr; we already called any RR overrides uintptr_t carry; newisa.bits = subc(newisa.bits, RC_ONE, 0, &carry); // extra_rc-- @@ -606,10 +796,16 @@ objc_object::rootRelease(bool performDealloc, bool handleUnderflow) // don't ClearExclusive() goto underflow; } - } while (slowpath(!StoreReleaseExclusive(&isa.bits, - oldisa.bits, newisa.bits))); + } while (slowpath(!StoreReleaseExclusive(&isa.bits, &oldisa.bits, newisa.bits))); - if (slowpath(sideTableLocked)) sidetable_unlock(); + if (slowpath(newisa.isDeallocating())) + goto deallocate; + + if (variant == RRVariant::Full) { + if (slowpath(sideTableLocked)) sidetable_unlock(); + } else { + ASSERT(!sideTableLocked); + } return false; underflow: @@ -619,7 +815,7 @@ objc_object::rootRelease(bool performDealloc, bool handleUnderflow) newisa = oldisa; if (slowpath(newisa.has_sidetable_rc)) { - if (!handleUnderflow) { + if (variant != RRVariant::Full) { ClearExclusive(&isa.bits); return rootRelease_underflow(performDealloc); } @@ -632,35 +828,37 @@ objc_object::rootRelease(bool performDealloc, bool handleUnderflow) sideTableLocked = true; // Need to start over to avoid a race against // the nonpointer -> raw pointer transition. + oldisa = LoadExclusive(&isa.bits); goto retry; } // Try to remove some retain counts from the side table. - size_t borrowed = sidetable_subExtraRC_nolock(RC_HALF); + auto borrow = sidetable_subExtraRC_nolock(RC_HALF); - // To avoid races, has_sidetable_rc must remain set - // even if the side table count is now zero. + bool emptySideTable = borrow.remaining == 0; // we'll clear the side table if no refcounts remain there - if (borrowed > 0) { + if (borrow.borrowed > 0) { // Side table retain count decreased. // Try to add them to the inline count. - newisa.extra_rc = borrowed - 1; // redo the original decrement too - bool stored = StoreReleaseExclusive(&isa.bits, - oldisa.bits, newisa.bits); - if (!stored) { + bool didTransitionToDeallocating = false; + newisa.extra_rc = borrow.borrowed - 1; // redo the original decrement too + newisa.has_sidetable_rc = !emptySideTable; + + bool stored = StoreReleaseExclusive(&isa.bits, &oldisa.bits, newisa.bits); + + if (!stored && oldisa.nonpointer) { // Inline update failed. // Try it again right now. This prevents livelock on LL/SC // architectures where the side table access itself may have // dropped the reservation. - isa_t oldisa2 = LoadExclusive(&isa.bits); - isa_t newisa2 = oldisa2; - if (newisa2.nonpointer) { - uintptr_t overflow; - newisa2.bits = - addc(newisa2.bits, RC_ONE * (borrowed-1), 0, &overflow); - if (!overflow) { - stored = StoreReleaseExclusive(&isa.bits, oldisa2.bits, - newisa2.bits); + uintptr_t overflow; + newisa.bits = + addc(oldisa.bits, RC_ONE * (borrow.borrowed-1), 0, &overflow); + newisa.has_sidetable_rc = !emptySideTable; + if (!overflow) { + stored = StoreReleaseExclusive(&isa.bits, &oldisa.bits, newisa.bits); + if (stored) { + didTransitionToDeallocating = newisa.isDeallocating(); } } } @@ -668,32 +866,31 @@ objc_object::rootRelease(bool performDealloc, bool handleUnderflow) if (!stored) { // Inline update failed. // Put the retains back in the side table. - sidetable_addExtraRC_nolock(borrowed); + ClearExclusive(&isa.bits); + sidetable_addExtraRC_nolock(borrow.borrowed); + oldisa = LoadExclusive(&isa.bits); goto retry; } // Decrement successful after borrowing from side table. - // This decrement cannot be the deallocating decrement - the side - // table lock and has_sidetable_rc bit ensure that if everyone - // else tried to -release while we worked, the last one would block. - sidetable_unlock(); - return false; + if (emptySideTable) + sidetable_clearExtraRC_nolock(); + + if (!didTransitionToDeallocating) { + if (slowpath(sideTableLocked)) sidetable_unlock(); + return false; + } } else { // Side table is empty after all. Fall-through to the dealloc path. } } +deallocate: // Really deallocate. - if (slowpath(newisa.deallocating)) { - ClearExclusive(&isa.bits); - if (sideTableLocked) sidetable_unlock(); - return overrelease_error(); - // does not actually return - } - newisa.deallocating = true; - if (!StoreExclusive(&isa.bits, oldisa.bits, newisa.bits)) goto retry; + ASSERT(newisa.isDeallocating()); + ASSERT(isa.isDeallocating()); if (slowpath(sideTableLocked)) sidetable_unlock(); @@ -736,10 +933,9 @@ objc_object::rootRetainCount() if (isTaggedPointer()) return (uintptr_t)this; sidetable_lock(); - isa_t bits = LoadExclusive(&isa.bits); - ClearExclusive(&isa.bits); + isa_t bits = __c11_atomic_load((_Atomic uintptr_t *)&isa.bits, __ATOMIC_RELAXED); if (bits.nonpointer) { - uintptr_t rc = 1 + bits.extra_rc; + uintptr_t rc = bits.extra_rc; if (bits.has_sidetable_rc) { rc += sidetable_getExtraRC_nolock(); } @@ -756,12 +952,29 @@ objc_object::rootRetainCount() #else // not SUPPORT_NONPOINTER_ISA +inline void +isa_t::setClass(Class cls, objc_object *obj) +{ + this->cls = cls; +} + +inline Class +isa_t::getClass(bool authenticated __unused) +{ + return cls; +} + +inline Class +isa_t::getDecodedClass(bool authenticated) +{ + return getClass(authenticated); +} inline Class -objc_object::ISA() +objc_object::ISA(bool authenticated __unused) { ASSERT(!isTaggedPointer()); - return isa.cls; + return isa.getClass(/*authenticated*/false); } inline Class @@ -781,7 +994,7 @@ inline void objc_object::initIsa(Class cls) { ASSERT(!isTaggedPointer()); - isa = (uintptr_t)cls; + isa.setClass(cls, this); } @@ -822,18 +1035,17 @@ objc_object::changeIsa(Class cls) // cls->isInitializing() || cls->isInitialized()); ASSERT(!isTaggedPointer()); - - isa_t oldisa, newisa; - newisa.cls = cls; - do { - oldisa = LoadExclusive(&isa.bits); - } while (!StoreExclusive(&isa.bits, oldisa.bits, newisa.bits)); - - if (oldisa.cls && oldisa.cls->instancesHaveAssociatedObjects()) { + + isa_t newisa, oldisa; + newisa.setClass(cls, this); + oldisa.bits = __c11_atomic_exchange((_Atomic uintptr_t *)&isa.bits, newisa.bits, __ATOMIC_RELAXED); + + Class oldcls = oldisa.getDecodedClass(/*authenticated*/false); + if (oldcls && oldcls->instancesHaveAssociatedObjects()) { cls->setInstancesHaveAssociatedObjects(); } - - return oldisa.cls; + + return oldcls; } @@ -873,7 +1085,7 @@ inline bool objc_object::hasCxxDtor() { ASSERT(!isTaggedPointer()); - return isa.cls->hasCxxDtor(); + return isa.getClass(/*authenticated*/false)->hasCxxDtor(); } @@ -949,14 +1161,14 @@ inline bool objc_object::rootRelease() { if (isTaggedPointer()) return false; - return sidetable_release(true); + return sidetable_release(); } inline bool objc_object::rootReleaseShouldDealloc() { if (isTaggedPointer()) return false; - return sidetable_release(false); + return sidetable_release(/*locked*/false, /*performDealloc*/false); } diff --git a/runtime/objc-opt.mm b/runtime/objc-opt.mm index b21869b..44abbdf 100644 --- a/runtime/objc-opt.mm +++ b/runtime/objc-opt.mm @@ -515,7 +515,7 @@ void preopt_init(void) const uintptr_t start = (uintptr_t)_dyld_get_shared_cache_range(&length); if (start) { - objc::dataSegmentsRanges.add(start, start + length); + objc::dataSegmentsRanges.setSharedCacheRange(start, start + length); } // `opt` not set at compile time in order to detect too-early usage diff --git a/runtime/objc-os.h b/runtime/objc-os.h index 5a06252..6e38e0e 100644 --- a/runtime/objc-os.h +++ b/runtime/objc-os.h @@ -93,6 +93,16 @@ struct explicit_atomic : public std::atomic { } }; +namespace objc { +static inline uintptr_t mask16ShiftBits(uint16_t mask) +{ + // returns by how much 0xffff must be shifted "right" to return mask + uintptr_t maskShift = __builtin_clz(mask) - 16; + ASSERT((0xffff >> maskShift) == mask); + return maskShift; +} +} + #if TARGET_OS_MAC # define OS_UNFAIR_LOCK_INLINE 1 @@ -175,17 +185,25 @@ LoadExclusive(uintptr_t *src) static ALWAYS_INLINE bool -StoreExclusive(uintptr_t *dst, uintptr_t oldvalue __unused, uintptr_t value) +StoreExclusive(uintptr_t *dst, uintptr_t *oldvalue, uintptr_t value) { - return !__builtin_arm_strex(value, dst); + if (slowpath(__builtin_arm_strex(value, dst))) { + *oldvalue = LoadExclusive(dst); + return false; + } + return true; } static ALWAYS_INLINE bool -StoreReleaseExclusive(uintptr_t *dst, uintptr_t oldvalue __unused, uintptr_t value) +StoreReleaseExclusive(uintptr_t *dst, uintptr_t *oldvalue, uintptr_t value) { - return !__builtin_arm_stlex(value, dst); + if (slowpath(__builtin_arm_stlex(value, dst))) { + *oldvalue = LoadExclusive(dst); + return false; + } + return true; } static ALWAYS_INLINE @@ -206,17 +224,17 @@ LoadExclusive(uintptr_t *src) static ALWAYS_INLINE bool -StoreExclusive(uintptr_t *dst, uintptr_t oldvalue, uintptr_t value) +StoreExclusive(uintptr_t *dst, uintptr_t *oldvalue, uintptr_t value) { - return __c11_atomic_compare_exchange_weak((_Atomic(uintptr_t) *)dst, &oldvalue, value, __ATOMIC_RELAXED, __ATOMIC_RELAXED); + return __c11_atomic_compare_exchange_weak((_Atomic(uintptr_t) *)dst, oldvalue, value, __ATOMIC_RELAXED, __ATOMIC_RELAXED); } static ALWAYS_INLINE bool -StoreReleaseExclusive(uintptr_t *dst, uintptr_t oldvalue, uintptr_t value) +StoreReleaseExclusive(uintptr_t *dst, uintptr_t *oldvalue, uintptr_t value) { - return __c11_atomic_compare_exchange_weak((_Atomic(uintptr_t) *)dst, &oldvalue, value, __ATOMIC_RELEASE, __ATOMIC_RELAXED); + return __c11_atomic_compare_exchange_weak((_Atomic(uintptr_t) *)dst, oldvalue, value, __ATOMIC_RELEASE, __ATOMIC_RELAXED); } static ALWAYS_INLINE @@ -726,7 +744,7 @@ class mutex_tt : nocopy_t { lockdebug_remember_mutex(this); } - constexpr mutex_tt(const fork_unsafe_lock_t unsafe) : mLock(OS_UNFAIR_LOCK_INIT) { } + constexpr mutex_tt(__unused const fork_unsafe_lock_t unsafe) : mLock(OS_UNFAIR_LOCK_INIT) { } void lock() { lockdebug_mutex_lock(this); @@ -762,7 +780,7 @@ class mutex_tt : nocopy_t { // Address-ordered lock discipline for a pair of locks. static void lockTwo(mutex_tt *lock1, mutex_tt *lock2) { - if (lock1 < lock2) { + if ((uintptr_t)lock1 < (uintptr_t)lock2) { lock1->lock(); lock2->lock(); } else { @@ -812,7 +830,7 @@ class recursive_mutex_tt : nocopy_t { lockdebug_remember_recursive_mutex(this); } - constexpr recursive_mutex_tt(const fork_unsafe_lock_t unsafe) + constexpr recursive_mutex_tt(__unused const fork_unsafe_lock_t unsafe) : mLock(OS_UNFAIR_RECURSIVE_LOCK_INIT) { } @@ -877,7 +895,7 @@ class monitor_tt { lockdebug_remember_monitor(this); } - monitor_tt(const fork_unsafe_lock_t unsafe) + monitor_tt(__unused const fork_unsafe_lock_t unsafe) : mutex(PTHREAD_MUTEX_INITIALIZER), cond(PTHREAD_COND_INITIALIZER) { } @@ -1019,62 +1037,18 @@ ustrdupMaybeNil(const uint8_t *str) // OS version checking: // -// sdkVersion() -// DYLD_OS_VERSION(mac, ios, tv, watch, bridge) -// sdkIsOlderThan(mac, ios, tv, watch, bridge) // sdkIsAtLeast(mac, ios, tv, watch, bridge) -// +// // This version order matches OBJC_AVAILABLE. +// +// NOTE: prefer dyld_program_sdk_at_least when possible +#define sdkIsAtLeast(x, i, t, w, b) \ + (dyld_program_sdk_at_least(dyld_platform_version_macOS_ ## x) || \ + dyld_program_sdk_at_least(dyld_platform_version_iOS_ ## i) || \ + dyld_program_sdk_at_least(dyld_platform_version_tvOS_ ## t) || \ + dyld_program_sdk_at_least(dyld_platform_version_watchOS_ ## w) || \ + dyld_program_sdk_at_least(dyld_platform_version_bridgeOS_ ## b)) -#if TARGET_OS_OSX -# define DYLD_OS_VERSION(x, i, t, w, b) DYLD_MACOSX_VERSION_##x -# define sdkVersion() dyld_get_program_sdk_version() - -#elif TARGET_OS_IOS -# define DYLD_OS_VERSION(x, i, t, w, b) DYLD_IOS_VERSION_##i -# define sdkVersion() dyld_get_program_sdk_version() - -#elif TARGET_OS_TV - // dyld does not currently have distinct constants for tvOS -# define DYLD_OS_VERSION(x, i, t, w, b) DYLD_IOS_VERSION_##t -# define sdkVersion() dyld_get_program_sdk_version() - -#elif TARGET_OS_BRIDGE -# if TARGET_OS_WATCH -# error bridgeOS 1.0 not supported -# endif - // fixme don't need bridgeOS versioning yet -# define DYLD_OS_VERSION(x, i, t, w, b) DYLD_IOS_VERSION_##t -# define sdkVersion() dyld_get_program_sdk_bridge_os_version() - -#elif TARGET_OS_WATCH -# define DYLD_OS_VERSION(x, i, t, w, b) DYLD_WATCHOS_VERSION_##w - // watchOS has its own API for compatibility reasons -# define sdkVersion() dyld_get_program_sdk_watch_os_version() - -#else -# error unknown OS -#endif - - -#define sdkIsOlderThan(x, i, t, w, b) \ - (sdkVersion() < DYLD_OS_VERSION(x, i, t, w, b)) -#define sdkIsAtLeast(x, i, t, w, b) \ - (sdkVersion() >= DYLD_OS_VERSION(x, i, t, w, b)) - -// Allow bare 0 to be used in DYLD_OS_VERSION() and sdkIsOlderThan() -#define DYLD_MACOSX_VERSION_0 0 -#define DYLD_IOS_VERSION_0 0 -#define DYLD_TVOS_VERSION_0 0 -#define DYLD_WATCHOS_VERSION_0 0 -#define DYLD_BRIDGEOS_VERSION_0 0 - -// Pretty-print a DYLD_*_VERSION_* constant. -#define SDK_FORMAT "%hu.%hhu.%hhu" -#define FORMAT_SDK(v) \ - (unsigned short)(((uint32_t)(v))>>16), \ - (unsigned char)(((uint32_t)(v))>>8), \ - (unsigned char)(((uint32_t)(v))>>0) #ifndef __BUILDING_OBJCDT__ // fork() safety requires careful tracking of all locks. diff --git a/runtime/objc-os.mm b/runtime/objc-os.mm index db021d0..39cf2db 100644 --- a/runtime/objc-os.mm +++ b/runtime/objc-os.mm @@ -28,7 +28,7 @@ #include "objc-private.h" #include "objc-loadmethod.h" -#include "objc-cache.h" +#include "objc-bp-assist.h" #if TARGET_OS_WIN32 @@ -564,13 +564,12 @@ void objc_addLoadImageFunc(objc_func_loadImage _Nonnull func) { // Disable +initialize fork safety if the app has a // __DATA,__objc_fork_ok section. - if (dyld_get_program_sdk_version() < DYLD_MACOSX_VERSION_10_13) { + if (!dyld_program_sdk_at_least(dyld_platform_version_macOS_10_13)) { DisableInitializeForkSafety = true; if (PrintInitializing) { _objc_inform("INITIALIZE: disabling +initialize fork " "safety enforcement because the app is " - "too old (SDK version " SDK_FORMAT ")", - FORMAT_SDK(dyld_get_program_sdk_version())); + "too old.)"); } } @@ -662,6 +661,11 @@ static void static_init() for (size_t i = 0; i < count; i++) { inits[i](); } + auto offsets = getLibobjcInitializerOffsets(&_mh_dylib_header, &count); + for (size_t i = 0; i < count; i++) { + UnsignedInitializer init(offsets[i]); + init(); + } } @@ -927,7 +931,9 @@ void _objc_init(void) static_init(); runtime_init(); exception_init(); - cache_init(); +#if __OBJC2__ + cache_t::init(); +#endif _imp_implementationWithBlock_init(); _dyld_objc_notify_register(&map_images, load_images, unmap_image); diff --git a/runtime/objc-private.h b/runtime/objc-private.h index bf2a8de..c801ba0 100644 --- a/runtime/objc-private.h +++ b/runtime/objc-private.h @@ -53,6 +53,16 @@ #define ASSERT(x) assert(x) #endif +// `this` is never NULL in C++ unless we encounter UB, but checking for what's impossible +// is the point of these asserts, so disable the corresponding warning, and let's hope +// we will reach the assert despite the UB +#define ASSERT_THIS_NOT_NULL \ +_Pragma("clang diagnostic push") \ +_Pragma("clang diagnostic ignored \"-Wundefined-bool-conversion\"") \ +ASSERT(this) \ +_Pragma("clang diagnostic pop") + + struct objc_class; struct objc_object; struct category_t; @@ -71,13 +81,32 @@ union isa_t { isa_t() { } isa_t(uintptr_t value) : bits(value) { } - Class cls; uintptr_t bits; + +private: + // Accessing the class requires custom ptrauth operations, so + // force clients to go through setClass/getClass by making this + // private. + Class cls; + +public: #if defined(ISA_BITFIELD) struct { ISA_BITFIELD; // defined in isa.h }; + + bool isDeallocating() { + return extra_rc == 0 && has_sidetable_rc == 0; + } + void setDeallocating() { + extra_rc = 0; + has_sidetable_rc = 0; + } #endif + + void setClass(Class cls, objc_object *obj); + Class getClass(bool authenticated); + Class getDecodedClass(bool authenticated); }; @@ -88,7 +117,7 @@ struct objc_object { public: // ISA() assumes this is NOT a tagged pointer object - Class ISA(); + Class ISA(bool authenticated = false); // rawISA() assumes this is NOT a tagged pointer object or a non pointer ISA Class rawISA(); @@ -115,6 +144,7 @@ struct objc_object { bool hasNonpointerIsa(); bool isTaggedPointer(); + bool isTaggedPointerOrNil(); bool isBasicTaggedPointer(); bool isExtTaggedPointer(); bool isClass(); @@ -156,22 +186,36 @@ struct objc_object { uintptr_t overrelease_error(); #if SUPPORT_NONPOINTER_ISA + // Controls what parts of root{Retain,Release} to emit/inline + // - Full means the full (slow) implementation + // - Fast means the fastpaths only + // - FastOrMsgSend means the fastpaths but checking whether we should call + // -retain/-release or Swift, for the usage of objc_{retain,release} + enum class RRVariant { + Full, + Fast, + FastOrMsgSend, + }; + // Unified retain count manipulation for nonpointer isa - id rootRetain(bool tryRetain, bool handleOverflow); - bool rootRelease(bool performDealloc, bool handleUnderflow); + inline id rootRetain(bool tryRetain, RRVariant variant); + inline bool rootRelease(bool performDealloc, RRVariant variant); id rootRetain_overflow(bool tryRetain); uintptr_t rootRelease_underflow(bool performDealloc); void clearDeallocating_slow(); // Side table retain count overflow for nonpointer isa + struct SidetableBorrow { size_t borrowed, remaining; }; + void sidetable_lock(); void sidetable_unlock(); void sidetable_moveExtraRC_nolock(size_t extra_rc, bool isDeallocating, bool weaklyReferenced); bool sidetable_addExtraRC_nolock(size_t delta_rc); - size_t sidetable_subExtraRC_nolock(size_t delta_rc); + SidetableBorrow sidetable_subExtraRC_nolock(size_t delta_rc); size_t sidetable_getExtraRC_nolock(); + void sidetable_clearExtraRC_nolock(); #endif // Side-table-only retain count @@ -181,10 +225,10 @@ struct objc_object { bool sidetable_isWeaklyReferenced(); void sidetable_setWeaklyReferenced_nolock(); - id sidetable_retain(); + id sidetable_retain(bool locked = false); id sidetable_retain_slow(SideTable& table); - uintptr_t sidetable_release(bool performDealloc = true); + uintptr_t sidetable_release(bool locked = false, bool performDealloc = true); uintptr_t sidetable_release_slow(SideTable& table, bool performDealloc = true); bool sidetable_tryRetain(); @@ -278,16 +322,24 @@ struct SafeRanges { } }; + struct Range shared_cache; struct Range *ranges; uint32_t count; uint32_t size : 31; uint32_t sorted : 1; public: + inline bool inSharedCache(uintptr_t ptr) const { + return shared_cache.contains(ptr); + } inline bool contains(uint16_t witness, uintptr_t ptr) const { return witness < count && ranges[witness].contains(ptr); } + inline void setSharedCacheRange(uintptr_t start, uintptr_t end) { + shared_cache = Range{start, end}; + add(start, end); + } bool find(uintptr_t ptr, uint32_t &pos); void add(uintptr_t start, uintptr_t end); void remove(uintptr_t start, uintptr_t end); @@ -295,6 +347,10 @@ struct SafeRanges { extern struct SafeRanges dataSegmentsRanges; +static inline bool inSharedCache(uintptr_t ptr) { + return dataSegmentsRanges.inSharedCache(ptr); +} + } // objc struct header_info; @@ -546,16 +602,12 @@ extern Class _calloc_class(size_t size); enum { LOOKUP_INITIALIZE = 1, LOOKUP_RESOLVER = 2, - LOOKUP_CACHE = 4, - LOOKUP_NIL = 8, + LOOKUP_NIL = 4, + LOOKUP_NOCACHE = 8, }; extern IMP lookUpImpOrForward(id obj, SEL, Class cls, int behavior); - -static inline IMP -lookUpImpOrNil(id obj, SEL sel, Class cls, int behavior = 0) -{ - return lookUpImpOrForward(obj, sel, cls, behavior | LOOKUP_CACHE | LOOKUP_NIL); -} +extern IMP lookUpImpOrForwardTryCache(id obj, SEL, Class cls, int behavior = 0); +extern IMP lookUpImpOrNilTryCache(id obj, SEL, Class cls, int behavior = 0); extern IMP lookupMethodInClassAndLoadCache(Class cls, SEL sel); @@ -816,18 +868,18 @@ __attribute__((aligned(1))) typedef int16_t unaligned_int16_t; // Global operator new and delete. We must not use any app overrides. // This ALSO REQUIRES each of these be in libobjc's unexported symbol list. -#if __cplusplus +#if __cplusplus && !defined(TEST_OVERRIDES_NEW) #pragma clang diagnostic push #pragma clang diagnostic ignored "-Winline-new-delete" #include -inline void* operator new(std::size_t size) throw (std::bad_alloc) { return malloc(size); } -inline void* operator new[](std::size_t size) throw (std::bad_alloc) { return malloc(size); } -inline void* operator new(std::size_t size, const std::nothrow_t&) throw() { return malloc(size); } -inline void* operator new[](std::size_t size, const std::nothrow_t&) throw() { return malloc(size); } -inline void operator delete(void* p) throw() { free(p); } -inline void operator delete[](void* p) throw() { free(p); } -inline void operator delete(void* p, const std::nothrow_t&) throw() { free(p); } -inline void operator delete[](void* p, const std::nothrow_t&) throw() { free(p); } +inline void* operator new(std::size_t size) { return malloc(size); } +inline void* operator new[](std::size_t size) { return malloc(size); } +inline void* operator new(std::size_t size, const std::nothrow_t&) noexcept(true) { return malloc(size); } +inline void* operator new[](std::size_t size, const std::nothrow_t&) noexcept(true) { return malloc(size); } +inline void operator delete(void* p) noexcept(true) { free(p); } +inline void operator delete[](void* p) noexcept(true) { free(p); } +inline void operator delete(void* p, const std::nothrow_t&) noexcept(true) { free(p); } +inline void operator delete[](void* p, const std::nothrow_t&) noexcept(true) { free(p); } #pragma clang diagnostic pop #endif diff --git a/runtime/objc-ptrauth.h b/runtime/objc-ptrauth.h index 388abe6..8b8ed5b 100644 --- a/runtime/objc-ptrauth.h +++ b/runtime/objc-ptrauth.h @@ -60,6 +60,12 @@ #define __ptrauth_swift_value_witness_function_pointer(__key) #endif +// Workaround Definitions of ptrauth_sign_unauthenticated and friends generate unused variables warnings +#if __has_feature(ptrauth_calls) +#define UNUSED_WITHOUT_PTRAUTH +#else +#define UNUSED_WITHOUT_PTRAUTH __unused +#endif #if __has_feature(ptrauth_calls) @@ -124,12 +130,12 @@ struct WrappedPtr { // A "ptrauth" struct that just passes pointers through unchanged. struct PtrauthRaw { template - static T *sign(T *ptr, const void *address) { + static T *sign(T *ptr, __unused const void *address) { return ptr; } template - static T *auth(T *ptr, const void *address) { + static T *auth(T *ptr, __unused const void *address) { return ptr; } }; @@ -138,12 +144,12 @@ struct PtrauthRaw { // when reading. struct PtrauthStrip { template - static T *sign(T *ptr, const void *address) { + static T *sign(T *ptr, __unused const void *address) { return ptr; } template - static T *auth(T *ptr, const void *address) { + static T *auth(T *ptr, __unused const void *address) { return ptrauth_strip(ptr, ptrauth_key_process_dependent_data); } }; @@ -153,14 +159,14 @@ struct PtrauthStrip { template struct Ptrauth { template - static T *sign(T *ptr, const void *address) { + static T *sign(T *ptr, UNUSED_WITHOUT_PTRAUTH const void *address) { if (!ptr) return nullptr; return ptrauth_sign_unauthenticated(ptr, ptrauth_key_process_dependent_data, ptrauth_blend_discriminator(address, discriminator)); } template - static T *auth(T *ptr, const void *address) { + static T *auth(T *ptr, UNUSED_WITHOUT_PTRAUTH const void *address) { if (!ptr) return nullptr; return ptrauth_auth_data(ptr, ptrauth_key_process_dependent_data, ptrauth_blend_discriminator(address, discriminator)); @@ -173,7 +179,11 @@ template using RawPtr = WrappedPtr; #if __has_feature(ptrauth_calls) // Get a ptrauth type that uses a string discriminator. +#if __BUILDING_OBJCDT__ +#define PTRAUTH_STR(name) PtrauthStrip +#else #define PTRAUTH_STR(name) Ptrauth +#endif // When ptrauth is available, declare a template that wraps a type // in a WrappedPtr that uses an authenticated pointer using the diff --git a/runtime/objc-references.h b/runtime/objc-references.h index 8c79405..71fadae 100644 --- a/runtime/objc-references.h +++ b/runtime/objc-references.h @@ -35,7 +35,7 @@ __BEGIN_DECLS extern void _objc_associations_init(); extern void _object_set_associative_reference(id object, const void *key, id value, uintptr_t policy); extern id _object_get_associative_reference(id object, const void *key); -extern void _object_remove_assocations(id object); +extern void _object_remove_assocations(id object, bool deallocating); __END_DECLS diff --git a/runtime/objc-references.mm b/runtime/objc-references.mm index caa8910..b9ea085 100644 --- a/runtime/objc-references.mm +++ b/runtime/objc-references.mm @@ -38,7 +38,8 @@ OBJC_ASSOCIATION_SETTER_COPY = 3, // NOTE: both bits are set, so we can simply test 1 bit in releaseValue below. OBJC_ASSOCIATION_GETTER_READ = (0 << 8), OBJC_ASSOCIATION_GETTER_RETAIN = (1 << 8), - OBJC_ASSOCIATION_GETTER_AUTORELEASE = (2 << 8) + OBJC_ASSOCIATION_GETTER_AUTORELEASE = (2 << 8), + OBJC_ASSOCIATION_SYSTEM_OBJECT = _OBJC_ASSOCIATION_SYSTEM_OBJECT, // 1 << 16 }; spinlock_t AssociationsManagerLock; @@ -172,6 +173,7 @@ static void init() { // retain the new value (if any) outside the lock. association.acquireValue(); + bool isFirstAssociation = false; { AssociationsManager manager; AssociationsHashMap &associations(manager.get()); @@ -180,7 +182,7 @@ static void init() { auto refs_result = associations.try_emplace(disguised, ObjectAssociationMap{}); if (refs_result.second) { /* it's the first association we make */ - object->setHasAssociatedObjects(); + isFirstAssociation = true; } /* establish or replace the association */ @@ -206,6 +208,13 @@ static void init() { } } + // Call setHasAssociatedObjects outside the lock, since this + // will call the object's _noteAssociatedObjects method if it + // has one, and this may trigger +initialize which might do + // arbitrary stuff, including setting more associated objects. + if (isFirstAssociation) + object->setHasAssociatedObjects(); + // release the old value (outside of the lock). association.releaseHeldValue(); } @@ -215,7 +224,7 @@ static void init() { // raw isa objects (such as OS Objects) that can't track // whether they have associated objects. void -_object_remove_assocations(id object) +_object_remove_assocations(id object, bool deallocating) { ObjectAssociationMap refs{}; @@ -225,12 +234,36 @@ static void init() { AssociationsHashMap::iterator i = associations.find((objc_object *)object); if (i != associations.end()) { refs.swap(i->second); - associations.erase(i); + + // If we are not deallocating, then SYSTEM_OBJECT associations are preserved. + bool didReInsert = false; + if (!deallocating) { + for (auto &ref: refs) { + if (ref.second.policy() & OBJC_ASSOCIATION_SYSTEM_OBJECT) { + i->second.insert(ref); + didReInsert = true; + } + } + } + if (!didReInsert) + associations.erase(i); } } + // Associations to be released after the normal ones. + SmallVector laterRefs; + // release everything (outside of the lock). for (auto &i: refs) { - i.second.releaseHeldValue(); + if (i.second.policy() & OBJC_ASSOCIATION_SYSTEM_OBJECT) { + // If we are not deallocating, then RELEASE_LATER associations don't get released. + if (deallocating) + laterRefs.append(&i.second); + } else { + i.second.releaseHeldValue(); + } + } + for (auto *later: laterRefs) { + later->releaseHeldValue(); } } diff --git a/runtime/objc-runtime-new.h b/runtime/objc-runtime-new.h index d6ce37c..f44a0d0 100644 --- a/runtime/objc-runtime-new.h +++ b/runtime/objc-runtime-new.h @@ -25,6 +25,7 @@ #define _OBJC_RUNTIME_NEW_H #include "PointerUnion.h" +#include // class_data_bits_t is the class_t->data field (class_rw_t pointer plus flags) // The extra bits are optimized for the retain/release and alloc/dealloc paths. @@ -94,13 +95,19 @@ // class has started realizing but not yet completed it #define RW_REALIZING (1<<19) +#if CONFIG_USE_PREOPT_CACHES +// this class and its descendants can't have preopt caches with inlined sels +#define RW_NOPREOPT_SELS (1<<2) +// this class and its descendants can't have preopt caches +#define RW_NOPREOPT_CACHE (1<<1) +#endif + // class is a metaclass (copied from ro) #define RW_META RO_META // (1<<0) // NOTE: MORE RW_ FLAGS DEFINED BELOW - // Values for class_rw_t->flags (RW_*), cache_t->_flags (FAST_CACHE_*), // or class_t->bits (FAST_*). // @@ -215,19 +222,19 @@ struct bucket_t { #endif // Compute the ptrauth signing modifier from &_imp, newSel, and cls. - uintptr_t modifierForSEL(SEL newSel, Class cls) const { - return (uintptr_t)&_imp ^ (uintptr_t)newSel ^ (uintptr_t)cls; + uintptr_t modifierForSEL(bucket_t *base, SEL newSel, Class cls) const { + return (uintptr_t)base ^ (uintptr_t)newSel ^ (uintptr_t)cls; } // Sign newImp, with &_imp, newSel, and cls as modifiers. - uintptr_t encodeImp(IMP newImp, SEL newSel, Class cls) const { + uintptr_t encodeImp(UNUSED_WITHOUT_PTRAUTH bucket_t *base, IMP newImp, UNUSED_WITHOUT_PTRAUTH SEL newSel, Class cls) const { if (!newImp) return 0; #if CACHE_IMP_ENCODING == CACHE_IMP_ENCODING_PTRAUTH return (uintptr_t) ptrauth_auth_and_resign(newImp, ptrauth_key_function_pointer, 0, ptrauth_key_process_dependent_code, - modifierForSEL(newSel, cls)); + modifierForSEL(base, newSel, cls)); #elif CACHE_IMP_ENCODING == CACHE_IMP_ENCODING_ISA_XOR return (uintptr_t)newImp ^ (uintptr_t)cls; #elif CACHE_IMP_ENCODING == CACHE_IMP_ENCODING_NONE @@ -238,10 +245,16 @@ struct bucket_t { } public: - inline SEL sel() const { return _sel.load(memory_order::memory_order_relaxed); } + static inline size_t offsetOfSel() { return offsetof(bucket_t, _sel); } + inline SEL sel() const { return _sel.load(memory_order_relaxed); } - inline IMP rawImp(objc_class *cls) const { - uintptr_t imp = _imp.load(memory_order::memory_order_relaxed); +#if CACHE_IMP_ENCODING == CACHE_IMP_ENCODING_ISA_XOR +#define MAYBE_UNUSED_ISA +#else +#define MAYBE_UNUSED_ISA __attribute__((unused)) +#endif + inline IMP rawImp(MAYBE_UNUSED_ISA objc_class *cls) const { + uintptr_t imp = _imp.load(memory_order_relaxed); if (!imp) return nil; #if CACHE_IMP_ENCODING == CACHE_IMP_ENCODING_PTRAUTH #elif CACHE_IMP_ENCODING == CACHE_IMP_ENCODING_ISA_XOR @@ -253,15 +266,15 @@ struct bucket_t { return (IMP)imp; } - inline IMP imp(Class cls) const { - uintptr_t imp = _imp.load(memory_order::memory_order_relaxed); + inline IMP imp(UNUSED_WITHOUT_PTRAUTH bucket_t *base, Class cls) const { + uintptr_t imp = _imp.load(memory_order_relaxed); if (!imp) return nil; #if CACHE_IMP_ENCODING == CACHE_IMP_ENCODING_PTRAUTH - SEL sel = _sel.load(memory_order::memory_order_relaxed); + SEL sel = _sel.load(memory_order_relaxed); return (IMP) ptrauth_auth_and_resign((const void *)imp, ptrauth_key_process_dependent_code, - modifierForSEL(sel, cls), + modifierForSEL(base, sel, cls), ptrauth_key_function_pointer, 0); #elif CACHE_IMP_ENCODING == CACHE_IMP_ENCODING_ISA_XOR return (IMP)(imp ^ (uintptr_t)cls); @@ -273,26 +286,97 @@ struct bucket_t { } template - void set(SEL newSel, IMP newImp, Class cls); + void set(bucket_t *base, SEL newSel, IMP newImp, Class cls); }; +/* dyld_shared_cache_builder and obj-C agree on these definitions */ +enum { + OBJC_OPT_METHODNAME_START = 0, + OBJC_OPT_METHODNAME_END = 1, + OBJC_OPT_INLINED_METHODS_START = 2, + OBJC_OPT_INLINED_METHODS_END = 3, + + __OBJC_OPT_OFFSETS_COUNT, +}; + +#if CONFIG_USE_PREOPT_CACHES +extern uintptr_t objc_opt_offsets[__OBJC_OPT_OFFSETS_COUNT]; +#endif + +/* dyld_shared_cache_builder and obj-C agree on these definitions */ +struct preopt_cache_entry_t { + uint32_t sel_offs; + uint32_t imp_offs; +}; + +/* dyld_shared_cache_builder and obj-C agree on these definitions */ +struct preopt_cache_t { + int32_t fallback_class_offset; + union { + struct { + uint16_t shift : 5; + uint16_t mask : 11; + }; + uint16_t hash_params; + }; + uint16_t occupied : 14; + uint16_t has_inlines : 1; + uint16_t bit_one : 1; + preopt_cache_entry_t entries[]; + + inline int capacity() const { + return mask + 1; + } +}; + +// returns: +// - the cached IMP when one is found +// - nil if there's no cached value and the cache is dynamic +// - `value_on_constant_cache_miss` if there's no cached value and the cache is preoptimized +extern "C" IMP cache_getImp(Class cls, SEL sel, IMP value_on_constant_cache_miss = nil); struct cache_t { +private: + explicit_atomic _bucketsAndMaybeMask; + union { + struct { + explicit_atomic _maybeMask; +#if __LP64__ + uint16_t _flags; +#endif + uint16_t _occupied; + }; + explicit_atomic _originalPreoptCache; + }; + #if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_OUTLINED - explicit_atomic _buckets; - explicit_atomic _mask; -#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16 - explicit_atomic _maskAndBuckets; - mask_t _mask_unused; + // _bucketsAndMaybeMask is a buckets_t pointer + // _maybeMask is the buckets mask + + static constexpr uintptr_t bucketsMask = ~0ul; + static_assert(!CONFIG_USE_PREOPT_CACHES, "preoptimized caches not supported"); +#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16_BIG_ADDRS + static constexpr uintptr_t maskShift = 48; + static constexpr uintptr_t maxMask = ((uintptr_t)1 << (64 - maskShift)) - 1; + static constexpr uintptr_t bucketsMask = ((uintptr_t)1 << maskShift) - 1; + static_assert(bucketsMask >= MACH_VM_MAX_ADDRESS, "Bucket field doesn't have enough bits for arbitrary pointers."); +#if CONFIG_USE_PREOPT_CACHES + static constexpr uintptr_t preoptBucketsMarker = 1ul; + static constexpr uintptr_t preoptBucketsMask = bucketsMask & ~preoptBucketsMarker; +#endif +#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16 + // _bucketsAndMaybeMask is a buckets_t pointer in the low 48 bits + // _maybeMask is unused, the mask is stored in the top 16 bits. + // How much the mask is shifted by. static constexpr uintptr_t maskShift = 48; - + // Additional bits after the mask which must be zero. msgSend // takes advantage of these additional bits to construct the value // `mask << 4` from `_maskAndBuckets` in a single instruction. static constexpr uintptr_t maskZeroBits = 4; - + // The largest mask value we can store. static constexpr uintptr_t maxMask = ((uintptr_t)1 << (64 - maskShift)) - 1; @@ -300,40 +384,107 @@ struct cache_t { static constexpr uintptr_t bucketsMask = ((uintptr_t)1 << (maskShift - maskZeroBits)) - 1; // Ensure we have enough bits for the buckets pointer. - static_assert(bucketsMask >= MACH_VM_MAX_ADDRESS, "Bucket field doesn't have enough bits for arbitrary pointers."); + static_assert(bucketsMask >= MACH_VM_MAX_ADDRESS, + "Bucket field doesn't have enough bits for arbitrary pointers."); + +#if CONFIG_USE_PREOPT_CACHES + static constexpr uintptr_t preoptBucketsMarker = 1ul; +#if __has_feature(ptrauth_calls) + // 63..60: hash_mask_shift + // 59..55: hash_shift + // 54.. 1: buckets ptr + auth + // 0: always 1 + static constexpr uintptr_t preoptBucketsMask = 0x007ffffffffffffe; + static inline uintptr_t preoptBucketsHashParams(const preopt_cache_t *cache) { + uintptr_t value = (uintptr_t)cache->shift << 55; + // masks have 11 bits but can be 0, so we compute + // the right shift for 0x7fff rather than 0xffff + return value | ((objc::mask16ShiftBits(cache->mask) - 1) << 60); + } +#else + // 63..53: hash_mask + // 52..48: hash_shift + // 47.. 1: buckets ptr + // 0: always 1 + static constexpr uintptr_t preoptBucketsMask = 0x0000fffffffffffe; + static inline uintptr_t preoptBucketsHashParams(const preopt_cache_t *cache) { + return (uintptr_t)cache->hash_params << 48; + } +#endif +#endif // CONFIG_USE_PREOPT_CACHES #elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_LOW_4 - // _maskAndBuckets stores the mask shift in the low 4 bits, and - // the buckets pointer in the remainder of the value. The mask - // shift is the value where (0xffff >> shift) produces the correct - // mask. This is equal to 16 - log2(cache_size). - explicit_atomic _maskAndBuckets; - mask_t _mask_unused; + // _bucketsAndMaybeMask is a buckets_t pointer in the top 28 bits + // _maybeMask is unused, the mask length is stored in the low 4 bits static constexpr uintptr_t maskBits = 4; static constexpr uintptr_t maskMask = (1 << maskBits) - 1; static constexpr uintptr_t bucketsMask = ~maskMask; + static_assert(!CONFIG_USE_PREOPT_CACHES, "preoptimized caches not supported"); #else #error Unknown cache mask storage type. #endif - -#if __LP64__ - uint16_t _flags; + + bool isConstantEmptyCache() const; + bool canBeFreed() const; + mask_t mask() const; + +#if CONFIG_USE_PREOPT_CACHES + void initializeToPreoptCacheInDisguise(const preopt_cache_t *cache); + const preopt_cache_t *disguised_preopt_cache() const; #endif - uint16_t _occupied; -public: - static bucket_t *emptyBuckets(); - - struct bucket_t *buckets(); - mask_t mask(); - mask_t occupied(); void incrementOccupied(); void setBucketsAndMask(struct bucket_t *newBuckets, mask_t newMask); + + void reallocate(mask_t oldCapacity, mask_t newCapacity, bool freeOld); + void collect_free(bucket_t *oldBuckets, mask_t oldCapacity); + + static bucket_t *emptyBuckets(); + static bucket_t *allocateBuckets(mask_t newCapacity); + static bucket_t *emptyBucketsForCapacity(mask_t capacity, bool allocate = true); + static struct bucket_t * endMarker(struct bucket_t *b, uint32_t cap); + void bad_cache(id receiver, SEL sel) __attribute__((noreturn, cold)); + +public: + // The following four fields are public for objcdt's use only. + // objcdt reaches into fields while the process is suspended + // hence doesn't care for locks and pesky little details like this + // and can safely use these. + unsigned capacity() const; + struct bucket_t *buckets() const; + Class cls() const; + +#if CONFIG_USE_PREOPT_CACHES + const preopt_cache_t *preopt_cache() const; +#endif + + mask_t occupied() const; void initializeToEmpty(); - unsigned capacity(); - bool isConstantEmptyCache(); - bool canBeFreed(); +#if CONFIG_USE_PREOPT_CACHES + bool isConstantOptimizedCache(bool strict = false, uintptr_t empty_addr = (uintptr_t)&_objc_empty_cache) const; + bool shouldFlush(SEL sel, IMP imp) const; + bool isConstantOptimizedCacheWithInlinedSels() const; + Class preoptFallbackClass() const; + void maybeConvertToPreoptimized(); + void initializeToEmptyOrPreoptimizedInDisguise(); +#else + inline bool isConstantOptimizedCache(bool strict = false, uintptr_t empty_addr = 0) const { return false; } + inline bool shouldFlush(SEL sel, IMP imp) const { + return cache_getImp(cls(), sel) == imp; + } + inline bool isConstantOptimizedCacheWithInlinedSels() const { return false; } + inline void initializeToEmptyOrPreoptimizedInDisguise() { initializeToEmpty(); } +#endif + + void insert(SEL sel, IMP imp, id receiver); + void copyCacheNolock(objc_imp_cache_entry *buffer, int len); + void destroy(); + void eraseNolock(const char *func); + + static void init(); + static void collectNolock(bool collectALot); + static size_t bytesForCapacity(uint32_t cap); #if __LP64__ bool getBit(uint16_t flags) const { @@ -396,14 +547,6 @@ struct cache_t { // nothing } #endif - - static size_t bytesForCapacity(uint32_t cap); - static struct bucket_t * endMarker(struct bucket_t *b, uint32_t cap); - - void reallocate(mask_t oldCapacity, mask_t newCapacity, bool freeOld); - void insert(Class cls, SEL sel, IMP imp, id receiver); - - static void bad_cache(id receiver, SEL sel, Class isa) __attribute__((noreturn, cold)); }; @@ -424,6 +567,8 @@ struct RelativePointer: nocopy_t { int32_t offset; T get() const { + if (offset == 0) + return nullptr; uintptr_t base = (uintptr_t)&offset; uintptr_t signExtendedOffset = (uintptr_t)(intptr_t)offset; uintptr_t pointer = base + signExtendedOffset; @@ -445,7 +590,7 @@ struct stub_class_t { // A pointer modifier that does nothing to the pointer. struct PointerModifierNop { template - static T *modify(const ListType &list, T *ptr) { return ptr; } + static T *modify(__unused const ListType &list, T *ptr) { return ptr; } }; /*********************************************************************** @@ -573,6 +718,11 @@ struct entsize_list_tt { }; +namespace objc { +// Let method_t::small use this from objc-private.h. +static inline bool inSharedCache(uintptr_t ptr); +} + struct method_t { static const uint32_t smallMethodListFlag = 0x80000000; @@ -595,9 +745,16 @@ struct method_t { // The representation of a "small" method. This stores three // relative offsets to the name, types, and implementation. struct small { - RelativePointer name; + // The name field either refers to a selector (in the shared + // cache) or a selref (everywhere else). + RelativePointer name; RelativePointer types; RelativePointer imp; + + bool inSharedCache() const { + return (CONFIG_SHARED_CACHE_RELATIVE_DIRECT_SELECTORS && + objc::inSharedCache((uintptr_t)this)); + } }; small &small() const { @@ -631,8 +788,14 @@ struct method_t { return *(struct big *)this; } - SEL &name() const { - return isSmall() ? *small().name.get() : big().name; + SEL name() const { + if (isSmall()) { + return (small().inSharedCache() + ? (SEL)small().name.get() + : *(SEL *)small().name.get()); + } else { + return big().name; + } } const char *types() const { return isSmall() ? small().types.get() : big().types; @@ -648,13 +811,31 @@ struct method_t { return big().imp; } + SEL getSmallNameAsSEL() const { + ASSERT(small().inSharedCache()); + return (SEL)small().name.get(); + } + + SEL getSmallNameAsSELRef() const { + ASSERT(!small().inSharedCache()); + return *(SEL *)small().name.get(); + } + + void setName(SEL name) { + if (isSmall()) { + ASSERT(!small().inSharedCache()); + *(SEL *)small().name.get() = name; + } else { + big().name = name; + } + } + void setImp(IMP imp) { if (isSmall()) { remapImp(imp); } else { big().imp = imp; } - } objc_method_description *getDescription() const { @@ -800,7 +981,7 @@ struct protocol_t : objc_object { bool isCanonical() const; void clearIsCanonical(); -# define HAS_FIELD(f) (size >= offsetof(protocol_t, f) + sizeof(f)) +# define HAS_FIELD(f) ((uintptr_t)(&f) < ((uintptr_t)this + size)) bool hasExtendedMethodTypesField() const { return HAS_FIELD(_extendedMethodTypes); @@ -861,10 +1042,15 @@ struct class_ro_t { uint32_t reserved; #endif - const uint8_t * ivarLayout; - - const char * name; - WrappedPtr baseMethodList; + union { + const uint8_t * ivarLayout; + Class nonMetaclass; + }; + + explicit_atomic name; + // With ptrauth, this is signed if it points to a small list, but + // may be unsigned if it points to a big list. + void *baseMethodList; protocol_list_t * baseProtocols; const ivar_list_t * ivars; @@ -882,21 +1068,105 @@ struct class_ro_t { } } + const char *getName() const { + return name.load(std::memory_order_acquire); + } + + static const uint16_t methodListPointerDiscriminator = 0xC310; +#if 0 // FIXME: enable this when we get a non-empty definition of __ptrauth_objc_method_list_pointer from ptrauth.h. + static_assert(std::is_same< + void * __ptrauth_objc_method_list_pointer *, + void * __ptrauth(ptrauth_key_method_list_pointer, 1, methodListPointerDiscriminator) *>::value, + "Method list pointer signing discriminator must match ptrauth.h"); +#endif + method_list_t *baseMethods() const { - return baseMethodList; +#if __has_feature(ptrauth_calls) + method_list_t *ptr = ptrauth_strip((method_list_t *)baseMethodList, ptrauth_key_method_list_pointer); + if (ptr == nullptr) + return nullptr; + + // Don't auth if the class_ro and the method list are both in the shared cache. + // This is secure since they'll be read-only, and this allows the shared cache + // to cut down on the number of signed pointers it has. + bool roInSharedCache = objc::inSharedCache((uintptr_t)this); + bool listInSharedCache = objc::inSharedCache((uintptr_t)ptr); + if (roInSharedCache && listInSharedCache) + return ptr; + + // Auth all other small lists. + if (ptr->isSmallList()) + ptr = ptrauth_auth_data((method_list_t *)baseMethodList, + ptrauth_key_method_list_pointer, + ptrauth_blend_discriminator(&baseMethodList, + methodListPointerDiscriminator)); + return ptr; +#else + return (method_list_t *)baseMethodList; +#endif + } + + uintptr_t baseMethodListPtrauthData() const { + return ptrauth_blend_discriminator(&baseMethodList, + methodListPointerDiscriminator); } class_ro_t *duplicate() const { - if (flags & RO_HAS_SWIFT_INITIALIZER) { - size_t size = sizeof(*this) + sizeof(_swiftMetadataInitializer_NEVER_USE[0]); - class_ro_t *ro = (class_ro_t *)memdup(this, size); + bool hasSwiftInitializer = flags & RO_HAS_SWIFT_INITIALIZER; + + size_t size = sizeof(*this); + if (hasSwiftInitializer) + size += sizeof(_swiftMetadataInitializer_NEVER_USE[0]); + + class_ro_t *ro = (class_ro_t *)memdup(this, size); + + if (hasSwiftInitializer) ro->_swiftMetadataInitializer_NEVER_USE[0] = this->_swiftMetadataInitializer_NEVER_USE[0]; - return ro; + +#if __has_feature(ptrauth_calls) + // Re-sign the method list pointer if it was signed. + // NOTE: It is possible for a signed pointer to have a signature + // that is all zeroes. This is indistinguishable from a raw pointer. + // This code will treat such a pointer as signed and re-sign it. A + // false positive is safe: method list pointers are either authed or + // stripped, so if baseMethods() doesn't expect it to be signed, it + // will ignore the signature. + void *strippedBaseMethodList = ptrauth_strip(baseMethodList, ptrauth_key_method_list_pointer); + void *signedBaseMethodList = ptrauth_sign_unauthenticated(strippedBaseMethodList, + ptrauth_key_method_list_pointer, + baseMethodListPtrauthData()); + if (baseMethodList == signedBaseMethodList) { + ro->baseMethodList = ptrauth_auth_and_resign(baseMethodList, + ptrauth_key_method_list_pointer, + baseMethodListPtrauthData(), + ptrauth_key_method_list_pointer, + ro->baseMethodListPtrauthData()); } else { - size_t size = sizeof(*this); - class_ro_t *ro = (class_ro_t *)memdup(this, size); - return ro; + // Special case: a class_ro_t in the shared cache pointing to a + // method list in the shared cache will not have a signed pointer, + // but the duplicate will be expected to have a signed pointer since + // it's not in the shared cache. Detect that and sign it. + bool roInSharedCache = objc::inSharedCache((uintptr_t)this); + bool listInSharedCache = objc::inSharedCache((uintptr_t)strippedBaseMethodList); + if (roInSharedCache && listInSharedCache) + ro->baseMethodList = ptrauth_sign_unauthenticated(strippedBaseMethodList, + ptrauth_key_method_list_pointer, + ro->baseMethodListPtrauthData()); } +#endif + + return ro; + } + + Class getNonMetaclass() const { + ASSERT(flags & RO_META); + return nonMetaclass; + } + + const uint8_t *getIvarLayout() const { + if (flags & RO_META) + return nullptr; + return ivarLayout; } }; @@ -1036,10 +1306,9 @@ class list_array_tt { return iterator(e, e); } - - uint32_t countLists() { + inline uint32_t countLists(const std::function & peek) const { if (hasArray()) { - return array()->count; + return peek(array())->count; } else if (list) { return 1; } else { @@ -1047,6 +1316,10 @@ class list_array_tt { } } + uint32_t countLists() { + return countLists([](array_t *x) { return x; }); + } + const Ptr* beginLists() const { if (hasArray()) { return array()->lists; @@ -1317,12 +1590,10 @@ struct class_data_bits_t { void setAndClearBits(uintptr_t set, uintptr_t clear) { ASSERT((set & clear) == 0); - uintptr_t oldBits; - uintptr_t newBits; + uintptr_t newBits, oldBits = LoadExclusive(&bits); do { - oldBits = LoadExclusive(&bits); newBits = (oldBits | set) & ~clear; - } while (!StoreReleaseExclusive(&bits, oldBits, newBits)); + } while (slowpath(!StoreReleaseExclusive(&bits, &oldBits, newBits))); } void setBits(uintptr_t set) { @@ -1352,7 +1623,7 @@ struct class_data_bits_t { // Get the class's ro data, even in the presence of concurrent realization. // fixme this isn't really safe without a compiler barrier at least // and probably a memory barrier when realizeClass changes the data field - const class_ro_t *safe_ro() { + const class_ro_t *safe_ro() const { class_rw_t *maybe_rw = data(); if (maybe_rw->flags & RW_REALIZED) { // maybe_rw is rw @@ -1363,13 +1634,16 @@ struct class_data_bits_t { } } - void setClassArrayIndex(unsigned Idx) { #if SUPPORT_INDEXED_ISA + void setClassArrayIndex(unsigned Idx) { // 0 is unused as then we can rely on zero-initialisation from calloc. ASSERT(Idx > 0); data()->index = Idx; -#endif } +#else + void setClassArrayIndex(__unused unsigned Idx) { + } +#endif unsigned classArrayIndex() { #if SUPPORT_INDEXED_ISA @@ -1412,11 +1686,49 @@ struct class_data_bits_t { struct objc_class : objc_object { + objc_class(const objc_class&) = delete; + objc_class(objc_class&&) = delete; + void operator=(const objc_class&) = delete; + void operator=(objc_class&&) = delete; // Class ISA; Class superclass; cache_t cache; // formerly cache pointer and vtable class_data_bits_t bits; // class_rw_t * plus custom rr/alloc flags + Class getSuperclass() const { +#if __has_feature(ptrauth_calls) +# if ISA_SIGNING_AUTH_MODE == ISA_SIGNING_AUTH + if (superclass == Nil) + return Nil; + +#if SUPERCLASS_SIGNING_TREAT_UNSIGNED_AS_NIL + void *stripped = ptrauth_strip((void *)superclass, ISA_SIGNING_KEY); + if ((void *)superclass == stripped) { + void *resigned = ptrauth_sign_unauthenticated(stripped, ISA_SIGNING_KEY, ptrauth_blend_discriminator(&superclass, ISA_SIGNING_DISCRIMINATOR_CLASS_SUPERCLASS)); + if ((void *)superclass != resigned) + return Nil; + } +#endif + + void *result = ptrauth_auth_data((void *)superclass, ISA_SIGNING_KEY, ptrauth_blend_discriminator(&superclass, ISA_SIGNING_DISCRIMINATOR_CLASS_SUPERCLASS)); + return (Class)result; + +# else + return (Class)ptrauth_strip((void *)superclass, ISA_SIGNING_KEY); +# endif +#else + return superclass; +#endif + } + + void setSuperclass(Class newSuperclass) { +#if ISA_SIGNING_SIGN_MODE == ISA_SIGNING_SIGN_ALL + superclass = (Class)ptrauth_sign_unauthenticated((void *)newSuperclass, ISA_SIGNING_KEY, ptrauth_blend_discriminator(&superclass, ISA_SIGNING_DISCRIMINATOR_CLASS_SUPERCLASS)); +#else + superclass = newSuperclass; +#endif + } + class_rw_t *data() const { return bits.data(); } @@ -1568,6 +1880,30 @@ struct objc_class : objc_object { void setInstancesRequireRawIsaRecursively(bool inherited = false); void printInstancesRequireRawIsa(bool inherited); +#if CONFIG_USE_PREOPT_CACHES + bool allowsPreoptCaches() const { + return !(bits.data()->flags & RW_NOPREOPT_CACHE); + } + bool allowsPreoptInlinedSels() const { + return !(bits.data()->flags & RW_NOPREOPT_SELS); + } + void setDisallowPreoptCaches() { + bits.data()->setFlags(RW_NOPREOPT_CACHE | RW_NOPREOPT_SELS); + } + void setDisallowPreoptInlinedSels() { + bits.data()->setFlags(RW_NOPREOPT_SELS); + } + void setDisallowPreoptCachesRecursively(const char *why); + void setDisallowPreoptInlinedSelsRecursively(const char *why); +#else + bool allowsPreoptCaches() const { return false; } + bool allowsPreoptInlinedSels() const { return false; } + void setDisallowPreoptCaches() { } + void setDisallowPreoptInlinedSels() { } + void setDisallowPreoptCachesRecursively(const char *why) { } + void setDisallowPreoptInlinedSelsRecursively(const char *why) { } +#endif + bool canAllocNonpointer() { ASSERT(!isFuture()); return !instancesRequireRawIsa(); @@ -1589,6 +1925,28 @@ struct objc_class : objc_object { return bits.isSwiftStable_ButAllowLegacyForNow(); } + uint32_t swiftClassFlags() { + return *(uint32_t *)(&bits + 1); + } + + bool usesSwiftRefcounting() { + if (!isSwiftStable()) return false; + return bool(swiftClassFlags() & 2); //ClassFlags::UsesSwiftRefcounting + } + + bool canCallSwiftRR() { + // !hasCustomCore() is being used as a proxy for isInitialized(). All + // classes with Swift refcounting are !hasCustomCore() (unless there are + // category or swizzling shenanigans), but that bit is not set until a + // class is initialized. Checking isInitialized requires an extra + // indirection that we want to avoid on RR fast paths. + // + // In the unlikely event that someone causes a class with Swift + // refcounting to be hasCustomCore(), we'll fall back to sending -retain + // or -release, which is still correct. + return !hasCustomCore() && usesSwiftRefcounting(); + } + bool isStubClass() const { uintptr_t isa = (uintptr_t)isaBits(); return 1 <= isa && isa < 16; @@ -1608,8 +1966,7 @@ struct objc_class : objc_object { // Check the true legacy vs stable distinguisher. // The low bit of Swift's ClassFlags is SET for true legacy // and UNSET for stable pretending to be legacy. - uint32_t swiftClassFlags = *(uint32_t *)(&bits + 1); - bool isActuallySwiftLegacy = bool(swiftClassFlags & 1); + bool isActuallySwiftLegacy = bool(swiftClassFlags() & 1); return !isActuallySwiftLegacy; } @@ -1695,11 +2052,13 @@ struct objc_class : objc_object { // Returns true if this is an unrealized future class. // Locking: To prevent concurrent realization, hold runtimeLock. bool isFuture() const { + if (isStubClass()) + return false; return data()->flags & RW_FUTURE; } - bool isMetaClass() { - ASSERT(this); + bool isMetaClass() const { + ASSERT_THIS_NOT_NULL; ASSERT(isRealized()); #if FAST_CACHE_META return cache.getBit(FAST_CACHE_META); @@ -1712,31 +2071,46 @@ struct objc_class : objc_object { bool isMetaClassMaybeUnrealized() { static_assert(offsetof(class_rw_t, flags) == offsetof(class_ro_t, flags), "flags alias"); static_assert(RO_META == RW_META, "flags alias"); + if (isStubClass()) + return false; return data()->flags & RW_META; } // NOT identical to this->ISA when this is a metaclass Class getMeta() { - if (isMetaClass()) return (Class)this; + if (isMetaClassMaybeUnrealized()) return (Class)this; else return this->ISA(); } bool isRootClass() { - return superclass == nil; + return getSuperclass() == nil; } bool isRootMetaclass() { return ISA() == (Class)this; } + + // If this class does not have a name already, we can ask Swift to construct one for us. + const char *installMangledNameForLazilyNamedClass(); + + // Get the class's mangled name, or NULL if the class has a lazy + // name that hasn't been created yet. + const char *nonlazyMangledName() const { + return bits.safe_ro()->getName(); + } const char *mangledName() { // fixme can't assert locks here - ASSERT(this); + ASSERT_THIS_NOT_NULL; - if (isRealized() || isFuture()) { - return data()->ro()->name; - } else { - return ((const class_ro_t *)data())->name; + const char *result = nonlazyMangledName(); + + if (!result) { + // This class lazily instantiates its name. Emplace and + // return it. + result = installMangledNameForLazilyNamedClass(); } + + return result; } const char *demangledName(bool needsLock); @@ -1765,7 +2139,7 @@ struct objc_class : objc_object { return word_align(unalignedInstanceSize()); } - size_t instanceSize(size_t extraBytes) const { + inline size_t instanceSize(size_t extraBytes) const { if (fastpath(cache.hasFastInstanceSize(extraBytes))) { return cache.fastInstanceSize(extraBytes); } diff --git a/runtime/objc-runtime-new.mm b/runtime/objc-runtime-new.mm index 1eabd5c..df3f9fa 100644 --- a/runtime/objc-runtime-new.mm +++ b/runtime/objc-runtime-new.mm @@ -32,7 +32,6 @@ #include "objc-private.h" #include "objc-runtime-new.h" #include "objc-file.h" -#include "objc-cache.h" #include "objc-zalloc.h" #include #include @@ -48,7 +47,7 @@ static method_t *search_method_list(const method_list_t *mlist, SEL sel); template static bool method_lists_contains_any(T *mlists, T *end, SEL sels[], size_t selcount); -static void flushCaches(Class cls); +static void flushCaches(Class cls, const char *func, bool (^predicate)(Class c)); static void initializeTaggedPointerObfuscator(void); #if SUPPORT_FIXUP static void fixupMessageRef(message_ref_t *msg); @@ -151,7 +150,19 @@ asm("\n .globl _objc_absolute_packed_isa_class_mask" \ "\n _objc_absolute_packed_isa_class_mask = " STRINGIFY2(ISA_MASK)); -const uintptr_t objc_debug_isa_class_mask = ISA_MASK; +// a better definition is +// (uintptr_t)ptrauth_strip((void *)ISA_MASK, ISA_SIGNING_KEY) +// however we know that PAC uses bits outside of MACH_VM_MAX_ADDRESS +// so approximate the definition here to be constant +template +static constexpr T coveringMask(T n) { + for (T mask = 0; mask != ~T{0}; mask = (mask << 1) | 1) { + if ((n & mask) == n) return mask; + } + return ~T{0}; +} +const uintptr_t objc_debug_isa_class_mask = ISA_MASK & coveringMask(MACH_VM_MAX_ADDRESS - 1); + const uintptr_t objc_debug_isa_magic_mask = ISA_MAGIC_MASK; const uintptr_t objc_debug_isa_magic_value = ISA_MAGIC_VALUE; @@ -267,15 +278,9 @@ static IMP method_t_remappedImp_nolock(const method_t *m) { /* Low two bits of mlist->entsize is used as the fixed-up marker. - PREOPTIMIZED VERSION: Method lists from shared cache are 1 (uniqued) or 3 (uniqued and sorted). (Protocol method lists are not sorted because of their extra parallel data) Runtime fixed-up method lists get 3. - UN-PREOPTIMIZED VERSION: - Method lists from shared cache are 1 (uniqued) or 3 (uniqued and sorted) - Shared cache's sorting and uniquing are not trusted, but do affect the - location of the selector name string. - Runtime fixed-up method lists get 2. High two bits of protocol->flags is used as the fixed-up marker. PREOPTIMIZED VERSION: @@ -287,18 +292,14 @@ static IMP method_t_remappedImp_nolock(const method_t *m) { Runtime fixed-up protocols get 3<<30. */ -static uint32_t fixed_up_method_list = 3; -static uint32_t uniqued_method_list = 1; +static const uint32_t fixed_up_method_list = 3; +static const uint32_t uniqued_method_list = 1; static uint32_t fixed_up_protocol = PROTOCOL_FIXED_UP_1; static uint32_t canonical_protocol = PROTOCOL_IS_CANONICAL; void disableSharedCacheOptimizations(void) { - fixed_up_method_list = 2; - // It is safe to set uniqued method lists to 0 as we'll never call it unless - // the method list was already in need of being fixed up - uniqued_method_list = 0; fixed_up_protocol = PROTOCOL_FIXED_UP_1 | PROTOCOL_FIXED_UP_2; // Its safe to just set canonical protocol to 0 as we'll never call // clearIsCanonical() unless isCanonical() returned true, which can't happen @@ -437,8 +438,7 @@ void _objc_setClassCopyFixupHandler(void (* _Nonnull newFixupHandler) { uint8_t *base = (uint8_t *)obj; - if (!obj) return nil; - if (obj->isTaggedPointer()) return nil; + if (obj->isTaggedPointerOrNil()) return nil; if (!obj->isClass()) return base + obj->ISA()->alignedInstanceSize(); @@ -682,7 +682,7 @@ static unsigned unreasonableClassCount() cls = cls->data()->firstSubclass; } else { while (!cls->data()->nextSiblingClass && cls != top) { - cls = cls->superclass; + cls = cls->getSuperclass(); if (--count == 0) { _objc_fatal("Memory corruption in class list."); } @@ -852,20 +852,20 @@ static void __attribute__((cold, noinline)) static void scanAddedClassImpl(Class cls, bool isMeta) { - Class NSOClass = (isMeta ? metaclassNSObject() : classNSObject()); bool setCustom = NO, inherited = NO; if (isNSObjectSwizzled(isMeta)) { setCustom = YES; - } else if (cls == NSOClass) { - // NSObject is default but we need to check categories + } else if (Traits::knownClassHasDefaultImpl(cls, isMeta)) { + // This class is known to have the default implementations, + // but we need to check categories. auto &methods = as_objc_class(cls)->data()->methods(); setCustom = Traits::scanMethodLists(methods.beginCategoryMethodLists(), methods.endCategoryMethodLists(cls)); - } else if (!isMeta && !as_objc_class(cls)->superclass) { + } else if (!isMeta && !as_objc_class(cls)->getSuperclass()) { // Custom Root class setCustom = YES; - } else if (Traits::isCustom(as_objc_class(cls)->superclass)) { + } else if (Traits::isCustom(as_objc_class(cls)->getSuperclass())) { // Superclass is custom, therefore we are too. setCustom = YES; inherited = YES; @@ -883,6 +883,14 @@ static void __attribute__((cold, noinline)) } public: + static bool knownClassHasDefaultImpl(Class cls, bool isMeta) { + // Typically only NSObject has default implementations. + // Allow this to be extended by overriding (to allow + // SwiftObject, for example). + Class NSOClass = (isMeta ? metaclassNSObject() : classNSObject()); + return cls == NSOClass; + } + // Scan a class that is about to be marked Initialized for particular // bundles of selectors, and mark the class and its children // accordingly. @@ -1047,6 +1055,16 @@ static bool scanMethodLists(T *mlists, T *end) { // // +new, ±class, ±self, ±isKindOfClass:, ±respondsToSelector struct CoreScanner : scanner::Mixin { + static bool knownClassHasDefaultImpl(Class cls, bool isMeta) { + if (scanner::Mixin::knownClassHasDefaultImpl(cls, isMeta)) + return true; + if ((cls->isRootClass() || cls->isRootMetaclass()) + && strcmp(cls->mangledName(), "_TtCs12_SwiftObject") == 0) + return true; + + return false; + } + static bool isCustom(Class cls) { return cls->hasCustomCore(); } @@ -1171,7 +1189,7 @@ void addForClass(locstamped_category_t lc, Class cls) if (slowpath(PrintConnecting)) { _objc_inform("CLASS: found category %c%s(%s)", - cls->isMetaClass() ? '+' : '-', + cls->isMetaClassMaybeUnrealized() ? '+' : '-', cls->nameForLogging(), lc.cat->name); } @@ -1251,7 +1269,7 @@ static bool isBundleClass(Class cls) // Unique selectors in list. for (auto& meth : *mlist) { const char *name = sel_cname(meth.name()); - meth.name() = sel_registerNameNoLock(name, bundleCopy); + meth.setName(sel_registerNameNoLock(name, bundleCopy)); } } @@ -1274,7 +1292,7 @@ static bool isBundleClass(Class cls) static void prepareMethodLists(Class cls, method_list_t **addedLists, int addedCount, - bool baseMethods, bool methodsFromBundle) + bool baseMethods, bool methodsFromBundle, const char *why) { runtimeLock.assertLocked(); @@ -1286,6 +1304,16 @@ static bool isBundleClass(Class cls) // Therefore we need not handle any special cases here. if (baseMethods) { ASSERT(cls->hasCustomAWZ() && cls->hasCustomRR() && cls->hasCustomCore()); + } else if (cls->cache.isConstantOptimizedCache()) { + cls->setDisallowPreoptCachesRecursively(why); + } else if (cls->allowsPreoptInlinedSels()) { +#if CONFIG_USE_PREOPT_CACHES + SEL *sels = (SEL *)objc_opt_offsets[OBJC_OPT_INLINED_METHODS_START]; + SEL *sels_end = (SEL *)objc_opt_offsets[OBJC_OPT_INLINED_METHODS_END]; + if (method_lists_contains_any(addedLists, addedLists + addedCount, sels, sels_end - sels)) { + cls->setDisallowPreoptInlinedSelsRecursively(why); + } +#endif } // Add method lists to array. @@ -1390,7 +1418,7 @@ static bool isBundleClass(Class cls) method_list_t *mlist = entry.cat->methodsForMeta(isMeta); if (mlist) { if (mcount == ATTACH_BUFSIZ) { - prepareMethodLists(cls, mlists, mcount, NO, fromBundle); + prepareMethodLists(cls, mlists, mcount, NO, fromBundle, __func__); rwe->methods.attachLists(mlists, mcount); mcount = 0; } @@ -1419,9 +1447,16 @@ static bool isBundleClass(Class cls) } if (mcount > 0) { - prepareMethodLists(cls, mlists + ATTACH_BUFSIZ - mcount, mcount, NO, fromBundle); + prepareMethodLists(cls, mlists + ATTACH_BUFSIZ - mcount, mcount, + NO, fromBundle, __func__); rwe->methods.attachLists(mlists + ATTACH_BUFSIZ - mcount, mcount); - if (flags & ATTACH_EXISTING) flushCaches(cls); + if (flags & ATTACH_EXISTING) { + flushCaches(cls, __func__, [](Class c){ + // constant caches have been dealt with in prepareMethodLists + // if the class still is constant here, it's fine to keep + return !c->cache.isConstantOptimizedCache(); + }); + } } rwe->properties.attachLists(proplists + ATTACH_BUFSIZ - propcount, propcount); @@ -1454,10 +1489,7 @@ static void methodizeClass(Class cls, Class previously) // Install methods and properties that the class implements itself. method_list_t *list = ro->baseMethods(); if (list) { - if (list->isSmallList() && !_dyld_is_memory_immutable(list, list->byteSize())) - _objc_fatal("CLASS: class '%s' %p small method list %p is not in immutable memory", - cls->nameForLogging(), cls, list); - prepareMethodLists(cls, &list, 1, YES, isBundleClass(cls)); + prepareMethodLists(cls, &list, 1, YES, isBundleClass(cls), nullptr); if (rwe) rwe->methods.attachLists(&list, 1); } @@ -1690,6 +1722,8 @@ static bool scanMangledField(const char *&string, const char *end, // This is a misnomer: gdb_objc_realized_classes is actually a list of // named classes not in the dyld shared cache, whether realized or not. +// This list excludes lazily named classes, which have to be looked up +// using a getClass hook. NXMapTable *gdb_objc_realized_classes; // exported for debuggers in objc-gdb.h uintptr_t objc_debug_realized_class_generation_count; @@ -1820,7 +1854,7 @@ static void addFutureNamedClass(const char *name, Class cls) class_rw_t *rw = objc::zalloc(); class_ro_t *ro = (class_ro_t *)calloc(sizeof(class_ro_t), 1); - ro->name = strdupIfMutable(name); + ro->name.store(strdupIfMutable(name), std::memory_order_relaxed); rw->set_ro(ro); cls->setData(rw); cls->data()->flags = RO_FUTURE; @@ -2012,7 +2046,7 @@ static Class getMaybeUnrealizedNonMetaClass(Class metacls, id inst) // special case for root metaclass // where inst == inst->ISA() == metacls is possible if (metacls->ISA() == metacls) { - Class cls = metacls->superclass; + Class cls = metacls->getSuperclass(); ASSERT(cls->isRealized()); ASSERT(!cls->isMetaClass()); ASSERT(cls->ISA() == metacls); @@ -2030,7 +2064,7 @@ static Class getMaybeUnrealizedNonMetaClass(Class metacls, id inst) ASSERT(!cls->isMetaClassMaybeUnrealized()); return cls; } - cls = cls->superclass; + cls = cls->getSuperclass(); } #if DEBUG _objc_fatal("cls is not an instance of metacls"); @@ -2039,6 +2073,10 @@ static Class getMaybeUnrealizedNonMetaClass(Class metacls, id inst) #endif } + // See if the metaclass has a pointer to its nonmetaclass. + if (Class cls = metacls->bits.safe_ro()->getNonMetaclass()) + return cls; + // try name lookup { Class cls = getClassExceptSomeSwift(metacls->mangledName()); @@ -2263,9 +2301,15 @@ static void addSubclass(Class supercls, Class subcls) objc::RRScanner::scanAddedSubClass(subcls, supercls); objc::CoreScanner::scanAddedSubClass(subcls, supercls); + if (!supercls->allowsPreoptCaches()) { + subcls->setDisallowPreoptCachesRecursively(__func__); + } else if (!supercls->allowsPreoptInlinedSels()) { + subcls->setDisallowPreoptInlinedSelsRecursively(__func__); + } + // Special case: instancesRequireRawIsa does not propagate // from root class to root metaclass - if (supercls->instancesRequireRawIsa() && supercls->superclass) { + if (supercls->instancesRequireRawIsa() && supercls->getSuperclass()) { subcls->setInstancesRequireRawIsaRecursively(true); } } @@ -2282,7 +2326,7 @@ static void removeSubclass(Class supercls, Class subcls) runtimeLock.assertLocked(); ASSERT(supercls->isRealized()); ASSERT(subcls->isRealized()); - ASSERT(subcls->superclass == supercls); + ASSERT(subcls->getSuperclass() == supercls); objc_debug_realized_class_generation_count++; @@ -2329,15 +2373,23 @@ static void removeSubclass(Class supercls, Class subcls) Protocol *result = (Protocol *)NXMapGet(protocols(), name); if (result) return result; + // Try table from dyld3 closure and dyld shared cache + result = getPreoptimizedProtocol(name); + if (result) return result; + // Try Swift-mangled equivalent of the given name. if (char *swName = copySwiftV1MangledName(name, true/*isProtocol*/)) { result = (Protocol *)NXMapGet(protocols(), swName); + + // Try table from dyld3 closure and dyld shared cache + if (!result) + result = getPreoptimizedProtocol(swName); + free(swName); - if (result) return result; + return result; } - // Try table from dyld3 closure and dyld shared cache - return getPreoptimizedProtocol(name); + return nullptr; } @@ -2526,10 +2578,22 @@ static void reconcileInstanceVariables(Class cls, Class supercls, const class_ro class_ro_t *ro_w = make_ro_writeable(rw); ro = rw->ro(); moveIvars(ro_w, super_ro->instanceSize); - gdb_objc_class_changed(cls, OBJC_CLASS_IVARS_CHANGED, ro->name); + gdb_objc_class_changed(cls, OBJC_CLASS_IVARS_CHANGED, ro->getName()); } } +static void validateAlreadyRealizedClass(Class cls) { + ASSERT(cls->isRealized()); +#if TARGET_OS_OSX + class_rw_t *rw = cls->data(); + size_t rwSize = malloc_size(rw); + + // Note: this check will need some adjustment if class_rw_t's + // size changes to not match the malloc bucket. + if (rwSize != sizeof(class_rw_t)) + _objc_fatal("realized class %p has corrupt data pointer %p", cls, rw); +#endif +} /*********************************************************************** * realizeClassWithoutSwift @@ -2548,7 +2612,10 @@ static Class realizeClassWithoutSwift(Class cls, Class previously) Class metacls; if (!cls) return nil; - if (cls->isRealized()) return cls; + if (cls->isRealized()) { + validateAlreadyRealizedClass(cls); + return cls; + } ASSERT(cls == remapClass(cls)); // fixme verify class is not in an un-dlopened part of the shared cache? @@ -2569,6 +2636,8 @@ static Class realizeClassWithoutSwift(Class cls, Class previously) cls->setData(rw); } + cls->cache.initializeToEmptyOrPreoptimizedInDisguise(); + #if FAST_CACHE_META if (isMeta) cls->cache.setBit(FAST_CACHE_META); #endif @@ -2592,7 +2661,7 @@ static Class realizeClassWithoutSwift(Class cls, Class previously) // or that Swift's initializers have already been called. // fixme that assumption will be wrong if we add support // for ObjC subclasses of Swift classes. - supercls = realizeClassWithoutSwift(remapClass(cls->superclass), nil); + supercls = realizeClassWithoutSwift(remapClass(cls->getSuperclass()), nil); metacls = realizeClassWithoutSwift(remapClass(cls->ISA()), nil); #if SUPPORT_NONPOINTER_ISA @@ -2611,13 +2680,13 @@ static Class realizeClassWithoutSwift(Class cls, Class previously) // Non-pointer isa disabled by environment or app SDK version instancesRequireRawIsa = true; } - else if (!hackedDispatch && 0 == strcmp(ro->name, "OS_object")) + else if (!hackedDispatch && 0 == strcmp(ro->getName(), "OS_object")) { // hack for libdispatch et al - isa also acts as vtable pointer hackedDispatch = true; instancesRequireRawIsa = true; } - else if (supercls && supercls->superclass && + else if (supercls && supercls->getSuperclass() && supercls->instancesRequireRawIsa()) { // This is also propagated by addSubclass() @@ -2636,7 +2705,7 @@ static Class realizeClassWithoutSwift(Class cls, Class previously) #endif // Update superclass and metaclass in case of remapping - cls->superclass = supercls; + cls->setSuperclass(supercls); cls->initClassIsa(metacls); // Reconcile instance variable offsets / layout. @@ -2758,7 +2827,7 @@ static Class realizeSwiftClass(Class cls) ASSERT(remapClass(cls) == cls); ASSERT(cls->isSwiftStable_ButAllowLegacyForNow()); ASSERT(!cls->isMetaClassMaybeUnrealized()); - ASSERT(cls->superclass); + ASSERT(cls->getSuperclass()); runtimeLock.unlock(); #endif @@ -2850,13 +2919,13 @@ static Class realizeSwiftClass(Class cls) { ASSERT(!cls->isRealized()); - if (!cls->superclass) { + if (!cls->getSuperclass()) { // superclass nil. This is normal for root classes only. return (!(cls->data()->flags & RO_ROOT)); } else { // superclass not nil. Check if a higher superclass is missing. - Class supercls = remapClass(cls->superclass); - ASSERT(cls != cls->superclass); + Class supercls = remapClass(cls->getSuperclass()); + ASSERT(cls != cls->getSuperclass()); ASSERT(cls != supercls); if (!supercls) return YES; if (supercls->isRealized()) return NO; @@ -2975,6 +3044,10 @@ BOOL _class_isFutureClass(Class cls) return cls && cls->isFuture(); } +BOOL _class_isSwift(Class _Nullable cls) +{ + return cls && cls->isSwiftStable(); +} /*********************************************************************** * _objc_flush_caches @@ -2983,24 +3056,25 @@ BOOL _class_isFutureClass(Class cls) * and subclasses thereof. Nil flushes all classes.) * Locking: acquires runtimeLock **********************************************************************/ -static void flushCaches(Class cls) +static void flushCaches(Class cls, const char *func, bool (^predicate)(Class)) { runtimeLock.assertLocked(); #if CONFIG_USE_CACHE_LOCK mutex_locker_t lock(cacheUpdateLock); #endif + const auto handler = ^(Class c) { + if (predicate(c)) { + c->cache.eraseNolock(func); + } + + return true; + }; + if (cls) { - foreach_realized_class_and_subclass(cls, [](Class c){ - cache_erase_nolock(c); - return true; - }); - } - else { - foreach_realized_class_and_metaclass([](Class c){ - cache_erase_nolock(c); - return true; - }); + foreach_realized_class_and_subclass(cls, handler); + } else { + foreach_realized_class_and_metaclass(handler); } } @@ -3009,9 +3083,13 @@ void _objc_flush_caches(Class cls) { { mutex_locker_t lock(runtimeLock); - flushCaches(cls); - if (cls && cls->superclass && cls != cls->getIsa()) { - flushCaches(cls->getIsa()); + flushCaches(cls, __func__, [](Class c){ + return !c->cache.isConstantOptimizedCache(); + }); + if (cls && !cls->isMetaClass() && !cls->isRootClass()) { + flushCaches(cls->ISA(), __func__, [](Class c){ + return !c->cache.isConstantOptimizedCache(); + }); } else { // cls is a root class or root metaclass. Its metaclass is itself // or a subclass so the metaclass caches were already flushed. @@ -3025,7 +3103,7 @@ void _objc_flush_caches(Class cls) #else mutex_locker_t lock(runtimeLock); #endif - cache_collect(true); + cache_t::collectNolock(true); } } @@ -3246,7 +3324,7 @@ bool mustReadClasses(header_info *hi, bool hasDyldRoots) **********************************************************************/ Class readClass(Class cls, bool headerIsBundle, bool headerIsPreoptimized) { - const char *mangledName = cls->mangledName(); + const char *mangledName = cls->nonlazyMangledName(); if (missingWeakSuperclass(cls)) { // No superclass (probably weak-linked). @@ -3257,45 +3335,60 @@ Class readClass(Class cls, bool headerIsBundle, bool headerIsPreoptimized) cls->nameForLogging()); } addRemappedClass(cls, nil); - cls->superclass = nil; + cls->setSuperclass(nil); return nil; } cls->fixupBackwardDeployingStableSwift(); Class replacing = nil; - if (Class newCls = popFutureNamedClass(mangledName)) { - // This name was previously allocated as a future class. - // Copy objc_class to future class's struct. - // Preserve future's rw data block. - - if (newCls->isAnySwift()) { - _objc_fatal("Can't complete future class request for '%s' " - "because the real class is too big.", - cls->nameForLogging()); + if (mangledName != nullptr) { + if (Class newCls = popFutureNamedClass(mangledName)) { + // This name was previously allocated as a future class. + // Copy objc_class to future class's struct. + // Preserve future's rw data block. + + if (newCls->isAnySwift()) { + _objc_fatal("Can't complete future class request for '%s' " + "because the real class is too big.", + cls->nameForLogging()); + } + + class_rw_t *rw = newCls->data(); + const class_ro_t *old_ro = rw->ro(); + memcpy(newCls, cls, sizeof(objc_class)); + + // Manually set address-discriminated ptrauthed fields + // so that newCls gets the correct signatures. + newCls->setSuperclass(cls->getSuperclass()); + newCls->initIsa(cls->getIsa()); + + rw->set_ro((class_ro_t *)newCls->data()); + newCls->setData(rw); + freeIfMutable((char *)old_ro->getName()); + free((void *)old_ro); + + addRemappedClass(cls, newCls); + + replacing = cls; + cls = newCls; } - - class_rw_t *rw = newCls->data(); - const class_ro_t *old_ro = rw->ro(); - memcpy(newCls, cls, sizeof(objc_class)); - rw->set_ro((class_ro_t *)newCls->data()); - newCls->setData(rw); - freeIfMutable((char *)old_ro->name); - free((void *)old_ro); - - addRemappedClass(cls, newCls); - - replacing = cls; - cls = newCls; } if (headerIsPreoptimized && !replacing) { // class list built in shared cache // fixme strict assert doesn't work because of duplicates // ASSERT(cls == getClass(name)); - ASSERT(getClassExceptSomeSwift(mangledName)); + ASSERT(mangledName == nullptr || getClassExceptSomeSwift(mangledName)); } else { - addNamedClass(cls, mangledName, replacing); + if (mangledName) { //some Swift generic classes can lazily generate their names + addNamedClass(cls, mangledName, replacing); + } else { + Class meta = cls->ISA(); + const class_ro_t *metaRO = meta->bits.safe_ro(); + ASSERT(metaRO->getNonMetaclass() && "Metaclass with lazy name must have a pointer to the corresponding nonmetaclass."); + ASSERT(metaRO->getNonMetaclass() == cls && "Metaclass nonmetaclass pointer must equal the original class."); + } addClassTableEntry(cls); } @@ -3384,9 +3477,8 @@ Class readClass(Class cls, bool headerIsBundle, bool headerIsPreoptimized) } } } - else if (newproto->size >= sizeof(protocol_t)) { - // New protocol from an un-preoptimized image - // with sufficient storage. Fix it up in place. + else { + // New protocol from an un-preoptimized image. Fix it up in place. // fixme duplicate protocols from unloadable bundle newproto->initIsa(protocol_class); // fixme pinned insertFn(protocol_map, newproto->mangledName, newproto); @@ -3395,26 +3487,6 @@ Class readClass(Class cls, bool headerIsBundle, bool headerIsPreoptimized) newproto, newproto->nameForLogging()); } } - else { - // New protocol from an un-preoptimized image - // with insufficient storage. Reallocate it. - // fixme duplicate protocols from unloadable bundle - size_t size = max(sizeof(protocol_t), (size_t)newproto->size); - protocol_t *installedproto = (protocol_t *)calloc(size, 1); - memcpy(installedproto, newproto, newproto->size); - installedproto->size = (typeof(installedproto->size))size; - - installedproto->initIsa(protocol_class); // fixme pinned - insertFn(protocol_map, installedproto->mangledName, installedproto); - if (PrintProtocols) { - _objc_inform("PROTOCOLS: protocol at %p is %s ", - installedproto, installedproto->nameForLogging()); - _objc_inform("PROTOCOLS: protocol at %p is %s " - "(reallocated to %p)", - newproto, installedproto->nameForLogging(), - installedproto); - } - } } /*********************************************************************** @@ -3472,12 +3544,11 @@ void _read_images(header_info **hList, uint32_t hCount, int totalClasses, int un # if TARGET_OS_OSX // Disable non-pointer isa if the app is too old // (linked before OS X 10.11) - if (dyld_get_program_sdk_version() < DYLD_MACOSX_VERSION_10_11) { + if (!dyld_program_sdk_at_least(dyld_platform_version_macOS_10_11)) { DisableNonpointerIsa = true; if (PrintRawIsa) { _objc_inform("RAW ISA: disabling non-pointer isa because " - "the app is too old (SDK version " SDK_FORMAT ")", - FORMAT_SDK(dyld_get_program_sdk_version())); + "the app is too old."); } } @@ -3755,13 +3826,13 @@ void _read_images(header_info **hList, uint32_t hCount, int totalClasses, int un } const method_list_t *mlist; - if ((mlist = ((class_ro_t *)cls->data())->baseMethods())) { + if ((mlist = cls->bits.safe_ro()->baseMethods())) { PreoptTotalMethodLists++; if (mlist->isFixedUp()) { PreoptOptimizedMethodLists++; } } - if ((mlist=((class_ro_t *)cls->ISA()->data())->baseMethods())) { + if ((mlist = cls->ISA()->bits.safe_ro()->baseMethods())) { PreoptTotalMethodLists++; if (mlist->isFixedUp()) { PreoptOptimizedMethodLists++; @@ -3805,7 +3876,7 @@ static void schedule_class_load(Class cls) if (cls->data()->flags & RW_LOADED) return; // Ensure superclass-first ordering - schedule_class_load(cls->superclass); + schedule_class_load(cls->getSuperclass()); add_class_to_loadable_list(cls); cls->setInfo(RW_LOADED); @@ -3990,13 +4061,17 @@ IMPAndSEL _method_getImplementationAndName(Method m) if (!imp) return nil; IMP old = m->imp(false); + SEL sel = m->name(); + m->setImp(imp); // Cache updates are slow if cls is nil (i.e. unknown) // RR/AWZ updates are slow if cls is nil (i.e. unknown) // fixme build list of classes whose Methods are known externally? - flushCaches(cls); + flushCaches(cls, __func__, [sel, old](Class c){ + return c->cache.shouldFlush(sel, old); + }); adjustCustomFlagsForMethodChange(cls, m); @@ -4012,6 +4087,12 @@ IMPAndSEL _method_getImplementationAndName(Method m) return _method_setImplementation(Nil, m, imp); } +extern void _method_setImplementationRawUnsafe(Method m, IMP imp) +{ + mutex_locker_t lock(runtimeLock); + m->setImp(imp); +} + void method_exchangeImplementations(Method m1, Method m2) { @@ -4019,16 +4100,22 @@ void method_exchangeImplementations(Method m1, Method m2) mutex_locker_t lock(runtimeLock); - IMP m1_imp = m1->imp(false); - m1->setImp(m2->imp(false)); - m2->setImp(m1_imp); + IMP imp1 = m1->imp(false); + IMP imp2 = m2->imp(false); + SEL sel1 = m1->name(); + SEL sel2 = m2->name(); + + m1->setImp(imp2); + m2->setImp(imp1); // RR/AWZ updates are slow because class is unknown // Cache updates are slow because class is unknown // fixme build list of classes whose Methods are known externally? - flushCaches(nil); + flushCaches(nil, __func__, [sel1, sel2, imp1, imp2](Class c){ + return c->cache.shouldFlush(sel1, imp1) || c->cache.shouldFlush(sel2, imp2); + }); adjustCustomFlagsForMethodChange(nil, m1); adjustCustomFlagsForMethodChange(nil, m2); @@ -4395,7 +4482,8 @@ static uint32_t getExtendedTypesIndexForMethod(protocol_t *proto, const method_t const char * protocol_t::demangledName() { - ASSERT(hasDemangledNameField()); + if (!hasDemangledNameField()) + return mangledName; if (! _demangledName) { char *de = copySwiftV1DemangledName(mangledName, true/*isProtocol*/); @@ -4981,24 +5069,6 @@ void objc_registerProtocol(Protocol *proto_gen) return result; } -static void -class_getImpCache_nolock(Class cls, cache_t &cache, objc_imp_cache_entry *buffer, int len) -{ - bucket_t *buckets = cache.buckets(); - - uintptr_t count = cache.capacity(); - uintptr_t index; - int wpos = 0; - - for (index = 0; index < count && wpos < len; index += 1) { - if (buckets[index].sel()) { - buffer[wpos].imp = buckets[index].imp(cls); - buffer[wpos].sel = buckets[index].sel(); - wpos++; - } - } -} - /*********************************************************************** * objc_getClassList * Returns pointers to all classes. @@ -5078,7 +5148,7 @@ void objc_registerProtocol(Protocol *proto_gen) if (count) { buffer = (objc_imp_cache_entry *)calloc(1+count, sizeof(objc_imp_cache_entry)); - class_getImpCache_nolock(cls, cache, buffer, count); + cache.copyCacheNolock(buffer, count); } if (outCount) *outCount = count; @@ -5524,6 +5594,32 @@ void objc_registerProtocol(Protocol *proto_gen) return names; } +Class * +copyClassesForImage_nolock(header_info *hi, unsigned int *outCount) +{ + runtimeLock.assertLocked(); + ASSERT(hi); + + size_t count; + classref_t const *classlist = _getObjc2ClassList(hi, &count); + Class *classes = (Class *) + malloc((count+1) * sizeof(Class)); + + size_t shift = 0; + for (size_t i = 0; i < count; i++) { + Class cls = remapClass(classlist[i]); + if (cls) { + classes[i-shift] = cls; + } else { + shift++; // ignored weak-linked class + } + } + count -= shift; + classes[count] = nil; + + if (outCount) *outCount = (unsigned int)count; + return classes; +} /*********************************************************************** @@ -5563,6 +5659,29 @@ void objc_registerProtocol(Protocol *proto_gen) return copyClassNamesForImage_nolock(hi, outCount); } +Class * +objc_copyClassesForImage(const char *image, unsigned int *outCount) +{ + if (!image) { + if (outCount) *outCount = 0; + return nil; + } + + mutex_locker_t lock(runtimeLock); + + // Find the image. + header_info *hi; + for (hi = FirstHeader; hi != nil; hi = hi->getNext()) { + if (0 == strcmp(image, hi->fname())) break; + } + + if (!hi) { + if (outCount) *outCount = 0; + return nil; + } + + return copyClassesForImage_nolock(hi, outCount); +} /*********************************************************************** * objc_copyClassNamesForImageHeader @@ -5631,7 +5750,7 @@ void objc_registerProtocol(Protocol *proto_gen) // Handle the easy case directly. if (isRealized() || isFuture()) { if (!isAnySwift()) { - return data()->ro()->name; + return data()->ro()->getName(); } auto rwe = data()->ext(); if (rwe && rwe->demangledName) { @@ -5641,11 +5760,15 @@ void objc_registerProtocol(Protocol *proto_gen) char *result; - const char *name = mangledName(); - char *de = copySwiftV1DemangledName(name); - if (de) result = de; - else result = strdup(name); - + if (isStubClass()) { + asprintf(&result, "", this); + } else if (const char *name = nonlazyMangledName()) { + char *de = copySwiftV1DemangledName(name); + if (de) result = de; + else result = strdup(name); + } else { + asprintf(&result, "", this); + } saveTemporaryString(result); return result; } @@ -5669,8 +5792,8 @@ void objc_registerProtocol(Protocol *proto_gen) if (isRealized() || isFuture()) { // Swift metaclasses don't have the is-Swift bit. // We can't take this shortcut for them. - if (!isMetaClass() && !isAnySwift()) { - return data()->ro()->name; + if (isFuture() || (!isMetaClass() && !isAnySwift())) { + return data()->ro()->getName(); } auto rwe = data()->ext(); if (rwe && rwe->demangledName) { @@ -5798,8 +5921,9 @@ void objc_registerProtocol(Protocol *proto_gen) /*********************************************************************** * search_method_list_inline **********************************************************************/ +template ALWAYS_INLINE static method_t * -findMethodInSortedMethodList(SEL key, const method_list_t *list) +findMethodInSortedMethodList(SEL key, const method_list_t *list, const getNameFunc &getName) { ASSERT(list); @@ -5813,13 +5937,13 @@ void objc_registerProtocol(Protocol *proto_gen) for (count = list->count; count != 0; count >>= 1) { probe = base + (count >> 1); - uintptr_t probeValue = (uintptr_t)probe->name(); + uintptr_t probeValue = (uintptr_t)getName(probe); if (keyValue == probeValue) { // `probe` is a match. // Rewind looking for the *first* occurrence of this value. // This is required for correct category overrides. - while (probe > first && keyValue == (uintptr_t)(probe - 1)->name()) { + while (probe > first && keyValue == (uintptr_t)getName((probe - 1))) { probe--; } return &*probe; @@ -5834,6 +5958,44 @@ void objc_registerProtocol(Protocol *proto_gen) return nil; } +ALWAYS_INLINE static method_t * +findMethodInSortedMethodList(SEL key, const method_list_t *list) +{ + if (list->isSmallList()) { + if (CONFIG_SHARED_CACHE_RELATIVE_DIRECT_SELECTORS && objc::inSharedCache((uintptr_t)list)) { + return findMethodInSortedMethodList(key, list, [](method_t &m) { return m.getSmallNameAsSEL(); }); + } else { + return findMethodInSortedMethodList(key, list, [](method_t &m) { return m.getSmallNameAsSELRef(); }); + } + } else { + return findMethodInSortedMethodList(key, list, [](method_t &m) { return m.big().name; }); + } +} + +template +ALWAYS_INLINE static method_t * +findMethodInUnsortedMethodList(SEL sel, const method_list_t *list, const getNameFunc &getName) +{ + for (auto& meth : *list) { + if (getName(meth) == sel) return &meth; + } + return nil; +} + +ALWAYS_INLINE static method_t * +findMethodInUnsortedMethodList(SEL key, const method_list_t *list) +{ + if (list->isSmallList()) { + if (CONFIG_SHARED_CACHE_RELATIVE_DIRECT_SELECTORS && objc::inSharedCache((uintptr_t)list)) { + return findMethodInUnsortedMethodList(key, list, [](method_t &m) { return m.getSmallNameAsSEL(); }); + } else { + return findMethodInUnsortedMethodList(key, list, [](method_t &m) { return m.getSmallNameAsSELRef(); }); + } + } else { + return findMethodInUnsortedMethodList(key, list, [](method_t &m) { return m.big().name; }); + } +} + ALWAYS_INLINE static method_t * search_method_list_inline(const method_list_t *mlist, SEL sel) { @@ -5844,9 +6006,8 @@ void objc_registerProtocol(Protocol *proto_gen) return findMethodInSortedMethodList(sel, mlist); } else { // Linear search of unsorted method list - for (auto& meth : *mlist) { - if (meth.name() == sel) return &meth; - } + if (auto *m = findMethodInUnsortedMethodList(sel, mlist)) + return m; } #if DEBUG @@ -5889,11 +6050,9 @@ void objc_registerProtocol(Protocol *proto_gen) } } } else { - for (auto& meth : *mlist) { - for (size_t i = 0; i < selcount; i++) { - if (meth.name() == sels[i]) { - return true; - } + for (size_t i = 0; i < selcount; i++) { + if (findMethodInUnsortedMethodList(sels[i], mlist)) { + return true; } } } @@ -5901,6 +6060,7 @@ void objc_registerProtocol(Protocol *proto_gen) return false; } + /*********************************************************************** * getMethodNoSuper_nolock * fixme @@ -5951,7 +6111,7 @@ void objc_registerProtocol(Protocol *proto_gen) ASSERT(cls->isRealized()); while (cls && ((m = getMethodNoSuper_nolock(cls, sel))) == nil) { - cls = cls->superclass; + cls = cls->getSuperclass(); } return m; @@ -6006,7 +6166,7 @@ static void resolveClassMethod(id inst, SEL sel, Class cls) ASSERT(cls->isRealized()); ASSERT(cls->isMetaClass()); - if (!lookUpImpOrNil(inst, @selector(resolveClassMethod:), cls)) { + if (!lookUpImpOrNilTryCache(inst, @selector(resolveClassMethod:), cls)) { // Resolver not implemented. return; } @@ -6026,7 +6186,7 @@ static void resolveClassMethod(id inst, SEL sel, Class cls) // Cache the result (good or bad) so the resolver doesn't fire next time. // +resolveClassMethod adds to self->ISA() a.k.a. cls - IMP imp = lookUpImpOrNil(inst, sel, cls); + IMP imp = lookUpImpOrNilTryCache(inst, sel, cls); if (resolved && PrintResolving) { if (imp) { @@ -6059,7 +6219,7 @@ static void resolveInstanceMethod(id inst, SEL sel, Class cls) ASSERT(cls->isRealized()); SEL resolve_sel = @selector(resolveInstanceMethod:); - if (!lookUpImpOrNil(cls, resolve_sel, cls->ISA())) { + if (!lookUpImpOrNilTryCache(cls, resolve_sel, cls->ISA(/*authenticated*/true))) { // Resolver not implemented. return; } @@ -6069,7 +6229,7 @@ static void resolveInstanceMethod(id inst, SEL sel, Class cls) // Cache the result (good or bad) so the resolver doesn't fire next time. // +resolveInstanceMethod adds to self a.k.a. cls - IMP imp = lookUpImpOrNil(inst, sel, cls); + IMP imp = lookUpImpOrNilTryCache(inst, sel, cls); if (resolved && PrintResolving) { if (imp) { @@ -6113,14 +6273,14 @@ static void resolveInstanceMethod(id inst, SEL sel, Class cls) // try [nonMetaClass resolveClassMethod:sel] // and [cls resolveInstanceMethod:sel] resolveClassMethod(inst, sel, cls); - if (!lookUpImpOrNil(inst, sel, cls)) { + if (!lookUpImpOrNilTryCache(inst, sel, cls)) { resolveInstanceMethod(inst, sel, cls); } } // chances are that calling the resolver have populated the cache // so attempt using it - return lookUpImpOrForward(inst, sel, cls, behavior | LOOKUP_CACHE); + return lookUpImpOrForwardTryCache(inst, sel, cls, behavior); } @@ -6142,22 +6302,94 @@ static void resolveInstanceMethod(id inst, SEL sel, Class cls) if (!cacheIt) return; } #endif - cache_fill(cls, sel, imp, receiver); + cls->cache.insert(sel, imp, receiver); } /*********************************************************************** -* lookUpImpOrForward. -* The standard IMP lookup. +* realizeAndInitializeIfNeeded_locked +* Realize the given class if not already realized, and initialize it if +* not already initialized. +* inst is an instance of cls or a subclass, or nil if none is known. +* cls is the class to initialize and realize. +* initializer is true to initialize the class, false to skip initialization. +**********************************************************************/ +static Class +realizeAndInitializeIfNeeded_locked(id inst, Class cls, bool initialize) +{ + runtimeLock.assertLocked(); + if (slowpath(!cls->isRealized())) { + cls = realizeClassMaybeSwiftAndLeaveLocked(cls, runtimeLock); + // runtimeLock may have been dropped but is now locked again + } + + if (slowpath(initialize && !cls->isInitialized())) { + cls = initializeAndLeaveLocked(cls, inst, runtimeLock); + // runtimeLock may have been dropped but is now locked again + + // If sel == initialize, class_initialize will send +initialize and + // then the messenger will send +initialize again after this + // procedure finishes. Of course, if this is not being called + // from the messenger then it won't happen. 2778172 + } + return cls; +} + +/*********************************************************************** +* lookUpImpOrForward / lookUpImpOrForwardTryCache / lookUpImpOrNilTryCache +* The standard IMP lookup. +* +* The TryCache variant attempts a fast-path lookup in the IMP Cache. +* Most callers should use lookUpImpOrForwardTryCache with LOOKUP_INITIALIZE +* * Without LOOKUP_INITIALIZE: tries to avoid +initialize (but sometimes fails) -* Without LOOKUP_CACHE: skips optimistic unlocked lookup (but uses cache elsewhere) -* Most callers should use LOOKUP_INITIALIZE and LOOKUP_CACHE -* inst is an instance of cls or a subclass thereof, or nil if none is known. +* With LOOKUP_NIL: returns nil on negative cache hits +* +* inst is an instance of cls or a subclass thereof, or nil if none is known. * If cls is an un-initialized metaclass then a non-nil inst is faster. * May return _objc_msgForward_impcache. IMPs destined for external use * must be converted to _objc_msgForward or _objc_msgForward_stret. * If you don't want forwarding at all, use LOOKUP_NIL. **********************************************************************/ +ALWAYS_INLINE +static IMP _lookUpImpTryCache(id inst, SEL sel, Class cls, int behavior) +{ + runtimeLock.assertUnlocked(); + + if (slowpath(!cls->isInitialized())) { + // see comment in lookUpImpOrForward + return lookUpImpOrForward(inst, sel, cls, behavior); + } + + IMP imp = cache_getImp(cls, sel); + if (imp != NULL) goto done; +#if CONFIG_USE_PREOPT_CACHES + if (fastpath(cls->cache.isConstantOptimizedCache(/* strict */true))) { + imp = cache_getImp(cls->cache.preoptFallbackClass(), sel); + } +#endif + if (slowpath(imp == NULL)) { + return lookUpImpOrForward(inst, sel, cls, behavior); + } + +done: + if ((behavior & LOOKUP_NIL) && imp == (IMP)_objc_msgForward_impcache) { + return nil; + } + return imp; +} + +IMP lookUpImpOrForwardTryCache(id inst, SEL sel, Class cls, int behavior) +{ + return _lookUpImpTryCache(inst, sel, cls, behavior); +} + +IMP lookUpImpOrNilTryCache(id inst, SEL sel, Class cls, int behavior) +{ + return _lookUpImpTryCache(inst, sel, cls, behavior | LOOKUP_NIL); +} + +NEVER_INLINE IMP lookUpImpOrForward(id inst, SEL sel, Class cls, int behavior) { const IMP forward_imp = (IMP)_objc_msgForward_impcache; @@ -6166,10 +6398,21 @@ IMP lookUpImpOrForward(id inst, SEL sel, Class cls, int behavior) runtimeLock.assertUnlocked(); - // Optimistic cache lookup - if (fastpath(behavior & LOOKUP_CACHE)) { - imp = cache_getImp(cls, sel); - if (imp) goto done_nolock; + if (slowpath(!cls->isInitialized())) { + // The first message sent to a class is often +new or +alloc, or +self + // which goes through objc_opt_* or various optimized entry points. + // + // However, the class isn't realized/initialized yet at this point, + // and the optimized entry points fall down through objc_msgSend, + // which ends up here. + // + // We really want to avoid caching these, as it can cause IMP caches + // to be made with a single entry forever. + // + // Note that this check is racy as several threads might try to + // message a given class for the first time at the same time, + // in which case we might cache anyway. + behavior |= LOOKUP_NOCACHE; } // runtimeLock is held during isRealized and isInitialized checking @@ -6191,25 +6434,12 @@ IMP lookUpImpOrForward(id inst, SEL sel, Class cls, int behavior) // objc_duplicateClass, objc_initializeClassPair or objc_allocateClassPair. checkIsKnownClass(cls); - if (slowpath(!cls->isRealized())) { - cls = realizeClassMaybeSwiftAndLeaveLocked(cls, runtimeLock); - // runtimeLock may have been dropped but is now locked again - } - - if (slowpath((behavior & LOOKUP_INITIALIZE) && !cls->isInitialized())) { - cls = initializeAndLeaveLocked(cls, inst, runtimeLock); - // runtimeLock may have been dropped but is now locked again - - // If sel == initialize, class_initialize will send +initialize and - // then the messenger will send +initialize again after this - // procedure finishes. Of course, if this is not being called - // from the messenger then it won't happen. 2778172 - } - + cls = realizeAndInitializeIfNeeded_locked(inst, cls, behavior & LOOKUP_INITIALIZE); + // runtimeLock may have been dropped but is now locked again runtimeLock.assertLocked(); curClass = cls; - // The code used to lookpu the class's cache again right after + // The code used to lookup the class's cache again right after // we take the lock but for the vast majority of the cases // evidence shows this is a miss most of the time, hence a time loss. // @@ -6217,18 +6447,26 @@ IMP lookUpImpOrForward(id inst, SEL sel, Class cls, int behavior) // kind of cache lookup is class_getInstanceMethod(). for (unsigned attempts = unreasonableClassCount();;) { - // curClass method list. - Method meth = getMethodNoSuper_nolock(curClass, sel); - if (meth) { - imp = meth->imp(false); - goto done; - } + if (curClass->cache.isConstantOptimizedCache(/* strict */true)) { +#if CONFIG_USE_PREOPT_CACHES + imp = cache_getImp(curClass, sel); + if (imp) goto done_unlock; + curClass = curClass->cache.preoptFallbackClass(); +#endif + } else { + // curClass method list. + Method meth = getMethodNoSuper_nolock(curClass, sel); + if (meth) { + imp = meth->imp(false); + goto done; + } - if (slowpath((curClass = curClass->superclass) == nil)) { - // No implementation found, and method resolver didn't help. - // Use forwarding. - imp = forward_imp; - break; + if (slowpath((curClass = curClass->getSuperclass()) == nil)) { + // No implementation found, and method resolver didn't help. + // Use forwarding. + imp = forward_imp; + break; + } } // Halt if there is a cycle in the superclass chain. @@ -6258,9 +6496,16 @@ IMP lookUpImpOrForward(id inst, SEL sel, Class cls, int behavior) } done: - log_and_fill_cache(cls, imp, sel, inst, curClass); + if (fastpath((behavior & LOOKUP_NOCACHE) == 0)) { +#if CONFIG_USE_PREOPT_CACHES + while (cls->cache.isConstantOptimizedCache(/* strict */true)) { + cls = cls->cache.preoptFallbackClass(); + } +#endif + log_and_fill_cache(cls, imp, sel, inst, curClass); + } + done_unlock: runtimeLock.unlock(); - done_nolock: if (slowpath((behavior & LOOKUP_NIL) && imp == forward_imp)) { return nil; } @@ -6274,7 +6519,6 @@ IMP lookUpImpOrForward(id inst, SEL sel, Class cls, int behavior) **********************************************************************/ IMP lookupMethodInClassAndLoadCache(Class cls, SEL sel) { - Method meth; IMP imp; // fixme this is incomplete - no resolver, +initialize - @@ -6282,24 +6526,35 @@ IMP lookupMethodInClassAndLoadCache(Class cls, SEL sel) ASSERT(sel == SEL_cxx_construct || sel == SEL_cxx_destruct); // Search cache first. - imp = cache_getImp(cls, sel); - if (imp) return imp; + // + // If the cache used for the lookup is preoptimized, + // we ask for `_objc_msgForward_impcache` to be returned on cache misses, + // so that there's no TOCTOU race between using `isConstantOptimizedCache` + // and calling cache_getImp() when not under the runtime lock. + // + // For dynamic caches, a miss will return `nil` + imp = cache_getImp(cls, sel, _objc_msgForward_impcache); - // Cache miss. Search method list. + if (slowpath(imp == nil)) { + // Cache miss. Search method list. - mutex_locker_t lock(runtimeLock); + mutex_locker_t lock(runtimeLock); - meth = getMethodNoSuper_nolock(cls, sel); + if (auto meth = getMethodNoSuper_nolock(cls, sel)) { + // Hit in method list. Cache it. + imp = meth->imp(false); + } else { + imp = _objc_msgForward_impcache; + } - if (meth) { - // Hit in method list. Cache it. - cache_fill(cls, sel, meth->imp(false), nil); - return meth->imp(false); - } else { - // Miss in method list. Cache objc_msgForward. - cache_fill(cls, sel, _objc_msgForward_impcache, nil); - return _objc_msgForward_impcache; + // Note, because we do not hold the runtime lock above + // isConstantOptimizedCache might flip, so we need to double check + if (!cls->cache.isConstantOptimizedCache(true /* strict */)) { + cls->cache.insert(sel, imp, nil); + } } + + return imp; } @@ -6318,7 +6573,7 @@ objc_property_t class_getProperty(Class cls, const char *name) ASSERT(cls->isRealized()); - for ( ; cls; cls = cls->superclass) { + for ( ; cls; cls = cls->getSuperclass()) { for (auto& prop : cls->data()->properties()) { if (0 == strcmp(name, prop.name)) { return (objc_property_t)∝ @@ -6376,6 +6631,15 @@ Class gdb_object_getClass(id obj) objc::RRScanner::scanInitializedClass(cls, metacls); objc::CoreScanner::scanInitializedClass(cls, metacls); +#if CONFIG_USE_PREOPT_CACHES + cls->cache.maybeConvertToPreoptimized(); + metacls->cache.maybeConvertToPreoptimized(); +#endif + + if (PrintInitializing) { + _objc_inform("INITIALIZE: thread %p: setInitialized(%s)", + objc_thread_self(), cls->nameForLogging()); + } // Update the +initialize flags. // Do this last. metacls->changeInfo(RW_INITIALIZED, RW_INITIALIZING); @@ -6414,6 +6678,59 @@ Class gdb_object_getClass(id obj) }); } +#if CONFIG_USE_PREOPT_CACHES +void objc_class::setDisallowPreoptCachesRecursively(const char *why) +{ + Class cls = (Class)this; + runtimeLock.assertLocked(); + + if (!allowsPreoptCaches()) return; + + foreach_realized_class_and_subclass(cls, [=](Class c){ + if (!c->allowsPreoptCaches()) { + return false; + } + + if (c->cache.isConstantOptimizedCache(/* strict */true)) { + c->cache.eraseNolock(why); + } else { + if (PrintCaches) { + _objc_inform("CACHES: %sclass %s: disallow preopt cache (from %s)", + isMetaClass() ? "meta" : "", + nameForLogging(), why); + } + c->setDisallowPreoptCaches(); + } + return true; + }); +} + +void objc_class::setDisallowPreoptInlinedSelsRecursively(const char *why) +{ + Class cls = (Class)this; + runtimeLock.assertLocked(); + + if (!allowsPreoptInlinedSels()) return; + + foreach_realized_class_and_subclass(cls, [=](Class c){ + if (!c->allowsPreoptInlinedSels()) { + return false; + } + + if (PrintCaches) { + _objc_inform("CACHES: %sclass %s: disallow sel-inlined preopt cache (from %s)", + isMetaClass() ? "meta" : "", + nameForLogging(), why); + } + + c->setDisallowPreoptInlinedSels(); + if (c->cache.isConstantOptimizedCacheWithInlinedSels()) { + c->cache.eraseNolock(why); + } + return true; + }); +} +#endif /*********************************************************************** * Choose a class index. @@ -6439,6 +6756,62 @@ Class gdb_object_getClass(id obj) #endif } +static const char *empty_lazyClassNamer(Class cls __unused) { + return nullptr; +} + +static ChainedHookFunction LazyClassNamerHook{empty_lazyClassNamer}; + +void objc_setHook_lazyClassNamer(_Nonnull objc_hook_lazyClassNamer newValue, + _Nonnull objc_hook_lazyClassNamer * _Nonnull oldOutValue) { + LazyClassNamerHook.set(newValue, oldOutValue); +} + +const char * objc_class::installMangledNameForLazilyNamedClass() { + auto lazyClassNamer = LazyClassNamerHook.get(); + if (!*lazyClassNamer) { + _objc_fatal("Lazily named class %p with no lazy name handler registered", this); + } + + // If this is called on a metaclass, extract the original class + // and make it do the installation instead. It will install + // the metaclass's name too. + if (isMetaClass()) { + Class nonMeta = bits.safe_ro()->getNonMetaclass(); + return nonMeta->installMangledNameForLazilyNamedClass(); + } + + Class cls = (Class)this; + Class metaclass = ISA(); + + const char *name = lazyClassNamer((Class)this); + if (!name) { + _objc_fatal("Lazily named class %p wasn't named by lazy name handler", this); + } + + // Emplace the name into the class_ro_t. If we lose the race, + // then we'll free our name and use whatever got placed there + // instead of our name. + const char *previously = NULL; + class_ro_t *ro = (class_ro_t *)cls->bits.safe_ro(); + bool wonRace = ro->name.compare_exchange_strong(previously, name, std::memory_order_release, std::memory_order_acquire); + if (!wonRace) { + free((void *)name); + name = previously; + } + + // Emplace whatever name won the race in the metaclass too. + class_ro_t *metaRO = (class_ro_t *)metaclass->bits.safe_ro(); + + // Write our pointer if the current value is NULL. There's no + // need to loop or check success, since the only way this can + // fail is if another thread succeeded in writing the exact + // same pointer. + const char *expected = NULL; + metaRO->name.compare_exchange_strong(expected, name, std::memory_order_release, std::memory_order_acquire); + + return name; +} /*********************************************************************** * Update custom RR and AWZ when a method changes its IMP @@ -6461,7 +6834,7 @@ Class gdb_object_getClass(id obj) const uint8_t * class_getIvarLayout(Class cls) { - if (cls) return cls->data()->ro()->ivarLayout; + if (cls) return cls->data()->ro()->getIvarLayout(); else return nil; } @@ -6494,6 +6867,8 @@ Class gdb_object_getClass(id obj) { if (!cls) return; + ASSERT(!cls->isMetaClass()); + mutex_locker_t lock(runtimeLock); checkIsKnownClass(cls); @@ -6509,7 +6884,7 @@ Class gdb_object_getClass(id obj) class_ro_t *ro_w = make_ro_writeable(cls->data()); - try_free(ro_w->ivarLayout); + try_free(ro_w->getIvarLayout()); ro_w->ivarLayout = ustrdupMaybeNil(layout); } @@ -6583,7 +6958,7 @@ Class _class_getClassForIvar(Class cls, Ivar ivar) { mutex_locker_t lock(runtimeLock); - for ( ; cls; cls = cls->superclass) { + for ( ; cls; cls = cls->getSuperclass()) { if (auto ivars = cls->data()->ro()->ivars) { if (ivars->containsIvar(ivar)) { return cls; @@ -6605,7 +6980,7 @@ Class _class_getClassForIvar(Class cls, Ivar ivar) { mutex_locker_t lock(runtimeLock); - for ( ; cls; cls = cls->superclass) { + for ( ; cls; cls = cls->getSuperclass()) { ivar_t *ivar = getIvar(cls, name); if (ivar) { return ivar; @@ -6644,6 +7019,29 @@ BOOL class_conformsToProtocol(Class cls, Protocol *proto_gen) return NO; } +static void +addMethods_finish(Class cls, method_list_t *newlist) +{ + auto rwe = cls->data()->extAllocIfNeeded(); + + if (newlist->count > 1) { + method_t::SortBySELAddress sorter; + std::stable_sort(&newlist->begin()->big(), &newlist->end()->big(), sorter); + } + + prepareMethodLists(cls, &newlist, 1, NO, NO, __func__); + rwe->methods.attachLists(&newlist, 1); + + // If the class being modified has a constant cache, + // then all children classes are flattened constant caches + // and need to be flushed as well. + flushCaches(cls, __func__, [](Class c){ + // constant caches have been dealt with in prepareMethodLists + // if the class still is constant here, it's fine to keep + return !c->cache.isConstantOptimizedCache(); + }); +} + /********************************************************************** * addMethod @@ -6671,8 +7069,6 @@ BOOL class_conformsToProtocol(Class cls, Protocol *proto_gen) result = _method_setImplementation(cls, m, imp); } } else { - auto rwe = cls->data()->extAllocIfNeeded(); - // fixme optimize method_list_t *newlist; newlist = (method_list_t *)calloc(method_list_t::byteSize(method_t::bigSize, 1), 1); @@ -6684,10 +7080,7 @@ BOOL class_conformsToProtocol(Class cls, Protocol *proto_gen) first.types = strdupIfMutable(types); first.imp = imp; - prepareMethodLists(cls, &newlist, 1, NO, NO); - rwe->methods.attachLists(&newlist, 1); - flushCaches(cls); - + addMethods_finish(cls, newlist); result = nil; } @@ -6750,17 +7143,9 @@ BOOL class_conformsToProtocol(Class cls, Protocol *proto_gen) } if (newlist->count > 0) { - auto rwe = cls->data()->extAllocIfNeeded(); - // fixme resize newlist because it may have been over-allocated above. // Note that realloc() alone doesn't work due to ptrauth. - - method_t::SortBySELAddress sorter; - std::stable_sort(&newlist->begin()->big(), &newlist->end()->big(), sorter); - - prepareMethodLists(cls, &newlist, 1, NO, NO); - rwe->methods.attachLists(&newlist, 1); - flushCaches(cls); + addMethods_finish(cls, newlist); } else { // Attaching the method list to the class consumes it. If we don't // do that, we have to free the memory ourselves. @@ -7096,7 +7481,7 @@ bool includeClassHandler __attribute__((unused))) duplicate = alloc_class_for_subclass(original, extraBytes); duplicate->initClassIsa(original->ISA()); - duplicate->superclass = original->superclass; + duplicate->setSuperclass(original->getSuperclass()); duplicate->cache.initializeToEmpty(); @@ -7128,8 +7513,8 @@ bool includeClassHandler __attribute__((unused))) duplicate->chooseClassArrayIndex(); - if (duplicate->superclass) { - addSubclass(duplicate->superclass, duplicate); + if (duplicate->getSuperclass()) { + addSubclass(duplicate->getSuperclass(), duplicate); // duplicate->isa == original->isa so don't addSubclass() for it } else { addRootClass(duplicate); @@ -7137,7 +7522,7 @@ bool includeClassHandler __attribute__((unused))) // Don't methodize class - construction above is correct - addNamedClass(duplicate, ro->name); + addNamedClass(duplicate, ro->getName()); addClassTableEntry(duplicate, /*addMeta=*/false); if (PrintConnecting) { @@ -7198,8 +7583,8 @@ static void objc_initializeClassPair_internal(Class superclass, const char *name meta->setInstanceSize(meta_ro_w->instanceStart); } - cls_ro_w->name = strdupIfMutable(name); - meta_ro_w->name = strdupIfMutable(name); + cls_ro_w->name.store(strdupIfMutable(name), std::memory_order_release); + meta_ro_w->name.store(strdupIfMutable(name), std::memory_order_release); cls_ro_w->ivarLayout = &UnsetLayout; cls_ro_w->weakIvarLayout = &UnsetLayout; @@ -7222,14 +7607,14 @@ static void objc_initializeClassPair_internal(Class superclass, const char *name if (superclass) { meta->initClassIsa(superclass->ISA()->ISA()); - cls->superclass = superclass; - meta->superclass = superclass->ISA(); + cls->setSuperclass(superclass); + meta->setSuperclass(superclass->ISA()); addSubclass(superclass, cls); addSubclass(superclass->ISA(), meta); } else { meta->initClassIsa(meta); - cls->superclass = Nil; - meta->superclass = cls; + cls->setSuperclass(Nil); + meta->setSuperclass(cls); addRootClass(cls); addSubclass(cls, meta); } @@ -7336,7 +7721,7 @@ void objc_registerClassPair(Class cls) (cls->ISA()->data()->flags & RW_CONSTRUCTED)) { _objc_inform("objc_registerClassPair: class '%s' was already " - "registered!", cls->data()->ro()->name); + "registered!", cls->data()->ro()->getName()); return; } @@ -7345,7 +7730,7 @@ void objc_registerClassPair(Class cls) { _objc_inform("objc_registerClassPair: class '%s' was not " "allocated with objc_allocateClassPair!", - cls->data()->ro()->name); + cls->data()->ro()->getName()); return; } @@ -7354,7 +7739,7 @@ void objc_registerClassPair(Class cls) cls->changeInfo(RW_CONSTRUCTED, RW_CONSTRUCTING | RW_REALIZING); // Add to named class table. - addNamedClass(cls, cls->data()->ro()->name); + addNamedClass(cls, cls->data()->ro()->getName()); } @@ -7377,7 +7762,7 @@ Class objc_readClassPair(Class bits, const struct objc_image_info *info) // Fail if the superclass isn't kosher. bool rootOK = bits->data()->flags & RO_ROOT; - if (!verifySuperclass(bits->superclass, rootOK)){ + if (!verifySuperclass(bits->getSuperclass(), rootOK)){ return nil; } @@ -7416,7 +7801,7 @@ static void detach_class(Class cls, bool isMeta) // superclass's subclass list if (cls->isRealized()) { - Class supercls = cls->superclass; + Class supercls = cls->getSuperclass(); if (supercls) { removeSubclass(supercls, cls); } else { @@ -7448,7 +7833,7 @@ static void free_class(Class cls) auto rwe = rw->ext(); auto ro = rw->ro(); - cache_delete(cls); + cls->cache.destroy(); if (rwe) { for (auto& meth : rwe->methods) { @@ -7477,9 +7862,9 @@ static void free_class(Class cls) rwe->protocols.tryFree(); } - try_free(ro->ivarLayout); + try_free(ro->getIvarLayout()); try_free(ro->weakIvarLayout); - try_free(ro->name); + try_free(ro->getName()); try_free(ro); objc::zfree(rwe); objc::zfree(rw); @@ -7500,25 +7885,25 @@ void objc_disposeClassPair(Class cls) // disposing still-unregistered class is OK! _objc_inform("objc_disposeClassPair: class '%s' was not " "allocated with objc_allocateClassPair!", - cls->data()->ro()->name); + cls->data()->ro()->getName()); return; } if (cls->isMetaClass()) { _objc_inform("objc_disposeClassPair: class '%s' is a metaclass, " - "not a class!", cls->data()->ro()->name); + "not a class!", cls->data()->ro()->getName()); return; } // Shouldn't have any live subclasses. if (cls->data()->firstSubclass) { _objc_inform("objc_disposeClassPair: class '%s' still has subclasses, " - "including '%s'!", cls->data()->ro()->name, + "including '%s'!", cls->data()->ro()->getName(), cls->data()->firstSubclass->nameForLogging()); } if (cls->ISA()->data()->firstSubclass) { _objc_inform("objc_disposeClassPair: class '%s' still has subclasses, " - "including '%s'!", cls->data()->ro()->name, + "including '%s'!", cls->data()->ro()->getName(), cls->ISA()->data()->firstSubclass->nameForLogging()); } @@ -7661,12 +8046,11 @@ void objc_disposeClassPair(Class cls) static id _object_copyFromZone(id oldObj, size_t extraBytes, void *zone) { - if (!oldObj) return nil; - if (oldObj->isTaggedPointer()) return oldObj; + if (oldObj->isTaggedPointerOrNil()) return oldObj; // fixme this doesn't handle C++ ivars correctly (#4619414) - Class cls = oldObj->ISA(); + Class cls = oldObj->ISA(/*authenticated*/true); size_t size; id obj = _class_createInstanceFromZone(cls, extraBytes, zone, OBJECT_CONSTRUCT_NONE, false, &size); @@ -7741,7 +8125,7 @@ void objc_disposeClassPair(Class cls) // This order is important. if (cxx) object_cxxDestruct(obj); - if (assoc) _object_remove_assocations(obj); + if (assoc) _object_remove_assocations(obj, /*deallocating*/true); obj->clearDeallocating(); } @@ -7826,6 +8210,8 @@ Class _objc_getFreedObjectClass (void) unsigned objc_debug_taggedpointer_ext_payload_rshift = 0; Class objc_debug_taggedpointer_ext_classes[1] = { nil }; +uintptr_t objc_debug_constant_cfstring_tag_bits = 0; + static void disableTaggedPointers() { } @@ -7853,6 +8239,13 @@ Class _objc_getFreedObjectClass (void) unsigned objc_debug_taggedpointer_ext_payload_rshift = _OBJC_TAG_EXT_PAYLOAD_RSHIFT; // objc_debug_taggedpointer_ext_classes is defined in objc-msg-*.s +#if OBJC_SPLIT_TAGGED_POINTERS +uint8_t objc_debug_tag60_permutations[8] = { 0, 1, 2, 3, 4, 5, 6, 7 }; +uintptr_t objc_debug_constant_cfstring_tag_bits = _OBJC_TAG_EXT_MASK | ((uintptr_t)(OBJC_TAG_Constant_CFString - OBJC_TAG_First52BitPayload) << _OBJC_TAG_EXT_SLOT_SHIFT); +#else +uintptr_t objc_debug_constant_cfstring_tag_bits = 0; +#endif + static void disableTaggedPointers() { @@ -7875,15 +8268,21 @@ Class _objc_getFreedObjectClass (void) static Class * classSlotForBasicTagIndex(objc_tag_index_t tag) { +#if OBJC_SPLIT_TAGGED_POINTERS + uintptr_t obfuscatedTag = _objc_basicTagToObfuscatedTag(tag); + return &objc_tag_classes[obfuscatedTag]; +#else uintptr_t tagObfuscator = ((objc_debug_taggedpointer_obfuscator >> _OBJC_TAG_INDEX_SHIFT) & _OBJC_TAG_INDEX_MASK); uintptr_t obfuscatedTag = tag ^ tagObfuscator; + // Array index in objc_tag_classes includes the tagged bit itself -#if SUPPORT_MSB_TAGGED_POINTERS +# if SUPPORT_MSB_TAGGED_POINTERS return &objc_tag_classes[0x8 | obfuscatedTag]; -#else +# else return &objc_tag_classes[(obfuscatedTag << 1) | 1]; +# endif #endif } @@ -7899,6 +8298,10 @@ Class _objc_getFreedObjectClass (void) if (tag >= OBJC_TAG_First52BitPayload && tag <= OBJC_TAG_Last52BitPayload) { int index = tag - OBJC_TAG_First52BitPayload; +#if OBJC_SPLIT_TAGGED_POINTERS + if (tag >= OBJC_TAG_FirstUnobfuscatedSplitTag) + return &objc_tag_ext_classes[index]; +#endif uintptr_t tagObfuscator = ((objc_debug_taggedpointer_obfuscator >> _OBJC_TAG_EXT_INDEX_SHIFT) & _OBJC_TAG_EXT_INDEX_MASK); @@ -7922,16 +8325,28 @@ Class _objc_getFreedObjectClass (void) static void initializeTaggedPointerObfuscator(void) { - if (sdkIsOlderThan(10_14, 12_0, 12_0, 5_0, 3_0) || - // Set the obfuscator to zero for apps linked against older SDKs, - // in case they're relying on the tagged pointer representation. - DisableTaggedPointerObfuscation) { - objc_debug_taggedpointer_obfuscator = 0; - } else { + if (!DisableTaggedPointerObfuscation && dyld_program_sdk_at_least(dyld_fall_2018_os_versions)) { // Pull random data into the variable, then shift away all non-payload bits. arc4random_buf(&objc_debug_taggedpointer_obfuscator, sizeof(objc_debug_taggedpointer_obfuscator)); objc_debug_taggedpointer_obfuscator &= ~_OBJC_TAG_MASK; + +#if OBJC_SPLIT_TAGGED_POINTERS + // The obfuscator doesn't apply to any of the extended tag mask or the no-obfuscation bit. + objc_debug_taggedpointer_obfuscator &= ~(_OBJC_TAG_EXT_MASK | _OBJC_TAG_NO_OBFUSCATION_MASK); + + // Shuffle the first seven entries of the tag permutator. + int max = 7; + for (int i = max - 1; i >= 0; i--) { + int target = arc4random_uniform(i + 1); + swap(objc_debug_tag60_permutations[i], + objc_debug_tag60_permutations[target]); + } +#endif + } else { + // Set the obfuscator to zero for apps linked against older SDKs, + // in case they're relying on the tagged pointer representation. + objc_debug_taggedpointer_obfuscator = 0; } } @@ -8079,19 +8494,19 @@ static Class setSuperclass(Class cls, Class newSuper) ASSERT(cls->isRealized()); ASSERT(newSuper->isRealized()); - oldSuper = cls->superclass; + oldSuper = cls->getSuperclass(); removeSubclass(oldSuper, cls); removeSubclass(oldSuper->ISA(), cls->ISA()); - cls->superclass = newSuper; - cls->ISA()->superclass = newSuper->ISA(); + cls->setSuperclass(newSuper); + cls->ISA()->setSuperclass(newSuper->ISA(/*authenticated*/true)); addSubclass(newSuper, cls); addSubclass(newSuper->ISA(), cls->ISA()); // Flush subclass's method caches. - flushCaches(cls); - flushCaches(cls->ISA()); - + flushCaches(cls, __func__, [](Class c){ return true; }); + flushCaches(cls->ISA(), __func__, [](Class c){ return true; }); + return oldSuper; } diff --git a/runtime/objc-runtime.mm b/runtime/objc-runtime.mm index 08a1b77..e38b274 100644 --- a/runtime/objc-runtime.mm +++ b/runtime/objc-runtime.mm @@ -33,6 +33,7 @@ * Imports. **********************************************************************/ +#include // os_feature_enabled_simple() #include "objc-private.h" #include "objc-loadmethod.h" #include "objc-file.h" @@ -87,6 +88,9 @@ #undef OPTION }; +namespace objc { + int PageCountWarning = 50; // Default value if the environment variable is not set +} // objc's key for pthread_getspecific #if SUPPORT_DIRECT_THREAD_KEYS @@ -338,6 +342,22 @@ void removeHeader(header_info *hi) #endif } +/*********************************************************************** +* SetPageCountWarning +* Convert environment variable value to integer value. +* If the value is valid, set the global PageCountWarning value. +**********************************************************************/ +void SetPageCountWarning(const char* envvar) { + if (envvar) { + long result = strtol(envvar, NULL, 10); + if (result <= INT_MAX && result >= -1) { + int32_t var = (int32_t)result; + if (var != 0) { // 0 is not a valid value for the env var + objc::PageCountWarning = var; + } + } + } +} /*********************************************************************** * environ_init @@ -352,6 +372,13 @@ void environ_init(void) return; } + // Turn off autorelease LRU coalescing by default for apps linked against + // older SDKs. LRU coalescing can reorder releases and certain older apps + // are accidentally relying on the ordering. + // rdar://problem/63886091 + if (!dyld_program_sdk_at_least(dyld_fall_2020_os_versions)) + DisableAutoreleaseCoalescingLRU = true; + bool PrintHelp = false; bool PrintOptions = false; bool maybeMallocDebugging = false; @@ -376,6 +403,11 @@ void environ_init(void) continue; } + if (0 == strncmp(*p, "OBJC_DEBUG_POOL_DEPTH=", 22)) { + SetPageCountWarning(*p + 22); + continue; + } + const char *value = strchr(*p, '='); if (!*value) continue; value++; @@ -388,10 +420,10 @@ void environ_init(void) *opt->var = (0 == strcmp(value, "YES")); break; } - } + } } - // Special case: enable some autorelease pool debugging + // Special case: enable some autorelease pool debugging // when some malloc debugging is enabled // and OBJC_DEBUG_POOL_ALLOCATION is not set to something other than NO. if (maybeMallocDebugging) { @@ -409,6 +441,10 @@ void environ_init(void) } } + if (!os_feature_enabled_simple(objc4, preoptimizedCaches, true)) { + DisablePreoptCaches = true; + } + // Print OBJC_HELP and OBJC_PRINT_OPTIONS output. if (PrintHelp || PrintOptions) { if (PrintHelp) { @@ -649,31 +685,25 @@ void objc_setEnumerationMutationHandler(void (*handler)(id)) { return _object_get_associative_reference(object, key); } -static void -_base_objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy) -{ - _object_set_associative_reference(object, key, value, policy); -} - -static ChainedHookFunction SetAssocHook{_base_objc_setAssociatedObject}; +typedef void (*objc_hook_setAssociatedObject)(id _Nonnull object, const void * _Nonnull key, + id _Nullable value, objc_AssociationPolicy policy); void objc_setHook_setAssociatedObject(objc_hook_setAssociatedObject _Nonnull newValue, objc_hook_setAssociatedObject _Nullable * _Nonnull outOldValue) { - SetAssocHook.set(newValue, outOldValue); + // See objc_object::setHasAssociatedObjects() for a replacement } void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy) { - SetAssocHook.get()(object, key, value, policy); + _object_set_associative_reference(object, key, value, policy); } - void objc_removeAssociatedObjects(id object) { if (object && object->hasAssociatedObjects()) { - _object_remove_assocations(object); + _object_remove_assocations(object, /*deallocating*/false); } } diff --git a/runtime/objc-sel-set.mm b/runtime/objc-sel-set.mm index ab21b00..0fcf6f6 100644 --- a/runtime/objc-sel-set.mm +++ b/runtime/objc-sel-set.mm @@ -120,7 +120,7 @@ static struct __objc_sel_set_finds __objc_sel_set_findBuckets(struct __objc_sel_ sset->_count = 0; // heuristic to convert executable's selrefs count to table size -#if TARGET_OS_IPHONE && !TARGET_OS_IOSMAC +#if TARGET_OS_IPHONE && !TARGET_OS_MACCATALYST for (idx = 0; __objc_sel_set_capacities[idx] < selrefs; idx++); if (idx > 0 && selrefs < 1536) idx--; #else diff --git a/runtime/objc-sel-table.s b/runtime/objc-sel-table.s index 6d9710d..3fb517a 100644 --- a/runtime/objc-sel-table.s +++ b/runtime/objc-sel-table.s @@ -2,7 +2,12 @@ #include #if __LP64__ +#if __arm64e__ +// 0x6AE1 +# define PTR(x) .quad x@AUTH(da, 27361, addr) +#else # define PTR(x) .quad x +#endif #else # define PTR(x) .long x #endif diff --git a/runtime/objc-sel.mm b/runtime/objc-sel.mm index da4c228..a8623d8 100644 --- a/runtime/objc-sel.mm +++ b/runtime/objc-sel.mm @@ -24,10 +24,8 @@ #if __OBJC2__ #include "objc-private.h" -#include "objc-cache.h" #include "DenseMapExtras.h" - static objc::ExplicitInitDenseSet namedSelectors; static SEL search_builtins(const char *key); @@ -69,6 +67,16 @@ static SEL sel_alloc(const char *name, bool copy) } +unsigned long sel_hash(SEL sel) +{ + unsigned long selAddr = (unsigned long)sel; +#if CONFIG_USE_PREOPT_CACHES + selAddr ^= (selAddr >> 7); +#endif + return selAddr; +} + + BOOL sel_isMapped(SEL sel) { if (!sel) return NO; diff --git a/runtime/objc-weak.h b/runtime/objc-weak.h index 8c50050..535fc88 100644 --- a/runtime/objc-weak.h +++ b/runtime/objc-weak.h @@ -123,9 +123,15 @@ struct weak_table_t { uintptr_t max_hash_displacement; }; +enum WeakRegisterDeallocatingOptions { + ReturnNilIfDeallocating, + CrashIfDeallocating, + DontCheckDeallocating +}; + /// Adds an (object, weak pointer) pair to the weak table. id weak_register_no_lock(weak_table_t *weak_table, id referent, - id *referrer, bool crashIfDeallocating); + id *referrer, WeakRegisterDeallocatingOptions deallocatingOptions); /// Removes an (object, weak pointer) pair from the weak table. void weak_unregister_no_lock(weak_table_t *weak_table, id referent, id *referrer); diff --git a/runtime/objc-weak.mm b/runtime/objc-weak.mm index 4d9c43d..3289953 100644 --- a/runtime/objc-weak.mm +++ b/runtime/objc-weak.mm @@ -389,38 +389,43 @@ static void weak_entry_remove(weak_table_t *weak_table, weak_entry_t *entry) */ id weak_register_no_lock(weak_table_t *weak_table, id referent_id, - id *referrer_id, bool crashIfDeallocating) + id *referrer_id, WeakRegisterDeallocatingOptions deallocatingOptions) { objc_object *referent = (objc_object *)referent_id; objc_object **referrer = (objc_object **)referrer_id; - if (!referent || referent->isTaggedPointer()) return referent_id; + if (referent->isTaggedPointerOrNil()) return referent_id; // ensure that the referenced object is viable - bool deallocating; - if (!referent->ISA()->hasCustomRR()) { - deallocating = referent->rootIsDeallocating(); - } - else { - BOOL (*allowsWeakReference)(objc_object *, SEL) = - (BOOL(*)(objc_object *, SEL)) - object_getMethodImplementation((id)referent, - @selector(allowsWeakReference)); - if ((IMP)allowsWeakReference == _objc_msgForward) { - return nil; + if (deallocatingOptions == ReturnNilIfDeallocating || + deallocatingOptions == CrashIfDeallocating) { + bool deallocating; + if (!referent->ISA()->hasCustomRR()) { + deallocating = referent->rootIsDeallocating(); } - deallocating = + else { + // Use lookUpImpOrForward so we can avoid the assert in + // class_getInstanceMethod, since we intentionally make this + // callout with the lock held. + auto allowsWeakReference = (BOOL(*)(objc_object *, SEL)) + lookUpImpOrForwardTryCache((id)referent, @selector(allowsWeakReference), + referent->getIsa()); + if ((IMP)allowsWeakReference == _objc_msgForward) { + return nil; + } + deallocating = ! (*allowsWeakReference)(referent, @selector(allowsWeakReference)); - } + } - if (deallocating) { - if (crashIfDeallocating) { - _objc_fatal("Cannot form weak reference to instance (%p) of " - "class %s. It is possible that this object was " - "over-released, or is in the process of deallocation.", - (void*)referent, object_getClassName((id)referent)); - } else { - return nil; + if (deallocating) { + if (deallocatingOptions == CrashIfDeallocating) { + _objc_fatal("Cannot form weak reference to instance (%p) of " + "class %s. It is possible that this object was " + "over-released, or is in the process of deallocation.", + (void*)referent, object_getClassName((id)referent)); + } else { + return nil; + } } } diff --git a/runtime/objc.h b/runtime/objc.h index 6b974a3..9e22d90 100644 --- a/runtime/objc.h +++ b/runtime/objc.h @@ -67,7 +67,7 @@ typedef id _Nullable (*IMP)(id _Nonnull, SEL _Nonnull, ...); # endif #else // __OBJC_BOOL_IS_BOOL not set. -# if TARGET_OS_OSX || TARGET_OS_IOSMAC || ((TARGET_OS_IOS || TARGET_OS_BRIDGE) && !__LP64__ && !__ARM_ARCH_7K) +# if TARGET_OS_OSX || TARGET_OS_MACCATALYST || ((TARGET_OS_IOS || TARGET_OS_BRIDGE) && !__LP64__ && !__ARM_ARCH_7K) # define OBJC_BOOL_IS_BOOL 0 # else # define OBJC_BOOL_IS_BOOL 1 diff --git a/runtime/runtime.h b/runtime/runtime.h index c97129b..67145bd 100644 --- a/runtime/runtime.h +++ b/runtime/runtime.h @@ -1767,43 +1767,6 @@ OBJC_EXPORT void objc_setHook_getClass(objc_hook_getClass _Nonnull newValue, OBJC_AVAILABLE(10.14.4, 12.2, 12.2, 5.2, 3.2); #endif -/** - * Function type for a hook that assists objc_setAssociatedObject(). - * - * @param object The source object for the association. - * @param key The key for the association. - * @param value The value to associate with the key key for object. Pass nil to clear an existing association. - * @param policy The policy for the association. For possible values, see “Associative Object Behaviors.” - * - * @see objc_setAssociatedObject - * @see objc_setHook_setAssociatedObject - */ -typedef void (*objc_hook_setAssociatedObject)(id _Nonnull object, const void * _Nonnull key, - id _Nullable value, objc_AssociationPolicy policy); - -/** - * Install a hook for objc_setAssociatedObject(). - * - * @param newValue The hook function to install. - * @param outOldValue The address of a function pointer variable. On return, - * the old hook function is stored in the variable. - * - * @note The store to *outOldValue is thread-safe: the variable will be - * updated before objc_setAssociatedObject() calls your new hook to read it, - * even if your new hook is called from another thread before this - * setter completes. - * @note Your hook should always call the previous hook. - * - * @see objc_setAssociatedObject - * @see objc_hook_setAssociatedObject - */ -#if !(TARGET_OS_OSX && __i386__) -#define OBJC_SETASSOCIATEDOBJECTHOOK_DEFINED 1 -OBJC_EXPORT void objc_setHook_setAssociatedObject(objc_hook_setAssociatedObject _Nonnull newValue, - objc_hook_setAssociatedObject _Nullable * _Nonnull outOldValue) - OBJC_AVAILABLE(10.15, 13.0, 13.0, 6.0, 4.0); -#endif - /** * Function type for a function that is called when an image is loaded. * @@ -1831,7 +1794,39 @@ typedef void (*objc_func_loadImage)(const struct mach_header * _Nonnull header); OBJC_EXPORT void objc_addLoadImageFunc(objc_func_loadImage _Nonnull func) OBJC_AVAILABLE(10.15, 13.0, 13.0, 6.0, 4.0); -/** +/** + * Function type for a hook that provides a name for lazily named classes. + * + * @param cls The class to generate a name for. + * @return The name of the class, or NULL if the name isn't known or can't me generated. + * + * @see objc_setHook_lazyClassNamer + */ +typedef const char * _Nullable (*objc_hook_lazyClassNamer)(_Nonnull Class cls); + +/** + * Install a hook to provide a name for lazily-named classes. + * + * @param newValue The hook function to install. + * @param outOldValue The address of a function pointer variable. On return, + * the old hook function is stored in the variable. + * + * @note The store to *outOldValue is thread-safe: the variable will be + * updated before objc_getClass() calls your new hook to read it, + * even if your new hook is called from another thread before this + * setter completes. + * @note Your hook must call the previous hook for class names + * that you do not recognize. + */ +#if !(TARGET_OS_OSX && __i386__) +#define OBJC_SETHOOK_LAZYCLASSNAMER_DEFINED 1 +OBJC_EXPORT +void objc_setHook_lazyClassNamer(_Nonnull objc_hook_lazyClassNamer newValue, + _Nonnull objc_hook_lazyClassNamer * _Nonnull oldOutValue) + OBJC_AVAILABLE(10.16, 14.0, 14.0, 7.0, 5.0); +#endif + +/** * Callback from Objective-C to Swift to perform Swift class initialization. */ #if !(TARGET_OS_OSX && __i386__) diff --git a/test/association.m b/test/association.m index e148fc5..35f81f4 100644 --- a/test/association.m +++ b/test/association.m @@ -3,6 +3,8 @@ #include "test.h" #include #include +#include +#include static int values; static int supers; @@ -85,6 +87,100 @@ -(void) dealloc { } @end +@interface Sub59318867: NSObject @end +@implementation Sub59318867 ++ (void)initialize { + objc_setAssociatedObject(self, &key, self, OBJC_ASSOCIATION_ASSIGN); +} +@end + +@interface CallOnDealloc: NSObject @end +@implementation CallOnDealloc { + void (^_block)(void); +} +- (id)initWithBlock: (void (^)(void))block { + _block = (__bridge id)Block_copy((__bridge void *)block); + return self; +} +- (void)dealloc { + _block(); + _Block_release((__bridge void *)_block); + SUPER_DEALLOC(); +} +@end + +void TestReleaseLater(void) { + int otherObjsCount = 100; + char keys1[otherObjsCount]; + char keys2[otherObjsCount]; + char laterKey; + + __block int normalDeallocs = 0; + __block int laterDeallocs = 0; + + { + id target = [NSObject new]; + for (int i = 0; i < otherObjsCount; i++) { + id value = [[CallOnDealloc alloc] initWithBlock: ^{ normalDeallocs++; }]; + objc_setAssociatedObject(target, keys1 + i, value, OBJC_ASSOCIATION_RETAIN); + RELEASE_VALUE(value); + } + { + id laterValue = [[CallOnDealloc alloc] initWithBlock: ^{ + testassertequal(laterDeallocs, 0); + testassertequal(normalDeallocs, otherObjsCount * 2); + laterDeallocs++; + }]; + objc_setAssociatedObject(target, &laterKey, laterValue, (objc_AssociationPolicy)(OBJC_ASSOCIATION_RETAIN | _OBJC_ASSOCIATION_SYSTEM_OBJECT)); + RELEASE_VALUE(laterValue); + } + for (int i = 0; i < otherObjsCount; i++) { + id value = [[CallOnDealloc alloc] initWithBlock: ^{ normalDeallocs++; }]; + objc_setAssociatedObject(target, keys2 + i, value, OBJC_ASSOCIATION_RETAIN); + RELEASE_VALUE(value); + } + RELEASE_VALUE(target); + } + testassertequal(laterDeallocs, 1); + testassertequal(normalDeallocs, otherObjsCount * 2); +} + +void TestReleaseLaterRemoveAssociations(void) { + + char normalKey; + char laterKey; + + __block int normalDeallocs = 0; + __block int laterDeallocs = 0; + + @autoreleasepool { + id target = [NSObject new]; + { + id normalValue = [[CallOnDealloc alloc] initWithBlock: ^{ normalDeallocs++; }]; + id laterValue = [[CallOnDealloc alloc] initWithBlock: ^{ laterDeallocs++; }]; + objc_setAssociatedObject(target, &normalKey, normalValue, OBJC_ASSOCIATION_RETAIN); + objc_setAssociatedObject(target, &laterKey, laterValue, (objc_AssociationPolicy)(OBJC_ASSOCIATION_RETAIN | _OBJC_ASSOCIATION_SYSTEM_OBJECT)); + RELEASE_VALUE(normalValue); + RELEASE_VALUE(laterValue); + } + testassertequal(normalDeallocs, 0); + testassertequal(laterDeallocs, 0); + + objc_removeAssociatedObjects(target); + testassertequal(normalDeallocs, 1); + testassertequal(laterDeallocs, 0); + + id normalValue = objc_getAssociatedObject(target, &normalKey); + id laterValue = objc_getAssociatedObject(target, &laterKey); + testassert(!normalValue); + testassert(laterValue); + + RELEASE_VALUE(target); + } + + testassertequal(normalDeallocs, 1); + testassertequal(laterDeallocs, 1); +} int main() { @@ -123,5 +219,13 @@ int main() objc_setAssociatedObject(nil, &key, nil, OBJC_ASSOCIATION_ASSIGN); #pragma clang diagnostic pop + // rdar://problem/59318867 Make sure we don't reenter the association lock + // when setting an associated object on an uninitialized class. + Class Sub59318867Local = objc_getClass("Sub59318867"); + objc_setAssociatedObject(Sub59318867Local, &key, Sub59318867Local, OBJC_ASSOCIATION_ASSIGN); + + TestReleaseLater(); + TestReleaseLaterRemoveAssociations(); + succeed(__FILE__); } diff --git a/test/badPoolCompat-ios-tvos.m b/test/badPoolCompat-ios-tvos.m deleted file mode 100644 index 5f1b92c..0000000 --- a/test/badPoolCompat-ios-tvos.m +++ /dev/null @@ -1,14 +0,0 @@ -// Run test badPool as if it were built with an old SDK. - -// TEST_CONFIG MEM=mrc OS=iphoneos,iphonesimulator,appletvos,appletvsimulator -// TEST_CRASHES -// TEST_CFLAGS -DOLD=1 -Xlinker -sdk_version -Xlinker 9.0 - -/* -TEST_RUN_OUTPUT -objc\[\d+\]: Invalid or prematurely-freed autorelease pool 0x[0-9a-fA-f]+\. Set a breakpoint .* Proceeding anyway .* -OK: badPool.m -END -*/ - -#include "badPool.m" diff --git a/test/badPoolCompat-ios.m b/test/badPoolCompat-ios.m new file mode 100644 index 0000000..a5f684f --- /dev/null +++ b/test/badPoolCompat-ios.m @@ -0,0 +1,18 @@ +// Run test badPool as if it were built with an old SDK. + +// TEST_CONFIG MEM=mrc OS=iphoneos,iphonesimulator ARCH=x86_64,arm64 +// TEST_CRASHES +// TEST_CFLAGS -DOLD=1 -Xlinker -platform_version -Xlinker ios -Xlinker 9.0 -Xlinker 9.0 -miphoneos-version-min=9.0 + +/* +TEST_BUILD_OUTPUT +ld: warning: passed two min versions.*for platform.* +END + +TEST_RUN_OUTPUT +objc\[\d+\]: Invalid or prematurely-freed autorelease pool 0x[0-9a-fA-f]+\. Set a breakpoint .* Proceeding anyway .* +OK: badPool.m +END +*/ + +#include "badPool.m" diff --git a/test/badPoolCompat-macos.m b/test/badPoolCompat-macos.m index afd2117..1131c83 100644 --- a/test/badPoolCompat-macos.m +++ b/test/badPoolCompat-macos.m @@ -1,10 +1,14 @@ // Run test badPool as if it were built with an old SDK. -// TEST_CONFIG MEM=mrc OS=macosx +// TEST_CONFIG MEM=mrc OS=macosx ARCH=x86_64 // TEST_CRASHES -// TEST_CFLAGS -DOLD=1 -Xlinker -sdk_version -Xlinker 10.11 +// TEST_CFLAGS -DOLD=1 -Xlinker -platform_version -Xlinker macos -Xlinker 10.11 -Xlinker 10.11 -mmacosx-version-min=10.11 /* +TEST_BUILD_OUTPUT +ld: warning: passed two min versions.*for platform.* +END + TEST_RUN_OUTPUT objc\[\d+\]: Invalid or prematurely-freed autorelease pool 0x[0-9a-fA-f]+\. Set a breakpoint .* Proceeding anyway .* OK: badPool.m diff --git a/test/badPoolCompat-tvos.m b/test/badPoolCompat-tvos.m new file mode 100644 index 0000000..3adfacd --- /dev/null +++ b/test/badPoolCompat-tvos.m @@ -0,0 +1,18 @@ +// Run test badPool as if it were built with an old SDK. + +// TEST_CONFIG MEM=mrc OS=appletvos,appletvsimulator ARCH=x86_64,arm64 +// TEST_CRASHES +// TEST_CFLAGS -DOLD=1 -Xlinker -platform_version -Xlinker tvos -Xlinker 9.0 -Xlinker 9.0 -mtvos-version-min=9.0 + +/* +TEST_BUILD_OUTPUT +ld: warning: passed two min versions.*for platform.* +END + +TEST_RUN_OUTPUT +objc\[\d+\]: Invalid or prematurely-freed autorelease pool 0x[0-9a-fA-f]+\. Set a breakpoint .* Proceeding anyway .* +OK: badPool.m +END +*/ + +#include "badPool.m" diff --git a/test/badPoolCompat-watchos.m b/test/badPoolCompat-watchos.m index 6e89e44..19e8ca7 100644 --- a/test/badPoolCompat-watchos.m +++ b/test/badPoolCompat-watchos.m @@ -2,9 +2,13 @@ // TEST_CONFIG MEM=mrc OS=watchos,watchsimulator // TEST_CRASHES -// TEST_CFLAGS -DOLD=1 -Xlinker -sdk_version -Xlinker 2.0 +// TEST_CFLAGS -DOLD=1 -Xlinker -platform_version -Xlinker watchos -Xlinker 2.0 -Xlinker 2.0 -mwatchos-version-min=2.0 /* +TEST_BUILD_OUTPUT +ld: warning: passed two min versions.*for platform.* +END + TEST_RUN_OUTPUT objc\[\d+\]: Invalid or prematurely-freed autorelease pool 0x[0-9a-fA-f]+\. Set a breakpoint .* Proceeding anyway .* OK: badPool.m diff --git a/test/badSuperclass.m b/test/badSuperclass.m index 2fa0bc7..2ac22b5 100644 --- a/test/badSuperclass.m +++ b/test/badSuperclass.m @@ -26,7 +26,7 @@ int main() // Create a cycle in a superclass chain (Sub->supercls == Sub) // then attempt to walk that chain. Runtime should halt eventually. _objc_flush_caches(supercls); - ((Class *)(__bridge void *)subcls)[1] = subcls; + ((Class __ptrauth_objc_super_pointer *)(__bridge void *)subcls)[1] = subcls; #ifdef CACHE_FLUSH _objc_flush_caches(supercls); #else diff --git a/test/bigrc.m b/test/bigrc.m index 419bbb6..3918d8f 100644 --- a/test/bigrc.m +++ b/test/bigrc.m @@ -1,13 +1,4 @@ // TEST_CONFIG MEM=mrc -/* -TEST_RUN_OUTPUT -objc\[\d+\]: Deallocator object 0x[0-9a-fA-F]+ overreleased while already deallocating; break on objc_overrelease_during_dealloc_error to debug -OK: bigrc.m -OR -no overrelease enforcement -OK: bigrc.m -END - */ #include "test.h" #include "testroot.i" @@ -20,37 +11,15 @@ @implementation Deallocator -(void)dealloc { id o = self; - size_t rc = 1; - testprintf("Retain a lot during dealloc\n"); + testprintf("Retain/release during dealloc\n"); - testassert(rc == 1); - testassert([o retainCount] == rc); - do { - [o retain]; - if (rc % 0x100000 == 0) testprintf("%zx/%zx ++\n", rc, LOTS); - } while (++rc < LOTS); - - testassert([o retainCount] == rc); - - do { - [o release]; - if (rc % 0x100000 == 0) testprintf("%zx/%zx --\n", rc, LOTS); - } while (--rc > 1); - - testassert(rc == 1); - testassert([o retainCount] == rc); - - - testprintf("Overrelease during dealloc\n"); - - // Not all architectures enforce this. -#if !SUPPORT_NONPOINTER_ISA - testwarn("no overrelease enforcement"); - fprintf(stderr, "no overrelease enforcement\n"); -#endif + testassertequal([o retainCount], 0); + [o retain]; + testassertequal([o retainCount], 0); [o release]; + testassertequal([o retainCount], 0); [super dealloc]; } diff --git a/test/bool.c b/test/bool.c index c12cc32..f112414 100644 --- a/test/bool.c +++ b/test/bool.c @@ -5,7 +5,11 @@ #include #if TARGET_OS_OSX -# define RealBool 0 +# if __x86_64__ +# define RealBool 0 +# else +# define RealBool 1 +# endif #elif TARGET_OS_IOS || TARGET_OS_BRIDGE # if (__arm__ && !__armv7k__) || __i386__ # define RealBool 0 diff --git a/test/cacheflush-constant.m b/test/cacheflush-constant.m new file mode 100644 index 0000000..94da6e2 --- /dev/null +++ b/test/cacheflush-constant.m @@ -0,0 +1,44 @@ +// TEST_CFLAGS -framework Foundation +/* +TEST_RUN_OUTPUT +foo +bar +bar +foo +END +*/ + +// NOTE: This test won't catch problems when running against a root, so it's of +// limited utility, but it would at least catch things when testing against the +// shared cache. + +#include +#include + +@interface NSBlock: NSObject @end + +// NSBlock is a conveniently accessible superclass that (currently) has a constant cache. +@interface MyBlock: NSBlock ++(void)foo; ++(void)bar; +@end +@implementation MyBlock ++(void)foo { + printf("foo\n"); +} ++(void)bar { + printf("bar\n"); +} +@end + +int main() { + [MyBlock foo]; + [MyBlock bar]; + + Method m1 = class_getClassMethod([MyBlock class], @selector(foo)); + Method m2 = class_getClassMethod([MyBlock class], @selector(bar)); + method_exchangeImplementations(m1, m2); + + [MyBlock foo]; + [MyBlock bar]; +} diff --git a/test/category.m b/test/category.m index 80795e2..334bc5f 100644 --- a/test/category.m +++ b/test/category.m @@ -135,25 +135,25 @@ @implementation PropertyClass (PropertyCategory) "l_OBJC_$_CATEGORY_INSTANCE_METHODS_Super_$_Category_catlist2: \n" " .long 24 \n" " .long 1 \n" -" "PTR" L_catlist2MethodString \n" -" "PTR" L_catlist2MethodTypes \n" -" "PTR" _catlist2MethodImplementation"SIGNED_CATEGORY_IMP" \n" +" " PTR " L_catlist2MethodString \n" +" " PTR " L_catlist2MethodTypes \n" +" " PTR " _catlist2MethodImplementation" SIGNED_CATEGORY_IMP" \n" " .p2align 3 \n" "l_OBJC_$_CATEGORY_Super_$_Category_catlist2: \n" -" "PTR" L_catlist2CategoryName \n" -" "PTR" _OBJC_CLASS_$_Super \n" -" "PTR" l_OBJC_$_CATEGORY_INSTANCE_METHODS_Super_$_Category_catlist2 \n" -" "PTR" 0 \n" -" "PTR" 0 \n" -" "PTR" 0 \n" -" "PTR" 0 \n" +" " PTR " L_catlist2CategoryName \n" +" " PTR " _OBJC_CLASS_$_Super \n" +" " PTR " l_OBJC_$_CATEGORY_INSTANCE_METHODS_Super_$_Category_catlist2 \n" +" " PTR " 0 \n" +" " PTR " 0 \n" +" " PTR " 0 \n" +" " PTR " 0 \n" " .long 64 \n" " .space 4 \n" " .section __DATA,__objc_catlist2 \n" " .p2align 3 \n" -" "PTR" l_OBJC_$_CATEGORY_Super_$_Category_catlist2 \n" +" " PTR " l_OBJC_$_CATEGORY_Super_$_Category_catlist2 \n" " .text \n" ); diff --git a/test/consolidatePoolPointers.m b/test/consolidatePoolPointers.m new file mode 100644 index 0000000..241df6f --- /dev/null +++ b/test/consolidatePoolPointers.m @@ -0,0 +1,142 @@ +//TEST_CONFIG MEM=mrc ARCH=x86_64,ARM64,ARM64e +//TEST_ENV OBJC_DISABLE_AUTORELEASE_COALESCING=NO OBJC_DISABLE_AUTORELEASE_COALESCING_LRU=NO + +#include "test.h" +#import +#include + +@interface Counter: NSObject { +@public + int retains; + int releases; + int autoreleases; +} +@end +@implementation Counter + +- (id)retain { + retains++; + return [super retain]; +} + +- (oneway void)release { + releases++; + [super release]; +} + +- (id)autorelease { + autoreleases++; + return [super autorelease]; +} + +- (void)dealloc { + testprintf("%p dealloc\n", self); + [super dealloc]; +} + +@end + +// Create a number of objects, autoreleasing each one a number of times in a +// round robin fashion. Verify that each object gets sent retain, release, and +// autorelease the correct number of times. Verify that the gap between +// autoreleasepool pointers is the given number of objects. Note: this will not +// work when the pool hits a page boundary, to be sure to stay under that limit. +void test(int objCount, int autoreleaseCount, int expectedGap) { + testprintf("Testing %d objects, %d autoreleases, expecting gap of %d\n", + objCount, autoreleaseCount, expectedGap); + + Counter *objs[objCount]; + for (int i = 0; i < objCount; i++) + objs[i] = [Counter new]; + + for (int j = 0; j < autoreleaseCount; j++) + for (int i = 0; i < objCount; i++) + [objs[i] retain]; + + for (int i = 0; i < objCount; i++) { + testassertequal(objs[i]->retains, autoreleaseCount); + testassertequal(objs[i]->releases, 0); + testassertequal(objs[i]->autoreleases, 0); + } + + void *outer = objc_autoreleasePoolPush(); + uintptr_t outerAddr = (uintptr_t)outer; + for (int j = 0; j < autoreleaseCount; j++) + for (int i = 0; i < objCount; i++) + [objs[i] autorelease]; + for (int i = 0; i < objCount; i++) { + testassertequal(objs[i]->retains, autoreleaseCount); + testassertequal(objs[i]->releases, 0); + testassertequal(objs[i]->autoreleases, autoreleaseCount); + } + + void *inner = objc_autoreleasePoolPush(); + uintptr_t innerAddr = (uintptr_t)inner; + testprintf("outer=%p inner=%p\n", outer, inner); + // Do one more autorelease in the inner pool to make sure we correctly + // handle pool boundaries. + for (int i = 0; i < objCount; i++) + [[objs[i] retain] autorelease]; + for (int i = 0; i < objCount; i++) { + testassertequal(objs[i]->retains, autoreleaseCount + 1); + testassertequal(objs[i]->releases, 0); + testassertequal(objs[i]->autoreleases, autoreleaseCount + 1); + } + + objc_autoreleasePoolPop(inner); + for (int i = 0; i < objCount; i++) { + testassertequal(objs[i]->retains, autoreleaseCount + 1); + testassertequal(objs[i]->releases, 1); + testassertequal(objs[i]->autoreleases, autoreleaseCount + 1); + } + + objc_autoreleasePoolPop(outer); + for (int i = 0; i < objCount; i++) { + testassertequal(objs[i]->retains, autoreleaseCount + 1); + testassertequal(objs[i]->releases, autoreleaseCount + 1); + testassertequal(objs[i]->autoreleases, autoreleaseCount + 1); + } + + intptr_t gap = innerAddr - outerAddr; + testprintf("gap=%ld\n", gap); + testassertequal(gap, expectedGap * sizeof(id)); + + // Destroy our test objects. + for (int i = 0; i < objCount; i++) + [objs[i] release]; +} + +int main() +{ + // Push a pool here so test() doesn't see a placeholder. + objc_autoreleasePoolPush(); + + test(1, 1, 2); + test(1, 2, 2); + test(1, 10, 2); + test(1, 100, 2); + test(1, 70000, 3); + + test(2, 1, 3); + test(2, 2, 3); + test(2, 10, 3); + test(2, 100, 3); + test(2, 70000, 5); + + test(3, 1, 4); + test(3, 2, 4); + test(3, 10, 4); + test(3, 100, 4); + test(3, 70000, 7); + + test(4, 1, 5); + test(4, 2, 5); + test(4, 10, 5); + test(4, 100, 5); + test(4, 70000, 9); + + test(5, 1, 6); + test(5, 2, 11); + + succeed(__FILE__); +} diff --git a/test/customrr-nsobject.m b/test/customrr-nsobject.m index 912f414..f25e4ad 100644 --- a/test/customrr-nsobject.m +++ b/test/customrr-nsobject.m @@ -10,6 +10,8 @@ typedef IMP MethodListIMP; #endif +EXTERN_C void _method_setImplementationRawUnsafe(Method m, IMP imp); + static int Retains; static int Releases; static int Autoreleases; @@ -64,7 +66,7 @@ int main(int argc __unused, char **argv) #if SWIZZLE_AWZ method_setImplementation(meth, (IMP)HackAllocWithZone); #else - ((MethodListIMP *)meth)[2] = (IMP)HackAllocWithZone; + _method_setImplementationRawUnsafe(meth, (IMP)HackAllocWithZone); #endif meth = class_getClassMethod(cls, @selector(new)); @@ -72,7 +74,7 @@ int main(int argc __unused, char **argv) #if SWIZZLE_CORE method_setImplementation(meth, (IMP)HackPlusNew); #else - ((MethodListIMP *)meth)[2] = (IMP)HackPlusNew; + _method_setImplementationRawUnsafe(meth, (IMP)HackPlusNew); #endif meth = class_getClassMethod(cls, @selector(self)); @@ -80,7 +82,7 @@ int main(int argc __unused, char **argv) #if SWIZZLE_CORE method_setImplementation(meth, (IMP)HackPlusSelf); #else - ((MethodListIMP *)meth)[2] = (IMP)HackPlusSelf; + _method_setImplementationRawUnsafe(meth, (IMP)HackPlusSelf); #endif meth = class_getInstanceMethod(cls, @selector(self)); @@ -88,7 +90,7 @@ int main(int argc __unused, char **argv) #if SWIZZLE_CORE method_setImplementation(meth, (IMP)HackSelf); #else - ((MethodListIMP *)meth)[2] = (IMP)HackSelf; + _method_setImplementationRawUnsafe(meth, (IMP)HackSelf); #endif meth = class_getInstanceMethod(cls, @selector(release)); @@ -96,25 +98,25 @@ int main(int argc __unused, char **argv) #if SWIZZLE_RELEASE method_setImplementation(meth, (IMP)HackRelease); #else - ((MethodListIMP *)meth)[2] = (IMP)HackRelease; + _method_setImplementationRawUnsafe(meth, (IMP)HackRelease); #endif // These other methods get hacked for counting purposes only meth = class_getInstanceMethod(cls, @selector(retain)); RealRetain = (typeof(RealRetain))method_getImplementation(meth); - ((MethodListIMP *)meth)[2] = (IMP)HackRetain; + _method_setImplementationRawUnsafe(meth, (IMP)HackRetain); meth = class_getInstanceMethod(cls, @selector(autorelease)); RealAutorelease = (typeof(RealAutorelease))method_getImplementation(meth); - ((MethodListIMP *)meth)[2] = (IMP)HackAutorelease; + _method_setImplementationRawUnsafe(meth, (IMP)HackAutorelease); meth = class_getClassMethod(cls, @selector(alloc)); RealAlloc = (typeof(RealAlloc))method_getImplementation(meth); - ((MethodListIMP *)meth)[2] = (IMP)HackAlloc; + _method_setImplementationRawUnsafe(meth, (IMP)HackAlloc); meth = class_getInstanceMethod(cls, @selector(init)); - ((MethodListIMP *)meth)[2] = (IMP)HackInit; + _method_setImplementationRawUnsafe(meth, (IMP)HackInit); // Verify that the swizzles occurred before +initialize by provoking it now testassert(PlusInitializes == 0); diff --git a/test/customrr.m b/test/customrr.m index 4ebcece..633c260 100644 --- a/test/customrr.m +++ b/test/customrr.m @@ -191,38 +191,31 @@ int main(int argc __unused, char **argv) // Don't use runtime functions to do this - // we want the runtime to think that these are NSObject's real code { -#if __has_feature(ptrauth_calls) - typedef IMP __ptrauth_objc_method_list_imp MethodListIMP; -#else - typedef IMP MethodListIMP; -#endif - Class cls = [NSObject class]; IMP imp = class_getMethodImplementation(cls, @selector(retain)); - MethodListIMP *m = (MethodListIMP *) - class_getInstanceMethod(cls, @selector(retain)); - testassert(m[2] == imp); // verify Method struct is as we expect - - m = (MethodListIMP *)class_getInstanceMethod(cls, @selector(retain)); - m[2] = (IMP)HackRetain; - m = (MethodListIMP *)class_getInstanceMethod(cls, @selector(release)); - m[2] = (IMP)HackRelease; - m = (MethodListIMP *)class_getInstanceMethod(cls, @selector(autorelease)); - m[2] = (IMP)HackAutorelease; - m = (MethodListIMP *)class_getInstanceMethod(cls, @selector(retainCount)); - m[2] = (IMP)HackRetainCount; - m = (MethodListIMP *)class_getClassMethod(cls, @selector(retain)); - m[2] = (IMP)HackPlusRetain; - m = (MethodListIMP *)class_getClassMethod(cls, @selector(release)); - m[2] = (IMP)HackPlusRelease; - m = (MethodListIMP *)class_getClassMethod(cls, @selector(autorelease)); - m[2] = (IMP)HackPlusAutorelease; - m = (MethodListIMP *)class_getClassMethod(cls, @selector(retainCount)); - m[2] = (IMP)HackPlusRetainCount; - m = (MethodListIMP *)class_getClassMethod(cls, @selector(alloc)); - m[2] = (IMP)HackAlloc; - m = (MethodListIMP *)class_getClassMethod(cls, @selector(allocWithZone:)); - m[2] = (IMP)HackAllocWithZone; + Method m = class_getInstanceMethod(cls, @selector(retain)); + testassert(method_getImplementation(m) == imp); // verify Method struct is as we expect + + m = class_getInstanceMethod(cls, @selector(retain)); + _method_setImplementationRawUnsafe(m, (IMP)HackRetain); + m = class_getInstanceMethod(cls, @selector(release)); + _method_setImplementationRawUnsafe(m, (IMP)HackRelease); + m = class_getInstanceMethod(cls, @selector(autorelease)); + _method_setImplementationRawUnsafe(m, (IMP)HackAutorelease); + m = class_getInstanceMethod(cls, @selector(retainCount)); + _method_setImplementationRawUnsafe(m, (IMP)HackRetainCount); + m = class_getClassMethod(cls, @selector(retain)); + _method_setImplementationRawUnsafe(m, (IMP)HackPlusRetain); + m = class_getClassMethod(cls, @selector(release)); + _method_setImplementationRawUnsafe(m, (IMP)HackPlusRelease); + m = class_getClassMethod(cls, @selector(autorelease)); + _method_setImplementationRawUnsafe(m, (IMP)HackPlusAutorelease); + m = class_getClassMethod(cls, @selector(retainCount)); + _method_setImplementationRawUnsafe(m, (IMP)HackPlusRetainCount); + m = class_getClassMethod(cls, @selector(alloc)); + _method_setImplementationRawUnsafe(m, (IMP)HackAlloc); + m = class_getClassMethod(cls, @selector(allocWithZone:)); + _method_setImplementationRawUnsafe(m, (IMP)HackAllocWithZone); _objc_flush_caches(cls); diff --git a/test/evil-class-def.m b/test/evil-class-def.m index c49bda8..066691a 100644 --- a/test/evil-class-def.m +++ b/test/evil-class-def.m @@ -12,8 +12,14 @@ #if __has_feature(ptrauth_calls) # define SIGNED_METHOD_LIST_IMP "@AUTH(ia,0,addr) " +# define SIGNED_METHOD_LIST "@AUTH(da,0xC310,addr) " +# define SIGNED_ISA "@AUTH(da, 0x6AE1, addr) " +# define SIGNED_SUPER "@AUTH(da, 0xB5AB, addr) " #else # define SIGNED_METHOD_LIST_IMP +# define SIGNED_METHOD_LIST +# define SIGNED_ISA +# define SIGNED_SUPER #endif #define str(x) #x @@ -25,15 +31,15 @@ __END_DECLS asm( - ".globl _OBJC_CLASS_$_Super \n" - ".section __DATA,__objc_data \n" - ".align 3 \n" - "_OBJC_CLASS_$_Super: \n" - PTR "_OBJC_METACLASS_$_Super \n" - PTR "0 \n" - PTR "__objc_empty_cache \n" - PTR "0 \n" - PTR "L_ro \n" + ".globl _OBJC_CLASS_$_Super \n" + ".section __DATA,__objc_data \n" + ".align 3 \n" + "_OBJC_CLASS_$_Super: \n" + PTR "_OBJC_METACLASS_$_Super" SIGNED_ISA "\n" + PTR "0 \n" + PTR "__objc_empty_cache \n" + PTR "0 \n" + PTR "L_ro \n" // pad to OBJC_MAX_CLASS_SIZE PTR "0 \n" PTR "0 \n" @@ -63,12 +69,12 @@ PTR "0 \n" PTR "0 \n" "" - "_OBJC_METACLASS_$_Super: \n" - PTR "_OBJC_METACLASS_$_Super \n" - PTR "_OBJC_CLASS_$_Super \n" - PTR "__objc_empty_cache \n" - PTR "0 \n" - PTR "L_meta_ro \n" + "_OBJC_METACLASS_$_Super: \n" + PTR "_OBJC_METACLASS_$_Super" SIGNED_ISA "\n" + PTR "_OBJC_CLASS_$_Super" SIGNED_SUPER "\n" + PTR "__objc_empty_cache \n" + PTR "0 \n" + PTR "L_meta_ro \n" // pad to OBJC_MAX_CLASS_SIZE PTR "0 \n" PTR "0 \n" @@ -108,9 +114,9 @@ PTR "0 \n" PTR "L_super_name \n" #if EVIL_SUPER - PTR "L_evil_methods \n" + PTR "L_evil_methods" SIGNED_METHOD_LIST "\n" #else - PTR "L_good_methods \n" + PTR "L_good_methods" SIGNED_METHOD_LIST "\n" #endif PTR "0 \n" PTR "L_super_ivars \n" @@ -127,24 +133,24 @@ PTR "0 \n" PTR "L_super_name \n" #if EVIL_SUPER_META - PTR "L_evil_methods \n" + PTR "L_evil_methods" SIGNED_METHOD_LIST "\n" #else - PTR "L_good_methods \n" + PTR "L_good_methods" SIGNED_METHOD_LIST "\n" #endif PTR "0 \n" PTR "0 \n" PTR "0 \n" PTR "0 \n" - ".globl _OBJC_CLASS_$_Sub \n" - ".section __DATA,__objc_data \n" - ".align 3 \n" - "_OBJC_CLASS_$_Sub: \n" - PTR "_OBJC_METACLASS_$_Sub \n" - PTR "_OBJC_CLASS_$_Super \n" - PTR "__objc_empty_cache \n" - PTR "0 \n" - PTR "L_sub_ro \n" + ".globl _OBJC_CLASS_$_Sub \n" + ".section __DATA,__objc_data \n" + ".align 3 \n" + "_OBJC_CLASS_$_Sub: \n" + PTR "_OBJC_METACLASS_$_Sub" SIGNED_ISA "\n" + PTR "_OBJC_CLASS_$_Super" SIGNED_SUPER "\n" + PTR "__objc_empty_cache \n" + PTR "0 \n" + PTR "L_sub_ro \n" // pad to OBJC_MAX_CLASS_SIZE PTR "0 \n" PTR "0 \n" @@ -174,12 +180,12 @@ PTR "0 \n" PTR "0 \n" "" - "_OBJC_METACLASS_$_Sub: \n" - PTR "_OBJC_METACLASS_$_Super \n" - PTR "_OBJC_METACLASS_$_Super \n" - PTR "__objc_empty_cache \n" - PTR "0 \n" - PTR "L_sub_meta_ro \n" + "_OBJC_METACLASS_$_Sub: \n" + PTR "_OBJC_METACLASS_$_Super" SIGNED_ISA "\n" + PTR "_OBJC_METACLASS_$_Super" SIGNED_SUPER "\n" + PTR "__objc_empty_cache \n" + PTR "0 \n" + PTR "L_sub_meta_ro \n" // pad to OBJC_MAX_CLASS_SIZE PTR "0 \n" PTR "0 \n" @@ -219,9 +225,9 @@ PTR "0 \n" PTR "L_sub_name \n" #if EVIL_SUB - PTR "L_evil_methods \n" + PTR "L_evil_methods" SIGNED_METHOD_LIST "\n" #else - PTR "L_good_methods \n" + PTR "L_good_methods" SIGNED_METHOD_LIST "\n" #endif PTR "0 \n" PTR "L_sub_ivars \n" @@ -238,9 +244,9 @@ PTR "0 \n" PTR "L_sub_name \n" #if EVIL_SUB_META - PTR "L_evil_methods \n" + PTR "L_evil_methods" SIGNED_METHOD_LIST "\n" #else - PTR "L_good_methods \n" + PTR "L_good_methods" SIGNED_METHOD_LIST "\n" #endif PTR "0 \n" PTR "0 \n" diff --git a/test/exchangeImp.m b/test/exchangeImp.m index da84f94..489c691 100644 --- a/test/exchangeImp.m +++ b/test/exchangeImp.m @@ -24,6 +24,9 @@ #include static int state; +static int swizzleOld; +static int swizzleNew; +static int swizzleB; #define ONE 1 #define TWO 2 @@ -36,6 +39,13 @@ +(void) one { state = ONE; } +(void) two { state = TWO; } +(void) length { state = LENGTH; } +(void) count { state = COUNT; } + +-(void) swizzleTarget { + swizzleOld++; +} +-(void) swizzleReplacement { + swizzleNew++; +} @end #define checkExchange(s1, v1, s2, v2) \ @@ -90,6 +100,42 @@ +(void) count { state = COUNT; } testassert(state == v2); \ } while (0) +@interface A : Super +@end +@implementation A +@end + +@interface B : Super +@end +@implementation B +- (void) swizzleTarget { + swizzleB++; +} +@end + +@interface C : Super +@end +@implementation C +- (void) hello { } +@end + +static IMP findInCache(Class cls, SEL sel) +{ + struct objc_imp_cache_entry *ents; + int count; + IMP ret = nil; + + ents = class_copyImpCache(cls, &count); + for (int i = 0; i < count; i++) { + if (ents[i].sel == sel) { + ret = ents[i].imp; + break; + } + } + free(ents); + return ret; +} + int main() { // Check ordinary selectors @@ -102,5 +148,66 @@ int main() checkExchange(count, COUNT, one, ONE); checkExchange(two, TWO, length, LENGTH); + Super *s = [Super new]; + A *a = [A new]; + B *b = [B new]; + C *c = [C new]; + + // cache swizzleTarget in Super, A and B + [s swizzleTarget]; + testassert(swizzleOld == 1); + testassert(swizzleNew == 0); + testassert(swizzleB == 0); + testassert(findInCache([Super class], @selector(swizzleTarget)) != nil); + + [a swizzleTarget]; + testassert(swizzleOld == 2); + testassert(swizzleNew == 0); + testassert(swizzleB == 0); + testassert(findInCache([A class], @selector(swizzleTarget)) != nil); + + [b swizzleTarget]; + testassert(swizzleOld == 2); + testassert(swizzleNew == 0); + testassert(swizzleB == 1); + testassert(findInCache([B class], @selector(swizzleTarget)) != nil); + + // prime C's cache too + [c hello]; + testassert(findInCache([C class], @selector(hello)) != nil); + + Method m1 = class_getInstanceMethod([Super class], @selector(swizzleTarget)); + Method m2 = class_getInstanceMethod([Super class], @selector(swizzleReplacement)); + method_exchangeImplementations(m1, m2); + + // this should invalidate Super, A, but: + // - not B because it overrides - swizzleTarget and hence doesn't care + // - not C because it neither called swizzleTarget nor swizzleReplacement + testassert(findInCache([Super class], @selector(swizzleTarget)) == nil); + testassert(findInCache([A class], @selector(swizzleTarget)) == nil); + testassert(findInCache([B class], @selector(swizzleTarget)) != nil); + testassert(findInCache([C class], @selector(hello)) != nil); + + // now check that all lookups do the right thing + [s swizzleTarget]; + testassert(swizzleOld == 2); + testassert(swizzleNew == 1); + testassert(swizzleB == 1); + + [a swizzleTarget]; + testassert(swizzleOld == 2); + testassert(swizzleNew == 2); + testassert(swizzleB == 1); + + [b swizzleTarget]; + testassert(swizzleOld == 2); + testassert(swizzleNew == 2); + testassert(swizzleB == 2); + + [c swizzleTarget]; + testassert(swizzleOld == 2); + testassert(swizzleNew == 3); + testassert(swizzleB == 2); + succeed(__FILE__); } diff --git a/test/fakeRealizedClass.m b/test/fakeRealizedClass.m new file mode 100644 index 0000000..cec1c12 --- /dev/null +++ b/test/fakeRealizedClass.m @@ -0,0 +1,74 @@ +/* +Make sure we detect classes with the RW_REALIZED bit set in the binary. rdar://problem/67692760 +TEST_CONFIG OS=macosx +TEST_CRASHES +TEST_RUN_OUTPUT +objc\[\d+\]: realized class 0x[0-9a-fA-F]+ has corrupt data pointer 0x[0-9a-fA-F]+ +objc\[\d+\]: HALTED +END +*/ + +#include "test.h" + +#include + +#define RW_REALIZED (1U<<31) + +struct ObjCClass { + struct ObjCClass * __ptrauth_objc_isa_pointer isa; + struct ObjCClass * __ptrauth_objc_super_pointer superclass; + void *cachePtr; + uintptr_t zero; + uintptr_t data; +}; + +struct ObjCClass_ro { + uint32_t flags; + uint32_t instanceStart; + uint32_t instanceSize; +#ifdef __LP64__ + uint32_t reserved; +#endif + + union { + const uint8_t * ivarLayout; + struct ObjCClass * nonMetaClass; + }; + + const char * name; + struct ObjCMethodList * __ptrauth_objc_method_list_pointer baseMethodList; + struct protocol_list_t * baseProtocols; + const struct ivar_list_t * ivars; + + const uint8_t * weakIvarLayout; + struct property_list_t *baseProperties; +}; + +extern struct ObjCClass OBJC_METACLASS_$_NSObject; +extern struct ObjCClass OBJC_CLASS_$_NSObject; + +struct ObjCClass_ro FakeSuperclassRO = { + .flags = RW_REALIZED +}; + +struct ObjCClass FakeSuperclass = { + &OBJC_METACLASS_$_NSObject, + &OBJC_METACLASS_$_NSObject, + NULL, + 0, + (uintptr_t)&FakeSuperclassRO +}; + +struct ObjCClass_ro FakeSubclassRO; + +struct ObjCClass FakeSubclass = { + &FakeSuperclass, + &FakeSuperclass, + NULL, + 0, + (uintptr_t)&FakeSubclassRO +}; + +static struct ObjCClass *class_ptr __attribute__((used)) __attribute((section("__DATA,__objc_nlclslist"))) = &FakeSubclass; + +int main() {} diff --git a/test/fakeRealizedClass2.m b/test/fakeRealizedClass2.m new file mode 100644 index 0000000..487c4d2 --- /dev/null +++ b/test/fakeRealizedClass2.m @@ -0,0 +1,74 @@ +/* +Variant on fakeRealizedClass which tests a fake class with no superclass rdar://problem/67692760 +TEST_CONFIG OS=macosx +TEST_CRASHES +TEST_RUN_OUTPUT +objc\[\d+\]: realized class 0x[0-9a-fA-F]+ has corrupt data pointer 0x[0-9a-fA-F]+ +objc\[\d+\]: HALTED +END +*/ + +#include "test.h" + +#include + +#define RW_REALIZED (1U<<31) + +struct ObjCClass { + struct ObjCClass * __ptrauth_objc_isa_pointer isa; + struct ObjCClass * __ptrauth_objc_super_pointer superclass; + void *cachePtr; + uintptr_t zero; + uintptr_t data; +}; + +struct ObjCClass_ro { + uint32_t flags; + uint32_t instanceStart; + uint32_t instanceSize; +#ifdef __LP64__ + uint32_t reserved; +#endif + + union { + const uint8_t * ivarLayout; + struct ObjCClass * nonMetaClass; + }; + + const char * name; + struct ObjCMethodList * __ptrauth_objc_method_list_pointer baseMethodList; + struct protocol_list_t * baseProtocols; + const struct ivar_list_t * ivars; + + const uint8_t * weakIvarLayout; + struct property_list_t *baseProperties; +}; + +extern struct ObjCClass OBJC_METACLASS_$_NSObject; +extern struct ObjCClass OBJC_CLASS_$_NSObject; + +struct ObjCClass_ro FakeSuperclassRO = { + .flags = RW_REALIZED +}; + +struct ObjCClass FakeSuperclass = { + &OBJC_METACLASS_$_NSObject, + NULL, + NULL, + 0, + (uintptr_t)&FakeSuperclassRO +}; + +struct ObjCClass_ro FakeSubclassRO; + +struct ObjCClass FakeSubclass = { + &FakeSuperclass, + &FakeSuperclass, + NULL, + 0, + (uintptr_t)&FakeSubclassRO +}; + +static struct ObjCClass *class_ptr __attribute__((used)) __attribute((section("__DATA,__objc_nlclslist"))) = &FakeSubclass; + +int main() {} diff --git a/test/forward.m b/test/forward.m index 517f5e2..e1d133d 100644 --- a/test/forward.m +++ b/test/forward.m @@ -67,7 +67,7 @@ long long forward_handler(id self, SEL _cmd, long i1, long i2, long i3, long i4, # define p "w" // arm64_32 # endif void *struct_addr; - __asm__ volatile("mov %"p"0, "p"8" : "=r" (struct_addr) : : p"8"); + __asm__ volatile("mov %" p "0, " p "8" : "=r" (struct_addr) : : p "8"); #endif testassert(self == receiver); diff --git a/test/gc-main.m b/test/gc-main.m deleted file mode 100644 index 44f7476..0000000 --- a/test/gc-main.m +++ /dev/null @@ -1,10 +0,0 @@ -#include "test.h" - -OBJC_ROOT_CLASS -@interface Main @end -@implementation Main @end - -int main(int argc __attribute__((unused)), char **argv) -{ - succeed(basename(argv[0])); -} diff --git a/test/gc.c b/test/gc.c deleted file mode 100644 index dab0f7b..0000000 --- a/test/gc.c +++ /dev/null @@ -1 +0,0 @@ -int GC(void) { return 42; } diff --git a/test/gc.m b/test/gc.m deleted file mode 100644 index 65ba5f9..0000000 --- a/test/gc.m +++ /dev/null @@ -1,8 +0,0 @@ -#import - -OBJC_ROOT_CLASS -@interface GC @end -@implementation GC @end - -// silence "no debug symbols in executable" warning -void foo(void) { } diff --git a/test/gcenforcer-app-aso.m b/test/gcenforcer-app-aso.m deleted file mode 100644 index 8507a62..0000000 --- a/test/gcenforcer-app-aso.m +++ /dev/null @@ -1,12 +0,0 @@ -/* -fixme disabled in BATS because of gcfiles -TEST_CONFIG OS=macosx BATS=0 - -TEST_BUILD - cp $DIR/gcfiles/$C{ARCH}-aso gcenforcer-app-aso.exe -END - -TEST_RUN_OUTPUT -.*No Info\.plist file in application bundle or no NSPrincipalClass in the Info\.plist file, exiting -END -*/ diff --git a/test/gcenforcer-app-gc.m b/test/gcenforcer-app-gc.m deleted file mode 100644 index a8ff65b..0000000 --- a/test/gcenforcer-app-gc.m +++ /dev/null @@ -1,14 +0,0 @@ -/* -fixme disabled in BATS because of gcfiles -TEST_CONFIG OS=macosx BATS=0 - -TEST_BUILD - cp $DIR/gcfiles/$C{ARCH}-gc gcenforcer-app-gc.exe -END - -TEST_CRASHES -TEST_RUN_OUTPUT -objc\[\d+\]: Objective-C garbage collection is no longer supported\. -objc\[\d+\]: HALTED -END -*/ diff --git a/test/gcenforcer-app-gcaso.m b/test/gcenforcer-app-gcaso.m deleted file mode 100644 index 2094937..0000000 --- a/test/gcenforcer-app-gcaso.m +++ /dev/null @@ -1,14 +0,0 @@ -/* -fixme disabled in BATS because of gcfiles -TEST_CONFIG OS=macosx BATS=0 - -TEST_BUILD - cp $DIR/gcfiles/$C{ARCH}-gcaso gcenforcer-app-gcaso.exe -END - -TEST_CRASHES -TEST_RUN_OUTPUT -objc\[\d+\]: Objective-C garbage collection is no longer supported\. -objc\[\d+\]: HALTED -END -*/ diff --git a/test/gcenforcer-app-gcaso2.m b/test/gcenforcer-app-gcaso2.m deleted file mode 100644 index 8231993..0000000 --- a/test/gcenforcer-app-gcaso2.m +++ /dev/null @@ -1,14 +0,0 @@ -/* -fixme disabled in BATS because of gcfiles -TEST_CONFIG OS=macosx BATS=0 - -TEST_BUILD - cp $DIR/gcfiles/$C{ARCH}-gcaso2 gcenforcer-app-gcaso2.exe -END - -TEST_CRASHES -TEST_RUN_OUTPUT -objc\[\d+\]: Objective-C garbage collection is no longer supported\. -objc\[\d+\]: HALTED -END -*/ diff --git a/test/gcenforcer-app-gconly.m b/test/gcenforcer-app-gconly.m deleted file mode 100644 index 1b8e6a6..0000000 --- a/test/gcenforcer-app-gconly.m +++ /dev/null @@ -1,14 +0,0 @@ -/* -fixme disabled in BATS because of gcfiles -TEST_CONFIG OS=macosx BATS=0 - -TEST_BUILD - cp $DIR/gcfiles/$C{ARCH}-gconly gcenforcer-app-gconly.exe -END - -TEST_CRASHES -TEST_RUN_OUTPUT -objc\[\d+\]: Objective-C garbage collection is no longer supported\. -objc\[\d+\]: HALTED -END -*/ diff --git a/test/gcenforcer-app-nogc.m b/test/gcenforcer-app-nogc.m deleted file mode 100644 index d99db0f..0000000 --- a/test/gcenforcer-app-nogc.m +++ /dev/null @@ -1,12 +0,0 @@ -/* -fixme disabled in BATS because of gcfiles -TEST_CONFIG OS=macosx BATS=0 - -TEST_BUILD - cp $DIR/gcfiles/$C{ARCH}-nogc gcenforcer-app-nogc.exe -END - -TEST_RUN_OUTPUT -running -END -*/ diff --git a/test/gcenforcer-app-noobjc.m b/test/gcenforcer-app-noobjc.m deleted file mode 100644 index ad746c3..0000000 --- a/test/gcenforcer-app-noobjc.m +++ /dev/null @@ -1,12 +0,0 @@ -/* -fixme disabled in BATS because of gcfiles -TEST_CONFIG OS=macosx BATS=0 - -TEST_BUILD - cp $DIR/gcfiles/$C{ARCH}-noobjc gcenforcer-app-noobjc.exe -END - -TEST_RUN_OUTPUT - -END -*/ diff --git a/test/gcenforcer-dylib-nogc.m b/test/gcenforcer-dylib-nogc.m deleted file mode 100644 index b10fbe1..0000000 --- a/test/gcenforcer-dylib-nogc.m +++ /dev/null @@ -1,11 +0,0 @@ -// gc-off app loading gc-off dylib: should work - -/* -fixme disabled in BATS because of gcfiles -TEST_CONFIG OS=macosx BATS=0 - -TEST_BUILD - cp $DIR/gcfiles/libnogc.dylib . - $C{COMPILE} $DIR/gc-main.m -x none libnogc.dylib -o gcenforcer-dylib-nogc.exe -END -*/ diff --git a/test/gcenforcer-dylib-noobjc.m b/test/gcenforcer-dylib-noobjc.m deleted file mode 100644 index a06fa54..0000000 --- a/test/gcenforcer-dylib-noobjc.m +++ /dev/null @@ -1,9 +0,0 @@ -/* -fixme disabled in BATS because of gcfiles -TEST_CONFIG OS=macosx BATS=0 - -TEST_BUILD - cp $DIR/gcfiles/libnoobjc.dylib . - $C{COMPILE} $DIR/gc-main.m -x none libnoobjc.dylib -o gcenforcer-dylib-noobjc.exe -END -*/ diff --git a/test/gcenforcer-dylib-requiresgc.m b/test/gcenforcer-dylib-requiresgc.m deleted file mode 100644 index 69a4d25..0000000 --- a/test/gcenforcer-dylib-requiresgc.m +++ /dev/null @@ -1,22 +0,0 @@ -// gc-off app loading gc-required dylib: should crash -// linker sees librequiresgc.fake.dylib, runtime uses librequiresgc.dylib - -/* -fixme disabled in BATS because of gcfiles -TEST_CONFIG OS=macosx BATS=0 -TEST_CRASHES - -TEST_RUN_OUTPUT -dyld: Library not loaded: librequiresgc\.dylib - Referenced from: .*gcenforcer-dylib-requiresgc.exe - Reason: no suitable image found\. Did find: - (.*librequiresgc\.dylib: cannot load '.*librequiresgc\.dylib' because Objective-C garbage collection is not supported(\n)?)+ - librequiresgc.dylib: cannot load 'librequiresgc\.dylib' because Objective-C garbage collection is not supported( - .*librequiresgc\.dylib: cannot load '.*librequiresgc\.dylib' because Objective-C garbage collection is not supported(\n)?)* -END - -TEST_BUILD - cp $DIR/gcfiles/librequiresgc.dylib . - $C{COMPILE} $DIR/gc-main.m -x none $DIR/gcfiles/librequiresgc.fake.dylib -o gcenforcer-dylib-requiresgc.exe -END -*/ diff --git a/test/gcenforcer-dylib-supportsgc.m b/test/gcenforcer-dylib-supportsgc.m deleted file mode 100644 index d8ce9e3..0000000 --- a/test/gcenforcer-dylib-supportsgc.m +++ /dev/null @@ -1,9 +0,0 @@ -/* -fixme disabled in BATS because of gcfiles -TEST_CONFIG OS=macosx BATS=0 - -TEST_BUILD - cp $DIR/gcfiles/libsupportsgc.dylib . - $C{COMPILE} $DIR/gc-main.m -x none libsupportsgc.dylib -o gcenforcer-dylib-supportsgc.exe -END -*/ diff --git a/test/gcenforcer-preflight.m b/test/gcenforcer-preflight.m deleted file mode 100644 index 828cc33..0000000 --- a/test/gcenforcer-preflight.m +++ /dev/null @@ -1,88 +0,0 @@ -#pragma clang diagnostic ignored "-Wcomment" -/* -fixme disabled in BATS because of gcfiles -TEST_CONFIG OS=macosx BATS=0 - -TEST_BUILD - cp $DIR/gcfiles/* . - $C{COMPILE} $DIR/gcenforcer-preflight.m -o gcenforcer-preflight.exe -END -*/ - -#include "test.h" -#include - -void check(int expected, const char *name) -{ - int fd = open(name, O_RDONLY); - testassert(fd >= 0); - - int result = objc_appRequiresGC(fd); - - close(fd); - testprintf("want %2d got %2d for %s\n", expected, result, name); - if (result != expected) { - fail("want %2d got %2d for %s\n", expected, result, name); - } - testassert(result == expected); -} - -int main() -{ - int i; - for (i = 0; i < 1000; i++) { - // dlopen_preflight - - testassert(dlopen_preflight("libsupportsgc.dylib")); - testassert(dlopen_preflight("libnoobjc.dylib")); - testassert(! dlopen_preflight("librequiresgc.dylib")); - testassert(dlopen_preflight("libnogc.dylib")); - - // objc_appRequiresGC - - // noobjc: no ObjC content - // nogc: ordinary not GC - // aso: trivial AppleScriptObjC wrapper that can run without GC - // gc: -fobjc-gc - // gconly: -fobjc-gc-only - // gcaso: non-trivial AppleScriptObjC with too many classrefs - // gcaso2: non-trivial AppleScriptObjC with too many class impls - - check(0, "x86_64-noobjc"); - check(0, "x86_64-nogc"); - check(0, "x86_64-aso"); - check(1, "x86_64-gc"); - check(1, "x86_64-gconly"); - check(1, "x86_64-gcaso"); - check(1, "x86_64-gcaso2"); - - check(0, "i386-noobjc"); - check(0, "i386-nogc"); - check(0, "i386-aso"); - check(1, "i386-gc"); - check(1, "i386-gconly"); - check(1, "i386-gcaso"); - check(1, "i386-gcaso2"); - - // fat files - check(0, "i386-aso--x86_64-aso"); - check(0, "i386-nogc--x86_64-nogc"); - check(1, "i386-gc--x86_64-gc"); - check(1, "i386-gc--x86_64-nogc"); - check(1, "i386-nogc--x86_64-gc"); - - // broken files - check(-1, "x86_64-broken"); - check(-1, "i386-broken"); - check(-1, "i386-broken--x86_64-gc"); - check(-1, "i386-broken--x86_64-nogc"); - check(-1, "i386-gc--x86_64-broken"); - check(-1, "i386-nogc--x86_64-broken"); - - // evil files - // evil1: claims to have 4 billion load commands of size 0 - check(-1, "evil1"); - } - - succeed(__FILE__); -} diff --git a/test/gcfiles/evil1 b/test/gcfiles/evil1 deleted file mode 100644 index 88bd337c1adf4cf57147abe815ff7740f598744d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 441 zcmZXQOGuOf5XV17T)Rl1hl0HfB=cB&uz1j!F8KfjK~Z5*I+;i)Dq0eR1%YmfDEQbt zb@1dt6!awc_z+S|4_ZDJQ8Z8qIxN*eMUA!3tIlD5fBwu|ew$bEgwJu3GK;Sdk{!o+ zxIUL~J#Kov2j}ta@}_sP$u%NYys55Ha#L4_UVkS1LKK0REeIgxTLE!h4DA%VNdsG| z9asE@p_alc@U8QLGm08S3A);4gr>2vOz5q$Qqhut{QH4M!I$>SK4G-)yi%$PW~Gn_ zF()&k3hyNSkLpy`&+Wqj2JYJDqvkVh+1(N0ZR7(DKpV+&qn(F&Y=4nTsh-qA_V?p@ zi6#@7@Q{B`lt-SzPLSpLxrMYkz`DS~;+rV>QhBpML6RVa;=_82latp(xQkG2f`lWS%_}U=k@dMlhCc-m_7S?$D`cy l7;nX(76WE4j&8^}P0t*`S_&9>@?Y?WAVon{CHMjS2}H#c4}C#MeL_6-0i;H15by&K!kn`ojlHqVGcZ@VJ2UsroVjy$ zHkw`EJAeH4*CRrRhoEE7C!t9gJ`veO0`)hbQm*FTF1=Sezk)_26H(=G$#I|IB4wp? zsnX}?m}neI&O06|dr9dTTOMnH#K%uQCqy2u$)iz^uuO{W$RefNZB|>BbiGa$#yCa= z7-`rmz+jw_q{ah`hhYE)2Fr$FwGGE_53XY!7+KiYfkDn5NQl98_D0Lc9E{@@FwVe!D;`HUCkw9eTd0($@)eR7NfwppeuVcT z+5_w&r40@PD%+kjbbN{(F}6Qu@)xzGwDeIJsjLvhNX0O?FOpCS<3EFolq-dIioPkh zyov(jL^dcnhVe(^z`x~6$JlUTgbW++_st%}%YN98MD<0=rs2B!rV--AF_MUZ@h7t* zF+a#4s? zI)TBGJhBj0Kj*%mfo%dR zjy&q?iKtE6*FwC6wQ^ki|BL_s0(Sa>y?qR|gwV=e*E5>hl38_hr>mWI^rmsG<$UOB z#a6AQ=QbjPcF}NLvt_$lK5+SQHwG}X|Ad)uw=JVwbIi84QoT|fjvnje&okdQPa^Rh z=tk@jXW%VZMy`+pe`Yn{^itm#AwI%m@ye+6V%l@2H!P&07WjZi9@htO?;)1C#0VZw zw>oxR_dwln>;r#@-@jHN?!h{^)>idOEtij0D#VNE!9B5seTHDf%VC@QSBYXkF`yVw z3@8Q^1OFQaF6``Gy#8zEJ{Np>_tT$oJlMUlhwIMLOmZhVbAK9+-5dLT7F5Oc+nLKB z>~m0WmTxw_S)S<|d^zaN3S4jUyeIa5>GgVzXIT3QwdHaFCmhQVP2IG^s@2l#gXcJ- z=9yQGVsQBL-)MM#=J}V>U-Ep)^DECm)C=MJCF^`YO5;2<0ga1=cgd?zC5i#XfMP%~ zpcqgLC^Ql=&`_7U)PHTa?VKludWDY}j?!C<$axH>9y;)HZjxKLO3nhJ=xtB*1`1*i9=Gh`c0Xe5aydKo)7Nwx>f08H%b*3~t$ouvO x2(dVr2Yxt$oF8h<(p^{DNZG}j!pw^ys~cR`Az~c8?!2jKCl_9uKRJ(J{|1kHi{$_S diff --git a/test/gcfiles/i386-aso--x86_64-aso b/test/gcfiles/i386-aso--x86_64-aso deleted file mode 100755 index 93b331649cdfb041bf5fa300f34f51b8b1d3aa67..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 29060 zcmeHPU2I&%6`mU$Sb+r&S*QzvZj1wI>UyC{qKF^KYh!SNNgS_}Mo=(|<_wL#z z?F%XA$a81T{CxM!cW14+b7#&c-+b_)Qfd>zH3(NJ)r07s30%58+9)gH4 zAPfit!hkR!3JkBv?q3U!Xl1ZxP}y^M?2Q=`YGLY3IYwL{GNZnlB7v~KwG4WM;CnNUpEP~b#W~NAr!q#j6_N)VkBZ192Z>(6xu(Ai`IvR4~&?s;Bq%2v{R`dW*ge?+JXO4UDy1Rb!0+1 zwKsKBtM;-U#v@UUXg%xK>$y|D#ff8d!3Nsjm0B00lAkO2tIsgnk8WV_hTSm?WQFV~ z&e94y4BGm#&DybT9Srgq{FcY}AADr=!Ts)cIi%DC3xUCyJW_Sihark0Q=UYWgwpE@ z5f4CT`7dzUhM3Qu<OdwE*?*i+)XU4L)oU=Ib{Oggy`|-D&o~zf= zrQ&otzfj6UzZHQtBnSHc@OOXj!kJwcKKS(C1HbM^UJru0?phOeIh}d$DYX-QWxM$Q z7xVuD`78^@_RUCnDxF=ZH~iW3cyYRxt1YA-s^w<=rz^Ez*3%=EnM!W(q{~Pj^=tKF zrCd*Ef+AygA%dCO2s2Zymi+8YtypawntpPmGkUC&xzFyC&<$SII>N;bu zmRtOAUzIwK+-P4b(@)L}W?WB|x)U`xCgw5DV2t{))j57egaKhd7!U@80b$^K!@!Z| zg`=lGPhMn)k1ze|FPI)Iy?O!H=6HWsv#bAN9}1RUy%fqpQk_1RJpRNbHd@)iH=C6m zoYNQhaS=BkR&^o2p)Q)8VZ{+(;X2f}h6zb8Lb?TvN-cmPiii zgIR7(D9)HonU7-A0t>ot9@5-dsb}k_vVJ*_Jc@@M)CKL&`!jP5AN%QZg!+NmLi^h7 zO65`IeuUxC+-B~*yurij1OB+r7CRKM=Txbm1!)$m(I9P>B(g!8TN+yXzD)zAhNAnx z+btM0rp}ad^|~%&%P#t4cV2YpU9?|q#ME;6+P&%Yp1t?%-m{y=iU)k$tjeG=Li}Mb|qs0sTf?w0}|3UR!h77vjT>S6ovn{`E|+~S!!wc@=L8myKytyLg_u>EE-AtscFABsH)U6y#nIbiB zV{*o^d#^rQ>PML&Y6Bk!@&AjwPxQ?H|L)^}b`rOMK+b~kmO^S>-wDy9ZVe7ung#=G-(!0{|13$_?&;v zyUI(l5aK;Esl53fuc!N(#4^Fxkb-@0V=?Es?mhGQQ2rWCC0iH}2801&Ko}4PgaKhd z7!U@80bxKG5C*Y}j6n#$JVo}qE5FsN_nU#Zjo59WYIr>K%$Fw(;W+TU9?hKfz$;HR8^RBX8f9oUHSvcz3QEJ?|bi_ zx$n$4dNXr-^TD70JfoD_2OWZrK=1EY>T_jfGO*u+YCSi3y8Ll@;T#+z!^RkF8TSAyf{^?oTYW-tVgsG>5sQwRq8Z`tiwuW%_O#2wP{?f!;MC@>FF?P zSJByzQ3pmI{dHh4Pb0bg3C00*00Y6g?szRHi2c3mh=5_CKLQ3hdn7#!`dY7qQQ-P( zy}|xGt^ngDj9mdnTGDu0xBZK*U(>F?+Qg)OjH|#ngZ``id87cj#$TXXPfspQrUoq7 z=6dw5g}DbfOKTq-CRDbf;IZ*JPDE@vXYyx!t-Z>VFmjes#K`qw@V+qDm-(N;LF;o< z@1KNYa8^TfLLL_%&$V{*At#Aw#qp0j%m_pT!c44#l&7~bGgQhB%yX3@J12G&vl#{F)$Td@8cgFGg`<(ady zAC#xhn%`xqcP3zzFo`^Ju90I(ZDL3%97fTMqAi#~v=i{h(9WTqK+837oI$)7ZlPU9 zdk{JYO$+Bz%{5Dp8T)z<$M~i_1mhJbf6I0|3~bM>+O-X@2LEL!bGT+5KlIb`iAz7< z^lu-S{oxztISf??pN;h`lVuoPe-5rdj=Tr8sFKlsb+XT>m9-iW8_edwz0(;fM$W!hkR!3b*(i&=IT0`?AB^J^qMetSBpQ8wV zK2X~#$c>bA3q|eGD!u#*CCw63-3Zqf9lwS?NJgWEdPq0w+F5s2+L41g`U)YAHY%>K z#`#iVpZfj?8Xo8#&fv`h{=FF6sAIr82-Y^B)*&uMemAjM#-OGXJAO4Wld@UQ$_f1c m1dx*PEmTVn=Az?VigEUFk%jhAx~m@*UhG|$S@QrZIMKgpur(k6 diff --git a/test/gcfiles/i386-broken--x86_64-gc b/test/gcfiles/i386-broken--x86_64-gc deleted file mode 100755 index b22be31828f6f0905f149b02e06526076f2099e8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 29056 zcmeHPTWnNC7@jRfR;jQcF*Pb4u;mugg`kEPk}XhJv;u9x5EC7GIb~P3+h%tgis7LN z!MIjTP0+*#6CO12ff$~AF!7Q|BKUw0UV;e^Jb)<#6JvZJN~qsAXJ&i$Y+Jx5!2eI@ z{PWM{J2T&$)0~-|f4;x@#aBwHI)r%$bCgOztxw_75JTFiN!tx`8KSBIK|~l32801& zKo}4PgaKhd7!U@80bxKG5C-ls2G0Hc`%OFXH;Vv^(f&W?Ds@zOG0gwfMg*;Qw{CBH zxvg_osB>HXX-yX+vy}eH^6uR3BW*Qo$+%+e!3gDPW|L zek$6I5Fq>b4T9ENTlchvGCaiHc-XPP;!mYNTIa!GK;=-WFm3%f;ZQf+X7cB3t#jS8 zVZ=S9h!Kxq@VRi~%l4;n(R$bB=eNLcvJMf{Q(mB1hwXRuz<;i&3x0nQiIC~)o3h?i zdsz<8BTVCM-3NS&6UC@U8;DWw&5AMDH=OgQ#<02{abWO<xc4$;?*xn#3Xw;ab+TNkQDiBF%Mz$1(2Xt= zdM)fG=-tq3q1gxKvl5?$i_rU_=OgS!2n)+HxG@Xox%e52<7}rFAo2tP-{s_Rv5?GV zdy;*Ja(%ELM_?OHdn*=vxqtoPug~Q#J+SlRciGMaf|~!332U6qG{`D6oAvnrB`mzh zegx*Ry-Hm|lh?+QU5AP#e<0bO?J0B@4kfo0x(ED&gN4_N$!&wf`M&N_b}--4?^2U5 z`Gq1UrYHJ4r3`0a15C((+VL%uV2801&KK8=n4ifg%{ zsl2DDzT9?WVkPp%Uc7Pay%9vpOPb1U*C!rEbp6?K+h64~M@~1s^6mI!Gr#aBn;US= zaKbRz%&E(GMnSGzot&J^@RRn+MZ|V?ZN}GQ&R4y;?qX3DhVyw$B=OA(;kP-zdnsYf z?^1rZ@;j1&^IHnzoEJ5qX2R5FYORc7F8MZsh%g`w2m``^Fdz&F1HynXAPfit!hkR! z4BUSV@OS8s5cs$?B?EIkqigaKhd7!U@80bxKG5C((+VL%uV2L5jh zELtA#d|GvG?pVC+>e8XQq1aGe$Kn(>(gvI0d$9i;o8UVNYFriKi&J_Co7zkLv>F~s zX>Kvq1H}Vfe!dTBkeJ3E>N-7;(f)|vJ6!UykG`7_%Lcl$dDYBCg*ocPQYc<%E{tL2 z0e_F0IQB7M4T5_cu-CzCMSd=ba~p%&8pO@gM0+UC{j4lNe*#F#*&cRF*S1BQwzQbm zn@tuvkFC49vhkw2U#`zJtOWOxt1uu82m``^Fdz&F1HynXAPfit!hkSv*E8@#;Qt@P z+O~0W;kXO`f75pQsHYL8Jp;g$u#2k7fexz*VLIg8g6 z6j!v(VurQhm^jZ_@xY&C~Ei{w|MUUw8K(`ZSZD4m*4}7_n-x#9hO?W+0Q6A zVe$A3QD{dof}G&0wV%<(W!l-*<1u0J=qpX3Z5D_Bk;J1@N4JXn#G67q=B? zhuhuPIr+(d){VL%uV2801&Kp2>ffxYSS<@AwjiS*c9o`;tn zd-hKhPnW-&;1@>v*rpC+wp~A^Ciu-}o&M8DPA81l|4F1xHh+ezQtii{d>^W{YqI$$ zwDtY}t>XKC0kNpdKkixO=MU4^{Dunq`*U7OnZe&bwj{__bKdAwv5`b9(J(JQPVk5v zw)3kut!6gw<>r}@;$jK|!hkR!3V&<(KA@AFcKSQHw%d>-+;zxeFIqV4*UaH+&BtAa}C1y y2|#n%qT(xH*D1pO0?1Yyf^wCg5L*MAFh+LZ3Y?0~-%?rlPW%UEVC;^Z2mS?nVR-8R diff --git a/test/gcfiles/i386-broken--x86_64-nogc b/test/gcfiles/i386-broken--x86_64-nogc deleted file mode 100755 index a401acce90c1eea84e67f306c0b47ef70137b3ff..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 29056 zcmeHPU2IfE6rP0wD^%E4Vro>pV2fZ#7yJtkl5By(q7`ThhM36E(pz?QyKQ#2p%@-& z2*$NyYJ$F*@Zf_F#PDWf;!h%ppb0*ipz(nR&_XaV#s{K=`h9a}ws-Hg1$+WLC!Krd z%+GgczPY!#GdpL#yYcy#N~t*r3lQcim4I58!lgcjv{93`6XtS6RXu`;Fdz&F1HynX zAPfit!hkR!3$rN*yf1EL0!wLK@<&RH1rn|AJ*L!FWO+-Js&j>? z>(r<-wJshS=pD@IVrjS+iBXIUFt~`30S4RYBt9>|n2!Wtpt8>Rxgoz`?CO4$fZ-v% z1Pm^mRK#M?)_Q-jRLJHJ*vx1+9q z+Kvz)`}j42)|=aQwS_V~#NBwFS%Z-c);8 z4$mV|jc7gK7mGaye2Wvss6!iwQRmHyG1xbp^QXqJx*u_1@P_2V@dlF;YDac}t+0}Kel&}*RC2j;U9pM?w1`=A#g>_i9)%QCnz3+K7`8H?j=rxzpg2m;^bC% zeTQ;=updTX8%}#G7k{yD-Qlm!OdJ2b&4`j!Qp&gPboW?Z|!%f$(Q{? z5h=ywrcgf6i2n?N!;i(z0zWp@FZ3LeTgaKhd7!U@80bxMxE?+uwwP}J(>=}FeM|?hwjsJvexxTr) ztGTY+etqHzh?JK$m)oyR+>hwGv*q@`%4d$8ZhG~b@yQl`;ZL^Ie|@Zxcthpp*cgbp*fw6DQ=_1{e++Zy zQBJBG5YEGK7ykdI?etMkBTRb+fGJ@o={7!-BF+eI5cOeac{9o9=7X?UoBI+tr>Cae z^o#)bJrHk$DXnHbD+p`Dg>rA=X*#S9=*BlgA zw9R6Mwc(gM>Y~)zE=BmI43{Ws_#3x)?*6pHQiW~sWP~J(}Gv@-RA?US8Bxii~K7q|K^_DBO5Bk5JZxBa)_wE9wB6J4x z5cq#5dcFaYDrX#g|K{H3n>ReQ^|OWlw9Nhax0U?AVu-o}&p23*wvjt>cq?ZdpqS}@ zxZbUtbHI8l5y-;{FBsR=*5=f1jP$V$oyKgxc1lg~o6S1?r;nUY7_a}6NSkc=6j!A>jy?JTR9p9C%TZ|S z`~Pdj_x~5fqAvfqXO*8nOk?vKD(vsic`0QEfB)FhAY09Sqf5m`60t=6g7`SWBXZcz zuimoSWxIK%vqcL7!hkR!3cPNJN^SRFm_wc1OEbwId#UXeI(dH{abMt| z^-}55QfizrCJrsni&MqQSz1T#dPFOc{&@RUrOsl=KCD#6Ok$f+o5s~T+-Ovqbsa|S z3Of5SYQV^$zXlBEX(YQp!8m{pU?5o6+_zSb*Y6nOqx zZ?Hd)YruF3W7mL@mNcH$ZU3_8SGDJ_HZiFm<2o?Tq5pb+9w|Vs@h7O()00b+sR0|d zxgNc1VeSFW(%J`y36-rVcx-%@6A_!vnfw`F>w5V~7+G5>Vr2U;cwd<7%lyybp!J2R zcTdAH@+J(#DLZKy!~9Jg_^*dLa92ZgLLL_%&$V~+@WU$T6ihF%*xFq8WvgH-l&=;E$o5Lpy<%Yv4G8crV;RyNvcA zbPk#p&ZU}bmL4Ns5w9$_N(Q{Yx;#%)9YMxgAhHTb2>E+G~&MnHTafw3;5RVy0Et7RE4Pm z*639RV~`UN_Qo_TAFmW9O@va%F@x*B0#0!Pb)x4d7Z{F+Fdz&F1HynXAPfit!hrgq z^V`+m^E+hX($;6cVEeSS{VR^0+*oI6EYm4H*m)geTkk*k^6PaNozbyQ>F=HAV4k|w zDgD*CdG$vAqo23CR2ZfWn`fi=50h23M_RV(#! z7^J7%>(|u7~800z&i-mHlWraE=7Jjv028TrV~4UH8GR2Si@@FrH(2uhV{SN2&eV#*6nRC zw{`9cHICZ^GYHGQgNN38+V<@UWnvju4nE(DR~g4#T9^Eh5<`JR%X?2LwH-;`LZ#|m zCi?Yi)Tvq*4-NDT=5(<%+=Iv{Mg|z%#K-`H^>h-S8(=(u2wwNS@fgyM10yV9JgtZG2ebJ;oz3?TB2yIO6fn|= zKNYP<2#{_322Sg(t$SKS2_C|(KWtxM^QY1tt@Ge8pmL~Gn6`YJa3~wDGx>A6*17K4 zFyfw4#E3^QI4)fOvi@m2wBEJ(`7KbKtV00hlo$9c!}_~&;6GQ?1;4+DNXT^MO92k5dxp2HerG)B{9bhf& zFsNxuBQVHgqc<}Kc?`bg_8mK4XxqBOeU~XD@J+x-ArpCIUMI_y8by*xUjlClltz~b ze=YPT_`Bh+h0iuHorO3SF2dgre?Hu9xUeuUo$Ir3nhT$?I8Jx|0tB9bY}XFZZuM{Pnr~r3ZF?{4VR6fK&4yGH#9283$Q~YO@^wzl4SF z*bm1v)>o-ZsPfuavg=T>3 z$iem>2B&y|T5a{9gQ18B1HynXAPfit!hkR!45+>3%SWy?PLPRxV{iS8>C@QwuXvUl zn#y~c>dS36CRQSC?8O_$-Wx%nyrikzc75Vu1lOM}xBXQg42PhEv)r7ZKXowHdR=oUeLw-Nm9R4CnJ$NMgK zyp;1+&Lio#zNIkCbx{LKCQNC@*2*Z>l5fL_2m``^Fdz&F1HynXAPfit!hkR!3XHaE*+{HiVf9uEKcztZEy&_2j|am2)?7B##JG{IHiYhsJ+xrt>KZB z<`GjpP(0A(=lc)`iD{gnuG0ex6@M!CAjl>OpwXOfF7LPW%6sHFo?5^$I1GIIf-0JRtoW*Mj zvMcIlGsEgI%pG+>`r2+q_@xZDC~Ei{w|MUT)WcGRZSZElm*4}7_n-x#9+q0X+3zSg zVevSIDAc1EK}zt{+RtcXGVS#0{+O_Mw3Vh%HycBNTJz+%JC6ZTbN>OxSJk+4<1aMs zLiaE6gSwyDFYZP!n!3C`K9(SQ2L>4dTRKZ&@>=Fjj{s{PoL@55{D znruD_-`f8FRS z6Fee^?fU9X>ob|}a_h`Uc2R`^VL%uV2801&Ko}4PgaKhd7!U@80b$@CU|`YmL}x4Z zFcziE1?zfCduvyhZkjp@-%(gMK8-=Z=oxHQ7>SLunT14^Z9wC+wgJp|2lfHXZVUzB za}UDU3Bc#JMa5RY?o)*A1(2*Z1or;5t#}GGa;yR!%Lr~Sji3kJ2fG{8o z2m``^Fdz&F1HynXAPfit!oWSoz`4JFziAi#W)olu>i@?)rH(2uhV{SN2&Z*N>-M&n z+q!mz8pmyd8HDBD!9(laZF_fzGO>&+2cPf7tBhkVtxNt$iJ?HE<-Mnr+Kwb|ky3Ro z6a6|h>Qt?ZhX#5FbGld>?nPu2BLfU>Vq}29dOC^E3osr)1TauoXZ+ldUod)gJ4(Rt z5MKfYH%=;IF{o?3zgQ|{^9O8Vv>wNS@fgyM10yV9JgtZG2ebJ;oz3?TB2yIO6fn|= zKNYP<2#{_322Sg(t-D)82_C|(KWtxM^QY1tt@Ge8pmL~Gn6`YJa3~wDGx>A6*14Y9 zFyfw4#E3^QI4)fOvi@m2wBEJ(`7KbKtV00hlo$9c!}_~&;6GQ?1;4+DNXT^MO92k5dxp2HerG)B{9bhf& zFsNxuBQVHgqc<}Kc?`bg_8mK4XxqBOeU~XD@J+x-ArpCIUMI_y8by*xZ-CbTrO_qA zUkkkneh2)u@Yx2Yvk=F^Mfm&RFM#WS3k&noxjqZ0x$qf_<8Vj-E! z_9pud<@%sM4#zs2_Es$Xa^L#HU!TigdSK_r@3Ni=IJMv*|nm7-^C_h z@(V@86q8#*=|Cg?GjI++7CQ_4*i^gFw%}DQn_JMvY;SED)CmaHWd?g++uzdaN+`7g zIoSTg;1n-VtF0b%FccACKo}4PgaKhd7!U@80kx-m`N*}#2{N&F?5&?MeHt7870+^g zQ+an&UAgVX#7d-%y?Eo;dm{*x8=A^(*C!rEaQ)eG+h64~M@~1s^6mI!GiUgd&GmR@ zxL}xU=F(+6qaatVPEJl{IHkRE5uu&kn=yOL`D%Zzr&v^l;d~woNz7RxoSSprO9}g& zmvY|9c_bazw-koCE~-b#gelF~S{cP!@@+T~VL%uV2801&Ko}4PgaKhd7!U@80bxKG zxc?a7@6aE?@pnJ|Hgqzy2N3+$b^obe#5B%O&(Q-J?T`5Thf6-r(f1Hy*+5S=ubR23Fjt*e3Xcz}i(=S$ zz~7@LjB^awgW%Z)oOLiqk)I2~JjS5524S-`(H;u(JS#KMo&b_^x`)%!wRO>^E-j|^ z=8%QXS?%X?*K44>>}NUXYz*qPrf@_G0mELP{a1g`1n zQ*L=j0PG%!x5?yIv)&aD;L+CK8i^bHYFqzTEgo%jDNYYG*j?MZ2Waa|xz*hPIg8g6 zWLMP9W`@;am^jvsfVQs+u+T9FTn>E??DSfJuJ0&v)@s0 z!s2lZQK&~Tf|TH?wV%<(WZLP~{V`$jXe&*jZZ?JjwdTojcOC;F-{KYhTyTd%6mL}ok9xR%_+}9^B^J+Ywh=Lkw=RN5J&XsWD!i%xOOg8wyhy)7 zr;M7;p5=Z6FStX2R$3|KcarwZy#Q(m{yIdGGmgDap|elDWlHUX|6lJnh@-swb^%in z8iRRo{J#^u-vCjSI}Sei@xtoQf7-C}!!`f>a&mBO7yqvqqVB>w4mP5057kfG{8o2m``^Fdz)f#=xF*`EvTmwM2Ss9`D0T zk3IV*vZu@6O>l;hKDMdTsBPCzsR_>6tkHk^$mxW!`ag-d$>z`SRI2^hlkdZ8?VfBt z3g6oP|5ma6zkpEGvh#;(bk3o|_WoR#Qf9FCk2M6zYVI3dDmIdcCFfG{8o2m``^Fdz&F1HynXAPfit!hkSv4=}KJd7`Todl-vT z=7M#-rM;&L*+oEDCVD~A)_5w&&8-je5pAcIEoiIjr;0c_H&fij*`A+NyW?<}&j0gS&1-Exw diff --git a/test/gcfiles/i386-gc--x86_64-nogc b/test/gcfiles/i386-gc--x86_64-nogc deleted file mode 100755 index 3a237e19cb2e19990cce1a370e23ab68f735fcb2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 29056 zcmeHPTWnNC7@ma!D^%DbF*Pb4uthMW3*N$mBwL`cXocP|#6*Uc9@y3Gw%Of=VtA+_ z7}tua3HoNjgAYCs!<&hTmqZdl6MQm3;{y+%g>71E= zF5j8?W==CRJO6xlhbKo}4PgaKhd z7!U@80bxKG5C((+VL%wT%NRKK_wP6C!ryEHEJgkQn5WcH<;Af6R~z88?r7cC_DWmV z&QRmHO)!J7+`D*a-P5+aCzOe0TsioBFJ5IFb7@`jM@kF@5-smNrqnhhc}tY4bD8MZ zsZpnDT|6|>N)$J$&!$W)t z7~D9ih{d3;^}b@Mkj)>kiP3r-2gXB4I}VJngz>Z<&L7O?`*k+Ie-N3X7^i@dM*OL0 zJwkwN00M{XTykl zN)aO-!Qi-X{mc5N@z8qbrWZCtak35plv7^dvkdF+%7On}Q5XFEMMOfTD{u08Q}ty& zj7Opx(R#oy7JCo)7AJ~PhdL0W&YKluuzxt`PxWDSJL16L3(1A!4Jsv6kL&^JTIb19xbJ@OR z|Djwz^oQYChtuAw#b4}QfB36&`HS;+eEc5knSfJ^78>`Y(-{X@jcT(T|G$KV@7N2+ zG}c$Ci>UJ2ShD+2vE&aV+p~Rz-ol~e)b0{5X#D50P;m2ZUfghV{7upuQs%29P+L-ODErU7%p}Ndq-)s9?T3rdHRv`!5 ze;AzN1!|4egARrwA`A!v!hkR!3cu1`FHw6T}2AA5fUf%3AZa@)0u`w?7!w%qnt`OJ~ijjw()KH1C}{$z7Ko*6C} zCY!l*8P6!lv@SxUzg`&QNSEAGkkUM2 zst1Y(y8V1V;vg}NGt_hRKt}r`{=VUok8||Bgjg}qo6W0cZYs=GCziwGgX)qPb{_Ee zpb6s~1NI!aUE)474YJq@3>Iv~+DtaYpchs0};wn?*hkAB4r~JeR;VJ$=ee z?+Ae11MxPR+-lal0s=hR`dcG$gI{gyzsKUyMwjCBK!e@2y?cPR&Xk+o9gwqlO+j`= z-E3x99frB1E=XV7tq8xA;TA;=f8!RBS0 zj`J;E(a!~UC`9pANAReJ>xXX^F;imk%w`)Aqj>8gc+|sqK&rx<`o9#p&%lfHD|E`J z>FinVH}HZx6lkTDLVhP{&)f^3hTyM5Bst^Q`vf}s)LWs{Uikm@euFs5yKfgT6`?Vh z2gmLeTgaKhd7!U@8f!P??l`daOAGw-HkImzKcbKQ|{ zLT5aVE5LXLzAM0pN)%7ZmUXFSRbf^O z&m(*mY7MZLlomJ)sI)z2-|;DS#Mo%eq%uMfBNfBox=_!T@ect|%Cl3i zOrxS?0tUwU4+15}Fn*R4DI2C&l;wKSb%T2MctUWzikDRgQGLNPRKvr1C&mN-h%u4r zjp0hiT=t{rp2rdZIM<~Z2L3N1aWqWVEv}d$PMnKMh*9Z@5j@N=j4nQ_z~Bw5F$^?C z<7ie|b@QL~*vZFgh_Tueqt@U|!X@1m#|ALCCN_Fta7_g7rPH%>ujWtAs&`Bl4n_-% zYv{zaz`jb332_-N-#>z41eIasM0o=BF_eoa`G3WALI0PiGp3PAA+}K-hMt2)g=>$> z_t`;`#CZOH#N~c8Fdn{_jF#&d^;*fOtko;1KMZ9IKU{eGlixm_nB3}Le{cAU#_NnT z4Hbv)^_A}uzs@$uQ7=v zlbjLjx>z31smO$Q0zLTrKA#BQ2TzA}u01W90nLDBKr^5j&oWw zmp575+{hmhVsm>I*Y?0zdtofu&fngB628qdx37NChS44xYv=#iy$|N(^>+UE_IK;w z4!`-$_TKSSMu@%R1GrX`zA13s=H5u$`gw0}ugY!Tt)F1c6|nCy%bv)udQK6Wn7P6f zwp?{n1iwF=mSy3WBsO}%ewKShO0?u&j(a`sJE;79OQFqu=>TBU(1XOrZ^?I{S~LTi z0nLDBKr^5j&Gy|Fe&46Y=GoTsx|1rSt(A2BpZ-1U6^!(%iXS6?06z~zHOFj(o zK0^6fXtNXh^Sdd(&84AV!N+fpl;C?*VC$=9Kr^5j&Gy|Fe&46Y=GoTsJ3}^;4 z1OFZaM~h@ z+DLBqmRTEkFe{nPC>!od!L%xVj;!dD;*oK`*-v7S7V=4Dkl-){XQL|3RkPgkOyt=Y z3H@TDShK`SJOt<$+oQPbWRWm28k`FZ-V>Y~JOuY(0w)Lfd(O9E;tb(=*f?A86CBP2 zwtp zxELscaVkuqxD$kK1#uyCSV3hn#osx#ym9 z&-uL%?t3}E{`JQ{PYNLpfycm;;M-3K@tKH6BIv&jmU21$ZsxJkJ^6S_0l((W1# zQm$n#u7&ip(ROIL?%^(TmXyA|;jAH?htb$w8lvf%k{#ZmDp zx!1=H_?X7{4fqIC*q@Y5=SsyX%8FB}V^V)Ex8UP6#&7k<5pu{JZ-b>gm0nAS6mj(R z`RJYteGPDylnyWqpltZ=Q2Qt+!ZsW;@zb)D)!Zl_(YO%w5$*HA>q4Jj#(x?IDVOHn zn}?w3b#!1S;z3Ir#;@&wf7O$&UGgvp8fLHJj~~R#dAJ_wRgsi6+w*ekcGpk8k0@f` zddx%TOv64O^KLyOzvHyeMy%|n=i4>wTqW=3+)Zo2 z&DHEHb@vm`TBtXjV$QGBon%RmS{H2B!-!|ihxG8Ko=0d8{Kp|h#0dZCMx$zH3vQ+1 zFXb=K4_hQ{^-WUiyct?261jssu#UzL7zQaN=aRs(k{=e^E^NLU72AB>wMXZlwtD9v zCSnkd4HyR)gkr1bFBg(&9hnd>V+POXr?^HClz6o(2Nx^^Lk0nZfI+|@U=T0}7zBnO zaK5#B;p$$ZP1VKi&mUqxvAy#*j;&atwU!ufW%k<(&~3lJzkPk5+V7QiGSTv>eb~mX zk=ie>e}hg7564?3Mr znMU`2LvJxVhuve<7KLig^Tc8nTRgkq3%BVwc%#JTHQ1wb|4RzDYJZCTEB8`lzUPwI z=lf9%`@M;t9X_?#_~Kpi3$P)BfI+|@U=T0}7z7Lg1_6VBLBJqj5HJWB1Rg&G_&@Yb z@Yi7eUxzOo;)l?8fZ+eG$1iV_kwL&9U=T0}7z7Lg1_6VBLBJqj5HJWB1pZG5JTo0# zeMw~JR-Qfi;6!7r5owIA980l~HYkG6qy8L4@GGv^5pMKYN;Xi`?w2UVo=ZuVWXhVi zp0%AK#y}B?dfqWvD@%LBE;M}`b@Vy9I9bb895KTW3Wvnj2{b%VJQ~4Q2mFq!J`VW) zz?ufsA*)j4w}L)PJXH-b235)MrC^+84Wbn4Q_0EQy5W?TF diff --git a/test/gcfiles/i386-gconly b/test/gcfiles/i386-gconly deleted file mode 100755 index 910274770f9e14fcd5b663d0236e3ec37f77fd7f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 12608 zcmeHO&u<$=6rN38u&ANIM92tKwk6@0A}=6>I67(E9}wF7DpW{lYR8*+!CqUtYb6mq zM5>T#0#cB2LPFxii9`PcBsjDePH-rv9*UG!RN}w^p$hYTyPjram)=3|sc+}KnR(y5 z`F0)6+OI$Ub?5Jgl~M&RV?XeBZjZ$GQlNetPCmCBe&Y%^-pxLSuB&1$Qm!>Ch5 z=O9KM7&-LUfx$eDWDh1751|7X2-bDC(RPE_-@lIt7&iJNV34zW(#N2$^>P>mp1;~3 z9L!?}7*Anr2N-Ed<7wUT*F3+bJ%6QzNrMh^9RI$gg=<`Za*X*ZbrUI1TF&A|7`aVn)~g1UV8b~P zRGRKuEBGjMPP96HtrB@Hzqn$0op;?JL{I1(PmKeO_^(3^zGYnlzO}b6>@8WUI9HiyQ0jTi;QlXyQ!Jof>ifwJh9e>j2m``^Fdz&F1HynXpw4xF zz4TjQmrR`By8JVaPg~p9aqZ^DyG!GlZu#!+3mDsa=kDiUti$Mzjd#m`?mi0h)ob1I zAKj~$ekgqK{dRAHC;Z+-4%a$g7+W(Va#2TLA;i&U#q-q!3l$Ei zuaBVNh3;Vs-#p;old+9D27Cv>+6L4*#HGk@BsR+!)J$SCOwFcj*0XW~|33kwWPBUd g(!;svIG19aeOzRreU$F%dxaPK_ht4x#0nPr2ZBa1l>h($ diff --git a/test/gcfiles/i386-nogc b/test/gcfiles/i386-nogc deleted file mode 100755 index 4349810f0d0eaa39d8ac541048bc09bcd48d49f3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 12608 zcmeHO--{bn6uz@fZLra9w~IuSI&F7bl{KRv>SK1h=?`7o{bebF+$NjZO>{B|GZVTC zLKll*T!j_-D)P@#R%Cm($4gTl6`^q~(@it+nqa=Wuh?H^Fjp=a(n_uli} zbHAA^caqbe?*94D9;MVS=n!-SdV9B0A1m9kV7~#?dUo<;>D|)&SvW?9jWO6V?lWAp zUM!toOpPNWFTA!VI z^EezMFT+5bvXhoE%-_U;e=pR5yBwku@}T&5uDzWv=izxIYAjke+%PP!xILVHj0|!h zM#g?9Mzh-X-0gd~b01k?a6ximc!NtxB=D+9zQ=3*~3oX@`O1)hkYQ&8xzH2Fe^hdg(}W{?hB;ezX6; zUqAnN={EBmhN``f$NHl2*#~(ZX)}(0U(&)gE6cNgU~tAZ2Q%6RB!r)Wz*}NbAu2)p>sSn4m9Gw3N`qabshND_P(&UWU0bb z0eh@h24j#D5O&5iEAKBACQXD=M=^u@{}7yF0rf)9Pi`)+6L4*#HGluB{s_#)O2DqOwFWh*0XW~|33kwXnY&h g(u29^IF~}4eOzRreU$F%`-K-f_ht4xzzP=n7j?!l%K!iX diff --git a/test/gcfiles/i386-nogc--x86_64-broken b/test/gcfiles/i386-nogc--x86_64-broken deleted file mode 100755 index 6570d398af036d501a9c0b9d6060fb2afadeab28..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 29056 zcmeHPTWDNG7@loYyV__su}DO%r)^SgiP=cKy$GAO(XGasn<9cvljfwmy4el88=8pF zV5qppLIf3j5c(qcP^3>jh^U}q>w|spf|76b0 zKbP;!d^0D^%+5cb{Pp&`N~tw)>*3Zam4J6^3XjGZ;-*bpFVrmvszx{wVL%uV2801& zKo}4PgaKhd7!U@80bxKGxWpLv^!K0svI~E+3DAuCpI@icQRT(3{#SRwY2Dj(Z}+3! z{SSs3$8CaTgymkqL+iorM+QTgSjLru&-db0#xa-HWq-2FP$1Fr-i=D#izIKOQVlK> z{RTDdRIN)>W5b14NGKR50djb7W1GB7;E zmw~~Jld4z@>RKNxm5bT@A)6Sj$4kJt7HKa5BP?M&ttaw_v-uI7&5st4DT;9t7-__x zjMgIr$Tq$Yr}f^h!LCq(hp_7p+ZWjUskTS!JU9%f94{A_EFUKv%7*Jq{+zCLZfGTp zxTh2`;t>pv3)jD_e;N<1AKZQ49w<&a5kNWR1wPBL{;nMO&y{r1A1xsgvQ&AK*ITSF z^I<#^)ri((eyKEc$hSCAj0V(!7!BTv7=@9EoWIzIwe5%lgD)f(jyI^3P(89^tc4v0 zb!}+@26=4pmd7BE!MEJAZ~y(>d-u8TGKB=b2^cA4B9F}LWUErsNHXc0;BA7^;u7Jv zLvMxO3%?yc+rV@d;#fEb{{Z|AaJ_J0VO~1dXW=v#K4o#7?)<9|xE_w*a&n?nOy;t~ z$&snt2=wdVSchkB*;eR3a_4*RG+q7knU9W~XFU^eYQq)A?Q}ZhAUC7hEXV&ZVc|Ot zz%h;WRq7j5xjmK~m@1Y1v1CtnxHwdtO5Rf(8uJeqicgl3_Y@}bBSYnEA>T3TVv`U1 z#S&sl$vvTTpb`HzIENpLod$kvv0Z3e@T!j89cW{AxV{YP1ccf$h2bX-c67NCN^M6D zw*P5xiWjIIRu4KDiij{E3tz~i$>-rjgaKhd7!U@80bxKG5C((+VL%uV2801& z;J?QJe}{ezj=%fyx1kfV2k_tPWNDEwAPfit!hkR!3tK!|-w}j)j6v-U!YrnGLSdd~Wd_<4KvGWka9X;)F51+k!_?j!ve0=P z-PP5D7q#tjZLVV_c$Ykd0bxKG5C((+VL%uV2801&Ko}4Pgn^5mfir>qe++BqX)dap z;J$+4F6{qJ-D#tqhFkIu0F%Qm(rtJ-k2sTfLDYtw`OPAqhY!MHb)HM$nw~yo)jI-U z_dvY6O>VW~T>$|eZT+p0xWTWr^?%&r(MFfz^gx5%wY__Qw$7B*?heRVyw)JQqHZ=b ztPaE6X&0og?N)?e&TxyOj=wpJ=iW~}EH&5$Z{>RlUa@$WTM+7DsmELSj)LPBk7I~J zJ&F;e1W&E~j5a3IPOt5cd5cF|X$p0-F%+nEPma5xXX^F^gjH%w`)Aqj;SWJnCUQAl2Y4{$GmRXW&Kp6*^_w zboL7O8+gGT3bfKnA-|KfXYK`1{j;+>-uiskO|S0!x{*6__)qRQKsM9< zaJheS&jHJAha(Tiy`W!rSe^YQA`A!v!hkR!3}HUb@3>CU$Jv;34?&?Q`oF95}RW)i-{WBfR-g~1DNpw>;stH7z)7W9)z(I xfX{7PFA>aH=|gOJ(K@u^(85u?sRD_#1(3d3yi= diff --git a/test/gcfiles/i386-nogc--x86_64-gc b/test/gcfiles/i386-nogc--x86_64-gc deleted file mode 100755 index 3f3b82c3832ef9857d316ee902c278b6577eb445..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 29056 zcmeHPTWnNC7@mb9t5(@!V`@}9V9Ui|x5gVUBwL`eC<<-C5EC6*ddjYDcbna9Yz+yG z4aT*S)C5g@FyY0-2V!{g!5B3WBlsW>UV;e^Jcua-jWIqLHP-K&GqXK=wiodU_5ag3 zGyhz^GxN=yW@dK&`TWoKKU7LBfm;r@RH+2K`%-u`#t=7d;&ww_ji73T6A=c40bxKG z5C((+VL%uV2801&Ko}4Pgn@q<17H01!=HBHZ#Dr|qW(WEQ|gHFVp#vHO>kQ8?%dw> zR9DZgP~*5wFpsd@t9WR=r)%GyP$rgf<>2$Zc$IO?rFGdKEi)8Iw7hqhQrnT_tx&4L zWuo7p#+|BlX>6#!kkh5|NIxQ@7#U!26C(o**3(IRS%7gJB7lLyI^*Yt{i4xp+ffFF zhxjruxN%Yyi$PuM1Eq2?n?Gn1qxCojjN6cQ3>aYv<7qvTe?FTZ(AoT80hyv0CxMYh z{K;rNLV#@J$8cJ2>)g{BO7IYN{bBn8n?Kd|Xq^X#0hPn$;+*B-{4eFuGu6UAsi9f;B3Er?MV7|HpweOTL$I57A^a^ZM`N(t2?JH%Sp zVNlnW7GRLa7H@tG@)&%}J9j+sSl6~4?z>DOfo}pv3Yo|w^Ez3h)Hsq%dNaIcC@n4# z{zmAn@OQ)C2%l|WIty_uT!6nH{*7?E;ljeabgs|BX)b));yB&;HzRNd9KYq{NU502 zW&4u@W4Qt7x5KdxFW!51q37_!AAQht%a3P2J^T~vnSfI_-eBB4PG=lsJ*v%e{QnXb zzGFWe(^y}nzC)Ea#*)2brLsSi+?nk!_7%sHj}-fc{O1eB=Ss;(3M2V}zH+vZZy$8A z$tV3{2{EPQ)=)aoi2neb!;i(z06#X{F0?IpRr{89v@zRXUj}soLT#Bs|FZ|$J6#E- z)*=Vne+Zo71!{xUgARrwA`A!v!hkR!366z8~k$aR|P*og$2Z?E%pH4~8QS3wJTlkKG;}(x& zh(bMz5u^l9t^JHPCeu!@?T=}TM_XwMb+a)PsC7?{yK@*2b@v}&eASFQH~vE7F0`M7 z@{8PuVDTIb_p`$FV*iBOz1ZCdi3lF`Fdn}L?r}M>0HMRjQbJ_+l<5k!PFuO4nfX_V$VijL0nXkrvU>?S<%6Q-}P#<^e diff --git a/test/gcfiles/i386-nogc--x86_64-nogc b/test/gcfiles/i386-nogc--x86_64-nogc deleted file mode 100755 index 48dce6ab2d6a3f3ccae5a3ad72116e0df59fa9c3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 29056 zcmeHPU2IfE7@dV8D^%G6F*Pb)phYm)1%G*vWD67)MW8i(FbC9GciU@#0Z+mlL;Chcn~cF4KY3tCDe1~&TQ}AZ43AW_MzT;I!V}x~c7@wvKI~ z#&Mfq3Sqf-@X)%eZD&^~6U(@A@cCZ6$~flIy5tX)7z!j>-g{W7O-S+v2i?nl~tzz9niPwT<_{%pQiXY+gektvFC0vKt;pNQ5Y z1jsgi38(dj)~?o2f`_o{58D^m{He4@>pVCNs2nI2CM_Q)9Lk34O#Ym%b*_6ljJT&1 zG2#&njtkentbZC0t+%avZaowys}MjrP(b_|Fw}!QWd%BxJJkCa*V9U*^Mj zB&rdu`}|_Dd!KJ{q8N3k12O8nX)*eH2Xp>JA6B;`4h+7KTsYpKQbP5}_OTXr7}T_- z5g6pL(VH5BJOyUkGm@ltz~be22jEzTHy&Hs-*M=fFFtEn@W+|24_#(G6L4zo9OE8!I^!VAQEisv|Cg}v9lPO} z#`-Gt3#z;_mh3!GEct!OE!mzzci}*CW1+jx-``(&wV2%4KbY_BE@k`kEqh&T@iI|3vCNt)v~SyZOr!6mO-6>P+g|K=aoGzt*(Sp%aDWZ zKL}3o0=2^GK?g$-5e9?-VL%uV2801&Kp0Rv%I6PXY8)dIJ4fF70n?|E(I4?F*Ef~B zn(E4JSH~Vf+QJqWHoU2glUeCqJY#+Sbu9dG6gf4sRK&kPp~ z z1NdDYB=3NzKQMez8bIxmKu2mIYOpwXOdyi$@z>iqiuPcGvdq0opoKu6K7p&f+x%*%fuOnPGJp z<_^0ceQmcQ{8ENn6gB*fT0HlD>S3wEHh9zDOYnikyW4_L4@)iH^mh~-vv?ds6zWlo zASHNe?Ps(xnRa@0e~ei?+DcQXn~kABt$A|XoyCBtx&Hv;t7_c2@fR9*q5UM3zs`LK z7SF+OKPy}>_D{Io*Si}b5y7J##^d+EJuWA9oS2IOcuzpIy`II3+XB?X^>H}Pw|GTA z7u=x`#akZ1qaLmwzFEXfh{ZFTZA6UXt%~4L590x;3UA{7Qsh1ZFVe5jDZ{3-r@7z2 z3+_;$l~xM*ouoZ;FMt|=zY3A$jAQR3=nLquujQ>{*QMce72WwC_az_qtH>^Bi%Ko}4PgaKhd7!U@gV_-+Rd_H~nQX)Mvi}&HBN1pl<+0*53 z#yG=BA6?sS)V3=p)EMV%*62Td_+-LZ{U1l%c=N}2;`i#K54{hswR61r2z+b%|7*qe z|7V1vCjWS6m7PCSqjL@ww)f|{lrn|Ae{5lptY*H}p<+XcSfYM*e3al3IcV2cZ(83a zyLG11#TN#I0bxKG5C((+VL%uV2801&Ko}4Pgn`?KfrcfCj#lhpG^ET0>w5i`*3M4d zG;tKZy|8L@5`%!@Q`oF95*uY}3yCV*fW}E}1DNq9>;stH7z)7WeuuFWfX{7e|y@GoBXb>;v7 diff --git a/test/gcfiles/i386-noobjc b/test/gcfiles/i386-noobjc deleted file mode 100755 index 8f01860aba841a005c4accf46f4d7cb404973b38..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4228 zcmeHKK`TU26u!@pLRd*rh7i)sD9K8Z7aAc(Nx03MdGa*#)XaMsyID}|tX(<3SJvGEQfADJOyrz3stzmrHUR8PlhU&*Ug(GM_xkIeu;#i=i2AQI z22~YG>AQ!Ho0pEedw|WKGt_X$Joan2L-kd)YW;fm;=)qeoLx|Fh$G|U4aaafKXaA! z2~ka{;I9>TjN+XAJAo8p@}I%V`etASh#GT=Xus(BUp~KSH_!qk{cGy|Fe&46Y=GoTsxuMDi8 zJ&)aP-WB288sL|olB!njoAV%)*e-nhJ*&Mlf^x0SL~ec*>ca$=IxT` zEoL284DiC3p!5#@7`7P@X83gkez?yWFjF8;<51nd?~}oOl%2Bd`IfYx($NO?4%XCP ADB(pKjom4d zM%9cQ`=TgK!Zy%DZatN3dni2=HixtgrIbQ?Fx|Gh6be1GkV8^ROP~}=VGm2czxgA} zV>@gPg(iF-e)Im!dvD%+W_-}VoB7TCpZ`)Rbqetdh!aXpBl*?`*i0oxbvIFbZC~mT z87M#j3Q&Lo6rcbFC_n)UP=Epy_>2ks{@%y;!yo?Y4Ca0oaW|>d8+ZfN>n;+YgMSd(shT$c_9wb`n>ZcskjKP@}5M~*EIVEh1ssC!sGJVrXJl#C&1Z2n_oxO?q})AA>Qwpt$^ z<0bT!#*&^qM%!ujTh@W&;W4`57?PemhTX1jkA^rhk0JU_Dy1YnX$-j*ONd&R*2>Qn z3v1CmIYea^5$Ol$EZ0KXM&S`;E+Nh!j@P&BM#tGUQ_Vf(uve> z;ZXjV&!8;F_!H7eR6dOSRYVC7g;Cnd!?W?@qxeAKQM8h|-}%SWKYqKEx$ukgubsYg zJOAA|)cNzA!ch>PjLJwQ$nh2trLP=csSgno%ILd21E(y}X?(*>rP3Q&Lo6rcbFC_n)U zP=Epypa2CZKmiI+fC3bt00k&O0SZun0u-PC1t>rP3Q&Lo6rcbFC_n)UP=Epypa2CZ zKmiI+fC3bt00lln0-JAs^y2#~ScfjFa?ghoA5UeSudJ(=b-xmS60VPzysXckLeF4j zKMDxZ`uQl8{<8k}#E=d4MC~7RJ+rivb?UMrP3Q&Lo6rcbFC_n)UP=Epypa2CZKmiI+fC3bt00k&O0SZun0u-PC1t>rP z3Q&Lo6rcbFC_n)UP=Epypa2DwI`^3BCcATp>F(qPL{8}~$59tj@?r4c`}4X}Z(ABK z9k@85ZoQ(C>YZUiGX6Kkn<3(Dq*4AZ@>>%L_17E4YqIV?g^j$E;Co2t!u$`Az8>~P zR?Q)vMwEOR8_Dk@okc1^>YK7b_%qV8Xdv$s_^0F%W&D35#h<+9K+P(n+p~A-o@IFL zuHjj(w-Ec>`9h;_x0*(7Bfpxlzp`{?kk4n#R=u-5C={=jDle=T%QaogZLAb)nak7uPfGo;j#gGwr(RxVwXm&2ltK z)7q-{Ti&2rE0orjGnkJ#KR>8dDy95tZmpa_b7R}II$KW9w0fyvehOcxZ+~vi1}Ebv z{}wZ@Ijw%%a&et3({}oopYOAsed*%krK-`3>*?7)PqLXYG2(gdskj{C<}pbC3{~gA%q$H5}!U(fZ&4h5vja zHoxe-htct)ax{j#=Wsk|-S51NAC20^uW#&#&+ol(eyd?8DaXecA5YxA?Yk9nwcOaA zY?wbG6*rkPE`cA$&$DH_p4P{YHyO59*#q;iN?x}(3gLKmyS{D7sm8~f%mz)P@ud7o z=NZk<>A3#YjgL1Sj`vnDtdeqcO~R@}NjP4!?$!PJ_;@$bU&7TOQBwY-@#H$lHPgDZ zR(`HnSc~rAAu1(g{jG}buKjQ@xemB|qwolFmk?(V$LsM7r{myB1s*-v4eYy!k%GADyD1AO{coZGvI6wN&&h1|p zo}Rz)_Sa_rUb^?kIn>E<)zm|Nd@?Fa9l{ji5~B1Cj{6~U31#%%o`F-ADF2D6+=u}C zgMMkG2mM6taKC!rbD~~Hk8?K}Z(rSWJ*%Bsj{3-P9!HerP3Q&Lo6rcbFC_n)UP=Epypa2CZKmiI+fC3bt z00k&O0SZun0u-PC1t>rP3Q&Lo6!;$tY_7if(TnfnU3{{d_I!A-@l@96%G&uwq!ND` zu3MM9tRJ32&uPCm3Rqx!GwY|LjkJ+<&JxE%eEt0RzT@&Itx^88rP3Q&Lo6rcbFC_n)UP=Epypa2CZKmiI+fC3bt00k&O0SZun z0u-PC1t>rP3Q&Lo6rcbFC_n)UP=Epypa2CZKmiI+fC3bt00k&O0Sf#-37mUOb!WTT z?xpTbb@_Rv&Sk?duv_h}x2Mgz`E^S@|IK$2@4gjGJE7HSb!y9T)R!vD&*rt7nNYW0 z!N3*( zZ+U}itx#H9&R|C7{QRI+sg&}oxwUcz&5do->TEeZ)9R&y8LDkF)&3lv4^GEXT5fgY zn$zmHEf?3yGHs{t8s?5`$an7-{YwT5oVoV@A;#|epd$9q@PMu4u*du>HuqXh?Rx!& NZrGis)r;=S{{kfq;2!`0 diff --git a/test/gcfiles/libnoobjc.dylib b/test/gcfiles/libnoobjc.dylib deleted file mode 100755 index 56167e2988e548060e5b67bab17adf9af211be26..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 41640 zcmeI4U1%It7>3W9YW<1W%lZR~#-f(&BsGK}EZxoSk_0!qCD|0ckTE+ui7WdPc4uRH zGYwb~F9mNEEEIdwURMPbyihFmN4ymDrlkm83%v-w@64RAEfQ1EQp5X^Z@%-LIcMga zXL7R#zW0Z%FTOIy+y~zeA2Q|u!t@$0!>R4QH`#ZVeQ=owh=2%)fCz|y2#A0Ph=2%) zfCz}d|BS$;n}2L2Km5%DSUZ2<7jPQz9?k*q``>&6cin37wJI8emQ4~w&c7edLj;=Z z2L476x8wZQ`R9)s^Du5O-eUXArp;Zq-+rgjuDgx)N(Y^{ui*mb&Z3{uzSqvRk?Ws= zyKZT&{A#fK}$^>~L5ht*1@l%LJbl`|Obta?7w z3c8-(O~>mqt6tjMGrPfNI)?u)&hbvO-|~C-OupCX^m~rC-gEeUCZewe5ZE?HLMWl` z_JV%RtwGGe@5UcqR}l~a5fA|p5CIVo0TB=Z5fFj9PT+E^|Ci#~fYtxo#32Ip|7efH zZ@_QY-{ZZ=vPrZd$?sGX@LY0EK`lI*&EpK$0c}9`?M5Gv4@05-5dUa#;HP%^!O_ zw!`L7XwvTw{oe2W-n`%Mz4^@OFaz(+Z|?r$mrAKq(9c3gl$wC~#=9uSQu}Q;;eK;Z z+MpB?AOR8}0TLhq5+DH*AOR8}0TLjAPZ)teeE7lL@WEew26G>Wem$+!t9TE9=YN$( zp>?fvwU#)seCWeU{Anm&9{8fQXYF{Q1Cxxu^;FPb>>)|3VdQIVx?a!jY=sTU9B(2P z(^O*PA}=x`(YoJx(e5<0-Pv@|n9Ok-u}f$d8;^_P#R`LO`wXh{my59@dDLSXF%%9W zTDL8)uFY26bp!usd{z$RkBluZV0;0CC_MBJ=g8)jk{n_a^N;0lciRo8otN_T(IPyS}xb;>bLPh#gf*i9IQYT#GrV*5&2O)1`&w z=$;&+F%N}(7r}BZq;Dkmqw+=QBsAXMvKu|?THo$j?v|Np?!s5+kVTkEu6>B&CuCx% z)OobYTqW<5@MWC0VMoz;2K7~_$bF$oUwMHRp99=`k_XXC#`#t42S2&o{c`We&wb}B zpC5nyN7HEY=Q)N^ppQpBtcZ*^3zb+IU#a(?DP{ECo`Dkw!Jvj6(7>sjs0d=&F2GRnN?hQa^%vCtQe6cQi-5+DH*AOR8}0TLhq z5+DH*AOR8}0TLhq5+DH*AOR8}0TLhq5+DH*AOR8}0TLhq5+DH*AOR8}0TLhq5+DH* zAOR8}0TLhq5+DH*AOR8}0TTFx5ZHL_{pa6W#5#0Yk^4|M>A00OzOt@f*8Gb8G+ZAq zby=T3hR9%LKO9K3em=4iFKd5Klzea`>VL0mnWdksQ!I5(nhy`<9WjbTbE{`bV2A-vzhM)f;Xh z$h!Xw3fYt3b=c{!{(ab2!dO(*H1sr7>J=1H--Dfk6_NI)6p(jd$I(Id6Zogpp_2dK zu=vR~2WmSy_v+P&Sx5ZyVW!bYsICU{iV4}gJLmfw(6a&!9r=Z zTzz(>RH^A&VQsNg%U#T7E+$gLqtQ5-SYB84`lnWI!ES7RWWtpD6ZOxd2mC{PC z-7^Q(^<2AdI_}P3ZKD!pX~T8 zo@wSN&c3l==evIXqNrT`OI!!AVL!(GdEw&!0TQ`w!!=T6@-xhe{~PeDeG*672;T z`9Uknv>NW$QPKL~1%>~9A~C;c-@|D9$d7W!K8JD9y5D&b4~^Q!(>D$z=XX1t-%=PP zelkZqpK$w@&uyVlE5wfE!}tEeuzKJm3jUcTOKe{Gi(;*Y)Yu3HG-=55O6Y(Oe zfl=arR6e;5a?P|ZFIS!}Ei6a(@DPn6dB0TA-L)4clk0%Xw=egjmW572+Rj7#6MP-Vg zgG$WDl?M?Z$ z8GW~B;G{+6ADJn{AfSiDWuXUgLho=~z3(|u)X{P7q?7&EcU{kFXXc|ABvg+=W&S%u zrR3gyEcAsZg#<`|1W14cNPq-LfCNZ@1W14cNPq-LfCNZ@1W14cNPq-LfCNZ@1W14c zNPq-LfCNZ@1W14cNPq-LfCNZ@1W14cNPq-LfCNZ@1W14cNPq-LfCTMbJ_k4G9ef}Ax&gR1}uv_h}x2w&% z`Bh6jbM1}Pn>T_fr?fh)PHj4ldZ0Rgxv16Th`RL>J_=qe7uMJHw8rKHmsU!(Ld<_M zt!}=YQfak^G7t$7k$nQ1ek~||pYZ<#e{&?I{&Br@MS4hl28HZ#Ao4xbr(^yPQGX>k zH$Qd-Hid*-0Jo@>`l$K4sMZB(KxO>48>Z+V0D+Cq7GK8G2Z7cLBH)oQu8R9LR$ z(B0TFtJwhJ5dS*1u$6 pfiu_sA0l?&2Mvio!#(zrVUPJMHg{W1?Rx!&ZrGis)r;=S{{U2%Gzo*_IPZE z&7shw-v>SK^FHsq&-1?XnbBbe-gn-=^XuO#rA|S=0v%Oq0_K$uQH+o5w_St#gFR`3 zQb>RVNPq-LfCNZ@1W14cNPq-LfCRo^1paXQlRM#qzdD1tpM`!urPOP94}j-?l|rF) zt$3xDII&#l!%F;VC|(};qP1u3c%cK6jKBV^pugBdl2XIS*V=Twp556B87X&0<2}SKqg`w~E{Yc`41VM@s4P5Hh#kqH9@B`Sa1hbD zZFzNVw(71M_($V2av*nPYCa09-5Sy5PEQh<>Za6J}66mY-;T+E+ zRyvD4IY--R_FL8g$HO_gVGgk;=dj!Lt^E{7<}pOBe@Ti$DxzZczerk^sK9WyJxvuX1cixU!6x5VJf-yA&Q@niJ?*# z&?a-0yidZHasC23hQ@nQUxSL=6RPx;7ijT0z`ZMZ0KH_Kw|?@cPrviKZ~o>N)xzvw zZ>RUB(dN%{9HT%Vi+orS8E+0Mu`<3=A3;Zy(RX_WPFU1we9uhhLjX7`F~Z;=MrM3C zrr!6QDB|cCw^GUe>$|RJwbKhx%p=Gs^PV3D|KmqPUpP`ofCNZ@1W14cNPq-LfCNZ@ z1W14cNPq-LfCNZ@1W14cNPq-LfCNZ@1W14cNPq-LfCNZ@1W14cNPq-LfCNZ@1W14c zNPq-LfCNZ@1W14cNPq-L;0r=vyRn3I=&~aB!En-XD{FjZUA?UN75!znK3?jw zK7SmM!ODI(kZApUWF=nK{+=kg;7HW}Zr3tPKUt?PYqMqD@2@bg=i!S4>ym>yOA;Uf z5+DH*AOR8}0TLhq5+DH*AOR8}0TLhq5+DH*AOR8}0TLhq5+DH*AOR8}0TLhq5+DH* zAOR8}0TLhq5+DH*AOR8}0TLhq5+DH*AOR8}0TLhq5+DH*_!I6tbczob&??cs=2{B`@y5ZVW@QT-O`o1-J@AFmfL z%ewzG3fYt3$FS33{b#VRhOwxsY3OOF)XONOz6U!6D-n zsq*YH$T3pSx zd*-0Bo^97n$K4rJH_B0#rnOn`x4gl6ZLzenki~q=ix&sAN~Kg-&aafS=x%J8R%g@c znN}|y%unIl^zARr+2CXx<=8? zFQwFL{w{q=eGUFR9;&sj6|dCbBqf(<2z}Y-PoClZhjCb~J!{89C6r`7d43m>Ifz7l z;Gs;Z;eH(ztq)#M`0pnY^NaR9jK+`rC`WS7L9P3p7xB=jZ9IMZ`46A(d*S?+!vx|d zb40nt3b!Bn+!ph-eC$Xrte;?oo6H%Pz?b-Wwrtna`uKcfVSkl7U>-Kfc8mKG=Cj-N zElW-{nQtr?bdBN7p25I%L9p&AM0j+mrdOAzoxH zFiQN-$|u)Bu9?=QmGaZY#g*tD9->hs=a(wFyY|9lavgB__T@g*GSEqAydCdwde+sx z-LqW0ufPikyWz*2M@H!{c_&~dV5eY{k{Jf`JB0(1dkA0p4_`(*8t+AY4JsmaQJLab zp%U|X+xOwx&H@^GIcNj zQAXeG88~TC`A4SnF$m}(aT(}AoX|TQSMPgH6m@i*Td8FK^f0mlyctY+M61*4)TZO8`zs4i6||ZhRo7p_N5KoF{QA0{*4Uij(rU4mkNHog z)U}sKR7&lk3`9aiWS@YhUki$x3IEUVH%CX*KVL6imL3wHMj?9~i2M}w>6rf(>aPZe z{1UF3hMo!A#4n?W{NJNK6&@4w_k<5uHKhpUU#OqO$Kj)W21aoL;z#=rOp1>_(fl5Q z4|hL9Ch)_r%*)tDw`XtHJvuksZz*h2Bmx%4NJu=zR+%&p1p0& zn1j{gYPQ`o2bJ|~yKXw}&Y-$cjYRgQwzeMMQ({YrRTiv+qwEAt!#kI0b+v&T8x$PSAz56-;l7R)z nT>F2B*nJ-~B>oI{*-M5!=C9b?Z8f#)^&7fjcbZl&x-b6&uxRCk diff --git a/test/gcfiles/libsupportsgc.dylib b/test/gcfiles/libsupportsgc.dylib deleted file mode 100755 index 8a1efdac309cb57c33805b544ce9ff7e6ab022ee..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 74696 zcmeI5-)|#V700hP-X&_YY!(CxYAdCysM>ak?Omc(Bx+&DF-cngNE0X8K2*2%c$^Nh zJ;pOm6A8IqXtk}x!~O$E3lH$PeOOAx11}X)1T8EG2_8V|LyLHz0wH)Q(u(hyAIW5r zRNDt6TE0g<_uO;mo^$VfX8bTC-8;Xy_tT##rA|RV107Rp66V|QqL@hRx7~vKojqxT zQb>RVNPq-LfCNZ@1W14cNPq-LfCN5a1b+452ls*pe{}|PKL`C*QmI$)9stk(Dv3ht zTJc&fc4E1}hn4u#P`o_wL~GaFaRUb?9)J65et)rtB&kNBueIU0eXF|_G{keguZlEc~UH0-uF3G~(aaE|8@ zE1kujoTFnm2W|6!y7gxf2 za)ibl6!u*N%e9ccp`1nK%g`xkw7qRLoI$T=_g!bpNHuris|&~?OeNPoMDb%XIa2B( z+GMVh_euCN&L3dM(fA>Z=Mqv1^Ptj!-~jwb5Mzu@s)ZHno#M1(@*1sMeW8nj8r}VfTI#43=U#s#)o6- z1J@2Cj*f9R8SlTo>$qkowGhTUhKw@r`H}xWemwAnBZUM=fCNZ@1W14cNPq-LfCNZ@ z1W14cNPq-LfCNZ@1W14cNPq-LfCNZ@1W14cNPq-LfCNZ@1W14cNPq-LfCNZ@1W14c zNPq-LfCNZ@1W14cNPq-BAp|yFd;ht&maq<8R^&b$Ogd_1jjyb$mo>klKMvN%OI_CI zPax7?*$)R2uAdLB#LL>>6D8*#3H#sgT4w1d>(pg!wygX83Fh@Yd=Y)oy4VsW)p zeYRYz)O0Pswp6TTFK1GhuMC$q@@tFbe5q6|WHZB3zJi9OViu@O!?m`}Sz}l(mb0C{ zF|4j(?#}EB5M|{sgtq0u~@zAI(Jbiol51;Rw!Tgqk1meeY zgt^5Ew^u!Gi}_kUawHejPq4yG=8Q|=N%TD1mg8!De7^CZzsemj51M4V#eE6#S)KZp zDW@9GH=gsmhWW(*sPhcxXLlX%>c;a;2KjFKX_fflH3^yynIK=Y?$*8bc)nYR7g_a< z691#}$#sxxrgdqh@^o==CA^16XcWnLr3&w^y&#!f2VA~=Ig45bIt7ik;~kDO==JQr z>)?F_UO-q4FXjR=N`J{a2{Q>h4I7ut$e-US9FW{2_|kuL9qnj*5cO54h}1=8il2o_ z%*U075Fq3HzC8AuH|~7(=huJqxwHTL`tLSp&?e)mi3dD=BJ`yVG69{3N~}Nbd#EK; zdf@caIB8M&N2c--22gN3!Xa-Q`HiIqMV1N9n%BEEh; zdhEEmzef1zk&l~aFLyLYwB=fGJnR=epJ=~u9eT8lbrK)}5+DH*AOR8}0TLhq5+DH* zAOR8}0TLhq5+DH*AOR8}0TLhq5+DH*AOR8}0TLhq5+DH*AOR8}0TLhq5+DH*AOR8} z0TLhq5+DH*AOR8}0TLhq5+DH*_+KG#{!!JN?&W$HdsFKR&nR_17kq);?DX7SZPbky zO!dq+Ur*e*=}$SK)oFFAW!viW)rF@DT1}0q+b`jx;Du6teO=FJY)){sT&(3I{*y^{ z>v}>Z)gDS;Bt%5^321sXzj!C+{}z60ETR5#qqr$OBtC^g_BasvKI$_O|A(l*>>u(< zxM~J^CTJ7Cf+F6EN6G-uj;FF!$KizwCmli z;bL*MRDHHwtkiTZzqVAYWiMw^m#++$Hu7tW<$S4BEo3vpQoe$QrD7IeXg3Yl+BRp6 zVYyh&cKXJ!x}NRS4cplnu5DDpEKRdjAGF=!dTp_^vXI4$j7yh>wQ99gSkA9hvgn@P zGR$tv?i*%5<7%Q6rQDpT5Uv)2*v!$R4oK3SwIP@pq0FKU7Xf5Ar2)ff?;iM;w|eR?XGD8 zsuT%0xB2MV;=++jLG*y&R3VTs-`kzBch^qNNX(PQGjHC^H}8Ej zJD%P7{;%KuIl!2}Ok1opaCcR=r_W z5^q`H$?>HwJER1jw1;@SVL2VkMWZC%s=#|#0FX|+4`s~1cJ*?NH!Z)>GMm;Io-6Q9 z2msQf62pdp^KR>k-KulDwbl;w_q zWwcJt7%N7k8P-E=oyhePSto=$nuqmvWSJoD$#^L_1aP8$5l%3kuyfvWxVPD?wjJK_ z-7&nB@K3r$dJ-?nBj=8}7hXo-N&iTX;?Z-lU;2)m^I)CWO>^C%sZGY4P2iCp^(O)q z6a}7VIj*&a$0Lb1Bk+&~LVDCcJhRdi#j81{=MiEO@1($!@sb|J14s;Sv@ewxE}So( zS&(~5Hk`MQ<>Wypkvj1vO;mpr``+_Z$TCJ_r;slpm$I4-Ol(8s#2KyS9q6=1rk`M} zgZvPZ>__-AQTymTL5&Og)Bcu}dr^@^nnKdMo~t`{Rj+S4b?79IAdw%}>QDXt>3662 z&MRM?`|{G&o5xU>Mq-B!2I(28Q{C6tgAp(FOYVy_&MFew!gX;3C5`EoP0zQQdfBeJ zrn{-1rAPfr+kMZ|3+-CloDH6Rs?aZ5u4lJfo^FID^bh@SARpmoq3wxlbUKbzskwH? zU#wm(OdcW8DB$U2WE@TV7PMm=$sPC$Lz&Ibf=9cG#*p#2Kg4uAw^!U4`n>H=a;u<+ zHi7#t@=@Hk;Lizd4?fSfyREwEgTl$!BfG$l#>?0()W+kb9bwjpM$6cfXhrMe^K7`@ zpBDNKbR~)b#eiZ!F`yXu|1t1JsrR$-rg5>dw9vcL`@6J#I)g)8X?tEnR^C4S11K>D zKceDKluEsuP(2^X>PoLU{V2w^EGF9;LpJXijG|I>XH&r&K|igvxYoo$++ow&F5u$yt%(RWPmYzD0>J#`b(h%=$WTtbje9ycZso$lms#Xjr1{4E|0mXn~ zKrx^gPz)#r6a$I@#eibqKgPg)M>Cz&GGq7W=yEBz4Chtc599LsbvM<2AjcQ;=L)<~ zHYycX&avsOSFBc@ZQa1vjX<6Hu;nfowU3)xiX(Bs?lrNgWH5o2Z*kLWW;R?QP(ESQNV^DP< z&*B&Q@JcNo>huXCkR|-WKD=6_0jaa-kdqEf@@_&ryRZ`{W{g?ath+Dk`us~T%+1fe G$o>UZ$ed*W diff --git a/test/gcfiles/x86_64-broken b/test/gcfiles/x86_64-broken deleted file mode 100755 index 4346f8ac435152adab696f59ce65004d50094f49..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8576 zcmeHM&x;&I7_Cl>8I#4#Mv;g==!C2$A?&O~!AoFfHxp-d$&Z~?M9|92PVdfWPtVdl zLw3c(K!~t}z+4o(?M3hq#FPJkg2~B)B01!!vH=AV52E1sUUl_sb&s37P#;uRy{fNX zfAy-msqXsruV4S!FNCvKi2fcS#3a()hlRK_cKsld|ircjI!5(|qnB3JMIL16G|C% zd-Z5km5uKm%{QSv5}b6%X+B7%EasC<&tLOG)MWX5&39b;6I1t*jm6igzq>5kUbh+4 z+TIx7K=UQ{CpIm`jRq-~qi)y=R<%<$zK=EEBf6c~v~j*(aJ3aQWGh(dz%k3WrTMsq zXvA5LxFx^_5z8$@(+a9eKI- zXi^5gj5gZ!jxX1`;jVlM4cD(37w5+ux0HVEL40}iXM71CvB}5h!Cco;s&&$8*H%4F zMRvTd=KF0J8?hM==u)QnBF_)K6+9kUz7v`+r+X2b@j*A#?3m_T_G?i@#w_1VhL70f z15=7`=e@Krf8n+Ax%uRs;%4R*nATU59lu9(QPm$q(+o?l5FASmc@cSH6<3H80BgwU zN6onQJ_F{Qx<`e$g8U%TB_z}6%Vf@z+0Jqf{gY?cYIMdEv>(2K%?->MfH8-4?r z_7oE1`1HGbC%^pu%=4d|`um4lozshauPC7o9zakB#Hj=$r(rC8_TzieG~VY565ANF z5I-X9(@vqf5p}(GVWCwIYvD%WJRi-go$$S=aK6(E8nte#6HKcImbHa9yf8vdRG2f` z!AAae5>i%X^gW~hchhO4{@P3-H$7Ox@7he;yHLxhb=dr*OMA^ z-mF;#tO8a6tAJI&Dqt0OkOHq)Hh-#I`z2qwvCnbvDSYFl-{D@_{AQawjLOZkmlZ7k z`Gwf#p6!J@o8>V{LF<0{0$&1XJBR;muCt|K2bS$Y3wY{;AJ zdjB3uYJ)7oUJ&3L4O=+eQ#?&Sn3;2r#y#AN$eH+EN@JP(<_Yx7tKO^?&f_XW+xv^c zS5lJ-%i+x6aFHiZ>ZDxOaoCHK?fz|j zkT`S(9C4UGT$F1#SnjTffe4R0R*VlM+tF&(3mT}?=182W?vd@L^wz!QUf07}d5w%m z+qG68ruegAuh@E4yC(%!A9B>mmij;Ogu+7o14;goARqnFID>Y8(Gg-P#-vd;k1N_o h7>7~HS)cb76YS#Tb39h5V%_!nS2NTC1# diff --git a/test/gcfiles/x86_64-gc b/test/gcfiles/x86_64-gc deleted file mode 100755 index 23246c22b53107dab8a66a5c06b88481390e5dfc..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8576 zcmeHM&x;&I7_Cl>8I#4#Mv;g==!C2$A?&O~!AoFfHxp-d$&Z~?M9|92PVdfWPtVfb zLw3c(K!~t}z+4o(?M3hq#FPJkg2~B)B01z3WdnK<52E1sUUl_MO|P4~P#;uRzpuV} z{ne}Lrn~F=zkd5?zYxw|A$og+5R)i(9~I)daB(KY(1IJ%7y$;mPv(n(w%dC#LR0+mTEzQR@ zL?g~}BrVBNO0OwbLJn0~mTz109nqbLv37SguDiQf5K^8iEtQNgtgo?km##l!-I14T zk0xc{%V?ur@Az^p4tM2CX}EsPyf{DRxTW-K597XvAhdiFb6=e39pe-U@DyEZ>O?AF-Jqbc1JPns3>!MG+aZd@~t7Vv`R{ zV|=6M(!%_OH_GSc(`SmCnO9(1Pfd369MPxBe+*4CJadJhHaXNq)TvcmAx;3Sp=KO4 z{Or`wEz4nBo%yz)E3E1TbK^9`eN^Xz2>%YS|) zw)xKX(w)unADg!;*S^ia`%Ql^bql`1)R#C4v2gRbPf(SrgQ@GN`%O9@{4y8}n(X@E z9?sMYvIx6DfNwOs!r_|YS;oQ4n(t_QhkF?{3%^TgJm-7!1bXIGZ+eCEq^i*N;i7QX z^G<$Zf38pRjC}+ix6|f3e1*`&A0jq#jz$#!BunJfOtOEZp z1tyQ>7fbkuF9cd$F_KyN#a} z%A7t&9Oe%fWIWog zwE{84pACD(*7G_%J)!%Mqjt8`|B0s*7U~~J`i}(l@Q=nBv;z#c5K9Rrjk0+h(LTi3 gj8egRzqgoT7dxLjbCoLBT@)K1cq+;c1(Q7f1%l~7zyJUM diff --git a/test/gcfiles/x86_64-gcaso b/test/gcfiles/x86_64-gcaso deleted file mode 100755 index 9a58c23c2353415bf3e89506ea46fe2663d39895..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8920 zcmeHNO>7%Q6rLrN28ifZDX54aIaPuJs^V5EQZAM3I4&-Pq={1m6=-5Ro7iQ&wszNT z!UaneKT*}n9QY9@4shTWAugbTkU%1pkjSwI4jfvPN+5y6A>vT;z1g2P z3OV=uM$zl2biQf9Hy~O_EIafIzI%isE(w&0WVA*@O-U&-^`inA;l>GoX}eD{ia z!m9N0HJpnTXOUN&r5YOQd>;rtib)c}I!9O&{&H@W`I1Xh4MgYrS@0beoe1Mv*j2i5 zJei(PE6otEkvNv|ti&<#%5~!0vAMTeE!J#a_g&0$XFf?o*Mn*ot&ciy&h5fpd?xyn zeUgu`%176kdYyjusXkhMEQ~S z)f_Ll8|Zw;1Yb+^A*|V_uyyS7EZen~a68cXz7c$KzJygi+@qlBinNceTVmAtHZ(rM zDj%43;#(~EgnCUPoE+ffXJ;*k=KxtshB z9(k`)+dKe}K%)D0vf;T&yHZRpuG)*hB#$7G9kcKMVZLRZNMHTp^@XX;uYMSY4z7bi z_XX)GiAe{+T$uOh0NQ^Q)e3Rg+VdqFZoRlc(rbbg|~X>LpLr z8V=U8Qgg)AhP-5gU9|eL>{gcxcMzG&Ys- z4iqG09g*Lm9mT&W&~um)$(v%l|H>o3S3{P6{*~Va@_l)?9xKYccWV>tV-Lh0h&>Q{ zAof7)f!G7F2VxJz9*8{MiFjXAwFf z2FL#f`gpa>tre@(@GYFl7s%kzYN6t=qroiMaoW@9W39(=YNJ{fZp)mNhKBH5AKM&4 z>+K=Eu*F1@k~qPl>mJTH)b%=s@8DM+_%+JXi5^`o*1ea1 VIxnljbyZ^^y3#TVdLlBI*xyyL45|PC diff --git a/test/gcfiles/x86_64-gcaso2 b/test/gcfiles/x86_64-gcaso2 deleted file mode 100755 index 3ac79a2f133918ea56a660a31c210729f126b194..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8640 zcmeHMO^6&t6t2#XS(C*~+$a%$=v}fBf7o8pfS16`P9}EZl1+A2Sp}(`pWdC0Jv~Er z57`xkkr2tS1jd5_ZyrTFcu?>dR4_St_24m)2`G3|R2;uoT{Y9wlU?vA)Pw4(_v+PG zufKlX)6?&VKY#t_0At2p#@c%rV^e6K!f=(D7%}!3nvKT!h1@HJHw(*4WE~Y_RK2f8 zIGfmj^OeHt%BYK2pBc3=sUsA&AB}nv&Ld|tipQ~@!aIzUAwT5;eV-arzmK((D(7L; zDu)A8I=(fDHz_@ot`RN-|es`;h5 zGltidcxr#rlQM#h3C=5F6uAC|bV|o}L*hLs=Sfe>I9|)Y?D|#i`s)oirtvF=5}?E9(&E|kuNF?9RnHVNbzPy8+!Pl(evZhYu+QMRH%}vzG2&(d{VIATYbIkS zpj<;we#9B&-c9Iq4eV(=*U%qAyMUGy@};J7C^@Ib^H{I$U1g*GAv6<>o;y}63@pzr zTh%SE3Z3L}G>YS!YhTTL*gEyWr=VP0uM-0=F40l`rM*w@J=QIf;EXe6CGy|Fe&46Y=GoTsxZw$O}y|uk?yHmK+xn*DZ&V1|JZh!VO*!r`d z=xsEaxsVb|lM}7S82)elb@p^5gt` zaRJpG&tVsX`R-JfDo>(P&dVtFqO#q-g^v|NjPB8~wW!xPl?au!ZjhPE@+Jz-(K_q0 z;MgPCxKLRSH;Rs5g-wdVQ77BO>ox9dI+a%BpoU(eou=z0*Jrcz5n?ZEKPBDOfXs&t zQSlPrEgls*9fsdd@I7PjrQ=0RK|yUNmUFQ_DABFP`k?f9`NqusTd zAP$zQB894wFXfyg2ZZT&f2?c(mT?8((KHe`R466 zZ)R7sJKz2F%Rfhiunr5+J0ygdLfU;;h?~MjNr)$qDo9dZDZN&Hr@VZbwP~}`@IMiG zHgN+fSITd%q+L}1e7a6(jM&5k5_6iAp|crA)^osXbyGcgSfzNTw{}lTF9naD3Ec`CQF+R{IlE_o0o&*Qvk1D%(!j^lEKq zjIXcx;`TkF6v%eSNXxQ1xN zS&pbBx=QIZ<(ki-D$DZiX}(js6EW8AfyQ;Wiv=O&h0;nX;fD2?nddDxQxqavH|cV?VwZlg9g8Lt-0a7UD;Q z{eo4fZUv#!E-ba`e$C%1T;!v9z2kon6fSnUUZWPaI^K+WU|Cyu%kcx$1cik}JJ`to z3R28(S@*zijmC!YMYo!npTRg=bvVPa{SX&w)BU*eXG+OlLOcsE>ab%+^ZJ~|oHt`e z0i%FXz$jo8FbWt29;CpVmF@eL8$ahOw?Qi$E!>HUoe^tTqpI?eS z?%C$gC%qB(YS|u2{{wLOKB`~-#m$)dDWY>!aAukw0*cNtj)ZY zpFEQ5lRRS|frr%}>zvKppT^&r#Kw#QMggOMQNSo*6fg=H1&jhl0i%FXz$jo8_$aaim;qut{ItB+giWJ~>@cwAwj{(;2*NRSWzXuODafZ-8hDZ->twumd*hZu)Z e%2}WH7GrGVY}T7@ln?HEBehf>043+E#H@5Q@W>#DPe5QnwC7{fGlXNOWq)o7k{kN4sk^ zK^%;#B1KgtU*Mc02ZXrs13;n`2d)+3$VV!{1#v(iVV-Ap*4bT?-jU{&W@n#w-g)+& zXJ%KsJKz2F+doHyuqK4)9}+@LquhN^h#SJjnGlboR8ge7R6bvMyRveT-n3b1_@9V8 zTeyIftChD_(;=#VKJ614BR6pvg*8pe(Af&3^9ZN;>|+=V8s>Ef@dNwe1pOqGG6;K( zVC2f?cSZ9}>4+G|9deovk|~S$WXo|k9Uq=7pR4(1bUZP2AK6%b-Nw6Xvg3p;uikOS z_y(FUzCLl%Qru{eaxDn`wzr{!viW_Y`5w~k#7P_H>v`AOUQ@Qc^)3Rle7l;DYludi z<%n9Mqm)iduKOISvMk@8<~yM~5o7HhXk2%9u^^;8TV5?E!mz$l>n>h@iFHR_u05KR zo-2dxPNVC}&CoxPFQ(!8P3Fb? zcx}y>Lq8gEGM~u1f7E<|mDcR2R9Qw!iZnEuR02_tRonkNWi*CD7Y;LNX z%;Vb+ue>@l|HAn%C;u)?eE-X7K39}bM<&YLl# zfKk9GU=%P47zK<1_fz1F>dsHq>%Zo!HxFADK80_-{5!&{JKyZ_4WoK%{<4CVKfe%r zd}n+9&Q9fzo!iyxU+3TXYA`H(2H&voIgUas-+Jm}ROQ;Ra0B(AMd!Vrhr?luUGM#X zGxdTj{GR9G8x5~;xTbiLaj;~~cQn4ky@Z;D-=#F3^SyZrJ@cwJy~27_RcL#EMOa&T zD?fE4Hz0Y&J^&BhAL%-7j=wWwY-*!`QNSo*6fg=H1&jhl0i%FXz$jo8FbWt2{yz## zpUSV4@egCVBrh(!wkQ{u%e9)Ekr!$U*xhj*aoL|694~R_N$r%&1~z-Kv)#XqpB2ii z0Y~fSkC)^oHkQM6F%bTVM@rF#WGC3DIbIV!ZI;BI>LJ-_NoUJh>xB;X%Ijo2*{QcZ zQQ(h_39C~YF*hxJq?6E+w9{Bz7&e-? z>k=3K1pk056W!oOegYd;#y{YKaL)AA&eZC{^}ETP_s)HH&b{x@tgyYsXwpm)4n^e2BT;py@N_4c61|^E#>8RtCYRu7 zDKgd2wnGQ74Dxj*aT&zNT?+ANKb_zx4pm$A7eT$EL4EH42ZMaaj*lA9h^f=xOx0F( zuTiSmZpI+rnd7_W2#9-k8^+n!e`9TPz3^bIrycZ3=;rh~b|lm-I(K*$-*;(Pfx#Gf zn8!P^$2>6mtGf6E_Ci=Y2%&;Kn8Ps4MqW+S&BlyjBC{R%McI-GrcIsN+M zR&n{!$KW9K>dlAuw-G~2A`_P*U+7`RU~{-H-{bd>8|&oZJd1iZC-^-~zc%VWK5T}z zws?OLeFWw1+jp_B-Vz@Cx8@zDKHj_nUIDLwSHLUa74Qmp1-t@Y0k6RSDDY(U?f2zR z&p&gPy5R`oD`ytYH21L5-?{mQd(LTT%rcq&Bxa@%qorPMi)m*}COtOZi4STsubIvar = 10; - uintptr_t *subwords = (uintptr_t *)(__bridge void*)sub; - testassert(subwords[2] == 10); + testassertequal(readWord(sub, 2), 10); #ifdef __cplusplus - testassert(subwords[5] == 1); - testassert(sub->cxx.magic == 1); + testassertequal(readWord(sub, 5), 1); + testassertequal(sub->cxx.magic, 1); sub->cxx.magic++; - testassert(subwords[5] == 2); - testassert(sub->cxx.magic == 2); + testassertequal(readWord(sub, 5), 2); + testassertequal(sub->cxx.magic, 2); # if __has_feature(objc_arc) sub = nil; # else @@ -254,15 +261,14 @@ int main(int argc __attribute__((unused)), char **argv) */ Sub2 *sub2 = [Sub2 new]; - uintptr_t *sub2words = (uintptr_t *)(__bridge void*)sub2; sub2->subIvar = (void *)10; - testassert(sub2words[11] == 10); + testassertequal(readWord(sub2, 11), 10); - testassert(class_getInstanceSize([Sub2 class]) == 13*sizeof(void*)); + testassertequal(class_getInstanceSize([Sub2 class]), 13*sizeof(void*)); ivar = class_getInstanceVariable([Sub2 class], "subIvar"); testassert(ivar); - testassert(11*sizeof(void*) == (size_t)ivar_getOffset(ivar)); + testassertequal(11*sizeof(void*), (size_t)ivar_getOffset(ivar)); testassert(0 == strcmp(ivar_getName(ivar), "subIvar")); ivar = class_getInstanceVariable([ShrinkingSuper class], "superIvar"); diff --git a/test/lazyClassName.m b/test/lazyClassName.m new file mode 100644 index 0000000..264c20f --- /dev/null +++ b/test/lazyClassName.m @@ -0,0 +1,136 @@ +/* +TEST_RUN_OUTPUT +LazyClassName +LazyClassName2 +END +*/ + +#include "test.h" +#include "testroot.i" + +typedef const char * _Nullable (*objc_hook_lazyClassNamer)(_Nonnull Class); + +void objc_setHook_lazyClassNamer(_Nonnull objc_hook_lazyClassNamer newValue, + _Nonnull objc_hook_lazyClassNamer * _Nonnull oldOutValue); + +#define RW_COPIED_RO (1<<27) + +struct ObjCClass { + struct ObjCClass * __ptrauth_objc_isa_pointer isa; + struct ObjCClass * __ptrauth_objc_super_pointer superclass; + void *cachePtr; + uintptr_t zero; + uintptr_t data; +}; + +struct ObjCClass_ro { + uint32_t flags; + uint32_t instanceStart; + uint32_t instanceSize; +#ifdef __LP64__ + uint32_t reserved; +#endif + + union { + const uint8_t * ivarLayout; + struct ObjCClass * nonMetaClass; + }; + + const char * name; + struct ObjCMethodList * __ptrauth_objc_method_list_pointer baseMethodList; + struct protocol_list_t * baseProtocols; + const struct ivar_list_t * ivars; + + const uint8_t * weakIvarLayout; + struct property_list_t *baseProperties; +}; + +extern struct ObjCClass OBJC_METACLASS_$_NSObject; +extern struct ObjCClass OBJC_CLASS_$_NSObject; + +extern struct ObjCClass LazyClassName; +extern struct ObjCClass LazyClassName2; + +struct ObjCClass_ro LazyClassNameMetaclass_ro = { + .flags = 1, + .instanceStart = 40, + .instanceSize = 40, + .nonMetaClass = &LazyClassName, +}; + +struct ObjCClass LazyClassNameMetaclass = { + .isa = &OBJC_METACLASS_$_NSObject, + .superclass = &OBJC_METACLASS_$_NSObject, + .cachePtr = &_objc_empty_cache, + .data = (uintptr_t)&LazyClassNameMetaclass_ro, +}; + +struct ObjCClass_ro LazyClassName_ro = { + .instanceStart = 8, + .instanceSize = 8, +}; + +struct ObjCClass LazyClassName = { + .isa = &LazyClassNameMetaclass, + .superclass = &OBJC_CLASS_$_NSObject, + .cachePtr = &_objc_empty_cache, + .data = (uintptr_t)&LazyClassName_ro + 2, +}; + +struct ObjCClass_ro LazyClassName2Metaclass_ro = { + .flags = 1, + .instanceStart = 40, + .instanceSize = 40, + .nonMetaClass = &LazyClassName2, +}; + +struct ObjCClass LazyClassName2Metaclass = { + .isa = &OBJC_METACLASS_$_NSObject, + .superclass = &OBJC_METACLASS_$_NSObject, + .cachePtr = &_objc_empty_cache, + .data = (uintptr_t)&LazyClassName2Metaclass_ro, +}; + +struct ObjCClass_ro LazyClassName2_ro = { + .instanceStart = 8, + .instanceSize = 8, +}; + +struct ObjCClass LazyClassName2 = { + .isa = &LazyClassName2Metaclass, + .superclass = &OBJC_CLASS_$_NSObject, + .cachePtr = &_objc_empty_cache, + .data = (uintptr_t)&LazyClassName2_ro + 2, +}; + +static objc_hook_lazyClassNamer OrigNamer; + +static const char *ClassNamer(Class cls) { + if (cls == (__bridge Class)&LazyClassName) + return "LazyClassName"; + return OrigNamer(cls); +} + +static objc_hook_lazyClassNamer OrigNamer2; + +static const char *ClassNamer2(Class cls) { + if (cls == (__bridge Class)&LazyClassName2) + return "LazyClassName2"; + return OrigNamer2(cls); +} + +__attribute__((section("__DATA,__objc_classlist,regular,no_dead_strip"))) +struct ObjCClass *LazyClassNamePtr = &LazyClassName; +__attribute__((section("__DATA,__objc_classlist,regular,no_dead_strip"))) +struct ObjCClass *LazyClassNamePtr2 = &LazyClassName2; + +int main() { +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunguarded-availability" + objc_setHook_lazyClassNamer(ClassNamer, &OrigNamer); + objc_setHook_lazyClassNamer(ClassNamer2, &OrigNamer2); +#pragma clang diagnostic pop + + printf("%s\n", class_getName([(__bridge id)&LazyClassName class])); + printf("%s\n", class_getName([(__bridge id)&LazyClassName2 class])); +} diff --git a/test/libraryPath.c b/test/libraryPath.c index 40cf7bd..af3151a 100644 --- a/test/libraryPath.c +++ b/test/libraryPath.c @@ -8,11 +8,16 @@ // etc.) then the typical result is a silent failure and we end up testing // /usr/lib/libobjc.A.dylib instead. This test detects when DYLD_LIBRARY_PATH is // set but libobjc isn't loaded from it. -int main() { +int main(int argc __unused, char **argv) { + char *containingDirectory = realpath(dirname(argv[0]), NULL); + testprintf("containingDirectory is %s\n", containingDirectory); + char *dyldLibraryPath = getenv("DYLD_LIBRARY_PATH"); testprintf("DYLD_LIBRARY_PATH is %s\n", dyldLibraryPath); + if (dyldLibraryPath != NULL && strlen(dyldLibraryPath) > 0) { int foundMatch = 0; + int foundNonMatch = 0; dyldLibraryPath = strdup(dyldLibraryPath); @@ -27,6 +32,10 @@ int main() { while ((path = strsep(&cursor, ":"))) { char *resolved = realpath(path, NULL); testprintf("Resolved %s to %s\n", path, resolved); + if (strcmp(resolved, containingDirectory) == 0) { + testprintf("This is equal to our containing directory, ignoring.\n"); + continue; + } testprintf("Comparing %s and %s\n", resolved, info.dli_fname); int comparison = strncmp(resolved, info.dli_fname, strlen(resolved)); free(resolved); @@ -34,11 +43,13 @@ int main() { testprintf("Found a match!\n"); foundMatch = 1; break; + } else { + foundNonMatch = 1; } } - testprintf("Finished searching, foundMatch=%d\n", foundMatch); - testassert(foundMatch); + testprintf("Finished searching, foundMatch=%d foundNonMatch=%d\n", foundMatch, foundNonMatch); + testassert(foundMatch || !foundNonMatch); } succeed(__FILE__); } diff --git a/test/methodCacheLeaks.m b/test/methodCacheLeaks.m index 968bf5a..cb624c0 100644 --- a/test/methodCacheLeaks.m +++ b/test/methodCacheLeaks.m @@ -61,5 +61,10 @@ int main() exit(1); } wait4(pid, NULL, 0, NULL); - printf("objs=%p\n", objs); + + // Clean up. Otherwise leaks can end up seeing this as a leak, oddly enough. + for (int i = 0; i < classCount; i++) { + [objs[i] release]; + } + free(objs); } diff --git a/test/methodListSmall.h b/test/methodListSmall.h index c6f32e2..233e9c0 100644 --- a/test/methodListSmall.h +++ b/test/methodListSmall.h @@ -1,8 +1,8 @@ #include "test.h" struct ObjCClass { - struct ObjCClass *isa; - struct ObjCClass *superclass; + struct ObjCClass * __ptrauth_objc_isa_pointer isa; + struct ObjCClass * __ptrauth_objc_super_pointer superclass; void *cachePtr; uintptr_t zero; struct ObjCClass_ro *data; @@ -19,7 +19,7 @@ struct ObjCClass_ro { const uint8_t * ivarLayout; const char * name; - struct ObjCMethodList * baseMethodList; + struct ObjCMethodList * __ptrauth_objc_method_list_pointer baseMethodList; struct protocol_list_t * baseProtocols; const struct ivar_list_t * ivars; @@ -142,6 +142,8 @@ asm(R"ASM( .asciz "v16@0:8" _MyMethodStretName: .asciz "myMethodStret" +_MyMethodNullTypesName: + .asciz "myMethodNullTypes" _StretType: .asciz "{BigStruct=QQQQQQQ}16@0:8" )ASM"); @@ -157,6 +159,8 @@ asm(R"ASM( .quad _MyMethod3Name _MyMethodStretNameRef: .quad _MyMethodStretName +_MyMethodNullTypesNameRef: + .quad _MyMethodNullTypesName )ASM"); #else asm(R"ASM( @@ -169,6 +173,8 @@ asm(R"ASM( .long _MyMethod3Name _MyMethodStretNameRef: .long _MyMethodStretName +_MyMethodNullTypesNameRef: + .long _MyMethodNullTypesName )ASM"); #endif @@ -182,7 +188,7 @@ asm(R"ASM( .p2align 2 _Foo_methodlistSmall: .long 12 | 0x80000000 - .long 4 + .long 5 .long _MyMethod1NameRef - . .long _BoringMethodType - . @@ -199,6 +205,10 @@ asm(R"ASM( .long _MyMethodStretNameRef - . .long _StretType - . .long _myMethodStret - . + + .long _MyMethodNullTypesNameRef - . + .long 0 + .long _myMethod1 - . )ASM"); struct ObjCClass_ro Foo_ro = { diff --git a/test/methodListSmall.mm b/test/methodListSmall.mm index c10f29d..82f157a 100644 --- a/test/methodListSmall.mm +++ b/test/methodListSmall.mm @@ -68,6 +68,12 @@ void testClass(Class c) { auto *descstret = method_getDescription(mstret); testassert(descstret->name == @selector(myMethodStret)); testassert(descstret->types == method_getTypeEncoding(mstret)); + + Method nullTypeMethod = class_getInstanceMethod(c, @selector(myMethodNullTypes)); + testassert(nullTypeMethod); + testassert(method_getName(nullTypeMethod) == @selector(myMethodNullTypes)); + testassertequal(method_getTypeEncoding(nullTypeMethod), NULL); + testassertequal(method_getImplementation(nullTypeMethod), (IMP)myMethod1); } int main() { diff --git a/test/methodListSmallMutableMemory.mm b/test/methodListSmallMutableMemory.mm deleted file mode 100644 index 9250fea..0000000 --- a/test/methodListSmallMutableMemory.mm +++ /dev/null @@ -1,18 +0,0 @@ -/* -TEST_CFLAGS -std=c++11 -TEST_CRASHES -TEST_RUN_OUTPUT -objc\[\d+\]: CLASS: class 'Foo' 0x[0-9a-fA-F]+ small method list 0x[0-9a-fA-F]+ is not in immutable memory -objc\[\d+\]: HALTED -END -*/ - -#define MUTABLE_METHOD_LIST 1 - -#include "methodListSmall.h" - -int main() { - Class fooClass = (__bridge Class)&FooClass; - [fooClass new]; - fail("Should have crashed"); -} diff --git a/test/nonpointerisa.m b/test/nonpointerisa.m index d7b007f..659aed5 100644 --- a/test/nonpointerisa.m +++ b/test/nonpointerisa.m @@ -14,7 +14,9 @@ # if __x86_64__ # define RC_ONE (1ULL<<56) # elif __arm64__ && __LP64__ -# define RC_ONE (1ULL<<45) +// Quiet the warning about redefining the macro from isa.h. +# undef RC_ONE +# define RC_ONE (objc_debug_isa_magic_value == 1 ? 1ULL<<56 : 1ULL<<45) # elif __ARM_ARCH_7K__ >= 2 || (__arm64__ && !__LP64__) # define RC_ONE (1ULL<<25) # else @@ -29,9 +31,9 @@ void check_raw_pointer(id obj, Class cls) testassert(!NONPOINTER(obj)); uintptr_t isa = ISA(obj); - testassert((Class)isa == cls); - testassert((Class)(isa & objc_debug_isa_class_mask) == cls); - testassert((Class)(isa & ~objc_debug_isa_class_mask) == 0); + testassertequal(ptrauth_strip((void *)isa, ptrauth_key_process_independent_data), (void *)cls); + testassertequal((Class)(isa & objc_debug_isa_class_mask), cls); + testassertequal(ptrauth_strip((void *)(isa & ~objc_debug_isa_class_mask), ptrauth_key_process_independent_data), 0); CFRetain(obj); testassert(ISA(obj) == isa); @@ -80,37 +82,37 @@ int main() void check_nonpointer(id obj, Class cls) { - testassert(object_getClass(obj) == cls); + testassertequal(object_getClass(obj), cls); testassert(NONPOINTER(obj)); uintptr_t isa = ISA(obj); if (objc_debug_indexed_isa_magic_mask != 0) { // Indexed isa. - testassert((isa & objc_debug_indexed_isa_magic_mask) == objc_debug_indexed_isa_magic_value); + testassertequal((isa & objc_debug_indexed_isa_magic_mask), objc_debug_indexed_isa_magic_value); testassert((isa & ~objc_debug_indexed_isa_index_mask) != 0); uintptr_t index = (isa & objc_debug_indexed_isa_index_mask) >> objc_debug_indexed_isa_index_shift; testassert(index < objc_indexed_classes_count); - testassert(objc_indexed_classes[index] == cls); + testassertequal(objc_indexed_classes[index], cls); } else { // Packed isa. - testassert((Class)(isa & objc_debug_isa_class_mask) == cls); + testassertequal((Class)(isa & objc_debug_isa_class_mask), cls); testassert((Class)(isa & ~objc_debug_isa_class_mask) != 0); - testassert((isa & objc_debug_isa_magic_mask) == objc_debug_isa_magic_value); + testassertequal((isa & objc_debug_isa_magic_mask), objc_debug_isa_magic_value); } CFRetain(obj); - testassert(ISA(obj) == isa + RC_ONE); - testassert([obj retainCount] == 2); + testassertequal(ISA(obj), isa + RC_ONE); + testassertequal([obj retainCount], 2); [obj retain]; - testassert(ISA(obj) == isa + RC_ONE*2); - testassert([obj retainCount] == 3); + testassertequal(ISA(obj), isa + RC_ONE*2); + testassertequal([obj retainCount], 3); CFRelease(obj); - testassert(ISA(obj) == isa + RC_ONE); - testassert([obj retainCount] == 2); + testassertequal(ISA(obj), isa + RC_ONE); + testassertequal([obj retainCount], 2); [obj release]; - testassert(ISA(obj) == isa); - testassert([obj retainCount] == 1); + testassertequal(ISA(obj), isa); + testassertequal([obj retainCount], 1); } @@ -152,14 +154,21 @@ int main() # if !OBJC_HAVE_NONPOINTER_ISA || !OBJC_HAVE_PACKED_NONPOINTER_ISA || OBJC_HAVE_INDEXED_NONPOINTER_ISA # error wrong # endif - testassert(objc_debug_isa_class_mask == (uintptr_t)&objc_absolute_packed_isa_class_mask); + void *absoluteMask = (void *)&objc_absolute_packed_isa_class_mask; +#if __has_feature(ptrauth_calls) + absoluteMask = ptrauth_strip(absoluteMask, ptrauth_key_process_independent_data); +#endif + // absoluteMask should "cover" objc_debug_isa_class_mask + testassert((objc_debug_isa_class_mask & (uintptr_t)absoluteMask) == objc_debug_isa_class_mask); + // absoluteMask should only possibly differ in the high bits + testassert((objc_debug_isa_class_mask & 0xffff) == ((uintptr_t)absoluteMask & 0xffff)); // Indexed isa variables DO NOT exist on packed-isa platforms testassert(!dlsym(RTLD_DEFAULT, "objc_absolute_indexed_isa_magic_mask")); testassert(!dlsym(RTLD_DEFAULT, "objc_absolute_indexed_isa_magic_value")); testassert(!dlsym(RTLD_DEFAULT, "objc_absolute_indexed_isa_index_mask")); testassert(!dlsym(RTLD_DEFAULT, "objc_absolute_indexed_isa_index_shift")); - + #elif SUPPORT_INDEXED_ISA # if !OBJC_HAVE_NONPOINTER_ISA || OBJC_HAVE_PACKED_NONPOINTER_ISA || !OBJC_HAVE_INDEXED_NONPOINTER_ISA # error wrong @@ -175,7 +184,7 @@ int main() #else # error unknown nonpointer isa format #endif - + testprintf("Isa with index\n"); id index_o = [Fake_OS_object new]; check_nonpointer(index_o, [Fake_OS_object class]); diff --git a/test/preopt-caches.entitlements b/test/preopt-caches.entitlements new file mode 100644 index 0000000..bc4acf2 --- /dev/null +++ b/test/preopt-caches.entitlements @@ -0,0 +1,12 @@ + + + + + com.apple.springboard-ui.client + + com.apple.security.system-groups + + systemgroup.com.apple.powerlog + + + diff --git a/test/preopt-caches.mm b/test/preopt-caches.mm new file mode 100644 index 0000000..7aec275 --- /dev/null +++ b/test/preopt-caches.mm @@ -0,0 +1,380 @@ +/* +TEST_ENTITLEMENTS preopt-caches.entitlements +TEST_CONFIG OS=iphoneos MEM=mrc +TEST_BUILD + mkdir -p $T{OBJDIR} + /usr/sbin/dtrace -h -s $DIR/../runtime/objc-probes.d -o $T{OBJDIR}/objc-probes.h + $C{COMPILE} $DIR/preopt-caches.mm -std=gnu++17 -isystem $C{SDK_PATH}/System/Library/Frameworks/System.framework/PrivateHeaders -I$T{OBJDIR} -ldsc -o preopt-caches.exe +END +*/ +// +// check_preopt_caches.m +// check-preopt-caches +// +// Created by Thomas Deniau on 11/06/2020. +// + +#define TEST_CALLS_OPERATOR_NEW + +#include "test-defines.h" +#include "../runtime/objc-private.h" +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "test.h" + +int validate_dylib_in_forked_process(const char * const toolPath, const char * const dylib) +{ + int out_pipe[2] = {-1}; + int err_pipe[2] = {-1}; + int exit_code = -1; + pid_t pid = 0; + int rval = 0; + + std::string child_stdout; + std::string child_stderr; + + posix_spawn_file_actions_t actions = NULL; + const char * const args[] = {toolPath, dylib, NULL}; + int ret = 0; + + if (pipe(out_pipe)) { + exit(3); + } + + if (pipe(err_pipe)) { + exit(3); + } + + //Do-si-do the FDs + posix_spawn_file_actions_init(&actions); + posix_spawn_file_actions_addclose(&actions, out_pipe[0]); + posix_spawn_file_actions_addclose(&actions, err_pipe[0]); + posix_spawn_file_actions_adddup2(&actions, out_pipe[1], 1); + posix_spawn_file_actions_adddup2(&actions, err_pipe[1], 2); + posix_spawn_file_actions_addclose(&actions, out_pipe[1]); + posix_spawn_file_actions_addclose(&actions, err_pipe[1]); + + // Fork so that we can dlopen the dylib in a clean context + ret = posix_spawnp(&pid, args[0], &actions, NULL, (char * const *)args, NULL); + + if (ret != 0) { + fail("posix_spawn for %s failed: returned %d, %s\n", dylib, ret, strerror(ret)); + exit(3); + } + + posix_spawn_file_actions_destroy(&actions); + close(out_pipe[1]); + close(err_pipe[1]); + + std::string buffer(4096,' '); + std::vector plist = { {out_pipe[0],POLLIN,0}, {err_pipe[0],POLLIN,0} }; + while (( (rval = poll(&plist[0],(nfds_t)plist.size(), 100000)) > 0 ) || ((rval < 0) && (errno == EINTR))) { + if (rval < 0) { + // EINTR + continue; + } + + ssize_t bytes_read = 0; + + if (plist[0].revents&(POLLERR|POLLHUP) || plist[1].revents&(POLLERR|POLLHUP)) { + bytes_read = read(out_pipe[0], &buffer[0], buffer.length()); + bytes_read = read(err_pipe[0], &buffer[0], buffer.length()); + break; + } + + if (plist[0].revents&POLLIN) { + bytes_read = read(out_pipe[0], &buffer[0], buffer.length()); + child_stdout += buffer.substr(0, static_cast(bytes_read)); + } + else if ( plist[1].revents&POLLIN ) { + bytes_read = read(err_pipe[0], &buffer[0], buffer.length()); + child_stderr += buffer.substr(0, static_cast(bytes_read)); + } + else break; // nothing left to read + + plist[0].revents = 0; + plist[1].revents = 0; + } + if (rval == 0) { + // Early timeout so try to clean up. + fail("Failed to validate dylib %s: timeout!\n", dylib); + return 1; + } + + + if (err_pipe[0] != -1) { + close(err_pipe[0]); + } + + if (out_pipe[0] != -1) { + close(out_pipe[0]); + } + + if (pid != 0) { + if (waitpid(pid, &exit_code, 0) < 0) { + fail("Could not wait for PID %d (dylib %s): err %s\n", pid, dylib, strerror(errno)); + } + + if (!WIFEXITED(exit_code)) { + fail("PID %d (%s) did not exit: %d. stdout: %s\n stderr: %s\n", pid, dylib, exit_code, child_stdout.c_str(), child_stderr.c_str()); + } + if (WEXITSTATUS(exit_code) != 0) { + fail("Failed to validate dylib %s\nstdout: %s\nstderr: %s\n", dylib, child_stdout.c_str(), child_stderr.c_str()); + } + } + + testprintf("%s", child_stdout.c_str()); + + return 0; +} + +bool check_class(Class cls, unsigned & cacheCount) { + // printf("%s %s\n", class_getName(cls), class_isMetaClass(cls) ? "(metaclass)" : ""); + + // For the initialization of the cache so that we setup the constant cache if any + class_getMethodImplementation(cls, @selector(initialize)); + + if (objc_cache_isConstantOptimizedCache(&(cls->cache), true, (uintptr_t)&_objc_empty_cache)) { + cacheCount++; + // printf("%s has a preopt cache\n", class_getName(cls)); + + // Make the union of all selectors until the preopt fallback class + const class_ro_t * fallback = ((const objc_class *) objc_cache_preoptFallbackClass(&(cls->cache)))->data()->ro(); + + std::unordered_map methods; + + Method *methodList; + unsigned count; + Class currentClass = cls; + unsigned dynamicCount = 0; + while (currentClass->data()->ro() != fallback) { + methodList = class_copyMethodList(currentClass, &count); + // printf("%d methods in method list for %s\n", count, class_getName(currentClass)); + for (unsigned i = 0 ; i < count ; i++) { + SEL sel = method_getName(methodList[i]); + if (methods.find(sel) == methods.end()) { + const char *name = sel_getName(sel); + // printf("[dynamic] %s -> %p\n", name, method_getImplementation(methodList[i])); + methods[sel] = ptrauth_strip(method_getImplementation(methodList[i]), ptrauth_key_function_pointer); + if ( (currentClass == cls) || + ( (strcmp(name, ".cxx_construct") != 0) + && (strcmp(name, ".cxx_destruct") != 0))) { + dynamicCount++; + } + } + } + if (count > 0) { + free(methodList); + } + currentClass = class_getSuperclass(currentClass); + } + + // Check we have an equality between the two caches + + // Count the methods in the preopt cache + unsigned preoptCacheCount = 0; + unsigned capacity = objc_cache_preoptCapacity(&(cls->cache)); + const preopt_cache_entry_t *buckets = objc_cache_preoptCache(&(cls->cache))->entries; + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wcast-of-sel-type" + const uint8_t *selOffsetsBase = (const uint8_t*)@selector(🤯); +#pragma clang diagnostic pop + for (unsigned i = 0 ; i < capacity ; i++) { + uint32_t selOffset = buckets[i].sel_offs; + if (selOffset != 0xFFFFFFFF) { + SEL sel = (SEL)(selOffsetsBase + selOffset); + IMP imp = (IMP)((uint8_t*)cls - buckets[i].imp_offs); + if (methods.find(sel) == methods.end()) { + fail("ERROR: %s: %s not found in dynamic method list\n", class_getName(cls), sel_getName(sel)); + return false; + } + IMP dynamicImp = methods.at(sel); + // printf("[static] %s -> %p\n", sel_getName(sel), imp); + if (imp != dynamicImp) { + fail("ERROR: %s: %s has different implementations %p vs %p in static and dynamic caches", class_getName(cls), sel_getName(sel), imp, dynamicImp); + return false; + } + preoptCacheCount++; + } + } + + if (preoptCacheCount != dynamicCount) { + testwarn("Methods in preopt cache:\n"); + + for (unsigned i = 0 ; i < capacity ; i++) { + uint32_t selOffset = buckets[i].sel_offs; + if (selOffset != 0xFFFFFFFF) { + SEL sel = (SEL)(selOffsetsBase + selOffset); + testwarn("%s\n", sel_getName(sel)); + } + } + + testwarn("Methods in dynamic cache:\n"); + + for (const auto & [sel, imp] : methods) { + testwarn("%s\n", sel_getName(sel)); + } + + fail("ERROR: %s's preoptimized cache is missing some methods\n", class_getName(cls)); + + return false; + } + + } else { + // printf("%s does NOT have a preopt cache\n", class_getName(cls)); + } + + return true; +} + +bool check_library(const char *path) { + std::set blacklistedClasses { + "PNPWizardScratchpadInkView", // Can only be +initialized on Pencil-capable devices + "CACDisplayManager", // rdar://64929282 (CACDisplayManager does layout in +initialize!) + }; + + testprintf("Checking %s… ", path); + + __unused void *lib = dlopen(path, RTLD_NOW); + extern uint32_t _dyld_image_count(void) __OSX_AVAILABLE_STARTING(__MAC_10_1, __IPHONE_2_0); + unsigned outCount = 0; + + // Realize all classes first. + Class *allClasses = objc_copyClassList(&outCount); + if (allClasses != NULL) { + free(allClasses); + } + + allClasses = objc_copyClassesForImage(path, &outCount); + if (allClasses != NULL) { + unsigned classCount = 0; + unsigned cacheCount = 0; + + for (const Class * clsPtr = allClasses ; *clsPtr != nil ; clsPtr++) { + classCount++; + Class cls = *clsPtr; + + if (blacklistedClasses.find(class_getName(cls)) != blacklistedClasses.end()) { + continue; + } + + if (!check_class(cls, cacheCount)) { + return false; + } + + if (!class_isMetaClass(cls)) { + if (!check_class(object_getClass(cls), cacheCount)) { + return false; + } + } + } + testprintf("checked %d caches in %d classes\n", cacheCount, classCount); + free(allClasses); + } else { + testprintf("could not find %s or no class names inside\n", path); + } + + return true; +} + +size_t size_of_shared_cache_with_uuid(uuid_t uuid) { + DIR* dfd = opendir(IPHONE_DYLD_SHARED_CACHE_DIR); + if (!dfd) { + fail("Error: unable to open shared cache dir %s\n", + IPHONE_DYLD_SHARED_CACHE_DIR); + exit(1); + } + + uint64_t shared_cache_size = 0; + + struct dirent *dp; + while ((dp = readdir(dfd))) { + char full_filename[512]; + snprintf(full_filename, sizeof(full_filename), "%s%s", + IPHONE_DYLD_SHARED_CACHE_DIR, dp->d_name); + + struct stat stat_buf; + if (stat(full_filename, &stat_buf) != 0) + continue; + + if ((stat_buf.st_mode & S_IFMT) == S_IFDIR) + continue; + + int fd = open(full_filename, O_RDONLY); + if (fd < 0) { + fprintf(stderr, "Error: unable to open file %s\n", full_filename); + continue; + } + + struct dyld_cache_header header; + if (read(fd, &header, sizeof(header)) != sizeof(header)) { + fprintf(stderr, "Error: unable to read dyld shared cache header from %s\n", + full_filename); + close(fd); + continue; + } + + if (uuid_compare(header.uuid, uuid) == 0) { + shared_cache_size = stat_buf.st_size; + break; + } + } + + closedir(dfd); + + return shared_cache_size; +} + +int main (int argc, const char * argv[]) +{ + if (argc == 1) { + int err = 0; + dyld_process_info process_info = _dyld_process_info_create(mach_task_self(), 0, &err); + if (NULL == process_info) { + mach_error("_dyld_process_info_create", err); + fail("_dyld_process_info_create"); + return 2; + } + dyld_process_cache_info cache_info; + _dyld_process_info_get_cache(process_info, &cache_info); + + __block std::set dylibsSet; + size_t size = size_of_shared_cache_with_uuid(cache_info.cacheUUID); + dyld_shared_cache_iterate((void*)cache_info.cacheBaseAddress, (uint32_t)size, ^(const dyld_shared_cache_dylib_info* dylibInfo, __unused const dyld_shared_cache_segment_info* segInfo) { + if (dylibInfo->isAlias) return; + std::string path(dylibInfo->path); + dylibsSet.insert(path); + }); + std::vector dylibs(dylibsSet.begin(), dylibsSet.end()); + + dispatch_apply(dylibs.size(), DISPATCH_APPLY_AUTO, ^(size_t idx) { + validate_dylib_in_forked_process(argv[0], dylibs[idx].c_str()); + }); + } else { + const char *libraryName = argv[1]; + if (!check_library(libraryName)) { + fail("checking library %s\n", libraryName); + return 1; + } + } + + succeed(__FILE__); + return 0; +} diff --git a/test/protocolSmall.m b/test/protocolSmall.m new file mode 100644 index 0000000..a3f6fa6 --- /dev/null +++ b/test/protocolSmall.m @@ -0,0 +1,91 @@ +// TEST_CFLAGS -framework Foundation +// need Foundation to get NSObject compatibility additions for class Protocol +// because ARC calls [protocol retain] +/* +TEST_BUILD_OUTPUT +.*protocolSmall.m:\d+:\d+: warning: cannot find protocol definition for 'SmallProto' +.*protocolSmall.m:\d+:\d+: note: protocol 'SmallProto' has no definition +END +*/ + +#include "test.h" +#include "testroot.i" +#include + +struct MethodListOneEntry { + uint32_t entSizeAndFlags; + uint32_t count; + SEL name; + const char *types; + void *imp; +}; + +struct SmallProtoStructure { + Class isa; + const char *mangledName; + struct protocol_list_t *protocols; + void *instanceMethods; + void *classMethods; + void *optionalInstanceMethods; + void *optionalClassMethods; + void *instanceProperties; + uint32_t size; // sizeof(protocol_t) + uint32_t flags; +}; + +struct MethodListOneEntry SmallProtoMethodList = { + .entSizeAndFlags = 3 * sizeof(void *), + .count = 1, + .name = NULL, + .types = "v@:", + .imp = NULL, +}; + +struct SmallProtoStructure SmallProtoData + __asm__("__OBJC_PROTOCOL_$_SmallProto") + = { + .mangledName = "SmallProto", + .instanceMethods = &SmallProtoMethodList, + .size = sizeof(struct SmallProtoStructure), +}; + +void *SmallProtoListEntry + __attribute__((section("__DATA,__objc_protolist,coalesced,no_dead_strip"))) + = &SmallProtoData; + +@protocol SmallProto; +@protocol NormalProto +- (void)protoMethod; +@end + +@interface C: TestRoot @end +@implementation C +- (void)protoMethod {} +@end + +int main() +{ + // Fix up the method list selector by hand, getting the compiler to generate a + // proper selref as a compile-time constant is a pain. + SmallProtoMethodList.name = @selector(protoMethod); + unsigned protoCount; + + Protocol * __unsafe_unretained *protos = class_copyProtocolList([C class], &protoCount); + for (unsigned i = 0; i < protoCount; i++) { + testprintf("Checking index %u protocol %p\n", i, protos[i]); + const char *name = protocol_getName(protos[i]); + testprintf("Name is %s\n", name); + testassert(strcmp(name, "SmallProto") == 0 || strcmp(name, "NormalProto") == 0); + + objc_property_t *classProperties = protocol_copyPropertyList2(protos[i], NULL, YES, NO); + testassert(classProperties == NULL); + + struct objc_method_description desc = protocol_getMethodDescription(protos[i], @selector(protoMethod), YES, YES); + testprintf("Protocol protoMethod name is %s types are %s\n", desc.name, desc.types); + testassert(desc.name == @selector(protoMethod)); + testassert(desc.types[0] == 'v'); + } + free(protos); + + succeed(__FILE__); +} diff --git a/test/readClassPair.m b/test/readClassPair.m index 80313b2..ebc8587 100644 --- a/test/readClassPair.m +++ b/test/readClassPair.m @@ -48,10 +48,16 @@ int main() // Read a non-root class. testassert(!objc_getClass("Sub")); - extern intptr_t OBJC_CLASS_$_Sub[OBJC_MAX_CLASS_SIZE/sizeof(void*)]; + // Clang assumes too much alignment on this by default (rdar://problem/60881608), + // so tell it that it's only as aligned as an intptr_t. + extern _Alignas(intptr_t) intptr_t OBJC_CLASS_$_Sub[OBJC_MAX_CLASS_SIZE/sizeof(void*)]; // Make a duplicate of class Sub for use later. intptr_t Sub2_buf[OBJC_MAX_CLASS_SIZE/sizeof(void*)]; memcpy(Sub2_buf, &OBJC_CLASS_$_Sub, sizeof(Sub2_buf)); + // Re-sign the isa and super pointers in the new location. + ((Class __ptrauth_objc_isa_pointer *)(void *)Sub2_buf)[0] = ((Class __ptrauth_objc_isa_pointer *)(void *)&OBJC_CLASS_$_Sub)[0]; + ((Class __ptrauth_objc_super_pointer *)(void *)Sub2_buf)[1] = ((Class __ptrauth_objc_super_pointer *)(void *)&OBJC_CLASS_$_Sub)[1]; + Class Sub = objc_readClassPair((__bridge Class)(void*)&OBJC_CLASS_$_Sub, &ii); testassert(Sub); diff --git a/test/rr-sidetable.m b/test/rr-sidetable.m index daa4090..ac3606a 100644 --- a/test/rr-sidetable.m +++ b/test/rr-sidetable.m @@ -9,7 +9,7 @@ #include "test.h" #import -#define OBJECTS 1 +#define OBJECTS 10 #define LOOPS 256 #define THREADS 16 #if __x86_64__ diff --git a/test/runtime.m b/test/runtime.m index 50bd68c..4e22606 100644 --- a/test/runtime.m +++ b/test/runtime.m @@ -221,6 +221,13 @@ int main() testassert(strcmp(class_getName([SwiftV1Class3 class]), class_getName(object_getClass([SwiftV1Class3 class]))) == 0); testassert(strcmp(class_getName([SwiftV1Class4 class]), class_getName(object_getClass([SwiftV1Class4 class]))) == 0); + testassert(!_class_isSwift([TestRoot class])); + testassert(!_class_isSwift([Sub class])); + testassert(_class_isSwift([SwiftV1Class class])); + testassert(_class_isSwift([SwiftV1Class2 class])); + testassert(_class_isSwift([SwiftV1Class3 class])); + testassert(_class_isSwift([SwiftV1Class4 class])); + succeed(__FILE__); } diff --git a/test/setAssociatedObjectHook.m b/test/setAssociatedObjectHook.m index e244d5c..97f78c1 100644 --- a/test/setAssociatedObjectHook.m +++ b/test/setAssociatedObjectHook.m @@ -1,47 +1,46 @@ -// TEST_CONFIG +/* + TEST_CONFIG MEM=mrc + TEST_ENV OBJC_DISABLE_NONPOINTER_ISA=YES +*/ #include "test.h" #include "testroot.i" -id sawObject; -const void *sawKey; -id sawValue; -objc_AssociationPolicy sawPolicy; +bool hasAssociations = false; -objc_hook_setAssociatedObject originalSetAssociatedObject; +@interface TestRoot (AssocHooks) +@end -void hook(id _Nonnull object, const void * _Nonnull key, id _Nullable value, objc_AssociationPolicy policy) { - sawObject = object; - sawKey = key; - sawValue = value; - sawPolicy = policy; - originalSetAssociatedObject(object, key, value, policy); +@implementation TestRoot (AssocHooks) + +- (void)_noteAssociatedObjects { + hasAssociations = true; +} + +// -_noteAssociatedObjects is currently limited to raw-isa custom-rr to avoid overhead +- (void) release { } +@end + int main() { id obj = [TestRoot new]; id value = [TestRoot new]; const void *key = "key"; objc_setAssociatedObject(obj, key, value, OBJC_ASSOCIATION_RETAIN); - testassert(sawObject == nil); - testassert(sawKey == nil); - testassert(sawValue == nil); - testassert(sawPolicy == 0); + testassert(hasAssociations == true); id out = objc_getAssociatedObject(obj, key); testassert(out == value); - objc_setHook_setAssociatedObject(hook, &originalSetAssociatedObject); - + hasAssociations = false; key = "key2"; objc_setAssociatedObject(obj, key, value, OBJC_ASSOCIATION_RETAIN); - testassert(sawObject == obj); - testassert(sawKey == key); - testassert(sawValue == value); - testassert(sawPolicy == OBJC_ASSOCIATION_RETAIN); + testassert(hasAssociations == false); //only called once + out = objc_getAssociatedObject(obj, key); testassert(out == value); succeed(__FILE__); -} \ No newline at end of file +} diff --git a/test/swift-class-def.m b/test/swift-class-def.m index 6bc2d05..9ca2f16 100644 --- a/test/swift-class-def.m +++ b/test/swift-class-def.m @@ -15,9 +15,15 @@ #if __has_feature(ptrauth_calls) # define SIGNED_METHOD_LIST_IMP "@AUTH(ia,0,addr) " # define SIGNED_STUB_INITIALIZER "@AUTH(ia,0xc671,addr) " +# define SIGNED_METHOD_LIST "@AUTH(da,0xC310,addr) " +# define SIGNED_ISA "@AUTH(da, 0x6AE1, addr) " +# define SIGNED_SUPER "@AUTH(da, 0xB5AB, addr) " #else # define SIGNED_METHOD_LIST_IMP # define SIGNED_STUB_INITIALIZER +# define SIGNED_METHOD_LIST +# define SIGNED_ISA +# define SIGNED_SUPER #endif #define str(x) #x @@ -41,8 +47,8 @@ @interface SwiftSub : SwiftSuper @end ".section __DATA,__objc_data \n" \ ".align 3 \n" \ "_OBJC_CLASS_$_" #name ": \n" \ - PTR "_OBJC_METACLASS_$_" #name "\n" \ - PTR "_OBJC_CLASS_$_" #superclass "\n" \ + PTR "_OBJC_METACLASS_$_" #name SIGNED_ISA "\n" \ + PTR "_OBJC_CLASS_$_" #superclass SIGNED_SUPER "\n" \ PTR "__objc_empty_cache \n" \ PTR "0 \n" \ PTR "L_" #name "_ro + 2 \n" \ @@ -82,8 +88,8 @@ @interface SwiftSub : SwiftSuper @end PTR "0 \n" \ \ "_OBJC_METACLASS_$_" #name ": \n" \ - PTR "_OBJC_METACLASS_$_" #superclass "\n" \ - PTR "_OBJC_METACLASS_$_" #superclass "\n" \ + PTR "_OBJC_METACLASS_$_" #superclass SIGNED_ISA "\n" \ + PTR "_OBJC_METACLASS_$_" #superclass SIGNED_SUPER "\n" \ PTR "__objc_empty_cache \n" \ PTR "0 \n" \ PTR "L_" #name "_meta_ro \n" \ @@ -123,7 +129,7 @@ @interface SwiftSub : SwiftSuper @end ONLY_LP64(".long 0 \n") \ PTR "0 \n" \ PTR "L_" #name "_name \n" \ - PTR "L_" #name "_methods \n" \ + PTR "L_" #name "_methods" SIGNED_METHOD_LIST "\n" \ PTR "0 \n" \ PTR "L_" #name "_ivars \n" \ PTR "0 \n" \ @@ -137,7 +143,7 @@ @interface SwiftSub : SwiftSuper @end ONLY_LP64(".long 0 \n") \ PTR "0 \n" \ PTR "L_" #name "_name \n" \ - PTR "L_" #name "_meta_methods \n" \ + PTR "L_" #name "_meta_methods" SIGNED_METHOD_LIST "\n" \ PTR "0 \n" \ PTR "0 \n" \ PTR "0 \n" \ diff --git a/test/swiftMetadataInitializerRealloc.m b/test/swiftMetadataInitializerRealloc.m index c50d1dc..9e72211 100644 --- a/test/swiftMetadataInitializerRealloc.m +++ b/test/swiftMetadataInitializerRealloc.m @@ -65,6 +65,9 @@ Class initSub(Class cls, void *arg) // Example: rdar://problem/50707074 Class HeapSwiftSub = (Class)malloc(OBJC_MAX_CLASS_SIZE); memcpy(HeapSwiftSub, RawRealSwiftSub, OBJC_MAX_CLASS_SIZE); + // Re-sign the isa and super pointers in the new location. + ((Class __ptrauth_objc_isa_pointer *)(void *)HeapSwiftSub)[0] = ((Class __ptrauth_objc_isa_pointer *)(void *)RawRealSwiftSub)[0]; + ((Class __ptrauth_objc_super_pointer *)(void *)HeapSwiftSub)[1] = ((Class __ptrauth_objc_super_pointer *)(void *)RawRealSwiftSub)[1]; testprintf("initSub beginning _objc_realizeClassFromSwift\n"); _objc_realizeClassFromSwift(HeapSwiftSub, cls); diff --git a/test/taggedPointers.m b/test/taggedPointers.m index 76f1617..490838b 100644 --- a/test/taggedPointers.m +++ b/test/taggedPointers.m @@ -295,6 +295,22 @@ void testGenericTaggedPointer(objc_tag_index_t tag, Class cls) RELEASE_VAR(w); } +#if OBJC_SPLIT_TAGGED_POINTERS +void testConstantTaggedPointerRoundTrip(void *ptr) +{ + uintptr_t tagged = (uintptr_t)ptr | objc_debug_constant_cfstring_tag_bits; + void *untagged = _objc_getTaggedPointerRawPointerValue((void *)tagged); + testassert(ptr == untagged); +} + +void testConstantTaggedPointers(void) +{ + testConstantTaggedPointerRoundTrip(0); + testConstantTaggedPointerRoundTrip((void *)sizeof(void *)); + testConstantTaggedPointerRoundTrip((void *)(MACH_VM_MAX_ADDRESS - sizeof(void *))); +} +#endif + int main() { testassert(objc_debug_taggedpointer_mask != 0); @@ -336,6 +352,10 @@ int main() objc_getClass("TaggedNSObjectSubclass")); testGenericTaggedPointer(OBJC_TAG_NSManagedObjectID, objc_getClass("TaggedNSObjectSubclass")); + +#if OBJC_SPLIT_TAGGED_POINTERS + testConstantTaggedPointers(); +#endif } POP_POOL; succeed(__FILE__); diff --git a/test/taggedPointersTagObfuscationDisabled.m b/test/taggedPointersTagObfuscationDisabled.m index a3aad8b..e9fee7d 100644 --- a/test/taggedPointersTagObfuscationDisabled.m +++ b/test/taggedPointersTagObfuscationDisabled.m @@ -14,7 +14,13 @@ int main() int main() { - testassert(_objc_getTaggedPointerTag((void *)1) == 0); +#if OBJC_SPLIT_TAGGED_POINTERS + void *obj = (void *)0; +#else + void *obj = (void *)1; +#endif + + testassert(_objc_getTaggedPointerTag(obj) == 0); succeed(__FILE__); } diff --git a/test/test-defines.h b/test/test-defines.h new file mode 100644 index 0000000..0a74274 --- /dev/null +++ b/test/test-defines.h @@ -0,0 +1 @@ +#define TEST_OVERRIDES_NEW 1 diff --git a/test/test.h b/test/test.h index 4ae8137..33f223a 100644 --- a/test/test.h +++ b/test/test.h @@ -15,7 +15,8 @@ #include #if __cplusplus #include -using namespace std; +using std::atomic_int; +using std::memory_order_relaxed; #else #include #endif @@ -83,6 +84,40 @@ static inline void fail(const char *msg, ...) #define __testassert(cond, file, line) \ (fail("failed assertion '%s' at %s:%u", cond, __FILE__, __LINE__)) +static inline char *hexstring(uint8_t *data, size_t size) +{ + char *str; + switch (size) { + case sizeof(unsigned long long): + asprintf(&str, "%016llx", *(unsigned long long *)data); + break; + case sizeof(unsigned int): + asprintf(&str, "%08x", *(unsigned int*)data); + break; + case sizeof(uint16_t): + asprintf(&str, "%04x", *(uint16_t *)data); + break; + default: + str = (char *)malloc(size * 2 + 1); + for (size_t i = 0; i < size; i++) { + sprintf(str + i, "%02x", data[i]); + } + } + return str; +} + +static inline void failnotequal(uint8_t *lhs, size_t lhsSize, uint8_t *rhs, size_t rhsSize, const char *lhsStr, const char *rhsStr, const char *file, unsigned line) +{ + fprintf(stderr, "BAD: failed assertion '%s != %s' (0x%s != 0x%s) at %s:%u\n", lhsStr, rhsStr, hexstring(lhs, lhsSize), hexstring(rhs, rhsSize), file, line); + exit(1); +} + +#define testassertequal(lhs, rhs) do {\ + __typeof__(lhs) __lhs = lhs; \ + __typeof__(rhs) __rhs = rhs; \ + if ((lhs) != (rhs)) failnotequal((uint8_t *)&__lhs, sizeof(__lhs), (uint8_t *)&__rhs, sizeof(__rhs), #lhs, #rhs, __FILE__, __LINE__); \ +} while(0) + /* time-sensitive assertion, disabled under valgrind */ #define timecheck(name, time, fast, slow) \ if (getenv("VALGRIND") && 0 != strcmp(getenv("VALGRIND"), "NO")) { \ @@ -208,17 +243,20 @@ static inline void testonthread(__unsafe_unretained testblock_t code) `#define TEST_CALLS_OPERATOR_NEW` before including test.h. */ #if __cplusplus && !defined(TEST_CALLS_OPERATOR_NEW) +#if !defined(TEST_OVERRIDES_NEW) +#define TEST_OVERRIDES_NEW 1 +#endif #pragma clang diagnostic push #pragma clang diagnostic ignored "-Winline-new-delete" #import -inline void* operator new(std::size_t) throw (std::bad_alloc) { fail("called global operator new"); } -inline void* operator new[](std::size_t) throw (std::bad_alloc) { fail("called global operator new[]"); } -inline void* operator new(std::size_t, const std::nothrow_t&) throw() { fail("called global operator new(nothrow)"); } -inline void* operator new[](std::size_t, const std::nothrow_t&) throw() { fail("called global operator new[](nothrow)"); } -inline void operator delete(void*) throw() { fail("called global operator delete"); } -inline void operator delete[](void*) throw() { fail("called global operator delete[]"); } -inline void operator delete(void*, const std::nothrow_t&) throw() { fail("called global operator delete(nothrow)"); } -inline void operator delete[](void*, const std::nothrow_t&) throw() { fail("called global operator delete[](nothrow)"); } +inline void* operator new(std::size_t) { fail("called global operator new"); } +inline void* operator new[](std::size_t) { fail("called global operator new[]"); } +inline void* operator new(std::size_t, const std::nothrow_t&) noexcept(true) { fail("called global operator new(nothrow)"); } +inline void* operator new[](std::size_t, const std::nothrow_t&) noexcept(true) { fail("called global operator new[](nothrow)"); } +inline void operator delete(void*) noexcept(true) { fail("called global operator delete"); } +inline void operator delete[](void*) noexcept(true) { fail("called global operator delete[]"); } +inline void operator delete(void*, const std::nothrow_t&) noexcept(true) { fail("called global operator delete(nothrow)"); } +inline void operator delete[](void*, const std::nothrow_t&) noexcept(true) { fail("called global operator delete[](nothrow)"); } #pragma clang diagnostic pop #endif @@ -301,7 +339,7 @@ static inline void leak_mark(void) leak_dump_heap("HEAP AT leak_check"); \ } \ inuse = leak_inuse(); \ - if (inuse > _leak_start + n) { \ + if (inuse > _leak_start + (n)) { \ fprintf(stderr, "BAD: %zu bytes leaked at %s:%u " \ "(try LEAK_HEAP and HANG_ON_LEAK to debug)\n", \ inuse - _leak_start, __FILE__, __LINE__); \ diff --git a/test/test.pl b/test/test.pl index 46c3d03..88221aa 100755 --- a/test/test.pl +++ b/test/test.pl @@ -6,6 +6,16 @@ use strict; use File::Basename; +use Config; +my $supportsParallelBuilds = $Config{useithreads}; + +if ($supportsParallelBuilds) { + require threads; + import threads; + require Thread::Queue; + import Thread::Queue; +} + # We use encode_json() to write BATS plist files. # JSON::PP does not exist on iOS devices, but we need not write plists there. # So we simply load JSON:PP if it exists. @@ -13,6 +23,13 @@ JSON::PP->import(); } +# iOS also doesn't have Text::Glob. We don't need it there. +my $has_match_glob = 0; +if (eval { require Text::Glob; 1; }) { + Text::Glob->import(); + $has_match_glob = 1; +} + chdir dirname $0; chomp (my $DIR = `pwd`); @@ -31,6 +48,8 @@ ARCH= OS=[sdk version][-[-]] ROOT=/path/to/project.roots/ + HOST= + DEVICE= CC= @@ -44,6 +63,8 @@ BATS=0|1 (build for and/or run in BATS?) BUILD_SHARED_CACHE=0|1 (build a dyld shared cache with the root and test against that) DYLD=2|3 (test in dyld 2 or dyld 3 mode) + PARALLELBUILDS=N (number of parallel builds to run simultaneously) + SHAREDCACHEDIR=/path/to/custom/shared/cache/directory examples: @@ -108,6 +129,11 @@ END my $HOST; my $PORT; +my $DEVICE; + +my $PARALLELBUILDS; + +my $SHAREDCACHEDIR; my @TESTLIBNAMES = ("libobjc.A.dylib", "libobjc-trampolines.dylib"); my $TESTLIBDIR = "/usr/lib"; @@ -223,31 +249,20 @@ END # Run some newline-separated commands like `make` would, stopping if any fail # run("cmd1 \n cmd2 \n cmd3") sub make { + my ($cmdstr, $cwd) = @_; my $output = ""; - my @cmds = split("\n", $_[0]); + my @cmds = split("\n", $cmdstr); die if scalar(@cmds) == 0; $? = 0; foreach my $cmd (@cmds) { chomp $cmd; next if $cmd =~ /^\s*$/; $cmd .= " 2>&1"; - print "$cmd\n" if $VERBOSE; - eval { - local $SIG{ALRM} = sub { die "alarm\n" }; - # Timeout after 600 seconds so a deadlocked test doesn't wedge the - # entire test suite. Increase to an hour for B&I builds. - if (exists $ENV{"RC_XBS"}) { - alarm 3600; - } else { - alarm 600; - } - $output .= `$cmd`; - alarm 0; - }; - if ($@) { - die unless $@ eq "alarm\n"; - $output .= "\nTIMED OUT"; + if (defined $cwd) { + $cmd = "cd $cwd; $cmd"; } + print "$cmd\n" if $VERBOSE; + $output .= `$cmd`; last if $?; } print "$output\n" if $VERBOSE; @@ -262,7 +277,7 @@ sub chdir_verbose { sub rm_rf_verbose { my $dir = shift || die; - print "mkdir -p $dir\n" if $VERBOSE; + print "rm -rf $dir\n" if $VERBOSE; `rm -rf '$dir'`; die "couldn't rm -rf $dir" if $?; } @@ -749,6 +764,7 @@ sub gather_simple { # TEST_BUILD build instructions # TEST_BUILD_OUTPUT expected build stdout/stderr # TEST_RUN_OUTPUT expected run stdout/stderr + # TEST_ENTITLEMENTS path to entitlements file open(my $in, "< $file") || die; my $contents = join "", <$in>; @@ -758,11 +774,15 @@ sub gather_simple { my ($conditionstring) = ($contents =~ /\bTEST_CONFIG\b(.*)$/m); my ($envstring) = ($contents =~ /\bTEST_ENV\b(.*)$/m); my ($cflags) = ($contents =~ /\bTEST_CFLAGS\b(.*)$/m); + my ($entitlements) = ($contents =~ /\bTEST_ENTITLEMENTS\b(.*)$/m); + $entitlements =~ s/^\s+|\s+$//g; my ($buildcmd) = extract_multiline("TEST_BUILD", $contents, $name); my ($builderror) = extract_multiple_multiline("TEST_BUILD_OUTPUT", $contents, $name); my ($runerror) = extract_multiple_multiline("TEST_RUN_OUTPUT", $contents, $name); - return 0 if !$test_h && !$disabled && !$crashes && !defined($conditionstring) && !defined($envstring) && !defined($cflags) && !defined($buildcmd) && !defined($builderror) && !defined($runerror); + return 0 if !$test_h && !$disabled && !$crashes && !defined($conditionstring) + && !defined($envstring) && !defined($cflags) && !defined($buildcmd) + && !defined($builderror) && !defined($runerror) && !defined($entitlements); if ($disabled) { colorprint $yellow, "SKIP: $name (disabled by $disabled)"; @@ -828,6 +848,7 @@ sub gather_simple { TEST_RUN => $run, DSTDIR => "$C{DSTDIR}/$name.build", OBJDIR => "$C{OBJDIR}/$name.build", + ENTITLEMENTS => $entitlements, }; return 1; @@ -873,22 +894,34 @@ sub build_simple { my $name = shift; my %T = %{$C{"TEST_$name"}}; - mkdir_verbose $T{DSTDIR}; - chdir_verbose $T{DSTDIR}; + my $dstdir = $T{DSTDIR}; + if (-e "$dstdir/build-succeeded") { + # We delete the whole test directory before building (if it existed), + # so if this file exists now, that means another configuration already + # did an equivalent build. + print "note: $name is already built at $dstdir, skipping the build\n" if $VERBOSE; + return 1; + } + + mkdir_verbose $dstdir; # we don't mkdir $T{OBJDIR} because most tests don't use it my $ext = $ALL_TESTS{$name}; my $file = "$DIR/$name.$ext"; if ($T{TEST_CRASHES}) { - `echo '$crashcatch' > crashcatch.c`; - make("$C{COMPILE_C} -dynamiclib -o libcrashcatch.dylib -x c crashcatch.c"); - die "$?" if $?; + `echo '$crashcatch' > $dstdir/crashcatch.c`; + my $output = make("$C{COMPILE_C} -dynamiclib -o libcrashcatch.dylib -x c crashcatch.c", $dstdir); + if ($?) { + colorprint $red, "FAIL: building crashcatch.c"; + colorprefix $red, $output; + return 0; + } } my $cmd = $T{TEST_BUILD} ? eval "return \"$T{TEST_BUILD}\"" : "$C{COMPILE} $T{TEST_CFLAGS} $file -o $name.exe"; - my $output = make($cmd); + my $output = make($cmd, $dstdir); # ignore out-of-date text-based stubs (caused by ditto into SDK) $output =~ s/ld: warning: text-based stub file.*\n//g; @@ -901,6 +934,7 @@ sub build_simple { $output =~ s/^warning: callee: [^\n]+\n//g; # rdar://38710948 $output =~ s/ld: warning: ignoring file [^\n]*libclang_rt\.bridgeos\.a[^\n]*\n//g; + $output =~ s/ld: warning: building for iOS Simulator, but[^\n]*\n//g; # ignore compiler logging of CCC_OVERRIDE_OPTIONS effects if (defined $ENV{CCC_OVERRIDE_OPTIONS}) { $output =~ s/### (CCC_OVERRIDE_OPTIONS:|Adding argument|Deleting argument|Replacing) [^\n]*\n//g; @@ -943,23 +977,36 @@ sub build_simple { } if ($ok) { - foreach my $file (glob("*.exe *.dylib *.bundle")) { + foreach my $file (glob("$dstdir/*.exe $dstdir/*.dylib $dstdir/*.bundle")) { if (!$BATS) { # not for BATS to save space and build time # fixme use SYMROOT? - make("xcrun dsymutil $file"); + make("xcrun dsymutil $file", $dstdir); } if ($C{OS} eq "macosx" || $C{OS} =~ /simulator/) { # setting any entitlements disables dyld environment variables } else { # get-task-allow entitlement is required # to enable dyld environment variables - make("xcrun codesign -s - --entitlements $DIR/get_task_allow_entitlement.plist $file"); - die "$?" if $?; + if (!$T{ENTITLEMENTS}) { + $T{ENTITLEMENTS} = "get_task_allow_entitlement.plist"; + } + my $output = make("xcrun codesign -s - --entitlements $DIR/$T{ENTITLEMENTS} $file", $dstdir); + if ($?) { + colorprint $red, "FAIL: codesign $file"; + colorprefix $red, $output; + return 0; + } } } } + # Mark the build as successful so other configs with the same build + # requirements can skip buildiing. + if ($ok) { + make("touch build-succeeded", $dstdir); + } + return $ok; } @@ -993,6 +1040,10 @@ sub run_simple { die "unknown DYLD setting $C{DYLD}"; } + if ($SHAREDCACHEDIR) { + $env .= " DYLD_SHARED_REGION=private DYLD_SHARED_CACHE_DIR=$SHAREDCACHEDIR"; + } + my $output; if ($C{ARCH} =~ /^arm/ && `uname -p` !~ /^arm/) { @@ -1008,23 +1059,12 @@ sub run_simple { $env .= " DYLD_INSERT_LIBRARIES=$remotedir/libcrashcatch.dylib"; } - my $cmd = "ssh -p $PORT $HOST 'cd $remotedir && env $env ./$name.exe'"; + my $cmd = "ssh $PORT $HOST 'cd $remotedir && env $env ./$name.exe'"; $output = make("$cmd"); } elsif ($C{OS} =~ /simulator/) { # run locally in a simulator - # fixme selection of simulated OS version - my $simdevice; - if ($C{OS} =~ /iphonesimulator/) { - $simdevice = 'iPhone X'; - } elsif ($C{OS} =~ /watchsimulator/) { - $simdevice = 'Apple Watch Series 4 - 40mm'; - } elsif ($C{OS} =~ /tvsimulator/) { - $simdevice = 'Apple TV 1080p'; - } else { - die "unknown simulator $C{OS}\n"; - } - my $sim = "xcrun -sdk iphonesimulator simctl spawn '$simdevice'"; + my $sim = "xcrun -sdk iphonesimulator simctl spawn '$DEVICE'"; # Add test dir and libobjc's dir to DYLD_LIBRARY_PATH. # Insert libcrashcatch.dylib if necessary. $env .= " DYLD_LIBRARY_PATH=$testdir"; @@ -1138,11 +1178,11 @@ sub make_one_config { # set the config name now, after massaging the language and OS versions, # but before adding other settings - my $configname = config_name(%C); - die if ($configname =~ /'/); - die if ($configname =~ / /); - ($C{NAME} = $configname) =~ s/~/ /g; - (my $configdir = $configname) =~ s#/##g; + my $configdirname = config_dir_name(%C); + die if ($configdirname =~ /'/); + die if ($configdirname =~ / /); + ($C{NAME} = $configdirname) =~ s/~/ /g; + (my $configdir = $configdirname) =~ s#/##g; $C{DSTDIR} = "$DSTROOT$BUILDDIR/$configdir"; $C{OBJDIR} = "$OBJROOT$BUILDDIR/$configdir"; @@ -1404,9 +1444,9 @@ sub make_one_config { $C{XCRUN} = "env LANG=C /usr/bin/xcrun -toolchain '$C{TOOLCHAIN}'"; $C{COMPILE_C} = "$C{XCRUN} '$C{CC}' $cflags -x c -std=gnu99"; - $C{COMPILE_CXX} = "$C{XCRUN} '$C{CXX}' $cflags -x c++"; + $C{COMPILE_CXX} = "$C{XCRUN} '$C{CXX}' $cflags -x c++ -std=gnu++17"; $C{COMPILE_M} = "$C{XCRUN} '$C{CC}' $cflags $objcflags -x objective-c -std=gnu99"; - $C{COMPILE_MM} = "$C{XCRUN} '$C{CXX}' $cflags $objcflags -x objective-c++"; + $C{COMPILE_MM} = "$C{XCRUN} '$C{CXX}' $cflags $objcflags -x objective-c++ -std=gnu++17"; $C{COMPILE_SWIFT} = "$C{XCRUN} '$C{SWIFT}' $swiftflags"; $C{COMPILE} = $C{COMPILE_C} if $C{LANGUAGE} eq "c"; @@ -1483,10 +1523,13 @@ sub make_configs { return @newresults; } -sub config_name { +sub config_dir_name { my %config = @_; my $name = ""; for my $key (sort keys %config) { + # Exclude settings that only influence the run, not the build. + next if $key eq "DYLD" || $key eq "GUARDMALLOC"; + $name .= '~' if $name ne ""; $name .= "$key=$config{$key}"; } @@ -1496,7 +1539,7 @@ sub config_name { sub rsync_ios { my ($src, $timeout) = @_; for (my $i = 0; $i < 10; $i++) { - make("$DIR/timeout.pl $timeout rsync -e 'ssh -p $PORT' -av $src $HOST:/$REMOTEBASE/"); + make("$DIR/timeout.pl $timeout rsync -e 'ssh $PORT' -av $src $HOST:/$REMOTEBASE/"); return if $? == 0; colorprint $yellow, "WARN: RETRY\n" if $VERBOSE; } @@ -1521,8 +1564,15 @@ sub build_and_run_one_config { if ($ALL_TESTS{$test}) { gather_simple(\%C, $test) || next; # not pass, not fail push @gathertests, $test; - } else { - die "No test named '$test'\n"; + } elsif ($has_match_glob) { + my @matched = Text::Glob::match_glob($test, (keys %ALL_TESTS)); + if (not @matched) { + die "No test matched '$test'\n"; + } + foreach my $match (@matched) { + gather_simple(\%C, $match) || next; # not pass, not fail + push @gathertests, $match; + } } } @@ -1530,7 +1580,56 @@ sub build_and_run_one_config { if (!$BUILD) { @builttests = @gathertests; $testcount = scalar(@gathertests); + } elsif ($PARALLELBUILDS > 1 && $supportsParallelBuilds) { + my $workQueue = Thread::Queue->new(); + my $resultsQueue = Thread::Queue->new(); + my @threads = map { + threads->create(sub { + while (defined(my $test = $workQueue->dequeue())) { + local *STDOUT; + local *STDERR; + my $output; + open STDOUT, '>>', \$output; + open STDERR, '>>', \$output; + + my $success = build_simple(\%C, $test); + $resultsQueue->enqueue({ test => $test, success => $success, output => $output }); + } + }); + } (1 .. $PARALLELBUILDS); + + foreach my $test (@gathertests) { + if ($VERBOSE) { + print "\nBUILD $test\n"; + } + if ($ALL_TESTS{$test}) { + $testcount++; + $workQueue->enqueue($test); + } else { + die "No test named '$test'\n"; + } + } + $workQueue->end(); + foreach (@gathertests) { + my $result = $resultsQueue->dequeue(); + my $test = $result->{test}; + my $success = $result->{success}; + my $output = $result->{output}; + + print $output; + if ($success) { + push @builttests, $test; + } else { + $failcount++; + } + } + foreach my $thread (@threads) { + $thread->join(); + } } else { + if ($PARALLELBUILDS > 1) { + print "WARNING: requested parallel builds, but this perl interpreter does not support threads. Falling back to sequential builds.\n"; + } foreach my $test (@gathertests) { if ($VERBOSE) { print "\nBUILD $test\n"; @@ -1553,7 +1652,7 @@ sub build_and_run_one_config { # nothing to do } else { - if ($C{ARCH} =~ /^arm/ && `uname -p` !~ /^arm/) { + if ($HOST && $C{ARCH} =~ /^arm/ && `uname -p` !~ /^arm/) { # upload timeout - longer for slow watch devices my $timeout = ($C{OS} =~ /watch/) ? 120 : 20; @@ -1686,8 +1785,16 @@ sub getint { $args{CC} = getargs("CC", "clang"); -$HOST = getarg("HOST", "iphone"); -$PORT = getarg("PORT", "10022"); +$HOST = getarg("HOST", 0); +$PORT = getarg("PORT", ""); +if ($PORT) { + $PORT = "-p $PORT"; +} +$DEVICE = getarg("DEVICE", "booted"); + +$PARALLELBUILDS = getarg("PARALLELBUILDS", `sysctl -n hw.ncpu`); + +$SHAREDCACHEDIR = getarg("SHAREDCACHEDIR", ""); { my $guardmalloc = getargs("GUARDMALLOC", 0); @@ -1760,6 +1867,8 @@ sub getint { } } +make("find $DSTROOT$BUILDDIR -name build-succeeded -delete", "/"); + print "note: -----\n"; my $color = ($failconfigs ? $red : ""); colorprint $color, "note: $testconfigs configurations, " . diff --git a/test/unload.m b/test/unload.m index ccd99b7..0cf437c 100644 --- a/test/unload.m +++ b/test/unload.m @@ -138,6 +138,9 @@ void cycle(void) int main() { + char *useClosures = getenv("DYLD_USE_CLOSURES"); + int dyld3 = useClosures != NULL && useClosures[0] != '0'; + objc_setForwardHandler((void*)&forward_handler, (void*)&forward_handler); #if defined(__arm__) || defined(__arm64__) @@ -153,10 +156,11 @@ int main() #endif leak_mark(); - while (count--) { + for (int i = 0; i < count; i++) { cycle(); } - leak_check(0); + // dyld3 currently leaks 8 bytes for each dlopen/dlclose pair, so accommodate it. rdar://problem/53769254 + leak_check(dyld3 ? (count * sizeof(void *)) : 0); // 5359412 Make sure dylibs with nothing other than image_info can close void *dylib = dlopen("unload3.dylib", RTLD_LAZY); @@ -164,7 +168,9 @@ int main() int err = dlclose(dylib); testassert(err == 0); err = dlclose(dylib); - testassert(err == -1); // already closed + // dyld3 doesn't error when dlclosing the dylib twice. This is probably expected. rdar://problem/53769374 + if (!dyld3) + testassert(err == -1); // already closed // Make sure dylibs with real objc content cannot close dylib = dlopen("unload4.dylib", RTLD_LAZY); @@ -172,7 +178,9 @@ int main() err = dlclose(dylib); testassert(err == 0); err = dlclose(dylib); - testassert(err == -1); // already closed + // dyld3 doesn't error when dlclosing the dylib twice. This is probably expected. rdar://problem/53769374 + if (!dyld3) + testassert(err == -1); // already closed succeed(__FILE__); } diff --git a/test/weakReferenceHook.m b/test/weakReferenceHook.m new file mode 100644 index 0000000..890173d --- /dev/null +++ b/test/weakReferenceHook.m @@ -0,0 +1,49 @@ +/* + TEST_CONFIG MEM=mrc + TEST_ENV OBJC_DISABLE_NONPOINTER_ISA=YES +*/ + +#include "test.h" +#include "testroot.i" + +bool hasWeakRefs = false; + +@interface TestRoot (WeakHooks) +@end + +@implementation TestRoot (WeakHooks) + +- (void)_setWeaklyReferenced { + hasWeakRefs = true; +} + +// -_setWeaklyReferenced is currently limited to raw-isa custom-rr to avoid overhead +- (void) release { +} + +@end + +int main() { + id obj = [TestRoot new]; + id wobj = nil; + objc_storeWeak(&wobj, obj); + testassert(hasWeakRefs == true); + + id out = objc_loadWeak(&wobj); + testassert(out == obj); + + objc_storeWeak(&wobj, nil); + out = objc_loadWeak(&wobj); + testassert(out == nil); + + hasWeakRefs = false; + objc_storeWeak(&wobj, obj); + testassert(hasWeakRefs == true); + + + out = objc_loadWeak(&wobj); + testassert(out == obj); + objc_storeWeak(&wobj, nil); + + succeed(__FILE__); +}