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 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<bool(char)> &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

View File

@@ -32,7 +32,6 @@
#include <QJsonObject>
#include <QVector>
#include <functional>
#include <utils/fileutils.h>
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<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
{
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;

View File

@@ -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 <NL> *running,thread-id="all" <NL> (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;
}
}

View File

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