1 //===--- RedundantBranchConditionCheck.cpp - clang-tidy -------------------------===//
3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4 // See https://llvm.org/LICENSE.txt for license information.
5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
7 //===----------------------------------------------------------------------===//
9 #include "RedundantBranchConditionCheck.h"
10 #include "../utils/Aliasing.h"
11 #include "clang/AST/ASTContext.h"
12 #include "clang/ASTMatchers/ASTMatchFinder.h"
13 #include "clang/Analysis/Analyses/ExprMutationAnalyzer.h"
14 #include "clang/Lex/Lexer.h"
16 using namespace clang::ast_matchers
;
17 using clang::tidy::utils::hasPtrOrReferenceInFunc
;
19 namespace clang::tidy::bugprone
{
21 static const char CondVarStr
[] = "cond_var";
22 static const char OuterIfStr
[] = "outer_if";
23 static const char InnerIfStr
[] = "inner_if";
24 static const char OuterIfVar1Str
[] = "outer_if_var1";
25 static const char OuterIfVar2Str
[] = "outer_if_var2";
26 static const char InnerIfVar1Str
[] = "inner_if_var1";
27 static const char InnerIfVar2Str
[] = "inner_if_var2";
28 static const char FuncStr
[] = "func";
30 /// Returns whether `Var` is changed in range (`PrevS`..`NextS`).
31 static bool isChangedBefore(const Stmt
*S
, const Stmt
*NextS
, const Stmt
*PrevS
,
32 const VarDecl
*Var
, ASTContext
*Context
) {
33 ExprMutationAnalyzer
MutAn(*S
, *Context
);
34 const auto &SM
= Context
->getSourceManager();
35 const Stmt
*MutS
= MutAn
.findMutation(Var
);
37 SM
.isBeforeInTranslationUnit(PrevS
->getEndLoc(),
38 MutS
->getBeginLoc()) &&
39 SM
.isBeforeInTranslationUnit(MutS
->getEndLoc(), NextS
->getBeginLoc());
42 void RedundantBranchConditionCheck::registerMatchers(MatchFinder
*Finder
) {
43 const auto ImmutableVar
=
44 varDecl(anyOf(parmVarDecl(), hasLocalStorage()), hasType(isInteger()),
45 unless(hasType(isVolatileQualified())))
50 declRefExpr(hasDeclaration(ImmutableVar
)).bind(OuterIfVar1Str
),
52 hasOperatorName("&&"),
53 hasEitherOperand(declRefExpr(hasDeclaration(ImmutableVar
))
54 .bind(OuterIfVar2Str
))))),
55 hasThen(hasDescendant(
56 ifStmt(hasCondition(anyOf(
57 declRefExpr(hasDeclaration(
58 varDecl(equalsBoundNode(CondVarStr
))))
59 .bind(InnerIfVar1Str
),
61 hasAnyOperatorName("&&", "||"),
63 declRefExpr(hasDeclaration(varDecl(
64 equalsBoundNode(CondVarStr
))))
65 .bind(InnerIfVar2Str
))))))
67 forFunction(functionDecl().bind(FuncStr
)))
70 // FIXME: Handle longer conjunctive and disjunctive clauses.
73 void RedundantBranchConditionCheck::check(const MatchFinder::MatchResult
&Result
) {
74 const auto *OuterIf
= Result
.Nodes
.getNodeAs
<IfStmt
>(OuterIfStr
);
75 const auto *InnerIf
= Result
.Nodes
.getNodeAs
<IfStmt
>(InnerIfStr
);
76 const auto *CondVar
= Result
.Nodes
.getNodeAs
<VarDecl
>(CondVarStr
);
77 const auto *Func
= Result
.Nodes
.getNodeAs
<FunctionDecl
>(FuncStr
);
79 const DeclRefExpr
*OuterIfVar
= nullptr, *InnerIfVar
= nullptr;
80 if (const auto *Inner
= Result
.Nodes
.getNodeAs
<DeclRefExpr
>(InnerIfVar1Str
))
83 InnerIfVar
= Result
.Nodes
.getNodeAs
<DeclRefExpr
>(InnerIfVar2Str
);
84 if (const auto *Outer
= Result
.Nodes
.getNodeAs
<DeclRefExpr
>(OuterIfVar1Str
))
87 OuterIfVar
= Result
.Nodes
.getNodeAs
<DeclRefExpr
>(OuterIfVar2Str
);
89 if (OuterIfVar
&& InnerIfVar
) {
90 if (isChangedBefore(OuterIf
->getThen(), InnerIfVar
, OuterIfVar
, CondVar
,
94 if (isChangedBefore(OuterIf
->getCond(), InnerIfVar
, OuterIfVar
, CondVar
,
99 // If the variable has an alias then it can be changed by that alias as well.
100 // FIXME: could potentially support tracking pointers and references in the
101 // future to improve catching true positives through aliases.
102 if (hasPtrOrReferenceInFunc(Func
, CondVar
))
105 auto Diag
= diag(InnerIf
->getBeginLoc(), "redundant condition %0") << CondVar
;
107 // For standalone condition variables and for "or" binary operations we simply
108 // remove the inner `if`.
109 const auto *BinOpCond
=
110 dyn_cast
<BinaryOperator
>(InnerIf
->getCond()->IgnoreParenImpCasts());
112 if (isa
<DeclRefExpr
>(InnerIf
->getCond()->IgnoreParenImpCasts()) ||
113 (BinOpCond
&& BinOpCond
->getOpcode() == BO_LOr
)) {
114 SourceLocation IfBegin
= InnerIf
->getBeginLoc();
115 const Stmt
*Body
= InnerIf
->getThen();
116 const Expr
*OtherSide
= nullptr;
118 const auto *LeftDRE
=
119 dyn_cast
<DeclRefExpr
>(BinOpCond
->getLHS()->IgnoreParenImpCasts());
120 if (LeftDRE
&& LeftDRE
->getDecl() == CondVar
)
121 OtherSide
= BinOpCond
->getRHS();
123 OtherSide
= BinOpCond
->getLHS();
126 SourceLocation IfEnd
= Body
->getBeginLoc().getLocWithOffset(-1);
128 // For compound statements also remove the left brace.
129 if (isa
<CompoundStmt
>(Body
))
130 IfEnd
= Body
->getBeginLoc();
132 // If the other side has side effects then keep it.
133 if (OtherSide
&& OtherSide
->HasSideEffects(*Result
.Context
)) {
134 SourceLocation BeforeOtherSide
=
135 OtherSide
->getBeginLoc().getLocWithOffset(-1);
136 SourceLocation AfterOtherSide
=
137 Lexer::findNextToken(OtherSide
->getEndLoc(), *Result
.SourceManager
,
140 Diag
<< FixItHint::CreateRemoval(
141 CharSourceRange::getTokenRange(IfBegin
, BeforeOtherSide
))
142 << FixItHint::CreateInsertion(AfterOtherSide
, ";")
143 << FixItHint::CreateRemoval(
144 CharSourceRange::getTokenRange(AfterOtherSide
, IfEnd
));
146 Diag
<< FixItHint::CreateRemoval(
147 CharSourceRange::getTokenRange(IfBegin
, IfEnd
));
150 // For compound statements also remove the right brace at the end.
151 if (isa
<CompoundStmt
>(Body
))
152 Diag
<< FixItHint::CreateRemoval(
153 CharSourceRange::getTokenRange(Body
->getEndLoc(), Body
->getEndLoc()));
155 // For "and" binary operations we remove the "and" operation with the
156 // condition variable from the inner if.
159 cast
<BinaryOperator
>(InnerIf
->getCond()->IgnoreParenImpCasts());
160 const auto *LeftDRE
=
161 dyn_cast
<DeclRefExpr
>(CondOp
->getLHS()->IgnoreParenImpCasts());
162 if (LeftDRE
&& LeftDRE
->getDecl() == CondVar
) {
163 SourceLocation BeforeRHS
=
164 CondOp
->getRHS()->getBeginLoc().getLocWithOffset(-1);
165 Diag
<< FixItHint::CreateRemoval(CharSourceRange::getTokenRange(
166 CondOp
->getLHS()->getBeginLoc(), BeforeRHS
));
168 SourceLocation AfterLHS
=
169 Lexer::findNextToken(CondOp
->getLHS()->getEndLoc(),
170 *Result
.SourceManager
, getLangOpts())
172 Diag
<< FixItHint::CreateRemoval(CharSourceRange::getTokenRange(
173 AfterLHS
, CondOp
->getRHS()->getEndLoc()));
178 } // namespace clang::tidy::bugprone