(*
 *
 * Copyright (c) 2001-2002, 
 *  George C. Necula    <necula@cs.berkeley.edu>
 *  Scott McPeak        <smcpeak@cs.berkeley.edu>
 *  Wes Weimer          <weimer@cs.berkeley.edu>
 * All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 * 1. Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright
 * notice, this list of conditions and the following disclaimer in the
 * documentation and/or other materials provided with the distribution.
 *
 * 3. The names of the contributors may not be used to endorse or promote
 * products derived from this software without specific prior written
 * permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
 * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
 * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
 * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 *)

(* 
 * Heapify: a program transform that looks over functions, finds those
 * that have local (stack) variables that contain arrays, puts all such
 * local variables into a single heap allocated structure, changes all
 * accesses to such variables into accesses to fields of that structure
 * and frees the structure on return. 
 *)
open Cil

(* utilities that should be in Cil.ml *)
(* sfg: this function appears to never be called *)
let mkSimpleField ci fn ft fl =
  { fcomp = ci ; fname = fn ; ftype = ft ; fbitfield = None ; fattr = [];
    floc = fl }


(* actual Heapify begins *)

let heapifyNonArrays = ref false

(* Does this local var contain an array? *)
let rec containsArray (t:typ) : bool =  (* does this type contain an array? *)
  match unrollType t with
    TArray _ -> true
  | TComp(ci, _) -> (* look at the types of the fields *)
      List.exists (fun fi -> containsArray fi.ftype) ci.cfields
  | _ -> 
    (* Ignore other types, including TInt and TPtr.  We don't care whether
       there are arrays in the base types of pointers; only about whether
       this local variable itself needs to be moved to the heap. *)
   false


class heapifyModifyVisitor big_struct big_struct_fields varlist free 
    (currentFunction: fundec) = object(self)
  inherit nopCilVisitor  (* visit lvalues and statements *)
  method vlval l = match l with (* should we change this one? *)
    Var(vi),vi_offset when List.mem_assoc vi varlist -> (* check list *)
      let i = List.assoc vi varlist in (* find field offset *)
      let big_struct_field = List.nth big_struct_fields i in
      let new_lval = Mem(Lval(big_struct, NoOffset)),
	Field(big_struct_field,vi_offset) in (* rewrite the lvalue *)
      ChangeDoChildrenPost(new_lval, (fun l -> l))
  | _ -> DoChildren (* ignore other lvalues *)
  method vstmt s = match s.skind with (* also rewrite the return *)
    Return(None,loc) -> 
      let free_instr = Call(None,free,[Lval(big_struct,NoOffset)],loc) in
      self#queueInstr [free_instr]; (* insert free_instr before the return *)
      DoChildren
  | Return(Some exp ,loc) ->
      (* exp may depend on big_struct, so evaluate it before calling free. 
       * This becomes:  tmp = exp; free(big_struct); return tmp; *)
      let exp_new = visitCilExpr (self :> cilVisitor) exp in
      let ret_tmp = makeTempVar currentFunction (typeOf exp_new) in
      let eval_ret_instr = Set(var ret_tmp, exp_new, loc) in
      let free_instr = Call(None,free,[Lval(big_struct,NoOffset)],loc) in
      (* insert the instructions before the return *)
      self#queueInstr [eval_ret_instr; free_instr];
      s.skind <- (Return(Some(Lval(var ret_tmp)), loc));
      DoChildren
  | _ -> DoChildren (* ignore other statements *)
end
    
class heapifyAnalyzeVisitor f alloc free = object 
  inherit nopCilVisitor (* only look at function bodies *)
  method vglob gl = match gl with
    GFun(fundec,funloc) -> 
      let counter = ref 0 in (* the number of local vars containing arrays *)
      let varlist = ref [] in  (* a list of (var,id) pairs, in reverse order *)
      List.iter (fun vi ->  
         (* find all local vars with arrays.  If the user requests it,
            we also look for non-array vars whose address is taken. *)
        if (containsArray vi.vtype) || (vi.vaddrof && !heapifyNonArrays)
        then begin
          varlist := (vi,!counter) :: !varlist ; (* add it to the list *)
          incr counter (* put the next such var in the next slot *)
        end
        ) fundec.slocals ; 
      if (!varlist <> []) then begin (* some local vars contain arrays *)
        let name = (fundec.svar.vname ^ "_heapify") in
        let ci = mkCompInfo true name (* make a big structure *)
	    (fun _ -> List.rev_map (* reverse the list to fix the order *)
                (* each local var becomes a field *)
		(fun (vi,i) -> vi.vname,vi.vtype,None,[],vi.vdecl) !varlist) [] in
        let vi = makeLocalVar fundec name (TPtr(TComp(ci,[]),[])) in
        let modify = new heapifyModifyVisitor (Var(vi)) ci.cfields
	    !varlist free fundec in (* rewrite accesses to local vars *)
        fundec.sbody <- visitCilBlock modify fundec.sbody ;
        let alloc_stmt = mkStmt (* allocate the big struct on the heap *)
            (Instr [Call(Some(Var(vi),NoOffset), alloc, 
			 [SizeOf(TComp(ci,[]))],funloc)]) in
        fundec.sbody.bstmts <- alloc_stmt :: fundec.sbody.bstmts ; 
	fundec.slocals <- List.filter (fun vi -> (* remove local vars *)
	  not (List.mem_assoc vi !varlist)) fundec.slocals ; 
	let typedec = (GCompTag(ci,funloc)) in (* declare the big struct *)
        ChangeTo([typedec ; GFun(fundec,funloc)])  (* done! *)
      end else
	DoChildren	(* ignore everything else *)
  | _ -> DoChildren
end
    
let heapify (f : file) (alloc : exp) (free : exp)  =
  visitCilFile (new heapifyAnalyzeVisitor f alloc free) f;
  f

(* heapify code ends here *)

let default_heapify (f : file) =
  let alloc_fun = emptyFunction "malloc" in
  let free_fun = emptyFunction "free" in
  let alloc_exp = (Lval((Var(alloc_fun.svar)),NoOffset)) in
  let free_exp = (Lval((Var(free_fun.svar)),NoOffset)) in
  ignore (heapify f alloc_exp free_exp)
    
(* StackGuard clone *)

class sgModifyVisitor restore_ra_stmt = object
	inherit nopCilVisitor
  method vstmt s = match s.skind with (* also rewrite the return *)
    Return(_,loc) -> let new_block = mkBlock [restore_ra_stmt ; s] in 
			ChangeTo(mkStmt (Block(new_block)))  
	| _ -> DoChildren (* ignore other statements *)
end

class sgAnalyzeVisitor f push pop get_ra set_ra = object
  inherit nopCilVisitor
  method vfunc fundec = 
    let needs_guarding = List.fold_left 
	(fun acc vi -> acc || containsArray vi.vtype) 
	false fundec.slocals in
    if needs_guarding then begin
      let ra_tmp = makeLocalVar fundec "return_address" voidPtrType in
      let ra_exp = Lval(Var(ra_tmp),NoOffset) in 
      let save_ra_stmt = mkStmt (* save the current return address *)
	  (Instr [Call(Some(Var(ra_tmp),NoOffset), get_ra, [], locUnknown) ;
		   Call(None, push, [ra_exp], locUnknown)]) in
      let restore_ra_stmt = mkStmt (* restore the old return address *)
	  (Instr [Call(Some(Var(ra_tmp),NoOffset), pop, [], locUnknown) ;
		   Call(None, set_ra, [ra_exp], locUnknown)]) in
      let modify = new sgModifyVisitor restore_ra_stmt in
      fundec.sbody <- visitCilBlock modify fundec.sbody ;
      fundec.sbody.bstmts <- save_ra_stmt :: fundec.sbody.bstmts ;
      ChangeTo(fundec)  (* done! *)
    end else DoChildren
end
          
let stackguard (f : file) (push : exp) (pop : exp) 
    (get_ra : exp) (set_ra : exp) = 
  visitCilFileSameGlobals (new sgAnalyzeVisitor f push pop get_ra set_ra) f;
  f
    (* stackguard code ends *)
    
let default_stackguard (f : file) =
  let expify fundec = Lval(Var(fundec.svar),NoOffset) in
  let push = expify (emptyFunction "stackguard_push") in
  let pop = expify (emptyFunction "stackguard_pop") in
  let get_ra = expify (emptyFunction "stackguard_get_ra") in
  let set_ra = expify (emptyFunction "stackguard_set_ra") in
  let global_decl = 
"extern void * stackguard_get_ra();
extern void stackguard_set_ra(void *new_ra);
/* You must provide an implementation for functions that get and set the
 * return address. Such code is unfortunately architecture specific.
 */
struct stackguard_stack {
  void * data;
  struct stackguard_stack * next;
} * stackguard_stack;

void stackguard_push(void *ra) {
  void * old = stackguard_stack;
  stackguard_stack = (struct stackguard_stack *)
    malloc(sizeof(stackguard_stack));
  stackguard_stack->data = ra;
  stackguard_stack->next = old;
}

void * stackguard_pop() {
  void * ret = stackguard_stack->data;
  void * next = stackguard_stack->next;
  free(stackguard_stack);
  stackguard_stack->next = next;
  return ret;
}" in
    f.globals <- GText(global_decl) :: f.globals ;
    ignore (stackguard f push pop get_ra set_ra )
      
      
let feature1 : featureDescr = 
  { fd_name = "stackGuard";
    fd_enabled = Cilutil.doStackGuard;
    fd_description = "instrument function calls and returns to maintain a\n\t\t\t\tseparate stack for return addresses" ;
    fd_extraopt = [];
    fd_doit = (function (f: file) -> default_stackguard f);
    fd_post_check = true;
  } 
let feature2 : featureDescr = 
  { fd_name = "heapify";
    fd_enabled = Cilutil.doHeapify;
    fd_description = "move stack-allocated arrays to the heap" ;
    fd_extraopt = [
      "--heapifyAll", Arg.Set heapifyNonArrays,
      " When using heapify, move all local vars whose address is taken,\n\t\t\t\tnot just arrays.";
    ];
    fd_doit = (function (f: file) -> default_heapify f);
    fd_post_check = true;
  } 
      





