Skip to content

[Serialization] Report modularization breaks as proper diagnostics #65713

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
May 18, 2023

Conversation

xymus
Copy link
Contributor

@xymus xymus commented May 5, 2023

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 and if they trigger a fatal failure.


Next I'll look to add a mode to remark on any modularization issue detected, not only the fatal ones. And a mode to workaround some modules issues and recover when a type moves between modules.

@xymus xymus requested review from hborla, slavapestov and xedin as code owners May 5, 2023 19:32
@xymus xymus marked this pull request as draft May 5, 2023 19:32
@xymus
Copy link
Contributor Author

xymus commented May 5, 2023

@swift-ci Please smoke test

@xymus xymus force-pushed the report-modularization-breaks branch 3 times, most recently from 0829c68 to 2c43e2b Compare May 11, 2023 21:43
@xymus xymus changed the title [Serialization] Report detected modularization breaks [Serialization] Report modularization breaks as proper diagnostics May 11, 2023
@xymus
Copy link
Contributor Author

xymus commented May 11, 2023

@swift-ci Please smoke test

@xymus xymus marked this pull request as ready for review May 11, 2023 21:47
@xymus xymus requested review from bnbarham and tshortli May 11, 2023 21:47
"module reference to top-level %0 broken by a context change, "
"module '%2' references %0 in %1, but it cannot be found in this build. "
"Audit related headers or uninstall SDK roots.",
(DeclName, Identifier, StringRef, Identifier))
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@tshortli @bnbarham I'd be interest in having your opinions on these three main diagnostics. You can also see them in the test.

Do you have suggestions on how to make them more actionable or simply more understandable by project owners?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like the general direction of providing more complete diagnostics for these failures. My main criticism is that I'm not sure the specific calls to action here are applicable. Suggesting uninstallation of roots may not help because presumably the root is needed. Suggesting auditing headers is a bit vague; I'm not sure what I would personally do to follow through on that suggestion.

If the problem is that the .swiftmodule is stale but the build system/compiler is unable to detect that it ought to rebuild it, maybe what we should be suggesting is to remove the potentially stale .swiftmodule?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggesting to delete the stale swiftmodule makes perfect sense, it would fix it in most roots case.

I'm currently looking into pointing the diagnostics on the swiftmodule file with the broken reference instead of the current <unknown>:0:. By itself it should help to put into doubts the SDK or recently installed roots.

The case of auditing headers may be too general to be useful. I'm mostly thinking about the Xcc issue but describing that in a diagnostic is a challenge. I'm considerong moving the recommended solutions to separate notes, so we could make them longer if it helps.

Copy link
Contributor

@bnbarham bnbarham May 12, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is deleting the swiftmodule actually always an option? It could be a distributed binary module without library evolution enabled couldn't it?

I'm considerong moving the recommended solutions to separate notes

This would be good IMO. I think it'd be better if the error should be a short summary, with the rest in notes.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is deleting the swiftmodule actually always an option? It could be a distributed binary module without library evolution enabled couldn't it?

Yeah, we should only suggest it when the module is resilient I suppose.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I reworked those diagnostics to make them simpler and shorter. They are now reported on the swiftmodule file making it more intuitive to see where is the broken reference.

I removed the hints to solutions and potential problems from this PR. I plan on replacing them with notes and further analysis of the context, i.e. hint that the swiftmodule is outdated only when resilient and distributed.

@xymus xymus force-pushed the report-modularization-breaks branch from 2c43e2b to 4fd7d8e Compare May 11, 2023 23:23
@xymus
Copy link
Contributor Author

xymus commented May 11, 2023

@swift-ci Please smoke test

// RUN: %target-swift-frontend %t/Empty.swift -emit-module-path %t/B.swiftmodule -module-name B
// RUN: not %target-swift-frontend -emit-sil %t/Client.swiftmodule -module-name Client -I %t 2>&1 \
// RUN: | %FileCheck --check-prefixes CHECK,CHECK-TYPE-CHANGED %s
// CHECK-TYPE-CHANGED: <unknown>:0: error: module reference to top-level 'MyType' broken by a context change, its type changed since building 'Client', it was in 'A' now found in 'A'. Ensure Swift language version match between modules or uninstall SDK roots.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it possible to show the type info here? e.g. it was a struct but now a func in module 'A'

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I should be able to. Since it failed to deserialize we pretty much just know if it's a type or if it's a ValueDecl. In most cases it's a type so having that precision should help.

@@ -872,6 +872,31 @@ ERROR(need_hermetic_seal_to_import_module,none,
"current compilation does not have -experimental-hermetic-seal-at-link",
(Identifier))

ERROR(modularization_issue_moved,Fatal,
"module reference to top-level %0 broken by a context change, "
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

would read better with ; instead of , after 'context change'

@@ -872,6 +872,31 @@ ERROR(need_hermetic_seal_to_import_module,none,
"current compilation does not have -experimental-hermetic-seal-at-link",
(Identifier))

ERROR(modularization_issue_moved,Fatal,
Copy link
Contributor

@elsh elsh May 12, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

modularization_issue_decl_moved might be more clear

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added decl_ to all of them and the related diagnostics.

DiagnosticBehavior limit = DiagnosticBehavior::Fatal) const;

void log(raw_ostream &OS) const override {
OS << "modularization issue on '" << name << "', reference from '";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

referenced from

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I left this as is because it reads reference from X not resolvable. In theory nobody should see this error as it is replaced by the proper diagnostic.

(DeclName, Identifier, StringRef, Identifier))
ERROR(modularization_issue_not_found,Fatal,
"module reference to top-level %0 broken by a context change, "
"module '%2' references %0 in %1, but it cannot be found in this build. "
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

%0 cannot be found in this build


public:
enum class Kind {
Moved,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

DeclMoved

enum class Kind {
Moved,
TypeChanged,
NotFound
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

DeclNotFound

"Audit related headers or uninstall SDK roots.",
(DeclName, Identifier, StringRef, Identifier))

NOTE(modularization_issue_effect_extension_error,none,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what does 'effect' here mean?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Replaced it with side_effect, hopefully it's clearer.

@@ -359,82 +429,111 @@ class OverrideError : public llvm::ErrorInfo<OverrideError,
}
};

class TypeError : public llvm::ErrorInfo<TypeError, DeclDeserializationError> {
// Service class for errors with an underlying cause.
class CausedError {
Copy link
Contributor

@elsh elsh May 12, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ErrorCause might be clearer to read

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed it to ErrorWithUnderlyingReason.

xymus added 2 commits May 16, 2023 15:58
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.
@xymus xymus force-pushed the report-modularization-breaks branch from 4fd7d8e to f62af93 Compare May 16, 2023 23:15
@xymus xymus requested review from artemcm and zoecarver as code owners May 16, 2023 23:15
@xymus xymus requested review from hyp and egorzhdan as code owners May 16, 2023 23:15
@xymus xymus force-pushed the report-modularization-breaks branch from f62af93 to 7f8bf0b Compare May 17, 2023 16:14
@@ -32,6 +32,7 @@
#include "swift/Config.h"
#include "swift/Localization/LocalizationFormat.h"
#include "swift/Parse/Lexer.h" // bad dependency
#include "../Serialization/ModuleFile.h"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it would be good to avoid this:

  1. It's a private header in Serialization. If we wanted to use it we really should move it out into the top level include.
  2. This introduces a circular dependency - Serialization depends on ClangImporter depends on AST. And AST depends on Serialization now.

Could we just add the new diagnose in Serialization and have it do the ModuleFile -> SourceLoc mapping instead? That would also avoid all the {} -> SourceLoc() changes.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point, I'm looking into it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added a ModuleFile::getSourceLoc() instead, it cleans up the PR nicely.

auto filename = file->getModuleFilename();
auto bufferID = SourceMgr.getIDForBufferIdentifier(filename);
if (!bufferID)
bufferID = SourceMgr.addMemBufferCopy(filename, filename);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any particular reason to have the filename in the fake buffer? For debugging? Doesn't really hurt I suppose.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Replaced it with an empty string.

xymus added 2 commits May 17, 2023 10:21
Generate a fake empty buffer to return a SourceLoc pointing to the
beginning of a swiftmodule file.
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.
@xymus xymus force-pushed the report-modularization-breaks branch from 7f8bf0b to 144d7eb Compare May 17, 2023 17:25
@xymus
Copy link
Contributor Author

xymus commented May 17, 2023

@swift-ci Please smoke test

(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'"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reading this diagnostic in context in the test case made me feel like the details of was a bit vague. Would the declaration kind of make sense? It would be especially helpful if we could print what kind of declaration we expected it to be and what it actually is but that seems like a stretch goal for another time.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's an improvement, I'll apply it in my next PR.

I took another look and we could print more details here. That diagnostics actually points out if there were candidate decls but all of them were filtered out. There's about a dozen reasons why a decl could be filtered out, most of them are simply to differentiate decls sharing a same name. However, we could modify that logic to collect the specific reasons that are triggered and show them in the diagnostics. I've seen that issue very rarely, about once a year, but when it does hit having more details could be critical. If time permits I'll come back to this diagnostic.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants