From 3f69b6f47f6d3019b017c277154e77c0dcc8613d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ion=20Gazta=C3=B1aga?= Date: Fri, 10 Jul 2026 15:17:20 +0200 Subject: [PATCH] Correctly handle zero-sized allocations in polymorphic memory resources. --- src/monotonic_buffer_resource.cpp | 5 ++++ test/global_resource_test.cpp | 23 +++++++++++++++ test/monotonic_buffer_resource_test.cpp | 37 +++++++++++++++++++++++++ test/pool_resource_test.hpp | 37 +++++++++++++++++++++++++ test/resource_adaptor_test.cpp | 20 +++++++++++++ 5 files changed, 122 insertions(+) diff --git a/src/monotonic_buffer_resource.cpp b/src/monotonic_buffer_resource.cpp index 5507ef2..2fb6e73 100644 --- a/src/monotonic_buffer_resource.cpp +++ b/src/monotonic_buffer_resource.cpp @@ -145,6 +145,11 @@ void* monotonic_buffer_resource::do_allocate(std::size_t bytes, std::size_t alig #endif } + //A zero-sized request must still work correctly so treat it as a 1-byte allocation + if(!bytes){ + bytes = 1u; + } + //See if there is room in current buffer std::size_t aligner = 0u; if(this->remaining_storage(alignment, aligner) < bytes){ diff --git a/test/global_resource_test.cpp b/test/global_resource_test.cpp index 9a00902..07abb81 100644 --- a/test/global_resource_test.cpp +++ b/test/global_resource_test.cpp @@ -116,6 +116,11 @@ void test_new_delete_resource() #endif mr->deallocate(addr, 16, 1); BOOST_TEST(memcount == allocation_count); + + //A zero-sized allocation must still work + void *const zero_addr = mr->allocate(0, 1); + mr->deallocate(zero_addr, 0, 1); + BOOST_TEST(memcount == allocation_count); } #endif //BOOST_CONTAINER_ASAN @@ -143,6 +148,24 @@ void test_null_memory_resource() BOOST_TEST(bad_allocexception_thrown == true); if(p) mr->deallocate(p, 1, 1); + + //A zero-sized allocation must also fail (the standard permits a zero + //request to fail, and null_memory_resource always throws). + bool zero_bad_alloc_thrown = false; + void *pz = 0; + BOOST_CONTAINER_TRY{ + pz = mr->allocate(0, 1); + } + BOOST_CONTAINER_CATCH(std::bad_alloc&) { + zero_bad_alloc_thrown = true; + } + BOOST_CONTAINER_CATCH(...) { + } + BOOST_CONTAINER_CATCH_END + + BOOST_TEST(zero_bad_alloc_thrown == true); + if(pz) + mr->deallocate(pz, 0, 1); #endif //BOOST_NO_EXCEPTIONS } diff --git a/test/monotonic_buffer_resource_test.cpp b/test/monotonic_buffer_resource_test.cpp index 33e3a7b..fe0e78b 100644 --- a/test/monotonic_buffer_resource_test.cpp +++ b/test/monotonic_buffer_resource_test.cpp @@ -360,6 +360,42 @@ void test_do_allocate() BOOST_TEST(mrl.m_info.size() == 0u); } +void test_do_allocate_zero() +{ + //Regression test: after exhausting an external buffer, a zero-sized + //allocation requesting an alignment that needs padding used to underflow + //remaining_storage() (m_current_buffer_size wrapped around to SIZE_MAX) + //because the padding alone did not fit in the current buffer. In addition, + //a zero-sized allocation must return a non-null pointer to distinct storage. + memory_resource_logger mrl; + //An 8-aligned buffer of 7 bytes reproduces the reported scenario. + boost::move_detail::aligned_storage<8u, 8u>::type storage; + const std::size_t buffer_size = 7u; + { + monotonic_buffer_resource m(&storage, buffer_size, &mrl); + BOOST_TEST(m.remaining_storage(1u) == buffer_size); + + //Exhaust the buffer with a byte-aligned allocation + void *const p_full = m.allocate(buffer_size, 1u); + BOOST_TEST(p_full != 0); + BOOST_TEST(m.remaining_storage(1u) == 0u); + BOOST_TEST(mrl.m_info.size() == 0u); + + //A zero-sized allocation on an exhausted buffer must still work + void *const p_zero1 = m.allocate(0u, 1u); + BOOST_TEST(p_zero1 != 0); + BOOST_TEST(m.remaining_storage(1u) < m.next_buffer_size()); + + //A zero-sized allocation with a stricter alignment must honor it + void *const p_zero2 = m.allocate(0u, 4u); + BOOST_TEST((std::size_t(p_zero2) % 4u) == 0u); + BOOST_TEST(m.remaining_storage(1u) < m.next_buffer_size()); + } + //All upstream memory must have been released + BOOST_TEST(mrl.m_mismatches == 0u); + BOOST_TEST(mrl.m_info.size() == 0u); +} + void test_do_deallocate() { memory_resource_logger mrl; @@ -477,6 +513,7 @@ int main() test_upstream_resource(); test_do_allocate(); + test_do_allocate_zero(); test_do_deallocate(); test_do_is_equal(); test_release(); diff --git a/test/pool_resource_test.hpp b/test/pool_resource_test.hpp index 110274e..b80ca1a 100644 --- a/test/pool_resource_test.hpp +++ b/test/pool_resource_test.hpp @@ -390,6 +390,42 @@ void test_do_allocate_deallocate() BOOST_TEST(mrl.m_info.size() == 0u); } +template +void test_do_allocate_zero() +{ + //A zero-sized allocation must return a valid, non-null pointer that can be + //deallocated, and successive zero-sized allocations must return distinct + //blocks (pooled allocations never alias live blocks). + memory_resource_logger mrl; + { + derived_from_pool_resource dmbr(&mrl); + //Zero bytes is served from the smallest pool + void *p0 = dmbr.do_allocate(0u, 1u); + BOOST_TEST(p0 != 0); + void *p1 = dmbr.do_allocate(0u, 1u); + BOOST_TEST(p1 != 0); + //Distinct live blocks must not alias + BOOST_TEST(p0 != p1); + //Both come from the smallest pool (index 0) + BOOST_TEST(dmbr.pool_index(0u) == 0u); + + //Round-trip deallocation must not report mismatches + dmbr.do_deallocate(p1, 0u, 1u); + dmbr.do_deallocate(p0, 0u, 1u); + //Deallocated zero-sized blocks are cached, not returned to upstream + BOOST_TEST(dmbr.pool_cached_blocks(0u) >= 1u); + + //A max-aligned zero-sized allocation must also succeed + void *p2 = dmbr.do_allocate(0u, memory_resource::max_align); + BOOST_TEST(p2 != 0); + BOOST_TEST((std::size_t(p2) % memory_resource::max_align) == 0u); + dmbr.do_deallocate(p2, 0u, memory_resource::max_align); + } + //All memory must have been released back to upstream on destruction + BOOST_TEST(mrl.m_mismatches == 0u); + BOOST_TEST(mrl.m_info.size() == 0u); +} + template void test_do_is_equal() { @@ -487,6 +523,7 @@ void test_pool_resource() test_options_constructor(); test_options(); test_do_allocate_deallocate(); + test_do_allocate_zero(); test_do_is_equal(); test_release(); test_destructor(); diff --git a/test/resource_adaptor_test.cpp b/test/resource_adaptor_test.cpp index 5f8ece2..437880b 100644 --- a/test/resource_adaptor_test.cpp +++ b/test/resource_adaptor_test.cpp @@ -188,6 +188,15 @@ void test_do_allocate_deallocate() //new_allocator, high alignment mr.deallocate(mr.allocate(16, max_alignment_value*4u), 16, max_alignment_value*4u); + + //new_allocator, zero size, low alignment: must return a non-null pointer + void *pz = mr.allocate(0, 1); + mr.deallocate(pz, 0, 1); + + //new_allocator, zero size, high alignment + void *pza = mr.allocate(0, max_alignment_value*4u); + BOOST_TEST((std::size_t(pza) % (max_alignment_value*4u)) == 0u); + mr.deallocate(pza, 0, max_alignment_value*4u); } { typedef resource_adaptor > new_resource_alloc_t; @@ -199,6 +208,17 @@ void test_do_allocate_deallocate() //std::allocator, high alignment mr.deallocate(mr.allocate(16, max_alignment_value*4u), 16, max_alignment_value*4u); + + //std::allocator, zero size, low alignment: must return a non-null pointer + void *pz = mr.allocate(0, 1); + mr.deallocate(pz, 0, 1); + + //std::allocator, zero size, high alignment: must return a non-null, + //correctly aligned pointer + void *pza = mr.allocate(0, max_alignment_value*4u); + BOOST_TEST(pza != 0); + BOOST_TEST((std::size_t(pza) % (max_alignment_value*4u)) == 0u); + mr.deallocate(pza, 0, max_alignment_value*4u); } }