Skip to content
Snippets Groups Projects
watchutils.cpp 53.5 KiB
Newer Older
/**************************************************************************
**
** 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://www.qtsoftware.com/contact.
**
**************************************************************************/

#include "watchutils.h"
#include "watchhandler.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(".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("/="))
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
        || 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
    // 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;
            --level;
            *(level == 0 ? tmplate : inner) += c;
            *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 int") << QLatin1String("unsigned long")
        << QLatin1String("long long")  << QLatin1String("unsigned long long");
    return types.contains(type);
}

bool isIntOrFloatType(const QString &type)
{
    static const QStringList types = QStringList()
        << QLatin1String("float") << QLatin1String("double");
    return isIntType(type) || 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.

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('"'));
            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;
}

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

QtDumperResult::Child::Child() :
   childCount(-1),
   valuedisabled(false),
   valueEncountered(false)
{
}

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

void QtDumperResult::clear()
{
    iname.clear();
    value.clear();
    address.clear();
    type.clear();
    valueEncoded = 0;
    valueEncountered = valuedisabled = false;
    childCount = -1;
    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);
    if (valueEncountered) {
        root.setValue(decodeData(value, valueEncoded));
        root.valuedisabled = valuedisabled;
    }
    root.setType(displayedType.isEmpty() ? type : displayedType);
    root.setAddress(address);
    root.source = source;
    if (childCount >= 0)
        root.setHasChildren(childCount > 0);
    // Children. Sanity check after parsing sets childcount to list size
    // if list is not empty
    if (children.empty()) {
        if (childCount > 0)
            root.setChildrenNeeded();
    } else {
        root.setChildrenUnneeded();
        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;
            // Use key entry as name (which is used for map nodes)
            if (dchild.key.isEmpty()) {
                wchild.name = dchild.name;
            } else {
                wchild.name = decodeData(dchild.key, dchild.keyEncoded);
                if (wchild.name.size() > 13) {
                    wchild.name.truncate(12);
                    wchild.name += QLatin1String("...");
            }
            wchild.exp = dchild.exp;
            if (dchild.valueEncountered) {
                wchild.valuedisabled = dchild.valuedisabled;
                wchild.setValue(decodeData(dchild.value, dchild.valueEncoded));
            }
            wchild.setType(dchild.type.isEmpty() ? childType : dchild.type);
            wchild.setAddress(dchild.address);
            // Child overrides.
            const int effectiveChildChildCount = dchild.childCount == -1 ?  childChildCount : dchild.childCount;
            switch (effectiveChildChildCount) {
                case -1:
                wchild.setChildrenNeeded();
                wchild.setHasChildrenNeeded();
                break;
                case 0:
                break;
                default:
                wchild.setHasChildren(true);
                break;
    if (debug) {
        QDebug nospace = qDebug().nospace();
        nospace << "QtDumperResult::toWatchData" << *this << '\n';
        foreach(const WatchData &wd, rc)
            nospace << "  " << wd.toString() << '\n';
    }

    return rc;
}

QDebug operator<<(QDebug in, const QtDumperResult &d)
{
    QDebug nospace = in.nospace();
    nospace << " iname=" << d.iname << " type=" << d.type << " displayed=" << d.displayedType
            << " address=" << d.address;
    if (!d.addressInfo.isEmpty())
        nospace << " addressInfo=" << d.addressInfo;
    if (d.valueEncountered) {
        nospace << " encoded=" << d.valueEncoded
                << " value="  << d.value
                << " disabled=" << d.valuedisabled;
    } else {
        nospace  << " <no value>";
    }
    nospace << " childnumchild=" << d.childChildCount
            << " internal=" << d.internal
            << " extra='" << d.extra << "'\n";
    const int realChildCount = d.children.size();
    if (d.childCount || realChildCount) {
        nospace << "childCount=" << d.childCount << '/' << realChildCount
                << " childType=" << d.childType << '\n';
        for (int i = 0; i < realChildCount; i++) {
            const QtDumperResult::Child &c = d.children.at(i);
            nospace << "   #" << i << " addr=" << c.address
                    << " disabled=" << c.valuedisabled
                    << " type=" << c.type << " exp=" << c.exp
                    << " name=" << c.name;
            if (!c.key.isEmpty())
                nospace << " keyencoded=" << c.keyEncoded << " key=" << c.key;
            if (c.valueEncountered) {
                nospace << " valueencoded=" << c.valueEncoded << " value=" << c.value;
            } else {
                nospace  << " <no value>";
            }
            nospace  << "childcount=" << c.childCount << '\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_qtVersion(0)
{
    qFill(m_specialSizes, m_specialSizes + SpecialSizeCount, 0);
}

void QtDumperHelper::clear()
{
    m_nameTypeMap.clear();
    m_qtVersion = 0;
    m_qtNamespace.clear();
    m_sizeCache.clear();
    qFill(m_specialSizes, m_specialSizes + SpecialSizeCount, 0);
    m_expressionCache.clear();
}

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();
        }
        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, 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;
}

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
    // 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("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("QAbstractItem"))
        return QAbstractItemType;
    if (s == QLatin1String("QMap"))
        return QMapType;
    if (s == QLatin1String("QMultiMap"))
        return QMultiMapType;
    if (s == QLatin1String("QMapNode"))
        return QMapNodeType;
    return UnknownType;
}

QtDumperHelper::ExpressionRequirement QtDumperHelper::expressionRequirements(Type t)
    switch (t) {
    case QAbstractItemType:
    case QVectorType:
    case StdMapType:
        return NeedsComplexExpression;
    case QMapType:
    case QMultiMapType:
    case QMapNodeType:
        return NeedsCachedExpression;
    default:
        // QObjectSlotType, QObjectSignalType need the signal number, which is numeric
hjk's avatar
hjk committed
    }
}

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
            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. */

hjk's avatar
hjk committed
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)
        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)
        if (!parseValue(level + 1, pos))
        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 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)