1 /****************************************************************************
2 **
3 ** Copyright (C) 2018 Sergey Morozov
4 ** Contact: https://www.qt.io/licensing/
5 **
6 ** This file is part of Qt Creator.
7 **
8 ** Commercial License Usage
9 ** Licensees holding valid commercial Qt licenses may use this file in
10 ** accordance with the commercial license agreement provided with the
11 ** Software or, alternatively, in accordance with the terms contained in
12 ** a written agreement between you and The Qt Company. For licensing terms
13 ** and conditions see https://www.qt.io/terms-conditions. For further
14 ** information use the contact form at https://www.qt.io/contact-us.
15 **
16 ** GNU General Public License Usage
17 ** Alternatively, this file may be used under the terms of the GNU
18 ** General Public License version 3 as published by the Free Software
19 ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
20 ** included in the packaging of this file. Please review the following
21 ** information to ensure the GNU General Public License requirements will
22 ** be met: https://www.gnu.org/licenses/gpl-3.0.html.
23 **
24 ****************************************************************************/
25 
26 #include "cppcheckdiagnostic.h"
27 #include "cppcheckoptions.h"
28 #include "cppcheckrunner.h"
29 #include "cppchecktextmarkmanager.h"
30 #include "cppchecktool.h"
31 
32 #include <coreplugin/messagemanager.h>
33 #include <coreplugin/progressmanager/futureprogress.h>
34 #include <coreplugin/progressmanager/progressmanager.h>
35 
36 #include <cpptools/cppmodelmanager.h>
37 
38 #include <utils/algorithm.h>
39 #include <utils/macroexpander.h>
40 #include <utils/qtcassert.h>
41 #include <utils/stringutils.h>
42 
43 #include <QThread>
44 
45 namespace Cppcheck {
46 namespace Internal {
47 
CppcheckTool(CppcheckDiagnosticManager & manager,const Utils::Id & progressId)48 CppcheckTool::CppcheckTool(CppcheckDiagnosticManager &manager,
49                            const Utils::Id &progressId) :
50     m_manager(manager),
51     m_progressRegexp("^.* checked (\\d+)% done$"),
52     m_messageRegexp("^(.+),(\\d+),(\\w+),(\\w+),(.*)$"),
53     m_progressId(progressId)
54 {
55     m_runner = std::make_unique<CppcheckRunner>(*this);
56     QTC_ASSERT(m_progressRegexp.isValid(), return);
57     QTC_ASSERT(m_messageRegexp.isValid(), return);
58 }
59 
60 CppcheckTool::~CppcheckTool() = default;
61 
updateOptions(const CppcheckOptions & options)62 void CppcheckTool::updateOptions(const CppcheckOptions &options)
63 {
64     m_options = options;
65     m_filters.clear();
66     for (const QString &pattern : m_options.ignoredPatterns.split(',')) {
67         const QString trimmedPattern = pattern.trimmed();
68         if (trimmedPattern.isEmpty())
69             continue;
70 
71         const QRegularExpression re(Utils::wildcardToRegularExpression(trimmedPattern));
72         if (re.isValid())
73             m_filters.push_back(re);
74     }
75 
76     updateArguments();
77 }
78 
setProject(ProjectExplorer::Project * project)79 void CppcheckTool::setProject(ProjectExplorer::Project *project)
80 {
81     m_project = project;
82     updateArguments();
83 }
84 
updateArguments()85 void CppcheckTool::updateArguments()
86 {
87     if (!m_project)
88         return;
89 
90     m_cachedAdditionalArguments.clear();
91 
92     QStringList arguments;
93     if (!m_options.customArguments.isEmpty()) {
94         Utils::MacroExpander *expander = Utils::globalMacroExpander();
95         const QString expanded = expander->expand(m_options.customArguments);
96         arguments.push_back(expanded);
97     }
98 
99     if (m_options.warning)
100         arguments.push_back("--enable=warning");
101     if (m_options.style)
102         arguments.push_back("--enable=style");
103     if (m_options.performance)
104         arguments.push_back("--enable=performance");
105     if (m_options.portability)
106         arguments.push_back("--enable=portability");
107     if (m_options.information)
108         arguments.push_back("--enable=information");
109     if (m_options.unusedFunction)
110         arguments.push_back("--enable=unusedFunction");
111     if (m_options.missingInclude)
112         arguments.push_back("--enable=missingInclude");
113     if (m_options.inconclusive)
114         arguments.push_back("--inconclusive");
115     if (m_options.forceDefines)
116         arguments.push_back("--force");
117 
118     if (!m_options.unusedFunction && !m_options.customArguments.contains("-j "))
119         arguments.push_back("-j " + QString::number(QThread::idealThreadCount()));
120 
121     arguments.push_back("--template={file},{line},{severity},{id},{message}");
122 
123     m_runner->reconfigure(m_options.binary, arguments.join(' '));
124 }
125 
additionalArguments(const CppTools::ProjectPart & part) const126 QStringList CppcheckTool::additionalArguments(const CppTools::ProjectPart &part) const
127 {
128     QStringList result;
129 
130     if (m_options.addIncludePaths) {
131         for (const ProjectExplorer::HeaderPath &path : part.headerPaths) {
132             const QString projectDir = m_project->projectDirectory().toString();
133             if (path.type == ProjectExplorer::HeaderPathType::User
134                 && path.path.startsWith(projectDir))
135                 result.push_back("-I " + path.path);
136         }
137     }
138 
139     if (!m_options.guessArguments)
140         return result;
141 
142     using Version = Utils::LanguageVersion;
143     switch (part.languageVersion) {
144     case Version::C89:
145         result.push_back("--std=c89 --language=c");
146         break;
147     case Version::C99:
148         result.push_back("--std=c99 --language=c");
149         break;
150     case Version::C11:
151         result.push_back("--std=c11 --language=c");
152         break;
153     case Version::C18:
154         result.push_back("--language=c");
155         break;
156     case Version::CXX03:
157         result.push_back("--std=c++03 --language=c++");
158         break;
159     case Version::CXX11:
160         result.push_back("--std=c++11 --language=c++");
161         break;
162     case Version::CXX14:
163         result.push_back("--std=c++14 --language=c++");
164         break;
165     case Version::CXX98:
166     case Version::CXX17:
167     case Version::CXX20:
168     case Version::CXX2b:
169         result.push_back("--language=c++");
170         break;
171     case Version::None:
172         break;
173     }
174 
175     if (part.qtVersion != Utils::QtVersion::None)
176         result.push_back("--library=qt");
177 
178     return result;
179 }
180 
options() const181 const CppcheckOptions &CppcheckTool::options() const
182 {
183     return m_options;
184 }
185 
check(const Utils::FilePaths & files)186 void CppcheckTool::check(const Utils::FilePaths &files)
187 {
188     QTC_ASSERT(m_project, return);
189 
190     Utils::FilePaths filtered;
191     if (m_filters.isEmpty()) {
192         filtered = files;
193     } else {
194         std::copy_if(files.cbegin(), files.cend(), std::back_inserter(filtered),
195                      [this](const Utils::FilePath &file) {
196             const QString stringed = file.toString();
197             const auto filter = [stringed](const QRegularExpression &re) {
198                 return re.match(stringed).hasMatch();
199             };
200             return !Utils::contains(m_filters, filter);
201         });
202     }
203 
204     if (filtered.isEmpty())
205         return;
206 
207     const CppTools::ProjectInfo info = CppTools::CppModelManager::instance()->projectInfo(m_project);
208     const QVector<CppTools::ProjectPart::Ptr> parts = info.projectParts();
209     if (parts.size() == 1) {
210         QTC_ASSERT(parts.first(), return);
211         addToQueue(filtered, *parts.first());
212         return;
213     }
214 
215     std::map<CppTools::ProjectPart::Ptr, Utils::FilePaths> groups;
216     for (const Utils::FilePath &file : qAsConst(filtered)) {
217         const QString stringed = file.toString();
218         for (const CppTools::ProjectPart::Ptr &part : parts) {
219             using CppTools::ProjectFile;
220             QTC_ASSERT(part, continue);
221             const auto match = [stringed](const ProjectFile &pFile){return pFile.path == stringed;};
222             if (Utils::contains(part->files, match))
223                 groups[part].push_back(file);
224         }
225     }
226 
227     for (const auto &group : groups)
228         addToQueue(group.second, *group.first);
229 }
230 
addToQueue(const Utils::FilePaths & files,CppTools::ProjectPart & part)231 void CppcheckTool::addToQueue(const Utils::FilePaths &files, CppTools::ProjectPart &part)
232 {
233     const QString key = part.id();
234     if (!m_cachedAdditionalArguments.contains(key))
235         m_cachedAdditionalArguments.insert(key, additionalArguments(part).join(' '));
236     m_runner->addToQueue(files, m_cachedAdditionalArguments[key]);
237 }
238 
stop(const Utils::FilePaths & files)239 void CppcheckTool::stop(const Utils::FilePaths &files)
240 {
241     m_runner->removeFromQueue(files);
242     m_runner->stop(files);
243 }
244 
startParsing()245 void CppcheckTool::startParsing()
246 {
247     if (m_options.showOutput) {
248         const QString message = tr("Cppcheck started: \"%1\".").arg(m_runner->currentCommand());
249         Core::MessageManager::writeSilently(message);
250     }
251 
252     m_progress = std::make_unique<QFutureInterface<void>>();
253     const Core::FutureProgress *progress = Core::ProgressManager::addTask(
254                 m_progress->future(), QObject::tr("Cppcheck"), m_progressId);
255     QObject::connect(progress, &Core::FutureProgress::canceled,
256                      this, [this]{stop({});});
257     m_progress->setProgressRange(0, 100);
258     m_progress->reportStarted();
259 }
260 
parseOutputLine(const QString & line)261 void CppcheckTool::parseOutputLine(const QString &line)
262 {
263     if (line.isEmpty())
264         return;
265 
266     if (m_options.showOutput)
267         Core::MessageManager::writeSilently(line);
268 
269     enum Matches { Percentage = 1 };
270     const QRegularExpressionMatch match = m_progressRegexp.match(line);
271     if (!match.hasMatch())
272         return;
273 
274     QTC_ASSERT(m_progress, return);
275     const int done = match.captured(Percentage).toInt();
276     m_progress->setProgressValue(done);
277 }
278 
toSeverity(const QString & text)279 static Diagnostic::Severity toSeverity(const QString &text)
280 {
281     static const QMap<QString, Diagnostic::Severity> values{
282         {"error", Diagnostic::Severity::Error},
283         {"warning", Diagnostic::Severity::Warning},
284         {"performance", Diagnostic::Severity::Performance},
285         {"portability", Diagnostic::Severity::Portability},
286         {"style", Diagnostic::Severity::Style},
287         {"information", Diagnostic::Severity::Information}
288     };
289     return values.value(text, Diagnostic::Severity::Information);
290 }
291 
parseErrorLine(const QString & line)292 void CppcheckTool::parseErrorLine(const QString &line)
293 {
294     if (line.isEmpty())
295         return;
296 
297     if (m_options.showOutput)
298         Core::MessageManager::writeSilently(line);
299 
300     enum Matches { File = 1, Line, Severity, Id, Message };
301     const QRegularExpressionMatch match = m_messageRegexp.match(line);
302     if (!match.hasMatch())
303         return;
304 
305     const Utils::FilePath fileName = Utils::FilePath::fromUserInput(match.captured(File));
306     if (!m_runner->currentFiles().contains(fileName))
307         return;
308 
309     Diagnostic diagnostic;
310     diagnostic.fileName = fileName;
311     diagnostic.lineNumber = std::max(match.captured(Line).toInt(), 1);
312     diagnostic.severityText = match.captured(Severity);
313     diagnostic.severity = toSeverity(diagnostic.severityText);
314     diagnostic.checkId = match.captured(Id);
315     diagnostic.message = match.captured(Message);
316     if (diagnostic.isValid())
317         m_manager.add(diagnostic);
318 }
319 
finishParsing()320 void CppcheckTool::finishParsing()
321 {
322     if (m_options.showOutput)
323         Core::MessageManager::writeSilently(tr("Cppcheck finished."));
324 
325     QTC_ASSERT(m_progress, return);
326     m_progress->reportFinished();
327 }
328 
329 } // namespace Internal
330 } // namespace Cppcheck
331