From bc4fafcea49b79b651a74506afcac72862f13fef Mon Sep 17 00:00:00 2001 From: Apple Date: Fri, 1 May 2020 18:26:59 +0000 Subject: [PATCH] objc4-787.1.tar.gz --- libobjc.order | 2 - objc.xcodeproj/project.pbxproj | 202 +++++++++- objcdt/json.h | 82 +++++ objcdt/json.mm | 234 ++++++++++++ objcdt/objcdt-entitlements.plist | 10 + objcdt/objcdt.1 | 20 + objcdt/objcdt.mm | 34 ++ runtime/Messengers.subproj/objc-msg-arm.s | 80 +++- runtime/Messengers.subproj/objc-msg-arm64.s | 117 ++++-- .../objc-msg-simulator-i386.s | 92 ++++- .../objc-msg-simulator-x86_64.s | 140 +++++-- runtime/Messengers.subproj/objc-msg-x86_64.s | 140 +++++-- runtime/PointerUnion.h | 32 +- runtime/objc-cache.mm | 12 + runtime/objc-file.h | 14 +- runtime/objc-internal.h | 8 +- runtime/objc-opt.mm | 218 +++++++---- runtime/objc-os.h | 3 +- runtime/objc-os.mm | 15 +- runtime/objc-private.h | 73 +++- runtime/objc-ptrauth.h | 114 ++++++ runtime/objc-runtime-new.h | 346 +++++++++++++----- runtime/objc-runtime-new.mm | 280 ++++++++------ runtime/objc-sel-old.mm | 13 - runtime/objc-sel.mm | 43 +-- runtime/objc.h | 3 +- test/gdb.m | 26 +- test/libraryPath.c | 44 +++ test/load-noobjc.m | 4 + test/methodListSmall.h | 226 ++++++++++++ test/methodListSmall.mm | 89 +++++ test/methodListSmallMutableMemory.mm | 18 + test/nonpointerisa.m | 10 +- test/supported-inline-refcnt.m | 85 +++++ test/test.h | 13 + test/test.pl | 58 ++- 36 files changed, 2391 insertions(+), 509 deletions(-) create mode 100644 objcdt/json.h create mode 100644 objcdt/json.mm create mode 100644 objcdt/objcdt-entitlements.plist create mode 100644 objcdt/objcdt.1 create mode 100644 objcdt/objcdt.mm create mode 100644 test/libraryPath.c create mode 100644 test/methodListSmall.h create mode 100644 test/methodListSmall.mm create mode 100644 test/methodListSmallMutableMemory.mm create mode 100644 test/supported-inline-refcnt.m diff --git a/libobjc.order b/libobjc.order index c7415fc..1d56c59 100644 --- a/libobjc.order +++ b/libobjc.order @@ -19,8 +19,6 @@ __objc_update_stubs_in_mach_header _sel_init ___sel_registerName __objc_search_builtins -__ZNK8objc_opt13objc_selopt_t3getEPKc -__ZNK8objc_opt13objc_selopt_t4hashEPKc _sel_registerName _arr_init __ZN4objc8DenseMapIP11objc_objectmLb1ENS_12DenseMapInfoIS2_EENS3_ImEEE4initEj diff --git a/objc.xcodeproj/project.pbxproj b/objc.xcodeproj/project.pbxproj index 80c47fb..5587470 100644 --- a/objc.xcodeproj/project.pbxproj +++ b/objc.xcodeproj/project.pbxproj @@ -7,6 +7,17 @@ objects = { /* Begin PBXAggregateTarget section */ + 6EF877EF23263D7000963DBB /* objc_executables */ = { + isa = PBXAggregateTarget; + buildConfigurationList = 6EF877F223263D7000963DBB /* Build configuration list for PBXAggregateTarget "objc_executables" */; + buildPhases = ( + ); + dependencies = ( + 6EF877F423263D8000963DBB /* PBXTargetDependency */, + ); + name = objc_executables; + productName = "objc-executables"; + }; 834F9B01212E560100F95A54 /* objc4_tests */ = { isa = PBXAggregateTarget; buildConfigurationList = 834F9B04212E560200F95A54 /* Build configuration list for PBXAggregateTarget "objc4_tests" */; @@ -45,6 +56,14 @@ 6EACB842232C97A400CE9176 /* objc-zalloc.h in Headers */ = {isa = PBXBuildFile; fileRef = 6EACB841232C97A400CE9176 /* objc-zalloc.h */; }; 6EACB844232C97B900CE9176 /* objc-zalloc.mm in Sources */ = {isa = PBXBuildFile; fileRef = 6EACB843232C97B900CE9176 /* objc-zalloc.mm */; }; 6ECD0B1F2244999E00910D88 /* llvm-DenseSet.h in Headers */ = {isa = PBXBuildFile; fileRef = 6ECD0B1E2244999E00910D88 /* llvm-DenseSet.h */; }; + 6EF877DA2325D62600963DBB /* objcdt.mm in Sources */ = {isa = PBXBuildFile; fileRef = 6EF877D92325D62600963DBB /* objcdt.mm */; }; + 6EF877DE2325D79000963DBB /* objc-probes.d in Sources */ = {isa = PBXBuildFile; fileRef = 87BB4E900EC39633005D08E1 /* objc-probes.d */; }; + 6EF877E02325D92E00963DBB /* CoreSymbolication.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6EF877DF2325D92E00963DBB /* CoreSymbolication.framework */; }; + 6EF877E22325D93200963DBB /* Symbolication.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6EF877E12325D93200963DBB /* Symbolication.framework */; }; + 6EF877E52325FAC400963DBB /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6EF877E42325FAC400963DBB /* Foundation.framework */; }; + 6EF877E82326184000963DBB /* json.mm in Sources */ = {isa = PBXBuildFile; fileRef = 6EF877E72326184000963DBB /* json.mm */; }; + 6EF877E923261D3E00963DBB /* objc-cache.mm in Sources */ = {isa = PBXBuildFile; fileRef = 838485CB0D6D68A200CEA253 /* objc-cache.mm */; }; + 6EF877EC232635A700963DBB /* objcdt.1 in Install Manpages */ = {isa = PBXBuildFile; fileRef = 6EF877EA232633CC00963DBB /* objcdt.1 */; }; 7213C36321FA7C730090A271 /* NSObject-internal.h in Headers */ = {isa = PBXBuildFile; fileRef = 7213C36221FA7C730090A271 /* NSObject-internal.h */; settings = {ATTRIBUTES = (Private, ); }; }; 7593EC58202248E50046AB96 /* objc-object.h in Headers */ = {isa = PBXBuildFile; fileRef = 7593EC57202248DF0046AB96 /* objc-object.h */; }; 75A9504F202BAA0600D7D56F /* objc-locks-new.h in Headers */ = {isa = PBXBuildFile; fileRef = 75A9504E202BAA0300D7D56F /* objc-locks-new.h */; }; @@ -128,12 +147,20 @@ 83F550E0155E030800E95D3B /* objc-cache-old.mm in Sources */ = {isa = PBXBuildFile; fileRef = 83F550DF155E030800E95D3B /* objc-cache-old.mm */; }; 87BB4EA70EC39854005D08E1 /* objc-probes.d in Sources */ = {isa = PBXBuildFile; fileRef = 87BB4E900EC39633005D08E1 /* objc-probes.d */; }; 9672F7EE14D5F488007CEC96 /* NSObject.mm in Sources */ = {isa = PBXBuildFile; fileRef = 9672F7ED14D5F488007CEC96 /* NSObject.mm */; }; + C22F5208230EF38B001BFE14 /* objc-ptrauth.h in Headers */ = {isa = PBXBuildFile; fileRef = C22F5207230EF38B001BFE14 /* objc-ptrauth.h */; }; C2E6D3FC2225DCF00059DFAA /* DenseMapExtras.h in Headers */ = {isa = PBXBuildFile; fileRef = C2E6D3FB2225DCF00059DFAA /* DenseMapExtras.h */; }; E8923DA5116AB2820071B552 /* objc-block-trampolines.mm in Sources */ = {isa = PBXBuildFile; fileRef = E8923DA0116AB2820071B552 /* objc-block-trampolines.mm */; }; F9BCC71B205C68E800DD9AFC /* objc-blocktramps-arm64.s in Sources */ = {isa = PBXBuildFile; fileRef = 8379996D13CBAF6F007C2B5F /* objc-blocktramps-arm64.s */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ + 6EF877F323263D8000963DBB /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 08FB7793FE84155DC02AAC07 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 6EF877D62325D62600963DBB; + remoteInfo = objcdt; + }; 837F67AC1A771F6E004D34FA /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 08FB7793FE84155DC02AAC07 /* Project object */; @@ -150,6 +177,20 @@ }; /* End PBXContainerItemProxy section */ +/* Begin PBXCopyFilesBuildPhase section */ + 6EF877D52325D62600963DBB /* Install Manpages */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = /usr/local/share/man/man1/; + dstSubfolderSpec = 0; + files = ( + 6EF877EC232635A700963DBB /* objcdt.1 in Install Manpages */, + ); + name = "Install Manpages"; + runOnlyForDeploymentPostprocessing = 1; + }; +/* End PBXCopyFilesBuildPhase section */ + /* Begin PBXFileReference section */ 393CEABF0DC69E3E000B69DE /* objc-references.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = "objc-references.mm"; path = "runtime/objc-references.mm"; sourceTree = ""; }; 393CEAC50DC69E67000B69DE /* objc-references.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "objc-references.h"; path = "runtime/objc-references.h"; sourceTree = ""; }; @@ -164,6 +205,15 @@ 6EACB841232C97A400CE9176 /* objc-zalloc.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "objc-zalloc.h"; path = "runtime/objc-zalloc.h"; sourceTree = ""; }; 6EACB843232C97B900CE9176 /* objc-zalloc.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = "objc-zalloc.mm"; path = "runtime/objc-zalloc.mm"; sourceTree = ""; }; 6ECD0B1E2244999E00910D88 /* llvm-DenseSet.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "llvm-DenseSet.h"; path = "runtime/llvm-DenseSet.h"; sourceTree = ""; }; + 6EF877D72325D62600963DBB /* objcdt */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = objcdt; sourceTree = BUILT_PRODUCTS_DIR; }; + 6EF877D92325D62600963DBB /* objcdt.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = objcdt.mm; sourceTree = ""; usesTabs = 0; }; + 6EF877DF2325D92E00963DBB /* CoreSymbolication.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreSymbolication.framework; path = System/Library/PrivateFrameworks/CoreSymbolication.framework; sourceTree = SDKROOT; }; + 6EF877E12325D93200963DBB /* Symbolication.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Symbolication.framework; path = System/Library/PrivateFrameworks/Symbolication.framework; sourceTree = SDKROOT; }; + 6EF877E32325D95300963DBB /* objcdt-entitlements.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "objcdt-entitlements.plist"; sourceTree = ""; }; + 6EF877E42325FAC400963DBB /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; + 6EF877E62326184000963DBB /* json.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = json.h; sourceTree = ""; usesTabs = 1; }; + 6EF877E72326184000963DBB /* json.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = json.mm; sourceTree = ""; usesTabs = 1; }; + 6EF877EA232633CC00963DBB /* objcdt.1 */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.man; path = objcdt.1; sourceTree = ""; }; 7213C36221FA7C730090A271 /* NSObject-internal.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "NSObject-internal.h"; path = "runtime/NSObject-internal.h"; sourceTree = ""; }; 7593EC57202248DF0046AB96 /* objc-object.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "objc-object.h"; path = "runtime/objc-object.h"; sourceTree = ""; }; 75A9504E202BAA0300D7D56F /* objc-locks-new.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "objc-locks-new.h"; path = "runtime/objc-locks-new.h"; sourceTree = ""; }; @@ -252,6 +302,8 @@ 87BB4E900EC39633005D08E1 /* objc-probes.d */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.dtrace; name = "objc-probes.d"; path = "runtime/objc-probes.d"; sourceTree = ""; }; 9672F7ED14D5F488007CEC96 /* NSObject.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = NSObject.mm; path = runtime/NSObject.mm; sourceTree = ""; }; BC8B5D1212D3D48100C78A5B /* libauto.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libauto.dylib; path = /usr/lib/libauto.dylib; sourceTree = ""; }; + C217B55222DE556D004369BA /* objc-env.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "objc-env.h"; path = "runtime/objc-env.h"; sourceTree = ""; }; + C22F5207230EF38B001BFE14 /* objc-ptrauth.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "objc-ptrauth.h"; path = "runtime/objc-ptrauth.h"; sourceTree = ""; }; C2E6D3FB2225DCF00059DFAA /* DenseMapExtras.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = DenseMapExtras.h; path = runtime/DenseMapExtras.h; sourceTree = ""; }; D2AAC0630554660B00DB518D /* libobjc.A.dylib */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.dylib"; includeInIndex = 0; path = libobjc.A.dylib; sourceTree = BUILT_PRODUCTS_DIR; }; E8923D9C116AB2820071B552 /* objc-blocktramps-i386.s */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.asm; name = "objc-blocktramps-i386.s"; path = "runtime/objc-blocktramps-i386.s"; sourceTree = ""; }; @@ -261,6 +313,16 @@ /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ + 6EF877D42325D62600963DBB /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 6EF877E22325D93200963DBB /* Symbolication.framework in Frameworks */, + 6EF877E52325FAC400963DBB /* Foundation.framework in Frameworks */, + 6EF877E02325D92E00963DBB /* CoreSymbolication.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; D289988505E68E00004EDB86 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; @@ -289,6 +351,7 @@ 838486270D6D690F00CEA253 /* Obsolete Source */, 08FB7795FE84155DC02AAC07 /* Source */, 838485B20D6D67F900CEA253 /* Other */, + 6EF877D82325D62600963DBB /* objcdt */, 1AB674ADFE9D54B511CA2CBB /* Products */, F9BCC72A205C6A1600DD9AFC /* Frameworks */, ); @@ -352,10 +415,23 @@ children = ( D2AAC0630554660B00DB518D /* libobjc.A.dylib */, F9BCC727205C68E800DD9AFC /* libobjc-trampolines.dylib */, + 6EF877D72325D62600963DBB /* objcdt */, ); name = Products; sourceTree = ""; }; + 6EF877D82325D62600963DBB /* objcdt */ = { + isa = PBXGroup; + children = ( + 6EF877EA232633CC00963DBB /* objcdt.1 */, + 6EF877E62326184000963DBB /* json.h */, + 6EF877E72326184000963DBB /* json.mm */, + 6EF877D92325D62600963DBB /* objcdt.mm */, + 6EF877E32325D95300963DBB /* objcdt-entitlements.plist */, + ); + path = objcdt; + sourceTree = ""; + }; 838485B20D6D67F900CEA253 /* Other */ = { isa = PBXGroup; children = ( @@ -389,12 +465,13 @@ 838485C70D6D688200CEA253 /* Private Headers */ = { isa = PBXGroup; children = ( + 838485BB0D6D687300CEA253 /* maptable.h */, 7213C36221FA7C730090A271 /* NSObject-internal.h */, - 83112ED30F00599600A5FBAF /* objc-internal.h */, 834EC0A311614167009B2563 /* objc-abi.h */, - 838485BB0D6D687300CEA253 /* maptable.h */, - 834266D70E665A8B002E4DA2 /* objc-gdb.h */, 8306440620D24A3E00E356D2 /* objc-block-trampolines.h */, + 834266D70E665A8B002E4DA2 /* objc-gdb.h */, + 83112ED30F00599600A5FBAF /* objc-internal.h */, + C22F5207230EF38B001BFE14 /* objc-ptrauth.h */, ); name = "Private Headers"; sourceTree = ""; @@ -437,6 +514,7 @@ 83D9269721225A7400299F69 /* arm64-asm.h */, 83D92695212254CF00299F69 /* isa.h */, 838485CF0D6D68A200CEA253 /* objc-config.h */, + C217B55222DE556D004369BA /* objc-env.h */, 83BE02E50FCCB24D00661494 /* objc-file-old.h */, 83BE02E60FCCB24D00661494 /* objc-file.h */, 838485D40D6D68A200CEA253 /* objc-initialize.h */, @@ -460,6 +538,9 @@ F9BCC72A205C6A1600DD9AFC /* Frameworks */ = { isa = PBXGroup; children = ( + 6EF877E42325FAC400963DBB /* Foundation.framework */, + 6EF877E12325D93200963DBB /* Symbolication.framework */, + 6EF877DF2325D92E00963DBB /* CoreSymbolication.framework */, ); name = Frameworks; sourceTree = ""; @@ -528,6 +609,7 @@ 838486200D6D68A800CEA253 /* runtime.h in Headers */, 39ABD72312F0B61800D1054C /* objc-weak.h in Headers */, 83F4B52815E843B100E0926F /* NSObjCRuntime.h in Headers */, + C22F5208230EF38B001BFE14 /* objc-ptrauth.h in Headers */, 6ECD0B1F2244999E00910D88 /* llvm-DenseSet.h in Headers */, 83F4B52915E843B100E0926F /* NSObject.h in Headers */, ); @@ -536,6 +618,23 @@ /* End PBXHeadersBuildPhase section */ /* Begin PBXNativeTarget section */ + 6EF877D62325D62600963DBB /* objcdt */ = { + isa = PBXNativeTarget; + buildConfigurationList = 6EF877DD2325D62600963DBB /* Build configuration list for PBXNativeTarget "objcdt" */; + buildPhases = ( + 6EF877D32325D62600963DBB /* Sources */, + 6EF877D42325D62600963DBB /* Frameworks */, + 6EF877D52325D62600963DBB /* Install Manpages */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = objcdt; + productName = objcdt; + productReference = 6EF877D72325D62600963DBB /* objcdt */; + productType = "com.apple.product-type.tool"; + }; D2AAC0620554660B00DB518D /* objc */ = { isa = PBXNativeTarget; buildConfigurationList = 1DEB914A08733D8E0010E9CD /* Build configuration list for PBXNativeTarget "objc" */; @@ -582,6 +681,13 @@ BuildIndependentTargetsInParallel = NO; LastUpgradeCheck = 0440; TargetAttributes = { + 6EF877D62325D62600963DBB = { + CreatedOnToolsVersion = 11.0; + }; + 6EF877EF23263D7000963DBB = { + CreatedOnToolsVersion = 11.0; + ProvisioningStyle = Automatic; + }; 834F9B01212E560100F95A54 = { CreatedOnToolsVersion = 10.0; DevelopmentTeam = 59GAB85EFG; @@ -610,6 +716,8 @@ 837F67A81A771F63004D34FA /* objc-simulator */, F9BCC6CA205C68E800DD9AFC /* objc-trampolines */, 834F9B01212E560100F95A54 /* objc4_tests */, + 6EF877EF23263D7000963DBB /* objc_executables */, + 6EF877D62325D62600963DBB /* objcdt */, ); }; /* End PBXProject section */ @@ -665,6 +773,17 @@ /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ + 6EF877D32325D62600963DBB /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 6EF877E923261D3E00963DBB /* objc-cache.mm in Sources */, + 6EF877E82326184000963DBB /* json.mm in Sources */, + 6EF877DA2325D62600963DBB /* objcdt.mm in Sources */, + 6EF877DE2325D79000963DBB /* objc-probes.d in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; D2AAC0610554660B00DB518D /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; @@ -729,6 +848,11 @@ /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ + 6EF877F423263D8000963DBB /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 6EF877D62325D62600963DBB /* objcdt */; + targetProxy = 6EF877F323263D8000963DBB /* PBXContainerItemProxy */; + }; 837F67AD1A771F6E004D34FA /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = D2AAC0620554660B00DB518D /* objc */; @@ -995,6 +1119,58 @@ }; name = Release; }; + 6EF877DB2325D62600963DBB /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_ENTITLEMENTS = "objcdt/objcdt-entitlements.plist"; + CODE_SIGN_IDENTITY = "-"; + GCC_PREPROCESSOR_DEFINITIONS = ( + "__BUILDING_OBJCDT__=1", + "$(inherited)", + ); + HEADER_SEARCH_PATHS = ( + "$(SRCROOT)/runtime", + /System/Library/Frameworks/System.framework/PrivateHeaders, + ); + PRODUCT_NAME = "$(TARGET_NAME)"; + SYSTEM_FRAMEWORK_SEARCH_PATHS = "$(inherited) $(SDKROOT)$(SYSTEM_LIBRARY_DIR)/PrivateFrameworks"; + }; + name = Debug; + }; + 6EF877DC2325D62600963DBB /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_ENTITLEMENTS = "objcdt/objcdt-entitlements.plist"; + CODE_SIGN_IDENTITY = "-"; + GCC_PREPROCESSOR_DEFINITIONS = ( + "__BUILDING_OBJCDT__=1", + "$(inherited)", + ); + HEADER_SEARCH_PATHS = ( + "$(SRCROOT)/runtime", + /System/Library/Frameworks/System.framework/PrivateHeaders, + ); + PRODUCT_NAME = "$(TARGET_NAME)"; + SYSTEM_FRAMEWORK_SEARCH_PATHS = "$(inherited) $(SDKROOT)$(SYSTEM_LIBRARY_DIR)/PrivateFrameworks"; + }; + name = Release; + }; + 6EF877F023263D7000963DBB /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Debug; + }; + 6EF877F123263D7000963DBB /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Release; + }; 834F9B02212E560200F95A54 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { @@ -1034,6 +1210,7 @@ COPY_HEADERS_UNIFDEF_FLAGS = "-UBUILD_FOR_OSX"; "COPY_HEADERS_UNIFDEF_FLAGS[sdk=macosx*]" = "-DBUILD_FOR_OSX"; COPY_PHASE_STRIP = NO; + DEPLOYMENT_LOCATION = YES; DYLIB_CURRENT_VERSION = 228; EXECUTABLE_PREFIX = lib; GCC_CW_ASM_SYNTAX = NO; @@ -1070,6 +1247,7 @@ COPY_HEADERS_RUN_UNIFDEF = YES; COPY_HEADERS_UNIFDEF_FLAGS = "-UBUILD_FOR_OSX"; "COPY_HEADERS_UNIFDEF_FLAGS[sdk=macosx*]" = "-DBUILD_FOR_OSX"; + DEPLOYMENT_LOCATION = YES; DYLIB_CURRENT_VERSION = 228; EXECUTABLE_PREFIX = lib; GCC_CW_ASM_SYNTAX = NO; @@ -1120,6 +1298,24 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; + 6EF877DD2325D62600963DBB /* Build configuration list for PBXNativeTarget "objcdt" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 6EF877DB2325D62600963DBB /* Debug */, + 6EF877DC2325D62600963DBB /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 6EF877F223263D7000963DBB /* Build configuration list for PBXAggregateTarget "objc_executables" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 6EF877F023263D7000963DBB /* Debug */, + 6EF877F123263D7000963DBB /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; 834F9B04212E560200F95A54 /* Build configuration list for PBXAggregateTarget "objc4_tests" */ = { isa = XCConfigurationList; buildConfigurations = ( diff --git a/objcdt/json.h b/objcdt/json.h new file mode 100644 index 0000000..95dbea2 --- /dev/null +++ b/objcdt/json.h @@ -0,0 +1,82 @@ +/* +* Copyright (c) 2019 Apple Inc. All Rights Reserved. +* +* @APPLE_LICENSE_HEADER_START@ +* +* This file contains Original Code and/or Modifications of Original Code +* as defined in and that are subject to the Apple Public Source License +* Version 2.0 (the 'License'). You may not use this file except in +* compliance with the License. Please obtain a copy of the License at +* http://www.opensource.apple.com/apsl/ and read it before using this +* file. +* +* The Original Code and all software distributed under the License are +* distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER +* EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, +* INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. +* Please see the License for the specific language governing rights and +* limitations under the License. +* +* @APPLE_LICENSE_HEADER_END@ +*/ + +#ifndef _OBJC_OBJCDT_JSON_H_ +#define _OBJC_OBJCDT_JSON_H_ + +#include +#include +#include +#include + +namespace json { + +enum context: uint8_t { + root, + array_value, + object_value, + object_key, + done, +}; + +class writer { +private: + FILE *_file; + context _context; + int _depth; + bool _needs_comma; + + void begin_value(int sep = '\0'); + void advance(context old); + void key(const char *key); + +public: + + writer(FILE *f); + ~writer(); + + void object(std::function); + void object(const char *key, std::function); + + void array(std::function); + void array(const char *key, std::function); + + void boolean(bool value); + void boolean(const char *key, bool value); + + void number(uint64_t value); + void number(const char *key, uint64_t value); + + void string(const char *s); + void string(const char *key, const char *s); + + __printflike(2, 3) + void stringf(const char *fmt, ...); + + __printflike(3, 4) + void stringf(const char *key, const char *fmt, ...); +}; + +} + +#endif /* _OBJC_OBJCDT_JSON_H_ */ diff --git a/objcdt/json.mm b/objcdt/json.mm new file mode 100644 index 0000000..7eb7488 --- /dev/null +++ b/objcdt/json.mm @@ -0,0 +1,234 @@ +/* +* Copyright (c) 2019 Apple Inc. All Rights Reserved. +* +* @APPLE_LICENSE_HEADER_START@ +* +* This file contains Original Code and/or Modifications of Original Code +* as defined in and that are subject to the Apple Public Source License +* Version 2.0 (the 'License'). You may not use this file except in +* compliance with the License. Please obtain a copy of the License at +* http://www.opensource.apple.com/apsl/ and read it before using this +* file. +* +* The Original Code and all software distributed under the License are +* distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER +* EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, +* INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. +* Please see the License for the specific language governing rights and +* limitations under the License. +* +* @APPLE_LICENSE_HEADER_END@ +*/ + +#include +#include "json.h" + +namespace json { + +static bool +context_is_value(context c) +{ + return c == root || c == array_value || c == object_value; +} + +writer::writer(FILE *f) +: _file(f) +, _context(root) +, _depth(0) +, _needs_comma(false) +{ +} + +writer::~writer() +{ + fputc('\n', _file); + fflush(_file); +} + +void +writer::begin_value(int sep) +{ + if (_needs_comma) { + _needs_comma = false; + if (sep) { + fprintf(_file, ", %c\n", sep); + return; + } + fputs(",\n", _file); + } + if (_context == array_value || _context == object_key) { + fprintf(_file, "%*s", _depth * 2, ""); + } + if (sep) { + fprintf(_file, "%c\n", sep); + } +} + +void +writer::advance(context c) +{ + switch (c) { + case root: + _context = done; + _needs_comma = false; + break; + case array_value: + _context = array_value; + _needs_comma = true; + break; + case object_value: + _context = object_key; + _needs_comma = true; + break; + case object_key: + _context = object_value; + _needs_comma = false; + break; + case done: + assert(false); + break; + } +} + +void +writer::key(const char *key) +{ + assert(_context == object_key); + + begin_value(); + fprintf(_file, "\"%s\": ", key); + advance(_context); +} + +void +writer::object(std::function f) +{ + context old = _context; + assert(context_is_value(old)); + + begin_value('{'); + + _depth++; + _context = object_key; + _needs_comma = false; + f(); + + _depth--; + fprintf(_file, "\n%*s}", _depth * 2, ""); + advance(old); +} + +void +writer::object(const char *k, std::function f) +{ + key(k); + object(f); +} + +void +writer::array(std::function f) +{ + context old = _context; + assert(context_is_value(old)); + + begin_value('['); + + _depth++; + _context = array_value; + _needs_comma = false; + f(); + + _depth--; + fprintf(_file, "\n%*s]", _depth * 2, ""); + advance(old); +} + +void +writer::array(const char *k, std::function f) +{ + key(k); + array(f); +} + +void +writer::boolean(bool value) +{ + assert(context_is_value(_context)); + begin_value(); + fputs(value ? "true" : "false", _file); + advance(_context); +} + +void +writer::boolean(const char *k, bool value) +{ + key(k); + boolean(value); +} + +void +writer::number(uint64_t value) +{ + assert(context_is_value(_context)); + begin_value(); + fprintf(_file, "%lld", value); + advance(_context); +} + +void +writer::number(const char *k, uint64_t value) +{ + key(k); + number(value); +} + +void +writer::string(const char *s) +{ + assert(context_is_value(_context)); + begin_value(); + fprintf(_file, "\"%s\"", s); + advance(_context); +} + +void +writer::string(const char *k, const char *s) +{ + key(k); + string(s); +} + +void +writer::stringf(const char *fmt, ...) +{ + va_list ap; + + assert(context_is_value(_context)); + begin_value(); + fputc('"', _file); + va_start(ap, fmt); + vfprintf(_file, fmt, ap); + va_end(ap); + fputc('"', _file); + advance(_context); +} + +void +writer::stringf(const char *k, const char *fmt, ...) +{ + va_list ap; + + key(k); + + assert(context_is_value(_context)); + begin_value(); + fputc('"', _file); + va_start(ap, fmt); + vfprintf(_file, fmt, ap); + va_end(ap); + fputc('"', _file); + advance(_context); +} + +} // json diff --git a/objcdt/objcdt-entitlements.plist b/objcdt/objcdt-entitlements.plist new file mode 100644 index 0000000..b7b4e6c --- /dev/null +++ b/objcdt/objcdt-entitlements.plist @@ -0,0 +1,10 @@ + + + + + task_for_pid-allow + + com.apple.system-task-ports + + + diff --git a/objcdt/objcdt.1 b/objcdt/objcdt.1 new file mode 100644 index 0000000..5522491 --- /dev/null +++ b/objcdt/objcdt.1 @@ -0,0 +1,20 @@ +.\" Copyright (c) 2019, Apple Computer, Inc. All rights reserved. +.\" +.Dd September 9, 2019 \" DATE +.Dt objcdt 1 \" Program name and manual section number +.Os "OS X" +.Sh NAME +.Nm objcdt +.Nd Tool to debug objective-C usage in live processes +.Sh SYNOPSIS +.Nm objcdt +.Sh DESCRIPTION +The +.Nm +utility is a small CLI with embedded help that can dump some information about +the Objective-C runtime state in live processes. +.Pp +Help can be obtained using +.Nm +.Ar help +.Ed diff --git a/objcdt/objcdt.mm b/objcdt/objcdt.mm new file mode 100644 index 0000000..81a5a49 --- /dev/null +++ b/objcdt/objcdt.mm @@ -0,0 +1,34 @@ +/* +* Copyright (c) 2019 Apple Inc. All Rights Reserved. +* +* @APPLE_LICENSE_HEADER_START@ +* +* This file contains Original Code and/or Modifications of Original Code +* as defined in and that are subject to the Apple Public Source License +* Version 2.0 (the 'License'). You may not use this file except in +* compliance with the License. Please obtain a copy of the License at +* http://www.opensource.apple.com/apsl/ and read it before using this +* file. +* +* The Original Code and all software distributed under the License are +* distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER +* EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, +* INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. +* Please see the License for the specific language governing rights and +* limitations under the License. +* +* @APPLE_LICENSE_HEADER_END@ +*/ + +#include "objc-private.h" +#include "objc-ptrauth.h" +#include +#include +#include +#include + +int main(int argc, const char *argv[]) +{ + return EX_UNAVAILABLE; +} diff --git a/runtime/Messengers.subproj/objc-msg-arm.s b/runtime/Messengers.subproj/objc-msg-arm.s index b1a9aec..67317c7 100644 --- a/runtime/Messengers.subproj/objc-msg-arm.s +++ b/runtime/Messengers.subproj/objc-msg-arm.s @@ -222,6 +222,40 @@ LExit$0: .endmacro +////////////////////////////////////////////////////////////////////// +// +// SAVE_REGS +// +// Create a stack frame and save all argument registers in preparation +// for a function call. +////////////////////////////////////////////////////////////////////// + +.macro SAVE_REGS + + stmfd sp!, {r0-r3,r7,lr} + add r7, sp, #16 + sub sp, #8 // align stack + FP_SAVE + +.endmacro + + +////////////////////////////////////////////////////////////////////// +// +// RESTORE_REGS +// +// Restore all argument registers and pop the stack frame created by +// SAVE_REGS. +////////////////////////////////////////////////////////////////////// + +.macro RESTORE_REGS + + FP_RESTORE + add sp, #8 // align stack + ldmfd sp!, {r0-r3,r7,lr} + +.endmacro + ///////////////////////////////////////////////////////////////////// // // CacheLookup NORMAL|STRET @@ -666,10 +700,7 @@ LNilReceiver: .macro MethodTableLookup - stmfd sp!, {r0-r3,r7,lr} - add r7, sp, #16 - sub sp, #8 // align stack - FP_SAVE + SAVE_REGS // lookUpImpOrForward(obj, sel, cls, LOOKUP_INITIALIZE | LOOKUP_RESOLVER) .if $0 == NORMAL @@ -690,9 +721,7 @@ LNilReceiver: tst r12, r12 // set ne for stret forwarding .endif - FP_RESTORE - add sp, #8 // align stack - ldmfd sp!, {r0-r3,r7,lr} + RESTORE_REGS .endmacro @@ -819,18 +848,55 @@ LNilReceiver: ENTRY _method_invoke + + // See if this is a small method. + lsls r12, r1, #31 + bne.w L_method_invoke_small + + // We can directly load the IMP from big methods. // r1 is method triplet instead of SEL ldr r12, [r1, #METHOD_IMP] ldr r1, [r1, #METHOD_NAME] bx r12 + +L_method_invoke_small: + // Small methods require a call to handle swizzling. + SAVE_REGS + mov r0, r1 + bl __method_getImplementationAndName + mov r12, r0 + mov r9, r1 + RESTORE_REGS + mov r1, r9 + bx r12 + + END_ENTRY _method_invoke ENTRY _method_invoke_stret + + // See if this is a small method. + lsls r12, r2, #31 + bne.w L_method_invoke_stret_small + + // We can directly load the IMP from big methods. // r2 is method triplet instead of SEL ldr r12, [r2, #METHOD_IMP] ldr r2, [r2, #METHOD_NAME] bx r12 + +L_method_invoke_stret_small: + // Small methods require a call to handle swizzling. + SAVE_REGS + mov r0, r2 + bl __method_getImplementationAndName + mov r12, r0 + mov r9, r1 + RESTORE_REGS + mov r2, r9 + bx r12 + END_ENTRY _method_invoke_stret diff --git a/runtime/Messengers.subproj/objc-msg-arm64.s b/runtime/Messengers.subproj/objc-msg-arm64.s index 6bf3f29..595b03e 100755 --- a/runtime/Messengers.subproj/objc-msg-arm64.s +++ b/runtime/Messengers.subproj/objc-msg-arm64.s @@ -169,6 +169,63 @@ LExit$0: #define FrameWithNoSaves 0x04000000 // frame, no non-volatile saves +////////////////////////////////////////////////////////////////////// +// +// SAVE_REGS +// +// Create a stack frame and save all argument registers in preparation +// for a function call. +////////////////////////////////////////////////////////////////////// + +.macro SAVE_REGS + + // push frame + SignLR + stp fp, lr, [sp, #-16]! + mov fp, sp + + // save parameter registers: x0..x8, q0..q7 + sub sp, sp, #(10*8 + 8*16) + stp q0, q1, [sp, #(0*16)] + stp q2, q3, [sp, #(2*16)] + stp q4, q5, [sp, #(4*16)] + stp q6, q7, [sp, #(6*16)] + stp x0, x1, [sp, #(8*16+0*8)] + stp x2, x3, [sp, #(8*16+2*8)] + stp x4, x5, [sp, #(8*16+4*8)] + stp x6, x7, [sp, #(8*16+6*8)] + str x8, [sp, #(8*16+8*8)] + +.endmacro + + +////////////////////////////////////////////////////////////////////// +// +// RESTORE_REGS +// +// Restore all argument registers and pop the stack frame created by +// SAVE_REGS. +////////////////////////////////////////////////////////////////////// + +.macro RESTORE_REGS + + ldp q0, q1, [sp, #(0*16)] + ldp q2, q3, [sp, #(2*16)] + ldp q4, q5, [sp, #(4*16)] + ldp q6, q7, [sp, #(6*16)] + ldp x0, x1, [sp, #(8*16+0*8)] + ldp x2, x3, [sp, #(8*16+2*8)] + ldp x4, x5, [sp, #(8*16+4*8)] + ldp x6, x7, [sp, #(8*16+6*8)] + ldr x8, [sp, #(8*16+8*8)] + + mov sp, fp + ldp fp, lr, [sp], #16 + AuthenticateLR + +.endmacro + + /******************************************************************** * * CacheLookup NORMAL|GETIMP|LOOKUP @@ -494,22 +551,7 @@ LLookup_Nil: .macro MethodTableLookup - // push frame - SignLR - stp fp, lr, [sp, #-16]! - mov fp, sp - - // save parameter registers: x0..x8, q0..q7 - sub sp, sp, #(10*8 + 8*16) - stp q0, q1, [sp, #(0*16)] - stp q2, q3, [sp, #(2*16)] - stp q4, q5, [sp, #(4*16)] - stp q6, q7, [sp, #(6*16)] - stp x0, x1, [sp, #(8*16+0*8)] - stp x2, x3, [sp, #(8*16+2*8)] - stp x4, x5, [sp, #(8*16+4*8)] - stp x6, x7, [sp, #(8*16+6*8)] - str x8, [sp, #(8*16+8*8)] + SAVE_REGS // lookUpImpOrForward(obj, sel, cls, LOOKUP_INITIALIZE | LOOKUP_RESOLVER) // receiver and selector already in x0 and x1 @@ -519,21 +561,8 @@ LLookup_Nil: // IMP in x0 mov x17, x0 - - // restore registers and return - ldp q0, q1, [sp, #(0*16)] - ldp q2, q3, [sp, #(2*16)] - ldp q4, q5, [sp, #(4*16)] - ldp q6, q7, [sp, #(6*16)] - ldp x0, x1, [sp, #(8*16+0*8)] - ldp x2, x3, [sp, #(8*16+2*8)] - ldp x4, x5, [sp, #(8*16+4*8)] - ldp x6, x7, [sp, #(8*16+6*8)] - ldr x8, [sp, #(8*16+8*8)] - - mov sp, fp - ldp fp, lr, [sp], #16 - AuthenticateLR + + RESTORE_REGS .endmacro @@ -615,11 +644,37 @@ LGetImpMiss: ENTRY _method_invoke + + // See if this is a small method. + tbnz p1, #0, L_method_invoke_small + + // We can directly load the IMP from big methods. // x1 is method triplet instead of SEL add p16, p1, #METHOD_IMP ldr p17, [x16] ldr p1, [x1, #METHOD_NAME] TailCallMethodListImp x17, x16 + +L_method_invoke_small: + // Small methods require a call to handle swizzling. + SAVE_REGS + mov p0, p1 + bl __method_getImplementationAndName + // ARM64_32 packs both return values into x0, with SEL in the high bits and IMP in the low. + // ARM64 just returns them in x0 and x1. + mov x17, x0 +#if __LP64__ + mov x16, x1 +#endif + RESTORE_REGS +#if __LP64__ + mov x1, x16 +#else + lsr x1, x17, #32 + mov w17, w17 +#endif + TailCallFunctionPointer x17 + END_ENTRY _method_invoke #endif diff --git a/runtime/Messengers.subproj/objc-msg-simulator-i386.s b/runtime/Messengers.subproj/objc-msg-simulator-i386.s index 727b983..914a9ac 100644 --- a/runtime/Messengers.subproj/objc-msg-simulator-i386.s +++ b/runtime/Messengers.subproj/objc-msg-simulator-i386.s @@ -192,6 +192,47 @@ LExit$0: #define FrameWithNoSaves 0x01000000 // frame, no non-volatile saves +////////////////////////////////////////////////////////////////////// +// +// SAVE_REGS +// +// Create a stack frame and save all argument registers in preparation +// for a function call. +////////////////////////////////////////////////////////////////////// + +.macro SAVE_REGS + + pushl %ebp + movl %esp, %ebp + + subl $$(8+5*16), %esp + + movdqa %xmm3, 4*16(%esp) + movdqa %xmm2, 3*16(%esp) + movdqa %xmm1, 2*16(%esp) + movdqa %xmm0, 1*16(%esp) + +.endmacro + + +////////////////////////////////////////////////////////////////////// +// +// RESTORE_REGS +// +// Restore all argument registers and pop the stack frame created by +// SAVE_REGS. +////////////////////////////////////////////////////////////////////// + +.macro RESTORE_REGS + + movdqa 4*16(%esp), %xmm3 + movdqa 3*16(%esp), %xmm2 + movdqa 2*16(%esp), %xmm1 + movdqa 1*16(%esp), %xmm0 + + leave + +.endmacro ///////////////////////////////////////////////////////////////////// // // CacheLookup return-type, caller @@ -314,10 +355,7 @@ LExit$0: ///////////////////////////////////////////////////////////////////// .macro MethodTableLookup - pushl %ebp - movl %esp, %ebp - - subl $$(8+5*16), %esp + SAVE_REGS .if $0 == NORMAL movl self+4(%ebp), %eax @@ -327,11 +365,6 @@ LExit$0: movl selector_stret+4(%ebp), %ecx .endif - movdqa %xmm3, 4*16(%esp) - movdqa %xmm2, 3*16(%esp) - movdqa %xmm1, 2*16(%esp) - movdqa %xmm0, 1*16(%esp) - // lookUpImpOrForward(obj, sel, cls, LOOKUP_INITIALIZE | LOOKUP_RESOLVER) movl $$3, 12(%esp) // LOOKUP_INITIALIZE | LOOKUP_RESOLVER movl %edx, 8(%esp) // class @@ -341,18 +374,13 @@ LExit$0: // imp in eax - movdqa 4*16(%esp), %xmm3 - movdqa 3*16(%esp), %xmm2 - movdqa 2*16(%esp), %xmm1 - movdqa 1*16(%esp), %xmm0 - .if $0 == NORMAL test %eax, %eax // set ne for stret forwarding .else cmp %eax, %eax // set eq for nonstret forwarding .endif - leave + RESTORE_REGS .endmacro @@ -906,23 +934,55 @@ L_forward_stret_handler: ENTRY _method_invoke + // See if this is a small method. + testb $1, selector(%esp) + jnz L_method_invoke_small + + // We can directly load the IMP from big methods. movl selector(%esp), %ecx movl method_name(%ecx), %edx movl method_imp(%ecx), %eax movl %edx, selector(%esp) jmp *%eax - + +L_method_invoke_small: + // Small methods require a call to handle swizzling. + SAVE_REGS + + movl selector+4(%ebp), %eax + movl %eax, 0(%esp) + call __method_getImplementationAndName + RESTORE_REGS + movl %edx, selector(%esp) + jmp *%eax + END_ENTRY _method_invoke ENTRY _method_invoke_stret + // See if this is a small method. + testb $1, selector_stret(%esp) + jnz L_method_invoke_stret_small + + // We can directly load the IMP from big methods. movl selector_stret(%esp), %ecx movl method_name(%ecx), %edx movl method_imp(%ecx), %eax movl %edx, selector_stret(%esp) jmp *%eax +L_method_invoke_stret_small: + // Small methods require a call to handle swizzling. + SAVE_REGS + + movl selector_stret+4(%ebp), %eax + movl %eax, 0(%esp) + call __method_getImplementationAndName + RESTORE_REGS + movl %edx, selector_stret(%esp) + jmp *%eax + END_ENTRY _method_invoke_stret diff --git a/runtime/Messengers.subproj/objc-msg-simulator-x86_64.s b/runtime/Messengers.subproj/objc-msg-simulator-x86_64.s index a5410c4..9186278 100644 --- a/runtime/Messengers.subproj/objc-msg-simulator-x86_64.s +++ b/runtime/Messengers.subproj/objc-msg-simulator-x86_64.s @@ -93,6 +93,7 @@ _objc_restartableRanges: #define a2b sil #define a3 rdx #define a3d edx +#define a3b dl #define a4 rcx #define a4d ecx #define a5 r8 @@ -212,6 +213,70 @@ LExit$0: #define FrameWithNoSaves 0x01000000 // frame, no non-volatile saves +////////////////////////////////////////////////////////////////////// +// +// SAVE_REGS +// +// Create a stack frame and save all argument registers in preparation +// for a function call. +////////////////////////////////////////////////////////////////////// + +.macro SAVE_REGS + + push %rbp + mov %rsp, %rbp + + sub $$0x80+8, %rsp // +8 for alignment + + movdqa %xmm0, -0x80(%rbp) + push %rax // might be xmm parameter count + movdqa %xmm1, -0x70(%rbp) + push %a1 + movdqa %xmm2, -0x60(%rbp) + push %a2 + movdqa %xmm3, -0x50(%rbp) + push %a3 + movdqa %xmm4, -0x40(%rbp) + push %a4 + movdqa %xmm5, -0x30(%rbp) + push %a5 + movdqa %xmm6, -0x20(%rbp) + push %a6 + movdqa %xmm7, -0x10(%rbp) + +.endmacro + + +////////////////////////////////////////////////////////////////////// +// +// RESTORE_REGS +// +// Restore all argument registers and pop the stack frame created by +// SAVE_REGS. +////////////////////////////////////////////////////////////////////// + +.macro RESTORE_REGS + + movdqa -0x80(%rbp), %xmm0 + pop %a6 + movdqa -0x70(%rbp), %xmm1 + pop %a5 + movdqa -0x60(%rbp), %xmm2 + pop %a4 + movdqa -0x50(%rbp), %xmm3 + pop %a3 + movdqa -0x40(%rbp), %xmm4 + pop %a2 + movdqa -0x30(%rbp), %xmm5 + pop %a1 + movdqa -0x20(%rbp), %xmm6 + pop %rax + movdqa -0x10(%rbp), %xmm7 + leave + +.endmacro + + ///////////////////////////////////////////////////////////////////// // // CacheLookup return-type, caller @@ -347,26 +412,7 @@ LExit$0: .macro MethodTableLookup - push %rbp - mov %rsp, %rbp - - sub $$0x80+8, %rsp // +8 for alignment - - movdqa %xmm0, -0x80(%rbp) - push %rax // might be xmm parameter count - movdqa %xmm1, -0x70(%rbp) - push %a1 - movdqa %xmm2, -0x60(%rbp) - push %a2 - movdqa %xmm3, -0x50(%rbp) - push %a3 - movdqa %xmm4, -0x40(%rbp) - push %a4 - movdqa %xmm5, -0x30(%rbp) - push %a5 - movdqa %xmm6, -0x20(%rbp) - push %a6 - movdqa %xmm7, -0x10(%rbp) + SAVE_REGS // lookUpImpOrForward(obj, sel, cls, LOOKUP_INITIALIZE | LOOKUP_RESOLVER) .if $0 == NORMAL @@ -383,29 +429,13 @@ LExit$0: // IMP is now in %rax movq %rax, %r11 - movdqa -0x80(%rbp), %xmm0 - pop %a6 - movdqa -0x70(%rbp), %xmm1 - pop %a5 - movdqa -0x60(%rbp), %xmm2 - pop %a4 - movdqa -0x50(%rbp), %xmm3 - pop %a3 - movdqa -0x40(%rbp), %xmm4 - pop %a2 - movdqa -0x30(%rbp), %xmm5 - pop %a1 - movdqa -0x20(%rbp), %xmm6 - pop %rax - movdqa -0x10(%rbp), %xmm7 + RESTORE_REGS .if $0 == NORMAL test %r11, %r11 // set ne for stret forwarding .else cmp %r11, %r11 // set eq for nonstret forwarding .endif - - leave .endmacro @@ -1104,19 +1134,51 @@ LCacheMiss: ENTRY _method_invoke + // See if this is a small method. + testb $1, %a2b + jnz L_method_invoke_small + + // We can directly load the IMP from big methods. movq method_imp(%a2), %r11 movq method_name(%a2), %a2 jmp *%r11 - + +L_method_invoke_small: + // Small methods require a call to handle swizzling. + SAVE_REGS + movq %a2, %a1 + call __method_getImplementationAndName + movq %rdx, %r10 + movq %rax, %r11 + RESTORE_REGS + movq %r10, %a2 + jmp *%r11 + END_ENTRY _method_invoke ENTRY _method_invoke_stret + // See if this is a small method. + testb $1, %a3b + jnz L_method_invoke_stret_small + + // We can directly load the IMP from big methods. movq method_imp(%a3), %r11 movq method_name(%a3), %a3 jmp *%r11 - + +L_method_invoke_stret_small: + // Small methods require a call to handle swizzling. + SAVE_REGS + movq %a3, %a1 + call __method_getImplementationAndName + movq %rdx, %r10 + movq %rax, %r11 + RESTORE_REGS + movq %r10, %a3 + jmp *%r11 + END_ENTRY _method_invoke_stret diff --git a/runtime/Messengers.subproj/objc-msg-x86_64.s b/runtime/Messengers.subproj/objc-msg-x86_64.s index 8fc6d48..d090995 100644 --- a/runtime/Messengers.subproj/objc-msg-x86_64.s +++ b/runtime/Messengers.subproj/objc-msg-x86_64.s @@ -102,6 +102,7 @@ _objc_restartableRanges: #define a2b sil #define a3 rdx #define a3d edx +#define a3b dl #define a4 rcx #define a4d ecx #define a5 r8 @@ -218,6 +219,70 @@ LExit$0: #define FrameWithNoSaves 0x01000000 // frame, no non-volatile saves +////////////////////////////////////////////////////////////////////// +// +// SAVE_REGS +// +// Create a stack frame and save all argument registers in preparation +// for a function call. +////////////////////////////////////////////////////////////////////// + +.macro SAVE_REGS + + push %rbp + mov %rsp, %rbp + + sub $$0x80+8, %rsp // +8 for alignment + + movdqa %xmm0, -0x80(%rbp) + push %rax // might be xmm parameter count + movdqa %xmm1, -0x70(%rbp) + push %a1 + movdqa %xmm2, -0x60(%rbp) + push %a2 + movdqa %xmm3, -0x50(%rbp) + push %a3 + movdqa %xmm4, -0x40(%rbp) + push %a4 + movdqa %xmm5, -0x30(%rbp) + push %a5 + movdqa %xmm6, -0x20(%rbp) + push %a6 + movdqa %xmm7, -0x10(%rbp) + +.endmacro + + +////////////////////////////////////////////////////////////////////// +// +// RESTORE_REGS +// +// Restore all argument registers and pop the stack frame created by +// SAVE_REGS. +////////////////////////////////////////////////////////////////////// + +.macro RESTORE_REGS + + movdqa -0x80(%rbp), %xmm0 + pop %a6 + movdqa -0x70(%rbp), %xmm1 + pop %a5 + movdqa -0x60(%rbp), %xmm2 + pop %a4 + movdqa -0x50(%rbp), %xmm3 + pop %a3 + movdqa -0x40(%rbp), %xmm4 + pop %a2 + movdqa -0x30(%rbp), %xmm5 + pop %a1 + movdqa -0x20(%rbp), %xmm6 + pop %rax + movdqa -0x10(%rbp), %xmm7 + leave + +.endmacro + + ///////////////////////////////////////////////////////////////////// // // CacheLookup return-type, caller, function @@ -382,26 +447,7 @@ LLookupEnd$2: .macro MethodTableLookup - push %rbp - mov %rsp, %rbp - - sub $$0x80+8, %rsp // +8 for alignment - - movdqa %xmm0, -0x80(%rbp) - push %rax // might be xmm parameter count - movdqa %xmm1, -0x70(%rbp) - push %a1 - movdqa %xmm2, -0x60(%rbp) - push %a2 - movdqa %xmm3, -0x50(%rbp) - push %a3 - movdqa %xmm4, -0x40(%rbp) - push %a4 - movdqa %xmm5, -0x30(%rbp) - push %a5 - movdqa %xmm6, -0x20(%rbp) - push %a6 - movdqa %xmm7, -0x10(%rbp) + SAVE_REGS // lookUpImpOrForward(obj, sel, cls, LOOKUP_INITIALIZE | LOOKUP_RESOLVER) .if $0 == NORMAL @@ -418,29 +464,13 @@ LLookupEnd$2: // IMP is now in %rax movq %rax, %r11 - movdqa -0x80(%rbp), %xmm0 - pop %a6 - movdqa -0x70(%rbp), %xmm1 - pop %a5 - movdqa -0x60(%rbp), %xmm2 - pop %a4 - movdqa -0x50(%rbp), %xmm3 - pop %a3 - movdqa -0x40(%rbp), %xmm4 - pop %a2 - movdqa -0x30(%rbp), %xmm5 - pop %a1 - movdqa -0x20(%rbp), %xmm6 - pop %rax - movdqa -0x10(%rbp), %xmm7 + RESTORE_REGS .if $0 == NORMAL test %r11, %r11 // set ne for nonstret forwarding .else cmp %r11, %r11 // set eq for stret forwarding .endif - - leave .endmacro @@ -1216,19 +1246,51 @@ LCacheMiss_objc_msgLookupSuper2_stret: ENTRY _method_invoke + // See if this is a small method. + testb $1, %a2b + jnz L_method_invoke_small + + // We can directly load the IMP from big methods. movq method_imp(%a2), %r11 movq method_name(%a2), %a2 jmp *%r11 - + +L_method_invoke_small: + // Small methods require a call to handle swizzling. + SAVE_REGS + movq %a2, %a1 + call __method_getImplementationAndName + movq %rdx, %r10 + movq %rax, %r11 + RESTORE_REGS + movq %r10, %a2 + jmp *%r11 + END_ENTRY _method_invoke ENTRY _method_invoke_stret + // See if this is a small method. + testb $1, %a3b + jnz L_method_invoke_stret_small + + // We can directly load the IMP from big methods. movq method_imp(%a3), %r11 movq method_name(%a3), %a3 jmp *%r11 - + +L_method_invoke_stret_small: + // Small methods require a call to handle swizzling. + SAVE_REGS + movq %a3, %a1 + call __method_getImplementationAndName + movq %rdx, %r10 + movq %rax, %r11 + RESTORE_REGS + movq %r10, %a3 + jmp *%r11 + END_ENTRY _method_invoke_stret diff --git a/runtime/PointerUnion.h b/runtime/PointerUnion.h index 85b7846..1108673 100644 --- a/runtime/PointerUnion.h +++ b/runtime/PointerUnion.h @@ -59,12 +59,12 @@ struct PointerUnionTypeSelectorReturn< typename PointerUnionTypeSelector::Return; }; -template +template class PointerUnion { uintptr_t _value; - static_assert(alignof(PT1) >= 2, "alignment requirement"); - static_assert(alignof(PT2) >= 2, "alignment requirement"); + static_assert(alignof(T1) >= 2, "alignment requirement"); + static_assert(alignof(T2) >= 2, "alignment requirement"); struct IsPT1 { static const uintptr_t Num = 0; @@ -85,8 +85,12 @@ public: explicit PointerUnion(const std::atomic &raw) : _value(raw.load(std::memory_order_relaxed)) { } - PointerUnion(PT1 t) : _value((uintptr_t)t) { } - PointerUnion(PT2 t) : _value((uintptr_t)t | 1) { } + PointerUnion(T1 *t, const void *address) { + _value = (uintptr_t)Auth1::sign(t, address); + } + PointerUnion(T2 *t, const void *address) { + _value = (uintptr_t)Auth2::sign(t, address) | 1; + } void storeAt(std::atomic &raw, std::memory_order order) const { raw.store(_value, order); @@ -94,20 +98,24 @@ public: template bool is() const { - using Ty = typename PointerUnionTypeSelector>>::Return; return getTag() == Ty::Num; } - template T get() const { - ASSERT(is() && "Invalid accessor called"); - return reinterpret_cast(getPointer()); + template T get(const void *address) const { + ASSERT(is() && "Invalid accessor called"); + using AuthT = typename PointerUnionTypeSelector>>::Return; + + return AuthT::auth((T)getPointer(), address); } - template T dyn_cast() const { + template T dyn_cast(const void *address) const { if (is()) - return get(); + return get(address); return T(); } }; diff --git a/runtime/objc-cache.mm b/runtime/objc-cache.mm index 4e6ca11..7656391 100644 --- a/runtime/objc-cache.mm +++ b/runtime/objc-cache.mm @@ -99,6 +99,18 @@ static void cache_collect_free(struct bucket_t *data, mask_t capacity); static int _collecting_in_critical(void); static void _garbage_make_room(void); +#if DEBUG_TASK_THREADS +static kern_return_t objc_task_threads +( + task_t target_task, + thread_act_array_t *act_list, + mach_msg_type_number_t *act_listCnt +); +#endif + +#if DEBUG_TASK_THREADS +#undef HAVE_TASK_RESTARTABLE_RANGES +#endif /*********************************************************************** * Cache statistics for OBJC_PRINT_CACHE_SETUP diff --git a/runtime/objc-file.h b/runtime/objc-file.h index 23c0da1..3dc54c7 100644 --- a/runtime/objc-file.h +++ b/runtime/objc-file.h @@ -38,10 +38,14 @@ extern message_ref_t *_getObjc2MessageRefs(const header_info *hi, size_t *count) extern Class*_getObjc2ClassRefs(const header_info *hi, size_t *count); extern Class*_getObjc2SuperRefs(const header_info *hi, size_t *count); extern classref_t const *_getObjc2ClassList(const header_info *hi, size_t *count); -extern classref_t const *_getObjc2NonlazyClassList(const header_info *hi, size_t *count); -extern category_t * const *_getObjc2CategoryList(const header_info *hi, size_t *count); -extern category_t * const *_getObjc2CategoryList2(const header_info *hi, size_t *count); -extern category_t * const *_getObjc2NonlazyCategoryList(const header_info *hi, size_t *count); +// Use hi->nlclslist() instead +// extern classref_t const *_getObjc2NonlazyClassList(const header_info *hi, size_t *count); +// Use hi->catlist() instead +// extern category_t * const *_getObjc2CategoryList(const header_info *hi, size_t *count); +// Use hi->catlist2() instead +// extern category_t * const *_getObjc2CategoryList2(const header_info *hi, size_t *count); +// Use hi->nlcatlist() instead +// extern category_t * const *_getObjc2NonlazyCategoryList(const header_info *hi, size_t *count); extern protocol_t * const *_getObjc2ProtocolList(const header_info *hi, size_t *count); extern protocol_t **_getObjc2ProtocolRefs(const header_info *hi, size_t *count); @@ -62,6 +66,8 @@ public: extern UnsignedInitializer *getLibobjcInitializers(const header_info *hi, size_t *count); extern classref_t const *_getObjc2NonlazyClassList(const headerType *mhdr, size_t *count); +extern category_t * const *_getObjc2CategoryList(const headerType *mhdr, size_t *count); +extern category_t * const *_getObjc2CategoryList2(const headerType *mhdr, size_t *count); extern category_t * const *_getObjc2NonlazyCategoryList(const headerType *mhdr, size_t *count); extern UnsignedInitializer *getLibobjcInitializers(const headerType *mhdr, size_t *count); diff --git a/runtime/objc-internal.h b/runtime/objc-internal.h index afc82f5..112804d 100644 --- a/runtime/objc-internal.h +++ b/runtime/objc-internal.h @@ -287,6 +287,8 @@ enum OBJC_TAG_UIColor = 17, OBJC_TAG_CGColor = 18, OBJC_TAG_NSIndexSet = 19, + OBJC_TAG_NSMethodSignature = 20, + OBJC_TAG_UTTypeRecord = 21, OBJC_TAG_First60BitPayload = 0, OBJC_TAG_Last60BitPayload = 6, @@ -944,7 +946,7 @@ typedef enum { } \ } \ -(NSUInteger)retainCount { \ - return (_rc_ivar + 2) >> 1; \ + return (NSUInteger)(_rc_ivar + 2) >> 1; \ } \ -(BOOL)_tryRetain { \ __typeof__(_rc_ivar) _prev; \ @@ -966,12 +968,12 @@ typedef enum { } else if (_rc_ivar < -2) { \ __builtin_trap(); /* BUG: over-release elsewhere */ \ } \ - return _rc_ivar & 1; \ + return (_rc_ivar & 1) != 0; \ } #define _OBJC_SUPPORTED_INLINE_REFCNT_LOGIC(_rc_ivar, _dealloc2main) \ _OBJC_SUPPORTED_INLINE_REFCNT_LOGIC_BLOCK(_rc_ivar, (^(id _self_ __attribute__((unused))) { \ - if (_dealloc2main && !pthread_main_np()) { \ + if ((_dealloc2main) && !pthread_main_np()) { \ return _OBJC_DEALLOC_OBJECT_LATER; \ } else { \ return _OBJC_DEALLOC_OBJECT_NOW; \ diff --git a/runtime/objc-opt.mm b/runtime/objc-opt.mm index f4afb20..b21869b 100644 --- a/runtime/objc-opt.mm +++ b/runtime/objc-opt.mm @@ -27,13 +27,13 @@ */ #include "objc-private.h" +#include "objc-os.h" +#include "objc-file.h" #if !SUPPORT_PREOPT // Preoptimization not supported on this platform. -struct objc_selopt_t; - bool isPreoptimized(void) { return false; @@ -64,16 +64,6 @@ bool header_info::hasPreoptimizedProtocols() const return false; } -objc_selopt_t *preoptimizedSelectors(void) -{ - return nil; -} - -bool sharedCacheSupportsProtocolRoots(void) -{ - return false; -} - Protocol *getPreoptimizedProtocol(const char *name) { return nil; @@ -123,7 +113,6 @@ void preopt_init(void) #include using objc_opt::objc_stringhash_offset_t; -using objc_opt::objc_protocolopt_t; using objc_opt::objc_protocolopt2_t; using objc_opt::objc_clsopt_t; using objc_opt::objc_headeropt_ro_t; @@ -141,6 +130,62 @@ static bool preoptimized; extern const objc_opt_t _objc_opt_data; // in __TEXT, __objc_opt_ro +namespace objc_opt { +struct objc_headeropt_ro_t { + uint32_t count; + uint32_t entsize; + header_info headers[0]; // sorted by mhdr address + + header_info& getOrEnd(uint32_t i) const { + ASSERT(i <= count); + return *(header_info *)((uint8_t *)&headers + (i * entsize)); + } + + header_info& get(uint32_t i) const { + ASSERT(i < count); + return *(header_info *)((uint8_t *)&headers + (i * entsize)); + } + + uint32_t index(const header_info* hi) const { + const header_info* begin = &get(0); + const header_info* end = &getOrEnd(count); + ASSERT(hi >= begin && hi < end); + return (uint32_t)(((uintptr_t)hi - (uintptr_t)begin) / entsize); + } + + header_info *get(const headerType *mhdr) + { + int32_t start = 0; + int32_t end = count; + while (start <= end) { + int32_t i = (start+end)/2; + header_info &hi = get(i); + if (mhdr == hi.mhdr()) return &hi; + else if (mhdr < hi.mhdr()) end = i-1; + else start = i+1; + } + +#if DEBUG + for (uint32_t i = 0; i < count; i++) { + header_info &hi = get(i); + if (mhdr == hi.mhdr()) { + _objc_fatal("failed to find header %p (%d/%d)", + mhdr, i, count); + } + } +#endif + + return nil; + } +}; + +struct objc_headeropt_rw_t { + uint32_t count; + uint32_t entsize; + header_info_rw headers[0]; // sorted by mhdr address +}; +}; + /*********************************************************************** * Return YES if we have a valid optimized shared cache. **********************************************************************/ @@ -199,38 +244,114 @@ bool header_info::hasPreoptimizedProtocols() const return info()->optimizedByDyld() || info()->optimizedByDyldClosure(); } +bool header_info::hasPreoptimizedSectionLookups() const +{ + objc_opt::objc_headeropt_ro_t *hinfoRO = opt->headeropt_ro(); + if (hinfoRO->entsize == (2 * sizeof(intptr_t))) + return NO; -objc_selopt_t *preoptimizedSelectors(void) + return YES; +} + +const classref_t *header_info::nlclslist(size_t *outCount) const { - return opt ? opt->selopt() : nil; +#if __OBJC2__ + // This field is new, so temporarily be resilient to the shared cache + // not generating it + if (isPreoptimized() && hasPreoptimizedSectionLookups()) { + *outCount = nlclslist_count; + const classref_t *list = (const classref_t *)(((intptr_t)&nlclslist_offset) + nlclslist_offset); + #if DEBUG + size_t debugCount; + assert((list == _getObjc2NonlazyClassList(mhdr(), &debugCount)) && (*outCount == debugCount)); + #endif + return list; + } + return _getObjc2NonlazyClassList(mhdr(), outCount); +#else + return NULL; +#endif } -bool sharedCacheSupportsProtocolRoots(void) +category_t * const *header_info::nlcatlist(size_t *outCount) const { - return (opt != nil) && (opt->protocolopt2() != nil); +#if __OBJC2__ + // This field is new, so temporarily be resilient to the shared cache + // not generating it + if (isPreoptimized() && hasPreoptimizedSectionLookups()) { + *outCount = nlcatlist_count; + category_t * const *list = (category_t * const *)(((intptr_t)&nlcatlist_offset) + nlcatlist_offset); + #if DEBUG + size_t debugCount; + assert((list == _getObjc2NonlazyCategoryList(mhdr(), &debugCount)) && (*outCount == debugCount)); + #endif + return list; + } + return _getObjc2NonlazyCategoryList(mhdr(), outCount); +#else + return NULL; +#endif } +category_t * const *header_info::catlist(size_t *outCount) const +{ +#if __OBJC2__ + // This field is new, so temporarily be resilient to the shared cache + // not generating it + if (isPreoptimized() && hasPreoptimizedSectionLookups()) { + *outCount = catlist_count; + category_t * const *list = (category_t * const *)(((intptr_t)&catlist_offset) + catlist_offset); + #if DEBUG + size_t debugCount; + assert((list == _getObjc2CategoryList(mhdr(), &debugCount)) && (*outCount == debugCount)); + #endif + return list; + } + return _getObjc2CategoryList(mhdr(), outCount); +#else + return NULL; +#endif +} -Protocol *getSharedCachePreoptimizedProtocol(const char *name) +category_t * const *header_info::catlist2(size_t *outCount) const { - // Look in the new table if we have it - if (objc_protocolopt2_t *protocols2 = opt ? opt->protocolopt2() : nil) { - // Note, we have to pass the lambda directly here as otherwise we would try - // message copy and autorelease. - return (Protocol *)protocols2->getProtocol(name, [](const void* hi) -> bool { - return ((header_info *)hi)->isLoaded(); - }); +#if __OBJC2__ + // This field is new, so temporarily be resilient to the shared cache + // not generating it + if (isPreoptimized() && hasPreoptimizedSectionLookups()) { + *outCount = catlist2_count; + category_t * const *list = (category_t * const *)(((intptr_t)&catlist2_offset) + catlist2_offset); + #if DEBUG + size_t debugCount; + assert((list == _getObjc2CategoryList2(mhdr(), &debugCount)) && (*outCount == debugCount)); + #endif + return list; } + return _getObjc2CategoryList2(mhdr(), outCount); +#else + return NULL; +#endif +} + - objc_protocolopt_t *protocols = opt ? opt->protocolopt() : nil; +Protocol *getSharedCachePreoptimizedProtocol(const char *name) +{ + objc_protocolopt2_t *protocols = opt ? opt->protocolopt2() : nil; if (!protocols) return nil; - return (Protocol *)protocols->getProtocol(name); + // Note, we have to pass the lambda directly here as otherwise we would try + // message copy and autorelease. + return (Protocol *)protocols->getProtocol(name, [](const void* hi) -> bool { + return ((header_info *)hi)->isLoaded(); + }); } Protocol *getPreoptimizedProtocol(const char *name) { + objc_protocolopt2_t *protocols = opt ? opt->protocolopt2() : nil; + if (!protocols) return nil; + // Try table from dyld closure first. It was built to ignore the dupes it // knows will come from the cache, so anything left in here was there when // we launched @@ -354,47 +475,6 @@ Class* copyPreoptimizedClasses(const char *name, int *outCount) return nil; } -namespace objc_opt { -struct objc_headeropt_ro_t { - uint32_t count; - uint32_t entsize; - header_info headers[0]; // sorted by mhdr address - - header_info *get(const headerType *mhdr) - { - ASSERT(entsize == sizeof(header_info)); - - int32_t start = 0; - int32_t end = count; - while (start <= end) { - int32_t i = (start+end)/2; - header_info *hi = headers+i; - if (mhdr == hi->mhdr()) return hi; - else if (mhdr < hi->mhdr()) end = i-1; - else start = i+1; - } - -#if DEBUG - for (uint32_t i = 0; i < count; i++) { - header_info *hi = headers+i; - if (mhdr == hi->mhdr()) { - _objc_fatal("failed to find header %p (%d/%d)", - mhdr, i, count); - } - } -#endif - - return nil; - } -}; - -struct objc_headeropt_rw_t { - uint32_t count; - uint32_t entsize; - header_info_rw headers[0]; // sorted by mhdr address -}; -}; - header_info *preoptimizedHinfoForHeader(const headerType *mhdr) { @@ -422,7 +502,7 @@ header_info_rw *getPreoptimizedHeaderRW(const struct header_info *const hdr) _objc_fatal("preoptimized header_info missing for %s (%p %p %p)", hdr->fname(), hdr, hinfoRO, hinfoRW); } - int32_t index = (int32_t)(hdr - hinfoRO->headers); + int32_t index = hinfoRO->index(hdr); ASSERT(hinfoRW->entsize == sizeof(header_info_rw)); return &hinfoRW->headers[index]; } diff --git a/runtime/objc-os.h b/runtime/objc-os.h index c28ba05..5a06252 100644 --- a/runtime/objc-os.h +++ b/runtime/objc-os.h @@ -1076,6 +1076,7 @@ ustrdupMaybeNil(const uint8_t *str) (unsigned char)(((uint32_t)(v))>>8), \ (unsigned char)(((uint32_t)(v))>>0) +#ifndef __BUILDING_OBJCDT__ // fork() safety requires careful tracking of all locks. // Our custom lock types check this in debug builds. // Disallow direct use of all other lock types. @@ -1083,6 +1084,6 @@ typedef __darwin_pthread_mutex_t pthread_mutex_t UNAVAILABLE_ATTRIBUTE; typedef __darwin_pthread_rwlock_t pthread_rwlock_t UNAVAILABLE_ATTRIBUTE; typedef int32_t OSSpinLock UNAVAILABLE_ATTRIBUTE; typedef struct os_unfair_lock_s os_unfair_lock UNAVAILABLE_ATTRIBUTE; - +#endif #endif diff --git a/runtime/objc-os.mm b/runtime/objc-os.mm index 7d600ef..db021d0 100644 --- a/runtime/objc-os.mm +++ b/runtime/objc-os.mm @@ -492,11 +492,16 @@ map_images_nolock(unsigned mhCount, const char * const mhPaths[], if (mhdr->filetype == MH_EXECUTE) { // Size some data structures based on main executable's size #if __OBJC2__ - size_t count; - _getObjc2SelectorRefs(hi, &count); - selrefCount += count; - _getObjc2MessageRefs(hi, &count); - selrefCount += count; + // If dyld3 optimized the main executable, then there shouldn't + // be any selrefs needed in the dynamic map so we can just init + // to a 0 sized map + if ( !hi->hasPreoptimizedSelectors() ) { + size_t count; + _getObjc2SelectorRefs(hi, &count); + selrefCount += count; + _getObjc2MessageRefs(hi, &count); + selrefCount += count; + } #else _getObjcSelectorRefs(hi, &selrefCount); #endif diff --git a/runtime/objc-private.h b/runtime/objc-private.h index 4d7aab2..bf2a8de 100644 --- a/runtime/objc-private.h +++ b/runtime/objc-private.h @@ -55,9 +55,11 @@ struct objc_class; struct objc_object; +struct category_t; typedef struct objc_class *Class; typedef struct objc_object *id; +typedef struct classref *classref_t; namespace { struct SideTable; @@ -241,14 +243,6 @@ typedef struct old_property *objc_property_t; #include "objc-loadmethod.h" -#if SUPPORT_PREOPT && __cplusplus -#include -using objc_selopt_t = const objc_opt::objc_selopt_t; -#else -struct objc_selopt_t; -#endif - - #define STRINGIFY(x) #x #define STRINGIFY2(x) STRINGIFY(x) @@ -358,6 +352,22 @@ private: // from this location. intptr_t info_offset; + // Offset from this location to the non-lazy class list + intptr_t nlclslist_offset; + uintptr_t nlclslist_count; + + // Offset from this location to the non-lazy category list + intptr_t nlcatlist_offset; + uintptr_t nlcatlist_count; + + // Offset from this location to the category list + intptr_t catlist_offset; + uintptr_t catlist_count; + + // Offset from this location to the category list 2 + intptr_t catlist2_offset; + uintptr_t catlist2_count; + // Do not add fields without editing ObjCModernAbstraction.hpp public: @@ -384,6 +394,30 @@ public: info_offset = (intptr_t)info - (intptr_t)&info_offset; } + const classref_t *nlclslist(size_t *outCount) const; + + void set_nlclslist(const void *list) { + nlclslist_offset = (intptr_t)list - (intptr_t)&nlclslist_offset; + } + + category_t * const *nlcatlist(size_t *outCount) const; + + void set_nlcatlist(const void *list) { + nlcatlist_offset = (intptr_t)list - (intptr_t)&nlcatlist_offset; + } + + category_t * const *catlist(size_t *outCount) const; + + void set_catlist(const void *list) { + catlist_offset = (intptr_t)list - (intptr_t)&catlist_offset; + } + + category_t * const *catlist2(size_t *outCount) const; + + void set_catlist2(const void *list) { + catlist2_offset = (intptr_t)list - (intptr_t)&catlist2_offset; + } + bool isLoaded() { return getHeaderInfoRW()->getLoaded(); } @@ -424,6 +458,8 @@ public: bool hasPreoptimizedProtocols() const; + bool hasPreoptimizedSectionLookups() const; + #if !__OBJC2__ struct old_protocol **proto_refs; struct objc_module *mod_ptr; @@ -497,9 +533,6 @@ extern bool isPreoptimized(void); extern bool noMissingWeakSuperclasses(void); extern header_info *preoptimizedHinfoForHeader(const headerType *mhdr); -extern objc_selopt_t *preoptimizedSelectors(void); - -extern bool sharedCacheSupportsProtocolRoots(void); extern Protocol *getPreoptimizedProtocol(const char *name); extern Protocol *getSharedCachePreoptimizedProtocol(const char *name); @@ -525,6 +558,14 @@ lookUpImpOrNil(id obj, SEL sel, Class cls, int behavior = 0) } extern IMP lookupMethodInClassAndLoadCache(Class cls, SEL sel); + +struct IMPAndSEL { + IMP imp; + SEL sel; +}; + +extern IMPAndSEL _method_getImplementationAndName(Method m); + extern BOOL class_respondsToSelector_inst(id inst, SEL sel, Class cls); extern Class class_initialize(Class cls, id inst); @@ -971,7 +1012,7 @@ class ChainedHookFunction { std::atomic hook{nil}; public: - ChainedHookFunction(Fn f) : hook{f} { }; + constexpr ChainedHookFunction(Fn f) : hook{f} { }; Fn get() { return hook.load(std::memory_order_acquire); @@ -990,10 +1031,10 @@ public: // A small vector for use as a global variable. Only supports appending and -// iteration. Stores a single element inline, and multiple elements in a heap +// iteration. Stores up to N elements inline, and multiple elements in a heap // allocation. There is no attempt to amortize reallocation cost; this is -// intended to be used in situation where zero or one element is common, two -// might happen, and three or more is very rare. +// intended to be used in situation where a small number of elements is +// common, more might happen, and significantly more is very rare. // // This does not clean up its allocation, and thus cannot be used as a local // variable or member of something with limited lifetime. @@ -1006,7 +1047,7 @@ protected: unsigned count{0}; union { T inlineElements[InlineCount]; - T *elements; + T *elements{nullptr}; }; public: diff --git a/runtime/objc-ptrauth.h b/runtime/objc-ptrauth.h index e275dca..388abe6 100644 --- a/runtime/objc-ptrauth.h +++ b/runtime/objc-ptrauth.h @@ -76,5 +76,119 @@ using MethodListIMP = IMP; #endif +// A struct that wraps a pointer using the provided template. +// The provided Auth parameter is used to sign and authenticate +// the pointer as it is read and written. +template +struct WrappedPtr { +private: + T *ptr; + +public: + WrappedPtr(T *p) { + *this = p; + } + + WrappedPtr(const WrappedPtr &p) { + *this = p; + } + + WrappedPtr &operator =(T *p) { + ptr = Auth::sign(p, &ptr); + return *this; + } + + WrappedPtr &operator =(const WrappedPtr &p) { + *this = (T *)p; + return *this; + } + + operator T*() const { return get(); } + T *operator->() const { return get(); } + + T *get() const { return Auth::auth(ptr, &ptr); } + + // When asserts are enabled, ensure that we can read a byte from + // the underlying pointer. This can be used to catch ptrauth + // errors early for easier debugging. + void validate() const { +#if !NDEBUG + char *p = (char *)get(); + char dummy; + memset_s(&dummy, 1, *p, 1); + ASSERT(dummy == *p); +#endif + } +}; + +// A "ptrauth" struct that just passes pointers through unchanged. +struct PtrauthRaw { + template + static T *sign(T *ptr, const void *address) { + return ptr; + } + + template + static T *auth(T *ptr, const void *address) { + return ptr; + } +}; + +// A ptrauth struct that stores pointers raw, and strips ptrauth +// when reading. +struct PtrauthStrip { + template + static T *sign(T *ptr, const void *address) { + return ptr; + } + + template + static T *auth(T *ptr, const void *address) { + return ptrauth_strip(ptr, ptrauth_key_process_dependent_data); + } +}; + +// A ptrauth struct that signs and authenticates pointers using the +// DB key with the given discriminator and address diversification. +template +struct Ptrauth { + template + static T *sign(T *ptr, const void *address) { + if (!ptr) + return nullptr; + return ptrauth_sign_unauthenticated(ptr, ptrauth_key_process_dependent_data, ptrauth_blend_discriminator(address, discriminator)); + } + + template + static T *auth(T *ptr, const void *address) { + if (!ptr) + return nullptr; + return ptrauth_auth_data(ptr, ptrauth_key_process_dependent_data, ptrauth_blend_discriminator(address, discriminator)); + } +}; + +// A template that produces a WrappedPtr to the given type using a +// plain unauthenticated pointer. +template using RawPtr = WrappedPtr; + +#if __has_feature(ptrauth_calls) +// Get a ptrauth type that uses a string discriminator. +#define PTRAUTH_STR(name) Ptrauth + +// When ptrauth is available, declare a template that wraps a type +// in a WrappedPtr that uses an authenticated pointer using the +// process-dependent data key, address diversification, and a +// discriminator based on the name passed in. +// +// When ptrauth is not available, equivalent to RawPtr. +#define DECLARE_AUTHED_PTR_TEMPLATE(name) \ + template using name ## _authed_ptr \ + = WrappedPtr; +#else +#define PTRAUTH_STR(name) PtrauthRaw +#define DECLARE_AUTHED_PTR_TEMPLATE(name) \ + template using name ## _authed_ptr = RawPtr; +#endif + // _OBJC_PTRAUTH_H_ #endif diff --git a/runtime/objc-runtime-new.h b/runtime/objc-runtime-new.h index d3541cf..d6ce37c 100644 --- a/runtime/objc-runtime-new.h +++ b/runtime/objc-runtime-new.h @@ -240,6 +240,19 @@ private: public: inline SEL sel() const { return _sel.load(memory_order::memory_order_relaxed); } + inline IMP rawImp(objc_class *cls) const { + uintptr_t imp = _imp.load(memory_order::memory_order_relaxed); + if (!imp) return nil; +#if CACHE_IMP_ENCODING == CACHE_IMP_ENCODING_PTRAUTH +#elif CACHE_IMP_ENCODING == CACHE_IMP_ENCODING_ISA_XOR + imp ^= (uintptr_t)cls; +#elif CACHE_IMP_ENCODING == CACHE_IMP_ENCODING_NONE +#else +#error Unknown method cache IMP encoding. +#endif + return (IMP)imp; + } + inline IMP imp(Class cls) const { uintptr_t imp = _imp.load(memory_order::memory_order_relaxed); if (!imp) return nil; @@ -398,6 +411,27 @@ public: typedef struct classref * classref_t; +/*********************************************************************** +* RelativePointer +* A pointer stored as an offset from the address of that offset. +* +* The target address is computed by taking the address of this struct +* and adding the offset stored within it. This is a 32-bit signed +* offset giving ±2GB of range. +**********************************************************************/ +template +struct RelativePointer: nocopy_t { + int32_t offset; + + T get() const { + uintptr_t base = (uintptr_t)&offset; + uintptr_t signExtendedOffset = (uintptr_t)(intptr_t)offset; + uintptr_t pointer = base + signExtendedOffset; + return (T)pointer; + } +}; + + #ifdef __PTRAUTH_INTRINSICS__ # define StubClassInitializerPtrauth __ptrauth(ptrauth_key_function_pointer, 1, 0xc671) #else @@ -408,20 +442,27 @@ struct stub_class_t { _objc_swiftMetadataInitializer StubClassInitializerPtrauth initializer; }; +// A pointer modifier that does nothing to the pointer. +struct PointerModifierNop { + template + static T *modify(const ListType &list, T *ptr) { return ptr; } +}; + /*********************************************************************** -* entsize_list_tt +* entsize_list_tt * Generic implementation of an array of non-fragile structs. * * Element is the struct type (e.g. method_t) * List is the specialization of entsize_list_tt (e.g. method_list_t) * FlagMask is used to stash extra bits in the entsize field * (e.g. method list fixup markers) +* PointerModifier is applied to the element pointers retrieved from +* the array. **********************************************************************/ -template +template struct entsize_list_tt { uint32_t entsizeAndFlags; uint32_t count; - Element first; uint32_t entsize() const { return entsizeAndFlags & ~FlagMask; @@ -432,7 +473,7 @@ struct entsize_list_tt { Element& getOrEnd(uint32_t i) const { ASSERT(i <= count); - return *(Element *)((uint8_t *)&first + i*entsize()); + return *PointerModifier::modify(*this, (Element *)((uint8_t *)this + sizeof(*this) + i*entsize())); } Element& get(uint32_t i) const { ASSERT(i < count); @@ -444,15 +485,7 @@ struct entsize_list_tt { } static size_t byteSize(uint32_t entsize, uint32_t count) { - return sizeof(entsize_list_tt) + (count-1)*entsize; - } - - List *duplicate() const { - auto *dup = (List *)calloc(this->byteSize(), 1); - dup->entsizeAndFlags = this->entsizeAndFlags; - dup->count = this->count; - std::copy(begin(), end(), dup->begin()); - return dup; + return sizeof(entsize_list_tt) + count*entsize; } struct iterator; @@ -541,18 +574,109 @@ struct entsize_list_tt { struct method_t { - SEL name; - const char *types; - MethodListIMP imp; + static const uint32_t smallMethodListFlag = 0x80000000; + + method_t(const method_t &other) = delete; + + // The representation of a "big" method. This is the traditional + // representation of three pointers storing the selector, types + // and implementation. + struct big { + SEL name; + const char *types; + MethodListIMP imp; + }; + +private: + bool isSmall() const { + return ((uintptr_t)this & 1) == 1; + } + + // The representation of a "small" method. This stores three + // relative offsets to the name, types, and implementation. + struct small { + RelativePointer name; + RelativePointer types; + RelativePointer imp; + }; + + small &small() const { + ASSERT(isSmall()); + return *(struct small *)((uintptr_t)this & ~(uintptr_t)1); + } + + IMP remappedImp(bool needsLock) const; + void remapImp(IMP imp); + objc_method_description *getSmallDescription() const; + +public: + static const auto bigSize = sizeof(struct big); + static const auto smallSize = sizeof(struct small); + + // The pointer modifier used with method lists. When the method + // list contains small methods, set the bottom bit of the pointer. + // We use that bottom bit elsewhere to distinguish between big + // and small methods. + struct pointer_modifier { + template + static method_t *modify(const ListType &list, method_t *ptr) { + if (list.flags() & smallMethodListFlag) + return (method_t *)((uintptr_t)ptr | 1); + return ptr; + } + }; + + big &big() const { + ASSERT(!isSmall()); + return *(struct big *)this; + } + + SEL &name() const { + return isSmall() ? *small().name.get() : big().name; + } + const char *types() const { + return isSmall() ? small().types.get() : big().types; + } + IMP imp(bool needsLock) const { + if (isSmall()) { + IMP imp = remappedImp(needsLock); + if (!imp) + imp = ptrauth_sign_unauthenticated(small().imp.get(), + ptrauth_key_function_pointer, 0); + return imp; + } + return big().imp; + } + + void setImp(IMP imp) { + if (isSmall()) { + remapImp(imp); + } else { + big().imp = imp; + } + + } + + objc_method_description *getDescription() const { + return isSmall() ? getSmallDescription() : (struct objc_method_description *)this; + } struct SortBySELAddress : - public std::binary_function + public std::binary_function { - bool operator() (const method_t& lhs, - const method_t& rhs) + bool operator() (const struct method_t::big& lhs, + const struct method_t::big& rhs) { return lhs.name < rhs.name; } }; + + method_t &operator=(const method_t &other) { + ASSERT(!isSmall()); + big().name = other.name(); + big().types = other.types(); + big().imp = other.imp(false); + return *this; + } }; struct ivar_t { @@ -583,7 +707,15 @@ struct property_t { }; // Two bits of entsize are used for fixup markers. -struct method_list_t : entsize_list_tt { +// Reserve the top half of entsize for more flags. We never +// need entry sizes anywhere close to 64kB. +// +// Currently there is one flag defined: the small method list flag, +// method_t::smallMethodListFlag. Other flags are currently ignored. +// (NOTE: these bits are only ignored on runtimes that support small +// method lists. Older runtimes will treat them as part of the entry +// size!) +struct method_list_t : entsize_list_tt { bool isUniqued() const; bool isFixedUp() const; void setFixedUp(); @@ -594,6 +726,31 @@ struct method_list_t : entsize_list_tt { ASSERT(i < count); return i; } + + bool isSmallList() const { + return flags() & method_t::smallMethodListFlag; + } + + bool isExpectedSize() const { + if (isSmallList()) + return entsize() == method_t::smallSize; + else + return entsize() == method_t::bigSize; + } + + method_list_t *duplicate() const { + method_list_t *dup; + if (isSmallList()) { + dup = (method_list_t *)calloc(byteSize(method_t::bigSize, count), 1); + dup->entsizeAndFlags = method_t::bigSize; + } else { + dup = (method_list_t *)calloc(this->byteSize(), 1); + dup->entsizeAndFlags = this->entsizeAndFlags; + } + dup->count = this->count; + std::copy(begin(), end(), dup->begin()); + return dup; + } }; struct ivar_list_t : entsize_list_tt { @@ -707,7 +864,7 @@ struct class_ro_t { const uint8_t * ivarLayout; const char * name; - method_list_t * baseMethodList; + WrappedPtr baseMethodList; protocol_list_t * baseProtocols; const ivar_list_t * ivars; @@ -745,11 +902,13 @@ struct class_ro_t { /*********************************************************************** -* list_array_tt +* list_array_tt * Generic implementation for metadata that can be augmented by categories. * * Element is the underlying metadata type (e.g. method_t) * List is the metadata's list type (e.g. method_list_t) +* List is a template applied to Element to make Element*. Useful for +* applying qualifiers to the pointer type. * * A list_array_tt has one of three values: * - empty @@ -759,11 +918,11 @@ struct class_ro_t { * countLists/beginLists/endLists iterate the metadata lists * count/begin/end iterate the underlying metadata elements **********************************************************************/ -template +template class Ptr> class list_array_tt { struct array_t { uint32_t count; - List* lists[0]; + Ptr lists[0]; static size_t byteSize(uint32_t count) { return sizeof(array_t) + count*sizeof(lists[0]); @@ -775,12 +934,12 @@ class list_array_tt { protected: class iterator { - List * const *lists; - List * const *listsEnd; + const Ptr *lists; + const Ptr *listsEnd; typename List::iterator m, mEnd; public: - iterator(List *const *begin, List *const *end) + iterator(const Ptr *begin, const Ptr *end) : lists(begin), listsEnd(end) { if (begin != end) { @@ -820,7 +979,7 @@ class list_array_tt { private: union { - List* list; + Ptr list; uintptr_t arrayAndFlag; }; @@ -836,9 +995,26 @@ class list_array_tt { arrayAndFlag = (uintptr_t)array | 1; } + void validate() { + for (auto cursor = beginLists(), end = endLists(); cursor != end; cursor++) + cursor->validate(); + } + public: list_array_tt() : list(nullptr) { } list_array_tt(List *l) : list(l) { } + list_array_tt(const list_array_tt &other) { + *this = other; + } + + list_array_tt &operator =(const list_array_tt &other) { + if (other.hasArray()) { + arrayAndFlag = other.arrayAndFlag; + } else { + list = other.list; + } + return *this; + } uint32_t count() const { uint32_t result = 0; @@ -856,7 +1032,7 @@ class list_array_tt { } iterator end() const { - List * const *e = endLists(); + auto e = endLists(); return iterator(e, e); } @@ -871,7 +1047,7 @@ class list_array_tt { } } - List* const * beginLists() const { + const Ptr* beginLists() const { if (hasArray()) { return array()->lists; } else { @@ -879,7 +1055,7 @@ class list_array_tt { } } - List* const * endLists() const { + const Ptr* endLists() const { if (hasArray()) { return array()->lists + array()->count; } else if (list) { @@ -896,27 +1072,34 @@ class list_array_tt { // many lists -> many lists uint32_t oldCount = array()->count; uint32_t newCount = oldCount + addedCount; - setArray((array_t *)realloc(array(), array_t::byteSize(newCount))); + array_t *newArray = (array_t *)malloc(array_t::byteSize(newCount)); + newArray->count = newCount; array()->count = newCount; - memmove(array()->lists + addedCount, array()->lists, - oldCount * sizeof(array()->lists[0])); - memcpy(array()->lists, addedLists, - addedCount * sizeof(array()->lists[0])); + + for (int i = oldCount - 1; i >= 0; i--) + newArray->lists[i + addedCount] = array()->lists[i]; + for (unsigned i = 0; i < addedCount; i++) + newArray->lists[i] = addedLists[i]; + free(array()); + setArray(newArray); + validate(); } else if (!list && addedCount == 1) { // 0 lists -> 1 list list = addedLists[0]; + validate(); } else { // 1 list -> many lists - List* oldList = list; + Ptr oldList = list; uint32_t oldCount = oldList ? 1 : 0; uint32_t newCount = oldCount + addedCount; setArray((array_t *)malloc(array_t::byteSize(newCount))); array()->count = newCount; if (oldList) array()->lists[addedCount] = oldList; - memcpy(array()->lists, addedLists, - addedCount * sizeof(array()->lists[0])); + for (unsigned i = 0; i < addedCount; i++) + array()->lists[i] = addedLists[i]; + validate(); } } @@ -932,79 +1115,66 @@ class list_array_tt { } } - template - Result duplicate() { - Result result; - + template + void duplicateInto(Other &other) { if (hasArray()) { array_t *a = array(); - result.setArray((array_t *)memdup(a, a->byteSize())); + other.setArray((array_t *)memdup(a, a->byteSize())); for (uint32_t i = 0; i < a->count; i++) { - result.array()->lists[i] = a->lists[i]->duplicate(); + other.array()->lists[i] = a->lists[i]->duplicate(); } } else if (list) { - result.list = list->duplicate(); + other.list = list->duplicate(); } else { - result.list = nil; + other.list = nil; } - - return result; } }; +DECLARE_AUTHED_PTR_TEMPLATE(method_list_t) + class method_array_t : - public list_array_tt + public list_array_tt { - typedef list_array_tt Super; + typedef list_array_tt Super; public: method_array_t() : Super() { } method_array_t(method_list_t *l) : Super(l) { } - method_list_t * const *beginCategoryMethodLists() const { + const method_list_t_authed_ptr *beginCategoryMethodLists() const { return beginLists(); } - method_list_t * const *endCategoryMethodLists(Class cls) const; - - method_array_t duplicate() { - return Super::duplicate(); - } + const method_list_t_authed_ptr *endCategoryMethodLists(Class cls) const; }; class property_array_t : - public list_array_tt + public list_array_tt { - typedef list_array_tt Super; + typedef list_array_tt Super; public: property_array_t() : Super() { } property_array_t(property_list_t *l) : Super(l) { } - - property_array_t duplicate() { - return Super::duplicate(); - } }; class protocol_array_t : - public list_array_tt + public list_array_tt { - typedef list_array_tt Super; + typedef list_array_tt Super; public: protocol_array_t() : Super() { } protocol_array_t(protocol_list_t *l) : Super(l) { } - - protocol_array_t duplicate() { - return Super::duplicate(); - } }; struct class_rw_ext_t { - const class_ro_t *ro; + DECLARE_AUTHED_PTR_TEMPLATE(class_ro_t) + class_ro_t_authed_ptr ro; method_array_t methods; property_array_t properties; protocol_array_t protocols; @@ -1026,21 +1196,21 @@ struct class_rw_t { Class nextSiblingClass; private: - using ro_or_rw_ext_t = objc::PointerUnion; + using ro_or_rw_ext_t = objc::PointerUnion; const ro_or_rw_ext_t get_ro_or_rwe() const { return ro_or_rw_ext_t{ro_or_rw_ext}; } void set_ro_or_rwe(const class_ro_t *ro) { - ro_or_rw_ext_t{ro}.storeAt(ro_or_rw_ext, memory_order_relaxed); + ro_or_rw_ext_t{ro, &ro_or_rw_ext}.storeAt(ro_or_rw_ext, memory_order_relaxed); } void set_ro_or_rwe(class_rw_ext_t *rwe, const class_ro_t *ro) { // the release barrier is so that the class_rw_ext_t::ro initialization // is visible to lockless readers rwe->ro = ro; - ro_or_rw_ext_t{rwe}.storeAt(ro_or_rw_ext, memory_order_release); + ro_or_rw_ext_t{rwe, &ro_or_rw_ext}.storeAt(ro_or_rw_ext, memory_order_release); } class_rw_ext_t *extAlloc(const class_ro_t *ro, bool deep = false); @@ -1069,15 +1239,15 @@ public: } class_rw_ext_t *ext() const { - return get_ro_or_rwe().dyn_cast(); + return get_ro_or_rwe().dyn_cast(&ro_or_rw_ext); } class_rw_ext_t *extAllocIfNeeded() { auto v = get_ro_or_rwe(); if (fastpath(v.is())) { - return v.get(); + return v.get(&ro_or_rw_ext); } else { - return extAlloc(v.get()); + return extAlloc(v.get(&ro_or_rw_ext)); } } @@ -1088,15 +1258,15 @@ public: const class_ro_t *ro() const { auto v = get_ro_or_rwe(); if (slowpath(v.is())) { - return v.get()->ro; + return v.get(&ro_or_rw_ext)->ro; } - return v.get(); + return v.get(&ro_or_rw_ext); } void set_ro(const class_ro_t *ro) { auto v = get_ro_or_rwe(); if (v.is()) { - v.get()->ro = ro; + v.get(&ro_or_rw_ext)->ro = ro; } else { set_ro_or_rwe(ro); } @@ -1105,27 +1275,27 @@ public: const method_array_t methods() const { auto v = get_ro_or_rwe(); if (v.is()) { - return v.get()->methods; + return v.get(&ro_or_rw_ext)->methods; } else { - return method_array_t{v.get()->baseMethods()}; + return method_array_t{v.get(&ro_or_rw_ext)->baseMethods()}; } } const property_array_t properties() const { auto v = get_ro_or_rwe(); if (v.is()) { - return v.get()->properties; + return v.get(&ro_or_rw_ext)->properties; } else { - return property_array_t{v.get()->baseProperties}; + return property_array_t{v.get(&ro_or_rw_ext)->baseProperties}; } } const protocol_array_t protocols() const { auto v = get_ro_or_rwe(); if (v.is()) { - return v.get()->protocols; + return v.get(&ro_or_rw_ext)->protocols; } else { - return protocol_array_t{v.get()->baseProtocols}; + return protocol_array_t{v.get(&ro_or_rw_ext)->baseProtocols}; } } }; @@ -1650,8 +1820,8 @@ struct swift_class_t : objc_class { struct category_t { const char *name; classref_t cls; - struct method_list_t *instanceMethods; - struct method_list_t *classMethods; + WrappedPtr instanceMethods; + WrappedPtr classMethods; struct protocol_list_t *protocols; struct property_list_t *instanceProperties; // Fields below this point are not always present on disk. diff --git a/runtime/objc-runtime-new.mm b/runtime/objc-runtime-new.mm index e158375..1eabd5c 100644 --- a/runtime/objc-runtime-new.mm +++ b/runtime/objc-runtime-new.mm @@ -46,7 +46,7 @@ static void free_class(Class cls); static IMP addMethod(Class cls, SEL name, IMP imp, const char *types, bool replace); static void adjustCustomFlagsForMethodChange(Class cls, method_t *meth); static method_t *search_method_list(const method_list_t *mlist, SEL sel); -static bool method_lists_contains_any(method_list_t * const *mlists, method_list_t * const *end, +template static bool method_lists_contains_any(T *mlists, T *end, SEL sels[], size_t selcount); static void flushCaches(Class cls); static void initializeTaggedPointerObfuscator(void); @@ -212,6 +212,59 @@ static bool didInitialAttachCategories = false; **********************************************************************/ bool didCallDyldNotifyRegister = false; + +/*********************************************************************** +* smallMethodIMPMap +* The map from small method pointers to replacement IMPs. +* +* Locking: runtimeLock must be held when accessing this map. +**********************************************************************/ +namespace objc { + static objc::LazyInitDenseMap smallMethodIMPMap; +} + +static IMP method_t_remappedImp_nolock(const method_t *m) { + runtimeLock.assertLocked(); + auto *map = objc::smallMethodIMPMap.get(false); + if (!map) + return nullptr; + auto iter = map->find(m); + if (iter == map->end()) + return nullptr; + return iter->second; +} + +IMP method_t::remappedImp(bool needsLock) const { + ASSERT(isSmall()); + if (needsLock) { + mutex_locker_t guard(runtimeLock); + return method_t_remappedImp_nolock(this); + } else { + return method_t_remappedImp_nolock(this); + } +} + +void method_t::remapImp(IMP imp) { + ASSERT(isSmall()); + runtimeLock.assertLocked(); + auto *map = objc::smallMethodIMPMap.get(true); + (*map)[this] = imp; +} + +objc_method_description *method_t::getSmallDescription() const { + static objc::LazyInitDenseMap map; + + mutex_locker_t guard(runtimeLock); + + auto &ptr = (*map.get(true))[this]; + if (!ptr) { + ptr = (objc_method_description *)malloc(sizeof *ptr); + ptr->name = name(); + ptr->types = (char *)types(); + } + return ptr; +} + /* Low two bits of mlist->entsize is used as the fixed-up marker. PREOPTIMIZED VERSION: @@ -258,7 +311,8 @@ bool method_list_t::isUniqued() const { } bool method_list_t::isFixedUp() const { - return flags() == fixed_up_method_list; + // Ignore any flags in the top bits, just look at the bottom two. + return (flags() & 0x3) == fixed_up_method_list; } void method_list_t::setFixedUp() { @@ -288,11 +342,11 @@ void protocol_t::clearIsCanonical() { } -method_list_t * const *method_array_t::endCategoryMethodLists(Class cls) const +const method_list_t_authed_ptr *method_array_t::endCategoryMethodLists(Class cls) const { auto mlists = beginLists(); auto mlistsEnd = endLists(); - + if (mlists == mlistsEnd || !cls->data()->ro()->baseMethods()) { // No methods, or no base methods. @@ -536,7 +590,7 @@ printReplacements(Class cls, const locstamped_category_t *cats_list, uint32_t ca if (!mlist) continue; for (const auto& meth : *mlist) { - SEL s = sel_registerName(sel_cname(meth.name)); + SEL s = sel_registerName(sel_cname(meth.name())); // Search for replaced methods in method lookup order. // Complain about the first duplicate only. @@ -549,11 +603,11 @@ printReplacements(Class cls, const locstamped_category_t *cats_list, uint32_t ca if (!mlist2) continue; for (const auto& meth2 : *mlist2) { - SEL s2 = sel_registerName(sel_cname(meth2.name)); + SEL s2 = sel_registerName(sel_cname(meth2.name())); if (s == s2) { logReplacedMethod(cls->nameForLogging(), s, cls->isMetaClass(), cat->name, - meth2.imp, meth.imp); + meth2.imp(false), meth.imp(false)); goto complained; } } @@ -561,11 +615,11 @@ printReplacements(Class cls, const locstamped_category_t *cats_list, uint32_t ca // Look for method in cls for (const auto& meth2 : cls->data()->methods()) { - SEL s2 = sel_registerName(sel_cname(meth2.name)); + SEL s2 = sel_registerName(sel_cname(meth2.name())); if (s == s2) { logReplacedMethod(cls->nameForLogging(), s, cls->isMetaClass(), cat->name, - meth2.imp, meth.imp); + meth2.imp(false), meth.imp(false)); goto complained; } } @@ -892,7 +946,7 @@ public: static void scanChangedMethod(Class cls, const method_t *meth) { - if (fastpath(!Traits::isInterestingSelector(meth->name))) { + if (fastpath(!Traits::isInterestingSelector(meth->name()))) { return; } @@ -938,7 +992,8 @@ struct AWZScanner : scanner::Mixin + static bool scanMethodLists(T *mlists, T *end) { SEL sels[2] = { @selector(alloc), @selector(allocWithZone:), }; return method_lists_contains_any(mlists, end, sels, 2); } @@ -972,7 +1027,8 @@ struct RRScanner : scanner::Mixin + static bool scanMethodLists(T *mlists, T *end) { SEL sels[8] = { @selector(retain), @selector(release), @@ -1007,7 +1063,8 @@ struct CoreScanner : scanner::Mixin { sel == @selector(isKindOfClass:) || sel == @selector(respondsToSelector:); } - static bool scanMethodLists(method_list_t * const *mlists, method_list_t * const *end) { + template + static bool scanMethodLists(T *mlists, T *end) { SEL sels[5] = { @selector(new), @selector(self), @@ -1193,19 +1250,25 @@ fixupMethodList(method_list_t *mlist, bool bundleCopy, bool sort) // Unique selectors in list. for (auto& meth : *mlist) { - const char *name = sel_cname(meth.name); - meth.name = sel_registerNameNoLock(name, bundleCopy); + const char *name = sel_cname(meth.name()); + meth.name() = sel_registerNameNoLock(name, bundleCopy); } } // Sort by selector address. - if (sort) { + // Don't try to sort small lists, as they're immutable. + // Don't try to sort big lists of nonstandard size, as stable_sort + // won't copy the entries properly. + if (sort && !mlist->isSmallList() && mlist->entsize() == method_t::bigSize) { method_t::SortBySELAddress sorter; - std::stable_sort(mlist->begin(), mlist->end(), sorter); + std::stable_sort(&mlist->begin()->big(), &mlist->end()->big(), sorter); } - // Mark method list as uniqued and sorted - mlist->setFixedUp(); + // Mark method list as uniqued and sorted. + // Can't mark small lists, since they're immutable. + if (!mlist->isSmallList()) { + mlist->setFixedUp(); + } } @@ -1391,6 +1454,9 @@ static void methodizeClass(Class cls, Class previously) // Install methods and properties that the class implements itself. method_list_t *list = ro->baseMethods(); if (list) { + if (list->isSmallList() && !_dyld_is_memory_immutable(list, list->byteSize())) + _objc_fatal("CLASS: class '%s' %p small method list %p is not in immutable memory", + cls->nameForLogging(), cls, list); prepareMethodLists(cls, &list, 1, YES, isBundleClass(cls)); if (rwe) rwe->methods.attachLists(&list, 1); } @@ -1433,9 +1499,9 @@ static void methodizeClass(Class cls, Class previously) for (const auto& meth : rw->methods()) { if (PrintConnecting) { _objc_inform("METHOD %c[%s %s]", isMeta ? '+' : '-', - cls->nameForLogging(), sel_getName(meth.name)); + cls->nameForLogging(), sel_getName(meth.name())); } - ASSERT(sel_registerName(sel_getName(meth.name)) == meth.name); + ASSERT(sel_registerName(sel_getName(meth.name())) == meth.name()); } #endif } @@ -2270,16 +2336,8 @@ static NEVER_INLINE Protocol *getProtocol(const char *name) if (result) return result; } - // Try table from dyld shared cache - // Temporarily check that we are using the new table. Eventually this check - // will always be true. - // FIXME: Remove this check when we can - if (sharedCacheSupportsProtocolRoots()) { - result = getPreoptimizedProtocol(name); - if (result) return result; - } - - return nil; + // Try table from dyld3 closure and dyld shared cache + return getPreoptimizedProtocol(name); } @@ -3053,8 +3111,8 @@ static void load_categories_nolock(header_info *hi) { } }; - processCatlist(_getObjc2CategoryList(hi, &count)); - processCatlist(_getObjc2CategoryList2(hi, &count)); + processCatlist(hi->catlist(&count)); + processCatlist(hi->catlist2(&count)); } static void loadAllCategories() { @@ -3554,7 +3612,6 @@ void _read_images(header_info **hList, uint32_t hCount, int totalClasses, int un ts.log("IMAGE TIMES: fix up objc_msgSend_fixup"); #endif - bool cacheSupportsProtocolRoots = sharedCacheSupportsProtocolRoots(); // Discover protocols. Fix up protocol refs. for (EACH_HEADER) { @@ -3570,7 +3627,7 @@ void _read_images(header_info **hList, uint32_t hCount, int totalClasses, int un // in the shared cache is marked with isCanonical() and that may not // be true if some non-shared cache binary was chosen as the canonical // definition - if (launchTime && isPreoptimized && cacheSupportsProtocolRoots) { + if (launchTime && isPreoptimized) { if (PrintProtocols) { _objc_inform("PROTOCOLS: Skipping reading protocols in image: %s", hi->fname()); @@ -3597,7 +3654,7 @@ void _read_images(header_info **hList, uint32_t hCount, int totalClasses, int un // shared cache definition of a protocol. We can skip the check on // launch, but have to visit @protocol refs for shared cache images // loaded later. - if (launchTime && cacheSupportsProtocolRoots && hi->isPreoptimized()) + if (launchTime && hi->isPreoptimized()) continue; protocol_t **protolist = _getObjc2ProtocolRefs(hi, &count); for (i = 0; i < count; i++) { @@ -3627,8 +3684,7 @@ void _read_images(header_info **hList, uint32_t hCount, int totalClasses, int un // Realize non-lazy classes (for +load methods and static instances) for (EACH_HEADER) { - classref_t const *classlist = - _getObjc2NonlazyClassList(hi, &count); + classref_t const *classlist = hi->nlclslist(&count); for (i = 0; i < count; i++) { Class cls = remapClass(classlist[i]); if (!cls) continue; @@ -3808,7 +3864,7 @@ void _unload_image(header_info *hi) // Ignore __objc_catlist2. We don't support unloading Swift // and we never will. - category_t * const *catlist = _getObjc2CategoryList(hi, &count); + category_t * const *catlist = hi->catlist(&count); for (i = 0; i < count; i++) { category_t *cat = catlist[i]; Class cls = remapClass(cat->cls); @@ -3838,7 +3894,7 @@ void _unload_image(header_info *hi) if (cls) classes.insert(cls); } - classlist = _getObjc2NonlazyClassList(hi, &count); + classlist = hi->nlclslist(&count); for (i = 0; i < count; i++) { Class cls = remapClass(classlist[i]); if (cls) classes.insert(cls); @@ -3873,14 +3929,19 @@ struct objc_method_description * method_getDescription(Method m) { if (!m) return nil; - return (struct objc_method_description *)m; + return m->getDescription(); } IMP method_getImplementation(Method m) { - return m ? m->imp : nil; + return m ? m->imp(true) : nil; +} + +IMPAndSEL _method_getImplementationAndName(Method m) +{ + return { m->imp(true), m->name() }; } @@ -3896,8 +3957,8 @@ method_getName(Method m) { if (!m) return nil; - ASSERT(m->name == sel_registerName(sel_getName(m->name))); - return m->name; + ASSERT(m->name() == sel_registerName(sel_getName(m->name()))); + return m->name(); } @@ -3911,7 +3972,7 @@ const char * method_getTypeEncoding(Method m) { if (!m) return nil; - return m->types; + return m->types(); } @@ -3928,8 +3989,8 @@ _method_setImplementation(Class cls, method_t *m, IMP imp) if (!m) return nil; if (!imp) return nil; - IMP old = m->imp; - m->imp = imp; + IMP old = m->imp(false); + m->setImp(imp); // Cache updates are slow if cls is nil (i.e. unknown) // RR/AWZ updates are slow if cls is nil (i.e. unknown) @@ -3958,9 +4019,9 @@ void method_exchangeImplementations(Method m1, Method m2) mutex_locker_t lock(runtimeLock); - IMP m1_imp = m1->imp; - m1->imp = m2->imp; - m2->imp = m1_imp; + IMP m1_imp = m1->imp(false); + m1->setImp(m2->imp(false)); + m2->setImp(m1_imp); // RR/AWZ updates are slow because class is unknown @@ -4121,7 +4182,7 @@ fixupProtocolMethodList(protocol_t *proto, method_list_t *mlist, fixupMethodList(mlist, true/*always copy for simplicity*/, !extTypes/*sort if no extended method types*/); - if (extTypes) { + if (extTypes && !mlist->isSmallList()) { // Sort method list and extended method types together. // fixupMethodList() can't do this. // fixme COW stomp @@ -4132,8 +4193,8 @@ fixupProtocolMethodList(protocol_t *proto, method_list_t *mlist, required, instance, prefix, junk); for (uint32_t i = 0; i < count; i++) { for (uint32_t j = i+1; j < count; j++) { - method_t& mi = mlist->get(i); - method_t& mj = mlist->get(j); + auto& mi = mlist->get(i).big(); + auto& mj = mlist->get(j).big(); if (mi.name > mj.name) { std::swap(mi, mj); std::swap(extTypes[prefix+i], extTypes[prefix+j]); @@ -4372,7 +4433,9 @@ protocol_getMethodDescription(Protocol *p, SEL aSel, Method m = protocol_getMethod(newprotocol(p), aSel, isRequiredMethod, isInstanceMethod, true); - if (m) return *method_getDescription(m); + // method_getDescription is inefficient for small methods. Don't bother + // trying to use it, just make our own. + if (m) return (struct objc_method_description){m->name(), (char *)m->types()}; else return (struct objc_method_description){nil, nil}; } @@ -4477,8 +4540,8 @@ protocol_copyMethodDescriptionList(Protocol *p, result = (struct objc_method_description *) calloc(mlist->count + 1, sizeof(struct objc_method_description)); for (const auto& meth : *mlist) { - result[count].name = meth.name; - result[count].types = (char *)meth.types; + result[count].name = meth.name(); + result[count].types = (char *)meth.types(); count++; } } @@ -4763,15 +4826,15 @@ static void protocol_addMethod_nolock(method_list_t*& list, SEL name, const char *types) { if (!list) { - list = (method_list_t *)calloc(sizeof(method_list_t), 1); - list->entsizeAndFlags = sizeof(list->first); + list = (method_list_t *)calloc(method_list_t::byteSize(sizeof(struct method_t::big), 1), 1); + list->entsizeAndFlags = sizeof(struct method_t::big); list->setFixedUp(); } else { size_t size = list->byteSize() + list->entsize(); list = (method_list_t *)realloc(list, size); } - method_t& meth = list->get(list->count++); + auto &meth = list->get(list->count++).big(); meth.name = name; meth.types = types ? strdupIfMutable(types) : ""; meth.imp = nil; @@ -4819,15 +4882,15 @@ protocol_addProperty_nolock(property_list_t *&plist, const char *name, unsigned int count) { if (!plist) { - plist = (property_list_t *)calloc(sizeof(property_list_t), 1); + plist = (property_list_t *)calloc(property_list_t::byteSize(sizeof(property_t), 1), 1); plist->entsizeAndFlags = sizeof(property_t); + plist->count = 1; } else { - plist = (property_list_t *) - realloc(plist, sizeof(property_list_t) - + plist->count * plist->entsize()); + plist->count++; + plist = (property_list_t *)realloc(plist, plist->byteSize()); } - property_t& prop = plist->get(plist->count++); + property_t& prop = plist->get(plist->count - 1); prop.name = strdupIfMutable(name); prop.attributes = copyPropertyAttributeString(attrs, count); } @@ -5038,7 +5101,7 @@ objc_copyProtocolList(unsigned int *outCount) // Find all the protocols from the pre-optimized images. These protocols // won't be in the protocol map. objc::DenseMap preoptimizedProtocols; - if (sharedCacheSupportsProtocolRoots()) { + { header_info *hi; for (hi = FirstHeader; hi; hi = hi->getNext()) { if (!hi->hasPreoptimizedProtocols()) @@ -5242,9 +5305,9 @@ objc_class::getLoadMethod() mlist = ISA()->data()->ro()->baseMethods(); if (mlist) { for (const auto& meth : *mlist) { - const char *name = sel_cname(meth.name); + const char *name = sel_cname(meth.name()); if (0 == strcmp(name, "load")) { - return meth.imp; + return meth.imp(false); } } } @@ -5312,9 +5375,9 @@ _category_getLoadMethod(Category cat) mlist = cat->classMethods; if (mlist) { for (const auto& meth : *mlist) { - const char *name = sel_cname(meth.name); + const char *name = sel_cname(meth.name()); if (0 == strcmp(name, "load")) { - return meth.imp; + return meth.imp(false); } } } @@ -5740,25 +5803,26 @@ findMethodInSortedMethodList(SEL key, const method_list_t *list) { ASSERT(list); - const method_t * const first = &list->first; - const method_t *base = first; - const method_t *probe; + auto first = list->begin(); + auto base = first; + decltype(first) probe; + uintptr_t keyValue = (uintptr_t)key; uint32_t count; for (count = list->count; count != 0; count >>= 1) { probe = base + (count >> 1); - uintptr_t probeValue = (uintptr_t)probe->name; + uintptr_t probeValue = (uintptr_t)probe->name(); if (keyValue == probeValue) { // `probe` is a match. // Rewind looking for the *first* occurrence of this value. // This is required for correct category overrides. - while (probe > first && keyValue == (uintptr_t)probe[-1].name) { + while (probe > first && keyValue == (uintptr_t)(probe - 1)->name()) { probe--; } - return (method_t *)probe; + return &*probe; } if (keyValue > probeValue) { @@ -5774,14 +5838,14 @@ ALWAYS_INLINE static method_t * search_method_list_inline(const method_list_t *mlist, SEL sel) { int methodListIsFixedUp = mlist->isFixedUp(); - int methodListHasExpectedSize = mlist->entsize() == sizeof(method_t); + int methodListHasExpectedSize = mlist->isExpectedSize(); if (fastpath(methodListIsFixedUp && methodListHasExpectedSize)) { return findMethodInSortedMethodList(sel, mlist); } else { // Linear search of unsorted method list for (auto& meth : *mlist) { - if (meth.name == sel) return &meth; + if (meth.name() == sel) return &meth; } } @@ -5789,7 +5853,7 @@ search_method_list_inline(const method_list_t *mlist, SEL sel) // sanity-check negative results if (mlist->isFixedUp()) { for (auto& meth : *mlist) { - if (meth.name == sel) { + if (meth.name() == sel) { _objc_fatal("linear search worked when binary search did not"); } } @@ -5808,14 +5872,15 @@ search_method_list(const method_list_t *mlist, SEL sel) /*********************************************************************** * method_lists_contains_any **********************************************************************/ +template static NEVER_INLINE bool -method_lists_contains_any(method_list_t * const *mlists, method_list_t * const *end, +method_lists_contains_any(T *mlists, T *end, SEL sels[], size_t selcount) { while (mlists < end) { const method_list_t *mlist = *mlists++; int methodListIsFixedUp = mlist->isFixedUp(); - int methodListHasExpectedSize = mlist->entsize() == sizeof(method_t); + int methodListHasExpectedSize = mlist->entsize() == sizeof(struct method_t::big); if (fastpath(methodListIsFixedUp && methodListHasExpectedSize)) { for (size_t i = 0; i < selcount; i++) { @@ -5826,7 +5891,7 @@ method_lists_contains_any(method_list_t * const *mlists, method_list_t * const * } else { for (auto& meth : *mlist) { for (size_t i = 0; i < selcount; i++) { - if (meth.name == sels[i]) { + if (meth.name() == sels[i]) { return true; } } @@ -6124,8 +6189,6 @@ IMP lookUpImpOrForward(id inst, SEL sel, Class cls, int behavior) // To make these harder we want to make sure this is a class that was // either built into the binary or legitimately registered through // objc_duplicateClass, objc_initializeClassPair or objc_allocateClassPair. - // - // TODO: this check is quite costly during process startup. checkIsKnownClass(cls); if (slowpath(!cls->isRealized())) { @@ -6157,7 +6220,7 @@ IMP lookUpImpOrForward(id inst, SEL sel, Class cls, int behavior) // curClass method list. Method meth = getMethodNoSuper_nolock(curClass, sel); if (meth) { - imp = meth->imp; + imp = meth->imp(false); goto done; } @@ -6230,8 +6293,8 @@ IMP lookupMethodInClassAndLoadCache(Class cls, SEL sel) if (meth) { // Hit in method list. Cache it. - cache_fill(cls, sel, meth->imp, nil); - return meth->imp; + cache_fill(cls, sel, meth->imp(false), nil); + return meth->imp(false); } else { // Miss in method list. Cache objc_msgForward. cache_fill(cls, sel, _objc_msgForward_impcache, nil); @@ -6603,7 +6666,7 @@ addMethod(Class cls, SEL name, IMP imp, const char *types, bool replace) if ((m = getMethodNoSuper_nolock(cls, name))) { // already exists if (!replace) { - result = m->imp; + result = m->imp(false); } else { result = _method_setImplementation(cls, m, imp); } @@ -6612,13 +6675,14 @@ addMethod(Class cls, SEL name, IMP imp, const char *types, bool replace) // fixme optimize method_list_t *newlist; - newlist = (method_list_t *)calloc(sizeof(*newlist), 1); + newlist = (method_list_t *)calloc(method_list_t::byteSize(method_t::bigSize, 1), 1); newlist->entsizeAndFlags = - (uint32_t)sizeof(method_t) | fixed_up_method_list; + (uint32_t)sizeof(struct method_t::big) | fixed_up_method_list; newlist->count = 1; - newlist->first.name = name; - newlist->first.types = strdupIfMutable(types); - newlist->first.imp = imp; + auto &first = newlist->begin()->big(); + first.name = name; + first.types = strdupIfMutable(types); + first.imp = imp; prepareMethodLists(cls, &newlist, 1, NO, NO); rwe->methods.attachLists(&newlist, 1); @@ -6650,14 +6714,12 @@ addMethods(Class cls, const SEL *names, const IMP *imps, const char **types, ASSERT(cls->isRealized()); method_list_t *newlist; - size_t newlistSize = method_list_t::byteSize(sizeof(method_t), count); + size_t newlistSize = method_list_t::byteSize(sizeof(struct method_t::big), count); newlist = (method_list_t *)calloc(newlistSize, 1); newlist->entsizeAndFlags = - (uint32_t)sizeof(method_t) | fixed_up_method_list; + (uint32_t)sizeof(struct method_t::big) | fixed_up_method_list; newlist->count = 0; - method_t *newlistMethods = &newlist->first; - SEL *failedNames = nil; uint32_t failedCount = 0; @@ -6673,16 +6735,16 @@ addMethods(Class cls, const SEL *names, const IMP *imps, const char **types, failedNames = (SEL *)calloc(sizeof(*failedNames), count + 1); } - failedNames[failedCount] = m->name; + failedNames[failedCount] = m->name(); failedCount++; } else { _method_setImplementation(cls, m, imps[i]); } } else { - method_t *newmethod = &newlistMethods[newlist->count]; - newmethod->name = names[i]; - newmethod->types = strdupIfMutable(types[i]); - newmethod->imp = imps[i]; + auto &newmethod = newlist->end()->big(); + newmethod.name = names[i]; + newmethod.types = strdupIfMutable(types[i]); + newmethod.imp = imps[i]; newlist->count++; } } @@ -6694,7 +6756,7 @@ addMethods(Class cls, const SEL *names, const IMP *imps, const char **types, // Note that realloc() alone doesn't work due to ptrauth. method_t::SortBySELAddress sorter; - std::stable_sort(newlist->begin(), newlist->end(), sorter); + std::stable_sort(&newlist->begin()->big(), &newlist->end()->big(), sorter); prepareMethodLists(cls, &newlist, 1, NO, NO); rwe->methods.attachLists(&newlist, 1); @@ -6803,7 +6865,7 @@ class_addIvar(Class cls, const char *name, size_t size, memcpy(newlist, oldlist, oldsize); free(oldlist); } else { - newlist = (ivar_list_t *)calloc(sizeof(ivar_list_t), 1); + newlist = (ivar_list_t *)calloc(ivar_list_t::byteSize(sizeof(ivar_t), 1), 1); newlist->entsizeAndFlags = (uint32_t)sizeof(ivar_t); } @@ -6897,11 +6959,11 @@ _class_addProperty(Class cls, const char *name, ASSERT(cls->isRealized()); property_list_t *proplist = (property_list_t *) - malloc(sizeof(*proplist)); + malloc(property_list_t::byteSize(sizeof(property_t), 1)); proplist->count = 1; - proplist->entsizeAndFlags = sizeof(proplist->first); - proplist->first.name = strdupIfMutable(name); - proplist->first.attributes = copyPropertyAttributeString(attrs, count); + proplist->entsizeAndFlags = sizeof(property_t); + proplist->begin()->name = strdupIfMutable(name); + proplist->begin()->attributes = copyPropertyAttributeString(attrs, count); rwe->properties.attachLists(&proplist, 1); @@ -7053,7 +7115,7 @@ objc_duplicateClass(Class original, const char *name, if (orig_rwe) { auto rwe = rw->extAllocIfNeeded(); rwe->version = orig_rwe->version; - rwe->methods = orig_rwe->methods.duplicate(); + orig_rwe->methods.duplicateInto(rwe->methods); // fixme dies when categories are added to the base rwe->properties = orig_rwe->properties; @@ -7390,7 +7452,7 @@ static void free_class(Class cls) if (rwe) { for (auto& meth : rwe->methods) { - try_free(meth.types); + try_free(meth.types()); } rwe->methods.tryFree(); } diff --git a/runtime/objc-sel-old.mm b/runtime/objc-sel-old.mm index 2a3a242..02fc2b5 100644 --- a/runtime/objc-sel-old.mm +++ b/runtime/objc-sel-old.mm @@ -33,11 +33,6 @@ #include "objc-private.h" #include "objc-sel-set.h" -#if SUPPORT_PREOPT -#include -static const objc_selopt_t *builtins = NULL; -#endif - __BEGIN_DECLS static size_t SelrefCount = 0; @@ -55,10 +50,6 @@ static SEL _objc_search_builtins(const char *key) if (!key) return (SEL)0; if ('\0' == *key) return (SEL)_objc_empty_selector; -#if SUPPORT_PREOPT - if (builtins) return (SEL)builtins->get(key); -#endif - return (SEL)0; } @@ -151,10 +142,6 @@ void sel_init(size_t selrefCount) // save this value for later SelrefCount = selrefCount; -#if SUPPORT_PREOPT - builtins = preoptimizedSelectors(); -#endif - // Register selectors used by libobjc mutex_locker_t lock(selLock); diff --git a/runtime/objc-sel.mm b/runtime/objc-sel.mm index 27ee356..da4c228 100644 --- a/runtime/objc-sel.mm +++ b/runtime/objc-sel.mm @@ -27,11 +27,6 @@ #include "objc-cache.h" #include "DenseMapExtras.h" -#if SUPPORT_PREOPT -static const objc_selopt_t *builtins = NULL; -static bool useDyldSelectorLookup = false; -#endif - static objc::ExplicitInitDenseSet namedSelectors; static SEL search_builtins(const char *key); @@ -44,32 +39,13 @@ static SEL search_builtins(const char *key); void sel_init(size_t selrefCount) { #if SUPPORT_PREOPT - // If dyld finds a known shared cache selector, then it must be also looking - // in the shared cache table. - if (_dyld_get_objc_selector("retain") != nil) - useDyldSelectorLookup = true; - else - builtins = preoptimizedSelectors(); - - if (PrintPreopt && useDyldSelectorLookup) { + if (PrintPreopt) { _objc_inform("PREOPTIMIZATION: using dyld selector opt"); } - - if (PrintPreopt && builtins) { - uint32_t occupied = builtins->occupied; - uint32_t capacity = builtins->capacity; - - _objc_inform("PREOPTIMIZATION: using selopt at %p", builtins); - _objc_inform("PREOPTIMIZATION: %u selectors", occupied); - _objc_inform("PREOPTIMIZATION: %u/%u (%u%%) hash table occupancy", - occupied, capacity, - (unsigned)(occupied/(double)capacity*100)); - } - namedSelectors.init(useDyldSelectorLookup ? 0 : (unsigned)selrefCount); -#else - namedSelectors.init((unsigned)selrefCount); #endif + namedSelectors.init((unsigned)selrefCount); + // Register selectors used by libobjc mutex_locker_t lock(selLock); @@ -110,17 +86,8 @@ BOOL sel_isMapped(SEL sel) static SEL search_builtins(const char *name) { #if SUPPORT_PREOPT - if (builtins) { - SEL result = 0; - if ((result = (SEL)builtins->get(name))) - return result; - - if ((result = (SEL)_dyld_get_objc_selector(name))) - return result; - } else if (useDyldSelectorLookup) { - if (SEL result = (SEL)_dyld_get_objc_selector(name)) - return result; - } + if (SEL result = (SEL)_dyld_get_objc_selector(name)) + return result; #endif return nil; } diff --git a/runtime/objc.h b/runtime/objc.h index 6a73568..6b974a3 100644 --- a/runtime/objc.h +++ b/runtime/objc.h @@ -180,8 +180,7 @@ OBJC_EXPORT const char * _Nonnull object_getClassName(id _Nullable obj) * @note In a garbage-collected environment, the memory is scanned conservatively. */ OBJC_EXPORT void * _Nullable object_getIndexedIvars(id _Nullable obj) - OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0) - OBJC_ARC_UNAVAILABLE; + OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0); /** * Identifies a selector as being valid or invalid. diff --git a/test/gdb.m b/test/gdb.m index e2c8b7d..22e0334 100644 --- a/test/gdb.m +++ b/test/gdb.m @@ -23,9 +23,31 @@ int main() [TestRoot class]; // Now class should be realized - result = (__bridge Class)(NXMapGet(gdb_objc_realized_classes, "TestRoot")); + if (!testdyld3()) { + // In dyld3 mode, the class will be in the launch closure and not in our table. + result = (__bridge Class)(NXMapGet(gdb_objc_realized_classes, "TestRoot")); + testassert(result); + testassert(result == [TestRoot class]); + } + + Class dynamic = objc_allocateClassPair([TestRoot class], "Dynamic", 0); + objc_registerClassPair(dynamic); + result = (__bridge Class)(NXMapGet(gdb_objc_realized_classes, "Dynamic")); testassert(result); - testassert(result == [TestRoot class]); + testassert(result == dynamic); + + Class *realizedClasses = objc_copyRealizedClassList(NULL); + bool foundTestRoot = false; + bool foundDynamic = false; + for (Class *cursor = realizedClasses; *cursor; cursor++) { + if (*cursor == [TestRoot class]) + foundTestRoot = true; + if (*cursor == dynamic) + foundDynamic = true; + } + free(realizedClasses); + testassert(foundTestRoot); + testassert(foundDynamic); result = (__bridge Class)(NXMapGet(gdb_objc_realized_classes, "DoesNotExist")); testassert(!result); diff --git a/test/libraryPath.c b/test/libraryPath.c new file mode 100644 index 0000000..40cf7bd --- /dev/null +++ b/test/libraryPath.c @@ -0,0 +1,44 @@ +// TEST_CFLAGS -lobjc + +#include "test.h" +#include + +// We use DYLD_LIBRARY_PATH to run the tests against a particular copy of +// libobjc. If this fails somehow (path is wrong, codesigning prevents loading, +// etc.) then the typical result is a silent failure and we end up testing +// /usr/lib/libobjc.A.dylib instead. This test detects when DYLD_LIBRARY_PATH is +// set but libobjc isn't loaded from it. +int main() { + char *dyldLibraryPath = getenv("DYLD_LIBRARY_PATH"); + testprintf("DYLD_LIBRARY_PATH is %s\n", dyldLibraryPath); + if (dyldLibraryPath != NULL && strlen(dyldLibraryPath) > 0) { + int foundMatch = 0; + + dyldLibraryPath = strdup(dyldLibraryPath); + + Dl_info info; + int success = dladdr((void *)objc_msgSend, &info); + testassert(success); + + testprintf("libobjc is located at %s\n", info.dli_fname); + + char *cursor = dyldLibraryPath; + char *path; + while ((path = strsep(&cursor, ":"))) { + char *resolved = realpath(path, NULL); + testprintf("Resolved %s to %s\n", path, resolved); + testprintf("Comparing %s and %s\n", resolved, info.dli_fname); + int comparison = strncmp(resolved, info.dli_fname, strlen(resolved)); + free(resolved); + if (comparison == 0) { + testprintf("Found a match!\n"); + foundMatch = 1; + break; + } + } + + testprintf("Finished searching, foundMatch=%d\n", foundMatch); + testassert(foundMatch); + } + succeed(__FILE__); +} diff --git a/test/load-noobjc.m b/test/load-noobjc.m index 4dd9f86..c2be1a0 100644 --- a/test/load-noobjc.m +++ b/test/load-noobjc.m @@ -1,4 +1,8 @@ /* +dyld3 calls the load callback with its own internal lock held, which causes +this test to deadlock. Disable the test in dyld3 mode. If +rdar://problem/53769512 is fixed then remove this. +TEST_CONFIG DYLD=2 TEST_BUILD $C{COMPILE} $DIR/load-noobjc.m -o load-noobjc.exe $C{COMPILE} $DIR/load-noobjc2.m -o libload-noobjc2.dylib -bundle -bundle_loader load-noobjc.exe diff --git a/test/methodListSmall.h b/test/methodListSmall.h new file mode 100644 index 0000000..c6f32e2 --- /dev/null +++ b/test/methodListSmall.h @@ -0,0 +1,226 @@ +#include "test.h" + +struct ObjCClass { + struct ObjCClass *isa; + struct ObjCClass *superclass; + void *cachePtr; + uintptr_t zero; + struct ObjCClass_ro *data; +}; + +struct ObjCClass_ro { + uint32_t flags; + uint32_t instanceStart; + uint32_t instanceSize; +#ifdef __LP64__ + uint32_t reserved; +#endif + + const uint8_t * ivarLayout; + + const char * name; + struct ObjCMethodList * baseMethodList; + struct protocol_list_t * baseProtocols; + const struct ivar_list_t * ivars; + + const uint8_t * weakIvarLayout; + struct property_list_t *baseProperties; +}; + +struct ObjCMethod { + char *name; + char *type; + IMP imp; +}; + +struct ObjCMethodList { + uint32_t sizeAndFlags; + uint32_t count; + struct ObjCMethod methods[]; +}; + +struct ObjCMethodSmall { + int32_t nameOffset; + int32_t typeOffset; + int32_t impOffset; +}; + +struct ObjCMethodListSmall { + uint32_t sizeAndFlags; + uint32_t count; + struct ObjCMethodSmall methods[]; +}; + + +extern struct ObjCClass OBJC_METACLASS_$_NSObject; +extern struct ObjCClass OBJC_CLASS_$_NSObject; + + +struct ObjCClass_ro FooMetaclass_ro = { + .flags = 1, + .instanceStart = 40, + .instanceSize = 40, + .name = "Foo", +}; + +struct ObjCClass FooMetaclass = { + .isa = &OBJC_METACLASS_$_NSObject, + .superclass = &OBJC_METACLASS_$_NSObject, + .cachePtr = &_objc_empty_cache, + .data = &FooMetaclass_ro, +}; + + +int ranMyMethod1; +extern "C" void myMethod1(id self __unused, SEL _cmd) { + testprintf("myMethod1\n"); + testassert(_cmd == @selector(myMethod1)); + ranMyMethod1 = 1; +} + +int ranMyMethod2; +extern "C" void myMethod2(id self __unused, SEL _cmd) { + testprintf("myMethod2\n"); + testassert(_cmd == @selector(myMethod2)); + ranMyMethod2 = 1; +} + +int ranMyMethod3; +extern "C" void myMethod3(id self __unused, SEL _cmd) { + testprintf("myMethod3\n"); + testassert(_cmd == @selector(myMethod3)); + ranMyMethod3 = 1; +} + +int ranMyReplacedMethod1; +extern "C" void myReplacedMethod1(id self __unused, SEL _cmd) { + testprintf("myReplacedMethod1\n"); + testassert(_cmd == @selector(myMethod1)); + ranMyReplacedMethod1 = 1; +} + +int ranMyReplacedMethod2; +extern "C" void myReplacedMethod2(id self __unused, SEL _cmd) { + testprintf("myReplacedMethod2\n"); + testassert(_cmd == @selector(myMethod2)); + ranMyReplacedMethod2 = 1; +} + +struct BigStruct { + uintptr_t a, b, c, d, e, f, g; +}; + +int ranMyMethodStret; +extern "C" BigStruct myMethodStret(id self __unused, SEL _cmd) { + testprintf("myMethodStret\n"); + testassert(_cmd == @selector(myMethodStret)); + ranMyMethodStret = 1; + BigStruct ret = {}; + return ret; +} + +int ranMyReplacedMethodStret; +extern "C" BigStruct myReplacedMethodStret(id self __unused, SEL _cmd) { + testprintf("myReplacedMethodStret\n"); + testassert(_cmd == @selector(myMethodStret)); + ranMyReplacedMethodStret = 1; + BigStruct ret = {}; + return ret; +} + +extern struct ObjCMethodList Foo_methodlistSmall; + +asm(R"ASM( +.section __TEXT,__cstring +_MyMethod1Name: + .asciz "myMethod1" +_MyMethod2Name: + .asciz "myMethod2" +_MyMethod3Name: + .asciz "myMethod3" +_BoringMethodType: + .asciz "v16@0:8" +_MyMethodStretName: + .asciz "myMethodStret" +_StretType: + .asciz "{BigStruct=QQQQQQQ}16@0:8" +)ASM"); + +#if __LP64__ +asm(R"ASM( +.section __DATA,__objc_selrefs,literal_pointers,no_dead_strip +_MyMethod1NameRef: + .quad _MyMethod1Name +_MyMethod2NameRef: + .quad _MyMethod2Name +_MyMethod3NameRef: + .quad _MyMethod3Name +_MyMethodStretNameRef: + .quad _MyMethodStretName +)ASM"); +#else +asm(R"ASM( +.section __DATA,__objc_selrefs,literal_pointers,no_dead_strip +_MyMethod1NameRef: + .long _MyMethod1Name +_MyMethod2NameRef: + .long _MyMethod2Name +_MyMethod3NameRef: + .long _MyMethod3Name +_MyMethodStretNameRef: + .long _MyMethodStretName +)ASM"); +#endif + +#if MUTABLE_METHOD_LIST +asm(".section __DATA,__objc_methlist\n"); +#else +asm(".section __TEXT,__objc_methlist\n"); +#endif + +asm(R"ASM( + .p2align 2 +_Foo_methodlistSmall: + .long 12 | 0x80000000 + .long 4 + + .long _MyMethod1NameRef - . + .long _BoringMethodType - . + .long _myMethod1 - . + + .long _MyMethod2NameRef - . + .long _BoringMethodType - . + .long _myMethod2 - . + + .long _MyMethod3NameRef - . + .long _BoringMethodType - . + .long _myMethod3 - . + + .long _MyMethodStretNameRef - . + .long _StretType - . + .long _myMethodStret - . +)ASM"); + +struct ObjCClass_ro Foo_ro = { + .instanceStart = 8, + .instanceSize = 8, + .name = "Foo", + .baseMethodList = &Foo_methodlistSmall, +}; + +struct ObjCClass FooClass = { + .isa = &FooMetaclass, + .superclass = &OBJC_CLASS_$_NSObject, + .cachePtr = &_objc_empty_cache, + .data = &Foo_ro, +}; + + +@interface Foo: NSObject + +- (void)myMethod1; +- (void)myMethod2; +- (void)myMethod3; +- (BigStruct)myMethodStret; + +@end diff --git a/test/methodListSmall.mm b/test/methodListSmall.mm new file mode 100644 index 0000000..c10f29d --- /dev/null +++ b/test/methodListSmall.mm @@ -0,0 +1,89 @@ +// TEST_CFLAGS -std=c++11 + +#include "methodListSmall.h" + +void testClass(Class c) { + id foo = [c new]; + [foo myMethod1]; + testassert(ranMyMethod1); + [foo myMethod2]; + testassert(ranMyMethod2); + [foo myMethod3]; + testassert(ranMyMethod3); + + Method m1 = class_getInstanceMethod(c, @selector(myMethod1)); + testassert(m1); + testassert(method_getName(m1) == @selector(myMethod1)); + testassert(strcmp(method_getTypeEncoding(m1), "v16@0:8") == 0); + testassert(method_getImplementation(m1) == (IMP)myMethod1); + + method_setImplementation(m1, (IMP)myReplacedMethod1); + testassert(method_getImplementation(m1) == (IMP)myReplacedMethod1); + [foo myMethod1]; + testassert(ranMyReplacedMethod1); + + Method m2 = class_getInstanceMethod(c, @selector(myMethod2)); + auto method_invoke_cast = (void (*)(id, Method))method_invoke; + + ranMyMethod2 = 0; + method_invoke_cast(foo, m2); + testassert(ranMyMethod2); + + method_setImplementation(m2, (IMP)myReplacedMethod2); + method_invoke_cast(foo, m2); + testassert(ranMyReplacedMethod2); + + Method mstret = class_getInstanceMethod(c, @selector(myMethodStret)); +#if __arm64__ + // No _stret variant on ARM64. We'll test struct return through + // method_invoke anyway just to be thorough. + auto method_invoke_stret_cast = (BigStruct (*)(id, Method))method_invoke; +#else + auto method_invoke_stret_cast = (BigStruct (*)(id, Method))method_invoke_stret; +#endif + + [foo myMethodStret]; + testassert(ranMyMethodStret); + + ranMyMethodStret = 0; + method_invoke_stret_cast(foo, mstret); + testassert(ranMyMethodStret); + + method_setImplementation(mstret, (IMP)myReplacedMethodStret); + [foo myMethodStret]; + testassert(ranMyReplacedMethodStret); + + ranMyReplacedMethodStret = 0; + method_invoke_stret_cast(foo, mstret); + testassert(ranMyReplacedMethodStret); + + auto *desc1 = method_getDescription(m1); + testassert(desc1->name == @selector(myMethod1)); + testassert(desc1->types == method_getTypeEncoding(m1)); + + auto *desc2 = method_getDescription(m2); + testassert(desc2->name == @selector(myMethod2)); + testassert(desc2->types == method_getTypeEncoding(m2)); + + auto *descstret = method_getDescription(mstret); + testassert(descstret->name == @selector(myMethodStret)); + testassert(descstret->types == method_getTypeEncoding(mstret)); +} + +int main() { + Class fooClass = (__bridge Class)&FooClass; + + // Make sure this class can be duplicated and works as expected. + // Duplicate it before testClass mucks around with the methods. + // Need to realize fooClass before duplicating it, hence the + // class message. + Class dupedClass = objc_duplicateClass([fooClass class], "FooDup", 0); + + testprintf("Testing class.\n"); + testClass(fooClass); + + testprintf("Testing duplicate class.\n"); + testClass(dupedClass); + + succeed(__FILE__); +} diff --git a/test/methodListSmallMutableMemory.mm b/test/methodListSmallMutableMemory.mm new file mode 100644 index 0000000..9250fea --- /dev/null +++ b/test/methodListSmallMutableMemory.mm @@ -0,0 +1,18 @@ +/* +TEST_CFLAGS -std=c++11 +TEST_CRASHES +TEST_RUN_OUTPUT +objc\[\d+\]: CLASS: class 'Foo' 0x[0-9a-fA-F]+ small method list 0x[0-9a-fA-F]+ is not in immutable memory +objc\[\d+\]: HALTED +END +*/ + +#define MUTABLE_METHOD_LIST 1 + +#include "methodListSmall.h" + +int main() { + Class fooClass = (__bridge Class)&FooClass; + [fooClass new]; + fail("Should have crashed"); +} diff --git a/test/nonpointerisa.m b/test/nonpointerisa.m index dbe222c..d7b007f 100644 --- a/test/nonpointerisa.m +++ b/test/nonpointerisa.m @@ -114,10 +114,6 @@ void check_nonpointer(id obj, Class cls) } -@interface OS_object -+(id)alloc; -@end - @interface Fake_OS_object : NSObject { int refcnt; int xref_cnt; @@ -138,7 +134,7 @@ void check_nonpointer(id obj, Class cls) } @end -@interface Sub_OS_object : OS_object @end +@interface Sub_OS_object : NSObject @end @implementation Sub_OS_object @end @@ -147,6 +143,9 @@ void check_nonpointer(id obj, Class cls) int main() { + Class OS_object = objc_getClass("OS_object"); + class_setSuperclass([Sub_OS_object class], OS_object); + uintptr_t isa; #if SUPPORT_PACKED_ISA @@ -193,7 +192,6 @@ int main() objc_setAssociatedObject(index_o, assoc, assoc, OBJC_ASSOCIATION_ASSIGN); testassert(__builtin_popcountl(isa ^ ISA(index_o)) == 1); - testprintf("Isa without index\n"); id raw_o = [OS_object alloc]; check_raw_pointer(raw_o, [OS_object class]); diff --git a/test/supported-inline-refcnt.m b/test/supported-inline-refcnt.m new file mode 100644 index 0000000..bd32ba9 --- /dev/null +++ b/test/supported-inline-refcnt.m @@ -0,0 +1,85 @@ +// TEST_CONFIG MEM=mrc +// TEST_CFLAGS -framework CoreFoundation -Weverything + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Weverything" +#include "test.h" +#pragma clang diagnostic pop +#include +#include +#include + + +// Some warnings just aren't feasible to work around. We'll disable them instead. +#pragma clang diagnostic ignored "-Watomic-implicit-seq-cst" +#pragma clang diagnostic ignored "-Wdirect-ivar-access" +#pragma clang diagnostic ignored "-Wold-style-cast" + +static int deallocCount; +@interface Refcnt: NSObject @end +@implementation Refcnt { + int _rc; +} + +_OBJC_SUPPORTED_INLINE_REFCNT(_rc) + +- (void)dealloc { + deallocCount++; + [super dealloc]; +} + +@end + +@interface MainRefcnt: NSObject @end +@implementation MainRefcnt { + int _rc; +} + +_OBJC_SUPPORTED_INLINE_REFCNT_WITH_DEALLOC2MAIN(_rc) + +- (void)dealloc { + testassert(pthread_main_np()); + deallocCount++; + [super dealloc]; +} + +@end + +int main() +{ + Refcnt *obj = [Refcnt new]; + [obj retain]; + [obj retain]; + [obj retain]; + [obj release]; + [obj release]; + [obj release]; + [obj release]; + testassert(deallocCount == 1); + + MainRefcnt *obj2 = [MainRefcnt new]; + [obj2 retain]; + [obj2 retain]; + [obj2 retain]; + + dispatch_group_t group = dispatch_group_create(); + dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{ + [obj2 release]; + }); + dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{ + [obj2 release]; + }); + dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{ + [obj2 release]; + }); + dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{ + [obj2 release]; + }); + + dispatch_group_notify(group, dispatch_get_main_queue(), ^{ + testassert(deallocCount == 2); + succeed(__FILE__); + }); + + CFRunLoopRun(); +} diff --git a/test/test.h b/test/test.h index f4332a1..4ae8137 100644 --- a/test/test.h +++ b/test/test.h @@ -143,6 +143,19 @@ static inline void testwarn(const char *msg, ...) static inline void testnoop() { } +// Are we running in dyld3 mode? +// Note: checks by looking for the DYLD_USE_CLOSURES environment variable. +// This is is always set by our test script, but this won't give the right +// answer when being run manually unless that variable is set. +static inline bool testdyld3(void) { + static int dyld = 0; + if (dyld == 0) { + const char *useClosures = getenv("DYLD_USE_CLOSURES"); + dyld = useClosures && useClosures[0] == '1' ? 3 : 2; + } + return dyld == 3; +} + // Prevent deprecation warnings from some runtime functions. static inline void test_objc_flush_caches(Class cls) diff --git a/test/test.pl b/test/test.pl index 0d0886b..46c3d03 100755 --- a/test/test.pl +++ b/test/test.pl @@ -42,6 +42,8 @@ options: RUN=0|1 (run the tests?) VERBOSE=0|1|2 (0=quieter 1=print commands executed 2=full test output) BATS=0|1 (build for and/or run in BATS?) + BUILD_SHARED_CACHE=0|1 (build a dyld shared cache with the root and test against that) + DYLD=2|3 (test in dyld 2 or dyld 3 mode) examples: @@ -230,7 +232,22 @@ sub make { next if $cmd =~ /^\s*$/; $cmd .= " 2>&1"; print "$cmd\n" if $VERBOSE; - $output .= `$cmd`; + eval { + local $SIG{ALRM} = sub { die "alarm\n" }; + # Timeout after 600 seconds so a deadlocked test doesn't wedge the + # entire test suite. Increase to an hour for B&I builds. + if (exists $ENV{"RC_XBS"}) { + alarm 3600; + } else { + alarm 600; + } + $output .= `$cmd`; + alarm 0; + }; + if ($@) { + die unless $@ eq "alarm\n"; + $output .= "\nTIMED OUT"; + } last if $?; } print "$output\n" if $VERBOSE; @@ -966,6 +983,16 @@ sub run_simple { $env .= " OBJC_DEBUG_DONT_CRASH=YES"; } + if ($C{DYLD} eq "2") { + $env .= " DYLD_USE_CLOSURES=0"; + } + elsif ($C{DYLD} eq "3") { + $env .= " DYLD_USE_CLOSURES=1"; + } + else { + die "unknown DYLD setting $C{DYLD}"; + } + my $output; if ($C{ARCH} =~ /^arm/ && `uname -p` !~ /^arm/) { @@ -1060,6 +1087,26 @@ sub dirContainsAllTestLibs { return 1; } +sub findIncludeDir { + my ($root, $includePath) = @_; + + foreach my $candidate ("$root/../SDKContentRoot/$includePath", "$root/$includePath") { + my $found = -e $candidate; + my $foundstr = ($found ? "found" : "didn't find"); + print "note: $foundstr $includePath at $candidate\n" if $VERBOSE; + return $candidate if $found; + } + + die "Unable to find $includePath in $root.\n"; +} + +sub buildSharedCache { + my $Cref = shift; + my %C = %$Cref; + + make("update_dyld_shared_cache -verbose -cache_dir $BUILDDIR -overlay $C{TESTLIBDIR}/../.."); +} + sub make_one_config { my $configref = shift; my $root = shift; @@ -1315,9 +1362,8 @@ sub make_one_config { my $library_path = $C{TESTLIBDIR}; $cflags .= " -L$library_path"; - # fixme Root vs SDKContentRoot - $C{TESTINCLUDEDIR} = "$root/../SDKContentRoot/usr/include"; - $C{TESTLOCALINCLUDEDIR} = "$root/../SDKContentRoot/usr/local/include"; + $C{TESTINCLUDEDIR} = findIncludeDir($root, "usr/include"); + $C{TESTLOCALINCLUDEDIR} = findIncludeDir($root, "usr/local/include"); $cflags .= " -isystem '$C{TESTINCLUDEDIR}'"; $cflags .= " -isystem '$C{TESTLOCALINCLUDEDIR}'"; } @@ -1634,6 +1680,10 @@ $args{OSVERSION} = getargs("OS", "macosx-default-default"); $args{MEM} = getargs("MEM", "mrc,arc"); $args{LANGUAGE} = [ map { lc($_) } @{getargs("LANGUAGE", "c,objective-c,c++,objective-c++")} ]; +$args{BUILD_SHARED_CACHE} = getargs("BUILD_SHARED_CACHE", 0); + +$args{DYLD} = getargs("DYLD", "2,3"); + $args{CC} = getargs("CC", "clang"); $HOST = getarg("HOST", "iphone"); -- 2.45.2