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