forked from qt-creator/qt-creator
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:
324
src/plugins/cppcheck/cppchecktool.cpp
Normal file
324
src/plugins/cppcheck/cppchecktool.cpp
Normal 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
|
Reference in New Issue
Block a user