From 13ba007ef885ec1d079cdb0e881efe5cc776a7d2 Mon Sep 17 00:00:00 2001 From: Apple Date: Tue, 18 Jun 2019 01:08:51 +0000 Subject: [PATCH] objc4-756.2.tar.gz --- 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 @@ storeWeak(id *location, objc_object *newObj) !((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 @@ objc_loadWeakRetained(id *location) } else { table->unlock(); - _class_initialize(cls); + class_initialize(cls, obj); goto retry; } } @@ -614,7 +614,12 @@ struct magic_t { } ~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 @@ objc_allocWithZone(Class cls) 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 @@ public: 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 @@ public: 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 @@ public: 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 @@ struct TrampolineBlockPageGroup { 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 @@ struct TrampolineBlockPageGroup } 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 @@ struct TrampolineBlockPageGroup } 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 TrampolineBlockPageGroup *_allocateTrampolinesAndData() 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 TrampolineBlockPageGroup *_allocateTrampolinesAndData() _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 @@ ldp(uintptr_t& onep, uintptr_t& twop, const void *srcp) // 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 @@ cache_t *getCache(Class cls) 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 @@ bucket_t *allocateBuckets(mask_t newCapacity) 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 @@ void cache_t::bad_cache(id receiver, SEL sel, Class isa) } -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 @@ const char *class_getName(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 @@ struct ReleaseValue { }; 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 @@ public: 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 @@ private: } #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 @@ public: 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 @@ public: 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 disableTaggedPointers(); 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 @@ static void initializeTaggedPointerObfuscator(void); #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 @@ const uintptr_t objc_debug_isa_magic_value = 0; #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 @@ static class_ro_t *make_ro_writeable(class_rw_t *rw) 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 char *copySwiftV1MangledName(const char *string, bool isProtocol = false) /*********************************************************************** -* 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 @@ missingWeakSuperclass(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 @@ copyClassNamesForImage_nolock(header_info *hi, unsigned int *outCount) 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 @@ objc_class::nameForLogging() /*********************************************************************** * 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 @@ objc_class::demangledName(bool realize) 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 @@ class_replaceProperty(Class cls, const char *name, * 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 @@ look_up_class(const char *name, 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 @@ objc_duplicateClass(Class original, const char *name, 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 @@ verifySuperclass(Class superclass, bool rootOK) **********************************************************************/ 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__); +} -- 2.45.2