Index: clang-tidy/CMakeLists.txt =================================================================== --- clang-tidy/CMakeLists.txt +++ clang-tidy/CMakeLists.txt @@ -27,6 +27,7 @@ ) add_subdirectory(android) +add_subdirectory(absl) add_subdirectory(boost) add_subdirectory(bugprone) add_subdirectory(cert) Index: clang-tidy/absl/AbslTidyModule.cpp =================================================================== --- clang-tidy/absl/AbslTidyModule.cpp +++ clang-tidy/absl/AbslTidyModule.cpp @@ -0,0 +1,39 @@ +//===------- AbslTidyModule.cpp - clang-tidy -----------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "../ClangTidy.h" +#include "../ClangTidyModule.h" +#include "../ClangTidyModuleRegistry.h" +#include "StringFindStartswithCheck.h" +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace absl { + +class AbslModule : public ClangTidyModule { +public: + void addCheckFactories(ClangTidyCheckFactories &CheckFactories) override { + CheckFactories.registerCheck( + "absl-string-find-startswith"); + } +}; + +// Register the AbslModule using this statically initialized variable. +static ClangTidyModuleRegistry::Add X("absl-module", + "Add absl checks."); + +} // namespace absl + +// This anchor is used to force the linker to link in the generated object file +// and thus register the AbslModule. +volatile int AbslModuleAnchorSource = 0; + +} // namespace tidy +} // namespace clang Index: clang-tidy/absl/CMakeLists.txt =================================================================== --- clang-tidy/absl/CMakeLists.txt +++ clang-tidy/absl/CMakeLists.txt @@ -0,0 +1,14 @@ +set(LLVM_LINK_COMPONENTS support) + +add_clang_library(clangTidyAbslModule + AbslTidyModule.cpp + StringFindStartswithCheck.cpp + + LINK_LIBS + clangAST + clangASTMatchers + clangBasic + clangLex + clangTidy + clangTidyUtils + ) Index: clang-tidy/absl/StringFindStartswithCheck.h =================================================================== --- clang-tidy/absl/StringFindStartswithCheck.h +++ clang-tidy/absl/StringFindStartswithCheck.h @@ -0,0 +1,37 @@ +//===--- StringFindStartswithCheck.h - clang-tidy----------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_ABSL_STRING_FIND_STARTSWITH_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_ABSL_STRING_FIND_STARTSWITH_H_ + +#include "../ClangTidy.h" +#include "../utils/IncludeInserter.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" + +namespace clang { +namespace tidy { +namespace absl { + +// Find string.find(...) == 0 comparisons and suggest replacing with StartsWith. +class StringFindStartswithCheck : public ClangTidyCheck { +public: + using ClangTidyCheck::ClangTidyCheck; + void registerPPCallbacks(CompilerInstance &compiler) override; + void registerMatchers(ast_matchers::MatchFinder *finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &result) override; + +private: + std::unique_ptr include_inserter_; +}; + +} // namespace absl +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_ABSL_STRING_FIND_STARTSWITH_H_ Index: clang-tidy/absl/StringFindStartswithCheck.cpp =================================================================== --- clang-tidy/absl/StringFindStartswithCheck.cpp +++ clang-tidy/absl/StringFindStartswithCheck.cpp @@ -0,0 +1,99 @@ +#include "StringFindStartswithCheck.h" + +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchers.h" +#include "clang/Frontend/CompilerInstance.h" + +using namespace clang::ast_matchers; // NOLINT: Too many names. + +namespace clang { +namespace tidy { +namespace absl { + +void StringFindStartswithCheck::registerMatchers(MatchFinder *finder) { + auto zero = integerLiteral(equals(0)); + // TODO(nikoweh/reviewers): There are a few other options here, + // like adding std::string or std::basic_string (?) + auto stringClassMatcher = anyOf(cxxRecordDecl(hasName("string")), + cxxRecordDecl(hasName("__versa_string")), + cxxRecordDecl(hasName("basic_string"))); + + auto stringFind = cxxMemberCallExpr( + // .find()-call on a string... + callee(cxxMethodDecl(hasName("find"), ofClass(stringClassMatcher))), + // ... with some search expression ... + hasArgument(0, expr().bind("needle")), + // ... and either "0" as second argument or the default argument (also 0). + anyOf(hasArgument(1, zero), hasArgument(1, cxxDefaultArgExpr()))); + + finder->addMatcher( + // Match [=!]= with a zero on one side and a string.find on the other. + binaryOperator( + anyOf(hasOperatorName("=="), hasOperatorName("!=")), + hasEitherOperand(ignoringParenImpCasts(zero)), + hasEitherOperand(ignoringParenImpCasts(stringFind.bind("findexpr")))) + .bind("expr"), + this); +} + +void StringFindStartswithCheck::check(const MatchFinder::MatchResult &result) { + const auto &context = *result.Context; + const auto &source = context.getSourceManager(); + + // Extract matching (sub)expressions + const auto *expr = result.Nodes.getNodeAs("expr"); + const auto *needle = result.Nodes.getNodeAs("needle"); + const auto *haystack = result.Nodes.getNodeAs("findexpr") + ->getImplicitObjectArgument(); + + // Get the source code blocks (as characters) for both the string object + // and the search expression + const StringRef needle_expr_code = Lexer::getSourceText( + CharSourceRange::getTokenRange(needle->getSourceRange()), source, + context.getLangOpts()); + const StringRef haystack_expr_code = Lexer::getSourceText( + CharSourceRange::getTokenRange(haystack->getSourceRange()), source, + context.getLangOpts()); + + // Create the StartsWith string, negating if comparison was "!=". + bool neg = expr->getOpcodeStr() == "!="; + StringRef startswith_str; + if (neg) { + startswith_str = "!absl::StartsWith"; + } else { + startswith_str = "absl::StartsWith"; + } + + // Create the warning message and a FixIt hint replacing the original expr. + auto diag_out = diag(expr->getLocStart(), + (StringRef("Use ") + startswith_str + + " instead of find() " + expr->getOpcodeStr() + " 0") + .str()) + << FixItHint::CreateReplacement(expr->getSourceRange(), + (startswith_str + "(" + + haystack_expr_code + ", " + + needle_expr_code + ")") + .str()); + + // Create a preprocessor #include FixIt hint (CreateIncludeInsertion checks + // whether this already exists). + auto include_hint = include_inserter_->CreateIncludeInsertion( + source.getFileID(expr->getLocStart()), "third_party/absl/strings/match.h", + false); + if (include_hint) { + diag_out << *include_hint; + } +} + +void StringFindStartswithCheck::registerPPCallbacks( + CompilerInstance &compiler) { + include_inserter_ = llvm::make_unique( + compiler.getSourceManager(), compiler.getLangOpts(), + clang::tidy::utils::IncludeSorter::IS_Google); + compiler.getPreprocessor().addPPCallbacks( + include_inserter_->CreatePPCallbacks()); +} + +} // namespace absl +} // namespace tidy +} // namespace clang Index: clang-tidy/plugin/CMakeLists.txt =================================================================== --- clang-tidy/plugin/CMakeLists.txt +++ clang-tidy/plugin/CMakeLists.txt @@ -9,6 +9,7 @@ clangSema clangTidy clangTidyAndroidModule + clangTidyAbslModule clangTidyBoostModule clangTidyCERTModule clangTidyCppCoreGuidelinesModule Index: clang-tidy/tool/CMakeLists.txt =================================================================== --- clang-tidy/tool/CMakeLists.txt +++ clang-tidy/tool/CMakeLists.txt @@ -18,6 +18,7 @@ clangBasic clangTidy clangTidyAndroidModule + clangTidyAbslModule clangTidyBoostModule clangTidyBugproneModule clangTidyCERTModule Index: clang-tidy/tool/ClangTidyMain.cpp =================================================================== --- clang-tidy/tool/ClangTidyMain.cpp +++ clang-tidy/tool/ClangTidyMain.cpp @@ -462,6 +462,11 @@ static int LLVM_ATTRIBUTE_UNUSED CERTModuleAnchorDestination = CERTModuleAnchorSource; +// This anchor is used to force the linker to link the AbslModule. +extern volatile int AbslModuleAnchorSource; +static int LLVM_ATTRIBUTE_UNUSED AbslModuleAnchorDestination = + AbslModuleAnchorSource; + // This anchor is used to force the linker to link the BoostModule. extern volatile int BoostModuleAnchorSource; static int LLVM_ATTRIBUTE_UNUSED BoostModuleAnchorDestination = Index: docs/ReleaseNotes.rst =================================================================== --- docs/ReleaseNotes.rst +++ docs/ReleaseNotes.rst @@ -57,6 +57,12 @@ Improvements to clang-tidy -------------------------- +- New `absl-string-find-startswith + `_ check + + Checks whether a string::find result is compared with 0, and suggests + replacing with absl::StartsWith. + - New `fuchsia-statically-constructed-objects `_ check Index: docs/clang-tidy/checks/absl-string-find-startswith.rst =================================================================== --- docs/clang-tidy/checks/absl-string-find-startswith.rst +++ docs/clang-tidy/checks/absl-string-find-startswith.rst @@ -0,0 +1,18 @@ +.. title:: clang-tidy - absl-string-find-startswith + +absl-string-find-startswith +========================================== + +This check triggers on (in)equality comparisons between string.find() +and zero. Comparisons like this should be replaced with +absl::StartsWith(string, prefix). This is both a readability and a +performance issue. + +:: + + string s = "..."; + if (s.find("Hello World") == 0) { /* do something */ } + +should be replaced with +``if (absl::StartsWith(s, "Hello World")) { /* do something */ };`` + Index: docs/clang-tidy/checks/list.rst =================================================================== --- docs/clang-tidy/checks/list.rst +++ docs/clang-tidy/checks/list.rst @@ -4,6 +4,7 @@ ================= .. toctree:: + absl-string-find-startswith android-cloexec-accept android-cloexec-accept4 android-cloexec-creat Index: test/clang-tidy/absl-string-find-startswith.cpp =================================================================== --- test/clang-tidy/absl-string-find-startswith.cpp +++ test/clang-tidy/absl-string-find-startswith.cpp @@ -0,0 +1,49 @@ +// RUN: %check_clang_tidy %s absl-string-find-startswith %t -- -- -I%S +// -std=c++11 + +namespace std { +template class allocator {}; +template class char_traits {}; +template , + typename A = std::allocator> +struct basic_string { + basic_string(); + basic_string(const basic_string &); + basic_string(const C *, const A &a = A()); + ~basic_string(); + int find(basic_string s, int pos = 0); + int find(const char *s, int pos = 0); +}; +typedef basic_string string; +typedef basic_string wstring; +} // namespace std + +std::string foo(std::string); +std::string bar(); + +void tests(std::string s) { + s.find("a") == 0; + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: Use absl::StartsWith instead of + // find() == 0 [absl-string-find-startswith] CHECK_FIXES: + // {{^}}absl::StartsWith(s, "a");{{$}} + + s.find(s) == 0; + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: Use absl::StartsWith instead of + // find() == 0 [absl-string-find-startswith] CHECK_FIXES: + // {{^}}absl::StartsWith(s, s);{{$}} + + s.find("aaa") != 0; + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: Use !absl::StartsWith instead of + // find() != 0 [absl-string-find-startswith] CHECK_FIXES: + // {{^}}!absl::StartsWith(s, "aaa");{{$}} + + s.find(foo(foo(bar()))) != 0; + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: Use !absl::StartsWith instead of + // find() != 0 [absl-string-find-startswith] CHECK_FIXES: + // {{^}}!absl::StartsWith(s, foo(foo(foo)));{{$}} + + // expressions that don't trigger the check are here. + s.find("a", 1) == 0; + s.find("a", 1) == 1; + s.find("a") == 1; +}