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 "SimplifyBooleanExprMatchers.h"
11 #include "clang/AST/RecursiveASTVisitor.h"
12 #include "clang/Lex/Lexer.h"
13 
14 #include <string>
15 #include <utility>
16 
17 using namespace clang::ast_matchers;
18 
19 namespace clang {
20 namespace tidy {
21 namespace readability {
22 
23 namespace {
24 
25 StringRef getText(const MatchFinder::MatchResult &Result, SourceRange Range) {
26   return Lexer::getSourceText(CharSourceRange::getTokenRange(Range),
27                               *Result.SourceManager,
28                               Result.Context->getLangOpts());
29 }
30 
31 template <typename T>
32 StringRef getText(const MatchFinder::MatchResult &Result, T &Node) {
33   return getText(Result, Node.getSourceRange());
34 }
35 
36 } // namespace
37 
38 static constexpr char ConditionThenStmtId[] = "if-bool-yields-then";
39 static constexpr char ConditionElseStmtId[] = "if-bool-yields-else";
40 static constexpr char TernaryId[] = "ternary-bool-yields-condition";
41 static constexpr char TernaryNegatedId[] = "ternary-bool-yields-not-condition";
42 static constexpr char IfReturnsBoolId[] = "if-return";
43 static constexpr char IfReturnsNotBoolId[] = "if-not-return";
44 static constexpr char ThenLiteralId[] = "then-literal";
45 static constexpr char IfAssignVariableId[] = "if-assign-lvalue";
46 static constexpr char IfAssignLocId[] = "if-assign-loc";
47 static constexpr char IfAssignBoolId[] = "if-assign";
48 static constexpr char IfAssignNotBoolId[] = "if-assign-not";
49 static constexpr char IfAssignVarId[] = "if-assign-var";
50 static constexpr char CompoundReturnId[] = "compound-return";
51 static constexpr char CompoundIfId[] = "compound-if";
52 static constexpr char CompoundBoolId[] = "compound-bool";
53 static constexpr char CompoundNotBoolId[] = "compound-bool-not";
54 static constexpr char CaseId[] = "case";
55 static constexpr char CaseCompoundBoolId[] = "case-compound-bool";
56 static constexpr char CaseCompoundNotBoolId[] = "case-compound-bool-not";
57 static constexpr char DefaultId[] = "default";
58 static constexpr char DefaultCompoundBoolId[] = "default-compound-bool";
59 static constexpr char DefaultCompoundNotBoolId[] = "default-compound-bool-not";
60 static constexpr char LabelId[] = "label";
61 static constexpr char LabelCompoundBoolId[] = "label-compound-bool";
62 static constexpr char LabelCompoundNotBoolId[] = "label-compound-bool-not";
63 static constexpr char IfStmtId[] = "if";
64 
65 static constexpr char SimplifyOperatorDiagnostic[] =
66     "redundant boolean literal supplied to boolean operator";
67 static constexpr char SimplifyConditionDiagnostic[] =
68     "redundant boolean literal in if statement condition";
69 static constexpr char SimplifyConditionalReturnDiagnostic[] =
70     "redundant boolean literal in conditional return statement";
71 
72 static const Expr *getBoolLiteral(const MatchFinder::MatchResult &Result,
73                                   StringRef Id) {
74   if (const Expr *Literal = Result.Nodes.getNodeAs<CXXBoolLiteralExpr>(Id))
75     return Literal->getBeginLoc().isMacroID() ? nullptr : Literal;
76   if (const auto *Negated = Result.Nodes.getNodeAs<UnaryOperator>(Id)) {
77     if (Negated->getOpcode() == UO_LNot &&
78         isa<CXXBoolLiteralExpr>(Negated->getSubExpr()))
79       return Negated->getBeginLoc().isMacroID() ? nullptr : Negated;
80   }
81   return nullptr;
82 }
83 
84 static internal::BindableMatcher<Stmt> literalOrNegatedBool(bool Value) {
85   return expr(
86       anyOf(cxxBoolLiteral(equals(Value)),
87             unaryOperator(hasUnaryOperand(cxxBoolLiteral(equals(!Value))),
88                           hasOperatorName("!"))));
89 }
90 
91 static internal::Matcher<Stmt> returnsBool(bool Value,
92                                            StringRef Id = "ignored") {
93   auto SimpleReturnsBool = returnStmt(has(literalOrNegatedBool(Value).bind(Id)))
94                                .bind("returns-bool");
95   return anyOf(SimpleReturnsBool,
96                compoundStmt(statementCountIs(1), has(SimpleReturnsBool)));
97 }
98 
99 static bool needsParensAfterUnaryNegation(const Expr *E) {
100   E = E->IgnoreImpCasts();
101   if (isa<BinaryOperator>(E) || isa<ConditionalOperator>(E))
102     return true;
103 
104   if (const auto *Op = dyn_cast<CXXOperatorCallExpr>(E))
105     return Op->getNumArgs() == 2 && Op->getOperator() != OO_Call &&
106            Op->getOperator() != OO_Subscript;
107 
108   return false;
109 }
110 
111 static std::pair<BinaryOperatorKind, BinaryOperatorKind> Opposites[] = {
112     {BO_LT, BO_GE}, {BO_GT, BO_LE}, {BO_EQ, BO_NE}};
113 
114 static StringRef negatedOperator(const BinaryOperator *BinOp) {
115   const BinaryOperatorKind Opcode = BinOp->getOpcode();
116   for (auto NegatableOp : Opposites) {
117     if (Opcode == NegatableOp.first)
118       return BinOp->getOpcodeStr(NegatableOp.second);
119     if (Opcode == NegatableOp.second)
120       return BinOp->getOpcodeStr(NegatableOp.first);
121   }
122   return {};
123 }
124 
125 static std::pair<OverloadedOperatorKind, StringRef> OperatorNames[] = {
126     {OO_EqualEqual, "=="},   {OO_ExclaimEqual, "!="}, {OO_Less, "<"},
127     {OO_GreaterEqual, ">="}, {OO_Greater, ">"},       {OO_LessEqual, "<="}};
128 
129 static StringRef getOperatorName(OverloadedOperatorKind OpKind) {
130   for (auto Name : OperatorNames) {
131     if (Name.first == OpKind)
132       return Name.second;
133   }
134 
135   return {};
136 }
137 
138 static std::pair<OverloadedOperatorKind, OverloadedOperatorKind>
139     OppositeOverloads[] = {{OO_EqualEqual, OO_ExclaimEqual},
140                            {OO_Less, OO_GreaterEqual},
141                            {OO_Greater, OO_LessEqual}};
142 
143 static StringRef negatedOperator(const CXXOperatorCallExpr *OpCall) {
144   const OverloadedOperatorKind Opcode = OpCall->getOperator();
145   for (auto NegatableOp : OppositeOverloads) {
146     if (Opcode == NegatableOp.first)
147       return getOperatorName(NegatableOp.second);
148     if (Opcode == NegatableOp.second)
149       return getOperatorName(NegatableOp.first);
150   }
151   return {};
152 }
153 
154 static std::string asBool(StringRef Text, bool NeedsStaticCast) {
155   if (NeedsStaticCast)
156     return ("static_cast<bool>(" + Text + ")").str();
157 
158   return std::string(Text);
159 }
160 
161 static bool needsNullPtrComparison(const Expr *E) {
162   if (const auto *ImpCast = dyn_cast<ImplicitCastExpr>(E))
163     return ImpCast->getCastKind() == CK_PointerToBoolean ||
164            ImpCast->getCastKind() == CK_MemberPointerToBoolean;
165 
166   return false;
167 }
168 
169 static bool needsZeroComparison(const Expr *E) {
170   if (const auto *ImpCast = dyn_cast<ImplicitCastExpr>(E))
171     return ImpCast->getCastKind() == CK_IntegralToBoolean;
172 
173   return false;
174 }
175 
176 static bool needsStaticCast(const Expr *E) {
177   if (const auto *ImpCast = dyn_cast<ImplicitCastExpr>(E)) {
178     if (ImpCast->getCastKind() == CK_UserDefinedConversion &&
179         ImpCast->getSubExpr()->getType()->isBooleanType()) {
180       if (const auto *MemCall =
181               dyn_cast<CXXMemberCallExpr>(ImpCast->getSubExpr())) {
182         if (const auto *MemDecl =
183                 dyn_cast<CXXConversionDecl>(MemCall->getMethodDecl())) {
184           if (MemDecl->isExplicit())
185             return true;
186         }
187       }
188     }
189   }
190 
191   E = E->IgnoreImpCasts();
192   return !E->getType()->isBooleanType();
193 }
194 
195 static std::string
196 compareExpressionToConstant(const MatchFinder::MatchResult &Result,
197                             const Expr *E, bool Negated, const char *Constant) {
198   E = E->IgnoreImpCasts();
199   const std::string ExprText =
200       (isa<BinaryOperator>(E) ? ("(" + getText(Result, *E) + ")")
201                               : getText(Result, *E))
202           .str();
203   return ExprText + " " + (Negated ? "!=" : "==") + " " + Constant;
204 }
205 
206 static std::string
207 compareExpressionToNullPtr(const MatchFinder::MatchResult &Result,
208                            const Expr *E, bool Negated) {
209   const char *NullPtr =
210       Result.Context->getLangOpts().CPlusPlus11 ? "nullptr" : "NULL";
211   return compareExpressionToConstant(Result, E, Negated, NullPtr);
212 }
213 
214 static std::string
215 compareExpressionToZero(const MatchFinder::MatchResult &Result, const Expr *E,
216                         bool Negated) {
217   return compareExpressionToConstant(Result, E, Negated, "0");
218 }
219 
220 static std::string replacementExpression(const MatchFinder::MatchResult &Result,
221                                          bool Negated, const Expr *E) {
222   E = E->IgnoreParenBaseCasts();
223   if (const auto *EC = dyn_cast<ExprWithCleanups>(E))
224     E = EC->getSubExpr();
225 
226   const bool NeedsStaticCast = needsStaticCast(E);
227   if (Negated) {
228     if (const auto *UnOp = dyn_cast<UnaryOperator>(E)) {
229       if (UnOp->getOpcode() == UO_LNot) {
230         if (needsNullPtrComparison(UnOp->getSubExpr()))
231           return compareExpressionToNullPtr(Result, UnOp->getSubExpr(), true);
232 
233         if (needsZeroComparison(UnOp->getSubExpr()))
234           return compareExpressionToZero(Result, UnOp->getSubExpr(), true);
235 
236         return replacementExpression(Result, false, UnOp->getSubExpr());
237       }
238     }
239 
240     if (needsNullPtrComparison(E))
241       return compareExpressionToNullPtr(Result, E, false);
242 
243     if (needsZeroComparison(E))
244       return compareExpressionToZero(Result, E, false);
245 
246     StringRef NegatedOperator;
247     const Expr *LHS = nullptr;
248     const Expr *RHS = nullptr;
249     if (const auto *BinOp = dyn_cast<BinaryOperator>(E)) {
250       NegatedOperator = negatedOperator(BinOp);
251       LHS = BinOp->getLHS();
252       RHS = BinOp->getRHS();
253     } else if (const auto *OpExpr = dyn_cast<CXXOperatorCallExpr>(E)) {
254       if (OpExpr->getNumArgs() == 2) {
255         NegatedOperator = negatedOperator(OpExpr);
256         LHS = OpExpr->getArg(0);
257         RHS = OpExpr->getArg(1);
258       }
259     }
260     if (!NegatedOperator.empty() && LHS && RHS)
261       return (asBool((getText(Result, *LHS) + " " + NegatedOperator + " " +
262                       getText(Result, *RHS))
263                          .str(),
264                      NeedsStaticCast));
265 
266     StringRef Text = getText(Result, *E);
267     if (!NeedsStaticCast && needsParensAfterUnaryNegation(E))
268       return ("!(" + Text + ")").str();
269 
270     if (needsNullPtrComparison(E))
271       return compareExpressionToNullPtr(Result, E, false);
272 
273     if (needsZeroComparison(E))
274       return compareExpressionToZero(Result, E, false);
275 
276     return ("!" + asBool(Text, NeedsStaticCast));
277   }
278 
279   if (const auto *UnOp = dyn_cast<UnaryOperator>(E)) {
280     if (UnOp->getOpcode() == UO_LNot) {
281       if (needsNullPtrComparison(UnOp->getSubExpr()))
282         return compareExpressionToNullPtr(Result, UnOp->getSubExpr(), false);
283 
284       if (needsZeroComparison(UnOp->getSubExpr()))
285         return compareExpressionToZero(Result, UnOp->getSubExpr(), false);
286     }
287   }
288 
289   if (needsNullPtrComparison(E))
290     return compareExpressionToNullPtr(Result, E, true);
291 
292   if (needsZeroComparison(E))
293     return compareExpressionToZero(Result, E, true);
294 
295   return asBool(getText(Result, *E), NeedsStaticCast);
296 }
297 
298 static const Expr *stmtReturnsBool(const ReturnStmt *Ret, bool Negated) {
299   if (const auto *Bool = dyn_cast<CXXBoolLiteralExpr>(Ret->getRetValue())) {
300     if (Bool->getValue() == !Negated)
301       return Bool;
302   }
303   if (const auto *Unary = dyn_cast<UnaryOperator>(Ret->getRetValue())) {
304     if (Unary->getOpcode() == UO_LNot) {
305       if (const auto *Bool =
306               dyn_cast<CXXBoolLiteralExpr>(Unary->getSubExpr())) {
307         if (Bool->getValue() == Negated)
308           return Bool;
309       }
310     }
311   }
312 
313   return nullptr;
314 }
315 
316 static const Expr *stmtReturnsBool(const IfStmt *IfRet, bool Negated) {
317   if (IfRet->getElse() != nullptr)
318     return nullptr;
319 
320   if (const auto *Ret = dyn_cast<ReturnStmt>(IfRet->getThen()))
321     return stmtReturnsBool(Ret, Negated);
322 
323   if (const auto *Compound = dyn_cast<CompoundStmt>(IfRet->getThen())) {
324     if (Compound->size() == 1) {
325       if (const auto *CompoundRet = dyn_cast<ReturnStmt>(Compound->body_back()))
326         return stmtReturnsBool(CompoundRet, Negated);
327     }
328   }
329 
330   return nullptr;
331 }
332 
333 static bool containsDiscardedTokens(const MatchFinder::MatchResult &Result,
334                                     CharSourceRange CharRange) {
335   std::string ReplacementText =
336       Lexer::getSourceText(CharRange, *Result.SourceManager,
337                            Result.Context->getLangOpts())
338           .str();
339   Lexer Lex(CharRange.getBegin(), Result.Context->getLangOpts(),
340             ReplacementText.data(), ReplacementText.data(),
341             ReplacementText.data() + ReplacementText.size());
342   Lex.SetCommentRetentionState(true);
343 
344   Token Tok;
345   while (!Lex.LexFromRawLexer(Tok)) {
346     if (Tok.is(tok::TokenKind::comment) || Tok.is(tok::TokenKind::hash))
347       return true;
348   }
349 
350   return false;
351 }
352 
353 class SimplifyBooleanExprCheck::Visitor : public RecursiveASTVisitor<Visitor> {
354 public:
355   Visitor(SimplifyBooleanExprCheck *Check,
356           const MatchFinder::MatchResult &Result)
357       : Check(Check), Result(Result) {}
358 
359   bool VisitBinaryOperator(const BinaryOperator *Op) const {
360     Check->reportBinOp(Result, Op);
361     return true;
362   }
363 
364 private:
365   SimplifyBooleanExprCheck *Check;
366   const MatchFinder::MatchResult &Result;
367 };
368 
369 SimplifyBooleanExprCheck::SimplifyBooleanExprCheck(StringRef Name,
370                                                    ClangTidyContext *Context)
371     : ClangTidyCheck(Name, Context),
372       ChainedConditionalReturn(Options.get("ChainedConditionalReturn", false)),
373       ChainedConditionalAssignment(
374           Options.get("ChainedConditionalAssignment", false)) {}
375 
376 static bool containsBoolLiteral(const Expr *E) {
377   if (!E)
378     return false;
379   E = E->IgnoreParenImpCasts();
380   if (isa<CXXBoolLiteralExpr>(E))
381     return true;
382   if (const auto *BinOp = dyn_cast<BinaryOperator>(E))
383     return containsBoolLiteral(BinOp->getLHS()) ||
384            containsBoolLiteral(BinOp->getRHS());
385   if (const auto *UnaryOp = dyn_cast<UnaryOperator>(E))
386     return containsBoolLiteral(UnaryOp->getSubExpr());
387   return false;
388 }
389 
390 void SimplifyBooleanExprCheck::reportBinOp(
391     const MatchFinder::MatchResult &Result, const BinaryOperator *Op) {
392   const auto *LHS = Op->getLHS()->IgnoreParenImpCasts();
393   const auto *RHS = Op->getRHS()->IgnoreParenImpCasts();
394 
395   const CXXBoolLiteralExpr *Bool;
396   const Expr *Other;
397   if ((Bool = dyn_cast<CXXBoolLiteralExpr>(LHS)) != nullptr)
398     Other = RHS;
399   else if ((Bool = dyn_cast<CXXBoolLiteralExpr>(RHS)) != nullptr)
400     Other = LHS;
401   else
402     return;
403 
404   if (Bool->getBeginLoc().isMacroID())
405     return;
406 
407   // FIXME: why do we need this?
408   if (!isa<CXXBoolLiteralExpr>(Other) && containsBoolLiteral(Other))
409     return;
410 
411   bool BoolValue = Bool->getValue();
412 
413   auto ReplaceWithExpression = [this, &Result, LHS, RHS,
414                                 Bool](const Expr *ReplaceWith, bool Negated) {
415     std::string Replacement =
416         replacementExpression(Result, Negated, ReplaceWith);
417     SourceRange Range(LHS->getBeginLoc(), RHS->getEndLoc());
418     issueDiag(Result, Bool->getBeginLoc(), SimplifyOperatorDiagnostic, Range,
419               Replacement);
420   };
421 
422   switch (Op->getOpcode()) {
423   case BO_LAnd:
424     if (BoolValue)
425       // expr && true -> expr
426       ReplaceWithExpression(Other, /*Negated=*/false);
427     else
428       // expr && false -> false
429       ReplaceWithExpression(Bool, /*Negated=*/false);
430     break;
431   case BO_LOr:
432     if (BoolValue)
433       // expr || true -> true
434       ReplaceWithExpression(Bool, /*Negated=*/false);
435     else
436       // expr || false -> expr
437       ReplaceWithExpression(Other, /*Negated=*/false);
438     break;
439   case BO_EQ:
440     // expr == true -> expr, expr == false -> !expr
441     ReplaceWithExpression(Other, /*Negated=*/!BoolValue);
442     break;
443   case BO_NE:
444     // expr != true -> !expr, expr != false -> expr
445     ReplaceWithExpression(Other, /*Negated=*/BoolValue);
446     break;
447   default:
448     break;
449   }
450 }
451 
452 void SimplifyBooleanExprCheck::matchBoolCondition(MatchFinder *Finder,
453                                                   bool Value,
454                                                   StringRef BooleanId) {
455   Finder->addMatcher(
456       ifStmt(hasCondition(literalOrNegatedBool(Value).bind(BooleanId)))
457           .bind(IfStmtId),
458       this);
459 }
460 
461 void SimplifyBooleanExprCheck::matchTernaryResult(MatchFinder *Finder,
462                                                   bool Value, StringRef Id) {
463   Finder->addMatcher(
464       conditionalOperator(hasTrueExpression(literalOrNegatedBool(Value)),
465                           hasFalseExpression(literalOrNegatedBool(!Value)))
466           .bind(Id),
467       this);
468 }
469 
470 void SimplifyBooleanExprCheck::matchIfReturnsBool(MatchFinder *Finder,
471                                                   bool Value, StringRef Id) {
472   if (ChainedConditionalReturn)
473     Finder->addMatcher(ifStmt(hasThen(returnsBool(Value, ThenLiteralId)),
474                               hasElse(returnsBool(!Value)))
475                            .bind(Id),
476                        this);
477   else
478     Finder->addMatcher(ifStmt(unless(hasParent(ifStmt())),
479                               hasThen(returnsBool(Value, ThenLiteralId)),
480                               hasElse(returnsBool(!Value)))
481                            .bind(Id),
482                        this);
483 }
484 
485 void SimplifyBooleanExprCheck::matchIfAssignsBool(MatchFinder *Finder,
486                                                   bool Value, StringRef Id) {
487   auto VarAssign = declRefExpr(hasDeclaration(decl().bind(IfAssignVarId)));
488   auto VarRef = declRefExpr(hasDeclaration(equalsBoundNode(IfAssignVarId)));
489   auto MemAssign = memberExpr(hasDeclaration(decl().bind(IfAssignVarId)));
490   auto MemRef = memberExpr(hasDeclaration(equalsBoundNode(IfAssignVarId)));
491   auto SimpleThen =
492       binaryOperator(hasOperatorName("="), hasLHS(anyOf(VarAssign, MemAssign)),
493                      hasLHS(expr().bind(IfAssignVariableId)),
494                      hasRHS(literalOrNegatedBool(Value).bind(IfAssignLocId)));
495   auto Then = anyOf(SimpleThen, compoundStmt(statementCountIs(1),
496                                              hasAnySubstatement(SimpleThen)));
497   auto SimpleElse =
498       binaryOperator(hasOperatorName("="), hasLHS(anyOf(VarRef, MemRef)),
499                      hasRHS(literalOrNegatedBool(!Value)));
500   auto Else = anyOf(SimpleElse, compoundStmt(statementCountIs(1),
501                                              hasAnySubstatement(SimpleElse)));
502   if (ChainedConditionalAssignment)
503     Finder->addMatcher(ifStmt(hasThen(Then), hasElse(Else)).bind(Id), this);
504   else
505     Finder->addMatcher(
506         ifStmt(unless(hasParent(ifStmt())), hasThen(Then), hasElse(Else))
507             .bind(Id),
508         this);
509 }
510 
511 static internal::Matcher<Stmt> ifReturnValue(bool Value) {
512   return ifStmt(hasThen(returnsBool(Value)), unless(hasElse(stmt())))
513       .bind(CompoundIfId);
514 }
515 
516 static internal::Matcher<Stmt> returnNotValue(bool Value) {
517   return returnStmt(has(literalOrNegatedBool(!Value))).bind(CompoundReturnId);
518 }
519 
520 void SimplifyBooleanExprCheck::matchCompoundIfReturnsBool(MatchFinder *Finder,
521                                                           bool Value,
522                                                           StringRef Id) {
523   if (ChainedConditionalReturn)
524     Finder->addMatcher(
525         compoundStmt(hasSubstatementSequence(ifReturnValue(Value),
526                                              returnNotValue(Value)))
527             .bind(Id),
528         this);
529   else
530     Finder->addMatcher(
531         compoundStmt(hasSubstatementSequence(ifStmt(hasThen(returnsBool(Value)),
532                                                     unless(hasElse(stmt())),
533                                                     unless(hasParent(ifStmt())))
534                                                  .bind(CompoundIfId),
535                                              returnNotValue(Value)))
536             .bind(Id),
537         this);
538 }
539 
540 void SimplifyBooleanExprCheck::matchCaseIfReturnsBool(MatchFinder *Finder,
541                                                       bool Value,
542                                                       StringRef Id) {
543   internal::Matcher<Stmt> CaseStmt =
544       caseStmt(hasSubstatement(ifReturnValue(Value))).bind(CaseId);
545   internal::Matcher<Stmt> CompoundStmt =
546       compoundStmt(hasSubstatementSequence(CaseStmt, returnNotValue(Value)))
547           .bind(Id);
548   Finder->addMatcher(switchStmt(has(CompoundStmt)), this);
549 }
550 
551 void SimplifyBooleanExprCheck::matchDefaultIfReturnsBool(MatchFinder *Finder,
552                                                          bool Value,
553                                                          StringRef Id) {
554   internal::Matcher<Stmt> DefaultStmt =
555       defaultStmt(hasSubstatement(ifReturnValue(Value))).bind(DefaultId);
556   internal::Matcher<Stmt> CompoundStmt =
557       compoundStmt(hasSubstatementSequence(DefaultStmt, returnNotValue(Value)))
558           .bind(Id);
559   Finder->addMatcher(switchStmt(has(CompoundStmt)), this);
560 }
561 
562 void SimplifyBooleanExprCheck::matchLabelIfReturnsBool(MatchFinder *Finder,
563                                                        bool Value,
564                                                        StringRef Id) {
565   internal::Matcher<Stmt> LabelStmt =
566       labelStmt(hasSubstatement(ifReturnValue(Value))).bind(LabelId);
567   internal::Matcher<Stmt> CompoundStmt =
568       compoundStmt(hasSubstatementSequence(LabelStmt, returnNotValue(Value)))
569           .bind(Id);
570   Finder->addMatcher(CompoundStmt, this);
571 }
572 
573 void SimplifyBooleanExprCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
574   Options.store(Opts, "ChainedConditionalReturn", ChainedConditionalReturn);
575   Options.store(Opts, "ChainedConditionalAssignment",
576                 ChainedConditionalAssignment);
577 }
578 
579 void SimplifyBooleanExprCheck::registerMatchers(MatchFinder *Finder) {
580   Finder->addMatcher(translationUnitDecl().bind("top"), this);
581 
582   matchBoolCondition(Finder, true, ConditionThenStmtId);
583   matchBoolCondition(Finder, false, ConditionElseStmtId);
584 
585   matchTernaryResult(Finder, true, TernaryId);
586   matchTernaryResult(Finder, false, TernaryNegatedId);
587 
588   matchIfReturnsBool(Finder, true, IfReturnsBoolId);
589   matchIfReturnsBool(Finder, false, IfReturnsNotBoolId);
590 
591   matchIfAssignsBool(Finder, true, IfAssignBoolId);
592   matchIfAssignsBool(Finder, false, IfAssignNotBoolId);
593 
594   matchCompoundIfReturnsBool(Finder, true, CompoundBoolId);
595   matchCompoundIfReturnsBool(Finder, false, CompoundNotBoolId);
596 
597   matchCaseIfReturnsBool(Finder, true, CaseCompoundBoolId);
598   matchCaseIfReturnsBool(Finder, false, CaseCompoundNotBoolId);
599 
600   matchDefaultIfReturnsBool(Finder, true, DefaultCompoundBoolId);
601   matchDefaultIfReturnsBool(Finder, false, DefaultCompoundNotBoolId);
602 
603   matchLabelIfReturnsBool(Finder, true, LabelCompoundBoolId);
604   matchLabelIfReturnsBool(Finder, false, LabelCompoundNotBoolId);
605 }
606 
607 void SimplifyBooleanExprCheck::check(const MatchFinder::MatchResult &Result) {
608   if (Result.Nodes.getNodeAs<TranslationUnitDecl>("top"))
609     Visitor(this, Result).TraverseAST(*Result.Context);
610   else if (const Expr *TrueConditionRemoved =
611                getBoolLiteral(Result, ConditionThenStmtId))
612     replaceWithThenStatement(Result, TrueConditionRemoved);
613   else if (const Expr *FalseConditionRemoved =
614                getBoolLiteral(Result, ConditionElseStmtId))
615     replaceWithElseStatement(Result, FalseConditionRemoved);
616   else if (const auto *Ternary =
617                Result.Nodes.getNodeAs<ConditionalOperator>(TernaryId))
618     replaceWithCondition(Result, Ternary, false);
619   else if (const auto *TernaryNegated =
620                Result.Nodes.getNodeAs<ConditionalOperator>(TernaryNegatedId))
621     replaceWithCondition(Result, TernaryNegated, true);
622   else if (const auto *If = Result.Nodes.getNodeAs<IfStmt>(IfReturnsBoolId))
623     replaceWithReturnCondition(Result, If, false);
624   else if (const auto *IfNot =
625                Result.Nodes.getNodeAs<IfStmt>(IfReturnsNotBoolId))
626     replaceWithReturnCondition(Result, IfNot, true);
627   else if (const auto *IfAssign =
628                Result.Nodes.getNodeAs<IfStmt>(IfAssignBoolId))
629     replaceWithAssignment(Result, IfAssign, false);
630   else if (const auto *IfAssignNot =
631                Result.Nodes.getNodeAs<IfStmt>(IfAssignNotBoolId))
632     replaceWithAssignment(Result, IfAssignNot, true);
633   else if (const auto *Compound =
634                Result.Nodes.getNodeAs<CompoundStmt>(CompoundBoolId))
635     replaceCompoundReturnWithCondition(Result, Compound, false);
636   else if (const auto *CompoundNot =
637                Result.Nodes.getNodeAs<CompoundStmt>(CompoundNotBoolId))
638     replaceCompoundReturnWithCondition(Result, CompoundNot, true);
639   else if (Result.Nodes.getNodeAs<CompoundStmt>(CaseCompoundBoolId))
640     replaceCaseCompoundReturnWithCondition(Result, false);
641   else if (Result.Nodes.getNodeAs<CompoundStmt>(CaseCompoundNotBoolId))
642     replaceCaseCompoundReturnWithCondition(Result, true);
643   else if (Result.Nodes.getNodeAs<CompoundStmt>(DefaultCompoundBoolId))
644     replaceDefaultCompoundReturnWithCondition(Result, false);
645   else if (Result.Nodes.getNodeAs<CompoundStmt>(DefaultCompoundNotBoolId))
646     replaceDefaultCompoundReturnWithCondition(Result, true);
647   else if (Result.Nodes.getNodeAs<CompoundStmt>(LabelCompoundBoolId))
648     replaceLabelCompoundReturnWithCondition(Result, false);
649   else if (Result.Nodes.getNodeAs<CompoundStmt>(LabelCompoundNotBoolId))
650     replaceLabelCompoundReturnWithCondition(Result, true);
651   else if (const auto TU = Result.Nodes.getNodeAs<Decl>("top"))
652     Visitor(this, Result).TraverseDecl(const_cast<Decl *>(TU));
653 }
654 
655 void SimplifyBooleanExprCheck::issueDiag(const MatchFinder::MatchResult &Result,
656                                          SourceLocation Loc,
657                                          StringRef Description,
658                                          SourceRange ReplacementRange,
659                                          StringRef Replacement) {
660   CharSourceRange CharRange =
661       Lexer::makeFileCharRange(CharSourceRange::getTokenRange(ReplacementRange),
662                                *Result.SourceManager, getLangOpts());
663 
664   DiagnosticBuilder Diag = diag(Loc, Description);
665   if (!containsDiscardedTokens(Result, CharRange))
666     Diag << FixItHint::CreateReplacement(CharRange, Replacement);
667 }
668 
669 void SimplifyBooleanExprCheck::replaceWithThenStatement(
670     const MatchFinder::MatchResult &Result, const Expr *BoolLiteral) {
671   const auto *IfStatement = Result.Nodes.getNodeAs<IfStmt>(IfStmtId);
672   issueDiag(Result, BoolLiteral->getBeginLoc(), SimplifyConditionDiagnostic,
673             IfStatement->getSourceRange(),
674             getText(Result, *IfStatement->getThen()));
675 }
676 
677 void SimplifyBooleanExprCheck::replaceWithElseStatement(
678     const MatchFinder::MatchResult &Result, const Expr *BoolLiteral) {
679   const auto *IfStatement = Result.Nodes.getNodeAs<IfStmt>(IfStmtId);
680   const Stmt *ElseStatement = IfStatement->getElse();
681   issueDiag(Result, BoolLiteral->getBeginLoc(), SimplifyConditionDiagnostic,
682             IfStatement->getSourceRange(),
683             ElseStatement ? getText(Result, *ElseStatement) : "");
684 }
685 
686 void SimplifyBooleanExprCheck::replaceWithCondition(
687     const MatchFinder::MatchResult &Result, const ConditionalOperator *Ternary,
688     bool Negated) {
689   std::string Replacement =
690       replacementExpression(Result, Negated, Ternary->getCond());
691   issueDiag(Result, Ternary->getTrueExpr()->getBeginLoc(),
692             "redundant boolean literal in ternary expression result",
693             Ternary->getSourceRange(), Replacement);
694 }
695 
696 void SimplifyBooleanExprCheck::replaceWithReturnCondition(
697     const MatchFinder::MatchResult &Result, const IfStmt *If, bool Negated) {
698   StringRef Terminator = isa<CompoundStmt>(If->getElse()) ? ";" : "";
699   std::string Condition = replacementExpression(Result, Negated, If->getCond());
700   std::string Replacement = ("return " + Condition + Terminator).str();
701   SourceLocation Start =
702       Result.Nodes.getNodeAs<CXXBoolLiteralExpr>(ThenLiteralId)->getBeginLoc();
703   issueDiag(Result, Start, SimplifyConditionalReturnDiagnostic,
704             If->getSourceRange(), Replacement);
705 }
706 
707 void SimplifyBooleanExprCheck::replaceCompoundReturnWithCondition(
708     const MatchFinder::MatchResult &Result, const CompoundStmt *Compound,
709     bool Negated) {
710   const auto *Ret = Result.Nodes.getNodeAs<ReturnStmt>(CompoundReturnId);
711 
712   // Scan through the CompoundStmt to look for a chained-if construct.
713   const IfStmt *BeforeIf = nullptr;
714   CompoundStmt::const_body_iterator Current = Compound->body_begin();
715   CompoundStmt::const_body_iterator After = Compound->body_begin();
716   for (++After; After != Compound->body_end() && *Current != Ret;
717        ++Current, ++After) {
718     if (const auto *If = dyn_cast<IfStmt>(*Current)) {
719       if (const Expr *Lit = stmtReturnsBool(If, Negated)) {
720         if (*After == Ret) {
721           if (!ChainedConditionalReturn && BeforeIf)
722             continue;
723 
724           std::string Replacement =
725               "return " + replacementExpression(Result, Negated, If->getCond());
726           issueDiag(
727               Result, Lit->getBeginLoc(), SimplifyConditionalReturnDiagnostic,
728               SourceRange(If->getBeginLoc(), Ret->getEndLoc()), Replacement);
729           return;
730         }
731 
732         BeforeIf = If;
733       }
734     } else {
735       BeforeIf = nullptr;
736     }
737   }
738 }
739 
740 void SimplifyBooleanExprCheck::replaceCompoundReturnWithCondition(
741     const MatchFinder::MatchResult &Result, bool Negated, const IfStmt *If) {
742   const auto *Lit = stmtReturnsBool(If, Negated);
743   const auto *Ret = Result.Nodes.getNodeAs<ReturnStmt>(CompoundReturnId);
744   const std::string Replacement =
745       "return " + replacementExpression(Result, Negated, If->getCond());
746   issueDiag(Result, Lit->getBeginLoc(), SimplifyConditionalReturnDiagnostic,
747             SourceRange(If->getBeginLoc(), Ret->getEndLoc()), Replacement);
748 }
749 
750 void SimplifyBooleanExprCheck::replaceCaseCompoundReturnWithCondition(
751     const MatchFinder::MatchResult &Result, bool Negated) {
752   const auto *CaseDefault = Result.Nodes.getNodeAs<CaseStmt>(CaseId);
753   const auto *If = cast<IfStmt>(CaseDefault->getSubStmt());
754   replaceCompoundReturnWithCondition(Result, Negated, If);
755 }
756 
757 void SimplifyBooleanExprCheck::replaceDefaultCompoundReturnWithCondition(
758     const MatchFinder::MatchResult &Result, bool Negated) {
759   const SwitchCase *CaseDefault =
760       Result.Nodes.getNodeAs<DefaultStmt>(DefaultId);
761   const auto *If = cast<IfStmt>(CaseDefault->getSubStmt());
762   replaceCompoundReturnWithCondition(Result, Negated, If);
763 }
764 
765 void SimplifyBooleanExprCheck::replaceLabelCompoundReturnWithCondition(
766     const MatchFinder::MatchResult &Result, bool Negated) {
767   const auto *Label = Result.Nodes.getNodeAs<LabelStmt>(LabelId);
768   const auto *If = cast<IfStmt>(Label->getSubStmt());
769   replaceCompoundReturnWithCondition(Result, Negated, If);
770 }
771 
772 void SimplifyBooleanExprCheck::replaceWithAssignment(
773     const MatchFinder::MatchResult &Result, const IfStmt *IfAssign,
774     bool Negated) {
775   SourceRange Range = IfAssign->getSourceRange();
776   StringRef VariableName =
777       getText(Result, *Result.Nodes.getNodeAs<Expr>(IfAssignVariableId));
778   StringRef Terminator = isa<CompoundStmt>(IfAssign->getElse()) ? ";" : "";
779   std::string Condition =
780       replacementExpression(Result, Negated, IfAssign->getCond());
781   std::string Replacement =
782       (VariableName + " = " + Condition + Terminator).str();
783   SourceLocation Location =
784       Result.Nodes.getNodeAs<CXXBoolLiteralExpr>(IfAssignLocId)->getBeginLoc();
785   issueDiag(Result, Location,
786             "redundant boolean literal in conditional assignment", Range,
787             Replacement);
788 }
789 
790 } // namespace readability
791 } // namespace tidy
792 } // namespace clang
793