Skip to content

Commit efa287d

Browse files
authored
[libc++] Slightly simplify max_size and add new tests for vector (#119990)
This PR slightly simplifies the implementation of `vector<bool>::max_size` and adds extensive tests for the `max_size()` function for both `vector<bool>` and `vector<T>`. The main purposes of the new tests include: - Verify correctness of `max_size()` under various `size_type` and `difference_type` definitions: check that `max_size()` works properly with allocators that have custom `size_type` and `difference_type`. This is particularly useful for `vector<bool>`, as different `size_type` lead to different `__storage_type` of different word lengths, resulting in varying `max_size()` values for `vector<bool>`. Additionally, different `difference_type` also sets different upper limit of `max_size()` for both `vector<bool>` and `std::vector`. These tests were previously missing. - Eliminate incorrect implementations: Special tests are added to identify and reject incorrect implementations of `vector<bool>::max_size` that unconditionally return `std::min<size_type>(size-max, __internal_cap_to_external(allocator-max-size))`. This can cause overflow in the `__internal_cap_to_external()` call and lead to incorrect results. The new tests ensure that such incorrect implementations are identified.
1 parent 4a2a8ed commit efa287d

File tree

3 files changed

+153
-11
lines changed

3 files changed

+153
-11
lines changed

libcxx/include/__vector/vector_bool.h

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -533,10 +533,8 @@ template <class _Allocator>
533533
_LIBCPP_CONSTEXPR_SINCE_CXX20 typename vector<bool, _Allocator>::size_type
534534
vector<bool, _Allocator>::max_size() const _NOEXCEPT {
535535
size_type __amax = __storage_traits::max_size(__alloc_);
536-
size_type __nmax = numeric_limits<size_type>::max() / 2; // end() >= begin(), always
537-
if (__nmax / __bits_per_word <= __amax)
538-
return __nmax;
539-
return __internal_cap_to_external(__amax);
536+
size_type __nmax = numeric_limits<difference_type>::max();
537+
return __nmax / __bits_per_word <= __amax ? __nmax : __internal_cap_to_external(__amax);
540538
}
541539

542540
// Precondition: __new_size > capacity()
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
9+
// <vector>
10+
// vector<bool>
11+
12+
// size_type max_size() const;
13+
14+
#include <algorithm>
15+
#include <cassert>
16+
#include <climits>
17+
#include <cstdint>
18+
#include <limits>
19+
#include <memory>
20+
#include <type_traits>
21+
#include <vector>
22+
23+
#include "min_allocator.h"
24+
#include "sized_allocator.h"
25+
#include "test_allocator.h"
26+
#include "test_macros.h"
27+
28+
#if TEST_STD_VER >= 11
29+
30+
template <typename Alloc>
31+
TEST_CONSTEXPR_CXX20 void test(const std::vector<bool, Alloc>& v) {
32+
using Vector = std::vector<bool, Alloc>;
33+
using size_type = typename Vector::size_type;
34+
using difference_type = typename Vector::difference_type;
35+
const size_type max_dist = static_cast<size_type>(std::numeric_limits<difference_type>::max());
36+
assert(v.max_size() <= max_dist);
37+
38+
// The following check is specific to libc++ implementation details and is not portable to libstdc++
39+
// and MSVC STL, as they use different types for the underlying word storage.
40+
# if defined(_LIBCPP_VERSION)
41+
using storage_type = typename Vector::__storage_type;
42+
using storage_alloc = typename std::allocator_traits<Alloc>::template rebind_alloc<storage_type>;
43+
using storage_traits = typename std::allocator_traits<Alloc>::template rebind_traits<storage_type>;
44+
const size_type max_alloc = storage_traits::max_size(storage_alloc(v.get_allocator()));
45+
std::size_t bits_per_word = sizeof(storage_type) * CHAR_BIT;
46+
const size_type max_size = max_dist / bits_per_word < max_alloc ? max_dist : max_alloc * bits_per_word;
47+
assert(v.max_size() / bits_per_word <= max_alloc); // max_alloc * bits_per_word may overflow
48+
assert(v.max_size() == max_size);
49+
# endif // defined(_LIBCPP_VERSION)
50+
}
51+
52+
#endif // TEST_STD_VER >= 11
53+
54+
TEST_CONSTEXPR_CXX20 bool tests() {
55+
// The following check is specific to libc++ implementation details and is not portable to libstdc++
56+
// and MSVC STL, as they use different types for the underlying word storage.
57+
#if defined(_LIBCPP_VERSION)
58+
// Test cases where v.max_size() is determined by allocator::max_size()
59+
{
60+
using Alloc = limited_allocator<bool, 10>;
61+
using Vector = std::vector<bool, Alloc>;
62+
using storage_type = Vector::__storage_type;
63+
Vector v;
64+
std::size_t bits_per_word = sizeof(storage_type) * CHAR_BIT;
65+
assert(v.max_size() == 10 * bits_per_word);
66+
}
67+
#endif // defined(_LIBCPP_VERSION)
68+
69+
#if TEST_STD_VER >= 11
70+
71+
// Test with various allocators and different `size_type`s
72+
{
73+
test(std::vector<bool>());
74+
test(std::vector<bool, std::allocator<int> >());
75+
test(std::vector<bool, min_allocator<bool> >());
76+
test(std::vector<bool, test_allocator<bool> >(test_allocator<bool>(1)));
77+
test(std::vector<bool, other_allocator<bool> >(other_allocator<bool>(5)));
78+
test(std::vector<bool, sized_allocator<bool, std::uint8_t, std::int8_t> >());
79+
test(std::vector<bool, sized_allocator<bool, std::uint16_t, std::int16_t> >());
80+
test(std::vector<bool, sized_allocator<bool, std::uint32_t, std::int32_t> >());
81+
test(std::vector<bool, sized_allocator<bool, std::uint64_t, std::int64_t> >());
82+
test(std::vector<bool, limited_allocator<bool, static_cast<std::size_t>(-1)> >());
83+
}
84+
85+
// Test cases to identify incorrect implementations that unconditionally compute an internal-to-external
86+
// capacity in a way that can overflow, leading to incorrect results.
87+
{
88+
test(std::vector<bool, limited_allocator<bool, static_cast<std::size_t>(-1) / 61> >());
89+
test(std::vector<bool, limited_allocator<bool, static_cast<std::size_t>(-1) / 63> >());
90+
}
91+
92+
#endif // TEST_STD_VER >= 11
93+
94+
return true;
95+
}
96+
97+
int main(int, char**) {
98+
tests();
99+
100+
#if TEST_STD_VER >= 20
101+
static_assert(tests());
102+
#endif
103+
104+
return 0;
105+
}

libcxx/test/std/containers/sequences/vector/vector.capacity/max_size.pass.cpp

Lines changed: 46 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,16 +10,36 @@
1010

1111
// size_type max_size() const;
1212

13+
#include <algorithm>
1314
#include <cassert>
15+
#include <cstdint>
1416
#include <limits>
1517
#include <type_traits>
1618
#include <vector>
1719

20+
#include "min_allocator.h"
21+
#include "sized_allocator.h"
1822
#include "test_allocator.h"
1923
#include "test_macros.h"
2024

25+
#if TEST_STD_VER >= 11
2126

22-
TEST_CONSTEXPR_CXX20 bool test() {
27+
template <typename T, typename Alloc>
28+
TEST_CONSTEXPR_CXX20 void test(const std::vector<T, Alloc>& v) {
29+
using Vector = std::vector<T, Alloc>;
30+
using alloc_traits = std::allocator_traits<typename Vector::allocator_type>;
31+
using size_type = typename Vector::size_type;
32+
using difference_type = typename Vector::difference_type;
33+
const size_type max_dist = static_cast<size_type>(std::numeric_limits<difference_type>::max());
34+
const size_type max_alloc = alloc_traits::max_size(v.get_allocator());
35+
assert(v.max_size() <= max_dist);
36+
assert(v.max_size() <= max_alloc);
37+
LIBCPP_ASSERT(v.max_size() == std::min<size_type>(max_dist, max_alloc));
38+
}
39+
40+
#endif // TEST_STD_VER >= 11
41+
42+
TEST_CONSTEXPR_CXX20 bool tests() {
2343
{
2444
typedef limited_allocator<int, 10> A;
2545
typedef std::vector<int, A> C;
@@ -30,29 +50,48 @@ TEST_CONSTEXPR_CXX20 bool test() {
3050
{
3151
typedef limited_allocator<int, (std::size_t)-1> A;
3252
typedef std::vector<int, A> C;
33-
const C::size_type max_dist =
34-
static_cast<C::size_type>(std::numeric_limits<C::difference_type>::max());
53+
const C::size_type max_dist = static_cast<C::size_type>(std::numeric_limits<C::difference_type>::max());
3554
C c;
3655
assert(c.max_size() <= max_dist);
3756
LIBCPP_ASSERT(c.max_size() == max_dist);
3857
}
3958
{
4059
typedef std::vector<char> C;
41-
const C::size_type max_dist =
42-
static_cast<C::size_type>(std::numeric_limits<C::difference_type>::max());
60+
const C::size_type max_dist = static_cast<C::size_type>(std::numeric_limits<C::difference_type>::max());
4361
C c;
4462
assert(c.max_size() <= max_dist);
4563
assert(c.max_size() <= alloc_max_size(c.get_allocator()));
64+
LIBCPP_ASSERT(c.max_size() == std::min(max_dist, alloc_max_size(c.get_allocator())));
65+
}
66+
67+
#if TEST_STD_VER >= 11
68+
69+
// Test with various allocators and diffrent size_type
70+
{
71+
test(std::vector<int>());
72+
test(std::vector<short, std::allocator<short> >());
73+
test(std::vector<unsigned, min_allocator<unsigned> >());
74+
test(std::vector<char, test_allocator<char> >(test_allocator<char>(1)));
75+
test(std::vector<std::size_t, other_allocator<std::size_t> >(other_allocator<std::size_t>(5)));
76+
test(std::vector<int, sized_allocator<int, std::uint8_t, std::int8_t> >());
77+
test(std::vector<int, sized_allocator<int, std::uint16_t, std::int16_t> >());
78+
test(std::vector<int, sized_allocator<int, std::uint32_t, std::int32_t> >());
79+
test(std::vector<int, sized_allocator<int, std::uint64_t, std::int64_t> >());
80+
test(std::vector<int, limited_allocator<int, static_cast<std::size_t>(-1)> >());
81+
test(std::vector<int, limited_allocator<int, static_cast<std::size_t>(-1) / 2> >());
82+
test(std::vector<int, limited_allocator<int, static_cast<std::size_t>(-1) / 4> >());
4683
}
4784

85+
#endif // TEST_STD_VER >= 11
86+
4887
return true;
4988
}
5089

5190
int main(int, char**) {
52-
test();
91+
tests();
5392

5493
#if TEST_STD_VER > 17
55-
static_assert(test());
94+
static_assert(tests());
5695
#endif
5796

5897
return 0;

0 commit comments

Comments
 (0)