diff --git a/CMakeLists.txt b/CMakeLists.txt index 4e60d1f..f8f781d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,305 +1,308 @@ # 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.7) if (${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.12.0") cmake_policy(SET CMP0074 NEW) endif() 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 5.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) option(APPIMAGE_HACK "Links the clazy plugin to the clang tooling libs only. For some reason this is needed when building on our old CentOS 6.8 to create the AppImage." OFF) 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") +if(NOT RUN_RESULT EQUAL 0) + message("Using boost::regex instead of std::regex") + set(CLAZY_USES_BOOST_REGEX TRUE) + add_definitions(-DCLAZY_USES_BOOST_REGEX) + find_package(Boost REQUIRED COMPONENTS regex) + include_directories(${Boost_INCLUDE_DIRS}) 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}) if (${LLVM_VERSION} VERSION_GREATER_EQUAL "9.0.0") set(clang_tooling_refactoring_lib clangToolingRefactoring) else() set(clang_tooling_refactoring_lib clangToolingRefactor) endif() 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} ${clang_tooling_refactoring_lib}) + + if (CLAZY_USES_BOOST_REGEX) + target_link_libraries(${name} ${Boost_LIBRARIES}) + endif() 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) else() if (APPIMAGE_HACK) # Hack to build on old CentOS 6.8 target_link_libraries(${name} clangTooling) target_link_libraries(${name} clangToolingCore) target_link_libraries(${name} ${clang_tooling_refactoring_lib}) endif() 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) set(CLAZY_MINI_AST_DUMPER_SRCS src/MiniAstDumper.cpp) add_clang_plugin(ClazyPlugin ${CLAZY_PLUGIN_SRCS} ${CLAZY_MINI_AST_DUMPER_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 ${clang_tooling_refactoring_lib} 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() function(to_raw_string_literal input_string output_string) if (MSVC) # Work around "C2026: string too big, trailing characters truncated" # https://docs.microsoft.com/en-us/cpp/error-messages/compiler-errors-1/compiler-error-c2026?view=vs-2019 # The limit is 16380 single-byte characters, so split up the string as # suggested on the site. set(str ${input_string}) set(chunk_size 1000) set(result "\n") string(LENGTH ${str} str_size) while (${str_size} GREATER ${chunk_size}) string(SUBSTRING ${str} 0 ${chunk_size} chunk) string(SUBSTRING ${str} ${chunk_size} -1 str) set(chunk "R\"meta(${chunk})meta\"\n") string(APPEND result ${chunk}) string(LENGTH ${str} str_size) endwhile() if (str_size GREATER 0) string(APPEND result "R\"meta(${str})meta\"\n") endif() set(${output_string} ${result} PARENT_SCOPE) else() set(result "\nR\"meta(${input_string})meta\"\n") set(${output_string} ${result} PARENT_SCOPE) endif() endfunction() file(READ checks.json SUPPORTED_CHECKS_JSON_STR) to_raw_string_literal(${SUPPORTED_CHECKS_JSON_STR} SUPPORTED_CHECKS_JSON_STR) configure_file(checks.json.h.in checks.json.h) install(FILES org.kde.clazy.metainfo.xml DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/metainfo) diff --git a/CheckSources.cmake b/CheckSources.cmake index 8d310ae..9760eeb 100644 --- a/CheckSources.cmake +++ b/CheckSources.cmake @@ -1,89 +1,85 @@ set(CLAZY_CHECKS_SRCS ${CLAZY_CHECKS_SRCS} ${CMAKE_CURRENT_LIST_DIR}/src/checks/manuallevel/assert-with-side-effects.cpp ${CMAKE_CURRENT_LIST_DIR}/src/checks/manuallevel/container-inside-loop.cpp ${CMAKE_CURRENT_LIST_DIR}/src/checks/manuallevel/detaching-member.cpp ${CMAKE_CURRENT_LIST_DIR}/src/checks/manuallevel/heap-allocated-small-trivial-type.cpp ${CMAKE_CURRENT_LIST_DIR}/src/checks/manuallevel/ifndef-define-typo.cpp ${CMAKE_CURRENT_LIST_DIR}/src/checks/manuallevel/inefficient-qlist.cpp ${CMAKE_CURRENT_LIST_DIR}/src/checks/manuallevel/isempty-vs-count.cpp ${CMAKE_CURRENT_LIST_DIR}/src/checks/manuallevel/qhash-with-char-pointer-key.cpp ${CMAKE_CURRENT_LIST_DIR}/src/checks/manuallevel/qproperty-type-mismatch.cpp ${CMAKE_CURRENT_LIST_DIR}/src/checks/manuallevel/qrequiredresult-candidates.cpp ${CMAKE_CURRENT_LIST_DIR}/src/checks/manuallevel/qstring-varargs.cpp ${CMAKE_CURRENT_LIST_DIR}/src/checks/manuallevel/qt-keywords.cpp ${CMAKE_CURRENT_LIST_DIR}/src/checks/manuallevel/qt4-qstring-from-array.cpp ${CMAKE_CURRENT_LIST_DIR}/src/checks/manuallevel/qvariant-template-instantiation.cpp ${CMAKE_CURRENT_LIST_DIR}/src/checks/manuallevel/raw-environment-function.cpp ${CMAKE_CURRENT_LIST_DIR}/src/checks/manuallevel/reserve-candidates.cpp ${CMAKE_CURRENT_LIST_DIR}/src/checks/manuallevel/signal-with-return-value.cpp ${CMAKE_CURRENT_LIST_DIR}/src/checks/manuallevel/thread-with-slots.cpp ${CMAKE_CURRENT_LIST_DIR}/src/checks/manuallevel/tr-non-literal.cpp ${CMAKE_CURRENT_LIST_DIR}/src/checks/manuallevel/unneeded-cast.cpp ${CMAKE_CURRENT_LIST_DIR}/src/checks/level0/connect-by-name.cpp ${CMAKE_CURRENT_LIST_DIR}/src/checks/level0/connect-non-signal.cpp ${CMAKE_CURRENT_LIST_DIR}/src/checks/level0/connect-not-normalized.cpp ${CMAKE_CURRENT_LIST_DIR}/src/checks/level0/container-anti-pattern.cpp ${CMAKE_CURRENT_LIST_DIR}/src/checks/level0/empty-qstringliteral.cpp ${CMAKE_CURRENT_LIST_DIR}/src/checks/level0/fully-qualified-moc-types.cpp ${CMAKE_CURRENT_LIST_DIR}/src/checks/level0/lambda-in-connect.cpp ${CMAKE_CURRENT_LIST_DIR}/src/checks/level0/lambda-unique-connection.cpp ${CMAKE_CURRENT_LIST_DIR}/src/checks/level0/lowercase-qml-type-name.cpp ${CMAKE_CURRENT_LIST_DIR}/src/checks/level0/mutable-container-key.cpp ${CMAKE_CURRENT_LIST_DIR}/src/checks/level0/qcolor-from-literal.cpp ${CMAKE_CURRENT_LIST_DIR}/src/checks/level0/qdatetime-utc.cpp ${CMAKE_CURRENT_LIST_DIR}/src/checks/level0/qenums.cpp ${CMAKE_CURRENT_LIST_DIR}/src/checks/level0/qfileinfo-exists.cpp ${CMAKE_CURRENT_LIST_DIR}/src/checks/level0/qgetenv.cpp ${CMAKE_CURRENT_LIST_DIR}/src/checks/level0/qmap-with-pointer-key.cpp ${CMAKE_CURRENT_LIST_DIR}/src/checks/level0/qstring-arg.cpp ${CMAKE_CURRENT_LIST_DIR}/src/checks/level0/qstring-insensitive-allocation.cpp ${CMAKE_CURRENT_LIST_DIR}/src/checks/level0/qstring-ref.cpp ${CMAKE_CURRENT_LIST_DIR}/src/checks/level0/qt-macros.cpp ${CMAKE_CURRENT_LIST_DIR}/src/checks/level0/strict-iterators.cpp ${CMAKE_CURRENT_LIST_DIR}/src/checks/level0/temporary-iterator.cpp ${CMAKE_CURRENT_LIST_DIR}/src/checks/level0/unused-non-trivial-variable.cpp ${CMAKE_CURRENT_LIST_DIR}/src/checks/level0/writing-to-temporary.cpp ${CMAKE_CURRENT_LIST_DIR}/src/checks/level0/wrong-qevent-cast.cpp ${CMAKE_CURRENT_LIST_DIR}/src/checks/level0/wrong-qglobalstatic.cpp ${CMAKE_CURRENT_LIST_DIR}/src/checks/level1/auto-unexpected-qstringbuilder.cpp ${CMAKE_CURRENT_LIST_DIR}/src/checks/level1/child-event-qobject-cast.cpp ${CMAKE_CURRENT_LIST_DIR}/src/checks/level1/connect-3arg-lambda.cpp ${CMAKE_CURRENT_LIST_DIR}/src/checks/level1/const-signal-or-slot.cpp ${CMAKE_CURRENT_LIST_DIR}/src/checks/level1/detaching-temporary.cpp ${CMAKE_CURRENT_LIST_DIR}/src/checks/level1/foreach.cpp ${CMAKE_CURRENT_LIST_DIR}/src/checks/level1/incorrect-emit.cpp ${CMAKE_CURRENT_LIST_DIR}/src/checks/level1/inefficient-qlist-soft.cpp ${CMAKE_CURRENT_LIST_DIR}/src/checks/level1/install-event-filter.cpp ${CMAKE_CURRENT_LIST_DIR}/src/checks/level1/non-pod-global-static.cpp ${CMAKE_CURRENT_LIST_DIR}/src/checks/level1/overridden-signal.cpp ${CMAKE_CURRENT_LIST_DIR}/src/checks/level1/post-event.cpp ${CMAKE_CURRENT_LIST_DIR}/src/checks/level1/qdeleteall.cpp ${CMAKE_CURRENT_LIST_DIR}/src/checks/level1/qhash-namespace.cpp ${CMAKE_CURRENT_LIST_DIR}/src/checks/level1/qlatin1string-non-ascii.cpp ${CMAKE_CURRENT_LIST_DIR}/src/checks/level1/qproperty-without-notify.cpp ${CMAKE_CURRENT_LIST_DIR}/src/checks/level1/qstring-left.cpp ${CMAKE_CURRENT_LIST_DIR}/src/checks/level1/range-loop.cpp ${CMAKE_CURRENT_LIST_DIR}/src/checks/level1/returning-data-from-temporary.cpp ${CMAKE_CURRENT_LIST_DIR}/src/checks/level1/rule-of-two-soft.cpp ${CMAKE_CURRENT_LIST_DIR}/src/checks/level1/skipped-base-method.cpp ${CMAKE_CURRENT_LIST_DIR}/src/checks/level1/virtual-signal.cpp ${CMAKE_CURRENT_LIST_DIR}/src/checks/level2/base-class-event.cpp ${CMAKE_CURRENT_LIST_DIR}/src/checks/level2/copyable-polymorphic.cpp ${CMAKE_CURRENT_LIST_DIR}/src/checks/level2/ctor-missing-parent-argument.cpp ${CMAKE_CURRENT_LIST_DIR}/src/checks/level2/function-args-by-ref.cpp ${CMAKE_CURRENT_LIST_DIR}/src/checks/level2/function-args-by-value.cpp ${CMAKE_CURRENT_LIST_DIR}/src/checks/level2/global-const-char-pointer.cpp ${CMAKE_CURRENT_LIST_DIR}/src/checks/level2/implicit-casts.cpp ${CMAKE_CURRENT_LIST_DIR}/src/checks/level2/missing-qobject-macro.cpp ${CMAKE_CURRENT_LIST_DIR}/src/checks/level2/missing-typeinfo.cpp ${CMAKE_CURRENT_LIST_DIR}/src/checks/level2/old-style-connect.cpp ${CMAKE_CURRENT_LIST_DIR}/src/checks/level2/qstring-allocations.cpp ${CMAKE_CURRENT_LIST_DIR}/src/checks/level2/returning-void-expression.cpp ${CMAKE_CURRENT_LIST_DIR}/src/checks/level2/rule-of-three.cpp ${CMAKE_CURRENT_LIST_DIR}/src/checks/level2/static-pmf.cpp ${CMAKE_CURRENT_LIST_DIR}/src/checks/level2/virtual-call-ctor.cpp ) - -if(HAS_STD_REGEX OR CLAZY_BUILD_WITH_CLANG) - set(CLAZY_CHECKS_SRCS ${CLAZY_CHECKS_SRCS} ${CMAKE_CURRENT_LIST_DIR}/src/checks/level2/old-style-connect.cpp) -endif() diff --git a/checks.json b/checks.json index 86456dd..898dda7 100644 --- a/checks.json +++ b/checks.json @@ -1,616 +1,615 @@ { "available_categories" : ["readability", "qt4", "containers", "qstring", "cpp", "bug", "performance", "deprecation", "qml"], "checks" : [ { "name" : "qt-keywords", "level" : -1, "fixits" : [ { "name" : "qt-keywords" } ] }, { "name" : "signal-with-return-value", "level" : -1, "visits_decls" : true }, { "name" : "heap-allocated-small-trivial-type", "level" : -1, "categories" : ["performance"], "visits_stmts" : true }, { "name" : "ifndef-define-typo", "level" : -1, "categories" : ["bug"] }, { "name" : "inefficient-qlist", "level" : -1, "categories" : ["containers", "performance"], "visits_decls" : true }, { "name" : "isempty-vs-count", "class_name" : "IsEmptyVSCount", "level" : -1, "categories" : ["readability"], "visits_stmts" : true }, { "name" : "qrequiredresult-candidates", "class_name" : "QRequiredResultCandidates", "level" : -1, "categories" : ["bug"], "visits_decls" : true }, { "name" : "qstring-varargs", "level" : -1, "categories" : ["bug"], "visits_stmts" : true }, { "name" : "qt4-qstring-from-array", "class_name" : "Qt4QStringFromArray", "level" : -1, "categories" : ["qt4", "qstring"], "fixits" : [ { "name" : "qt4-qstring-from-array" } ], "visits_stmts" : true }, { "name" : "tr-non-literal", "level" : -1, "categories" : ["bug"], "visits_stmts" : true }, { "name" : "raw-environment-function", "level" : -1, "categories" : ["bug"], "visits_stmts" : true }, { "name" : "container-inside-loop", "level" : -1, "categories" : ["containers", "performance"], "visits_stmts" : true }, { "name" : "qhash-with-char-pointer-key", "level" : -1, "categories" : ["cpp", "bug"], "visits_decls" : true }, { "name" : "connect-by-name", "level" : 0, "categories" : ["bug", "readability"], "visits_decls" : true }, { "name" : "connect-non-signal", "minimum_qt_version" : 50700, "level" : 0, "categories" : ["bug"], "visits_stmts" : true }, { "name" : "wrong-qevent-cast", "level" : 0, "categories" : ["bug"], "visits_stmts" : true }, { "name" : "lambda-in-connect", "level" : 0, "categories" : ["bug"], "visits_stmts" : true }, { "name" : "lambda-unique-connection", "level" : 0, "categories" : ["bug"], "visits_stmts" : true }, { "name" : "qdatetime-utc", "class_name" : "QDateTimeUtc", "level" : 0, "categories" : ["performance"], "fixits" : [ { "name" : "qdatetime-utc" } ], "visits_stmts" : true }, { "name" : "qgetenv", "class_name" : "QGetEnv", "level" : 0, "minimum_qt_version" : 50500, "categories" : ["performance"], "fixits" : [ { "name" : "qgetenv" } ], "visits_stmts" : true }, { "name" : "qstring-insensitive-allocation", "level" : 0, "categories" : ["performance", "qstring"], "visits_stmts" : true }, { "name" : "fully-qualified-moc-types", "class_name" : "FullyQualifiedMocTypes", "level" : 0, "categories" : ["bug", "qml"], "visits_decls" : true }, { "name" : "qvariant-template-instantiation", "level" : -1, "visits_stmts" : true }, { "name" : "unused-non-trivial-variable", "level" : 0, "categories" : ["readability"], "visits_stmts" : true }, { "name" : "connect-not-normalized", "level" : 0, "categories" : ["performance"], "visits_stmts" : true }, { "name" : "mutable-container-key", "level" : 0, "categories" : ["containers", "bug"], "visits_decls" : true }, { "name" : "qenums", "level" : 0, "minimum_qt_version" : 50500, "categories" : ["deprecation"] }, { "name" : "qmap-with-pointer-key", "level" : 0, "categories" : ["containers", "performance"], "visits_decls" : true }, { "name" : "qstring-ref", "class_name" : "StringRefCandidates", "level" : 0, "categories" : ["performance", "qstring"], "fixits" : [ { "name" : "missing-qstringref" } ], "visits_stmts" : true }, { "name" : "strict-iterators", "level" : 0, "categories" : ["containers", "performance", "bug"], "visits_stmts" : true }, { "name" : "writing-to-temporary", "level" : 0, "categories" : ["bug"], "options" : [ { "name" : "widen-criteria" } ], "visits_stmts" : true }, { "name" : "container-anti-pattern", "level" : 0, "categories" : ["containers", "performance"], "visits_stmts" : true }, { "name" : "qcolor-from-literal", "level" : 0, "categories" : ["performance"], "visits_stmts" : true, "ifndef" : "CLAZY_DISABLE_AST_MATCHERS" }, { "name" : "qfileinfo-exists", "class_name" : "QFileInfoExists", "level" : 0, "categories" : ["performance"], "visits_stmts" : true }, { "name" : "qstring-arg", "level" : 0, "categories" : ["performance", "qstring"], "options" : [ { "name" : "fillChar-overloads" } ], "visits_stmts" : true }, { "name" : "empty-qstringliteral", "level" : 0, "categories" : ["performance"], "visits_stmts" : true }, { "name" : "qt-macros", "class_name" : "QtMacros", "level" : 0, "categories" : ["bug"] }, { "name" : "temporary-iterator", "level" : 0, "categories" : ["containers", "bug"], "visits_stmts" : true }, { "name" : "wrong-qglobalstatic", "class_name" : "WrongQGlobalStatic", "level" : 0, "categories" : ["performance"], "visits_stmts" : true }, { "name" : "lowercase-qml-type-name", "level" : 0, "categories" : ["qml", "bug"], "visits_stmts" : true }, { "name" : "auto-unexpected-qstringbuilder", "class_name" : "AutoUnexpectedQStringBuilder", "level" : 1, "categories" : ["bug", "qstring"], "visits_decls" : true, "visits_stmts" : true, "fixits" : [ { "name" : "auto-unexpected-qstringbuilder" } ] }, { "name" : "connect-3arg-lambda", "level" : 1, "categories" : ["bug"], "visits_stmts" : true }, { "name" : "const-signal-or-slot", "level" : 1, "categories" : ["readability", "bug"], "visits_decls" : true, "visits_stmts" : true }, { "name" : "qproperty-type-mismatch", "level" : -1, "categories" : ["bug"], "visits_decls" : true }, { "name" : "detaching-temporary", "level" : 1, "categories" : ["containers", "performance"], "visits_stmts" : true }, { "name" : "foreach", "level" : 1, "categories" : ["containers", "performance"], "visits_stmts" : true }, { "name" : "incorrect-emit", "level" : 1, "categories" : ["readability"], "visits_stmts" : true }, { "name" : "inefficient-qlist-soft", "level" : 1, "categories" : ["containers", "performance"], "visits_decls" : true }, { "name" : "install-event-filter", "level" : 1, "categories" : ["bug"], "visits_stmts" : true }, { "name" : "non-pod-global-static", "level" : 1, "categories" : ["performance"], "visits_stmts" : true }, { "name" : "post-event", "level" : 1, "categories" : ["bug"], "visits_stmts" : true }, { "name" : "qdeleteall", "class_name" : "QDeleteAll", "level" : 1, "categories" : ["containers", "performance"], "visits_stmts" : true }, { "name" : "qlatin1string-non-ascii", "level" : 1, "categories" : ["bug", "qstring"], "visits_stmts" : true }, { "name" : "qproperty-without-notify", "level" : 1, "categories" : ["bug"], "visits_stmts" : true }, { "name" : "qstring-left", "level" : 1, "categories" : ["bug", "performance", "qstring"], "visits_stmts" : true }, { "name" : "range-loop", "level" : 1, "categories" : ["containers", "performance"], "visits_stmts" : true, "fixits" : [ { "name" : "range-loop-add-ref" }, { "name" : "range-loop-add-qasconst" } ] }, { "name" : "returning-data-from-temporary", "level" : 1, "categories" : ["bug"], "visits_stmts" : true }, { "name" : "rule-of-two-soft", "level" : 1, "categories" : ["cpp", "bug"], "visits_stmts" : true }, { "name" : "child-event-qobject-cast", "level" : 1, "categories" : ["bug"], "visits_decls" : true }, { "name" : "virtual-signal", "level" : 1, "categories" : ["bug", "readability"], "visits_decls" : true }, { "name" : "overridden-signal", "level" : 1, "categories" : ["bug", "readability"], "visits_decls" : true }, { "name" : "qhash-namespace", "level" : 1, "categories" : ["bug"], "visits_decls" : true }, { "name" : "skipped-base-method", "level" : 1, "categories" : ["bug", "cpp"], "visits_stmts" : true }, { "name" : "unneeded-cast", "level" : -1, "categories" : ["cpp", "readability"], "options" : [ { "name" : "prefer-dynamic-cast-over-qobject" } ], "visits_stmts" : true }, { "name" : "ctor-missing-parent-argument", "level" : 2, "categories" : ["bug"], "visits_decls" : true }, { "name" : "base-class-event", "level" : 2, "categories" : ["bug"], "visits_decls" : true }, { "name" : "copyable-polymorphic", "level" : 2, "categories" : ["cpp", "bug"], "visits_decls" : true }, { "name" : "function-args-by-ref", "level" : 2, "categories" : ["cpp", "performance"], "options" : [ { "name" : "warn-for-overridden-methods" } ], "fixits" : [ { "name" : "function-args-by-ref" } ], "visits_decls" : true, "visits_stmts" : true }, { "name" : "function-args-by-value", "level" : 2, "categories" : ["cpp", "performance"], "options" : [ { "name" : "warn-for-overridden-methods" } ], "visits_decls" : true, "visits_stmts" : true }, { "name" : "global-const-char-pointer", "level" : 2, "categories" : ["cpp", "performance"], "visits_decls" : true }, { "name" : "implicit-casts", "level" : 2, "categories" : ["cpp", "bug"], "options" : [ { "name" : "bool-to-int" } ], "visits_stmts" : true }, { "name" : "missing-qobject-macro", "level" : 2, "categories" : ["bug"], "visits_decls" : true }, { "name" : "missing-typeinfo", "class_name" : "MissingTypeInfo", "level" : 2, "categories" : ["containers", "performance"], "visits_decls" : true }, { "name" : "old-style-connect", "level" : 2, "minimum_qt_version" : 50500, - "ifndef" : "NO_STD_REGEX", "categories" : ["performance"], "fixits" : [ { "name" : "old-style-connect" } ], "visits_stmts" : true }, { "name" : "qstring-allocations", "level" : 2, "minimum_qt_version" : 50000, "categories" : ["performance", "qstring"], "fixits" : [ { "name" : "qlatin1string-allocations" }, { "name" : "fromLatin1_fromUtf8-allocations" }, { "name" : "fromCharPtrAllocations" } ], "options" : [ { "name" : "no-msvc-compat" } ], "visits_stmts" : true }, { "name" : "returning-void-expression", "level" : 2, "categories" : ["readability", "cpp"], "visits_stmts" : true }, { "name" : "rule-of-three", "level" : 2, "categories" : ["cpp", "bug"], "visits_decls" : true }, { "name" : "virtual-call-ctor", "level" : 2, "categories" : ["cpp", "bug"], "visits_decls" : true }, { "name" : "static-pmf", "level" : 2, "categories" : ["bug"], "visits_decls" : true }, { "name" : "assert-with-side-effects", "level" : -1, "categories" : ["bug"], "visits_stmts" : true }, { "name" : "detaching-member", "level" : -1, "categories" : ["containers", "performance"], "visits_stmts" : true }, { "name" : "thread-with-slots", "level" : -1, "categories" : ["bug"], "visits_decls" : true, "visits_stmts" : true }, { "name" : "reserve-candidates", "level" : -1, "categories" : ["containers"], "visits_stmts" : true } ] } diff --git a/dev-scripts/generate.py b/dev-scripts/generate.py index fd81815..c7f9d3a 100755 --- a/dev-scripts/generate.py +++ b/dev-scripts/generate.py @@ -1,549 +1,540 @@ #!/usr/bin/env python _license_text = \ """/* This file is part of the clazy static checker. Copyright (C) 2017 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com Author: Sérgio 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. */ """ import sys, os, json, argparse, datetime, io from shutil import copyfile CHECKS_FILENAME = 'checks.json' _checks = [] _specified_check_names = [] _available_categories = [] def checkSortKey(check): return str(check.level) + check.name def level_num_to_enum(n): if n == -1: return 'ManualCheckLevel' if n >= 0 and n <= 3: return 'CheckLevel' + str(n) return 'CheckLevelUndefined' def level_num_to_name(n): if n == -1: return 'Manual Level' if n >= 0 and n <= 3: return 'Level ' + str(n) return 'undefined' def level_num_to_cmake_readme_variable(n): if n == -1: return 'README_manuallevel_FILES' if n >= 0 and n <= 3: return 'README_LEVEL%s_FILES' % str(n) return 'undefined' def clazy_source_path(): return os.path.abspath(os.path.dirname(os.path.realpath(__file__)) + "/..") + "/" def templates_path(): return clazy_source_path() + "dev-scripts/templates/" def docs_relative_path(): return "docs/checks/" def docs_path(): return clazy_source_path() + docs_relative_path() def read_file(filename): f = io.open(filename, 'r', newline='\n', encoding='utf8') contents = f.read() f.close() return contents def write_file(filename, contents): f = io.open(filename, 'w', newline='\n', encoding='utf8') f.write(contents) f.close() def get_copyright(): year = datetime.datetime.now().year author = os.getenv('GIT_AUTHOR_NAME', 'Author') email = os.getenv('GIT_AUTHOR_EMAIL', 'your@email') return "Copyright (C) %s %s <%s>" % (year, author, email) class Check: def __init__(self): self.name = "" self.class_name = "" self.level = 0 self.categories = [] self.minimum_qt_version = 40000 # Qt 4.0.0 self.fixits = [] self.visits_stmts = False self.visits_decls = False self.ifndef = "" def include(self): # Returns for example: "returning-void-expression.h" oldstyle_headername = (self.name + ".h").replace('-', '') if os.path.exists(self.path() + oldstyle_headername): return oldstyle_headername return self.name + '.h' def qualified_include(self): # Returns for example: "checks/level2/returning-void-expression.h" return self.basedir() + self.include() def qualified_cpp_filename(self): # Returns for example: "checks/level2/returning-void-expression.cpp" return self.basedir() + self.cpp_filename() def cpp_filename(self): # Returns for example: "returning-void-expression.cpp" filename = self.include() filename = filename.replace(".h", ".cpp") return filename def path(self): return clazy_source_path() + self.basedir(True) + "/" def basedir(self, with_src=False): level = 'level' + str(self.level) if self.level == -1: level = 'manuallevel' if with_src: return "src/checks/" + level + '/' return "checks/" + level + '/' def readme_name(self): return "README-" + self.name + ".md" def readme_path(self): return docs_path() + self.readme_name() def supportsQt4(self): return self.minimum_qt_version < 50000 def get_class_name(self): if self.class_name: return self.class_name # Deduce the class name splitted = self.name.split('-') classname = "" for word in splitted: if word == 'qt': word = 'Qt' else: word = word.title() if word.startswith('Q'): word = 'Q' + word[1:].title() classname += word return classname def valid_name(self): if self.name in ['clazy']: return False if self.name.startswith('level'): return False if self.name.startswith('fix'): return False return True def fixits_text(self): if not self.fixits: return "" text = "" fixitnames = [] for f in self.fixits: fixitnames.append("fix-" + f) text = ','.join(fixitnames) return "(" + text + ")" def include_guard(self): guard = self.name.replace('-', '_') return guard.upper() def load_json(filename): jsonContents = read_file(filename) decodedJson = json.loads(jsonContents) if 'checks' not in decodedJson: print("No checks found in " + filename) return False checks = decodedJson['checks'] global _available_categories, _checks, _specified_check_names if 'available_categories' in decodedJson: _available_categories = decodedJson['available_categories'] for check in checks: c = Check() try: c.name = check['name'] c.level = check['level'] if 'categories' in check: c.categories = check['categories'] for cat in c.categories: if cat not in _available_categories: print('Unknown category ' + cat) return False except KeyError: print("Missing mandatory field while processing " + str(check)) return False if _specified_check_names and c.name not in _specified_check_names: continue if 'class_name' in check: c.class_name = check['class_name'] if 'ifndef' in check: c.ifndef = check['ifndef'] if 'minimum_qt_version' in check: c.minimum_qt_version = check['minimum_qt_version'] if 'visits_stmts' in check: c.visits_stmts = check['visits_stmts'] if 'visits_decls' in check: c.visits_decls = check['visits_decls'] if 'fixits' in check: for fixit in check['fixits']: if 'name' not in fixit: print('fixit doesnt have a name. check=' + str(check)) return False c.fixits.append(fixit['name']) if not c.valid_name(): print("Invalid check name: %s" % (c.name())) return False _checks.append(c) _checks = sorted(_checks, key=checkSortKey) return True def print_checks(checks): for c in checks: print(c.name + " " + str(c.level) + " " + str(c.categories)) #------------------------------------------------------------------------------- def generate_register_checks(checks): text = '#include "checkmanager.h"\n' for c in checks: text += '#include "' + c.qualified_include() + '"\n' text += \ """ template RegisteredCheck check(const char *name, CheckLevel level, RegisteredCheck::Options options = RegisteredCheck::Option_None) { auto factoryFuntion = [name](ClazyContext *context){ return new T(name, context); }; return RegisteredCheck{name, level, factoryFuntion, options}; } void CheckManager::registerChecks() { """ for c in checks: qt4flag = "RegisteredCheck::Option_None" if not c.supportsQt4(): qt4flag = "RegisteredCheck::Option_Qt4Incompatible" if c.visits_stmts: qt4flag += " | RegisteredCheck::Option_VisitsStmts" if c.visits_decls: qt4flag += " | RegisteredCheck::Option_VisitsDecls" qt4flag = qt4flag.replace("RegisteredCheck::Option_None |", "") if c.ifndef: text += "#ifndef " + c.ifndef + "\n" text += ' registerCheck(check<%s>("%s", %s, %s));\n' % (c.get_class_name(), c.name, level_num_to_enum(c.level), qt4flag) fixitID = 1 for fixit in c.fixits: text += ' registerFixIt(%d, "%s", "%s");\n' % (fixitID, "fix-" + fixit, c.name) fixitID = fixitID * 2 if c.ifndef: text += "#endif" + "\n" text += "}\n" comment_text = \ """ /** * To add a new check you can either edit this file, or use the python script: * dev-scripts/generate.py > src/Checks.h */ """ text = _license_text + '\n' + comment_text + '\n' + text filename = clazy_source_path() + "src/Checks.h" old_text = read_file(filename) if old_text != text: write_file(filename, text) print("Generated " + filename) return True return False #------------------------------------------------------------------------------- def generate_cmake_file(checks): text = "set(CLAZY_CHECKS_SRCS ${CLAZY_CHECKS_SRCS}\n" - checks_with_regexp = [] for level in [-1, 0, 1, 2, 3]: for check in checks: if check.level == level: text += " ${CMAKE_CURRENT_LIST_DIR}/src/" + check.qualified_cpp_filename() + "\n" - if check.ifndef == "NO_STD_REGEX": - checks_with_regexp.append(check) text += ")\n" - if checks_with_regexp: - text += "\nif(HAS_STD_REGEX OR CLAZY_BUILD_WITH_CLANG)\n" - for check in checks_with_regexp: - text += " set(CLAZY_CHECKS_SRCS ${CLAZY_CHECKS_SRCS} ${CMAKE_CURRENT_LIST_DIR}/src/" + check.qualified_cpp_filename() + ")\n" - text += "endif()\n" - filename = clazy_source_path() + "CheckSources.cmake" old_text = read_file(filename) if old_text != text: write_file(filename, text) print("Generated " + filename) return True return False #------------------------------------------------------------------------------- def create_readmes(checks): generated = False for check in checks: if not os.path.exists(check.readme_path()): existing_readme = search_in_all_levels(check.readme_name()) if existing_readme: contents = read_file(existing_readme) write_file(check.readme_path(), contents) os.remove(existing_readme) print("Moved " + check.readme_name()) else: contents = read_file(templates_path() + "check-readme.md") contents = contents.replace('[check-name]', check.name) write_file(check.readme_path(), contents) print("Created " + check.readme_path()) generated = True return generated #------------------------------------------------------------------------------- def create_unittests(checks): generated = False for check in checks: unittest_folder = clazy_source_path() + "tests/" + check.name if not os.path.exists(unittest_folder): os.mkdir(unittest_folder) print("Created " + unittest_folder) generated = True configjson_file = unittest_folder + "/config.json" if not os.path.exists(configjson_file): copyfile(templates_path() + "test-config.json", configjson_file) print("Created " + configjson_file) generated = True testmain_file = unittest_folder + "/main.cpp" if not os.path.exists(testmain_file) and check.name != 'non-pod-global-static': copyfile(templates_path() + "test-main.cpp", testmain_file) print("Created " + testmain_file) generated = True return generated #------------------------------------------------------------------------------- def search_in_all_levels(filename): for level in ['manuallevel', 'level0', 'level1', 'level2']: complete_filename = clazy_source_path() + 'src/checks/' + level + '/' + filename if os.path.exists(complete_filename): return complete_filename return "" #------------------------------------------------------------------------------- def create_checks(checks): generated = False for check in checks: edit_changelog = False include_file = check.path() + check.include() cpp_file = check.path() + check.cpp_filename() copyright = get_copyright() include_missing = not os.path.exists(include_file) cpp_missing = not os.path.exists(cpp_file) if include_missing: existing_include_path = search_in_all_levels(check.include()) if existing_include_path: # File already exists, but is in another level. Just move it: contents = read_file(existing_include_path) write_file(include_file, contents) os.remove(existing_include_path) print("Moved " + check.include()) else: contents = read_file(templates_path() + 'check.h') contents = contents.replace('%1', check.include_guard()) contents = contents.replace('%2', check.get_class_name()) contents = contents.replace('%3', check.name) contents = contents.replace('%4', copyright) write_file(include_file, contents) print("Created " + include_file) edit_changelog = True generated = True if cpp_missing: existing_cpp_path = search_in_all_levels(check.cpp_filename()) if existing_cpp_path: # File already exists, but is in another level. Just move it: contents = read_file(existing_cpp_path) write_file(cpp_file, contents) os.remove(existing_cpp_path) print("Moved " + check.cpp_filename()) else: contents = read_file(templates_path() + 'check.cpp') contents = contents.replace('%1', check.include()) contents = contents.replace('%2', check.get_class_name()) contents = contents.replace('%3', copyright) write_file(cpp_file, contents) print("Created " + cpp_file) generated = True if edit_changelog: # We created a new check, let's also edit the ChangeLog changelog_file = clazy_source_path() + 'Changelog' contents = read_file(changelog_file) contents += '\n - \n' write_file(changelog_file, contents) print('Edited Changelog') return generated #------------------------------------------------------------------------------- def generate_readme(checks): filename = clazy_source_path() + "README.md" f = io.open(filename, 'r', newline='\n', encoding='utf8') old_contents = f.readlines(); f.close(); new_text_to_insert = "" for level in ['-1', '0', '1', '2']: new_text_to_insert += "- Checks from %s:" % level_num_to_name(int(level)) + "\n" for c in checks: if str(c.level) == level: fixits_text = c.fixits_text() if fixits_text: fixits_text = " " + fixits_text new_text_to_insert += " - [%s](%sREADME-%s.md)%s" % (c.name, docs_relative_path(), c.name, fixits_text) + "\n" new_text_to_insert += "\n" f = io.open(filename, 'w', newline='\n', encoding='utf8') skip = False for line in old_contents: if skip and line.startswith("#"): skip = False if skip: continue if line.startswith("- Checks from Manual Level:"): skip = True f.write(new_text_to_insert) continue f.write(line) f.close() f = io.open(filename, 'r', newline='\n', encoding='utf8') new_contents = f.readlines(); f.close(); if old_contents != new_contents: print("Generated " + filename) return True return False #------------------------------------------------------------------------------- def generate_readmes_cmake_install(checks): old_contents = "" filename = clazy_source_path() + 'readmes.cmake' if os.path.exists(filename): f = io.open(filename, 'r', newline='\n', encoding='utf8') old_contents = f.readlines(); f.close(); new_text_to_insert = "" for level in ['-1', '0', '1', '2']: new_text_to_insert += 'SET(' + level_num_to_cmake_readme_variable(int(level)) + "\n" for c in checks: if str(c.level) == level: new_text_to_insert += ' ${CMAKE_CURRENT_LIST_DIR}/docs/checks/' + c.readme_name() + '\n' new_text_to_insert += ')\n\n' if old_contents == new_text_to_insert: return False f = io.open(filename, 'w', newline='\n', encoding='utf8') f.write(new_text_to_insert) f.close() return True #------------------------------------------------------------------------------- complete_json_filename = clazy_source_path() + CHECKS_FILENAME if not os.path.exists(complete_json_filename): print("File doesn't exist: " + complete_json_filename) exit(1) parser = argparse.ArgumentParser() parser.add_argument("--generate", action='store_true', help="Generate src/Checks.h, CheckSources.cmake and README.md") parser.add_argument("checks", nargs='*', help="Optional check names to build. Useful to speedup builds during development, by building only the specified checks. Default is to build all checks.") args = parser.parse_args() _specified_check_names = args.checks if not load_json(complete_json_filename): exit(1) if args.generate: generated = False generated = generate_register_checks(_checks) or generated generated = generate_cmake_file(_checks) or generated generated = generate_readme(_checks) or generated generated = create_readmes(_checks) or generated generated = create_unittests(_checks) or generated generated = create_checks(_checks) or generated generated = generate_readmes_cmake_install(_checks) or generated if not generated: print("Nothing to do, everything is OK") else: parser.print_help(sys.stderr) diff --git a/src/Checks.h b/src/Checks.h index 4867210..b2dab47 100644 --- a/src/Checks.h +++ b/src/Checks.h @@ -1,223 +1,221 @@ /* This file is part of the clazy static checker. Copyright (C) 2017 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com Author: Sérgio 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. */ /** * To add a new check you can either edit this file, or use the python script: * dev-scripts/generate.py > src/Checks.h */ #include "checkmanager.h" #include "checks/manuallevel/assert-with-side-effects.h" #include "checks/manuallevel/container-inside-loop.h" #include "checks/manuallevel/detaching-member.h" #include "checks/manuallevel/heap-allocated-small-trivial-type.h" #include "checks/manuallevel/ifndef-define-typo.h" #include "checks/manuallevel/inefficient-qlist.h" #include "checks/manuallevel/isempty-vs-count.h" #include "checks/manuallevel/qhash-with-char-pointer-key.h" #include "checks/manuallevel/qproperty-type-mismatch.h" #include "checks/manuallevel/qrequiredresult-candidates.h" #include "checks/manuallevel/qstring-varargs.h" #include "checks/manuallevel/qt-keywords.h" #include "checks/manuallevel/qt4-qstring-from-array.h" #include "checks/manuallevel/qvariant-template-instantiation.h" #include "checks/manuallevel/raw-environment-function.h" #include "checks/manuallevel/reserve-candidates.h" #include "checks/manuallevel/signal-with-return-value.h" #include "checks/manuallevel/thread-with-slots.h" #include "checks/manuallevel/tr-non-literal.h" #include "checks/manuallevel/unneeded-cast.h" #include "checks/level0/connect-by-name.h" #include "checks/level0/connect-non-signal.h" #include "checks/level0/connect-not-normalized.h" #include "checks/level0/container-anti-pattern.h" #include "checks/level0/empty-qstringliteral.h" #include "checks/level0/fully-qualified-moc-types.h" #include "checks/level0/lambda-in-connect.h" #include "checks/level0/lambda-unique-connection.h" #include "checks/level0/lowercase-qml-type-name.h" #include "checks/level0/mutable-container-key.h" #include "checks/level0/qcolor-from-literal.h" #include "checks/level0/qdatetime-utc.h" #include "checks/level0/qenums.h" #include "checks/level0/qfileinfo-exists.h" #include "checks/level0/qgetenv.h" #include "checks/level0/qmap-with-pointer-key.h" #include "checks/level0/qstring-arg.h" #include "checks/level0/qstring-insensitive-allocation.h" #include "checks/level0/qstring-ref.h" #include "checks/level0/qt-macros.h" #include "checks/level0/strict-iterators.h" #include "checks/level0/temporary-iterator.h" #include "checks/level0/unused-non-trivial-variable.h" #include "checks/level0/writing-to-temporary.h" #include "checks/level0/wrong-qevent-cast.h" #include "checks/level0/wrong-qglobalstatic.h" #include "checks/level1/auto-unexpected-qstringbuilder.h" #include "checks/level1/child-event-qobject-cast.h" #include "checks/level1/connect-3arg-lambda.h" #include "checks/level1/const-signal-or-slot.h" #include "checks/level1/detaching-temporary.h" #include "checks/level1/foreach.h" #include "checks/level1/incorrect-emit.h" #include "checks/level1/inefficient-qlist-soft.h" #include "checks/level1/install-event-filter.h" #include "checks/level1/non-pod-global-static.h" #include "checks/level1/overridden-signal.h" #include "checks/level1/post-event.h" #include "checks/level1/qdeleteall.h" #include "checks/level1/qhash-namespace.h" #include "checks/level1/qlatin1string-non-ascii.h" #include "checks/level1/qproperty-without-notify.h" #include "checks/level1/qstring-left.h" #include "checks/level1/range-loop.h" #include "checks/level1/returning-data-from-temporary.h" #include "checks/level1/rule-of-two-soft.h" #include "checks/level1/skipped-base-method.h" #include "checks/level1/virtual-signal.h" #include "checks/level2/base-class-event.h" #include "checks/level2/copyable-polymorphic.h" #include "checks/level2/ctor-missing-parent-argument.h" #include "checks/level2/function-args-by-ref.h" #include "checks/level2/function-args-by-value.h" #include "checks/level2/global-const-char-pointer.h" #include "checks/level2/implicit-casts.h" #include "checks/level2/missing-qobject-macro.h" #include "checks/level2/missing-typeinfo.h" #include "checks/level2/old-style-connect.h" #include "checks/level2/qstring-allocations.h" #include "checks/level2/returning-void-expression.h" #include "checks/level2/rule-of-three.h" #include "checks/level2/static-pmf.h" #include "checks/level2/virtual-call-ctor.h" template RegisteredCheck check(const char *name, CheckLevel level, RegisteredCheck::Options options = RegisteredCheck::Option_None) { auto factoryFuntion = [name](ClazyContext *context){ return new T(name, context); }; return RegisteredCheck{name, level, factoryFuntion, options}; } void CheckManager::registerChecks() { registerCheck(check("assert-with-side-effects", ManualCheckLevel, RegisteredCheck::Option_VisitsStmts)); registerCheck(check("container-inside-loop", ManualCheckLevel, RegisteredCheck::Option_VisitsStmts)); registerCheck(check("detaching-member", ManualCheckLevel, RegisteredCheck::Option_VisitsStmts)); registerCheck(check("heap-allocated-small-trivial-type", ManualCheckLevel, RegisteredCheck::Option_VisitsStmts)); registerCheck(check("ifndef-define-typo", ManualCheckLevel, RegisteredCheck::Option_None)); registerCheck(check("inefficient-qlist", ManualCheckLevel, RegisteredCheck::Option_VisitsDecls)); registerCheck(check("isempty-vs-count", ManualCheckLevel, RegisteredCheck::Option_VisitsStmts)); registerCheck(check("qhash-with-char-pointer-key", ManualCheckLevel, RegisteredCheck::Option_VisitsDecls)); registerCheck(check("qproperty-type-mismatch", ManualCheckLevel, RegisteredCheck::Option_VisitsDecls)); registerCheck(check("qrequiredresult-candidates", ManualCheckLevel, RegisteredCheck::Option_VisitsDecls)); registerCheck(check("qstring-varargs", ManualCheckLevel, RegisteredCheck::Option_VisitsStmts)); registerCheck(check("qt-keywords", ManualCheckLevel, RegisteredCheck::Option_None)); registerFixIt(1, "fix-qt-keywords", "qt-keywords"); registerCheck(check("qt4-qstring-from-array", ManualCheckLevel, RegisteredCheck::Option_VisitsStmts)); registerFixIt(1, "fix-qt4-qstring-from-array", "qt4-qstring-from-array"); registerCheck(check("qvariant-template-instantiation", ManualCheckLevel, RegisteredCheck::Option_VisitsStmts)); registerCheck(check("raw-environment-function", ManualCheckLevel, RegisteredCheck::Option_VisitsStmts)); registerCheck(check("reserve-candidates", ManualCheckLevel, RegisteredCheck::Option_VisitsStmts)); registerCheck(check("signal-with-return-value", ManualCheckLevel, RegisteredCheck::Option_VisitsDecls)); registerCheck(check("thread-with-slots", ManualCheckLevel, RegisteredCheck::Option_VisitsStmts | RegisteredCheck::Option_VisitsDecls)); registerCheck(check("tr-non-literal", ManualCheckLevel, RegisteredCheck::Option_VisitsStmts)); registerCheck(check("unneeded-cast", ManualCheckLevel, RegisteredCheck::Option_VisitsStmts)); registerCheck(check("connect-by-name", CheckLevel0, RegisteredCheck::Option_VisitsDecls)); registerCheck(check("connect-non-signal", CheckLevel0, RegisteredCheck::Option_Qt4Incompatible | RegisteredCheck::Option_VisitsStmts)); registerCheck(check("connect-not-normalized", CheckLevel0, RegisteredCheck::Option_VisitsStmts)); registerCheck(check("container-anti-pattern", CheckLevel0, RegisteredCheck::Option_VisitsStmts)); registerCheck(check("empty-qstringliteral", CheckLevel0, RegisteredCheck::Option_VisitsStmts)); registerCheck(check("fully-qualified-moc-types", CheckLevel0, RegisteredCheck::Option_VisitsDecls)); registerCheck(check("lambda-in-connect", CheckLevel0, RegisteredCheck::Option_VisitsStmts)); registerCheck(check("lambda-unique-connection", CheckLevel0, RegisteredCheck::Option_VisitsStmts)); registerCheck(check("lowercase-qml-type-name", CheckLevel0, RegisteredCheck::Option_VisitsStmts)); registerCheck(check("mutable-container-key", CheckLevel0, RegisteredCheck::Option_VisitsDecls)); #ifndef CLAZY_DISABLE_AST_MATCHERS registerCheck(check("qcolor-from-literal", CheckLevel0, RegisteredCheck::Option_VisitsStmts)); #endif registerCheck(check("qdatetime-utc", CheckLevel0, RegisteredCheck::Option_VisitsStmts)); registerFixIt(1, "fix-qdatetime-utc", "qdatetime-utc"); registerCheck(check("qenums", CheckLevel0, RegisteredCheck::Option_Qt4Incompatible)); registerCheck(check("qfileinfo-exists", CheckLevel0, RegisteredCheck::Option_VisitsStmts)); registerCheck(check("qgetenv", CheckLevel0, RegisteredCheck::Option_Qt4Incompatible | RegisteredCheck::Option_VisitsStmts)); registerFixIt(1, "fix-qgetenv", "qgetenv"); registerCheck(check("qmap-with-pointer-key", CheckLevel0, RegisteredCheck::Option_VisitsDecls)); registerCheck(check("qstring-arg", CheckLevel0, RegisteredCheck::Option_VisitsStmts)); registerCheck(check("qstring-insensitive-allocation", CheckLevel0, RegisteredCheck::Option_VisitsStmts)); registerCheck(check("qstring-ref", CheckLevel0, RegisteredCheck::Option_VisitsStmts)); registerFixIt(1, "fix-missing-qstringref", "qstring-ref"); registerCheck(check("qt-macros", CheckLevel0, RegisteredCheck::Option_None)); registerCheck(check("strict-iterators", CheckLevel0, RegisteredCheck::Option_VisitsStmts)); registerCheck(check("temporary-iterator", CheckLevel0, RegisteredCheck::Option_VisitsStmts)); registerCheck(check("unused-non-trivial-variable", CheckLevel0, RegisteredCheck::Option_VisitsStmts)); registerCheck(check("writing-to-temporary", CheckLevel0, RegisteredCheck::Option_VisitsStmts)); registerCheck(check("wrong-qevent-cast", CheckLevel0, RegisteredCheck::Option_VisitsStmts)); registerCheck(check("wrong-qglobalstatic", CheckLevel0, RegisteredCheck::Option_VisitsStmts)); registerCheck(check("auto-unexpected-qstringbuilder", CheckLevel1, RegisteredCheck::Option_VisitsStmts | RegisteredCheck::Option_VisitsDecls)); registerFixIt(1, "fix-auto-unexpected-qstringbuilder", "auto-unexpected-qstringbuilder"); registerCheck(check("child-event-qobject-cast", CheckLevel1, RegisteredCheck::Option_VisitsDecls)); registerCheck(check("connect-3arg-lambda", CheckLevel1, RegisteredCheck::Option_VisitsStmts)); registerCheck(check("const-signal-or-slot", CheckLevel1, RegisteredCheck::Option_VisitsStmts | RegisteredCheck::Option_VisitsDecls)); registerCheck(check("detaching-temporary", CheckLevel1, RegisteredCheck::Option_VisitsStmts)); registerCheck(check("foreach", CheckLevel1, RegisteredCheck::Option_VisitsStmts)); registerCheck(check("incorrect-emit", CheckLevel1, RegisteredCheck::Option_VisitsStmts)); registerCheck(check("inefficient-qlist-soft", CheckLevel1, RegisteredCheck::Option_VisitsDecls)); registerCheck(check("install-event-filter", CheckLevel1, RegisteredCheck::Option_VisitsStmts)); registerCheck(check("non-pod-global-static", CheckLevel1, RegisteredCheck::Option_VisitsStmts)); registerCheck(check("overridden-signal", CheckLevel1, RegisteredCheck::Option_VisitsDecls)); registerCheck(check("post-event", CheckLevel1, RegisteredCheck::Option_VisitsStmts)); registerCheck(check("qdeleteall", CheckLevel1, RegisteredCheck::Option_VisitsStmts)); registerCheck(check("qhash-namespace", CheckLevel1, RegisteredCheck::Option_VisitsDecls)); registerCheck(check("qlatin1string-non-ascii", CheckLevel1, RegisteredCheck::Option_VisitsStmts)); registerCheck(check("qproperty-without-notify", CheckLevel1, RegisteredCheck::Option_VisitsStmts)); registerCheck(check("qstring-left", CheckLevel1, RegisteredCheck::Option_VisitsStmts)); registerCheck(check("range-loop", CheckLevel1, RegisteredCheck::Option_VisitsStmts)); registerFixIt(1, "fix-range-loop-add-ref", "range-loop"); registerFixIt(2, "fix-range-loop-add-qasconst", "range-loop"); registerCheck(check("returning-data-from-temporary", CheckLevel1, RegisteredCheck::Option_VisitsStmts)); registerCheck(check("rule-of-two-soft", CheckLevel1, RegisteredCheck::Option_VisitsStmts)); registerCheck(check("skipped-base-method", CheckLevel1, RegisteredCheck::Option_VisitsStmts)); registerCheck(check("virtual-signal", CheckLevel1, RegisteredCheck::Option_VisitsDecls)); registerCheck(check("base-class-event", CheckLevel2, RegisteredCheck::Option_VisitsDecls)); registerCheck(check("copyable-polymorphic", CheckLevel2, RegisteredCheck::Option_VisitsDecls)); registerCheck(check("ctor-missing-parent-argument", CheckLevel2, RegisteredCheck::Option_VisitsDecls)); registerCheck(check("function-args-by-ref", CheckLevel2, RegisteredCheck::Option_VisitsStmts | RegisteredCheck::Option_VisitsDecls)); registerFixIt(1, "fix-function-args-by-ref", "function-args-by-ref"); registerCheck(check("function-args-by-value", CheckLevel2, RegisteredCheck::Option_VisitsStmts | RegisteredCheck::Option_VisitsDecls)); registerCheck(check("global-const-char-pointer", CheckLevel2, RegisteredCheck::Option_VisitsDecls)); registerCheck(check("implicit-casts", CheckLevel2, RegisteredCheck::Option_VisitsStmts)); registerCheck(check("missing-qobject-macro", CheckLevel2, RegisteredCheck::Option_VisitsDecls)); registerCheck(check("missing-typeinfo", CheckLevel2, RegisteredCheck::Option_VisitsDecls)); -#ifndef NO_STD_REGEX registerCheck(check("old-style-connect", CheckLevel2, RegisteredCheck::Option_Qt4Incompatible | RegisteredCheck::Option_VisitsStmts)); registerFixIt(1, "fix-old-style-connect", "old-style-connect"); -#endif registerCheck(check("qstring-allocations", CheckLevel2, RegisteredCheck::Option_Qt4Incompatible | RegisteredCheck::Option_VisitsStmts)); registerFixIt(1, "fix-qlatin1string-allocations", "qstring-allocations"); registerFixIt(2, "fix-fromLatin1_fromUtf8-allocations", "qstring-allocations"); registerFixIt(4, "fix-fromCharPtrAllocations", "qstring-allocations"); registerCheck(check("returning-void-expression", CheckLevel2, RegisteredCheck::Option_VisitsStmts)); registerCheck(check("rule-of-three", CheckLevel2, RegisteredCheck::Option_VisitsDecls)); registerCheck(check("static-pmf", CheckLevel2, RegisteredCheck::Option_VisitsDecls)); registerCheck(check("virtual-call-ctor", CheckLevel2, RegisteredCheck::Option_VisitsDecls)); } diff --git a/src/SourceCompatibilityHelpers.h b/src/SourceCompatibilityHelpers.h index 3397e32..4db141a 100644 --- a/src/SourceCompatibilityHelpers.h +++ b/src/SourceCompatibilityHelpers.h @@ -1,97 +1,108 @@ /* 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. */ #ifndef SOURCE_COMPAT_HELPERS #define SOURCE_COMPAT_HELPERS #include #include #include #include #include #include #include +#if defined(CLAZY_USES_BOOST_REGEX) +# define BOOST_NO_EXCEPTIONS +# include +inline void boost::throw_exception(std::exception const &){} +# include +using namespace boost; +#else +# include +using namespace std; +#endif + namespace clazy { template inline clang::SourceLocation getLocStart(const T *t) { #if LLVM_VERSION_MAJOR >= 8 return t->getBeginLoc(); #else return t->getLocStart(); #endif } template inline clang::SourceLocation getLocEnd(const T *t) { #if LLVM_VERSION_MAJOR >= 8 return t->getEndLoc(); #else return t->getLocEnd(); #endif } inline clang::CharSourceRange getImmediateExpansionRange(clang::SourceLocation macroLoc, const clang::SourceManager &sm) { #if LLVM_VERSION_MAJOR >= 7 return sm.getImmediateExpansionRange(macroLoc); #else auto pair = sm.getImmediateExpansionRange(macroLoc); return clang::CharSourceRange(clang::SourceRange(pair.first, pair.second), false); #endif } inline bool hasUnusedResultAttr(clang::FunctionDecl *func) { #if LLVM_VERSION_MAJOR >= 8 auto RetType = func->getReturnType(); if (const auto *Ret = RetType->getAsRecordDecl()) { if (const auto *R = Ret->getAttr()) return R != nullptr; } else if (const auto *ET = RetType->getAs()) { if (const clang::EnumDecl *ED = ET->getDecl()) { if (const auto *R = ED->getAttr()) return R != nullptr; } } return func->getAttr() != nullptr; #else return func->hasUnusedResultAttr(); #endif } inline clang::tooling::Replacements& DiagnosticFix(clang::tooling::Diagnostic &diag, llvm::StringRef filePath) { #if LLVM_VERSION_MAJOR >= 9 return diag.Message.Fix[filePath]; #else return diag.Fix[filePath]; #endif } } #endif diff --git a/src/SuppressionManager.cpp b/src/SuppressionManager.cpp index ff7753c..9d774ea 100644 --- a/src/SuppressionManager.cpp +++ b/src/SuppressionManager.cpp @@ -1,141 +1,131 @@ /* This file is part of the clazy static checker. Copyright (C) 2016 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 "SuppressionManager.h" +#include "SourceCompatibilityHelpers.h" #include "clazy_stl.h" #include #include #include #include #include #include #include #include -#ifndef NO_STD_REGEX -# include -#endif - using namespace clang; using namespace std; SuppressionManager::SuppressionManager() { } bool SuppressionManager::isSuppressed(const std::string &checkName, clang::SourceLocation loc, const clang::SourceManager &sm, const clang::LangOptions &lo) const { -#ifdef NO_STD_REGEX - return false; -#endif - if (loc.isMacroID()) loc = sm.getExpansionLoc(loc); FileID fileID = sm.getFileID(loc); if (fileID.isInvalid()) return false; auto it = m_processedFileIDs.find(fileID.getHashValue()); const bool notProcessedYet = (it == m_processedFileIDs.cend()); if (notProcessedYet) { parseFile(fileID, sm, lo); it = m_processedFileIDs.find(fileID.getHashValue()); } Suppressions &suppressions = (*it).second; // Case 1: clazy:skip, the whole file is skipped, regardless of which check if (suppressions.skipEntireFile) return true; // Case 2: clazy:excludeall=foo, the check foo will be ignored for this file const bool checkIsSuppressed = suppressions.checksToSkip.find(checkName) != suppressions.checksToSkip.cend(); if (checkIsSuppressed) return true; // Case 3: clazy:exclude=foo, the check foo will be ignored for this file in this line number if (loc.isInvalid()) return false; const int lineNumber = sm.getSpellingLineNumber(loc); const bool checkIsSuppressedByLine = suppressions.checksToSkipByLine.find(LineAndCheckName(lineNumber, checkName)) != suppressions.checksToSkipByLine.cend(); return checkIsSuppressedByLine; } void SuppressionManager::parseFile(FileID id, const SourceManager &sm, const clang::LangOptions &lo) const { -#ifndef NO_STD_REGEX const unsigned hash = id.getHashValue(); auto it = m_processedFileIDs.insert({hash, Suppressions()}).first; Suppressions &suppressions = (*it).second; bool invalid = false; auto buffer = sm.getBuffer(id, &invalid); if (invalid) { llvm::errs() << "SuppressionManager::parseFile: Invalid buffer "; if (buffer) llvm::errs() << buffer->getBuffer() << "\n"; return; } Lexer lexer(id, buffer, sm, lo); lexer.SetCommentRetentionState(true); Token token; while (!lexer.LexFromRawLexer(token)) { if (token.getKind() == tok::comment) { std::string comment = Lexer::getSpelling(token, sm, lo); if (clazy::contains(comment, "clazy:skip")) { suppressions.skipEntireFile = true; return; } static regex rx(R"(clazy:excludeall=(.*?)(\s|$))"); smatch match; if (regex_search(comment, match, rx) && match.size() > 1) { vector checks = clazy::splitString(match[1], ','); suppressions.checksToSkip.insert(checks.cbegin(), checks.cend()); } - const int lineNumber = sm.getSpellingLineNumber(token.getLocation()); if (lineNumber < 0) { llvm::errs() << "SuppressionManager::parseFile: Invalid line number " << lineNumber << "\n"; continue; } static regex rx2(R"(clazy:exclude=(.*?)(\s|$))"); if (regex_search(comment, match, rx2) && match.size() > 1) { vector checks = clazy::splitString(match[1], ','); for (const string &checkName : checks) { suppressions.checksToSkipByLine.insert(LineAndCheckName(lineNumber, checkName)); } } } } -#endif } diff --git a/src/checks/level2/old-style-connect.cpp b/src/checks/level2/old-style-connect.cpp index 0fe68c1..235890f 100644 --- a/src/checks/level2/old-style-connect.cpp +++ b/src/checks/level2/old-style-connect.cpp @@ -1,509 +1,507 @@ /* 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 (!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; }