Skip to content

Commit 144d7eb

Browse files
committed
[Serialization] Report detected modularization breaks
The Swift compiler expects the context to remain stable between when a module is built and loaded by a client. Usually the build system would rebuild a module if a dependency changes, or the compiler would rebuilt the module from a swiftinterface on a context change. However, such changes are not always detected and in that case the compiler may crash on an inconsistency in the context. We often see this when a clang module is poorly modularized, the headers are modified in the SDK, or some clang define change its API. These are project issues that used to make the compiler crash, it provided a poor experience and doesn't encourage the developer to fix them by themselves. Instead, let's keep track of modularization issues encountered during deserialization and report them as proper errors when they trigger a fatal failure preventing compilation.
1 parent 008047f commit 144d7eb

File tree

6 files changed

+309
-58
lines changed

6 files changed

+309
-58
lines changed

include/swift/AST/DiagnosticsSema.def

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -872,6 +872,27 @@ ERROR(need_hermetic_seal_to_import_module,none,
872872
"current compilation does not have -experimental-hermetic-seal-at-link",
873873
(Identifier))
874874

875+
ERROR(modularization_issue_decl_moved,Fatal,
876+
"reference to %select{top-level|type}0 %1 broken by a context change; "
877+
"%1 was expected to be in %2, but now a candidate is found only in %3",
878+
(bool, DeclName, Identifier, Identifier))
879+
ERROR(modularization_issue_decl_type_changed,Fatal,
880+
"reference to %select{top-level|type}0 %1 broken by a context change; "
881+
"the details of %1 %select{from %2|}5 changed since building '%3'"
882+
"%select{|, it was in %2 and is now found in %4}5",
883+
(bool, DeclName, Identifier, StringRef, Identifier, bool))
884+
ERROR(modularization_issue_decl_not_found,Fatal,
885+
"reference to %select{top-level|type}0 %1 broken by a context change; "
886+
"%1 is not found, it was expected to be in %2",
887+
(bool, DeclName, Identifier))
888+
889+
NOTE(modularization_issue_side_effect_extension_error,none,
890+
"could not deserialize extension",
891+
())
892+
NOTE(modularization_issue_side_effect_type_error,none,
893+
"could not deserialize type for %0",
894+
(DeclName))
895+
875896
ERROR(reserved_member_name,none,
876897
"type member must not be named %0, since it would conflict with the"
877898
" 'foo.%1' expression", (DeclName, StringRef))

lib/Serialization/Deserialization.cpp

Lines changed: 117 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,8 @@ const char InvalidRecordKindError::ID = '\0';
157157
void InvalidRecordKindError::anchor() {}
158158
const char UnsafeDeserializationError::ID = '\0';
159159
void UnsafeDeserializationError::anchor() {}
160+
const char ModularizationError::ID = '\0';
161+
void ModularizationError::anchor() {}
160162

161163
static llvm::Error consumeErrorIfXRefNonLoadedModule(llvm::Error &&error);
162164

@@ -188,18 +190,99 @@ SourceLoc ModuleFile::getSourceLoc() const {
188190
return SourceMgr.getLocForBufferStart(*bufferID);
189191
}
190192

193+
void
194+
ModularizationError::diagnose(const ModuleFile *MF,
195+
DiagnosticBehavior limit) const {
196+
auto &ctx = MF->getContext();
197+
198+
auto diagnoseError = [&](Kind errorKind) {
199+
switch (errorKind) {
200+
case Kind::DeclMoved:
201+
return ctx.Diags.diagnose(MF->getSourceLoc(), diag::modularization_issue_decl_moved,
202+
declIsType, name, expectedModuleName,
203+
foundModuleName);
204+
case Kind::DeclKindChanged:
205+
return
206+
ctx.Diags.diagnose(MF->getSourceLoc(), diag::modularization_issue_decl_type_changed,
207+
declIsType, name, expectedModuleName,
208+
referencedFromModuleName, foundModuleName,
209+
foundModuleName != expectedModuleName);
210+
case Kind::DeclNotFound:
211+
return ctx.Diags.diagnose(MF->getSourceLoc(), diag::modularization_issue_decl_not_found,
212+
declIsType, name, expectedModuleName);
213+
}
214+
llvm_unreachable("Unhandled ModularizationError::Kind in switch.");
215+
};
216+
217+
auto inFlight = diagnoseError(errorKind);
218+
inFlight.limitBehavior(limit);
219+
inFlight.flush();
220+
221+
// We could pass along the `path` information through notes.
222+
// However, for a top-level decl a path would just duplicate the
223+
// expected module name and the decl name from the diagnostic.
224+
}
225+
226+
void TypeError::diagnose(const ModuleFile *MF) const {
227+
MF->getContext().Diags.diagnose(MF->getSourceLoc(),
228+
diag::modularization_issue_side_effect_type_error,
229+
name);
230+
}
231+
232+
void ExtensionError::diagnose(const ModuleFile *MF) const {
233+
MF->getContext().Diags.diagnose(MF->getSourceLoc(),
234+
diag::modularization_issue_side_effect_extension_error);
235+
}
236+
191237
llvm::Error ModuleFile::diagnoseFatal(llvm::Error error) const {
192-
if (FileContext)
193-
getContext().Diags.diagnose(SourceLoc(), diag::serialization_fatal,
194-
Core->Name);
238+
239+
auto &ctx = getContext();
240+
if (FileContext) {
241+
if (ctx.LangOpts.EnableDeserializationRecovery) {
242+
// Attempt to report relevant errors as diagnostics.
243+
// At this time, only ModularizationErrors are reported directly. They
244+
// can get here either directly or as underlying causes to a TypeError or
245+
// and ExtensionError.
246+
auto handleModularizationError =
247+
[&](const ModularizationError &modularError) -> llvm::Error {
248+
modularError.diagnose(this);
249+
return llvm::Error::success();
250+
};
251+
error = llvm::handleErrors(std::move(error),
252+
handleModularizationError,
253+
[&](TypeError &typeError) -> llvm::Error {
254+
if (typeError.diagnoseUnderlyingReason(handleModularizationError)) {
255+
typeError.diagnose(this);
256+
return llvm::Error::success();
257+
}
258+
return llvm::make_error<TypeError>(std::move(typeError));
259+
},
260+
[&](ExtensionError &extError) -> llvm::Error {
261+
if (extError.diagnoseUnderlyingReason(handleModularizationError)) {
262+
extError.diagnose(this);
263+
return llvm::Error::success();
264+
}
265+
return llvm::make_error<ExtensionError>(std::move(extError));
266+
});
267+
268+
// If no error is left, it was reported as a diagnostic. There's no
269+
// need to crash.
270+
if (!error)
271+
return llvm::Error::success();
272+
}
273+
274+
// General deserialization failure message.
275+
ctx.Diags.diagnose(getSourceLoc(), diag::serialization_fatal, Core->Name);
276+
}
195277
// Unless in the debugger, crash. ModuleFileSharedCore::fatal() calls abort().
196278
// This allows aggregation of crash logs for compiler development, but in a
197279
// long-running process like LLDB this is undesirable. Only abort() if not in
198280
// the debugger.
199-
if (!getContext().LangOpts.DebuggerSupport)
281+
if (!ctx.LangOpts.DebuggerSupport)
200282
Core->fatal(std::move(error));
201283

202-
// Otherwise, augment the error with contextual information and pass it back.
284+
// Otherwise, augment the error with contextual information at this point
285+
// of failure and pass it back to be reported later.
203286
std::string msg;
204287
{
205288
llvm::raw_string_ostream os(msg);
@@ -1789,18 +1872,21 @@ ModuleFile::resolveCrossReference(ModuleID MID, uint32_t pathLen) {
17891872
// is mostly for compiler engineers to understand a likely solution at a
17901873
// quick glance.
17911874
SmallVector<char, 64> strScratch;
1792-
SmallVector<std::string, 2> notes;
1793-
auto declName = getXRefDeclNameForError();
1875+
1876+
auto errorKind = ModularizationError::Kind::DeclNotFound;
1877+
Identifier foundIn;
1878+
bool isType = false;
1879+
17941880
if (recordID == XREF_TYPE_PATH_PIECE ||
17951881
recordID == XREF_VALUE_PATH_PIECE) {
17961882
auto &ctx = getContext();
17971883
for (auto nameAndModule : ctx.getLoadedModules()) {
1798-
auto baseModule = nameAndModule.second;
1884+
auto otherModule = nameAndModule.second;
17991885

18001886
IdentifierID IID;
18011887
IdentifierID privateDiscriminator = 0;
18021888
TypeID TID = 0;
1803-
bool isType = (recordID == XREF_TYPE_PATH_PIECE);
1889+
isType = (recordID == XREF_TYPE_PATH_PIECE);
18041890
bool inProtocolExt = false;
18051891
bool importedFromClang = false;
18061892
bool isStatic = false;
@@ -1824,10 +1910,10 @@ ModuleFile::resolveCrossReference(ModuleID MID, uint32_t pathLen) {
18241910

18251911
values.clear();
18261912
if (privateDiscriminator) {
1827-
baseModule->lookupMember(values, baseModule, name,
1913+
otherModule->lookupMember(values, otherModule, name,
18281914
getIdentifier(privateDiscriminator));
18291915
} else {
1830-
baseModule->lookupQualified(baseModule, DeclNameRef(name),
1916+
otherModule->lookupQualified(otherModule, DeclNameRef(name),
18311917
NL_QualifiedDefault,
18321918
values);
18331919
}
@@ -1841,30 +1927,31 @@ ModuleFile::resolveCrossReference(ModuleID MID, uint32_t pathLen) {
18411927
// Found a full match in a different module. It should be a different
18421928
// one because otherwise it would have succeeded on the first search.
18431929
// This is usually caused by the use of poorly modularized headers.
1844-
auto line = "There is a matching '" +
1845-
declName.getString(strScratch).str() +
1846-
"' in module '" +
1847-
std::string(nameAndModule.first.str()) +
1848-
"'. If this is imported from clang, please make sure " +
1849-
"the header is part of a single clang module.";
1850-
notes.emplace_back(line);
1930+
errorKind = ModularizationError::Kind::DeclMoved;
1931+
foundIn = otherModule->getName();
1932+
break;
18511933
} else if (hadAMatchBeforeFiltering) {
18521934
// Found a match that was filtered out. This may be from the same
18531935
// expected module if there's a type difference. This can be caused
18541936
// by the use of different Swift language versions between a library
18551937
// with serialized SIL and a client.
1856-
auto line = "'" +
1857-
declName.getString(strScratch).str() +
1858-
"' in module '" +
1859-
std::string(nameAndModule.first.str()) +
1860-
"' was filtered out.";
1861-
notes.emplace_back(line);
1938+
errorKind = ModularizationError::Kind::DeclKindChanged;
1939+
foundIn = otherModule->getName();
1940+
break;
18621941
}
18631942
}
18641943
}
18651944

1866-
return llvm::make_error<XRefError>("top-level value not found", pathTrace,
1867-
declName, notes);
1945+
auto declName = getXRefDeclNameForError();
1946+
auto expectedIn = baseModule->getName();
1947+
auto referencedFrom = getName();
1948+
return llvm::make_error<ModularizationError>(declName,
1949+
isType,
1950+
errorKind,
1951+
expectedIn,
1952+
referencedFrom,
1953+
foundIn,
1954+
pathTrace);
18681955
}
18691956

18701957
// Filters for values discovered in the remaining path pieces.
@@ -7265,7 +7352,8 @@ static llvm::Error consumeErrorIfXRefNonLoadedModule(llvm::Error &&error) {
72657352
// implementation-only import hiding types and decls.
72667353
// rdar://problem/60291019
72677354
if (error.isA<XRefNonLoadedModuleError>() ||
7268-
error.isA<UnsafeDeserializationError>()) {
7355+
error.isA<UnsafeDeserializationError>() ||
7356+
error.isA<ModularizationError>()) {
72697357
consumeError(std::move(error));
72707358
return llvm::Error::success();
72717359
}
@@ -7278,7 +7366,8 @@ static llvm::Error consumeErrorIfXRefNonLoadedModule(llvm::Error &&error) {
72787366
auto *TE = static_cast<TypeError*>(errorInfo.get());
72797367

72807368
if (TE->underlyingReasonIsA<XRefNonLoadedModuleError>() ||
7281-
TE->underlyingReasonIsA<UnsafeDeserializationError>()) {
7369+
TE->underlyingReasonIsA<UnsafeDeserializationError>() ||
7370+
TE->underlyingReasonIsA<ModularizationError>()) {
72827371
consumeError(std::move(errorInfo));
72837372
return llvm::Error::success();
72847373
}

0 commit comments

Comments
 (0)