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:
parent
1f0bcec874
commit
df97b6b2de
@ -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 {
|
||||
|
@ -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)
|
||||
|
Loading…
x
Reference in New Issue
Block a user