diff options
Diffstat (limited to 'src/plugins/clangstaticanalyzer/clangstaticanalyzertool.cpp')
-rw-r--r-- | src/plugins/clangstaticanalyzer/clangstaticanalyzertool.cpp | 332 |
1 files changed, 332 insertions, 0 deletions
diff --git a/src/plugins/clangstaticanalyzer/clangstaticanalyzertool.cpp b/src/plugins/clangstaticanalyzer/clangstaticanalyzertool.cpp new file mode 100644 index 0000000000..9fe109d63d --- /dev/null +++ b/src/plugins/clangstaticanalyzer/clangstaticanalyzertool.cpp @@ -0,0 +1,332 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** 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 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. +** +** 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. +** +****************************************************************************/ + +#include "clangstaticanalyzertool.h" + +#include "clangstaticanalyzerconstants.h" +#include "clangstaticanalyzerdiagnostic.h" +#include "clangstaticanalyzerdiagnosticmodel.h" +#include "clangstaticanalyzerdiagnosticview.h" +#include "clangstaticanalyzerruncontrol.h" + +#include <analyzerbase/analyzermanager.h> +#include <coreplugin/coreconstants.h> +#include <coreplugin/coreicons.h> +#include <coreplugin/icore.h> +#include <cpptools/cppmodelmanager.h> +#include <projectexplorer/buildconfiguration.h> +#include <projectexplorer/projectexplorer.h> +#include <projectexplorer/projectexplorerconstants.h> +#include <projectexplorer/session.h> +#include <projectexplorer/target.h> + +#include <utils/checkablemessagebox.h> +#include <utils/fancymainwindow.h> + +#include <QDockWidget> +#include <QHBoxLayout> +#include <QLabel> +#include <QListView> +#include <QSortFilterProxyModel> +#include <QToolButton> + +using namespace Analyzer; +using namespace ProjectExplorer; + +namespace ClangStaticAnalyzer { +namespace Internal { + +class DummyRunConfiguration : public RunConfiguration +{ + Q_OBJECT + +public: + DummyRunConfiguration(Target *parent) + : RunConfiguration(parent, "ClangStaticAnalyzer.DummyRunConfig") + { + setDefaultDisplayName(tr("Clang Static Analyzer")); + addExtraAspects(); + } + +private: + QWidget *createConfigurationWidget() override { return 0; } +}; + +ClangStaticAnalyzerTool::ClangStaticAnalyzerTool(QObject *parent) + : QObject(parent) + , m_diagnosticModel(0) + , m_diagnosticFilterModel(0) + , m_diagnosticView(0) + , m_goBack(0) + , m_goNext(0) + , m_running(false) +{ + setObjectName(QLatin1String("ClangStaticAnalyzerTool")); +} + +QWidget *ClangStaticAnalyzerTool::createWidgets() +{ + QTC_ASSERT(!m_diagnosticView, return 0); + QTC_ASSERT(!m_diagnosticModel, return 0); + QTC_ASSERT(!m_goBack, return 0); + QTC_ASSERT(!m_goNext, return 0); + + // + // Diagnostic View + // + m_diagnosticView = new ClangStaticAnalyzerDiagnosticView; + m_diagnosticView->setFrameStyle(QFrame::NoFrame); + m_diagnosticView->setAttribute(Qt::WA_MacShowFocusRect, false); + m_diagnosticModel = new ClangStaticAnalyzerDiagnosticModel(this); + m_diagnosticFilterModel = new ClangStaticAnalyzerDiagnosticFilterModel(this); + m_diagnosticFilterModel->setSourceModel(m_diagnosticModel); + m_diagnosticView->setModel(m_diagnosticFilterModel); + m_diagnosticView->setVerticalScrollMode(QAbstractItemView::ScrollPerPixel); + m_diagnosticView->setAutoScroll(false); + m_diagnosticView->setObjectName(QLatin1String("ClangStaticAnalyzerIssuesView")); + m_diagnosticView->setWindowTitle(tr("Clang Static Analyzer Issues")); + 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); + } + + QDockWidget *issuesDock = AnalyzerManager::createDockWidget(ClangStaticAnalyzerToolId, + m_diagnosticView); + issuesDock->show(); + Utils::FancyMainWindow *mw = AnalyzerManager::mainWindow(); + mw->splitDockWidget(mw->toolBarDockWidget(), issuesDock, Qt::Vertical); + + // + // Toolbar widget + // + QHBoxLayout *layout = new QHBoxLayout; + layout->setMargin(0); + layout->setSpacing(0); + + QAction *action = 0; + QToolButton *button = 0; + + // Go to previous diagnostic + action = new QAction(this); + action->setDisabled(true); + action->setIcon(Core::Icons::PREV.icon()); + action->setToolTip(tr("Go to previous bug.")); + connect(action, &QAction::triggered, m_diagnosticView, &DetailedErrorView::goBack); + button = new QToolButton; + button->setDefaultAction(action); + layout->addWidget(button); + m_goBack = action; + + // Go to next diagnostic + action = new QAction(this); + action->setDisabled(true); + action->setIcon(Core::Icons::NEXT.icon()); + action->setToolTip(tr("Go to next bug.")); + connect(action, &QAction::triggered, m_diagnosticView, &DetailedErrorView::goNext); + button = new QToolButton; + button->setDefaultAction(action); + layout->addWidget(button); + m_goNext = action; + + layout->addStretch(); + + QWidget *toolbarWidget = new QWidget; + toolbarWidget->setObjectName(QLatin1String("ClangStaticAnalyzerToolBarWidget")); + toolbarWidget->setLayout(layout); + return toolbarWidget; +} + +AnalyzerRunControl *ClangStaticAnalyzerTool::createRunControl(RunConfiguration *runConfiguration, + Core::Id runMode) +{ + QTC_ASSERT(runConfiguration, return 0); + QTC_ASSERT(m_projectInfoBeforeBuild.isValid(), return 0); + + // Some projects provides CompilerCallData once a build is finished, + // so pass on the updated Project Info unless no configuration change + // (defines/includes/files) happened. + Project *project = runConfiguration->target()->project(); + 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(); + + auto runControl = new ClangStaticAnalyzerRunControl(runConfiguration, runMode, + projectInfoAfterBuild); + connect(runControl, &ClangStaticAnalyzerRunControl::starting, + this, &ClangStaticAnalyzerTool::onEngineIsStarting); + connect(runControl, &ClangStaticAnalyzerRunControl::newDiagnosticsAvailable, + this, &ClangStaticAnalyzerTool::onNewDiagnosticsAvailable); + connect(runControl, &ClangStaticAnalyzerRunControl::finished, + this, &ClangStaticAnalyzerTool::onEngineFinished); + return runControl; +} + +static bool dontStartAfterHintForDebugMode(Project *project) +{ + 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(), + QLatin1String("ClangStaticAnalyzerCorrectModeWarning")) != QDialogButtonBox::Yes) + return true; + } + + return false; +} + +void ClangStaticAnalyzerTool::startTool() +{ + AnalyzerManager::showMode(); + + Project *project = SessionManager::startupProject(); + QTC_ASSERT(project, emit finished(false); return); + + if (dontStartAfterHintForDebugMode(project)) + return; + + m_diagnosticModel->clear(); + setBusyCursor(true); + m_diagnosticFilterModel->setProject(project); + m_projectInfoBeforeBuild = CppTools::CppModelManager::instance()->projectInfo(project); + QTC_ASSERT(m_projectInfoBeforeBuild.isValid(), emit finished(false); return); + m_running = true; + handleStateUpdate(); + + Target * const target = project->activeTarget(); + QTC_ASSERT(target, return); + DummyRunConfiguration *& rc = m_runConfigs[target]; + if (!rc) { + rc = new DummyRunConfiguration(target); + connect(project, &Project::aboutToRemoveTarget, this, + [this](Target *t) { m_runConfigs.remove(t); }); + const auto onProjectRemoved = [this](Project *p) { + foreach (Target * const t, p->targets()) + m_runConfigs.remove(t); + }; + connect(SessionManager::instance(), &SessionManager::aboutToRemoveProject, this, + onProjectRemoved, Qt::UniqueConnection); + } + ProjectExplorerPlugin::runRunConfiguration(rc, Constants::CLANGSTATICANALYZER_RUN_MODE); +} + +CppTools::ProjectInfo ClangStaticAnalyzerTool::projectInfoBeforeBuild() const +{ + return m_projectInfoBeforeBuild; +} + +void ClangStaticAnalyzerTool::resetCursorAndProjectInfoBeforeBuild() +{ + setBusyCursor(false); + m_projectInfoBeforeBuild = CppTools::ProjectInfo(); +} + +QList<Diagnostic> ClangStaticAnalyzerTool::diagnostics() const +{ + return m_diagnosticModel->diagnostics(); +} + +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() +{ + resetCursorAndProjectInfoBeforeBuild(); + m_running = false; + handleStateUpdate(); + emit finished(static_cast<ClangStaticAnalyzerRunControl *>(sender())->success()); +} + +void ClangStaticAnalyzerTool::setBusyCursor(bool busy) +{ + QTC_ASSERT(m_diagnosticView, return); + QCursor cursor(busy ? Qt::BusyCursor : Qt::ArrowCursor); + m_diagnosticView->setCursor(cursor); +} + +void ClangStaticAnalyzerTool::handleStateUpdate() +{ + QTC_ASSERT(m_goBack, return); + QTC_ASSERT(m_goNext, return); + QTC_ASSERT(m_diagnosticModel, return); + QTC_ASSERT(m_diagnosticFilterModel, return); + + const int issuesFound = m_diagnosticModel->diagnostics().count(); + const int issuesVisible = m_diagnosticFilterModel->rowCount(); + m_goBack->setEnabled(issuesVisible > 1); + m_goNext->setEnabled(issuesVisible > 1); + + QString message = m_running ? tr("Clang Static Analyzer running.") + : 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); + } + AnalyzerManager::showPermanentStatusMessage(ClangStaticAnalyzerToolId, message); +} + +} // namespace Internal +} // namespace ClangStaticAnalyzer + +#include "clangstaticanalyzertool.moc" |