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 | |
2 | |
3 | |
4 | |
5 | |
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 | |
17 | using namespace clang::ast_matchers; |
18 | |
19 | namespace clang { |
20 | namespace tidy { |
21 | namespace readability { |
22 | |
23 | namespace { |
24 | |
25 | StringRef getText(const MatchFinder::MatchResult &Result, SourceRange Range) { |
26 | return Lexer::getSourceText(CharSourceRange::getTokenRange(Range), |
27 | *Result.SourceManager, |
28 | Result.Context->getLangOpts()); |
29 | } |
30 | |
31 | template <typename T> |
32 | StringRef getText(const MatchFinder::MatchResult &Result, T &Node) { |
33 | return getText(Result, Node.getSourceRange()); |
34 | } |
35 | |
36 | } |
37 | |
38 | static constexpr char ConditionThenStmtId[] = "if-bool-yields-then"; |
39 | static constexpr char ConditionElseStmtId[] = "if-bool-yields-else"; |
40 | static constexpr char TernaryId[] = "ternary-bool-yields-condition"; |
41 | static constexpr char TernaryNegatedId[] = "ternary-bool-yields-not-condition"; |
42 | static constexpr char IfReturnsBoolId[] = "if-return"; |
43 | static constexpr char IfReturnsNotBoolId[] = "if-not-return"; |
44 | static constexpr char ThenLiteralId[] = "then-literal"; |
45 | static constexpr char IfAssignVariableId[] = "if-assign-lvalue"; |
46 | static constexpr char IfAssignLocId[] = "if-assign-loc"; |
47 | static constexpr char IfAssignBoolId[] = "if-assign"; |
48 | static constexpr char IfAssignNotBoolId[] = "if-assign-not"; |
49 | static constexpr char IfAssignVarId[] = "if-assign-var"; |
50 | static constexpr char CompoundReturnId[] = "compound-return"; |
51 | static constexpr char CompoundIfId[] = "compound-if"; |
52 | static constexpr char CompoundBoolId[] = "compound-bool"; |
53 | static constexpr char CompoundNotBoolId[] = "compound-bool-not"; |
54 | static constexpr char CaseId[] = "case"; |
55 | static constexpr char CaseCompoundBoolId[] = "case-compound-bool"; |
56 | static constexpr char CaseCompoundNotBoolId[] = "case-compound-bool-not"; |
57 | static constexpr char DefaultId[] = "default"; |
58 | static constexpr char DefaultCompoundBoolId[] = "default-compound-bool"; |
59 | static constexpr char DefaultCompoundNotBoolId[] = "default-compound-bool-not"; |
60 | static constexpr char LabelId[] = "label"; |
61 | static constexpr char LabelCompoundBoolId[] = "label-compound-bool"; |
62 | static constexpr char LabelCompoundNotBoolId[] = "label-compound-bool-not"; |
63 | static constexpr char IfStmtId[] = "if"; |
64 | |
65 | static constexpr char SimplifyOperatorDiagnostic[] = |
66 | "redundant boolean literal supplied to boolean operator"; |
67 | static constexpr char SimplifyConditionDiagnostic[] = |
68 | "redundant boolean literal in if statement condition"; |
69 | static constexpr char SimplifyConditionalReturnDiagnostic[] = |
70 | "redundant boolean literal in conditional return statement"; |
71 | |
72 | static 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 | |
84 | static internal::BindableMatcher<Stmt> literalOrNegatedBool(bool Value) { |
85 | return expr( |
86 | anyOf(cxxBoolLiteral(equals(Value)), |
87 | unaryOperator(hasUnaryOperand(cxxBoolLiteral(equals(!Value))), |
88 | hasOperatorName("!")))); |
89 | } |
90 | |
91 | static 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 | |
99 | static 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 | |
111 | static std::pair<BinaryOperatorKind, BinaryOperatorKind> Opposites[] = { |
112 | {BO_LT, BO_GE}, {BO_GT, BO_LE}, {BO_EQ, BO_NE}}; |
113 | |
114 | static 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 | |
125 | static std::pair<OverloadedOperatorKind, StringRef> OperatorNames[] = { |
126 | {OO_EqualEqual, "=="}, {OO_ExclaimEqual, "!="}, {OO_Less, "<"}, |
127 | {OO_GreaterEqual, ">="}, {OO_Greater, ">"}, {OO_LessEqual, "<="}}; |
128 | |
129 | static StringRef getOperatorName(OverloadedOperatorKind OpKind) { |
130 | for (auto Name : OperatorNames) { |
131 | if (Name.first == OpKind) |
132 | return Name.second; |
133 | } |
134 | |
135 | return {}; |
136 | } |
137 | |
138 | static std::pair<OverloadedOperatorKind, OverloadedOperatorKind> |
139 | OppositeOverloads[] = {{OO_EqualEqual, OO_ExclaimEqual}, |
140 | {OO_Less, OO_GreaterEqual}, |
141 | {OO_Greater, OO_LessEqual}}; |
142 | |
143 | static 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 | |
154 | static 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 | |
161 | static 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 | |
169 | static 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 | |
176 | static 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 | |
195 | static std::string |
196 | compareExpressionToConstant(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 | |
206 | static std::string |
207 | compareExpressionToNullPtr(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 | |
214 | static std::string |
215 | compareExpressionToZero(const MatchFinder::MatchResult &Result, const Expr *E, |
216 | bool Negated) { |
217 | return compareExpressionToConstant(Result, E, Negated, "0"); |
218 | } |
219 | |
220 | static 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 | |
298 | static 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 | |
316 | static 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 | |
333 | static 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 | |
353 | class SimplifyBooleanExprCheck::Visitor : public RecursiveASTVisitor<Visitor> { |
354 | public: |
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 | |
364 | private: |
365 | SimplifyBooleanExprCheck *Check; |
366 | const MatchFinder::MatchResult &Result; |
367 | }; |
368 | |
369 | SimplifyBooleanExprCheck::SimplifyBooleanExprCheck(StringRef Name, |
370 | ClangTidyContext *Context) |
371 | : ClangTidyCheck(Name, Context), |
372 | ChainedConditionalReturn(Options.get("ChainedConditionalReturn", false)), |
373 | ChainedConditionalAssignment( |
374 | Options.get("ChainedConditionalAssignment", false)) {} |
375 | |
376 | static 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 | |
390 | void 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' | |
|
| |
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 | |
408 | if (!isa<CXXBoolLiteralExpr>(Other) && containsBoolLiteral(Other)) |
| 4 | | Assuming 'Other' is not a 'CXXBoolLiteralExpr' | |
|
| |
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 | |
426 | ReplaceWithExpression(Other, false); |
427 | else |
428 | |
429 | ReplaceWithExpression(Bool, false); |
430 | break; |
431 | case BO_LOr: |
432 | if (BoolValue) |
433 | |
434 | ReplaceWithExpression(Bool, false); |
435 | else |
436 | |
437 | ReplaceWithExpression(Other, false); |
438 | break; |
439 | case BO_EQ: |
440 | |
441 | ReplaceWithExpression(Other, !BoolValue); |
442 | break; |
443 | case BO_NE: |
444 | |
445 | ReplaceWithExpression(Other, BoolValue); |
| |
446 | break; |
447 | default: |
448 | break; |
449 | } |
450 | } |
451 | |
452 | void 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 | |
461 | void 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 | |
470 | void 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 | |
485 | void 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 | |
511 | static internal::Matcher<Stmt> ifReturnValue(bool Value) { |
512 | return ifStmt(hasThen(returnsBool(Value)), unless(hasElse(stmt()))) |
513 | .bind(CompoundIfId); |
514 | } |
515 | |
516 | static internal::Matcher<Stmt> returnNotValue(bool Value) { |
517 | return returnStmt(has(literalOrNegatedBool(!Value))).bind(CompoundReturnId); |
518 | } |
519 | |
520 | void 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 | |
540 | void 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 | |
551 | void 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 | |
562 | void 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 | |
573 | void SimplifyBooleanExprCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) { |
574 | Options.store(Opts, "ChainedConditionalReturn", ChainedConditionalReturn); |
575 | Options.store(Opts, "ChainedConditionalAssignment", |
576 | ChainedConditionalAssignment); |
577 | } |
578 | |
579 | void 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 | |
607 | void 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 | |
655 | void 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 | |
669 | void 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 | |
677 | void 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 | |
686 | void 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 | |
696 | void 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 | |
707 | void SimplifyBooleanExprCheck::replaceCompoundReturnWithCondition( |
708 | const MatchFinder::MatchResult &Result, const CompoundStmt *Compound, |
709 | bool Negated) { |
710 | const auto *Ret = Result.Nodes.getNodeAs<ReturnStmt>(CompoundReturnId); |
711 | |
712 | |
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 | |
740 | void 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 | |
750 | void 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 | |
757 | void 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 | |
765 | void 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 | |
772 | void 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 | } |
791 | } |
792 | } |