File indexing completed on 2024-04-21 05:38:45

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 &registeredCheck : 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 }