memchecktool.cpp 18.5 KB
Newer Older
hjk's avatar
hjk committed
1
/****************************************************************************
2
**
3
** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
hjk's avatar
hjk committed
4
** Contact: http://www.qt-project.org/legal
5 6
** Author: Nicolas Arnaud-Cormos, KDAB (nicolas.arnaud-cormos@kdab.com)
**
hjk's avatar
hjk committed
7
** This file is part of Qt Creator.
8
**
hjk's avatar
hjk committed
9 10 11 12 13 14 15
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and Digia.  For licensing terms and
** conditions see http://qt.digia.com/licensing.  For further information
** use the contact form at http://qt.digia.com/contact-us.
16 17
**
** GNU Lesser General Public License Usage
hjk's avatar
hjk committed
18 19 20 21 22 23 24 25 26
** Alternatively, 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, Digia gives you certain additional
** rights.  These rights are described in the Digia Qt LGPL Exception
27 28
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
hjk's avatar
hjk committed
29
****************************************************************************/
30 31 32 33

#include "memchecktool.h"
#include "memcheckengine.h"
#include "memcheckerrorview.h"
34
#include "valgrindsettings.h"
hjk's avatar
hjk committed
35
#include "valgrindplugin.h"
36 37 38 39

#include <analyzerbase/analyzermanager.h>
#include <analyzerbase/analyzerconstants.h>

hjk's avatar
hjk committed
40
#include <valgrind/valgrindsettings.h>
41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58
#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/actionmanager/actioncontainer.h>
59
#include <coreplugin/actionmanager/actionmanager.h>
60
#include <coreplugin/actionmanager/command.h>
61 62 63
#include <coreplugin/coreconstants.h>
#include <coreplugin/editormanager/editormanager.h>
#include <coreplugin/icore.h>
64
#include <coreplugin/id.h>
65 66 67 68 69

#include <utils/fancymainwindow.h>
#include <utils/styledbar.h>
#include <utils/qtcassert.h>

70 71 72 73 74
#include <QString>
#include <QLatin1String>
#include <QFileInfo>
#include <QFile>
#include <QDir>
75

76 77 78 79 80 81 82 83 84 85
#include <QDockWidget>
#include <QHBoxLayout>
#include <QComboBox>
#include <QLabel>
#include <QSpinBox>
#include <QAction>
#include <QMenu>
#include <QMessageBox>
#include <QToolButton>
#include <QCheckBox>
86 87
#include <utils/stylehelper.h>

88
using namespace Analyzer;
hjk's avatar
hjk committed
89
using namespace ProjectExplorer;
90 91
using namespace Valgrind::XmlProtocol;

92
namespace Valgrind {
Friedemann Kleint's avatar
Friedemann Kleint committed
93 94 95
namespace Internal {

// ---------------------------- MemcheckErrorFilterProxyModel
96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119
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
{
hjk's avatar
hjk committed
120
    // We only deal with toplevel items.
121 122 123
    if (sourceParent.isValid())
        return true;

hjk's avatar
hjk committed
124
    // Because toplevel items have no parent, we can't use sourceParent to find them. we just use
125 126 127 128 129 130 131 132 133
    // 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>();

hjk's avatar
hjk committed
134
    // Filter on kind
135 136 137
    if (!m_acceptedKinds.contains(error.kind()))
        return false;

hjk's avatar
hjk committed
138
    // Filter non-project stuff
139 140 141
    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
hjk's avatar
hjk committed
142
        SessionManager *session = ProjectExplorerPlugin::instance()->session();
143
        QSet<QString> validFolders;
hjk's avatar
hjk committed
144
        foreach (Project *project, session->projects()) {
145
            validFolders << project->projectDirectory();
hjk's avatar
hjk committed
146 147
            foreach (Target *target, project->targets()) {
                foreach (BuildConfiguration *config, target->buildConfigurations())
148 149 150 151 152 153 154 155 156
                    validFolders << config->buildDirectory();
            }
        }

        const QVector< Frame > frames = error.stacks().first().frames();

        const int framesToLookAt = qMin(6, frames.size());

        bool inProject = false;
hjk's avatar
hjk committed
157
        for (int i = 0; i < framesToLookAt; ++i) {
158
            const Frame &frame = frames.at(i);
159
            foreach (const QString &folder, validFolders) {
160 161 162 163 164 165 166 167 168 169 170 171 172
                if (frame.object().startsWith(folder)) {
                    inProject = true;
                    break;
                }
            }
        }
        if (!inProject)
            return false;
    }

    return true;
}

Friedemann Kleint's avatar
Friedemann Kleint committed
173 174 175 176 177 178 179 180 181
static void initKindFilterAction(QAction *action, const QList<int> &kinds)
{
    action->setCheckable(true);
    QVariantList data;
    foreach (int kind, kinds)
        data << kind;
    action->setData(data);
}

182
MemcheckTool::MemcheckTool(QObject *parent)
183
  : ValgrindTool(parent)
184
{
185 186 187 188 189
    m_settings = 0;
    m_errorModel = 0;
    m_errorProxyModel = 0;
    m_errorView = 0;
    m_filterMenu = 0;
Friedemann Kleint's avatar
Friedemann Kleint committed
190
    setObjectName(QLatin1String("MemcheckTool"));
191 192

    m_filterProjectAction = new QAction(tr("External Errors"), this);
193 194
    m_filterProjectAction->setToolTip(
        tr("Show issues originating outside currently opened projects."));
195 196 197 198
    m_filterProjectAction->setCheckable(true);

    m_suppressionSeparator = new QAction(tr("Suppressions"), this);
    m_suppressionSeparator->setSeparator(true);
199 200
    m_suppressionSeparator->setToolTip(
        tr("These suppression files were used in the last memory analyzer run."));
Friedemann Kleint's avatar
Friedemann Kleint committed
201 202 203

    QAction *a = new QAction(tr("Definite Memory Leaks"), this);
    initKindFilterAction(a, QList<int>() << Leak_DefinitelyLost << Leak_IndirectlyLost);
204
    m_errorFilterActions.append(a);
Friedemann Kleint's avatar
Friedemann Kleint committed
205 206 207

    a = new QAction(tr("Possible Memory Leaks"), this);
    initKindFilterAction(a, QList<int>() << Leak_PossiblyLost << Leak_StillReachable);
208
    m_errorFilterActions.append(a);
Friedemann Kleint's avatar
Friedemann Kleint committed
209 210 211 212 213

    a = new QAction(tr("Use of Uninitialized Memory"), this);
    initKindFilterAction(a, QList<int>() << InvalidRead << InvalidWrite << InvalidJump << Overlap
                         << InvalidMemPool << UninitCondition << UninitValue
                         << SyscallParam << ClientCheck);
214
    m_errorFilterActions.append(a);
Friedemann Kleint's avatar
Friedemann Kleint committed
215

216
    a = new QAction(tr("Invalid Calls to \"free()\""), this);
Friedemann Kleint's avatar
Friedemann Kleint committed
217
    initKindFilterAction(a, QList<int>() << InvalidFree << MismatchedFree);
218
    m_errorFilterActions.append(a);
219 220 221 222
}

void MemcheckTool::settingsDestroyed(QObject *settings)
{
223
    QTC_ASSERT(m_settings == settings, return);
hjk's avatar
hjk committed
224
    m_settings = ValgrindPlugin::globalSettings();
225 226 227 228
}

void MemcheckTool::maybeActiveRunConfigurationChanged()
{
hjk's avatar
hjk committed
229
    ValgrindBaseSettings *settings = 0;
hjk's avatar
hjk committed
230 231 232 233
    ProjectExplorerPlugin *pe = ProjectExplorerPlugin::instance();
    if (Project *project = pe->startupProject())
        if (Target *target = project->activeTarget())
            if (RunConfiguration *rc = target->activeRunConfiguration())
hjk's avatar
hjk committed
234
                if (AnalyzerRunConfigurationAspect *aspect = rc->extraAspect<AnalyzerRunConfigurationAspect>(ANALYZER_VALGRIND_SETTINGS))
235
                    settings = qobject_cast<ValgrindBaseSettings *>(aspect->projectSettings());
236 237

    if (!settings) // fallback to global settings
hjk's avatar
hjk committed
238
        settings = ValgrindPlugin::globalSettings();
239 240 241 242 243 244 245 246 247 248 249 250 251

    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);
Robert Loehning's avatar
Robert Loehning committed
252
    connect(m_settings, SIGNAL(destroyed(QObject*)), SLOT(settingsDestroyed(QObject*)));
253 254 255 256 257 258

    foreach (QAction *action, m_errorFilterActions) {
        bool contained = true;
        foreach (const QVariant &v, action->data().toList()) {
            bool ok;
            int kind = v.toInt(&ok);
hjk's avatar
hjk committed
259
            if (ok && !m_settings->visibleErrorKinds().contains(kind))
260 261 262 263 264
                contained = false;
        }
        action->setChecked(contained);
    }

hjk's avatar
hjk committed
265
    m_filterProjectAction->setChecked(!m_settings->filterExternalIssues());
266 267
    m_errorView->settingsChanged(m_settings);

hjk's avatar
hjk committed
268
    connect(m_settings, SIGNAL(visibleErrorKindsChanged(QList<int>)),
269
            m_errorProxyModel, SLOT(setAcceptedKinds(QList<int>)));
hjk's avatar
hjk committed
270
    m_errorProxyModel->setAcceptedKinds(m_settings->visibleErrorKinds());
271

hjk's avatar
hjk committed
272
    connect(m_settings, SIGNAL(filterExternalIssuesChanged(bool)),
273
            m_errorProxyModel, SLOT(setFilterExternalIssues(bool)));
hjk's avatar
hjk committed
274
    m_errorProxyModel->setFilterExternalIssues(m_settings->filterExternalIssues());
275 276
}

hjk's avatar
hjk committed
277
RunMode MemcheckTool::runMode() const
278
{
hjk's avatar
hjk committed
279
    return MemcheckRunMode;
280 281
}

282
IAnalyzerTool::ToolMode MemcheckTool::toolMode() const
283 284 285 286
{
    return DebugMode;
}

287 288
class FrameFinder : public ErrorListModel::RelevantFrameFinder
{
289
public:
290 291
    Frame findRelevant(const Error &error) const
    {
292 293 294 295 296 297 298 299 300
        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
301
        if (!m_projectFiles.isEmpty()) {
302
            foreach (const Frame &frame, frames) {
303 304 305 306 307 308 309 310
                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;
            }
311 312 313
        }

        //if no frame belonging to the project was found, return the first one that is not malloc/new
314
        foreach (const Frame &frame, frames) {
315
            if (!frame.functionName().isEmpty() && frame.functionName() != QLatin1String("malloc")
316
                && !frame.functionName().startsWith(QLatin1String("operator new(")))
317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333
            {
                return frame;
            }
        }

        //else fallback to the first frame
        return frames.first();
    }
    void setFiles(const QStringList &files)
    {
        m_projectFiles = files;
    }
private:
    QStringList m_projectFiles;
};


334
QWidget *MemcheckTool::createWidgets()
335
{
336
    QTC_ASSERT(!m_errorView, return 0);
337

338
    Utils::FancyMainWindow *mw = AnalyzerManager::mainWindow();
339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355

    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);
356
    m_errorView->setObjectName(QLatin1String("Valgrind.MemcheckTool.ErrorView"));
357

358 359
    QDockWidget *errorDock = AnalyzerManager::createDockWidget
        (this, tr("Memory Issues"), m_errorView, Qt::BottomDockWidgetArea);
360
    errorDock->show();
361 362
    mw->splitDockWidget(mw->toolBarDockWidget(), errorDock, Qt::Vertical);

hjk's avatar
hjk committed
363
    connect(ProjectExplorerPlugin::instance(),
364 365
            SIGNAL(updateRunActions()), SLOT(maybeActiveRunConfigurationChanged()));

366 367 368
    //
    // The Control Widget.
    //
369
    QAction *action = 0;
370
    QHBoxLayout *layout = new QHBoxLayout;
371 372
    QToolButton *button = 0;

373 374
    layout->setMargin(0);
    layout->setSpacing(0);
375 376 377 378

    // Go to previous leak.
    action = new QAction(this);
    action->setDisabled(true);
379
    action->setIcon(QIcon(QLatin1String(Core::Constants::ICON_PREV)));
380 381 382 383 384 385 386 387 388 389
    action->setToolTip(tr("Go to previous leak."));
    connect(action, SIGNAL(triggered(bool)), m_errorView, SLOT(goBack()));
    button = new QToolButton;
    button->setDefaultAction(action);
    layout->addWidget(button);
    m_goBack = action;

    // Go to next leak.
    action = new QAction(this);
    action->setDisabled(true);
390
    action->setIcon(QIcon(QLatin1String(Core::Constants::ICON_NEXT)));
391 392 393 394 395 396 397
    action->setToolTip(tr("Go to next leak."));
    connect(action, SIGNAL(triggered(bool)), m_errorView, SLOT(goNext()));
    button = new QToolButton;
    button->setDefaultAction(action);
    layout->addWidget(button);
    m_goNext = action;

398
    QToolButton *filterButton = new QToolButton;
399
    filterButton->setIcon(QIcon(QLatin1String(Core::Constants::ICON_FILTER)));
400 401
    filterButton->setText(tr("Error Filter"));
    filterButton->setPopupMode(QToolButton::InstantPopup);
402 403

    m_filterMenu = new QMenu(filterButton);
Friedemann Kleint's avatar
Friedemann Kleint committed
404
    foreach (QAction *filterAction, m_errorFilterActions)
405 406 407 408
        m_filterMenu->addAction(filterAction);
    m_filterMenu->addSeparator();
    m_filterMenu->addAction(m_filterProjectAction);
    m_filterMenu->addAction(m_suppressionSeparator);
Robert Loehning's avatar
Robert Loehning committed
409
    connect(m_filterMenu, SIGNAL(triggered(QAction*)), SLOT(updateErrorFilter()));
410
    filterButton->setMenu(m_filterMenu);
411 412
    layout->addWidget(filterButton);

413 414 415 416 417
    layout->addStretch();
    QWidget *widget = new QWidget;
    widget->setObjectName(QLatin1String("MemCheckToolBarWidget"));
    widget->setLayout(layout);
    return widget;
418 419
}

420
AnalyzerRunControl *MemcheckTool::createRunControl(const AnalyzerStartParameters &sp,
hjk's avatar
hjk committed
421
                                            RunConfiguration *runConfiguration)
422
{
423
    m_frameFinder->setFiles(runConfiguration ? runConfiguration->target()
hjk's avatar
hjk committed
424
        ->project()->files(Project::AllFiles) : QStringList());
425

426
    MemcheckRunControl *engine = new MemcheckRunControl(sp, runConfiguration);
427

428 429
    connect(engine, SIGNAL(starting(const Analyzer::AnalyzerRunControl*)),
            this, SLOT(engineStarting(const Analyzer::AnalyzerRunControl*)));
430 431 432 433
    connect(engine, SIGNAL(parserError(Valgrind::XmlProtocol::Error)),
            this, SLOT(parserError(Valgrind::XmlProtocol::Error)));
    connect(engine, SIGNAL(internalParserError(QString)),
            this, SLOT(internalParserError(QString)));
434
    connect(engine, SIGNAL(finished()), this, SLOT(finished()));
435 436 437
    return engine;
}

438
void MemcheckTool::engineStarting(const AnalyzerRunControl *engine)
439
{
440
    setBusyCursor(true);
Friedemann Kleint's avatar
Friedemann Kleint committed
441
    clearErrorView();
442

443
    QString dir;
hjk's avatar
hjk committed
444
    if (RunConfiguration *rc = engine->runConfiguration())
445 446
        dir = rc->target()->project()->projectDirectory() + QDir::separator();

447
    const MemcheckRunControl *mEngine = dynamic_cast<const MemcheckRunControl *>(engine);
448 449 450
    QTC_ASSERT(mEngine, return);
    const QString name = QFileInfo(mEngine->executable()).fileName();

451
    m_errorView->setDefaultSuppressionFile(dir + name + QLatin1String(".supp"));
452

453
    foreach (const QString &file, mEngine->suppressionFiles()) {
454
        QAction *action = m_filterMenu->addAction(QFileInfo(file).fileName());
455 456 457 458
        action->setToolTip(file);
        action->setData(file);
        connect(action, SIGNAL(triggered(bool)),
                this, SLOT(suppressionActionTriggered()));
459
        m_suppressionActions.append(action);
460 461 462 463 464
    }
}

void MemcheckTool::suppressionActionTriggered()
{
465
    QAction *action = qobject_cast<QAction *>(sender());
466 467 468 469
    QTC_ASSERT(action, return);
    const QString file = action->data().toString();
    QTC_ASSERT(!file.isEmpty(), return);

470
    Core::EditorManager::openEditorAt(file, 0);
471 472 473 474 475 476 477 478 479
}

void MemcheckTool::parserError(const Valgrind::XmlProtocol::Error &error)
{
    m_errorModel->addError(error);
}

void MemcheckTool::internalParserError(const QString &errorString)
{
480 481
    QMessageBox::critical(m_errorView, tr("Internal Error"),
        tr("Error occurred parsing valgrind output: %1").arg(errorString));
482 483
}

Friedemann Kleint's avatar
Friedemann Kleint committed
484
void MemcheckTool::clearErrorView()
485
{
486
    QTC_ASSERT(m_errorView, return);
487 488 489 490
    m_errorModel->clear();

    qDeleteAll(m_suppressionActions);
    m_suppressionActions.clear();
491
    //QTC_ASSERT(filterMenu()->actions().last() == m_suppressionSeparator, qt_noop());
492 493 494 495
}

void MemcheckTool::updateErrorFilter()
{
496
    QTC_ASSERT(m_errorView, return);
497 498
    QTC_ASSERT(m_settings, return);

hjk's avatar
hjk committed
499
    m_settings->setFilterExternalIssues(!m_filterProjectAction->isChecked());
500 501 502 503 504 505 506 507 508 509 510 511

    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;
        }
    }
hjk's avatar
hjk committed
512
    m_settings->setVisibleErrorKinds(errorKinds);
513
}
Friedemann Kleint's avatar
Friedemann Kleint committed
514

515 516
void MemcheckTool::finished()
{
517 518 519
    const int issuesFound = m_errorModel->rowCount();
    m_goBack->setEnabled(issuesFound > 1);
    m_goNext->setEnabled(issuesFound > 1);
hjk's avatar
hjk committed
520 521 522
    AnalyzerManager::showStatusMessage(issuesFound > 0
        ? AnalyzerManager::tr("Memory Analyzer Tool finished, %n issues were found.", 0, issuesFound)
        : AnalyzerManager::tr("Memory Analyzer Tool finished, no issues were found."));
523

524 525 526 527 528 529 530
    setBusyCursor(false);
}

void MemcheckTool::setBusyCursor(bool busy)
{
    QCursor cursor(busy ? Qt::BusyCursor : Qt::ArrowCursor);
    m_errorView->setCursor(cursor);
hjk's avatar
hjk committed
531 532
}

Friedemann Kleint's avatar
Friedemann Kleint committed
533
} // namespace Internal
534
} // namespace Valgrind