mirror of
https://github.com/bitcoin/bitcoin.git
synced 2025-01-27 19:47:30 -03:00
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 <ryan@ofsky.org> Co-authored-by: stickies-v <stickies-v@protonmail.com>
This commit is contained in:
parent
8bd5f8a38c
commit
c03a2795a8
3 changed files with 155 additions and 0 deletions
|
@ -13,6 +13,7 @@
|
|||
#include <test/util/setup_common.h>
|
||||
#include <uint256.h>
|
||||
#include <util/bitdeque.h>
|
||||
#include <util/byte_units.h>
|
||||
#include <util/fs.h>
|
||||
#include <util/fs_helpers.h>
|
||||
#include <util/moneystr.h>
|
||||
|
@ -1877,4 +1878,100 @@ BOOST_AUTO_TEST_CASE(clearshrink_test)
|
|||
}
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void TestCheckedLeftShift()
|
||||
{
|
||||
constexpr auto MAX{std::numeric_limits<T>::max()};
|
||||
|
||||
// Basic operations
|
||||
BOOST_CHECK_EQUAL(CheckedLeftShift<T>(0, 1), 0);
|
||||
BOOST_CHECK_EQUAL(CheckedLeftShift<T>(0, 127), 0);
|
||||
BOOST_CHECK_EQUAL(CheckedLeftShift<T>(1, 1), 2);
|
||||
BOOST_CHECK_EQUAL(CheckedLeftShift<T>(2, 2), 8);
|
||||
BOOST_CHECK_EQUAL(CheckedLeftShift<T>(MAX >> 1, 1), MAX - 1);
|
||||
|
||||
// Max left shift
|
||||
BOOST_CHECK_EQUAL(CheckedLeftShift<T>(1, std::numeric_limits<T>::digits - 1), MAX / 2 + 1);
|
||||
|
||||
// Overflow cases
|
||||
BOOST_CHECK(!CheckedLeftShift<T>((MAX >> 1) + 1, 1));
|
||||
BOOST_CHECK(!CheckedLeftShift<T>(MAX, 1));
|
||||
BOOST_CHECK(!CheckedLeftShift<T>(1, std::numeric_limits<T>::digits));
|
||||
BOOST_CHECK(!CheckedLeftShift<T>(1, std::numeric_limits<T>::digits + 1));
|
||||
|
||||
if constexpr (std::is_signed_v<T>) {
|
||||
constexpr auto MIN{std::numeric_limits<T>::min()};
|
||||
// Negative input
|
||||
BOOST_CHECK_EQUAL(CheckedLeftShift<T>(-1, 1), -2);
|
||||
BOOST_CHECK_EQUAL(CheckedLeftShift<T>((MIN >> 2), 1), MIN / 2);
|
||||
BOOST_CHECK_EQUAL(CheckedLeftShift<T>((MIN >> 1) + 1, 1), MIN + 2);
|
||||
BOOST_CHECK_EQUAL(CheckedLeftShift<T>(MIN >> 1, 1), MIN);
|
||||
// Overflow negative
|
||||
BOOST_CHECK(!CheckedLeftShift<T>((MIN >> 1) - 1, 1));
|
||||
BOOST_CHECK(!CheckedLeftShift<T>(MIN >> 1, 2));
|
||||
BOOST_CHECK(!CheckedLeftShift<T>(-1, 100));
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void TestSaturatingLeftShift()
|
||||
{
|
||||
constexpr auto MAX{std::numeric_limits<T>::max()};
|
||||
|
||||
// Basic operations
|
||||
BOOST_CHECK_EQUAL(SaturatingLeftShift<T>(0, 1), 0);
|
||||
BOOST_CHECK_EQUAL(SaturatingLeftShift<T>(0, 127), 0);
|
||||
BOOST_CHECK_EQUAL(SaturatingLeftShift<T>(1, 1), 2);
|
||||
BOOST_CHECK_EQUAL(SaturatingLeftShift<T>(2, 2), 8);
|
||||
BOOST_CHECK_EQUAL(SaturatingLeftShift<T>(MAX >> 1, 1), MAX - 1);
|
||||
|
||||
// Max left shift
|
||||
BOOST_CHECK_EQUAL(SaturatingLeftShift<T>(1, std::numeric_limits<T>::digits - 1), MAX / 2 + 1);
|
||||
|
||||
// Saturation cases
|
||||
BOOST_CHECK_EQUAL(SaturatingLeftShift<T>((MAX >> 1) + 1, 1), MAX);
|
||||
BOOST_CHECK_EQUAL(SaturatingLeftShift<T>(MAX, 1), MAX);
|
||||
BOOST_CHECK_EQUAL(SaturatingLeftShift<T>(1, std::numeric_limits<T>::digits), MAX);
|
||||
BOOST_CHECK_EQUAL(SaturatingLeftShift<T>(1, std::numeric_limits<T>::digits + 1), MAX);
|
||||
|
||||
if constexpr (std::is_signed_v<T>) {
|
||||
constexpr auto MIN{std::numeric_limits<T>::min()};
|
||||
// Negative input
|
||||
BOOST_CHECK_EQUAL(SaturatingLeftShift<T>(-1, 1), -2);
|
||||
BOOST_CHECK_EQUAL(SaturatingLeftShift<T>((MIN >> 2), 1), MIN / 2);
|
||||
BOOST_CHECK_EQUAL(SaturatingLeftShift<T>((MIN >> 1) + 1, 1), MIN + 2);
|
||||
BOOST_CHECK_EQUAL(SaturatingLeftShift<T>(MIN >> 1, 1), MIN);
|
||||
// Saturation negative
|
||||
BOOST_CHECK_EQUAL(SaturatingLeftShift<T>((MIN >> 1) - 1, 1), MIN);
|
||||
BOOST_CHECK_EQUAL(SaturatingLeftShift<T>(MIN >> 1, 2), MIN);
|
||||
BOOST_CHECK_EQUAL(SaturatingLeftShift<T>(-1, 100), MIN);
|
||||
}
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(checked_left_shift_test)
|
||||
{
|
||||
TestCheckedLeftShift<uint8_t>();
|
||||
TestCheckedLeftShift<int8_t>();
|
||||
TestCheckedLeftShift<size_t>();
|
||||
TestCheckedLeftShift<uint64_t>();
|
||||
TestCheckedLeftShift<int64_t>();
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(saturating_left_shift_test)
|
||||
{
|
||||
TestSaturatingLeftShift<uint8_t>();
|
||||
TestSaturatingLeftShift<int8_t>();
|
||||
TestSaturatingLeftShift<size_t>();
|
||||
TestSaturatingLeftShift<uint64_t>();
|
||||
TestSaturatingLeftShift<int64_t>();
|
||||
}
|
||||
|
||||
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<size_t>::max() >> 20};
|
||||
BOOST_CHECK_EXCEPTION(operator""_MiB(static_cast<unsigned long long>(max_mib) + 1), std::overflow_error, HasReason("MiB value too large for size_t byte conversion"));
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_SUITE_END()
|
||||
|
|
22
src/util/byte_units.h
Normal file
22
src/util/byte_units.h
Normal file
|
@ -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 <util/overflow.h>
|
||||
|
||||
#include <stdexcept>
|
||||
|
||||
//! 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<size_t>::max()) {
|
||||
throw std::overflow_error("MiB value too large for size_t byte conversion");
|
||||
}
|
||||
return *bytes;
|
||||
}
|
||||
|
||||
#endif // BITCOIN_UTIL_BYTE_UNITS_H
|
|
@ -5,6 +5,8 @@
|
|||
#ifndef BITCOIN_UTIL_OVERFLOW_H
|
||||
#define BITCOIN_UTIL_OVERFLOW_H
|
||||
|
||||
#include <climits>
|
||||
#include <concepts>
|
||||
#include <limits>
|
||||
#include <optional>
|
||||
#include <type_traits>
|
||||
|
@ -47,4 +49,38 @@ template <class T>
|
|||
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 <std::integral T>
|
||||
constexpr std::optional<T> 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<T>::max() >> shift)) return std::nullopt;
|
||||
if (input < (std::numeric_limits<T>::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 <std::integral T>
|
||||
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<T>::min() : std::numeric_limits<T>::max();
|
||||
}
|
||||
|
||||
#endif // BITCOIN_UTIL_OVERFLOW_H
|
||||
|
|
Loading…
Add table
Reference in a new issue