Skip to content

Commit 1c415ca

Browse files
author
Greg Parker
committed
[runtime] Register a hook for class name lookup from libobjc
libobjc needs to look up classes by name. Some Swift classes, such as instantiated generics and their subclasses, are created only on demand. Now a by-name lookup from libobjc counts as a demand for those classes. rdar://problem/27808571
1 parent d8f1917 commit 1c415ca

File tree

4 files changed

+221
-0
lines changed

4 files changed

+221
-0
lines changed

include/swift/Runtime/Metadata.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -495,6 +495,11 @@ SWIFT_RUNTIME_EXPORT
495495
const ClassMetadata *
496496
swift_getObjCClassFromMetadata(const Metadata *theClass);
497497

498+
// Get the ObjC class object from class type metadata,
499+
// or nullptr if the type isn't an ObjC class.
500+
const ClassMetadata *
501+
swift_getObjCClassFromMetadataConditional(const Metadata *theClass);
502+
498503
SWIFT_RUNTIME_EXPORT
499504
const ClassMetadata *
500505
swift_getObjCClassFromObject(HeapObject *object);

stdlib/public/runtime/Metadata.cpp

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -756,6 +756,22 @@ swift::swift_getObjCClassFromMetadata(const Metadata *theMetadata) {
756756
return theClass;
757757
}
758758

759+
const ClassMetadata *
760+
swift::swift_getObjCClassFromMetadataConditional(const Metadata *theMetadata) {
761+
// If it's an ordinary class, return it.
762+
if (auto theClass = dyn_cast<ClassMetadata>(theMetadata)) {
763+
return theClass;
764+
}
765+
766+
// Unwrap ObjC class wrappers.
767+
if (auto wrapper = dyn_cast<ObjCClassWrapperMetadata>(theMetadata)) {
768+
return wrapper->Class;
769+
}
770+
771+
// Not an ObjC class after all.
772+
return nil;
773+
}
774+
759775
#endif
760776

761777
/***************************************************************************/

stdlib/public/runtime/MetadataLookup.cpp

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ using namespace reflection;
4747
#include <objc/runtime.h>
4848
#include <objc/message.h>
4949
#include <objc/objc.h>
50+
#include <dlfcn.h>
5051
#endif
5152

5253
/// Produce a Demangler value suitable for resolving runtime type metadata
@@ -1284,6 +1285,63 @@ swift_stdlib_getTypeByMangledName(
12841285
return swift_checkMetadataState(MetadataState::Complete, metadata).Value;
12851286
}
12861287

1288+
#if SWIFT_OBJC_INTEROP
1289+
1290+
// Return the ObjC class for the given type name.
1291+
// This gets installed as a callback from libobjc.
1292+
1293+
// FIXME: delete this #if and dlsym once we don't
1294+
// need to build with older libobjc headers
1295+
#if !OBJC_GETCLASSHOOK_DEFINED
1296+
using objc_hook_getClass = BOOL(*)(const char * _Nonnull name,
1297+
Class _Nullable * _Nonnull outClass);
1298+
#endif
1299+
static objc_hook_getClass OldGetClassHook;
1300+
1301+
static BOOL
1302+
getObjCClassByMangledName(const char * _Nonnull typeName,
1303+
Class _Nullable * _Nonnull outClass) {
1304+
auto metadata = swift_stdlib_getTypeByMangledName(typeName, strlen(typeName),
1305+
/* no substitutions */
1306+
nullptr, nullptr);
1307+
if (metadata) {
1308+
auto objcClass =
1309+
reinterpret_cast<Class>(
1310+
const_cast<ClassMetadata *>(
1311+
swift_getObjCClassFromMetadataConditional(metadata)));
1312+
1313+
if (objcClass) {
1314+
*outClass = objcClass;
1315+
return YES;
1316+
}
1317+
}
1318+
1319+
return OldGetClassHook(typeName, outClass);
1320+
}
1321+
1322+
__attribute__((constructor))
1323+
static void installGetClassHook() {
1324+
// FIXME: delete this #if and dlsym once we don't
1325+
// need to build with older libobjc headers
1326+
#if !OBJC_GETCLASSHOOK_DEFINED
1327+
using objc_hook_getClass = BOOL(*)(const char * _Nonnull name,
1328+
Class _Nullable * _Nonnull outClass);
1329+
auto objc_setHook_getClass =
1330+
(void(*)(objc_hook_getClass _Nonnull,
1331+
objc_hook_getClass _Nullable * _Nonnull))
1332+
dlsym(RTLD_DEFAULT, "objc_setHook_getClass");
1333+
#endif
1334+
1335+
#pragma clang diagnostic push
1336+
#pragma clang diagnostic ignored "-Wunguarded-availability"
1337+
if (objc_setHook_getClass) {
1338+
objc_setHook_getClass(getObjCClassByMangledName, &OldGetClassHook);
1339+
}
1340+
#pragma clang diagnostic pop
1341+
}
1342+
1343+
#endif
1344+
12871345
unsigned SubstGenericParametersFromMetadata::
12881346
buildDescriptorPath(const ContextDescriptor *context) const {
12891347
// Terminating condition: we don't have a context.
Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
// RUN: %target-run-simple-swift
2+
// REQUIRES: executable_test
3+
// REQUIRES: objc_interop
4+
5+
// Test Swift's hook for objc_getClass()
6+
7+
import StdlibUnittest
8+
import ObjectiveC
9+
import Foundation
10+
11+
// Old OS versions do not have this hook.
12+
let getClassHookMissing = {
13+
nil == dlsym(UnsafeMutableRawPointer(bitPattern: -2),
14+
"objc_setHook_getClass")
15+
}()
16+
17+
var testSuite = TestSuite("objc_getClass")
18+
19+
20+
class SwiftSuperclass { }
21+
class SwiftSubclass : SwiftSuperclass { }
22+
23+
class ObjCSuperclass : NSObject { }
24+
class ObjCSubclass : ObjCSuperclass { }
25+
26+
27+
class MangledSwiftSuperclass { }
28+
class MangledSwiftSubclass : MangledSwiftSuperclass { }
29+
30+
class MangledObjCSuperclass : NSObject { }
31+
class MangledObjCSubclass : MangledObjCSuperclass { }
32+
33+
34+
class GenericSwiftClass<Value> {
35+
let value: Value
36+
init(value: Value) { self.value = value }
37+
}
38+
class ConstrainedSwiftSuperclass : GenericSwiftClass<String> {
39+
init() { super.init(value:"") }
40+
}
41+
class ConstrainedSwiftSubclass : ConstrainedSwiftSuperclass { }
42+
43+
44+
class MangledGenericSwiftClass<Value> {
45+
let value: Value
46+
init(value: Value) { self.value = value }
47+
}
48+
class MangledConstrainedSwiftSuperclass : MangledGenericSwiftClass<String> {
49+
init() { super.init(value:"") }
50+
}
51+
class MangledConstrainedSwiftSubclass : MangledConstrainedSwiftSuperclass { }
52+
53+
54+
class GenericObjCClass<Value> : NSObject {
55+
let value: Value
56+
init(value: Value) { self.value = value }
57+
}
58+
class ConstrainedObjCSuperclass : GenericObjCClass<String> {
59+
init() { super.init(value:"") }
60+
}
61+
class ConstrainedObjCSubclass : ConstrainedObjCSuperclass { }
62+
63+
64+
class MangledGenericObjCClass<Value> : NSObject {
65+
let value: Value
66+
init(value: Value) { self.value = value }
67+
}
68+
class MangledConstrainedObjCSuperclass : MangledGenericObjCClass<String> {
69+
init() { super.init(value:"") }
70+
}
71+
class MangledConstrainedObjCSubclass : MangledConstrainedObjCSuperclass { }
72+
73+
74+
func requireClass(named name: String, demangledName: String) {
75+
for _ in 1...2 {
76+
let cls: AnyClass? = NSClassFromString(name)
77+
expectNotNil(cls, "class named \(name) unexpectedly not found")
78+
expectEqual(NSStringFromClass(cls!), demangledName,
79+
"class named \(name) has the wrong name");
80+
}
81+
}
82+
83+
func requireClass(named name: String) {
84+
return requireClass(named: name, demangledName: name)
85+
}
86+
87+
testSuite.test("Basic") {
88+
requireClass(named: "main.SwiftSubclass")
89+
requireClass(named: "main.SwiftSuperclass")
90+
requireClass(named: "main.ObjCSubclass")
91+
requireClass(named: "main.ObjCSuperclass")
92+
}
93+
94+
testSuite.test("BasicMangled") {
95+
requireClass(named: "_TtC4main20MangledSwiftSubclass",
96+
demangledName: "main.MangledSwiftSubclass")
97+
requireClass(named: "_TtC4main22MangledSwiftSuperclass",
98+
demangledName: "main.MangledSwiftSuperclass")
99+
requireClass(named: "_TtC4main19MangledObjCSubclass",
100+
demangledName: "main.MangledObjCSubclass")
101+
requireClass(named: "_TtC4main21MangledObjCSuperclass",
102+
demangledName: "main.MangledObjCSuperclass")
103+
}
104+
105+
testSuite.test("Generic")
106+
.skip(.custom({ getClassHookMissing },
107+
reason: "objc_getClass hook not present"))
108+
.code {
109+
requireClass(named: "main.ConstrainedSwiftSubclass")
110+
requireClass(named: "main.ConstrainedSwiftSuperclass")
111+
requireClass(named: "main.ConstrainedObjCSubclass")
112+
requireClass(named: "main.ConstrainedObjCSuperclass")
113+
}
114+
115+
testSuite.test("GenericMangled")
116+
.skip(.custom({ getClassHookMissing },
117+
reason: "objc_getClass hook not present"))
118+
.code {
119+
requireClass(named: "_TtC4main24ConstrainedSwiftSubclass",
120+
demangledName: "main.ConstrainedSwiftSubclass")
121+
requireClass(named: "_TtC4main26ConstrainedSwiftSuperclass",
122+
demangledName: "main.ConstrainedSwiftSuperclass")
123+
requireClass(named: "_TtC4main23ConstrainedObjCSubclass",
124+
demangledName: "main.ConstrainedObjCSubclass")
125+
requireClass(named: "_TtC4main25ConstrainedObjCSuperclass",
126+
demangledName: "main.ConstrainedObjCSuperclass")
127+
}
128+
129+
130+
testSuite.test("NotPresent") {
131+
// This class does not exist.
132+
expectNil(NSClassFromString("main.ThisClassDoesNotExist"));
133+
134+
// This name is improperly mangled
135+
expectNil(NSClassFromString("_TtC5main"));
136+
137+
// Swift.Int is not a class type.
138+
expectNil(NSClassFromString("Si"))
139+
}
140+
141+
runAllTests()
142+

0 commit comments

Comments
 (0)