Skip to content

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

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 6 commits into from
Jun 20, 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
2 changes: 1 addition & 1 deletion clang-tools-extra/clangd/unittests/ASTTests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down
2 changes: 1 addition & 1 deletion clang-tools-extra/clangd/unittests/HoverTests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down
2 changes: 1 addition & 1 deletion clang-tools-extra/clangd/unittests/InlayHintTests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
2 changes: 1 addition & 1 deletion clang-tools-extra/clangd/unittests/XRefsTests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 = "";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,11 @@
// RUN: true}}"

namespace std {
template <typename>
template <typename E>
class initializer_list
{
public:
const E *a, *b;
initializer_list() noexcept {}
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {}
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

namespace std {

typedef int size_t;
typedef decltype(sizeof 0) size_t;

template<class E> class initializer_list {
public:
Expand All @@ -15,6 +15,8 @@ template<class E> class initializer_list {
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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
4 changes: 4 additions & 0 deletions clang/docs/ReleaseNotes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -589,6 +589,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
----------------------------------

Expand Down
3 changes: 3 additions & 0 deletions clang/include/clang/Basic/DiagnosticSemaKinds.td
Original file line number Diff line number Diff line change
Expand Up @@ -12209,6 +12209,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<
Expand Down
51 changes: 20 additions & 31 deletions clang/lib/AST/ExprConstant.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10535,48 +10535,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();

if (Field->getType()->isPointerType() &&
Info.Ctx.hasSameType(Field->getType()->getPointeeType(),
ArrayType->getElementType())) {
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 (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;
}
Expand Down
44 changes: 18 additions & 26 deletions clang/lib/CodeGen/CGExprAgg.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
51 changes: 51 additions & 0 deletions clang/lib/Sema/SemaInit.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9392,6 +9392,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))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
};
}

Expand Down
2 changes: 1 addition & 1 deletion clang/test/CodeCompletion/ctor-signature.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
2 changes: 1 addition & 1 deletion clang/test/Coverage/unresolved-ctor-expr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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());
Expand Down
2 changes: 1 addition & 1 deletion clang/test/Modules/Inputs/initializer_list/direct.h
Original file line number Diff line number Diff line change
Expand Up @@ -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>);
Expand Down
5 changes: 3 additions & 2 deletions clang/test/Modules/pr60775.cppm
Original file line number Diff line number Diff line change
Expand Up @@ -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();
};
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Loading
Loading