//===- ObjCMessage.h - Wrapper for ObjC messages and dot syntax ---*- 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 ObjCMessage which serves as a common wrapper for ObjC
// message expressions or implicit messages for loading/storing ObjC properties.
//
//===----------------------------------------------------------------------===//

#ifndef LLVM_CLANG_STATICANALYZER_PATHSENSITIVE_OBJCMESSAGE
#define LLVM_CLANG_STATICANALYZER_PATHSENSITIVE_OBJCMESSAGE

#include "clang/StaticAnalyzer/Core/PathSensitive/SVals.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/ProgramState.h"
#include "clang/AST/ExprObjC.h"
#include "clang/AST/ExprCXX.h"
#include "clang/Basic/SourceManager.h"
#include "llvm/ADT/PointerUnion.h"
#include "llvm/ADT/StringExtras.h"
#include "llvm/Support/Compiler.h"

namespace clang {
namespace ento {
using llvm::StrInStrNoCase;

/// \brief Represents both explicit ObjC message expressions and implicit
/// messages that are sent for handling properties in dot syntax.
class ObjCMessage {
  const ObjCMessageExpr *Msg;
  const ObjCPropertyRefExpr *PE;
  const bool IsPropSetter;
public:
  ObjCMessage() : Msg(0), PE(0), IsPropSetter(false) {}

  ObjCMessage(const ObjCMessageExpr *E, const ObjCPropertyRefExpr *pe = 0,
              bool isSetter = false)
    : Msg(E), PE(pe), IsPropSetter(isSetter) {
    assert(E && "should not be initialized with null expression");
  }

  bool isValid() const { return Msg; }
  
  bool isPureMessageExpr() const { return !PE; }

  bool isPropertyGetter() const { return PE && !IsPropSetter; }

  bool isPropertySetter() const {
    return IsPropSetter;
  }

  const Expr *getMessageExpr() const { 
    return Msg;
  }

  QualType getType(ASTContext &ctx) const {
    return Msg->getType();
  }

  QualType getResultType(ASTContext &ctx) const {
    if (const ObjCMethodDecl *MD = Msg->getMethodDecl())
      return MD->getResultType();
    return getType(ctx);
  }

  ObjCMethodFamily getMethodFamily() const {
    return Msg->getMethodFamily();
  }

  Selector getSelector() const {
    return Msg->getSelector();
  }

  const Expr *getInstanceReceiver() const {
    return Msg->getInstanceReceiver();
  }

  SVal getInstanceReceiverSVal(ProgramStateRef State,
                               const LocationContext *LC) const {
    if (!isInstanceMessage())
      return UndefinedVal();
    if (const Expr *Ex = getInstanceReceiver())
      return State->getSValAsScalarOrLoc(Ex, LC);

    // An instance message with no expression means we are sending to super.
    // In this case the object reference is the same as 'self'.
    const ImplicitParamDecl *SelfDecl = LC->getSelfDecl();
    assert(SelfDecl && "No message receiver Expr, but not in an ObjC method");
    return State->getSVal(State->getRegion(SelfDecl, LC));
  }

  bool isInstanceMessage() const {
    return Msg->isInstanceMessage();
  }

  const ObjCMethodDecl *getMethodDecl() const {
    return Msg->getMethodDecl();
  }

  const ObjCInterfaceDecl *getReceiverInterface() const {
    return Msg->getReceiverInterface();
  }

  SourceLocation getSuperLoc() const {
    if (PE)
      return PE->getReceiverLocation();
    return Msg->getSuperLoc();
  }  

  SourceRange getSourceRange() const LLVM_READONLY {
    if (PE)
      return PE->getSourceRange();
    return Msg->getSourceRange();
  }

  unsigned getNumArgs() const {
    return Msg->getNumArgs();
  }

  SVal getArgSVal(unsigned i,
                  const LocationContext *LCtx,
                  ProgramStateRef state) const {
    assert(i < getNumArgs() && "Invalid index for argument");
    return state->getSVal(Msg->getArg(i), LCtx);
  }

  QualType getArgType(unsigned i) const {
    assert(i < getNumArgs() && "Invalid index for argument");
    return Msg->getArg(i)->getType();
  }

  const Expr *getArgExpr(unsigned i) const {
    assert(i < getNumArgs() && "Invalid index for argument");
    return Msg->getArg(i);
  }

  SourceRange getArgSourceRange(unsigned i) const {
    const Expr *argE = getArgExpr(i);
    return argE->getSourceRange();
  }

  SourceRange getReceiverSourceRange() const {
    if (PE) {
      if (PE->isObjectReceiver())
        return PE->getBase()->getSourceRange();
    }
    else {
      return Msg->getReceiverRange();
    }

    // FIXME: This isn't a range.
    return PE->getReceiverLocation();
  }
};

/// \brief Common wrapper for a call expression, ObjC message, or C++ 
/// constructor, mainly to provide a common interface for their arguments.
class CallOrObjCMessage {
  llvm::PointerUnion<const CallExpr *, const CXXConstructExpr *> CallE;
  ObjCMessage Msg;
  ProgramStateRef State;
  const LocationContext *LCtx;
public:
  CallOrObjCMessage(const CallExpr *callE, ProgramStateRef state,
                    const LocationContext *lctx)
    : CallE(callE), State(state), LCtx(lctx) {}
  CallOrObjCMessage(const CXXConstructExpr *consE, ProgramStateRef state,
                    const LocationContext *lctx)
    : CallE(consE), State(state), LCtx(lctx) {}
  CallOrObjCMessage(const ObjCMessage &msg, ProgramStateRef state,
                    const LocationContext *lctx)
    : CallE((CallExpr *)0), Msg(msg), State(state), LCtx(lctx) {}

  QualType getResultType(ASTContext &ctx) const;
  
  bool isFunctionCall() const {
    return CallE && CallE.is<const CallExpr *>();
  }

  bool isCXXConstructExpr() const {
    return CallE && CallE.is<const CXXConstructExpr *>();
  }

  bool isObjCMessage() const {
    return !CallE;
  }

  bool isCXXCall() const {
    const CallExpr *ActualCallE = CallE.dyn_cast<const CallExpr *>();
    return ActualCallE && isa<CXXMemberCallExpr>(ActualCallE);
  }

  /// Check if the callee is declared in the system header.
  bool isInSystemHeader() const {
    if (const Decl *FD = getDecl()) {
      const SourceManager &SM =
        State->getStateManager().getContext().getSourceManager();
      return SM.isInSystemHeader(FD->getLocation());
    }
    return false;
  }

  const Expr *getOriginExpr() const {
    if (!CallE)
      return Msg.getMessageExpr();
    if (const CXXConstructExpr *Ctor =
          CallE.dyn_cast<const CXXConstructExpr *>())
      return Ctor;
    return CallE.get<const CallExpr *>();
  }
  
  SVal getFunctionCallee() const;
  SVal getCXXCallee() const;
  SVal getInstanceMessageReceiver(const LocationContext *LC) const;

  /// Get the declaration of the function or method.
  const Decl *getDecl() const;

  unsigned getNumArgs() const {
    if (!CallE)
      return Msg.getNumArgs();
    if (const CXXConstructExpr *Ctor =
          CallE.dyn_cast<const CXXConstructExpr *>())
      return Ctor->getNumArgs();
    return CallE.get<const CallExpr *>()->getNumArgs();
  }

  SVal getArgSVal(unsigned i) const {
    assert(i < getNumArgs());
    if (!CallE)
      return Msg.getArgSVal(i, LCtx, State);
    return State->getSVal(getArg(i), LCtx);
  }

  const Expr *getArg(unsigned i) const {
    assert(i < getNumArgs());
    if (!CallE)
      return Msg.getArgExpr(i);
    if (const CXXConstructExpr *Ctor =
          CallE.dyn_cast<const CXXConstructExpr *>())
      return Ctor->getArg(i);
    return CallE.get<const CallExpr *>()->getArg(i);
  }

  SourceRange getArgSourceRange(unsigned i) const {
    assert(i < getNumArgs());
    if (CallE)
      return getArg(i)->getSourceRange();
    return Msg.getArgSourceRange(i);
  }

  SourceRange getReceiverSourceRange() const {
    assert(isObjCMessage());
    return Msg.getReceiverSourceRange();
  }

  /// \brief Check if the name corresponds to a CoreFoundation or CoreGraphics 
  /// function that allows objects to escape.
  ///
  /// Many methods allow a tracked object to escape.  For example:
  ///
  ///   CFMutableDictionaryRef x = CFDictionaryCreateMutable(..., customDeallocator);
  ///   CFDictionaryAddValue(y, key, x);
  ///
  /// We handle this and similar cases with the following heuristic.  If the
  /// function name contains "InsertValue", "SetValue", "AddValue",
  /// "AppendValue", or "SetAttribute", then we assume that arguments may
  /// escape.
  //
  // TODO: To reduce false negatives here, we should track the container
  // allocation site and check if a proper deallocator was set there.
  static bool isCFCGAllowingEscape(StringRef FName) {
    if (FName[0] == 'C' && (FName[1] == 'F' || FName[1] == 'G'))
           if (StrInStrNoCase(FName, "InsertValue") != StringRef::npos||
               StrInStrNoCase(FName, "AddValue") != StringRef::npos ||
               StrInStrNoCase(FName, "SetValue") != StringRef::npos ||
               StrInStrNoCase(FName, "WithData") != StringRef::npos ||
               StrInStrNoCase(FName, "AppendValue") != StringRef::npos||
               StrInStrNoCase(FName, "SetAttribute") != StringRef::npos) {
         return true;
       }
    return false;
  }
};

}
}

#endif
