[clang] ec5c918 - [clang-repl] Don't double-remove extern "C" decls from the IdResolver (#218129)
via cfe-commits
cfe-commits at lists.llvm.org
Sun Aug 23 00:22:36 PDT 2026
Author: Vipul Cariappa
Date: 2026-08-23T10:22:31+03:00
New Revision: ec5c9182be2aa168b7c843374c5c7497dd3bef17
URL: https://github.com/llvm/llvm-project/commit/ec5c9182be2aa168b7c843374c5c7497dd3bef17
DIFF: https://github.com/llvm/llvm-project/commit/ec5c9182be2aa168b7c843374c5c7497dd3bef17.diff
LOG: [clang-repl] Don't double-remove extern "C" decls from the IdResolver (#218129)
Regression was introduced in https://github.com/llvm/llvm-project/pull/178648
This is required for the 23 release; the downstream tools CppInterOp & Cppyy will fail otherwise.
Added:
clang/test/Interpreter/extern-c-error-recovery.cpp
Modified:
clang/lib/Interpreter/IncrementalParser.cpp
Removed:
################################################################################
diff --git a/clang/lib/Interpreter/IncrementalParser.cpp b/clang/lib/Interpreter/IncrementalParser.cpp
index 59018907056b8..12beb542572d7 100644
--- a/clang/lib/Interpreter/IncrementalParser.cpp
+++ b/clang/lib/Interpreter/IncrementalParser.cpp
@@ -217,6 +217,13 @@ void IncrementalParser::CleanUpPTU(TranslationUnitDecl *MostRecentTU) {
Map->erase(Key);
}
+ // Check if we need to clean up the IdResolver chain.
+ auto RemoveFromIdResolver = [&](NamedDecl *D) {
+ if (D->getDeclName().getFETokenInfo() && !D->getLangOpts().ObjC &&
+ !D->getLangOpts().CPlusPlus)
+ S.IdResolver.RemoveDecl(D);
+ };
+
ExternCContextDecl *ECCD = S.getASTContext().getExternCContextDecl();
if (StoredDeclsMap *Map = ECCD->getPrimaryContext()->getLookupPtr()) {
for (auto &&[Key, List] : *Map) {
@@ -235,7 +242,7 @@ void IncrementalParser::CleanUpPTU(TranslationUnitDecl *MostRecentTU) {
}
for (NamedDecl *D : NamedDeclsToRemove) {
List.remove(D);
- S.IdResolver.RemoveDecl(D);
+ RemoveFromIdResolver(D);
}
}
}
@@ -244,10 +251,7 @@ void IncrementalParser::CleanUpPTU(TranslationUnitDecl *MostRecentTU) {
auto *ND = dyn_cast<NamedDecl>(D);
if (!ND || ND->getDeclName().isEmpty())
continue;
- // Check if we need to clean up the IdResolver chain.
- if (ND->getDeclName().getFETokenInfo() && !D->getLangOpts().ObjC &&
- !D->getLangOpts().CPlusPlus)
- S.IdResolver.RemoveDecl(ND);
+ RemoveFromIdResolver(ND);
}
// Lookup alone is not enough: the redeclaration chain still reaches these.
diff --git a/clang/test/Interpreter/extern-c-error-recovery.cpp b/clang/test/Interpreter/extern-c-error-recovery.cpp
new file mode 100644
index 0000000000000..20b5223700007
--- /dev/null
+++ b/clang/test/Interpreter/extern-c-error-recovery.cpp
@@ -0,0 +1,63 @@
+// REQUIRES: host-supports-jit
+// RUN: cat %s | clang-repl > %t.out 2>&1
+// RUN: FileCheck %s --input-file=%t.out
+// RUN: FileCheck %s --check-prefix=NEGATIVE --input-file=%t.out
+
+// An input that declares something with C language linkage and then fails must
+// not take the interpreter down with it.
+
+extern "C" int printf(const char *, ...);
+
+// An error in the body of an extern "C" function definition.
+extern "C" void f1() { undeclared_thing; }
+// CHECK-DAG: error: use of undeclared identifier 'undeclared_thing'
+printf("alive %d\n", 1);
+// CHECK-DAG: alive 1
+
+// The same, written as an `extern "C" { ... }` block.
+extern "C" { void f2() { undeclared_thing; } }
+printf("alive %d\n", 2);
+// CHECK-DAG: alive 2
+
+// An extern "C" *variable* whose initializer fails: variables are registered
+// with the ExternCContext by a
diff erent Sema path than functions.
+extern "C" int v1 = undeclared_thing;
+printf("alive %d\n", 3);
+// CHECK-DAG: alive 3
+
+// A deleted destructor reached through a wrapper -- the shape CppInterOp's
+// generated destructor wrappers hit.
+class D { public: ~D() = delete; };
+extern "C" void g(D *p) { delete p; }
+// CHECK-DAG: error: attempt to use a deleted function
+printf("alive %d\n", 4);
+// CHECK-DAG: alive 4
+
+// A block-scope `extern` inside an extern "C" function is registered with the
+// ExternCContext but is already off the IdResolver by the time CleanUpPTU runs,
+// because its own scope popped while the input was still being parsed. Removing
+// it again therefore does not just fail to find it: with assertions off it
+// clears the identifier's chain out from under whatever else is on it.
+extern "C" void h1() { extern int fresh; undeclared_thing; } int fresh = 1;
+printf("alive %d\n", 5);
+// CHECK-DAG: alive 5
+
+// Surviving is not enough: the discarded PTU must leave nothing behind, so the
+// very same names have to be definable afterwards and the definitions have to
+// be the ones that run.
+extern "C" void f1() { printf("f1 ran\n"); }
+extern "C" void f2() { printf("f2 ran\n"); }
+extern "C" int v1 = 5;
+f1();
+f2();
+printf("v1 %d\n", v1);
+// CHECK-DAG: f1 ran
+// CHECK-DAG: f2 ran
+// CHECK-DAG: v1 5
+
+// Nothing anywhere in the session may claim the recovered definitions clash
+// with what the discarded inputs left behind.
+// NEGATIVE-NOT: error: redefinition
+// NEGATIVE-NOT: error: conflicting types
+
+%quit
More information about the cfe-commits
mailing list