Skip to content

[libc++][test] Add exception tests for vector capacity operations #118141

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 1 commit into from
Jan 13, 2025
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
135 changes: 135 additions & 0 deletions libcxx/test/std/containers/sequences/vector/common.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,18 @@
#ifndef TEST_STD_CONTAINERS_SEQUENCES_VECTOR_COMMON_H
#define TEST_STD_CONTAINERS_SEQUENCES_VECTOR_COMMON_H

#include <array>
#include <cassert>
#include <cstddef>
#include <cstdlib>
#include <memory>
#include <string>
#include <type_traits>
#include <utility>
#include <vector>

#include "count_new.h"
#include "test_macros.h"

struct throwing_t {
int* throw_after_n_ = nullptr;
Expand Down Expand Up @@ -48,6 +54,95 @@ struct throwing_t {
}
};

#if TEST_STD_VER >= 11

template <typename T>
struct move_only_throwing_t {
T data_;
int* throw_after_n_ = nullptr;
bool moved_from_ = false;

move_only_throwing_t() = default;

explicit move_only_throwing_t(const T& data, int& throw_after_n) : data_(data), throw_after_n_(&throw_after_n) {
if (throw_after_n == 0)
throw 1;
--throw_after_n;
}

explicit move_only_throwing_t(T&& data, int& throw_after_n) : data_(std::move(data)), throw_after_n_(&throw_after_n) {
if (throw_after_n == 0)
throw 1;
--throw_after_n;
}

move_only_throwing_t(const move_only_throwing_t&) = delete;
move_only_throwing_t& operator=(const move_only_throwing_t&) = delete;

move_only_throwing_t(move_only_throwing_t&& rhs) : data_(std::move(rhs.data_)), throw_after_n_(rhs.throw_after_n_) {
rhs.throw_after_n_ = nullptr;
rhs.moved_from_ = true;
if (throw_after_n_ == nullptr || *throw_after_n_ == 0)
throw 1;
--*throw_after_n_;
}

move_only_throwing_t& operator=(move_only_throwing_t&& rhs) {
if (this == &rhs)
return *this;
data_ = std::move(rhs.data_);
throw_after_n_ = rhs.throw_after_n_;
rhs.moved_from_ = true;
rhs.throw_after_n_ = nullptr;
if (throw_after_n_ == nullptr || *throw_after_n_ == 0)
throw 1;
--*throw_after_n_;
return *this;
}

friend bool operator==(const move_only_throwing_t& lhs, const move_only_throwing_t& rhs) {
return lhs.data_ == rhs.data_;
}
friend bool operator!=(const move_only_throwing_t& lhs, const move_only_throwing_t& rhs) {
return lhs.data_ != rhs.data_;
}
};

#endif

template <typename T>
struct throwing_data {
T data_;
int* throw_after_n_ = nullptr;
throwing_data() { throw 0; }

throwing_data(const T& data, int& throw_after_n) : data_(data), throw_after_n_(&throw_after_n) {
if (throw_after_n == 0)
throw 0;
--throw_after_n;
}

throwing_data(const throwing_data& rhs) : data_(rhs.data_), throw_after_n_(rhs.throw_after_n_) {
if (throw_after_n_ == nullptr || *throw_after_n_ == 0)
throw 1;
--*throw_after_n_;
}

throwing_data& operator=(const throwing_data& rhs) {
data_ = rhs.data_;
throw_after_n_ = rhs.throw_after_n_;
if (throw_after_n_ == nullptr || *throw_after_n_ == 0)
throw 1;
--*throw_after_n_;
return *this;
}

friend bool operator==(const throwing_data& lhs, const throwing_data& rhs) {
return lhs.data_ == rhs.data_ && lhs.throw_after_n_ == rhs.throw_after_n_;
}
friend bool operator!=(const throwing_data& lhs, const throwing_data& rhs) { return !(lhs == rhs); }
};

template <class T>
struct throwing_allocator {
using value_type = T;
Expand Down Expand Up @@ -125,4 +220,44 @@ inline void check_new_delete_called() {
assert(globalMemCounter.aligned_new_array_called == globalMemCounter.aligned_delete_array_called);
}

template <class T, typename Alloc>
void use_unspecified_but_valid_state_vector(std::vector<T, Alloc> const& v) {
assert(v.size() >= 0); // make sure it can be called
assert(v.capacity() >= 0);
assert(v.empty() || !v.empty());
for (auto it = v.begin(); it != v.end(); ++it) {
auto& element = *it;
(void)element;
}
}

static const std::array<char, 62> letters = {
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K',
'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f',
'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'};

inline std::string getString(std::size_t n, std::size_t len) {
std::string s;
s.reserve(len);
for (std::size_t i = 0; i < len; ++i)
s += letters[(i * i + n) % letters.size()];
return s;
}

inline std::vector<int> getIntegerInputs(std::size_t n) {
std::vector<int> v;
v.reserve(n);
for (std::size_t i = 0; i < n; ++i)
v.push_back(static_cast<int>(i * i + n));
return v;
}

inline std::vector<std::string> getStringInputsWithLength(std::size_t n, std::size_t len) {
std::vector<std::string> v;
v.reserve(n);
for (std::size_t i = 0; i < n; ++i)
v.push_back(getString(i, len));
return v;
}

#endif // TEST_STD_CONTAINERS_SEQUENCES_VECTOR_COMMON_H
Original file line number Diff line number Diff line change
Expand Up @@ -19,126 +19,76 @@
#include "asan_testing.h"

TEST_CONSTEXPR_CXX20 bool tests() {
{
std::vector<int> v;
v.reserve(10);
assert(v.capacity() >= 10);
assert(is_contiguous_container_asan_correct(v));
}
{
std::vector<int> v(100);
assert(v.capacity() == 100);
v.reserve(50);
assert(v.size() == 100);
assert(v.capacity() == 100);
v.reserve(150);
assert(v.size() == 100);
assert(v.capacity() == 150);
assert(is_contiguous_container_asan_correct(v));
}
{
// Add 1 for implementations that dynamically allocate a container proxy.
std::vector<int, limited_allocator<int, 250 + 1> > v(100);
assert(v.capacity() == 100);
v.reserve(50);
assert(v.size() == 100);
assert(v.capacity() == 100);
v.reserve(150);
assert(v.size() == 100);
assert(v.capacity() == 150);
assert(is_contiguous_container_asan_correct(v));
}
#ifndef TEST_HAS_NO_EXCEPTIONS
if (!TEST_IS_CONSTANT_EVALUATED) {
std::vector<int> v;
std::size_t sz = v.max_size() + 1;

try {
v.reserve(sz);
assert(false);
} catch (const std::length_error&) {
assert(v.size() == 0);
assert(v.capacity() == 0);
}
}
if (!TEST_IS_CONSTANT_EVALUATED) {
std::vector<int> v(10, 42);
int* previous_data = v.data();
std::size_t previous_capacity = v.capacity();
std::size_t sz = v.max_size() + 1;

try {
v.reserve(sz);
assert(false);
} catch (std::length_error&) {
assert(v.size() == 10);
assert(v.capacity() == previous_capacity);
assert(v.data() == previous_data);

for (int i = 0; i < 10; ++i) {
assert(v[i] == 42);
}
}
}
#endif
{
std::vector<int> v;
v.reserve(10);
assert(v.capacity() >= 10);
assert(is_contiguous_container_asan_correct(v));
}
{
std::vector<int> v(100);
assert(v.capacity() == 100);
v.reserve(50);
assert(v.size() == 100);
assert(v.capacity() == 100);
v.reserve(150);
assert(v.size() == 100);
assert(v.capacity() == 150);
assert(is_contiguous_container_asan_correct(v));
}
{
// Add 1 for implementations that dynamically allocate a container proxy.
std::vector<int, limited_allocator<int, 250 + 1> > v(100);
assert(v.capacity() == 100);
v.reserve(50);
assert(v.size() == 100);
assert(v.capacity() == 100);
v.reserve(150);
assert(v.size() == 100);
assert(v.capacity() == 150);
assert(is_contiguous_container_asan_correct(v));
}
#if TEST_STD_VER >= 11
{
std::vector<int, min_allocator<int>> v;
v.reserve(10);
assert(v.capacity() >= 10);
assert(is_contiguous_container_asan_correct(v));
}
{
std::vector<int, min_allocator<int>> v(100);
assert(v.capacity() == 100);
v.reserve(50);
assert(v.size() == 100);
assert(v.capacity() == 100);
v.reserve(150);
assert(v.size() == 100);
assert(v.capacity() == 150);
assert(is_contiguous_container_asan_correct(v));
}
{
std::vector<int, safe_allocator<int>> v;
v.reserve(10);
assert(v.capacity() >= 10);
assert(is_contiguous_container_asan_correct(v));
}
{
std::vector<int, safe_allocator<int>> v(100);
assert(v.capacity() == 100);
v.reserve(50);
assert(v.size() == 100);
assert(v.capacity() == 100);
v.reserve(150);
assert(v.size() == 100);
assert(v.capacity() == 150);
assert(is_contiguous_container_asan_correct(v));
}
#endif
#ifndef TEST_HAS_NO_EXCEPTIONS
if (!TEST_IS_CONSTANT_EVALUATED) {
std::vector<int, limited_allocator<int, 100> > v;
v.reserve(50);
assert(v.capacity() == 50);
assert(is_contiguous_container_asan_correct(v));
try {
v.reserve(101);
assert(false);
} catch (const std::length_error&) {
// no-op
}
assert(v.capacity() == 50);
assert(is_contiguous_container_asan_correct(v));
}
{
std::vector<int, min_allocator<int>> v;
v.reserve(10);
assert(v.capacity() >= 10);
assert(is_contiguous_container_asan_correct(v));
}
{
std::vector<int, min_allocator<int>> v(100);
assert(v.capacity() == 100);
v.reserve(50);
assert(v.size() == 100);
assert(v.capacity() == 100);
v.reserve(150);
assert(v.size() == 100);
assert(v.capacity() == 150);
assert(is_contiguous_container_asan_correct(v));
}
{
std::vector<int, safe_allocator<int>> v;
v.reserve(10);
assert(v.capacity() >= 10);
assert(is_contiguous_container_asan_correct(v));
}
{
std::vector<int, safe_allocator<int>> v(100);
assert(v.capacity() == 100);
v.reserve(50);
assert(v.size() == 100);
assert(v.capacity() == 100);
v.reserve(150);
assert(v.size() == 100);
assert(v.capacity() == 150);
assert(is_contiguous_container_asan_correct(v));
}
#endif

return true;
return true;
}

int main(int, char**)
{
int main(int, char**) {
tests();

#if TEST_STD_VER > 17
Expand Down
Loading
Loading