/*******************************************************************\

Module: Symbolic Execution of ANSI-C

Author: Daniel Kroening, kroening@cs.cmu.edu

\*******************************************************************/

#include <assert.h>

#include <expr_util.h>
#include <c_misc.h>
#include <base_type.h>
#include <arith_tools.h>
#include <rename.h>
#include <i2string.h>
#include <array_name.h>
#include <config.h>
#include <cprover_prefix.h>
#include <pointer_offset_size.h>

#include <ansi-c/c_types.h>
#include <ansi-c/c_typecast.h>
#include <pointer-analysis/value_set.h>

#include "dereference.h"
#include "pointer_offset_sum.h"

// global data, horrible
unsigned int dereferencet::invalid_counter=0;

/*******************************************************************\

Function: dereferencet::has_dereference

  Inputs:

 Outputs:

 Purpose:

\*******************************************************************/

bool dereferencet::has_dereference(const exprt &expr) const
{
  forall_operands(it, expr)
    if(has_dereference(*it))
      return true;

  if(expr.id()=="dereference" ||
     expr.id()=="implicit_dereference" ||
     (expr.id()=="index" && expr.operands().size()==2 &&
      expr.op0().type().id()=="pointer"))
    return true;

  return false;
}

/*******************************************************************\

Function: dereferencet::get_points_to_set

  Inputs:

 Outputs:

 Purpose:

\*******************************************************************/

void dereferencet::get_points_to_set(
  const exprt &dest,
  const exprt &offset,
  expr_sett &points_to_set)
{
  if(dest.id()=="symbol" ||
     dest.id()=="member" ||
     dest.id()=="index" ||
     dest.id()=="dereference")
  {
    // we have *p with p symbol
    // p must be a pointer

    if(dest.type().id()!="pointer")
      throw "non-pointer is dereferenced: "+
            dest.to_string();

    expr_sett value_set;
    dereference_callback.get_value_set(dest, value_set);

    if(value_set.empty())
    {
      exprt points_to("unknown");
      points_to_set.insert(points_to);
    }
    else
    {
      for(expr_sett::const_iterator it=value_set.begin();
          it!=value_set.end(); it++)
      {
        if(it->is_nil())
        {
          exprt points_to("unknown");
          points_to_set.insert(points_to);
        }
        else if(it->id()=="invalid")
        {
          exprt tmp(*it);
          tmp.id("invalid-pointer");
          points_to_set.insert(tmp);
        }
        else
        {
          get_points_to_set(*it, offset, points_to_set);
        }
      }
    }
    
    return;
  }
  else if(dest.id()=="unknown")
  {
    points_to_set.insert(dest);
    return;
  }
  else if(dest.id()=="invalid")
  {
    points_to_set.insert(exprt("unknown"));
    return;
  }
  else if(dest.id()=="typecast")
  {
    if(dest.operands().size()!=1)
      throw "typecast must have one operand";

    if(dest.type().id()!="pointer")
      throw "typecast to pointer or reference type expected";

    const exprt &op=dest.op0();

    if(op.type().id()=="pointer")
    {
      // a real pointer typecast
      get_points_to_set(op, offset, points_to_set); // recursive call
    }
    else
    {
      // uh. converting an integer to a pointer, or something worse
      points_to_set.insert(exprt("unknown"));
    }

    return;
  }
  else if(dest.id()=="implicit_address_of")
  {
    if(dest.operands().size()!=1)
      throw "implicit_address_of must have one operand";

    if(dest.type().id()!="pointer")
      throw "implicit_address_of expected to have pointer type";

    const exprt &op=dest.op0();
    
    if(op.type().id()=="array")
    {
      if(!base_type_eq(op.type().subtype(), dest.type().subtype(), ns))
        throw "wrong array subtype for implicit_address_of";

      // p=a with a array: syntactic sugar for p=&a[0]

      exprt tmp("index");
      tmp.copy_to_operands(op, gen_zero(index_type()));
      tmp.type()=dest.type().subtype();
      tmp.set("#location", dest.find("#location"));

      get_reference_set(tmp, offset, points_to_set);
      return;
    }
    else if(op.type().id()=="code")
    {
      if(!base_type_eq(op.type(), dest.type().subtype(), ns))
        throw "wrong code subtype for implicit_address_of";

      // p=f with f function: syntactic sugar for p=&f
      get_reference_set(op, offset, points_to_set);
      return;
    }
    else
    {
      get_reference_set(dest.op0(), offset, points_to_set);
      return;
    }
  }
  else if(dest.id()=="address_of" ||
          dest.id()=="reference_to")
  {
    if(dest.operands().size()!=1)
      throw "address_of must have one operand";

    get_reference_set(dest.op0(), offset, points_to_set);
    return;
  }
  else if(dest.id()=="if")
  {
    if(dest.operands().size()!=3)
      throw "if must have three operands";

    get_points_to_set(dest.op1(), offset, points_to_set);
    get_points_to_set(dest.op2(), offset, points_to_set);

    return;
  }
  else if(dest.id()=="+" || dest.id()=="-") // pointer arithmetic
  {
    const exprt *pointer=NULL;

    if(dest.operands().size()==0)
      throw "expected at least one operand for "+dest.id_string();

    // must be sum of one pointer and one or more integers
    
    bool offset_unknown=false;

    if(dest.id()=="-")
    {
      if(dest.op0().type().id()!="pointer")
        throw "first operand of substraction must be the pointer";

      pointer=&dest.op0();
    }
    else // +
    {
      forall_operands(it, dest)
        if(it->type().id()=="pointer")
        {
          if(pointer!=NULL)
            throw "cannot add two pointers";

          pointer=&(*it);
        }
        else if(it->id()=="unknown")
          offset_unknown=true;

      if(pointer==NULL)
      {
        points_to_set.insert(exprt("unknown"));
        return;
      }
    }
    
    // check type: TODO

    exprt new_offset;

    // see if offset is unknown
    if(offset_unknown)
    {
      new_offset=exprt("unknown", index_type());
    }
    else
    {
      // add/substract integers to/from offset
      new_offset=exprt(dest.id(), index_type());

      if(!offset.is_zero() || dest.id()=="-")
        new_offset.copy_to_operands(offset);

      forall_operands(it, dest)
        if(it->type().id()!="pointer" && !it->is_zero())
          new_offset.copy_to_operands(*it);

      if(new_offset.operands().size()==0)
        new_offset=offset;
      else if(new_offset.operands().size()==1)
      {
        exprt tmp;
        tmp.swap(new_offset.op0());
        new_offset.swap(tmp);
      }
      else
        Forall_operands(it, new_offset)
          if(it->type()!=index_type())
            it->make_typecast(index_type());
    }

    get_points_to_set(*pointer, new_offset, points_to_set);
    return;
  }
  else if(dest.id()=="constant")
  {   
    exprt points_to;

    if(dest.id()=="constant" && 
       dest.get("value")=="NULL")
    {
      points_to.id("NULL-pointer");
    }
    else
    {
      points_to.id("invalid-pointer");
      points_to.copy_to_operands(dest);
    }

    points_to_set.insert(points_to);
    return;
  }

  throw "unexpected dereference: "+dest.to_string();
}

/*******************************************************************\

Function: dereferencet::dereference

  Inputs:

 Outputs:

 Purpose:

\*******************************************************************/

void dereferencet::dereference(
  exprt &dest,
  const guardt &guard)
{
  if(dest.type().id()!="pointer")
    throw "dereference expected pointer type, but got "+
          dest.type().pretty();

  // save the dest for later, dest might be destroyed
  const exprt deref_expr(dest);
  
  // type of the object
  const typet &type=deref_expr.type().subtype();

  #if 0
  std::cout << "DEREF: " << dest.pretty() << std::endl;
  #endif

  // collect objects dest may point to
  expr_sett points_to_set;
  get_points_to_set(dest, gen_zero(index_type()), points_to_set);

  // now build big case split
  // only "good" objects

  exprt value;
  value.make_nil();

  for(expr_sett::const_iterator
      it=points_to_set.begin();
      it!=points_to_set.end();
      it++)
  {
    exprt new_value, pointer_guard;

    build_reference_to(*it, dest, type, gen_zero(index_type()),
                       new_value, pointer_guard, guard);

    if(new_value.is_not_nil())
    {
      if(value.is_nil())
        value.swap(new_value);
      else
      {
        exprt tmp("if", type);
        tmp.reserve_operands(3);
        tmp.copy_to_operands(pointer_guard);
        tmp.move_to_operands(new_value);
        tmp.move_to_operands(value);
        value.swap(tmp);
      }
    }
  }

  if(value.is_nil())
  {
    // first see if we have a "failed object" for this pointer
    
    const symbolt *failed_symbol;

    if(dereference_callback.has_failed_symbol(deref_expr, failed_symbol))
    {
      // yes!
      value=symbol_expr(*failed_symbol);
    }
    else
    {
      // else, do new symbol

      symbolt symbol;
      symbol.name="symex::invalid_object"+i2string(invalid_counter++);
      symbol.base_name="invalid_object";
      symbol.type=type;

      // make it a lvalue, so we can assign to it
      symbol.lvalue=true;
      
      get_new_name(symbol, ns);

      value=symbol_expr(symbol);
      
      new_context.move(symbol);
    }
    
    value.set("#invalid_object", true);
  }

  dest.swap(value);
}

/*******************************************************************\

Function: dereferencet::add_checks

  Inputs:

 Outputs:

 Purpose:

\*******************************************************************/

void dereferencet::add_checks(
  const exprt &dest,
  const guardt &guard)
{
  if(dest.type().id()!="pointer")
    throw "dereference expected pointer type, but got "+
          dest.type().pretty();

  #if 0
  std::cout << "ADD CHECK: " << dest.pretty() << std::endl;
  #endif

  const typet &type=dest.type().subtype();

  // collect objects dest may point to
  expr_sett points_to_set;
  exprt offset(gen_zero(index_type()));

  get_points_to_set(dest, offset, points_to_set);

  for(expr_sett::const_iterator
      it=points_to_set.begin();
      it!=points_to_set.end();
      it++)
  {
    exprt new_value, pointer_guard;

    build_reference_to(*it, dest, type, offset,
                       new_value, pointer_guard, guard);
  }
}

/*******************************************************************\

Function: dereferencet::get_symbol

  Inputs:

 Outputs:

 Purpose:

\*******************************************************************/

const exprt &dereferencet::get_symbol(const exprt &object) const
{
  if(object.id()=="symbol")
    return object;

  if(object.id()=="index")
  {
    if(object.operands().size()!=2)
      throw "index expected to have two operands";

    return get_symbol(object.op0());
  }

  if(object.id()=="member")
  {
    if(object.operands().size()!=1)
      throw "member expected to have one operand";

    return get_symbol(object.op0());
  }

  if(object.id()=="constant" ||
     object.id()=="string-constant")
    return (const exprt &)nil_rep;
 
  if(object.id()=="dynamic_object")
    return (const exprt &)nil_rep;

  throw "unexpected expression in get_symbol: "+
    object.id_string();
}

/*******************************************************************\

Function: dereferencet::dereference_type_compare

  Inputs:

 Outputs:

 Purpose:

\*******************************************************************/

bool dereferencet::dereference_type_compare(
  exprt &object,
  const typet &dereference_type) const
{
  const typet &object_type=object.type();

  if(dereference_type.id()=="empty")
    return true; // always ok

  if(base_type_eq(object_type, dereference_type, ns))
    return true; // ok, they just match

  // check for struct prefixes

  typet ot_base(object_type),
        dt_base(dereference_type);

  base_type(ot_base, ns);
  base_type(dt_base, ns);

  if(ot_base.id()=="struct" &&
     dt_base.id()=="struct")
  {
    if(to_struct_type(dt_base).is_prefix_of(
       to_struct_type(ot_base)))
    {
      object.make_typecast(dereference_type);
      return true; // ok, dt is a prefix of ot
    }
  }

  // really different

  return false;
}

/*******************************************************************\

Function: dereferencet::build_reference_to

  Inputs:

 Outputs:

 Purpose:

\*******************************************************************/

void dereferencet::build_reference_to(
  const exprt &what,
  const exprt &deref_expr,
  const typet &type,
  const exprt &offset,
  exprt &value,
  exprt &pointer_guard,
  const guardt &guard)
{
  value.make_nil();
  pointer_guard.make_false();

  if(what.id()=="points-to-symbol")
  {
    const exprt &object=what.op0();

    exprt new_offset=pointer_offset_sum(offset, what.op1());

    value=object;

    exprt object_pointer("address_of", typet("pointer"));
    object_pointer.type().subtype()=value.type();
    object_pointer.copy_to_operands(value);

    pointer_guard=exprt("=", typet("bool"));
    pointer_guard.reserve_operands(2);
    pointer_guard.copy_to_operands(deref_expr);
    pointer_guard.move_to_operands(object_pointer);

    if(pointer_guard.op0().type()!=
       pointer_guard.op1().type())
      pointer_guard.op1().make_typecast(pointer_guard.op0().type());

    guardt tmp_guard(guard);
    tmp_guard.add(pointer_guard);
    
    valid_check(object, tmp_guard);

    if(!dereference_type_compare(value, type))
    {
      if(memory_model(value, type, tmp_guard, new_offset))
      {
        // ok
      }
      else
      {
        if(options.get_bool_option("pointer-check"))
          dereference_callback.dereference_failure(
            "pointer dereference",
            "wrong object type", tmp_guard);

        value.make_nil();
        return; // give up, no way that this is ok
      }
    }
    else if(!new_offset.is_zero())
    {
      if(options.get_bool_option("pointer-check"))
      {
        exprt object_offset=exprt("pointer_offset", index_type());
        object_offset.copy_to_operands(deref_expr);

        exprt offset_not_zero("=", typet("bool"));
        offset_not_zero.copy_to_operands(object_offset);
        offset_not_zero.copy_to_operands(gen_zero(object_offset.type()));
        offset_not_zero.make_not();
        
        guardt tmp_guard2(guard);
        tmp_guard2.move(offset_not_zero);
        
        dereference_callback.dereference_failure(
          "pointer dereference",
          "offset not zero", tmp_guard2);
      }
    }
  }
  else if(what.id()=="points-to-array")
  {
    const exprt &object=what.op0();
    exprt object_offset=what.op1();
    
    // do we have a specific or unknown offset?
    bool unknown_offset=object_offset.id()=="unknown";

    if(unknown_offset)
    {
      mp_integer extra_offset=compute_pointer_offset(ns, object);

      exprt pointer_offset_expr("pointer_offset", index_type());
      pointer_offset_expr.copy_to_operands(deref_expr);
      
      if(extra_offset==0)
        object_offset.swap(pointer_offset_expr);
      else
      {
        object_offset=exprt("-", index_type());
        object_offset.copy_to_operands(
          pointer_offset_expr,
          from_integer(extra_offset, index_type()));
      }
    }

    exprt new_offset=pointer_offset_sum(object_offset, offset);

    if(object.type().id()!="array")
      throw "points-to-array expected to have "
            "object with array type, "
            "but got "+what.type().id_string();

    value=exprt("index", object.type().subtype());
    value.copy_to_operands(object, new_offset);

    if(unknown_offset)
    {
      exprt object_pointer("implicit_address_of", typet("pointer"));
      object_pointer.type().subtype()=value.type();
      object_pointer.copy_to_operands(object);

      pointer_guard=exprt("same-object", typet("bool"));
      pointer_guard.reserve_operands(2);
      pointer_guard.copy_to_operands(deref_expr);
      pointer_guard.move_to_operands(object_pointer);
    }
    else
    {
      exprt object_with_offset(value.id(), value.type());
      object_with_offset.copy_to_operands(object, object_offset);

      exprt object_pointer("address_of", typet("pointer"));
      object_pointer.type().subtype()=object_with_offset.type();
      object_pointer.copy_to_operands(object_with_offset);

      pointer_guard=exprt("=", typet("bool"));
      pointer_guard.reserve_operands(2);
      pointer_guard.copy_to_operands(deref_expr);
      pointer_guard.move_to_operands(object_pointer);

      if(pointer_guard.op0().type()!=
         pointer_guard.op1().type())
        pointer_guard.op0().make_typecast(
          pointer_guard.op1().type());
    }

    guardt tmp_guard(guard);
    tmp_guard.add(pointer_guard);

    valid_check(object, tmp_guard);

    if(!dereference_type_compare(value, type))
    {
      if(memory_model(value, type, tmp_guard, new_offset))
      {
        // ok
      }
      else
      {
        if(options.get_bool_option("pointer-check"))
          dereference_callback.dereference_failure(
            "pointer dereference",
            "wrong object type", tmp_guard);

        value.make_nil();
        return; // give up, no way that this is ok
      }
    }
    else
      bounds_check(value, tmp_guard);
  }
  else if(what.id()=="NULL-pointer")
  {
    if(options.get_bool_option("pointer-check"))
    {
      exprt pointer("constant", typet("pointer"));
      pointer.type().subtype()=type;
      pointer.set("value", "NULL");

      exprt pointer_guard("same-object", typet("bool"));
      pointer_guard.reserve_operands(2);
      pointer_guard.copy_to_operands(deref_expr);
      pointer_guard.move_to_operands(pointer);

      guardt tmp_guard(guard);
      tmp_guard.add(pointer_guard);

      dereference_callback.dereference_failure(
        "pointer dereference",
        "NULL pointer", tmp_guard);
    }
  }
  else if(what.id()=="dynamic_object")
  {
    if(options.get_bool_option("pointer-check"))
    {
      // constraint that it actually is a dynamic object

      exprt is_dynamic_object_expr("is_dynamic_object", typet("bool"));
      is_dynamic_object_expr.copy_to_operands(deref_expr);
      
      {
        // check if it is still alive

        exprt valid_expr("valid_object", typet("bool"));
        valid_expr.copy_to_operands(deref_expr);
        valid_expr.make_not();
        
        guardt tmp_guard(guard);
        tmp_guard.add(is_dynamic_object_expr);
        tmp_guard.move(valid_expr);
        dereference_callback.dereference_failure(
          "pointer dereference",
          "invalidated dynamic object", 
          tmp_guard);
      }

      if(options.get_bool_option("bounds-check"))
      {
        // check lower bound
        exprt zero=gen_zero(index_type());
        assert(zero.is_not_nil());

        exprt object_offset=exprt("pointer_offset", index_type());
        object_offset.copy_to_operands(deref_expr);

        exprt inequality("<", typet("bool"));
        inequality.move_to_operands(object_offset, zero);

        guardt tmp_guard(guard);
        tmp_guard.add(is_dynamic_object_expr);
        tmp_guard.move(inequality);
        dereference_callback.dereference_failure(
          "pointer dereference",
          "dynamic object lower bound", tmp_guard);
      }

      if(options.get_bool_option("bounds-check"))
      {
        // check upper bound
        exprt size_expr=exprt("dynamic_size", uint_type());
        size_expr.copy_to_operands(deref_expr);

        exprt object_offset=exprt("pointer_offset", index_type());
        object_offset.copy_to_operands(deref_expr);
        object_offset.make_typecast(uint_type());

        exprt inequality("<=", typet("bool"));
        inequality.move_to_operands(size_expr, object_offset);

        guardt tmp_guard(guard);
        tmp_guard.add(is_dynamic_object_expr);
        tmp_guard.move(inequality);
        dereference_callback.dereference_failure(
          "pointer dereference",
          "dynamic object upper bound", tmp_guard);
      }
      
      // check type
      
      exprt object(what);

      if(!dereference_type_compare(object, type))
      {
        exprt type_expr=exprt("pointer_object_has_type", typet("bool"));
        type_expr.copy_to_operands(deref_expr);
        type_expr.set("object_type", type);
        type_expr.make_not();
      
        guardt tmp_guard(guard);
        tmp_guard.add(is_dynamic_object_expr);
        tmp_guard.move(type_expr);

        dereference_callback.dereference_failure(
          "pointer dereference",
          "wrong object type", tmp_guard);
      }
      
      value=exprt("dereference", type);
      value.copy_to_operands(deref_expr);
    }
  }
  else if(what.id()=="unknown" ||
          what.id()=="invalid-pointer")
  {
    if(options.get_bool_option("pointer-check"))
    {
      // constraint that it actually is an invalid pointer

      exprt invalid_pointer_expr("invalid-pointer", typet("bool"));
      invalid_pointer_expr.copy_to_operands(deref_expr);
      
      // produce new guard
      
      guardt tmp_guard(guard);
      tmp_guard.move(invalid_pointer_expr);
      dereference_callback.dereference_failure(
        "pointer dereference",
        "invalid pointer", 
        tmp_guard);
    }
  }
  else
    throw "unknown points-to: "+what.id_string();
}

/*******************************************************************\

Function: dereferencet::get_reference_set

  Inputs:

 Outputs:

 Purpose:

\*******************************************************************/

void dereferencet::get_reference_set(
  const exprt &dest,
  const exprt &offset,
  expr_sett &points_to_set)
{
  if(dest.id()=="index")
  {
    if(dest.operands().size()!=2)
      throw "index expected to have two operands";
      
    // adjust offset
    
    exprt new_offset=pointer_offset_sum(offset, dest.op1());

    // get what dest.op0() could be
    
    expr_sett tmp_set;
    get_reference_set(dest.op0(), gen_zero(index_type()), tmp_set);
    
    for(expr_sett::const_iterator it=tmp_set.begin();
        it!=tmp_set.end(); it++)
    {
      if(it->id()=="points-to-symbol")
      {
        assert(it->operands().size()==2);
        assert(it->op0().type().id()=="array");
        exprt points_to(*it);
        points_to.id("points-to-array");
        points_to.op1()=pointer_offset_sum(points_to.op1(), new_offset);
        points_to_set.insert(points_to);
      }
      else if(it->id()=="points-to-array")
      {
        assert(it->operands().size()==2);
        exprt points_to("points-to-array");
        assert(it->op0().type().id()=="array");
        assert(it->op0().type().subtype().id()=="array");
        exprt index_expr("index", it->op0().type().subtype());
        index_expr.copy_to_operands(it->op0(), it->op1());
        points_to.copy_to_operands(index_expr, new_offset);
        points_to_set.insert(points_to);
      }
      else if(it->id()=="dynamic_object")
      {
        points_to_set.insert(*it); // TODO, clean up, index is lost
      }
      else if(it->id()=="unknown" ||
              it->is_nil())
      {
        points_to_set.insert(*it);
      }
      else
        throw "unexpected reference: "+it->id_string();
    }
  }
  else if(dest.id()=="member")
  {
    if(dest.operands().size()!=1)
      throw "member expected to have one operand";

    // get what dest.op0() could be
    
    expr_sett tmp_set;
    get_reference_set(dest.op0(), gen_zero(index_type()), tmp_set);
    
    for(expr_sett::const_iterator it=tmp_set.begin();
        it!=tmp_set.end(); it++)
    {
      if(it->id()=="points-to-symbol")
      {
        assert(it->operands().size()==2);
        exprt member_expr(dest);
        member_expr.op0()=it->op0();
        exprt points_to("points-to-symbol");
        points_to.copy_to_operands(member_expr, offset);
        points_to_set.insert(points_to);
      }
      else if(it->id()=="points-to-array")
      {
        assert(it->operands().size()==2);
        
        assert(it->op0().type().id()=="array");
        exprt index_expr("index", it->op0().type().subtype());
        index_expr.copy_to_operands(it->op0(), it->op1());

        exprt member_expr(dest);
        member_expr.op0().swap(index_expr);
        exprt points_to("points-to-symbol");
        points_to.copy_to_operands(member_expr, offset);
        points_to_set.insert(points_to);
      }
      else if(it->id()=="dynamic_object")
      {
        points_to_set.insert(*it); // TODO, clean up, member is lost
      }
      else if(it->id()=="unknown" ||
              it->is_nil())
      {
        points_to_set.insert(*it);
      }
      else
        throw "unexpected reference: "+it->id_string();
    }
  }
  else if(dest.id()=="dereference" ||
          dest.id()=="implicit_dereference")
  {
    if(dest.operands().size()!=1)
      throw dest.id_string()+" expected to have one operand";

    // get what dest.op0() could be
    
    expr_sett tmp_set;
    get_points_to_set(dest.op0(), offset, tmp_set);
    
    for(expr_sett::const_iterator it=tmp_set.begin();
        it!=tmp_set.end(); it++)
      points_to_set.insert(*it);
  }
  else if(dest.id()=="dynamic_object")
  {
    points_to_set.insert(dest);
  }
  else if(dest.id()=="if")
  {
    assert(dest.operands().size()==3);
    
    expr_sett tmp_set1, tmp_set2;

    get_points_to_set(dest.op1(), offset, tmp_set1);
    get_points_to_set(dest.op2(), offset, tmp_set2);
    
    points_to_set.insert(tmp_set1.begin(), tmp_set1.end());
    points_to_set.insert(tmp_set2.begin(), tmp_set2.end());
  }
  else if(dest.id()=="symbol" ||
          dest.id()=="string-constant")
  {
    exprt points_to("points-to-symbol");
    points_to.copy_to_operands(dest, offset);
    points_to_set.insert(points_to);
  }
  else
  {
    std::cout << dest.pretty() << std::endl;
    throw "unexpected reference to: "+dest.id_string();
  }
}

/*******************************************************************\

Function: dereferencet::valid_check

  Inputs:

 Outputs:

 Purpose:

\*******************************************************************/

void dereferencet::valid_check(
  const exprt &object,
  const guardt &guard)
{
  if(!options.get_bool_option("pointer-check"))
    return;

  bool is_dynamic=
    object.type().get_bool("#dynamic");

  if(!is_dynamic)
  {
    if(object.id()=="string-constant")
    {
      // always valid
      return;
    }
    else
    {   
      const exprt &symbol=get_symbol(object);
    
      if(symbol.is_nil() ||
         symbol.get_bool("#invalid_object"))
      { 
        // always "valid"
        return;
      }
      else
      {
        const irep_idt identifier=symbol.get("identifier");
    
        if(dereference_callback.is_valid_object(identifier))
          return; // always ok
      }
    }
  }
      
  // build &object
  exprt pointer_expr;
  
  if(object.type().id()=="array")
  {
    pointer_expr=exprt("implicit_address_of", typet("pointer"));
    pointer_expr.type().subtype()=object.type().subtype();
  }
  else
  {
    pointer_expr=exprt("address_of", typet("pointer"));
    pointer_expr.type().subtype()=object.type();
  }
  
  pointer_expr.copy_to_operands(object);
  
  // build !valid(&object)
    
  exprt valid_expr("valid_object", typet("bool"));
  valid_expr.move_to_operands(pointer_expr);
  valid_expr.make_not();
  
  // require that to be true
        
  const std::string error_msg=
    is_dynamic?
      "dynamic object invalidated":
      "temporary object invalidated";
  
  guardt tmp_guard(guard);
  tmp_guard.move(valid_expr);
  dereference_callback.dereference_failure(
    "pointer dereference",
    error_msg, tmp_guard);
}

/*******************************************************************\

Function: dereferencet::bounds_check

  Inputs:

 Outputs:

 Purpose:

\*******************************************************************/

void dereferencet::bounds_check(
  const exprt &expr,
  const guardt &guard)
{
  if(!options.get_bool_option("bounds-check"))
    return;

  if(expr.operands().size()!=2)
    throw "index takes two operands";

  const typet &array_type=expr.op0().type();

  if(array_type.id()!="array")
    throw "bounds check expected array type";

  std::string name=array_name(ns, expr.op0());

  {
    exprt zero=gen_zero(expr.op1().type());

    if(zero.is_nil())
      throw "no zero constant of index type "+
        expr.op1().type().to_string();

    exprt inequality("<", typet("bool"));
    inequality.copy_to_operands(expr.op1());
    inequality.move_to_operands(zero);

    guardt tmp_guard(guard);
    tmp_guard.move(inequality);
    dereference_callback.dereference_failure(
      "array bounds",
      name+" lower bound", tmp_guard);
  }

  const exprt &size_expr=
    (const exprt &)array_type.find("size");

  if(size_expr.id()!="infinity")
  {
    if(array_type.find("size").is_nil())
      throw "index array operand of wrong type";

    exprt inequality(">=", typet("bool"));
    inequality.copy_to_operands(expr.op1(), size_expr);

    if(c_implicit_typecast(
      inequality.op0(),
      inequality.op1().type(),
      ns))
      throw "index address of wrong type";

    guardt tmp_guard(guard);
    tmp_guard.move(inequality);
    dereference_callback.dereference_failure(
      "array bounds",
      name+" upper bound", tmp_guard);
  }
}

/*******************************************************************\

Function: dereferencet::memory_model

  Inputs:

 Outputs:

 Purpose:

\*******************************************************************/

bool dereferencet::memory_model(
  exprt &value,
  const typet &type,
  const guardt &guard,
  exprt &new_offset)
{
  // we are interested in casts to 8-bit bv
  
  if(type.id()=="unsignedbv" ||
     type.id()=="signedbv" ||
     type.id()=="bv")
  {
    if(type.get("width")=="8")
    {
      // from what are we coming?
      if(value.type().id()=="unsignedbv" ||
         value.type().id()=="signedbv" ||
         value.type().id()=="bv")
      {
        unsigned object_width=atoi(value.type().get("width").c_str());

        if(object_width==8)
        {
          // only doing type conversion
          if(!new_offset.is_zero())
          {
            exprt offset_is_zero("=", typet("bool"));
            offset_is_zero.copy_to_operands(new_offset);
            offset_is_zero.copy_to_operands(gen_zero(new_offset.type()));

            guardt tmp_guard(guard);
            tmp_guard.move(offset_is_zero);
            dereference_callback.dereference_failure(
              "pointer dereference",
              "offset not zero", tmp_guard);
          }
          
          // do the typecast
          value.make_typecast(type);
          
          return true;
        }
      
        if(config.ansi_c.endianess!=configt::ansi_ct::NO_ENDIANESS)
        {
          if(object_width>8)
          {
            const char *byte_extract_id=NULL;
            
            switch(config.ansi_c.endianess)
            {
            case configt::ansi_ct::IS_LITTLE_ENDIAN:
              byte_extract_id="byte_extract_little_endian";
              break;

            case configt::ansi_ct::IS_BIG_ENDIAN:
              byte_extract_id="byte_extract_big_endian";
              break;
              
            default:
              assert(false);
            }
          
            exprt byte_extract(byte_extract_id, type);
            byte_extract.reserve_operands(2);
            byte_extract.move_to_operands(value);
            byte_extract.copy_to_operands(new_offset);
            value.swap(byte_extract);
            
            if(!new_offset.is_zero())
            {
              {
                exprt bound=from_integer(object_width/8, new_offset.type());

                exprt offset_upper_bound("<", typet("bool"));
                offset_upper_bound.reserve_operands(2);
                offset_upper_bound.copy_to_operands(new_offset);
                offset_upper_bound.move_to_operands(bound);
                
                guardt tmp_guard(guard);
                tmp_guard.move(offset_upper_bound);
                dereference_callback.dereference_failure(
                  "array bounds",
                  "offset upper bound", tmp_guard);
              }

              {
                exprt offset_lower_bound(">=", typet("bool"));
                offset_lower_bound.reserve_operands(2);
                offset_lower_bound.copy_to_operands(new_offset);
                offset_lower_bound.copy_to_operands(gen_zero(new_offset.type()));

                guardt tmp_guard(guard);
                tmp_guard.move(offset_lower_bound);                
                dereference_callback.dereference_failure(
                  "array bounds",
                  "offset lower bound", tmp_guard);
              }
            }
            
            return true;
          }
        }
      }
    }
  }

  return false;
}

/*******************************************************************\

Function: dereference

  Inputs:

 Outputs:

 Purpose:

\*******************************************************************/

class trivial_dereference_callbackt:public dereference_callbackt
{
public:
  virtual bool is_valid_object(const irep_idt &identifier)
  {
    return true;
  }

  virtual void dereference_failure(
    const std::string &property,
    const std::string &msg,
    const guardt &guard)
  {
    // ignore
  }

  virtual void get_value_set(const exprt &expr, expr_sett &value_set)
  {
    v.get_value_set(expr, value_set, ns);
  }
  
  const value_sett &v;
  const namespacet &ns;
  
  trivial_dereference_callbackt(
    const value_sett &_v,
    const namespacet &_ns):
    v(_v),
    ns(_ns)
  {
  }

  virtual bool has_failed_symbol(
    const exprt &expr,
    const symbolt *&symbol)
  {
    return false;
  }
};

void dereference(
  const namespacet &ns,
  const class value_sett &value_set,
  exprt &dest)
{
  trivial_dereference_callbackt trivial_dereference_callback(value_set, ns);
  optionst options;
  contextt context;
  guardt guard;
  dereferencet dereference(ns, context, options, trivial_dereference_callback);
  dereference.dereference(dest, guard);
}

