clangstaticanalyzertool.cpp 12.4 KB
Newer Older
1
2
/****************************************************************************
**
3
4
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
5
**
6
** This file is part of Qt Creator.
7
**
8
9
10
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
11
** Software or, alternatively, in accordance with the terms contained in
12
13
14
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
15
**
16
17
18
19
20
21
22
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
23
24
25
26
27
**
****************************************************************************/

#include "clangstaticanalyzertool.h"

Eike Ziller's avatar
Eike Ziller committed
28
#include "clangstaticanalyzerconstants.h"
29
#include "clangstaticanalyzerdiagnostic.h"
30
31
32
33
#include "clangstaticanalyzerdiagnosticmodel.h"
#include "clangstaticanalyzerdiagnosticview.h"
#include "clangstaticanalyzerruncontrol.h"

hjk's avatar
hjk committed
34
#include <debugger/analyzer/analyzermanager.h>
35
#include <coreplugin/coreconstants.h>
36
#include <coreplugin/coreicons.h>
37
#include <coreplugin/icore.h>
38
#include <cpptools/cppmodelmanager.h>
39
#include <projectexplorer/buildconfiguration.h>
40
41
42
#include <projectexplorer/projectexplorer.h>
#include <projectexplorer/projectexplorerconstants.h>
#include <projectexplorer/session.h>
43
#include <projectexplorer/target.h>
44

45
#include <utils/checkablemessagebox.h>
46
47
#include <utils/fancymainwindow.h>

48
#include <QAction>
49
50
51
52
53
54
55
#include <QDockWidget>
#include <QHBoxLayout>
#include <QLabel>
#include <QListView>
#include <QSortFilterProxyModel>
#include <QToolButton>

56
using namespace Debugger;
57
using namespace ProjectExplorer;
58
using namespace Utils;
59
60
61
62
63

namespace ClangStaticAnalyzer {
namespace Internal {

ClangStaticAnalyzerTool::ClangStaticAnalyzerTool(QObject *parent)
64
    : QObject(parent)
65
    , m_diagnosticModel(0)
66
    , m_diagnosticFilterModel(0)
67
68
69
    , m_diagnosticView(0)
    , m_goBack(0)
    , m_goNext(0)
Christian Kandeler's avatar
Christian Kandeler committed
70
    , m_running(false)
71
72
73
74
75
76
{
    setObjectName(QLatin1String("ClangStaticAnalyzerTool"));

    //
    // Diagnostic View
    //
77
    m_diagnosticView = new ClangStaticAnalyzerDiagnosticView;
78
79
    m_diagnosticView->setFrameStyle(QFrame::NoFrame);
    m_diagnosticView->setAttribute(Qt::WA_MacShowFocusRect, false);
80
81
    m_diagnosticModel = new ClangStaticAnalyzerDiagnosticModel(this);
    m_diagnosticFilterModel = new ClangStaticAnalyzerDiagnosticFilterModel(this);
82
83
    m_diagnosticFilterModel->setSourceModel(m_diagnosticModel);
    m_diagnosticView->setModel(m_diagnosticFilterModel);
84
85
86
87
    m_diagnosticView->setVerticalScrollMode(QAbstractItemView::ScrollPerPixel);
    m_diagnosticView->setAutoScroll(false);
    m_diagnosticView->setObjectName(QLatin1String("ClangStaticAnalyzerIssuesView"));
    m_diagnosticView->setWindowTitle(tr("Clang Static Analyzer Issues"));
88
89
90
91
92
93
94
95
96
97
98
    foreach (auto * const model,
             QList<QAbstractItemModel *>() << m_diagnosticModel << m_diagnosticFilterModel) {
        connect(model, &QAbstractItemModel::rowsInserted,
                this, &ClangStaticAnalyzerTool::handleStateUpdate);
        connect(model, &QAbstractItemModel::rowsRemoved,
                this, &ClangStaticAnalyzerTool::handleStateUpdate);
        connect(model, &QAbstractItemModel::modelReset,
                this, &ClangStaticAnalyzerTool::handleStateUpdate);
        connect(model, &QAbstractItemModel::layoutChanged, // For QSortFilterProxyModel::invalidate()
                this, &ClangStaticAnalyzerTool::handleStateUpdate);
    }
99
100
101
102
103

    //
    // Toolbar widget
    //

104
105
    m_startAction = Debugger::createStartAction();
    m_stopAction = Debugger::createStopAction();
106
107

    // Go to previous diagnostic
108
    auto action = new QAction(this);
109
    action->setDisabled(true);
110
    action->setIcon(Core::Icons::PREV.icon());
111
112
113
114
115
116
117
    action->setToolTip(tr("Go to previous bug."));
    connect(action, &QAction::triggered, m_diagnosticView, &DetailedErrorView::goBack);
    m_goBack = action;

    // Go to next diagnostic
    action = new QAction(this);
    action->setDisabled(true);
118
    action->setIcon(Core::Icons::NEXT.icon());
119
120
121
122
    action->setToolTip(tr("Go to next bug."));
    connect(action, &QAction::triggered, m_diagnosticView, &DetailedErrorView::goNext);
    m_goNext = action;

123
    const QString toolTip = tr("Clang Static Analyzer uses the analyzer from the Clang project "
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
                               "to find bugs.");

    Debugger::registerPerspective(ClangStaticAnalyzerPerspectiveId, {
        tr("Clang Static Analyzer"),
        {{ ClangStaticAnalyzerDockId, m_diagnosticView, {}, Perspective::SplitVertical }}
    });

    ActionDescription desc;
    desc.setText(tr("Clang Static Analyzer"));
    desc.setToolTip(toolTip);
    desc.setRunMode(Constants::CLANGSTATICANALYZER_RUN_MODE);
    desc.setPerspectiveId(ClangStaticAnalyzerPerspectiveId);
    desc.setRunControlCreator([this](RunConfiguration *runConfiguration, Core::Id runMode) {
        return createRunControl(runConfiguration, runMode);
    });
    desc.setCustomToolStarter([this](RunConfiguration *runConfiguration) {
        startTool(runConfiguration);
    });
    desc.setMenuGroup(Debugger::Constants::G_ANALYZER_TOOLS);
    Debugger::registerAction(ClangStaticAnalyzerActionId, desc, m_startAction);

    ToolbarDescription toolbar;
    toolbar.addAction(m_startAction);
    toolbar.addAction(m_stopAction);
    toolbar.addAction(m_goBack);
    toolbar.addAction(m_goNext);
    Debugger::registerToolbar(ClangStaticAnalyzerPerspectiveId, toolbar);

    updateRunActions();

    connect(ProjectExplorerPlugin::instance(), &ProjectExplorerPlugin::updateRunActions,
            this, &ClangStaticAnalyzerTool::updateRunActions);
156
157
}

hjk's avatar
hjk committed
158
159
AnalyzerRunControl *ClangStaticAnalyzerTool::createRunControl(RunConfiguration *runConfiguration,
                                                              Core::Id runMode)
160
{
161
    QTC_ASSERT(runConfiguration, return 0);
Nikolai Kosjar's avatar
Nikolai Kosjar committed
162
    QTC_ASSERT(m_projectInfoBeforeBuild.isValid(), return 0);
163

Nikolai Kosjar's avatar
Nikolai Kosjar committed
164
165
166
    // Some projects provides CompilerCallData once a build is finished,
    // so pass on the updated Project Info unless no configuration change
    // (defines/includes/files) happened.
167
    Project *project = runConfiguration->target()->project();
Nikolai Kosjar's avatar
Nikolai Kosjar committed
168
169
170
171
172
173
174
    QTC_ASSERT(project, return 0);
    const CppTools::ProjectInfo projectInfoAfterBuild
            = CppTools::CppModelManager::instance()->projectInfo(project);
    QTC_ASSERT(!projectInfoAfterBuild.configurationOrFilesChanged(m_projectInfoBeforeBuild),
               return 0);
    m_projectInfoBeforeBuild = CppTools::ProjectInfo();

hjk's avatar
hjk committed
175
176
177
    auto runControl = new ClangStaticAnalyzerRunControl(runConfiguration, runMode,
                                                        projectInfoAfterBuild);
    connect(runControl, &ClangStaticAnalyzerRunControl::starting,
178
            this, &ClangStaticAnalyzerTool::onEngineIsStarting);
hjk's avatar
hjk committed
179
    connect(runControl, &ClangStaticAnalyzerRunControl::newDiagnosticsAvailable,
180
            this, &ClangStaticAnalyzerTool::onNewDiagnosticsAvailable);
hjk's avatar
hjk committed
181
    connect(runControl, &ClangStaticAnalyzerRunControl::finished,
182
            this, &ClangStaticAnalyzerTool::onEngineFinished);
183
184
185
186
187

    connect(m_stopAction, &QAction::triggered, runControl, [runControl] { runControl->stop(); });

    m_toolBusy = true;
    updateRunActions();
hjk's avatar
hjk committed
188
    return runControl;
189
190
}

191
static bool dontStartAfterHintForDebugMode(Project *project)
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
{
    BuildConfiguration::BuildType buildType = BuildConfiguration::Unknown;
    if (project) {
        if (const Target *target = project->activeTarget()) {
            if (const BuildConfiguration *buildConfig = target->activeBuildConfiguration())
                buildType = buildConfig->buildType();
        }
    }

    if (buildType == BuildConfiguration::Release) {
        const QString wrongMode = ClangStaticAnalyzerTool::tr("Release");
        const QString toolName = ClangStaticAnalyzerTool::tr("Clang Static Analyzer");
        const QString title = ClangStaticAnalyzerTool::tr("Run %1 in %2 Mode?").arg(toolName)
                .arg(wrongMode);
        const QString message = ClangStaticAnalyzerTool::tr(
            "<html><head/><body>"
            "<p>You are trying to run the tool \"%1\" on an application in %2 mode. The tool is "
            "designed to be used in Debug mode since enabled assertions can reduce the number of "
            "false positives.</p>"
            "<p>Do you want to continue and run the tool in %2 mode?</p>"
            "</body></html>")
                .arg(toolName).arg(wrongMode);
        if (Utils::CheckableMessageBox::doNotAskAgainQuestion(Core::ICore::mainWindow(),
                title, message, Core::ICore::settings(),
216
                QLatin1String("ClangStaticAnalyzerCorrectModeWarning")) != QDialogButtonBox::Yes)
217
218
219
220
221
222
            return true;
    }

    return false;
}

223
void ClangStaticAnalyzerTool::startTool(ProjectExplorer::RunConfiguration *runConfiguration)
224
{
225
    Project *project = SessionManager::startupProject();
226
    QTC_ASSERT(project, emit finished(false); return);
227
228

    if (dontStartAfterHintForDebugMode(project))
229
230
        return;

231
232
    m_diagnosticModel->clear();
    setBusyCursor(true);
233
    m_diagnosticFilterModel->setProject(project);
Nikolai Kosjar's avatar
Nikolai Kosjar committed
234
    m_projectInfoBeforeBuild = CppTools::CppModelManager::instance()->projectInfo(project);
235
    QTC_ASSERT(m_projectInfoBeforeBuild.isValid(), emit finished(false); return);
Christian Kandeler's avatar
Christian Kandeler committed
236
    m_running = true;
237
    handleStateUpdate();
238

239
240
    ProjectExplorerPlugin::runRunConfiguration(runConfiguration,
                                               Constants::CLANGSTATICANALYZER_RUN_MODE);
241
242
}

Nikolai Kosjar's avatar
Nikolai Kosjar committed
243
CppTools::ProjectInfo ClangStaticAnalyzerTool::projectInfoBeforeBuild() const
244
{
Nikolai Kosjar's avatar
Nikolai Kosjar committed
245
    return m_projectInfoBeforeBuild;
246
247
}

Nikolai Kosjar's avatar
Nikolai Kosjar committed
248
void ClangStaticAnalyzerTool::resetCursorAndProjectInfoBeforeBuild()
249
250
{
    setBusyCursor(false);
Nikolai Kosjar's avatar
Nikolai Kosjar committed
251
    m_projectInfoBeforeBuild = CppTools::ProjectInfo();
252
253
}

Christian Kandeler's avatar
Christian Kandeler committed
254
255
256
257
258
QList<Diagnostic> ClangStaticAnalyzerTool::diagnostics() const
{
    return m_diagnosticModel->diagnostics();
}

259
260
261
262
263
264
265
266
267
268
269
270
271
void ClangStaticAnalyzerTool::onEngineIsStarting()
{
    QTC_ASSERT(m_diagnosticModel, return);
}

void ClangStaticAnalyzerTool::onNewDiagnosticsAvailable(const QList<Diagnostic> &diagnostics)
{
    QTC_ASSERT(m_diagnosticModel, return);
    m_diagnosticModel->addDiagnostics(diagnostics);
}

void ClangStaticAnalyzerTool::onEngineFinished()
{
Nikolai Kosjar's avatar
Nikolai Kosjar committed
272
    resetCursorAndProjectInfoBeforeBuild();
Christian Kandeler's avatar
Christian Kandeler committed
273
    m_running = false;
274
    handleStateUpdate();
275
    emit finished(static_cast<ClangStaticAnalyzerRunControl *>(sender())->success());
276
277
    m_toolBusy = false;
    updateRunActions();
278
279
}

280
281
282
283
void ClangStaticAnalyzerTool::updateRunActions()
{
    if (m_toolBusy) {
        m_startAction->setEnabled(false);
284
        m_startAction->setToolTip(tr("Clang Static Analyzer is still running."));
285
286
        m_stopAction->setEnabled(true);
    } else {
287
288
289
290
291
292
        QString whyNot = tr("Start Clang Static Analyzer.");
        bool canRun = ProjectExplorerPlugin::canRunStartupProject(
            Constants::CLANGSTATICANALYZER_RUN_MODE, &whyNot);
        m_startAction->setToolTip(whyNot);
        m_startAction->setEnabled(canRun);
        m_stopAction->setEnabled(false);
293
294
    }
}
295
296
297
298
299
300
301
void ClangStaticAnalyzerTool::setBusyCursor(bool busy)
{
    QTC_ASSERT(m_diagnosticView, return);
    QCursor cursor(busy ? Qt::BusyCursor : Qt::ArrowCursor);
    m_diagnosticView->setCursor(cursor);
}

302
303
304
305
306
307
308
void ClangStaticAnalyzerTool::handleStateUpdate()
{
    QTC_ASSERT(m_goBack, return);
    QTC_ASSERT(m_goNext, return);
    QTC_ASSERT(m_diagnosticModel, return);
    QTC_ASSERT(m_diagnosticFilterModel, return);

309
    const int issuesFound = m_diagnosticModel->diagnostics().count();
310
311
312
313
    const int issuesVisible = m_diagnosticFilterModel->rowCount();
    m_goBack->setEnabled(issuesVisible > 1);
    m_goNext->setEnabled(issuesVisible > 1);

314
    QString message = m_running ? tr("Clang Static Analyzer is running.")
315
316
317
318
319
320
321
322
                                : tr("Clang Static Analyzer finished.");
    message += QLatin1Char(' ');
    if (issuesFound == 0) {
        message += tr("No issues found.");
    } else {
        message += tr("%n issues found (%1 suppressed).", 0, issuesFound)
                .arg(issuesFound - issuesVisible);
    }
323
    Debugger::showPermanentStatusMessage(message);
324
325
}

326
327
} // namespace Internal
} // namespace ClangStaticAnalyzer