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