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()))
112             .str();
113       }
114     }
115   }
116   StringRef Text = getText(Result, *E);
117   return (Negated ? (needsParensAfterUnaryNegation(E) ? "!(" + Text + ")"
118                                                       : "!" + Text)
119                   : Text)
120       .str();
121 }
122 
123 } // namespace
124 
125 void SimplifyBooleanExprCheck::matchBoolBinOpExpr(MatchFinder *Finder,
126                                                   bool Value,
127                                                   StringRef OperatorName,
128                                                   StringRef BooleanId) {
129   Finder->addMatcher(
130       binaryOperator(isExpansionInMainFile(), hasOperatorName(OperatorName),
131                      hasLHS(allOf(expr().bind(LHSId),
132                                   boolLiteral(equals(Value)).bind(BooleanId))),
133                      hasRHS(expr().bind(RHSId)),
134                      unless(hasRHS(hasDescendant(boolLiteral())))),
135       this);
136 }
137 
138 void SimplifyBooleanExprCheck::matchExprBinOpBool(MatchFinder *Finder,
139                                                   bool Value,
140                                                   StringRef OperatorName,
141                                                   StringRef BooleanId) {
142   Finder->addMatcher(
143       binaryOperator(
144           isExpansionInMainFile(), hasOperatorName(OperatorName),
145           hasLHS(expr().bind(LHSId)),
146           unless(hasLHS(anyOf(boolLiteral(), hasDescendant(boolLiteral())))),
147           hasRHS(allOf(expr().bind(RHSId),
148                        boolLiteral(equals(Value)).bind(BooleanId)))),
149       this);
150 }
151 
152 void SimplifyBooleanExprCheck::matchBoolCompOpExpr(MatchFinder *Finder,
153                                                    bool Value,
154                                                    StringRef OperatorName,
155                                                    StringRef BooleanId) {
156   Finder->addMatcher(
157       binaryOperator(isExpansionInMainFile(), hasOperatorName(OperatorName),
158                      hasLHS(allOf(expr().bind(LHSId),
159                                   ignoringImpCasts(boolLiteral(equals(Value))
160                                                        .bind(BooleanId)))),
161                      hasRHS(expr().bind(RHSId)),
162                      unless(hasRHS(hasDescendant(boolLiteral())))),
163       this);
164 }
165 
166 void SimplifyBooleanExprCheck::matchExprCompOpBool(MatchFinder *Finder,
167                                                    bool Value,
168                                                    StringRef OperatorName,
169                                                    StringRef BooleanId) {
170   Finder->addMatcher(
171       binaryOperator(isExpansionInMainFile(), hasOperatorName(OperatorName),
172                      unless(hasLHS(hasDescendant(boolLiteral()))),
173                      hasLHS(expr().bind(LHSId)),
174                      hasRHS(allOf(expr().bind(RHSId),
175                                   ignoringImpCasts(boolLiteral(equals(Value))
176                                                        .bind(BooleanId))))),
177       this);
178 }
179 
180 void SimplifyBooleanExprCheck::matchBoolCondition(MatchFinder *Finder,
181                                                   bool Value,
182                                                   StringRef BooleanId) {
183   Finder->addMatcher(ifStmt(isExpansionInMainFile(),
184                             hasCondition(boolLiteral(equals(Value))
185                                              .bind(BooleanId))).bind(IfStmtId),
186                      this);
187 }
188 
189 void SimplifyBooleanExprCheck::matchTernaryResult(MatchFinder *Finder,
190                                                   bool Value,
191                                                   StringRef TernaryId) {
192   Finder->addMatcher(
193       conditionalOperator(isExpansionInMainFile(),
194                           hasTrueExpression(boolLiteral(equals(Value))),
195                           hasFalseExpression(boolLiteral(equals(!Value))))
196           .bind(TernaryId),
197       this);
198 }
199 
200 void SimplifyBooleanExprCheck::matchIfReturnsBool(MatchFinder *Finder,
201                                                   bool Value, StringRef Id) {
202   Finder->addMatcher(ifStmt(isExpansionInMainFile(),
203                             hasThen(ReturnsBool(Value, ThenLiteralId)),
204                             hasElse(ReturnsBool(!Value))).bind(Id),
205                      this);
206 }
207 
208 void SimplifyBooleanExprCheck::matchIfAssignsBool(MatchFinder *Finder,
209                                                   bool Value, StringRef Id) {
210   auto SimpleThen = binaryOperator(
211       hasOperatorName("="),
212       hasLHS(declRefExpr(hasDeclaration(decl().bind(IfAssignObjId)))),
213       hasLHS(expr().bind(IfAssignVariableId)),
214       hasRHS(boolLiteral(equals(Value)).bind(IfAssignLocId)));
215   auto Then = anyOf(SimpleThen, compoundStmt(statementCountIs(1),
216                                              hasAnySubstatement(SimpleThen)));
217   auto SimpleElse = binaryOperator(
218       hasOperatorName("="),
219       hasLHS(declRefExpr(hasDeclaration(equalsBoundNode(IfAssignObjId)))),
220       hasRHS(boolLiteral(equals(!Value))));
221   auto Else = anyOf(SimpleElse, compoundStmt(statementCountIs(1),
222                                              hasAnySubstatement(SimpleElse)));
223   Finder->addMatcher(
224       ifStmt(isExpansionInMainFile(), hasThen(Then), hasElse(Else)).bind(Id),
225       this);
226 }
227 
228 void SimplifyBooleanExprCheck::registerMatchers(MatchFinder *Finder) {
229   matchBoolBinOpExpr(Finder, true, "&&", RightExpressionId);
230   matchBoolBinOpExpr(Finder, false, "||", RightExpressionId);
231   matchExprBinOpBool(Finder, false, "&&", RightExpressionId);
232   matchExprBinOpBool(Finder, true, "||", RightExpressionId);
233   matchBoolCompOpExpr(Finder, true, "==", RightExpressionId);
234   matchBoolCompOpExpr(Finder, false, "!=", RightExpressionId);
235 
236   matchExprBinOpBool(Finder, true, "&&", LeftExpressionId);
237   matchExprBinOpBool(Finder, false, "||", LeftExpressionId);
238   matchBoolBinOpExpr(Finder, false, "&&", LeftExpressionId);
239   matchBoolBinOpExpr(Finder, true, "||", LeftExpressionId);
240   matchExprCompOpBool(Finder, true, "==", LeftExpressionId);
241   matchExprCompOpBool(Finder, false, "!=", LeftExpressionId);
242 
243   matchBoolCompOpExpr(Finder, false, "==", NegatedRightExpressionId);
244   matchBoolCompOpExpr(Finder, true, "!=", NegatedRightExpressionId);
245 
246   matchExprCompOpBool(Finder, false, "==", NegatedLeftExpressionId);
247   matchExprCompOpBool(Finder, true, "!=", NegatedLeftExpressionId);
248 
249   matchBoolCondition(Finder, true, ConditionThenStmtId);
250   matchBoolCondition(Finder, false, ConditionElseStmtId);
251 
252   matchTernaryResult(Finder, true, TernaryId);
253   matchTernaryResult(Finder, false, TernaryNegatedId);
254 
255   matchIfReturnsBool(Finder, true, IfReturnsBoolId);
256   matchIfReturnsBool(Finder, false, IfReturnsNotBoolId);
257 
258   matchIfAssignsBool(Finder, true, IfAssignBoolId);
259   matchIfAssignsBool(Finder, false, IfAssignNotBoolId);
260 }
261 
262 void SimplifyBooleanExprCheck::check(const MatchFinder::MatchResult &Result) {
263   if (const CXXBoolLiteralExpr *LeftRemoved =
264           getBoolLiteral(Result, RightExpressionId)) {
265     replaceWithExpression(Result, LeftRemoved, false);
266   } else if (const CXXBoolLiteralExpr *RightRemoved =
267                  getBoolLiteral(Result, LeftExpressionId)) {
268     replaceWithExpression(Result, RightRemoved, true);
269   } else if (const CXXBoolLiteralExpr *NegatedLeftRemoved =
270                  getBoolLiteral(Result, NegatedRightExpressionId)) {
271     replaceWithExpression(Result, NegatedLeftRemoved, false, true);
272   } else if (const CXXBoolLiteralExpr *NegatedRightRemoved =
273                  getBoolLiteral(Result, NegatedLeftExpressionId)) {
274     replaceWithExpression(Result, NegatedRightRemoved, true, true);
275   } else if (const CXXBoolLiteralExpr *TrueConditionRemoved =
276                  getBoolLiteral(Result, ConditionThenStmtId)) {
277     replaceWithThenStatement(Result, TrueConditionRemoved);
278   } else if (const CXXBoolLiteralExpr *FalseConditionRemoved =
279                  getBoolLiteral(Result, ConditionElseStmtId)) {
280     replaceWithElseStatement(Result, FalseConditionRemoved);
281   } else if (const auto *Ternary =
282                  Result.Nodes.getNodeAs<ConditionalOperator>(TernaryId)) {
283     replaceWithCondition(Result, Ternary);
284   } else if (const auto *TernaryNegated =
285                  Result.Nodes.getNodeAs<ConditionalOperator>(
286                      TernaryNegatedId)) {
287     replaceWithCondition(Result, TernaryNegated, true);
288   } else if (const auto *If = Result.Nodes.getNodeAs<IfStmt>(IfReturnsBoolId)) {
289     replaceWithReturnCondition(Result, If);
290   } else if (const auto *IfNot =
291                  Result.Nodes.getNodeAs<IfStmt>(IfReturnsNotBoolId)) {
292     replaceWithReturnCondition(Result, IfNot, true);
293   } else if (const auto *IfAssign =
294                  Result.Nodes.getNodeAs<IfStmt>(IfAssignBoolId)) {
295     replaceWithAssignment(Result, IfAssign);
296   } else if (const auto *IfAssignNot =
297                  Result.Nodes.getNodeAs<IfStmt>(IfAssignNotBoolId)) {
298     replaceWithAssignment(Result, IfAssignNot, true);
299   }
300 }
301 
302 void SimplifyBooleanExprCheck::replaceWithExpression(
303     const ast_matchers::MatchFinder::MatchResult &Result,
304     const CXXBoolLiteralExpr *BoolLiteral, bool UseLHS, bool Negated) {
305   const auto *LHS = Result.Nodes.getNodeAs<Expr>(LHSId);
306   const auto *RHS = Result.Nodes.getNodeAs<Expr>(RHSId);
307   std::string Replacement =
308       replacementExpression(Result, Negated, UseLHS ? LHS : RHS);
309   SourceLocation Start = LHS->getLocStart();
310   SourceLocation End = RHS->getLocEnd();
311   diag(BoolLiteral->getLocStart(), SimplifyOperatorDiagnostic)
312       << FixItHint::CreateReplacement(SourceRange(Start, End), Replacement);
313 }
314 
315 void SimplifyBooleanExprCheck::replaceWithThenStatement(
316     const MatchFinder::MatchResult &Result,
317     const CXXBoolLiteralExpr *TrueConditionRemoved) {
318   const auto *IfStatement = Result.Nodes.getNodeAs<IfStmt>(IfStmtId);
319   diag(TrueConditionRemoved->getLocStart(), SimplifyConditionDiagnostic)
320       << FixItHint::CreateReplacement(IfStatement->getSourceRange(),
321                                       getText(Result, *IfStatement->getThen()));
322 }
323 
324 void SimplifyBooleanExprCheck::replaceWithElseStatement(
325     const MatchFinder::MatchResult &Result,
326     const CXXBoolLiteralExpr *FalseConditionRemoved) {
327   const auto *IfStatement = Result.Nodes.getNodeAs<IfStmt>(IfStmtId);
328   const Stmt *ElseStatement = IfStatement->getElse();
329   diag(FalseConditionRemoved->getLocStart(), SimplifyConditionDiagnostic)
330       << FixItHint::CreateReplacement(
331           IfStatement->getSourceRange(),
332           ElseStatement ? getText(Result, *ElseStatement) : "");
333 }
334 
335 void SimplifyBooleanExprCheck::replaceWithCondition(
336     const MatchFinder::MatchResult &Result, const ConditionalOperator *Ternary,
337     bool Negated) {
338   std::string Replacement =
339       replacementExpression(Result, Negated, Ternary->getCond());
340   diag(Ternary->getTrueExpr()->getLocStart(),
341        "redundant boolean literal in ternary expression result")
342       << FixItHint::CreateReplacement(Ternary->getSourceRange(), Replacement);
343 }
344 
345 void SimplifyBooleanExprCheck::replaceWithReturnCondition(
346     const MatchFinder::MatchResult &Result, const IfStmt *If, bool Negated) {
347   StringRef Terminator = isa<CompoundStmt>(If->getElse()) ? ";" : "";
348   std::string Condition = replacementExpression(Result, Negated, If->getCond());
349   std::string Replacement = ("return " + Condition + Terminator).str();
350   SourceLocation Start =
351       Result.Nodes.getNodeAs<CXXBoolLiteralExpr>(ThenLiteralId)->getLocStart();
352   diag(Start, "redundant boolean literal in conditional return statement")
353       << FixItHint::CreateReplacement(If->getSourceRange(), Replacement);
354 }
355 
356 void SimplifyBooleanExprCheck::replaceWithAssignment(
357     const MatchFinder::MatchResult &Result, const IfStmt *IfAssign,
358     bool Negated) {
359   SourceRange Range = IfAssign->getSourceRange();
360   StringRef VariableName =
361       getText(Result, *Result.Nodes.getNodeAs<Expr>(IfAssignVariableId));
362   StringRef Terminator = isa<CompoundStmt>(IfAssign->getElse()) ? ";" : "";
363   std::string Condition =
364       replacementExpression(Result, Negated, IfAssign->getCond());
365   std::string Replacement =
366       (VariableName + " = " + Condition + Terminator).str();
367   SourceLocation Location =
368       Result.Nodes.getNodeAs<CXXBoolLiteralExpr>(IfAssignLocId)->getLocStart();
369   this->diag(Location, "redundant boolean literal in conditional assignment")
370       << FixItHint::CreateReplacement(Range, Replacement);
371 }
372 
373 } // namespace readability
374 } // namespace tidy
375 } // namespace clang
376