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

source code of clang-tools-extra/clang-tidy/readability/SimplifyBooleanExprCheck.cpp