//===--- 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 using namespace clang::ast_matchers; namespace clang::tidy::bugprone { static bool isExprAComparisonOperator(const Expr *E) { if (const auto *Op = dyn_cast_or_null(E->IgnoreImplicit())) return Op->isComparisonOp(); if (const auto *Op = dyn_cast_or_null(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 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(Op)) { extract(BinaryOp); return; } if (const auto *OverloadedOp = dyn_cast(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("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