2019-03-01 17:52:53 +08:00
|
|
|
//===-- SimplifyBooleanExprCheck.cpp - clang-tidy -------------------------===//
|
2015-04-11 03:26:43 +08:00
|
|
|
//
|
2019-01-19 16:50:56 +08:00
|
|
|
// 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
|
2015-04-11 03:26:43 +08:00
|
|
|
//
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
|
|
|
|
#include "SimplifyBooleanExprCheck.h"
|
2017-05-16 01:06:51 +08:00
|
|
|
#include "clang/AST/RecursiveASTVisitor.h"
|
2015-04-11 03:26:43 +08:00
|
|
|
#include "clang/Lex/Lexer.h"
|
|
|
|
|
|
|
|
#include <cassert>
|
|
|
|
#include <string>
|
2015-07-01 20:39:40 +08:00
|
|
|
#include <utility>
|
2015-04-11 03:26:43 +08:00
|
|
|
|
|
|
|
using namespace clang::ast_matchers;
|
|
|
|
|
|
|
|
namespace clang {
|
|
|
|
namespace tidy {
|
|
|
|
namespace readability {
|
|
|
|
|
|
|
|
namespace {
|
|
|
|
|
|
|
|
StringRef getText(const MatchFinder::MatchResult &Result, SourceRange Range) {
|
|
|
|
return Lexer::getSourceText(CharSourceRange::getTokenRange(Range),
|
|
|
|
*Result.SourceManager,
|
|
|
|
Result.Context->getLangOpts());
|
|
|
|
}
|
|
|
|
|
|
|
|
template <typename T>
|
|
|
|
StringRef getText(const MatchFinder::MatchResult &Result, T &Node) {
|
|
|
|
return getText(Result, Node.getSourceRange());
|
|
|
|
}
|
|
|
|
|
|
|
|
const char ConditionThenStmtId[] = "if-bool-yields-then";
|
|
|
|
const char ConditionElseStmtId[] = "if-bool-yields-else";
|
|
|
|
const char TernaryId[] = "ternary-bool-yields-condition";
|
|
|
|
const char TernaryNegatedId[] = "ternary-bool-yields-not-condition";
|
|
|
|
const char IfReturnsBoolId[] = "if-return";
|
|
|
|
const char IfReturnsNotBoolId[] = "if-not-return";
|
|
|
|
const char ThenLiteralId[] = "then-literal";
|
|
|
|
const char IfAssignVariableId[] = "if-assign-lvalue";
|
|
|
|
const char IfAssignLocId[] = "if-assign-loc";
|
|
|
|
const char IfAssignBoolId[] = "if-assign";
|
|
|
|
const char IfAssignNotBoolId[] = "if-assign-not";
|
2019-05-16 20:35:00 +08:00
|
|
|
const char IfAssignVarId[] = "if-assign-var";
|
2015-07-01 20:39:40 +08:00
|
|
|
const char CompoundReturnId[] = "compound-return";
|
|
|
|
const char CompoundBoolId[] = "compound-bool";
|
|
|
|
const char CompoundNotBoolId[] = "compound-bool-not";
|
2015-04-11 03:26:43 +08:00
|
|
|
|
|
|
|
const char IfStmtId[] = "if";
|
|
|
|
|
|
|
|
const char SimplifyOperatorDiagnostic[] =
|
|
|
|
"redundant boolean literal supplied to boolean operator";
|
|
|
|
const char SimplifyConditionDiagnostic[] =
|
|
|
|
"redundant boolean literal in if statement condition";
|
2015-07-01 20:39:40 +08:00
|
|
|
const char SimplifyConditionalReturnDiagnostic[] =
|
|
|
|
"redundant boolean literal in conditional return statement";
|
2015-04-11 03:26:43 +08:00
|
|
|
|
2020-08-22 20:57:34 +08:00
|
|
|
const Expr *getBoolLiteral(const MatchFinder::MatchResult &Result,
|
|
|
|
StringRef Id) {
|
|
|
|
if (const Expr *Literal = Result.Nodes.getNodeAs<CXXBoolLiteralExpr>(Id))
|
|
|
|
return Literal->getBeginLoc().isMacroID() ? nullptr : Literal;
|
|
|
|
if (const auto *Negated = Result.Nodes.getNodeAs<UnaryOperator>(Id)) {
|
|
|
|
if (Negated->getOpcode() == UO_LNot &&
|
|
|
|
isa<CXXBoolLiteralExpr>(Negated->getSubExpr()))
|
|
|
|
return Negated->getBeginLoc().isMacroID() ? nullptr : Negated;
|
|
|
|
}
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
|
|
|
|
internal::BindableMatcher<Stmt> literalOrNegatedBool(bool Value) {
|
2020-12-30 07:31:09 +08:00
|
|
|
return expr(
|
|
|
|
anyOf(cxxBoolLiteral(equals(Value)),
|
|
|
|
unaryOperator(hasUnaryOperand(cxxBoolLiteral(equals(!Value))),
|
|
|
|
hasOperatorName("!"))));
|
2015-04-11 03:26:43 +08:00
|
|
|
}
|
|
|
|
|
2015-07-01 20:39:40 +08:00
|
|
|
internal::Matcher<Stmt> returnsBool(bool Value, StringRef Id = "ignored") {
|
2020-08-22 20:57:34 +08:00
|
|
|
auto SimpleReturnsBool = returnStmt(has(literalOrNegatedBool(Value).bind(Id)))
|
|
|
|
.bind("returns-bool");
|
2015-04-11 03:26:43 +08:00
|
|
|
return anyOf(SimpleReturnsBool,
|
|
|
|
compoundStmt(statementCountIs(1), has(SimpleReturnsBool)));
|
|
|
|
}
|
|
|
|
|
|
|
|
bool needsParensAfterUnaryNegation(const Expr *E) {
|
2015-07-01 20:39:40 +08:00
|
|
|
E = E->IgnoreImpCasts();
|
2015-04-11 03:26:43 +08:00
|
|
|
if (isa<BinaryOperator>(E) || isa<ConditionalOperator>(E))
|
|
|
|
return true;
|
2016-02-12 23:09:05 +08:00
|
|
|
|
|
|
|
if (const auto *Op = dyn_cast<CXXOperatorCallExpr>(E))
|
2015-04-11 03:26:43 +08:00
|
|
|
return Op->getNumArgs() == 2 && Op->getOperator() != OO_Call &&
|
|
|
|
Op->getOperator() != OO_Subscript;
|
2016-02-12 23:09:05 +08:00
|
|
|
|
2015-04-11 03:26:43 +08:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
std::pair<BinaryOperatorKind, BinaryOperatorKind> Opposites[] = {
|
2015-07-01 20:39:40 +08:00
|
|
|
{BO_LT, BO_GE}, {BO_GT, BO_LE}, {BO_EQ, BO_NE}};
|
2015-04-11 03:26:43 +08:00
|
|
|
|
|
|
|
StringRef negatedOperator(const BinaryOperator *BinOp) {
|
|
|
|
const BinaryOperatorKind Opcode = BinOp->getOpcode();
|
|
|
|
for (auto NegatableOp : Opposites) {
|
|
|
|
if (Opcode == NegatableOp.first)
|
|
|
|
return BinOp->getOpcodeStr(NegatableOp.second);
|
|
|
|
if (Opcode == NegatableOp.second)
|
|
|
|
return BinOp->getOpcodeStr(NegatableOp.first);
|
|
|
|
}
|
|
|
|
return StringRef();
|
|
|
|
}
|
|
|
|
|
2015-07-01 20:39:40 +08:00
|
|
|
std::pair<OverloadedOperatorKind, StringRef> OperatorNames[] = {
|
2016-02-12 23:09:05 +08:00
|
|
|
{OO_EqualEqual, "=="}, {OO_ExclaimEqual, "!="}, {OO_Less, "<"},
|
|
|
|
{OO_GreaterEqual, ">="}, {OO_Greater, ">"}, {OO_LessEqual, "<="}};
|
2015-07-01 20:39:40 +08:00
|
|
|
|
|
|
|
StringRef getOperatorName(OverloadedOperatorKind OpKind) {
|
|
|
|
for (auto Name : OperatorNames) {
|
|
|
|
if (Name.first == OpKind)
|
|
|
|
return Name.second;
|
|
|
|
}
|
|
|
|
|
|
|
|
return StringRef();
|
|
|
|
}
|
|
|
|
|
|
|
|
std::pair<OverloadedOperatorKind, OverloadedOperatorKind> OppositeOverloads[] =
|
|
|
|
{{OO_EqualEqual, OO_ExclaimEqual},
|
|
|
|
{OO_Less, OO_GreaterEqual},
|
|
|
|
{OO_Greater, OO_LessEqual}};
|
|
|
|
|
|
|
|
StringRef negatedOperator(const CXXOperatorCallExpr *OpCall) {
|
|
|
|
const OverloadedOperatorKind Opcode = OpCall->getOperator();
|
|
|
|
for (auto NegatableOp : OppositeOverloads) {
|
|
|
|
if (Opcode == NegatableOp.first)
|
|
|
|
return getOperatorName(NegatableOp.second);
|
|
|
|
if (Opcode == NegatableOp.second)
|
|
|
|
return getOperatorName(NegatableOp.first);
|
|
|
|
}
|
|
|
|
return StringRef();
|
|
|
|
}
|
|
|
|
|
2021-01-29 07:49:53 +08:00
|
|
|
std::string asBool(StringRef Text, bool NeedsStaticCast) {
|
2015-07-01 20:39:40 +08:00
|
|
|
if (NeedsStaticCast)
|
2021-01-29 07:49:53 +08:00
|
|
|
return ("static_cast<bool>(" + Text + ")").str();
|
2015-07-01 20:39:40 +08:00
|
|
|
|
2021-01-29 07:49:53 +08:00
|
|
|
return std::string(Text);
|
2015-07-01 20:39:40 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
bool needsNullPtrComparison(const Expr *E) {
|
|
|
|
if (const auto *ImpCast = dyn_cast<ImplicitCastExpr>(E))
|
2016-02-12 23:09:05 +08:00
|
|
|
return ImpCast->getCastKind() == CK_PointerToBoolean ||
|
|
|
|
ImpCast->getCastKind() == CK_MemberPointerToBoolean;
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool needsZeroComparison(const Expr *E) {
|
|
|
|
if (const auto *ImpCast = dyn_cast<ImplicitCastExpr>(E))
|
|
|
|
return ImpCast->getCastKind() == CK_IntegralToBoolean;
|
2015-07-01 20:39:40 +08:00
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool needsStaticCast(const Expr *E) {
|
|
|
|
if (const auto *ImpCast = dyn_cast<ImplicitCastExpr>(E)) {
|
|
|
|
if (ImpCast->getCastKind() == CK_UserDefinedConversion &&
|
|
|
|
ImpCast->getSubExpr()->getType()->isBooleanType()) {
|
|
|
|
if (const auto *MemCall =
|
|
|
|
dyn_cast<CXXMemberCallExpr>(ImpCast->getSubExpr())) {
|
|
|
|
if (const auto *MemDecl =
|
|
|
|
dyn_cast<CXXConversionDecl>(MemCall->getMethodDecl())) {
|
|
|
|
if (MemDecl->isExplicit())
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
E = E->IgnoreImpCasts();
|
|
|
|
return !E->getType()->isBooleanType();
|
|
|
|
}
|
|
|
|
|
2016-02-12 23:09:05 +08:00
|
|
|
std::string compareExpressionToConstant(const MatchFinder::MatchResult &Result,
|
|
|
|
const Expr *E, bool Negated,
|
|
|
|
const char *Constant) {
|
|
|
|
E = E->IgnoreImpCasts();
|
|
|
|
const std::string ExprText =
|
|
|
|
(isa<BinaryOperator>(E) ? ("(" + getText(Result, *E) + ")")
|
|
|
|
: getText(Result, *E))
|
|
|
|
.str();
|
|
|
|
return ExprText + " " + (Negated ? "!=" : "==") + " " + Constant;
|
|
|
|
}
|
|
|
|
|
|
|
|
std::string compareExpressionToNullPtr(const MatchFinder::MatchResult &Result,
|
|
|
|
const Expr *E, bool Negated) {
|
|
|
|
const char *NullPtr =
|
|
|
|
Result.Context->getLangOpts().CPlusPlus11 ? "nullptr" : "NULL";
|
|
|
|
return compareExpressionToConstant(Result, E, Negated, NullPtr);
|
|
|
|
}
|
|
|
|
|
|
|
|
std::string compareExpressionToZero(const MatchFinder::MatchResult &Result,
|
|
|
|
const Expr *E, bool Negated) {
|
|
|
|
return compareExpressionToConstant(Result, E, Negated, "0");
|
|
|
|
}
|
|
|
|
|
2015-04-11 03:26:43 +08:00
|
|
|
std::string replacementExpression(const MatchFinder::MatchResult &Result,
|
|
|
|
bool Negated, const Expr *E) {
|
2020-09-01 00:03:31 +08:00
|
|
|
E = E->IgnoreParenBaseCasts();
|
2018-05-23 01:24:28 +08:00
|
|
|
if (const auto *EC = dyn_cast<ExprWithCleanups>(E))
|
|
|
|
E = EC->getSubExpr();
|
|
|
|
|
2015-07-01 20:39:40 +08:00
|
|
|
const bool NeedsStaticCast = needsStaticCast(E);
|
2015-04-11 03:26:43 +08:00
|
|
|
if (Negated) {
|
2015-07-01 20:39:40 +08:00
|
|
|
if (const auto *UnOp = dyn_cast<UnaryOperator>(E)) {
|
|
|
|
if (UnOp->getOpcode() == UO_LNot) {
|
|
|
|
if (needsNullPtrComparison(UnOp->getSubExpr()))
|
2016-02-12 23:09:05 +08:00
|
|
|
return compareExpressionToNullPtr(Result, UnOp->getSubExpr(), true);
|
|
|
|
|
|
|
|
if (needsZeroComparison(UnOp->getSubExpr()))
|
|
|
|
return compareExpressionToZero(Result, UnOp->getSubExpr(), true);
|
2015-07-01 20:39:40 +08:00
|
|
|
|
|
|
|
return replacementExpression(Result, false, UnOp->getSubExpr());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (needsNullPtrComparison(E))
|
2016-02-12 23:09:05 +08:00
|
|
|
return compareExpressionToNullPtr(Result, E, false);
|
|
|
|
|
|
|
|
if (needsZeroComparison(E))
|
|
|
|
return compareExpressionToZero(Result, E, false);
|
2015-07-01 20:39:40 +08:00
|
|
|
|
|
|
|
StringRef NegatedOperator;
|
|
|
|
const Expr *LHS = nullptr;
|
|
|
|
const Expr *RHS = nullptr;
|
2015-04-11 03:26:43 +08:00
|
|
|
if (const auto *BinOp = dyn_cast<BinaryOperator>(E)) {
|
2015-07-01 20:39:40 +08:00
|
|
|
NegatedOperator = negatedOperator(BinOp);
|
|
|
|
LHS = BinOp->getLHS();
|
|
|
|
RHS = BinOp->getRHS();
|
|
|
|
} else if (const auto *OpExpr = dyn_cast<CXXOperatorCallExpr>(E)) {
|
|
|
|
if (OpExpr->getNumArgs() == 2) {
|
|
|
|
NegatedOperator = negatedOperator(OpExpr);
|
|
|
|
LHS = OpExpr->getArg(0);
|
|
|
|
RHS = OpExpr->getArg(1);
|
2015-04-11 03:26:43 +08:00
|
|
|
}
|
|
|
|
}
|
2016-02-12 23:09:05 +08:00
|
|
|
if (!NegatedOperator.empty() && LHS && RHS)
|
2015-07-01 20:39:40 +08:00
|
|
|
return (asBool((getText(Result, *LHS) + " " + NegatedOperator + " " +
|
2016-02-12 23:09:05 +08:00
|
|
|
getText(Result, *RHS))
|
|
|
|
.str(),
|
2015-07-01 20:39:40 +08:00
|
|
|
NeedsStaticCast));
|
|
|
|
|
|
|
|
StringRef Text = getText(Result, *E);
|
|
|
|
if (!NeedsStaticCast && needsParensAfterUnaryNegation(E))
|
|
|
|
return ("!(" + Text + ")").str();
|
|
|
|
|
|
|
|
if (needsNullPtrComparison(E))
|
2016-02-12 23:09:05 +08:00
|
|
|
return compareExpressionToNullPtr(Result, E, false);
|
|
|
|
|
|
|
|
if (needsZeroComparison(E))
|
|
|
|
return compareExpressionToZero(Result, E, false);
|
2015-07-01 20:39:40 +08:00
|
|
|
|
|
|
|
return ("!" + asBool(Text, NeedsStaticCast));
|
2015-04-11 03:26:43 +08:00
|
|
|
}
|
2015-07-01 20:39:40 +08:00
|
|
|
|
|
|
|
if (const auto *UnOp = dyn_cast<UnaryOperator>(E)) {
|
|
|
|
if (UnOp->getOpcode() == UO_LNot) {
|
|
|
|
if (needsNullPtrComparison(UnOp->getSubExpr()))
|
2016-02-12 23:09:05 +08:00
|
|
|
return compareExpressionToNullPtr(Result, UnOp->getSubExpr(), false);
|
|
|
|
|
|
|
|
if (needsZeroComparison(UnOp->getSubExpr()))
|
|
|
|
return compareExpressionToZero(Result, UnOp->getSubExpr(), false);
|
2015-07-01 20:39:40 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (needsNullPtrComparison(E))
|
2016-02-12 23:09:05 +08:00
|
|
|
return compareExpressionToNullPtr(Result, E, true);
|
|
|
|
|
|
|
|
if (needsZeroComparison(E))
|
|
|
|
return compareExpressionToZero(Result, E, true);
|
2015-07-01 20:39:40 +08:00
|
|
|
|
|
|
|
return asBool(getText(Result, *E), NeedsStaticCast);
|
|
|
|
}
|
|
|
|
|
2020-08-22 20:57:34 +08:00
|
|
|
const Expr *stmtReturnsBool(const ReturnStmt *Ret, bool Negated) {
|
2015-07-01 20:39:40 +08:00
|
|
|
if (const auto *Bool = dyn_cast<CXXBoolLiteralExpr>(Ret->getRetValue())) {
|
|
|
|
if (Bool->getValue() == !Negated)
|
|
|
|
return Bool;
|
|
|
|
}
|
2020-08-22 20:57:34 +08:00
|
|
|
if (const auto *Unary = dyn_cast<UnaryOperator>(Ret->getRetValue())) {
|
|
|
|
if (Unary->getOpcode() == UO_LNot) {
|
|
|
|
if (const auto *Bool =
|
|
|
|
dyn_cast<CXXBoolLiteralExpr>(Unary->getSubExpr())) {
|
|
|
|
if (Bool->getValue() == Negated)
|
|
|
|
return Bool;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2015-07-01 20:39:40 +08:00
|
|
|
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
|
2020-08-22 20:57:34 +08:00
|
|
|
const Expr *stmtReturnsBool(const IfStmt *IfRet, bool Negated) {
|
2015-07-01 20:39:40 +08:00
|
|
|
if (IfRet->getElse() != nullptr)
|
|
|
|
return nullptr;
|
|
|
|
|
|
|
|
if (const auto *Ret = dyn_cast<ReturnStmt>(IfRet->getThen()))
|
|
|
|
return stmtReturnsBool(Ret, Negated);
|
|
|
|
|
|
|
|
if (const auto *Compound = dyn_cast<CompoundStmt>(IfRet->getThen())) {
|
|
|
|
if (Compound->size() == 1) {
|
|
|
|
if (const auto *CompoundRet = dyn_cast<ReturnStmt>(Compound->body_back()))
|
|
|
|
return stmtReturnsBool(CompoundRet, Negated);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return nullptr;
|
2015-04-11 03:26:43 +08:00
|
|
|
}
|
|
|
|
|
2016-02-12 23:09:05 +08:00
|
|
|
bool containsDiscardedTokens(const MatchFinder::MatchResult &Result,
|
|
|
|
CharSourceRange CharRange) {
|
|
|
|
std::string ReplacementText =
|
|
|
|
Lexer::getSourceText(CharRange, *Result.SourceManager,
|
|
|
|
Result.Context->getLangOpts())
|
|
|
|
.str();
|
|
|
|
Lexer Lex(CharRange.getBegin(), Result.Context->getLangOpts(),
|
|
|
|
ReplacementText.data(), ReplacementText.data(),
|
|
|
|
ReplacementText.data() + ReplacementText.size());
|
|
|
|
Lex.SetCommentRetentionState(true);
|
|
|
|
|
|
|
|
Token Tok;
|
|
|
|
while (!Lex.LexFromRawLexer(Tok)) {
|
|
|
|
if (Tok.is(tok::TokenKind::comment) || Tok.is(tok::TokenKind::hash))
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2015-04-11 03:26:43 +08:00
|
|
|
} // namespace
|
|
|
|
|
2017-05-16 01:06:51 +08:00
|
|
|
class SimplifyBooleanExprCheck::Visitor : public RecursiveASTVisitor<Visitor> {
|
|
|
|
public:
|
|
|
|
Visitor(SimplifyBooleanExprCheck *Check,
|
|
|
|
const MatchFinder::MatchResult &Result)
|
|
|
|
: Check(Check), Result(Result) {}
|
|
|
|
|
|
|
|
bool VisitBinaryOperator(BinaryOperator *Op) {
|
|
|
|
Check->reportBinOp(Result, Op);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
private:
|
|
|
|
SimplifyBooleanExprCheck *Check;
|
|
|
|
const MatchFinder::MatchResult &Result;
|
|
|
|
};
|
|
|
|
|
2015-05-17 20:31:12 +08:00
|
|
|
SimplifyBooleanExprCheck::SimplifyBooleanExprCheck(StringRef Name,
|
|
|
|
ClangTidyContext *Context)
|
|
|
|
: ClangTidyCheck(Name, Context),
|
2020-04-10 05:47:09 +08:00
|
|
|
ChainedConditionalReturn(Options.get("ChainedConditionalReturn", false)),
|
2015-05-17 20:31:12 +08:00
|
|
|
ChainedConditionalAssignment(
|
2020-04-10 05:47:09 +08:00
|
|
|
Options.get("ChainedConditionalAssignment", false)) {}
|
2015-05-17 20:31:12 +08:00
|
|
|
|
2017-05-16 01:06:51 +08:00
|
|
|
bool containsBoolLiteral(const Expr *E) {
|
|
|
|
if (!E)
|
|
|
|
return false;
|
|
|
|
E = E->IgnoreParenImpCasts();
|
|
|
|
if (isa<CXXBoolLiteralExpr>(E))
|
|
|
|
return true;
|
|
|
|
if (const auto *BinOp = dyn_cast<BinaryOperator>(E))
|
|
|
|
return containsBoolLiteral(BinOp->getLHS()) ||
|
|
|
|
containsBoolLiteral(BinOp->getRHS());
|
|
|
|
if (const auto *UnaryOp = dyn_cast<UnaryOperator>(E))
|
|
|
|
return containsBoolLiteral(UnaryOp->getSubExpr());
|
|
|
|
return false;
|
2015-04-11 03:26:43 +08:00
|
|
|
}
|
|
|
|
|
2017-05-16 01:06:51 +08:00
|
|
|
void SimplifyBooleanExprCheck::reportBinOp(
|
|
|
|
const MatchFinder::MatchResult &Result, const BinaryOperator *Op) {
|
|
|
|
const auto *LHS = Op->getLHS()->IgnoreParenImpCasts();
|
|
|
|
const auto *RHS = Op->getRHS()->IgnoreParenImpCasts();
|
2015-04-11 03:26:43 +08:00
|
|
|
|
2019-05-16 20:35:00 +08:00
|
|
|
const CXXBoolLiteralExpr *Bool;
|
2017-05-16 01:06:51 +08:00
|
|
|
const Expr *Other = nullptr;
|
|
|
|
if ((Bool = dyn_cast<CXXBoolLiteralExpr>(LHS)))
|
|
|
|
Other = RHS;
|
|
|
|
else if ((Bool = dyn_cast<CXXBoolLiteralExpr>(RHS)))
|
|
|
|
Other = LHS;
|
|
|
|
else
|
|
|
|
return;
|
|
|
|
|
2018-08-10 06:42:26 +08:00
|
|
|
if (Bool->getBeginLoc().isMacroID())
|
2017-05-16 01:06:51 +08:00
|
|
|
return;
|
|
|
|
|
|
|
|
// FIXME: why do we need this?
|
|
|
|
if (!isa<CXXBoolLiteralExpr>(Other) && containsBoolLiteral(Other))
|
|
|
|
return;
|
|
|
|
|
|
|
|
bool BoolValue = Bool->getValue();
|
|
|
|
|
2021-01-29 07:49:53 +08:00
|
|
|
auto ReplaceWithExpression = [this, &Result, LHS, RHS,
|
|
|
|
Bool](const Expr *ReplaceWith, bool Negated) {
|
2017-05-16 01:06:51 +08:00
|
|
|
std::string Replacement =
|
|
|
|
replacementExpression(Result, Negated, ReplaceWith);
|
2018-08-10 06:43:02 +08:00
|
|
|
SourceRange Range(LHS->getBeginLoc(), RHS->getEndLoc());
|
2018-08-10 06:42:26 +08:00
|
|
|
issueDiag(Result, Bool->getBeginLoc(), SimplifyOperatorDiagnostic, Range,
|
2017-05-16 01:06:51 +08:00
|
|
|
Replacement);
|
|
|
|
};
|
|
|
|
|
|
|
|
switch (Op->getOpcode()) {
|
|
|
|
case BO_LAnd:
|
|
|
|
if (BoolValue) {
|
|
|
|
// expr && true -> expr
|
2021-01-29 07:49:53 +08:00
|
|
|
ReplaceWithExpression(Other, /*Negated=*/false);
|
2017-05-16 01:06:51 +08:00
|
|
|
} else {
|
|
|
|
// expr && false -> false
|
2021-01-29 07:49:53 +08:00
|
|
|
ReplaceWithExpression(Bool, /*Negated=*/false);
|
2017-05-16 01:06:51 +08:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
case BO_LOr:
|
|
|
|
if (BoolValue) {
|
|
|
|
// expr || true -> true
|
2021-01-29 07:49:53 +08:00
|
|
|
ReplaceWithExpression(Bool, /*Negated=*/false);
|
2017-05-16 01:06:51 +08:00
|
|
|
} else {
|
|
|
|
// expr || false -> expr
|
2021-01-29 07:49:53 +08:00
|
|
|
ReplaceWithExpression(Other, /*Negated=*/false);
|
2017-05-16 01:06:51 +08:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
case BO_EQ:
|
|
|
|
// expr == true -> expr, expr == false -> !expr
|
2021-01-29 07:49:53 +08:00
|
|
|
ReplaceWithExpression(Other, /*Negated=*/!BoolValue);
|
2017-05-16 01:06:51 +08:00
|
|
|
break;
|
|
|
|
case BO_NE:
|
|
|
|
// expr != true -> !expr, expr != false -> expr
|
2021-01-29 07:49:53 +08:00
|
|
|
ReplaceWithExpression(Other, /*Negated=*/BoolValue);
|
2017-05-16 01:06:51 +08:00
|
|
|
break;
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
2015-04-11 03:26:43 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
void SimplifyBooleanExprCheck::matchBoolCondition(MatchFinder *Finder,
|
|
|
|
bool Value,
|
|
|
|
StringRef BooleanId) {
|
2016-02-12 23:09:05 +08:00
|
|
|
Finder->addMatcher(
|
2020-12-30 07:31:09 +08:00
|
|
|
ifStmt(hasCondition(literalOrNegatedBool(Value).bind(BooleanId)))
|
2016-02-12 23:09:05 +08:00
|
|
|
.bind(IfStmtId),
|
|
|
|
this);
|
2015-04-11 03:26:43 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
void SimplifyBooleanExprCheck::matchTernaryResult(MatchFinder *Finder,
|
|
|
|
bool Value,
|
|
|
|
StringRef TernaryId) {
|
|
|
|
Finder->addMatcher(
|
2020-12-30 07:31:09 +08:00
|
|
|
conditionalOperator(hasTrueExpression(literalOrNegatedBool(Value)),
|
2020-08-22 20:57:34 +08:00
|
|
|
hasFalseExpression(literalOrNegatedBool(!Value)))
|
2015-04-11 03:26:43 +08:00
|
|
|
.bind(TernaryId),
|
|
|
|
this);
|
|
|
|
}
|
|
|
|
|
|
|
|
void SimplifyBooleanExprCheck::matchIfReturnsBool(MatchFinder *Finder,
|
|
|
|
bool Value, StringRef Id) {
|
2016-02-12 23:09:05 +08:00
|
|
|
if (ChainedConditionalReturn)
|
2020-12-30 07:31:09 +08:00
|
|
|
Finder->addMatcher(ifStmt(hasThen(returnsBool(Value, ThenLiteralId)),
|
2016-02-12 23:09:05 +08:00
|
|
|
hasElse(returnsBool(!Value)))
|
|
|
|
.bind(Id),
|
2015-05-17 20:31:12 +08:00
|
|
|
this);
|
2016-02-12 23:09:05 +08:00
|
|
|
else
|
2020-12-30 07:31:09 +08:00
|
|
|
Finder->addMatcher(ifStmt(unless(hasParent(ifStmt())),
|
2015-07-01 20:39:40 +08:00
|
|
|
hasThen(returnsBool(Value, ThenLiteralId)),
|
2016-02-12 23:09:05 +08:00
|
|
|
hasElse(returnsBool(!Value)))
|
|
|
|
.bind(Id),
|
2015-05-17 20:31:12 +08:00
|
|
|
this);
|
2015-04-11 03:26:43 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
void SimplifyBooleanExprCheck::matchIfAssignsBool(MatchFinder *Finder,
|
|
|
|
bool Value, StringRef Id) {
|
2019-05-16 20:35:00 +08:00
|
|
|
auto VarAssign = declRefExpr(hasDeclaration(decl().bind(IfAssignVarId)));
|
|
|
|
auto VarRef = declRefExpr(hasDeclaration(equalsBoundNode(IfAssignVarId)));
|
|
|
|
auto MemAssign = memberExpr(hasDeclaration(decl().bind(IfAssignVarId)));
|
|
|
|
auto MemRef = memberExpr(hasDeclaration(equalsBoundNode(IfAssignVarId)));
|
|
|
|
auto SimpleThen =
|
|
|
|
binaryOperator(hasOperatorName("="), hasLHS(anyOf(VarAssign, MemAssign)),
|
|
|
|
hasLHS(expr().bind(IfAssignVariableId)),
|
2020-08-22 20:57:34 +08:00
|
|
|
hasRHS(literalOrNegatedBool(Value).bind(IfAssignLocId)));
|
2015-04-11 03:26:43 +08:00
|
|
|
auto Then = anyOf(SimpleThen, compoundStmt(statementCountIs(1),
|
|
|
|
hasAnySubstatement(SimpleThen)));
|
2019-05-16 20:35:00 +08:00
|
|
|
auto SimpleElse =
|
|
|
|
binaryOperator(hasOperatorName("="), hasLHS(anyOf(VarRef, MemRef)),
|
2020-08-22 20:57:34 +08:00
|
|
|
hasRHS(literalOrNegatedBool(!Value)));
|
2015-04-11 03:26:43 +08:00
|
|
|
auto Else = anyOf(SimpleElse, compoundStmt(statementCountIs(1),
|
|
|
|
hasAnySubstatement(SimpleElse)));
|
2016-02-12 23:09:05 +08:00
|
|
|
if (ChainedConditionalAssignment)
|
2020-12-30 07:31:09 +08:00
|
|
|
Finder->addMatcher(ifStmt(hasThen(Then), hasElse(Else)).bind(Id), this);
|
2019-05-16 20:35:00 +08:00
|
|
|
else
|
2020-12-30 07:31:09 +08:00
|
|
|
Finder->addMatcher(
|
|
|
|
ifStmt(unless(hasParent(ifStmt())), hasThen(Then), hasElse(Else))
|
|
|
|
.bind(Id),
|
|
|
|
this);
|
2015-05-17 20:31:12 +08:00
|
|
|
}
|
|
|
|
|
2015-07-01 20:39:40 +08:00
|
|
|
void SimplifyBooleanExprCheck::matchCompoundIfReturnsBool(MatchFinder *Finder,
|
|
|
|
bool Value,
|
|
|
|
StringRef Id) {
|
|
|
|
Finder->addMatcher(
|
2018-11-25 10:41:01 +08:00
|
|
|
compoundStmt(
|
|
|
|
hasAnySubstatement(
|
|
|
|
ifStmt(hasThen(returnsBool(Value)), unless(hasElse(stmt())))),
|
2020-12-30 07:31:09 +08:00
|
|
|
hasAnySubstatement(returnStmt(has(literalOrNegatedBool(!Value)))
|
2018-11-25 10:41:01 +08:00
|
|
|
.bind(CompoundReturnId)))
|
2016-02-12 23:09:05 +08:00
|
|
|
.bind(Id),
|
2015-07-01 20:39:40 +08:00
|
|
|
this);
|
|
|
|
}
|
|
|
|
|
2015-05-17 20:31:12 +08:00
|
|
|
void SimplifyBooleanExprCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
|
|
|
|
Options.store(Opts, "ChainedConditionalReturn", ChainedConditionalReturn);
|
|
|
|
Options.store(Opts, "ChainedConditionalAssignment",
|
|
|
|
ChainedConditionalAssignment);
|
2015-04-11 03:26:43 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
void SimplifyBooleanExprCheck::registerMatchers(MatchFinder *Finder) {
|
2018-12-20 21:50:04 +08:00
|
|
|
Finder->addMatcher(translationUnitDecl().bind("top"), this);
|
2015-04-11 03:26:43 +08:00
|
|
|
|
|
|
|
matchBoolCondition(Finder, true, ConditionThenStmtId);
|
|
|
|
matchBoolCondition(Finder, false, ConditionElseStmtId);
|
|
|
|
|
|
|
|
matchTernaryResult(Finder, true, TernaryId);
|
|
|
|
matchTernaryResult(Finder, false, TernaryNegatedId);
|
|
|
|
|
|
|
|
matchIfReturnsBool(Finder, true, IfReturnsBoolId);
|
|
|
|
matchIfReturnsBool(Finder, false, IfReturnsNotBoolId);
|
|
|
|
|
|
|
|
matchIfAssignsBool(Finder, true, IfAssignBoolId);
|
|
|
|
matchIfAssignsBool(Finder, false, IfAssignNotBoolId);
|
2015-07-01 20:39:40 +08:00
|
|
|
|
|
|
|
matchCompoundIfReturnsBool(Finder, true, CompoundBoolId);
|
|
|
|
matchCompoundIfReturnsBool(Finder, false, CompoundNotBoolId);
|
2015-04-11 03:26:43 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
void SimplifyBooleanExprCheck::check(const MatchFinder::MatchResult &Result) {
|
2018-12-21 16:51:04 +08:00
|
|
|
if (Result.Nodes.getNodeAs<TranslationUnitDecl>("top"))
|
2018-12-20 21:50:04 +08:00
|
|
|
Visitor(this, Result).TraverseAST(*Result.Context);
|
2020-08-22 20:57:34 +08:00
|
|
|
else if (const Expr *TrueConditionRemoved =
|
2018-12-20 21:50:04 +08:00
|
|
|
getBoolLiteral(Result, ConditionThenStmtId))
|
2015-04-11 03:26:43 +08:00
|
|
|
replaceWithThenStatement(Result, TrueConditionRemoved);
|
2020-08-22 20:57:34 +08:00
|
|
|
else if (const Expr *FalseConditionRemoved =
|
2016-02-12 23:09:05 +08:00
|
|
|
getBoolLiteral(Result, ConditionElseStmtId))
|
2015-04-11 03:26:43 +08:00
|
|
|
replaceWithElseStatement(Result, FalseConditionRemoved);
|
2016-02-12 23:09:05 +08:00
|
|
|
else if (const auto *Ternary =
|
|
|
|
Result.Nodes.getNodeAs<ConditionalOperator>(TernaryId))
|
2015-04-11 03:26:43 +08:00
|
|
|
replaceWithCondition(Result, Ternary);
|
2016-02-12 23:09:05 +08:00
|
|
|
else if (const auto *TernaryNegated =
|
|
|
|
Result.Nodes.getNodeAs<ConditionalOperator>(TernaryNegatedId))
|
2015-04-11 03:26:43 +08:00
|
|
|
replaceWithCondition(Result, TernaryNegated, true);
|
2016-02-12 23:09:05 +08:00
|
|
|
else if (const auto *If = Result.Nodes.getNodeAs<IfStmt>(IfReturnsBoolId))
|
2015-04-11 03:26:43 +08:00
|
|
|
replaceWithReturnCondition(Result, If);
|
2016-02-12 23:09:05 +08:00
|
|
|
else if (const auto *IfNot =
|
|
|
|
Result.Nodes.getNodeAs<IfStmt>(IfReturnsNotBoolId))
|
2015-04-11 03:26:43 +08:00
|
|
|
replaceWithReturnCondition(Result, IfNot, true);
|
2016-02-12 23:09:05 +08:00
|
|
|
else if (const auto *IfAssign =
|
|
|
|
Result.Nodes.getNodeAs<IfStmt>(IfAssignBoolId))
|
2015-04-11 03:26:43 +08:00
|
|
|
replaceWithAssignment(Result, IfAssign);
|
2016-02-12 23:09:05 +08:00
|
|
|
else if (const auto *IfAssignNot =
|
|
|
|
Result.Nodes.getNodeAs<IfStmt>(IfAssignNotBoolId))
|
2015-04-11 03:26:43 +08:00
|
|
|
replaceWithAssignment(Result, IfAssignNot, true);
|
2016-02-12 23:09:05 +08:00
|
|
|
else if (const auto *Compound =
|
|
|
|
Result.Nodes.getNodeAs<CompoundStmt>(CompoundBoolId))
|
2015-07-01 20:39:40 +08:00
|
|
|
replaceCompoundReturnWithCondition(Result, Compound);
|
2016-02-12 23:09:05 +08:00
|
|
|
else if (const auto *Compound =
|
|
|
|
Result.Nodes.getNodeAs<CompoundStmt>(CompoundNotBoolId))
|
2015-07-01 20:39:40 +08:00
|
|
|
replaceCompoundReturnWithCondition(Result, Compound, true);
|
2015-12-28 21:21:22 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
void SimplifyBooleanExprCheck::issueDiag(
|
|
|
|
const ast_matchers::MatchFinder::MatchResult &Result, SourceLocation Loc,
|
|
|
|
StringRef Description, SourceRange ReplacementRange,
|
|
|
|
StringRef Replacement) {
|
2016-09-24 10:13:45 +08:00
|
|
|
CharSourceRange CharRange =
|
|
|
|
Lexer::makeFileCharRange(CharSourceRange::getTokenRange(ReplacementRange),
|
|
|
|
*Result.SourceManager, getLangOpts());
|
2015-12-28 21:21:22 +08:00
|
|
|
|
|
|
|
DiagnosticBuilder Diag = diag(Loc, Description);
|
|
|
|
if (!containsDiscardedTokens(Result, CharRange))
|
|
|
|
Diag << FixItHint::CreateReplacement(CharRange, Replacement);
|
|
|
|
}
|
|
|
|
|
2015-04-11 03:26:43 +08:00
|
|
|
void SimplifyBooleanExprCheck::replaceWithThenStatement(
|
2020-08-22 20:57:34 +08:00
|
|
|
const MatchFinder::MatchResult &Result, const Expr *TrueConditionRemoved) {
|
2015-04-11 03:26:43 +08:00
|
|
|
const auto *IfStatement = Result.Nodes.getNodeAs<IfStmt>(IfStmtId);
|
2018-08-10 06:42:26 +08:00
|
|
|
issueDiag(Result, TrueConditionRemoved->getBeginLoc(),
|
2015-12-28 21:21:22 +08:00
|
|
|
SimplifyConditionDiagnostic, IfStatement->getSourceRange(),
|
|
|
|
getText(Result, *IfStatement->getThen()));
|
2015-04-11 03:26:43 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
void SimplifyBooleanExprCheck::replaceWithElseStatement(
|
2020-08-22 20:57:34 +08:00
|
|
|
const MatchFinder::MatchResult &Result, const Expr *FalseConditionRemoved) {
|
2015-04-11 03:26:43 +08:00
|
|
|
const auto *IfStatement = Result.Nodes.getNodeAs<IfStmt>(IfStmtId);
|
|
|
|
const Stmt *ElseStatement = IfStatement->getElse();
|
2018-08-10 06:42:26 +08:00
|
|
|
issueDiag(Result, FalseConditionRemoved->getBeginLoc(),
|
2015-12-28 21:21:22 +08:00
|
|
|
SimplifyConditionDiagnostic, IfStatement->getSourceRange(),
|
|
|
|
ElseStatement ? getText(Result, *ElseStatement) : "");
|
2015-04-11 03:26:43 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
void SimplifyBooleanExprCheck::replaceWithCondition(
|
|
|
|
const MatchFinder::MatchResult &Result, const ConditionalOperator *Ternary,
|
|
|
|
bool Negated) {
|
|
|
|
std::string Replacement =
|
|
|
|
replacementExpression(Result, Negated, Ternary->getCond());
|
2018-08-10 06:42:26 +08:00
|
|
|
issueDiag(Result, Ternary->getTrueExpr()->getBeginLoc(),
|
2015-12-28 21:21:22 +08:00
|
|
|
"redundant boolean literal in ternary expression result",
|
|
|
|
Ternary->getSourceRange(), Replacement);
|
2015-04-11 03:26:43 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
void SimplifyBooleanExprCheck::replaceWithReturnCondition(
|
|
|
|
const MatchFinder::MatchResult &Result, const IfStmt *If, bool Negated) {
|
|
|
|
StringRef Terminator = isa<CompoundStmt>(If->getElse()) ? ";" : "";
|
|
|
|
std::string Condition = replacementExpression(Result, Negated, If->getCond());
|
|
|
|
std::string Replacement = ("return " + Condition + Terminator).str();
|
|
|
|
SourceLocation Start =
|
2018-08-10 06:42:26 +08:00
|
|
|
Result.Nodes.getNodeAs<CXXBoolLiteralExpr>(ThenLiteralId)->getBeginLoc();
|
2015-12-28 21:21:22 +08:00
|
|
|
issueDiag(Result, Start, SimplifyConditionalReturnDiagnostic,
|
|
|
|
If->getSourceRange(), Replacement);
|
2015-04-11 03:26:43 +08:00
|
|
|
}
|
|
|
|
|
2015-07-01 20:39:40 +08:00
|
|
|
void SimplifyBooleanExprCheck::replaceCompoundReturnWithCondition(
|
|
|
|
const MatchFinder::MatchResult &Result, const CompoundStmt *Compound,
|
|
|
|
bool Negated) {
|
|
|
|
const auto *Ret = Result.Nodes.getNodeAs<ReturnStmt>(CompoundReturnId);
|
|
|
|
|
|
|
|
// The body shouldn't be empty because the matcher ensures that it must
|
|
|
|
// contain at least two statements:
|
|
|
|
// 1) A `return` statement returning a boolean literal `false` or `true`
|
2016-02-12 23:09:05 +08:00
|
|
|
// 2) An `if` statement with no `else` clause that consists of a single
|
2015-07-01 20:39:40 +08:00
|
|
|
// `return` statement returning the opposite boolean literal `true` or
|
|
|
|
// `false`.
|
|
|
|
assert(Compound->size() >= 2);
|
|
|
|
const IfStmt *BeforeIf = nullptr;
|
|
|
|
CompoundStmt::const_body_iterator Current = Compound->body_begin();
|
|
|
|
CompoundStmt::const_body_iterator After = Compound->body_begin();
|
|
|
|
for (++After; After != Compound->body_end() && *Current != Ret;
|
|
|
|
++Current, ++After) {
|
|
|
|
if (const auto *If = dyn_cast<IfStmt>(*Current)) {
|
2020-08-22 20:57:34 +08:00
|
|
|
if (const Expr *Lit = stmtReturnsBool(If, Negated)) {
|
2015-07-01 20:39:40 +08:00
|
|
|
if (*After == Ret) {
|
|
|
|
if (!ChainedConditionalReturn && BeforeIf)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
const Expr *Condition = If->getCond();
|
|
|
|
std::string Replacement =
|
|
|
|
"return " + replacementExpression(Result, Negated, Condition);
|
2015-12-28 21:21:22 +08:00
|
|
|
issueDiag(
|
2018-08-10 06:42:26 +08:00
|
|
|
Result, Lit->getBeginLoc(), SimplifyConditionalReturnDiagnostic,
|
2018-08-10 06:43:02 +08:00
|
|
|
SourceRange(If->getBeginLoc(), Ret->getEndLoc()), Replacement);
|
2015-07-01 20:39:40 +08:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
BeforeIf = If;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
BeforeIf = nullptr;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-04-11 03:26:43 +08:00
|
|
|
void SimplifyBooleanExprCheck::replaceWithAssignment(
|
|
|
|
const MatchFinder::MatchResult &Result, const IfStmt *IfAssign,
|
|
|
|
bool Negated) {
|
|
|
|
SourceRange Range = IfAssign->getSourceRange();
|
|
|
|
StringRef VariableName =
|
|
|
|
getText(Result, *Result.Nodes.getNodeAs<Expr>(IfAssignVariableId));
|
|
|
|
StringRef Terminator = isa<CompoundStmt>(IfAssign->getElse()) ? ";" : "";
|
|
|
|
std::string Condition =
|
|
|
|
replacementExpression(Result, Negated, IfAssign->getCond());
|
|
|
|
std::string Replacement =
|
|
|
|
(VariableName + " = " + Condition + Terminator).str();
|
|
|
|
SourceLocation Location =
|
2018-08-10 06:42:26 +08:00
|
|
|
Result.Nodes.getNodeAs<CXXBoolLiteralExpr>(IfAssignLocId)->getBeginLoc();
|
2015-12-28 21:21:22 +08:00
|
|
|
issueDiag(Result, Location,
|
|
|
|
"redundant boolean literal in conditional assignment", Range,
|
|
|
|
Replacement);
|
2015-04-11 03:26:43 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
} // namespace readability
|
|
|
|
} // namespace tidy
|
|
|
|
} // namespace clang
|