1 /*
2     SPDX-FileCopyrightText: 2016 Anton Anikin <anton.anikin@htower.ru>
3 
4     SPDX-License-Identifier: GPL-2.0-or-later
5 */
6 
7 #include "parameters.h"
8 
9 #include "globalsettings.h"
10 #include "projectsettings.h"
11 
12 #include <interfaces/iplugin.h>
13 #include <interfaces/iproject.h>
14 #include <project/interfaces/ibuildsystemmanager.h>
15 #include <project/projectmodel.h>
16 
17 #include <KShell>
18 #include <KLocalizedString>
19 
20 #include <QFile>
21 #include <QRegularExpression>
22 
23 namespace cppcheck
24 {
25 
includesForItem(KDevelop::ProjectBaseItem * parent,QSet<KDevelop::Path> & includes)26 void includesForItem(KDevelop::ProjectBaseItem* parent, QSet<KDevelop::Path>& includes)
27 {
28     const auto children = parent->children();
29     for (auto* child : children) {
30         if (child->type() == KDevelop::ProjectBaseItem::ProjectItemType::File) {
31             continue;
32         }
33 
34         else if (child->type() == KDevelop::ProjectBaseItem::ProjectItemType::ExecutableTarget ||
35                  child->type() == KDevelop::ProjectBaseItem::ProjectItemType::LibraryTarget ||
36                  child->type() == KDevelop::ProjectBaseItem::ProjectItemType::Target) {
37 
38             if (auto buildSystemManager = child->project()->buildSystemManager()) {
39                 const auto includeDirectories = buildSystemManager->includeDirectories(child);
40                 for (auto& dir : includeDirectories) {
41                     includes.insert(dir);
42                 }
43             }
44         }
45 
46         includesForItem(child, includes);
47     }
48 }
49 
includesForProject(KDevelop::IProject * project)50 QList<KDevelop::Path> includesForProject(KDevelop::IProject* project)
51 {
52     QSet<KDevelop::Path> includesSet;
53     includesForItem(project->projectItem(), includesSet);
54 
55     return includesSet.values();
56 }
57 
Parameters(KDevelop::IProject * project)58 Parameters::Parameters(KDevelop::IProject* project)
59     : m_project(project)
60 {
61     executablePath = KDevelop::Path(GlobalSettings::executablePath()).toLocalFile();
62     hideOutputView = GlobalSettings::hideOutputView();
63     showXmlOutput  = GlobalSettings::showXmlOutput();
64 
65     if (!project) {
66         checkStyle           = defaults::checkStyle;
67         checkPerformance     = defaults::checkPerformance;
68         checkPortability     = defaults::checkPortability;
69         checkInformation     = defaults::checkInformation;
70         checkUnusedFunction  = defaults::checkUnusedFunction;
71         checkMissingInclude  = defaults::checkMissingInclude;
72         inconclusiveAnalysis = defaults::inconclusiveAnalysis;
73         forceCheck           = defaults::forceCheck;
74         checkConfig          = defaults::checkConfig;
75 
76         useProjectIncludes   = defaults::useProjectIncludes;
77         useSystemIncludes    = defaults::useSystemIncludes;
78 
79         return;
80     }
81 
82     ProjectSettings projectSettings;
83     projectSettings.setSharedConfig(project->projectConfiguration());
84     projectSettings.load();
85 
86     checkStyle           = projectSettings.checkStyle();
87     checkPerformance     = projectSettings.checkPerformance();
88     checkPortability     = projectSettings.checkPortability();
89     checkInformation     = projectSettings.checkInformation();
90     checkUnusedFunction  = projectSettings.checkUnusedFunction();
91     checkMissingInclude  = projectSettings.checkMissingInclude();
92     inconclusiveAnalysis = projectSettings.inconclusiveAnalysis();
93     forceCheck           = projectSettings.forceCheck();
94     checkConfig          = projectSettings.checkConfig();
95 
96     useProjectIncludes   = projectSettings.useProjectIncludes();
97     useSystemIncludes    = projectSettings.useSystemIncludes();
98     ignoredIncludes      = projectSettings.ignoredIncludes();
99 
100     extraParameters      = projectSettings.extraParameters();
101 
102     m_projectRootPath    = m_project->path();
103 
104     if (auto buildSystemManager = m_project->buildSystemManager()) {
105         m_projectBuildPath   = buildSystemManager->buildDirectory(m_project->projectItem());
106     }
107     m_includeDirectories = includesForProject(project);
108 }
109 
commandLine() const110 QStringList Parameters::commandLine() const
111 {
112     QString temp;
113     return commandLine(temp);
114 }
115 
commandLine(QString & infoMessage) const116 QStringList Parameters::commandLine(QString& infoMessage) const
117 {
118     static const auto mocHeaderRegex = QRegularExpression(QStringLiteral("#define\\s+Q_MOC_OUTPUT_REVISION\\s+(.+)"));
119     static const auto mocParametersRegex = QRegularExpression(QStringLiteral("-DQ_MOC_OUTPUT_REVISION=\\d{2,}"));
120 
121     const QString mocMessage = i18n(
122         "It seems that this project uses Qt library. For correctly work of cppcheck "
123         "the value for define Q_MOC_OUTPUT_REVISION must be set. Unfortunately, the plugin is unable "
124         "to find this value automatically - you should set it manually by adding "
125         "'-DQ_MOC_OUTPUT_REVISION=XX' to extra parameters. The 'XX' value can be found in any project's "
126         "moc-generated file or in the <QtCore/qobjectdefs.h> header file.");
127 
128     QStringList result;
129 
130     result << executablePath;
131     result << QStringLiteral("--xml-version=2");
132 
133     if (checkStyle) {
134         result << QStringLiteral("--enable=style");
135     }
136 
137     if (checkPerformance) {
138         result << QStringLiteral("--enable=performance");
139     }
140 
141     if (checkPortability) {
142         result << QStringLiteral("--enable=portability");
143     }
144 
145     if (checkInformation) {
146         result << QStringLiteral("--enable=information");
147     }
148 
149     if (checkUnusedFunction) {
150         result << QStringLiteral("--enable=unusedFunction");
151     }
152 
153     if (checkMissingInclude) {
154         result << QStringLiteral("--enable=missingInclude");
155     }
156 
157     if (inconclusiveAnalysis) {
158         result << QStringLiteral("--inconclusive");
159     }
160 
161     if (forceCheck) {
162         result << QStringLiteral("--force");
163     }
164 
165     if (checkConfig) {
166         result << QStringLiteral("--check-config");
167     }
168 
169     // Try to automatically get value of Q_MOC_OUTPUT_REVISION for Qt-projects.
170     // If such define is not correctly set, cppcheck 'fails' on files with moc-includes
171     // and not return any errors, even if the file contains them.
172     if (!mocParametersRegex.match(extraParameters).hasMatch()) {
173         bool qtUsed = false;
174         bool mocDefineFound = false;
175         for (const auto& dir : m_includeDirectories) {
176             if (dir.path().endsWith(QLatin1String("QtCore"))) {
177                 qtUsed = true;
178 
179                 QFile qtHeader(dir.path() + QStringLiteral("/qobjectdefs.h"));
180                 if (!qtHeader.open(QIODevice::ReadOnly)) {
181                     break;
182                 }
183 
184                 while(!qtHeader.atEnd()) {
185                     auto match = mocHeaderRegex.match(QString::fromUtf8(qtHeader.readLine()));
186                     if (match.hasMatch()) {
187                         mocDefineFound = true;
188                         result << QLatin1String("-DQ_MOC_OUTPUT_REVISION=") + match.capturedRef(1);
189                         break;
190                     }
191                 }
192                 break;
193             }
194         }
195 
196         if (qtUsed && !mocDefineFound) {
197             infoMessage = mocMessage;
198         }
199     }
200 
201     if (!extraParameters.isEmpty()) {
202         result << KShell::splitArgs(applyPlaceholders(extraParameters));
203     }
204 
205     if (m_project && useProjectIncludes) {
206         QList<KDevelop::Path> ignored;
207 
208         const auto elements = applyPlaceholders(ignoredIncludes).split(QLatin1Char(';'));
209         for (const QString& element : elements) {
210             if (!element.trimmed().isEmpty()) {
211                 ignored.append(KDevelop::Path(element));
212             }
213         }
214 
215         for (const auto& dir : m_includeDirectories) {
216             if (ignored.contains(dir)) {
217                 continue;
218             }
219 
220             else if (useSystemIncludes ||
221                      dir == m_projectRootPath || m_projectRootPath.isParentOf(dir) ||
222                      dir == m_projectBuildPath || m_projectBuildPath.isParentOf(dir)) {
223 
224                 result << QStringLiteral("-I");
225                 result << dir.toLocalFile();
226             }
227         }
228     }
229 
230     if (m_project && m_project->managerPlugin()) {
231         if (m_project->managerPlugin()->componentName() == QLatin1String("kdevcmakemanager")) {
232             result << QStringLiteral("-i")
233                    << m_projectBuildPath.toLocalFile() + QLatin1String("/CMakeFiles");
234         }
235     }
236 
237     result << checkPath;
238 
239     return result;
240 }
241 
projectRootPath() const242 KDevelop::Path Parameters::projectRootPath() const
243 {
244     return m_projectRootPath;
245 }
246 
applyPlaceholders(const QString & text) const247 QString Parameters::applyPlaceholders(const QString& text) const
248 {
249     QString result(text);
250 
251     if (m_project) {
252         result.replace(QLatin1String("%p"), m_projectRootPath.toLocalFile());
253         result.replace(QLatin1String("%b"), m_projectBuildPath.toLocalFile());
254     }
255 
256     return result;
257 }
258 
259 }
260