clang-tools 19.0.0git
SimplifyBooleanExprCheck.cpp
Go to the documentation of this file.
1//===-- SimplifyBooleanExprCheck.cpp - clang-tidy -------------------------===//
2//
3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4// See https://llvm.org/LICENSE.txt for license information.
5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6//
7//===----------------------------------------------------------------------===//
8
10#include "clang/AST/Expr.h"
11#include "clang/AST/RecursiveASTVisitor.h"
12#include "clang/Lex/Lexer.h"
13#include "llvm/Support/SaveAndRestore.h"
14
15#include <optional>
16#include <string>
17#include <utility>
18
19using namespace clang::ast_matchers;
20
22
23namespace {
24
25StringRef getText(const ASTContext &Context, SourceRange Range) {
26 return Lexer::getSourceText(CharSourceRange::getTokenRange(Range),
27 Context.getSourceManager(),
28 Context.getLangOpts());
29}
30
31template <typename T> StringRef getText(const ASTContext &Context, T &Node) {
32 return getText(Context, Node.getSourceRange());
33}
34
35} // namespace
36
37static constexpr char SimplifyOperatorDiagnostic[] =
38 "redundant boolean literal supplied to boolean operator";
39static constexpr char SimplifyConditionDiagnostic[] =
40 "redundant boolean literal in if statement condition";
41static constexpr char SimplifyConditionalReturnDiagnostic[] =
42 "redundant boolean literal in conditional return statement";
43
44static bool needsParensAfterUnaryNegation(const Expr *E) {
45 E = E->IgnoreImpCasts();
46 if (isa<BinaryOperator>(E) || isa<ConditionalOperator>(E))
47 return true;
48
49 if (const auto *Op = dyn_cast<CXXOperatorCallExpr>(E))
50 return Op->getNumArgs() == 2 && Op->getOperator() != OO_Call &&
51 Op->getOperator() != OO_Subscript;
52
53 return false;
54}
55
56static std::pair<BinaryOperatorKind, BinaryOperatorKind> Opposites[] = {
57 {BO_LT, BO_GE}, {BO_GT, BO_LE}, {BO_EQ, BO_NE}};
58
59static StringRef negatedOperator(const BinaryOperator *BinOp) {
60 const BinaryOperatorKind Opcode = BinOp->getOpcode();
61 for (auto NegatableOp : Opposites) {
62 if (Opcode == NegatableOp.first)
63 return BinaryOperator::getOpcodeStr(NegatableOp.second);
64 if (Opcode == NegatableOp.second)
65 return BinaryOperator::getOpcodeStr(NegatableOp.first);
66 }
67 return {};
68}
69
70static std::pair<OverloadedOperatorKind, StringRef> OperatorNames[] = {
71 {OO_EqualEqual, "=="}, {OO_ExclaimEqual, "!="}, {OO_Less, "<"},
72 {OO_GreaterEqual, ">="}, {OO_Greater, ">"}, {OO_LessEqual, "<="}};
73
74static StringRef getOperatorName(OverloadedOperatorKind OpKind) {
75 for (auto Name : OperatorNames) {
76 if (Name.first == OpKind)
77 return Name.second;
78 }
79
80 return {};
81}
82
83static std::pair<OverloadedOperatorKind, OverloadedOperatorKind>
84 OppositeOverloads[] = {{OO_EqualEqual, OO_ExclaimEqual},
85 {OO_Less, OO_GreaterEqual},
86 {OO_Greater, OO_LessEqual}};
87
88static StringRef negatedOperator(const CXXOperatorCallExpr *OpCall) {
89 const OverloadedOperatorKind Opcode = OpCall->getOperator();
90 for (auto NegatableOp : OppositeOverloads) {
91 if (Opcode == NegatableOp.first)
92 return getOperatorName(NegatableOp.second);
93 if (Opcode == NegatableOp.second)
94 return getOperatorName(NegatableOp.first);
95 }
96 return {};
97}
98
99static std::string asBool(StringRef Text, bool NeedsStaticCast) {
100 if (NeedsStaticCast)
101 return ("static_cast<bool>(" + Text + ")").str();
102
103 return std::string(Text);
104}
105
106static bool needsNullPtrComparison(const Expr *E) {
107 if (const auto *ImpCast = dyn_cast<ImplicitCastExpr>(E))
108 return ImpCast->getCastKind() == CK_PointerToBoolean ||
109 ImpCast->getCastKind() == CK_MemberPointerToBoolean;
110
111 return false;
112}
113
114static bool needsZeroComparison(const Expr *E) {
115 if (const auto *ImpCast = dyn_cast<ImplicitCastExpr>(E))
116 return ImpCast->getCastKind() == CK_IntegralToBoolean;
117
118 return false;
119}
120
121static bool needsStaticCast(const Expr *E) {
122 if (const auto *ImpCast = dyn_cast<ImplicitCastExpr>(E)) {
123 if (ImpCast->getCastKind() == CK_UserDefinedConversion &&
124 ImpCast->getSubExpr()->getType()->isBooleanType()) {
125 if (const auto *MemCall =
126 dyn_cast<CXXMemberCallExpr>(ImpCast->getSubExpr())) {
127 if (const auto *MemDecl =
128 dyn_cast<CXXConversionDecl>(MemCall->getMethodDecl())) {
129 if (MemDecl->isExplicit())
130 return true;
131 }
132 }
133 }
134 }
135
136 E = E->IgnoreImpCasts();
137 return !E->getType()->isBooleanType();
138}
139
140static std::string compareExpressionToConstant(const ASTContext &Context,
141 const Expr *E, bool Negated,
142 const char *Constant) {
143 E = E->IgnoreImpCasts();
144 const std::string ExprText =
145 (isa<BinaryOperator>(E) ? ("(" + getText(Context, *E) + ")")
146 : getText(Context, *E))
147 .str();
148 return ExprText + " " + (Negated ? "!=" : "==") + " " + Constant;
149}
150
151static std::string compareExpressionToNullPtr(const ASTContext &Context,
152 const Expr *E, bool Negated) {
153 const char *NullPtr = Context.getLangOpts().CPlusPlus11 ? "nullptr" : "NULL";
154 return compareExpressionToConstant(Context, E, Negated, NullPtr);
155}
156
157static std::string compareExpressionToZero(const ASTContext &Context,
158 const Expr *E, bool Negated) {
159 return compareExpressionToConstant(Context, E, Negated, "0");
160}
161
162static std::string replacementExpression(const ASTContext &Context,
163 bool Negated, const Expr *E) {
164 E = E->IgnoreParenBaseCasts();
165 if (const auto *EC = dyn_cast<ExprWithCleanups>(E))
166 E = EC->getSubExpr();
167
168 const bool NeedsStaticCast = needsStaticCast(E);
169 if (Negated) {
170 if (const auto *UnOp = dyn_cast<UnaryOperator>(E)) {
171 if (UnOp->getOpcode() == UO_LNot) {
172 if (needsNullPtrComparison(UnOp->getSubExpr()))
173 return compareExpressionToNullPtr(Context, UnOp->getSubExpr(), true);
174
175 if (needsZeroComparison(UnOp->getSubExpr()))
176 return compareExpressionToZero(Context, UnOp->getSubExpr(), true);
177
178 return replacementExpression(Context, false, UnOp->getSubExpr());
179 }
180 }
181
183 return compareExpressionToNullPtr(Context, E, false);
184
186 return compareExpressionToZero(Context, E, false);
187
188 StringRef NegatedOperator;
189 const Expr *LHS = nullptr;
190 const Expr *RHS = nullptr;
191 if (const auto *BinOp = dyn_cast<BinaryOperator>(E)) {
192 NegatedOperator = negatedOperator(BinOp);
193 LHS = BinOp->getLHS();
194 RHS = BinOp->getRHS();
195 } else if (const auto *OpExpr = dyn_cast<CXXOperatorCallExpr>(E)) {
196 if (OpExpr->getNumArgs() == 2) {
197 NegatedOperator = negatedOperator(OpExpr);
198 LHS = OpExpr->getArg(0);
199 RHS = OpExpr->getArg(1);
200 }
201 }
202 if (!NegatedOperator.empty() && LHS && RHS)
203 return (asBool((getText(Context, *LHS) + " " + NegatedOperator + " " +
204 getText(Context, *RHS))
205 .str(),
206 NeedsStaticCast));
207
208 StringRef Text = getText(Context, *E);
209 if (!NeedsStaticCast && needsParensAfterUnaryNegation(E))
210 return ("!(" + Text + ")").str();
211
213 return compareExpressionToNullPtr(Context, E, false);
214
216 return compareExpressionToZero(Context, E, false);
217
218 return ("!" + asBool(Text, NeedsStaticCast));
219 }
220
221 if (const auto *UnOp = dyn_cast<UnaryOperator>(E)) {
222 if (UnOp->getOpcode() == UO_LNot) {
223 if (needsNullPtrComparison(UnOp->getSubExpr()))
224 return compareExpressionToNullPtr(Context, UnOp->getSubExpr(), false);
225
226 if (needsZeroComparison(UnOp->getSubExpr()))
227 return compareExpressionToZero(Context, UnOp->getSubExpr(), false);
228 }
229 }
230
232 return compareExpressionToNullPtr(Context, E, true);
233
235 return compareExpressionToZero(Context, E, true);
236
237 return asBool(getText(Context, *E), NeedsStaticCast);
238}
239
240static bool containsDiscardedTokens(const ASTContext &Context,
241 CharSourceRange CharRange) {
242 std::string ReplacementText =
243 Lexer::getSourceText(CharRange, Context.getSourceManager(),
244 Context.getLangOpts())
245 .str();
246 Lexer Lex(CharRange.getBegin(), Context.getLangOpts(), ReplacementText.data(),
247 ReplacementText.data(),
248 ReplacementText.data() + ReplacementText.size());
249 Lex.SetCommentRetentionState(true);
250
251 Token Tok;
252 while (!Lex.LexFromRawLexer(Tok)) {
253 if (Tok.is(tok::TokenKind::comment) || Tok.is(tok::TokenKind::hash))
254 return true;
255 }
256
257 return false;
258}
259
260class SimplifyBooleanExprCheck::Visitor : public RecursiveASTVisitor<Visitor> {
261 using Base = RecursiveASTVisitor<Visitor>;
262
263public:
264 Visitor(SimplifyBooleanExprCheck *Check, ASTContext &Context)
265 : Check(Check), Context(Context) {}
266
267 bool traverse() { return TraverseAST(Context); }
268
269 static bool shouldIgnore(Stmt *S) {
270 switch (S->getStmtClass()) {
271 case Stmt::ImplicitCastExprClass:
272 case Stmt::MaterializeTemporaryExprClass:
273 case Stmt::CXXBindTemporaryExprClass:
274 return true;
275 default:
276 return false;
277 }
278 }
279
280 bool dataTraverseStmtPre(Stmt *S) {
281 if (!S) {
282 return true;
283 }
284 if (Check->canBeBypassed(S))
285 return false;
286 if (!shouldIgnore(S))
287 StmtStack.push_back(S);
288 return true;
289 }
290
291 bool dataTraverseStmtPost(Stmt *S) {
292 if (S && !shouldIgnore(S)) {
293 assert(StmtStack.back() == S);
294 StmtStack.pop_back();
295 }
296 return true;
297 }
298
299 bool VisitBinaryOperator(const BinaryOperator *Op) const {
300 Check->reportBinOp(Context, Op);
301 return true;
302 }
303
304 // Extracts a bool if an expression is (true|false|!true|!false);
305 static std::optional<bool> getAsBoolLiteral(const Expr *E, bool FilterMacro) {
306 if (const auto *Bool = dyn_cast<CXXBoolLiteralExpr>(E)) {
307 if (FilterMacro && Bool->getBeginLoc().isMacroID())
308 return std::nullopt;
309 return Bool->getValue();
310 }
311 if (const auto *UnaryOp = dyn_cast<UnaryOperator>(E)) {
312 if (FilterMacro && UnaryOp->getBeginLoc().isMacroID())
313 return std::nullopt;
314 if (UnaryOp->getOpcode() == UO_LNot)
315 if (std::optional<bool> Res = getAsBoolLiteral(
316 UnaryOp->getSubExpr()->IgnoreImplicit(), FilterMacro))
317 return !*Res;
318 }
319 return std::nullopt;
320 }
321
322 template <typename Node> struct NodeAndBool {
323 const Node *Item = nullptr;
324 bool Bool = false;
325
326 operator bool() const { return Item != nullptr; }
327 };
328
331
332 /// Detect's return (true|false|!true|!false);
333 static ExprAndBool parseReturnLiteralBool(const Stmt *S) {
334 const auto *RS = dyn_cast<ReturnStmt>(S);
335 if (!RS || !RS->getRetValue())
336 return {};
337 if (std::optional<bool> Ret =
338 getAsBoolLiteral(RS->getRetValue()->IgnoreImplicit(), false)) {
339 return {RS->getRetValue(), *Ret};
340 }
341 return {};
342 }
343
344 /// If \p S is not a \c CompoundStmt, applies F on \p S, otherwise if there is
345 /// only 1 statement in the \c CompoundStmt, applies F on that single
346 /// statement.
347 template <typename Functor>
348 static auto checkSingleStatement(Stmt *S, Functor F) -> decltype(F(S)) {
349 if (auto *CS = dyn_cast<CompoundStmt>(S)) {
350 if (CS->size() == 1)
351 return F(CS->body_front());
352 return {};
353 }
354 return F(S);
355 }
356
357 Stmt *parent() const {
358 return StmtStack.size() < 2 ? nullptr : StmtStack[StmtStack.size() - 2];
359 }
360
361 bool VisitIfStmt(IfStmt *If) {
362 // Skip any if's that have a condition var or an init statement, or are
363 // "if consteval" statements.
364 if (If->hasInitStorage() || If->hasVarStorage() || If->isConsteval())
365 return true;
366 /*
367 * if (true) ThenStmt(); -> ThenStmt();
368 * if (false) ThenStmt(); -> <Empty>;
369 * if (false) ThenStmt(); else ElseStmt() -> ElseStmt();
370 */
371 Expr *Cond = If->getCond()->IgnoreImplicit();
372 if (std::optional<bool> Bool = getAsBoolLiteral(Cond, true)) {
373 if (*Bool)
374 Check->replaceWithThenStatement(Context, If, Cond);
375 else
376 Check->replaceWithElseStatement(Context, If, Cond);
377 }
378
379 if (If->getElse()) {
380 /*
381 * if (Cond) return true; else return false; -> return Cond;
382 * if (Cond) return false; else return true; -> return !Cond;
383 */
384 if (ExprAndBool ThenReturnBool =
386 ExprAndBool ElseReturnBool =
388 if (ElseReturnBool && ThenReturnBool.Bool != ElseReturnBool.Bool) {
389 if (Check->ChainedConditionalReturn ||
390 !isa_and_nonnull<IfStmt>(parent())) {
391 Check->replaceWithReturnCondition(Context, If, ThenReturnBool.Item,
392 ElseReturnBool.Bool);
393 }
394 }
395 } else {
396 /*
397 * if (Cond) A = true; else A = false; -> A = Cond;
398 * if (Cond) A = false; else A = true; -> A = !Cond;
399 */
400 Expr *Var = nullptr;
401 SourceLocation Loc;
402 auto VarBoolAssignmentMatcher = [&Var,
403 &Loc](const Stmt *S) -> DeclAndBool {
404 const auto *BO = dyn_cast<BinaryOperator>(S);
405 if (!BO || BO->getOpcode() != BO_Assign)
406 return {};
407 std::optional<bool> RightasBool =
408 getAsBoolLiteral(BO->getRHS()->IgnoreImplicit(), false);
409 if (!RightasBool)
410 return {};
411 Expr *IgnImp = BO->getLHS()->IgnoreImplicit();
412 if (!Var) {
413 // We only need to track these for the Then branch.
414 Loc = BO->getRHS()->getBeginLoc();
415 Var = IgnImp;
416 }
417 if (auto *DRE = dyn_cast<DeclRefExpr>(IgnImp))
418 return {DRE->getDecl(), *RightasBool};
419 if (auto *ME = dyn_cast<MemberExpr>(IgnImp))
420 return {ME->getMemberDecl(), *RightasBool};
421 return {};
422 };
423 if (DeclAndBool ThenAssignment =
424 checkSingleStatement(If->getThen(), VarBoolAssignmentMatcher)) {
425 DeclAndBool ElseAssignment =
426 checkSingleStatement(If->getElse(), VarBoolAssignmentMatcher);
427 if (ElseAssignment.Item == ThenAssignment.Item &&
428 ElseAssignment.Bool != ThenAssignment.Bool) {
429 if (Check->ChainedConditionalAssignment ||
430 !isa_and_nonnull<IfStmt>(parent())) {
431 Check->replaceWithAssignment(Context, If, Var, Loc,
432 ElseAssignment.Bool);
433 }
434 }
435 }
436 }
437 }
438 return true;
439 }
440
441 bool VisitConditionalOperator(ConditionalOperator *Cond) {
442 /*
443 * Condition ? true : false; -> Condition
444 * Condition ? false : true; -> !Condition;
445 */
446 if (std::optional<bool> Then =
447 getAsBoolLiteral(Cond->getTrueExpr()->IgnoreImplicit(), false)) {
448 if (std::optional<bool> Else =
449 getAsBoolLiteral(Cond->getFalseExpr()->IgnoreImplicit(), false)) {
450 if (*Then != *Else)
451 Check->replaceWithCondition(Context, Cond, *Else);
452 }
453 }
454 return true;
455 }
456
457 bool VisitCompoundStmt(CompoundStmt *CS) {
458 if (CS->size() < 2)
459 return true;
460 bool CurIf = false, PrevIf = false;
461 for (auto First = CS->body_begin(), Second = std::next(First),
462 End = CS->body_end();
463 Second != End; ++Second, ++First) {
464 PrevIf = CurIf;
465 CurIf = isa<IfStmt>(*First);
466 ExprAndBool TrailingReturnBool = parseReturnLiteralBool(*Second);
467 if (!TrailingReturnBool)
468 continue;
469
470 if (CurIf) {
471 /*
472 * if (Cond) return true; return false; -> return Cond;
473 * if (Cond) return false; return true; -> return !Cond;
474 */
475 auto *If = cast<IfStmt>(*First);
476 if (!If->hasInitStorage() && !If->hasVarStorage() &&
477 !If->isConsteval()) {
478 ExprAndBool ThenReturnBool =
480 if (ThenReturnBool &&
481 ThenReturnBool.Bool != TrailingReturnBool.Bool) {
482 if ((Check->ChainedConditionalReturn || !PrevIf) &&
483 If->getElse() == nullptr) {
484 Check->replaceCompoundReturnWithCondition(
485 Context, cast<ReturnStmt>(*Second), TrailingReturnBool.Bool,
486 If, ThenReturnBool.Item);
487 }
488 }
489 }
490 } else if (isa<LabelStmt, CaseStmt, DefaultStmt>(*First)) {
491 /*
492 * (case X|label_X|default): if (Cond) return BoolLiteral;
493 * return !BoolLiteral
494 */
495 Stmt *SubStmt =
496 isa<LabelStmt>(*First) ? cast<LabelStmt>(*First)->getSubStmt()
497 : isa<CaseStmt>(*First) ? cast<CaseStmt>(*First)->getSubStmt()
498 : cast<DefaultStmt>(*First)->getSubStmt();
499 auto *SubIf = dyn_cast<IfStmt>(SubStmt);
500 if (SubIf && !SubIf->getElse() && !SubIf->hasInitStorage() &&
501 !SubIf->hasVarStorage() && !SubIf->isConsteval()) {
502 ExprAndBool ThenReturnBool =
504 if (ThenReturnBool &&
505 ThenReturnBool.Bool != TrailingReturnBool.Bool) {
506 Check->replaceCompoundReturnWithCondition(
507 Context, cast<ReturnStmt>(*Second), TrailingReturnBool.Bool,
508 SubIf, ThenReturnBool.Item);
509 }
510 }
511 }
512 }
513 return true;
514 }
515
516 bool isExpectedUnaryLNot(const Expr *E) {
517 return !Check->canBeBypassed(E) && isa<UnaryOperator>(E) &&
518 cast<UnaryOperator>(E)->getOpcode() == UO_LNot;
519 }
520
521 bool isExpectedBinaryOp(const Expr *E) {
522 const auto *BinaryOp = dyn_cast<BinaryOperator>(E);
523 return !Check->canBeBypassed(E) && BinaryOp && BinaryOp->isLogicalOp() &&
524 BinaryOp->getType()->isBooleanType();
525 }
526
527 template <typename Functor>
528 static bool checkEitherSide(const BinaryOperator *BO, Functor Func) {
529 return Func(BO->getLHS()) || Func(BO->getRHS());
530 }
531
532 bool nestedDemorgan(const Expr *E, unsigned NestingLevel) {
533 const auto *BO = dyn_cast<BinaryOperator>(E->IgnoreUnlessSpelledInSource());
534 if (!BO)
535 return false;
536 if (!BO->getType()->isBooleanType())
537 return false;
538 switch (BO->getOpcode()) {
539 case BO_LT:
540 case BO_GT:
541 case BO_LE:
542 case BO_GE:
543 case BO_EQ:
544 case BO_NE:
545 return true;
546 case BO_LAnd:
547 case BO_LOr:
548 return checkEitherSide(
549 BO,
550 [this](const Expr *E) { return isExpectedUnaryLNot(E); }) ||
551 (NestingLevel &&
552 checkEitherSide(BO, [this, NestingLevel](const Expr *E) {
553 return nestedDemorgan(E, NestingLevel - 1);
554 }));
555 default:
556 return false;
557 }
558 }
559
560 bool TraverseUnaryOperator(UnaryOperator *Op) {
561 if (!Check->SimplifyDeMorgan || Op->getOpcode() != UO_LNot)
562 return Base::TraverseUnaryOperator(Op);
563 const Expr *SubImp = Op->getSubExpr()->IgnoreImplicit();
564 const auto *Parens = dyn_cast<ParenExpr>(SubImp);
565 const Expr *SubExpr =
566 Parens ? Parens->getSubExpr()->IgnoreImplicit() : SubImp;
567 if (!isExpectedBinaryOp(SubExpr))
568 return Base::TraverseUnaryOperator(Op);
569 const auto *BinaryOp = cast<BinaryOperator>(SubExpr);
570 if (Check->SimplifyDeMorganRelaxed ||
572 BinaryOp,
573 [this](const Expr *E) { return isExpectedUnaryLNot(E); }) ||
575 BinaryOp, [this](const Expr *E) { return nestedDemorgan(E, 1); })) {
576 if (Check->reportDeMorgan(Context, Op, BinaryOp, !IsProcessing, parent(),
577 Parens) &&
578 !Check->areDiagsSelfContained()) {
579 llvm::SaveAndRestore RAII(IsProcessing, true);
580 return Base::TraverseUnaryOperator(Op);
581 }
582 }
583 return Base::TraverseUnaryOperator(Op);
584 }
585
586private:
587 bool IsProcessing = false;
589 SmallVector<Stmt *, 32> StmtStack;
590 ASTContext &Context;
591};
592
594 ClangTidyContext *Context)
595 : ClangTidyCheck(Name, Context),
596 IgnoreMacros(Options.get("IgnoreMacros", false)),
597 ChainedConditionalReturn(Options.get("ChainedConditionalReturn", false)),
598 ChainedConditionalAssignment(
599 Options.get("ChainedConditionalAssignment", false)),
600 SimplifyDeMorgan(Options.get("SimplifyDeMorgan", true)),
601 SimplifyDeMorganRelaxed(Options.get("SimplifyDeMorganRelaxed", false)) {
602 if (SimplifyDeMorganRelaxed && !SimplifyDeMorgan)
603 configurationDiag("%0: 'SimplifyDeMorganRelaxed' cannot be enabled "
604 "without 'SimplifyDeMorgan' enabled")
605 << Name;
606}
607
608static bool containsBoolLiteral(const Expr *E) {
609 if (!E)
610 return false;
611 E = E->IgnoreParenImpCasts();
612 if (isa<CXXBoolLiteralExpr>(E))
613 return true;
614 if (const auto *BinOp = dyn_cast<BinaryOperator>(E))
615 return containsBoolLiteral(BinOp->getLHS()) ||
616 containsBoolLiteral(BinOp->getRHS());
617 if (const auto *UnaryOp = dyn_cast<UnaryOperator>(E))
618 return containsBoolLiteral(UnaryOp->getSubExpr());
619 return false;
620}
621
622void SimplifyBooleanExprCheck::reportBinOp(const ASTContext &Context,
623 const BinaryOperator *Op) {
624 const auto *LHS = Op->getLHS()->IgnoreParenImpCasts();
625 const auto *RHS = Op->getRHS()->IgnoreParenImpCasts();
626
627 const CXXBoolLiteralExpr *Bool = nullptr;
628 const Expr *Other = nullptr;
629 if ((Bool = dyn_cast<CXXBoolLiteralExpr>(LHS)) != nullptr)
630 Other = RHS;
631 else if ((Bool = dyn_cast<CXXBoolLiteralExpr>(RHS)) != nullptr)
632 Other = LHS;
633 else
634 return;
635
636 if (Bool->getBeginLoc().isMacroID())
637 return;
638
639 // FIXME: why do we need this?
640 if (!isa<CXXBoolLiteralExpr>(Other) && containsBoolLiteral(Other))
641 return;
642
643 bool BoolValue = Bool->getValue();
644
645 auto ReplaceWithExpression = [this, &Context, LHS, RHS,
646 Bool](const Expr *ReplaceWith, bool Negated) {
647 std::string Replacement =
648 replacementExpression(Context, Negated, ReplaceWith);
649 SourceRange Range(LHS->getBeginLoc(), RHS->getEndLoc());
650 issueDiag(Context, Bool->getBeginLoc(), SimplifyOperatorDiagnostic, Range,
651 Replacement);
652 };
653
654 switch (Op->getOpcode()) {
655 case BO_LAnd:
656 if (BoolValue)
657 // expr && true -> expr
658 ReplaceWithExpression(Other, /*Negated=*/false);
659 else
660 // expr && false -> false
661 ReplaceWithExpression(Bool, /*Negated=*/false);
662 break;
663 case BO_LOr:
664 if (BoolValue)
665 // expr || true -> true
666 ReplaceWithExpression(Bool, /*Negated=*/false);
667 else
668 // expr || false -> expr
669 ReplaceWithExpression(Other, /*Negated=*/false);
670 break;
671 case BO_EQ:
672 // expr == true -> expr, expr == false -> !expr
673 ReplaceWithExpression(Other, /*Negated=*/!BoolValue);
674 break;
675 case BO_NE:
676 // expr != true -> !expr, expr != false -> expr
677 ReplaceWithExpression(Other, /*Negated=*/BoolValue);
678 break;
679 default:
680 break;
681 }
682}
683
685 Options.store(Opts, "IgnoreMacros", IgnoreMacros);
686 Options.store(Opts, "ChainedConditionalReturn", ChainedConditionalReturn);
687 Options.store(Opts, "ChainedConditionalAssignment",
688 ChainedConditionalAssignment);
689 Options.store(Opts, "SimplifyDeMorgan", SimplifyDeMorgan);
690 Options.store(Opts, "SimplifyDeMorganRelaxed", SimplifyDeMorganRelaxed);
691}
692
694 Finder->addMatcher(translationUnitDecl(), this);
695}
696
697void SimplifyBooleanExprCheck::check(const MatchFinder::MatchResult &Result) {
698 Visitor(this, *Result.Context).traverse();
699}
700
701bool SimplifyBooleanExprCheck::canBeBypassed(const Stmt *S) const {
702 return IgnoreMacros && S->getBeginLoc().isMacroID();
703}
704
705void SimplifyBooleanExprCheck::issueDiag(const ASTContext &Context,
706 SourceLocation Loc,
707 StringRef Description,
708 SourceRange ReplacementRange,
709 StringRef Replacement) {
710 CharSourceRange CharRange =
711 Lexer::makeFileCharRange(CharSourceRange::getTokenRange(ReplacementRange),
712 Context.getSourceManager(), getLangOpts());
713
714 DiagnosticBuilder Diag = diag(Loc, Description);
715 if (!containsDiscardedTokens(Context, CharRange))
716 Diag << FixItHint::CreateReplacement(CharRange, Replacement);
717}
718
719void SimplifyBooleanExprCheck::replaceWithThenStatement(
720 const ASTContext &Context, const IfStmt *IfStatement,
721 const Expr *BoolLiteral) {
722 issueDiag(Context, BoolLiteral->getBeginLoc(), SimplifyConditionDiagnostic,
723 IfStatement->getSourceRange(),
724 getText(Context, *IfStatement->getThen()));
725}
726
727void SimplifyBooleanExprCheck::replaceWithElseStatement(
728 const ASTContext &Context, const IfStmt *IfStatement,
729 const Expr *BoolLiteral) {
730 const Stmt *ElseStatement = IfStatement->getElse();
731 issueDiag(Context, BoolLiteral->getBeginLoc(), SimplifyConditionDiagnostic,
732 IfStatement->getSourceRange(),
733 ElseStatement ? getText(Context, *ElseStatement) : "");
734}
735
736void SimplifyBooleanExprCheck::replaceWithCondition(
737 const ASTContext &Context, const ConditionalOperator *Ternary,
738 bool Negated) {
739 std::string Replacement =
740 replacementExpression(Context, Negated, Ternary->getCond());
741 issueDiag(Context, Ternary->getTrueExpr()->getBeginLoc(),
742 "redundant boolean literal in ternary expression result",
743 Ternary->getSourceRange(), Replacement);
744}
745
746void SimplifyBooleanExprCheck::replaceWithReturnCondition(
747 const ASTContext &Context, const IfStmt *If, const Expr *BoolLiteral,
748 bool Negated) {
749 StringRef Terminator = isa<CompoundStmt>(If->getElse()) ? ";" : "";
750 std::string Condition =
751 replacementExpression(Context, Negated, If->getCond());
752 std::string Replacement = ("return " + Condition + Terminator).str();
753 SourceLocation Start = BoolLiteral->getBeginLoc();
754 issueDiag(Context, Start, SimplifyConditionalReturnDiagnostic,
755 If->getSourceRange(), Replacement);
756}
757
758void SimplifyBooleanExprCheck::replaceCompoundReturnWithCondition(
759 const ASTContext &Context, const ReturnStmt *Ret, bool Negated,
760 const IfStmt *If, const Expr *ThenReturn) {
761 const std::string Replacement =
762 "return " + replacementExpression(Context, Negated, If->getCond());
763 issueDiag(Context, ThenReturn->getBeginLoc(),
765 SourceRange(If->getBeginLoc(), Ret->getEndLoc()), Replacement);
766}
767
768void SimplifyBooleanExprCheck::replaceWithAssignment(const ASTContext &Context,
769 const IfStmt *IfAssign,
770 const Expr *Var,
771 SourceLocation Loc,
772 bool Negated) {
773 SourceRange Range = IfAssign->getSourceRange();
774 StringRef VariableName = getText(Context, *Var);
775 StringRef Terminator = isa<CompoundStmt>(IfAssign->getElse()) ? ";" : "";
776 std::string Condition =
777 replacementExpression(Context, Negated, IfAssign->getCond());
778 std::string Replacement =
779 (VariableName + " = " + Condition + Terminator).str();
780 issueDiag(Context, Loc, "redundant boolean literal in conditional assignment",
781 Range, Replacement);
782}
783
784/// Swaps a \c BinaryOperator opcode from `&&` to `||` or vice-versa.
786 const BinaryOperator *BO) {
787 assert(BO->isLogicalOp());
788 if (BO->getOperatorLoc().isMacroID())
789 return true;
790 Output.push_back(FixItHint::CreateReplacement(
791 BO->getOperatorLoc(), BO->getOpcode() == BO_LAnd ? "||" : "&&"));
792 return false;
793}
794
795static BinaryOperatorKind getDemorganFlippedOperator(BinaryOperatorKind BO) {
796 assert(BinaryOperator::isLogicalOp(BO));
797 return BO == BO_LAnd ? BO_LOr : BO_LAnd;
798}
799
800static bool flipDemorganSide(SmallVectorImpl<FixItHint> &Fixes,
801 const ASTContext &Ctx, const Expr *E,
802 std::optional<BinaryOperatorKind> OuterBO);
803
804/// Inverts \p BinOp, Removing \p Parens if they exist and are safe to remove.
805/// returns \c true if there is any issue building the Fixes, \c false
806/// otherwise.
807static bool
808flipDemorganBinaryOperator(SmallVectorImpl<FixItHint> &Fixes,
809 const ASTContext &Ctx, const BinaryOperator *BinOp,
810 std::optional<BinaryOperatorKind> OuterBO,
811 const ParenExpr *Parens = nullptr) {
812 switch (BinOp->getOpcode()) {
813 case BO_LAnd:
814 case BO_LOr: {
815 // if we have 'a && b' or 'a || b', use demorgan to flip it to '!a || !b'
816 // or '!a && !b'.
817 if (flipDemorganOperator(Fixes, BinOp))
818 return true;
819 auto NewOp = getDemorganFlippedOperator(BinOp->getOpcode());
820 if (OuterBO) {
821 // The inner parens are technically needed in a fix for
822 // `!(!A1 && !(A2 || A3)) -> (A1 || (A2 && A3))`,
823 // however this would trip the LogicalOpParentheses warning.
824 // FIXME: Make this user configurable or detect if that warning is
825 // enabled.
826 constexpr bool LogicalOpParentheses = true;
827 if (((*OuterBO == NewOp) || (!LogicalOpParentheses &&
828 (*OuterBO == BO_LOr && NewOp == BO_LAnd))) &&
829 Parens) {
830 if (!Parens->getLParen().isMacroID() &&
831 !Parens->getRParen().isMacroID()) {
832 Fixes.push_back(FixItHint::CreateRemoval(Parens->getLParen()));
833 Fixes.push_back(FixItHint::CreateRemoval(Parens->getRParen()));
834 }
835 }
836 if (*OuterBO == BO_LAnd && NewOp == BO_LOr && !Parens) {
837 Fixes.push_back(FixItHint::CreateInsertion(BinOp->getBeginLoc(), "("));
838 Fixes.push_back(FixItHint::CreateInsertion(
839 Lexer::getLocForEndOfToken(BinOp->getEndLoc(), 0,
840 Ctx.getSourceManager(),
841 Ctx.getLangOpts()),
842 ")"));
843 }
844 }
845 if (flipDemorganSide(Fixes, Ctx, BinOp->getLHS(), NewOp) ||
846 flipDemorganSide(Fixes, Ctx, BinOp->getRHS(), NewOp))
847 return true;
848 return false;
849 };
850 case BO_LT:
851 case BO_GT:
852 case BO_LE:
853 case BO_GE:
854 case BO_EQ:
855 case BO_NE:
856 // For comparison operators, just negate the comparison.
857 if (BinOp->getOperatorLoc().isMacroID())
858 return true;
859 Fixes.push_back(FixItHint::CreateReplacement(
860 BinOp->getOperatorLoc(),
861 BinaryOperator::getOpcodeStr(
862 BinaryOperator::negateComparisonOp(BinOp->getOpcode()))));
863 return false;
864 default:
865 // for any other binary operator, just use logical not and wrap in
866 // parens.
867 if (Parens) {
868 if (Parens->getBeginLoc().isMacroID())
869 return true;
870 Fixes.push_back(FixItHint::CreateInsertion(Parens->getBeginLoc(), "!"));
871 } else {
872 if (BinOp->getBeginLoc().isMacroID() || BinOp->getEndLoc().isMacroID())
873 return true;
874 Fixes.append({FixItHint::CreateInsertion(BinOp->getBeginLoc(), "!("),
875 FixItHint::CreateInsertion(
876 Lexer::getLocForEndOfToken(BinOp->getEndLoc(), 0,
877 Ctx.getSourceManager(),
878 Ctx.getLangOpts()),
879 ")")});
880 }
881 break;
882 }
883 return false;
884}
885
886static bool flipDemorganSide(SmallVectorImpl<FixItHint> &Fixes,
887 const ASTContext &Ctx, const Expr *E,
888 std::optional<BinaryOperatorKind> OuterBO) {
889 if (isa<UnaryOperator>(E) && cast<UnaryOperator>(E)->getOpcode() == UO_LNot) {
890 // if we have a not operator, '!a', just remove the '!'.
891 if (cast<UnaryOperator>(E)->getOperatorLoc().isMacroID())
892 return true;
893 Fixes.push_back(
894 FixItHint::CreateRemoval(cast<UnaryOperator>(E)->getOperatorLoc()));
895 return false;
896 }
897 if (const auto *BinOp = dyn_cast<BinaryOperator>(E)) {
898 return flipDemorganBinaryOperator(Fixes, Ctx, BinOp, OuterBO);
899 }
900 if (const auto *Paren = dyn_cast<ParenExpr>(E)) {
901 if (const auto *BinOp = dyn_cast<BinaryOperator>(Paren->getSubExpr())) {
902 return flipDemorganBinaryOperator(Fixes, Ctx, BinOp, OuterBO, Paren);
903 }
904 }
905 // Fallback case just insert a logical not operator.
906 if (E->getBeginLoc().isMacroID())
907 return true;
908 Fixes.push_back(FixItHint::CreateInsertion(E->getBeginLoc(), "!"));
909 return false;
910}
911
912static bool shouldRemoveParens(const Stmt *Parent,
913 BinaryOperatorKind NewOuterBinary,
914 const ParenExpr *Parens) {
915 if (!Parens)
916 return false;
917 if (!Parent)
918 return true;
919 switch (Parent->getStmtClass()) {
920 case Stmt::BinaryOperatorClass: {
921 const auto *BO = cast<BinaryOperator>(Parent);
922 if (BO->isAssignmentOp())
923 return true;
924 if (BO->isCommaOp())
925 return true;
926 if (BO->getOpcode() == NewOuterBinary)
927 return true;
928 return false;
929 }
930 case Stmt::UnaryOperatorClass:
931 case Stmt::CXXRewrittenBinaryOperatorClass:
932 return false;
933 default:
934 return true;
935 }
936}
937
938bool SimplifyBooleanExprCheck::reportDeMorgan(const ASTContext &Context,
939 const UnaryOperator *Outer,
940 const BinaryOperator *Inner,
941 bool TryOfferFix,
942 const Stmt *Parent,
943 const ParenExpr *Parens) {
944 assert(Outer);
945 assert(Inner);
946 assert(Inner->isLogicalOp());
947
948 auto Diag =
949 diag(Outer->getBeginLoc(),
950 "boolean expression can be simplified by DeMorgan's theorem");
951 Diag << Outer->getSourceRange();
952 // If we have already fixed this with a previous fix, don't attempt any fixes
953 if (!TryOfferFix)
954 return false;
955 if (Outer->getOperatorLoc().isMacroID())
956 return false;
957 SmallVector<FixItHint> Fixes;
958 auto NewOpcode = getDemorganFlippedOperator(Inner->getOpcode());
959 if (shouldRemoveParens(Parent, NewOpcode, Parens)) {
960 Fixes.push_back(FixItHint::CreateRemoval(
961 SourceRange(Outer->getOperatorLoc(), Parens->getLParen())));
962 Fixes.push_back(FixItHint::CreateRemoval(Parens->getRParen()));
963 } else {
964 Fixes.push_back(FixItHint::CreateRemoval(Outer->getOperatorLoc()));
965 }
966 if (flipDemorganOperator(Fixes, Inner))
967 return false;
968 if (flipDemorganSide(Fixes, Context, Inner->getLHS(), NewOpcode) ||
969 flipDemorganSide(Fixes, Context, Inner->getRHS(), NewOpcode))
970 return false;
971 Diag << Fixes;
972 return true;
973}
974} // namespace clang::tidy::readability
const Expr * E
std::pair< Context, Canceler > Inner
llvm::SmallString< 256U > Name
const Node * Parent
std::string Text
CharSourceRange Range
SourceRange for the file name.
SourceLocation Loc
std::string Condition
Condition used after the preprocessor directive.
::clang::DynTypedNode Node
std::string Output
Definition: TraceTests.cpp:159
void store(ClangTidyOptions::OptionMap &Options, StringRef LocalName, StringRef Value) const
Stores an option with the check-local name LocalName with string value Value to Options.
Base class for all clang-tidy checks.
DiagnosticBuilder diag(SourceLocation Loc, StringRef Description, DiagnosticIDs::Level Level=DiagnosticIDs::Warning)
Add a diagnostic with the check's name.
DiagnosticBuilder configurationDiag(StringRef Description, DiagnosticIDs::Level Level=DiagnosticIDs::Warning) const
Adds a diagnostic to report errors in the check's configuration.
bool areDiagsSelfContained() const
Returns true when the check is run in a use case when only 1 fix will be applied at a time.
const LangOptions & getLangOpts() const
Returns the language options from the context.
Every ClangTidyCheck reports errors through a DiagnosticsEngine provided by this context.
Visitor(SimplifyBooleanExprCheck *Check, ASTContext &Context)
static std::optional< bool > getAsBoolLiteral(const Expr *E, bool FilterMacro)
static bool checkEitherSide(const BinaryOperator *BO, Functor Func)
static auto checkSingleStatement(Stmt *S, Functor F) -> decltype(F(S))
If S is not a CompoundStmt, applies F on S, otherwise if there is only 1 statement in the CompoundStm...
static ExprAndBool parseReturnLiteralBool(const Stmt *S)
Detect's return (true|false|!true|!false);.
Looks for boolean expressions involving boolean constants and simplifies them to use the appropriate ...
void storeOptions(ClangTidyOptions::OptionMap &Opts) override
Should store all options supported by this check with their current values or default values for opti...
void registerMatchers(ast_matchers::MatchFinder *Finder) override
Override this to register AST matchers with Finder.
SimplifyBooleanExprCheck(StringRef Name, ClangTidyContext *Context)
void check(const ast_matchers::MatchFinder::MatchResult &Result) override
ClangTidyChecks that register ASTMatchers should do the actual work in here.
static std::string replacementExpression(const ASTContext &Context, bool Negated, const Expr *E)
static bool needsZeroComparison(const Expr *E)
static bool containsBoolLiteral(const Expr *E)
static StringRef negatedOperator(const BinaryOperator *BinOp)
static bool shouldRemoveParens(const Stmt *Parent, BinaryOperatorKind NewOuterBinary, const ParenExpr *Parens)
static std::string compareExpressionToConstant(const ASTContext &Context, const Expr *E, bool Negated, const char *Constant)
static std::pair< BinaryOperatorKind, BinaryOperatorKind > Opposites[]
static bool needsParensAfterUnaryNegation(const Expr *E)
static bool containsDiscardedTokens(const ASTContext &Context, CharSourceRange CharRange)
static StringRef getOperatorName(OverloadedOperatorKind OpKind)
static constexpr char SimplifyConditionDiagnostic[]
static bool flipDemorganOperator(llvm::SmallVectorImpl< FixItHint > &Output, const BinaryOperator *BO)
Swaps a BinaryOperator opcode from && to || or vice-versa.
static bool needsNullPtrComparison(const Expr *E)
static constexpr char SimplifyConditionalReturnDiagnostic[]
static bool flipDemorganBinaryOperator(SmallVectorImpl< FixItHint > &Fixes, const ASTContext &Ctx, const BinaryOperator *BinOp, std::optional< BinaryOperatorKind > OuterBO, const ParenExpr *Parens=nullptr)
Inverts BinOp, Removing Parens if they exist and are safe to remove.
static constexpr char SimplifyOperatorDiagnostic[]
static bool flipDemorganSide(SmallVectorImpl< FixItHint > &Fixes, const ASTContext &Ctx, const Expr *E, std::optional< BinaryOperatorKind > OuterBO)
static BinaryOperatorKind getDemorganFlippedOperator(BinaryOperatorKind BO)
static std::string asBool(StringRef Text, bool NeedsStaticCast)
static std::pair< OverloadedOperatorKind, OverloadedOperatorKind > OppositeOverloads[]
static std::string compareExpressionToZero(const ASTContext &Context, const Expr *E, bool Negated)
static bool isMacroID(SourceRange R)
static std::string compareExpressionToNullPtr(const ASTContext &Context, const Expr *E, bool Negated)
static std::pair< OverloadedOperatorKind, StringRef > OperatorNames[]
static bool needsStaticCast(const Expr *E)
llvm::StringMap< ClangTidyValue > OptionMap