Skip to content

Commit

Permalink
[libc++] Fix constexpr initialization of std::array<T, 0> (llvm#74667)
Browse files Browse the repository at this point in the history
This patch fixes constexpr default initialization of empty arrays and
improves the tests accordingly.

Fixes llvm#74375
  • Loading branch information
ldionne authored Dec 15, 2023
1 parent 22426d9 commit 70bcd81
Show file tree
Hide file tree
Showing 6 changed files with 187 additions and 101 deletions.
5 changes: 3 additions & 2 deletions libcxx/include/array
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@ template <size_t I, class T, size_t N> const T&& get(const array<T, N>&&) noexce
#include <__type_traits/is_same.h>
#include <__type_traits/is_swappable.h>
#include <__type_traits/remove_cv.h>
#include <__utility/empty.h>
#include <__utility/integer_sequence.h>
#include <__utility/move.h>
#include <__utility/unreachable.h>
Expand Down Expand Up @@ -280,10 +281,10 @@ struct _LIBCPP_TEMPLATE_VIS array<_Tp, 0>
typedef std::reverse_iterator<iterator> reverse_iterator;
typedef std::reverse_iterator<const_iterator> const_reverse_iterator;

typedef __conditional_t<is_const<_Tp>::value, const char, char> _CharType;
typedef __conditional_t<is_const<_Tp>::value, const __empty, __empty> _EmptyType;

struct _ArrayInStructT { _Tp __data_[1]; };
_ALIGNAS_TYPE(_ArrayInStructT) _CharType __elems_[sizeof(_ArrayInStructT)];
_ALIGNAS_TYPE(_ArrayInStructT) _EmptyType __elems_[sizeof(_ArrayInStructT)];

_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX17
value_type* data() _NOEXCEPT {return nullptr;}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,7 @@ void test()
test_not_const<void(Empty::*)(const int&) noexcept(false)>();

// Sequence containers
test_not_const<std::array< int, 0>>();
test_true <std::array< int, 0>>();
test_not_const<std::array< int, 1>>();
test_false <std::array<const int, 1>>();
test_not_const<std::array< volatile int, 1>>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,26 +18,33 @@ struct NoDefault {
TEST_CONSTEXPR NoDefault(int) { }
};

// Test default initialization
// This one isn't constexpr because omitting to initialize fundamental types
// isn't valid in a constexpr context.
struct test_default_initialization {
struct test_initialization {
template <typename T>
void operator()() const
TEST_CONSTEXPR_CXX14 void operator()() const
{
std::array<T, 0> a0; (void)a0;
std::array<T, 1> a1; (void)a1;
std::array<T, 2> a2; (void)a2;
std::array<T, 3> a3; (void)a3;
// Check default initalization
{
std::array<T, 0> a0; (void)a0;
// Before C++20, default initialization doesn't work inside constexpr for
// trivially default constructible types. This only apply to non-empty arrays,
// since empty arrays don't hold an element of type T.
if (TEST_STD_AT_LEAST_20_OR_RUNTIME_EVALUATED || !std::is_trivially_default_constructible<T>::value) {
std::array<T, 1> a1; (void)a1;
std::array<T, 2> a2; (void)a2;
std::array<T, 3> a3; (void)a3;
}

std::array<NoDefault, 0> nodefault; (void)nodefault;
}
};
std::array<NoDefault, 0> nodefault; (void)nodefault;
}

// A const empty array can also be default-initialized regardless of the type
// it contains. For non-empty arrays, this doesn't work whenever T doesn't
// have a user-provided default constructor.
{
const std::array<T, 0> a0; (void)a0;
const std::array<NoDefault, 0> nodefault; (void)nodefault;
}

struct test_nondefault_initialization {
template <typename T>
TEST_CONSTEXPR_CXX14 void operator()() const
{
// Check direct-list-initialization syntax (introduced in C++11)
#if TEST_STD_VER >= 11
{
Expand Down Expand Up @@ -174,13 +181,26 @@ TEST_CONSTEXPR_CXX14 bool with_all_types()
return true;
}

// This is a regression test -- previously, libc++ would implement empty arrays by
// storing an array of characters, which means that the array would be initializable
// from nonsense like an integer (or anything else that can be narrowed to char).
#if TEST_STD_VER >= 20
template <class T>
concept is_list_initializable_int = requires {
{ T{123} };
};

struct Foo { };
static_assert(!is_list_initializable_int<std::array<Foo, 0>>);
static_assert(!is_list_initializable_int<std::array<Foo, 1>>);
#endif

int main(int, char**)
{
with_all_types<test_nondefault_initialization>();
with_all_types<test_default_initialization>(); // not constexpr
with_all_types<test_initialization>();
test_initializer_list();
#if TEST_STD_VER >= 14
static_assert(with_all_types<test_nondefault_initialization>(), "");
static_assert(with_all_types<test_initialization>(), "");
static_assert(test_initializer_list(), "");
#endif

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
//===----------------------------------------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//

// <array>

// template <class T, size_t N>
// struct array

// Make sure std::array<T, N> has the correct object size and alignment.
// This test is mostly meant to catch subtle ABI-breaking regressions.

// Ignore error about requesting a large alignment not being ABI compatible with older AIX systems.
#if defined(_AIX)
# pragma clang diagnostic ignored "-Waix-compat"
#endif

#include <array>
#include <cstddef>
#include <type_traits>
#include <__type_traits/datasizeof.h>

#include "test_macros.h"

template <class T, std::size_t Size>
struct MyArray {
T elems[Size];
};

template <class T>
void test_type() {
{
using Array = std::array<T, 0>;
LIBCPP_STATIC_ASSERT(sizeof(Array) == sizeof(T), "");
LIBCPP_STATIC_ASSERT(TEST_ALIGNOF(Array) == TEST_ALIGNOF(T), "");
LIBCPP_STATIC_ASSERT(sizeof(Array) == sizeof(T[1]), "");
LIBCPP_STATIC_ASSERT(sizeof(Array) == sizeof(MyArray<T, 1>), "");
LIBCPP_STATIC_ASSERT(TEST_ALIGNOF(Array) == TEST_ALIGNOF(MyArray<T, 1>), "");
static_assert(!std::is_empty<Array>::value, "");

// Make sure empty arrays don't have padding bytes
LIBCPP_STATIC_ASSERT(std::__libcpp_datasizeof<Array>::value == sizeof(Array), "");
}

{
using Array = std::array<T, 1>;
static_assert(sizeof(Array) == sizeof(T), "");
static_assert(TEST_ALIGNOF(Array) == TEST_ALIGNOF(T), "");
static_assert(sizeof(Array) == sizeof(T[1]), "");
static_assert(sizeof(Array) == sizeof(MyArray<T, 1>), "");
static_assert(TEST_ALIGNOF(Array) == TEST_ALIGNOF(MyArray<T, 1>), "");
static_assert(!std::is_empty<Array>::value, "");
}

{
using Array = std::array<T, 2>;
static_assert(sizeof(Array) == sizeof(T) * 2, "");
static_assert(TEST_ALIGNOF(Array) == TEST_ALIGNOF(T), "");
static_assert(sizeof(Array) == sizeof(T[2]), "");
static_assert(sizeof(Array) == sizeof(MyArray<T, 2>), "");
static_assert(TEST_ALIGNOF(Array) == TEST_ALIGNOF(MyArray<T, 2>), "");
static_assert(!std::is_empty<Array>::value, "");
}

{
using Array = std::array<T, 3>;
static_assert(sizeof(Array) == sizeof(T) * 3, "");
static_assert(TEST_ALIGNOF(Array) == TEST_ALIGNOF(T), "");
static_assert(sizeof(Array) == sizeof(T[3]), "");
static_assert(sizeof(Array) == sizeof(MyArray<T, 3>), "");
static_assert(TEST_ALIGNOF(Array) == TEST_ALIGNOF(MyArray<T, 3>), "");
static_assert(!std::is_empty<Array>::value, "");
}

{
using Array = std::array<T, 444>;
static_assert(sizeof(Array) == sizeof(T) * 444, "");
static_assert(TEST_ALIGNOF(Array) == TEST_ALIGNOF(T), "");
static_assert(sizeof(Array) == sizeof(T[444]), "");
static_assert(sizeof(Array) == sizeof(MyArray<T, 444>), "");
static_assert(TEST_ALIGNOF(Array) == TEST_ALIGNOF(MyArray<T, 444>), "");
static_assert(!std::is_empty<Array>::value, "");
}
}

struct Empty {};

struct Aggregate {
int i;
};

struct WithPadding {
long double ld;
char c;
};

#if TEST_STD_VER >= 11
struct alignas(TEST_ALIGNOF(std::max_align_t) * 2) Overaligned1 {};

struct alignas(TEST_ALIGNOF(std::max_align_t) * 2) Overaligned2 {
char data[1000];
};

struct alignas(TEST_ALIGNOF(std::max_align_t)) Overaligned3 {
char data[1000];
};

struct alignas(8) Overaligned4 {
char c;
};

struct alignas(8) Overaligned5 {};
#endif

void test() {
test_type<char>();
test_type<short>();
test_type<int>();
test_type<long>();
test_type<long long>();
test_type<float>();
test_type<double>();
test_type<long double>();
test_type<char[1]>();
test_type<char[2]>();
test_type<char[3]>();
test_type<Empty>();
test_type<Aggregate>();
test_type<WithPadding>();

#if TEST_STD_VER >= 11
test_type<std::max_align_t>();
test_type<Overaligned1>();
test_type<Overaligned2>();
test_type<Overaligned3>();
test_type<Overaligned4>();
test_type<Overaligned5>();
#endif
}

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ constexpr void test_construction(AllExtents all_ext) {

// test construction from just dynamic extents
// create an array of just the extents corresponding to dynamic values
std::array<typename AllExtents::value_type, E::rank_dynamic()> dyn_ext{0};
std::array<typename AllExtents::value_type, E::rank_dynamic()> dyn_ext{};
size_t dynamic_idx = 0;
for (size_t r = 0; r < E::rank(); r++) {
if (E::static_extent(r) == std::dynamic_extent) {
Expand Down

0 comments on commit 70bcd81

Please sign in to comment.