diff --git a/src/Clazy.cpp b/src/Clazy.cpp index fca56d0..7f21afb 100644 --- a/src/Clazy.cpp +++ b/src/Clazy.cpp @@ -1,412 +1,418 @@ /* 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 "clazy_stl.h" #include "checkbase.h" #include "AccessSpecifierManager.h" #include "SourceCompatibilityHelpers.h" #include "FixItExporter.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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 = clazy::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 fdecl = dyn_cast(decl)) { m_context->lastFunctionDecl = fdecl; if (auto mdecl = dyn_cast(fdecl)) 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 = clazy::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) { // FIXME: EndSourceFile() is called automatically, but not BeginsSourceFile() - m_context->exporter->BeginSourceFile(clang::LangOptions()); + if (m_context->exporter) + m_context->exporter->BeginSourceFile(clang::LangOptions()); 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_; const string headerFilter = getEnvVariable("CLAZY_HEADER_FILTER"); const string ignoreDirs = getEnvVariable("CLAZY_IGNORE_DIRS"); + std::string exportFixes; if (parseArgument("help", args)) { - m_context = new ClazyContext(ci, headerFilter, ignoreDirs, ClazyContext::ClazyOption_None); + m_context = new ClazyContext(ci, headerFilter, ignoreDirs, exportFixes, 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); + if (parseArgument("export-fixes", args)) + exportFixes = args.at(0); + + m_context = new ClazyContext(ci, headerFilter, ignoreDirs, exportFixes, 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-clazy -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, +ClazyStandaloneASTAction::ClazyStandaloneASTAction(const string &checkList, const string &headerFilter, const string &ignoreDirs, const string &exportFixes, 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_exportFixes(exportFixes) , m_options(options) { } unique_ptr ClazyStandaloneASTAction::CreateASTConsumer(CompilerInstance &ci, llvm::StringRef) { - auto context = new ClazyContext(ci, m_headerFilter, m_ignoreDirs, m_options); + auto context = new ClazyContext(ci, m_headerFilter, m_ignoreDirs, m_exportFixes, 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("clazy", "clang lazy plugin"); diff --git a/src/Clazy.h b/src/Clazy.h index da2488c..846e694 100644 --- a/src/Clazy.h +++ b/src/Clazy.h @@ -1,130 +1,132 @@ /* 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. */ #ifndef CLAZY_AST_ACTION_H #define CLAZY_AST_ACTION_H #include "checkmanager.h" #include "ClazyContext.h" #include "checkbase.h" #include #include #include #include #include #include #include #include namespace llvm { class raw_ostream; } // namespace llvm namespace clang { class CompilerInstance; class ASTContext; class Decl; class Stmt; } /** * This is the FrontendAction that is run with clazy is used as a plugin. */ class ClazyASTAction : public clang::PluginASTAction { public: ClazyASTAction(); protected: /// @note This function is reentrant std::unique_ptr CreateASTConsumer(clang::CompilerInstance &ci, llvm::StringRef) override; /// @note This function is reentrant bool ParseArgs(const clang::CompilerInstance &ci, const std::vector &args_) override; void PrintHelp(llvm::raw_ostream &ros) const; void PrintAnchorHeader(llvm::raw_ostream &ro, RegisteredCheck::List &checks) const; private: void printRequestedChecks() const; RegisteredCheck::List m_checks; ClazyContext::ClazyOptions m_options = 0; CheckManager *const m_checkManager; ClazyContext *m_context = nullptr; }; /** * This is the FrontendAction that is run with clazy is used standalone instead of as a plugin. * i.e: when you run clazy-standalone, this is the invoked FrontendAction */ class ClazyStandaloneASTAction : public clang::ASTFrontendAction { public: explicit ClazyStandaloneASTAction(const std::string &checkList, const std::string &headerFilter, const std::string &ignoreDirs, + const std::string &exportFixes, ClazyContext::ClazyOptions = ClazyContext::ClazyOption_None); protected: std::unique_ptr CreateASTConsumer(clang::CompilerInstance &ci, llvm::StringRef) override; private: const std::string m_checkList; const std::string m_headerFilter; const std::string m_ignoreDirs; + const std::string m_exportFixes; const ClazyContext::ClazyOptions m_options; }; /** * Clazy's AST Consumer. */ class ClazyASTConsumer : public clang::ASTConsumer , public clang::RecursiveASTVisitor { public: explicit ClazyASTConsumer(ClazyContext *context); ~ClazyASTConsumer(); bool shouldVisitImplicitCode() const { return m_context->isVisitImplicitCode(); } bool VisitDecl(clang::Decl *decl); bool VisitStmt(clang::Stmt *stm); void HandleTranslationUnit(clang::ASTContext &ctx) override; void addCheck(const std::pair &check); ClazyContext *context() const { return m_context; } private: ClazyASTConsumer(const ClazyASTConsumer &) = delete; clang::Stmt *lastStm = nullptr; ClazyContext *const m_context; //CheckBase::List m_createdChecks; CheckBase::List m_checksToVisitStmts; CheckBase::List m_checksToVisitDecls; #ifndef CLAZY_DISABLE_AST_MATCHERS clang::ast_matchers::MatchFinder *m_matchFinder = nullptr; #endif }; #endif diff --git a/src/ClazyContext.cpp b/src/ClazyContext.cpp index b5d9868..96bdf9f 100644 --- a/src/ClazyContext.cpp +++ b/src/ClazyContext.cpp @@ -1,142 +1,145 @@ /* This file is part of the clazy static checker. Copyright (C) 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 "AccessSpecifierManager.h" #include "ClazyContext.h" #include "FixItExporter.h" #include "PreProcessorVisitor.h" #include #include #include #include #include #include using namespace std; using namespace clang; class ClazyFixItOptions : public FixItOptions { public: ClazyFixItOptions(const ClazyFixItOptions &other) = delete; ClazyFixItOptions(bool inplace) { if (const char *suffix = getenv("CLAZY_FIXIT_SUFFIX")) m_suffix = suffix; InPlace = inplace && m_suffix.empty(); FixWhatYouCan = true; FixOnlyWarnings = true; Silent = false; } std::string RewriteFilename(const std::string &filename, int &fd) override { fd = -1; return InPlace ? filename : filename + m_suffix; } std::string m_suffix; }; ClazyContext::ClazyContext(const clang::CompilerInstance &compiler, - const string &headerFilter, const string &ignoreDirs, ClazyOptions opts) + const string &headerFilter, const string &ignoreDirs, const string &exportFixes, ClazyOptions opts) : ci(compiler) , astContext(ci.getASTContext()) , sm(ci.getSourceManager()) , m_noWerror(getenv("CLAZY_NO_WERROR") != nullptr) // Allows user to make clazy ignore -Werror , options(opts) , extraOptions(clazy::splitString(getenv("CLAZY_EXTRA_OPTIONS"), ',')) + , exportFixes(exportFixes) { if (!headerFilter.empty()) headerFilterRegex = std::unique_ptr(new llvm::Regex(headerFilter)); if (!ignoreDirs.empty()) ignoreDirsRegex = std::unique_ptr(new llvm::Regex(ignoreDirs)); const char *fixitsEnv = getenv("CLAZY_FIXIT"); allFixitsEnabled = (options & ClazyOption_AllFixitsEnabled); if (!allFixitsEnabled && fixitsEnv) { const string fixitsEnvStr = clazy::unquoteString(fixitsEnv); if (fixitsEnvStr == "all_fixits") { allFixitsEnabled = true; } else { requestedFixitName = fixitsEnvStr; } } - if (fixitsEnabled() && !(options & ClazyOption_NoFixitsAutoWrite)) - rewriter = new FixItRewriter(ci.getDiagnostics(), sm, - ci.getLangOpts(), new ClazyFixItOptions(fixitsAreInplace())); - - exporter = new FixItExporter(ci.getDiagnostics(), sm, ci.getLangOpts(), - new ClazyFixItOptions(fixitsAreInplace())); + if (fixitsEnabled()) { + if (exportFixesEnabled()) + exporter = new FixItExporter(ci.getDiagnostics(), sm, ci.getLangOpts(), + exportFixes); + else if (!(options & ClazyOption_NoFixitsAutoWrite)) + rewriter = new FixItRewriter(ci.getDiagnostics(), sm, + ci.getLangOpts(), new ClazyFixItOptions(fixitsAreInplace())); + } } ClazyContext::~ClazyContext() { //delete preprocessorVisitor; // we don't own it delete accessSpecifierManager; delete parentMap; if (exporter) { exporter->Export(); delete exporter; } if (rewriter) { rewriter->WriteFixedFiles(); delete rewriter; } preprocessorVisitor = nullptr; accessSpecifierManager = nullptr; parentMap = nullptr; } void ClazyContext::enableAccessSpecifierManager() { if (!accessSpecifierManager && !usingPreCompiledHeaders()) accessSpecifierManager = new AccessSpecifierManager(ci); } void ClazyContext::enablePreprocessorVisitor() { if (!preprocessorVisitor && !usingPreCompiledHeaders()) preprocessorVisitor = new PreProcessorVisitor(ci); } bool ClazyContext::isQt() const { static const bool s_isQt = [this] { - for (auto s : ci.getPreprocessorOpts().Macros) { - if (s.first == "QT_CORE_LIB") - return true; - } - return false; - } (); + for (auto s : ci.getPreprocessorOpts().Macros) { + if (s.first == "QT_CORE_LIB") + return true; + } + return false; + } (); return s_isQt; } diff --git a/src/ClazyContext.h b/src/ClazyContext.h index a53572b..ae997df 100644 --- a/src/ClazyContext.h +++ b/src/ClazyContext.h @@ -1,195 +1,202 @@ /* This file is part of the clazy static checker. Copyright (C) 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. */ #ifndef CLAZY_CONTEXT_H #define CLAZY_CONTEXT_H #include "SuppressionManager.h" #include "clazy_stl.h" #include #include #include #include #include #include #include #include #include #include #include // ClazyContext is just a struct to share data and code between all checks namespace clang { class CompilerInstance; class ASTContext; class ParentMap; class SourceManager; class FixItRewriter; class CXXMethodDecl; class Decl; } class AccessSpecifierManager; class PreProcessorVisitor; class FixItExporter; class ClazyContext { public: enum ClazyOption { ClazyOption_None = 0, ClazyOption_NoFixitsInplace = 1, ClazyOption_NoFixitsAutoWrite = 2, // If enabled then fixits are reported, but not applied ClazyOption_AllFixitsEnabled = 4, ClazyOption_Qt4Compat = 8, ClazyOption_OnlyQt = 16, // Ignore non-Qt files. This is done by bailing out if QT_CORE_LIB is not set. ClazyOption_QtDeveloper = 32, // For running clazy on Qt itself, optional, but honours specific guidelines ClazyOption_VisitImplicitCode = 64, // Inspect compiler generated code aswell, useful for custom checks, if they need it ClazyOption_IgnoreIncludedFiles = 128 // Only warn for the current file being compiled, not on includes (useful for performance reasons) }; typedef int ClazyOptions; explicit ClazyContext(const clang::CompilerInstance &ci, const std::string &headerFilter, const std::string &ignoreDirs, + const std::string &exportFixes, ClazyOptions = ClazyOption_None); ~ClazyContext(); bool usingPreCompiledHeaders() const { return !ci.getPreprocessorOpts().ImplicitPCHInclude.empty(); } bool userDisabledWError() const { return m_noWerror; } bool fixitsAreInplace() const { return !(options & ClazyOption_NoFixitsInplace); } bool fixitsEnabled() const { return allFixitsEnabled || !requestedFixitName.empty(); } + bool exportFixesEnabled() const + { + return !exportFixes.empty(); + } + bool isQtDeveloper() const { return options & ClazyOption_QtDeveloper; } bool ignoresIncludedFiles() const { return options & ClazyOption_IgnoreIncludedFiles; } bool isVisitImplicitCode() const { return options & ClazyContext::ClazyOption_VisitImplicitCode; } bool isOptionSet(const std::string &optionName) const { return clazy::contains(extraOptions, optionName); } bool fileMatchesLoc(const std::unique_ptr ®ex, clang::SourceLocation loc, const clang::FileEntry **file) const { if (!regex) return false; if (!(*file)) { clang::FileID fid = sm.getDecomposedExpansionLoc(loc).first; *file = sm.getFileEntryForID(fid); if (!(*file)) { return false; } } llvm::StringRef fileName((*file)->getName()); return regex->match(fileName); } bool shouldIgnoreFile(clang::SourceLocation loc) const { // 1. Process the regexp that excludes files const clang::FileEntry *file = nullptr; if (ignoreDirsRegex) { const bool matches = fileMatchesLoc(ignoreDirsRegex, loc, &file); if (matches) return true; } // 2. Process the regexp that includes files. Has lower priority. if (!headerFilterRegex || isMainFile(loc)) return false; const bool matches = fileMatchesLoc(headerFilterRegex, loc, &file); if (!file) return false; return !matches; } bool isMainFile(clang::SourceLocation loc) const { if (loc.isMacroID()) loc = sm.getExpansionLoc(loc); return sm.isInFileID(loc, sm.getMainFileID()); } /** * We only enable it if a check needs it, for performance reasons */ void enableAccessSpecifierManager(); void enablePreprocessorVisitor(); bool isQt() const; // TODO: More things will follow const clang::CompilerInstance &ci; clang::ASTContext &astContext; clang::SourceManager &sm; AccessSpecifierManager *accessSpecifierManager = nullptr; PreProcessorVisitor *preprocessorVisitor = nullptr; SuppressionManager suppressionManager; const bool m_noWerror; clang::ParentMap *parentMap = nullptr; const ClazyOptions options; const std::vector extraOptions; clang::FixItRewriter *rewriter = nullptr; FixItExporter *exporter = nullptr; bool allFixitsEnabled = false; std::string requestedFixitName; + std::string exportFixes; clang::CXXMethodDecl *lastMethodDecl = nullptr; clang::FunctionDecl *lastFunctionDecl = nullptr; clang::Decl *lastDecl = nullptr; std::unique_ptr headerFilterRegex; std::unique_ptr ignoreDirsRegex; }; #endif diff --git a/src/ClazyStandaloneMain.cpp b/src/ClazyStandaloneMain.cpp index 1183575..3c0e3d8 100644 --- a/src/ClazyStandaloneMain.cpp +++ b/src/ClazyStandaloneMain.cpp @@ -1,121 +1,125 @@ /* This file is part of the clazy static checker. Copyright (C) 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. */ // clazy:excludeall=non-pod-global-static #include "Clazy.h" #include "ClazyContext.h" #include #include #include #include #include #include namespace clang { class FrontendAction; } // namespace clang using namespace clang; using namespace clang::tooling; using namespace llvm; static llvm::cl::OptionCategory s_clazyCategory("clazy options"); static cl::opt s_checks("checks", cl::desc("Comma-separated list of clazy checks. Default is level1"), cl::init(""), cl::cat(s_clazyCategory)); static cl::opt s_noInplaceFixits("no-inplace-fixits", cl::desc("Fixits will be applied to a separate file (for unit-test use only)"), cl::init(false), cl::cat(s_clazyCategory)); static cl::opt s_enableAllFixits("enable-all-fixits", cl::desc("Enables all fixits"), cl::init(false), cl::cat(s_clazyCategory)); +static cl::opt s_exportFixes("export-fixes", cl::desc("YAML file to store suggested fixes in. The stored fixes can be applied to the input source code with clang-apply-replacements."), + cl::init(""), cl::cat(s_clazyCategory)); + static cl::opt s_qt4Compat("qt4-compat", cl::desc("Turns off checks not compatible with Qt 4"), cl::init(false), cl::cat(s_clazyCategory)); static cl::opt s_onlyQt("only-qt", cl::desc("Won't emit warnings for non-Qt files, or in other words, if -DQT_CORE_LIB is missing."), cl::init(false), cl::cat(s_clazyCategory)); static cl::opt s_qtDeveloper("qt-developer", cl::desc("For running clazy on Qt itself, optional, but honours specific guidelines"), cl::init(false), cl::cat(s_clazyCategory)); static cl::opt s_visitImplicitCode("visit-implicit-code", cl::desc("For visiting implicit code like compiler generated constructors. None of the built-in checks benefit from this, but can be useful for custom checks"), cl::init(false), cl::cat(s_clazyCategory)); static cl::opt s_ignoreIncludedFiles("ignore-included-files", cl::desc("Only emit warnings for the current file being compiled and ignore any includes. Useful for performance reasons."), cl::init(false), cl::cat(s_clazyCategory)); static cl::opt s_headerFilter("header-filter", cl::desc(R"(Regular expression matching the names of the headers to output diagnostics from. Diagnostics from the main file of each translation unit are always displayed.)"), cl::init(""), cl::cat(s_clazyCategory)); static cl::opt s_ignoreDirs("ignore-dirs", cl::desc(R"(Regular expression matching the names of the directories for which diagnostics should never be emitted. Useful for ignoring 3rdparty code.)"), cl::init(""), cl::cat(s_clazyCategory)); static cl::extrahelp s_commonHelp(CommonOptionsParser::HelpMessage); class ClazyToolActionFactory : public clang::tooling::FrontendActionFactory { public: ClazyToolActionFactory() : FrontendActionFactory() {} FrontendAction *create() override { ClazyContext::ClazyOptions options = ClazyContext::ClazyOption_None; if (s_noInplaceFixits.getValue()) options |= ClazyContext::ClazyOption_NoFixitsInplace; if (s_enableAllFixits.getValue()) options |= ClazyContext::ClazyOption_AllFixitsEnabled; if (s_qt4Compat.getValue()) options |= ClazyContext::ClazyOption_Qt4Compat; if (s_qtDeveloper.getValue()) options |= ClazyContext::ClazyOption_QtDeveloper; if (s_onlyQt.getValue()) options |= ClazyContext::ClazyOption_OnlyQt; if (s_visitImplicitCode.getValue()) options |= ClazyContext::ClazyOption_VisitImplicitCode; if (s_ignoreIncludedFiles.getValue()) options |= ClazyContext::ClazyOption_IgnoreIncludedFiles; - return new ClazyStandaloneASTAction(s_checks.getValue(), s_headerFilter.getValue(), s_ignoreDirs.getValue(), options); + // TODO: We need to agregate the fixes with previous run + return new ClazyStandaloneASTAction(s_checks.getValue(), s_headerFilter.getValue(), s_ignoreDirs.getValue(), s_exportFixes.getValue(), options); } }; int main(int argc, const char **argv) { CommonOptionsParser optionsParser(argc, argv, s_clazyCategory); ClangTool tool(optionsParser.getCompilations(), optionsParser.getSourcePathList()); return tool.run(new ClazyToolActionFactory()); } diff --git a/src/FixItExporter.cpp b/src/FixItExporter.cpp index d16deb9..d27dfd2 100644 --- a/src/FixItExporter.cpp +++ b/src/FixItExporter.cpp @@ -1,166 +1,182 @@ /* This file is part of the clazy static checker. Copyright (C) 2019 Christian Gagneraud 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 "FixItExporter.h" #include #include #include #include -#define DEBUG_FIX_IT_EXPORTER - -#ifdef DEBUG_FIX_IT_EXPORTER -# include -#endif +// #define DEBUG_FIX_IT_EXPORTER using namespace clang; FixItExporter::FixItExporter(DiagnosticsEngine &DiagEngine, SourceManager &SourceMgr, - const LangOptions &LangOpts, FixItOptions *FixItOpts) - : DiagEngine(DiagEngine), SourceMgr(SourceMgr), LangOpts(LangOpts) - , FixItOpts(FixItOpts) + const LangOptions &LangOpts, const std::string &exportFixes) + : DiagEngine(DiagEngine) + , SourceMgr(SourceMgr) + , LangOpts(LangOpts) + , exportFixes(exportFixes) { Owner = DiagEngine.takeClient(); Client = DiagEngine.getClient(); DiagEngine.setClient(this, false); } FixItExporter::~FixItExporter() { - DiagEngine.setClient(Client, Owner.release() != nullptr); + if (Client) + DiagEngine.setClient(Client, Owner.release() != nullptr); } void FixItExporter::BeginSourceFile(const LangOptions &LangOpts, const Preprocessor *PP) { if (Client) Client->BeginSourceFile(LangOpts, PP); const auto id = SourceMgr.getMainFileID(); const auto entry = SourceMgr.getFileEntryForID(id); TUDiag.MainSourceFile = entry->getName(); } bool FixItExporter::IncludeInDiagnosticCounts() const { return Client ? Client->IncludeInDiagnosticCounts() : true; } void FixItExporter::EndSourceFile() { if (Client) Client->EndSourceFile(); } tooling::Diagnostic FixItExporter::ConvertDiagnostic(const Diagnostic &Info) { - SmallString<100> TmpMessageText; + SmallString<256> TmpMessageText; Info.FormatDiagnostic(TmpMessageText); + // TODO: This returns an empty string: DiagEngine->getDiagnosticIDs()->getWarningOptionForDiag(Info.getID()); + // HACK: capture it at the end of the message: Message text [check-name] const auto MessageText = TmpMessageText.slice(0, TmpMessageText.find_last_of('[') - 1).str(); const auto CheckName = TmpMessageText.slice(TmpMessageText.find_last_of('[') + 3, TmpMessageText.find_last_of(']')).str(); llvm::StringRef CurrentBuildDir; // Not needed? tooling::Diagnostic ToolingDiag(CheckName, tooling::Diagnostic::Warning, CurrentBuildDir); + // FIXME: Sometimes the file path is an empty string. ToolingDiag.Message = tooling::DiagnosticMessage(MessageText, SourceMgr, Info.getLocation()); return ToolingDiag; } tooling::Replacement FixItExporter::ConvertFixIt(const FixItHint &Hint) { + // TODO: Proper handling of macros + // https://stackoverflow.com/questions/24062989/clang-fails-replacing-a-statement-if-it-contains-a-macro tooling::Replacement Replacement; if (Hint.CodeToInsert.empty()) { if (Hint.InsertFromRange.isValid()) { clang::SourceLocation b(Hint.InsertFromRange.getBegin()), _e(Hint.InsertFromRange.getEnd()); if (b.isMacroID()) b = SourceMgr.getSpellingLoc(b); if (_e.isMacroID()) _e = SourceMgr.getSpellingLoc(_e); clang::SourceLocation e(clang::Lexer::getLocForEndOfToken(_e, 0, SourceMgr, LangOpts)); StringRef Text(SourceMgr.getCharacterData(b), - SourceMgr.getCharacterData(e)-SourceMgr.getCharacterData(b)); + SourceMgr.getCharacterData(e) - SourceMgr.getCharacterData(b)); return tooling::Replacement(SourceMgr, Hint.RemoveRange, Text); } return tooling::Replacement(SourceMgr, Hint.RemoveRange, ""); } return tooling::Replacement(SourceMgr, Hint.RemoveRange, Hint.CodeToInsert); } void FixItExporter::HandleDiagnostic(DiagnosticsEngine::Level DiagLevel, const Diagnostic &Info) { // Default implementation (Warnings/errors count). DiagnosticConsumer::HandleDiagnostic(DiagLevel, Info); - // Let origianl client do it's handling + // Let original client do it's handling if (Client) Client->HandleDiagnostic(DiagLevel, Info); - // We only deal with warnings - if (DiagLevel != DiagnosticsEngine::Warning) - return; - - // Convert and record this diagnostic - auto ToolingDiag = ConvertDiagnostic(Info); - for (unsigned Idx = 0, Last = Info.getNumFixItHints(); - Idx < Last; ++Idx) { - const FixItHint &Hint = Info.getFixItHint(Idx); - const auto replacement = ConvertFixIt(Hint); + // Convert and record warning diagnostics and their notes + if (DiagLevel == DiagnosticsEngine::Warning) { + auto ToolingDiag = ConvertDiagnostic(Info); + for (unsigned Idx = 0, Last = Info.getNumFixItHints(); + Idx < Last; ++Idx) { + const FixItHint &Hint = Info.getFixItHint(Idx); + const auto replacement = ConvertFixIt(Hint); #ifdef DEBUG_FIX_IT_EXPORTER - const auto FileName = SourceMgr.getFilename(Info.getLocation()); - std::cerr << "Handling Fixit #" << Idx << " for " << FileName.str() << std::endl; - std::cerr << "F: " - << Hint.RemoveRange.getBegin().printToString(SourceMgr) << ":" - << Hint.RemoveRange.getEnd().printToString(SourceMgr) << " " - << Hint.InsertFromRange.getBegin().printToString(SourceMgr) << ":" - << Hint.InsertFromRange.getEnd().printToString(SourceMgr) << " " - << Hint.BeforePreviousInsertions << " " - << Hint.CodeToInsert << std::endl; - std::cerr << "R: " << replacement.toString() << std::endl; + const auto FileName = SourceMgr.getFilename(Info.getLocation()); + llvm::errs() << "Handling Fixit #" << Idx << " for " << FileName.str() << "\n"; + llvm::errs() << "F: " + << Hint.RemoveRange.getBegin().printToString(SourceMgr) << ":" + << Hint.RemoveRange.getEnd().printToString(SourceMgr) << " " + << Hint.InsertFromRange.getBegin().printToString(SourceMgr) << ":" + << Hint.InsertFromRange.getEnd().printToString(SourceMgr) << " " + << Hint.BeforePreviousInsertions << " " + << Hint.CodeToInsert << "\n"; + llvm::errs() << "R: " << replacement.toString() << "\n"; #endif - auto &Replacements = ToolingDiag.Fix[replacement.getFilePath()]; - auto error = Replacements.add(ConvertFixIt(Hint)); - if (error) { - Diag(Info.getLocation(), diag::note_fixit_failed); + auto &Replacements = ToolingDiag.Fix[replacement.getFilePath()]; + auto error = Replacements.add(ConvertFixIt(Hint)); + if (error) { + Diag(Info.getLocation(), diag::note_fixit_failed); + } } + TUDiag.Diagnostics.push_back(ToolingDiag); + m_recordNotes = true; + } + // FIXME: We do not receive notes. + else if (DiagLevel == DiagnosticsEngine::Note && m_recordNotes) { +#ifdef DEBUG_FIX_IT_EXPORTER + const auto FileName = SourceMgr.getFilename(Info.getLocation()); + llvm::errs() << "Handling Note for " << FileName.str() << "\n"; +#endif + auto diags = TUDiag.Diagnostics.back(); + auto diag = ConvertDiagnostic(Info); + diags.Notes.append(1, diag.Message); + } + else { + m_recordNotes = false; } - TUDiag.Diagnostics.push_back(ToolingDiag); } void FixItExporter::Export() { std::error_code EC; - llvm::raw_fd_ostream OS(TUDiag.MainSourceFile + ".yaml", EC, llvm::sys::fs::F_None); + llvm::raw_fd_ostream OS(exportFixes, EC, llvm::sys::fs::F_None); llvm::yaml::Output YAML(OS); YAML << TUDiag; } void FixItExporter::Diag(SourceLocation Loc, unsigned DiagID) { // When producing this diagnostic, we temporarily bypass ourselves, // clear out any current diagnostic, and let the downstream client // format the diagnostic. DiagEngine.setClient(Client, false); DiagEngine.Clear(); DiagEngine.Report(Loc, DiagID); DiagEngine.setClient(this, false); } diff --git a/src/FixItExporter.h b/src/FixItExporter.h index 3de100d..7bb5d87 100644 --- a/src/FixItExporter.h +++ b/src/FixItExporter.h @@ -1,67 +1,70 @@ /* This file is part of the clazy static checker. Copyright (C) 2019 Christian Gagneraud 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. */ #ifndef CLAZY_FIX_IT_EXPORTER_H #define CLAZY_FIX_IT_EXPORTER_H #include #include namespace clang { class FixItOptions; } -class FixItExporter : public clang::DiagnosticConsumer +class FixItExporter + : public clang::DiagnosticConsumer { public: FixItExporter(clang::DiagnosticsEngine &DiagEngine, clang::SourceManager &SourceMgr, - const clang::LangOptions &LangOpts, clang::FixItOptions *FixItOpts); + const clang::LangOptions &LangOpts, const std::string &exportFixes); ~FixItExporter() override; bool IncludeInDiagnosticCounts() const override; void BeginSourceFile(const clang::LangOptions &LangOpts, const clang::Preprocessor *PP = nullptr) override; void EndSourceFile() override; void HandleDiagnostic(clang::DiagnosticsEngine::Level DiagLevel, const clang::Diagnostic &Info) override; void Export(); /// Emit a diagnostic via the adapted diagnostic client. void Diag(clang::SourceLocation Loc, unsigned DiagID); private: clang::DiagnosticsEngine &DiagEngine; clang::SourceManager &SourceMgr; const clang::LangOptions &LangOpts; - clang::FixItOptions *FixItOpts; - DiagnosticConsumer *Client; + const std::string exportFixes; + DiagnosticConsumer *Client = nullptr; std::unique_ptr Owner; clang::tooling::TranslationUnitDiagnostics TUDiag; + bool m_recordNotes = false; clang::tooling::Diagnostic ConvertDiagnostic(const clang::Diagnostic &Info); clang::tooling::Replacement ConvertFixIt(const clang::FixItHint &Hint); + }; #endif // CLAZY_FIX_IT_EXPORTER_H