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

Module: GOTO Programs

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

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

#include <location.h>
#include <i2string.h>
#include <expr_util.h>
#include <guard.h>
#include <simplify_expr.h>
#include <array_name.h>
#include <base_type.h>

#include "goto_check.h"

class goto_checkt
{
public:
  goto_checkt(
    const namespacet &_ns,
    const optionst &_options):
    ns(_ns),
    options(_options) { }

  void goto_check(goto_programt &goto_program);
    
protected:
  const namespacet &ns;
  const optionst &options;

  void check_rec(const exprt &expr, guardt &guard);
  void check(const exprt &expr);

  void bounds_check(const exprt &expr, const guardt &guard);
  void div_by_zero_check(const exprt &expr, const guardt &guard);
  void pointer_rel_check(const exprt &expr, const guardt &guard);
  //void array_size_check(const exprt &expr);
  void overflow_check(const exprt &expr, const guardt &guard);
  std::string array_name(const exprt &expr);

  void add_guarded_claim(
    const exprt &expr,
    const std::string &comment,
    const std::string &property,
    const locationt &location,
    const guardt &guard);
  
  goto_programt new_code;
  std::set<exprt> assertions;
};

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

Function: goto_checkt::div_by_zero_check

  Inputs:

 Outputs:

 Purpose:

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

void goto_checkt::div_by_zero_check(
  const exprt &expr,
  const guardt &guard)
{
  if(!options.get_bool_option("div-by-zero-check"))
    return;

  if(expr.operands().size()!=2)
    throw expr.id_string()+" takes two arguments";

  // add divison by zero subgoal

  exprt zero=gen_zero(expr.op1().type());

  if(zero.is_nil())
    throw "no zero of argument type of operator "+expr.id_string();

  exprt inequality("notequal", typet("bool"));
  inequality.copy_to_operands(expr.op1());
  inequality.move_to_operands(zero);

  add_guarded_claim(
    inequality,
    "division by zero",
    "division-by-zero",
    expr.find_location(),
    guard);
}

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

Function: goto_checkt::overflow_check

  Inputs:

 Outputs:

 Purpose:

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

void goto_checkt::overflow_check(
  const exprt &expr,
  const guardt &guard)
{
  if(!options.get_bool_option("overflow-check"))
    return;

  // first, check type
  if(expr.type().id()!="signedbv")
    return;

  // add overflow subgoal

  exprt overflow("overflow-"+expr.id_string(), typet("bool"));
  overflow.operands()=expr.operands();

  if(expr.id()=="typecast")
  {
    if(expr.operands().size()!=1)
      throw "typecast takes one operand";

    const typet &old_type=expr.op0().type();

    unsigned new_width=atoi(expr.type().get("width").c_str());
    unsigned old_width=atoi(old_type.get("width").c_str());

    if(old_type.id()=="unsignedbv") new_width--;
    if(new_width>=old_width) return;

    overflow.id(overflow.id_string()+"-"+i2string(new_width));
  }

  overflow.make_not();

  add_guarded_claim(
    overflow,
    "arithmetic overflow on "+expr.id_string(),
    "overflow",
    expr.find_location(),
    guard);
}

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

Function: goto_checkt::pointer_rel_check

  Inputs:

 Outputs:

 Purpose:

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

void goto_checkt::pointer_rel_check(
  const exprt &expr,
  const guardt &guard)
{
  if(expr.operands().size()!=2)
    throw expr.id_string()+" takes one argument";

  if(expr.op0().type().id()=="pointer" &&
     expr.op1().type().id()=="pointer")
  {
    // add same-object subgoal

    if(options.get_bool_option("pointer-check"))
    {
      exprt same_object("same-object", typet("bool"));
      same_object.copy_to_operands(expr.op0(), expr.op1());

      add_guarded_claim(
        same_object,
        "same object violation",
        "pointer",
        expr.find_location(),
        guard);
    }
  }
}

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

Function: goto_checkt::array_name

  Inputs:

 Outputs:

 Purpose:

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

std::string goto_checkt::array_name(const exprt &expr)
{
  return ::array_name(ns, expr);
}

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

Function: goto_checkt::bounds_check

  Inputs:

 Outputs:

 Purpose:

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

void goto_checkt::bounds_check(
  const exprt &expr,
  const guardt &guard)
{
  if(!options.get_bool_option("bounds-check"))
    return;

  if(expr.id()!="index")
    return;
    
  if(expr.find("bounds_check").is_not_nil() &&
     !expr.get_bool("bounds_check"))
    return;

  if(expr.operands().size()!=2)
    throw "index takes two operands";

  typet array_type=expr.op0().type();

  if(array_type.id()=="pointer")
    return; // done by the pointer code
  else if(array_type.id()=="incomplete_array")
  {
    std::cerr << expr.pretty() << std::endl;
    throw "index got incomplete array";
  }
  else if(array_type.id()!="array")
    throw "bounds check expected array type, got "+array_type.id_string();

  std::string name=array_name(expr.op0());
  
  const exprt &index=expr.op1();

  if(index.type().id()!="unsignedbv")
  {
    // we undo typecasts to signedbv
    if(index.id()=="typecast" &&
       index.operands().size()==1 &&
       index.op0().type().id()=="unsignedbv")
    {
      // ok
    }
    else
    {
      exprt zero=gen_zero(index.type());

      if(zero.is_nil())
        throw "no zero constant of index type "+
          index.type().to_string();

      exprt inequality(">=", typet("bool"));
      inequality.copy_to_operands(index);
      inequality.move_to_operands(zero);

      add_guarded_claim(
        inequality,
        name+" lower bound",
        "array bounds",
        expr.find_location(),
        guard);
    }
  }

  {
    if(array_type.find("size").is_nil())
      throw "index array operand of wrong type";

    const exprt &size=(const exprt &)array_type.find("size");

    if(size.id()!="infinity")
    {
      exprt inequality("<", typet("bool"));
      inequality.copy_to_operands(index);
      inequality.copy_to_operands(size);

      // typecast size
      if(inequality.op1().type()!=inequality.op0().type())
        inequality.op1().make_typecast(inequality.op0().type());

      add_guarded_claim(
        inequality,
        name+" upper bound",
        "array bounds",
        expr.find_location(),
        guard);
    }
  }
}

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

Function: goto_checkt::array_size_check

  Inputs:

 Outputs:

 Purpose:

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

#if 0
void goto_checkt::array_size_check(
  const exprt &expr,
  const irept &location)
{
  if(expr.type().id()=="array")
  {
    const exprt &size=(exprt &)expr.type().find("size");

    if(size.type().id()=="unsignedbv")
    {
      // nothing to do
    }
    else
    {
      exprt zero=gen_zero(size.type());

      if(zero.is_nil())
        throw "no zero constant of index type "+
          size.type().to_string();

      exprt inequality(">=", typet("bool"));
      inequality.copy_to_operands(size);
      inequality.move_to_operands(zero);

      std::string name=array_name(expr);

      guardt guard;
      add_guarded_claim(inequality, name+" size", location, guard);
    }
  }
}
#endif

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

Function: goto_checkt::add_guarded_claim

  Inputs:

 Outputs:

 Purpose:

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

void goto_checkt::add_guarded_claim(
  const exprt &_expr,
  const std::string &comment,
  const std::string &property,
  const locationt &location,
  const guardt &guard)
{
  bool all_claims=options.get_bool_option("all-claims");
  exprt expr(_expr);

  // first try simplifier on it
  if(options.get_bool_option("simplify"))
  {
    base_type(expr, ns);
    simplify(expr);
  }

  if(!all_claims && expr.is_true())
    return;

  // add the guard
  exprt guard_expr;
  guard.expr(guard_expr);

  exprt new_expr;

  if(guard_expr.is_true())
    new_expr.swap(expr);
  else
  {
    new_expr=exprt("=>", typet("bool"));
    new_expr.move_to_operands(guard_expr, expr);
  }
  
  if(assertions.insert(new_expr).second)
  {
    goto_programt::targett t=new_code.add_instruction(ASSERT);

    t->guard.swap(new_expr);
    t->location=location;
    t->location.set("comment", comment);
    t->location.set("property", property);
  }
}

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

Function: goto_checkt::check_rec

  Inputs:

 Outputs:

 Purpose:

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

void goto_checkt::check_rec(
  const exprt &expr,
  guardt &guard)
{
  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++)
    {
      const exprt &op=expr.operands()[i];

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

      check_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;
    }

    check_rec(expr.op0(), guard);

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

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

    return;
  }

  forall_operands(it, expr)
    check_rec(*it, guard);

  if(expr.id()=="index")
    bounds_check(expr, guard);
  else if(expr.id()=="/" || expr.id()=="mod")
    div_by_zero_check(expr, guard);
  else if(expr.id()=="+" || expr.id()=="-" ||
          expr.id()=="*" ||
          expr.id()=="unary-" ||
          expr.id()=="typecast")
    overflow_check(expr, guard);
  else if(expr.id()=="<=" || expr.id()=="<" ||
          expr.id()==">=" || expr.id()==">")
    pointer_rel_check(expr, guard);
}

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

Function: goto_checkt::check

  Inputs:

 Outputs:

 Purpose:

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

void goto_checkt::check(const exprt &expr)
{
  guardt guard;
  check_rec(expr, guard);
}

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

Function: goto_checkt::goto_check

  Inputs:

 Outputs:

 Purpose:

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

void goto_checkt::goto_check(goto_programt &goto_program)
{
  for(goto_programt::instructionst::iterator
      it=goto_program.instructions.begin();
      it!=goto_program.instructions.end();
      it++)
  {
    goto_programt::instructiont &i=*it;
    
    new_code.clear();
    assertions.clear();
    
    check(i.guard);

    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";

        check(i.code.op0());
        check(i.code.op1());
      }
      else if(statement=="functioncall")
      {
        forall_operands(it, i.code)
          check(*it);
      }
      else if(statement=="expression")
      {
        check(i.code);
      }
      else if(statement=="printf")
      {
        forall_operands(it, i.code)
          check(*it);
      }
    }
    else if(i.is_return())
    {
      if(i.code.operands().size()==1)
        check(i.code.op0());
    }
    
    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 -- make sure targets are not moved

    while(!new_code.instructions.empty())
    {
      goto_program.insert_swap(it, new_code.instructions.front());
      new_code.instructions.pop_front();
      it++;
    }
  }
}

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

Function: goto_check

  Inputs:

 Outputs:

 Purpose:

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

void goto_check(
  const namespacet &ns,
  const optionst &options,
  goto_programt &goto_program)
{
  goto_checkt goto_check(ns, options);
  goto_check.goto_check(goto_program);
}                    

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

Function: goto_check

  Inputs:

 Outputs:

 Purpose:

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

void goto_check(
  const namespacet &ns,
  const optionst &options,
  goto_functionst &goto_functions)
{
  goto_checkt goto_check(ns, options);
  
  for(goto_functionst::function_mapt::iterator
      it=goto_functions.function_map.begin();
      it!=goto_functions.function_map.end();
      it++)
  {
    goto_check.goto_check(it->second.body);
  }
}                    
