xref: /llvm-project/clang-tools-extra/clang-tidy/modernize/UseStartsEndsWithCheck.cpp (revision 2f02b5af6ecb973d3a7faad9b0daff22646e724d)
1bc8cff1dSNicolas van Kempen //===--- UseStartsEndsWithCheck.cpp - clang-tidy --------------------------===//
2bc8cff1dSNicolas van Kempen //
3bc8cff1dSNicolas van Kempen // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4bc8cff1dSNicolas van Kempen // See https://llvm.org/LICENSE.txt for license information.
5bc8cff1dSNicolas van Kempen // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6bc8cff1dSNicolas van Kempen //
7bc8cff1dSNicolas van Kempen //===----------------------------------------------------------------------===//
8bc8cff1dSNicolas van Kempen 
9bc8cff1dSNicolas van Kempen #include "UseStartsEndsWithCheck.h"
10bc8cff1dSNicolas van Kempen 
11f1367a47SNicolas van Kempen #include "../utils/ASTUtils.h"
1218b50189SJulian Schmidt #include "../utils/Matchers.h"
1318b50189SJulian Schmidt #include "clang/ASTMatchers/ASTMatchers.h"
14bc8cff1dSNicolas van Kempen #include "clang/Lex/Lexer.h"
15bc8cff1dSNicolas van Kempen 
16bc8cff1dSNicolas van Kempen #include <string>
17bc8cff1dSNicolas van Kempen 
18bc8cff1dSNicolas van Kempen using namespace clang::ast_matchers;
19bc8cff1dSNicolas van Kempen 
20bc8cff1dSNicolas van Kempen namespace clang::tidy::modernize {
2150844828SHelmut Januschka 
2250844828SHelmut Januschka static bool isNegativeComparison(const Expr *ComparisonExpr) {
23*2f02b5afSNicolas van Kempen   if (const auto *Op = llvm::dyn_cast<BinaryOperator>(ComparisonExpr))
24*2f02b5afSNicolas van Kempen     return Op->getOpcode() == BO_NE;
2550844828SHelmut Januschka 
2650844828SHelmut Januschka   if (const auto *Op = llvm::dyn_cast<CXXOperatorCallExpr>(ComparisonExpr))
2750844828SHelmut Januschka     return Op->getOperator() == OO_ExclaimEqual;
2850844828SHelmut Januschka 
29*2f02b5afSNicolas van Kempen   if (const auto *Op =
30*2f02b5afSNicolas van Kempen           llvm::dyn_cast<CXXRewrittenBinaryOperator>(ComparisonExpr))
31*2f02b5afSNicolas van Kempen     return Op->getOperator() == BO_NE;
32*2f02b5afSNicolas van Kempen 
3350844828SHelmut Januschka   return false;
3450844828SHelmut Januschka }
3550844828SHelmut Januschka 
36f1367a47SNicolas van Kempen struct NotLengthExprForStringNode {
37f1367a47SNicolas van Kempen   NotLengthExprForStringNode(std::string ID, DynTypedNode Node,
38f1367a47SNicolas van Kempen                              ASTContext *Context)
39f1367a47SNicolas van Kempen       : ID(std::move(ID)), Node(std::move(Node)), Context(Context) {}
40f1367a47SNicolas van Kempen   bool operator()(const internal::BoundNodesMap &Nodes) const {
41f1367a47SNicolas van Kempen     // Match a string literal and an integer size or strlen() call.
42f1367a47SNicolas van Kempen     if (const auto *StringLiteralNode = Nodes.getNodeAs<StringLiteral>(ID)) {
43f1367a47SNicolas van Kempen       if (const auto *IntegerLiteralSizeNode = Node.get<IntegerLiteral>()) {
44f1367a47SNicolas van Kempen         return StringLiteralNode->getLength() !=
45f1367a47SNicolas van Kempen                IntegerLiteralSizeNode->getValue().getZExtValue();
46f1367a47SNicolas van Kempen       }
47f1367a47SNicolas van Kempen 
48f1367a47SNicolas van Kempen       if (const auto *StrlenNode = Node.get<CallExpr>()) {
49f1367a47SNicolas van Kempen         if (StrlenNode->getDirectCallee()->getName() != "strlen" ||
50f1367a47SNicolas van Kempen             StrlenNode->getNumArgs() != 1) {
51f1367a47SNicolas van Kempen           return true;
52f1367a47SNicolas van Kempen         }
53f1367a47SNicolas van Kempen 
54f1367a47SNicolas van Kempen         if (const auto *StrlenArgNode = dyn_cast<StringLiteral>(
55f1367a47SNicolas van Kempen                 StrlenNode->getArg(0)->IgnoreParenImpCasts())) {
56f1367a47SNicolas van Kempen           return StrlenArgNode->getLength() != StringLiteralNode->getLength();
57f1367a47SNicolas van Kempen         }
58f1367a47SNicolas van Kempen       }
59f1367a47SNicolas van Kempen     }
60f1367a47SNicolas van Kempen 
61f1367a47SNicolas van Kempen     // Match a string variable and a call to length() or size().
62f1367a47SNicolas van Kempen     if (const auto *ExprNode = Nodes.getNodeAs<Expr>(ID)) {
63f1367a47SNicolas van Kempen       if (const auto *MemberCallNode = Node.get<CXXMemberCallExpr>()) {
64f1367a47SNicolas van Kempen         const CXXMethodDecl *MethodDeclNode = MemberCallNode->getMethodDecl();
65f1367a47SNicolas van Kempen         const StringRef Name = MethodDeclNode->getName();
66f1367a47SNicolas van Kempen         if (!MethodDeclNode->isConst() || MethodDeclNode->getNumParams() != 0 ||
67f1367a47SNicolas van Kempen             (Name != "size" && Name != "length")) {
68f1367a47SNicolas van Kempen           return true;
69f1367a47SNicolas van Kempen         }
70f1367a47SNicolas van Kempen 
71f1367a47SNicolas van Kempen         if (const auto *OnNode =
72f1367a47SNicolas van Kempen                 dyn_cast<Expr>(MemberCallNode->getImplicitObjectArgument())) {
73f1367a47SNicolas van Kempen           return !utils::areStatementsIdentical(OnNode->IgnoreParenImpCasts(),
74f1367a47SNicolas van Kempen                                                 ExprNode->IgnoreParenImpCasts(),
75f1367a47SNicolas van Kempen                                                 *Context);
76f1367a47SNicolas van Kempen         }
77f1367a47SNicolas van Kempen       }
78f1367a47SNicolas van Kempen     }
79f1367a47SNicolas van Kempen 
80f1367a47SNicolas van Kempen     return true;
81f1367a47SNicolas van Kempen   }
82f1367a47SNicolas van Kempen 
83f1367a47SNicolas van Kempen private:
84f1367a47SNicolas van Kempen   std::string ID;
85f1367a47SNicolas van Kempen   DynTypedNode Node;
86f1367a47SNicolas van Kempen   ASTContext *Context;
87f1367a47SNicolas van Kempen };
88f1367a47SNicolas van Kempen 
89f1367a47SNicolas van Kempen AST_MATCHER_P(Expr, lengthExprForStringNode, std::string, ID) {
90f1367a47SNicolas van Kempen   return Builder->removeBindings(NotLengthExprForStringNode(
91f1367a47SNicolas van Kempen       ID, DynTypedNode::create(Node), &(Finder->getASTContext())));
92f1367a47SNicolas van Kempen }
93bc8cff1dSNicolas van Kempen 
94bc8cff1dSNicolas van Kempen UseStartsEndsWithCheck::UseStartsEndsWithCheck(StringRef Name,
95bc8cff1dSNicolas van Kempen                                                ClangTidyContext *Context)
96bc8cff1dSNicolas van Kempen     : ClangTidyCheck(Name, Context) {}
97bc8cff1dSNicolas van Kempen 
98bc8cff1dSNicolas van Kempen void UseStartsEndsWithCheck::registerMatchers(MatchFinder *Finder) {
99bc8cff1dSNicolas van Kempen   const auto ZeroLiteral = integerLiteral(equals(0));
100f1367a47SNicolas van Kempen 
10118b50189SJulian Schmidt   const auto ClassTypeWithMethod = [](const StringRef MethodBoundName,
10218b50189SJulian Schmidt                                       const auto... Methods) {
10318b50189SJulian Schmidt     return cxxRecordDecl(anyOf(
10418b50189SJulian Schmidt         hasMethod(cxxMethodDecl(isConst(), parameterCountIs(1),
10518b50189SJulian Schmidt                                 returns(booleanType()), hasAnyName(Methods))
10618b50189SJulian Schmidt                       .bind(MethodBoundName))...));
107bc8cff1dSNicolas van Kempen   };
108bc8cff1dSNicolas van Kempen 
10918b50189SJulian Schmidt   const auto OnClassWithStartsWithFunction =
11018b50189SJulian Schmidt       ClassTypeWithMethod("starts_with_fun", "starts_with", "startsWith",
11118b50189SJulian Schmidt                           "startswith", "StartsWith");
11218b50189SJulian Schmidt 
11318b50189SJulian Schmidt   const auto OnClassWithEndsWithFunction = ClassTypeWithMethod(
11418b50189SJulian Schmidt       "ends_with_fun", "ends_with", "endsWith", "endswith", "EndsWith");
115f1367a47SNicolas van Kempen 
116f1367a47SNicolas van Kempen   // Case 1: X.find(Y) [!=]= 0 -> starts_with.
117bc8cff1dSNicolas van Kempen   const auto FindExpr = cxxMemberCallExpr(
118bc8cff1dSNicolas van Kempen       anyOf(argumentCountIs(1), hasArgument(1, ZeroLiteral)),
11918b50189SJulian Schmidt       callee(
12018b50189SJulian Schmidt           cxxMethodDecl(hasName("find"), ofClass(OnClassWithStartsWithFunction))
12118b50189SJulian Schmidt               .bind("find_fun")),
12218b50189SJulian Schmidt       hasArgument(0, expr().bind("needle")));
123bc8cff1dSNicolas van Kempen 
124f1367a47SNicolas van Kempen   // Case 2: X.rfind(Y, 0) [!=]= 0 -> starts_with.
125bc8cff1dSNicolas van Kempen   const auto RFindExpr = cxxMemberCallExpr(
126bc8cff1dSNicolas van Kempen       hasArgument(1, ZeroLiteral),
12718b50189SJulian Schmidt       callee(cxxMethodDecl(hasName("rfind"),
12818b50189SJulian Schmidt                            ofClass(OnClassWithStartsWithFunction))
12918b50189SJulian Schmidt                  .bind("find_fun")),
13018b50189SJulian Schmidt       hasArgument(0, expr().bind("needle")));
131bc8cff1dSNicolas van Kempen 
132f1367a47SNicolas van Kempen   // Case 3: X.compare(0, LEN(Y), Y) [!=]= 0 -> starts_with.
133ef590698SNicolas van Kempen   const auto CompareExpr = cxxMemberCallExpr(
134f1367a47SNicolas van Kempen       argumentCountIs(3), hasArgument(0, ZeroLiteral),
13518b50189SJulian Schmidt       callee(cxxMethodDecl(hasName("compare"),
13618b50189SJulian Schmidt                            ofClass(OnClassWithStartsWithFunction))
13718b50189SJulian Schmidt                  .bind("find_fun")),
13818b50189SJulian Schmidt       hasArgument(2, expr().bind("needle")),
139f1367a47SNicolas van Kempen       hasArgument(1, lengthExprForStringNode("needle")));
140bc8cff1dSNicolas van Kempen 
141f1367a47SNicolas van Kempen   // Case 4: X.compare(LEN(X) - LEN(Y), LEN(Y), Y) [!=]= 0 -> ends_with.
142f1367a47SNicolas van Kempen   const auto CompareEndsWithExpr = cxxMemberCallExpr(
143f1367a47SNicolas van Kempen       argumentCountIs(3),
14418b50189SJulian Schmidt       callee(cxxMethodDecl(hasName("compare"),
14518b50189SJulian Schmidt                            ofClass(OnClassWithEndsWithFunction))
14618b50189SJulian Schmidt                  .bind("find_fun")),
14718b50189SJulian Schmidt       on(expr().bind("haystack")), hasArgument(2, expr().bind("needle")),
148f1367a47SNicolas van Kempen       hasArgument(1, lengthExprForStringNode("needle")),
149f1367a47SNicolas van Kempen       hasArgument(0,
150f1367a47SNicolas van Kempen                   binaryOperator(hasOperatorName("-"),
151f1367a47SNicolas van Kempen                                  hasLHS(lengthExprForStringNode("haystack")),
152f1367a47SNicolas van Kempen                                  hasRHS(lengthExprForStringNode("needle")))));
153f1367a47SNicolas van Kempen 
154f1367a47SNicolas van Kempen   // All cases comparing to 0.
155bc8cff1dSNicolas van Kempen   Finder->addMatcher(
156ef590698SNicolas van Kempen       binaryOperator(
15718b50189SJulian Schmidt           matchers::isEqualityOperator(),
158f1367a47SNicolas van Kempen           hasOperands(cxxMemberCallExpr(anyOf(FindExpr, RFindExpr, CompareExpr,
159f1367a47SNicolas van Kempen                                               CompareEndsWithExpr))
160ef590698SNicolas van Kempen                           .bind("find_expr"),
161ef590698SNicolas van Kempen                       ZeroLiteral))
162bc8cff1dSNicolas van Kempen           .bind("expr"),
163bc8cff1dSNicolas van Kempen       this);
164f1367a47SNicolas van Kempen 
165f1367a47SNicolas van Kempen   // Case 5: X.rfind(Y) [!=]= LEN(X) - LEN(Y) -> ends_with.
166f1367a47SNicolas van Kempen   Finder->addMatcher(
167f1367a47SNicolas van Kempen       binaryOperator(
16818b50189SJulian Schmidt           matchers::isEqualityOperator(),
169f1367a47SNicolas van Kempen           hasOperands(
170f1367a47SNicolas van Kempen               cxxMemberCallExpr(
171f1367a47SNicolas van Kempen                   anyOf(
172f1367a47SNicolas van Kempen                       argumentCountIs(1),
173f1367a47SNicolas van Kempen                       allOf(argumentCountIs(2),
174f1367a47SNicolas van Kempen                             hasArgument(
175f1367a47SNicolas van Kempen                                 1,
176f1367a47SNicolas van Kempen                                 anyOf(declRefExpr(to(varDecl(hasName("npos")))),
177f1367a47SNicolas van Kempen                                       memberExpr(member(hasName("npos"))))))),
17818b50189SJulian Schmidt                   callee(cxxMethodDecl(hasName("rfind"),
17918b50189SJulian Schmidt                                        ofClass(OnClassWithEndsWithFunction))
18018b50189SJulian Schmidt                              .bind("find_fun")),
18118b50189SJulian Schmidt                   on(expr().bind("haystack")),
182f1367a47SNicolas van Kempen                   hasArgument(0, expr().bind("needle")))
183f1367a47SNicolas van Kempen                   .bind("find_expr"),
184f1367a47SNicolas van Kempen               binaryOperator(hasOperatorName("-"),
185f1367a47SNicolas van Kempen                              hasLHS(lengthExprForStringNode("haystack")),
186f1367a47SNicolas van Kempen                              hasRHS(lengthExprForStringNode("needle")))))
187f1367a47SNicolas van Kempen           .bind("expr"),
188f1367a47SNicolas van Kempen       this);
18950844828SHelmut Januschka 
19050844828SHelmut Januschka   // Case 6: X.substr(0, LEN(Y)) [!=]= Y -> starts_with.
19150844828SHelmut Januschka   Finder->addMatcher(
192*2f02b5afSNicolas van Kempen       binaryOperation(
19350844828SHelmut Januschka           hasAnyOperatorName("==", "!="),
19450844828SHelmut Januschka           hasOperands(
19550844828SHelmut Januschka               expr().bind("needle"),
19650844828SHelmut Januschka               cxxMemberCallExpr(
19750844828SHelmut Januschka                   argumentCountIs(2), hasArgument(0, ZeroLiteral),
19850844828SHelmut Januschka                   hasArgument(1, lengthExprForStringNode("needle")),
19950844828SHelmut Januschka                   callee(cxxMethodDecl(hasName("substr"),
20050844828SHelmut Januschka                                        ofClass(OnClassWithStartsWithFunction))
20150844828SHelmut Januschka                              .bind("find_fun")))
20250844828SHelmut Januschka                   .bind("find_expr")))
20350844828SHelmut Januschka           .bind("expr"),
20450844828SHelmut Januschka       this);
205bc8cff1dSNicolas van Kempen }
206bc8cff1dSNicolas van Kempen 
207bc8cff1dSNicolas van Kempen void UseStartsEndsWithCheck::check(const MatchFinder::MatchResult &Result) {
20850844828SHelmut Januschka   const auto *ComparisonExpr = Result.Nodes.getNodeAs<Expr>("expr");
209bc8cff1dSNicolas van Kempen   const auto *FindExpr = Result.Nodes.getNodeAs<CXXMemberCallExpr>("find_expr");
210bc8cff1dSNicolas van Kempen   const auto *FindFun = Result.Nodes.getNodeAs<CXXMethodDecl>("find_fun");
211f1367a47SNicolas van Kempen   const auto *SearchExpr = Result.Nodes.getNodeAs<Expr>("needle");
212bc8cff1dSNicolas van Kempen   const auto *StartsWithFunction =
213bc8cff1dSNicolas van Kempen       Result.Nodes.getNodeAs<CXXMethodDecl>("starts_with_fun");
214f1367a47SNicolas van Kempen   const auto *EndsWithFunction =
215f1367a47SNicolas van Kempen       Result.Nodes.getNodeAs<CXXMethodDecl>("ends_with_fun");
216f1367a47SNicolas van Kempen   assert(bool(StartsWithFunction) != bool(EndsWithFunction));
21750844828SHelmut Januschka 
218f1367a47SNicolas van Kempen   const CXXMethodDecl *ReplacementFunction =
219f1367a47SNicolas van Kempen       StartsWithFunction ? StartsWithFunction : EndsWithFunction;
220ef590698SNicolas van Kempen 
22150844828SHelmut Januschka   if (ComparisonExpr->getBeginLoc().isMacroID() ||
22250844828SHelmut Januschka       FindExpr->getBeginLoc().isMacroID())
223bc8cff1dSNicolas van Kempen     return;
224bc8cff1dSNicolas van Kempen 
22550844828SHelmut Januschka   // Make sure FindExpr->getArg(0) can be used to make a range in the FitItHint.
22650844828SHelmut Januschka   if (FindExpr->getNumArgs() == 0)
22750844828SHelmut Januschka     return;
228bc8cff1dSNicolas van Kempen 
22950844828SHelmut Januschka   // Retrieve the source text of the search expression.
23050844828SHelmut Januschka   const auto SearchExprText = Lexer::getSourceText(
23150844828SHelmut Januschka       CharSourceRange::getTokenRange(SearchExpr->getSourceRange()),
23250844828SHelmut Januschka       *Result.SourceManager, Result.Context->getLangOpts());
233bc8cff1dSNicolas van Kempen 
23450844828SHelmut Januschka   auto Diagnostic = diag(FindExpr->getExprLoc(), "use %0 instead of %1")
23550844828SHelmut Januschka                     << ReplacementFunction->getName() << FindFun->getName();
236bc8cff1dSNicolas van Kempen 
23750844828SHelmut Januschka   // Remove everything before the function call.
238bc8cff1dSNicolas van Kempen   Diagnostic << FixItHint::CreateRemoval(CharSourceRange::getCharRange(
239bc8cff1dSNicolas van Kempen       ComparisonExpr->getBeginLoc(), FindExpr->getBeginLoc()));
240bc8cff1dSNicolas van Kempen 
24150844828SHelmut Januschka   // Rename the function to `starts_with` or `ends_with`.
24250844828SHelmut Januschka   Diagnostic << FixItHint::CreateReplacement(FindExpr->getExprLoc(),
24350844828SHelmut Januschka                                              ReplacementFunction->getName());
244bc8cff1dSNicolas van Kempen 
24550844828SHelmut Januschka   // Replace arguments and everything after the function call.
24650844828SHelmut Januschka   Diagnostic << FixItHint::CreateReplacement(
24750844828SHelmut Januschka       CharSourceRange::getTokenRange(FindExpr->getArg(0)->getBeginLoc(),
24850844828SHelmut Januschka                                      ComparisonExpr->getEndLoc()),
24950844828SHelmut Januschka       (SearchExprText + ")").str());
25050844828SHelmut Januschka 
25150844828SHelmut Januschka   // Add negation if necessary.
25250844828SHelmut Januschka   if (isNegativeComparison(ComparisonExpr))
253bc8cff1dSNicolas van Kempen     Diagnostic << FixItHint::CreateInsertion(FindExpr->getBeginLoc(), "!");
254bc8cff1dSNicolas van Kempen }
255bc8cff1dSNicolas van Kempen 
256bc8cff1dSNicolas van Kempen } // namespace clang::tidy::modernize
257