Skip to content

Commit 9210830

Browse files
authored
Merge pull request #19389 from mikeash/patch-bundleforclass
[Runtime][Foundation] Supplement the class_getImageName patch with a patch to +[NSBundle bundleForClass:] on older "embedded" targets
2 parents 72ff34e + e704dd0 commit 9210830

File tree

4 files changed

+133
-10
lines changed

4 files changed

+133
-10
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: 85 additions & 9 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

@@ -238,11 +260,62 @@ static const char *patchedGetImageNameFromClassForOldOSs(Class _Nullable cls) {
238260
if (!cls)
239261
return nullptr;
240262
const char *result;
241-
if (getImageNameFromSwiftClass(cls, &result))
263+
if (replacementGetImageNameFromClass(cls, &result))
242264
return result;
243265
return nullptr;
244266
}
245267

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+
246319
/// A hook for _dyld_register_func_for_add_image that overwrites any references
247320
/// to class_getImageName with our custom implementation.
248321
static void patchGetImageNameInImage(const struct mach_header *mh,
@@ -251,6 +324,9 @@ static void patchGetImageNameInImage(const struct mach_header *mh,
251324
const void *newImplementationAddr =
252325
reinterpret_cast<const void *>(&patchedGetImageNameFromClassForOldOSs);
253326
patchLazyPointers(mh, "_class_getImageName", newImplementationAddr);
327+
#if PATCH_NSBUNDLE
328+
patchNSBundle();
329+
#endif
254330
}
255331

256332
/***************************************************************************/
@@ -267,7 +343,7 @@ void swift::setUpObjCRuntimeGetImageNameFromClass() {
267343
auto setHook = reinterpret_cast<
268344
void(*)(objc_hook_getImageName _Nonnull,
269345
objc_hook_getImageName _Nullable * _Nonnull)>(setHookPtr);
270-
setHook(getImageNameFromSwiftClass, &defaultGetImageNameFromClass);
346+
setHook(replacementGetImageNameFromClass, &defaultGetImageNameFromClass);
271347

272348
} else {
273349
// On older OSs, manually patch in our new implementation of

0 commit comments

Comments
 (0)