Skip to content

Commit c03a279

Browse files
TheCharlatanryanofskystickies-v
committed
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 <[email protected]> Co-authored-by: stickies-v <[email protected]>
1 parent 8bd5f8a commit c03a279

File tree

3 files changed

+155
-0
lines changed

3 files changed

+155
-0
lines changed

src/test/util_tests.cpp

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
#include <test/util/setup_common.h>
1414
#include <uint256.h>
1515
#include <util/bitdeque.h>
16+
#include <util/byte_units.h>
1617
#include <util/fs.h>
1718
#include <util/fs_helpers.h>
1819
#include <util/moneystr.h>
@@ -1877,4 +1878,100 @@ BOOST_AUTO_TEST_CASE(clearshrink_test)
18771878
}
18781879
}
18791880

1881+
template <typename T>
1882+
void TestCheckedLeftShift()
1883+
{
1884+
constexpr auto MAX{std::numeric_limits<T>::max()};
1885+
1886+
// Basic operations
1887+
BOOST_CHECK_EQUAL(CheckedLeftShift<T>(0, 1), 0);
1888+
BOOST_CHECK_EQUAL(CheckedLeftShift<T>(0, 127), 0);
1889+
BOOST_CHECK_EQUAL(CheckedLeftShift<T>(1, 1), 2);
1890+
BOOST_CHECK_EQUAL(CheckedLeftShift<T>(2, 2), 8);
1891+
BOOST_CHECK_EQUAL(CheckedLeftShift<T>(MAX >> 1, 1), MAX - 1);
1892+
1893+
// Max left shift
1894+
BOOST_CHECK_EQUAL(CheckedLeftShift<T>(1, std::numeric_limits<T>::digits - 1), MAX / 2 + 1);
1895+
1896+
// Overflow cases
1897+
BOOST_CHECK(!CheckedLeftShift<T>((MAX >> 1) + 1, 1));
1898+
BOOST_CHECK(!CheckedLeftShift<T>(MAX, 1));
1899+
BOOST_CHECK(!CheckedLeftShift<T>(1, std::numeric_limits<T>::digits));
1900+
BOOST_CHECK(!CheckedLeftShift<T>(1, std::numeric_limits<T>::digits + 1));
1901+
1902+
if constexpr (std::is_signed_v<T>) {
1903+
constexpr auto MIN{std::numeric_limits<T>::min()};
1904+
// Negative input
1905+
BOOST_CHECK_EQUAL(CheckedLeftShift<T>(-1, 1), -2);
1906+
BOOST_CHECK_EQUAL(CheckedLeftShift<T>((MIN >> 2), 1), MIN / 2);
1907+
BOOST_CHECK_EQUAL(CheckedLeftShift<T>((MIN >> 1) + 1, 1), MIN + 2);
1908+
BOOST_CHECK_EQUAL(CheckedLeftShift<T>(MIN >> 1, 1), MIN);
1909+
// Overflow negative
1910+
BOOST_CHECK(!CheckedLeftShift<T>((MIN >> 1) - 1, 1));
1911+
BOOST_CHECK(!CheckedLeftShift<T>(MIN >> 1, 2));
1912+
BOOST_CHECK(!CheckedLeftShift<T>(-1, 100));
1913+
}
1914+
}
1915+
1916+
template <typename T>
1917+
void TestSaturatingLeftShift()
1918+
{
1919+
constexpr auto MAX{std::numeric_limits<T>::max()};
1920+
1921+
// Basic operations
1922+
BOOST_CHECK_EQUAL(SaturatingLeftShift<T>(0, 1), 0);
1923+
BOOST_CHECK_EQUAL(SaturatingLeftShift<T>(0, 127), 0);
1924+
BOOST_CHECK_EQUAL(SaturatingLeftShift<T>(1, 1), 2);
1925+
BOOST_CHECK_EQUAL(SaturatingLeftShift<T>(2, 2), 8);
1926+
BOOST_CHECK_EQUAL(SaturatingLeftShift<T>(MAX >> 1, 1), MAX - 1);
1927+
1928+
// Max left shift
1929+
BOOST_CHECK_EQUAL(SaturatingLeftShift<T>(1, std::numeric_limits<T>::digits - 1), MAX / 2 + 1);
1930+
1931+
// Saturation cases
1932+
BOOST_CHECK_EQUAL(SaturatingLeftShift<T>((MAX >> 1) + 1, 1), MAX);
1933+
BOOST_CHECK_EQUAL(SaturatingLeftShift<T>(MAX, 1), MAX);
1934+
BOOST_CHECK_EQUAL(SaturatingLeftShift<T>(1, std::numeric_limits<T>::digits), MAX);
1935+
BOOST_CHECK_EQUAL(SaturatingLeftShift<T>(1, std::numeric_limits<T>::digits + 1), MAX);
1936+
1937+
if constexpr (std::is_signed_v<T>) {
1938+
constexpr auto MIN{std::numeric_limits<T>::min()};
1939+
// Negative input
1940+
BOOST_CHECK_EQUAL(SaturatingLeftShift<T>(-1, 1), -2);
1941+
BOOST_CHECK_EQUAL(SaturatingLeftShift<T>((MIN >> 2), 1), MIN / 2);
1942+
BOOST_CHECK_EQUAL(SaturatingLeftShift<T>((MIN >> 1) + 1, 1), MIN + 2);
1943+
BOOST_CHECK_EQUAL(SaturatingLeftShift<T>(MIN >> 1, 1), MIN);
1944+
// Saturation negative
1945+
BOOST_CHECK_EQUAL(SaturatingLeftShift<T>((MIN >> 1) - 1, 1), MIN);
1946+
BOOST_CHECK_EQUAL(SaturatingLeftShift<T>(MIN >> 1, 2), MIN);
1947+
BOOST_CHECK_EQUAL(SaturatingLeftShift<T>(-1, 100), MIN);
1948+
}
1949+
}
1950+
1951+
BOOST_AUTO_TEST_CASE(checked_left_shift_test)
1952+
{
1953+
TestCheckedLeftShift<uint8_t>();
1954+
TestCheckedLeftShift<int8_t>();
1955+
TestCheckedLeftShift<size_t>();
1956+
TestCheckedLeftShift<uint64_t>();
1957+
TestCheckedLeftShift<int64_t>();
1958+
}
1959+
1960+
BOOST_AUTO_TEST_CASE(saturating_left_shift_test)
1961+
{
1962+
TestSaturatingLeftShift<uint8_t>();
1963+
TestSaturatingLeftShift<int8_t>();
1964+
TestSaturatingLeftShift<size_t>();
1965+
TestSaturatingLeftShift<uint64_t>();
1966+
TestSaturatingLeftShift<int64_t>();
1967+
}
1968+
1969+
BOOST_AUTO_TEST_CASE(mib_string_literal_test)
1970+
{
1971+
BOOST_CHECK_EQUAL(0_MiB, 0);
1972+
BOOST_CHECK_EQUAL(1_MiB, 1024 * 1024);
1973+
const auto max_mib{std::numeric_limits<size_t>::max() >> 20};
1974+
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"));
1975+
}
1976+
18801977
BOOST_AUTO_TEST_SUITE_END()

src/util/byte_units.h

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
// Copyright (c) 2025-present The Bitcoin Core developers
2+
// Distributed under the MIT software license, see the accompanying
3+
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
4+
5+
#ifndef BITCOIN_UTIL_BYTE_UNITS_H
6+
#define BITCOIN_UTIL_BYTE_UNITS_H
7+
8+
#include <util/overflow.h>
9+
10+
#include <stdexcept>
11+
12+
//! Overflow-safe conversion of MiB to bytes.
13+
constexpr size_t operator"" _MiB(unsigned long long mebibytes)
14+
{
15+
auto bytes{CheckedLeftShift(mebibytes, 20)};
16+
if (!bytes || *bytes > std::numeric_limits<size_t>::max()) {
17+
throw std::overflow_error("MiB value too large for size_t byte conversion");
18+
}
19+
return *bytes;
20+
}
21+
22+
#endif // BITCOIN_UTIL_BYTE_UNITS_H

src/util/overflow.h

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
#ifndef BITCOIN_UTIL_OVERFLOW_H
66
#define BITCOIN_UTIL_OVERFLOW_H
77

8+
#include <climits>
9+
#include <concepts>
810
#include <limits>
911
#include <optional>
1012
#include <type_traits>
@@ -47,4 +49,38 @@ template <class T>
4749
return i + j;
4850
}
4951

52+
/**
53+
* @brief Left bit shift with overflow checking.
54+
* @param input The input value to be left shifted.
55+
* @param shift The number of bits to left shift.
56+
* @return (input * 2^shift) or nullopt if it would not fit in the return type.
57+
*/
58+
template <std::integral T>
59+
constexpr std::optional<T> CheckedLeftShift(T input, unsigned shift) noexcept
60+
{
61+
if (shift == 0 || input == 0) return input;
62+
// Avoid undefined c++ behaviour if shift is >= number of bits in T.
63+
if (shift >= sizeof(T) * CHAR_BIT) return std::nullopt;
64+
// If input << shift is too big to fit in T, return nullopt.
65+
if (input > (std::numeric_limits<T>::max() >> shift)) return std::nullopt;
66+
if (input < (std::numeric_limits<T>::min() >> shift)) return std::nullopt;
67+
return input << shift;
68+
}
69+
70+
/**
71+
* @brief Left bit shift with safe minimum and maximum values.
72+
* @param input The input value to be left shifted.
73+
* @param shift The number of bits to left shift.
74+
* @return (input * 2^shift) clamped to fit between the lowest and highest
75+
* representable values of the type T.
76+
*/
77+
template <std::integral T>
78+
constexpr T SaturatingLeftShift(T input, unsigned shift) noexcept
79+
{
80+
if (auto result{CheckedLeftShift(input, shift)}) return *result;
81+
// If input << shift is too big to fit in T, return biggest positive or negative
82+
// number that fits.
83+
return input < 0 ? std::numeric_limits<T>::min() : std::numeric_limits<T>::max();
84+
}
85+
5086
#endif // BITCOIN_UTIL_OVERFLOW_H

0 commit comments

Comments
 (0)