[Clang] [Sema] Diagnose unknown std::initializer_list layout in SemaInit (#95580)

This checks if the layout of `std::initializer_list` is something Clang
can handle much earlier and deduplicates the checks in
CodeGen/CGExprAgg.cpp and AST/ExprConstant.cpp

Also now diagnose `union initializer_list` (Fixes #95495), bit-field for
the size (Fixes a crash that would happen during codegen if it were
unnamed), base classes (that wouldn't be initialized) and polymorphic
classes (whose vtable pointer wouldn't be initialized).
This commit is contained in:
Mital Ashok 2024-06-20 18:44:06 +01:00 committed by GitHub
parent 6cea40400d
commit 482c41e992
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
33 changed files with 194 additions and 107 deletions

View File

@ -80,7 +80,7 @@ TEST(GetDeducedType, KwAutoKwDecltypeExpansion) {
namespace std
{
template<class _E>
class [[initializer_list]] {};
class [[initializer_list]] { const _E *a, *b; };
}
^auto i = {1,2};

View File

@ -2284,7 +2284,7 @@ TEST(Hover, All) {
namespace std
{
template<class _E>
class initializer_list {};
class initializer_list { const _E *a, *b; };
}
void foo() {
^[[auto]] i = {1,2};

View File

@ -945,7 +945,7 @@ TEST(ParameterHints, ConstructorStdInitList) {
// Do not show hints for std::initializer_list constructors.
assertParameterHints(R"cpp(
namespace std {
template <typename> class initializer_list {};
template <typename E> class initializer_list { const E *a, *b; };
}
struct S {
S(std::initializer_list<int> param);

View File

@ -771,7 +771,7 @@ TEST(LocateSymbol, All) {
namespace std
{
template<class _E>
class [[initializer_list]] {};
class [[initializer_list]] { const _E *a, *b; };
}
^auto i = {1,2};

View File

@ -534,7 +534,7 @@ TEST(WalkAST, Enums) {
TEST(WalkAST, InitializerList) {
testWalk(R"cpp(
namespace std {
template <typename T> struct $implicit^initializer_list {};
template <typename T> struct $implicit^initializer_list { const T *a, *b; };
})cpp",
R"cpp(
const char* s = "";

View File

@ -11,6 +11,7 @@ T max(T a, T b) {
namespace std {
template< class T >
struct initializer_list {
const T *a, *b;
initializer_list()=default;
initializer_list(T*,int){}
const T* begin() const {return nullptr;}

View File

@ -4,10 +4,11 @@
// RUN: true}}"
namespace std {
template <typename>
template <typename E>
class initializer_list
{
public:
const E *a, *b;
initializer_list() noexcept {}
};

View File

@ -8,9 +8,10 @@
// RUN: '::std::make_pair; ::std::make_tuple; ::test::MakeSingle'}}"
namespace std {
template <typename>
template <typename E>
class initializer_list {
public:
const E *a, *b;
initializer_list() noexcept {}
};

View File

@ -5,7 +5,7 @@
namespace std {
typedef int size_t;
typedef decltype(sizeof 0) size_t;
template<class E> class initializer_list {
public:
@ -15,6 +15,8 @@ public:
using size_type = size_t;
using iterator = const E*;
using const_iterator = const E*;
iterator p;
size_t sz;
initializer_list();
size_t size() const; // number of elements
const E* begin() const; // first element

View File

@ -31,7 +31,7 @@ struct SomeClass {
namespace std {
template <typename T>
class initializer_list {};
class initializer_list { const T *a, *b; };
template <typename T>
class vector {

View File

@ -598,6 +598,10 @@ Improvements to Clang's diagnostics
- Clang no longer emits a "declared here" note for a builtin function that has no declaration in source.
Fixes #GH93369.
- Clang now diagnoses unsupported class declarations for ``std::initializer_list<E>`` when they are
used rather than when they are needed for constant evaluation or when code is generated for them.
The check is now stricter to prevent crashes for some unsupported declarations (Fixes #GH95495).
Improvements to Clang's time-trace
----------------------------------

View File

@ -12207,6 +12207,9 @@ def err_std_source_location_impl_not_found : Error<
def err_std_source_location_impl_malformed : Error<
"'std::source_location::__impl' must be standard-layout and have only two 'const char *' fields '_M_file_name' and '_M_function_name', and two integral fields '_M_line' and '_M_column'">;
def err_std_initializer_list_malformed : Error<
"%0 layout not recognized. Must be a non-polymorphic class type with no bases and two fields: a 'const E *' and either another 'const E *' or a 'std::size_t'">;
// HLSL Diagnostics
def err_hlsl_attr_unsupported_in_stage : Error<"attribute %0 is unsupported in '%1' shaders, requires %select{|one of the following: }2%3">;
def err_hlsl_attr_invalid_type : Error<

View File

@ -10545,48 +10545,37 @@ bool RecordExprEvaluator::VisitCXXStdInitializerListExpr(
// Get a pointer to the first element of the array.
Array.addArray(Info, E, ArrayType);
auto InvalidType = [&] {
Info.FFDiag(E, diag::note_constexpr_unsupported_layout)
<< E->getType();
return false;
};
// FIXME: Perform the checks on the field types in SemaInit.
RecordDecl *Record = E->getType()->castAs<RecordType>()->getDecl();
RecordDecl::field_iterator Field = Record->field_begin();
if (Field == Record->field_end())
return InvalidType();
// Start pointer.
if (!Field->getType()->isPointerType() ||
!Info.Ctx.hasSameType(Field->getType()->getPointeeType(),
ArrayType->getElementType()))
return InvalidType();
// FIXME: What if the initializer_list type has base classes, etc?
Result = APValue(APValue::UninitStruct(), 0, 2);
Array.moveInto(Result.getStructField(0));
if (++Field == Record->field_end())
return InvalidType();
RecordDecl *Record = E->getType()->castAs<RecordType>()->getDecl();
RecordDecl::field_iterator Field = Record->field_begin();
assert(Field != Record->field_end() &&
Info.Ctx.hasSameType(Field->getType()->getPointeeType(),
ArrayType->getElementType()) &&
"Expected std::initializer_list first field to be const E *");
++Field;
assert(Field != Record->field_end() &&
"Expected std::initializer_list to have two fields");
if (Field->getType()->isPointerType() &&
Info.Ctx.hasSameType(Field->getType()->getPointeeType(),
ArrayType->getElementType())) {
if (Info.Ctx.hasSameType(Field->getType(), Info.Ctx.getSizeType())) {
// Length.
Result.getStructField(1) = APValue(APSInt(ArrayType->getSize()));
} else {
// End pointer.
assert(Info.Ctx.hasSameType(Field->getType()->getPointeeType(),
ArrayType->getElementType()) &&
"Expected std::initializer_list second field to be const E *");
if (!HandleLValueArrayAdjustment(Info, E, Array,
ArrayType->getElementType(),
ArrayType->getZExtSize()))
return false;
Array.moveInto(Result.getStructField(1));
} else if (Info.Ctx.hasSameType(Field->getType(), Info.Ctx.getSizeType()))
// Length.
Result.getStructField(1) = APValue(APSInt(ArrayType->getSize()));
else
return InvalidType();
}
if (++Field != Record->field_end())
return InvalidType();
assert(++Field == Record->field_end() &&
"Expected std::initializer_list to only have two fields");
return true;
}

View File

@ -426,53 +426,45 @@ AggExprEmitter::VisitCXXStdInitializerListExpr(CXXStdInitializerListExpr *E) {
Ctx.getAsConstantArrayType(E->getSubExpr()->getType());
assert(ArrayType && "std::initializer_list constructed from non-array");
// FIXME: Perform the checks on the field types in SemaInit.
RecordDecl *Record = E->getType()->castAs<RecordType>()->getDecl();
RecordDecl::field_iterator Field = Record->field_begin();
if (Field == Record->field_end()) {
CGF.ErrorUnsupported(E, "weird std::initializer_list");
return;
}
assert(Field != Record->field_end() &&
Ctx.hasSameType(Field->getType()->getPointeeType(),
ArrayType->getElementType()) &&
"Expected std::initializer_list first field to be const E *");
// Start pointer.
if (!Field->getType()->isPointerType() ||
!Ctx.hasSameType(Field->getType()->getPointeeType(),
ArrayType->getElementType())) {
CGF.ErrorUnsupported(E, "weird std::initializer_list");
return;
}
AggValueSlot Dest = EnsureSlot(E->getType());
LValue DestLV = CGF.MakeAddrLValue(Dest.getAddress(), E->getType());
LValue Start = CGF.EmitLValueForFieldInitialization(DestLV, *Field);
llvm::Value *ArrayStart = ArrayPtr.emitRawPointer(CGF);
CGF.EmitStoreThroughLValue(RValue::get(ArrayStart), Start);
++Field;
if (Field == Record->field_end()) {
CGF.ErrorUnsupported(E, "weird std::initializer_list");
return;
}
assert(Field != Record->field_end() &&
"Expected std::initializer_list to have two fields");
llvm::Value *Size = Builder.getInt(ArrayType->getSize());
LValue EndOrLength = CGF.EmitLValueForFieldInitialization(DestLV, *Field);
if (Field->getType()->isPointerType() &&
Ctx.hasSameType(Field->getType()->getPointeeType(),
ArrayType->getElementType())) {
if (Ctx.hasSameType(Field->getType(), Ctx.getSizeType())) {
// Length.
CGF.EmitStoreThroughLValue(RValue::get(Size), EndOrLength);
} else {
// End pointer.
assert(Field->getType()->isPointerType() &&
Ctx.hasSameType(Field->getType()->getPointeeType(),
ArrayType->getElementType()) &&
"Expected std::initializer_list second field to be const E *");
llvm::Value *Zero = llvm::ConstantInt::get(CGF.PtrDiffTy, 0);
llvm::Value *IdxEnd[] = { Zero, Size };
llvm::Value *ArrayEnd = Builder.CreateInBoundsGEP(
ArrayPtr.getElementType(), ArrayPtr.emitRawPointer(CGF), IdxEnd,
"arrayend");
CGF.EmitStoreThroughLValue(RValue::get(ArrayEnd), EndOrLength);
} else if (Ctx.hasSameType(Field->getType(), Ctx.getSizeType())) {
// Length.
CGF.EmitStoreThroughLValue(RValue::get(Size), EndOrLength);
} else {
CGF.ErrorUnsupported(E, "weird std::initializer_list");
return;
}
assert(++Field == Record->field_end() &&
"Expected std::initializer_list to only have two fields");
}
/// Determine if E is a trivial array filler, that is, one that is

View File

@ -9479,6 +9479,57 @@ ExprResult InitializationSequence::Perform(Sema &S,
// Wrap it in a construction of a std::initializer_list<T>.
CurInit = new (S.Context) CXXStdInitializerListExpr(Step->Type, MTE);
if (!Step->Type->isDependentType()) {
QualType ElementType;
[[maybe_unused]] bool IsStdInitializerList =
S.isStdInitializerList(Step->Type, &ElementType);
assert(IsStdInitializerList &&
"StdInitializerList step to non-std::initializer_list");
const CXXRecordDecl *Record =
Step->Type->getAsCXXRecordDecl()->getDefinition();
assert(Record && Record->isCompleteDefinition() &&
"std::initializer_list should have already be "
"complete/instantiated by this point");
auto InvalidType = [&] {
S.Diag(Record->getLocation(),
diag::err_std_initializer_list_malformed)
<< Step->Type.getUnqualifiedType();
return ExprError();
};
if (Record->isUnion() || Record->getNumBases() != 0 ||
Record->isPolymorphic())
return InvalidType();
RecordDecl::field_iterator Field = Record->field_begin();
if (Field == Record->field_end())
return InvalidType();
// Start pointer
if (!Field->getType()->isPointerType() ||
!S.Context.hasSameType(Field->getType()->getPointeeType(),
ElementType.withConst()))
return InvalidType();
if (++Field == Record->field_end())
return InvalidType();
// Size or end pointer
if (const auto *PT = Field->getType()->getAs<PointerType>()) {
if (!S.Context.hasSameType(PT->getPointeeType(),
ElementType.withConst()))
return InvalidType();
} else {
if (Field->isBitField() ||
!S.Context.hasSameType(Field->getType(), S.Context.getSizeType()))
return InvalidType();
}
if (++Field != Record->field_end())
return InvalidType();
}
// Bind the result, in case the library has given initializer_list a
// non-trivial destructor.
if (shouldBindAsTemporary(Entity))

View File

@ -43,7 +43,7 @@ struct S {
const int S::b;
const auto S::c = 0;
namespace std { template<typename T> struct initializer_list { initializer_list(); }; }
namespace std { template<typename T> struct initializer_list { const T *a, *b; initializer_list(); }; }
// In an initializer of the form ( expression-list ), the expression-list
// shall be a single assigment-expression.

View File

@ -4,9 +4,7 @@
namespace std {
template<typename T> struct initializer_list {
const T *p;
unsigned long n;
initializer_list(const T *p, unsigned long n);
const T *a, *b;
};
}

View File

@ -17,7 +17,7 @@ void foo() {
}
namespace std {
template <typename> struct initializer_list {};
template <typename E> struct initializer_list { const E *a, *b; };
} // namespace std
struct Bar {

View File

@ -4,7 +4,7 @@
// GH62105 demonstrated a crash with this example code when calculating
// coverage mapping because some source location information was being dropped.
// Demonstrate that we do not crash on this code.
namespace std { template <typename> class initializer_list {}; }
namespace std { template <typename E> class initializer_list { const E *a, *b; }; }
template <typename> struct T {
T(std::initializer_list<int>, int = int());

View File

@ -2,7 +2,7 @@ namespace std {
using size_t = decltype(sizeof(0));
template<typename T> struct initializer_list {
initializer_list(T*, size_t);
const T* ptr; size_t sz;
};
template<typename T> int min(initializer_list<T>);

View File

@ -29,9 +29,10 @@
namespace std {
typedef decltype(sizeof(int)) size_t;
template<typename T> struct initializer_list {
const T* ptr; size_t sz;
initializer_list(const T *, size_t);
T* begin();
T* end();
const T* begin();
const T* end();
};
}

View File

@ -16,7 +16,7 @@
#ifndef HEADER
#define HEADER
typedef long unsigned a;
typedef decltype(sizeof 0) a;
namespace std {
template <class> class initializer_list {
const int *b;

View File

@ -3,7 +3,7 @@
namespace std {
template <class X>
class initializer_list {
public:
public: const X *a, *b;
initializer_list();
};
}

View File

@ -1,23 +1,17 @@
// RUN: %clang_cc1 -std=c++11 -verify -emit-llvm-only %s
// RUN: %clang_cc1 -std=c++98 -fsyntax-only -verify %s -DCPP98
// RUN: %clang_cc1 -std=c++11 -verify -emit-llvm-only %s -fexperimental-new-constant-interpreter
// RUN: %clang_cc1 -std=c++98 -fsyntax-only -verify %s -DCPP98 -fexperimental-new-constant-interpreter
// RUN: %clang_cc1 -std=c++11 -verify=cxx11 -emit-llvm-only %s
// RUN: %clang_cc1 -std=c++98 -fsyntax-only -verify=cxx98 %s
// RUN: %clang_cc1 -std=c++11 -verify=cxx11 -emit-llvm-only %s -fexperimental-new-constant-interpreter
// RUN: %clang_cc1 -std=c++98 -fsyntax-only -verify=cxx98 %s -fexperimental-new-constant-interpreter
namespace std {
template <class _E>
class initializer_list
{};
class initializer_list {};
// cxx11-error@-1 {{'std::initializer_list<int>' layout not recognized. Must be a non-polymorphic class type with no bases and two fields: a 'const E *' and either another 'const E *' or a 'std::size_t'}}
}
template<class E> int f(std::initializer_list<E> il);
int F = f({1, 2, 3});
#ifdef CPP98
//expected-error@-2{{expected expression}}
#else
//expected-error@-4{{cannot compile}}
#endif
// cxx98-error@-1 {{expected expression}}

View File

@ -1,8 +1,8 @@
// RUN: %clang_cc1 -std=c++11 -fsyntax-only -fno-recovery-ast -verify %s
namespace std {
template <typename>
class initializer_list{};
template <typename E>
class initializer_list { const E *a, *b; };
int a;
auto c = a, &d = {a}; // expected-error {{'auto' deduced as 'int'}} \
expected-error {{non-const lvalue reference to type}}

View File

@ -1,5 +1,18 @@
// RUN: %clang_cc1 -std=c++11 -fsyntax-only -verify %s
// RUN: %clang_cc1 -std=c++11 -fsyntax-only -DUNION_TEST -verify %s
#ifdef UNION_TEST
namespace std {
template<class E>
union initializer_list {
// expected-error@-1 {{'std::initializer_list<int>' layout not recognized.}}
const E* begin;
decltype(sizeof 0) size;
};
auto x = { 1, 2, 3 };
};
#else
// This must obviously come before the definition of std::initializer_list.
void missing_initializerlist() {
auto l = {1, 2, 3, 4}; // expected-error {{std::initializer_list was not found}}
@ -367,14 +380,49 @@ namespace designated_init {
}
namespace weird_initlist {
template<int>
struct weird {};
}
template<> struct std::initializer_list<weird_initlist::weird> { int a, b, c; };
template<> struct std::initializer_list<weird_initlist::weird<0>> { int a, b, c; };
// expected-error@-1 2 {{'std::initializer_list<weird<0>>' layout not recognized}}
template<> struct std::initializer_list<weird_initlist::weird<1>> { std::size_t sz; const weird_initlist::weird<1>* p; };
// expected-error@-1 {{'std::initializer_list<weird<1>>' layout not recognized}}
template<> struct std::initializer_list<weird_initlist::weird<2>> { const weird_initlist::weird<2>* first; const weird_initlist::weird<2>* last; };
template<> struct std::initializer_list<weird_initlist::weird<3>> { weird_initlist::weird<3>* p; std::size_t sz; };
// expected-error@-1 {{'std::initializer_list<weird<3>>' layout not recognized}}
template<> struct std::initializer_list<weird_initlist::weird<4>> { const weird_initlist::weird<4>& p; std::size_t sz; };
// expected-error@-1 {{'std::initializer_list<weird<4>>' layout not recognized}}
template<> struct std::initializer_list<weird_initlist::weird<5>> { const weird_initlist::weird<5>* p; std::size_t : sizeof(std::size_t) * __CHAR_BIT__; };
// expected-error@-1 {{'std::initializer_list<weird<5>>' layout not recognized}}
template<> struct std::initializer_list<weird_initlist::weird<6>> { const weird_initlist::weird<6>* p; std::size_t sz : sizeof(std::size_t) * __CHAR_BIT__; };
// expected-error@-1 {{'std::initializer_list<weird<6>>' layout not recognized}}
struct empty_base {};
template<> struct std::initializer_list<weird_initlist::weird<7>> : empty_base { const weird_initlist::weird<7>* p; std::size_t sz; };
// expected-error@-1 {{'std::initializer_list<weird<7>>' layout not recognized}}
template<> struct std::initializer_list<weird_initlist::weird<8>> { const weird_initlist::weird<8>* p; std::size_t sz; ~initializer_list(); };
template<> struct std::initializer_list<weird_initlist::weird<9>> { const weird_initlist::weird<9>* p; std::size_t sz; virtual void f(); };
// expected-error@-1 {{'std::initializer_list<weird<9>>' layout not recognized}}
template<> struct std::initializer_list<weird_initlist::weird<10>> { const weird_initlist::weird<10>* p; alignas(64) std::size_t sz; };
template<> struct std::initializer_list<weird_initlist::weird<11>>;
// expected-note@-1 {{forward declaration of 'std::initializer_list<weird_initlist::weird<11>>'}}
namespace weird_initlist {
// We don't check the struct layout in Sema.
auto x = {weird{}, weird{}, weird{}, weird{}, weird{}};
// ... but we do in constant evaluation.
constexpr auto y = {weird{}, weird{}, weird{}, weird{}, weird{}}; // expected-error {{constant}} expected-note {{type 'const std::initializer_list<weird>' has unexpected layout}}
auto _0 = {weird<0>{}, weird<0>{}, weird<0>{}, weird<0>{}, weird<0>{}};
constexpr auto _0c = {weird<0>{}, weird<0>{}, weird<0>{}, weird<0>{}, weird<0>{}};
auto _1 = {weird<1>{}, weird<1>{}};
auto _2 = {weird<2>{}, weird<2>{}, weird<2>{}};
constexpr auto _2c = {weird<2>{}, weird<2>{}, weird<2>{}}; // (Two pointer representation is supported)
static_assert(_2c.first + 3 == _2c.last, "");
auto _3 = {weird<3>{}, weird<3>{}};
auto _4 = {weird<4>{}, weird<4>{}};
auto _5 = {weird<5>{}, weird<5>{}};
auto _6 = {weird<6>{}, weird<6>{}};
auto _7 = {weird<7>{}, weird<7>{}};
auto _8 = {weird<8>{}, weird<8>{}};
auto _9 = {weird<9>{}, weird<9>{}};
auto _10 = {weird<10>{}, weird<10>{}};
constexpr auto _10c = {weird<10>{}, weird<10>{}, weird<10>{}};
static_assert(_10c.sz == 3, "");
const auto& _11 = {weird<11>{}, weird<11>{}}; // expected-error {{initialization of incomplete type 'const std::initializer_list<weird<11>>'}}
}
auto v = std::initializer_list<int>{1,2,3}; // expected-warning {{array backing local initializer list 'v' will be destroyed at the end of the full-expression}}
@ -386,3 +434,4 @@ std::initializer_list<int> get(int cond) {
return {1, 2, 3}; // expected-warning {{returning address of local temporary object}}
return std::initializer_list<int>{1, 2, 3}; // expected-warning {{returning address of local temporary object}}
}
#endif // UNION_TEST

View File

@ -1,7 +1,7 @@
// RUN: %clang_cc1 -std=c++11 -fsyntax-only -verify %s
namespace std {
template<class _Ep> class initializer_list { };
template<class _Ep> class initializer_list { const _Ep *a, *b; };
}
namespace cva {

View File

@ -6,11 +6,11 @@ namespace std {
struct type_info;
using size_t = decltype(sizeof(0)); // expected-warning {{decltype}} expected-warning {{alias}}
template<typename T> struct initializer_list {
initializer_list(T*, size_t);
T *p;
initializer_list(const T*, size_t);
const T *p;
size_t n;
T *begin();
T *end();
const T *begin();
const T *end();
};
}

View File

@ -26,7 +26,7 @@ void test2() {
// PR6327
namespace test3 {
template <class A, class B> struct pair {};
template <class _E> class initializer_list {};
template <class _E> class initializer_list { const _E *a, *b; };
template <typename _Tp> pair<_Tp, _Tp> minmax(initializer_list<_Tp> __l) {};
void test0() {

View File

@ -2,9 +2,9 @@
namespace std {
template<typename T> struct initializer_list {
T *p;
const T *p;
__SIZE_TYPE__ n;
initializer_list(T*, __SIZE_TYPE__);
initializer_list(const T*, __SIZE_TYPE__);
};
}

View File

@ -1427,8 +1427,8 @@ TEST_P(ASTMatchersTest,
return;
}
StringRef code = "namespace std {"
"template <typename> class initializer_list {"
" public: initializer_list() noexcept {}"
"template <typename E> class initializer_list {"
" public: const E *a, *b;"
"};"
"}"
"struct A {"

View File

@ -3265,7 +3265,7 @@ TEST(TransferTest, ResultObjectLocationForStdInitializerListExpr) {
std::string Code = R"(
namespace std {
template <typename T>
struct initializer_list {};
struct initializer_list { const T *a, *b; };
} // namespace std
void target() {

View File

@ -552,6 +552,7 @@ namespace std {
template <typename T>
class initializer_list {
public:
const T *a, *b;
initializer_list() noexcept;
};