https://github.com/conrade-ctc created 
https://github.com/llvm/llvm-project/pull/226285

When an input fails, the interpreter withdraws its declarations, but CodeGen 
keeps the IR that the input emitted before the error, and the next input 
continues in the same module:

```
clang-repl> int f() { return 1; } int g() { return no_such_name; }
error: use of undeclared identifier 'no_such_name'
clang-repl> int f() { return 1; }
error: definition with same mangled name '_Z1fv' as another definition
```

The leak is not only the name. The initializers and top-level statements of the 
failed input run with the next input, and its inline definitions stay as 
deferred decls.

This PR ends a failed input as a whole-file compile ends a unit with errors. 
`IncrementalParser` calls `HandleTranslationUnit`, so CodeGen drops the module, 
and then starts a new one. As for `Undo`, CodeGen keeps the lazy emission state 
of the earlier inputs, and drops the deferred decls, vtables and pending inline 
member definitions of the failed unit.

The parser also checks for errors after `HandleTranslationUnit`. CodeGen 
reports some errors only there, such as an alias to an undefined target. 
Before, the next input crashed.

I think this is the smallest change inside the existing CodeGen protocol. One 
visible change: a consumer now gets `HandleTranslationUnit` for a failed input, 
as it does for a normal compile with errors.

Tests: unit tests for each kind of leaked IR, for deferred decls, for an error 
from CodeGen and for `Undo`, and a lit test with the repro from the issue.

This change is independent of #226253.

Fixes #226252

🤖 Done with the help of [Claude Code](https://claude.com/claude-code) (Opus 
5.5, human in the loop)


>From 84fdfbcf409773dd4b5d7f95dab456cca1e076b8 Mon Sep 17 00:00:00 2001
From: Emery Conrad <[email protected]>
Date: Thu, 24 Sep 2026 13:21:56 -0500
Subject: [PATCH] [clang-repl] Drop the IR of a failed input

When an input fails, the interpreter withdraws its declarations, but
CodeGen keeps the IR that the input emitted before the error, and the
next input continues in the same module. A later definition of the same
name then fails with "definition with same mangled name". The
initializers and statements of the failed input run with the next
input, and its inline definitions stay as deferred decls.

End a failed input as a whole-file compile ends a unit with errors: call
HandleTranslationUnit, so CodeGen drops the module, then start a new
module. As for Undo, keep the lazy emission state of the earlier inputs:
move the deferred decls that the failed input referenced back to
DeferredDecls, and drop the deferred decls, vtables and pending inline
member definitions of the failed unit.

Check for errors after HandleTranslationUnit too. CodeGen reports some
errors only there, such as an alias to an undefined target. Before, the
next input crashed.

Fixes #226252

Co-developed-with-the-help-of: Claude Code (Opus 5.5, human in the loop)
---
 clang/lib/CodeGen/CodeGenModule.cpp           |  17 ++
 clang/lib/CodeGen/ModuleBuilder.cpp           |   1 +
 clang/lib/Interpreter/IncrementalAction.cpp   |   6 +-
 clang/lib/Interpreter/IncrementalAction.h     |   1 +
 clang/lib/Interpreter/IncrementalParser.cpp   |  28 ++--
 .../Interpreter/failed-input-drops-ir.cpp     |  32 ++++
 .../unittests/Interpreter/InterpreterTest.cpp | 151 ++++++++++++++++++
 7 files changed, 223 insertions(+), 13 deletions(-)
 create mode 100644 clang/test/Interpreter/failed-input-drops-ir.cpp

diff --git a/clang/lib/CodeGen/CodeGenModule.cpp 
b/clang/lib/CodeGen/CodeGenModule.cpp
index 7274a8588670f..929ce8a913cb2 100644
--- a/clang/lib/CodeGen/CodeGenModule.cpp
+++ b/clang/lib/CodeGen/CodeGenModule.cpp
@@ -1062,8 +1062,25 @@ void CodeGenModule::checkAliases() {
 
 void CodeGenModule::clear() {
   DeferredDeclsToEmit.clear();
+  if (Context.getLangOpts().IncrementalExtensions) {
+    // The next input starts a new module. It can still emit the deferred decls
+    // of the earlier inputs, but not those of the failed unit.
+    DeferredDecls.insert_range(EmittedDeferredDecls);
+    const TranslationUnitDecl *TU = Context.getTranslationUnitDecl();
+    auto InFailedUnit = [TU](const Decl *D) {
+      return D->getTranslationUnitDecl() == TU;
+    };
+    SmallVector<StringRef, 8> Dropped;
+    for (const auto &[Name, GD] : DeferredDecls)
+      if (InFailedUnit(GD.getDecl()))
+        Dropped.push_back(Name);
+    for (StringRef Name : Dropped)
+      DeferredDecls.erase(Name);
+    llvm::erase_if(DeferredVTables, InFailedUnit);
+  }
   EmittedDeferredDecls.clear();
   DeferredAnnotations.clear();
+  GlobalTopLevelStmtBlockInFlight = {nullptr, nullptr};
   if (OpenMPRuntime)
     OpenMPRuntime->clear();
 }
diff --git a/clang/lib/CodeGen/ModuleBuilder.cpp 
b/clang/lib/CodeGen/ModuleBuilder.cpp
index 0b00362487d2a..359c9f00b4a78 100644
--- a/clang/lib/CodeGen/ModuleBuilder.cpp
+++ b/clang/lib/CodeGen/ModuleBuilder.cpp
@@ -319,6 +319,7 @@ namespace {
       if (Diags.hasErrorOccurred()) {
         if (Builder)
           Builder->clear();
+        DeferredInlineMemberFuncDefs.clear();
         M.reset();
       }
 
diff --git a/clang/lib/Interpreter/IncrementalAction.cpp 
b/clang/lib/Interpreter/IncrementalAction.cpp
index 85f00c36dd5aa..4136fd90048d4 100644
--- a/clang/lib/Interpreter/IncrementalAction.cpp
+++ b/clang/lib/Interpreter/IncrementalAction.cpp
@@ -54,7 +54,8 @@ IncrementalAction::IncrementalAction(CompilerInstance 
&Instance,
         }
         return Act;
       }()),
-      Interp(I), CI(Instance), Consumer(std::move(Consumer)) {}
+      Interp(I), CI(Instance), LLVMCtx(LLVMCtx), Consumer(std::move(Consumer)) 
{
+}
 
 std::unique_ptr<ASTConsumer>
 IncrementalAction::CreateASTConsumer(CompilerInstance & /*CI*/,
@@ -114,8 +115,9 @@ std::unique_ptr<llvm::Module> 
IncrementalAction::GenModule() {
              CachedInCodeGenModule->alias_empty() &&
              CachedInCodeGenModule->ifunc_empty())) &&
            "CodeGen wrote to a readonly module");
+    // M is null after a failed input: CodeGen dropped that module.
     std::unique_ptr<llvm::Module> M(CG->ReleaseModule());
-    CG->StartModule("incr_module_" + std::to_string(ID++), M->getContext());
+    CG->StartModule("incr_module_" + std::to_string(ID++), LLVMCtx);
     return M;
   }
   return nullptr;
diff --git a/clang/lib/Interpreter/IncrementalAction.h 
b/clang/lib/Interpreter/IncrementalAction.h
index 2893ff7b5baa6..27daf15d8c91e 100644
--- a/clang/lib/Interpreter/IncrementalAction.h
+++ b/clang/lib/Interpreter/IncrementalAction.h
@@ -36,6 +36,7 @@ class IncrementalAction : public WrapperFrontendAction {
   bool IsTerminating = false;
   Interpreter &Interp;
   [[maybe_unused]] CompilerInstance &CI;
+  llvm::LLVMContext &LLVMCtx;
   std::unique_ptr<ASTConsumer> Consumer;
 
   /// When CodeGen is created the first llvm::Module gets cached in many places
diff --git a/clang/lib/Interpreter/IncrementalParser.cpp 
b/clang/lib/Interpreter/IncrementalParser.cpp
index 3cfabb5e5daa1..ee0b5abf83264 100644
--- a/clang/lib/Interpreter/IncrementalParser.cpp
+++ b/clang/lib/Interpreter/IncrementalParser.cpp
@@ -87,8 +87,25 @@ IncrementalParser::ParseOrWrapTopLevelDecl() {
   }
 
   DiagnosticsEngine &Diags = S.getDiagnostics();
+  if (!Diags.hasErrorOccurred()) {
+    // Process any TopLevelDecls generated by #pragma weak.
+    for (Decl *D : S.WeakTopLevelDecls()) {
+      DeclGroupRef DGR(D);
+      Consumer->HandleTopLevelDecl(DGR);
+    }
+
+    LocalInstantiations.perform();
+    GlobalInstantiations.perform();
+  }
+
+  // End the unit as a whole-file compile does. After an error, CodeGen drops
+  // the input's IR and the next input starts a new module. CodeGen can also
+  // report the input's first error here.
+  Consumer->HandleTranslationUnit(C);
+
   if (Diags.hasErrorOccurred()) {
     CleanUpPTU(C.getTranslationUnitDecl());
+    Act->GenModule();
 
     Diags.Reset(/*soft=*/true);
     Diags.getClient()->clear();
@@ -96,17 +113,6 @@ IncrementalParser::ParseOrWrapTopLevelDecl() {
                                                std::error_code());
   }
 
-  // Process any TopLevelDecls generated by #pragma weak.
-  for (Decl *D : S.WeakTopLevelDecls()) {
-    DeclGroupRef DGR(D);
-    Consumer->HandleTopLevelDecl(DGR);
-  }
-
-  LocalInstantiations.perform();
-  GlobalInstantiations.perform();
-
-  Consumer->HandleTranslationUnit(C);
-
   return C.getTranslationUnitDecl();
 }
 
diff --git a/clang/test/Interpreter/failed-input-drops-ir.cpp 
b/clang/test/Interpreter/failed-input-drops-ir.cpp
new file mode 100644
index 0000000000000..779d6c902feaf
--- /dev/null
+++ b/clang/test/Interpreter/failed-input-drops-ir.cpp
@@ -0,0 +1,32 @@
+// REQUIRES: host-supports-jit
+// RUN: cat %s | clang-repl 2>&1 | FileCheck %s
+// RUN: cat %s | clang-repl 2>&1 | FileCheck %s --check-prefix=NEG
+
+// A failed input must not leave its IR behind. A definition before the error
+// must not block a later definition, and its initializers must not run.
+
+extern "C" int printf(const char *, ...);
+
+int f() { return 1; } int g() { return no_such_name; }
+// CHECK-DAG: error: use of undeclared identifier 'no_such_name'
+int f() { return 1; }
+// NEG-NOT: error: definition with same mangled name
+printf("f() = %d\n", f());
+// CHECK-DAG: f() = 1
+
+int v = printf("v init\n"); int w = no_such_name;
+printf("stmt\n"); no_such_name;
+int ok = 1;
+// NEG-NOT: {{^}}v init
+// NEG-NOT: {{^}}stmt
+printf("ok = %d\n", ok);
+// CHECK-DAG: ok = 1
+
+// CodeGen reports this error at the end of the unit. The input fails, and the
+// next input works.
+extern "C" int no_target(); extern "C" int ali() 
__attribute__((alias("no_target")));
+// CHECK-DAG: error: alias must point to a defined variable or function
+printf("after = %d\n", ok + 1);
+// CHECK-DAG: after = 2
+
+%quit
diff --git a/clang/unittests/Interpreter/InterpreterTest.cpp 
b/clang/unittests/Interpreter/InterpreterTest.cpp
index 3becd00c12820..39375876ff8b2 100644
--- a/clang/unittests/Interpreter/InterpreterTest.cpp
+++ b/clang/unittests/Interpreter/InterpreterTest.cpp
@@ -121,6 +121,157 @@ TEST_F(InterpreterTest, Errors) {
   EXPECT_TRUE(!!RecoverErr);
 }
 
+// Collect the diagnostics of an interpreter into a string.
+struct CapturedDiagnostics {
+  std::string Output;
+  llvm::raw_string_ostream OS{Output};
+  DiagnosticOptions Opts;
+  TextDiagnosticPrinter Printer{OS, Opts};
+};
+
+// The error text of ParseAndExecute, or "" on success.
+static std::string ParseAndExecuteError(Interpreter &Interp,
+                                        llvm::StringRef Code) {
+  return llvm::toString(Interp.ParseAndExecute(Code));
+}
+
+// A failed input drops the IR of the definitions before the error. A later
+// input can define the same names again, and the JIT sees the new definitions.
+TEST_F(InterpreterTest, FailedInputDropsCodeGen) {
+  using ::testing::HasSubstr;
+  using ::testing::Not;
+  CapturedDiagnostics Diags;
+  auto Interp = createInterpreter({}, &Diags.Printer);
+
+  const char *Defs[] = {
+      // A function.
+      "int f() { return 1; }",
+      // A global variable.
+      "int gv = 1;",
+      // An inline function that the input uses.
+      "inline int inl() { return 1; } int useinl() { return inl(); }",
+      // A class with a vtable.
+      "struct V { virtual int k(); }; int V::k() { return 1; }",
+      // A static local.
+      "int sl() { static int s = 1; return s; }",
+      // A string literal.
+      "const char *str = \"s\";",
+  };
+  for (const char *Def : Defs) {
+    std::string Failed = std::string(Def) + " int e = no_such_name;";
+    auto Err = Interp->Parse(Failed).takeError();
+    EXPECT_EQ("Parsing failed.", llvm::toString(std::move(Err))) << Def;
+    EXPECT_THAT(Diags.Output, HasSubstr("undeclared identifier 
'no_such_name'"))
+        << Def;
+    Diags.Output.clear();
+
+    EXPECT_EQ("", ParseAndExecuteError(*Interp, Def));
+    EXPECT_THAT(Diags.Output, Not(HasSubstr("error:"))) << Def;
+  }
+
+  Value V;
+  cantFail(Interp->ParseAndExecute(
+      "f() + gv + useinl() + V().k() + sl() + (str[0] == 's')", &V));
+  EXPECT_EQ(6, V.getInt());
+
+  // A definition after the error never reaches CodeGen.
+  auto Err = Interp->Parse("int e = no_such_name; int after() { return 1; }")
+                 .takeError();
+  EXPECT_EQ("Parsing failed.", llvm::toString(std::move(Err)));
+  Diags.Output.clear();
+  EXPECT_EQ("", ParseAndExecuteError(*Interp, "int after() { return 2; }"));
+  EXPECT_THAT(Diags.Output, Not(HasSubstr("error:")));
+}
+
+// The initializers and statements of a failed input must not run later.
+TEST_F(InterpreterTest, FailedInputDoesNotRun) {
+  auto Interp = createInterpreter();
+  cantFail(Interp->ParseAndExecute("void setGlobal(int);"));
+  const int Before = getGlobal();
+
+  auto Err =
+      Interp->Parse("int side = (setGlobal(1), 0); int e = no_such_name;")
+          .takeError();
+  EXPECT_EQ("Parsing failed.", llvm::toString(std::move(Err)));
+  Err = Interp->Parse("setGlobal(2); no_such_name;").takeError();
+  EXPECT_EQ("Parsing failed.", llvm::toString(std::move(Err)));
+
+  cantFail(Interp->ParseAndExecute("int ok = 1;"));
+  EXPECT_EQ(Before, getGlobal());
+}
+
+// A failed input can be the first user of a deferred definition from an
+// earlier input. A later input must still get that definition.
+TEST_F(InterpreterTest, FailedInputKeepsDeferredDecls) {
+  auto Interp = createInterpreter();
+  cantFail(Interp->ParseAndExecute("inline int seven() { return 7; }"));
+
+  auto Err =
+      Interp->Parse("int use = seven(); int e = no_such_name;").takeError();
+  EXPECT_EQ("Parsing failed.", llvm::toString(std::move(Err)));
+
+  Value V;
+  cantFail(Interp->ParseAndExecute("seven()", &V));
+  EXPECT_EQ(7, V.getInt());
+}
+
+// Undo after a failed input removes the last good input. The names of both
+// inputs are then free again.
+TEST_F(InterpreterTest, RedefineAfterUndoPastFailedInput) {
+  using ::testing::HasSubstr;
+  using ::testing::Not;
+  CapturedDiagnostics Diags;
+  auto Interp = createInterpreter({}, &Diags.Printer);
+
+  cantFail(Interp->ParseAndExecute("int u = 1;"));
+  auto Err = Interp->Parse("int u2 = 2; int e = no_such_name;").takeError();
+  EXPECT_EQ("Parsing failed.", llvm::toString(std::move(Err)));
+  Diags.Output.clear();
+  cantFail(Interp->Undo());
+
+  EXPECT_EQ("", ParseAndExecuteError(*Interp, "int u = 3; int u2 = 4;"));
+  EXPECT_THAT(Diags.Output, Not(HasSubstr("error:")));
+  Value V;
+  cantFail(Interp->ParseAndExecute("u + u2", &V));
+  EXPECT_EQ(7, V.getInt());
+}
+
+// An inline definition of a failed input must not stay behind as a deferred
+// decl. A later input has no definition of the name.
+TEST_F(InterpreterTest, FailedInputDropsDeferredDefinitions) {
+  using ::testing::HasSubstr;
+  auto Interp = createInterpreter();
+  auto Err = Interp
+                 ->Parse("inline int f() { return 1; }"
+                         "int usef() { return f(); } int e = no_such_name;")
+                 .takeError();
+  EXPECT_EQ("Parsing failed.", llvm::toString(std::move(Err)));
+
+  // The JIT reports "Symbols not found: [ _Z1fv ]" on stderr.
+  EXPECT_THAT(ParseAndExecuteError(*Interp, "int f(); int r = f();"),
+              HasSubstr("Failed to materialize symbols"));
+}
+
+// CodeGen reports some errors only at the end of the unit. Such an input fails
+// like any other, and the next input works.
+TEST_F(InterpreterTest, ErrorFromCodeGenFailsTheInput) {
+  using ::testing::HasSubstr;
+  CapturedDiagnostics Diags;
+  auto Interp = createInterpreter({}, &Diags.Printer);
+
+  auto Err = Interp
+                 ->Parse("extern \"C\" int no_target(); extern \"C\" int ali()"
+                         " __attribute__((alias(\"no_target\")));")
+                 .takeError();
+  EXPECT_EQ("Parsing failed.", llvm::toString(std::move(Err)));
+  EXPECT_THAT(Diags.Output,
+              HasSubstr("alias must point to a defined variable or function"));
+
+  Value V;
+  cantFail(Interp->ParseAndExecute("1 + 1", &V));
+  EXPECT_EQ(2, V.getInt());
+}
+
 // Here we test whether the user can mix declarations and statements. The
 // interpreter should be smart enough to recognize the declarations from the
 // statements and wrap the latter into a declaration, producing valid code.

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

Reply via email to