| //===--- 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<Stmt, ParenExpr> parenExpr; |
| |
| 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<Expr> 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" : "0"; |
| |
| default: |
| llvm_unreachable("Unexpected cast kind"); |
| } |
| } |
| |
| bool isUnaryLogicalNotOperator(const Stmt *Statement) { |
| const auto *UnaryOperatorExpression = |
| llvm::dyn_cast<UnaryOperator>(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) { |
| if (const CXXOperatorCallExpr *OverloadedOperatorCall = |
| llvm::dyn_cast<CXXOperatorCallExpr>(Statement)) { |
| return areParensNeededForOverloadedOperator( |
| OverloadedOperatorCall->getOperator()); |
| } |
| |
| return llvm::isa<BinaryOperator>(Statement) || |
| llvm::isa<UnaryOperator>(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<UnaryOperator>(ParentStatement)->getSubExpr()->getLocStart(); |
| Diagnostic.AddFixItHint(FixItHint::CreateRemoval( |
| CharSourceRange::getCharRange(ParentStartLoc, ParentEndLoc))); |
| |
| auto FurtherParents = Context.getParents(*ParentStatement); |
| ParentStatement = FurtherParents[0].get<Stmt>(); |
| } |
| |
| 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<IntegerLiteral>(Expression)) { |
| return (IntLit->getValue() == 0) ? "false" : "true"; |
| } |
| |
| if (const auto *FloatLit = llvm::dyn_cast<FloatingLiteral>(Expression)) { |
| llvm::APFloat FloatLitAbsValue = FloatLit->getValue(); |
| FloatLitAbsValue.clearSign(); |
| return (FloatLitAbsValue.bitcastToAPInt() == 0) ? "false" : "true"; |
| } |
| |
| if (const auto *CharLit = llvm::dyn_cast<CharacterLiteral>(Expression)) { |
| return (CharLit->getValue() == 0) ? "false" : "true"; |
| } |
| |
| if (llvm::isa<StringLiteral>(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<ParenExpr>(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 "0"; |
| } |
| |
| 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<CXXBoolLiteralExpr>(CastExpression->getSubExpr()); |
| |
| Diagnostic.AddFixItHint(FixItHint::CreateReplacement( |
| CharSourceRange::getTokenRange(StartLoc, EndLoc), |
| getEquivalentLiteralForBoolLiteral(BoolLiteralExpression, DestinationType, |
| Context))); |
| } |
| |
| StatementMatcher createConditionalExpressionMatcher() { |
| return stmt(anyOf(ifStmt(), conditionalOperator(), |
| parenExpr(hasParent(conditionalOperator())))); |
| } |
| |
| bool isAllowedConditionalCast(const ImplicitCastExpr *CastExpression, |
| ASTContext &Context) { |
| auto AllowedConditionalMatcher = stmt(hasParent(stmt( |
| anyOf(createConditionalExpressionMatcher(), |
| unaryOperator(hasOperatorName("!"), |
| hasParent(createConditionalExpressionMatcher())))))); |
| |
| 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<ImplicitCastExpr>("implicitCastToBool")) { |
| const auto *ParentStatement = Result.Nodes.getNodeAs<Stmt>("parentStmt"); |
| return handleCastToBool(CastToBool, ParentStatement, *Result.Context); |
| } |
| |
| if (const auto *CastFromBool = |
| Result.Nodes.getNodeAs<ImplicitCastExpr>("implicitCastFromBool")) { |
| const auto *FurtherImplicitCastExpression = |
| Result.Nodes.getNodeAs<ImplicitCastExpr>("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<CXXBoolLiteralExpr>(CastExpression->getSubExpr())) { |
| addFixItHintsForLiteralCastFromBool(Diagnostic, CastExpression, Context, |
| DestinationType); |
| } else { |
| addFixItHintsForGenericExpressionCastFromBool( |
| Diagnostic, CastExpression, Context, DestinationTypeString); |
| } |
| } |
| |
| } // namespace tidy |
| } // namespace clang |