Skip to content

Commit 1ca2b61

Browse files
committed
Runtime: Less eager instantiation of type metadata in the conformance cache
The ConformanceCandidate constructor would eagerly instantiate metadata for all non-generic types in the conformance cache. This was not the intention of the code though, because it can compare nominal type descriptors -- which are emitted statically -- for those types that have them, namely everything except for foreign and Objective-C classes. Change the order of two calls so that the lazy path has a chance to run. This fixes a crash when type metadata uses weakly-linked symbols which are not available, which can come up in backward deployment scenarios. Fixes <rdar://problem/59460603>.
1 parent 38815ec commit 1ca2b61

File tree

3 files changed

+74
-8
lines changed

3 files changed

+74
-8
lines changed

stdlib/public/runtime/ProtocolConformance.cpp

Lines changed: 28 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -481,7 +481,7 @@ searchInConformanceCache(const Metadata *type,
481481

482482
namespace {
483483
/// Describes a protocol conformance "candidate" that can be checked
484-
/// against the
484+
/// against a type metadata.
485485
class ConformanceCandidate {
486486
const void *candidate;
487487
bool candidateIsMetadata;
@@ -492,17 +492,17 @@ namespace {
492492
ConformanceCandidate(const ProtocolConformanceDescriptor &conformance)
493493
: ConformanceCandidate()
494494
{
495-
if (auto metadata = conformance.getCanonicalTypeMetadata()) {
496-
candidate = metadata;
497-
candidateIsMetadata = true;
498-
return;
499-
}
500-
501495
if (auto description = conformance.getTypeDescriptor()) {
502496
candidate = description;
503497
candidateIsMetadata = false;
504498
return;
505499
}
500+
501+
if (auto metadata = conformance.getCanonicalTypeMetadata()) {
502+
candidate = metadata;
503+
candidateIsMetadata = true;
504+
return;
505+
}
506506
}
507507

508508
/// Retrieve the conforming type as metadata, or NULL if the candidate's
@@ -513,6 +513,26 @@ namespace {
513513
: nullptr;
514514
}
515515

516+
const ContextDescriptor *
517+
getContextDescriptor(const Metadata *conformingType) const {
518+
const auto *description = conformingType->getTypeContextDescriptor();
519+
if (description)
520+
return description;
521+
522+
// Handle single-protocol existential types for self-conformance.
523+
auto *existentialType = dyn_cast<ExistentialTypeMetadata>(conformingType);
524+
if (existentialType == nullptr ||
525+
existentialType->getProtocols().size() != 1 ||
526+
existentialType->getSuperclassConstraint() != nullptr)
527+
return nullptr;
528+
529+
auto proto = existentialType->getProtocols()[0];
530+
if (proto.isObjC())
531+
return nullptr;
532+
533+
return proto.getSwiftProtocol();
534+
}
535+
516536
/// Whether the conforming type exactly matches the conformance candidate.
517537
bool matches(const Metadata *conformingType) const {
518538
// Check whether the types match.
@@ -521,7 +541,7 @@ namespace {
521541

522542
// Check whether the nominal type descriptors match.
523543
if (!candidateIsMetadata) {
524-
const auto *description = conformingType->getTypeContextDescriptor();
544+
const auto *description = getContextDescriptor(conformingType);
525545
auto candidateDescription =
526546
static_cast<const ContextDescriptor *>(candidate);
527547
if (description && equalContexts(description, candidateDescription))
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
public func getVersion() -> Int {
2+
#if BEFORE
3+
return 0
4+
#else
5+
return 1
6+
#endif
7+
}
8+
9+
#if AFTER
10+
@_weakLinked
11+
public struct NewStruct<T> {
12+
var t: T
13+
14+
public init(_ t: T) {
15+
self.t = t
16+
}
17+
}
18+
#endif
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
// RUN: %target-resilience-test --backward-deployment
2+
// REQUIRES: executable_test
3+
4+
import StdlibUnittest
5+
import backward_deploy_conformance
6+
7+
8+
var BackwardDeployConformanceTest = TestSuite("BackwardDeployConformance")
9+
10+
public class UsesNewStruct: CustomStringConvertible {
11+
public var field: NewStruct<Bool>? = nil
12+
public let description = "This is my description"
13+
}
14+
15+
public class OtherClass {}
16+
17+
@_optimize(none)
18+
func blackHole<T>(_: T) {}
19+
20+
BackwardDeployConformanceTest.test("ConformanceCache") {
21+
if getVersion() == 1 {
22+
blackHole(UsesNewStruct())
23+
}
24+
25+
blackHole(OtherClass() as? CustomStringConvertible)
26+
}
27+
28+
runAllTests()

0 commit comments

Comments
 (0)