Files
qt-creator/src/plugins/debugger/watchutils.cpp
Friedemann Kleint caa164a4ab CDB: Use GdbMi parser for everything.
..preparing recursive dumpers. Add recursive parser to
watchutils.cpp Use insertBulkData() within CDB, add sizeof()
information for some Qt containers in order to be able to
dump QList<QList<KnownType> > .
2009-09-21 14:55:39 +02:00

1422 lines
47 KiB
C++

/**************************************************************************
**
** This file is part of Qt Creator
**
** Copyright (c) 2009 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 "watchhandler.h"
#include "gdb/gdbmi.h"
#include <utils/qtcassert.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 <extensionsystem/pluginmanager.h>
#include <QtCore/QDebug>
#include <QtCore/QTime>
#include <QtCore/QStringList>
#include <QtCore/QCoreApplication>
#include <QtCore/QTextStream>
#include <QtGui/QTextCursor>
#include <QtGui/QPlainTextEdit>
#include <string.h>
#include <ctype.h>
enum { debug = 0 };
namespace Debugger {
namespace Internal {
QString dotEscape(QString str)
{
const QChar dot = QLatin1Char('.');
str.replace(QLatin1Char(' '), dot);
str.replace(QLatin1Char('\\'), dot);
str.replace(QLatin1Char('/'), dot);
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("::qt_metacall"))
return true;
if (funcName.endsWith("::d_func"))
return true;
if (funcName.endsWith("::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 isAccessSpecifier(const QString &str)
{
static const QStringList items = QStringList()
<< QLatin1String("private")
<< QLatin1String("protected")
<< QLatin1String("public");
return items.contains(str);
}
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;
}
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("long long") << QLatin1String("unsigned long long");
return type.endsWith(QLatin1String(" int")) || 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 (isPointerType(type))
return HasChildren;
if (type.endsWith(QLatin1String("QString")))
return HasNoChildren;
return HasPossiblyChildren;
}
QString sizeofTypeExpression(const QString &type)
{
if (type.endsWith(QLatin1Char('*')))
return QLatin1String("sizeof(void*)");
if (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));
}
}
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();
if (expr.isEmpty()) {
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;
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 (CppTools::CppModelManagerInterface *modelManager = ExtensionSystem::PluginManager::instance()->getObject<CppTools::CppModelManagerInterface>())
*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 a too-old 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();
}
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;
}
QtDumperHelper::ExpressionRequirement QtDumperHelper::expressionRequirements(Type t)
{
switch (t) {
case QAbstractItemType:
return NeedsComplexExpression;
case QMapType:
case QMultiMapType:
case QMapNodeType:
case StdMapType:
return NeedsCachedExpression;
default:
// QObjectSlotType, QObjectSignalType need the signal number, which is numeric
break;
}
return NeedsNoExpression;
}
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));
if (t != UnknownType) {
// Exclude types that require expression syntax for CDB
if (debugger == GdbDebugger || expressionRequirements(t) != NeedsComplexExpression)
m_nameTypeMap.insert(l.at(i), t);
} else {
m_nameTypeMap.insert(l.at(i), SupportedType);
}
}
}
/* A parse for dumper output:
* "iname="local.sl",addr="0x0012BA84",value="<3 items>",valuedisabled="true",
* numchild="3",childtype="QString",childnumchild="0",children=[{name="0",value="<binhex>",
* valueencoded="2"},{name="1",value="dAB3AG8A",valueencoded="2"},{name="2",
* value="dABoAHIAZQBlAA==",valueencoded="2"}]"
* Default implementation can be used for debugging purposes. */
class DumperParser
{
public:
explicit DumperParser(const char *s) : m_s(s) {}
bool run();
virtual ~DumperParser() {}
protected:
// handle 'key="value"'
virtual bool handleKeyword(const char *k, int size);
virtual bool handleListStart();
virtual bool handleListEnd();
virtual bool handleHashStart();
virtual bool handleHashEnd();
virtual bool handleValue(const char *k, int size);
private:
bool parseHash(int level, const char *&pos);
bool parseValue(int level, const char *&pos);
bool parseStringValue(const char *&ptr, int &size, const char *&pos) const;
const char *m_s;
};
// get a string value with pos at the opening double quote
bool DumperParser::parseStringValue(const char *&ptr, int &size, const char *&pos) const
{
pos++;
const char *endValuePtr = strchr(pos, '"');
if (!endValuePtr)
return false;
size = endValuePtr - pos;
ptr = pos;
pos = endValuePtr + 1;
return true;
}
bool DumperParser::run()
{
const char *ptr = m_s;
const bool rc = parseHash(0, ptr);
if (debug > 1)
qDebug() << Q_FUNC_INFO << '\n' << m_s << rc;
return rc;
}
// Parse a non-empty hash with pos at the first keyword.
// Curly braces are present at level 0 only.
// '{a="X", b="X"}'
bool DumperParser::parseHash(int level, const char *&pos)
{
while (true) {
switch (*pos) {
case '\0': // EOS is acceptable at level 0 only
return level == 0;
case '}':
pos++;
return true;
default:
break;
}
const char *equalsPtr = strchr(pos, '=');
if (!equalsPtr)
return false;
const int keywordLen = equalsPtr - pos;
if (!handleKeyword(pos, keywordLen))
return false;
pos = equalsPtr + 1;
if (!*pos)
return false;
if (!parseValue(level + 1, pos))
return false;
if (*pos == ',')
pos++;
}
return false;
}
bool DumperParser::parseValue(int level, const char *&pos)
{
// Simple string literal
switch (*pos) {
case '"': {
const char *valuePtr;
int valueSize;
return parseStringValue(valuePtr, valueSize, pos) && handleValue(valuePtr, valueSize);
}
// A List. Note that it has a trailing comma '["a",]'
case '[': {
if (!handleListStart())
return false;
pos++;
while (true) {
switch (*pos) {
case ']':
pos++;
return handleListEnd();
case '\0':
return false;
default:
break;
}
if (!parseValue(level + 1, pos))
return false;
if (*pos == ',')
pos++;
}
}
return false;
// A hash '{a="b",b="c"}'
case '{': {
if (!handleHashStart())
return false;
pos++;
if (!parseHash(level + 1, pos))
return false;
return handleHashEnd();
}
return false;
}
return false;
}
bool DumperParser::handleKeyword(const char *k, int size)
{
if (debug > 1)
qDebug() << Q_FUNC_INFO << '\n' << QByteArray(k, size);
return true;
}
bool DumperParser::handleListStart()
{
if (debug > 1)
qDebug() << Q_FUNC_INFO;
return true;
}
bool DumperParser::handleListEnd()
{
if (debug > 1)
qDebug() << Q_FUNC_INFO;
return true;
}
bool DumperParser::handleHashStart()
{
if (debug > 1)
qDebug() << Q_FUNC_INFO;
return true;
}
bool DumperParser::handleHashEnd()
{
if (debug > 1)
qDebug() << Q_FUNC_INFO;
return true;
}
bool DumperParser::handleValue(const char *k, int size)
{
if (debug > 1)
qDebug() << Q_FUNC_INFO << '\n' << QByteArray(k, size);
return true;
}
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");
}
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)
{
QByteArray fullData = data;
fullData.insert(0, '{');
fullData.append(data);
fullData.append('}');
GdbMi root(fullData);
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);
}
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] = QLatin1String("(size_t)&(('");
extraArgs[3] += nodetype;
extraArgs[3] += QLatin1String("'*)0)->value");
}
break;
case QMapNodeType:
extraArgs[2] = evaluationSizeofTypeExpression(data.type, debugger);
extraArgs[3] = QLatin1String("(size_t)&(('");
extraArgs[3] += data.type;
extraArgs[3] += QLatin1String("'*)0)->value");
break;
case StdVectorType:
//qDebug() << "EXTRACT TEMPLATE: " << outertype << inners;
if (inners.at(0) == QLatin1String("bool")) {
outertype = QLatin1String("std::vector::bool");
} else {
//extraArgs[extraArgCount++] = evaluationSizeofTypeExpression(data.type, debugger);
//extraArgs[extraArgCount++] = "(size_t)&(('" + data.type + "'*)0)->value";
}
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 don't want the comparator and the allocator confuse gdb.
// But 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 can
// read it back: "std::allocator<std::pair<Key,Value> >"
// -> "std::pair<Key,Value>". Different debuggers have varying
// amounts of terminating blanks...
QString pairType = inners.at(3);
int bracketPos = pairType.indexOf(QLatin1Char('<'));
if (bracketPos != -1)
pairType.remove(0, bracketPos + 1);
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);
extraArgs[2] = QLatin1String("(size_t)&(('");
extraArgs[2] += pairType;
extraArgs[2] += QLatin1String("'*)0)->second");
extraArgs[3] = zero;
}
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.toUtf8());
inBuffer->append('\0');
inBuffer->append(data.exp.toUtf8());
inBuffer->append('\0');
inBuffer->append(inner.toUtf8());
inBuffer->append('\0');
inBuffer->append(data.iname.toUtf8());
inBuffer->append('\0');
if (debug)
qDebug() << '\n' << Q_FUNC_INFO << '\n' << data.toString() << "\n-->" << outertype << td.type << extraArgs;
}
// 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 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;
QString 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;
// Check for name/iname and use as expression default
if (ctx.recursionLevel == 0) {
// parents have only iname, from which name is derived
if (!gdbMiGetStringValue(&w.iname, root, "iname"))
qWarning("Internal error: iname missing");
w.name = w.iname;
const int lastDotPos = w.name.lastIndexOf(QLatin1Char('.'));
if (lastDotPos != -1)
w.name.remove(0, lastDotPos + 1);
w.exp = w.name;
} 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 += QLatin1Char('.');
w.iname += w.name;
// 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 (gdbMiGetStringValue(&v, root, "editvalue"))
w.editvalue = v.toLatin1();
if (gdbMiGetStringValue(&v, root, "exp"))
w.exp = v;
gdbMiGetStringValue(&w.addr, root, "addr");
gdbMiGetStringValue(&w.saddr, root, "saddr");
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();
QByteArray fullData = data;
fullData.insert(0, '{');
fullData.append(data);
fullData.append('}');
GdbMi root(fullData);
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;
}
} // namespace Internal
} // namespace Debugger