Commit 5bd1d2a0 authored by Friedemann Kleint's avatar Friedemann Kleint

Fix up Debugger helpers to be able to dump QObject's with CDB.

- Make container dumper routines set "childnumchild" when known
  in order to avoid roundtrips; avoid repeated invocations of
  container.end().
- Completed dumper information in some places to avoid roundtrips.
- Extended QVariant helpers by dumpers for common GUI types
  (rectangles, points, sizes, fonts, size policies).
- Introduced artificial QObjectChildList/QObjectProperty types to
  be able to dump QObject children and properties without using
  gdb expressions.
- Fixed dumping of Signal/Slot list to pass on correct types. Avoid
  recursions if signal is connected to self.
- Replaced expressions by addresses in the dumpers to it make work
  for CDB.
- Reworked dumper test program to have -a, making it usable for tests,
  add further types.
- Gdb: Clear output buffer before calling dumpers, avoiding mixups
  in case evaluation of expression fails.
- Fix the dumper parser used by CDB, do not be fooled by
  "<synthetic>" addresses, etc.
- Pass on a "dumperVersion" in initial query.
parent 45448ce5
This diff is collapsed.
......@@ -4,8 +4,6 @@
#
#-------------------------------------------------
QT -= gui
TARGET = dumpertest
CONFIG += console
CONFIG -= app_bundle
......
......@@ -32,6 +32,8 @@
#include <QtCore/QSharedPointer>
#include <QtCore/QTimer>
#include <QtCore/QMap>
#include <QtCore/QVariant>
#include <QtGui/QAction>
#include <string>
#include <list>
......@@ -174,6 +176,20 @@ static int dumpQMapIntString()
return 0;
}
static int dumpQVariant()
{
QVariant test(QLatin1String("item"));
prepareInBuffer("QVariant", "local.qvariant", "local.qvariant", "");
qDumpObjectData440(2, 42, testAddress(&test), 1, 0, 0,0 ,0);
fputs(qDumpOutBuffer, stdout);
fputs("\n\n", stdout);
test = QVariant(QStringList(QLatin1String("item1")));
prepareInBuffer("QVariant", "local.qvariant", "local.qvariant", "");
qDumpObjectData440(2, 42, testAddress(&test), 1, 0, 0,0 ,0);
fputs(qDumpOutBuffer, stdout);
return 0;
}
// --------------- std types
static int dumpStdString()
......@@ -309,76 +325,127 @@ static int dumpStdMapIntString()
static int dumpQObject()
{
// Requires the childOffset to be know, but that is not critical
QTimer t;
QAction action(0);
QObject x;
QAction *a2= new QAction(&action);
a2->setObjectName(QLatin1String("a2"));
action.setObjectName(QLatin1String("action"));
QObject::connect(&action, SIGNAL(triggered()), &x, SLOT(deleteLater()));
prepareInBuffer("QObject", "local.qobject", "local.qobject", "");
qDumpObjectData440(2, 42, testAddress(&t), 1, 0, 0, 0, 0);
qDumpObjectData440(2, 42, testAddress(&action), 1, 0, 0, 0, 0);
fputs(qDumpOutBuffer, stdout);
fputs("\n\n", stdout);
// Property list
prepareInBuffer("QObjectPropertyList", "local.qobjectpropertylist", "local.qobjectpropertylist", "");
qDumpObjectData440(2, 42, testAddress(&action), 1, 0, 0, 0, 0);
fputs(qDumpOutBuffer, stdout);
fputs("\n\n", stdout);
// Signal list
prepareInBuffer("QObjectSignalList", "local.qobjectsignallist", "local.qobjectsignallist", "");
qDumpObjectData440(2, 42, testAddress(&action), 1, 0, 0, 0, 0);
fputs(qDumpOutBuffer, stdout);
// Slot list
prepareInBuffer("QObjectSlotList", "local.qobjectslotlist", "local.qobjectslotlist", "");
qDumpObjectData440(2, 42, testAddress(&action), 1, 0, 0, 0, 0);
fputs(qDumpOutBuffer, stdout);
fputs("\n\n", stdout);
// Signal list
prepareInBuffer("QObjectChildList", "local.qobjectchildlist", "local.qobjectchildlist", "");
qDumpObjectData440(2, 42, testAddress(&action), 1, 0, 0, 0, 0);
fputs(qDumpOutBuffer, stdout);
return 0;
}
static int dumpQObjectList()
{
// Requires the childOffset to be know, but that is not critical
QObject *root = new QObject;
root ->setObjectName("root");
QTimer *t1 = new QTimer;
t1 ->setObjectName("t1");
QTimer *t2 = new QTimer;
t2 ->setObjectName("t2");
QObjectList test;
test << root << t1 << t2;
prepareInBuffer("QList", "local.qobjectlist", "local.qobjectlist", "QObject *");
qDumpObjectData440(2, 42, testAddress(&test), sizeof(QObject*), 0, 0, 0, 0);
fputs(qDumpOutBuffer, stdout);
fputc('\n', stdout);
delete root;
return 0;
}
static bool dumpType(const char *arg)
typedef int (*DumpFunction)();
typedef QMap<QString, DumpFunction> TypeDumpFunctionMap;
static TypeDumpFunctionMap registerTypes()
{
if (!qstrcmp(arg, "QString"))
{ dumpQString(); return true; }
if (!qstrcmp(arg, "QSharedPointer<QString>"))
{ dumpQSharedPointerQString(); return true; }
if (!qstrcmp(arg, "QStringList"))
{ dumpQStringList(); return true; }
if (!qstrcmp(arg, "QList<int>"))
{ dumpQIntList(); return true; }
if (!qstrcmp(arg, "QList<std::string>"))
{ dumpStdStringQList(); return true; }
if (!qstrcmp(arg, "QVector<int>"))
{ dumpQIntVector(); return true; }
if (!qstrcmp(arg, "QMap<int,QString>"))
{ dumpQMapIntString(); return true; }
if (!qstrcmp(arg, "QMap<int,int>"))
{ dumpQMapIntInt(); return true; }
if (!qstrcmp(arg, "string"))
{ dumpStdString(); return true; }
if (!qstrcmp(arg, "wstring"))
{ dumpStdWString(); return true; }
if (!qstrcmp(arg, "list<int>"))
{ dumpStdIntList(); return true; }
if (!qstrcmp(arg, "list<string>"))
{ dumpStdStringList(); return true; }
if (!qstrcmp(arg, "vector<int>"))
{ dumpStdIntVector(); return true; }
if (!qstrcmp(arg, "vector<string>"))
{ dumpStdStringVector(); return true; }
if (!qstrcmp(arg, "vector<wstring>"))
{ dumpStdWStringVector(); return true; }
if (!qstrcmp(arg, "set<int>"))
{ dumpStdIntSet(); return true; }
if (!qstrcmp(arg, "set<string>"))
{ dumpStdStringSet(); return true; }
if (!qstrcmp(arg, "map<int,string>"))
{ dumpStdMapIntString(); return true; }
if (!qstrcmp(arg, "QObject"))
{ dumpQObject(); return true; }
return false;
TypeDumpFunctionMap rc;
rc.insert("QString", dumpQString);
rc.insert("QSharedPointer<QString>", dumpQSharedPointerQString);
rc.insert("QStringList", dumpQStringList);
rc.insert("QList<int>", dumpQIntList);
rc.insert("QList<std::string>", dumpStdStringQList);
rc.insert("QVector<int>", dumpQIntVector);
rc.insert("QMap<int,QString>", dumpQMapIntString);
rc.insert("QMap<int,int>", dumpQMapIntInt);
rc.insert("string", dumpStdString);
rc.insert("wstring", dumpStdWString);
rc.insert("list<int>", dumpStdIntList);
rc.insert("list<string>", dumpStdStringList);
rc.insert("vector<int>", dumpStdIntVector);
rc.insert("vector<string>", dumpStdStringVector);
rc.insert("vector<wstring>", dumpStdWStringVector);
rc.insert("set<int>", dumpStdIntSet);
rc.insert("set<string>", dumpStdStringSet);
rc.insert("map<int,string>", dumpStdMapIntString);
rc.insert("QObject", dumpQObject);
rc.insert("QObjectList", dumpQObjectList);
rc.insert("QVariant", dumpQVariant);
return rc;
}
int main(int argc, char *argv[])
{
printf("\nQt Creator Debugging Helper testing tool\n\n");
printf("Running query protocol\n");
qDumpObjectData440(1, 42, 0, 1, 0, 0, 0, 0);
fputs(qDumpOutBuffer, stdout);
fputc('\n', stdout);
fputc('\n', stdout);
if (argc < 2)
const TypeDumpFunctionMap tdm = registerTypes();
const TypeDumpFunctionMap::const_iterator cend = tdm.constEnd();
if (argc < 2) {
printf("Usage: %s [-a]|<type1> <type2..>\n", argv[0]);
printf("Supported types: ");
for (TypeDumpFunctionMap::const_iterator it = tdm.constBegin(); it != cend; ++it) {
fputs(qPrintable(it.key()), stdout);
fputc(' ', stdout);
}
fputc('\n', stdout);
return 0;
for (int i = 1; i < argc; i++) {
const char *arg = argv[i];
if (!strcmp(arg, "-u")) {
optTestUninitialized = true;
printf("\nTesting uninitialized...\n");
continue;
}
int rc = 0;
if (argc == 2 && !qstrcmp(argv[1], "-a")) {
for (TypeDumpFunctionMap::const_iterator it = tdm.constBegin(); it != cend; ++it) {
printf("\nTesting: %s\n", qPrintable(it.key()));
rc += (*it.value())();
}
} else {
for (int i = 1; i < argc; i++) {
const char *arg = argv[i];
printf("\nTesting: %s\n", arg);
const TypeDumpFunctionMap::const_iterator it = tdm.constFind(QLatin1String(arg));
if (it == cend) {
rc = -1;
fprintf(stderr, "\nUnhandled type: %s\n", argv[i]);
} else {
rc = (*it.value())();
}
}
printf("\nTesting %s\n", arg);
if (!dumpType(arg))
printf("\nUnhandled type: %s\n", arg);
}
return 0;
return rc;
}
......@@ -561,6 +561,10 @@ CdbDumperHelper::DumpResult CdbDumperHelper::dumpType(const WatchData &wd, bool
*errorMessage = msgNotHandled(wd.type);
return DumpNotHandled;
}
if (wd.addr.isEmpty()) {
*errorMessage = QString::fromLatin1("Adress is missing for '%1' (%2).").arg(wd.exp, wd.type);
return DumpNotHandled;
}
// Ensure types are parsed and known.
if (!ensureInitialized(errorMessage)) {
......
......@@ -35,6 +35,7 @@
#include "watchhandler.h"
#include <QtCore/QDebug>
#include <QtCore/QCoreApplication>
namespace Debugger {
namespace Internal {
......@@ -164,6 +165,46 @@ bool WatchHandleDumperInserter::expandPointerToDumpable(const WatchData &wd, QSt
return handled;
}
// When querying an item, the queried item is sometimes returned in incomplete form.
// Take over values from source.
static inline void fixDumperResult(const WatchData &source,
QList<WatchData> *result,
bool suppressGrandChildren)
{
const int size = result->size();
if (!size)
return;
WatchData &returned = result->front();
if (returned.iname != source.iname)
return;
if (returned.type.isEmpty())
returned.setType(source.type);
if (returned.isValueNeeded()) {
if (source.isValueKnown()) {
returned.setValue(source.value);
} else {
// Should not happen
returned.setValue(QCoreApplication::translate("CdbStackFrameContext", "<Unknown>"));
}
}
if (size == 1)
return;
// Fix the children: If the address is missing, we cannot query any further.
const QList<WatchData>::iterator wend = result->end();
QList<WatchData>::iterator it = result->begin();
for (++it; it != wend; ++it) {
WatchData &wd = *it;
if (wd.addr.isEmpty() && wd.isSomethingNeeded()) {
wd.setAllUnneeded();
} else {
// Hack: Suppress endless recursion of the model. To be fixed,
// the model should not query non-visible items.
if (suppressGrandChildren && (wd.isChildrenNeeded() || wd.isHasChildrenNeeded()))
wd.setHasChildren(false);
}
}
}
WatchHandleDumperInserter &WatchHandleDumperInserter::operator=(WatchData &wd)
{
if (debugCDBWatchHandling)
......@@ -180,9 +221,7 @@ WatchHandleDumperInserter &WatchHandleDumperInserter::operator=(WatchData &wd)
if (debugCDBWatchHandling)
qDebug() << "dumper triggered";
// Dumpers omit types for complicated templates
if (!m_dumperResult.isEmpty() && m_dumperResult.front().type.isEmpty()
&& m_dumperResult.front().iname == wd.iname)
m_dumperResult.front().setType(wd.type);
fixDumperResult(wd, &m_dumperResult, false);
// Discard the original item and insert the dumper results
foreach(const WatchData &dwd, m_dumperResult)
m_wh->insertData(dwd);
......@@ -254,12 +293,17 @@ bool CdbStackFrameContext::completeData(const WatchData &incompleteLocal,
QList<WatchData> dumperResult;
const CdbDumperHelper::DumpResult dr = m_dumper->dumpType(incompleteLocal, true, OwnerDumper, &dumperResult, errorMessage);
if (dr == CdbDumperHelper::DumpOk) {
// Hack to stop endless model recursion
const bool suppressGrandChildren = !wh->isExpandedIName(incompleteLocal.iname);
fixDumperResult(incompleteLocal, &dumperResult, suppressGrandChildren);
foreach(const WatchData &dwd, dumperResult)
wh->insertData(dwd);
} else {
const QString msg = QString::fromLatin1("Unable to further expand dumper watch data: '%1' (%2): %3/%4").arg(incompleteLocal.name, incompleteLocal.type).arg(int(dr)).arg(*errorMessage);
qWarning("%s", qPrintable(msg));
WatchData wd = incompleteLocal;
if (wd.isValueNeeded())
wd.setValue(QCoreApplication::translate("CdbStackFrameContext", "<Unknown>"));
wd.setAllUnneeded();
wh->insertData(wd);
}
......
......@@ -3192,24 +3192,36 @@ void GdbEngine::handleQueryDebuggingHelper(const GdbResultRecord &record, const
//qDebug() << m_availableSimpleDebuggingHelpers << "DATA DUMPERS AVAILABLE";
}
void GdbEngine::sendWatchParameters(const QByteArray &params0)
static inline QString arrayFillCommand(const char *array, const QByteArray &params)
{
QByteArray params = params0;
params.append('\0');
char buf[50];
sprintf(buf, "set {char[%d]} &qDumpInBuffer = {", params.size());
sprintf(buf, "set {char[%d]} &%s = {", params.size(), array);
QByteArray encoded;
encoded.append(buf);
for (int i = 0; i != params.size(); ++i) {
const int size = params.size();
for (int i = 0; i != size; ++i) {
sprintf(buf, "%d,", int(params[i]));
encoded.append(buf);
}
encoded[encoded.size() - 1] = '}';
return _(encoded);
}
void GdbEngine::sendWatchParameters(const QByteArray &params0)
{
QByteArray params = params0;
params.append('\0');
const QString inBufferCmd = arrayFillCommand("qDumpInBuffer", params);
params.replace('\0','!');
emit gdbInputAvailable(LogMisc, QString::fromUtf8(params));
postCommand(_(encoded));
params.clear();
params.append('\0');
const QString outBufferCmd = arrayFillCommand("qDumpOutBuffer", params);
postCommand(inBufferCmd);
postCommand(outBufferCmd);
}
void GdbEngine::handleVarAssign(const GdbResultRecord &, const QVariant &)
......
......@@ -209,6 +209,8 @@ QString WatchData::toString() const
QTextStream str(&res);
if (!iname.isEmpty())
str << "iname=\"" << iname << doubleQuoteComma;
if (!addr.isEmpty())
str << "addr=\"" << addr << doubleQuoteComma;
if (!exp.isEmpty())
str << "exp=\"" << exp << doubleQuoteComma;
......@@ -780,6 +782,7 @@ static int findInsertPosition(const QList<WatchItem *> &list, const WatchItem *i
void WatchModel::insertData(const WatchData &data)
{
// qDebug() << "WMI:" << data.toString();
QTC_ASSERT(!data.iname.isEmpty(), return);
WatchItem *parent = findItem(parentName(data.iname), m_root);
if (!parent) {
......
......@@ -470,16 +470,19 @@ QString cppExpressionAt(TextEditor::ITextEditor *editor, int pos,
QtDumperResult::Child::Child() :
keyEncoded(0),
valueEncoded(0),
childCount(0),
valuedisabled(false)
childCount(-1),
valuedisabled(false),
valueEncountered(false)
{
}
QtDumperResult::QtDumperResult() :
valueEncountered(false),
valueEncoded(0),
valuedisabled(false),
childCount(0),
internal(false)
childCount(-1),
internal(false),
childChildCount(-1)
{
}
......@@ -488,15 +491,17 @@ void QtDumperResult::clear()
iname.clear();
value.clear();
address.clear();
addressInfo.clear();
type.clear();
extra.clear();
displayedType.clear();
valueEncoded = 0;
valuedisabled = false;
childCount = 0;
valueEncountered = valuedisabled = false;
childCount = -1;
internal = false;
childType.clear();
children.clear();
childChildCount = -1;
}
QList<WatchData> QtDumperResult::toWatchData(int source) const
......@@ -508,45 +513,70 @@ QList<WatchData> QtDumperResult::toWatchData(int source) const
const QChar dot = QLatin1Char('.');
const int lastDotIndex = root.iname.lastIndexOf(dot);
root.exp = root.name = lastDotIndex == -1 ? iname : iname.mid(lastDotIndex + 1);
root.setValue(decodeData(value, valueEncoded));
if (valueEncountered) {
root.setValue(decodeData(value, valueEncoded));
root.valuedisabled = valuedisabled;
}
root.setType(displayedType.isEmpty() ? type : displayedType);
root.valuedisabled = valuedisabled;
root.setAddress(address);
root.source = source;
root.setHasChildren(childCount > 0);
// Children
if (childCount > 0) {
if (children.size() == childCount) {
for (int c = 0; c < childCount; c++) {
const Child &dchild = children.at(c);
rc.push_back(WatchData());
WatchData &wchild = rc.back();
wchild.source = source;
wchild.iname = iname;
wchild.iname += dot;
wchild.iname += dchild.name;
// Use key entry as name (which is used for map nodes)
if (dchild.key.isEmpty()) {
wchild.name = dchild.name;
} else {
wchild.name = decodeData(dchild.key, dchild.keyEncoded);
if (wchild.name.size() > 13) {
wchild.name.truncate(12);
wchild.name += QLatin1String("...");
}
if (childCount >= 0)
root.setHasChildren(childCount > 0);
// Children. Sanity check after parsing sets childcount to list size
// if list is not empty
if (children.empty()) {
if (childCount > 0)
root.setChildrenNeeded();
} else {
root.setChildrenUnneeded();
for (int c = 0; c < childCount; c++) {
const Child &dchild = children.at(c);
rc.push_back(WatchData());
WatchData &wchild = rc.back();
wchild.source = source;
wchild.iname = iname;
wchild.iname += dot;
wchild.iname += dchild.name;
// Use key entry as name (which is used for map nodes)
if (dchild.key.isEmpty()) {
wchild.name = dchild.name;
} else {
wchild.name = decodeData(dchild.key, dchild.keyEncoded);
if (wchild.name.size() > 13) {
wchild.name.truncate(12);
wchild.name += QLatin1String("...");
}
wchild.exp = dchild.exp;
}
wchild.exp = dchild.exp;
if (dchild.valueEncountered) {
wchild.valuedisabled = dchild.valuedisabled;
wchild.setType(dchild.type.isEmpty() ? childType : dchild.type);
wchild.setAddress(dchild.address);
wchild.setValue(decodeData(dchild.value, dchild.valueEncoded));
}
wchild.setType(dchild.type.isEmpty() ? childType : dchild.type);
wchild.setAddress(dchild.address);
// Child overrides.
const int effectiveChildChildCount = dchild.childCount == -1 ? childChildCount : dchild.childCount;
switch (effectiveChildChildCount) {
case -1:
wchild.setChildrenNeeded();
wchild.setHasChildrenNeeded();
break;
case 0:
wchild.setHasChildren(false);
break;
default:
wchild.setHasChildren(true);
break;
}
root.setChildrenUnneeded();
} else {
root.setChildrenNeeded();
}
}
if (debug) {
QDebug nospace = qDebug().nospace();
nospace << "QtDumperResult::toWatchData" << *this << '\n';
foreach(const WatchData &wd, rc)
nospace << " " << wd.toString() << '\n';
}
return rc;
}
......@@ -554,11 +584,20 @@ QDebug operator<<(QDebug in, const QtDumperResult &d)
{
QDebug nospace = in.nospace();
nospace << " iname=" << d.iname << " type=" << d.type << " displayed=" << d.displayedType
<< " address=" << d.address
<< " value=" << d.value
<< " disabled=" << d.valuedisabled
<< " encoded=" << d.valueEncoded << " internal=" << d.internal
<< " address=" << d.address;
if (!d.addressInfo.isEmpty())
nospace << " addressInfo=" << d.addressInfo;
if (d.valueEncountered) {
nospace << " encoded=" << d.valueEncoded
<< " value=" << d.value
<< " disabled=" << d.valuedisabled;
} else {
nospace << " <no value>";
}
nospace << " childnumchild=" << d.childChildCount
<< " internal=" << d.internal
<< " extra='" << d.extra << "'\n";
const int realChildCount = d.children.size();
if (d.childCount || realChildCount) {
nospace << "childCount=" << d.childCount << '/' << realChildCount
......@@ -571,8 +610,12 @@ QDebug operator<<(QDebug in, const QtDumperResult &d)
<< " name=" << c.name;
if (!c.key.isEmpty())
nospace << " keyencoded=" << c.keyEncoded << " key=" << c.key;
nospace << " valueencoded=" << c.valueEncoded << " value=" << c.value
<< "childcount=" << c.childCount << '\n';
if (c.valueEncountered) {
nospace << " valueencoded=" << c.valueEncoded << " value=" << c.value;
} else {
nospace << " <no value>";
}
nospace << "childcount=" << c.childCount << '\n';
}
}
return in;
......@@ -608,6 +651,7 @@ void QtDumperHelper::clear()
m_sizeCache.clear();
qFill(m_specialSizes, m_specialSizes + SpecialSizeCount, 0);
m_expressionCache.clear();
m_dumperVersion.clear();
}
static inline void formatQtVersion(int v, QTextStream &str)
......@@ -622,7 +666,7 @@ QString QtDumperHelper::toString(bool debug) const
QTextStream str(&rc);
str << "version=";
formatQtVersion(m_qtVersion, str);
str << " namespace='" << m_qtNamespace << "'," << m_nameTypeMap.size() << " known types <type enum>: ";
str << "dumperversion='" << m_dumperVersion << "' namespace='" << m_qtNamespace << "'," << m_nameTypeMap.size() << " known types <type enum>: ";
const NameTypeMap::const_iterator cend = m_nameTypeMap.constEnd();
for (NameTypeMap::const_iterator it = m_nameTypeMap.constBegin(); it != cend; ++it) {
str <<",[" << it.key() << ',' << it.value() << ']';
......@@ -639,9 +683,9 @@ QString QtDumperHelper::toString(bool debug) const
}
const QString nameSpace = m_qtNamespace.isEmpty() ? QCoreApplication::translate("QtDumperHelper", "<none>") : m_qtNamespace;
return QCoreApplication::translate("QtDumperHelper",
"%n known types, Qt version: %1, Qt namespace: %2",
"%n known types, Qt version: %1, Qt namespace: %2 Dumper version: %3",
0, QCoreApplication::CodecForTr,
m_nameTypeMap.size()).arg(qtVersionString(), nameSpace);
m_nameTypeMap.size()).arg(qtVersionString(), nameSpace, m_dumperVersion);