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