clang-tools 19.0.0git
UseStartsEndsWithCheck.cpp
Go to the documentation of this file.
1//===--- UseStartsEndsWithCheck.cpp - clang-tidy --------------------------===//
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/Lex/Lexer.h"
13
14#include <string>
15
16using namespace clang::ast_matchers;
17
18namespace clang::tidy::modernize {
19
21 ClangTidyContext *Context)
22 : ClangTidyCheck(Name, Context) {}
23
25 const auto ZeroLiteral = integerLiteral(equals(0));
26 const auto HasStartsWithMethodWithName = [](const std::string &Name) {
27 return hasMethod(
28 cxxMethodDecl(hasName(Name), isConst(), parameterCountIs(1))
29 .bind("starts_with_fun"));
30 };
31 const auto HasStartsWithMethod =
32 anyOf(HasStartsWithMethodWithName("starts_with"),
33 HasStartsWithMethodWithName("startsWith"),
34 HasStartsWithMethodWithName("startswith"));
35 const auto ClassWithStartsWithFunction = cxxRecordDecl(anyOf(
36 HasStartsWithMethod, hasAnyBase(hasType(hasCanonicalType(hasDeclaration(
37 cxxRecordDecl(HasStartsWithMethod)))))));
38
39 const auto FindExpr = cxxMemberCallExpr(
40 // A method call with no second argument or the second argument is zero...
41 anyOf(argumentCountIs(1), hasArgument(1, ZeroLiteral)),
42 // ... named find...
43 callee(cxxMethodDecl(hasName("find")).bind("find_fun")),
44 // ... on a class with a starts_with function.
45 on(hasType(
46 hasCanonicalType(hasDeclaration(ClassWithStartsWithFunction)))),
47 // Bind search expression.
48 hasArgument(0, expr().bind("search_expr")));
49
50 const auto RFindExpr = cxxMemberCallExpr(
51 // A method call with a second argument of zero...
52 hasArgument(1, ZeroLiteral),
53 // ... named rfind...
54 callee(cxxMethodDecl(hasName("rfind")).bind("find_fun")),
55 // ... on a class with a starts_with function.
56 on(hasType(
57 hasCanonicalType(hasDeclaration(ClassWithStartsWithFunction)))),
58 // Bind search expression.
59 hasArgument(0, expr().bind("search_expr")));
60
61 // Match a string literal and an integer or strlen() call matching the length.
62 const auto HasStringLiteralAndLengthArgs = [](const auto StringArgIndex,
63 const auto LengthArgIndex) {
64 return allOf(
65 hasArgument(StringArgIndex, stringLiteral().bind("string_literal_arg")),
66 hasArgument(LengthArgIndex,
67 anyOf(integerLiteral().bind("integer_literal_size_arg"),
68 callExpr(callee(functionDecl(parameterCountIs(1),
69 hasName("strlen"))),
70 hasArgument(0, stringLiteral().bind(
71 "strlen_arg"))))));
72 };
73
74 // Match a string variable and a call to length() or size().
75 const auto HasStringVariableAndSizeCallArgs = [](const auto StringArgIndex,
76 const auto LengthArgIndex) {
77 return allOf(
78 hasArgument(StringArgIndex, declRefExpr(hasDeclaration(
79 decl().bind("string_var_decl")))),
80 hasArgument(LengthArgIndex,
81 cxxMemberCallExpr(
82 callee(cxxMethodDecl(isConst(), parameterCountIs(0),
83 hasAnyName("size", "length"))),
84 on(declRefExpr(
85 to(decl(equalsBoundNode("string_var_decl"))))))));
86 };
87
88 // Match either one of the two cases above.
89 const auto HasStringAndLengthArgs =
90 [HasStringLiteralAndLengthArgs, HasStringVariableAndSizeCallArgs](
91 const auto StringArgIndex, const auto LengthArgIndex) {
92 return anyOf(
93 HasStringLiteralAndLengthArgs(StringArgIndex, LengthArgIndex),
94 HasStringVariableAndSizeCallArgs(StringArgIndex, LengthArgIndex));
95 };
96
97 const auto CompareExpr = cxxMemberCallExpr(
98 // A method call with three arguments...
99 argumentCountIs(3),
100 // ... where the first argument is zero...
101 hasArgument(0, ZeroLiteral),
102 // ... named compare...
103 callee(cxxMethodDecl(hasName("compare")).bind("find_fun")),
104 // ... on a class with a starts_with function...
105 on(hasType(
106 hasCanonicalType(hasDeclaration(ClassWithStartsWithFunction)))),
107 // ... where the third argument is some string and the second a length.
108 HasStringAndLengthArgs(2, 1),
109 // Bind search expression.
110 hasArgument(2, expr().bind("search_expr")));
111
112 Finder->addMatcher(
113 // Match [=!]= with a zero on one side and (r?)find|compare on the other.
114 binaryOperator(
115 hasAnyOperatorName("==", "!="),
116 hasOperands(cxxMemberCallExpr(anyOf(FindExpr, RFindExpr, CompareExpr))
117 .bind("find_expr"),
118 ZeroLiteral))
119 .bind("expr"),
120 this);
121}
122
123void UseStartsEndsWithCheck::check(const MatchFinder::MatchResult &Result) {
124 const auto *ComparisonExpr = Result.Nodes.getNodeAs<BinaryOperator>("expr");
125 const auto *FindExpr = Result.Nodes.getNodeAs<CXXMemberCallExpr>("find_expr");
126 const auto *FindFun = Result.Nodes.getNodeAs<CXXMethodDecl>("find_fun");
127 const auto *SearchExpr = Result.Nodes.getNodeAs<Expr>("search_expr");
128 const auto *StartsWithFunction =
129 Result.Nodes.getNodeAs<CXXMethodDecl>("starts_with_fun");
130
131 const auto *StringLiteralArg =
132 Result.Nodes.getNodeAs<StringLiteral>("string_literal_arg");
133 const auto *IntegerLiteralSizeArg =
134 Result.Nodes.getNodeAs<IntegerLiteral>("integer_literal_size_arg");
135 const auto *StrlenArg = Result.Nodes.getNodeAs<StringLiteral>("strlen_arg");
136
137 // Filter out compare cases where the length does not match string literal.
138 if (StringLiteralArg && IntegerLiteralSizeArg &&
139 StringLiteralArg->getLength() !=
140 IntegerLiteralSizeArg->getValue().getZExtValue()) {
141 return;
142 }
143
144 if (StringLiteralArg && StrlenArg &&
145 StringLiteralArg->getLength() != StrlenArg->getLength()) {
146 return;
147 }
148
149 if (ComparisonExpr->getBeginLoc().isMacroID()) {
150 return;
151 }
152
153 const bool Neg = ComparisonExpr->getOpcode() == BO_NE;
154
155 auto Diagnostic =
156 diag(FindExpr->getExprLoc(), "use %0 instead of %1() %select{==|!=}2 0")
157 << StartsWithFunction->getName() << FindFun->getName() << Neg;
158
159 // Remove possible arguments after search expression and ' [!=]= 0' suffix.
160 Diagnostic << FixItHint::CreateReplacement(
161 CharSourceRange::getTokenRange(
162 Lexer::getLocForEndOfToken(SearchExpr->getEndLoc(), 0,
163 *Result.SourceManager, getLangOpts()),
164 ComparisonExpr->getEndLoc()),
165 ")");
166
167 // Remove possible '0 [!=]= ' prefix.
168 Diagnostic << FixItHint::CreateRemoval(CharSourceRange::getCharRange(
169 ComparisonExpr->getBeginLoc(), FindExpr->getBeginLoc()));
170
171 // Replace method name by 'starts_with'.
172 // Remove possible arguments before search expression.
173 Diagnostic << FixItHint::CreateReplacement(
174 CharSourceRange::getCharRange(FindExpr->getExprLoc(),
175 SearchExpr->getBeginLoc()),
176 (StartsWithFunction->getName() + "(").str());
177
178 // Add possible negation '!'.
179 if (Neg) {
180 Diagnostic << FixItHint::CreateInsertion(FindExpr->getBeginLoc(), "!");
181 }
182}
183
184} // namespace clang::tidy::modernize
llvm::SmallString< 256U > Name
DiagnosticCallback Diagnostic
Base class for all clang-tidy checks.
DiagnosticBuilder diag(SourceLocation Loc, StringRef Description, DiagnosticIDs::Level Level=DiagnosticIDs::Warning)
Add a diagnostic with the check's name.
const LangOptions & getLangOpts() const
Returns the language options from the context.
Every ClangTidyCheck reports errors through a DiagnosticsEngine provided by this context.
void check(const ast_matchers::MatchFinder::MatchResult &Result) override
ClangTidyChecks that register ASTMatchers should do the actual work in here.
void registerMatchers(ast_matchers::MatchFinder *Finder) override
Override this to register AST matchers with Finder.
UseStartsEndsWithCheck(StringRef Name, ClangTidyContext *Context)