clang-tools 18.0.0git
ImplicitBoolConversionCheck.cpp
Go to the documentation of this file.
1//===--- ImplicitBoolConversionCheck.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
10#include "clang/AST/ASTContext.h"
11#include "clang/ASTMatchers/ASTMatchFinder.h"
12#include "clang/Lex/Lexer.h"
13#include "clang/Tooling/FixIt.h"
14#include <queue>
15
16using namespace clang::ast_matchers;
17
19
20namespace {
21
22AST_MATCHER(Stmt, isMacroExpansion) {
23 SourceManager &SM = Finder->getASTContext().getSourceManager();
24 SourceLocation Loc = Node.getBeginLoc();
25 return SM.isMacroBodyExpansion(Loc) || SM.isMacroArgExpansion(Loc);
26}
27
28bool isNULLMacroExpansion(const Stmt *Statement, ASTContext &Context) {
29 SourceManager &SM = Context.getSourceManager();
30 const LangOptions &LO = Context.getLangOpts();
31 SourceLocation Loc = Statement->getBeginLoc();
32 return SM.isMacroBodyExpansion(Loc) &&
33 Lexer::getImmediateMacroName(Loc, SM, LO) == "NULL";
34}
35
36AST_MATCHER(Stmt, isNULLMacroExpansion) {
37 return isNULLMacroExpansion(&Node, Finder->getASTContext());
38}
39
40StringRef getZeroLiteralToCompareWithForType(CastKind CastExprKind,
41 QualType Type,
42 ASTContext &Context) {
43 switch (CastExprKind) {
44 case CK_IntegralToBoolean:
45 return Type->isUnsignedIntegerType() ? "0u" : "0";
46
47 case CK_FloatingToBoolean:
48 return Context.hasSameType(Type, Context.FloatTy) ? "0.0f" : "0.0";
49
50 case CK_PointerToBoolean:
51 case CK_MemberPointerToBoolean: // Fall-through on purpose.
52 return Context.getLangOpts().CPlusPlus11 ? "nullptr" : "0";
53
54 default:
55 llvm_unreachable("Unexpected cast kind");
56 }
57}
58
59bool isUnaryLogicalNotOperator(const Stmt *Statement) {
60 const auto *UnaryOperatorExpr = dyn_cast<UnaryOperator>(Statement);
61 return UnaryOperatorExpr && UnaryOperatorExpr->getOpcode() == UO_LNot;
62}
63
64bool areParensNeededForOverloadedOperator(OverloadedOperatorKind OperatorKind) {
65 switch (OperatorKind) {
66 case OO_New:
67 case OO_Delete: // Fall-through on purpose.
68 case OO_Array_New:
69 case OO_Array_Delete:
70 case OO_ArrowStar:
71 case OO_Arrow:
72 case OO_Call:
73 case OO_Subscript:
74 return false;
75
76 default:
77 return true;
78 }
79}
80
81bool areParensNeededForStatement(const Stmt *Statement) {
82 if (const auto *OperatorCall = dyn_cast<CXXOperatorCallExpr>(Statement)) {
83 return areParensNeededForOverloadedOperator(OperatorCall->getOperator());
84 }
85
86 return isa<BinaryOperator>(Statement) || isa<UnaryOperator>(Statement);
87}
88
89void fixGenericExprCastToBool(DiagnosticBuilder &Diag,
90 const ImplicitCastExpr *Cast, const Stmt *Parent,
91 ASTContext &Context) {
92 // In case of expressions like (! integer), we should remove the redundant not
93 // operator and use inverted comparison (integer == 0).
94 bool InvertComparison =
95 Parent != nullptr && isUnaryLogicalNotOperator(Parent);
96 if (InvertComparison) {
97 SourceLocation ParentStartLoc = Parent->getBeginLoc();
98 SourceLocation ParentEndLoc =
99 cast<UnaryOperator>(Parent)->getSubExpr()->getBeginLoc();
100 Diag << FixItHint::CreateRemoval(
101 CharSourceRange::getCharRange(ParentStartLoc, ParentEndLoc));
102
103 Parent = Context.getParents(*Parent)[0].get<Stmt>();
104 }
105
106 const Expr *SubExpr = Cast->getSubExpr();
107
108 bool NeedInnerParens = areParensNeededForStatement(SubExpr);
109 bool NeedOuterParens =
110 Parent != nullptr && areParensNeededForStatement(Parent);
111
112 std::string StartLocInsertion;
113
114 if (NeedOuterParens) {
115 StartLocInsertion += "(";
116 }
117 if (NeedInnerParens) {
118 StartLocInsertion += "(";
119 }
120
121 if (!StartLocInsertion.empty()) {
122 Diag << FixItHint::CreateInsertion(Cast->getBeginLoc(), StartLocInsertion);
123 }
124
125 std::string EndLocInsertion;
126
127 if (NeedInnerParens) {
128 EndLocInsertion += ")";
129 }
130
131 if (InvertComparison) {
132 EndLocInsertion += " == ";
133 } else {
134 EndLocInsertion += " != ";
135 }
136
137 EndLocInsertion += getZeroLiteralToCompareWithForType(
138 Cast->getCastKind(), SubExpr->getType(), Context);
139
140 if (NeedOuterParens) {
141 EndLocInsertion += ")";
142 }
143
144 SourceLocation EndLoc = Lexer::getLocForEndOfToken(
145 Cast->getEndLoc(), 0, Context.getSourceManager(), Context.getLangOpts());
146 Diag << FixItHint::CreateInsertion(EndLoc, EndLocInsertion);
147}
148
149StringRef getEquivalentBoolLiteralForExpr(const Expr *Expression,
150 ASTContext &Context) {
151 if (isNULLMacroExpansion(Expression, Context)) {
152 return "false";
153 }
154
155 if (const auto *IntLit = dyn_cast<IntegerLiteral>(Expression)) {
156 return (IntLit->getValue() == 0) ? "false" : "true";
157 }
158
159 if (const auto *FloatLit = dyn_cast<FloatingLiteral>(Expression)) {
160 llvm::APFloat FloatLitAbsValue = FloatLit->getValue();
161 FloatLitAbsValue.clearSign();
162 return (FloatLitAbsValue.bitcastToAPInt() == 0) ? "false" : "true";
163 }
164
165 if (const auto *CharLit = dyn_cast<CharacterLiteral>(Expression)) {
166 return (CharLit->getValue() == 0) ? "false" : "true";
167 }
168
169 if (isa<StringLiteral>(Expression->IgnoreCasts())) {
170 return "true";
171 }
172
173 return {};
174}
175
176void fixGenericExprCastFromBool(DiagnosticBuilder &Diag,
177 const ImplicitCastExpr *Cast,
178 ASTContext &Context, StringRef OtherType) {
179 const Expr *SubExpr = Cast->getSubExpr();
180 bool NeedParens = !isa<ParenExpr>(SubExpr);
181
182 Diag << FixItHint::CreateInsertion(
183 Cast->getBeginLoc(),
184 (Twine("static_cast<") + OtherType + ">" + (NeedParens ? "(" : ""))
185 .str());
186
187 if (NeedParens) {
188 SourceLocation EndLoc = Lexer::getLocForEndOfToken(
189 Cast->getEndLoc(), 0, Context.getSourceManager(),
190 Context.getLangOpts());
191
192 Diag << FixItHint::CreateInsertion(EndLoc, ")");
193 }
194}
195
196StringRef getEquivalentForBoolLiteral(const CXXBoolLiteralExpr *BoolLiteral,
197 QualType DestType, ASTContext &Context) {
198 // Prior to C++11, false literal could be implicitly converted to pointer.
199 if (!Context.getLangOpts().CPlusPlus11 &&
200 (DestType->isPointerType() || DestType->isMemberPointerType()) &&
201 BoolLiteral->getValue() == false) {
202 return "0";
203 }
204
205 if (DestType->isFloatingType()) {
206 if (Context.hasSameType(DestType, Context.FloatTy)) {
207 return BoolLiteral->getValue() ? "1.0f" : "0.0f";
208 }
209 return BoolLiteral->getValue() ? "1.0" : "0.0";
210 }
211
212 if (DestType->isUnsignedIntegerType()) {
213 return BoolLiteral->getValue() ? "1u" : "0u";
214 }
215 return BoolLiteral->getValue() ? "1" : "0";
216}
217
218bool isCastAllowedInCondition(const ImplicitCastExpr *Cast,
219 ASTContext &Context) {
220 std::queue<const Stmt *> Q;
221 Q.push(Cast);
222
223 TraversalKindScope RAII(Context, TK_AsIs);
224
225 while (!Q.empty()) {
226 for (const auto &N : Context.getParents(*Q.front())) {
227 const Stmt *S = N.get<Stmt>();
228 if (!S)
229 return false;
230 if (isa<IfStmt>(S) || isa<ConditionalOperator>(S) || isa<ForStmt>(S) ||
231 isa<WhileStmt>(S) || isa<DoStmt>(S) ||
232 isa<BinaryConditionalOperator>(S))
233 return true;
234 if (isa<ParenExpr>(S) || isa<ImplicitCastExpr>(S) ||
235 isUnaryLogicalNotOperator(S) ||
236 (isa<BinaryOperator>(S) && cast<BinaryOperator>(S)->isLogicalOp())) {
237 Q.push(S);
238 } else {
239 return false;
240 }
241 }
242 Q.pop();
243 }
244 return false;
245}
246
247} // anonymous namespace
248
250 StringRef Name, ClangTidyContext *Context)
251 : ClangTidyCheck(Name, Context),
252 AllowIntegerConditions(Options.get("AllowIntegerConditions", false)),
253 AllowPointerConditions(Options.get("AllowPointerConditions", false)) {}
254
257 Options.store(Opts, "AllowIntegerConditions", AllowIntegerConditions);
258 Options.store(Opts, "AllowPointerConditions", AllowPointerConditions);
259}
260
262 auto ExceptionCases =
263 expr(anyOf(allOf(isMacroExpansion(), unless(isNULLMacroExpansion())),
264 has(ignoringImplicit(
265 memberExpr(hasDeclaration(fieldDecl(hasBitWidth(1)))))),
266 hasParent(explicitCastExpr()),
267 expr(hasType(qualType().bind("type")),
268 hasParent(initListExpr(hasParent(explicitCastExpr(
269 hasType(qualType(equalsBoundNode("type"))))))))));
270 auto ImplicitCastFromBool = implicitCastExpr(
271 anyOf(hasCastKind(CK_IntegralCast), hasCastKind(CK_IntegralToFloating),
272 // Prior to C++11 cast from bool literal to pointer was allowed.
273 allOf(anyOf(hasCastKind(CK_NullToPointer),
274 hasCastKind(CK_NullToMemberPointer)),
275 hasSourceExpression(cxxBoolLiteral()))),
276 hasSourceExpression(expr(hasType(booleanType()))),
277 unless(ExceptionCases));
278 auto BoolXor =
279 binaryOperator(hasOperatorName("^"), hasLHS(ImplicitCastFromBool),
280 hasRHS(ImplicitCastFromBool));
281 Finder->addMatcher(
282 traverse(TK_AsIs,
283 implicitCastExpr(
284 anyOf(hasCastKind(CK_IntegralToBoolean),
285 hasCastKind(CK_FloatingToBoolean),
286 hasCastKind(CK_PointerToBoolean),
287 hasCastKind(CK_MemberPointerToBoolean)),
288 // Exclude case of using if or while statements with variable
289 // declaration, e.g.:
290 // if (int var = functionCall()) {}
291 unless(hasParent(
292 stmt(anyOf(ifStmt(), whileStmt()), has(declStmt())))),
293 // Exclude cases common to implicit cast to and from bool.
294 unless(ExceptionCases), unless(has(BoolXor)),
295 // Retrieve also parent statement, to check if we need
296 // additional parens in replacement.
297 optionally(hasParent(stmt().bind("parentStmt"))),
298 unless(isInTemplateInstantiation()),
299 unless(hasAncestor(functionTemplateDecl())))
300 .bind("implicitCastToBool")),
301 this);
302
303 auto BoolComparison = binaryOperator(hasAnyOperatorName("==", "!="),
304 hasLHS(ImplicitCastFromBool),
305 hasRHS(ImplicitCastFromBool));
306 auto BoolOpAssignment = binaryOperator(hasAnyOperatorName("|=", "&="),
307 hasLHS(expr(hasType(booleanType()))));
308 auto BitfieldAssignment = binaryOperator(
309 hasLHS(memberExpr(hasDeclaration(fieldDecl(hasBitWidth(1))))));
310 auto BitfieldConstruct = cxxConstructorDecl(hasDescendant(cxxCtorInitializer(
311 withInitializer(equalsBoundNode("implicitCastFromBool")),
312 forField(hasBitWidth(1)))));
313 Finder->addMatcher(
314 traverse(
315 TK_AsIs,
316 implicitCastExpr(
317 ImplicitCastFromBool,
318 // Exclude comparisons of bools, as they are always cast to
319 // integers in such context:
320 // bool_expr_a == bool_expr_b
321 // bool_expr_a != bool_expr_b
322 unless(hasParent(
323 binaryOperator(anyOf(BoolComparison, BoolXor,
324 BoolOpAssignment, BitfieldAssignment)))),
325 implicitCastExpr().bind("implicitCastFromBool"),
326 unless(hasParent(BitfieldConstruct)),
327 // Check also for nested casts, for example: bool -> int -> float.
328 anyOf(hasParent(implicitCastExpr().bind("furtherImplicitCast")),
329 anything()),
330 unless(isInTemplateInstantiation()),
331 unless(hasAncestor(functionTemplateDecl())))),
332 this);
333}
334
336 const MatchFinder::MatchResult &Result) {
337
338 if (const auto *CastToBool =
339 Result.Nodes.getNodeAs<ImplicitCastExpr>("implicitCastToBool")) {
340 const auto *Parent = Result.Nodes.getNodeAs<Stmt>("parentStmt");
341 return handleCastToBool(CastToBool, Parent, *Result.Context);
342 }
343
344 if (const auto *CastFromBool =
345 Result.Nodes.getNodeAs<ImplicitCastExpr>("implicitCastFromBool")) {
346 const auto *NextImplicitCast =
347 Result.Nodes.getNodeAs<ImplicitCastExpr>("furtherImplicitCast");
348 return handleCastFromBool(CastFromBool, NextImplicitCast, *Result.Context);
349 }
350}
351
352void ImplicitBoolConversionCheck::handleCastToBool(const ImplicitCastExpr *Cast,
353 const Stmt *Parent,
354 ASTContext &Context) {
355 if (AllowPointerConditions &&
356 (Cast->getCastKind() == CK_PointerToBoolean ||
357 Cast->getCastKind() == CK_MemberPointerToBoolean) &&
358 isCastAllowedInCondition(Cast, Context)) {
359 return;
360 }
361
362 if (AllowIntegerConditions && Cast->getCastKind() == CK_IntegralToBoolean &&
363 isCastAllowedInCondition(Cast, Context)) {
364 return;
365 }
366
367 auto Diag = diag(Cast->getBeginLoc(), "implicit conversion %0 -> bool")
368 << Cast->getSubExpr()->getType();
369
370 StringRef EquivalentLiteral =
371 getEquivalentBoolLiteralForExpr(Cast->getSubExpr(), Context);
372 if (!EquivalentLiteral.empty()) {
373 Diag << tooling::fixit::createReplacement(*Cast, EquivalentLiteral);
374 } else {
375 fixGenericExprCastToBool(Diag, Cast, Parent, Context);
376 }
377}
378
379void ImplicitBoolConversionCheck::handleCastFromBool(
380 const ImplicitCastExpr *Cast, const ImplicitCastExpr *NextImplicitCast,
381 ASTContext &Context) {
382 QualType DestType =
383 NextImplicitCast ? NextImplicitCast->getType() : Cast->getType();
384 auto Diag = diag(Cast->getBeginLoc(), "implicit conversion bool -> %0")
385 << DestType;
386
387 if (const auto *BoolLiteral =
388 dyn_cast<CXXBoolLiteralExpr>(Cast->getSubExpr())) {
389 Diag << tooling::fixit::createReplacement(
390 *Cast, getEquivalentForBoolLiteral(BoolLiteral, DestType, Context));
391 } else {
392 fixGenericExprCastFromBool(Diag, Cast, Context, DestType.getAsString());
393 }
394}
395
396} // namespace clang::tidy::readability
const Node * Parent
NodeType Type
llvm::StringRef Name
SourceLocation Loc
::clang::DynTypedNode Node
void store(ClangTidyOptions::OptionMap &Options, StringRef LocalName, StringRef Value) const
Stores an option with the check-local name LocalName with string value Value to Options.
Base class for all clang-tidy checks.
DiagnosticBuilder diag(SourceLocation Loc, StringRef Description, DiagnosticIDs::Level Level=DiagnosticIDs::Warning)
Add a diagnostic with the check's name.
Every ClangTidyCheck reports errors through a DiagnosticsEngine provided by this context.
void check(const ast_matchers::MatchFinder::MatchResult &Result) override
ClangTidyChecks that register ASTMatchers should do the actual work in here.
void storeOptions(ClangTidyOptions::OptionMap &Opts) override
Should store all options supported by this check with their current values or default values for opti...
ImplicitBoolConversionCheck(StringRef Name, ClangTidyContext *Context)
void registerMatchers(ast_matchers::MatchFinder *Finder) override
Override this to register AST matchers with Finder.
llvm::StringMap< ClangTidyValue > OptionMap