Skip to content
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

[Clang][Sema] Diagnose variable template explicit specializations with storage-class-specifiers #93873

Merged
merged 5 commits into from
Jun 18, 2024
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
1 change: 1 addition & 0 deletions clang/docs/ReleaseNotes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -865,6 +865,7 @@ Bug Fixes to C++ Support
- Fixed a failed assertion when attempting to convert an integer representing the difference
between the addresses of two labels (a GNU extension) to a pointer within a constant expression. (#GH95366).
- Fix immediate escalation bugs in the presence of dependent call arguments. (#GH94935)
- Clang now diagnoses explicit specializations with storage class specifiers in all contexts.


Bug Fixes to AST Handling
Expand Down
3 changes: 0 additions & 3 deletions clang/include/clang/Basic/DiagnosticSemaKinds.td
Original file line number Diff line number Diff line change
Expand Up @@ -5360,9 +5360,6 @@ def err_not_class_template_specialization : Error<
"parameter}0">;
def ext_explicit_specialization_storage_class : ExtWarn<
"explicit specialization cannot have a storage class">;
def err_explicit_specialization_inconsistent_storage_class : Error<
"explicit specialization has extraneous, inconsistent storage class "
"'%select{none|extern|static|__private_extern__|auto|register}0'">;
def err_dependent_function_template_spec_no_match : Error<
"no candidate function template was found for dependent"
" %select{member|friend}0 function template specialization">;
Expand Down
5 changes: 3 additions & 2 deletions clang/lib/Parse/ParseDeclCXX.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3162,7 +3162,8 @@ Parser::DeclGroupPtrTy Parser::ParseCXXClassMemberDeclaration(
DeclSpec::SCS_static &&
DeclaratorInfo.getDeclSpec().getStorageClassSpec() !=
DeclSpec::SCS_typedef &&
!DS.isFriendSpecified()) {
!DS.isFriendSpecified() &&
TemplateInfo.Kind == ParsedTemplateInfo::NonTemplate) {
// It's a default member initializer.
if (BitfieldSize.get())
Diag(Tok, getLangOpts().CPlusPlus20
Expand Down Expand Up @@ -3261,7 +3262,7 @@ Parser::DeclGroupPtrTy Parser::ParseCXXClassMemberDeclaration(
} else if (ThisDecl)
Actions.AddInitializerToDecl(ThisDecl, Init.get(),
EqualLoc.isInvalid());
} else if (ThisDecl && DS.getStorageClassSpec() == DeclSpec::SCS_static)
} else if (ThisDecl && DeclaratorInfo.isStaticMember())
// No initializer.
Actions.ActOnUninitializedDecl(ThisDecl);

Expand Down
1 change: 1 addition & 0 deletions clang/lib/Sema/DeclSpec.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -416,6 +416,7 @@ bool Declarator::isDeclarationOfFunction() const {
bool Declarator::isStaticMember() {
assert(getContext() == DeclaratorContext::Member);
return getDeclSpec().getStorageClassSpec() == DeclSpec::SCS_static ||
(!isDeclarationOfFunction() && !getTemplateParameterLists().empty()) ||
(getName().getKind() == UnqualifiedIdKind::IK_OperatorFunctionId &&
CXXMethodDecl::isStaticOverloadedOperator(
getName().OperatorFunctionId.Operator));
Expand Down
248 changes: 134 additions & 114 deletions clang/lib/Sema/SemaDecl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7609,89 +7609,16 @@ NamedDecl *Sema::ActOnVariableDeclarator(
NTCUC_AutoVar, NTCUK_Destruct);
} else {
bool Invalid = false;

if (DC->isRecord() && !CurContext->isRecord()) {
// This is an out-of-line definition of a static data member.
switch (SC) {
case SC_None:
break;
case SC_Static:
Diag(D.getDeclSpec().getStorageClassSpecLoc(),
diag::err_static_out_of_line)
<< FixItHint::CreateRemoval(D.getDeclSpec().getStorageClassSpecLoc());
break;
case SC_Auto:
case SC_Register:
case SC_Extern:
// [dcl.stc] p2: The auto or register specifiers shall be applied only
// to names of variables declared in a block or to function parameters.
// [dcl.stc] p6: The extern specifier cannot be used in the declaration
// of class members

Diag(D.getDeclSpec().getStorageClassSpecLoc(),
diag::err_storage_class_for_static_member)
<< FixItHint::CreateRemoval(D.getDeclSpec().getStorageClassSpecLoc());
break;
case SC_PrivateExtern:
llvm_unreachable("C storage class in c++!");
}
}

if (SC == SC_Static && CurContext->isRecord()) {
if (const CXXRecordDecl *RD = dyn_cast<CXXRecordDecl>(DC)) {
// Walk up the enclosing DeclContexts to check for any that are
// incompatible with static data members.
const DeclContext *FunctionOrMethod = nullptr;
const CXXRecordDecl *AnonStruct = nullptr;
for (DeclContext *Ctxt = DC; Ctxt; Ctxt = Ctxt->getParent()) {
if (Ctxt->isFunctionOrMethod()) {
FunctionOrMethod = Ctxt;
break;
}
const CXXRecordDecl *ParentDecl = dyn_cast<CXXRecordDecl>(Ctxt);
if (ParentDecl && !ParentDecl->getDeclName()) {
AnonStruct = ParentDecl;
break;
}
}
if (FunctionOrMethod) {
// C++ [class.static.data]p5: A local class shall not have static data
// members.
Diag(D.getIdentifierLoc(),
diag::err_static_data_member_not_allowed_in_local_class)
<< Name << RD->getDeclName()
<< llvm::to_underlying(RD->getTagKind());
} else if (AnonStruct) {
// C++ [class.static.data]p4: Unnamed classes and classes contained
// directly or indirectly within unnamed classes shall not contain
// static data members.
Diag(D.getIdentifierLoc(),
diag::err_static_data_member_not_allowed_in_anon_struct)
<< Name << llvm::to_underlying(AnonStruct->getTagKind());
Invalid = true;
} else if (RD->isUnion()) {
// C++98 [class.union]p1: If a union contains a static data member,
// the program is ill-formed. C++11 drops this restriction.
Diag(D.getIdentifierLoc(),
getLangOpts().CPlusPlus11
? diag::warn_cxx98_compat_static_data_member_in_union
: diag::ext_static_data_member_in_union) << Name;
}
}
}

// Match up the template parameter lists with the scope specifier, then
// determine whether we have a template or a template specialization.
bool InvalidScope = false;
TemplateParams = MatchTemplateParametersToScopeSpecifier(
D.getDeclSpec().getBeginLoc(), D.getIdentifierLoc(),
D.getCXXScopeSpec(),
D.getName().getKind() == UnqualifiedIdKind::IK_TemplateId
? D.getName().TemplateId
: nullptr,
TemplateParamLists,
/*never a friend*/ false, IsMemberSpecialization, InvalidScope);
Invalid |= InvalidScope;
/*never a friend*/ false, IsMemberSpecialization, Invalid);

if (TemplateParams) {
if (!TemplateParams->size() &&
Expand Down Expand Up @@ -7734,6 +7661,102 @@ NamedDecl *Sema::ActOnVariableDeclarator(
"should have a 'template<>' for this decl");
}

bool IsExplicitSpecialization =
IsVariableTemplateSpecialization && !IsPartialSpecialization;

// C++ [temp.expl.spec]p2:
// The declaration in an explicit-specialization shall not be an
// export-declaration. An explicit specialization shall not use a
// storage-class-specifier other than thread_local.
//
// We use the storage-class-specifier from DeclSpec because we may have
// added implicit 'extern' for declarations with __declspec(dllimport)!
if (SCSpec != DeclSpec::SCS_unspecified &&
(IsExplicitSpecialization || IsMemberSpecialization)) {
Diag(D.getDeclSpec().getStorageClassSpecLoc(),
diag::ext_explicit_specialization_storage_class)
<< FixItHint::CreateRemoval(D.getDeclSpec().getStorageClassSpecLoc());
}

if (CurContext->isRecord()) {
if (SC == SC_Static) {
if (const CXXRecordDecl *RD = dyn_cast<CXXRecordDecl>(DC)) {
// Walk up the enclosing DeclContexts to check for any that are
// incompatible with static data members.
const DeclContext *FunctionOrMethod = nullptr;
const CXXRecordDecl *AnonStruct = nullptr;
for (DeclContext *Ctxt = DC; Ctxt; Ctxt = Ctxt->getParent()) {
if (Ctxt->isFunctionOrMethod()) {
FunctionOrMethod = Ctxt;
break;
}
const CXXRecordDecl *ParentDecl = dyn_cast<CXXRecordDecl>(Ctxt);
if (ParentDecl && !ParentDecl->getDeclName()) {
AnonStruct = ParentDecl;
break;
}
}
if (FunctionOrMethod) {
// C++ [class.static.data]p5: A local class shall not have static
// data members.
Diag(D.getIdentifierLoc(),
diag::err_static_data_member_not_allowed_in_local_class)
<< Name << RD->getDeclName()
<< llvm::to_underlying(RD->getTagKind());
} else if (AnonStruct) {
// C++ [class.static.data]p4: Unnamed classes and classes contained
// directly or indirectly within unnamed classes shall not contain
// static data members.
Diag(D.getIdentifierLoc(),
diag::err_static_data_member_not_allowed_in_anon_struct)
<< Name << llvm::to_underlying(AnonStruct->getTagKind());
Invalid = true;
} else if (RD->isUnion()) {
// C++98 [class.union]p1: If a union contains a static data member,
// the program is ill-formed. C++11 drops this restriction.
Diag(D.getIdentifierLoc(),
getLangOpts().CPlusPlus11
? diag::warn_cxx98_compat_static_data_member_in_union
: diag::ext_static_data_member_in_union)
<< Name;
}
}
} else if (IsVariableTemplate || IsPartialSpecialization) {
// There is no such thing as a member field template.
Diag(D.getIdentifierLoc(), diag::err_template_member)
<< II << TemplateParams->getSourceRange();
// Recover by pretending this is a static data member template.
SC = SC_Static;
}
} else if (DC->isRecord()) {
// This is an out-of-line definition of a static data member.
switch (SC) {
case SC_None:
break;
case SC_Static:
Diag(D.getDeclSpec().getStorageClassSpecLoc(),
diag::err_static_out_of_line)
<< FixItHint::CreateRemoval(
D.getDeclSpec().getStorageClassSpecLoc());
break;
case SC_Auto:
case SC_Register:
case SC_Extern:
// [dcl.stc] p2: The auto or register specifiers shall be applied only
// to names of variables declared in a block or to function parameters.
// [dcl.stc] p6: The extern specifier cannot be used in the declaration
// of class members

Diag(D.getDeclSpec().getStorageClassSpecLoc(),
diag::err_storage_class_for_static_member)
<< FixItHint::CreateRemoval(
D.getDeclSpec().getStorageClassSpecLoc());
break;
case SC_PrivateExtern:
llvm_unreachable("C storage class in c++!");
}
}

if (IsVariableTemplateSpecialization) {
SourceLocation TemplateKWLoc =
TemplateParamLists.size() > 0
Expand Down Expand Up @@ -7779,8 +7802,6 @@ NamedDecl *Sema::ActOnVariableDeclarator(
// the variable (matching the scope specifier), store them.
// An explicit variable template specialization does not own any template
// parameter lists.
bool IsExplicitSpecialization =
IsVariableTemplateSpecialization && !IsPartialSpecialization;
unsigned VDTemplateParamLists =
(TemplateParams && !IsExplicitSpecialization) ? 1 : 0;
if (TemplateParamLists.size() > VDTemplateParamLists)
Expand Down Expand Up @@ -10210,25 +10231,45 @@ Sema::ActOnFunctionDeclarator(Scope *S, Declarator &D, DeclContext *DC,
NewFD->setImplicitlyInline(ImplicitInlineCXX20);
}

if (SC == SC_Static && isa<CXXMethodDecl>(NewFD) &&
!CurContext->isRecord()) {
// C++ [class.static]p1:
// A data or function member of a class may be declared static
// in a class definition, in which case it is a static member of
// the class.
if (!isFriend && SC != SC_None) {
// C++ [temp.expl.spec]p2:
// The declaration in an explicit-specialization shall not be an
// export-declaration. An explicit specialization shall not use a
// storage-class-specifier other than thread_local.
//
// We diagnose friend declarations with storage-class-specifiers
// elsewhere.
if (isFunctionTemplateSpecialization || isMemberSpecialization) {
Diag(D.getDeclSpec().getStorageClassSpecLoc(),
diag::ext_explicit_specialization_storage_class)
<< FixItHint::CreateRemoval(
D.getDeclSpec().getStorageClassSpecLoc());
}

// Complain about the 'static' specifier if it's on an out-of-line
// member function definition.
if (SC == SC_Static && !CurContext->isRecord() && DC->isRecord()) {
assert(isa<CXXMethodDecl>(NewFD) &&
"Out-of-line member function should be a CXXMethodDecl");
// C++ [class.static]p1:
// A data or function member of a class may be declared static
// in a class definition, in which case it is a static member of
// the class.

// MSVC permits the use of a 'static' storage specifier on an out-of-line
// member function template declaration and class member template
// declaration (MSVC versions before 2015), warn about this.
Diag(D.getDeclSpec().getStorageClassSpecLoc(),
((!getLangOpts().isCompatibleWithMSVC(LangOptions::MSVC2015) &&
cast<CXXRecordDecl>(DC)->getDescribedClassTemplate()) ||
(getLangOpts().MSVCCompat && NewFD->getDescribedFunctionTemplate()))
? diag::ext_static_out_of_line : diag::err_static_out_of_line)
<< FixItHint::CreateRemoval(D.getDeclSpec().getStorageClassSpecLoc());
// Complain about the 'static' specifier if it's on an out-of-line
// member function definition.

// MSVC permits the use of a 'static' storage specifier on an
// out-of-line member function template declaration and class member
// template declaration (MSVC versions before 2015), warn about this.
Diag(D.getDeclSpec().getStorageClassSpecLoc(),
((!getLangOpts().isCompatibleWithMSVC(LangOptions::MSVC2015) &&
cast<CXXRecordDecl>(DC)->getDescribedClassTemplate()) ||
(getLangOpts().MSVCCompat &&
NewFD->getDescribedFunctionTemplate()))
? diag::ext_static_out_of_line
: diag::err_static_out_of_line)
<< FixItHint::CreateRemoval(
D.getDeclSpec().getStorageClassSpecLoc());
}
}

// C++11 [except.spec]p15:
Expand Down Expand Up @@ -10596,27 +10637,6 @@ Sema::ActOnFunctionDeclarator(Scope *S, Declarator &D, DeclContext *DC,
Previous))
NewFD->setInvalidDecl();
}

// C++ [dcl.stc]p1:
// A storage-class-specifier shall not be specified in an explicit
// specialization (14.7.3)
// FIXME: We should be checking this for dependent specializations.
FunctionTemplateSpecializationInfo *Info =
NewFD->getTemplateSpecializationInfo();
if (Info && SC != SC_None) {
if (SC != Info->getTemplate()->getTemplatedDecl()->getStorageClass())
Diag(NewFD->getLocation(),
diag::err_explicit_specialization_inconsistent_storage_class)
<< SC
<< FixItHint::CreateRemoval(
D.getDeclSpec().getStorageClassSpecLoc());

else
Diag(NewFD->getLocation(),
diag::ext_explicit_specialization_storage_class)
<< FixItHint::CreateRemoval(
D.getDeclSpec().getStorageClassSpecLoc());
}
} else if (isMemberSpecialization && isa<CXXMethodDecl>(NewFD)) {
if (CheckMemberSpecialization(NewFD, Previous))
NewFD->setInvalidDecl();
Expand Down
28 changes: 3 additions & 25 deletions clang/lib/Sema/SemaDeclCXX.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3508,9 +3508,9 @@ Sema::ActOnCXXMemberDeclarator(Scope *S, AccessSpecifier AS, Declarator &D,
break;
}

bool isInstField = ((DS.getStorageClassSpec() == DeclSpec::SCS_unspecified ||
DS.getStorageClassSpec() == DeclSpec::SCS_mutable) &&
!isFunc);
bool isInstField = (DS.getStorageClassSpec() == DeclSpec::SCS_unspecified ||
DS.getStorageClassSpec() == DeclSpec::SCS_mutable) &&
!isFunc && TemplateParameterLists.empty();

if (DS.hasConstexprSpecifier() && isInstField) {
SemaDiagnosticBuilder B =
Expand Down Expand Up @@ -3559,28 +3559,6 @@ Sema::ActOnCXXMemberDeclarator(Scope *S, AccessSpecifier AS, Declarator &D,
}

IdentifierInfo *II = Name.getAsIdentifierInfo();

// Member field could not be with "template" keyword.
// So TemplateParameterLists should be empty in this case.
if (TemplateParameterLists.size()) {
TemplateParameterList* TemplateParams = TemplateParameterLists[0];
if (TemplateParams->size()) {
// There is no such thing as a member field template.
Diag(D.getIdentifierLoc(), diag::err_template_member)
<< II
<< SourceRange(TemplateParams->getTemplateLoc(),
TemplateParams->getRAngleLoc());
} else {
// There is an extraneous 'template<>' for this member.
Diag(TemplateParams->getTemplateLoc(),
diag::err_template_member_noparams)
<< II
<< SourceRange(TemplateParams->getTemplateLoc(),
TemplateParams->getRAngleLoc());
}
return nullptr;
}

if (D.getName().getKind() == UnqualifiedIdKind::IK_TemplateId) {
Diag(D.getIdentifierLoc(), diag::err_member_with_template_arguments)
<< II
Expand Down
5 changes: 3 additions & 2 deletions clang/test/CXX/dcl.dcl/dcl.spec/dcl.stc/p1.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ template<typename T> void f(T) {}
template<typename T> static void g(T) {}


template<> static void f<int>(int); // expected-error{{explicit specialization has extraneous, inconsistent storage class 'static'}}
template<> static void f<int>(int); // expected-warning{{explicit specialization cannot have a storage class}}
template static void f<float>(float); // expected-error{{explicit instantiation cannot have a storage class}}

template<> void f<double>(double);
Expand All @@ -29,4 +29,5 @@ int X<T>::value = 17;

template static int X<int>::value; // expected-error{{explicit instantiation cannot have a storage class}}

template<> static int X<float>::value; // expected-error{{'static' can only be specified inside the class definition}}
template<> static int X<float>::value; // expected-warning{{explicit specialization cannot have a storage class}}
// expected-error@-1{{'static' can only be specified inside the class definition}}
Loading
Loading