Skip to content

[libc++] Add exception guard for vector<bool>::__init_with_sentinel #115491

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 8 commits into from
Nov 28, 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
18 changes: 6 additions & 12 deletions libcxx/include/__vector/vector_bool.h
Original file line number Diff line number Diff line change
Expand Up @@ -390,18 +390,12 @@ class _LIBCPP_TEMPLATE_VIS vector<bool, _Allocator> {
template <class _InputIterator, class _Sentinel>
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 void
__init_with_sentinel(_InputIterator __first, _Sentinel __last) {
#if _LIBCPP_HAS_EXCEPTIONS
try {
#endif // _LIBCPP_HAS_EXCEPTIONS
for (; __first != __last; ++__first)
push_back(*__first);
#if _LIBCPP_HAS_EXCEPTIONS
} catch (...) {
if (__begin_ != nullptr)
__storage_traits::deallocate(__alloc_, __begin_, __cap_);
throw;
}
#endif // _LIBCPP_HAS_EXCEPTIONS
auto __guard = std::__make_exception_guard(__destroy_vector(*this));

for (; __first != __last; ++__first)
push_back(*__first);

__guard.__complete();
}

template <class _Iterator, class _Sentinel>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,133 +12,121 @@
// Check that vector<bool> constructors don't leak memory when an operation inside the constructor throws an exception

#include <cstddef>
#include <type_traits>
#include <memory>
#include <type_traits>
#include <vector>

#include "../vector/common.h"
#include "count_new.h"
#include "test_iterators.h"

template <class T>
struct Allocator {
using value_type = T;
using is_always_equal = std::false_type;

template <class U>
Allocator(const Allocator<U>&) {}

Allocator(bool should_throw = true) {
if (should_throw)
throw 0;
}

T* allocate(std::size_t n) { return std::allocator<T>().allocate(n); }
void deallocate(T* ptr, std::size_t n) { std::allocator<T>().deallocate(ptr, n); }

template <class U>
friend bool operator==(const Allocator&, const Allocator<U>&) { return true; }
};

template <class IterCat>
struct Iterator {
using iterator_category = IterCat;
using difference_type = std::ptrdiff_t;
using value_type = bool;
using reference = bool&;
using pointer = bool*;

int i_;
bool b_ = true;
Iterator(int i = 0) : i_(i) {}
bool& operator*() {
if (i_ == 1)
throw 1;
return b_;
}

friend bool operator==(const Iterator& lhs, const Iterator& rhs) { return lhs.i_ == rhs.i_; }

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

Iterator& operator++() {
++i_;
return *this;
}
int main(int, char**) {
using AllocVec = std::vector<bool, throwing_allocator<bool> >;

Iterator operator++(int) {
auto tmp = *this;
++i_;
return tmp;
try { // Throw in vector() from allocator
AllocVec vec;
} catch (int) {
}
};

void check_new_delete_called() {
assert(globalMemCounter.new_called == globalMemCounter.delete_called);
assert(globalMemCounter.new_array_called == globalMemCounter.delete_array_called);
assert(globalMemCounter.aligned_new_called == globalMemCounter.aligned_delete_called);
assert(globalMemCounter.aligned_new_array_called == globalMemCounter.aligned_delete_array_called);
}

int main(int, char**) {
using AllocVec = std::vector<bool, Allocator<bool> >;
check_new_delete_called();

#if TEST_STD_VER >= 14
try { // Throw in vector(size_type, const allocator_type&) from allocator
Allocator<bool> alloc(false);
throwing_allocator<bool> alloc(/*throw_on_ctor = */ false, /*throw_on_copy = */ true);
AllocVec get_alloc(0, alloc);
} catch (int) {
}
check_new_delete_called();
#endif // TEST_STD_VER >= 14
#endif // TEST_STD_VER >= 14

try { // Throw in vector(size_type, const value_type&, const allocator_type&) from allocator
throwing_allocator<bool> alloc(/*throw_on_ctor = */ false, /*throw_on_copy = */ true);
AllocVec get_alloc(0, true, alloc);
} catch (int) {
}
check_new_delete_called();

try { // Throw in vector(InputIterator, InputIterator) from input iterator
std::vector<bool> vec((Iterator<std::input_iterator_tag>()), Iterator<std::input_iterator_tag>(2));
std::vector<bool> vec(
throwing_iterator<bool, std::input_iterator_tag>(), throwing_iterator<bool, std::input_iterator_tag>(2));
} catch (int) {
}
check_new_delete_called();

try { // Throw in vector(InputIterator, InputIterator) from forward iterator
std::vector<bool> vec((Iterator<std::forward_iterator_tag>()), Iterator<std::forward_iterator_tag>(2));
std::vector<bool> vec(
throwing_iterator<bool, std::forward_iterator_tag>(), throwing_iterator<bool, std::forward_iterator_tag>(2));
} catch (int) {
}
check_new_delete_called();

try { // Throw in vector(InputIterator, InputIterator) from allocator
int a[] = {1, 2};
AllocVec vec(cpp17_input_iterator<int*>(a), cpp17_input_iterator<int*>(a + 2));
bool a[] = {true, true};
AllocVec vec(cpp17_input_iterator<bool*>(a), cpp17_input_iterator<bool*>(a + 2));
} catch (int) {
}
check_new_delete_called();

try { // Throw in vector(InputIterator, InputIterator, const allocator_type&) from input iterator
std::allocator<bool> alloc;
std::vector<bool> vec(Iterator<std::input_iterator_tag>(), Iterator<std::input_iterator_tag>(2), alloc);
std::vector<bool> vec(
throwing_iterator<bool, std::input_iterator_tag>(), throwing_iterator<bool, std::input_iterator_tag>(2), alloc);
} catch (int) {
}
check_new_delete_called();

try { // Throw in vector(InputIterator, InputIterator, const allocator_type&) from forward iterator
std::allocator<bool> alloc;
std::vector<bool> vec(Iterator<std::forward_iterator_tag>(), Iterator<std::forward_iterator_tag>(2), alloc);
std::vector<bool> vec(throwing_iterator<bool, std::forward_iterator_tag>(),
throwing_iterator<bool, std::forward_iterator_tag>(2),
alloc);
} catch (int) {
}
check_new_delete_called();

try { // Throw in vector(InputIterator, InputIterator, const allocator_type&) from allocator
bool a[] = {true, false};
Allocator<bool> alloc(false);
throwing_allocator<bool> alloc(/*throw_on_ctor = */ false, /*throw_on_copy = */ true);
AllocVec vec(cpp17_input_iterator<bool*>(a), cpp17_input_iterator<bool*>(a + 2), alloc);
} catch (int) {
}
check_new_delete_called();

try { // Throw in vector(InputIterator, InputIterator, const allocator_type&) from allocator
bool a[] = {true, false};
Allocator<bool> alloc(false);
throwing_allocator<bool> alloc(/*throw_on_ctor = */ false, /*throw_on_copy = */ true);
AllocVec vec(forward_iterator<bool*>(a), forward_iterator<bool*>(a + 2), alloc);
} catch (int) {
}
check_new_delete_called();

#if TEST_STD_VER >= 11
try { // Throw in vector(const vector&, const allocator_type&) from allocator
throwing_allocator<bool> alloc(/*throw_on_ctor = */ false, /*throw_on_copy = */ false);
AllocVec vec(alloc);
vec.push_back(true);
alloc.throw_on_copy_ = true;
AllocVec vec2(vec, alloc);
} catch (int) {
}
check_new_delete_called();

try { // Throw in vector(vector&&, const allocator_type&) from allocator
throwing_allocator<bool> alloc(/*throw_on_ctor = */ false, /*throw_on_copy = */ false);
AllocVec vec(alloc);
vec.push_back(true);
alloc.throw_on_copy_ = true;
AllocVec vec2(std::move(vec), alloc);
} catch (int) {
}
check_new_delete_called();

try { // Throw in vector(initializer_list<value_type>, const allocator_type&) constructor from allocator
throwing_allocator<bool> alloc(/*throw_on_ctor = */ false, /*throw_on_copy = */ true);
AllocVec vec({true, true}, alloc);
} catch (int) {
}
check_new_delete_called();
#endif // TEST_STD_VER >= 11

return 0;
}
97 changes: 97 additions & 0 deletions libcxx/test/std/containers/sequences/vector/common.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
//===----------------------------------------------------------------------===//
//
// 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
//
//===----------------------------------------------------------------------===//

#ifndef TEST_STD_CONTAINERS_SEQUENCES_VECTOR_COMMON_H
#define TEST_STD_CONTAINERS_SEQUENCES_VECTOR_COMMON_H

#include <cassert>
#include <cstddef>
#include <memory>
#include <type_traits>

#include "count_new.h"

template <class T>
struct throwing_allocator {
using value_type = T;
using is_always_equal = std::false_type;

bool throw_on_copy_ = false;

explicit throwing_allocator(bool throw_on_ctor = true) {
if (throw_on_ctor)
throw 0;
}

explicit throwing_allocator(bool throw_on_ctor, bool throw_on_copy) : throw_on_copy_(throw_on_copy) {
if (throw_on_ctor)
throw 0;
}

throwing_allocator(const throwing_allocator& rhs) : throw_on_copy_(rhs.throw_on_copy_) {
if (throw_on_copy_)
throw 0;
}

template <class U>
throwing_allocator(const throwing_allocator<U>& rhs) : throw_on_copy_(rhs.throw_on_copy_) {
if (throw_on_copy_)
throw 0;
}

T* allocate(std::size_t n) { return std::allocator<T>().allocate(n); }
void deallocate(T* ptr, std::size_t n) { std::allocator<T>().deallocate(ptr, n); }

template <class U>
friend bool operator==(const throwing_allocator&, const throwing_allocator<U>&) {
return true;
}
};

template <class T, class IterCat>
struct throwing_iterator {
using iterator_category = IterCat;
using difference_type = std::ptrdiff_t;
using value_type = T;
using reference = T&;
using pointer = T*;

int i_;
T v_;

throwing_iterator(int i = 0, const T& v = T()) : i_(i), v_(v) {}

reference operator*() {
if (i_ == 1)
throw 1;
return v_;
}

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

throwing_iterator& operator++() {
++i_;
return *this;
}

throwing_iterator operator++(int) {
auto tmp = *this;
++i_;
return tmp;
}
};

inline void check_new_delete_called() {
assert(globalMemCounter.new_called == globalMemCounter.delete_called);
assert(globalMemCounter.new_array_called == globalMemCounter.delete_array_called);
assert(globalMemCounter.aligned_new_called == globalMemCounter.aligned_delete_called);
assert(globalMemCounter.aligned_new_array_called == globalMemCounter.aligned_delete_array_called);
}

#endif // TEST_STD_CONTAINERS_SEQUENCES_VECTOR_COMMON_H
Loading