clang-tools  10.0.0git
StringFindStartswithCheck.cpp
Go to the documentation of this file.
1 //===--- StringFindStartswithCheck.cc - clang-tidy---------------*- C++ -*-===//
2 //
3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4 // See https://llvm.org/LICENSE.txt for license information.
5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6 //
7 //===----------------------------------------------------------------------===//
8 
10 
11 #include "../utils/OptionsUtils.h"
12 #include "clang/AST/ASTContext.h"
13 #include "clang/ASTMatchers/ASTMatchers.h"
14 #include "clang/Frontend/CompilerInstance.h"
15 
16 #include <cassert>
17 
18 using namespace clang::ast_matchers;
19 
20 namespace clang {
21 namespace tidy {
22 namespace abseil {
23 
24 StringFindStartswithCheck::StringFindStartswithCheck(StringRef Name,
25  ClangTidyContext *Context)
26  : ClangTidyCheck(Name, Context),
27  StringLikeClasses(utils::options::parseStringList(
28  Options.get("StringLikeClasses", "::std::basic_string"))),
29  IncludeStyle(utils::IncludeSorter::parseIncludeStyle(
30  Options.getLocalOrGlobal("IncludeStyle", "llvm"))),
31  AbseilStringsMatchHeader(
32  Options.get("AbseilStringsMatchHeader", "absl/strings/match.h")) {}
33 
35  auto ZeroLiteral = integerLiteral(equals(0));
36  auto StringClassMatcher = cxxRecordDecl(hasAnyName(SmallVector<StringRef, 4>(
37  StringLikeClasses.begin(), StringLikeClasses.end())));
38  auto StringType = hasUnqualifiedDesugaredType(
39  recordType(hasDeclaration(StringClassMatcher)));
40 
41  auto StringFind = cxxMemberCallExpr(
42  // .find()-call on a string...
43  callee(cxxMethodDecl(hasName("find"))),
44  on(hasType(StringType)),
45  // ... with some search expression ...
46  hasArgument(0, expr().bind("needle")),
47  // ... and either "0" as second argument or the default argument (also 0).
48  anyOf(hasArgument(1, ZeroLiteral), hasArgument(1, cxxDefaultArgExpr())));
49 
50  Finder->addMatcher(
51  // Match [=!]= with a zero on one side and a string.find on the other.
52  binaryOperator(
53  anyOf(hasOperatorName("=="), hasOperatorName("!=")),
54  hasEitherOperand(ignoringParenImpCasts(ZeroLiteral)),
55  hasEitherOperand(ignoringParenImpCasts(StringFind.bind("findexpr"))))
56  .bind("expr"),
57  this);
58 }
59 
60 void StringFindStartswithCheck::check(const MatchFinder::MatchResult &Result) {
61  const ASTContext &Context = *Result.Context;
62  const SourceManager &Source = Context.getSourceManager();
63 
64  // Extract matching (sub)expressions
65  const auto *ComparisonExpr = Result.Nodes.getNodeAs<BinaryOperator>("expr");
66  assert(ComparisonExpr != nullptr);
67  const auto *Needle = Result.Nodes.getNodeAs<Expr>("needle");
68  assert(Needle != nullptr);
69  const Expr *Haystack = Result.Nodes.getNodeAs<CXXMemberCallExpr>("findexpr")
70  ->getImplicitObjectArgument();
71  assert(Haystack != nullptr);
72 
73  if (ComparisonExpr->getBeginLoc().isMacroID())
74  return;
75 
76  // Get the source code blocks (as characters) for both the string object
77  // and the search expression
78  const StringRef NeedleExprCode = Lexer::getSourceText(
79  CharSourceRange::getTokenRange(Needle->getSourceRange()), Source,
80  Context.getLangOpts());
81  const StringRef HaystackExprCode = Lexer::getSourceText(
82  CharSourceRange::getTokenRange(Haystack->getSourceRange()), Source,
83  Context.getLangOpts());
84 
85  // Create the StartsWith string, negating if comparison was "!=".
86  bool Neg = ComparisonExpr->getOpcodeStr() == "!=";
87  StringRef StartswithStr;
88  if (Neg) {
89  StartswithStr = "!absl::StartsWith";
90  } else {
91  StartswithStr = "absl::StartsWith";
92  }
93 
94  // Create the warning message and a FixIt hint replacing the original expr.
95  auto Diagnostic =
96  diag(ComparisonExpr->getBeginLoc(),
97  (StringRef("use ") + StartswithStr + " instead of find() " +
98  ComparisonExpr->getOpcodeStr() + " 0")
99  .str());
100 
101  Diagnostic << FixItHint::CreateReplacement(
102  ComparisonExpr->getSourceRange(),
103  (StartswithStr + "(" + HaystackExprCode + ", " + NeedleExprCode + ")")
104  .str());
105 
106  // Create a preprocessor #include FixIt hint (CreateIncludeInsertion checks
107  // whether this already exists).
108  auto IncludeHint = IncludeInserter->CreateIncludeInsertion(
109  Source.getFileID(ComparisonExpr->getBeginLoc()), AbseilStringsMatchHeader,
110  false);
111  if (IncludeHint) {
112  Diagnostic << *IncludeHint;
113  }
114 }
115 
117  const SourceManager &SM, Preprocessor *PP, Preprocessor *ModuleExpanderPP) {
118  IncludeInserter = std::make_unique<utils::IncludeInserter>(SM, getLangOpts(),
119  IncludeStyle);
120  PP->addPPCallbacks(IncludeInserter->CreatePPCallbacks());
121 }
122 
125  Options.store(Opts, "StringLikeClasses",
126  utils::options::serializeStringList(StringLikeClasses));
127  Options.store(Opts, "IncludeStyle",
128  utils::IncludeSorter::toString(IncludeStyle));
129  Options.store(Opts, "AbseilStringsMatchHeader", AbseilStringsMatchHeader);
130 }
131 
132 } // namespace abseil
133 } // namespace tidy
134 } // namespace clang
void check(const ast_matchers::MatchFinder::MatchResult &Result) override
ClangTidyChecks that register ASTMatchers should do the actual work in here.
std::string serializeStringList(ArrayRef< std::string > Strings)
Serialize a sequence of names that can be parsed by parseStringList.
void storeOptions(ClangTidyOptions::OptionMap &Opts) override
Should store all options supported by this check with their current values or default values for opti...
static StringRef toString(IncludeStyle Style)
Converts IncludeStyle to string representation.
Base class for all clang-tidy checks.
const LangOptions & getLangOpts() const
Returns the language options from the context.
void registerPPCallbacks(const SourceManager &SM, Preprocessor *PP, Preprocessor *ModuleExpanderPP) override
Override this to register PPCallbacks in the preprocessor.
std::vector< std::string > parseStringList(StringRef Option)
Parse a semicolon separated list of strings.
void store(ClangTidyOptions::OptionMap &Options, StringRef LocalName, StringRef Value) const
Stores an option with the check-local name LocalName with string value Value to Options.
static constexpr llvm::StringLiteral Name
std::map< std::string, std::string > OptionMap
llvm::Optional< Range > getTokenRange(const SourceManager &SM, const LangOptions &LangOpts, SourceLocation TokLoc)
Returns the taken range at TokLoc.
Definition: SourceCode.cpp:227
===– Representation.cpp - ClangDoc Representation --------—*- C++ -*-===//
Every ClangTidyCheck reports errors through a DiagnosticsEngine provided by this context.
DiagnosticBuilder diag(SourceLocation Loc, StringRef Description, DiagnosticIDs::Level Level=DiagnosticIDs::Warning)
Add a diagnostic with the check&#39;s name.
void registerMatchers(ast_matchers::MatchFinder *Finder) override
Override this to register AST matchers with Finder.