clang-tools 22.0.0git
ChainedComparisonCheck.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 "llvm/ADT/SmallString.h"
13#include "llvm/ADT/SmallVector.h"
14
15using namespace clang::ast_matchers;
16
17namespace clang::tidy::bugprone {
18static bool isExprAComparisonOperator(const Expr *E) {
19 if (const auto *Op = dyn_cast_or_null<BinaryOperator>(E->IgnoreImplicit()))
20 return Op->isComparisonOp();
21 if (const auto *Op =
22 dyn_cast_or_null<CXXOperatorCallExpr>(E->IgnoreImplicit()))
23 return Op->isComparisonOp();
24 return false;
25}
26
27namespace {
28AST_MATCHER(BinaryOperator,
29 hasBinaryOperatorAChildComparisonOperatorWithoutParen) {
30 return isExprAComparisonOperator(Node.getLHS()) ||
31 isExprAComparisonOperator(Node.getRHS());
32}
33
34AST_MATCHER(CXXOperatorCallExpr,
35 hasCppOperatorAChildComparisonOperatorWithoutParen) {
36 return llvm::any_of(Node.arguments(), isExprAComparisonOperator);
37}
38
39struct ChainedComparisonData {
40 llvm::SmallString<256U> Name;
41 llvm::SmallVector<const Expr *, 32U> Operands;
42
43 explicit ChainedComparisonData(const Expr *Op) { extract(Op); }
44
45private:
46 void add(const Expr *Operand);
47 void add(llvm::StringRef Opcode);
48 void extract(const Expr *Op);
49 void extract(const BinaryOperator *Op);
50 void extract(const CXXOperatorCallExpr *Op);
51};
52
53} // namespace
54
55void 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
63void ChainedComparisonData::add(llvm::StringRef Opcode) {
64 Name += ' ';
65 Name += Opcode;
66}
67
68void ChainedComparisonData::extract(const BinaryOperator *Op) {
69 const Expr *LHS = Op->getLHS()->IgnoreImplicit();
71 extract(LHS);
72 else
73 add(LHS);
74
75 add(Op->getOpcodeStr());
76
77 const Expr *RHS = Op->getRHS()->IgnoreImplicit();
79 extract(RHS);
80 else
81 add(RHS);
82}
83
84void 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
100void 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
116 ClangTidyContext *Context)
117 : ClangTidyCheck(Name, Context),
118 IgnoreMacros(Options.get("IgnoreMacros", false)) {}
119
121 Options.store(Opts, "IgnoreMacros", IgnoreMacros);
122}
123
125 const auto OperatorMatcher = expr(anyOf(
126 binaryOperator(isComparisonOperator(),
127 hasBinaryOperatorAChildComparisonOperatorWithoutParen()),
128 cxxOperatorCallExpr(
129 isComparisonOperator(),
130 hasCppOperatorAChildComparisonOperatorWithoutParen())));
131
132 Finder->addMatcher(
133 expr(OperatorMatcher, unless(hasParent(OperatorMatcher))).bind("op"),
134 this);
135}
136
137void ChainedComparisonCheck::check(const MatchFinder::MatchResult &Result) {
138 const auto *MatchedOperator = Result.Nodes.getNodeAs<Expr>("op");
139
140 if (IgnoreMacros && MatchedOperator->getBeginLoc().isMacroID())
141 return;
142
143 ChainedComparisonData Data(MatchedOperator);
144 if (Data.Operands.empty())
145 return;
146
147 diag(MatchedOperator->getBeginLoc(),
148 "chained comparison '%0' may generate unintended results, use "
149 "parentheses to specify order of evaluation or a logical operator to "
150 "separate comparison expressions")
151 << llvm::StringRef(Data.Name).trim() << MatchedOperator->getSourceRange();
152
153 for (std::size_t Index = 0U; Index < Data.Operands.size(); ++Index) {
154 diag(Data.Operands[Index]->getBeginLoc(), "operand 'v%0' is here",
155 DiagnosticIDs::Note)
156 << Index << Data.Operands[Index]->getSourceRange();
157 }
158}
159
160} // namespace clang::tidy::bugprone
Every ClangTidyCheck reports errors through a DiagnosticsEngine provided by this context.
void check(const ast_matchers::MatchFinder::MatchResult &Result) override
void storeOptions(ClangTidyOptions::OptionMap &Opts) override
void registerMatchers(ast_matchers::MatchFinder *Finder) override
ChainedComparisonCheck(StringRef Name, ClangTidyContext *Context)
static bool isExprAComparisonOperator(const Expr *E)
llvm::StringMap< ClangTidyValue > OptionMap