Index: clang-tidy/readability/CMakeLists.txt =================================================================== --- clang-tidy/readability/CMakeLists.txt +++ clang-tidy/readability/CMakeLists.txt @@ -6,6 +6,7 @@ ElseAfterReturnCheck.cpp FunctionSizeCheck.cpp IdentifierNamingCheck.cpp + ImplicitBoolCastCheck.cpp InconsistentDeclarationParameterNameCheck.cpp NamedParameterCheck.cpp NamespaceCommentCheck.cpp Index: clang-tidy/readability/ImplicitBoolCastCheck.h =================================================================== --- clang-tidy/readability/ImplicitBoolCastCheck.h +++ clang-tidy/readability/ImplicitBoolCastCheck.h @@ -0,0 +1,47 @@ +//===--- ImplicitBoolCastCheck.h - clang-tidy--------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_IMPLICIT_BOOL_CAST_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_IMPLICIT_BOOL_CAST_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { + +/// \brief Checks for use of implicit bool casts in expressions. +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/readability-implicit-bool-cast.html +class ImplicitBoolCastCheck : public ClangTidyCheck { +public: + ImplicitBoolCastCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context), + AllowConditionalIntegerCasts( + Options.get("AllowConditionalIntegerCasts", 0) != 0), + AllowConditionalPointerCasts( + Options.get("AllowConditionalPointerCasts", 0) != 0) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + +private: + void handleCastToBool(const ImplicitCastExpr *CastExpression, + const Stmt *ParentStatement, ASTContext &Context); + void handleCastFromBool(const ImplicitCastExpr *CastExpression, + const ImplicitCastExpr *FurtherImplicitCastExpression, + ASTContext &Context); + + bool AllowConditionalIntegerCasts; + bool AllowConditionalPointerCasts; +}; + +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_IMPLICIT_BOOL_CAST_H Index: clang-tidy/readability/ImplicitBoolCastCheck.cpp =================================================================== --- clang-tidy/readability/ImplicitBoolCastCheck.cpp +++ clang-tidy/readability/ImplicitBoolCastCheck.cpp @@ -0,0 +1,431 @@ +//===--- ImplicitBoolCastCheck.cpp - clang-tidy----------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "ImplicitBoolCastCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/Lex/Lexer.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { + +namespace { + +const internal::VariadicDynCastAllOfMatcher parenExpr; + +AST_MATCHER_P(Expr, ignoringParens, ast_matchers::internal::Matcher, + InnerMatcher) { + return InnerMatcher.matches(*Node.IgnoreParens(), Finder, Builder); +} + +AST_MATCHER_P(CastExpr, hasCastKind, CastKind, Kind) { + return Node.getCastKind() == Kind; +} + +AST_MATCHER(QualType, isBool) { + return !Node.isNull() && Node->isBooleanType(); +} + +AST_MATCHER(Stmt, isMacroExpansion) { + SourceManager &SM = Finder->getASTContext().getSourceManager(); + SourceLocation Loc = Node.getLocStart(); + return SM.isMacroBodyExpansion(Loc) || SM.isMacroArgExpansion(Loc); +} + +bool isNullMacroExpansion(const Stmt *Statement, ASTContext &Context) { + SourceManager &SM = Context.getSourceManager(); + const LangOptions &LO = Context.getLangOpts(); + SourceLocation Loc = Statement->getLocStart(); + return SM.isMacroBodyExpansion(Loc) && + clang::Lexer::getImmediateMacroName(Loc, SM, LO) == "NULL"; +} + +AST_MATCHER(Stmt, isNullMacroExpansion) { + return isNullMacroExpansion(&Node, Finder->getASTContext()); +} + +ast_matchers::internal::Matcher createExceptionCasesMatcher() { + return expr(anyOf(hasParent(explicitCastExpr()), + allOf(isMacroExpansion(), unless(isNullMacroExpansion())), + isInTemplateInstantiation(), + hasAncestor(functionTemplateDecl()))); +} + +StatementMatcher createImplicitCastFromBoolMatcher() { + return implicitCastExpr( + unless(createExceptionCasesMatcher()), + anyOf(hasCastKind(CK_IntegralCast), hasCastKind(CK_IntegralToFloating), + // Prior to C++11 cast from bool literal to pointer was allowed. + allOf(anyOf(hasCastKind(CK_NullToPointer), + hasCastKind(CK_NullToMemberPointer)), + hasSourceExpression(cxxBoolLiteral()))), + hasSourceExpression(expr(hasType(qualType(isBool()))))); +} + +StringRef +getZeroLiteralToCompareWithForGivenType(CastKind CastExpressionKind, + QualType CastSubExpressionType, + ASTContext &Context) { + switch (CastExpressionKind) { + case CK_IntegralToBoolean: + return CastSubExpressionType->isUnsignedIntegerType() ? "0u" : "0"; + + case CK_FloatingToBoolean: + return Context.hasSameType(CastSubExpressionType, Context.FloatTy) ? "0.0f" + : "0.0"; + + case CK_PointerToBoolean: + case CK_MemberPointerToBoolean: // Fall-through on purpose. + return Context.getLangOpts().CPlusPlus11 ? "nullptr" : "NULL"; + + default: + assert(false && "Unexpected cast kind"); + } +} + +bool isUnaryLogicalNotOperator(const Stmt *Statement) { + const auto *UnaryOperatorExpression = + llvm::dyn_cast(Statement); + return UnaryOperatorExpression != nullptr && + UnaryOperatorExpression->getOpcode() == UO_LNot; +} + +bool areParensNeededForOverloadedOperator(OverloadedOperatorKind OperatorKind) { + switch (OperatorKind) { + case OO_New: + case OO_Delete: // Fall-through on purpose. + case OO_Array_New: + case OO_Array_Delete: + case OO_ArrowStar: + case OO_Arrow: + case OO_Call: + case OO_Subscript: + return false; + + default: + return true; + } +} + +bool areParensNeededForStatement(const Stmt *Statement) { + const CXXOperatorCallExpr *OverloadedOperatorCall = + llvm::dyn_cast(Statement); + if (OverloadedOperatorCall != nullptr) { + return areParensNeededForOverloadedOperator( + OverloadedOperatorCall->getOperator()); + } + + return llvm::isa(Statement) || + llvm::isa(Statement); +} + +void addFixItHintsForGenericExpressionCastToBool( + DiagnosticBuilder &Diagnostic, const ImplicitCastExpr *CastExpression, + const Stmt *ParentStatement, ASTContext &Context) { + // In case of expressions like (! integer), we should remove the redundant not + // operator and use inverted comparison (integer == 0). + bool InvertComparison = + ParentStatement != nullptr && isUnaryLogicalNotOperator(ParentStatement); + if (InvertComparison) { + SourceLocation ParentStartLoc = ParentStatement->getLocStart(); + SourceLocation ParentEndLoc = + llvm::cast(ParentStatement)->getSubExpr()->getLocStart(); + Diagnostic.AddFixItHint(FixItHint::CreateRemoval( + CharSourceRange::getCharRange(ParentStartLoc, ParentEndLoc))); + + auto FurtherParents = Context.getParents(*ParentStatement); + ParentStatement = FurtherParents[0].get(); + } + + const Expr *SubExpression = CastExpression->getSubExpr(); + + bool NeedInnerParens = areParensNeededForStatement(SubExpression); + bool NeedOuterParens = ParentStatement != nullptr && + areParensNeededForStatement(ParentStatement); + + std::string StartLocInsertion; + + if (NeedOuterParens) { + StartLocInsertion += "("; + } + if (NeedInnerParens) { + StartLocInsertion += "("; + } + + if (!StartLocInsertion.empty()) { + SourceLocation StartLoc = CastExpression->getLocStart(); + Diagnostic.AddFixItHint( + FixItHint::CreateInsertion(StartLoc, StartLocInsertion)); + } + + std::string EndLocInsertion; + + if (NeedInnerParens) { + EndLocInsertion += ")"; + } + + if (InvertComparison) { + EndLocInsertion += " == "; + } else { + EndLocInsertion += " != "; + } + + EndLocInsertion += getZeroLiteralToCompareWithForGivenType( + CastExpression->getCastKind(), SubExpression->getType(), Context); + + if (NeedOuterParens) { + EndLocInsertion += ")"; + } + + SourceLocation EndLoc = Lexer::getLocForEndOfToken( + CastExpression->getLocEnd(), 0, Context.getSourceManager(), + Context.getLangOpts()); + Diagnostic.AddFixItHint(FixItHint::CreateInsertion(EndLoc, EndLocInsertion)); +} + +StringRef getEquivalentBoolLiteralForExpression(const Expr *Expression, + ASTContext &Context) { + if (isNullMacroExpansion(Expression, Context)) { + return "false"; + } + + if (const auto *IntLit = llvm::dyn_cast(Expression)) { + return (IntLit->getValue() == 0) ? "false" : "true"; + } + + if (const auto *FloatLit = llvm::dyn_cast(Expression)) { + return (FloatLit->getValue().bitcastToAPInt() == 0) ? "false" : "true"; + } + + if (const auto *CharLit = llvm::dyn_cast(Expression)) { + return (CharLit->getValue() == 0) ? "false" : "true"; + } + + if (llvm::isa(Expression->IgnoreCasts())) { + return "true"; + } + + return StringRef(); +} + +void addFixItHintsForLiteralCastToBool(DiagnosticBuilder &Diagnostic, + const ImplicitCastExpr *CastExpression, + StringRef EquivalentLiteralExpression) { + SourceLocation StartLoc = CastExpression->getLocStart(); + SourceLocation EndLoc = CastExpression->getLocEnd(); + + Diagnostic.AddFixItHint(FixItHint::CreateReplacement( + CharSourceRange::getTokenRange(StartLoc, EndLoc), + EquivalentLiteralExpression)); +} + +void addFixItHintsForGenericExpressionCastFromBool( + DiagnosticBuilder &Diagnostic, const ImplicitCastExpr *CastExpression, + ASTContext &Context, StringRef OtherType) { + const Expr *SubExpression = CastExpression->getSubExpr(); + bool NeedParens = !llvm::isa(SubExpression); + + std::string StartLocInsertion = "static_cast<"; + StartLocInsertion += OtherType.str(); + StartLocInsertion += ">"; + if (NeedParens) { + StartLocInsertion += "("; + } + + SourceLocation StartLoc = CastExpression->getLocStart(); + Diagnostic.AddFixItHint( + FixItHint::CreateInsertion(StartLoc, StartLocInsertion)); + + if (NeedParens) { + SourceLocation EndLoc = Lexer::getLocForEndOfToken( + CastExpression->getLocEnd(), 0, Context.getSourceManager(), + Context.getLangOpts()); + + Diagnostic.AddFixItHint(FixItHint::CreateInsertion(EndLoc, ")")); + } +} + +StringRef getEquivalentLiteralForBoolLiteral( + const CXXBoolLiteralExpr *BoolLiteralExpression, QualType DestinationType, + ASTContext &Context) { + // Prior to C++11, false literal could be implicitly converted to pointer. + if (!Context.getLangOpts().CPlusPlus11 && + (DestinationType->isPointerType() || + DestinationType->isMemberPointerType()) && + BoolLiteralExpression->getValue() == false) { + return "NULL"; + } + + if (DestinationType->isFloatingType()) { + if (BoolLiteralExpression->getValue() == true) { + return Context.hasSameType(DestinationType, Context.FloatTy) ? "1.0f" + : "1.0"; + } + return Context.hasSameType(DestinationType, Context.FloatTy) ? "0.0f" + : "0.0"; + } + + if (BoolLiteralExpression->getValue() == true) { + return DestinationType->isUnsignedIntegerType() ? "1u" : "1"; + } + return DestinationType->isUnsignedIntegerType() ? "0u" : "0"; +} + +void addFixItHintsForLiteralCastFromBool(DiagnosticBuilder &Diagnostic, + const ImplicitCastExpr *CastExpression, + ASTContext &Context, + QualType DestinationType) { + SourceLocation StartLoc = CastExpression->getLocStart(); + SourceLocation EndLoc = CastExpression->getLocEnd(); + const auto *BoolLiteralExpression = + llvm::dyn_cast(CastExpression->getSubExpr()); + + Diagnostic.AddFixItHint(FixItHint::CreateReplacement( + CharSourceRange::getTokenRange(StartLoc, EndLoc), + getEquivalentLiteralForBoolLiteral(BoolLiteralExpression, DestinationType, + Context))); +} + +StatementMatcher createConditionalParentMatcher() { + return stmt( + hasParent(stmt(anyOf(ifStmt(), conditionalOperator(), + parenExpr(hasParent(conditionalOperator())), + expr(ignoringParens(conditionalOperator())))))); +} + +bool isAllowedConditionalCast(const ImplicitCastExpr *CastExpression, + ASTContext &Context) { + auto AllowedConditionalMatcher = stmt( + anyOf(createConditionalParentMatcher(), + stmt(hasParent(unaryOperator(hasOperatorName("!"), + createConditionalParentMatcher()))))); + + auto MatchResult = match(AllowedConditionalMatcher, *CastExpression, Context); + return !MatchResult.empty(); +} + +} // anonymous namespace + +void ImplicitBoolCastCheck::registerMatchers(MatchFinder *Finder) { + // This check doesn't make much sense if we run it on language without + // built-in bool support. + if (!getLangOpts().Bool) { + return; + } + + Finder->addMatcher( + implicitCastExpr( + // Exclude cases common to implicit cast to and from bool. + unless(createExceptionCasesMatcher()), + // Exclude case of using if or while statements with variable + // declaration, e.g.: + // if (int var = functionCall()) {} + unless( + hasParent(stmt(anyOf(ifStmt(), whileStmt()), has(declStmt())))), + anyOf(hasCastKind(CK_IntegralToBoolean), + hasCastKind(CK_FloatingToBoolean), + hasCastKind(CK_PointerToBoolean), + hasCastKind(CK_MemberPointerToBoolean)), + // Retrive also parent statement, to check if we need additional + // parens in replacement. + anyOf(hasParent(stmt().bind("parentStmt")), anything())) + .bind("implicitCastToBool"), + this); + + Finder->addMatcher( + implicitCastExpr( + createImplicitCastFromBoolMatcher(), + // Exclude comparisons of bools, as they are always cast to integers + // in such context: + // bool_expr_a == bool_expr_b + // bool_expr_a != bool_expr_b + unless(hasParent(binaryOperator( + anyOf(hasOperatorName("=="), hasOperatorName("!=")), + hasLHS(createImplicitCastFromBoolMatcher()), + hasRHS(createImplicitCastFromBoolMatcher())))), + // Check also for nested casts, for example: bool -> int -> float. + anyOf(hasParent(implicitCastExpr().bind("furtherImplicitCast")), + anything())) + .bind("implicitCastFromBool"), + this); +} + +void ImplicitBoolCastCheck::check(const MatchFinder::MatchResult &Result) { + if (const auto *CastToBool = + Result.Nodes.getNodeAs("implicitCastToBool")) { + const auto *ParentStatement = Result.Nodes.getNodeAs("parentStmt"); + return handleCastToBool(CastToBool, ParentStatement, *Result.Context); + } + + if (const auto *CastFromBool = + Result.Nodes.getNodeAs("implicitCastFromBool")) { + const auto *FurtherImplicitCastExpression = + Result.Nodes.getNodeAs("furtherImplicitCast"); + return handleCastFromBool(CastFromBool, FurtherImplicitCastExpression, + *Result.Context); + } +} + +void ImplicitBoolCastCheck::handleCastToBool( + const ImplicitCastExpr *CastExpression, const Stmt *ParentStatement, + ASTContext &Context) { + if (AllowConditionalPointerCasts && + (CastExpression->getCastKind() == CK_PointerToBoolean || + CastExpression->getCastKind() == CK_MemberPointerToBoolean) && + isAllowedConditionalCast(CastExpression, Context)) { + return; + } + + if (AllowConditionalIntegerCasts && + CastExpression->getCastKind() == CK_IntegralToBoolean && + isAllowedConditionalCast(CastExpression, Context)) { + return; + } + + std::string OtherType = CastExpression->getSubExpr()->getType().getAsString(); + DiagnosticBuilder Diagnostic = + diag(CastExpression->getLocStart(), "implicit cast '%0' -> bool") + << OtherType; + + StringRef EquivalentLiteralExpression = getEquivalentBoolLiteralForExpression( + CastExpression->getSubExpr(), Context); + if (!EquivalentLiteralExpression.empty()) { + addFixItHintsForLiteralCastToBool(Diagnostic, CastExpression, + EquivalentLiteralExpression); + } else { + addFixItHintsForGenericExpressionCastToBool(Diagnostic, CastExpression, + ParentStatement, Context); + } +} + +void ImplicitBoolCastCheck::handleCastFromBool( + const ImplicitCastExpr *CastExpression, + const ImplicitCastExpr *FurtherImplicitCastExpression, + ASTContext &Context) { + QualType DestinationType = (FurtherImplicitCastExpression != nullptr) + ? FurtherImplicitCastExpression->getType() + : CastExpression->getType(); + std::string DestinationTypeString = DestinationType.getAsString(); + DiagnosticBuilder Diagnostic = + diag(CastExpression->getLocStart(), "implicit cast bool -> '%0'") + << DestinationTypeString; + + if (llvm::isa(CastExpression->getSubExpr())) { + addFixItHintsForLiteralCastFromBool(Diagnostic, CastExpression, Context, + DestinationType); + } else { + addFixItHintsForGenericExpressionCastFromBool( + Diagnostic, CastExpression, Context, DestinationTypeString); + } +} + +} // namespace tidy +} // namespace clang Index: clang-tidy/readability/ReadabilityTidyModule.cpp =================================================================== --- clang-tidy/readability/ReadabilityTidyModule.cpp +++ clang-tidy/readability/ReadabilityTidyModule.cpp @@ -15,6 +15,7 @@ #include "ElseAfterReturnCheck.h" #include "FunctionSizeCheck.h" #include "IdentifierNamingCheck.h" +#include "ImplicitBoolCastCheck.h" #include "InconsistentDeclarationParameterNameCheck.h" #include "NamedParameterCheck.h" #include "RedundantSmartptrGetCheck.h" @@ -38,6 +39,8 @@ "readability-function-size"); CheckFactories.registerCheck( "readability-identifier-naming"); + CheckFactories.registerCheck( + "readability-implicit-bool-cast"); CheckFactories.registerCheck( "readability-inconsistent-declaration-parameter-name"); CheckFactories.registerCheck( Index: docs/clang-tidy/checks/list.rst =================================================================== --- docs/clang-tidy/checks/list.rst +++ docs/clang-tidy/checks/list.rst @@ -61,6 +61,7 @@ readability-else-after-return readability-function-size readability-identifier-naming + readability-implicit-bool-cast readability-inconsistent-declaration-parameter-name readability-named-parameter readability-redundant-smartptr-get Index: docs/clang-tidy/checks/readability-implicit-bool-cast.rst =================================================================== --- docs/clang-tidy/checks/readability-implicit-bool-cast.rst +++ docs/clang-tidy/checks/readability-implicit-bool-cast.rst @@ -0,0 +1,97 @@ +readability-implicit-bool-cast +============================== + +This check can be used to find implicit conversions between built-in types and +booleans. Depending on use case, it may simply help with readability of the code, +or in some cases, point to potential bugs which remain unnoticed due to implicit +conversions. + +The following is a real-world example of bug which was hiding behind implicit +bool cast: + +.. code:: c++ + + class Foo { + int m_foo; + public: + void setFoo(bool foo) { m_foo = foo; } // warning: implicit cast bool -> int + int getFoo() { return m_foo; } + }; + + void use(Foo& foo) { + bool value = foo.getFoo(); // warning: implicit cast int -> bool + } + +This code is the result of unsuccessful refactoring, where type of ``m_foo`` +changed from ``bool`` to ``int``. The programmer forgot to change all +occurences of ``bool``, and the remaining code is no longer correct, yet it +still compiles without any visible warnings. + +In addition to issuing warnings, FixIt hints are provided to help solve +the reported issues. This can be used for improving readabilty of code, for example: + +.. code:: c++ + + void conversionsToBool() { + float floating; + bool boolean = floating; + // ^ propose replacement: bool boolean = floating != 0.0f; + + int integer + if (integer) {} + // ^ propose replacement: if (integer != 0) {} + + int* pointer; + if (!pointer) {} + // ^ propose replacement: if (pointer == nullptr) {} + + while (1) {} + // ^ propose replacement: while (true) {} + } + + void functionTakingBool(bool param); + + void conversionsFromBool() { + int integer; + functionTakingBool(integer); + // ^ propose replacement: functionTakingBool(static_cast(integer)); + + functionTakingBool(1); + // ^ propose replacement: functionTakingBool(true); + } + +In general, the following cast types are checked: + - integer expression/literal to boolean, + - floating expression/literal to boolean, + - pointer/pointer to member/``nullptr``/``NULL`` to boolean, + - boolean expression/literal to integer, + - boolean expression/literal to floating. + +The rules for generating FixIt hints are: + - in case of casts from other built-in type to bool, an explicit comparison + is proposed to make it clear what exaclty is being compared: + + - ``bool boolean = floating;`` is changed to ``bool boolean = floating == 0.0f;``, + - for other types, appropriate literals are used (``0``, ``0u``, ``0.0f``, ``0.0``, ``nullptr``), + - in case of negated expressions cast to bool, the proposed replacement with + comparison is simplified: + + - ``if (!pointer)`` is changed to ``if (pointer == nullptr)``, + - in case of casts from bool to other built-in types, an explicit ``static_cast`` + is proposed to make it clear that a cast is taking place: + + - ``int integer = boolean;`` is changed to ``int integer = static_cast(boolean);``, + - if the cast is performed on type literals, an equivalent literal is proposed, + according to what type is actually expected, for example: + + - ``functionTakingBool(0);`` is changed to ``functionTakingBool(false);``, + - ``functionTakingInt(true);`` is changed to ``functionTakingInt(1);``, + - for other types, appropriate literals are used (``false``, ``true``, ``0``, ``1``, ``0u``, ``1u``, + ``0.0f``, ``1.0f``, ``0.0``, ``1.0f``). + +Some additional accommodations are made for pre-C++11 dialects: + - ``false`` literal cast to pointer is detected, + - instead of ``nullptr``, ``NULL`` macro is proposed as replacement. + +Occurences of implicit casts inside macros and template instantiations are +deliberately ignored, as it is not clear how to deal with such cases. Index: test/clang-tidy/readability-implicit-bool-cast-allow-conditional-casts.cpp =================================================================== --- test/clang-tidy/readability-implicit-bool-cast-allow-conditional-casts.cpp +++ test/clang-tidy/readability-implicit-bool-cast-allow-conditional-casts.cpp @@ -0,0 +1,51 @@ +// RUN: %python %S/check_clang_tidy.py %s readability-implicit-bool-cast %t -config="{CheckOptions: [{key: readability-implicit-bool-cast.AllowConditionalIntegerCasts, value: 1}, {key: readability-implicit-bool-cast.AllowConditionalPointerCasts, value: 1}]}" -- -std=c++11 + +template +void functionTaking(T); + +int functionReturningInt(); +int* functionReturningPointer(); + +struct Struct { + int member; +}; + + +void regularImplicitCastIntegerToBoolIsNotIgnored() { + int integer = 0; + functionTaking(integer); + // CHECK-MESSAGES: :[[@LINE-1]]:24: warning: implicit cast 'int' -> bool [readability-implicit-bool-cast] + // CHECK-FIXES: functionTaking(integer != 0); +} + +void implicitCastIntegerToBoolInConditionalsIsAllowed() { + if (functionReturningInt()) {} + if (!functionReturningInt()) {} + int value1 = functionReturningInt() ? 1 : 2; + int value2 = ! functionReturningInt() ? 1 : 2; +} + +void regularImplicitCastPointerToBoolIsNotIgnored() { + int* pointer = nullptr; + functionTaking(pointer); + // CHECK-MESSAGES: :[[@LINE-1]]:24: warning: implicit cast 'int *' -> bool + // CHECK-FIXES: functionTaking(pointer != nullptr); + + int Struct::* memberPointer = &Struct::member; + functionTaking(memberPointer); + // CHECK-MESSAGES: :[[@LINE-1]]:24: warning: implicit cast 'int struct Struct::*' -> bool + // CHECK-FIXES: functionTaking(memberPointer != nullptr); +} + +void implicitCastPointerToBoolInConditionalsIsAllowed() { + if (functionReturningPointer()) {} + if (not functionReturningPointer()) {} + int value1 = functionReturningPointer() ? 1 : 2; + int value2 = (not functionReturningPointer()) ? 1 : 2; + + int Struct::* memberPointer = &Struct::member; + if (memberPointer) {} + if (memberPointer) {} + int value3 = memberPointer ? 1 : 2; + int value4 = (not memberPointer) ? 1 : 2; +} Index: test/clang-tidy/readability-implicit-bool-cast-cxx98.cpp =================================================================== --- test/clang-tidy/readability-implicit-bool-cast-cxx98.cpp +++ test/clang-tidy/readability-implicit-bool-cast-cxx98.cpp @@ -0,0 +1,42 @@ +// RUN: %python %S/check_clang_tidy.py %s readability-implicit-bool-cast %t -- -std=c++98 + +#include // for NULL + +template +void functionTaking(T); + +struct Struct { + int member; +}; + +void useOldNullMacroInReplacements() { + int* pointer = NULL; + functionTaking(pointer); + // CHECK-MESSAGES: :[[@LINE-1]]:24: warning: implicit cast 'int *' -> bool [readability-implicit-bool-cast] + // CHECK-FIXES: functionTaking(pointer != NULL); + + int Struct::* memberPointer = NULL; + functionTaking(!memberPointer); + // CHECK-MESSAGES: :[[@LINE-1]]:25: warning: implicit cast 'int struct Struct::*' -> bool [readability-implicit-bool-cast] + // CHECK-FIXES: functionTaking(memberPointer == NULL); +} + +void fixFalseLiteralConvertingToNullPointer() { + functionTaking(false); + // CHECK-MESSAGES: :[[@LINE-1]]:24: warning: implicit cast bool -> 'int *' [readability-implicit-bool-cast] + // CHECK-FIXES: functionTaking(NULL); + + int* pointer = NULL; + if (pointer == false) {} + // CHECK-MESSAGES: :[[@LINE-1]]:18: warning: implicit cast bool -> 'int *' [readability-implicit-bool-cast] + // CHECK-FIXES: if (pointer == NULL) {} + + functionTaking(false); + // CHECK-MESSAGES: :[[@LINE-1]]:33: warning: implicit cast bool -> 'int struct Struct::*' [readability-implicit-bool-cast] + // CHECK-FIXES: functionTaking(NULL); + + int Struct::* memberPointer = NULL; + if (memberPointer != false) {} + // CHECK-MESSAGES: :[[@LINE-1]]:24: warning: implicit cast bool -> 'int struct Struct::*' [readability-implicit-bool-cast] + // CHECK-FIXES: if (memberPointer != NULL) {} +} Index: test/clang-tidy/readability-implicit-bool-cast.cpp =================================================================== --- test/clang-tidy/readability-implicit-bool-cast.cpp +++ test/clang-tidy/readability-implicit-bool-cast.cpp @@ -0,0 +1,416 @@ +// RUN: %python %S/check_clang_tidy.py %s readability-implicit-bool-cast %t + +#include // for NULL + +template +void functionTaking(T); + +struct Struct { + int member; +}; + + +////////// Implicit cast from bool. + +void implicitCastFromBoolSimpleCases() { + bool boolean = true; + + functionTaking(boolean); + + functionTaking(boolean); + // CHECK-MESSAGES: :[[@LINE-1]]:23: warning: implicit cast bool -> 'int' [readability-implicit-bool-cast] + // CHECK-FIXES: functionTaking(static_cast(boolean)); + + functionTaking(boolean); + // CHECK-MESSAGES: :[[@LINE-1]]:33: warning: implicit cast bool -> 'unsigned long' + // CHECK-FIXES: functionTaking(static_cast(boolean)); + + functionTaking(boolean); + // CHECK-MESSAGES: :[[@LINE-1]]:24: warning: implicit cast bool -> 'char' + // CHECK-FIXES: functionTaking(static_cast(boolean)); + + functionTaking(boolean); + // CHECK-MESSAGES: :[[@LINE-1]]:25: warning: implicit cast bool -> 'float' + // CHECK-FIXES: functionTaking(static_cast(boolean)); + + functionTaking(boolean); + // CHECK-MESSAGES: :[[@LINE-1]]:26: warning: implicit cast bool -> 'double' + // CHECK-FIXES: functionTaking(static_cast(boolean)); +} + +float implicitCastFromBoolInReturnValue() { + bool boolean = false; + return boolean; + // CHECK-MESSAGES: :[[@LINE-1]]:10: warning: implicit cast bool -> 'float' + // CHECK-FIXES: return static_cast(boolean); +} + +void implicitCastFromBoolInSingleBoolExpressions() { + bool boolean = true; + + int integer = boolean - 3; + // CHECK-MESSAGES: :[[@LINE-1]]:17: warning: implicit cast bool -> 'int' + // CHECK-FIXES: int integer = static_cast(boolean) - 3; + + float floating = boolean / 0.3f; + // CHECK-MESSAGES: :[[@LINE-1]]:20: warning: implicit cast bool -> 'float' + // CHECK-FIXES: float floating = static_cast(boolean) / 0.3f; + + char character = boolean; + // CHECK-MESSAGES: :[[@LINE-1]]:20: warning: implicit cast bool -> 'char' + // CHECK-FIXES: char character = static_cast(boolean); +} + +void implicitCastFromBoollInComplexBoolExpressions() { + bool boolean = true; + bool anotherBoolean = false; + + int integer = boolean && anotherBoolean; + // CHECK-MESSAGES: :[[@LINE-1]]:17: warning: implicit cast bool -> 'int' + // CHECK-FIXES: int integer = static_cast(boolean && anotherBoolean); + + unsigned long unsignedLong = (! boolean) + 4ul; + // CHECK-MESSAGES: :[[@LINE-1]]:32: warning: implicit cast bool -> 'unsigned long' + // CHECK-FIXES: unsigned long unsignedLong = static_cast(! boolean) + 4ul; + + float floating = (boolean || anotherBoolean) * 0.3f; + // CHECK-MESSAGES: :[[@LINE-1]]:20: warning: implicit cast bool -> 'float' + // CHECK-FIXES: float floating = static_cast(boolean || anotherBoolean) * 0.3f; + + double doubleFloating = (boolean && (anotherBoolean || boolean)) * 0.3; + // CHECK-MESSAGES: :[[@LINE-1]]:27: warning: implicit cast bool -> 'double' + // CHECK-FIXES: double doubleFloating = static_cast(boolean && (anotherBoolean || boolean)) * 0.3; +} + +void implicitCastFromBoolLiterals() { + functionTaking(true); + // CHECK-MESSAGES: :[[@LINE-1]]:23: warning: implicit cast bool -> 'int' + // CHECK-FIXES: functionTaking(1); + + functionTaking(false); + // CHECK-MESSAGES: :[[@LINE-1]]:33: warning: implicit cast bool -> 'unsigned long' + // CHECK-FIXES: functionTaking(0u); + + functionTaking(true); + // CHECK-MESSAGES: :[[@LINE-1]]:24: warning: implicit cast bool -> 'char' + // CHECK-FIXES: functionTaking(1); + + functionTaking(false); + // CHECK-MESSAGES: :[[@LINE-1]]:25: warning: implicit cast bool -> 'float' + // CHECK-FIXES: functionTaking(0.0f); + + functionTaking(true); + // CHECK-MESSAGES: :[[@LINE-1]]:26: warning: implicit cast bool -> 'double' + // CHECK-FIXES: functionTaking(1.0); +} + +void implicitCastFromBoolInComparisons() { + bool boolean = true; + int integer = 0; + + functionTaking(boolean == integer); + // CHECK-MESSAGES: :[[@LINE-1]]:24: warning: implicit cast bool -> 'int' + // CHECK-FIXES: functionTaking(static_cast(boolean) == integer); + + functionTaking(integer != boolean); + // CHECK-MESSAGES: :[[@LINE-1]]:35: warning: implicit cast bool -> 'int' + // CHECK-FIXES: functionTaking(integer != static_cast(boolean)); +} + +void ignoreBoolComparisons() { + bool boolean = true; + bool anotherBoolean = false; + + functionTaking(boolean == anotherBoolean); + functionTaking(boolean != anotherBoolean); +} + +void ignoreExplicitCastsFromBool() { + bool boolean = true; + + int integer = static_cast(boolean) + 3; + float floating = static_cast(boolean) * 0.3f; + char character = static_cast(boolean); +} + +void ignoreImplicitCastFromBoolInMacroExpansions() { + bool boolean = true; + + #define CAST_FROM_BOOL_IN_MACRO_BODY boolean + 3 + int integerFromMacroBody = CAST_FROM_BOOL_IN_MACRO_BODY; + + #define CAST_FROM_BOOL_IN_MACRO_ARGUMENT(x) x + 3 + int integerFromMacroArgument = CAST_FROM_BOOL_IN_MACRO_ARGUMENT(boolean); +} + +namespace ignoreImplicitCastFromBoolInTemplateInstantiations { + +template +void templateFunction() { + bool boolean = true; + T uknownType = boolean + 3; +} + +void useOfTemplateFunction() { + templateFunction(); +} + +} // namespace ignoreImplicitCastFromBoolInTemplateInstantiations + +////////// Implicit cast to bool. + +void implicitCastToBoolSimpleCases() { + int integer = 10; + functionTaking(integer); + // CHECK-MESSAGES: :[[@LINE-1]]:24: warning: implicit cast 'int' -> bool + // CHECK-FIXES: functionTaking(integer != 0); + + unsigned long unsignedLong = 10; + functionTaking(unsignedLong); + // CHECK-MESSAGES: :[[@LINE-1]]:24: warning: implicit cast 'unsigned long' -> bool + // CHECK-FIXES: functionTaking(unsignedLong != 0u); + + float floating = 0.0f; + functionTaking(floating); + // CHECK-MESSAGES: :[[@LINE-1]]:24: warning: implicit cast 'float' -> bool + // CHECK-FIXES: functionTaking(floating != 0.0f); + + double doubleFloating = 1.0f; + functionTaking(doubleFloating); + // CHECK-MESSAGES: :[[@LINE-1]]:24: warning: implicit cast 'double' -> bool + // CHECK-FIXES: functionTaking(doubleFloating != 0.0); + + char character = 'a'; + functionTaking(character); + // CHECK-MESSAGES: :[[@LINE-1]]:24: warning: implicit cast 'char' -> bool + // CHECK-FIXES: functionTaking(character != 0); + + int* pointer = nullptr; + functionTaking(pointer); + // CHECK-MESSAGES: :[[@LINE-1]]:24: warning: implicit cast 'int *' -> bool + // CHECK-FIXES: functionTaking(pointer != nullptr); + + auto pointerToMember = &Struct::member; + functionTaking(pointerToMember); + // CHECK-MESSAGES: :[[@LINE-1]]:24: warning: implicit cast 'int struct Struct::*' -> bool + // CHECK-FIXES: functionTaking(pointerToMember != nullptr); +} + +void implicitCastToBoolInSingleExpressions() { + int integer = 10; + bool boolComingFromInt = integer; + // CHECK-MESSAGES: :[[@LINE-1]]:28: warning: implicit cast 'int' -> bool + // CHECK-FIXES: bool boolComingFromInt = integer != 0; + + float floating = 10.0f; + bool boolComingFromFloat = floating; + // CHECK-MESSAGES: :[[@LINE-1]]:30: warning: implicit cast 'float' -> bool + // CHECK-FIXES: bool boolComingFromFloat = floating != 0.0f; + + char character = 'a'; + bool boolComingFromChar = character; + // CHECK-MESSAGES: :[[@LINE-1]]:29: warning: implicit cast 'char' -> bool + // CHECK-FIXES: bool boolComingFromChar = character != 0; + + int* pointer = nullptr; + bool boolComingFromPointer = pointer; + // CHECK-MESSAGES: :[[@LINE-1]]:32: warning: implicit cast 'int *' -> bool + // CHECK-FIXES: bool boolComingFromPointer = pointer != nullptr; +} + +void implicitCastToBoolInComplexExpressions() { + bool boolean = true; + + int integer = 10; + int anotherInteger = 20; + bool boolComingFromInteger = integer + anotherInteger; + // CHECK-MESSAGES: :[[@LINE-1]]:32: warning: implicit cast 'int' -> bool + // CHECK-FIXES: bool boolComingFromInteger = (integer + anotherInteger) != 0; + + float floating = 0.2f; + bool boolComingFromFloating = floating - 0.3f || boolean; + // CHECK-MESSAGES: :[[@LINE-1]]:33: warning: implicit cast 'float' -> bool + // CHECK-FIXES: bool boolComingFromFloating = ((floating - 0.3f) != 0.0f) || boolean; + + double doubleFloating = 0.3; + bool boolComingFromDoubleFloating = (doubleFloating - 0.4) && boolean; + // CHECK-MESSAGES: :[[@LINE-1]]:39: warning: implicit cast 'double' -> bool + // CHECK-FIXES: bool boolComingFromDoubleFloating = ((doubleFloating - 0.4) != 0.0) && boolean; +} + +void implicitCastInNegationExpressions() { + int integer = 10; + bool boolComingFromNegatedInt = !integer; + // CHECK-MESSAGES: :[[@LINE-1]]:36: warning: implicit cast 'int' -> bool + // CHECK-FIXES: bool boolComingFromNegatedInt = integer == 0; + + float floating = 10.0f; + bool boolComingFromNegatedFloat = ! floating; + // CHECK-MESSAGES: :[[@LINE-1]]:39: warning: implicit cast 'float' -> bool + // CHECK-FIXES: bool boolComingFromNegatedFloat = floating == 0.0f; + + char character = 'a'; + bool boolComingFromNegatedChar = (! character); + // CHECK-MESSAGES: :[[@LINE-1]]:39: warning: implicit cast 'char' -> bool + // CHECK-FIXES: bool boolComingFromNegatedChar = (character == 0); + + int* pointer = nullptr; + bool boolComingFromNegatedPointer = not pointer; + // CHECK-MESSAGES: :[[@LINE-1]]:43: warning: implicit cast 'int *' -> bool + // CHECK-FIXES: bool boolComingFromNegatedPointer = pointer == nullptr; +} + +void implicitCastToBoolInControlStatements() { + int integer = 10; + if (integer) {} + // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: implicit cast 'int' -> bool + // CHECK-FIXES: if (integer != 0) {} + + long int longInteger = 0.2f; + for (;longInteger;) {} + // CHECK-MESSAGES: :[[@LINE-1]]:9: warning: implicit cast 'long' -> bool + // CHECK-FIXES: for (;longInteger != 0;) {} + + float floating = 0.3f; + while (floating) {} + // CHECK-MESSAGES: :[[@LINE-1]]:10: warning: implicit cast 'float' -> bool + // CHECK-FIXES: while (floating != 0.0f) {} + + double doubleFloating = 0.4; + do {} while (doubleFloating); + // CHECK-MESSAGES: :[[@LINE-1]]:16: warning: implicit cast 'double' -> bool + // CHECK-FIXES: do {} while (doubleFloating != 0.0); +} + +bool implicitCastToBoolInReturnValue() { + float floating = 1.0f; + return floating; + // CHECK-MESSAGES: :[[@LINE-1]]:10: warning: implicit cast 'float' -> bool + // CHECK-FIXES: return floating != 0.0f; +} + +void implicitCastToBoolFromLiterals() { + functionTaking(0); + // CHECK-MESSAGES: :[[@LINE-1]]:24: warning: implicit cast 'int' -> bool + // CHECK-FIXES: functionTaking(false); + + functionTaking(1); + // CHECK-MESSAGES: :[[@LINE-1]]:24: warning: implicit cast 'int' -> bool + // CHECK-FIXES: functionTaking(true); + + functionTaking(2ul); + // CHECK-MESSAGES: :[[@LINE-1]]:24: warning: implicit cast 'unsigned long' -> bool + // CHECK-FIXES: functionTaking(true); + + + functionTaking(0.0f); + // CHECK-MESSAGES: :[[@LINE-1]]:24: warning: implicit cast 'float' -> bool + // CHECK-FIXES: functionTaking(false); + + functionTaking(1.0f); + // CHECK-MESSAGES: :[[@LINE-1]]:24: warning: implicit cast 'float' -> bool + // CHECK-FIXES: functionTaking(true); + + functionTaking(2.0); + // CHECK-MESSAGES: :[[@LINE-1]]:24: warning: implicit cast 'double' -> bool + // CHECK-FIXES: functionTaking(true); + + + functionTaking('\0'); + // CHECK-MESSAGES: :[[@LINE-1]]:24: warning: implicit cast 'char' -> bool + // CHECK-FIXES: functionTaking(false); + + functionTaking('a'); + // CHECK-MESSAGES: :[[@LINE-1]]:24: warning: implicit cast 'char' -> bool + // CHECK-FIXES: functionTaking(true); + + + functionTaking(""); + // CHECK-MESSAGES: :[[@LINE-1]]:24: warning: implicit cast 'const char *' -> bool + // CHECK-FIXES: functionTaking(true); + + functionTaking("abc"); + // CHECK-MESSAGES: :[[@LINE-1]]:24: warning: implicit cast 'const char *' -> bool + // CHECK-FIXES: functionTaking(true); + + functionTaking(NULL); + // CHECK-MESSAGES: :[[@LINE-1]]:24: warning: implicit cast 'long' -> bool + // CHECK-FIXES: functionTaking(false); +} + +void implicitCastToBoolInWithOverloadedOperators() { + struct UserStruct { + int operator()(int x) { return x; } + int operator+(int y) { return y; } + }; + + UserStruct s; + + functionTaking(s(0)); + // CHECK-MESSAGES: :[[@LINE-1]]:24: warning: implicit cast 'int' -> bool + // CHECK-FIXES: functionTaking(s(0) != 0); + + functionTaking(s + 2); + // CHECK-MESSAGES: :[[@LINE-1]]:24: warning: implicit cast 'int' -> bool + // CHECK-FIXES: functionTaking((s + 2) != 0); +} + +int functionReturningInt(); +int* functionReturningPointer(); + +void ignoreImplicitCastToBoolWhenDeclaringVariableInControlStatements() { + if (int integer = functionReturningInt()) {} + + while (int* pointer = functionReturningPointer()) {} +} + +void ignoreExplicitCastsToBool() { + int integer = 10; + bool boolComingFromInt = static_cast(integer); + + float floating = 10.0f; + bool boolComingFromFloat = static_cast(floating); + + char character = 'a'; + bool boolComingFromChar = static_cast(character); + + int* pointer = nullptr; + bool booleanComingFromPointer = static_cast(pointer); +} + +void ignoreImplicitCastToBoolInMacroExpansions() { + int integer = 3; + + #define CAST_TO_BOOL_IN_MACRO_BODY integer && false + bool boolFromMacroBody = CAST_TO_BOOL_IN_MACRO_BODY; + + #define CAST_TO_BOOL_IN_MACRO_ARGUMENT(x) x || true + bool boolFromMacroArgument = CAST_TO_BOOL_IN_MACRO_ARGUMENT(integer); +} + +namespace ignoreImplicitCastToBoolInTemplateInstantiations { + +template +void templateFunction() { + T unknownType = 0; + bool boolean = unknownType; +} + +void useOfTemplateFunction() { + templateFunction(); +} + +} // namespace ignoreImplicitCastToBoolInTemplateInstantiations + +namespace ignoreUserDefinedConversionOperator { + +struct StructWithUserConversion { + operator bool(); +}; + +void useOfUserConversion() { + StructWithUserConversion structure; + functionTaking(structure); +} + +} // namespace ignoreUserDefinedConversionOperator