Skip to content

EndSourceFile() for preprocessor before diagnostic client #145784

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

Open
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

dbartol
Copy link
Contributor

@dbartol dbartol commented Jun 25, 2025

The comment for DiagnosticConsumer::BeginSourceFile() states that "diagnostics with source range information are required to only be emitted in between BeginSourceFile() and EndSourceFile().". While working on some upcoming changes to the static analyzer, we hit some crashes when diagnostics were reported from the EndOfMainFile callback in the preprocessor. This turned out to be because FrontEndAction::EndSourceFile() notifies the diagnostic clients of the end of the source file before it notifies the preprocessor. Thus, the diagnostics from the preprocessor callback are reported when the diagnostic client is no longer expecting any diagnostics.

The fix is to swap the order of the EndSourceFile() calls so that the preprocessor is notified first.

I've added asserts to the ClangTidyDiagnosticConsumer to catch unexpected diagnostics outside of a source file. Before swapping the order of the calls as described above, this causes several failures in the clang-tidy regression tests. With the swap, there are no failures in check-all.

@llvmbot llvmbot added clang Clang issues not falling into any other category clang-tools-extra clang-tidy labels Jun 25, 2025
@llvmbot
Copy link
Member

llvmbot commented Jun 25, 2025

@llvm/pr-subscribers-clang

@llvm/pr-subscribers-clang-tools-extra

Author: Dave Bartolomeo (dbartol)

Changes

The comment for DiagnosticConsumer::BeginSourceFile() states that "diagnostics with source range information are required to only be emitted in between BeginSourceFile() and EndSourceFile().". While working on some upcoming changes to the static analyzer, we hit some crashes when diagnostics were reported from the EndOfMainFile callback in the preprocessor. This turned out to be because FrontEndAction::EndSourceFile() notifies the diagnostic clients of the end of the source file before it notifies the preprocessor. Thus, the diagnostics from the preprocessor callback are reported when the diagnostic client is no longer expecting any diagnostics.

The fix is to swap the order of the EndSourceFile() calls so that the preprocessor is notified first.

I've added asserts to the ClangTidyDiagnosticConsumer to catch unexpected diagnostics outside of a source file. Before swapping the order of the calls as described above, this causes several failures in the clang-tidy regression tests. With the swap, there are no failures in check-all.


Full diff: https://github.com/llvm/llvm-project/pull/145784.diff

3 Files Affected:

  • (modified) clang-tools-extra/clang-tidy/ClangTidyDiagnosticConsumer.cpp (+2)
  • (modified) clang-tools-extra/clang-tidy/ClangTidyDiagnosticConsumer.h (+16)
  • (modified) clang/lib/Frontend/FrontendAction.cpp (+5-3)
diff --git a/clang-tools-extra/clang-tidy/ClangTidyDiagnosticConsumer.cpp b/clang-tools-extra/clang-tidy/ClangTidyDiagnosticConsumer.cpp
index a71813a103df3..5f140703c47d8 100644
--- a/clang-tools-extra/clang-tidy/ClangTidyDiagnosticConsumer.cpp
+++ b/clang-tools-extra/clang-tidy/ClangTidyDiagnosticConsumer.cpp
@@ -360,6 +360,8 @@ getFixIt(const tooling::Diagnostic &Diagnostic, bool AnyFix) {
 
 void ClangTidyDiagnosticConsumer::HandleDiagnostic(
     DiagnosticsEngine::Level DiagLevel, const Diagnostic &Info) {
+  assert(InSourceFile || Info.getLocation().isInvalid());  // A diagnostic should not be reported outside of a BeginSourceFile()/EndSourceFile() pair if it has a source location.
+
   if (LastErrorWasIgnored && DiagLevel == DiagnosticsEngine::Note)
     return;
 
diff --git a/clang-tools-extra/clang-tidy/ClangTidyDiagnosticConsumer.h b/clang-tools-extra/clang-tidy/ClangTidyDiagnosticConsumer.h
index a8851e794f24b..2bf5a65fb8264 100644
--- a/clang-tools-extra/clang-tidy/ClangTidyDiagnosticConsumer.h
+++ b/clang-tools-extra/clang-tidy/ClangTidyDiagnosticConsumer.h
@@ -292,6 +292,21 @@ class ClangTidyDiagnosticConsumer : public DiagnosticConsumer {
   void HandleDiagnostic(DiagnosticsEngine::Level DiagLevel,
                         const Diagnostic &Info) override;
 
+  void BeginSourceFile(const LangOptions &LangOpts,
+                               const Preprocessor *PP = nullptr) override {
+    DiagnosticConsumer::BeginSourceFile(LangOpts, PP);
+    
+    assert(!InSourceFile);
+    InSourceFile = true;
+  }
+    
+  void EndSourceFile() override {
+    assert(InSourceFile);
+    InSourceFile = false;
+      
+    DiagnosticConsumer::EndSourceFile();
+  }
+
   // Retrieve the diagnostics that were captured.
   std::vector<ClangTidyError> take();
 
@@ -326,6 +341,7 @@ class ClangTidyDiagnosticConsumer : public DiagnosticConsumer {
   bool LastErrorRelatesToUserCode = false;
   bool LastErrorPassesLineFilter = false;
   bool LastErrorWasIgnored = false;
+  bool InSourceFile = false;
 };
 
 } // end namespace tidy
diff --git a/clang/lib/Frontend/FrontendAction.cpp b/clang/lib/Frontend/FrontendAction.cpp
index ef7ae27a2694a..f5996a8e1e88b 100644
--- a/clang/lib/Frontend/FrontendAction.cpp
+++ b/clang/lib/Frontend/FrontendAction.cpp
@@ -1243,13 +1243,15 @@ llvm::Error FrontendAction::Execute() {
 void FrontendAction::EndSourceFile() {
   CompilerInstance &CI = getCompilerInstance();
 
-  // Inform the diagnostic client we are done with this source file.
-  CI.getDiagnosticClient().EndSourceFile();
-
   // Inform the preprocessor we are done.
   if (CI.hasPreprocessor())
     CI.getPreprocessor().EndSourceFile();
 
+  // Inform the diagnostic client we are done with this source file.
+  // Do this after notifying the preprocessor, so that end-of-file preprocessor
+  // callbacks can report diagnostics.
+  CI.getDiagnosticClient().EndSourceFile();
+
   // Finalize the action.
   EndSourceFileAction();
 

Copy link

github-actions bot commented Jun 25, 2025

✅ With the latest revision this PR passed the C/C++ code formatter.

@dbartol dbartol changed the title EndSourceFile() for preprocessor before diagnstic client EndSourceFile() for preprocessor before diagnostic client Jun 25, 2025
@dbartol
Copy link
Contributor Author

dbartol commented Jun 25, 2025

Similar (but old) change: 88d99e4

The comment for `DiagnosticConsumer::BeginSourceFile()` states that "diagnostics with source range information are required to only be emitted in between BeginSourceFile() and EndSourceFile().". While working on some upcoming changes to the static analyzer, we hit some crashes when diagnostics were reported from the `EndOfMainFile` callback in the preprocessor. This turned out to be because `FrontEndAction::EndSourceFile()` notifies the diagnostic clients of the end of the source file before it notifies the preprocessor. Thus, the diagnostics from the preprocessor callback are reported when the diagnostic client is no longer expecting any diagnostics.

The fix is to swap the order of the `EndSourceFile()` calls so that the preprocessor is notified first.

I've added asserts to the `ClangTidyDiagnosticConsumer` to catch unexpected diagnostics outside of a source file. Before swapping the order of the calls as described above, this causes several failures in the clang-tidy regression tests. With the swap, there are no failures in `check-all`.
@dbartol dbartol force-pushed the fix/diagnostics-lifetime branch from e51d411 to 0be6598 Compare June 25, 2025 21:45
Copy link
Contributor

@zwuis zwuis left a comment

Choose a reason for hiding this comment

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

Please add a tag to the beginning of the PR title in square brackets like other PRs. E.g. [clang-tidy].

Comment on lines +363 to +367
assert(InSourceFile ||
Info.getLocation()
.isInvalid()); // A diagnostic should not be reported outside of a
// BeginSourceFile()/EndSourceFile() pair if it has
// a source location.
Copy link
Contributor

Choose a reason for hiding this comment

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

// ...
assert(...);

Do not write long comment on same line as the code.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
clang Clang issues not falling into any other category clang-tidy clang-tools-extra
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants