25
25
26
26
#include < dlfcn.h>
27
27
#include < objc/runtime.h>
28
+ #include < objc/message.h>
29
+ #include < TargetConditionals.h>
28
30
29
31
// Note: There are more #includes below under "Function patching machinery".
30
32
// Those are only relevant to the function patching machinery.
31
33
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
+
32
45
using namespace swift ;
33
46
34
47
@@ -40,12 +53,8 @@ typedef BOOL (*objc_hook_getImageName)(
40
53
// / \see customGetImageNameFromClass
41
54
static objc_hook_getImageName defaultGetImageNameFromClass = nullptr ;
42
55
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.
49
58
static BOOL
50
59
getImageNameFromSwiftClass (Class _Nonnull objcClass,
51
60
const char * _Nullable * _Nonnull outImageName) {
@@ -63,8 +72,21 @@ getImageNameFromSwiftClass(Class _Nonnull objcClass,
63
72
*outImageName = imageInfo.dli_fname ;
64
73
return imageInfo.dli_fname != nullptr ;
65
74
}
75
+
76
+ return NO ;
77
+ }
66
78
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 ;
68
90
return defaultGetImageNameFromClass (objcClass, outImageName);
69
91
}
70
92
@@ -238,11 +260,62 @@ static const char *patchedGetImageNameFromClassForOldOSs(Class _Nullable cls) {
238
260
if (!cls)
239
261
return nullptr ;
240
262
const char *result;
241
- if (getImageNameFromSwiftClass (cls, &result))
263
+ if (replacementGetImageNameFromClass (cls, &result))
242
264
return result;
243
265
return nullptr ;
244
266
}
245
267
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
+
246
319
// / A hook for _dyld_register_func_for_add_image that overwrites any references
247
320
// / to class_getImageName with our custom implementation.
248
321
static void patchGetImageNameInImage (const struct mach_header *mh,
@@ -251,6 +324,9 @@ static void patchGetImageNameInImage(const struct mach_header *mh,
251
324
const void *newImplementationAddr =
252
325
reinterpret_cast <const void *>(&patchedGetImageNameFromClassForOldOSs);
253
326
patchLazyPointers (mh, " _class_getImageName" , newImplementationAddr);
327
+ #if PATCH_NSBUNDLE
328
+ patchNSBundle ();
329
+ #endif
254
330
}
255
331
256
332
/* **************************************************************************/
@@ -267,7 +343,7 @@ void swift::setUpObjCRuntimeGetImageNameFromClass() {
267
343
auto setHook = reinterpret_cast <
268
344
void (*)(objc_hook_getImageName _Nonnull,
269
345
objc_hook_getImageName _Nullable * _Nonnull)>(setHookPtr);
270
- setHook (getImageNameFromSwiftClass , &defaultGetImageNameFromClass);
346
+ setHook (replacementGetImageNameFromClass , &defaultGetImageNameFromClass);
271
347
272
348
} else {
273
349
// On older OSs, manually patch in our new implementation of
0 commit comments