|  | //===--- ChainedComparisonCheck.cpp - clang-tidy --------------------------===// | 
|  | // | 
|  | // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. | 
|  | // See https://llvm.org/LICENSE.txt for license information. | 
|  | // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception | 
|  | // | 
|  | //===----------------------------------------------------------------------===// | 
|  |  | 
|  | #include "ChainedComparisonCheck.h" | 
|  | #include "clang/AST/ASTContext.h" | 
|  | #include "clang/ASTMatchers/ASTMatchFinder.h" | 
|  | #include "llvm/ADT/SmallString.h" | 
|  | #include "llvm/ADT/SmallVector.h" | 
|  | #include <algorithm> | 
|  |  | 
|  | using namespace clang::ast_matchers; | 
|  |  | 
|  | namespace clang::tidy::bugprone { | 
|  | static bool isExprAComparisonOperator(const Expr *E) { | 
|  | if (const auto *Op = dyn_cast_or_null<BinaryOperator>(E->IgnoreImplicit())) | 
|  | return Op->isComparisonOp(); | 
|  | if (const auto *Op = | 
|  | dyn_cast_or_null<CXXOperatorCallExpr>(E->IgnoreImplicit())) | 
|  | return Op->isComparisonOp(); | 
|  | return false; | 
|  | } | 
|  |  | 
|  | namespace { | 
|  | AST_MATCHER(BinaryOperator, | 
|  | hasBinaryOperatorAChildComparisonOperatorWithoutParen) { | 
|  | return isExprAComparisonOperator(Node.getLHS()) || | 
|  | isExprAComparisonOperator(Node.getRHS()); | 
|  | } | 
|  |  | 
|  | AST_MATCHER(CXXOperatorCallExpr, | 
|  | hasCppOperatorAChildComparisonOperatorWithoutParen) { | 
|  | return std::any_of(Node.arg_begin(), Node.arg_end(), | 
|  | isExprAComparisonOperator); | 
|  | } | 
|  |  | 
|  | struct ChainedComparisonData { | 
|  | llvm::SmallString<256U> Name; | 
|  | llvm::SmallVector<const Expr *, 32U> Operands; | 
|  |  | 
|  | explicit ChainedComparisonData(const Expr *Op) { extract(Op); } | 
|  |  | 
|  | private: | 
|  | void add(const Expr *Operand); | 
|  | void add(llvm::StringRef Opcode); | 
|  | void extract(const Expr *Op); | 
|  | void extract(const BinaryOperator *Op); | 
|  | void extract(const CXXOperatorCallExpr *Op); | 
|  | }; | 
|  |  | 
|  | void ChainedComparisonData::add(const Expr *Operand) { | 
|  | if (!Name.empty()) | 
|  | Name += ' '; | 
|  | Name += 'v'; | 
|  | Name += std::to_string(Operands.size()); | 
|  | Operands.push_back(Operand); | 
|  | } | 
|  |  | 
|  | void ChainedComparisonData::add(llvm::StringRef Opcode) { | 
|  | Name += ' '; | 
|  | Name += Opcode; | 
|  | } | 
|  |  | 
|  | void ChainedComparisonData::extract(const BinaryOperator *Op) { | 
|  | const Expr *LHS = Op->getLHS()->IgnoreImplicit(); | 
|  | if (isExprAComparisonOperator(LHS)) | 
|  | extract(LHS); | 
|  | else | 
|  | add(LHS); | 
|  |  | 
|  | add(Op->getOpcodeStr()); | 
|  |  | 
|  | const Expr *RHS = Op->getRHS()->IgnoreImplicit(); | 
|  | if (isExprAComparisonOperator(RHS)) | 
|  | extract(RHS); | 
|  | else | 
|  | add(RHS); | 
|  | } | 
|  |  | 
|  | void ChainedComparisonData::extract(const CXXOperatorCallExpr *Op) { | 
|  | const Expr *FirstArg = Op->getArg(0U)->IgnoreImplicit(); | 
|  | if (isExprAComparisonOperator(FirstArg)) | 
|  | extract(FirstArg); | 
|  | else | 
|  | add(FirstArg); | 
|  |  | 
|  | add(getOperatorSpelling(Op->getOperator())); | 
|  |  | 
|  | const Expr *SecondArg = Op->getArg(1U)->IgnoreImplicit(); | 
|  | if (isExprAComparisonOperator(SecondArg)) | 
|  | extract(SecondArg); | 
|  | else | 
|  | add(SecondArg); | 
|  | } | 
|  |  | 
|  | void ChainedComparisonData::extract(const Expr *Op) { | 
|  | if (!Op) | 
|  | return; | 
|  |  | 
|  | if (const auto *BinaryOp = dyn_cast<BinaryOperator>(Op)) { | 
|  | extract(BinaryOp); | 
|  | return; | 
|  | } | 
|  |  | 
|  | if (const auto *OverloadedOp = dyn_cast<CXXOperatorCallExpr>(Op)) { | 
|  | if (OverloadedOp->getNumArgs() == 2U) | 
|  | extract(OverloadedOp); | 
|  | } | 
|  | } | 
|  |  | 
|  | } // namespace | 
|  |  | 
|  | void ChainedComparisonCheck::registerMatchers(MatchFinder *Finder) { | 
|  | const auto OperatorMatcher = expr(anyOf( | 
|  | binaryOperator(isComparisonOperator(), | 
|  | hasBinaryOperatorAChildComparisonOperatorWithoutParen()), | 
|  | cxxOperatorCallExpr( | 
|  | isComparisonOperator(), | 
|  | hasCppOperatorAChildComparisonOperatorWithoutParen()))); | 
|  |  | 
|  | Finder->addMatcher( | 
|  | expr(OperatorMatcher, unless(hasParent(OperatorMatcher))).bind("op"), | 
|  | this); | 
|  | } | 
|  |  | 
|  | void ChainedComparisonCheck::check(const MatchFinder::MatchResult &Result) { | 
|  | const auto *MatchedOperator = Result.Nodes.getNodeAs<Expr>("op"); | 
|  |  | 
|  | ChainedComparisonData Data(MatchedOperator); | 
|  | if (Data.Operands.empty()) | 
|  | return; | 
|  |  | 
|  | diag(MatchedOperator->getBeginLoc(), | 
|  | "chained comparison '%0' may generate unintended results, use " | 
|  | "parentheses to specify order of evaluation or a logical operator to " | 
|  | "separate comparison expressions") | 
|  | << llvm::StringRef(Data.Name).trim() << MatchedOperator->getSourceRange(); | 
|  |  | 
|  | for (std::size_t Index = 0U; Index < Data.Operands.size(); ++Index) { | 
|  | diag(Data.Operands[Index]->getBeginLoc(), "operand 'v%0' is here", | 
|  | DiagnosticIDs::Note) | 
|  | << Index << Data.Operands[Index]->getSourceRange(); | 
|  | } | 
|  | } | 
|  |  | 
|  | } // namespace clang::tidy::bugprone |