Skip to content

Commit 4fd7d8e

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 dd19f14 commit 4fd7d8e

File tree

6 files changed

+293
-56
lines changed

6 files changed

+293
-56
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: 106 additions & 26 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,92 @@ void ModuleFile::fatal(llvm::Error error) const {
179181
Core->fatal(diagnoseFatal(std::move(error)));
180182
}
181183

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

193-
// Otherwise, augment the error with contextual information and pass it back.
268+
// Otherwise, augment the error with contextual information at this point
269+
// of failure and pass it back to be reported later.
194270
std::string msg;
195271
{
196272
llvm::raw_string_ostream os(msg);
@@ -1780,13 +1856,15 @@ ModuleFile::resolveCrossReference(ModuleID MID, uint32_t pathLen) {
17801856
// is mostly for compiler engineers to understand a likely solution at a
17811857
// quick glance.
17821858
SmallVector<char, 64> strScratch;
1783-
SmallVector<std::string, 2> notes;
1784-
auto declName = getXRefDeclNameForError();
1859+
1860+
auto errorKind = ModularizationError::Kind::NotFound;
1861+
Identifier foundIn;
1862+
17851863
if (recordID == XREF_TYPE_PATH_PIECE ||
17861864
recordID == XREF_VALUE_PATH_PIECE) {
17871865
auto &ctx = getContext();
17881866
for (auto nameAndModule : ctx.getLoadedModules()) {
1789-
auto baseModule = nameAndModule.second;
1867+
auto otherModule = nameAndModule.second;
17901868

17911869
IdentifierID IID;
17921870
IdentifierID privateDiscriminator = 0;
@@ -1815,10 +1893,10 @@ ModuleFile::resolveCrossReference(ModuleID MID, uint32_t pathLen) {
18151893

18161894
values.clear();
18171895
if (privateDiscriminator) {
1818-
baseModule->lookupMember(values, baseModule, name,
1896+
otherModule->lookupMember(values, otherModule, name,
18191897
getIdentifier(privateDiscriminator));
18201898
} else {
1821-
baseModule->lookupQualified(baseModule, DeclNameRef(name),
1899+
otherModule->lookupQualified(otherModule, DeclNameRef(name),
18221900
NL_QualifiedDefault,
18231901
values);
18241902
}
@@ -1832,30 +1910,30 @@ ModuleFile::resolveCrossReference(ModuleID MID, uint32_t pathLen) {
18321910
// Found a full match in a different module. It should be a different
18331911
// one because otherwise it would have succeeded on the first search.
18341912
// 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);
1913+
errorKind = ModularizationError::Kind::Moved;
1914+
foundIn = otherModule->getName();
1915+
break;
18421916
} else if (hadAMatchBeforeFiltering) {
18431917
// Found a match that was filtered out. This may be from the same
18441918
// expected module if there's a type difference. This can be caused
18451919
// by the use of different Swift language versions between a library
18461920
// 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);
1921+
errorKind = ModularizationError::Kind::TypeChanged;
1922+
foundIn = otherModule->getName();
1923+
break;
18531924
}
18541925
}
18551926
}
18561927

1857-
return llvm::make_error<XRefError>("top-level value not found", pathTrace,
1858-
declName, notes);
1928+
auto declName = getXRefDeclNameForError();
1929+
auto expectedIn = baseModule->getName();
1930+
auto referencedFrom = getName();
1931+
return llvm::make_error<ModularizationError>(declName,
1932+
errorKind,
1933+
expectedIn,
1934+
referencedFrom,
1935+
foundIn,
1936+
pathTrace);
18591937
}
18601938

18611939
// Filters for values discovered in the remaining path pieces.
@@ -7256,7 +7334,8 @@ static llvm::Error consumeErrorIfXRefNonLoadedModule(llvm::Error &&error) {
72567334
// implementation-only import hiding types and decls.
72577335
// rdar://problem/60291019
72587336
if (error.isA<XRefNonLoadedModuleError>() ||
7259-
error.isA<UnsafeDeserializationError>()) {
7337+
error.isA<UnsafeDeserializationError>() ||
7338+
error.isA<ModularizationError>()) {
72607339
consumeError(std::move(error));
72617340
return llvm::Error::success();
72627341
}
@@ -7269,7 +7348,8 @@ static llvm::Error consumeErrorIfXRefNonLoadedModule(llvm::Error &&error) {
72697348
auto *TE = static_cast<TypeError*>(errorInfo.get());
72707349

72717350
if (TE->underlyingReasonIsA<XRefNonLoadedModuleError>() ||
7272-
TE->underlyingReasonIsA<UnsafeDeserializationError>()) {
7351+
TE->underlyingReasonIsA<UnsafeDeserializationError>() ||
7352+
TE->underlyingReasonIsA<ModularizationError>()) {
72737353
consumeError(std::move(errorInfo));
72747354
return llvm::Error::success();
72757355
}

0 commit comments

Comments
 (0)