Skip to content

Commit 9ba0b68

Browse files
authored
Merge pull request #17930 from jrose-apple/4.2-better-class_getImageName
[4.2] Override ObjC's class_getImageName to handle Swift classes https://bugs.swift.org/browse/SR-1917
2 parents 85df97e + ab5bf9f commit 9ba0b68

File tree

9 files changed

+330
-13
lines changed

9 files changed

+330
-13
lines changed

lib/Driver/ToolChains.cpp

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1293,6 +1293,26 @@ static void addLinkSanitizerLibArgsForLinux(const ArgList &Args,
12931293
Arguments.push_back("-ldl");
12941294
}
12951295

1296+
/// Returns true if the compiler depends on features provided by the ObjC
1297+
/// runtime that are not present on the deployment target indicated by
1298+
/// \p triple.
1299+
static bool wantsObjCRuntime(const llvm::Triple &triple) {
1300+
assert((!triple.isTvOS() || triple.isiOS()) &&
1301+
"tvOS is considered a kind of iOS");
1302+
1303+
// When updating the versions listed here, please record the most recent
1304+
// feature being depended on and when it was introduced:
1305+
//
1306+
// - The hook to override class_getImageName (macOS 10.14 and equivalent)
1307+
if (triple.isiOS())
1308+
return triple.isOSVersionLT(12);
1309+
if (triple.isMacOSX())
1310+
return triple.isMacOSXVersionLT(10, 14);
1311+
if (triple.isWatchOS())
1312+
return triple.isOSVersionLT(5);
1313+
llvm_unreachable("unknown Darwin OS");
1314+
}
1315+
12961316
ToolChain::InvocationInfo
12971317
toolchains::Darwin::constructInvocation(const LinkJobAction &job,
12981318
const JobContext &context) const {
@@ -1377,15 +1397,9 @@ toolchains::Darwin::constructInvocation(const LinkJobAction &job,
13771397
if (llvm::sys::fs::exists(CompilerRTPath))
13781398
Arguments.push_back(context.Args.MakeArgString(CompilerRTPath));
13791399

1380-
bool wantsObjCRuntime = false;
1381-
if (Triple.isiOS())
1382-
wantsObjCRuntime = Triple.isOSVersionLT(9);
1383-
else if (Triple.isMacOSX())
1384-
wantsObjCRuntime = Triple.isMacOSXVersionLT(10, 11);
1385-
13861400
if (context.Args.hasFlag(options::OPT_link_objc_runtime,
13871401
options::OPT_no_link_objc_runtime,
1388-
/*Default=*/wantsObjCRuntime)) {
1402+
/*Default=*/wantsObjCRuntime(Triple))) {
13891403
llvm::SmallString<128> ARCLiteLib(D.getSwiftProgramPath());
13901404
llvm::sys::path::remove_filename(ARCLiteLib); // 'swift'
13911405
llvm::sys::path::remove_filename(ARCLiteLib); // 'bin'

stdlib/public/runtime/Metadata.cpp

Lines changed: 59 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
#include "swift/Runtime/ExistentialContainer.h"
2525
#include "swift/Runtime/HeapObject.h"
2626
#include "swift/Runtime/Mutex.h"
27+
#include "swift/Runtime/Once.h"
2728
#include "swift/Strings.h"
2829
#include "llvm/Support/MathExtras.h"
2930
#include "llvm/Support/PointerLikeTypeTraits.h"
@@ -54,6 +55,7 @@
5455
#endif
5556

5657
#if SWIFT_OBJC_INTEROP
58+
#include <dlfcn.h>
5759
#include <objc/runtime.h>
5860
#endif
5961

@@ -593,7 +595,7 @@ swift::swift_getObjCClassFromMetadata(const Metadata *theMetadata) {
593595

594596
// Otherwise, the input should already be a Swift class object.
595597
auto theClass = cast<ClassMetadata>(theMetadata);
596-
assert(theClass->isTypeMetadata() && !theClass->isArtificialSubclass());
598+
assert(theClass->isTypeMetadata());
597599
return theClass;
598600
}
599601

@@ -2017,6 +2019,45 @@ swift::swift_relocateClassMetadata(ClassMetadata *self,
20172019
return self;
20182020
}
20192021

2022+
#if SWIFT_OBJC_INTEROP
2023+
2024+
// FIXME: This is from a later version of <objc/runtime.h>. Once the declaration
2025+
// is available in SDKs, we can remove this typedef.
2026+
typedef BOOL (*objc_hook_getImageName)(
2027+
Class _Nonnull cls, const char * _Nullable * _Nonnull outImageName);
2028+
2029+
/// \see customGetImageNameFromClass
2030+
static objc_hook_getImageName defaultGetImageNameFromClass = nullptr;
2031+
2032+
/// A custom implementation of Objective-C's class_getImageName for Swift
2033+
/// classes, which knows how to handle dynamically-initialized class metadata.
2034+
///
2035+
/// Per the documentation for objc_setHook_getImageName, any non-Swift classes
2036+
/// will still go through the normal implementation of class_getImageName,
2037+
/// which is stored in defaultGetImageNameFromClass.
2038+
static BOOL
2039+
customGetImageNameFromClass(Class _Nonnull objcClass,
2040+
const char * _Nullable * _Nonnull outImageName) {
2041+
auto *classAsMetadata = reinterpret_cast<const ClassMetadata *>(objcClass);
2042+
2043+
// Is this a Swift class?
2044+
if (classAsMetadata->isTypeMetadata() &&
2045+
!classAsMetadata->isArtificialSubclass()) {
2046+
const void *descriptor = classAsMetadata->getDescription();
2047+
assert(descriptor &&
2048+
"all non-artificial Swift classes should have a descriptor");
2049+
Dl_info imageInfo = {};
2050+
if (!dladdr(descriptor, &imageInfo))
2051+
return NO;
2052+
*outImageName = imageInfo.dli_fname;
2053+
return imageInfo.dli_fname != nullptr;
2054+
}
2055+
2056+
// If not, fall back to the default implementation.
2057+
return defaultGetImageNameFromClass(objcClass, outImageName);
2058+
}
2059+
#endif
2060+
20202061
/// Initialize the field offset vector for a dependent-layout class, using the
20212062
/// "Universal" layout strategy.
20222063
void
@@ -2025,6 +2066,23 @@ swift::swift_initClassMetadata(ClassMetadata *self,
20252066
size_t numFields,
20262067
const TypeLayout * const *fieldTypes,
20272068
size_t *fieldOffsets) {
2069+
#if SWIFT_OBJC_INTEROP
2070+
// Register our custom implementation of class_getImageName.
2071+
static swift_once_t onceToken;
2072+
swift_once(&onceToken, [](void *unused) {
2073+
(void)unused;
2074+
// FIXME: This is from a later version of <objc/runtime.h>. Once the
2075+
// declaration is available in SDKs, we can access this directly instead of
2076+
// using dlsym.
2077+
if (void *setHookPtr = dlsym(RTLD_DEFAULT, "objc_setHook_getImageName")) {
2078+
auto setHook = reinterpret_cast<
2079+
void(*)(objc_hook_getImageName _Nonnull,
2080+
objc_hook_getImageName _Nullable * _Nonnull)>(setHookPtr);
2081+
setHook(customGetImageNameFromClass, &defaultGetImageNameFromClass);
2082+
}
2083+
}, nullptr);
2084+
#endif
2085+
20282086
_swift_initializeSuperclass(self);
20292087

20302088
// Start layout by appending to a standard heap object header.

test/Driver/linker-arclite.swift

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,15 +11,24 @@
1111

1212
// RUN: %swiftc_driver -driver-print-jobs -target x86_64-apple-ios8.0 %S/../Inputs/empty.swift | %FileCheck -check-prefix IOS_ARCLITE %s
1313

14-
// RUN: %swiftc_driver -driver-print-jobs -target x86_64-apple-macosx10.11 %S/../Inputs/empty.swift | %FileCheck -check-prefix NO_ARCLITE %s
15-
// RUN: %swiftc_driver -driver-print-jobs -target x86_64-apple-ios9.0 %S/../Inputs/empty.swift | %FileCheck -check-prefix NO_ARCLITE %s
16-
// RUN: %swiftc_driver -driver-print-jobs -target arm64-apple-tvos9.0 %S/../Inputs/empty.swift | %FileCheck -check-prefix NO_ARCLITE %s
17-
// RUN: %swiftc_driver -driver-print-jobs -target armv7k-apple-watchos2.0 %S/../Inputs/empty.swift | %FileCheck -check-prefix NO_ARCLITE %s
18-
1914
// IOS_ARCLITE: bin/ld{{"? }}
2015
// IOS_ARCLITE: -force_load {{[^ ]+/lib/arc/libarclite_iphonesimulator.a}}
2116
// IOS_ARCLITE: -o {{[^ ]+}}
2217

18+
19+
// RUN: %swiftc_driver -driver-print-jobs -target x86_64-apple-macosx10.14 %S/../Inputs/empty.swift | %FileCheck -check-prefix NO_ARCLITE %s
20+
// RUN: %swiftc_driver -driver-print-jobs -target x86_64-apple-macosx10.13 %S/../Inputs/empty.swift | %FileCheck -check-prefix ANY_ARCLITE %s
21+
// RUN: %swiftc_driver -driver-print-jobs -target x86_64-apple-ios12 %S/../Inputs/empty.swift | %FileCheck -check-prefix NO_ARCLITE %s
22+
// RUN: %swiftc_driver -driver-print-jobs -target x86_64-apple-ios11 %S/../Inputs/empty.swift | %FileCheck -check-prefix ANY_ARCLITE %s
23+
// RUN: %swiftc_driver -driver-print-jobs -target arm64-apple-tvos12 %S/../Inputs/empty.swift | %FileCheck -check-prefix NO_ARCLITE %s
24+
// RUN: %swiftc_driver -driver-print-jobs -target arm64-apple-tvos11 %S/../Inputs/empty.swift | %FileCheck -check-prefix ANY_ARCLITE %s
25+
// RUN: %swiftc_driver -driver-print-jobs -target armv7k-apple-watchos5 %S/../Inputs/empty.swift | %FileCheck -check-prefix NO_ARCLITE %s
26+
// RUN: %swiftc_driver -driver-print-jobs -target armv7k-apple-watchos4 %S/../Inputs/empty.swift | %FileCheck -check-prefix ANY_ARCLITE %s
27+
2328
// NO_ARCLITE: bin/ld{{"? }}
2429
// NO_ARCLITE-NOT: arclite
2530
// NO_ARCLITE: -o {{[^ ]+}}
31+
32+
// ANY_ARCLITE: bin/ld{{"? }}
33+
// ANY_ARCLITE: -force_load {{[^ ]+}}/lib/arc/libarclite_{{.+}}.a
34+
// ANY_ARCLITE: -o {{[^ ]+}}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import Foundation
2+
3+
public class SimpleSubclass: NSObject {}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import Foundation
2+
3+
public class SimpleSwiftObject {}
4+
public class SimpleNSObject: NSObject {
5+
@objc public dynamic var observableName: String = ""
6+
}
7+
8+
public class GenericSwiftObject<T> {}
9+
public class GenericNSObject<T>: NSObject {}
10+
11+
public class GenericAncestrySwiftObject: GenericSwiftObject<AnyObject> {}
12+
public class GenericAncestryNSObject: GenericNSObject<AnyObject> {
13+
@objc public dynamic var observableName: String = ""
14+
}
15+
16+
public class ResilientFieldSwiftObject {
17+
public var url: URL?
18+
public var data: Data?
19+
}
20+
public class ResilientFieldNSObject: NSObject {
21+
public var url: URL?
22+
public var data: Data?
23+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
static inline const char *getNameOfClassToFind() {
2+
return "SimpleNSObjectSubclass.SimpleSubclass";
3+
}
4+
5+
static inline const char *getHookName() {
6+
return "objc_setHook_getImageName";
7+
}

test/Interpreter/SDK/KVO.swift

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,3 +55,20 @@ foo.addObserver(foo, forKeyPath: "foo", options: [], context: &kvoContext)
5555
let bar = foo.foo
5656
// CHECK-NEXT: 0
5757
print(bar)
58+
59+
let fooClass: AnyClass = object_getClass(foo)!
60+
precondition(fooClass !== Foo.self, "no KVO subclass?")
61+
precondition(fooClass is Foo.Type, "improper KVO subclass")
62+
precondition(!(fooClass is Observer.Type), "improper KVO subclass")
63+
64+
let fooClassAsObject: AnyObject = fooClass
65+
precondition(fooClassAsObject !== Foo.self, "no KVO subclass?")
66+
precondition(fooClassAsObject is Foo.Type, "improper KVO subclass")
67+
precondition(!(fooClassAsObject is Observer.Type), "improper KVO subclass")
68+
69+
let fooClassAsAny: Any = fooClass
70+
precondition(fooClassAsAny is Foo.Type, "improper KVO subclass")
71+
precondition(!(fooClassAsAny is Observer.Type), "improper KVO subclass")
72+
73+
// CHECK-NEXT: class metadata checks okay
74+
print("class metadata checks okay")
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
// RUN: %empty-directory(%t)
2+
// RUN: %target-build-swift -emit-library -o %t/libSimpleNSObjectSubclass.dylib %S/Inputs/SimpleNSObjectSubclass.swift -Xlinker -install_name -Xlinker @executable_path/libSimpleNSObjectSubclass.dylib
3+
// RUN: %target-codesign %t/libSimpleNSObjectSubclass.dylib
4+
5+
// RUN: %target-build-swift %s -o %t/main -lSimpleNSObjectSubclass -L%t -import-objc-header %S/Inputs/class_getImageName-static-helper.h
6+
// RUN: %target-run %t/main %t/libSimpleNSObjectSubclass.dylib
7+
8+
// REQUIRES: executable_test
9+
// REQUIRES: objc_interop
10+
11+
import Darwin
12+
import ObjectiveC
13+
// import SimpleNSObjectSubclass // Deliberately omitted in favor of dynamic loads.
14+
15+
// Note: The following typealias uses AnyObject instead of AnyClass so that the
16+
// function type is trivially bridgeable to Objective-C. (The representation of
17+
// AnyClass is not the same as Objective-C's 'Class' type.)
18+
typealias GetImageHook = @convention(c) (AnyObject, UnsafeMutablePointer<UnsafePointer<CChar>?>) -> ObjCBool
19+
var hook: GetImageHook?
20+
21+
func checkThatSwiftHookWasNotInstalled() {
22+
// Check that the Swift hook did not get installed.
23+
guard let setHookPtr = dlsym(UnsafeMutableRawPointer(bitPattern: -2),
24+
getHookName()) else {
25+
// If the version of the ObjC runtime we're using doesn't have the hook,
26+
// we're good.
27+
return
28+
}
29+
30+
let setHook = unsafeBitCast(setHookPtr, to: (@convention(c) (GetImageHook, UnsafeMutablePointer<GetImageHook?>) -> Void).self)
31+
setHook({ hook!($0, $1) }, &hook)
32+
33+
var info: Dl_info = .init()
34+
guard 0 != dladdr(unsafeBitCast(hook, to: UnsafeRawPointer.self), &info) else {
35+
fatalError("could not get dladdr info for objc_hook_getImageName")
36+
}
37+
38+
precondition(String(cString: info.dli_fname).hasSuffix("libobjc.A.dylib"),
39+
"hook was replaced")
40+
}
41+
42+
// It's important that this test does not register any Swift classes with the
43+
// Objective-C runtime---that's where Swift sets up its custom hook, and we want
44+
// to check the behavior /without/ that hook. That includes the buffer types for
45+
// String and Array. Therefore, we get C strings directly from a bridging
46+
// header.
47+
48+
guard let theClass = objc_getClass(getNameOfClassToFind()) as! AnyClass? else {
49+
fatalError("could not find class")
50+
}
51+
52+
guard let imageName = class_getImageName(theClass) else {
53+
fatalError("could not find image")
54+
}
55+
56+
checkThatSwiftHookWasNotInstalled()
57+
58+
// Okay, now we can use String.
59+
60+
precondition(String(cString: imageName).hasSuffix("libSimpleNSObjectSubclass.dylib"),
61+
"found wrong image")

0 commit comments

Comments
 (0)