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

Module: Value Set Propagation

Author: Daniel Kroening, kroening@kroening.com

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

#include <assert.h>

#include <std_code.h>
#include <expr_util.h>

#include "static_analysis.h"

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

Function: abstract_domain_baset::get_guard

  Inputs:

 Outputs:

 Purpose:

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

exprt abstract_domain_baset::get_guard(
  locationt from,
  locationt to) const
{
  if(!from->is_goto())
    return gen_true();

  locationt next=from;
  next++;

  if(next==to)
  {
    exprt tmp(from->guard);
    tmp.make_not();
    return tmp;
  }
  
  return from->guard;
}

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

Function: abstract_domain_baset::get_return_lhs

  Inputs:

 Outputs:

 Purpose:

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

exprt abstract_domain_baset::get_return_lhs(
  locationt from,
  locationt to) const
{
  assert(from->is_return());
  
  if(to->is_end_function())
    return static_cast<const exprt &>(nil_rep);
  
  assert(to->is_other());

  const codet &code=to_code(to->code);
  
  if(code.get_statement()=="functioncall")
  {
    // no LHS
    return static_cast<const exprt &>(nil_rep);
  }
  else if(code.get_statement()=="assign" &&
          code.operands().size()==2 &&
          code.op1().id()=="sideeffect" &&
          code.op1().get("statement")=="functioncall")
  {
    return code.op0();
  }
  else
    throw "expected function call";
}

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

Function: static_analysis_baset::operator()

  Inputs:

 Outputs:

 Purpose:

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

void static_analysis_baset::operator()(
  const goto_functionst &goto_functions)
{
  initialize(goto_functions);
  fixedpoint(goto_functions);
}

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

Function: static_analysis_baset::operator()

  Inputs:

 Outputs:

 Purpose:

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

void static_analysis_baset::operator()(
  const goto_programt &goto_program)
{
  initialize(goto_program);
  goto_functionst goto_functions;
  locationt call_l=goto_program.instructions.end();
  fixedpoint(goto_program, call_l, goto_functions);
}

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

Function: static_analysis_baset::output

  Inputs:

 Outputs:

 Purpose:

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

void static_analysis_baset::output(
  const goto_functionst &goto_functions,
  std::ostream &out) const
{
  for(goto_functionst::function_mapt::const_iterator
      f_it=goto_functions.function_map.begin();
      f_it!=goto_functions.function_map.end();
      f_it++)
  {
    out << "////" << std::endl;
    out << "//// Function: " << f_it->first << std::endl;
    out << "////" << std::endl;
    out << std::endl;

    output(f_it->second.body, f_it->first, out);
  }
}

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

Function: static_analysis_baset::output

  Inputs:

 Outputs:

 Purpose:

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

void static_analysis_baset::output(
  const goto_programt &goto_program,
  const irep_idt &identifier,
  std::ostream &out) const
{
  forall_goto_program_instructions(i_it, goto_program)
  {
    out << "**** " << i_it->location << std::endl;

    get_state(i_it).output(ns, out);
    out << std::endl;
    goto_program.output_instruction(ns, identifier, out, i_it);
    out << std::endl;
  }
}

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

Function: static_analysis_baset::generate_states

  Inputs:

 Outputs:

 Purpose:

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

void static_analysis_baset::generate_states(
  const goto_functionst &goto_functions)
{
  for(goto_functionst::function_mapt::const_iterator
      f_it=goto_functions.function_map.begin();
      f_it!=goto_functions.function_map.end();
      f_it++)
    generate_states(f_it->second.body);
}

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

Function: static_analysis_baset::generate_states

  Inputs:

 Outputs:

 Purpose:

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

void static_analysis_baset::generate_states(
  const goto_programt &goto_program)
{
  forall_goto_program_instructions(i_it, goto_program)
    generate_state(i_it);
}

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

Function: static_analysis_baset::update

  Inputs:

 Outputs:

 Purpose:

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

void static_analysis_baset::update(
  const goto_functionst &goto_functions)
{
  for(goto_functionst::function_mapt::const_iterator
      f_it=goto_functions.function_map.begin();
      f_it!=goto_functions.function_map.end();
      f_it++)
    update(f_it->second.body);
}

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

Function: static_analysis_baset::generate_states

  Inputs:

 Outputs:

 Purpose:

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

void static_analysis_baset::update(
  const goto_programt &goto_program)
{
  locationt previous;
  bool first=true;

  forall_goto_program_instructions(i_it, goto_program)
  {
    // do we have it already?
    if(!has_location(i_it))
    {
      generate_state(i_it);

      if(!first)
        merge(get_state(i_it), get_state(previous));
    }
    
    if(first)
      first=false;
    else
      previous=i_it;
  }
}

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

Function: static_analysis_baset::get_next

  Inputs:

 Outputs:

 Purpose:

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

static_analysis_baset::locationt static_analysis_baset::get_next(
  working_sett &working_set)
{
  assert(!working_set.empty());
  
  working_sett::iterator i=working_set.begin();
  locationt l=i->second;
  working_set.erase(i);
    
  return l;
}

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

Function: static_analysis_baset::fixedpoint

  Inputs:

 Outputs:

 Purpose:

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

bool static_analysis_baset::fixedpoint(
  const goto_programt &goto_program,
  locationt call_l,
  const goto_functionst &goto_functions)
{
  working_sett working_set;

  if(goto_program.instructions.begin()==
     goto_program.instructions.end())
    return false;
  
  put_in_working_set(
    working_set,
    goto_program.instructions.begin());
    
  bool new_data=false;

  while(!working_set.empty())
  {
    locationt l=get_next(working_set);
    
    if(visit(l, call_l, working_set, goto_program, goto_functions))
      new_data=true;
  }

  return new_data;
}

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

Function: static_analysis_baset::visit

  Inputs:

 Outputs:

 Purpose:

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

bool static_analysis_baset::visit(
  locationt l,
  locationt call_l,
  working_sett &working_set,
  const goto_programt &goto_program,
  const goto_functionst &goto_functions)
{
  bool new_data=false;

  statet &current=get_state(l);

  current.seen=true;
  
  goto_programt::const_targetst successors;

  goto_program.get_successors(l, successors);

  for(goto_programt::const_targetst::const_iterator
      it=successors.begin();
      it!=successors.end();
      it++)
  {
    locationt to_l=*it;

    if(to_l==goto_program.instructions.end())
      continue;

    bool do_return=
      l->is_return() &&
      call_l!=goto_program.instructions.end();
    
    std::auto_ptr<statet> tmp_state(
      make_temporary_state(current));
    
    statet &new_values=*tmp_state;
  
    if(do_return)
      new_values.transform(ns, l, call_l);
    else
      new_values.transform(ns, l, to_l);

    if(l->is_other())
    {
      const exprt &code=l->code;

      if(code.get("statement")=="functioncall")
      {
        if(code.operands().size()<1)
          throw "functioncall takes at least one argument";

        do_functioncall_rec(
          l,
          static_cast<const exprt &>(nil_rep), // lhs
          code.op0(),                          // function
          code,                                // call
          new_values,
          goto_functions);
      }
      else if(code.get("statement")=="assign" &&
              code.operands().size()==2 &&
              code.op1().id()=="sideeffect" &&
              code.op1().get("statement")=="functioncall")
      {
        if(code.op1().operands().size()<1)
          throw "functioncall takes at least one argument";

        do_functioncall_rec(
          l,
          code.op0(),       // lhs
          code.op1().op0(), // function
          code.op1(),       // call
          new_values,
          goto_functions);
      }
    }
    
    locationt merge_l=to_l;
    
    if(do_return) { merge_l=call_l; merge_l++; }

    statet &other=get_state(merge_l);
  
    bool have_new_values=
      merge(other, new_values);
    
    if(have_new_values)
      new_data=true;
    
    if(have_new_values || !other.seen)
      if(!do_return)
        put_in_working_set(working_set, to_l);
  }
  
  return new_data;
}

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

Function: static_analysis_baset::do_function_call

  Inputs:

 Outputs:

 Purpose:

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

void static_analysis_baset::do_function_call(
  locationt l,
  const exprt &lhs,
  const goto_functionst &goto_functions,
  const goto_functionst::function_mapt::const_iterator f_it,
  const exprt &call,
  statet &new_state)
{
  const goto_functiont &goto_function=f_it->second;

  if(!goto_function.body_available)
    return; // do nothing
    
  assert(!goto_function.body.instructions.empty());

  // get the state at the beginning of the function
  locationt l_begin=goto_function.body.instructions.begin();
  
  statet &begin_state=get_state(l_begin);

  bool new_data=false;

  // merge the stuff
  if(merge(begin_state, new_state))
    new_data=true;

  // do each function at least once
  if(functions_done.find(f_it->first)==
     functions_done.end())
  {
    new_data=true;
    functions_done.insert(f_it->first);
  }

  // do we need to do the fixedpoint of the body?
  if(new_data)
  {
    // recursive call
    fixedpoint(goto_function.body, l, goto_functions);

    // get location at end of procedure
    locationt l_end=--goto_function.body.instructions.end();

    assert(l_end->is_end_function());

    const statet &end_of_function=get_state(l_end);

    // propagate those -- not exceedingly precise, this is,
    // as still it contains all the state from the
    // call site
    merge(new_state, end_of_function);
  }

  #if 0    
  // TODO: add local variables that are pointed to
  // by function arguments
    
  do_arguments(goto_function.type, call, start_of_function, ns, new_data);
  
  if(new_data)
  {
    // recursive call
    _compute(f_it, goto_functions, ns, new_data);

    assert(goto_function.body.instructions.back().is_end_function());

    const value_sett &end_of_function=
      (*this)[--goto_function.body.instructions.end()];

    // TODO: worry about return value
      
    new_values.add_vars(end_of_function);
    new_values.make_union(end_of_function);      
  }

  // add missing stuff from start_values
  new_values.add_missing(start_values);
  #endif
}    

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

Function: static_analysis_baset::do_functioncall_rec

  Inputs:

 Outputs:

 Purpose:

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

void static_analysis_baset::do_functioncall_rec(
  locationt l,
  const exprt &lhs,
  const exprt &function,
  const exprt &call,
  statet &new_state,
  const goto_functionst &goto_functions)
{
  if(function.id()=="symbol")
  {
    const irep_idt &identifier=function.get("identifier");
    
    if(recursion_set.find(identifier)!=recursion_set.end())
    {
      // recursion detected!
      return;
    }
    else
      recursion_set.insert(identifier);
      
    goto_functionst::function_mapt::const_iterator it=
      goto_functions.function_map.find(identifier);
      
    if(it==goto_functions.function_map.end())
      throw "failed to find function "+id2string(identifier);
    
    do_function_call(
      l,
      lhs,
      goto_functions,
      it,
      call,
      new_state);
    
    recursion_set.erase(identifier);
  }
  else if(function.id()=="if")
  {
    if(function.operands().size()!=3)
      throw "if takes three arguments";
    
    std::auto_ptr<statet> n2(make_temporary_state(new_state));
    
    do_functioncall_rec(
      l,
      lhs,
      function.op1(),
      call,
      new_state,
      goto_functions);

    do_functioncall_rec(
      l,
      lhs,
      function.op2(),
      call,
      *n2,
      goto_functions);
      
    merge(new_state, *n2);
  }
  else
  {
    throw "unexpected functioncall argument: "+
      function.id_string();
  }
}

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

Function: static_analysis_baset::fixedpoint

  Inputs:

 Outputs:

 Purpose:

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

void static_analysis_baset::fixedpoint(
  const goto_functionst &goto_functions)
{
  // do each function at least once

  for(goto_functionst::function_mapt::const_iterator
      it=goto_functions.function_map.begin();
      it!=goto_functions.function_map.end();
      it++)
    if(functions_done.find(it->first)==
       functions_done.end())
    {
      locationt call_l=it->second.body.instructions.end();
      fixedpoint(it, call_l, goto_functions);
    }
}

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

Function: static_analysis_baset::fixedpoint

  Inputs:

 Outputs:

 Purpose:

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

bool static_analysis_baset::fixedpoint(
  const goto_functionst::function_mapt::const_iterator it,
  locationt call_l,
  const goto_functionst &goto_functions)
{
  functions_done.insert(it->first);
  return fixedpoint(it->second.body, call_l, goto_functions);
}
