clang-tools 24.0.0git
RedundantBranchConditionCheck.cpp
Go to the documentation of this file.
1//===----------------------------------------------------------------------===//
2//
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
6//
7//===----------------------------------------------------------------------===//
8
10#include "../utils/Aliasing.h"
11#include "../utils/LexerUtils.h"
12#include "clang/AST/ASTContext.h"
13#include "clang/AST/ParentMapContext.h"
14#include "clang/AST/StmtCXX.h"
15#include "clang/ASTMatchers/ASTMatchFinder.h"
16#include "clang/Analysis/Analyses/ExprMutationAnalyzer.h"
17#include "clang/Lex/Lexer.h"
18
19using namespace clang::ast_matchers;
21
22namespace clang::tidy::bugprone {
23
24static const char CondVarStr[] = "cond_var";
25static const char OuterIfStr[] = "outer_if";
26static const char InnerIfStr[] = "inner_if";
27static const char OuterIfVar1Str[] = "outer_if_var1";
28static const char OuterIfVar2Str[] = "outer_if_var2";
29static const char InnerIfVar1Str[] = "inner_if_var1";
30static const char InnerIfVar2Str[] = "inner_if_var2";
31static const char FuncStr[] = "func";
32
33/// Returns whether `Var` is changed in range (`PrevS`..`NextS`).
34static bool isChangedBefore(const Stmt *S, const Stmt *NextS, const Stmt *PrevS,
35 const VarDecl *Var, ASTContext *Context) {
36 ExprMutationAnalyzer MutAn(*S, *Context);
37 const auto &SM = Context->getSourceManager();
38 const Stmt *MutS = MutAn.findMutation(Var);
39 return MutS &&
40 SM.isBeforeInTranslationUnit(PrevS->getEndLoc(),
41 MutS->getBeginLoc()) &&
42 SM.isBeforeInTranslationUnit(MutS->getEndLoc(), NextS->getBeginLoc());
43}
44
45/// Returns the outermost loop that encloses `S` and is itself enclosed by
46/// `Outer`, or null if there is no such loop. The walk passes through
47/// declarations, such as a variable initialized by a lambda, but stops at the
48/// enclosing function.
49static const Stmt *getOutermostLoopBetween(const Stmt *S, const Stmt *Outer,
50 ASTContext *Context) {
51 const Stmt *Loop = nullptr;
52 // getParents() returns only the direct parents of a node, usually exactly
53 // one, so the walk calls it once per level.
54 DynTypedNodeList Parents = Context->getParents(*S);
55 while (!Parents.empty()) {
56 const DynTypedNode Parent = Parents[0];
57 if (Parent.get<FunctionDecl>())
58 break;
59 if (const auto *ParentStmt = Parent.get<Stmt>()) {
60 if (ParentStmt == Outer)
61 break;
62 if (isa<ForStmt, WhileStmt, DoStmt, CXXForRangeStmt>(ParentStmt))
63 Loop = ParentStmt;
64 }
65 Parents = Context->getParents(Parent);
66 }
67 return Loop;
68}
69
71 const auto ImmutableVar =
72 varDecl(anyOf(parmVarDecl(), hasLocalStorage()), hasType(isInteger()),
73 unless(hasType(isVolatileQualified())))
74 .bind(CondVarStr);
75 Finder->addMatcher(
76 ifStmt(
77 hasCondition(anyOf(
78 declRefExpr(hasDeclaration(ImmutableVar)).bind(OuterIfVar1Str),
79 binaryOperator(
80 hasOperatorName("&&"),
81 hasEitherOperand(declRefExpr(hasDeclaration(ImmutableVar))
82 .bind(OuterIfVar2Str))))),
83 hasThen(hasDescendant(
84 ifStmt(hasCondition(anyOf(
85 declRefExpr(hasDeclaration(
86 varDecl(equalsBoundNode(CondVarStr))))
87 .bind(InnerIfVar1Str),
88 binaryOperator(
89 hasAnyOperatorName("&&", "||"),
90 hasEitherOperand(
91 declRefExpr(hasDeclaration(varDecl(
92 equalsBoundNode(CondVarStr))))
93 .bind(InnerIfVar2Str))))))
94 .bind(InnerIfStr))),
95 forFunction(functionDecl().bind(FuncStr)))
96 .bind(OuterIfStr),
97 this);
98 // FIXME: Handle longer conjunctive and disjunctive clauses.
99}
100
102 const MatchFinder::MatchResult &Result) {
103 const auto *OuterIf = Result.Nodes.getNodeAs<IfStmt>(OuterIfStr);
104 const auto *InnerIf = Result.Nodes.getNodeAs<IfStmt>(InnerIfStr);
105 const auto *CondVar = Result.Nodes.getNodeAs<VarDecl>(CondVarStr);
106 const auto *Func = Result.Nodes.getNodeAs<FunctionDecl>(FuncStr);
107
108 const DeclRefExpr *OuterIfVar = nullptr, *InnerIfVar = nullptr;
109 if (const auto *Inner = Result.Nodes.getNodeAs<DeclRefExpr>(InnerIfVar1Str))
110 InnerIfVar = Inner;
111 else
112 InnerIfVar = Result.Nodes.getNodeAs<DeclRefExpr>(InnerIfVar2Str);
113 if (const auto *Outer = Result.Nodes.getNodeAs<DeclRefExpr>(OuterIfVar1Str))
114 OuterIfVar = Outer;
115 else
116 OuterIfVar = Result.Nodes.getNodeAs<DeclRefExpr>(OuterIfVar2Str);
117
118 if (OuterIfVar && InnerIfVar) {
119 if (isChangedBefore(OuterIf->getThen(), InnerIfVar, OuterIfVar, CondVar,
120 Result.Context))
121 return;
122
123 if (isChangedBefore(OuterIf->getCond(), InnerIfVar, OuterIfVar, CondVar,
124 Result.Context))
125 return;
126 }
127
128 // Inside a loop, a mutation anywhere in the loop runs before the inner
129 // condition is evaluated again, even if it comes later in the source.
130 const Stmt *Loop = getOutermostLoopBetween(InnerIf, OuterIf, Result.Context);
131 if (Loop &&
132 ExprMutationAnalyzer(*Loop, *Result.Context).findMutation(CondVar))
133 return;
134
135 // If the variable has an alias then it can be changed by that alias as well.
136 // FIXME: could potentially support tracking pointers and references in the
137 // future to improve catching true positives through aliases.
138 if (hasPtrOrReferenceInFunc(Func, CondVar))
139 return;
140
141 const auto Diag = diag(InnerIf->getBeginLoc(), "redundant condition %0")
142 << CondVar;
143
144 // For standalone condition variables and for "or" binary operations we simply
145 // remove the inner `if`.
146 const auto *BinOpCond =
147 dyn_cast<BinaryOperator>(InnerIf->getCond()->IgnoreParenImpCasts());
148
149 if (isa<DeclRefExpr>(InnerIf->getCond()->IgnoreParenImpCasts()) ||
150 (BinOpCond && BinOpCond->getOpcode() == BO_LOr)) {
151 const SourceLocation IfBegin = InnerIf->getBeginLoc();
152 const Stmt *Body = InnerIf->getThen();
153 const Expr *OtherSide = nullptr;
154 if (BinOpCond) {
155 const auto *LeftDRE =
156 dyn_cast<DeclRefExpr>(BinOpCond->getLHS()->IgnoreParenImpCasts());
157 if (LeftDRE && LeftDRE->getDecl() == CondVar)
158 OtherSide = BinOpCond->getRHS();
159 else
160 OtherSide = BinOpCond->getLHS();
161 }
162
163 SourceLocation IfEnd = Body->getBeginLoc().getLocWithOffset(-1);
164
165 // For compound statements also remove the left brace.
166 if (isa<CompoundStmt>(Body))
167 IfEnd = Body->getBeginLoc();
168
169 // If the other side has side effects then keep it.
170 if (OtherSide && OtherSide->HasSideEffects(*Result.Context)) {
171 const SourceLocation BeforeOtherSide =
172 OtherSide->getBeginLoc().getLocWithOffset(-1);
173 if (const auto NextToken = utils::lexer::findNextTokenSkippingComments(
174 OtherSide->getEndLoc(), *Result.SourceManager, getLangOpts())) {
175 const SourceLocation AfterOtherSide = NextToken->getLocation();
176 Diag << FixItHint::CreateRemoval(
177 CharSourceRange::getTokenRange(IfBegin, BeforeOtherSide))
178 << FixItHint::CreateInsertion(AfterOtherSide, ";")
179 << FixItHint::CreateRemoval(
180 CharSourceRange::getTokenRange(AfterOtherSide, IfEnd));
181 }
182 } else {
183 Diag << FixItHint::CreateRemoval(
184 CharSourceRange::getTokenRange(IfBegin, IfEnd));
185 }
186
187 // For compound statements also remove the right brace at the end.
188 if (isa<CompoundStmt>(Body))
189 Diag << FixItHint::CreateRemoval(
190 CharSourceRange::getTokenRange(Body->getEndLoc(), Body->getEndLoc()));
191
192 // For "and" binary operations we remove the "and" operation with the
193 // condition variable from the inner if.
194 } else {
195 const auto *CondOp =
196 cast<BinaryOperator>(InnerIf->getCond()->IgnoreParenImpCasts());
197 const auto *LeftDRE =
198 dyn_cast<DeclRefExpr>(CondOp->getLHS()->IgnoreParenImpCasts());
199 if (LeftDRE && LeftDRE->getDecl() == CondVar) {
200 const SourceLocation BeforeRHS =
201 CondOp->getRHS()->getBeginLoc().getLocWithOffset(-1);
202 Diag << FixItHint::CreateRemoval(CharSourceRange::getTokenRange(
203 CondOp->getLHS()->getBeginLoc(), BeforeRHS));
204 } else if (const auto NextToken =
206 CondOp->getLHS()->getEndLoc(), *Result.SourceManager,
207 getLangOpts())) {
208 const SourceLocation AfterLHS = NextToken->getLocation();
209 Diag << FixItHint::CreateRemoval(CharSourceRange::getTokenRange(
210 AfterLHS, CondOp->getRHS()->getEndLoc()));
211 }
212 }
213}
214
215} // namespace clang::tidy::bugprone
bool hasPtrOrReferenceInFunc(const Decl *Func, const ValueDecl *Var)
Returns whether Var has a pointer or reference in Func.
Definition Aliasing.cpp:84
void check(const ast_matchers::MatchFinder::MatchResult &Result) override
void registerMatchers(ast_matchers::MatchFinder *Finder) override
static const Stmt * getOutermostLoopBetween(const Stmt *S, const Stmt *Outer, ASTContext *Context)
Returns the outermost loop that encloses S and is itself enclosed by Outer, or null if there is no su...
static bool isChangedBefore(const Stmt *S, const Stmt *NextS, const Stmt *PrevS, const VarDecl *Var, ASTContext *Context)
Returns whether Var is changed in range (PrevS..NextS).
std::optional< Token > findNextTokenSkippingComments(SourceLocation Start, const SourceManager &SM, const LangOptions &LangOpts)
Definition LexerUtils.h:106
bool hasPtrOrReferenceInFunc(const Decl *Func, const ValueDecl *Var)
Returns whether Var has a pointer or reference in Func.
Definition Aliasing.cpp:84