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