https://github.com/RexTechnology1 updated 
https://github.com/llvm/llvm-project/pull/202958

>From 64df0a761cb0a5849f514475d8241f13e53afeed Mon Sep 17 00:00:00 2001
From: rextechnology <[email protected]>
Date: Tue, 9 Jun 2026 21:32:36 +0300
Subject: [PATCH 1/4] [Clang][Sema] Fix crash instantiating a member variable
 template partial specialization when the enclosing class template is
 deserialized

VisitVarTemplatePartialSpecializationDecl assumes the primary member variable
template has already been instantiated into the current instantiation (Owner),
which is normally true because the primary is declared before its partial
specializations and is visited first while the enclosing class is instantiated.

When the enclosing class template's pattern is deserialized from a precompiled
preamble built with compiler errors (-fallow-pch-with-compiler-errors, which
clangd uses for preambles), the primary may not have been materialized into
Owner yet, leaving the lookup empty. This tripped the
assert(!Found.empty() && "Instantiation found nothing?") in assertions builds
and dereferenced a null Found.front() (SIGSEGV) in release builds.

This is hit in practice by clangd: completing std::expected<T, E> from
libstdc++ 15.x crashes whenever the preamble contains an unresolved #include,
a routine state in interactive editing (e.g. not-yet-generated headers). All
released clang versions tested (20, 21, 22) and current trunk are affected.

Fix: when the lookup is empty, instantiate the primary member variable template
on demand and look it up again, which restores the invariant the assertions
expect. The original invariant assertions are kept, so a genuinely-missing
primary is still flagged in +Asserts builds, with a null return as a
release-safe guard. The common (non-deserialized) path is unchanged.

Signed-off-by: rextechnology <[email protected]>
Assisted-by: Claude (Anthropic)
---
 .../lib/Sema/SemaTemplateInstantiateDecl.cpp  | 24 +++++++++++++++++++
 1 file changed, 24 insertions(+)

diff --git a/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp 
b/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp
index aa381f09138de..c11312843b18e 100644
--- a/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp
+++ b/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp
@@ -2558,10 +2558,34 @@ Decl 
*TemplateDeclInstantiator::VisitVarTemplatePartialSpecializationDecl(
 
   // Lookup the already-instantiated declaration and return that.
   DeclContext::lookup_result Found = Owner->lookup(VarTemplate->getDeclName());
+
+  // Normally the primary member variable template has already been 
instantiated
+  // into Owner, because it is declared before its partial specializations and
+  // so is visited first while instantiating the enclosing class. However, when
+  // the class template pattern is deserialized from a module or precompiled
+  // preamble, the primary may not have been materialized into Owner yet,
+  // leaving the lookup empty. Previously this asserted (and crashed release
+  // builds with a null dereference). Instantiate the primary on demand and 
look
+  // it up again.
+  if (Found.empty()) {
+    if (Decl *InstPrimary = Visit(VarTemplate))
+      if (auto *InstVTD = dyn_cast<VarTemplateDecl>(InstPrimary))
+        Found = Owner->lookup(InstVTD->getDeclName());
+  }
+
+  // After the on-demand instantiation above the primary must be present. Keep
+  // the original invariant assertions so a genuinely-missing primary is still
+  // flagged in +Asserts builds, with a null return as a release-safe guard so 
a
+  // stray case degrades gracefully instead of dereferencing an empty lookup
+  // result.
   assert(!Found.empty() && "Instantiation found nothing?");
+  if (Found.empty())
+    return nullptr;
 
   VarTemplateDecl *InstVarTemplate = dyn_cast<VarTemplateDecl>(Found.front());
   assert(InstVarTemplate && "Instantiation did not find a variable template?");
+  if (!InstVarTemplate)
+    return nullptr;
 
   if (VarTemplatePartialSpecializationDecl *Result =
           InstVarTemplate->findPartialSpecInstantiatedFromMember(D))

>From b348a8092f4f4b21e251268f8bffba91d41dc2af Mon Sep 17 00:00:00 2001
From: Rex Technology <[email protected]>
Date: Tue, 30 Jun 2026 11:29:31 +0200
Subject: [PATCH 2/4] [Clang] Add regression test and release note for GH202956

Address review feedback by adding the requested test and ReleaseNotes entry
for the fix to the crash in VisitVarTemplatePartialSpecializationDecl when the
enclosing class template is deserialized from a PCH/module built with errors.

The crash reproduces as a standalone clang_cc1 PCH test (no clangd or libstdc++
needed): build a PCH containing an unresolved #include with
-fallow-pch-with-compiler-errors, then complete a class template specialization
deserialized from it in a separate TU via -include-pch. The minimal trigger is
a member variable template whose last template parameter has a default argument
that is a dependent alias-template specialization, plus a partial specialization
fixing that parameter -- mirroring libstdc++ 15's __cons_from_expected in
std::expected.
---
 clang/docs/ReleaseNotes.md                    |  4 +++
 ...late-partial-spec-from-pch-with-errors.cpp | 19 ++++++++++++
 ...mplate-partial-spec-from-pch-with-errors.h | 31 +++++++++++++++++++
 3 files changed, 54 insertions(+)
 create mode 100644 
clang/test/PCH/var-template-partial-spec-from-pch-with-errors.cpp
 create mode 100644 
clang/test/PCH/var-template-partial-spec-from-pch-with-errors.h

diff --git a/clang/docs/ReleaseNotes.md b/clang/docs/ReleaseNotes.md
index b372e5b58068b..f34955a5fd84d 100644
--- a/clang/docs/ReleaseNotes.md
+++ b/clang/docs/ReleaseNotes.md
@@ -800,6 +800,10 @@ latest release, please see the [Clang Web 
Site](https://clang.llvm.org) or the
 - Fixed a crash when parsing invalid friend declaration with storage-class 
specifier. (#GH186569)
 - Fixed a missing vtable for `dynamic_cast<FinalClass *>(this)` in a function 
template. (#GH198511)
 - Fixed an assertion failure during init-list checking of an array whose 
element type is an incomplete class. (#GH140685)
+- Fixed a crash when instantiating a member variable template partial 
specialization
+  whose enclosing class template was deserialized from a precompiled header or 
module
+  built with errors, so that the primary member variable template had not yet 
been
+  instantiated. (#GH202956)
 
 #### Bug Fixes to AST Handling
 
diff --git a/clang/test/PCH/var-template-partial-spec-from-pch-with-errors.cpp 
b/clang/test/PCH/var-template-partial-spec-from-pch-with-errors.cpp
new file mode 100644
index 0000000000000..3559784b2d981
--- /dev/null
+++ b/clang/test/PCH/var-template-partial-spec-from-pch-with-errors.cpp
@@ -0,0 +1,19 @@
+// Build a PCH that contains compiler errors (an unresolved #include), then use
+// it and force instantiation of a class template that was deserialized from 
it.
+// The class has a member variable template with a partial specialization; when
+// the enclosing class template comes from the deserialized PCH, the primary
+// member variable template has not been instantiated into the current
+// instantiation yet, so the lookup for it used to come back empty and crash
+// (assertion "Instantiation found nothing?" in +Asserts builds, null
+// dereference otherwise). See GH202956.
+
+// RUN: %clang_cc1 -x c++-header -std=c++23 -fallow-pch-with-compiler-errors \
+// RUN:   -emit-pch -o %t %S/var-template-partial-spec-from-pch-with-errors.h
+// RUN: %clang_cc1 -std=c++23 -fallow-pch-with-compiler-errors -include-pch %t 
\
+// RUN:   -fsyntax-only -verify %s
+
+// expected-no-diagnostics
+
+// Completing wrapper<int, int> instantiates the member variable template
+// partial specialization above.
+GH202956::wrapper<int, int> w;
diff --git a/clang/test/PCH/var-template-partial-spec-from-pch-with-errors.h 
b/clang/test/PCH/var-template-partial-spec-from-pch-with-errors.h
new file mode 100644
index 0000000000000..7e772a7f574b2
--- /dev/null
+++ b/clang/test/PCH/var-template-partial-spec-from-pch-with-errors.h
@@ -0,0 +1,31 @@
+// Header used to build a precompiled header that contains compiler errors.
+//
+// The unresolved #include makes this a "PCH built with compiler errors"
+// (clangd always builds preambles with -fallow-pch-with-compiler-errors, and 
an
+// unresolved include is a routine state in interactive editing). The class
+// template below is a reduction of libstdc++ 15's std::expected, whose private
+// member variable template __cons_from_expected has a partial specialization.
+// See GH202956.
+
+#include "this_header_does_not_exist.h"
+
+namespace GH202956 {
+
+template <typename T> struct type_identity { using type = T; };
+template <typename T> using type_identity_t = typename type_identity<T>::type;
+
+template <typename E> class box { E e; };
+
+template <typename T, typename E> class wrapper {
+  // A member variable template whose last template parameter has a default
+  // argument that is a dependent alias-template specialization, plus a partial
+  // specialization that fixes that parameter. This mirrors libstdc++'s
+  // __cons_from_expected<_Up, _Gr, _Unex = unexpected<_Er>, = 
remove_cv_t<_Tp>>.
+  template <typename U, typename G, typename Unex = box<E>,
+            typename = type_identity_t<T>>
+  static constexpr bool cons_from = true;
+  template <typename U, typename G, typename Unex>
+  static constexpr bool cons_from<U, G, Unex, bool> = false;
+};
+
+} // namespace GH202956

>From 033f326a55298fc385a2f14e43656fc28ae4aa61 Mon Sep 17 00:00:00 2001
From: Rex Technology <[email protected]>
Date: Tue, 30 Jun 2026 17:18:02 +0200
Subject: [PATCH 3/4] [Clang] Simplify member variable template partial spec
 crash fix

Replace the contradictory assert/early-return pair flagged in review.
---
 .../lib/Sema/SemaTemplateInstantiateDecl.cpp  | 30 +++++--------------
 ...late-partial-spec-from-pch-with-errors.cpp | 11 -------
 ...mplate-partial-spec-from-pch-with-errors.h | 13 --------
 3 files changed, 8 insertions(+), 46 deletions(-)

diff --git a/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp 
b/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp
index 0deda7a7e2adf..abef99b1442cd 100644
--- a/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp
+++ b/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp
@@ -2534,33 +2534,19 @@ Decl 
*TemplateDeclInstantiator::VisitVarTemplatePartialSpecializationDecl(
   VarTemplateDecl *VarTemplate = D->getSpecializedTemplate();
 
   // Lookup the already-instantiated declaration and return that.
-  DeclContext::lookup_result Found = Owner->lookup(VarTemplate->getDeclName());
+  //
+  // The primary member variable template is normally instantiated into Owner
+  // before its partial specializations, because it is declared first. That may
+  // not hold when the enclosing class template is deserialized from a PCH or
+  // module built with errors, so instantiate the primary on demand here.
+  if (Owner->lookup(VarTemplate->getDeclName()).empty())
+    Visit(VarTemplate);
 
-  // Normally the primary member variable template has already been 
instantiated
-  // into Owner, because it is declared before its partial specializations and
-  // so is visited first while instantiating the enclosing class. However, when
-  // the class template pattern is deserialized from a module or precompiled
-  // preamble, the primary may not have been materialized into Owner yet,
-  // leaving the lookup empty. Previously this asserted (and crashed release
-  // builds with a null dereference). Instantiate the primary on demand and 
look
-  // it up again.
-  if (Found.empty()) {
-    if (Decl *InstPrimary = Visit(VarTemplate))
-      if (auto *InstVTD = dyn_cast<VarTemplateDecl>(InstPrimary))
-        Found = Owner->lookup(InstVTD->getDeclName());
-  }
-
-  // After the on-demand instantiation above the primary must be present. Keep
-  // the original invariant assertions so a genuinely-missing primary is still
-  // flagged in +Asserts builds, with a null return as a release-safe guard so 
a
-  // stray case degrades gracefully instead of dereferencing an empty lookup
-  // result.
-  assert(!Found.empty() && "Instantiation found nothing?");
+  DeclContext::lookup_result Found = Owner->lookup(VarTemplate->getDeclName());
   if (Found.empty())
     return nullptr;
 
   VarTemplateDecl *InstVarTemplate = dyn_cast<VarTemplateDecl>(Found.front());
-  assert(InstVarTemplate && "Instantiation did not find a variable template?");
   if (!InstVarTemplate)
     return nullptr;
 
diff --git a/clang/test/PCH/var-template-partial-spec-from-pch-with-errors.cpp 
b/clang/test/PCH/var-template-partial-spec-from-pch-with-errors.cpp
index 3559784b2d981..76f53fa1085e1 100644
--- a/clang/test/PCH/var-template-partial-spec-from-pch-with-errors.cpp
+++ b/clang/test/PCH/var-template-partial-spec-from-pch-with-errors.cpp
@@ -1,12 +1,3 @@
-// Build a PCH that contains compiler errors (an unresolved #include), then use
-// it and force instantiation of a class template that was deserialized from 
it.
-// The class has a member variable template with a partial specialization; when
-// the enclosing class template comes from the deserialized PCH, the primary
-// member variable template has not been instantiated into the current
-// instantiation yet, so the lookup for it used to come back empty and crash
-// (assertion "Instantiation found nothing?" in +Asserts builds, null
-// dereference otherwise). See GH202956.
-
 // RUN: %clang_cc1 -x c++-header -std=c++23 -fallow-pch-with-compiler-errors \
 // RUN:   -emit-pch -o %t %S/var-template-partial-spec-from-pch-with-errors.h
 // RUN: %clang_cc1 -std=c++23 -fallow-pch-with-compiler-errors -include-pch %t 
\
@@ -14,6 +5,4 @@
 
 // expected-no-diagnostics
 
-// Completing wrapper<int, int> instantiates the member variable template
-// partial specialization above.
 GH202956::wrapper<int, int> w;
diff --git a/clang/test/PCH/var-template-partial-spec-from-pch-with-errors.h 
b/clang/test/PCH/var-template-partial-spec-from-pch-with-errors.h
index 7e772a7f574b2..9a92d02919c9f 100644
--- a/clang/test/PCH/var-template-partial-spec-from-pch-with-errors.h
+++ b/clang/test/PCH/var-template-partial-spec-from-pch-with-errors.h
@@ -1,12 +1,3 @@
-// Header used to build a precompiled header that contains compiler errors.
-//
-// The unresolved #include makes this a "PCH built with compiler errors"
-// (clangd always builds preambles with -fallow-pch-with-compiler-errors, and 
an
-// unresolved include is a routine state in interactive editing). The class
-// template below is a reduction of libstdc++ 15's std::expected, whose private
-// member variable template __cons_from_expected has a partial specialization.
-// See GH202956.
-
 #include "this_header_does_not_exist.h"
 
 namespace GH202956 {
@@ -17,10 +8,6 @@ template <typename T> using type_identity_t = typename 
type_identity<T>::type;
 template <typename E> class box { E e; };
 
 template <typename T, typename E> class wrapper {
-  // A member variable template whose last template parameter has a default
-  // argument that is a dependent alias-template specialization, plus a partial
-  // specialization that fixes that parameter. This mirrors libstdc++'s
-  // __cons_from_expected<_Up, _Gr, _Unex = unexpected<_Er>, = 
remove_cv_t<_Tp>>.
   template <typename U, typename G, typename Unex = box<E>,
             typename = type_identity_t<T>>
   static constexpr bool cons_from = true;

>From fdf582becec79039142751483c46215c6cc483a3 Mon Sep 17 00:00:00 2001
From: Rex Technology <[email protected]>
Date: Wed, 1 Jul 2026 14:29:48 +0200
Subject: [PATCH 4/4] [Clang] Fix crash from var template partial spec with
 invalid primary

When a class template is deserialized from a PCH/module built with errors,
its primary member variable template can come back invalid while a partial
specialization of it does not. InstantiateClass skips the invalid primary
(and marks the instantiation invalid), so it is never instantiated into the
current instantiation; the partial specialization was still instantiated and
crashed looking up the missing primary.

Mark a variable template partial specialization invalid on deserialization
when its primary is invalid, so it is skipped alongside the primary. This
restores the instantiation invariants, so the Sema-side asserts are reverted
back to trunk.
---
 clang/docs/ReleaseNotes.md                     |  5 ++---
 clang/lib/Sema/SemaTemplateInstantiateDecl.cpp | 14 ++------------
 clang/lib/Serialization/ASTReaderDecl.cpp      |  8 ++++++++
 3 files changed, 12 insertions(+), 15 deletions(-)

diff --git a/clang/docs/ReleaseNotes.md b/clang/docs/ReleaseNotes.md
index f34955a5fd84d..2f460252c1956 100644
--- a/clang/docs/ReleaseNotes.md
+++ b/clang/docs/ReleaseNotes.md
@@ -801,9 +801,8 @@ latest release, please see the [Clang Web 
Site](https://clang.llvm.org) or the
 - Fixed a missing vtable for `dynamic_cast<FinalClass *>(this)` in a function 
template. (#GH198511)
 - Fixed an assertion failure during init-list checking of an array whose 
element type is an incomplete class. (#GH140685)
 - Fixed a crash when instantiating a member variable template partial 
specialization
-  whose enclosing class template was deserialized from a precompiled header or 
module
-  built with errors, so that the primary member variable template had not yet 
been
-  instantiated. (#GH202956)
+  whose primary template was left invalid after deserializing the enclosing 
class
+  template from a precompiled header or module built with errors. (#GH202956)
 
 #### Bug Fixes to AST Handling
 
diff --git a/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp 
b/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp
index abef99b1442cd..324d6bf3857c7 100644
--- a/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp
+++ b/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp
@@ -2534,21 +2534,11 @@ Decl 
*TemplateDeclInstantiator::VisitVarTemplatePartialSpecializationDecl(
   VarTemplateDecl *VarTemplate = D->getSpecializedTemplate();
 
   // Lookup the already-instantiated declaration and return that.
-  //
-  // The primary member variable template is normally instantiated into Owner
-  // before its partial specializations, because it is declared first. That may
-  // not hold when the enclosing class template is deserialized from a PCH or
-  // module built with errors, so instantiate the primary on demand here.
-  if (Owner->lookup(VarTemplate->getDeclName()).empty())
-    Visit(VarTemplate);
-
   DeclContext::lookup_result Found = Owner->lookup(VarTemplate->getDeclName());
-  if (Found.empty())
-    return nullptr;
+  assert(!Found.empty() && "Instantiation found nothing?");
 
   VarTemplateDecl *InstVarTemplate = dyn_cast<VarTemplateDecl>(Found.front());
-  if (!InstVarTemplate)
-    return nullptr;
+  assert(InstVarTemplate && "Instantiation did not find a variable template?");
 
   if (VarTemplatePartialSpecializationDecl *Result =
           InstVarTemplate->findPartialSpecInstantiatedFromMember(D))
diff --git a/clang/lib/Serialization/ASTReaderDecl.cpp 
b/clang/lib/Serialization/ASTReaderDecl.cpp
index fb291a4b0f2c5..7dc119b9104eb 100644
--- a/clang/lib/Serialization/ASTReaderDecl.cpp
+++ b/clang/lib/Serialization/ASTReaderDecl.cpp
@@ -2697,6 +2697,14 @@ void 
ASTDeclReader::VisitVarTemplatePartialSpecializationDecl(
 
   RedeclarableResult Redecl = VisitVarTemplateSpecializationDeclImpl(D);
 
+  // A partial specialization whose primary template is invalid cannot be
+  // instantiated. Mark it invalid too so InstantiateClass skips it, the same
+  // way it already skips the invalid primary, instead of later looking for an
+  // instantiated primary that was never created.
+  if (VarTemplateDecl *Primary = D->getSpecializedTemplate();
+      Primary && Primary->isInvalidDecl())
+    D->setInvalidDecl();
+
   // These are read/set from/to the first declaration.
   if (ThisDeclID == Redecl.getFirstID()) {
     D->InstantiatedFromMember.setPointer(

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

Reply via email to