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