/*
** Splint - annotation-assisted static program checker
** Copyright (C) 1994-2003 University of Virginia,
**         Massachusetts Institute of Technology
**
** This program is free software; you can redistribute it and/or modify it
** under the terms of the GNU General Public License as published by the
** Free Software Foundation; either version 2 of the License, or (at your
** option) any later version.
** 
** This program is distributed in the hope that it will be useful, but
** WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
** General Public License for more details.
** 
** The GNU General Public License is available from http://www.gnu.org/ or
** the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
** MA 02111-1307, USA.
**
** For information on splint: info@splint.org
** To report a bug: splint-bug@splint.org
** For more information: http://www.splint.org
*/

# include "splintMacros.nf"
# include "basic.h"

static /*@observer@*/ cstring stateAction_unparse (stateAction p_sa) /*@*/ ;
static /*@only@*/ /*@notnull@*/ stateInfo stateInfo_makeRefLoc (
  /*@exposed@*/ sRef p_ref, fileloc p_loc, stateAction p_action) /*@*/ ; 


void stateInfo_free (/*@only@*/ stateInfo a)
{
  if (a != NULL)
    {
      fileloc_free (a->loc);
      /* shouldn't we free a->previous? most likely memory leak */
      sfree (a);
    }
}

/*@only@*/ stateInfo stateInfo_update (/*@only@*/ stateInfo old, stateInfo newinfo)
     /*
     ** returns an stateInfo with the same value as new.  May reuse the
     ** storage of old.  (i.e., same effect as copy, but more
     ** efficient.)
     */
{
  if (old == NULL) 
    {
      DPRINTF (("Update state ==> %s", stateInfo_unparse (newinfo)));
      return stateInfo_copy (newinfo);
    }
  else if (newinfo == NULL)
    {
      stateInfo_free (old);
      return NULL;
    }
  else
    {
      if (fileloc_equal (old->loc, newinfo->loc)
	  && old->action == newinfo->action
	  /*@-abstractcompare@*/ && old->ref == newinfo->ref /*@=abstractcompare@*/)
	{
	  /*
	  ** Duplicate (change through alias most likely)
	  ** don't add this info
	  */
	  
	  return old;
	}
      else
	{
	  stateInfo snew = stateInfo_makeRefLoc (newinfo->ref, 
						 newinfo->loc, newinfo->action);
	  llassert (snew->previous == NULL);
	  snew->previous = old;
	  DPRINTF (("Update state ==> %s", stateInfo_unparse (snew)));
	  return snew;
	}
    }
}

static /*@observer@*/ stateInfo stateInfo_sort (/*@temp@*/ stateInfo stinfo)
     /* Sorts in reverse location order */
{
  DPRINTF (("Sorting: %s", stateInfo_unparse (stinfo)));

  if (stinfo == NULL || stinfo->previous == NULL) 
    {
      return stinfo;
    }
  else
    {
      stateInfo snext = stateInfo_sort (stinfo->previous);
      stateInfo sfirst = snext;

      DPRINTF (("stinfo/sext: %s // %s", stateInfo_unparse (stinfo), stateInfo_unparse (snext)));
      llassert (snext != NULL);

      if (!fileloc_lessthan (stinfo->loc, snext->loc))
	{
	  /*@i2@*/ stinfo->previous = sfirst; /* spurious? */
	  DPRINTF (("Sorted ==> %s", stateInfo_unparse (stinfo)));
	  /*@i2@*/ return stinfo; /* spurious? */
	}
      else
	{
	  while (snext != NULL && fileloc_lessthan (stinfo->loc, snext->loc))
	    {
	      /*
	      ** swap the order
	      */
	      fileloc tloc = snext->loc;
	      stateAction taction = snext->action;
	      sRef tref = snext->ref;
	      
	      DPRINTF (("in while: stinfo/sext: %s // %s", stateInfo_unparse (stinfo), stateInfo_unparse (snext)));
      
	      snext->loc = stinfo->loc;
	      snext->action = stinfo->action;
	      /*@-modobserver@*/
	      snext->ref = stinfo->ref; /* Doesn't actually modifie sfirst */ 
	      /*@=modobserver@*/
	      
	      stinfo->loc = tloc;
	      stinfo->action = taction;
	      stinfo->ref = tref;
	      /*@-mustfreeonly@*/
	      stinfo->previous = snext->previous;
	      /*@=mustfreeonly@*/
	      snext = snext->previous;
	      DPRINTF (("in while: stinfo/sext: %s // %s", stateInfo_unparse (stinfo), stateInfo_unparse (snext)));
	    }
	  
	  DPRINTF (("Sorted ==> %s", stateInfo_unparse (sfirst)));
	  /*@-compmempass@*/
	  return sfirst;
	  /*@=compmempass@*/
	}
    }
}

/*@only@*/ stateInfo 
stateInfo_updateLoc (/*@only@*/ stateInfo old, stateAction action, fileloc loc)
{
  if (fileloc_isUndefined (loc)) {
    loc = fileloc_copy (g_currentloc);
  }

  if (old != NULL && fileloc_equal (old->loc, loc) && old->action == action)
    {
      /*
      ** Duplicate (change through alias most likely)
      ** don't add this info
      */
      
      return old;
    }
  else
    {
      stateInfo snew = stateInfo_makeLoc (loc, action);
      llassert (snew->previous == NULL);
      snew->previous = old;
      DPRINTF (("Update state ==> %s", stateInfo_unparse (snew)));
      return snew;
    }
}

/*@only@*/ stateInfo 
stateInfo_updateRefLoc (/*@only@*/ stateInfo old, /*@exposed@*/ sRef ref, 
			stateAction action, fileloc loc)
{
  if (fileloc_isUndefined (loc)) {
    loc = fileloc_copy (g_currentloc);
  }

  if (old != NULL && fileloc_equal (old->loc, loc)
      && old->action == action
      /*@-abstractcompare*/ && old->ref == ref /*@=abstractcompare@*/)
    {
      /*
      ** Duplicate (change through alias most likely)
      ** don't add this info
      */
      
      return old;
    }
  else
    {
      stateInfo snew = stateInfo_makeRefLoc (ref, loc, action);
      llassert (snew->previous == NULL);
      snew->previous = old;
      DPRINTF (("Update state ==> %s", stateInfo_unparse (snew)));
      return snew;
    }
}

/*@only@*/ stateInfo stateInfo_copy (stateInfo a)
{
  if (a == NULL)
    {
      return NULL;
    }
  else
    {
      stateInfo ret = (stateInfo) dmalloc (sizeof (*ret));
      
      ret->loc = fileloc_copy (a->loc); /*< should report bug without copy! >*/
      ret->ref = a->ref;
      ret->action = a->action;
      ret->previous = stateInfo_copy (a->previous); 

      return ret;
    }
}

/*@only@*/ /*@notnull@*/ stateInfo
stateInfo_currentLoc (void)
{
  return stateInfo_makeLoc (g_currentloc, SA_DECLARED);
}

/*@only@*/ /*@notnull@*/ stateInfo
stateInfo_makeLoc (fileloc loc, stateAction action)
{
  return stateInfo_makeRefLoc (sRef_undefined, loc, action);
}

static /*@only@*/ /*@notnull@*/ stateInfo
stateInfo_makeRefLoc (/*@exposed@*/ sRef ref, fileloc loc, stateAction action)
     /*@post:isnull result->previous@*/
{
  stateInfo ret = (stateInfo) dmalloc (sizeof (*ret));

  if (fileloc_isUndefined (loc)) {
    ret->loc = fileloc_copy (g_currentloc);
  } else {
    ret->loc = fileloc_copy (loc);
  }

  ret->ref = ref;
  ret->action = action;
  ret->previous = stateInfo_undefined;

  DPRINTF (("Make loc ==> %s", stateInfo_unparse (ret)));
  return ret;
}

/*@only@*/ cstring
stateInfo_unparse (stateInfo s)
{
  cstring res = cstring_makeLiteral ("");

  while (stateInfo_isDefined (s)) {
    res = message ("%q%q: ", res, fileloc_unparse (s->loc));
    if (sRef_isValid (s->ref)) {
      res = message ("%q through alias %q ", res, sRef_unparse (s->ref));
    }

    res = message ("%q%s; ", res, stateAction_unparse (s->action)); 
    s = s->previous;
  }

  return res;
}

fileloc stateInfo_getLoc (stateInfo info)
{
    if (stateInfo_isDefined (info)) 
      {
	return info->loc;
      }
    
    return fileloc_undefined;
}

stateAction stateAction_fromNState (nstate ns)
{
  switch (ns) 
    {
    case NS_ERROR:
    case NS_UNKNOWN:
    default:
      return SA_UNKNOWN;
    case NS_NOTNULL:
    case NS_MNOTNULL:
      return SA_BECOMESNONNULL;
    case NS_RELNULL:
    case NS_CONSTNULL:
      return SA_DECLARED;
    case NS_POSNULL:
      return SA_BECOMESPOSSIBLYNULL;
    case NS_DEFNULL:
      return SA_BECOMESNULL;
    case NS_ABSNULL:
      return SA_BECOMESPOSSIBLYNULL;
    }
}

stateAction stateAction_fromExkind (exkind ex)
{
  switch (ex) 
    {
    case XO_UNKNOWN:
    case XO_NORMAL:
      return SA_UNKNOWN;
    case XO_EXPOSED:
      return SA_EXPOSED;
    case XO_OBSERVER:
      return SA_OBSERVER;
    }

  BADBRANCH;
  /*@notreached@*/ return SA_UNKNOWN;
}

stateAction stateAction_fromAlkind (alkind ak)
{
  switch (ak)
    {
    case AK_UNKNOWN:
    case AK_ERROR:
      return SA_UNKNOWN;
    case AK_ONLY:
      return SA_ONLY;
    case AK_IMPONLY:
      return SA_IMPONLY;
    case AK_KEEP:
      return SA_KEEP;
    case AK_KEPT:
      return SA_KEPT;
    case AK_TEMP:
      return SA_TEMP;
    case AK_IMPTEMP:
      return SA_IMPTEMP;
    case AK_SHARED:
      return SA_SHARED;
    case AK_UNIQUE:
    case AK_RETURNED:
      return SA_DECLARED;
    case AK_FRESH:
      return SA_FRESH;
    case AK_STACK:
      return SA_XSTACK;
    case AK_REFCOUNTED:
      return SA_REFCOUNTED;
    case AK_REFS:
      return SA_REFS;
    case AK_KILLREF:
      return SA_KILLREF;
    case AK_NEWREF:
      return SA_NEWREF;
    case AK_OWNED:
      return SA_OWNED;
    case AK_DEPENDENT:
      return SA_DEPENDENT;
    case AK_IMPDEPENDENT:
      return SA_IMPDEPENDENT;
    case AK_STATIC:
      return SA_STATIC;
    case AK_LOCAL:
      return SA_LOCAL;
    }

  BADBRANCH;
  /*@notreached@*/ return SA_UNKNOWN;
}

stateAction stateAction_fromSState (sstate ss)
{
  switch (ss) 
    {
    case SS_UNKNOWN: return SA_DECLARED;
    case SS_UNUSEABLE: return SA_KILLED;
    case SS_UNDEFINED: return SA_UNDEFINED;
    case SS_MUNDEFINED: return SA_MUNDEFINED;
    case SS_ALLOCATED: return SA_ALLOCATED;
    case SS_PDEFINED: return SA_PDEFINED;
    case SS_DEFINED: return SA_DEFINED;
    case SS_PARTIAL: return SA_PDEFINED;
    case SS_DEAD: return SA_RELEASED;
    case SS_HOFFA: return SA_PKILLED;
    case SS_SPECIAL: return SA_DECLARED;
    case SS_RELDEF: return SA_DECLARED;
    default:
      llbug (message ("Unexpected sstate: %s", sstate_unparse (ss)));
      /*@notreached@*/ return SA_UNKNOWN;
    }
}

static /*@observer@*/ cstring stateAction_unparse (stateAction sa)
{
  switch (sa) 
    {
    case SA_UNKNOWN: return cstring_makeLiteralTemp ("changed <unknown modification>");
    case SA_CHANGED: return cstring_makeLiteralTemp ("changed");

    case SA_CREATED: return cstring_makeLiteralTemp ("created");
    case SA_DECLARED: return cstring_makeLiteralTemp ("declared");
    case SA_DEFINED: return cstring_makeLiteralTemp ("defined");
    case SA_PDEFINED: return cstring_makeLiteralTemp ("partially defined");
    case SA_RELEASED: return cstring_makeLiteralTemp ("released");
    case SA_ALLOCATED: return cstring_makeLiteralTemp ("allocated");
    case SA_KILLED: return cstring_makeLiteralTemp ("released");
    case SA_PKILLED: return cstring_makeLiteralTemp ("possibly released");
    case SA_MERGED: return cstring_makeLiteralTemp ("merged");
    case SA_UNDEFINED: return cstring_makeLiteralTemp ("becomes undefined");
    case SA_MUNDEFINED: return cstring_makeLiteralTemp ("possibly undefined");

    case SA_SHARED: return cstring_makeLiteralTemp ("becomes shared");
    case SA_ONLY: return cstring_makeLiteralTemp ("becomes only");
    case SA_IMPONLY: return cstring_makeLiteralTemp ("becomes implicitly only");
    case SA_OWNED: return cstring_makeLiteralTemp ("becomes owned");
    case SA_DEPENDENT: return cstring_makeLiteralTemp ("becomes dependent");
    case SA_IMPDEPENDENT: return cstring_makeLiteralTemp ("becomes implicitly dependent");
    case SA_KEPT: return cstring_makeLiteralTemp ("becomes kept");
    case SA_KEEP: return cstring_makeLiteralTemp ("becomes keep");
    case SA_FRESH: return cstring_makeLiteralTemp ("becomes fresh");
    case SA_TEMP: return cstring_makeLiteralTemp ("becomes temp");
    case SA_IMPTEMP: return cstring_makeLiteralTemp ("becomes implicitly temp");
    case SA_XSTACK: return cstring_makeLiteralTemp ("becomes stack-allocated storage");
    case SA_STATIC: return cstring_makeLiteralTemp ("becomes static");
    case SA_LOCAL: return cstring_makeLiteralTemp ("becomes local");

    case SA_REFCOUNTED: return cstring_makeLiteralTemp ("becomes refcounted");
    case SA_REFS: return cstring_makeLiteralTemp ("becomes refs");
    case SA_NEWREF: return cstring_makeLiteralTemp ("becomes newref");
    case SA_KILLREF: return cstring_makeLiteralTemp ("becomes killref");

    case SA_OBSERVER: return cstring_makeLiteralTemp ("becomes observer");
    case SA_EXPOSED: return cstring_makeLiteralTemp ("becomes exposed");

    case SA_BECOMESNULL: return cstring_makeLiteralTemp ("becomes null");
    case SA_BECOMESNONNULL: return cstring_makeLiteralTemp ("becomes non-null");
    case SA_BECOMESPOSSIBLYNULL: return cstring_makeLiteralTemp ("becomes possibly null");
    default: BADBRANCHRET (cstring_undefined);
    } 
}

void stateInfo_display (stateInfo s, cstring sname)
{
  bool showdeep = context_flagOn (FLG_SHOWDEEPHISTORY, g_currentloc);

  s = stateInfo_sort (s);
  
  while (stateInfo_isDefined (s))
    {
      cstring msg = message ("%s%s", sname, stateAction_unparse (s->action)); 
      
      if (sRef_isValid (s->ref)) {
	msg = message ("%q (through alias %q)", msg, sRef_unparse (s->ref));
      }
      
      llgenindentmsg (msg, s->loc);

      if (!showdeep) {
	break;
      }

      s = s->previous;
    }

  cstring_free (sname);
}


