https://github.com/hongtaihu updated 
https://github.com/llvm/llvm-project/pull/209157

>From 05f7bd70c2af55a9374e7515aca89a3ae8f3efcc Mon Sep 17 00:00:00 2001
From: hongtaihu <[email protected]>
Date: Mon, 13 Jul 2026 20:13:13 +0800
Subject: [PATCH 1/2] [clang][FrontendAction] Extract
 EndSourceFileForDiagnostics to fix clangd assertion on preprocessed inputs
 (#197662)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

When handling an empty .i file (or -x cpp-output with .c/.cc/.m/.mm),
clangd crashes during shutdown with "Compiler instance has no
preprocessor!" because ParsedAST::~ParsedAST() detaches the preprocessor
before calling EndSourceFile(), but EndSourceFileAction() unconditionally
accesses it for preprocessed inputs.

The existing workaround in ~ParsedAST was already noted as messy — it
manually detached the preprocessor via setPreprocessor(nullptr) to
prevent duplicate EOF. This replaces that with a clean flag-based
approach:

- Extract PP.EndSourceFile() and DiagnosticClient.EndSourceFile() into
  a new public method EndSourceFileForDiagnostics(), guarded by an
  EndOfFileSignaled flag to prevent double notification.
- EndSourceFile() internally calls EndSourceFileForDiagnostics() then
  proceeds with the rest of cleanup — external behavior unchanged.
- clangd calls EndSourceFileForDiagnostics() at build time to flush
  clang-tidy diagnostics while retaining the AST. At destruction,
  EndSourceFile() sees the flag and skips repeated EOF, so the
  setPreprocessor(nullptr) hack is no longer needed.
- Since PP is never detached, EndSourceFileAction() safely accesses it.

Added ParsedASTTest.PreprocessedInputLifecycle covering both .i and
-x c++-cpp-output.
---
 clang-tools-extra/clangd/ParsedAST.cpp        | 19 +++++------------
 .../clangd/unittests/ParsedASTTests.cpp       | 11 ++++++++++
 clang/include/clang/Frontend/FrontendAction.h |  9 ++++++++
 clang/lib/Frontend/FrontendAction.cpp         | 21 +++++++++++++------
 4 files changed, 40 insertions(+), 20 deletions(-)

diff --git a/clang-tools-extra/clangd/ParsedAST.cpp 
b/clang-tools-extra/clangd/ParsedAST.cpp
index df56420cd7f24..5cfdc29d0a990 100644
--- a/clang-tools-extra/clangd/ParsedAST.cpp
+++ b/clang-tools-extra/clangd/ParsedAST.cpp
@@ -752,15 +752,12 @@ ParsedAST::build(llvm::StringRef Filename, const 
ParseInputs &Inputs,
     CTFinder.matchAST(Clang->getASTContext());
   }
 
-  // XXX: This is messy: clang-tidy checks flush some diagnostics at EOF.
-  // However Action->EndSourceFile() would destroy the ASTContext!
-  // So just inform the preprocessor of EOF, while keeping everything alive.
-  PP.EndSourceFile();
+  // Clang-tidy checks flush some diagnostics at EOF, but we need to retain the
+  // AST until ParsedAST is destroyed.
+  Action->EndSourceFileForDiagnostics();
   // UnitDiagsConsumer is local, we can not store it in CompilerInstance that
   // has a longer lifetime.
   Clang->getDiagnostics().setClient(new IgnoreDiagnostics);
-  // CompilerInstance won't run this callback, do it directly.
-  ASTDiags.EndSourceFile();
 
   std::vector<Diag> Diags = CompilerInvocationDiags;
   // FIXME: Also skip generation of diagnostics altogether to speed up ast
@@ -787,14 +784,8 @@ ParsedAST::ParsedAST(ParsedAST &&Other) = default;
 ParsedAST &ParsedAST::operator=(ParsedAST &&Other) = default;
 
 ParsedAST::~ParsedAST() {
-  if (Action) {
-    // We already notified the PP of end-of-file earlier, so detach it first.
-    // We must keep it alive until after EndSourceFile(), Sema relies on this.
-    auto PP = Clang->getPreprocessorPtr(); // Keep PP alive for now.
-    Clang->setPreprocessor(nullptr);       // Detach so we don't send EOF 
again.
-    Action->EndSourceFile();               // Destroy ASTContext and Sema.
-    // Now Sema is gone, it's safe for PP to go out of scope.
-  }
+  if (Action)
+    Action->EndSourceFile(); // Destroy ASTContext and Sema.
 }
 
 ASTContext &ParsedAST::getASTContext() { return Clang->getASTContext(); }
diff --git a/clang-tools-extra/clangd/unittests/ParsedASTTests.cpp 
b/clang-tools-extra/clangd/unittests/ParsedASTTests.cpp
index f9752d5d44f97..3b07f31230547 100644
--- a/clang-tools-extra/clangd/unittests/ParsedASTTests.cpp
+++ b/clang-tools-extra/clangd/unittests/ParsedASTTests.cpp
@@ -266,6 +266,17 @@ TEST(ParsedASTTest, NoCrashOnTokensWithTidyCheck) {
   EXPECT_EQ(T.expandedTokens().drop_back().back().text(SM), "}");
 }
 
+TEST(ParsedASTTest, PreprocessedInputLifecycle) {
+  TestTU TU;
+  TU.Filename = "TestTU.i";
+  TU.Code = "";
+  TU.build();
+
+  TU.Filename = "TestTU.cc";
+  TU.ExtraArgs = {"-x", "c++-cpp-output"};
+  TU.build();
+}
+
 TEST(ParsedASTTest, CanBuildInvocationWithUnknownArgs) {
   MockFS FS;
   FS.Files = {{testPath("foo.cpp"), "void test() {}"}};
diff --git a/clang/include/clang/Frontend/FrontendAction.h 
b/clang/include/clang/Frontend/FrontendAction.h
index 08c5fbc78f8ae..1b7f13f55a8d6 100644
--- a/clang/include/clang/Frontend/FrontendAction.h
+++ b/clang/include/clang/Frontend/FrontendAction.h
@@ -38,6 +38,7 @@ class FrontendAction {
   FrontendInputFile CurrentInput;
   std::unique_ptr<ASTUnit> CurrentASTUnit;
   CompilerInstance *Instance;
+  bool EndOfFileSignaled = false;
   friend class ASTMergeAction;
   friend class WrapperFrontendAction;
 
@@ -244,6 +245,14 @@ class FrontendAction {
   /// Set the source manager's main input file, and run the action.
   llvm::Error Execute();
 
+  /// Notify the preprocessor and diagnostic client that the source file has
+  /// ended, without destroying per-file frontend state.
+  ///
+  /// This is useful for clients that need end-of-file diagnostics while
+  /// retaining the AST. The matching EndSourceFile() performs the remaining
+  /// cleanup and will not notify them a second time.
+  void EndSourceFileForDiagnostics();
+
   /// Perform any per-file post processing, deallocate per-file
   /// objects, and run statistics and output file cleanup code.
   virtual void EndSourceFile();
diff --git a/clang/lib/Frontend/FrontendAction.cpp 
b/clang/lib/Frontend/FrontendAction.cpp
index 55e3877384622..f6f0ff344517f 100644
--- a/clang/lib/Frontend/FrontendAction.cpp
+++ b/clang/lib/Frontend/FrontendAction.cpp
@@ -831,6 +831,7 @@ bool FrontendAction::BeginSourceFile(CompilerInstance &CI,
   assert(!Input.isEmpty() && "Unexpected empty filename!");
   setCurrentInput(Input);
   setCompilerInstance(&CI);
+  EndOfFileSignaled = false;
 
   bool HasBegunSourceFile = false;
   bool ReplayASTFile = Input.getKind().getFormat() == InputKind::Precompiled &&
@@ -1362,17 +1363,25 @@ llvm::Error FrontendAction::Execute() {
   return llvm::Error::success();
 }
 
-void FrontendAction::EndSourceFile() {
+void FrontendAction::EndSourceFileForDiagnostics() {
   CompilerInstance &CI = getCompilerInstance();
 
-  // Inform the preprocessor we are done.
+  if (EndOfFileSignaled)
+    return;
+
+  // Inform the preprocessor and diagnostic client we are done with this source
+  // file. Do this in order so that end-of-file preprocessor callbacks can
+  // report diagnostics.
   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();
+  EndOfFileSignaled = true;
+}
+
+void FrontendAction::EndSourceFile() {
+  CompilerInstance &CI = getCompilerInstance();
+
+  EndSourceFileForDiagnostics();
 
   // Finalize the action.
   EndSourceFileAction();

>From cbad3d92dc1b50a03ae5f8648c79d3aa1c59adf7 Mon Sep 17 00:00:00 2001
From: hongtaihu <[email protected]>
Date: Wed, 15 Jul 2026 00:48:22 +0800
Subject: [PATCH 2/2] [clang] Fix infinite loop when parsing `::` in C mode
 (#208044)

In `isTypeSpecifierQualifier`, the `case tok::coloncolon` tries to
annotate `::` as a C++ scope token via `TryAnnotateTypeOrScopeToken`.
In C mode, `ParseOptionalCXXScopeSpecifier` is gated on `CPlusPlus`,
so the annotation does nothing and the token remains `::`.  The code
then recursively calls `isTypeSpecifierQualifier(getCurToken())`,
hitting the same `::` token again in an infinite loop.

Fix by returning `false` when the current token is still `::` after
the failed annotation attempt, cleanly answering "this is not a type
specifier qualifier" and breaking the cycle.

Fixes https://github.com/llvm/llvm-project/issues/208044
---
 clang/lib/Parse/ParseDecl.cpp         | 2 ++
 clang/test/Parser/coloncolon-c-mode.c | 4 ++++
 2 files changed, 6 insertions(+)
 create mode 100644 clang/test/Parser/coloncolon-c-mode.c

diff --git a/clang/lib/Parse/ParseDecl.cpp b/clang/lib/Parse/ParseDecl.cpp
index 3f41e7c5c6f0d..1ce8424606e51 100644
--- a/clang/lib/Parse/ParseDecl.cpp
+++ b/clang/lib/Parse/ParseDecl.cpp
@@ -5663,6 +5663,8 @@ bool Parser::isTypeSpecifierQualifier(const Token &Tok) {
 
     if (TryAnnotateTypeOrScopeToken())
       return true;
+    if (getCurToken().is(tok::coloncolon))
+      return false;
     return isTypeSpecifierQualifier(getCurToken());
 
     // GNU attributes support.
diff --git a/clang/test/Parser/coloncolon-c-mode.c 
b/clang/test/Parser/coloncolon-c-mode.c
new file mode 100644
index 0000000000000..832655825530a
--- /dev/null
+++ b/clang/test/Parser/coloncolon-c-mode.c
@@ -0,0 +1,4 @@
+// Regression test for GH-208044: clang should not hang when parsing
+// `::` in C mode.
+// RUN: %clang_cc1 -x c -std=c23 -fsyntax-only %s
+int sink = (::i);

_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to