1 /**************************************************************************
3 ** This file is part of Qt Creator
5 ** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies).
7 ** Author: Nicolas Arnaud-Cormos, KDAB (nicolas.arnaud-cormos@kdab.com)
9 ** Contact: Nokia Corporation (qt-info@nokia.com)
11 ** No Commercial Usage
13 ** This file contains pre-release code and may not be distributed.
14 ** You may use this file in accordance with the terms and conditions
15 ** contained in the Technology Preview License Agreement accompanying
18 ** GNU Lesser General Public License Usage
20 ** Alternatively, this file may be used under the terms of the GNU Lesser
21 ** General Public License version 2.1 as published by the Free Software
22 ** Foundation and appearing in the file LICENSE.LGPL included in the
23 ** packaging of this file. Please review the following information to
24 ** ensure the GNU Lesser General Public License version 2.1 requirements
25 ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
27 ** In addition, as a special exception, Nokia gives you certain additional
28 ** rights. These rights are described in the Nokia Qt LGPL Exception
29 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
31 ** If you have questions regarding the use of this file, please contact
32 ** Nokia at qt-info@nokia.com.
34 **************************************************************************/
36 #include "memchecktool.h"
37 #include "memcheckengine.h"
38 #include "memcheckerrorview.h"
39 #include "memchecksettings.h"
41 #include <analyzerbase/analyzermanager.h>
42 #include <analyzerbase/analyzerconstants.h>
44 #include <valgrind/xmlprotocol/errorlistmodel.h>
45 #include <valgrind/xmlprotocol/stackmodel.h>
46 #include <valgrind/xmlprotocol/error.h>
47 #include <valgrind/xmlprotocol/frame.h>
48 #include <valgrind/xmlprotocol/stack.h>
49 #include <valgrind/xmlprotocol/suppression.h>
51 #include <valgrindtoolbase/valgrindsettings.h>
53 #include <extensionsystem/iplugin.h>
54 #include <extensionsystem/pluginmanager.h>
56 #include <projectexplorer/projectexplorer.h>
57 #include <projectexplorer/project.h>
58 #include <projectexplorer/runconfiguration.h>
59 #include <projectexplorer/target.h>
60 #include <projectexplorer/session.h>
61 #include <projectexplorer/buildconfiguration.h>
63 #include <coreplugin/coreconstants.h>
64 #include <coreplugin/icore.h>
65 #include <coreplugin/actionmanager/actionmanager.h>
66 #include <coreplugin/actionmanager/actioncontainer.h>
67 #include <coreplugin/actionmanager/command.h>
68 #include <coreplugin/uniqueidmanager.h>
70 #include <texteditor/basetexteditor.h>
72 #include <utils/fancymainwindow.h>
73 #include <utils/styledbar.h>
74 #include <utils/qtcassert.h>
76 #include <QtCore/QString>
77 #include <QtCore/QLatin1String>
78 #include <QtCore/QFileInfo>
79 #include <QtCore/QFile>
80 #include <QtCore/QDir>
82 #include <QtGui/QDockWidget>
83 #include <QtGui/QHBoxLayout>
84 #include <QtGui/QComboBox>
85 #include <QtGui/QLabel>
86 #include <QtGui/QSpinBox>
87 #include <QtGui/QAction>
88 #include <QtGui/QMenu>
89 #include <QtGui/QMessageBox>
90 #include <QtGui/QToolButton>
91 #include <QtGui/QCheckBox>
92 #include <utils/stylehelper.h>
94 using namespace Valgrind::XmlProtocol;
99 // Adapter for output pane.
100 class MemCheckOutputPaneAdapter : public Analyzer::ListItemViewOutputPaneAdapter
103 explicit MemCheckOutputPaneAdapter(MemcheckTool *mct) :
104 ListItemViewOutputPaneAdapter(mct), m_tool(mct) {}
106 virtual QWidget *toolBarWidget() { return m_tool->createPaneToolBarWidget(); }
107 virtual void clearContents() { m_tool->clearErrorView(); }
110 virtual QAbstractItemView *createItemView() { return m_tool->ensurePaneErrorView(); }
113 MemcheckTool *m_tool;
116 // ---------------------------- MemcheckErrorFilterProxyModel
117 MemcheckErrorFilterProxyModel::MemcheckErrorFilterProxyModel(QObject *parent)
118 : QSortFilterProxyModel(parent),
119 m_filterExternalIssues(false)
123 void MemcheckErrorFilterProxyModel::setAcceptedKinds(const QList<int> &acceptedKinds)
125 if (m_acceptedKinds != acceptedKinds) {
126 m_acceptedKinds = acceptedKinds;
131 void MemcheckErrorFilterProxyModel::setFilterExternalIssues(bool filter)
133 if (m_filterExternalIssues != filter) {
134 m_filterExternalIssues = filter;
139 bool MemcheckErrorFilterProxyModel::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const
141 // we only deal with toplevel items
142 if (sourceParent.isValid())
145 // because toplevel items have no parent, we can't use sourceParent to find them. we just use
146 // sourceParent as an invalid index, telling the model that the index we're looking for has no
148 QAbstractItemModel *model = sourceModel();
149 QModelIndex sourceIndex = model->index(sourceRow, filterKeyColumn(), sourceParent);
150 if (!sourceIndex.isValid())
153 const Error error = sourceIndex.data(ErrorListModel::ErrorRole).value<Error>();
156 if (!m_acceptedKinds.contains(error.kind()))
159 // filter non-project stuff
160 if (m_filterExternalIssues && !error.stacks().isEmpty()) {
161 // ALGORITHM: look at last five stack frames, if none of these is inside any open projects,
162 // assume this error was created by an external library
163 ProjectExplorer::SessionManager *session
164 = ProjectExplorer::ProjectExplorerPlugin::instance()->session();
165 QSet<QString> validFolders;
166 foreach(ProjectExplorer::Project *project, session->projects()) {
167 validFolders << project->projectDirectory();
168 foreach(ProjectExplorer::Target *target, project->targets()) {
169 foreach(ProjectExplorer::BuildConfiguration *config, target->buildConfigurations()) {
170 validFolders << config->buildDirectory();
175 const QVector< Frame > frames = error.stacks().first().frames();
177 const int framesToLookAt = qMin(6, frames.size());
179 bool inProject = false;
180 for ( int i = 0; i < framesToLookAt; ++i ) {
181 const Frame &frame = frames.at(i);
182 foreach(const QString &folder, validFolders) {
183 if (frame.object().startsWith(folder)) {
196 static void initKindFilterAction(QAction *action, const QList<int> &kinds)
198 action->setCheckable(true);
200 foreach (int kind, kinds)
202 action->setData(data);
205 MemcheckTool::MemcheckTool(QObject *parent) :
206 Analyzer::IAnalyzerTool(parent),
209 m_errorProxyModel(0),
211 m_filterProjectAction(new QAction(tr("External Errors"), this)),
212 m_suppressionSeparator(new QAction(tr("Suppressions"), this)),
213 m_outputPaneAdapter(0)
215 setObjectName(QLatin1String("MemcheckTool"));
216 connect(ProjectExplorer::ProjectExplorerPlugin::instance(),
217 SIGNAL(updateRunActions()), SLOT(maybeActiveRunConfigurationChanged()));
219 QAction *a = new QAction(tr("Definite Memory Leaks"), this);
220 initKindFilterAction(a, QList<int>() << Leak_DefinitelyLost << Leak_IndirectlyLost);
221 m_errorFilterActions << a;
223 a = new QAction(tr("Possible Memory Leaks"), this);
224 initKindFilterAction(a, QList<int>() << Leak_PossiblyLost << Leak_StillReachable);
225 m_errorFilterActions << a;
227 a = new QAction(tr("Use of Uninitialized Memory"), this);
228 initKindFilterAction(a, QList<int>() << InvalidRead << InvalidWrite << InvalidJump << Overlap
229 << InvalidMemPool << UninitCondition << UninitValue
230 << SyscallParam << ClientCheck);
231 m_errorFilterActions << a;
233 a = new QAction(tr("Invalid Frees"), this);
234 initKindFilterAction(a, QList<int>() << InvalidFree << MismatchedFree);
235 m_errorFilterActions << a;
237 m_filterProjectAction->setToolTip(tr("Show issues originating outside currently opened projects."));
238 m_filterProjectAction->setCheckable(true);
240 m_suppressionSeparator->setSeparator(true);
241 m_suppressionSeparator->setToolTip(tr("These suppression files were used in the last memory analyzer run."));
244 void MemcheckTool::settingsDestroyed(QObject *settings)
246 Q_ASSERT(m_settings == settings);
247 m_settings = AnalyzerGlobalSettings::instance();
250 void MemcheckTool::maybeActiveRunConfigurationChanged()
252 AnalyzerSettings *settings = 0;
253 ProjectExplorer::ProjectExplorerPlugin *pe = ProjectExplorer::ProjectExplorerPlugin::instance();
254 if (ProjectExplorer::Project *project = pe->startupProject()) {
255 if (ProjectExplorer::Target *target = project->activeTarget()) {
256 if (ProjectExplorer::RunConfiguration *rc = target->activeRunConfiguration()) {
257 settings = rc->extraAspect<AnalyzerProjectSettings>();
262 if (!settings) // fallback to global settings
263 settings = AnalyzerGlobalSettings::instance();
265 if (m_settings == settings)
268 // disconnect old settings class if any
270 m_settings->disconnect(this);
271 m_settings->disconnect(m_errorProxyModel);
274 // now make the new settings current, update and connect input widgets
275 m_settings = settings;
276 QTC_ASSERT(m_settings, return);
278 connect(m_settings, SIGNAL(destroyed(QObject *)), SLOT(settingsDestroyed(QObject *)));
280 AbstractMemcheckSettings *memcheckSettings = m_settings->subConfig<AbstractMemcheckSettings>();
281 QTC_ASSERT(memcheckSettings, return);
283 foreach (QAction *action, m_errorFilterActions) {
284 bool contained = true;
285 foreach (const QVariant &v, action->data().toList()) {
287 int kind = v.toInt(&ok);
288 if (ok && !memcheckSettings->visibleErrorKinds().contains(kind))
291 action->setChecked(contained);
294 m_filterProjectAction->setChecked(!memcheckSettings->filterExternalIssues());
296 m_errorView->settingsChanged(m_settings);
298 connect(memcheckSettings, SIGNAL(visibleErrorKindsChanged(QList<int>)),
299 m_errorProxyModel, SLOT(setAcceptedKinds(QList<int>)));
300 m_errorProxyModel->setAcceptedKinds(memcheckSettings->visibleErrorKinds());
302 connect(memcheckSettings, SIGNAL(filterExternalIssuesChanged(bool)),
303 m_errorProxyModel, SLOT(setFilterExternalIssues(bool)));
304 m_errorProxyModel->setFilterExternalIssues(memcheckSettings->filterExternalIssues());
307 QString MemcheckTool::id() const
312 QString MemcheckTool::displayName() const
314 return tr("Analyze Memory");
317 IAnalyzerTool::ToolMode MemcheckTool::mode() const
322 class FrameFinder : public ErrorListModel::RelevantFrameFinder {
324 Frame findRelevant(const Error &error) const {
325 const QVector<Stack> stacks = error.stacks();
326 if (stacks.isEmpty())
328 const Stack &stack = stacks[0];
329 const QVector<Frame> frames = stack.frames();
330 if (frames.isEmpty())
333 //find the first frame belonging to the project
334 foreach(const Frame &frame, frames) {
335 if (frame.directory().isEmpty() || frame.file().isEmpty())
338 //filepaths can contain "..", clean them:
339 const QString f = QFileInfo(frame.directory() + QLatin1Char('/') + frame.file()).absoluteFilePath();
340 if (m_projectFiles.contains(f))
344 //if no frame belonging to the project was found, return the first one that is not malloc/new
345 foreach(const Frame &frame, frames) {
346 if (!frame.functionName().isEmpty() && frame.functionName() != QLatin1String("malloc")
347 && !frame.functionName().startsWith("operator new(") )
353 //else fallback to the first frame
354 return frames.first();
356 void setFiles(const QStringList &files)
358 m_projectFiles = files;
361 QStringList m_projectFiles;
364 MemcheckErrorView *MemcheckTool::ensurePaneErrorView()
367 m_errorView = new MemcheckErrorView;
368 m_errorView->setObjectName(QLatin1String("MemcheckErrorView"));
369 m_errorView->setFrameStyle(QFrame::NoFrame);
370 m_errorView->setAttribute(Qt::WA_MacShowFocusRect, false);
371 m_errorModel = new ErrorListModel(m_errorView);
372 m_frameFinder = new Internal::FrameFinder;
373 m_errorModel->setRelevantFrameFinder(QSharedPointer<Internal::FrameFinder>(m_frameFinder));
374 m_errorProxyModel = new MemcheckErrorFilterProxyModel(m_errorView);
375 m_errorProxyModel->setSourceModel(m_errorModel);
376 m_errorProxyModel->setDynamicSortFilter(true);
377 m_errorView->setModel(m_errorProxyModel);
378 m_errorView->setSelectionMode(QAbstractItemView::ExtendedSelection);
379 // make m_errorView->selectionModel()->selectedRows() return something
380 m_errorView->setSelectionBehavior(QAbstractItemView::SelectRows);
381 m_errorView->setVerticalScrollMode(QAbstractItemView::ScrollPerPixel);
382 m_errorView->setAutoScroll(false);
383 m_errorView->setObjectName("Valgrind.MemcheckTool.ErrorView");
388 QWidget *MemcheckTool::createPaneToolBarWidget()
390 QWidget *toolbarWidget = new QWidget;
391 toolbarWidget->setObjectName(QLatin1String("MemCheckToolBarWidget"));
392 QHBoxLayout *layout = new QHBoxLayout;
393 layout->setMargin(0);
394 layout->setSpacing(0);
396 QToolButton *filterButton = new QToolButton;
397 filterButton->setIcon(QIcon(Core::Constants::ICON_FILTER));
398 filterButton->setText(tr("Error Filter"));
399 filterButton->setPopupMode(QToolButton::InstantPopup);
400 QMenu *filterMenu = new QMenu(filterButton);
401 foreach (QAction *filterAction, m_errorFilterActions)
402 filterMenu->addAction(filterAction);
403 filterMenu->addSeparator();
404 filterMenu->addAction(m_filterProjectAction);
405 filterMenu->addAction(m_suppressionSeparator);
406 connect(filterMenu, SIGNAL(triggered(QAction *)), SLOT(updateErrorFilter()));
407 filterButton->setMenu(filterMenu);
408 layout->addWidget(filterButton);
409 layout->addStretch();
410 toolbarWidget->setLayout(layout);
411 return toolbarWidget;
414 void MemcheckTool::initialize(ExtensionSystem::IPlugin */*plugin*/)
416 ensurePaneErrorView();
417 // register shortcuts
418 maybeActiveRunConfigurationChanged();
421 IAnalyzerEngine *MemcheckTool::createEngine(ProjectExplorer::RunConfiguration *runConfiguration)
423 m_frameFinder->setFiles(runConfiguration->target()->project()->files(ProjectExplorer::Project::AllFiles));
425 MemcheckEngine *engine = new MemcheckEngine(runConfiguration);
427 connect(engine, SIGNAL(starting(const IAnalyzerEngine*)),
428 this, SLOT(engineStarting(const IAnalyzerEngine*)));
429 connect(engine, SIGNAL(parserError(Valgrind::XmlProtocol::Error)),
430 this, SLOT(parserError(Valgrind::XmlProtocol::Error)));
431 connect(engine, SIGNAL(internalParserError(QString)),
432 this, SLOT(internalParserError(QString)));
433 connect(engine, SIGNAL(finished()), this, SLOT(finished()));
434 AnalyzerManager::instance()->showStatusMessage(AnalyzerManager::msgToolStarted(displayName()));
438 void MemcheckTool::engineStarting(const IAnalyzerEngine *engine)
442 const QString dir = engine->runConfiguration()->target()->project()->projectDirectory();
443 const MemcheckEngine *mEngine = dynamic_cast<const MemcheckEngine*>(engine);
444 QTC_ASSERT(mEngine, return);
445 const QString name = QFileInfo(mEngine->executable()).fileName();
447 m_errorView->setDefaultSuppressionFile(dir + QDir::separator() + name + QLatin1String(".supp"));
449 QMenu *menu = filterMenu();
450 QTC_ASSERT(menu, return);
451 foreach(const QString &file, mEngine->suppressionFiles()) {
452 QAction *action = menu->addAction(QFileInfo(file).fileName());
453 action->setToolTip(file);
454 action->setData(file);
455 connect(action, SIGNAL(triggered(bool)),
456 this, SLOT(suppressionActionTriggered()));
457 m_suppressionActions << action;
461 QMenu *MemcheckTool::filterMenu() const
463 QTC_ASSERT(m_suppressionSeparator, return 0; )
464 foreach (QWidget *w, m_suppressionSeparator->associatedWidgets())
465 if (QMenu *menu = qobject_cast<QMenu*>(w))
470 void MemcheckTool::suppressionActionTriggered()
472 QAction *action = qobject_cast<QAction*>(sender());
473 QTC_ASSERT(action, return);
474 const QString file = action->data().toString();
475 QTC_ASSERT(!file.isEmpty(), return);
477 TextEditor::BaseTextEditorWidget::openEditorAt(file, 0);
480 void MemcheckTool::parserError(const Valgrind::XmlProtocol::Error &error)
482 m_errorModel->addError(error);
485 void MemcheckTool::internalParserError(const QString &errorString)
487 QMessageBox::critical(m_errorView, tr("Internal Error"), tr("Error occurred parsing valgrind output: %1").arg(errorString));
490 void MemcheckTool::clearErrorView()
492 m_errorModel->clear();
494 qDeleteAll(m_suppressionActions);
495 m_suppressionActions.clear();
496 QTC_ASSERT(filterMenu()->actions().last() == m_suppressionSeparator, qt_noop());
499 void MemcheckTool::updateErrorFilter()
501 QTC_ASSERT(m_settings, return);
503 AbstractMemcheckSettings *memcheckSettings = m_settings->subConfig<AbstractMemcheckSettings>();
504 QTC_ASSERT(memcheckSettings, return);
505 memcheckSettings->setFilterExternalIssues(!m_filterProjectAction->isChecked());
507 QList<int> errorKinds;
508 foreach (QAction *a, m_errorFilterActions) {
511 foreach (const QVariant &v, a->data().toList()) {
513 int kind = v.toInt(&ok);
518 memcheckSettings->setVisibleErrorKinds(errorKinds);
521 IAnalyzerOutputPaneAdapter *MemcheckTool::outputPaneAdapter()
523 if (!m_outputPaneAdapter)
524 m_outputPaneAdapter = new MemCheckOutputPaneAdapter(this);
525 return m_outputPaneAdapter;
528 void MemcheckTool::finished()
530 const QString msg = AnalyzerManager::msgToolFinished(displayName(), m_errorModel->rowCount());
531 AnalyzerManager::instance()->showStatusMessage(msg);
534 } // namespace Internal
535 } // namespace Analyzer