From ae0cbc8af5382ee24d452a652491a13784265220 Mon Sep 17 00:00:00 2001
From: Valentin Perrelle <valentin.perrelle@cea.fr>
Date: Fri, 10 Dec 2021 22:08:56 +0100
Subject: [PATCH] [Eva] multidim: accept array iterations through *(p + i)

---
 .../abstract_interp/abstract_memory.ml        | 40 ++++++++++++++-----
 .../abstract_interp/abstract_offset.ml        | 32 ++++++++++++++-
 .../abstract_interp/abstract_offset.mli       |  1 +
 src/plugins/value/domains/multidim_domain.ml  | 12 +++++-
 tests/value/multidim.c                        | 12 ++++++
 tests/value/oracle/multidim.res.oracle        | 34 ++++++++++++----
 6 files changed, 111 insertions(+), 20 deletions(-)

diff --git a/src/kernel_services/abstract_interp/abstract_memory.ml b/src/kernel_services/abstract_interp/abstract_memory.ml
index dc13f84d1de..cb1d202d3d5 100644
--- a/src/kernel_services/abstract_interp/abstract_memory.ml
+++ b/src/kernel_services/abstract_interp/abstract_memory.ml
@@ -796,11 +796,23 @@ struct
     and u2 = Option.get (Ival.max_int i2) in
     Const (Integer.max u1 u2)
 
+  exception NoBound
+
   let lower_const ~oracle b =
-    Const (Option.get (Ival.min_int (to_ival ~oracle b))) (* TODO: handle exception *)
+    match Ival.min_int (to_ival ~oracle b) with
+    | Some l -> Const l
+    | None -> (* TODO: handle exception *)
+      Kernel.feedback ~current:true "cannot retrieve lower bound for %a in %a"
+        pretty b Ival.pretty (to_ival ~oracle b);
+      raise NoBound
 
   let upper_const ~oracle b =
-    Const (Option.get (Ival.min_int (to_ival ~oracle b))) (* TODO: handle exception *)
+    match Ival.max_int (to_ival ~oracle b) with
+    | Some u -> Const u
+    | None -> (* TODO: handle exception *)
+      Kernel.feedback ~current:true "cannot retrieve upper bound for %a in %a"
+        pretty b Ival.pretty (to_ival ~oracle b);
+      raise NoBound
 
   let _operators oracle = operators (cmp ~oracle)
 end
@@ -1526,15 +1538,23 @@ struct
           let array_value = A.unify ~oracle aux a1.array_value a2.array_value in
           Array { a1 with array_value }
         | Array a, Raw b ->
-          let array_value' = raw_to_array ~oracle Left a.array_value b in
-          let array_value = A.unify ~oracle aux a.array_value array_value' in
-          debug demerging "emerging unification between@.%a and@.%a result:@.%a" A.pretty a.array_value A.pretty array_value' A.pretty array_value;
-          Array { a with array_value }
+          begin try
+              let array_value' = raw_to_array ~oracle Left a.array_value b in
+              let array_value = A.unify ~oracle aux a.array_value array_value' in
+              debug demerging "emerging unification between@.%a and@.%a result:@.%a" A.pretty a.array_value A.pretty array_value' A.pretty array_value;
+              Array { a with array_value }
+            with Bound.NoBound ->
+              weak_erase b m1 (* TODO: find a better way to handle this case *)
+          end
         | Raw b, Array a ->
-          let array_value' = raw_to_array ~oracle Right a.array_value b in
-          let array_value = A.unify ~oracle aux array_value' a.array_value in
-          debug demerging "emerging unification between@.%a and@.%a result:@.%a" A.pretty array_value' A.pretty a.array_value A.pretty array_value;
-          Array { a with array_value }
+          begin try
+              let array_value' = raw_to_array ~oracle Right a.array_value b in
+              let array_value = A.unify ~oracle aux array_value' a.array_value in
+              debug demerging "emerging unification between@.%a and@.%a result:@.%a" A.pretty array_value' A.pretty a.array_value A.pretty array_value;
+              Array { a with array_value }
+            with Bound.NoBound ->
+              weak_erase b m2 (* TODO: find a better way to handle this case *)
+          end
         | Struct s1, Struct s2 when are_structs_compatible s1 s2 ->
           let struct_value = S.unify aux s1.struct_value s2.struct_value in
           Struct { s1 with struct_value }
diff --git a/src/kernel_services/abstract_interp/abstract_offset.ml b/src/kernel_services/abstract_interp/abstract_offset.ml
index 2b546b54f12..84e6056507e 100644
--- a/src/kernel_services/abstract_interp/abstract_offset.ml
+++ b/src/kernel_services/abstract_interp/abstract_offset.ml
@@ -25,7 +25,8 @@ sig
   type t
 
   val pretty : Format.formatter -> t -> unit
-  val append : t -> t -> t (* Does not check that the appened offset fits *)
+  val append : t -> t -> t
+  val add_index : (Cil_types.exp -> Int_val.t) -> t -> Cil_types.exp -> t
   val join : t -> t -> t
   val of_cil_offset : (Cil_types.exp -> Int_val.t) -> Cil_types.typ -> Cil_types.offset -> t
   val of_ival : base_typ:Cil_types.typ -> typ:Cil_types.typ -> Ival.t -> t
@@ -63,6 +64,28 @@ struct
     | Field (fi, s) -> Field (fi, append s o2)
     | Index (e, i, t, s) -> Index (e, i, t, append s o2)
 
+  let add_index oracle base exp =
+    let rec aux = function
+      | NoOffset _ -> assert false
+      | Field (fi, s) -> Field (fi, aux s)
+      | Index (e, i, t, (NoOffset _ as s)) ->
+        begin
+          let idx' = Int_val.add i (oracle exp) in
+          let loc = Cil_datatype.Location.unknown in
+          let e = match e with (* If i is singleton, we can use this as the index expression *)
+            | None when Int_val.is_singleton i ->
+              let loc = Cil_datatype.Location.unknown in
+              Some (Cil.kinteger64 ~loc (Int_val.project_int i))
+            | e -> e
+          in
+          let e' = Option.map (Fun.flip (Cil.mkBinOp ~loc Cil_types.PlusA) exp) e in
+          (* TODO: is idx inside bounds ? *)
+          Index (e', idx', t, s)
+        end
+      | Index (e, i, t, s) -> Index (e, i, t, aux s)
+    in
+    aux base
+
   let rec join o1 o2 =
     match o1, o2 with
     | NoOffset t, NoOffset t'
@@ -127,7 +150,7 @@ struct
             let elem_size = Integer.of_int (Cil.bitsSizeOf elem_typ) in
             if Integer.is_zero elem_size then (* array of elements of size 0 *)
               if Int_val.is_zero ival then (* the whole range is valid *)
-                let range = (array_range array_size) in
+                let range = array_range array_size in
                 Extlib.the ~exn:Abstract_interp.Error_Top range, ival
               else (* Non-zero offset cannot represent anything here *)
                 raise Abstract_interp.Error_Top
@@ -245,6 +268,11 @@ struct
     | `Top, _ | _, `Top -> `Top
     | `Value o1, `Value o2 -> `Value (TypedOffset.append o1 o2)
 
+  let add_index oracle base exp =
+    match base with
+    | `Top -> `Top
+    | `Value base -> `Value (TypedOffset.add_index oracle base exp)
+
   let join o1 o2 =
     match o1, o2 with
     | `Top, _ | _, `Top -> `Top
diff --git a/src/kernel_services/abstract_interp/abstract_offset.mli b/src/kernel_services/abstract_interp/abstract_offset.mli
index 5b2dc622609..64638b2bd04 100644
--- a/src/kernel_services/abstract_interp/abstract_offset.mli
+++ b/src/kernel_services/abstract_interp/abstract_offset.mli
@@ -26,6 +26,7 @@ sig
 
   val pretty : Format.formatter -> t -> unit
   val append : t -> t -> t (* Does not check that the appened offset fits *)
+  val add_index : (Cil_types.exp -> Int_val.t) -> t -> Cil_types.exp -> t
   val join : t -> t -> t
   val of_cil_offset : (Cil_types.exp -> Int_val.t) -> Cil_types.typ -> Cil_types.offset -> t
   val of_ival : base_typ:Cil_types.typ -> typ:Cil_types.typ -> Ival.t -> t
diff --git a/src/plugins/value/domains/multidim_domain.ml b/src/plugins/value/domains/multidim_domain.ml
index a6df4575f06..112b20da720 100644
--- a/src/plugins/value/domains/multidim_domain.ml
+++ b/src/plugins/value/domains/multidim_domain.ml
@@ -141,13 +141,23 @@ struct
     | Var vi ->
       Map.singleton (Base.of_varinfo vi) offset
     | Mem exp ->
+      let exp, index = match exp.enode with
+        | BinOp (PlusPI, e1, e2, _typ) ->
+          e1, Some e2
+        | _ -> exp, None
+      in
       let add base ival map =
         let offset' : Offset.t =
           match Base.typeof base with
           | None -> `Top
           | Some base_typ ->
             let typ = Cil.typeOf_pointed (Cil.typeOf exp) in
-            Offset.(append (of_ival ~base_typ ~typ ival) offset)
+            let base_offset = Offset.of_ival ~base_typ ~typ ival in
+            let base_offset = match index with
+              | None -> base_offset
+              | Some exp -> Offset.add_index oracle' base_offset exp
+            in
+            Offset.append base_offset offset
         in
         Map.add base offset' map
       in
diff --git a/tests/value/multidim.c b/tests/value/multidim.c
index 9fea8b14e58..16853e329c4 100644
--- a/tests/value/multidim.c
+++ b/tests/value/multidim.c
@@ -158,6 +158,17 @@ void main7() {
   }
 }
 
+void main8(void) {
+  int t[10] = {0};
+  int *p = t;
+
+  for (int i = 0 ; i < 10 ; i++) {
+    *(p + i) = 1;
+  }
+
+  Frama_C_domain_show_each(t);
+}
+
 void main(s x) {
   main1(x);
   main2();
@@ -166,4 +177,5 @@ void main(s x) {
   main5();
   main6();
   main7();
+  main8();
 }
diff --git a/tests/value/oracle/multidim.res.oracle b/tests/value/oracle/multidim.res.oracle
index 4bf906a8671..9d11b591920 100644
--- a/tests/value/oracle/multidim.res.oracle
+++ b/tests/value/oracle/multidim.res.oracle
@@ -9,7 +9,7 @@
   z[0..3] ∈ {0}
   nondet ∈ [--..--]
 [eva] computing for function main1 <- main.
-  Called from multidim.c:162.
+  Called from multidim.c:173.
 [eva] multidim.c:39: 
   Frama_C_domain_show_each:
   x : # cvalue: .t1[0].f ∈ [-3.40282346639e+38 .. 3.40282346639e+38]
@@ -86,7 +86,7 @@
 [eva] Recording results for main1
 [eva] Done for function main1
 [eva] computing for function main2 <- main.
-  Called from multidim.c:163.
+  Called from multidim.c:174.
 [eva] multidim.c:55: starting to merge loop iterations
 [eva:alarm] multidim.c:58: Warning: check got status unknown.
 [eva] multidim.c:59: 
@@ -96,7 +96,7 @@
 [eva] Recording results for main2
 [eva] Done for function main2
 [eva] computing for function main3 <- main.
-  Called from multidim.c:164.
+  Called from multidim.c:175.
 [eva] multidim.c:64: starting to merge loop iterations
 [eva] multidim.c:63: starting to merge loop iterations
 [kernel] multidim.c:65: 
@@ -137,7 +137,7 @@
 [kernel] multidim.c:66: more than 1(28) elements to enumerate. Approximating.
 [eva] Done for function main3
 [eva] computing for function main4 <- main.
-  Called from multidim.c:165.
+  Called from multidim.c:176.
 [eva:loop-unroll:partial] multidim.c:81: loop not completely unrolled
 [eva] multidim.c:81: starting to merge loop iterations
 [eva] multidim.c:83: starting to merge loop iterations
@@ -148,7 +148,7 @@
 [eva] Recording results for main4
 [eva] Done for function main4
 [eva] computing for function main5 <- main.
-  Called from multidim.c:166.
+  Called from multidim.c:177.
 [eva] multidim.c:96: starting to merge loop iterations
 [eva] multidim.c:106: 
   Frama_C_domain_show_each:
@@ -163,7 +163,7 @@
 [eva] Recording results for main5
 [eva] Done for function main5
 [eva] computing for function main6 <- main.
-  Called from multidim.c:167.
+  Called from multidim.c:178.
 [eva] multidim.c:114: Trace partitioning superposing up to 100 states
 [eva] multidim.c:118: 
   Frama_C_domain_show_each:
@@ -181,7 +181,7 @@
 [eva] Recording results for main6
 [eva] Done for function main6
 [eva] computing for function main7 <- main.
-  Called from multidim.c:168.
+  Called from multidim.c:179.
 [eva] multidim.c:134: starting to merge loop iterations
 [kernel] multidim.c:136: 
   more than 1(1000) locations to update in array. Approximating.
@@ -215,6 +215,15 @@
 [kernel] multidim.c:136: more than 1(1000) elements to enumerate. Approximating.
 [kernel] multidim.c:137: more than 1(1000) elements to enumerate. Approximating.
 [eva] Done for function main7
+[eva] computing for function main8 <- main.
+  Called from multidim.c:180.
+[eva] multidim.c:165: starting to merge loop iterations
+[eva] multidim.c:169: 
+  Frama_C_domain_show_each:
+  t : # cvalue: {0; 1}
+      # multidim: { [0 .. 9] = {1}, [10 .. 9] = {0} }
+[eva] Recording results for main8
+[eva] Done for function main8
 [eva] Recording results for main
 [eva] done for function main
 [kernel] multidim.c:50: more than 1(28) elements to enumerate. Approximating.
@@ -245,6 +254,9 @@
   t[0].typ ∈ {1; 2} or UNINITIALIZED
    {[0].val; [1..999]} ∈ [--..--] or UNINITIALIZED
   j ∈ [0..999]
+[eva:final-states] Values at end of function main8:
+  t[0..9] ∈ {0; 1}
+  p ∈ {{ &t[0] }}
 [eva:final-states] Values at end of function main:
   Frama_C_entropy_source ∈ [--..--]
   z[0..3] ∈ [--..--]
@@ -276,6 +288,8 @@
 [kernel] multidim.c:141: 
   more than 1(1000) dependencies to update. Approximating.
 [from] Done for function main7
+[from] Computing for function main8
+[from] Done for function main8
 [from] Computing for function main
 [from] Done for function main
 [from] ====== DEPENDENCIES COMPUTED ======
@@ -300,6 +314,8 @@
   NO EFFECTS
 [from] Function main7:
   Frama_C_entropy_source FROM Frama_C_entropy_source (and SELF)
+[from] Function main8:
+  NO EFFECTS
 [from] Function main:
   Frama_C_entropy_source FROM Frama_C_entropy_source (and SELF)
   z[0..3] FROM \nothing
@@ -332,6 +348,10 @@
     Frama_C_entropy_source; t[0..999]; i; j
 [inout] Inputs for function main7:
     Frama_C_entropy_source; nondet
+[inout] Out (internal) for function main8:
+    t[0..9]; p; i
+[inout] Inputs for function main8:
+    \nothing
 [inout] Out (internal) for function main:
     Frama_C_entropy_source; z[0..3]
 [inout] Inputs for function main:
-- 
GitLab