Debugger: Use the code model to detect unitialized variables.

This should save debugger round trips and crashes in the debugging
helpers.
Add respective option to debugging helper option page, defaulting to
true.On this occasion, make CDB detect shadowed variables correctly
and display them as "<shadowed n>" as does the Gdb engine by
reversing the direction in which
CdbSymbolGroupContext::populateINameIndexMap works.
Rubber-stamped-by: hjk <qtc-committer@nokia.com>
This commit is contained in:
Friedemann Kleint
2009-10-16 16:26:28 +02:00
parent c79476e72f
commit 25ee70bb24
22 changed files with 557 additions and 164 deletions

View File

@@ -341,6 +341,8 @@ void CdbDumperInitThread ::run()
CdbDumperHelper::CdbDumperHelper(DebuggerManager *manager, CdbDumperHelper::CdbDumperHelper(DebuggerManager *manager,
CdbComInterfaces *cif) : CdbComInterfaces *cif) :
m_tryInjectLoad(true), m_tryInjectLoad(true),
m_msgDisabled(QLatin1String("Dumpers are disabled")),
m_msgNotInScope(QLatin1String("Data not in scope")),
m_state(NotLoaded), m_state(NotLoaded),
m_manager(manager), m_manager(manager),
m_cif(cif), m_cif(cif),
@@ -649,7 +651,11 @@ CdbDumperHelper::DumpResult CdbDumperHelper::dumpTypeI(const WatchData &wd, bool
errorMessage->clear(); errorMessage->clear();
// Check failure cache and supported types // Check failure cache and supported types
if (m_state == Disabled) { if (m_state == Disabled) {
*errorMessage = QLatin1String("Dumpers are disabled"); *errorMessage =m_msgDisabled;
return DumpNotHandled;
}
if (wd.error) {
*errorMessage =m_msgNotInScope;
return DumpNotHandled; return DumpNotHandled;
} }
if (m_failedTypes.contains(wd.type)) { if (m_failedTypes.contains(wd.type)) {

View File

@@ -134,6 +134,8 @@ private:
static bool writeToDebuggee(CIDebugDataSpaces *ds, const QByteArray &buffer, quint64 address, QString *errorMessage); static bool writeToDebuggee(CIDebugDataSpaces *ds, const QByteArray &buffer, quint64 address, QString *errorMessage);
const bool m_tryInjectLoad; const bool m_tryInjectLoad;
const QString m_msgDisabled;
const QString m_msgNotInScope;
State m_state; State m_state;
DebuggerManager *m_manager; DebuggerManager *m_manager;
CdbComInterfaces *m_cif; CdbComInterfaces *m_cif;

View File

@@ -217,7 +217,7 @@ bool WatchHandleDumperInserter::expandPointerToDumpable(const WatchData &wd, QSt
bool handled = false; bool handled = false;
do { do {
if (!isPointerType(wd.type)) if (wd.error || !isPointerType(wd.type))
break; break;
const int classPos = wd.value.indexOf(" class "); const int classPos = wd.value.indexOf(" class ");
if (classPos == -1) if (classPos == -1)
@@ -396,9 +396,9 @@ bool CdbStackFrameContext::editorToolTip(const QString &iname,
*errorMessage = QString::fromLatin1("%1 not found.").arg(iname); *errorMessage = QString::fromLatin1("%1 not found.").arg(iname);
return false; return false;
} }
const WatchData wd = m_symbolContext->symbolAt(index);
// Check dumpers. Should actually be just one item. // Check dumpers. Should actually be just one item.
if (m_useDumpers && m_dumper->state() != CdbDumperHelper::Disabled) { const WatchData wd = m_symbolContext->watchDataAt(index);
if (m_useDumpers && !wd.error && m_dumper->state() != CdbDumperHelper::Disabled) {
QList<WatchData> result; QList<WatchData> result;
if (CdbDumperHelper::DumpOk == m_dumper->dumpType(wd, false, &result, errorMessage)) { if (CdbDumperHelper::DumpOk == m_dumper->dumpType(wd, false, &result, errorMessage)) {
foreach (const WatchData &dwd, result) { foreach (const WatchData &dwd, result) {

View File

@@ -33,8 +33,11 @@
#include "cdbsymbolgroupcontext.h" #include "cdbsymbolgroupcontext.h"
#include "cdbdebugengine_p.h" #include "cdbdebugengine_p.h"
#include "cdbdumperhelper.h" #include "cdbdumperhelper.h"
#include "debuggeractions.h"
#include "debuggermanager.h"
#include <QtCore/QDir> #include <QtCore/QDir>
#include <QtCore/QDebug>
#include <QtCore/QTextStream> #include <QtCore/QTextStream>
namespace Debugger { namespace Debugger {
@@ -160,7 +163,13 @@ CdbStackFrameContext *CdbStackTraceContext::frameContextAt(int index, QString *e
*errorMessage = msgFrameContextFailed(index, m_frames.at(index), *errorMessage); *errorMessage = msgFrameContextFailed(index, m_frames.at(index), *errorMessage);
return 0; return 0;
} }
CdbSymbolGroupContext *sc = CdbSymbolGroupContext::create(QLatin1String("local"), sg, errorMessage); // Exclude unitialized variables if desired
QStringList uninitializedVariables;
if (theDebuggerAction(UseCodeModel)->isChecked()) {
const StackFrame &frame = m_frames.at(index);
getUninitializedVariables(DebuggerManager::instance()->cppCodeModelSnapshot(), frame.function, frame.file, frame.line, &uninitializedVariables);
}
CdbSymbolGroupContext *sc = CdbSymbolGroupContext::create(QLatin1String("local"), sg, uninitializedVariables, errorMessage);
if (!sc) { if (!sc) {
*errorMessage = msgFrameContextFailed(index, m_frames.at(index), *errorMessage); *errorMessage = msgFrameContextFailed(index, m_frames.at(index), *errorMessage);
return 0; return 0;

View File

@@ -39,6 +39,9 @@
enum { debug = 0 }; enum { debug = 0 };
enum { debugInternalDumpers = 0 }; enum { debugInternalDumpers = 0 };
// name separator for shadowed variables
static const char iNameShadowDelimiter = '#';
static inline QString msgSymbolNotFound(const QString &s) static inline QString msgSymbolNotFound(const QString &s)
{ {
return QString::fromLatin1("The symbol '%1' could not be found.").arg(s); return QString::fromLatin1("The symbol '%1' could not be found.").arg(s);
@@ -83,6 +86,14 @@ QTextStream &operator<<(QTextStream &str, const DEBUG_SYMBOL_PARAMETERS &p)
return str; return str;
} }
static inline QString hexSymbolOffset(CIDebugSymbolGroup *sg, unsigned long index)
{
ULONG64 rc = 0;
if (FAILED(sg->GetSymbolOffset(index, &rc)))
rc = 0;
return QLatin1String("0x") + QString::number(rc, 16);
}
// A helper function to extract a string value from a member function of // A helper function to extract a string value from a member function of
// IDebugSymbolGroup2 taking the symbol index and a character buffer. // IDebugSymbolGroup2 taking the symbol index and a character buffer.
// Pass in the the member function as '&IDebugSymbolGroup2::GetSymbolNameWide' // Pass in the the member function as '&IDebugSymbolGroup2::GetSymbolNameWide'
@@ -129,12 +140,15 @@ static inline CdbSymbolGroupContext::SymbolState getSymbolState(const DEBUG_SYMB
} }
CdbSymbolGroupContext::CdbSymbolGroupContext(const QString &prefix, CdbSymbolGroupContext::CdbSymbolGroupContext(const QString &prefix,
CIDebugSymbolGroup *symbolGroup) : CIDebugSymbolGroup *symbolGroup,
const QStringList &uninitializedVariables) :
m_prefix(prefix), m_prefix(prefix),
m_nameDelimiter(QLatin1Char('.')), m_nameDelimiter(QLatin1Char('.')),
m_uninitializedVariables(uninitializedVariables.toSet()),
m_symbolGroup(symbolGroup), m_symbolGroup(symbolGroup),
m_unnamedSymbolNumber(1) m_unnamedSymbolNumber(1)
{ {
} }
CdbSymbolGroupContext::~CdbSymbolGroupContext() CdbSymbolGroupContext::~CdbSymbolGroupContext()
@@ -144,9 +158,10 @@ CdbSymbolGroupContext::~CdbSymbolGroupContext()
CdbSymbolGroupContext *CdbSymbolGroupContext::create(const QString &prefix, CdbSymbolGroupContext *CdbSymbolGroupContext::create(const QString &prefix,
CIDebugSymbolGroup *symbolGroup, CIDebugSymbolGroup *symbolGroup,
const QStringList &uninitializedVariables,
QString *errorMessage) QString *errorMessage)
{ {
CdbSymbolGroupContext *rc = new CdbSymbolGroupContext(prefix, symbolGroup); CdbSymbolGroupContext *rc = new CdbSymbolGroupContext(prefix, symbolGroup, uninitializedVariables);
if (!rc->init(errorMessage)) { if (!rc->init(errorMessage)) {
delete rc; delete rc;
return 0; return 0;
@@ -173,28 +188,36 @@ bool CdbSymbolGroupContext::init(QString *errorMessage)
*errorMessage = QString::fromLatin1("In %1: %2 (%3 symbols)").arg(QLatin1String(Q_FUNC_INFO), msgComFailed("GetSymbolParameters", hr)).arg(count); *errorMessage = QString::fromLatin1("In %1: %2 (%3 symbols)").arg(QLatin1String(Q_FUNC_INFO), msgComFailed("GetSymbolParameters", hr)).arg(count);
return false; return false;
} }
populateINameIndexMap(m_prefix, DEBUG_ANY_ID, 0, count); populateINameIndexMap(m_prefix, DEBUG_ANY_ID, count);
} }
if (debug) if (debug)
qDebug() << Q_FUNC_INFO << '\n'<< toString(); qDebug() << Q_FUNC_INFO << '\n'<< toString(true);
return true; return true;
} }
/* Make the entries for iname->index mapping. We might encounter
* already expanded subitems when doing it for top-level ('this'-pointers),
* recurse in that case, (skip over expanded children).
* Loop backwards to detect shadowed variables in the order the
/* debugger expects them:
\code
int x; // Occurrence (1), should be reported as "x <shadowed 1>"
if (true) {
int x = 5; (2) // Occurrence (2), should be reported as "x"
}
\endcode
* The order in the symbol group is (1),(2). Give them an iname of
* <root>#<shadowed-nr>, which will be split apart for display. */
void CdbSymbolGroupContext::populateINameIndexMap(const QString &prefix, unsigned long parentId, void CdbSymbolGroupContext::populateINameIndexMap(const QString &prefix, unsigned long parentId,
unsigned long start, unsigned long count) unsigned long end)
{ {
// Make the entries for iname->index mapping. We might encounter
// already expanded subitems when doing it for top-level, recurse in that case.
const QString symbolPrefix = prefix + m_nameDelimiter; const QString symbolPrefix = prefix + m_nameDelimiter;
if (debug) if (debug)
qDebug() << Q_FUNC_INFO << '\n'<< symbolPrefix << start << count; qDebug() << Q_FUNC_INFO << '\n'<< symbolPrefix << parentId << end;
const unsigned long end = m_symbolParameters.size(); for (unsigned long i = end - 1; ; i--) {
unsigned long seenChildren = 0;
// Skip over expanded children
for (unsigned long i = start; i < end && seenChildren < count; i++) {
const DEBUG_SYMBOL_PARAMETERS &p = m_symbolParameters.at(i); const DEBUG_SYMBOL_PARAMETERS &p = m_symbolParameters.at(i);
if (parentId == p.ParentSymbol) { if (parentId == p.ParentSymbol) {
seenChildren++;
// "__formal" occurs when someone writes "void foo(int /* x */)..." // "__formal" occurs when someone writes "void foo(int /* x */)..."
static const QString unnamedFormalParameter = QLatin1String("__formal"); static const QString unnamedFormalParameter = QLatin1String("__formal");
QString symbolName = getSymbolString(m_symbolGroup, &IDebugSymbolGroup2::GetSymbolNameWide, i); QString symbolName = getSymbolString(m_symbolGroup, &IDebugSymbolGroup2::GetSymbolNameWide, i);
@@ -203,11 +226,21 @@ void CdbSymbolGroupContext::populateINameIndexMap(const QString &prefix, unsigne
symbolName += QString::number(m_unnamedSymbolNumber++); symbolName += QString::number(m_unnamedSymbolNumber++);
symbolName += QLatin1Char('>'); symbolName += QLatin1Char('>');
} }
const QString name = symbolPrefix + symbolName; // Find a unique name in case the variable is shadowed by
// an existing one
const QString namePrefix = symbolPrefix + symbolName;
QString name = namePrefix;
for (int n = 1; m_inameIndexMap.contains(name); n++) {
name.truncate(namePrefix.size());
name += QLatin1Char(iNameShadowDelimiter);
name += QString::number(n);
}
m_inameIndexMap.insert(name, i); m_inameIndexMap.insert(name, i);
if (getSymbolState(p) == ExpandedSymbol) if (getSymbolState(p) == ExpandedSymbol)
populateINameIndexMap(name, i, i + 1, p.SubElements); populateINameIndexMap(name, i, i + 1 + p.SubElements);
} }
if (i == 0 || i == parentId)
break;
} }
} }
@@ -223,7 +256,10 @@ QString CdbSymbolGroupContext::toString(bool verbose) const
str << " "; str << " ";
str << getSymbolString(m_symbolGroup, &IDebugSymbolGroup2::GetSymbolNameWide, i); str << getSymbolString(m_symbolGroup, &IDebugSymbolGroup2::GetSymbolNameWide, i);
if (p.Flags & DEBUG_SYMBOL_IS_LOCAL) if (p.Flags & DEBUG_SYMBOL_IS_LOCAL)
str << " '" << getSymbolString(m_symbolGroup, &IDebugSymbolGroup2::GetSymbolTypeNameWide, i); str << " '" << getSymbolString(m_symbolGroup, &IDebugSymbolGroup2::GetSymbolTypeNameWide, i) << '\'';
str << " Address: " << hexSymbolOffset(m_symbolGroup, i);
if (verbose)
str << " '" << getSymbolString(m_symbolGroup, &IDebugSymbolGroup2::GetSymbolValueTextWide, i) << '\'';
str << p << '\n'; str << p << '\n';
} }
if (verbose) { if (verbose) {
@@ -348,7 +384,7 @@ bool CdbSymbolGroupContext::expandSymbol(const QString &prefix, unsigned long in
if (it.value() > index) if (it.value() > index)
it.value() += newSymbolCount; it.value() += newSymbolCount;
// insert the new symbols // insert the new symbols
populateINameIndexMap(prefix, index, index + 1, newSymbolCount); populateINameIndexMap(prefix, index, index + 1 + newSymbolCount);
if (debug > 1) if (debug > 1)
qDebug() << '<' << Q_FUNC_INFO << '\n' << prefix << index << '\n' << toString(); qDebug() << '<' << Q_FUNC_INFO << '\n' << prefix << index << '\n' << toString();
return true; return true;
@@ -365,14 +401,6 @@ QString CdbSymbolGroupContext::symbolINameAt(unsigned long index) const
return m_inameIndexMap.key(index); return m_inameIndexMap.key(index);
} }
static inline QString hexSymbolOffset(CIDebugSymbolGroup *sg, unsigned long index)
{
ULONG64 rc = 0;
if (FAILED(sg->GetSymbolOffset(index, &rc)))
rc = 0;
return QLatin1String("0x") + QString::number(rc, 16);
}
// check for "0x000", "0x000 class X" // check for "0x000", "0x000 class X"
static inline bool isNullPointer(const WatchData &wd) static inline bool isNullPointer(const WatchData &wd)
{ {
@@ -409,19 +437,35 @@ static inline QString fixValue(const QString &value)
return removeInnerTemplateType(value); return removeInnerTemplateType(value);
} }
WatchData CdbSymbolGroupContext::symbolAt(unsigned long index) const WatchData CdbSymbolGroupContext::watchDataAt(unsigned long index) const
{ {
WatchData wd; WatchData wd;
wd.iname = symbolINameAt(index); wd.iname = symbolINameAt(index);
wd.exp = wd.iname; wd.exp = wd.iname;
// Determine name from iname and format shadowed variables correctly
// as "<shadowed X>, see populateINameIndexMap().
const int lastDelimiterPos = wd.iname.lastIndexOf(m_nameDelimiter); const int lastDelimiterPos = wd.iname.lastIndexOf(m_nameDelimiter);
QString name = lastDelimiterPos == -1 ? wd.iname : wd.iname.mid(lastDelimiterPos + 1);
int shadowedNumber = 0;
const int shadowedPos = name.lastIndexOf(QLatin1Char(iNameShadowDelimiter));
if (shadowedPos != -1) {
shadowedNumber = name.mid(shadowedPos + 1).toInt();
name.truncate(shadowedPos);
}
// For class hierarchies, we get sometimes complicated std::template types here. // For class hierarchies, we get sometimes complicated std::template types here.
// Remove them for display // (std::map extends std::tree<>... Remove them for display only.
wd.name = removeInnerTemplateType(lastDelimiterPos == -1 ? wd.iname : wd.iname.mid(lastDelimiterPos + 1)); const QString fullShadowedName = WatchData::shadowedName(name, shadowedNumber);
wd.name = WatchData::shadowedName(removeInnerTemplateType(name), shadowedNumber);
wd.addr = hexSymbolOffset(m_symbolGroup, index); wd.addr = hexSymbolOffset(m_symbolGroup, index);
const QString type = getSymbolString(m_symbolGroup, &IDebugSymbolGroup2::GetSymbolTypeNameWide, index); const QString type = getSymbolString(m_symbolGroup, &IDebugSymbolGroup2::GetSymbolTypeNameWide, index);
const QString value = getSymbolString(m_symbolGroup, &IDebugSymbolGroup2::GetSymbolValueTextWide, index);
wd.setType(type); wd.setType(type);
// Check for unitialized variables at level 0 only.
const DEBUG_SYMBOL_PARAMETERS &p = m_symbolParameters.at(index);
if (p.ParentSymbol == DEBUG_ANY_ID && m_uninitializedVariables.contains(fullShadowedName)) {
wd.setError(WatchData::msgNotInScope());
return wd;
}
const QString value = getSymbolString(m_symbolGroup, &IDebugSymbolGroup2::GetSymbolValueTextWide, index);
wd.setValue(fixValue(value)); wd.setValue(fixValue(value));
wd.setChildrenNeeded(); // compensate side effects of above setters wd.setChildrenNeeded(); // compensate side effects of above setters
// Figure out children. The SubElement is only a guess unless the symbol, // Figure out children. The SubElement is only a guess unless the symbol,
@@ -429,7 +473,7 @@ WatchData CdbSymbolGroupContext::symbolAt(unsigned long index) const
// If the symbol has children (expanded or not), we leave the 'Children' flag // If the symbol has children (expanded or not), we leave the 'Children' flag
// in 'needed' state. Suppress 0-pointers right ("0x000 class X") // in 'needed' state. Suppress 0-pointers right ("0x000 class X")
// here as they only lead to children with memory access errors. // here as they only lead to children with memory access errors.
const bool hasChildren = m_symbolParameters.at(index).SubElements && !isNullPointer(wd); const bool hasChildren = p.SubElements && !isNullPointer(wd);
wd.setHasChildren(hasChildren); wd.setHasChildren(hasChildren);
if (debug > 1) if (debug > 1)
qDebug() << Q_FUNC_INFO << index << '\n' << wd.toString(); qDebug() << Q_FUNC_INFO << index << '\n' << wd.toString();
@@ -438,7 +482,7 @@ WatchData CdbSymbolGroupContext::symbolAt(unsigned long index) const
WatchData CdbSymbolGroupContext::dumpSymbolAt(CIDebugDataSpaces *ds, unsigned long index) WatchData CdbSymbolGroupContext::dumpSymbolAt(CIDebugDataSpaces *ds, unsigned long index)
{ {
WatchData rc = symbolAt(index); WatchData rc = watchDataAt(index);
dump(ds, &rc); dump(ds, &rc);
return rc; return rc;
} }

View File

@@ -69,12 +69,14 @@ class CdbSymbolGroupContext
{ {
Q_DISABLE_COPY(CdbSymbolGroupContext); Q_DISABLE_COPY(CdbSymbolGroupContext);
explicit CdbSymbolGroupContext(const QString &prefix, explicit CdbSymbolGroupContext(const QString &prefix,
CIDebugSymbolGroup *symbolGroup); CIDebugSymbolGroup *symbolGroup,
const QStringList &uninitializedVariables = QStringList());
public: public:
~CdbSymbolGroupContext(); ~CdbSymbolGroupContext();
static CdbSymbolGroupContext *create(const QString &prefix, static CdbSymbolGroupContext *create(const QString &prefix,
CIDebugSymbolGroup *symbolGroup, CIDebugSymbolGroup *symbolGroup,
const QStringList &uninitializedVariables,
QString *errorMessage); QString *errorMessage);
QString prefix() const { return m_prefix; } QString prefix() const { return m_prefix; }
@@ -118,7 +120,7 @@ public:
int dumpedOwner, int dumpedOwner,
OutputIterator it, QString *errorMessage); OutputIterator it, QString *errorMessage);
WatchData symbolAt(unsigned long index) const; WatchData watchDataAt(unsigned long index) const;
// Run the internal dumpers on the symbol // Run the internal dumpers on the symbol
WatchData dumpSymbolAt(CIDebugDataSpaces *ds, unsigned long index); WatchData dumpSymbolAt(CIDebugDataSpaces *ds, unsigned long index);
@@ -155,7 +157,7 @@ private:
unsigned long *parentId, unsigned long *parentId,
QString *errorMessage); QString *errorMessage);
bool expandSymbol(const QString &prefix, unsigned long index, QString *errorMessage); bool expandSymbol(const QString &prefix, unsigned long index, QString *errorMessage);
void populateINameIndexMap(const QString &prefix, unsigned long parentId, unsigned long start, unsigned long count); void populateINameIndexMap(const QString &prefix, unsigned long parentId, unsigned long end);
QString symbolINameAt(unsigned long index) const; QString symbolINameAt(unsigned long index) const;
int dumpQString(CIDebugDataSpaces *ds, WatchData *wd); int dumpQString(CIDebugDataSpaces *ds, WatchData *wd);
@@ -166,6 +168,7 @@ private:
const QString m_prefix; const QString m_prefix;
const QChar m_nameDelimiter; const QChar m_nameDelimiter;
const QSet<QString> m_uninitializedVariables;
CIDebugSymbolGroup *m_symbolGroup; CIDebugSymbolGroup *m_symbolGroup;
NameIndexMap m_inameIndexMap; NameIndexMap m_inameIndexMap;

View File

@@ -61,7 +61,7 @@ bool CdbSymbolGroupContext::getDumpChildSymbols(CIDebugDataSpaces *ds, const QSt
for (int s = start; s < m_symbolParameters.size(); ++s) { for (int s = start; s < m_symbolParameters.size(); ++s) {
const DEBUG_SYMBOL_PARAMETERS &p = m_symbolParameters.at(s); const DEBUG_SYMBOL_PARAMETERS &p = m_symbolParameters.at(s);
if (p.ParentSymbol == parentId && isSymbolDisplayable(p)) { if (p.ParentSymbol == parentId && isSymbolDisplayable(p)) {
WatchData wd = symbolAt(s); WatchData wd = watchDataAt(s);
// Run internal dumper, mark ownership // Run internal dumper, mark ownership
if (ds) { if (ds) {
switch (dump(ds, &wd)) { switch (dump(ds, &wd)) {

View File

@@ -229,6 +229,13 @@ DebuggerSettings *DebuggerSettings::instance()
item->setValue(false); item->setValue(false);
instance->insertItem(DebugDebuggingHelpers, item); instance->insertItem(DebugDebuggingHelpers, item);
item = new SavedAction(instance);
item->setSettingsKey(debugModeGroup, QLatin1String("UseCodeModel"));
item->setText(tr("Use code model"));
item->setCheckable(true);
item->setDefaultValue(false);
item->setValue(false);
instance->insertItem(UseCodeModel, item);
item = new SavedAction(instance); item = new SavedAction(instance);
item->setText(tr("Recheck debugging helper availability")); item->setText(tr("Recheck debugging helper availability"));

View File

@@ -86,6 +86,8 @@ enum DebuggerActionCode
CustomDebuggingHelperLocation, CustomDebuggingHelperLocation,
DebugDebuggingHelpers, DebugDebuggingHelpers,
UseCodeModel,
UseToolTipsInMainEditor, UseToolTipsInMainEditor,
UseToolTipsInLocalsView, UseToolTipsInLocalsView,
UseToolTipsInBreakpointsView, UseToolTipsInBreakpointsView,

View File

@@ -61,6 +61,8 @@
#include <utils/qtcassert.h> #include <utils/qtcassert.h>
#include <utils/fancymainwindow.h> #include <utils/fancymainwindow.h>
#include <projectexplorer/toolchain.h> #include <projectexplorer/toolchain.h>
#include <cplusplus/CppDocument.h>
#include <cpptools/cppmodelmanagerinterface.h>
#include <QtCore/QDebug> #include <QtCore/QDebug>
#include <QtCore/QDir> #include <QtCore/QDir>
@@ -303,6 +305,8 @@ struct DebuggerManagerPrivate
IDebuggerEngine *m_engine; IDebuggerEngine *m_engine;
DebuggerState m_state; DebuggerState m_state;
CPlusPlus::Snapshot m_codeModelSnapshot;
}; };
DebuggerManager *DebuggerManagerPrivate::instance = 0; DebuggerManager *DebuggerManagerPrivate::instance = 0;
@@ -623,6 +627,18 @@ WatchHandler *DebuggerManager::watchHandler() const
return d->m_watchHandler; return d->m_watchHandler;
} }
const CPlusPlus::Snapshot &DebuggerManager::cppCodeModelSnapshot() const
{
if (d->m_codeModelSnapshot.isEmpty() && theDebuggerAction(UseCodeModel)->isChecked())
d->m_codeModelSnapshot = CppTools::CppModelManagerInterface::instance()->snapshot();
return d->m_codeModelSnapshot;
}
void DebuggerManager::clearCppCodeModelSnapshot()
{
d->m_codeModelSnapshot.clear();
}
SourceFilesWindow *DebuggerManager::sourceFileWindow() const SourceFilesWindow *DebuggerManager::sourceFileWindow() const
{ {
return d->m_sourceFilesWindow; return d->m_sourceFilesWindow;
@@ -1026,6 +1042,7 @@ void DebuggerManager::exitDebugger()
// in turn will handle the cleanup. // in turn will handle the cleanup.
if (d->m_engine && state() != DebuggerNotReady) if (d->m_engine && state() != DebuggerNotReady)
d->m_engine->exitDebugger(); d->m_engine->exitDebugger();
d->m_codeModelSnapshot.clear();
} }
DebuggerStartParametersPtr DebuggerManager::startParameters() const DebuggerStartParametersPtr DebuggerManager::startParameters() const

View File

@@ -59,6 +59,10 @@ namespace TextEditor {
class ITextEditor; class ITextEditor;
} }
namespace CPlusPlus {
class Snapshot;
}
namespace Debugger { namespace Debugger {
namespace Internal { namespace Internal {
@@ -180,6 +184,8 @@ public:
QString *settingsCategory = 0, QString *settingsCategory = 0,
QString *settingsPage = 0) const; QString *settingsPage = 0) const;
const CPlusPlus::Snapshot &cppCodeModelSnapshot() const;
static DebuggerManager *instance(); static DebuggerManager *instance();
public slots: public slots:
@@ -232,6 +238,7 @@ public slots:
void setRegisterValue(int nr, const QString &value); void setRegisterValue(int nr, const QString &value);
void showStatusMessage(const QString &msg, int timeout = -1); // -1 forever void showStatusMessage(const QString &msg, int timeout = -1); // -1 forever
void clearCppCodeModelSnapshot();
public slots: // FIXME public slots: // FIXME
void showDebuggerOutput(const QString &msg) void showDebuggerOutput(const QString &msg)
@@ -268,6 +275,7 @@ private:
Internal::WatchHandler *watchHandler() const; Internal::WatchHandler *watchHandler() const;
Internal::SourceFilesWindow *sourceFileWindow() const; Internal::SourceFilesWindow *sourceFileWindow() const;
QWidget *threadsWindow() const; QWidget *threadsWindow() const;
Internal::DebuggerManagerActions debuggerManagerActions() const; Internal::DebuggerManagerActions debuggerManagerActions() const;
void notifyInferiorStopped(); void notifyInferiorStopped();

View File

@@ -373,6 +373,9 @@ QWidget *DebuggingHelperOptionPage::createPage(QWidget *parent)
m_group.insert(theDebuggerAction(CustomDebuggingHelperLocation), m_group.insert(theDebuggerAction(CustomDebuggingHelperLocation),
m_ui.dumperLocationChooser); m_ui.dumperLocationChooser);
m_group.insert(theDebuggerAction(UseCodeModel),
m_ui.checkBoxUseCodeModel);
#ifdef QT_DEBUG #ifdef QT_DEBUG
m_group.insert(theDebuggerAction(DebugDebuggingHelpers), m_group.insert(theDebuggerAction(DebugDebuggingHelpers),
m_ui.checkBoxDebugDebuggingHelpers); m_ui.checkBoxDebugDebuggingHelpers);

View File

@@ -6,7 +6,7 @@
<rect> <rect>
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>403</width> <width>432</width>
<height>434</height> <height>434</height>
</rect> </rect>
</property> </property>
@@ -83,10 +83,20 @@
</widget> </widget>
</item> </item>
<item> <item>
<widget class="Utils::PathChooser" name="dumperLocationChooser" native="true"/> <widget class="Utils::PathChooser" name="dumperLocationChooser"/>
</item> </item>
</layout> </layout>
</item> </item>
<item>
<widget class="QCheckBox" name="checkBoxUseCodeModel">
<property name="toolTip">
<string>Makes use of Qt Creator's code model to find out if a variable has already been assigned a value at the point the debugger interrupts.</string>
</property>
<property name="text">
<string>Use code model</string>
</property>
</widget>
</item>
<item> <item>
<widget class="QCheckBox" name="checkBoxDebugDebuggingHelpers"> <widget class="QCheckBox" name="checkBoxDebugDebuggingHelpers">
<property name="toolTip"> <property name="toolTip">

View File

@@ -89,10 +89,6 @@
#endif #endif
#include <ctype.h> #include <ctype.h>
// FIXME: temporary hack to evalaute tbreak based step-over behaviour
static QString lastFile;
static int lastLine;
namespace Debugger { namespace Debugger {
namespace Internal { namespace Internal {
@@ -1233,9 +1229,9 @@ void GdbEngine::handleStop2(const GdbResponse &response)
void GdbEngine::handleStop2(const GdbMi &data) void GdbEngine::handleStop2(const GdbMi &data)
{ {
// Sometimes we get some interesting extra information. Grab it. // Sometimes we get some interesting extra information. Grab it.
GdbMi frame = data.findChild("frame"); const GdbMi gdbmiFrame = data.findChild("frame");
GdbMi shortName = frame.findChild("file"); GdbMi shortName = gdbmiFrame.findChild("file");
GdbMi fullName = frame.findChild("fullname"); GdbMi fullName = gdbmiFrame.findChild("fullname");
if (shortName.isValid() && fullName.isValid()) { if (shortName.isValid() && fullName.isValid()) {
QString file = QFile::decodeName(shortName.data()); QString file = QFile::decodeName(shortName.data());
QString full = QFile::decodeName(fullName.data()); QString full = QFile::decodeName(fullName.data());
@@ -1246,16 +1242,17 @@ void GdbEngine::handleStop2(const GdbMi &data)
} }
// Quick shot: Jump to stack frame #0. // Quick shot: Jump to stack frame #0.
if (frame.isValid()) { StackFrame frame;
const StackFrame f = parseStackFrame(frame, 0); if (gdbmiFrame.isValid()) {
gotoLocation(f, true); frame = parseStackFrame(gdbmiFrame, 0);
gotoLocation(frame, true);
} }
// //
// Stack // Stack
// //
manager()->stackHandler()->setCurrentIndex(0); manager()->stackHandler()->setCurrentIndex(0);
updateLocals(); // Quick shot updateLocals(qVariantFromValue(frame)); // Quick shot
reloadStack(false); reloadStack(false);
@@ -2583,11 +2580,6 @@ void GdbEngine::setToolTipExpression(const QPoint &mousePos,
// //
////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////
//: Variable
static const QString strNotInScope =
QCoreApplication::translate("Debugger::Internal::GdbEngine", "<not in scope>");
static void setWatchDataValue(WatchData &data, const GdbMi &mi, static void setWatchDataValue(WatchData &data, const GdbMi &mi,
int encoding = 0) int encoding = 0)
{ {
@@ -2804,8 +2796,8 @@ void GdbEngine::updateSubItem(const WatchData &data0)
qDebug() << "FIXME: GdbEngine::updateSubItem:" qDebug() << "FIXME: GdbEngine::updateSubItem:"
<< data.toString() << "should not happen"; << data.toString() << "should not happen";
#else #else
data.setType(strNotInScope); data.setType(WatchData::msgNotInScope());
data.setValue(strNotInScope); data.setValue(WatchData::msgNotInScope());
data.setHasChildren(false); data.setHasChildren(false);
insertData(data); insertData(data);
return; return;
@@ -3139,7 +3131,7 @@ void GdbEngine::handleVarCreate(const GdbResponse &response)
} else { } else {
data.setError(QString::fromLocal8Bit(response.data.findChild("msg").data())); data.setError(QString::fromLocal8Bit(response.data.findChild("msg").data()));
if (data.isWatcher()) { if (data.isWatcher()) {
data.value = strNotInScope; data.value = WatchData::msgNotInScope();
data.type = _(" "); data.type = _(" ");
data.setAllUnneeded(); data.setAllUnneeded();
data.setHasChildren(false); data.setHasChildren(false);
@@ -3213,7 +3205,7 @@ void GdbEngine::handleDebuggingHelperValue2(const GdbResponse &response)
GdbMi contents; GdbMi contents;
if (!parseConsoleStream(response, &contents)) { if (!parseConsoleStream(response, &contents)) {
data.setError(strNotInScope); data.setError(WatchData::msgNotInScope());
insertData(data); insertData(data);
return; return;
} }
@@ -3306,7 +3298,7 @@ void GdbEngine::handleDebuggingHelperValue3(const GdbResponse &response)
// << " STREAM:" << out; // << " STREAM:" << out;
if (list.isEmpty()) { if (list.isEmpty()) {
//: Value for variable //: Value for variable
data.setError(strNotInScope); data.setError(WatchData::msgNotInScope());
data.setAllUnneeded(); data.setAllUnneeded();
insertData(data); insertData(data);
} else if (data.type == __("QString") } else if (data.type == __("QString")
@@ -3351,19 +3343,19 @@ void GdbEngine::handleDebuggingHelperValue3(const GdbResponse &response)
} }
} else { } else {
//: Value for variable //: Value for variable
data.setError(strNotInScope); data.setError(WatchData::msgNotInScope());
data.setAllUnneeded(); data.setAllUnneeded();
insertData(data); insertData(data);
} }
} else { } else {
WatchData data = response.cookie.value<WatchData>(); WatchData data = response.cookie.value<WatchData>();
data.setError(strNotInScope); data.setError(WatchData::msgNotInScope());
data.setAllUnneeded(); data.setAllUnneeded();
insertData(data); insertData(data);
} }
} }
void GdbEngine::updateLocals() void GdbEngine::updateLocals(const QVariant &cookie)
{ {
m_pendingRequests = 0; m_pendingRequests = 0;
m_processedNames.clear(); m_processedNames.clear();
@@ -3393,7 +3385,7 @@ void GdbEngine::updateLocals()
postCommand(cmd, WatchUpdate, CB(handleStackListArguments)); postCommand(cmd, WatchUpdate, CB(handleStackListArguments));
// '2' is 'list with type and value' // '2' is 'list with type and value'
postCommand(_("-stack-list-locals 2"), WatchUpdate, postCommand(_("-stack-list-locals 2"), WatchUpdate,
CB(handleStackListLocals)); // stage 2/2 CB(handleStackListLocals), cookie); // stage 2/2
} }
} }
@@ -3489,91 +3481,102 @@ void GdbEngine::handleStackListLocals(const GdbResponse &response)
// There could be shadowed variables // There could be shadowed variables
QList<GdbMi> locals = response.data.findChild("locals").children(); QList<GdbMi> locals = response.data.findChild("locals").children();
locals += m_currentFunctionArgs; locals += m_currentFunctionArgs;
QMap<QByteArray, int> seen;
setLocals(locals); // If desired, retrieve list of uninitialized variables looking at
// the current frame. This is invoked first time after a stop from
// handleStop2, which passes on the frame as cookie. The whole stack
// is not known at this point.
QStringList uninitializedVariables;
if (theDebuggerAction(UseCodeModel)->isChecked()) {
const StackFrame frame = qVariantCanConvert<Debugger::Internal::StackFrame>(response.cookie) ?
qVariantValue<Debugger::Internal::StackFrame>(response.cookie) :
m_manager->stackHandler()->currentFrame();
if (frame.isUsable())
getUninitializedVariables(m_manager->cppCodeModelSnapshot(),
frame.function, frame.file, frame.line,
&uninitializedVariables);
}
QList<WatchData> list;
foreach (const GdbMi &item, locals)
list.push_back(localVariable(item, uninitializedVariables, &seen));
manager()->watchHandler()->insertBulkData(list);
manager()->watchHandler()->updateWatchers(); manager()->watchHandler()->updateWatchers();
} }
void GdbEngine::setLocals(const QList<GdbMi> &locals) // Parse a local variable from GdbMi
WatchData GdbEngine::localVariable(const GdbMi &item,
const QStringList &uninitializedVariables,
QMap<QByteArray, int> *seen)
{ {
//qDebug() << m_varToType; // Local variables of inlined code are reported as
QMap<QByteArray, int> seen; // 26^done,locals={varobj={exp="this",value="",name="var4",exp="this",
// numchild="1",type="const QtSharedPointer::Basic<CPlusPlus::..."
QList<WatchData> list; // We do not want these at all. Current hypotheses is that those
foreach (const GdbMi &item, locals) { // "spurious" locals have _two_ "exp" field. Try to filter them:
// Local variables of inlined code are reported as #ifdef Q_OS_MAC
// 26^done,locals={varobj={exp="this",value="",name="var4",exp="this", int numExps = 0;
// numchild="1",type="const QtSharedPointer::Basic<CPlusPlus::..." foreach (const GdbMi &child, item.children())
// We do not want these at all. Current hypotheses is that those numExps += int(child.name() == "exp");
// "spurious" locals have _two_ "exp" field. Try to filter them: if (numExps > 1)
#ifdef Q_OS_MAC continue;
int numExps = 0; QByteArray name = item.findChild("exp").data();
foreach (const GdbMi &child, item.children()) #else
numExps += int(child.name() == "exp"); QByteArray name = item.findChild("name").data();
if (numExps > 1) #endif
continue; const QMap<QByteArray, int>::iterator it = seen->find(name);
QByteArray name = item.findChild("exp").data(); if (it != seen->end()) {
#else const int n = it.value();
QByteArray name = item.findChild("name").data(); ++(it.value());
#endif WatchData data;
int n = seen.value(name); QString nam = _(name);
if (n) { data.iname = _("local.") + nam + QString::number(n + 1);
seen[name] = n + 1; //: Variable %1 is the variable name, %2 is a simple count
WatchData data; data.name = WatchData::shadowedName(nam, n);
QString nam = _(name); if (uninitializedVariables.contains(data.name)) {
data.iname = _("local.") + nam + QString::number(n + 1); data.setError(WatchData::msgNotInScope());
//: Variable %1 is the variable name, %2 is a simple count return data;
data.name = tr("%1 <shadowed %2>").arg(nam).arg(n); }
//: Type of local variable or parameter shadowed by another
//: variable of the same name in a nested block.
setWatchDataValue(data, item.findChild("value"));
data.setType(GdbEngine::tr("<shadowed>"));
data.setHasChildren(false);
return data;
}
seen->insert(name, 1);
WatchData data;
QString nam = _(name);
data.iname = _("local.") + nam;
data.name = nam;
data.exp = nam;
data.framekey = m_currentFrame + data.name;
setWatchDataType(data, item.findChild("type"));
if (uninitializedVariables.contains(data.name)) {
data.setError(WatchData::msgNotInScope());
return data;
}
if (isSynchroneous()) {
setWatchDataValue(data, item.findChild("value"),
item.findChild("valueencoded").data().toInt());
// We know that the complete list of children is
// somewhere in the response.
data.setChildrenUnneeded();
} else {
// set value only directly if it is simple enough, otherwise
// pass through the insertData() machinery
if (isIntOrFloatType(data.type) || isPointerType(data.type))
setWatchDataValue(data, item.findChild("value"));
if (isSymbianIntType(data.type)) {
setWatchDataValue(data, item.findChild("value")); setWatchDataValue(data, item.findChild("value"));
//: Type of local variable or parameter shadowed by another
//variable of the same name in a nested block
data.setType(tr("<shadowed>"));
data.setHasChildren(false); data.setHasChildren(false);
list.append(data);
} else {
seen[name] = 1;
WatchData data;
QString nam = _(name);
data.iname = _("local.") + nam;
data.name = nam;
data.exp = nam;
data.framekey = m_currentFrame + data.name;
setWatchDataType(data, item.findChild("type"));
if (isSynchroneous()) {
setWatchDataValue(data, item.findChild("value"),
item.findChild("valueencoded").data().toInt());
// We know that the complete list of children is
// somewhere in the response.
data.setChildrenUnneeded();
} else {
// set value only directly if it is simple enough, otherwise
// pass through the insertData() machinery
if (isIntOrFloatType(data.type) || isPointerType(data.type))
setWatchDataValue(data, item.findChild("value"));
if (isSymbianIntType(data.type)) {
setWatchDataValue(data, item.findChild("value"));
data.setHasChildren(false);
}
}
// Let's be a bit more bold:
//if (!hasDebuggingHelperForType(data.type)) {
// QByteArray value = item.findChild("value").data();
// if (!value.isEmpty() && value != "{...}")
// data.setValue(decodeData(value, 0));
//}
if (!manager()->watchHandler()->isExpandedIName(data.iname))
data.setChildrenUnneeded();
if (isPointerType(data.type) || data.name == __("this"))
data.setHasChildren(true);
if (0 && m_varToType.contains(data.framekey)) {
qDebug() << "RE-USING" << m_varToType.value(data.framekey);
data.setType(m_varToType.value(data.framekey));
}
list.append(data);
} }
} }
manager()->watchHandler()->insertBulkData(list);
if (!m_manager->watchHandler()->isExpandedIName(data.iname))
data.setChildrenUnneeded();
if (isPointerType(data.type) || data.name == __("this"))
data.setHasChildren(true);
return data;
} }
void GdbEngine::insertData(const WatchData &data0) void GdbEngine::insertData(const WatchData &data0)
@@ -4089,9 +4092,7 @@ void GdbEngine::handleFetchDisassemblerByAddress0(const GdbResponse &response)
void GdbEngine::gotoLocation(const StackFrame &frame, bool setMarker) void GdbEngine::gotoLocation(const StackFrame &frame, bool setMarker)
{ {
lastFile = frame.file; // qDebug() << "GOTO " << frame << setMarker;
lastLine = frame.line;
//qDebug() << "GOTO " << frame.toString() << setMarker;
m_manager->gotoLocation(frame, setMarker); m_manager->gotoLocation(frame, setMarker);
} }
@@ -4291,4 +4292,3 @@ IDebuggerEngine *createGdbEngine(DebuggerManager *manager)
Q_DECLARE_METATYPE(Debugger::Internal::MemoryAgentCookie); Q_DECLARE_METATYPE(Debugger::Internal::MemoryAgentCookie);
Q_DECLARE_METATYPE(Debugger::Internal::DisassemblerAgentCookie); Q_DECLARE_METATYPE(Debugger::Internal::DisassemblerAgentCookie);
Q_DECLARE_METATYPE(Debugger::Internal::GdbMi); Q_DECLARE_METATYPE(Debugger::Internal::GdbMi);

View File

@@ -231,7 +231,7 @@ private:
void setTokenBarrier(); void setTokenBarrier();
void updateAll(); void updateAll();
void updateLocals(); void updateLocals(const QVariant &cookie = QVariant());
void gdbInputAvailable(int channel, const QString &msg) void gdbInputAvailable(int channel, const QString &msg)
{ m_manager->showDebuggerInput(channel, msg); } { m_manager->showDebuggerInput(channel, msg); }
@@ -399,7 +399,9 @@ private:
const WatchData &parent); const WatchData &parent);
void setWatchDataType(WatchData &data, const GdbMi &mi); void setWatchDataType(WatchData &data, const GdbMi &mi);
void setWatchDataDisplayedType(WatchData &data, const GdbMi &mi); void setWatchDataDisplayedType(WatchData &data, const GdbMi &mi);
void setLocals(const QList<GdbMi> &locals); inline WatchData localVariable(const GdbMi &item,
const QStringList &uninitializedVariables,
QMap<QByteArray, int> *seen);
void connectDebuggingHelperActions(); void connectDebuggingHelperActions();
void disconnectDebuggingHelperActions(); void disconnectDebuggingHelperActions();
AbstractGdbAdapter *createAdapter(const DebuggerStartParametersPtr &dp); AbstractGdbAdapter *createAdapter(const DebuggerStartParametersPtr &dp);

View File

@@ -33,12 +33,17 @@
#include <QtCore/QString> #include <QtCore/QString>
#include <QtCore/QMetaType> #include <QtCore/QMetaType>
QT_BEGIN_NAMESPACE
class QDebug;
QT_END_NAMESPACE
namespace Debugger { namespace Debugger {
namespace Internal { namespace Internal {
struct StackFrame struct StackFrame
{ {
StackFrame(); StackFrame();
void clear();
bool isUsable() const; bool isUsable() const;
QString toToolTip() const; QString toToolTip() const;
QString toString() const; QString toString() const;
@@ -52,6 +57,8 @@ struct StackFrame
QString address; QString address;
}; };
QDebug operator<<(QDebug d, const StackFrame &);
} // namespace Internal } // namespace Internal
} // namespace Debugger } // namespace Debugger

View File

@@ -37,12 +37,23 @@
#include <QtCore/QDebug> #include <QtCore/QDebug>
#include <QtCore/QFileInfo> #include <QtCore/QFileInfo>
using namespace Debugger::Internal; namespace Debugger {
namespace Internal {
StackFrame::StackFrame() StackFrame::StackFrame()
: level(0), line(0) : level(0), line(0)
{} {}
void StackFrame::clear()
{
line = level = 0;
function.clear();
file.clear();
from.clear();
to.clear();
address.clear();
}
bool StackFrame::isUsable() const bool StackFrame::isUsable() const
{ {
return !file.isEmpty() && QFileInfo(file).isReadable(); return !file.isEmpty() && QFileInfo(file).isReadable();
@@ -52,12 +63,12 @@ QString StackFrame::toString() const
{ {
QString res; QString res;
QTextStream str(&res); QTextStream str(&res);
str << StackHandler::tr("Address:") << " " << address << " " str << StackHandler::tr("Address:") << ' ' << address << ' '
<< StackHandler::tr("Function:") << " " << function << " " << StackHandler::tr("Function:") << ' ' << function << ' '
<< StackHandler::tr("File:") << " " << file << " " << StackHandler::tr("File:") << ' ' << file << ' '
<< StackHandler::tr("Line:") << " " << line << " " << StackHandler::tr("Line:") << ' ' << line << ' '
<< StackHandler::tr("From:") << " " << from << " " << StackHandler::tr("From:") << ' ' << from << ' '
<< StackHandler::tr("To:") << " " << to; << StackHandler::tr("To:") << ' ' << to;
return res; return res;
} }
@@ -76,6 +87,23 @@ QString StackFrame::toToolTip() const
return res; return res;
} }
QDebug operator<<(QDebug d, const StackFrame &f)
{
QString res;
QTextStream str(&res);
str << "level=" << f.level << " address=" << f.address;
if (!f.function.isEmpty())
str << ' ' << f.function;
if (!f.file.isEmpty())
str << ' ' << f.file << ':' << f.line;
if (!f.from.isEmpty())
str << " from=" << f.from;
if (!f.to.isEmpty())
str << " to=" << f.to;
d.nospace() << res;
return d;
}
//////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////
// //
// StackHandler // StackHandler
@@ -379,3 +407,5 @@ void ThreadsHandler::notifyRunning()
it->notifyRunning(); it->notifyRunning();
emit dataChanged(index(0, 1), index(m_threads.size()- 1, ColumnCount - 1)); emit dataChanged(index(0, 1), index(m_threads.size()- 1, ColumnCount - 1));
} }
} // namespace Internal
} // namespace Debugger

View File

@@ -105,6 +105,7 @@ WatchData::WatchData() :
generation(-1), generation(-1),
valueEnabled(true), valueEnabled(true),
valueEditable(true), valueEditable(true),
error(false),
source(0), source(0),
state(InitialState), state(InitialState),
changed(false) changed(false)
@@ -127,7 +128,8 @@ bool WatchData::isEqual(const WatchData &other) const
&& framekey == other.framekey && framekey == other.framekey
&& hasChildren == other.hasChildren && hasChildren == other.hasChildren
&& valueEnabled == other.valueEnabled && valueEnabled == other.valueEnabled
&& valueEditable == other.valueEditable; && valueEditable == other.valueEditable
&& error == other.error;
} }
void WatchData::setError(const QString &msg) void WatchData::setError(const QString &msg)
@@ -137,6 +139,7 @@ void WatchData::setError(const QString &msg)
setHasChildren(false); setHasChildren(false);
valueEnabled = false; valueEnabled = false;
valueEditable = false; valueEditable = false;
error = true;
} }
void WatchData::setValue(const QString &value0) void WatchData::setValue(const QString &value0)
@@ -232,6 +235,8 @@ QString WatchData::toString() const
str << "iname=\"" << iname << doubleQuoteComma; str << "iname=\"" << iname << doubleQuoteComma;
if (!name.isEmpty() && name != iname) if (!name.isEmpty() && name != iname)
str << "name=\"" << name << doubleQuoteComma; str << "name=\"" << name << doubleQuoteComma;
if (error)
str << "error,";
if (!addr.isEmpty()) if (!addr.isEmpty())
str << "addr=\"" << addr << doubleQuoteComma; str << "addr=\"" << addr << doubleQuoteComma;
if (!exp.isEmpty()) if (!exp.isEmpty())
@@ -310,6 +315,19 @@ QString WatchData::toToolTip() const
return res; return res;
} }
QString WatchData::msgNotInScope()
{
static const QString rc = QCoreApplication::translate("Debugger::Internal::WatchData", "<not in scope>");
return rc;
}
QString WatchData::shadowedName(const QString &name, int seen)
{
if (seen <= 0)
return name;
return QCoreApplication::translate("Debugger::Internal::WatchData", "%1 <shadowed %2>").arg(name).arg(seen);
}
/////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////
// //
// WatchModel // WatchModel

View File

@@ -117,6 +117,9 @@ public:
bool isEqual(const WatchData &other) const; bool isEqual(const WatchData &other) const;
static QString msgNotInScope();
static QString shadowedName(const QString &name, int seen);
public: public:
QString iname; // internal name sth like 'local.baz.public.a' QString iname; // internal name sth like 'local.baz.public.a'
QString exp; // the expression QString exp; // the expression
@@ -135,6 +138,7 @@ public:
int generation; // when updated? int generation; // when updated?
bool valueEnabled; // value will be greyed out or not bool valueEnabled; // value will be greyed out or not
bool valueEditable; // value will be editable bool valueEditable; // value will be editable
bool error;
private: private:

View File

@@ -43,6 +43,9 @@
#include <cpptools/cpptoolsconstants.h> #include <cpptools/cpptoolsconstants.h>
#include <cplusplus/ExpressionUnderCursor.h> #include <cplusplus/ExpressionUnderCursor.h>
#include <cplusplus/Overview.h>
#include <Symbols.h>
#include <Scope.h>
#include <extensionsystem/pluginmanager.h> #include <extensionsystem/pluginmanager.h>
@@ -51,6 +54,7 @@
#include <QtCore/QStringList> #include <QtCore/QStringList>
#include <QtCore/QCoreApplication> #include <QtCore/QCoreApplication>
#include <QtCore/QTextStream> #include <QtCore/QTextStream>
#include <QtCore/QHash>
#include <QtGui/QTextCursor> #include <QtGui/QTextCursor>
#include <QtGui/QPlainTextEdit> #include <QtGui/QPlainTextEdit>
@@ -59,6 +63,78 @@
#include <ctype.h> #include <ctype.h>
enum { debug = 0 }; enum { debug = 0 };
// Debug helpers for code model. @todo: Move to some CppTools library?
namespace CPlusPlus {
static void debugCppSymbolRecursion(QTextStream &str, const Overview &o,
const Symbol &s, bool doRecurse = true,
int recursion = 0)
{
for (int i = 0; i < recursion; i++)
str << " ";
str << "Symbol: " << o.prettyName(s.name()) << " at line " << s.line();
if (s.isFunction())
str << " function";
if (s.isClass())
str << " class";
if (s.isDeclaration())
str << " declaration";
if (s.isBlock())
str << " block";
if (doRecurse && s.isScopedSymbol()) {
const ScopedSymbol *scoped = s.asScopedSymbol();
const int size = scoped->memberCount();
str << " scoped symbol of " << size << '\n';
for (int m = 0; m < size; m++)
debugCppSymbolRecursion(str, o, *scoped->memberAt(m), true, recursion + 1);
} else {
str << '\n';
}
}
QDebug operator<<(QDebug d, const Symbol &s)
{
QString output;
CPlusPlus::Overview o;
QTextStream str(&output);
debugCppSymbolRecursion(str, o, s, true, 0);
d.nospace() << output;
return d;
}
QDebug operator<<(QDebug d, const Scope &scope)
{
QString output;
Overview o;
QTextStream str(&output);
const int size = scope.symbolCount();
str << "Scope of " << size;
if (scope.isNamespaceScope())
str << " namespace";
if (scope.isClassScope())
str << " class";
if (scope.isEnumScope())
str << " enum";
if (scope.isBlockScope())
str << " block";
if (scope.isFunctionScope())
str << " function";
if (scope.isPrototypeScope())
str << " prototype";
if (const Symbol *owner = scope.owner()) {
str << " owner: ";
debugCppSymbolRecursion(str, o, *owner, false, 0);
} else {
str << " 0-owner\n";
}
for (int s = 0; s < size; s++)
debugCppSymbolRecursion(str, o, *scope.symbolAt(s), true, 2);
d.nospace() << output;
return d;
}
} // namespace CPlusPlus
namespace Debugger { namespace Debugger {
namespace Internal { namespace Internal {
@@ -217,6 +293,133 @@ QString stripPointerType(QString type)
return type; return type;
} }
/* getUninitializedVariables(): Get variables that are not initialized
* at a certain line of a function from the code model to be able to
* indicate them as not in scope in the locals view.
* Find document + function in the code model, do a double check and
* collect declarative symbols that are in the function past or on
* the current line. blockRecursion() recurses up the scopes
* and collect symbols declared past or on the current line.
* Recursion goes up from the innermost scope, keeping a map
* of occurrences seen, to be able to derive the names of
* shadowed variables as the debugger sees them:
\code
int x; // Occurrence (1), should be reported as "x <shadowed 1>"
if (true) {
int x = 5; (2) // Occurrence (2), should be reported as "x"
}
\endcode
*/
typedef QHash<QString, int> SeenHash;
static void blockRecursion(const CPlusPlus::Overview &overview,
const CPlusPlus::Scope *scope,
unsigned line,
QStringList *uninitializedVariables,
SeenHash *seenHash,
int level = 0)
{
const int size = scope->symbolCount();
for (int s = 0; s < size; s++){
const CPlusPlus::Symbol *symbol = scope->symbolAt(s);
if (symbol->isDeclaration()) {
// Find out about shadowed symbols by bookkeeping
// the already seen occurrences in a hash.
const QString name = overview.prettyName(symbol->name());
SeenHash::iterator it = seenHash->find(name);
if (it == seenHash->end()) {
it = seenHash->insert(name, 0);
} else {
++(it.value());
}
// 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()));
}
}
// Next block scope.
if (const CPlusPlus::Scope *enclosingScope = scope->enclosingBlockScope())
blockRecursion(overview, enclosingScope, line, uninitializedVariables, seenHash, level + 1);
}
// Inline helper with integer error return codes.
static inline
int getUninitializedVariablesI(const CPlusPlus::Snapshot &snapshot,
const QString &functionName,
const QString &file,
int line,
QStringList *uninitializedVariables)
{
uninitializedVariables->clear();
// Find document
if (snapshot.empty() || functionName.isEmpty() || file.isEmpty() || line < 1)
return 1;
const CPlusPlus::Snapshot::ConstIterator docIt = snapshot.constFind(file);
if (docIt == snapshot.constEnd())
return 2;
const CPlusPlus::Document::Ptr doc = docIt.value();
// Look at symbol at line and find its function. Either it is the
// function itself or some expression/variable.
const CPlusPlus::Symbol *symbolAtLine = doc->findSymbolAt(line, 0);
if (!symbolAtLine)
return 4;
// First figure out the function to do a safety name check.
const CPlusPlus::Function *function = 0;
const bool hitFunction = symbolAtLine->isFunction();
if (hitFunction) {
function = symbolAtLine->asFunction();
} else {
if (CPlusPlus::Scope *functionScope = symbolAtLine->enclosingFunctionScope())
function = functionScope->owner()->asFunction();
}
if (!function)
return 7;
// Compare function names with a bit off fuzz,
// skipping modules from a CDB symbol "lib!foo" or namespaces
// that the code model does not show at this point
CPlusPlus::Overview overview;
const QString name = overview.prettyName(function->name());
if (!functionName.endsWith(name))
return 11;
if (functionName.size() > name.size()) {
const char previousChar = functionName.at(functionName.size() - name.size() - 1).toLatin1();
if (previousChar != ':' && previousChar != '!' )
return 11;
}
// Starting from the innermost block scope, collect
// declarations.
const CPlusPlus::Scope *innerMostScope = hitFunction ?
symbolAtLine->asFunction()->members() :
symbolAtLine->enclosingBlockScope();
if (!innerMostScope)
return 15;
SeenHash seenHash;
blockRecursion(overview, innerMostScope, line, uninitializedVariables, &seenHash);
return 0;
}
bool getUninitializedVariables(const CPlusPlus::Snapshot &snapshot,
const QString &function,
const QString &file,
int line,
QStringList *uninitializedVariables)
{
const int rc = getUninitializedVariablesI(snapshot, function, file, line, uninitializedVariables);
if (debug) {
QString msg;
QTextStream str(&msg);
str << "getUninitializedVariables() " << function << ' ' << file << ':' << line
<< " returns (int) " << rc << " '"
<< uninitializedVariables->join(QString(QLatin1Char(','))) << '\'';
if (rc)
str << " of " << snapshot.keys().size() << " documents";
qDebug() << msg;
}
return rc == 0;
}
QString gdbQuoteTypes(const QString &type) QString gdbQuoteTypes(const QString &type)
{ {
// gdb does not understand sizeof(Core::IFile*). // gdb does not understand sizeof(Core::IFile*).

View File

@@ -45,6 +45,10 @@ namespace Core {
class IEditor; class IEditor;
} }
namespace CPlusPlus {
class Snapshot;
}
namespace Debugger { namespace Debugger {
namespace Internal { namespace Internal {
@@ -89,6 +93,15 @@ QString cppExpressionAt(TextEditor::ITextEditor *editor, int pos,
// Decode string data as returned by the dumper helpers. // Decode string data as returned by the dumper helpers.
QString decodeData(const QByteArray &baIn, int encoding); QString decodeData(const QByteArray &baIn, int encoding);
// Get variables that are not initialized at a certain line
// of a function from the code model. Shadowed variables will
// be reported using the debugger naming conventions '<shadowed n>'
bool getUninitializedVariables(const CPlusPlus::Snapshot &snapshot,
const QString &function,
const QString &file,
int line,
QStringList *uninitializedVariables);
/* Attempt to put common code of the dumper handling into a helper /* Attempt to put common code of the dumper handling into a helper
* class. * class.
* "Custom dumper" is a library compiled against the current * "Custom dumper" is a library compiled against the current

View File

@@ -253,10 +253,11 @@ void WatchWindow::contextMenuEvent(QContextMenuEvent *ev)
QAction *actInsertNewWatchItem = menu.addAction(tr("Insert new watch item")); QAction *actInsertNewWatchItem = menu.addAction(tr("Insert new watch item"));
QAction *actSelectWidgetToWatch = menu.addAction(tr("Select widget to watch")); QAction *actSelectWidgetToWatch = menu.addAction(tr("Select widget to watch"));
const bool actionsEnabled = m_manager->debuggerActionsEnabled();
const QString address = model()->data(mi0, AddressRole).toString(); const QString address = model()->data(mi0, AddressRole).toString();
QAction *actWatchKnownMemory = 0; QAction *actWatchKnownMemory = 0;
QAction *actWatchUnknownMemory = new QAction(tr("Open memory editor..."), &menu); QAction *actWatchUnknownMemory = new QAction(tr("Open memory editor..."), &menu);
actWatchUnknownMemory->setEnabled(m_manager->debuggerActionsEnabled()); actWatchUnknownMemory->setEnabled(actionsEnabled);
if (!address.isEmpty()) if (!address.isEmpty())
actWatchKnownMemory = new QAction(tr("Open memory editor at %1").arg(address), &menu); actWatchKnownMemory = new QAction(tr("Open memory editor at %1").arg(address), &menu);
@@ -276,7 +277,9 @@ void WatchWindow::contextMenuEvent(QContextMenuEvent *ev)
menu.addAction(theDebuggerAction(RecheckDebuggingHelpers)); menu.addAction(theDebuggerAction(RecheckDebuggingHelpers));
menu.addAction(theDebuggerAction(UseDebuggingHelpers)); menu.addAction(theDebuggerAction(UseDebuggingHelpers));
QAction *actClearCodeModelSnapshot = new QAction(tr("Refresh code model snapshot"), &menu);
actClearCodeModelSnapshot->setEnabled(actionsEnabled && theDebuggerAction(UseCodeModel)->isChecked());
menu.addAction(actClearCodeModelSnapshot);
menu.addSeparator(); menu.addSeparator();
menu.addAction(theDebuggerAction(UseToolTipsInLocalsView)); menu.addAction(theDebuggerAction(UseToolTipsInLocalsView));
@@ -311,6 +314,8 @@ void WatchWindow::contextMenuEvent(QContextMenuEvent *ev)
} else if (act == actSelectWidgetToWatch) { } else if (act == actSelectWidgetToWatch) {
grabMouse(Qt::CrossCursor); grabMouse(Qt::CrossCursor);
m_grabbing = true; m_grabbing = true;
} else if (act == actClearCodeModelSnapshot) {
m_manager->clearCppCodeModelSnapshot();
} else { } else {
for (int i = 0; i != alternativeFormats.size(); ++i) { for (int i = 0; i != alternativeFormats.size(); ++i) {
if (act == typeFormatActions.at(i)) if (act == typeFormatActions.at(i))