//===-- SimpleStreamChecker.cpp -----------------------------------------*- C++ -*--//
//
//                     The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
//
// Defines a checker for std::list::size()
// It is more efficient to use containers empty() method to identify an empty container.
//
//===----------------------------------------------------------------------===//

#include "ClangSACheckers.h"
#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"
#include "clang/StaticAnalyzer/Core/Checker.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"
#include "clang/AST/ExprCXX.h"
#include "llvm/Support/raw_ostream.h"
//#include <assert.h>
#include <string>

using namespace clang;
using namespace ento;

namespace {
class EmptyContainerChecker : public Checker<check::PostStmt<Stmt> > {
  OwningPtr<BugType> ContainerSizeBugType;
private:
  void checkBinaryOperator(const BinaryOperator*B, CheckerContext &C) const;
  void checkUnaryOperator(const UnaryOperator *U, CheckerContext &C) const;

  //return value: true , yes, this Expr is a const zero 
  //			  false, no, this Expr is not a const zero
  bool checkIsScalarZero(const Expr *E, CheckerContext &C) const;

  //wthether the function is std::list::size()
  bool checkFunctionName(const Expr *E, CheckerContext &C) const;
  void reportWarning(const Expr *BeginStmt, SourceRange SR, CheckerContext &C) const;

public:
  EmptyContainerChecker();

  /// Process fclose.
  void checkPostStmt(const Stmt *S, CheckerContext &C) const;
};
} // end anonymous namespace

EmptyContainerChecker::EmptyContainerChecker() {
  // Initialize the bug types.
  ContainerSizeBugType.reset(new BugType("size() called",
                                       "not god usage"));
}

#if 0
void printDeclNamespaceName(const Decl *D) {
	 const NamedDecl *ND = dyn_cast<NamedDecl>(D); 
	 if (ND) {
       std::string Name = ND->getNameAsString();
       printf("call name : %s\n", Name.c_str());
	 }
	 else {
	    printf("this decl is not NamedDecl\n"); 
	 }
}
#endif

void EmptyContainerChecker::checkPostStmt(const Stmt *S, CheckerContext &C) const {
	const UnaryOperator * U = dyn_cast<UnaryOperator>(S);
	if (U) {
		checkUnaryOperator(U, C);
		return ;
	}
	
	const BinaryOperator *B = dyn_cast<BinaryOperator>(S);
	if (B) {
		checkBinaryOperator(B, C);
		return ;
	}

	return ;
}

void EmptyContainerChecker::checkBinaryOperator(const BinaryOperator *B, CheckerContext &C) const {
	if (!B->isEqualityOp()){
		return ;
	} 
	Expr *RightExpr = B->getRHS();
	Expr *LeftExpr = B->getLHS();
	if ((!LeftExpr) || (!RightExpr))
		return ;

	if (checkIsScalarZero(RightExpr, C)) {
		if (checkFunctionName(LeftExpr, C)) {
			reportWarning(LeftExpr, B->getSourceRange(), C);
		}
	}
	else if (checkIsScalarZero(LeftExpr, C)) {
		if (checkFunctionName(RightExpr, C)) {
			reportWarning(LeftExpr, B->getSourceRange(), C);
		}
	}
}

void EmptyContainerChecker::checkUnaryOperator(const UnaryOperator *U, CheckerContext &C) const {
	if (U->getOpcode() != UO_LNot)  {
		return ;
	}

	Expr *E = U->getSubExpr();
	if (!E)
		return ;

	ImplicitCastExpr * ICE = dyn_cast<ImplicitCastExpr>(E);
	if (ICE) {
		E = E->IgnoreImpCasts();
	}

	if (checkFunctionName(E, C)) {
		reportWarning(E, E->getSourceRange(), C);
	}
}


bool EmptyContainerChecker::checkIsScalarZero(const Expr *E, CheckerContext &C) const{
	if (!E->getType()->isScalarType()){
		return false;
	}

	SVal V = C.getState()->getSVal(E, C.getLocationContext());
	Optional<DefinedSVal> DV = V.getAs<DefinedSVal>();

	// Divide-by-undefined handled in the generic checking for uses of
	// undefined values.
	if (!DV)
		return false;

	// Check for divide by zero.
	ConstraintManager &CM = C.getConstraintManager();
	ProgramStateRef stateNotZero, stateZero;
	llvm::tie(stateNotZero, stateZero) = CM.assumeDual(C.getState(), *DV);

	if (stateNotZero) {
		return false;
	}

	return true;
}

bool EmptyContainerChecker::checkFunctionName(const Expr *E, CheckerContext &C) const{
	const CXXMemberCallExpr *MemberCallExpr = dyn_cast<CXXMemberCallExpr>(E);
	if (!MemberCallExpr) {
		return false;
	}

	const CXXMethodDecl *MD = MemberCallExpr->getMethodDecl();
	const CXXRecordDecl *RD = MemberCallExpr->getRecordDecl();
	if (!MD) {
		return false;
	}
	if (!RD) {
		return false;
	}

	if (MD->getNameAsString() != "size") {
		return false;
	}

	std::string RecordName = RD	->getNameAsString();
	if (RecordName != "list") {
		return false;
	}

	const DeclContext *DC = RD->getDeclContext();
    const NamespaceDecl *ND = dyn_cast<NamespaceDecl>(DC);
	if (!ND) {
		return false;
	}

	//ND = ND->getOriginalNamespace();
/*	
	std::string NDName = ND->getNameAsString();
	//printf("namespace name is : %s, length=%ld\n", NDName.c_str(), NDName.size());
	if (NDName != "std"){
		return false;
	}*/
	IdentifierInfo * II = ND->getIdentifier();
	if (!II) {
		return false;
	}
	std::string Name(II->getNameStart(), II->getLength());
	if (Name != "std")
		return false;
	
	return true;
}

void EmptyContainerChecker::reportWarning(const Expr *BeginStmt, SourceRange SR, CheckerContext &C) const {
	LocationOrAnalysisDeclContext LAC = (LocationOrAnalysisDeclContext)C.getCurrentAnalysisDeclContext();
	PathDiagnosticLocation Location =
		PathDiagnosticLocation::createBegin(BeginStmt, C.getSourceManager(), LAC);

	
	const char *Description = "use empty() instead of size() to check a standard container is emty";
	BugReport *R = new BugReport(*ContainerSizeBugType, Description, Location);
	R->addRange(SR);
	//R->markInteresting(C.getSVal(B));
	C.emitReport(R);
}

void ento::registerEmptyContainerChecker(CheckerManager &mgr) {
  mgr.registerChecker<EmptyContainerChecker>();
}
