diff --git a/src/corelib/global/qnumeric_p.h b/src/corelib/global/qnumeric_p.h index 2c5d19d274..c7d44591cc 100644 --- a/src/corelib/global/qnumeric_p.h +++ b/src/corelib/global/qnumeric_p.h @@ -1,7 +1,7 @@ /**************************************************************************** ** -** Copyright (C) 2019 The Qt Company Ltd. -** Copyright (C) 2018 Intel Corporation. +** Copyright (C) 2020 The Qt Company Ltd. +** Copyright (C) 2020 Intel Corporation. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the QtCore module of the Qt Toolkit. @@ -55,6 +55,7 @@ #include "QtCore/private/qglobal_p.h" #include #include +#include #if defined(Q_CC_MSVC) # include @@ -255,6 +256,7 @@ QT_WARNING_POP #if ((defined(Q_CC_INTEL) ? (Q_CC_INTEL >= 1800 && !defined(Q_OS_WIN)) : defined(Q_CC_GNU)) \ && Q_CC_GNU >= 500) || __has_builtin(__builtin_add_overflow) // GCC 5, ICC 18, and Clang 3.8 have builtins to detect overflows +#define Q_INTRINSIC_MUL_OVERFLOW64 template inline typename std::enable_if::value || std::is_signed::value, bool>::type @@ -409,6 +411,83 @@ template <> inline bool add_overflow(quint64 v1, quint64 v2, quint64 *r) } # endif // MSVC X86 #endif // !GCC + +// Implementations for addition, subtraction or multiplication by a +// compile-time constant. For addition and subtraction, we simply call the code +// that detects overflow at runtime. For multiplication, we compare to the +// maximum possible values before multiplying to ensure no overflow happens. + +template bool add_overflow(T v1, std::integral_constant, T *r) +{ + return add_overflow(v1, V2, r); +} + +template bool add_overflow(T v1, T *r) +{ + return add_overflow(v1, std::integral_constant{}, r); +} + +template bool sub_overflow(T v1, std::integral_constant, T *r) +{ + return sub_overflow(v1, V2, r); +} + +template bool sub_overflow(T v1, T *r) +{ + return sub_overflow(v1, std::integral_constant{}, r); +} + +template bool mul_overflow(T v1, std::integral_constant, T *r) +{ + // Runtime detection for anything smaller than or equal to a register + // width, as most architectures' multiplication instructions actually + // produce a result twice as wide as the input registers, allowing us to + // efficiently detect the overflow. + if constexpr (sizeof(T) <= sizeof(qregisteruint)) { + return mul_overflow(v1, V2, r); + +#ifdef Q_INTRINSIC_MUL_OVERFLOW64 + } else if constexpr (sizeof(T) <= sizeof(quint64)) { + // If we have intrinsics detecting overflow of 64-bit multiplications, + // then detect overflows through them up to 64 bits. + return mul_overflow(v1, V2, r); +#endif + + } else if constexpr (V2 == 0 || V2 == 1) { + // trivial cases (and simplify logic below due to division by zero) + *r = v1 * V2; + return false; + } else if constexpr (V2 == -1) { + // multiplication by -1 is valid *except* for signed minimum values + // (necessary to avoid diving min() by -1, which is an overflow) + if (v1 < 0 && v1 == std::numeric_limits::min()) + return true; + *r = -v1; + return false; + } else { + // For 64-bit multiplications on 32-bit platforms, let's instead compare v1 + // against the bounds that would overflow. + constexpr T Highest = std::numeric_limits::max() / V2; + constexpr T Lowest = std::numeric_limits::min() / V2; + if constexpr (Highest > Lowest) { + if (v1 > Highest || v1 < Lowest) + return true; + } else { + // this can only happen if V2 < 0 + static_assert(V2 < 0); + if (v1 > Lowest || v1 < Highest) + return true; + } + + *r = v1 * V2; + return false; + } +} + +template bool mul_overflow(T v1, T *r) +{ + return mul_overflow(v1, std::integral_constant{}, r); +} } #endif // Q_CLANG_QDOC diff --git a/tests/auto/corelib/global/qnumeric/tst_qnumeric.cpp b/tests/auto/corelib/global/qnumeric/tst_qnumeric.cpp index da52e6e026..9dc2c02c3b 100644 --- a/tests/auto/corelib/global/qnumeric/tst_qnumeric.cpp +++ b/tests/auto/corelib/global/qnumeric/tst_qnumeric.cpp @@ -400,119 +400,138 @@ template static void addOverflow_template() #if defined(Q_CC_MSVC) && Q_CC_MSVC < 2000 QSKIP("Test disabled, this test generates an Internal Compiler Error compiling in release mode"); #else - const Int max = std::numeric_limits::max(); - const Int min = std::numeric_limits::min(); + constexpr Int max = std::numeric_limits::max(); + constexpr Int min = std::numeric_limits::min(); Int r; - // basic values - QCOMPARE(add_overflow(Int(0), Int(0), &r), false); - QCOMPARE(r, Int(0)); - QCOMPARE(add_overflow(Int(1), Int(0), &r), false); - QCOMPARE(r, Int(1)); - QCOMPARE(add_overflow(Int(0), Int(1), &r), false); - QCOMPARE(r, Int(1)); +#define ADD_COMPARE_NONOVF(v1, v2, expected) \ + do { \ + QCOMPARE(add_overflow(Int(v1), Int(v2), &r), false); \ + QCOMPARE(r, Int(expected)); \ + QCOMPARE(add_overflow(Int(v1), (std::integral_constant()), &r), false); \ + QCOMPARE(r, Int(expected)); \ + QCOMPARE(add_overflow(Int(v1), &r), false); \ + QCOMPARE(r, Int(expected)); \ + } while (false) +#define ADD_COMPARE_OVF(v1, v2) \ + do { \ + QCOMPARE(add_overflow(Int(v1), Int(v2), &r), true); \ + QCOMPARE(add_overflow(Int(v1), (std::integral_constant()), &r), true); \ + QCOMPARE(add_overflow(Int(v1), &r), true); \ + } while (false) +#define SUB_COMPARE_NONOVF(v1, v2, expected) \ + do { \ + QCOMPARE(sub_overflow(Int(v1), Int(v2), &r), false); \ + QCOMPARE(r, Int(expected)); \ + QCOMPARE(sub_overflow(Int(v1), (std::integral_constant()), &r), false); \ + QCOMPARE(r, Int(expected)); \ + QCOMPARE(sub_overflow(Int(v1), &r), false); \ + QCOMPARE(r, Int(expected)); \ + } while (false) +#define SUB_COMPARE_OVF(v1, v2) \ + do { \ + QCOMPARE(sub_overflow(Int(v1), Int(v2), &r), true); \ + QCOMPARE(sub_overflow(Int(v1), (std::integral_constant()), &r), true); \ + QCOMPARE(sub_overflow(Int(v1), &r), true); \ + } while (false) - QCOMPARE(sub_overflow(Int(0), Int(0), &r), false); - QCOMPARE(r, Int(0)); - QCOMPARE(sub_overflow(Int(1), Int(0), &r), false); - QCOMPARE(r, Int(1)); - QCOMPARE(sub_overflow(Int(1), Int(1), &r), false); - QCOMPARE(r, Int(0)); - QCOMPARE(sub_overflow(Int(0), Int(1), &r), !min); + // basic values + ADD_COMPARE_NONOVF(0, 0, 0); + ADD_COMPARE_NONOVF(1, 0, 1); + ADD_COMPARE_NONOVF(0, 1, 1); + + SUB_COMPARE_NONOVF(0, 0, 0); + SUB_COMPARE_NONOVF(1, 0, 1); + SUB_COMPARE_NONOVF(1, 1, 0); if (min) - QCOMPARE(r, Int(-1)); + SUB_COMPARE_NONOVF(0, 1, -1); + else + SUB_COMPARE_OVF(0, 1); // half-way through max - QCOMPARE(add_overflow(Int(max/2), Int(max/2), &r), false); - QCOMPARE(r, Int(max / 2 * 2)); - QCOMPARE(sub_overflow(Int(max/2), Int(max/2), &r), false); - QCOMPARE(r, Int(0)); - QCOMPARE(add_overflow(Int(max/2 - 1), Int(max/2 + 1), &r), false); - QCOMPARE(r, Int(max / 2 * 2)); - QCOMPARE(sub_overflow(Int(max/2 - 1), Int(max/2 + 1), &r), !min); + ADD_COMPARE_NONOVF(max/2, max/2, max / 2 * 2); + SUB_COMPARE_NONOVF(max/2, max/2, 0); + ADD_COMPARE_NONOVF(max/2 - 1, max/2 + 1, max / 2 * 2); if (min) - QCOMPARE(r, Int(-2)); - QCOMPARE(add_overflow(Int(max/2 + 1), Int(max/2), &r), false); - QCOMPARE(r, max); - QCOMPARE(sub_overflow(Int(max/2 + 1), Int(max/2), &r), false); - QCOMPARE(r, Int(1)); - QCOMPARE(add_overflow(Int(max/2), Int(max/2 + 1), &r), false); - QCOMPARE(r, max); - QCOMPARE(sub_overflow(Int(max/2), Int(max/2 + 1), &r), !min); - if (min) - QCOMPARE(r, Int(-1)); + SUB_COMPARE_NONOVF(max/2 - 1, max/2 + 1, -2); + else + SUB_COMPARE_OVF(max/2 - 1, max/2 + 1); - QCOMPARE(add_overflow(Int(min/2), Int(min/2), &r), false); - QCOMPARE(r, Int(min / 2 * 2)); - QCOMPARE(sub_overflow(Int(min/2), Int(min/2), &r), false); - QCOMPARE(r, Int(0)); - QCOMPARE(add_overflow(Int(min/2 - 1), Int(min/2 + 1), &r), !min); + ADD_COMPARE_NONOVF(max/2 + 1, max/2, max); + SUB_COMPARE_NONOVF(max/2 + 1, max/2, 1); + ADD_COMPARE_NONOVF(max/2, max/2 + 1, max); if (min) - QCOMPARE(r, Int(min / 2 * 2)); - QCOMPARE(sub_overflow(Int(min/2 - 1), Int(min/2 + 1), &r), false); - QCOMPARE(r, Int(-2)); - QCOMPARE(sub_overflow(Int(min/2 + 1), Int(min/2), &r), false); - QCOMPARE(r, Int(1)); - QCOMPARE(sub_overflow(Int(min/2), Int(min/2 + 1), &r), !min); + SUB_COMPARE_NONOVF(max/2, max/2 + 1, -1); + else + SUB_COMPARE_OVF(max/2, max/2 + 1); + + ADD_COMPARE_NONOVF(min/2, min/2, min / 2 * 2); + SUB_COMPARE_NONOVF(min/2, min/2, 0); if (min) - QCOMPARE(r, Int(-1)); + ADD_COMPARE_NONOVF(min/2 - 1, min/2 + 1, min / 2 * 2); + else + ADD_COMPARE_OVF(min/2 - 1, min/2 + 1); + SUB_COMPARE_NONOVF(min/2 - 1, min/2 + 1, -2); + SUB_COMPARE_NONOVF(min/2 + 1, min/2, 1); + if (min) + SUB_COMPARE_NONOVF(min/2, min/2 + 1, -1); + else + SUB_COMPARE_OVF(min/2, min/2 + 1); // more than half - QCOMPARE(add_overflow(Int(max/4 * 3), Int(max/4), &r), false); - QCOMPARE(r, Int(max / 4 * 4)); + ADD_COMPARE_NONOVF(max/4 * 3, max/4, max / 4 * 4); // max - QCOMPARE(add_overflow(max, Int(0), &r), false); - QCOMPARE(r, max); - QCOMPARE(sub_overflow(max, Int(0), &r), false); - QCOMPARE(r, max); - QCOMPARE(add_overflow(Int(0), max, &r), false); - QCOMPARE(r, max); - QCOMPARE(sub_overflow(Int(0), max, &r), !min); + ADD_COMPARE_NONOVF(max, 0, max); + SUB_COMPARE_NONOVF(max, 0, max); + ADD_COMPARE_NONOVF(0, max, max); if (min) - QCOMPARE(r, Int(-max)); + SUB_COMPARE_NONOVF(0, max, -max); + else + SUB_COMPARE_OVF(0, max); - QCOMPARE(add_overflow(min, Int(0), &r), false); - QCOMPARE(r, min); - QCOMPARE(sub_overflow(min, Int(0), &r), false); - QCOMPARE(r, min); - QCOMPARE(add_overflow(Int(0), min, &r), false); - QCOMPARE(r, min); - QCOMPARE(sub_overflow(Int(0), Int(min+1), &r), !min); + ADD_COMPARE_NONOVF(min, 0, min); + SUB_COMPARE_NONOVF(min, 0, min); + ADD_COMPARE_NONOVF(0, min, min); if (min) - QCOMPARE(r, Int(-(min+1))); + SUB_COMPARE_NONOVF(0, min+1, -(min+1)); + else + SUB_COMPARE_OVF(0, min+1); // 64-bit issues - if (max > std::numeric_limits::max()) { - QCOMPARE(add_overflow(Int(std::numeric_limits::max()), Int(std::numeric_limits::max()), &r), false); - QCOMPARE(r, Int(2 * Int(std::numeric_limits::max()))); - QCOMPARE(sub_overflow(Int(std::numeric_limits::max()), Int(std::numeric_limits::max()), &r), false); - QCOMPARE(r, Int(0)); + if constexpr (max > std::numeric_limits::max()) { + ADD_COMPARE_NONOVF(std::numeric_limits::max(), std::numeric_limits::max(), 2 * Int(std::numeric_limits::max())); + SUB_COMPARE_NONOVF(std::numeric_limits::max(), std::numeric_limits::max(), 0); } - if (min && min < -Int(std::numeric_limits::max())) { - QCOMPARE(add_overflow(Int(-Int(std::numeric_limits::max())), Int(-Int(std::numeric_limits::max())), &r), false); - QCOMPARE(r, Int(-2 * Int(std::numeric_limits::max()))); - QCOMPARE(sub_overflow(Int(-Int(std::numeric_limits::max())), Int(-Int(std::numeric_limits::max())), &r), false); - QCOMPARE(r, Int(0)); + if constexpr (min != 0) { + if (qint64(min) < qint64(-std::numeric_limits::max())) { + ADD_COMPARE_NONOVF(-Int(std::numeric_limits::max()), -Int(std::numeric_limits::max()), -2 * Int(std::numeric_limits::max())); + SUB_COMPARE_NONOVF(-Int(std::numeric_limits::max()), -Int(std::numeric_limits::max()), 0); + } } // overflows past max - QCOMPARE(add_overflow(max, Int(1), &r), true); - QCOMPARE(add_overflow(Int(1), max, &r), true); - QCOMPARE(add_overflow(Int(max/2 + 1), Int(max/2 + 1), &r), true); - if (!min) { - QCOMPARE(sub_overflow(Int(-max), Int(-2), &r), true); - QCOMPARE(sub_overflow(Int(max/2 - 1), Int(max/2 + 1), &r), true); + ADD_COMPARE_OVF(max, 1); + ADD_COMPARE_OVF(1, max); + ADD_COMPARE_OVF(max/2 + 1, max/2 + 1); + + // overflows past min + if constexpr (min != 0) { + ADD_COMPARE_OVF(-max, -2); + SUB_COMPARE_OVF(-max, 2); + SUB_COMPARE_OVF(-max/2 - 1, max/2 + 2); + + SUB_COMPARE_OVF(min, 1); + SUB_COMPARE_OVF(1, min); + SUB_COMPARE_OVF(min/2 - 1, -Int(min/2)); + ADD_COMPARE_OVF(min, -1); + ADD_COMPARE_OVF(-1, min); } - // overflows past min (in case of min == 0, repeats some tests above) - if (min) { - QCOMPARE(sub_overflow(min, Int(1), &r), true); - QCOMPARE(sub_overflow(Int(1), min, &r), true); - QCOMPARE(sub_overflow(Int(min/2 - 1), Int(-Int(min/2)), &r), true); - QCOMPARE(add_overflow(min, Int(-1), &r), true); - QCOMPARE(add_overflow(Int(-1), min, &r), true); - } +#undef ADD_COMPARE_NONOVF +#undef ADD_COMPARE_OVF +#undef SUB_COMPARE_NONOVF +#undef SUB_COMPARE_OVF #endif } @@ -552,77 +571,82 @@ template static void mulOverflow_template() #if defined(Q_CC_MSVC) && Q_CC_MSVC < 1900 QSKIP("Test disabled, this test generates an Internal Compiler Error compiling"); #else - const Int max = std::numeric_limits::max(); - const Int min = std::numeric_limits::min(); + constexpr Int max = std::numeric_limits::max(); + constexpr Int min = std::numeric_limits::min(); // for unsigned (even number of significant bits): mid2 = mid1 - 1 // for signed (odd number of significant bits): mid2 = mid1 / 2 - 1 - const Int mid1 = Int(Int(1) << sizeof(Int) * CHAR_BIT / 2); - const Int mid2 = (std::numeric_limits::digits % 2 ? mid1 / 2 : mid1) - 1; + constexpr Int mid1 = Int(Int(1) << (sizeof(Int) * CHAR_BIT / 2)); + constexpr Int mid2 = (std::numeric_limits::digits % 2 ? mid1 / 2 : mid1) - 1; Int r; - // basic multiplications - QCOMPARE(mul_overflow(Int(0), Int(0), &r), false); - QCOMPARE(r, Int(0)); - QCOMPARE(mul_overflow(Int(1), Int(0), &r), false); - QCOMPARE(r, Int(0)); - QCOMPARE(mul_overflow(Int(0), Int(1), &r), false); - QCOMPARE(r, Int(0)); - QCOMPARE(mul_overflow(max, Int(0), &r), false); - QCOMPARE(r, Int(0)); - QCOMPARE(mul_overflow(Int(0), max, &r), false); - QCOMPARE(r, Int(0)); - QCOMPARE(mul_overflow(min, Int(0), &r), false); - QCOMPARE(r, Int(0)); - QCOMPARE(mul_overflow(Int(0), min, &r), false); - QCOMPARE(r, Int(0)); +#define MUL_COMPARE_NONOVF(v1, v2, expected) \ + do { \ + QCOMPARE(mul_overflow(Int(v1), Int(v2), &r), false); \ + QCOMPARE(r, Int(expected)); \ + QCOMPARE(mul_overflow(Int(v1), (std::integral_constant()), &r), false); \ + QCOMPARE(r, Int(expected)); \ + QCOMPARE(mul_overflow(Int(v1), &r), false); \ + QCOMPARE(r, Int(expected)); \ + } while (false); +#define MUL_COMPARE_OVF(v1, v2) \ + do { \ + QCOMPARE(mul_overflow(Int(v1), Int(v2), &r), true); \ + QCOMPARE(mul_overflow(Int(v1), (std::integral_constant()), &r), true); \ + QCOMPARE(mul_overflow(Int(v1), &r), true); \ + } while (false); - QCOMPARE(mul_overflow(Int(1), Int(1), &r), false); - QCOMPARE(r, Int(1)); - QCOMPARE(mul_overflow(Int(1), max, &r), false); - QCOMPARE(r, max); - QCOMPARE(mul_overflow(max, Int(1), &r), false); - QCOMPARE(r, max); - QCOMPARE(mul_overflow(Int(1), min, &r), false); - QCOMPARE(r, min); - QCOMPARE(mul_overflow(min, Int(1), &r), false); - QCOMPARE(r, min); + // basic multiplications + MUL_COMPARE_NONOVF(0, 0, 0); + MUL_COMPARE_NONOVF(1, 0, 0); + MUL_COMPARE_NONOVF(0, 1, 0); + MUL_COMPARE_NONOVF(max, 0, 0); + MUL_COMPARE_NONOVF(0, max, 0); + MUL_COMPARE_NONOVF(min, 0, 0); + MUL_COMPARE_NONOVF(0, min, 0); + if constexpr (min != 0) { + MUL_COMPARE_NONOVF(0, -1, 0); + MUL_COMPARE_NONOVF(1, -1, -1); + MUL_COMPARE_NONOVF(max, -1, -max); + } + + MUL_COMPARE_NONOVF(1, 1, 1); + MUL_COMPARE_NONOVF(1, max, max); + MUL_COMPARE_NONOVF(max, 1, max); + MUL_COMPARE_NONOVF(1, min, min); + MUL_COMPARE_NONOVF(min, 1, min); // almost max - QCOMPARE(mul_overflow(mid1, mid2, &r), false); - QCOMPARE(r, Int(max - mid1 + 1)); - QCOMPARE(mul_overflow(Int(max / 2), Int(2), &r), false); - QCOMPARE(r, Int(max & ~Int(1))); - QCOMPARE(mul_overflow(Int(max / 4), Int(4), &r), false); - QCOMPARE(r, Int(max & ~Int(3))); - if (min) { - QCOMPARE(mul_overflow(Int(-mid1), mid2, &r), false); - QCOMPARE(r, Int(-max + mid1 - 1)); - QCOMPARE(mul_overflow(Int(-max / 2), Int(2), &r), false); - QCOMPARE(r, Int(-max + 1)); - QCOMPARE(mul_overflow(Int(-max / 4), Int(4), &r), false); - QCOMPARE(r, Int(-max + 3)); + MUL_COMPARE_NONOVF(mid1, mid2, max - mid1 + 1); + MUL_COMPARE_NONOVF(max / 2, 2, max & ~Int(1)); + MUL_COMPARE_NONOVF(max / 4, 4, max & ~Int(3)); + if constexpr (min != 0) { + MUL_COMPARE_NONOVF(-mid1, mid2, -max + mid1 - 1); + MUL_COMPARE_NONOVF(-max / 2, 2, -max + 1); + MUL_COMPARE_NONOVF(-max / 4, 4, -max + 3); - QCOMPARE(mul_overflow(Int(-mid1), Int(mid2 + 1), &r), false); - QCOMPARE(r, min); - QCOMPARE(mul_overflow(mid1, Int(-mid2 - 1), &r), false); - QCOMPARE(r, min); + MUL_COMPARE_NONOVF(-mid1, mid2 + 1, min); + MUL_COMPARE_NONOVF(mid1, -mid2 - 1, min); } // overflows - QCOMPARE(mul_overflow(max, Int(2), &r), true); - QCOMPARE(mul_overflow(Int(max / 2), Int(3), &r), true); - QCOMPARE(mul_overflow(mid1, Int(mid2 + 1), &r), true); - QCOMPARE(mul_overflow(Int(max / 2 + 2), Int(2), &r), true); - QCOMPARE(mul_overflow(Int(max - max / 2), Int(2), &r), true); - QCOMPARE(mul_overflow(Int(1ULL << (std::numeric_limits::digits - 1)), Int(2), &r), true); + MUL_COMPARE_OVF(max, 2); + MUL_COMPARE_OVF(max / 2, 3); + MUL_COMPARE_OVF(mid1, mid2 + 1); + MUL_COMPARE_OVF(max / 2 + 2, 2); + MUL_COMPARE_OVF(max - max / 2, 2); + MUL_COMPARE_OVF(1ULL << (std::numeric_limits::digits - 1), 2); - if (min) { - QCOMPARE(mul_overflow(min, Int(2), &r), true); - QCOMPARE(mul_overflow(Int(min / 2), Int(3), &r), true); - QCOMPARE(mul_overflow(Int(min / 2 - 1), Int(2), &r), true); + if constexpr (min != 0) { + MUL_COMPARE_OVF(min, -1); + MUL_COMPARE_OVF(min, 2); + MUL_COMPARE_OVF(min / 2, 3); + MUL_COMPARE_OVF(min / 2 - 1, 2); } + +#undef MUL_COMPARE_NONOVF +#undef MUL_COMPARE_OVF #endif } @@ -657,7 +681,7 @@ void tst_QNumeric::mulOverflow() if (size == -32) MulOverflowDispatch()(); if (size == -64) { -#if QT_POINTER_SIZE == 8 +#if QT_POINTER_SIZE == 8 || defined(Q_INTRINSIC_MUL_OVERFLOW64) MulOverflowDispatch()(); #else QFAIL("128-bit multiplication not supported on this platform");