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

Module: Symbolic Execution of ANSI-C

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

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

#include <assert.h>

#include <expr_util.h>
#include <i2string.h>
#include <cprover_prefix.h>
#include <prefix.h>
#include <arith_tools.h>
#include <base_type.h>

#include <ansi-c/c_types.h>

#include "goto_symex.h"

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

Function: goto_symext::get_unwind_recursion

  Inputs:

 Outputs:

 Purpose:

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

bool goto_symext::get_unwind_recursion(
  const irep_idt &identifier,
  unsigned unwind)
{
  return false;
}

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

Function: goto_symext::parameters

  Inputs:

 Outputs:

 Purpose:

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

void goto_symext::parameters(
  const code_typet &function_type,
  statet &saved_state,
  statet &state,
  const exprt &call)
{
  // iterates over the operands
  exprt::operandst::const_iterator it1=call.operands().begin();

  it1++; // skip function operand

  // these are the types of the arguments
  const code_typet::argumentst &arguments=function_type.arguments();

  // iterates over the types of the arguments
  for(code_typet::argumentst::const_iterator
      it2=arguments.begin();
      it2!=arguments.end();
      it2++)
  {
    // if you run out of actual arguments there was a mismatch
    if(it1==call.operands().end())
      throw "function call: not enough arguments";

    const code_typet::argumentt &argument=*it2;

    // this is the type the n-th argument should be
    const typet &arg_type=argument.type();

    const irep_idt &identifier=argument.get("#identifier");
    
    if(identifier=="")
      throw "no identifier for function argument";

    const symbolt &symbol=ns.lookup(identifier);
    exprt lhs(symbol_expr(symbol));

    if(it1->is_nil())
    {
    }
    else
    {
      exprt rhs=*it1;

      // it should be the same exact type
      if(!base_type_eq(arg_type, rhs.type(), ns))
      {
        std::string error="function call: argument \""+
          id2string(identifier)+"\" type mismatch: got "+
          it1->type().to_string()+", expected "+
          arg_type.to_string();
        throw error;
      }
      
      saved_state.rename(rhs);
      do_simplify(rhs);

      assignment(state, lhs, rhs, true);
    }

    it1++;
  }

  if(function_type.has_ellipsis())
  {
    for(; it1!=call.operands().end(); it1++)
    {
    }
  }
  else if(it1!=call.operands().end())
    throw "function call: too many arguments"
          " (got "+i2string(call.operands().size()-1)+
          ", expected "+i2string(arguments.size())+")";
}

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

Function: goto_symext::symex_functioncall

  Inputs:

 Outputs:

 Purpose:

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

void goto_symext::symex_functioncall(
  const goto_functionst &goto_functions,
  statet &state,
  const exprt &lhs,
  const exprt &code)
{
  const exprt &op0=code.op0();

  if(op0.id()=="symbol")
    symex_functioncall_symbol(goto_functions, state, lhs, code);
  else
    throw "unexpected function for symex_functioncall: "+op0.id_string();
}

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

Function: goto_symext::symex_functioncall_symbol

  Inputs:

 Outputs:

 Purpose:

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

void goto_symext::symex_functioncall_symbol(
  const goto_functionst &goto_functions,
  statet &state,
  const exprt &lhs,
  const exprt &code)
{
  target.location(state.guard, state.source);

  assert(code.op0().id()=="symbol");

  const irep_idt &identifier=
    code.op0().get("identifier");

  if(identifier=="c::CBMC_trace")
  {
    if(code.operands().size()<3)
      throw "CBMC_trace expects at least two arguments";
  
    int debug_thresh=atol(options.get_option("debug-level").c_str());
    
    mp_integer debug_lvl;

    if(to_integer(code.op1(), debug_lvl))
      throw "CBMC_trace expects constant as first argument";
      
    if(code.op2().id()!="implicit_address_of" ||
       code.op2().operands().size()!=1 ||
       code.op2().op0().id()!="string-constant")
      throw "CBMC_trace expects string constant as second argument";
    
    if(mp_integer(debug_thresh)>=debug_lvl)
    {
      std::list<exprt> vars;
      
      exprt trace_event("trave_event");
      trace_event.set("event", code.op2().op0().get("value"));
      
      vars.push_back(trace_event);

      for(unsigned j=3; j<code.operands().size(); j++)
      {
	exprt var(code.operands()[j]);
	state.rename(var);
	vars.push_back(var);
      }

      target.output(state.guard, state.source, vars);
    }
  }
  else if(has_prefix(id2string(identifier), CPROVER_FKT_PREFIX))
  {
    symex_fkt(state, lhs, code);
  }
  else if(has_prefix(id2string(identifier), CPROVER_MACRO_PREFIX))
  {
    symex_macro(state, lhs, code);
  }
  else
    symex_functioncall_code(goto_functions, state, lhs, code);
}

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

Function: goto_symext::symex_functioncall_code

  Inputs:

 Outputs:

 Purpose: do function call by inlining

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

void goto_symext::symex_functioncall_code(
  const goto_functionst &goto_functions,
  statet &state,
  const exprt &lhs,
  const exprt &call)
{
  const irep_idt &identifier=call.op0().get("identifier");
  
  // find code in function map
  
  goto_functionst::function_mapt::const_iterator it=
    goto_functions.function_map.find(identifier);

  if(it==goto_functions.function_map.end())
    throw "failed to find `"+id2string(identifier)+"' in function_map";

  const goto_functiont &goto_function=it->second;
  
  unsigned &unwinding_counter=function_unwind[identifier];

  // see if it's too much
  if(get_unwind_recursion(identifier, unwinding_counter))
  {
    if(options.get_bool_option("unwinding-assertions"))
    {
      exprt false_expr;
      false_expr.make_false();
      claim(false_expr, "recursion unwinding assertion", state);
    }

    return;
  }

  if(!goto_function.body_available)
  {
    no_body(identifier);
  
    if(lhs.is_not_nil())
    {
      exprt rhs=exprt("nondet_symbol", call.type());
      rhs.set("identifier", "symex::"+i2string(nondet_count++));
      rhs.location()=call.location();
      code_assignt code;
      code.copy_to_operands(lhs);
      code.move_to_operands(rhs);
      basic_symext::symex(state, code);
    }

    return;
  }
  
  // increase unwinding counter
  unwinding_counter++;
  
  // new frame
  framet &saved_frame=top();
  saved_frame.state=state;
  call_stack.push_back(framet());
  
  unsigned &frame_nr=function_frame[identifier];
  frame_nr++;

  // preserve locality of local variables
  locality(frame_nr, state, goto_function);

  // parameters
  parameters(to_code_type(goto_function.type), saved_frame.state, state, call);

  top().return_point_set=true;
  top().return_point=--goto_function.body.instructions.end();
  top().return_value=lhs;

  // execute body  
  for(state.source.pc=
      goto_function.body.instructions.begin();
      state.source.pc!=goto_function.body.instructions.end();
      )
    symex(goto_functions, state);

  if(unwinding_counter==0) throw "unexpected end-of-function";
  unwinding_counter--;

  // restore state
  state.source.pc=saved_frame.state.source.pc;
  state.guard=saved_frame.state.guard;
  state.level1.current_names=saved_frame.state.level1.current_names;
  call_stack.pop_back();
}

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

Function: goto_symext::locality

  Inputs:

 Outputs:

 Purpose:

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

void goto_symext::locality(
  unsigned frame,
  statet &state,
  const goto_functiont &goto_function)
{
  goto_programt::local_variablest local_identifiers;

  for(goto_programt::instructionst::const_iterator
      it=goto_function.body.instructions.begin();
      it!=goto_function.body.instructions.end();
      it++)
    local_identifiers.insert(it->local_variables.begin(),
                             it->local_variables.end());

  for(goto_programt::local_variablest::const_iterator
      it=local_identifiers.begin();
      it!=local_identifiers.end();
      it++)
    state.level1.rename(*it, frame);
}

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

Function: goto_symext::symex_return

  Inputs:

 Outputs:

 Purpose:

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

void goto_symext::symex_return(statet &state)
{
  if(!top().return_point_set)
    throw "return without function call";
    
  const goto_programt::instructiont &instruction=*state.source.pc;
  const exprt &code=instruction.code;
  
  target.location(state.guard, state.source);

  if(code.operands().size()==1)
  {
    exprt value(code.op0());
    
    dereference(value, state);
  
    if(top().return_value.is_not_nil())
    {
      code_assignt assignment;
      assignment.copy_to_operands(top().return_value);
      assignment.move_to_operands(value);
      basic_symext::symex_assign(state, assignment, false);
    }
  }
  else
  {
    if(top().return_value.is_not_nil())
      throw "return with unexpected value";
  }

  // put on state-queue
  state_listt &state_list=top().state_map[top().return_point];
  state_list.push_back(state);
  statet &new_state=state_list.back();
  new_state.source.pc=top().return_point;
  
  state.guard.make_false();
}
