//===--- NonConstParameterCheck.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 "NonConstParameterCheck.h"
#include "clang/AST/ASTContext.h"
#include "clang/ASTMatchers/ASTMatchFinder.h"

using namespace clang::ast_matchers;

namespace clang {
namespace tidy {
namespace readability {

void NonConstParameterCheck::registerMatchers(MatchFinder *Finder) {
  // Add parameters to Parameters.
  Finder->addMatcher(parmVarDecl(unless(isInstantiated())).bind("Parm"), this);

  // C++ constructor.
  Finder->addMatcher(cxxConstructorDecl().bind("Ctor"), this);

  // Track unused parameters, there is Wunused-parameter about unused
  // parameters.
  Finder->addMatcher(declRefExpr().bind("Ref"), this);

  // Analyse parameter usage in function.
  Finder->addMatcher(stmt(anyOf(unaryOperator(anyOf(hasOperatorName("++"),
                                                    hasOperatorName("--"))),
                                binaryOperator(), callExpr(), returnStmt(),
                                cxxConstructExpr()))
                         .bind("Mark"),
                     this);
  Finder->addMatcher(varDecl(hasInitializer(anything())).bind("Mark"), this);
}

void NonConstParameterCheck::check(const MatchFinder::MatchResult &Result) {
  if (const auto *Parm = Result.Nodes.getNodeAs<ParmVarDecl>("Parm")) {
    if (const DeclContext *D = Parm->getParentFunctionOrMethod()) {
      if (const auto *M = dyn_cast<CXXMethodDecl>(D)) {
        if (M->isVirtual() || M->size_overridden_methods() != 0)
          return;
      }
    }
    addParm(Parm);
  } else if (const auto *Ctor =
                 Result.Nodes.getNodeAs<CXXConstructorDecl>("Ctor")) {
    for (const auto *Parm : Ctor->parameters())
      addParm(Parm);
    for (const auto *Init : Ctor->inits())
      markCanNotBeConst(Init->getInit(), true);
  } else if (const auto *Ref = Result.Nodes.getNodeAs<DeclRefExpr>("Ref")) {
    setReferenced(Ref);
  } else if (const auto *S = Result.Nodes.getNodeAs<Stmt>("Mark")) {
    if (const auto *B = dyn_cast<BinaryOperator>(S)) {
      if (B->isAssignmentOp())
        markCanNotBeConst(B, false);
    } else if (const auto *CE = dyn_cast<CallExpr>(S)) {
      // Typically, if a parameter is const then it is fine to make the data
      // const. But sometimes the data is written even though the parameter
      // is const. Mark all data passed by address to the function.
      for (const auto *Arg : CE->arguments()) {
        markCanNotBeConst(Arg->IgnoreParenCasts(), true);
      }

      // Data passed by nonconst reference should not be made const.
      if (const FunctionDecl *FD = CE->getDirectCallee()) {
        unsigned ArgNr = 0U;
        for (const auto *Par : FD->parameters()) {
          if (ArgNr >= CE->getNumArgs())
            break;
          const Expr *Arg = CE->getArg(ArgNr++);
          // Is this a non constant reference parameter?
          const Type *ParType = Par->getType().getTypePtr();
          if (!ParType->isReferenceType() || Par->getType().isConstQualified())
            continue;
          markCanNotBeConst(Arg->IgnoreParenCasts(), false);
        }
      }
    } else if (const auto *CE = dyn_cast<CXXConstructExpr>(S)) {
      for (const auto *Arg : CE->arguments()) {
        markCanNotBeConst(Arg->IgnoreParenCasts(), true);
      }
    } else if (const auto *R = dyn_cast<ReturnStmt>(S)) {
      markCanNotBeConst(R->getRetValue(), true);
    } else if (const auto *U = dyn_cast<UnaryOperator>(S)) {
      markCanNotBeConst(U, true);
    }
  } else if (const auto *VD = Result.Nodes.getNodeAs<VarDecl>("Mark")) {
    const QualType T = VD->getType();
    if ((T->isPointerType() && !T->getPointeeType().isConstQualified()) ||
        T->isArrayType())
      markCanNotBeConst(VD->getInit(), true);
  }
}

void NonConstParameterCheck::addParm(const ParmVarDecl *Parm) {
  // Only add nonconst integer/float pointer parameters.
  const QualType T = Parm->getType();
  if (!T->isPointerType() || T->getPointeeType().isConstQualified() ||
      !(T->getPointeeType()->isIntegerType() ||
        T->getPointeeType()->isFloatingType()))
    return;

  if (Parameters.find(Parm) != Parameters.end())
    return;

  ParmInfo PI;
  PI.IsReferenced = false;
  PI.CanBeConst = true;
  Parameters[Parm] = PI;
}

void NonConstParameterCheck::setReferenced(const DeclRefExpr *Ref) {
  auto It = Parameters.find(dyn_cast<ParmVarDecl>(Ref->getDecl()));
  if (It != Parameters.end())
    It->second.IsReferenced = true;
}

void NonConstParameterCheck::onEndOfTranslationUnit() {
  diagnoseNonConstParameters();
}

void NonConstParameterCheck::diagnoseNonConstParameters() {
  for (const auto &It : Parameters) {
    const ParmVarDecl *Par = It.first;
    const ParmInfo &ParamInfo = It.second;

    // Unused parameter => there are other warnings about this.
    if (!ParamInfo.IsReferenced)
      continue;

    // Parameter can't be const.
    if (!ParamInfo.CanBeConst)
      continue;

    diag(Par->getLocation(), "pointer parameter '%0' can be pointer to const")
        << Par->getName()
        << FixItHint::CreateInsertion(Par->getLocStart(), "const ");
  }
}

void NonConstParameterCheck::markCanNotBeConst(const Expr *E,
                                               bool CanNotBeConst) {
  if (!E)
    return;

  if (const auto *Cast = dyn_cast<ImplicitCastExpr>(E)) {
    // If expression is const then ignore usage.
    const QualType T = Cast->getType();
    if (T->isPointerType() && T->getPointeeType().isConstQualified())
      return;
  }

  E = E->IgnoreParenCasts();

  if (const auto *B = dyn_cast<BinaryOperator>(E)) {
    if (B->isAdditiveOp()) {
      // p + 2
      markCanNotBeConst(B->getLHS(), CanNotBeConst);
      markCanNotBeConst(B->getRHS(), CanNotBeConst);
    } else if (B->isAssignmentOp()) {
      markCanNotBeConst(B->getLHS(), false);

      // If LHS is not const then RHS can't be const.
      const QualType T = B->getLHS()->getType();
      if (T->isPointerType() && !T->getPointeeType().isConstQualified())
        markCanNotBeConst(B->getRHS(), true);
    }
  } else if (const auto *C = dyn_cast<ConditionalOperator>(E)) {
    markCanNotBeConst(C->getTrueExpr(), CanNotBeConst);
    markCanNotBeConst(C->getFalseExpr(), CanNotBeConst);
  } else if (const auto *U = dyn_cast<UnaryOperator>(E)) {
    if (U->getOpcode() == UO_PreInc || U->getOpcode() == UO_PreDec ||
        U->getOpcode() == UO_PostInc || U->getOpcode() == UO_PostDec) {
      if (const auto *SubU =
              dyn_cast<UnaryOperator>(U->getSubExpr()->IgnoreParenCasts()))
        markCanNotBeConst(SubU->getSubExpr(), true);
      markCanNotBeConst(U->getSubExpr(), CanNotBeConst);
    } else if (U->getOpcode() == UO_Deref) {
      if (!CanNotBeConst)
        markCanNotBeConst(U->getSubExpr(), true);
    } else {
      markCanNotBeConst(U->getSubExpr(), CanNotBeConst);
    }
  } else if (const auto *A = dyn_cast<ArraySubscriptExpr>(E)) {
    markCanNotBeConst(A->getBase(), true);
  } else if (const auto *CLE = dyn_cast<CompoundLiteralExpr>(E)) {
    markCanNotBeConst(CLE->getInitializer(), true);
  } else if (const auto *Constr = dyn_cast<CXXConstructExpr>(E)) {
    for (const auto *Arg : Constr->arguments()) {
      if (const auto *M = dyn_cast<MaterializeTemporaryExpr>(Arg))
        markCanNotBeConst(cast<Expr>(M->getTemporary()), CanNotBeConst);
    }
  } else if (const auto *ILE = dyn_cast<InitListExpr>(E)) {
    for (unsigned I = 0U; I < ILE->getNumInits(); ++I)
      markCanNotBeConst(ILE->getInit(I), true);
  } else if (CanNotBeConst) {
    // Referencing parameter.
    if (const auto *D = dyn_cast<DeclRefExpr>(E)) {
      auto It = Parameters.find(dyn_cast<ParmVarDecl>(D->getDecl()));
      if (It != Parameters.end())
        It->second.CanBeConst = false;
    }
  }
}

} // namespace readability
} // namespace tidy
} // namespace clang
