From f1592187688843e19601e12a748869f76aad3cb5 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Loi=CC=88c=20Correnson?= <loic.correnson@cea.fr>
Date: Thu, 3 Dec 2020 16:05:40 +0100
Subject: [PATCH] [eva/api] EVA proxies

---
 src/plugins/value/api/values_request.ml | 488 ++++++++----------------
 1 file changed, 169 insertions(+), 319 deletions(-)

diff --git a/src/plugins/value/api/values_request.ml b/src/plugins/value/api/values_request.ml
index 85d2df413cd..25c4f4626a2 100644
--- a/src/plugins/value/api/values_request.ml
+++ b/src/plugins/value/api/values_request.ml
@@ -24,6 +24,11 @@ open Server
 open Data
 open Cil_types
 module Md = Markdown
+module Jkf = Kernel_ast.Kf
+module Jki = Kernel_ast.Ki
+module Jstmt = Kernel_ast.Stmt
+module CS = Value_types.Callstack
+module CSmap = CS.Hashtbl
 
 let package =
   Package.package
@@ -33,334 +38,179 @@ let package =
     ~readme:"eva.md"
     ()
 
-type value =
-  { value: string;
-    alarm: bool; }
-
-type evaluation =
-  | Unreachable
-  | Evaluation of value
-
-type after =
-  | Unchanged
-  | Reduced of evaluation
-
-type before_after =
-  { before: evaluation;
-    after_instr: after option;
-    after_then: after option;
-    after_else: after option; }
-
-type values =
-  { values: before_after;
-    callstack: (Value_util.callstack * before_after) list option; }
-
-let get_value = function
-  | Unreachable -> "Unreachable"
-  | Evaluation { value } -> value
-
-let get_alarm = function
-  | Unreachable -> false
-  | Evaluation { alarm } -> alarm
-
-let get_after_value =
-  Extlib.opt_map
-    (function Unchanged -> "unchanged" | Reduced eval -> get_value eval)
-
-module CallStackId =
-  Data.Index
-    (Value_types.Callstack.Map)
-    (struct
-      let name = "eva-callstack-id"
-    end)
-
-(* This pretty-printer drops the toplevel kf, which is always the function
-   in which we are pretty-printing the expression/term *)
-let pretty_callstack fmt cs =
-  match cs with
-  | [_, Kglobal] -> ()
-  | (_kf_cur, Kstmt callsite) :: q -> begin
-      let rec aux callsite = function
-        | (kf, callsite') :: q -> begin
-            Format.fprintf fmt "%a (%a)"
-              Kernel_function.pretty kf
-              Cil_datatype.Location.pretty (Cil_datatype.Stmt.loc callsite);
-            match callsite' with
-            | Kglobal -> ()
-            | Kstmt callsite' ->
-              Format.fprintf fmt " ←@ ";
-              aux callsite' q
-          end
-        | _ -> assert false
-      in
-      Format.fprintf fmt "@[<hv>%a" Value_types.Callstack.pretty_hash cs;
-      aux callsite q;
-      Format.fprintf fmt "@]"
-    end
-  | _ -> assert false
-
-(* This pretty-printer prints only the lists of the functions, not
-   the locations. *)
-let pretty_callstack_short fmt cs =
-  match cs with
-  | [_, Kglobal] -> ()
-  | (_kf_cur, Kstmt _callsite) :: q ->
-    Format.fprintf fmt "%a" Value_types.Callstack.pretty_hash cs;
-    Pretty_utils.pp_flowlist ~left:"@[" ~sep:" ←@ " ~right:"@]"
-      (fun fmt (kf, _) -> Kernel_function.pretty fmt kf) fmt q
-  | _ -> assert false
-
-module CallStack = struct
-  type record
-
-  let record: record Record.signature = Record.signature ()
-
-  let id = Record.field record ~name:"id"
-      ~descr:(Md.plain "Callstack id") (module Jint)
-  let short = Record.field record ~name:"short"
-      ~descr:(Md.plain "Short name for the callstack") (module Jstring)
-  let full = Record.field record ~name:"full"
-      ~descr:(Md.plain "Full name for the callstack") (module Jstring)
-
-  module R =
-    (val
-      (Record.publish
-         ~package
-         ~name:"callstack"
-         ~descr:(Md.plain "CallStack")
-         record) : Record.S with type r = record)
-
-  type t = Value_types.callstack option
-
-  let jtype = R.jtype
-
-  let pp_callstack ~short = function
-    | None -> if short then "all" else ""
-    | Some callstack ->
-      let pp_text =
-        if short
-        then Pretty_utils.to_string ~margin:50 pretty_callstack_short
-        else Pretty_utils.to_string pretty_callstack
-      in
-      (pp_text callstack)
-
-  let id_callstack = function
-    | None -> -1
-    | Some callstack -> CallStackId.get callstack
-
-  let to_json callstack =
-    R.default |>
-    R.set id (id_callstack callstack) |>
-    R.set short (pp_callstack ~short:true callstack) |>
-    R.set full (pp_callstack ~short:false callstack) |>
-    R.to_json
-
-  let key = function
-    | None -> "all"
-    | Some callstack -> string_of_int (CallStackId.get callstack)
-end
-
-
-let consolidated = ref None
-let table = Hashtbl.create 100
-
-let iter f =
-  if Hashtbl.length table > 1
-  then Extlib.may (fun values -> f (None, values)) !consolidated;
-  Hashtbl.iter (fun key data -> f (Some key, data)) table
-
-let array =
-  let model = States.model () in
-  let () =
-    States.column
-      ~name:"callstack"
-      ~descr:(Md.plain "CallStack")
-      ~data:(module CallStack)
-      ~get:fst
-      model
-  in
-  let () =
-    States.column
-      ~name:"value_before"
-      ~descr:(Md.plain "Value inferred just before the selected point")
-      ~data:(module Jstring)
-      ~get:(fun (_, e) -> get_value e.before)
-      model
-  in
-  let () =
-    States.column
-      ~name:"alarm"
-      ~descr:(Md.plain "Did the evaluation led to an alarm?")
-      ~data:(module Jbool)
-      ~get:(fun (_, e) -> get_alarm e.before)
-      model
-  in
-  let () =
-    States.column
-      ~name:"value_after"
-      ~descr:(Md.plain "Value inferred just after the selected point")
-      ~data:(module Joption(Jstring))
-      ~get:(fun (_, e) -> get_after_value e.after_instr)
-      model
-  in
-  States.register_array
-    ~package
-    ~name:"values"
-    ~descr:(Md.plain "Abstract values inferred by the Eva analysis")
-    ~key:(fun (cs, _) -> CallStack.key cs)
-    ~iter
-    model
-
-let update_values values =
-  Hashtbl.clear table;
-  consolidated := Some values.values;
-  let () =
-    match values.callstack with
-    | None -> ()
-    | Some by_callstack ->
-      List.iter
-        (fun (callstack, before_after) ->
-           Hashtbl.add table callstack before_after)
-        by_callstack
-  in
-  States.reload array
-
-module type S = sig
-  val evaluate: kinstr -> exp -> values
-  val lvaluate: kinstr -> lval -> values
+type callstack = Value_types.callstack
+
+type truth = Abstract_interp.truth
+type step = [ `Here | `After | `Then of exp | `Else of exp ]
+
+type probe =
+  | Expr of exp * stmt
+  | Lval of lval * stmt
+
+type domain = {
+  values: ( step * string ) list ;
+  alarms: ( truth * string ) list ;
+}
+
+(* -------------------------------------------------------------------------- *)
+(* --- Domain Utilities                                                   --- *)
+(* -------------------------------------------------------------------------- *)
+
+let next_steps s : step list =
+  match s.skind with
+  | If(cond,_,_,_) -> [ `Then cond ; `Else cond ]
+  | Instr (Set _ | Call _ | Local_init _ | Asm _ | Code_annot _)
+  | Switch _ | Loop _ | Block _ | UnspecifiedSequence _
+  | TryCatch _ | TryFinally _ | TryExcept _
+    -> [ `After ]
+  | Instr (Skip _) | Return _ | Break _ | Continue _ | Goto _ | Throw _ -> []
+
+(* -------------------------------------------------------------------------- *)
+(* --- EVA Proxy                                                          --- *)
+(* -------------------------------------------------------------------------- *)
+
+module type EvaProxy =
+sig
+  val callstacks : stmt -> callstack list
+  val domain : probe -> callstack option -> domain
 end
 
-module Make (Eva: Analysis.S) : S = struct
-
-  let make_before eval before =
-    let before =
-      match before with
-      | `Bottom -> Unreachable
-      | `Value state -> Evaluation (eval state)
-    in
-    { before; after_instr = None; after_then = None; after_else = None; }
-
-  let make_callstack stmt eval =
-    let before = Eva.get_stmt_state_by_callstack ~after:false stmt in
-    match before with
-    | (`Bottom | `Top) -> []
-    | `Value before ->
-      let aux callstack before acc =
-        let before_after = make_before eval (`Value before) in
-        (callstack, before_after) :: acc
-      in
-      Value_types.Callstack.Hashtbl.fold aux before []
-
-  let make_before_after eval ~before ~after =
-    match before with
-    | `Bottom ->
-      { before = Unreachable;
-        after_instr = None;
-        after_then = None;
-        after_else = None; }
-    | `Value before ->
-      let before = eval before in
-      let after_instr =
-        match after with
-        | `Bottom -> Some (Reduced Unreachable)
-        | `Value after ->
-          let after = eval after in
-          if String.equal before.value after.value
-          then Some Unchanged
-          else Some (Reduced (Evaluation after))
-      in
-      { before = Evaluation before;
-        after_instr; after_then = None; after_else = None; }
-
-  let make_instr_callstack stmt eval =
-    let before = Eva.get_stmt_state_by_callstack ~after:false stmt in
-    let after = Eva.get_stmt_state_by_callstack ~after:true stmt in
-    match before, after with
-    | (`Bottom | `Top), _
-    | _, (`Bottom | `Top) -> []
-    | `Value before, `Value after ->
-      let aux callstack before acc =
-        let before = `Value before in
-        let after =
-          try `Value (Value_types.Callstack.Hashtbl.find after callstack)
-          with Not_found -> `Bottom
-        in
-        let before_after = make_before_after eval ~before ~after in
-        (callstack, before_after) :: acc
-      in
-      Value_types.Callstack.Hashtbl.fold aux before []
-
-  let make eval kinstr =
-    let before = Eva.get_kinstr_state ~after:false kinstr in
-    let values, callstack =
-      match kinstr with
-      | Cil_types.Kglobal ->
-        make_before eval before, None
-      | Cil_types.Kstmt stmt ->
-        match stmt.skind with
-        | Instr _ ->
-          let after = Eva.get_kinstr_state ~after:true kinstr in
-          let values = make_before_after eval ~before ~after in
-          let callstack = make_instr_callstack stmt eval in
-          values, Some callstack
-        | _ ->
-          make_before eval before, Some (make_callstack stmt eval)
-    in
-    { values; callstack; }
+module Proxy(A : Analysis.S) : EvaProxy =
+struct
+
+  open Eval
+  type dstate = A.Dom.state or_top_or_bottom
+
+  let callstacks stmt =
+    match A.get_stmt_state_by_callstack ~after:false stmt with
+    | `Top | `Bottom -> []
+    | `Value states ->
+      CSmap.fold_sorted (fun cs _st css -> cs :: css) states []
+
+  let dstate ~after stmt callstack =
+    match callstack with
+    | None -> (A.get_stmt_state ~after stmt :> dstate)
+    | Some cs ->
+      begin match A.get_stmt_state_by_callstack ~after stmt with
+        | `Top -> `Top
+          | `Bottom -> `Bottom
+          | `Value cmap ->
+            try `Value (CSmap.find cmap cs)
+            with Not_found -> `Bottom
+      end
+
+  let dbottom = {
+    alarms = [] ;
+    values = [`Here , "Unreachable (bottom)"] ;
+  }
+
+  let dtop = {
+    alarms = [] ;
+    values = [`Here , "Not available (top)"] ;
+  }
+
+  let dalarms alarms =
+    let pool = ref [] in
+    Alarmset.iter
+      (fun alarm status ->
+         let descr = Format.asprintf "@[<hov 2>%a@]" Alarms.pretty alarm
+         in pool := (status , descr) :: !pool
+      ) alarms ;
+    List.rev !pool
+
+  let deval (eval : A.Dom.state -> string * Alarmset.t) stmt callstack =
+    match dstate ~after:false stmt callstack with
+    | `Bottom -> dbottom
+    | `Top -> dtop
+    | `Value state ->
+      let value, alarms = eval state in
+      let dnext (step : step) vs = function
+        | `Top | `Bottom -> vs
+        | `Value state -> (step , fst @@ eval state) :: vs in
+      let others = List.fold_right
+          begin fun st vs ->
+            match st with
+            | `Here -> vs (* absurd *)
+            | `After -> dnext st vs @@ dstate ~after:false stmt callstack
+            | `Then cond -> dnext st vs @@ A.assume_cond stmt state cond true
+            | `Else cond -> dnext st vs @@ A.assume_cond stmt state cond false
+          end (next_steps stmt) []
+      in {
+        values = (`Here,value) :: others ;
+        alarms = dalarms alarms ;
+      }
+
+  let e_expr expr state =
+    let value, alarms = A.eval_expr state expr in
+    begin
+      Pretty_utils.to_string (Bottom.pretty A.Val.pretty) value,
+      alarms
+    end
 
+  let e_lval lval state =
+    let value, alarms = A.copy_lvalue state lval in
+    let flagged = match value with
+      | `Bottom -> Eval.Flagged_Value.bottom
+      | `Value v -> v in
+    begin
+      Pretty_utils.to_string (Eval.Flagged_Value.pretty A.Val.pretty) flagged,
+      alarms
+    end
 
-  let evaluate kinstr expr =
-    let eval state =
-      let value, alarms = Eva.eval_expr state expr in
-      let alarm = not (Alarmset.is_empty alarms) in
-      let str = Format.asprintf "%a" (Bottom.pretty Eva.Val.pretty) value in
-      { value = str; alarm }
-    in
-    make eval kinstr
+  let domain probe callstack =
+    match probe with
+    | Expr(expr,stmt) -> deval (e_expr expr) stmt callstack
+    | Lval(lval,stmt) -> deval (e_lval lval) stmt callstack
 
-  let lvaluate kinstr lval =
-    let eval state =
-      let value, alarms = Eva.copy_lvalue state lval in
-      let alarm = not (Alarmset.is_empty alarms) in
-      let flagged_value = match value with
-        | `Bottom -> Eval.Flagged_Value.bottom
-        | `Value v -> v
-      in
-      let pretty = Eval.Flagged_Value.pretty Eva.Val.pretty in
-      let str = Format.asprintf "%a" pretty flagged_value in
-      { value = str; alarm }
-    in
-    make eval kinstr
 end
 
+let proxy =
+  let make (a : (module Analysis.S)) = (module Proxy(val a) : EvaProxy) in
+  let current = ref (make @@ Analysis.current_analyzer ()) in
+  let () = Analysis.register_hook (fun a -> current := make a) in
+  current
 
-let ref_request =
-  let module Analyzer = (val Analysis.current_analyzer ()) in
-  ref (module Make (Analyzer) : S)
-
-let hook (module Analyzer: Analysis.S) =
-  ref_request := (module Make (Analyzer) : S)
+(* -------------------------------------------------------------------------- *)
+(* --- Request getCallstackInfos                                          --- *)
+(* -------------------------------------------------------------------------- *)
 
-let () = Analysis.register_hook hook
+module Jcallstack = Data.Index(Value_types.Callstack.Map)
+    (struct let name = "eva-callstack-id" end)
 
-
-let update tag =
-  let module Request = (val !ref_request) in
-  match tag with
-  | Printer_tag.PExp (_kf, kinstr, expr) ->
-    update_values (Request.evaluate kinstr expr)
-  | Printer_tag.PLval (_kf, kinstr, lval) ->
-    update_values (Request.lvaluate kinstr lval)
-  | PVDecl (_kf, kinstr, varinfo) ->
-    update_values (Request.lvaluate kinstr (Var varinfo, NoOffset))
-  | _ -> ()
+let pretty fmt cs =
+  match cs with
+    | (_, Kstmt _) :: callers ->
+      Value_types.Callstack.pretty_hash fmt cs;
+      Pretty_utils.pp_flowlist ~left:"@[" ~sep:" ←@ " ~right:"@]"
+        (fun fmt (kf, _) -> Kernel_function.pretty fmt kf) fmt callers
+    | _ -> ()
 
 let () =
-  Server.Request.register
-    ~package
-    ~kind:`GET
-    ~name:"getValues"
-    ~descr:(Md.plain "Get the abstract values computed for an expression or lvalue")
-    ~input:(module Kernel_ast.Marker)
-    ~output:(module Junit)
-    update
+  let getCallstackInfos = Request.signature
+      ~input:(module Jcallstack) () in
+  let set_descr = Request.result getCallstackInfos ~name:"descr"
+      ~descr:(Md.plain "Description")
+      (module Jstring) in
+  let set_calls = Request.result getCallstackInfos ~name:"calls"
+      ~descr:(Md.plain "Callers site, from last to first")
+      (module Jlist(Jpair(Jkf)(Jki))) in
+  Request.register_sig ~package getCallstackInfos
+      ~kind:`GET ~name:"getCallstackInfos"
+      ~descr:(Md.plain "Callstack Description")
+      begin fun rq cs ->
+        set_calls rq cs ;
+        set_descr rq (Pretty_utils.to_string pretty cs) ;
+      end
+
+(* -------------------------------------------------------------------------- *)
+(* --- Request getCallstacks                                              --- *)
+(* -------------------------------------------------------------------------- *)
+
+let () = Request.register ~package
+    ~kind:`GET ~name:"getCallstacks"
+    ~descr:(Md.plain "Callstacks to a statement")
+    ~input:(module Jstmt)
+    ~output:(module Jlist(Jcallstack))
+    (fun stmt -> let module A = (val !proxy) in A.callstacks stmt)
+
+(* -------------------------------------------------------------------------- *)
-- 
GitLab