From df7de13be89bb8349e04c903c4bcdbe2fb97f907 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?David=20B=C3=BChler?= <david.buhler@cea.fr>
Date: Mon, 14 Sep 2020 16:50:40 +0200
Subject: [PATCH] [Eva] New type Eval.recursion to support recursion in
 abstract domains.

---
 src/plugins/value/engine/compute_functions.ml |  3 +-
 src/plugins/value/engine/recursion.ml         | 72 ++++++++++++++++++-
 src/plugins/value/engine/recursion.mli        |  5 ++
 src/plugins/value/engine/transfer_stmt.ml     |  2 +-
 src/plugins/value/eval.ml                     |  8 +++
 src/plugins/value/eval.mli                    | 15 +++-
 6 files changed, 100 insertions(+), 5 deletions(-)

diff --git a/src/plugins/value/engine/compute_functions.ml b/src/plugins/value/engine/compute_functions.ml
index b622130d6e7..6a721abfab9 100644
--- a/src/plugins/value/engine/compute_functions.ml
+++ b/src/plugins/value/engine/compute_functions.ml
@@ -329,7 +329,8 @@ module Make (Abstract: Abstractions.Eva) = struct
       Value_util.push_call_stack kf Kglobal;
       store_initial_state kf init_state;
       let call =
-        {kf; arguments = []; rest = []; return = None; recursive = false}
+        {kf; arguments = []; rest = []; return = None;
+         recursive = false; }
       in
       let final_result = compute_using_spec_or_body Kglobal call init_state in
       let final_states = final_result.Transfer.states in
diff --git a/src/plugins/value/engine/recursion.ml b/src/plugins/value/engine/recursion.ml
index 99d150435c7..c7a7dfe117a 100644
--- a/src/plugins/value/engine/recursion.ml
+++ b/src/plugins/value/engine/recursion.ml
@@ -32,7 +32,7 @@ open Cil_types
 let check_formals_non_referenced kf =
   let formals = Kernel_function.get_formals kf in
   if List.exists (fun vi -> vi.vaddrof) formals then
-    Value_parameters.error ~current:true ~once:true
+    Value_parameters.warning ~current:true ~once:true
       "function '%a' (involved in a recursive call) has a formal parameter \
        whose address is taken. Analysis may be unsound."
       Kernel_function.pretty kf
@@ -104,3 +104,73 @@ let empty_spec_for_recursive_call kf =
   let bhv = Cil.mk_behavior ~assigns ~name:Cil.default_behavior_name () in
   empty.spec_behavior <- [bhv];
   empty
+
+
+(* -------------------------------------------------------------------------- *)
+
+module CallDepth =
+  Datatype.Pair_with_collections (Kernel_function) (Datatype.Int)
+    (struct let module_name = "RecDepth" end)
+
+module VarCopies =
+  Datatype.List (Datatype.Pair (Cil_datatype.Varinfo) (Cil_datatype.Varinfo))
+
+module Vars = Datatype.Pair (VarCopies) (Datatype.List (Cil_datatype.Varinfo))
+
+module VarStack =
+  State_builder.Hashtbl
+    (CallDepth.Hashtbl)
+    (Vars)
+    (struct
+      let name = "Eva.Recursion.VarStack"
+      let dependencies = [ Ast.self ]
+      let size = 9
+    end)
+
+let copy_variable depth varinfo =
+  let name = Format.asprintf "\\copy<%s>[%i]" varinfo.vname depth
+  and typ = varinfo.vtype
+  and source = true
+  and temp = varinfo.vtemp
+  and referenced = varinfo.vreferenced
+  and ghost = varinfo.vghost
+  and loc = varinfo.vdecl in
+  Cil.makeVarinfo ~source ~temp ~referenced ~ghost ~loc false false name typ
+
+let copy_fresh_variable fundec depth varinfo =
+  let v = copy_variable depth varinfo in
+  Cil.refresh_local_name fundec v;
+  v
+
+let make_stack (kf, depth) =
+  let fundec =
+    try Kernel_function.get_definition kf
+    with Kernel_function.No_Definition -> assert false
+  in
+  let vars = Kernel_function.(get_formals kf @ get_locals kf) in
+  let reachable, withdrawal = List.partition (fun vi -> vi.vaddrof) vars in
+  let copy v = v, copy_fresh_variable fundec depth v in
+  let substitution = List.map copy reachable in
+  substitution, withdrawal
+
+let get_stack kf depth = VarStack.memo make_stack (kf, depth)
+
+let make_recursive_call kf =
+  let call_stack = Value_util.call_stack () in
+  let previous_calls = List.filter (fun (f, _) -> f == kf) call_stack in
+  let depth = List.length previous_calls in
+  let substitution, withdrawal = get_stack kf depth in
+  let base_of_varinfo (v1, v2) = Base.of_varinfo v1, Base.of_varinfo v2 in
+  let list_substitution = List.map base_of_varinfo substitution in
+  let base_substitution = Base.substitution_from_list list_substitution in
+  let list_withdrawal = List.map Base.of_varinfo withdrawal in
+  let base_withdrawal = Base.Hptset.of_list list_withdrawal in
+  Eval.{ depth; substitution; base_substitution; withdrawal; base_withdrawal; }
+
+let revert_recursion recursion =
+  let revert (v1, v2) = v2, v1 in
+  let substitution = List.map revert recursion.Eval.substitution in
+  let base_of_varinfo (v1, v2) = Base.of_varinfo v1, Base.of_varinfo v2 in
+  let list = List.map base_of_varinfo substitution in
+  let base_substitution = Base.substitution_from_list list in
+  Eval.{ recursion with substitution; base_substitution; }
diff --git a/src/plugins/value/engine/recursion.mli b/src/plugins/value/engine/recursion.mli
index 3a3255c47d1..80c28c1cd88 100644
--- a/src/plugins/value/engine/recursion.mli
+++ b/src/plugins/value/engine/recursion.mli
@@ -32,3 +32,8 @@ val empty_spec_for_recursive_call: kernel_function -> spec
 (** Generate an empty spec [assigns \nothing] or
     [assigns \result \from \nothing], to be used to "approximate" the
     results of a recursive call. *)
+
+(** TODO *)
+val make_recursive_call: kernel_function -> Eval.recursion
+
+val revert_recursion: Eval.recursion -> Eval.recursion
diff --git a/src/plugins/value/engine/transfer_stmt.ml b/src/plugins/value/engine/transfer_stmt.ml
index 040629aa886..8e574e39a48 100644
--- a/src/plugins/value/engine/transfer_stmt.ml
+++ b/src/plugins/value/engine/transfer_stmt.ml
@@ -556,7 +556,7 @@ module Make (Abstract: Abstractions.Eva) = struct
         let arguments = List.rev arguments in
         arguments, rest
     in
-    {kf; arguments; rest; return; recursive}
+    {kf; arguments; rest; return; recursive; }
 
   let make_call ~subdivnb kf arguments valuation state =
     (* Evaluate the arguments of the call. *)
diff --git a/src/plugins/value/eval.ml b/src/plugins/value/eval.ml
index 8aab315162a..ab4e77305f4 100644
--- a/src/plugins/value/eval.ml
+++ b/src/plugins/value/eval.ml
@@ -248,6 +248,14 @@ type ('loc, 'value) call = {
   recursive: bool;
 }
 
+type recursion = {
+  depth: int;
+  substitution: (varinfo * varinfo) list;
+  base_substitution: Base.substitution;
+  withdrawal: varinfo list;
+  base_withdrawal: Base.Hptset.t;
+}
+
 type cacheable = Cacheable | NoCache | NoCacheCallers
 
 (*
diff --git a/src/plugins/value/eval.mli b/src/plugins/value/eval.mli
index e050aae7848..ef1a1937757 100644
--- a/src/plugins/value/eval.mli
+++ b/src/plugins/value/eval.mli
@@ -215,7 +215,6 @@ type 'location logic_assign =
 (**                       {2 Interprocedural Analysis }                       *)
 (* -------------------------------------------------------------------------- *)
 
-
 (** Argument of a function call. *)
 type ('loc, 'value) argument = {
   formal: varinfo;          (** The formal argument of the called function. *)
@@ -235,7 +234,19 @@ type ('loc, 'value) call = {
   recursive: bool;
 }
 
-(* Can the results of a function call be cached with memexec? *)
+(** Information needed to interpret a recursive call.
+    The local variables and formal parameters of different recursive calls
+    should not be mixed up. Those of the current call must be temporary withdraw
+    or replaced from the domain states before starting the new recursive call. *)
+type recursion = {
+  depth: int;
+  substitution: (varinfo * varinfo) list;
+  base_substitution: Base.substitution;
+  withdrawal: varinfo list;
+  base_withdrawal: Base.Hptset.t;
+}
+
+(** Can the results of a function call be cached with memexec? *)
 type cacheable =
   | Cacheable      (** Functions whose result can be safely cached *)
   | NoCache        (** Functions whose result should not be cached, but for
-- 
GitLab