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