blob: 559b085bef9657c4cb8c7763b87642a0395525d2 [file] [log] [blame]
Alexander Kornienkof5e72b02015-04-10 19:26:43 +00001//===--- 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"
Alexander Kornienko7009d652017-05-15 17:06:51 +000011#include "clang/AST/RecursiveASTVisitor.h"
Alexander Kornienkof5e72b02015-04-10 19:26:43 +000012#include "clang/Lex/Lexer.h"
13
14#include <cassert>
15#include <string>
Alexander Kornienko6ae400d2015-07-01 12:39:40 +000016#include <utility>
Alexander Kornienkof5e72b02015-04-10 19:26:43 +000017
18using namespace clang::ast_matchers;
19
20namespace clang {
21namespace tidy {
22namespace readability {
23
24namespace {
25
26StringRef getText(const MatchFinder::MatchResult &Result, SourceRange Range) {
27 return Lexer::getSourceText(CharSourceRange::getTokenRange(Range),
28 *Result.SourceManager,
29 Result.Context->getLangOpts());
30}
31
32template <typename T>
33StringRef getText(const MatchFinder::MatchResult &Result, T &Node) {
34 return getText(Result, Node.getSourceRange());
35}
36
Alexander Kornienkof5e72b02015-04-10 19:26:43 +000037const char ConditionThenStmtId[] = "if-bool-yields-then";
38const char ConditionElseStmtId[] = "if-bool-yields-else";
39const char TernaryId[] = "ternary-bool-yields-condition";
40const char TernaryNegatedId[] = "ternary-bool-yields-not-condition";
41const char IfReturnsBoolId[] = "if-return";
42const char IfReturnsNotBoolId[] = "if-not-return";
43const char ThenLiteralId[] = "then-literal";
44const char IfAssignVariableId[] = "if-assign-lvalue";
45const char IfAssignLocId[] = "if-assign-loc";
46const char IfAssignBoolId[] = "if-assign";
47const char IfAssignNotBoolId[] = "if-assign-not";
48const char IfAssignObjId[] = "if-assign-obj";
Alexander Kornienko6ae400d2015-07-01 12:39:40 +000049const char CompoundReturnId[] = "compound-return";
50const char CompoundBoolId[] = "compound-bool";
51const char CompoundNotBoolId[] = "compound-bool-not";
Alexander Kornienkof5e72b02015-04-10 19:26:43 +000052
53const char IfStmtId[] = "if";
Alexander Kornienkof5e72b02015-04-10 19:26:43 +000054
55const char SimplifyOperatorDiagnostic[] =
56 "redundant boolean literal supplied to boolean operator";
57const char SimplifyConditionDiagnostic[] =
58 "redundant boolean literal in if statement condition";
Alexander Kornienko6ae400d2015-07-01 12:39:40 +000059const char SimplifyConditionalReturnDiagnostic[] =
60 "redundant boolean literal in conditional return statement";
Alexander Kornienkof5e72b02015-04-10 19:26:43 +000061
62const CXXBoolLiteralExpr *getBoolLiteral(const MatchFinder::MatchResult &Result,
63 StringRef Id) {
64 const auto *Literal = Result.Nodes.getNodeAs<CXXBoolLiteralExpr>(Id);
Alexander Kornienkoac4fe482017-11-29 17:16:09 +000065 return (Literal && Literal->getLocStart().isMacroID()) ? nullptr : Literal;
Alexander Kornienkof5e72b02015-04-10 19:26:43 +000066}
67
Alexander Kornienko6ae400d2015-07-01 12:39:40 +000068internal::Matcher<Stmt> returnsBool(bool Value, StringRef Id = "ignored") {
69 auto SimpleReturnsBool =
Aaron Ballmanb9ea09c2015-09-17 13:31:25 +000070 returnStmt(has(cxxBoolLiteral(equals(Value)).bind(Id)))
71 .bind("returns-bool");
Alexander Kornienkof5e72b02015-04-10 19:26:43 +000072 return anyOf(SimpleReturnsBool,
73 compoundStmt(statementCountIs(1), has(SimpleReturnsBool)));
74}
75
76bool needsParensAfterUnaryNegation(const Expr *E) {
Alexander Kornienko6ae400d2015-07-01 12:39:40 +000077 E = E->IgnoreImpCasts();
Alexander Kornienkof5e72b02015-04-10 19:26:43 +000078 if (isa<BinaryOperator>(E) || isa<ConditionalOperator>(E))
79 return true;
Aaron Ballmanf034a8c2016-02-12 15:09:05 +000080
81 if (const auto *Op = dyn_cast<CXXOperatorCallExpr>(E))
Alexander Kornienkof5e72b02015-04-10 19:26:43 +000082 return Op->getNumArgs() == 2 && Op->getOperator() != OO_Call &&
83 Op->getOperator() != OO_Subscript;
Aaron Ballmanf034a8c2016-02-12 15:09:05 +000084
Alexander Kornienkof5e72b02015-04-10 19:26:43 +000085 return false;
86}
87
88std::pair<BinaryOperatorKind, BinaryOperatorKind> Opposites[] = {
Alexander Kornienko6ae400d2015-07-01 12:39:40 +000089 {BO_LT, BO_GE}, {BO_GT, BO_LE}, {BO_EQ, BO_NE}};
Alexander Kornienkof5e72b02015-04-10 19:26:43 +000090
91StringRef negatedOperator(const BinaryOperator *BinOp) {
92 const BinaryOperatorKind Opcode = BinOp->getOpcode();
93 for (auto NegatableOp : Opposites) {
94 if (Opcode == NegatableOp.first)
95 return BinOp->getOpcodeStr(NegatableOp.second);
96 if (Opcode == NegatableOp.second)
97 return BinOp->getOpcodeStr(NegatableOp.first);
98 }
99 return StringRef();
100}
101
Alexander Kornienko6ae400d2015-07-01 12:39:40 +0000102std::pair<OverloadedOperatorKind, StringRef> OperatorNames[] = {
Aaron Ballmanf034a8c2016-02-12 15:09:05 +0000103 {OO_EqualEqual, "=="}, {OO_ExclaimEqual, "!="}, {OO_Less, "<"},
104 {OO_GreaterEqual, ">="}, {OO_Greater, ">"}, {OO_LessEqual, "<="}};
Alexander Kornienko6ae400d2015-07-01 12:39:40 +0000105
106StringRef getOperatorName(OverloadedOperatorKind OpKind) {
107 for (auto Name : OperatorNames) {
108 if (Name.first == OpKind)
109 return Name.second;
Alexander Kornienkof5e72b02015-04-10 19:26:43 +0000110 }
Alexander Kornienko6ae400d2015-07-01 12:39:40 +0000111
112 return StringRef();
113}
114
115std::pair<OverloadedOperatorKind, OverloadedOperatorKind> OppositeOverloads[] =
116 {{OO_EqualEqual, OO_ExclaimEqual},
117 {OO_Less, OO_GreaterEqual},
118 {OO_Greater, OO_LessEqual}};
119
120StringRef negatedOperator(const CXXOperatorCallExpr *OpCall) {
121 const OverloadedOperatorKind Opcode = OpCall->getOperator();
122 for (auto NegatableOp : OppositeOverloads) {
123 if (Opcode == NegatableOp.first)
124 return getOperatorName(NegatableOp.second);
125 if (Opcode == NegatableOp.second)
126 return getOperatorName(NegatableOp.first);
127 }
128 return StringRef();
129}
130
131std::string asBool(StringRef text, bool NeedsStaticCast) {
132 if (NeedsStaticCast)
133 return ("static_cast<bool>(" + text + ")").str();
134
135 return text;
136}
137
138bool needsNullPtrComparison(const Expr *E) {
139 if (const auto *ImpCast = dyn_cast<ImplicitCastExpr>(E))
Aaron Ballmanf034a8c2016-02-12 15:09:05 +0000140 return ImpCast->getCastKind() == CK_PointerToBoolean ||
141 ImpCast->getCastKind() == CK_MemberPointerToBoolean;
142
143 return false;
144}
145
146bool needsZeroComparison(const Expr *E) {
147 if (const auto *ImpCast = dyn_cast<ImplicitCastExpr>(E))
148 return ImpCast->getCastKind() == CK_IntegralToBoolean;
Alexander Kornienko6ae400d2015-07-01 12:39:40 +0000149
150 return false;
151}
152
153bool needsStaticCast(const Expr *E) {
154 if (const auto *ImpCast = dyn_cast<ImplicitCastExpr>(E)) {
155 if (ImpCast->getCastKind() == CK_UserDefinedConversion &&
156 ImpCast->getSubExpr()->getType()->isBooleanType()) {
157 if (const auto *MemCall =
158 dyn_cast<CXXMemberCallExpr>(ImpCast->getSubExpr())) {
159 if (const auto *MemDecl =
160 dyn_cast<CXXConversionDecl>(MemCall->getMethodDecl())) {
161 if (MemDecl->isExplicit())
162 return true;
163 }
Alexander Kornienkof5e72b02015-04-10 19:26:43 +0000164 }
165 }
166 }
Alexander Kornienko6ae400d2015-07-01 12:39:40 +0000167
168 E = E->IgnoreImpCasts();
169 return !E->getType()->isBooleanType();
170}
171
Aaron Ballmanf034a8c2016-02-12 15:09:05 +0000172std::string compareExpressionToConstant(const MatchFinder::MatchResult &Result,
173 const Expr *E, bool Negated,
174 const char *Constant) {
175 E = E->IgnoreImpCasts();
176 const std::string ExprText =
177 (isa<BinaryOperator>(E) ? ("(" + getText(Result, *E) + ")")
178 : getText(Result, *E))
179 .str();
180 return ExprText + " " + (Negated ? "!=" : "==") + " " + Constant;
181}
182
183std::string compareExpressionToNullPtr(const MatchFinder::MatchResult &Result,
184 const Expr *E, bool Negated) {
185 const char *NullPtr =
186 Result.Context->getLangOpts().CPlusPlus11 ? "nullptr" : "NULL";
187 return compareExpressionToConstant(Result, E, Negated, NullPtr);
188}
189
190std::string compareExpressionToZero(const MatchFinder::MatchResult &Result,
191 const Expr *E, bool Negated) {
192 return compareExpressionToConstant(Result, E, Negated, "0");
193}
194
Alexander Kornienko6ae400d2015-07-01 12:39:40 +0000195std::string replacementExpression(const MatchFinder::MatchResult &Result,
196 bool Negated, const Expr *E) {
197 E = E->ignoreParenBaseCasts();
198 const bool NeedsStaticCast = needsStaticCast(E);
199 if (Negated) {
200 if (const auto *UnOp = dyn_cast<UnaryOperator>(E)) {
201 if (UnOp->getOpcode() == UO_LNot) {
202 if (needsNullPtrComparison(UnOp->getSubExpr()))
Aaron Ballmanf034a8c2016-02-12 15:09:05 +0000203 return compareExpressionToNullPtr(Result, UnOp->getSubExpr(), true);
204
205 if (needsZeroComparison(UnOp->getSubExpr()))
206 return compareExpressionToZero(Result, UnOp->getSubExpr(), true);
Alexander Kornienko6ae400d2015-07-01 12:39:40 +0000207
208 return replacementExpression(Result, false, UnOp->getSubExpr());
209 }
210 }
211
212 if (needsNullPtrComparison(E))
Aaron Ballmanf034a8c2016-02-12 15:09:05 +0000213 return compareExpressionToNullPtr(Result, E, false);
214
215 if (needsZeroComparison(E))
216 return compareExpressionToZero(Result, E, false);
Alexander Kornienko6ae400d2015-07-01 12:39:40 +0000217
218 StringRef NegatedOperator;
219 const Expr *LHS = nullptr;
220 const Expr *RHS = nullptr;
221 if (const auto *BinOp = dyn_cast<BinaryOperator>(E)) {
222 NegatedOperator = negatedOperator(BinOp);
223 LHS = BinOp->getLHS();
224 RHS = BinOp->getRHS();
225 } else if (const auto *OpExpr = dyn_cast<CXXOperatorCallExpr>(E)) {
226 if (OpExpr->getNumArgs() == 2) {
227 NegatedOperator = negatedOperator(OpExpr);
228 LHS = OpExpr->getArg(0);
229 RHS = OpExpr->getArg(1);
230 }
231 }
Aaron Ballmanf034a8c2016-02-12 15:09:05 +0000232 if (!NegatedOperator.empty() && LHS && RHS)
Alexander Kornienko6ae400d2015-07-01 12:39:40 +0000233 return (asBool((getText(Result, *LHS) + " " + NegatedOperator + " " +
Aaron Ballmanf034a8c2016-02-12 15:09:05 +0000234 getText(Result, *RHS))
235 .str(),
Alexander Kornienko6ae400d2015-07-01 12:39:40 +0000236 NeedsStaticCast));
Alexander Kornienko6ae400d2015-07-01 12:39:40 +0000237
238 StringRef Text = getText(Result, *E);
239 if (!NeedsStaticCast && needsParensAfterUnaryNegation(E))
240 return ("!(" + Text + ")").str();
241
242 if (needsNullPtrComparison(E))
Aaron Ballmanf034a8c2016-02-12 15:09:05 +0000243 return compareExpressionToNullPtr(Result, E, false);
244
245 if (needsZeroComparison(E))
246 return compareExpressionToZero(Result, E, false);
Alexander Kornienko6ae400d2015-07-01 12:39:40 +0000247
248 return ("!" + asBool(Text, NeedsStaticCast));
249 }
250
251 if (const auto *UnOp = dyn_cast<UnaryOperator>(E)) {
252 if (UnOp->getOpcode() == UO_LNot) {
253 if (needsNullPtrComparison(UnOp->getSubExpr()))
Aaron Ballmanf034a8c2016-02-12 15:09:05 +0000254 return compareExpressionToNullPtr(Result, UnOp->getSubExpr(), false);
255
256 if (needsZeroComparison(UnOp->getSubExpr()))
257 return compareExpressionToZero(Result, UnOp->getSubExpr(), false);
Alexander Kornienko6ae400d2015-07-01 12:39:40 +0000258 }
259 }
260
261 if (needsNullPtrComparison(E))
Aaron Ballmanf034a8c2016-02-12 15:09:05 +0000262 return compareExpressionToNullPtr(Result, E, true);
263
264 if (needsZeroComparison(E))
265 return compareExpressionToZero(Result, E, true);
Alexander Kornienko6ae400d2015-07-01 12:39:40 +0000266
267 return asBool(getText(Result, *E), NeedsStaticCast);
268}
269
270const CXXBoolLiteralExpr *stmtReturnsBool(const ReturnStmt *Ret, bool Negated) {
271 if (const auto *Bool = dyn_cast<CXXBoolLiteralExpr>(Ret->getRetValue())) {
272 if (Bool->getValue() == !Negated)
273 return Bool;
274 }
275
276 return nullptr;
277}
278
279const CXXBoolLiteralExpr *stmtReturnsBool(const IfStmt *IfRet, bool Negated) {
280 if (IfRet->getElse() != nullptr)
281 return nullptr;
282
283 if (const auto *Ret = dyn_cast<ReturnStmt>(IfRet->getThen()))
284 return stmtReturnsBool(Ret, Negated);
285
286 if (const auto *Compound = dyn_cast<CompoundStmt>(IfRet->getThen())) {
287 if (Compound->size() == 1) {
288 if (const auto *CompoundRet = dyn_cast<ReturnStmt>(Compound->body_back()))
289 return stmtReturnsBool(CompoundRet, Negated);
290 }
291 }
292
293 return nullptr;
Alexander Kornienkof5e72b02015-04-10 19:26:43 +0000294}
295
Aaron Ballmanf034a8c2016-02-12 15:09:05 +0000296bool containsDiscardedTokens(const MatchFinder::MatchResult &Result,
297 CharSourceRange CharRange) {
298 std::string ReplacementText =
299 Lexer::getSourceText(CharRange, *Result.SourceManager,
300 Result.Context->getLangOpts())
301 .str();
302 Lexer Lex(CharRange.getBegin(), Result.Context->getLangOpts(),
303 ReplacementText.data(), ReplacementText.data(),
304 ReplacementText.data() + ReplacementText.size());
305 Lex.SetCommentRetentionState(true);
306
307 Token Tok;
308 while (!Lex.LexFromRawLexer(Tok)) {
309 if (Tok.is(tok::TokenKind::comment) || Tok.is(tok::TokenKind::hash))
310 return true;
311 }
312
313 return false;
314}
315
Alexander Kornienkof5e72b02015-04-10 19:26:43 +0000316} // namespace
317
Alexander Kornienko7009d652017-05-15 17:06:51 +0000318class SimplifyBooleanExprCheck::Visitor : public RecursiveASTVisitor<Visitor> {
319 using Base = RecursiveASTVisitor<Visitor>;
320
321 public:
322 Visitor(SimplifyBooleanExprCheck *Check,
323 const MatchFinder::MatchResult &Result)
324 : Check(Check), Result(Result) {}
325
326 bool VisitBinaryOperator(BinaryOperator *Op) {
327 Check->reportBinOp(Result, Op);
328 return true;
329 }
330
331 private:
332 SimplifyBooleanExprCheck *Check;
333 const MatchFinder::MatchResult &Result;
334};
335
336
Alexander Kornienkofb3e2cd2015-05-17 12:31:12 +0000337SimplifyBooleanExprCheck::SimplifyBooleanExprCheck(StringRef Name,
338 ClangTidyContext *Context)
339 : ClangTidyCheck(Name, Context),
340 ChainedConditionalReturn(Options.get("ChainedConditionalReturn", 0U)),
341 ChainedConditionalAssignment(
342 Options.get("ChainedConditionalAssignment", 0U)) {}
343
Alexander Kornienko7009d652017-05-15 17:06:51 +0000344bool containsBoolLiteral(const Expr *E) {
345 if (!E)
346 return false;
347 E = E->IgnoreParenImpCasts();
348 if (isa<CXXBoolLiteralExpr>(E))
349 return true;
350 if (const auto *BinOp = dyn_cast<BinaryOperator>(E))
351 return containsBoolLiteral(BinOp->getLHS()) ||
352 containsBoolLiteral(BinOp->getRHS());
353 if (const auto *UnaryOp = dyn_cast<UnaryOperator>(E))
354 return containsBoolLiteral(UnaryOp->getSubExpr());
355 return false;
Alexander Kornienkof5e72b02015-04-10 19:26:43 +0000356}
357
Alexander Kornienko7009d652017-05-15 17:06:51 +0000358void SimplifyBooleanExprCheck::reportBinOp(
359 const MatchFinder::MatchResult &Result, const BinaryOperator *Op) {
360 const auto *LHS = Op->getLHS()->IgnoreParenImpCasts();
361 const auto *RHS = Op->getRHS()->IgnoreParenImpCasts();
Alexander Kornienkof5e72b02015-04-10 19:26:43 +0000362
Alexander Kornienko7009d652017-05-15 17:06:51 +0000363 const CXXBoolLiteralExpr *Bool = nullptr;
364 const Expr *Other = nullptr;
365 if ((Bool = dyn_cast<CXXBoolLiteralExpr>(LHS)))
366 Other = RHS;
367 else if ((Bool = dyn_cast<CXXBoolLiteralExpr>(RHS)))
368 Other = LHS;
369 else
370 return;
Alexander Kornienkof5e72b02015-04-10 19:26:43 +0000371
Alexander Kornienko7009d652017-05-15 17:06:51 +0000372 if (Bool->getLocStart().isMacroID())
373 return;
374
375 // FIXME: why do we need this?
376 if (!isa<CXXBoolLiteralExpr>(Other) && containsBoolLiteral(Other))
377 return;
378
379 bool BoolValue = Bool->getValue();
380
381 auto replaceWithExpression = [this, &Result, LHS, RHS, Bool](
382 const Expr *ReplaceWith, bool Negated) {
383 std::string Replacement =
384 replacementExpression(Result, Negated, ReplaceWith);
385 SourceRange Range(LHS->getLocStart(), RHS->getLocEnd());
386 issueDiag(Result, Bool->getLocStart(), SimplifyOperatorDiagnostic, Range,
387 Replacement);
388 };
389
390 switch (Op->getOpcode()) {
391 case BO_LAnd:
392 if (BoolValue) {
393 // expr && true -> expr
394 replaceWithExpression(Other, /*Negated=*/false);
395 } else {
396 // expr && false -> false
397 replaceWithExpression(Bool, /*Negated=*/false);
398 }
399 break;
400 case BO_LOr:
401 if (BoolValue) {
402 // expr || true -> true
403 replaceWithExpression(Bool, /*Negated=*/false);
404 } else {
405 // expr || false -> expr
406 replaceWithExpression(Other, /*Negated=*/false);
407 }
408 break;
409 case BO_EQ:
410 // expr == true -> expr, expr == false -> !expr
411 replaceWithExpression(Other, /*Negated=*/!BoolValue);
412 break;
413 case BO_NE:
414 // expr != true -> !expr, expr != false -> expr
415 replaceWithExpression(Other, /*Negated=*/BoolValue);
416 break;
417 default:
418 break;
419 }
Alexander Kornienkof5e72b02015-04-10 19:26:43 +0000420}
421
422void SimplifyBooleanExprCheck::matchBoolCondition(MatchFinder *Finder,
423 bool Value,
424 StringRef BooleanId) {
Aaron Ballmanf034a8c2016-02-12 15:09:05 +0000425 Finder->addMatcher(
426 ifStmt(isExpansionInMainFile(),
427 hasCondition(cxxBoolLiteral(equals(Value)).bind(BooleanId)))
428 .bind(IfStmtId),
429 this);
Alexander Kornienkof5e72b02015-04-10 19:26:43 +0000430}
431
432void SimplifyBooleanExprCheck::matchTernaryResult(MatchFinder *Finder,
433 bool Value,
434 StringRef TernaryId) {
435 Finder->addMatcher(
436 conditionalOperator(isExpansionInMainFile(),
Aaron Ballmanb9ea09c2015-09-17 13:31:25 +0000437 hasTrueExpression(cxxBoolLiteral(equals(Value))),
438 hasFalseExpression(cxxBoolLiteral(equals(!Value))))
Alexander Kornienkof5e72b02015-04-10 19:26:43 +0000439 .bind(TernaryId),
440 this);
441}
442
443void SimplifyBooleanExprCheck::matchIfReturnsBool(MatchFinder *Finder,
444 bool Value, StringRef Id) {
Aaron Ballmanf034a8c2016-02-12 15:09:05 +0000445 if (ChainedConditionalReturn)
Alexander Kornienkofb3e2cd2015-05-17 12:31:12 +0000446 Finder->addMatcher(ifStmt(isExpansionInMainFile(),
Alexander Kornienko6ae400d2015-07-01 12:39:40 +0000447 hasThen(returnsBool(Value, ThenLiteralId)),
Aaron Ballmanf034a8c2016-02-12 15:09:05 +0000448 hasElse(returnsBool(!Value)))
449 .bind(Id),
Alexander Kornienkofb3e2cd2015-05-17 12:31:12 +0000450 this);
Aaron Ballmanf034a8c2016-02-12 15:09:05 +0000451 else
Alexander Kornienkofb3e2cd2015-05-17 12:31:12 +0000452 Finder->addMatcher(ifStmt(isExpansionInMainFile(),
453 unless(hasParent(ifStmt())),
Alexander Kornienko6ae400d2015-07-01 12:39:40 +0000454 hasThen(returnsBool(Value, ThenLiteralId)),
Aaron Ballmanf034a8c2016-02-12 15:09:05 +0000455 hasElse(returnsBool(!Value)))
456 .bind(Id),
Alexander Kornienkofb3e2cd2015-05-17 12:31:12 +0000457 this);
Alexander Kornienkof5e72b02015-04-10 19:26:43 +0000458}
459
460void SimplifyBooleanExprCheck::matchIfAssignsBool(MatchFinder *Finder,
461 bool Value, StringRef Id) {
462 auto SimpleThen = binaryOperator(
463 hasOperatorName("="),
464 hasLHS(declRefExpr(hasDeclaration(decl().bind(IfAssignObjId)))),
465 hasLHS(expr().bind(IfAssignVariableId)),
Aaron Ballmanb9ea09c2015-09-17 13:31:25 +0000466 hasRHS(cxxBoolLiteral(equals(Value)).bind(IfAssignLocId)));
Alexander Kornienkof5e72b02015-04-10 19:26:43 +0000467 auto Then = anyOf(SimpleThen, compoundStmt(statementCountIs(1),
468 hasAnySubstatement(SimpleThen)));
469 auto SimpleElse = binaryOperator(
470 hasOperatorName("="),
471 hasLHS(declRefExpr(hasDeclaration(equalsBoundNode(IfAssignObjId)))),
Aaron Ballmanb9ea09c2015-09-17 13:31:25 +0000472 hasRHS(cxxBoolLiteral(equals(!Value))));
Alexander Kornienkof5e72b02015-04-10 19:26:43 +0000473 auto Else = anyOf(SimpleElse, compoundStmt(statementCountIs(1),
474 hasAnySubstatement(SimpleElse)));
Aaron Ballmanf034a8c2016-02-12 15:09:05 +0000475 if (ChainedConditionalAssignment)
Alexander Kornienkofb3e2cd2015-05-17 12:31:12 +0000476 Finder->addMatcher(
477 ifStmt(isExpansionInMainFile(), hasThen(Then), hasElse(Else)).bind(Id),
478 this);
Aaron Ballmanf034a8c2016-02-12 15:09:05 +0000479 else
Alexander Kornienkofb3e2cd2015-05-17 12:31:12 +0000480 Finder->addMatcher(ifStmt(isExpansionInMainFile(),
481 unless(hasParent(ifStmt())), hasThen(Then),
Aaron Ballmanf034a8c2016-02-12 15:09:05 +0000482 hasElse(Else))
483 .bind(Id),
Alexander Kornienkofb3e2cd2015-05-17 12:31:12 +0000484 this);
Alexander Kornienkofb3e2cd2015-05-17 12:31:12 +0000485}
486
Alexander Kornienko6ae400d2015-07-01 12:39:40 +0000487void SimplifyBooleanExprCheck::matchCompoundIfReturnsBool(MatchFinder *Finder,
488 bool Value,
489 StringRef Id) {
490 Finder->addMatcher(
Aaron Ballmanb9ea09c2015-09-17 13:31:25 +0000491 compoundStmt(allOf(hasAnySubstatement(ifStmt(hasThen(returnsBool(Value)),
492 unless(hasElse(stmt())))),
493 hasAnySubstatement(
Piotr Padlewskie93a73f2016-05-31 15:26:56 +0000494 returnStmt(has(ignoringParenImpCasts(
495 cxxBoolLiteral(equals(!Value)))))
Aaron Ballmanf034a8c2016-02-12 15:09:05 +0000496 .bind(CompoundReturnId))))
497 .bind(Id),
Alexander Kornienko6ae400d2015-07-01 12:39:40 +0000498 this);
499}
500
Alexander Kornienkofb3e2cd2015-05-17 12:31:12 +0000501void SimplifyBooleanExprCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
502 Options.store(Opts, "ChainedConditionalReturn", ChainedConditionalReturn);
503 Options.store(Opts, "ChainedConditionalAssignment",
504 ChainedConditionalAssignment);
Alexander Kornienkof5e72b02015-04-10 19:26:43 +0000505}
506
507void SimplifyBooleanExprCheck::registerMatchers(MatchFinder *Finder) {
Alexander Kornienko7009d652017-05-15 17:06:51 +0000508 Finder->addMatcher(translationUnitDecl().bind("top"), this);
Alexander Kornienkof5e72b02015-04-10 19:26:43 +0000509
510 matchBoolCondition(Finder, true, ConditionThenStmtId);
511 matchBoolCondition(Finder, false, ConditionElseStmtId);
512
513 matchTernaryResult(Finder, true, TernaryId);
514 matchTernaryResult(Finder, false, TernaryNegatedId);
515
516 matchIfReturnsBool(Finder, true, IfReturnsBoolId);
517 matchIfReturnsBool(Finder, false, IfReturnsNotBoolId);
518
519 matchIfAssignsBool(Finder, true, IfAssignBoolId);
520 matchIfAssignsBool(Finder, false, IfAssignNotBoolId);
Alexander Kornienko6ae400d2015-07-01 12:39:40 +0000521
522 matchCompoundIfReturnsBool(Finder, true, CompoundBoolId);
523 matchCompoundIfReturnsBool(Finder, false, CompoundNotBoolId);
Alexander Kornienkof5e72b02015-04-10 19:26:43 +0000524}
525
526void SimplifyBooleanExprCheck::check(const MatchFinder::MatchResult &Result) {
Alexander Kornienko7009d652017-05-15 17:06:51 +0000527 if (const CXXBoolLiteralExpr *TrueConditionRemoved =
528 getBoolLiteral(Result, ConditionThenStmtId))
Alexander Kornienkof5e72b02015-04-10 19:26:43 +0000529 replaceWithThenStatement(Result, TrueConditionRemoved);
Aaron Ballmanf034a8c2016-02-12 15:09:05 +0000530 else if (const CXXBoolLiteralExpr *FalseConditionRemoved =
531 getBoolLiteral(Result, ConditionElseStmtId))
Alexander Kornienkof5e72b02015-04-10 19:26:43 +0000532 replaceWithElseStatement(Result, FalseConditionRemoved);
Aaron Ballmanf034a8c2016-02-12 15:09:05 +0000533 else if (const auto *Ternary =
534 Result.Nodes.getNodeAs<ConditionalOperator>(TernaryId))
Alexander Kornienkof5e72b02015-04-10 19:26:43 +0000535 replaceWithCondition(Result, Ternary);
Aaron Ballmanf034a8c2016-02-12 15:09:05 +0000536 else if (const auto *TernaryNegated =
537 Result.Nodes.getNodeAs<ConditionalOperator>(TernaryNegatedId))
Alexander Kornienkof5e72b02015-04-10 19:26:43 +0000538 replaceWithCondition(Result, TernaryNegated, true);
Aaron Ballmanf034a8c2016-02-12 15:09:05 +0000539 else if (const auto *If = Result.Nodes.getNodeAs<IfStmt>(IfReturnsBoolId))
Alexander Kornienkof5e72b02015-04-10 19:26:43 +0000540 replaceWithReturnCondition(Result, If);
Aaron Ballmanf034a8c2016-02-12 15:09:05 +0000541 else if (const auto *IfNot =
542 Result.Nodes.getNodeAs<IfStmt>(IfReturnsNotBoolId))
Alexander Kornienkof5e72b02015-04-10 19:26:43 +0000543 replaceWithReturnCondition(Result, IfNot, true);
Aaron Ballmanf034a8c2016-02-12 15:09:05 +0000544 else if (const auto *IfAssign =
545 Result.Nodes.getNodeAs<IfStmt>(IfAssignBoolId))
Alexander Kornienkof5e72b02015-04-10 19:26:43 +0000546 replaceWithAssignment(Result, IfAssign);
Aaron Ballmanf034a8c2016-02-12 15:09:05 +0000547 else if (const auto *IfAssignNot =
548 Result.Nodes.getNodeAs<IfStmt>(IfAssignNotBoolId))
Alexander Kornienkof5e72b02015-04-10 19:26:43 +0000549 replaceWithAssignment(Result, IfAssignNot, true);
Aaron Ballmanf034a8c2016-02-12 15:09:05 +0000550 else if (const auto *Compound =
551 Result.Nodes.getNodeAs<CompoundStmt>(CompoundBoolId))
Alexander Kornienko6ae400d2015-07-01 12:39:40 +0000552 replaceCompoundReturnWithCondition(Result, Compound);
Aaron Ballmanf034a8c2016-02-12 15:09:05 +0000553 else if (const auto *Compound =
554 Result.Nodes.getNodeAs<CompoundStmt>(CompoundNotBoolId))
Alexander Kornienko6ae400d2015-07-01 12:39:40 +0000555 replaceCompoundReturnWithCondition(Result, Compound, true);
Alexander Kornienko7009d652017-05-15 17:06:51 +0000556 else if (const auto TU = Result.Nodes.getNodeAs<Decl>("top"))
557 Visitor(this, Result).TraverseDecl(const_cast<Decl*>(TU));
Alexander Kornienko4f74ec02015-12-28 13:21:22 +0000558}
559
560void SimplifyBooleanExprCheck::issueDiag(
561 const ast_matchers::MatchFinder::MatchResult &Result, SourceLocation Loc,
562 StringRef Description, SourceRange ReplacementRange,
563 StringRef Replacement) {
Gabor Horvathafad84c2016-09-24 02:13:45 +0000564 CharSourceRange CharRange =
565 Lexer::makeFileCharRange(CharSourceRange::getTokenRange(ReplacementRange),
566 *Result.SourceManager, getLangOpts());
Alexander Kornienko4f74ec02015-12-28 13:21:22 +0000567
568 DiagnosticBuilder Diag = diag(Loc, Description);
569 if (!containsDiscardedTokens(Result, CharRange))
570 Diag << FixItHint::CreateReplacement(CharRange, Replacement);
571}
572
Alexander Kornienkof5e72b02015-04-10 19:26:43 +0000573void SimplifyBooleanExprCheck::replaceWithThenStatement(
574 const MatchFinder::MatchResult &Result,
575 const CXXBoolLiteralExpr *TrueConditionRemoved) {
576 const auto *IfStatement = Result.Nodes.getNodeAs<IfStmt>(IfStmtId);
Alexander Kornienko4f74ec02015-12-28 13:21:22 +0000577 issueDiag(Result, TrueConditionRemoved->getLocStart(),
578 SimplifyConditionDiagnostic, IfStatement->getSourceRange(),
579 getText(Result, *IfStatement->getThen()));
Alexander Kornienkof5e72b02015-04-10 19:26:43 +0000580}
581
582void SimplifyBooleanExprCheck::replaceWithElseStatement(
583 const MatchFinder::MatchResult &Result,
584 const CXXBoolLiteralExpr *FalseConditionRemoved) {
585 const auto *IfStatement = Result.Nodes.getNodeAs<IfStmt>(IfStmtId);
586 const Stmt *ElseStatement = IfStatement->getElse();
Alexander Kornienko4f74ec02015-12-28 13:21:22 +0000587 issueDiag(Result, FalseConditionRemoved->getLocStart(),
588 SimplifyConditionDiagnostic, IfStatement->getSourceRange(),
589 ElseStatement ? getText(Result, *ElseStatement) : "");
Alexander Kornienkof5e72b02015-04-10 19:26:43 +0000590}
591
592void SimplifyBooleanExprCheck::replaceWithCondition(
593 const MatchFinder::MatchResult &Result, const ConditionalOperator *Ternary,
594 bool Negated) {
595 std::string Replacement =
596 replacementExpression(Result, Negated, Ternary->getCond());
Alexander Kornienko4f74ec02015-12-28 13:21:22 +0000597 issueDiag(Result, Ternary->getTrueExpr()->getLocStart(),
598 "redundant boolean literal in ternary expression result",
599 Ternary->getSourceRange(), Replacement);
Alexander Kornienkof5e72b02015-04-10 19:26:43 +0000600}
601
602void SimplifyBooleanExprCheck::replaceWithReturnCondition(
603 const MatchFinder::MatchResult &Result, const IfStmt *If, bool Negated) {
604 StringRef Terminator = isa<CompoundStmt>(If->getElse()) ? ";" : "";
605 std::string Condition = replacementExpression(Result, Negated, If->getCond());
606 std::string Replacement = ("return " + Condition + Terminator).str();
607 SourceLocation Start =
608 Result.Nodes.getNodeAs<CXXBoolLiteralExpr>(ThenLiteralId)->getLocStart();
Alexander Kornienko4f74ec02015-12-28 13:21:22 +0000609 issueDiag(Result, Start, SimplifyConditionalReturnDiagnostic,
610 If->getSourceRange(), Replacement);
Alexander Kornienkof5e72b02015-04-10 19:26:43 +0000611}
612
Alexander Kornienko6ae400d2015-07-01 12:39:40 +0000613void SimplifyBooleanExprCheck::replaceCompoundReturnWithCondition(
614 const MatchFinder::MatchResult &Result, const CompoundStmt *Compound,
615 bool Negated) {
616 const auto *Ret = Result.Nodes.getNodeAs<ReturnStmt>(CompoundReturnId);
617
618 // The body shouldn't be empty because the matcher ensures that it must
619 // contain at least two statements:
620 // 1) A `return` statement returning a boolean literal `false` or `true`
Aaron Ballmanf034a8c2016-02-12 15:09:05 +0000621 // 2) An `if` statement with no `else` clause that consists of a single
Alexander Kornienko6ae400d2015-07-01 12:39:40 +0000622 // `return` statement returning the opposite boolean literal `true` or
623 // `false`.
624 assert(Compound->size() >= 2);
625 const IfStmt *BeforeIf = nullptr;
626 CompoundStmt::const_body_iterator Current = Compound->body_begin();
627 CompoundStmt::const_body_iterator After = Compound->body_begin();
628 for (++After; After != Compound->body_end() && *Current != Ret;
629 ++Current, ++After) {
630 if (const auto *If = dyn_cast<IfStmt>(*Current)) {
631 if (const CXXBoolLiteralExpr *Lit = stmtReturnsBool(If, Negated)) {
632 if (*After == Ret) {
633 if (!ChainedConditionalReturn && BeforeIf)
634 continue;
635
636 const Expr *Condition = If->getCond();
637 std::string Replacement =
638 "return " + replacementExpression(Result, Negated, Condition);
Alexander Kornienko4f74ec02015-12-28 13:21:22 +0000639 issueDiag(
640 Result, Lit->getLocStart(), SimplifyConditionalReturnDiagnostic,
641 SourceRange(If->getLocStart(), Ret->getLocEnd()), Replacement);
Alexander Kornienko6ae400d2015-07-01 12:39:40 +0000642 return;
643 }
644
645 BeforeIf = If;
646 }
647 } else {
648 BeforeIf = nullptr;
649 }
650 }
651}
652
Alexander Kornienkof5e72b02015-04-10 19:26:43 +0000653void SimplifyBooleanExprCheck::replaceWithAssignment(
654 const MatchFinder::MatchResult &Result, const IfStmt *IfAssign,
655 bool Negated) {
656 SourceRange Range = IfAssign->getSourceRange();
657 StringRef VariableName =
658 getText(Result, *Result.Nodes.getNodeAs<Expr>(IfAssignVariableId));
659 StringRef Terminator = isa<CompoundStmt>(IfAssign->getElse()) ? ";" : "";
660 std::string Condition =
661 replacementExpression(Result, Negated, IfAssign->getCond());
662 std::string Replacement =
663 (VariableName + " = " + Condition + Terminator).str();
664 SourceLocation Location =
665 Result.Nodes.getNodeAs<CXXBoolLiteralExpr>(IfAssignLocId)->getLocStart();
Alexander Kornienko4f74ec02015-12-28 13:21:22 +0000666 issueDiag(Result, Location,
667 "redundant boolean literal in conditional assignment", Range,
668 Replacement);
Alexander Kornienkof5e72b02015-04-10 19:26:43 +0000669}
670
671} // namespace readability
672} // namespace tidy
673} // namespace clang