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");