summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorGiuseppe D'Angelo <giuseppe.dangelo@kdab.com>2025-01-28 02:33:43 +0100
committerGiuseppe D'Angelo <giuseppe.dangelo@kdab.com>2025-01-29 06:36:48 +0100
commit83c812e1322a2b004bc604be3e6f61fb83246eb0 (patch)
tree99d18b1b753d0c35dd182a45bbf7dd7c57c07e2e
parent57aabff91eed41fb7ba0cf67df4fe7767c6e0e43 (diff)
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>
-rw-r--r--src/corelib/global/qnumeric.h10
-rw-r--r--tests/auto/corelib/global/qnumeric/tst_qnumeric.cpp46
2 files changed, 56 insertions, 0 deletions
diff --git a/src/corelib/global/qnumeric.h b/src/corelib/global/qnumeric.h
index 5a25b1c2392..2e13da4b850 100644
--- a/src/corelib/global/qnumeric.h
+++ b/src/corelib/global/qnumeric.h
@@ -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.
diff --git a/tests/auto/corelib/global/qnumeric/tst_qnumeric.cpp b/tests/auto/corelib/global/qnumeric/tst_qnumeric.cpp
index 37127b4ce09..ec7896ca4fc 100644
--- a/tests/auto/corelib/global/qnumeric/tst_qnumeric.cpp
+++ b/tests/auto/corelib/global/qnumeric/tst_qnumeric.cpp
@@ -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"