Debugger: Fix parsing of octal-encoded gdb escapes

Fixes: QTCREATORBUG-24462
Change-Id: I89153a04eeef6a2e20fefef45e0efa3712ec0997
Reviewed-by: Christian Stenger <christian.stenger@qt.io>
This commit is contained in:
hjk
2020-08-12 00:18:00 +02:00
parent ee5feb4939
commit f08c3d150a
7 changed files with 260 additions and 87 deletions

View File

@@ -89,105 +89,112 @@ 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 GdbMi::parseCString(const QChar *&from, const QChar *to)
{ {
QString result; if (to == from)
return QString();
//qDebug() << "parseCString: " << QString(from, to - from); //qDebug() << "parseCString: " << QString(from, to - from);
if (*from != '"') { if (*from != '"') {
qDebug() << "MI Parse Error, double quote expected"; qDebug() << "MI Parse Error, double quote expected";
++from; // So we don't hang ++from; // So we don't hang
return QString(); 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; return result;
} }
parseCharOrEscape(from, to, result);
}
qDebug() << "MI Parse Error, unfinished string";
return QString();
}
void GdbMi::parseValue(const QChar *&from, const QChar *to) void GdbMi::parseValue(const QChar *&from, const QChar *to)
{ {

View File

@@ -32,6 +32,15 @@ add_qtc_test(tst_debugger_gdb
"${DEBUGGERDIR}/debuggerprotocol.cpp" "${DEBUGGERDIR}/debuggerprotocol.h" "${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 add_qtc_test(tst_debugger_offsets
DEPENDS Qt5::CorePrivate DEPENDS Qt5::CorePrivate
INCLUDES "${DEBUGGERDIR}" INCLUDES "${DEBUGGERDIR}"

View File

@@ -12,3 +12,4 @@ SUBDIRS += simplifytypes.pro
SUBDIRS += dumpers.pro SUBDIRS += dumpers.pro
SUBDIRS += disassembler.pro SUBDIRS += disassembler.pro
SUBDIRS += offsets.pro SUBDIRS += offsets.pro
SUBDIRS += protocol.pro

View File

@@ -7,6 +7,7 @@ Project {
"disassembler.qbs", "disassembler.qbs",
"dumpers.qbs", "dumpers.qbs",
"gdb.qbs", "gdb.qbs",
"protocol.qbs",
"offsets.qbs", "offsets.qbs",
"simplifytypes.qbs", "simplifytypes.qbs",
] ]

View File

@@ -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
}
}

View File

@@ -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])
}

View File

@@ -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 <debuggerprotocol.h>
#include <QtTest>
//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<QString>("input");
QTest::addColumn<QString>("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"