From 34945f84e4428c9037302cf9c38c71c50817c829 Mon Sep 17 00:00:00 2001 From: hjk Date: Tue, 11 Aug 2020 13:20:32 +0200 Subject: [PATCH] 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 --- src/plugins/debugger/debuggerprotocol.cpp | 206 +++++++++++++--------- src/plugins/debugger/debuggerprotocol.h | 42 ++++- src/plugins/debugger/gdb/gdbengine.cpp | 57 ++---- tests/auto/debugger/tst_protocol.cpp | 5 +- 4 files changed, 176 insertions(+), 134 deletions(-) diff --git a/src/plugins/debugger/debuggerprotocol.cpp b/src/plugins/debugger/debuggerprotocol.cpp index 8a751aa4879..c9ffcde22aa 100644 --- a/src/plugins/debugger/debuggerprotocol.cpp +++ b/src/plugins/debugger/debuggerprotocol.cpp @@ -46,7 +46,7 @@ namespace Debugger { namespace Internal { -uchar fromhex(uchar c) +static uchar fromhex(uchar c) { if (c >= '0' && c <= '9') return c - '0'; @@ -57,84 +57,122 @@ uchar fromhex(uchar c) 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; } -void GdbMi::parseResultOrValue(const QChar *&from, const QChar *to) +void DebuggerOutputParser::skipSpaces() { while (from != to && isspace(from->unicode())) ++from; +} - //qDebug() << "parseResultOrValue: " << QString(from, to - from); - parseValue(from, to); +QString DebuggerOutputParser::readString(const std::function &isValidChar) +{ + 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()) { - //qDebug() << "no valid result in " << QString(from, to - from); + //qDebug() << "no valid result in " << parser.buffer(); return; } - if (from == to || *from == '(') + if (parser.isAtEnd() || parser.isCurrent('(')) return; - const QChar *ptr = from; - while (ptr < to && *ptr != '=' && *ptr != ':') { - //qDebug() << "adding" << QChar(*ptr) << "to name"; - ++ptr; - } - m_name = QString(from, ptr - from); - from = ptr; - if (from < to && *from == '=') { - ++from; - parseValue(from, to); + + m_name = parser.readString(isNameChar); + + if (!parser.isAtEnd() && parser.isCurrent('=')) { + parser.advance(); + parseValue(parser); } } // 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; - if (*from != '\\') + if (!parser.isCurrent('\\')) return false; - const char c1 = from[1].unicode(); - const char c2 = from[2].unicode(); - const char c3 = from[3].unicode(); + const char c1 = parser.lookAhead(1).unicode(); + const char c2 = parser.lookAhead(2).unicode(); + const char c3 = parser.lookAhead(3).unicode(); if (!isdigit(c1) || !isdigit(c2) || !isdigit(c3)) return false; buffer += char((c1 - '0') * 64 + (c2 - '0') * 8 + (c3 - '0')); - from += 4; + parser.advance(4); 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; - if (from[0]!= '\\') + if (!parser.isCurrent('\\')) return false; - if (from[1] != 'x') + if (parser.lookAhead(1) != 'x') return false; - const char c1 = from[2].unicode(); - const char c2 = from[3].unicode(); + const char c1 = parser.lookAhead(2).unicode(); + const char c2 = parser.lookAhead(3).unicode(); if (!isxdigit(c1) || !isxdigit(c2)) return false; buffer += char(16 * fromhex(c1) + fromhex(c2)); - from += 4; + parser.advance(4); 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"; return; } - QChar c = *from++; + const QChar c = parser.current(); + parser.advance(); switch (c.unicode()) { case 'a': result += '\a'; 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, // *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; - 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)); - else if (*from == '\\') - parseSimpleEscape(++from, to, result); - else - result += *from++; + } else if (parser.isCurrent('\\')) { + parser.advance(); + parseSimpleEscape(parser, result); + } else { + result += parser.readChar(); + } } -QString GdbMi::parseCString(const QChar *&from, const QChar *to) +QString DebuggerOutputParser::readCString() { - if (to == from) + if (isAtEnd()) return QString(); - //qDebug() << "parseCString: " << QString(from, to - from); if (*from != '"') { qDebug() << "MI Parse Error, double quote expected"; ++from; // So we don't hang @@ -189,82 +228,83 @@ QString GdbMi::parseCString(const QChar *&from, const QChar *to) ++from; return result; } - parseCharOrEscape(from, to, result); + parseCharOrEscape(*this, result); } qDebug() << "MI Parse Error, unfinished string"; return QString(); } -void GdbMi::parseValue(const QChar *&from, const QChar *to) +void GdbMi::parseValue(DebuggerOutputParser &parser) { - if (from == to) + if (parser.isAtEnd()) return; - //qDebug() << "parseValue: " << QString(from, to - from); - switch (from->unicode()) { + //qDebug() << "parseValue: " << parser; + switch (parser.current().unicode()) { case '{': - parseTuple(from, to); + parseTuple(parser); break; case '[': - parseList(from, to); + parseList(parser); break; case '"': m_type = Const; - m_data = parseCString(from, to); + m_data = parser.readCString(); break; default: break; } } -void GdbMi::parseTuple(const QChar *&from, const QChar *to) +void GdbMi::parseTuple(DebuggerOutputParser &parser) { - //qDebug() << "parseTuple: " << QString(from, to - from); - //QTC_CHECK(*from == '{'); - ++from; - parseTuple_helper(from, to); + //qDebug() << "parseTuple: " << parser.buffer(); + QTC_CHECK(parser.isCurrent('{')); + parser.advance(); + parseTuple_helper(parser); } -void GdbMi::parseTuple_helper(const QChar *&from, const QChar *to) +void GdbMi::parseTuple_helper(DebuggerOutputParser &parser) { - skipCommas(from, to); - //qDebug() << "parseTuple_helper: " << QString(from, to - from); + parser.skipCommas(); + //qDebug() << "parseTuple_helper: " << parser.buffer(); + QString buf = parser.buffer(); m_type = Tuple; - while (from < to) { - if (*from == '}') { - ++from; + while (!parser.isAtEnd()) { + if (parser.isCurrent('}')) { + parser.advance(); break; } GdbMi child; - child.parseResultOrValue(from, to); + child.parseResultOrValue(parser); //qDebug() << "\n=======\n" << qPrintable(child.toString()) << "\n========\n"; if (!child.isValid()) return; 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); - //QTC_CHECK(*from == '['); - ++from; + //qDebug() << "parseList: " << parser.buffer(); + QTC_CHECK(parser.isCurrent('[')); + parser.advance(); m_type = List; - skipCommas(from, to); - while (from < to) { - if (*from == ']') { - ++from; + parser.skipCommas(); + while (!parser.isAtEnd()) { + if (parser.isCurrent(']')) { + parser.advance(); break; } GdbMi child; - child.parseResultOrValue(from, to); + child.parseResultOrValue(parser); if (child.isValid()) { m_children.push_back(child); - skipCommas(from, to); + parser.skipCommas(); } else { - ++from; + parser.advance(); } } } @@ -369,16 +409,14 @@ QString GdbMi::toString(bool multiline, int indent) const void GdbMi::fromString(const QString &ba) { - const QChar *from = ba.constBegin(); - const QChar *to = ba.constEnd(); - parseResultOrValue(from, to); + DebuggerOutputParser parser(ba); + parseResultOrValue(parser); } void GdbMi::fromStringMultiple(const QString &ba) { - const QChar *from = ba.constBegin(); - const QChar *to = ba.constEnd(); - parseTuple_helper(from, to); + DebuggerOutputParser parser(ba); + parseTuple_helper(parser); } const GdbMi &GdbMi::operator[](const char *name) const diff --git a/src/plugins/debugger/debuggerprotocol.h b/src/plugins/debugger/debuggerprotocol.h index 0d9589f5702..3154688c56e 100644 --- a/src/plugins/debugger/debuggerprotocol.h +++ b/src/plugins/debugger/debuggerprotocol.h @@ -32,7 +32,6 @@ #include #include -#include #include namespace Utils { class ProcessHandle; } @@ -125,7 +124,35 @@ public: 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 &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 { public: @@ -163,13 +190,12 @@ public: void fromString(const QString &str); void fromStringMultiple(const QString &str); - static QString parseCString(const QChar *&from, const QChar *to); static QString escapeCString(const QString &ba); - void parseResultOrValue(const QChar *&from, const QChar *to); - void parseValue(const QChar *&from, const QChar *to); - void parseTuple(const QChar *&from, const QChar *to); - void parseTuple_helper(const QChar *&from, const QChar *to); - void parseList(const QChar *&from, const QChar *to); + void parseResultOrValue(DebuggerOutputParser &state); + void parseValue(DebuggerOutputParser &state); + void parseTuple(DebuggerOutputParser &state); + void parseTuple_helper(DebuggerOutputParser &state); + void parseList(DebuggerOutputParser &state); private: void dumpChildren(QString *str, bool multiline, int indent) const; diff --git a/src/plugins/debugger/gdb/gdbengine.cpp b/src/plugins/debugger/gdb/gdbengine.cpp index 71746e73543..7bcd4d51d9e 100644 --- a/src/plugins/debugger/gdb/gdbengine.cpp +++ b/src/plugins/debugger/gdb/gdbengine.cpp @@ -259,45 +259,27 @@ void GdbEngine::handleResponse(const QString &buff) if (buff.isEmpty() || buff == "(gdb) ") return; - const QChar *from = buff.constData(); - const QChar *to = from + buff.size(); - const QChar *inner; + DebuggerOutputParser parser(buff); - int token = -1; - // 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; - } + const int token = parser.readInt(); // Next char decides kind of response. - const QChar c = *from++; - switch (c.unicode()) { + switch (parser.readChar().unicode()) { case '*': case '+': case '=': { - QString asyncClass; - for (; from != to; ++from) { - const QChar c = *from; - if (!isNameChar(c.unicode())) - break; - asyncClass += *from; - } - + const QString asyncClass = parser.readString(isNameChar); GdbMi result; - while (from != to) { + while (!parser.isAtEnd()) { GdbMi data; - if (*from != ',') { + if (!parser.isCurrent(',')) { // happens on archer where we get // 23^running *running,thread-id="all" (gdb) result.m_type = GdbMi::Tuple; break; } - ++from; // skip ',' - data.parseResultOrValue(from, to); + parser.advance(); // skip ',' + data.parseResultOrValue(parser); if (data.isValid()) { //qDebug() << "parsed result:" << data.toString(); result.addChild(data); @@ -309,7 +291,7 @@ void GdbEngine::handleResponse(const QString &buff) } case '~': { - QString data = GdbMi::parseCString(from, to); + QString data = parser.readCString(); if (data.startsWith("bridgemessage={")) { // It's already logged. break; @@ -374,14 +356,14 @@ void GdbEngine::handleResponse(const QString &buff) } case '@': { - QString data = GdbMi::parseCString(from, to); + QString data = parser.readCString(); QString msg = data.left(data.size() - 1); showMessage(msg, AppOutput); break; } case '&': { - QString data = GdbMi::parseCString(from, to); + QString data = parser.readCString(); // On Windows, the contents seem to depend on the debugger // version and/or OS version used. if (data.startsWith("warning:")) @@ -402,11 +384,8 @@ void GdbEngine::handleResponse(const QString &buff) response.token = token; - for (inner = from; inner != to; ++inner) - if (*inner < 'a' || *inner > 'z') - break; + QString resultClass = parser.readString(isNameChar); - QString resultClass = QString::fromRawData(from, inner - from); if (resultClass == "done") response.resultClass = ResultDone; else if (resultClass == "running") @@ -420,11 +399,10 @@ void GdbEngine::handleResponse(const QString &buff) else response.resultClass = ResultUnknown; - from = inner; - if (from != to) { - if (*from == ',') { - ++from; - response.data.parseTuple_helper(from, to); + if (!parser.isAtEnd()) { + if (parser.isCurrent(',')) { + parser.advance(); + response.data.parseTuple_helper(parser); response.data.m_type = GdbMi::Tuple; response.data.m_name = "data"; } else { @@ -446,7 +424,8 @@ void GdbEngine::handleResponse(const QString &buff) break; } default: { - qDebug() << "UNKNOWN RESPONSE TYPE '" << c << "'. REST: " << from; + qDebug() << "UNKNOWN RESPONSE TYPE '" << parser.current() << "'. BUFFER: " + << parser.buffer(); break; } } diff --git a/tests/auto/debugger/tst_protocol.cpp b/tests/auto/debugger/tst_protocol.cpp index 6d6f1190537..31f6423c43c 100644 --- a/tests/auto/debugger/tst_protocol.cpp +++ b/tests/auto/debugger/tst_protocol.cpp @@ -46,9 +46,8 @@ 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); + Debugger::Internal::DebuggerOutputParser parser(input); + QString parsed = parser.readCString(); QCOMPARE(parsed, expected); }