Cppcheck: Add cppcheck static analysis tool

Automatically checks currently opened documents and displays results via text marks/annotations.

CppcheckTrigger detects when to check files or clear results.
CppcheckTextMarkManager stores/clears text marks with checks' results.
CppcheckTool generates run arguments and parses output.
CppcheckRunner runs cppcheck binary.
CppcheckOptions configures CppcheckTool.

Task-number: QTCREATORBUG-20418
Change-Id: I8eafeac7af6137d2c9061ae75d4a56c85b3b5a2d
Reviewed-by: Alessandro Portale <alessandro.portale@qt.io>
Reviewed-by: Marco Bubke <marco.bubke@qt.io>
This commit is contained in:
Sergey Morozov
2018-07-30 21:42:47 +03:00
parent 20f3c8d654
commit 31b595314c
22 changed files with 1919 additions and 1 deletions

View File

@@ -0,0 +1,324 @@
/****************************************************************************
**
** Copyright (C) 2018 Sergey Morozov
** 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 "cppcheckconstants.h"
#include "cppcheckdiagnostic.h"
#include "cppcheckoptions.h"
#include "cppcheckrunner.h"
#include "cppchecktextmarkmanager.h"
#include "cppchecktool.h"
#include <coreplugin/messagemanager.h>
#include <coreplugin/progressmanager/futureprogress.h>
#include <coreplugin/progressmanager/progressmanager.h>
#include <cpptools/cppmodelmanager.h>
#include <utils/algorithm.h>
#include <utils/macroexpander.h>
#include <utils/qtcassert.h>
#include <QThread>
namespace Cppcheck {
namespace Internal {
CppcheckTool::CppcheckTool(CppcheckTextMarkManager &marks) :
m_marks(marks),
m_progressRegexp("^.* checked (\\d)% done$"),
m_messageRegexp("^(.+),(\\d+),(\\w+),(\\w+),(.*)$")
{
m_runner = std::make_unique<CppcheckRunner>(*this);
QTC_ASSERT(m_progressRegexp.isValid(), return);
QTC_ASSERT(m_messageRegexp.isValid(), return);
}
CppcheckTool::~CppcheckTool() = default;
void CppcheckTool::updateOptions(const CppcheckOptions &options)
{
m_options = options;
m_filters.clear();
const auto patterns = m_options.ignoredPatterns.split(',');
for (const auto &pattern : patterns) {
const auto trimmedPattern = pattern.trimmed();
if (trimmedPattern.isEmpty())
continue;
const QRegExp re(trimmedPattern, Qt::CaseSensitive, QRegExp::Wildcard);
if (re.isValid())
m_filters.push_back(re);
}
updateArguments();
}
void CppcheckTool::setProject(ProjectExplorer::Project *project)
{
m_project = project;
updateArguments();
}
void CppcheckTool::updateArguments()
{
if (!m_project)
return;
m_cachedAdditionalArguments.clear();
QStringList arguments;
if (!m_options.customArguments.isEmpty()) {
auto expander = Utils::globalMacroExpander();
const auto expanded = expander->expand(m_options.customArguments);
arguments.push_back(expanded);
}
if (m_options.warning)
arguments.push_back("--enable=warning");
if (m_options.style)
arguments.push_back("--enable=style");
if (m_options.performance)
arguments.push_back("--enable=performance");
if (m_options.portability)
arguments.push_back("--enable=portability");
if (m_options.information)
arguments.push_back("--enable=information");
if (m_options.unusedFunction)
arguments.push_back("--enable=unusedFunction");
if (m_options.missingInclude)
arguments.push_back("--enable=missingInclude");
if (m_options.inconclusive)
arguments.push_back("--inconclusive");
if (m_options.forceDefines)
arguments.push_back("--force");
if (!m_options.unusedFunction && !m_options.customArguments.contains("-j "))
arguments.push_back("-j " + QString::number(QThread::idealThreadCount()));
arguments.push_back("--template={file},{line},{severity},{id},{message}");
m_runner->reconfigure(m_options.binary, arguments.join(' '));
}
QStringList CppcheckTool::additionalArguments(const CppTools::ProjectPart &part) const
{
QStringList result;
if (m_options.addIncludePaths) {
for (const auto &path : qAsConst(part.headerPaths)) {
const auto projectDir = m_project->projectDirectory().toString();
if (path.type == ProjectExplorer::HeaderPathType::User
&& path.path.startsWith(projectDir))
result.push_back("-I " + path.path);
}
}
if (!m_options.guessArguments)
return result;
using Version = CppTools::ProjectPart::LanguageVersion;
switch (part.languageVersion) {
case Version::C89:
result.push_back("--std=c89 --language=c");
break;
case Version::C99:
result.push_back("--std=c99 --language=c");
break;
case Version::C11:
result.push_back("--std=c11 --language=c");
break;
case Version::CXX03:
result.push_back("--std=c++03 --language=c++");
break;
case Version::CXX11:
result.push_back("--std=c++11 --language=c++");
break;
case Version::CXX14:
result.push_back("--std=c++14 --language=c++");
break;
case Version::CXX98:
case Version::CXX17:
result.push_back("--language=c++");
break;
}
using QtVersion = CppTools::ProjectPart::QtVersion;
if (part.qtVersion != QtVersion::NoQt)
result.push_back("--library=qt");
return result;
}
const CppcheckOptions &CppcheckTool::options() const
{
return m_options;
}
void CppcheckTool::check(const Utils::FileNameList &files)
{
QTC_ASSERT(m_project, return);
Utils::FileNameList filtered;
if (m_filters.isEmpty()) {
filtered = files;
} else {
std::copy_if(files.cbegin(), files.cend(), std::back_inserter(filtered),
[this](const Utils::FileName &file) {
const auto stringed = file.toString();
const auto filter = [stringed](const QRegExp &re) {return re.exactMatch(stringed);};
return !Utils::contains(m_filters, filter);
});
}
if (filtered.isEmpty())
return;
const auto info = CppTools::CppModelManager::instance()->projectInfo(m_project);
const auto parts = info.projectParts();
if (parts.size() == 1) {
QTC_ASSERT(parts.first(), return);
addToQueue(filtered, *parts.first());
return;
}
std::map<CppTools::ProjectPart::Ptr, Utils::FileNameList> groups;
for (const auto &file : qAsConst(filtered)) {
const auto stringed = file.toString();
for (const auto &part : parts) {
QTC_ASSERT(part, continue);
const auto &partFiles = part->files;
using File = CppTools::ProjectFile;
const auto match = [stringed](const File &file){return file.path == stringed;};
if (Utils::contains(partFiles, match))
groups[part].push_back(file);
}
}
for (const auto &group : groups)
addToQueue(group.second, *group.first);
}
void CppcheckTool::addToQueue(const Utils::FileNameList &files, CppTools::ProjectPart &part)
{
const auto key = part.id();
if (!m_cachedAdditionalArguments.contains(key))
m_cachedAdditionalArguments.insert(key, additionalArguments(part).join(' '));
m_runner->addToQueue(files, m_cachedAdditionalArguments[key]);
}
void CppcheckTool::stop(const Utils::FileNameList &files)
{
m_runner->removeFromQueue(files);
m_runner->stop();
}
void CppcheckTool::startParsing()
{
if (m_options.showOutput) {
const auto message = tr("Cppcheck started: \"%1\".").arg(m_runner->currentCommand());
Core::MessageManager::write(message, Core::MessageManager::Silent);
}
m_progress = std::make_unique<QFutureInterface<void>>();
const auto progress = Core::ProgressManager::addTask(
m_progress->future(), QObject::tr("Cppcheck"),
Constants::CHECK_PROGRESS_ID);
QObject::connect(progress, &Core::FutureProgress::canceled,
this, [this]{stop({});});
m_progress->setProgressRange(0, 100);
m_progress->reportStarted();
}
void CppcheckTool::parseOutputLine(const QString &line)
{
if (line.isEmpty())
return;
if (m_options.showOutput)
Core::MessageManager::write(line, Core::MessageManager::Silent);
enum Matches { Percentage = 1 };
const auto match = m_progressRegexp.match(line);
if (!match.hasMatch())
return;
const auto done = match.captured(Percentage).toInt();
QTC_ASSERT(m_progress, return);
m_progress->setProgressValue(done);
}
static Diagnostic::Severity toSeverity(const QString &text)
{
static const QMap<QString, Diagnostic::Severity> values{
{"error", Diagnostic::Severity::Error},
{"warning", Diagnostic::Severity::Warning},
{"performance", Diagnostic::Severity::Performance},
{"portability", Diagnostic::Severity::Portability},
{"style", Diagnostic::Severity::Style},
{"information", Diagnostic::Severity::Information}
};
return values.value(text, Diagnostic::Severity::Information);
}
void CppcheckTool::parseErrorLine(const QString &line)
{
if (line.isEmpty())
return;
if (m_options.showOutput)
Core::MessageManager::write(line, Core::MessageManager::Silent);
enum Matches { File = 1, Line, Severity, Id, Message };
const auto match = m_messageRegexp.match(line);
if (!match.hasMatch())
return;
const auto fileName = Utils::FileName::fromString(
QDir::fromNativeSeparators(match.captured(File)));
if (!m_runner->currentFiles().contains(fileName))
return;
Diagnostic diagnostic;
diagnostic.fileName = fileName;
diagnostic.lineNumber = std::max(match.captured(Line).toInt(), 1);
diagnostic.severityText = match.captured(Severity);
diagnostic.severity = toSeverity(diagnostic.severityText);
diagnostic.checkId = match.captured(Id);
diagnostic.message = match.captured(Message);
if (diagnostic.isValid())
m_marks.add(diagnostic);
}
void CppcheckTool::finishParsing()
{
if (m_options.showOutput)
Core::MessageManager::write(tr("Cppcheck finished."), Core::MessageManager::Silent);
QTC_ASSERT(m_progress, return);
m_progress->reportFinished();
}
} // namespace Internal
} // namespace Cppcheck