File indexing completed on 2024-04-28 13:34:27
0001 /* 0002 SPDX-FileCopyrightText: 2015 Klarälvdalens Datakonsult AB a KDAB Group company info@kdab.com 0003 SPDX-FileContributor: SĂ©rgio Martins <sergio.martins@kdab.com> 0004 0005 SPDX-FileCopyrightText: 2015 Sergio Martins <smartins@kde.org> 0006 0007 SPDX-License-Identifier: LGPL-2.0-or-later 0008 */ 0009 0010 #include "checkmanager.h" 0011 #include "Checks.h" 0012 #include "ClazyContext.h" 0013 #include "clazy_stl.h" 0014 0015 #include <llvm/Support/raw_ostream.h> 0016 0017 #include <algorithm> 0018 #include <assert.h> 0019 #include <iterator> 0020 #include <stdlib.h> 0021 #include <string.h> 0022 0023 using namespace clang; 0024 0025 static const char *s_fixitNamePrefix = "fix-"; 0026 static const char *s_levelPrefix = "level"; 0027 0028 std::mutex CheckManager::m_lock; 0029 0030 CheckManager::CheckManager() 0031 { 0032 m_registeredChecks.reserve(100); 0033 registerChecks(); 0034 } 0035 0036 bool CheckManager::checkExists(const std::string &name) const 0037 { 0038 return checkForName(m_registeredChecks, name) != m_registeredChecks.cend(); 0039 } 0040 0041 CheckManager *CheckManager::instance() 0042 { 0043 static CheckManager s_instance; 0044 return &s_instance; 0045 } 0046 0047 void CheckManager::registerCheck(const RegisteredCheck &check) 0048 { 0049 m_registeredChecks.push_back(check); 0050 } 0051 0052 void CheckManager::registerFixIt(int id, const std::string &fixitName, const std::string &checkName) 0053 { 0054 if (!clazy::startsWith(fixitName, s_fixitNamePrefix)) { 0055 assert(false); 0056 return; 0057 } 0058 0059 auto &fixits = m_fixitsByCheckName[checkName]; 0060 for (const auto &fixit : fixits) { 0061 if (fixit.name == fixitName) { 0062 // It can't exist 0063 assert(false); 0064 return; 0065 } 0066 } 0067 RegisteredFixIt fixit = {id, fixitName}; 0068 fixits.push_back(fixit); 0069 m_fixitByName.insert({fixitName, fixit}); 0070 } 0071 0072 CheckBase *CheckManager::createCheck(const std::string &name, ClazyContext *context) 0073 { 0074 for (const auto &rc : m_registeredChecks) { 0075 if (rc.name == name) { 0076 return rc.factory(context); 0077 } 0078 } 0079 0080 llvm::errs() << "Invalid check name " << name << "\n"; 0081 return nullptr; 0082 } 0083 0084 std::string CheckManager::checkNameForFixIt(const std::string &fixitName) const 0085 { 0086 if (fixitName.empty()) { 0087 return {}; 0088 } 0089 0090 for (const auto ®isteredCheck : m_registeredChecks) { 0091 auto it = m_fixitsByCheckName.find(registeredCheck.name); 0092 if (it != m_fixitsByCheckName.end()) { 0093 const auto &fixits = (*it).second; 0094 for (const RegisteredFixIt &fixit : fixits) { 0095 if (fixit.name == fixitName) { 0096 return (*it).first; 0097 } 0098 } 0099 } 0100 } 0101 0102 return {}; 0103 } 0104 0105 RegisteredCheck::List CheckManager::availableChecks(CheckLevel maxLevel) const 0106 { 0107 RegisteredCheck::List checks = m_registeredChecks; 0108 0109 checks.erase(remove_if(checks.begin(), 0110 checks.end(), 0111 [maxLevel](const RegisteredCheck &r) { 0112 return r.level > maxLevel; 0113 }), 0114 checks.end()); 0115 0116 return checks; 0117 } 0118 0119 RegisteredCheck::List CheckManager::requestedChecksThroughEnv(std::vector<std::string> &userDisabledChecks) const 0120 { 0121 static RegisteredCheck::List requestedChecksThroughEnv; 0122 static std::vector<std::string> disabledChecksThroughEnv; 0123 if (requestedChecksThroughEnv.empty()) { 0124 const char *checksEnv = getenv("CLAZY_CHECKS"); 0125 if (checksEnv) { 0126 const std::string checksEnvStr = clazy::unquoteString(checksEnv); 0127 requestedChecksThroughEnv = 0128 checksEnvStr == "all_checks" ? availableChecks(CheckLevel2) : checksForCommaSeparatedString(checksEnvStr, /*by-ref=*/disabledChecksThroughEnv); 0129 } 0130 } 0131 0132 std::copy(disabledChecksThroughEnv.begin(), disabledChecksThroughEnv.end(), std::back_inserter(userDisabledChecks)); 0133 return requestedChecksThroughEnv; 0134 } 0135 0136 RegisteredCheck::List::const_iterator CheckManager::checkForName(const RegisteredCheck::List &checks, const std::string &name) const 0137 { 0138 return clazy::find_if(checks, [name](const RegisteredCheck &r) { 0139 return r.name == name; 0140 }); 0141 } 0142 0143 RegisteredFixIt::List CheckManager::availableFixIts(const std::string &checkName) const 0144 { 0145 auto it = m_fixitsByCheckName.find(checkName); 0146 return it == m_fixitsByCheckName.end() ? RegisteredFixIt::List() : (*it).second; 0147 } 0148 0149 static bool takeArgument(const std::string &arg, std::vector<std::string> &args) 0150 { 0151 auto it = clazy::find(args, arg); 0152 if (it != args.end()) { 0153 args.erase(it, it + 1); 0154 return true; 0155 } 0156 0157 return false; 0158 } 0159 0160 RegisteredCheck::List CheckManager::requestedChecks(std::vector<std::string> &args, bool qt4Compat) 0161 { 0162 RegisteredCheck::List result; 0163 0164 // #1 Check if a level was specified 0165 static const std::vector<std::string> levels = {"level0", "level1", "level2"}; 0166 const int numLevels = levels.size(); 0167 CheckLevel requestedLevel = CheckLevelUndefined; 0168 for (int i = 0; i < numLevels; ++i) { 0169 if (takeArgument(levels.at(i), args)) { 0170 requestedLevel = static_cast<CheckLevel>(i); 0171 break; 0172 } 0173 } 0174 0175 if (args.size() > 1) { // we only expect a level and a comma separated list of arguments 0176 return {}; 0177 } 0178 0179 std::vector<std::string> userDisabledChecks; 0180 if (args.size() == 1) { 0181 // #2 Process list of comma separated checks that were passed to compiler 0182 result = checksForCommaSeparatedString(args[0], /*by-ref*/ userDisabledChecks); 0183 if (result.empty() && userDisabledChecks.empty()) { // User passed inexisting checks. 0184 return {}; 0185 } 0186 } 0187 0188 // #3 Append checks specified from env variable 0189 RegisteredCheck::List checksFromEnv = requestedChecksThroughEnv(/*by-ref*/ userDisabledChecks); 0190 copy(checksFromEnv.cbegin(), checksFromEnv.cend(), back_inserter(result)); 0191 0192 if (result.empty() && requestedLevel == CheckLevelUndefined) { 0193 // No checks or level specified, lets use the default level 0194 requestedLevel = DefaultCheckLevel; 0195 } 0196 0197 // #4 Add checks from requested level 0198 RegisteredCheck::List checksFromRequestedLevel = checksForLevel(requestedLevel); 0199 clazy::append(checksFromRequestedLevel, result); 0200 clazy::sort_and_remove_dups(result, checkLessThan); 0201 CheckManager::removeChecksFromList(result, userDisabledChecks); 0202 0203 if (qt4Compat) { 0204 // #5 Remove Qt4 incompatible checks 0205 result.erase(remove_if(result.begin(), 0206 result.end(), 0207 [](const RegisteredCheck &c) { 0208 return c.options & RegisteredCheck::Option_Qt4Incompatible; 0209 }), 0210 result.end()); 0211 } 0212 0213 return result; 0214 } 0215 0216 RegisteredCheck::List CheckManager::checksForLevel(int level) const 0217 { 0218 RegisteredCheck::List result; 0219 if (level > CheckLevelUndefined && level <= MaxCheckLevel) { 0220 clazy::append_if(m_registeredChecks, result, [level](const RegisteredCheck &r) { 0221 return r.level <= level; 0222 }); 0223 } 0224 0225 return result; 0226 } 0227 0228 std::vector<std::pair<CheckBase *, RegisteredCheck>> CheckManager::createChecks(const RegisteredCheck::List &requestedChecks, ClazyContext *context) 0229 { 0230 assert(context); 0231 0232 std::vector<std::pair<CheckBase *, RegisteredCheck>> checks; 0233 checks.reserve(requestedChecks.size() + 1); 0234 for (const auto &check : requestedChecks) { 0235 checks.push_back({createCheck(check.name, context), check}); 0236 } 0237 0238 return checks; 0239 } 0240 0241 /*static */ 0242 void CheckManager::removeChecksFromList(RegisteredCheck::List &list, std::vector<std::string> &checkNames) 0243 { 0244 for (auto &name : checkNames) { 0245 list.erase(remove_if(list.begin(), 0246 list.end(), 0247 [name](const RegisteredCheck &c) { 0248 return c.name == name; 0249 }), 0250 list.end()); 0251 } 0252 } 0253 0254 RegisteredCheck::List CheckManager::checksForCommaSeparatedString(const std::string &str) const 0255 { 0256 std::vector<std::string> byRefDummy; 0257 return checksForCommaSeparatedString(str, byRefDummy); 0258 } 0259 0260 RegisteredCheck::List CheckManager::checksForCommaSeparatedString(const std::string &str, std::vector<std::string> &userDisabledChecks) const 0261 { 0262 std::vector<std::string> checkNames = clazy::splitString(str, ','); 0263 RegisteredCheck::List result; 0264 0265 for (const std::string &name : checkNames) { 0266 if (checkForName(result, name) != result.cend()) { 0267 continue; // Already added. Duplicate check specified. continue. 0268 } 0269 0270 auto it = checkForName(m_registeredChecks, name); 0271 if (it == m_registeredChecks.cend()) { 0272 // Unknown, but might be a fixit name 0273 const std::string checkName = checkNameForFixIt(name); 0274 auto it = checkForName(m_registeredChecks, checkName); 0275 const bool checkDoesntExist = it == m_registeredChecks.cend(); 0276 if (checkDoesntExist) { 0277 if (clazy::startsWith(name, s_levelPrefix) && name.size() == strlen(s_levelPrefix) + 1) { 0278 auto lastChar = name.back(); 0279 const int digit = lastChar - '0'; 0280 if (digit > CheckLevelUndefined && digit <= MaxCheckLevel) { 0281 RegisteredCheck::List levelChecks = checksForLevel(digit); 0282 clazy::append(levelChecks, result); 0283 } else { 0284 llvm::errs() << "Invalid level: " << name << "\n"; 0285 } 0286 } else { 0287 if (clazy::startsWith(name, "no-")) { 0288 std::string checkName = name; 0289 checkName.erase(0, 3); 0290 if (checkExists(checkName)) { 0291 userDisabledChecks.push_back(checkName); 0292 } else { 0293 llvm::errs() << "Invalid check to disable: " << name << "\n"; 0294 } 0295 } else { 0296 llvm::errs() << "Invalid check: " << name << "\n"; 0297 } 0298 } 0299 } else { 0300 result.push_back(*it); 0301 } 0302 continue; 0303 } 0304 result.push_back(*it); 0305 } 0306 0307 removeChecksFromList(result, userDisabledChecks); 0308 0309 return result; 0310 } 0311 0312 std::vector<std::string> CheckManager::checksAsErrors() const 0313 { 0314 auto *checksAsErrosEnv = getenv("CLAZY_CHECKS_AS_ERRORS"); 0315 0316 if (checksAsErrosEnv) { 0317 auto checkNames = clazy::splitString(checksAsErrosEnv, ','); 0318 std::vector<std::string> result; 0319 0320 // Check whether all supplied check names are valid 0321 for (const std::string &name : checkNames) { 0322 auto it = clazy::find_if(m_registeredChecks, [&name](const RegisteredCheck &check) { 0323 return check.name == name; 0324 }); 0325 if (it == m_registeredChecks.end()) { 0326 llvm::errs() << "Invalid check: " << name << '\n'; 0327 } else { 0328 result.emplace_back(name); 0329 } 0330 } 0331 return result; 0332 } 0333 return {}; 0334 }