diff --git a/src/Clazy.cpp b/src/Clazy.cpp index ba75bb9..b651d7b 100644 --- a/src/Clazy.cpp +++ b/src/Clazy.cpp @@ -1,410 +1,411 @@ /* 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() 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, exportFixes, ClazyContext::ClazyOption_None); PrintHelp(llvm::errs()); return true; } - 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("export-fixes", args)) + m_options |= ClazyContext::ClazyOption_ExportFixes; 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; 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, const string &exportFixes, +ClazyStandaloneASTAction::ClazyStandaloneASTAction(const string &checkList, + const string &headerFilter, + const string &ignoreDirs, + const string &exportFixesFilename, 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_exportFixesFilename(exportFixesFilename) , m_options(options) { } unique_ptr ClazyStandaloneASTAction::CreateASTConsumer(CompilerInstance &ci, llvm::StringRef) { - auto context = new ClazyContext(ci, m_headerFilter, m_ignoreDirs, m_exportFixes, m_options); + auto context = new ClazyContext(ci, m_headerFilter, m_ignoreDirs, m_exportFixesFilename, 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 846e694..bdb7568 100644 --- a/src/Clazy.h +++ b/src/Clazy.h @@ -1,132 +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, + const std::string &exportFixesFilename, 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 std::string m_exportFixesFilename; 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 8b18535..e29d4ad 100644 --- a/src/ClazyContext.cpp +++ b/src/ClazyContext.cpp @@ -1,134 +1,131 @@ /* 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() { if (const char *suffix = getenv("CLAZY_FIXIT_SUFFIX")) m_suffix = suffix; 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, const string &exportFixes, ClazyOptions opts) + const string &headerFilter, const string &ignoreDirs, + string exportFixesFilename, 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 (exportFixesEnabled()) { + if (exportFixesFilename.empty()) { + // Only clazy-standalone sets the filename by argument. + // clazy plugin sets it automatically here: + const FileEntry *fileEntry = sm.getFileEntryForID(sm.getMainFileID()); + exportFixesFilename = fileEntry->getName().str() + ".clazy.yaml"; } - } - if (fixitsEnabled() && exportFixesEnabled()) // TODO: A single one is enough - exporter = new FixItExporter(ci.getDiagnostics(), sm, ci.getLangOpts(), exportFixes); + exporter = new FixItExporter(ci.getDiagnostics(), sm, ci.getLangOpts(), exportFixesFilename); + } } ClazyContext::~ClazyContext() { //delete preprocessorVisitor; // we don't own it delete accessSpecifierManager; delete parentMap; if (exporter) { exporter->Export(); delete exporter; } 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; } (); return s_isQt; } diff --git a/src/ClazyContext.h b/src/ClazyContext.h index 511f3cd..c6af007 100644 --- a/src/ClazyContext.h +++ b/src/ClazyContext.h @@ -1,193 +1,185 @@ /* 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 CXXMethodDecl; class Decl; } class AccessSpecifierManager; class PreProcessorVisitor; class FixItExporter; class ClazyContext { public: enum ClazyOption { ClazyOption_None = 0, - ClazyOption_AllFixitsEnabled = 4, + ClazyOption_ExportFixes = 1, 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, + std::string exportFixesFilename, ClazyOptions = ClazyOption_None); ~ClazyContext(); bool usingPreCompiledHeaders() const { return !ci.getPreprocessorOpts().ImplicitPCHInclude.empty(); } bool userDisabledWError() const { return m_noWerror; } - bool fixitsEnabled() const - { - return allFixitsEnabled || !requestedFixitName.empty(); - } - bool exportFixesEnabled() const { - return !exportFixes.empty(); + return options & ClazyOption_ExportFixes; } 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; 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 93f0176..7d454da 100644 --- a/src/ClazyStandaloneMain.cpp +++ b/src/ClazyStandaloneMain.cpp @@ -1,120 +1,119 @@ /* 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_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_enableAllFixits.getValue()) - options |= ClazyContext::ClazyOption_AllFixitsEnabled; + if (!s_exportFixes.getValue().empty()) + options |= ClazyContext::ClazyOption_ExportFixes; 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; // 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); + 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/checkbase.cpp b/src/checkbase.cpp index de88ff0..8b40e19 100644 --- a/src/checkbase.cpp +++ b/src/checkbase.cpp @@ -1,325 +1,308 @@ /* 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 "checkbase.h" #include "ClazyContext.h" #include "SourceCompatibilityHelpers.h" #include "SuppressionManager.h" #include "Utils.h" #include "clazy_stl.h" #include #include #include #include #include #include #include #include #include #include #include #include #include namespace clang { class MacroArgs; class Token; } // namespace clang using namespace clang; using namespace clang::ast_matchers; using namespace std; ClazyPreprocessorCallbacks::ClazyPreprocessorCallbacks(CheckBase *check) : check(check) { } void ClazyPreprocessorCallbacks::MacroExpands(const Token ¯oNameTok, const MacroDefinition &md, SourceRange range, const MacroArgs *) { check->VisitMacroExpands(macroNameTok, range, md.getMacroInfo()); } void ClazyPreprocessorCallbacks::Defined(const Token ¯oNameTok, const MacroDefinition &, SourceRange range) { check->VisitDefined(macroNameTok, range); } void ClazyPreprocessorCallbacks::Ifdef(SourceLocation loc, const Token ¯oNameTok, const MacroDefinition &) { check->VisitIfdef(loc, macroNameTok); } void ClazyPreprocessorCallbacks::Ifndef(SourceLocation loc, const Token ¯oNameTok, const MacroDefinition &) { check->VisitIfndef(loc, macroNameTok); } void ClazyPreprocessorCallbacks::If(SourceLocation loc, SourceRange conditionRange, PPCallbacks::ConditionValueKind conditionValue) { check->VisitIf(loc, conditionRange, conditionValue); } void ClazyPreprocessorCallbacks::Elif(SourceLocation loc, SourceRange conditionRange, PPCallbacks::ConditionValueKind conditionValue, SourceLocation ifLoc) { check->VisitElif(loc, conditionRange, conditionValue, ifLoc); } void ClazyPreprocessorCallbacks::Else(SourceLocation loc, SourceLocation ifLoc) { check->VisitElse(loc, ifLoc); } void ClazyPreprocessorCallbacks::Endif(SourceLocation loc, SourceLocation ifLoc) { check->VisitEndif(loc, ifLoc); } void ClazyPreprocessorCallbacks::MacroDefined(const Token ¯oNameTok, const MacroDirective *) { check->VisitMacroDefined(macroNameTok); } CheckBase::CheckBase(const string &name, const ClazyContext *context, Options options) : m_sm(context->ci.getSourceManager()) , m_name(name) , m_context(context) , m_astContext(context->astContext) , m_preprocessorCallbacks(new ClazyPreprocessorCallbacks(this)) , m_options(options) , m_tag(" [-Wclazy-" + m_name + ']') { } CheckBase::~CheckBase() { } void CheckBase::VisitStmt(Stmt *) { // Overriden in derived classes } void CheckBase::VisitDecl(Decl *) { // Overriden in derived classes } void CheckBase::VisitMacroExpands(const Token &, const SourceRange &, const clang::MacroInfo *) { // Overriden in derived classes } void CheckBase::VisitMacroDefined(const Token &) { // Overriden in derived classes } void CheckBase::VisitDefined(const Token &, const SourceRange &) { // Overriden in derived classes } void CheckBase::VisitIfdef(clang::SourceLocation, const clang::Token &) { // Overriden in derived classes } void CheckBase::VisitIfndef(SourceLocation, const Token &) { // Overriden in derived classes } void CheckBase::VisitIf(SourceLocation, SourceRange, clang::PPCallbacks::ConditionValueKind) { // Overriden in derived classes } void CheckBase::VisitElif(SourceLocation, SourceRange, clang::PPCallbacks::ConditionValueKind, SourceLocation) { // Overriden in derived classes } void CheckBase::VisitElse(SourceLocation, SourceLocation) { // Overriden in derived classes } void CheckBase::VisitEndif(SourceLocation, SourceLocation) { // Overriden in derived classes } void CheckBase::enablePreProcessorCallbacks() { Preprocessor &pi = m_context->ci.getPreprocessor(); pi.addPPCallbacks(std::unique_ptr(m_preprocessorCallbacks)); } bool CheckBase::shouldIgnoreFile(SourceLocation loc) const { if (m_filesToIgnore.empty()) return false; if (!loc.isValid()) return true; string filename = sm().getFilename(loc); return clazy::any_of(m_filesToIgnore, [filename](const std::string &ignored) { return clazy::contains(filename, ignored); }); } void CheckBase::emitWarning(const clang::Decl *d, const std::string &error, bool printWarningTag) { emitWarning(clazy::getLocStart(d), error, printWarningTag); } void CheckBase::emitWarning(const clang::Stmt *s, const std::string &error, bool printWarningTag) { emitWarning(clazy::getLocStart(s), error, printWarningTag); } void CheckBase::emitWarning(clang::SourceLocation loc, const std::string &error, bool printWarningTag) { emitWarning(loc, error, {}, printWarningTag); } void CheckBase::emitWarning(clang::SourceLocation loc, std::string error, const vector &fixits, bool printWarningTag) { if (m_context->suppressionManager.isSuppressed(m_name, loc, sm(), lo())) return; if (m_context->shouldIgnoreFile(loc)) return; if (loc.isMacroID()) { if (warningAlreadyEmitted(loc)) return; // For warnings in macro arguments we get a warning in each place the argument is used within the expanded macro, so filter all the dups m_emittedWarningsInMacro.push_back(loc.getRawEncoding()); } if (printWarningTag) error += m_tag; reallyEmitWarning(loc, error, fixits); for (const auto& l : m_queuedManualInterventionWarnings) { string msg = string("FixIt failed, requires manual intervention: "); if (!l.second.empty()) msg += ' ' + l.second; reallyEmitWarning(l.first, msg + m_tag, {}); } m_queuedManualInterventionWarnings.clear(); } void CheckBase::emitInternalError(SourceLocation loc, string error) { llvm::errs() << m_tag << ": internal error: " << error << " at " << loc.printToString(sm()) << "\n"; } void CheckBase::reallyEmitWarning(clang::SourceLocation loc, const std::string &error, const vector &fixits) { FullSourceLoc full(loc, sm()); auto &engine = m_context->ci.getDiagnostics(); auto severity = (engine.getWarningsAsErrors() && !m_context->userDisabledWError()) ? DiagnosticIDs::Error : DiagnosticIDs::Warning; unsigned id = engine.getDiagnosticIDs()->getCustomDiagID(severity, error.c_str()); DiagnosticBuilder B = engine.Report(full, id); for (const FixItHint& fixit : fixits) { if (!fixit.isNull()) B.AddFixItHint(fixit); } } -void CheckBase::queueManualFixitWarning(clang::SourceLocation loc, const string &message, int fixitType) +void CheckBase::queueManualFixitWarning(clang::SourceLocation loc, const string &message) { - if (isFixitEnabled(fixitType) && !manualFixitAlreadyQueued(loc)) { + if (fixitsEnabled() && !manualFixitAlreadyQueued(loc)) { m_queuedManualInterventionWarnings.push_back({loc, message}); m_emittedManualFixItsWarningsInMacro.push_back(loc.getRawEncoding()); } } bool CheckBase::warningAlreadyEmitted(SourceLocation loc) const { PresumedLoc ploc = sm().getPresumedLoc(loc); for (auto rawLoc : m_emittedWarningsInMacro) { SourceLocation l = SourceLocation::getFromRawEncoding(rawLoc); PresumedLoc p = sm().getPresumedLoc(l); if (Utils::presumedLocationsEqual(p, ploc)) return true; } return false; } bool CheckBase::manualFixitAlreadyQueued(SourceLocation loc) const { PresumedLoc ploc = sm().getPresumedLoc(loc); for (auto loc : m_emittedManualFixItsWarningsInMacro) { SourceLocation l = SourceLocation::getFromRawEncoding(loc); PresumedLoc p = sm().getPresumedLoc(l); if (Utils::presumedLocationsEqual(p, ploc)) return true; } return false; } bool CheckBase::isOptionSet(const std::string &optionName) const { const string qualifiedName = name() + '-' + optionName; return m_context->isOptionSet(qualifiedName); } -void CheckBase::setEnabledFixits(int fixits) -{ - m_enabledFixits = fixits; -} - -bool CheckBase::isFixitEnabled(int fixit) const -{ - return (m_enabledFixits & fixit) || (m_context->options & ClazyContext::ClazyOption_AllFixitsEnabled); -} - -bool CheckBase::isFixitEnabled() const -{ - // Checks with only 1 fixit (which is most of them) don't need to pass fixit id - return isFixitEnabled(1); -} - ClazyAstMatcherCallback::ClazyAstMatcherCallback(CheckBase *check) : MatchFinder::MatchCallback() , m_check(check) { - } diff --git a/src/checkbase.h b/src/checkbase.h index 5a4a363..485d826 100644 --- a/src/checkbase.h +++ b/src/checkbase.h @@ -1,187 +1,184 @@ /* 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 CHECK_BASE_H #define CHECK_BASE_H #include "clazy_stl.h" #include "SourceCompatibilityHelpers.h" #include #include #include #include #include #include #include #include #include #include #include #include namespace clang { class CXXMethodDecl; class Stmt; class Decl; class TranslationUnitDecl; class FixItHint; class PresumedLoc; class SourceLocation; class PreprocessorOptions; class LangOptions; class MacroArgs; class MacroDefinition; class MacroDirective; class MacroInfo; class SourceManager; class Token; } class CheckBase; class ClazyContext; enum CheckLevel { // See README.md for what each level does CheckLevelUndefined = -1, CheckLevel0 = 0, CheckLevel1, CheckLevel2, ManualCheckLevel, MaxCheckLevel = CheckLevel2, DefaultCheckLevel = CheckLevel1 }; class ClazyPreprocessorCallbacks : public clang::PPCallbacks { public: ClazyPreprocessorCallbacks(const ClazyPreprocessorCallbacks &) = delete; explicit ClazyPreprocessorCallbacks(CheckBase *check); void MacroExpands(const clang::Token &MacroNameTok, const clang::MacroDefinition &, clang::SourceRange, const clang::MacroArgs *) override; void MacroDefined(const clang::Token &MacroNameTok, const clang::MacroDirective*) override; void Defined(const clang::Token &MacroNameTok, const clang::MacroDefinition &, clang::SourceRange Range) override; void Ifdef(clang::SourceLocation, const clang::Token &MacroNameTok, const clang::MacroDefinition &) override; void Ifndef(clang::SourceLocation Loc, const clang::Token &MacroNameTok, const clang::MacroDefinition &) override; void If(clang::SourceLocation loc, clang::SourceRange conditionRange, clang::PPCallbacks::ConditionValueKind conditionValue) override; void Elif(clang::SourceLocation loc, clang::SourceRange conditionRange, clang::PPCallbacks::ConditionValueKind ConditionValue, clang::SourceLocation ifLoc) override; void Else(clang::SourceLocation loc, clang::SourceLocation ifLoc) override; void Endif(clang::SourceLocation loc, clang::SourceLocation ifLoc) override; private: CheckBase *const check; }; class ClazyAstMatcherCallback : public clang::ast_matchers::MatchFinder::MatchCallback { public: explicit ClazyAstMatcherCallback(CheckBase *check); protected: CheckBase *const m_check; }; class CheckBase { public: enum Option { Option_None = 0, Option_CanIgnoreIncludes = 1 }; typedef int Options; typedef std::vector List; explicit CheckBase(const std::string &name, const ClazyContext *context, Options = Option_None); CheckBase(const CheckBase &other) = delete; virtual ~CheckBase(); std::string name() const { return m_name; } - void setEnabledFixits(int); - bool isFixitEnabled(int fixit) const; - bool isFixitEnabled() const; - void emitWarning(const clang::Decl *, const std::string &error, bool printWarningTag = true); void emitWarning(const clang::Stmt *, const std::string &error, bool printWarningTag = true); void emitWarning(clang::SourceLocation loc, const std::string &error, bool printWarningTag = true); void emitWarning(clang::SourceLocation loc, std::string error, const std::vector &fixits, bool printWarningTag = true); void emitInternalError(clang::SourceLocation loc, std::string error); virtual void registerASTMatchers(clang::ast_matchers::MatchFinder &) {}; bool canIgnoreIncludes() const { return m_options & Option_CanIgnoreIncludes; } virtual void VisitStmt(clang::Stmt *stm); virtual void VisitDecl(clang::Decl *decl); protected: virtual void VisitMacroExpands(const clang::Token ¯oNameTok, const clang::SourceRange &, const clang::MacroInfo *minfo = nullptr); virtual void VisitMacroDefined(const clang::Token ¯oNameTok); virtual void VisitDefined(const clang::Token ¯oNameTok, const clang::SourceRange &); virtual void VisitIfdef(clang::SourceLocation, const clang::Token &); virtual void VisitIfndef(clang::SourceLocation, const clang::Token &); virtual void VisitIf(clang::SourceLocation loc, clang::SourceRange conditionRange, clang::PPCallbacks::ConditionValueKind conditionValue); virtual void VisitElif(clang::SourceLocation loc, clang::SourceRange conditionRange, clang::PPCallbacks::ConditionValueKind ConditionValue, clang::SourceLocation ifLoc); virtual void VisitElse(clang::SourceLocation loc, clang::SourceLocation ifLoc); virtual void VisitEndif(clang::SourceLocation loc, clang::SourceLocation ifLoc); void enablePreProcessorCallbacks(); bool shouldIgnoreFile(clang::SourceLocation) const; void reallyEmitWarning(clang::SourceLocation loc, const std::string &error, const std::vector &fixits); - void queueManualFixitWarning(clang::SourceLocation loc, const std::string &message = {}, int fixitType = 1); + void queueManualFixitWarning(clang::SourceLocation loc, const std::string &message = {}); bool warningAlreadyEmitted(clang::SourceLocation loc) const; bool manualFixitAlreadyQueued(clang::SourceLocation loc) const; bool isOptionSet(const std::string &optionName) const; + bool fixitsEnabled() const { return true; } // Fixits are always shown + // 3 shortcuts for stuff that litter the codebase all over. const clang::SourceManager &sm() const { return m_sm; } const clang::LangOptions &lo() const { return m_astContext.getLangOpts(); } const clang::SourceManager &m_sm; const std::string m_name; const ClazyContext *const m_context; clang::ASTContext &m_astContext; std::vector m_filesToIgnore; private: friend class ClazyPreprocessorCallbacks; friend class ClazyAstMatcherCallback; ClazyPreprocessorCallbacks *const m_preprocessorCallbacks; std::vector m_emittedWarningsInMacro; std::vector m_emittedManualFixItsWarningsInMacro; std::vector> m_queuedManualInterventionWarnings; - int m_enabledFixits = 0; const Options m_options; const std::string m_tag; }; #endif diff --git a/src/checkmanager.cpp b/src/checkmanager.cpp index 85f4d51..eae5bed 100644 --- a/src/checkmanager.cpp +++ b/src/checkmanager.cpp @@ -1,339 +1,319 @@ /* 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 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 "checkmanager.h" #include "clazy_stl.h" #include "ClazyContext.h" #include "Checks.h" #include #include #include #include #include #include #include using namespace clang; using namespace std; static const char * s_fixitNamePrefix = "fix-"; static const char * s_levelPrefix = "level"; std::mutex CheckManager::m_lock; CheckManager::CheckManager() { m_registeredChecks.reserve(100); registerChecks(); } bool CheckManager::checkExists(const string &name) const { return checkForName(m_registeredChecks, name) != m_registeredChecks.cend(); } CheckManager *CheckManager::instance() { static CheckManager s_instance; return &s_instance; } void CheckManager::registerCheck(const RegisteredCheck &check) { m_registeredChecks.push_back(check); } void CheckManager::registerFixIt(int id, const string &fixitName, const string &checkName) { if (!clazy::startsWith(fixitName, s_fixitNamePrefix)) { assert(false); return; } auto &fixits = m_fixitsByCheckName[checkName]; for (const auto& fixit : fixits) { if (fixit.name == fixitName) { // It can't exist assert(false); return; } } RegisteredFixIt fixit = {id, fixitName}; fixits.push_back(fixit); m_fixitByName.insert({fixitName, fixit}); } CheckBase* CheckManager::createCheck(const string &name, ClazyContext *context) { for (const auto& rc : m_registeredChecks) { if (rc.name == name) { return rc.factory(context); } } llvm::errs() << "Invalid check name " << name << "\n"; return nullptr; } string CheckManager::checkNameForFixIt(const string &fixitName) const { if (fixitName.empty()) return {}; for (auto ®isteredCheck : m_registeredChecks) { auto it = m_fixitsByCheckName.find(registeredCheck.name); if (it != m_fixitsByCheckName.end()) { auto &fixits = (*it).second; for (const RegisteredFixIt &fixit : fixits) { if (fixit.name == fixitName) return (*it).first; } } } return {}; } RegisteredCheck::List CheckManager::availableChecks(CheckLevel maxLevel) const { RegisteredCheck::List checks = m_registeredChecks; checks.erase(remove_if(checks.begin(), checks.end(), [maxLevel](const RegisteredCheck &r) { return r.level > maxLevel; }), checks.end()); return checks; } RegisteredCheck::List CheckManager::requestedChecksThroughEnv(const ClazyContext *context, vector &userDisabledChecks) const { static RegisteredCheck::List requestedChecksThroughEnv; static vector disabledChecksThroughEnv; if (requestedChecksThroughEnv.empty()) { const char *checksEnv = getenv("CLAZY_CHECKS"); if (checksEnv) { const string checksEnvStr = clazy::unquoteString(checksEnv); requestedChecksThroughEnv = checksEnvStr == "all_checks" ? availableChecks(CheckLevel2) : checksForCommaSeparatedString(checksEnvStr, /*by-ref=*/ disabledChecksThroughEnv); } - - const string checkName = checkNameForFixIt(context->requestedFixitName); - if (!checkName.empty() && checkForName(requestedChecksThroughEnv, checkName) == requestedChecksThroughEnv.cend()) { - requestedChecksThroughEnv.push_back(*checkForName(m_registeredChecks, checkName)); - } } std::copy(disabledChecksThroughEnv.begin(), disabledChecksThroughEnv.end(), std::back_inserter(userDisabledChecks)); return requestedChecksThroughEnv; } RegisteredCheck::List::const_iterator CheckManager::checkForName(const RegisteredCheck::List &checks, const string &name) const { return clazy::find_if(checks, [name](const RegisteredCheck &r) { return r.name == name; } ); } RegisteredFixIt::List CheckManager::availableFixIts(const string &checkName) const { auto it = m_fixitsByCheckName.find(checkName); return it == m_fixitsByCheckName.end() ? RegisteredFixIt::List() : (*it).second; } static bool takeArgument(const string &arg, vector &args) { auto it = clazy::find(args, arg); if (it != args.end()) { args.erase(it, it + 1); return true; } return false; } RegisteredCheck::List CheckManager::requestedChecks(const ClazyContext *context, std::vector &args) { RegisteredCheck::List result; // #1 Check if a level was specified static const vector levels = { "level0", "level1", "level2" }; const int numLevels = levels.size(); CheckLevel requestedLevel = CheckLevelUndefined; for (int i = 0; i < numLevels; ++i) { if (takeArgument(levels.at(i), args)) { requestedLevel = static_cast(i); break; } } if (args.size() > 1) // we only expect a level and a comma separated list of arguments return {}; if (args.size() == 1) { // #2 Process list of comma separated checks that were passed to compiler result = checksForCommaSeparatedString(args[0]); if (result.empty()) // User passed inexisting checks. return {}; } // #3 Append checks specified from env variable vector userDisabledChecks; RegisteredCheck::List checksFromEnv = requestedChecksThroughEnv(context, /*by-ref*/ userDisabledChecks); copy(checksFromEnv.cbegin(), checksFromEnv.cend(), back_inserter(result)); if (result.empty() && requestedLevel == CheckLevelUndefined) { // No checks or level specified, lets use the default level requestedLevel = DefaultCheckLevel; } // #4 Add checks from requested level RegisteredCheck::List checksFromRequestedLevel = checksForLevel(requestedLevel); clazy::append(checksFromRequestedLevel, result); clazy::sort_and_remove_dups(result, checkLessThan); CheckManager::removeChecksFromList(result, userDisabledChecks); if (context->options & ClazyContext::ClazyOption_Qt4Compat) { // #5 Remove Qt4 incompatible checks result.erase(remove_if(result.begin(), result.end(), [](const RegisteredCheck &c){ return c.options & RegisteredCheck::Option_Qt4Incompatible; }), result.end()); } return result; } RegisteredCheck::List CheckManager::checksForLevel(int level) const { RegisteredCheck::List result; if (level > CheckLevelUndefined && level <= MaxCheckLevel) { clazy::append_if(m_registeredChecks, result, [level](const RegisteredCheck &r) { return r.level <= level; }); } return result; } std::vector> CheckManager::createChecks(const RegisteredCheck::List &requestedChecks, ClazyContext *context) { assert(context); - const string fixitCheckName = checkNameForFixIt(context->requestedFixitName); - RegisteredFixIt fixit = m_fixitByName[context->requestedFixitName]; std::vector> checks; checks.reserve(requestedChecks.size() + 1); for (const auto& check : requestedChecks) { checks.push_back({createCheck(check.name, context), check }); - if (check.name == fixitCheckName) { - checks.back().first->setEnabledFixits(fixit.id); - } - } - - if (!context->requestedFixitName.empty()) { - // We have one fixit enabled, we better have the check instance too. - if (!fixitCheckName.empty()) { - if (checkForName(requestedChecks, fixitCheckName) == requestedChecks.cend()) { - checks.push_back({createCheck(fixitCheckName, context), {} }); - checks.back().first->setEnabledFixits(fixit.id); - } - } } return checks; } /*static */ void CheckManager::removeChecksFromList(RegisteredCheck::List &list, vector &checkNames) { for (auto &name : checkNames) { list.erase(remove_if(list.begin(), list.end(), [name](const RegisteredCheck &c) { return c.name == name; }), list.end()); } } RegisteredCheck::List CheckManager::checksForCommaSeparatedString(const string &str) const { vector byRefDummy; return checksForCommaSeparatedString(str, byRefDummy); } RegisteredCheck::List CheckManager::checksForCommaSeparatedString(const string &str, vector &userDisabledChecks) const { vector checkNames = clazy::splitString(str, ','); RegisteredCheck::List result; for (const string &name : checkNames) { if (checkForName(result, name) != result.cend()) continue; // Already added. Duplicate check specified. continue. auto it = checkForName(m_registeredChecks, name); if (it == m_registeredChecks.cend()) { // Unknown, but might be a fixit name const string checkName = checkNameForFixIt(name); auto it = checkForName(m_registeredChecks, checkName); const bool checkDoesntExist = it == m_registeredChecks.cend(); if (checkDoesntExist) { if (clazy::startsWith(name, s_levelPrefix) && name.size() == strlen(s_levelPrefix) + 1) { auto lastChar = name.back(); const int digit = lastChar - '0'; if (digit > CheckLevelUndefined && digit <= MaxCheckLevel) { RegisteredCheck::List levelChecks = checksForLevel(digit); clazy::append(levelChecks, result); } else { llvm::errs() << "Invalid level: " << name << "\n"; } } else { if (clazy::startsWith(name, "no-")) { string checkName = name; checkName.erase(0, 3); if (checkExists(checkName)) { userDisabledChecks.push_back(checkName); } else { llvm::errs() << "Invalid check to disable: " << name << "\n"; } } else { llvm::errs() << "Invalid check: " << name << "\n"; } } } else { result.push_back(*it); } continue; } else { result.push_back(*it); } } removeChecksFromList(result, userDisabledChecks); return result; } diff --git a/src/checks/level0/qdatetime-utc.cpp b/src/checks/level0/qdatetime-utc.cpp index b7e19cf..0cabbe0 100644 --- a/src/checks/level0/qdatetime-utc.cpp +++ b/src/checks/level0/qdatetime-utc.cpp @@ -1,89 +1,89 @@ /* 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 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 "qdatetime-utc.h" #include "Utils.h" #include "FixItUtils.h" #include "SourceCompatibilityHelpers.h" #include #include #include #include #include #include #include #include #include class ClazyContext; using namespace clang; using namespace std; QDateTimeUtc::QDateTimeUtc(const std::string &name, ClazyContext *context) : CheckBase(name, context) { } void QDateTimeUtc::VisitStmt(clang::Stmt *stmt) { CXXMemberCallExpr *secondCall = dyn_cast(stmt); if (!secondCall || !secondCall->getMethodDecl()) return; CXXMethodDecl *secondMethod = secondCall->getMethodDecl(); const string secondMethodName = secondMethod->getQualifiedNameAsString(); const bool isTimeT = secondMethodName == "QDateTime::toTime_t"; if (!isTimeT && secondMethodName != "QDateTime::toUTC") return; vector chainedCalls = Utils::callListForChain(secondCall); if (chainedCalls.size() < 2) return; CallExpr *firstCall = chainedCalls[chainedCalls.size() - 1]; FunctionDecl *firstFunc = firstCall->getDirectCallee(); if (!firstFunc) return; CXXMethodDecl *firstMethod = dyn_cast(firstFunc); if (!firstMethod || firstMethod->getQualifiedNameAsString() != "QDateTime::currentDateTime") return; std::string replacement = "::currentDateTimeUtc()"; if (isTimeT) { replacement += ".toTime_t()"; } std::vector fixits; - if (isFixitEnabled()) { + if (fixitsEnabled()) { const bool success = clazy::transformTwoCallsIntoOneV2(&m_astContext, secondCall, replacement, fixits); if (!success) { queueManualFixitWarning(clazy::getLocStart(secondCall)); } } emitWarning(clazy::getLocStart(stmt), "Use QDateTime" + replacement + " instead", fixits); } diff --git a/src/checks/level0/qgetenv.cpp b/src/checks/level0/qgetenv.cpp index 7f5a46f..143ac96 100644 --- a/src/checks/level0/qgetenv.cpp +++ b/src/checks/level0/qgetenv.cpp @@ -1,108 +1,108 @@ /* 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 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 "qgetenv.h" #include "Utils.h" #include "StringUtils.h" #include "FixItUtils.h" #include "SourceCompatibilityHelpers.h" #include #include #include #include #include #include #include #include #include #include class ClazyContext; using namespace clang; using namespace std; QGetEnv::QGetEnv(const std::string &name, ClazyContext *context) : CheckBase(name, context, Option_CanIgnoreIncludes) { } void QGetEnv::VisitStmt(clang::Stmt *stmt) { // Lets check only in function calls. Otherwise there are too many false positives, it's common // to implicit cast to bool when checking pointers for validity, like if (ptr) auto *memberCall = dyn_cast(stmt); if (!memberCall) return; CXXMethodDecl *method = memberCall->getMethodDecl(); if (!method) return; CXXRecordDecl *record = method->getParent(); if (!record || clazy::name(record) != "QByteArray") { return; } std::vector calls = Utils::callListForChain(memberCall); if (calls.size() != 2) return; CallExpr *qgetEnvCall = calls.back(); FunctionDecl *func = qgetEnvCall->getDirectCallee(); if (!func || clazy::name(func) != "qgetenv") return; StringRef methodname = clazy::name(method); string errorMsg; std::string replacement; if (methodname == "isEmpty") { errorMsg = "qgetenv().isEmpty() allocates."; replacement = "qEnvironmentVariableIsEmpty"; } else if (methodname == "isNull") { errorMsg = "qgetenv().isNull() allocates."; replacement = "qEnvironmentVariableIsSet"; } else if (methodname == "toInt") { errorMsg = "qgetenv().toInt() is slow."; replacement = "qEnvironmentVariableIntValue"; } if (!errorMsg.empty()) { std::vector fixits; - if (isFixitEnabled()) { + if (fixitsEnabled()) { const bool success = clazy::transformTwoCallsIntoOne(&m_astContext, qgetEnvCall, memberCall, replacement, fixits); if (!success) { queueManualFixitWarning(clazy::getLocStart(memberCall)); } } errorMsg += " Use " + replacement + "() instead"; emitWarning(clazy::getLocStart(memberCall), errorMsg.c_str(), fixits); } } diff --git a/src/checks/level0/qstring-ref.cpp b/src/checks/level0/qstring-ref.cpp index 1495b5b..d1d8a4e 100644 --- a/src/checks/level0/qstring-ref.cpp +++ b/src/checks/level0/qstring-ref.cpp @@ -1,248 +1,248 @@ /* This file is part of the clazy static checker. Copyright (C) 2015 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 "qstring-ref.h" #include "ClazyContext.h" #include "Utils.h" #include "HierarchyUtils.h" #include "StringUtils.h" #include "FixItUtils.h" #include "SourceCompatibilityHelpers.h" #include "clazy_stl.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace clang { class Decl; class LangOptions; } // namespace clang using namespace clang; using namespace std; StringRefCandidates::StringRefCandidates(const std::string &name, ClazyContext *context) : CheckBase(name, context, Option_CanIgnoreIncludes) { } static bool isInterestingFirstMethod(CXXMethodDecl *method) { if (!method || clazy::name(method->getParent()) != "QString") return false; static const llvm::SmallVector list = {{ "left", "mid", "right" }}; return clazy::contains(list, clazy::name(method)); } static bool isInterestingSecondMethod(CXXMethodDecl *method, const clang::LangOptions &lo) { if (!method || clazy::name(method->getParent()) != "QString") return false; static const std::array list = {{ "compare", "contains", "count", "startsWith", "endsWith", "indexOf", "isEmpty", "isNull", "lastIndexOf", "length", "size", "toDouble", "toFloat", "toInt", "toUInt", "toULong", "toULongLong", "toUShort", "toUcs4" }}; if (!clazy::contains(list, clazy::name(method))) return false; return !clazy::anyArgIsOfAnySimpleType(method, {"QRegExp", "QRegularExpression"}, lo); } static bool isMethodReceivingQStringRef(CXXMethodDecl *method) { if (!method || clazy::name(method->getParent()) != "QString") return false; static const std::array list = {{ "append", "compare", "count", "indexOf", "endsWith", "lastIndexOf", "localAwareCompare", "startsWidth" }}; if (clazy::contains(list, clazy::name(method))) return true; if (method->getOverloadedOperator() == OO_PlusEqual) // operator+= return true; return false; } void StringRefCandidates::VisitStmt(clang::Stmt *stmt) { // Here we look for code like str.firstMethod().secondMethod(), where firstMethod() is for example mid() and secondMethod is for example, toInt() auto call = dyn_cast(stmt); if (!call || processCase1(dyn_cast(call))) return; processCase2(call); } static bool containsChild(Stmt *s, Stmt *target) { if (!s) return false; if (s == target) return true; if (auto mte = dyn_cast(s)) { return containsChild(mte->getTemporary(), target); } else if (auto ice = dyn_cast(s)) { return containsChild(ice->getSubExpr(), target); } else if (auto bte = dyn_cast(s)) { return containsChild(bte->getSubExpr(), target); } return false; } bool StringRefCandidates::isConvertedToSomethingElse(clang::Stmt* s) const { // While passing a QString to the QVariant ctor works fine, passing QStringRef doesn't // So let's not warn when QStrings are cast to something else. if (!s) return false; auto constr = clazy::getFirstParentOfType(m_context->parentMap, s); if (!constr || constr->getNumArgs() == 0) return false; if (containsChild(constr->getArg(0), s)) { CXXConstructorDecl *ctor = constr->getConstructor(); CXXRecordDecl *record = ctor ? ctor->getParent() : nullptr; return record ? record->getQualifiedNameAsString() != "QString" : false; } return false; } // Catches cases like: int i = s.mid(1, 1).toInt() bool StringRefCandidates::processCase1(CXXMemberCallExpr *memberCall) { if (!memberCall) return false; // In the AST secondMethod() is parent of firstMethod() call, and will be visited first (because at runtime firstMethod() is resolved first(). // So check for interesting second method first CXXMethodDecl *method = memberCall->getMethodDecl(); if (!isInterestingSecondMethod(method, lo())) return false; vector callExprs = Utils::callListForChain(memberCall); if (callExprs.size() < 2) return false; // The list now contains {secondMethod(), firstMethod() } auto firstMemberCall = dyn_cast(callExprs.at(1)); if (!firstMemberCall || !isInterestingFirstMethod(firstMemberCall->getMethodDecl())) return false; if (isConvertedToSomethingElse(memberCall)) return false; const string firstMethodName = firstMemberCall->getMethodDecl()->getNameAsString(); std::vector fixits; - if (isFixitEnabled()) + if (fixitsEnabled()) fixits = fixit(firstMemberCall); emitWarning(clazy::getLocEnd(firstMemberCall), "Use " + firstMethodName + "Ref() instead", fixits); return true; } // Catches cases like: s.append(s2.mid(1, 1)); bool StringRefCandidates::processCase2(CallExpr *call) { auto memberCall = dyn_cast(call); auto operatorCall = memberCall ? nullptr : dyn_cast(call); CXXMethodDecl *method = nullptr; if (memberCall) { method = memberCall->getMethodDecl(); } else if (operatorCall && operatorCall->getCalleeDecl()) { Decl *decl = operatorCall->getCalleeDecl(); method = dyn_cast(decl); } if (!isMethodReceivingQStringRef(method)) return false; Expr *firstArgument = call->getNumArgs() > 0 ? call->getArg(0) : nullptr; MaterializeTemporaryExpr *temp = firstArgument ? dyn_cast(firstArgument) : nullptr; if (!temp) { Expr *secondArgument = call->getNumArgs() > 1 ? call->getArg(1) : nullptr; temp = secondArgument ? dyn_cast(secondArgument) : nullptr; if (!temp) // For the CXXOperatorCallExpr it's in the second argument return false; } CallExpr *innerCall = clazy::getFirstChildOfType2(temp); auto innerMemberCall = innerCall ? dyn_cast(innerCall) : nullptr; if (!innerMemberCall) return false; CXXMethodDecl *innerMethod = innerMemberCall->getMethodDecl(); if (!isInterestingFirstMethod(innerMethod)) return false; std::vector fixits; - if (isFixitEnabled()) { + if (fixitsEnabled()) { fixits = fixit(innerMemberCall); } emitWarning(clazy::getLocStart(call), "Use " + innerMethod->getNameAsString() + "Ref() instead", fixits); return true; } std::vector StringRefCandidates::fixit(CXXMemberCallExpr *call) { MemberExpr *memberExpr = clazy::getFirstChildOfType(call); if (!memberExpr) { queueManualFixitWarning(clazy::getLocStart(call), "Internal error 1"); return {}; } auto insertionLoc = Lexer::getLocForEndOfToken(clazy::getLocEnd(memberExpr), 0, sm(), lo()); // llvm::errs() << insertionLoc.printToString(sm()) << "\n"; if (!insertionLoc.isValid()) { queueManualFixitWarning(clazy::getLocStart(call), "Internal error 2"); return {}; } std::vector fixits; fixits.push_back(clazy::createInsertion(insertionLoc, "Ref")); return fixits; } diff --git a/src/checks/level1/auto-unexpected-qstringbuilder.cpp b/src/checks/level1/auto-unexpected-qstringbuilder.cpp index 22a559f..b07d5bb 100644 --- a/src/checks/level1/auto-unexpected-qstringbuilder.cpp +++ b/src/checks/level1/auto-unexpected-qstringbuilder.cpp @@ -1,96 +1,96 @@ /* This file is part of the clazy static checker. Copyright (C) 2015 Sergio Martins Copyright (C) 2015 Mathias Hasselmann 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 "auto-unexpected-qstringbuilder.h" #include "FixItUtils.h" #include "TypeUtils.h" #include "SourceCompatibilityHelpers.h" #include "StringUtils.h" #include #include #include #include #include #include #include #include #include #include #include #include class ClazyContext; using namespace clang; using namespace std; static bool isQStringBuilder(QualType t) { CXXRecordDecl *record = TypeUtils::typeAsRecord(t); return record && clazy::name(record) == "QStringBuilder"; } AutoUnexpectedQStringBuilder::AutoUnexpectedQStringBuilder(const std::string &name, ClazyContext *context) : CheckBase(name, context, Option_CanIgnoreIncludes) { } void AutoUnexpectedQStringBuilder::VisitDecl(Decl *decl) { VarDecl *varDecl = dyn_cast(decl); if (!varDecl) return; QualType qualtype = varDecl->getType(); const Type *type = qualtype.getTypePtrOrNull(); if (!type || !type->isRecordType() || !dyn_cast(type) || !isQStringBuilder(qualtype)) return; std::vector fixits; - if (isFixitEnabled()) { + if (fixitsEnabled()) { std::string replacement = "QString " + clazy::name(varDecl).str(); if (qualtype.isConstQualified()) replacement = "const " + replacement; SourceLocation start = clazy::getLocStart(varDecl); SourceLocation end = varDecl->getLocation(); fixits.push_back(clazy::createReplacement({ start, end }, replacement)); } emitWarning(clazy::getLocStart(decl), "auto deduced to be QStringBuilder instead of QString. Possible crash.", fixits); } void AutoUnexpectedQStringBuilder::VisitStmt(Stmt *stmt) { auto lambda = dyn_cast(stmt); if (!lambda) return; CXXMethodDecl *method = lambda->getCallOperator(); if (!method || !isQStringBuilder(method->getReturnType())) return; emitWarning(clazy::getLocStart(stmt), "lambda return type deduced to be QStringBuilder instead of QString. Possible crash."); } diff --git a/src/checks/level1/range-loop.cpp b/src/checks/level1/range-loop.cpp index 7d2a88b..19695b8 100644 --- a/src/checks/level1/range-loop.cpp +++ b/src/checks/level1/range-loop.cpp @@ -1,164 +1,164 @@ /* 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 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 "range-loop.h" #include "Utils.h" #include "QtUtils.h" #include "TypeUtils.h" #include "StringUtils.h" #include "LoopUtils.h" #include "StmtBodyRange.h" #include "SourceCompatibilityHelpers.h" #include "FixItUtils.h" #include "ClazyContext.h" #include "PreProcessorVisitor.h" #include #include #include #include #include #include #include #include class ClazyContext; using namespace clang; using namespace std; enum Fixit { Fixit_AddRef = 1, Fixit_AddqAsConst = 2 }; RangeLoop::RangeLoop(const std::string &name, ClazyContext *context) : CheckBase(name, context, Option_CanIgnoreIncludes) { - if (isFixitEnabled(Fixit_AddqAsConst)) { + if (fixitsEnabled()) { context->enablePreprocessorVisitor(); } } void RangeLoop::VisitStmt(clang::Stmt *stmt) { if (auto rangeLoop = dyn_cast(stmt)) { processForRangeLoop(rangeLoop); } } bool RangeLoop::islvalue(Expr *exp, SourceLocation &endLoc) { if (isa(exp)) { endLoc = clazy::locForEndOfToken(&m_astContext, clazy::getLocStart(exp)); return true; } if (auto me = dyn_cast(exp)) { auto decl = me->getMemberDecl(); if (!decl || isa(decl)) return false; endLoc = clazy::locForEndOfToken(&m_astContext, me->getMemberLoc()); return true; } return false; } void RangeLoop::processForRangeLoop(CXXForRangeStmt *rangeLoop) { Expr *containerExpr = rangeLoop->getRangeInit(); if (!containerExpr) return; QualType qt = containerExpr->getType(); const Type *t = qt.getTypePtrOrNull(); if (!t || !t->isRecordType()) return; checkPassByConstRefCorrectness(rangeLoop); if (qt.isConstQualified()) // const won't detach return; auto loopVariableType = rangeLoop->getLoopVariable()->getType(); if (!TypeUtils::unrefQualType(loopVariableType).isConstQualified() && loopVariableType->isReferenceType()) return; CXXRecordDecl *record = t->getAsCXXRecordDecl(); if (!clazy::isQtCOWIterableClass(Utils::rootBaseClass(record))) return; StmtBodyRange bodyRange(nullptr, &sm(), clazy::getLocStart(rangeLoop)); if (clazy::containerNeverDetaches(clazy::containerDeclForLoop(rangeLoop), bodyRange)) return; std::vector fixits; SourceLocation end; - if (isFixitEnabled(Fixit_AddqAsConst) && islvalue(containerExpr, end)) { + if (fixitsEnabled() && islvalue(containerExpr, end)) { PreProcessorVisitor *preProcessorVisitor = m_context->preprocessorVisitor; if (!preProcessorVisitor || preProcessorVisitor->qtVersion() >= 50700) { // qAsConst() was added to 5.7 SourceLocation start = clazy::getLocStart(containerExpr); fixits.push_back(clazy::createInsertion(start, "qAsConst(")); //SourceLocation end = getLocEnd(containerExpr); fixits.push_back(clazy::createInsertion(end, ")")); } } emitWarning(clazy::getLocStart(rangeLoop), "c++11 range-loop might detach Qt container (" + record->getQualifiedNameAsString() + ')', fixits); } void RangeLoop::checkPassByConstRefCorrectness(CXXForRangeStmt *rangeLoop) { TypeUtils::QualTypeClassification classif; auto varDecl = rangeLoop->getLoopVariable(); bool success = TypeUtils::classifyQualType(m_context, varDecl, /*by-ref*/ classif, rangeLoop); if (!success) return; if (classif.passNonTriviallyCopyableByConstRef) { string msg; const string paramStr = clazy::simpleTypeName(varDecl->getType(), lo()); msg = "Missing reference in range-for with non trivial type (" + paramStr + ')'; std::vector fixits; - if (isFixitEnabled(Fixit_AddRef)) { + if (fixitsEnabled()) { const bool isConst = varDecl->getType().isConstQualified(); if (!isConst) { SourceLocation start = clazy::getLocStart(varDecl); fixits.push_back(clazy::createInsertion(start, "const ")); } SourceLocation end = varDecl->getLocation(); fixits.push_back(clazy::createInsertion(end, "&")); } // We ignore classif.passSmallTrivialByValue because it doesn't matter, the compiler is able // to optimize it, generating the same assembly, regardless of pass by value. emitWarning(clazy::getLocStart(varDecl), msg.c_str(), fixits); } } diff --git a/src/checks/level2/function-args-by-ref.cpp b/src/checks/level2/function-args-by-ref.cpp index cf49faa..6b7b5cb 100644 --- a/src/checks/level2/function-args-by-ref.cpp +++ b/src/checks/level2/function-args-by-ref.cpp @@ -1,202 +1,202 @@ /* 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,2018 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 "function-args-by-ref.h" #include "Utils.h" #include "TypeUtils.h" #include "ClazyContext.h" #include "StringUtils.h" #include "SourceCompatibilityHelpers.h" #include "clazy_stl.h" #include "FixItUtils.h" #include #include #include #include #include #include #include #include #include #include #include using namespace clang; using namespace std; static bool shouldIgnoreClass(CXXRecordDecl *record) { if (!record) return false; if (Utils::isSharedPointer(record)) return true; static const vector ignoreList = {"QDebug", // Too many warnings "QGenericReturnArgument", "QColor", // TODO: Remove in Qt6 "QStringRef", // TODO: Remove in Qt6 "QList::const_iterator", // TODO: Remove in Qt6 "QJsonArray::const_iterator", // TODO: Remove in Qt6 "QList::const_iterator", // TODO: Remove in Qt6 "QtMetaTypePrivate::QSequentialIterableImpl", "QtMetaTypePrivate::QAssociativeIterableImpl", "QVariantComparisonHelper", "QHashDummyValue", "QCharRef", "QString::Null" }; return clazy::contains(ignoreList, record->getQualifiedNameAsString()); } static bool shouldIgnoreOperator(FunctionDecl *function) { // Too many warnings in operator<< static const vector ignoreList = { "operator<<" }; return clazy::contains(ignoreList, clazy::name(function)); } static bool shouldIgnoreFunction(clang::FunctionDecl *function) { static const vector qualifiedIgnoreList = {"QDBusMessage::createErrorReply", // Fixed in Qt6 "QMenu::exec", // Fixed in Qt6 "QTextFrame::iterator", // Fixed in Qt6 "QGraphicsWidget::addActions", // Fixed in Qt6 "QListWidget::mimeData", // Fixed in Qt6 "QTableWidget::mimeData", // Fixed in Qt6 "QTreeWidget::mimeData", // Fixed in Qt6 "QWidget::addActions", // Fixed in Qt6 "QSslCertificate::verify", // Fixed in Qt6 "QSslConfiguration::setAllowedNextProtocols" // Fixed in Qt6 }; return clazy::contains(qualifiedIgnoreList, function->getQualifiedNameAsString()); } FunctionArgsByRef::FunctionArgsByRef(const std::string &name, ClazyContext *context) : CheckBase(name, context, Option_CanIgnoreIncludes) { } static std::string warningMsgForSmallType(int sizeOf, const std::string &typeName) { std::string sizeStr = std::to_string(sizeOf); return "Missing reference on large type (sizeof " + typeName + " is " + sizeStr + " bytes)"; } void FunctionArgsByRef::processFunction(FunctionDecl *func) { if (!func || !func->isThisDeclarationADefinition() || func->isDeleted() || shouldIgnoreOperator(func)) return; if (m_context->isQtDeveloper() && shouldIgnoreFunction(func)) return; - const bool warnForOverriddenMethods = isOptionSet("warn-for-overridden-methods") || isFixitEnabled(); + const bool warnForOverriddenMethods = isOptionSet("warn-for-overridden-methods") || fixitsEnabled(); if (!warnForOverriddenMethods && Utils::methodOverrides(dyn_cast(func))) { // When overriding you can't change the signature. You should fix the base classes first return; } Stmt *body = func->getBody(); auto funcParams = Utils::functionParameters(func); for (unsigned int i = 0; i < funcParams.size(); ++i) { ParmVarDecl* param = funcParams[i]; QualType paramQt = TypeUtils::unrefQualType(param->getType()); const Type *paramType = paramQt.getTypePtrOrNull(); if (!paramType || paramType->isIncompleteType() || paramType->isDependentType()) continue; if (shouldIgnoreClass(paramType->getAsCXXRecordDecl())) continue; TypeUtils::QualTypeClassification classif; bool success = TypeUtils::classifyQualType(m_context, param, classif, body); if (!success) continue; vector ctorInits = Utils::ctorInitializer(dyn_cast(func), param); if (Utils::ctorInitializerContainsMove(ctorInits)) continue; if (classif.passBigTypeByConstRef || classif.passNonTriviallyCopyableByConstRef) { string error; std::vector fixits; const string paramStr = param->getType().getAsString(); if (classif.passBigTypeByConstRef) { error = warningMsgForSmallType(classif.size_of_T, paramStr); } else if (classif.passNonTriviallyCopyableByConstRef) { error = "Missing reference on non-trivial type (" + paramStr + ')'; } addFixits(fixits, func, i); emitWarning(clazy::getLocStart(param), error.c_str(), fixits); } } } void FunctionArgsByRef::addFixits(std::vector &fixits, FunctionDecl *func, unsigned int parmIndex) { - if (isFixitEnabled()) { + if (fixitsEnabled()) { for (auto funcRedecl : func->redecls()) { auto funcParams = Utils::functionParameters(funcRedecl); if (funcParams.size() <= parmIndex) return; ParmVarDecl *param = funcParams[parmIndex]; QualType paramQt = TypeUtils::unrefQualType(param->getType()); const bool isConst = paramQt.isConstQualified(); if (!isConst) { SourceLocation start = clazy::getLocStart(param); fixits.push_back(clazy::createInsertion(start, "const ")); } SourceLocation end = param->getLocation(); fixits.push_back(clazy::createInsertion(end, "&")); } } } void FunctionArgsByRef::VisitDecl(Decl *decl) { processFunction(dyn_cast(decl)); } void FunctionArgsByRef::VisitStmt(Stmt *stmt) { if (auto lambda = dyn_cast(stmt)) { if (!shouldIgnoreFile(clazy::getLocStart(stmt))) processFunction(lambda->getCallOperator()); } } clang::FixItHint FunctionArgsByRef::fixit(const ParmVarDecl *, TypeUtils::QualTypeClassification) { FixItHint fixit; return fixit; } diff --git a/src/checks/level2/function-args-by-value.cpp b/src/checks/level2/function-args-by-value.cpp index d62fa86..0bd270a 100644 --- a/src/checks/level2/function-args-by-value.cpp +++ b/src/checks/level2/function-args-by-value.cpp @@ -1,227 +1,227 @@ /* This file is part of the clazy static checker. Copyright (C) 2016-2018 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 "function-args-by-value.h" #include "Utils.h" #include "StringUtils.h" #include "TypeUtils.h" #include "FixItUtils.h" #include "ClazyContext.h" #include "SourceCompatibilityHelpers.h" #include "clazy_stl.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace clang { class Decl; } // namespace clang using namespace clang; using namespace std; // TODO, go over all these static bool shouldIgnoreClass(CXXRecordDecl *record) { if (!record) return false; if (Utils::isSharedPointer(record)) return true; static const vector ignoreList = {"QDebug", // Too many warnings "QGenericReturnArgument", "QColor", // TODO: Remove in Qt6 "QStringRef", // TODO: Remove in Qt6 "QList::const_iterator", // TODO: Remove in Qt6 "QJsonArray::const_iterator", // TODO: Remove in Qt6 "QList::const_iterator", // TODO: Remove in Qt6 "QtMetaTypePrivate::QSequentialIterableImpl", "QtMetaTypePrivate::QAssociativeIterableImpl", "QVariantComparisonHelper", "QHashDummyValue", "QCharRef", "QString::Null" }; return clazy::contains(ignoreList, record->getQualifiedNameAsString()); } static bool shouldIgnoreOperator(FunctionDecl *function) { // Too many warnings in operator<< static const vector ignoreList = { "operator<<" }; return clazy::contains(ignoreList, clazy::name(function)); } static bool shouldIgnoreFunction(clang::FunctionDecl *function) { static const vector qualifiedIgnoreList = {"QDBusMessage::createErrorReply", // Fixed in Qt6 "QMenu::exec", // Fixed in Qt6 "QTextFrame::iterator", // Fixed in Qt6 "QGraphicsWidget::addActions", // Fixed in Qt6 "QListWidget::mimeData", // Fixed in Qt6 "QTableWidget::mimeData", // Fixed in Qt6 "QTreeWidget::mimeData", // Fixed in Qt6 "QWidget::addActions", // Fixed in Qt6 "QSslCertificate::verify", // Fixed in Qt6 "QSslConfiguration::setAllowedNextProtocols" // Fixed in Qt6 }; return clazy::contains(qualifiedIgnoreList, function->getQualifiedNameAsString()); } FunctionArgsByValue::FunctionArgsByValue(const std::string &name, ClazyContext *context) : CheckBase(name, context, Option_CanIgnoreIncludes) { } void FunctionArgsByValue::VisitDecl(Decl *decl) { processFunction(dyn_cast(decl)); } void FunctionArgsByValue::VisitStmt(Stmt *stmt) { if (auto lambda = dyn_cast(stmt)) processFunction(lambda->getCallOperator()); } void FunctionArgsByValue::processFunction(FunctionDecl *func) { if (!func || !func->isThisDeclarationADefinition() || func->isDeleted()) return; auto ctor = dyn_cast(func); if (ctor && ctor->isCopyConstructor()) return; // copy-ctor must take by ref const bool warnForOverriddenMethods = isOptionSet("warn-for-overridden-methods"); if (!warnForOverriddenMethods && Utils::methodOverrides(dyn_cast(func))) { // When overriding you can't change the signature. You should fix the base classes first return; } if (shouldIgnoreOperator(func)) return; if (m_context->isQtDeveloper() && shouldIgnoreFunction(func)) return; Stmt *body = func->getBody(); int i = -1; for (auto param : Utils::functionParameters(func)) { i++; QualType paramQt = TypeUtils::unrefQualType(param->getType()); const Type *paramType = paramQt.getTypePtrOrNull(); if (!paramType || paramType->isIncompleteType() || paramType->isDependentType()) continue; if (shouldIgnoreClass(paramType->getAsCXXRecordDecl())) continue; TypeUtils::QualTypeClassification classif; bool success = TypeUtils::classifyQualType(m_context, param, classif, body); if (!success) continue; if (classif.passSmallTrivialByValue) { if (ctor) { // Implements fix for Bug #379342 vector initializers = Utils::ctorInitializer(ctor, param); bool found_by_ref_member_init = false; for (auto initializer : initializers) { if (!initializer->isMemberInitializer()) continue; // skip base class initializer FieldDecl *field = initializer->getMember(); if (!field) continue; QualType type = field->getType(); if (type.isNull() || type->isReferenceType()) { found_by_ref_member_init = true; break; } } if (found_by_ref_member_init) continue; } std::vector fixits; auto method = dyn_cast(func); const bool isVirtualMethod = method && method->isVirtual(); - if ((!isVirtualMethod || warnForOverriddenMethods) && isFixitEnabled()) { // Don't try to fix virtual methods, as build can fail + if ((!isVirtualMethod || warnForOverriddenMethods) && fixitsEnabled()) { // Don't try to fix virtual methods, as build can fail for (auto redecl : func->redecls()) { // Fix in both header and .cpp auto fdecl = dyn_cast(redecl); const ParmVarDecl *param = fdecl->getParamDecl(i); fixits.push_back(fixit(fdecl, param, classif)); } } const string paramStr = param->getType().getAsString(); string error = "Pass small and trivially-copyable type by value (" + paramStr + ')'; emitWarning(clazy::getLocStart(param), error.c_str(), fixits); } } } FixItHint FunctionArgsByValue::fixit(FunctionDecl *func, const ParmVarDecl *param, TypeUtils::QualTypeClassification) { QualType qt = TypeUtils::unrefQualType(param->getType()); qt.removeLocalConst(); const string typeName = qt.getAsString(PrintingPolicy(lo())); string replacement = typeName + ' ' + string(clazy::name(param)); SourceLocation startLoc = clazy::getLocStart(param); SourceLocation endLoc = clazy::getLocEnd(param); const int numRedeclarations = std::distance(func->redecls_begin(), func->redecls_end()); const bool definitionIsAlsoDeclaration = numRedeclarations == 1; const bool isDeclarationButNotDefinition = !func->doesThisDeclarationHaveABody(); if (param->hasDefaultArg() && (isDeclarationButNotDefinition || definitionIsAlsoDeclaration)) { endLoc = clazy::getLocStart(param->getDefaultArg()).getLocWithOffset(-1); replacement += " ="; } if (!startLoc.isValid() || !endLoc.isValid()) { llvm::errs() << "Internal error could not apply fixit " << startLoc.printToString(sm()) << ';' << endLoc.printToString(sm()) << "\n"; return {}; } return clazy::createReplacement({ startLoc, endLoc }, replacement); } diff --git a/src/checks/level2/old-style-connect.cpp b/src/checks/level2/old-style-connect.cpp index 6a14931..0fe68c1 100644 --- a/src/checks/level2/old-style-connect.cpp +++ b/src/checks/level2/old-style-connect.cpp @@ -1,509 +1,509 @@ /* 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 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 "old-style-connect.h" #include "Utils.h" #include "StringUtils.h" #include "FixItUtils.h" #include "ContextUtils.h" #include "QtUtils.h" #include "ClazyContext.h" #include "AccessSpecifierManager.h" #include "HierarchyUtils.h" #include "SourceCompatibilityHelpers.h" #include "clazy_stl.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace clang { class MacroInfo; } // namespace clang using namespace clang; using namespace std; namespace clazy { // Copied from Clang's Expr.cpp and added "|| !DerivedType->isRecordType()" to avoid a crash const CXXRecordDecl *getBestDynamicClassType(Expr *expr) { if (!expr) return nullptr; const Expr *E = expr->getBestDynamicClassTypeExpr(); QualType DerivedType = E->getType(); if (const PointerType *PTy = DerivedType->getAs()) DerivedType = PTy->getPointeeType(); if (DerivedType->isDependentType() || !DerivedType->isRecordType()) return nullptr; const RecordType *Ty = DerivedType->castAs(); Decl *D = Ty->getDecl(); return cast(D); } } enum ConnectFlag { ConnectFlag_None = 0, // Not a disconnect or connect ConnectFlag_Connect = 1, // It's a connect ConnectFlag_Disconnect = 2, // It's a disconnect ConnectFlag_QTimerSingleShot = 4, ConnectFlag_OldStyle = 8, // Qt4 style ConnectFlag_4ArgsDisconnect = 16, // disconnect(const char *signal = 0, const QObject *receiver = 0, const char *method = 0) const ConnectFlag_3ArgsDisconnect = 32, // disconnect(SIGNAL(foo)) ConnectFlag_2ArgsDisconnect = 64, //disconnect(const QObject *receiver, const char *method = 0) const ConnectFlag_5ArgsConnect = 128, // connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type = Qt::AutoConnection) ConnectFlag_4ArgsConnect = 256, // connect(const QObject *sender, const char *signal, const char *method, Qt::ConnectionType type = Qt::AutoConnection) ConnectFlag_OldStyleButNonLiteral = 0x200, // connect(foo, SIGNAL(bar()), foo, variableWithSlotName); // here the slot name isn't a literal ConnectFlag_QStateAddTransition = 0x400, ConnectFlag_QMenuAddAction = 0x800, ConnectFlag_QMessageBoxOpen = 0x1000, ConnectFlag_QSignalSpy = 0x2000, ConnectFlag_Bogus = 0x4000 }; static bool classIsOk(StringRef className) { // List of classes we usually use Qt4 syntax return className != "QDBusInterface"; } OldStyleConnect::OldStyleConnect(const std::string &name, ClazyContext *context) : CheckBase(name, context, Option_CanIgnoreIncludes) { enablePreProcessorCallbacks(); context->enableAccessSpecifierManager(); } template int OldStyleConnect::classifyConnect(FunctionDecl *connectFunc, T *connectCall) const { int classification = ConnectFlag_None; const string methodName = connectFunc->getQualifiedNameAsString(); if (methodName == "QObject::connect") classification |= ConnectFlag_Connect; else if (methodName == "QObject::disconnect") classification |= ConnectFlag_Disconnect; else if (methodName == "QTimer::singleShot") classification |= ConnectFlag_QTimerSingleShot; else if (methodName == "QState::addTransition") classification |= ConnectFlag_QStateAddTransition; else if (methodName == "QMenu::addAction") classification |= ConnectFlag_QMenuAddAction; else if (methodName == "QMessageBox::open") classification |= ConnectFlag_QMessageBoxOpen; else if (methodName == "QSignalSpy::QSignalSpy") classification |= ConnectFlag_QSignalSpy; if (classification == ConnectFlag_None) return classification; if (clazy::connectHasPMFStyle(connectFunc)) return classification; else classification |= ConnectFlag_OldStyle; const unsigned int numParams = connectFunc->getNumParams(); if (classification & ConnectFlag_Connect) { if (numParams == 5) { classification |= ConnectFlag_5ArgsConnect; } else if (numParams == 4) { classification |= ConnectFlag_4ArgsConnect; } else { classification |= ConnectFlag_Bogus; } } else if (classification & ConnectFlag_Disconnect) { if (numParams == 4) { classification |= ConnectFlag_4ArgsDisconnect; } else if (numParams == 3) { classification |= ConnectFlag_3ArgsDisconnect; } else if (numParams == 2) { classification |= ConnectFlag_2ArgsDisconnect; } else { classification |= ConnectFlag_Bogus; } } if (classification & ConnectFlag_OldStyle) { // It's old style, but check if all macros are literals int numLiterals = 0; for (auto arg : connectCall->arguments()) { auto argLocation = clazy::getLocStart(arg); string dummy; if (isSignalOrSlot(argLocation, dummy)) ++numLiterals; } if ((classification & ConnectFlag_QTimerSingleShot) && numLiterals != 1) { classification |= ConnectFlag_OldStyleButNonLiteral; } else if (((classification & ConnectFlag_Connect) && numLiterals != 2)) { classification |= ConnectFlag_OldStyleButNonLiteral; } else if ((classification & ConnectFlag_4ArgsDisconnect) && numLiterals != 2) { classification |= ConnectFlag_OldStyleButNonLiteral; } else if ((classification & ConnectFlag_QStateAddTransition) && numLiterals != 1) { classification |= ConnectFlag_OldStyleButNonLiteral; } else if ((classification & ConnectFlag_Disconnect) && numLiterals == 0) { classification |= ConnectFlag_OldStyleButNonLiteral; } else if ((classification & ConnectFlag_QMenuAddAction) && numLiterals != 1) { classification |= ConnectFlag_OldStyleButNonLiteral; } else if ((classification & ConnectFlag_QMessageBoxOpen) && numLiterals != 1) { classification |= ConnectFlag_OldStyleButNonLiteral; } else if ((classification & ConnectFlag_QSignalSpy) && numLiterals != 1) { classification |= ConnectFlag_OldStyleButNonLiteral; } } return classification; } bool OldStyleConnect::isQPointer(Expr *expr) const { vector memberCalls; clazy::getChilds(expr, memberCalls); for (auto callExpr : memberCalls) { if (!callExpr->getDirectCallee()) continue; auto method = dyn_cast(callExpr->getDirectCallee()); if (!method) continue; // Any better way to detect it's an operator ? if (clazy::startsWith(method->getNameAsString(), "operator ")) return true; } return false; } bool OldStyleConnect::isPrivateSlot(const string &name) const { return clazy::any_of(m_privateSlots, [name](const PrivateSlot &slot) { return slot.name == name; }); } void OldStyleConnect::VisitStmt(Stmt *s) { auto call = dyn_cast(s); auto ctorExpr = call ? nullptr : dyn_cast(s); if (!call && !ctorExpr) return; if (m_context->lastMethodDecl && m_context->isQtDeveloper() && m_context->lastMethodDecl->getParent() && clazy::name(m_context->lastMethodDecl->getParent()) == "QObject") // Don't warn of stuff inside qobject.h return; FunctionDecl *function = call ? call->getDirectCallee() : ctorExpr->getConstructor(); if (!function) return; auto method = dyn_cast(function); if (!method) return; const int classification = call ? classifyConnect(method, call) : classifyConnect(method, ctorExpr); if (!(classification & ConnectFlag_OldStyle)) return; if ((classification & ConnectFlag_OldStyleButNonLiteral)) return; if (classification & ConnectFlag_Bogus) { emitWarning(clazy::getLocStart(s), "Internal error"); return; } emitWarning(clazy::getLocStart(s), "Old Style Connect", call ? fixits(classification, call) : fixits(classification, ctorExpr)); } void OldStyleConnect::addPrivateSlot(const PrivateSlot &slot) { m_privateSlots.push_back(slot); } void OldStyleConnect::VisitMacroExpands(const Token ¯oNameTok, const SourceRange &range, const MacroInfo *) { IdentifierInfo *ii = macroNameTok.getIdentifierInfo(); if (!ii || ii->getName() != "Q_PRIVATE_SLOT") return; auto charRange = Lexer::getAsCharRange(range, sm(), lo()); const string text = Lexer::getSourceText(charRange, sm(), lo()); static regex rx(R"(Q_PRIVATE_SLOT\s*\((.*)\s*,\s*.*\s+(.*)\(.*)"); smatch match; if (!regex_match(text, match, rx) || match.size() != 3) return; addPrivateSlot({match[1], match[2]}); } // SIGNAL(foo()) -> foo string OldStyleConnect::signalOrSlotNameFromMacro(SourceLocation macroLoc) { if (!macroLoc.isMacroID()) return "error"; CharSourceRange expansionRange = clazy::getImmediateExpansionRange(macroLoc, sm()); SourceRange range = SourceRange(expansionRange.getBegin(), expansionRange.getEnd()); auto charRange = Lexer::getAsCharRange(range, sm(), lo()); const string text = Lexer::getSourceText(charRange, sm(), lo()); static regex rx(R"(\s*(SIGNAL|SLOT)\s*\(\s*(.+)\s*\(.*)"); smatch match; if (regex_match(text, match, rx)) { if (match.size() == 3) { return match[2].str(); } else { return "error2"; } } else { return string("regexp failed for ") + text; } } bool OldStyleConnect::isSignalOrSlot(SourceLocation loc, string ¯oName) const { macroName.clear(); if (!loc.isMacroID() || loc.isInvalid()) return false; macroName = Lexer::getImmediateMacroName(loc, sm(), lo()); return macroName == "SIGNAL" || macroName == "SLOT"; } template vector OldStyleConnect::fixits(int classification, T *callOrCtor) { - if (!isFixitEnabled()) + if (!fixitsEnabled()) return {}; if (!callOrCtor) { llvm::errs() << "Call is invalid\n"; return {}; } const SourceLocation locStart = clazy::getLocStart(callOrCtor); if (classification & ConnectFlag_2ArgsDisconnect) { // Not implemented yet string msg = "Fix it not implemented for disconnect with 2 args"; queueManualFixitWarning(locStart, msg); return {}; } if (classification & ConnectFlag_3ArgsDisconnect) { // Not implemented yet string msg = "Fix it not implemented for disconnect with 3 args"; queueManualFixitWarning(locStart, msg); return {}; } if (classification & ConnectFlag_QMessageBoxOpen) { string msg = "Fix it not implemented for QMessageBox::open()"; queueManualFixitWarning(locStart, msg); return {}; } vector fixits; int macroNum = 0; string implicitCallee; string macroName; CXXMethodDecl *senderMethod = nullptr; for (auto arg : callOrCtor->arguments()) { SourceLocation s = clazy::getLocStart(arg); static const CXXRecordDecl *lastRecordDecl = nullptr; if (isSignalOrSlot(s, macroName)) { macroNum++; if (!lastRecordDecl && (classification & ConnectFlag_4ArgsConnect)) { // This means it's a connect with implicit receiver lastRecordDecl = Utils::recordForMemberCall(dyn_cast(callOrCtor), implicitCallee); if (macroNum == 1) llvm::errs() << "This first macro shouldn't enter this path"; if (!lastRecordDecl) { string msg = "Failed to get class name for implicit receiver"; queueManualFixitWarning(s, msg); return {}; } } if (!lastRecordDecl) { string msg = "Failed to get class name for explicit receiver"; queueManualFixitWarning(s, msg); return {}; } const string methodName = signalOrSlotNameFromMacro(s); auto methods = Utils::methodsFromString(lastRecordDecl, methodName); if (methods.empty()) { string msg; if (isPrivateSlot(methodName)) { msg = "Converting Q_PRIVATE_SLOTS not implemented yet\n"; } else { if (m_context->isQtDeveloper() && classIsOk(clazy::name(lastRecordDecl))) { // This is OK return {}; } else { msg = "No such method " + methodName + " in class " + lastRecordDecl->getNameAsString(); } } queueManualFixitWarning(s, msg); return {}; } else if (methods.size() != 1) { string msg = string("Too many overloads (") + to_string(methods.size()) + string(") for method ") + methodName + " for record " + lastRecordDecl->getNameAsString(); queueManualFixitWarning(s, msg); return {}; } else { AccessSpecifierManager *a = m_context->accessSpecifierManager; if (!a) return {}; const bool isSignal = a->qtAccessSpecifierType(methods[0]) == QtAccessSpecifier_Signal; if (isSignal && macroName == "SLOT") { // The method is actually a signal and the user used SLOT() // bail out with the fixing. string msg = string("Can't fix. SLOT macro used but method " + methodName + " is a signal"); queueManualFixitWarning(s, msg); return {}; } } auto methodDecl = methods[0]; if (methodDecl->isStatic()) return {}; if (macroNum == 1) { // Save the number of parameters of the signal. The slot should not have more arguments. senderMethod = methodDecl; } else if (macroNum == 2) { const unsigned int numReceiverParams = methodDecl->getNumParams(); if (numReceiverParams > senderMethod->getNumParams()) { string msg = string("Receiver has more parameters (") + to_string(methodDecl->getNumParams()) + ") than signal (" + to_string(senderMethod->getNumParams()) + ')'; queueManualFixitWarning(s, msg); return {}; } for (unsigned int i = 0; i < numReceiverParams; ++i) { ParmVarDecl *receiverParm = methodDecl->getParamDecl(i); ParmVarDecl *senderParm = senderMethod->getParamDecl(i); if (!clazy::isConvertibleTo(senderParm->getType().getTypePtr(), receiverParm->getType().getTypePtrOrNull())) { string msg = string("Sender's parameters are incompatible with the receiver's"); queueManualFixitWarning(s, msg); return {}; } } } if ((classification & ConnectFlag_QTimerSingleShot) && methodDecl->getNumParams() > 0) { string msg = "(QTimer) Fixit not implemented for slot with arguments, use a lambda"; queueManualFixitWarning(s, msg); return {}; } if ((classification & ConnectFlag_QMenuAddAction) && methodDecl->getNumParams() > 0) { string msg = "(QMenu) Fixit not implemented for slot with arguments, use a lambda"; queueManualFixitWarning(s, msg); return {}; } DeclContext *context = m_context->lastDecl->getDeclContext(); bool isSpecialProtectedCase = false; if (!clazy::canTakeAddressOf(methodDecl, context, /*by-ref*/ isSpecialProtectedCase)) { string msg = "Can't fix " + clazy::accessString(methodDecl->getAccess()) + ' ' + macroName + ' ' + methodDecl->getQualifiedNameAsString(); queueManualFixitWarning(s, msg); return {}; } string qualifiedName; auto contextRecord = clazy::firstContextOfType(m_context->lastDecl->getDeclContext()); const bool isInInclude = sm().getMainFileID() != sm().getFileID(locStart); if (isSpecialProtectedCase && contextRecord) { // We're inside a derived class trying to take address of a protected base member, must use &Derived::method instead of &Base::method. qualifiedName = contextRecord->getNameAsString() + "::" + methodDecl->getNameAsString(); } else { qualifiedName = clazy::getMostNeededQualifiedName(sm(), methodDecl, context, locStart, !isInInclude); // (In includes ignore using directives) } CharSourceRange expansionRange = clazy::getImmediateExpansionRange(s, sm()); SourceRange range = SourceRange(expansionRange.getBegin(), expansionRange.getEnd()); const string functionPointer = '&' + qualifiedName; string replacement = functionPointer; if ((classification & ConnectFlag_4ArgsConnect) && macroNum == 2) replacement = implicitCallee + ", " + replacement; fixits.push_back(FixItHint::CreateReplacement(range, replacement)); lastRecordDecl = nullptr; } else { Expr *expr = arg; const auto record = clazy::getBestDynamicClassType(expr); if (record) { lastRecordDecl = record; if (isQPointer(expr)) { auto endLoc = clazy::locForNextToken(&m_astContext, clazy::getLocStart(arg), tok::comma); if (endLoc.isValid()) { fixits.push_back(FixItHint::CreateInsertion(endLoc, ".data()")); } else { queueManualFixitWarning(s, "Can't fix this QPointer case"); return {}; } } } } } return fixits; } diff --git a/src/checks/level2/qstring-allocations.cpp b/src/checks/level2/qstring-allocations.cpp index 6ea4297..999ee1e 100644 --- a/src/checks/level2/qstring-allocations.cpp +++ b/src/checks/level2/qstring-allocations.cpp @@ -1,627 +1,627 @@ /* 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 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 "qstring-allocations.h" #include "ClazyContext.h" #include "Utils.h" #include "clazy_stl.h" #include "StringUtils.h" #include "FixItUtils.h" #include "FunctionUtils.h" #include "QtUtils.h" #include "HierarchyUtils.h" #include "SourceCompatibilityHelpers.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace clang { class LangOptions; class ParentMap; class SourceManager; } // namespace clang /// WARNING /// /// This code is a bit unreadable and unmaintanable due to the fact that there are more corner-cases than normal cases. /// It will be rewritten in a new check, so don't bother. using namespace clang; using namespace std; enum Fixit { FixitNone = 0, QLatin1StringAllocations = 0x1, FromLatin1_FromUtf8Allocations = 0x2, CharPtrAllocations = 0x4, }; struct Latin1Expr { CXXConstructExpr *qlatin1ctorexpr; bool enableFixit; bool isValid() const { return qlatin1ctorexpr != nullptr; } }; QStringAllocations::QStringAllocations(const std::string &name, ClazyContext *context) : CheckBase(name, context, Option_CanIgnoreIncludes) { } void QStringAllocations::VisitStmt(clang::Stmt *stm) { if (m_context->isQtDeveloper() && clazy::isBootstrapping(m_context->ci.getPreprocessorOpts())) { // During bootstrap many QString::fromLatin1() are used instead of tr(), which causes // much noise return; } VisitCtor(stm); VisitOperatorCall(stm); VisitFromLatin1OrUtf8(stm); VisitAssignOperatorQLatin1String(stm); } static bool betterTakeQLatin1String(CXXMethodDecl *method, StringLiteral *lt) { static const vector methods = {"append", "compare", "endsWith", "startsWith", "insert", "lastIndexOf", "prepend", "replace", "contains", "indexOf" }; if (!clazy::isOfClass(method, "QString")) return false; return (!lt || Utils::isAscii(lt)) && clazy::contains(methods, clazy::name(method)); } // Returns the first occurrence of a QLatin1String(char*) CTOR call Latin1Expr QStringAllocations::qlatin1CtorExpr(Stmt *stm, ConditionalOperator * &ternary) { if (!stm) return {}; auto constructExpr = dyn_cast(stm); if (constructExpr) { CXXConstructorDecl *ctor = constructExpr->getConstructor(); const int numArgs = ctor->getNumParams(); if (clazy::isOfClass(ctor, "QLatin1String")) { if (Utils::containsStringLiteral(constructExpr, /*allowEmpty=*/ false, 2)) return {constructExpr, /*enableFixits=*/ numArgs == 1}; if (Utils::userDefinedLiteral(constructExpr, "QLatin1String", lo())) return {constructExpr, /*enableFixits=*/ false}; } } if (!ternary) ternary = dyn_cast(stm); for (auto child : stm->children()) { auto expr = qlatin1CtorExpr(child, ternary); if (expr.isValid()) return expr; } return {}; } // Returns true if there's a literal in the hierarchy, but aborts if it's parented on CallExpr // so, returns true for: QLatin1String("foo") but false for QLatin1String(indirection("foo")); // static bool containsStringLiteralNoCallExpr(Stmt *stmt) { if (!stmt) return false; auto sl = dyn_cast(stmt); if (sl) return true; for (auto child : stmt->children()) { if (!child) continue; auto callExpr = dyn_cast(child); if (!callExpr && containsStringLiteralNoCallExpr(child)) return true; } return false; } // For QString::fromLatin1("foo") returns "foo" static StringLiteral* stringLiteralForCall(Stmt *call) { if (!call) return nullptr; vector literals; clazy::getChilds(call, literals, 2); return literals.empty() ? nullptr : literals[0]; } void QStringAllocations::VisitCtor(Stmt *stm) { auto ctorExpr = dyn_cast(stm); if (!Utils::containsStringLiteral(ctorExpr, /**allowEmpty=*/ true)) return; CXXConstructorDecl *ctorDecl = ctorExpr->getConstructor(); if (!clazy::isOfClass(ctorDecl, "QString")) return; if (Utils::insideCTORCall(m_context->parentMap, stm, { "QRegExp", "QIcon" })) { // https://blogs.kde.org/2015/11/05/qregexp-qstringliteral-crash-exit return; } if (!isOptionSet("no-msvc-compat")) { InitListExpr *initializerList = clazy::getFirstParentOfType(m_context->parentMap, ctorExpr); if (initializerList != nullptr) return; // Nothing to do here, MSVC doesn't like it StringLiteral *lt = stringLiteralForCall(stm); if (lt && lt->getNumConcatenated() > 1) { return; // Nothing to do here, MSVC doesn't like it } } bool isQLatin1String = false; string paramType; if (clazy::hasCharPtrArgument(ctorDecl, 1)) { paramType = "const char*"; } else if (ctorDecl->param_size() == 1 && clazy::hasArgumentOfType(ctorDecl, "QLatin1String", lo())) { paramType = "QLatin1String"; isQLatin1String = true; } else { return; } string msg = string("QString(") + paramType + string(") being called"); if (isQLatin1String) { ConditionalOperator *ternary = nullptr; Latin1Expr qlatin1expr = qlatin1CtorExpr(stm, ternary); if (!qlatin1expr.isValid()) { return; } auto qlatin1Ctor = qlatin1expr.qlatin1ctorexpr; if (clazy::getLocStart(qlatin1Ctor).isMacroID()) { auto macroName = Lexer::getImmediateMacroName(clazy::getLocStart(qlatin1Ctor), sm(), lo()); if (macroName == "Q_GLOBAL_STATIC_WITH_ARGS") // bug #391807 return; } vector fixits; - if (qlatin1expr.enableFixit && isFixitEnabled(QLatin1StringAllocations)) { + if (qlatin1expr.enableFixit && fixitsEnabled()) { if (!clazy::getLocStart(qlatin1Ctor).isMacroID()) { if (!ternary) { - fixits = fixItReplaceWordWithWord(qlatin1Ctor, "QStringLiteral", "QLatin1String", QLatin1StringAllocations); + fixits = fixItReplaceWordWithWord(qlatin1Ctor, "QStringLiteral", "QLatin1String"); bool shouldRemoveQString = clazy::getLocStart(qlatin1Ctor).getRawEncoding() != clazy::getLocStart(stm).getRawEncoding() && dyn_cast_or_null(clazy::parent(m_context->parentMap, ctorExpr)); if (shouldRemoveQString) { // This is the case of QString(QLatin1String("foo")), which we just fixed to be QString(QStringLiteral("foo)), so now remove QString auto removalFixits = clazy::fixItRemoveToken(&m_astContext, ctorExpr, true); if (removalFixits.empty()) { - queueManualFixitWarning(clazy::getLocStart(ctorExpr), "Internal error: invalid start or end location", QLatin1StringAllocations); + queueManualFixitWarning(clazy::getLocStart(ctorExpr), "Internal error: invalid start or end location"); } else { clazy::append(removalFixits, fixits); } } } else { fixits = fixItReplaceWordWithWordInTernary(ternary); } } else { - queueManualFixitWarning(clazy::getLocStart(qlatin1Ctor), "Can't use QStringLiteral in macro", QLatin1StringAllocations); + queueManualFixitWarning(clazy::getLocStart(qlatin1Ctor), "Can't use QStringLiteral in macro"); } } maybeEmitWarning(clazy::getLocStart(stm), msg, fixits); } else { vector fixits; if (clazy::hasChildren(ctorExpr)) { auto pointerDecay = dyn_cast(*(ctorExpr->child_begin())); if (clazy::hasChildren(pointerDecay)) { auto lt = dyn_cast(*pointerDecay->child_begin()); - if (lt && isFixitEnabled(CharPtrAllocations)) { + if (lt && fixitsEnabled()) { Stmt *grandParent = clazy::parent(m_context->parentMap, lt, 2); Stmt *grandGrandParent = clazy::parent(m_context->parentMap, lt, 3); Stmt *grandGrandGrandParent = clazy::parent(m_context->parentMap, lt, 4); if (grandParent == ctorExpr && grandGrandParent && isa(grandGrandParent) && grandGrandGrandParent && isa(grandGrandGrandParent)) { // This is the case of QString("foo"), replace QString const bool literalIsEmpty = lt->getLength() == 0; if (literalIsEmpty && clazy::getFirstParentOfType(m_context->parentMap, ctorExpr) == nullptr) - fixits = fixItReplaceWordWithWord(ctorExpr, "QLatin1String", "QString", CharPtrAllocations); + fixits = fixItReplaceWordWithWord(ctorExpr, "QLatin1String", "QString"); else if (!clazy::getLocStart(ctorExpr).isMacroID()) - fixits = fixItReplaceWordWithWord(ctorExpr, "QStringLiteral", "QString", CharPtrAllocations); + fixits = fixItReplaceWordWithWord(ctorExpr, "QStringLiteral", "QString"); else - queueManualFixitWarning(clazy::getLocStart(ctorExpr), "Can't use QStringLiteral in macro.", CharPtrAllocations); + queueManualFixitWarning(clazy::getLocStart(ctorExpr), "Can't use QStringLiteral in macro."); } else { auto parentMemberCallExpr = clazy::getFirstParentOfType(m_context->parentMap, lt, /*maxDepth=*/ 6); // 6 seems like a nice max from the ASTs I've seen string replacement = "QStringLiteral"; if (parentMemberCallExpr) { FunctionDecl *fDecl = parentMemberCallExpr->getDirectCallee(); if (fDecl) { auto method = dyn_cast(fDecl); if (method && betterTakeQLatin1String(method, lt)) { replacement = "QLatin1String"; } } } fixits = fixItRawLiteral(lt, replacement); } } } } maybeEmitWarning(clazy::getLocStart(stm), msg, fixits); } } -vector QStringAllocations::fixItReplaceWordWithWord(clang::Stmt *begin, const string &replacement, const string &replacee, int fixitType) +vector QStringAllocations::fixItReplaceWordWithWord(clang::Stmt *begin, const string &replacement, const string &replacee) { StringLiteral *lt = stringLiteralForCall(begin); if (replacee == "QLatin1String") { if (lt && !Utils::isAscii(lt)) { maybeEmitWarning(clazy::getLocStart(lt), "Don't use QLatin1String with non-latin1 literals"); return {}; } } if (Utils::literalContainsEscapedBytes(lt, sm(), lo())) return {}; vector fixits; FixItHint fixit = clazy::fixItReplaceWordWithWord(&m_astContext, begin, replacement, replacee); if (fixit.isNull()) { - queueManualFixitWarning(clazy::getLocStart(begin), "", fixitType); + queueManualFixitWarning(clazy::getLocStart(begin), ""); } else { fixits.push_back(fixit); } return fixits; } vector QStringAllocations::fixItReplaceWordWithWordInTernary(clang::ConditionalOperator *ternary) { vector constructExprs; auto addConstructExpr = [&constructExprs] (Expr* expr) { if (auto functionalCast = dyn_cast(expr)) { expr = functionalCast->getSubExpr(); } if (auto constructExpr = dyn_cast(expr)) constructExprs.push_back(constructExpr); }; addConstructExpr(ternary->getTrueExpr()); addConstructExpr(ternary->getFalseExpr()); if (constructExprs.size() != 2) { llvm::errs() << "Weird ternary operator with " << constructExprs.size() << " constructExprs at " << clazy::getLocStart(ternary).printToString(sm()) << "\n"; ternary->dump(); assert(false); return {}; } vector fixits; fixits.reserve(2); for (CXXConstructExpr *constructExpr : constructExprs) { SourceLocation rangeStart = clazy::getLocStart(constructExpr); SourceLocation rangeEnd = Lexer::getLocForEndOfToken(rangeStart, -1, sm(), lo()); fixits.push_back(FixItHint::CreateReplacement(SourceRange(rangeStart, rangeEnd), "QStringLiteral")); } return fixits; } // true for: QString::fromLatin1().arg() // false for: QString::fromLatin1("") // true for: QString s = QString::fromLatin1("foo") // false for: s += QString::fromLatin1("foo"), etc. static bool isQStringLiteralCandidate(Stmt *s, ParentMap *map, const LangOptions &lo, const SourceManager &sm, int currentCall = 0) { if (!s) return false; MemberExpr *memberExpr = dyn_cast(s); if (memberExpr) return true; auto constructExpr = dyn_cast(s); if (clazy::isOfClass(constructExpr, "QString")) return true; if (Utils::isAssignOperator(dyn_cast(s), "QString", "QLatin1String", lo)) return true; if (Utils::isAssignOperator(dyn_cast(s), "QString", "QString", lo)) return true; CallExpr *callExpr = dyn_cast(s); StringLiteral *literal = stringLiteralForCall(callExpr); auto operatorCall = dyn_cast(s); if (operatorCall && clazy::returnTypeName(operatorCall, lo) != "QTestData") { // QTest::newRow will static_assert when using QLatin1String // Q_STATIC_ASSERT_X(QMetaTypeId2::Defined, "Type is not registered, please use the Q_DECLARE_METATYPE macro to make it known to Qt's meta-object system"); string className = clazy::classNameFor(operatorCall); if (className == "QString") { return false; } else if (className.empty() && clazy::hasArgumentOfType(operatorCall->getDirectCallee(), "QString", lo)) { return false; } } if (currentCall > 0 && callExpr) { auto fDecl = callExpr->getDirectCallee(); if (fDecl && betterTakeQLatin1String(dyn_cast(fDecl), literal)) return false; return true; } if (currentCall == 0 || dyn_cast(s) || dyn_cast(s) || dyn_cast(s)) // skip this cruft return isQStringLiteralCandidate(clazy::parent(map, s), map, lo, sm, currentCall + 1); return false; } std::vector QStringAllocations::fixItReplaceFromLatin1OrFromUtf8(CallExpr *callExpr, FromFunction fromFunction) { vector fixits; std::string replacement = isQStringLiteralCandidate(callExpr, m_context->parentMap, lo(), sm()) ? "QStringLiteral" : "QLatin1String"; if (replacement == "QStringLiteral" && clazy::getLocStart(callExpr).isMacroID()) { - queueManualFixitWarning(clazy::getLocStart(callExpr), "Can't use QStringLiteral in macro!", FromLatin1_FromUtf8Allocations); + queueManualFixitWarning(clazy::getLocStart(callExpr), "Can't use QStringLiteral in macro!"); return {}; } StringLiteral *literal = stringLiteralForCall(callExpr); if (literal) { if (Utils::literalContainsEscapedBytes(literal, sm(), lo())) return {}; if (!Utils::isAscii(literal)) { // QString::fromLatin1() to QLatin1String() is fine // QString::fromUtf8() to QStringLiteral() is fine // all other combinations are not if (replacement == "QStringLiteral" && fromFunction == FromLatin1) { return {}; } else if (replacement == "QLatin1String" && fromFunction == FromUtf8) { replacement = "QStringLiteral"; } } auto classNameLoc = Lexer::getLocForEndOfToken(clazy::getLocStart(callExpr), 0, sm(), lo()); auto scopeOperatorLoc = Lexer::getLocForEndOfToken(classNameLoc, 0, sm(), lo()); auto methodNameLoc = Lexer::getLocForEndOfToken(scopeOperatorLoc, -1, sm(), lo()); SourceRange range(clazy::getLocStart(callExpr), methodNameLoc); fixits.push_back(FixItHint::CreateReplacement(range, replacement)); } else { - queueManualFixitWarning(clazy::getLocStart(callExpr), "Internal error: literal is null", FromLatin1_FromUtf8Allocations); + queueManualFixitWarning(clazy::getLocStart(callExpr), "Internal error: literal is null"); } return fixits; } std::vector QStringAllocations::fixItRawLiteral(clang::StringLiteral *lt, const string &replacement) { vector fixits; SourceRange range = clazy::rangeForLiteral(&m_astContext, lt); if (range.isInvalid()) { if (lt) { - queueManualFixitWarning(clazy::getLocStart(lt), "Internal error: Can't calculate source location", CharPtrAllocations); + queueManualFixitWarning(clazy::getLocStart(lt), "Internal error: Can't calculate source location"); } return {}; } SourceLocation start = clazy::getLocStart(lt); if (start.isMacroID()) { - queueManualFixitWarning(start, "Can't use QStringLiteral in macro", CharPtrAllocations); + queueManualFixitWarning(start, "Can't use QStringLiteral in macro"); } else { if (Utils::literalContainsEscapedBytes(lt, sm(), lo())) return {}; string revisedReplacement = lt->getLength() == 0 ? "QLatin1String" : replacement; // QLatin1String("") is better than QStringLiteral("") if (revisedReplacement == "QStringLiteral" && clazy::getLocStart(lt).isMacroID()) { - queueManualFixitWarning(clazy::getLocStart(lt), "Can't use QStringLiteral in macro...", CharPtrAllocations); + queueManualFixitWarning(clazy::getLocStart(lt), "Can't use QStringLiteral in macro..."); return {}; } clazy::insertParentMethodCall(revisedReplacement, range, /**by-ref*/ fixits); } return fixits; } void QStringAllocations::VisitOperatorCall(Stmt *stm) { auto operatorCall = dyn_cast(stm); if (!operatorCall) return; if (clazy::returnTypeName(operatorCall, lo()) == "QTestData") { // QTest::newRow will static_assert when using QLatin1String // Q_STATIC_ASSERT_X(QMetaTypeId2::Defined, "Type is not registered, please use the Q_DECLARE_METATYPE macro to make it known to Qt's meta-object system"); return; } std::vector stringLiterals; clazy::getChilds(operatorCall, stringLiterals); // We're only after string literals, str.contains(some_method_returning_const_char_is_fine()) if (stringLiterals.empty()) return; FunctionDecl *funcDecl = operatorCall->getDirectCallee(); if (!funcDecl) return; auto methodDecl = dyn_cast(funcDecl); if (!clazy::isOfClass(methodDecl, "QString")) return; if (!clazy::hasCharPtrArgument(methodDecl)) return; vector fixits; vector literals; clazy::getChilds(stm, literals, 2); if (!isOptionSet("no-msvc-compat") && !literals.empty()) { if (literals[0]->getNumConcatenated() > 1) { return; // Nothing to do here, MSVC doesn't like it } } - if (isFixitEnabled(CharPtrAllocations)) { + if (fixitsEnabled()) { if (literals.empty()) { - queueManualFixitWarning(clazy::getLocStart(stm), "Couldn't find literal", CharPtrAllocations); + queueManualFixitWarning(clazy::getLocStart(stm), "Couldn't find literal"); } else { const string replacement = Utils::isAscii(literals[0]) ? "QLatin1String" : "QStringLiteral"; fixits = fixItRawLiteral(literals[0], replacement); } } string msg = string("QString(const char*) being called"); maybeEmitWarning(clazy::getLocStart(stm), msg, fixits); } void QStringAllocations::VisitFromLatin1OrUtf8(Stmt *stmt) { auto callExpr = dyn_cast(stmt); if (!callExpr) return; FunctionDecl *functionDecl = callExpr->getDirectCallee(); if (!clazy::functionIsOneOf(functionDecl, {"fromLatin1", "fromUtf8"})) return; auto methodDecl = dyn_cast(functionDecl); if (!clazy::isOfClass(methodDecl, "QString")) return; if (!Utils::callHasDefaultArguments(callExpr) || !clazy::hasCharPtrArgument(functionDecl, 2)) // QString::fromLatin1("foo", 1) is ok return; if (!containsStringLiteralNoCallExpr(callExpr)) return; if (!isOptionSet("no-msvc-compat")) { StringLiteral *lt = stringLiteralForCall(callExpr); if (lt && lt->getNumConcatenated() > 1) { return; // Nothing to do here, MSVC doesn't like it } } vector ternaries; clazy::getChilds(callExpr, ternaries, 2); if (!ternaries.empty()) { auto ternary = ternaries[0]; if (Utils::ternaryOperatorIsOfStringLiteral(ternary)) { maybeEmitWarning(clazy::getLocStart(stmt), string("QString::fromLatin1() being passed a literal")); } return; } std::vector fixits; - if (isFixitEnabled(FromLatin1_FromUtf8Allocations)) { + if (fixitsEnabled()) { const FromFunction fromFunction = clazy::name(functionDecl) == "fromLatin1" ? FromLatin1 : FromUtf8; fixits = fixItReplaceFromLatin1OrFromUtf8(callExpr, fromFunction); } if (clazy::name(functionDecl) == "fromLatin1") { maybeEmitWarning(clazy::getLocStart(stmt), string("QString::fromLatin1() being passed a literal"), fixits); } else { maybeEmitWarning(clazy::getLocStart(stmt), string("QString::fromUtf8() being passed a literal"), fixits); } } void QStringAllocations::VisitAssignOperatorQLatin1String(Stmt *stmt) { auto callExpr = dyn_cast(stmt); if (!Utils::isAssignOperator(callExpr, "QString", "QLatin1String", lo())) return; if (!containsStringLiteralNoCallExpr(stmt)) return; ConditionalOperator *ternary = nullptr; Stmt *begin = qlatin1CtorExpr(stmt, ternary).qlatin1ctorexpr; if (!begin) return; vector fixits; - if (isFixitEnabled(QLatin1StringAllocations)) { - fixits = ternary == nullptr ? fixItReplaceWordWithWord(begin, "QStringLiteral", "QLatin1String", QLatin1StringAllocations) + if (fixitsEnabled()) { + fixits = ternary == nullptr ? fixItReplaceWordWithWord(begin, "QStringLiteral", "QLatin1String") : fixItReplaceWordWithWordInTernary(ternary); } maybeEmitWarning(clazy::getLocStart(stmt), string("QString::operator=(QLatin1String(\"literal\")"), fixits); } void QStringAllocations::maybeEmitWarning(SourceLocation loc, string error, std::vector fixits) { if (clazy::isUIFile(loc, sm())) { // Don't bother warning for generated UI files. // We do the check here instead of at the beginning so users that don't use UI files don't have to pay the performance price. return; } if (m_context->isQtDeveloper() && Utils::filenameForLoc(loc, sm()) == "qstring.cpp") { // There's an error replacing an internal fromLatin1() because the replacement code doesn't expect to be working on QString itself // not worth to fix, it's only 1 case in qstring.cpp, and related to Qt 1.x compat fixits = {}; } emitWarning(loc, error, fixits); } diff --git a/src/checks/level2/qstring-allocations.h b/src/checks/level2/qstring-allocations.h index a71f000..560be7b 100644 --- a/src/checks/level2/qstring-allocations.h +++ b/src/checks/level2/qstring-allocations.h @@ -1,86 +1,84 @@ /* 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 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_STRING_ALLOCATIONS_H #define CLAZY_STRING_ALLOCATIONS_H #include "checkbase.h" #include #include #include class ClazyContext; namespace clang { class FixItHint; class ConditionalOperator; class CallExpr; class StringLiteral; class ConditionalOperator; class Stmt; } struct Latin1Expr; enum FromFunction { FromLatin1, FromUtf8 }; /** * Finds places where there are unneeded memory allocations due to temporary QStrings. * * For example: * QString s = QLatin1String("foo"); // should be QStringLiteral * QString::fromLatin1("foo") and QString::fromUtf8("foo") // should be QStringLiteral, or QLatin1String if being passed to an overload taking QLatin1String * * See README-qstring-allocations for more information. */ class QStringAllocations : public CheckBase { public: QStringAllocations(const std::string &name, ClazyContext *context); void VisitStmt(clang::Stmt *stm) override; private: void VisitCtor(clang::Stmt *); void VisitOperatorCall(clang::Stmt *); void VisitFromLatin1OrUtf8(clang::Stmt *); void VisitAssignOperatorQLatin1String(clang::Stmt *); - void maybeEmitWarning(clang::SourceLocation loc, std::string error, - std::vector fixits = {}); - - std::vector fixItReplaceWordWithWord(clang::Stmt *begin, const std::string &replacement, const std::string &replacee, int fixitType); + void maybeEmitWarning(clang::SourceLocation loc, std::string error, std::vector fixits = {}); + std::vector fixItReplaceWordWithWord(clang::Stmt *begin, const std::string &replacement, const std::string &replacee); std::vector fixItReplaceWordWithWordInTernary(clang::ConditionalOperator *); std::vector fixItReplaceFromLatin1OrFromUtf8(clang::CallExpr *callExpr, FromFunction); std::vector fixItRawLiteral(clang::StringLiteral *stmt, const std::string &replacement); Latin1Expr qlatin1CtorExpr(clang::Stmt *stm, clang::ConditionalOperator * &ternary); }; #endif diff --git a/src/checks/manuallevel/qt-keywords.cpp b/src/checks/manuallevel/qt-keywords.cpp index dfe6a14..e792e95 100644 --- a/src/checks/manuallevel/qt-keywords.cpp +++ b/src/checks/manuallevel/qt-keywords.cpp @@ -1,79 +1,79 @@ /* This file is part of the clazy static checker. Copyright (C) 2018 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 "qt-keywords.h" #include "FixItUtils.h" #include "ClazyContext.h" #include "PreProcessorVisitor.h" #include "clazy_stl.h" #include #include #include #include #include #include #include #include #include using namespace clang; using namespace std; QtKeywords::QtKeywords(const std::string &name, ClazyContext *context) : CheckBase(name, context) { enablePreProcessorCallbacks(); context->enablePreprocessorVisitor(); } void QtKeywords::VisitMacroExpands(const Token ¯oNameTok, const SourceRange &range, const clang::MacroInfo *minfo) { IdentifierInfo *ii = macroNameTok.getIdentifierInfo(); if (!ii || !minfo) return; if (auto ppvisitor = m_context->preprocessorVisitor) { // Save some CPU cycles. No point in running if QT_NO_KEYWORDS if (ppvisitor->isQT_NO_KEYWORDS()) return; } static const vector keywords = { "foreach", "signals", "slots", "emit" }; std::string name = ii->getName(); if (!clazy::contains(keywords, name)) return; // Make sure the macro is Qt's. It must be defined in Qt's headers, not 3rdparty std::string qtheader = sm().getFilename(sm().getSpellingLoc(minfo->getDefinitionLoc())); if (!clazy::endsWith(qtheader, "qglobal.h") && !clazy::endsWith(qtheader, "qobjectdefs.h")) return; std::vector fixits; - if (isFixitEnabled()) { + if (fixitsEnabled()) { std::string replacement = "Q_" + name; std::transform(replacement.begin(), replacement.end(), replacement.begin(), ::toupper); fixits.push_back(clazy::createReplacement(range, replacement)); } emitWarning(range.getBegin(), "Using a Qt keyword (" + string(ii->getName()) + ")", fixits); } diff --git a/tests/range-loop/bug370609.cpp.fixed.expected b/tests/range-loop/bug370609.cpp.fixed.expected new file mode 100644 index 0000000..505d1bd --- /dev/null +++ b/tests/range-loop/bug370609.cpp.fixed.expected @@ -0,0 +1,18 @@ +// bug 370609, Simply tests if clazy crashes + +#include + +template +struct Example +{ + Example() + { + for (auto sample : qAsConst(m_sampleCache)) { } + } + QVector m_sampleCache; +}; + +void CreateExample() +{ + new Example(); +} diff --git a/tests/range-loop/config.json b/tests/range-loop/config.json index a5f3648..0184a57 100644 --- a/tests/range-loop/config.json +++ b/tests/range-loop/config.json @@ -1,11 +1,12 @@ { "tests" : [ { "filename" : "main.cpp", "has_fixits" : true }, { - "filename" : "bug370609.cpp" + "filename" : "bug370609.cpp", + "has_fixits" : true } ] } diff --git a/tests/run_tests.py b/tests/run_tests.py index 09fe9d2..387269c 100755 --- a/tests/run_tests.py +++ b/tests/run_tests.py @@ -1,723 +1,761 @@ #!/usr/bin/env python2 import sys, os, subprocess, string, re, json, threading, multiprocessing, argparse import shutil from threading import Thread from sys import platform as _platform _verbose = False def isWindows(): return _platform == 'win32' class QtInstallation: def __init__(self): self.int_version = 000 self.qmake_header_path = "/usr/include/qt/" self.qmake_lib_path = "/usr/lib" def compiler_flags(self): return "-isystem " + self.qmake_header_path + ("" if isWindows() else " -fPIC") + " -L " + self.qmake_lib_path class Test: def __init__(self, check): self.filenames = [] self.minimum_qt_version = 500 self.maximum_qt_version = 59999 self.minimum_clang_version = 380 self.compare_everything = False self.link = False # If true we also call the linker self.check = check self.expects_failure = False self.qt_major_version = 5 # Tests use Qt 5 by default self.env = os.environ self.checks = [] self.flags = "" self.must_fail = False self.blacklist_platforms = [] self.qt4compat = False self.only_qt = False self.qt_developer = False self.header_filter = "" self.ignore_dirs = "" self.has_fixits = False self.should_run_fixits_test = False def filename(self): if len(self.filenames) == 1: return self.filenames[0] return "" def relativeFilename(self): + # example: "auto-unexpected-qstringbuilder/main.cpp" return self.check.name + "/" + self.filename() - def yamlFilename(self): - # In case clazy-standalone generates a yaml file with fixits, this is what it will be called - return self.relativeFilename() + ".yaml" + def yamlFilename(self, is_standalone): + # The name of the yaml file with fixits + # example: "auto-unexpected-qstringbuilder/main.cpp.clazy.yaml" + if is_standalone: + return self.relativeFilename() + ".clazy-standalone.yaml" + else: + return self.relativeFilename() + ".clazy.yaml" - def fixedFilename(self): - return self.relativeFilename() + ".fixed" + def fixedFilename(self, is_standalone): + if is_standalone: + return self.relativeFilename() + ".clazy-standalone.fixed" + else: + return self.relativeFilename() + ".clazy.fixed" def expectedFixedFilename(self): return self.relativeFilename() + ".fixed.expected" def isScript(self): return self.filename().endswith(".sh") def dir(self): return self.check.name def setQtMajorVersion(self, major_version): if major_version == 4: self.qt_major_version = 4 if self.minimum_qt_version >= 500: self.minimum_qt_version = 400 def envString(self): result = "" for key in self.env: result += key + '="' + self.env[key] + '" ' return result def setEnv(self, e): self.env = os.environ.copy() for key in e: key_str = key.encode('ascii', 'ignore') self.env[key_str] = e[key].encode('ascii', 'ignore') def printableName(self, is_standalone, is_fixits): name = self.check.name if len(self.check.tests) > 1: name += "/" + self.filename() - if is_fixits: - name += " (fixits)" + if is_fixits and is_standalone: + name += " (standalone, fixits)" elif is_standalone: name += " (standalone)" + elif is_fixits: + name += " (plugin, fixits)" + else: + name += " (plugin)" return name + def removeYamlFiles(self): + for f in [test.yamlFilename(False), test.yamlFilename(True)]: + if os.path.exists(f): + os.remove(f) + class Check: def __init__(self, name): self.name = name self.minimum_clang_version = 380 # clang 3.8.0 self.minimum_qt_version = 500 self.maximum_qt_version = 59999 self.enabled = True self.clazy_standalone_only = False self.tests = [] #------------------------------------------------------------------------------- # utility functions #1 def get_command_output(cmd, test_env = os.environ): try: if _verbose: print cmd output = subprocess.check_output(cmd, stderr=subprocess.STDOUT, shell=True, env=test_env) except subprocess.CalledProcessError, e: return e.output,False return output,True def load_json(check_name): check = Check(check_name) filename = check_name + "/config.json" if not os.path.exists(filename): # Ignore this directory return check f = open(filename, 'r') contents = f.read() f.close() decoded = json.loads(contents) check_blacklist_platforms = [] if 'minimum_clang_version' in decoded: check.minimum_clang_version = decoded['minimum_clang_version'] if 'minimum_qt_version' in decoded: check.minimum_qt_version = decoded['minimum_qt_version'] if 'maximum_qt_version' in decoded: check.maximum_qt_version = decoded['maximum_qt_version'] if 'enabled' in decoded: check.enabled = decoded['enabled'] if 'clazy_standalone_only' in decoded: check.clazy_standalone_only = decoded['clazy_standalone_only'] if 'blacklist_platforms' in decoded: check_blacklist_platforms = decoded['blacklist_platforms'] if 'tests' in decoded: for t in decoded['tests']: test = Test(check) test.blacklist_platforms = check_blacklist_platforms if 'filename' in t: test.filenames.append(t['filename']) if 'filenames' in t: test.filenames += t['filenames'] if 'minimum_qt_version' in t: test.minimum_qt_version = t['minimum_qt_version'] else: test.minimum_qt_version = check.minimum_qt_version if 'maximum_qt_version' in t: test.maximum_qt_version = t['maximum_qt_version'] else: test.maximum_qt_version = check.maximum_qt_version if 'minimum_clang_version' in t: test.minimum_clang_version = t['minimum_clang_version'] else: test.minimum_clang_version = check.minimum_clang_version if 'blacklist_platforms' in t: test.blacklist_platforms = t['blacklist_platforms'] if 'compare_everything' in t: test.compare_everything = t['compare_everything'] if 'link' in t: test.link = t['link'] if 'qt_major_version' in t: test.setQtMajorVersion(t['qt_major_version']) if 'env' in t: test.setEnv(t['env']) if 'checks' in t: test.checks = t['checks'] if 'flags' in t: test.flags = t['flags'] if 'must_fail' in t: test.must_fail = t['must_fail'] if 'has_fixits' in t: test.has_fixits = t['has_fixits'] if 'expects_failure' in t: test.expects_failure = t['expects_failure'] if 'qt4compat' in t: test.qt4compat = t['qt4compat'] if 'only_qt' in t: test.only_qt = t['only_qt'] if 'qt_developer' in t: test.qt_developer = t['qt_developer'] if 'header_filter' in t: test.header_filter = t['header_filter'] if 'ignore_dirs' in t: test.ignore_dirs = t['ignore_dirs'] if not test.checks: test.checks.append(test.check.name) check.tests.append(test) return check def find_qt_installation(major_version, qmakes): installation = QtInstallation() for qmake in qmakes: qmake_version_str,success = get_command_output(qmake + " -query QT_VERSION") if success and qmake_version_str.startswith(str(major_version) + "."): qmake_header_path = get_command_output(qmake + " -query QT_INSTALL_HEADERS")[0].strip() qmake_lib_path = get_command_output(qmake + " -query QT_INSTALL_LIBS")[0].strip() if qmake_header_path: installation.qmake_header_path = qmake_header_path if qmake_lib_path: installation.qmake_lib_path = qmake_lib_path ver = qmake_version_str.split('.') installation.int_version = int(ver[0]) * 10000 + int(ver[1]) * 100 + int(ver[2]) if _verbose: print "Found Qt " + str(installation.int_version) + " using qmake " + qmake break if installation.int_version == 0 and major_version >= 5: # Don't warn for missing Qt4 headers print "Error: Couldn't find a Qt" + str(major_version) + " installation" return installation def libraryName(): if _platform == 'win32': return 'ClazyPlugin.dll' elif _platform == 'darwin': return 'ClazyPlugin.dylib' else: return 'ClazyPlugin.so' def link_flags(): flags = "-lQt5Core -lQt5Gui -lQt5Widgets" if _platform.startswith('linux'): flags += " -lstdc++" return flags def clazy_cpp_args(): return "-Wno-unused-value -Qunused-arguments -std=c++14 " def more_clazy_args(): return " " + clazy_cpp_args() def clazy_standalone_binary(): if 'CLAZYSTANDALONE_CXX' in os.environ: # in case we want to use "clazy.AppImage --standalone" instead return os.environ['CLAZYSTANDALONE_CXX'] return 'clazy-standalone' def clazy_standalone_command(test, qt): result = " -- " + clazy_cpp_args() + qt.compiler_flags() + " " + test.flags result = " -checks=" + string.join(test.checks, ',') + " " + result if test.has_fixits: - result = " -enable-all-fixits -export-fixes=" + test.yamlFilename() + result + result = " -export-fixes=" + test.yamlFilename(is_standalone=True) + result if test.qt4compat: result = " -qt4-compat " + result if test.only_qt: result = " -only-qt " + result if test.qt_developer: result = " -qt-developer " + result if test.header_filter: result = " -header-filter " + test.header_filter + " " + result if test.ignore_dirs: result = " -ignore-dirs " + test.ignore_dirs + " " + result return result def clazy_command(qt, test, filename): if test.isScript(): return "./" + filename if 'CLAZY_CXX' in os.environ: # In case we want to use clazy.bat result = os.environ['CLAZY_CXX'] + more_clazy_args() + qt.compiler_flags() else: clang = os.getenv('CLANGXX', 'clang') result = clang + " -Xclang -load -Xclang " + libraryName() + " -Xclang -add-plugin -Xclang clazy " + more_clazy_args() + qt.compiler_flags() if test.qt4compat: result = result + " -Xclang -plugin-arg-clazy -Xclang qt4-compat " if test.only_qt: result = result + " -Xclang -plugin-arg-clazy -Xclang only-qt " if test.qt_developer: result = result + " -Xclang -plugin-arg-clazy -Xclang qt-developer " if test.link and _platform.startswith('linux'): # Linking on one platform is enough. Won't waste time on macOS and Windows. result = result + " " + link_flags() else: result = result + " -c " result = result + test.flags + " -Xclang -plugin-arg-clazy -Xclang " + string.join(test.checks, ',') + " " - result += _enable_fixits_argument + " " + if test.has_fixits: + result += _export_fixes_argument + " " result += filename return result def dump_ast_command(test): return "clang -std=c++14 -fsyntax-only -Xclang -ast-dump -fno-color-diagnostics -c " + qt_installation(test.qt_major_version).compiler_flags() + " " + test.flags + " " + test.filename() def compiler_name(): if 'CLAZY_CXX' in os.environ: return os.environ['CLAZY_CXX'] # so we can set clazy.bat instead return os.getenv('CLANGXX', 'clang') #------------------------------------------------------------------------------- # Setup argparse parser = argparse.ArgumentParser() parser.add_argument("-v", "--verbose", action='store_true') parser.add_argument("--no-standalone", action='store_true', help="Don\'t run clazy-standalone") parser.add_argument("--only-standalone", action='store_true', help='Only run clazy-standalone') parser.add_argument("--dump-ast", action='store_true', help='Dump a unit-test AST to file') parser.add_argument("--exclude", help='Comma separated list of checks to ignore') parser.add_argument("check_names", nargs='*', help="The name of the check who's unit-tests will be run. Defaults to running all checks.") args = parser.parse_args() if args.only_standalone and args.no_standalone: print "Error: --only-standalone is incompatible with --no-standalone" sys.exit(1) #------------------------------------------------------------------------------- # Global variables -_enable_fixits_argument = "-Xclang -plugin-arg-clazy -Xclang enable-all-fixits" +_export_fixes_argument = "-Xclang -plugin-arg-clazy -Xclang export-fixes" _dump_ast = args.dump_ast _verbose = args.verbose _no_standalone = args.no_standalone _only_standalone = args.only_standalone _num_threads = multiprocessing.cpu_count() _lock = threading.Lock() _was_successful = True _qt5_installation = find_qt_installation(5, ["QT_SELECT=5 qmake", "qmake-qt5", "qmake"]) _qt4_installation = find_qt_installation(4, ["QT_SELECT=4 qmake", "qmake-qt4", "qmake"]) _excluded_checks = args.exclude.split(',') if args.exclude is not None else [] #------------------------------------------------------------------------------- # utility functions #2 version,success = get_command_output(compiler_name() + ' --version') match = re.search('clang version (.*?)[ -]', version) try: version = match.group(1) except: # Now try the Clazy.AppImage way match = re.search('clang version: (.*)', version) try: version = match.group(1) except: print "Could not determine clang version, is it in PATH?" sys.exit(-1) if _verbose: print 'Found clang version: ' + str(version) CLANG_VERSION = int(version.replace('.', '')) def qt_installation(major_version): if major_version == 5: return _qt5_installation elif major_version == 4: return _qt4_installation return None def run_command(cmd, output_file = "", test_env = os.environ): lines,success = get_command_output(cmd, test_env) lines = lines.replace("std::_Container_base0", "std::_Vector_base") # Hack for Windows, we have std::_Vector_base in the expected data lines = lines.replace("std::__1::__vector_base_common", "std::_Vector_base") # Hack for macOS lines = lines.replace("std::_Vector_alloc", "std::_Vector_base") if not success and not output_file: print lines return False if _verbose: print "Running: " + cmd print "output_file=" + output_file lines = lines.replace('\r\n', '\n') if output_file: f = open(output_file, 'w') f.writelines(lines) f.close() else: print lines return success def files_are_equal(file1, file2): try: f = open(file1, 'r') lines1 = f.readlines() f.close() f = open(file2, 'r') lines2 = f.readlines() f.close() return lines1 == lines2 except: return False def compare_files(expects_failure, expected_file, result_file, message): success = files_are_equal(expected_file, result_file) if expects_failure: if success: print "[XOK] " + message return False else: print "[XFAIL] " + message print_differences(expected_file, result_file) return True else: if success: print "[OK] " + message return True else: print "[FAIL] " + message print_differences(expected_file, result_file) return False def get_check_names(): return filter(lambda entry: os.path.isdir(entry), os.listdir(".")) # The yaml file references the test file in our git repo, but we don't want # to rewrite that one, as we would need to discard git changes afterwards, # so patch the yaml file and add a ".fixed" suffix to those files -def patch_fixit_yaml_file(test): +def patch_fixit_yaml_file(test, is_standalone): + + yamlfilename = test.yamlFilename(is_standalone) + fixedfilename = test.fixedFilename(is_standalone) - f = open(test.yamlFilename(), 'r') + f = open(yamlfilename, 'r') lines = f.readlines() f.close() - f = open(test.yamlFilename(), 'w') + f = open(yamlfilename, 'w') possible_headerfile = test.relativeFilename().replace(".cpp", ".h") for line in lines: stripped = line.strip() if stripped.startswith('MainSourceFile') or stripped.startswith("FilePath") or stripped.startswith("- FilePath"): - line = line.replace(test.relativeFilename(), test.fixedFilename()) + line = line.replace(test.relativeFilename(), fixedfilename) # Some tests also apply fix their to their headers: - line = line.replace(possible_headerfile, test.fixedFilename().replace(".cpp", ".h")) + line = line.replace(possible_headerfile, fixedfilename.replace(".cpp", ".h")) f.write(line) f.close() - shutil.copyfile(test.relativeFilename(), test.fixedFilename()) + shutil.copyfile(test.relativeFilename(), fixedfilename) if os.path.exists(possible_headerfile): - shutil.copyfile(possible_headerfile, test.fixedFilename().replace(".cpp", ".h")) + shutil.copyfile(possible_headerfile, fixedfilename.replace(".cpp", ".h")) return True def run_clang_apply_replacements(): return run_command('clang-apply-replacements .') def cleanup_fixit_files(checks): for check in checks: filestodelete = filter(lambda entry: entry.endswith('.fixed') or entry.endswith('.yaml'), os.listdir(check.name)) for f in filestodelete: os.remove(check.name + '/' + f) def print_differences(file1, file2): # Returns true if the the files are equal return run_command("diff -Naur {} {}".format(file1, file2)) def normalizedCwd(): return os.getcwd().replace('\\', '/') def extract_word(word, in_file, out_file): in_f = open(in_file, 'r') out_f = open(out_file, 'w') for line in in_f: if word in line: line = line.replace('\\', '/') line = line.replace(normalizedCwd() + '/', "") # clazy-standalone prints the complete cpp file path for some reason. Normalize it so it compares OK with the expected output. out_f.write(line) in_f.close() out_f.close() def print_file(filename): f = open(filename, 'r') print f.read() f.close() def file_contains(filename, text): f = open(filename, 'r') contents = f.read() f.close() return text in contents def run_unit_test(test, is_standalone): if test.check.clazy_standalone_only and not is_standalone: return True qt = qt_installation(test.qt_major_version) if _verbose: print print "Qt version: " + str(qt.int_version) print "Qt headers: " + qt.qmake_header_path if qt.int_version < test.minimum_qt_version or qt.int_version > test.maximum_qt_version or CLANG_VERSION < test.minimum_clang_version: if (_verbose): print "Skipping " + test.check.name + " because required version is not available" return True if _platform in test.blacklist_platforms: if (_verbose): print "Skipping " + test.check.name + " because it is blacklisted for this platform" return True checkname = test.check.name filename = checkname + "/" + test.filename() output_file = filename + ".out" result_file = filename + ".result" expected_file = filename + ".expected" if is_standalone and test.isScript(): return True if is_standalone: cmd_to_run = clazy_standalone_binary() + " " + filename + " " + clazy_standalone_command(test, qt) else: cmd_to_run = clazy_command(qt, test, filename) if test.compare_everything: result_file = output_file must_fail = test.must_fail cmd_success = run_command(cmd_to_run, output_file, test.env) if file_contains(output_file, 'Invalid check: '): return True if (not cmd_success and not must_fail) or (cmd_success and must_fail): print "[FAIL] " + checkname + " (Failed to build test. Check " + output_file + " for details)" print "-------------------" print "Contents of %s:" % output_file print_file(output_file) print "-------------------" print return False if not test.compare_everything: word_to_grep = "warning:" if not must_fail else "error:" extract_word(word_to_grep, output_file, result_file) # Check that it printed the expected warnings if not compare_files(test.expects_failure, expected_file, result_file, test.printableName(is_standalone, False)): - if os.path.exists(test.yamlFilename()): - os.remove(test.yamlFilename()) - + test.removeYamlFiles(); return False - if is_standalone and test.has_fixits: + if test.has_fixits: # The normal tests succeeded, we can run the respective fixits then test.should_run_fixits_test = True return True def run_unit_tests(tests): result = True for test in tests: if not _only_standalone: result = result and run_unit_test(test, False) if not _no_standalone: result = result and run_unit_test(test, True) global _was_successful, _lock with _lock: _was_successful = _was_successful and result -# This is run sequentially, due to races. As clang-apply-replacements just applies all .yaml files it can find. -# We run a single clang-apply-replacements invocation, which changes all files in the tests/ directory. -def run_fixit_tests(requested_checks): - if _no_standalone: - # Only clazy-standalone supports fixits +def patch_yaml_files(requested_checks, is_standalone): + if (is_standalone and _no_standalone) or (not is_standalone and _only_standalone): + # Nothing to do return True + success = True for check in requested_checks: for test in check.tests: if test.should_run_fixits_test: - if not os.path.exists(test.yamlFilename()): - print "[FAIL] " + test.yamlFilename() + " is missing!!" + yamlfilename = test.yamlFilename(is_standalone) + if not os.path.exists(yamlfilename): + print "[FAIL] " + yamlfilename + " is missing!!" success = False continue - if not patch_fixit_yaml_file(test): - print "[FAIL] Could not patch " + test.yamlFilename() + if not patch_fixit_yaml_file(test, is_standalone): + print "[FAIL] Could not patch " + yamlfilename success = False continue + return success + +def compare_fixit_results(test, is_standalone): + if (is_standalone and _no_standalone) or (not is_standalone and _only_standalone): + # Nothing to do + return True + + # Check that the rewritten file is identical to the expected one + if not compare_files(False, test.expectedFixedFilename(), test.fixedFilename(is_standalone), test.printableName(is_standalone, True)): + return False + + # Some fixed cpp files have an header that was also fixed. Compare it here too. + possible_headerfile_expected = test.expectedFixedFilename().replace('.cpp', '.h') + if os.path.exists(possible_headerfile_expected): + possible_headerfile = test.fixedFilename(is_standalone).replace('.cpp', '.h') + if not compare_files(False, possible_headerfile_expected, possible_headerfile, test.printableName(is_standalone, True).replace('.cpp', '.h')): + return False + + return True + +# This is run sequentially, due to races. As clang-apply-replacements just applies all .yaml files it can find. +# We run a single clang-apply-replacements invocation, which changes all files in the tests/ directory. +def run_fixit_tests(requested_checks): + + success = patch_yaml_files(requested_checks, is_standalone=False) + success = patch_yaml_files(requested_checks, is_standalone=True) and success # Call clazy-apply-replacements[.exe] if not run_clang_apply_replacements(): return False # Now compare all the *.fixed files with the *.fixed.expected counterparts - success = True - for check in requested_checks: for test in check.tests: if test.should_run_fixits_test: # Check that the rewritten file is identical to the expected one - if not compare_files(False, test.expectedFixedFilename(), test.fixedFilename(), test.printableName(True, True)): + if not compare_fixit_results(test, is_standalone=False): success = False continue - - # Some fixed cpp files have an header that was also fixed. Compare it here too. - possible_headerfile_expected = test.expectedFixedFilename().replace('.cpp', '.h') - if os.path.exists(possible_headerfile_expected): - possible_headerfile = test.fixedFilename().replace('.cpp', '.h') - if not compare_files(False, possible_headerfile_expected, possible_headerfile, test.printableName(True, True).replace('.cpp', '.h')): - success = False - continue + if not compare_fixit_results(test, is_standalone=True): + success = False + continue return success def dump_ast(check): for test in check.tests: ast_filename = test.filename() + ".ast" run_command(dump_ast_command(test) + " > " + ast_filename) print "Dumped AST to " + os.getcwd() + "/" + ast_filename #------------------------------------------------------------------------------- def load_checks(all_check_names): checks = [] for name in all_check_names: try: check = load_json(name) if check.enabled: checks.append(check) except: print "Error while loading " + name raise sys.exit(-1) return checks #------------------------------------------------------------------------------- # main if 'CLAZY_NO_WERROR' in os.environ: del os.environ['CLAZY_NO_WERROR'] os.environ['CLAZY_CHECKS'] = '' all_check_names = get_check_names() all_checks = load_checks(all_check_names) requested_check_names = args.check_names requested_check_names = map(lambda x: x.strip("/\\"), requested_check_names) for check_name in requested_check_names: if check_name not in all_check_names: print "Unknown check: " + check_name print sys.exit(-1) if not requested_check_names: requested_check_names = all_check_names requested_checks = filter(lambda check: check.name in requested_check_names and check.name not in _excluded_checks, all_checks) requested_checks = filter(lambda check: check.minimum_clang_version <= CLANG_VERSION, requested_checks) threads = [] if _dump_ast: for check in requested_checks: os.chdir(check.name) dump_ast(check) os.chdir("..") else: cleanup_fixit_files(all_checks) # Remove stale stuff from all checks, as clang-apply-replacements will apply all .yaml files it can find, even checks that werent requested list_of_chunks = [[] for x in range(_num_threads)] # Each list is a list of Test to be worked on by a thread i = _num_threads for check in requested_checks: for test in check.tests: i = (i + 1) % _num_threads list_of_chunks[i].append(test) for tests in list_of_chunks: if not tests: continue; t = Thread(target=run_unit_tests, args=(tests,)) t.start() threads.append(t) for thread in threads: thread.join() if not run_fixit_tests(requested_checks): _was_successful = False if _was_successful: print "SUCCESS" sys.exit(0) else: print "FAIL" sys.exit(-1)