| //===--- 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 |