Debugger: add <load more> functionality to array dumper

Change-Id: Ib44748fa3218788ca20a99b0a0f4cd85716dde06
Reviewed-by: hjk <hjk@qt.io>
This commit is contained in:
David Schulz
2023-02-21 15:32:13 +01:00
parent 8ecd8692bf
commit 5169469911
10 changed files with 82 additions and 37 deletions

View File

@@ -106,7 +106,7 @@ class Children():
self.d.putNumChild(0) self.d.putNumChild(0)
if self.d.currentMaxNumChild is not None: if self.d.currentMaxNumChild is not None:
if self.d.currentMaxNumChild < self.d.currentNumChild: if self.d.currentMaxNumChild < self.d.currentNumChild:
self.d.put('{name="<incomplete>",value="",type="",numchild="0"},') self.d.put('{name="<load more>",value="",type="",numchild="1"},')
self.d.currentChildType = self.savedChildType self.d.currentChildType = self.savedChildType
self.d.currentChildNumChild = self.savedChildNumChild self.d.currentChildNumChild = self.savedChildNumChild
self.d.currentNumChild = self.savedNumChild self.d.currentNumChild = self.savedNumChild
@@ -214,7 +214,7 @@ class DumperBase():
def setVariableFetchingOptions(self, args): def setVariableFetchingOptions(self, args):
self.resultVarName = args.get('resultvarname', '') self.resultVarName = args.get('resultvarname', '')
self.expandedINames = set(args.get('expanded', [])) self.expandedINames = args.get('expanded', {})
self.stringCutOff = int(args.get('stringcutoff', 10000)) self.stringCutOff = int(args.get('stringcutoff', 10000))
self.displayStringLimit = int(args.get('displaystringlimit', 100)) self.displayStringLimit = int(args.get('displaystringlimit', 100))
self.typeformats = args.get('typeformats', {}) self.typeformats = args.get('typeformats', {})
@@ -297,6 +297,11 @@ class DumperBase():
return range(0, self.currentNumChild) return range(0, self.currentNumChild)
return range(min(self.currentMaxNumChild, self.currentNumChild)) return range(min(self.currentMaxNumChild, self.currentNumChild))
def maxArrayCount(self):
if self.currentIName in self.expandedINames:
return self.expandedINames[self.currentIName]
return 100
def enterSubItem(self, item): def enterSubItem(self, item):
if self.useTimeStamps: if self.useTimeStamps:
item.startTime = time.time() item.startTime = time.time()
@@ -2232,7 +2237,7 @@ class DumperBase():
res = self.currentValue res = self.currentValue
return res # The 'short' display. return res # The 'short' display.
def putArrayData(self, base, n, innerType, childNumChild=None, maxNumChild=10000): def putArrayData(self, base, n, innerType, childNumChild=None):
self.checkIntType(base) self.checkIntType(base)
self.checkIntType(n) self.checkIntType(n)
addrBase = base addrBase = base
@@ -2240,6 +2245,7 @@ class DumperBase():
self.putNumChild(n) self.putNumChild(n)
#DumperBase.warn('ADDRESS: 0x%x INNERSIZE: %s INNERTYPE: %s' % (addrBase, innerSize, innerType)) #DumperBase.warn('ADDRESS: 0x%x INNERSIZE: %s INNERTYPE: %s' % (addrBase, innerSize, innerType))
enc = innerType.simpleEncoding() enc = innerType.simpleEncoding()
maxNumChild = self.maxArrayCount()
if enc: if enc:
self.put('childtype="%s",' % innerType.name) self.put('childtype="%s",' % innerType.name)
self.put('addrbase="0x%x",' % addrBase) self.put('addrbase="0x%x",' % addrBase)
@@ -2247,7 +2253,7 @@ class DumperBase():
self.put('arrayencoding="%s",' % enc) self.put('arrayencoding="%s",' % enc)
self.put('endian="%s",' % self.packCode) self.put('endian="%s",' % self.packCode)
if n > maxNumChild: if n > maxNumChild:
self.put('childrenelided="%s",' % n) # FIXME: Act on that in frontend self.put('childrenelided="%s",' % n)
n = maxNumChild n = maxNumChild
self.put('arraydata="') self.put('arraydata="')
self.put(self.readMemory(addrBase, n * innerSize)) self.put(self.readMemory(addrBase, n * innerSize))
@@ -2281,7 +2287,7 @@ class DumperBase():
def putPlotData(self, base, n, innerType, maxNumChild=1000 * 1000): def putPlotData(self, base, n, innerType, maxNumChild=1000 * 1000):
self.putPlotDataHelper(base, n, innerType, maxNumChild=maxNumChild) self.putPlotDataHelper(base, n, innerType, maxNumChild=maxNumChild)
if self.isExpanded(): if self.isExpanded():
self.putArrayData(base, n, innerType, maxNumChild=maxNumChild) self.putArrayData(base, n, innerType)
def putSpecialArgv(self, value): def putSpecialArgv(self, value):
""" """

View File

@@ -2120,7 +2120,7 @@ class SummaryDumper(Dumper, LogMixin):
# Expand variable if we need synthetic children # Expand variable if we need synthetic children
oldExpanded = self.expandedINames oldExpanded = self.expandedINames
self.expandedINames = [value.name] if expanded else [] self.expandedINames = {value.name: 100} if expanded else {}
savedOutput = self.output savedOutput = self.output
self.output = [] self.output = []

View File

@@ -1445,7 +1445,7 @@ class QtcInternalDumper():
self.updateData(args) self.updateData(args)
def updateData(self, args): def updateData(self, args):
self.expandedINames = __builtins__.set(args.get('expanded', [])) self.expandedINames = args.get('expanded', {})
self.typeformats = args.get('typeformats', {}) self.typeformats = args.get('typeformats', {})
self.formats = args.get('formats', {}) self.formats = args.get('formats', {})
self.output = '' self.output = ''

View File

@@ -471,7 +471,7 @@ public:
QString m_qtNamespace; QString m_qtNamespace;
// Safety net to avoid infinite lookups. // Safety net to avoid infinite lookups.
QSet<QString> m_lookupRequests; // FIXME: Integrate properly. QHash<QString, int> m_lookupRequests; // FIXME: Integrate properly.
QPointer<QWidget> m_alertBox; QPointer<QWidget> m_alertBox;
QPointer<BaseTreeView> m_breakView; QPointer<BaseTreeView> m_breakView;
@@ -2360,9 +2360,10 @@ bool DebuggerEngine::canHandleToolTip(const DebuggerToolTipContext &context) con
void DebuggerEngine::updateItem(const QString &iname) void DebuggerEngine::updateItem(const QString &iname)
{ {
if (d->m_lookupRequests.contains(iname)) { WatchHandler *handler = watchHandler();
const int maxArrayCount = handler->maxArrayCount(iname);
if (d->m_lookupRequests.value(iname, -1) == maxArrayCount) {
showMessage(QString("IGNORING REPEATED REQUEST TO EXPAND " + iname)); showMessage(QString("IGNORING REPEATED REQUEST TO EXPAND " + iname));
WatchHandler *handler = watchHandler();
WatchItem *item = handler->findItem(iname); WatchItem *item = handler->findItem(iname);
QTC_CHECK(item); QTC_CHECK(item);
WatchModelBase *model = handler->model(); WatchModelBase *model = handler->model();
@@ -2382,7 +2383,7 @@ void DebuggerEngine::updateItem(const QString &iname)
} }
// We could legitimately end up here after expanding + closing + re-expaning an item. // We could legitimately end up here after expanding + closing + re-expaning an item.
} }
d->m_lookupRequests.insert(iname); d->m_lookupRequests[iname] = maxArrayCount;
UpdateParameters params; UpdateParameters params;
params.partialVariable = iname; params.partialVariable = iname;

View File

@@ -2321,7 +2321,6 @@ void QmlEnginePrivate::insertSubItems(WatchItem *parent, const QVariantList &pro
QTC_ASSERT(parent, return); QTC_ASSERT(parent, return);
LookupItems itemsToLookup; LookupItems itemsToLookup;
const QSet<QString> expandedINames = engine->watchHandler()->expandedINames();
for (const QVariant &property : properties) { for (const QVariant &property : properties) {
QmlV8ObjectData propertyData = extractData(property); QmlV8ObjectData propertyData = extractData(property);
std::unique_ptr<WatchItem> item(new WatchItem); std::unique_ptr<WatchItem> item(new WatchItem);
@@ -2343,7 +2342,7 @@ void QmlEnginePrivate::insertSubItems(WatchItem *parent, const QVariantList &pro
item->id = propertyData.handle; item->id = propertyData.handle;
item->type = propertyData.type; item->type = propertyData.type;
item->value = propertyData.value.toString(); item->value = propertyData.value.toString();
if (item->type.isEmpty() || expandedINames.contains(item->iname)) if (item->type.isEmpty() || engine->watchHandler()->isExpandedIName(item->iname))
itemsToLookup.insert(propertyData.handle, {item->iname, item->name, item->exp}); itemsToLookup.insert(propertyData.handle, {item->iname, item->name, item->exp});
setWatchItemHasChildren(item.get(), propertyData.hasChildren()); setWatchItemHasChildren(item.get(), propertyData.hasChildren());
parent->appendChild(item.release()); parent->appendChild(item.release());

View File

@@ -212,6 +212,13 @@ public:
child->valueEditable = true; child->valueEditable = true;
item->appendChild(child); item->appendChild(child);
} }
if (childrenElided) {
auto child = new WatchItem;
child->name = WatchItem::loadMoreName;
child->iname = item->iname + "." + WatchItem::loadMoreName;
child->wantsChildren = true;
item->appendChild(child);
}
} }
void decode() void decode()
@@ -260,6 +267,7 @@ public:
QString rawData; QString rawData;
QString childType; QString childType;
DebuggerEncoding encoding; DebuggerEncoding encoding;
int childrenElided;
quint64 addrbase; quint64 addrbase;
quint64 addrstep; quint64 addrstep;
Endian endian; Endian endian;
@@ -375,6 +383,7 @@ void WatchItem::parseHelper(const GdbMi &input, bool maySort)
decoder.item = this; decoder.item = this;
decoder.rawData = mi.data(); decoder.rawData = mi.data();
decoder.childType = input["childtype"].data(); decoder.childType = input["childtype"].data();
decoder.childrenElided = input["childrenelided"].toInt();
decoder.addrbase = input["addrbase"].toAddress(); decoder.addrbase = input["addrbase"].toAddress();
decoder.addrstep = input["addrstep"].toAddress(); decoder.addrstep = input["addrstep"].toAddress();
decoder.endian = input["endian"].data() == ">" ? Endian::Big : Endian::Little; decoder.endian = input["endian"].data() == ">" ? Endian::Big : Endian::Little;
@@ -500,6 +509,11 @@ QString WatchItem::toToolTip() const
return res; return res;
} }
bool WatchItem::isLoadMore() const
{
return name == loadMoreName;
}
bool WatchItem::isLocal() const bool WatchItem::isLocal() const
{ {
if (arrayIndex >= 0) if (arrayIndex >= 0)

View File

@@ -36,8 +36,10 @@ public:
int editType() const; int editType() const;
static const qint64 InvalidId = -1; static const qint64 InvalidId = -1;
constexpr static char loadMoreName[] = "<load more>";
void setHasChildren(bool c) { wantsChildren = c; } void setHasChildren(bool c) { wantsChildren = c; }
bool isLoadMore() const;
bool isValid() const { return !iname.isEmpty(); } bool isValid() const { return !iname.isEmpty(); }
bool isVTablePointer() const; bool isVTablePointer() const;

View File

@@ -402,6 +402,7 @@ public:
WatchModel(WatchHandler *handler, DebuggerEngine *engine); WatchModel(WatchHandler *handler, DebuggerEngine *engine);
static QString nameForFormat(int format); static QString nameForFormat(int format);
constexpr static int defaultMaxArrayCount = 100;
QVariant data(const QModelIndex &idx, int role) const override; QVariant data(const QModelIndex &idx, int role) const override;
bool setData(const QModelIndex &idx, const QVariant &value, int role) override; bool setData(const QModelIndex &idx, const QVariant &value, int role) override;
@@ -410,6 +411,7 @@ public:
bool hasChildren(const QModelIndex &idx) const override; bool hasChildren(const QModelIndex &idx) const override;
bool canFetchMore(const QModelIndex &idx) const override; bool canFetchMore(const QModelIndex &idx) const override;
void fetchMore(const QModelIndex &idx) override; void fetchMore(const QModelIndex &idx) override;
void expand(WatchItem *item, bool requestEngineUpdate);
QString displayForAutoTest(const QByteArray &iname) const; QString displayForAutoTest(const QByteArray &iname) const;
void reinitialize(bool includeInspectData = false); void reinitialize(bool includeInspectData = false);
@@ -468,6 +470,7 @@ public:
SeparatedView *m_separatedView; // Not owned. SeparatedView *m_separatedView; // Not owned.
QSet<QString> m_expandedINames; QSet<QString> m_expandedINames;
QHash<QString, int> m_maxArrayCount;
QTimer m_requestUpdateTimer; QTimer m_requestUpdateTimer;
QHash<QString, TypeInfo> m_reportedTypeInfo; QHash<QString, TypeInfo> m_reportedTypeInfo;
@@ -1210,7 +1213,8 @@ bool WatchModel::setData(const QModelIndex &idx, const QVariant &value, int role
if (value.toBool()) { if (value.toBool()) {
// Should already have been triggered by fetchMore() // Should already have been triggered by fetchMore()
//QTC_CHECK(m_expandedINames.contains(item->iname)); //QTC_CHECK(m_expandedINames.contains(item->iname));
m_expandedINames.insert(item->iname); if (!item->isLoadMore())
m_expandedINames.insert(item->iname);
} else { } else {
m_expandedINames.remove(item->iname); m_expandedINames.remove(item->iname);
} }
@@ -1320,13 +1324,22 @@ bool WatchModel::canFetchMore(const QModelIndex &idx) const
void WatchModel::fetchMore(const QModelIndex &idx) void WatchModel::fetchMore(const QModelIndex &idx)
{ {
if (!idx.isValid()) if (idx.isValid())
return; expand(nonRootItemForIndex(idx), true);
}
WatchItem *item = nonRootItemForIndex(idx); void WatchModel::expand(WatchItem *item, bool requestEngineUpdate)
if (item) { {
if (!item)
return;
if (item->isLoadMore()) {
item = item->parent();
m_maxArrayCount[item->iname] = m_maxArrayCount.value(item->iname, defaultMaxArrayCount) * 10;
if (requestEngineUpdate)
m_engine->updateItem(item->iname);
} else {
m_expandedINames.insert(item->iname); m_expandedINames.insert(item->iname);
if (item->childCount() == 0) if (requestEngineUpdate && item->childCount() == 0)
m_engine->expandItem(item->iname); m_engine->expandItem(item->iname);
} }
} }
@@ -1749,10 +1762,14 @@ bool WatchModel::contextMenuEvent(const ItemViewEvent &ev)
menu->addSeparator(); menu->addSeparator();
addAction(this, menu, Tr::tr("Expand All Children"), item, [this, name = item ? item->iname : QString()] { addAction(this, menu, Tr::tr("Expand All Children"), item, [this, name = item ? item->iname : QString()] {
m_expandedINames.insert(name); if (name.isEmpty())
if (auto item = findItem(name)) { return;
item->forFirstLevelChildren( if (WatchItem *item = findItem(name)) {
[this](WatchItem *child) { m_expandedINames.insert(child->iname); }); expand(item, false);
item->forFirstLevelChildren([this](WatchItem *child) {
if (!child->isLoadMore())
expand(child, false);
});
m_engine->updateLocals(); m_engine->updateLocals();
} }
}); });
@@ -2210,7 +2227,7 @@ bool WatchHandler::insertItem(WatchItem *item)
void WatchModel::reexpandItems() void WatchModel::reexpandItems()
{ {
for (const QString &iname : std::as_const(m_expandedINames)) { for (const QString &iname: m_expandedINames) {
if (WatchItem *item = findItem(iname)) { if (WatchItem *item = findItem(iname)) {
emit itemIsExpanded(indexForItem(item)); emit itemIsExpanded(indexForItem(item));
emit inameIsExpanded(iname); emit inameIsExpanded(iname);
@@ -2292,7 +2309,8 @@ void WatchHandler::notifyUpdateFinished()
m_model->destroyItem(item); m_model->destroyItem(item);
m_model->forAllItems([this](WatchItem *item) { m_model->forAllItems([this](WatchItem *item) {
if (item->wantsChildren && isExpandedIName(item->iname)) { if (item->wantsChildren && isExpandedIName(item->iname)
&& item->name != WatchItem::loadMoreName) {
m_model->m_engine->showMessage(QString("ADJUSTING CHILD EXPECTATION FOR " + item->iname)); m_model->m_engine->showMessage(QString("ADJUSTING CHILD EXPECTATION FOR " + item->iname));
item->wantsChildren = false; item->wantsChildren = false;
} }
@@ -2593,11 +2611,8 @@ const WatchItem *WatchHandler::watchItem(const QModelIndex &idx) const
void WatchHandler::fetchMore(const QString &iname) const void WatchHandler::fetchMore(const QString &iname) const
{ {
if (WatchItem *item = m_model->findItem(iname)) { if (WatchItem *item = m_model->findItem(iname))
m_model->m_expandedINames.insert(iname); m_model->expand(item, true);
if (item->childCount() == 0)
m_model->m_engine->expandItem(iname);
}
} }
WatchItem *WatchHandler::findItem(const QString &iname) const WatchItem *WatchHandler::findItem(const QString &iname) const
@@ -2711,9 +2726,9 @@ QString WatchHandler::individualFormatRequests() const
void WatchHandler::appendFormatRequests(DebuggerCommand *cmd) const void WatchHandler::appendFormatRequests(DebuggerCommand *cmd) const
{ {
QJsonArray expanded; QJsonObject expanded;
for (const QString &name : std::as_const(m_model->m_expandedINames)) for (const QString &iname : std::as_const(m_model->m_expandedINames))
expanded.append(name); expanded.insert(iname, maxArrayCount(iname));
cmd->arg("expanded", expanded); cmd->arg("expanded", expanded);
@@ -2817,6 +2832,11 @@ QSet<QString> WatchHandler::expandedINames() const
return m_model->m_expandedINames; return m_model->m_expandedINames;
} }
int WatchHandler::maxArrayCount(const QString &iname) const
{
return m_model->m_maxArrayCount.value(iname, WatchModel::defaultMaxArrayCount);
}
void WatchHandler::recordTypeInfo(const GdbMi &typeInfo) void WatchHandler::recordTypeInfo(const GdbMi &typeInfo)
{ {
if (typeInfo.type() == GdbMi::List) { if (typeInfo.type() == GdbMi::List) {

View File

@@ -60,6 +60,7 @@ public:
bool isExpandedIName(const QString &iname) const; bool isExpandedIName(const QString &iname) const;
QSet<QString> expandedINames() const; QSet<QString> expandedINames() const;
int maxArrayCount(const QString &iname) const;
static QStringList watchedExpressions(); static QStringList watchedExpressions();
static QMap<QString, int> watcherNames(); static QMap<QString, int> watcherNames();

View File

@@ -1736,7 +1736,8 @@ void tst_Dumpers::dumper()
expandedq.append(','); expandedq.append(',');
} }
expanded += iname; expanded += iname;
expandedq += '\'' + iname + '\''; expandedq += '\'' + iname + "':";
expandedq += data.bigArray ? "10000" : "100";
} }
QString exe = m_debuggerBinary; QString exe = m_debuggerBinary;
@@ -1769,7 +1770,7 @@ void tst_Dumpers::dumper()
"'token':2,'fancy':1,'forcens':1," "'token':2,'fancy':1,'forcens':1,"
"'autoderef':1,'dyntype':1,'passexceptions':1," "'autoderef':1,'dyntype':1,'passexceptions':1,"
"'testing':1,'qobjectnames':1," "'testing':1,'qobjectnames':1,"
"'expanded':[" + expandedq + "]})\n"; "'expanded':{" + expandedq + "}})\n";
cmds += "quit\n"; cmds += "quit\n";
@@ -1792,7 +1793,7 @@ void tst_Dumpers::dumper()
"'token':2,'fancy':1,'forcens':1," "'token':2,'fancy':1,'forcens':1,"
"'autoderef':1,'dyntype':1,'passexceptions':0," "'autoderef':1,'dyntype':1,'passexceptions':0,"
"'testing':1,'qobjectnames':1," "'testing':1,'qobjectnames':1,"
"'expanded':[" + expandedq + "]})\n" "'expanded':{" + expandedq + "}})\n"
"q\n"; "q\n";
} else if (m_debuggerEngine == LldbEngine) { } else if (m_debuggerEngine == LldbEngine) {
QFile fullLldb(t->buildPath + "/lldbcommand.txt"); QFile fullLldb(t->buildPath + "/lldbcommand.txt");
@@ -1808,7 +1809,7 @@ void tst_Dumpers::dumper()
"'fancy':1,'forcens':1," "'fancy':1,'forcens':1,"
"'autoderef':1,'dyntype':1,'passexceptions':1," "'autoderef':1,'dyntype':1,'passexceptions':1,"
"'testing':1,'qobjectnames':1," "'testing':1,'qobjectnames':1,"
"'expanded':[" + expandedq + "]})\n" "'expanded':{" + expandedq + "}})\n"
"quit\n"; "quit\n";
fullLldb.write(cmds.toUtf8()); fullLldb.write(cmds.toUtf8());
@@ -5310,6 +5311,7 @@ void tst_Dumpers::dumper_data()
"&v0, &v1, &v2, &v3, &v4, &v5, &b0, &b1, &b2, &b3") "&v0, &v1, &v2, &v3, &v4, &v5, &b0, &b1, &b2, &b3")
+ Cxx11Profile() + Cxx11Profile()
+ BigArrayProfile()
+ Check("v0", "<0 items>", "std::valarray<double>") + Check("v0", "<0 items>", "std::valarray<double>")
+ Check("v1", "<3 items>", "std::valarray<double>") + Check("v1", "<3 items>", "std::valarray<double>")