Skip to content

[5.1][Runtime] Fix swift_conformsToProtocol crashing on conformances to unavailable classes. #26649

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
6 changes: 6 additions & 0 deletions include/swift/ABI/Metadata.h
Original file line number Diff line number Diff line change
Expand Up @@ -2333,6 +2333,12 @@ struct TargetProtocolConformanceDescriptor final
return TypeRef.getTypeDescriptor(getTypeKind());
}

const TargetContextDescriptor<Runtime> **_getTypeDescriptorLocation() const {
if (getTypeKind() != TypeReferenceKind::IndirectTypeDescriptor)
return nullptr;
return TypeRef.IndirectTypeDescriptor.get();
}

/// Retrieve the context of a retroactive conformance.
const TargetContextDescriptor<Runtime> *getRetroactiveContext() const {
if (!Flags.isRetroactive()) return nullptr;
Expand Down
15 changes: 8 additions & 7 deletions stdlib/public/runtime/ProtocolConformance.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -145,14 +145,15 @@ ProtocolConformanceDescriptor::getCanonicalTypeMetadata() const {

case TypeReferenceKind::DirectTypeDescriptor:
case TypeReferenceKind::IndirectTypeDescriptor: {
auto anyType = getTypeDescriptor();
if (auto type = dyn_cast<TypeContextDescriptor>(anyType)) {
if (!type->isGeneric()) {
if (auto accessFn = type->getAccessFunction())
return accessFn(MetadataState::Abstract).Value;
if (auto anyType = getTypeDescriptor()) {
if (auto type = dyn_cast<TypeContextDescriptor>(anyType)) {
if (!type->isGeneric()) {
if (auto accessFn = type->getAccessFunction())
return accessFn(MetadataState::Abstract).Value;
}
} else if (auto protocol = dyn_cast<ProtocolDescriptor>(anyType)) {
return _getSimpleProtocolTypeMetadata(protocol);
}
} else if (auto protocol = dyn_cast<ProtocolDescriptor>(anyType)) {
return _getSimpleProtocolTypeMetadata(protocol);
}

return nullptr;
Expand Down
60 changes: 60 additions & 0 deletions stdlib/toolchain/Compatibility50/ProtocolConformance.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,22 @@
#include "Overrides.h"
#include "../../public/runtime/Private.h"
#include "swift/Basic/Lazy.h"
#include <mach-o/dyld.h>
#include <mach-o/getsect.h>
#include <objc/runtime.h>

using namespace swift;

#if __POINTER_WIDTH__ == 64
using mach_header_platform = mach_header_64;
#else
using mach_header_platform = mach_header;
#endif

/// The Mach-O section name for the section containing protocol conformances.
/// This lives within SEG_TEXT.
constexpr const char ProtocolConformancesSection[] = "__swift5_proto";

// Clone of private function getRootSuperclass. This returns the SwiftObject
// class in the ABI-stable dylib, regardless of what the local runtime build
// does, since we're always patching an ABI-stable dylib.
Expand All @@ -33,6 +45,50 @@ const ClassMetadata *swift::getRootSuperclass() {
return (const ClassMetadata *)theClass;
}

// A dummy target context descriptor to use in conformance records which point
// to a NULL descriptor. It doesn't have to be completely valid, just something
// that code reading conformance descriptors will ignore.
struct {
ContextDescriptorFlags flags;
int32_t offset;
} DummyTargetContextDescriptor = {
ContextDescriptorFlags().withKind(ContextDescriptorKind::Extension),
0
};

// Search for any protocol conformance descriptors with a NULL type descriptor
// and rewrite those to point to the dummy descriptor. This occurs when an
// extension is used to declare a conformance on a weakly linked type and that
// type is not present at runtime.
static void addImageCallback(const mach_header *mh, intptr_t vmaddr_slide) {
unsigned long size;
const uint8_t *section =
getsectiondata(reinterpret_cast<const mach_header_platform *>(mh),
SEG_TEXT, ProtocolConformancesSection,
&size);
if (!section)
return;

auto recordsBegin
= reinterpret_cast<const ProtocolConformanceRecord*>(section);
auto recordsEnd
= reinterpret_cast<const ProtocolConformanceRecord*>
(section + size);
for (auto record = recordsBegin; record != recordsEnd; record++) {
auto descriptor = record->get();
if (auto typePtr = descriptor->_getTypeDescriptorLocation()) {
if (*typePtr == nullptr)
*typePtr = reinterpret_cast<TargetContextDescriptor<InProcess> *>(
&DummyTargetContextDescriptor);
}
}
}

// Register the add image callback with dyld.
static void registerAddImageCallback(void *) {
_dyld_register_func_for_add_image(addImageCallback);
}

// Clone of private helper swift::_swiftoverride_class_getSuperclass
// for use in the override implementation.
static const Metadata *_swift50override_class_getSuperclass(
Expand All @@ -56,6 +112,10 @@ swift::swift50override_conformsToProtocol(const Metadata *type,
const ProtocolDescriptor *protocol,
ConformsToProtocol_t *original_conformsToProtocol)
{
// Register our add image callback if necessary.
static OnceToken_t token;
SWIFT_ONCE_F(token, registerAddImageCallback, nullptr);

// The implementation of swift_conformsToProtocol in Swift 5.0 would return
// a false negative answer when asking whether a subclass conforms using
// a conformance from a superclass. Work around this by walking up the
Expand Down
9 changes: 9 additions & 0 deletions test/Interpreter/Inputs/FakeUnavailableSwiftDylib.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
@available(OSX 1066.0, iOS 1066.0, watchOS 1066.0, tvOS 1066.0, *)
public protocol UnavailableSwiftProtocol {
func someMethod()
}

@available(OSX 1066.0, iOS 1066.0, watchOS 1066.0, tvOS 1066.0, *)
public class UnavailableSwiftClass {
func someMethod() {}
}
61 changes: 59 additions & 2 deletions test/Interpreter/availability_weak_linking.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,18 @@
// RUN: cp -R %S/Inputs/FakeUnavailableObjCFramework.framework %t
// RUN: %target-clang -dynamiclib %S/Inputs/FakeUnavailableObjCFramework.m -fmodules -F %t -framework Foundation -o %t/FakeUnavailableObjCFramework.framework/FakeUnavailableObjCFramework
// RUN: %target-codesign %t/FakeUnavailableObjCFramework.framework/FakeUnavailableObjCFramework
// RUN: %target-build-swift -F %t %s -o %t/UseWeaklinkedUnavailableObjCFramework
// RUN: %target-build-swift -O -F %t %s -o %t/UseWeaklinkedUnavailableObjCFramework.opt
// RUN: %target-build-swift-dylib(%t/%target-library-name(FakeUnavailableSwiftDylib)) -emit-module -emit-module-path %t/FakeUnavailableSwiftDylib.swiftmodule %S/Inputs/FakeUnavailableSwiftDylib.swift
// RUN: %target-codesign %t/%target-library-name(FakeUnavailableSwiftDylib)
// RUN: %target-build-swift %t/%target-library-name(FakeUnavailableSwiftDylib) -I %t -F %t %s -o %t/UseWeaklinkedUnavailableObjCFramework
// RUN: %target-build-swift -O %t/%target-library-name(FakeUnavailableSwiftDylib) -I %t -F %t %s -o %t/UseWeaklinkedUnavailableObjCFramework.opt

// These tests emulate deploying back to an older OS where newer APIs are not
// available by linking to an Objective-C framework where APIs have been
// annotated to only be available in the far future (version 1066.0 of all
// platforms) and then moving the framework aside so that it can't be found
// at run time.
// RUN: mv %t/FakeUnavailableObjCFramework.framework %t/FakeUnavailableObjCFramework-MovedAside.framework
// RUN: mv %t/%target-library-name(FakeUnavailableSwiftDylib) %t/%target-library-name(FakeUnavailableSwiftDylib)-MovedAside

// RUN: %target-codesign %t/UseWeaklinkedUnavailableObjCFramework
// RUN: %target-codesign %t/UseWeaklinkedUnavailableObjCFramework.opt
Expand All @@ -24,6 +27,7 @@ import StdlibUnittest


import FakeUnavailableObjCFramework
import FakeUnavailableSwiftDylib
import Foundation

// CHECK: Running
Expand Down Expand Up @@ -154,6 +158,59 @@ protocol SomeSwiftProtocol { }
@available(OSX 1066.0, iOS 1066.0, watchOS 1066.0, tvOS 1066.0, *)
extension UnavailableObjCClass : SomeSwiftProtocol {
}
@available(OSX 1066.0, iOS 1066.0, watchOS 1066.0, tvOS 1066.0, *)
extension UnavailableSwiftClass : SomeSwiftProtocol {
}

func checkSwiftProtocolConformance() {
// Make sure the runtime doesn't crash in the presence of a conformance
// record for a class that doesn't exsit at runtime.
let x: Any = 42
_blackHole(x as? SomeSwiftProtocol)
}

checkSwiftProtocolConformance()

class ClassConformingToUnavailableSwiftProtocol : UnavailableSwiftProtocol {
func someMethod() {
print("Executed ClassConformingToUnavailableSwiftProtocol.someMethod()")
}
}

func useClassConformingToUnavailableSwiftProtocol() {
let o = ClassConformingToUnavailableSwiftProtocol()
o.someMethod()

if #available(OSX 1066.0, iOS 1066.0, watchOS 1066.0, tvOS 1066.0, *) {
let oAsUP: UnavailableSwiftProtocol = o as UnavailableSwiftProtocol
oAsUP.someMethod()
}
}

// CHECK-NEXT: Executed ClassConformingToUnavailableSwiftProtocol.someMethod()
useClassConformingToUnavailableSwiftProtocol()

class ClassThatWillBeExtendedToConformToUnavailableSwiftProtocol {
}

extension ClassThatWillBeExtendedToConformToUnavailableSwiftProtocol : UnavailableSwiftProtocol {
func someMethod() {
print("Executed ClassThatWillBeExtendedToConformToUnavailableSwiftProtocol.someMethod()")
}
}

func useClassThatWillBeExtendedToConformToUnavailableSwiftProtocol() {
let o = ClassThatWillBeExtendedToConformToUnavailableSwiftProtocol()
o.someMethod()

if #available(OSX 1066.0, iOS 1066.0, watchOS 1066.0, tvOS 1066.0, *) {
let oAsUP: UnavailableSwiftProtocol = o as UnavailableSwiftProtocol
oAsUP.someMethod()
}
}

// CHECK-NEXT: Executed ClassThatWillBeExtendedToConformToUnavailableSwiftProtocol.someMethod()
useClassThatWillBeExtendedToConformToUnavailableSwiftProtocol()

// CHECK-NEXT: Done
print("Done")