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 | |
18 | using namespace clang::ast_matchers; |
19 | |
20 | namespace clang::tidy::readability { |
21 | |
22 | namespace { |
23 | |
24 | StringRef 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 | |
30 | template <typename T> StringRef getText(const ASTContext &Context, T &Node) { |
31 | return getText(Context, Node.getSourceRange()); |
32 | } |
33 | |
34 | } // namespace |
35 | |
36 | static constexpr char SimplifyOperatorDiagnostic[] = |
37 | "redundant boolean literal supplied to boolean operator" ; |
38 | static constexpr char SimplifyConditionDiagnostic[] = |
39 | "redundant boolean literal in if statement condition" ; |
40 | static constexpr char SimplifyConditionalReturnDiagnostic[] = |
41 | "redundant boolean literal in conditional return statement" ; |
42 | |
43 | static 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 | |
55 | static std::pair<BinaryOperatorKind, BinaryOperatorKind> Opposites[] = { |
56 | {BO_LT, BO_GE}, {BO_GT, BO_LE}, {BO_EQ, BO_NE}}; |
57 | |
58 | static 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 | |
69 | static std::pair<OverloadedOperatorKind, StringRef> OperatorNames[] = { |
70 | {OO_EqualEqual, "==" }, {OO_ExclaimEqual, "!=" }, {OO_Less, "<" }, |
71 | {OO_GreaterEqual, ">=" }, {OO_Greater, ">" }, {OO_LessEqual, "<=" }}; |
72 | |
73 | static StringRef getOperatorName(OverloadedOperatorKind OpKind) { |
74 | for (auto Name : OperatorNames) { |
75 | if (Name.first == OpKind) |
76 | return Name.second; |
77 | } |
78 | |
79 | return {}; |
80 | } |
81 | |
82 | static std::pair<OverloadedOperatorKind, OverloadedOperatorKind> |
83 | OppositeOverloads[] = {{OO_EqualEqual, OO_ExclaimEqual}, |
84 | {OO_Less, OO_GreaterEqual}, |
85 | {OO_Greater, OO_LessEqual}}; |
86 | |
87 | static 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 | |
98 | static 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 | |
105 | static 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 | |
113 | static 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 | |
120 | static 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 | |
139 | static 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 | |
150 | static 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 | |
156 | static std::string compareExpressionToZero(const ASTContext &Context, |
157 | const Expr *E, bool Negated) { |
158 | return compareExpressionToConstant(Context, E, Negated, Constant: "0" ); |
159 | } |
160 | |
161 | static 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 | |
239 | static 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 | |
259 | class SimplifyBooleanExprCheck::Visitor : public RecursiveASTVisitor<Visitor> { |
260 | using Base = RecursiveASTVisitor<Visitor>; |
261 | |
262 | public: |
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 | |
582 | private: |
583 | bool IsProcessing = false; |
584 | SimplifyBooleanExprCheck *Check; |
585 | SmallVector<Stmt *, 32> StmtStack; |
586 | ASTContext &Context; |
587 | }; |
588 | |
589 | SimplifyBooleanExprCheck::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 | |
604 | static 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 | |
618 | void 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 | |
680 | void 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 | |
689 | void SimplifyBooleanExprCheck::registerMatchers(MatchFinder *Finder) { |
690 | Finder->addMatcher(NodeMatch: translationUnitDecl(), Action: this); |
691 | } |
692 | |
693 | void SimplifyBooleanExprCheck::check(const MatchFinder::MatchResult &Result) { |
694 | Visitor(this, *Result.Context).traverse(); |
695 | } |
696 | |
697 | void 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 | |
711 | void 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 | |
719 | void 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 | |
728 | void 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 | |
738 | void 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 | |
750 | void 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 | |
760 | void 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. |
777 | static 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 | |
787 | static BinaryOperatorKind getDemorganFlippedOperator(BinaryOperatorKind BO) { |
788 | assert(BinaryOperator::isLogicalOp(BO)); |
789 | return BO == BO_LAnd ? BO_LOr : BO_LAnd; |
790 | } |
791 | |
792 | static 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. |
799 | static bool |
800 | flipDemorganBinaryOperator(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 | |
878 | static 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 | |
904 | static 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 | |
930 | bool 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 | |