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

View File

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

View File

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

View File

@@ -471,7 +471,7 @@ public:
QString m_qtNamespace;
// 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<BaseTreeView> m_breakView;
@@ -2360,9 +2360,10 @@ bool DebuggerEngine::canHandleToolTip(const DebuggerToolTipContext &context) con
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));
WatchHandler *handler = watchHandler();
WatchItem *item = handler->findItem(iname);
QTC_CHECK(item);
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.
}
d->m_lookupRequests.insert(iname);
d->m_lookupRequests[iname] = maxArrayCount;
UpdateParameters params;
params.partialVariable = iname;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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