qAbs: check that we're not overflowing

qAbs shouldn't produce a result if that is not representable by the
return type. Not only that, but attempting to negate an integral type
may trigger UB. For instance, if one calls qAbs(INT_MIN), the code will
attempt negating INT_MIN, causing UB.

Add an assert that checks that the return is representable.

Formally, things are slightly more tricky, because the unary operator-
applies integral promotions before negating the value. This means that
something like qAbs(short(SHRT_MIN)) is actually well-defined behavior,
although extremely surprising. Inside qAbs:
* the unary negation will promote the argument to `int`;
* the negation is well defined (will produce `SHRT_MAX+1`, in range);
* the result of the ternary operator inside `qAbs` is, indeed, `int`;
* the return type is `short`, so there's an implicit conversion from the
  returned expression to the returned value. This conversion is implicit
  and well-defined even in case of overflow. The fun bit is that it will
  yield SHRT_MIN (!), a negative number.

So, rather than checking that the applying unary minus to the _promoted_
type doesn't yield UB, I'll check that we're not dealing with the
minimal value of the input type.

Change-Id: I26fbbf10d3a3ac333876864cff74422cb4cdafb9
Reviewed-by: Thiago Macieira <thiago.macieira@intel.com>
This commit is contained in:
Giuseppe D'Angelo 2025-01-27 16:27:35 +01:00
parent 5c20f9aa5c
commit 5b00521ecf

View File

@ -8,6 +8,7 @@
#pragma qt_class(QtNumeric)
#endif
#include <QtCore/qassert.h>
#include <QtCore/qtconfigmacros.h>
#include <QtCore/qtcoreexports.h>
#include <QtCore/qtypes.h>
@ -334,7 +335,12 @@ 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; }
constexpr inline T qAbs(const T &t)
{
if constexpr (std::is_integral_v<T> && std::is_signed_v<T>)
Q_ASSERT(t != std::numeric_limits<T>::min());
return t >= 0 ? t : -t;
}
namespace QtPrivate {
template <typename T,