From c442f89a300c1d0c19705ab5ac82a4e486d83ef2 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?David=20B=C3=BChler?= <david.buhler@cea.fr>
Date: Tue, 8 Dec 2020 15:27:02 +0100
Subject: [PATCH] [Eva] Octagon domain: adds an abstract interface for
 variables.

---
 src/plugins/eva/domains/octagons.ml | 206 +++++++++++++++++-----------
 1 file changed, 123 insertions(+), 83 deletions(-)

diff --git a/src/plugins/eva/domains/octagons.ml b/src/plugins/eva/domains/octagons.ml
index b463daa3ac3..e3936c089be 100644
--- a/src/plugins/eva/domains/octagons.ml
+++ b/src/plugins/eva/domains/octagons.ml
@@ -48,18 +48,40 @@ let intraprocedural () = not (Parameters.OctagonCall.get ())
 (*                  Basic types: pair of variables and Ival.t                 *)
 (* -------------------------------------------------------------------------- *)
 
+type kind = Integer | Float
+
+let typ_kind typ =
+  match Cil.unrollType typ with
+  | TInt _ | TEnum  _ -> Integer
+  | TFloat _ -> Float
+  | _ -> assert false
+
+(* Abstract interface for the variables used by the octagons. *)
+module type Variable = sig
+  include Datatype.S_with_collections
+  (* Creates a variable from a varinfo. Should be extended to support more
+     lvalues. *)
+  val make: varinfo -> t
+  (* Returns all variables that may have been created for a varinfo. *)
+  val get_all: varinfo -> t list
+  val kind: t -> kind   (* The kind of the variable: integer or float. *)
+  val lval: t -> lval   (* The CIL lval corresponding to the variable. *)
+  val base: t -> Base.t (* Base of the variable. *)
+  val id: t -> int      (* Unique id, needed to use variables as hptmap keys. *)
+end
+
 (* Variables of the octagons. Should be extended later to also include
    symbolic lvalues. *)
-module Variable = struct
+module Variable : Variable = struct
   include Cil_datatype.Varinfo
+  let make v = v
+  let get_all v = [ v ]
+  let kind v = typ_kind v.vtype
+  let lval v = Var v, NoOffset
+  let base v = Base.of_varinfo v
   let id v = v.vid
 end
 
-module VariableSet = struct
-  include Variable.Set
-  let pretty_debug = pretty
-end
-
 (* Pairs of related variables in an octagon.
    This module imposes an order between the two variables X and Y in a pair
    to avoid creating octagons about X±Y *and* about Y±X. *)
@@ -76,11 +98,15 @@ module Pair = struct
   (* Creates a pair, and also returns a boolean that is [true] if x, y are
      swapped in the pair. *)
   let make x y =
-    assert (x.vid <> y.vid);
-    let pair, swap = if x.vid < y.vid then (x, y), false else (y, x), true in
+    let x_id = Variable.id x
+    and y_id = Variable.id y in
+    assert (x_id <> y_id);
+    if debug then assert (Variable.kind x = Variable.kind y);
+    let pair, swap = if x_id < y_id then (x, y), false else (y, x), true in
     hashcons pair, swap
 
   let fst t = fst (get t)
+  let kind t = Variable.kind (fst t)
 end
 
 
@@ -114,12 +140,11 @@ module Arith = struct
     then neg_int ival
     else inject_float (Fval.neg (project_float ival))
 
-  let int_or_float_operation i_op f_op = fun typ ->
-    match Cil.unrollType typ with
-    | TInt _ | TEnum _ -> i_op
-    | TFloat _ -> fun i1 i2 ->
-      inject_float (f_op Fval.Real (project_float i1) (project_float i2))
-    | _ -> assert false
+  let int_or_float_operation i_op f_op = function
+    | Integer -> i_op
+    | Float ->
+      fun i1 i2 ->
+        inject_float (f_op Fval.Real (project_float i1) (project_float i2))
 
   let sub = int_or_float_operation Ival.sub_int Fval.sub
   let add = int_or_float_operation Ival.add_int Fval.add
@@ -150,7 +175,7 @@ let _pretty_octagon fmt octagon =
   let x, y = Pair.get octagon.variables in
   let op = match octagon.operation with Add -> "+" | Sub -> "-" in
   Format.fprintf fmt "%a %s %a %s %a"
-    Printer.pp_varinfo x op Printer.pp_varinfo y
+    Variable.pretty x op Variable.pretty y
     (Unicode.inset_string ()) Ival.pretty octagon.value
 
 (* Transforms Cil expressions into mathematical octagons.
@@ -186,11 +211,11 @@ module Rewriting = struct
 
   (* Simplified form [±X-coeff] for expressions,
      where X is a variable and coeff an interval. *)
-  type var_coeff = { varinfo: varinfo; sign: bool; coeff: Ival.t; }
+  type var_coeff = { var: Variable.t; sign: bool; coeff: Ival.t; }
 
   (* Negates a simplified form. *)
-  let neg { varinfo; sign; coeff } =
-    { varinfo; sign = not sign; coeff = Arith.neg coeff }
+  let neg { var; sign; coeff } =
+    { var; sign = not sign; coeff = Arith.neg coeff }
 
   (* Is the interval computed for a variable a singleton? *)
   let is_singleton = function
@@ -209,11 +234,11 @@ module Rewriting = struct
   let apply_binop f evaluate typ e1 op e2 =
     evaluate e1 >> fun v1 ->
     evaluate e2 >> fun v2 ->
-    let typ_e1 = Cil.typeOf e1 in
-    let result = Arith.apply op typ_e1 v1 v2 in
+    let kind = typ_kind (Cil.typeOf e1) in
+    let result = Arith.apply op kind v1 v2 in
     if may_overflow typ result
     then []
-    else f typ_e1 v1 v2
+    else f kind v1 v2
 
   (* Rewrites the Cil expression [expr] into the simplified form [±x-coeff],
      where [x] is a non-singleton variable and [coeff] is an interval. The
@@ -230,7 +255,9 @@ module Rewriting = struct
       if Cil.isIntegralType varinfo.vtype
       && not (Cil.typeHasQualifier "volatile" varinfo.vtype)
       && not (is_singleton (evaluate expr))
-      then [ { varinfo; sign = true; coeff = Ival.zero } ]
+      then
+        let var = Variable.make varinfo in
+        [ { var; sign = true; coeff = Ival.zero } ]
       else []
 
     | UnOp (Neg, e, typ) ->
@@ -264,20 +291,21 @@ module Rewriting = struct
 
   (* Rewrites the operation [e1 ± e2] into equivalent octagons ±(X±Y-value). *)
   let rewrite_binop evaluate e1 binop e2 =
+    let kind = typ_kind (Cil.typeOf e1) in
     let vars1 = rewrite evaluate e1 in
     let vars2 = rewrite evaluate e2 in
     let vars2 = if binop = Sub then List.map neg vars2 else vars2 in
     let aux acc var1 var2 =
-      if Cil_datatype.Varinfo.equal var1.varinfo var2.varinfo
+      if Variable.equal var1.var var2.var
       then acc
       else
-        let variables, swap = Pair.make var1.varinfo var2.varinfo in
+        let variables, swap = Pair.make var1.var var2.var in
         let operation = if var1.sign = var2.sign then Add else Sub in
         let sign = match operation with
           | Add -> var1.sign
           | Sub -> if swap then var2.sign else var1.sign
         in
-        let value = Arith.add (Cil.typeOf e1) var1.coeff var2.coeff in
+        let value = Arith.add kind var1.coeff var2.coeff in
         let value = if sign then value else Arith.neg value in
         (sign, { variables; operation; value }) :: acc
     in
@@ -297,13 +325,13 @@ module Rewriting = struct
   (* Transforms the constraint [expr] ∈ [ival] into a list of octagonal
      constraints. *)
   let make_octagons evaluate expr ival =
-    let make_octagons_from_binop typ e1 op e2 ival =
+    let make_octagons_from_binop kind e1 op e2 ival =
       (* equivalent octagonal forms ±(X±Y-v) for [e1 op e2]. *)
       let rewritings = rewrite_binop evaluate e1 op e2 in
       (* create the final octagon, knowning that [e1 op e2] ∈ [ival]. *)
       let make_octagon (sign, octagon) =
         let ival = if sign then ival else Arith.neg ival in
-        let value = Arith.add typ ival octagon.value in
+        let value = Arith.add kind ival octagon.value in
         { octagon with value }
       in
       List.map make_octagon rewritings
@@ -324,7 +352,7 @@ module Rewriting = struct
           if Ival.is_zero ival then Abstract_interp.Comp.inv comp else comp
         in
         let range = comparison_range comp in
-        make_octagons_from_binop typ e1 Sub e2 range
+        make_octagons_from_binop (typ_kind typ) e1 Sub e2 range
     | _ -> []
 
   let overflow_alarms typ expr ival =
@@ -370,10 +398,10 @@ module Rewriting = struct
       let octagons = rewrite_binop evaluate_expr e1 op e2 in
       let ival = evaluate_octagons octagons in
       if Ival.(equal top ival) then default else
-        let typ_e1 = Cil.typeOf e1 in
+        let kind = typ_kind (Cil.typeOf e1) in
         let ival2 =
           match evaluate_expr e1, evaluate_expr e2 with
-          | `Value v1, `Value v2 -> Arith.apply op typ_e1 v1 v2
+          | `Value v1, `Value v2 -> Arith.apply op kind v1 v2
           | _, _ -> Ival.top
         in
         let ival = Ival.narrow ival ival2 in
@@ -577,8 +605,8 @@ module Octagons = struct
       if Ival.(equal top ival)
       then None
       else
-        let typ = (Pair.fst octagon.variables).vtype in
-        let ival = Arith.sub typ ival octagon.value in
+        let kind = Pair.kind octagon.variables in
+        let ival = Arith.sub kind ival octagon.value in
         Some ival
     with Not_found -> None
 end
@@ -592,7 +620,8 @@ module Relations = struct
   module Initial_Values = struct let v = [[]] end
   module Dependencies = struct let l = [ Ast.self ] end
 
-  include Hptmap.Make (Variable) (VariableSet)
+  include Hptmap.Make (Variable)
+      (struct include Variable.Set let pretty_debug = pretty end)
       (Hptmap.Comp_unused) (Initial_Values) (Dependencies)
 
   let inter =
@@ -612,9 +641,9 @@ module Relations = struct
   let relate_aux x y t =
     let related =
       try find x t
-      with Not_found -> VariableSet.empty
+      with Not_found -> Variable.Set.empty
     in
-    let updated = VariableSet.add y related in
+    let updated = Variable.Set.add y related in
     add x updated t
 
   (* Marks x and y as mutually related. *)
@@ -623,7 +652,7 @@ module Relations = struct
     relate_aux y x (relate_aux x y t)
 
   let add variable set t =
-    if VariableSet.is_empty set
+    if Variable.Set.is_empty set
     then remove variable t
     else add variable set t
 end
@@ -751,8 +780,8 @@ module State = struct
          between them. *)
       let check_octagon pair _ =
         let x, y = Pair.get pair in
-        try VariableSet.mem x (Relations.find y t.relations)
-            && VariableSet.mem y (Relations.find x t.relations)
+        try Variable.Set.mem x (Relations.find y t.relations)
+            && Variable.Set.mem y (Relations.find x t.relations)
         with Not_found -> false
       in
       if Octagons.for_all check_octagon t.octagons
@@ -771,7 +800,7 @@ module State = struct
         let v1, v2 = Pair.get variables in
         let i1 = Intervals.find v1 intervals
         and i2 = Intervals.find v2 intervals in
-        let i = Arith.apply operation v1.vtype i1 i2 in
+        let i = Arith.apply operation (Variable.kind v1) i1 i2 in
         Ival.is_included i value
       with Not_found -> false
     else false
@@ -809,8 +838,9 @@ module State = struct
           try
             let i1 = Intervals.find v1 intervals
             and i2 = Intervals.find v2 intervals in
-            let add = Arith.add v1.vtype i1 i2
-            and sub = Arith.sub v1.vtype i1 i2 in
+            let kind = Variable.kind v1 in
+            let add = Arith.add kind i1 i2
+            and sub = Arith.sub kind i1 i2 in
             let diamond = Diamond.join diamond { add; sub } in
             if Diamond.is_top diamond then None else Some diamond
           with Not_found -> None
@@ -841,8 +871,9 @@ module State = struct
           try
             let i1 = Intervals.find v1 intervals
             and i2 = Intervals.find v2 intervals in
-            let add = Arith.add v1.vtype i1 i2
-            and sub = Arith.sub v1.vtype i1 i2 in
+            let kind = Variable.kind v1 in
+            let add = Arith.add kind i1 i2
+            and sub = Arith.sub kind i1 i2 in
             let diamond =
               if b
               then Diamond.widen { add; sub } diamond
@@ -877,7 +908,7 @@ module State = struct
   (* -------------- Transitive closure when adding an octagon --------------- *)
 
   type relation =
-    { vars: varinfo * varinfo;
+    { vars: Variable.t * Variable.t;
       diamond: diamond; }
 
   let add_diamond state variables diamond =
@@ -899,17 +930,17 @@ module State = struct
       if Variable.equal y (fst rel2.vars) then rel2 else inverse rel2
     in
     (* rel1 is about X±Y, rel2 is about Y±Z. *)
-    let typ = y.vtype in
+    let kind = Variable.kind y in
     (* X+Z = (X+Y) - (Y-Z) and X+Y = (X-Y) + (Y+Z) *)
     let add =
       Ival.narrow
-        (Arith.sub typ rel1.diamond.add rel2.diamond.sub)
-        (Arith.add typ rel1.diamond.sub rel2.diamond.add)
+        (Arith.sub kind rel1.diamond.add rel2.diamond.sub)
+        (Arith.add kind rel1.diamond.sub rel2.diamond.add)
     (* X-Z = (X+Y) - (Y+Z) and X-Z = (X-Y) + (Y-Z) *)
     and sub =
       Ival.narrow
-        (Arith.sub typ rel1.diamond.add rel2.diamond.add)
-        (Arith.add typ rel1.diamond.sub rel2.diamond.sub)
+        (Arith.sub kind rel1.diamond.add rel2.diamond.add)
+        (Arith.add kind rel1.diamond.sub rel2.diamond.sub)
     in
     let diamond = {add; sub} in
     let pair, swap = Pair.make (fst rel1.vars) (snd rel2.vars) in
@@ -919,7 +950,7 @@ module State = struct
   let saturate state x y rel1 =
     try
       let y_related = Relations.find y state.relations in
-      let y_related = VariableSet.remove x y_related in
+      let y_related = Variable.Set.remove x y_related in
       let aux z state =
         state >>- fun state ->
         try
@@ -931,7 +962,7 @@ module State = struct
           add_diamond state pair diamond
         with Not_found -> `Value state
       in
-      VariableSet.fold aux y_related (`Value state)
+      Variable.Set.fold aux y_related (`Value state)
     with Not_found -> `Value state
 
   let add_octagon state octagon =
@@ -964,14 +995,14 @@ module State = struct
       let remove_one y state =
         try
           let yrelations = Relations.find y state.relations in
-          let yrelations = VariableSet.remove x yrelations in
+          let yrelations = Variable.Set.remove x yrelations in
           let relations = Relations.add y yrelations state.relations in
           let pair, _ = Pair.make x y in
           let octagons = Octagons.remove pair state.octagons in
           { state with octagons; relations }
         with Not_found -> state
       in
-      let state = VariableSet.fold remove_one relations state in
+      let state = Variable.Set.fold remove_one relations state in
       let relations = Relations.remove x state.relations in
       { state with relations }
     with Not_found -> state
@@ -987,7 +1018,7 @@ module State = struct
           (y, diamond) :: acc
         with Not_found -> acc
       in
-      VariableSet.fold aux related []
+      Variable.Set.fold aux related []
     with Not_found -> []
 
   (* x' = ±x - delta *)
@@ -1007,23 +1038,23 @@ module State = struct
               sub = op diamond.add }
           else diamond
         in
-        let typ = x.vtype in
+        let kind = Variable.kind x in
         let op = if swap then Arith.add else Arith.sub in
         let add =
           if Ival.(equal top diamond.add)
           then diamond.add
-          else Arith.sub typ diamond.add delta
+          else Arith.sub kind diamond.add delta
         and sub =
           if Ival.(equal top diamond.sub)
           then diamond.sub
-          else op typ diamond.sub delta
+          else op kind diamond.sub delta
         in
         let diamond' = { add; sub } in
         let octagons = Octagons.unsafe_add pair diamond' state.octagons in
         { state with octagons }
       with Not_found -> state
     in
-    VariableSet.fold aux x_related state
+    Variable.Set.fold aux x_related state
 end
 
 (* -------------------------------------------------------------------------- *)
@@ -1067,21 +1098,23 @@ module Domain = struct
       begin
         try
           let x_ival = Cvalue.V.project_ival value in
-          let octagons = State.related_octagons state x in
+          let var = Variable.make x in
+          let kind = Variable.kind var in
+          let octagons = State.related_octagons state var in
           let reduce acc (y, octagons) =
             let y_ival1 =
               if Ival.(equal top octagons.add)
               then Ival.top
-              else Arith.sub x.vtype octagons.add x_ival
+              else Arith.sub kind octagons.add x_ival
             in
             let y_ival2 =
               if Ival.(equal top octagons.sub)
               then Ival.top
-              else Arith.sub x.vtype x_ival octagons.sub
+              else Arith.sub kind x_ival octagons.sub
             in
             let y_ival = Ival.narrow y_ival1 y_ival2 in
             if Ival.(equal top y_ival) then acc else
-              let y_enode = Lval (Var y, NoOffset) in
+              let y_enode = Lval (Variable.lval y) in
               let y_expr = Cil.new_exp ~loc:expr.eloc y_enode in
               let y_cvalue = Cvalue.V.inject_ival y_ival in
               (y_expr, y_cvalue) :: acc
@@ -1095,7 +1128,8 @@ module Domain = struct
   let kill_base base state =
     try
       let varinfo = Base.to_varinfo base in
-      State.remove state varinfo
+      let vars = Variable.get_all varinfo in
+      List.fold_left State.remove state vars
     with Base.Not_a_C_variable -> state
 
   let kill zone state =
@@ -1135,7 +1169,8 @@ module Domain = struct
       match expr.enode with
       | Lval (Var varinfo, NoOffset)
         when Cil.isIntegralType varinfo.vtype ->
-        let intervals = Intervals.add varinfo ival state.intervals in
+        let var = Variable.make varinfo in
+        let intervals = Intervals.add var ival state.intervals in
         { state with intervals }
       | _ -> state
 
@@ -1157,7 +1192,7 @@ module Domain = struct
     try `Value (check "update" (valuation.Abstract_domain.fold aux state))
     with EBottom -> `Bottom
 
-  let assign_interval varinfo assigned state =
+  let assign_interval variable assigned state =
     if not infer_intervals
     then state
     else
@@ -1167,7 +1202,7 @@ module Domain = struct
         begin
           try
             let ival = Cvalue.V.project_ival v in
-            let intervals = Intervals.add varinfo ival state.intervals in
+            let intervals = Intervals.add variable ival state.intervals in
             { state with intervals }
           with Cvalue.V.Not_based_on_null -> state
         end
@@ -1177,15 +1212,16 @@ module Domain = struct
     let evaluate = evaluation_function valuation in
     (* TODO: redundant with rewrite_binop below. *)
     let vars = Rewriting.rewrite evaluate expr in
-    let equal_varinfo v = Variable.equal varinfo v.Rewriting.varinfo in
+    let variable = Variable.make varinfo in
+    let equal_varinfo v = Variable.equal variable v.Rewriting.var in
     let state =
       try
         let var = List.find equal_varinfo vars in
         let inverse = not var.Rewriting.sign in
-        State.sub_delta ~inverse state varinfo var.Rewriting.coeff
-      with Not_found -> State.remove state varinfo
+        State.sub_delta ~inverse state variable var.Rewriting.coeff
+      with Not_found -> State.remove state variable
     in
-    let state = assign_interval varinfo assigned state in
+    let state = assign_interval variable assigned state in
     let enode = Lval (Var varinfo, NoOffset) in
     let left_expr = Cil.new_exp ~loc:expr.eloc enode in
     (* On the assignment X = E; if X-E can be rewritten as ±(X±Y-v),
@@ -1216,6 +1252,7 @@ module Domain = struct
 
   let start_recursive_call recursion state =
     let vars = List.map fst recursion.substitution @ recursion.withdrawal in
+    let vars = List.flatten (List.map Variable.get_all vars) in
     List.fold_left State.remove state vars
 
   let start_call _stmt call recursion valuation state =
@@ -1251,7 +1288,8 @@ module Domain = struct
 
   let enter_scope _kind _varinfos state = state
   let leave_scope _kf varinfos state =
-    let state = List.fold_left State.remove state varinfos in
+    let vars = List.flatten (List.map Variable.get_all varinfos) in
+    let state = List.fold_left State.remove state vars in
     check "leave_scope" state
 
   let initialize_variable _lval _location ~initialized:_ _value state = state
@@ -1261,32 +1299,34 @@ module Domain = struct
     if intraprocedural ()
     then Base.SetLattice.empty
     else
+      let add_related_bases acc var =
+        let related = Relations.find var state.relations in
+        Variable.Set.fold
+          (fun var -> Base.Hptset.add (Variable.base var))
+          related acc
+      in
       let aux base acc =
         try
           let varinfo = Base.to_varinfo base in
-          let varset = Relations.find varinfo state.relations in
-          let baseset =
-            VariableSet.fold
-              (fun vi acc -> Base.Hptset.add (Base.of_varinfo vi) acc)
-              varset Base.Hptset.empty
-          in
-          Base.SetLattice.(join (inject baseset) acc)
+          let variables = Variable.get_all varinfo in
+          List.fold_left add_related_bases acc variables
         with Base.Not_a_C_variable | Not_found -> acc
       in
-      Base.Hptset.fold aux bases Base.SetLattice.empty
+      let hptset = Base.Hptset.fold aux bases Base.Hptset.empty in
+      Base.SetLattice.inject hptset
 
   let filter _kind bases state =
     if intraprocedural ()
     then state
     else
-      let mem_vi varinfo = Base.Hptset.mem (Base.of_varinfo varinfo) bases in
+      let mem_var var = Base.Hptset.mem (Variable.base var) bases in
       let mem_pair pair =
         let x, y = Pair.get pair in
-        mem_vi x && mem_vi y
+        mem_var x && mem_var y
       in
       let octagons = Octagons.filter mem_pair state.octagons in
-      let intervals = Intervals.filter mem_vi state.intervals in
-      let relations = Relations.filter mem_vi state.relations in
+      let intervals = Intervals.filter mem_var state.intervals in
+      let relations = Relations.filter mem_var state.relations in
       { state with octagons; intervals; relations; }
 
   let reuse =
-- 
GitLab