[clang] [Clang] Fix crash when a local extern's previous declaration is in a discarded if `constexpr` branch (PR #233389)
Akash Manna via cfe-commits
cfe-commits at lists.llvm.org
Mon Oct 19 02:50:00 PDT 2026
https://github.com/akash-manna-sky created https://github.com/llvm/llvm-project/pull/233389
Fixes #223003
Instantiating a block-scope `extern` variable looks up the instantiation of the pattern's previous declaration so the two can be merged. When both branches of an `if constexpr` redeclare a namespace-scope variable, the previous declaration is the one in the discarded branch. It was never instantiated, so `findInstantiationOf` asserted.
Walk the pattern's redeclaration chain past local declarations that have no instantiation in the current scope, so the new declaration merges with the nearest one that exists, here the global. A previous declaration in another dependent context still falls back to lookup as before.
Assisted-by : LLM tools
>From 82f682e2cd071c27af302792c8647f20e0334dee Mon Sep 17 00:00:00 2001
From: Akash Manna <akash.manna.mymail at gmail.com>
Date: Mon, 19 Oct 2026 14:31:34 +0530
Subject: [PATCH] [Clang] Fix crash when a local extern's previous declaration
is in a discarded if constexpr branch
Fixes #223003
Instantiating a block-scope `extern` variable looks up the instantiation of the pattern's previous declaration so the two can be merged. When both branches of an `if constexpr` redeclare a namespace-scope variable, the previous declaration is the one in the discarded branch. It was never instantiated, so `findInstantiationOf` asserted.
Walk the pattern's redeclaration chain past local declarations that have no instantiation in the current scope, so the new declaration merges with the nearest one that exists, here the global. A previous declaration in another dependent context still falls back to lookup as before.
LLM tools were used for this contribution.
---
clang/docs/ReleaseNotes.md | 3 +
.../lib/Sema/SemaTemplateInstantiateDecl.cpp | 28 +++-
.../CXX/stmt.stmt/stmt.select/stmt.if/p2.cpp | 141 ++++++++++++++++++
clang/test/CodeGenCXX/cxx1z-constexpr-if.cpp | 18 +++
...n-statements-local-extern-if-constexpr.cpp | 8 +-
5 files changed, 187 insertions(+), 11 deletions(-)
diff --git a/clang/docs/ReleaseNotes.md b/clang/docs/ReleaseNotes.md
index 3f0c48cf36644..c3779651dae1e 100644
--- a/clang/docs/ReleaseNotes.md
+++ b/clang/docs/ReleaseNotes.md
@@ -974,6 +974,9 @@ features cannot lower the translation-unit ABI level;
- Fixed deduction of the template parameters appearing in the type of a
constant template parameter of reference type. (#GH40328)
+- Fixed a crash when instantiating a local `extern` variable declaration whose
+ previous declaration was in a discarded branch of an `if constexpr`. (#GH223003)
+
- Fixed an issue where an explicit specialization of a constexpr variable would
result in a link error. (#GH219796)
diff --git a/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp b/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp
index b4881d0c6db5c..a059c1c20eb72 100644
--- a/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp
+++ b/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp
@@ -6474,13 +6474,31 @@ void Sema::BuildVariableInstantiation(
NewVar->isLocalExternDecl() ? RedeclarationKind::ForExternalRedeclaration
: forRedeclarationInCurContext());
- if (NewVar->isLocalExternDecl() && OldVar->getPreviousDecl() &&
- (!OldVar->getPreviousDecl()->getDeclContext()->isDependentContext() ||
- OldVar->getPreviousDecl()->getDeclContext()==OldVar->getDeclContext())) {
+ VarDecl *PatternPrev = nullptr;
+ if (NewVar->isLocalExternDecl()) {
+ for (VarDecl *Prev = OldVar->getPreviousDecl(); Prev;
+ Prev = Prev->getPreviousDecl()) {
+ DeclContext *PrevDC = Prev->getDeclContext();
+ if (!PrevDC->isDependentContext()) {
+ PatternPrev = Prev;
+ break;
+ }
+ if (PrevDC != OldVar->getDeclContext())
+ break;
+ if (CurrentInstantiationScope &&
+ CurrentInstantiationScope->getInstantiationOfIfExists(Prev)) {
+ PatternPrev = Prev;
+ break;
+ }
+ // Discarded 'if constexpr' branch: look further back.
+ }
+ }
+
+ if (PatternPrev) {
// We have a previous declaration. Use that one, so we merge with the
// right type.
- if (NamedDecl *NewPrev = FindInstantiatedDecl(
- NewVar->getLocation(), OldVar->getPreviousDecl(), TemplateArgs))
+ if (NamedDecl *NewPrev = FindInstantiatedDecl(NewVar->getLocation(),
+ PatternPrev, TemplateArgs))
Previous.addDecl(NewPrev);
} else if (!isa<VarTemplateSpecializationDecl>(NewVar) &&
OldVar->hasLinkage()) {
diff --git a/clang/test/CXX/stmt.stmt/stmt.select/stmt.if/p2.cpp b/clang/test/CXX/stmt.stmt/stmt.select/stmt.if/p2.cpp
index 05830de9891fe..bedf8140881b6 100644
--- a/clang/test/CXX/stmt.stmt/stmt.select/stmt.if/p2.cpp
+++ b/clang/test/CXX/stmt.stmt/stmt.select/stmt.if/p2.cpp
@@ -256,4 +256,145 @@ namespace GH153884 {
}
}
+namespace GH223003 {
+ int foo8_decl;
+
+ template <bool x>
+ void foo8() {
+ if constexpr (x)
+ extern int foo8_decl;
+ else
+ extern int foo8_decl;
+ }
+ template void foo8<false>();
+ template void foo8<true>();
+
+ template <bool x>
+ void foo8_use() {
+ if constexpr (x) {
+ extern int foo8_decl;
+ foo8_decl = 1;
+ } else {
+ extern int foo8_decl;
+ foo8_decl = 2;
+ }
+ }
+ template void foo8_use<false>();
+ template void foo8_use<true>();
+
+ template <bool x>
+ void plain_if() {
+ if (x) {
+ extern int foo8_decl;
+ foo8_decl = 1;
+ } else {
+ extern int foo8_decl;
+ foo8_decl = 2;
+ }
+ }
+ template void plain_if<false>();
+
+ extern int linked;
+ template <bool x>
+ constexpr int *addr() {
+ if constexpr (x) {
+ extern int linked;
+ return &linked;
+ } else {
+ extern int linked;
+ return &linked;
+ }
+ }
+ static_assert(addr<false>() == &linked, "");
+ static_assert(addr<true>() == &linked, "");
+
+ int three;
+ template <int n>
+ void three_decls() {
+ if constexpr (n == 1)
+ extern int three;
+ else if constexpr (n == 2)
+ extern int three;
+ else
+ extern int three;
+ }
+ template void three_decls<3>();
+ template void three_decls<2>();
+ template void three_decls<1>();
+
+ int nest;
+ template <bool a, bool b>
+ void nested() {
+ if constexpr (a) {
+ if constexpr (b)
+ extern int nest;
+ else
+ extern int nest;
+ } else {
+ extern int nest;
+ }
+ }
+ template void nested<false, false>();
+ template void nested<true, false>();
+ template void nested<true, true>();
+
+ template <bool x>
+ void local_prev() {
+ extern int local_only;
+ if constexpr (x)
+ extern int local_only;
+ else
+ extern int local_only;
+ local_only = 1;
+ }
+ template void local_prev<false>();
+
+ int in_lambda;
+ template <bool x>
+ void lambda() {
+ [] {
+ if constexpr (x)
+ extern int in_lambda;
+ else
+ extern int in_lambda;
+ }();
+ }
+ template void lambda<false>();
+
+ int in_generic;
+ void generic() {
+ [](auto x) {
+ if constexpr (sizeof(x) == 1)
+ extern int in_generic;
+ else
+ extern int in_generic;
+ }(0);
+ }
+
+ void fn_decl();
+ template <bool x>
+ void local_fn() {
+ if constexpr (x)
+ extern void fn_decl();
+ else
+ extern void fn_decl();
+ fn_decl();
+ }
+ template void local_fn<false>();
+
+ int dep; // #dep
+ template <typename T>
+ void dependent() {
+ if constexpr (sizeof(T) == 1)
+ extern T dep;
+ else
+ extern T dep; // #dep-else
+ }
+ template void dependent<long>(); // #dep-long
+ // expected-error@#dep-else {{redeclaration of 'dep' with a different type: 'long' vs 'int'}}
+ // expected-note@#dep-long {{in instantiation of function template specialization 'GH223003::dependent<long>' requested here}}
+ // expected-note@#dep {{previous definition is here}}
+ template void dependent<int>();
+}
+
#endif
diff --git a/clang/test/CodeGenCXX/cxx1z-constexpr-if.cpp b/clang/test/CodeGenCXX/cxx1z-constexpr-if.cpp
index 5a11afb8dec40..05f3f24756773 100644
--- a/clang/test/CodeGenCXX/cxx1z-constexpr-if.cpp
+++ b/clang/test/CodeGenCXX/cxx1z-constexpr-if.cpp
@@ -34,6 +34,24 @@ foo: should_not_be_used();
// CHECK: should_be_used_2
// CHECK: should_be_used_3
+namespace GH223003 {
+ static int internal_var = 42;
+ template <bool x>
+ int read() {
+ if constexpr (x) {
+ extern int internal_var;
+ return internal_var;
+ } else {
+ extern int internal_var;
+ return internal_var;
+ }
+ }
+ int use() { return read<false>(); }
+}
+
+// CHECK-LABEL: define {{.*}} @_ZN8GH2230034readILb0EEEiv(
+// CHECK: load i32, ptr @_ZN8GH223003L12internal_varE
+
namespace BlockThisCapture {
void foo();
struct S {
diff --git a/clang/test/SemaCXX/expansion-statements-local-extern-if-constexpr.cpp b/clang/test/SemaCXX/expansion-statements-local-extern-if-constexpr.cpp
index cd83856a36c3b..d70e795f1f6ed 100644
--- a/clang/test/SemaCXX/expansion-statements-local-extern-if-constexpr.cpp
+++ b/clang/test/SemaCXX/expansion-statements-local-extern-if-constexpr.cpp
@@ -1,8 +1,4 @@
// RUN: %clang_cc1 %s -I %S/Inputs -std=c++2c -fsyntax-only -verify
-// XFAIL: *
-
-// FIXME: This test currently asserts due to a bug not related to expansion statements:
-// https://github.com/llvm/llvm-project/issues/223003. Reenable it once that bug is fixed.
int foo8_decl; // #foo8_decl
void foo8() {
@@ -11,9 +7,9 @@ void foo8() {
extern int foo8_decl;
} else {
extern thread_local int foo8_decl; // #mismatched_foo8_decl
- // expected-error@#mismatched_foo8_decl {{thread-local declaration of 'foo8_decl' follows non-thread-local declaration}}
+ // expected-error@#mismatched_foo8_decl 2 {{thread-local declaration of 'foo8_decl' follows non-thread-local declaration}}
// expected-note@#foo8_instantiation {{in instantiation of expansion statement requested here}}
- // expected-note@#foo8_decl {{previous definition is here}}
+ // expected-note@#foo8_decl 2 {{previous definition is here}}
}
}
}
More information about the cfe-commits
mailing list