Skip to content

Commit 55f273a

Browse files
authored
Merge pull request #19413 from mikeash/patch-bundleforclass-4.2
[4.2][Runtime][Foundation] Supplement the class_getImageName patch with a patch to +[NSBundle bundleForClass:] on older "embedded" targets
2 parents eeb6230 + 82dc19b commit 55f273a

File tree

4 files changed

+140
-24
lines changed

4 files changed

+140
-24
lines changed
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2014 - 2018 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
13+
#import <Foundation/Foundation.h>
14+
15+
#include <objc/runtime.h>
16+
17+
// This method is only used on "embedded" targets. It's not necessary on
18+
// Mac or simulators.
19+
#if TARGET_OS_IPHONE && !TARGET_OS_SIMULATOR
20+
21+
/// CoreFoundation SPI for finding the enclosing bundle. This is only
22+
/// ever called on older OSes, so there's no worry of running into
23+
/// trouble if the implementation is changed later on.
24+
extern "C" CFURLRef _CFBundleCopyBundleURLForExecutableURL(CFURLRef url);
25+
26+
@implementation NSBundle (SwiftAdditions)
27+
28+
/// Given an executable path as a C string, look up the corresponding
29+
/// NSBundle instance, if any.
30+
+ (NSBundle *)_swift_bundleWithExecutablePath: (const char *)path {
31+
NSString *nspath = [[NSFileManager defaultManager]
32+
stringWithFileSystemRepresentation:path length:strlen(path)];
33+
NSURL *executableURL = [NSURL fileURLWithPath:nspath];
34+
NSURL *bundleURL =
35+
(NSURL *)_CFBundleCopyBundleURLForExecutableURL((CFURLRef)executableURL);
36+
if (!bundleURL)
37+
return nil;
38+
39+
NSBundle *bundle = [NSBundle bundleWithURL: bundleURL];
40+
[bundleURL release];
41+
return bundle;
42+
}
43+
44+
@end
45+
46+
#endif

stdlib/public/SDK/Foundation/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ include("../../../../cmake/modules/StandaloneOverlay.cmake")
44
add_swift_library(swiftFoundation ${SWIFT_SDK_OVERLAY_LIBRARY_BUILD_TYPES} IS_SDK_OVERLAY
55
AffineTransform.swift
66
Boxing.swift
7+
BundleLookup.mm
78
Calendar.swift
89
CharacterSet.swift
910
Codable.swift

stdlib/public/runtime/CMakeLists.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ set(swift_runtime_objc_sources
3131
SwiftObject.mm
3232
SwiftValue.mm
3333
ReflectionMirror.mm
34-
ObjCRuntimeGetImageNameFromClass.cpp
34+
ObjCRuntimeGetImageNameFromClass.mm
3535
"${SWIFT_SOURCE_DIR}/lib/Demangling/OldRemangler.cpp"
3636
"${SWIFT_SOURCE_DIR}/lib/Demangling/Remangler.cpp"
3737
"${SWIFT_SOURCE_DIR}/lib/Demangling/TypeDecoder.cpp"

stdlib/public/runtime/ObjCRuntimeGetImageNameFromClass.cpp renamed to stdlib/public/runtime/ObjCRuntimeGetImageNameFromClass.mm

Lines changed: 92 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,23 @@
2525

2626
#include <dlfcn.h>
2727
#include <objc/runtime.h>
28+
#include <objc/message.h>
29+
#include <TargetConditionals.h>
2830

2931
// Note: There are more #includes below under "Function patching machinery".
3032
// Those are only relevant to the function patching machinery.
3133

34+
// On "embedded" targets (i.e. iOS/tvOS/watchOS devices), we need to
35+
// patch +[NSBundle bundleForClass:] directly. The symbol table patch
36+
// does not work for calls within the shared cache on those platforms,
37+
// so the call within +bundleForClass: does not get patched. Instead,
38+
// swizzle out the whole method with one that does the appropriate
39+
// lookup for Swift classes. The symbol table patch handles this on Mac
40+
// and simulators so this is not necessary there.
41+
#if TARGET_OS_IPHONE && !TARGET_OS_SIMULATOR
42+
#define PATCH_NSBUNDLE 1
43+
#endif
44+
3245
using namespace swift;
3346

3447

@@ -40,12 +53,8 @@ typedef BOOL (*objc_hook_getImageName)(
4053
/// \see customGetImageNameFromClass
4154
static objc_hook_getImageName defaultGetImageNameFromClass = nullptr;
4255

43-
/// A custom implementation of Objective-C's class_getImageName for Swift
44-
/// classes, which knows how to handle dynamically-initialized class metadata.
45-
///
46-
/// Per the documentation for objc_setHook_getImageName, any non-Swift classes
47-
/// will still go through the normal implementation of class_getImageName,
48-
/// which is stored in defaultGetImageNameFromClass.
56+
/// Get the image name corresponding to a Swift class, accounting for
57+
/// dynamically-initialized class metadata. Returns NO for ObjC classes.
4958
static BOOL
5059
getImageNameFromSwiftClass(Class _Nonnull objcClass,
5160
const char * _Nullable * _Nonnull outImageName) {
@@ -63,8 +72,21 @@ getImageNameFromSwiftClass(Class _Nonnull objcClass,
6372
*outImageName = imageInfo.dli_fname;
6473
return imageInfo.dli_fname != nullptr;
6574
}
75+
76+
return NO;
77+
}
6678

67-
// If not, fall back to the default implementation.
79+
/// A custom implementation of Objective-C's class_getImageName for Swift
80+
/// classes, which knows how to handle dynamically-initialized class metadata.
81+
///
82+
/// Per the documentation for objc_setHook_getImageName, any non-Swift classes
83+
/// will still go through the normal implementation of class_getImageName,
84+
/// which is stored in defaultGetImageNameFromClass.
85+
static BOOL
86+
replacementGetImageNameFromClass(Class _Nonnull objcClass,
87+
const char * _Nullable * _Nonnull outImageName) {
88+
if (getImageNameFromSwiftClass(objcClass, outImageName))
89+
return YES;
6890
return defaultGetImageNameFromClass(objcClass, outImageName);
6991
}
7092

@@ -100,15 +122,6 @@ namespace {
100122
typedef struct segment_command macho_segment_command;
101123
#endif
102124

103-
struct patch_t {
104-
const char *name;
105-
const void *fn;
106-
107-
template<typename T>
108-
patch_t(const char *newName, const T *newFn)
109-
: name(newName), fn((const void*)newFn) {
110-
}
111-
};
112125
} // end anonymous namespace
113126

114127
/// Overwrite a cross-image symbol reference by directly editing symbol tables
@@ -121,7 +134,8 @@ namespace {
121134
///
122135
/// Also, if the symbol being patched has references within the image where it
123136
/// was originaly defined, those references will \e not be patched.
124-
static void patchLazyPointers(const mach_header *mh, patch_t patch) {
137+
static void patchLazyPointers(const mach_header *mh, const char *symbolName,
138+
const void *newValue) {
125139
// Get linkEditBase
126140
const uint32_t cmd_count = mh->ncmds;
127141
const load_command * const cmds =
@@ -205,10 +219,10 @@ static void patchLazyPointers(const mach_header *mh, patch_t patch) {
205219
// Found symbol for this lazy pointer, now lookup address.
206220
const char *lazyTargetName =
207221
&stringTable[symbolTable[symbolIndex].n_un.n_strx];
208-
if (strcmp(patch.name, lazyTargetName) == 0) {
222+
if (strcmp(symbolName, lazyTargetName) == 0) {
209223
// Can't use the value currently stored here because it may
210224
// be a dyld stub binder that will undo our patch if called.
211-
symbolPointers[lazyIndex] = (uintptr_t)patch.fn;
225+
symbolPointers[lazyIndex] = (uintptr_t)newValue;
212226
}
213227
}
214228
}
@@ -246,18 +260,73 @@ static const char *patchedGetImageNameFromClassForOldOSs(Class _Nullable cls) {
246260
if (!cls)
247261
return nullptr;
248262
const char *result;
249-
if (getImageNameFromSwiftClass(cls, &result))
263+
if (replacementGetImageNameFromClass(cls, &result))
250264
return result;
251265
return nullptr;
252266
}
253267

268+
#if PATCH_NSBUNDLE
269+
/// Selectors for the target method, patch method, and helper method.
270+
#define BUNDLE_FOR_CLASS_SEL @selector(bundleForClass:)
271+
#define PATCHED_BUNDLE_FOR_CLASS_SEL @selector(_swift_bundleForClass:)
272+
#define BUNDLE_WITH_EXECUTABLE_PATH_SEL @selector(_swift_bundleWithExecutablePath:)
273+
274+
/// Whether the patch has already been done.
275+
static bool didPatchNSBundle = false;
276+
277+
/// The patched version of +[NSBundle bundleForClass:]. If the class is
278+
/// actually a Swift class and an image name can be retrieved from it,
279+
/// look up the bundle based on that image name. Otherwise fall back to
280+
/// the original version.
281+
static id patchedBundleForClass(id self, SEL _cmd, Class objcClass) {
282+
const char *imageName;
283+
if (getImageNameFromSwiftClass(objcClass, &imageName)) {
284+
return ((id (*)(id, SEL, const char *))objc_msgSend)(
285+
self, BUNDLE_WITH_EXECUTABLE_PATH_SEL, imageName);
286+
}
287+
288+
// Call through to the original, which is now found under the patched
289+
// selector.
290+
return ((id (*)(id, SEL, Class))objc_msgSend)(
291+
self, PATCHED_BUNDLE_FOR_CLASS_SEL, objcClass);
292+
}
293+
294+
/// Install the patched +[NSBundle bundleForClass:].
295+
static void patchNSBundle(void) {
296+
if (didPatchNSBundle) return;
297+
298+
Class NSBundle = objc_getClass("NSBundle");
299+
if (!NSBundle) return;
300+
301+
Method origMethod = class_getClassMethod(NSBundle, BUNDLE_FOR_CLASS_SEL);
302+
if (!origMethod) return;
303+
304+
// Stuff can fail below, but if it does then we can't reasonably try again.
305+
didPatchNSBundle = true;
306+
307+
BOOL success = class_addMethod(
308+
object_getClass(NSBundle), PATCHED_BUNDLE_FOR_CLASS_SEL,
309+
reinterpret_cast<IMP>(patchedBundleForClass), method_getTypeEncoding(origMethod));
310+
if (!success) return;
311+
312+
Method patchMethod = class_getClassMethod(NSBundle, PATCHED_BUNDLE_FOR_CLASS_SEL);
313+
if (!patchMethod) return;
314+
315+
method_exchangeImplementations(origMethod, patchMethod);
316+
}
317+
#endif
318+
254319
/// A hook for _dyld_register_func_for_add_image that overwrites any references
255320
/// to class_getImageName with our custom implementation.
256321
static void patchGetImageNameInImage(const struct mach_header *mh,
257322
intptr_t vmaddr_slide) {
258323
(void)vmaddr_slide;
259-
patchLazyPointers(mh, patch_t("_class_getImageName",
260-
&patchedGetImageNameFromClassForOldOSs));
324+
const void *newImplementationAddr =
325+
reinterpret_cast<const void *>(&patchedGetImageNameFromClassForOldOSs);
326+
patchLazyPointers(mh, "_class_getImageName", newImplementationAddr);
327+
#if PATCH_NSBUNDLE
328+
patchNSBundle();
329+
#endif
261330
}
262331

263332
/***************************************************************************/
@@ -274,7 +343,7 @@ void swift::setUpObjCRuntimeGetImageNameFromClass() {
274343
auto setHook = reinterpret_cast<
275344
void(*)(objc_hook_getImageName _Nonnull,
276345
objc_hook_getImageName _Nullable * _Nonnull)>(setHookPtr);
277-
setHook(getImageNameFromSwiftClass, &defaultGetImageNameFromClass);
346+
setHook(replacementGetImageNameFromClass, &defaultGetImageNameFromClass);
278347

279348
} else {
280349
// On older OSs, manually patch in our new implementation of

0 commit comments

Comments
 (0)