//===-- 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>
#include <stdio.h>

using namespace clang;
using namespace ento;

namespace {
class StrLengthCalculationChecker : public Checker<check::PostStmt<Stmt> > {
  OwningPtr<BugType> ContainerSizeBugType;
private:
  //wthether the function is std::list::size()
  bool checkFunctionCall(const Expr *E, CheckerContext &C) const;
  bool checkFunctionCall(const CallExpr *CE, CheckerContext &C) const;
  void reportWarning(const Expr *BeginStmt, SourceRange SR, CheckerContext &C) const;

  bool getFunctionName(const FunctionDecl *FD, std::string &Name) const;

  bool classNameIsInterested(const std::string &Name) const
  {
	if (Name == "basic_string")
	  return true;   
	/*
	if (Name == "string") 
	  return true;
	if (Name == "wstring")
	  return true;
	*/
	return false;
  }

  //for debug
  void printLocation(const CallExpr *CE, const SourceManager &Manager) const;

public:
  StrLengthCalculationChecker();

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

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

void StrLengthCalculationChecker::checkPostStmt(const Stmt *S, CheckerContext &C) const {
	const CallExpr *CE = dyn_cast<CallExpr>(S);
	if (CE) {
		//printLocation(CE, C.getSourceManager());
		if (checkFunctionCall(CE, C)) {
			reportWarning(CE, CE->getSourceRange(), C);
			return ;
		}
	}

	return ;
}

//strlen(s.c_str())
bool StrLengthCalculationChecker::checkFunctionCall(const Expr *E, CheckerContext &C) const{
	const CallExpr *CE = dyn_cast<CallExpr>(E);
	if (!CE)
		return false;
	return checkFunctionCall(CE, C);
}

bool StrLengthCalculationChecker::checkFunctionCall(const CallExpr *CE, CheckerContext &C) const{
	const FunctionDecl *FD = CE->getDirectCallee();
	if (!FD)
		return false;
	std::string FunctionName;
	if (!getFunctionName(FD, FunctionName)) {
		return false;
	}
	//printf("function name is : %s\n", FunctionName.c_str());
	if ((FunctionName!= "strlen") && (FunctionName != "wcslen"))
		return false;;

	//FunctionName may be "strlen" or "wcslen"
	
	if (CE->getNumArgs() == 0)
		return false;
	const Expr *const*Args = CE->getArgs();
	if (!Args)
		return false;
	const Expr *ArgExpr = Args[0];
	if (!ArgExpr)
		return false;

	const CXXMemberCallExpr *MemberCallExpr = dyn_cast<CXXMemberCallExpr>(ArgExpr);
	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() != "c_str") {
		return false;
	}

	std::string RecordName = RD	->getNameAsString();
	//printf("class name : %s\n", RecordName.c_str());
	if (!classNameIsInterested(RecordName)){
		return false;
	}

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

	IdentifierInfo * II = ND->getIdentifier();
	if (!II) {
		return false;
	}
	std::string NamespaceName(II->getNameStart(), II->getLength());
	if (NamespaceName != "std")
		return false;
	
	return true;
}

void StrLengthCalculationChecker::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 = "it is more efficient to use string::length() to calculate the length of an std::string or std::wstring";
	BugReport *R = new BugReport(*ContainerSizeBugType, Description, Location);
	R->addRange(SR);
	//R->markInteresting(C.getSVal(B));
	C.emitReport(R);
}

bool StrLengthCalculationChecker::getFunctionName(const FunctionDecl *FD, std::string &Name) const {
	IdentifierInfo * II = FD->getIdentifier();
	if (!II) {
		return false;
	}
	Name.assign(II->getNameStart(), II->getLength());
	return true;	
}

void StrLengthCalculationChecker::printLocation(const CallExpr *CE, const SourceManager &Manager) const {
	if (!CE)
		return ;
	SourceLocation StartLocation = CE->getLocStart();
	SourceLocation EndLocation = CE->getLocEnd();
	std::string Start = StartLocation.printToString(Manager);
	std::string End = EndLocation.printToString(Manager);
	printf("source location : %s, %s\n", Start.c_str(), End.c_str());
}

void ento::registerStrLengthCalculationChecker(CheckerManager &mgr) {
  mgr.registerChecker<StrLengthCalculationChecker>();
}
