https://gcc.gnu.org/g:4a051eb720e9fea04bd026f8b7eb6131e467037b
commit r17-5744-g4a051eb720e9fea04bd026f8b7eb6131e467037b Author: Jonathan Wakely <[email protected]> Date: Thu Oct 29 11:36:05 2026 +0000 libstdc++: Fix crash in pmr::unsynchronized_pool_resource [PR128264] There's an exception-safety bug in the initial allocation for a pool's chunks. If the allocation fails the exception is swallowed and the capacity has already been incorrectly set to a non-zero value. This leaves the pool in a corrupted state. There's no separate test for pmr::synchronized_pool_resource because that uses the same code and so will be fixed by this commit. libstdc++-v3/ChangeLog: PR libstdc++/128264 * src/c++17/memory_resource.cc (_Pool::replenish): Rethrow exception. (_Pool::vector::insert): Use named variables for new capacity. For initial allocation only set capacity if allocation succeeds. * testsuite/20_util/unsynchronized_pool_resource/128264.cc: New test. Reviewed-by: Tomasz KamiĆski <[email protected]> Diff: --- libstdc++-v3/src/c++17/memory_resource.cc | 10 ++- .../20_util/unsynchronized_pool_resource/128264.cc | 82 ++++++++++++++++++++++ 2 files changed, 89 insertions(+), 3 deletions(-) diff --git a/libstdc++-v3/src/c++17/memory_resource.cc b/libstdc++-v3/src/c++17/memory_resource.cc index d8b36554e9fc..f5f004a34fed 100644 --- a/libstdc++-v3/src/c++17/memory_resource.cc +++ b/libstdc++-v3/src/c++17/memory_resource.cc @@ -730,6 +730,7 @@ namespace __catch (...) { __r->deallocate(__p, __bytes, __alignment); + __throw_exception_again; } if (_M_blocks_per_chunk < __opts.max_blocks_per_chunk) { @@ -824,9 +825,10 @@ namespace } else if (size > 0) { + const size_t new_cap = capacity * 1.5; polymorphic_allocator<value_type> __alloc(r); auto __mid = std::lower_bound(begin(), end() - 1, back()); - auto __p = __alloc.allocate(capacity * 1.5); + auto __p = __alloc.allocate(new_cap); // move [begin,__mid) to new storage auto __p2 = std::move(begin(), __mid, __p); // move end-1 to new storage @@ -836,12 +838,14 @@ namespace std::destroy(begin(), end()); __alloc.deallocate(data, capacity); data = __p; - capacity *= 1.5; + capacity = new_cap; } else { + const size_t new_cap = 8; polymorphic_allocator<value_type> __alloc(r); - data = __alloc.allocate(capacity = 8); + data = __alloc.allocate(new_cap); + capacity = new_cap; } auto back = ::new (data + size) chunk(std::move(c)); __glibcxx_assert(std::is_sorted(begin(), back)); diff --git a/libstdc++-v3/testsuite/20_util/unsynchronized_pool_resource/128264.cc b/libstdc++-v3/testsuite/20_util/unsynchronized_pool_resource/128264.cc new file mode 100644 index 000000000000..630a0a9e2623 --- /dev/null +++ b/libstdc++-v3/testsuite/20_util/unsynchronized_pool_resource/128264.cc @@ -0,0 +1,82 @@ +// { dg-do run { target c++17 } } + +// Bug 128264 pmr::unsynchronized_pool_resource::allocate crashes instead +// of throwing when upstream fails in _Pool::replenish + +#include <memory_resource> +#include <new> + +// Keep allocating from the memory_resource until it throws bad_alloc. +static void +run_to_failure(std::pmr::memory_resource& upstream) +{ + std::pmr::pool_options opts; + // Set pool options so that we replenish pools often. + opts.max_blocks_per_chunk = 4; + opts.largest_required_pool_block = 128; + + try + { + std::pmr::unsynchronized_pool_resource res(opts, &upstream); + while (true) + for (int n = 0; n < 152; n += 8) + (void) res.allocate(n); + } + catch (const std::bad_alloc&) + { } +} + +static void +test_pr128264_limit_by_size() +{ + const int bufsz = 10240; + char* buf = new char[bufsz]; + + for (int limit = 0; limit < bufsz; limit += 16) + { + // Upstream resource that uses a fixed-size buffer then fails. + std::pmr::monotonic_buffer_resource res(buf, limit, + std::pmr::null_memory_resource()); + run_to_failure(res); + } + delete[] buf; +} + +// Upstream resource that succeeds for the first N allocations and then throws. +struct failing_resource : std::pmr::memory_resource +{ + explicit failing_resource(int n) : remaining(n) { } + + int remaining; + + void* do_allocate(std::size_t bytes, std::size_t align) override + { + if (remaining <= 0) + throw std::bad_alloc(); + --remaining; + return std::pmr::new_delete_resource()->allocate(bytes, align); + } + + void do_deallocate(void* p, std::size_t bytes, std::size_t align) override + { std::pmr::new_delete_resource()->deallocate(p, bytes, align); } + + bool do_is_equal(const std::pmr::memory_resource& r) const noexcept override + { return this == &r; } +}; + +static void +test_pr128264_limit_by_count() +{ + for (int limit = 0; limit < 200; ++limit) + { + // Upstream resource that allocates a fixed number of times, then fails. + failing_resource res(limit); + run_to_failure(res); + } +} + +int main() +{ + test_pr128264_limit_by_size(); + test_pr128264_limit_by_count(); +}
