From 12d3022fac9bd80397166917d01848541b4e216d Mon Sep 17 00:00:00 2001 From: Daniel Kral Date: Wed, 6 May 2026 20:34:30 +0200 Subject: [PATCH 1/2] Add fix The same fix as in https://github.com/boostorg/multi_index/pull/94/changes --- include/boost/unordered/detail/implementation.hpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/include/boost/unordered/detail/implementation.hpp b/include/boost/unordered/detail/implementation.hpp index 214243b1d1..595ec2693c 100644 --- a/include/boost/unordered/detail/implementation.hpp +++ b/include/boost/unordered/detail/implementation.hpp @@ -2712,8 +2712,9 @@ namespace boost { inline void table::reserve_for_insert(std::size_t num_elements) { if (num_elements > max_load_) { - std::size_t const num_buckets = static_cast( - 1.0f + std::ceil(static_cast(num_elements) / mlf_)); + std::size_t const num_buckets = (std::max)( + static_cast(1.0f + std::ceil(static_cast(num_elements) / mlf_)), + static_cast(bucket_count() + 1)); this->rehash_impl(num_buckets); } From 10a8e10802369acc6de27ed6dec94ef669901e0c Mon Sep 17 00:00:00 2001 From: joaquintides Date: Thu, 28 May 2026 20:30:05 +0200 Subject: [PATCH 2/2] regression-tested previous fix, plus regression-tested and fixed adjacent UB bug related to double-to-integer conversion --- .../boost/unordered/detail/implementation.hpp | 4 +- test/unordered/rehash_tests.cpp | 99 +++++++++++++++++++ 2 files changed, 101 insertions(+), 2 deletions(-) diff --git a/include/boost/unordered/detail/implementation.hpp b/include/boost/unordered/detail/implementation.hpp index 595ec2693c..3e1be70ddf 100644 --- a/include/boost/unordered/detail/implementation.hpp +++ b/include/boost/unordered/detail/implementation.hpp @@ -1,6 +1,6 @@ // Copyright (C) 2003-2004 Jeremy B. Maitin-Shepard. // Copyright (C) 2005-2016 Daniel James -// Copyright (C) 2022-2024 Joaquin M Lopez Munoz. +// Copyright (C) 2022-2026 Joaquin M Lopez Munoz. // Copyright (C) 2022-2023 Christian Mazakas // Copyright (C) 2024 Braden Ganetsky // @@ -1781,7 +1781,7 @@ namespace boost { static std::size_t min_buckets(std::size_t num_elements, float mlf) { - std::size_t num_buckets = static_cast( + std::size_t num_buckets = boost::unordered::detail::double_to_size( std::ceil(static_cast(num_elements) / mlf)); if (num_buckets == 0 && num_elements > 0) { // mlf == inf diff --git a/test/unordered/rehash_tests.cpp b/test/unordered/rehash_tests.cpp index 831a5c2347..da0b02712c 100644 --- a/test/unordered/rehash_tests.cpp +++ b/test/unordered/rehash_tests.cpp @@ -1,6 +1,7 @@ // Copyright 2006-2009 Daniel James. // Copyright 2022-2023 Christian Mazakas. +// Copyright 2026 Joaquin M Lopez Munoz. // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) @@ -587,6 +588,102 @@ namespace rehash_tests { } } + // White-box tests for https://github.com/boostorg/unordered/pull/348 + + struct failing_allocator_exception { std::size_t n; }; + + template + struct failing_allocator + { + using value_type = T; + + failing_allocator() = default; + template failing_allocator(const failing_allocator&) {} + + T* allocate(std::size_t n) { throw failing_allocator_exception{n}; } + void deallocate(T*, std::size_t) { } + + bool operator==(const failing_allocator&) const { return true; } + bool operator!=(const failing_allocator&) const { return false; } + }; + + using test_gh348_table = typename boost::unordered::detail::set< + failing_allocator, int, boost::hash, std::equal_to>::table; + static std::size_t test_gh348_table_bucket_count = 0; + +} // namespace rehash_tests + +namespace boost { +namespace unordered { +namespace detail { + + template<> + auto ::rehash_tests::test_gh348_table::bucket_count() const -> + ::rehash_tests::test_gh348_table::size_type + { + return ::rehash_tests::test_gh348_table_bucket_count; + } + +} // namespace detail +} // namespace unordered +} // namespace boost + +namespace rehash_tests { + + void test_gh348_1(int /* dummy for UNORDERED_TEST */) + { + using primes = boost::unordered::detail::prime_fmod_size<>; + const auto size_t_max = (std::numeric_limits::max)(); + + for(double mlf = 0.1; mlf < 10.0; mlf += 0.1) { + for(std::size_t i = 0; i < primes::sizes_len; ++i) { + test_gh348_table t; + t.mlf_ = (float)mlf; + test_gh348_table_bucket_count = 0; + auto bc = primes::sizes[i]; + std::size_t n = boost::unordered::detail::double_to_size( + 0.9 * static_cast(t.mlf_) * static_cast(bc)); + if(n == size_t_max) continue; + try { + t.reserve(n); + } + catch(const failing_allocator_exception& e) { + test_gh348_table_bucket_count = e.n; + BOOST_TEST_LE(bc, test_gh348_table_bucket_count); + } + t.max_load_ = boost::unordered::detail::double_to_size( + static_cast(t.mlf_) * + static_cast(test_gh348_table_bucket_count)); + if(t.max_load_ == size_t_max) continue; + try { + t.reserve_for_insert(t.max_load_ + 1); + } + catch(const failing_allocator_exception& e) { + if(i == primes::sizes_len - 1) { + BOOST_TEST_EQ(test_gh348_table_bucket_count, e.n); + } + else{ + BOOST_TEST_LT(test_gh348_table_bucket_count, e.n); + } + } + } + } + } + + void test_gh348_2(int /* dummy for UNORDERED_TEST */) + { + for(double mlf = 0.1; mlf < 2.0; mlf += 0.1) { + test_gh348_table t; + t.mlf_ = (float)mlf; + try { + t.reserve((std::numeric_limits::max)()); + } + catch(const failing_allocator_exception& e) { + BOOST_TEST_GT(e.n, 1000u); + } + } + } + using test::default_generator; using test::generate_collisions; using test::limited_range; @@ -739,6 +836,8 @@ namespace rehash_tests { (test_multiset_ptr)(int_multimap_ptr) (test_multiset_tracking)(test_multimap_tracking))( (default_generator)(generate_collisions)(limited_range))) + UNORDERED_TEST(test_gh348_1,((0))) + UNORDERED_TEST(test_gh348_2,((0))) // clang-format on #endif } // namespace rehash_tests