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