From f67f7e74f87e4ec3fa60c651cec2c38ddda8a79e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexis=20Laferri=C3=A8re?= Date: Fri, 5 May 2023 11:22:40 -0700 Subject: [PATCH 1/4] [Serialization] Refactor some services on errors Intro a new class ErrorWithUnderlyingReason to refactor duplicated logic from three classes. It can be used as super class to any error with an underlying error. --- lib/Serialization/DeserializationErrors.h | 65 +++++++++++++---------- 1 file changed, 36 insertions(+), 29 deletions(-) diff --git a/lib/Serialization/DeserializationErrors.h b/lib/Serialization/DeserializationErrors.h index 4e54f9d316349..bf762bfcc40a5 100644 --- a/lib/Serialization/DeserializationErrors.h +++ b/lib/Serialization/DeserializationErrors.h @@ -359,20 +359,13 @@ class OverrideError : public llvm::ErrorInfo { - friend ErrorInfo; - static const char ID; - void anchor() override; +// Service class for errors with an underlying cause. +class ErrorWithUnderlyingReason { + std::unique_ptr underlyingReason; - std::unique_ptr underlyingReason; public: - explicit TypeError(DeclName name, std::unique_ptr reason, - Flags flags={}, unsigned numVTableEntries=0) - : underlyingReason(std::move(reason)) { - this->name = name; - this->flags = flags; - this->numVTableEntries = numVTableEntries; - } + explicit ErrorWithUnderlyingReason (std::unique_ptr reason) : + underlyingReason(std::move(reason)) {} template bool underlyingReasonIsA() const { @@ -381,36 +374,53 @@ class TypeError : public llvm::ErrorInfo { return underlyingReason->isA(); } - void log(raw_ostream &OS) const override { - OS << "Could not deserialize type for '" << name << "'"; + void log(raw_ostream &OS) const { if (underlyingReason) { OS << "\nCaused by: "; underlyingReason->log(OS); } } +}; + +class TypeError : public llvm::ErrorInfo, + public ErrorWithUnderlyingReason { + friend ErrorInfo; + static const char ID; + void anchor() override; + +public: + explicit TypeError(DeclName name, std::unique_ptr reason, + Flags flags={}, unsigned numVTableEntries=0) + : ErrorWithUnderlyingReason(std::move(reason)) { + this->name = name; + this->flags = flags; + this->numVTableEntries = numVTableEntries; + } + + void log(raw_ostream &OS) const override { + OS << "Could not deserialize type for '" << name << "'"; + ErrorWithUnderlyingReason::log(OS); + } + std::error_code convertToErrorCode() const override { return llvm::inconvertibleErrorCode(); } }; -class ExtensionError : public llvm::ErrorInfo { +class ExtensionError : public llvm::ErrorInfo, + public ErrorWithUnderlyingReason { friend ErrorInfo; static const char ID; void anchor() override; - std::unique_ptr underlyingReason; - public: explicit ExtensionError(std::unique_ptr reason) - : underlyingReason(std::move(reason)) {} + : ErrorWithUnderlyingReason(std::move(reason)) {} void log(raw_ostream &OS) const override { OS << "could not deserialize extension"; - if (underlyingReason) { - OS << ": "; - underlyingReason->log(OS); - } + ErrorWithUnderlyingReason::log(OS); } std::error_code convertToErrorCode() const override { @@ -418,23 +428,20 @@ class ExtensionError : public llvm::ErrorInfo { } }; -class SILEntityError : public llvm::ErrorInfo { +class SILEntityError : public llvm::ErrorInfo, + public ErrorWithUnderlyingReason { friend ErrorInfo; static const char ID; void anchor() override; - std::unique_ptr underlyingReason; StringRef name; public: SILEntityError(StringRef name, std::unique_ptr reason) - : underlyingReason(std::move(reason)), name(name) {} + : ErrorWithUnderlyingReason(std::move(reason)), name(name) {} void log(raw_ostream &OS) const override { OS << "could not deserialize SIL entity '" << name << "'"; - if (underlyingReason) { - OS << ": "; - underlyingReason->log(OS); - } + ErrorWithUnderlyingReason::log(OS); } std::error_code convertToErrorCode() const override { From 478c653e7f4b74b0aae2115459654ed22472928c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexis=20Laferri=C3=A8re?= Date: Thu, 11 May 2023 14:25:18 -0700 Subject: [PATCH 2/4] [Serialization] Keep going after fatal errors in getAllWitnessTables --- lib/Serialization/DeserializeSIL.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Serialization/DeserializeSIL.cpp b/lib/Serialization/DeserializeSIL.cpp index 5fff4c599a7b8..3b1e9fb20bb51 100644 --- a/lib/Serialization/DeserializeSIL.cpp +++ b/lib/Serialization/DeserializeSIL.cpp @@ -3946,7 +3946,7 @@ void SILDeserializer::getAllWitnessTables() { // import, it is safe to ignore for this function's purpose. consumeError(maybeTable.takeError()); } else { - MF->fatal(maybeTable.takeError()); + MF->diagnoseAndConsumeFatal(maybeTable.takeError()); } } } From 008047f1fd2d916cd05c1a86375174f5e57c7950 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexis=20Laferri=C3=A8re?= Date: Wed, 17 May 2023 10:07:14 -0700 Subject: [PATCH 3/4] [Serialization] Intro ModuleFile::getSourceLoc() Generate a fake empty buffer to return a SourceLoc pointing to the beginning of a swiftmodule file. --- lib/Serialization/Deserialization.cpp | 9 +++++++++ lib/Serialization/ModuleFile.h | 3 +++ 2 files changed, 12 insertions(+) diff --git a/lib/Serialization/Deserialization.cpp b/lib/Serialization/Deserialization.cpp index 636ee240ea282..78becc557ae40 100644 --- a/lib/Serialization/Deserialization.cpp +++ b/lib/Serialization/Deserialization.cpp @@ -179,6 +179,15 @@ void ModuleFile::fatal(llvm::Error error) const { Core->fatal(diagnoseFatal(std::move(error))); } +SourceLoc ModuleFile::getSourceLoc() const { + auto &SourceMgr = getContext().Diags.SourceMgr; + auto filename = getModuleFilename(); + auto bufferID = SourceMgr.getIDForBufferIdentifier(filename); + if (!bufferID) + bufferID = SourceMgr.addMemBufferCopy(StringRef(), filename); + return SourceMgr.getLocForBufferStart(*bufferID); +} + llvm::Error ModuleFile::diagnoseFatal(llvm::Error error) const { if (FileContext) getContext().Diags.diagnose(SourceLoc(), diag::serialization_fatal, diff --git a/lib/Serialization/ModuleFile.h b/lib/Serialization/ModuleFile.h index 29c9ad27431c3..b6c13db8c1ede 100644 --- a/lib/Serialization/ModuleFile.h +++ b/lib/Serialization/ModuleFile.h @@ -667,6 +667,9 @@ class ModuleFile getTransitiveLoadingBehavior(const Dependency &dependency, bool forTestable) const; + /// Generate a \c SourceLoc pointing at the loaded swiftmodule file. + SourceLoc getSourceLoc() const; + /// Returns `true` if there is a buffer that might contain source code where /// other parts of the compiler could have emitted diagnostics, to indicate /// that the object must be kept alive as long as the diagnostics exist. From 144d7eb8a000a2cd47a6cbb90de36bc683e3da69 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexis=20Laferri=C3=A8re?= Date: Mon, 1 May 2023 12:18:23 -0700 Subject: [PATCH 4/4] [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. --- include/swift/AST/DiagnosticsSema.def | 21 +++ lib/Serialization/Deserialization.cpp | 145 ++++++++++++++---- lib/Serialization/DeserializationErrors.h | 126 ++++++++++++--- lib/Serialization/ModuleFile.h | 6 +- test/Serialization/Recovery/crash-xref.swift | 11 +- test/Serialization/modularization-error.swift | 58 +++++++ 6 files changed, 309 insertions(+), 58 deletions(-) create mode 100644 test/Serialization/modularization-error.swift diff --git a/include/swift/AST/DiagnosticsSema.def b/include/swift/AST/DiagnosticsSema.def index 5c40e4118fcbd..81c4839089825 100644 --- a/include/swift/AST/DiagnosticsSema.def +++ b/include/swift/AST/DiagnosticsSema.def @@ -872,6 +872,27 @@ ERROR(need_hermetic_seal_to_import_module,none, "current compilation does not have -experimental-hermetic-seal-at-link", (Identifier)) +ERROR(modularization_issue_decl_moved,Fatal, + "reference to %select{top-level|type}0 %1 broken by a context change; " + "%1 was expected to be in %2, but now a candidate is found only in %3", + (bool, DeclName, Identifier, Identifier)) +ERROR(modularization_issue_decl_type_changed,Fatal, + "reference to %select{top-level|type}0 %1 broken by a context change; " + "the details of %1 %select{from %2|}5 changed since building '%3'" + "%select{|, it was in %2 and is now found in %4}5", + (bool, DeclName, Identifier, StringRef, Identifier, bool)) +ERROR(modularization_issue_decl_not_found,Fatal, + "reference to %select{top-level|type}0 %1 broken by a context change; " + "%1 is not found, it was expected to be in %2", + (bool, DeclName, Identifier)) + +NOTE(modularization_issue_side_effect_extension_error,none, + "could not deserialize extension", + ()) +NOTE(modularization_issue_side_effect_type_error,none, + "could not deserialize type for %0", + (DeclName)) + ERROR(reserved_member_name,none, "type member must not be named %0, since it would conflict with the" " 'foo.%1' expression", (DeclName, StringRef)) diff --git a/lib/Serialization/Deserialization.cpp b/lib/Serialization/Deserialization.cpp index 78becc557ae40..e14225ae81cff 100644 --- a/lib/Serialization/Deserialization.cpp +++ b/lib/Serialization/Deserialization.cpp @@ -157,6 +157,8 @@ const char InvalidRecordKindError::ID = '\0'; void InvalidRecordKindError::anchor() {} const char UnsafeDeserializationError::ID = '\0'; void UnsafeDeserializationError::anchor() {} +const char ModularizationError::ID = '\0'; +void ModularizationError::anchor() {} static llvm::Error consumeErrorIfXRefNonLoadedModule(llvm::Error &&error); @@ -188,18 +190,99 @@ SourceLoc ModuleFile::getSourceLoc() const { return SourceMgr.getLocForBufferStart(*bufferID); } +void +ModularizationError::diagnose(const ModuleFile *MF, + DiagnosticBehavior limit) const { + auto &ctx = MF->getContext(); + + auto diagnoseError = [&](Kind errorKind) { + switch (errorKind) { + case Kind::DeclMoved: + return ctx.Diags.diagnose(MF->getSourceLoc(), diag::modularization_issue_decl_moved, + declIsType, name, expectedModuleName, + foundModuleName); + case Kind::DeclKindChanged: + return + ctx.Diags.diagnose(MF->getSourceLoc(), diag::modularization_issue_decl_type_changed, + declIsType, name, expectedModuleName, + referencedFromModuleName, foundModuleName, + foundModuleName != expectedModuleName); + case Kind::DeclNotFound: + return ctx.Diags.diagnose(MF->getSourceLoc(), diag::modularization_issue_decl_not_found, + declIsType, name, expectedModuleName); + } + llvm_unreachable("Unhandled ModularizationError::Kind in switch."); + }; + + auto inFlight = diagnoseError(errorKind); + inFlight.limitBehavior(limit); + inFlight.flush(); + + // We could pass along the `path` information through notes. + // However, for a top-level decl a path would just duplicate the + // expected module name and the decl name from the diagnostic. +} + +void TypeError::diagnose(const ModuleFile *MF) const { + MF->getContext().Diags.diagnose(MF->getSourceLoc(), + diag::modularization_issue_side_effect_type_error, + name); +} + +void ExtensionError::diagnose(const ModuleFile *MF) const { + MF->getContext().Diags.diagnose(MF->getSourceLoc(), + diag::modularization_issue_side_effect_extension_error); +} + llvm::Error ModuleFile::diagnoseFatal(llvm::Error error) const { - if (FileContext) - getContext().Diags.diagnose(SourceLoc(), diag::serialization_fatal, - Core->Name); + + auto &ctx = getContext(); + if (FileContext) { + if (ctx.LangOpts.EnableDeserializationRecovery) { + // Attempt to report relevant errors as diagnostics. + // At this time, only ModularizationErrors are reported directly. They + // can get here either directly or as underlying causes to a TypeError or + // and ExtensionError. + auto handleModularizationError = + [&](const ModularizationError &modularError) -> llvm::Error { + modularError.diagnose(this); + return llvm::Error::success(); + }; + error = llvm::handleErrors(std::move(error), + handleModularizationError, + [&](TypeError &typeError) -> llvm::Error { + if (typeError.diagnoseUnderlyingReason(handleModularizationError)) { + typeError.diagnose(this); + return llvm::Error::success(); + } + return llvm::make_error(std::move(typeError)); + }, + [&](ExtensionError &extError) -> llvm::Error { + if (extError.diagnoseUnderlyingReason(handleModularizationError)) { + extError.diagnose(this); + return llvm::Error::success(); + } + return llvm::make_error(std::move(extError)); + }); + + // If no error is left, it was reported as a diagnostic. There's no + // need to crash. + if (!error) + return llvm::Error::success(); + } + + // General deserialization failure message. + ctx.Diags.diagnose(getSourceLoc(), diag::serialization_fatal, Core->Name); + } // Unless in the debugger, crash. ModuleFileSharedCore::fatal() calls abort(). // This allows aggregation of crash logs for compiler development, but in a // long-running process like LLDB this is undesirable. Only abort() if not in // the debugger. - if (!getContext().LangOpts.DebuggerSupport) + if (!ctx.LangOpts.DebuggerSupport) Core->fatal(std::move(error)); - // Otherwise, augment the error with contextual information and pass it back. + // Otherwise, augment the error with contextual information at this point + // of failure and pass it back to be reported later. std::string msg; { llvm::raw_string_ostream os(msg); @@ -1789,18 +1872,21 @@ ModuleFile::resolveCrossReference(ModuleID MID, uint32_t pathLen) { // is mostly for compiler engineers to understand a likely solution at a // quick glance. SmallVector strScratch; - SmallVector notes; - auto declName = getXRefDeclNameForError(); + + auto errorKind = ModularizationError::Kind::DeclNotFound; + Identifier foundIn; + bool isType = false; + if (recordID == XREF_TYPE_PATH_PIECE || recordID == XREF_VALUE_PATH_PIECE) { auto &ctx = getContext(); for (auto nameAndModule : ctx.getLoadedModules()) { - auto baseModule = nameAndModule.second; + auto otherModule = nameAndModule.second; IdentifierID IID; IdentifierID privateDiscriminator = 0; TypeID TID = 0; - bool isType = (recordID == XREF_TYPE_PATH_PIECE); + isType = (recordID == XREF_TYPE_PATH_PIECE); bool inProtocolExt = false; bool importedFromClang = false; bool isStatic = false; @@ -1824,10 +1910,10 @@ ModuleFile::resolveCrossReference(ModuleID MID, uint32_t pathLen) { values.clear(); if (privateDiscriminator) { - baseModule->lookupMember(values, baseModule, name, + otherModule->lookupMember(values, otherModule, name, getIdentifier(privateDiscriminator)); } else { - baseModule->lookupQualified(baseModule, DeclNameRef(name), + otherModule->lookupQualified(otherModule, DeclNameRef(name), NL_QualifiedDefault, values); } @@ -1841,30 +1927,31 @@ ModuleFile::resolveCrossReference(ModuleID MID, uint32_t pathLen) { // Found a full match in a different module. It should be a different // one because otherwise it would have succeeded on the first search. // This is usually caused by the use of poorly modularized headers. - auto line = "There is a matching '" + - declName.getString(strScratch).str() + - "' in module '" + - std::string(nameAndModule.first.str()) + - "'. If this is imported from clang, please make sure " + - "the header is part of a single clang module."; - notes.emplace_back(line); + errorKind = ModularizationError::Kind::DeclMoved; + foundIn = otherModule->getName(); + break; } else if (hadAMatchBeforeFiltering) { // Found a match that was filtered out. This may be from the same // expected module if there's a type difference. This can be caused // by the use of different Swift language versions between a library // with serialized SIL and a client. - auto line = "'" + - declName.getString(strScratch).str() + - "' in module '" + - std::string(nameAndModule.first.str()) + - "' was filtered out."; - notes.emplace_back(line); + errorKind = ModularizationError::Kind::DeclKindChanged; + foundIn = otherModule->getName(); + break; } } } - return llvm::make_error("top-level value not found", pathTrace, - declName, notes); + auto declName = getXRefDeclNameForError(); + auto expectedIn = baseModule->getName(); + auto referencedFrom = getName(); + return llvm::make_error(declName, + isType, + errorKind, + expectedIn, + referencedFrom, + foundIn, + pathTrace); } // Filters for values discovered in the remaining path pieces. @@ -7265,7 +7352,8 @@ static llvm::Error consumeErrorIfXRefNonLoadedModule(llvm::Error &&error) { // implementation-only import hiding types and decls. // rdar://problem/60291019 if (error.isA() || - error.isA()) { + error.isA() || + error.isA()) { consumeError(std::move(error)); return llvm::Error::success(); } @@ -7278,7 +7366,8 @@ static llvm::Error consumeErrorIfXRefNonLoadedModule(llvm::Error &&error) { auto *TE = static_cast(errorInfo.get()); if (TE->underlyingReasonIsA() || - TE->underlyingReasonIsA()) { + TE->underlyingReasonIsA() || + TE->underlyingReasonIsA()) { consumeError(std::move(errorInfo)); return llvm::Error::success(); } diff --git a/lib/Serialization/DeserializationErrors.h b/lib/Serialization/DeserializationErrors.h index bf762bfcc40a5..b06c8358ecebe 100644 --- a/lib/Serialization/DeserializationErrors.h +++ b/lib/Serialization/DeserializationErrors.h @@ -24,6 +24,17 @@ namespace swift { namespace serialization { +[[nodiscard]] +static inline std::unique_ptr +takeErrorInfo(llvm::Error error) { + std::unique_ptr result; + llvm::handleAllErrors(std::move(error), + [&](std::unique_ptr info) { + result = std::move(info); + }); + return result; +} + /// This error is generated by ModuleFile::diagnoseFatal(). All /// FatalDeserializationError has already been added to the DiagnosticsEngine /// upon creation. @@ -289,25 +300,16 @@ class XRefError : public llvm::ErrorInfo { XRefTracePath path; const char *message; - SmallVector notes; public: template - XRefError(const char (&message)[N], XRefTracePath path, DeclName name, - SmallVector notes = {}) - : path(path), message(message), notes(notes) { + XRefError(const char (&message)[N], XRefTracePath path, DeclName name) + : path(path), message(message) { this->name = name; } void log(raw_ostream &OS) const override { OS << message << " (" << name << ")\n"; path.print(OS); - - if (!notes.empty()) { - OS << "Notes:\n"; - for (auto &line : notes) { - OS << "* " << line << "\n"; - } - } } std::error_code convertToErrorCode() const override { @@ -335,6 +337,75 @@ class XRefNonLoadedModuleError : } }; +/// Project issue affecting modules. Usually a change in the context between +/// the time a module was built and when it was imported. +class ModularizationError : public llvm::ErrorInfo { + friend ErrorInfo; + static const char ID; + void anchor() override; + +public: + enum class Kind { + DeclMoved, + DeclKindChanged, + DeclNotFound + }; + +private: + DeclName name; + bool declIsType; + Kind errorKind; + Identifier expectedModuleName; + StringRef referencedFromModuleName; + Identifier foundModuleName; + XRefTracePath path; + +public: + explicit ModularizationError(DeclName name, bool declIsType, Kind errorKind, + Identifier expectedModuleName, + StringRef referencedFromModuleName, + Identifier foundModuleName, + XRefTracePath path): + name(name), declIsType(declIsType), errorKind(errorKind), + expectedModuleName(expectedModuleName), + referencedFromModuleName(referencedFromModuleName), + foundModuleName(foundModuleName), path(path) {} + + void diagnose(const ModuleFile *MF, + DiagnosticBehavior limit = DiagnosticBehavior::Fatal) const; + + void log(raw_ostream &OS) const override { + OS << "modularization issue on '" << name << "', reference from '"; + OS << referencedFromModuleName << "' not resolvable: "; + switch (errorKind) { + case Kind::DeclMoved: + OS << "expected in '" << expectedModuleName << "' but found in '"; + OS << foundModuleName << "'"; + break; + case Kind::DeclKindChanged: + OS << "decl details changed between what was imported from '"; + OS << expectedModuleName << "' and what is now imported from '"; + OS << foundModuleName << "'"; + break; + case Kind::DeclNotFound: + OS << "not found, expected in '" << expectedModuleName << "'"; + break; + } + OS << "\n"; + path.print(OS); + } + + std::error_code convertToErrorCode() const override { + return llvm::inconvertibleErrorCode(); + } + + bool isA(const void *const ClassID) const override { + return ClassID == classID() || ErrorInfoBase::isA(ClassID); + } + + static const void *classID() { return &ID; } +}; + class OverrideError : public llvm::ErrorInfo { private: @@ -381,6 +452,24 @@ class ErrorWithUnderlyingReason { } } + // Returns \c true if the error was diagnosed. + template + bool diagnoseUnderlyingReason(HandlerT &&handler) { + if (underlyingReason && + llvm::ErrorHandlerTraits::appliesTo(*underlyingReason)) { + auto error = llvm::ErrorHandlerTraits::apply( + std::forward(handler), + std::move(underlyingReason)); + if (!error) { + // The underlying reason was diagnosed. + return true; + } else { + underlyingReason = takeErrorInfo(std::move(error)); + return false; + } + } + return false; + } }; class TypeError : public llvm::ErrorInfo, @@ -398,6 +487,8 @@ class TypeError : public llvm::ErrorInfo, this->numVTableEntries = numVTableEntries; } + void diagnose(const ModuleFile *MF) const; + void log(raw_ostream &OS) const override { OS << "Could not deserialize type for '" << name << "'"; ErrorWithUnderlyingReason::log(OS); @@ -418,6 +509,8 @@ class ExtensionError : public llvm::ErrorInfo, explicit ExtensionError(std::unique_ptr reason) : ErrorWithUnderlyingReason(std::move(reason)) {} + void diagnose(const ModuleFile *MF) const; + void log(raw_ostream &OS) const override { OS << "could not deserialize extension"; ErrorWithUnderlyingReason::log(OS); @@ -522,17 +615,6 @@ class UnsafeDeserializationError : public llvm::ErrorInfo -takeErrorInfo(llvm::Error error) { - std::unique_ptr result; - llvm::handleAllErrors(std::move(error), - [&](std::unique_ptr info) { - result = std::move(info); - }); - return result; -} - class PrettyStackTraceModuleFile : public llvm::PrettyStackTraceEntry { const char *Action; const ModuleFile &MF; diff --git a/lib/Serialization/ModuleFile.h b/lib/Serialization/ModuleFile.h index b6c13db8c1ede..aabc132c150d2 100644 --- a/lib/Serialization/ModuleFile.h +++ b/lib/Serialization/ModuleFile.h @@ -352,7 +352,11 @@ class ModuleFile } /// Enrich \c error with contextual information, emits a fatal diagnostic in - /// the ASTContext's DignosticsEngine, and return the augmented error. + /// the ASTContext's DiagnosticsEngine, and return the augmented error. + /// + /// The `diagnoseFatal` methods return only in LLDB where high error + /// tolerance is expected, or when hitting a project error. During normal + /// compilation, most calls won't return and lead to a compiler crash. llvm::Error diagnoseFatal(llvm::Error error) const; /// Emit a generic deserialization error via \c diagnoseFatal(). diff --git a/test/Serialization/Recovery/crash-xref.swift b/test/Serialization/Recovery/crash-xref.swift index df50edd22d21f..fc329c498ee42 100644 --- a/test/Serialization/Recovery/crash-xref.swift +++ b/test/Serialization/Recovery/crash-xref.swift @@ -1,5 +1,6 @@ /// Test xref error description by removing a type from a module after building -/// a client. +/// a client. This test disables deserialization recovery to hit a crash +/// reliably. // RUN: %empty-directory(%t) // RUN: %empty-directory(%t/partials) // RUN: %empty-directory(%t/normal) @@ -34,11 +35,9 @@ // NORMALFAILURE-LABEL: *** new swiftmodule files from the SDK and keep only swiftinterfaces. *** // NORMALFAILURE-NEXT: module 'Client', builder version {{.*}}', built from source, resilient, loaded from // NORMALFAILURE-NEXT: Could not deserialize type for 'foo()' -// NORMALFAILURE-NEXT: Caused by: top-level value not found +// NORMALFAILURE-NEXT: Caused by: modularization issue on 'SomeType', reference from 'Client' not resolvable: expected in 'A' but found in 'B' // NORMALFAILURE-NEXT: Cross-reference to module 'A' // NORMALFAILURE-NEXT: ... SomeType -// NORMALFAILURE-NEXT: Notes: -// NORMALFAILURE-NEXT: * There is a matching 'SomeType' in module 'B'. If this is imported from clang, please make sure the header is part of a single clang module. // RUN: cat %t/error_stderr | %FileCheck %s -check-prefixes=ALLOWFAILURE // ALLOWFAILURE-LABEL: *** DESERIALIZATION FAILURE *** @@ -46,11 +45,9 @@ // ALLOWFAILURE-LABEL: *** new swiftmodule files from the SDK and keep only swiftinterfaces. *** // ALLOWFAILURE-NEXT: module 'Client', builder version {{.*}}', built from source, non-resilient, built with -experimental-allow-module-with-compiler-errors, loaded from // ALLOWFAILURE-NEXT: Could not deserialize type for 'foo()' -// ALLOWFAILURE-NEXT: Caused by: top-level value not found +// ALLOWFAILURE-NEXT: Caused by: modularization issue on 'SomeType', reference from 'Client' not resolvable: expected in 'A' but found in 'B' // ALLOWFAILURE-NEXT: Cross-reference to module 'A' // ALLOWFAILURE-NEXT: ... SomeType -// ALLOWFAILURE-NEXT: Notes: -// ALLOWFAILURE-NEXT: * There is a matching 'SomeType' in module 'B'. If this is imported from clang, please make sure the header is part of a single clang module. /// Test a swiftmodule rebuilt from the swiftinterface. // RUN: not --crash %target-swift-frontend -emit-sil %t/cache/Client-*.swiftmodule -module-name Client -I %t/partials -disable-deserialization-recovery 2> %t/cache_stderr diff --git a/test/Serialization/modularization-error.swift b/test/Serialization/modularization-error.swift new file mode 100644 index 0000000000000..2c0ae39b05bec --- /dev/null +++ b/test/Serialization/modularization-error.swift @@ -0,0 +1,58 @@ +/// Simulate typical modularization issues using Swift modules. +// RUN: %empty-directory(%t) +// RUN: split-file %s %t + +/// Compile two library modules A and B, and a client. +// RUN: %target-swift-frontend %t/LibOriginal.swift -emit-module-path %t/A.swiftmodule -module-name A +// RUN: %target-swift-frontend %t/Empty.swift -emit-module-path %t/B.swiftmodule -module-name B +// RUN: %target-swift-frontend %t/LibWithXRef.swift -emit-module-path %t/LibWithXRef.swiftmodule -module-name LibWithXRef -I %t + +/// Move MyType from A to B. +// RUN: %target-swift-frontend %t/Empty.swift -emit-module-path %t/A.swiftmodule -module-name A +// RUN: %target-swift-frontend %t/LibOriginal.swift -emit-module-path %t/B.swiftmodule -module-name B +// RUN: not %target-swift-frontend -emit-sil %t/LibWithXRef.swiftmodule -module-name LibWithXRef -I %t 2>&1 \ +// RUN: | %FileCheck --check-prefixes CHECK,CHECK-MOVED %s +// CHECK-MOVED: LibWithXRef.swiftmodule:1:1: error: reference to type 'MyType' broken by a context change; 'MyType' was expected to be in 'A', but now a candidate is found only in 'B' + +/// Change MyType into a function. +// RUN: %target-swift-frontend %t/LibTypeChanged.swift -emit-module-path %t/A.swiftmodule -module-name A +// RUN: %target-swift-frontend %t/Empty.swift -emit-module-path %t/B.swiftmodule -module-name B +// RUN: not %target-swift-frontend -emit-sil %t/LibWithXRef.swiftmodule -module-name LibWithXRef -I %t 2>&1 \ +// RUN: | %FileCheck --check-prefixes CHECK,CHECK-KIND-CHANGED %s +// CHECK-KIND-CHANGED: LibWithXRef.swiftmodule:1:1: error: reference to type 'MyType' broken by a context change; the details of 'MyType' from 'A' changed since building 'LibWithXRef' + +/// Change MyType into a function and move it. +// RUN: %target-swift-frontend %t/Empty.swift -emit-module-path %t/A.swiftmodule -module-name A +// RUN: %target-swift-frontend %t/LibTypeChanged.swift -emit-module-path %t/B.swiftmodule -module-name B +// RUN: not %target-swift-frontend -emit-sil %t/LibWithXRef.swiftmodule -module-name LibWithXRef -I %t 2>&1 \ +// RUN: | %FileCheck --check-prefixes CHECK,CHECK-KIND-CHANGED-AND-MOVED %s +// CHECK-KIND-CHANGED-AND-MOVED: LibWithXRef.swiftmodule:1:1: error: reference to type 'MyType' broken by a context change; the details of 'MyType' changed since building 'LibWithXRef', it was in 'A' and is now found in 'B' + +/// Remove MyType from all imported modules. +// RUN: %target-swift-frontend %t/Empty.swift -emit-module-path %t/A.swiftmodule -module-name A +// RUN: %target-swift-frontend %t/Empty.swift -emit-module-path %t/B.swiftmodule -module-name B +// RUN: not %target-swift-frontend -emit-sil %t/LibWithXRef.swiftmodule -module-name LibWithXRef -I %t 2>&1 \ +// RUN: | %FileCheck --check-prefixes CHECK,CHECK-NOT-FOUND %s +// CHECK-NOT-FOUND: LibWithXRef.swiftmodule:1:1: error: reference to type 'MyType' broken by a context change; 'MyType' is not found, it was expected to be in 'A' + +// CHECK: LibWithXRef.swiftmodule:1:1: note: could not deserialize extension + +//--- Empty.swift +//--- LibOriginal.swift +public struct MyType { + public init() {} +} + +//--- LibTypeChanged.swift +/// Make it a function to fail filtering. +public func MyType() {} + +//--- LibWithXRef.swift +import A +import B + +public protocol Proto {} +extension MyType : Proto {} + +@available(SwiftStdlib 5.1, *) +public func foo() -> some Proto { return MyType() }