From e1c76caf7e4980cb5a037444f3c4bab545f68df8 Mon Sep 17 00:00:00 2001 From: Vladislav Shchapov Date: Fri, 12 Jan 2024 23:31:15 +0500 Subject: [PATCH] Implement allocator_max_size. Signed-off-by: Vladislav Shchapov --- include/fmt/format.h | 42 +++++++++++++++++++++++++++++++++++++--- test/format-impl-test.cc | 7 +++++++ test/format-test.cc | 35 +++++++++++++++++++-------------- 3 files changed, 67 insertions(+), 17 deletions(-) diff --git a/include/fmt/format.h b/include/fmt/format.h index 6ffba5dd030c4..3285abf6e8eaa 100644 --- a/include/fmt/format.h +++ b/include/fmt/format.h @@ -517,6 +517,42 @@ struct allocator_size> { using type = typename Allocator::size_type; }; +#if FMT_GCC_VERSION || FMT_CLANG_VERSION +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wdeprecated-declarations" +#endif + +// MSVC 2015 does not support of using a dependent decltype in the template +// argument of partial specialization of a class template. Use legacy +// implementation instead of ``void_t().max_size())>``. +template class has_max_size { + template static auto test(...) -> std::false_type; + template + static auto test(int) + -> decltype(std::declval().max_size(), std::true_type()); + + public: + static constexpr bool value = + std::is_same(0)), std::true_type>::value; +}; + +template ::value)> +FMT_CONSTEXPR20 auto allocator_max_size(const Allocator& a) -> + typename allocator_size::type { + return a.max_size(); +} +template ::value)> +FMT_CONSTEXPR20 auto allocator_max_size(const Allocator&) -> + typename allocator_size::type { + return max_value::type>() / + sizeof(typename Allocator::value_type); +} + +#if FMT_GCC_VERSION || FMT_CLANG_VERSION +# pragma GCC diagnostic pop +#endif + template auto copy_str(InputIt begin, InputIt end, appender out) -> appender { get_container(out).append(begin, end); @@ -902,6 +938,8 @@ template { private: T store_[SIZE]; + static_assert(std::is_same::value, + "Invalid allocator value_type"); // Don't inherit from Allocator to avoid generating type_info for it. FMT_NO_UNIQUE_ADDRESS Allocator alloc_; @@ -915,9 +953,7 @@ class basic_memory_buffer : public detail::buffer { static FMT_CONSTEXPR20 void grow(detail::buffer& buf, size_t size) { detail::abort_fuzzing_if(size > 5000); auto& self = static_cast(buf); - constexpr size_t max_size = - detail::max_value::type>() / - sizeof(T); + const size_t max_size = detail::allocator_max_size(self.alloc_); size_t old_capacity = buf.capacity(); size_t new_capacity = old_capacity + old_capacity / 2; if (size > new_capacity) diff --git a/test/format-impl-test.cc b/test/format-impl-test.cc index eda1f23958024..0ad263b7e4303 100644 --- a/test/format-impl-test.cc +++ b/test/format-impl-test.cc @@ -20,6 +20,13 @@ using fmt::detail::bigint; using fmt::detail::fp; using fmt::detail::max_value; +struct Empty {}; +struct MaxSize { + std::size_t max_size() const { return 0; } +}; +static_assert(!fmt::detail::has_max_size::value, ""); +static_assert(fmt::detail::has_max_size::value, ""); + static_assert(!std::is_copy_constructible::value, ""); static_assert(!std::is_copy_assignable::value, ""); diff --git a/test/format-test.cc b/test/format-test.cc index a76c44cf76505..b38a3bd439673 100644 --- a/test/format-test.cc +++ b/test/format-test.cc @@ -413,30 +413,37 @@ TEST(memory_buffer_test, exception_in_deallocate) { EXPECT_CALL(alloc, deallocate(&mem2[0], 2 * size)); } -class smol_allocator : public std::allocator { +template +class max_size_allocator : public Allocator { public: - using size_type = unsigned char; - - auto allocate(size_t n) -> value_type* { - if (n > fmt::detail::max_value()) + using typename Allocator::value_type; + size_t max_size() const noexcept { return MaxSize; } + value_type* allocate(size_t n) { + if (n > max_size()) { throw std::length_error("size > max_size"); - return std::allocator::allocate(n); + } + return std::allocator_traits::allocate( + *static_cast(this), n); } void deallocate(value_type* p, size_t n) { - std::allocator::deallocate(p, n); + std::allocator_traits::deallocate(*static_cast(this), + p, n); } }; TEST(memory_buffer_test, max_size_allocator) { - basic_memory_buffer buffer; - buffer.resize(200); - // new_capacity = 200 + 200/2 = 300 > 256 - buffer.resize(255); // Shouldn't throw. + // 160 = 128 + 32 + using test_allocator = max_size_allocator, 160>; + basic_memory_buffer buffer; + buffer.resize(128); + // new_capacity = 128 + 128/2 = 192 > 160 + buffer.resize(160); // Shouldn't throw. } TEST(memory_buffer_test, max_size_allocator_overflow) { - basic_memory_buffer buffer; - EXPECT_THROW(buffer.resize(256), std::exception); + using test_allocator = max_size_allocator, 160>; + basic_memory_buffer buffer; + EXPECT_THROW(buffer.resize(161), std::exception); } TEST(format_test, exception_from_lib) { @@ -2152,7 +2159,7 @@ TEST(format_int_test, format_int) { EXPECT_EQ(fmt::format_int(42ul).str(), "42"); EXPECT_EQ(fmt::format_int(-42l).str(), "-42"); EXPECT_EQ(fmt::format_int(42ull).str(), "42"); - EXPECT_EQ(fmt::format_int(-42ll).str(), "-42");\ + EXPECT_EQ(fmt::format_int(-42ll).str(), "-42"); EXPECT_EQ(fmt::format_int(max_value()).str(), std::to_string(max_value())); }