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();
+}

Reply via email to