Skip to content

Commit ea5658e

Browse files
committed
Serialization: Protect readNormalProtocolConformanceXRef
Failures in `readNormalProtocolConformanceXRef` are usually caused by a dependency change without the required rebuild of its dependents. Display a proper error instead of crashing when encountering such an issue during normal compilation. Recover silently when we can afford to, during indexing or in LLDB.
1 parent e814b3f commit ea5658e

File tree

4 files changed

+177
-18
lines changed

4 files changed

+177
-18
lines changed

include/swift/AST/DiagnosticsSema.def

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1037,6 +1037,12 @@ NOTE(modularization_issue_worked_around,none,
10371037
"-experimental-force-workaround-broken-modules",
10381038
())
10391039

1040+
ERROR(modularization_issue_conformance_xref_error,none,
1041+
"Conformance of %0 to %1 not found in referenced module %2",
1042+
(DeclName, DeclName, DeclName))
1043+
NOTE(modularization_issue_conformance_xref_note,none,
1044+
"Breaks conformances of '%0' to %1",
1045+
(StringRef, DeclName))
10401046
ERROR(reserved_member_name,none,
10411047
"type member must not be named %0, since it would conflict with the"
10421048
" 'foo.%1' expression", (const ValueDecl *, StringRef))

lib/Serialization/Deserialization.cpp

Lines changed: 59 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,8 @@ const char ModularizationError::ID = '\0';
179179
void ModularizationError::anchor() {}
180180
const char InvalidEnumValueError::ID = '\0';
181181
void InvalidEnumValueError::anchor() {}
182+
const char ConformanceXRefError::ID = '\0';
183+
void ConformanceXRefError::anchor() {}
182184

183185
/// Skips a single record in the bitstream.
184186
///
@@ -404,6 +406,16 @@ ModuleFile::diagnoseModularizationError(llvm::Error error,
404406
return outError;
405407
}
406408

409+
void
410+
ConformanceXRefError::diagnose(const ModuleFile *MF,
411+
DiagnosticBehavior limit) const {
412+
auto &diags = MF->getContext().Diags;
413+
diags.diagnose(MF->getSourceLoc(),
414+
diag::modularization_issue_conformance_xref_error,
415+
name, protoName, expectedModule->getName())
416+
.limitBehavior(limit);
417+
}
418+
407419
llvm::Error ModuleFile::diagnoseFatal(llvm::Error error) const {
408420

409421
auto &ctx = getContext();
@@ -860,7 +872,8 @@ ProtocolConformanceDeserializer::readSpecializedProtocolConformance(
860872
return subMapOrError.takeError();
861873
auto subMap = subMapOrError.get();
862874

863-
auto genericConformance = MF.getConformance(conformanceID);
875+
ProtocolConformanceRef genericConformance;
876+
UNWRAP(MF.getConformanceChecked(conformanceID), genericConformance);
864877

865878
PrettyStackTraceDecl traceTo("... to", genericConformance.getRequirement());
866879
++NumNormalProtocolConformancesLoaded;
@@ -892,8 +905,8 @@ ProtocolConformanceDeserializer::readInheritedProtocolConformance(
892905
PrettyStackTraceType trace(ctx, "reading inherited conformance for",
893906
conformingType);
894907

895-
ProtocolConformanceRef inheritedConformance =
896-
MF.getConformance(conformanceID);
908+
ProtocolConformanceRef inheritedConformance;
909+
UNWRAP(MF.getConformanceChecked(conformanceID), inheritedConformance);
897910
PrettyStackTraceDecl traceTo("... to",
898911
inheritedConformance.getRequirement());
899912

@@ -943,14 +956,16 @@ ProtocolConformanceDeserializer::readNormalProtocolConformanceXRef(
943956
ProtocolConformanceXrefLayout::readRecord(scratch, protoID, nominalID,
944957
moduleID);
945958

946-
auto maybeNominal = MF.getDeclChecked(nominalID);
947-
if (!maybeNominal)
948-
return maybeNominal.takeError();
949-
950-
auto nominal = cast<NominalTypeDecl>(maybeNominal.get());
959+
Decl *maybeNominal;
960+
UNWRAP(MF.getDeclChecked(nominalID), maybeNominal);
961+
auto nominal = cast<NominalTypeDecl>(maybeNominal);
951962
PrettyStackTraceDecl trace("cross-referencing conformance for", nominal);
952-
auto proto = cast<ProtocolDecl>(MF.getDecl(protoID));
963+
964+
Decl *maybeProto;
965+
UNWRAP(MF.getDeclChecked(protoID), maybeProto);
966+
auto proto = cast<ProtocolDecl>(maybeProto);
953967
PrettyStackTraceDecl traceTo("... to", proto);
968+
954969
auto module = MF.getModule(moduleID);
955970

956971
// FIXME: If the module hasn't been loaded, we probably don't want to fall
@@ -971,16 +986,26 @@ ProtocolConformanceDeserializer::readNormalProtocolConformanceXRef(
971986
// TODO: Sink Sendable derivation into the conformance lookup table
972987
if (proto->isSpecificProtocol(KnownProtocolKind::Sendable)) {
973988
auto conformanceRef = lookupConformance(nominal->getDeclaredInterfaceType(), proto);
974-
if (!conformanceRef.isConcrete())
975-
abort();
976-
return conformanceRef.getConcrete();
989+
if (conformanceRef.isConcrete())
990+
return conformanceRef.getConcrete();
977991
} else {
978992
SmallVector<ProtocolConformance *, 2> conformances;
979993
nominal->lookupConformance(proto, conformances);
980-
if (conformances.empty())
981-
abort();
982-
return conformances.front();
994+
if (!conformances.empty())
995+
return conformances.front();
983996
}
997+
998+
auto error = llvm::make_error<ConformanceXRefError>(
999+
nominal->getName(), proto->getName(), module);
1000+
1001+
if (!MF.enableExtendedDeserializationRecovery()) {
1002+
error = llvm::handleErrors(std::move(error),
1003+
[&](const ConformanceXRefError &error) -> llvm::Error {
1004+
error.diagnose(&MF);
1005+
return llvm::make_error<ConformanceXRefError>(std::move(error));
1006+
});
1007+
}
1008+
return error;
9841009
}
9851010

9861011
Expected<ProtocolConformance *>
@@ -7863,7 +7888,7 @@ Expected<Type> DESERIALIZE_TYPE(SIL_FUNCTION_TYPE)(
78637888
ProtocolConformanceRef witnessMethodConformance;
78647889
if (*representation == swift::SILFunctionTypeRepresentation::WitnessMethod) {
78657890
auto conformanceID = variableData[nextVariableDataIndex++];
7866-
witnessMethodConformance = MF.getConformance(conformanceID);
7891+
UNWRAP(MF.getConformanceChecked(conformanceID), witnessMethodConformance);
78677892
}
78687893

78697894
GenericSignature invocationSig =
@@ -8643,21 +8668,37 @@ void ModuleFile::finishNormalConformance(NormalProtocolConformance *conformance,
86438668
scratch, protoID, contextID, typeCount, valueCount, conformanceCount,
86448669
rawOptions, rawIDs);
86458670

8671+
const ProtocolDecl *proto = conformance->getProtocol();
8672+
86468673
// Read requirement signature conformances.
86478674
SmallVector<ProtocolConformanceRef, 4> reqConformances;
86488675
for (auto conformanceID : rawIDs.slice(0, conformanceCount)) {
86498676
auto maybeConformance = getConformanceChecked(conformanceID);
86508677
if (maybeConformance) {
86518678
reqConformances.push_back(maybeConformance.get());
86528679
} else if (getContext().LangOpts.EnableDeserializationRecovery) {
8653-
diagnoseAndConsumeError(maybeConformance.takeError());
8680+
llvm::Error error = maybeConformance.takeError();
8681+
if (error.isA<ConformanceXRefError>() &&
8682+
!enableExtendedDeserializationRecovery()) {
8683+
8684+
std::string typeStr = conformance->getType()->getString();
8685+
auto &diags = getContext().Diags;
8686+
diags.diagnose(getSourceLoc(),
8687+
diag::modularization_issue_conformance_xref_note,
8688+
typeStr, proto->getName());
8689+
8690+
consumeError(std::move(error));
8691+
conformance->setInvalid();
8692+
return;
8693+
}
8694+
8695+
diagnoseAndConsumeError(std::move(error));
86548696
reqConformances.push_back(ProtocolConformanceRef::forInvalid());
86558697
} else {
86568698
fatal(maybeConformance.takeError());
86578699
}
86588700
}
86598701

8660-
const ProtocolDecl *proto = conformance->getProtocol();
86618702
if (proto->isObjC() && getContext().LangOpts.EnableDeserializationRecovery) {
86628703
// Don't crash if inherited protocols are added or removed.
86638704
// This is limited to Objective-C protocols because we know their only

lib/Serialization/DeserializationErrors.h

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -677,6 +677,36 @@ class InvalidEnumValueError
677677
}
678678
};
679679

680+
// Cross-reference to a conformance was not found where it was expected.
681+
class ConformanceXRefError : public llvm::ErrorInfo<ConformanceXRefError,
682+
DeclDeserializationError> {
683+
friend ErrorInfo;
684+
static const char ID;
685+
void anchor() override;
686+
687+
DeclName protoName;
688+
const ModuleDecl *expectedModule;
689+
690+
public:
691+
ConformanceXRefError(Identifier name, Identifier protoName,
692+
const ModuleDecl *expectedModule):
693+
protoName(protoName), expectedModule(expectedModule) {
694+
this->name = name;
695+
}
696+
697+
void diagnose(const ModuleFile *MF,
698+
DiagnosticBehavior limit = DiagnosticBehavior::Fatal) const;
699+
700+
void log(raw_ostream &OS) const override {
701+
OS << "Conformance of '" << name << "' to '" << protoName
702+
<< "' not found in module '" << expectedModule->getName() << "'";
703+
}
704+
705+
std::error_code convertToErrorCode() const override {
706+
return llvm::inconvertibleErrorCode();
707+
}
708+
};
709+
680710
class PrettyStackTraceModuleFile : public llvm::PrettyStackTraceEntry {
681711
const char *Action;
682712
const ModuleFile &MF;
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
// RUN: %empty-directory(%t)
2+
// RUN: split-file %s %t
3+
4+
/// Reference build: Build libs and client for a normal build.
5+
// RUN: %target-swift-frontend -emit-module %t/ChangingLib.swift -I %t \
6+
// RUN: -emit-module-path %t/ChangingLib.swiftmodule
7+
// RUN: %target-swift-frontend -emit-module %t/MiddleLib.swift -I %t \
8+
// RUN: -emit-module-path %t/MiddleLib.swiftmodule
9+
// RUN: %target-swift-frontend -typecheck %t/Client.swift -I %t
10+
11+
/// Case 1: Remove a conformance from one lib, leave the other as stale and
12+
/// rebuild client. Error on the missing conformance.
13+
// RUN: %target-swift-frontend -emit-module %t/ChangingLib.swift -I %t \
14+
// RUN: -emit-module-path %t/ChangingLib.swiftmodule \
15+
// RUN: -D DROP_CONFORMANCE
16+
// RUN: not %target-swift-frontend -typecheck %t/Client.swift -I %t \
17+
// RUN: 2> %t/errors.log
18+
// RUN: %FileCheck %s --input-file=%t/errors.log \
19+
// RUN: --check-prefix CHECK-REMARK-CONFORMANCE
20+
21+
/// No errors when extended recovery is enabled.
22+
// RUN: %target-swift-frontend -typecheck %t/Client.swift -I %t \
23+
// RUN: -experimental-allow-module-with-compiler-errors -verify
24+
25+
26+
//--- ChangingLib.swift
27+
28+
public protocol SimpleProto {
29+
func successor() -> Self
30+
}
31+
32+
#if DROP_CONFORMANCE
33+
// CHECK-REMARK-CONFORMANCE: MiddleLib.swiftmodule:1:1: error: Conformance of 'Counter' to 'SimpleProto' not found in referenced module 'ChangingLib'
34+
// CHECK-REMARK-CONFORMANCE: note: Breaks conformances of 'OneToAThousand' to 'ProtoUser'
35+
#else
36+
extension Counter: SimpleProto {}
37+
#endif
38+
39+
public protocol ProtoUser {
40+
associatedtype Impl: SimpleProto
41+
42+
var start: Impl { get }
43+
44+
subscript(_: Impl) -> Element { get }
45+
}
46+
47+
public struct Counter<T> {
48+
public var value = 0
49+
50+
public init(value: Int) { self.value = value }
51+
52+
public func successor() -> Counter {
53+
return Counter(value: value + 1)
54+
}
55+
}
56+
57+
//--- MiddleLib.swift
58+
59+
import ChangingLib
60+
61+
// Instantiate Counter<Int>, relying on Counter's adoption of SimpleProto.
62+
public struct OneToAThousand : ProtoUser {
63+
public typealias Impl = Counter<Int>
64+
65+
public var start: Impl {
66+
return Impl(value: 1)
67+
}
68+
69+
public subscript(i: Impl) -> Int {
70+
return i.value
71+
}
72+
73+
public init() {}
74+
}
75+
76+
//--- Client.swift
77+
78+
import MiddleLib
79+
80+
var test = OneToAThousand()
81+
var s = test.start
82+
print(test[s])

0 commit comments

Comments
 (0)