File indexing completed on 2024-04-28 16:57:49

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