refactor: add overflow-safe CeilDiv helper

Introduce `CeilDiv()` for integral ceiling division without the typical `(dividend + divisor - 1) / divisor` overflow, asserting a non-zero divisor.

Replace existing ceiling-division expressions with `CeilDiv()` to centralize the preconditions.

Add unit tests covering return type deduction, max-value behavior, and divisor checks.
This commit is contained in:
Lőrinc 2026-01-28 14:45:30 +01:00
parent 4a05825a3f
commit 02d047fd5b
No known key found for this signature in database
GPG Key ID: 669FFF0FFA477A76
15 changed files with 85 additions and 19 deletions

View File

@ -5,8 +5,9 @@
#include <arith_uint256.h>
#include <uint256.h>
#include <crypto/common.h>
#include <uint256.h>
#include <util/overflow.h>
#include <cassert>
@ -194,7 +195,7 @@ arith_uint256& arith_uint256::SetCompact(uint32_t nCompact, bool* pfNegative, bo
uint32_t arith_uint256::GetCompact(bool fNegative) const
{
int nSize = (bits() + 7) / 8;
int nSize = CeilDiv(bits(), 8u);
uint32_t nCompact = 0;
if (nSize <= 3) {
nCompact = GetLow64() << 8 * (3 - nSize);

View File

@ -12,6 +12,7 @@
#include <span.h>
#include <streams.h>
#include <util/fastrange.h>
#include <util/overflow.h>
#include <algorithm>
#include <cmath>
@ -166,7 +167,7 @@ CRollingBloomFilter::CRollingBloomFilter(const unsigned int nElements, const dou
* restrict it to the range 1-50. */
nHashFuncs = std::max(1, std::min((int)round(logFpRate / log(0.5)), 50));
/* In this rolling bloom filter, we'll store between 2 and 3 generations of nElements / 2 entries. */
nEntriesPerGeneration = (nElements + 1) / 2;
nEntriesPerGeneration = CeilDiv(nElements, 2u);
uint32_t nMaxElements = nEntriesPerGeneration * 3;
/* The maximum fpRate = pow(1.0 - exp(-nHashFuncs * nMaxElements / nFilterBits), nHashFuncs)
* => pow(fpRate, 1.0 / nHashFuncs) = 1.0 - exp(-nHashFuncs * nMaxElements / nFilterBits)
@ -182,7 +183,7 @@ CRollingBloomFilter::CRollingBloomFilter(const unsigned int nElements, const dou
* treated as set in generation 1, 2, or 3 respectively.
* These bits are stored in separate integers: position P corresponds to bit
* (P & 63) of the integers data[(P >> 6) * 2] and data[(P >> 6) * 2 + 1]. */
data.resize(((nFilterBits + 63) / 64) << 1);
data.resize(CeilDiv(nFilterBits, 64u) << 1);
reset();
}

View File

@ -6,6 +6,7 @@
#define BITCOIN_CUCKOOCACHE_H
#include <util/fastrange.h>
#include <util/overflow.h>
#include <algorithm>
#include <array>
@ -63,7 +64,7 @@ public:
explicit bit_packed_atomic_flags(uint32_t size)
{
// pad out the size if needed
size = (size + 7) / 8;
size = CeilDiv(size, 8u);
mem.reset(new std::atomic<uint8_t>[size]);
for (uint32_t i = 0; i < size; ++i)
mem[i].store(0xFF);

View File

@ -8,6 +8,7 @@
#include <tinyformat.h>
#include <util/fs_helpers.h>
#include <util/log.h>
#include <util/overflow.h>
#include <stdexcept>
@ -59,8 +60,8 @@ size_t FlatFileSeq::Allocate(const FlatFilePos& pos, size_t add_size, bool& out_
{
out_of_space = false;
unsigned int n_old_chunks = (pos.nPos + m_chunk_size - 1) / m_chunk_size;
unsigned int n_new_chunks = (pos.nPos + add_size + m_chunk_size - 1) / m_chunk_size;
unsigned int n_old_chunks = CeilDiv(pos.nPos, m_chunk_size);
unsigned int n_new_chunks = CeilDiv(pos.nPos + add_size, m_chunk_size);
if (n_new_chunks > n_old_chunks) {
size_t old_size = pos.nPos;
size_t new_size = n_new_chunks * m_chunk_size;

View File

@ -9,6 +9,7 @@
#include <script/interpreter.h>
#include <script/solver.h>
#include <tinyformat.h>
#include <util/overflow.h>
#include <util/strencodings.h>
#include <algorithm>
@ -72,7 +73,7 @@ public:
return {};
}
std::vector<unsigned char> data = {(unsigned char)id.GetWitnessVersion()};
data.reserve(1 + (program.size() * 8 + 4) / 5);
data.reserve(1 + CeilDiv(program.size() * 8, 5u));
ConvertBits<8, 5, true>([&](unsigned char c) { data.push_back(c); }, program.begin(), program.end());
return bech32::Encode(bech32::Encoding::BECH32M, m_params.Bech32HRP(), data);
}

View File

@ -5,13 +5,14 @@
#include <merkleblock.h>
#include <hash.h>
#include <consensus/consensus.h>
#include <hash.h>
#include <util/overflow.h>
std::vector<unsigned char> BitsToBytes(const std::vector<bool>& bits)
{
std::vector<unsigned char> ret((bits.size() + 7) / 8);
std::vector<unsigned char> ret(CeilDiv(bits.size(), 8u));
for (unsigned int p = 0; p < bits.size(); p++) {
ret[p / 8] |= bits[p] << (p % 8);
}
@ -174,7 +175,7 @@ uint256 CPartialMerkleTree::ExtractMatches(std::vector<Txid> &vMatch, std::vecto
if (fBad)
return uint256();
// verify that all bits were consumed (except for the padding caused by serializing it as a byte sequence)
if ((nBitsUsed+7)/8 != (vBits.size()+7)/8)
if (CeilDiv(nBitsUsed, 8u) != CeilDiv(vBits.size(), 8u))
return uint256();
// verify that all hashes were consumed
if (nHashUsed != vHash.size())

View File

@ -28,6 +28,7 @@
#include <undo.h>
#include <util/any.h>
#include <util/check.h>
#include <util/overflow.h>
#include <util/strencodings.h>
#include <validation.h>
@ -993,7 +994,7 @@ static bool rest_getutxos(const std::any& context, HTTPRequest* req, const std::
std::vector<CCoin> outs;
std::string bitmapStringRepresentation;
std::vector<bool> hits;
bitmap.resize((vOutPoints.size() + 7) / 8);
bitmap.resize(CeilDiv(vOutPoints.size(), 8u));
ChainstateManager* maybe_chainman = GetChainman(context, req);
if (!maybe_chainman) return false;
ChainstateManager& chainman = *maybe_chainman;

View File

@ -11,6 +11,7 @@
#include <compat/endian.h>
#include <prevector.h>
#include <span.h>
#include <util/overflow.h>
#include <algorithm>
#include <concepts>
@ -425,7 +426,7 @@ template<typename Stream, VarIntMode Mode, typename I>
void WriteVarInt(Stream& os, I n)
{
CheckVarIntMode<Mode, I>();
unsigned char tmp[(sizeof(n)*8+6)/7];
unsigned char tmp[CeilDiv(sizeof(n) * 8, 7u)];
int len=0;
while(true) {
tmp[len] = (n & 0x7F) | (len ? 0x80 : 0x00);

View File

@ -15,6 +15,7 @@
#include <utility>
#include <util/check.h>
#include <util/overflow.h>
/**
* A memory resource similar to std::pmr::unsynchronized_pool_resource, but
@ -127,7 +128,7 @@ class PoolResource final
*/
[[nodiscard]] static constexpr std::size_t NumElemAlignBytes(std::size_t bytes)
{
return (bytes + ELEM_ALIGN_BYTES - 1) / ELEM_ALIGN_BYTES + (bytes == 0);
return CeilDiv(bytes, ELEM_ALIGN_BYTES) + (bytes == 0);
}
/**

View File

@ -1833,4 +1833,41 @@ BOOST_AUTO_TEST_CASE(mib_string_literal_test)
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_CASE(ceil_div_test)
{
// Type combinations used by current CeilDiv callsites.
BOOST_CHECK((std::is_same_v<decltype(CeilDiv(uint32_t{0}, 8u)), uint32_t>));
BOOST_CHECK((std::is_same_v<decltype(CeilDiv(size_t{0}, 8u)), size_t>));
BOOST_CHECK((std::is_same_v<decltype(CeilDiv(unsigned{0}, size_t{1})), size_t>));
// `common/bloom.cpp` and `cuckoocache.h` patterns.
BOOST_CHECK_EQUAL(CeilDiv(uint32_t{3}, 2u), uint32_t{2});
BOOST_CHECK_EQUAL(CeilDiv(uint32_t{65}, 64u), uint32_t{2});
BOOST_CHECK_EQUAL(CeilDiv(uint32_t{9}, 8u), uint32_t{2});
// `key_io.cpp`, `rest.cpp`, `merkleblock.cpp`, `strencodings.cpp` patterns.
BOOST_CHECK_EQUAL(CeilDiv(size_t{9}, 8u), size_t{2});
BOOST_CHECK_EQUAL(CeilDiv(size_t{10}, 3u), size_t{4});
BOOST_CHECK_EQUAL(CeilDiv(size_t{11}, 5u), size_t{3});
BOOST_CHECK_EQUAL(CeilDiv(size_t{41} * 8, 5u), size_t{66});
// `flatfile.cpp` mixed unsigned/size_t pattern.
BOOST_CHECK_EQUAL(CeilDiv(unsigned{10}, size_t{4}), size_t{3});
// `util/feefrac.h` fast-path rounding-up pattern.
constexpr int64_t fee{12345};
constexpr int32_t at_size{67};
constexpr int32_t size{10};
BOOST_CHECK_EQUAL(CeilDiv(uint64_t(fee) * at_size, uint32_t(size)),
(uint64_t(fee) * at_size + uint32_t(size) - 1) / uint32_t(size));
// `bitset.h` template parameter pattern.
constexpr unsigned bits{129};
constexpr size_t digits{std::numeric_limits<size_t>::digits};
BOOST_CHECK_EQUAL(CeilDiv(bits, digits), (bits + digits - 1) / digits);
// `serialize.h` varint scratch-buffer pattern.
BOOST_CHECK_EQUAL(CeilDiv(sizeof(uint64_t) * 8, 7u), (sizeof(uint64_t) * 8 + 6) / 7);
}
BOOST_AUTO_TEST_SUITE_END()

View File

@ -11,6 +11,7 @@
#include <limits>
#include <stdexcept>
#include <tuple>
#include <util/overflow.h>
/** Class that mimics std::deque<bool>, but with std::vector<bool>'s bit packing.
*
@ -211,7 +212,7 @@ public:
void assign(size_type count, bool val)
{
m_deque.clear();
m_deque.resize((count + BITS_PER_WORD - 1) / BITS_PER_WORD);
m_deque.resize(CeilDiv(count, size_type{BITS_PER_WORD}));
m_pad_begin = 0;
m_pad_end = 0;
if (val) {

View File

@ -6,6 +6,7 @@
#define BITCOIN_UTIL_BITSET_H
#include <util/check.h>
#include <util/overflow.h>
#include <array>
#include <bit>
@ -522,6 +523,6 @@ public:
template<unsigned BITS>
using BitSet = std::conditional_t<(BITS <= 32), bitset_detail::IntBitSet<uint32_t>,
std::conditional_t<(BITS <= std::numeric_limits<size_t>::digits), bitset_detail::IntBitSet<size_t>,
bitset_detail::MultiIntBitSet<size_t, (BITS + std::numeric_limits<size_t>::digits - 1) / std::numeric_limits<size_t>::digits>>>;
bitset_detail::MultiIntBitSet<size_t, CeilDiv(BITS, size_t{std::numeric_limits<size_t>::digits})>>>;
#endif // BITCOIN_UTIL_BITSET_H

View File

@ -7,6 +7,7 @@
#include <span.h>
#include <util/check.h>
#include <util/overflow.h>
#include <compare>
#include <cstdint>
@ -208,7 +209,7 @@ struct FeeFrac
if constexpr (RoundDown) {
return (uint64_t(fee) * at_size) / uint32_t(size);
} else {
return (uint64_t(fee) * at_size + size - 1U) / uint32_t(size);
return CeilDiv(uint64_t(fee) * at_size, uint32_t(size));
}
} else {
// Otherwise, use Mul and Div.

View File

@ -5,6 +5,7 @@
#ifndef BITCOIN_UTIL_OVERFLOW_H
#define BITCOIN_UTIL_OVERFLOW_H
#include <cassert>
#include <climits>
#include <concepts>
#include <limits>
@ -49,6 +50,21 @@ template <class T>
return i + j;
}
/**
* @brief Integer ceiling division (for unsigned values).
*
* Computes the smallest integer q such that q * divisor >= dividend.
* Both dividend and divisor must be unsigned, and divisor must be non-zero.
*
* The implementation avoids overflow that can occur with `(dividend + divisor - 1) / divisor`.
*/
template <std::unsigned_integral Dividend, std::unsigned_integral Divisor>
[[nodiscard]] constexpr auto CeilDiv(const Dividend dividend, const Divisor divisor)
{
assert(divisor > 0);
return dividend / divisor + (dividend % divisor != 0);
}
/**
* @brief Left bit shift with overflow checking.
* @param input The input value to be left shifted.

View File

@ -7,6 +7,7 @@
#include <crypto/hex_base.h>
#include <span.h>
#include <util/overflow.h>
#include <array>
#include <cassert>
@ -100,7 +101,7 @@ std::string EncodeBase64(std::span<const unsigned char> input)
static const char *pbase64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
std::string str;
str.reserve(((input.size() + 2) / 3) * 4);
str.reserve(CeilDiv(input.size(), 3u) * 4);
ConvertBits<8, 6, true>([&](int v) { str += pbase64[v]; }, input.begin(), input.end());
while (str.size() % 4) str += '=';
return str;
@ -146,7 +147,7 @@ std::string EncodeBase32(std::span<const unsigned char> input, bool pad)
static const char *pbase32 = "abcdefghijklmnopqrstuvwxyz234567";
std::string str;
str.reserve(((input.size() + 4) / 5) * 8);
str.reserve(CeilDiv(input.size(), 5u) * 8);
ConvertBits<8, 5, true>([&](int v) { str += pbase32[v]; }, input.begin(), input.end());
if (pad) {
while (str.size() % 8) {