[clang] [clang] Check `std::initializer_list` more strictly (PR #133822)
via cfe-commits
cfe-commits at lists.llvm.org
Wed Apr 2 07:41:04 PDT 2025
https://github.com/offsetof updated https://github.com/llvm/llvm-project/pull/133822
>From b5798e04281fb6d9475a1ae6af8b94bc0ed85a43 Mon Sep 17 00:00:00 2001
From: offsetof <offsetof at mailo.com>
Date: Mon, 31 Mar 2025 23:17:47 +0000
Subject: [PATCH 1/2] [clang] Check `std::initializer_list` more strictly
Require `std::initializer_list` to be a class template with a
template-head equivalent to `template<class>` and no default arguments.
---
.../clang/Basic/DiagnosticSemaKinds.td | 10 ++-
clang/lib/Sema/SemaDeclCXX.cpp | 49 +++++++++---
.../SemaCXX/invalid-std-initializer-list.cpp | 77 +++++++++++++++++++
3 files changed, 124 insertions(+), 12 deletions(-)
create mode 100644 clang/test/SemaCXX/invalid-std-initializer-list.cpp
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index 5e45482584946..4476751f9952a 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -2590,8 +2590,14 @@ def err_auto_non_deduced_not_alone : Error<
def err_implied_std_initializer_list_not_found : Error<
"cannot deduce type of initializer list because std::initializer_list was "
"not found; include <initializer_list>">;
-def err_malformed_std_initializer_list : Error<
- "std::initializer_list must be a class template with a single type parameter">;
+def err_malformed_std_initializer_list
+ : Error<"std::initializer_list %select{"
+ "must have exactly one template parameter|"
+ "cannot have associated constraints|"
+ "must have a type template parameter|"
+ "cannot have default template arguments|"
+ "cannot be a variadic template|"
+ "must be a class template}0">;
def err_auto_init_list_from_c : Error<
"cannot use %select{'auto'|<ERROR>|'__auto_type'}0 with "
"%select{initializer list|array}1 in C">;
diff --git a/clang/lib/Sema/SemaDeclCXX.cpp b/clang/lib/Sema/SemaDeclCXX.cpp
index 676d53a1f4b45..77f8b6e36fcb3 100644
--- a/clang/lib/Sema/SemaDeclCXX.cpp
+++ b/clang/lib/Sema/SemaDeclCXX.cpp
@@ -12071,6 +12071,37 @@ NamespaceDecl *Sema::getOrCreateStdNamespace() {
return getStdNamespace();
}
+/// Check that the template-head of this class template is acceptable for
+/// a declaration of 'std::initializer_list', and optionally diagnose if
+/// it is not.
+/// \returns true if any issues were found.
+static bool CheckStdInitializerList(Sema &S, ClassTemplateDecl *Template,
+ bool Diagnose) {
+ TemplateParameterList *Params = Template->getTemplateParameters();
+ int ErrorKind = -1;
+
+ if (Params->size() != 1)
+ ErrorKind = 0; // must have exactly one template parameter
+ else if (Template->hasAssociatedConstraints())
+ ErrorKind = 1; // cannot have associated constraints
+ else {
+ auto *Param = dyn_cast<TemplateTypeParmDecl>(Params->getParam(0));
+ if (!Param)
+ ErrorKind = 2; // must have a type template parameter
+ else if (Param->hasDefaultArgument())
+ ErrorKind = 3; // cannot have default template arguments
+ else if (Param->isTemplateParameterPack())
+ ErrorKind = 4; // cannot be a variadic template
+ else
+ return false;
+ }
+
+ if (Diagnose)
+ S.Diag(Template->getLocation(), diag::err_malformed_std_initializer_list)
+ << Params->getSourceRange() << ErrorKind;
+ return true;
+}
+
bool Sema::isStdInitializerList(QualType Ty, QualType *Element) {
assert(getLangOpts().CPlusPlus &&
"Looking for std::initializer_list outside of C++.");
@@ -12118,10 +12149,7 @@ bool Sema::isStdInitializerList(QualType Ty, QualType *Element) {
return false;
// This is a template called std::initializer_list, but is it the right
// template?
- TemplateParameterList *Params = Template->getTemplateParameters();
- if (Params->getMinRequiredArguments() != 1)
- return false;
- if (!isa<TemplateTypeParmDecl>(Params->getParam(0)))
+ if (CheckStdInitializerList(*this, Template, /*Diagnose=*/false))
return false;
// It's the right template.
@@ -12137,7 +12165,8 @@ bool Sema::isStdInitializerList(QualType Ty, QualType *Element) {
return true;
}
-static ClassTemplateDecl *LookupStdInitializerList(Sema &S, SourceLocation Loc){
+static ClassTemplateDecl *LookupStdInitializerList(Sema &S,
+ SourceLocation Loc) {
NamespaceDecl *Std = S.getStdNamespace();
if (!Std) {
S.Diag(Loc, diag::err_implied_std_initializer_list_not_found);
@@ -12155,16 +12184,16 @@ static ClassTemplateDecl *LookupStdInitializerList(Sema &S, SourceLocation Loc){
Result.suppressDiagnostics();
// We found something weird. Complain about the first thing we found.
NamedDecl *Found = *Result.begin();
- S.Diag(Found->getLocation(), diag::err_malformed_std_initializer_list);
+ S.Diag(Found->getLocation(), diag::err_malformed_std_initializer_list)
+ << 5 /* must be a class template */;
+ S.Diag(Loc, diag::note_used_here);
return nullptr;
}
// We found some template called std::initializer_list. Now verify that it's
// correct.
- TemplateParameterList *Params = Template->getTemplateParameters();
- if (Params->getMinRequiredArguments() != 1 ||
- !isa<TemplateTypeParmDecl>(Params->getParam(0))) {
- S.Diag(Template->getLocation(), diag::err_malformed_std_initializer_list);
+ if (CheckStdInitializerList(S, Template, /*Diagnose=*/true)) {
+ S.Diag(Loc, diag::note_used_here);
return nullptr;
}
diff --git a/clang/test/SemaCXX/invalid-std-initializer-list.cpp b/clang/test/SemaCXX/invalid-std-initializer-list.cpp
new file mode 100644
index 0000000000000..339accefbb6d1
--- /dev/null
+++ b/clang/test/SemaCXX/invalid-std-initializer-list.cpp
@@ -0,0 +1,77 @@
+// RUN: %clang_cc1 %s -verify=expected,type-param -std=c++23 -DTYPE_PARAM
+// RUN: %clang_cc1 %s -verify=expected,others -std=c++23 -DCONSTANT_PARAM
+// RUN: %clang_cc1 %s -verify=expected,others -std=c++23 -DTYPE_TEMPLATE_PARAM
+// RUN: %clang_cc1 %s -verify=expected,others -std=c++23 -DDEFAULT_ARG
+// RUN: %clang_cc1 %s -verify=expected,others -std=c++23 -DMULTIPLE_PARAMS
+// RUN: %clang_cc1 %s -verify=expected,others -std=c++23 -DPARAM_PACK
+// RUN: %clang_cc1 %s -verify=expected,others -std=c++23 -DCONSTRAINED_PARAM
+// RUN: %clang_cc1 %s -verify=expected,others -std=c++23 -DREQUIRES_CLAUSE
+// RUN: %clang_cc1 %s -verify=expected,others -std=c++23 -DNONCLASS_TEMPLATE
+// RUN: %clang_cc1 %s -verify=expected,others -std=c++23 -DNONTEMPLATE
+
+namespace std {
+
+#ifdef TYPE_PARAM
+template<class> class initializer_list;
+// expected-note at -1 2 {{template is declared here}}
+#elifdef CONSTANT_PARAM
+template<int> class initializer_list;
+// expected-error at -1 2 {{std::initializer_list must have a type template parameter}}
+#elifdef TYPE_TEMPLATE_PARAM
+template<template<class> class> class initializer_list;
+// expected-error at -1 2 {{std::initializer_list must have a type template parameter}}
+#elifdef DEFAULT_ARG
+template<class = int> class initializer_list;
+// expected-error at -1 2 {{std::initializer_list cannot have default template arguments}}
+#elifdef MULTIPLE_PARAMS
+template<class, class> class initializer_list;
+// expected-error at -1 2 {{std::initializer_list must have exactly one template parameter}}
+#elifdef PARAM_PACK
+template<class...> class initializer_list;
+// expected-error at -1 2 {{std::initializer_list cannot be a variadic template}}
+#elifdef CONSTRAINED_PARAM
+template<class> concept C = true;
+template<C> class initializer_list;
+// expected-error at -1 2 {{std::initializer_list cannot have associated constraints}}
+#elifdef REQUIRES_CLAUSE
+template<class> requires true class initializer_list;
+// expected-error at -1 2 {{std::initializer_list cannot have associated constraints}}
+#elifdef NONCLASS_TEMPLATE
+template<class> class IL;
+template<class T> using initializer_list = IL<T>;
+// expected-error at -1 2 {{std::initializer_list must be a class template}}
+#elifdef NONTEMPLATE
+class initializer_list;
+// expected-error at -1 2 {{std::initializer_list must be a class template}}
+#else
+#error Unexpected test kind
+#endif
+
+}
+
+struct Test { // expected-note 2 {{candidate constructor}}
+#ifdef CONSTANT_PARAM
+ Test(std::initializer_list<1>); // expected-note {{candidate constructor}}
+#elifdef TYPE_TEMPLATE_PARAM
+ template<class> using A = double;
+ Test(std::initializer_list<A>); // expected-note {{candidate constructor}}
+#elifdef MULTIPLE_PARAMS
+ Test(std::initializer_list<double, double>); // expected-note {{candidate constructor}}
+#elifdef NONTEMPLATE
+ Test(std::initializer_list); // expected-note {{candidate constructor}}
+#else
+ Test(std::initializer_list<double>); // expected-note {{candidate constructor}}
+#endif
+};
+Test test {1.2, 3.4}; // expected-error {{no matching constructor}}
+
+auto x = {1};
+// type-param-error at -1 {{implicit instantiation of undefined template}}
+// others-note at -2 {{used here}}
+
+void f() {
+ for(int x : {1, 2});
+ // type-param-error at -1 {{implicit instantiation of undefined template}}
+ // type-param-error at -2 {{invalid range expression}}
+ // others-note at -3 {{used here}}
+}
>From 64690442bac8b05f0df03c94b22d70c429422db3 Mon Sep 17 00:00:00 2001
From: offsetof <offsetof at mailo.com>
Date: Wed, 2 Apr 2025 14:37:35 +0000
Subject: [PATCH 2/2] fixup! [clang] Check `std::initializer_list` more
strictly
---
.../clang/Basic/DiagnosticSemaKinds.td | 14 +++++++-------
clang/lib/Sema/SemaDeclCXX.cpp | 19 ++++++++++---------
2 files changed, 17 insertions(+), 16 deletions(-)
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index 4476751f9952a..575f13336e441 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -2591,13 +2591,13 @@ def err_implied_std_initializer_list_not_found : Error<
"cannot deduce type of initializer list because std::initializer_list was "
"not found; include <initializer_list>">;
def err_malformed_std_initializer_list
- : Error<"std::initializer_list %select{"
- "must have exactly one template parameter|"
- "cannot have associated constraints|"
- "must have a type template parameter|"
- "cannot have default template arguments|"
- "cannot be a variadic template|"
- "must be a class template}0">;
+ : Error<"std::initializer_list %enum_select<MalformedStdInitializerList>{"
+ "%TooManyParams{must have exactly one template parameter}|"
+ "%Constrained{cannot have associated constraints}|"
+ "%BadParamKind{must have a type template parameter}|"
+ "%DefaultArg{cannot have default template arguments}|"
+ "%ParamPack{cannot be a variadic template}|"
+ "%BadEntityKind{must be a class template}}0">;
def err_auto_init_list_from_c : Error<
"cannot use %select{'auto'|<ERROR>|'__auto_type'}0 with "
"%select{initializer list|array}1 in C">;
diff --git a/clang/lib/Sema/SemaDeclCXX.cpp b/clang/lib/Sema/SemaDeclCXX.cpp
index 77f8b6e36fcb3..e51512283ccac 100644
--- a/clang/lib/Sema/SemaDeclCXX.cpp
+++ b/clang/lib/Sema/SemaDeclCXX.cpp
@@ -27,6 +27,7 @@
#include "clang/AST/TypeLoc.h"
#include "clang/AST/TypeOrdering.h"
#include "clang/Basic/AttributeCommonInfo.h"
+#include "clang/Basic/DiagnosticSema.h"
#include "clang/Basic/PartialDiagnostic.h"
#include "clang/Basic/Specifiers.h"
#include "clang/Basic/TargetInfo.h"
@@ -12075,23 +12076,23 @@ NamespaceDecl *Sema::getOrCreateStdNamespace() {
/// a declaration of 'std::initializer_list', and optionally diagnose if
/// it is not.
/// \returns true if any issues were found.
-static bool CheckStdInitializerList(Sema &S, ClassTemplateDecl *Template,
+static bool CheckStdInitializerList(Sema &S, const ClassTemplateDecl *Template,
bool Diagnose) {
- TemplateParameterList *Params = Template->getTemplateParameters();
+ const TemplateParameterList *Params = Template->getTemplateParameters();
int ErrorKind = -1;
if (Params->size() != 1)
- ErrorKind = 0; // must have exactly one template parameter
+ ErrorKind = diag::MalformedStdInitializerList::TooManyParams;
else if (Template->hasAssociatedConstraints())
- ErrorKind = 1; // cannot have associated constraints
+ ErrorKind = diag::MalformedStdInitializerList::Constrained;
else {
- auto *Param = dyn_cast<TemplateTypeParmDecl>(Params->getParam(0));
+ const auto *Param = dyn_cast<TemplateTypeParmDecl>(Params->getParam(0));
if (!Param)
- ErrorKind = 2; // must have a type template parameter
+ ErrorKind = diag::MalformedStdInitializerList::BadParamKind;
else if (Param->hasDefaultArgument())
- ErrorKind = 3; // cannot have default template arguments
+ ErrorKind = diag::MalformedStdInitializerList::DefaultArg;
else if (Param->isTemplateParameterPack())
- ErrorKind = 4; // cannot be a variadic template
+ ErrorKind = diag::MalformedStdInitializerList::ParamPack;
else
return false;
}
@@ -12185,7 +12186,7 @@ static ClassTemplateDecl *LookupStdInitializerList(Sema &S,
// We found something weird. Complain about the first thing we found.
NamedDecl *Found = *Result.begin();
S.Diag(Found->getLocation(), diag::err_malformed_std_initializer_list)
- << 5 /* must be a class template */;
+ << diag::MalformedStdInitializerList::BadEntityKind;
S.Diag(Loc, diag::note_used_here);
return nullptr;
}
More information about the cfe-commits
mailing list