//===--- 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<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(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<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 auto *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

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<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 readability
} // namespace tidy
} // namespace clang
