_sel_init
___sel_registerName
__objc_search_builtins
-__ZNK8objc_opt13objc_selopt_t3getEPKc
-__ZNK8objc_opt13objc_selopt_t4hashEPKc
_sel_registerName
_arr_init
__ZN4objc8DenseMapIP11objc_objectmLb1ENS_12DenseMapInfoIS2_EENS3_ImEEE4initEj
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" */;
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 */; };
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 */;
};
/* 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 = "<group>"; };
393CEAC50DC69E67000B69DE /* objc-references.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "objc-references.h"; path = "runtime/objc-references.h"; sourceTree = "<group>"; };
6EACB841232C97A400CE9176 /* objc-zalloc.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "objc-zalloc.h"; path = "runtime/objc-zalloc.h"; sourceTree = "<group>"; };
6EACB843232C97B900CE9176 /* objc-zalloc.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = "objc-zalloc.mm"; path = "runtime/objc-zalloc.mm"; sourceTree = "<group>"; };
6ECD0B1E2244999E00910D88 /* llvm-DenseSet.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "llvm-DenseSet.h"; path = "runtime/llvm-DenseSet.h"; sourceTree = "<group>"; };
+ 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 = "<group>"; 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 = "<group>"; };
+ 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 = "<group>"; usesTabs = 1; };
+ 6EF877E72326184000963DBB /* json.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = json.mm; sourceTree = "<group>"; usesTabs = 1; };
+ 6EF877EA232633CC00963DBB /* objcdt.1 */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.man; path = objcdt.1; sourceTree = "<group>"; };
7213C36221FA7C730090A271 /* NSObject-internal.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "NSObject-internal.h"; path = "runtime/NSObject-internal.h"; sourceTree = "<group>"; };
7593EC57202248DF0046AB96 /* objc-object.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "objc-object.h"; path = "runtime/objc-object.h"; sourceTree = "<group>"; };
75A9504E202BAA0300D7D56F /* objc-locks-new.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "objc-locks-new.h"; path = "runtime/objc-locks-new.h"; sourceTree = "<group>"; };
87BB4E900EC39633005D08E1 /* objc-probes.d */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.dtrace; name = "objc-probes.d"; path = "runtime/objc-probes.d"; sourceTree = "<group>"; };
9672F7ED14D5F488007CEC96 /* NSObject.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = NSObject.mm; path = runtime/NSObject.mm; sourceTree = "<group>"; };
BC8B5D1212D3D48100C78A5B /* libauto.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libauto.dylib; path = /usr/lib/libauto.dylib; sourceTree = "<absolute>"; };
+ C217B55222DE556D004369BA /* objc-env.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "objc-env.h"; path = "runtime/objc-env.h"; sourceTree = "<group>"; };
+ C22F5207230EF38B001BFE14 /* objc-ptrauth.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "objc-ptrauth.h"; path = "runtime/objc-ptrauth.h"; sourceTree = "<group>"; };
C2E6D3FB2225DCF00059DFAA /* DenseMapExtras.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = DenseMapExtras.h; path = runtime/DenseMapExtras.h; sourceTree = "<group>"; };
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 = "<group>"; };
/* 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;
838486270D6D690F00CEA253 /* Obsolete Source */,
08FB7795FE84155DC02AAC07 /* Source */,
838485B20D6D67F900CEA253 /* Other */,
+ 6EF877D82325D62600963DBB /* objcdt */,
1AB674ADFE9D54B511CA2CBB /* Products */,
F9BCC72A205C6A1600DD9AFC /* Frameworks */,
);
children = (
D2AAC0630554660B00DB518D /* libobjc.A.dylib */,
F9BCC727205C68E800DD9AFC /* libobjc-trampolines.dylib */,
+ 6EF877D72325D62600963DBB /* objcdt */,
);
name = Products;
sourceTree = "<group>";
};
+ 6EF877D82325D62600963DBB /* objcdt */ = {
+ isa = PBXGroup;
+ children = (
+ 6EF877EA232633CC00963DBB /* objcdt.1 */,
+ 6EF877E62326184000963DBB /* json.h */,
+ 6EF877E72326184000963DBB /* json.mm */,
+ 6EF877D92325D62600963DBB /* objcdt.mm */,
+ 6EF877E32325D95300963DBB /* objcdt-entitlements.plist */,
+ );
+ path = objcdt;
+ sourceTree = "<group>";
+ };
838485B20D6D67F900CEA253 /* Other */ = {
isa = PBXGroup;
children = (
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 = "<group>";
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 */,
F9BCC72A205C6A1600DD9AFC /* Frameworks */ = {
isa = PBXGroup;
children = (
+ 6EF877E42325FAC400963DBB /* Foundation.framework */,
+ 6EF877E12325D93200963DBB /* Symbolication.framework */,
+ 6EF877DF2325D92E00963DBB /* CoreSymbolication.framework */,
);
name = Frameworks;
sourceTree = "<group>";
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 */,
);
/* 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" */;
BuildIndependentTargetsInParallel = NO;
LastUpgradeCheck = 0440;
TargetAttributes = {
+ 6EF877D62325D62600963DBB = {
+ CreatedOnToolsVersion = 11.0;
+ };
+ 6EF877EF23263D7000963DBB = {
+ CreatedOnToolsVersion = 11.0;
+ ProvisioningStyle = Automatic;
+ };
834F9B01212E560100F95A54 = {
CreatedOnToolsVersion = 10.0;
DevelopmentTeam = 59GAB85EFG;
837F67A81A771F63004D34FA /* objc-simulator */,
F9BCC6CA205C68E800DD9AFC /* objc-trampolines */,
834F9B01212E560100F95A54 /* objc4_tests */,
+ 6EF877EF23263D7000963DBB /* objc_executables */,
+ 6EF877D62325D62600963DBB /* objcdt */,
);
};
/* End PBXProject section */
/* 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;
/* End PBXSourcesBuildPhase section */
/* Begin PBXTargetDependency section */
+ 6EF877F423263D8000963DBB /* PBXTargetDependency */ = {
+ isa = PBXTargetDependency;
+ target = 6EF877D62325D62600963DBB /* objcdt */;
+ targetProxy = 6EF877F323263D8000963DBB /* PBXContainerItemProxy */;
+ };
837F67AD1A771F6E004D34FA /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = D2AAC0620554660B00DB518D /* objc */;
};
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 = {
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;
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;
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 = (
--- /dev/null
+/*
+* 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 <cstdint>
+#include <cstdbool>
+#include <stdio.h>
+#include <functional>
+
+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()>);
+ void object(const char *key, std::function<void()>);
+
+ void array(std::function<void()>);
+ void array(const char *key, std::function<void()>);
+
+ 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_ */
--- /dev/null
+/*
+* 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 <assert.h>
+#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<void()> 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<void()> f)
+{
+ key(k);
+ object(f);
+}
+
+void
+writer::array(std::function<void()> 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<void()> 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
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>task_for_pid-allow</key>
+ <true/>
+ <key>com.apple.system-task-ports</key>
+ <true/>
+</dict>
+</plist>
--- /dev/null
+.\" 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
--- /dev/null
+/*
+* 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 <stdio.h>
+#include <sysexits.h>
+#include <getopt.h>
+#include <pthread.h>
+
+int main(int argc, const char *argv[])
+{
+ return EX_UNAVAILABLE;
+}
.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 <function>
.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
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
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
#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 <function>
.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
// 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
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
#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
/////////////////////////////////////////////////////////////////////
.macro MethodTableLookup
- pushl %ebp
- movl %esp, %ebp
-
- subl $$(8+5*16), %esp
+ SAVE_REGS
.if $0 == NORMAL
movl self+4(%ebp), %eax
movl selector_stret+4(%ebp), %ecx
.endif
- movdqa %xmm3, 4*16(%esp)
- movdqa %xmm2, 3*16(%esp)
- movdqa %xmm1, 2*16(%esp)
- 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
// 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
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
#define a2b sil
#define a3 rdx
#define a3d edx
+#define a3b dl
#define a4 rcx
#define a4d ecx
#define a5 r8
#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
.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
// 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
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
#define a2b sil
#define a3 rdx
#define a3d edx
+#define a3b dl
#define a4 rcx
#define a4d ecx
#define a5 r8
#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
.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
// 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
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
typename PointerUnionTypeSelector<T1, T2, RET_EQ, RET_NE>::Return;
};
-template <class PT1, class PT2>
+template <class T1, class T2, typename Auth1, typename Auth2>
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;
explicit PointerUnion(const std::atomic<uintptr_t> &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<uintptr_t> &raw, std::memory_order order) const {
raw.store(_value, order);
template <typename T>
bool is() const {
- using Ty = typename PointerUnionTypeSelector<PT1, T, IsPT1,
- PointerUnionTypeSelector<PT2, T, IsPT2,
+ using Ty = typename PointerUnionTypeSelector<T1 *, T, IsPT1,
+ PointerUnionTypeSelector<T2 *, T, IsPT2,
UNION_DOESNT_CONTAIN_TYPE<T>>>::Return;
return getTag() == Ty::Num;
}
- template <typename T> T get() const {
- ASSERT(is<T>() && "Invalid accessor called");
- return reinterpret_cast<T>(getPointer());
+ template <typename T> T get(const void *address) const {
+ ASSERT(is<T>() && "Invalid accessor called");
+ using AuthT = typename PointerUnionTypeSelector<T1 *, T, Auth1,
+ PointerUnionTypeSelector<T2 *, T, Auth2,
+ UNION_DOESNT_CONTAIN_TYPE<T>>>::Return;
+
+ return AuthT::auth((T)getPointer(), address);
}
- template <typename T> T dyn_cast() const {
+ template <typename T> T dyn_cast(const void *address) const {
if (is<T>())
- return get<T>();
+ return get<T>(address);
return T();
}
};
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
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);
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);
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,
} \
} \
-(NSUInteger)retainCount { \
- return (_rc_ivar + 2) >> 1; \
+ return (NSUInteger)(_rc_ivar + 2) >> 1; \
} \
-(BOOL)_tryRetain { \
__typeof__(_rc_ivar) _prev; \
} 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; \
*/
#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;
return false;
}
-objc_selopt_t *preoptimizedSelectors(void)
-{
- return nil;
-}
-
-bool sharedCacheSupportsProtocolRoots(void)
-{
- return false;
-}
-
Protocol *getPreoptimizedProtocol(const char *name)
{
return nil;
#include <objc-shared-cache.h>
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;
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.
**********************************************************************/
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
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)
{
_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];
}
(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.
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
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
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;
#include "objc-loadmethod.h"
-#if SUPPORT_PREOPT && __cplusplus
-#include <objc-shared-cache.h>
-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)
// 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:
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();
}
bool hasPreoptimizedProtocols() const;
+ bool hasPreoptimizedSectionLookups() const;
+
#if !__OBJC2__
struct old_protocol **proto_refs;
struct objc_module *mod_ptr;
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);
}
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);
std::atomic<Fn> hook{nil};
public:
- ChainedHookFunction(Fn f) : hook{f} { };
+ constexpr ChainedHookFunction(Fn f) : hook{f} { };
Fn get() {
return hook.load(std::memory_order_acquire);
// 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.
unsigned count{0};
union {
T inlineElements[InlineCount];
- T *elements;
+ T *elements{nullptr};
};
public:
#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<typename T, typename Auth>
+struct WrappedPtr {
+private:
+ T *ptr;
+
+public:
+ WrappedPtr(T *p) {
+ *this = p;
+ }
+
+ WrappedPtr(const WrappedPtr<T, Auth> &p) {
+ *this = p;
+ }
+
+ WrappedPtr<T, Auth> &operator =(T *p) {
+ ptr = Auth::sign(p, &ptr);
+ return *this;
+ }
+
+ WrappedPtr<T, Auth> &operator =(const WrappedPtr<T, Auth> &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 <typename T>
+ static T *sign(T *ptr, const void *address) {
+ return ptr;
+ }
+
+ template <typename T>
+ 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 <typename T>
+ static T *sign(T *ptr, const void *address) {
+ return ptr;
+ }
+
+ template <typename T>
+ 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 <unsigned discriminator>
+struct Ptrauth {
+ template <typename T>
+ 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 <typename T>
+ 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 <typename T> using RawPtr = WrappedPtr<T, PtrauthRaw>;
+
+#if __has_feature(ptrauth_calls)
+// Get a ptrauth type that uses a string discriminator.
+#define PTRAUTH_STR(name) Ptrauth<ptrauth_string_discriminator(#name)>
+
+// 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 <typename T> using name ## _authed_ptr \
+ = WrappedPtr<T, PTRAUTH_STR(name)>;
+#else
+#define PTRAUTH_STR(name) PtrauthRaw
+#define DECLARE_AUTHED_PTR_TEMPLATE(name) \
+ template <typename T> using name ## _authed_ptr = RawPtr<T>;
+#endif
+
// _OBJC_PTRAUTH_H_
#endif
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;
typedef struct classref * classref_t;
+/***********************************************************************
+* RelativePointer<T>
+* 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 <typename T>
+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
_objc_swiftMetadataInitializer StubClassInitializerPtrauth initializer;
};
+// A pointer modifier that does nothing to the pointer.
+struct PointerModifierNop {
+ template <typename ListType, typename T>
+ static T *modify(const ListType &list, T *ptr) { return ptr; }
+};
+
/***********************************************************************
-* entsize_list_tt<Element, List, FlagMask>
+* entsize_list_tt<Element, List, FlagMask, PointerModifier>
* 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 <typename Element, typename List, uint32_t FlagMask>
+template <typename Element, typename List, uint32_t FlagMask, typename PointerModifier = PointerModifierNop>
struct entsize_list_tt {
uint32_t entsizeAndFlags;
uint32_t count;
- Element first;
uint32_t entsize() const {
return entsizeAndFlags & ~FlagMask;
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);
}
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;
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<SEL *> name;
+ RelativePointer<const char *> types;
+ RelativePointer<IMP> 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 <typename ListType>
+ 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<const method_t&,
- const method_t&, bool>
+ public std::binary_function<const struct method_t::big&,
+ const struct method_t::big&, bool>
{
- 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 {
};
// Two bits of entsize are used for fixup markers.
-struct method_list_t : entsize_list_tt<method_t, method_list_t, 0x3> {
+// 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<method_t, method_list_t, 0xffff0003, method_t::pointer_modifier> {
bool isUniqued() const;
bool isFixedUp() const;
void setFixedUp();
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<ivar_t, ivar_list_t, 0> {
const uint8_t * ivarLayout;
const char * name;
- method_list_t * baseMethodList;
+ WrappedPtr<method_list_t, PtrauthStrip> baseMethodList;
protocol_list_t * baseProtocols;
const ivar_list_t * ivars;
/***********************************************************************
-* list_array_tt<Element, List>
+* list_array_tt<Element, List, Ptr>
* 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
* countLists/beginLists/endLists iterate the metadata lists
* count/begin/end iterate the underlying metadata elements
**********************************************************************/
-template <typename Element, typename List>
+template <typename Element, typename List, template<typename> class Ptr>
class list_array_tt {
struct array_t {
uint32_t count;
- List* lists[0];
+ Ptr<List> lists[0];
static size_t byteSize(uint32_t count) {
return sizeof(array_t) + count*sizeof(lists[0]);
protected:
class iterator {
- List * const *lists;
- List * const *listsEnd;
+ const Ptr<List> *lists;
+ const Ptr<List> *listsEnd;
typename List::iterator m, mEnd;
public:
- iterator(List *const *begin, List *const *end)
+ iterator(const Ptr<List> *begin, const Ptr<List> *end)
: lists(begin), listsEnd(end)
{
if (begin != end) {
private:
union {
- List* list;
+ Ptr<List> list;
uintptr_t arrayAndFlag;
};
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;
}
iterator end() const {
- List * const *e = endLists();
+ auto e = endLists();
return iterator(e, e);
}
}
}
- List* const * beginLists() const {
+ const Ptr<List>* beginLists() const {
if (hasArray()) {
return array()->lists;
} else {
}
}
- List* const * endLists() const {
+ const Ptr<List>* endLists() const {
if (hasArray()) {
return array()->lists + array()->count;
} else if (list) {
// 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<List> 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();
}
}
}
}
- template<typename Result>
- Result duplicate() {
- Result result;
-
+ template<typename Other>
+ 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<method_t, method_list_t>
+ public list_array_tt<method_t, method_list_t, method_list_t_authed_ptr>
{
- typedef list_array_tt<method_t, method_list_t> Super;
+ typedef list_array_tt<method_t, method_list_t, method_list_t_authed_ptr> 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<method_list_t> *beginCategoryMethodLists() const {
return beginLists();
}
- method_list_t * const *endCategoryMethodLists(Class cls) const;
-
- method_array_t duplicate() {
- return Super::duplicate<method_array_t>();
- }
+ const method_list_t_authed_ptr<method_list_t> *endCategoryMethodLists(Class cls) const;
};
class property_array_t :
- public list_array_tt<property_t, property_list_t>
+ public list_array_tt<property_t, property_list_t, RawPtr>
{
- typedef list_array_tt<property_t, property_list_t> Super;
+ typedef list_array_tt<property_t, property_list_t, RawPtr> Super;
public:
property_array_t() : Super() { }
property_array_t(property_list_t *l) : Super(l) { }
-
- property_array_t duplicate() {
- return Super::duplicate<property_array_t>();
- }
};
class protocol_array_t :
- public list_array_tt<protocol_ref_t, protocol_list_t>
+ public list_array_tt<protocol_ref_t, protocol_list_t, RawPtr>
{
- typedef list_array_tt<protocol_ref_t, protocol_list_t> Super;
+ typedef list_array_tt<protocol_ref_t, protocol_list_t, RawPtr> Super;
public:
protocol_array_t() : Super() { }
protocol_array_t(protocol_list_t *l) : Super(l) { }
-
- protocol_array_t duplicate() {
- return Super::duplicate<protocol_array_t>();
- }
};
struct class_rw_ext_t {
- const class_ro_t *ro;
+ DECLARE_AUTHED_PTR_TEMPLATE(class_ro_t)
+ class_ro_t_authed_ptr<const class_ro_t> ro;
method_array_t methods;
property_array_t properties;
protocol_array_t protocols;
Class nextSiblingClass;
private:
- using ro_or_rw_ext_t = objc::PointerUnion<const class_ro_t *, class_rw_ext_t *>;
+ using ro_or_rw_ext_t = objc::PointerUnion<const class_ro_t, class_rw_ext_t, PTRAUTH_STR("class_ro_t"), PTRAUTH_STR("class_rw_ext_t")>;
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);
}
class_rw_ext_t *ext() const {
- return get_ro_or_rwe().dyn_cast<class_rw_ext_t *>();
+ return get_ro_or_rwe().dyn_cast<class_rw_ext_t *>(&ro_or_rw_ext);
}
class_rw_ext_t *extAllocIfNeeded() {
auto v = get_ro_or_rwe();
if (fastpath(v.is<class_rw_ext_t *>())) {
- return v.get<class_rw_ext_t *>();
+ return v.get<class_rw_ext_t *>(&ro_or_rw_ext);
} else {
- return extAlloc(v.get<const class_ro_t *>());
+ return extAlloc(v.get<const class_ro_t *>(&ro_or_rw_ext));
}
}
const class_ro_t *ro() const {
auto v = get_ro_or_rwe();
if (slowpath(v.is<class_rw_ext_t *>())) {
- return v.get<class_rw_ext_t *>()->ro;
+ return v.get<class_rw_ext_t *>(&ro_or_rw_ext)->ro;
}
- return v.get<const class_ro_t *>();
+ return v.get<const class_ro_t *>(&ro_or_rw_ext);
}
void set_ro(const class_ro_t *ro) {
auto v = get_ro_or_rwe();
if (v.is<class_rw_ext_t *>()) {
- v.get<class_rw_ext_t *>()->ro = ro;
+ v.get<class_rw_ext_t *>(&ro_or_rw_ext)->ro = ro;
} else {
set_ro_or_rwe(ro);
}
const method_array_t methods() const {
auto v = get_ro_or_rwe();
if (v.is<class_rw_ext_t *>()) {
- return v.get<class_rw_ext_t *>()->methods;
+ return v.get<class_rw_ext_t *>(&ro_or_rw_ext)->methods;
} else {
- return method_array_t{v.get<const class_ro_t *>()->baseMethods()};
+ return method_array_t{v.get<const class_ro_t *>(&ro_or_rw_ext)->baseMethods()};
}
}
const property_array_t properties() const {
auto v = get_ro_or_rwe();
if (v.is<class_rw_ext_t *>()) {
- return v.get<class_rw_ext_t *>()->properties;
+ return v.get<class_rw_ext_t *>(&ro_or_rw_ext)->properties;
} else {
- return property_array_t{v.get<const class_ro_t *>()->baseProperties};
+ return property_array_t{v.get<const class_ro_t *>(&ro_or_rw_ext)->baseProperties};
}
}
const protocol_array_t protocols() const {
auto v = get_ro_or_rwe();
if (v.is<class_rw_ext_t *>()) {
- return v.get<class_rw_ext_t *>()->protocols;
+ return v.get<class_rw_ext_t *>(&ro_or_rw_ext)->protocols;
} else {
- return protocol_array_t{v.get<const class_ro_t *>()->baseProtocols};
+ return protocol_array_t{v.get<const class_ro_t *>(&ro_or_rw_ext)->baseProtocols};
}
}
};
struct category_t {
const char *name;
classref_t cls;
- struct method_list_t *instanceMethods;
- struct method_list_t *classMethods;
+ WrappedPtr<method_list_t, PtrauthStrip> instanceMethods;
+ WrappedPtr<method_list_t, PtrauthStrip> classMethods;
struct protocol_list_t *protocols;
struct property_list_t *instanceProperties;
// Fields below this point are not always present on disk.
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<typename T> static bool method_lists_contains_any(T *mlists, T *end,
SEL sels[], size_t selcount);
static void flushCaches(Class cls);
static void initializeTaggedPointerObfuscator(void);
**********************************************************************/
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<const method_t *, IMP> 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<const method_t *, objc_method_description *> 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:
}
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() {
}
-method_list_t * const *method_array_t::endCategoryMethodLists(Class cls) const
+const method_list_t_authed_ptr<method_list_t> *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.
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.
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;
}
}
// 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;
}
}
static void
scanChangedMethod(Class cls, const method_t *meth)
{
- if (fastpath(!Traits::isInterestingSelector(meth->name))) {
+ if (fastpath(!Traits::isInterestingSelector(meth->name()))) {
return;
}
static bool isInterestingSelector(SEL sel) {
return sel == @selector(alloc) || sel == @selector(allocWithZone:);
}
- static bool scanMethodLists(method_list_t * const *mlists, method_list_t * const *end) {
+ template<typename T>
+ static bool scanMethodLists(T *mlists, T *end) {
SEL sels[2] = { @selector(alloc), @selector(allocWithZone:), };
return method_lists_contains_any(mlists, end, sels, 2);
}
sel == @selector(allowsWeakReference) ||
sel == @selector(retainWeakReference);
}
- static bool scanMethodLists(method_list_t * const *mlists, method_list_t * const *end) {
+ template <typename T>
+ static bool scanMethodLists(T *mlists, T *end) {
SEL sels[8] = {
@selector(retain),
@selector(release),
sel == @selector(isKindOfClass:) ||
sel == @selector(respondsToSelector:);
}
- static bool scanMethodLists(method_list_t * const *mlists, method_list_t * const *end) {
+ template <typename T>
+ static bool scanMethodLists(T *mlists, T *end) {
SEL sels[5] = {
@selector(new),
@selector(self),
// 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();
+ }
}
// 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);
}
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
}
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);
}
}
};
- processCatlist(_getObjc2CategoryList(hi, &count));
- processCatlist(_getObjc2CategoryList2(hi, &count));
+ processCatlist(hi->catlist(&count));
+ processCatlist(hi->catlist2(&count));
}
static void loadAllCategories() {
ts.log("IMAGE TIMES: fix up objc_msgSend_fixup");
#endif
- bool cacheSupportsProtocolRoots = sharedCacheSupportsProtocolRoots();
// Discover protocols. Fix up protocol refs.
for (EACH_HEADER) {
// 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());
// 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++) {
// 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;
// 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);
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);
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() };
}
{
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();
}
method_getTypeEncoding(Method m)
{
if (!m) return nil;
- return m->types;
+ return m->types();
}
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)
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
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
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]);
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};
}
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++;
}
}
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;
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);
}
// Find all the protocols from the pre-optimized images. These protocols
// won't be in the protocol map.
objc::DenseMap<const char*, Protocol*> preoptimizedProtocols;
- if (sharedCacheSupportsProtocolRoots()) {
+ {
header_info *hi;
for (hi = FirstHeader; hi; hi = hi->getNext()) {
if (!hi->hasPreoptimizedProtocols())
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);
}
}
}
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);
}
}
}
{
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) {
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;
}
}
// 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");
}
}
/***********************************************************************
* method_lists_contains_any
**********************************************************************/
+template<typename T>
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++) {
} 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;
}
}
// 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())) {
// curClass method list.
Method meth = getMethodNoSuper_nolock(curClass, sel);
if (meth) {
- imp = meth->imp;
+ imp = meth->imp(false);
goto done;
}
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);
if ((m = getMethodNoSuper_nolock(cls, name))) {
// already exists
if (!replace) {
- result = m->imp;
+ result = m->imp(false);
} else {
result = _method_setImplementation(cls, m, imp);
}
// 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);
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;
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++;
}
}
// 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);
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);
}
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);
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;
if (rwe) {
for (auto& meth : rwe->methods) {
- try_free(meth.types);
+ try_free(meth.types());
}
rwe->methods.tryFree();
}
#include "objc-private.h"
#include "objc-sel-set.h"
-#if SUPPORT_PREOPT
-#include <objc-shared-cache.h>
-static const objc_selopt_t *builtins = NULL;
-#endif
-
__BEGIN_DECLS
static size_t SelrefCount = 0;
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;
}
// save this value for later
SelrefCount = selrefCount;
-#if SUPPORT_PREOPT
- builtins = preoptimizedSelectors();
-#endif
-
// Register selectors used by libobjc
mutex_locker_t lock(selLock);
#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<const char *> namedSelectors;
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);
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;
}
* @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.
[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);
--- /dev/null
+// TEST_CFLAGS -lobjc
+
+#include "test.h"
+#include <dlfcn.h>
+
+// 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__);
+}
/*
+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
--- /dev/null
+#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
--- /dev/null
+// 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__);
+}
--- /dev/null
+/*
+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");
+}
}
-@interface OS_object <NSObject>
-+(id)alloc;
-@end
-
@interface Fake_OS_object : NSObject {
int refcnt;
int xref_cnt;
}
@end
-@interface Sub_OS_object : OS_object @end
+@interface Sub_OS_object : NSObject @end
@implementation Sub_OS_object
@end
int main()
{
+ Class OS_object = objc_getClass("OS_object");
+ class_setSuperclass([Sub_OS_object class], OS_object);
+
uintptr_t isa;
#if SUPPORT_PACKED_ISA
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]);
--- /dev/null
+// 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 <objc/NSObject.h>
+#include <objc/objc-internal.h>
+#include <CoreFoundation/CoreFoundation.h>
+
+
+// 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();
+}
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)
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:
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;
$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/) {
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;
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}'";
}
$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");