From 7de7eb6bcada805eff176634e8131c4914e1213d Mon Sep 17 00:00:00 2001 From: hjk Date: Wed, 16 Dec 2015 17:17:38 +0100 Subject: [PATCH] Debugger: Work on WatchModel performance Don't instantiate repeating boilerplate item data in some cases (such as large arrays). This makes it necessary to access parent WatchItems in a lot more cases than before and needs another separation of WatchItem/WatchModel code to keep the dumper autotests in a functional state. For a plain std::vector with 1 mio items this reduces extraction time from more than 2 minutes to about 3 seconds. Change-Id: I175c5f6ee90434a6e85342d8bb71bd10a04dd271 Reviewed-by: Christian Stenger Reviewed-by: David Schulz --- share/qtcreator/debugger/gdbbridge.py | 10 + share/qtcreator/debugger/stdtypes.py | 10 +- src/plugins/debugger/debuggerengine.cpp | 5 +- src/plugins/debugger/debuggerengine.h | 1 - src/plugins/debugger/debuggerplugin.cpp | 1 - .../debugger/debuggertooltipmanager.cpp | 24 +- src/plugins/debugger/lldb/lldbengine.h | 1 - src/plugins/debugger/pdb/pdbengine.cpp | 7 +- src/plugins/debugger/qml/qmlengine.cpp | 16 +- src/plugins/debugger/qml/qmlengine.h | 1 - .../debugger/qml/qmlinspectoragent.cpp | 50 +- src/plugins/debugger/qml/qmlinspectoragent.h | 6 +- src/plugins/debugger/sourceutils.cpp | 2 +- src/plugins/debugger/watchdata.cpp | 667 +++++++-------- src/plugins/debugger/watchdata.h | 64 +- src/plugins/debugger/watchhandler.cpp | 798 +++++++++--------- src/plugins/debugger/watchhandler.h | 40 - tests/auto/debugger/dumpers.pro | 4 +- tests/auto/debugger/tst_dumpers.cpp | 83 +- 19 files changed, 878 insertions(+), 912 deletions(-) diff --git a/share/qtcreator/debugger/gdbbridge.py b/share/qtcreator/debugger/gdbbridge.py index 150b0cb09ba..b634375501c 100644 --- a/share/qtcreator/debugger/gdbbridge.py +++ b/share/qtcreator/debugger/gdbbridge.py @@ -354,7 +354,13 @@ class Dumper(DumperBase): def canCallLocale(self): return False if self.is32bit() else True + def reportTime(self, hint): + #from datetime import datetime + #warn("%s: %s" % (hint, datetime.now().time().isoformat())) + pass + def fetchVariables(self, args): + self.reportTime("begin fetch") self.prepare(args) partialVariable = args.get("partialvar", "") isPartial = len(partialVariable) > 0 @@ -390,6 +396,8 @@ class Dumper(DumperBase): else: locals = self.listOfLocals() + self.reportTime("locals") + # Take care of the return value of the last function call. if len(self.resultVarName) > 0: try: @@ -439,7 +447,9 @@ class Dumper(DumperBase): self.output.append(',partial="%d"' % isPartial) + self.reportTime("before print: %s" % len(self.output)) safePrint(''.join(self.output)) + self.reportTime("after print") def enterSubItem(self, item): if not item.iname: diff --git a/share/qtcreator/debugger/stdtypes.py b/share/qtcreator/debugger/stdtypes.py index 000cd0a73a8..a40ce296be8 100644 --- a/share/qtcreator/debugger/stdtypes.py +++ b/share/qtcreator/debugger/stdtypes.py @@ -807,7 +807,10 @@ def qdump__std__vector(d, value): base = d.pointerValue(start) for i in d.childRange(): q = base + int(i / 8) - d.putBoolItem(str(i), (int(d.extractPointer(q)) >> (i % 8)) & 1) + with SubItem(d, i): + d.putValue((int(d.extractPointer(q)) >> (i % 8)) & 1) + d.putType("bool") + d.putNumChild(0) else: d.putPlotData(start, size, type) @@ -840,7 +843,10 @@ def qdump__std__vector__QNX(d, value): with Children(d, size, maxNumChild=10000, childType=innerType): for i in d.childRange(): q = start + int(i / storagesize) - d.putBoolItem(str(i), (q.dereference() >> (i % storagesize)) & 1) + with SubItem(d, i): + d.putValue((q.dereference() >> (i % storagesize)) & 1) + d.putType("bool") + d.putNumChild(0) else: d.putArrayData(start, size, innerType) diff --git a/src/plugins/debugger/debuggerengine.cpp b/src/plugins/debugger/debuggerengine.cpp index 10e673fd7cb..b9b8de63fe2 100644 --- a/src/plugins/debugger/debuggerengine.cpp +++ b/src/plugins/debugger/debuggerengine.cpp @@ -1982,9 +1982,10 @@ void DebuggerEngine::updateLocalsView(const GdbMi &all) GdbMi data = all["data"]; foreach (const GdbMi &child, data.children()) { - WatchItem *item = new WatchItem(child); + WatchItem *item = new WatchItem; + item->parse(child); const TypeInfo ti = d->m_typeInfoCache.value(item->type); - if (ti.size) + if (ti.size && !item->size) item->size = ti.size; handler->insertItem(item); diff --git a/src/plugins/debugger/debuggerengine.h b/src/plugins/debugger/debuggerengine.h index d8a704e0198..09f4595e0b7 100644 --- a/src/plugins/debugger/debuggerengine.h +++ b/src/plugins/debugger/debuggerengine.h @@ -63,7 +63,6 @@ class DebuggerEnginePrivate; class DebuggerPluginPrivate; class DisassemblerAgent; class MemoryAgent; -class WatchData; class WatchItem; class BreakHandler; class LocationMark; diff --git a/src/plugins/debugger/debuggerplugin.cpp b/src/plugins/debugger/debuggerplugin.cpp index 6b08077ebe9..e152046698a 100644 --- a/src/plugins/debugger/debuggerplugin.cpp +++ b/src/plugins/debugger/debuggerplugin.cpp @@ -1036,7 +1036,6 @@ DebuggerPluginPrivate::DebuggerPluginPrivate(DebuggerPlugin *plugin) : m_dummyEngine(0), m_globalDebuggerOptions(new GlobalDebuggerOptions) { - qRegisterMetaType("WatchData"); qRegisterMetaType("ContextData"); qRegisterMetaType("DebuggerRunParameters"); diff --git a/src/plugins/debugger/debuggertooltipmanager.cpp b/src/plugins/debugger/debuggertooltipmanager.cpp index 91a02ac3f2d..f8eaeecab3e 100644 --- a/src/plugins/debugger/debuggertooltipmanager.cpp +++ b/src/plugins/debugger/debuggertooltipmanager.cpp @@ -193,11 +193,11 @@ void DraggableLabel::mouseMoveEvent(QMouseEvent * event) // ///////////////////////////////////////////////////////////////////////// -class ToolTipWatchItem : public Utils::TreeItem +class ToolTipWatchItem : public TreeItem { public: ToolTipWatchItem() : expandable(false) {} - ToolTipWatchItem(WatchItem *item); + ToolTipWatchItem(TreeItem *item); bool hasChildren() const { return expandable; } bool canFetchMore() const { return childCount() == 0 && expandable && model(); } @@ -214,17 +214,19 @@ public: QByteArray iname; }; -ToolTipWatchItem::ToolTipWatchItem(WatchItem *item) +ToolTipWatchItem::ToolTipWatchItem(TreeItem *item) { - name = item->displayName(); - value = item->displayValue(); - type = item->displayType(); - iname = item->iname; - valueColor = item->valueColor(1); + const TreeModel *model = item->model(); + QModelIndex idx = model->indexForItem(item); + name = model->data(idx.sibling(idx.row(), 0), Qt::DisplayRole).toString(); + value = model->data(idx.sibling(idx.row(), 1), Qt::DisplayRole).toString(); + type = model->data(idx.sibling(idx.row(), 2), Qt::DisplayRole).toString(); + iname = model->data(idx.sibling(idx.row(), 0), LocalsINameRole).toByteArray(); + valueColor = model->data(idx.sibling(idx.row(), 1), Qt::ForegroundRole).value(); expandable = item->hasChildren(); - expression = item->expression(); + expression = model->data(idx.sibling(idx.row(), 0), Qt::EditRole).toString(); foreach (TreeItem *child, item->children()) - appendChild(new ToolTipWatchItem(static_cast(child))); + appendChild(new ToolTipWatchItem(child)); } ///////////////////////////////////////////////////////////////////////// @@ -1175,7 +1177,7 @@ static void slotTooltipOverrideRequested purgeClosedToolTips(); // Prefer a filter on an existing local variable if it can be found. - const WatchData *localVariable = engine->watchHandler()->findCppLocalVariable(context.expression); + const WatchItem *localVariable = engine->watchHandler()->findCppLocalVariable(context.expression); if (localVariable) { context.expression = QLatin1String(localVariable->exp); if (context.expression.isEmpty()) diff --git a/src/plugins/debugger/lldb/lldbengine.h b/src/plugins/debugger/lldb/lldbengine.h index 60b604eb9e6..093f3c90957 100644 --- a/src/plugins/debugger/lldb/lldbengine.h +++ b/src/plugins/debugger/lldb/lldbengine.h @@ -52,7 +52,6 @@ namespace Debugger { namespace Internal { -class WatchData; class GdbMi; /* A debugger engine interfacing the LLDB debugger diff --git a/src/plugins/debugger/pdb/pdbengine.cpp b/src/plugins/debugger/pdb/pdbengine.cpp index 57b242dc4bc..8e818531c0e 100644 --- a/src/plugins/debugger/pdb/pdbengine.cpp +++ b/src/plugins/debugger/pdb/pdbengine.cpp @@ -518,8 +518,11 @@ void PdbEngine::refreshLocals(const GdbMi &vars) WatchHandler *handler = watchHandler(); handler->resetValueCache(); - foreach (const GdbMi &child, vars.children()) - handler->insertItem(new WatchItem(child)); + foreach (const GdbMi &child, vars.children()) { + WatchItem *item = new WatchItem; + item->parse(child); + handler->insertItem(item); + } handler->notifyUpdateFinished(); diff --git a/src/plugins/debugger/qml/qmlengine.cpp b/src/plugins/debugger/qml/qmlengine.cpp index 4804da4b1dd..e7250b536a1 100644 --- a/src/plugins/debugger/qml/qmlengine.cpp +++ b/src/plugins/debugger/qml/qmlengine.cpp @@ -1089,11 +1089,11 @@ void QmlEngine::updateCurrentContext() context = stackHandler()->currentFrame().function; } else { QModelIndex currentIndex = inspectorView()->currentIndex(); - const WatchData *currentData = watchHandler()->watchItem(currentIndex); + const WatchItem *currentData = watchHandler()->watchItem(currentIndex); if (!currentData) return; - const WatchData *parentData = watchHandler()->watchItem(currentIndex.parent()); - const WatchData *grandParentData = watchHandler()->watchItem(currentIndex.parent().parent()); + const WatchItem *parentData = watchHandler()->watchItem(currentIndex.parent()); + const WatchItem *grandParentData = watchHandler()->watchItem(currentIndex.parent().parent()); if (currentData->id != parentData->id) context = currentData->name; else if (parentData->id != grandParentData->id) @@ -1351,7 +1351,9 @@ void QmlEnginePrivate::handleEvaluateExpression(const QVariantMap &response, QmlV8ObjectData body = extractData(bodyVal); WatchHandler *watchHandler = engine->watchHandler(); - auto item = new WatchItem(iname, exp); + auto item = new WatchItem; + item->iname = iname; + item->name = exp; item->exp = exp.toLatin1(); item->id = body.handle; bool success = response.value(_("success")).toBool(); @@ -2170,9 +2172,11 @@ void QmlEnginePrivate::handleFrame(const QVariantMap &response) { QByteArray iname = "local.this"; QString exp = QLatin1String("this"); - - auto item = new WatchItem(iname, exp); QmlV8ObjectData objectData = extractData(body.value(_("receiver"))); + + auto item = new WatchItem; + item->iname = iname; + item->name = exp; item->id = objectData.handle; item->type = objectData.type; item->value = objectData.value.toString(); diff --git a/src/plugins/debugger/qml/qmlengine.h b/src/plugins/debugger/qml/qmlengine.h index d4fba51fbb2..0fdccd5c52b 100644 --- a/src/plugins/debugger/qml/qmlengine.h +++ b/src/plugins/debugger/qml/qmlengine.h @@ -40,7 +40,6 @@ namespace Debugger { namespace Internal { -class WatchData; class WatchItem; class QmlEnginePrivate; class QmlInspectorAgent; diff --git a/src/plugins/debugger/qml/qmlinspectoragent.cpp b/src/plugins/debugger/qml/qmlinspectoragent.cpp index f641b6a8658..9d03b7f4358 100644 --- a/src/plugins/debugger/qml/qmlinspectoragent.cpp +++ b/src/plugins/debugger/qml/qmlinspectoragent.cpp @@ -74,12 +74,12 @@ QmlInspectorAgent::QmlInspectorAgent(QmlEngine *engine, QmlDebugConnection *conn , m_engineClient(0) , m_engineQueryId(0) , m_rootContextQueryId(0) - , m_objectToSelect(WatchData::InvalidId) + , m_objectToSelect(WatchItem::InvalidId) , m_masterEngine(engine) , m_toolsClient(0) , m_targetToSync(NoTarget) - , m_debugIdToSelect(WatchData::InvalidId) - , m_currentSelectedDebugId(WatchData::InvalidId) + , m_debugIdToSelect(WatchItem::InvalidId) + , m_currentSelectedDebugId(WatchItem::InvalidId) , m_toolsClientConnected(false) , m_inspectorToolsContext("Debugger.QmlInspector") , m_selectAction(new QAction(this)) @@ -87,7 +87,7 @@ QmlInspectorAgent::QmlInspectorAgent(QmlEngine *engine, QmlDebugConnection *conn , m_showAppOnTopAction(action(ShowAppOnTop)) , m_engineClientConnected(false) { - m_debugIdToIname.insert(WatchData::InvalidId, QByteArray("inspect")); + m_debugIdToIname.insert(WatchItem::InvalidId, QByteArray("inspect")); connect(action(ShowQmlObjectTree), &Utils::SavedAction::valueChanged, this, &QmlInspectorAgent::updateState); m_delayQueryTimer.setSingleShot(true); @@ -173,13 +173,13 @@ quint32 QmlInspectorAgent::queryExpressionResult(int debugId, m_engine.debugId()); } -void QmlInspectorAgent::assignValue(const WatchData *data, +void QmlInspectorAgent::assignValue(const WatchItem *data, const QString &expr, const QVariant &valueV) { qCDebug(qmlInspectorLog) << __FUNCTION__ << '(' << data->id << ')' << data->iname; - if (data->id != WatchData::InvalidId) { + if (data->id != WatchItem::InvalidId) { QString val(valueV.toString()); if (valueV.type() == QVariant::String) { val = val.replace(QLatin1Char('\"'), QLatin1String("\\\"")); @@ -195,17 +195,17 @@ static int parentIdForIname(const QByteArray &iname) // Extract the parent id int lastIndex = iname.lastIndexOf('.'); int secondLastIndex = iname.lastIndexOf('.', lastIndex - 1); - int parentId = WatchData::InvalidId; - if (secondLastIndex != WatchData::InvalidId) + int parentId = WatchItem::InvalidId; + if (secondLastIndex != WatchItem::InvalidId) parentId = iname.mid(secondLastIndex + 1, lastIndex - secondLastIndex - 1).toInt(); return parentId; } -void QmlInspectorAgent::updateWatchData(const WatchData &data) +void QmlInspectorAgent::updateWatchData(const WatchItem &data) { qCDebug(qmlInspectorLog) << __FUNCTION__ << '(' << data.id << ')'; - if (data.id != WatchData::InvalidId && !m_fetchDataIds.contains(data.id)) { + if (data.id != WatchItem::InvalidId && !m_fetchDataIds.contains(data.id)) { // objects m_fetchDataIds << data.id; fetchObject(data.id); @@ -216,7 +216,7 @@ void QmlInspectorAgent::watchDataSelected(qint64 id) { qCDebug(qmlInspectorLog) << __FUNCTION__ << '(' << id << ')'; - if (id != WatchData::InvalidId) { + if (id != WatchItem::InvalidId) { QTC_ASSERT(m_debugIdLocations.keys().contains(id), return); jumpToObjectDefinitionInEditor(m_debugIdLocations.value(id), id); if (m_toolsClient) @@ -278,7 +278,7 @@ ObjectReference QmlInspectorAgent::objectForId(int objectDebugId) const } } // TODO: Set correct parentId - return ObjectReference(objectDebugId, WatchData::InvalidId, + return ObjectReference(objectDebugId, WatchItem::InvalidId, FileReference(QUrl::fromLocalFile(file), line, column)); } @@ -286,7 +286,7 @@ void QmlInspectorAgent::addObjectWatch(int objectDebugId) { qCDebug(qmlInspectorLog) << __FUNCTION__ << '(' << objectDebugId << ')'; - if (objectDebugId == WatchData::InvalidId) + if (objectDebugId == WatchItem::InvalidId) return; if (!isConnected() || !boolSetting(ShowQmlObjectTree)) @@ -470,7 +470,7 @@ void QmlInspectorAgent::verifyAndInsertObjectInTree(const ObjectReference &objec // Find out the correct position in the tree // Objects are inserted to the tree if they satisfy one of the two conditions. - // Condition 1: Object is a root object i.e. parentId == WatchData::InvalidId. + // Condition 1: Object is a root object i.e. parentId == WatchItem::InvalidId. // Condition 2: Object has an expanded parent i.e. siblings are known. // If the two conditions are not met then we push the object to a stack and recursively // fetch parents till we find a previously expanded parent. @@ -480,7 +480,7 @@ void QmlInspectorAgent::verifyAndInsertObjectInTree(const ObjectReference &objec const int objectDebugId = object.debugId(); if (m_debugIdToIname.contains(parentId)) { QByteArray parentIname = m_debugIdToIname.value(parentId); - if (parentId != WatchData::InvalidId && !handler->isExpandedIName(parentIname)) { + if (parentId != WatchItem::InvalidId && !handler->isExpandedIName(parentIname)) { m_objectStack.push(object); handler->fetchMore(parentIname); return; // recursive @@ -535,7 +535,7 @@ void QmlInspectorAgent::insertObjectInTree(const ObjectReference &object) << timeElapsed.elapsed() << " ms"; if (object.debugId() == m_debugIdToSelect) { - m_debugIdToSelect = WatchData::InvalidId; + m_debugIdToSelect = WatchItem::InvalidId; selectObject(object, m_targetToSync); } @@ -544,7 +544,7 @@ void QmlInspectorAgent::insertObjectInTree(const ObjectReference &object) QByteArray iname = m_debugIdToIname.value(m_objectToSelect); qCDebug(qmlInspectorLog) << " selecting" << iname << "in tree"; m_qmlEngine->watchHandler()->setCurrentItem(iname); - m_objectToSelect = WatchData::InvalidId; + m_objectToSelect = WatchItem::InvalidId; } m_qmlEngine->watchHandler()->updateWatchersWindow(); m_qmlEngine->watchHandler()->reexpandItems(); @@ -613,7 +613,9 @@ void QmlInspectorAgent::addWatchData(const ObjectReference &obj, return; // object - auto objWatch = new WatchItem(objIname, name); + auto objWatch = new WatchItem; + objWatch->iname = objIname; + objWatch->name = name; objWatch->id = objDebugId; objWatch->exp = name.toLatin1(); objWatch->type = obj.className().toLatin1(); @@ -643,7 +645,9 @@ void QmlInspectorAgent::addWatchData(const ObjectReference &obj, // properties if (append && obj.properties().count()) { QByteArray iname = objIname + ".[properties]"; - auto propertiesWatch = new WatchItem(iname, tr("Properties")); + auto propertiesWatch = new WatchItem; + propertiesWatch->iname = iname; + propertiesWatch->name = tr("Properties"); propertiesWatch->id = objDebugId; propertiesWatch->value = _("list"); propertiesWatch->wantsChildren = true; @@ -653,7 +657,9 @@ void QmlInspectorAgent::addWatchData(const ObjectReference &obj, const QString propertyName = property.name(); if (propertyName.isEmpty()) continue; - auto propertyWatch = new WatchItem(buildIName(iname, propertyName), propertyName); + auto propertyWatch = new WatchItem; + propertyWatch->iname = buildIName(iname, propertyName); + propertyWatch->name = propertyName; propertyWatch->id = objDebugId; propertyWatch->exp = propertyName.toLatin1(); propertyWatch->type = property.valueTypeName().toLatin1(); @@ -700,7 +706,7 @@ void QmlInspectorAgent::clearObjectTree() m_debugIdHash.clear(); m_debugIdHash.reserve(old_count + 1); m_debugIdToIname.clear(); - m_debugIdToIname.insert(WatchData::InvalidId, QByteArray("inspect")); + m_debugIdToIname.insert(WatchItem::InvalidId, QByteArray("inspect")); m_objectStack.clear(); m_objectWatches.clear(); } @@ -870,7 +876,7 @@ void QmlInspectorAgent::jumpToObjectDefinitionInEditor( const QString fileName = m_masterEngine->toFileInProject(objSource.url()); Core::EditorManager::openEditorAt(fileName, objSource.lineNumber()); - if (debugId != WatchData::InvalidId && debugId != m_currentSelectedDebugId) { + if (debugId != WatchItem::InvalidId && debugId != m_currentSelectedDebugId) { m_currentSelectedDebugId = debugId; m_currentSelectedDebugName = displayName(debugId); } diff --git a/src/plugins/debugger/qml/qmlinspectoragent.h b/src/plugins/debugger/qml/qmlinspectoragent.h index 7e16424eae6..a7331465a2b 100644 --- a/src/plugins/debugger/qml/qmlinspectoragent.h +++ b/src/plugins/debugger/qml/qmlinspectoragent.h @@ -52,7 +52,7 @@ namespace Internal { class DebuggerEngine; class QmlEngine; -class WatchData; +class WatchItem; //map -> -> debugId typedef @@ -67,8 +67,8 @@ public: void fetchObject(int debugId); quint32 queryExpressionResult(int debugId, const QString &expression); - void assignValue(const WatchData *data, const QString &expression, const QVariant &valueV); - void updateWatchData(const WatchData &data); + void assignValue(const WatchItem *data, const QString &expression, const QVariant &valueV); + void updateWatchData(const WatchItem &data); void watchDataSelected(qint64 id); bool selectObjectInTree(int debugId); void addObjectWatch(int objectDebugId); diff --git a/src/plugins/debugger/sourceutils.cpp b/src/plugins/debugger/sourceutils.cpp index 2e55dc8cbdc..c8223b65d44 100644 --- a/src/plugins/debugger/sourceutils.cpp +++ b/src/plugins/debugger/sourceutils.cpp @@ -179,7 +179,7 @@ static void blockRecursion(const Overview &overview, // Is the declaration on or past the current line, that is, // the variable not initialized. if (symbol->line() >= line) - uninitializedVariables->push_back(WatchData::shadowedName(name, it.value())); + uninitializedVariables->push_back(WatchItem::shadowedName(name, it.value())); } } // Next block scope. diff --git a/src/plugins/debugger/watchdata.cpp b/src/plugins/debugger/watchdata.cpp index 2456424efc7..f98f5c08328 100644 --- a/src/plugins/debugger/watchdata.cpp +++ b/src/plugins/debugger/watchdata.cpp @@ -37,12 +37,6 @@ #include -//////////////////////////////////////////////////////////////////// -// -// WatchData -// -//////////////////////////////////////////////////////////////////// - namespace Debugger { namespace Internal { @@ -118,8 +112,8 @@ bool isIntOrFloatType(const QByteArray &type) return isIntType(type) || isFloatType(type); } -WatchData::WatchData() : - id(WatchData::InvalidId), +WatchItem::WatchItem() : + id(WatchItem::InvalidId), state(InitialState), editformat(StopDisplay), address(0), @@ -128,6 +122,7 @@ WatchData::WatchData() : bitpos(0), bitsize(0), elided(0), + arrayIndex(-1), wantsChildren(false), valueEnabled(true), valueEditable(true), @@ -135,16 +130,7 @@ WatchData::WatchData() : { } -bool WatchData::isAncestorOf(const QByteArray &childIName) const -{ - if (iname.size() >= childIName.size()) - return false; - if (!childIName.startsWith(iname)) - return false; - return childIName.at(iname.size()) == '.'; -} - -bool WatchData::isVTablePointer() const +bool WatchItem::isVTablePointer() const { // First case: Cdb only. No user type can be named like this, this is safe. // Second case: Python dumper only. @@ -152,7 +138,7 @@ bool WatchData::isVTablePointer() const || (type.isEmpty() && name == QLatin1String("[vptr]")); } -void WatchData::setError(const QString &msg) +void WatchItem::setError(const QString &msg) { setAllUnneeded(); value = msg; @@ -161,7 +147,7 @@ void WatchData::setError(const QString &msg) valueEditable = false; } -void WatchData::setValue(const QString &value0) +void WatchItem::setValue(const QString &value0) { value = value0; if (value == QLatin1String("{...}")) { @@ -216,7 +202,7 @@ static GuessChildrenResult guessChildren(const QByteArray &type) return HasPossiblyChildren; } -void WatchData::setType(const QByteArray &str, bool guessChildrenFromType) +void WatchItem::setType(const QByteArray &str, bool guessChildrenFromType) { type = str.trimmed(); bool changed = true; @@ -253,7 +239,7 @@ void WatchData::setType(const QByteArray &str, bool guessChildrenFromType) } } -QString WatchData::toString() const +QString WatchItem::toString() const { const char *doubleQuoteComma = "\","; QString res; @@ -299,6 +285,254 @@ QString WatchData::toString() const return res + QLatin1Char('}'); } +QString WatchItem::msgNotInScope() +{ + //: Value of variable in Debugger Locals display for variables out + //: of scope (stopped above initialization). + static const QString rc = + QCoreApplication::translate("Debugger::Internal::WatchItem", ""); + return rc; +} + +const QString &WatchItem::shadowedNameFormat() +{ + //: Display of variables shadowed by variables of the same name + //: in nested scopes: Variable %1 is the variable name, %2 is a + //: simple count. + static const QString format = + QCoreApplication::translate("Debugger::Internal::WatchItem", "%1 "); + return format; +} + +QString WatchItem::shadowedName(const QString &name, int seen) +{ + if (seen <= 0) + return name; + return shadowedNameFormat().arg(name).arg(seen); +} + +QByteArray WatchItem::hexAddress() const +{ + if (address) + return QByteArray("0x") + QByteArray::number(address, 16); + return QByteArray(); +} + +template +QString decodeItemHelper(const T &t) +{ + return QString::number(t); +} + +QString decodeItemHelper(const double &t) +{ + return QString::number(t, 'g', 16); +} + +template +void decodeArrayHelper(WatchItem *item, const QByteArray &rawData, int size, const QByteArray &childType) +{ + const QByteArray ba = QByteArray::fromHex(rawData); + const T *p = (const T *) ba.data(); + for (int i = 0, n = ba.size() / sizeof(T); i < n; ++i) { + WatchItem *child = new WatchItem; + child->arrayIndex = i; + child->value = decodeItemHelper(p[i]); + child->size = size; + child->type = childType; + child->setAllUnneeded(); + item->appendChild(child); + } +} + +static void decodeArrayData(WatchItem *item, const QByteArray &rawData, + const DebuggerEncoding &encoding, const QByteArray &childType) +{ + switch (encoding.type) { + case DebuggerEncoding::HexEncodedSignedInteger: + switch (encoding.size) { + case 1: + return decodeArrayHelper(item, rawData, encoding.size, childType); + case 2: + return decodeArrayHelper(item, rawData, encoding.size, childType); + case 4: + return decodeArrayHelper(item, rawData, encoding.size, childType); + case 8: + return decodeArrayHelper(item, rawData, encoding.size, childType); + } + case DebuggerEncoding::HexEncodedUnsignedInteger: + switch (encoding.size) { + case 1: + return decodeArrayHelper(item, rawData, encoding.size, childType); + case 2: + return decodeArrayHelper(item, rawData, encoding.size, childType); + case 4: + return decodeArrayHelper(item, rawData, encoding.size, childType); + case 8: + return decodeArrayHelper(item, rawData, encoding.size, childType); + } + break; + case DebuggerEncoding::HexEncodedFloat: + switch (encoding.size) { + case 4: + return decodeArrayHelper(item, rawData, encoding.size, childType); + case 8: + return decodeArrayHelper(item, rawData, encoding.size, childType); + } + default: + break; + } + qDebug() << "ENCODING ERROR: " << encoding.toString(); +} + +void WatchItem::parseHelper(const GdbMi &input) +{ + setChildrenUnneeded(); + + GdbMi mi = input["type"]; + if (mi.isValid()) + setType(mi.data()); + + editvalue = input["editvalue"].data(); + editformat = DebuggerDisplay(input["editformat"].toInt()); + editencoding = DebuggerEncoding(input["editencoding"].data()); + + mi = input["valueelided"]; + if (mi.isValid()) + elided = mi.toInt(); + + mi = input["bitpos"]; + if (mi.isValid()) + bitpos = mi.toInt(); + + mi = input["bitsize"]; + if (mi.isValid()) + bitsize = mi.toInt(); + + mi = input["origaddr"]; + if (mi.isValid()) + origaddr = mi.toAddress(); + + mi = input["address"]; + if (mi.isValid()) { + address = mi.toAddress(); + if (exp.isEmpty()) { + if (iname.startsWith("local.") && iname.count('.') == 1) + // Solve one common case of adding 'class' in + // *(class X*)0xdeadbeef for gdb. + exp = name.toLatin1(); + else + exp = "*(" + gdbQuoteTypes(type) + "*)" + hexAddress(); + } + } + + mi = input["value"]; + QByteArray enc = input["valueencoded"].data(); + if (mi.isValid() || !enc.isEmpty()) { + setValue(decodeData(mi.data(), enc)); + } else { + setValueNeeded(); + } + + mi = input["size"]; + if (mi.isValid()) + size = mi.toInt(); + + mi = input["exp"]; + if (mi.isValid()) + exp = mi.data(); + + mi = input["valueenabled"]; + if (mi.data() == "true") + valueEnabled = true; + else if (mi.data() == "false") + valueEnabled = false; + + mi = input["valueeditable"]; + if (mi.data() == "true") + valueEditable = true; + else if (mi.data() == "false") + valueEditable = false; + + mi = input["numchild"]; // GDB/MI + if (mi.isValid()) + setHasChildren(mi.toInt() > 0); + mi = input["haschild"]; // native-mixed + if (mi.isValid()) + setHasChildren(mi.toInt() > 0); + + mi = input["arraydata"]; + if (mi.isValid()) { + DebuggerEncoding encoding(input["arrayencoding"].data()); + QByteArray childType = input["childtype"].data(); + decodeArrayData(this, mi.data(), encoding, childType); + } else { + const GdbMi children = input["children"]; + if (children.isValid()) { + bool ok = false; + // Try not to repeat data too often. + const GdbMi childType = input["childtype"]; + const GdbMi childNumChild = input["childnumchild"]; + + qulonglong addressBase = input["addrbase"].data().toULongLong(&ok, 0); + qulonglong addressStep = input["addrstep"].data().toULongLong(&ok, 0); + + for (int i = 0, n = int(children.children().size()); i != n; ++i) { + const GdbMi &subinput = children.children().at(i); + WatchItem *child = new WatchItem; + if (childType.isValid()) + child->setType(childType.data()); + if (childNumChild.isValid()) + child->setHasChildren(childNumChild.toInt() > 0); + GdbMi name = subinput["name"]; + QByteArray nn; + if (name.isValid()) { + nn = name.data(); + child->name = QString::fromLatin1(nn); + } else { + nn.setNum(i); + child->name = QString::fromLatin1("[%1]").arg(i); + } + GdbMi iname = subinput["iname"]; + if (iname.isValid()) + child->iname = iname.data(); + else + child->iname = this->iname + '.' + nn; + if (addressStep) { + child->address = addressBase + i * addressStep; + child->exp = "*(" + gdbQuoteTypes(child->type) + "*)" + child->hexAddress(); + } + QByteArray key = subinput["key"].data(); + if (!key.isEmpty()) + child->name = decodeData(key, subinput["keyencoded"].data()); + child->parseHelper(subinput); + appendChild(child); + } + } + } +} + +void WatchItem::parse(const GdbMi &data) +{ + iname = data["iname"].data(); + + GdbMi wname = data["wname"]; + if (wname.isValid()) // Happens (only) for watched expressions. + name = QString::fromUtf8(QByteArray::fromHex(wname.data())); + else + name = QString::fromLatin1(data["name"].data()); + + parseHelper(data); + + if (wname.isValid()) + exp = name.toUtf8(); +} + +WatchItem *WatchItem::parentItem() const +{ + return static_cast(parent()); +} + // Format a tooltip row with aligned colon. static void formatToolTipRow(QTextStream &str, const QString &category, const QString &value) { @@ -310,13 +544,13 @@ static void formatToolTipRow(QTextStream &str, const QString &category, const QS str << "" << val << ""; } -QString WatchData::toToolTip() const +QString WatchItem::toToolTip() const { QString res; QTextStream str(&res); str << ""; formatToolTipRow(str, tr("Name"), name); - formatToolTipRow(str, tr("Expression"), QLatin1String(exp)); + formatToolTipRow(str, tr("Expression"), expression()); formatToolTipRow(str, tr("Internal Type"), QLatin1String(type)); bool ok; const quint64 intValue = value.toULongLong(&ok); @@ -334,367 +568,92 @@ QString WatchData::toToolTip() const } formatToolTipRow(str, tr("Value"), val); } - if (address) - formatToolTipRow(str, tr("Object Address"), formatToolTipAddress(address)); + if (realAddress()) + formatToolTipRow(str, tr("Object Address"), formatToolTipAddress(realAddress())); if (origaddr) formatToolTipRow(str, tr("Pointer Address"), formatToolTipAddress(origaddr)); + if (arrayIndex >= 0) + formatToolTipRow(str, tr("Array Index"), QString::number(arrayIndex)); if (size) formatToolTipRow(str, tr("Static Object Size"), tr("%n bytes", 0, size)); - formatToolTipRow(str, tr("Internal ID"), QLatin1String(iname)); + formatToolTipRow(str, tr("Internal ID"), QLatin1String(internalName())); str << "
"; return res; } -QString WatchData::msgNotInScope() +bool WatchItem::isLocal() const { - //: Value of variable in Debugger Locals display for variables out - //: of scope (stopped above initialization). - static const QString rc = - QCoreApplication::translate("Debugger::Internal::WatchData", ""); - return rc; + if (arrayIndex >= 0) + if (const WatchItem *p = parentItem()) + return p->isLocal(); + return iname.startsWith("local."); } -const QString &WatchData::shadowedNameFormat() +bool WatchItem::isWatcher() const { - //: Display of variables shadowed by variables of the same name - //: in nested scopes: Variable %1 is the variable name, %2 is a - //: simple count. - static const QString format = - QCoreApplication::translate("Debugger::Internal::WatchData", "%1 "); - return format; + if (arrayIndex >= 0) + if (const WatchItem *p = parentItem()) + return p->isWatcher(); + return iname.startsWith("watch."); } -QString WatchData::shadowedName(const QString &name, int seen) +bool WatchItem::isInspect() const { - if (seen <= 0) - return name; - return shadowedNameFormat().arg(name).arg(seen); + if (arrayIndex >= 0) + if (const WatchItem *p = parentItem()) + return p->isInspect(); + return iname.startsWith("inspect."); } -QByteArray WatchData::hexAddress() const +quint64 WatchItem::realAddress() const { - if (address) - return QByteArray("0x") + QByteArray::number(address, 16); - return QByteArray(); -} - - -//////////////////////////////////////////////////// -// -// Protocol convienience -// -//////////////////////////////////////////////////// - -void WatchData::updateValue(const GdbMi &item) -{ - GdbMi value = item["value"]; - GdbMi encoding = item["valueencoded"]; - if (value.isValid() || encoding.isValid()) { - setValue(decodeData(value.data(), encoding.data())); - } else { - setValueNeeded(); + if (arrayIndex >= 0) { + if (const WatchItem *p = parentItem()) + return p->address + arrayIndex * size; } + return address; } -void WatchData::updateChildCount(const GdbMi &mi) +QByteArray WatchItem::internalName() const { - if (mi.isValid()) - setHasChildren(mi.toInt() > 0); -} - -static void setWatchDataValueEnabled(WatchData &data, const GdbMi &mi) -{ - if (mi.data() == "true") - data.valueEnabled = true; - else if (mi.data() == "false") - data.valueEnabled = false; -} - -static void setWatchDataValueEditable(WatchData &data, const GdbMi &mi) -{ - if (mi.data() == "true") - data.valueEditable = true; - else if (mi.data() == "false") - data.valueEditable = false; -} - -static void setWatchDataAddress(WatchData &data, quint64 address) -{ - data.address = address; - - if (data.exp.isEmpty()) { - if (data.iname.startsWith("local.") && data.iname.count('.') == 1) - // Solve one common case of adding 'class' in - // *(class X*)0xdeadbeef for gdb. - data.exp = data.name.toLatin1(); - else - data.exp = "*(" + gdbQuoteTypes(data.type) + "*)" + data.hexAddress(); + if (arrayIndex >= 0) { + if (const WatchItem *p = parentItem()) + return p->iname + '.' + QByteArray::number(arrayIndex); } + return iname; } -static void setWatchDataSize(WatchData &data, const GdbMi &mi) +QString WatchItem::realName() const { - if (mi.isValid()) { - bool ok = false; - const unsigned size = mi.data().toUInt(&ok); - if (ok) - data.size = size; + if (arrayIndex >= 0) + return QString::fromLatin1("[%1]").arg(arrayIndex); + return name; +} + +QString WatchItem::expression() const +{ + if (!exp.isEmpty()) + return QString::fromLatin1(exp); + if (quint64 addr = realAddress()) { + if (!type.isEmpty()) + return QString::fromLatin1("*(%1*)0x%2").arg(QLatin1String(type)).arg(addr, 0, 16); } + const WatchItem *p = parentItem(); + if (p && !p->exp.isEmpty()) + return QString::fromLatin1("(%1).%2").arg(QString::fromLatin1(p->exp), name); + return name; } -void WatchData::updateType(const GdbMi &item) +WatchItem *WatchItem::findItem(const QByteArray &iname) { - if (item.isValid()) - setType(item.data()); -} - -// Utilities to decode string data returned by the dumper helpers. - -template -QString decodeItemHelper(const T &t) -{ - return QString::number(t); -} - -QString decodeItemHelper(const double &t) -{ - return QString::number(t, 'g', 16); -} - -template -void decodeArrayHelper(std::function itemHandler, const WatchData &tmplate, - const QByteArray &rawData) -{ - const QByteArray ba = QByteArray::fromHex(rawData); - const T *p = (const T *) ba.data(); - WatchData data; - const QByteArray exp = "*(" + gdbQuoteTypes(tmplate.type) + "*)0x"; - for (int i = 0, n = ba.size() / sizeof(T); i < n; ++i) { - data = tmplate; - data.iname += QByteArray::number(i); - data.name = QString::fromLatin1("[%1]").arg(i); - data.value = decodeItemHelper(p[i]); - data.address += i * sizeof(T); - data.exp = exp + QByteArray::number(data.address, 16); - data.setAllUnneeded(); - itemHandler(data); + if (internalName() == iname) + return this; + foreach (TreeItem *child, children()) { + auto witem = static_cast(child); + if (WatchItem *result = witem->findItem(iname)) + return result; } -} - -void decodeArrayData(std::function itemHandler, const WatchData &tmplate, - const QByteArray &rawData, const DebuggerEncoding &encoding) -{ - switch (encoding.type) { - case DebuggerEncoding::HexEncodedSignedInteger: - switch (encoding.size) { - case 1: - return decodeArrayHelper(itemHandler, tmplate, rawData); - case 2: - return decodeArrayHelper(itemHandler, tmplate, rawData); - case 4: - return decodeArrayHelper(itemHandler, tmplate, rawData); - case 8: - return decodeArrayHelper(itemHandler, tmplate, rawData); - } - break; - case DebuggerEncoding::HexEncodedUnsignedInteger: - switch (encoding.size) { - case 1: - return decodeArrayHelper(itemHandler, tmplate, rawData); - case 2: - return decodeArrayHelper(itemHandler, tmplate, rawData); - case 4: - return decodeArrayHelper(itemHandler, tmplate, rawData); - case 8: - return decodeArrayHelper(itemHandler, tmplate, rawData); - } - break; - case DebuggerEncoding::HexEncodedFloat: - switch (encoding.size) { - case 4: - return decodeArrayHelper(itemHandler, tmplate, rawData); - case 8: - return decodeArrayHelper(itemHandler, tmplate, rawData); - } - default: - break; - } - qDebug() << "ENCODING ERROR: " << encoding.toString(); -} - -void parseChildrenData(const WatchData &data0, const GdbMi &item, - std::function itemHandler, - std::function childHandler, - std::function arrayDecoder) -{ - WatchData data = data0; - data.setChildrenUnneeded(); - - GdbMi children = item["children"]; - - data.updateType(item["type"]); - - data.editvalue = item["editvalue"].data(); - data.editformat = DebuggerDisplay(item["editformat"].toInt()); - data.editencoding = DebuggerEncoding(item["editencoding"].data()); - - GdbMi mi = item["valueelided"]; - if (mi.isValid()) - data.elided = mi.toInt(); - - mi = item["bitpos"]; - if (mi.isValid()) - data.bitpos = mi.toInt(); - - mi = item["bitsize"]; - if (mi.isValid()) - data.bitsize = mi.toInt(); - - mi = item["origaddr"]; - if (mi.isValid()) - data.origaddr = mi.toAddress(); - - mi = item["address"]; - if (mi.isValid()) - setWatchDataAddress(data, mi.toAddress()); - - data.updateValue(item); - - setWatchDataSize(data, item["size"]); - - mi = item["exp"]; - if (mi.isValid()) - data.exp = mi.data(); - - setWatchDataValueEnabled(data, item["valueenabled"]); - setWatchDataValueEditable(data, item["valueeditable"]); - data.updateChildCount(item["numchild"]); // GDB/MI - data.updateChildCount(item["haschild"]); // native-mixed - itemHandler(data); - - bool ok = false; - qulonglong addressBase = item["addrbase"].data().toULongLong(&ok, 0); - qulonglong addressStep = item["addrstep"].data().toULongLong(&ok, 0); - - // Try not to repeat data too often. - WatchData childtemplate; - childtemplate.updateType(item["childtype"]); - childtemplate.updateChildCount(item["childnumchild"]); - - mi = item["arraydata"]; - if (mi.isValid()) { - DebuggerEncoding encoding(item["arrayencoding"].data()); - childtemplate.iname = data.iname + '.'; - childtemplate.address = addressBase; - arrayDecoder(childtemplate, mi.data(), encoding); - } else { - for (int i = 0, n = int(children.children().size()); i != n; ++i) { - const GdbMi &child = children.children().at(i); - WatchData data1 = childtemplate; - GdbMi name = child["name"]; - if (name.isValid()) - data1.name = QString::fromLatin1(name.data()); - else - data1.name = QString::number(i); - GdbMi iname = child["iname"]; - if (iname.isValid()) { - data1.iname = iname.data(); - } else { - data1.iname = data.iname; - data1.iname += '.'; - data1.iname += data1.name.toLatin1(); - } - if (!data1.name.isEmpty() && data1.name.at(0).isDigit()) - data1.name = QLatin1Char('[') + data1.name + QLatin1Char(']'); - if (addressStep) { - setWatchDataAddress(data1, addressBase); - addressBase += addressStep; - } - QByteArray key = child["key"].data(); - if (!key.isEmpty()) { - data1.name = decodeData(key, child["keyencoded"].data()); - } - childHandler(data1, child); - } - } -} - -void parseWatchData(const WatchData &data0, const GdbMi &input, - QList *list) -{ - auto itemHandler = [list](const WatchData &data) { - list->append(data); - }; - auto childHandler = [list](const WatchData &innerData, const GdbMi &innerInput) { - parseWatchData(innerData, innerInput, list); - }; - auto arrayDecoder = [itemHandler](const WatchData &childTemplate, - const QByteArray &encodedData, const DebuggerEncoding &encoding) { - decodeArrayData(itemHandler, childTemplate, encodedData, encoding); - }; - - parseChildrenData(data0, input, itemHandler, childHandler, arrayDecoder); -} - -template -void readNumericVectorHelper(std::vector *v, const QByteArray &ba) -{ - const T *p = (const T *) ba.data(); - const int n = ba.size() / sizeof(T); - v->resize(n); - // Losing precision in case of 64 bit ints is ok here, as the result - // is only used to plot data. - for (int i = 0; i != n; ++i) - (*v)[i] = static_cast(p[i]); -} - -void readNumericVector(std::vector *v, const QByteArray &rawData, const DebuggerEncoding &encoding) -{ - switch (encoding.type) { - case DebuggerEncoding::HexEncodedSignedInteger: - switch (encoding.size) { - case 1: - readNumericVectorHelper(v, rawData); - return; - case 2: - readNumericVectorHelper(v, rawData); - return; - case 4: - readNumericVectorHelper(v, rawData); - return; - case 8: - readNumericVectorHelper(v, rawData); - return; - } - case DebuggerEncoding::HexEncodedUnsignedInteger: - switch (encoding.size) { - case 1: - readNumericVectorHelper(v, rawData); - return; - case 2: - readNumericVectorHelper(v, rawData); - return; - case 4: - readNumericVectorHelper(v, rawData); - return; - case 8: - readNumericVectorHelper(v, rawData); - return; - } - case DebuggerEncoding::HexEncodedFloat: - switch (encoding.size) { - case 4: - readNumericVectorHelper(v, rawData); - return; - case 8: - readNumericVectorHelper(v, rawData); - return; - } - default: - break; - } - qDebug() << "ENCODING ERROR: " << encoding.toString(); + return 0; } } // namespace Internal diff --git a/src/plugins/debugger/watchdata.h b/src/plugins/debugger/watchdata.h index fb4d9cbd47c..77b74588f39 100644 --- a/src/plugins/debugger/watchdata.h +++ b/src/plugins/debugger/watchdata.h @@ -33,10 +33,11 @@ #include "debuggerprotocol.h" +#include + #include #include -#include #include namespace Debugger { @@ -44,20 +45,35 @@ namespace Internal { class GdbMi; -class WatchData +class WatchItem : public Utils::TreeItem { public: - WatchData(); + WatchItem(); + + void parse(const GdbMi &input); + + bool isLocal() const; + bool isWatcher() const; + bool isInspect() const; + + QString expression() const; + QString realName() const; + quint64 realAddress() const; + QByteArray internalName() const; + QString toToolTip() const; + + QVariant editValue() const; + int editType() const; + + WatchItem *findItem(const QByteArray &iname); + WatchItem *parentItem() const; enum State { - HasChildrenNeeded = 1, ValueNeeded = 2, ChildrenNeeded = 8, - InitialState = ValueNeeded - | ChildrenNeeded - | HasChildrenNeeded + InitialState = ValueNeeded | ChildrenNeeded }; static const qint64 InvalidId = -1; @@ -73,20 +89,14 @@ public: void setChildrenUnneeded() { state = State(state & ~ChildrenNeeded); } void setHasChildren(bool c) { wantsChildren = c; if (!c) setChildrenUnneeded(); } - bool isLocal() const { return iname.startsWith("local."); } - bool isWatcher() const { return iname.startsWith("watch."); } - bool isInspect() const { return iname.startsWith("inspect."); } bool isValid() const { return !iname.isEmpty(); } bool isVTablePointer() const; - bool isAncestorOf(const QByteArray &childIName) const; - void setError(const QString &); void setValue(const QString &); void setType(const QByteArray &, bool guessChildrenFromType = true); QString toString() const; - QString toToolTip() const; static QString msgNotInScope(); static QString shadowedName(const QString &name, int seen); @@ -94,11 +104,6 @@ public: QByteArray hexAddress() const; - // Protocol interaction. - void updateValue(const GdbMi &item); - void updateChildCount(const GdbMi &mi); - void updateType(const GdbMi &item); - public: qint64 id; // Token for the engine for internal mapping qint32 state; // 'needed' flags; @@ -116,35 +121,18 @@ public: uint bitpos; // Position within bit fields uint bitsize; // Size in case of bit fields int elided; // Full size if value was cut off, -1 if cut on unknown size, 0 otherwise + int arrayIndex; // -1 if not an array member bool wantsChildren; bool valueEnabled; // Value will be enabled or not bool valueEditable; // Value will be editable bool outdated; // \internal item is to be removed. +private: + void parseHelper(const GdbMi &input); Q_DECLARE_TR_FUNCTIONS(Debugger::Internal::WatchHandler) }; -void decodeArrayData(std::function itemHandler, - const WatchData &tmplate, - const QByteArray &rawData, - const DebuggerEncoding &encoding); - -void readNumericVector(std::vector *, - const QByteArray &rawData, - const DebuggerEncoding &encoding); - -void parseChildrenData(const WatchData &parent, const GdbMi &child, - std::function itemHandler, - std::function childHandler, - std::function arrayDecoder); - -void parseWatchData(const WatchData &parent, const GdbMi &child, - QList *insertions); - } // namespace Internal } // namespace Debugger -Q_DECLARE_METATYPE(Debugger::Internal::WatchData) - - #endif // DEBUGGER_WATCHDATA_H diff --git a/src/plugins/debugger/watchhandler.cpp b/src/plugins/debugger/watchhandler.cpp index a4a136d1eaa..aa87de2a3c2 100644 --- a/src/plugins/debugger/watchhandler.cpp +++ b/src/plugins/debugger/watchhandler.cpp @@ -54,14 +54,14 @@ #include #include +#include +#include #include #include #include #include -#include -#include - #include + #include #include #include @@ -85,6 +85,71 @@ static int theUnprintableBase = -1; const char INameProperty[] = "INameProperty"; const char KeyProperty[] = "KeyProperty"; +static const WatchModel *watchModel(const WatchItem *item) +{ + return reinterpret_cast(item->model()); +} + +template +void readNumericVectorHelper(std::vector *v, const QByteArray &ba) +{ + const T *p = (const T *) ba.data(); + const int n = ba.size() / sizeof(T); + v->resize(n); + // Losing precision in case of 64 bit ints is ok here, as the result + // is only used to plot data. + for (int i = 0; i != n; ++i) + (*v)[i] = static_cast(p[i]); +} + +static void readNumericVector(std::vector *v, const QByteArray &rawData, DebuggerEncoding encoding) +{ + switch (encoding.type) { + case DebuggerEncoding::HexEncodedSignedInteger: + switch (encoding.size) { + case 1: + readNumericVectorHelper(v, rawData); + return; + case 2: + readNumericVectorHelper(v, rawData); + return; + case 4: + readNumericVectorHelper(v, rawData); + return; + case 8: + readNumericVectorHelper(v, rawData); + return; + } + case DebuggerEncoding::HexEncodedUnsignedInteger: + switch (encoding.size) { + case 1: + readNumericVectorHelper(v, rawData); + return; + case 2: + readNumericVectorHelper(v, rawData); + return; + case 4: + readNumericVectorHelper(v, rawData); + return; + case 8: + readNumericVectorHelper(v, rawData); + return; + } + case DebuggerEncoding::HexEncodedFloat: + switch (encoding.size) { + case 4: + readNumericVectorHelper(v, rawData); + return; + case 8: + readNumericVectorHelper(v, rawData); + return; + } + default: + break; + } + qDebug() << "ENCODING ERROR: " << encoding.toString(); +} + static QByteArray stripForFormat(const QByteArray &ba) { QByteArray res; @@ -271,13 +336,20 @@ public: static QString nameForFormat(int format); - QVariant data(const QModelIndex &idx, int role) const; - bool setData(const QModelIndex &idx, const QVariant &value, int role); + QVariant data(const QModelIndex &idx, int role) const override; + bool setData(const QModelIndex &idx, const QVariant &value, int role) override; + + Qt::ItemFlags flags(const QModelIndex &idx) const override; + bool hasChildren(const QModelIndex &idx) const override; + bool canFetchMore(const QModelIndex &idx) const override; + void fetchMore(const QModelIndex &idx) override; QString displayForAutoTest(const QByteArray &iname) const; void reinitialize(bool includeInspectData = false); WatchItem *findItem(const QByteArray &iname) const; + const WatchItem *watchItem(const QModelIndex &idx) const; + void insertItem(WatchItem *item); void reexpandItems(); @@ -321,11 +393,26 @@ WatchModel::WatchModel(WatchHandler *handler, DebuggerEngine *engine) setHeader(QStringList() << tr("Name") << tr("Value") << tr("Type")); auto root = new WatchItem; - root->appendChild(m_localsRoot = new WatchItem("local", tr("Locals"))); - root->appendChild(m_inspectorRoot = new WatchItem("inspect", tr("Inspector"))); - root->appendChild(m_watchRoot = new WatchItem("watch", tr("Expressions"))); - root->appendChild(m_returnRoot = new WatchItem("return", tr("Return Value"))); - root->appendChild(m_tooltipRoot = new WatchItem("tooltip", tr("Tooltip"))); + m_localsRoot = new WatchItem; + m_localsRoot->iname = "local"; + m_localsRoot->name = tr("Locals"); + m_inspectorRoot = new WatchItem; + m_inspectorRoot->iname = "inspect"; + m_inspectorRoot->name = tr("Inspector"); + m_watchRoot = new WatchItem; + m_watchRoot->iname = "watch"; + m_watchRoot->name = tr("Expressions"); + m_returnRoot = new WatchItem; + m_returnRoot->iname = "return"; + m_returnRoot->name = tr("Return Value"); + m_tooltipRoot = new WatchItem; + m_tooltipRoot->iname = "tooltip"; + m_tooltipRoot->name = tr("Tooltip"); + root->appendChild(m_localsRoot); + root->appendChild(m_inspectorRoot); + root->appendChild(m_watchRoot); + root->appendChild(m_returnRoot); + root->appendChild(m_tooltipRoot); setRootItem(root); m_requestUpdateTimer.setSingleShot(true); @@ -357,18 +444,9 @@ WatchItem *WatchModel::findItem(const QByteArray &iname) const return root()->findItem(iname); } -WatchItem *WatchItem::findItem(const QByteArray &iname) +const WatchItem *WatchModel::watchItem(const QModelIndex &idx) const { - if (this->iname == iname) - return this; - foreach (TreeItem *child, children()) { - auto witem = static_cast(child); - if (witem->iname == iname) - return witem; - if (witem->isAncestorOf(iname)) - return witem->findItem(iname); - } - return 0; + return static_cast(itemForIndex(idx)); } static QByteArray parentName(const QByteArray &iname) @@ -516,66 +594,74 @@ static QString quoteUnprintable(const QString &str) return encoded; } -QString WatchItem::formattedValue() const +static int itemFormat(const WatchItem *item) { - if (type == "bool") { - if (value == QLatin1String("0")) + const int individualFormat = theIndividualFormats.value(item->iname, AutomaticFormat); + if (individualFormat != AutomaticFormat) + return individualFormat; + return theTypeFormats.value(stripForFormat(item->type), AutomaticFormat); +} + +static QString formattedValue(const WatchItem *item) +{ + if (item->type == "bool") { + if (item->value == QLatin1String("0")) return QLatin1String("false"); - if (value == QLatin1String("1")) + if (item->value == QLatin1String("1")) return QLatin1String("true"); - return value; + return item->value; } - const int format = itemFormat(); + const int format = itemFormat(item); // Append quoted, printable character also for decimal. // FIXME: This is unreliable. - if (type.endsWith("char") || type.endsWith("QChar")) { + if (item->type.endsWith("char") || item->type.endsWith("QChar")) { bool ok; - const int code = value.toInt(&ok); - bool isUnsigned = type == "unsigned char" || type == "uchar"; - return ok ? reformatCharacter(code, format, !isUnsigned) : value; + const int code = item->value.toInt(&ok); + bool isUnsigned = item->type == "unsigned char" || item->type == "uchar"; + return ok ? reformatCharacter(code, format, !isUnsigned) : item->value; } if (format == HexadecimalIntegerFormat || format == DecimalIntegerFormat || format == OctalIntegerFormat || format == BinaryIntegerFormat) { - bool isSigned = value.startsWith(QLatin1Char('-')); - quint64 raw = isSigned ? quint64(value.toLongLong()) : value.toULongLong(); - return reformatInteger(raw, format, size, isSigned); + bool isSigned = item->value.startsWith(QLatin1Char('-')); + quint64 raw = isSigned ? quint64(item->value.toLongLong()) : item->value.toULongLong(); + return reformatInteger(raw, format, item->size, isSigned); } if (format == ScientificFloatFormat) { - double dd = value.toDouble(); + double dd = item->value.toDouble(); return QString::number(dd, 'e'); } if (format == CompactFloatFormat) { - double dd = value.toDouble(); + double dd = item->value.toDouble(); return QString::number(dd, 'g'); } - if (type == "va_list") - return value; + if (item->type == "va_list") + return item->value; - if (!isPointerType(type) && !isVTablePointer()) { + if (!isPointerType(item->type) && !item->isVTablePointer()) { bool ok = false; - qulonglong integer = value.toULongLong(&ok, 0); + qulonglong integer = item->value.toULongLong(&ok, 0); if (ok) { - const int format = itemFormat(); - return reformatInteger(integer, format, size, false); + const int format = itemFormat(item); + return reformatInteger(integer, format, item->size, false); } } - if (elided) { - QString v = value; + if (item->elided) { + QString v = item->value; v.chop(1); - QString len = elided > 0 ? QString::number(elided) : QLatin1String("unknown length"); + QString len = item->elided > 0 ? QString::number(item->elided) : QLatin1String("unknown length"); return quoteUnprintable(v) + QLatin1String("\"... (") + len + QLatin1Char(')'); } - return quoteUnprintable(value); + return quoteUnprintable(item->value); } // Get a pointer address from pointer values reported by the debugger. @@ -636,28 +722,6 @@ QVariant WatchItem::editValue() const return QVariant(quoteUnprintable(stringValue)); } -bool WatchItem::canFetchMore() const -{ - if (!wantsChildren) - return false; - const WatchModel *model = watchModel(); - if (!model) - return false; - if (!model->m_contentsValid && !isInspect()) - return false; - return true; -} - -void WatchItem::fetchMore() -{ - WatchModel *model = watchModel(); - model->m_expandedINames.insert(iname); - if (children().isEmpty()) { - setChildrenNeeded(); - model->m_engine->expandItem(iname); - } -} - // Truncate value for item view, maintaining quotes. static QString truncateValue(QString v) { @@ -670,42 +734,25 @@ static QString truncateValue(QString v) return v; } -int WatchItem::itemFormat() const -{ - const int individualFormat = theIndividualFormats.value(iname, AutomaticFormat); - if (individualFormat != AutomaticFormat) - return individualFormat; - return theTypeFormats.value(stripForFormat(type), AutomaticFormat); -} - -QString WatchItem::expression() const -{ - if (!exp.isEmpty()) - return QString::fromLatin1(exp); - if (address && !type.isEmpty()) { - return QString::fromLatin1("*(%1*)%2"). - arg(QLatin1String(type), QLatin1String(hexAddress())); - } - if (const WatchItem *p = parentItem()) { - if (!p->exp.isEmpty()) - return QString::fromLatin1("(%1).%2").arg(QString::fromLatin1(p->exp), name); - } - return name; -} - -QString WatchItem::displayName() const +static QString displayName(const WatchItem *item) { QString result; - if (!parentItem()) - return result; - if (iname.startsWith("return") && name.startsWith(QLatin1Char('$'))) - result = WatchModel::tr("returned value"); - else if (name == QLatin1String("*")) - result = QLatin1Char('*') + parentItem()->name; - else - result = watchModel()->removeNamespaces(name); - // Simplyfy names that refer to base classes. + const WatchItem *p = item->parentItem(); + if (!p) + return result; + if (item->arrayIndex >= 0) { + result = QString::fromLatin1("[%1]").arg(item->arrayIndex); + return result; + } + if (item->iname.startsWith("return") && item->name.startsWith(QLatin1Char('$'))) + result = WatchModel::tr("returned value"); + else if (item->name == QLatin1String("*")) + result = QLatin1Char('*') + p->name; + else + result = watchModel(item)->removeNamespaces(item->name); + + // Simplify names that refer to base classes. if (result.startsWith(QLatin1Char('['))) { result = simplifyType(result); if (result.size() > 30) @@ -715,146 +762,115 @@ QString WatchItem::displayName() const return result; } -QString WatchItem::displayValue() const +static QString displayValue(const WatchItem *item) { - QString result = watchModel()->removeNamespaces(truncateValue(formattedValue())); - if (result.isEmpty() && address) - result += QString::fromLatin1("@0x" + QByteArray::number(address, 16)); + QString result = watchModel(item)->removeNamespaces(truncateValue(formattedValue(item))); + if (result.isEmpty() && item->address) + result += QString::fromLatin1("@0x" + QByteArray::number(item->address, 16)); // if (origaddr) // result += QString::fromLatin1(" (0x" + QByteArray::number(origaddr, 16) + ')'); return result; } -QString WatchItem::displayType() const +static QString displayType(const WatchItem *item) { - QString result = niceTypeHelper(type); - if (bitsize) - result += QString::fromLatin1(":%1").arg(bitsize); + QString result = niceTypeHelper(item->type); + if (item->bitsize) + result += QString::fromLatin1(":%1").arg(item->bitsize); result.remove(QLatin1Char('\'')); - result = watchModel()->removeNamespaces(result); + result = watchModel(item)->removeNamespaces(result); return result; } -QColor WatchItem::valueColor(int column) const +static QColor valueColor(const WatchItem *item, int column) { Theme::Color color = Theme::Debugger_WatchItem_ValueNormal; - if (const WatchModel *model = watchModel()) { - if (!model->m_contentsValid && !isInspect()) { + if (const WatchModel *model = watchModel(item)) { + if (!model->m_contentsValid && !item->isInspect()) { color = Theme::Debugger_WatchItem_ValueInvalid; } else if (column == 1) { - if (!valueEnabled) + if (!item->valueEnabled) color = Theme::Debugger_WatchItem_ValueInvalid; - else if (!model->m_contentsValid && !isInspect()) + else if (!model->m_contentsValid && !item->isInspect()) color = Theme::Debugger_WatchItem_ValueInvalid; - else if (column == 1 && value.isEmpty()) // This might still show 0x... + else if (column == 1 && item->value.isEmpty()) // This might still show 0x... color = Theme::Debugger_WatchItem_ValueInvalid; - else if (column == 1 && value != model->m_valueCache.value(iname)) + else if (column == 1 && item->value != model->m_valueCache.value(item->iname)) color = Theme::Debugger_WatchItem_ValueChanged; } } return creatorTheme()->color(color); } -QVariant WatchItem::data(int column, int role) const +static DisplayFormats typeFormatList(const WatchItem *item) { - switch (role) { - case LocalsEditTypeRole: - return QVariant(editType()); + DisplayFormats formats; - case LocalsNameRole: - return QVariant(name); + // Types supported by dumpers: + // Hack: Compensate for namespaces. + QString t = QLatin1String(stripForFormat(item->type)); + int pos = t.indexOf(QLatin1String("::Q")); + if (pos >= 0 && t.count(QLatin1Char(':')) == 2) + t.remove(0, pos + 2); + pos = t.indexOf(QLatin1Char('<')); + if (pos >= 0) + t.truncate(pos); + t.replace(QLatin1Char(':'), QLatin1Char('_')); + formats << watchModel(item)->m_reportedTypeFormats.value(t); - case LocalsIntegerBaseRole: - if (isPointerType(type)) // Pointers using 0x-convention - return QVariant(16); - return QVariant(formatToIntegerBase(itemFormat())); + if (t.contains(QLatin1Char(']'))) + formats.append(ArrayPlotFormat); - case Qt::EditRole: { - switch (column) { - case 0: - return expression(); - case 1: - return editValue(); - case 2: - return QString::fromUtf8(type); - } - } - - case Qt::DisplayRole: { - switch (column) { - case 0: - return displayName(); - case 1: - return displayValue(); - case 2: - return displayType(); - } - } - - case Qt::ToolTipRole: - return boolSetting(UseToolTipsInLocalsView) - ? toToolTip() : QVariant(); - - case Qt::ForegroundRole: - return valueColor(column); - - case LocalsExpressionRole: - return expression(); - - case LocalsRawExpressionRole: - return exp; - - case LocalsINameRole: - return iname; - - case LocalsExpandedRole: - return watchModel()->m_expandedINames.contains(iname); - - case LocalsTypeFormatListRole: - return QVariant::fromValue(typeFormatList()); - - case LocalsTypeRole: - return watchModel()->removeNamespaces(displayType()); - - case LocalsRawTypeRole: - return QString::fromLatin1(type); - - case LocalsTypeFormatRole: - return theTypeFormats.value(stripForFormat(type), AutomaticFormat); - - case LocalsIndividualFormatRole: - return theIndividualFormats.value(iname, AutomaticFormat); - - case LocalsRawValueRole: - return value; - - case LocalsObjectAddressRole: - return address; - - case LocalsPointerAddressRole: - return origaddr; - - case LocalsIsWatchpointAtObjectAddressRole: { - BreakpointParameters bp(WatchpointAtAddress); - bp.address = address; - return watchModel()->m_engine->breakHandler()->findWatchpoint(bp) != 0; - } - - case LocalsSizeRole: - return QVariant(size); - - case LocalsIsWatchpointAtPointerAddressRole: - if (isPointerType(type)) { - BreakpointParameters bp(WatchpointAtAddress); - bp.address = pointerValue(value); - return watchModel()->m_engine->breakHandler()->findWatchpoint(bp) != 0; - } - return false; - - default: - break; + // Fixed artificial string and pointer types. + if (item->origaddr || isPointerType(item->type)) { + formats.append(RawFormat); + formats.append(Latin1StringFormat); + formats.append(SeparateLatin1StringFormat); + formats.append(Utf8StringFormat); + formats.append(SeparateUtf8StringFormat); + formats.append(Local8BitStringFormat); + formats.append(Utf16StringFormat); + formats.append(Ucs4StringFormat); + formats.append(Array10Format); + formats.append(Array100Format); + formats.append(Array1000Format); + formats.append(Array10000Format); + } else if (item->type.contains("char[") || item->type.contains("char [")) { + formats.append(RawFormat); + formats.append(Latin1StringFormat); + formats.append(SeparateLatin1StringFormat); + formats.append(Utf8StringFormat); + formats.append(SeparateUtf8StringFormat); + formats.append(Local8BitStringFormat); + formats.append(Utf16StringFormat); + formats.append(Ucs4StringFormat); } - return QVariant(); + + // Fixed artificial floating point types. + bool ok = false; + item->value.toDouble(&ok); + if (ok) { + formats.append(CompactFloatFormat); + formats.append(ScientificFloatFormat); + } + + // Fixed artificial integral types. + QString v = item->value; + if (v.startsWith(QLatin1Char('-'))) + v = v.mid(1); + v.toULongLong(&ok, 10); + if (!ok) + v.toULongLong(&ok, 16); + if (!ok) + v.toULongLong(&ok, 8); + if (ok) { + formats.append(DecimalIntegerFormat); + formats.append(HexadecimalIntegerFormat); + formats.append(BinaryIntegerFormat); + formats.append(OctalIntegerFormat); + } + + return formats; } QVariant WatchModel::data(const QModelIndex &idx, int role) const @@ -867,7 +883,109 @@ QVariant WatchModel::data(const QModelIndex &idx, int role) const l.append(indexForItem(item)); return QVariant::fromValue(l); } - return WatchModelBase::data(idx, role); + const WatchItem *item = watchItem(idx); + if (!item) + return QVariant(); + + const int column = idx.column(); + switch (role) { + case LocalsEditTypeRole: + return item->editType(); + + case LocalsNameRole: + return item->name; + + case LocalsIntegerBaseRole: + if (isPointerType(item->type)) // Pointers using 0x-convention + return QVariant(16); + return formatToIntegerBase(itemFormat(item)); + + case Qt::EditRole: { + switch (column) { + case 0: + return item->expression(); + case 1: + return item->editValue(); + case 2: + return QString::fromUtf8(item->type); + } + } + + case Qt::DisplayRole: { + switch (column) { + case 0: + return displayName(item); + case 1: + return displayValue(item); + case 2: + return displayType(item); + } + } + + case Qt::ToolTipRole: + return boolSetting(UseToolTipsInLocalsView) + ? item->toToolTip() : QVariant(); + + case Qt::ForegroundRole: + return valueColor(item, column); + + case LocalsExpressionRole: + return item->expression(); + + case LocalsRawExpressionRole: + return item->exp; + + case LocalsINameRole: + return item->iname; + + case LocalsExpandedRole: + return m_expandedINames.contains(item->iname); + + case LocalsTypeFormatListRole: + return QVariant::fromValue(typeFormatList(item)); + + case LocalsTypeRole: + return removeNamespaces(displayType(item)); + + case LocalsRawTypeRole: + return QString::fromLatin1(item->type); + + case LocalsTypeFormatRole: + return theTypeFormats.value(stripForFormat(item->type), AutomaticFormat); + + case LocalsIndividualFormatRole: + return theIndividualFormats.value(item->iname, AutomaticFormat); + + case LocalsRawValueRole: + return item->value; + + case LocalsObjectAddressRole: + return item->address; + + case LocalsPointerAddressRole: + return item->origaddr; + + case LocalsIsWatchpointAtObjectAddressRole: { + BreakpointParameters bp(WatchpointAtAddress); + bp.address = item->address; + return m_engine->breakHandler()->findWatchpoint(bp) != 0; + } + + case LocalsSizeRole: + return item->size; + + case LocalsIsWatchpointAtPointerAddressRole: + if (isPointerType(item->type)) { + BreakpointParameters bp(WatchpointAtAddress); + bp.address = pointerValue(item->value); + return m_engine->breakHandler()->findWatchpoint(bp) != 0; + } + return false; + + default: + break; + } + return QVariant(); } bool WatchModel::setData(const QModelIndex &idx, const QVariant &value, int role) @@ -920,12 +1038,19 @@ bool WatchModel::setData(const QModelIndex &idx, const QVariant &value, int role return true; } -Qt::ItemFlags WatchItem::flags(int column) const +Qt::ItemFlags WatchModel::flags(const QModelIndex &idx) const { - QTC_ASSERT(model(), return Qt::ItemFlags()); - DebuggerEngine *engine = watchModel()->m_engine; - QTC_ASSERT(engine, return Qt::ItemFlags()); - const DebuggerState state = engine->state(); + if (!idx.isValid()) + return 0; + + const WatchItem *item = watchItem(idx); + if (!item) + return Qt::ItemIsEnabled|Qt::ItemIsSelectable; + + const int column = idx.column(); + + QTC_ASSERT(m_engine, return Qt::ItemFlags()); + const DebuggerState state = m_engine->state(); // Enabled, editable, selectable, checkable, and can be used both as the // source of a drag and drop operation and as a drop target. @@ -933,37 +1058,88 @@ Qt::ItemFlags WatchItem::flags(int column) const const Qt::ItemFlags notEditable = Qt::ItemIsSelectable | Qt::ItemIsEnabled; const Qt::ItemFlags editable = notEditable | Qt::ItemIsEditable; - if (state == InferiorUnrunnable) - return (isWatcher() && column == 0 && iname.count('.') == 1) ? editable : notEditable; + if (item->isWatcher()) { + if (state == InferiorUnrunnable) + return (column == 0 && item->iname.count('.') == 1) ? editable : notEditable; - if (isWatcher()) { if (state != InferiorStopOk && state != DebuggerNotReady && state != DebuggerFinished - && !engine->hasCapability(AddWatcherWhileRunningCapability)) + && !m_engine->hasCapability(AddWatcherWhileRunningCapability)) return Qt::ItemFlags(); - if (column == 0 && iname.count('.') == 1) + if (column == 0 && item->iname.count('.') == 1) return editable; // Watcher names are editable. + if (column == 1 && item->arrayIndex >= 0) + return editable; - if (!name.isEmpty()) { + if (!item->name.isEmpty()) { // FIXME: Forcing types is not implemented yet. //if (idx.column() == 2) // return editable; // Watcher types can be set by force. - if (column == 1 && valueEditable && !elided) + if (column == 1 && item->valueEditable && !item->elided) return editable; // Watcher values are sometimes editable. } - } else if (isLocal()) { - if (state != InferiorStopOk && !engine->hasCapability(AddWatcherWhileRunningCapability)) + } else if (item->isLocal()) { + if (state != InferiorStopOk && !m_engine->hasCapability(AddWatcherWhileRunningCapability)) return Qt::ItemFlags(); - if (column == 1 && valueEditable && !elided) + if (column == 1 && item->valueEditable && !item->elided) return editable; // Locals values are sometimes editable. - } else if (isInspect()) { - if (column == 1 && valueEditable) + if (column == 1 && item->arrayIndex >= 0) + return editable; + } else if (item->isInspect()) { + if (column == 1 && item->valueEditable) return editable; // Inspector values are sometimes editable. } return notEditable; } +bool WatchModel::canFetchMore(const QModelIndex &idx) const +{ + if (!idx.isValid()) + return false; + + // See "hasChildren" below. + const WatchItem *item = watchItem(idx); + if (!item) + return false; + if (!item->wantsChildren) + return false; + if (!m_contentsValid && !item->isInspect()) + return false; + return true; +} + +void WatchModel::fetchMore(const QModelIndex &idx) +{ + if (!idx.isValid()) + return; + + WatchItem *item = static_cast(itemForIndex(idx)); + if (item) { + m_expandedINames.insert(item->iname); + if (item->children().isEmpty()) { + item->setChildrenNeeded(); + m_engine->expandItem(item->iname); + } + } +} + +bool WatchModel::hasChildren(const QModelIndex &idx) const +{ + const WatchItem *item = watchItem(idx); + if (!item) + return true; + if (item->rowCount() > 0) + return true; + + // "Can fetch more", see above. + if (!item->wantsChildren) + return false; + if (!m_contentsValid && !item->isInspect()) + return false; + return true; +} + static inline QString msgArrayFormat(int n) { return WatchModel::tr("Array of %n items", 0, n); @@ -1013,85 +1189,6 @@ QString WatchModel::nameForFormat(int format) return QString(); } -DisplayFormats WatchItem::typeFormatList() const -{ - DisplayFormats formats; - - // Types supported by dumpers: - // Hack: Compensate for namespaces. - QString t = QLatin1String(stripForFormat(type)); - int pos = t.indexOf(QLatin1String("::Q")); - if (pos >= 0 && t.count(QLatin1Char(':')) == 2) - t.remove(0, pos + 2); - pos = t.indexOf(QLatin1Char('<')); - if (pos >= 0) - t.truncate(pos); - t.replace(QLatin1Char(':'), QLatin1Char('_')); - formats << watchModel()->m_reportedTypeFormats.value(t); - - if (t.contains(QLatin1Char(']'))) - formats.append(ArrayPlotFormat); - - // Fixed artificial string and pointer types. - if (origaddr || isPointerType(type)) { - formats.append(RawFormat); - formats.append(Latin1StringFormat); - formats.append(SeparateLatin1StringFormat); - formats.append(Utf8StringFormat); - formats.append(SeparateUtf8StringFormat); - formats.append(Local8BitStringFormat); - formats.append(Utf16StringFormat); - formats.append(Ucs4StringFormat); - formats.append(Array10Format); - formats.append(Array100Format); - formats.append(Array1000Format); - formats.append(Array10000Format); - } else if (type.contains("char[") || type.contains("char [")) { - formats.append(RawFormat); - formats.append(Latin1StringFormat); - formats.append(SeparateLatin1StringFormat); - formats.append(Utf8StringFormat); - formats.append(SeparateUtf8StringFormat); - formats.append(Local8BitStringFormat); - formats.append(Utf16StringFormat); - formats.append(Ucs4StringFormat); - } - - // Fixed artificial floating point types. - bool ok = false; - value.toDouble(&ok); - if (ok) { - formats.append(CompactFloatFormat); - formats.append(ScientificFloatFormat); - } - - // Fixed artificial integral types. - QString v = value; - if (v.startsWith(QLatin1Char('-'))) - v = v.mid(1); - v.toULongLong(&ok, 10); - if (!ok) - v.toULongLong(&ok, 16); - if (!ok) - v.toULongLong(&ok, 8); - if (ok) { - formats.append(DecimalIntegerFormat); - formats.append(HexadecimalIntegerFormat); - formats.append(BinaryIntegerFormat); - formats.append(OctalIntegerFormat); - } - - return formats; -} - -int WatchItem::requestedFormat() const -{ - int format = theIndividualFormats.value(iname, AutomaticFormat); - if (format == AutomaticFormat) - format = theTypeFormats.value(stripForFormat(type), AutomaticFormat); - return format; -} - /////////////////////////////////////////////////////////////////////// // // WatchHandler @@ -1316,7 +1413,7 @@ void WatchHandler::updateWatchExpression(WatchItem *item, const QByteArray &newE // (address) if it can be found. Default to watchExpression(). void WatchHandler::watchVariable(const QString &exp) { - if (const WatchData *localVariable = findCppLocalVariable(exp)) + if (const WatchItem *localVariable = findCppLocalVariable(exp)) watchExpression(QLatin1String(localVariable->exp), exp); else watchExpression(exp); @@ -1489,8 +1586,13 @@ const WatchItem *WatchHandler::watchItem(const QModelIndex &idx) const void WatchHandler::fetchMore(const QByteArray &iname) const { - if (WatchItem *item = m_model->findItem(iname)) - item->fetchMore(); + if (WatchItem *item = m_model->findItem(iname)) { + m_model->m_expandedINames.insert(iname); + if (item->children().isEmpty()) { + item->setChildrenNeeded(); + m_model->m_engine->expandItem(iname); + } + } } WatchItem *WatchHandler::findItem(const QByteArray &iname) const @@ -1723,77 +1825,5 @@ QSet WatchHandler::expandedINames() const return m_model->m_expandedINames; } -//////////////////////////////////////////////////////////////////// -// -// WatchItem -// -//////////////////////////////////////////////////////////////////// - -WatchItem::WatchItem(const QByteArray &i, const QString &n) -{ - iname = i; - name = n; -} - -WatchItem::WatchItem(const WatchData &data) - : WatchData(data) -{ -} - -WatchItem::WatchItem(const GdbMi &data) -{ - iname = data["iname"].data(); - - GdbMi wname = data["wname"]; - if (wname.isValid()) // Happens (only) for watched expressions. - name = QString::fromUtf8(QByteArray::fromHex(wname.data())); - else - name = QString::fromLatin1(data["name"].data()); - - parseWatchData(data); - - if (wname.isValid()) - exp = name.toUtf8(); -} - -WatchItem *WatchItem::parentItem() const -{ - return dynamic_cast(parent()); -} - -const WatchModel *WatchItem::watchModel() const -{ - return static_cast(model()); -} - -WatchModel *WatchItem::watchModel() -{ - return static_cast(model()); -} - -void WatchItem::parseWatchData(const GdbMi &input) -{ - auto itemHandler = [this](const WatchData &data) { - static_cast(this)->operator=(data); // FIXME with 3.5 - }; - - auto childHandler = [this](const WatchData &innerData, const GdbMi &innerInput) { - WatchItem *item = new WatchItem(innerData); - item->parseWatchData(innerInput); - appendChild(item); - }; - - auto itemAdder = [this](const WatchData &data) { - appendChild(new WatchItem(data)); - }; - - auto arrayDecoder = [itemAdder](const WatchData &childTemplate, - const QByteArray &encodedData, const DebuggerEncoding &encoding) { - decodeArrayData(itemAdder, childTemplate, encodedData, encoding); - }; - - parseChildrenData(*this, input, itemHandler, childHandler, arrayDecoder); -} - } // namespace Internal } // namespace Debugger diff --git a/src/plugins/debugger/watchhandler.h b/src/plugins/debugger/watchhandler.h index 7a746e82b84..311d2c29171 100644 --- a/src/plugins/debugger/watchhandler.h +++ b/src/plugins/debugger/watchhandler.h @@ -33,8 +33,6 @@ #include "watchdata.h" -#include - #include namespace Debugger { @@ -46,44 +44,6 @@ class WatchModel; typedef QVector DisplayFormats; -class WatchItem : public Utils::TreeItem, public WatchData -{ -public: - WatchItem() {} - WatchItem(const QByteArray &i, const QString &n); - explicit WatchItem(const WatchData &data); - explicit WatchItem(const GdbMi &data); - - void fetchMore(); - - QString displayName() const; - QString displayType() const; - QString displayValue() const; - QString formattedValue() const; - QString expression() const; - - int itemFormat() const; - - QVariant editValue() const; - int editType() const; - QColor valueColor(int column) const; - - int requestedFormat() const; - WatchItem *findItem(const QByteArray &iname); - -private: - WatchItem *parentItem() const; - const WatchModel *watchModel() const; - WatchModel *watchModel(); - DisplayFormats typeFormatList() const; - - bool canFetchMore() const; - QVariant data(int column, int role) const; - Qt::ItemFlags flags(int column) const; - - void parseWatchData(const GdbMi &input); -}; - class WatchModelBase : public Utils::TreeModel { Q_OBJECT diff --git a/tests/auto/debugger/dumpers.pro b/tests/auto/debugger/dumpers.pro index 9664d0cb110..3a44baea6d7 100644 --- a/tests/auto/debugger/dumpers.pro +++ b/tests/auto/debugger/dumpers.pro @@ -1,8 +1,6 @@ QT = core network -win32-msvc* { - QTC_LIB_DEPENDS += utils -} +QTC_LIB_DEPENDS += utils include(../qttest.pri) diff --git a/tests/auto/debugger/tst_dumpers.cpp b/tests/auto/debugger/tst_dumpers.cpp index cbb1d94fdef..e84caff2d96 100644 --- a/tests/auto/debugger/tst_dumpers.cpp +++ b/tests/auto/debugger/tst_dumpers.cpp @@ -1425,62 +1425,65 @@ void tst_Dumpers::dumper() GdbMi actual; actual.fromString(contents); - WatchData local; + WatchItem local; local.iname = "local"; - QList list; foreach (const GdbMi &child, actual.children()) { - WatchData dummy; - dummy.iname = child["iname"].data(); - dummy.name = QLatin1String(child["name"].data()); - if (dummy.iname == "local.qtversion") + const QByteArray iname = child["iname"].data(); + if (iname == "local.qtversion") context.qtVersion = child["value"].toInt(); - else if (dummy.iname == "local.gccversion") + else if (iname == "local.gccversion") context.gccVersion = child["value"].toInt(); - else if (dummy.iname == "local.clangversion") + else if (iname == "local.clangversion") context.clangVersion = child["value"].toInt(); - else if (dummy.iname == "local.boostversion") + else if (iname == "local.boostversion") context.boostVersion = child["value"].toInt(); - else - parseWatchData(dummy, child, &list); + else { + WatchItem *item = new WatchItem; + item->parse(child); + local.appendChild(item); + } } //qDebug() << "QT VERSION " << QByteArray::number(context.qtVersion, 16); QSet seenINames; bool ok = true; - foreach (const WatchData &item, list) { - seenINames.insert(item.iname); + + for (int i = data.checks.size(); --i >= 0; ) { //qDebug() << "NUM CHECKS" << data.checks.size(); - for (int i = data.checks.size(); --i >= 0; ) { - Check check = data.checks.at(i); + Check check = data.checks.at(i); + QByteArray iname = "local." + check.iname; + if (const WatchItem *item = local.findItem(iname)) { + seenINames.insert(iname); //qDebug() << "CHECKS" << i << check.iname; - if ("local." + check.iname == item.iname) { - data.checks.removeAt(i); - if (check.matches(m_debuggerEngine, m_debuggerVersion, context)) { - //qDebug() << "USING MATCHING TEST FOR " << item.iname; - if (!check.expectedName.matches(item.name.toLatin1(), context)) { - qDebug() << "INAME : " << item.iname; - qDebug() << "NAME ACTUAL : " << item.name.toLatin1(); - qDebug() << "NAME EXPECTED: " << check.expectedName.name; - ok = false; - } - if (!check.expectedValue.matches(item.value, context)) { - qDebug() << "INAME : " << item.iname; - qDebug() << "VALUE ACTUAL : " << item.value << toHex(item.value); - qDebug() << "VALUE EXPECTED: " - << check.expectedValue.value << toHex(check.expectedValue.value); - ok = false; - } - if (!check.expectedType.matches(item.type, context)) { - qDebug() << "INAME : " << item.iname; - qDebug() << "TYPE ACTUAL : " << item.type; - qDebug() << "TYPE EXPECTED: " << check.expectedType.type; - ok = false; - } - } else { - qDebug() << "SKIPPING NON-MATCHING TEST FOR " << item.iname; + data.checks.removeAt(i); + if (check.matches(m_debuggerEngine, m_debuggerVersion, context)) { + //qDebug() << "USING MATCHING TEST FOR " << iname; + QByteArray name = item->realName().toLatin1(); + QByteArray type = item->type; + if (!check.expectedName.matches(name, context)) { + qDebug() << "INAME : " << iname; + qDebug() << "NAME ACTUAL : " << name; + qDebug() << "NAME EXPECTED: " << check.expectedName.name; + ok = false; } + if (!check.expectedValue.matches(item->value, context)) { + qDebug() << "INAME : " << iname; + qDebug() << "VALUE ACTUAL : " << item->value << toHex(item->value); + qDebug() << "VALUE EXPECTED: " << check.expectedValue.value << toHex(check.expectedValue.value); + ok = false; + } + if (!check.expectedType.matches(type, context)) { + qDebug() << "INAME : " << iname; + qDebug() << "TYPE ACTUAL : " << type; + qDebug() << "TYPE EXPECTED: " << check.expectedType.type; + ok = false; + } + } else { + qDebug() << "SKIPPING NON-MATCHING TEST FOR " << iname; } + } else { + qDebug() << "NOT SEEN: " << check.iname; } }