Skip to content

Commit 1bfaa2f

Browse files
authored
Merge pull request #20894 from gparker42/rdar27808571-5.0
[5.0][runtime] Register a hook for class name lookup from libobjc (#20650)
2 parents 3a20a83 + e7f7d91 commit 1bfaa2f

File tree

4 files changed

+289
-0
lines changed

4 files changed

+289
-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
@@ -827,6 +827,22 @@ swift::swift_getObjCClassFromMetadata(const Metadata *theMetadata) {
827827
return theClass;
828828
}
829829

830+
const ClassMetadata *
831+
swift::swift_getObjCClassFromMetadataConditional(const Metadata *theMetadata) {
832+
// If it's an ordinary class, return it.
833+
if (auto theClass = dyn_cast<ClassMetadata>(theMetadata)) {
834+
return theClass;
835+
}
836+
837+
// Unwrap ObjC class wrappers.
838+
if (auto wrapper = dyn_cast<ObjCClassWrapperMetadata>(theMetadata)) {
839+
return wrapper->Class;
840+
}
841+
842+
// Not an ObjC class after all.
843+
return nil;
844+
}
845+
830846
#endif
831847

832848
/***************************************************************************/

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: 210 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,210 @@
1+
// RUN: %empty-directory(%t)
2+
3+
// RUN: %target-build-swift-dylib(%t/libresilient_struct.%target-dylib-extension) -Xfrontend -enable-resilience -Xfrontend -enable-class-resilience %S/../../Inputs/resilient_struct.swift -emit-module -emit-module-path %t/resilient_struct.swiftmodule -module-name resilient_struct
4+
// RUN: %target-codesign %t/libresilient_struct.%target-dylib-extension
5+
6+
// RUN: %target-build-swift-dylib(%t/libresilient_class.%target-dylib-extension) -Xfrontend -enable-resilience -Xfrontend -enable-class-resilience %S/../../Inputs/resilient_class.swift -emit-module -emit-module-path %t/resilient_class.swiftmodule -module-name resilient_class -I%t -L%t -lresilient_struct
7+
// RUN: %target-codesign %t/libresilient_class.%target-dylib-extension
8+
9+
// RUN: %target-build-swift %s -L %t -I %t -lresilient_struct -lresilient_class -o %t/main -Xlinker -rpath -Xlinker %t
10+
// RUN: %target-codesign %t/main
11+
12+
// RUN: %target-run %t/main %t/libresilient_struct.%target-dylib-extension %t/libresilient_class.%target-dylib-extension
13+
14+
15+
// REQUIRES: executable_test
16+
// REQUIRES: objc_interop
17+
18+
// Test Swift's hook for objc_getClass()
19+
20+
import StdlibUnittest
21+
import ObjectiveC
22+
import Foundation
23+
import resilient_struct
24+
import resilient_class
25+
26+
// Old OS versions do not have this hook.
27+
let getClassHookMissing = {
28+
nil == dlsym(UnsafeMutableRawPointer(bitPattern: -2),
29+
"objc_setHook_getClass")
30+
}()
31+
32+
var testSuite = TestSuite("objc_getClass")
33+
34+
35+
class SwiftSuperclass { }
36+
class SwiftSubclass : SwiftSuperclass { }
37+
38+
class ObjCSuperclass : NSObject { }
39+
class ObjCSubclass : ObjCSuperclass { }
40+
41+
42+
class MangledSwiftSuperclass { }
43+
class MangledSwiftSubclass : MangledSwiftSuperclass { }
44+
45+
class MangledObjCSuperclass : NSObject { }
46+
class MangledObjCSubclass : MangledObjCSuperclass { }
47+
48+
49+
class GenericSwiftClass<Value> {
50+
let value: Value
51+
init(value: Value) { self.value = value }
52+
}
53+
class ConstrainedSwiftSuperclass : GenericSwiftClass<String> {
54+
init() { super.init(value:"") }
55+
}
56+
class ConstrainedSwiftSubclass : ConstrainedSwiftSuperclass { }
57+
58+
59+
class MangledGenericSwiftClass<Value> {
60+
let value: Value
61+
init(value: Value) { self.value = value }
62+
}
63+
class MangledConstrainedSwiftSuperclass : MangledGenericSwiftClass<String> {
64+
init() { super.init(value:"") }
65+
}
66+
class MangledConstrainedSwiftSubclass : MangledConstrainedSwiftSuperclass { }
67+
68+
69+
class GenericObjCClass<Value> : NSObject {
70+
let value: Value
71+
init(value: Value) { self.value = value }
72+
}
73+
class ConstrainedObjCSuperclass : GenericObjCClass<String> {
74+
init() { super.init(value:"") }
75+
}
76+
class ConstrainedObjCSubclass : ConstrainedObjCSuperclass { }
77+
78+
79+
class MangledGenericObjCClass<Value> : NSObject {
80+
let value: Value
81+
init(value: Value) { self.value = value }
82+
}
83+
class MangledConstrainedObjCSuperclass : MangledGenericObjCClass<String> {
84+
init() { super.init(value:"") }
85+
}
86+
class MangledConstrainedObjCSubclass : MangledConstrainedObjCSuperclass { }
87+
88+
89+
class ResilientSuperclass : ResilientOutsideParent {
90+
var supervalue = 10
91+
}
92+
93+
class ResilientSubclass : ResilientSuperclass {
94+
var subvalue = 20
95+
}
96+
97+
98+
class ResilientFieldSuperclassSwift {
99+
var supervalue = ResilientInt(i: 1)
100+
}
101+
102+
class ResilientFieldSubclassSwift : ResilientFieldSuperclassSwift {
103+
var subvalue = ResilientInt(i: 2)
104+
}
105+
106+
class ResilientFieldSuperclassObjC : NSObject {
107+
var supervalue = ResilientInt(i: 3)
108+
}
109+
class ResilientFieldSubclassObjC : ResilientFieldSuperclassObjC {
110+
var subvalue = ResilientInt(i: 4)
111+
}
112+
113+
114+
func requireClass(named name: String, demangledName: String) {
115+
for _ in 1...2 {
116+
let cls: AnyClass? = NSClassFromString(name)
117+
expectNotNil(cls, "class named \(name) unexpectedly not found")
118+
expectEqual(NSStringFromClass(cls!), demangledName,
119+
"class named \(name) has the wrong name");
120+
}
121+
}
122+
123+
func requireClass(named name: String) {
124+
return requireClass(named: name, demangledName: name)
125+
}
126+
127+
testSuite.test("Basic") {
128+
requireClass(named: "main.SwiftSubclass")
129+
requireClass(named: "main.SwiftSuperclass")
130+
requireClass(named: "main.ObjCSubclass")
131+
requireClass(named: "main.ObjCSuperclass")
132+
}
133+
134+
testSuite.test("BasicMangled") {
135+
requireClass(named: "_TtC4main20MangledSwiftSubclass",
136+
demangledName: "main.MangledSwiftSubclass")
137+
requireClass(named: "_TtC4main22MangledSwiftSuperclass",
138+
demangledName: "main.MangledSwiftSuperclass")
139+
requireClass(named: "_TtC4main19MangledObjCSubclass",
140+
demangledName: "main.MangledObjCSubclass")
141+
requireClass(named: "_TtC4main21MangledObjCSuperclass",
142+
demangledName: "main.MangledObjCSuperclass")
143+
}
144+
145+
testSuite.test("Generic")
146+
.skip(.custom({ getClassHookMissing },
147+
reason: "objc_getClass hook not present"))
148+
.code {
149+
requireClass(named: "main.ConstrainedSwiftSubclass")
150+
requireClass(named: "main.ConstrainedSwiftSuperclass")
151+
requireClass(named: "main.ConstrainedObjCSubclass")
152+
requireClass(named: "main.ConstrainedObjCSuperclass")
153+
}
154+
155+
testSuite.test("GenericMangled")
156+
.skip(.custom({ getClassHookMissing },
157+
reason: "objc_getClass hook not present"))
158+
.code {
159+
requireClass(named: "_TtC4main24ConstrainedSwiftSubclass",
160+
demangledName: "main.ConstrainedSwiftSubclass")
161+
requireClass(named: "_TtC4main26ConstrainedSwiftSuperclass",
162+
demangledName: "main.ConstrainedSwiftSuperclass")
163+
requireClass(named: "_TtC4main23ConstrainedObjCSubclass",
164+
demangledName: "main.ConstrainedObjCSubclass")
165+
requireClass(named: "_TtC4main25ConstrainedObjCSuperclass",
166+
demangledName: "main.ConstrainedObjCSuperclass")
167+
}
168+
169+
testSuite.test("ResilientSubclass")
170+
.skip(.custom({ getClassHookMissing },
171+
reason: "objc_getClass hook not present"))
172+
.code {
173+
requireClass(named: "main.ResilientSubclass")
174+
requireClass(named: "main.ResilientSuperclass")
175+
176+
expectEqual(ResilientSuperclass().supervalue, 10)
177+
expectEqual(ResilientSubclass().supervalue, 10)
178+
expectEqual(ResilientSubclass().subvalue, 20)
179+
}
180+
181+
testSuite.test("ResilientField")
182+
.skip(.custom({ getClassHookMissing },
183+
reason: "objc_getClass hook not present"))
184+
.code {
185+
requireClass(named: "main.ResilientFieldSubclassSwift")
186+
requireClass(named: "main.ResilientFieldSuperclassSwift")
187+
requireClass(named: "main.ResilientFieldSubclassObjC")
188+
requireClass(named: "main.ResilientFieldSuperclassObjC")
189+
190+
expectEqual(ResilientFieldSuperclassSwift().supervalue.i, 1)
191+
expectEqual(ResilientFieldSubclassSwift().supervalue.i, 1)
192+
expectEqual(ResilientFieldSubclassSwift().subvalue.i, 2)
193+
expectEqual(ResilientFieldSuperclassObjC().supervalue.i, 3)
194+
expectEqual(ResilientFieldSubclassObjC().supervalue.i, 3)
195+
expectEqual(ResilientFieldSubclassObjC().subvalue.i, 4)
196+
}
197+
198+
testSuite.test("NotPresent") {
199+
// This class does not exist.
200+
expectNil(NSClassFromString("main.ThisClassDoesNotExist"));
201+
202+
// This name is improperly mangled
203+
expectNil(NSClassFromString("_TtC5main"));
204+
205+
// Swift.Int is not a class type.
206+
expectNil(NSClassFromString("Si"))
207+
}
208+
209+
runAllTests()
210+

0 commit comments

Comments
 (0)