Skip to content

[Clang][Sema] Handle invalid variable template specialization whose type depends on itself #134522

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
May 7, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions clang/docs/ReleaseNotes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -658,6 +658,8 @@ Bug Fixes to C++ Support
referred to a reference to an incomplete type. (#GH129397)
- Fixed a crash when a cast involved a parenthesized aggregate initialization in dependent context. (#GH72880)
- Fixed a crash when forming an invalid function type in a dependent context. (#GH138657) (#GH115725) (#GH68852)
- No longer crashes when instantiating invalid variable template specialization
whose type depends on itself. (#GH51347), (#GH55872)

Bug Fixes to AST Handling
^^^^^^^^^^^^^^^^^^^^^^^^^
Expand Down
5 changes: 5 additions & 0 deletions clang/include/clang/AST/ASTContext.h
Original file line number Diff line number Diff line change
Expand Up @@ -2970,6 +2970,11 @@ class ASTContext : public RefCountedBase<ASTContext> {
TemplateTemplateParmDecl *insertCanonicalTemplateTemplateParmDeclInternal(
TemplateTemplateParmDecl *CanonTTP) const;

/// Determine whether the given template arguments \p Arg1 and \p Arg2 are
/// equivalent.
bool isSameTemplateArgument(const TemplateArgument &Arg1,
const TemplateArgument &Arg2) const;

/// Type Query functions. If the type is an instance of the specified class,
/// return the Type pointer for the underlying maximally pretty type. This
/// is a member of ASTContext because this may need to do some amount of
Expand Down
14 changes: 11 additions & 3 deletions clang/include/clang/Basic/DiagnosticSemaKinds.td
Original file line number Diff line number Diff line change
Expand Up @@ -2526,9 +2526,14 @@ def note_implicit_deduction_guide : Note<"implicit deduction guide declared as '
def warn_cxx98_compat_auto_type_specifier : Warning<
"'auto' type specifier is incompatible with C++98">,
InGroup<CXX98Compat>, DefaultIgnore;
def err_auto_variable_cannot_appear_in_own_initializer : Error<
"variable %0 declared with deduced type %1 "
"cannot appear in its own initializer">;
def err_auto_variable_cannot_appear_in_own_initializer
: Error<
"%enum_select<ParsingInitFor>{%Var{variable}|"
"%VarTemplate{variable template}|"
"%VarTemplatePartialSpec{variable template partial specialization}|"
"%VarTemplateExplicitSpec{variable template explicit "
"specialization}}0 %1 "
"declared with deduced type %2 cannot appear in its own initializer">;
def err_binding_cannot_appear_in_own_initializer : Error<
"binding %0 cannot appear in the initializer of its own "
"decomposition declaration">;
Expand Down Expand Up @@ -5302,6 +5307,9 @@ def err_template_member_noparams : Error<
"extraneous 'template<>' in declaration of member %0">;
def err_template_tag_noparams : Error<
"extraneous 'template<>' in declaration of %0 %1">;
def err_var_template_spec_type_depends_on_self : Error<
"the type of variable template specialization %0 declared with deduced type "
"%1 depends on itself">;

def warn_unqualified_call_to_std_cast_function : Warning<
"unqualified call to '%0'">, InGroup<DiagGroup<"unqualified-std-cast-call">>;
Expand Down
49 changes: 49 additions & 0 deletions clang/lib/AST/ASTContext.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7660,6 +7660,55 @@ ASTContext::getCanonicalTemplateArgument(const TemplateArgument &Arg) const {
llvm_unreachable("Unhandled template argument kind");
}

bool ASTContext::isSameTemplateArgument(const TemplateArgument &Arg1,
const TemplateArgument &Arg2) const {
if (Arg1.getKind() != Arg2.getKind())
return false;

switch (Arg1.getKind()) {
case TemplateArgument::Null:
llvm_unreachable("Comparing NULL template argument");

case TemplateArgument::Type:
return hasSameType(Arg1.getAsType(), Arg2.getAsType());

case TemplateArgument::Declaration:
return Arg1.getAsDecl()->getUnderlyingDecl()->getCanonicalDecl() ==
Arg2.getAsDecl()->getUnderlyingDecl()->getCanonicalDecl();

case TemplateArgument::NullPtr:
return hasSameType(Arg1.getNullPtrType(), Arg2.getNullPtrType());

case TemplateArgument::Template:
case TemplateArgument::TemplateExpansion:
return getCanonicalTemplateName(Arg1.getAsTemplateOrTemplatePattern()) ==
getCanonicalTemplateName(Arg2.getAsTemplateOrTemplatePattern());

case TemplateArgument::Integral:
return llvm::APSInt::isSameValue(Arg1.getAsIntegral(),
Arg2.getAsIntegral());

case TemplateArgument::StructuralValue:
return Arg1.structurallyEquals(Arg2);

case TemplateArgument::Expression: {
llvm::FoldingSetNodeID ID1, ID2;
Arg1.getAsExpr()->Profile(ID1, *this, /*Canonical=*/true);
Arg2.getAsExpr()->Profile(ID2, *this, /*Canonical=*/true);
return ID1 == ID2;
}

case TemplateArgument::Pack:
return llvm::equal(
Arg1.getPackAsArray(), Arg2.getPackAsArray(),
[&](const TemplateArgument &Arg1, const TemplateArgument &Arg2) {
return isSameTemplateArgument(Arg1, Arg2);
});
}

llvm_unreachable("Unhandled template argument kind");
}

NestedNameSpecifier *
ASTContext::getCanonicalNestedNameSpecifier(NestedNameSpecifier *NNS) const {
if (!NNS)
Expand Down
3 changes: 2 additions & 1 deletion clang/lib/Sema/SemaExpr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -251,7 +251,8 @@ bool Sema::DiagnoseUseOfDecl(NamedDecl *D, ArrayRef<SourceLocation> Locs,
<< D->getDeclName();
} else {
Diag(Loc, diag::err_auto_variable_cannot_appear_in_own_initializer)
<< D->getDeclName() << cast<VarDecl>(D)->getType();
<< diag::ParsingInitFor::Var << D->getDeclName()
<< cast<VarDecl>(D)->getType();
}
return true;
}
Expand Down
51 changes: 50 additions & 1 deletion clang/lib/Sema/SemaTemplate.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4372,15 +4372,64 @@ Sema::CheckVarTemplateId(VarTemplateDecl *Template, SourceLocation TemplateLoc,
// Produce a placeholder value if the specialization is dependent.
if (Template->getDeclContext()->isDependentContext() ||
TemplateSpecializationType::anyDependentTemplateArguments(
TemplateArgs, CTAI.CanonicalConverted))
TemplateArgs, CTAI.CanonicalConverted)) {
if (ParsingInitForAutoVars.empty())
return DeclResult();

auto IsSameTemplateArg = [&](const TemplateArgument &Arg1,
const TemplateArgument &Arg2) {
return Context.isSameTemplateArgument(Arg1, Arg2);
};

if (VarDecl *Var = Template->getTemplatedDecl();
ParsingInitForAutoVars.count(Var) &&
llvm::equal(
CTAI.CanonicalConverted,
Template->getTemplateParameters()->getInjectedTemplateArgs(Context),
IsSameTemplateArg)) {
Diag(TemplateNameLoc,
diag::err_auto_variable_cannot_appear_in_own_initializer)
<< diag::ParsingInitFor::VarTemplate << Var << Var->getType();
return true;
}

SmallVector<VarTemplatePartialSpecializationDecl *, 4> PartialSpecs;
Template->getPartialSpecializations(PartialSpecs);
for (VarTemplatePartialSpecializationDecl *Partial : PartialSpecs)
if (ParsingInitForAutoVars.count(Partial) &&
llvm::equal(CTAI.CanonicalConverted,
Partial->getTemplateArgs().asArray(),
IsSameTemplateArg)) {
Diag(TemplateNameLoc,
diag::err_auto_variable_cannot_appear_in_own_initializer)
<< diag::ParsingInitFor::VarTemplatePartialSpec << Partial
<< Partial->getType();
return true;
}

return DeclResult();
}

// Find the variable template specialization declaration that
// corresponds to these arguments.
void *InsertPos = nullptr;
if (VarTemplateSpecializationDecl *Spec =
Template->findSpecialization(CTAI.CanonicalConverted, InsertPos)) {
checkSpecializationReachability(TemplateNameLoc, Spec);
if (Spec->getType()->isUndeducedType()) {
if (ParsingInitForAutoVars.count(Spec))
Diag(TemplateNameLoc,
diag::err_auto_variable_cannot_appear_in_own_initializer)
<< diag::ParsingInitFor::VarTemplateExplicitSpec << Spec
<< Spec->getType();
else
// We are substituting the initializer of this variable template
// specialization.
Diag(TemplateNameLoc, diag::err_var_template_spec_type_depends_on_self)
<< Spec << Spec->getType();

return true;
}
// If we already have a variable template specialization, return it.
return Spec;
}
Expand Down
83 changes: 3 additions & 80 deletions clang/lib/Sema/SemaTemplateDeduction.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -114,27 +114,6 @@ namespace clang {
using namespace clang;
using namespace sema;

/// Compare two APSInts, extending and switching the sign as
/// necessary to compare their values regardless of underlying type.
static bool hasSameExtendedValue(llvm::APSInt X, llvm::APSInt Y) {
if (Y.getBitWidth() > X.getBitWidth())
X = X.extend(Y.getBitWidth());
else if (Y.getBitWidth() < X.getBitWidth())
Y = Y.extend(X.getBitWidth());

// If there is a signedness mismatch, correct it.
if (X.isSigned() != Y.isSigned()) {
// If the signed value is negative, then the values cannot be the same.
if ((Y.isSigned() && Y.isNegative()) || (X.isSigned() && X.isNegative()))
return false;

Y.setIsSigned(true);
X.setIsSigned(true);
}

return X == Y;
}

/// The kind of PartialOrdering we're performing template argument deduction
/// for (C++11 [temp.deduct.partial]).
enum class PartialOrderingKind { None, NonCall, Call };
Expand Down Expand Up @@ -273,7 +252,7 @@ checkDeducedTemplateArguments(ASTContext &Context,
if (Y.getKind() == TemplateArgument::Expression ||
Y.getKind() == TemplateArgument::Declaration ||
(Y.getKind() == TemplateArgument::Integral &&
hasSameExtendedValue(X.getAsIntegral(), Y.getAsIntegral())))
llvm::APSInt::isSameValue(X.getAsIntegral(), Y.getAsIntegral())))
return X.wasDeducedFromArrayBound() ? Y : X;

// All other combinations are incompatible.
Expand Down Expand Up @@ -2574,7 +2553,7 @@ DeduceTemplateArguments(Sema &S, TemplateParameterList *TemplateParams,

case TemplateArgument::Integral:
if (A.getKind() == TemplateArgument::Integral) {
if (hasSameExtendedValue(P.getAsIntegral(), A.getAsIntegral()))
if (llvm::APSInt::isSameValue(P.getAsIntegral(), A.getAsIntegral()))
return TemplateDeductionResult::Success;
}
Info.FirstArg = P;
Expand Down Expand Up @@ -2828,62 +2807,6 @@ TemplateDeductionResult Sema::DeduceTemplateArguments(
/*HasDeducedAnyParam=*/nullptr);
}

/// Determine whether two template arguments are the same.
static bool isSameTemplateArg(ASTContext &Context, const TemplateArgument &X,
const TemplateArgument &Y) {
if (X.getKind() != Y.getKind())
return false;

switch (X.getKind()) {
case TemplateArgument::Null:
llvm_unreachable("Comparing NULL template argument");

case TemplateArgument::Type:
return Context.getCanonicalType(X.getAsType()) ==
Context.getCanonicalType(Y.getAsType());

case TemplateArgument::Declaration:
return isSameDeclaration(X.getAsDecl(), Y.getAsDecl());

case TemplateArgument::NullPtr:
return Context.hasSameType(X.getNullPtrType(), Y.getNullPtrType());

case TemplateArgument::Template:
case TemplateArgument::TemplateExpansion:
return Context.getCanonicalTemplateName(
X.getAsTemplateOrTemplatePattern()).getAsVoidPointer() ==
Context.getCanonicalTemplateName(
Y.getAsTemplateOrTemplatePattern()).getAsVoidPointer();

case TemplateArgument::Integral:
return hasSameExtendedValue(X.getAsIntegral(), Y.getAsIntegral());

case TemplateArgument::StructuralValue:
return X.structurallyEquals(Y);

case TemplateArgument::Expression: {
llvm::FoldingSetNodeID XID, YID;
X.getAsExpr()->Profile(XID, Context, true);
Y.getAsExpr()->Profile(YID, Context, true);
return XID == YID;
}

case TemplateArgument::Pack: {
unsigned PackIterationSize = X.pack_size();
if (X.pack_size() != Y.pack_size())
return false;
ArrayRef<TemplateArgument> XP = X.pack_elements();
ArrayRef<TemplateArgument> YP = Y.pack_elements();
for (unsigned i = 0; i < PackIterationSize; ++i)
if (!isSameTemplateArg(Context, XP[i], YP[i]))
return false;
return true;
}
}

llvm_unreachable("Invalid TemplateArgument Kind!");
}

TemplateArgumentLoc
Sema::getTrivialTemplateArgumentLoc(const TemplateArgument &Arg,
QualType NTTPType, SourceLocation Loc,
Expand Down Expand Up @@ -3349,7 +3272,7 @@ static TemplateDeductionResult FinishTemplateArgumentDeduction(
break;
TemplateArgument PP = P.isPackExpansion() ? P.getPackExpansionPattern() : P,
PA = A.isPackExpansion() ? A.getPackExpansionPattern() : A;
if (!isSameTemplateArg(S.Context, PP, PA)) {
if (!S.Context.isSameTemplateArgument(PP, PA)) {
if (!P.isPackExpansion() && !A.isPackExpansion()) {
Info.Param = makeTemplateParameter(TPL->getParam(
(AsStack.empty() ? As.end() : AsStack.back().begin()) -
Expand Down
20 changes: 20 additions & 0 deletions clang/test/SemaCXX/cxx1y-variable-templates_in_class.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -412,6 +412,26 @@ namespace dependent_static_var_template {
}

int cf() { return F<int>(); }

#ifdef CPP1Y
namespace GH55872 {
struct s {
template<typename T>
static CONST auto f = [] { return T::template g<s>; };
// expected-note@-1 {{in instantiation of static data member 'dependent_static_var_template::GH55872::t::g' requested here}}
// expected-note@-2 {{while substituting into a lambda expression here}}
};

struct t {
template<typename T>
static CONST auto g = [] { return T::template f<t>; };
// expected-error@-1 {{the type of variable template specialization 'f<dependent_static_var_template::GH55872::t>' declared with deduced type 'const auto' depends on itself}}
// expected-note@-2 {{while substituting into a lambda expression here}}
};

void test() { s::f<t>()(); } // expected-note {{in instantiation of static data member 'dependent_static_var_template::GH55872::s::f' requested here}}
}
#endif
}

#ifndef PRECXX11
Expand Down
17 changes: 17 additions & 0 deletions clang/test/SemaCXX/cxx1y-variable-templates_top_level.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -492,4 +492,21 @@ static_assert(C<int, 0,1,2,3,4>::VALUEARRAY[3] == 3, "");
static_assert(C<int, 0,1,2,3,4>::VALUEARRAY[0] == 0, "");

}

namespace appear_in_its_own_init {
template <class T>
auto GH51347 = GH51347<T>; // expected-error {{variable template 'GH51347' declared with deduced type 'auto' cannot appear in its own initializer}}

template <class T, class... Ts>
auto a = [] {
using U = T;
a<U, Ts...>; // expected-error {{variable template 'a' declared with deduced type 'auto' cannot appear in its own initializer}}
};

template <int...> int b;
template <int I>
auto b<I, I * 2, 5> = b<I, I * 2, 5l>; // expected-error {{variable template partial specialization 'b<I, I * 2, 5>' declared with deduced type 'auto' cannot appear in its own initializer}}
template <> auto b<0, 0, 0> = b<0, 0, 0>; // expected-error {{variable template explicit specialization 'b<0, 0, 0>' declared with deduced type 'auto' cannot appear in its own initializer}}
}

#endif
11 changes: 11 additions & 0 deletions clang/test/SemaTemplate/instantiate-var-template.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -47,3 +47,14 @@ namespace InvalidInsertPos {
template<> int v<int, 0>;
int k = v<int, 500>;
}

namespace GH97881_comment {
template <bool B>
auto g = sizeof(g<!B>);
// expected-error@-1 {{the type of variable template specialization 'g<false>'}}
// expected-note@-2 {{in instantiation of variable template specialization 'GH97881_comment::g'}}

void test() {
(void)sizeof(g<false>); // expected-note {{in instantiation of variable template specialization 'GH97881_comment::g'}}
}
}
Loading