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