clang-tools 22.0.0git
ContainerContainsCheck.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#include "clang/AST/ASTContext.h"
11#include "clang/ASTMatchers/ASTMatchFinder.h"
12#include "clang/Lex/Lexer.h"
13
14using namespace clang::ast_matchers;
15
18 const auto Literal0 = integerLiteral(equals(0));
19 const auto Literal1 = integerLiteral(equals(1));
20
21 const auto ClassWithContains = cxxRecordDecl(
22 hasMethod(cxxMethodDecl(isConst(), parameterCountIs(1), isPublic(),
23 unless(isDeleted()), returns(booleanType()),
24 hasAnyName("contains", "Contains"))
25 .bind("contains_fun")));
26
27 const auto CountCall =
28 cxxMemberCallExpr(argumentCountIs(1),
29 callee(cxxMethodDecl(hasAnyName("count", "Count"),
30 ofClass(ClassWithContains))))
31 .bind("call");
32
33 const auto FindCall =
34 // Either one argument, or assume the second argument is the position to
35 // start searching from.
36 cxxMemberCallExpr(
37 anyOf(argumentCountIs(1),
38 allOf(argumentCountIs(2), hasArgument(1, Literal0))),
39 callee(cxxMethodDecl(hasAnyName("find", "Find"),
40 ofClass(ClassWithContains))))
41 .bind("call");
42
43 const auto EndCall = cxxMemberCallExpr(
44 argumentCountIs(0), callee(cxxMethodDecl(hasAnyName("end", "End"),
45 ofClass(ClassWithContains))));
46
47 const auto StringNpos = anyOf(declRefExpr(to(varDecl(hasName("npos")))),
48 memberExpr(member(hasName("npos"))));
49
50 auto AddSimpleMatcher = [&](const auto &Matcher) {
51 Finder->addMatcher(traverse(TK_IgnoreUnlessSpelledInSource, Matcher), this);
52 };
53
54 // Find membership tests which use `count()`.
55 Finder->addMatcher(implicitCastExpr(hasImplicitDestinationType(booleanType()),
56 hasSourceExpression(CountCall))
57 .bind("positiveComparison"),
58 this);
59 AddSimpleMatcher(
60 binaryOperation(hasOperatorName("!="), hasOperands(CountCall, Literal0))
61 .bind("positiveComparison"));
62 AddSimpleMatcher(
63 binaryOperation(hasLHS(CountCall), hasOperatorName(">"), hasRHS(Literal0))
64 .bind("positiveComparison"));
65 AddSimpleMatcher(
66 binaryOperation(hasLHS(Literal0), hasOperatorName("<"), hasRHS(CountCall))
67 .bind("positiveComparison"));
68 AddSimpleMatcher(binaryOperation(hasLHS(CountCall), hasOperatorName(">="),
69 hasRHS(Literal1))
70 .bind("positiveComparison"));
71 AddSimpleMatcher(binaryOperation(hasLHS(Literal1), hasOperatorName("<="),
72 hasRHS(CountCall))
73 .bind("positiveComparison"));
74
75 // Find inverted membership tests which use `count()`.
76 AddSimpleMatcher(
77 binaryOperation(hasOperatorName("=="), hasOperands(CountCall, Literal0))
78 .bind("negativeComparison"));
79 AddSimpleMatcher(binaryOperation(hasLHS(CountCall), hasOperatorName("<="),
80 hasRHS(Literal0))
81 .bind("negativeComparison"));
82 AddSimpleMatcher(binaryOperation(hasLHS(Literal0), hasOperatorName(">="),
83 hasRHS(CountCall))
84 .bind("negativeComparison"));
85 AddSimpleMatcher(
86 binaryOperation(hasLHS(CountCall), hasOperatorName("<"), hasRHS(Literal1))
87 .bind("negativeComparison"));
88 AddSimpleMatcher(
89 binaryOperation(hasLHS(Literal1), hasOperatorName(">"), hasRHS(CountCall))
90 .bind("negativeComparison"));
91
92 // Find membership tests based on `find() == end()` or `find() == npos`.
93 AddSimpleMatcher(
94 binaryOperation(hasOperatorName("!="),
95 hasOperands(FindCall, anyOf(EndCall, StringNpos)))
96 .bind("positiveComparison"));
97 AddSimpleMatcher(
98 binaryOperation(hasOperatorName("=="),
99 hasOperands(FindCall, anyOf(EndCall, StringNpos)))
100 .bind("negativeComparison"));
101}
102
103void ContainerContainsCheck::check(const MatchFinder::MatchResult &Result) {
104 // Extract the information about the match
105 const auto *Call = Result.Nodes.getNodeAs<CXXMemberCallExpr>("call");
106 const auto *PositiveComparison =
107 Result.Nodes.getNodeAs<Expr>("positiveComparison");
108 const auto *NegativeComparison =
109 Result.Nodes.getNodeAs<Expr>("negativeComparison");
110 assert((!PositiveComparison || !NegativeComparison) &&
111 "only one of PositiveComparison or NegativeComparison should be set");
112 const bool Negated = NegativeComparison != nullptr;
113 const auto *Comparison = Negated ? NegativeComparison : PositiveComparison;
114 const StringRef ContainsFunName =
115 Result.Nodes.getNodeAs<CXXMethodDecl>("contains_fun")->getName();
116 const Expr *SearchExpr = Call->getArg(0)->IgnoreParenImpCasts();
117
118 // Diagnose the issue.
119 auto Diag = diag(Call->getExprLoc(), "use '%0' to check for membership")
120 << ContainsFunName;
121
122 // Don't fix it if it's in a macro invocation. Leave fixing it to the user.
123 const SourceLocation FuncCallLoc = Comparison->getEndLoc();
124 if (!FuncCallLoc.isValid() || FuncCallLoc.isMacroID())
125 return;
126
127 const StringRef SearchExprText = Lexer::getSourceText(
128 CharSourceRange::getTokenRange(SearchExpr->getSourceRange()),
129 *Result.SourceManager, Result.Context->getLangOpts());
130
131 // Remove everything before the function call.
132 Diag << FixItHint::CreateRemoval(CharSourceRange::getCharRange(
133 Comparison->getBeginLoc(), Call->getBeginLoc()));
134
135 // Rename the function to `contains`.
136 Diag << FixItHint::CreateReplacement(Call->getExprLoc(), ContainsFunName);
137
138 // Replace arguments and everything after the function call.
139 Diag << FixItHint::CreateReplacement(
140 CharSourceRange::getTokenRange(Call->getArg(0)->getBeginLoc(),
141 Comparison->getEndLoc()),
142 (SearchExprText + ")").str());
143
144 // Add negation if necessary.
145 if (Negated)
146 Diag << FixItHint::CreateInsertion(Call->getBeginLoc(), "!");
147}
148
149} // namespace clang::tidy::readability
void check(const ast_matchers::MatchFinder::MatchResult &Result) final
void registerMatchers(ast_matchers::MatchFinder *Finder) final
static std::string getName(const EnumDecl *Decl)