Commit 25ee70bb authored by Friedemann Kleint's avatar Friedemann Kleint

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: default avatarhjk <qtc-committer@nokia.com>
parent c79476e7
......@@ -341,6 +341,8 @@ void CdbDumperInitThread ::run()
CdbDumperHelper::CdbDumperHelper(DebuggerManager *manager,
CdbComInterfaces *cif) :
m_tryInjectLoad(true),
m_msgDisabled(QLatin1String("Dumpers are disabled")),
m_msgNotInScope(QLatin1String("Data not in scope")),
m_state(NotLoaded),
m_manager(manager),
m_cif(cif),
......@@ -648,8 +650,12 @@ CdbDumperHelper::DumpResult CdbDumperHelper::dumpTypeI(const WatchData &wd, bool
{
errorMessage->clear();
// Check failure cache and supported types
if (m_state == Disabled) {
*errorMessage = QLatin1String("Dumpers are disabled");
if (m_state == Disabled) {
*errorMessage =m_msgDisabled;
return DumpNotHandled;
}
if (wd.error) {
*errorMessage =m_msgNotInScope;
return DumpNotHandled;
}
if (m_failedTypes.contains(wd.type)) {
......
......@@ -134,6 +134,8 @@ private:
static bool writeToDebuggee(CIDebugDataSpaces *ds, const QByteArray &buffer, quint64 address, QString *errorMessage);
const bool m_tryInjectLoad;
const QString m_msgDisabled;
const QString m_msgNotInScope;
State m_state;
DebuggerManager *m_manager;
CdbComInterfaces *m_cif;
......
......@@ -217,7 +217,7 @@ bool WatchHandleDumperInserter::expandPointerToDumpable(const WatchData &wd, QSt
bool handled = false;
do {
if (!isPointerType(wd.type))
if (wd.error || !isPointerType(wd.type))
break;
const int classPos = wd.value.indexOf(" class ");
if (classPos == -1)
......@@ -396,9 +396,9 @@ bool CdbStackFrameContext::editorToolTip(const QString &iname,
*errorMessage = QString::fromLatin1("%1 not found.").arg(iname);
return false;
}
const WatchData wd = m_symbolContext->symbolAt(index);
// 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;
if (CdbDumperHelper::DumpOk == m_dumper->dumpType(wd, false, &result, errorMessage)) {
foreach (const WatchData &dwd, result) {
......
......@@ -33,8 +33,11 @@
#include "cdbsymbolgroupcontext.h"
#include "cdbdebugengine_p.h"
#include "cdbdumperhelper.h"
#include "debuggeractions.h"
#include "debuggermanager.h"
#include <QtCore/QDir>
#include <QtCore/QDebug>
#include <QtCore/QTextStream>
namespace Debugger {
......@@ -160,7 +163,13 @@ CdbStackFrameContext *CdbStackTraceContext::frameContextAt(int index, QString *e
*errorMessage = msgFrameContextFailed(index, m_frames.at(index), *errorMessage);
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) {
*errorMessage = msgFrameContextFailed(index, m_frames.at(index), *errorMessage);
return 0;
......
......@@ -69,12 +69,14 @@ class CdbSymbolGroupContext
{
Q_DISABLE_COPY(CdbSymbolGroupContext);
explicit CdbSymbolGroupContext(const QString &prefix,
CIDebugSymbolGroup *symbolGroup);
CIDebugSymbolGroup *symbolGroup,
const QStringList &uninitializedVariables = QStringList());
public:
~CdbSymbolGroupContext();
static CdbSymbolGroupContext *create(const QString &prefix,
static CdbSymbolGroupContext *create(const QString &prefix,
CIDebugSymbolGroup *symbolGroup,
const QStringList &uninitializedVariables,
QString *errorMessage);
QString prefix() const { return m_prefix; }
......@@ -118,7 +120,7 @@ public:
int dumpedOwner,
OutputIterator it, QString *errorMessage);
WatchData symbolAt(unsigned long index) const;
WatchData watchDataAt(unsigned long index) const;
// Run the internal dumpers on the symbol
WatchData dumpSymbolAt(CIDebugDataSpaces *ds, unsigned long index);
......@@ -155,7 +157,7 @@ private:
unsigned long *parentId,
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;
int dumpQString(CIDebugDataSpaces *ds, WatchData *wd);
......@@ -166,6 +168,7 @@ private:
const QString m_prefix;
const QChar m_nameDelimiter;
const QSet<QString> m_uninitializedVariables;
CIDebugSymbolGroup *m_symbolGroup;
NameIndexMap m_inameIndexMap;
......
......@@ -61,7 +61,7 @@ bool CdbSymbolGroupContext::getDumpChildSymbols(CIDebugDataSpaces *ds, const QSt
for (int s = start; s < m_symbolParameters.size(); ++s) {
const DEBUG_SYMBOL_PARAMETERS &p = m_symbolParameters.at(s);
if (p.ParentSymbol == parentId && isSymbolDisplayable(p)) {
WatchData wd = symbolAt(s);
WatchData wd = watchDataAt(s);
// Run internal dumper, mark ownership
if (ds) {
switch (dump(ds, &wd)) {
......
......@@ -229,6 +229,13 @@ DebuggerSettings *DebuggerSettings::instance()
item->setValue(false);
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->setText(tr("Recheck debugging helper availability"));
......
......@@ -85,6 +85,8 @@ enum DebuggerActionCode
UseCustomDebuggingHelperLocation,
CustomDebuggingHelperLocation,
DebugDebuggingHelpers,
UseCodeModel,
UseToolTipsInMainEditor,
UseToolTipsInLocalsView,
......
......@@ -61,6 +61,8 @@
#include <utils/qtcassert.h>
#include <utils/fancymainwindow.h>
#include <projectexplorer/toolchain.h>
#include <cplusplus/CppDocument.h>
#include <cpptools/cppmodelmanagerinterface.h>
#include <QtCore/QDebug>
#include <QtCore/QDir>
......@@ -303,6 +305,8 @@ struct DebuggerManagerPrivate
IDebuggerEngine *m_engine;
DebuggerState m_state;
CPlusPlus::Snapshot m_codeModelSnapshot;
};
DebuggerManager *DebuggerManagerPrivate::instance = 0;
......@@ -623,6 +627,18 @@ WatchHandler *DebuggerManager::watchHandler() const
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
{
return d->m_sourceFilesWindow;
......@@ -1026,6 +1042,7 @@ void DebuggerManager::exitDebugger()
// in turn will handle the cleanup.
if (d->m_engine && state() != DebuggerNotReady)
d->m_engine->exitDebugger();
d->m_codeModelSnapshot.clear();
}
DebuggerStartParametersPtr DebuggerManager::startParameters() const
......
......@@ -59,6 +59,10 @@ namespace TextEditor {
class ITextEditor;
}
namespace CPlusPlus {
class Snapshot;
}
namespace Debugger {
namespace Internal {
......@@ -180,6 +184,8 @@ public:
QString *settingsCategory = 0,
QString *settingsPage = 0) const;
const CPlusPlus::Snapshot &cppCodeModelSnapshot() const;
static DebuggerManager *instance();
public slots:
......@@ -232,6 +238,7 @@ public slots:
void setRegisterValue(int nr, const QString &value);
void showStatusMessage(const QString &msg, int timeout = -1); // -1 forever
void clearCppCodeModelSnapshot();
public slots: // FIXME
void showDebuggerOutput(const QString &msg)
......@@ -267,7 +274,8 @@ private:
Internal::ThreadsHandler *threadsHandler() const;
Internal::WatchHandler *watchHandler() const;
Internal::SourceFilesWindow *sourceFileWindow() const;
QWidget *threadsWindow() const;
QWidget *threadsWindow() const;
Internal::DebuggerManagerActions debuggerManagerActions() const;
void notifyInferiorStopped();
......
......@@ -373,6 +373,9 @@ QWidget *DebuggingHelperOptionPage::createPage(QWidget *parent)
m_group.insert(theDebuggerAction(CustomDebuggingHelperLocation),
m_ui.dumperLocationChooser);
m_group.insert(theDebuggerAction(UseCodeModel),
m_ui.checkBoxUseCodeModel);
#ifdef QT_DEBUG
m_group.insert(theDebuggerAction(DebugDebuggingHelpers),
m_ui.checkBoxDebugDebuggingHelpers);
......
......@@ -6,7 +6,7 @@
<rect>
<x>0</x>
<y>0</y>
<width>403</width>
<width>432</width>
<height>434</height>
</rect>
</property>
......@@ -83,10 +83,20 @@
</widget>
</item>
<item>
<widget class="Utils::PathChooser" name="dumperLocationChooser" native="true"/>
<widget class="Utils::PathChooser" name="dumperLocationChooser"/>
</item>
</layout>
</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>
<widget class="QCheckBox" name="checkBoxDebugDebuggingHelpers">
<property name="toolTip">
......
This diff is collapsed.
......@@ -231,7 +231,7 @@ private:
void setTokenBarrier();
void updateAll();
void updateLocals();
void updateLocals(const QVariant &cookie = QVariant());
void gdbInputAvailable(int channel, const QString &msg)
{ m_manager->showDebuggerInput(channel, msg); }
......@@ -399,7 +399,9 @@ private:
const WatchData &parent);
void setWatchDataType(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 disconnectDebuggingHelperActions();
AbstractGdbAdapter *createAdapter(const DebuggerStartParametersPtr &dp);
......
......@@ -33,12 +33,17 @@
#include <QtCore/QString>
#include <QtCore/QMetaType>
QT_BEGIN_NAMESPACE
class QDebug;
QT_END_NAMESPACE
namespace Debugger {
namespace Internal {
struct StackFrame
{
StackFrame();
void clear();
bool isUsable() const;
QString toToolTip() const;
QString toString() const;
......@@ -52,6 +57,8 @@ struct StackFrame
QString address;
};
QDebug operator<<(QDebug d, const StackFrame &);
} // namespace Internal
} // namespace Debugger
......
......@@ -37,12 +37,23 @@
#include <QtCore/QDebug>
#include <QtCore/QFileInfo>
using namespace Debugger::Internal;
namespace Debugger {
namespace Internal {
StackFrame::StackFrame()
: level(0), line(0)
{}
void StackFrame::clear()
{
line = level = 0;
function.clear();
file.clear();
from.clear();
to.clear();
address.clear();
}
bool StackFrame::isUsable() const
{
return !file.isEmpty() && QFileInfo(file).isReadable();
......@@ -52,12 +63,12 @@ QString StackFrame::toString() const
{
QString res;
QTextStream str(&res);
str << StackHandler::tr("Address:") << " " << address << " "
<< StackHandler::tr("Function:") << " " << function << " "
<< StackHandler::tr("File:") << " " << file << " "
<< StackHandler::tr("Line:") << " " << line << " "
<< StackHandler::tr("From:") << " " << from << " "
<< StackHandler::tr("To:") << " " << to;
str << StackHandler::tr("Address:") << ' ' << address << ' '
<< StackHandler::tr("Function:") << ' ' << function << ' '
<< StackHandler::tr("File:") << ' ' << file << ' '
<< StackHandler::tr("Line:") << ' ' << line << ' '
<< StackHandler::tr("From:") << ' ' << from << ' '
<< StackHandler::tr("To:") << ' ' << to;
return res;
}
......@@ -76,6 +87,23 @@ QString StackFrame::toToolTip() const
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
......@@ -379,3 +407,5 @@ void ThreadsHandler::notifyRunning()
it->notifyRunning();
emit dataChanged(index(0, 1), index(m_threads.size()- 1, ColumnCount - 1));
}
} // namespace Internal
} // namespace Debugger
......@@ -105,6 +105,7 @@ WatchData::WatchData() :
generation(-1),
valueEnabled(true),
valueEditable(true),
error(false),
source(0),
state(InitialState),
changed(false)
......@@ -127,7 +128,8 @@ bool WatchData::isEqual(const WatchData &other) const
&& framekey == other.framekey
&& hasChildren == other.hasChildren
&& valueEnabled == other.valueEnabled
&& valueEditable == other.valueEditable;
&& valueEditable == other.valueEditable
&& error == other.error;
}
void WatchData::setError(const QString &msg)
......@@ -137,6 +139,7 @@ void WatchData::setError(const QString &msg)
setHasChildren(false);
valueEnabled = false;
valueEditable = false;
error = true;
}
void WatchData::setValue(const QString &value0)
......@@ -232,6 +235,8 @@ QString WatchData::toString() const
str << "iname=\"" << iname << doubleQuoteComma;
if (!name.isEmpty() && name != iname)
str << "name=\"" << name << doubleQuoteComma;
if (error)
str << "error,";
if (!addr.isEmpty())
str << "addr=\"" << addr << doubleQuoteComma;
if (!exp.isEmpty())
......@@ -310,6 +315,19 @@ QString WatchData::toToolTip() const
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
......
......@@ -117,6 +117,9 @@ public:
bool isEqual(const WatchData &other) const;
static QString msgNotInScope();
static QString shadowedName(const QString &name, int seen);
public:
QString iname; // internal name sth like 'local.baz.public.a'
QString exp; // the expression
......@@ -135,6 +138,7 @@ public:
int generation; // when updated?
bool valueEnabled; // value will be greyed out or not
bool valueEditable; // value will be editable
bool error;
private:
......
......@@ -43,6 +43,9 @@
#include <cpptools/cpptoolsconstants.h>
#include <cplusplus/ExpressionUnderCursor.h>
#include <cplusplus/Overview.h>
#include <Symbols.h>
#include <Scope.h>
#include <extensionsystem/pluginmanager.h>
......@@ -51,6 +54,7 @@
#include <QtCore/QStringList>
#include <QtCore/QCoreApplication>
#include <QtCore/QTextStream>
#include <QtCore/QHash>
#include <QtGui/QTextCursor>
#include <QtGui/QPlainTextEdit>
......@@ -59,6 +63,78 @@
#include <ctype.h>
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 Internal {
......@@ -217,6 +293,133 @@ QString stripPointerType(QString 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);
}