Skip to content

Commit 7f8bf0b

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 7d87798 commit 7f8bf0b

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

@@ -179,18 +181,99 @@ void ModuleFile::fatal(llvm::Error error) const {
179181
Core->fatal(diagnoseFatal(std::move(error)));
180182
}
181183

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

193-
// Otherwise, augment the error with contextual information and pass it back.
275+
// Otherwise, augment the error with contextual information at this point
276+
// of failure and pass it back to be reported later.
194277
std::string msg;
195278
{
196279
llvm::raw_string_ostream os(msg);
@@ -1780,18 +1863,21 @@ ModuleFile::resolveCrossReference(ModuleID MID, uint32_t pathLen) {
17801863
// is mostly for compiler engineers to understand a likely solution at a
17811864
// quick glance.
17821865
SmallVector<char, 64> strScratch;
1783-
SmallVector<std::string, 2> notes;
1784-
auto declName = getXRefDeclNameForError();
1866+
1867+
auto errorKind = ModularizationError::Kind::DeclNotFound;
1868+
Identifier foundIn;
1869+
bool isType = false;
1870+
17851871
if (recordID == XREF_TYPE_PATH_PIECE ||
17861872
recordID == XREF_VALUE_PATH_PIECE) {
17871873
auto &ctx = getContext();
17881874
for (auto nameAndModule : ctx.getLoadedModules()) {
1789-
auto baseModule = nameAndModule.second;
1875+
auto otherModule = nameAndModule.second;
17901876

17911877
IdentifierID IID;
17921878
IdentifierID privateDiscriminator = 0;
17931879
TypeID TID = 0;
1794-
bool isType = (recordID == XREF_TYPE_PATH_PIECE);
1880+
isType = (recordID == XREF_TYPE_PATH_PIECE);
17951881
bool inProtocolExt = false;
17961882
bool importedFromClang = false;
17971883
bool isStatic = false;
@@ -1815,10 +1901,10 @@ ModuleFile::resolveCrossReference(ModuleID MID, uint32_t pathLen) {
18151901

18161902
values.clear();
18171903
if (privateDiscriminator) {
1818-
baseModule->lookupMember(values, baseModule, name,
1904+
otherModule->lookupMember(values, otherModule, name,
18191905
getIdentifier(privateDiscriminator));
18201906
} else {
1821-
baseModule->lookupQualified(baseModule, DeclNameRef(name),
1907+
otherModule->lookupQualified(otherModule, DeclNameRef(name),
18221908
NL_QualifiedDefault,
18231909
values);
18241910
}
@@ -1832,30 +1918,31 @@ ModuleFile::resolveCrossReference(ModuleID MID, uint32_t pathLen) {
18321918
// Found a full match in a different module. It should be a different
18331919
// one because otherwise it would have succeeded on the first search.
18341920
// This is usually caused by the use of poorly modularized headers.
1835-
auto line = "There is a matching '" +
1836-
declName.getString(strScratch).str() +
1837-
"' in module '" +
1838-
std::string(nameAndModule.first.str()) +
1839-
"'. If this is imported from clang, please make sure " +
1840-
"the header is part of a single clang module.";
1841-
notes.emplace_back(line);
1921+
errorKind = ModularizationError::Kind::DeclMoved;
1922+
foundIn = otherModule->getName();
1923+
break;
18421924
} else if (hadAMatchBeforeFiltering) {
18431925
// Found a match that was filtered out. This may be from the same
18441926
// expected module if there's a type difference. This can be caused
18451927
// by the use of different Swift language versions between a library
18461928
// with serialized SIL and a client.
1847-
auto line = "'" +
1848-
declName.getString(strScratch).str() +
1849-
"' in module '" +
1850-
std::string(nameAndModule.first.str()) +
1851-
"' was filtered out.";
1852-
notes.emplace_back(line);
1929+
errorKind = ModularizationError::Kind::DeclKindChanged;
1930+
foundIn = otherModule->getName();
1931+
break;
18531932
}
18541933
}
18551934
}
18561935

1857-
return llvm::make_error<XRefError>("top-level value not found", pathTrace,
1858-
declName, notes);
1936+
auto declName = getXRefDeclNameForError();
1937+
auto expectedIn = baseModule->getName();
1938+
auto referencedFrom = getName();
1939+
return llvm::make_error<ModularizationError>(declName,
1940+
isType,
1941+
errorKind,
1942+
expectedIn,
1943+
referencedFrom,
1944+
foundIn,
1945+
pathTrace);
18591946
}
18601947

18611948
// Filters for values discovered in the remaining path pieces.
@@ -7256,7 +7343,8 @@ static llvm::Error consumeErrorIfXRefNonLoadedModule(llvm::Error &&error) {
72567343
// implementation-only import hiding types and decls.
72577344
// rdar://problem/60291019
72587345
if (error.isA<XRefNonLoadedModuleError>() ||
7259-
error.isA<UnsafeDeserializationError>()) {
7346+
error.isA<UnsafeDeserializationError>() ||
7347+
error.isA<ModularizationError>()) {
72607348
consumeError(std::move(error));
72617349
return llvm::Error::success();
72627350
}
@@ -7269,7 +7357,8 @@ static llvm::Error consumeErrorIfXRefNonLoadedModule(llvm::Error &&error) {
72697357
auto *TE = static_cast<TypeError*>(errorInfo.get());
72707358

72717359
if (TE->underlyingReasonIsA<XRefNonLoadedModuleError>() ||
7272-
TE->underlyingReasonIsA<UnsafeDeserializationError>()) {
7360+
TE->underlyingReasonIsA<UnsafeDeserializationError>() ||
7361+
TE->underlyingReasonIsA<ModularizationError>()) {
72737362
consumeError(std::move(errorInfo));
72747363
return llvm::Error::success();
72757364
}

0 commit comments

Comments
 (0)