Skip to content
Snippets Groups Projects
watchutils.cpp 53.2 KiB
Newer Older
            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;
        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,
    // Look up special size types
    const SpecialSizeType st = specialSizeType(typeName);
    if (st != SpecialSizeCount) {
        if (const int size = m_specialSizes[st])
            return QString::number(size);
    }
    // Look up size cache
    const SizeCache::const_iterator sit = m_sizeCache.constFind(typeName);
    if (sit != m_sizeCache.constEnd())
        return QString::number(sit.value());
    // Finally have the debugger evaluate
    return sizeofTypeExpression(typeName, debugger);
QtDumperHelper::SpecialSizeType QtDumperHelper::specialSizeType(const QString &typeName) const
{
    if (isPointerType(typeName))
        return PointerSize;
    static const QString intType = QLatin1String("int");
    static const QString stdAllocatorPrefix = QLatin1String("std::allocator");
    if (typeName == intType)
        return IntSize;
    if (typeName.startsWith(stdAllocatorPrefix))
        return StdAllocatorSize;
    if (typeName.startsWith(m_qPointerPrefix))
    if (typeName.startsWith(m_qSharedPointerPrefix))
    if (typeName.startsWith(m_qSharedDataPointerPrefix))
    if (typeName.startsWith(m_qWeakPointerPrefix))
    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());
                // FIXME: doesn't work for QMultiMap
                nodetype  = data.type + QLatin1String("::Node");
            //qDebug() << "OUTERTYPE: " << outertype << " NODETYPE: " << nodetype
            //    << "QT VERSION" << m_qtVersion << ((4 << 16) + (5 << 8) + 0);
            extraArgs[2] = evaluationSizeofTypeExpression(nodetype, debugger);
            extraArgs[3] = qMapNodeValueOffsetExpression(nodetype, data.addr, debugger);
        extraArgs[2] = evaluationSizeofTypeExpression(data.type, debugger);
        extraArgs[3] = qMapNodeValueOffsetExpression(data.type, data.addr, debugger);
        break;
    case StdVectorType:
        //qDebug() << "EXTRACT TEMPLATE: " << outertype << inners;
        if (inners.at(0) == QLatin1String("bool")) {
            outertype = QLatin1String("std::vector::bool");
        }
        break;
    case StdDequeType:
        extraArgs[1] = zero;
    case StdStackType:
        // remove 'std::allocator<...>':
        extraArgs[1] = zero;
        break;
    case StdSetType:
        // remove 'std::less<...>':
        extraArgs[1] = zero;
        // remove 'std::allocator<...>':
        extraArgs[2] = zero;
        break;
    case StdMapType: {
            // We need the offset of the second item in the value pair.
            // We read the type of the pair from the allocator argument because
            // that gets the constness "right" (in the sense that gdb/cdb can
            // read it back: "std::allocator<std::pair<Key,Value> >"
            // -> "std::pair<Key,Value>". Different debuggers have varying
            // amounts of terminating blanks...
            extraArgs[2].clear();
            extraArgs[3] = zero;
            QString pairType = inners.at(3);
            int bracketPos = pairType.indexOf(QLatin1Char('<'));
            if (bracketPos != -1)
                pairType.remove(0, bracketPos + 1);
            // We don't want the comparator and the allocator confuse gdb.
            const QChar closingBracket = QLatin1Char('>');
            bracketPos = pairType.lastIndexOf(closingBracket);
            if (bracketPos != -1)
                bracketPos = pairType.lastIndexOf(closingBracket, bracketPos - pairType.size() - 1);
            if (bracketPos != -1)
                pairType.truncate(bracketPos + 1);
            if (debugger == GdbDebugger) {
                extraArgs[2] = QLatin1String("(size_t)&(('");
                extraArgs[2] += pairType;
                extraArgs[2] += QLatin1String("'*)0)->second");
            } else {
                // Cdb: The std::pair is usually in scope. Still, this expression
                // occasionally fails for complex types (std::string).
                // We need an address as CDB cannot do the 0-trick.
                // Use data address or try at least cache if missing.
                const QString address = data.addr.isEmpty() ? QString::fromLatin1("DUMMY_ADDRESS") : data.addr;
                QString offsetExpr;
                QTextStream str(&offsetExpr);
                str << "(size_t)&(((" << pairType << " *)" << address << ")->second)" << '-' << address;
                extraArgs[2] = lookupCdbDummyAddressExpression(offsetExpr, address);
            }
        }
        break;
    case StdStringType:
        //qDebug() << "EXTRACT TEMPLATE: " << outertype << inners;
        if (inners.at(0) == QLatin1String("char")) {
            outertype = QLatin1String("std::string");
        } else if (inners.at(0) == QLatin1String("wchar_t")) {
            outertype = QLatin1String("std::wstring");
        }
        qFill(extraArgs, zero);
        break;
    case UnknownType:
        qWarning("Unknown type encountered in %s.\n", Q_FUNC_INFO);
        break;
    case SupportedType:
    case QStackType:
    // Look up expressions in the cache
    if (!m_expressionCache.empty()) {
        const QMap<QString, QString>::const_iterator excCend = m_expressionCache.constEnd();
        const QStringList::iterator eend = extraArgs.end();
        for (QStringList::iterator it = extraArgs.begin(); it != eend; ++it) {
            QString &e = *it;
            if (!e.isEmpty() && e != zero && !isInteger(e)) {
                const QMap<QString, QString>::const_iterator eit = m_expressionCache.constFind(e);
                if (eit != excCend)
                    e = eit.value();
            }
        }
    }

    inBuffer->clear();
    inBuffer->append(outertype.toUtf8());
    inBuffer->append('\0');
    inBuffer->append(data.iname);
    inBuffer->append('\0');
    inBuffer->append('\0');
    inBuffer->append(inner.toUtf8());
    inBuffer->append('\0');
    inBuffer->append(data.iname);

    if (debug)
        qDebug() << '\n' << Q_FUNC_INFO << '\n' << data.toString() << "\n-->" << outertype << td.type << extraArgs;
}

// Return debugger expression to get the offset of a map node.
QString QtDumperHelper::qMapNodeValueOffsetExpression(const QString &type,
                                                      const QString &addressIn,
                                                      Debugger debugger) const
{
    switch (debugger) {
    case GdbDebugger:
        return QLatin1String("(size_t)&(('") + type + QLatin1String("'*)0)->value");
    case CdbDebugger: {
            // Cdb: This will only work if a QMapNode is in scope.
            // We need an address as CDB cannot do the 0-trick.
            // Use data address or try at least cache if missing.
            const QString address = addressIn.isEmpty() ? QString::fromLatin1("DUMMY_ADDRESS") : addressIn;
            QString offsetExpression;
            QTextStream(&offsetExpression) << "(size_t)&(((" << type
                    << " *)" << address << ")->value)-" << address;
            return lookupCdbDummyAddressExpression(offsetExpression, address);
        }
    }
    return QString();
}

/* Cdb cannot do tricks like ( "&(std::pair<int,int>*)(0)->second)",
 * that is, use a null pointer to determine the offset of a member.
 * It tries to dereference the address at some point and fails with
 * "memory access error". As a trick, use the address of the watch item
 * to do this. However, in the expression cache, 0 is still used, so,
 * for cache lookups,  use '0' as address. */
QString QtDumperHelper::lookupCdbDummyAddressExpression(const QString &expr,
                                                        const QString &address) const
{
    QString nullExpr = expr;
    nullExpr.replace(address, QString(QLatin1Char('0')));
    const QString rc = m_expressionCache.value(nullExpr, expr);
    if (debug)
        qDebug() << "lookupCdbDummyAddressExpression" << expr << rc;
    return rc;
}

// GdbMi parsing helpers for parsing dumper value results
static bool gdbMiGetIntValue(int *target,
                             const GdbMi &node,
                             const char *child)
    *target = -1;
    const GdbMi childNode = node.findChild(child);
    if (!childNode.isValid())
        return false;
    bool ok;
    *target = childNode.data().toInt(&ok);
    return ok;
// Find a string child node and assign value if it exists.
// Optionally decode.
static bool gdbMiGetStringValue(QString *target,
                             const GdbMi &node,
                             const char *child,
                             const char *encodingChild = 0)
    target->clear();
    const GdbMi childNode = node.findChild(child);
    if (!childNode.isValid())
        return false;
    // Encoded data
    if (encodingChild) {
        int encoding;
        if (!gdbMiGetIntValue(&encoding, node, encodingChild))
            encoding = 0;
        *target = decodeData(childNode.data(), encoding);
        return true;
    // Plain data
    *target = QLatin1String(childNode.data());
static bool gdbMiGetByteArrayValue(QByteArray *target,
                             const GdbMi &node,
                             const char *child,
                             const char *encodingChild = 0)
{
    QString str;
    const bool success = gdbMiGetStringValue(&str, node, child, encodingChild);
    *target = str.toLatin1();
    return success;
}

static bool gdbMiGetBoolValue(bool *target,
                             const GdbMi &node,
                             const char *child)
    *target = false;
    const GdbMi childNode = node.findChild(child);
    if (!childNode.isValid())
        return false;
    *target = childNode.data() == "true";
/* 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. */

    GdbMiRecursionContext(int recursionLevelIn = 0) :
            recursionLevel(recursionLevelIn), childNumChild(-1), childIndex(0) {}

    int recursionLevel;
    int childNumChild;
    int childIndex;
    QString childType;
};

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
        QString iname;
        if (!gdbMiGetStringValue(&iname, root, "iname"))
            qWarning("Internal error: iname missing");
        w.iname = iname.toLatin1();
        w.name = iname;
        const int lastDotPos = w.name.lastIndexOf(QLatin1Char('.'));
        if (lastDotPos != -1)
            w.name.remove(0, lastDotPos + 1);
    } else {
        // Children can have a 'name' attribute. If missing, assume array index
        // For display purposes, it can be overridden by "key"
        if (!gdbMiGetStringValue(&w.name, root, "name")) {
            w.name = QString::number(ctx.childIndex);
        }
        // Set iname
        w.iname = ctx.parentIName;
        w.iname += '.';
        w.iname += w.name.toLatin1();
        // Key?
        QString key;
        if (gdbMiGetStringValue(&key, root, "key", "keyencoded")) {
            w.name = key.size() > 13 ? key.mid(0, 13) + QLatin1String("...") : key;
        }
    }
    if (w.name.isEmpty()) {
        const QString msg = QString::fromLatin1("Internal error: Unable to determine name at level %1/%2 for %3").arg(ctx.recursionLevel).arg(w.iname, QLatin1String(root.toString(true, 2)));
        qWarning("%s\n", qPrintable(msg));
    }
    gdbMiGetStringValue(&w.displayedType, root, "displayedtype");
    if (gdbMiGetByteArrayValue(&b, root, "editvalue"))
        w.editvalue = b;
    if (gdbMiGetByteArrayValue(&b, root, "exp"))
        w.exp = b;
    gdbMiGetByteArrayValue(&w.addr, root, "addr");
    gdbMiGetByteArrayValue(&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)
    GdbMi root;
    root.fromStringMultiple(QByteArray(data));
    if (!root.isValid())
        return false;
    gbdMiToWatchData(root, GdbMiRecursionContext(), l);
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;
}

hjk's avatar
hjk committed
} // namespace Internal
} // namespace Debugger