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

Module: Symbolic Execution

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

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

#include <assert.h>

#include <base_type.h>
#include <simplify_expr.h>
#include <i2string.h>
#include <cprover_prefix.h>
#include <expr_util.h>

#include <ansi-c/c_types.h>

#include "basic_symex.h"
#include "dynamic_allocation.h"

unsigned basic_symext::nondet_count=0;
unsigned basic_symext::dynamic_counter=0;

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

Function: basic_symext::assignment

  Inputs:

 Outputs:

 Purpose:

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

void basic_symext::assignment(
  statet &state,
  const exprt &lhs,
  exprt &rhs,
  bool init)
{
  exprt original_lhs(lhs);
  state.get_original_name(original_lhs);
  
  exprt new_lhs(lhs);
  replace_dynamic_allocation(state, rhs);

  state.assignment(new_lhs, rhs, ns, init, constant_propagation);
  
  target.assignment(
    state.guard,
    new_lhs, original_lhs,
    rhs,
    state.source,
    init?symex_targett::INIT:
    symex_targett::STATE);
}

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

Function: basic_symext::do_simplify

  Inputs:

 Outputs:

 Purpose:

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

void basic_symext::do_simplify(exprt &expr)
{
  if(options.get_bool_option("simplify"))
  {
    base_type(expr, ns);
    simplify(expr);
  }
}

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

Function: basic_symext::symex

  Inputs:

 Outputs:

 Purpose:

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

void basic_symext::symex(statet &state, const codet &code)
{
  const irep_idt &statement=code.get("statement");
  
  if(statement=="block")
    symex_block(state, code);
  else if(statement=="assign")
    symex_assign(state, code, false);
  else if(statement=="init")
    symex_assign(state, code, true);
  else if(statement=="decl")
  {
    // behaves like non-deterministic assignment
    // unless there is an initializer
    if(code.operands().size()==2)
      symex_assign(state, code, true);
    else if(code.operands().size()==1)
    {
      exprt rhs("nondet_symbol", code.op0().type());
      rhs.set("identifier", "symex::"+i2string(nondet_count++));
      rhs.location()=code.location();
  
      exprt new_lhs(code.op0());
      read(new_lhs);

      guardt guard; // NOT the state guard!
      symex_assign_rec(state, new_lhs, rhs, guard, false);      
    }
    else
      assert(false);
  }
  else if(statement=="expression")
  {
    // ignore
  }
  else if(statement=="cpp_delete" ||
          statement=="cpp_delete[]")
    symex_cpp_delete(state, code);
  else if(statement=="nondet")
  {
    // like skip
  }
  else if(statement=="printf")
    symex_printf(state, (const exprt &)nil_rep, code);
  else
  {
    std::cerr << code.pretty() << std::endl;
    throw "unexpected statement: "+id2string(statement);
  }
}

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

Function: basic_symext::symex_block

  Inputs:

 Outputs:

 Purpose:

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

void basic_symext::symex_block(statet &state, const codet &code)
{
  forall_operands(it, code)
    symex(state, to_code(*it));
}

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

Function: basic_symext::symex_assign

  Inputs:

 Outputs:

 Purpose:

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

void basic_symext::symex_assign(
  statet &state,
  const codet &code,
  bool init)
{
  if(code.operands().size()!=2)
    throw "assignment expects two operands";
    
  exprt lhs=code.op0();
  exprt rhs=code.op1();

  read(lhs);
  read(rhs);
  
  replace_dynamic_allocation(state, lhs);
  replace_dynamic_allocation(state, rhs);
  
  if(rhs.id()=="sideeffect")
  {
    const irep_idt &statement=rhs.get("statement");
    
    if(statement=="functioncall")
    {
      assert(rhs.operands().size()!=0);
    
      if(rhs.op0().id()!="symbol")
        throw "symex_assign: expected symbol as function";

      const irep_idt &identifier=rhs.op0().get("identifier");
      
      throw "symex_assign: unexpected function call: "+id2string(identifier);
    }
    else if(statement=="cpp_new" ||
            statement=="cpp_new[]")
      symex_cpp_new(state, lhs, rhs);
    else if(statement=="malloc")
      symex_malloc(state, lhs, rhs);
    else if(statement=="nondet")
      symex_nondet(state, lhs, rhs);
    else if(statement=="printf")
      symex_printf(state, lhs, rhs);
    else
      throw "symex_assign: unexpected sideeffect: "+id2string(statement);
  }
  else
  {
    guardt guard; // NOT the state guard!
    symex_assign_rec(state, lhs, rhs, guard, init);
  }
}

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

Function: basic_symext::read

  Inputs:

 Outputs:

 Purpose:

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

void basic_symext::read(exprt &expr)
{
}

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

Function: basic_symext::symex_assign_rec

  Inputs:

 Outputs:

 Purpose:

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

void basic_symext::symex_assign_rec(
  statet &state,
  const exprt &lhs,
  exprt &rhs,
  guardt &guard,
  bool init)
{
  if(lhs.id()=="symbol")
    symex_assign_symbol(state, lhs, rhs, guard, init);
  else if(lhs.id()=="index")
    symex_assign_array(state, lhs, rhs, guard, init);
  else if(lhs.id()=="member")
    symex_assign_member(state, lhs, rhs, guard, init);
  else if(lhs.id()=="if")
    symex_assign_if(state, lhs, rhs, guard, init);
  else if(lhs.id()=="typecast")
    symex_assign_typecast(state, lhs, rhs, guard, init);
  else
    throw "assignment to "+lhs.id_string()+" not handled";
}

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

Function: basic_symext::symex_assign_symbol

  Inputs:

 Outputs:

 Purpose:

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

void basic_symext::symex_assign_symbol(
  statet &state,
  const exprt &lhs,
  exprt &rhs,
  guardt &guard,
  bool init)
{
  // put assignment guard in rhs
  if(!guard.empty())
  {
    exprt new_rhs("if", rhs.type());
    new_rhs.operands().resize(3);
    guard.expr(new_rhs.op0());
    new_rhs.op1().swap(rhs);
    new_rhs.op2()=lhs;
    new_rhs.swap(rhs);
  }
  
  exprt original_lhs(lhs);
  state.get_original_name(original_lhs);
  
  state.rename(rhs);
  do_simplify(rhs);

  exprt new_lhs(lhs);
  state.assignment(new_lhs, rhs, ns, init, constant_propagation);
  
  guardt tmp_guard(state.guard);
  tmp_guard.append(guard);
  
  // do the assignment
  target.assignment(
    tmp_guard,
    new_lhs, original_lhs,
    rhs, 
    state.source,
    init?symex_targett::INIT:symex_targett::STATE);
}

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

Function: basic_symext::symex_assign_typecast

  Inputs:

 Outputs:

 Purpose:

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

void basic_symext::symex_assign_typecast(
  statet &state,
  const exprt &lhs,
  exprt &rhs,
  guardt &guard,
  bool init)
{
  // these may come from dereferencing on the lhs
  
  assert(lhs.operands().size()==1);
  
  exprt rhs_typecasted(rhs);
  
  rhs_typecasted.make_typecast(lhs.op0().type());
  
  symex_assign_rec(state, lhs.op0(), rhs_typecasted, guard, init);
}

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

Function: basic_symext::symex_assign_array

  Inputs:

 Outputs:

 Purpose:

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

void basic_symext::symex_assign_array(
  statet &state,
  const exprt &lhs,
  exprt &rhs,
  guardt &guard,
  bool init)
{
  // lhs must be index operand
  // that takes two operands: the first must be an array
  // the second is the index

  if(lhs.operands().size()!=2)
    throw "index must have two operands";

  const exprt &lhs_array=lhs.op0();
  const exprt &lhs_index=lhs.op1();

  if(lhs_array.type().id()!="array")
    throw "index must take array type operand";

  // turn
  //   a[i]=e
  // into
  //   a'==a WITH [i:=e]

  exprt new_rhs("with", lhs_array.type());

  new_rhs.reserve_operands(3);
  new_rhs.copy_to_operands(lhs_array);
  new_rhs.copy_to_operands(lhs_index);
  new_rhs.move_to_operands(rhs);

  symex_assign_rec(state, lhs_array, new_rhs, guard, init);
}

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

Function: basic_symext::symex_assign_member

  Inputs:

 Outputs:

 Purpose:

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

void basic_symext::symex_assign_member(
  statet &state,
  const exprt &lhs,
  exprt &rhs,
  guardt &guard,
  bool init)
{
  // symbolic execution of a struct member assignment

  // lhs must be member operand
  // that takes one operands, which must be a structure

  if(lhs.operands().size()!=1)
    throw "member must have one operand";

  exprt lhs_struct=lhs.op0();
  typet struct_type=ns.follow(lhs_struct.type());

  if(struct_type.id()!="struct" &&
     struct_type.id()!="union")
    throw "member must take struct/union type operand but got "
          +struct_type.pretty();

  const irep_idt &component_name=lhs.get("component_name");

  // typecasts involved? C++ does that for inheritance.
  if(lhs_struct.id()=="typecast")
  {
    assert(lhs_struct.operands().size()==1);

    // remove the type cast, we assume that the member is there
    exprt tmp(lhs_struct.op0());
    struct_type=ns.follow(tmp.type());
    assert(struct_type.id()=="struct" || struct_type.id()=="union");
    lhs_struct=tmp;
  }

  // turn
  //   a.c=e
  // into
  //   a'==a WITH [c:=e]

  exprt new_rhs("with", struct_type);

  new_rhs.reserve_operands(3);
  new_rhs.copy_to_operands(lhs_struct);
  new_rhs.copy_to_operands(exprt("member_name"));
  new_rhs.move_to_operands(rhs);

  new_rhs.op1().set("component_name", component_name);

  symex_assign_rec(state, lhs_struct, new_rhs, guard, init);
}

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

Function: basic_symext::symex_assign_if

  Inputs:

 Outputs:

 Purpose:

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

void basic_symext::symex_assign_if(
  statet &state,
  const exprt &lhs,
  exprt &rhs,
  guardt &guard,
  bool init)
{
  // we have (c?a:b)=e;

  if(lhs.operands().size()!=3)
    throw "if must have three operands";

  unsigned old_guard_size=guard.size();
  
  // need to copy rhs -- it gets destroyed
  exprt rhs_copy(rhs);
  
  exprt condition(lhs.op0());

  guard.add(condition);
  symex_assign_rec(state, lhs.op1(), rhs, guard, init);
  guard.resize(old_guard_size);
    
  condition.make_not();
  
  guard.add(condition);
  symex_assign_rec(state, lhs.op2(), rhs_copy, guard, init);
  guard.resize(old_guard_size);
}

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

Function: basic_symext::replace_dynamic_allocation

  Inputs:

 Outputs:

 Purpose:

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

void basic_symext::replace_dynamic_allocation(
  const statet &state,
  exprt &expr)
{
  ::replace_dynamic_allocation(ns, expr);
}

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

Function: basic_symex

  Inputs:

 Outputs:

 Purpose:

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

void basic_symex(
  const codet &code,
  const namespacet &ns,
  symex_targett &target,
  goto_symex_statet &state)
{
  contextt new_context;
  basic_symext basic_symex(ns, new_context, target);
  basic_symex.symex(state, code);
}

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

Function: basic_symex

  Inputs:

 Outputs:

 Purpose:

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

void basic_symex(
  const codet &code,
  const namespacet &ns,
  symex_targett &target)
{
  contextt new_context;
  basic_symext basic_symex(ns, new_context, target);
  goto_symex_statet state;
  basic_symex.symex(state, code);
}
