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