https://github.com/devajithvs updated 
https://github.com/llvm/llvm-project/pull/153945

>From 4ba8f5a6cd94a7c77da3ae4301d57f11c103d8ae Mon Sep 17 00:00:00 2001
From: Devajith Valaparambil Sreeramaswamy
 <devajith.valaparambil.sreeramasw...@cern.ch>
Date: Fri, 15 Aug 2025 17:08:07 +0200
Subject: [PATCH] [clang-repl] Remove invalid TopLevelStmtDecl from TU on parse
 failure

`ActOnStartTopLevelStmtDecl` immediately adds the new `TopLevelStmtDecl` to
the current `DeclContext`.

If parsing fails, the decl remains attached to the TU even though it has no
valid `Stmt`, leaving the AST in an invalid state.
---
 clang/lib/Parse/ParseDecl.cpp                 |  5 +-
 .../unittests/Interpreter/InterpreterTest.cpp | 49 +++++++++++++++++++
 2 files changed, 53 insertions(+), 1 deletion(-)

diff --git a/clang/lib/Parse/ParseDecl.cpp b/clang/lib/Parse/ParseDecl.cpp
index fd53cca5a13ff..59c5f4cacc36e 100644
--- a/clang/lib/Parse/ParseDecl.cpp
+++ b/clang/lib/Parse/ParseDecl.cpp
@@ -5696,8 +5696,11 @@ Parser::DeclGroupPtrTy Parser::ParseTopLevelStmtDecl() {
   TopLevelStmtDecl *TLSD = Actions.ActOnStartTopLevelStmtDecl(getCurScope());
   StmtResult R = ParseStatementOrDeclaration(Stmts, SubStmtCtx);
   Actions.ActOnFinishTopLevelStmtDecl(TLSD, R.get());
-  if (!R.isUsable())
+  if (!R.isUsable()) {
+    if (DeclContext *DC = TLSD->getDeclContext())
+      DC->removeDecl(TLSD); // unlink from TU
     R = Actions.ActOnNullStmt(Tok.getLocation());
+  }
 
   if (Tok.is(tok::annot_repl_input_end) &&
       Tok.getAnnotationValue() != nullptr) {
diff --git a/clang/unittests/Interpreter/InterpreterTest.cpp 
b/clang/unittests/Interpreter/InterpreterTest.cpp
index 8639fb668f1fe..e0a007f68227c 100644
--- a/clang/unittests/Interpreter/InterpreterTest.cpp
+++ b/clang/unittests/Interpreter/InterpreterTest.cpp
@@ -21,6 +21,8 @@
 #include "clang/Interpreter/Value.h"
 #include "clang/Sema/Lookup.h"
 #include "clang/Sema/Sema.h"
+#include "llvm/Support/FileSystem.h"
+#include "llvm/Support/Path.h"
 
 #include "llvm/TargetParser/Host.h"
 
@@ -91,6 +93,53 @@ TEST_F(InterpreterTest, IncrementalInputTopLevelDecls) {
   EXPECT_EQ("var2", DeclToString(*R2DeclRange.begin()));
 }
 
+TEST_F(InterpreterTest, BadIncludeDoesNotCorruptTU) {
+  // Create a temporary header that triggers an error at top level.
+  llvm::SmallString<256> HeaderPath;
+  ASSERT_FALSE(llvm::sys::fs::createTemporaryFile("interp-bad-include", "h",
+                                                  HeaderPath));
+  {
+    std::error_code EC;
+    llvm::raw_fd_ostream OS(HeaderPath, EC, llvm::sys::fs::OF_Text);
+    ASSERT_FALSE(EC);
+    OS << R"(error_here; // expected-error {{use of undeclared identifier}})";
+  }
+
+  llvm::SmallString<256> HeaderDir = llvm::sys::path::parent_path(HeaderPath);
+  llvm::SmallString<256> HeaderFile = llvm::sys::path::filename(HeaderPath);
+
+  // Set up the interpreter with the include path.
+  using Args = std::vector<const char *>;
+  Args ExtraArgs = {"-I",      HeaderDir.c_str(),
+                    "-Xclang", "-diagnostic-log-file",
+                    "-Xclang", "-"};
+
+  // Create the diagnostic engine with unowned consumer.
+  std::string DiagnosticOutput;
+  llvm::raw_string_ostream DiagnosticsOS(DiagnosticOutput);
+  DiagnosticOptions DiagOpts;
+  auto DiagPrinter =
+      std::make_unique<TextDiagnosticPrinter>(DiagnosticsOS, DiagOpts);
+
+  auto Interp = createInterpreter(ExtraArgs, DiagPrinter.get());
+  // This parse should fail because of an undeclared identifier, but should
+  // leave TU intact.
+  auto Err =
+      Interp->Parse("#include \"" + HeaderFile.str().str() + "\"").takeError();
+  using ::testing::HasSubstr;
+  EXPECT_THAT(DiagnosticOutput,
+              HasSubstr("use of undeclared identifier 'error_here'"));
+  EXPECT_EQ("Parsing failed.", llvm::toString(std::move(Err)));
+
+  // Inspect the TU, there must not be any TopLevelStmtDecl left.
+  TranslationUnitDecl *TU =
+      Interp->getCompilerInstance()->getASTContext().getTranslationUnitDecl();
+  EXPECT_EQ(0U, DeclsSize(TU));
+
+  // Cleanup temp file.
+  llvm::sys::fs::remove(HeaderPath);
+}
+
 TEST_F(InterpreterTest, Errors) {
   Args ExtraArgs = {"-Xclang", "-diagnostic-log-file", "-Xclang", "-"};
 

_______________________________________________
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to