Skip to content

Commit 482c41e

Browse files
authored
[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).
1 parent 6cea404 commit 482c41e

33 files changed

+195
-108
lines changed

clang-tools-extra/clangd/unittests/ASTTests.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ TEST(GetDeducedType, KwAutoKwDecltypeExpansion) {
8080
namespace std
8181
{
8282
template<class _E>
83-
class [[initializer_list]] {};
83+
class [[initializer_list]] { const _E *a, *b; };
8484
}
8585
8686
^auto i = {1,2};

clang-tools-extra/clangd/unittests/HoverTests.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2284,7 +2284,7 @@ TEST(Hover, All) {
22842284
namespace std
22852285
{
22862286
template<class _E>
2287-
class initializer_list {};
2287+
class initializer_list { const _E *a, *b; };
22882288
}
22892289
void foo() {
22902290
^[[auto]] i = {1,2};

clang-tools-extra/clangd/unittests/InlayHintTests.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -945,7 +945,7 @@ TEST(ParameterHints, ConstructorStdInitList) {
945945
// Do not show hints for std::initializer_list constructors.
946946
assertParameterHints(R"cpp(
947947
namespace std {
948-
template <typename> class initializer_list {};
948+
template <typename E> class initializer_list { const E *a, *b; };
949949
}
950950
struct S {
951951
S(std::initializer_list<int> param);

clang-tools-extra/clangd/unittests/XRefsTests.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -771,7 +771,7 @@ TEST(LocateSymbol, All) {
771771
namespace std
772772
{
773773
template<class _E>
774-
class [[initializer_list]] {};
774+
class [[initializer_list]] { const _E *a, *b; };
775775
}
776776
777777
^auto i = {1,2};

clang-tools-extra/include-cleaner/unittests/WalkASTTest.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -534,7 +534,7 @@ TEST(WalkAST, Enums) {
534534
TEST(WalkAST, InitializerList) {
535535
testWalk(R"cpp(
536536
namespace std {
537-
template <typename T> struct $implicit^initializer_list {};
537+
template <typename T> struct $implicit^initializer_list { const T *a, *b; };
538538
})cpp",
539539
R"cpp(
540540
const char* s = "";

clang-tools-extra/test/clang-tidy/checkers/modernize/min-max-use-initializer-list.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ T max(T a, T b) {
1111
namespace std {
1212
template< class T >
1313
struct initializer_list {
14+
const T *a, *b;
1415
initializer_list()=default;
1516
initializer_list(T*,int){}
1617
const T* begin() const {return nullptr;}

clang-tools-extra/test/clang-tidy/checkers/modernize/use-emplace-ignore-implicit-constructors.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,11 @@
44
// RUN: true}}"
55

66
namespace std {
7-
template <typename>
7+
template <typename E>
88
class initializer_list
99
{
1010
public:
11+
const E *a, *b;
1112
initializer_list() noexcept {}
1213
};
1314

clang-tools-extra/test/clang-tidy/checkers/modernize/use-emplace.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,10 @@
88
// RUN: '::std::make_pair; ::std::make_tuple; ::test::MakeSingle'}}"
99

1010
namespace std {
11-
template <typename>
11+
template <typename E>
1212
class initializer_list {
1313
public:
14+
const E *a, *b;
1415
initializer_list() noexcept {}
1516
};
1617

clang-tools-extra/test/clang-tidy/checkers/performance/inefficient-vector-operation.cpp

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
namespace std {
77

8-
typedef int size_t;
8+
typedef decltype(sizeof 0) size_t;
99

1010
template<class E> class initializer_list {
1111
public:
@@ -15,6 +15,8 @@ template<class E> class initializer_list {
1515
using size_type = size_t;
1616
using iterator = const E*;
1717
using const_iterator = const E*;
18+
iterator p;
19+
size_t sz;
1820
initializer_list();
1921
size_t size() const; // number of elements
2022
const E* begin() const; // first element

clang-tools-extra/test/clang-tidy/checkers/readability/isolate-declaration-cxx17.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ struct SomeClass {
3131

3232
namespace std {
3333
template <typename T>
34-
class initializer_list {};
34+
class initializer_list { const T *a, *b; };
3535

3636
template <typename T>
3737
class vector {

clang/docs/ReleaseNotes.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -598,6 +598,10 @@ Improvements to Clang's diagnostics
598598
- Clang no longer emits a "declared here" note for a builtin function that has no declaration in source.
599599
Fixes #GH93369.
600600

601+
- Clang now diagnoses unsupported class declarations for ``std::initializer_list<E>`` when they are
602+
used rather than when they are needed for constant evaluation or when code is generated for them.
603+
The check is now stricter to prevent crashes for some unsupported declarations (Fixes #GH95495).
604+
601605
Improvements to Clang's time-trace
602606
----------------------------------
603607

clang/include/clang/Basic/DiagnosticSemaKinds.td

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12207,6 +12207,9 @@ def err_std_source_location_impl_not_found : Error<
1220712207
def err_std_source_location_impl_malformed : Error<
1220812208
"'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'">;
1220912209

12210+
def err_std_initializer_list_malformed : Error<
12211+
"%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'">;
12212+
1221012213
// HLSL Diagnostics
1221112214
def err_hlsl_attr_unsupported_in_stage : Error<"attribute %0 is unsupported in '%1' shaders, requires %select{|one of the following: }2%3">;
1221212215
def err_hlsl_attr_invalid_type : Error<

clang/lib/AST/ExprConstant.cpp

Lines changed: 20 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -10545,48 +10545,37 @@ bool RecordExprEvaluator::VisitCXXStdInitializerListExpr(
1054510545
// Get a pointer to the first element of the array.
1054610546
Array.addArray(Info, E, ArrayType);
1054710547

10548-
auto InvalidType = [&] {
10549-
Info.FFDiag(E, diag::note_constexpr_unsupported_layout)
10550-
<< E->getType();
10551-
return false;
10552-
};
10553-
10554-
// FIXME: Perform the checks on the field types in SemaInit.
10555-
RecordDecl *Record = E->getType()->castAs<RecordType>()->getDecl();
10556-
RecordDecl::field_iterator Field = Record->field_begin();
10557-
if (Field == Record->field_end())
10558-
return InvalidType();
10559-
10560-
// Start pointer.
10561-
if (!Field->getType()->isPointerType() ||
10562-
!Info.Ctx.hasSameType(Field->getType()->getPointeeType(),
10563-
ArrayType->getElementType()))
10564-
return InvalidType();
10565-
1056610548
// FIXME: What if the initializer_list type has base classes, etc?
1056710549
Result = APValue(APValue::UninitStruct(), 0, 2);
1056810550
Array.moveInto(Result.getStructField(0));
1056910551

10570-
if (++Field == Record->field_end())
10571-
return InvalidType();
10572-
10573-
if (Field->getType()->isPointerType() &&
10574-
Info.Ctx.hasSameType(Field->getType()->getPointeeType(),
10575-
ArrayType->getElementType())) {
10552+
RecordDecl *Record = E->getType()->castAs<RecordType>()->getDecl();
10553+
RecordDecl::field_iterator Field = Record->field_begin();
10554+
assert(Field != Record->field_end() &&
10555+
Info.Ctx.hasSameType(Field->getType()->getPointeeType(),
10556+
ArrayType->getElementType()) &&
10557+
"Expected std::initializer_list first field to be const E *");
10558+
++Field;
10559+
assert(Field != Record->field_end() &&
10560+
"Expected std::initializer_list to have two fields");
10561+
10562+
if (Info.Ctx.hasSameType(Field->getType(), Info.Ctx.getSizeType())) {
10563+
// Length.
10564+
Result.getStructField(1) = APValue(APSInt(ArrayType->getSize()));
10565+
} else {
1057610566
// End pointer.
10567+
assert(Info.Ctx.hasSameType(Field->getType()->getPointeeType(),
10568+
ArrayType->getElementType()) &&
10569+
"Expected std::initializer_list second field to be const E *");
1057710570
if (!HandleLValueArrayAdjustment(Info, E, Array,
1057810571
ArrayType->getElementType(),
1057910572
ArrayType->getZExtSize()))
1058010573
return false;
1058110574
Array.moveInto(Result.getStructField(1));
10582-
} else if (Info.Ctx.hasSameType(Field->getType(), Info.Ctx.getSizeType()))
10583-
// Length.
10584-
Result.getStructField(1) = APValue(APSInt(ArrayType->getSize()));
10585-
else
10586-
return InvalidType();
10575+
}
1058710576

10588-
if (++Field != Record->field_end())
10589-
return InvalidType();
10577+
assert(++Field == Record->field_end() &&
10578+
"Expected std::initializer_list to only have two fields");
1059010579

1059110580
return true;
1059210581
}

clang/lib/CodeGen/CGExprAgg.cpp

Lines changed: 18 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -426,53 +426,45 @@ AggExprEmitter::VisitCXXStdInitializerListExpr(CXXStdInitializerListExpr *E) {
426426
Ctx.getAsConstantArrayType(E->getSubExpr()->getType());
427427
assert(ArrayType && "std::initializer_list constructed from non-array");
428428

429-
// FIXME: Perform the checks on the field types in SemaInit.
430429
RecordDecl *Record = E->getType()->castAs<RecordType>()->getDecl();
431430
RecordDecl::field_iterator Field = Record->field_begin();
432-
if (Field == Record->field_end()) {
433-
CGF.ErrorUnsupported(E, "weird std::initializer_list");
434-
return;
435-
}
431+
assert(Field != Record->field_end() &&
432+
Ctx.hasSameType(Field->getType()->getPointeeType(),
433+
ArrayType->getElementType()) &&
434+
"Expected std::initializer_list first field to be const E *");
436435

437436
// Start pointer.
438-
if (!Field->getType()->isPointerType() ||
439-
!Ctx.hasSameType(Field->getType()->getPointeeType(),
440-
ArrayType->getElementType())) {
441-
CGF.ErrorUnsupported(E, "weird std::initializer_list");
442-
return;
443-
}
444-
445437
AggValueSlot Dest = EnsureSlot(E->getType());
446438
LValue DestLV = CGF.MakeAddrLValue(Dest.getAddress(), E->getType());
447439
LValue Start = CGF.EmitLValueForFieldInitialization(DestLV, *Field);
448440
llvm::Value *ArrayStart = ArrayPtr.emitRawPointer(CGF);
449441
CGF.EmitStoreThroughLValue(RValue::get(ArrayStart), Start);
450442
++Field;
451-
452-
if (Field == Record->field_end()) {
453-
CGF.ErrorUnsupported(E, "weird std::initializer_list");
454-
return;
455-
}
443+
assert(Field != Record->field_end() &&
444+
"Expected std::initializer_list to have two fields");
456445

457446
llvm::Value *Size = Builder.getInt(ArrayType->getSize());
458447
LValue EndOrLength = CGF.EmitLValueForFieldInitialization(DestLV, *Field);
459-
if (Field->getType()->isPointerType() &&
460-
Ctx.hasSameType(Field->getType()->getPointeeType(),
461-
ArrayType->getElementType())) {
448+
if (Ctx.hasSameType(Field->getType(), Ctx.getSizeType())) {
449+
// Length.
450+
CGF.EmitStoreThroughLValue(RValue::get(Size), EndOrLength);
451+
452+
} else {
462453
// End pointer.
454+
assert(Field->getType()->isPointerType() &&
455+
Ctx.hasSameType(Field->getType()->getPointeeType(),
456+
ArrayType->getElementType()) &&
457+
"Expected std::initializer_list second field to be const E *");
463458
llvm::Value *Zero = llvm::ConstantInt::get(CGF.PtrDiffTy, 0);
464459
llvm::Value *IdxEnd[] = { Zero, Size };
465460
llvm::Value *ArrayEnd = Builder.CreateInBoundsGEP(
466461
ArrayPtr.getElementType(), ArrayPtr.emitRawPointer(CGF), IdxEnd,
467462
"arrayend");
468463
CGF.EmitStoreThroughLValue(RValue::get(ArrayEnd), EndOrLength);
469-
} else if (Ctx.hasSameType(Field->getType(), Ctx.getSizeType())) {
470-
// Length.
471-
CGF.EmitStoreThroughLValue(RValue::get(Size), EndOrLength);
472-
} else {
473-
CGF.ErrorUnsupported(E, "weird std::initializer_list");
474-
return;
475464
}
465+
466+
assert(++Field == Record->field_end() &&
467+
"Expected std::initializer_list to only have two fields");
476468
}
477469

478470
/// Determine if E is a trivial array filler, that is, one that is

clang/lib/Sema/SemaInit.cpp

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9479,6 +9479,57 @@ ExprResult InitializationSequence::Perform(Sema &S,
94799479
// Wrap it in a construction of a std::initializer_list<T>.
94809480
CurInit = new (S.Context) CXXStdInitializerListExpr(Step->Type, MTE);
94819481

9482+
if (!Step->Type->isDependentType()) {
9483+
QualType ElementType;
9484+
[[maybe_unused]] bool IsStdInitializerList =
9485+
S.isStdInitializerList(Step->Type, &ElementType);
9486+
assert(IsStdInitializerList &&
9487+
"StdInitializerList step to non-std::initializer_list");
9488+
const CXXRecordDecl *Record =
9489+
Step->Type->getAsCXXRecordDecl()->getDefinition();
9490+
assert(Record && Record->isCompleteDefinition() &&
9491+
"std::initializer_list should have already be "
9492+
"complete/instantiated by this point");
9493+
9494+
auto InvalidType = [&] {
9495+
S.Diag(Record->getLocation(),
9496+
diag::err_std_initializer_list_malformed)
9497+
<< Step->Type.getUnqualifiedType();
9498+
return ExprError();
9499+
};
9500+
9501+
if (Record->isUnion() || Record->getNumBases() != 0 ||
9502+
Record->isPolymorphic())
9503+
return InvalidType();
9504+
9505+
RecordDecl::field_iterator Field = Record->field_begin();
9506+
if (Field == Record->field_end())
9507+
return InvalidType();
9508+
9509+
// Start pointer
9510+
if (!Field->getType()->isPointerType() ||
9511+
!S.Context.hasSameType(Field->getType()->getPointeeType(),
9512+
ElementType.withConst()))
9513+
return InvalidType();
9514+
9515+
if (++Field == Record->field_end())
9516+
return InvalidType();
9517+
9518+
// Size or end pointer
9519+
if (const auto *PT = Field->getType()->getAs<PointerType>()) {
9520+
if (!S.Context.hasSameType(PT->getPointeeType(),
9521+
ElementType.withConst()))
9522+
return InvalidType();
9523+
} else {
9524+
if (Field->isBitField() ||
9525+
!S.Context.hasSameType(Field->getType(), S.Context.getSizeType()))
9526+
return InvalidType();
9527+
}
9528+
9529+
if (++Field != Record->field_end())
9530+
return InvalidType();
9531+
}
9532+
94829533
// Bind the result, in case the library has given initializer_list a
94839534
// non-trivial destructor.
94849535
if (shouldBindAsTemporary(Entity))

clang/test/CXX/dcl.dcl/dcl.spec/dcl.type/dcl.spec.auto/p4.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ struct S {
4343
const int S::b;
4444
const auto S::c = 0;
4545

46-
namespace std { template<typename T> struct initializer_list { initializer_list(); }; }
46+
namespace std { template<typename T> struct initializer_list { const T *a, *b; initializer_list(); }; }
4747

4848
// In an initializer of the form ( expression-list ), the expression-list
4949
// shall be a single assigment-expression.

clang/test/CXX/dcl.dcl/dcl.spec/dcl.type/dcl.spec.auto/p7-cxx14.cpp

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,7 @@
44

55
namespace std {
66
template<typename T> struct initializer_list {
7-
const T *p;
8-
unsigned long n;
9-
initializer_list(const T *p, unsigned long n);
7+
const T *a, *b;
108
};
119
}
1210

clang/test/CodeCompletion/ctor-signature.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ void foo() {
1717
}
1818

1919
namespace std {
20-
template <typename> struct initializer_list {};
20+
template <typename E> struct initializer_list { const E *a, *b; };
2121
} // namespace std
2222

2323
struct Bar {

clang/test/Coverage/unresolved-ctor-expr.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
// GH62105 demonstrated a crash with this example code when calculating
55
// coverage mapping because some source location information was being dropped.
66
// Demonstrate that we do not crash on this code.
7-
namespace std { template <typename> class initializer_list {}; }
7+
namespace std { template <typename E> class initializer_list { const E *a, *b; }; }
88

99
template <typename> struct T {
1010
T(std::initializer_list<int>, int = int());

clang/test/Modules/Inputs/initializer_list/direct.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ namespace std {
22
using size_t = decltype(sizeof(0));
33

44
template<typename T> struct initializer_list {
5-
initializer_list(T*, size_t);
5+
const T* ptr; size_t sz;
66
};
77

88
template<typename T> int min(initializer_list<T>);

clang/test/Modules/pr60775.cppm

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,10 @@
2929
namespace std {
3030
typedef decltype(sizeof(int)) size_t;
3131
template<typename T> struct initializer_list {
32+
const T* ptr; size_t sz;
3233
initializer_list(const T *, size_t);
33-
T* begin();
34-
T* end();
34+
const T* begin();
35+
const T* end();
3536
};
3637
}
3738

clang/test/OpenMP/declare_reduction_codegen_in_templates.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
#ifndef HEADER
1717
#define HEADER
1818

19-
typedef long unsigned a;
19+
typedef decltype(sizeof 0) a;
2020
namespace std {
2121
template <class> class initializer_list {
2222
const int *b;

0 commit comments

Comments
 (0)