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>
This commit is contained in:
parent
f9f1272e7c
commit
ab910e09c7
@ -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
|
||||
@ -902,6 +984,23 @@ QDebug &QDebug::resetFormat()
|
||||
The unit is not localized.
|
||||
*/
|
||||
|
||||
/*!
|
||||
\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
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -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
|
||||
|
@ -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();
|
||||
|
Loading…
x
Reference in New Issue
Block a user