[clang] [Clang][AST] Fix crash in ExplicitInstantiationDecl printing for variable templates with tag types (PR #197856)
via cfe-commits
cfe-commits at lists.llvm.org
Fri May 15 06:26:00 PDT 2026
https://github.com/16bit-ykiko updated https://github.com/llvm/llvm-project/pull/197856
>From a527caae1fe28ef5ca27cf38c01c9a8196d1fac0 Mon Sep 17 00:00:00 2001
From: ykiko <ykiko at clice.io>
Date: Fri, 15 May 2026 11:13:32 +0800
Subject: [PATCH] [Clang][AST] Fix ExplicitInstantiationDecl accessors
confusing declared type with class encoding
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Several ExplicitInstantiationDecl accessors (getTypeAsWritten,
getQualifierLoc, getTagKWLoc, getNumTemplateArgs, and the angle-loc
getters) used TypeLoc kind checks to distinguish class template /
nested class instantiations from function / variable template
instantiations. This was incorrect: a variable template's declared
type can be a TagType or TemplateSpecializationType, causing:
- getTypeAsWritten() returning nullptr → crash in DeclPrinter (GH#197797)
- getQualifierLoc() returning the type's qualifier instead of the
variable's → wrong output (e.g. "ns::var" instead of "var")
- getTagKWLoc() returning the type's elaborated keyword
- getNumTemplateArgs/AngleLocs extracting from the type instead of
the variable's template arguments
Add a private isClassOrNestedClassSpec() helper that checks
isa<RecordDecl>(getSpecialization()), and guard all TypeSourceInfo
fallback paths with it.
Fixes #197797
---
clang/include/clang/AST/DeclTemplate.h | 7 +++
clang/lib/AST/DeclTemplate.cpp | 38 +++++++++-------
clang/lib/Sema/SemaTemplate.cpp | 5 +--
.../AST/ast-print-explicit-instantiation.cpp | 24 ++++++++++
.../explicit-instantiation-source-info.cpp | 44 +++++++++++++++++++
5 files changed, 99 insertions(+), 19 deletions(-)
diff --git a/clang/include/clang/AST/DeclTemplate.h b/clang/include/clang/AST/DeclTemplate.h
index 9fb41c87da732..4e9f348f28847 100644
--- a/clang/include/clang/AST/DeclTemplate.h
+++ b/clang/include/clang/AST/DeclTemplate.h
@@ -3449,6 +3449,13 @@ class ExplicitInstantiationDecl final
return hasTrailingQualifier() ? 1 : 0;
}
+ /// Whether the specialization is a class template or nested class.
+ /// When true, TypeSourceInfo encodes the class itself (qualifier, tag
+ /// keyword, template arguments) rather than a declared type.
+ bool isClassOrNestedClassSpec() const {
+ return isa<RecordDecl>(getSpecialization());
+ }
+
/// Raw access to the internal TypeSourceInfo. For class templates this is
/// a TemplateSpecializationTypeLoc; for nested classes a TagTypeLoc.
/// Public getTypeAsWritten() returns null for those cases.
diff --git a/clang/lib/AST/DeclTemplate.cpp b/clang/lib/AST/DeclTemplate.cpp
index 08e6512a1c74d..0786c3ba56518 100644
--- a/clang/lib/AST/DeclTemplate.cpp
+++ b/clang/lib/AST/DeclTemplate.cpp
@@ -1843,6 +1843,8 @@ ExplicitInstantiationDecl::CreateDeserialized(ASTContext &C, GlobalDeclID ID,
}
SourceLocation ExplicitInstantiationDecl::getTagKWLoc() const {
+ if (!isClassOrNestedClassSpec())
+ return SourceLocation();
if (auto *TSI = getRawTypeSourceInfo()) {
if (auto TL = TSI->getTypeLoc().getAs<TemplateSpecializationTypeLoc>())
return TL.getElaboratedKeywordLoc();
@@ -1855,8 +1857,9 @@ SourceLocation ExplicitInstantiationDecl::getTagKWLoc() const {
NestedNameSpecifierLoc ExplicitInstantiationDecl::getQualifierLoc() const {
if (hasTrailingQualifier())
return *getTrailingObjects<NestedNameSpecifierLoc>();
- if (auto *TSI = getRawTypeSourceInfo())
- return TSI->getTypeLoc().getPrefix();
+ if (isClassOrNestedClassSpec())
+ if (auto *TSI = getRawTypeSourceInfo())
+ return TSI->getTypeLoc().getPrefix();
return NestedNameSpecifierLoc();
}
@@ -1864,20 +1867,21 @@ TypeSourceInfo *ExplicitInstantiationDecl::getTypeAsWritten() const {
auto *TSI = getRawTypeSourceInfo();
if (!TSI)
return nullptr;
- TypeLoc TL = TSI->getTypeLoc();
- // For class templates and nested classes, the "type" is fully described by
- // the unified accessors (getQualifierLoc, getTemplateArg, getTagKWLoc).
- if (TL.getAs<TemplateSpecializationTypeLoc>() || TL.getAs<TagTypeLoc>())
- return nullptr;
+ if (isClassOrNestedClassSpec()) {
+ TypeLoc TL = TSI->getTypeLoc();
+ if (TL.getAs<TemplateSpecializationTypeLoc>() || TL.getAs<TagTypeLoc>())
+ return nullptr;
+ }
return TSI;
}
unsigned ExplicitInstantiationDecl::getNumTemplateArgs() const {
if (const auto *Args = getTrailingArgsInfo())
return Args->NumTemplateArgs;
- if (auto *TSI = getRawTypeSourceInfo())
- if (auto TL = TSI->getTypeLoc().getAs<TemplateSpecializationTypeLoc>())
- return TL.getNumArgs();
+ if (isClassOrNestedClassSpec())
+ if (auto *TSI = getRawTypeSourceInfo())
+ if (auto TL = TSI->getTypeLoc().getAs<TemplateSpecializationTypeLoc>())
+ return TL.getNumArgs();
return 0;
}
@@ -1892,18 +1896,20 @@ ExplicitInstantiationDecl::getTemplateArg(unsigned I) const {
SourceLocation ExplicitInstantiationDecl::getTemplateArgsLAngleLoc() const {
if (const auto *Args = getTrailingArgsInfo())
return Args->getLAngleLoc();
- if (auto *TSI = getRawTypeSourceInfo())
- if (auto TL = TSI->getTypeLoc().getAs<TemplateSpecializationTypeLoc>())
- return TL.getLAngleLoc();
+ if (isClassOrNestedClassSpec())
+ if (auto *TSI = getRawTypeSourceInfo())
+ if (auto TL = TSI->getTypeLoc().getAs<TemplateSpecializationTypeLoc>())
+ return TL.getLAngleLoc();
return SourceLocation();
}
SourceLocation ExplicitInstantiationDecl::getTemplateArgsRAngleLoc() const {
if (const auto *Args = getTrailingArgsInfo())
return Args->getRAngleLoc();
- if (auto *TSI = getRawTypeSourceInfo())
- if (auto TL = TSI->getTypeLoc().getAs<TemplateSpecializationTypeLoc>())
- return TL.getRAngleLoc();
+ if (isClassOrNestedClassSpec())
+ if (auto *TSI = getRawTypeSourceInfo())
+ if (auto TL = TSI->getTypeLoc().getAs<TemplateSpecializationTypeLoc>())
+ return TL.getRAngleLoc();
return SourceLocation();
}
diff --git a/clang/lib/Sema/SemaTemplate.cpp b/clang/lib/Sema/SemaTemplate.cpp
index 71c2928b22d53..798efe94d82d1 100644
--- a/clang/lib/Sema/SemaTemplate.cpp
+++ b/clang/lib/Sema/SemaTemplate.cpp
@@ -10790,6 +10790,7 @@ DeclResult Sema::ActOnExplicitInstantiation(Scope *S,
VarDecl *Prev = Previous.getAsSingle<VarDecl>();
VarTemplateDecl *PrevTemplate = Previous.getAsSingle<VarTemplateDecl>();
+ const ASTTemplateArgumentListInfo *ArgsAsWritten = nullptr;
if (!PrevTemplate) {
if (!Prev || !Prev->isStaticDataMember()) {
@@ -10858,6 +10859,7 @@ DeclResult Sema::ActOnExplicitInstantiation(Scope *S,
// Ignore access control bits, we don't need them for redeclaration
// checking.
Prev = cast<VarDecl>(Res.get());
+ ArgsAsWritten = ASTTemplateArgumentListInfo::Create(Context, TemplateArgs);
}
// C++0x [temp.explicit]p2:
@@ -10912,9 +10914,6 @@ DeclResult Sema::ActOnExplicitInstantiation(Scope *S,
return true;
}
- const ASTTemplateArgumentListInfo *ArgsAsWritten = nullptr;
- if (auto *VTSD = dyn_cast<VarTemplateSpecializationDecl>(Prev))
- ArgsAsWritten = VTSD->getTemplateArgsAsWritten();
addExplicitInstantiationDecl(
Context, CurContext, Prev, ExternLoc, TemplateLoc,
D.getCXXScopeSpec().getWithLocInContext(Context), ArgsAsWritten,
diff --git a/clang/test/AST/ast-print-explicit-instantiation.cpp b/clang/test/AST/ast-print-explicit-instantiation.cpp
index 6c794e9575039..40e71881e9fdf 100644
--- a/clang/test/AST/ast-print-explicit-instantiation.cpp
+++ b/clang/test/AST/ast-print-explicit-instantiation.cpp
@@ -25,6 +25,7 @@ template int ns::var<int>;
// CHECK: extern template float ns::var<float>;
extern template float ns::var<float>;
+
template <typename T> struct X { struct Inner {}; };
// CHECK: template struct X<int>::Inner;
template struct X<int>::Inner;
@@ -58,3 +59,26 @@ void A<T>::B<U>::g(V) {}
// CHECK: template void A<int>::B<double>::g<float>(float);
template void A<int>::B<double>::g<float>(float);
+
+namespace GH197797 {
+struct S {};
+enum E { X };
+template <typename T> struct Wrap {};
+
+// Variable templates with tag / class-template-specialization declared types.
+template <typename T> T var = T{};
+// CHECK: extern template S var<S>;
+extern template S var<S>;
+// CHECK: extern template E var<E>;
+extern template E var<E>;
+// CHECK: extern template Wrap<int> var<Wrap<int>>;
+extern template Wrap<int> var<Wrap<int>>;
+
+// Variable declared type has a qualifier but the variable itself does not:
+// the type's qualifier must not leak into the variable name.
+template <typename T> T var2 = T{};
+// CHECK: extern template ns::S<int> var2<ns::S<int>>;
+extern template ns::S<int> var2<ns::S<int>>;
+// CHECK: extern template ns::S<float> var2<ns::S<float>>;
+extern template ns::S<float> var2<ns::S<float>>;
+} // namespace GH197797
diff --git a/clang/test/AST/explicit-instantiation-source-info.cpp b/clang/test/AST/explicit-instantiation-source-info.cpp
index 3086fd261dd25..b304bb6114946 100644
--- a/clang/test/AST/explicit-instantiation-source-info.cpp
+++ b/clang/test/AST/explicit-instantiation-source-info.cpp
@@ -1,5 +1,9 @@
// RUN: %clang_cc1 -fsyntax-only -ast-dump %s | FileCheck %s
+struct Plain {};
+enum Color { Red };
+template <typename T> struct Wrap {};
+
namespace ns {
template <typename T> void foo(T x) {}
template <typename T> T bar = T{};
@@ -212,3 +216,43 @@ namespace ns {
// CHECK-NEXT: TemplateArgument <col:21> type 'short'
// CHECK-NEXT: BuiltinType {{.*}} 'short'
}
+
+// GH197797: variable template with tag declared type
+extern template Plain ns::bar<Plain>;
+// CHECK: ExplicitInstantiationDecl {{.*}} <line:[[@LINE-1]]:1, col:36> col:8 explicit_instantiation_declaration extern 'bar'
+// CHECK-NEXT: NestedNameSpecifier Namespace {{.*}} 'ns'
+// CHECK-NEXT: VarTemplateSpecialization {{.*}} 'bar' 'Plain'
+// CHECK-NEXT: RecordTypeLoc <col:17> 'Plain'
+// CHECK: TemplateArgument <col:31> type 'Plain'
+
+// Definition after declaration — template arg locations must be independent.
+template Plain ns::bar<Plain>;
+// CHECK: ExplicitInstantiationDecl {{.*}} <line:[[@LINE-1]]:1, col:29> col:1 explicit_instantiation_definition 'bar'
+// CHECK-NEXT: NestedNameSpecifier Namespace {{.*}} 'ns'
+// CHECK-NEXT: VarTemplateSpecialization {{.*}} 'bar' 'Plain'
+// CHECK-NEXT: RecordTypeLoc <col:10> 'Plain'
+// CHECK: TemplateArgument <col:24> type 'Plain'
+
+// Variable template with enum declared type.
+extern template Color ns::bar<Color>;
+// CHECK: ExplicitInstantiationDecl {{.*}} <line:[[@LINE-1]]:1, col:36> col:8 explicit_instantiation_declaration extern 'bar'
+// CHECK-NEXT: NestedNameSpecifier Namespace {{.*}} 'ns'
+// CHECK-NEXT: VarTemplateSpecialization {{.*}} 'bar' 'Color'
+// CHECK-NEXT: EnumTypeLoc <col:17> 'Color'
+// CHECK: TemplateArgument <col:31> type 'Color'
+
+// Variable template with class template specialization declared type.
+extern template Wrap<int> ns::bar<Wrap<int>>;
+// CHECK: ExplicitInstantiationDecl {{.*}} <line:[[@LINE-1]]:1, col:44> col:8 explicit_instantiation_declaration extern 'bar'
+// CHECK-NEXT: NestedNameSpecifier Namespace {{.*}} 'ns'
+// CHECK-NEXT: VarTemplateSpecialization {{.*}} 'bar' 'Wrap<int>'
+// CHECK-NEXT: TemplateSpecializationTypeLoc <col:17, col:25> 'Wrap<int>'
+
+// Variable template: type has qualifier, variable does not — no leaking.
+namespace ns {
+ extern template Wrap<int> bar<Wrap<int>>;
+ // CHECK: ExplicitInstantiationDecl {{.*}} <line:[[@LINE-1]]:3, col:42> col:10 explicit_instantiation_declaration extern 'bar'
+ // CHECK-NOT: NestedNameSpecifier
+ // CHECK-NEXT: VarTemplateSpecialization {{.*}} 'bar' 'Wrap<int>'
+ // CHECK-NEXT: TemplateSpecializationTypeLoc <col:19, col:27> 'Wrap<int>'
+}
More information about the cfe-commits
mailing list