QtNumeric/QtMath: add assertions in number rounding

qRound, qCeil, qFloor convert the input floating point number to an
int, using various rounding strategies.

They all have UB if the input fp number is out of range for the target
representation. This is actually documented (at least for qRound, see
a47a5932a64a8867077cab7c8efa57587b637481), and just the reality of the
current implementation for all of them.

This commit therefore adds an additional check, which asserts if result
of std::trunc (applied to the FP number after rounding) overflows.
std::trunc has the very same semantics of FP->integral conversions in
the language (round towards zero / discard the fractional part).

Since qRound is constexpr, make the wrapper constexpr-friendly as well,
which requires C++23 or a suitable compiler (GCC has had constexpr cmath
since GCC 5 or so.)

Change-Id: I17740381c2af98ebdda4c240035e53aea26116b0
Reviewed-by: Thiago Macieira <thiago.macieira@intel.com>
This commit is contained in:
Giuseppe D'Angelo 2025-02-05 13:09:17 +01:00
parent 1f0bcec874
commit df97b6b2de
2 changed files with 28 additions and 6 deletions

View File

@ -488,6 +488,28 @@ constexpr inline auto qUnsignedAbs(T t)
return (t >= 0) ? U(t) : U(~U(t) + U(1));
}
template <typename Result,
typename FP,
typename std::enable_if_t<std::is_integral_v<Result>, bool> = true,
typename std::enable_if_t<std::is_floating_point_v<FP>, bool> = true>
constexpr inline Result qCheckedFPConversionToInteger(FP value)
{
// GCC always has constexpr cmath
#if !defined(__cpp_lib_constexpr_cmath) && !defined(Q_CC_GNU_ONLY)
# ifdef QT_SUPPORTS_IS_CONSTANT_EVALUATED
if (!q20::is_constant_evaluated())
# else
if (false)
# endif
#endif
{
const FP truncatedValue = std::trunc(value);
Q_ASSERT(truncatedValue >= FP((std::numeric_limits<Result>::min)()));
Q_ASSERT(truncatedValue <= FP((std::numeric_limits<Result>::max)()));
}
return Result(value);
}
namespace QRoundImpl {
// gcc < 10 doesn't have __has_builtin
#if defined(Q_PROCESSOR_ARM_64) && (__has_builtin(__builtin_round) || defined(Q_CC_GNU)) && !defined(Q_CC_CLANG)
@ -530,22 +552,22 @@ constexpr inline int qSaturateRound(FP value)
constexpr inline int qRound(double d)
{
return int(QtPrivate::QRoundImpl::qRound(d));
return QtPrivate::qCheckedFPConversionToInteger<int>(QtPrivate::QRoundImpl::qRound(d));
}
constexpr inline int qRound(float f)
{
return int(QtPrivate::QRoundImpl::qRound(f));
return QtPrivate::qCheckedFPConversionToInteger<int>(QtPrivate::QRoundImpl::qRound(f));
}
constexpr inline qint64 qRound64(double d)
{
return qint64(QtPrivate::QRoundImpl::qRound(d));
return QtPrivate::qCheckedFPConversionToInteger<qint64>(QtPrivate::QRoundImpl::qRound(d));
}
constexpr inline qint64 qRound64(float f)
{
return qint64(QtPrivate::QRoundImpl::qRound(f));
return QtPrivate::qCheckedFPConversionToInteger<qint64>(QtPrivate::QRoundImpl::qRound(f));
}
namespace QtPrivate {

View File

@ -27,13 +27,13 @@ extern Q_CORE_EXPORT const qreal qt_sine_table[QT_SINE_TABLE_SIZE];
template <typename T> int qCeil(T v)
{
using std::ceil;
return int(ceil(v));
return QtPrivate::qCheckedFPConversionToInteger<int>(ceil(v));
}
template <typename T> int qFloor(T v)
{
using std::floor;
return int(floor(v));
return QtPrivate::qCheckedFPConversionToInteger<int>(floor(v));
}
template <typename T> auto qFabs(T v)