1 //===--- ImplicitBoolConversionCheck.cpp - clang-tidy----------------------===//
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
7 //===----------------------------------------------------------------------===//
9 #include "ImplicitBoolConversionCheck.h"
10 #include "../utils/FixItHintUtils.h"
11 #include "clang/AST/ASTContext.h"
12 #include "clang/ASTMatchers/ASTMatchFinder.h"
13 #include "clang/ASTMatchers/ASTMatchers.h"
14 #include "clang/Lex/Lexer.h"
15 #include "clang/Tooling/FixIt.h"
18 using namespace clang::ast_matchers
;
20 namespace clang::tidy::readability
{
24 AST_MATCHER(Stmt
, isMacroExpansion
) {
25 SourceManager
&SM
= Finder
->getASTContext().getSourceManager();
26 SourceLocation Loc
= Node
.getBeginLoc();
27 return SM
.isMacroBodyExpansion(Loc
) || SM
.isMacroArgExpansion(Loc
);
30 AST_MATCHER(Stmt
, isC23
) { return Finder
->getASTContext().getLangOpts().C23
; }
32 bool isNULLMacroExpansion(const Stmt
*Statement
, ASTContext
&Context
) {
33 SourceManager
&SM
= Context
.getSourceManager();
34 const LangOptions
&LO
= Context
.getLangOpts();
35 SourceLocation Loc
= Statement
->getBeginLoc();
36 return SM
.isMacroBodyExpansion(Loc
) &&
37 Lexer::getImmediateMacroName(Loc
, SM
, LO
) == "NULL";
40 AST_MATCHER(Stmt
, isNULLMacroExpansion
) {
41 return isNULLMacroExpansion(&Node
, Finder
->getASTContext());
44 StringRef
getZeroLiteralToCompareWithForType(CastKind CastExprKind
,
46 ASTContext
&Context
) {
47 switch (CastExprKind
) {
48 case CK_IntegralToBoolean
:
49 return Type
->isUnsignedIntegerType() ? "0u" : "0";
51 case CK_FloatingToBoolean
:
52 return Context
.hasSameType(Type
, Context
.FloatTy
) ? "0.0f" : "0.0";
54 case CK_PointerToBoolean
:
55 case CK_MemberPointerToBoolean
: // Fall-through on purpose.
56 return (Context
.getLangOpts().CPlusPlus11
|| Context
.getLangOpts().C23
)
61 llvm_unreachable("Unexpected cast kind");
65 bool isUnaryLogicalNotOperator(const Stmt
*Statement
) {
66 const auto *UnaryOperatorExpr
= dyn_cast
<UnaryOperator
>(Statement
);
67 return UnaryOperatorExpr
&& UnaryOperatorExpr
->getOpcode() == UO_LNot
;
70 void fixGenericExprCastToBool(DiagnosticBuilder
&Diag
,
71 const ImplicitCastExpr
*Cast
, const Stmt
*Parent
,
73 bool UseUpperCaseLiteralSuffix
) {
74 // In case of expressions like (! integer), we should remove the redundant not
75 // operator and use inverted comparison (integer == 0).
76 bool InvertComparison
=
77 Parent
!= nullptr && isUnaryLogicalNotOperator(Parent
);
78 if (InvertComparison
) {
79 SourceLocation ParentStartLoc
= Parent
->getBeginLoc();
80 SourceLocation ParentEndLoc
=
81 cast
<UnaryOperator
>(Parent
)->getSubExpr()->getBeginLoc();
82 Diag
<< FixItHint::CreateRemoval(
83 CharSourceRange::getCharRange(ParentStartLoc
, ParentEndLoc
));
85 Parent
= Context
.getParents(*Parent
)[0].get
<Stmt
>();
88 const Expr
*SubExpr
= Cast
->getSubExpr();
90 bool NeedInnerParens
= utils::fixit::areParensNeededForStatement(*SubExpr
);
91 bool NeedOuterParens
=
92 Parent
!= nullptr && utils::fixit::areParensNeededForStatement(*Parent
);
94 std::string StartLocInsertion
;
96 if (NeedOuterParens
) {
97 StartLocInsertion
+= "(";
99 if (NeedInnerParens
) {
100 StartLocInsertion
+= "(";
103 if (!StartLocInsertion
.empty()) {
104 Diag
<< FixItHint::CreateInsertion(Cast
->getBeginLoc(), StartLocInsertion
);
107 std::string EndLocInsertion
;
109 if (NeedInnerParens
) {
110 EndLocInsertion
+= ")";
113 if (InvertComparison
) {
114 EndLocInsertion
+= " == ";
116 EndLocInsertion
+= " != ";
119 const StringRef ZeroLiteral
= getZeroLiteralToCompareWithForType(
120 Cast
->getCastKind(), SubExpr
->getType(), Context
);
122 if (UseUpperCaseLiteralSuffix
)
123 EndLocInsertion
+= ZeroLiteral
.upper();
125 EndLocInsertion
+= ZeroLiteral
;
127 if (NeedOuterParens
) {
128 EndLocInsertion
+= ")";
131 SourceLocation EndLoc
= Lexer::getLocForEndOfToken(
132 Cast
->getEndLoc(), 0, Context
.getSourceManager(), Context
.getLangOpts());
133 Diag
<< FixItHint::CreateInsertion(EndLoc
, EndLocInsertion
);
136 StringRef
getEquivalentBoolLiteralForExpr(const Expr
*Expression
,
137 ASTContext
&Context
) {
138 if (isNULLMacroExpansion(Expression
, Context
)) {
142 if (const auto *IntLit
=
143 dyn_cast
<IntegerLiteral
>(Expression
->IgnoreParens())) {
144 return (IntLit
->getValue() == 0) ? "false" : "true";
147 if (const auto *FloatLit
= dyn_cast
<FloatingLiteral
>(Expression
)) {
148 llvm::APFloat FloatLitAbsValue
= FloatLit
->getValue();
149 FloatLitAbsValue
.clearSign();
150 return (FloatLitAbsValue
.bitcastToAPInt() == 0) ? "false" : "true";
153 if (const auto *CharLit
= dyn_cast
<CharacterLiteral
>(Expression
)) {
154 return (CharLit
->getValue() == 0) ? "false" : "true";
157 if (isa
<StringLiteral
>(Expression
->IgnoreCasts())) {
164 bool needsSpacePrefix(SourceLocation Loc
, ASTContext
&Context
) {
165 SourceRange
PrefixRange(Loc
.getLocWithOffset(-1), Loc
);
166 StringRef SpaceBeforeStmtStr
= Lexer::getSourceText(
167 CharSourceRange::getCharRange(PrefixRange
), Context
.getSourceManager(),
168 Context
.getLangOpts(), nullptr);
169 if (SpaceBeforeStmtStr
.empty())
172 const StringRef
AllowedCharacters(" \t\n\v\f\r(){}[]<>;,+=-|&~!^*/");
173 return !AllowedCharacters
.contains(SpaceBeforeStmtStr
.back());
176 void fixGenericExprCastFromBool(DiagnosticBuilder
&Diag
,
177 const ImplicitCastExpr
*Cast
,
178 ASTContext
&Context
, StringRef OtherType
) {
179 if (!Context
.getLangOpts().CPlusPlus
) {
180 Diag
<< FixItHint::CreateInsertion(Cast
->getBeginLoc(),
181 (Twine("(") + OtherType
+ ")").str());
185 const Expr
*SubExpr
= Cast
->getSubExpr();
186 const bool NeedParens
= !isa
<ParenExpr
>(SubExpr
->IgnoreImplicit());
187 const bool NeedSpace
= needsSpacePrefix(Cast
->getBeginLoc(), Context
);
189 Diag
<< FixItHint::CreateInsertion(
190 Cast
->getBeginLoc(), (Twine() + (NeedSpace
? " " : "") + "static_cast<" +
191 OtherType
+ ">" + (NeedParens
? "(" : ""))
195 SourceLocation EndLoc
= Lexer::getLocForEndOfToken(
196 Cast
->getEndLoc(), 0, Context
.getSourceManager(),
197 Context
.getLangOpts());
199 Diag
<< FixItHint::CreateInsertion(EndLoc
, ")");
203 StringRef
getEquivalentForBoolLiteral(const CXXBoolLiteralExpr
*BoolLiteral
,
204 QualType DestType
, ASTContext
&Context
) {
205 // Prior to C++11, false literal could be implicitly converted to pointer.
206 if (!Context
.getLangOpts().CPlusPlus11
&&
207 (DestType
->isPointerType() || DestType
->isMemberPointerType()) &&
208 BoolLiteral
->getValue() == false) {
212 if (DestType
->isFloatingType()) {
213 if (Context
.hasSameType(DestType
, Context
.FloatTy
)) {
214 return BoolLiteral
->getValue() ? "1.0f" : "0.0f";
216 return BoolLiteral
->getValue() ? "1.0" : "0.0";
219 if (DestType
->isUnsignedIntegerType()) {
220 return BoolLiteral
->getValue() ? "1u" : "0u";
222 return BoolLiteral
->getValue() ? "1" : "0";
225 bool isCastAllowedInCondition(const ImplicitCastExpr
*Cast
,
226 ASTContext
&Context
) {
227 std::queue
<const Stmt
*> Q
;
230 TraversalKindScope
RAII(Context
, TK_AsIs
);
233 for (const auto &N
: Context
.getParents(*Q
.front())) {
234 const Stmt
*S
= N
.get
<Stmt
>();
237 if (isa
<IfStmt
>(S
) || isa
<ConditionalOperator
>(S
) || isa
<ForStmt
>(S
) ||
238 isa
<WhileStmt
>(S
) || isa
<DoStmt
>(S
) ||
239 isa
<BinaryConditionalOperator
>(S
))
241 if (isa
<ParenExpr
>(S
) || isa
<ImplicitCastExpr
>(S
) ||
242 isUnaryLogicalNotOperator(S
) ||
243 (isa
<BinaryOperator
>(S
) && cast
<BinaryOperator
>(S
)->isLogicalOp())) {
254 } // anonymous namespace
256 ImplicitBoolConversionCheck::ImplicitBoolConversionCheck(
257 StringRef Name
, ClangTidyContext
*Context
)
258 : ClangTidyCheck(Name
, Context
),
259 AllowIntegerConditions(Options
.get("AllowIntegerConditions", false)),
260 AllowPointerConditions(Options
.get("AllowPointerConditions", false)),
261 UseUpperCaseLiteralSuffix(
262 Options
.get("UseUpperCaseLiteralSuffix", false)) {}
264 void ImplicitBoolConversionCheck::storeOptions(
265 ClangTidyOptions::OptionMap
&Opts
) {
266 Options
.store(Opts
, "AllowIntegerConditions", AllowIntegerConditions
);
267 Options
.store(Opts
, "AllowPointerConditions", AllowPointerConditions
);
268 Options
.store(Opts
, "UseUpperCaseLiteralSuffix", UseUpperCaseLiteralSuffix
);
271 void ImplicitBoolConversionCheck::registerMatchers(MatchFinder
*Finder
) {
272 auto ExceptionCases
=
273 expr(anyOf(allOf(isMacroExpansion(), unless(isNULLMacroExpansion())),
274 has(ignoringImplicit(
275 memberExpr(hasDeclaration(fieldDecl(hasBitWidth(1)))))),
276 hasParent(explicitCastExpr()),
277 expr(hasType(qualType().bind("type")),
278 hasParent(initListExpr(hasParent(explicitCastExpr(
279 hasType(qualType(equalsBoundNode("type"))))))))));
280 auto ImplicitCastFromBool
= implicitCastExpr(
281 anyOf(hasCastKind(CK_IntegralCast
), hasCastKind(CK_IntegralToFloating
),
282 // Prior to C++11 cast from bool literal to pointer was allowed.
283 allOf(anyOf(hasCastKind(CK_NullToPointer
),
284 hasCastKind(CK_NullToMemberPointer
)),
285 hasSourceExpression(cxxBoolLiteral()))),
286 hasSourceExpression(expr(hasType(booleanType()))));
288 binaryOperator(hasOperatorName("^"), hasLHS(ImplicitCastFromBool
),
289 hasRHS(ImplicitCastFromBool
));
290 auto ComparisonInCall
= allOf(
291 hasParent(callExpr()),
292 hasSourceExpression(binaryOperator(hasAnyOperatorName("==", "!="))));
294 auto IsInCompilerGeneratedFunction
= hasAncestor(namedDecl(anyOf(
295 isImplicit(), functionDecl(isDefaulted()), functionTemplateDecl())));
300 anyOf(hasCastKind(CK_IntegralToBoolean
),
301 hasCastKind(CK_FloatingToBoolean
),
302 hasCastKind(CK_PointerToBoolean
),
303 hasCastKind(CK_MemberPointerToBoolean
)),
304 // Exclude cases of C23 comparison result.
305 unless(allOf(isC23(),
306 hasSourceExpression(ignoringParens(
307 binaryOperator(hasAnyOperatorName(
308 ">", ">=", "==", "!=", "<", "<=")))))),
309 // Exclude case of using if or while statements with variable
310 // declaration, e.g.:
311 // if (int var = functionCall()) {}
313 stmt(anyOf(ifStmt(), whileStmt()), has(declStmt())))),
314 // Exclude cases common to implicit cast to and from bool.
315 unless(ExceptionCases
), unless(has(BoolXor
)),
316 // Exclude C23 cases common to implicit cast to bool.
317 unless(ComparisonInCall
),
318 // Retrieve also parent statement, to check if we need
319 // additional parens in replacement.
320 optionally(hasParent(stmt().bind("parentStmt"))),
321 unless(isInTemplateInstantiation()),
322 unless(IsInCompilerGeneratedFunction
))
323 .bind("implicitCastToBool")),
326 auto BoolComparison
= binaryOperator(hasAnyOperatorName("==", "!="),
327 hasLHS(ImplicitCastFromBool
),
328 hasRHS(ImplicitCastFromBool
));
329 auto BoolOpAssignment
= binaryOperator(hasAnyOperatorName("|=", "&="),
330 hasLHS(expr(hasType(booleanType()))));
331 auto BitfieldAssignment
= binaryOperator(
332 hasLHS(memberExpr(hasDeclaration(fieldDecl(hasBitWidth(1))))));
333 auto BitfieldConstruct
= cxxConstructorDecl(hasDescendant(cxxCtorInitializer(
334 withInitializer(equalsBoundNode("implicitCastFromBool")),
335 forField(hasBitWidth(1)))));
340 ImplicitCastFromBool
, unless(ExceptionCases
),
341 // Exclude comparisons of bools, as they are always cast to
342 // integers in such context:
343 // bool_expr_a == bool_expr_b
344 // bool_expr_a != bool_expr_b
346 binaryOperator(anyOf(BoolComparison
, BoolXor
,
347 BoolOpAssignment
, BitfieldAssignment
)))),
348 implicitCastExpr().bind("implicitCastFromBool"),
349 unless(hasParent(BitfieldConstruct
)),
350 // Check also for nested casts, for example: bool -> int -> float.
351 anyOf(hasParent(implicitCastExpr().bind("furtherImplicitCast")),
353 unless(isInTemplateInstantiation()),
354 unless(IsInCompilerGeneratedFunction
))),
358 void ImplicitBoolConversionCheck::check(
359 const MatchFinder::MatchResult
&Result
) {
361 if (const auto *CastToBool
=
362 Result
.Nodes
.getNodeAs
<ImplicitCastExpr
>("implicitCastToBool")) {
363 const auto *Parent
= Result
.Nodes
.getNodeAs
<Stmt
>("parentStmt");
364 return handleCastToBool(CastToBool
, Parent
, *Result
.Context
);
367 if (const auto *CastFromBool
=
368 Result
.Nodes
.getNodeAs
<ImplicitCastExpr
>("implicitCastFromBool")) {
369 const auto *NextImplicitCast
=
370 Result
.Nodes
.getNodeAs
<ImplicitCastExpr
>("furtherImplicitCast");
371 return handleCastFromBool(CastFromBool
, NextImplicitCast
, *Result
.Context
);
375 void ImplicitBoolConversionCheck::handleCastToBool(const ImplicitCastExpr
*Cast
,
377 ASTContext
&Context
) {
378 if (AllowPointerConditions
&&
379 (Cast
->getCastKind() == CK_PointerToBoolean
||
380 Cast
->getCastKind() == CK_MemberPointerToBoolean
) &&
381 isCastAllowedInCondition(Cast
, Context
)) {
385 if (AllowIntegerConditions
&& Cast
->getCastKind() == CK_IntegralToBoolean
&&
386 isCastAllowedInCondition(Cast
, Context
)) {
390 auto Diag
= diag(Cast
->getBeginLoc(), "implicit conversion %0 -> 'bool'")
391 << Cast
->getSubExpr()->getType();
393 StringRef EquivalentLiteral
=
394 getEquivalentBoolLiteralForExpr(Cast
->getSubExpr(), Context
);
395 if (!EquivalentLiteral
.empty()) {
396 Diag
<< tooling::fixit::createReplacement(*Cast
, EquivalentLiteral
);
398 fixGenericExprCastToBool(Diag
, Cast
, Parent
, Context
,
399 UseUpperCaseLiteralSuffix
);
403 void ImplicitBoolConversionCheck::handleCastFromBool(
404 const ImplicitCastExpr
*Cast
, const ImplicitCastExpr
*NextImplicitCast
,
405 ASTContext
&Context
) {
407 NextImplicitCast
? NextImplicitCast
->getType() : Cast
->getType();
408 auto Diag
= diag(Cast
->getBeginLoc(), "implicit conversion 'bool' -> %0")
411 if (const auto *BoolLiteral
=
412 dyn_cast
<CXXBoolLiteralExpr
>(Cast
->getSubExpr()->IgnoreParens())) {
414 const auto EquivalentForBoolLiteral
=
415 getEquivalentForBoolLiteral(BoolLiteral
, DestType
, Context
);
416 if (UseUpperCaseLiteralSuffix
)
417 Diag
<< tooling::fixit::createReplacement(
418 *Cast
, EquivalentForBoolLiteral
.upper());
420 Diag
<< tooling::fixit::createReplacement(*Cast
,
421 EquivalentForBoolLiteral
);
424 fixGenericExprCastFromBool(Diag
, Cast
, Context
, DestType
.getAsString());
428 } // namespace clang::tidy::readability