1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147
|
//===--- StringFindStartswithCheck.cc - 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
//
//===----------------------------------------------------------------------===//
#include "StringFindStartswithCheck.h"
#include "../utils/OptionsUtils.h"
#include "clang/AST/ASTContext.h"
#include "clang/ASTMatchers/ASTMatchers.h"
#include "clang/Frontend/CompilerInstance.h"
#include "clang/Lex/Lexer.h"
#include "clang/Lex/Preprocessor.h"
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"))),
IncludeInserter(Options.getLocalOrGlobal("IncludeStyle",
utils::IncludeSorter::IS_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 StringType = hasUnqualifiedDesugaredType(
recordType(hasDeclaration(StringClassMatcher)));
auto StringFind = cxxMemberCallExpr(
// .find()-call on a string...
callee(cxxMethodDecl(hasName("find")).bind("findfun")),
on(hasType(StringType)),
// ... 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(
hasAnyOperatorName("==", "!="),
hasOperands(ignoringParenImpCasts(ZeroLiteral),
ignoringParenImpCasts(StringFind.bind("findexpr"))))
.bind("expr"),
this);
auto StringRFind = cxxMemberCallExpr(
// .rfind()-call on a string...
callee(cxxMethodDecl(hasName("rfind")).bind("findfun")),
on(hasType(StringType)),
// ... with some search expression ...
hasArgument(0, expr().bind("needle")),
// ... and "0" as second argument.
hasArgument(1, ZeroLiteral));
Finder->addMatcher(
// Match [=!]= with either a zero or npos on one side and a string.rfind
// on the other.
binaryOperator(
hasAnyOperatorName("==", "!="),
hasOperands(ignoringParenImpCasts(ZeroLiteral),
ignoringParenImpCasts(StringRFind.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);
const CXXMethodDecl *FindFun =
Result.Nodes.getNodeAs<CXXMethodDecl>("findfun");
assert(FindFun != nullptr);
bool Rev = FindFun->getName().contains("rfind");
if (ComparisonExpr->getBeginLoc().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->getOpcode() == BO_NE;
// Create the warning message and a FixIt hint replacing the original expr.
auto Diagnostic =
diag(ComparisonExpr->getBeginLoc(),
"use %select{absl::StartsWith|!absl::StartsWith}0 "
"instead of %select{find()|rfind()}1 %select{==|!=}0 0")
<< Neg << Rev;
Diagnostic << FixItHint::CreateReplacement(
ComparisonExpr->getSourceRange(),
((Neg ? "!absl::StartsWith(" : "absl::StartsWith(") + HaystackExprCode +
", " + NeedleExprCode + ")")
.str());
// Create a preprocessor #include FixIt hint (createIncludeInsertion checks
// whether this already exists).
Diagnostic << IncludeInserter.createIncludeInsertion(
Source.getFileID(ComparisonExpr->getBeginLoc()),
AbseilStringsMatchHeader);
}
void StringFindStartswithCheck::registerPPCallbacks(
const SourceManager &SM, Preprocessor *PP, Preprocessor *ModuleExpanderPP) {
IncludeInserter.registerPreprocessor(PP);
}
void StringFindStartswithCheck::storeOptions(
ClangTidyOptions::OptionMap &Opts) {
Options.store(Opts, "StringLikeClasses",
utils::options::serializeStringList(StringLikeClasses));
Options.store(Opts, "IncludeStyle", IncludeInserter.getStyle());
Options.store(Opts, "AbseilStringsMatchHeader", AbseilStringsMatchHeader);
}
} // namespace abseil
} // namespace tidy
} // namespace clang
|