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

Module: Dereferencing Operations on GOTO Programs

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

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

#include <simplify_expr.h>
#include <base_type.h>

#include "goto_program_dereference.h"

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

Function: goto_program_dereferencet::has_failed_symbol

  Inputs:

 Outputs:

 Purpose:

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

bool goto_program_dereferencet::has_failed_symbol(
  const exprt &expr,
  const symbolt *&symbol)
{
  if(expr.id()=="symbol")
  {
    if(expr.get_bool("#invalid_object"))
      return false;

    const symbolt &ptr_symbol=ns.lookup(expr);

    const irep_idt &failed_symbol=
      ptr_symbol.type.get("#failed_symbol");    
      
    if(failed_symbol=="") return false;

    return !ns.lookup(failed_symbol, symbol);
  }
  
  return false;
}

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

Function: goto_program_dereferencet::is_valid_object

  Inputs:

 Outputs:

 Purpose:

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

bool goto_program_dereferencet::is_valid_object(
  const irep_idt &identifier)
{
  const symbolt &symbol=ns.lookup(identifier);

  if(symbol.type.id()=="code")
    return true;

  if(symbol.static_lifetime)
    return true; // global/static

  if(valid_local_variables->find(symbol.name)!=
     valid_local_variables->end())
    return true; // valid local

  return false;
}

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

Function: goto_program_dereferencet::dereference_failure

  Inputs:

 Outputs:

 Purpose:

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

void goto_program_dereferencet::dereference_failure(
  const std::string &property,
  const std::string &msg,
  const guardt &guard)
{
  exprt guard_expr;
  guard.expr(guard_expr);
  
  if(assertions.insert(guard_expr).second)
  {
    guard_expr.make_not();

    // first try simplifier on it
    if(options.get_bool_option("simplify"))
    {
      base_type(guard_expr, ns);
      simplify(guard_expr);
    }
  
    if(!guard_expr.is_true())
    {
      goto_programt::targett t=new_code.add_instruction(ASSERT);
      t->guard.swap(guard_expr);
      t->location=dereference_location;
      t->location.set("property", property);
      t->location.set("comment", "dereference failure: "+msg);
    }
  }
}

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

Function: goto_program_dereferencet::dereference_rec

  Inputs:

 Outputs:

 Purpose:

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

void goto_program_dereferencet::dereference_rec(
  exprt &expr,
  guardt &guard)
{
  if(!dereference.has_dereference(expr))
    return;

  if(expr.id()=="and" || expr.id()=="or")
  {
    if(!expr.is_boolean())
      throw expr.id_string()+" must be Boolean, but got "+
            expr.pretty();

    unsigned old_guards=guard.size();

    for(unsigned i=0; i<expr.operands().size(); i++)
    {
      exprt &op=expr.operands()[i];

      if(!op.is_boolean())
        throw expr.id_string()+" takes Boolean operands only, but got "+
              op.pretty();

      if(dereference.has_dereference(op))
        dereference_rec(op, guard);

      if(expr.id()=="or")
      {
        exprt tmp(op);
        tmp.make_not();
        guard.move(tmp);
      }
      else
        guard.add(op);
    }

    guard.resize(old_guards);

    return;
  }
  else if(expr.id()=="if")
  {
    if(expr.operands().size()!=3)
      throw "if takes three arguments";

    if(!expr.op0().is_boolean())
    {
      std::string msg=
        "first argument of if must be boolean, but got "
        +expr.op0().to_string();
      throw msg;
    }

    dereference_rec(expr.op0(), guard);

    bool o1=dereference.has_dereference(expr.op1());
    bool o2=dereference.has_dereference(expr.op2());

    if(o1)
    {
      unsigned old_guard=guard.size();
      guard.add(expr.op0());
      dereference_rec(expr.op1(), guard);
      guard.resize(old_guard);
    }

    if(o2)
    {
      unsigned old_guard=guard.size();
      exprt tmp(expr.op0());
      tmp.make_not();
      guard.move(tmp);
      dereference_rec(expr.op2(), guard);
      guard.resize(old_guard);
    }

    return;
  }

  if(expr.id()=="address_of" ||
     expr.id()=="reference_to")
  {
    // turn &*p to p
    // this has *no* side effect!
    
    assert(expr.operands().size()==1);
    
    if(expr.op0().id()=="dereference" ||
       expr.op0().id()=="implicit_dereference")
    {
      assert(expr.op0().operands().size()==1);

      exprt tmp;
      tmp.swap(expr.op0().op0());
      
      if(tmp.type()!=expr.type())
        tmp.make_typecast(expr.type());

      expr.swap(tmp);      
    }
  }

  Forall_operands(it, expr)
    dereference_rec(*it, guard);

  if(expr.id()=="dereference" ||
     expr.id()=="implicit_dereference")
  {
    if(expr.operands().size()!=1)
      throw "dereference expects one operand";

    dereference_location=expr.find_location();

    exprt tmp;
    tmp.swap(expr.op0());
    dereference.dereference(tmp, guard);
    expr.swap(tmp);
  }
  else if(expr.id()=="index")
  {
    if(expr.operands().size()!=2)
      throw "index expects two operands";

    if(expr.op0().type().id()=="pointer")
    {
      dereference_location=expr.find_location();

      exprt tmp("+", expr.op0().type());
      tmp.operands().swap(expr.operands());
      dereference.dereference(tmp, guard);
      tmp.swap(expr);
    }
  }
}

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

Function: goto_program_dereferencet::get_value_set

  Inputs:

 Outputs:

 Purpose:

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

void goto_program_dereferencet::get_value_set(
  const exprt &expr,
  value_sett::expr_sett &value_set)
{
  current_value_set->get_value_set(expr, value_set, ns);
}

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

Function: goto_program_dereferencet::remove

  Inputs:

 Outputs:

 Purpose:

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

void goto_program_dereferencet::dereference_expr(
  exprt &expr,
  bool checks_only)
{
  guardt guard;
  
  if(checks_only)
  {
    exprt tmp(expr);
    dereference_rec(tmp, guard);
  }
  else
    dereference_rec(expr, guard);
}

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

Function: goto_program_dereferencet::dereference

  Inputs:

 Outputs:

 Purpose:

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

void goto_program_dereferencet::dereference_program(
  goto_programt &goto_program,
  const value_propagationt &value_propagation,
  bool checks_only)
{
  for(goto_programt::instructionst::iterator
      it=goto_program.instructions.begin();
      it!=goto_program.instructions.end();
      it++)
  {
    new_code.clear();
    assertions.clear();

    dereference_instruction(it, value_propagation, checks_only);  
    
    for(goto_programt::instructionst::iterator
        i_it=new_code.instructions.begin();
        i_it!=new_code.instructions.end();
        i_it++)
      i_it->local_variables=it->local_variables;
    
    // insert new instructions
    while(!new_code.instructions.empty())
    {
      goto_program.insert_swap(it, new_code.instructions.front());
      new_code.instructions.pop_front();
      it++;
    }
  }
}

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

Function: goto_program_dereferencet::dereference

  Inputs:

 Outputs:

 Purpose:

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

void goto_program_dereferencet::dereference_program(
  goto_functionst &goto_functions,
  const value_propagationt &value_propagation,
  bool checks_only)
{
  for(goto_functionst::function_mapt::iterator
      it=goto_functions.function_map.begin();
      it!=goto_functions.function_map.end();
      it++)
    dereference_program(
      it->second.body, value_propagation, checks_only);
}

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

Function: goto_program_dereferencet::dereference

  Inputs:

 Outputs:

 Purpose:

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

void goto_program_dereferencet::dereference_instruction(
  goto_programt::targett target,
  const value_propagationt &value_propagation,
  bool checks_only)
{
  current_value_set=&value_propagation.lookup(target);
  valid_local_variables=&target->local_variables;
  goto_programt::instructiont &i=*target;

  dereference_expr(i.guard, checks_only);

  if(i.is_other())
  {
    const irep_idt &statement=i.code.get("statement");
    
    if(statement=="assign")
    {
      if(i.code.operands().size()!=2)
        throw "assignment expects two operands";

      dereference_expr(i.code.op0(), checks_only);
      dereference_expr(i.code.op1(), checks_only);
    }
    else if(statement=="decl")
    {
      if(i.code.operands().size()!=1 &&
         i.code.operands().size()!=2)
        throw "decl expects one or two operands";

      if(i.code.operands().size()==2)
        dereference_expr(i.code.op1(), checks_only);
    }
    else if(statement=="expression")
    {
      if(i.code.operands().size()!=1)
        throw "expression expects one operand";

      dereference_expr(i.code.op0(), checks_only);
    }
    else if(statement=="functioncall")
    {
      Forall_operands(it, i.code)
        dereference_expr(*it, checks_only);
    }
    else if(statement=="printf")
    {
      Forall_operands(it, i.code)
        dereference_expr(*it, checks_only);
    }
  }
}

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

Function: goto_program_dereferencet::dereference

  Inputs:

 Outputs:

 Purpose:

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

void goto_program_dereferencet::dereference_expression(
  goto_programt::const_targett target,
  exprt &expr,
  const value_propagationt &value_propagation)
{
  current_value_set=&value_propagation.lookup(target);
  valid_local_variables=&target->local_variables;

  dereference_expr(expr, false);
}

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

Function: goto_program_dereferencet::pointer_checks

  Inputs:

 Outputs:

 Purpose:

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

void goto_program_dereferencet::pointer_checks(
  goto_programt &goto_program,
  const value_propagationt &value_propagation)
{
  dereference_program(goto_program, value_propagation, true);
}

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

Function: goto_program_dereferencet::pointer_checks

  Inputs:

 Outputs:

 Purpose:

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

void goto_program_dereferencet::pointer_checks(
  goto_functionst &goto_functions,
  const value_propagationt &value_propagation)
{
  dereference_program(goto_functions, value_propagation, true);
}

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

Function: remove_pointers

  Inputs:

 Outputs:

 Purpose:

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

void remove_pointers(
  goto_programt &goto_program,
  contextt &context,
  const optionst &options,
  const value_propagationt &value_propagation)
{
  namespacet ns(context);
  goto_program_dereferencet goto_program_dereference(ns, context, options);
  goto_program_dereference.dereference_program(goto_program, value_propagation);
}                    

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

Function: pointer_checks

  Inputs:

 Outputs:

 Purpose:

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

void pointer_checks(
  goto_programt &goto_program,
  const namespacet &ns,
  const optionst &options,
  const value_propagationt &value_propagation)
{
  contextt new_context;
  goto_program_dereferencet goto_program_dereference(ns, new_context, options);
  goto_program_dereference.pointer_checks(goto_program, value_propagation);
}                    

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

Function: pointer_checks

  Inputs:

 Outputs:

 Purpose:

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

void pointer_checks(
  goto_functionst &goto_functions,
  const namespacet &ns,
  const optionst &options,
  const value_propagationt &value_propagation)
{
  contextt new_context;
  goto_program_dereferencet goto_program_dereference(ns, new_context, options);
  goto_program_dereference.pointer_checks(goto_functions, value_propagation);
}                    

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

Function: dereference

  Inputs:

 Outputs:

 Purpose:

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

void dereference(
  goto_programt::const_targett target,
  exprt &expr,
  const namespacet &ns,
  const value_propagationt &value_propagation)
{
  optionst options;
  contextt new_context;
  goto_program_dereferencet goto_program_dereference(ns, new_context, options);
  goto_program_dereference.dereference_expression(target, expr, value_propagation);
}
