//===--- 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 readability { namespace { 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(booleanType()))))); } 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(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(Statement)) { 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)) { llvm::APFloat FloatLitAbsValue = FloatLit->getValue(); FloatLitAbsValue.clearSign(); return (FloatLitAbsValue.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 "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(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 ImplicitBoolCastCheck::ImplicitBoolCastCheck(StringRef Name, ClangTidyContext *Context) : ClangTidyCheck(Name, Context), AllowConditionalIntegerCasts( Options.get("AllowConditionalIntegerCasts", false)), AllowConditionalPointerCasts( Options.get("AllowConditionalPointerCasts", false)) {} void ImplicitBoolCastCheck::storeOptions( ClangTidyOptions::OptionMap &Opts) { Options.store(Opts, "AllowConditionalIntegerCasts", AllowConditionalIntegerCasts); Options.store(Opts, "AllowConditionalPointerCasts", AllowConditionalPointerCasts); } 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 readability } // namespace tidy } // namespace clang