/* This file is part of the clazy static checker. Copyright (C) 2015 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com Author: Sérgio Martins Copyright (C) 2015-2017 Sergio Martins This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "Utils.h" #include "Clazy.h" #include "StringUtils.h" #include "clazy_stl.h" #include "checkbase.h" #include "AccessSpecifierManager.h" #include "clang/Frontend/FrontendPluginRegistry.h" #include "clang/AST/AST.h" #include "clang/Frontend/CompilerInstance.h" #include "llvm/Support/raw_ostream.h" #include "clang/AST/ParentMap.h" #include #include #include #include #include using namespace clang; using namespace std; using namespace clang::ast_matchers; static void manuallyPopulateParentMap(ParentMap *map, Stmt *s) { if (!s) return; for (Stmt *child : s->children()) { llvm::errs() << "Patching " << child->getStmtClassName() << "\n"; map->setParent(child, s); manuallyPopulateParentMap(map, child); } } ClazyASTConsumer::ClazyASTConsumer(ClazyContext *context) : m_context(context) { #ifndef CLAZY_DISABLE_AST_MATCHERS m_matchFinder = new clang::ast_matchers::MatchFinder(); #endif } void ClazyASTConsumer::addCheck(const std::pair &check) { CheckBase *checkBase = check.first; #ifndef CLAZY_DISABLE_AST_MATCHERS checkBase->registerASTMatchers(*m_matchFinder); #endif //m_createdChecks.push_back(checkBase); const RegisteredCheck &rcheck = check.second; if (rcheck.options & RegisteredCheck::Option_VisitsStmts) m_checksToVisitStmts.push_back(checkBase); if (rcheck.options & RegisteredCheck::Option_VisitsDecls) m_checksToVisitDecls.push_back(checkBase); } ClazyASTConsumer::~ClazyASTConsumer() { #ifndef CLAZY_DISABLE_AST_MATCHERS delete m_matchFinder; #endif delete m_context; } bool ClazyASTConsumer::VisitDecl(Decl *decl) { if (AccessSpecifierManager *a = m_context->accessSpecifierManager) // Needs to visit system headers too (qobject.h for example) a->VisitDeclaration(decl); const SourceLocation locStart = getLocStart(decl); if (locStart.isInvalid() || m_context->sm.isInSystemHeader(locStart)) return true; const bool isFromIgnorableInclude = m_context->ignoresIncludedFiles() && !Utils::isMainFile(m_context->sm, locStart); m_context->lastDecl = decl; if (auto mdecl = dyn_cast(decl)) m_context->lastMethodDecl = mdecl; for (CheckBase *check : m_checksToVisitDecls) { if (!(isFromIgnorableInclude && check->canIgnoreIncludes())) check->VisitDecl(decl); } return true; } bool ClazyASTConsumer::VisitStmt(Stmt *stm) { const SourceLocation locStart = getLocStart(stm); if (locStart.isInvalid() || m_context->sm.isInSystemHeader(locStart)) return true; if (!m_context->parentMap) { if (m_context->ci.getDiagnostics().hasUnrecoverableErrorOccurred()) return false; // ParentMap sometimes crashes when there were errors. Doesn't like a botched AST. m_context->parentMap = new ParentMap(stm); } ParentMap *parentMap = m_context->parentMap; // Workaround llvm bug: Crashes creating a parent map when encountering Catch Statements. if (lastStm && isa(lastStm) && !parentMap->hasParent(stm)) { parentMap->setParent(stm, lastStm); manuallyPopulateParentMap(parentMap, stm); } lastStm = stm; // clang::ParentMap takes a root statement, but there's no root statement in the AST, the root is a declaration // So add to parent map each time we go into a different hierarchy if (!parentMap->hasParent(stm)) parentMap->addStmt(stm); const bool isFromIgnorableInclude = m_context->ignoresIncludedFiles() && !Utils::isMainFile(m_context->sm, locStart); for (CheckBase *check : m_checksToVisitStmts) { if (!(isFromIgnorableInclude && check->canIgnoreIncludes())) check->VisitStmt(stm); } return true; } void ClazyASTConsumer::HandleTranslationUnit(ASTContext &ctx) { if ((m_context->options & ClazyContext::ClazyOption_OnlyQt) && !m_context->isQt()) return; // Run our RecursiveAstVisitor based checks: TraverseDecl(ctx.getTranslationUnitDecl()); #ifndef CLAZY_DISABLE_AST_MATCHERS // Run our AstMatcher base checks: m_matchFinder->matchAST(ctx); #endif } static bool parseArgument(const string &arg, vector &args) { auto it = clazy::find(args, arg); if (it != args.end()) { args.erase(it); return true; } return false; } ClazyASTAction::ClazyASTAction() : PluginASTAction() , m_checkManager(CheckManager::instance()) { } std::unique_ptr ClazyASTAction::CreateASTConsumer(CompilerInstance &, llvm::StringRef) { // NOTE: This method needs to be kept reentrant (but not necessarily thread-safe) // Might be called from multiple threads via libclang, each thread operates on a different instance though std::lock_guard lock(CheckManager::lock()); auto astConsumer = std::unique_ptr(new ClazyASTConsumer(m_context)); auto createdChecks = m_checkManager->createChecks(m_checks, m_context); for (auto check : createdChecks) { astConsumer->addCheck(check); } return std::unique_ptr(astConsumer.release()); } static std::string getEnvVariable(const char *name) { const char *result = getenv(name); if (result) return result; else return std::string(); } bool ClazyASTAction::ParseArgs(const CompilerInstance &ci, const std::vector &args_) { // NOTE: This method needs to be kept reentrant (but not necessarily thread-safe) // Might be called from multiple threads via libclang, each thread operates on a different instance though std::vector args = args_; if (parseArgument("help", args)) { m_context = new ClazyContext(ci, getEnvVariable("CLAZY_HEADER_FILTER"), getEnvVariable("CLAZY_IGNORE_DIRS"), ClazyContext::ClazyOption_None); PrintHelp(llvm::errs()); return true; } if (parseArgument("no-inplace-fixits", args)) { // Unit-tests don't use inplace fixits m_options |= ClazyContext::ClazyOption_NoFixitsInplace; } if (parseArgument("enable-all-fixits", args)) { // This is useful for unit-tests, where we also want to run fixits. Don't use it otherwise. m_options |= ClazyContext::ClazyOption_AllFixitsEnabled; } if (parseArgument("no-autowrite-fixits", args)) m_options |= ClazyContext::ClazyOption_NoFixitsAutoWrite; if (parseArgument("qt4-compat", args)) m_options |= ClazyContext::ClazyOption_Qt4Compat; if (parseArgument("only-qt", args)) m_options |= ClazyContext::ClazyOption_OnlyQt; if (parseArgument("qt-developer", args)) m_options |= ClazyContext::ClazyOption_QtDeveloper; if (parseArgument("visit-implicit-code", args)) m_options |= ClazyContext::ClazyOption_VisitImplicitCode; if (parseArgument("ignore-included-files", args)) m_options |= ClazyContext::ClazyOption_IgnoreIncludedFiles; m_context = new ClazyContext(ci, /*headerFilter=*/ "", /*ignoreDirs=*/ "", m_options); // This argument is for debugging purposes const bool dbgPrintRequestedChecks = parseArgument("print-requested-checks", args); { std::lock_guard lock(CheckManager::lock()); m_checks = m_checkManager->requestedChecks(m_context, args); } if (args.size() > 1) { // Too many arguments. llvm::errs() << "Too many arguments: "; for (const std::string &a : args) llvm::errs() << a << ' '; llvm::errs() << "\n"; PrintHelp(llvm::errs()); return false; } else if (args.size() == 1 && m_checks.empty()) { // Checks were specified but couldn't be found llvm::errs() << "Could not find checks in comma separated string " + args[0] + "\n"; PrintHelp(llvm::errs()); return false; } if (dbgPrintRequestedChecks) printRequestedChecks(); return true; } void ClazyASTAction::printRequestedChecks() const { llvm::errs() << "Requested checks: "; const unsigned int numChecks = m_checks.size(); for (unsigned int i = 0; i < numChecks; ++i) { llvm::errs() << m_checks.at(i).name; const bool isLast = i == numChecks - 1; if (!isLast) { llvm::errs() << ", "; } } llvm::errs() << "\n"; } void ClazyASTAction::PrintHelp(llvm::raw_ostream &ros) const { std::lock_guard lock(CheckManager::lock()); RegisteredCheck::List checks = m_checkManager->availableChecks(MaxCheckLevel); clazy::sort(checks, checkLessThanByLevel); ros << "Available checks and FixIts:\n\n"; int lastPrintedLevel = -1; const auto numChecks = checks.size(); for (unsigned int i = 0; i < numChecks; ++i) { const RegisteredCheck &check = checks[i]; const string levelStr = "level" + to_string(check.level); if (lastPrintedLevel < check.level) { lastPrintedLevel = check.level; if (check.level > 0) ros << "\n"; ros << "- Checks from " << levelStr << ":\n"; } const string relativeReadmePath = "src/checks/" + levelStr + "/README-" + check.name + ".md"; auto padded = check.name; padded.insert(padded.end(), 39 - padded.size(), ' '); ros << " - " << check.name;; auto fixits = m_checkManager->availableFixIts(check.name); if (!fixits.empty()) { ros << " ("; bool isFirst = true; for (const auto& fixit : fixits) { if (isFirst) { isFirst = false; } else { ros << ','; } ros << fixit.name; } ros << ')'; } ros << "\n"; } ros << "\nIf nothing is specified, all checks from level0 and level1 will be run.\n\n"; ros << "To specify which checks to enable set the CLAZY_CHECKS env variable, for example:\n"; ros << " export CLAZY_CHECKS=\"level0\"\n"; ros << " export CLAZY_CHECKS=\"level0,reserve-candidates,qstring-allocations\"\n"; ros << " export CLAZY_CHECKS=\"reserve-candidates\"\n\n"; ros << "or pass as compiler arguments, for example:\n"; ros << " -Xclang -plugin-arg-clang-lazy -Xclang reserve-candidates,qstring-allocations\n"; ros << "\n"; ros << "To enable FixIts for a check, also set the env variable CLAZY_FIXIT, for example:\n"; ros << " export CLAZY_FIXIT=\"fix-qlatin1string-allocations\"\n\n"; ros << "FixIts are experimental and rewrite your code therefore only one FixIt is allowed per build.\nSpecifying a list of different FixIts is not supported.\nBackup your code before running them.\n"; } ClazyStandaloneASTAction::ClazyStandaloneASTAction(const string &checkList, const string &headerFilter, const string &ignoreDirs, ClazyContext::ClazyOptions options) : clang::ASTFrontendAction() , m_checkList(checkList.empty() ? "level1" : checkList) , m_headerFilter(headerFilter.empty() ? getEnvVariable("CLAZY_HEADER_FILTER") : headerFilter) , m_ignoreDirs(ignoreDirs.empty() ? getEnvVariable("CLAZY_IGNORE_DIRS") : ignoreDirs) , m_options(options) { } unique_ptr ClazyStandaloneASTAction::CreateASTConsumer(CompilerInstance &ci, llvm::StringRef) { auto context = new ClazyContext(ci, m_headerFilter, m_ignoreDirs, m_options); auto astConsumer = new ClazyASTConsumer(context); auto cm = CheckManager::instance(); vector checks; checks.push_back(m_checkList); const RegisteredCheck::List requestedChecks = cm->requestedChecks(context, checks); if (requestedChecks.size() == 0) { llvm::errs() << "No checks were requested!\n" << "\n"; return nullptr; } auto createdChecks = cm->createChecks(requestedChecks, context); for (const auto &check : createdChecks) { astConsumer->addCheck(check); } return unique_ptr(astConsumer); } volatile int ClazyPluginAnchorSource = 0; static FrontendPluginRegistry::Add X("clang-lazy", "clang lazy plugin");