From fcf35a4c86dcceb8e3150aa7526cc7caf3c538a8 Mon Sep 17 00:00:00 2001
From: Allan Blanchard <allan.blanchard@cea.fr>
Date: Thu, 21 Jan 2021 14:24:53 +0100
Subject: [PATCH] [wp] Extend -wp-unfold-assigns to handle ranges

---
 src/plugins/wp/LogicSemantics.ml              | 81 ++++++++++++++++++-
 src/plugins/wp/Sigs.ml                        |  3 +-
 src/plugins/wp/cfgWP.ml                       |  3 +-
 .../oracle/unfold_assigns.0.res.oracle        | 41 ++++++++++
 .../oracle/unfold_assigns.1.res.oracle        | 37 +++++++++
 .../wp/tests/wp_plugin/unfold_assigns.i       |  9 +++
 6 files changed, 168 insertions(+), 6 deletions(-)

diff --git a/src/plugins/wp/LogicSemantics.ml b/src/plugins/wp/LogicSemantics.ml
index 682e9adb223..f2bbd01805f 100644
--- a/src/plugins/wp/LogicSemantics.ml
+++ b/src/plugins/wp/LogicSemantics.ml
@@ -1082,12 +1082,85 @@ struct
   (* --- CheckAssigns                                                       --- *)
   (* -------------------------------------------------------------------------- *)
 
-  let check_assigns sigma ~written ~assignable =
+  let rec decompose_region (obj, sloc) =
+    let is_primitive_int t = Lang.F.is_int t && Lang.F.is_primitive t in
+    match sloc with
+    | Sloc loc ->
+        begin match obj with
+          | C_pointer _ | C_int _ | C_float _
+          | C_comp { cfields = None } | C_array { arr_flat = None } ->
+              [obj, sloc]
+
+          | C_comp { cfields = Some fields } ->
+              let slocs_of_field f =
+                let obj = Ctypes.object_of f.ftype in
+                decompose_region (obj, (Sloc (M.field loc f)))
+              in
+              List.(concat (map slocs_of_field fields))
+
+          | C_array { arr_flat = Some { arr_size=len ; arr_cell=typ } } ->
+              let obj = Ctypes.object_of typ in
+              decompose_range loc obj Z.zero (Z.of_int (len - 1))
+        end
+
+    | Sarray (loc, obj, n) ->
+        decompose_range loc obj Z.zero (Z.of_int (n - 1))
+
+    | Srange (loc, obj, Some b, Some e)
+      when is_primitive_int b && is_primitive_int e ->
+        let b, e = match Lang.F.repr b, Lang.F.repr e with
+          | Kint b, Kint e -> b, e
+          | _ -> assert false (* by match guard *)
+        in
+        decompose_range loc obj b e
+
+    | _ ->
+        (* It is not trivial to go further: the list of regions depends on some
+           unknown values (length of the range, property, ...) *)
+        [obj, sloc]
+
+  and decompose_range l obj b e =
+    let rec generate_i l f i =
+      if Z.leq i b then l
+      else generate_i (f i :: l) f (Z.sub i Z.one)
+    in
+    let slocs_of_index i =
+      let l = M.shift l obj (e_zint i) in
+      decompose_region (obj, (Sloc l))
+    in
+    List.(concat (generate_i [] slocs_of_index e))
+
+  let rec assignable_region ~unfold reg assignable =
+    let check_inclusion reg =
+      p_any (L.included reg) assignable
+    in
+    if unfold then
+      (* This phase unfolds terms that cannot be "statically" unfolded *)
+      let logic_decompose = function
+        | (_, Sloc _) as s -> check_inclusion s
+        | (_, Sarray _) -> assert false (* decompose always remove Sarray *)
+        | (obj, Sdescr(xs, l, p)) ->
+            let sloc = obj, Sloc l in
+            p_forall xs
+              (p_imply p (assignable_region ~unfold:true sloc assignable))
+        | (_, Srange(l, obj, low, up)) ->
+            let x = Lang.freshvar ~basename:"k" Lang.t_int in
+            let k = e_var x in
+            let p = Vset.in_range k low up in
+            let sloc = obj, Sloc (M.shift l obj k) in
+            p_forall [x]
+              (p_imply p (assignable_region ~unfold:true sloc assignable))
+      in
+      p_all logic_decompose (decompose_region reg)
+    else
+      check_inclusion reg
+
+  let check_assigns ~unfold sigma ~written ~assignable =
     p_all
-      (fun seg ->
+      (fun reg ->
          p_imply
-           (p_not (L.invalid sigma seg))
-           (p_any (L.included seg) assignable)
+           (p_not (L.invalid sigma reg))
+           (assignable_region ~unfold reg assignable)
       ) (written : region)
 
 end
diff --git a/src/plugins/wp/Sigs.ml b/src/plugins/wp/Sigs.ml
index 1b594803754..7973ef55de1 100644
--- a/src/plugins/wp/Sigs.ml
+++ b/src/plugins/wp/Sigs.ml
@@ -812,7 +812,8 @@ sig
       Compute a formula that checks whether written locations are either
       invalid (at the given memory location)
       or included in some assignable region. *)
-  val check_assigns : sigma -> written:region -> assignable:region -> pred
+  val check_assigns :
+    unfold:bool -> sigma -> written:region -> assignable:region -> pred
 
 end
 
diff --git a/src/plugins/wp/cfgWP.ml b/src/plugins/wp/cfgWP.ml
index ebf53f21db4..4c8d46d3d00 100644
--- a/src/plugins/wp/cfgWP.ml
+++ b/src/plugins/wp/cfgWP.ml
@@ -517,7 +517,8 @@ struct
   (* -------------------------------------------------------------------------- *)
 
   let assigns_condition (region : L.region) (e:effect) : F.pred =
-    L.check_assigns e.e_valid ~written:region ~assignable:e.e_region
+    let unfold = Wp_parameters.UnfoldAssigns.get () in
+    L.check_assigns ~unfold e.e_valid ~written:region ~assignable:e.e_region
 
   exception COLLECTED
 
diff --git a/src/plugins/wp/tests/wp_plugin/oracle/unfold_assigns.0.res.oracle b/src/plugins/wp/tests/wp_plugin/oracle/unfold_assigns.0.res.oracle
index 60291308af6..8178bf2d553 100644
--- a/src/plugins/wp/tests/wp_plugin/oracle/unfold_assigns.0.res.oracle
+++ b/src/plugins/wp/tests/wp_plugin/oracle/unfold_assigns.0.res.oracle
@@ -97,6 +97,47 @@ Goal Assigns (file tests/wp_plugin/unfold_assigns.i, line 28) in 'NO_UNFOLD_OK_3
 Call Effect at line 30
 Prove: true.
 
+------------------------------------------------------------
+------------------------------------------------------------
+  Function PARTIAL_ASSIGNS
+------------------------------------------------------------
+
+Goal Assigns (file tests/wp_plugin/unfold_assigns.i, line 72) in 'PARTIAL_ASSIGNS':
+Call Effect at line 74
+Let a = shift_sint32(p, 0).
+Let x = 1 + n.
+Assume {
+  Have: 0 <= n.
+  Type: is_uint32(n).
+  (* Heap *)
+  Type: (region(p.base) <= 0) /\ linked(Malloc_0).
+  (* Goal *)
+  When: !invalid(Malloc_0, a, x).
+  (* Pre-condition *)
+  Have: 5 <= n.
+}
+Prove: (n <= 0) \/ included(a, x, shift_sint32(p, 1), n - 1) \/
+    included(a, x, shift_sint32(p, n), 1).
+
+------------------------------------------------------------
+
+Goal Assigns (file tests/wp_plugin/unfold_assigns.i, line 72) in 'PARTIAL_ASSIGNS':
+Call Effect at line 74
+Let a = shift_sint32(p, 0).
+Let x = 1 + n.
+Assume {
+  Have: 0 <= n.
+  Type: is_uint32(n).
+  (* Heap *)
+  Type: (region(p.base) <= 0) /\ linked(Malloc_0).
+  (* Goal *)
+  When: !invalid(Malloc_0, a, x).
+  (* Pre-condition *)
+  Have: 5 <= n.
+}
+Prove: (n <= 0) \/ included(a, x, shift_sint32(p, 1), n - 1) \/
+    included(a, x, shift_sint32(p, n), 1).
+
 ------------------------------------------------------------
 ------------------------------------------------------------
   Function USE_ASSIGN_UNFOLD_KO
diff --git a/src/plugins/wp/tests/wp_plugin/oracle/unfold_assigns.1.res.oracle b/src/plugins/wp/tests/wp_plugin/oracle/unfold_assigns.1.res.oracle
index f681ec0f923..cb15fe7af3f 100644
--- a/src/plugins/wp/tests/wp_plugin/oracle/unfold_assigns.1.res.oracle
+++ b/src/plugins/wp/tests/wp_plugin/oracle/unfold_assigns.1.res.oracle
@@ -79,6 +79,43 @@ Goal Assigns (file tests/wp_plugin/unfold_assigns.i, line 28) in 'NO_UNFOLD_OK_3
 Call Effect at line 30
 Prove: true.
 
+------------------------------------------------------------
+------------------------------------------------------------
+  Function PARTIAL_ASSIGNS
+------------------------------------------------------------
+
+Goal Assigns (file tests/wp_plugin/unfold_assigns.i, line 72) in 'PARTIAL_ASSIGNS':
+Call Effect at line 74
+Assume {
+  Type: is_uint32(n).
+  (* Heap *)
+  Type: (region(p.base) <= 0) /\ linked(Malloc_0).
+  (* Goal *)
+  When: (0 <= i) /\ (i <= n) /\
+      (!invalid(Malloc_0, shift_sint32(p, 0), 1 + n)).
+  (* Pre-condition *)
+  Have: 5 <= n.
+}
+Prove: (i = 0) \/ (n = i) \/
+    included(shift_sint32(p, i), 1, shift_sint32(p, 1), n - 1).
+
+------------------------------------------------------------
+
+Goal Assigns (file tests/wp_plugin/unfold_assigns.i, line 72) in 'PARTIAL_ASSIGNS':
+Call Effect at line 74
+Assume {
+  Type: is_uint32(n).
+  (* Heap *)
+  Type: (region(p.base) <= 0) /\ linked(Malloc_0).
+  (* Goal *)
+  When: (0 <= i) /\ (i <= n) /\
+      (!invalid(Malloc_0, shift_sint32(p, 0), 1 + n)).
+  (* Pre-condition *)
+  Have: 5 <= n.
+}
+Prove: (i = 0) \/ (n = i) \/
+    included(shift_sint32(p, i), 1, shift_sint32(p, 1), n - 1).
+
 ------------------------------------------------------------
 ------------------------------------------------------------
   Function USE_ASSIGN_UNFOLD_KO
diff --git a/src/plugins/wp/tests/wp_plugin/unfold_assigns.i b/src/plugins/wp/tests/wp_plugin/unfold_assigns.i
index 54ba848675c..24c9a9347bd 100644
--- a/src/plugins/wp/tests/wp_plugin/unfold_assigns.i
+++ b/src/plugins/wp/tests/wp_plugin/unfold_assigns.i
@@ -64,3 +64,12 @@ void ASSIGN_NO_UNFOLD_KO(struct S *s) {
   struct S p = { 0,1 };
   *s = p ;
 }
+
+/*@ assigns *(p+(0..n)); */
+void f_assigns_array(int *p, unsigned n);
+
+/*@ requires n > 4 ;
+    assigns *(p+0), *(p+(1..n-1)), *(p+n); */
+void PARTIAL_ASSIGNS(int *p, unsigned n) {
+  f_assigns_array(p, n);
+}
-- 
GitLab