diff --git a/src/plugins/debugger/debuggerprotocol.cpp b/src/plugins/debugger/debuggerprotocol.cpp index ccd834636c6..b62931705f7 100644 --- a/src/plugins/debugger/debuggerprotocol.cpp +++ b/src/plugins/debugger/debuggerprotocol.cpp @@ -89,104 +89,111 @@ void GdbMi::parseResultOrValue(const QChar *&from, const QChar *to) } } +// Reads one \ooo entity. +static bool parseOctalEscapedHelper(const QChar *&from, const QChar *to, QByteArray &buffer) +{ + if (to - from < 4) + return false; + if (*from != '\\') + return false; + + const char c1 = from[1].unicode(); + const char c2 = from[2].unicode(); + const char c3 = from[3].unicode(); + if (!isdigit(c1) || !isdigit(c2) || !isdigit(c3)) + return false; + + buffer += char((c1 - '0') * 64 + (c2 - '0') * 8 + (c3 - '0')); + from += 4; + return true; +} + +static bool parseHexEscapedHelper(const QChar *&from, const QChar *to, QByteArray &buffer) +{ + if (to - from < 4) + return false; + if (from[0]!= '\\') + return false; + if (from[1] != 'x') + return false; + + const char c1 = from[2].unicode(); + const char c2 = from[3].unicode(); + if (!isxdigit(c1) || !isxdigit(c2)) + return false; + + buffer += char(16 * fromhex(c1) + fromhex(c2)); + from += 4; + return true; +} + +static void parseSimpleEscape(const QChar *&from, const QChar *to, QString &result) +{ + if (from == to) { + qDebug() << "MI Parse Error, unterminated backslash escape"; + return; + } + + QChar c = *from++; + switch (c.unicode()) { + case 'a': result += '\a'; break; + case 'b': result += '\b'; break; + case 'f': result += '\f'; break; + case 'n': result += '\n'; break; + case 'r': result += '\r'; break; + case 't': result += '\t'; break; + case 'v': result += '\v'; break; + case '"': result += '"'; break; + case '\'': result += '\''; break; + case '\\': result += '\\'; break; + default: + qDebug() << "MI Parse Error, unrecognized backslash escape"; + } +} + +// Reads subsequent \123 or \x12 entities and converts to Utf8, +// *or* one escaped char, *or* one unescaped char. +static void parseCharOrEscape(const QChar *&from, const QChar *to, QString &result) +{ + QByteArray buffer; + while (parseOctalEscapedHelper(from, to, buffer)) + ; + while (parseHexEscapedHelper(from, to, buffer)) + ; + + if (!buffer.isEmpty()) + result.append(QString::fromUtf8(buffer)); + else if (*from == '\\') + parseSimpleEscape(++from, to, result); + else + result += *from++; +} + QString GdbMi::parseCString(const QChar *&from, const QChar *to) { - QString result; + if (to == from) + return QString(); + //qDebug() << "parseCString: " << QString(from, to - from); if (*from != '"') { qDebug() << "MI Parse Error, double quote expected"; ++from; // So we don't hang return QString(); } - const QChar *ptr = from; - ++ptr; - while (ptr < to) { - if (*ptr == '"') { - ++ptr; - result = QString(from + 1, ptr - from - 2); - break; - } - if (*ptr == '\\') { - ++ptr; - if (ptr == to) { - qDebug() << "MI Parse Error, unterminated backslash escape"; - from = ptr; // So we don't hang - return QString(); - } - } - ++ptr; - } - from = ptr; - int idx = result.indexOf('\\'); - if (idx >= 0) { - QChar *dst = result.data() + idx; - const QChar *src = dst + 1, *end = result.data() + result.length(); - do { - QChar c = *src++; - switch (c.unicode()) { - case 'a': *dst++ = '\a'; break; - case 'b': *dst++ = '\b'; break; - case 'f': *dst++ = '\f'; break; - case 'n': *dst++ = '\n'; break; - case 'r': *dst++ = '\r'; break; - case 't': *dst++ = '\t'; break; - case 'v': *dst++ = '\v'; break; - case '"': *dst++ = '"'; break; - case '\\': *dst++ = '\\'; break; - case 'x': { - c = *src++; - int chars = 0; - uchar prod = 0; - while (true) { - uchar val = fromhex(c.unicode()); - if (val == UCHAR_MAX) - break; - prod = prod * 16 + val; - if (++chars == 3 || src == end) - break; - c = *src++; - } - if (!chars) { - qDebug() << "MI Parse Error, unrecognized hex escape"; - return QString(); - } - *dst++ = prod; - break; - } - default: - { - int chars = 0; - uchar prod = 0; - forever { - if (c < '0' || c > '7') { - --src; - break; - } - prod = prod * 8 + c.unicode() - '0'; - if (++chars == 3 || src == end) - break; - c = *src++; - } - if (!chars) { - qDebug() << "MI Parse Error, unrecognized backslash escape"; - return QString(); - } - *dst++ = prod; - } - } - while (src != end) { - QChar c = *src++; - if (c == '\\') - break; - *dst++ = c; - } - } while (src != end); - *dst = 0; - result.truncate(dst - result.data()); + ++from; // Skip initial quote. + QString result; + result.reserve(to - from); + while (from < to) { + if (*from == '"') { + ++from; + return result; + } + parseCharOrEscape(from, to, result); } - return result; + qDebug() << "MI Parse Error, unfinished string"; + return QString(); } void GdbMi::parseValue(const QChar *&from, const QChar *to) diff --git a/tests/auto/debugger/CMakeLists.txt b/tests/auto/debugger/CMakeLists.txt index 66900623c34..71f48e399c1 100644 --- a/tests/auto/debugger/CMakeLists.txt +++ b/tests/auto/debugger/CMakeLists.txt @@ -32,6 +32,15 @@ add_qtc_test(tst_debugger_gdb "${DEBUGGERDIR}/debuggerprotocol.cpp" "${DEBUGGERDIR}/debuggerprotocol.h" ) +add_qtc_test(tst_debugger_protocol + DEPENDS Qt5::Network Utils + INCLUDES + "${DEBUGGERDIR}" + SOURCES + "${DEBUGGERDIR}/debuggerprotocol.cpp" "${DEBUGGERDIR}/debuggerprotocol.h" + tst_protocol.cpp +) + add_qtc_test(tst_debugger_offsets DEPENDS Qt5::CorePrivate INCLUDES "${DEBUGGERDIR}" diff --git a/tests/auto/debugger/debugger.pro b/tests/auto/debugger/debugger.pro index 284db38b0c8..6d24b5f7999 100644 --- a/tests/auto/debugger/debugger.pro +++ b/tests/auto/debugger/debugger.pro @@ -12,3 +12,4 @@ SUBDIRS += simplifytypes.pro SUBDIRS += dumpers.pro SUBDIRS += disassembler.pro SUBDIRS += offsets.pro +SUBDIRS += protocol.pro diff --git a/tests/auto/debugger/debugger.qbs b/tests/auto/debugger/debugger.qbs index 456c604b261..e4ae1051023 100644 --- a/tests/auto/debugger/debugger.qbs +++ b/tests/auto/debugger/debugger.qbs @@ -7,6 +7,7 @@ Project { "disassembler.qbs", "dumpers.qbs", "gdb.qbs", + "protocol.qbs", "offsets.qbs", "simplifytypes.qbs", ] diff --git a/tests/auto/debugger/protocol.pro b/tests/auto/debugger/protocol.pro new file mode 100644 index 00000000000..1008ba3d930 --- /dev/null +++ b/tests/auto/debugger/protocol.pro @@ -0,0 +1,46 @@ +QT = core network + +msvc: QTC_LIB_DEPENDS += utils +include(../qttest.pri) + +DEBUGGERDIR = $$IDE_SOURCE_TREE/src/plugins/debugger +UTILSDIR = $$IDE_SOURCE_TREE/src/libs/utils + +INCLUDEPATH += $$DEBUGGERDIR + +SOURCES += \ + tst_protocol.cpp \ + $$DEBUGGERDIR/debuggerprotocol.cpp + +HEADERS += \ + $$DEBUGGERDIR/debuggerprotocol.h + +!msvc { + SOURCES += \ + $$UTILSDIR/environment.cpp \ + $$UTILSDIR/fileutils.cpp \ + $$UTILSDIR/hostosinfo.cpp \ + $$UTILSDIR/namevaluedictionary.cpp \ + $$UTILSDIR/namevalueitem.cpp \ + $$UTILSDIR/qtcassert.cpp \ + $$UTILSDIR/qtcprocess.cpp \ + $$UTILSDIR/processhandle.cpp \ + $$UTILSDIR/savefile.cpp \ + + HEADERS += \ + $$UTILSDIR/environment.h \ + $$UTILSDIR/fileutils.h \ + $$UTILSDIR/hostosinfo.h \ + $$UTILSDIR/namevaluedictionary.h \ + $$UTILSDIR/namevalueitem.h \ + $$UTILSDIR/qtcassert.h \ + $$UTILSDIR/qtcprocess.h \ + $$UTILSDIR/processhandle.h \ + $$UTILSDIR/savefile.h \ + + macos: { + HEADERS += $$UTILSDIR/fileutils_mac.h + OBJECTIVE_SOURCES += $$UTILSDIR/fileutils_mac.mm + LIBS += -framework Foundation + } +} diff --git a/tests/auto/debugger/protocol.qbs b/tests/auto/debugger/protocol.qbs new file mode 100644 index 00000000000..ca1b0d8bece --- /dev/null +++ b/tests/auto/debugger/protocol.qbs @@ -0,0 +1,17 @@ +import qbs + +QtcAutotest { + name: "debugger protocol autotest" + Depends { name: "Utils" } + Depends { name: "Qt.network" } // For QHostAddress + Group { + name: "Sources from Debugger plugin" + prefix: project.debuggerDir + files: "debuggerprotocol.cpp" + } + Group { + name: "Test sources" + files: "tst_protocol.cpp" + } + cpp.includePaths: base.concat([project.debuggerDir]) +} diff --git a/tests/auto/debugger/tst_protocol.cpp b/tests/auto/debugger/tst_protocol.cpp new file mode 100644 index 00000000000..6d6f1190537 --- /dev/null +++ b/tests/auto/debugger/tst_protocol.cpp @@ -0,0 +1,92 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include + +#include + +//TESTED_COMPONENT=src/plugins/debugger + +class tst_protocol : public QObject +{ + Q_OBJECT + +public: + tst_protocol() {} + +private slots: + void parseCString(); + void parseCString_data(); +}; + +void tst_protocol::parseCString() +{ + QFETCH(QString, input); + QFETCH(QString, expected); + + const QChar *from = input.begin(); + const QChar *to = input.end(); + QString parsed = Debugger::Internal::GdbMi::parseCString(from, to); + + QCOMPARE(parsed, expected); +} + +void tst_protocol::parseCString_data() +{ + QTest::addColumn("input"); + QTest::addColumn("expected"); + + QTest::newRow("empty") + << "" + << ""; + + QTest::newRow("unquoted") + << "irgendwas" + << ""; + + QTest::newRow("plain") + << R"("plain")" + << "plain"; + + // This is expected to throw several warnings + // "MI Parse Error, unrecognized backslash escape" + QChar escapes[] = {'\a', '\b', '\f', '\n', '\r', '\t', '\v', '"', '\'', '\\'}; + QTest::newRow("escaped") + << R"("\a\b\c\d\e\f\g\h\i\j\k\l\m\n\o\p\q\r\s\t\u\v\w\y\z\"\'\\")" + << QString(escapes, sizeof(escapes)/sizeof(escapes[0])); + + QTest::newRow("octal") + << R"("abc\303\244\303\251def\303\261")" + << R"(abcäédefñ)"; + + QTest::newRow("hex") + << R"("abc\xc3\xa4\xc3\xa9def\xc3\xb1")" + << R"(abcäédefñ)"; +} + +QTEST_APPLESS_MAIN(tst_protocol); + +#include "tst_protocol.moc" +