File indexing completed on 2024-04-28 09:33: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-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");