File indexing completed on 2024-05-05 16:45:13
0001 /* 0002 SPDX-FileCopyrightText: 2016 Anton Anikin <anton.anikin@htower.ru> 0003 0004 SPDX-License-Identifier: GPL-2.0-or-later 0005 */ 0006 0007 #include "parameters.h" 0008 0009 #include "globalsettings.h" 0010 #include "projectsettings.h" 0011 0012 #include <interfaces/iplugin.h> 0013 #include <interfaces/iproject.h> 0014 #include <project/interfaces/ibuildsystemmanager.h> 0015 #include <project/projectmodel.h> 0016 0017 #include <KShell> 0018 #include <KLocalizedString> 0019 0020 #include <QFile> 0021 #include <QRegularExpression> 0022 0023 namespace cppcheck 0024 { 0025 0026 void includesForItem(KDevelop::ProjectBaseItem* parent, QSet<KDevelop::Path>& includes) 0027 { 0028 // FIXME: only one build system manager - QMakeManager - ever returns a nonempty include directory list 0029 // for a target item. With any other project build system manager, cppcheck::Parameters::m_includeDirectories 0030 // is always empty. For example, during the loop below CMakeManager prints the following debug message: 0031 // kdevelop.plugins.cmake: no information found for "" 0032 // because a target item's path() is always empty. 0033 // The algorithm here attempts to collect include directories for all project targets and use them to analyze any 0034 // file. This algorithm can be patched up by passing the first child of a target item instead of the target item 0035 // itself to IBuildSystemManager::includeDirectories(). However, the include directories would needlessly contain 0036 // include paths for all unit tests then. Also each file in a target can potentially have different include paths. 0037 // So perhaps the current algorithm should be replaced with something else altogether: 0038 // 1. When KDevelop runs cppcheck on a single file, that file's project item should be passed to 0039 // IBuildSystemManager::includeDirectories(). 0040 // 2. When KDevelop runs cppcheck on an entire project, it passes the project's root directory to cppcheck 0041 // on the command line. cppcheck checks all source files in the given directory recursively. 0042 // db0d8027749ba8c94702981ccb3062fa6c6006eb even implemented a workaround to skip cppcheck-ing files in 0043 // <current build dir>/CMakeFiles/. That workaround is not perfect: what if there are multiple build 0044 // subdirectories (e.g. debug and release) within the project's directory? Instead of running a single 0045 // cppcheck command, we could run a separate cppcheck command for each not-filtered-out source file in 0046 // the project. This way we could pass each file's project item to IBuildSystemManager::includeDirectories() 0047 // and use a separate include directory list to analyze every file. But this would require determining which 0048 // files should and which shouldn't be analyzed (C and C++ MIME types?), and might be slower. 0049 // Apparently fixing this issue properly requires substantial refactoring and testing effort. 0050 0051 const auto children = parent->children(); 0052 for (auto* child : children) { 0053 if (child->type() == KDevelop::ProjectBaseItem::ProjectItemType::File) { 0054 continue; 0055 } 0056 0057 else if (child->type() == KDevelop::ProjectBaseItem::ProjectItemType::ExecutableTarget || 0058 child->type() == KDevelop::ProjectBaseItem::ProjectItemType::LibraryTarget || 0059 child->type() == KDevelop::ProjectBaseItem::ProjectItemType::Target) { 0060 0061 if (auto buildSystemManager = child->project()->buildSystemManager()) { 0062 const auto includeDirectories = buildSystemManager->includeDirectories(child); 0063 for (auto& dir : includeDirectories) { 0064 includes.insert(dir); 0065 } 0066 } 0067 } 0068 0069 includesForItem(child, includes); 0070 } 0071 } 0072 0073 QList<KDevelop::Path> includesForProject(KDevelop::IProject* project) 0074 { 0075 QSet<KDevelop::Path> includesSet; 0076 includesForItem(project->projectItem(), includesSet); 0077 0078 return includesSet.values(); 0079 } 0080 0081 Parameters::Parameters(KDevelop::IProject* project) 0082 : m_project(project) 0083 { 0084 executablePath = KDevelop::Path(GlobalSettings::executablePath()).toLocalFile(); 0085 hideOutputView = GlobalSettings::hideOutputView(); 0086 showXmlOutput = GlobalSettings::showXmlOutput(); 0087 0088 if (!project) { 0089 checkStyle = defaults::checkStyle; 0090 checkPerformance = defaults::checkPerformance; 0091 checkPortability = defaults::checkPortability; 0092 checkInformation = defaults::checkInformation; 0093 checkUnusedFunction = defaults::checkUnusedFunction; 0094 checkMissingInclude = defaults::checkMissingInclude; 0095 inconclusiveAnalysis = defaults::inconclusiveAnalysis; 0096 forceCheck = defaults::forceCheck; 0097 checkConfig = defaults::checkConfig; 0098 0099 useProjectIncludes = defaults::useProjectIncludes; 0100 useSystemIncludes = defaults::useSystemIncludes; 0101 0102 return; 0103 } 0104 0105 ProjectSettings projectSettings; 0106 projectSettings.setSharedConfig(project->projectConfiguration()); 0107 projectSettings.load(); 0108 0109 checkStyle = projectSettings.checkStyle(); 0110 checkPerformance = projectSettings.checkPerformance(); 0111 checkPortability = projectSettings.checkPortability(); 0112 checkInformation = projectSettings.checkInformation(); 0113 checkUnusedFunction = projectSettings.checkUnusedFunction(); 0114 checkMissingInclude = projectSettings.checkMissingInclude(); 0115 inconclusiveAnalysis = projectSettings.inconclusiveAnalysis(); 0116 forceCheck = projectSettings.forceCheck(); 0117 checkConfig = projectSettings.checkConfig(); 0118 0119 useProjectIncludes = projectSettings.useProjectIncludes(); 0120 useSystemIncludes = projectSettings.useSystemIncludes(); 0121 ignoredIncludes = projectSettings.ignoredIncludes(); 0122 0123 extraParameters = projectSettings.extraParameters(); 0124 0125 m_projectRootPath = m_project->path(); 0126 0127 if (auto buildSystemManager = m_project->buildSystemManager()) { 0128 m_projectBuildPath = buildSystemManager->buildDirectory(m_project->projectItem()); 0129 } 0130 m_includeDirectories = includesForProject(project); 0131 } 0132 0133 QStringList Parameters::commandLine() const 0134 { 0135 QString temp; 0136 return commandLine(temp); 0137 } 0138 0139 QStringList Parameters::commandLine(QString& infoMessage) const 0140 { 0141 static const auto mocHeaderRegex = QRegularExpression(QStringLiteral("#define\\s+Q_MOC_OUTPUT_REVISION\\s+(.+)")); 0142 static const auto mocParametersRegex = QRegularExpression(QStringLiteral("-DQ_MOC_OUTPUT_REVISION=\\d{2,}")); 0143 0144 const QString mocMessage = i18n( 0145 "It seems that this project uses Qt library. For correctly work of cppcheck " 0146 "the value for define Q_MOC_OUTPUT_REVISION must be set. Unfortunately, the plugin is unable " 0147 "to find this value automatically - you should set it manually by adding " 0148 "'-DQ_MOC_OUTPUT_REVISION=XX' to extra parameters. The 'XX' value can be found in any project's " 0149 "moc-generated file or in the <QtCore/qobjectdefs.h> header file."); 0150 0151 QStringList result; 0152 0153 result << executablePath; 0154 result << QStringLiteral("--xml-version=2"); 0155 0156 if (checkStyle) { 0157 result << QStringLiteral("--enable=style"); 0158 } 0159 0160 if (checkPerformance) { 0161 result << QStringLiteral("--enable=performance"); 0162 } 0163 0164 if (checkPortability) { 0165 result << QStringLiteral("--enable=portability"); 0166 } 0167 0168 if (checkInformation) { 0169 result << QStringLiteral("--enable=information"); 0170 } 0171 0172 if (checkUnusedFunction) { 0173 result << QStringLiteral("--enable=unusedFunction"); 0174 } 0175 0176 if (checkMissingInclude) { 0177 result << QStringLiteral("--enable=missingInclude"); 0178 } 0179 0180 if (inconclusiveAnalysis) { 0181 result << QStringLiteral("--inconclusive"); 0182 } 0183 0184 if (forceCheck) { 0185 result << QStringLiteral("--force"); 0186 } 0187 0188 if (checkConfig) { 0189 result << QStringLiteral("--check-config"); 0190 } 0191 0192 // Try to automatically get value of Q_MOC_OUTPUT_REVISION for Qt-projects. 0193 // If such define is not correctly set, cppcheck 'fails' on files with moc-includes 0194 // and not return any errors, even if the file contains them. 0195 if (!mocParametersRegex.match(extraParameters).hasMatch()) { 0196 bool qtUsed = false; 0197 bool mocDefineFound = false; 0198 for (const auto& dir : m_includeDirectories) { 0199 if (dir.path().endsWith(QLatin1String("QtCore"))) { 0200 qtUsed = true; 0201 0202 QFile qtHeader(dir.path() + QStringLiteral("/qobjectdefs.h")); 0203 if (!qtHeader.open(QIODevice::ReadOnly)) { 0204 break; 0205 } 0206 0207 while(!qtHeader.atEnd()) { 0208 auto match = mocHeaderRegex.match(QString::fromUtf8(qtHeader.readLine())); 0209 if (match.hasMatch()) { 0210 mocDefineFound = true; 0211 result << QLatin1String("-DQ_MOC_OUTPUT_REVISION=") + match.capturedRef(1); 0212 break; 0213 } 0214 } 0215 break; 0216 } 0217 } 0218 0219 if (qtUsed && !mocDefineFound) { 0220 infoMessage = mocMessage; 0221 } 0222 } 0223 0224 if (!extraParameters.isEmpty()) { 0225 result << KShell::splitArgs(applyPlaceholders(extraParameters)); 0226 } 0227 0228 if (m_project && useProjectIncludes) { 0229 QList<KDevelop::Path> ignored; 0230 0231 const auto elements = applyPlaceholders(ignoredIncludes).split(QLatin1Char(';')); 0232 for (const QString& element : elements) { 0233 if (!element.trimmed().isEmpty()) { 0234 ignored.append(KDevelop::Path(element)); 0235 } 0236 } 0237 0238 for (const auto& dir : m_includeDirectories) { 0239 if (ignored.contains(dir)) { 0240 continue; 0241 } 0242 0243 else if (useSystemIncludes || 0244 dir == m_projectRootPath || m_projectRootPath.isParentOf(dir) || 0245 dir == m_projectBuildPath || m_projectBuildPath.isParentOf(dir)) { 0246 0247 result << QStringLiteral("-I"); 0248 result << dir.toLocalFile(); 0249 } 0250 } 0251 } 0252 0253 if (m_project && m_project->managerPlugin()) { 0254 if (m_project->managerPlugin()->componentName() == QLatin1String("kdevcmakemanager")) { 0255 result << QStringLiteral("-i") 0256 << m_projectBuildPath.toLocalFile() + QLatin1String("/CMakeFiles"); 0257 } 0258 } 0259 0260 result << checkPath; 0261 0262 return result; 0263 } 0264 0265 KDevelop::Path Parameters::projectRootPath() const 0266 { 0267 return m_projectRootPath; 0268 } 0269 0270 QString Parameters::applyPlaceholders(const QString& text) const 0271 { 0272 QString result(text); 0273 0274 if (m_project) { 0275 result.replace(QLatin1String("%p"), m_projectRootPath.toLocalFile()); 0276 result.replace(QLatin1String("%b"), m_projectBuildPath.toLocalFile()); 0277 } 0278 0279 return result; 0280 } 0281 0282 }