diff --git a/src/corelib/io/qdebug.cpp b/src/corelib/io/qdebug.cpp index d8b77dd603d..9ed29c38af3 100644 --- a/src/corelib/io/qdebug.cpp +++ b/src/corelib/io/qdebug.cpp @@ -46,6 +46,7 @@ QT_BEGIN_NAMESPACE using QtMiscUtils::toHexUpper; +using QtMiscUtils::fromHex; // This file is needed to force compilation of QDebug into the kernel library. @@ -173,13 +174,24 @@ void QDebug::putUcs4(uint ucs4) } template -static inline void putEscapedString(QTextStreamPrivate *d, const Char *begin, int length) +static inline void putEscapedString(QTextStreamPrivate *d, const Char *begin, int length, bool isUnicode = true) { QChar quote(QLatin1Char('"')); d->write("e, 1); + bool lastWasHexEscape = false; const Char *end = begin + length; for (const Char *p = begin; p != end; ++p) { + // check if we need to insert "" to break an hex escape sequence + if (Q_UNLIKELY(lastWasHexEscape)) { + if (fromHex(*p) != -1) { + // yes, insert it + QChar quotes[] = { QLatin1Char('"'), QLatin1Char('"') }; + d->write(quotes, 2); + } + lastWasHexEscape = false; + } + if (sizeof(Char) == sizeof(QChar)) { int runLength = 0; while (p + runLength != end && @@ -190,6 +202,10 @@ static inline void putEscapedString(QTextStreamPrivate *d, const Char *begin, in p += runLength - 1; continue; } + } else if (*p < 0x7f && *p >= 0x20 && *p != '\\' && *p != '"') { + QChar c = QLatin1Char(*p); + d->write(&c, 1); + continue; } // print as an escape sequence @@ -218,6 +234,15 @@ static inline void putEscapedString(QTextStreamPrivate *d, const Char *begin, in buf[1] = 't'; break; default: + if (!isUnicode) { + // print as hex escape + buf[1] = 'x'; + buf[2] = toHexUpper(uchar(*p) >> 4); + buf[3] = toHexUpper(uchar(*p)); + buflen = 4; + lastWasHexEscape = true; + break; + } if (QChar::isHighSurrogate(*p)) { if ((p + 1) != end && QChar::isLowSurrogate(p[1])) { // properly-paired surrogates @@ -272,6 +297,26 @@ void QDebug::putString(const QChar *begin, size_t length) } } +/*! + \internal + Duplicated from QtTest::toPrettyCString(). +*/ +void QDebug::putByteArray(const char *begin, size_t length, Latin1Content content) +{ + if (stream->testFlag(Stream::NoQuotes)) { + // no quotes, write the string directly too (no pretty-printing) + // this respects the QTextStream state, though + QString string = content == ContainsLatin1 ? QString::fromLatin1(begin, length) : QString::fromUtf8(begin, length); + stream->ts.d_ptr->putString(string); + } 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(begin), + length, content == ContainsLatin1); + } +} + /*! \fn QDebug::swap(QDebug &other) \since 5.0 diff --git a/src/corelib/io/qdebug.h b/src/corelib/io/qdebug.h index a2d6598bce5..2f487b8a1f3 100644 --- a/src/corelib/io/qdebug.h +++ b/src/corelib/io/qdebug.h @@ -77,8 +77,11 @@ class Q_CORE_EXPORT QDebug int flags; } *stream; + enum Latin1Content { ContainsBinary = 0, ContainsLatin1 }; + void putUcs4(uint ucs4); void putString(const QChar *begin, size_t length); + void putByteArray(const char *begin, size_t length, Latin1Content content); public: inline QDebug(QIODevice *device) : stream(new Stream(device)) {} inline QDebug(QString *string) : stream(new Stream(string)) {} @@ -121,8 +124,8 @@ public: inline QDebug &operator<<(const char* t) { stream->ts << QString::fromUtf8(t); return maybeSpace(); } 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<<(QLatin1String t) { putByteArray(t.latin1(), t.size(), ContainsLatin1); return maybeSpace(); } + inline QDebug &operator<<(const QByteArray & t) { putByteArray(t.constData(), t.size(), ContainsBinary); return maybeSpace(); } inline QDebug &operator<<(const void * t) { stream->ts << t; return maybeSpace(); } #ifdef Q_COMPILER_NULLPTR inline QDebug &operator<<(std::nullptr_t) { stream->ts << "(nullptr)"; return maybeSpace(); } diff --git a/tests/auto/corelib/io/qdebug/tst_qdebug.cpp b/tests/auto/corelib/io/qdebug/tst_qdebug.cpp index 63de9534378..4718b83df05 100644 --- a/tests/auto/corelib/io/qdebug/tst_qdebug.cpp +++ b/tests/auto/corelib/io/qdebug/tst_qdebug.cpp @@ -490,6 +490,28 @@ void tst_QDebug::qDebugQByteArray() const QCOMPARE(QString::fromLatin1(s_file), file); QCOMPARE(s_line, line); QCOMPARE(QString::fromLatin1(s_function), function); + + /* simpler tests from now on */ + QByteArray ba = "\"Hello\""; + qDebug() << ba; + QCOMPARE(s_msg, QString("\"\\\"Hello\\\"\"")); + + qDebug().noquote().nospace() << ba; + QCOMPARE(s_msg, QString::fromLatin1(ba)); + + qDebug().noquote().nospace() << qSetFieldWidth(8) << ba; + QCOMPARE(s_msg, " " + QString::fromLatin1(ba)); + + ba = "\nSm\xC3\xB8rg\xC3\xA5sbord\\"; + qDebug().noquote().nospace() << ba; + QCOMPARE(s_msg, QString::fromUtf8(ba)); + + qDebug() << ba; + QCOMPARE(s_msg, QString("\"\\nSm\\xC3\\xB8rg\\xC3\\xA5sbord\\\\\"")); + + // ensure that it closes hex escape sequences correctly + qDebug() << QByteArray("\377FFFF"); + QCOMPARE(s_msg, QString("\"\\xFF\"\"FFFF\"")); } enum TestEnum {