File indexing completed on 2024-04-28 13:34:28

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-2017 Sergio Martins <smartins@kde.org>
0006 
0007     SPDX-License-Identifier: LGPL-2.0-or-later
0008 */
0009 
0010 #include "Clazy.h"
0011 #include "AccessSpecifierManager.h"
0012 #include "FixItExporter.h"
0013 #include "Utils.h"
0014 #include "checkbase.h"
0015 #include "clazy_stl.h"
0016 
0017 #include <clang/AST/ASTConsumer.h>
0018 #include <clang/AST/ASTContext.h>
0019 #include <clang/AST/Decl.h>
0020 #include <clang/AST/DeclBase.h>
0021 #include <clang/AST/DeclCXX.h>
0022 #include <clang/AST/ParentMap.h>
0023 #include <clang/AST/Stmt.h>
0024 #include <clang/AST/StmtCXX.h>
0025 #include <clang/Basic/Diagnostic.h>
0026 #include <clang/Basic/LLVM.h>
0027 #include <clang/Basic/SourceLocation.h>
0028 #include <clang/Basic/SourceManager.h>
0029 #include <clang/Frontend/CompilerInstance.h>
0030 #include <clang/Frontend/FrontendAction.h>
0031 #include <clang/Frontend/FrontendPluginRegistry.h>
0032 #include <llvm/Support/Casting.h>
0033 #include <llvm/Support/raw_ostream.h>
0034 
0035 #include <mutex>
0036 #include <stdlib.h>
0037 
0038 using namespace clang;
0039 using namespace clang::ast_matchers;
0040 
0041 static void manuallyPopulateParentMap(ParentMap *map, Stmt *s)
0042 {
0043     if (!s) {
0044         return;
0045     }
0046 
0047     for (Stmt *child : s->children()) {
0048         llvm::errs() << "Patching " << child->getStmtClassName() << "\n";
0049         map->setParent(child, s);
0050         manuallyPopulateParentMap(map, child);
0051     }
0052 }
0053 
0054 ClazyASTConsumer::ClazyASTConsumer(ClazyContext *context)
0055     : m_context(context)
0056 {
0057 #ifndef CLAZY_DISABLE_AST_MATCHERS
0058     m_matchFinder = new clang::ast_matchers::MatchFinder();
0059 #endif
0060 }
0061 
0062 void ClazyASTConsumer::addCheck(const std::pair<CheckBase *, RegisteredCheck> &check)
0063 {
0064     CheckBase *checkBase = check.first;
0065 #ifndef CLAZY_DISABLE_AST_MATCHERS
0066     checkBase->registerASTMatchers(*m_matchFinder);
0067 #endif
0068     // m_createdChecks.push_back(checkBase);
0069 
0070     const RegisteredCheck &rcheck = check.second;
0071 
0072     if (rcheck.options & RegisteredCheck::Option_VisitsStmts) {
0073         m_checksToVisitStmts.push_back(checkBase);
0074     }
0075 
0076     if (rcheck.options & RegisteredCheck::Option_VisitsDecls) {
0077         m_checksToVisitDecls.push_back(checkBase);
0078     }
0079 }
0080 
0081 ClazyASTConsumer::~ClazyASTConsumer()
0082 {
0083 #ifndef CLAZY_DISABLE_AST_MATCHERS
0084     delete m_matchFinder;
0085 #endif
0086     delete m_context;
0087 }
0088 
0089 bool ClazyASTConsumer::VisitDecl(Decl *decl)
0090 {
0091     if (AccessSpecifierManager *a = m_context->accessSpecifierManager) { // Needs to visit system headers too (qobject.h for example)
0092         a->VisitDeclaration(decl);
0093     }
0094 
0095     const bool isTypeDefToVisit = m_context->visitsAllTypedefs() && isa<TypedefNameDecl>(decl);
0096     const SourceLocation locStart = decl->getBeginLoc();
0097     if (locStart.isInvalid() || (m_context->sm.isInSystemHeader(locStart) && !isTypeDefToVisit)) {
0098         return true;
0099     }
0100 
0101     const bool isFromIgnorableInclude = m_context->ignoresIncludedFiles() && !Utils::isMainFile(m_context->sm, locStart);
0102 
0103     m_context->lastDecl = decl;
0104 
0105     if (auto *fdecl = dyn_cast<FunctionDecl>(decl)) {
0106         m_context->lastFunctionDecl = fdecl;
0107         if (auto *mdecl = dyn_cast<CXXMethodDecl>(fdecl)) {
0108             m_context->lastMethodDecl = mdecl;
0109         }
0110     }
0111 
0112     for (CheckBase *check : m_checksToVisitDecls) {
0113         if (!(isFromIgnorableInclude && check->canIgnoreIncludes())) {
0114             check->VisitDecl(decl);
0115         }
0116     }
0117 
0118     return true;
0119 }
0120 
0121 bool ClazyASTConsumer::VisitStmt(Stmt *stm)
0122 {
0123     const SourceLocation locStart = stm->getBeginLoc();
0124     if (locStart.isInvalid() || m_context->sm.isInSystemHeader(locStart)) {
0125         return true;
0126     }
0127 
0128     if (!m_context->parentMap) {
0129         if (m_context->ci.getDiagnostics().hasUnrecoverableErrorOccurred()) {
0130             return false; // ParentMap sometimes crashes when there were errors. Doesn't like a botched AST.
0131         }
0132 
0133         m_context->parentMap = new ParentMap(stm);
0134     }
0135 
0136     ParentMap *parentMap = m_context->parentMap;
0137 
0138     // Workaround llvm bug: Crashes creating a parent map when encountering Catch Statements.
0139     if (lastStm && isa<CXXCatchStmt>(lastStm) && !parentMap->hasParent(stm)) {
0140         parentMap->setParent(stm, lastStm);
0141         manuallyPopulateParentMap(parentMap, stm);
0142     }
0143 
0144     lastStm = stm;
0145 
0146     // clang::ParentMap takes a root statement, but there's no root statement in the AST, the root is a declaration
0147     // So add to parent map each time we go into a different hierarchy
0148     if (!parentMap->hasParent(stm)) {
0149         parentMap->addStmt(stm);
0150     }
0151 
0152     const bool isFromIgnorableInclude = m_context->ignoresIncludedFiles() && !Utils::isMainFile(m_context->sm, locStart);
0153     for (CheckBase *check : m_checksToVisitStmts) {
0154         if (!(isFromIgnorableInclude && check->canIgnoreIncludes())) {
0155             check->VisitStmt(stm);
0156         }
0157     }
0158 
0159     return true;
0160 }
0161 
0162 void ClazyASTConsumer::HandleTranslationUnit(ASTContext &ctx)
0163 {
0164     // FIXME: EndSourceFile() is called automatically, but not BeginsSourceFile()
0165     if (m_context->exporter) {
0166         m_context->exporter->BeginSourceFile(clang::LangOptions());
0167     }
0168 
0169     if ((m_context->options & ClazyContext::ClazyOption_OnlyQt) && !m_context->isQt()) {
0170         return;
0171     }
0172 
0173     // Run our RecursiveAstVisitor based checks:
0174     TraverseDecl(ctx.getTranslationUnitDecl());
0175 
0176 #ifndef CLAZY_DISABLE_AST_MATCHERS
0177     // Run our AstMatcher base checks:
0178     m_matchFinder->matchAST(ctx);
0179 #endif
0180 }
0181 
0182 static bool parseArgument(const std::string &arg, std::vector<std::string> &args)
0183 {
0184     auto it = clazy::find(args, arg);
0185     if (it != args.end()) {
0186         args.erase(it);
0187         return true;
0188     }
0189 
0190     return false;
0191 }
0192 
0193 ClazyASTAction::ClazyASTAction()
0194     : PluginASTAction()
0195     , m_checkManager(CheckManager::instance())
0196 {
0197 }
0198 
0199 std::unique_ptr<clang::ASTConsumer> ClazyASTAction::CreateASTConsumer(CompilerInstance &, llvm::StringRef)
0200 {
0201     // NOTE: This method needs to be kept reentrant (but not necessarily thread-safe)
0202     // Might be called from multiple threads via libclang, each thread operates on a different instance though
0203 
0204     std::lock_guard<std::mutex> lock(CheckManager::lock());
0205 
0206     auto astConsumer = std::unique_ptr<ClazyASTConsumer>(new ClazyASTConsumer(m_context));
0207     auto createdChecks = m_checkManager->createChecks(m_checks, m_context);
0208     for (const auto &check : createdChecks) {
0209         astConsumer->addCheck(check);
0210     }
0211 
0212     return std::unique_ptr<clang::ASTConsumer>(astConsumer.release());
0213 }
0214 
0215 static std::string getEnvVariable(const char *name)
0216 {
0217     const char *result = getenv(name);
0218     if (result) {
0219         return result;
0220     }
0221     return std::string();
0222 }
0223 
0224 bool ClazyASTAction::ParseArgs(const CompilerInstance &ci, const std::vector<std::string> &args_)
0225 {
0226     // NOTE: This method needs to be kept reentrant (but not necessarily thread-safe)
0227     // Might be called from multiple threads via libclang, each thread operates on a different instance though
0228 
0229     std::vector<std::string> args = args_;
0230 
0231     const std::string headerFilter = getEnvVariable("CLAZY_HEADER_FILTER");
0232     const std::string ignoreDirs = getEnvVariable("CLAZY_IGNORE_DIRS");
0233     std::string exportFixesFilename;
0234 
0235     if (parseArgument("help", args)) {
0236         m_context = new ClazyContext(ci, headerFilter, ignoreDirs, exportFixesFilename, {}, ClazyContext::ClazyOption_None);
0237         PrintHelp(llvm::errs());
0238         return true;
0239     }
0240 
0241     if (parseArgument("export-fixes", args) || getenv("CLAZY_EXPORT_FIXES")) {
0242         m_options |= ClazyContext::ClazyOption_ExportFixes;
0243     }
0244 
0245     if (parseArgument("qt4-compat", args)) {
0246         m_options |= ClazyContext::ClazyOption_Qt4Compat;
0247     }
0248 
0249     if (parseArgument("only-qt", args)) {
0250         m_options |= ClazyContext::ClazyOption_OnlyQt;
0251     }
0252 
0253     if (parseArgument("qt-developer", args)) {
0254         m_options |= ClazyContext::ClazyOption_QtDeveloper;
0255     }
0256 
0257     if (parseArgument("visit-implicit-code", args)) {
0258         m_options |= ClazyContext::ClazyOption_VisitImplicitCode;
0259     }
0260 
0261     if (parseArgument("ignore-included-files", args)) {
0262         m_options |= ClazyContext::ClazyOption_IgnoreIncludedFiles;
0263     }
0264 
0265     if (parseArgument("export-fixes", args)) {
0266         exportFixesFilename = args.at(0);
0267     }
0268 
0269     m_context = new ClazyContext(ci, headerFilter, ignoreDirs, exportFixesFilename, {}, m_options);
0270 
0271     // This argument is for debugging purposes
0272     const bool dbgPrintRequestedChecks = parseArgument("print-requested-checks", args);
0273 
0274     {
0275         std::lock_guard<std::mutex> lock(CheckManager::lock());
0276         m_checks = m_checkManager->requestedChecks(args, m_options & ClazyContext::ClazyOption_Qt4Compat);
0277     }
0278 
0279     if (args.size() > 1) {
0280         // Too many arguments.
0281         llvm::errs() << "Too many arguments: ";
0282         for (const std::string &a : args) {
0283             llvm::errs() << a << ' ';
0284         }
0285         llvm::errs() << "\n";
0286 
0287         PrintHelp(llvm::errs());
0288         return false;
0289     }
0290     if (args.size() == 1 && m_checks.empty()) {
0291         // Checks were specified but couldn't be found
0292         llvm::errs() << "Could not find checks in comma separated string " + args[0] + "\n";
0293         PrintHelp(llvm::errs());
0294         return false;
0295     }
0296 
0297     if (dbgPrintRequestedChecks) {
0298         printRequestedChecks();
0299     }
0300 
0301     return true;
0302 }
0303 
0304 void ClazyASTAction::printRequestedChecks() const
0305 {
0306     llvm::errs() << "Requested checks: ";
0307     const unsigned int numChecks = m_checks.size();
0308     for (unsigned int i = 0; i < numChecks; ++i) {
0309         llvm::errs() << m_checks.at(i).name;
0310         const bool isLast = i == numChecks - 1;
0311         if (!isLast) {
0312             llvm::errs() << ", ";
0313         }
0314     }
0315 
0316     llvm::errs() << "\n";
0317 }
0318 
0319 void ClazyASTAction::PrintHelp(llvm::raw_ostream &ros) const
0320 {
0321     std::lock_guard<std::mutex> lock(CheckManager::lock());
0322     RegisteredCheck::List checks = m_checkManager->availableChecks(MaxCheckLevel);
0323 
0324     clazy::sort(checks, checkLessThanByLevel);
0325 
0326     ros << "Available checks and FixIts:\n\n";
0327 
0328     int lastPrintedLevel = -1;
0329     const auto numChecks = checks.size();
0330     for (unsigned int i = 0; i < numChecks; ++i) {
0331         const RegisteredCheck &check = checks[i];
0332         const std::string levelStr = "level" + std::to_string(check.level);
0333         if (lastPrintedLevel < check.level) {
0334             lastPrintedLevel = check.level;
0335 
0336             if (check.level > 0) {
0337                 ros << "\n";
0338             }
0339 
0340             ros << "- Checks from " << levelStr << ":\n";
0341         }
0342 
0343         const std::string relativeReadmePath = "src/checks/" + levelStr + "/README-" + check.name + ".md";
0344 
0345         auto padded = check.name;
0346         padded.insert(padded.end(), 39 - padded.size(), ' ');
0347         ros << "    - " << check.name;
0348         ;
0349         auto fixits = m_checkManager->availableFixIts(check.name);
0350         if (!fixits.empty()) {
0351             ros << "    (";
0352             bool isFirst = true;
0353             for (const auto &fixit : fixits) {
0354                 if (isFirst) {
0355                     isFirst = false;
0356                 } else {
0357                     ros << ',';
0358                 }
0359 
0360                 ros << fixit.name;
0361             }
0362             ros << ')';
0363         }
0364         ros << "\n";
0365     }
0366     ros << "\nIf nothing is specified, all checks from level0 and level1 will be run.\n\n";
0367     ros << "To specify which checks to enable set the CLAZY_CHECKS env variable, for example:\n";
0368     ros << "    export CLAZY_CHECKS=\"level0\"\n";
0369     ros << "    export CLAZY_CHECKS=\"level0,reserve-candidates,qstring-allocations\"\n";
0370     ros << "    export CLAZY_CHECKS=\"reserve-candidates\"\n\n";
0371     ros << "or pass as compiler arguments, for example:\n";
0372     ros << "    -Xclang -plugin-arg-clazy -Xclang reserve-candidates,qstring-allocations\n";
0373     ros << "\n";
0374 }
0375 
0376 ClazyStandaloneASTAction::ClazyStandaloneASTAction(const std::string &checkList,
0377                                                    const std::string &headerFilter,
0378                                                    const std::string &ignoreDirs,
0379                                                    const std::string &exportFixesFilename,
0380                                                    const std::vector<std::string> &translationUnitPaths,
0381                                                    ClazyContext::ClazyOptions options)
0382     : m_checkList(checkList.empty() ? "level1" : checkList)
0383     , m_headerFilter(headerFilter.empty() ? getEnvVariable("CLAZY_HEADER_FILTER") : headerFilter)
0384     , m_ignoreDirs(ignoreDirs.empty() ? getEnvVariable("CLAZY_IGNORE_DIRS") : ignoreDirs)
0385     , m_exportFixesFilename(exportFixesFilename)
0386     , m_translationUnitPaths(translationUnitPaths)
0387     , m_options(options)
0388 {
0389 }
0390 
0391 std::unique_ptr<ASTConsumer> ClazyStandaloneASTAction::CreateASTConsumer(CompilerInstance &ci, llvm::StringRef)
0392 {
0393     auto *context = new ClazyContext(ci, m_headerFilter, m_ignoreDirs, m_exportFixesFilename, m_translationUnitPaths, m_options);
0394     auto *astConsumer = new ClazyASTConsumer(context);
0395 
0396     auto *cm = CheckManager::instance();
0397 
0398     std::vector<std::string> checks;
0399     checks.push_back(m_checkList);
0400     const bool qt4Compat = m_options & ClazyContext::ClazyOption_Qt4Compat;
0401     const RegisteredCheck::List requestedChecks = cm->requestedChecks(checks, qt4Compat);
0402 
0403     if (requestedChecks.empty()) {
0404         llvm::errs() << "No checks were requested!\n"
0405                      << "\n";
0406         return nullptr;
0407     }
0408 
0409     auto createdChecks = cm->createChecks(requestedChecks, context);
0410     for (const auto &check : createdChecks) {
0411         astConsumer->addCheck(check);
0412     }
0413 
0414     return std::unique_ptr<ASTConsumer>(astConsumer);
0415 }
0416 
0417 volatile int ClazyPluginAnchorSource = 0;
0418 
0419 static FrontendPluginRegistry::Add<ClazyASTAction> X("clazy", "clang lazy plugin");