Files
qt-creator/src/plugins/debugger/watchutils.cpp
Erik Verbruggen c2393df023 Introduced a token cache for the C++ editor.
This should speed things up a bit, because before, the line was tokenized at
least 3 times.
2010-06-04 09:36:05 +02:00

1712 lines
60 KiB
C++

/**************************************************************************
**
** This file is part of Qt Creator
**
** Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies).
**
** Contact: Nokia Corporation (qt-info@nokia.com)
**
** Commercial Usage
**
** Licensees holding valid Qt Commercial licenses may use this file in
** accordance with the Qt Commercial License Agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and Nokia.
**
** GNU Lesser General Public License Usage
**
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 2.1 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 2.1 requirements
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** If you are unsure which license is appropriate for your use, please
** contact the sales department at http://qt.nokia.com/contact.
**
**************************************************************************/
#include "watchutils.h"
#include "watchdata.h"
#include "debuggerstringutils.h"
#include "gdb/gdbmi.h"
#include <utils/qtcassert.h>
#include <coreplugin/ifile.h>
#include <texteditor/basetexteditor.h>
#include <texteditor/basetextmark.h>
#include <texteditor/itexteditor.h>
#include <texteditor/texteditorconstants.h>
#include <cpptools/cppmodelmanagerinterface.h>
#include <cpptools/cpptoolsconstants.h>
#include <cplusplus/ExpressionUnderCursor.h>
#include <cplusplus/Overview.h>
#include <Symbols.h>
#include <Scope.h>
#include <extensionsystem/pluginmanager.h>
#include <QtCore/QDebug>
#include <QtCore/QTime>
#include <QtCore/QStringList>
#include <QtCore/QCoreApplication>
#include <QtCore/QTextStream>
#include <QtCore/QHash>
#include <QtGui/QTextCursor>
#include <QtGui/QPlainTextEdit>
#include <string.h>
#include <ctype.h>
enum { debug = 0 };
// Debug helpers for code model. @todo: Move to some CppTools library?
namespace CPlusPlus {
static void debugCppSymbolRecursion(QTextStream &str, const Overview &o,
const Symbol &s, bool doRecurse = true,
int recursion = 0)
{
for (int i = 0; i < recursion; i++)
str << " ";
str << "Symbol: " << o.prettyName(s.name()) << " at line " << s.line();
if (s.isFunction())
str << " function";
if (s.isClass())
str << " class";
if (s.isDeclaration())
str << " declaration";
if (s.isBlock())
str << " block";
if (doRecurse && s.isScopedSymbol()) {
const ScopedSymbol *scoped = s.asScopedSymbol();
const int size = scoped->memberCount();
str << " scoped symbol of " << size << '\n';
for (int m = 0; m < size; m++)
debugCppSymbolRecursion(str, o, *scoped->memberAt(m), true, recursion + 1);
} else {
str << '\n';
}
}
QDebug operator<<(QDebug d, const Symbol &s)
{
QString output;
CPlusPlus::Overview o;
QTextStream str(&output);
debugCppSymbolRecursion(str, o, s, true, 0);
d.nospace() << output;
return d;
}
QDebug operator<<(QDebug d, const Scope &scope)
{
QString output;
Overview o;
QTextStream str(&output);
const int size = scope.symbolCount();
str << "Scope of " << size;
if (scope.isNamespaceScope())
str << " namespace";
if (scope.isClassScope())
str << " class";
if (scope.isEnumScope())
str << " enum";
if (scope.isBlockScope())
str << " block";
if (scope.isFunctionScope())
str << " function";
if (scope.isPrototypeScope())
str << " prototype";
if (const Symbol *owner = scope.owner()) {
str << " owner: ";
debugCppSymbolRecursion(str, o, *owner, false, 0);
} else {
str << " 0-owner\n";
}
for (int s = 0; s < size; s++)
debugCppSymbolRecursion(str, o, *scope.symbolAt(s), true, 2);
d.nospace() << output;
return d;
}
} // namespace CPlusPlus
namespace Debugger {
namespace Internal {
QByteArray dotEscape(QByteArray str)
{
str.replace(' ', '.');
str.replace('\\', '.');
str.replace('/', '.');
return str;
}
QString currentTime()
{
return QTime::currentTime().toString(QLatin1String("hh:mm:ss.zzz"));
}
bool isSkippableFunction(const QString &funcName, const QString &fileName)
{
if (fileName.endsWith(QLatin1String("kernel/qobject.cpp")))
return true;
if (fileName.endsWith(QLatin1String("kernel/moc_qobject.cpp")))
return true;
if (fileName.endsWith(QLatin1String("kernel/qmetaobject.cpp")))
return true;
if (fileName.endsWith(QLatin1String("kernel/qmetaobject_p.h")))
return true;
if (fileName.endsWith(QLatin1String(".moc")))
return true;
if (funcName.endsWith(QLatin1String("::qt_metacall")))
return true;
if (funcName.endsWith(QLatin1String("::d_func")))
return true;
if (funcName.endsWith(QLatin1String("::q_func")))
return true;
return false;
}
bool isLeavableFunction(const QString &funcName, const QString &fileName)
{
if (funcName.endsWith(QLatin1String("QObjectPrivate::setCurrentSender")))
return true;
if (funcName.endsWith(QLatin1String("QMutexPool::get")))
return true;
if (fileName.endsWith(QLatin1String("kernel/qmetaobject.cpp"))
&& funcName.endsWith(QLatin1String("QMetaObject::methodOffset")))
return true;
if (fileName.endsWith(QLatin1String("kernel/qobject.h")))
return true;
if (fileName.endsWith(QLatin1String("kernel/qobject.cpp"))
&& funcName.endsWith(QLatin1String("QObjectConnectionListVector::at")))
return true;
if (fileName.endsWith(QLatin1String("kernel/qobject.cpp"))
&& funcName.endsWith(QLatin1String("~QObject")))
return true;
if (fileName.endsWith(QLatin1String("thread/qmutex.cpp")))
return true;
if (fileName.endsWith(QLatin1String("thread/qthread.cpp")))
return true;
if (fileName.endsWith(QLatin1String("thread/qthread_unix.cpp")))
return true;
if (fileName.endsWith(QLatin1String("thread/qmutex.h")))
return true;
if (fileName.contains(QLatin1String("thread/qbasicatomic")))
return true;
if (fileName.contains(QLatin1String("thread/qorderedmutexlocker_p")))
return true;
if (fileName.contains(QLatin1String("arch/qatomic")))
return true;
if (fileName.endsWith(QLatin1String("tools/qvector.h")))
return true;
if (fileName.endsWith(QLatin1String("tools/qlist.h")))
return true;
if (fileName.endsWith(QLatin1String("tools/qhash.h")))
return true;
if (fileName.endsWith(QLatin1String("tools/qmap.h")))
return true;
if (fileName.endsWith(QLatin1String("tools/qshareddata.h")))
return true;
if (fileName.endsWith(QLatin1String("tools/qstring.h")))
return true;
if (fileName.endsWith(QLatin1String("global/qglobal.h")))
return true;
return false;
}
bool hasLetterOrNumber(const QString &exp)
{
const QChar underscore = QLatin1Char('_');
for (int i = exp.size(); --i >= 0; )
if (exp.at(i).isLetterOrNumber() || exp.at(i) == underscore)
return true;
return false;
}
bool hasSideEffects(const QString &exp)
{
// FIXME: complete?
return exp.contains(QLatin1String("-="))
|| exp.contains(QLatin1String("+="))
|| exp.contains(QLatin1String("/="))
|| exp.contains(QLatin1String("%="))
|| exp.contains(QLatin1String("*="))
|| exp.contains(QLatin1String("&="))
|| exp.contains(QLatin1String("|="))
|| exp.contains(QLatin1String("^="))
|| exp.contains(QLatin1String("--"))
|| exp.contains(QLatin1String("++"));
}
bool isKeyWord(const QString &exp)
{
// FIXME: incomplete
return exp == QLatin1String("class")
|| exp == QLatin1String("const")
|| exp == QLatin1String("do")
|| exp == QLatin1String("if")
|| exp == QLatin1String("return")
|| exp == QLatin1String("struct")
|| exp == QLatin1String("template")
|| exp == QLatin1String("void")
|| exp == QLatin1String("volatile")
|| exp == QLatin1String("while");
}
bool isPointerType(const QString &type)
{
return type.endsWith(QLatin1Char('*')) || type.endsWith(QLatin1String("* const"));
}
bool isCharPointerType(const QString &type)
{
return type == QLatin1String("char *")
|| type == QLatin1String("const char *")
|| type == QLatin1String("char const *");
}
bool startsWithDigit(const QString &str)
{
return !str.isEmpty() && str.at(0).isDigit();
}
QString stripPointerType(QString type)
{
if (type.endsWith(QLatin1Char('*')))
type.chop(1);
if (type.endsWith(QLatin1String("* const")))
type.chop(7);
if (type.endsWith(QLatin1Char(' ')))
type.chop(1);
return type;
}
/* getUninitializedVariables(): Get variables that are not initialized
* at a certain line of a function from the code model to be able to
* indicate them as not in scope in the locals view.
* Find document + function in the code model, do a double check and
* collect declarative symbols that are in the function past or on
* the current line. blockRecursion() recurses up the scopes
* and collect symbols declared past or on the current line.
* Recursion goes up from the innermost scope, keeping a map
* of occurrences seen, to be able to derive the names of
* shadowed variables as the debugger sees them:
\code
int x; // Occurrence (1), should be reported as "x <shadowed 1>"
if (true) {
int x = 5; (2) // Occurrence (2), should be reported as "x"
}
\endcode
*/
typedef QHash<QString, int> SeenHash;
static void blockRecursion(const CPlusPlus::Overview &overview,
const CPlusPlus::Scope *scope,
unsigned line,
QStringList *uninitializedVariables,
SeenHash *seenHash,
int level = 0)
{
const int size = scope->symbolCount();
for (int s = 0; s < size; s++){
const CPlusPlus::Symbol *symbol = scope->symbolAt(s);
if (symbol->isDeclaration()) {
// Find out about shadowed symbols by bookkeeping
// the already seen occurrences in a hash.
const QString name = overview.prettyName(symbol->name());
SeenHash::iterator it = seenHash->find(name);
if (it == seenHash->end()) {
it = seenHash->insert(name, 0);
} else {
++(it.value());
}
// Is the declaration on or past the current line, that is,
// the variable not initialized.
if (symbol->line() >= line)
uninitializedVariables->push_back(WatchData::shadowedName(name, it.value()));
}
}
// Next block scope.
if (const CPlusPlus::Scope *enclosingScope = scope->enclosingBlockScope())
blockRecursion(overview, enclosingScope, line, uninitializedVariables, seenHash, level + 1);
}
// Inline helper with integer error return codes.
static inline
int getUninitializedVariablesI(const CPlusPlus::Snapshot &snapshot,
const QString &functionName,
const QString &file,
int line,
QStringList *uninitializedVariables)
{
uninitializedVariables->clear();
// Find document
if (snapshot.isEmpty() || functionName.isEmpty() || file.isEmpty() || line < 1)
return 1;
const CPlusPlus::Snapshot::const_iterator docIt = snapshot.find(file);
if (docIt == snapshot.end())
return 2;
const CPlusPlus::Document::Ptr doc = docIt.value();
// Look at symbol at line and find its function. Either it is the
// function itself or some expression/variable.
const CPlusPlus::Symbol *symbolAtLine = doc->lastVisibleSymbolAt(line, 0);
if (!symbolAtLine)
return 4;
// First figure out the function to do a safety name check
// and the innermost scope at cursor position
const CPlusPlus::Function *function = 0;
const CPlusPlus::Scope *innerMostScope = 0;
if (symbolAtLine->isFunction()) {
function = symbolAtLine->asFunction();
if (function->memberCount() == 1) // Skip over function block
if (CPlusPlus::Block *block = function->memberAt(0)->asBlock())
innerMostScope = block->members();
} else {
if (const CPlusPlus::Scope *functionScope = symbolAtLine->enclosingFunctionScope()) {
function = functionScope->owner()->asFunction();
innerMostScope = symbolAtLine->isBlock() ?
symbolAtLine->asBlock()->members() :
symbolAtLine->enclosingBlockScope();
}
}
if (!function || !innerMostScope)
return 7;
// Compare function names with a bit off fuzz,
// skipping modules from a CDB symbol "lib!foo" or namespaces
// that the code model does not show at this point
CPlusPlus::Overview overview;
const QString name = overview.prettyName(function->name());
if (!functionName.endsWith(name))
return 11;
if (functionName.size() > name.size()) {
const char previousChar = functionName.at(functionName.size() - name.size() - 1).toLatin1();
if (previousChar != ':' && previousChar != '!' )
return 11;
}
// Starting from the innermost block scope, collect declarations.
SeenHash seenHash;
blockRecursion(overview, innerMostScope, line, uninitializedVariables, &seenHash);
return 0;
}
bool getUninitializedVariables(const CPlusPlus::Snapshot &snapshot,
const QString &function,
const QString &file,
int line,
QStringList *uninitializedVariables)
{
const int rc = getUninitializedVariablesI(snapshot, function, file, line, uninitializedVariables);
if (debug) {
QString msg;
QTextStream str(&msg);
str << "getUninitializedVariables() " << function << ' ' << file << ':' << line
<< " returns (int) " << rc << " '"
<< uninitializedVariables->join(QString(QLatin1Char(','))) << '\'';
if (rc)
str << " of " << snapshot.size() << " documents";
qDebug() << msg;
}
return rc == 0;
}
QString gdbQuoteTypes(const QString &type)
{
// gdb does not understand sizeof(Core::IFile*).
// "sizeof('Core::IFile*')" is also not acceptable,
// it needs to be "sizeof('Core::IFile'*)"
//
// We never will have a perfect solution here (even if we had a full blown
// C++ parser as we do not have information on what is a type and what is
// a variable name. So "a<b>::c" could either be two comparisons of values
// 'a', 'b' and '::c', or a nested type 'c' in a template 'a<b>'. We
// assume here it is the latter.
//return type;
// (*('myns::QPointer<myns::QObject>*'*)0x684060)" is not acceptable
// (*('myns::QPointer<myns::QObject>'**)0x684060)" is acceptable
if (isPointerType(type))
return gdbQuoteTypes(stripPointerType(type)) + QLatin1Char('*');
QString accu;
QString result;
int templateLevel = 0;
const QChar colon = QLatin1Char(':');
const QChar singleQuote = QLatin1Char('\'');
const QChar lessThan = QLatin1Char('<');
const QChar greaterThan = QLatin1Char('>');
for (int i = 0; i != type.size(); ++i) {
const QChar c = type.at(i);
if (c.isLetterOrNumber() || c == QLatin1Char('_') || c == colon || c == QLatin1Char(' ')) {
accu += c;
} else if (c == lessThan) {
++templateLevel;
accu += c;
} else if (c == greaterThan) {
--templateLevel;
accu += c;
} else if (templateLevel > 0) {
accu += c;
} else {
if (accu.contains(colon) || accu.contains(lessThan))
result += singleQuote + accu + singleQuote;
else
result += accu;
accu.clear();
result += c;
}
}
if (accu.contains(colon) || accu.contains(lessThan))
result += singleQuote + accu + singleQuote;
else
result += accu;
//qDebug() << "GDB_QUOTING" << type << " TO " << result;
return result;
}
bool extractTemplate(const QString &type, QString *tmplate, QString *inner)
{
// Input "Template<Inner1,Inner2,...>::Foo" will return "Template::Foo" in
// 'tmplate' and "Inner1@Inner2@..." etc in 'inner'. Result indicates
// whether parsing was successful
// Gdb inserts a blank after each comma which we would like to avoid
tmplate->clear();
inner->clear();
if (!type.contains(QLatin1Char('<')))
return false;
int level = 0;
bool skipSpace = false;
const int size = type.size();
for (int i = 0; i != size; ++i) {
const QChar c = type.at(i);
const char asciiChar = c.toAscii();
switch (asciiChar) {
case '<':
*(level == 0 ? tmplate : inner) += c;
++level;
break;
case '>':
--level;
*(level == 0 ? tmplate : inner) += c;
break;
case ',':
*inner += (level == 1) ? QLatin1Char('@') : QLatin1Char(',');
skipSpace = true;
break;
default:
if (!skipSpace || asciiChar != ' ') {
*(level == 0 ? tmplate : inner) += c;
skipSpace = false;
}
break;
}
}
*tmplate = tmplate->trimmed();
*tmplate = tmplate->remove(QLatin1String("<>"));
*inner = inner->trimmed();
// qDebug() << "EXTRACT TEMPLATE: " << *tmplate << *inner << " FROM " << type;
return !inner->isEmpty();
}
QString extractTypeFromPTypeOutput(const QString &str)
{
int pos0 = str.indexOf(QLatin1Char('='));
int pos1 = str.indexOf(QLatin1Char('{'));
int pos2 = str.lastIndexOf(QLatin1Char('}'));
QString res = str;
if (pos0 != -1 && pos1 != -1 && pos2 != -1)
res = str.mid(pos0 + 2, pos1 - 1 - pos0)
+ QLatin1String(" ... ") + str.right(str.size() - pos2);
return res.simplified();
}
bool isIntType(const QString &type)
{
static const QStringList types = QStringList()
<< QLatin1String("char") << QLatin1String("int") << QLatin1String("short")
<< QLatin1String("long") << QLatin1String("bool")
<< QLatin1String("signed char") << QLatin1String("unsigned")
<< QLatin1String("unsigned char") << QLatin1String("unsigned long")
<< QLatin1String("short") << QLatin1String("unsigned short")
<< QLatin1String("long long") << QLatin1String("unsigned long long")
<< QLatin1String("qint16") << QLatin1String("quint16")
<< QLatin1String("qint32") << QLatin1String("quint32")
<< QLatin1String("qint64") << QLatin1String("quint64");
return type.endsWith(QLatin1String(" int"))
|| type.endsWith(QLatin1String(" int64"))
|| types.contains(type);
}
bool isSymbianIntType(const QString &type)
{
static const QStringList types = QStringList()
<< QLatin1String("TInt") << QLatin1String("TBool");
return types.contains(type);
}
bool isIntOrFloatType(const QString &type)
{
static const QStringList types = QStringList()
<< QLatin1String("float") << QLatin1String("double");
return isIntType(type) || types.contains(type);
}
GuessChildrenResult guessChildren(const QString &type)
{
if (isIntOrFloatType(type))
return HasNoChildren;
if (isCharPointerType(type))
return HasNoChildren;
if (isPointerType(type))
return HasChildren;
if (type.endsWith(QLatin1String("QString")))
return HasNoChildren;
return HasPossiblyChildren;
}
QString sizeofTypeExpression(const QString &type, QtDumperHelper::Debugger debugger)
{
if (type.endsWith(QLatin1Char('*')))
return QLatin1String("sizeof(void*)");
if (debugger != QtDumperHelper::GdbDebugger || type.endsWith(QLatin1Char('>')))
return QLatin1String("sizeof(") + type + QLatin1Char(')');
return QLatin1String("sizeof(") + gdbQuoteTypes(type) + QLatin1Char(')');
}
// Utilities to decode string data returned by the dumper helpers.
QString quoteUnprintableLatin1(const QByteArray &ba)
{
QString res;
char buf[10];
for (int i = 0, n = ba.size(); i != n; ++i) {
const unsigned char c = ba.at(i);
if (isprint(c)) {
res += c;
} else {
qsnprintf(buf, sizeof(buf) - 1, "\\%x", int(c));
res += buf;
}
}
return res;
}
QString decodeData(const QByteArray &ba, int encoding)
{
switch (encoding) {
case 0: // unencoded 8 bit data
return quoteUnprintableLatin1(ba);
case 1: { // base64 encoded 8 bit data, used for QByteArray
const QChar doubleQuote(QLatin1Char('"'));
QString rc = doubleQuote;
rc += quoteUnprintableLatin1(QByteArray::fromBase64(ba));
rc += doubleQuote;
return rc;
}
case 2: { // base64 encoded 16 bit data, used for QString
const QChar doubleQuote(QLatin1Char('"'));
const QByteArray decodedBa = QByteArray::fromBase64(ba);
QString rc = doubleQuote;
rc += QString::fromUtf16(reinterpret_cast<const ushort *>
(decodedBa.data()), decodedBa.size() / 2);
rc += doubleQuote;
return rc;
}
case 3: { // base64 encoded 32 bit data
const QByteArray decodedBa = QByteArray::fromBase64(ba);
const QChar doubleQuote(QLatin1Char('"'));
QString rc = doubleQuote;
rc += QString::fromUcs4(reinterpret_cast<const uint *>
(decodedBa.data()), decodedBa.size() / 4);
rc += doubleQuote;
return rc;
}
case 4: { // base64 encoded 16 bit data, without quotes (see 2)
const QByteArray decodedBa = QByteArray::fromBase64(ba);
return QString::fromUtf16(reinterpret_cast<const ushort *>
(decodedBa.data()), decodedBa.size() / 2);
}
case 5: { // base64 encoded 8 bit data, without quotes (see 1)
return quoteUnprintableLatin1(QByteArray::fromBase64(ba));
}
case 6: { // %02x encoded 8 bit Latin1 data
const QChar doubleQuote(QLatin1Char('"'));
const QByteArray decodedBa = QByteArray::fromHex(ba);
//qDebug() << quoteUnprintableLatin1(decodedBa) << "\n\n";
return doubleQuote + QString::fromLatin1(decodedBa) + doubleQuote;
}
case 7: { // %04x encoded 16 bit data, Little Endian
const QChar doubleQuote(QLatin1Char('"'));
const QByteArray decodedBa = QByteArray::fromHex(ba);
//qDebug() << quoteUnprintableLatin1(decodedBa) << "\n\n";
return doubleQuote + QString::fromUtf16(reinterpret_cast<const ushort *>
(decodedBa.data()), decodedBa.size() / 2) + doubleQuote;
}
case 8: { // %08x encoded 32 bit data, Little Endian
const QChar doubleQuote(QLatin1Char('"'));
const QByteArray decodedBa = QByteArray::fromHex(ba);
//qDebug() << quoteUnprintableLatin1(decodedBa) << "\n\n";
return doubleQuote + QString::fromUcs4(reinterpret_cast<const uint *>
(decodedBa.data()), decodedBa.size() / 4) + doubleQuote;
}
case 9: { // %02x encoded 8 bit Utf-8 data
const QChar doubleQuote(QLatin1Char('"'));
const QByteArray decodedBa = QByteArray::fromHex(ba);
//qDebug() << quoteUnprintableLatin1(decodedBa) << "\n\n";
return doubleQuote + QString::fromUtf8(decodedBa) + doubleQuote;
}
case 10: { // %08x encoded 32 bit data, Big Endian
const QChar doubleQuote(QLatin1Char('"'));
QByteArray decodedBa = QByteArray::fromHex(ba);
for (int i = 0; i < decodedBa.size(); i += 4) {
char c = decodedBa.at(i);
decodedBa[i] = decodedBa.at(i + 3);
decodedBa[i + 3] = c;
c = decodedBa.at(i + 1);
decodedBa[i + 1] = decodedBa.at(i + 2);
decodedBa[i + 2] = c;
}
//qDebug() << quoteUnprintableLatin1(decodedBa) << "\n\n";
return doubleQuote + QString::fromUcs4(reinterpret_cast<const uint *>
(decodedBa.data()), decodedBa.size() / 4) + doubleQuote;
}
case 11: { // %04x encoded 16 bit data, Big Endian
const QChar doubleQuote(QLatin1Char('"'));
QByteArray decodedBa = QByteArray::fromHex(ba);
for (int i = 0; i < decodedBa.size(); i += 2) {
char c = decodedBa.at(i);
decodedBa[i] = decodedBa.at(i + 1);
decodedBa[i + 1] = c;
}
//qDebug() << quoteUnprintableLatin1(decodedBa) << "\n\n";
return doubleQuote + QString::fromUcs4(reinterpret_cast<const uint *>
(decodedBa.data()), decodedBa.size() / 4) + doubleQuote;
}
}
qDebug() << "ENCODING ERROR: " << encoding;
return QCoreApplication::translate("Debugger", "<Encoding error>");
}
// Editor tooltip support
bool isCppEditor(Core::IEditor *editor)
{
static QStringList cppMimeTypes;
if (cppMimeTypes.empty()) {
cppMimeTypes << QLatin1String(CppTools::Constants::C_SOURCE_MIMETYPE)
<< QLatin1String(CppTools::Constants::CPP_SOURCE_MIMETYPE)
<< QLatin1String(CppTools::Constants::CPP_HEADER_MIMETYPE)
<< QLatin1String(CppTools::Constants::OBJECTIVE_CPP_SOURCE_MIMETYPE);
}
if (const Core::IFile *file = editor->file())
return cppMimeTypes.contains(file->mimeType());
return false;
}
// Find the function the cursor is in to use a scope.
// Return the Cpp expression, and, if desired, the function
QString cppExpressionAt(TextEditor::ITextEditor *editor, int pos,
int *line, int *column, QString *function /* = 0 */)
{
*line = *column = 0;
if (function)
function->clear();
const QPlainTextEdit *plaintext = qobject_cast<QPlainTextEdit*>(editor->widget());
if (!plaintext)
return QString();
QString expr = plaintext->textCursor().selectedText();
CppTools::CppModelManagerInterface *modelManager = ExtensionSystem::PluginManager::instance()->getObject<CppTools::CppModelManagerInterface>();
if (expr.isEmpty() && modelManager) {
QTextCursor tc(plaintext->document());
tc.setPosition(pos);
const QChar ch = editor->characterAt(pos);
if (ch.isLetterOrNumber() || ch == QLatin1Char('_'))
tc.movePosition(QTextCursor::EndOfWord);
// Fetch the expression's code.
CPlusPlus::ExpressionUnderCursor expressionUnderCursor(modelManager->tokenCache(editor));
expr = expressionUnderCursor(tc);
*column = tc.columnNumber();
*line = tc.blockNumber();
} else {
const QTextCursor tc = plaintext->textCursor();
*column = tc.columnNumber();
*line = tc.blockNumber();
}
if (function && !expr.isEmpty())
if (const Core::IFile *file = editor->file())
if (modelManager)
*function = CppTools::AbstractEditorSupport::functionAt(modelManager, file->fileName(), *line, *column);
return expr;
}
// ----------------- QtDumperHelper::TypeData
QtDumperHelper::TypeData::TypeData() :
type(UnknownType),
isTemplate(false)
{
}
void QtDumperHelper::TypeData::clear()
{
isTemplate = false;
type = UnknownType;
tmplate.clear();
inner.clear();
}
// ----------------- QtDumperHelper
QtDumperHelper::QtDumperHelper() :
m_qtVersion(0),
m_dumperVersion(1.0)
{
qFill(m_specialSizes, m_specialSizes + SpecialSizeCount, 0);
setQClassPrefixes(QString());
}
void QtDumperHelper::clear()
{
m_nameTypeMap.clear();
m_qtVersion = 0;
m_dumperVersion = 1.0;
m_qtNamespace.clear();
m_sizeCache.clear();
qFill(m_specialSizes, m_specialSizes + SpecialSizeCount, 0);
m_expressionCache.clear();
setQClassPrefixes(QString());
}
QString QtDumperHelper::msgDumperOutdated(double requiredVersion, double currentVersion)
{
return QCoreApplication::translate("QtDumperHelper",
"Found an outdated version of the debugging helper library (%1); version %2 is required.").
arg(currentVersion).arg(requiredVersion);
}
static inline void formatQtVersion(int v, QTextStream &str)
{
str << ((v >> 16) & 0xFF) << '.' << ((v >> 8) & 0xFF) << '.' << (v & 0xFF);
}
QString QtDumperHelper::toString(bool debug) const
{
if (debug) {
QString rc;
QTextStream str(&rc);
str << "version=";
formatQtVersion(m_qtVersion, str);
str << "dumperversion='" << m_dumperVersion << "' namespace='" << m_qtNamespace << "'," << m_nameTypeMap.size() << " known types <type enum>: ";
const NameTypeMap::const_iterator cend = m_nameTypeMap.constEnd();
for (NameTypeMap::const_iterator it = m_nameTypeMap.constBegin(); it != cend; ++it) {
str <<",[" << it.key() << ',' << it.value() << ']';
}
str << "\nSpecial size: ";
for (int i = 0; i < SpecialSizeCount; i++)
str << ' ' << m_specialSizes[i];
str << "\nSize cache: ";
const SizeCache::const_iterator scend = m_sizeCache.constEnd();
for (SizeCache::const_iterator it = m_sizeCache.constBegin(); it != scend; ++it) {
str << ' ' << it.key() << '=' << it.value() << '\n';
}
str << "\nExpression cache: (" << m_expressionCache.size() << ")\n";
const QMap<QString, QString>::const_iterator excend = m_expressionCache.constEnd();
for (QMap<QString, QString>::const_iterator it = m_expressionCache.constBegin(); it != excend; ++it)
str << " " << it.key() << ' ' << it.value() << '\n';
return rc;
}
const QString nameSpace = m_qtNamespace.isEmpty() ? QCoreApplication::translate("QtDumperHelper", "<none>") : m_qtNamespace;
return QCoreApplication::translate("QtDumperHelper",
"%n known types, Qt version: %1, Qt namespace: %2 Dumper version: %3",
0, QCoreApplication::CodecForTr,
m_nameTypeMap.size()).arg(qtVersionString(), nameSpace).arg(m_dumperVersion);
}
QtDumperHelper::Type QtDumperHelper::simpleType(const QString &simpleType) const
{
return m_nameTypeMap.value(simpleType, UnknownType);
}
int QtDumperHelper::qtVersion() const
{
return m_qtVersion;
}
QString QtDumperHelper::qtNamespace() const
{
return m_qtNamespace;
}
int QtDumperHelper::typeCount() const
{
return m_nameTypeMap.size();
}
// Look up unnamespaced 'std' types.
static inline QtDumperHelper::Type stdType(const QString &s)
{
if (s == QLatin1String("vector"))
return QtDumperHelper::StdVectorType;
if (s == QLatin1String("deque"))
return QtDumperHelper::StdDequeType;
if (s == QLatin1String("set"))
return QtDumperHelper::StdSetType;
if (s == QLatin1String("stack"))
return QtDumperHelper::StdStackType;
if (s == QLatin1String("map"))
return QtDumperHelper::StdMapType;
if (s == QLatin1String("basic_string"))
return QtDumperHelper::StdStringType;
return QtDumperHelper::UnknownType;
}
QtDumperHelper::Type QtDumperHelper::specialType(QString s)
{
// Std classes.
if (s.startsWith(QLatin1String("std::")))
return stdType(s.mid(5));
// Strip namespace
// FIXME: that's not a good idea as it makes all namespaces equal.
const int namespaceIndex = s.lastIndexOf(QLatin1String("::"));
if (namespaceIndex == -1) {
// None ... check for std..
const Type sType = stdType(s);
if (sType != UnknownType)
return sType;
} else {
s = s.mid(namespaceIndex + 2);
}
if (s == QLatin1String("QAbstractItem"))
return QAbstractItemType;
if (s == QLatin1String("QMap"))
return QMapType;
if (s == QLatin1String("QMapNode"))
return QMapNodeType;
if (s == QLatin1String("QMultiMap"))
return QMultiMapType;
if (s == QLatin1String("QObject"))
return QObjectType;
if (s == QLatin1String("QObjectSignal"))
return QObjectSignalType;
if (s == QLatin1String("QObjectSlot"))
return QObjectSlotType;
if (s == QLatin1String("QStack"))
return QStackType;
if (s == QLatin1String("QVector"))
return QVectorType;
if (s == QLatin1String("QWidget"))
return QWidgetType;
return UnknownType;
}
QString QtDumperHelper::qtVersionString() const
{
QString rc;
QTextStream str(&rc);
formatQtVersion(m_qtVersion, str);
return rc;
}
// Parse a list of types.
void QtDumperHelper::parseQueryTypes(const QStringList &l, Debugger /* debugger */)
{
m_nameTypeMap.clear();
const int count = l.count();
for (int i = 0; i < count; i++) {
const Type t = specialType(l.at(i));
m_nameTypeMap.insert(l.at(i), t != UnknownType ? t : SupportedType);
}
}
static inline QString qClassName(const QString &qtNamespace, const char *className)
{
if (qtNamespace.isEmpty())
return QString::fromAscii(className);
QString rc = qtNamespace;
rc += QLatin1String("::");
rc += QString::fromAscii(className);
return rc;
}
void QtDumperHelper::setQClassPrefixes(const QString &qNamespace)
{
// Prefixes with namespaces
m_qPointerPrefix = qClassName(qNamespace, "QPointer");
m_qSharedPointerPrefix = qClassName(qNamespace, "QSharedPointer");
m_qSharedDataPointerPrefix = qClassName(qNamespace, "QSharedDataPointer");
m_qWeakPointerPrefix = qClassName(qNamespace, "QWeakPointer");
m_qListPrefix = qClassName(qNamespace, "QList");
m_qLinkedListPrefix = qClassName(qNamespace, "QLinkedList");
m_qVectorPrefix = qClassName(qNamespace, "QVector");
m_qQueuePrefix = qClassName(qNamespace, "QQueue");
}
static inline double getDumperVersion(const GdbMi &contents)
{
const GdbMi dumperVersionG = contents.findChild("dumperversion");
if (dumperVersionG.type() != GdbMi::Invalid) {
bool ok;
const double v = QString::fromAscii(dumperVersionG.data()).toDouble(&ok);
if (ok)
return v;
}
return 1.0;
}
bool QtDumperHelper::parseQuery(const GdbMi &contents, Debugger debugger)
{
clear();
if (debug > 1)
qDebug() << "parseQuery" << contents.toString(true, 2);
// Common info, dumper version, etc
m_qtNamespace = QLatin1String(contents.findChild("namespace").data());
int qtv = 0;
const GdbMi qtversion = contents.findChild("qtversion");
if (qtversion.children().size() == 3) {
qtv = (qtversion.childAt(0).data().toInt() << 16)
+ (qtversion.childAt(1).data().toInt() << 8)
+ qtversion.childAt(2).data().toInt();
}
m_qtVersion = qtv;
// Get list of helpers
QStringList availableSimpleDebuggingHelpers;
foreach (const GdbMi &item, contents.findChild("dumpers").children())
availableSimpleDebuggingHelpers.append(QLatin1String(item.data()));
parseQueryTypes(availableSimpleDebuggingHelpers, debugger);
m_dumperVersion = getDumperVersion(contents);
// Parse sizes
foreach (const GdbMi &sizesList, contents.findChild("sizes").children()) {
const int childCount = sizesList.childCount();
if (childCount > 1) {
const int size = sizesList.childAt(0).data().toInt();
for (int c = 1; c < childCount; c++)
addSize(QLatin1String(sizesList.childAt(c).data()), size);
}
}
// Parse expressions
foreach (const GdbMi &exprList, contents.findChild("expressions").children())
if (exprList.childCount() == 2)
m_expressionCache.insert(QLatin1String(exprList.childAt(0).data()),
QLatin1String(exprList.childAt(1).data()));
return true;
}
// parse a query
bool QtDumperHelper::parseQuery(const char *data, Debugger debugger)
{
GdbMi root;
root.fromStringMultiple(QByteArray(data));
if (!root.isValid())
return false;
return parseQuery(root, debugger);
}
void QtDumperHelper::addSize(const QString &name, int size)
{
// Special interest cases
if (name == QLatin1String("char*")) {
m_specialSizes[PointerSize] = size;
return;
}
const SpecialSizeType st = specialSizeType(name);
if (st != SpecialSizeCount) {
m_specialSizes[st] = size;
return;
}
do {
// CDB helpers
if (name == QLatin1String("std::string")) {
m_sizeCache.insert(QLatin1String("std::basic_string<char,std::char_traits<char>,std::allocator<char> >"), size);
m_sizeCache.insert(QLatin1String("basic_string<char,char_traits<char>,allocator<char> >"), size);
break;
}
if (name == QLatin1String("std::wstring")) {
m_sizeCache.insert(QLatin1String("basic_string<unsigned short,char_traits<unsignedshort>,allocator<unsignedshort> >"), size);
m_sizeCache.insert(QLatin1String("std::basic_string<unsigned short,std::char_traits<unsigned short>,std::allocator<unsigned short> >"), size);
break;
}
} while (false);
m_sizeCache.insert(name, size);
}
QtDumperHelper::Type QtDumperHelper::type(const QString &typeName) const
{
const QtDumperHelper::TypeData td = typeData(typeName);
return td.type;
}
QtDumperHelper::TypeData QtDumperHelper::typeData(const QString &typeName) const
{
TypeData td;
td.type = UnknownType;
const Type st = simpleType(typeName);
if (st != UnknownType) {
td.isTemplate = false;
td.type = st;
return td;
}
// Try template
td.isTemplate = extractTemplate(typeName, &td.tmplate, &td.inner);
if (!td.isTemplate)
return td;
// Check the template type QMap<X,Y> -> 'QMap'
td.type = simpleType(td.tmplate);
return td;
}
// Format an expression to have the debugger query the
// size. Use size cache if possible
QString QtDumperHelper::evaluationSizeofTypeExpression(const QString &typeName,
Debugger debugger) const
{
// Look up special size types
const SpecialSizeType st = specialSizeType(typeName);
if (st != SpecialSizeCount) {
if (const int size = m_specialSizes[st])
return QString::number(size);
}
// Look up size cache
const SizeCache::const_iterator sit = m_sizeCache.constFind(typeName);
if (sit != m_sizeCache.constEnd())
return QString::number(sit.value());
// Finally have the debugger evaluate
return sizeofTypeExpression(typeName, debugger);
}
QtDumperHelper::SpecialSizeType QtDumperHelper::specialSizeType(const QString &typeName) const
{
if (isPointerType(typeName))
return PointerSize;
static const QString intType = QLatin1String("int");
static const QString stdAllocatorPrefix = QLatin1String("std::allocator");
if (typeName == intType)
return IntSize;
if (typeName.startsWith(stdAllocatorPrefix))
return StdAllocatorSize;
if (typeName.startsWith(m_qPointerPrefix))
return QPointerSize;
if (typeName.startsWith(m_qSharedPointerPrefix))
return QSharedPointerSize;
if (typeName.startsWith(m_qSharedDataPointerPrefix))
return QSharedDataPointerSize;
if (typeName.startsWith(m_qWeakPointerPrefix))
return QWeakPointerSize;
if (typeName.startsWith(m_qListPrefix))
return QListSize;
if (typeName.startsWith(m_qLinkedListPrefix))
return QLinkedListSize;
if (typeName.startsWith(m_qVectorPrefix))
return QVectorSize;
if (typeName.startsWith(m_qQueuePrefix))
return QQueueSize;
return SpecialSizeCount;
}
static inline bool isInteger(const QString &n)
{
const int size = n.size();
if (!size)
return false;
for (int i = 0; i < size; i++)
if (!n.at(i).isDigit())
return false;
return true;
}
void QtDumperHelper::evaluationParameters(const WatchData &data,
const TypeData &td,
Debugger debugger,
QByteArray *inBuffer,
QStringList *extraArgsIn) const
{
enum { maxExtraArgCount = 4 };
QStringList &extraArgs = *extraArgsIn;
// See extractTemplate for parameters
QStringList inners = td.inner.split(QLatin1Char('@'));
if (inners.at(0).isEmpty())
inners.clear();
for (int i = 0; i != inners.size(); ++i)
inners[i] = inners[i].simplified();
QString outertype = td.isTemplate ? td.tmplate : data.type;
// adjust the data extract
if (outertype == m_qtNamespace + QLatin1String("QWidget"))
outertype = m_qtNamespace + QLatin1String("QObject");
QString inner = td.inner;
extraArgs.clear();
if (!inners.empty()) {
// "generic" template dumpers: passing sizeof(argument)
// gives already most information the dumpers need
const int count = qMin(int(maxExtraArgCount), inners.size());
for (int i = 0; i < count; i++)
extraArgs.push_back(evaluationSizeofTypeExpression(inners.at(i), debugger));
}
int extraArgCount = extraArgs.size();
// Pad with zeros
const QString zero = QString(QLatin1Char('0'));
const int extraPad = maxExtraArgCount - extraArgCount;
for (int i = 0; i < extraPad; i++)
extraArgs.push_back(zero);
// in rare cases we need more or less:
switch (td.type) {
case QAbstractItemType:
inner = data.addr.mid(1);
break;
case QObjectSlotType:
case QObjectSignalType: {
// we need the number out of something like
// iname="local.ob.slots.2" // ".deleteLater()"?
const int pos = data.iname.lastIndexOf('.');
const QString slotNumber = data.iname.mid(pos + 1);
QTC_ASSERT(slotNumber.toInt() != -1, /**/);
extraArgs[0] = slotNumber;
}
break;
case QMapType:
case QMultiMapType: {
QString nodetype;
if (m_qtVersion >= 0x040500) {
nodetype = m_qtNamespace + QLatin1String("QMapNode");
nodetype += data.type.mid(outertype.size());
} else {
// FIXME: doesn't work for QMultiMap
nodetype = data.type + QLatin1String("::Node");
}
//qDebug() << "OUTERTYPE: " << outertype << " NODETYPE: " << nodetype
// << "QT VERSION" << m_qtVersion << ((4 << 16) + (5 << 8) + 0);
extraArgs[2] = evaluationSizeofTypeExpression(nodetype, debugger);
extraArgs[3] = qMapNodeValueOffsetExpression(nodetype, data.addr, debugger);
}
break;
case QMapNodeType:
extraArgs[2] = evaluationSizeofTypeExpression(data.type, debugger);
extraArgs[3] = qMapNodeValueOffsetExpression(data.type, data.addr, debugger);
break;
case StdVectorType:
//qDebug() << "EXTRACT TEMPLATE: " << outertype << inners;
if (inners.at(0) == QLatin1String("bool")) {
outertype = QLatin1String("std::vector::bool");
}
break;
case StdDequeType:
extraArgs[1] = zero;
case StdStackType:
// remove 'std::allocator<...>':
extraArgs[1] = zero;
break;
case StdSetType:
// remove 'std::less<...>':
extraArgs[1] = zero;
// remove 'std::allocator<...>':
extraArgs[2] = zero;
break;
case StdMapType: {
// We need the offset of the second item in the value pair.
// We read the type of the pair from the allocator argument because
// that gets the constness "right" (in the sense that gdb/cdb can
// read it back: "std::allocator<std::pair<Key,Value> >"
// -> "std::pair<Key,Value>". Different debuggers have varying
// amounts of terminating blanks...
extraArgs[2].clear();
extraArgs[3] = zero;
QString pairType = inners.at(3);
int bracketPos = pairType.indexOf(QLatin1Char('<'));
if (bracketPos != -1)
pairType.remove(0, bracketPos + 1);
// We don't want the comparator and the allocator confuse gdb.
const QChar closingBracket = QLatin1Char('>');
bracketPos = pairType.lastIndexOf(closingBracket);
if (bracketPos != -1)
bracketPos = pairType.lastIndexOf(closingBracket, bracketPos - pairType.size() - 1);
if (bracketPos != -1)
pairType.truncate(bracketPos + 1);
if (debugger == GdbDebugger) {
extraArgs[2] = QLatin1String("(size_t)&(('");
extraArgs[2] += pairType;
extraArgs[2] += QLatin1String("'*)0)->second");
} else {
// Cdb: The std::pair is usually in scope. Still, this expression
// occasionally fails for complex types (std::string).
// We need an address as CDB cannot do the 0-trick.
// Use data address or try at least cache if missing.
const QString address = data.addr.isEmpty() ? QString::fromLatin1("DUMMY_ADDRESS") : data.addr;
QString offsetExpr;
QTextStream str(&offsetExpr);
str << "(size_t)&(((" << pairType << " *)" << address << ")->second)" << '-' << address;
extraArgs[2] = lookupCdbDummyAddressExpression(offsetExpr, address);
}
}
break;
case StdStringType:
//qDebug() << "EXTRACT TEMPLATE: " << outertype << inners;
if (inners.at(0) == QLatin1String("char")) {
outertype = QLatin1String("std::string");
} else if (inners.at(0) == QLatin1String("wchar_t")) {
outertype = QLatin1String("std::wstring");
}
qFill(extraArgs, zero);
break;
case UnknownType:
qWarning("Unknown type encountered in %s.\n", Q_FUNC_INFO);
break;
case SupportedType:
case QVectorType:
case QStackType:
case QObjectType:
case QWidgetType:
break;
}
// Look up expressions in the cache
if (!m_expressionCache.empty()) {
const QMap<QString, QString>::const_iterator excCend = m_expressionCache.constEnd();
const QStringList::iterator eend = extraArgs.end();
for (QStringList::iterator it = extraArgs.begin(); it != eend; ++it) {
QString &e = *it;
if (!e.isEmpty() && e != zero && !isInteger(e)) {
const QMap<QString, QString>::const_iterator eit = m_expressionCache.constFind(e);
if (eit != excCend)
e = eit.value();
}
}
}
inBuffer->clear();
inBuffer->append(outertype.toUtf8());
inBuffer->append('\0');
inBuffer->append(data.iname);
inBuffer->append('\0');
inBuffer->append(data.exp);
inBuffer->append('\0');
inBuffer->append(inner.toUtf8());
inBuffer->append('\0');
inBuffer->append(data.iname);
inBuffer->append('\0');
if (debug)
qDebug() << '\n' << Q_FUNC_INFO << '\n' << data.toString() << "\n-->" << outertype << td.type << extraArgs;
}
// Return debugger expression to get the offset of a map node.
QString QtDumperHelper::qMapNodeValueOffsetExpression(const QString &type,
const QString &addressIn,
Debugger debugger) const
{
switch (debugger) {
case GdbDebugger:
return QLatin1String("(size_t)&(('") + type + QLatin1String("'*)0)->value");
case CdbDebugger: {
// Cdb: This will only work if a QMapNode is in scope.
// We need an address as CDB cannot do the 0-trick.
// Use data address or try at least cache if missing.
const QString address = addressIn.isEmpty() ? QString::fromLatin1("DUMMY_ADDRESS") : addressIn;
QString offsetExpression;
QTextStream(&offsetExpression) << "(size_t)&(((" << type
<< " *)" << address << ")->value)-" << address;
return lookupCdbDummyAddressExpression(offsetExpression, address);
}
}
return QString();
}
/* Cdb cannot do tricks like ( "&(std::pair<int,int>*)(0)->second)",
* that is, use a null pointer to determine the offset of a member.
* It tries to dereference the address at some point and fails with
* "memory access error". As a trick, use the address of the watch item
* to do this. However, in the expression cache, 0 is still used, so,
* for cache lookups, use '0' as address. */
QString QtDumperHelper::lookupCdbDummyAddressExpression(const QString &expr,
const QString &address) const
{
QString nullExpr = expr;
nullExpr.replace(address, QString(QLatin1Char('0')));
const QString rc = m_expressionCache.value(nullExpr, expr);
if (debug)
qDebug() << "lookupCdbDummyAddressExpression" << expr << rc;
return rc;
}
// GdbMi parsing helpers for parsing dumper value results
static bool gdbMiGetIntValue(int *target,
const GdbMi &node,
const char *child)
{
*target = -1;
const GdbMi childNode = node.findChild(child);
if (!childNode.isValid())
return false;
bool ok;
*target = childNode.data().toInt(&ok);
return ok;
}
// Find a string child node and assign value if it exists.
// Optionally decode.
static bool gdbMiGetStringValue(QString *target,
const GdbMi &node,
const char *child,
const char *encodingChild = 0)
{
target->clear();
const GdbMi childNode = node.findChild(child);
if (!childNode.isValid())
return false;
// Encoded data
if (encodingChild) {
int encoding;
if (!gdbMiGetIntValue(&encoding, node, encodingChild))
encoding = 0;
*target = decodeData(childNode.data(), encoding);
return true;
}
// Plain data
*target = QLatin1String(childNode.data());
return true;
}
static bool gdbMiGetByteArrayValue(QByteArray *target,
const GdbMi &node,
const char *child,
const char *encodingChild = 0)
{
QString str;
const bool success = gdbMiGetStringValue(&str, node, child, encodingChild);
*target = str.toLatin1();
return success;
}
static bool gdbMiGetBoolValue(bool *target,
const GdbMi &node,
const char *child)
{
*target = false;
const GdbMi childNode = node.findChild(child);
if (!childNode.isValid())
return false;
*target = childNode.data() == "true";
return true;
}
/* Context to store parameters that influence the next level children.
* (next level only, it is not further inherited). For example, the root item
* can provide a "childtype" node that specifies the type of the children. */
struct GdbMiRecursionContext
{
GdbMiRecursionContext(int recursionLevelIn = 0) :
recursionLevel(recursionLevelIn), childNumChild(-1), childIndex(0) {}
int recursionLevel;
int childNumChild;
int childIndex;
QString childType;
QByteArray parentIName;
};
static void gbdMiToWatchData(const GdbMi &root,
const GdbMiRecursionContext &ctx,
QList<WatchData> *wl)
{
if (debug > 1)
qDebug() << Q_FUNC_INFO << '\n' << root.toString(false, 0);
WatchData w;
QString v;
QByteArray b;
// Check for name/iname and use as expression default
if (ctx.recursionLevel == 0) {
// parents have only iname, from which name is derived
QString iname;
if (!gdbMiGetStringValue(&iname, root, "iname"))
qWarning("Internal error: iname missing");
w.iname = iname.toLatin1();
w.name = iname;
const int lastDotPos = w.name.lastIndexOf(QLatin1Char('.'));
if (lastDotPos != -1)
w.name.remove(0, lastDotPos + 1);
w.exp = w.name.toLatin1();
} else {
// Children can have a 'name' attribute. If missing, assume array index
// For display purposes, it can be overridden by "key"
if (!gdbMiGetStringValue(&w.name, root, "name")) {
w.name = QString::number(ctx.childIndex);
}
// Set iname
w.iname = ctx.parentIName;
w.iname += '.';
w.iname += w.name.toLatin1();
// Key?
QString key;
if (gdbMiGetStringValue(&key, root, "key", "keyencoded")) {
w.name = key.size() > 13 ? key.mid(0, 13) + QLatin1String("...") : key;
}
}
if (w.name.isEmpty()) {
const QString msg = QString::fromLatin1("Internal error: Unable to determine name at level %1/%2 for %3").arg(ctx.recursionLevel).arg(w.iname, QLatin1String(root.toString(true, 2)));
qWarning("%s\n", qPrintable(msg));
}
gdbMiGetStringValue(&w.displayedType, root, "displayedtype");
if (gdbMiGetByteArrayValue(&b, root, "editvalue"))
w.editvalue = b;
if (gdbMiGetByteArrayValue(&b, root, "exp"))
w.exp = b;
gdbMiGetByteArrayValue(&w.addr, root, "addr");
gdbMiGetBoolValue(&w.valueEnabled, root, "valueenabled");
gdbMiGetBoolValue(&w.valueEditable, root, "valueeditable");
if (gdbMiGetStringValue(&v, root, "valuetooltip", "valuetooltipencoded"))
w.setValue(v);
if (gdbMiGetStringValue(&v, root, "value", "valueencoded"))
w.setValue(v);
// Type from context or self
if (ctx.childType.isEmpty()) {
if (gdbMiGetStringValue(&v, root, "type"))
w.setType(v);
} else {
w.setType(ctx.childType);
}
// child count?
int numChild = -1;
if (ctx.childNumChild >= 0) {
numChild = ctx.childNumChild;
} else {
gdbMiGetIntValue(&numChild, root, "numchild");
}
if (numChild >= 0)
w.setHasChildren(numChild > 0);
wl->push_back(w);
// Parse children with a new context
if (numChild == 0)
return;
const GdbMi childrenNode = root.findChild("children");
if (!childrenNode.isValid())
return;
const QList<GdbMi> children =childrenNode.children();
if (children.empty())
return;
wl->back().setChildrenUnneeded();
GdbMiRecursionContext nextLevelContext(ctx.recursionLevel + 1);
nextLevelContext.parentIName = w.iname;
gdbMiGetStringValue(&nextLevelContext.childType, root, "childtype");
if (!gdbMiGetIntValue(&nextLevelContext.childNumChild, root, "childnumchild"))
nextLevelContext.childNumChild = -1;
foreach(const GdbMi &child, children) {
gbdMiToWatchData(child, nextLevelContext, wl);
nextLevelContext.childIndex++;
}
}
bool QtDumperHelper::parseValue(const char *data,
QList<WatchData> *l)
{
l->clear();
GdbMi root;
root.fromStringMultiple(QByteArray(data));
if (!root.isValid())
return false;
gbdMiToWatchData(root, GdbMiRecursionContext(), l);
return true;
}
QDebug operator<<(QDebug in, const QtDumperHelper::TypeData &d)
{
QDebug nsp = in.nospace();
nsp << " type=" << d.type << " tpl=" << d.isTemplate;
if (d.isTemplate)
nsp << d.tmplate << '<' << d.inner << '>';
return in;
}
//////////////////////////////////////////////////////////////////////
//
// GdbMi interaction
//
//////////////////////////////////////////////////////////////////////
void setWatchDataValue(WatchData &data, const GdbMi &item)
{
GdbMi value = item.findChild("value");
if (value.isValid()) {
int encoding = item.findChild("valueencoded").data().toInt();
data.setValue(decodeData(value.data(), encoding));
} else {
data.setValueNeeded();
}
}
void setWatchDataValueToolTip(WatchData &data, const GdbMi &mi,
int encoding)
{
if (mi.isValid())
data.setValueToolTip(decodeData(mi.data(), encoding));
}
void setWatchDataChildCount(WatchData &data, const GdbMi &mi)
{
if (mi.isValid())
data.setHasChildren(mi.data().toInt() > 0);
}
void setWatchDataValueEnabled(WatchData &data, const GdbMi &mi)
{
if (mi.data() == "true")
data.valueEnabled = true;
else if (mi.data() == "false")
data.valueEnabled = false;
}
void setWatchDataValueEditable(WatchData &data, const GdbMi &mi)
{
if (mi.data() == "true")
data.valueEditable = true;
else if (mi.data() == "false")
data.valueEditable = false;
}
void setWatchDataExpression(WatchData &data, const GdbMi &mi)
{
if (mi.isValid())
data.exp = mi.data();
}
void setWatchDataAddress(WatchData &data, const GdbMi &mi)
{
if (mi.isValid())
setWatchDataAddressHelper(data, mi.data());
}
void setWatchDataAddressHelper(WatchData &data, const QByteArray &addr)
{
data.addr = addr;
if (data.exp.isEmpty() && !data.addr.startsWith("$"))
data.exp = "*(" + gdbQuoteTypes(data.type).toLatin1() + "*)" + data.addr;
}
// Find the "type" and "displayedtype" children of root and set up type.
void setWatchDataType(WatchData &data, const GdbMi &item)
{
if (item.isValid())
data.setType(_(item.data()));
else if (data.type.isEmpty())
data.setTypeNeeded();
}
void setWatchDataDisplayedType(WatchData &data, const GdbMi &item)
{
if (item.isValid())
data.displayedType = _(item.data());
}
void parseWatchData(const QSet<QByteArray> &expandedINames,
const WatchData &data0, const GdbMi &item, QList<WatchData> *list)
{
//qDebug() << "HANDLE CHILDREN: " << data0.toString() << item.toString();
WatchData data = data0;
bool isExpanded = expandedINames.contains(data.iname);
if (!isExpanded)
data.setChildrenUnneeded();
GdbMi children = item.findChild("children");
if (children.isValid() || !isExpanded)
data.setChildrenUnneeded();
setWatchDataType(data, item.findChild("type"));
GdbMi mi = item.findChild("editvalue");
if (mi.isValid())
data.editvalue = mi.data();
mi = item.findChild("editformat");
if (mi.isValid())
data.editformat = mi.data().toInt();
mi = item.findChild("typeformats");
if (mi.isValid())
data.typeFormats = QString::fromUtf8(mi.data());
setWatchDataValue(data, item);
setWatchDataAddress(data, item.findChild("addr"));
setWatchDataExpression(data, item.findChild("exp"));
setWatchDataValueEnabled(data, item.findChild("valueenabled"));
setWatchDataValueEditable(data, item.findChild("valueeditable"));
setWatchDataChildCount(data, item.findChild("numchild"));
//qDebug() << "\nAPPEND TO LIST: " << data.toString() << "\n";
list->append(data);
bool ok = false;
qulonglong addressBase = item.findChild("addrbase").data().toULongLong(&ok, 0);
qulonglong addressStep = item.findChild("addrstep").data().toULongLong();
// Try not to repeat data too often.
WatchData childtemplate;
setWatchDataType(childtemplate, item.findChild("childtype"));
setWatchDataChildCount(childtemplate, item.findChild("childnumchild"));
//qDebug() << "CHILD TEMPLATE:" << childtemplate.toString();
int i = 0;
foreach (const GdbMi &child, children.children()) {
WatchData data1 = childtemplate;
GdbMi name = child.findChild("name");
if (name.isValid())
data1.name = _(name.data());
else
data1.name = QString::number(i);
GdbMi iname = child.findChild("iname");
if (iname.isValid())
data1.iname = iname.data();
else
data1.iname = data.iname + '.' + data1.name.toLatin1();
if (!data1.name.isEmpty() && data1.name.at(0).isDigit())
data1.name = _c('[') + data1.name + _c(']');
if (addressStep) {
const QByteArray addr = "0x" + QByteArray::number(addressBase, 16);
setWatchDataAddressHelper(data1, addr);
addressBase += addressStep;
}
QByteArray key = child.findChild("key").data();
if (!key.isEmpty()) {
int encoding = child.findChild("keyencoded").data().toInt();
QString skey = decodeData(key, encoding);
if (skey.size() > 13) {
skey = skey.left(12);
skey += _("...");
}
//data1.name += " (" + skey + ")";
data1.name = skey;
}
parseWatchData(expandedINames, data1, child, list);
++i;
}
}
} // namespace Internal
} // namespace Debugger