Skip to content

Commit c0a4056

Browse files
authored
Merge pull request #79851 from xymus/deser-recover-conformance-xref
Serialization: Diagnose broken conformances from stale swiftmodule files
2 parents b852f94 + fb3944f commit c0a4056

File tree

8 files changed

+269
-32
lines changed

8 files changed

+269
-32
lines changed

include/swift/AST/ASTContext.h

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -381,11 +381,10 @@ class ASTContext final {
381381
/// files?
382382
bool IgnoreAdjacentModules = false;
383383

384-
/// Override to accept reading errors from swiftmodules and being generally
385-
/// more tolerant to inconsistencies. This is enabled for
386-
/// index-while-building as it runs last and it can afford to read an AST
387-
/// with inconsistencies.
388-
bool ForceAllowModuleWithCompilerErrors = false;
384+
/// Accept recovering from more issues at deserialization, even if it can
385+
/// lead to an inconsistent state. This is enabled for index-while-building
386+
/// as it runs last and it can afford to read an AST with inconsistencies.
387+
bool ForceExtendedDeserializationRecovery = false;
389388

390389
// Define the set of known identifiers.
391390
#define IDENTIFIER_WITH_NAME(Name, IdStr) Identifier Id_##Name;

include/swift/AST/DiagnosticsSema.def

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1037,6 +1037,18 @@ 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))
1046+
ERROR(modularization_issue_conformance_error,none,
1047+
"Conformances of '%0' "
1048+
"do not match requirement signature of %1; "
1049+
"%2 conformances for %3 requirements",
1050+
(StringRef, DeclName, unsigned int, unsigned int))
1051+
10401052
ERROR(reserved_member_name,none,
10411053
"type member must not be named %0, since it would conflict with the"
10421054
" 'foo.%1' expression", (const ValueDecl *, StringRef))

lib/Index/Index.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2168,7 +2168,7 @@ void index::indexModule(ModuleDecl *module, IndexDataConsumer &consumer) {
21682168
return;
21692169
}
21702170

2171-
llvm::SaveAndRestore<bool> S(ctx.ForceAllowModuleWithCompilerErrors, true);
2171+
llvm::SaveAndRestore<bool> S(ctx.ForceExtendedDeserializationRecovery, true);
21722172

21732173
IndexSwiftASTWalker walker(consumer, ctx);
21742174
walker.visitModule(*module);

lib/Serialization/Deserialization.cpp

Lines changed: 92 additions & 24 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 *>
@@ -7867,7 +7892,7 @@ Expected<Type> DESERIALIZE_TYPE(SIL_FUNCTION_TYPE)(
78677892
ProtocolConformanceRef witnessMethodConformance;
78687893
if (*representation == swift::SILFunctionTypeRepresentation::WitnessMethod) {
78697894
auto conformanceID = variableData[nextVariableDataIndex++];
7870-
witnessMethodConformance = MF.getConformance(conformanceID);
7895+
UNWRAP(MF.getConformanceChecked(conformanceID), witnessMethodConformance);
78717896
}
78727897

78737898
GenericSignature invocationSig =
@@ -8647,21 +8672,37 @@ void ModuleFile::finishNormalConformance(NormalProtocolConformance *conformance,
86478672
scratch, protoID, contextID, typeCount, valueCount, conformanceCount,
86488673
rawOptions, rawIDs);
86498674

8675+
const ProtocolDecl *proto = conformance->getProtocol();
8676+
86508677
// Read requirement signature conformances.
86518678
SmallVector<ProtocolConformanceRef, 4> reqConformances;
86528679
for (auto conformanceID : rawIDs.slice(0, conformanceCount)) {
86538680
auto maybeConformance = getConformanceChecked(conformanceID);
86548681
if (maybeConformance) {
86558682
reqConformances.push_back(maybeConformance.get());
86568683
} else if (getContext().LangOpts.EnableDeserializationRecovery) {
8657-
diagnoseAndConsumeError(maybeConformance.takeError());
8684+
llvm::Error error = maybeConformance.takeError();
8685+
if (error.isA<ConformanceXRefError>() &&
8686+
!enableExtendedDeserializationRecovery()) {
8687+
8688+
std::string typeStr = conformance->getType()->getString();
8689+
auto &diags = getContext().Diags;
8690+
diags.diagnose(getSourceLoc(),
8691+
diag::modularization_issue_conformance_xref_note,
8692+
typeStr, proto->getName());
8693+
8694+
consumeError(std::move(error));
8695+
conformance->setInvalid();
8696+
return;
8697+
}
8698+
8699+
diagnoseAndConsumeError(std::move(error));
86588700
reqConformances.push_back(ProtocolConformanceRef::forInvalid());
86598701
} else {
86608702
fatal(maybeConformance.takeError());
86618703
}
86628704
}
86638705

8664-
const ProtocolDecl *proto = conformance->getProtocol();
86658706
if (proto->isObjC() && getContext().LangOpts.EnableDeserializationRecovery) {
86668707
// Don't crash if inherited protocols are added or removed.
86678708
// This is limited to Objective-C protocols because we know their only
@@ -8702,12 +8743,39 @@ void ModuleFile::finishNormalConformance(NormalProtocolConformance *conformance,
87028743
return req.getKind() == RequirementKind::Conformance;
87038744
};
87048745
auto requirements = proto->getRequirementSignature().getRequirements();
8705-
if (!allowCompilerErrors() &&
8706-
conformanceCount != llvm::count_if(requirements,
8707-
isConformanceReq)) {
8708-
fatal(llvm::make_error<llvm::StringError>(
8709-
"serialized conformances do not match requirement signature",
8710-
llvm::inconvertibleErrorCode()));
8746+
unsigned int conformanceRequirementCount =
8747+
llvm::count_if(requirements, isConformanceReq);
8748+
if (conformanceCount != conformanceRequirementCount) {
8749+
// Mismatch between the number of loaded conformances and the expected
8750+
// requirements. One or the other likely comes from a stale module.
8751+
8752+
if (!enableExtendedDeserializationRecovery()) {
8753+
// Error and print full context for visual inspection.
8754+
ASTContext &ctx = getContext();
8755+
std::string typeStr = conformance->getType()->getString();
8756+
ctx.Diags.diagnose(getSourceLoc(),
8757+
diag::modularization_issue_conformance_error,
8758+
typeStr, proto->getName(), conformanceCount,
8759+
conformanceRequirementCount);
8760+
ctx.Diags.flushConsumers();
8761+
8762+
// Print context to stderr.
8763+
PrintOptions Opts;
8764+
llvm::errs() << "Requirements:\n";
8765+
for (auto req: requirements) {
8766+
req.print(llvm::errs(), Opts);
8767+
llvm::errs() << "\n";
8768+
}
8769+
8770+
llvm::errs() << "Conformances:\n";
8771+
for (auto req: reqConformances) {
8772+
req.print(llvm::errs());
8773+
llvm::errs() << "\n";
8774+
}
8775+
}
8776+
8777+
conformance->setInvalid();
8778+
return;
87118779
}
87128780
}
87138781

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;

lib/Serialization/ModuleFile.cpp

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -135,8 +135,15 @@ ModuleFile::ModuleFile(std::shared_ptr<const ModuleFileSharedCore> core)
135135
}
136136

137137
bool ModuleFile::allowCompilerErrors() const {
138-
return getContext().LangOpts.AllowModuleWithCompilerErrors ||
139-
getContext().ForceAllowModuleWithCompilerErrors;
138+
return getContext().LangOpts.AllowModuleWithCompilerErrors;
139+
}
140+
141+
bool ModuleFile::enableExtendedDeserializationRecovery() const {
142+
ASTContext &ctx = getContext();
143+
return ctx.LangOpts.EnableDeserializationRecovery &&
144+
(allowCompilerErrors() ||
145+
ctx.LangOpts.DebuggerSupport ||
146+
ctx.ForceExtendedDeserializationRecovery);
140147
}
141148

142149
Status

lib/Serialization/ModuleFile.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -691,6 +691,10 @@ class ModuleFile
691691
/// '-experimental-allow-module-with-compiler-errors' is currently enabled).
692692
bool allowCompilerErrors() const;
693693

694+
/// Allow recovering from errors that could be unsafe when compiling
695+
/// the binary. Useful for the debugger and IDE support tools.
696+
bool enableExtendedDeserializationRecovery() const;
697+
694698
/// \c true if this module has incremental dependency information.
695699
bool hasIncrementalInfo() const { return Core->hasIncrementalInfo(); }
696700

0 commit comments

Comments
 (0)