xref: /llvm-project/clang-tools-extra/clang-tidy/bugprone/ChainedComparisonCheck.cpp (revision 06c3c3b67cb0287856145806cfb0179def3214bd)
1 //===--- ChainedComparisonCheck.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 
9 #include "ChainedComparisonCheck.h"
10 #include "clang/AST/ASTContext.h"
11 #include "clang/ASTMatchers/ASTMatchFinder.h"
12 #include "llvm/ADT/SmallString.h"
13 #include "llvm/ADT/SmallVector.h"
14 #include <algorithm>
15 
16 using namespace clang::ast_matchers;
17 
18 namespace clang::tidy::bugprone {
isExprAComparisonOperator(const Expr * E)19 static bool isExprAComparisonOperator(const Expr *E) {
20   if (const auto *Op = dyn_cast_or_null<BinaryOperator>(E->IgnoreImplicit()))
21     return Op->isComparisonOp();
22   if (const auto *Op =
23           dyn_cast_or_null<CXXOperatorCallExpr>(E->IgnoreImplicit()))
24     return Op->isComparisonOp();
25   return false;
26 }
27 
28 namespace {
AST_MATCHER(BinaryOperator,hasBinaryOperatorAChildComparisonOperatorWithoutParen)29 AST_MATCHER(BinaryOperator,
30             hasBinaryOperatorAChildComparisonOperatorWithoutParen) {
31   return isExprAComparisonOperator(Node.getLHS()) ||
32          isExprAComparisonOperator(Node.getRHS());
33 }
34 
AST_MATCHER(CXXOperatorCallExpr,hasCppOperatorAChildComparisonOperatorWithoutParen)35 AST_MATCHER(CXXOperatorCallExpr,
36             hasCppOperatorAChildComparisonOperatorWithoutParen) {
37   return std::any_of(Node.arg_begin(), Node.arg_end(),
38                      isExprAComparisonOperator);
39 }
40 
41 struct ChainedComparisonData {
42   llvm::SmallString<256U> Name;
43   llvm::SmallVector<const Expr *, 32U> Operands;
44 
ChainedComparisonDataclang::tidy::bugprone::__anon67711e870111::ChainedComparisonData45   explicit ChainedComparisonData(const Expr *Op) { extract(Op); }
46 
47 private:
48   void add(const Expr *Operand);
49   void add(llvm::StringRef Opcode);
50   void extract(const Expr *Op);
51   void extract(const BinaryOperator *Op);
52   void extract(const CXXOperatorCallExpr *Op);
53 };
54 
add(const Expr * Operand)55 void ChainedComparisonData::add(const Expr *Operand) {
56   if (!Name.empty())
57     Name += ' ';
58   Name += 'v';
59   Name += std::to_string(Operands.size());
60   Operands.push_back(Operand);
61 }
62 
add(llvm::StringRef Opcode)63 void ChainedComparisonData::add(llvm::StringRef Opcode) {
64   Name += ' ';
65   Name += Opcode;
66 }
67 
extract(const BinaryOperator * Op)68 void ChainedComparisonData::extract(const BinaryOperator *Op) {
69   const Expr *LHS = Op->getLHS()->IgnoreImplicit();
70   if (isExprAComparisonOperator(LHS))
71     extract(LHS);
72   else
73     add(LHS);
74 
75   add(Op->getOpcodeStr());
76 
77   const Expr *RHS = Op->getRHS()->IgnoreImplicit();
78   if (isExprAComparisonOperator(RHS))
79     extract(RHS);
80   else
81     add(RHS);
82 }
83 
extract(const CXXOperatorCallExpr * Op)84 void ChainedComparisonData::extract(const CXXOperatorCallExpr *Op) {
85   const Expr *FirstArg = Op->getArg(0U)->IgnoreImplicit();
86   if (isExprAComparisonOperator(FirstArg))
87     extract(FirstArg);
88   else
89     add(FirstArg);
90 
91   add(getOperatorSpelling(Op->getOperator()));
92 
93   const Expr *SecondArg = Op->getArg(1U)->IgnoreImplicit();
94   if (isExprAComparisonOperator(SecondArg))
95     extract(SecondArg);
96   else
97     add(SecondArg);
98 }
99 
extract(const Expr * Op)100 void ChainedComparisonData::extract(const Expr *Op) {
101   if (!Op)
102     return;
103 
104   if (const auto *BinaryOp = dyn_cast<BinaryOperator>(Op)) {
105     extract(BinaryOp);
106     return;
107   }
108 
109   if (const auto *OverloadedOp = dyn_cast<CXXOperatorCallExpr>(Op)) {
110     if (OverloadedOp->getNumArgs() == 2U)
111       extract(OverloadedOp);
112   }
113 }
114 
115 } // namespace
116 
registerMatchers(MatchFinder * Finder)117 void ChainedComparisonCheck::registerMatchers(MatchFinder *Finder) {
118   const auto OperatorMatcher = expr(anyOf(
119       binaryOperator(isComparisonOperator(),
120                      hasBinaryOperatorAChildComparisonOperatorWithoutParen()),
121       cxxOperatorCallExpr(
122           isComparisonOperator(),
123           hasCppOperatorAChildComparisonOperatorWithoutParen())));
124 
125   Finder->addMatcher(
126       expr(OperatorMatcher, unless(hasParent(OperatorMatcher))).bind("op"),
127       this);
128 }
129 
check(const MatchFinder::MatchResult & Result)130 void ChainedComparisonCheck::check(const MatchFinder::MatchResult &Result) {
131   const auto *MatchedOperator = Result.Nodes.getNodeAs<Expr>("op");
132 
133   ChainedComparisonData Data(MatchedOperator);
134   if (Data.Operands.empty())
135     return;
136 
137   diag(MatchedOperator->getBeginLoc(),
138        "chained comparison '%0' may generate unintended results, use "
139        "parentheses to specify order of evaluation or a logical operator to "
140        "separate comparison expressions")
141       << llvm::StringRef(Data.Name).trim() << MatchedOperator->getSourceRange();
142 
143   for (std::size_t Index = 0U; Index < Data.Operands.size(); ++Index) {
144     diag(Data.Operands[Index]->getBeginLoc(), "operand 'v%0' is here",
145          DiagnosticIDs::Note)
146         << Index << Data.Operands[Index]->getSourceRange();
147   }
148 }
149 
150 } // namespace clang::tidy::bugprone
151