Fix float bugs in reserve_for_insert and min_buckets (#348)

* Add fix
* regression-tested previous fix, plus regression-tested and fixed adjacent UB bug related to double-to-integer conversion

---------

Co-authored-by: Daniel Kral <daniel.kral@recombee.com>
Co-authored-by: joaquintides <joaquin@tid.es>
This commit is contained in:
kraldan
2026-05-29 08:57:52 +02:00
committed by GitHub
parent cebefdede7
commit 02c0fc5faa
2 changed files with 103 additions and 3 deletions
@@ -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>(
std::size_t num_buckets = boost::unordered::detail::double_to_size(
std::ceil(static_cast<float>(num_elements) / mlf));
if (num_buckets == 0 && num_elements > 0) { // mlf == inf
@@ -2712,8 +2712,9 @@ namespace boost {
inline void table<Types>::reserve_for_insert(std::size_t num_elements)
{
if (num_elements > max_load_) {
std::size_t const num_buckets = static_cast<std::size_t>(
1.0f + std::ceil(static_cast<float>(num_elements) / mlf_));
std::size_t const num_buckets = (std::max)(
static_cast<std::size_t>(1.0f + std::ceil(static_cast<float>(num_elements) / mlf_)),
static_cast<std::size_t>(bucket_count() + 1));
this->rehash_impl(num_buckets);
}
+99
View File
@@ -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<typename T>
struct failing_allocator
{
using value_type = T;
failing_allocator() = default;
template<typename U> failing_allocator(const failing_allocator<U>&) {}
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>, int, boost::hash<int>, std::equal_to<int>>::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<std::size_t>::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<double>(t.mlf_) * static_cast<double>(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<double>(t.mlf_) *
static_cast<double>(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<std::size_t>::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