Fix basic_fields move assignment

Fixes #2517
This commit is contained in:
Mohammad Nejati
2024-06-08 20:36:39 +00:00
committed by Mohammad Nejati
parent e55b559ed3
commit e7f49190ef
3 changed files with 83 additions and 63 deletions

View File

@ -204,6 +204,15 @@ private:
using alloc_traits = using alloc_traits =
beast::detail::allocator_traits<rebind_type>; beast::detail::allocator_traits<rebind_type>;
using pocma = typename
alloc_traits::propagate_on_container_move_assignment;
using pocca = typename
alloc_traits::propagate_on_container_copy_assignment;
using pocs = typename
alloc_traits::propagate_on_container_swap;
using size_type = typename using size_type = typename
beast::detail::allocator_traits<Allocator>::size_type; beast::detail::allocator_traits<Allocator>::size_type;
@ -265,7 +274,7 @@ public:
as if constructed using the same allocator. as if constructed using the same allocator.
*/ */
basic_fields& operator=(basic_fields&&) noexcept( basic_fields& operator=(basic_fields&&) noexcept(
alloc_traits::propagate_on_container_move_assignment::value); pocma::value && std::is_nothrow_move_assignable<Allocator>::value);
/// Copy assignment. /// Copy assignment.
basic_fields& operator=(basic_fields const&); basic_fields& operator=(basic_fields const&);

View File

@ -23,8 +23,6 @@
#include <boost/beast/http/chunk_encode.hpp> #include <boost/beast/http/chunk_encode.hpp>
#include <boost/core/exchange.hpp> #include <boost/core/exchange.hpp>
#include <boost/throw_exception.hpp> #include <boost/throw_exception.hpp>
#include <stdexcept>
#include <string>
namespace boost { namespace boost {
namespace beast { namespace beast {
@ -434,15 +432,12 @@ template<class Allocator>
auto auto
basic_fields<Allocator>:: basic_fields<Allocator>::
operator=(basic_fields&& other) noexcept( operator=(basic_fields&& other) noexcept(
alloc_traits::propagate_on_container_move_assignment::value) pocma::value && std::is_nothrow_move_assignable<Allocator>::value)
-> basic_fields& -> basic_fields&
{ {
static_assert(is_nothrow_move_assignable<Allocator>::value,
"Allocator must be noexcept assignable.");
if(this == &other) if(this == &other)
return *this; return *this;
move_assign(other, std::integral_constant<bool, move_assign(other, pocma{});
alloc_traits:: propagate_on_container_move_assignment::value>{});
return *this; return *this;
} }
@ -452,8 +447,7 @@ basic_fields<Allocator>::
operator=(basic_fields const& other) -> operator=(basic_fields const& other) ->
basic_fields& basic_fields&
{ {
copy_assign(other, std::integral_constant<bool, copy_assign(other, pocca{});
alloc_traits::propagate_on_container_copy_assignment::value>{});
return *this; return *this;
} }
@ -653,8 +647,7 @@ void
basic_fields<Allocator>:: basic_fields<Allocator>::
swap(basic_fields<Allocator>& other) swap(basic_fields<Allocator>& other)
{ {
swap(other, std::integral_constant<bool, swap(other, pocs{});
alloc_traits::propagate_on_container_swap::value>{});
} }
template<class Allocator> template<class Allocator>
@ -1124,13 +1117,13 @@ basic_fields<Allocator>::
move_assign(basic_fields& other, std::true_type) move_assign(basic_fields& other, std::true_type)
{ {
clear_all(); clear_all();
this->get() = std::move(other.get());
set_ = std::move(other.set_); set_ = std::move(other.set_);
list_ = std::move(other.list_); list_ = std::move(other.list_);
method_ = other.method_; method_ = other.method_;
target_or_reason_ = other.target_or_reason_; target_or_reason_ = other.target_or_reason_;
other.method_ = {}; other.method_ = {};
other.target_or_reason_ = {}; other.target_or_reason_ = {};
this->get() = other.get();
} }
template<class Allocator> template<class Allocator>

View File

@ -28,62 +28,13 @@ public:
static constexpr std::size_t max_static_buffer = static constexpr std::size_t max_static_buffer =
sizeof(beast::detail::temporary_buffer); sizeof(beast::detail::temporary_buffer);
template<class T>
class test_allocator
{
public:
using value_type = T;
test_allocator() noexcept(false) {}
template<class U, class = typename
std::enable_if<!std::is_same<test_allocator, U>::value>::type>
test_allocator(test_allocator<U> const&) noexcept {}
value_type*
allocate(std::size_t n)
{
return static_cast<value_type*>(::operator new (n*sizeof(value_type)));
}
void
deallocate(value_type* p, std::size_t) noexcept
{
::operator delete(p);
}
template<class U>
friend
bool
operator==(test_allocator<T> const&, test_allocator<U> const&) noexcept
{
return true;
}
template<class U>
friend
bool
operator!=(test_allocator<T> const& x, test_allocator<U> const& y) noexcept
{
return !(x == y);
}
};
using test_fields = basic_fields<test_allocator<char>>;
BOOST_STATIC_ASSERT(is_fields<fields>::value); BOOST_STATIC_ASSERT(is_fields<fields>::value);
BOOST_STATIC_ASSERT(is_fields<test_fields>::value);
// std::allocator is noexcept movable, fields should satisfy // std::allocator is noexcept movable, fields should satisfy
// these constraints as well. // these constraints as well.
BOOST_STATIC_ASSERT(std::is_nothrow_move_constructible<fields>::value); BOOST_STATIC_ASSERT(std::is_nothrow_move_constructible<fields>::value);
BOOST_STATIC_ASSERT(std::is_nothrow_move_assignable<fields>::value); BOOST_STATIC_ASSERT(std::is_nothrow_move_assignable<fields>::value);
// Check if basic_fields respects throw-constructibility and
// propagate_on_container_move_assignment of the allocator.
BOOST_STATIC_ASSERT(std::is_nothrow_move_constructible<test_fields>::value);
BOOST_STATIC_ASSERT(!std::is_nothrow_move_assignable<test_fields>::value);
template<class Allocator> template<class Allocator>
using fa_t = basic_fields<Allocator>; using fa_t = basic_fields<Allocator>;
@ -1072,6 +1023,72 @@ public:
BOOST_STATIC_ASSERT(( insert_test<string_view, const char(&)[10]>::value)); BOOST_STATIC_ASSERT(( insert_test<string_view, const char(&)[10]>::value));
} }
template<class T>
class throwing_allocator
{
public:
using value_type = T;
throwing_allocator() noexcept(false) {}
throwing_allocator(throwing_allocator const&) noexcept(false) {}
throwing_allocator(throwing_allocator&&) noexcept(false) {}
throwing_allocator& operator=(throwing_allocator const&) noexcept(false) { return *this; }
throwing_allocator& operator=(throwing_allocator&&) noexcept(false) { return *this; }
template<class U, class = typename
std::enable_if<!std::is_same<throwing_allocator, U>::value>::type>
throwing_allocator(throwing_allocator<U> const&) noexcept(false) {}
value_type*
allocate(std::size_t n)
{
return static_cast<value_type*>(::operator new (n*sizeof(value_type)));
}
void
deallocate(value_type* p, std::size_t) noexcept
{
::operator delete(p);
}
template<class U>
friend
bool
operator==(throwing_allocator<T> const&, throwing_allocator<U> const&) noexcept
{
return true;
}
template<class U>
friend
bool
operator!=(throwing_allocator<T> const& x, throwing_allocator<U> const& y) noexcept
{
return !(x == y);
}
};
void
testIssue2517()
{
using test_fields = basic_fields<throwing_allocator<char>>;
BOOST_STATIC_ASSERT(is_fields<test_fields>::value);
// Check if basic_fields respects throw-constructibility and
// propagate_on_container_move_assignment of the allocator.
BOOST_STATIC_ASSERT(std::is_nothrow_move_constructible<test_fields>::value);
BOOST_STATIC_ASSERT(!std::is_nothrow_move_assignable<test_fields>::value);
test_fields f1;
f1.insert("1", "1");
test_fields f2;
f2 = std::move(f1);
BEAST_EXPECT(f1.begin() == f1.end());
BEAST_EXPECT(f2["1"] == "1");
}
void void
testEmpty() testEmpty()
{ {
@ -1103,6 +1120,7 @@ public:
testIssue1828(); testIssue1828();
boost::ignore_unused(&fields_test::testIssue2085); boost::ignore_unused(&fields_test::testIssue2085);
testIssue2517();
testEmpty(); testEmpty();
} }
}; };