1 //===-- SimplifyBooleanExprCheck.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 "SimplifyBooleanExprCheck.h"
10 #include "clang/AST/RecursiveASTVisitor.h"
11 #include "clang/Lex/Lexer.h"
12 
13 #include <string>
14 #include <utility>
15 
16 using namespace clang::ast_matchers;
17 
18 namespace clang {
19 namespace tidy {
20 namespace readability {
21 
22 namespace {
23 
24 StringRef getText(const ASTContext &Context, SourceRange Range) {
25   return Lexer::getSourceText(CharSourceRange::getTokenRange(Range),
26                               Context.getSourceManager(),
27                               Context.getLangOpts());
28 }
29 
30 template <typename T> StringRef getText(const ASTContext &Context, T &Node) {
31   return getText(Context, Node.getSourceRange());
32 }
33 
34 } // namespace
35 
36 static constexpr char SimplifyOperatorDiagnostic[] =
37     "redundant boolean literal supplied to boolean operator";
38 static constexpr char SimplifyConditionDiagnostic[] =
39     "redundant boolean literal in if statement condition";
40 static constexpr char SimplifyConditionalReturnDiagnostic[] =
41     "redundant boolean literal in conditional return statement";
42 
43 static bool needsParensAfterUnaryNegation(const Expr *E) {
44   E = E->IgnoreImpCasts();
45   if (isa<BinaryOperator>(E) || isa<ConditionalOperator>(E))
46     return true;
47 
48   if (const auto *Op = dyn_cast<CXXOperatorCallExpr>(E))
49     return Op->getNumArgs() == 2 && Op->getOperator() != OO_Call &&
50            Op->getOperator() != OO_Subscript;
51 
52   return false;
53 }
54 
55 static std::pair<BinaryOperatorKind, BinaryOperatorKind> Opposites[] = {
56     {BO_LT, BO_GE}, {BO_GT, BO_LE}, {BO_EQ, BO_NE}};
57 
58 static StringRef negatedOperator(const BinaryOperator *BinOp) {
59   const BinaryOperatorKind Opcode = BinOp->getOpcode();
60   for (auto NegatableOp : Opposites) {
61     if (Opcode == NegatableOp.first)
62       return BinOp->getOpcodeStr(NegatableOp.second);
63     if (Opcode == NegatableOp.second)
64       return BinOp->getOpcodeStr(NegatableOp.first);
65   }
66   return {};
67 }
68 
69 static std::pair<OverloadedOperatorKind, StringRef> OperatorNames[] = {
70     {OO_EqualEqual, "=="},   {OO_ExclaimEqual, "!="}, {OO_Less, "<"},
71     {OO_GreaterEqual, ">="}, {OO_Greater, ">"},       {OO_LessEqual, "<="}};
72 
73 static StringRef getOperatorName(OverloadedOperatorKind OpKind) {
74   for (auto Name : OperatorNames) {
75     if (Name.first == OpKind)
76       return Name.second;
77   }
78 
79   return {};
80 }
81 
82 static std::pair<OverloadedOperatorKind, OverloadedOperatorKind>
83     OppositeOverloads[] = {{OO_EqualEqual, OO_ExclaimEqual},
84                            {OO_Less, OO_GreaterEqual},
85                            {OO_Greater, OO_LessEqual}};
86 
87 static StringRef negatedOperator(const CXXOperatorCallExpr *OpCall) {
88   const OverloadedOperatorKind Opcode = OpCall->getOperator();
89   for (auto NegatableOp : OppositeOverloads) {
90     if (Opcode == NegatableOp.first)
91       return getOperatorName(NegatableOp.second);
92     if (Opcode == NegatableOp.second)
93       return getOperatorName(NegatableOp.first);
94   }
95   return {};
96 }
97 
98 static std::string asBool(StringRef Text, bool NeedsStaticCast) {
99   if (NeedsStaticCast)
100     return ("static_cast<bool>(" + Text + ")").str();
101 
102   return std::string(Text);
103 }
104 
105 static bool needsNullPtrComparison(const Expr *E) {
106   if (const auto *ImpCast = dyn_cast<ImplicitCastExpr>(E))
107     return ImpCast->getCastKind() == CK_PointerToBoolean ||
108            ImpCast->getCastKind() == CK_MemberPointerToBoolean;
109 
110   return false;
111 }
112 
113 static bool needsZeroComparison(const Expr *E) {
114   if (const auto *ImpCast = dyn_cast<ImplicitCastExpr>(E))
115     return ImpCast->getCastKind() == CK_IntegralToBoolean;
116 
117   return false;
118 }
119 
120 static bool needsStaticCast(const Expr *E) {
121   if (const auto *ImpCast = dyn_cast<ImplicitCastExpr>(E)) {
122     if (ImpCast->getCastKind() == CK_UserDefinedConversion &&
123         ImpCast->getSubExpr()->getType()->isBooleanType()) {
124       if (const auto *MemCall =
125               dyn_cast<CXXMemberCallExpr>(ImpCast->getSubExpr())) {
126         if (const auto *MemDecl =
127                 dyn_cast<CXXConversionDecl>(MemCall->getMethodDecl())) {
128           if (MemDecl->isExplicit())
129             return true;
130         }
131       }
132     }
133   }
134 
135   E = E->IgnoreImpCasts();
136   return !E->getType()->isBooleanType();
137 }
138 
139 static std::string compareExpressionToConstant(const ASTContext &Context,
140                                                const Expr *E, bool Negated,
141                                                const char *Constant) {
142   E = E->IgnoreImpCasts();
143   const std::string ExprText =
144       (isa<BinaryOperator>(E) ? ("(" + getText(Context, *E) + ")")
145                               : getText(Context, *E))
146           .str();
147   return ExprText + " " + (Negated ? "!=" : "==") + " " + Constant;
148 }
149 
150 static std::string compareExpressionToNullPtr(const ASTContext &Context,
151                                               const Expr *E, bool Negated) {
152   const char *NullPtr = Context.getLangOpts().CPlusPlus11 ? "nullptr" : "NULL";
153   return compareExpressionToConstant(Context, E, Negated, NullPtr);
154 }
155 
156 static std::string compareExpressionToZero(const ASTContext &Context,
157                                            const Expr *E, bool Negated) {
158   return compareExpressionToConstant(Context, E, Negated, "0");
159 }
160 
161 static std::string replacementExpression(const ASTContext &Context,
162                                          bool Negated, const Expr *E) {
163   E = E->IgnoreParenBaseCasts();
164   if (const auto *EC = dyn_cast<ExprWithCleanups>(E))
165     E = EC->getSubExpr();
166 
167   const bool NeedsStaticCast = needsStaticCast(E);
168   if (Negated) {
169     if (const auto *UnOp = dyn_cast<UnaryOperator>(E)) {
170       if (UnOp->getOpcode() == UO_LNot) {
171         if (needsNullPtrComparison(UnOp->getSubExpr()))
172           return compareExpressionToNullPtr(Context, UnOp->getSubExpr(), true);
173 
174         if (needsZeroComparison(UnOp->getSubExpr()))
175           return compareExpressionToZero(Context, UnOp->getSubExpr(), true);
176 
177         return replacementExpression(Context, false, UnOp->getSubExpr());
178       }
179     }
180 
181     if (needsNullPtrComparison(E))
182       return compareExpressionToNullPtr(Context, E, false);
183 
184     if (needsZeroComparison(E))
185       return compareExpressionToZero(Context, E, false);
186 
187     StringRef NegatedOperator;
188     const Expr *LHS = nullptr;
189     const Expr *RHS = nullptr;
190     if (const auto *BinOp = dyn_cast<BinaryOperator>(E)) {
191       NegatedOperator = negatedOperator(BinOp);
192       LHS = BinOp->getLHS();
193       RHS = BinOp->getRHS();
194     } else if (const auto *OpExpr = dyn_cast<CXXOperatorCallExpr>(E)) {
195       if (OpExpr->getNumArgs() == 2) {
196         NegatedOperator = negatedOperator(OpExpr);
197         LHS = OpExpr->getArg(0);
198         RHS = OpExpr->getArg(1);
199       }
200     }
201     if (!NegatedOperator.empty() && LHS && RHS)
202       return (asBool((getText(Context, *LHS) + " " + NegatedOperator + " " +
203                       getText(Context, *RHS))
204                          .str(),
205                      NeedsStaticCast));
206 
207     StringRef Text = getText(Context, *E);
208     if (!NeedsStaticCast && needsParensAfterUnaryNegation(E))
209       return ("!(" + Text + ")").str();
210 
211     if (needsNullPtrComparison(E))
212       return compareExpressionToNullPtr(Context, E, false);
213 
214     if (needsZeroComparison(E))
215       return compareExpressionToZero(Context, E, false);
216 
217     return ("!" + asBool(Text, NeedsStaticCast));
218   }
219 
220   if (const auto *UnOp = dyn_cast<UnaryOperator>(E)) {
221     if (UnOp->getOpcode() == UO_LNot) {
222       if (needsNullPtrComparison(UnOp->getSubExpr()))
223         return compareExpressionToNullPtr(Context, UnOp->getSubExpr(), false);
224 
225       if (needsZeroComparison(UnOp->getSubExpr()))
226         return compareExpressionToZero(Context, UnOp->getSubExpr(), false);
227     }
228   }
229 
230   if (needsNullPtrComparison(E))
231     return compareExpressionToNullPtr(Context, E, true);
232 
233   if (needsZeroComparison(E))
234     return compareExpressionToZero(Context, E, true);
235 
236   return asBool(getText(Context, *E), NeedsStaticCast);
237 }
238 
239 static const Expr *stmtReturnsBool(const ReturnStmt *Ret, bool Negated) {
240   if (const auto *Bool = dyn_cast<CXXBoolLiteralExpr>(Ret->getRetValue())) {
241     if (Bool->getValue() == !Negated)
242       return Bool;
243   }
244   if (const auto *Unary = dyn_cast<UnaryOperator>(Ret->getRetValue())) {
245     if (Unary->getOpcode() == UO_LNot) {
246       if (const auto *Bool =
247               dyn_cast<CXXBoolLiteralExpr>(Unary->getSubExpr())) {
248         if (Bool->getValue() == Negated)
249           return Bool;
250       }
251     }
252   }
253 
254   return nullptr;
255 }
256 
257 static const Expr *stmtReturnsBool(const IfStmt *IfRet, bool Negated) {
258   if (IfRet->getElse() != nullptr)
259     return nullptr;
260 
261   if (const auto *Ret = dyn_cast<ReturnStmt>(IfRet->getThen()))
262     return stmtReturnsBool(Ret, Negated);
263 
264   if (const auto *Compound = dyn_cast<CompoundStmt>(IfRet->getThen())) {
265     if (Compound->size() == 1) {
266       if (const auto *CompoundRet = dyn_cast<ReturnStmt>(Compound->body_back()))
267         return stmtReturnsBool(CompoundRet, Negated);
268     }
269   }
270 
271   return nullptr;
272 }
273 
274 static bool containsDiscardedTokens(const ASTContext &Context,
275                                     CharSourceRange CharRange) {
276   std::string ReplacementText =
277       Lexer::getSourceText(CharRange, Context.getSourceManager(),
278                            Context.getLangOpts())
279           .str();
280   Lexer Lex(CharRange.getBegin(), Context.getLangOpts(), ReplacementText.data(),
281             ReplacementText.data(),
282             ReplacementText.data() + ReplacementText.size());
283   Lex.SetCommentRetentionState(true);
284 
285   Token Tok;
286   while (!Lex.LexFromRawLexer(Tok)) {
287     if (Tok.is(tok::TokenKind::comment) || Tok.is(tok::TokenKind::hash))
288       return true;
289   }
290 
291   return false;
292 }
293 
294 class SimplifyBooleanExprCheck::Visitor : public RecursiveASTVisitor<Visitor> {
295 public:
296   Visitor(SimplifyBooleanExprCheck *Check, ASTContext &Context)
297       : Check(Check), Context(Context) {}
298 
299   bool traverse() { return TraverseAST(Context); }
300 
301   static bool shouldIgnore(Stmt *S) {
302     switch (S->getStmtClass()) {
303     case Stmt::ImplicitCastExprClass:
304     case Stmt::MaterializeTemporaryExprClass:
305     case Stmt::CXXBindTemporaryExprClass:
306       return true;
307     default:
308       return false;
309     }
310   }
311 
312   bool dataTraverseStmtPre(Stmt *S) {
313     if (S && !shouldIgnore(S))
314       StmtStack.push_back(S);
315     return true;
316   }
317 
318   bool dataTraverseStmtPost(Stmt *S) {
319     if (S && !shouldIgnore(S)) {
320       assert(StmtStack.back() == S);
321       StmtStack.pop_back();
322     }
323     return true;
324   }
325 
326   bool VisitBinaryOperator(const BinaryOperator *Op) const {
327     Check->reportBinOp(Context, Op);
328     return true;
329   }
330 
331   // Extracts a bool if an expression is (true|false|!true|!false);
332   static Optional<bool> getAsBoolLiteral(const Expr *E, bool FilterMacro) {
333     if (const auto *Bool = dyn_cast<CXXBoolLiteralExpr>(E)) {
334       if (FilterMacro && Bool->getBeginLoc().isMacroID())
335         return llvm::None;
336       return Bool->getValue();
337     }
338     if (const auto *UnaryOp = dyn_cast<UnaryOperator>(E)) {
339       if (FilterMacro && UnaryOp->getBeginLoc().isMacroID())
340         return None;
341       if (UnaryOp->getOpcode() == UO_LNot)
342         if (Optional<bool> Res = getAsBoolLiteral(
343                 UnaryOp->getSubExpr()->IgnoreImplicit(), FilterMacro))
344           return !*Res;
345     }
346     return llvm::None;
347   }
348 
349   template <typename Node> struct NodeAndBool {
350     const Node *Item = nullptr;
351     bool Bool = false;
352 
353     operator bool() const { return Item != nullptr; }
354   };
355 
356   using ExprAndBool = NodeAndBool<Expr>;
357   using DeclAndBool = NodeAndBool<Decl>;
358 
359   /// Detect's return (true|false|!true|!false);
360   static ExprAndBool parseReturnLiteralBool(const Stmt *S) {
361     const auto *RS = dyn_cast<ReturnStmt>(S);
362     if (!RS || !RS->getRetValue())
363       return {};
364     if (Optional<bool> Ret =
365             getAsBoolLiteral(RS->getRetValue()->IgnoreImplicit(), false)) {
366       return {RS->getRetValue(), *Ret};
367     }
368     return {};
369   }
370 
371   /// If \p S is not a \c CompoundStmt, applies F on \p S, otherwise if there is
372   /// only 1 statement in the \c CompoundStmt, applies F on that single
373   /// statement.
374   template <typename Functor>
375   static auto checkSingleStatement(Stmt *S, Functor F) -> decltype(F(S)) {
376     if (auto *CS = dyn_cast<CompoundStmt>(S)) {
377       if (CS->size() == 1)
378         return F(CS->body_front());
379       return {};
380     }
381     return F(S);
382   }
383 
384   Stmt *parent() const {
385     return StmtStack.size() < 2 ? nullptr : StmtStack[StmtStack.size() - 2];
386   }
387 
388   bool VisitIfStmt(IfStmt *If) {
389     /*
390      * if (true) ThenStmt(); -> ThenStmt();
391      * if (false) ThenStmt(); -> <Empty>;
392      * if (false) ThenStmt(); else ElseStmt() -> ElseStmt();
393      */
394     Expr *Cond = If->getCond()->IgnoreImplicit();
395     if (Optional<bool> Bool = getAsBoolLiteral(Cond, true)) {
396       if (*Bool)
397         Check->replaceWithThenStatement(Context, If, Cond);
398       else
399         Check->replaceWithElseStatement(Context, If, Cond);
400     }
401 
402     if (If->getElse()) {
403       /*
404        * if (Cond) return true; else return false; -> return Cond;
405        * if (Cond) return false; else return true; -> return !Cond;
406        */
407       if (ExprAndBool ThenReturnBool =
408               checkSingleStatement(If->getThen(), parseReturnLiteralBool)) {
409         ExprAndBool ElseReturnBool =
410             checkSingleStatement(If->getElse(), parseReturnLiteralBool);
411         if (ElseReturnBool && ThenReturnBool.Bool != ElseReturnBool.Bool) {
412           if (Check->ChainedConditionalReturn ||
413               !isa_and_nonnull<IfStmt>(parent())) {
414             Check->replaceWithReturnCondition(Context, If, ThenReturnBool.Item,
415                                               ElseReturnBool.Bool);
416           }
417         }
418       } else {
419         /*
420          * if (Cond) A = true; else A = false; -> A = Cond;
421          * if (Cond) A = false; else A = true; -> A = !Cond;
422          */
423         Expr *Var = nullptr;
424         SourceLocation Loc;
425         auto VarBoolAssignmentMatcher = [&Var,
426                                          &Loc](const Stmt *S) -> DeclAndBool {
427           const auto *BO = dyn_cast<BinaryOperator>(S);
428           if (!BO || BO->getOpcode() != BO_Assign)
429             return {};
430           Optional<bool> RightasBool =
431               getAsBoolLiteral(BO->getRHS()->IgnoreImplicit(), false);
432           if (!RightasBool)
433             return {};
434           Expr *IgnImp = BO->getLHS()->IgnoreImplicit();
435           if (!Var) {
436             // We only need to track these for the Then branch.
437             Loc = BO->getRHS()->getBeginLoc();
438             Var = IgnImp;
439           }
440           if (auto *DRE = dyn_cast<DeclRefExpr>(IgnImp))
441             return {DRE->getDecl(), *RightasBool};
442           if (auto *ME = dyn_cast<MemberExpr>(IgnImp))
443             return {ME->getMemberDecl(), *RightasBool};
444           return {};
445         };
446         if (DeclAndBool ThenAssignment =
447                 checkSingleStatement(If->getThen(), VarBoolAssignmentMatcher)) {
448           DeclAndBool ElseAssignment =
449               checkSingleStatement(If->getElse(), VarBoolAssignmentMatcher);
450           if (ElseAssignment.Item == ThenAssignment.Item &&
451               ElseAssignment.Bool != ThenAssignment.Bool) {
452             if (Check->ChainedConditionalAssignment ||
453                 !isa_and_nonnull<IfStmt>(parent())) {
454               Check->replaceWithAssignment(Context, If, Var, Loc,
455                                            ElseAssignment.Bool);
456             }
457           }
458         }
459       }
460     }
461     return true;
462   }
463 
464   bool VisitConditionalOperator(ConditionalOperator *Cond) {
465     /*
466      * Condition ? true : false; -> Condition
467      * Condition ? false : true; -> !Condition;
468      */
469     if (Optional<bool> Then =
470             getAsBoolLiteral(Cond->getTrueExpr()->IgnoreImplicit(), false)) {
471       if (Optional<bool> Else =
472               getAsBoolLiteral(Cond->getFalseExpr()->IgnoreImplicit(), false)) {
473         if (*Then != *Else)
474           Check->replaceWithCondition(Context, Cond, *Else);
475       }
476     }
477     return true;
478   }
479 
480   bool VisitCompoundStmt(CompoundStmt *CS) {
481     if (CS->size() < 2)
482       return true;
483     bool CurIf = false, PrevIf = false;
484     for (auto First = CS->body_begin(), Second = std::next(First),
485               End = CS->body_end();
486          Second != End; ++Second, ++First) {
487       PrevIf = CurIf;
488       CurIf = isa<IfStmt>(*First);
489       ExprAndBool TrailingReturnBool = parseReturnLiteralBool(*Second);
490       if (!TrailingReturnBool)
491         continue;
492 
493       if (CurIf) {
494         /*
495          * if (Cond) return true; return false; -> return Cond;
496          * if (Cond) return false; return true; -> return !Cond;
497          */
498         auto *If = cast<IfStmt>(*First);
499         ExprAndBool ThenReturnBool =
500             checkSingleStatement(If->getThen(), parseReturnLiteralBool);
501         if (ThenReturnBool && ThenReturnBool.Bool != TrailingReturnBool.Bool) {
502           if (Check->ChainedConditionalReturn ||
503               (!PrevIf && If->getElse() == nullptr)) {
504             Check->replaceCompoundReturnWithCondition(
505                 Context, cast<ReturnStmt>(*Second), TrailingReturnBool.Bool,
506                 If);
507           }
508         }
509       } else if (isa<LabelStmt, CaseStmt, DefaultStmt>(*First)) {
510         /*
511          * (case X|label_X|default): if (Cond) return BoolLiteral;
512          *                           return !BoolLiteral
513          */
514         Stmt *SubStmt =
515             isa<LabelStmt>(*First)  ? cast<LabelStmt>(*First)->getSubStmt()
516             : isa<CaseStmt>(*First) ? cast<CaseStmt>(*First)->getSubStmt()
517                                     : cast<DefaultStmt>(*First)->getSubStmt();
518         auto *SubIf = dyn_cast<IfStmt>(SubStmt);
519         if (SubIf && !SubIf->getElse()) {
520           ExprAndBool ThenReturnBool =
521               checkSingleStatement(SubIf->getThen(), parseReturnLiteralBool);
522           if (ThenReturnBool &&
523               ThenReturnBool.Bool != TrailingReturnBool.Bool) {
524             Check->replaceCompoundReturnWithCondition(
525                 Context, cast<ReturnStmt>(*Second), TrailingReturnBool.Bool,
526                 SubIf);
527           }
528         }
529       }
530     }
531     return true;
532   }
533 
534 private:
535   SimplifyBooleanExprCheck *Check;
536   SmallVector<Stmt *, 32> StmtStack;
537   ASTContext &Context;
538 };
539 
540 SimplifyBooleanExprCheck::SimplifyBooleanExprCheck(StringRef Name,
541                                                    ClangTidyContext *Context)
542     : ClangTidyCheck(Name, Context),
543       ChainedConditionalReturn(Options.get("ChainedConditionalReturn", false)),
544       ChainedConditionalAssignment(
545           Options.get("ChainedConditionalAssignment", false)) {}
546 
547 static bool containsBoolLiteral(const Expr *E) {
548   if (!E)
549     return false;
550   E = E->IgnoreParenImpCasts();
551   if (isa<CXXBoolLiteralExpr>(E))
552     return true;
553   if (const auto *BinOp = dyn_cast<BinaryOperator>(E))
554     return containsBoolLiteral(BinOp->getLHS()) ||
555            containsBoolLiteral(BinOp->getRHS());
556   if (const auto *UnaryOp = dyn_cast<UnaryOperator>(E))
557     return containsBoolLiteral(UnaryOp->getSubExpr());
558   return false;
559 }
560 
561 void SimplifyBooleanExprCheck::reportBinOp(const ASTContext &Context,
562                                            const BinaryOperator *Op) {
563   const auto *LHS = Op->getLHS()->IgnoreParenImpCasts();
564   const auto *RHS = Op->getRHS()->IgnoreParenImpCasts();
565 
566   const CXXBoolLiteralExpr *Bool;
567   const Expr *Other;
568   if ((Bool = dyn_cast<CXXBoolLiteralExpr>(LHS)) != nullptr)
569     Other = RHS;
570   else if ((Bool = dyn_cast<CXXBoolLiteralExpr>(RHS)) != nullptr)
571     Other = LHS;
572   else
573     return;
574 
575   if (Bool->getBeginLoc().isMacroID())
576     return;
577 
578   // FIXME: why do we need this?
579   if (!isa<CXXBoolLiteralExpr>(Other) && containsBoolLiteral(Other))
580     return;
581 
582   bool BoolValue = Bool->getValue();
583 
584   auto ReplaceWithExpression = [this, &Context, LHS, RHS,
585                                 Bool](const Expr *ReplaceWith, bool Negated) {
586     std::string Replacement =
587         replacementExpression(Context, Negated, ReplaceWith);
588     SourceRange Range(LHS->getBeginLoc(), RHS->getEndLoc());
589     issueDiag(Context, Bool->getBeginLoc(), SimplifyOperatorDiagnostic, Range,
590               Replacement);
591   };
592 
593   switch (Op->getOpcode()) {
594   case BO_LAnd:
595     if (BoolValue)
596       // expr && true -> expr
597       ReplaceWithExpression(Other, /*Negated=*/false);
598     else
599       // expr && false -> false
600       ReplaceWithExpression(Bool, /*Negated=*/false);
601     break;
602   case BO_LOr:
603     if (BoolValue)
604       // expr || true -> true
605       ReplaceWithExpression(Bool, /*Negated=*/false);
606     else
607       // expr || false -> expr
608       ReplaceWithExpression(Other, /*Negated=*/false);
609     break;
610   case BO_EQ:
611     // expr == true -> expr, expr == false -> !expr
612     ReplaceWithExpression(Other, /*Negated=*/!BoolValue);
613     break;
614   case BO_NE:
615     // expr != true -> !expr, expr != false -> expr
616     ReplaceWithExpression(Other, /*Negated=*/BoolValue);
617     break;
618   default:
619     break;
620   }
621 }
622 
623 void SimplifyBooleanExprCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
624   Options.store(Opts, "ChainedConditionalReturn", ChainedConditionalReturn);
625   Options.store(Opts, "ChainedConditionalAssignment",
626                 ChainedConditionalAssignment);
627 }
628 
629 void SimplifyBooleanExprCheck::registerMatchers(MatchFinder *Finder) {
630   Finder->addMatcher(translationUnitDecl(), this);
631 }
632 
633 void SimplifyBooleanExprCheck::check(const MatchFinder::MatchResult &Result) {
634   Visitor(this, *Result.Context).traverse();
635 }
636 
637 void SimplifyBooleanExprCheck::issueDiag(const ASTContext &Context,
638                                          SourceLocation Loc,
639                                          StringRef Description,
640                                          SourceRange ReplacementRange,
641                                          StringRef Replacement) {
642   CharSourceRange CharRange =
643       Lexer::makeFileCharRange(CharSourceRange::getTokenRange(ReplacementRange),
644                                Context.getSourceManager(), getLangOpts());
645 
646   DiagnosticBuilder Diag = diag(Loc, Description);
647   if (!containsDiscardedTokens(Context, CharRange))
648     Diag << FixItHint::CreateReplacement(CharRange, Replacement);
649 }
650 
651 void SimplifyBooleanExprCheck::replaceWithThenStatement(
652     const ASTContext &Context, const IfStmt *IfStatement,
653     const Expr *BoolLiteral) {
654   issueDiag(Context, BoolLiteral->getBeginLoc(), SimplifyConditionDiagnostic,
655             IfStatement->getSourceRange(),
656             getText(Context, *IfStatement->getThen()));
657 }
658 
659 void SimplifyBooleanExprCheck::replaceWithElseStatement(
660     const ASTContext &Context, const IfStmt *IfStatement,
661     const Expr *BoolLiteral) {
662   const Stmt *ElseStatement = IfStatement->getElse();
663   issueDiag(Context, BoolLiteral->getBeginLoc(), SimplifyConditionDiagnostic,
664             IfStatement->getSourceRange(),
665             ElseStatement ? getText(Context, *ElseStatement) : "");
666 }
667 
668 void SimplifyBooleanExprCheck::replaceWithCondition(
669     const ASTContext &Context, const ConditionalOperator *Ternary,
670     bool Negated) {
671   std::string Replacement =
672       replacementExpression(Context, Negated, Ternary->getCond());
673   issueDiag(Context, Ternary->getTrueExpr()->getBeginLoc(),
674             "redundant boolean literal in ternary expression result",
675             Ternary->getSourceRange(), Replacement);
676 }
677 
678 void SimplifyBooleanExprCheck::replaceWithReturnCondition(
679     const ASTContext &Context, const IfStmt *If, const Expr *BoolLiteral,
680     bool Negated) {
681   StringRef Terminator = isa<CompoundStmt>(If->getElse()) ? ";" : "";
682   std::string Condition =
683       replacementExpression(Context, Negated, If->getCond());
684   std::string Replacement = ("return " + Condition + Terminator).str();
685   SourceLocation Start = BoolLiteral->getBeginLoc();
686   issueDiag(Context, Start, SimplifyConditionalReturnDiagnostic,
687             If->getSourceRange(), Replacement);
688 }
689 
690 void SimplifyBooleanExprCheck::replaceCompoundReturnWithCondition(
691     const ASTContext &Context, const ReturnStmt *Ret, bool Negated,
692     const IfStmt *If) {
693   const auto *Lit = stmtReturnsBool(If, Negated);
694   const std::string Replacement =
695       "return " + replacementExpression(Context, Negated, If->getCond());
696   issueDiag(Context, Lit->getBeginLoc(), SimplifyConditionalReturnDiagnostic,
697             SourceRange(If->getBeginLoc(), Ret->getEndLoc()), Replacement);
698 }
699 
700 void SimplifyBooleanExprCheck::replaceWithAssignment(const ASTContext &Context,
701                                                      const IfStmt *IfAssign,
702                                                      const Expr *Var,
703                                                      SourceLocation Loc,
704                                                      bool Negated) {
705   SourceRange Range = IfAssign->getSourceRange();
706   StringRef VariableName = getText(Context, *Var);
707   StringRef Terminator = isa<CompoundStmt>(IfAssign->getElse()) ? ";" : "";
708   std::string Condition =
709       replacementExpression(Context, Negated, IfAssign->getCond());
710   std::string Replacement =
711       (VariableName + " = " + Condition + Terminator).str();
712   issueDiag(Context, Loc, "redundant boolean literal in conditional assignment",
713             Range, Replacement);
714 }
715 
716 } // namespace readability
717 } // namespace tidy
718 } // namespace clang
719