clang-tools 23.0.0git
UseStartsEndsWithCheck.cpp
Go to the documentation of this file.
1//===----------------------------------------------------------------------===//
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/ASTUtils.h"
12#include "../utils/Matchers.h"
13#include "clang/ASTMatchers/ASTMatchers.h"
14#include "clang/Lex/Lexer.h"
15
16#include <string>
17
18using namespace clang::ast_matchers;
19
20namespace clang::tidy::modernize {
21
22static bool isNegativeComparison(const Expr *ComparisonExpr) {
23 if (const auto *Op = dyn_cast<BinaryOperator>(ComparisonExpr))
24 return Op->getOpcode() == BO_NE;
25
26 if (const auto *Op = dyn_cast<CXXOperatorCallExpr>(ComparisonExpr))
27 return Op->getOperator() == OO_ExclaimEqual;
28
29 if (const auto *Op = dyn_cast<CXXRewrittenBinaryOperator>(ComparisonExpr))
30 return Op->getOperator() == BO_NE;
31
32 return false;
33}
34
35namespace {
36
37struct NotLengthExprForStringNode {
38 NotLengthExprForStringNode(std::string ID, DynTypedNode Node,
39 ASTContext *Context)
40 : ID(std::move(ID)), Node(std::move(Node)), Context(Context) {}
41 bool operator()(const internal::BoundNodesMap &Nodes) const {
42 // Match a string literal and an integer size or strlen() call.
43 if (const auto *StringLiteralNode = Nodes.getNodeAs<StringLiteral>(ID)) {
44 if (const auto *IntegerLiteralSizeNode = Node.get<IntegerLiteral>()) {
45 return StringLiteralNode->getLength() !=
46 IntegerLiteralSizeNode->getValue().getZExtValue();
47 }
48
49 if (const auto *StrlenNode = Node.get<CallExpr>()) {
50 if (StrlenNode->getDirectCallee()->getName() != "strlen" ||
51 StrlenNode->getNumArgs() != 1) {
52 return true;
53 }
54
55 if (const auto *StrlenArgNode = dyn_cast<StringLiteral>(
56 StrlenNode->getArg(0)->IgnoreParenImpCasts())) {
57 return StrlenArgNode->getLength() != StringLiteralNode->getLength();
58 }
59 }
60 }
61
62 // Match a string variable and a call to length() or size().
63 if (const auto *ExprNode = Nodes.getNodeAs<Expr>(ID)) {
64 if (const auto *MemberCallNode = Node.get<CXXMemberCallExpr>()) {
65 const CXXMethodDecl *MethodDeclNode = MemberCallNode->getMethodDecl();
66 const StringRef Name = MethodDeclNode->getName();
67 if (!MethodDeclNode->isConst() || MethodDeclNode->getNumParams() != 0 ||
68 (Name != "size" && Name != "length")) {
69 return true;
70 }
71
72 if (const auto *OnNode =
73 dyn_cast<Expr>(MemberCallNode->getImplicitObjectArgument())) {
74 return !utils::areStatementsIdentical(OnNode->IgnoreParenImpCasts(),
75 ExprNode->IgnoreParenImpCasts(),
76 *Context);
77 }
78 }
79 }
80
81 return true;
82 }
83
84private:
85 std::string ID;
86 DynTypedNode Node;
87 ASTContext *Context;
88};
89
90AST_MATCHER_P(Expr, lengthExprForStringNode, std::string, ID) {
91 return Builder->removeBindings(NotLengthExprForStringNode(
92 ID, DynTypedNode::create(Node), &(Finder->getASTContext())));
93}
94
95} // namespace
96
98 ClangTidyContext *Context)
99 : ClangTidyCheck(Name, Context) {}
100
102 const auto ZeroLiteral = integerLiteral(equals(0));
103
104 const auto ClassTypeWithMethod = [](const StringRef MethodBoundName,
105 const auto... Methods) {
106 return cxxRecordDecl(anyOf(
107 hasMethod(cxxMethodDecl(isConst(), parameterCountIs(1),
108 returns(booleanType()), hasAnyName(Methods))
109 .bind(MethodBoundName))...));
110 };
111
112 const auto OnClassWithStartsWithFunction =
113 ClassTypeWithMethod("starts_with_fun", "starts_with", "startsWith",
114 "startswith", "StartsWith");
115
116 const auto OnClassWithEndsWithFunction = ClassTypeWithMethod(
117 "ends_with_fun", "ends_with", "endsWith", "endswith", "EndsWith");
118
119 // Case 1: X.find(Y, [0], [LEN(Y)]) [!=]= 0 -> starts_with.
120 const auto FindExpr = cxxMemberCallExpr(
121 callee(
122 cxxMethodDecl(hasName("find"), ofClass(OnClassWithStartsWithFunction))
123 .bind("find_fun")),
124 hasArgument(0, expr().bind("needle")),
125 anyOf(
126 // Detect the expression: X.find(Y);
127 argumentCountIs(1),
128 // Detect the expression: X.find(Y, 0);
129 allOf(argumentCountIs(2), hasArgument(1, ZeroLiteral)),
130 // Detect the expression: X.find(Y, 0, LEN(Y));
131 allOf(argumentCountIs(3), hasArgument(1, ZeroLiteral),
132 hasArgument(2, lengthExprForStringNode("needle")))));
133
134 // Case 2: X.rfind(Y, 0, [LEN(Y)]) [!=]= 0 -> starts_with.
135 const auto RFindExpr = cxxMemberCallExpr(
136 callee(cxxMethodDecl(hasName("rfind"),
137 ofClass(OnClassWithStartsWithFunction))
138 .bind("find_fun")),
139 hasArgument(0, expr().bind("needle")),
140 anyOf(
141 // Detect the expression: X.rfind(Y, 0);
142 allOf(argumentCountIs(2), hasArgument(1, ZeroLiteral)),
143 // Detect the expression: X.rfind(Y, 0, LEN(Y));
144 allOf(argumentCountIs(3), hasArgument(1, ZeroLiteral),
145 hasArgument(2, lengthExprForStringNode("needle")))));
146
147 // Case 3: X.compare(0, LEN(Y), Y) [!=]= 0 -> starts_with.
148 const auto CompareExpr = cxxMemberCallExpr(
149 argumentCountIs(3), hasArgument(0, ZeroLiteral),
150 callee(cxxMethodDecl(hasName("compare"),
151 ofClass(OnClassWithStartsWithFunction))
152 .bind("find_fun")),
153 hasArgument(2, expr().bind("needle")),
154 hasArgument(1, lengthExprForStringNode("needle")));
155
156 // Case 4: X.compare(LEN(X) - LEN(Y), LEN(Y), Y) [!=]= 0 -> ends_with.
157 const auto CompareEndsWithExpr = cxxMemberCallExpr(
158 argumentCountIs(3),
159 callee(cxxMethodDecl(hasName("compare"),
160 ofClass(OnClassWithEndsWithFunction))
161 .bind("find_fun")),
162 on(expr().bind("haystack")), hasArgument(2, expr().bind("needle")),
163 hasArgument(1, lengthExprForStringNode("needle")),
164 hasArgument(0,
165 binaryOperator(hasOperatorName("-"),
166 hasLHS(lengthExprForStringNode("haystack")),
167 hasRHS(lengthExprForStringNode("needle")))));
168
169 // All cases comparing to 0.
170 Finder->addMatcher(
171 binaryOperator(
172 matchers::isEqualityOperator(),
173 hasOperands(cxxMemberCallExpr(anyOf(FindExpr, RFindExpr, CompareExpr,
174 CompareEndsWithExpr))
175 .bind("find_expr"),
176 ZeroLiteral))
177 .bind("expr"),
178 this);
179
180 // Case 5: X.rfind(Y) [!=]= LEN(X) - LEN(Y) -> ends_with.
181 Finder->addMatcher(
182 binaryOperator(
183 matchers::isEqualityOperator(),
184 hasOperands(
185 cxxMemberCallExpr(
186 anyOf(
187 argumentCountIs(1),
188 allOf(argumentCountIs(2),
189 hasArgument(
190 1,
191 anyOf(declRefExpr(to(varDecl(hasName("npos")))),
192 memberExpr(member(hasName("npos"))))))),
193 callee(cxxMethodDecl(hasName("rfind"),
194 ofClass(OnClassWithEndsWithFunction))
195 .bind("find_fun")),
196 on(expr().bind("haystack")),
197 hasArgument(0, expr().bind("needle")))
198 .bind("find_expr"),
199 binaryOperator(hasOperatorName("-"),
200 hasLHS(lengthExprForStringNode("haystack")),
201 hasRHS(lengthExprForStringNode("needle")))))
202 .bind("expr"),
203 this);
204
205 // Case 6: X.substr(0, LEN(Y)) [!=]= Y -> starts_with.
206 Finder->addMatcher(
207 binaryOperation(
208 hasAnyOperatorName("==", "!="),
209 hasOperands(
210 expr().bind("needle"),
211 cxxMemberCallExpr(
212 argumentCountIs(2), hasArgument(0, ZeroLiteral),
213 hasArgument(1, lengthExprForStringNode("needle")),
214 callee(cxxMethodDecl(hasName("substr"),
215 ofClass(OnClassWithStartsWithFunction))
216 .bind("find_fun")))
217 .bind("find_expr")))
218 .bind("expr"),
219 this);
220}
221
222void UseStartsEndsWithCheck::check(const MatchFinder::MatchResult &Result) {
223 const auto *ComparisonExpr = Result.Nodes.getNodeAs<Expr>("expr");
224 const auto *FindExpr = Result.Nodes.getNodeAs<CXXMemberCallExpr>("find_expr");
225 const auto *FindFun = Result.Nodes.getNodeAs<CXXMethodDecl>("find_fun");
226 const auto *SearchExpr = Result.Nodes.getNodeAs<Expr>("needle");
227 const auto *StartsWithFunction =
228 Result.Nodes.getNodeAs<CXXMethodDecl>("starts_with_fun");
229 const auto *EndsWithFunction =
230 Result.Nodes.getNodeAs<CXXMethodDecl>("ends_with_fun");
231 assert(bool(StartsWithFunction) != bool(EndsWithFunction));
232
233 const CXXMethodDecl *ReplacementFunction =
234 StartsWithFunction ? StartsWithFunction : EndsWithFunction;
235
236 if (ComparisonExpr->getBeginLoc().isMacroID() ||
237 FindExpr->getBeginLoc().isMacroID())
238 return;
239
240 // Make sure FindExpr->getArg(0) can be used to make a range in the FitItHint.
241 if (FindExpr->getNumArgs() == 0)
242 return;
243
244 // Retrieve the source text of the search expression.
245 const auto SearchExprText = Lexer::getSourceText(
246 CharSourceRange::getTokenRange(SearchExpr->getSourceRange()),
247 *Result.SourceManager, Result.Context->getLangOpts());
248
249 auto Diagnostic = diag(FindExpr->getExprLoc(), "use %0 instead of %1")
250 << ReplacementFunction->getName() << FindFun->getName();
251
252 // Remove everything before the function call.
253 Diagnostic << FixItHint::CreateRemoval(CharSourceRange::getCharRange(
254 ComparisonExpr->getBeginLoc(), FindExpr->getBeginLoc()));
255
256 // Rename the function to `starts_with` or `ends_with`.
257 Diagnostic << FixItHint::CreateReplacement(FindExpr->getExprLoc(),
258 ReplacementFunction->getName());
259
260 // Replace arguments and everything after the function call.
261 Diagnostic << FixItHint::CreateReplacement(
262 CharSourceRange::getTokenRange(FindExpr->getArg(0)->getBeginLoc(),
263 ComparisonExpr->getEndLoc()),
264 (SearchExprText + ")").str());
265
266 // Add negation if necessary.
267 if (isNegativeComparison(ComparisonExpr))
268 Diagnostic << FixItHint::CreateInsertion(FindExpr->getBeginLoc(), "!");
269}
270
271} // namespace clang::tidy::modernize
Every ClangTidyCheck reports errors through a DiagnosticsEngine provided by this context.
void check(const ast_matchers::MatchFinder::MatchResult &Result) override
void registerMatchers(ast_matchers::MatchFinder *Finder) override
UseStartsEndsWithCheck(StringRef Name, ClangTidyContext *Context)
AST_MATCHER_P(Stmt, isStatementIdenticalToBoundNode, std::string, ID)
static bool isNegativeComparison(const Expr *ComparisonExpr)
bool areStatementsIdentical(const Stmt *FirstStmt, const Stmt *SecondStmt, const ASTContext &Context, bool Canonical)
Definition ASTUtils.cpp:89