[clang-tidy] Add check: replace string::find(...) == 0 with absl::StartsWith

Patch by Niko Weh!

Reviewers: hokein

Subscribers: klimek, cfe-commits, ioeric, ilya-biryukov, ahedberg

Differential Revision: https://reviews.llvm.org/D43847

llvm-svn: 327111
This commit is contained in:
Haojian Wu 2018-03-09 10:47:14 +00:00
parent 5f56fca4e1
commit 40571b7c1c
12 changed files with 347 additions and 0 deletions

View File

@ -27,6 +27,7 @@ add_clang_library(clangTidy
)
add_subdirectory(android)
add_subdirectory(abseil)
add_subdirectory(boost)
add_subdirectory(bugprone)
add_subdirectory(cert)

View File

@ -0,0 +1,38 @@
//===------- AbseilTidyModule.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"
namespace clang {
namespace tidy {
namespace abseil {
class AbseilModule : public ClangTidyModule {
public:
void addCheckFactories(ClangTidyCheckFactories &CheckFactories) override {
CheckFactories.registerCheck<StringFindStartswithCheck>(
"abseil-string-find-startswith");
}
};
// Register the AbseilModule using this statically initialized variable.
static ClangTidyModuleRegistry::Add<AbseilModule> X("abseil-module",
"Add Abseil checks.");
} // namespace abseil
// This anchor is used to force the linker to link in the generated object file
// and thus register the AbseilModule.
volatile int AbseilModuleAnchorSource = 0;
} // namespace tidy
} // namespace clang

View File

@ -0,0 +1,14 @@
set(LLVM_LINK_COMPONENTS support)
add_clang_library(clangTidyAbseilModule
AbseilTidyModule.cpp
StringFindStartswithCheck.cpp
LINK_LIBS
clangAST
clangASTMatchers
clangBasic
clangLex
clangTidy
clangTidyUtils
)

View File

@ -0,0 +1,133 @@
//===--- StringFindStartswithCheck.cc - clang-tidy---------------*- C++ -*-===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
#include "StringFindStartswithCheck.h"
#include "../utils/OptionsUtils.h"
#include "clang/AST/ASTContext.h"
#include "clang/ASTMatchers/ASTMatchers.h"
#include "clang/Frontend/CompilerInstance.h"
#include <cassert>
using namespace clang::ast_matchers;
namespace clang {
namespace tidy {
namespace abseil {
StringFindStartswithCheck::StringFindStartswithCheck(StringRef Name,
ClangTidyContext *Context)
: ClangTidyCheck(Name, Context),
StringLikeClasses(utils::options::parseStringList(
Options.get("StringLikeClasses", "::std::basic_string"))),
IncludeStyle(utils::IncludeSorter::parseIncludeStyle(
Options.getLocalOrGlobal("IncludeStyle", "llvm"))),
AbseilStringsMatchHeader(
Options.get("AbseilStringsMatchHeader", "absl/strings/match.h")) {}
void StringFindStartswithCheck::registerMatchers(MatchFinder *Finder) {
auto ZeroLiteral = integerLiteral(equals(0));
auto StringClassMatcher = cxxRecordDecl(hasAnyName(SmallVector<StringRef, 4>(
StringLikeClasses.begin(), StringLikeClasses.end())));
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, ZeroLiteral), 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(ZeroLiteral)),
hasEitherOperand(ignoringParenImpCasts(StringFind.bind("findexpr"))))
.bind("expr"),
this);
}
void StringFindStartswithCheck::check(const MatchFinder::MatchResult &Result) {
const ASTContext &Context = *Result.Context;
const SourceManager &Source = Context.getSourceManager();
// Extract matching (sub)expressions
const auto *ComparisonExpr = Result.Nodes.getNodeAs<BinaryOperator>("expr");
assert(ComparisonExpr != nullptr);
const auto *Needle = Result.Nodes.getNodeAs<Expr>("needle");
assert(Needle != nullptr);
const Expr *Haystack = Result.Nodes.getNodeAs<CXXMemberCallExpr>("findexpr")
->getImplicitObjectArgument();
assert(Haystack != nullptr);
if (ComparisonExpr->getLocStart().isMacroID())
return;
// Get the source code blocks (as characters) for both the string object
// and the search expression
const StringRef NeedleExprCode = Lexer::getSourceText(
CharSourceRange::getTokenRange(Needle->getSourceRange()), Source,
Context.getLangOpts());
const StringRef HaystackExprCode = Lexer::getSourceText(
CharSourceRange::getTokenRange(Haystack->getSourceRange()), Source,
Context.getLangOpts());
// Create the StartsWith string, negating if comparison was "!=".
bool Neg = ComparisonExpr->getOpcodeStr() == "!=";
StringRef StartswithStr;
if (Neg) {
StartswithStr = "!absl::StartsWith";
} else {
StartswithStr = "absl::StartsWith";
}
// Create the warning message and a FixIt hint replacing the original expr.
auto Diagnostic =
diag(ComparisonExpr->getLocStart(),
(StringRef("use ") + StartswithStr + " instead of find() " +
ComparisonExpr->getOpcodeStr() + " 0")
.str());
Diagnostic << FixItHint::CreateReplacement(
ComparisonExpr->getSourceRange(),
(StartswithStr + "(" + HaystackExprCode + ", " + NeedleExprCode + ")")
.str());
// Create a preprocessor #include FixIt hint (CreateIncludeInsertion checks
// whether this already exists).
auto IncludeHint = IncludeInserter->CreateIncludeInsertion(
Source.getFileID(ComparisonExpr->getLocStart()), AbseilStringsMatchHeader,
false);
if (IncludeHint) {
Diagnostic << *IncludeHint;
}
}
void StringFindStartswithCheck::registerPPCallbacks(
CompilerInstance &Compiler) {
IncludeInserter = llvm::make_unique<clang::tidy::utils::IncludeInserter>(
Compiler.getSourceManager(), Compiler.getLangOpts(), IncludeStyle);
Compiler.getPreprocessor().addPPCallbacks(
IncludeInserter->CreatePPCallbacks());
}
void StringFindStartswithCheck::storeOptions(
ClangTidyOptions::OptionMap &Opts) {
Options.store(Opts, "StringLikeClasses",
utils::options::serializeStringList(StringLikeClasses));
Options.store(Opts, "IncludeStyle",
utils::IncludeSorter::toString(IncludeStyle));
Options.store(Opts, "AbseilStringsMatchHeader", AbseilStringsMatchHeader);
}
} // namespace abseil
} // namespace tidy
} // namespace clang

View File

@ -0,0 +1,48 @@
//===--- 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_ABSEIL_STRING_FIND_STARTSWITH_H
#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_ABSEIL_STRING_FIND_STARTSWITH_H_
#include "../ClangTidy.h"
#include "../utils/IncludeInserter.h"
#include "clang/ASTMatchers/ASTMatchFinder.h"
#include <memory>
#include <string>
#include <vector>
namespace clang {
namespace tidy {
namespace abseil {
// Find string.find(...) == 0 comparisons and suggest replacing with StartsWith.
// FIXME(niko): Add similar check for EndsWith
// FIXME(niko): Add equivalent modernize checks for C++20's std::starts_With
class StringFindStartswithCheck : public ClangTidyCheck {
public:
using ClangTidyCheck::ClangTidyCheck;
StringFindStartswithCheck(StringRef Name, ClangTidyContext *Context);
void registerPPCallbacks(CompilerInstance &Compiler) override;
void registerMatchers(ast_matchers::MatchFinder *Finder) override;
void check(const ast_matchers::MatchFinder::MatchResult &Result) override;
void storeOptions(ClangTidyOptions::OptionMap &Opts) override;
private:
std::unique_ptr<clang::tidy::utils::IncludeInserter> IncludeInserter;
const std::vector<std::string> StringLikeClasses;
const utils::IncludeSorter::IncludeStyle IncludeStyle;
const std::string AbseilStringsMatchHeader;
};
} // namespace abseil
} // namespace tidy
} // namespace clang
#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_ABSEIL_STRING_FIND_STARTSWITH_H_

View File

@ -9,6 +9,7 @@ add_clang_library(clangTidyPlugin
clangSema
clangTidy
clangTidyAndroidModule
clangTidyAbseilModule
clangTidyBoostModule
clangTidyCERTModule
clangTidyCppCoreGuidelinesModule

View File

@ -18,6 +18,7 @@ target_link_libraries(clang-tidy
clangBasic
clangTidy
clangTidyAndroidModule
clangTidyAbseilModule
clangTidyBoostModule
clangTidyBugproneModule
clangTidyCERTModule

View File

@ -499,6 +499,11 @@ extern volatile int CERTModuleAnchorSource;
static int LLVM_ATTRIBUTE_UNUSED CERTModuleAnchorDestination =
CERTModuleAnchorSource;
// This anchor is used to force the linker to link the AbseilModule.
extern volatile int AbseilModuleAnchorSource;
static int LLVM_ATTRIBUTE_UNUSED AbseilModuleAnchorDestination =
AbseilModuleAnchorSource;
// This anchor is used to force the linker to link the BoostModule.
extern volatile int BoostModuleAnchorSource;
static int LLVM_ATTRIBUTE_UNUSED BoostModuleAnchorDestination =

View File

@ -77,6 +77,15 @@ Improvements to clang-tidy
Warns if a class inherits from multiple classes that are not pure virtual.
- New `abseil` module for checks related to the `Abseil <https://abseil.io>`_
library.
- New `abseil-string-find-startswith
<http://clang.llvm.org/extra/clang-tidy/checks/abseil-string-find-startswith.html>`_ check
Checks whether a ``std::string::find()`` result is compared with 0, and
suggests replacing with ``absl::StartsWith()``.
- New `fuchsia-statically-constructed-objects
<http://clang.llvm.org/extra/clang-tidy/checks/fuchsia-statically-constructed-objects.html>`_ check

View File

@ -0,0 +1,41 @@
.. title:: clang-tidy - abseil-string-find-startswith
abseil-string-find-startswith
=============================
Checks whether a ``std::string::find()`` result is compared with 0, and
suggests replacing with ``absl::StartsWith()``. This is both a readability and
performance issue.
.. code-block:: c++
string s = "...";
if (s.find("Hello World") == 0) { /* do something */ }
becomes
.. code-block:: c++
string s = "...";
if (absl::StartsWith(s, "Hello World")) { /* do something */ }
Options
-------
.. option:: StringLikeClasses
Semicolon-separated list of names of string-like classes. By default only
``std::basic_string`` is considered. The list of methods to considered is
fixed.
.. option:: IncludeStyle
A string specifying which include-style is used, `llvm` or `google`. Default
is `llvm`.
.. option:: AbseilStringsMatchHeader
The location of Abseil's ``strings/match.h``. Defaults to
``absl/strings/match.h``.

View File

@ -4,6 +4,7 @@ Clang-Tidy Checks
=================
.. toctree::
abseil-string-find-startswith
android-cloexec-accept
android-cloexec-accept4
android-cloexec-creat

View File

@ -0,0 +1,55 @@
// RUN: %check_clang_tidy %s abseil-string-find-startswith %t
namespace std {
template <typename T> class allocator {};
template <typename T> class char_traits {};
template <typename C, typename T = std::char_traits<C>,
typename A = std::allocator<C>>
struct basic_string {
basic_string();
basic_string(const basic_string &);
basic_string(const C *, const A &a = A());
~basic_string();
int find(basic_string<C> s, int pos = 0);
int find(const char *s, int pos = 0);
};
typedef basic_string<char> string;
typedef basic_string<wchar_t> wstring;
} // namespace std
std::string foo(std::string);
std::string bar();
#define A_MACRO(x, y) ((x) == (y))
void tests(std::string s) {
s.find("a") == 0;
// CHECK-MESSAGES: :[[@LINE-1]]:3: warning: use absl::StartsWith instead of find() == 0 [abseil-string-find-startswith]
// CHECK-FIXES: {{^[[:space:]]*}}absl::StartsWith(s, "a");{{$}}
s.find(s) == 0;
// CHECK-MESSAGES: :[[@LINE-1]]:3: warning: use absl::StartsWith
// CHECK-FIXES: {{^[[:space:]]*}}absl::StartsWith(s, s);{{$}}
s.find("aaa") != 0;
// CHECK-MESSAGES: :[[@LINE-1]]:3: warning: use !absl::StartsWith
// CHECK-FIXES: {{^[[:space:]]*}}!absl::StartsWith(s, "aaa");{{$}}
s.find(foo(foo(bar()))) != 0;
// CHECK-MESSAGES: :[[@LINE-1]]:3: warning: use !absl::StartsWith
// CHECK-FIXES: {{^[[:space:]]*}}!absl::StartsWith(s, foo(foo(bar())));{{$}}
if (s.find("....") == 0) { /* do something */ }
// CHECK-MESSAGES: :[[@LINE-1]]:7: warning: use absl::StartsWith
// CHECK-FIXES: {{^[[:space:]]*}}if (absl::StartsWith(s, "....")) { /* do something */ }{{$}}
0 != s.find("a");
// CHECK-MESSAGES: :[[@LINE-1]]:3: warning: use !absl::StartsWith
// CHECK-FIXES: {{^[[:space:]]*}}!absl::StartsWith(s, "a");{{$}}
// expressions that don't trigger the check are here.
A_MACRO(s.find("a"), 0);
s.find("a", 1) == 0;
s.find("a", 1) == 1;
s.find("a") == 1;
}