150 lines
4.4 KiB
C++
150 lines
4.4 KiB
C++
//===--- 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
|