Skip to content

[runtime] Register a hook for class name lookup from libobjc #20650

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions include/swift/Runtime/Metadata.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
16 changes: 16 additions & 0 deletions stdlib/public/runtime/Metadata.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<ClassMetadata>(theMetadata)) {
return theClass;
}

// Unwrap ObjC class wrappers.
if (auto wrapper = dyn_cast<ObjCClassWrapperMetadata>(theMetadata)) {
return wrapper->Class;
}

// Not an ObjC class after all.
return nil;
}

#endif

/***************************************************************************/
Expand Down
58 changes: 58 additions & 0 deletions stdlib/public/runtime/MetadataLookup.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ using namespace reflection;
#include <objc/runtime.h>
#include <objc/message.h>
#include <objc/objc.h>
#include <dlfcn.h>
#endif

/// Produce a Demangler value suitable for resolving runtime type metadata
Expand Down Expand Up @@ -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<Class>(
const_cast<ClassMetadata *>(
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.
Expand Down
210 changes: 210 additions & 0 deletions test/Interpreter/SDK/objc_getClass.swift
Original file line number Diff line number Diff line change
@@ -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<Value> {
let value: Value
init(value: Value) { self.value = value }
}
class ConstrainedSwiftSuperclass : GenericSwiftClass<String> {
init() { super.init(value:"") }
}
class ConstrainedSwiftSubclass : ConstrainedSwiftSuperclass { }


class MangledGenericSwiftClass<Value> {
let value: Value
init(value: Value) { self.value = value }
}
class MangledConstrainedSwiftSuperclass : MangledGenericSwiftClass<String> {
init() { super.init(value:"") }
}
class MangledConstrainedSwiftSubclass : MangledConstrainedSwiftSuperclass { }


class GenericObjCClass<Value> : NSObject {
let value: Value
init(value: Value) { self.value = value }
}
class ConstrainedObjCSuperclass : GenericObjCClass<String> {
init() { super.init(value:"") }
}
class ConstrainedObjCSubclass : ConstrainedObjCSuperclass { }


class MangledGenericObjCClass<Value> : NSObject {
let value: Value
init(value: Value) { self.value = value }
}
class MangledConstrainedObjCSuperclass : MangledGenericObjCClass<String> {
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()