From c03a2795a8e044d17835bbf03de0c64dc7b41da8 Mon Sep 17 00:00:00 2001 From: TheCharlatan Date: Thu, 9 Jan 2025 11:25:43 +0100 Subject: [PATCH] util: Add integer left shift helpers The helpers are used in the following commits to increase the safety of conversions during cache size calculations. Co-authored-by: Ryan Ofsky Co-authored-by: stickies-v --- src/test/util_tests.cpp | 97 +++++++++++++++++++++++++++++++++++++++++ src/util/byte_units.h | 22 ++++++++++ src/util/overflow.h | 36 +++++++++++++++ 3 files changed, 155 insertions(+) create mode 100644 src/util/byte_units.h diff --git a/src/test/util_tests.cpp b/src/test/util_tests.cpp index 3f6e5b1b661..129e84bc240 100644 --- a/src/test/util_tests.cpp +++ b/src/test/util_tests.cpp @@ -13,6 +13,7 @@ #include #include #include +#include #include #include #include @@ -1877,4 +1878,100 @@ BOOST_AUTO_TEST_CASE(clearshrink_test) } } +template +void TestCheckedLeftShift() +{ + constexpr auto MAX{std::numeric_limits::max()}; + + // Basic operations + BOOST_CHECK_EQUAL(CheckedLeftShift(0, 1), 0); + BOOST_CHECK_EQUAL(CheckedLeftShift(0, 127), 0); + BOOST_CHECK_EQUAL(CheckedLeftShift(1, 1), 2); + BOOST_CHECK_EQUAL(CheckedLeftShift(2, 2), 8); + BOOST_CHECK_EQUAL(CheckedLeftShift(MAX >> 1, 1), MAX - 1); + + // Max left shift + BOOST_CHECK_EQUAL(CheckedLeftShift(1, std::numeric_limits::digits - 1), MAX / 2 + 1); + + // Overflow cases + BOOST_CHECK(!CheckedLeftShift((MAX >> 1) + 1, 1)); + BOOST_CHECK(!CheckedLeftShift(MAX, 1)); + BOOST_CHECK(!CheckedLeftShift(1, std::numeric_limits::digits)); + BOOST_CHECK(!CheckedLeftShift(1, std::numeric_limits::digits + 1)); + + if constexpr (std::is_signed_v) { + constexpr auto MIN{std::numeric_limits::min()}; + // Negative input + BOOST_CHECK_EQUAL(CheckedLeftShift(-1, 1), -2); + BOOST_CHECK_EQUAL(CheckedLeftShift((MIN >> 2), 1), MIN / 2); + BOOST_CHECK_EQUAL(CheckedLeftShift((MIN >> 1) + 1, 1), MIN + 2); + BOOST_CHECK_EQUAL(CheckedLeftShift(MIN >> 1, 1), MIN); + // Overflow negative + BOOST_CHECK(!CheckedLeftShift((MIN >> 1) - 1, 1)); + BOOST_CHECK(!CheckedLeftShift(MIN >> 1, 2)); + BOOST_CHECK(!CheckedLeftShift(-1, 100)); + } +} + +template +void TestSaturatingLeftShift() +{ + constexpr auto MAX{std::numeric_limits::max()}; + + // Basic operations + BOOST_CHECK_EQUAL(SaturatingLeftShift(0, 1), 0); + BOOST_CHECK_EQUAL(SaturatingLeftShift(0, 127), 0); + BOOST_CHECK_EQUAL(SaturatingLeftShift(1, 1), 2); + BOOST_CHECK_EQUAL(SaturatingLeftShift(2, 2), 8); + BOOST_CHECK_EQUAL(SaturatingLeftShift(MAX >> 1, 1), MAX - 1); + + // Max left shift + BOOST_CHECK_EQUAL(SaturatingLeftShift(1, std::numeric_limits::digits - 1), MAX / 2 + 1); + + // Saturation cases + BOOST_CHECK_EQUAL(SaturatingLeftShift((MAX >> 1) + 1, 1), MAX); + BOOST_CHECK_EQUAL(SaturatingLeftShift(MAX, 1), MAX); + BOOST_CHECK_EQUAL(SaturatingLeftShift(1, std::numeric_limits::digits), MAX); + BOOST_CHECK_EQUAL(SaturatingLeftShift(1, std::numeric_limits::digits + 1), MAX); + + if constexpr (std::is_signed_v) { + constexpr auto MIN{std::numeric_limits::min()}; + // Negative input + BOOST_CHECK_EQUAL(SaturatingLeftShift(-1, 1), -2); + BOOST_CHECK_EQUAL(SaturatingLeftShift((MIN >> 2), 1), MIN / 2); + BOOST_CHECK_EQUAL(SaturatingLeftShift((MIN >> 1) + 1, 1), MIN + 2); + BOOST_CHECK_EQUAL(SaturatingLeftShift(MIN >> 1, 1), MIN); + // Saturation negative + BOOST_CHECK_EQUAL(SaturatingLeftShift((MIN >> 1) - 1, 1), MIN); + BOOST_CHECK_EQUAL(SaturatingLeftShift(MIN >> 1, 2), MIN); + BOOST_CHECK_EQUAL(SaturatingLeftShift(-1, 100), MIN); + } +} + +BOOST_AUTO_TEST_CASE(checked_left_shift_test) +{ + TestCheckedLeftShift(); + TestCheckedLeftShift(); + TestCheckedLeftShift(); + TestCheckedLeftShift(); + TestCheckedLeftShift(); +} + +BOOST_AUTO_TEST_CASE(saturating_left_shift_test) +{ + TestSaturatingLeftShift(); + TestSaturatingLeftShift(); + TestSaturatingLeftShift(); + TestSaturatingLeftShift(); + TestSaturatingLeftShift(); +} + +BOOST_AUTO_TEST_CASE(mib_string_literal_test) +{ + BOOST_CHECK_EQUAL(0_MiB, 0); + BOOST_CHECK_EQUAL(1_MiB, 1024 * 1024); + const auto max_mib{std::numeric_limits::max() >> 20}; + BOOST_CHECK_EXCEPTION(operator""_MiB(static_cast(max_mib) + 1), std::overflow_error, HasReason("MiB value too large for size_t byte conversion")); +} + BOOST_AUTO_TEST_SUITE_END() diff --git a/src/util/byte_units.h b/src/util/byte_units.h new file mode 100644 index 00000000000..894f057a7e4 --- /dev/null +++ b/src/util/byte_units.h @@ -0,0 +1,22 @@ +// Copyright (c) 2025-present The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef BITCOIN_UTIL_BYTE_UNITS_H +#define BITCOIN_UTIL_BYTE_UNITS_H + +#include + +#include + +//! Overflow-safe conversion of MiB to bytes. +constexpr size_t operator"" _MiB(unsigned long long mebibytes) +{ + auto bytes{CheckedLeftShift(mebibytes, 20)}; + if (!bytes || *bytes > std::numeric_limits::max()) { + throw std::overflow_error("MiB value too large for size_t byte conversion"); + } + return *bytes; +} + +#endif // BITCOIN_UTIL_BYTE_UNITS_H diff --git a/src/util/overflow.h b/src/util/overflow.h index 7e0cce6c272..67711af0a5e 100644 --- a/src/util/overflow.h +++ b/src/util/overflow.h @@ -5,6 +5,8 @@ #ifndef BITCOIN_UTIL_OVERFLOW_H #define BITCOIN_UTIL_OVERFLOW_H +#include +#include #include #include #include @@ -47,4 +49,38 @@ template return i + j; } +/** + * @brief Left bit shift with overflow checking. + * @param input The input value to be left shifted. + * @param shift The number of bits to left shift. + * @return (input * 2^shift) or nullopt if it would not fit in the return type. + */ +template +constexpr std::optional CheckedLeftShift(T input, unsigned shift) noexcept +{ + if (shift == 0 || input == 0) return input; + // Avoid undefined c++ behaviour if shift is >= number of bits in T. + if (shift >= sizeof(T) * CHAR_BIT) return std::nullopt; + // If input << shift is too big to fit in T, return nullopt. + if (input > (std::numeric_limits::max() >> shift)) return std::nullopt; + if (input < (std::numeric_limits::min() >> shift)) return std::nullopt; + return input << shift; +} + +/** + * @brief Left bit shift with safe minimum and maximum values. + * @param input The input value to be left shifted. + * @param shift The number of bits to left shift. + * @return (input * 2^shift) clamped to fit between the lowest and highest + * representable values of the type T. + */ +template +constexpr T SaturatingLeftShift(T input, unsigned shift) noexcept +{ + if (auto result{CheckedLeftShift(input, shift)}) return *result; + // If input << shift is too big to fit in T, return biggest positive or negative + // number that fits. + return input < 0 ? std::numeric_limits::min() : std::numeric_limits::max(); +} + #endif // BITCOIN_UTIL_OVERFLOW_H