140571b7cSHaojian Wu //===--- StringFindStartswithCheck.cc - clang-tidy---------------*- C++ -*-===//
240571b7cSHaojian Wu //
32946cd70SChandler Carruth // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
42946cd70SChandler Carruth // See https://llvm.org/LICENSE.txt for license information.
52946cd70SChandler Carruth // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
640571b7cSHaojian Wu //
740571b7cSHaojian Wu //===----------------------------------------------------------------------===//
840571b7cSHaojian Wu
940571b7cSHaojian Wu #include "StringFindStartswithCheck.h"
1040571b7cSHaojian Wu
1140571b7cSHaojian Wu #include "../utils/OptionsUtils.h"
1240571b7cSHaojian Wu #include "clang/AST/ASTContext.h"
1340571b7cSHaojian Wu #include "clang/ASTMatchers/ASTMatchers.h"
1440571b7cSHaojian Wu #include "clang/Frontend/CompilerInstance.h"
15860aefd0SNathan James #include "clang/Lex/Lexer.h"
16860aefd0SNathan James #include "clang/Lex/Preprocessor.h"
1740571b7cSHaojian Wu
1840571b7cSHaojian Wu using namespace clang::ast_matchers;
1940571b7cSHaojian Wu
207d2ea6c4SCarlos Galvez namespace clang::tidy::abseil {
2140571b7cSHaojian Wu
22*2aec8668SNicolas van Kempen const auto DefaultStringLikeClasses =
23*2aec8668SNicolas van Kempen "::std::basic_string;::std::basic_string_view";
24*2aec8668SNicolas van Kempen
StringFindStartswithCheck(StringRef Name,ClangTidyContext * Context)2540571b7cSHaojian Wu StringFindStartswithCheck::StringFindStartswithCheck(StringRef Name,
2640571b7cSHaojian Wu ClangTidyContext *Context)
2740571b7cSHaojian Wu : ClangTidyCheck(Name, Context),
2840571b7cSHaojian Wu StringLikeClasses(utils::options::parseStringList(
29*2aec8668SNicolas van Kempen Options.get("StringLikeClasses", DefaultStringLikeClasses))),
3013c9bbc2SNathan James IncludeInserter(Options.getLocalOrGlobal("IncludeStyle",
31b859c39cSNathan James utils::IncludeSorter::IS_LLVM),
32b859c39cSNathan James areDiagsSelfContained()),
3340571b7cSHaojian Wu AbseilStringsMatchHeader(
3440571b7cSHaojian Wu Options.get("AbseilStringsMatchHeader", "absl/strings/match.h")) {}
3540571b7cSHaojian Wu
registerMatchers(MatchFinder * Finder)3640571b7cSHaojian Wu void StringFindStartswithCheck::registerMatchers(MatchFinder *Finder) {
3740571b7cSHaojian Wu auto ZeroLiteral = integerLiteral(equals(0));
3812cb5405SNathan James auto StringClassMatcher = cxxRecordDecl(hasAnyName(StringLikeClasses));
3907996a54SHaojian Wu auto StringType = hasUnqualifiedDesugaredType(
4007996a54SHaojian Wu recordType(hasDeclaration(StringClassMatcher)));
4140571b7cSHaojian Wu
4240571b7cSHaojian Wu auto StringFind = cxxMemberCallExpr(
4340571b7cSHaojian Wu // .find()-call on a string...
44fd8fc5e8SIvan Gerasimov callee(cxxMethodDecl(hasName("find")).bind("findfun")),
4507996a54SHaojian Wu on(hasType(StringType)),
4640571b7cSHaojian Wu // ... with some search expression ...
4740571b7cSHaojian Wu hasArgument(0, expr().bind("needle")),
4840571b7cSHaojian Wu // ... and either "0" as second argument or the default argument (also 0).
4940571b7cSHaojian Wu anyOf(hasArgument(1, ZeroLiteral), hasArgument(1, cxxDefaultArgExpr())));
5040571b7cSHaojian Wu
5140571b7cSHaojian Wu Finder->addMatcher(
5240571b7cSHaojian Wu // Match [=!]= with a zero on one side and a string.find on the other.
5340571b7cSHaojian Wu binaryOperator(
5497572fa6SNathan James hasAnyOperatorName("==", "!="),
554f0cc10bSNathan James hasOperands(ignoringParenImpCasts(ZeroLiteral),
564f0cc10bSNathan James ignoringParenImpCasts(StringFind.bind("findexpr"))))
5740571b7cSHaojian Wu .bind("expr"),
5840571b7cSHaojian Wu this);
59fd8fc5e8SIvan Gerasimov
60fd8fc5e8SIvan Gerasimov auto StringRFind = cxxMemberCallExpr(
61fd8fc5e8SIvan Gerasimov // .rfind()-call on a string...
62fd8fc5e8SIvan Gerasimov callee(cxxMethodDecl(hasName("rfind")).bind("findfun")),
63fd8fc5e8SIvan Gerasimov on(hasType(StringType)),
64fd8fc5e8SIvan Gerasimov // ... with some search expression ...
65fd8fc5e8SIvan Gerasimov hasArgument(0, expr().bind("needle")),
66fd8fc5e8SIvan Gerasimov // ... and "0" as second argument.
67fd8fc5e8SIvan Gerasimov hasArgument(1, ZeroLiteral));
68fd8fc5e8SIvan Gerasimov
69fd8fc5e8SIvan Gerasimov Finder->addMatcher(
70fd8fc5e8SIvan Gerasimov // Match [=!]= with either a zero or npos on one side and a string.rfind
71fd8fc5e8SIvan Gerasimov // on the other.
72fd8fc5e8SIvan Gerasimov binaryOperator(
73fd8fc5e8SIvan Gerasimov hasAnyOperatorName("==", "!="),
74fd8fc5e8SIvan Gerasimov hasOperands(ignoringParenImpCasts(ZeroLiteral),
75fd8fc5e8SIvan Gerasimov ignoringParenImpCasts(StringRFind.bind("findexpr"))))
76fd8fc5e8SIvan Gerasimov .bind("expr"),
77fd8fc5e8SIvan Gerasimov this);
7840571b7cSHaojian Wu }
7940571b7cSHaojian Wu
check(const MatchFinder::MatchResult & Result)8040571b7cSHaojian Wu void StringFindStartswithCheck::check(const MatchFinder::MatchResult &Result) {
8140571b7cSHaojian Wu const ASTContext &Context = *Result.Context;
8240571b7cSHaojian Wu const SourceManager &Source = Context.getSourceManager();
8340571b7cSHaojian Wu
8440571b7cSHaojian Wu // Extract matching (sub)expressions
8540571b7cSHaojian Wu const auto *ComparisonExpr = Result.Nodes.getNodeAs<BinaryOperator>("expr");
8640571b7cSHaojian Wu assert(ComparisonExpr != nullptr);
8740571b7cSHaojian Wu const auto *Needle = Result.Nodes.getNodeAs<Expr>("needle");
8840571b7cSHaojian Wu assert(Needle != nullptr);
8940571b7cSHaojian Wu const Expr *Haystack = Result.Nodes.getNodeAs<CXXMemberCallExpr>("findexpr")
9040571b7cSHaojian Wu ->getImplicitObjectArgument();
9140571b7cSHaojian Wu assert(Haystack != nullptr);
92fc2a9ad1SPiotr Zegar const auto *FindFun = Result.Nodes.getNodeAs<CXXMethodDecl>("findfun");
93fd8fc5e8SIvan Gerasimov assert(FindFun != nullptr);
94fd8fc5e8SIvan Gerasimov
95fd8fc5e8SIvan Gerasimov bool Rev = FindFun->getName().contains("rfind");
9640571b7cSHaojian Wu
9743465bf3SStephen Kelly if (ComparisonExpr->getBeginLoc().isMacroID())
9840571b7cSHaojian Wu return;
9940571b7cSHaojian Wu
10040571b7cSHaojian Wu // Get the source code blocks (as characters) for both the string object
10140571b7cSHaojian Wu // and the search expression
10240571b7cSHaojian Wu const StringRef NeedleExprCode = Lexer::getSourceText(
10340571b7cSHaojian Wu CharSourceRange::getTokenRange(Needle->getSourceRange()), Source,
10440571b7cSHaojian Wu Context.getLangOpts());
10540571b7cSHaojian Wu const StringRef HaystackExprCode = Lexer::getSourceText(
10640571b7cSHaojian Wu CharSourceRange::getTokenRange(Haystack->getSourceRange()), Source,
10740571b7cSHaojian Wu Context.getLangOpts());
10840571b7cSHaojian Wu
10940571b7cSHaojian Wu // Create the StartsWith string, negating if comparison was "!=".
1101a721b6aSNathan James bool Neg = ComparisonExpr->getOpcode() == BO_NE;
11140571b7cSHaojian Wu
11240571b7cSHaojian Wu // Create the warning message and a FixIt hint replacing the original expr.
113fd8fc5e8SIvan Gerasimov auto Diagnostic =
114fd8fc5e8SIvan Gerasimov diag(ComparisonExpr->getBeginLoc(),
1151a721b6aSNathan James "use %select{absl::StartsWith|!absl::StartsWith}0 "
116fd8fc5e8SIvan Gerasimov "instead of %select{find()|rfind()}1 %select{==|!=}0 0")
117fd8fc5e8SIvan Gerasimov << Neg << Rev;
11840571b7cSHaojian Wu
11940571b7cSHaojian Wu Diagnostic << FixItHint::CreateReplacement(
12040571b7cSHaojian Wu ComparisonExpr->getSourceRange(),
1211a721b6aSNathan James ((Neg ? "!absl::StartsWith(" : "absl::StartsWith(") + HaystackExprCode +
1221a721b6aSNathan James ", " + NeedleExprCode + ")")
12340571b7cSHaojian Wu .str());
12440571b7cSHaojian Wu
125ade0662cSSalman Javed // Create a preprocessor #include FixIt hint (createIncludeInsertion checks
12640571b7cSHaojian Wu // whether this already exists).
12713c9bbc2SNathan James Diagnostic << IncludeInserter.createIncludeInsertion(
128fdfe324dSAlexander Kornienko Source.getFileID(ComparisonExpr->getBeginLoc()),
129fdfe324dSAlexander Kornienko AbseilStringsMatchHeader);
13040571b7cSHaojian Wu }
13140571b7cSHaojian Wu
registerPPCallbacks(const SourceManager & SM,Preprocessor * PP,Preprocessor * ModuleExpanderPP)13240571b7cSHaojian Wu void StringFindStartswithCheck::registerPPCallbacks(
133b6c4db99SAlexander Kornienko const SourceManager &SM, Preprocessor *PP, Preprocessor *ModuleExpanderPP) {
13413c9bbc2SNathan James IncludeInserter.registerPreprocessor(PP);
13540571b7cSHaojian Wu }
13640571b7cSHaojian Wu
storeOptions(ClangTidyOptions::OptionMap & Opts)13740571b7cSHaojian Wu void StringFindStartswithCheck::storeOptions(
13840571b7cSHaojian Wu ClangTidyOptions::OptionMap &Opts) {
13940571b7cSHaojian Wu Options.store(Opts, "StringLikeClasses",
14040571b7cSHaojian Wu utils::options::serializeStringList(StringLikeClasses));
14113c9bbc2SNathan James Options.store(Opts, "IncludeStyle", IncludeInserter.getStyle());
14240571b7cSHaojian Wu Options.store(Opts, "AbseilStringsMatchHeader", AbseilStringsMatchHeader);
14340571b7cSHaojian Wu }
14440571b7cSHaojian Wu
1457d2ea6c4SCarlos Galvez } // namespace clang::tidy::abseil
146