[clang-tidy] false-positive for bugprone-redundant-branch-condition in case of passed...
[lldb.git] / clang-tools-extra / clang-tidy / bugprone / RedundantBranchConditionCheck.cpp
1 //===--- RedundantBranchConditionCheck.cpp - clang-tidy -------------------------===//
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
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"
15
16 using namespace clang::ast_matchers;
17 using clang::tidy::utils::hasPtrOrReferenceInFunc;
18
19 namespace clang {
20 namespace tidy {
21 namespace bugprone {
22
23 static const char CondVarStr[] = "cond_var";
24 static const char OuterIfStr[] = "outer_if";
25 static const char InnerIfStr[] = "inner_if";
26 static const char OuterIfVar1Str[] = "outer_if_var1";
27 static const char OuterIfVar2Str[] = "outer_if_var2";
28 static const char InnerIfVar1Str[] = "inner_if_var1";
29 static const char InnerIfVar2Str[] = "inner_if_var2";
30 static const char FuncStr[] = "func";
31
32 /// Returns whether `Var` is changed in range (`PrevS`..`NextS`).
33 static bool isChangedBefore(const Stmt *S, const Stmt *NextS, const Stmt *PrevS,
34                             const VarDecl *Var, ASTContext *Context) {
35   ExprMutationAnalyzer MutAn(*S, *Context);
36   const auto &SM = Context->getSourceManager();
37   const Stmt *MutS = MutAn.findMutation(Var);
38   return MutS &&
39          SM.isBeforeInTranslationUnit(PrevS->getEndLoc(),
40                                       MutS->getBeginLoc()) &&
41          SM.isBeforeInTranslationUnit(MutS->getEndLoc(), NextS->getBeginLoc());
42 }
43
44 void RedundantBranchConditionCheck::registerMatchers(MatchFinder *Finder) {
45   const auto ImmutableVar =
46       varDecl(anyOf(parmVarDecl(), hasLocalStorage()), hasType(isInteger()),
47               unless(hasType(isVolatileQualified())))
48           .bind(CondVarStr);
49   Finder->addMatcher(
50       ifStmt(
51           hasCondition(ignoringParenImpCasts(anyOf(
52               declRefExpr(hasDeclaration(ImmutableVar)).bind(OuterIfVar1Str),
53               binaryOperator(hasOperatorName("&&"),
54                              hasEitherOperand(ignoringParenImpCasts(
55                                  declRefExpr(hasDeclaration(ImmutableVar))
56                                      .bind(OuterIfVar2Str))))))),
57           hasThen(hasDescendant(
58               ifStmt(hasCondition(ignoringParenImpCasts(
59                          anyOf(declRefExpr(hasDeclaration(varDecl(
60                                             equalsBoundNode(CondVarStr))))
61                                 .bind(InnerIfVar1Str),
62                                binaryOperator(
63                                    hasAnyOperatorName("&&", "||"),
64                                    hasEitherOperand(ignoringParenImpCasts(
65                                        declRefExpr(hasDeclaration(varDecl(
66                                                  equalsBoundNode(CondVarStr))))
67                                      .bind(InnerIfVar2Str))))))))
68                   .bind(InnerIfStr))),
69           forFunction(functionDecl().bind(FuncStr)))
70           .bind(OuterIfStr),
71       this);
72   // FIXME: Handle longer conjunctive and disjunctive clauses.
73 }
74
75 void RedundantBranchConditionCheck::check(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, *InnerIfVar;
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     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       SourceLocation BeforeOtherSide =
137           OtherSide->getBeginLoc().getLocWithOffset(-1);
138       SourceLocation AfterOtherSide =
139           Lexer::findNextToken(OtherSide->getEndLoc(), *Result.SourceManager,
140                                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       SourceLocation BeforeRHS =
166           CondOp->getRHS()->getBeginLoc().getLocWithOffset(-1);
167       Diag << FixItHint::CreateRemoval(CharSourceRange::getTokenRange(
168           CondOp->getLHS()->getBeginLoc(), BeforeRHS));
169     } else {
170       SourceLocation AfterLHS =
171           Lexer::findNextToken(CondOp->getLHS()->getEndLoc(),
172                                *Result.SourceManager, getLangOpts())
173               ->getLocation();
174       Diag << FixItHint::CreateRemoval(CharSourceRange::getTokenRange(
175           AfterLHS, CondOp->getRHS()->getEndLoc()));
176     }
177   }
178 }
179
180 } // namespace bugprone
181 } // namespace tidy
182 } // namespace clang