diff options
| author | Sergey Morozov <dev@gres.biz> | 2018-07-30 21:42:47 +0300 |
|---|---|---|
| committer | Sergey Morozov <dev@gres.biz> | 2018-09-18 19:07:12 +0000 |
| commit | 31b595314c0963cad42ed4cd18d7dc78502479e0 (patch) | |
| tree | 670ab7ccac2a0d738cfb59ef47752ee22b26e371 /src/plugins/cppcheck/cppchecktool.cpp | |
| parent | 20f3c8d65432bffaa1ba6ca9d2512ce3f3e3ce12 (diff) | |
| download | qt-creator-31b595314c0963cad42ed4cd18d7dc78502479e0.tar.gz | |
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>
Diffstat (limited to 'src/plugins/cppcheck/cppchecktool.cpp')
| -rw-r--r-- | src/plugins/cppcheck/cppchecktool.cpp | 324 |
1 files changed, 324 insertions, 0 deletions
diff --git a/src/plugins/cppcheck/cppchecktool.cpp b/src/plugins/cppcheck/cppchecktool.cpp new file mode 100644 index 0000000000..2c80de3674 --- /dev/null +++ b/src/plugins/cppcheck/cppchecktool.cpp @@ -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 |
