diff --git a/include/swift/Runtime/Metadata.h b/include/swift/Runtime/Metadata.h index fffea61a4bc97..8b1b37df2d5fd 100644 --- a/include/swift/Runtime/Metadata.h +++ b/include/swift/Runtime/Metadata.h @@ -495,6 +495,11 @@ SWIFT_RUNTIME_EXPORT const ClassMetadata * swift_getObjCClassFromMetadata(const Metadata *theClass); +// Get the ObjC class object from class type metadata, +// or nullptr if the type isn't an ObjC class. +const ClassMetadata * +swift_getObjCClassFromMetadataConditional(const Metadata *theClass); + SWIFT_RUNTIME_EXPORT const ClassMetadata * swift_getObjCClassFromObject(HeapObject *object); diff --git a/stdlib/public/runtime/Metadata.cpp b/stdlib/public/runtime/Metadata.cpp index 42f5abf452da9..009724ef0ebc1 100644 --- a/stdlib/public/runtime/Metadata.cpp +++ b/stdlib/public/runtime/Metadata.cpp @@ -756,6 +756,22 @@ swift::swift_getObjCClassFromMetadata(const Metadata *theMetadata) { return theClass; } +const ClassMetadata * +swift::swift_getObjCClassFromMetadataConditional(const Metadata *theMetadata) { + // If it's an ordinary class, return it. + if (auto theClass = dyn_cast(theMetadata)) { + return theClass; + } + + // Unwrap ObjC class wrappers. + if (auto wrapper = dyn_cast(theMetadata)) { + return wrapper->Class; + } + + // Not an ObjC class after all. + return nil; +} + #endif /***************************************************************************/ diff --git a/stdlib/public/runtime/MetadataLookup.cpp b/stdlib/public/runtime/MetadataLookup.cpp index f4a83c313ec05..7f0abdd86fa65 100644 --- a/stdlib/public/runtime/MetadataLookup.cpp +++ b/stdlib/public/runtime/MetadataLookup.cpp @@ -47,6 +47,7 @@ using namespace reflection; #include #include #include +#include #endif /// Produce a Demangler value suitable for resolving runtime type metadata @@ -1284,6 +1285,63 @@ swift_stdlib_getTypeByMangledName( return swift_checkMetadataState(MetadataState::Complete, metadata).Value; } +#if SWIFT_OBJC_INTEROP + +// Return the ObjC class for the given type name. +// This gets installed as a callback from libobjc. + +// FIXME: delete this #if and dlsym once we don't +// need to build with older libobjc headers +#if !OBJC_GETCLASSHOOK_DEFINED +using objc_hook_getClass = BOOL(*)(const char * _Nonnull name, + Class _Nullable * _Nonnull outClass); +#endif +static objc_hook_getClass OldGetClassHook; + +static BOOL +getObjCClassByMangledName(const char * _Nonnull typeName, + Class _Nullable * _Nonnull outClass) { + auto metadata = swift_stdlib_getTypeByMangledName(typeName, strlen(typeName), + /* no substitutions */ + nullptr, nullptr); + if (metadata) { + auto objcClass = + reinterpret_cast( + const_cast( + swift_getObjCClassFromMetadataConditional(metadata))); + + if (objcClass) { + *outClass = objcClass; + return YES; + } + } + + return OldGetClassHook(typeName, outClass); +} + +__attribute__((constructor)) +static void installGetClassHook() { + // FIXME: delete this #if and dlsym once we don't + // need to build with older libobjc headers +#if !OBJC_GETCLASSHOOK_DEFINED + using objc_hook_getClass = BOOL(*)(const char * _Nonnull name, + Class _Nullable * _Nonnull outClass); + auto objc_setHook_getClass = + (void(*)(objc_hook_getClass _Nonnull, + objc_hook_getClass _Nullable * _Nonnull)) + dlsym(RTLD_DEFAULT, "objc_setHook_getClass"); +#endif + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunguarded-availability" + if (objc_setHook_getClass) { + objc_setHook_getClass(getObjCClassByMangledName, &OldGetClassHook); + } +#pragma clang diagnostic pop +} + +#endif + unsigned SubstGenericParametersFromMetadata:: buildDescriptorPath(const ContextDescriptor *context) const { // Terminating condition: we don't have a context. diff --git a/test/Interpreter/SDK/objc_getClass.swift b/test/Interpreter/SDK/objc_getClass.swift new file mode 100644 index 0000000000000..2156471f419dc --- /dev/null +++ b/test/Interpreter/SDK/objc_getClass.swift @@ -0,0 +1,210 @@ +// RUN: %empty-directory(%t) + +// 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 +// RUN: %target-codesign %t/libresilient_struct.%target-dylib-extension + +// 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 +// RUN: %target-codesign %t/libresilient_class.%target-dylib-extension + +// RUN: %target-build-swift %s -L %t -I %t -lresilient_struct -lresilient_class -o %t/main -Xlinker -rpath -Xlinker %t +// RUN: %target-codesign %t/main + +// RUN: %target-run %t/main %t/libresilient_struct.%target-dylib-extension %t/libresilient_class.%target-dylib-extension + + +// REQUIRES: executable_test +// REQUIRES: objc_interop + +// Test Swift's hook for objc_getClass() + +import StdlibUnittest +import ObjectiveC +import Foundation +import resilient_struct +import resilient_class + +// Old OS versions do not have this hook. +let getClassHookMissing = { + nil == dlsym(UnsafeMutableRawPointer(bitPattern: -2), + "objc_setHook_getClass") +}() + +var testSuite = TestSuite("objc_getClass") + + +class SwiftSuperclass { } +class SwiftSubclass : SwiftSuperclass { } + +class ObjCSuperclass : NSObject { } +class ObjCSubclass : ObjCSuperclass { } + + +class MangledSwiftSuperclass { } +class MangledSwiftSubclass : MangledSwiftSuperclass { } + +class MangledObjCSuperclass : NSObject { } +class MangledObjCSubclass : MangledObjCSuperclass { } + + +class GenericSwiftClass { + let value: Value + init(value: Value) { self.value = value } +} +class ConstrainedSwiftSuperclass : GenericSwiftClass { + init() { super.init(value:"") } +} +class ConstrainedSwiftSubclass : ConstrainedSwiftSuperclass { } + + +class MangledGenericSwiftClass { + let value: Value + init(value: Value) { self.value = value } +} +class MangledConstrainedSwiftSuperclass : MangledGenericSwiftClass { + init() { super.init(value:"") } +} +class MangledConstrainedSwiftSubclass : MangledConstrainedSwiftSuperclass { } + + +class GenericObjCClass : NSObject { + let value: Value + init(value: Value) { self.value = value } +} +class ConstrainedObjCSuperclass : GenericObjCClass { + init() { super.init(value:"") } +} +class ConstrainedObjCSubclass : ConstrainedObjCSuperclass { } + + +class MangledGenericObjCClass : NSObject { + let value: Value + init(value: Value) { self.value = value } +} +class MangledConstrainedObjCSuperclass : MangledGenericObjCClass { + init() { super.init(value:"") } +} +class MangledConstrainedObjCSubclass : MangledConstrainedObjCSuperclass { } + + +class ResilientSuperclass : ResilientOutsideParent { + var supervalue = 10 +} + +class ResilientSubclass : ResilientSuperclass { + var subvalue = 20 +} + + +class ResilientFieldSuperclassSwift { + var supervalue = ResilientInt(i: 1) +} + +class ResilientFieldSubclassSwift : ResilientFieldSuperclassSwift { + var subvalue = ResilientInt(i: 2) +} + +class ResilientFieldSuperclassObjC : NSObject { + var supervalue = ResilientInt(i: 3) +} +class ResilientFieldSubclassObjC : ResilientFieldSuperclassObjC { + var subvalue = ResilientInt(i: 4) +} + + +func requireClass(named name: String, demangledName: String) { + for _ in 1...2 { + let cls: AnyClass? = NSClassFromString(name) + expectNotNil(cls, "class named \(name) unexpectedly not found") + expectEqual(NSStringFromClass(cls!), demangledName, + "class named \(name) has the wrong name"); + } +} + +func requireClass(named name: String) { + return requireClass(named: name, demangledName: name) +} + +testSuite.test("Basic") { + requireClass(named: "main.SwiftSubclass") + requireClass(named: "main.SwiftSuperclass") + requireClass(named: "main.ObjCSubclass") + requireClass(named: "main.ObjCSuperclass") +} + +testSuite.test("BasicMangled") { + requireClass(named: "_TtC4main20MangledSwiftSubclass", + demangledName: "main.MangledSwiftSubclass") + requireClass(named: "_TtC4main22MangledSwiftSuperclass", + demangledName: "main.MangledSwiftSuperclass") + requireClass(named: "_TtC4main19MangledObjCSubclass", + demangledName: "main.MangledObjCSubclass") + requireClass(named: "_TtC4main21MangledObjCSuperclass", + demangledName: "main.MangledObjCSuperclass") +} + +testSuite.test("Generic") + .skip(.custom({ getClassHookMissing }, + reason: "objc_getClass hook not present")) + .code { + requireClass(named: "main.ConstrainedSwiftSubclass") + requireClass(named: "main.ConstrainedSwiftSuperclass") + requireClass(named: "main.ConstrainedObjCSubclass") + requireClass(named: "main.ConstrainedObjCSuperclass") +} + +testSuite.test("GenericMangled") + .skip(.custom({ getClassHookMissing }, + reason: "objc_getClass hook not present")) + .code { + requireClass(named: "_TtC4main24ConstrainedSwiftSubclass", + demangledName: "main.ConstrainedSwiftSubclass") + requireClass(named: "_TtC4main26ConstrainedSwiftSuperclass", + demangledName: "main.ConstrainedSwiftSuperclass") + requireClass(named: "_TtC4main23ConstrainedObjCSubclass", + demangledName: "main.ConstrainedObjCSubclass") + requireClass(named: "_TtC4main25ConstrainedObjCSuperclass", + demangledName: "main.ConstrainedObjCSuperclass") +} + +testSuite.test("ResilientSubclass") + .skip(.custom({ getClassHookMissing }, + reason: "objc_getClass hook not present")) + .code { + requireClass(named: "main.ResilientSubclass") + requireClass(named: "main.ResilientSuperclass") + + expectEqual(ResilientSuperclass().supervalue, 10) + expectEqual(ResilientSubclass().supervalue, 10) + expectEqual(ResilientSubclass().subvalue, 20) +} + +testSuite.test("ResilientField") + .skip(.custom({ getClassHookMissing }, + reason: "objc_getClass hook not present")) + .code { + requireClass(named: "main.ResilientFieldSubclassSwift") + requireClass(named: "main.ResilientFieldSuperclassSwift") + requireClass(named: "main.ResilientFieldSubclassObjC") + requireClass(named: "main.ResilientFieldSuperclassObjC") + + expectEqual(ResilientFieldSuperclassSwift().supervalue.i, 1) + expectEqual(ResilientFieldSubclassSwift().supervalue.i, 1) + expectEqual(ResilientFieldSubclassSwift().subvalue.i, 2) + expectEqual(ResilientFieldSuperclassObjC().supervalue.i, 3) + expectEqual(ResilientFieldSubclassObjC().supervalue.i, 3) + expectEqual(ResilientFieldSubclassObjC().subvalue.i, 4) +} + +testSuite.test("NotPresent") { + // This class does not exist. + expectNil(NSClassFromString("main.ThisClassDoesNotExist")); + + // This name is improperly mangled + expectNil(NSClassFromString("_TtC5main")); + + // Swift.Int is not a class type. + expectNil(NSClassFromString("Si")) +} + +runAllTests() +