Bug Summary

File:build/llvm-toolchain-snapshot-15~++20220420111733+e13d2efed663/clang-tools-extra/clang-tidy/readability/SimplifyBooleanExprCheck.cpp
Warning:line 417, column 43
Called C++ object pointer is null

Annotated Source Code

Press '?' to see keyboard shortcuts

clang -cc1 -cc1 -triple x86_64-pc-linux-gnu -analyze -disable-free -clear-ast-before-backend -disable-llvm-verifier -discard-value-names -main-file-name SimplifyBooleanExprCheck.cpp -analyzer-store=region -analyzer-opt-analyze-nested-blocks -analyzer-checker=core -analyzer-checker=apiModeling -analyzer-checker=unix -analyzer-checker=deadcode -analyzer-checker=cplusplus -analyzer-checker=security.insecureAPI.UncheckedReturn -analyzer-checker=security.insecureAPI.getpw -analyzer-checker=security.insecureAPI.gets -analyzer-checker=security.insecureAPI.mktemp -analyzer-checker=security.insecureAPI.mkstemp -analyzer-checker=security.insecureAPI.vfork -analyzer-checker=nullability.NullPassedToNonnull -analyzer-checker=nullability.NullReturnedFromNonnull -analyzer-output plist -w -setup-static-analyzer -analyzer-config-compatibility-mode=true -mrelocation-model pic -pic-level 2 -mframe-pointer=none -relaxed-aliasing -fmath-errno -ffp-contract=on -fno-rounding-math -mconstructor-aliases -funwind-tables=2 -target-cpu x86-64 -tune-cpu generic -debugger-tuning=gdb -ffunction-sections -fdata-sections -fcoverage-compilation-dir=/build/llvm-toolchain-snapshot-15~++20220420111733+e13d2efed663/build-llvm/tools/clang/stage2-bins -resource-dir /usr/lib/llvm-15/lib/clang/15.0.0 -D _DEBUG -D _GNU_SOURCE -D __STDC_CONSTANT_MACROS -D __STDC_FORMAT_MACROS -D __STDC_LIMIT_MACROS -I tools/clang/tools/extra/clang-tidy/readability -I /build/llvm-toolchain-snapshot-15~++20220420111733+e13d2efed663/clang-tools-extra/clang-tidy/readability -I tools/clang/tools/extra/clang-tidy -I /build/llvm-toolchain-snapshot-15~++20220420111733+e13d2efed663/clang/include -I tools/clang/include -I include -I /build/llvm-toolchain-snapshot-15~++20220420111733+e13d2efed663/llvm/include -D _FORTIFY_SOURCE=2 -D NDEBUG -U NDEBUG -internal-isystem /usr/lib/gcc/x86_64-linux-gnu/10/../../../../include/c++/10 -internal-isystem /usr/lib/gcc/x86_64-linux-gnu/10/../../../../include/x86_64-linux-gnu/c++/10 -internal-isystem /usr/lib/gcc/x86_64-linux-gnu/10/../../../../include/c++/10/backward -internal-isystem /usr/lib/llvm-15/lib/clang/15.0.0/include -internal-isystem /usr/local/include -internal-isystem /usr/lib/gcc/x86_64-linux-gnu/10/../../../../x86_64-linux-gnu/include -internal-externc-isystem /usr/include/x86_64-linux-gnu -internal-externc-isystem /include -internal-externc-isystem /usr/include -fmacro-prefix-map=/build/llvm-toolchain-snapshot-15~++20220420111733+e13d2efed663/build-llvm/tools/clang/stage2-bins=build-llvm/tools/clang/stage2-bins -fmacro-prefix-map=/build/llvm-toolchain-snapshot-15~++20220420111733+e13d2efed663/= -fcoverage-prefix-map=/build/llvm-toolchain-snapshot-15~++20220420111733+e13d2efed663/build-llvm/tools/clang/stage2-bins=build-llvm/tools/clang/stage2-bins -fcoverage-prefix-map=/build/llvm-toolchain-snapshot-15~++20220420111733+e13d2efed663/= -O3 -Wno-unused-command-line-argument -Wno-unused-parameter -Wwrite-strings -Wno-missing-field-initializers -Wno-long-long -Wno-maybe-uninitialized -Wno-class-memaccess -Wno-redundant-move -Wno-pessimizing-move -Wno-noexcept-type -Wno-comment -std=c++14 -fdeprecated-macro -fdebug-compilation-dir=/build/llvm-toolchain-snapshot-15~++20220420111733+e13d2efed663/build-llvm/tools/clang/stage2-bins -fdebug-prefix-map=/build/llvm-toolchain-snapshot-15~++20220420111733+e13d2efed663/build-llvm/tools/clang/stage2-bins=build-llvm/tools/clang/stage2-bins -fdebug-prefix-map=/build/llvm-toolchain-snapshot-15~++20220420111733+e13d2efed663/= -ferror-limit 19 -fvisibility-inlines-hidden -stack-protector 2 -fgnuc-version=4.2.1 -fcolor-diagnostics -vectorize-loops -vectorize-slp -analyzer-output=html -analyzer-config stable-report-filename=true -faddrsig -D__GCC_HAVE_DWARF2_CFI_ASM=1 -o /tmp/scan-build-2022-04-20-140412-16051-1 -x c++ /build/llvm-toolchain-snapshot-15~++20220420111733+e13d2efed663/clang-tools-extra/clang-tidy/readability/SimplifyBooleanExprCheck.cpp
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 "SimplifyBooleanExprMatchers.h"
11#include "clang/AST/RecursiveASTVisitor.h"
12#include "clang/Lex/Lexer.h"
13
14#include <string>
15#include <utility>
16
17using namespace clang::ast_matchers;
18
19namespace clang {
20namespace tidy {
21namespace readability {
22
23namespace {
24
25StringRef getText(const MatchFinder::MatchResult &Result, SourceRange Range) {
26 return Lexer::getSourceText(CharSourceRange::getTokenRange(Range),
27 *Result.SourceManager,
28 Result.Context->getLangOpts());
29}
30
31template <typename T>
32StringRef getText(const MatchFinder::MatchResult &Result, T &Node) {
33 return getText(Result, Node.getSourceRange());
34}
35
36} // namespace
37
38static constexpr char ConditionThenStmtId[] = "if-bool-yields-then";
39static constexpr char ConditionElseStmtId[] = "if-bool-yields-else";
40static constexpr char TernaryId[] = "ternary-bool-yields-condition";
41static constexpr char TernaryNegatedId[] = "ternary-bool-yields-not-condition";
42static constexpr char IfReturnsBoolId[] = "if-return";
43static constexpr char IfReturnsNotBoolId[] = "if-not-return";
44static constexpr char ThenLiteralId[] = "then-literal";
45static constexpr char IfAssignVariableId[] = "if-assign-lvalue";
46static constexpr char IfAssignLocId[] = "if-assign-loc";
47static constexpr char IfAssignBoolId[] = "if-assign";
48static constexpr char IfAssignNotBoolId[] = "if-assign-not";
49static constexpr char IfAssignVarId[] = "if-assign-var";
50static constexpr char CompoundReturnId[] = "compound-return";
51static constexpr char CompoundIfId[] = "compound-if";
52static constexpr char CompoundBoolId[] = "compound-bool";
53static constexpr char CompoundNotBoolId[] = "compound-bool-not";
54static constexpr char CaseId[] = "case";
55static constexpr char CaseCompoundBoolId[] = "case-compound-bool";
56static constexpr char CaseCompoundNotBoolId[] = "case-compound-bool-not";
57static constexpr char DefaultId[] = "default";
58static constexpr char DefaultCompoundBoolId[] = "default-compound-bool";
59static constexpr char DefaultCompoundNotBoolId[] = "default-compound-bool-not";
60static constexpr char LabelId[] = "label";
61static constexpr char LabelCompoundBoolId[] = "label-compound-bool";
62static constexpr char LabelCompoundNotBoolId[] = "label-compound-bool-not";
63static constexpr char IfStmtId[] = "if";
64
65static constexpr char SimplifyOperatorDiagnostic[] =
66 "redundant boolean literal supplied to boolean operator";
67static constexpr char SimplifyConditionDiagnostic[] =
68 "redundant boolean literal in if statement condition";
69static constexpr char SimplifyConditionalReturnDiagnostic[] =
70 "redundant boolean literal in conditional return statement";
71
72static const Expr *getBoolLiteral(const MatchFinder::MatchResult &Result,
73 StringRef Id) {
74 if (const Expr *Literal = Result.Nodes.getNodeAs<CXXBoolLiteralExpr>(Id))
75 return Literal->getBeginLoc().isMacroID() ? nullptr : Literal;
76 if (const auto *Negated = Result.Nodes.getNodeAs<UnaryOperator>(Id)) {
77 if (Negated->getOpcode() == UO_LNot &&
78 isa<CXXBoolLiteralExpr>(Negated->getSubExpr()))
79 return Negated->getBeginLoc().isMacroID() ? nullptr : Negated;
80 }
81 return nullptr;
82}
83
84static internal::BindableMatcher<Stmt> literalOrNegatedBool(bool Value) {
85 return expr(
86 anyOf(cxxBoolLiteral(equals(Value)),
87 unaryOperator(hasUnaryOperand(cxxBoolLiteral(equals(!Value))),
88 hasOperatorName("!"))));
89}
90
91static internal::Matcher<Stmt> returnsBool(bool Value,
92 StringRef Id = "ignored") {
93 auto SimpleReturnsBool = returnStmt(has(literalOrNegatedBool(Value).bind(Id)))
94 .bind("returns-bool");
95 return anyOf(SimpleReturnsBool,
96 compoundStmt(statementCountIs(1), has(SimpleReturnsBool)));
97}
98
99static bool needsParensAfterUnaryNegation(const Expr *E) {
100 E = E->IgnoreImpCasts();
101 if (isa<BinaryOperator>(E) || isa<ConditionalOperator>(E))
102 return true;
103
104 if (const auto *Op = dyn_cast<CXXOperatorCallExpr>(E))
105 return Op->getNumArgs() == 2 && Op->getOperator() != OO_Call &&
106 Op->getOperator() != OO_Subscript;
107
108 return false;
109}
110
111static std::pair<BinaryOperatorKind, BinaryOperatorKind> Opposites[] = {
112 {BO_LT, BO_GE}, {BO_GT, BO_LE}, {BO_EQ, BO_NE}};
113
114static StringRef negatedOperator(const BinaryOperator *BinOp) {
115 const BinaryOperatorKind Opcode = BinOp->getOpcode();
116 for (auto NegatableOp : Opposites) {
117 if (Opcode == NegatableOp.first)
118 return BinOp->getOpcodeStr(NegatableOp.second);
119 if (Opcode == NegatableOp.second)
120 return BinOp->getOpcodeStr(NegatableOp.first);
121 }
122 return {};
123}
124
125static std::pair<OverloadedOperatorKind, StringRef> OperatorNames[] = {
126 {OO_EqualEqual, "=="}, {OO_ExclaimEqual, "!="}, {OO_Less, "<"},
127 {OO_GreaterEqual, ">="}, {OO_Greater, ">"}, {OO_LessEqual, "<="}};
128
129static StringRef getOperatorName(OverloadedOperatorKind OpKind) {
130 for (auto Name : OperatorNames) {
131 if (Name.first == OpKind)
132 return Name.second;
133 }
134
135 return {};
136}
137
138static std::pair<OverloadedOperatorKind, OverloadedOperatorKind>
139 OppositeOverloads[] = {{OO_EqualEqual, OO_ExclaimEqual},
140 {OO_Less, OO_GreaterEqual},
141 {OO_Greater, OO_LessEqual}};
142
143static StringRef negatedOperator(const CXXOperatorCallExpr *OpCall) {
144 const OverloadedOperatorKind Opcode = OpCall->getOperator();
145 for (auto NegatableOp : OppositeOverloads) {
146 if (Opcode == NegatableOp.first)
147 return getOperatorName(NegatableOp.second);
148 if (Opcode == NegatableOp.second)
149 return getOperatorName(NegatableOp.first);
150 }
151 return {};
152}
153
154static std::string asBool(StringRef Text, bool NeedsStaticCast) {
155 if (NeedsStaticCast)
156 return ("static_cast<bool>(" + Text + ")").str();
157
158 return std::string(Text);
159}
160
161static bool needsNullPtrComparison(const Expr *E) {
162 if (const auto *ImpCast = dyn_cast<ImplicitCastExpr>(E))
163 return ImpCast->getCastKind() == CK_PointerToBoolean ||
164 ImpCast->getCastKind() == CK_MemberPointerToBoolean;
165
166 return false;
167}
168
169static bool needsZeroComparison(const Expr *E) {
170 if (const auto *ImpCast = dyn_cast<ImplicitCastExpr>(E))
171 return ImpCast->getCastKind() == CK_IntegralToBoolean;
172
173 return false;
174}
175
176static bool needsStaticCast(const Expr *E) {
177 if (const auto *ImpCast = dyn_cast<ImplicitCastExpr>(E)) {
178 if (ImpCast->getCastKind() == CK_UserDefinedConversion &&
179 ImpCast->getSubExpr()->getType()->isBooleanType()) {
180 if (const auto *MemCall =
181 dyn_cast<CXXMemberCallExpr>(ImpCast->getSubExpr())) {
182 if (const auto *MemDecl =
183 dyn_cast<CXXConversionDecl>(MemCall->getMethodDecl())) {
184 if (MemDecl->isExplicit())
185 return true;
186 }
187 }
188 }
189 }
190
191 E = E->IgnoreImpCasts();
192 return !E->getType()->isBooleanType();
193}
194
195static std::string
196compareExpressionToConstant(const MatchFinder::MatchResult &Result,
197 const Expr *E, bool Negated, const char *Constant) {
198 E = E->IgnoreImpCasts();
199 const std::string ExprText =
200 (isa<BinaryOperator>(E) ? ("(" + getText(Result, *E) + ")")
201 : getText(Result, *E))
202 .str();
203 return ExprText + " " + (Negated ? "!=" : "==") + " " + Constant;
204}
205
206static std::string
207compareExpressionToNullPtr(const MatchFinder::MatchResult &Result,
208 const Expr *E, bool Negated) {
209 const char *NullPtr =
210 Result.Context->getLangOpts().CPlusPlus11 ? "nullptr" : "NULL";
211 return compareExpressionToConstant(Result, E, Negated, NullPtr);
212}
213
214static std::string
215compareExpressionToZero(const MatchFinder::MatchResult &Result, const Expr *E,
216 bool Negated) {
217 return compareExpressionToConstant(Result, E, Negated, "0");
218}
219
220static std::string replacementExpression(const MatchFinder::MatchResult &Result,
221 bool Negated, const Expr *E) {
222 E = E->IgnoreParenBaseCasts();
223 if (const auto *EC = dyn_cast<ExprWithCleanups>(E))
224 E = EC->getSubExpr();
225
226 const bool NeedsStaticCast = needsStaticCast(E);
227 if (Negated) {
228 if (const auto *UnOp = dyn_cast<UnaryOperator>(E)) {
229 if (UnOp->getOpcode() == UO_LNot) {
230 if (needsNullPtrComparison(UnOp->getSubExpr()))
231 return compareExpressionToNullPtr(Result, UnOp->getSubExpr(), true);
232
233 if (needsZeroComparison(UnOp->getSubExpr()))
234 return compareExpressionToZero(Result, UnOp->getSubExpr(), true);
235
236 return replacementExpression(Result, false, UnOp->getSubExpr());
237 }
238 }
239
240 if (needsNullPtrComparison(E))
241 return compareExpressionToNullPtr(Result, E, false);
242
243 if (needsZeroComparison(E))
244 return compareExpressionToZero(Result, E, false);
245
246 StringRef NegatedOperator;
247 const Expr *LHS = nullptr;
248 const Expr *RHS = nullptr;
249 if (const auto *BinOp = dyn_cast<BinaryOperator>(E)) {
250 NegatedOperator = negatedOperator(BinOp);
251 LHS = BinOp->getLHS();
252 RHS = BinOp->getRHS();
253 } else if (const auto *OpExpr = dyn_cast<CXXOperatorCallExpr>(E)) {
254 if (OpExpr->getNumArgs() == 2) {
255 NegatedOperator = negatedOperator(OpExpr);
256 LHS = OpExpr->getArg(0);
257 RHS = OpExpr->getArg(1);
258 }
259 }
260 if (!NegatedOperator.empty() && LHS && RHS)
261 return (asBool((getText(Result, *LHS) + " " + NegatedOperator + " " +
262 getText(Result, *RHS))
263 .str(),
264 NeedsStaticCast));
265
266 StringRef Text = getText(Result, *E);
267 if (!NeedsStaticCast && needsParensAfterUnaryNegation(E))
268 return ("!(" + Text + ")").str();
269
270 if (needsNullPtrComparison(E))
271 return compareExpressionToNullPtr(Result, E, false);
272
273 if (needsZeroComparison(E))
274 return compareExpressionToZero(Result, E, false);
275
276 return ("!" + asBool(Text, NeedsStaticCast));
277 }
278
279 if (const auto *UnOp = dyn_cast<UnaryOperator>(E)) {
280 if (UnOp->getOpcode() == UO_LNot) {
281 if (needsNullPtrComparison(UnOp->getSubExpr()))
282 return compareExpressionToNullPtr(Result, UnOp->getSubExpr(), false);
283
284 if (needsZeroComparison(UnOp->getSubExpr()))
285 return compareExpressionToZero(Result, UnOp->getSubExpr(), false);
286 }
287 }
288
289 if (needsNullPtrComparison(E))
290 return compareExpressionToNullPtr(Result, E, true);
291
292 if (needsZeroComparison(E))
293 return compareExpressionToZero(Result, E, true);
294
295 return asBool(getText(Result, *E), NeedsStaticCast);
296}
297
298static const Expr *stmtReturnsBool(const ReturnStmt *Ret, bool Negated) {
299 if (const auto *Bool = dyn_cast<CXXBoolLiteralExpr>(Ret->getRetValue())) {
300 if (Bool->getValue() == !Negated)
301 return Bool;
302 }
303 if (const auto *Unary = dyn_cast<UnaryOperator>(Ret->getRetValue())) {
304 if (Unary->getOpcode() == UO_LNot) {
305 if (const auto *Bool =
306 dyn_cast<CXXBoolLiteralExpr>(Unary->getSubExpr())) {
307 if (Bool->getValue() == Negated)
308 return Bool;
309 }
310 }
311 }
312
313 return nullptr;
314}
315
316static const Expr *stmtReturnsBool(const IfStmt *IfRet, bool Negated) {
317 if (IfRet->getElse() != nullptr)
318 return nullptr;
319
320 if (const auto *Ret = dyn_cast<ReturnStmt>(IfRet->getThen()))
321 return stmtReturnsBool(Ret, Negated);
322
323 if (const auto *Compound = dyn_cast<CompoundStmt>(IfRet->getThen())) {
324 if (Compound->size() == 1) {
325 if (const auto *CompoundRet = dyn_cast<ReturnStmt>(Compound->body_back()))
326 return stmtReturnsBool(CompoundRet, Negated);
327 }
328 }
329
330 return nullptr;
331}
332
333static bool containsDiscardedTokens(const MatchFinder::MatchResult &Result,
334 CharSourceRange CharRange) {
335 std::string ReplacementText =
336 Lexer::getSourceText(CharRange, *Result.SourceManager,
337 Result.Context->getLangOpts())
338 .str();
339 Lexer Lex(CharRange.getBegin(), Result.Context->getLangOpts(),
340 ReplacementText.data(), ReplacementText.data(),
341 ReplacementText.data() + ReplacementText.size());
342 Lex.SetCommentRetentionState(true);
343
344 Token Tok;
345 while (!Lex.LexFromRawLexer(Tok)) {
346 if (Tok.is(tok::TokenKind::comment) || Tok.is(tok::TokenKind::hash))
347 return true;
348 }
349
350 return false;
351}
352
353class SimplifyBooleanExprCheck::Visitor : public RecursiveASTVisitor<Visitor> {
354public:
355 Visitor(SimplifyBooleanExprCheck *Check,
356 const MatchFinder::MatchResult &Result)
357 : Check(Check), Result(Result) {}
358
359 bool VisitBinaryOperator(const BinaryOperator *Op) const {
360 Check->reportBinOp(Result, Op);
1
Calling 'SimplifyBooleanExprCheck::reportBinOp'
361 return true;
362 }
363
364private:
365 SimplifyBooleanExprCheck *Check;
366 const MatchFinder::MatchResult &Result;
367};
368
369SimplifyBooleanExprCheck::SimplifyBooleanExprCheck(StringRef Name,
370 ClangTidyContext *Context)
371 : ClangTidyCheck(Name, Context),
372 ChainedConditionalReturn(Options.get("ChainedConditionalReturn", false)),
373 ChainedConditionalAssignment(
374 Options.get("ChainedConditionalAssignment", false)) {}
375
376static bool containsBoolLiteral(const Expr *E) {
377 if (!E)
378 return false;
379 E = E->IgnoreParenImpCasts();
380 if (isa<CXXBoolLiteralExpr>(E))
381 return true;
382 if (const auto *BinOp = dyn_cast<BinaryOperator>(E))
383 return containsBoolLiteral(BinOp->getLHS()) ||
384 containsBoolLiteral(BinOp->getRHS());
385 if (const auto *UnaryOp = dyn_cast<UnaryOperator>(E))
386 return containsBoolLiteral(UnaryOp->getSubExpr());
387 return false;
388}
389
390void SimplifyBooleanExprCheck::reportBinOp(
391 const MatchFinder::MatchResult &Result, const BinaryOperator *Op) {
392 const auto *LHS = Op->getLHS()->IgnoreParenImpCasts();
393 const auto *RHS = Op->getRHS()->IgnoreParenImpCasts();
394
395 const CXXBoolLiteralExpr *Bool;
396 const Expr *Other;
397 if ((Bool = dyn_cast<CXXBoolLiteralExpr>(LHS)) != nullptr)
2
Assuming 'LHS' is a 'CXXBoolLiteralExpr'
3
Taking true branch
398 Other = RHS;
399 else if ((Bool = dyn_cast<CXXBoolLiteralExpr>(RHS)) != nullptr)
400 Other = LHS;
401 else
402 return;
403
404 if (Bool->getBeginLoc().isMacroID())
405 return;
406
407 // FIXME: why do we need this?
408 if (!isa<CXXBoolLiteralExpr>(Other) && containsBoolLiteral(Other))
4
Assuming 'Other' is not a 'CXXBoolLiteralExpr'
5
Taking false branch
409 return;
410
411 bool BoolValue = Bool->getValue();
412
413 auto ReplaceWithExpression = [this, &Result, LHS, RHS,
6
Null pointer value stored to 'ReplaceWithExpression.'
414 Bool](const Expr *ReplaceWith, bool Negated) {
415 std::string Replacement =
416 replacementExpression(Result, Negated, ReplaceWith);
417 SourceRange Range(LHS->getBeginLoc(), RHS->getEndLoc());
9
Called C++ object pointer is null
418 issueDiag(Result, Bool->getBeginLoc(), SimplifyOperatorDiagnostic, Range,
419 Replacement);
420 };
421
422 switch (Op->getOpcode()) {
7
Control jumps to 'case BO_NE:' at line 443
423 case BO_LAnd:
424 if (BoolValue)
425 // expr && true -> expr
426 ReplaceWithExpression(Other, /*Negated=*/false);
427 else
428 // expr && false -> false
429 ReplaceWithExpression(Bool, /*Negated=*/false);
430 break;
431 case BO_LOr:
432 if (BoolValue)
433 // expr || true -> true
434 ReplaceWithExpression(Bool, /*Negated=*/false);
435 else
436 // expr || false -> expr
437 ReplaceWithExpression(Other, /*Negated=*/false);
438 break;
439 case BO_EQ:
440 // expr == true -> expr, expr == false -> !expr
441 ReplaceWithExpression(Other, /*Negated=*/!BoolValue);
442 break;
443 case BO_NE:
444 // expr != true -> !expr, expr != false -> expr
445 ReplaceWithExpression(Other, /*Negated=*/BoolValue);
8
Calling 'operator()'
446 break;
447 default:
448 break;
449 }
450}
451
452void SimplifyBooleanExprCheck::matchBoolCondition(MatchFinder *Finder,
453 bool Value,
454 StringRef BooleanId) {
455 Finder->addMatcher(
456 ifStmt(hasCondition(literalOrNegatedBool(Value).bind(BooleanId)))
457 .bind(IfStmtId),
458 this);
459}
460
461void SimplifyBooleanExprCheck::matchTernaryResult(MatchFinder *Finder,
462 bool Value, StringRef Id) {
463 Finder->addMatcher(
464 conditionalOperator(hasTrueExpression(literalOrNegatedBool(Value)),
465 hasFalseExpression(literalOrNegatedBool(!Value)))
466 .bind(Id),
467 this);
468}
469
470void SimplifyBooleanExprCheck::matchIfReturnsBool(MatchFinder *Finder,
471 bool Value, StringRef Id) {
472 if (ChainedConditionalReturn)
473 Finder->addMatcher(ifStmt(hasThen(returnsBool(Value, ThenLiteralId)),
474 hasElse(returnsBool(!Value)))
475 .bind(Id),
476 this);
477 else
478 Finder->addMatcher(ifStmt(unless(hasParent(ifStmt())),
479 hasThen(returnsBool(Value, ThenLiteralId)),
480 hasElse(returnsBool(!Value)))
481 .bind(Id),
482 this);
483}
484
485void SimplifyBooleanExprCheck::matchIfAssignsBool(MatchFinder *Finder,
486 bool Value, StringRef Id) {
487 auto VarAssign = declRefExpr(hasDeclaration(decl().bind(IfAssignVarId)));
488 auto VarRef = declRefExpr(hasDeclaration(equalsBoundNode(IfAssignVarId)));
489 auto MemAssign = memberExpr(hasDeclaration(decl().bind(IfAssignVarId)));
490 auto MemRef = memberExpr(hasDeclaration(equalsBoundNode(IfAssignVarId)));
491 auto SimpleThen =
492 binaryOperator(hasOperatorName("="), hasLHS(anyOf(VarAssign, MemAssign)),
493 hasLHS(expr().bind(IfAssignVariableId)),
494 hasRHS(literalOrNegatedBool(Value).bind(IfAssignLocId)));
495 auto Then = anyOf(SimpleThen, compoundStmt(statementCountIs(1),
496 hasAnySubstatement(SimpleThen)));
497 auto SimpleElse =
498 binaryOperator(hasOperatorName("="), hasLHS(anyOf(VarRef, MemRef)),
499 hasRHS(literalOrNegatedBool(!Value)));
500 auto Else = anyOf(SimpleElse, compoundStmt(statementCountIs(1),
501 hasAnySubstatement(SimpleElse)));
502 if (ChainedConditionalAssignment)
503 Finder->addMatcher(ifStmt(hasThen(Then), hasElse(Else)).bind(Id), this);
504 else
505 Finder->addMatcher(
506 ifStmt(unless(hasParent(ifStmt())), hasThen(Then), hasElse(Else))
507 .bind(Id),
508 this);
509}
510
511static internal::Matcher<Stmt> ifReturnValue(bool Value) {
512 return ifStmt(hasThen(returnsBool(Value)), unless(hasElse(stmt())))
513 .bind(CompoundIfId);
514}
515
516static internal::Matcher<Stmt> returnNotValue(bool Value) {
517 return returnStmt(has(literalOrNegatedBool(!Value))).bind(CompoundReturnId);
518}
519
520void SimplifyBooleanExprCheck::matchCompoundIfReturnsBool(MatchFinder *Finder,
521 bool Value,
522 StringRef Id) {
523 if (ChainedConditionalReturn)
524 Finder->addMatcher(
525 compoundStmt(hasSubstatementSequence(ifReturnValue(Value),
526 returnNotValue(Value)))
527 .bind(Id),
528 this);
529 else
530 Finder->addMatcher(
531 compoundStmt(hasSubstatementSequence(ifStmt(hasThen(returnsBool(Value)),
532 unless(hasElse(stmt())),
533 unless(hasParent(ifStmt())))
534 .bind(CompoundIfId),
535 returnNotValue(Value)))
536 .bind(Id),
537 this);
538}
539
540void SimplifyBooleanExprCheck::matchCaseIfReturnsBool(MatchFinder *Finder,
541 bool Value,
542 StringRef Id) {
543 internal::Matcher<Stmt> CaseStmt =
544 caseStmt(hasSubstatement(ifReturnValue(Value))).bind(CaseId);
545 internal::Matcher<Stmt> CompoundStmt =
546 compoundStmt(hasSubstatementSequence(CaseStmt, returnNotValue(Value)))
547 .bind(Id);
548 Finder->addMatcher(switchStmt(has(CompoundStmt)), this);
549}
550
551void SimplifyBooleanExprCheck::matchDefaultIfReturnsBool(MatchFinder *Finder,
552 bool Value,
553 StringRef Id) {
554 internal::Matcher<Stmt> DefaultStmt =
555 defaultStmt(hasSubstatement(ifReturnValue(Value))).bind(DefaultId);
556 internal::Matcher<Stmt> CompoundStmt =
557 compoundStmt(hasSubstatementSequence(DefaultStmt, returnNotValue(Value)))
558 .bind(Id);
559 Finder->addMatcher(switchStmt(has(CompoundStmt)), this);
560}
561
562void SimplifyBooleanExprCheck::matchLabelIfReturnsBool(MatchFinder *Finder,
563 bool Value,
564 StringRef Id) {
565 internal::Matcher<Stmt> LabelStmt =
566 labelStmt(hasSubstatement(ifReturnValue(Value))).bind(LabelId);
567 internal::Matcher<Stmt> CompoundStmt =
568 compoundStmt(hasSubstatementSequence(LabelStmt, returnNotValue(Value)))
569 .bind(Id);
570 Finder->addMatcher(CompoundStmt, this);
571}
572
573void SimplifyBooleanExprCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
574 Options.store(Opts, "ChainedConditionalReturn", ChainedConditionalReturn);
575 Options.store(Opts, "ChainedConditionalAssignment",
576 ChainedConditionalAssignment);
577}
578
579void SimplifyBooleanExprCheck::registerMatchers(MatchFinder *Finder) {
580 Finder->addMatcher(translationUnitDecl().bind("top"), this);
581
582 matchBoolCondition(Finder, true, ConditionThenStmtId);
583 matchBoolCondition(Finder, false, ConditionElseStmtId);
584
585 matchTernaryResult(Finder, true, TernaryId);
586 matchTernaryResult(Finder, false, TernaryNegatedId);
587
588 matchIfReturnsBool(Finder, true, IfReturnsBoolId);
589 matchIfReturnsBool(Finder, false, IfReturnsNotBoolId);
590
591 matchIfAssignsBool(Finder, true, IfAssignBoolId);
592 matchIfAssignsBool(Finder, false, IfAssignNotBoolId);
593
594 matchCompoundIfReturnsBool(Finder, true, CompoundBoolId);
595 matchCompoundIfReturnsBool(Finder, false, CompoundNotBoolId);
596
597 matchCaseIfReturnsBool(Finder, true, CaseCompoundBoolId);
598 matchCaseIfReturnsBool(Finder, false, CaseCompoundNotBoolId);
599
600 matchDefaultIfReturnsBool(Finder, true, DefaultCompoundBoolId);
601 matchDefaultIfReturnsBool(Finder, false, DefaultCompoundNotBoolId);
602
603 matchLabelIfReturnsBool(Finder, true, LabelCompoundBoolId);
604 matchLabelIfReturnsBool(Finder, false, LabelCompoundNotBoolId);
605}
606
607void SimplifyBooleanExprCheck::check(const MatchFinder::MatchResult &Result) {
608 if (Result.Nodes.getNodeAs<TranslationUnitDecl>("top"))
609 Visitor(this, Result).TraverseAST(*Result.Context);
610 else if (const Expr *TrueConditionRemoved =
611 getBoolLiteral(Result, ConditionThenStmtId))
612 replaceWithThenStatement(Result, TrueConditionRemoved);
613 else if (const Expr *FalseConditionRemoved =
614 getBoolLiteral(Result, ConditionElseStmtId))
615 replaceWithElseStatement(Result, FalseConditionRemoved);
616 else if (const auto *Ternary =
617 Result.Nodes.getNodeAs<ConditionalOperator>(TernaryId))
618 replaceWithCondition(Result, Ternary, false);
619 else if (const auto *TernaryNegated =
620 Result.Nodes.getNodeAs<ConditionalOperator>(TernaryNegatedId))
621 replaceWithCondition(Result, TernaryNegated, true);
622 else if (const auto *If = Result.Nodes.getNodeAs<IfStmt>(IfReturnsBoolId))
623 replaceWithReturnCondition(Result, If, false);
624 else if (const auto *IfNot =
625 Result.Nodes.getNodeAs<IfStmt>(IfReturnsNotBoolId))
626 replaceWithReturnCondition(Result, IfNot, true);
627 else if (const auto *IfAssign =
628 Result.Nodes.getNodeAs<IfStmt>(IfAssignBoolId))
629 replaceWithAssignment(Result, IfAssign, false);
630 else if (const auto *IfAssignNot =
631 Result.Nodes.getNodeAs<IfStmt>(IfAssignNotBoolId))
632 replaceWithAssignment(Result, IfAssignNot, true);
633 else if (const auto *Compound =
634 Result.Nodes.getNodeAs<CompoundStmt>(CompoundBoolId))
635 replaceCompoundReturnWithCondition(Result, Compound, false);
636 else if (const auto *CompoundNot =
637 Result.Nodes.getNodeAs<CompoundStmt>(CompoundNotBoolId))
638 replaceCompoundReturnWithCondition(Result, CompoundNot, true);
639 else if (Result.Nodes.getNodeAs<CompoundStmt>(CaseCompoundBoolId))
640 replaceCaseCompoundReturnWithCondition(Result, false);
641 else if (Result.Nodes.getNodeAs<CompoundStmt>(CaseCompoundNotBoolId))
642 replaceCaseCompoundReturnWithCondition(Result, true);
643 else if (Result.Nodes.getNodeAs<CompoundStmt>(DefaultCompoundBoolId))
644 replaceDefaultCompoundReturnWithCondition(Result, false);
645 else if (Result.Nodes.getNodeAs<CompoundStmt>(DefaultCompoundNotBoolId))
646 replaceDefaultCompoundReturnWithCondition(Result, true);
647 else if (Result.Nodes.getNodeAs<CompoundStmt>(LabelCompoundBoolId))
648 replaceLabelCompoundReturnWithCondition(Result, false);
649 else if (Result.Nodes.getNodeAs<CompoundStmt>(LabelCompoundNotBoolId))
650 replaceLabelCompoundReturnWithCondition(Result, true);
651 else if (const auto TU = Result.Nodes.getNodeAs<Decl>("top"))
652 Visitor(this, Result).TraverseDecl(const_cast<Decl *>(TU));
653}
654
655void SimplifyBooleanExprCheck::issueDiag(const MatchFinder::MatchResult &Result,
656 SourceLocation Loc,
657 StringRef Description,
658 SourceRange ReplacementRange,
659 StringRef Replacement) {
660 CharSourceRange CharRange =
661 Lexer::makeFileCharRange(CharSourceRange::getTokenRange(ReplacementRange),
662 *Result.SourceManager, getLangOpts());
663
664 DiagnosticBuilder Diag = diag(Loc, Description);
665 if (!containsDiscardedTokens(Result, CharRange))
666 Diag << FixItHint::CreateReplacement(CharRange, Replacement);
667}
668
669void SimplifyBooleanExprCheck::replaceWithThenStatement(
670 const MatchFinder::MatchResult &Result, const Expr *BoolLiteral) {
671 const auto *IfStatement = Result.Nodes.getNodeAs<IfStmt>(IfStmtId);
672 issueDiag(Result, BoolLiteral->getBeginLoc(), SimplifyConditionDiagnostic,
673 IfStatement->getSourceRange(),
674 getText(Result, *IfStatement->getThen()));
675}
676
677void SimplifyBooleanExprCheck::replaceWithElseStatement(
678 const MatchFinder::MatchResult &Result, const Expr *BoolLiteral) {
679 const auto *IfStatement = Result.Nodes.getNodeAs<IfStmt>(IfStmtId);
680 const Stmt *ElseStatement = IfStatement->getElse();
681 issueDiag(Result, BoolLiteral->getBeginLoc(), SimplifyConditionDiagnostic,
682 IfStatement->getSourceRange(),
683 ElseStatement ? getText(Result, *ElseStatement) : "");
684}
685
686void SimplifyBooleanExprCheck::replaceWithCondition(
687 const MatchFinder::MatchResult &Result, const ConditionalOperator *Ternary,
688 bool Negated) {
689 std::string Replacement =
690 replacementExpression(Result, Negated, Ternary->getCond());
691 issueDiag(Result, Ternary->getTrueExpr()->getBeginLoc(),
692 "redundant boolean literal in ternary expression result",
693 Ternary->getSourceRange(), Replacement);
694}
695
696void SimplifyBooleanExprCheck::replaceWithReturnCondition(
697 const MatchFinder::MatchResult &Result, const IfStmt *If, bool Negated) {
698 StringRef Terminator = isa<CompoundStmt>(If->getElse()) ? ";" : "";
699 std::string Condition = replacementExpression(Result, Negated, If->getCond());
700 std::string Replacement = ("return " + Condition + Terminator).str();
701 SourceLocation Start =
702 Result.Nodes.getNodeAs<CXXBoolLiteralExpr>(ThenLiteralId)->getBeginLoc();
703 issueDiag(Result, Start, SimplifyConditionalReturnDiagnostic,
704 If->getSourceRange(), Replacement);
705}
706
707void SimplifyBooleanExprCheck::replaceCompoundReturnWithCondition(
708 const MatchFinder::MatchResult &Result, const CompoundStmt *Compound,
709 bool Negated) {
710 const auto *Ret = Result.Nodes.getNodeAs<ReturnStmt>(CompoundReturnId);
711
712 // Scan through the CompoundStmt to look for a chained-if construct.
713 const IfStmt *BeforeIf = nullptr;
714 CompoundStmt::const_body_iterator Current = Compound->body_begin();
715 CompoundStmt::const_body_iterator After = Compound->body_begin();
716 for (++After; After != Compound->body_end() && *Current != Ret;
717 ++Current, ++After) {
718 if (const auto *If = dyn_cast<IfStmt>(*Current)) {
719 if (const Expr *Lit = stmtReturnsBool(If, Negated)) {
720 if (*After == Ret) {
721 if (!ChainedConditionalReturn && BeforeIf)
722 continue;
723
724 std::string Replacement =
725 "return " + replacementExpression(Result, Negated, If->getCond());
726 issueDiag(
727 Result, Lit->getBeginLoc(), SimplifyConditionalReturnDiagnostic,
728 SourceRange(If->getBeginLoc(), Ret->getEndLoc()), Replacement);
729 return;
730 }
731
732 BeforeIf = If;
733 }
734 } else {
735 BeforeIf = nullptr;
736 }
737 }
738}
739
740void SimplifyBooleanExprCheck::replaceCompoundReturnWithCondition(
741 const MatchFinder::MatchResult &Result, bool Negated, const IfStmt *If) {
742 const auto *Lit = stmtReturnsBool(If, Negated);
743 const auto *Ret = Result.Nodes.getNodeAs<ReturnStmt>(CompoundReturnId);
744 const std::string Replacement =
745 "return " + replacementExpression(Result, Negated, If->getCond());
746 issueDiag(Result, Lit->getBeginLoc(), SimplifyConditionalReturnDiagnostic,
747 SourceRange(If->getBeginLoc(), Ret->getEndLoc()), Replacement);
748}
749
750void SimplifyBooleanExprCheck::replaceCaseCompoundReturnWithCondition(
751 const MatchFinder::MatchResult &Result, bool Negated) {
752 const auto *CaseDefault = Result.Nodes.getNodeAs<CaseStmt>(CaseId);
753 const auto *If = cast<IfStmt>(CaseDefault->getSubStmt());
754 replaceCompoundReturnWithCondition(Result, Negated, If);
755}
756
757void SimplifyBooleanExprCheck::replaceDefaultCompoundReturnWithCondition(
758 const MatchFinder::MatchResult &Result, bool Negated) {
759 const SwitchCase *CaseDefault =
760 Result.Nodes.getNodeAs<DefaultStmt>(DefaultId);
761 const auto *If = cast<IfStmt>(CaseDefault->getSubStmt());
762 replaceCompoundReturnWithCondition(Result, Negated, If);
763}
764
765void SimplifyBooleanExprCheck::replaceLabelCompoundReturnWithCondition(
766 const MatchFinder::MatchResult &Result, bool Negated) {
767 const auto *Label = Result.Nodes.getNodeAs<LabelStmt>(LabelId);
768 const auto *If = cast<IfStmt>(Label->getSubStmt());
769 replaceCompoundReturnWithCondition(Result, Negated, If);
770}
771
772void SimplifyBooleanExprCheck::replaceWithAssignment(
773 const MatchFinder::MatchResult &Result, const IfStmt *IfAssign,
774 bool Negated) {
775 SourceRange Range = IfAssign->getSourceRange();
776 StringRef VariableName =
777 getText(Result, *Result.Nodes.getNodeAs<Expr>(IfAssignVariableId));
778 StringRef Terminator = isa<CompoundStmt>(IfAssign->getElse()) ? ";" : "";
779 std::string Condition =
780 replacementExpression(Result, Negated, IfAssign->getCond());
781 std::string Replacement =
782 (VariableName + " = " + Condition + Terminator).str();
783 SourceLocation Location =
784 Result.Nodes.getNodeAs<CXXBoolLiteralExpr>(IfAssignLocId)->getBeginLoc();
785 issueDiag(Result, Location,
786 "redundant boolean literal in conditional assignment", Range,
787 Replacement);
788}
789
790} // namespace readability
791} // namespace tidy
792} // namespace clang