Skip to content

Commit 0829c68

Browse files
committed
[Serialization] Report detected modularization breaks
The Swift compiler expects the context against which a binary swiftmodule was built to remain stable when imported by a client. Usually the build system would rebuild a module if a dependency changes, or the compiler would regenerate it from a swiftinterface on a context change. However, such changes are not always detected and in such a case the compiler can crash on a inconsistency in the context. We often see that when a clang module is poorly modularized which makes it be import differently depending on the precise compiler invocation. These are project issues, having them make the compiler crash provides a bad experience and doesn't encourage the developer to fix them by themselves. Let's keep track of modularization issues encountered during deserialization and report them as errors when they trigger a fatal failure preventing compilation.
1 parent c6acb4d commit 0829c68

File tree

7 files changed

+296
-59
lines changed

7 files changed

+296
-59
lines changed

include/swift/AST/DiagnosticsSema.def

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -872,6 +872,31 @@ 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_moved,Fatal,
876+
"module reference to top-level %0 broken by a context change, "
877+
"was found in %1 when building '%2', "
878+
"but now a candidate is found only in %3. "
879+
"Audit related headers or uninstall SDK roots.",
880+
(DeclName, Identifier, StringRef, Identifier))
881+
ERROR(modularization_issue_type_changed,Fatal,
882+
"module reference to top-level %0 broken by a context change, "
883+
"its type changed since building '%2', "
884+
"it was in %1 now found in %3. "
885+
"Ensure Swift language version match between modules or uninstall SDK roots.",
886+
(DeclName, Identifier, StringRef, Identifier))
887+
ERROR(modularization_issue_not_found,Fatal,
888+
"module reference to top-level %0 broken by a context change, "
889+
"module '%2' references %0 in %1, but it cannot be found in this build. "
890+
"Audit related headers or uninstall SDK roots.",
891+
(DeclName, Identifier, StringRef, Identifier))
892+
893+
NOTE(modularization_issue_effect_extension_error,none,
894+
"could not deserialize extension",
895+
())
896+
NOTE(modularization_issue_effect_type_error,none,
897+
"could not deserialize type for %0",
898+
(DeclName))
899+
875900
ERROR(reserved_member_name,none,
876901
"type member must not be named %0, since it would conflict with the"
877902
" 'foo.%1' expression", (DeclName, StringRef))

lib/Serialization/Deserialization.cpp

Lines changed: 108 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
/// Skips a single record in the bitstream.
162164
///
@@ -177,18 +179,92 @@ void ModuleFile::fatal(llvm::Error error) const {
177179
Core->fatal(diagnoseFatal(std::move(error)));
178180
}
179181

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

191-
// Otherwise, augment the error with contextual information and pass it back.
266+
// Otherwise, augment the error with contextual information at this point
267+
// of failure and pass it back to be reported later.
192268
std::string msg;
193269
{
194270
llvm::raw_string_ostream os(msg);
@@ -1778,13 +1854,15 @@ ModuleFile::resolveCrossReference(ModuleID MID, uint32_t pathLen) {
17781854
// is mostly for compiler engineers to understand a likely solution at a
17791855
// quick glance.
17801856
SmallVector<char, 64> strScratch;
1781-
SmallVector<std::string, 2> notes;
1782-
auto declName = getXRefDeclNameForError();
1857+
1858+
auto errorKind = ModularizationError::Kind::NotFound;
1859+
Identifier foundIn;
1860+
17831861
if (recordID == XREF_TYPE_PATH_PIECE ||
17841862
recordID == XREF_VALUE_PATH_PIECE) {
17851863
auto &ctx = getContext();
17861864
for (auto nameAndModule : ctx.getLoadedModules()) {
1787-
auto baseModule = nameAndModule.second;
1865+
auto otherModule = nameAndModule.second;
17881866

17891867
IdentifierID IID;
17901868
IdentifierID privateDiscriminator = 0;
@@ -1813,10 +1891,10 @@ ModuleFile::resolveCrossReference(ModuleID MID, uint32_t pathLen) {
18131891

18141892
values.clear();
18151893
if (privateDiscriminator) {
1816-
baseModule->lookupMember(values, baseModule, name,
1894+
otherModule->lookupMember(values, otherModule, name,
18171895
getIdentifier(privateDiscriminator));
18181896
} else {
1819-
baseModule->lookupQualified(baseModule, DeclNameRef(name),
1897+
otherModule->lookupQualified(otherModule, DeclNameRef(name),
18201898
NL_QualifiedDefault,
18211899
values);
18221900
}
@@ -1830,30 +1908,30 @@ ModuleFile::resolveCrossReference(ModuleID MID, uint32_t pathLen) {
18301908
// Found a full match in a different module. It should be a different
18311909
// one because otherwise it would have succeeded on the first search.
18321910
// This is usually caused by the use of poorly modularized headers.
1833-
auto line = "There is a matching '" +
1834-
declName.getString(strScratch).str() +
1835-
"' in module '" +
1836-
std::string(nameAndModule.first.str()) +
1837-
"'. If this is imported from clang, please make sure " +
1838-
"the header is part of a single clang module.";
1839-
notes.emplace_back(line);
1911+
errorKind = ModularizationError::Kind::Moved;
1912+
foundIn = otherModule->getName();
1913+
break;
18401914
} else if (hadAMatchBeforeFiltering) {
18411915
// Found a match that was filtered out. This may be from the same
18421916
// expected module if there's a type difference. This can be caused
18431917
// by the use of different Swift language versions between a library
18441918
// with serialized SIL and a client.
1845-
auto line = "'" +
1846-
declName.getString(strScratch).str() +
1847-
"' in module '" +
1848-
std::string(nameAndModule.first.str()) +
1849-
"' was filtered out.";
1850-
notes.emplace_back(line);
1919+
errorKind = ModularizationError::Kind::TypeChanged;
1920+
foundIn = otherModule->getName();
1921+
break;
18511922
}
18521923
}
18531924
}
18541925

1855-
return llvm::make_error<XRefError>("top-level value not found", pathTrace,
1856-
declName, notes);
1926+
auto declName = getXRefDeclNameForError();
1927+
auto expectedIn = baseModule->getName();
1928+
auto referencedFrom = getName();
1929+
return llvm::make_error<ModularizationError>(declName,
1930+
errorKind,
1931+
expectedIn,
1932+
referencedFrom,
1933+
foundIn,
1934+
pathTrace);
18571935
}
18581936

18591937
// Filters for values discovered in the remaining path pieces.
@@ -7257,8 +7335,9 @@ llvm::Error ModuleFile::consumeExpectedError(llvm::Error &&error) {
72577335
// implementation-only import hiding types and decls.
72587336
// rdar://problem/60291019
72597337
if (error.isA<XRefNonLoadedModuleError>() ||
7260-
error.isA<UnsafeDeserializationError>()) {
7261-
consumeError(std::move(error));
7338+
error.isA<UnsafeDeserializationError>() ||
7339+
error.isA<ModularizationError>()) {
7340+
diagnoseAndConsumeError(std::move(error));
72627341
return llvm::Error::success();
72637342
}
72647343

@@ -7270,8 +7349,9 @@ llvm::Error ModuleFile::consumeExpectedError(llvm::Error &&error) {
72707349
auto *TE = static_cast<TypeError*>(errorInfo.get());
72717350

72727351
if (TE->underlyingReasonIsA<XRefNonLoadedModuleError>() ||
7273-
TE->underlyingReasonIsA<UnsafeDeserializationError>()) {
7274-
consumeError(std::move(errorInfo));
7352+
TE->underlyingReasonIsA<UnsafeDeserializationError>() ||
7353+
TE->underlyingReasonIsA<ModularizationError>()) {
7354+
diagnoseAndConsumeError(std::move(errorInfo));
72757355
return llvm::Error::success();
72767356
}
72777357

0 commit comments

Comments
 (0)