diff options
Diffstat (limited to 'Utilities/ClangTidyModule')
38 files changed, 1970 insertions, 0 deletions
diff --git a/Utilities/ClangTidyModule/CMakeLists.txt b/Utilities/ClangTidyModule/CMakeLists.txt new file mode 100644 index 0000000000..97c176f635 --- /dev/null +++ b/Utilities/ClangTidyModule/CMakeLists.txt @@ -0,0 +1,37 @@ +# Distributed under the OSI-approved BSD 3-Clause License. See accompanying +# file Copyright.txt or https://cmake.org/licensing for details. +cmake_minimum_required(VERSION 3.13) +project(CMakeClangTidyModule C CXX) + +get_filename_component(tmp "${CMAKE_CURRENT_SOURCE_DIR}" PATH) +get_filename_component(CMake_SOURCE_DIR "${tmp}" PATH) + +set(CMAKE_CXX_STANDARD 14) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +find_package(Clang REQUIRED) + +add_library(cmake-clang-tidy-module MODULE + Module.cxx + + OstringstreamUseCmstrcatCheck.cxx + OstringstreamUseCmstrcatCheck.h + StringConcatenationUseCmstrcatCheck.cxx + StringConcatenationUseCmstrcatCheck.h + UseBespokeEnumClassCheck.cxx + UseBespokeEnumClassCheck.h + UseCmstrlenCheck.cxx + UseCmstrlenCheck.h + UseCmsysFstreamCheck.cxx + UseCmsysFstreamCheck.h + UsePragmaOnceCheck.cxx + UsePragmaOnceCheck.h + ) +target_include_directories(cmake-clang-tidy-module PRIVATE ${CLANG_INCLUDE_DIRS}) +target_link_libraries(cmake-clang-tidy-module PRIVATE clang-tidy) + +option(RUN_TESTS "Run the tests for the clang-tidy module" OFF) +if(RUN_TESTS) + enable_testing() + add_subdirectory(Tests) +endif() diff --git a/Utilities/ClangTidyModule/Module.cxx b/Utilities/ClangTidyModule/Module.cxx new file mode 100644 index 0000000000..4dd7dcd007 --- /dev/null +++ b/Utilities/ClangTidyModule/Module.cxx @@ -0,0 +1,38 @@ +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file Copyright.txt or https://cmake.org/licensing for details. */ +#include <clang-tidy/ClangTidyModule.h> +#include <clang-tidy/ClangTidyModuleRegistry.h> + +#include "OstringstreamUseCmstrcatCheck.h" +#include "StringConcatenationUseCmstrcatCheck.h" +#include "UseBespokeEnumClassCheck.h" +#include "UseCmstrlenCheck.h" +#include "UseCmsysFstreamCheck.h" +#include "UsePragmaOnceCheck.h" + +namespace clang { +namespace tidy { +namespace cmake { +class CMakeClangTidyModule : public ClangTidyModule +{ +public: + void addCheckFactories(ClangTidyCheckFactories& CheckFactories) override + { + CheckFactories.registerCheck<UseCmstrlenCheck>("cmake-use-cmstrlen"); + CheckFactories.registerCheck<UseCmsysFstreamCheck>( + "cmake-use-cmsys-fstream"); + CheckFactories.registerCheck<UseBespokeEnumClassCheck>( + "cmake-use-bespoke-enum-class"); + CheckFactories.registerCheck<OstringstreamUseCmstrcatCheck>( + "cmake-ostringstream-use-cmstrcat"); + CheckFactories.registerCheck<UsePragmaOnceCheck>("cmake-use-pragma-once"); + CheckFactories.registerCheck<StringConcatenationUseCmstrcatCheck>( + "cmake-string-concatenation-use-cmstrcat"); + } +}; + +static ClangTidyModuleRegistry::Add<CMakeClangTidyModule> X( + "cmake-clang-tidy", "Adds lint checks for the CMake code base."); +} +} +} diff --git a/Utilities/ClangTidyModule/OstringstreamUseCmstrcatCheck.cxx b/Utilities/ClangTidyModule/OstringstreamUseCmstrcatCheck.cxx new file mode 100644 index 0000000000..920fdf3a90 --- /dev/null +++ b/Utilities/ClangTidyModule/OstringstreamUseCmstrcatCheck.cxx @@ -0,0 +1,52 @@ +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file Copyright.txt or https://cmake.org/licensing for details. */ +#include "OstringstreamUseCmstrcatCheck.h" + +#include <clang/AST/Type.h> +#include <clang/ASTMatchers/ASTMatchFinder.h> + +namespace clang { +namespace tidy { +namespace cmake { +using namespace ast_matchers; + +OstringstreamUseCmstrcatCheck::OstringstreamUseCmstrcatCheck( + StringRef Name, ClangTidyContext* Context) + : ClangTidyCheck(Name, Context) +{ +} + +void OstringstreamUseCmstrcatCheck::registerMatchers(MatchFinder* Finder) +{ + Finder->addMatcher( + typeLoc(unless(elaboratedTypeLoc()), + optionally(hasParent(elaboratedTypeLoc().bind("parentType"))), + loc(qualType( + hasDeclaration(namedDecl(hasName("::std::ostringstream")))))) + .bind("ostringstream"), + this); +} + +void OstringstreamUseCmstrcatCheck::check( + const MatchFinder::MatchResult& Result) +{ + const TypeLoc* ParentTypeNode = + Result.Nodes.getNodeAs<TypeLoc>("parentType"); + const TypeLoc* RootNode = Result.Nodes.getNodeAs<TypeLoc>("ostringstream"); + + if (ParentTypeNode != nullptr) { + if (ParentTypeNode->getBeginLoc().isValid()) { + this->diag(ParentTypeNode->getBeginLoc(), + "use strings and cmStrCat() instead of std::ostringstream"); + } + + } else if (RootNode != nullptr) { + if (RootNode->getBeginLoc().isValid()) { + this->diag(RootNode->getBeginLoc(), + "use strings and cmStrCat() instead of std::ostringstream"); + } + } +} +} +} +} diff --git a/Utilities/ClangTidyModule/OstringstreamUseCmstrcatCheck.h b/Utilities/ClangTidyModule/OstringstreamUseCmstrcatCheck.h new file mode 100644 index 0000000000..ecb5616cfc --- /dev/null +++ b/Utilities/ClangTidyModule/OstringstreamUseCmstrcatCheck.h @@ -0,0 +1,21 @@ +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file Copyright.txt or https://cmake.org/licensing for details. */ +#pragma once + +#include <clang-tidy/ClangTidyCheck.h> +#include <clang/ASTMatchers/ASTMatchFinder.h> + +namespace clang { +namespace tidy { +namespace cmake { +class OstringstreamUseCmstrcatCheck : public ClangTidyCheck +{ +public: + OstringstreamUseCmstrcatCheck(StringRef Name, ClangTidyContext* Context); + void registerMatchers(ast_matchers::MatchFinder* Finder) override; + + void check(const ast_matchers::MatchFinder::MatchResult& Result) override; +}; +} +} +} diff --git a/Utilities/ClangTidyModule/StringConcatenationUseCmstrcatCheck.cxx b/Utilities/ClangTidyModule/StringConcatenationUseCmstrcatCheck.cxx new file mode 100644 index 0000000000..e282d23d9b --- /dev/null +++ b/Utilities/ClangTidyModule/StringConcatenationUseCmstrcatCheck.cxx @@ -0,0 +1,180 @@ +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file Copyright.txt or https://cmake.org/licensing for details. */ +#include "StringConcatenationUseCmstrcatCheck.h" + +#include <cassert> + +#include <clang/ASTMatchers/ASTMatchFinder.h> +#include <clang/Lex/Lexer.h> + +namespace clang { +namespace tidy { +namespace cmake { +using namespace ast_matchers; + +StringConcatenationUseCmstrcatCheck::StringConcatenationUseCmstrcatCheck( + StringRef Name, ClangTidyContext* Context) + : ClangTidyCheck(Name, Context) +{ +} + +void StringConcatenationUseCmstrcatCheck::registerMatchers(MatchFinder* Finder) +{ + auto IsString = expr(hasType(qualType(hasUnqualifiedDesugaredType( + recordType(hasDeclaration(classTemplateSpecializationDecl( + hasName("::std::basic_string"), + hasTemplateArgument( + 0, templateArgument(refersToType(asString("char"))))))))))); + + auto IsChar = expr(hasType(asString("char"))); + + auto IsCharPtr = expr(hasType(pointerType(pointee(asString("const char"))))); + + auto IsStringConcat = + cxxOperatorCallExpr(hasOperatorName("+"), + anyOf(allOf(hasLHS(IsString), hasRHS(IsString)), + allOf(hasLHS(IsString), hasRHS(IsChar)), + allOf(hasLHS(IsString), hasRHS(IsCharPtr)), + allOf(hasLHS(IsChar), hasRHS(IsString)), + allOf(hasLHS(IsCharPtr), hasRHS(IsString)))); + + auto IsStringAppend = cxxOperatorCallExpr( + hasOperatorName("+="), hasLHS(IsString), + anyOf(hasRHS(IsString), hasRHS(IsChar), hasRHS(IsCharPtr))); + + auto IsStringConcatWithLHS = + cxxOperatorCallExpr( + IsStringConcat, + optionally(hasLHS(materializeTemporaryExpr( + has(cxxBindTemporaryExpr(has(IsStringConcat.bind("lhs")))))))) + .bind("concat"); + + auto IsStringAppendWithRHS = + cxxOperatorCallExpr( + IsStringAppend, + optionally(hasRHS(materializeTemporaryExpr(has(implicitCastExpr( + has(cxxBindTemporaryExpr(has(IsStringConcat.bind("rhs")))))))))) + .bind("append"); + + Finder->addMatcher(IsStringConcatWithLHS, this); + Finder->addMatcher(IsStringAppendWithRHS, this); +} + +void StringConcatenationUseCmstrcatCheck::check( + const MatchFinder::MatchResult& Result) +{ + const CXXOperatorCallExpr* AppendNode = + Result.Nodes.getNodeAs<CXXOperatorCallExpr>("append"); + const CXXOperatorCallExpr* ConcatNode = + Result.Nodes.getNodeAs<CXXOperatorCallExpr>("concat"); + + if (AppendNode != nullptr) { + if (AppendNode->getBeginLoc().isValid()) { + assert(InProgressExprChains.find(AppendNode) == + InProgressExprChains.end()); + + ExprChain TmpExprChain = + std::make_pair(OperatorType::PlusEquals, + std::vector<const CXXOperatorCallExpr*>{ AppendNode }); + const CXXOperatorCallExpr* RHSNode = + Result.Nodes.getNodeAs<CXXOperatorCallExpr>("rhs"); + + if (RHSNode != nullptr) { + if (RHSNode->getBeginLoc().isValid()) { + InProgressExprChains[RHSNode] = std::move(TmpExprChain); + } + } else { + issueCorrection(TmpExprChain, Result); + } + } + } + + if (ConcatNode != nullptr) { + if (ConcatNode->getBeginLoc().isValid()) { + ExprChain TmpExprChain; + + if (!(InProgressExprChains.find(ConcatNode) == + InProgressExprChains.end())) { + TmpExprChain = std::move(InProgressExprChains[ConcatNode]); + InProgressExprChains.erase(ConcatNode); + if (TmpExprChain.first == OperatorType::PlusEquals) { + TmpExprChain.second.insert(TmpExprChain.second.begin() + 1, + ConcatNode); + } else { + TmpExprChain.second.insert(TmpExprChain.second.begin(), ConcatNode); + } + } else { + TmpExprChain = std::make_pair( + OperatorType::Plus, + std::vector<const CXXOperatorCallExpr*>{ ConcatNode }); + } + + const CXXOperatorCallExpr* LHSNode = + Result.Nodes.getNodeAs<CXXOperatorCallExpr>("lhs"); + + if (LHSNode != nullptr) { + if (LHSNode->getBeginLoc().isValid()) { + InProgressExprChains[LHSNode] = std::move(TmpExprChain); + } + } else { + issueCorrection(TmpExprChain, Result); + } + } + } +} + +void StringConcatenationUseCmstrcatCheck::issueCorrection( + const ExprChain& Chain, const MatchFinder::MatchResult& Result) +{ + std::vector<FixItHint> FixIts; + const CXXOperatorCallExpr* ExprNode; + std::vector<const clang::CXXOperatorCallExpr*>::const_iterator It = + Chain.second.begin(); + + if (Chain.first == OperatorType::PlusEquals) { + ExprNode = *It; + StringRef LHS = Lexer::getSourceText( + CharSourceRange::getTokenRange(ExprNode->getArg(0)->getSourceRange()), + Result.Context->getSourceManager(), Result.Context->getLangOpts()); + + FixIts.push_back(FixItHint::CreateReplacement( + ExprNode->getExprLoc(), "= cmStrCat(" + LHS.str() + ",")); + It++; + } else { + ExprNode = *It; + FixIts.push_back( + FixItHint::CreateInsertion(ExprNode->getBeginLoc(), "cmStrCat(")); + } + + while (It != std::end(Chain.second)) { + ExprNode = *It; + FixIts.push_back( + FixItHint::CreateReplacement(ExprNode->getOperatorLoc(), ",")); + It++; + } + It--; + ExprNode = *It; + + StringRef LastToken = Lexer::getSourceText( + CharSourceRange::getTokenRange( + ExprNode->getArg(1)->getSourceRange().getEnd()), + Result.Context->getSourceManager(), Result.Context->getLangOpts()); + FixIts.push_back(FixItHint::CreateInsertion( + ExprNode->getEndLoc().getLocWithOffset(LastToken.str().size()), ")")); + + It = Chain.second.begin(); + ExprNode = *It; + + if (Chain.first == OperatorType::PlusEquals) { + this->diag(ExprNode->getOperatorLoc(), + "use cmStrCat() instead of string append") + << FixIts; + } else { + this->diag(ExprNode->getBeginLoc(), + "use cmStrCat() instead of string concatenation") + << FixIts; + } +} +} +} +} diff --git a/Utilities/ClangTidyModule/StringConcatenationUseCmstrcatCheck.h b/Utilities/ClangTidyModule/StringConcatenationUseCmstrcatCheck.h new file mode 100644 index 0000000000..43ff539c7a --- /dev/null +++ b/Utilities/ClangTidyModule/StringConcatenationUseCmstrcatCheck.h @@ -0,0 +1,34 @@ +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file Copyright.txt or https://cmake.org/licensing for details. */ +#pragma once + +#include <clang-tidy/ClangTidyCheck.h> +#include <clang/ASTMatchers/ASTMatchFinder.h> + +namespace clang { +namespace tidy { +namespace cmake { +class StringConcatenationUseCmstrcatCheck : public ClangTidyCheck +{ +public: + StringConcatenationUseCmstrcatCheck(StringRef Name, + ClangTidyContext* Context); + void registerMatchers(ast_matchers::MatchFinder* Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult& Result) override; + +private: + enum class OperatorType + { + Plus, + PlusEquals + }; + typedef std::pair<OperatorType, std::vector<const CXXOperatorCallExpr*>> + ExprChain; + std::map<const CXXOperatorCallExpr*, ExprChain> InProgressExprChains; + + void issueCorrection(const ExprChain& ExprChain, + const ast_matchers::MatchFinder::MatchResult& Result); +}; +} +} +} diff --git a/Utilities/ClangTidyModule/Tests/CMakeLists.txt b/Utilities/ClangTidyModule/Tests/CMakeLists.txt new file mode 100644 index 0000000000..8220f39e1d --- /dev/null +++ b/Utilities/ClangTidyModule/Tests/CMakeLists.txt @@ -0,0 +1,18 @@ +configure_file("${CMake_SOURCE_DIR}/.clang-format" ".clang-format" COPYONLY) + +function(add_run_clang_tidy_test check_name) + add_test(NAME "RunClangTidy.${check_name}" COMMAND ${CMAKE_COMMAND} + "-DCLANG_TIDY_COMMAND=$<TARGET_FILE:clang-tidy>" + "-DCLANG_TIDY_MODULE=$<TARGET_FILE:cmake-clang-tidy-module>" + "-DCHECK_NAME=${check_name}" + "-DRunClangTidy_BINARY_DIR=${CMAKE_CURRENT_BINARY_DIR}" + -P "${CMAKE_CURRENT_SOURCE_DIR}/RunClangTidy.cmake" + ) +endfunction() + +add_run_clang_tidy_test(cmake-use-cmstrlen) +add_run_clang_tidy_test(cmake-use-cmsys-fstream) +add_run_clang_tidy_test(cmake-use-bespoke-enum-class) +add_run_clang_tidy_test(cmake-ostringstream-use-cmstrcat) +add_run_clang_tidy_test(cmake-use-pragma-once) +add_run_clang_tidy_test(cmake-string-concatenation-use-cmstrcat) diff --git a/Utilities/ClangTidyModule/Tests/RunClangTidy.cmake b/Utilities/ClangTidyModule/Tests/RunClangTidy.cmake new file mode 100644 index 0000000000..98770d7034 --- /dev/null +++ b/Utilities/ClangTidyModule/Tests/RunClangTidy.cmake @@ -0,0 +1,93 @@ +set(config_arg) +if(EXISTS "${CMAKE_CURRENT_LIST_DIR}/${CHECK_NAME}.clang-tidy") + set(config_arg "--config-file=${CMAKE_CURRENT_LIST_DIR}/${CHECK_NAME}.clang-tidy") +endif() + +if(EXISTS "${CMAKE_CURRENT_LIST_DIR}/${CHECK_NAME}-stdout.txt") + file(READ "${CMAKE_CURRENT_LIST_DIR}/${CHECK_NAME}-stdout.txt" expect_stdout) + string(REGEX REPLACE "\n+$" "" expect_stdout "${expect_stdout}") +else() + set(expect_stdout "") +endif() + +set(source_file "${RunClangTidy_BINARY_DIR}/${CHECK_NAME}.cxx") +configure_file("${CMAKE_CURRENT_LIST_DIR}/${CHECK_NAME}.cxx" "${source_file}" COPYONLY) + +file(GLOB header_files RELATIVE "${CMAKE_CURRENT_LIST_DIR}/${CHECK_NAME}" "${CMAKE_CURRENT_LIST_DIR}/${CHECK_NAME}/*") +file(REMOVE_RECURSE "${RunClangTiy_BINARY_DIR}/${CHECK_NAME}") +foreach(header_file IN LISTS header_files) + if(NOT header_file MATCHES "-fixit\\.h\$") + file(MAKE_DIRECTORY "${RunClangTidy_BINARY_DIR}/${CHECK_NAME}") + configure_file("${CMAKE_CURRENT_LIST_DIR}/${CHECK_NAME}/${header_file}" "${RunClangTidy_BINARY_DIR}/${CHECK_NAME}/${header_file}" COPYONLY) + endif() +endforeach() + +set(command + "${CLANG_TIDY_COMMAND}" + "--load=${CLANG_TIDY_MODULE}" + "--checks=-*,${CHECK_NAME}" + "--fix" + "--format-style=file" + "--header-filter=/${CHECK_NAME}/" + ${config_arg} + "${source_file}" + -- + ) +execute_process( + COMMAND ${command} + RESULT_VARIABLE result + OUTPUT_VARIABLE actual_stdout + ERROR_VARIABLE actual_stderr + ) +string(REPLACE "${RunClangTidy_BINARY_DIR}/" "" actual_stdout "${actual_stdout}") + +set(RunClangTidy_TEST_FAILED) + +if(NOT result EQUAL 0) + string(APPEND RunClangTidy_TEST_FAILED "Expected result: 0, actual result: ${result}\n") +endif() + +string(REGEX REPLACE "\n+$" "" actual_stdout "${actual_stdout}") +if(NOT actual_stdout STREQUAL expect_stdout) + string(REPLACE "\n" "\n " expect_stdout_formatted " ${expect_stdout}") + string(REPLACE "\n" "\n " actual_stdout_formatted " ${actual_stdout}") + string(APPEND RunClangTidy_TEST_FAILED "Expected stdout:\n${expect_stdout_formatted}\nActual stdout:\n${actual_stdout_formatted}\n") +endif() + +function(check_fixit expected fallback_expected actual) + if(EXISTS "${expected}") + set(expect_fixit_file "${expected}") + else() + set(expect_fixit_file "${fallback_expected}") + endif() + file(READ "${expect_fixit_file}" expect_fixit) + file(READ "${actual}" actual_fixit) + if(NOT expect_fixit STREQUAL actual_fixit) + string(REPLACE "\n" "\n " expect_fixit_formatted " ${expect_fixit}") + string(REPLACE "\n" "\n " actual_fixit_formatted " ${actual_fixit}") + string(APPEND RunClangTidy_TEST_FAILED "Expected fixit for ${actual}:\n${expect_fixit_formatted}\nActual fixit:\n${actual_fixit_formatted}\n") + set(RunClangTidy_TEST_FAILED "${RunClangTidy_TEST_FAILED}" PARENT_SCOPE) + endif() +endfunction() + +check_fixit( + "${CMAKE_CURRENT_LIST_DIR}/${CHECK_NAME}-fixit.cxx" + "${CMAKE_CURRENT_LIST_DIR}/${CHECK_NAME}.cxx" + "${source_file}" + ) + +foreach(header_file IN LISTS header_files) + if(NOT header_file MATCHES "-fixit\\.h\$") + string(REGEX REPLACE "\\.h\$" "-fixit.h" header_fixit "${header_file}") + check_fixit( + "${CMAKE_CURRENT_LIST_DIR}/${CHECK_NAME}/${header_fixit}" + "${CMAKE_CURRENT_LIST_DIR}/${CHECK_NAME}/${header_file}" + "${RunClangTidy_BINARY_DIR}/${CHECK_NAME}/${header_file}" + ) + endif() +endforeach() + +if(RunClangTidy_TEST_FAILED) + string(REPLACE ";" " " command_formatted "${command}") + message(FATAL_ERROR "Command:\n ${command_formatted}\n${RunClangTidy_TEST_FAILED}") +endif() diff --git a/Utilities/ClangTidyModule/Tests/cmake-ostringstream-use-cmstrcat-stdout.txt b/Utilities/ClangTidyModule/Tests/cmake-ostringstream-use-cmstrcat-stdout.txt new file mode 100644 index 0000000000..1b2d6e740e --- /dev/null +++ b/Utilities/ClangTidyModule/Tests/cmake-ostringstream-use-cmstrcat-stdout.txt @@ -0,0 +1,6 @@ +cmake-ostringstream-use-cmstrcat.cxx:5:3: warning: use strings and cmStrCat() instead of std::ostringstream [cmake-ostringstream-use-cmstrcat] + std::ostringstream test; + ^ +cmake-ostringstream-use-cmstrcat.cxx:8:13: warning: use strings and cmStrCat() instead of std::ostringstream [cmake-ostringstream-use-cmstrcat] +void check2(std::ostringstream& test2) + ^ diff --git a/Utilities/ClangTidyModule/Tests/cmake-ostringstream-use-cmstrcat.cxx b/Utilities/ClangTidyModule/Tests/cmake-ostringstream-use-cmstrcat.cxx new file mode 100644 index 0000000000..ab749a6928 --- /dev/null +++ b/Utilities/ClangTidyModule/Tests/cmake-ostringstream-use-cmstrcat.cxx @@ -0,0 +1,10 @@ +#include <sstream> + +void check() +{ + std::ostringstream test; +} + +void check2(std::ostringstream& test2) +{ +} diff --git a/Utilities/ClangTidyModule/Tests/cmake-string-concatenation-use-cmstrcat-fixit.cxx b/Utilities/ClangTidyModule/Tests/cmake-string-concatenation-use-cmstrcat-fixit.cxx new file mode 100644 index 0000000000..dd1e6c4e89 --- /dev/null +++ b/Utilities/ClangTidyModule/Tests/cmake-string-concatenation-use-cmstrcat-fixit.cxx @@ -0,0 +1,40 @@ +#include <string> +#include <utility> + +template <typename... Args> +std::string cmStrCat(Args&&... args) +{ + return ""; +} + +std::string a = "This is a string variable"; +std::string b = " and this is a string variable"; +std::string concat; + +// Correction needed +void test1() +{ + concat = cmStrCat(a, b); + concat = cmStrCat(a, " and this is a string literal"); + concat = cmStrCat(a, 'O'); + concat = cmStrCat("This is a string literal", b); + concat = cmStrCat('O', a); + concat = cmStrCat(a, " and this is a string literal", 'O', b); + + concat = cmStrCat(concat, b); + concat = cmStrCat(concat, " and this is a string literal"); + concat = cmStrCat(concat, 'o'); + concat = cmStrCat(concat, b, " and this is a string literal ", 'o', b); + + std::pair<std::string, std::string> p; + concat = cmStrCat(p.first, p.second); +} + +// No correction needed +void test2() +{ + a = b; + a = "This is a string literal"; + a = 'X'; + cmStrCat(a, b); +} diff --git a/Utilities/ClangTidyModule/Tests/cmake-string-concatenation-use-cmstrcat-stdout.txt b/Utilities/ClangTidyModule/Tests/cmake-string-concatenation-use-cmstrcat-stdout.txt new file mode 100644 index 0000000000..83b8d83d8f --- /dev/null +++ b/Utilities/ClangTidyModule/Tests/cmake-string-concatenation-use-cmstrcat-stdout.txt @@ -0,0 +1,124 @@ +cmake-string-concatenation-use-cmstrcat.cxx:17:12: warning: use cmStrCat() instead of string concatenation [cmake-string-concatenation-use-cmstrcat] + concat = a + b; + ^ ~ + cmStrCat( , ) +cmake-string-concatenation-use-cmstrcat.cxx:17:12: note: FIX-IT applied suggested code changes +cmake-string-concatenation-use-cmstrcat.cxx:17:14: note: FIX-IT applied suggested code changes + concat = a + b; + ^ +cmake-string-concatenation-use-cmstrcat.cxx:17:17: note: FIX-IT applied suggested code changes + concat = a + b; + ^ +cmake-string-concatenation-use-cmstrcat.cxx:18:12: warning: use cmStrCat() instead of string concatenation [cmake-string-concatenation-use-cmstrcat] + concat = a + " and this is a string literal"; + ^ ~ + cmStrCat( , ) +cmake-string-concatenation-use-cmstrcat.cxx:18:12: note: FIX-IT applied suggested code changes +cmake-string-concatenation-use-cmstrcat.cxx:18:14: note: FIX-IT applied suggested code changes + concat = a + " and this is a string literal"; + ^ +cmake-string-concatenation-use-cmstrcat.cxx:18:47: note: FIX-IT applied suggested code changes + concat = a + " and this is a string literal"; + ^ +cmake-string-concatenation-use-cmstrcat.cxx:19:12: warning: use cmStrCat() instead of string concatenation [cmake-string-concatenation-use-cmstrcat] + concat = a + 'O'; + ^ ~ + cmStrCat( , ) +cmake-string-concatenation-use-cmstrcat.cxx:19:12: note: FIX-IT applied suggested code changes +cmake-string-concatenation-use-cmstrcat.cxx:19:14: note: FIX-IT applied suggested code changes + concat = a + 'O'; + ^ +cmake-string-concatenation-use-cmstrcat.cxx:19:19: note: FIX-IT applied suggested code changes + concat = a + 'O'; + ^ +cmake-string-concatenation-use-cmstrcat.cxx:20:12: warning: use cmStrCat() instead of string concatenation [cmake-string-concatenation-use-cmstrcat] + concat = "This is a string literal" + b; + ^ ~ + cmStrCat( , ) +cmake-string-concatenation-use-cmstrcat.cxx:20:12: note: FIX-IT applied suggested code changes +cmake-string-concatenation-use-cmstrcat.cxx:20:39: note: FIX-IT applied suggested code changes + concat = "This is a string literal" + b; + ^ +cmake-string-concatenation-use-cmstrcat.cxx:20:42: note: FIX-IT applied suggested code changes + concat = "This is a string literal" + b; + ^ +cmake-string-concatenation-use-cmstrcat.cxx:21:12: warning: use cmStrCat() instead of string concatenation [cmake-string-concatenation-use-cmstrcat] + concat = 'O' + a; + ^ ~ + cmStrCat( , ) +cmake-string-concatenation-use-cmstrcat.cxx:21:12: note: FIX-IT applied suggested code changes +cmake-string-concatenation-use-cmstrcat.cxx:21:16: note: FIX-IT applied suggested code changes + concat = 'O' + a; + ^ +cmake-string-concatenation-use-cmstrcat.cxx:21:19: note: FIX-IT applied suggested code changes + concat = 'O' + a; + ^ +cmake-string-concatenation-use-cmstrcat.cxx:22:12: warning: use cmStrCat() instead of string concatenation [cmake-string-concatenation-use-cmstrcat] + concat = a + " and this is a string literal" + 'O' + b; + ^ ~ ~ ~ + cmStrCat( , , , ) +cmake-string-concatenation-use-cmstrcat.cxx:22:12: note: FIX-IT applied suggested code changes +cmake-string-concatenation-use-cmstrcat.cxx:22:14: note: FIX-IT applied suggested code changes + concat = a + " and this is a string literal" + 'O' + b; + ^ +cmake-string-concatenation-use-cmstrcat.cxx:22:48: note: FIX-IT applied suggested code changes + concat = a + " and this is a string literal" + 'O' + b; + ^ +cmake-string-concatenation-use-cmstrcat.cxx:22:54: note: FIX-IT applied suggested code changes + concat = a + " and this is a string literal" + 'O' + b; + ^ +cmake-string-concatenation-use-cmstrcat.cxx:22:57: note: FIX-IT applied suggested code changes + concat = a + " and this is a string literal" + 'O' + b; + ^ +cmake-string-concatenation-use-cmstrcat.cxx:24:10: warning: use cmStrCat() instead of string append [cmake-string-concatenation-use-cmstrcat] + concat += b; + ^~ + = cmStrCat(concat, ) +cmake-string-concatenation-use-cmstrcat.cxx:24:10: note: FIX-IT applied suggested code changes +cmake-string-concatenation-use-cmstrcat.cxx:24:14: note: FIX-IT applied suggested code changes + concat += b; + ^ +cmake-string-concatenation-use-cmstrcat.cxx:25:10: warning: use cmStrCat() instead of string append [cmake-string-concatenation-use-cmstrcat] + concat += " and this is a string literal"; + ^~ + = cmStrCat(concat, ) +cmake-string-concatenation-use-cmstrcat.cxx:25:10: note: FIX-IT applied suggested code changes +cmake-string-concatenation-use-cmstrcat.cxx:25:44: note: FIX-IT applied suggested code changes + concat += " and this is a string literal"; + ^ +cmake-string-concatenation-use-cmstrcat.cxx:26:10: warning: use cmStrCat() instead of string append [cmake-string-concatenation-use-cmstrcat] + concat += 'o'; + ^~ + = cmStrCat(concat, ) +cmake-string-concatenation-use-cmstrcat.cxx:26:10: note: FIX-IT applied suggested code changes +cmake-string-concatenation-use-cmstrcat.cxx:26:16: note: FIX-IT applied suggested code changes + concat += 'o'; + ^ +cmake-string-concatenation-use-cmstrcat.cxx:27:10: warning: use cmStrCat() instead of string append [cmake-string-concatenation-use-cmstrcat] + concat += b + " and this is a string literal " + 'o' + b; + ^~ ~ ~ ~ + = cmStrCat(concat, , , , ) +cmake-string-concatenation-use-cmstrcat.cxx:27:10: note: FIX-IT applied suggested code changes +cmake-string-concatenation-use-cmstrcat.cxx:27:15: note: FIX-IT applied suggested code changes + concat += b + " and this is a string literal " + 'o' + b; + ^ +cmake-string-concatenation-use-cmstrcat.cxx:27:50: note: FIX-IT applied suggested code changes + concat += b + " and this is a string literal " + 'o' + b; + ^ +cmake-string-concatenation-use-cmstrcat.cxx:27:56: note: FIX-IT applied suggested code changes + concat += b + " and this is a string literal " + 'o' + b; + ^ +cmake-string-concatenation-use-cmstrcat.cxx:27:59: note: FIX-IT applied suggested code changes + concat += b + " and this is a string literal " + 'o' + b; + ^ +cmake-string-concatenation-use-cmstrcat.cxx:30:12: warning: use cmStrCat() instead of string concatenation [cmake-string-concatenation-use-cmstrcat] + concat = p.first + p.second; + ^ ~ + cmStrCat( , ) +cmake-string-concatenation-use-cmstrcat.cxx:30:12: note: FIX-IT applied suggested code changes +cmake-string-concatenation-use-cmstrcat.cxx:30:20: note: FIX-IT applied suggested code changes + concat = p.first + p.second; + ^ +cmake-string-concatenation-use-cmstrcat.cxx:30:30: note: FIX-IT applied suggested code changes + concat = p.first + p.second; + ^ diff --git a/Utilities/ClangTidyModule/Tests/cmake-string-concatenation-use-cmstrcat.cxx b/Utilities/ClangTidyModule/Tests/cmake-string-concatenation-use-cmstrcat.cxx new file mode 100644 index 0000000000..b088ca3f52 --- /dev/null +++ b/Utilities/ClangTidyModule/Tests/cmake-string-concatenation-use-cmstrcat.cxx @@ -0,0 +1,40 @@ +#include <string> +#include <utility> + +template <typename... Args> +std::string cmStrCat(Args&&... args) +{ + return ""; +} + +std::string a = "This is a string variable"; +std::string b = " and this is a string variable"; +std::string concat; + +// Correction needed +void test1() +{ + concat = a + b; + concat = a + " and this is a string literal"; + concat = a + 'O'; + concat = "This is a string literal" + b; + concat = 'O' + a; + concat = a + " and this is a string literal" + 'O' + b; + + concat += b; + concat += " and this is a string literal"; + concat += 'o'; + concat += b + " and this is a string literal " + 'o' + b; + + std::pair<std::string, std::string> p; + concat = p.first + p.second; +} + +// No correction needed +void test2() +{ + a = b; + a = "This is a string literal"; + a = 'X'; + cmStrCat(a, b); +} diff --git a/Utilities/ClangTidyModule/Tests/cmake-use-bespoke-enum-class-stdout.txt b/Utilities/ClangTidyModule/Tests/cmake-use-bespoke-enum-class-stdout.txt new file mode 100644 index 0000000000..5e0acddc66 --- /dev/null +++ b/Utilities/ClangTidyModule/Tests/cmake-use-bespoke-enum-class-stdout.txt @@ -0,0 +1,18 @@ +cmake-use-bespoke-enum-class.cxx:3:16: warning: use a bespoke enum class instead of booleans as parameters [cmake-use-bespoke-enum-class] +bool function1(bool i) + ^ +cmake-use-bespoke-enum-class.cxx:8:15: warning: use a bespoke enum class instead of booleans as parameters [cmake-use-bespoke-enum-class] +int function2(bool i) + ^ +cmake-use-bespoke-enum-class.cxx:13:16: warning: use a bespoke enum class instead of booleans as parameters [cmake-use-bespoke-enum-class] +char function3(bool i) + ^ +cmake-use-bespoke-enum-class.cxx:18:16: warning: use a bespoke enum class instead of booleans as parameters [cmake-use-bespoke-enum-class] +void function4(bool i) + ^ +cmake-use-bespoke-enum-class.cxx:22:17: warning: use a bespoke enum class instead of booleans as parameters [cmake-use-bespoke-enum-class] +float function5(bool i) + ^ +cmake-use-bespoke-enum-class.cxx:27:18: warning: use a bespoke enum class instead of booleans as parameters [cmake-use-bespoke-enum-class] +double function6(bool i) + ^ diff --git a/Utilities/ClangTidyModule/Tests/cmake-use-bespoke-enum-class.cxx b/Utilities/ClangTidyModule/Tests/cmake-use-bespoke-enum-class.cxx new file mode 100644 index 0000000000..2913e6a2d6 --- /dev/null +++ b/Utilities/ClangTidyModule/Tests/cmake-use-bespoke-enum-class.cxx @@ -0,0 +1,63 @@ +// Correction needed + +bool function1(bool i) +{ + return true; +} + +int function2(bool i) +{ + return 0; +} + +char function3(bool i) +{ + return 'a'; +} + +void function4(bool i) +{ +} + +float function5(bool i) +{ + return 1.0; +} + +double function6(bool i) +{ + return 0; +} + +// No correction needed +bool global; + +bool function7(int i) +{ + bool l; + return true; +} + +int function8(int i) +{ + return i; +} + +char function9(char i) +{ + return i; +} + +void function10() +{ +} + +float function11(float i) +{ + return i; +} + +double function12(double i) +{ + return i; +} diff --git a/Utilities/ClangTidyModule/Tests/cmake-use-cmstrlen-fixit.cxx b/Utilities/ClangTidyModule/Tests/cmake-use-cmstrlen-fixit.cxx new file mode 100644 index 0000000000..cde00864f1 --- /dev/null +++ b/Utilities/ClangTidyModule/Tests/cmake-use-cmstrlen-fixit.cxx @@ -0,0 +1,42 @@ +#include <cstring> + +template <size_t N> +constexpr size_t cmStrLen(const char (&/*str*/)[N]) +{ + return N - 1; +} + +namespace ns1 { +using std::strlen; +} + +namespace ns2 { +std::size_t strlen(const char* str) +{ + return std::strlen(str); +} +} + +int main() +{ + // String variable used for calling strlen() on a variable + auto s0 = "howdy"; + + // Correction needed + (void)cmStrLen("Hello"); + (void)cmStrLen("Goodbye"); + (void)cmStrLen("Hola"); + (void)cmStrLen("Bonjour"); + (void)(cmStrLen("Hallo")); + (void)(4 + cmStrLen("Hallo")); + (void)(cmStrLen("Hallo")); + (void)(4 + cmStrLen("Hallo")); + + // No correction needed + (void)ns2::strlen("Salve"); + (void)cmStrLen("Konnichiwa"); + (void)strlen(s0); + (void)(sizeof("Hallo") - 2); + + return 0; +} diff --git a/Utilities/ClangTidyModule/Tests/cmake-use-cmstrlen-stdout.txt b/Utilities/ClangTidyModule/Tests/cmake-use-cmstrlen-stdout.txt new file mode 100644 index 0000000000..d18822a4f4 --- /dev/null +++ b/Utilities/ClangTidyModule/Tests/cmake-use-cmstrlen-stdout.txt @@ -0,0 +1,52 @@ +cmake-use-cmstrlen.cxx:26:9: warning: use cmStrLen() for string literals [cmake-use-cmstrlen] + (void)strlen("Hello"); + ^~~~~~ + cmStrLen +cmake-use-cmstrlen.cxx:26:9: note: FIX-IT applied suggested code changes +cmake-use-cmstrlen.cxx:27:9: warning: use cmStrLen() for string literals [cmake-use-cmstrlen] + (void)::strlen("Goodbye"); + ^~~~~~~~ + cmStrLen +cmake-use-cmstrlen.cxx:27:9: note: FIX-IT applied suggested code changes +cmake-use-cmstrlen.cxx:28:9: warning: use cmStrLen() for string literals [cmake-use-cmstrlen] + (void)std::strlen("Hola"); + ^~~~~~~~~~~ + cmStrLen +cmake-use-cmstrlen.cxx:28:9: note: FIX-IT applied suggested code changes +cmake-use-cmstrlen.cxx:29:9: warning: use cmStrLen() for string literals [cmake-use-cmstrlen] + (void)ns1::strlen("Bonjour"); + ^~~~~~~~~~~ + cmStrLen +cmake-use-cmstrlen.cxx:29:9: note: FIX-IT applied suggested code changes +cmake-use-cmstrlen.cxx:30:10: warning: use cmStrLen() for string literals [cmake-use-cmstrlen] + (void)(sizeof("Hallo") - 1); + ^~~~~~ ~~~ + cmStrLen +cmake-use-cmstrlen.cxx:30:10: note: FIX-IT applied suggested code changes +cmake-use-cmstrlen.cxx:30:26: note: FIX-IT applied suggested code changes + (void)(sizeof("Hallo") - 1); + ^ +cmake-use-cmstrlen.cxx:31:14: warning: use cmStrLen() for string literals [cmake-use-cmstrlen] + (void)(4 + sizeof("Hallo") - 1); + ^~~~~~ ~~~ + cmStrLen +cmake-use-cmstrlen.cxx:31:14: note: FIX-IT applied suggested code changes +cmake-use-cmstrlen.cxx:31:30: note: FIX-IT applied suggested code changes + (void)(4 + sizeof("Hallo") - 1); + ^ +cmake-use-cmstrlen.cxx:32:10: warning: use cmStrLen() for string literals [cmake-use-cmstrlen] + (void)(sizeof "Hallo" - 1); + ^~~~~~ ~~~ + cmStrLen( ) +cmake-use-cmstrlen.cxx:32:10: note: FIX-IT applied suggested code changes +cmake-use-cmstrlen.cxx:32:25: note: FIX-IT applied suggested code changes + (void)(sizeof "Hallo" - 1); + ^ +cmake-use-cmstrlen.cxx:33:14: warning: use cmStrLen() for string literals [cmake-use-cmstrlen] + (void)(4 + sizeof "Hallo" - 1); + ^~~~~~ ~~~ + cmStrLen( ) +cmake-use-cmstrlen.cxx:33:14: note: FIX-IT applied suggested code changes +cmake-use-cmstrlen.cxx:33:29: note: FIX-IT applied suggested code changes + (void)(4 + sizeof "Hallo" - 1); + ^ diff --git a/Utilities/ClangTidyModule/Tests/cmake-use-cmstrlen.cxx b/Utilities/ClangTidyModule/Tests/cmake-use-cmstrlen.cxx new file mode 100644 index 0000000000..205bc9cc85 --- /dev/null +++ b/Utilities/ClangTidyModule/Tests/cmake-use-cmstrlen.cxx @@ -0,0 +1,42 @@ +#include <cstring> + +template <size_t N> +constexpr size_t cmStrLen(const char (&/*str*/)[N]) +{ + return N - 1; +} + +namespace ns1 { +using std::strlen; +} + +namespace ns2 { +std::size_t strlen(const char* str) +{ + return std::strlen(str); +} +} + +int main() +{ + // String variable used for calling strlen() on a variable + auto s0 = "howdy"; + + // Correction needed + (void)strlen("Hello"); + (void)::strlen("Goodbye"); + (void)std::strlen("Hola"); + (void)ns1::strlen("Bonjour"); + (void)(sizeof("Hallo") - 1); + (void)(4 + sizeof("Hallo") - 1); + (void)(sizeof "Hallo" - 1); + (void)(4 + sizeof "Hallo" - 1); + + // No correction needed + (void)ns2::strlen("Salve"); + (void)cmStrLen("Konnichiwa"); + (void)strlen(s0); + (void)(sizeof("Hallo") - 2); + + return 0; +} diff --git a/Utilities/ClangTidyModule/Tests/cmake-use-cmsys-fstream-fixit.cxx b/Utilities/ClangTidyModule/Tests/cmake-use-cmsys-fstream-fixit.cxx new file mode 100644 index 0000000000..5c7c12323f --- /dev/null +++ b/Utilities/ClangTidyModule/Tests/cmake-use-cmsys-fstream-fixit.cxx @@ -0,0 +1,81 @@ +#include <fstream> +#include <vector> + +namespace cmsys { +using std::ifstream; +using std::ofstream; +using std::fstream; +} + +namespace ns { +using std::ifstream; +using std::ofstream; +using std::fstream; + +namespace ns { +using std::ifstream; +using std::ofstream; +using std::fstream; +} + +class cl +{ +public: + using ifstream = cmsys::ifstream; + using ofstream = cmsys::ofstream; + using fstream = cmsys::fstream; +}; + +using ifs = cmsys::ifstream; +using ofs = cmsys::ofstream; +using fs = cmsys::fstream; +} + +int main() +{ + using std::ifstream; + using std::ofstream; + using std::fstream; + + // Correction needed + cmsys::ifstream ifsUnqual; + cmsys::ifstream ifsQual; + cmsys::ifstream ifsNS; + cmsys::ifstream ifsNested; + cmsys::ifstream ifsClass; + cmsys::ifstream ifsRenamed; + + cmsys::ofstream ofsUnqual; + cmsys::ofstream ofsQual; + cmsys::ofstream ofsNS; + cmsys::ofstream ofsNested; + cmsys::ofstream ofsClass; + cmsys::ofstream ofsRenamed; + + cmsys::fstream fsUnqual; + cmsys::fstream fsQual; + cmsys::fstream fsNS; + cmsys::fstream fsNested; + cmsys::fstream fsClass; + cmsys::fstream fsRenamed; + + cmsys::ifstream::off_type offsetQual = 0; + cmsys::ifstream::off_type offsetUnqual = 0; + cmsys::ifstream::off_type offsetNS = 0; + cmsys::ifstream::off_type offsetNested = 0; + cmsys::ifstream::traits_type::off_type offsetTraitsNested = 0; + cmsys::ifstream::traits_type::off_type offsetTraitsClass = 0; + + std::vector<cmsys::ifstream> ifsVectorUnqual; + + // No correction needed + cmsys::ifstream ifsCmsys; + cmsys::ofstream ofsCmsys; + cmsys::fstream fsCmsys; + cmsys::ifstream::off_type offsetCmsys = 0; + cmsys::ifstream::traits_type::off_type offsetTraitsCmsys = 0; + std::vector<cmsys::ifstream> ifsVectorCmsys; + std::basic_ifstream<wchar_t> ifsWchar; + + return 0; +} diff --git a/Utilities/ClangTidyModule/Tests/cmake-use-cmsys-fstream-stdout.txt b/Utilities/ClangTidyModule/Tests/cmake-use-cmsys-fstream-stdout.txt new file mode 100644 index 0000000000..d2c45f2c10 --- /dev/null +++ b/Utilities/ClangTidyModule/Tests/cmake-use-cmsys-fstream-stdout.txt @@ -0,0 +1,155 @@ +cmake-use-cmsys-fstream.cxx:24:20: warning: use cmsys::ifstream [cmake-use-cmsys-fstream] + using ifstream = std::ifstream; + ^~~~~~~~~~~~~ + cmsys::ifstream +cmake-use-cmsys-fstream.cxx:24:20: note: FIX-IT applied suggested code changes +cmake-use-cmsys-fstream.cxx:25:20: warning: use cmsys::ofstream [cmake-use-cmsys-fstream] + using ofstream = std::ofstream; + ^~~~~~~~~~~~~ + cmsys::ofstream +cmake-use-cmsys-fstream.cxx:25:20: note: FIX-IT applied suggested code changes +cmake-use-cmsys-fstream.cxx:26:19: warning: use cmsys::fstream [cmake-use-cmsys-fstream] + using fstream = std::fstream; + ^~~~~~~~~~~~ + cmsys::fstream +cmake-use-cmsys-fstream.cxx:26:19: note: FIX-IT applied suggested code changes +cmake-use-cmsys-fstream.cxx:29:13: warning: use cmsys::ifstream [cmake-use-cmsys-fstream] +using ifs = std::ifstream; + ^~~~~~~~~~~~~ + cmsys::ifstream +cmake-use-cmsys-fstream.cxx:29:13: note: FIX-IT applied suggested code changes +cmake-use-cmsys-fstream.cxx:30:13: warning: use cmsys::ofstream [cmake-use-cmsys-fstream] +using ofs = std::ofstream; + ^~~~~~~~~~~~~ + cmsys::ofstream +cmake-use-cmsys-fstream.cxx:30:13: note: FIX-IT applied suggested code changes +cmake-use-cmsys-fstream.cxx:31:12: warning: use cmsys::fstream [cmake-use-cmsys-fstream] +using fs = std::fstream; + ^~~~~~~~~~~~ + cmsys::fstream +cmake-use-cmsys-fstream.cxx:31:12: note: FIX-IT applied suggested code changes +cmake-use-cmsys-fstream.cxx:41:3: warning: use cmsys::ifstream [cmake-use-cmsys-fstream] + ifstream ifsUnqual; + ^~~~~~~~ + cmsys::ifstream +cmake-use-cmsys-fstream.cxx:41:3: note: FIX-IT applied suggested code changes +cmake-use-cmsys-fstream.cxx:42:3: warning: use cmsys::ifstream [cmake-use-cmsys-fstream] + std::ifstream ifsQual; + ^~~~~~~~~~~~~ + cmsys::ifstream +cmake-use-cmsys-fstream.cxx:42:3: note: FIX-IT applied suggested code changes +cmake-use-cmsys-fstream.cxx:43:3: warning: use cmsys::ifstream [cmake-use-cmsys-fstream] + ns::ifstream ifsNS; + ^~~~~~~~~~~~ + cmsys::ifstream +cmake-use-cmsys-fstream.cxx:43:3: note: FIX-IT applied suggested code changes +cmake-use-cmsys-fstream.cxx:44:3: warning: use cmsys::ifstream [cmake-use-cmsys-fstream] + ns::ns::ifstream ifsNested; + ^~~~~~~~~~~~~~~~ + cmsys::ifstream +cmake-use-cmsys-fstream.cxx:44:3: note: FIX-IT applied suggested code changes +cmake-use-cmsys-fstream.cxx:45:3: warning: use cmsys::ifstream [cmake-use-cmsys-fstream] + ns::cl::ifstream ifsClass; + ^~~~~~~~~~~~~~~~ + cmsys::ifstream +cmake-use-cmsys-fstream.cxx:45:3: note: FIX-IT applied suggested code changes +cmake-use-cmsys-fstream.cxx:46:3: warning: use cmsys::ifstream [cmake-use-cmsys-fstream] + ns::ifs ifsRenamed; + ^~~~~~~ + cmsys::ifstream +cmake-use-cmsys-fstream.cxx:46:3: note: FIX-IT applied suggested code changes +cmake-use-cmsys-fstream.cxx:48:3: warning: use cmsys::ofstream [cmake-use-cmsys-fstream] + ofstream ofsUnqual; + ^~~~~~~~ + cmsys::ofstream +cmake-use-cmsys-fstream.cxx:48:3: note: FIX-IT applied suggested code changes +cmake-use-cmsys-fstream.cxx:49:3: warning: use cmsys::ofstream [cmake-use-cmsys-fstream] + std::ofstream ofsQual; + ^~~~~~~~~~~~~ + cmsys::ofstream +cmake-use-cmsys-fstream.cxx:49:3: note: FIX-IT applied suggested code changes +cmake-use-cmsys-fstream.cxx:50:3: warning: use cmsys::ofstream [cmake-use-cmsys-fstream] + ns::ofstream ofsNS; + ^~~~~~~~~~~~ + cmsys::ofstream +cmake-use-cmsys-fstream.cxx:50:3: note: FIX-IT applied suggested code changes +cmake-use-cmsys-fstream.cxx:51:3: warning: use cmsys::ofstream [cmake-use-cmsys-fstream] + ns::ns::ofstream ofsNested; + ^~~~~~~~~~~~~~~~ + cmsys::ofstream +cmake-use-cmsys-fstream.cxx:51:3: note: FIX-IT applied suggested code changes +cmake-use-cmsys-fstream.cxx:52:3: warning: use cmsys::ofstream [cmake-use-cmsys-fstream] + ns::cl::ofstream ofsClass; + ^~~~~~~~~~~~~~~~ + cmsys::ofstream +cmake-use-cmsys-fstream.cxx:52:3: note: FIX-IT applied suggested code changes +cmake-use-cmsys-fstream.cxx:53:3: warning: use cmsys::ofstream [cmake-use-cmsys-fstream] + ns::ofs ofsRenamed; + ^~~~~~~ + cmsys::ofstream +cmake-use-cmsys-fstream.cxx:53:3: note: FIX-IT applied suggested code changes +cmake-use-cmsys-fstream.cxx:55:3: warning: use cmsys::fstream [cmake-use-cmsys-fstream] + fstream fsUnqual; + ^~~~~~~ + cmsys::fstream +cmake-use-cmsys-fstream.cxx:55:3: note: FIX-IT applied suggested code changes +cmake-use-cmsys-fstream.cxx:56:3: warning: use cmsys::fstream [cmake-use-cmsys-fstream] + std::fstream fsQual; + ^~~~~~~~~~~~ + cmsys::fstream +cmake-use-cmsys-fstream.cxx:56:3: note: FIX-IT applied suggested code changes +cmake-use-cmsys-fstream.cxx:57:3: warning: use cmsys::fstream [cmake-use-cmsys-fstream] + ns::fstream fsNS; + ^~~~~~~~~~~ + cmsys::fstream +cmake-use-cmsys-fstream.cxx:57:3: note: FIX-IT applied suggested code changes +cmake-use-cmsys-fstream.cxx:58:3: warning: use cmsys::fstream [cmake-use-cmsys-fstream] + ns::ns::fstream fsNested; + ^~~~~~~~~~~~~~~ + cmsys::fstream +cmake-use-cmsys-fstream.cxx:58:3: note: FIX-IT applied suggested code changes +cmake-use-cmsys-fstream.cxx:59:3: warning: use cmsys::fstream [cmake-use-cmsys-fstream] + ns::ns::fstream fsClass; + ^~~~~~~~~~~~~~~ + cmsys::fstream +cmake-use-cmsys-fstream.cxx:59:3: note: FIX-IT applied suggested code changes +cmake-use-cmsys-fstream.cxx:60:3: warning: use cmsys::fstream [cmake-use-cmsys-fstream] + ns::fs fsRenamed; + ^~~~~~ + cmsys::fstream +cmake-use-cmsys-fstream.cxx:60:3: note: FIX-IT applied suggested code changes +cmake-use-cmsys-fstream.cxx:62:3: warning: use cmsys::ifstream [cmake-use-cmsys-fstream] + std::ifstream::off_type offsetQual = 0; + ^~~~~~~~~~~~~ + cmsys::ifstream +cmake-use-cmsys-fstream.cxx:62:3: note: FIX-IT applied suggested code changes +cmake-use-cmsys-fstream.cxx:63:3: warning: use cmsys::ifstream [cmake-use-cmsys-fstream] + ifstream::off_type offsetUnqual = 0; + ^~~~~~~~ + cmsys::ifstream +cmake-use-cmsys-fstream.cxx:63:3: note: FIX-IT applied suggested code changes +cmake-use-cmsys-fstream.cxx:64:3: warning: use cmsys::ifstream [cmake-use-cmsys-fstream] + ns::ifstream::off_type offsetNS = 0; + ^~~~~~~~~~~~ + cmsys::ifstream +cmake-use-cmsys-fstream.cxx:64:3: note: FIX-IT applied suggested code changes +cmake-use-cmsys-fstream.cxx:65:3: warning: use cmsys::ifstream [cmake-use-cmsys-fstream] + ns::ns::ifstream::off_type offsetNested = 0; + ^~~~~~~~~~~~~~~~ + cmsys::ifstream +cmake-use-cmsys-fstream.cxx:65:3: note: FIX-IT applied suggested code changes +cmake-use-cmsys-fstream.cxx:66:3: warning: use cmsys::ifstream [cmake-use-cmsys-fstream] + ns::ns::ifstream::traits_type::off_type offsetTraitsNested = 0; + ^~~~~~~~~~~~~~~~ + cmsys::ifstream +cmake-use-cmsys-fstream.cxx:66:3: note: FIX-IT applied suggested code changes +cmake-use-cmsys-fstream.cxx:67:3: warning: use cmsys::ifstream [cmake-use-cmsys-fstream] + ns::cl::ifstream::traits_type::off_type offsetTraitsClass = 0; + ^~~~~~~~~~~~~~~~ + cmsys::ifstream +cmake-use-cmsys-fstream.cxx:67:3: note: FIX-IT applied suggested code changes +cmake-use-cmsys-fstream.cxx:69:15: warning: use cmsys::ifstream [cmake-use-cmsys-fstream] + std::vector<ifstream> ifsVectorUnqual; + ^~~~~~~~ + cmsys::ifstream +cmake-use-cmsys-fstream.cxx:69:15: note: FIX-IT applied suggested code changes diff --git a/Utilities/ClangTidyModule/Tests/cmake-use-cmsys-fstream.cxx b/Utilities/ClangTidyModule/Tests/cmake-use-cmsys-fstream.cxx new file mode 100644 index 0000000000..56a7611dcb --- /dev/null +++ b/Utilities/ClangTidyModule/Tests/cmake-use-cmsys-fstream.cxx @@ -0,0 +1,81 @@ +#include <fstream> +#include <vector> + +namespace cmsys { +using std::ifstream; +using std::ofstream; +using std::fstream; +} + +namespace ns { +using std::ifstream; +using std::ofstream; +using std::fstream; + +namespace ns { +using std::ifstream; +using std::ofstream; +using std::fstream; +} + +class cl +{ +public: + using ifstream = std::ifstream; + using ofstream = std::ofstream; + using fstream = std::fstream; +}; + +using ifs = std::ifstream; +using ofs = std::ofstream; +using fs = std::fstream; +} + +int main() +{ + using std::ifstream; + using std::ofstream; + using std::fstream; + + // Correction needed + ifstream ifsUnqual; + std::ifstream ifsQual; + ns::ifstream ifsNS; + ns::ns::ifstream ifsNested; + ns::cl::ifstream ifsClass; + ns::ifs ifsRenamed; + + ofstream ofsUnqual; + std::ofstream ofsQual; + ns::ofstream ofsNS; + ns::ns::ofstream ofsNested; + ns::cl::ofstream ofsClass; + ns::ofs ofsRenamed; + + fstream fsUnqual; + std::fstream fsQual; + ns::fstream fsNS; + ns::ns::fstream fsNested; + ns::ns::fstream fsClass; + ns::fs fsRenamed; + + std::ifstream::off_type offsetQual = 0; + ifstream::off_type offsetUnqual = 0; + ns::ifstream::off_type offsetNS = 0; + ns::ns::ifstream::off_type offsetNested = 0; + ns::ns::ifstream::traits_type::off_type offsetTraitsNested = 0; + ns::cl::ifstream::traits_type::off_type offsetTraitsClass = 0; + + std::vector<ifstream> ifsVectorUnqual; + + // No correction needed + cmsys::ifstream ifsCmsys; + cmsys::ofstream ofsCmsys; + cmsys::fstream fsCmsys; + cmsys::ifstream::off_type offsetCmsys = 0; + cmsys::ifstream::traits_type::off_type offsetTraitsCmsys = 0; + std::vector<cmsys::ifstream> ifsVectorCmsys; + std::basic_ifstream<wchar_t> ifsWchar; + + return 0; +} diff --git a/Utilities/ClangTidyModule/Tests/cmake-use-pragma-once-stdout.txt b/Utilities/ClangTidyModule/Tests/cmake-use-pragma-once-stdout.txt new file mode 100644 index 0000000000..e80e4a4f80 --- /dev/null +++ b/Utilities/ClangTidyModule/Tests/cmake-use-pragma-once-stdout.txt @@ -0,0 +1,25 @@ +cmake-use-pragma-once/cmake-use-pragma-once-both.h:1:1: warning: use #pragma once [cmake-use-pragma-once] +#ifndef BOTH_H +^~~~~~~~~~~~~~ +cmake-use-pragma-once/cmake-use-pragma-once-both.h:1:1: note: FIX-IT applied suggested code changes +cmake-use-pragma-once/cmake-use-pragma-once-both.h:2:1: note: FIX-IT applied suggested code changes +#define BOTH_H +^ +cmake-use-pragma-once/cmake-use-pragma-once-both.h:10:1: note: FIX-IT applied suggested code changes +#endif +^ +cmake-use-pragma-once/cmake-use-pragma-once-include-guards.h:1:1: warning: use #pragma once [cmake-use-pragma-once] +#ifndef INCLUDE_GUARDS_H +^~~~~~~~~~~~~~~~~~~~~~~~ +#pragma once +cmake-use-pragma-once/cmake-use-pragma-once-include-guards.h:1:1: note: FIX-IT applied suggested code changes +cmake-use-pragma-once/cmake-use-pragma-once-include-guards.h:2:1: note: FIX-IT applied suggested code changes +#define INCLUDE_GUARDS_H +^ +cmake-use-pragma-once/cmake-use-pragma-once-include-guards.h:9:1: note: FIX-IT applied suggested code changes +#endif +^ +cmake-use-pragma-once/cmake-use-pragma-once-neither.h:1:1: warning: use #pragma once [cmake-use-pragma-once] +int neither() +^ +cmake-use-pragma-once/cmake-use-pragma-once-neither.h:1:1: note: FIX-IT applied suggested code changes diff --git a/Utilities/ClangTidyModule/Tests/cmake-use-pragma-once.cxx b/Utilities/ClangTidyModule/Tests/cmake-use-pragma-once.cxx new file mode 100644 index 0000000000..a571bc1137 --- /dev/null +++ b/Utilities/ClangTidyModule/Tests/cmake-use-pragma-once.cxx @@ -0,0 +1,5 @@ +#include "cmake-use-pragma-once/cmake-use-pragma-once.h" + +#include "cmake-use-pragma-once/cmake-use-pragma-once-both.h" +#include "cmake-use-pragma-once/cmake-use-pragma-once-include-guards.h" +#include "cmake-use-pragma-once/cmake-use-pragma-once-neither.h" diff --git a/Utilities/ClangTidyModule/Tests/cmake-use-pragma-once/cmake-use-pragma-once-both-fixit.h b/Utilities/ClangTidyModule/Tests/cmake-use-pragma-once/cmake-use-pragma-once-both-fixit.h new file mode 100644 index 0000000000..73c9720d73 --- /dev/null +++ b/Utilities/ClangTidyModule/Tests/cmake-use-pragma-once/cmake-use-pragma-once-both-fixit.h @@ -0,0 +1,8 @@ + + +#pragma once + +int both() +{ + return 0; +} diff --git a/Utilities/ClangTidyModule/Tests/cmake-use-pragma-once/cmake-use-pragma-once-both.h b/Utilities/ClangTidyModule/Tests/cmake-use-pragma-once/cmake-use-pragma-once-both.h new file mode 100644 index 0000000000..fdf3cd3edc --- /dev/null +++ b/Utilities/ClangTidyModule/Tests/cmake-use-pragma-once/cmake-use-pragma-once-both.h @@ -0,0 +1,10 @@ +#ifndef BOTH_H +#define BOTH_H +#pragma once + +int both() +{ + return 0; +} + +#endif diff --git a/Utilities/ClangTidyModule/Tests/cmake-use-pragma-once/cmake-use-pragma-once-include-guards-fixit.h b/Utilities/ClangTidyModule/Tests/cmake-use-pragma-once/cmake-use-pragma-once-include-guards-fixit.h new file mode 100644 index 0000000000..36461c24f0 --- /dev/null +++ b/Utilities/ClangTidyModule/Tests/cmake-use-pragma-once/cmake-use-pragma-once-include-guards-fixit.h @@ -0,0 +1,6 @@ +#pragma once + +int includeGuards() +{ + return 0; +} diff --git a/Utilities/ClangTidyModule/Tests/cmake-use-pragma-once/cmake-use-pragma-once-include-guards.h b/Utilities/ClangTidyModule/Tests/cmake-use-pragma-once/cmake-use-pragma-once-include-guards.h new file mode 100644 index 0000000000..687306db33 --- /dev/null +++ b/Utilities/ClangTidyModule/Tests/cmake-use-pragma-once/cmake-use-pragma-once-include-guards.h @@ -0,0 +1,9 @@ +#ifndef INCLUDE_GUARDS_H +#define INCLUDE_GUARDS_H + +int includeGuards() +{ + return 0; +} + +#endif diff --git a/Utilities/ClangTidyModule/Tests/cmake-use-pragma-once/cmake-use-pragma-once-neither-fixit.h b/Utilities/ClangTidyModule/Tests/cmake-use-pragma-once/cmake-use-pragma-once-neither-fixit.h new file mode 100644 index 0000000000..eb5c6dd2e2 --- /dev/null +++ b/Utilities/ClangTidyModule/Tests/cmake-use-pragma-once/cmake-use-pragma-once-neither-fixit.h @@ -0,0 +1,5 @@ +#pragma once +int neither() +{ + return 0; +} diff --git a/Utilities/ClangTidyModule/Tests/cmake-use-pragma-once/cmake-use-pragma-once-neither.h b/Utilities/ClangTidyModule/Tests/cmake-use-pragma-once/cmake-use-pragma-once-neither.h new file mode 100644 index 0000000000..c779ca0b58 --- /dev/null +++ b/Utilities/ClangTidyModule/Tests/cmake-use-pragma-once/cmake-use-pragma-once-neither.h @@ -0,0 +1,4 @@ +int neither() +{ + return 0; +} diff --git a/Utilities/ClangTidyModule/Tests/cmake-use-pragma-once/cmake-use-pragma-once.h b/Utilities/ClangTidyModule/Tests/cmake-use-pragma-once/cmake-use-pragma-once.h new file mode 100644 index 0000000000..b0b2ea2313 --- /dev/null +++ b/Utilities/ClangTidyModule/Tests/cmake-use-pragma-once/cmake-use-pragma-once.h @@ -0,0 +1,6 @@ +#pragma once + +int once() +{ + return 0; +} diff --git a/Utilities/ClangTidyModule/UseBespokeEnumClassCheck.cxx b/Utilities/ClangTidyModule/UseBespokeEnumClassCheck.cxx new file mode 100644 index 0000000000..26f3749374 --- /dev/null +++ b/Utilities/ClangTidyModule/UseBespokeEnumClassCheck.cxx @@ -0,0 +1,35 @@ +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file Copyright.txt or https://cmake.org/licensing for details. */ +#include "UseBespokeEnumClassCheck.h" + +#include <clang/AST/Type.h> +#include <clang/ASTMatchers/ASTMatchFinder.h> + +namespace clang { +namespace tidy { +namespace cmake { +using namespace ast_matchers; + +UseBespokeEnumClassCheck::UseBespokeEnumClassCheck(StringRef Name, + ClangTidyContext* Context) + : ClangTidyCheck(Name, Context) +{ +} + +void UseBespokeEnumClassCheck::registerMatchers(MatchFinder* Finder) +{ + Finder->addMatcher( + parmVarDecl( + hasTypeLoc(typeLoc(loc(qualType(asString("_Bool")))).bind("type"))), + this); +} + +void UseBespokeEnumClassCheck::check(const MatchFinder::MatchResult& Result) +{ + const TypeLoc* Node = Result.Nodes.getNodeAs<TypeLoc>("type"); + this->diag(Node->getBeginLoc(), + "use a bespoke enum class instead of booleans as parameters"); +} +} +} +} diff --git a/Utilities/ClangTidyModule/UseBespokeEnumClassCheck.h b/Utilities/ClangTidyModule/UseBespokeEnumClassCheck.h new file mode 100644 index 0000000000..be76db037e --- /dev/null +++ b/Utilities/ClangTidyModule/UseBespokeEnumClassCheck.h @@ -0,0 +1,21 @@ +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file Copyright.txt or https://cmake.org/licensing for details. */ +#pragma once + +#include <clang-tidy/ClangTidyCheck.h> +#include <clang/ASTMatchers/ASTMatchFinder.h> + +namespace clang { +namespace tidy { +namespace cmake { +class UseBespokeEnumClassCheck : public ClangTidyCheck +{ +public: + UseBespokeEnumClassCheck(StringRef Name, ClangTidyContext* Context); + void registerMatchers(ast_matchers::MatchFinder* Finder) override; + + void check(const ast_matchers::MatchFinder::MatchResult& Result) override; +}; +} +} +} diff --git a/Utilities/ClangTidyModule/UseCmstrlenCheck.cxx b/Utilities/ClangTidyModule/UseCmstrlenCheck.cxx new file mode 100644 index 0000000000..d4bae1f794 --- /dev/null +++ b/Utilities/ClangTidyModule/UseCmstrlenCheck.cxx @@ -0,0 +1,78 @@ +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file Copyright.txt or https://cmake.org/licensing for details. */ +#include "UseCmstrlenCheck.h" + +#include <clang/ASTMatchers/ASTMatchFinder.h> + +namespace clang { +namespace tidy { +namespace cmake { +using namespace ast_matchers; + +UseCmstrlenCheck::UseCmstrlenCheck(StringRef Name, ClangTidyContext* Context) + : ClangTidyCheck(Name, Context) +{ +} + +void UseCmstrlenCheck::registerMatchers(MatchFinder* Finder) +{ + Finder->addMatcher(callExpr(callee(functionDecl(hasName("::strlen"))), + callee(expr().bind("strlen")), + hasArgument(0, stringLiteral())), + this); + + auto IsSizeOfStringLiteral = + unaryExprOrTypeTraitExpr( + ofKind(UETT_SizeOf), + anyOf(has(parenExpr(has(stringLiteral())).bind("paren")), + has(stringLiteral()))) + .bind("sizeOf"); + Finder->addMatcher( + binaryOperator( + hasOperatorName("-"), + hasLHS(anyOf( + binaryOperator(hasOperatorName("+"), hasRHS(IsSizeOfStringLiteral)), + IsSizeOfStringLiteral)), + hasRHS(implicitCastExpr(has(integerLiteral(equals(1)).bind("literal"))))) + .bind("sizeOfMinus"), + this); +} + +void UseCmstrlenCheck::check(const MatchFinder::MatchResult& Result) +{ + const Expr* Strlen = Result.Nodes.getNodeAs<Expr>("strlen"); + const BinaryOperator* SizeOfMinus = + Result.Nodes.getNodeAs<BinaryOperator>("sizeOfMinus"); + + if (Strlen) { + this->diag(Strlen->getBeginLoc(), "use cmStrLen() for string literals") + << FixItHint::CreateReplacement(Strlen->getSourceRange(), "cmStrLen"); + } + + if (SizeOfMinus) { + const ParenExpr* Paren = Result.Nodes.getNodeAs<ParenExpr>("paren"); + const UnaryExprOrTypeTraitExpr* SizeOf = + Result.Nodes.getNodeAs<UnaryExprOrTypeTraitExpr>("sizeOf"); + const IntegerLiteral* Literal = + Result.Nodes.getNodeAs<IntegerLiteral>("literal"); + + std::vector<FixItHint> FixIts; + if (Paren) { + FixIts.push_back( + FixItHint::CreateReplacement(SizeOf->getOperatorLoc(), "cmStrLen")); + FixIts.push_back(FixItHint::CreateRemoval( + SourceRange(SizeOfMinus->getOperatorLoc(), Literal->getLocation()))); + } else { + FixIts.push_back( + FixItHint::CreateReplacement(SizeOf->getOperatorLoc(), "cmStrLen(")); + FixIts.push_back(FixItHint::CreateReplacement( + SourceRange(SizeOfMinus->getOperatorLoc(), Literal->getLocation()), + ")")); + } + this->diag(SizeOf->getOperatorLoc(), "use cmStrLen() for string literals") + << FixIts; + } +} +} +} +} diff --git a/Utilities/ClangTidyModule/UseCmstrlenCheck.h b/Utilities/ClangTidyModule/UseCmstrlenCheck.h new file mode 100644 index 0000000000..08f77c23a9 --- /dev/null +++ b/Utilities/ClangTidyModule/UseCmstrlenCheck.h @@ -0,0 +1,21 @@ +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file Copyright.txt or https://cmake.org/licensing for details. */ +#pragma once + +#include <clang-tidy/ClangTidyCheck.h> +#include <clang/ASTMatchers/ASTMatchFinder.h> + +namespace clang { +namespace tidy { +namespace cmake { +class UseCmstrlenCheck : public ClangTidyCheck +{ +public: + UseCmstrlenCheck(StringRef Name, ClangTidyContext* Context); + void registerMatchers(ast_matchers::MatchFinder* Finder) override; + + void check(const ast_matchers::MatchFinder::MatchResult& Result) override; +}; +} +} +} diff --git a/Utilities/ClangTidyModule/UseCmsysFstreamCheck.cxx b/Utilities/ClangTidyModule/UseCmsysFstreamCheck.cxx new file mode 100644 index 0000000000..95a0a4d6fa --- /dev/null +++ b/Utilities/ClangTidyModule/UseCmsysFstreamCheck.cxx @@ -0,0 +1,101 @@ +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file Copyright.txt or https://cmake.org/licensing for details. */ +#include "UseCmsysFstreamCheck.h" + +#include <clang/ASTMatchers/ASTMatchFinder.h> + +namespace clang { +namespace tidy { +namespace cmake { +using namespace ast_matchers; + +UseCmsysFstreamCheck::UseCmsysFstreamCheck(StringRef Name, + ClangTidyContext* Context) + : ClangTidyCheck(Name, Context) +{ +} + +void UseCmsysFstreamCheck::registerMatchers(MatchFinder* Finder) +{ + this->createMatcher("::std::basic_ifstream", "::cmsys::ifstream", Finder, + "ifstream"); + this->createMatcher("::std::basic_ofstream", "::cmsys::ofstream", Finder, + "ofstream"); + this->createMatcher("::std::basic_fstream", "::cmsys::fstream", Finder, + "fstream"); +} + +void UseCmsysFstreamCheck::check(const MatchFinder::MatchResult& Result) +{ + const TypeLoc* ParentTypeNode = + Result.Nodes.getNodeAs<TypeLoc>("parentType"); + const NestedNameSpecifierLoc* ParentNameNode = + Result.Nodes.getNodeAs<NestedNameSpecifierLoc>("parentName"); + const TypeLoc* RootNode = nullptr; + StringRef BindName; + StringRef Warning; + + if ((RootNode = Result.Nodes.getNodeAs<TypeLoc>("ifstream")) != nullptr) { + BindName = "cmsys::ifstream"; + Warning = "use cmsys::ifstream"; + } else if ((RootNode = Result.Nodes.getNodeAs<TypeLoc>("ofstream")) != + nullptr) { + BindName = "cmsys::ofstream"; + Warning = "use cmsys::ofstream"; + } else if ((RootNode = Result.Nodes.getNodeAs<TypeLoc>("fstream")) != + nullptr) { + BindName = "cmsys::fstream"; + Warning = "use cmsys::fstream"; + } + + if (ParentTypeNode != nullptr) { + if (ParentTypeNode->getBeginLoc().isValid()) { + this->diag(ParentTypeNode->getBeginLoc(), Warning) + << FixItHint::CreateReplacement(ParentTypeNode->getSourceRange(), + BindName); + } + } else if (ParentNameNode != nullptr) { + if (ParentNameNode->getBeginLoc().isValid()) { + this->diag(ParentNameNode->getBeginLoc(), Warning) + << FixItHint::CreateReplacement( + SourceRange(ParentNameNode->getBeginLoc(), RootNode->getEndLoc()), + BindName); + } + } else if (RootNode != nullptr) { + if (RootNode->getBeginLoc().isValid()) { + this->diag(RootNode->getBeginLoc(), Warning) + << FixItHint::CreateReplacement(RootNode->getSourceRange(), BindName); + } + } +} + +void UseCmsysFstreamCheck::createMatcher(StringRef StdName, + StringRef CmsysName, + ast_matchers::MatchFinder* Finder, + StringRef Bind) +{ + TypeLocMatcher IsStd = loc(qualType(hasUnqualifiedDesugaredType( + recordType(hasDeclaration(classTemplateSpecializationDecl( + hasName(StdName), + hasTemplateArgument( + 0, templateArgument(refersToType(asString("char")))))))))); + + // TODO This only checks to see if the type directly refers to + // cmsys::fstream. There are some corner cases involving template parameters + // that refer to cmsys::fstream that are missed by this matcher, resulting in + // a false positive. Figure out how to find these indirect references to + // cmsys::fstream and filter them out. In the meantime, such false positives + // can be silenced with NOLINT(cmake-use-cmsys-fstream). + TypeLocMatcher IsCmsys = + loc(usingType(throughUsingDecl(namedDecl(hasName(CmsysName))))); + + Finder->addMatcher( + typeLoc(IsStd, unless(IsCmsys), unless(elaboratedTypeLoc()), + optionally(hasParent(elaboratedTypeLoc().bind("parentType"))), + optionally(hasParent(nestedNameSpecifierLoc().bind("parentName")))) + .bind(Bind), + this); +} +} +} +} diff --git a/Utilities/ClangTidyModule/UseCmsysFstreamCheck.h b/Utilities/ClangTidyModule/UseCmsysFstreamCheck.h new file mode 100644 index 0000000000..782123c12a --- /dev/null +++ b/Utilities/ClangTidyModule/UseCmsysFstreamCheck.h @@ -0,0 +1,24 @@ +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file Copyright.txt or https://cmake.org/licensing for details. */ +#pragma once + +#include <clang-tidy/ClangTidyCheck.h> +#include <clang/ASTMatchers/ASTMatchFinder.h> + +namespace clang { +namespace tidy { +namespace cmake { +class UseCmsysFstreamCheck : public ClangTidyCheck +{ +public: + UseCmsysFstreamCheck(StringRef Name, ClangTidyContext* Context); + void registerMatchers(ast_matchers::MatchFinder* Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult& Result) override; + +private: + void createMatcher(StringRef name, StringRef CmsysName, + ast_matchers::MatchFinder* Finder, StringRef bind); +}; +} +} +} diff --git a/Utilities/ClangTidyModule/UsePragmaOnceCheck.cxx b/Utilities/ClangTidyModule/UsePragmaOnceCheck.cxx new file mode 100644 index 0000000000..7a42798a6d --- /dev/null +++ b/Utilities/ClangTidyModule/UsePragmaOnceCheck.cxx @@ -0,0 +1,325 @@ +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file Copyright.txt or https://cmake.org/licensing for details. */ + +/* This code was originally taken from part of the Clang-Tidy LLVM project and + * modified for use with CMake under the following original license: */ + +//===--- HeaderGuard.cpp - clang-tidy +//-------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM +// Exceptions. See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "UsePragmaOnceCheck.h" + +#include <algorithm> +#include <cassert> + +#include <clang/Frontend/CompilerInstance.h> +#include <clang/Lex/PPCallbacks.h> +#include <clang/Lex/Preprocessor.h> +#include <clang/Tooling/Tooling.h> +#include <llvm/Support/Path.h> + +namespace clang { +namespace tidy { +namespace cmake { + +/// canonicalize a path by removing ./ and ../ components. +static std::string cleanPath(StringRef Path) +{ + SmallString<256> Result = Path; + llvm::sys::path::remove_dots(Result, true); + return std::string(Result.str()); +} + +namespace { +// This class is a workaround for the fact that PPCallbacks doesn't give us the +// location of the hash for an #ifndef, #define, or #endif, so we have to find +// it ourselves. We can't lex backwards, and attempting to turn on the +// preprocessor's backtracking functionality wreaks havoc, so we have to +// instantiate a second lexer and lex all the way from the beginning of the +// file. Cache the results of this lexing so that we don't have to do it more +// times than needed. +// +// TODO: Upstream a change to LLVM to give us the location of the hash in +// PPCallbacks so we don't have to do this workaround. +class DirectiveCache +{ +public: + DirectiveCache(Preprocessor* PP, FileID FID) + : PP(PP) + , FID(FID) + { + SourceManager& SM = this->PP->getSourceManager(); + const FileEntry* Entry = SM.getFileEntryForID(FID); + assert(Entry && "Invalid FileID given"); + + Lexer MyLexer(FID, SM.getMemoryBufferForFileOrFake(Entry), SM, + this->PP->getLangOpts()); + Token Tok; + + while (!MyLexer.LexFromRawLexer(Tok)) { + if (Tok.getKind() == tok::hash) { + assert(SM.getFileID(Tok.getLocation()) == this->FID && + "Token FileID does not match passed FileID"); + if (!this->HashLocs.empty()) { + assert(SM.getFileOffset(this->HashLocs.back()) < + SM.getFileOffset(Tok.getLocation()) && + "Tokens in file are not in order"); + } + + this->HashLocs.push_back(Tok.getLocation()); + } + } + } + + SourceRange createRangeForIfndef(SourceLocation IfndefMacroTokLoc) + { + // The #ifndef of an include guard is likely near the beginning of the + // file, so search from the front. + return SourceRange(this->findPreviousHashFromFront(IfndefMacroTokLoc), + IfndefMacroTokLoc); + } + + SourceRange createRangeForDefine(SourceLocation DefineMacroTokLoc) + { + // The #define of an include guard is likely near the beginning of the + // file, so search from the front. + return SourceRange(this->findPreviousHashFromFront(DefineMacroTokLoc), + DefineMacroTokLoc); + } + + SourceRange createRangeForEndif(SourceLocation EndifLoc) + { + // The #endif of an include guard is likely near the end of the file, so + // search from the back. + return SourceRange(this->findPreviousHashFromBack(EndifLoc), EndifLoc); + } + +private: + Preprocessor* PP; + FileID FID; + SmallVector<SourceLocation> HashLocs; + + SourceLocation findPreviousHashFromFront(SourceLocation Loc) + { + SourceManager& SM = this->PP->getSourceManager(); + Loc = SM.getExpansionLoc(Loc); + assert(SM.getFileID(Loc) == this->FID && + "Loc FileID does not match our FileID"); + + auto It = std::find_if( + this->HashLocs.begin(), this->HashLocs.end(), + [&SM, &Loc](const SourceLocation& OtherLoc) -> bool { + return SM.getFileOffset(OtherLoc) >= SM.getFileOffset(Loc); + }); + assert(It != this->HashLocs.begin() && + "No hash associated with passed Loc"); + return *--It; + } + + SourceLocation findPreviousHashFromBack(SourceLocation Loc) + { + SourceManager& SM = this->PP->getSourceManager(); + Loc = SM.getExpansionLoc(Loc); + assert(SM.getFileID(Loc) == this->FID && + "Loc FileID does not match our FileID"); + + auto It = + std::find_if(this->HashLocs.rbegin(), this->HashLocs.rend(), + [&SM, &Loc](const SourceLocation& OtherLoc) -> bool { + return SM.getFileOffset(OtherLoc) < SM.getFileOffset(Loc); + }); + assert(It != this->HashLocs.rend() && + "No hash associated with passed Loc"); + return *It; + } +}; + +class UsePragmaOncePPCallbacks : public PPCallbacks +{ +public: + UsePragmaOncePPCallbacks(Preprocessor* PP, UsePragmaOnceCheck* Check) + : PP(PP) + , Check(Check) + { + } + + void FileChanged(SourceLocation Loc, FileChangeReason Reason, + SrcMgr::CharacteristicKind FileType, + FileID PrevFID) override + { + // Record all files we enter. We'll need them to diagnose headers without + // guards. + SourceManager& SM = this->PP->getSourceManager(); + if (Reason == EnterFile && FileType == SrcMgr::C_User) { + if (const FileEntry* FE = SM.getFileEntryForID(SM.getFileID(Loc))) { + std::string FileName = cleanPath(FE->getName()); + this->Files[FileName] = FE; + } + } + } + + void Ifndef(SourceLocation Loc, const Token& MacroNameTok, + const MacroDefinition& MD) override + { + if (MD) { + return; + } + + // Record #ifndefs that succeeded. We also need the Location of the Name. + this->Ifndefs[MacroNameTok.getIdentifierInfo()] = + std::make_pair(Loc, MacroNameTok.getLocation()); + } + + void MacroDefined(const Token& MacroNameTok, + const MacroDirective* MD) override + { + // Record all defined macros. We store the whole token to get info on the + // name later. + this->Macros.emplace_back(MacroNameTok, MD->getMacroInfo()); + } + + void Endif(SourceLocation Loc, SourceLocation IfLoc) override + { + // Record all #endif and the corresponding #ifs (including #ifndefs). + this->EndIfs[IfLoc] = Loc; + } + + void EndOfMainFile() override + { + // Now that we have all this information from the preprocessor, use it! + SourceManager& SM = this->PP->getSourceManager(); + + for (const auto& MacroEntry : this->Macros) { + const MacroInfo* MI = MacroEntry.second; + + // We use clang's header guard detection. This has the advantage of also + // emitting a warning for cases where a pseudo header guard is found but + // preceded by something blocking the header guard optimization. + if (!MI->isUsedForHeaderGuard()) { + continue; + } + + const FileEntry* FE = + SM.getFileEntryForID(SM.getFileID(MI->getDefinitionLoc())); + std::string FileName = cleanPath(FE->getName()); + this->Files.erase(FileName); + + // Look up Locations for this guard. + SourceLocation Ifndef = + this->Ifndefs[MacroEntry.first.getIdentifierInfo()].second; + SourceLocation Define = MacroEntry.first.getLocation(); + SourceLocation EndIf = + this + ->EndIfs[this->Ifndefs[MacroEntry.first.getIdentifierInfo()].first]; + + StringRef CurHeaderGuard = + MacroEntry.first.getIdentifierInfo()->getName(); + std::vector<FixItHint> FixIts; + + HeaderSearch& HeaderInfo = this->PP->getHeaderSearchInfo(); + + HeaderFileInfo& Info = HeaderInfo.getFileInfo(FE); + + DirectiveCache Cache(this->PP, SM.getFileID(MI->getDefinitionLoc())); + SourceRange IfndefSrcRange = Cache.createRangeForIfndef(Ifndef); + SourceRange DefineSrcRange = Cache.createRangeForDefine(Define); + SourceRange EndifSrcRange = Cache.createRangeForEndif(EndIf); + + if (Info.isPragmaOnce) { + FixIts.push_back(FixItHint::CreateRemoval(IfndefSrcRange)); + } else { + FixIts.push_back( + FixItHint::CreateReplacement(IfndefSrcRange, "#pragma once")); + } + + FixIts.push_back(FixItHint::CreateRemoval(DefineSrcRange)); + FixIts.push_back(FixItHint::CreateRemoval(EndifSrcRange)); + + this->Check->diag(IfndefSrcRange.getBegin(), "use #pragma once") + << FixIts; + } + + // Emit warnings for headers that are missing guards. + checkGuardlessHeaders(); + clearAllState(); + } + + /// Looks for files that were visited but didn't have a header guard. + /// Emits a warning with fixits suggesting adding one. + void checkGuardlessHeaders() + { + // Look for header files that didn't have a header guard. Emit a warning + // and fix-its to add the guard. + // TODO: Insert the guard after top comments. + for (const auto& FE : this->Files) { + StringRef FileName = FE.getKey(); + if (!Check->shouldSuggestToAddPragmaOnce(FileName)) { + continue; + } + + SourceManager& SM = this->PP->getSourceManager(); + FileID FID = SM.translateFile(FE.getValue()); + SourceLocation StartLoc = SM.getLocForStartOfFile(FID); + if (StartLoc.isInvalid()) { + continue; + } + + HeaderSearch& HeaderInfo = this->PP->getHeaderSearchInfo(); + + HeaderFileInfo& Info = HeaderInfo.getFileInfo(FE.second); + if (Info.isPragmaOnce) { + continue; + } + + this->Check->diag(StartLoc, "use #pragma once") + << FixItHint::CreateInsertion(StartLoc, "#pragma once\n"); + } + } + +private: + void clearAllState() + { + this->Macros.clear(); + this->Files.clear(); + this->Ifndefs.clear(); + this->EndIfs.clear(); + } + + std::vector<std::pair<Token, const MacroInfo*>> Macros; + llvm::StringMap<const FileEntry*> Files; + std::map<const IdentifierInfo*, std::pair<SourceLocation, SourceLocation>> + Ifndefs; + std::map<SourceLocation, SourceLocation> EndIfs; + + Preprocessor* PP; + UsePragmaOnceCheck* Check; +}; +} // namespace + +void UsePragmaOnceCheck::storeOptions(ClangTidyOptions::OptionMap& Opts) +{ + this->Options.store(Opts, "HeaderFileExtensions", + RawStringHeaderFileExtensions); +} + +void UsePragmaOnceCheck::registerPPCallbacks(const SourceManager& SM, + Preprocessor* PP, + Preprocessor* ModuleExpanderPP) +{ + PP->addPPCallbacks(std::make_unique<UsePragmaOncePPCallbacks>(PP, this)); +} + +bool UsePragmaOnceCheck::shouldSuggestToAddPragmaOnce(StringRef FileName) +{ + return utils::isFileExtension(FileName, this->HeaderFileExtensions); +} + +} // namespace cmake +} // namespace tidy +} // namespace clang diff --git a/Utilities/ClangTidyModule/UsePragmaOnceCheck.h b/Utilities/ClangTidyModule/UsePragmaOnceCheck.h new file mode 100644 index 0000000000..08c2099a9f --- /dev/null +++ b/Utilities/ClangTidyModule/UsePragmaOnceCheck.h @@ -0,0 +1,60 @@ +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file Copyright.txt or https://cmake.org/licensing for details. */ + +/* This code was originally taken from part of the Clang-Tidy LLVM project and + * modified for use with CMake under the following original license: */ + +//===--- HeaderGuard.h - clang-tidy -----------------------------*- C++ +//-*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM +// Exceptions. See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#pragma once + +#include <clang-tidy/ClangTidyCheck.h> +#include <clang-tidy/utils/FileExtensionsUtils.h> + +namespace clang { +namespace tidy { +namespace cmake { + +/// Finds and replaces header guards with pragma once. +/// The check supports these options: +/// - `HeaderFileExtensions`: a semicolon-separated list of filename +/// extensions of header files (The filename extension should not contain +/// "." prefix). ";h;hh;hpp;hxx" by default. +/// +/// For extension-less header files, using an empty string or leaving an +/// empty string between ";" if there are other filename extensions. +class UsePragmaOnceCheck : public ClangTidyCheck +{ +public: + UsePragmaOnceCheck(StringRef Name, ClangTidyContext* Context) + : ClangTidyCheck(Name, Context) + , RawStringHeaderFileExtensions(Options.getLocalOrGlobal( + "HeaderFileExtensions", utils::defaultHeaderFileExtensions())) + { + utils::parseFileExtensions(RawStringHeaderFileExtensions, + HeaderFileExtensions, + utils::defaultFileExtensionDelimiters()); + } + void storeOptions(ClangTidyOptions::OptionMap& Opts) override; + void registerPPCallbacks(const SourceManager& SM, Preprocessor* PP, + Preprocessor* ModuleExpanderPP) override; + + /// Returns ``true`` if the check should add pragma once to the file + /// if it has none. + virtual bool shouldSuggestToAddPragmaOnce(StringRef Filename); + +private: + std::string RawStringHeaderFileExtensions; + utils::FileExtensionsSet HeaderFileExtensions; +}; + +} // namespace cmake +} // namespace tidy +} // namespace clang |