QNumeric: add a private qUnsignedAbs

Checking qAbs preconditions reveals that several places into Qt are
calling it with minimal values, causing UB. Those places do not actually
need that the absolute value is of the same type as the parameter.
Therefore, introduce qUnsignedAbs, which is like qAbs() but whose return
type is always unsigned, and therefore can accept everything.

This function is private; I don't want to encourage users to rely on our
extension.

Aggressively cherry picking because this will be needed in subsequent
bugfixes.

Change-Id: I2047768a5fd35f12bf898ca8c2008813434edd8d
Pick-to: 6.9 6.8 6.5 6.2
Reviewed-by: Thiago Macieira <thiago.macieira@intel.com>
This commit is contained in:
Giuseppe D'Angelo 2025-01-28 02:33:43 +01:00
parent 57aabff91e
commit 83c812e132
2 changed files with 56 additions and 0 deletions

View File

@ -336,6 +336,16 @@ template <auto V2, typename T> bool qMulOverflow(T v1, T *r)
template <typename T>
constexpr inline T qAbs(const T &t) { return t >= 0 ? t : -t; }
namespace QtPrivate {
template <typename T,
typename std::enable_if_t<std::is_integral_v<T>, bool> = true>
constexpr inline auto qUnsignedAbs(T t)
{
using U = std::make_unsigned_t<T>;
return (t >= 0) ? U(t) : U(~U(t) + U(1));
}
} // namespace QtPrivate
// 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)
// ARM64 has a single instruction that can do C++ rounding with conversion to integer.

View File

@ -9,6 +9,7 @@
#include <math.h>
#include <float.h>
#include <limits.h>
#include <QtCore/q26numeric.h>
@ -789,5 +790,50 @@ static_assert(q26::saturate_cast<quint64>(min<qint64>) == 0);
} // namespace SaturateCastTest
namespace UnsignedAbsTest {
using QtPrivate::qUnsignedAbs;
static_assert(std::is_same_v<decltype(qUnsignedAbs((char)0)), unsigned char>);
static_assert(std::is_same_v<decltype(qUnsignedAbs((signed char)0)), unsigned char>);
static_assert(std::is_same_v<decltype(qUnsignedAbs((unsigned char)0)), unsigned char>);
static_assert(std::is_same_v<decltype(qUnsignedAbs((short)0)), unsigned short>);
static_assert(std::is_same_v<decltype(qUnsignedAbs((unsigned short)0)), unsigned short>);
static_assert(std::is_same_v<decltype(qUnsignedAbs((int)0)), unsigned int>);
static_assert(std::is_same_v<decltype(qUnsignedAbs((unsigned int)0)), unsigned int>);
static_assert(std::is_same_v<decltype(qUnsignedAbs((long)0)), unsigned long>);
static_assert(std::is_same_v<decltype(qUnsignedAbs((unsigned long)0)), unsigned long>);
static_assert(std::is_same_v<decltype(qUnsignedAbs((long long)0)), unsigned long long>);
static_assert(std::is_same_v<decltype(qUnsignedAbs((unsigned long long)0)), unsigned long long>);
template <typename T> constexpr bool isEqual(T a, T b) { return a == b; }
#define TEST_TYPE(type, utype, minimal, maximal) \
static_assert(isEqual(qUnsignedAbs(type(minimal)), (utype)((utype)(maximal) + (utype)1))); \
static_assert(isEqual(qUnsignedAbs(type(0)), (utype)(0))); \
static_assert(isEqual(qUnsignedAbs(type(1)), (utype)(1))); \
static_assert(isEqual(qUnsignedAbs(type(-1)), (utype)(1))); \
static_assert(isEqual(qUnsignedAbs(type(10)), (utype)(10))); \
static_assert(isEqual(qUnsignedAbs(type(-10)), (utype)(10))); \
static_assert(isEqual(qUnsignedAbs(type(maximal)), (utype)(maximal))); \
using schar = signed char;
using uchar = unsigned char;
using ushort = unsigned short;
using uint = unsigned int;
using ulong = unsigned long;
#if CHAR_MAX == 127
// char is signed
TEST_TYPE(char, uchar, CHAR_MIN, CHAR_MAX)
#endif
TEST_TYPE(schar, uchar, SCHAR_MIN, SCHAR_MAX)
TEST_TYPE(short, ushort, SHRT_MIN, SHRT_MAX)
TEST_TYPE(int, uint, INT_MIN, INT_MAX)
TEST_TYPE(long, ulong, LONG_MIN, LONG_MAX)
TEST_TYPE(qlonglong, qulonglong, LLONG_MIN, LLONG_MAX)
#undef TEST_TYPE
} // namespace UnsignedAbsTest
QTEST_APPLESS_MAIN(tst_QNumeric)
#include "tst_qnumeric.moc"