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 = [&](auto Matcher) {
51 Finder->addMatcher(
52 traverse(TK_IgnoreUnlessSpelledInSource, std::move(Matcher)), this);
53 };
54
55 // Find membership tests which use `count()`.
56 Finder->addMatcher(implicitCastExpr(hasImplicitDestinationType(booleanType()),
57 hasSourceExpression(CountCall))
58 .bind("positiveComparison"),
59 this);
60 AddSimpleMatcher(
61 binaryOperation(hasOperatorName("!="), hasOperands(CountCall, Literal0))
62 .bind("positiveComparison"));
63 AddSimpleMatcher(
64 binaryOperation(hasLHS(CountCall), hasOperatorName(">"), hasRHS(Literal0))
65 .bind("positiveComparison"));
66 AddSimpleMatcher(
67 binaryOperation(hasLHS(Literal0), hasOperatorName("<"), hasRHS(CountCall))
68 .bind("positiveComparison"));
69 AddSimpleMatcher(binaryOperation(hasLHS(CountCall), hasOperatorName(">="),
70 hasRHS(Literal1))
71 .bind("positiveComparison"));
72 AddSimpleMatcher(binaryOperation(hasLHS(Literal1), hasOperatorName("<="),
73 hasRHS(CountCall))
74 .bind("positiveComparison"));
75
76 // Find inverted membership tests which use `count()`.
77 AddSimpleMatcher(
78 binaryOperation(hasOperatorName("=="), hasOperands(CountCall, Literal0))
79 .bind("negativeComparison"));
80 AddSimpleMatcher(binaryOperation(hasLHS(CountCall), hasOperatorName("<="),
81 hasRHS(Literal0))
82 .bind("negativeComparison"));
83 AddSimpleMatcher(binaryOperation(hasLHS(Literal0), hasOperatorName(">="),
84 hasRHS(CountCall))
85 .bind("negativeComparison"));
86 AddSimpleMatcher(
87 binaryOperation(hasLHS(CountCall), hasOperatorName("<"), hasRHS(Literal1))
88 .bind("negativeComparison"));
89 AddSimpleMatcher(
90 binaryOperation(hasLHS(Literal1), hasOperatorName(">"), hasRHS(CountCall))
91 .bind("negativeComparison"));
92
93 // Find membership tests based on `find() == end()` or `find() == npos`.
94 AddSimpleMatcher(
95 binaryOperation(hasOperatorName("!="),
96 hasOperands(FindCall, anyOf(EndCall, StringNpos)))
97 .bind("positiveComparison"));
98 AddSimpleMatcher(
99 binaryOperation(hasOperatorName("=="),
100 hasOperands(FindCall, anyOf(EndCall, StringNpos)))
101 .bind("negativeComparison"));
102}
103
104void ContainerContainsCheck::check(const MatchFinder::MatchResult &Result) {
105 // Extract the information about the match
106 const auto *Call = Result.Nodes.getNodeAs<CXXMemberCallExpr>("call");
107 const auto *PositiveComparison =
108 Result.Nodes.getNodeAs<Expr>("positiveComparison");
109 const auto *NegativeComparison =
110 Result.Nodes.getNodeAs<Expr>("negativeComparison");
111 assert((!PositiveComparison || !NegativeComparison) &&
112 "only one of PositiveComparison or NegativeComparison should be set");
113 bool Negated = NegativeComparison != nullptr;
114 const auto *Comparison = Negated ? NegativeComparison : PositiveComparison;
115 const StringRef ContainsFunName =
116 Result.Nodes.getNodeAs<CXXMethodDecl>("contains_fun")->getName();
117 const Expr *SearchExpr = Call->getArg(0)->IgnoreParenImpCasts();
118
119 // Diagnose the issue.
120 auto Diag = diag(Call->getExprLoc(), "use '%0' to check for membership")
121 << ContainsFunName;
122
123 // Don't fix it if it's in a macro invocation. Leave fixing it to the user.
124 SourceLocation FuncCallLoc = Comparison->getEndLoc();
125 if (!FuncCallLoc.isValid() || FuncCallLoc.isMacroID())
126 return;
127
128 const StringRef SearchExprText = Lexer::getSourceText(
129 CharSourceRange::getTokenRange(SearchExpr->getSourceRange()),
130 *Result.SourceManager, Result.Context->getLangOpts());
131
132 // Remove everything before the function call.
133 Diag << FixItHint::CreateRemoval(CharSourceRange::getCharRange(
134 Comparison->getBeginLoc(), Call->getBeginLoc()));
135
136 // Rename the function to `contains`.
137 Diag << FixItHint::CreateReplacement(Call->getExprLoc(), ContainsFunName);
138
139 // Replace arguments and everything after the function call.
140 Diag << FixItHint::CreateReplacement(
141 CharSourceRange::getTokenRange(Call->getArg(0)->getBeginLoc(),
142 Comparison->getEndLoc()),
143 (SearchExprText + ")").str());
144
145 // Add negation if necessary.
146 if (Negated)
147 Diag << FixItHint::CreateInsertion(Call->getBeginLoc(), "!");
148}
149
150} // 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)