OSDN Git Service

e6d0f4ddd74ec4d27ddb544c73df233876baf49a
[qt-creator-jp/qt-creator-jp.git] / src / plugins / memcheck / memchecktool.cpp
1 /**************************************************************************
2 **
3 ** This file is part of Qt Creator
4 **
5 ** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies).
6 **
7 ** Author: Nicolas Arnaud-Cormos, KDAB (nicolas.arnaud-cormos@kdab.com)
8 **
9 ** Contact: Nokia Corporation (qt-info@nokia.com)
10 **
11 ** No Commercial Usage
12 **
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
16 ** this package.
17 **
18 ** GNU Lesser General Public License Usage
19 **
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.
26 **
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.
30 **
31 ** If you have questions regarding the use of this file, please contact
32 ** Nokia at qt-info@nokia.com.
33 **
34 **************************************************************************/
35
36 #include "memchecktool.h"
37 #include "memcheckengine.h"
38 #include "memcheckerrorview.h"
39 #include "memchecksettings.h"
40
41 #include <analyzerbase/analyzermanager.h>
42 #include <analyzerbase/analyzerconstants.h>
43
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>
50
51 #include <valgrindtoolbase/valgrindsettings.h>
52
53 #include <extensionsystem/iplugin.h>
54 #include <extensionsystem/pluginmanager.h>
55
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>
62
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>
69
70 #include <texteditor/basetexteditor.h>
71
72 #include <utils/fancymainwindow.h>
73 #include <utils/styledbar.h>
74 #include <utils/qtcassert.h>
75
76 #include <QtCore/QString>
77 #include <QtCore/QLatin1String>
78 #include <QtCore/QFileInfo>
79 #include <QtCore/QFile>
80 #include <QtCore/QDir>
81
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>
93
94 using namespace Valgrind::XmlProtocol;
95
96 namespace Analyzer {
97 namespace Internal {
98
99 // Adapter for output pane.
100 class MemCheckOutputPaneAdapter : public Analyzer::ListItemViewOutputPaneAdapter
101 {
102 public:
103     explicit MemCheckOutputPaneAdapter(MemcheckTool *mct) :
104         ListItemViewOutputPaneAdapter(mct), m_tool(mct) {}
105
106     virtual QWidget *toolBarWidget() { return m_tool->createPaneToolBarWidget(); }
107     virtual void clearContents() { m_tool->clearErrorView(); }
108
109 protected:
110     virtual QAbstractItemView *createItemView() { return m_tool->ensurePaneErrorView(); }
111
112 private:
113     MemcheckTool *m_tool;
114 };
115
116 // ---------------------------- MemcheckErrorFilterProxyModel
117 MemcheckErrorFilterProxyModel::MemcheckErrorFilterProxyModel(QObject *parent)
118     : QSortFilterProxyModel(parent),
119       m_filterExternalIssues(false)
120 {
121 }
122
123 void MemcheckErrorFilterProxyModel::setAcceptedKinds(const QList<int> &acceptedKinds)
124 {
125     if (m_acceptedKinds != acceptedKinds) {
126         m_acceptedKinds = acceptedKinds;
127         invalidate();
128     }
129 }
130
131 void MemcheckErrorFilterProxyModel::setFilterExternalIssues(bool filter)
132 {
133     if (m_filterExternalIssues != filter) {
134         m_filterExternalIssues = filter;
135         invalidate();
136     }
137 }
138
139 bool MemcheckErrorFilterProxyModel::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const
140 {
141     // we only deal with toplevel items
142     if (sourceParent.isValid())
143         return true;
144
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
147     // parent.
148     QAbstractItemModel *model = sourceModel();
149     QModelIndex sourceIndex = model->index(sourceRow, filterKeyColumn(), sourceParent);
150     if (!sourceIndex.isValid())
151         return true;
152
153     const Error error = sourceIndex.data(ErrorListModel::ErrorRole).value<Error>();
154
155     // filter on kind
156     if (!m_acceptedKinds.contains(error.kind()))
157         return false;
158
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();
171                 }
172             }
173         }
174
175         const QVector< Frame > frames = error.stacks().first().frames();
176
177         const int framesToLookAt = qMin(6, frames.size());
178
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)) {
184                     inProject = true;
185                     break;
186                 }
187             }
188         }
189         if (!inProject)
190             return false;
191     }
192
193     return true;
194 }
195
196 static void initKindFilterAction(QAction *action, const QList<int> &kinds)
197 {
198     action->setCheckable(true);
199     QVariantList data;
200     foreach (int kind, kinds)
201         data << kind;
202     action->setData(data);
203 }
204
205 MemcheckTool::MemcheckTool(QObject *parent) :
206     Analyzer::IAnalyzerTool(parent),
207     m_settings(0),
208     m_errorModel(0),
209     m_errorProxyModel(0),
210     m_errorView(0),
211     m_filterProjectAction(new QAction(tr("External Errors"), this)),
212     m_suppressionSeparator(new QAction(tr("Suppressions"), this)),
213     m_outputPaneAdapter(0)
214 {
215     setObjectName(QLatin1String("MemcheckTool"));
216     connect(ProjectExplorer::ProjectExplorerPlugin::instance(),
217             SIGNAL(updateRunActions()), SLOT(maybeActiveRunConfigurationChanged()));
218
219     QAction *a = new QAction(tr("Definite Memory Leaks"), this);
220     initKindFilterAction(a, QList<int>() << Leak_DefinitelyLost << Leak_IndirectlyLost);
221     m_errorFilterActions << a;
222
223     a = new QAction(tr("Possible Memory Leaks"), this);
224     initKindFilterAction(a, QList<int>() << Leak_PossiblyLost << Leak_StillReachable);
225     m_errorFilterActions << a;
226
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;
232
233     a = new QAction(tr("Invalid Frees"), this);
234     initKindFilterAction(a, QList<int>() << InvalidFree << MismatchedFree);
235     m_errorFilterActions << a;
236
237     m_filterProjectAction->setToolTip(tr("Show issues originating outside currently opened projects."));
238     m_filterProjectAction->setCheckable(true);
239
240     m_suppressionSeparator->setSeparator(true);
241     m_suppressionSeparator->setToolTip(tr("These suppression files were used in the last memory analyzer run."));
242 }
243
244 void MemcheckTool::settingsDestroyed(QObject *settings)
245 {
246     Q_ASSERT(m_settings == settings);
247     m_settings = AnalyzerGlobalSettings::instance();
248 }
249
250 void MemcheckTool::maybeActiveRunConfigurationChanged()
251 {
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>();
258             }
259         }
260     }
261
262     if (!settings) // fallback to global settings
263         settings = AnalyzerGlobalSettings::instance();
264
265     if (m_settings == settings)
266         return;
267
268     // disconnect old settings class if any
269     if (m_settings) {
270         m_settings->disconnect(this);
271         m_settings->disconnect(m_errorProxyModel);
272     }
273
274     // now make the new settings current, update and connect input widgets
275     m_settings = settings;
276     QTC_ASSERT(m_settings, return);
277
278     connect(m_settings, SIGNAL(destroyed(QObject *)), SLOT(settingsDestroyed(QObject *)));
279
280     AbstractMemcheckSettings *memcheckSettings = m_settings->subConfig<AbstractMemcheckSettings>();
281     QTC_ASSERT(memcheckSettings, return);
282
283     foreach (QAction *action, m_errorFilterActions) {
284         bool contained = true;
285         foreach (const QVariant &v, action->data().toList()) {
286             bool ok;
287             int kind = v.toInt(&ok);
288             if (ok && !memcheckSettings->visibleErrorKinds().contains(kind))
289                 contained = false;
290         }
291         action->setChecked(contained);
292     }
293
294     m_filterProjectAction->setChecked(!memcheckSettings->filterExternalIssues());
295
296     m_errorView->settingsChanged(m_settings);
297
298     connect(memcheckSettings, SIGNAL(visibleErrorKindsChanged(QList<int>)),
299             m_errorProxyModel, SLOT(setAcceptedKinds(QList<int>)));
300     m_errorProxyModel->setAcceptedKinds(memcheckSettings->visibleErrorKinds());
301
302     connect(memcheckSettings, SIGNAL(filterExternalIssuesChanged(bool)),
303             m_errorProxyModel, SLOT(setFilterExternalIssues(bool)));
304     m_errorProxyModel->setFilterExternalIssues(memcheckSettings->filterExternalIssues());
305 }
306
307 QString MemcheckTool::id() const
308 {
309     return "Memcheck";
310 }
311
312 QString MemcheckTool::displayName() const
313 {
314     return tr("Analyze Memory");
315 }
316
317 IAnalyzerTool::ToolMode MemcheckTool::mode() const
318 {
319     return DebugMode;
320 }
321
322 class FrameFinder : public ErrorListModel::RelevantFrameFinder {
323 public:
324     Frame findRelevant(const Error &error) const {
325         const QVector<Stack> stacks = error.stacks();
326         if (stacks.isEmpty())
327             return Frame();
328         const Stack &stack = stacks[0];
329         const QVector<Frame> frames = stack.frames();
330         if (frames.isEmpty())
331             return Frame();
332
333         //find the first frame belonging to the project
334         foreach(const Frame &frame, frames) {
335             if (frame.directory().isEmpty() || frame.file().isEmpty())
336                 continue;
337
338             //filepaths can contain "..", clean them:
339             const QString f = QFileInfo(frame.directory() + QLatin1Char('/') + frame.file()).absoluteFilePath();
340             if (m_projectFiles.contains(f))
341                 return frame;
342         }
343
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(") )
348             {
349                 return frame;
350             }
351         }
352
353         //else fallback to the first frame
354         return frames.first();
355     }
356     void setFiles(const QStringList &files)
357     {
358         m_projectFiles = files;
359     }
360 private:
361     QStringList m_projectFiles;
362 };
363
364 MemcheckErrorView *MemcheckTool::ensurePaneErrorView()
365 {
366     if (!m_errorView) {
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");
384     }
385     return m_errorView;
386 }
387
388 QWidget *MemcheckTool::createPaneToolBarWidget()
389 {
390     QWidget *toolbarWidget = new QWidget;
391     toolbarWidget->setObjectName(QLatin1String("MemCheckToolBarWidget"));
392     QHBoxLayout *layout = new QHBoxLayout;
393     layout->setMargin(0);
394     layout->setSpacing(0);
395     // filter
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;
412 }
413
414 void MemcheckTool::initialize(ExtensionSystem::IPlugin */*plugin*/)
415 {
416     ensurePaneErrorView();
417     // register shortcuts
418     maybeActiveRunConfigurationChanged();
419 }
420
421 IAnalyzerEngine *MemcheckTool::createEngine(ProjectExplorer::RunConfiguration *runConfiguration)
422 {
423     m_frameFinder->setFiles(runConfiguration->target()->project()->files(ProjectExplorer::Project::AllFiles));
424
425     MemcheckEngine *engine = new MemcheckEngine(runConfiguration);
426
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()));
435     return engine;
436 }
437
438 void MemcheckTool::engineStarting(const IAnalyzerEngine *engine)
439 {
440     clearErrorView();
441
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();
446
447     m_errorView->setDefaultSuppressionFile(dir + QDir::separator() + name + QLatin1String(".supp"));
448
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;
458     }
459 }
460
461 QMenu *MemcheckTool::filterMenu() const
462 {
463     QTC_ASSERT(m_suppressionSeparator, return 0; )
464     foreach (QWidget *w, m_suppressionSeparator->associatedWidgets())
465         if (QMenu *menu = qobject_cast<QMenu*>(w))
466             return menu;
467     return 0;
468 }
469
470 void MemcheckTool::suppressionActionTriggered()
471 {
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);
476
477     TextEditor::BaseTextEditorWidget::openEditorAt(file, 0);
478 }
479
480 void MemcheckTool::parserError(const Valgrind::XmlProtocol::Error &error)
481 {
482     m_errorModel->addError(error);
483 }
484
485 void MemcheckTool::internalParserError(const QString &errorString)
486 {
487     QMessageBox::critical(m_errorView, tr("Internal Error"), tr("Error occurred parsing valgrind output: %1").arg(errorString));
488 }
489
490 void MemcheckTool::clearErrorView()
491 {
492     m_errorModel->clear();
493
494     qDeleteAll(m_suppressionActions);
495     m_suppressionActions.clear();
496     QTC_ASSERT(filterMenu()->actions().last() == m_suppressionSeparator, qt_noop());
497 }
498
499 void MemcheckTool::updateErrorFilter()
500 {
501     QTC_ASSERT(m_settings, return);
502
503     AbstractMemcheckSettings *memcheckSettings = m_settings->subConfig<AbstractMemcheckSettings>();
504     QTC_ASSERT(memcheckSettings, return);
505     memcheckSettings->setFilterExternalIssues(!m_filterProjectAction->isChecked());
506
507     QList<int> errorKinds;
508     foreach (QAction *a, m_errorFilterActions) {
509         if (!a->isChecked())
510             continue;
511         foreach (const QVariant &v, a->data().toList()) {
512             bool ok;
513             int kind = v.toInt(&ok);
514             if (ok)
515                 errorKinds << kind;
516         }
517     }
518     memcheckSettings->setVisibleErrorKinds(errorKinds);
519 }
520
521 IAnalyzerOutputPaneAdapter *MemcheckTool::outputPaneAdapter()
522 {
523     if (!m_outputPaneAdapter)
524         m_outputPaneAdapter = new MemCheckOutputPaneAdapter(this);
525     return m_outputPaneAdapter;
526 }
527
528 void MemcheckTool::finished()
529 {
530     const QString msg = AnalyzerManager::msgToolFinished(displayName(), m_errorModel->rowCount());
531     AnalyzerManager::instance()->showStatusMessage(msg);
532 }
533
534 } // namespace Internal
535 } // namespace Analyzer