//==- CheckObjCViewController.cpp - Check ObjC UIViewController subclass implementation --*- C++ -*-==//
//
//                     The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
//
//  This file defines a CheckObjCViewController, a checker that
//  analyzes an UIViewController implementation to determine if it
//  correctly calls super in the methods where this is mandatory.
//
//===----------------------------------------------------------------------===//

#include "ClangSACheckers.h"
#include "clang/StaticAnalyzer/Core/Checker.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/AnalysisManager.h"
#include "clang/StaticAnalyzer/Core/BugReporter/PathDiagnostic.h"
#include "clang/StaticAnalyzer/Core/BugReporter/BugReporter.h"
#include "clang/AST/ExprObjC.h"
#include "clang/AST/Expr.h"
#include "clang/AST/DeclObjC.h"
#include "clang/Basic/LangOptions.h"
#include "llvm/Support/raw_ostream.h"

using namespace clang;
using namespace ento;

static bool scan_selector(Stmt *S, Selector selector) {

  if (ObjCMessageExpr *ME = dyn_cast<ObjCMessageExpr>(S))
    if (ME->getSelector() == selector) {
      switch (ME->getReceiverKind()) {
      case ObjCMessageExpr::Instance: return false;
      case ObjCMessageExpr::SuperInstance: return true;
      case ObjCMessageExpr::Class: break;
      case ObjCMessageExpr::SuperClass: break;
      }
    }

  // Recurse to children.

  for (Stmt::child_iterator I = S->child_begin(), E= S->child_end(); I!=E; ++I)
    if (*I && scan_selector(*I, selector))
      return true;

  return false;
}


static void checkObjCViewController(const ObjCImplementationDecl *D,
                             const LangOptions& LOpts, BugReporter& BR) {

  ASTContext &Ctx = BR.getContext();
  const ObjCInterfaceDecl *ID = D->getClassInterface();


  // Determine if the class subclasses UIViewController.
  IdentifierInfo* NSObjectII = &Ctx.Idents.get("NSObject");
  IdentifierInfo* ViewController = &Ctx.Idents.get("UIViewController");
  bool hasProperSuper = false;

  for ( ; ID ; ID = ID->getSuperClass()) {
    IdentifierInfo *II = ID->getIdentifier();

    if (II == NSObjectII)
      break;

    if (II == ViewController)
      hasProperSuper = true;
  }

  if (!hasProperSuper)
	return;

  if (!ID)
    return;
	
  std::vector< std::pair<std::string, int> > list;
  list.push_back(std::make_pair("addChildViewController", 1));
  list.push_back(std::make_pair("viewDidAppear", 1));
  list.push_back(std::make_pair("viewDidDisappear", 1));
  list.push_back(std::make_pair("viewWillAppear", 1));
  list.push_back(std::make_pair("viewWillDisappear", 1));
  list.push_back(std::make_pair("removeFromParentViewController", 0));
  list.push_back(std::make_pair("didReceiveMemoryWarning", 0));
  list.push_back(std::make_pair("viewDidUnload", 0));
  list.push_back(std::make_pair("viewWillUnload", 0));
  list.push_back(std::make_pair("viewDidLoad", 0));
  list.push_back(std::make_pair("transitionFromViewControllertoViewControllerdurationoptionsanimationscompletion", 6));


  for (size_t i = 0; i < list.size(); i++)
  { 
      int argumentCount = list[i].second;
      std::string selectorString = list[i].first;
      const char * selectorCString = selectorString.c_str();

	  // Get the selector.
	  IdentifierInfo* II = &Ctx.Idents.get(selectorCString);
	  Selector S = Ctx.Selectors.getSelector(argumentCount, &II);
	  ObjCMethodDecl *MD = 0;
	
	  // Scan the instance methods for the selector.
	  for (ObjCImplementationDecl::instmeth_iterator I = D->instmeth_begin(),
		   E = D->instmeth_end(); I!=E; ++I) {
	
		if ((*I)->getSelector() == S) {
		  MD = *I;
		  break;
		}
	  }
	
	  PathDiagnosticLocation DLoc =
		PathDiagnosticLocation::createBegin(D, BR.getSourceManager());
	
	
	  // selector found.  Scan for missing super call.
	  if (MD && MD->getBody() && !scan_selector(MD->getBody(), S)) {
	
		const char* name = "missing call to superclass";
	
		std::string buf;
		llvm::raw_string_ostream os(buf);
		os << "The '" << selectorString << ":' instance method in UIViewController subclass '" << *D
		   << "' does not send a '" << selectorString << ":' message to its super class"
			   " (missing [super " << selectorString << ":])";
	
		BR.EmitBasicReport(MD, name, categories::CoreFoundationObjectiveC,
						   os.str(), DLoc);
	  }
  }
}

//===----------------------------------------------------------------------===//
// ObjCViewControllerChecker
//===----------------------------------------------------------------------===//

namespace {
class ObjCViewControllerChecker : public Checker<
                                      check::ASTDecl<ObjCImplementationDecl> > {
public:
  void checkASTDecl(const ObjCImplementationDecl *D, AnalysisManager& mgr,
                    BugReporter &BR) const {
    checkObjCViewController(cast<ObjCImplementationDecl>(D), mgr.getLangOpts(), BR);
  }
};
}

void ento::registerObjCViewControllerChecker(CheckerManager &mgr) {
  mgr.registerChecker<ObjCViewControllerChecker>();
}


/* should check all these - list is not exhaustive. there are also cases where calling super is suggested but not "mandatory"

*** trivial cases:
UIViewController
- loadView (minus indicates it should never call super)

UIDocument
+ finishedHandlingError:recovered:
+ finishedHandlingError:recovered:

UIView
+ initWithFrame

UIResponder
+ resignFirstResponder

NSResponder
+ cursorUpdate

*** difficult cases:

UICollectionViewController
+ loadView (take care because UIViewController subclasses should NOT call super in loadView, but UICollectionViewController should)

NSObject
+ init* (take care because any init* method has to call *a* init method in self /or/ super)
+ doesNotRecognizeSelector (take care because it only has to call super if it doesnt throw)

UIPopoverBackgroundView (take care because some of those are class methods)
- arrowDirection
- arrowOffset
- arrowBase
- arrowHeight
- contentViewInsets

UITextSelectionRect (take care because some of those are properties)
- rect
- range
- writingDirection
- isVertical
- containsStart
- containsEnd

*/
