From 0973293ea32244580ef56c129ee7fae699687d8b Mon Sep 17 00:00:00 2001 From: Slava Pestov Date: Mon, 17 Feb 2020 14:20:18 -0500 Subject: [PATCH] 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 . --- stdlib/public/runtime/ProtocolConformance.cpp | 39 +++++++++++++++---- .../Inputs/backward_deploy_conformance.swift | 18 +++++++++ .../test_backward_deploy_conformance.swift | 28 +++++++++++++ 3 files changed, 77 insertions(+), 8 deletions(-) create mode 100644 validation-test/Evolution/Inputs/backward_deploy_conformance.swift create mode 100644 validation-test/Evolution/test_backward_deploy_conformance.swift diff --git a/stdlib/public/runtime/ProtocolConformance.cpp b/stdlib/public/runtime/ProtocolConformance.cpp index 5cc50b09051c0..b04a286e203e0 100644 --- a/stdlib/public/runtime/ProtocolConformance.cpp +++ b/stdlib/public/runtime/ProtocolConformance.cpp @@ -481,7 +481,7 @@ searchInConformanceCache(const Metadata *type, namespace { /// Describes a protocol conformance "candidate" that can be checked - /// against the + /// against a type metadata. class ConformanceCandidate { const void *candidate; bool candidateIsMetadata; @@ -492,17 +492,17 @@ namespace { ConformanceCandidate(const ProtocolConformanceDescriptor &conformance) : ConformanceCandidate() { - if (auto metadata = conformance.getCanonicalTypeMetadata()) { - candidate = metadata; - candidateIsMetadata = true; - return; - } - if (auto description = conformance.getTypeDescriptor()) { candidate = description; candidateIsMetadata = false; return; } + + if (auto metadata = conformance.getCanonicalTypeMetadata()) { + candidate = metadata; + candidateIsMetadata = true; + return; + } } /// Retrieve the conforming type as metadata, or NULL if the candidate's @@ -513,6 +513,29 @@ namespace { : nullptr; } + const ContextDescriptor * + getContextDescriptor(const Metadata *conformingType) const { + const auto *description = conformingType->getTypeContextDescriptor(); + if (description) + return description; + + // Handle single-protocol existential types for self-conformance. + auto *existentialType = dyn_cast(conformingType); + if (existentialType == nullptr || + existentialType->getProtocols().size() != 1 || + existentialType->getSuperclassConstraint() != nullptr) + return nullptr; + + auto proto = existentialType->getProtocols()[0]; + +#if SWIFT_OBJC_INTEROP + if (proto.isObjC()) + return nullptr; +#endif + + return proto.getSwiftProtocol(); + } + /// Whether the conforming type exactly matches the conformance candidate. bool matches(const Metadata *conformingType) const { // Check whether the types match. @@ -521,7 +544,7 @@ namespace { // Check whether the nominal type descriptors match. if (!candidateIsMetadata) { - const auto *description = conformingType->getTypeContextDescriptor(); + const auto *description = getContextDescriptor(conformingType); auto candidateDescription = static_cast(candidate); if (description && equalContexts(description, candidateDescription)) diff --git a/validation-test/Evolution/Inputs/backward_deploy_conformance.swift b/validation-test/Evolution/Inputs/backward_deploy_conformance.swift new file mode 100644 index 0000000000000..86e6759ae7633 --- /dev/null +++ b/validation-test/Evolution/Inputs/backward_deploy_conformance.swift @@ -0,0 +1,18 @@ +public func getVersion() -> Int { +#if BEFORE + return 0 +#else + return 1 +#endif +} + +#if AFTER +@_weakLinked +public struct NewStruct { + var t: T + + public init(_ t: T) { + self.t = t + } +} +#endif diff --git a/validation-test/Evolution/test_backward_deploy_conformance.swift b/validation-test/Evolution/test_backward_deploy_conformance.swift new file mode 100644 index 0000000000000..555c8d4b91bb7 --- /dev/null +++ b/validation-test/Evolution/test_backward_deploy_conformance.swift @@ -0,0 +1,28 @@ +// RUN: %target-resilience-test --backward-deployment +// REQUIRES: executable_test + +import StdlibUnittest +import backward_deploy_conformance + + +var BackwardDeployConformanceTest = TestSuite("BackwardDeployConformance") + +public class UsesNewStruct: CustomStringConvertible { + public var field: NewStruct? = nil + public let description = "This is my description" +} + +public class OtherClass {} + +@_optimize(none) +func blackHole(_: T) {} + +BackwardDeployConformanceTest.test("ConformanceCache") { + if getVersion() == 1 { + blackHole(UsesNewStruct()) + } + + blackHole(OtherClass() as? CustomStringConvertible) +} + +runAllTests()