summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMarc Mutz <marc.mutz@qt.io>2023-09-14 18:22:12 +0200
committerMarc Mutz <marc.mutz@qt.io>2023-12-09 21:00:13 +0100
commitab910e09c713161f568a867a09af5a52eb79c35f (patch)
tree0c03a2cab401ad7d806389edc51d19769461df86
parentf9f1272e7c438b79591032877f2b386af8085c3f (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.cpp99
-rw-r--r--src/corelib/io/qdebug.h18
-rw-r--r--tests/auto/corelib/global/qglobal/tst_qglobal.cpp40
-rw-r--r--tests/auto/testlib/tostring/tst_tostring.cpp56
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();