blob: 8934e7be58e21f088bd389c8301783782d2cc353 [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);
65 return (Literal &&
66 Result.SourceManager->isMacroBodyExpansion(Literal->getLocStart()))
67 ? nullptr
68 : Literal;
69}
70
Alexander Kornienko6ae400d2015-07-01 12:39:40 +000071internal::Matcher<Stmt> returnsBool(bool Value, StringRef Id = "ignored") {
72 auto SimpleReturnsBool =
Aaron Ballmanb9ea09c2015-09-17 13:31:25 +000073 returnStmt(has(cxxBoolLiteral(equals(Value)).bind(Id)))
74 .bind("returns-bool");
Alexander Kornienkof5e72b02015-04-10 19:26:43 +000075 return anyOf(SimpleReturnsBool,
76 compoundStmt(statementCountIs(1), has(SimpleReturnsBool)));
77}
78
79bool needsParensAfterUnaryNegation(const Expr *E) {
Alexander Kornienko6ae400d2015-07-01 12:39:40 +000080 E = E->IgnoreImpCasts();
Alexander Kornienkof5e72b02015-04-10 19:26:43 +000081 if (isa<BinaryOperator>(E) || isa<ConditionalOperator>(E))
82 return true;
Aaron Ballmanf034a8c2016-02-12 15:09:05 +000083
84 if (const auto *Op = dyn_cast<CXXOperatorCallExpr>(E))
Alexander Kornienkof5e72b02015-04-10 19:26:43 +000085 return Op->getNumArgs() == 2 && Op->getOperator() != OO_Call &&
86 Op->getOperator() != OO_Subscript;
Aaron Ballmanf034a8c2016-02-12 15:09:05 +000087
Alexander Kornienkof5e72b02015-04-10 19:26:43 +000088 return false;
89}
90
91std::pair<BinaryOperatorKind, BinaryOperatorKind> Opposites[] = {
Alexander Kornienko6ae400d2015-07-01 12:39:40 +000092 {BO_LT, BO_GE}, {BO_GT, BO_LE}, {BO_EQ, BO_NE}};
Alexander Kornienkof5e72b02015-04-10 19:26:43 +000093
94StringRef negatedOperator(const BinaryOperator *BinOp) {
95 const BinaryOperatorKind Opcode = BinOp->getOpcode();
96 for (auto NegatableOp : Opposites) {
97 if (Opcode == NegatableOp.first)
98 return BinOp->getOpcodeStr(NegatableOp.second);
99 if (Opcode == NegatableOp.second)
100 return BinOp->getOpcodeStr(NegatableOp.first);
101 }
102 return StringRef();
103}
104
Alexander Kornienko6ae400d2015-07-01 12:39:40 +0000105std::pair<OverloadedOperatorKind, StringRef> OperatorNames[] = {
Aaron Ballmanf034a8c2016-02-12 15:09:05 +0000106 {OO_EqualEqual, "=="}, {OO_ExclaimEqual, "!="}, {OO_Less, "<"},
107 {OO_GreaterEqual, ">="}, {OO_Greater, ">"}, {OO_LessEqual, "<="}};
Alexander Kornienko6ae400d2015-07-01 12:39:40 +0000108
109StringRef getOperatorName(OverloadedOperatorKind OpKind) {
110 for (auto Name : OperatorNames) {
111 if (Name.first == OpKind)
112 return Name.second;
Alexander Kornienkof5e72b02015-04-10 19:26:43 +0000113 }
Alexander Kornienko6ae400d2015-07-01 12:39:40 +0000114
115 return StringRef();
116}
117
118std::pair<OverloadedOperatorKind, OverloadedOperatorKind> OppositeOverloads[] =
119 {{OO_EqualEqual, OO_ExclaimEqual},
120 {OO_Less, OO_GreaterEqual},
121 {OO_Greater, OO_LessEqual}};
122
123StringRef negatedOperator(const CXXOperatorCallExpr *OpCall) {
124 const OverloadedOperatorKind Opcode = OpCall->getOperator();
125 for (auto NegatableOp : OppositeOverloads) {
126 if (Opcode == NegatableOp.first)
127 return getOperatorName(NegatableOp.second);
128 if (Opcode == NegatableOp.second)
129 return getOperatorName(NegatableOp.first);
130 }
131 return StringRef();
132}
133
134std::string asBool(StringRef text, bool NeedsStaticCast) {
135 if (NeedsStaticCast)
136 return ("static_cast<bool>(" + text + ")").str();
137
138 return text;
139}
140
141bool needsNullPtrComparison(const Expr *E) {
142 if (const auto *ImpCast = dyn_cast<ImplicitCastExpr>(E))
Aaron Ballmanf034a8c2016-02-12 15:09:05 +0000143 return ImpCast->getCastKind() == CK_PointerToBoolean ||
144 ImpCast->getCastKind() == CK_MemberPointerToBoolean;
145
146 return false;
147}
148
149bool needsZeroComparison(const Expr *E) {
150 if (const auto *ImpCast = dyn_cast<ImplicitCastExpr>(E))
151 return ImpCast->getCastKind() == CK_IntegralToBoolean;
Alexander Kornienko6ae400d2015-07-01 12:39:40 +0000152
153 return false;
154}
155
156bool needsStaticCast(const Expr *E) {
157 if (const auto *ImpCast = dyn_cast<ImplicitCastExpr>(E)) {
158 if (ImpCast->getCastKind() == CK_UserDefinedConversion &&
159 ImpCast->getSubExpr()->getType()->isBooleanType()) {
160 if (const auto *MemCall =
161 dyn_cast<CXXMemberCallExpr>(ImpCast->getSubExpr())) {
162 if (const auto *MemDecl =
163 dyn_cast<CXXConversionDecl>(MemCall->getMethodDecl())) {
164 if (MemDecl->isExplicit())
165 return true;
166 }
Alexander Kornienkof5e72b02015-04-10 19:26:43 +0000167 }
168 }
169 }
Alexander Kornienko6ae400d2015-07-01 12:39:40 +0000170
171 E = E->IgnoreImpCasts();
172 return !E->getType()->isBooleanType();
173}
174
Aaron Ballmanf034a8c2016-02-12 15:09:05 +0000175std::string compareExpressionToConstant(const MatchFinder::MatchResult &Result,
176 const Expr *E, bool Negated,
177 const char *Constant) {
178 E = E->IgnoreImpCasts();
179 const std::string ExprText =
180 (isa<BinaryOperator>(E) ? ("(" + getText(Result, *E) + ")")
181 : getText(Result, *E))
182 .str();
183 return ExprText + " " + (Negated ? "!=" : "==") + " " + Constant;
184}
185
186std::string compareExpressionToNullPtr(const MatchFinder::MatchResult &Result,
187 const Expr *E, bool Negated) {
188 const char *NullPtr =
189 Result.Context->getLangOpts().CPlusPlus11 ? "nullptr" : "NULL";
190 return compareExpressionToConstant(Result, E, Negated, NullPtr);
191}
192
193std::string compareExpressionToZero(const MatchFinder::MatchResult &Result,
194 const Expr *E, bool Negated) {
195 return compareExpressionToConstant(Result, E, Negated, "0");
196}
197
Alexander Kornienko6ae400d2015-07-01 12:39:40 +0000198std::string replacementExpression(const MatchFinder::MatchResult &Result,
199 bool Negated, const Expr *E) {
200 E = E->ignoreParenBaseCasts();
201 const bool NeedsStaticCast = needsStaticCast(E);
202 if (Negated) {
203 if (const auto *UnOp = dyn_cast<UnaryOperator>(E)) {
204 if (UnOp->getOpcode() == UO_LNot) {
205 if (needsNullPtrComparison(UnOp->getSubExpr()))
Aaron Ballmanf034a8c2016-02-12 15:09:05 +0000206 return compareExpressionToNullPtr(Result, UnOp->getSubExpr(), true);
207
208 if (needsZeroComparison(UnOp->getSubExpr()))
209 return compareExpressionToZero(Result, UnOp->getSubExpr(), true);
Alexander Kornienko6ae400d2015-07-01 12:39:40 +0000210
211 return replacementExpression(Result, false, UnOp->getSubExpr());
212 }
213 }
214
215 if (needsNullPtrComparison(E))
Aaron Ballmanf034a8c2016-02-12 15:09:05 +0000216 return compareExpressionToNullPtr(Result, E, false);
217
218 if (needsZeroComparison(E))
219 return compareExpressionToZero(Result, E, false);
Alexander Kornienko6ae400d2015-07-01 12:39:40 +0000220
221 StringRef NegatedOperator;
222 const Expr *LHS = nullptr;
223 const Expr *RHS = nullptr;
224 if (const auto *BinOp = dyn_cast<BinaryOperator>(E)) {
225 NegatedOperator = negatedOperator(BinOp);
226 LHS = BinOp->getLHS();
227 RHS = BinOp->getRHS();
228 } else if (const auto *OpExpr = dyn_cast<CXXOperatorCallExpr>(E)) {
229 if (OpExpr->getNumArgs() == 2) {
230 NegatedOperator = negatedOperator(OpExpr);
231 LHS = OpExpr->getArg(0);
232 RHS = OpExpr->getArg(1);
233 }
234 }
Aaron Ballmanf034a8c2016-02-12 15:09:05 +0000235 if (!NegatedOperator.empty() && LHS && RHS)
Alexander Kornienko6ae400d2015-07-01 12:39:40 +0000236 return (asBool((getText(Result, *LHS) + " " + NegatedOperator + " " +
Aaron Ballmanf034a8c2016-02-12 15:09:05 +0000237 getText(Result, *RHS))
238 .str(),
Alexander Kornienko6ae400d2015-07-01 12:39:40 +0000239 NeedsStaticCast));
Alexander Kornienko6ae400d2015-07-01 12:39:40 +0000240
241 StringRef Text = getText(Result, *E);
242 if (!NeedsStaticCast && needsParensAfterUnaryNegation(E))
243 return ("!(" + Text + ")").str();
244
245 if (needsNullPtrComparison(E))
Aaron Ballmanf034a8c2016-02-12 15:09:05 +0000246 return compareExpressionToNullPtr(Result, E, false);
247
248 if (needsZeroComparison(E))
249 return compareExpressionToZero(Result, E, false);
Alexander Kornienko6ae400d2015-07-01 12:39:40 +0000250
251 return ("!" + asBool(Text, NeedsStaticCast));
252 }
253
254 if (const auto *UnOp = dyn_cast<UnaryOperator>(E)) {
255 if (UnOp->getOpcode() == UO_LNot) {
256 if (needsNullPtrComparison(UnOp->getSubExpr()))
Aaron Ballmanf034a8c2016-02-12 15:09:05 +0000257 return compareExpressionToNullPtr(Result, UnOp->getSubExpr(), false);
258
259 if (needsZeroComparison(UnOp->getSubExpr()))
260 return compareExpressionToZero(Result, UnOp->getSubExpr(), false);
Alexander Kornienko6ae400d2015-07-01 12:39:40 +0000261 }
262 }
263
264 if (needsNullPtrComparison(E))
Aaron Ballmanf034a8c2016-02-12 15:09:05 +0000265 return compareExpressionToNullPtr(Result, E, true);
266
267 if (needsZeroComparison(E))
268 return compareExpressionToZero(Result, E, true);
Alexander Kornienko6ae400d2015-07-01 12:39:40 +0000269
270 return asBool(getText(Result, *E), NeedsStaticCast);
271}
272
273const CXXBoolLiteralExpr *stmtReturnsBool(const ReturnStmt *Ret, bool Negated) {
274 if (const auto *Bool = dyn_cast<CXXBoolLiteralExpr>(Ret->getRetValue())) {
275 if (Bool->getValue() == !Negated)
276 return Bool;
277 }
278
279 return nullptr;
280}
281
282const CXXBoolLiteralExpr *stmtReturnsBool(const IfStmt *IfRet, bool Negated) {
283 if (IfRet->getElse() != nullptr)
284 return nullptr;
285
286 if (const auto *Ret = dyn_cast<ReturnStmt>(IfRet->getThen()))
287 return stmtReturnsBool(Ret, Negated);
288
289 if (const auto *Compound = dyn_cast<CompoundStmt>(IfRet->getThen())) {
290 if (Compound->size() == 1) {
291 if (const auto *CompoundRet = dyn_cast<ReturnStmt>(Compound->body_back()))
292 return stmtReturnsBool(CompoundRet, Negated);
293 }
294 }
295
296 return nullptr;
Alexander Kornienkof5e72b02015-04-10 19:26:43 +0000297}
298
Aaron Ballmanf034a8c2016-02-12 15:09:05 +0000299bool containsDiscardedTokens(const MatchFinder::MatchResult &Result,
300 CharSourceRange CharRange) {
301 std::string ReplacementText =
302 Lexer::getSourceText(CharRange, *Result.SourceManager,
303 Result.Context->getLangOpts())
304 .str();
305 Lexer Lex(CharRange.getBegin(), Result.Context->getLangOpts(),
306 ReplacementText.data(), ReplacementText.data(),
307 ReplacementText.data() + ReplacementText.size());
308 Lex.SetCommentRetentionState(true);
309
310 Token Tok;
311 while (!Lex.LexFromRawLexer(Tok)) {
312 if (Tok.is(tok::TokenKind::comment) || Tok.is(tok::TokenKind::hash))
313 return true;
314 }
315
316 return false;
317}
318
Alexander Kornienkof5e72b02015-04-10 19:26:43 +0000319} // namespace
320
Alexander Kornienko7009d652017-05-15 17:06:51 +0000321class SimplifyBooleanExprCheck::Visitor : public RecursiveASTVisitor<Visitor> {
322 using Base = RecursiveASTVisitor<Visitor>;
323
324 public:
325 Visitor(SimplifyBooleanExprCheck *Check,
326 const MatchFinder::MatchResult &Result)
327 : Check(Check), Result(Result) {}
328
329 bool VisitBinaryOperator(BinaryOperator *Op) {
330 Check->reportBinOp(Result, Op);
331 return true;
332 }
333
334 private:
335 SimplifyBooleanExprCheck *Check;
336 const MatchFinder::MatchResult &Result;
337};
338
339
Alexander Kornienkofb3e2cd2015-05-17 12:31:12 +0000340SimplifyBooleanExprCheck::SimplifyBooleanExprCheck(StringRef Name,
341 ClangTidyContext *Context)
342 : ClangTidyCheck(Name, Context),
343 ChainedConditionalReturn(Options.get("ChainedConditionalReturn", 0U)),
344 ChainedConditionalAssignment(
345 Options.get("ChainedConditionalAssignment", 0U)) {}
346
Alexander Kornienko7009d652017-05-15 17:06:51 +0000347bool containsBoolLiteral(const Expr *E) {
348 if (!E)
349 return false;
350 E = E->IgnoreParenImpCasts();
351 if (isa<CXXBoolLiteralExpr>(E))
352 return true;
353 if (const auto *BinOp = dyn_cast<BinaryOperator>(E))
354 return containsBoolLiteral(BinOp->getLHS()) ||
355 containsBoolLiteral(BinOp->getRHS());
356 if (const auto *UnaryOp = dyn_cast<UnaryOperator>(E))
357 return containsBoolLiteral(UnaryOp->getSubExpr());
358 return false;
Alexander Kornienkof5e72b02015-04-10 19:26:43 +0000359}
360
Alexander Kornienko7009d652017-05-15 17:06:51 +0000361void SimplifyBooleanExprCheck::reportBinOp(
362 const MatchFinder::MatchResult &Result, const BinaryOperator *Op) {
363 const auto *LHS = Op->getLHS()->IgnoreParenImpCasts();
364 const auto *RHS = Op->getRHS()->IgnoreParenImpCasts();
Alexander Kornienkof5e72b02015-04-10 19:26:43 +0000365
Alexander Kornienko7009d652017-05-15 17:06:51 +0000366 const CXXBoolLiteralExpr *Bool = nullptr;
367 const Expr *Other = nullptr;
368 if ((Bool = dyn_cast<CXXBoolLiteralExpr>(LHS)))
369 Other = RHS;
370 else if ((Bool = dyn_cast<CXXBoolLiteralExpr>(RHS)))
371 Other = LHS;
372 else
373 return;
Alexander Kornienkof5e72b02015-04-10 19:26:43 +0000374
Alexander Kornienko7009d652017-05-15 17:06:51 +0000375 if (Bool->getLocStart().isMacroID())
376 return;
377
378 // FIXME: why do we need this?
379 if (!isa<CXXBoolLiteralExpr>(Other) && containsBoolLiteral(Other))
380 return;
381
382 bool BoolValue = Bool->getValue();
383
384 auto replaceWithExpression = [this, &Result, LHS, RHS, Bool](
385 const Expr *ReplaceWith, bool Negated) {
386 std::string Replacement =
387 replacementExpression(Result, Negated, ReplaceWith);
388 SourceRange Range(LHS->getLocStart(), RHS->getLocEnd());
389 issueDiag(Result, Bool->getLocStart(), SimplifyOperatorDiagnostic, Range,
390 Replacement);
391 };
392
393 switch (Op->getOpcode()) {
394 case BO_LAnd:
395 if (BoolValue) {
396 // expr && true -> expr
397 replaceWithExpression(Other, /*Negated=*/false);
398 } else {
399 // expr && false -> false
400 replaceWithExpression(Bool, /*Negated=*/false);
401 }
402 break;
403 case BO_LOr:
404 if (BoolValue) {
405 // expr || true -> true
406 replaceWithExpression(Bool, /*Negated=*/false);
407 } else {
408 // expr || false -> expr
409 replaceWithExpression(Other, /*Negated=*/false);
410 }
411 break;
412 case BO_EQ:
413 // expr == true -> expr, expr == false -> !expr
414 replaceWithExpression(Other, /*Negated=*/!BoolValue);
415 break;
416 case BO_NE:
417 // expr != true -> !expr, expr != false -> expr
418 replaceWithExpression(Other, /*Negated=*/BoolValue);
419 break;
420 default:
421 break;
422 }
Alexander Kornienkof5e72b02015-04-10 19:26:43 +0000423}
424
425void SimplifyBooleanExprCheck::matchBoolCondition(MatchFinder *Finder,
426 bool Value,
427 StringRef BooleanId) {
Aaron Ballmanf034a8c2016-02-12 15:09:05 +0000428 Finder->addMatcher(
429 ifStmt(isExpansionInMainFile(),
430 hasCondition(cxxBoolLiteral(equals(Value)).bind(BooleanId)))
431 .bind(IfStmtId),
432 this);
Alexander Kornienkof5e72b02015-04-10 19:26:43 +0000433}
434
435void SimplifyBooleanExprCheck::matchTernaryResult(MatchFinder *Finder,
436 bool Value,
437 StringRef TernaryId) {
438 Finder->addMatcher(
439 conditionalOperator(isExpansionInMainFile(),
Aaron Ballmanb9ea09c2015-09-17 13:31:25 +0000440 hasTrueExpression(cxxBoolLiteral(equals(Value))),
441 hasFalseExpression(cxxBoolLiteral(equals(!Value))))
Alexander Kornienkof5e72b02015-04-10 19:26:43 +0000442 .bind(TernaryId),
443 this);
444}
445
446void SimplifyBooleanExprCheck::matchIfReturnsBool(MatchFinder *Finder,
447 bool Value, StringRef Id) {
Aaron Ballmanf034a8c2016-02-12 15:09:05 +0000448 if (ChainedConditionalReturn)
Alexander Kornienkofb3e2cd2015-05-17 12:31:12 +0000449 Finder->addMatcher(ifStmt(isExpansionInMainFile(),
Alexander Kornienko6ae400d2015-07-01 12:39:40 +0000450 hasThen(returnsBool(Value, ThenLiteralId)),
Aaron Ballmanf034a8c2016-02-12 15:09:05 +0000451 hasElse(returnsBool(!Value)))
452 .bind(Id),
Alexander Kornienkofb3e2cd2015-05-17 12:31:12 +0000453 this);
Aaron Ballmanf034a8c2016-02-12 15:09:05 +0000454 else
Alexander Kornienkofb3e2cd2015-05-17 12:31:12 +0000455 Finder->addMatcher(ifStmt(isExpansionInMainFile(),
456 unless(hasParent(ifStmt())),
Alexander Kornienko6ae400d2015-07-01 12:39:40 +0000457 hasThen(returnsBool(Value, ThenLiteralId)),
Aaron Ballmanf034a8c2016-02-12 15:09:05 +0000458 hasElse(returnsBool(!Value)))
459 .bind(Id),
Alexander Kornienkofb3e2cd2015-05-17 12:31:12 +0000460 this);
Alexander Kornienkof5e72b02015-04-10 19:26:43 +0000461}
462
463void SimplifyBooleanExprCheck::matchIfAssignsBool(MatchFinder *Finder,
464 bool Value, StringRef Id) {
465 auto SimpleThen = binaryOperator(
466 hasOperatorName("="),
467 hasLHS(declRefExpr(hasDeclaration(decl().bind(IfAssignObjId)))),
468 hasLHS(expr().bind(IfAssignVariableId)),
Aaron Ballmanb9ea09c2015-09-17 13:31:25 +0000469 hasRHS(cxxBoolLiteral(equals(Value)).bind(IfAssignLocId)));
Alexander Kornienkof5e72b02015-04-10 19:26:43 +0000470 auto Then = anyOf(SimpleThen, compoundStmt(statementCountIs(1),
471 hasAnySubstatement(SimpleThen)));
472 auto SimpleElse = binaryOperator(
473 hasOperatorName("="),
474 hasLHS(declRefExpr(hasDeclaration(equalsBoundNode(IfAssignObjId)))),
Aaron Ballmanb9ea09c2015-09-17 13:31:25 +0000475 hasRHS(cxxBoolLiteral(equals(!Value))));
Alexander Kornienkof5e72b02015-04-10 19:26:43 +0000476 auto Else = anyOf(SimpleElse, compoundStmt(statementCountIs(1),
477 hasAnySubstatement(SimpleElse)));
Aaron Ballmanf034a8c2016-02-12 15:09:05 +0000478 if (ChainedConditionalAssignment)
Alexander Kornienkofb3e2cd2015-05-17 12:31:12 +0000479 Finder->addMatcher(
480 ifStmt(isExpansionInMainFile(), hasThen(Then), hasElse(Else)).bind(Id),
481 this);
Aaron Ballmanf034a8c2016-02-12 15:09:05 +0000482 else
Alexander Kornienkofb3e2cd2015-05-17 12:31:12 +0000483 Finder->addMatcher(ifStmt(isExpansionInMainFile(),
484 unless(hasParent(ifStmt())), hasThen(Then),
Aaron Ballmanf034a8c2016-02-12 15:09:05 +0000485 hasElse(Else))
486 .bind(Id),
Alexander Kornienkofb3e2cd2015-05-17 12:31:12 +0000487 this);
Alexander Kornienkofb3e2cd2015-05-17 12:31:12 +0000488}
489
Alexander Kornienko6ae400d2015-07-01 12:39:40 +0000490void SimplifyBooleanExprCheck::matchCompoundIfReturnsBool(MatchFinder *Finder,
491 bool Value,
492 StringRef Id) {
493 Finder->addMatcher(
Aaron Ballmanb9ea09c2015-09-17 13:31:25 +0000494 compoundStmt(allOf(hasAnySubstatement(ifStmt(hasThen(returnsBool(Value)),
495 unless(hasElse(stmt())))),
496 hasAnySubstatement(
Piotr Padlewskie93a73f2016-05-31 15:26:56 +0000497 returnStmt(has(ignoringParenImpCasts(
498 cxxBoolLiteral(equals(!Value)))))
Aaron Ballmanf034a8c2016-02-12 15:09:05 +0000499 .bind(CompoundReturnId))))
500 .bind(Id),
Alexander Kornienko6ae400d2015-07-01 12:39:40 +0000501 this);
502}
503
Alexander Kornienkofb3e2cd2015-05-17 12:31:12 +0000504void SimplifyBooleanExprCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
505 Options.store(Opts, "ChainedConditionalReturn", ChainedConditionalReturn);
506 Options.store(Opts, "ChainedConditionalAssignment",
507 ChainedConditionalAssignment);
Alexander Kornienkof5e72b02015-04-10 19:26:43 +0000508}
509
510void SimplifyBooleanExprCheck::registerMatchers(MatchFinder *Finder) {
Alexander Kornienko7009d652017-05-15 17:06:51 +0000511 Finder->addMatcher(translationUnitDecl().bind("top"), this);
Alexander Kornienkof5e72b02015-04-10 19:26:43 +0000512
513 matchBoolCondition(Finder, true, ConditionThenStmtId);
514 matchBoolCondition(Finder, false, ConditionElseStmtId);
515
516 matchTernaryResult(Finder, true, TernaryId);
517 matchTernaryResult(Finder, false, TernaryNegatedId);
518
519 matchIfReturnsBool(Finder, true, IfReturnsBoolId);
520 matchIfReturnsBool(Finder, false, IfReturnsNotBoolId);
521
522 matchIfAssignsBool(Finder, true, IfAssignBoolId);
523 matchIfAssignsBool(Finder, false, IfAssignNotBoolId);
Alexander Kornienko6ae400d2015-07-01 12:39:40 +0000524
525 matchCompoundIfReturnsBool(Finder, true, CompoundBoolId);
526 matchCompoundIfReturnsBool(Finder, false, CompoundNotBoolId);
Alexander Kornienkof5e72b02015-04-10 19:26:43 +0000527}
528
529void SimplifyBooleanExprCheck::check(const MatchFinder::MatchResult &Result) {
Alexander Kornienko7009d652017-05-15 17:06:51 +0000530 if (const CXXBoolLiteralExpr *TrueConditionRemoved =
531 getBoolLiteral(Result, ConditionThenStmtId))
Alexander Kornienkof5e72b02015-04-10 19:26:43 +0000532 replaceWithThenStatement(Result, TrueConditionRemoved);
Aaron Ballmanf034a8c2016-02-12 15:09:05 +0000533 else if (const CXXBoolLiteralExpr *FalseConditionRemoved =
534 getBoolLiteral(Result, ConditionElseStmtId))
Alexander Kornienkof5e72b02015-04-10 19:26:43 +0000535 replaceWithElseStatement(Result, FalseConditionRemoved);
Aaron Ballmanf034a8c2016-02-12 15:09:05 +0000536 else if (const auto *Ternary =
537 Result.Nodes.getNodeAs<ConditionalOperator>(TernaryId))
Alexander Kornienkof5e72b02015-04-10 19:26:43 +0000538 replaceWithCondition(Result, Ternary);
Aaron Ballmanf034a8c2016-02-12 15:09:05 +0000539 else if (const auto *TernaryNegated =
540 Result.Nodes.getNodeAs<ConditionalOperator>(TernaryNegatedId))
Alexander Kornienkof5e72b02015-04-10 19:26:43 +0000541 replaceWithCondition(Result, TernaryNegated, true);
Aaron Ballmanf034a8c2016-02-12 15:09:05 +0000542 else if (const auto *If = Result.Nodes.getNodeAs<IfStmt>(IfReturnsBoolId))
Alexander Kornienkof5e72b02015-04-10 19:26:43 +0000543 replaceWithReturnCondition(Result, If);
Aaron Ballmanf034a8c2016-02-12 15:09:05 +0000544 else if (const auto *IfNot =
545 Result.Nodes.getNodeAs<IfStmt>(IfReturnsNotBoolId))
Alexander Kornienkof5e72b02015-04-10 19:26:43 +0000546 replaceWithReturnCondition(Result, IfNot, true);
Aaron Ballmanf034a8c2016-02-12 15:09:05 +0000547 else if (const auto *IfAssign =
548 Result.Nodes.getNodeAs<IfStmt>(IfAssignBoolId))
Alexander Kornienkof5e72b02015-04-10 19:26:43 +0000549 replaceWithAssignment(Result, IfAssign);
Aaron Ballmanf034a8c2016-02-12 15:09:05 +0000550 else if (const auto *IfAssignNot =
551 Result.Nodes.getNodeAs<IfStmt>(IfAssignNotBoolId))
Alexander Kornienkof5e72b02015-04-10 19:26:43 +0000552 replaceWithAssignment(Result, IfAssignNot, true);
Aaron Ballmanf034a8c2016-02-12 15:09:05 +0000553 else if (const auto *Compound =
554 Result.Nodes.getNodeAs<CompoundStmt>(CompoundBoolId))
Alexander Kornienko6ae400d2015-07-01 12:39:40 +0000555 replaceCompoundReturnWithCondition(Result, Compound);
Aaron Ballmanf034a8c2016-02-12 15:09:05 +0000556 else if (const auto *Compound =
557 Result.Nodes.getNodeAs<CompoundStmt>(CompoundNotBoolId))
Alexander Kornienko6ae400d2015-07-01 12:39:40 +0000558 replaceCompoundReturnWithCondition(Result, Compound, true);
Alexander Kornienko7009d652017-05-15 17:06:51 +0000559 else if (const auto TU = Result.Nodes.getNodeAs<Decl>("top"))
560 Visitor(this, Result).TraverseDecl(const_cast<Decl*>(TU));
Alexander Kornienko4f74ec02015-12-28 13:21:22 +0000561}
562
563void SimplifyBooleanExprCheck::issueDiag(
564 const ast_matchers::MatchFinder::MatchResult &Result, SourceLocation Loc,
565 StringRef Description, SourceRange ReplacementRange,
566 StringRef Replacement) {
Gabor Horvathafad84c2016-09-24 02:13:45 +0000567 CharSourceRange CharRange =
568 Lexer::makeFileCharRange(CharSourceRange::getTokenRange(ReplacementRange),
569 *Result.SourceManager, getLangOpts());
Alexander Kornienko4f74ec02015-12-28 13:21:22 +0000570
571 DiagnosticBuilder Diag = diag(Loc, Description);
572 if (!containsDiscardedTokens(Result, CharRange))
573 Diag << FixItHint::CreateReplacement(CharRange, Replacement);
574}
575
Alexander Kornienkof5e72b02015-04-10 19:26:43 +0000576void SimplifyBooleanExprCheck::replaceWithThenStatement(
577 const MatchFinder::MatchResult &Result,
578 const CXXBoolLiteralExpr *TrueConditionRemoved) {
579 const auto *IfStatement = Result.Nodes.getNodeAs<IfStmt>(IfStmtId);
Alexander Kornienko4f74ec02015-12-28 13:21:22 +0000580 issueDiag(Result, TrueConditionRemoved->getLocStart(),
581 SimplifyConditionDiagnostic, IfStatement->getSourceRange(),
582 getText(Result, *IfStatement->getThen()));
Alexander Kornienkof5e72b02015-04-10 19:26:43 +0000583}
584
585void SimplifyBooleanExprCheck::replaceWithElseStatement(
586 const MatchFinder::MatchResult &Result,
587 const CXXBoolLiteralExpr *FalseConditionRemoved) {
588 const auto *IfStatement = Result.Nodes.getNodeAs<IfStmt>(IfStmtId);
589 const Stmt *ElseStatement = IfStatement->getElse();
Alexander Kornienko4f74ec02015-12-28 13:21:22 +0000590 issueDiag(Result, FalseConditionRemoved->getLocStart(),
591 SimplifyConditionDiagnostic, IfStatement->getSourceRange(),
592 ElseStatement ? getText(Result, *ElseStatement) : "");
Alexander Kornienkof5e72b02015-04-10 19:26:43 +0000593}
594
595void SimplifyBooleanExprCheck::replaceWithCondition(
596 const MatchFinder::MatchResult &Result, const ConditionalOperator *Ternary,
597 bool Negated) {
598 std::string Replacement =
599 replacementExpression(Result, Negated, Ternary->getCond());
Alexander Kornienko4f74ec02015-12-28 13:21:22 +0000600 issueDiag(Result, Ternary->getTrueExpr()->getLocStart(),
601 "redundant boolean literal in ternary expression result",
602 Ternary->getSourceRange(), Replacement);
Alexander Kornienkof5e72b02015-04-10 19:26:43 +0000603}
604
605void SimplifyBooleanExprCheck::replaceWithReturnCondition(
606 const MatchFinder::MatchResult &Result, const IfStmt *If, bool Negated) {
607 StringRef Terminator = isa<CompoundStmt>(If->getElse()) ? ";" : "";
608 std::string Condition = replacementExpression(Result, Negated, If->getCond());
609 std::string Replacement = ("return " + Condition + Terminator).str();
610 SourceLocation Start =
611 Result.Nodes.getNodeAs<CXXBoolLiteralExpr>(ThenLiteralId)->getLocStart();
Alexander Kornienko4f74ec02015-12-28 13:21:22 +0000612 issueDiag(Result, Start, SimplifyConditionalReturnDiagnostic,
613 If->getSourceRange(), Replacement);
Alexander Kornienkof5e72b02015-04-10 19:26:43 +0000614}
615
Alexander Kornienko6ae400d2015-07-01 12:39:40 +0000616void SimplifyBooleanExprCheck::replaceCompoundReturnWithCondition(
617 const MatchFinder::MatchResult &Result, const CompoundStmt *Compound,
618 bool Negated) {
619 const auto *Ret = Result.Nodes.getNodeAs<ReturnStmt>(CompoundReturnId);
620
621 // The body shouldn't be empty because the matcher ensures that it must
622 // contain at least two statements:
623 // 1) A `return` statement returning a boolean literal `false` or `true`
Aaron Ballmanf034a8c2016-02-12 15:09:05 +0000624 // 2) An `if` statement with no `else` clause that consists of a single
Alexander Kornienko6ae400d2015-07-01 12:39:40 +0000625 // `return` statement returning the opposite boolean literal `true` or
626 // `false`.
627 assert(Compound->size() >= 2);
628 const IfStmt *BeforeIf = nullptr;
629 CompoundStmt::const_body_iterator Current = Compound->body_begin();
630 CompoundStmt::const_body_iterator After = Compound->body_begin();
631 for (++After; After != Compound->body_end() && *Current != Ret;
632 ++Current, ++After) {
633 if (const auto *If = dyn_cast<IfStmt>(*Current)) {
634 if (const CXXBoolLiteralExpr *Lit = stmtReturnsBool(If, Negated)) {
635 if (*After == Ret) {
636 if (!ChainedConditionalReturn && BeforeIf)
637 continue;
638
639 const Expr *Condition = If->getCond();
640 std::string Replacement =
641 "return " + replacementExpression(Result, Negated, Condition);
Alexander Kornienko4f74ec02015-12-28 13:21:22 +0000642 issueDiag(
643 Result, Lit->getLocStart(), SimplifyConditionalReturnDiagnostic,
644 SourceRange(If->getLocStart(), Ret->getLocEnd()), Replacement);
Alexander Kornienko6ae400d2015-07-01 12:39:40 +0000645 return;
646 }
647
648 BeforeIf = If;
649 }
650 } else {
651 BeforeIf = nullptr;
652 }
653 }
654}
655
Alexander Kornienkof5e72b02015-04-10 19:26:43 +0000656void SimplifyBooleanExprCheck::replaceWithAssignment(
657 const MatchFinder::MatchResult &Result, const IfStmt *IfAssign,
658 bool Negated) {
659 SourceRange Range = IfAssign->getSourceRange();
660 StringRef VariableName =
661 getText(Result, *Result.Nodes.getNodeAs<Expr>(IfAssignVariableId));
662 StringRef Terminator = isa<CompoundStmt>(IfAssign->getElse()) ? ";" : "";
663 std::string Condition =
664 replacementExpression(Result, Negated, IfAssign->getCond());
665 std::string Replacement =
666 (VariableName + " = " + Condition + Terminator).str();
667 SourceLocation Location =
668 Result.Nodes.getNodeAs<CXXBoolLiteralExpr>(IfAssignLocId)->getLocStart();
Alexander Kornienko4f74ec02015-12-28 13:21:22 +0000669 issueDiag(Result, Location,
670 "redundant boolean literal in conditional assignment", Range,
671 Replacement);
Alexander Kornienkof5e72b02015-04-10 19:26:43 +0000672}
673
674} // namespace readability
675} // namespace tidy
676} // namespace clang