diff options
author | Marc Mutz <marc.mutz@qt.io> | 2023-09-14 18:22:12 +0200 |
---|---|---|
committer | Marc Mutz <marc.mutz@qt.io> | 2023-12-09 21:00:13 +0100 |
commit | ab910e09c713161f568a867a09af5a52eb79c35f (patch) | |
tree | 0c03a2cab401ad7d806389edc51d19769461df86 | |
parent | f9f1272e7c438b79591032877f2b386af8085c3f (diff) |
Long live QDebug::operator<<(q(u)int128)!
Replace the ad-hoc implementation of QTest::toString() in
tst_qglobal.cpp with a QDebug stream operator, so the
QTest::toString() fall-back to QDebug::toString() kicks in.
Since the ABI issues revolving around the new int128 types are not
known, yet, avoid baking the types into the ABI by a) making the
operators constrained templates¹ and b) passing though void* to the
exported helpers. These functions return an error message if Qt was
compiled without support for int128.
Use the Thiago Trick™ (leaving obviouly dead code around for the
compiler to remove without warning) to expose more code to more
compilers. This appears to work elsewhere in Qt, so I hope it does
here, too.
This completes the minimum qint128 support so we're able to debug code
and write tests that use these types.
¹ Templates, unlike inline member functions of wholly-exported
classes, never² become part of the ABI.
² <insert here the convoluted scenario under which this is false>
Fixes: QTBUG-117011
Change-Id: Ia4e56d26c6ffd18b7d69a7ceaed65b2211d258b2
Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org>
Reviewed-by: Volker Hilsheimer <volker.hilsheimer@qt.io>
-rw-r--r-- | src/corelib/io/qdebug.cpp | 99 | ||||
-rw-r--r-- | src/corelib/io/qdebug.h | 18 | ||||
-rw-r--r-- | tests/auto/corelib/global/qglobal/tst_qglobal.cpp | 40 | ||||
-rw-r--r-- | tests/auto/testlib/tostring/tst_tostring.cpp | 56 |
4 files changed, 173 insertions, 40 deletions
diff --git a/src/corelib/io/qdebug.cpp b/src/corelib/io/qdebug.cpp index 0eaeb5ccda..ca5da10022 100644 --- a/src/corelib/io/qdebug.cpp +++ b/src/corelib/io/qdebug.cpp @@ -15,6 +15,7 @@ #include <private/qtextstream_p.h> #include <private/qtools_p.h> +#include <array> #include <q20chrono.h> QT_BEGIN_NAMESPACE @@ -436,6 +437,87 @@ void QDebug::putTimeUnit(qint64 num, qint64 den) stream->ts << timeUnit(num, den); // ### optimize } +namespace { + +#ifdef QT_SUPPORTS_INT128 + +constexpr char Q_INT128_MIN_STR[] = "-170141183460469231731687303715884105728"; + +constexpr int Int128BufferSize = sizeof(Q_INT128_MIN_STR); +using Int128Buffer = std::array<char, Int128BufferSize>; + // numeric_limits<qint128>::digits10 may not exist + +static char *i128ToStringHelper(Int128Buffer &buffer, quint128 n) +{ + auto dst = buffer.data() + buffer.size(); + *--dst = '\0'; // NUL-terminate + if (n == 0) { + *--dst = '0'; // and done + } else { + while (n != 0) { + *--dst = "0123456789"[n % 10]; + n /= 10; + } + } + return dst; +} +#endif // QT_SUPPORTS_INT128 + +[[maybe_unused]] +static const char *int128Warning() +{ + const char *msg = "Qt was not compiled with int128 support."; + qWarning("%s", msg); + return msg; +} + +} // unnamed namespace + +/*! + \since 6.7 + \internal + Helper to the qint128 debug streaming output. + */ +void QDebug::putInt128([[maybe_unused]] const void *p) +{ +#ifdef QT_SUPPORTS_INT128 + Q_ASSERT(p); + qint128 i; + memcpy(&i, p, sizeof(i)); // alignment paranoia + if (i == Q_INT128_MIN) { + // -i is not representable, hardcode the result: + stream->ts << Q_INT128_MIN_STR; + } else { + Int128Buffer buffer; + auto dst = i128ToStringHelper(buffer, i < 0 ? -i : i); + if (i < 0) + *--dst = '-'; + stream->ts << dst; + } + return; +#endif // QT_SUPPORTS_INT128 + stream->ts << int128Warning(); +} + +/*! + \since 6.7 + \internal + Helper to the quint128 debug streaming output. + */ +void QDebug::putUInt128([[maybe_unused]] const void *p) +{ +#ifdef QT_SUPPORTS_INT128 + Q_ASSERT(p); + quint128 i; + memcpy(&i, p, sizeof(i)); // alignment paranoia + Int128Buffer buffer; + stream->ts << i128ToStringHelper(buffer, i); + return; +#endif // QT_SUPPORTS_INT128 + stream->ts << int128Warning(); +} + + /*! \fn QDebug::swap(QDebug &other) \since 5.0 @@ -903,6 +985,23 @@ QDebug &QDebug::resetFormat() */ /*! + \fn template <typename T, if_qint128<T>> QDebug::operator<<(T i) + \fn template <typename T, if_quint128<T>> QDebug::operator<<(T i) + \since 6.7 + + Prints the textual representation of the 128-bit integer \a i. + + \note This operator is only available if Qt supports 128-bit integer types. + If 128-bit integer types are available in your build, but the Qt libraries + were compiled without, the operator will print a warning instead. + + \note Because the operator is a function template, no implicit conversions + are performed on its argument. It must be exactly qint128/quint128. + + \sa QT_SUPPORTS_INT128 +*/ + +/*! \fn template <class T> QString QDebug::toString(T &&object) \since 6.0 diff --git a/src/corelib/io/qdebug.h b/src/corelib/io/qdebug.h index 9566314233..c7ab4c2553 100644 --- a/src/corelib/io/qdebug.h +++ b/src/corelib/io/qdebug.h @@ -11,6 +11,7 @@ #include <QtCore/qcontainerfwd.h> #include <QtCore/qtextstream.h> +#include <QtCore/qtypes.h> #include <QtCore/qstring.h> #include <QtCore/qcontiguouscache.h> #include <QtCore/qsharedpointer.h> @@ -69,6 +70,8 @@ class QT6_ONLY(Q_CORE_EXPORT) QDebug : public QIODeviceBase QT7_ONLY(Q_CORE_EXPORT) void putString(const QChar *begin, size_t length); QT7_ONLY(Q_CORE_EXPORT) void putByteArray(const char *begin, size_t length, Latin1Content content); QT7_ONLY(Q_CORE_EXPORT) void putTimeUnit(qint64 num, qint64 den); + QT7_ONLY(Q_CORE_EXPORT) void putInt128(const void *i); + QT7_ONLY(Q_CORE_EXPORT) void putUInt128(const void *i); public: explicit QDebug(QIODevice *device) : stream(new Stream(device)) {} explicit QDebug(QString *string) : stream(new Stream(string)) {} @@ -203,6 +206,21 @@ public: return maybeSpace(); } +#ifdef QT_SUPPORTS_INT128 +private: + // Constrained templates so they only match q(u)int128 without conversions. + // Also keeps these operators out of the ABI. + template <typename T> + using if_qint128 = std::enable_if_t<std::is_same_v<T, qint128>, bool>; + template <typename T> + using if_quint128 = std::enable_if_t<std::is_same_v<T, quint128>, bool>; +public: + template <typename T, if_qint128<T> = true> + QDebug &operator<<(T i128) { putInt128(&i128); return maybeSpace(); } + template <typename T, if_quint128<T> = true> + QDebug &operator<<(T u128) { putUInt128(&u128); return maybeSpace(); } +#endif // QT_SUPPORTS_INT128 + template <typename T> static QString toString(T &&object) { diff --git a/tests/auto/corelib/global/qglobal/tst_qglobal.cpp b/tests/auto/corelib/global/qglobal/tst_qglobal.cpp index b7c45b6480..644335d667 100644 --- a/tests/auto/corelib/global/qglobal/tst_qglobal.cpp +++ b/tests/auto/corelib/global/qglobal/tst_qglobal.cpp @@ -15,46 +15,6 @@ #include <array> #include <cmath> -QT_BEGIN_NAMESPACE -namespace QTest { -#ifdef QT_SUPPORTS_INT128 -namespace detail { - char *i128ToStringHelper(std::array<char, 64> &buffer, quint128 n) - { - auto dst = buffer.data() + buffer.size(); - *--dst = '\0'; // NUL-terminate - if (n == 0) { - *--dst = '0'; // and done - } else { - while (n != 0) { - *--dst = "0123456789"[n % 10]; - n /= 10; - } - } - return dst; - } -} -template <> -char *toString(const qint128 &i) -{ - if (i == Q_INT128_MIN) // -i is not representable, hardcode: - return qstrdup("-170141183460469231731687303715884105728"); - std::array<char, 64> buffer; - auto dst = detail::i128ToStringHelper(buffer, i < 0 ? -i : i); - if (i < 0) - *--dst = '-'; - return qstrdup(dst); -} -template <> -char *toString(const quint128 &i) -{ - std::array<char, 64> buffer; - return qstrdup(detail::i128ToStringHelper(buffer, i)); -} -#endif // QT_SUPPORTS_INT128 -} // namespace QTest -QT_END_NAMESPACE - class tst_QGlobal: public QObject { Q_OBJECT diff --git a/tests/auto/testlib/tostring/tst_tostring.cpp b/tests/auto/testlib/tostring/tst_tostring.cpp index 65e50fa511..29582c446a 100644 --- a/tests/auto/testlib/tostring/tst_tostring.cpp +++ b/tests/auto/testlib/tostring/tst_tostring.cpp @@ -2,6 +2,9 @@ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #include <QTest> + +#include <QtCore/qtypes.h> + #include <memory> #include <q20chrono.h> @@ -14,6 +17,8 @@ private: void addColumns(); void testRows(); private slots: + void int128(); + void chrono_duration_data(); void chrono_duration() { testRows(); } }; @@ -57,6 +62,57 @@ template <typename T> void addRow(QByteArrayView name, T &&value, QByteArrayView #define ADD_ROW(name, expr, expected) \ ::addRow(name, expr, #expr, expected, __FILE__, __LINE__) +void tst_toString::int128() +{ +#ifndef QT_SUPPORTS_INT128 + QSKIP("This test requires int128 support enabled in the compiler."); +#else + // ### port to data-driven once QVariant has support for qint128/quint128 + std::unique_ptr<char[]> s; + + { + // build Q_INT128_MIN without using Q_INT128_ macros, + // because we use Q_INT128_MIN in the impl + qint128 accu = 1701411834604692317LL; + accu *= 1000000000000000000LL; + accu += 316873037158841057LL; + accu *= -100; + accu -= 28; + QCOMPARE_EQ(accu, Q_INT128_MIN); + s.reset(QTest::toString(accu)); + QCOMPARE(s.get(), "-170141183460469231731687303715884105728"); + } + + // now test with the macro, too: + s.reset(QTest::toString(Q_INT128_MIN)); + QCOMPARE(s.get(), "-170141183460469231731687303715884105728"); + + s.reset(QTest::toString(Q_INT128_MIN + 1)); + QCOMPARE(s.get(), "-170141183460469231731687303715884105727"); + + s.reset(QTest::toString(Q_INT128_MAX)); + QCOMPARE(s.get(), "170141183460469231731687303715884105727"); + + s.reset(QTest::toString(Q_INT128_MAX - 1)); + QCOMPARE(s.get(), "170141183460469231731687303715884105726"); + + s.reset(QTest::toString(Q_UINT128_MAX)); + QCOMPARE(s.get(), "340282366920938463463374607431768211455"); + + s.reset(QTest::toString(Q_UINT128_MAX - 1)); + QCOMPARE(s.get(), "340282366920938463463374607431768211454"); + + s.reset(QTest::toString(quint128{0})); + QCOMPARE(s.get(), "0"); + + s.reset(QTest::toString(qint128{0})); + QCOMPARE(s.get(), "0"); + + s.reset(QTest::toString(qint128{-1})); + QCOMPARE(s.get(), "-1"); +#endif // QT_SUPPORTS_INT128 +} + void tst_toString::chrono_duration_data() { addColumns(); |