1 //===--- SimplifyBooleanExpr.cpp clang-tidy ---------------------*- C++ -*-===//
2 //
3 //                     The LLVM Compiler Infrastructure
4 //
5 // This file is distributed under the University of Illinois Open Source
6 // License. See LICENSE.TXT for details.
7 //
8 //===----------------------------------------------------------------------===//
9 
10 #include "SimplifyBooleanExprCheck.h"
11 #include "clang/Lex/Lexer.h"
12 
13 #include <cassert>
14 #include <string>
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 MatchFinder::MatchResult &Result, SourceRange Range) {
25   return Lexer::getSourceText(CharSourceRange::getTokenRange(Range),
26                               *Result.SourceManager,
27                               Result.Context->getLangOpts());
28 }
29 
30 template <typename T>
31 StringRef getText(const MatchFinder::MatchResult &Result, T &Node) {
32   return getText(Result, Node.getSourceRange());
33 }
34 
35 const char RightExpressionId[] = "bool-op-expr-yields-expr";
36 const char LeftExpressionId[] = "expr-op-bool-yields-expr";
37 const char NegatedRightExpressionId[] = "bool-op-expr-yields-not-expr";
38 const char NegatedLeftExpressionId[] = "expr-op-bool-yields-not-expr";
39 const char ConditionThenStmtId[] = "if-bool-yields-then";
40 const char ConditionElseStmtId[] = "if-bool-yields-else";
41 const char TernaryId[] = "ternary-bool-yields-condition";
42 const char TernaryNegatedId[] = "ternary-bool-yields-not-condition";
43 const char IfReturnsBoolId[] = "if-return";
44 const char IfReturnsNotBoolId[] = "if-not-return";
45 const char ThenLiteralId[] = "then-literal";
46 const char IfAssignVariableId[] = "if-assign-lvalue";
47 const char IfAssignLocId[] = "if-assign-loc";
48 const char IfAssignBoolId[] = "if-assign";
49 const char IfAssignNotBoolId[] = "if-assign-not";
50 const char IfAssignObjId[] = "if-assign-obj";
51 
52 const char IfStmtId[] = "if";
53 const char LHSId[] = "lhs-expr";
54 const char RHSId[] = "rhs-expr";
55 
56 const char SimplifyOperatorDiagnostic[] =
57     "redundant boolean literal supplied to boolean operator";
58 const char SimplifyConditionDiagnostic[] =
59     "redundant boolean literal in if statement condition";
60 
61 const CXXBoolLiteralExpr *getBoolLiteral(const MatchFinder::MatchResult &Result,
62                                          StringRef Id) {
63   const auto *Literal = Result.Nodes.getNodeAs<CXXBoolLiteralExpr>(Id);
64   return (Literal &&
65           Result.SourceManager->isMacroBodyExpansion(Literal->getLocStart()))
66              ? nullptr
67              : Literal;
68 }
69 
70 internal::Matcher<Stmt> ReturnsBool(bool Value, StringRef Id = "") {
71   auto SimpleReturnsBool = returnStmt(
72       has(boolLiteral(equals(Value)).bind(Id.empty() ? "ignored" : Id)));
73   return anyOf(SimpleReturnsBool,
74                compoundStmt(statementCountIs(1), has(SimpleReturnsBool)));
75 }
76 
77 bool needsParensAfterUnaryNegation(const Expr *E) {
78   if (isa<BinaryOperator>(E) || isa<ConditionalOperator>(E))
79     return true;
80   if (const auto *Op = dyn_cast<CXXOperatorCallExpr>(E))
81     return Op->getNumArgs() == 2 && Op->getOperator() != OO_Call &&
82            Op->getOperator() != OO_Subscript;
83   return false;
84 }
85 
86 std::pair<BinaryOperatorKind, BinaryOperatorKind> Opposites[] = {
87     std::make_pair(BO_LT, BO_GE), std::make_pair(BO_GT, BO_LE),
88     std::make_pair(BO_EQ, BO_NE)};
89 
90 StringRef negatedOperator(const BinaryOperator *BinOp) {
91   const BinaryOperatorKind Opcode = BinOp->getOpcode();
92   for (auto NegatableOp : Opposites) {
93     if (Opcode == NegatableOp.first)
94       return BinOp->getOpcodeStr(NegatableOp.second);
95     if (Opcode == NegatableOp.second)
96       return BinOp->getOpcodeStr(NegatableOp.first);
97   }
98   return StringRef();
99 }
100 
101 std::string replacementExpression(const MatchFinder::MatchResult &Result,
102                                   bool Negated, const Expr *E) {
103   while (const auto *Parenthesized = dyn_cast<ParenExpr>(E)) {
104     E = Parenthesized->getSubExpr();
105   }
106   if (Negated) {
107     if (const auto *BinOp = dyn_cast<BinaryOperator>(E)) {
108       StringRef NegatedOperator = negatedOperator(BinOp);
109       if (!NegatedOperator.empty()) {
110         return (getText(Result, *BinOp->getLHS()) + " " + NegatedOperator +
111                 " " + getText(Result, *BinOp->getRHS())).str();
112       }
113     }
114   }
115   StringRef Text = getText(Result, *E);
116   return (Negated ? (needsParensAfterUnaryNegation(E) ? "!(" + Text + ")"
117                                                       : "!" + Text)
118                   : Text).str();
119 }
120 
121 } // namespace
122 
123 SimplifyBooleanExprCheck::SimplifyBooleanExprCheck(StringRef Name,
124                                                    ClangTidyContext *Context)
125     : ClangTidyCheck(Name, Context),
126       ChainedConditionalReturn(Options.get("ChainedConditionalReturn", 0U)),
127       ChainedConditionalAssignment(
128           Options.get("ChainedConditionalAssignment", 0U)) {}
129 
130 void SimplifyBooleanExprCheck::matchBoolBinOpExpr(MatchFinder *Finder,
131                                                   bool Value,
132                                                   StringRef OperatorName,
133                                                   StringRef BooleanId) {
134   Finder->addMatcher(
135       binaryOperator(isExpansionInMainFile(), hasOperatorName(OperatorName),
136                      hasLHS(allOf(expr().bind(LHSId),
137                                   boolLiteral(equals(Value)).bind(BooleanId))),
138                      hasRHS(expr().bind(RHSId)),
139                      unless(hasRHS(hasDescendant(boolLiteral())))),
140       this);
141 }
142 
143 void SimplifyBooleanExprCheck::matchExprBinOpBool(MatchFinder *Finder,
144                                                   bool Value,
145                                                   StringRef OperatorName,
146                                                   StringRef BooleanId) {
147   Finder->addMatcher(
148       binaryOperator(
149           isExpansionInMainFile(), hasOperatorName(OperatorName),
150           hasLHS(expr().bind(LHSId)),
151           unless(hasLHS(anyOf(boolLiteral(), hasDescendant(boolLiteral())))),
152           hasRHS(allOf(expr().bind(RHSId),
153                        boolLiteral(equals(Value)).bind(BooleanId)))),
154       this);
155 }
156 
157 void SimplifyBooleanExprCheck::matchBoolCompOpExpr(MatchFinder *Finder,
158                                                    bool Value,
159                                                    StringRef OperatorName,
160                                                    StringRef BooleanId) {
161   Finder->addMatcher(
162       binaryOperator(isExpansionInMainFile(), hasOperatorName(OperatorName),
163                      hasLHS(allOf(expr().bind(LHSId),
164                                   ignoringImpCasts(boolLiteral(equals(Value))
165                                                        .bind(BooleanId)))),
166                      hasRHS(expr().bind(RHSId)),
167                      unless(hasRHS(hasDescendant(boolLiteral())))),
168       this);
169 }
170 
171 void SimplifyBooleanExprCheck::matchExprCompOpBool(MatchFinder *Finder,
172                                                    bool Value,
173                                                    StringRef OperatorName,
174                                                    StringRef BooleanId) {
175   Finder->addMatcher(
176       binaryOperator(isExpansionInMainFile(), hasOperatorName(OperatorName),
177                      unless(hasLHS(hasDescendant(boolLiteral()))),
178                      hasLHS(expr().bind(LHSId)),
179                      hasRHS(allOf(expr().bind(RHSId),
180                                   ignoringImpCasts(boolLiteral(equals(Value))
181                                                        .bind(BooleanId))))),
182       this);
183 }
184 
185 void SimplifyBooleanExprCheck::matchBoolCondition(MatchFinder *Finder,
186                                                   bool Value,
187                                                   StringRef BooleanId) {
188   Finder->addMatcher(ifStmt(isExpansionInMainFile(),
189                             hasCondition(boolLiteral(equals(Value))
190                                              .bind(BooleanId))).bind(IfStmtId),
191                      this);
192 }
193 
194 void SimplifyBooleanExprCheck::matchTernaryResult(MatchFinder *Finder,
195                                                   bool Value,
196                                                   StringRef TernaryId) {
197   Finder->addMatcher(
198       conditionalOperator(isExpansionInMainFile(),
199                           hasTrueExpression(boolLiteral(equals(Value))),
200                           hasFalseExpression(boolLiteral(equals(!Value))))
201           .bind(TernaryId),
202       this);
203 }
204 
205 void SimplifyBooleanExprCheck::matchIfReturnsBool(MatchFinder *Finder,
206                                                   bool Value, StringRef Id) {
207   if (ChainedConditionalReturn) {
208     Finder->addMatcher(ifStmt(isExpansionInMainFile(),
209                               hasThen(ReturnsBool(Value, ThenLiteralId)),
210                               hasElse(ReturnsBool(!Value))).bind(Id),
211                        this);
212   } else {
213     Finder->addMatcher(ifStmt(isExpansionInMainFile(),
214                               unless(hasParent(ifStmt())),
215                               hasThen(ReturnsBool(Value, ThenLiteralId)),
216                               hasElse(ReturnsBool(!Value))).bind(Id),
217                        this);
218   }
219 }
220 
221 void SimplifyBooleanExprCheck::matchIfAssignsBool(MatchFinder *Finder,
222                                                   bool Value, StringRef Id) {
223   auto SimpleThen = binaryOperator(
224       hasOperatorName("="),
225       hasLHS(declRefExpr(hasDeclaration(decl().bind(IfAssignObjId)))),
226       hasLHS(expr().bind(IfAssignVariableId)),
227       hasRHS(boolLiteral(equals(Value)).bind(IfAssignLocId)));
228   auto Then = anyOf(SimpleThen, compoundStmt(statementCountIs(1),
229                                              hasAnySubstatement(SimpleThen)));
230   auto SimpleElse = binaryOperator(
231       hasOperatorName("="),
232       hasLHS(declRefExpr(hasDeclaration(equalsBoundNode(IfAssignObjId)))),
233       hasRHS(boolLiteral(equals(!Value))));
234   auto Else = anyOf(SimpleElse, compoundStmt(statementCountIs(1),
235                                              hasAnySubstatement(SimpleElse)));
236   if (ChainedConditionalAssignment) {
237     Finder->addMatcher(
238         ifStmt(isExpansionInMainFile(), hasThen(Then), hasElse(Else)).bind(Id),
239         this);
240   } else {
241     Finder->addMatcher(ifStmt(isExpansionInMainFile(),
242                               unless(hasParent(ifStmt())), hasThen(Then),
243                               hasElse(Else)).bind(Id),
244                        this);
245   }
246 }
247 
248 void SimplifyBooleanExprCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
249   Options.store(Opts, "ChainedConditionalReturn", ChainedConditionalReturn);
250   Options.store(Opts, "ChainedConditionalAssignment",
251                 ChainedConditionalAssignment);
252 }
253 
254 void SimplifyBooleanExprCheck::registerMatchers(MatchFinder *Finder) {
255   matchBoolBinOpExpr(Finder, true, "&&", RightExpressionId);
256   matchBoolBinOpExpr(Finder, false, "||", RightExpressionId);
257   matchExprBinOpBool(Finder, false, "&&", RightExpressionId);
258   matchExprBinOpBool(Finder, true, "||", RightExpressionId);
259   matchBoolCompOpExpr(Finder, true, "==", RightExpressionId);
260   matchBoolCompOpExpr(Finder, false, "!=", RightExpressionId);
261 
262   matchExprBinOpBool(Finder, true, "&&", LeftExpressionId);
263   matchExprBinOpBool(Finder, false, "||", LeftExpressionId);
264   matchBoolBinOpExpr(Finder, false, "&&", LeftExpressionId);
265   matchBoolBinOpExpr(Finder, true, "||", LeftExpressionId);
266   matchExprCompOpBool(Finder, true, "==", LeftExpressionId);
267   matchExprCompOpBool(Finder, false, "!=", LeftExpressionId);
268 
269   matchBoolCompOpExpr(Finder, false, "==", NegatedRightExpressionId);
270   matchBoolCompOpExpr(Finder, true, "!=", NegatedRightExpressionId);
271 
272   matchExprCompOpBool(Finder, false, "==", NegatedLeftExpressionId);
273   matchExprCompOpBool(Finder, true, "!=", NegatedLeftExpressionId);
274 
275   matchBoolCondition(Finder, true, ConditionThenStmtId);
276   matchBoolCondition(Finder, false, ConditionElseStmtId);
277 
278   matchTernaryResult(Finder, true, TernaryId);
279   matchTernaryResult(Finder, false, TernaryNegatedId);
280 
281   matchIfReturnsBool(Finder, true, IfReturnsBoolId);
282   matchIfReturnsBool(Finder, false, IfReturnsNotBoolId);
283 
284   matchIfAssignsBool(Finder, true, IfAssignBoolId);
285   matchIfAssignsBool(Finder, false, IfAssignNotBoolId);
286 }
287 
288 void SimplifyBooleanExprCheck::check(const MatchFinder::MatchResult &Result) {
289   if (const CXXBoolLiteralExpr *LeftRemoved =
290           getBoolLiteral(Result, RightExpressionId)) {
291     replaceWithExpression(Result, LeftRemoved, false);
292   } else if (const CXXBoolLiteralExpr *RightRemoved =
293                  getBoolLiteral(Result, LeftExpressionId)) {
294     replaceWithExpression(Result, RightRemoved, true);
295   } else if (const CXXBoolLiteralExpr *NegatedLeftRemoved =
296                  getBoolLiteral(Result, NegatedRightExpressionId)) {
297     replaceWithExpression(Result, NegatedLeftRemoved, false, true);
298   } else if (const CXXBoolLiteralExpr *NegatedRightRemoved =
299                  getBoolLiteral(Result, NegatedLeftExpressionId)) {
300     replaceWithExpression(Result, NegatedRightRemoved, true, true);
301   } else if (const CXXBoolLiteralExpr *TrueConditionRemoved =
302                  getBoolLiteral(Result, ConditionThenStmtId)) {
303     replaceWithThenStatement(Result, TrueConditionRemoved);
304   } else if (const CXXBoolLiteralExpr *FalseConditionRemoved =
305                  getBoolLiteral(Result, ConditionElseStmtId)) {
306     replaceWithElseStatement(Result, FalseConditionRemoved);
307   } else if (const auto *Ternary =
308                  Result.Nodes.getNodeAs<ConditionalOperator>(TernaryId)) {
309     replaceWithCondition(Result, Ternary);
310   } else if (const auto *TernaryNegated =
311                  Result.Nodes.getNodeAs<ConditionalOperator>(
312                      TernaryNegatedId)) {
313     replaceWithCondition(Result, TernaryNegated, true);
314   } else if (const auto *If = Result.Nodes.getNodeAs<IfStmt>(IfReturnsBoolId)) {
315     replaceWithReturnCondition(Result, If);
316   } else if (const auto *IfNot =
317                  Result.Nodes.getNodeAs<IfStmt>(IfReturnsNotBoolId)) {
318     replaceWithReturnCondition(Result, IfNot, true);
319   } else if (const auto *IfAssign =
320                  Result.Nodes.getNodeAs<IfStmt>(IfAssignBoolId)) {
321     replaceWithAssignment(Result, IfAssign);
322   } else if (const auto *IfAssignNot =
323                  Result.Nodes.getNodeAs<IfStmt>(IfAssignNotBoolId)) {
324     replaceWithAssignment(Result, IfAssignNot, true);
325   }
326 }
327 
328 void SimplifyBooleanExprCheck::replaceWithExpression(
329     const ast_matchers::MatchFinder::MatchResult &Result,
330     const CXXBoolLiteralExpr *BoolLiteral, bool UseLHS, bool Negated) {
331   const auto *LHS = Result.Nodes.getNodeAs<Expr>(LHSId);
332   const auto *RHS = Result.Nodes.getNodeAs<Expr>(RHSId);
333   std::string Replacement =
334       replacementExpression(Result, Negated, UseLHS ? LHS : RHS);
335   SourceLocation Start = LHS->getLocStart();
336   SourceLocation End = RHS->getLocEnd();
337   diag(BoolLiteral->getLocStart(), SimplifyOperatorDiagnostic)
338       << FixItHint::CreateReplacement(SourceRange(Start, End), Replacement);
339 }
340 
341 void SimplifyBooleanExprCheck::replaceWithThenStatement(
342     const MatchFinder::MatchResult &Result,
343     const CXXBoolLiteralExpr *TrueConditionRemoved) {
344   const auto *IfStatement = Result.Nodes.getNodeAs<IfStmt>(IfStmtId);
345   diag(TrueConditionRemoved->getLocStart(), SimplifyConditionDiagnostic)
346       << FixItHint::CreateReplacement(IfStatement->getSourceRange(),
347                                       getText(Result, *IfStatement->getThen()));
348 }
349 
350 void SimplifyBooleanExprCheck::replaceWithElseStatement(
351     const MatchFinder::MatchResult &Result,
352     const CXXBoolLiteralExpr *FalseConditionRemoved) {
353   const auto *IfStatement = Result.Nodes.getNodeAs<IfStmt>(IfStmtId);
354   const Stmt *ElseStatement = IfStatement->getElse();
355   diag(FalseConditionRemoved->getLocStart(), SimplifyConditionDiagnostic)
356       << FixItHint::CreateReplacement(
357           IfStatement->getSourceRange(),
358           ElseStatement ? getText(Result, *ElseStatement) : "");
359 }
360 
361 void SimplifyBooleanExprCheck::replaceWithCondition(
362     const MatchFinder::MatchResult &Result, const ConditionalOperator *Ternary,
363     bool Negated) {
364   std::string Replacement =
365       replacementExpression(Result, Negated, Ternary->getCond());
366   diag(Ternary->getTrueExpr()->getLocStart(),
367        "redundant boolean literal in ternary expression result")
368       << FixItHint::CreateReplacement(Ternary->getSourceRange(), Replacement);
369 }
370 
371 void SimplifyBooleanExprCheck::replaceWithReturnCondition(
372     const MatchFinder::MatchResult &Result, const IfStmt *If, bool Negated) {
373   StringRef Terminator = isa<CompoundStmt>(If->getElse()) ? ";" : "";
374   std::string Condition = replacementExpression(Result, Negated, If->getCond());
375   std::string Replacement = ("return " + Condition + Terminator).str();
376   SourceLocation Start =
377       Result.Nodes.getNodeAs<CXXBoolLiteralExpr>(ThenLiteralId)->getLocStart();
378   diag(Start, "redundant boolean literal in conditional return statement")
379       << FixItHint::CreateReplacement(If->getSourceRange(), Replacement);
380 }
381 
382 void SimplifyBooleanExprCheck::replaceWithAssignment(
383     const MatchFinder::MatchResult &Result, const IfStmt *IfAssign,
384     bool Negated) {
385   SourceRange Range = IfAssign->getSourceRange();
386   StringRef VariableName =
387       getText(Result, *Result.Nodes.getNodeAs<Expr>(IfAssignVariableId));
388   StringRef Terminator = isa<CompoundStmt>(IfAssign->getElse()) ? ";" : "";
389   std::string Condition =
390       replacementExpression(Result, Negated, IfAssign->getCond());
391   std::string Replacement =
392       (VariableName + " = " + Condition + Terminator).str();
393   SourceLocation Location =
394       Result.Nodes.getNodeAs<CXXBoolLiteralExpr>(IfAssignLocId)->getLocStart();
395   this->diag(Location, "redundant boolean literal in conditional assignment")
396       << FixItHint::CreateReplacement(Range, Replacement);
397 }
398 
399 } // namespace readability
400 } // namespace tidy
401 } // namespace clang
402