aboutsummaryrefslogtreecommitdiffstats
path: root/src/plugins/valgrind/memchecktool.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/plugins/valgrind/memchecktool.cpp')
-rw-r--r--src/plugins/valgrind/memchecktool.cpp549
1 files changed, 549 insertions, 0 deletions
diff --git a/src/plugins/valgrind/memchecktool.cpp b/src/plugins/valgrind/memchecktool.cpp
new file mode 100644
index 00000000000..5388662e896
--- /dev/null
+++ b/src/plugins/valgrind/memchecktool.cpp
@@ -0,0 +1,549 @@
+/**************************************************************************
+**
+** This file is part of Qt Creator
+**
+** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies).
+**
+** Author: Nicolas Arnaud-Cormos, KDAB ([email protected])
+**
+** Contact: Nokia Corporation ([email protected])
+**
+**
+** GNU Lesser General Public License Usage
+**
+** This file may be used under the terms of the GNU Lesser General Public
+** License version 2.1 as published by the Free Software Foundation and
+** appearing in the file LICENSE.LGPL included in the packaging of this file.
+** Please review the following information to ensure the GNU Lesser General
+** Public License version 2.1 requirements will be met:
+** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** Other Usage
+**
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at [email protected].
+**
+**************************************************************************/
+
+#include "memchecktool.h"
+#include "memcheckengine.h"
+#include "memcheckerrorview.h"
+#include "memchecksettings.h"
+#include "valgrindsettings.h"
+
+#include <analyzerbase/analyzermanager.h>
+#include <analyzerbase/analyzerconstants.h>
+#include <analyzerbase/ianalyzeroutputpaneadapter.h>
+
+#include <valgrind/xmlprotocol/errorlistmodel.h>
+#include <valgrind/xmlprotocol/stackmodel.h>
+#include <valgrind/xmlprotocol/error.h>
+#include <valgrind/xmlprotocol/frame.h>
+#include <valgrind/xmlprotocol/stack.h>
+#include <valgrind/xmlprotocol/suppression.h>
+
+#include <extensionsystem/iplugin.h>
+#include <extensionsystem/pluginmanager.h>
+
+#include <projectexplorer/projectexplorer.h>
+#include <projectexplorer/project.h>
+#include <projectexplorer/runconfiguration.h>
+#include <projectexplorer/target.h>
+#include <projectexplorer/session.h>
+#include <projectexplorer/buildconfiguration.h>
+
+#include <coreplugin/coreconstants.h>
+#include <coreplugin/icore.h>
+#include <coreplugin/actionmanager/actionmanager.h>
+#include <coreplugin/actionmanager/actioncontainer.h>
+#include <coreplugin/actionmanager/command.h>
+#include <coreplugin/uniqueidmanager.h>
+
+#include <texteditor/basetexteditor.h>
+
+#include <utils/fancymainwindow.h>
+#include <utils/styledbar.h>
+#include <utils/qtcassert.h>
+
+#include <QtCore/QString>
+#include <QtCore/QLatin1String>
+#include <QtCore/QFileInfo>
+#include <QtCore/QFile>
+#include <QtCore/QDir>
+
+#include <QtGui/QDockWidget>
+#include <QtGui/QHBoxLayout>
+#include <QtGui/QComboBox>
+#include <QtGui/QLabel>
+#include <QtGui/QSpinBox>
+#include <QtGui/QAction>
+#include <QtGui/QMenu>
+#include <QtGui/QMessageBox>
+#include <QtGui/QToolButton>
+#include <QtGui/QCheckBox>
+#include <utils/stylehelper.h>
+
+using namespace Analyzer;
+using namespace Valgrind::XmlProtocol;
+
+namespace Valgrind {
+namespace Internal {
+
+// Adapter for output pane.
+class MemCheckOutputPaneAdapter : public Analyzer::ListItemViewOutputPaneAdapter
+{
+public:
+ explicit MemCheckOutputPaneAdapter(MemcheckTool *mct) :
+ ListItemViewOutputPaneAdapter(mct), m_tool(mct) {}
+
+ virtual QWidget *toolBarWidget() { return m_tool->createPaneToolBarWidget(); }
+ virtual void clearContents() { m_tool->clearErrorView(); }
+
+protected:
+ virtual QAbstractItemView *createItemView() { return m_tool->ensurePaneErrorView(); }
+
+private:
+ MemcheckTool *m_tool;
+};
+
+// ---------------------------- MemcheckErrorFilterProxyModel
+MemcheckErrorFilterProxyModel::MemcheckErrorFilterProxyModel(QObject *parent)
+ : QSortFilterProxyModel(parent),
+ m_filterExternalIssues(false)
+{
+}
+
+void MemcheckErrorFilterProxyModel::setAcceptedKinds(const QList<int> &acceptedKinds)
+{
+ if (m_acceptedKinds != acceptedKinds) {
+ m_acceptedKinds = acceptedKinds;
+ invalidate();
+ }
+}
+
+void MemcheckErrorFilterProxyModel::setFilterExternalIssues(bool filter)
+{
+ if (m_filterExternalIssues != filter) {
+ m_filterExternalIssues = filter;
+ invalidate();
+ }
+}
+
+bool MemcheckErrorFilterProxyModel::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const
+{
+ // we only deal with toplevel items
+ if (sourceParent.isValid())
+ return true;
+
+ // because toplevel items have no parent, we can't use sourceParent to find them. we just use
+ // sourceParent as an invalid index, telling the model that the index we're looking for has no
+ // parent.
+ QAbstractItemModel *model = sourceModel();
+ QModelIndex sourceIndex = model->index(sourceRow, filterKeyColumn(), sourceParent);
+ if (!sourceIndex.isValid())
+ return true;
+
+ const Error error = sourceIndex.data(ErrorListModel::ErrorRole).value<Error>();
+
+ // filter on kind
+ if (!m_acceptedKinds.contains(error.kind()))
+ return false;
+
+ // filter non-project stuff
+ if (m_filterExternalIssues && !error.stacks().isEmpty()) {
+ // ALGORITHM: look at last five stack frames, if none of these is inside any open projects,
+ // assume this error was created by an external library
+ ProjectExplorer::SessionManager *session
+ = ProjectExplorer::ProjectExplorerPlugin::instance()->session();
+ QSet<QString> validFolders;
+ foreach (ProjectExplorer::Project *project, session->projects()) {
+ validFolders << project->projectDirectory();
+ foreach (ProjectExplorer::Target *target, project->targets()) {
+ foreach (ProjectExplorer::BuildConfiguration *config, target->buildConfigurations()) {
+ validFolders << config->buildDirectory();
+ }
+ }
+ }
+
+ const QVector< Frame > frames = error.stacks().first().frames();
+
+ const int framesToLookAt = qMin(6, frames.size());
+
+ bool inProject = false;
+ for ( int i = 0; i < framesToLookAt; ++i ) {
+ const Frame &frame = frames.at(i);
+ foreach (const QString &folder, validFolders) {
+ if (frame.object().startsWith(folder)) {
+ inProject = true;
+ break;
+ }
+ }
+ }
+ if (!inProject)
+ return false;
+ }
+
+ return true;
+}
+
+static void initKindFilterAction(QAction *action, const QList<int> &kinds)
+{
+ action->setCheckable(true);
+ QVariantList data;
+ foreach (int kind, kinds)
+ data << kind;
+ action->setData(data);
+}
+
+MemcheckTool::MemcheckTool(QObject *parent) :
+ Analyzer::IAnalyzerTool(parent),
+ m_settings(0),
+ m_errorModel(0),
+ m_errorProxyModel(0),
+ m_errorView(0),
+ m_filterProjectAction(new QAction(tr("External Errors"), this)),
+ m_suppressionSeparator(new QAction(tr("Suppressions"), this)),
+ m_outputPaneAdapter(0)
+{
+ setObjectName(QLatin1String("MemcheckTool"));
+ connect(ProjectExplorer::ProjectExplorerPlugin::instance(),
+ SIGNAL(updateRunActions()), SLOT(maybeActiveRunConfigurationChanged()));
+
+ QAction *a = new QAction(tr("Definite Memory Leaks"), this);
+ initKindFilterAction(a, QList<int>() << Leak_DefinitelyLost << Leak_IndirectlyLost);
+ m_errorFilterActions << a;
+
+ a = new QAction(tr("Possible Memory Leaks"), this);
+ initKindFilterAction(a, QList<int>() << Leak_PossiblyLost << Leak_StillReachable);
+ m_errorFilterActions << a;
+
+ a = new QAction(tr("Use of Uninitialized Memory"), this);
+ initKindFilterAction(a, QList<int>() << InvalidRead << InvalidWrite << InvalidJump << Overlap
+ << InvalidMemPool << UninitCondition << UninitValue
+ << SyscallParam << ClientCheck);
+ m_errorFilterActions << a;
+
+ a = new QAction(tr("Invalid Frees"), this);
+ initKindFilterAction(a, QList<int>() << InvalidFree << MismatchedFree);
+ m_errorFilterActions << a;
+
+ m_filterProjectAction->setToolTip(tr("Show issues originating outside currently opened projects."));
+ m_filterProjectAction->setCheckable(true);
+
+ m_suppressionSeparator->setSeparator(true);
+ m_suppressionSeparator->setToolTip(tr("These suppression files were used in the last memory analyzer run."));
+}
+
+void MemcheckTool::settingsDestroyed(QObject *settings)
+{
+ QTC_ASSERT(m_settings == settings, return);
+ m_settings = AnalyzerGlobalSettings::instance();
+}
+
+void MemcheckTool::maybeActiveRunConfigurationChanged()
+{
+ AnalyzerSettings *settings = 0;
+ ProjectExplorer::ProjectExplorerPlugin *pe = ProjectExplorer::ProjectExplorerPlugin::instance();
+ if (ProjectExplorer::Project *project = pe->startupProject()) {
+ if (ProjectExplorer::Target *target = project->activeTarget()) {
+ if (ProjectExplorer::RunConfiguration *rc = target->activeRunConfiguration()) {
+ settings = rc->extraAspect<AnalyzerProjectSettings>();
+ }
+ }
+ }
+
+ if (!settings) // fallback to global settings
+ settings = AnalyzerGlobalSettings::instance();
+
+ if (m_settings == settings)
+ return;
+
+ // disconnect old settings class if any
+ if (m_settings) {
+ m_settings->disconnect(this);
+ m_settings->disconnect(m_errorProxyModel);
+ }
+
+ // now make the new settings current, update and connect input widgets
+ m_settings = settings;
+ QTC_ASSERT(m_settings, return);
+
+ connect(m_settings, SIGNAL(destroyed(QObject *)), SLOT(settingsDestroyed(QObject *)));
+
+ AbstractMemcheckSettings *memcheckSettings = m_settings->subConfig<AbstractMemcheckSettings>();
+ QTC_ASSERT(memcheckSettings, return);
+
+ foreach (QAction *action, m_errorFilterActions) {
+ bool contained = true;
+ foreach (const QVariant &v, action->data().toList()) {
+ bool ok;
+ int kind = v.toInt(&ok);
+ if (ok && !memcheckSettings->visibleErrorKinds().contains(kind))
+ contained = false;
+ }
+ action->setChecked(contained);
+ }
+
+ m_filterProjectAction->setChecked(!memcheckSettings->filterExternalIssues());
+
+ m_errorView->settingsChanged(m_settings);
+
+ connect(memcheckSettings, SIGNAL(visibleErrorKindsChanged(QList<int>)),
+ m_errorProxyModel, SLOT(setAcceptedKinds(QList<int>)));
+ m_errorProxyModel->setAcceptedKinds(memcheckSettings->visibleErrorKinds());
+
+ connect(memcheckSettings, SIGNAL(filterExternalIssuesChanged(bool)),
+ m_errorProxyModel, SLOT(setFilterExternalIssues(bool)));
+ m_errorProxyModel->setFilterExternalIssues(memcheckSettings->filterExternalIssues());
+}
+
+QString MemcheckTool::id() const
+{
+ return "Memcheck";
+}
+
+QString MemcheckTool::displayName() const
+{
+ return tr("Analyze Memory");
+}
+
+IAnalyzerTool::ToolMode MemcheckTool::mode() const
+{
+ return DebugMode;
+}
+
+class FrameFinder : public ErrorListModel::RelevantFrameFinder
+{
+public:
+ Frame findRelevant(const Error &error) const
+ {
+ const QVector<Stack> stacks = error.stacks();
+ if (stacks.isEmpty())
+ return Frame();
+ const Stack &stack = stacks[0];
+ const QVector<Frame> frames = stack.frames();
+ if (frames.isEmpty())
+ return Frame();
+
+ //find the first frame belonging to the project
+ if (!m_projectFiles.isEmpty()) {
+ foreach (const Frame &frame, frames) {
+ if (frame.directory().isEmpty() || frame.file().isEmpty())
+ continue;
+
+ //filepaths can contain "..", clean them:
+ const QString f = QFileInfo(frame.directory() + QLatin1Char('/') + frame.file()).absoluteFilePath();
+ if (m_projectFiles.contains(f))
+ return frame;
+ }
+ }
+
+ //if no frame belonging to the project was found, return the first one that is not malloc/new
+ foreach (const Frame &frame, frames) {
+ if (!frame.functionName().isEmpty() && frame.functionName() != QLatin1String("malloc")
+ && !frame.functionName().startsWith("operator new(") )
+ {
+ return frame;
+ }
+ }
+
+ //else fallback to the first frame
+ return frames.first();
+ }
+ void setFiles(const QStringList &files)
+ {
+ m_projectFiles = files;
+ }
+private:
+ QStringList m_projectFiles;
+};
+
+MemcheckErrorView *MemcheckTool::ensurePaneErrorView()
+{
+ if (!m_errorView) {
+ m_errorView = new MemcheckErrorView;
+ m_errorView->setObjectName(QLatin1String("MemcheckErrorView"));
+ m_errorView->setFrameStyle(QFrame::NoFrame);
+ m_errorView->setAttribute(Qt::WA_MacShowFocusRect, false);
+ m_errorModel = new ErrorListModel(m_errorView);
+ m_frameFinder = new Internal::FrameFinder;
+ m_errorModel->setRelevantFrameFinder(QSharedPointer<Internal::FrameFinder>(m_frameFinder));
+ m_errorProxyModel = new MemcheckErrorFilterProxyModel(m_errorView);
+ m_errorProxyModel->setSourceModel(m_errorModel);
+ m_errorProxyModel->setDynamicSortFilter(true);
+ m_errorView->setModel(m_errorProxyModel);
+ m_errorView->setSelectionMode(QAbstractItemView::ExtendedSelection);
+ // make m_errorView->selectionModel()->selectedRows() return something
+ m_errorView->setSelectionBehavior(QAbstractItemView::SelectRows);
+ m_errorView->setVerticalScrollMode(QAbstractItemView::ScrollPerPixel);
+ m_errorView->setAutoScroll(false);
+ m_errorView->setObjectName("Valgrind.MemcheckTool.ErrorView");
+ }
+ return m_errorView;
+}
+
+QWidget *MemcheckTool::createPaneToolBarWidget()
+{
+ QWidget *toolbarWidget = new QWidget;
+ toolbarWidget->setObjectName(QLatin1String("MemCheckToolBarWidget"));
+ QHBoxLayout *layout = new QHBoxLayout;
+ layout->setMargin(0);
+ layout->setSpacing(0);
+ // filter
+ QToolButton *filterButton = new QToolButton;
+ filterButton->setIcon(QIcon(Core::Constants::ICON_FILTER));
+ filterButton->setText(tr("Error Filter"));
+ filterButton->setPopupMode(QToolButton::InstantPopup);
+ QMenu *filterMenu = new QMenu(filterButton);
+ foreach (QAction *filterAction, m_errorFilterActions)
+ filterMenu->addAction(filterAction);
+ filterMenu->addSeparator();
+ filterMenu->addAction(m_filterProjectAction);
+ filterMenu->addAction(m_suppressionSeparator);
+ connect(filterMenu, SIGNAL(triggered(QAction *)), SLOT(updateErrorFilter()));
+ filterButton->setMenu(filterMenu);
+ layout->addWidget(filterButton);
+ layout->addStretch();
+ toolbarWidget->setLayout(layout);
+ return toolbarWidget;
+}
+
+void MemcheckTool::initialize()
+{
+ ensurePaneErrorView();
+ // register shortcuts
+ maybeActiveRunConfigurationChanged();
+}
+
+IAnalyzerEngine *MemcheckTool::createEngine(const AnalyzerStartParameters &sp,
+ ProjectExplorer::RunConfiguration *runConfiguration)
+{
+ m_frameFinder->setFiles(runConfiguration ? runConfiguration->target()->project()->files(ProjectExplorer::Project::AllFiles) : QStringList());
+
+ MemcheckEngine *engine = new MemcheckEngine(sp, runConfiguration);
+
+ connect(engine, SIGNAL(starting(const Analyzer::IAnalyzerEngine*)),
+ this, SLOT(engineStarting(const Analyzer::IAnalyzerEngine*)));
+ connect(engine, SIGNAL(parserError(Valgrind::XmlProtocol::Error)),
+ this, SLOT(parserError(Valgrind::XmlProtocol::Error)));
+ connect(engine, SIGNAL(internalParserError(QString)),
+ this, SLOT(internalParserError(QString)));
+ connect(engine, SIGNAL(finished()), this, SLOT(finished()));
+ AnalyzerManager::instance()->showStatusMessage(AnalyzerManager::msgToolStarted(displayName()));
+ return engine;
+}
+
+void MemcheckTool::engineStarting(const IAnalyzerEngine *engine)
+{
+ clearErrorView();
+
+ QString dir;
+ if (ProjectExplorer::RunConfiguration *rc = engine->runConfiguration())
+ dir = rc->target()->project()->projectDirectory() + QDir::separator();
+
+ const MemcheckEngine *mEngine = dynamic_cast<const MemcheckEngine *>(engine);
+ QTC_ASSERT(mEngine, return);
+ const QString name = QFileInfo(mEngine->executable()).fileName();
+
+ m_errorView->setDefaultSuppressionFile(dir + name + QLatin1String(".supp"));
+
+ QMenu *menu = filterMenu();
+ QTC_ASSERT(menu, return);
+ foreach (const QString &file, mEngine->suppressionFiles()) {
+ QAction *action = menu->addAction(QFileInfo(file).fileName());
+ action->setToolTip(file);
+ action->setData(file);
+ connect(action, SIGNAL(triggered(bool)),
+ this, SLOT(suppressionActionTriggered()));
+ m_suppressionActions << action;
+ }
+}
+
+QMenu *MemcheckTool::filterMenu() const
+{
+ QTC_ASSERT(m_suppressionSeparator, return 0);
+ foreach (QWidget *w, m_suppressionSeparator->associatedWidgets())
+ if (QMenu *menu = qobject_cast<QMenu *>(w))
+ return menu;
+ return 0;
+}
+
+void MemcheckTool::suppressionActionTriggered()
+{
+ QAction *action = qobject_cast<QAction *>(sender());
+ QTC_ASSERT(action, return);
+ const QString file = action->data().toString();
+ QTC_ASSERT(!file.isEmpty(), return);
+
+ TextEditor::BaseTextEditorWidget::openEditorAt(file, 0);
+}
+
+void MemcheckTool::parserError(const Valgrind::XmlProtocol::Error &error)
+{
+ m_errorModel->addError(error);
+}
+
+void MemcheckTool::internalParserError(const QString &errorString)
+{
+ QMessageBox::critical(m_errorView, tr("Internal Error"),
+ tr("Error occurred parsing valgrind output: %1").arg(errorString));
+}
+
+void MemcheckTool::clearErrorView()
+{
+ m_errorModel->clear();
+
+ qDeleteAll(m_suppressionActions);
+ m_suppressionActions.clear();
+ QTC_ASSERT(filterMenu()->actions().last() == m_suppressionSeparator, qt_noop());
+}
+
+void MemcheckTool::updateErrorFilter()
+{
+ QTC_ASSERT(m_settings, return);
+
+ AbstractMemcheckSettings *memcheckSettings = m_settings->subConfig<AbstractMemcheckSettings>();
+ QTC_ASSERT(memcheckSettings, return);
+ memcheckSettings->setFilterExternalIssues(!m_filterProjectAction->isChecked());
+
+ QList<int> errorKinds;
+ foreach (QAction *a, m_errorFilterActions) {
+ if (!a->isChecked())
+ continue;
+ foreach (const QVariant &v, a->data().toList()) {
+ bool ok;
+ int kind = v.toInt(&ok);
+ if (ok)
+ errorKinds << kind;
+ }
+ }
+ memcheckSettings->setVisibleErrorKinds(errorKinds);
+}
+
+IAnalyzerOutputPaneAdapter *MemcheckTool::outputPaneAdapter()
+{
+ if (!m_outputPaneAdapter)
+ m_outputPaneAdapter = new MemCheckOutputPaneAdapter(this);
+ return m_outputPaneAdapter;
+}
+
+void MemcheckTool::finished()
+{
+ const QString msg = AnalyzerManager::msgToolFinished(displayName(), m_errorModel->rowCount());
+ AnalyzerManager::instance()->showStatusMessage(msg);
+}
+
+bool MemcheckTool::canRunRemotely() const
+{
+ return true;
+}
+
+} // namespace Internal
+} // namespace Valgrind