[Serialization] Recover from ObjC protocols changing inheritance. (#11109)

When there's an Objective-C protocol that adopts other protocols, the
other protocols become part of the requirement signature. If that can
change, Swift conformances to that protocol will get very confused
when it comes time to deserialize the conformances that satisfy the
requirement signature.

To recover from this, just deserialize /all/ trailing conformances,
rather than follow the requirement signature, and match them up after
the fact. (This only works for Objective-C protocols where we know all
conformance requirements represent inherited protocols, as opposed to
constraints on associated types.)

rdar://problem/33356098
This commit is contained in:
Jordan Rose
2017-07-24 16:22:10 -07:00
committed by GitHub
parent 780616a651
commit 4d26358708
6 changed files with 177 additions and 11 deletions

View File

@@ -54,7 +54,7 @@ const uint16_t VERSION_MAJOR = 0;
/// in source control, you should also update the comment to briefly
/// describe what change you made. The content of this comment isn't important;
/// it just ensures a conflict if two people change the module format.
const uint16_t VERSION_MINOR = 352; // Last change: 'shared' type attribute
const uint16_t VERSION_MINOR = 353; // Last change: count inherited conformances
using DeclID = PointerEmbeddedInt<unsigned, 31>;
using DeclIDField = BCFixed<31>;
@@ -1170,10 +1170,11 @@ namespace decls_block {
DeclContextIDField, // the decl that provided this conformance
BCVBR<5>, // value mapping count
BCVBR<5>, // type mapping count
BCVBR<5>, // requirement signature conformance count
BCArray<DeclIDField>
// The array contains archetype-value pairs, then type declarations.
// Inherited conformances follow, then the substitution records for the
// associated types.
// Requirement signature conformances follow, then the substitution records
// for the associated types.
>;
using SpecializedProtocolConformanceLayout = BCRecordLayout<

View File

@@ -611,7 +611,7 @@ NormalProtocolConformance *ModuleFile::readNormalConformance(
DeclID protoID;
DeclContextID contextID;
unsigned valueCount, typeCount;
unsigned valueCount, typeCount, conformanceCount;
ArrayRef<uint64_t> rawIDs;
SmallVector<uint64_t, 16> scratch;
@@ -622,7 +622,7 @@ NormalProtocolConformance *ModuleFile::readNormalConformance(
}
NormalProtocolConformanceLayout::readRecord(scratch, protoID,
contextID, valueCount,
typeCount,
typeCount, conformanceCount,
rawIDs);
ASTContext &ctx = getContext();
@@ -4585,7 +4585,7 @@ void ModuleFile::finishNormalConformance(NormalProtocolConformance *conformance,
DeclID protoID;
DeclContextID contextID;
unsigned valueCount, typeCount;
unsigned valueCount, typeCount, conformanceCount;
ArrayRef<uint64_t> rawIDs;
SmallVector<uint64_t, 16> scratch;
@@ -4595,17 +4595,60 @@ void ModuleFile::finishNormalConformance(NormalProtocolConformance *conformance,
"registered lazy loader incorrectly");
NormalProtocolConformanceLayout::readRecord(scratch, protoID,
contextID, valueCount,
typeCount,
typeCount, conformanceCount,
rawIDs);
// Read requirement signature conformances.
const ProtocolDecl *proto = conformance->getProtocol();
SmallVector<ProtocolConformanceRef, 4> reqConformances;
for (const auto &req : proto->getRequirementSignature()) {
if (req.getKind() == RequirementKind::Conformance) {
auto reqConformance = readConformance(DeclTypeCursor);
reqConformances.push_back(reqConformance);
if (proto->isObjC() && getContext().LangOpts.EnableDeserializationRecovery) {
// Don't crash if inherited protocols are added or removed.
// This is limited to Objective-C protocols because we know their only
// conformance requirements are on Self. This isn't actually a /safe/ change
// even in Objective-C, but we mostly just don't want to crash.
// FIXME: DenseMap requires that its value type be default-constructible,
// which ProtocolConformanceRef is not, hence the extra Optional.
llvm::SmallDenseMap<ProtocolDecl *, Optional<ProtocolConformanceRef>, 16>
conformancesForProtocols;
while (conformanceCount--) {
ProtocolConformanceRef nextConformance = readConformance(DeclTypeCursor);
ProtocolDecl *confProto = nextConformance.getRequirement();
conformancesForProtocols[confProto] = nextConformance;
}
for (const auto &req : proto->getRequirementSignature()) {
if (req.getKind() != RequirementKind::Conformance)
continue;
ProtocolDecl *proto =
req.getSecondType()->castTo<ProtocolType>()->getDecl();
auto iter = conformancesForProtocols.find(proto);
if (iter != conformancesForProtocols.end()) {
reqConformances.push_back(iter->getSecond().getValue());
} else {
// Put in an abstract conformance as a placeholder. This is a lie, but
// there's not much better we can do. We're relying on the fact that
// the rest of the compiler doesn't actually need to check the
// conformance to an Objective-C protocol for anything important.
// There are no associated types and we don't emit a Swift conformance
// record.
reqConformances.push_back(ProtocolConformanceRef(proto));
}
}
} else {
auto isConformanceReq = [](const Requirement &req) {
return req.getKind() == RequirementKind::Conformance;
};
if (conformanceCount != llvm::count_if(proto->getRequirementSignature(),
isConformanceReq)) {
fatal(llvm::make_error<llvm::StringError>(
"serialized conformances do not match requirement signature",
llvm::inconvertibleErrorCode()));
}
while (conformanceCount--)
reqConformances.push_back(readConformance(DeclTypeCursor));
}
conformance->setSignatureConformances(reqConformances);

View File

@@ -1361,6 +1361,9 @@ void Serializer::writeNormalConformance(
return false;
});
unsigned numSignatureConformances =
conformance->getSignatureConformances().size();
unsigned abbrCode
= DeclTypeAbbrCodes[NormalProtocolConformanceLayout::Code];
auto ownerID = addDeclContextRef(conformance->getDeclContext());
@@ -1368,6 +1371,7 @@ void Serializer::writeNormalConformance(
addDeclRef(protocol), ownerID,
numValueWitnesses,
numTypeWitnesses,
numSignatureConformances,
data);
// Write requirement signature conformances.

View File

@@ -0,0 +1,42 @@
@protocol Order2_ConsistentBaseProto
- (void)consistent;
@end
@protocol Order4_ConsistentBaseProto
- (void)consistent;
@end
@protocol Order1_FickleBaseProto
- (void)fickle;
@optional
- (void)extraFickle;
@end
@protocol Order3_FickleBaseProto
- (void)fickle;
@optional
- (void)extraFickle;
@end
@protocol Order5_FickleBaseProto
- (void)fickle;
@optional
- (void)extraFickle;
@end
// The actual order here is determined by the protocol names.
#if EXTRA_PROTOCOL_FIRST
@protocol SubProto <Order1_FickleBaseProto, Order2_ConsistentBaseProto, Order4_ConsistentBaseProto>
@end
#elif EXTRA_PROTOCOL_MIDDLE
@protocol SubProto <Order2_ConsistentBaseProto, Order3_FickleBaseProto, Order4_ConsistentBaseProto>
@end
#elif EXTRA_PROTOCOL_LAST
@protocol SubProto <Order2_ConsistentBaseProto, Order4_ConsistentBaseProto, Order5_FickleBaseProto>
@end
#elif NO_EXTRA_PROTOCOLS
@protocol SubProto <Order2_ConsistentBaseProto, Order4_ConsistentBaseProto>
@end
#else
# error "Missing -D flag"
#endif

View File

@@ -1,4 +1,5 @@
module Overrides { header "Overrides.h" }
module ProtocolInheritance { header "ProtocolInheritance.h" }
module Typedefs { header "Typedefs.h" }
module TypeRemovalObjC { header "TypeRemovalObjC.h" }
module Types { header "Types.h" }

View File

@@ -0,0 +1,75 @@
// RUN: %empty-directory(%t)
// RUN: %target-swift-frontend -emit-module -o %t -module-name Lib -I %S/Inputs/custom-modules -Xcc -DNO_EXTRA_PROTOCOLS %s
// RUN: %target-swift-frontend -typecheck -DTEST -Xcc -DNO_EXTRA_PROTOCOLS -I %t -I %S/Inputs/custom-modules %s
// RUN: %target-swift-frontend -emit-ir -DTEST -Xcc -DEXTRA_PROTOCOL_FIRST -I %t -I %S/Inputs/custom-modules %s -o /dev/null
// RUN: %target-swift-frontend -emit-module -o %t -module-name Lib -I %S/Inputs/custom-modules -Xcc -DEXTRA_PROTOCOL_FIRST %s
// RUN: %target-swift-frontend -typecheck -DTEST -Xcc -DEXTRA_PROTOCOL_FIRST -I %t -I %S/Inputs/custom-modules %s
// RUN: %target-swift-frontend -emit-ir -DTEST -Xcc -DNO_EXTRA_PROTOCOLS -I %t -I %S/Inputs/custom-modules %s -o /dev/null
// RUN: %target-swift-frontend -emit-module -o %t -module-name Lib -I %S/Inputs/custom-modules -Xcc -DNO_EXTRA_PROTOCOLS %s
// RUN: %target-swift-frontend -typecheck -DTEST -Xcc -DNO_EXTRA_PROTOCOLS -I %t -I %S/Inputs/custom-modules %s
// RUN: %target-swift-frontend -emit-ir -DTEST -Xcc -DEXTRA_PROTOCOL_MIDDLE -I %t -I %S/Inputs/custom-modules %s -o /dev/null
// RUN: %target-swift-frontend -emit-module -o %t -module-name Lib -I %S/Inputs/custom-modules -Xcc -DEXTRA_PROTOCOL_MIDDLE %s
// RUN: %target-swift-frontend -typecheck -DTEST -Xcc -DEXTRA_PROTOCOL_MIDDLE -I %t -I %S/Inputs/custom-modules %s
// RUN: %target-swift-frontend -emit-ir -DTEST -Xcc -DNO_EXTRA_PROTOCOLS -I %t -I %S/Inputs/custom-modules %s -o /dev/null
// RUN: %target-swift-frontend -emit-module -o %t -module-name Lib -I %S/Inputs/custom-modules -Xcc -DNO_EXTRA_PROTOCOLS %s
// RUN: %target-swift-frontend -typecheck -DTEST -Xcc -DNO_EXTRA_PROTOCOLS -I %t -I %S/Inputs/custom-modules %s
// RUN: %target-swift-frontend -emit-ir -DTEST -Xcc -DEXTRA_PROTOCOL_LAST -I %t -I %S/Inputs/custom-modules %s -o /dev/null
// RUN: %target-swift-frontend -emit-module -o %t -module-name Lib -I %S/Inputs/custom-modules -Xcc -DEXTRA_PROTOCOL_LAST %s
// RUN: %target-swift-frontend -typecheck -DTEST -Xcc -DEXTRA_PROTOCOL_LAST -I %t -I %S/Inputs/custom-modules %s
// RUN: %target-swift-frontend -emit-ir -DTEST -Xcc -DNO_EXTRA_PROTOCOLS -I %t -I %S/Inputs/custom-modules %s -o /dev/null
// REQUIRES: objc_interop
#if TEST
import Lib
import ProtocolInheritance
func useSubProto<T: SubProto>(_: T) {}
func useConsistentProto<T: Order2_ConsistentBaseProto>(_: T) {}
func useFickleProto<T: Order1_FickleBaseProto>(_: T) {}
func test(obj: Impl) {
useConsistentProto(obj)
useFickleProto(obj)
useSubProto(obj)
}
protocol ForceDeserializationProto: SubProto {}
extension Impl: ForceDeserializationProto {}
func test(obj: PartialImpl) {
useConsistentProto(obj)
useSubProto(obj)
}
extension PartialImpl: ForceDeserializationProto {}
#else // TEST
import ProtocolInheritance
open class Impl: SubProto {
public func consistent() {}
}
extension Impl: Order1_FickleBaseProto, Order3_FickleBaseProto, Order5_FickleBaseProto {
public func fickle() {}
}
open class PartialImpl: SubProto {
public func consistent() {}
public func fickle() {}
}
#endif // TEST