QDebug: pretty-print QStrings and QStringRefs

[ChangeLog][QtCore][QDebug] Printing of QStrings and QStringRefs
whenever "noquote" is not active now prints the strings in a format that
can be copied back to C++ code. All characters that aren't printable in
US-ASCII are escaped (this includes printable Unicode characters outside
of US-ASCII). Pretty-printing will not respect QTextFormat padding or
field widths.

Change-Id: I169a8a0508e24693f5652f0129defe7f709e5d08
Reviewed-by: Oswald Buddenhagen <oswald.buddenhagen@theqtcompany.com>
Reviewed-by: Thiago Macieira <thiago.macieira@intel.com>
This commit is contained in:
Thiago Macieira 2014-11-18 14:32:38 -08:00
parent e00d8ad86b
commit 2c01d402e1
9 changed files with 182 additions and 8 deletions

View File

@ -1,6 +1,7 @@
/****************************************************************************
**
** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies).
** Copyright (C) 2014 Intel Corporation.
** Contact: http://www.qt-project.org/legal
**
** This file is part of the QtCore module of the Qt Toolkit.
@ -40,9 +41,12 @@
#include "qdebug.h"
#include <private/qtextstream_p.h>
#include <private/qtools_p.h>
QT_BEGIN_NAMESPACE
using QtMiscUtils::toHexUpper;
// This file is needed to force compilation of QDebug into the kernel library.
/*!
@ -168,6 +172,105 @@ void QDebug::putUcs4(uint ucs4)
maybeQuote('\'');
}
template <typename Char>
static inline void putEscapedString(QTextStreamPrivate *d, const Char *begin, int length)
{
QChar quote(QLatin1Char('"'));
d->write(&quote, 1);
const Char *end = begin + length;
for (const Char *p = begin; p != end; ++p) {
if (sizeof(Char) == sizeof(QChar)) {
int runLength = 0;
while (p + runLength != end &&
p[runLength] < 0x7f && p[runLength] >= 0x20 && p[runLength] != '\\' && p[runLength] != '"')
++runLength;
if (runLength) {
d->write(reinterpret_cast<const QChar *>(p), runLength);
p += runLength - 1;
continue;
}
}
// print as an escape sequence
int buflen = 2;
ushort buf[sizeof "\\U12345678" - 1];
buf[0] = '\\';
switch (*p) {
case '"':
case '\\':
buf[1] = *p;
break;
case '\b':
buf[1] = 'b';
break;
case '\f':
buf[1] = 'f';
break;
case '\n':
buf[1] = 'n';
break;
case '\r':
buf[1] = 'r';
break;
case '\t':
buf[1] = 't';
break;
default:
if (QChar::isHighSurrogate(*p)) {
if ((p + 1) != end && QChar::isLowSurrogate(p[1])) {
// properly-paired surrogates
uint ucs4 = QChar::surrogateToUcs4(*p, p[1]);
++p;
buf[1] = 'U';
buf[2] = '0'; // toHexUpper(ucs4 >> 32);
buf[3] = '0'; // toHexUpper(ucs4 >> 28);
buf[4] = toHexUpper(ucs4 >> 20);
buf[5] = toHexUpper(ucs4 >> 16);
buf[6] = toHexUpper(ucs4 >> 12);
buf[7] = toHexUpper(ucs4 >> 8);
buf[8] = toHexUpper(ucs4 >> 4);
buf[9] = toHexUpper(ucs4);
buflen = 10;
break;
}
// improperly-paired surrogates, fall through
}
buf[1] = 'u';
if (sizeof(Char) == 1) {
buf[2] = buf[3] = '0';
} else {
buf[2] = toHexUpper(*p >> 12);
buf[3] = toHexUpper(*p >> 8);
}
buf[4] = toHexUpper(*p >> 4);
buf[5] = toHexUpper(*p);
buflen = 6;
}
d->write(reinterpret_cast<QChar *>(buf), buflen);
}
d->write(&quote, 1);
}
/*!
\internal
Duplicated from QtTest::toPrettyUnicode().
*/
void QDebug::putString(const QChar *begin, size_t length)
{
if (stream->testFlag(Stream::NoQuotes)) {
// no quotes, write the string directly too (no pretty-printing)
// this respects the QTextStream state, though
stream->ts.d_ptr->putString(begin, length);
} else {
// we'll reset the QTextStream formatting mechanisms, so save the state
QDebugStateSaver saver(*this);
stream->ts.d_ptr->params.reset();
putEscapedString(stream->ts.d_ptr.data(), reinterpret_cast<const ushort *>(begin), length);
}
}
/*!
\fn QDebug::swap(QDebug &other)

View File

@ -78,6 +78,7 @@ class Q_CORE_EXPORT QDebug
} *stream;
void putUcs4(uint ucs4);
void putString(const QChar *begin, size_t length);
public:
inline QDebug(QIODevice *device) : stream(new Stream(device)) {}
inline QDebug(QString *string) : stream(new Stream(string)) {}
@ -118,8 +119,8 @@ public:
inline QDebug &operator<<(float t) { stream->ts << t; return maybeSpace(); }
inline QDebug &operator<<(double t) { stream->ts << t; return maybeSpace(); }
inline QDebug &operator<<(const char* t) { stream->ts << QString::fromUtf8(t); return maybeSpace(); }
inline QDebug &operator<<(const QString & t) { maybeQuote(); stream->ts << t; maybeQuote(); return maybeSpace(); }
inline QDebug &operator<<(const QStringRef & t) { return operator<<(t.toString()); }
inline QDebug &operator<<(const QString & t) { putString(t.constData(), uint(t.length())); return maybeSpace(); }
inline QDebug &operator<<(const QStringRef & t) { putString(t.constData(), uint(t.length())); return maybeSpace(); }
inline QDebug &operator<<(QLatin1String t) { maybeQuote(); stream->ts << t; maybeQuote(); return maybeSpace(); }
inline QDebug &operator<<(const QByteArray & t) { maybeQuote(); stream->ts << t; maybeQuote(); return maybeSpace(); }
inline QDebug &operator<<(const void * t) { stream->ts << t; return maybeSpace(); }

View File

@ -806,7 +806,7 @@ inline void QTextStreamPrivate::restoreToSavedConverterState()
/*!
\internal
*/
inline void QTextStreamPrivate::write(const QChar *data, int len)
void QTextStreamPrivate::write(const QChar *data, int len)
{
if (string) {
// ### What about seek()??
@ -885,7 +885,7 @@ inline void QTextStreamPrivate::putChar(QChar ch)
/*!
\internal
*/
inline void QTextStreamPrivate::putString(const QChar *data, int len, bool number)
void QTextStreamPrivate::putString(const QChar *data, int len, bool number)
{
QString pad;
int padLeft = 0, padRight = 0;

View File

@ -185,6 +185,7 @@ public:
private:
Q_DISABLE_COPY(QTextStream)
friend class QDebugStateSaverPrivate;
friend class QDebug;
QScopedPointer<QTextStreamPrivate> d_ptr;
};

View File

@ -2146,6 +2146,8 @@ char *toHexRepresentation(const char *ba, int length)
\internal
Returns the same QString but with only the ASCII characters still shown;
everything else is replaced with \c {\uXXXX}.
Similar to QDebug::putString().
*/
char *toPrettyUnicode(const ushort *p, int length)
{

View File

@ -52,6 +52,7 @@ private slots:
void stateSaver() const;
void veryLongWarningMessage() const;
void qDebugQChar() const;
void qDebugQString() const;
void qDebugQStringRef() const;
void qDebugQLatin1String() const;
void qDebugQByteArray() const;
@ -344,6 +345,54 @@ void tst_QDebug::qDebugQChar() const
}
void tst_QDebug::qDebugQString() const
{
/* Use a basic string. */
{
QString file, function;
int line = 0;
const QString in(QLatin1String("input"));
const QStringRef inRef(&in);
MessageHandlerSetter mhs(myMessageHandler);
{ qDebug() << inRef; }
#ifndef QT_NO_MESSAGELOGCONTEXT
file = __FILE__; line = __LINE__ - 2; function = Q_FUNC_INFO;
#endif
QCOMPARE(s_msgType, QtDebugMsg);
QCOMPARE(s_msg, QString::fromLatin1("\"input\""));
QCOMPARE(QString::fromLatin1(s_file), file);
QCOMPARE(s_line, line);
QCOMPARE(QString::fromLatin1(s_function), function);
}
/* simpler tests from now on */
MessageHandlerSetter mhs(myMessageHandler);
QString string = "Hello";
qDebug() << string;
QCOMPARE(s_msg, QString("\"Hello\""));
qDebug().noquote().nospace() << string;
QCOMPARE(s_msg, string);
qDebug().noquote().nospace() << qSetFieldWidth(8) << string;
QCOMPARE(s_msg, " " + string);
string = QLatin1String("\nSm\xF8rg\xE5sbord\\");
qDebug().noquote().nospace() << string;
QCOMPARE(s_msg, string);
qDebug() << string;
QCOMPARE(s_msg, QString("\"\\nSm\\u00F8rg\\u00E5sbord\\\\\""));
// surrogate pairs (including broken pairings)
ushort utf16[] = { 0xDC00, 0xD800, 0xDC00, 'x', 0xD800, 0xDC00, 0xD800, 0 };
string = QString::fromUtf16(utf16);
qDebug() << string;
QCOMPARE(s_msg, QString("\"\\uDC00\\U00010000x\\U00010000\\uD800\""));
}
void tst_QDebug::qDebugQStringRef() const
{
/* Use a basic string. */
@ -403,6 +452,24 @@ void tst_QDebug::qDebugQLatin1String() const
QCOMPARE(QString::fromLatin1(s_file), file);
QCOMPARE(s_line, line);
QCOMPARE(QString::fromLatin1(s_function), function);
/* simpler tests from now on */
QLatin1String string("\"Hello\"");
qDebug() << string;
QCOMPARE(s_msg, QString("\"\\\"Hello\\\"\""));
qDebug().noquote().nospace() << string;
QCOMPARE(s_msg, QString(string));
qDebug().noquote().nospace() << qSetFieldWidth(8) << string;
QCOMPARE(s_msg, " " + QString(string));
string = QLatin1String("\nSm\xF8rg\xE5sbord\\");
qDebug().noquote().nospace() << string;
QCOMPARE(s_msg, QString(string));
qDebug() << string;
QCOMPARE(s_msg, QString("\"\\nSm\\u00F8rg\\u00E5sbord\\\\\""));
}
void tst_QDebug::qDebugQByteArray() const

View File

@ -113,7 +113,7 @@
</TestFunction>
<TestFunction name="encoding">
<Message type="qdebug" file="" line="0">
<Description><![CDATA["Ülrich Ümläut"]]></Description>
<Description><![CDATA["\u00DClrich \u00DCml\u00E4ut"]]></Description>
</Message>
<Incident type="pass" file="" line="0" />
<Duration msecs="0"/>

View File

@ -115,7 +115,7 @@
</TestFunction>
<TestFunction name="encoding">
<Message type="qdebug" file="" line="0">
<Description><![CDATA["Ülrich Ümläut"]]></Description>
<Description><![CDATA["\u00DClrich \u00DCml\u00E4ut"]]></Description>
</Message>
<Incident type="pass" file="" line="0" />
<Duration msecs="0"/>

View File

@ -30,7 +30,7 @@
<failure message="failure message" result="fail"/>
</testcase>
<testcase result="pass" name="encoding">
<!-- message="&quot;Ülrich Ümläut&quot;" type="qdebug" -->
<!-- message="&quot;\u00DClrich \u00DCml\u00E4ut&quot;" type="qdebug" -->
</testcase>
<testcase result="pass" name="cleanupTestCase"/>
<system-err>
@ -46,6 +46,6 @@
<![CDATA[quotes " text" more text]]>
<![CDATA[xml close > open < tags < text]]>
<![CDATA[all > " mixed ]]]><![CDATA[]> up > " in < the ]]]><![CDATA[]> hopes < of triggering "< ]]]><![CDATA[]> bugs]]>
<![CDATA["Ülrich Ümläut"]]>
<![CDATA["\u00DClrich \u00DCml\u00E4ut"]]>
</system-err>
</testsuite>