clang-tools 22.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/ASTMatchers/ASTMatchFinder.h"
14#include "clang/Analysis/Analyses/ExprMutationAnalyzer.h"
15#include "clang/Lex/Lexer.h"
16
17using namespace clang::ast_matchers;
19
20namespace clang::tidy::bugprone {
21
22static const char CondVarStr[] = "cond_var";
23static const char OuterIfStr[] = "outer_if";
24static const char InnerIfStr[] = "inner_if";
25static const char OuterIfVar1Str[] = "outer_if_var1";
26static const char OuterIfVar2Str[] = "outer_if_var2";
27static const char InnerIfVar1Str[] = "inner_if_var1";
28static const char InnerIfVar2Str[] = "inner_if_var2";
29static const char FuncStr[] = "func";
30
31/// Returns whether `Var` is changed in range (`PrevS`..`NextS`).
32static bool isChangedBefore(const Stmt *S, const Stmt *NextS, const Stmt *PrevS,
33 const VarDecl *Var, ASTContext *Context) {
34 ExprMutationAnalyzer MutAn(*S, *Context);
35 const auto &SM = Context->getSourceManager();
36 const Stmt *MutS = MutAn.findMutation(Var);
37 return MutS &&
38 SM.isBeforeInTranslationUnit(PrevS->getEndLoc(),
39 MutS->getBeginLoc()) &&
40 SM.isBeforeInTranslationUnit(MutS->getEndLoc(), NextS->getBeginLoc());
41}
42
44 const auto ImmutableVar =
45 varDecl(anyOf(parmVarDecl(), hasLocalStorage()), hasType(isInteger()),
46 unless(hasType(isVolatileQualified())))
47 .bind(CondVarStr);
48 Finder->addMatcher(
49 ifStmt(
50 hasCondition(anyOf(
51 declRefExpr(hasDeclaration(ImmutableVar)).bind(OuterIfVar1Str),
52 binaryOperator(
53 hasOperatorName("&&"),
54 hasEitherOperand(declRefExpr(hasDeclaration(ImmutableVar))
55 .bind(OuterIfVar2Str))))),
56 hasThen(hasDescendant(
57 ifStmt(hasCondition(anyOf(
58 declRefExpr(hasDeclaration(
59 varDecl(equalsBoundNode(CondVarStr))))
60 .bind(InnerIfVar1Str),
61 binaryOperator(
62 hasAnyOperatorName("&&", "||"),
63 hasEitherOperand(
64 declRefExpr(hasDeclaration(varDecl(
65 equalsBoundNode(CondVarStr))))
66 .bind(InnerIfVar2Str))))))
67 .bind(InnerIfStr))),
68 forFunction(functionDecl().bind(FuncStr)))
69 .bind(OuterIfStr),
70 this);
71 // FIXME: Handle longer conjunctive and disjunctive clauses.
72}
73
75 const MatchFinder::MatchResult &Result) {
76 const auto *OuterIf = Result.Nodes.getNodeAs<IfStmt>(OuterIfStr);
77 const auto *InnerIf = Result.Nodes.getNodeAs<IfStmt>(InnerIfStr);
78 const auto *CondVar = Result.Nodes.getNodeAs<VarDecl>(CondVarStr);
79 const auto *Func = Result.Nodes.getNodeAs<FunctionDecl>(FuncStr);
80
81 const DeclRefExpr *OuterIfVar = nullptr, *InnerIfVar = nullptr;
82 if (const auto *Inner = Result.Nodes.getNodeAs<DeclRefExpr>(InnerIfVar1Str))
83 InnerIfVar = Inner;
84 else
85 InnerIfVar = Result.Nodes.getNodeAs<DeclRefExpr>(InnerIfVar2Str);
86 if (const auto *Outer = Result.Nodes.getNodeAs<DeclRefExpr>(OuterIfVar1Str))
87 OuterIfVar = Outer;
88 else
89 OuterIfVar = Result.Nodes.getNodeAs<DeclRefExpr>(OuterIfVar2Str);
90
91 if (OuterIfVar && InnerIfVar) {
92 if (isChangedBefore(OuterIf->getThen(), InnerIfVar, OuterIfVar, CondVar,
93 Result.Context))
94 return;
95
96 if (isChangedBefore(OuterIf->getCond(), InnerIfVar, OuterIfVar, CondVar,
97 Result.Context))
98 return;
99 }
100
101 // If the variable has an alias then it can be changed by that alias as well.
102 // FIXME: could potentially support tracking pointers and references in the
103 // future to improve catching true positives through aliases.
104 if (hasPtrOrReferenceInFunc(Func, CondVar))
105 return;
106
107 auto Diag = diag(InnerIf->getBeginLoc(), "redundant condition %0") << CondVar;
108
109 // For standalone condition variables and for "or" binary operations we simply
110 // remove the inner `if`.
111 const auto *BinOpCond =
112 dyn_cast<BinaryOperator>(InnerIf->getCond()->IgnoreParenImpCasts());
113
114 if (isa<DeclRefExpr>(InnerIf->getCond()->IgnoreParenImpCasts()) ||
115 (BinOpCond && BinOpCond->getOpcode() == BO_LOr)) {
116 const SourceLocation IfBegin = InnerIf->getBeginLoc();
117 const Stmt *Body = InnerIf->getThen();
118 const Expr *OtherSide = nullptr;
119 if (BinOpCond) {
120 const auto *LeftDRE =
121 dyn_cast<DeclRefExpr>(BinOpCond->getLHS()->IgnoreParenImpCasts());
122 if (LeftDRE && LeftDRE->getDecl() == CondVar)
123 OtherSide = BinOpCond->getRHS();
124 else
125 OtherSide = BinOpCond->getLHS();
126 }
127
128 SourceLocation IfEnd = Body->getBeginLoc().getLocWithOffset(-1);
129
130 // For compound statements also remove the left brace.
131 if (isa<CompoundStmt>(Body))
132 IfEnd = Body->getBeginLoc();
133
134 // If the other side has side effects then keep it.
135 if (OtherSide && OtherSide->HasSideEffects(*Result.Context)) {
136 const SourceLocation BeforeOtherSide =
137 OtherSide->getBeginLoc().getLocWithOffset(-1);
138 const SourceLocation AfterOtherSide =
140 OtherSide->getEndLoc(), *Result.SourceManager, getLangOpts())
141 ->getLocation();
142 Diag << FixItHint::CreateRemoval(
143 CharSourceRange::getTokenRange(IfBegin, BeforeOtherSide))
144 << FixItHint::CreateInsertion(AfterOtherSide, ";")
145 << FixItHint::CreateRemoval(
146 CharSourceRange::getTokenRange(AfterOtherSide, IfEnd));
147 } else {
148 Diag << FixItHint::CreateRemoval(
149 CharSourceRange::getTokenRange(IfBegin, IfEnd));
150 }
151
152 // For compound statements also remove the right brace at the end.
153 if (isa<CompoundStmt>(Body))
154 Diag << FixItHint::CreateRemoval(
155 CharSourceRange::getTokenRange(Body->getEndLoc(), Body->getEndLoc()));
156
157 // For "and" binary operations we remove the "and" operation with the
158 // condition variable from the inner if.
159 } else {
160 const auto *CondOp =
161 cast<BinaryOperator>(InnerIf->getCond()->IgnoreParenImpCasts());
162 const auto *LeftDRE =
163 dyn_cast<DeclRefExpr>(CondOp->getLHS()->IgnoreParenImpCasts());
164 if (LeftDRE && LeftDRE->getDecl() == CondVar) {
165 const SourceLocation BeforeRHS =
166 CondOp->getRHS()->getBeginLoc().getLocWithOffset(-1);
167 Diag << FixItHint::CreateRemoval(CharSourceRange::getTokenRange(
168 CondOp->getLHS()->getBeginLoc(), BeforeRHS));
169 } else {
170 const SourceLocation AfterLHS =
172 CondOp->getLHS()->getEndLoc(), *Result.SourceManager,
173 getLangOpts())
174 ->getLocation();
175 Diag << FixItHint::CreateRemoval(CharSourceRange::getTokenRange(
176 AfterLHS, CondOp->getRHS()->getEndLoc()));
177 }
178 }
179}
180
181} // 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:85
void check(const ast_matchers::MatchFinder::MatchResult &Result) override
void registerMatchers(ast_matchers::MatchFinder *Finder) override
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:101
bool hasPtrOrReferenceInFunc(const Decl *Func, const ValueDecl *Var)
Returns whether Var has a pointer or reference in Func.
Definition Aliasing.cpp:85