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

Module: Symbolic Execution

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

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

#include <find_symbols.h>

#include <pointer-analysis/dereference.h>

#include "goto_symex_state.h"
#include "precondition.h"

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

   Class: preconditiont

  Inputs:

 Outputs:

 Purpose:

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

class preconditiont
{
public:
  preconditiont(
    const namespacet &_ns,
    const value_sett &_value_set,
    const symex_target_equationt::statet &_state,
    const goto_symex_statet &_s):
    ns(_ns),
    value_set(_value_set),
    state(_state),
    s(_s)
  {
  }

protected:
  const namespacet &ns;
  const value_sett &value_set;
  const symex_target_equationt::statet &state;
  const goto_symex_statet &s;
  void compute_rec(exprt &dest);

public:
  void compute(exprt &dest);
  
protected:
  void compute_address_of(exprt &dest);
};

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

Function: precondition

  Inputs:

 Outputs:

 Purpose:

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

void precondition(
  const namespacet &ns,
  const value_sett &value_set,
  const symex_target_equationt &equation,
  const goto_symex_statet &s,
  exprt &dest)
{
  for(symex_target_equationt::statest::const_reverse_iterator
      it=equation.states.rbegin();
      it!=equation.states.rend();
      it++)
  {
    preconditiont precondition(ns, value_set, *it, s);
    precondition.compute(dest);
    if(dest.is_false()) return;
  }
}

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

Function: preconditiont::compute_address_of

  Inputs:

 Outputs:

 Purpose:

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

void preconditiont::compute_address_of(exprt &dest)
{
  if(dest.id()=="symbol")
  {
    // leave alone
  }
  else if(dest.id()=="index")
  {
    assert(dest.operands().size()==2);
    compute_address_of(dest.op0());
    compute(dest.op1());
  }
  else if(dest.id()=="member")
  {
    assert(dest.operands().size()==1);
    compute_address_of(dest.op0());
  }
  else if(dest.id()=="dereference" || 
          dest.id()=="implicit_dereference")
  {
    assert(dest.operands().size()==1);
    compute(dest.op0());
  }
}

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

Function: preconditiont::compute

  Inputs:

 Outputs:

 Purpose:

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

void preconditiont::compute(exprt &dest)
{
  compute_rec(dest);
}

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

Function: preconditiont::compute_rec

  Inputs:

 Outputs:

 Purpose:

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

void preconditiont::compute_rec(exprt &dest)
{
  if(dest.id()=="address_of" ||
     dest.id()=="reference_to" ||
     dest.id()=="implicit_address_of")
  {
    // only do index!
    assert(dest.operands().size()==1);
    compute_address_of(dest.op0());
  }
  else if(dest.id()=="symbol")
  {
    if(dest.get("identifier")==
       s.get_original_name(state.lhs.get("identifier")))
    {
      dest=state.rhs;
      s.get_original_name(dest);
    }
  }
  else if(dest.id()=="dereference" ||
          dest.id()=="implicit_dereference")
  {
    assert(dest.operands().size()==1);

    const irep_idt &lhs_identifier=
      s.get_original_name(state.lhs.get("identifier"));
  
    // aliasing may happen here

    value_sett::expr_sett expr_set;
    value_set.get_value_set(dest.op0(), expr_set, ns);
    hash_set_cont<irep_idt, irep_id_hash> symbols;

    for(value_sett::expr_sett::const_iterator
        it=expr_set.begin();
        it!=expr_set.end();
        it++)
      find_symbols(*it, symbols);
    
    if(symbols.find(lhs_identifier)!=symbols.end())
    {
      // may alias!
      exprt tmp;
      tmp.swap(dest.op0());
      dereference(ns, value_set, tmp);
      dest.swap(tmp);
      compute_rec(dest);
    }
    else
    {
      // nah, ok
      compute_rec(dest.op0());
    }
  }
  else
    Forall_operands(it, dest)
      compute_rec(*it);
}
