Debugger: Use a class for GdbMI parser parameters

Currently only the previous from/to pair, but can be extended
by codec state.

Task-number: QTCREATORBUG-24462
Change-Id: I3d101e74d1fef65bb75ddaab1dc2eaf77201dcde
Reviewed-by: David Schulz <david.schulz@qt.io>
This commit is contained in:
hjk
2020-08-11 13:20:32 +02:00
parent 144f46bb54
commit 34945f84e4
4 changed files with 176 additions and 134 deletions

View File

@@ -46,7 +46,7 @@
namespace Debugger { namespace Debugger {
namespace Internal { namespace Internal {
uchar fromhex(uchar c) static uchar fromhex(uchar c)
{ {
if (c >= '0' && c <= '9') if (c >= '0' && c <= '9')
return c - '0'; return c - '0';
@@ -57,84 +57,122 @@ uchar fromhex(uchar c)
return UCHAR_MAX; return UCHAR_MAX;
} }
void skipCommas(const QChar *&from, const QChar *to) // DebuggerOutputParser
DebuggerOutputParser::DebuggerOutputParser(const QString &output)
: from(output.begin()), to(output.end())
{ {
while (*from == ',' && from != to) }
void DebuggerOutputParser::skipCommas()
{
while (from != to && *from == ',')
++from; ++from;
} }
void GdbMi::parseResultOrValue(const QChar *&from, const QChar *to) void DebuggerOutputParser::skipSpaces()
{ {
while (from != to && isspace(from->unicode())) while (from != to && isspace(from->unicode()))
++from; ++from;
}
//qDebug() << "parseResultOrValue: " << QString(from, to - from); QString DebuggerOutputParser::readString(const std::function<bool(char)> &isValidChar)
parseValue(from, to); {
QString res;
while (from != to && isValidChar(from->unicode()))
res += *from++;
return res;
}
int DebuggerOutputParser::readInt()
{
int res = 0;
while (from != to && *from >= '0' && *from <= '9') {
res *= 10;
res += (*from++).unicode() - '0';
}
return res;
}
QChar DebuggerOutputParser::readChar()
{
return *from++;
}
static bool isNameChar(char c)
{
return c != '=' && c != ':' && !isspace(c);
}
void GdbMi::parseResultOrValue(DebuggerOutputParser &parser)
{
parser.skipSpaces();
//qDebug() << "parseResultOrValue: " << parser.buffer();
parseValue(parser);
parser.skipSpaces();
if (isValid()) { if (isValid()) {
//qDebug() << "no valid result in " << QString(from, to - from); //qDebug() << "no valid result in " << parser.buffer();
return; return;
} }
if (from == to || *from == '(') if (parser.isAtEnd() || parser.isCurrent('('))
return; return;
const QChar *ptr = from;
while (ptr < to && *ptr != '=' && *ptr != ':') { m_name = parser.readString(isNameChar);
//qDebug() << "adding" << QChar(*ptr) << "to name";
++ptr; if (!parser.isAtEnd() && parser.isCurrent('=')) {
} parser.advance();
m_name = QString(from, ptr - from); parseValue(parser);
from = ptr;
if (from < to && *from == '=') {
++from;
parseValue(from, to);
} }
} }
// Reads one \ooo entity. // Reads one \ooo entity.
static bool parseOctalEscapedHelper(const QChar *&from, const QChar *to, QByteArray &buffer) static bool parseOctalEscapedHelper(DebuggerOutputParser &parser, QByteArray &buffer)
{ {
if (to - from < 4) if (parser.remainingChars() < 4)
return false; return false;
if (*from != '\\') if (!parser.isCurrent('\\'))
return false; return false;
const char c1 = from[1].unicode(); const char c1 = parser.lookAhead(1).unicode();
const char c2 = from[2].unicode(); const char c2 = parser.lookAhead(2).unicode();
const char c3 = from[3].unicode(); const char c3 = parser.lookAhead(3).unicode();
if (!isdigit(c1) || !isdigit(c2) || !isdigit(c3)) if (!isdigit(c1) || !isdigit(c2) || !isdigit(c3))
return false; return false;
buffer += char((c1 - '0') * 64 + (c2 - '0') * 8 + (c3 - '0')); buffer += char((c1 - '0') * 64 + (c2 - '0') * 8 + (c3 - '0'));
from += 4; parser.advance(4);
return true; return true;
} }
static bool parseHexEscapedHelper(const QChar *&from, const QChar *to, QByteArray &buffer) static bool parseHexEscapedHelper(DebuggerOutputParser &parser, QByteArray &buffer)
{ {
if (to - from < 4) if (parser.remainingChars() < 4)
return false; return false;
if (from[0]!= '\\') if (!parser.isCurrent('\\'))
return false; return false;
if (from[1] != 'x') if (parser.lookAhead(1) != 'x')
return false; return false;
const char c1 = from[2].unicode(); const char c1 = parser.lookAhead(2).unicode();
const char c2 = from[3].unicode(); const char c2 = parser.lookAhead(3).unicode();
if (!isxdigit(c1) || !isxdigit(c2)) if (!isxdigit(c1) || !isxdigit(c2))
return false; return false;
buffer += char(16 * fromhex(c1) + fromhex(c2)); buffer += char(16 * fromhex(c1) + fromhex(c2));
from += 4; parser.advance(4);
return true; return true;
} }
static void parseSimpleEscape(const QChar *&from, const QChar *to, QString &result) static void parseSimpleEscape(DebuggerOutputParser &parser, QString &result)
{ {
if (from == to) { if (parser.isAtEnd()) {
qDebug() << "MI Parse Error, unterminated backslash escape"; qDebug() << "MI Parse Error, unterminated backslash escape";
return; return;
} }
QChar c = *from++; const QChar c = parser.current();
parser.advance();
switch (c.unicode()) { switch (c.unicode()) {
case 'a': result += '\a'; break; case 'a': result += '\a'; break;
case 'b': result += '\b'; break; case 'b': result += '\b'; break;
@@ -153,28 +191,29 @@ static void parseSimpleEscape(const QChar *&from, const QChar *to, QString &resu
// Reads subsequent \123 or \x12 entities and converts to Utf8, // Reads subsequent \123 or \x12 entities and converts to Utf8,
// *or* one escaped char, *or* one unescaped char. // *or* one escaped char, *or* one unescaped char.
static void parseCharOrEscape(const QChar *&from, const QChar *to, QString &result) static void parseCharOrEscape(DebuggerOutputParser &parser, QString &result)
{ {
QByteArray buffer; QByteArray buffer;
while (parseOctalEscapedHelper(from, to, buffer)) while (parseOctalEscapedHelper(parser, buffer))
; ;
while (parseHexEscapedHelper(from, to, buffer)) while (parseHexEscapedHelper(parser, buffer))
; ;
if (!buffer.isEmpty()) if (!buffer.isEmpty()) {
result.append(QString::fromUtf8(buffer)); result.append(QString::fromUtf8(buffer));
else if (*from == '\\') } else if (parser.isCurrent('\\')) {
parseSimpleEscape(++from, to, result); parser.advance();
else parseSimpleEscape(parser, result);
result += *from++; } else {
result += parser.readChar();
}
} }
QString GdbMi::parseCString(const QChar *&from, const QChar *to) QString DebuggerOutputParser::readCString()
{ {
if (to == from) if (isAtEnd())
return QString(); return QString();
//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
@@ -189,82 +228,83 @@ QString GdbMi::parseCString(const QChar *&from, const QChar *to)
++from; ++from;
return result; return result;
} }
parseCharOrEscape(from, to, result); parseCharOrEscape(*this, result);
} }
qDebug() << "MI Parse Error, unfinished string"; qDebug() << "MI Parse Error, unfinished string";
return QString(); return QString();
} }
void GdbMi::parseValue(const QChar *&from, const QChar *to) void GdbMi::parseValue(DebuggerOutputParser &parser)
{ {
if (from == to) if (parser.isAtEnd())
return; return;
//qDebug() << "parseValue: " << QString(from, to - from); //qDebug() << "parseValue: " << parser;
switch (from->unicode()) { switch (parser.current().unicode()) {
case '{': case '{':
parseTuple(from, to); parseTuple(parser);
break; break;
case '[': case '[':
parseList(from, to); parseList(parser);
break; break;
case '"': case '"':
m_type = Const; m_type = Const;
m_data = parseCString(from, to); m_data = parser.readCString();
break; break;
default: default:
break; break;
} }
} }
void GdbMi::parseTuple(const QChar *&from, const QChar *to) void GdbMi::parseTuple(DebuggerOutputParser &parser)
{ {
//qDebug() << "parseTuple: " << QString(from, to - from); //qDebug() << "parseTuple: " << parser.buffer();
//QTC_CHECK(*from == '{'); QTC_CHECK(parser.isCurrent('{'));
++from; parser.advance();
parseTuple_helper(from, to); parseTuple_helper(parser);
} }
void GdbMi::parseTuple_helper(const QChar *&from, const QChar *to) void GdbMi::parseTuple_helper(DebuggerOutputParser &parser)
{ {
skipCommas(from, to); parser.skipCommas();
//qDebug() << "parseTuple_helper: " << QString(from, to - from); //qDebug() << "parseTuple_helper: " << parser.buffer();
QString buf = parser.buffer();
m_type = Tuple; m_type = Tuple;
while (from < to) { while (!parser.isAtEnd()) {
if (*from == '}') { if (parser.isCurrent('}')) {
++from; parser.advance();
break; break;
} }
GdbMi child; GdbMi child;
child.parseResultOrValue(from, to); child.parseResultOrValue(parser);
//qDebug() << "\n=======\n" << qPrintable(child.toString()) << "\n========\n"; //qDebug() << "\n=======\n" << qPrintable(child.toString()) << "\n========\n";
if (!child.isValid()) if (!child.isValid())
return; return;
m_children.push_back(child); m_children.push_back(child);
skipCommas(from, to); parser.skipCommas();
} }
} }
void GdbMi::parseList(const QChar *&from, const QChar *to) void GdbMi::parseList(DebuggerOutputParser &parser)
{ {
//qDebug() << "parseList: " << QString(from, to - from); //qDebug() << "parseList: " << parser.buffer();
//QTC_CHECK(*from == '['); QTC_CHECK(parser.isCurrent('['));
++from; parser.advance();
m_type = List; m_type = List;
skipCommas(from, to); parser.skipCommas();
while (from < to) { while (!parser.isAtEnd()) {
if (*from == ']') { if (parser.isCurrent(']')) {
++from; parser.advance();
break; break;
} }
GdbMi child; GdbMi child;
child.parseResultOrValue(from, to); child.parseResultOrValue(parser);
if (child.isValid()) { if (child.isValid()) {
m_children.push_back(child); m_children.push_back(child);
skipCommas(from, to); parser.skipCommas();
} else { } else {
++from; parser.advance();
} }
} }
} }
@@ -369,16 +409,14 @@ QString GdbMi::toString(bool multiline, int indent) const
void GdbMi::fromString(const QString &ba) void GdbMi::fromString(const QString &ba)
{ {
const QChar *from = ba.constBegin(); DebuggerOutputParser parser(ba);
const QChar *to = ba.constEnd(); parseResultOrValue(parser);
parseResultOrValue(from, to);
} }
void GdbMi::fromStringMultiple(const QString &ba) void GdbMi::fromStringMultiple(const QString &ba)
{ {
const QChar *from = ba.constBegin(); DebuggerOutputParser parser(ba);
const QChar *to = ba.constEnd(); parseTuple_helper(parser);
parseTuple_helper(from, to);
} }
const GdbMi &GdbMi::operator[](const char *name) const const GdbMi &GdbMi::operator[](const char *name) const

View File

@@ -32,7 +32,6 @@
#include <QJsonObject> #include <QJsonObject>
#include <QVector> #include <QVector>
#include <functional>
#include <utils/fileutils.h> #include <utils/fileutils.h>
namespace Utils { class ProcessHandle; } namespace Utils { class ProcessHandle; }
@@ -125,7 +124,35 @@ public:
bool m_continue = false; bool m_continue = false;
}; };
// FIXME: rename into GdbMiValue class DebuggerOutputParser
{
public:
explicit DebuggerOutputParser(const QString &output);
QChar current() const { return *from; }
bool isCurrent(QChar c) const { return *from == c; }
bool isAtEnd() const { return from == to; }
void advance() { ++from; }
void advance(int n) { from += n; }
QChar lookAhead(int offset) const { return from[offset]; }
int readInt();
QChar readChar();
QString readCString();
QString readString(const std::function<bool(char)> &isValidChar);
QString buffer() const { return QString(from, to - from); }
int remainingChars() const { return int(to - from); }
void skipCommas();
void skipSpaces();
private:
const QChar *from = nullptr;
const QChar *to = nullptr;
};
class GdbMi class GdbMi
{ {
public: public:
@@ -163,13 +190,12 @@ public:
void fromString(const QString &str); void fromString(const QString &str);
void fromStringMultiple(const QString &str); void fromStringMultiple(const QString &str);
static QString parseCString(const QChar *&from, const QChar *to);
static QString escapeCString(const QString &ba); static QString escapeCString(const QString &ba);
void parseResultOrValue(const QChar *&from, const QChar *to); void parseResultOrValue(DebuggerOutputParser &state);
void parseValue(const QChar *&from, const QChar *to); void parseValue(DebuggerOutputParser &state);
void parseTuple(const QChar *&from, const QChar *to); void parseTuple(DebuggerOutputParser &state);
void parseTuple_helper(const QChar *&from, const QChar *to); void parseTuple_helper(DebuggerOutputParser &state);
void parseList(const QChar *&from, const QChar *to); void parseList(DebuggerOutputParser &state);
private: private:
void dumpChildren(QString *str, bool multiline, int indent) const; void dumpChildren(QString *str, bool multiline, int indent) const;

View File

@@ -259,45 +259,27 @@ void GdbEngine::handleResponse(const QString &buff)
if (buff.isEmpty() || buff == "(gdb) ") if (buff.isEmpty() || buff == "(gdb) ")
return; return;
const QChar *from = buff.constData(); DebuggerOutputParser parser(buff);
const QChar *to = from + buff.size();
const QChar *inner;
int token = -1; const int token = parser.readInt();
// Token is a sequence of numbers.
for (inner = from; inner != to; ++inner)
if (*inner < '0' || *inner > '9')
break;
if (from != inner) {
token = QString(from, inner - from).toInt();
from = inner;
}
// Next char decides kind of response. // Next char decides kind of response.
const QChar c = *from++; switch (parser.readChar().unicode()) {
switch (c.unicode()) {
case '*': case '*':
case '+': case '+':
case '=': { case '=': {
QString asyncClass; const QString asyncClass = parser.readString(isNameChar);
for (; from != to; ++from) {
const QChar c = *from;
if (!isNameChar(c.unicode()))
break;
asyncClass += *from;
}
GdbMi result; GdbMi result;
while (from != to) { while (!parser.isAtEnd()) {
GdbMi data; GdbMi data;
if (*from != ',') { if (!parser.isCurrent(',')) {
// happens on archer where we get // happens on archer where we get
// 23^running <NL> *running,thread-id="all" <NL> (gdb) // 23^running <NL> *running,thread-id="all" <NL> (gdb)
result.m_type = GdbMi::Tuple; result.m_type = GdbMi::Tuple;
break; break;
} }
++from; // skip ',' parser.advance(); // skip ','
data.parseResultOrValue(from, to); data.parseResultOrValue(parser);
if (data.isValid()) { if (data.isValid()) {
//qDebug() << "parsed result:" << data.toString(); //qDebug() << "parsed result:" << data.toString();
result.addChild(data); result.addChild(data);
@@ -309,7 +291,7 @@ void GdbEngine::handleResponse(const QString &buff)
} }
case '~': { case '~': {
QString data = GdbMi::parseCString(from, to); QString data = parser.readCString();
if (data.startsWith("bridgemessage={")) { if (data.startsWith("bridgemessage={")) {
// It's already logged. // It's already logged.
break; break;
@@ -374,14 +356,14 @@ void GdbEngine::handleResponse(const QString &buff)
} }
case '@': { case '@': {
QString data = GdbMi::parseCString(from, to); QString data = parser.readCString();
QString msg = data.left(data.size() - 1); QString msg = data.left(data.size() - 1);
showMessage(msg, AppOutput); showMessage(msg, AppOutput);
break; break;
} }
case '&': { case '&': {
QString data = GdbMi::parseCString(from, to); QString data = parser.readCString();
// On Windows, the contents seem to depend on the debugger // On Windows, the contents seem to depend on the debugger
// version and/or OS version used. // version and/or OS version used.
if (data.startsWith("warning:")) if (data.startsWith("warning:"))
@@ -402,11 +384,8 @@ void GdbEngine::handleResponse(const QString &buff)
response.token = token; response.token = token;
for (inner = from; inner != to; ++inner) QString resultClass = parser.readString(isNameChar);
if (*inner < 'a' || *inner > 'z')
break;
QString resultClass = QString::fromRawData(from, inner - from);
if (resultClass == "done") if (resultClass == "done")
response.resultClass = ResultDone; response.resultClass = ResultDone;
else if (resultClass == "running") else if (resultClass == "running")
@@ -420,11 +399,10 @@ void GdbEngine::handleResponse(const QString &buff)
else else
response.resultClass = ResultUnknown; response.resultClass = ResultUnknown;
from = inner; if (!parser.isAtEnd()) {
if (from != to) { if (parser.isCurrent(',')) {
if (*from == ',') { parser.advance();
++from; response.data.parseTuple_helper(parser);
response.data.parseTuple_helper(from, to);
response.data.m_type = GdbMi::Tuple; response.data.m_type = GdbMi::Tuple;
response.data.m_name = "data"; response.data.m_name = "data";
} else { } else {
@@ -446,7 +424,8 @@ void GdbEngine::handleResponse(const QString &buff)
break; break;
} }
default: { default: {
qDebug() << "UNKNOWN RESPONSE TYPE '" << c << "'. REST: " << from; qDebug() << "UNKNOWN RESPONSE TYPE '" << parser.current() << "'. BUFFER: "
<< parser.buffer();
break; break;
} }
} }

View File

@@ -46,9 +46,8 @@ void tst_protocol::parseCString()
QFETCH(QString, input); QFETCH(QString, input);
QFETCH(QString, expected); QFETCH(QString, expected);
const QChar *from = input.begin(); Debugger::Internal::DebuggerOutputParser parser(input);
const QChar *to = input.end(); QString parsed = parser.readCString();
QString parsed = Debugger::Internal::GdbMi::parseCString(from, to);
QCOMPARE(parsed, expected); QCOMPARE(parsed, expected);
} }