diff --git a/debuggers/common/mivariable.h b/debuggers/common/mivariable.h --- a/debuggers/common/mivariable.h +++ b/debuggers/common/mivariable.h @@ -32,6 +32,7 @@ class CreateVarobjHandler; class FetchMoreChildrenHandler; +class SetFormatHandler; namespace KDevMI { class MIDebugSession; class MIVariable : public KDevelop::Variable @@ -61,8 +62,15 @@ protected: // Internal friend class ::CreateVarobjHandler; friend class ::FetchMoreChildrenHandler; + friend class ::SetFormatHandler; + + /** + * Construct a MIVariable child directly from a MI value + */ + MIVariable *createChild(const MI::Value &child); QString enquotedExpression() const; + virtual QString formatValue(const QString &rawValue) const; bool sessionIsAlive() const; diff --git a/debuggers/common/mivariable.cpp b/debuggers/common/mivariable.cpp --- a/debuggers/common/mivariable.cpp +++ b/debuggers/common/mivariable.cpp @@ -24,6 +24,7 @@ #include "debuglog.h" #include "midebugsession.h" #include "mi/micommand.h" +#include "stringhelpers.h" #include #include @@ -49,6 +50,24 @@ { } +MIVariable *MIVariable::createChild(const Value& child) +{ + if (!debugSession) return nullptr; + auto var = static_cast(debugSession->variableController()->createVariable(model(), this, child["exp"].literal())); + var->setTopLevel(false); + var->setVarobj(child["name"].literal()); + bool hasMore = child["numchild"].toInt() != 0 || ( child.hasField("dynamic") && child["dynamic"].toInt()!=0 ); + var->setHasMoreInitial(hasMore); + + // *this must be parent's child before we can set type and value + appendChild(var); + + var->setType(child["type"].literal()); + var->setValue(formatValue(child["value"].literal())); + var->setChanged(true); + return var; +} + MIVariable::~MIVariable() { if (!varobj_.isEmpty()) @@ -117,7 +136,7 @@ variable->setHasMore(hasMore); variable->setType(r["type"].literal()); - variable->setValue(r["value"].literal()); + variable->setValue(variable->formatValue(r["value"].literal())); hasValue = !r["value"].literal().isEmpty(); if (variable->isExpanded() && r["numchild"].toInt()) { variable->fetchMoreChildren(); @@ -188,17 +207,8 @@ QString("--all-values \"%1\"").arg(child["name"].literal()), this/*use again as handler*/); } else { - KDevelop::Variable* xvar = m_session->variableController()-> - createVariable(variable->model(), variable, - child["exp"].literal()); - MIVariable* var = static_cast(xvar); - var->setTopLevel(false); - var->setVarobj(child["name"].literal()); - bool hasMore = child["numchild"].toInt() != 0 || ( child.hasField("dynamic") && child["dynamic"].toInt()!=0 ); - var->setHasMoreInitial(hasMore); - variable->appendChild(var); - var->setType(child["type"].literal()); - var->setValue(child["value"].literal()); + variable->createChild(child); + // it's automatically appended to variable's children list } } } @@ -276,36 +286,21 @@ } } - // FIXME: the below code is essentially copy-paste from - // FetchMoreChildrenHandler. We need to introduce GDB-specific - // subclass of KDevelop::Variable that is capable of creating - // itself from MI output directly, and relay to that. if (var.hasField("new_children")) { const Value& children = var["new_children"]; - for (int i = 0; i < children.size(); ++i) { - const Value& child = children[i]; - const QString& exp = child["exp"].literal(); - - if (debugSession) { - auto xvar = debugSession->variableController()->createVariable(model(), this, exp); - auto var = static_cast(xvar); - var->setTopLevel(false); - var->setVarobj(child["name"].literal()); - bool hasMore = child["numchild"].toInt() != 0 || ( child.hasField("dynamic") && child["dynamic"].toInt()!=0 ); - var->setHasMoreInitial(hasMore); - appendChild(var); - var->setType(child["type"].literal()); - var->setValue(child["value"].literal()); - var->setChanged(true); + if (debugSession) { + for (int i = 0; i < children.size(); ++i) { + createChild(children[i]); + // it's automatically appended to this's children list } } } if (var.hasField("type_changed") && var["type_changed"].literal() == "true") { setType(var["new_type"].literal()); } - setValue(var["value"].literal()); + setValue(formatValue(var["value"].literal())); setChanged(true); setHasMore(var.hasField("has_more") && var["has_more"].toInt()); } @@ -318,10 +313,7 @@ QString MIVariable::enquotedExpression() const { - QString expr = expression(); - expr.replace('"', "\\\""); - expr = expr.prepend('"').append('"'); - return expr; + return Utils::quoteExpression(expression()); } @@ -334,8 +326,8 @@ void handle(const ResultRecord &r) override { - if(r.hasField("value")) - m_variable.data()->setValue(r["value"].literal()); + if(m_variable && r.hasField("value")) + m_variable->setValue(m_variable->formatValue(r["value"].literal())); } private: QPointer m_variable; @@ -360,3 +352,8 @@ } } } + +QString MIVariable::formatValue(const QString &rawValue) const +{ + return rawValue; +} diff --git a/debuggers/common/stringhelpers.h b/debuggers/common/stringhelpers.h --- a/debuggers/common/stringhelpers.h +++ b/debuggers/common/stringhelpers.h @@ -33,6 +33,21 @@ QString unquoteExpression(QString expr); +/** + * Qoute the string, using quoteCh + */ +QString quote(QString str, char quoteCh = '"'); + +/** + * Unquote and optionally unescape unicode escape sequence. + * Handle escape sequence + * '\\' '\\' -> '\\' + * '\\' quoteCh -> quoteCh + * '\\' 'u' 'N' 'N' 'N' 'N' -> '\uNNNN' + * '\\' 'x''N''N' -> '\xNN' + */ +QString unquote(const QString &str, bool unescapeUnicode = false, char quoteCh = '"'); + } // end of namespace Utils #endif // __STRINGHELPERS_H__ diff --git a/debuggers/common/stringhelpers.cpp b/debuggers/common/stringhelpers.cpp --- a/debuggers/common/stringhelpers.cpp +++ b/debuggers/common/stringhelpers.cpp @@ -16,10 +16,16 @@ Boston, MA 02110-1301, USA. */ +#include "stringhelpers.h" + +#include "debuglog.h" + #include #include -#include + +#include #include +#include #include namespace Utils { @@ -167,18 +173,114 @@ QString quoteExpression(QString expr) { - expr.replace('"', "\\\""); - expr = expr.prepend('"').append('"'); - return expr; + return quote(expr, '"'); } QString unquoteExpression(QString expr) { - if (expr.left(1) == QString('"') && expr.right(1) == QString('"')) { - expr = expr.mid(1, expr.length()-2); - expr.replace("\\\"", "\""); + return unquote(expr, false); +} + +QString quote(QString str, char quoteCh) +{ + str.replace("\\", "\\\\").replace(quoteCh, QStringLiteral("\\") + quoteCh); + return str.prepend(quoteCh).append(quoteCh); +} + +QString unquote(const QString &str, bool unescapeUnicode, char quoteCh) +{ + if (str.startsWith(quoteCh) && str.endsWith(quoteCh)) { + QString res; + res.reserve(str.length()); + bool esc = false; + int type = 0; + QString escSeq; + escSeq.reserve(4); + // skip begining and ending quoteCh, no need for str = str.mid(1, str.length() - 2) + for (int i = 1; i != str.length() - 1; i++) { + auto ch = str[i]; + if (esc) { + switch (ch.unicode()) { + case '\\': + if (type != 0) { + escSeq += ch; + qCDebug(DEBUGGERCOMMON) << "Unrecognized escape sequence:" << escSeq; + res += '\\'; + res += escSeq; + escSeq.clear(); + esc = false; + type = 0; + } else { + res.append('\\'); + // escSeq.clear(); // escSeq must be empty. + esc = false; + } + break; + case 'u': + case 'x': + if (type != 0 || !unescapeUnicode) { + escSeq += ch; + qCDebug(DEBUGGERCOMMON) << "Unrecognized escape sequence:" << escSeq; + res += '\\'; + res += escSeq; + escSeq.clear(); + esc = false; + type = 0; + } else { + type = ch == 'u' ? 1 : 2; + } + break; + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': + case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': + escSeq += ch; + if (type == 0) { + qCDebug(DEBUGGERCOMMON) << "Unrecognized escape sequence:" << escSeq; + res += '\\'; + res += escSeq; + escSeq.clear(); + esc = false; + type = 0; + } else { + // \uNNNN + // \xNN + if (escSeq.length() == (type == 1 ? 4 : 2)) { + // no need to handle error, we know for sure escSeq is '[0-9a-fA-F]+' + auto code = escSeq.toInt(nullptr, 16); + res += QChar(code); + escSeq.clear(); + esc = false; + type = 0; + } + } + break; + default: + if (type == 0 && ch == quoteCh) { + res += ch; + } else { + escSeq += ch; + qCDebug(DEBUGGERCOMMON) << "Unrecognized escape sequence:" << escSeq; + res += '\\'; + res += escSeq; + escSeq.clear(); + } + esc = false; + type = 0; + break; + } + } else { + if (ch == '\\') { + esc = true; + continue; + } + res += ch; + } + } + return res; + } else { + return str; } - return expr; } } // end of namespace Utils diff --git a/debuggers/gdb/unittests/test_gdb.cpp b/debuggers/gdb/unittests/test_gdb.cpp --- a/debuggers/gdb/unittests/test_gdb.cpp +++ b/debuggers/gdb/unittests/test_gdb.cpp @@ -1203,12 +1203,15 @@ TestLaunchConfiguration cfg; - const QString testString("test"); - const QString quotedTestString("\"" + testString + "\""); + // the unquoted string (the actual content): t\"t + // quoted string (what we would write as a c string): "t\\\"t" + // written in source file: R"("t\\\"t")" + const QString testString("t\\\"t"); // the actual content + const QString quotedTestString(R"("t\\\"t")"); breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(debugeeFileName), 38); QVERIFY(session->startDebugging(&cfg, m_iface)); - WAIT_FOR_STATE(session, DebugSession::PausedState); + WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); variableCollection()->watches()->add(quotedTestString); //just a constant string QTest::qWait(300); @@ -1227,7 +1230,14 @@ { COMPARE_DATA(variableCollection()->index(ind, 0, testStr), QString::number(ind)); QChar c = testString.at(ind); - QString value = QString::number(c.toLatin1()) + " '" + c + "'"; + QString value = QString::number(c.toLatin1()) + " '"; + if (c == '\\') + value += "\\\\"; + else if (c == '\'') + value += "\\'"; + else + value += c; + value += "'"; COMPARE_DATA(variableCollection()->index(ind, 1, testStr), value); } COMPARE_DATA(variableCollection()->index(len, 0, testStr), QString::number(len)); diff --git a/debuggers/lldb/controllers/variable.h b/debuggers/lldb/controllers/variable.h --- a/debuggers/lldb/controllers/variable.h +++ b/debuggers/lldb/controllers/variable.h @@ -48,6 +48,7 @@ protected: void formatChanged() override; + QString formatValue(const QString &value) const override; }; } // end of namespace LLDB diff --git a/debuggers/lldb/controllers/variable.cpp b/debuggers/lldb/controllers/variable.cpp --- a/debuggers/lldb/controllers/variable.cpp +++ b/debuggers/lldb/controllers/variable.cpp @@ -25,13 +25,16 @@ #include "debuglog.h" #include "debugsession.h" #include "mi/micommand.h" +#include "stringhelpers.h" + +#include using namespace KDevelop; using namespace KDevMI::LLDB; using namespace KDevMI::MI; LldbVariable::LldbVariable(DebugSession *session, TreeModel *model, TreeItem *parent, - const QString& expression, const QString& display) + const QString& expression, const QString& display) : MIVariable(session, model, parent, expression, display) { } @@ -73,3 +76,16 @@ } } } + +QString LldbVariable::formatValue(const QString& value) const +{ + // Data formatter emits value with unicode escape sequence for string and char, + // translate them back. + // Only check with first char is enough, as unquote will do the rest check + if (value.startsWith('"')) { + return Utils::quote(Utils::unquote(value, true)); + } else if (value.startsWith('\'')) { + return Utils::quote(Utils::unquote(value, true, '\''), '\''); + } + return value; +} diff --git a/debuggers/lldb/debugsession.h b/debuggers/lldb/debugsession.h --- a/debuggers/lldb/debugsession.h +++ b/debuggers/lldb/debugsession.h @@ -59,6 +59,8 @@ void updateAllVariables(); + void setFormatterPath(const QString &path); + public Q_SLOTS: void interruptDebugger() override; @@ -83,6 +85,8 @@ BreakpointController *m_breakpointController; VariableController *m_variableController; LldbFrameStackModel *m_frameStackModel; + + QString m_formatterPath; }; } // end of namespace GDB diff --git a/debuggers/lldb/debugsession.cpp b/debuggers/lldb/debugsession.cpp --- a/debuggers/lldb/debugsession.cpp +++ b/debuggers/lldb/debugsession.cpp @@ -147,15 +147,23 @@ return new LldbCommand(type, arguments, flags); } +void DebugSession::setFormatterPath(const QString &path) +{ + m_formatterPath = path; +} + void DebugSession::initializeDebugger() { //addCommand(MI::EnableTimings, "yes"); // load data formatter - QString fileName = QStandardPaths::locate(QStandardPaths::GenericDataLocation, - "kdevlldb/formatters/qt.py"); - if (!fileName.isEmpty()) { - addCommand(MI::NonMI, "command script import " + KShell::quoteArg(fileName)); + auto formatterPath = m_formatterPath; + if (formatterPath.isEmpty()) { + formatterPath = QStandardPaths::locate(QStandardPaths::GenericDataLocation, + "kdevlldb/formatters/qt.py"); + } + if (!formatterPath.isEmpty()) { + addCommand(MI::NonMI, "command script import " + KShell::quoteArg(formatterPath)); } // set a larger term width. diff --git a/debuggers/lldb/formatters/helpers.py b/debuggers/lldb/formatters/helpers.py --- a/debuggers/lldb/formatters/helpers.py +++ b/debuggers/lldb/formatters/helpers.py @@ -41,6 +41,28 @@ unichr = unichr # END +def quote(string, quote='"'): + """Quote a string so it's suitable to be used in quote""" + return '{q}{s}{q}'.format(s = string.replace('\\', '\\\\').replace(quote, '\\' + quote), + q = quote) + +def unquote(string, quote='"'): + """Unquote a string""" + if string.startswith(quote) and string.endswith(quote): + string = string.lstrip(quote).rstrip(quote) + ls = [] + esc = False + for idx in range(0, len(string)): + ch = string[idx] + if ch == '\\': + if esc: + ls.append(ch) + esc = not esc + else: + ls.append(ch) + string = ''.join(ls) + return string + class SummaryProvider(object): """A lldb synthetic provider that defaults return real children of the value""" def __init__(self, valobj, internal_dict): diff --git a/debuggers/lldb/formatters/qt.py b/debuggers/lldb/formatters/qt.py --- a/debuggers/lldb/formatters/qt.py +++ b/debuggers/lldb/formatters/qt.py @@ -71,7 +71,7 @@ debugger.HandleCommand('type category enable kdevelop-qt') -def dataForQString(valobj): +def printableQString(valobj): pointer = 0 length = 0 if valobj.IsValid(): @@ -94,32 +94,34 @@ # size in the number of chars, each char is 2 bytes in UTF16 length = size.GetValueAsUnsigned(0) * 2 - return (pointer, length) + error = lldb.SBError() + string_data = valobj.process.ReadMemory(pointer, length, error) + # The QString object might be not yet initialized. In this case size is a bogus value, + # and memory access may fail + if error.Success(): + string = string_data.decode('utf-16').__repr__().lstrip("u'").rstrip("'") + return string, pointer, length + return None, 0, 0 def QStringSummaryProvider(valobj, internal_dict): if valobj.IsValid(): - content = valobj.GetChildMemberWithName('content') + content = valobj.GetChildMemberWithName('(content)') if content.IsValid(): - return valobj.GetChildMemberWithName('content').GetSummary() + return content.GetSummary() else: # No synthetic provider installed, get the content by ourselves - dataPointer, byteLength = dataForQString(valobj) - error = lldb.SBError() - string_data = valobj.process.ReadMemory(dataPointer, byteLength, error) - # The QString object might be not yet initialized. In this case size is a bogus value, - # and memory access may fail - if error.Success(): - return '"{}"'.format(string_data.decode('utf-16').encode('utf-8').replace('"', r'\"')) + printable, _, _ = printableQString(valobj) + if printable is not None: + return quote(printable) return None class QStringFormatter(object): """A lldb synthetic provider for QString""" def __init__(self, valobj, internal_dict): self.valobj = valobj - self._size_member = None self._content_member = None self._members = [] self._num_children = 0 @@ -134,68 +136,54 @@ return self._num_children def get_child_index(self, name): - if name == 'size': - return 0 - elif name == 'content': - return 1 + if name == '(content)': + return self._num_children else: try: - return int(name.lstrip('[').rstrip(']')) + 2 + return int(name.lstrip('[').rstrip(']')) except Exception: return None def get_child_at_index(self, idx): - if idx < 0 or idx >= self._num_children: + if idx < 0 or idx > self._num_children: return None - if idx == 0: - return self._size_member - elif idx == 1: + if idx == self._num_children: return self._content_member else: - return self._members[idx - 2] + return self._members[idx] def update(self): - self._num_children = 2 + self._num_children = 0 self._members = [] if not self.valobj.IsValid(): - self._size_member = self.valobj.CreateValueFromExpression('size', '(size_t) 0') self._content_member = lldb.SBValue() return - dataPointer, byteLength = dataForQString(self.valobj) + printable, dataPointer, byteLength = printableQString(self.valobj) strLength = byteLength / 2 - self._num_children += strLength - self._size_member = self.valobj.CreateValueFromExpression('size', - '(size_t) {}'.format(strLength)) - - error = lldb.SBError() - string_data = self.valobj.process.ReadMemory(dataPointer, byteLength, error) - # The QString object might be not yet initialized. In this case size is a bogus value, - # and memory access may fail - if error.Success(): - string = string_data.decode('utf-16').encode('utf-8') + self._num_children = strLength + if printable is not None: for idx in range(0, strLength): var = self.valobj.CreateValueFromAddress('[{}]'.format(idx), dataPointer + idx * self._qchar_size, self._qchar_type) self._members.append(var) - self._content_member = self.valobj.CreateValueFromExpression('content', - '(const char*) "{}"'.format(string.replace('"', r'\"'))) + + self._content_member = self.valobj.CreateValueFromExpression('(content)', + '(const char*) {}'.format(quote(printable))) else: self._content_member = lldb.SBValue() def QCharSummaryProvider(valobj, internal_dict): if valobj.IsValid(): ucs = valobj.GetChildMemberWithName('ucs').GetValueAsUnsigned(0) - return u"'{}'".format(unichr(ucs)).encode('utf-8') - return '' + return unichr(ucs).__repr__().lstrip('u') + return None -def dataForQByteArray(valobj): - pointer = 0 - length = 0 +def printableQByteArray(valobj): if valobj.IsValid(): d = valobj.GetChildMemberWithName('d') data = d.GetChildMemberWithName('data') @@ -211,27 +199,37 @@ pointer = d.GetValueAsUnsigned(0) + 24 # Fallback to hardcoded value length = size.GetValueAsUnsigned(0) - return (pointer, length) + error = lldb.SBError() + string_data = valobj.process.ReadMemory(pointer, length, error) + # The object might be not yet initialized. In this case size is a bogus value, + # and memory access may fail + if error.Success(): + # replace non-ascii byte with a space and get a printable version + ls = list(string_data) + for idx in range(0, length): + byte = ord(ls[idx]) + if byte >= 128 or byte < 0: + ls[idx] = hex(byte).replace('0', '\\', 1) + elif byte == 0: # specical handle for 0, as hex(0) returns '\\x0' + ls[idx] = '\\x00' + string = ''.join(ls) + return string, pointer, length + return None, 0, 0 def QByteArraySummaryProvider(valobj, internal_dict): if valobj.IsValid(): - content = valobj.GetChildMemberWithName('content') + content = valobj.GetChildMemberWithName('(content)') if content.IsValid(): - return valobj.GetChildMemberWithName('content').GetSummary() + return content.GetSummary() else: - # No synthetic provider installed, get the content by ourselves - dataPointer, byteLength = dataForQByteArray(self.valobj) - error = lldb.SBError() - string_data = self.valobj.process.ReadMemory(dataPointer, byteLength, error) - # The object might be not yet initialized. In this case size is a bogus value, - # and memory access may fail - if error.Success(): - # replace non-ascii byte with a space and get a printable version - string = ''.join([i if ord(i) < 128 else ' ' for i in string_data]) - return '"{}"'.format(string) + # Our synthetic provider is not installed, get the content by ourselves + printable, _, _ = printableQByteArray(valobj) + if printable is not None: + return quote(printable) return None + class QByteArrayFormatter(object): """A lldb synthetic provider for QByteArray""" @@ -251,55 +249,45 @@ return self._num_children def get_child_index(self, name): - if name == 'size': - return 0 - elif name == 'content': - return 1 + # 'content' is a hidden child, since lldb won't ask for a child at + # index >= self._num_children. But we know that index == self._num_children + # is indeed valid, so we can use it in SummaryProvider, to reduce + # another fetch from the inferior + if name == '(content)': + return self._num_children else: try: - return int(name.lstrip('[').rstrip(']')) + 2 + return int(name.lstrip('[').rstrip(']')) except Exception: return None def get_child_at_index(self, idx): - if idx < 0 or idx >= self._num_children: + if idx < 0 or idx > self._num_children: return None - if idx == 0: - return self._size_member - elif idx == 1: + # see comments in self.get_child_index + if idx == self._num_children: return self._content_member else: - return self._members[idx - 2] + return self._members[idx] def update(self): - self._num_children = 2 + self._num_children = 0 self._members = [] if not self.valobj.IsValid(): - self._size_member = self.valobj.CreateValueFromExpression('size', '(size_t) 0') self._content_member = lldb.SBValue() return - dataPointer, byteLength = dataForQByteArray(self.valobj) - strLength = byteLength - self._num_children += strLength - self._size_member = self.valobj.CreateValueFromExpression('size', - '(size_t) {}'.format(strLength)) + printable, dataPointer, byteLength = printableQByteArray(self.valobj) + self._num_children = byteLength - error = lldb.SBError() - string_data = self.valobj.process.ReadMemory(dataPointer, byteLength, error) - # The object might be not yet initialized. In this case size is a bogus value, - # and memory access may fail - if error.Success(): - # replace non-ascii byte with a space and get a printable version - string = ''.join([i if ord(i) < 128 else ' ' for i in string_data]) - - for idx in range(0, strLength): + if printable is not None: + self._content_member = self.valobj.CreateValueFromExpression('(content)', + '(const char*) {}'.format(quote(printable))) + for idx in range(0, self._num_children): var = self.valobj.CreateValueFromAddress('[{}]'.format(idx), dataPointer + idx * self._char_size, self._char_type) self._members.append(var) - self._content_member = self.valobj.CreateValueFromExpression('content', - '(const char*) "{}"'.format(string.replace('"', r'\"'))) else: self._content_member = lldb.SBValue() @@ -877,4 +865,4 @@ """lldb synthethic provider for QHash""" def __init__(self, valobj, internal_dict): - super(QMultiHashFormatter, self).__init__(valobj.GetChildAtIndex(0), internal_dict) \ No newline at end of file + super(QMultiHashFormatter, self).__init__(valobj.GetChildAtIndex(0), internal_dict) diff --git a/debuggers/lldb/unittests/debugees/debugeeqt.cpp b/debuggers/lldb/unittests/debugees/debugeeqt.cpp --- a/debuggers/lldb/unittests/debugees/debugeeqt.cpp +++ b/debuggers/lldb/unittests/debugees/debugeeqt.cpp @@ -27,5 +27,6 @@ x += QString::number(i); qDebug() << x; } + QString ustr = QString::fromUtf8("\u4f60\u597d\u4e16\u754c"); return 0; } diff --git a/debuggers/lldb/unittests/test_lldb.h b/debuggers/lldb/unittests/test_lldb.h --- a/debuggers/lldb/unittests/test_lldb.h +++ b/debuggers/lldb/unittests/test_lldb.h @@ -100,6 +100,7 @@ void testVariablesStartSecondSession(); void testVariablesSwitchFrame(); void testVariablesQuicklySwitchFrame(); + void testVariablesNonascii(); void testSwitchFrameLldbConsole(); void testSegfaultDebugee(); @@ -120,7 +121,7 @@ KDevelop::VariableCollection *variableCollection(); KDevelop::Variable *watchVariableAt(int i); - KDevelop::Variable *localVariableAt(int i); + QModelIndex localVariableIndexAt(int i, int col = 0); private: KDevelop::TestCore *m_core; diff --git a/debuggers/lldb/unittests/test_lldb.cpp b/debuggers/lldb/unittests/test_lldb.cpp --- a/debuggers/lldb/unittests/test_lldb.cpp +++ b/debuggers/lldb/unittests/test_lldb.cpp @@ -44,6 +44,7 @@ #include #include +#include #include #include #include @@ -149,6 +150,7 @@ { setSourceInitFile(false); m_frameStackModel = new TestFrameStackModel(this); + setFormatterPath(findSourceFile("../formatters/qt.py")); KDevelop::ICore::self()->debugController()->addSession(this); } @@ -181,11 +183,10 @@ return dynamic_cast(variableCollection()->itemForIndex(idx)); } -Variable *LldbTest::localVariableAt(int i) +QModelIndex LldbTest::localVariableIndexAt(int i, int col) { auto localRoot = variableCollection()->indexForItem(variableCollection()->locals(), 0); - auto idx = variableCollection()->index(i, 0, localRoot); - return dynamic_cast(variableCollection()->itemForIndex(idx)); + return variableCollection()->index(i, col, localRoot); } // Called before the first testfunction is executed @@ -1461,8 +1462,11 @@ session->variableController()->setAutoUpdate(KDevelop::IVariableController::UpdateWatches); - const QString testString("test"); - const QString quotedTestString("\"" + testString + "\""); + // the unquoted string (the actual content): t\"t + // quoted string (what we would write as a c string): "t\\\"t" + // written in source file: R"("t\\\"t")" + const QString testString("t\\\"t"); // the actual content + const QString quotedTestString(R"("t\\\"t")"); breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(m_debugeeFileName), 38); QVERIFY(session->startDebugging(&cfg, m_iface)); @@ -1665,6 +1669,26 @@ WAIT_FOR_STATE(session, DebugSession::EndedState); } +void LldbTest::testVariablesNonascii() +{ + TestDebugSession *session = new TestDebugSession; + TestLaunchConfiguration cfg(findExecutable("lldb_debugeeqt")); + + session->variableController()->setAutoUpdate(KDevelop::IVariableController::UpdateLocals); + + QString fileName = findSourceFile("debugeeqt.cpp"); + breakpoints()->addCodeBreakpoint(QUrl::fromLocalFile(fileName), 30); + + QVERIFY(session->startDebugging(&cfg, m_iface)); + WAIT_FOR_STATE_AND_IDLE(session, DebugSession::PausedState); + + QCOMPARE(session->currentLine(), 30); + COMPARE_DATA(localVariableIndexAt(0, 1), QString("\"\u4f60\u597d\u4e16\u754c\"")); + + session->run(); + WAIT_FOR_STATE(session, DebugSession::EndedState); +} + void LldbTest::testSwitchFrameLldbConsole() { TestDebugSession *session = new TestDebugSession;