diff --git a/CMakeLists.txt b/CMakeLists.txt index ce358e9..91217b5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,246 +1,252 @@ # This is the top-level CMakeLists.txt file for the Clazy project. # # To build the man page from POD, run 'make man' after CMake (assumes perl is available) # To install the resulting man page, run 'make install' # The man page is not available on Windows. # project(clazy) cmake_minimum_required(VERSION 3.3) include(FeatureSummary) include(GenerateExportHeader) include("GNUInstallDirs") # Version setup set(CLAZY_VERSION_MAJOR "1") set(CLAZY_VERSION_MINOR "6") set(CLAZY_VERSION_PATCH "0") set(CLAZY_VERSION "${CLAZY_VERSION_MAJOR}.${CLAZY_VERSION_MINOR}.${CLAZY_VERSION_PATCH}") set(CLAZY_PRINT_VERSION "${CLAZY_VERSION_MAJOR}.${CLAZY_VERSION_MINOR}") set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_CURRENT_LIST_DIR}/cmake) if (NOT CLAZY_BUILD_WITH_CLANG) find_package(Clang 4.0 MODULE REQUIRED) endif() set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) add_definitions(-D__STDC_LIMIT_MACROS -D__STDC_CONSTANT_MACROS) add_definitions(-D_GNU_SOURCE -DHAVE_CLANG_CONFIG_H) option(CLAZY_AST_MATCHERS_CRASH_WORKAROUND "Disable AST Matchers if being built with clang. See bug #392223" ON) option(LINK_CLAZY_TO_LLVM "Links the clazy plugin to LLVM. Switch to OFF if your clang binary has all symbols already. Might need to be OFF if your LLVM is static." ON) if (CLAZY_AST_MATCHERS_CRASH_WORKAROUND AND "${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang") message("Enabling AST Matchers workaround. Consider building with gcc instead. See bug #392223.") add_definitions(-DCLAZY_DISABLE_AST_MATCHERS) endif() if(NOT CLAZY_BUILD_WITH_CLANG AND MSVC AND NOT CLANG_LIBRARY_IMPORT) message(FATAL_ERROR "\nOn MSVC you need to pass -DCLANG_LIBRARY_IMPORT=C:/path/to/llvm-build/lib/clang.lib to cmake when building Clazy.\nAlso make sure you've built LLVM with -DLLVM_EXPORT_SYMBOLS_FOR_PLUGINS=ON") endif() if(MSVC) # disable trigger-happy warnings from Clang/LLVM headers set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /wd4267 /wd4244 /wd4291 /wd4800 /wd4141 /wd4146 /wd4251") elseif(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -fno-common -Woverloaded-virtual -Wcast-qual -fno-strict-aliasing -pedantic -Wno-long-long -Wall -W -Wno-unused-parameter -Wwrite-strings -fno-exceptions -fno-rtti") endif() set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} -Wl,-flat_namespace -Wl,-undefined -Wl,suppress") if(WIN32) add_definitions(-D_CRT_SECURE_NO_WARNINGS) else() set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fPIC") endif() # Look for std::regex support message("Looking for std::regex support...") try_run(RUN_RESULT COMPILE_RESULT ${CMAKE_BINARY_DIR} ${CMAKE_CURRENT_LIST_DIR}/.cmake_has_regex_test.cpp) if(RUN_RESULT EQUAL 0) set(HAS_STD_REGEX TRUE) else() set(HAS_STD_REGEX FALSE) add_definitions(-DNO_STD_REGEX) message("old-style-connect check is disabled due to missing std::regex support") message("Suppressions are disabled due to missing std::regex support") endif() include(ClazySources.cmake) include_directories(${CMAKE_BINARY_DIR}) include_directories(${CLANG_INCLUDE_DIRS} ${CMAKE_CURRENT_LIST_DIR} ${CMAKE_CURRENT_LIST_DIR}/src) link_directories("${LLVM_INSTALL_PREFIX}/lib" ${LLVM_LIBRARY_DIRS}) macro(link_to_llvm name is_standalone) foreach(clang_lib ${CLANG_LIBS}) if(MSVC) get_filename_component(LIB_FILENAME ${clang_lib} NAME) if(LIB_FILENAME STREQUAL "clangFrontend.lib") # On MSVC we don't link against clangFrontend.lib, instead we link against clang.exe (via clang.lib) # Otherwise the clazy plugin would have it's own plugin registry and clang wouldn't see it. # This way clazy registers with clang. continue() endif() endif() target_link_libraries(${name} ${clang_lib}) endforeach() foreach(llvm_lib ${LLVM_LIBS}) if(NOT ${is_standalone} AND NOT APPLE AND NOT MINGW AND NOT MSVC) ## Don't link against LLVMSupport, causes: CommandLine Error: Option 'view-background' registered more than once! if (NOT llvm_lib MATCHES ".*LLVMSupport.*") target_link_libraries(${name} ${llvm_lib}) endif() else() target_link_libraries(${name} ${llvm_lib}) endif() endforeach() foreach(user_lib ${USER_LIBS}) target_link_libraries(${name} ${user_lib}) endforeach() foreach(llvm_system_lib ${LLVM_SYSTEM_LIBS}) target_link_libraries(${name} ${llvm_system_lib}) endforeach() if(WIN32) target_link_libraries(${name} version.lib) endif() + target_link_libraries(${name} clangTooling) + target_link_libraries(${name} clangToolingCore) + target_link_libraries(${name} clangToolingRefactor) endmacro() macro(add_clang_plugin name) set(srcs ${ARGN}) add_library(${name} SHARED ${srcs}) if(SYMBOL_FILE) set_target_properties(${name} PROPERTIES LINK_FlAGS "-exported_symbols_list ${SYMBOL_FILE}") endif() if (LINK_CLAZY_TO_LLVM) link_to_llvm(${name} FALSE) endif() if(MSVC) target_link_libraries(${name} ${CLANG_LIBRARY_IMPORT}) # Link against clang.exe to share the plugin registry endif() endmacro() set(SYMBOL_FILE Lazy.exports) if (NOT CLAZY_BUILD_WITH_CLANG) add_clang_plugin(ClazyPlugin ${CLAZY_PLUGIN_SRCS}) set_target_properties(ClazyPlugin PROPERTIES LINKER_LANGUAGE CXX PREFIX "" ) install(TARGETS ClazyPlugin RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} ) set(SHARE_INSTALL_DIR ${CMAKE_INSTALL_DATAROOTDIR} CACHE STRING "Share directory name") if(NOT WIN32) file(RELATIVE_PATH BIN_RELATIVE_LIBDIR "${CMAKE_INSTALL_FULL_BINDIR}" "${CMAKE_INSTALL_FULL_LIBDIR}") file(RELATIVE_PATH BIN_RELATIVE_SHAREDIR "${CMAKE_INSTALL_FULL_BINDIR}" "${CMAKE_INSTALL_FULL_DATAROOTDIR}") configure_file(${CMAKE_CURRENT_LIST_DIR}/clazy.cmake ${CMAKE_BINARY_DIR}/clazy @ONLY) install(PROGRAMS ${CMAKE_BINARY_DIR}/clazy DESTINATION bin) else() install(PROGRAMS ${CMAKE_CURRENT_LIST_DIR}/clazy.bat DESTINATION bin) if(MSVC) install(PROGRAMS ${CMAKE_CURRENT_LIST_DIR}/clazy-cl.bat DESTINATION bin) endif() endif() # Install the explanation README's set(DOC_INSTALL_DIR ${SHARE_INSTALL_DIR}/doc/clazy) include(${CMAKE_CURRENT_LIST_DIR}/readmes.cmake) install(FILES ${README_LEVEL0_FILES} DESTINATION ${DOC_INSTALL_DIR}/level0) install(FILES ${README_LEVEL1_FILES} DESTINATION ${DOC_INSTALL_DIR}/level1) install(FILES ${README_LEVEL2_FILES} DESTINATION ${DOC_INSTALL_DIR}/level2) install(FILES ${README_manuallevel_FILES} DESTINATION ${DOC_INSTALL_DIR}/manuallevel) # Install more doc files install(FILES README.md COPYING-LGPL2.txt checks.json DESTINATION ${DOC_INSTALL_DIR}) # Build docs set(MAN_INSTALL_DIR "${SHARE_INSTALL_DIR}/man/man1") add_subdirectory(docs) # rpath set(CMAKE_SKIP_BUILD_RPATH FALSE) set(CMAKE_BUILD_WITH_INSTALL_RPATH FALSE) set(CMAKE_INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_LIBDIR}") set(CMAKE_INSTALL_RPATH_USE_LINK_PATH TRUE) list(FIND CMAKE_PLATFORM_IMPLICIT_LINK_DIRECTORIES "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_LIBDIR}" isSystemDir) if("${isSystemDir}" STREQUAL "-1") set(CMAKE_INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_LIBDIR}") endif("${isSystemDir}" STREQUAL "-1") # Build clazy-standalone add_executable(clazy-standalone ${CLAZY_STANDALONE_SRCS}) if(MSVC) # On MSVC clang-standalone crashes with a meaningless backtrace if linked to ClazyPlugin.dll target_link_libraries(clazy-standalone clangFrontend) else() target_link_libraries(clazy-standalone ClazyPlugin) endif() link_to_llvm(clazy-standalone TRUE) install(TARGETS clazy-standalone DESTINATION bin PERMISSIONS OWNER_WRITE OWNER_EXECUTE OWNER_READ GROUP_EXECUTE GROUP_READ WORLD_READ WORLD_EXECUTE) set(CPACK_PACKAGE_VERSION_MAJOR ${CLAZY_VERSION_MAJOR}) set(CPACK_PACKAGE_VERSION_MINOR ${CLAZY_VERSION_MINOR}) set(CPACK_PACKAGE_VERSION_PATCH ${CLAZY_VERSION_PATCH}) include(CPack) else() set(LLVM_LINK_COMPONENTS Support ) add_clang_library(clazyPlugin ${CLAZY_PLUGIN_SRCS} LINK_LIBS + clangToolingCore + clangToolingInclusions + clangToolingRefactor clangFrontend clangDriver clangCodeGen clangSema clangAnalysis clangRewriteFrontend clangRewrite clangAST clangASTMatchers clangParse clangLex clangBasic clangARCMigrate clangEdit clangFrontendTool clangRewrite clangSerialization clangTooling clangStaticAnalyzerCheckers clangStaticAnalyzerCore clangStaticAnalyzerFrontend ) add_executable(clazy-standalone ${CLAZY_STANDALONE_SRCS}) target_link_libraries(clazy-standalone clazyPlugin) install(TARGETS clazy-standalone DESTINATION bin PERMISSIONS OWNER_WRITE OWNER_EXECUTE OWNER_READ GROUP_EXECUTE GROUP_READ WORLD_READ WORLD_EXECUTE) endif() diff --git a/ClazySources.cmake b/ClazySources.cmake index 497b952..7969d56 100644 --- a/ClazySources.cmake +++ b/ClazySources.cmake @@ -1,44 +1,45 @@ set(CLAZY_LIB_SRC ${CMAKE_CURRENT_LIST_DIR}/src/AccessSpecifierManager.cpp ${CMAKE_CURRENT_LIST_DIR}/src/checkbase.cpp ${CMAKE_CURRENT_LIST_DIR}/src/checkmanager.cpp ${CMAKE_CURRENT_LIST_DIR}/src/SuppressionManager.cpp ${CMAKE_CURRENT_LIST_DIR}/src/ContextUtils.cpp ${CMAKE_CURRENT_LIST_DIR}/src/FixItUtils.cpp + ${CMAKE_CURRENT_LIST_DIR}/src/FixItExporter.cpp ${CMAKE_CURRENT_LIST_DIR}/src/LoopUtils.cpp ${CMAKE_CURRENT_LIST_DIR}/src/PreProcessorVisitor.cpp ${CMAKE_CURRENT_LIST_DIR}/src/QtUtils.cpp ${CMAKE_CURRENT_LIST_DIR}/src/StringUtils.cpp ${CMAKE_CURRENT_LIST_DIR}/src/TemplateUtils.cpp ${CMAKE_CURRENT_LIST_DIR}/src/TypeUtils.cpp ${CMAKE_CURRENT_LIST_DIR}/src/Utils.cpp ) set(CLAZY_CHECKS_SRCS ${CMAKE_CURRENT_LIST_DIR}/src/checks/detachingbase.cpp ${CMAKE_CURRENT_LIST_DIR}/src/checks/inefficientqlistbase.cpp ${CMAKE_CURRENT_LIST_DIR}/src/checks/ruleofbase.cpp ) include(CheckSources.cmake) set(CLAZY_SHARED_SRCS # sources shared between clazy-standalone and clazy plugin ${CLAZY_CHECKS_SRCS} ${CMAKE_CURRENT_LIST_DIR}/src/ClazyContext.cpp ${CMAKE_CURRENT_LIST_DIR}/src/Clazy.cpp ${CLAZY_LIB_SRC} ) set(CLAZY_PLUGIN_SRCS # Sources for the plugin ${CLAZY_SHARED_SRCS} ) if (MSVC) set(CLAZY_STANDALONE_SRCS ${CLAZY_SHARED_SRCS} ${CMAKE_CURRENT_LIST_DIR}/src/ClazyStandaloneMain.cpp ) else() set(CLAZY_STANDALONE_SRCS ${CMAKE_CURRENT_LIST_DIR}/src/ClazyStandaloneMain.cpp ) endif() diff --git a/src/Clazy.cpp b/src/Clazy.cpp index cb78d63..fca56d0 100644 --- a/src/Clazy.cpp +++ b/src/Clazy.cpp @@ -1,408 +1,412 @@ /* This file is part of the clazy static checker. Copyright (C) 2015 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com Author: SĂ©rgio Martins Copyright (C) 2015-2017 Sergio Martins This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "Utils.h" #include "Clazy.h" #include "clazy_stl.h" #include "checkbase.h" #include "AccessSpecifierManager.h" #include "SourceCompatibilityHelpers.h" +#include "FixItExporter.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace clang; using namespace std; using namespace clang::ast_matchers; static void manuallyPopulateParentMap(ParentMap *map, Stmt *s) { if (!s) return; for (Stmt *child : s->children()) { llvm::errs() << "Patching " << child->getStmtClassName() << "\n"; map->setParent(child, s); manuallyPopulateParentMap(map, child); } } ClazyASTConsumer::ClazyASTConsumer(ClazyContext *context) : m_context(context) { #ifndef CLAZY_DISABLE_AST_MATCHERS m_matchFinder = new clang::ast_matchers::MatchFinder(); #endif } void ClazyASTConsumer::addCheck(const std::pair &check) { CheckBase *checkBase = check.first; #ifndef CLAZY_DISABLE_AST_MATCHERS checkBase->registerASTMatchers(*m_matchFinder); #endif //m_createdChecks.push_back(checkBase); const RegisteredCheck &rcheck = check.second; if (rcheck.options & RegisteredCheck::Option_VisitsStmts) m_checksToVisitStmts.push_back(checkBase); if (rcheck.options & RegisteredCheck::Option_VisitsDecls) m_checksToVisitDecls.push_back(checkBase); } ClazyASTConsumer::~ClazyASTConsumer() { #ifndef CLAZY_DISABLE_AST_MATCHERS delete m_matchFinder; #endif delete m_context; } bool ClazyASTConsumer::VisitDecl(Decl *decl) { if (AccessSpecifierManager *a = m_context->accessSpecifierManager) // Needs to visit system headers too (qobject.h for example) a->VisitDeclaration(decl); const SourceLocation locStart = clazy::getLocStart(decl); if (locStart.isInvalid() || m_context->sm.isInSystemHeader(locStart)) return true; const bool isFromIgnorableInclude = m_context->ignoresIncludedFiles() && !Utils::isMainFile(m_context->sm, locStart); m_context->lastDecl = decl; if (auto fdecl = dyn_cast(decl)) { m_context->lastFunctionDecl = fdecl; if (auto mdecl = dyn_cast(fdecl)) m_context->lastMethodDecl = mdecl; } for (CheckBase *check : m_checksToVisitDecls) { if (!(isFromIgnorableInclude && check->canIgnoreIncludes())) check->VisitDecl(decl); } return true; } bool ClazyASTConsumer::VisitStmt(Stmt *stm) { const SourceLocation locStart = clazy::getLocStart(stm); if (locStart.isInvalid() || m_context->sm.isInSystemHeader(locStart)) return true; if (!m_context->parentMap) { if (m_context->ci.getDiagnostics().hasUnrecoverableErrorOccurred()) return false; // ParentMap sometimes crashes when there were errors. Doesn't like a botched AST. m_context->parentMap = new ParentMap(stm); } ParentMap *parentMap = m_context->parentMap; // Workaround llvm bug: Crashes creating a parent map when encountering Catch Statements. if (lastStm && isa(lastStm) && !parentMap->hasParent(stm)) { parentMap->setParent(stm, lastStm); manuallyPopulateParentMap(parentMap, stm); } lastStm = stm; // clang::ParentMap takes a root statement, but there's no root statement in the AST, the root is a declaration // So add to parent map each time we go into a different hierarchy if (!parentMap->hasParent(stm)) parentMap->addStmt(stm); const bool isFromIgnorableInclude = m_context->ignoresIncludedFiles() && !Utils::isMainFile(m_context->sm, locStart); for (CheckBase *check : m_checksToVisitStmts) { if (!(isFromIgnorableInclude && check->canIgnoreIncludes())) check->VisitStmt(stm); } return true; } void ClazyASTConsumer::HandleTranslationUnit(ASTContext &ctx) { + // FIXME: EndSourceFile() is called automatically, but not BeginsSourceFile() + m_context->exporter->BeginSourceFile(clang::LangOptions()); + if ((m_context->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"); if (parseArgument("help", args)) { m_context = new ClazyContext(ci, headerFilter, ignoreDirs, ClazyContext::ClazyOption_None); PrintHelp(llvm::errs()); return true; } if (parseArgument("no-inplace-fixits", args)) { // Unit-tests don't use inplace fixits m_options |= ClazyContext::ClazyOption_NoFixitsInplace; } if (parseArgument("enable-all-fixits", args)) { // This is useful for unit-tests, where we also want to run fixits. Don't use it otherwise. m_options |= ClazyContext::ClazyOption_AllFixitsEnabled; } if (parseArgument("no-autowrite-fixits", args)) m_options |= ClazyContext::ClazyOption_NoFixitsAutoWrite; if (parseArgument("qt4-compat", args)) m_options |= ClazyContext::ClazyOption_Qt4Compat; if (parseArgument("only-qt", args)) m_options |= ClazyContext::ClazyOption_OnlyQt; if (parseArgument("qt-developer", args)) m_options |= ClazyContext::ClazyOption_QtDeveloper; if (parseArgument("visit-implicit-code", args)) m_options |= ClazyContext::ClazyOption_VisitImplicitCode; if (parseArgument("ignore-included-files", args)) m_options |= ClazyContext::ClazyOption_IgnoreIncludedFiles; m_context = new ClazyContext(ci, headerFilter, ignoreDirs, m_options); // This argument is for debugging purposes const bool dbgPrintRequestedChecks = parseArgument("print-requested-checks", args); { std::lock_guard lock(CheckManager::lock()); m_checks = m_checkManager->requestedChecks(m_context, args); } if (args.size() > 1) { // Too many arguments. llvm::errs() << "Too many arguments: "; for (const std::string &a : args) llvm::errs() << a << ' '; llvm::errs() << "\n"; PrintHelp(llvm::errs()); return false; } else if (args.size() == 1 && m_checks.empty()) { // Checks were specified but couldn't be found llvm::errs() << "Could not find checks in comma separated string " + args[0] + "\n"; PrintHelp(llvm::errs()); return false; } if (dbgPrintRequestedChecks) printRequestedChecks(); return true; } void ClazyASTAction::printRequestedChecks() const { llvm::errs() << "Requested checks: "; const unsigned int numChecks = m_checks.size(); for (unsigned int i = 0; i < numChecks; ++i) { llvm::errs() << m_checks.at(i).name; const bool isLast = i == numChecks - 1; if (!isLast) { llvm::errs() << ", "; } } llvm::errs() << "\n"; } void ClazyASTAction::PrintHelp(llvm::raw_ostream &ros) const { std::lock_guard lock(CheckManager::lock()); RegisteredCheck::List checks = m_checkManager->availableChecks(MaxCheckLevel); clazy::sort(checks, checkLessThanByLevel); ros << "Available checks and FixIts:\n\n"; int lastPrintedLevel = -1; const auto numChecks = checks.size(); for (unsigned int i = 0; i < numChecks; ++i) { const RegisteredCheck &check = checks[i]; const string levelStr = "level" + to_string(check.level); if (lastPrintedLevel < check.level) { lastPrintedLevel = check.level; if (check.level > 0) ros << "\n"; ros << "- Checks from " << levelStr << ":\n"; } const string relativeReadmePath = "src/checks/" + levelStr + "/README-" + check.name + ".md"; auto padded = check.name; padded.insert(padded.end(), 39 - padded.size(), ' '); ros << " - " << check.name;; auto fixits = m_checkManager->availableFixIts(check.name); if (!fixits.empty()) { ros << " ("; bool isFirst = true; for (const auto& fixit : fixits) { if (isFirst) { isFirst = false; } else { ros << ','; } ros << fixit.name; } ros << ')'; } ros << "\n"; } ros << "\nIf nothing is specified, all checks from level0 and level1 will be run.\n\n"; ros << "To specify which checks to enable set the CLAZY_CHECKS env variable, for example:\n"; ros << " export CLAZY_CHECKS=\"level0\"\n"; ros << " export CLAZY_CHECKS=\"level0,reserve-candidates,qstring-allocations\"\n"; ros << " export CLAZY_CHECKS=\"reserve-candidates\"\n\n"; ros << "or pass as compiler arguments, for example:\n"; ros << " -Xclang -plugin-arg-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, ClazyContext::ClazyOptions options) : clang::ASTFrontendAction() , m_checkList(checkList.empty() ? "level1" : checkList) , m_headerFilter(headerFilter.empty() ? getEnvVariable("CLAZY_HEADER_FILTER") : headerFilter) , m_ignoreDirs(ignoreDirs.empty() ? getEnvVariable("CLAZY_IGNORE_DIRS") : ignoreDirs) , m_options(options) { } unique_ptr ClazyStandaloneASTAction::CreateASTConsumer(CompilerInstance &ci, llvm::StringRef) { auto context = new ClazyContext(ci, m_headerFilter, m_ignoreDirs, m_options); auto astConsumer = new ClazyASTConsumer(context); auto cm = CheckManager::instance(); vector checks; checks.push_back(m_checkList); const RegisteredCheck::List requestedChecks = cm->requestedChecks(context, checks); if (requestedChecks.size() == 0) { llvm::errs() << "No checks were requested!\n" << "\n"; return nullptr; } auto createdChecks = cm->createChecks(requestedChecks, context); for (const auto &check : createdChecks) { astConsumer->addCheck(check); } return unique_ptr(astConsumer); } volatile int ClazyPluginAnchorSource = 0; static FrontendPluginRegistry::Add X("clazy", "clang lazy plugin"); diff --git a/src/ClazyContext.cpp b/src/ClazyContext.cpp index 0ba7a5d..b5d9868 100644 --- a/src/ClazyContext.cpp +++ b/src/ClazyContext.cpp @@ -1,133 +1,142 @@ /* 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 "PreProcessorVisitor.h" #include "ClazyContext.h" +#include "FixItExporter.h" +#include "PreProcessorVisitor.h" #include -#include #include #include +#include #include #include using namespace std; using namespace clang; class ClazyFixItOptions : public FixItOptions { public: ClazyFixItOptions(const ClazyFixItOptions &other) = delete; ClazyFixItOptions(bool inplace) { if (const char *suffix = getenv("CLAZY_FIXIT_SUFFIX")) m_suffix = suffix; InPlace = inplace && m_suffix.empty(); FixWhatYouCan = true; FixOnlyWarnings = true; Silent = false; } std::string RewriteFilename(const std::string &filename, int &fd) override { fd = -1; return InPlace ? filename : filename + m_suffix; } std::string m_suffix; }; ClazyContext::ClazyContext(const clang::CompilerInstance &compiler, const string &headerFilter, const string &ignoreDirs, ClazyOptions opts) : 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"), ',')) { if (!headerFilter.empty()) headerFilterRegex = std::unique_ptr(new llvm::Regex(headerFilter)); if (!ignoreDirs.empty()) ignoreDirsRegex = std::unique_ptr(new llvm::Regex(ignoreDirs)); const char *fixitsEnv = getenv("CLAZY_FIXIT"); allFixitsEnabled = (options & ClazyOption_AllFixitsEnabled); if (!allFixitsEnabled && fixitsEnv) { const string fixitsEnvStr = clazy::unquoteString(fixitsEnv); if (fixitsEnvStr == "all_fixits") { allFixitsEnabled = true; } else { requestedFixitName = fixitsEnvStr; } } if (fixitsEnabled() && !(options & ClazyOption_NoFixitsAutoWrite)) rewriter = new FixItRewriter(ci.getDiagnostics(), sm, ci.getLangOpts(), new ClazyFixItOptions(fixitsAreInplace())); + + exporter = new FixItExporter(ci.getDiagnostics(), sm, ci.getLangOpts(), + new ClazyFixItOptions(fixitsAreInplace())); } ClazyContext::~ClazyContext() { //delete preprocessorVisitor; // we don't own it delete accessSpecifierManager; delete parentMap; + if (exporter) { + exporter->Export(); + delete exporter; + } + if (rewriter) { rewriter->WriteFixedFiles(); delete rewriter; } preprocessorVisitor = nullptr; accessSpecifierManager = nullptr; parentMap = nullptr; } void ClazyContext::enableAccessSpecifierManager() { if (!accessSpecifierManager && !usingPreCompiledHeaders()) accessSpecifierManager = new AccessSpecifierManager(ci); } void ClazyContext::enablePreprocessorVisitor() { if (!preprocessorVisitor && !usingPreCompiledHeaders()) preprocessorVisitor = new PreProcessorVisitor(ci); } bool ClazyContext::isQt() const { static const bool s_isQt = [this] { for (auto s : ci.getPreprocessorOpts().Macros) { if (s.first == "QT_CORE_LIB") return true; } return false; } (); return s_isQt; } diff --git a/src/ClazyContext.h b/src/ClazyContext.h index 1d7ad9a..a53572b 100644 --- a/src/ClazyContext.h +++ b/src/ClazyContext.h @@ -1,193 +1,195 @@ /* This file is part of the clazy static checker. Copyright (C) 2017 Sergio Martins This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef CLAZY_CONTEXT_H #define CLAZY_CONTEXT_H #include "SuppressionManager.h" #include "clazy_stl.h" #include #include #include #include #include #include #include #include #include #include #include // ClazyContext is just a struct to share data and code between all checks namespace clang { class CompilerInstance; class ASTContext; class ParentMap; class SourceManager; class FixItRewriter; class CXXMethodDecl; class Decl; } class AccessSpecifierManager; class PreProcessorVisitor; +class FixItExporter; class ClazyContext { public: enum ClazyOption { ClazyOption_None = 0, ClazyOption_NoFixitsInplace = 1, ClazyOption_NoFixitsAutoWrite = 2, // If enabled then fixits are reported, but not applied ClazyOption_AllFixitsEnabled = 4, ClazyOption_Qt4Compat = 8, ClazyOption_OnlyQt = 16, // Ignore non-Qt files. This is done by bailing out if QT_CORE_LIB is not set. ClazyOption_QtDeveloper = 32, // For running clazy on Qt itself, optional, but honours specific guidelines ClazyOption_VisitImplicitCode = 64, // Inspect compiler generated code aswell, useful for custom checks, if they need it ClazyOption_IgnoreIncludedFiles = 128 // Only warn for the current file being compiled, not on includes (useful for performance reasons) }; typedef int ClazyOptions; explicit ClazyContext(const clang::CompilerInstance &ci, const std::string &headerFilter, const std::string &ignoreDirs, ClazyOptions = ClazyOption_None); ~ClazyContext(); bool usingPreCompiledHeaders() const { return !ci.getPreprocessorOpts().ImplicitPCHInclude.empty(); } bool userDisabledWError() const { return m_noWerror; } bool fixitsAreInplace() const { return !(options & ClazyOption_NoFixitsInplace); } bool fixitsEnabled() const { return allFixitsEnabled || !requestedFixitName.empty(); } bool isQtDeveloper() const { return options & ClazyOption_QtDeveloper; } bool ignoresIncludedFiles() const { return options & ClazyOption_IgnoreIncludedFiles; } bool isVisitImplicitCode() const { return options & ClazyContext::ClazyOption_VisitImplicitCode; } bool isOptionSet(const std::string &optionName) const { return clazy::contains(extraOptions, optionName); } bool fileMatchesLoc(const std::unique_ptr ®ex, clang::SourceLocation loc, const clang::FileEntry **file) const { if (!regex) return false; if (!(*file)) { clang::FileID fid = sm.getDecomposedExpansionLoc(loc).first; *file = sm.getFileEntryForID(fid); if (!(*file)) { return false; } } llvm::StringRef fileName((*file)->getName()); return regex->match(fileName); } bool shouldIgnoreFile(clang::SourceLocation loc) const { // 1. Process the regexp that excludes files const clang::FileEntry *file = nullptr; if (ignoreDirsRegex) { const bool matches = fileMatchesLoc(ignoreDirsRegex, loc, &file); if (matches) return true; } // 2. Process the regexp that includes files. Has lower priority. if (!headerFilterRegex || isMainFile(loc)) return false; const bool matches = fileMatchesLoc(headerFilterRegex, loc, &file); if (!file) return false; return !matches; } bool isMainFile(clang::SourceLocation loc) const { if (loc.isMacroID()) loc = sm.getExpansionLoc(loc); return sm.isInFileID(loc, sm.getMainFileID()); } /** * We only enable it if a check needs it, for performance reasons */ void enableAccessSpecifierManager(); void enablePreprocessorVisitor(); bool isQt() const; // TODO: More things will follow const clang::CompilerInstance &ci; clang::ASTContext &astContext; clang::SourceManager &sm; AccessSpecifierManager *accessSpecifierManager = nullptr; PreProcessorVisitor *preprocessorVisitor = nullptr; SuppressionManager suppressionManager; const bool m_noWerror; clang::ParentMap *parentMap = nullptr; const ClazyOptions options; const std::vector extraOptions; clang::FixItRewriter *rewriter = nullptr; + FixItExporter *exporter = nullptr; bool allFixitsEnabled = false; std::string requestedFixitName; 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/FixItExporter.cpp b/src/FixItExporter.cpp new file mode 100644 index 0000000..d16deb9 --- /dev/null +++ b/src/FixItExporter.cpp @@ -0,0 +1,166 @@ +/* + This file is part of the clazy static checker. + + Copyright (C) 2019 Christian Gagneraud + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#include "FixItExporter.h" + +#include +#include +#include +#include + +#define DEBUG_FIX_IT_EXPORTER + +#ifdef DEBUG_FIX_IT_EXPORTER +# include +#endif + +using namespace clang; + +FixItExporter::FixItExporter(DiagnosticsEngine &DiagEngine, SourceManager &SourceMgr, + const LangOptions &LangOpts, FixItOptions *FixItOpts) + : DiagEngine(DiagEngine), SourceMgr(SourceMgr), LangOpts(LangOpts) + , FixItOpts(FixItOpts) +{ + Owner = DiagEngine.takeClient(); + Client = DiagEngine.getClient(); + DiagEngine.setClient(this, false); +} + +FixItExporter::~FixItExporter() +{ + DiagEngine.setClient(Client, Owner.release() != nullptr); +} + +void FixItExporter::BeginSourceFile(const LangOptions &LangOpts, const Preprocessor *PP) +{ + if (Client) + Client->BeginSourceFile(LangOpts, PP); + + const auto id = SourceMgr.getMainFileID(); + const auto entry = SourceMgr.getFileEntryForID(id); + TUDiag.MainSourceFile = entry->getName(); +} + +bool FixItExporter::IncludeInDiagnosticCounts() const +{ + return Client ? Client->IncludeInDiagnosticCounts() : true; +} + +void FixItExporter::EndSourceFile() +{ + if (Client) + Client->EndSourceFile(); +} + +tooling::Diagnostic FixItExporter::ConvertDiagnostic(const Diagnostic &Info) +{ + SmallString<100> TmpMessageText; + Info.FormatDiagnostic(TmpMessageText); + const auto MessageText = TmpMessageText.slice(0, TmpMessageText.find_last_of('[') - 1).str(); + const auto CheckName = TmpMessageText.slice(TmpMessageText.find_last_of('[') + 3, + TmpMessageText.find_last_of(']')).str(); + llvm::StringRef CurrentBuildDir; // Not needed? + + tooling::Diagnostic ToolingDiag(CheckName, + tooling::Diagnostic::Warning, + CurrentBuildDir); + ToolingDiag.Message = tooling::DiagnosticMessage(MessageText, SourceMgr, Info.getLocation()); + return ToolingDiag; +} + +tooling::Replacement FixItExporter::ConvertFixIt(const FixItHint &Hint) +{ + tooling::Replacement Replacement; + if (Hint.CodeToInsert.empty()) { + if (Hint.InsertFromRange.isValid()) { + clang::SourceLocation b(Hint.InsertFromRange.getBegin()), _e(Hint.InsertFromRange.getEnd()); + if (b.isMacroID()) + b = SourceMgr.getSpellingLoc(b); + if (_e.isMacroID()) + _e = SourceMgr.getSpellingLoc(_e); + clang::SourceLocation e(clang::Lexer::getLocForEndOfToken(_e, 0, SourceMgr, LangOpts)); + StringRef Text(SourceMgr.getCharacterData(b), + SourceMgr.getCharacterData(e)-SourceMgr.getCharacterData(b)); + return tooling::Replacement(SourceMgr, Hint.RemoveRange, Text); + } + return tooling::Replacement(SourceMgr, Hint.RemoveRange, ""); + } + return tooling::Replacement(SourceMgr, Hint.RemoveRange, Hint.CodeToInsert); +} + +void FixItExporter::HandleDiagnostic(DiagnosticsEngine::Level DiagLevel, const Diagnostic &Info) +{ + // Default implementation (Warnings/errors count). + DiagnosticConsumer::HandleDiagnostic(DiagLevel, Info); + + // Let origianl client do it's handling + if (Client) + Client->HandleDiagnostic(DiagLevel, Info); + + // We only deal with warnings + if (DiagLevel != DiagnosticsEngine::Warning) + return; + + // Convert and record this diagnostic + auto ToolingDiag = ConvertDiagnostic(Info); + for (unsigned Idx = 0, Last = Info.getNumFixItHints(); + Idx < Last; ++Idx) { + const FixItHint &Hint = Info.getFixItHint(Idx); + const auto replacement = ConvertFixIt(Hint); +#ifdef DEBUG_FIX_IT_EXPORTER + const auto FileName = SourceMgr.getFilename(Info.getLocation()); + std::cerr << "Handling Fixit #" << Idx << " for " << FileName.str() << std::endl; + std::cerr << "F: " + << Hint.RemoveRange.getBegin().printToString(SourceMgr) << ":" + << Hint.RemoveRange.getEnd().printToString(SourceMgr) << " " + << Hint.InsertFromRange.getBegin().printToString(SourceMgr) << ":" + << Hint.InsertFromRange.getEnd().printToString(SourceMgr) << " " + << Hint.BeforePreviousInsertions << " " + << Hint.CodeToInsert << std::endl; + std::cerr << "R: " << replacement.toString() << std::endl; +#endif + auto &Replacements = ToolingDiag.Fix[replacement.getFilePath()]; + auto error = Replacements.add(ConvertFixIt(Hint)); + if (error) { + Diag(Info.getLocation(), diag::note_fixit_failed); + } + } + TUDiag.Diagnostics.push_back(ToolingDiag); +} + +void FixItExporter::Export() +{ + std::error_code EC; + llvm::raw_fd_ostream OS(TUDiag.MainSourceFile + ".yaml", EC, llvm::sys::fs::F_None); + llvm::yaml::Output YAML(OS); + YAML << TUDiag; +} + +void FixItExporter::Diag(SourceLocation Loc, unsigned DiagID) +{ + // When producing this diagnostic, we temporarily bypass ourselves, + // clear out any current diagnostic, and let the downstream client + // format the diagnostic. + DiagEngine.setClient(Client, false); + DiagEngine.Clear(); + DiagEngine.Report(Loc, DiagID); + DiagEngine.setClient(this, false); +} diff --git a/src/FixItExporter.h b/src/FixItExporter.h new file mode 100644 index 0000000..3de100d --- /dev/null +++ b/src/FixItExporter.h @@ -0,0 +1,67 @@ +/* + This file is part of the clazy static checker. + + Copyright (C) 2019 Christian Gagneraud + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#ifndef CLAZY_FIX_IT_EXPORTER_H +#define CLAZY_FIX_IT_EXPORTER_H + +#include +#include + +namespace clang { +class FixItOptions; +} + +class FixItExporter : public clang::DiagnosticConsumer +{ +public: + FixItExporter(clang::DiagnosticsEngine &DiagEngine, clang::SourceManager &SourceMgr, + const clang::LangOptions &LangOpts, clang::FixItOptions *FixItOpts); + + ~FixItExporter() override; + + bool IncludeInDiagnosticCounts() const override; + + void BeginSourceFile(const clang::LangOptions &LangOpts, + const clang::Preprocessor *PP = nullptr) override; + + void EndSourceFile() override; + + void HandleDiagnostic(clang::DiagnosticsEngine::Level DiagLevel, + const clang::Diagnostic &Info) override; + + void Export(); + + /// Emit a diagnostic via the adapted diagnostic client. + void Diag(clang::SourceLocation Loc, unsigned DiagID); + +private: + clang::DiagnosticsEngine &DiagEngine; + clang::SourceManager &SourceMgr; + const clang::LangOptions &LangOpts; + clang::FixItOptions *FixItOpts; + DiagnosticConsumer *Client; + std::unique_ptr Owner; + clang::tooling::TranslationUnitDiagnostics TUDiag; + clang::tooling::Diagnostic ConvertDiagnostic(const clang::Diagnostic &Info); + clang::tooling::Replacement ConvertFixIt(const clang::FixItHint &Hint); +}; + +#endif // CLAZY_FIX_IT_EXPORTER_H diff --git a/src/checks/level1/connect-3arg-lambda.cpp b/src/checks/level1/connect-3arg-lambda.cpp index f0d32ea..b09d8a6 100644 --- a/src/checks/level1/connect-3arg-lambda.cpp +++ b/src/checks/level1/connect-3arg-lambda.cpp @@ -1,157 +1,158 @@ /* 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 "connect-3arg-lambda.h" #include "HierarchyUtils.h" #include "QtUtils.h" +#include "ClazyContext.h" #include #include #include #include #include #include #include class ClazyContext; using namespace clang; using namespace std; using uint = unsigned; Connect3ArgLambda::Connect3ArgLambda(const std::string &name, ClazyContext *context) : CheckBase(name, context, Option_CanIgnoreIncludes) { } void Connect3ArgLambda::VisitStmt(clang::Stmt *stmt) { auto callExpr = dyn_cast(stmt); if (!callExpr) return; FunctionDecl *fdecl = callExpr->getDirectCallee(); if (!fdecl) return; const uint numParams = fdecl->getNumParams(); if (numParams != 2 && numParams != 3) return; string qualifiedName = fdecl->getQualifiedNameAsString(); if (qualifiedName == "QTimer::singleShot") { processQTimer(fdecl, stmt); return; } if (qualifiedName == "QMenu::addAction") { processQMenu(fdecl, stmt); return; } if (numParams != 3 || !clazy::isConnect(fdecl)) return; auto lambda = clazy::getFirstChildOfType2(callExpr->getArg(2)); if (!lambda) return; DeclRefExpr *senderDeclRef = nullptr; MemberExpr *senderMemberExpr = nullptr; Stmt *s = callExpr->getArg(0); while (s) { if ((senderDeclRef = dyn_cast(s))) break; if ((senderMemberExpr = dyn_cast(s))) break; s = clazy::getFirstChild(s); } // The sender can be: this auto senderThis = clazy::unpeal(callExpr->getArg(0), clazy::IgnoreImplicitCasts); // The variables used inside the lambda auto declrefs = clazy::getStatements(lambda->getBody()); ValueDecl *senderDecl = senderDeclRef ? senderDeclRef->getDecl() : nullptr; // We'll only warn if the lambda is dereferencing another QObject (besides the sender) bool found = false; for (auto declref : declrefs) { ValueDecl *decl = declref->getDecl(); if (decl == senderDecl) continue; // It's the sender, continue. if (clazy::isQObject(decl->getType())) { found = true; break; } } if (!found) { auto thisexprs = clazy::getStatements(lambda->getBody()); if (!thisexprs.empty() && !senderThis) found = true; } if (found) emitWarning(stmt, "Pass a context object as 3rd connect parameter"); } void Connect3ArgLambda::processQTimer(FunctionDecl *func, Stmt *stmt) { // Signatures to catch: // QTimer::singleShot(int msec, Functor functor) // QTimer::singleShot(int msec, Qt::TimerType timerType, Functor functor) const uint numParams = func->getNumParams(); if (numParams == 2) { if (func->getParamDecl(0)->getNameAsString() == "interval" && func->getParamDecl(1)->getNameAsString() == "slot") { emitWarning(stmt, "Pass a context object as 2nd singleShot parameter"); } } else if (numParams == 3) { if (func->getParamDecl(0)->getNameAsString() == "interval" && func->getParamDecl(1)->getNameAsString() == "timerType" && func->getParamDecl(2)->getNameAsString() == "slot") { emitWarning(stmt, "Pass a context object as 3rd singleShot parameter"); } } } void Connect3ArgLambda::processQMenu(FunctionDecl *func, Stmt *stmt) { // Signatures to catch: // QMenu::addAction(const QString &text, Func1 slot, const QKeySequence &shortcut = 0) const uint numParams = func->getNumParams(); if (numParams == 3) { if (func->getParamDecl(0)->getNameAsString() == "text" && func->getParamDecl(1)->getNameAsString() == "slot" && func->getParamDecl(2)->getNameAsString() == "shortcut") { emitWarning(stmt, "Pass a context object as 2nd singleShot parameter"); } } }