File indexing completed on 2024-05-05 05:51:35

0001 //
0002 // Description: GDB variable parser
0003 //
0004 // This class parses a "flat" GDB variable string and outputs the structure of its child variables.
0005 // A signal is emitted for each of its child variable with a reference to its parent variable.
0006 //
0007 // Example : GDB gives the following flat value for the variable 'abcd':
0008 // "{name = \"hello\", d = {a = 0x7fffe0020ff0, size = 5}, value = 12}"
0009 //
0010 // The output will be the following structure :
0011 //
0012 // Symbol            Value
0013 // ----------------------------------
0014 // abcd
0015 //   name          "hello"
0016 //     d
0017 //       a          0x7fffe0020ff0
0018 //       size       5
0019 //     value        12
0020 //
0021 // SPDX-FileCopyrightText: 2010 Kåre Särs <kare.sars@iki.fi>
0022 // SPDX-FileCopyrightText: 2023 Rémi Peuchot <kde.remi@proton.me>
0023 //
0024 // SPDX-License-Identifier: LGPL-2.0-only
0025 
0026 #include "gdbvariableparser.h"
0027 
0028 // Smaller ids are reserved for scopes
0029 static constexpr int MIN_VAR_ID = 10;
0030 
0031 GDBVariableParser::GDBVariableParser(QObject *parent)
0032     : QObject(parent)
0033 {
0034 }
0035 
0036 void GDBVariableParser::insertVariable(const QString &name, const QString &value, const QString &type, bool changed)
0037 {
0038     QStringView tail(value);
0039     insertNamedVariable(0, name, 0, tail, type, changed);
0040 }
0041 
0042 // Take the next available variable id, signal the variable and return its id
0043 int GDBVariableParser::createAndSignalVariable(int parentId, const QStringView name, const QStringView value, const QString &type, bool changed)
0044 {
0045     // Increment variable id, restart from MIN_VAR_ID in case of int overflow
0046     m_variableId = std::max(MIN_VAR_ID, m_variableId + 1);
0047 
0048     dap::Variable var(name.toString(), value.toString(), m_variableId);
0049     var.valueChanged = changed;
0050     if (!type.isEmpty()) {
0051         var.type = type;
0052     }
0053     Q_EMIT variable(parentId, var);
0054     return m_variableId;
0055 }
0056 
0057 enum ParsingState {
0058     Normal,
0059     InQuotedString,
0060     InParenthesis,
0061 };
0062 
0063 // Return the first index of the given char outside of quoted strings and parenthesis
0064 // example : finding the first comma in the following string:
0065 // "aaa\"bbb,bbb\\"bbb,bbb\\"bbb\"aaa(ccc,ccc)aaa,aaa,aaa"
0066 //                                               ^
0067 //                              gives this one : ^
0068 //
0069 // because the string is decomposed into :
0070 // - bbb : "in quoted string"
0071 // - ccc : "in parenthesis"
0072 // - aaa : normal (where the comma is searched for)
0073 qsizetype firstIndexOf(const QStringView tail, QChar ch)
0074 {
0075     const QChar QUOTE((short)'"');
0076     const QChar BACKSLASH((short)'\\');
0077     const QChar OPENING_PARENTHESIS((short)'(');
0078     const QChar CLOSING_PARENTHESIS((short)')');
0079     QChar previous(0);
0080     ParsingState state = Normal;
0081     for (int i = 0; i < tail.length(); i++) {
0082         QChar current = tail[i];
0083         if (state == Normal) {
0084             if (current == ch) {
0085                 return i;
0086             }
0087             if (current == QUOTE) {
0088                 state = InQuotedString;
0089             } else if (current == OPENING_PARENTHESIS) {
0090                 state = InParenthesis;
0091             }
0092         } else if (state == InQuotedString) {
0093             if (current == QUOTE && previous != BACKSLASH) {
0094                 state = Normal;
0095             }
0096         } else if (state == InParenthesis) {
0097             if (current == CLOSING_PARENTHESIS) {
0098                 state = Normal;
0099             }
0100         }
0101         previous = current;
0102     }
0103     return -1;
0104 }
0105 
0106 // Return index of first char among 'characters' in tail
0107 qsizetype firstIndexOf(const QStringView tail, QString characters)
0108 {
0109     qsizetype first = -1;
0110     for (auto ch : characters) {
0111         qsizetype i = firstIndexOf(tail, ch);
0112         if (i != -1 && (first == -1 || i < first)) {
0113             first = i;
0114         }
0115     }
0116     return first;
0117 }
0118 
0119 // If tail starts with pattern "name = value" :
0120 // - advance tail to first char of value
0121 // - extract and return name
0122 // else :
0123 // - let tail unchanged
0124 // - return empty string
0125 QStringView findVariableName(QStringView &tail)
0126 {
0127     const QChar EQUAL((short)'=');
0128     auto closingIndex = firstIndexOf(tail, QStringLiteral("=,{}"));
0129     if (closingIndex != -1 && tail[closingIndex] == EQUAL) {
0130         QStringView name = tail.mid(0, closingIndex).trimmed();
0131         tail = tail.mid(closingIndex + 1).trimmed();
0132         return name;
0133     }
0134     return QStringView();
0135 }
0136 
0137 // Parse the (eventually named) variable at the beginning of the given tail.
0138 // The pattern defines the value type :
0139 // - "name = value" : it's a named variable, 'itemIndex' is ignored
0140 // - "value" : it's an array item of index 'itemIndex'
0141 // The given tail will be advanced to the next character after this variable value.
0142 void GDBVariableParser::insertVariable(int parentId, int itemIndex, QStringView &tail, const QString &type, bool changed)
0143 {
0144     QStringView name = findVariableName(tail);
0145     if (name.isEmpty()) {
0146         // No variable name : it's an array item
0147         QString itemName = QStringLiteral("[%1]").arg(itemIndex);
0148         insertNamedVariable(parentId, itemName, itemIndex, tail, type, changed);
0149         return;
0150     }
0151     insertNamedVariable(parentId, name, itemIndex, tail, type, changed);
0152 }
0153 
0154 // Parse the variable value at the beginning of the given tail.
0155 // The pattern defines the value type :
0156 // - if value contains an open brace, it's a parent object with children : "optional_string{child0, child1, ...}"
0157 // - else, it's a simple variable value
0158 // The given tail will be advanced to the next character after this variable value.
0159 void GDBVariableParser::insertNamedVariable(int parentId, QStringView name, int itemIndex, QStringView &tail, const QString &type, bool changed)
0160 {
0161     // Find an opening brace in the value (before next comma or next closing brace)
0162     const QChar OPENING_BRACE((short)'{');
0163     int openingIndex = firstIndexOf(tail, QStringLiteral(",{}"));
0164     if (openingIndex != -1 && tail[openingIndex] == OPENING_BRACE) {
0165         // It's a parent object
0166         QString value = tail.mid(0, openingIndex).toString();
0167         tail = tail.mid(openingIndex + 1).trimmed(); // Advance after the opening brace
0168 
0169         if (tail.startsWith(QStringLiteral("}"))) {
0170             // It's an empty object
0171             value = value + QStringLiteral("{}");
0172             createAndSignalVariable(parentId, name, value, type, changed);
0173         } else {
0174             // It contains some child variables
0175             value = value + QStringLiteral("{...}");
0176 
0177             // Create the parent object
0178             int id = createAndSignalVariable(parentId, name, value, type, changed);
0179 
0180             // Insert the first child variable, siblings will be created recursively
0181             insertVariable(id, 0, tail, QStringLiteral(""), changed);
0182         }
0183 
0184         // All child variables have been parsed, the parent is supposed to be closed now
0185         if (tail.startsWith(QStringLiteral("}"))) {
0186             tail = tail.mid(1).trimmed(); // Advance after the closing brace
0187         } else {
0188             qWarning() << "Missing closing brace at the end of parent variable";
0189         }
0190     } else {
0191         // It's a simple variable value
0192         auto valueLength = firstIndexOf(tail, QStringLiteral(",}"));
0193         if (valueLength == -1) {
0194             // It's the last value in the tail : take everything else
0195             valueLength = tail.length();
0196         }
0197 
0198         // Extract the value
0199         QStringView value = tail.mid(0, valueLength).trimmed();
0200         createAndSignalVariable(parentId, name, value, QStringLiteral(""), changed);
0201         tail = tail.mid(valueLength).trimmed(); // Advance after value
0202     }
0203 
0204     // Variable has been parsed, eventually parse its next sibling
0205     if (tail.startsWith(QStringLiteral(","))) {
0206         // There is a sibling
0207         tail = tail.mid(1).trimmed(); // Advance after the comma
0208         insertVariable(parentId, itemIndex + 1, tail, QStringLiteral(""), changed);
0209     }
0210 }
0211 
0212 #include "moc_gdbvariableparser.cpp"