Skip to content
Snippets Groups Projects
values_request.ml 22 KiB
Newer Older
(**************************************************************************)
(*                                                                        *)
(*  This file is part of Frama-C.                                         *)
(*                                                                        *)
(*  Copyright (C) 2007-2021                                               *)
(*    CEA (Commissariat à l'énergie atomique et aux énergies              *)
(*         alternatives)                                                  *)
(*                                                                        *)
(*  you can redistribute it and/or modify it under the terms of the GNU   *)
(*  Lesser General Public License as published by the Free Software       *)
(*  Foundation, version 2.1.                                              *)
(*                                                                        *)
(*  It is distributed in the hope that it will be useful,                 *)
(*  but WITHOUT ANY WARRANTY; without even the implied warranty of        *)
(*  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *)
(*  GNU Lesser General Public License for more details.                   *)
(*                                                                        *)
(*  See the GNU Lesser General Public License version 2.1                 *)
(*  for more details (enclosed in the file licenses/LGPLv2.1).            *)
(*                                                                        *)
(**************************************************************************)

open Server
open Data
open Cil_types
module Kmap = Kernel_function.Hashtbl
module Smap = Cil_datatype.Stmt.Hashtbl
module CS = Value_types.Callstack
module CSet = CS.Set
module CSmap = CS.Hashtbl

Loïc Correnson's avatar
Loïc Correnson committed
module Jkf = Kernel_ast.Kf
module Jstmt = Kernel_ast.Stmt
module Jmarker = Kernel_ast.Marker
let package =
  Package.package
    ~plugin:"eva"
    ~name:"values"
    ~title:"Eva Values"
    ~readme:"eva.md"
    ()
type probe =
  | Pexpr of exp * stmt
  | Plval of lval * stmt
  | Pnone
Loïc Correnson's avatar
Loïc Correnson committed
type callstack = Value_types.callstack
type truth = Abstract_interp.truth
type step = [ `Here | `After | `Then of exp | `Else of exp ]

type domain = {
  values: ( step * string ) list ;
  alarms: ( truth * string ) list ;
}

let signal = Request.signal ~package ~name:"changed"
    ~descr:(Md.plain "Emitted when EVA results has changed")

let () = Analysis.register_computation_hook ~on:Computed
    (fun _ -> Request.emit signal)
let handle_top_or_bottom ~top ~bottom compute = function
  | `Bottom -> bottom
  | `Top -> top
  | `Value v -> compute v



Loïc Correnson's avatar
Loïc Correnson committed
(* -------------------------------------------------------------------------- *)
(* --- Marker Utilities                                                   --- *)
Loïc Correnson's avatar
Loïc Correnson committed
(* -------------------------------------------------------------------------- *)

let next_steps s : step list =
  match s.skind with
  | If (cond, _, _, _) -> [ `Then cond ; `Else cond ]
  | Instr (Set _ | Call _ | Local_init _ | Asm _ | Code_annot _) -> [ `After ]
  | Switch _ | Loop _ | Block _ | UnspecifiedSequence _ -> [ `After ]
  | TryCatch _ | TryFinally _ | TryExcept _ -> [ `After ]
Loïc Correnson's avatar
Loïc Correnson committed
  | Instr (Skip _) | Return _ | Break _ | Continue _ | Goto _ | Throw _ -> []

let probe_stmt s =
  match s.skind with
  | Instr (Set(lv,_,_)) -> Plval(lv,s)
  | Instr (Call(Some lr,_,_,_)) -> Plval(lr,s)
  | Instr (Local_init(v,_,_)) -> Plval((Var v,NoOffset),s)
  | Return (Some e,_) | If(e,_,_,_) | Switch(e,_,_,_) -> Pexpr(e,s)
  | _ -> Pnone

let probe marker =
  let open Printer_tag in
  match marker with
  | PLval(_,Kstmt s,l) -> Plval(l,s)
  | PExp(_,Kstmt s,e) -> Pexpr(e,s)
  | PStmt(_,s) | PStmtStart(_,s) -> probe_stmt s
  | PVDecl(_,Kstmt s,v) -> Plval((Var v,NoOffset),s)
  | _ -> Pnone

(* -------------------------------------------------------------------------- *)
(* --- Stmt Ranking                                                       --- *)
(* -------------------------------------------------------------------------- *)

  val stmt : stmt -> int
  val sort : callstack list -> callstack list
end

module Ranking : Ranking_sig = struct

  class ranker = object(self)
    inherit Visitor.frama_c_inplace
    (* ranks really starts at 1 *)
    (* rank < 0 means not computed yet *)
    val mutable rank = (-1)
    val rmap = Smap.create 0
    val fmark = Kmap.create 0
    val fqueue = Queue.create ()

    method private call kf =
      if not (Kmap.mem fmark kf) then
        ( Kmap.add fmark kf () ; Queue.push kf fqueue )

    method private newrank s =
      let r = succ rank in
      Smap.add rmap s r ;
      rank <- r ; r

    method! vlval lv =
      begin
        try match fst lv with
          | Var vi -> self#call (Globals.Functions.get vi)
          | _ -> ()
        with Not_found -> ()
      end ; Cil.DoChildren

    method! vstmt_aux s =
      ignore (self#newrank s) ;
      Cil.DoChildren

    method flush =
      while not (Queue.is_empty fqueue) do
        let kf = Queue.pop fqueue in
        ignore (Visitor.(visitFramacKf (self :> frama_c_visitor) kf))
      done

    method compute =
      match Globals.entry_point () with
      | kf , _ -> self#call kf ; self#flush
      | exception Globals.No_such_entry_point _ -> ()

    method rank s =
      if rank < 0 then (rank <- 0 ; self#compute) ;
      try Smap.find rmap s
      with Not_found ->
        let kf = Kernel_function.find_englobing_kf s in
        self#call kf ;
        self#flush ;
        try Smap.find rmap s

  let stmt = let rk = new ranker in rk#rank

  let rec ranks (rks : int list) (cs : callstack) : int list =
    match cs with
    | [] -> rks
    | (_,Kglobal)::wcs -> ranks rks wcs
    | (_,Kstmt s)::wcs -> ranks (stmt s :: rks) wcs

  let order : int list -> int list -> int = Stdlib.compare

  let sort (wcs : callstack list) : callstack list =
    List.map fst @@
    List.sort (fun (_,rp) (_,rq) -> order rp rq) @@
    List.map (fun cs -> cs , ranks [] cs) wcs

end

(* -------------------------------------------------------------------------- *)
(* --- Domain Utilities                                                   --- *)
(* -------------------------------------------------------------------------- *)
module Jcallstack : S with type t = callstack = struct
  module I = Data.Index
      (Value_types.Callstack.Map)
      (struct let name = "eva-callstack-id" end)
  let jtype = Data.declare ~package ~name:"callstack" I.jtype
  type t = I.t
  let to_json = I.to_json
  let of_json = I.of_json
end
module Jcalls : Request.Output with type t = callstack = struct

  type t = callstack
  let jtype = Package.(Jlist (Jrecord [
      "callee" , Jkf.jtype ;
      "caller" , Joption Jkf.jtype ;
      "stmt" , Joption Jstmt.jtype ;
      "rank" , Joption Jnumber ;
    ]))

  let rec jcallstack jcallee ki cs : json list =
    match ki , cs with
    | Kglobal , _ | _ , [] -> [ `Assoc [ "callee", jcallee ] ]
    | Kstmt stmt , (called,ki) :: cs ->
      let jcaller = Jkf.to_json called in
      let callsite = `Assoc [
          "callee", jcallee ;
          "caller", jcaller ;
          "stmt", Jstmt.to_json stmt ;
          "rank", Jint.to_json (Ranking.stmt stmt) ;
        ]
      in callsite :: jcallstack jcaller ki cs

  let to_json = function
    | [] -> `List []
    | (callee,ki)::cs -> `List (jcallstack (Jkf.to_json callee) ki cs)

module Jtruth : Data.S with type t = truth = struct
  type t = truth
  let jtype = Package.(Junion [ Jtag "True" ; Jtag "False" ; Jtag "Unknown" ])
  let to_json = function
    | Abstract_interp.Unknown -> `String "Unknown"
    | True -> `String "True"
    | False -> `String "False"
  let of_json = function
    | `String "True" -> Abstract_interp.True
    | `String "False" -> Abstract_interp.False
    | _ -> Abstract_interp.Unknown
Loïc Correnson's avatar
Loïc Correnson committed
(* -------------------------------------------------------------------------- *)
(* --- EVA Proxy                                                          --- *)
(* -------------------------------------------------------------------------- *)

Loïc Correnson's avatar
Loïc Correnson committed
  val callstacks : stmt -> callstack list
  val domain : probe -> callstack option -> domain
  val pointed_lvalues : probe -> callstack option -> lval list option
module Proxy(A : Analysis.S) : EvaProxy = struct
Loïc Correnson's avatar
Loïc Correnson committed

  open Eval
  type dstate = A.Dom.state or_top_or_bottom

  let dnone = { alarms = [] ; values = [] }
  let dtop = { alarms = [] ; values = [`Here , "Not available."] }
  let dbottom = { alarms = [] ; values = [`Here , "Unreachable."] }

Loïc Correnson's avatar
Loïc Correnson committed
  let callstacks stmt =
    match A.get_stmt_state_by_callstack ~after:false stmt with
    | `Top | `Bottom -> []
    | `Value states -> CSmap.fold_sorted (fun cs _ wcs -> cs :: wcs) states []
  let dstate ~after stmt = function
    | None -> (A.get_stmt_state ~after stmt :> dstate)
    | Some cs ->
      match A.get_stmt_state_by_callstack ~after stmt with
      | (`Top | `Bottom) as res -> res
      | `Value cmap -> try `Value (CSmap.find cmap cs) with Not_found -> `Bottom
  type to_offsetmap =
    | Bottom
    | Offsetmap of Cvalue.V_Offsetmap.t
    | Empty
    | Top
    | InvalidLoc

  let extract_single_var vi state =
    let b = Base.of_varinfo vi in
    try
      match Cvalue.Model.find_base b state with
      | `Bottom -> InvalidLoc
      | `Value m -> Offsetmap m
      | `Top -> Top
    with Not_found -> InvalidLoc

  let reduce_loc_and_eval loc state =
    if Cvalue.Model.is_top state then Top
    else if not @@ Cvalue.Model.is_reachable state then Bottom
    else if Int_Base.(equal loc.Locations.size zero) then Empty
    else
      let loc' = Locations.(valid_part Read loc) in
      if Locations.is_bottom_loc loc' then InvalidLoc
      else
        try
          let size = Int_Base.project loc'.Locations.size in
          match Cvalue.Model.copy_offsetmap loc'.Locations.loc size state with
          | `Bottom -> Bottom
          | `Value offsm -> Offsetmap offsm
        with Abstract_interp.Error_Top -> Top

  let extract_or_reduce_lval lval state =
    match lval with
    | Var vi, NoOffset -> let r = extract_single_var vi state in fun _ -> r
    | _ -> fun loc -> reduce_loc_and_eval loc state

  let lval_to_offsetmap lval state =
    let loc, alarms = A.eval_lval_to_loc state lval in
    let state = A.Dom.get_cvalue_or_top state in
    let extract = extract_or_reduce_lval lval state in
    let precise_loc =
      match A.Loc.get Main_locations.PLoc.key with
      | None -> `Value (Precise_locs.loc_top)
      | Some get -> loc >>-: get
    and f loc acc =
      match acc, extract loc with
      | Offsetmap o1, Offsetmap o2 -> Offsetmap (Cvalue.V_Offsetmap.join o1 o2)
      | Bottom, v | v, Bottom -> v
      | Empty, v | v, Empty -> v
      | Top, Top -> Top
      | InvalidLoc, InvalidLoc -> InvalidLoc
      | InvalidLoc, (Offsetmap _ as res) -> res
      | Offsetmap _, InvalidLoc -> acc
      | Top, r | r, Top -> r (* cannot happen, we should get Top everywhere *)
    in precise_loc >>-: (fun loc -> Precise_locs.fold f loc Bottom), alarms

  type evaluation =
    | ToValue of A.Val.t
    | ToOffsetmap of to_offsetmap

  let pp_evaluation typ fmt = function
    | ToValue v -> A.Val.pretty fmt v
    | ToOffsetmap Bottom -> Format.fprintf fmt "<BOTTOM>"
    | ToOffsetmap Empty -> Format.fprintf fmt "<EMPTY>"
    | ToOffsetmap Top -> Format.fprintf fmt "<NO INFORMATION>"
    | ToOffsetmap InvalidLoc -> Format.fprintf fmt "<INVALID LOCATION>"
    | ToOffsetmap (Offsetmap o) ->
      Cvalue.V_Offsetmap.pretty_generic ?typ:(Some typ) () fmt o ;
      Eval_op.pretty_stitched_offsetmap fmt typ o
  let eval_lval lval state =
    match Cil.(unrollType @@ typeOfLval lval) with
    | TInt _ | TEnum _ | TPtr _ | TFloat _ ->
      let value, alarms = A.copy_lvalue state lval in
      let _ = lval_to_offsetmap lval state in
      let default = Eval.Flagged_Value.bottom in
      (Bottom.default ~default value).v >>-: (fun v -> ToValue v), alarms
    | _ ->
      let offsetmap, alarms = lval_to_offsetmap lval state in
      offsetmap >>-: (fun r -> ToOffsetmap r), alarms

  let eval_expr expr state =
    let value, alarms = A.eval_expr state expr in
    value >>-: (fun v -> ToValue v), alarms
Loïc Correnson's avatar
Loïc Correnson committed
  let dalarms alarms =
    let descr = Format.asprintf "@[<hov 2>%a@]" Alarms.pretty in
    let f alarm status pool = (status, descr alarm) :: pool in
    Alarmset.fold f [] alarms |> List.rev

  let fold_steps f stmt callstack state acc =
    let steps = `Here :: next_steps stmt in
    let add_state = function
      | `Here -> `Value state
      | `After -> dstate ~after:true stmt callstack
      | `Then cond -> (A.assume_cond stmt state cond true :> dstate)
      | `Else cond -> (A.assume_cond stmt state cond false :> dstate)
    in List.fold_left (fun acc step -> f acc step @@ add_state step) acc steps

  let domain_step typ eval ((values, alarms) as acc) step =
    let to_str = Pretty_utils.to_string (Bottom.pretty (pp_evaluation typ)) in
    handle_top_or_bottom ~top:acc ~bottom:acc @@ fun state ->
    let step_value, step_alarms = eval state in
    let alarms = match step with `Here -> step_alarms | _ -> alarms in
    (step, to_str step_value) :: values, alarms
  let domain_eval typ eval stmt callstack =
    let fold = fold_steps (domain_step typ eval) stmt callstack in
    let build (vs, als) = { values = List.rev vs ; alarms = dalarms als } in
    let compute_domain state = fold state ([], Alarmset.none) |> build in
    handle_top_or_bottom ~top:dtop ~bottom:dbottom compute_domain @@
    dstate ~after:false stmt callstack
  let domain p callstack =
    match p with
    | Plval (lval, stmt) ->
      domain_eval (Cil.typeOfLval lval) (eval_lval lval) stmt callstack
    | Pexpr (expr, stmt) ->
      domain_eval (Cil.typeOf expr) (eval_expr expr) stmt callstack
    | Pnone -> dnone
  let var_of_base base acc =
    let add vi acc = if Cil.isFunctionType vi.vtype then acc else vi :: acc in
    try add (Base.to_varinfo base) acc with Base.Not_a_C_variable -> acc

  let compute_pointed_lvalues eval_lval stmt callstack =
    Option.(bind (A.Val.get Main_values.CVal.key)) @@ fun get ->
    let get_eval = function ToValue v -> `Value (get v) | _ -> `Bottom in
    let loc state = eval_lval state |> fst >>- get_eval in
    let bases value = Cvalue.V.fold_bases var_of_base value [] in
    let lvalues state = loc state >>-: bases >>-: List.map Cil.var in
    let compute state = lvalues state |> Bottom.to_option in
    handle_top_or_bottom ~top:None ~bottom:None compute @@
    dstate ~after:true stmt callstack

  let pointed_lvalues p callstack =
    match p with
    | Plval (l, stmt) -> compute_pointed_lvalues (eval_lval l) stmt callstack
    | Pexpr (e, stmt) -> compute_pointed_lvalues (eval_expr e) stmt callstack
Loïc Correnson's avatar
Loïc Correnson committed
let proxy =
  let make (a : (module Analysis.S)) = (module Proxy(val a) : EvaProxy) in
  let current = ref (make @@ Analysis.current_analyzer ()) in
  let hook a = current := make a ; Request.emit signal in
  Analysis.register_hook hook ;
  fun () -> !current
Loïc Correnson's avatar
Loïc Correnson committed
(* -------------------------------------------------------------------------- *)
(* --- Request getCallstacks                                              --- *)
Loïc Correnson's avatar
Loïc Correnson committed
(* -------------------------------------------------------------------------- *)
let () =
  Request.register ~package
    ~kind:`GET ~name:"getCallstacks"
    ~descr:(Md.plain "Callstacks for markers")
    ~input:(module Jlist(Jmarker))
    ~output:(module Jlist(Jcallstack))
    begin fun markers ->
      let module A : EvaProxy = (val proxy ()) in
      let add stmt = List.fold_right CSet.add (A.callstacks stmt) in
      let gather_callstacks cset marker =
        match probe marker with
        | Pexpr (_, stmt) | Plval (_, stmt) -> add stmt cset
        | Pnone -> cset
      in
      let cset = List.fold_left gather_callstacks CSet.empty markers in
      Ranking.sort (CSet.elements cset)
(* -------------------------------------------------------------------------- *)
(* --- Request getCallstackInfo                                           --- *)
(* -------------------------------------------------------------------------- *)
  Request.register ~package
    ~kind:`GET ~name:"getCallstackInfo"
    ~descr:(Md.plain "Callstack Description")
    ~input:(module Jcallstack)
    ~output:(module Jcalls)
    begin fun cs -> cs end
(* -------------------------------------------------------------------------- *)
(* --- Request getStmtInfo                                                --- *)
(* -------------------------------------------------------------------------- *)

let () =
  let getStmtInfo = Request.signature ~input:(module Jstmt) () in
  let set_fct = Request.result getStmtInfo ~name:"fct"
      ~descr:(Md.plain "Englobing function")
      (module Jkf)
  and set_rank = Request.result getStmtInfo ~name:"rank"
      ~descr:(Md.plain "Global stmt order")
  Request.register_sig ~package getStmtInfo
    ~kind:`GET ~name:"getStmtInfo"
    ~descr:(Md.plain "Stmt Information")
    begin fun rq s ->
      set_fct rq (Kernel_function.find_englobing_kf s) ;
      set_rank rq (Ranking.stmt s) ;
    end

Loïc Correnson's avatar
Loïc Correnson committed
(* -------------------------------------------------------------------------- *)
(* --- Request getProbeInfo                                               --- *)
Loïc Correnson's avatar
Loïc Correnson committed
(* -------------------------------------------------------------------------- *)

let () =
  let getProbeInfo = Request.signature ~input:(module Jmarker) () in
  let set_code = Request.result_opt getProbeInfo
      ~name:"code" ~descr:(Md.plain "Probe source code")
      (module Jstring)
  and set_stmt = Request.result_opt getProbeInfo
      ~name:"stmt" ~descr:(Md.plain "Probe statement")
      (module Jstmt)
  and set_rank = Request.result getProbeInfo
      ~name:"rank" ~descr:(Md.plain "Probe statement rank")
      ~default:0 (module Jint)
  and set_effects = Request.result getProbeInfo
      ~name:"effects" ~descr:(Md.plain "Effectfull statement")
      ~default:false (module Jbool)
  and set_condition = Request.result getProbeInfo
      ~name:"condition" ~descr:(Md.plain "Conditional statement")
      ~default:false (module Jbool)
  in
  let set_probe rq pp p s =
    set_code rq (Some (Pretty_utils.to_string pp p)) ;
    set_stmt rq (Some s) ;
    set_rank rq (Ranking.stmt s) ;
    let on_steps = function
      | `Here -> ()
      | `Then _ | `Else _ -> set_condition rq true
      | `After -> set_effects rq true
    in List.iter on_steps (next_steps s)
  in
  Request.register_sig ~package getProbeInfo
    ~kind:`GET ~name:"getProbeInfo"
    ~descr:(Md.plain "Probe informations")
    begin fun rq marker ->
      match probe marker with
      | Plval (l, s) -> set_probe rq Printer.pp_lval l s
      | Pexpr (e, s) -> set_probe rq Printer.pp_exp  e s
      | Pnone -> ()
    end
Loïc Correnson's avatar
Loïc Correnson committed
(* -------------------------------------------------------------------------- *)
(* --- Request getValues                                                  --- *)
(* -------------------------------------------------------------------------- *)

let () =
  let getValues = Request.signature () in
  let get_tgt = Request.param getValues ~name:"target"
      ~descr:(Md.plain "Works with all markers containing an expression")
      (module Jmarker)
  and get_cs = Request.param_opt getValues ~name:"callstack"
      ~descr:(Md.plain "Callstack to collect (defaults to none)")
      (module Jcallstack)
  and set_alarms = Request.result getValues ~name:"alarms"
      ~descr:(Md.plain "Alarms raised during evaluation")
      (module Jlist(Jpair(Jtruth)(Jstring)))
  and set_domain = Request.result_opt getValues ~name:"values"
      ~descr:(Md.plain "Domain values")
      (module Jstring)
  and set_after = Request.result_opt getValues ~name:"v_after"
      ~descr:(Md.plain "Domain values after execution")
      (module Jstring)
  and set_then = Request.result_opt getValues ~name:"v_then"
      ~descr:(Md.plain "Domain values for true condition")
      (module Jstring)
  and set_else = Request.result_opt getValues ~name:"v_else"
      ~descr:(Md.plain "Domain values for false condition")
  Request.register_sig ~package getValues
    ~kind:`GET ~name:"getValues"
    ~descr:(Md.plain "Abstract values for the given marker")
    begin fun rq () ->
      let module A : EvaProxy = (val proxy ()) in
      let marker = get_tgt rq and callstack = get_cs rq in
      let domain = A.domain (probe marker) callstack in
      set_alarms rq domain.alarms ;
      let set_values = function
        | `Here,   values -> set_domain rq (Some values)
        | `After,  values -> set_after  rq (Some values)
        | `Then _, values -> set_then   rq (Some values)
        | `Else _, values -> set_else   rq (Some values)
      in List.iter set_values domain.values
(* -------------------------------------------------------------------------- *)
(* --- Request getPointedLvalues                                          --- *)
(* -------------------------------------------------------------------------- *)

let () =
  let getPointedLvalues = Request.signature () in
  let get_tgt = Request.param getPointedLvalues ~name:"pointer"
      ~descr:(Md.plain "Marker to the pointer we want to lookup")
      (module Jmarker)
  and get_cs = Request.param_opt getPointedLvalues ~name:"callstack"
      ~descr:(Md.plain "Callstack to collect (defaults to none)")
      (module Jcallstack)
  and set_lvalues = Request.result_opt getPointedLvalues ~name:"lvalues"
      ~descr:(Md.plain "List of pointed lvalues")
      (module Jlist(Jpair(Jstring)(Jmarker)))
  in
  Request.register_sig ~package getPointedLvalues
    ~kind:`GET ~name:"getPointedLvalues"
    ~descr:(Md.plain "Pointed lvalues for the given marker")
      let module A : EvaProxy = (val proxy ()) in
      let marker = get_tgt rq and callstack = get_cs rq in
      let kf = Printer_tag.kf_of_localizable marker in
      let ki = Printer_tag.ki_of_localizable marker in
      let pp = Pretty_utils.to_string Printer.pp_lval in
      let to_marker lval = pp lval, Printer_tag.PLval (kf, ki, lval) in
      let lvalues = A.pointed_lvalues (probe marker) callstack in
      Option.map (List.map to_marker) lvalues |> set_lvalues rq

(* -------------------------------------------------------------------------- *)