Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 4 additions & 5 deletions include/swift/AST/ASTContext.h
Original file line number Diff line number Diff line change
Expand Up @@ -381,11 +381,10 @@ class ASTContext final {
/// files?
bool IgnoreAdjacentModules = false;

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

// Define the set of known identifiers.
#define IDENTIFIER_WITH_NAME(Name, IdStr) Identifier Id_##Name;
Expand Down
12 changes: 12 additions & 0 deletions include/swift/AST/DiagnosticsSema.def
Original file line number Diff line number Diff line change
Expand Up @@ -1037,6 +1037,18 @@ NOTE(modularization_issue_worked_around,none,
"-experimental-force-workaround-broken-modules",
())

ERROR(modularization_issue_conformance_xref_error,none,
"Conformance of %0 to %1 not found in referenced module %2",
(DeclName, DeclName, DeclName))
NOTE(modularization_issue_conformance_xref_note,none,
"Breaks conformances of '%0' to %1",
(StringRef, DeclName))
ERROR(modularization_issue_conformance_error,none,
"Conformances of '%0' "
"do not match requirement signature of %1; "
"%2 conformances for %3 requirements",
(StringRef, DeclName, unsigned int, unsigned int))

ERROR(reserved_member_name,none,
"type member must not be named %0, since it would conflict with the"
" 'foo.%1' expression", (const ValueDecl *, StringRef))
Expand Down
2 changes: 1 addition & 1 deletion lib/Index/Index.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2168,7 +2168,7 @@ void index::indexModule(ModuleDecl *module, IndexDataConsumer &consumer) {
return;
}

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

IndexSwiftASTWalker walker(consumer, ctx);
walker.visitModule(*module);
Expand Down
116 changes: 92 additions & 24 deletions lib/Serialization/Deserialization.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,8 @@ const char ModularizationError::ID = '\0';
void ModularizationError::anchor() {}
const char InvalidEnumValueError::ID = '\0';
void InvalidEnumValueError::anchor() {}
const char ConformanceXRefError::ID = '\0';
void ConformanceXRefError::anchor() {}

/// Skips a single record in the bitstream.
///
Expand Down Expand Up @@ -404,6 +406,16 @@ ModuleFile::diagnoseModularizationError(llvm::Error error,
return outError;
}

void
ConformanceXRefError::diagnose(const ModuleFile *MF,
DiagnosticBehavior limit) const {
auto &diags = MF->getContext().Diags;
diags.diagnose(MF->getSourceLoc(),
diag::modularization_issue_conformance_xref_error,
name, protoName, expectedModule->getName())
.limitBehavior(limit);
}

llvm::Error ModuleFile::diagnoseFatal(llvm::Error error) const {

auto &ctx = getContext();
Expand Down Expand Up @@ -860,7 +872,8 @@ ProtocolConformanceDeserializer::readSpecializedProtocolConformance(
return subMapOrError.takeError();
auto subMap = subMapOrError.get();

auto genericConformance = MF.getConformance(conformanceID);
ProtocolConformanceRef genericConformance;
UNWRAP(MF.getConformanceChecked(conformanceID), genericConformance);

PrettyStackTraceDecl traceTo("... to", genericConformance.getRequirement());
++NumNormalProtocolConformancesLoaded;
Expand Down Expand Up @@ -892,8 +905,8 @@ ProtocolConformanceDeserializer::readInheritedProtocolConformance(
PrettyStackTraceType trace(ctx, "reading inherited conformance for",
conformingType);

ProtocolConformanceRef inheritedConformance =
MF.getConformance(conformanceID);
ProtocolConformanceRef inheritedConformance;
UNWRAP(MF.getConformanceChecked(conformanceID), inheritedConformance);
PrettyStackTraceDecl traceTo("... to",
inheritedConformance.getRequirement());

Expand Down Expand Up @@ -943,14 +956,16 @@ ProtocolConformanceDeserializer::readNormalProtocolConformanceXRef(
ProtocolConformanceXrefLayout::readRecord(scratch, protoID, nominalID,
moduleID);

auto maybeNominal = MF.getDeclChecked(nominalID);
if (!maybeNominal)
return maybeNominal.takeError();

auto nominal = cast<NominalTypeDecl>(maybeNominal.get());
Decl *maybeNominal;
UNWRAP(MF.getDeclChecked(nominalID), maybeNominal);
auto nominal = cast<NominalTypeDecl>(maybeNominal);
PrettyStackTraceDecl trace("cross-referencing conformance for", nominal);
auto proto = cast<ProtocolDecl>(MF.getDecl(protoID));

Decl *maybeProto;
UNWRAP(MF.getDeclChecked(protoID), maybeProto);
auto proto = cast<ProtocolDecl>(maybeProto);
PrettyStackTraceDecl traceTo("... to", proto);

auto module = MF.getModule(moduleID);

// FIXME: If the module hasn't been loaded, we probably don't want to fall
Expand All @@ -971,16 +986,26 @@ ProtocolConformanceDeserializer::readNormalProtocolConformanceXRef(
// TODO: Sink Sendable derivation into the conformance lookup table
if (proto->isSpecificProtocol(KnownProtocolKind::Sendable)) {
auto conformanceRef = lookupConformance(nominal->getDeclaredInterfaceType(), proto);
if (!conformanceRef.isConcrete())
abort();
return conformanceRef.getConcrete();
if (conformanceRef.isConcrete())
return conformanceRef.getConcrete();
} else {
SmallVector<ProtocolConformance *, 2> conformances;
nominal->lookupConformance(proto, conformances);
if (conformances.empty())
abort();
return conformances.front();
if (!conformances.empty())
return conformances.front();
}

auto error = llvm::make_error<ConformanceXRefError>(
nominal->getName(), proto->getName(), module);

if (!MF.enableExtendedDeserializationRecovery()) {
error = llvm::handleErrors(std::move(error),
[&](const ConformanceXRefError &error) -> llvm::Error {
error.diagnose(&MF);
return llvm::make_error<ConformanceXRefError>(std::move(error));
});
}
return error;
}

Expected<ProtocolConformance *>
Expand Down Expand Up @@ -7863,7 +7888,7 @@ Expected<Type> DESERIALIZE_TYPE(SIL_FUNCTION_TYPE)(
ProtocolConformanceRef witnessMethodConformance;
if (*representation == swift::SILFunctionTypeRepresentation::WitnessMethod) {
auto conformanceID = variableData[nextVariableDataIndex++];
witnessMethodConformance = MF.getConformance(conformanceID);
UNWRAP(MF.getConformanceChecked(conformanceID), witnessMethodConformance);
}

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

const ProtocolDecl *proto = conformance->getProtocol();

// Read requirement signature conformances.
SmallVector<ProtocolConformanceRef, 4> reqConformances;
for (auto conformanceID : rawIDs.slice(0, conformanceCount)) {
auto maybeConformance = getConformanceChecked(conformanceID);
if (maybeConformance) {
reqConformances.push_back(maybeConformance.get());
} else if (getContext().LangOpts.EnableDeserializationRecovery) {
diagnoseAndConsumeError(maybeConformance.takeError());
llvm::Error error = maybeConformance.takeError();
if (error.isA<ConformanceXRefError>() &&
!enableExtendedDeserializationRecovery()) {

std::string typeStr = conformance->getType()->getString();
auto &diags = getContext().Diags;
diags.diagnose(getSourceLoc(),
diag::modularization_issue_conformance_xref_note,
typeStr, proto->getName());

consumeError(std::move(error));
conformance->setInvalid();
return;
}

diagnoseAndConsumeError(std::move(error));
reqConformances.push_back(ProtocolConformanceRef::forInvalid());
} else {
fatal(maybeConformance.takeError());
}
}

const ProtocolDecl *proto = conformance->getProtocol();
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
Expand Down Expand Up @@ -8698,12 +8739,39 @@ void ModuleFile::finishNormalConformance(NormalProtocolConformance *conformance,
return req.getKind() == RequirementKind::Conformance;
};
auto requirements = proto->getRequirementSignature().getRequirements();
if (!allowCompilerErrors() &&
conformanceCount != llvm::count_if(requirements,
isConformanceReq)) {
fatal(llvm::make_error<llvm::StringError>(
"serialized conformances do not match requirement signature",
llvm::inconvertibleErrorCode()));
unsigned int conformanceRequirementCount =
llvm::count_if(requirements, isConformanceReq);
if (conformanceCount != conformanceRequirementCount) {
// Mismatch between the number of loaded conformances and the expected
// requirements. One or the other likely comes from a stale module.

if (!enableExtendedDeserializationRecovery()) {
// Error and print full context for visual inspection.
ASTContext &ctx = getContext();
std::string typeStr = conformance->getType()->getString();
ctx.Diags.diagnose(getSourceLoc(),
diag::modularization_issue_conformance_error,
typeStr, proto->getName(), conformanceCount,
conformanceRequirementCount);
ctx.Diags.flushConsumers();

// Print context to stderr.
PrintOptions Opts;
llvm::errs() << "Requirements:\n";
for (auto req: requirements) {
req.print(llvm::errs(), Opts);
llvm::errs() << "\n";
}

llvm::errs() << "Conformances:\n";
for (auto req: reqConformances) {
req.print(llvm::errs());
llvm::errs() << "\n";
}
}

conformance->setInvalid();
return;
}
}

Expand Down
30 changes: 30 additions & 0 deletions lib/Serialization/DeserializationErrors.h
Original file line number Diff line number Diff line change
Expand Up @@ -677,6 +677,36 @@ class InvalidEnumValueError
}
};

// Cross-reference to a conformance was not found where it was expected.
class ConformanceXRefError : public llvm::ErrorInfo<ConformanceXRefError,
DeclDeserializationError> {
friend ErrorInfo;
static const char ID;
void anchor() override;

DeclName protoName;
const ModuleDecl *expectedModule;

public:
ConformanceXRefError(Identifier name, Identifier protoName,
const ModuleDecl *expectedModule):
protoName(protoName), expectedModule(expectedModule) {
this->name = name;
}

void diagnose(const ModuleFile *MF,
DiagnosticBehavior limit = DiagnosticBehavior::Fatal) const;

void log(raw_ostream &OS) const override {
OS << "Conformance of '" << name << "' to '" << protoName
<< "' not found in module '" << expectedModule->getName() << "'";
}

std::error_code convertToErrorCode() const override {
return llvm::inconvertibleErrorCode();
}
};

class PrettyStackTraceModuleFile : public llvm::PrettyStackTraceEntry {
const char *Action;
const ModuleFile &MF;
Expand Down
11 changes: 9 additions & 2 deletions lib/Serialization/ModuleFile.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -135,8 +135,15 @@ ModuleFile::ModuleFile(std::shared_ptr<const ModuleFileSharedCore> core)
}

bool ModuleFile::allowCompilerErrors() const {
return getContext().LangOpts.AllowModuleWithCompilerErrors ||
getContext().ForceAllowModuleWithCompilerErrors;
return getContext().LangOpts.AllowModuleWithCompilerErrors;
}

bool ModuleFile::enableExtendedDeserializationRecovery() const {
ASTContext &ctx = getContext();
return ctx.LangOpts.EnableDeserializationRecovery &&
(allowCompilerErrors() ||
ctx.LangOpts.DebuggerSupport ||
ctx.ForceExtendedDeserializationRecovery);
}

Status
Expand Down
4 changes: 4 additions & 0 deletions lib/Serialization/ModuleFile.h
Original file line number Diff line number Diff line change
Expand Up @@ -691,6 +691,10 @@ class ModuleFile
/// '-experimental-allow-module-with-compiler-errors' is currently enabled).
bool allowCompilerErrors() const;

/// Allow recovering from errors that could be unsafe when compiling
/// the binary. Useful for the debugger and IDE support tools.
bool enableExtendedDeserializationRecovery() const;

/// \c true if this module has incremental dependency information.
bool hasIncrementalInfo() const { return Core->hasIncrementalInfo(); }

Expand Down
Loading