Skip to content
Snippets Groups Projects
watchutils.cpp 41.8 KiB
Newer Older
/**************************************************************************
**
** This file is part of Qt Creator
**
** Copyright (c) 2009 Nokia Corporation and/or its subsidiary(-ies).
**
** Contact:  Qt Software Information (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 qt-sales@nokia.com.
**
**************************************************************************/

#include "watchutils.h"
#include "watchhandler.h"
#include <utils/qtcassert.h>

#include <QtCore/QDebug>
#include <QtCore/QTime>
#include <QtCore/QStringList>
#include <QtCore/QCoreApplication>
#include <QtCore/QTextStream>

#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(".moc")))
        return true;

    if (funcName.endsWith("::qt_metacall"))
        return true;

    return false;
}

bool isLeavableFunction(const QString &funcName, const QString &fileName)
{
    if (funcName.endsWith(QLatin1String("QObjectPrivate::setCurrentSender")))
        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/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("++"));
}

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)
{
hjk's avatar
hjk committed
    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
    int level = 0;
    bool skipSpace = false;

    for (int i = 0; i != type.size(); ++i) {
        const QChar c = type.at(i);
        if (c == QLatin1Char(' ') && skipSpace) {
            skipSpace = false;
        } else if (c == QLatin1Char('<')) {
            *(level == 0 ? tmplate : inner) += c;
            ++level;
        } else if (c == QLatin1Char('>')) {
            --level;
            *(level == 0 ? tmplate : inner) += c;
        } else if (c == QLatin1Char(',')) {
            *inner += (level == 1) ? QLatin1Char('@') : QLatin1Char(',');
            skipSpace = true;
        } else {
            *(level == 0 ? tmplate : inner) += c;
        }
    }
    *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 isIntOrFloatType(const QString &type)
{
    static const QStringList types = QStringList()
        << QLatin1String("char") << QLatin1String("int") << QLatin1String("short")
        << QLatin1String("float") << QLatin1String("double") << QLatin1String("long")
        << QLatin1String("bool") << QLatin1String("signed char") << QLatin1String("unsigned")
        << QLatin1String("unsigned char")
        << QLatin1String("unsigned int") << QLatin1String("unsigned long")
        << QLatin1String("long long")  << QLatin1String("unsigned long long");
    return types.contains(type);
}

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.

static 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;
}

hjk's avatar
hjk committed
QString decodeData(const QByteArray &ba, int encoding)
{
    switch (encoding) {
        case 0: // unencoded 8 bit data
hjk's avatar
hjk committed
            return quoteUnprintableLatin1(ba);
        case 1: { //  base64 encoded 8 bit data, used for QByteArray
            const QChar doubleQuote(QLatin1Char('"'));
            QString rc = doubleQuote;
hjk's avatar
hjk committed
            rc += quoteUnprintableLatin1(QByteArray::fromBase64(ba));
            rc += doubleQuote;
            return rc;
        }
        case 2: { //  base64 encoded 16 bit data, used for QString
            const QChar doubleQuote(QLatin1Char('"'));
hjk's avatar
hjk committed
            const QByteArray ba = QByteArray::fromBase64(ba);
            QString rc = doubleQuote;
            rc += QString::fromUtf16(reinterpret_cast<const ushort *>(ba.data()), ba.size() / 2);
            rc += doubleQuote;
            return rc;
        }
        case 3: { //  base64 encoded 32 bit data
hjk's avatar
hjk committed
            const QByteArray ba = QByteArray::fromBase64(ba);
            const QChar doubleQuote(QLatin1Char('"'));
            QString rc = doubleQuote;
            rc += QString::fromUcs4(reinterpret_cast<const uint *>(ba.data()), ba.size() / 4);
            rc += doubleQuote;
            return rc;
        }
        case 4: { //  base64 encoded 16 bit data, without quotes (see 2)
hjk's avatar
hjk committed
            const QByteArray ba = QByteArray::fromBase64(ba);
            return QString::fromUtf16(reinterpret_cast<const ushort *>(ba.data()), ba.size() / 2);
        }
    }
    return QCoreApplication::translate("Debugger", "<Encoding error>");
}

// --------------- QtDumperResult

QtDumperResult::Child::Child() :
   valueEncoded(0)
{
}

QtDumperResult::QtDumperResult() :
    valueEncoded(0),
    valuedisabled(false),
    childCount(0),
    internal(false)
{
}

void QtDumperResult::clear()
{
    iname.clear();
    value.clear();
    address.clear();
    type.clear();
    valueEncoded = 0;
    valuedisabled = false;
    childCount = 0;
    internal = false;
    childType.clear();
    children.clear();
}

QList<WatchData> QtDumperResult::toWatchData(int source) const
{
    QList<WatchData> rc;
    rc.push_back(WatchData());
    WatchData &root = rc.front();
    root.iname = iname;
    const QChar dot = QLatin1Char('.');
    const int lastDotIndex = root.iname.lastIndexOf(dot);
    root.exp = root.name = lastDotIndex == -1 ? iname : iname.mid(lastDotIndex + 1);
    root.setValue(decodeData(value, valueEncoded));
    root.setType(type);
    root.valuedisabled = valuedisabled;
    root.setAddress(address);
    root.source = source;
    root.setChildCount(childCount);
    // Children
    if (childCount > 0) {
        if (children.size() == childCount) {
            for (int c = 0; c < childCount; c++) {
                const Child &dchild = children.at(c);
                rc.push_back(WatchData());
                WatchData &wchild = rc.back();
                wchild.source = source;
                wchild.iname = iname;
                wchild.iname += dot;
                wchild.iname += dchild.name;                
                wchild.exp = wchild.name = dchild.name;
                wchild.setType(childType);
                wchild.setAddress(dchild.address);
                wchild.setValue(decodeData(dchild.value, dchild.valueEncoded));
                wchild.setChildCount(0);
            }
            root.setChildrenUnneeded();
        } else {
            root.setChildrenNeeded();
        }
    }
    return rc;
}

QDebug operator<<(QDebug in, const QtDumperResult &d)
{
    QDebug nospace = in.nospace();
    nospace << " iname=" << d.iname << " type=" << d.type << " address=" << d.address
            << " value="  << d.value
            << " disabled=" << d.valuedisabled
            << " encoded=" << d.valueEncoded << " internal=" << d.internal;
    if (d.childCount) {
        nospace << " childCount=" << d.childCount
                << " childType=" << d.childType << '\n';
        const int childCount = d.children.size();
        for (int i = 0; i < childCount; i++) {
            const QtDumperResult::Child &c = d.children.at(i);
            nospace << "   #" << i << " addr=" << c.address
                    << " name=" << c.name << " encoded=" << c.valueEncoded
                    << " value=" << c.value << '\n';
        }
    }
    return in;
}

// ----------------- 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_stdAllocatorPrefix(QLatin1String("std::allocator")),
    m_intSize(0),
    m_pointerSize(0),
    m_stdAllocatorSize(0),
    m_qtVersion(0)
{
}

void QtDumperHelper::clear()
{
    m_nameTypeMap.clear();
    m_qtVersion = 0;
    m_qtNamespace.clear();
    m_sizeCache.clear();
    m_intSize = 0;
    m_pointerSize = 0;
    m_stdAllocatorSize = 0;
}

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 << " namespace='" << m_qtNamespace << "'," << m_nameTypeMap.size() << " known types: ";
        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 << "Sizes: intsize=" << m_intSize << " pointer size=" << m_pointerSize
                << " allocatorsize=" << m_stdAllocatorSize;
        const SizeCache::const_iterator scend = m_sizeCache.constEnd();
        for (SizeCache::const_iterator it = m_sizeCache.constBegin(); it != scend; ++it) {
            str << ' ' << it.key() << '=' << it.value();
        }
        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",
                                       0, QCoreApplication::CodecForTr,
                                       m_nameTypeMap.size()).arg(qtVersionString(), nameSpace);
}

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

void QtDumperHelper::setQtNamespace(const QString &qtNamespace)
{
    m_qtNamespace = 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
    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.remove(namespaceIndex + 2);
    }
    if (s == QLatin1String("QObject"))
        return QObjectType;
    if (s == QLatin1String("QWidget"))
        return QWidgetType;
    if (s == QLatin1String("QObjectSlot"))
        return QObjectSlotType;
    if (s == QLatin1String("QObjectSignal"))
        return QObjectSignalType;
    if (s == QLatin1String("QVector"))
        return QVectorType;
    if (s == QLatin1String("QMap"))
        return QMapType;
    if (s == QLatin1String("QMultiMap"))
        return QMultiMapType;
    if (s == QLatin1String("QMapNode"))
        return QMapNodeType;
    return UnknownType;
}

bool QtDumperHelper::needsExpressionSyntax(Type t)
{
    switch (t) {
        case QObjectType:
        case QWidgetType:
        case QObjectSlotType:
        case QObjectSignalType:
        case QMapType:
        case QVectorType:
        case QMultiMapType:
        case QMapNodeType:
        case StdMapType:
            return true;
        default:
            break;
hjk's avatar
hjk committed
    }
    return false;
}

QString QtDumperHelper::qtVersionString() const
{
    QString rc;
    QTextStream str(&rc);
    formatQtVersion(m_qtVersion, str);
    return rc;
}

void QtDumperHelper::setQtVersion(int v)
{
    m_qtVersion = v;
}

void QtDumperHelper::setQtVersion(const QString &v)
{
    m_qtVersion = 0;
    const QStringList vl = v.split(QLatin1Char('.'));
    if (vl.size() == 3) {
        const int major = vl.at(0).toInt();
        const int minor = vl.at(1).toInt();
        const int patch = vl.at(2).toInt();
        m_qtVersion = (major << 16) | (minor << 8) | patch;
    }
}

// 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
hjk's avatar
hjk committed
            if (debugger == GdbDebugger || !needsExpressionSyntax(t))
                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. */

hjk's avatar
hjk committed
class DumperParser
{
public:
    explicit DumperParser(const char *s) : m_s(s) {}
    bool run();

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)
        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)
        qDebug() << Q_FUNC_INFO << '\n' << QByteArray(k, size);
    return true;
}

bool DumperParser::handleListStart()
{
    if (debug)
        qDebug() << Q_FUNC_INFO;
    return true;
}

bool DumperParser::handleListEnd()
{
    if (debug)
        qDebug() << Q_FUNC_INFO;
    return true;
}

bool DumperParser::handleHashStart()
{
    if (debug)
        qDebug() << Q_FUNC_INFO;
    return true;
}

bool DumperParser::handleHashEnd()
{
    if (debug)
        qDebug() << Q_FUNC_INFO;

    return true;
}

bool DumperParser::handleValue(const char *k, int size)
{
    if (debug)
        qDebug() << Q_FUNC_INFO << '\n' << QByteArray(k, size);
    return true;
}

/* Parse 'query' (1) protocol response of the custom dumpers:
 * "'dumpers=["QByteArray","QDateTime",..."std::basic_string",],
 * qtversion=["4","5","1"],namespace="""' */

class QueryDumperParser : public DumperParser {
public:
    typedef QPair<QString, int> SizeEntry;
    explicit QueryDumperParser(const char *s);

    struct Data {
        Data() : qtVersion(0) {}
        QString qtNameSpace;
        QString qtVersion;
        QStringList types;
        QList<SizeEntry> sizes;
    };

    inline Data data() const { return m_data; }

protected:
    virtual bool handleKeyword(const char *k, int size);
    virtual bool handleListStart();    
    virtual bool handleListEnd();
    virtual bool handleHashEnd();
    virtual bool handleValue(const char *k, int size);

private:
    enum Mode { None, ExpectingDumpers, ExpectingVersion, ExpectingNameSpace, ExpectingSizes };
    Mode m_mode;
    Data m_data;
    QString m_lastSizeType;
};

QueryDumperParser::QueryDumperParser(const char *s) :
    DumperParser(s),
    m_mode(None)
{
}

bool QueryDumperParser::handleKeyword(const char *k, int size)        
{    
    if (m_mode == ExpectingSizes) {
        m_lastSizeType = QString::fromLatin1(k, size);
        return true;
    }
    if (!qstrncmp(k, "dumpers", size)) {
        m_mode = ExpectingDumpers;
        return true;
    }
    if (!qstrncmp(k, "qtversion", size)) {
        m_mode = ExpectingVersion;
        return true;
    }
    if (!qstrncmp(k, "namespace", size)) {
        m_mode = ExpectingNameSpace;
        return true;
    }
    if (!qstrncmp(k, "sizes", size)) {
        m_mode = ExpectingSizes;
        return true;
    }
    qWarning("%s Unexpected keyword %s.\n", Q_FUNC_INFO, QByteArray(k, size).constData());
    return false;
}

bool QueryDumperParser::handleListStart()
{
    return m_mode == ExpectingDumpers || m_mode == ExpectingVersion;
}

bool QueryDumperParser::handleListEnd()
{
    m_mode = None;
    return true;
}

bool QueryDumperParser::handleHashEnd()
{
    m_mode = None; // Size hash
    return true;
}

bool QueryDumperParser::handleValue(const char *k, int size)
{
    switch (m_mode) {
        return false;
    case ExpectingDumpers:
        m_data.types.push_back(QString::fromLatin1(k, size));
        break;
    case ExpectingNameSpace:
        m_data.qtNameSpace = QString::fromLatin1(k, size);
        break;
    case ExpectingVersion: // ["4","1","5"]
        if (!m_data.qtVersion.isEmpty())
            m_data.qtVersion += QLatin1Char('.');
        m_data.qtVersion += QString::fromLatin1(k, size);
        break;
    case ExpectingSizes:
        m_data.sizes.push_back(SizeEntry(m_lastSizeType, QString::fromLatin1(k, size).toInt()));
        break;
    }
    return true;
}

// parse a query
bool QtDumperHelper::parseQuery(const char *data, Debugger debugger)
{
    QueryDumperParser parser(data);
    if (!parser.run())
        return false;
    clear();
    m_qtNamespace = parser.data().qtNameSpace;
    setQtVersion(parser.data().qtVersion);
    parseQueryTypes(parser.data().types, debugger);
    foreach (const QueryDumperParser::SizeEntry &se, parser.data().sizes)
        addSize(se.first, se.second);
void QtDumperHelper::addSize(const QString &name, int size)
{
    // Special interest cases
    do {
        if (name == QLatin1String("char*")) {
            m_pointerSize = size;
            break;
        }
        if (name == QLatin1String("int")) {
            m_intSize = size;
            break;
        }
        if (name.startsWith(m_stdAllocatorPrefix)) {
            m_stdAllocatorSize = size;
            break;
        }
        if (name == QLatin1String("std::string")) {
            m_sizeCache.insert(QLatin1String("std::basic_string<char,std::char_traits<char>,std::allocator<char>>"), size);
            break;
        }
        if (name == QLatin1String("std::wstring")) {
            m_sizeCache.insert(QLatin1String("std::basic_string<unsigned short,std::char_traits<unsignedshort>,std::allocator<unsignedshort> >"), 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);