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