Skip to content

Commit 1329017

Browse files
authored
Merge pull request #26649 from mikeash/fix-protocol-conformances-on-unavailable-classes-5.1
[5.1][Runtime] Fix swift_conformsToProtocol crashing on conformances to unavailable classes.
2 parents 20b8d4c + 7ca665b commit 1329017

File tree

5 files changed

+142
-9
lines changed

5 files changed

+142
-9
lines changed

include/swift/ABI/Metadata.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2333,6 +2333,12 @@ struct TargetProtocolConformanceDescriptor final
23332333
return TypeRef.getTypeDescriptor(getTypeKind());
23342334
}
23352335

2336+
const TargetContextDescriptor<Runtime> **_getTypeDescriptorLocation() const {
2337+
if (getTypeKind() != TypeReferenceKind::IndirectTypeDescriptor)
2338+
return nullptr;
2339+
return TypeRef.IndirectTypeDescriptor.get();
2340+
}
2341+
23362342
/// Retrieve the context of a retroactive conformance.
23372343
const TargetContextDescriptor<Runtime> *getRetroactiveContext() const {
23382344
if (!Flags.isRetroactive()) return nullptr;

stdlib/public/runtime/ProtocolConformance.cpp

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -145,14 +145,15 @@ ProtocolConformanceDescriptor::getCanonicalTypeMetadata() const {
145145

146146
case TypeReferenceKind::DirectTypeDescriptor:
147147
case TypeReferenceKind::IndirectTypeDescriptor: {
148-
auto anyType = getTypeDescriptor();
149-
if (auto type = dyn_cast<TypeContextDescriptor>(anyType)) {
150-
if (!type->isGeneric()) {
151-
if (auto accessFn = type->getAccessFunction())
152-
return accessFn(MetadataState::Abstract).Value;
148+
if (auto anyType = getTypeDescriptor()) {
149+
if (auto type = dyn_cast<TypeContextDescriptor>(anyType)) {
150+
if (!type->isGeneric()) {
151+
if (auto accessFn = type->getAccessFunction())
152+
return accessFn(MetadataState::Abstract).Value;
153+
}
154+
} else if (auto protocol = dyn_cast<ProtocolDescriptor>(anyType)) {
155+
return _getSimpleProtocolTypeMetadata(protocol);
153156
}
154-
} else if (auto protocol = dyn_cast<ProtocolDescriptor>(anyType)) {
155-
return _getSimpleProtocolTypeMetadata(protocol);
156157
}
157158

158159
return nullptr;

stdlib/toolchain/Compatibility50/ProtocolConformance.cpp

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,22 @@
2020
#include "Overrides.h"
2121
#include "../../public/runtime/Private.h"
2222
#include "swift/Basic/Lazy.h"
23+
#include <mach-o/dyld.h>
24+
#include <mach-o/getsect.h>
2325
#include <objc/runtime.h>
2426

2527
using namespace swift;
2628

29+
#if __POINTER_WIDTH__ == 64
30+
using mach_header_platform = mach_header_64;
31+
#else
32+
using mach_header_platform = mach_header;
33+
#endif
34+
35+
/// The Mach-O section name for the section containing protocol conformances.
36+
/// This lives within SEG_TEXT.
37+
constexpr const char ProtocolConformancesSection[] = "__swift5_proto";
38+
2739
// Clone of private function getRootSuperclass. This returns the SwiftObject
2840
// class in the ABI-stable dylib, regardless of what the local runtime build
2941
// does, since we're always patching an ABI-stable dylib.
@@ -33,6 +45,50 @@ const ClassMetadata *swift::getRootSuperclass() {
3345
return (const ClassMetadata *)theClass;
3446
}
3547

48+
// A dummy target context descriptor to use in conformance records which point
49+
// to a NULL descriptor. It doesn't have to be completely valid, just something
50+
// that code reading conformance descriptors will ignore.
51+
struct {
52+
ContextDescriptorFlags flags;
53+
int32_t offset;
54+
} DummyTargetContextDescriptor = {
55+
ContextDescriptorFlags().withKind(ContextDescriptorKind::Extension),
56+
0
57+
};
58+
59+
// Search for any protocol conformance descriptors with a NULL type descriptor
60+
// and rewrite those to point to the dummy descriptor. This occurs when an
61+
// extension is used to declare a conformance on a weakly linked type and that
62+
// type is not present at runtime.
63+
static void addImageCallback(const mach_header *mh, intptr_t vmaddr_slide) {
64+
unsigned long size;
65+
const uint8_t *section =
66+
getsectiondata(reinterpret_cast<const mach_header_platform *>(mh),
67+
SEG_TEXT, ProtocolConformancesSection,
68+
&size);
69+
if (!section)
70+
return;
71+
72+
auto recordsBegin
73+
= reinterpret_cast<const ProtocolConformanceRecord*>(section);
74+
auto recordsEnd
75+
= reinterpret_cast<const ProtocolConformanceRecord*>
76+
(section + size);
77+
for (auto record = recordsBegin; record != recordsEnd; record++) {
78+
auto descriptor = record->get();
79+
if (auto typePtr = descriptor->_getTypeDescriptorLocation()) {
80+
if (*typePtr == nullptr)
81+
*typePtr = reinterpret_cast<TargetContextDescriptor<InProcess> *>(
82+
&DummyTargetContextDescriptor);
83+
}
84+
}
85+
}
86+
87+
// Register the add image callback with dyld.
88+
static void registerAddImageCallback(void *) {
89+
_dyld_register_func_for_add_image(addImageCallback);
90+
}
91+
3692
// Clone of private helper swift::_swiftoverride_class_getSuperclass
3793
// for use in the override implementation.
3894
static const Metadata *_swift50override_class_getSuperclass(
@@ -56,6 +112,10 @@ swift::swift50override_conformsToProtocol(const Metadata *type,
56112
const ProtocolDescriptor *protocol,
57113
ConformsToProtocol_t *original_conformsToProtocol)
58114
{
115+
// Register our add image callback if necessary.
116+
static OnceToken_t token;
117+
SWIFT_ONCE_F(token, registerAddImageCallback, nullptr);
118+
59119
// The implementation of swift_conformsToProtocol in Swift 5.0 would return
60120
// a false negative answer when asking whether a subclass conforms using
61121
// a conformance from a superclass. Work around this by walking up the
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
@available(OSX 1066.0, iOS 1066.0, watchOS 1066.0, tvOS 1066.0, *)
2+
public protocol UnavailableSwiftProtocol {
3+
func someMethod()
4+
}
5+
6+
@available(OSX 1066.0, iOS 1066.0, watchOS 1066.0, tvOS 1066.0, *)
7+
public class UnavailableSwiftClass {
8+
func someMethod() {}
9+
}

test/Interpreter/availability_weak_linking.swift

Lines changed: 59 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,18 @@
22
// RUN: cp -R %S/Inputs/FakeUnavailableObjCFramework.framework %t
33
// RUN: %target-clang -dynamiclib %S/Inputs/FakeUnavailableObjCFramework.m -fmodules -F %t -framework Foundation -o %t/FakeUnavailableObjCFramework.framework/FakeUnavailableObjCFramework
44
// RUN: %target-codesign %t/FakeUnavailableObjCFramework.framework/FakeUnavailableObjCFramework
5-
// RUN: %target-build-swift -F %t %s -o %t/UseWeaklinkedUnavailableObjCFramework
6-
// RUN: %target-build-swift -O -F %t %s -o %t/UseWeaklinkedUnavailableObjCFramework.opt
5+
// RUN: %target-build-swift-dylib(%t/%target-library-name(FakeUnavailableSwiftDylib)) -emit-module -emit-module-path %t/FakeUnavailableSwiftDylib.swiftmodule %S/Inputs/FakeUnavailableSwiftDylib.swift
6+
// RUN: %target-codesign %t/%target-library-name(FakeUnavailableSwiftDylib)
7+
// RUN: %target-build-swift %t/%target-library-name(FakeUnavailableSwiftDylib) -I %t -F %t %s -o %t/UseWeaklinkedUnavailableObjCFramework
8+
// RUN: %target-build-swift -O %t/%target-library-name(FakeUnavailableSwiftDylib) -I %t -F %t %s -o %t/UseWeaklinkedUnavailableObjCFramework.opt
79

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

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

2528

2629
import FakeUnavailableObjCFramework
30+
import FakeUnavailableSwiftDylib
2731
import Foundation
2832

2933
// CHECK: Running
@@ -154,6 +158,59 @@ protocol SomeSwiftProtocol { }
154158
@available(OSX 1066.0, iOS 1066.0, watchOS 1066.0, tvOS 1066.0, *)
155159
extension UnavailableObjCClass : SomeSwiftProtocol {
156160
}
161+
@available(OSX 1066.0, iOS 1066.0, watchOS 1066.0, tvOS 1066.0, *)
162+
extension UnavailableSwiftClass : SomeSwiftProtocol {
163+
}
164+
165+
func checkSwiftProtocolConformance() {
166+
// Make sure the runtime doesn't crash in the presence of a conformance
167+
// record for a class that doesn't exsit at runtime.
168+
let x: Any = 42
169+
_blackHole(x as? SomeSwiftProtocol)
170+
}
171+
172+
checkSwiftProtocolConformance()
173+
174+
class ClassConformingToUnavailableSwiftProtocol : UnavailableSwiftProtocol {
175+
func someMethod() {
176+
print("Executed ClassConformingToUnavailableSwiftProtocol.someMethod()")
177+
}
178+
}
179+
180+
func useClassConformingToUnavailableSwiftProtocol() {
181+
let o = ClassConformingToUnavailableSwiftProtocol()
182+
o.someMethod()
183+
184+
if #available(OSX 1066.0, iOS 1066.0, watchOS 1066.0, tvOS 1066.0, *) {
185+
let oAsUP: UnavailableSwiftProtocol = o as UnavailableSwiftProtocol
186+
oAsUP.someMethod()
187+
}
188+
}
189+
190+
// CHECK-NEXT: Executed ClassConformingToUnavailableSwiftProtocol.someMethod()
191+
useClassConformingToUnavailableSwiftProtocol()
192+
193+
class ClassThatWillBeExtendedToConformToUnavailableSwiftProtocol {
194+
}
195+
196+
extension ClassThatWillBeExtendedToConformToUnavailableSwiftProtocol : UnavailableSwiftProtocol {
197+
func someMethod() {
198+
print("Executed ClassThatWillBeExtendedToConformToUnavailableSwiftProtocol.someMethod()")
199+
}
200+
}
201+
202+
func useClassThatWillBeExtendedToConformToUnavailableSwiftProtocol() {
203+
let o = ClassThatWillBeExtendedToConformToUnavailableSwiftProtocol()
204+
o.someMethod()
205+
206+
if #available(OSX 1066.0, iOS 1066.0, watchOS 1066.0, tvOS 1066.0, *) {
207+
let oAsUP: UnavailableSwiftProtocol = o as UnavailableSwiftProtocol
208+
oAsUP.someMethod()
209+
}
210+
}
211+
212+
// CHECK-NEXT: Executed ClassThatWillBeExtendedToConformToUnavailableSwiftProtocol.someMethod()
213+
useClassThatWillBeExtendedToConformToUnavailableSwiftProtocol()
157214

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

0 commit comments

Comments
 (0)