-
Adds a few comments.
Adds a few comments.
values_request.ml 22.42 KiB
(**************************************************************************)
(* *)
(* 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
module Md = Markdown
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
type callstack = Value_types.callstack
type truth = Abstract_interp.truth
(* The result of an evaluation:
- the resulting value as a text to be printed;
- the alarms emitted for the evaluation;
- the variables pointed by the resulting value, if any. *)
type evaluation = {
value: string;
alarms: ( truth * string ) list ;
pointed_vars: (string * Printer_tag.localizable) list;
}
(* Evaluations after the given statement. If the statement is a conditional
branch, evaluations in the [then] and [else] branch. *)
type 'v next =
| After of 'v
| Cond of 'v * 'v
| Nothing
type evaluations = {
here: evaluation;
next: evaluation next;
}
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)
(* -------------------------------------------------------------------------- *)
(* --- Marker Utilities --- *)
(* -------------------------------------------------------------------------- *)
let next_steps s =
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 _ -> []
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 --- *)
(* -------------------------------------------------------------------------- *)
module type Ranking_sig = sig
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
with Not_found -> self#newrank s
end
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)
end
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
end
(* -------------------------------------------------------------------------- *)
(* --- Utility functions for cvalue and offsetmaps --- *)
(* -------------------------------------------------------------------------- *)
type offsetmap =
| Offsetmap of Cvalue.V_Offsetmap.t
| Bottom | Empty | Top | InvalidLoc
let pp_offsetmap typ fmt = function
| Bottom -> Format.fprintf fmt "<BOTTOM>"
| Empty -> Format.fprintf fmt "<EMPTY>"
| Top -> Format.fprintf fmt "<NO INFORMATION>"
| InvalidLoc -> Format.fprintf fmt "<INVALID LOCATION>"
| Offsetmap offsm ->
Cvalue.V_Offsetmap.pretty_generic ~typ () fmt offsm ;
Eval_op.pretty_stitched_offsetmap fmt typ offsm
let extract_single_var vi state =
let b = Base.of_varinfo vi in
try
match Cvalue.Model.find_base b state with
| `Bottom -> Bottom
| `Value m -> Offsetmap m
| `Top -> Top
with Not_found -> InvalidLoc
let reduce_loc_and_eval state loc =
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 -> InvalidLoc
| `Value offsm -> Offsetmap offsm
with Abstract_interp.Error_Top -> Top
let find_offsetmap cvalue_state precise_loc =
let f loc acc =
match acc, reduce_loc_and_eval cvalue_state 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_locs.fold f precise_loc Bottom
(* Get pointed bases from a cvalue. *)
let get_bases cvalue =
try Base.SetLattice.project (Cvalue.V.get_bases cvalue)
with Abstract_interp.Error_Top -> Base.Hptset.empty
(* Get pointed bases from an offsetmap. *)
let get_pointed_bases = function
| Offsetmap offsm ->
Cvalue.V_Offsetmap.fold_on_values
(fun v acc ->
let v = Cvalue.V_Or_Uninitialized.get_v v in
Base.Hptset.union acc (get_bases v))
offsm Base.Hptset.empty
| Bottom | Empty | Top | InvalidLoc -> Base.Hptset.empty
(* Only keep a list of C variables from both previous functions. *)
let filter_variables bases =
let add_var base acc =
try Base.to_varinfo base :: acc
with Base.Not_a_C_variable -> acc
in
let vars = List.rev (Base.Hptset.fold add_var bases []) in
List.filter (fun vi -> not (Cil.isFunctionType vi.vtype)) vars
(* -------------------------------------------------------------------------- *)
(* --- EVA Proxy --- *)
(* -------------------------------------------------------------------------- *)
module type EvaProxy = sig
val callstacks : stmt -> callstack list
val evaluate : probe -> callstack option -> evaluations
end
module Proxy(A : Analysis.S) : EvaProxy = struct
open Eval
type dstate = A.Dom.state or_top_or_bottom
let get_precise_loc =
match A.Loc.get Main_locations.PLoc.key with
| None -> fun _ -> Precise_locs.loc_top
| Some get -> get
let get_cvalue =
let default = fun _ -> Cvalue.V.top in
Option.value ~default (A.Val.get Main_values.CVal.key)
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
(* --- Converts an evaluation [result] into an exported [value]. ---------- *)
(* Result of an evaluation: a generic value for scalar types, or an offsetmap
for struct and arrays. *)
type result =
| Value of A.Val.t
| Offsetmap of offsetmap
let pp_result typ fmt = function
| Value v -> A.Val.pretty fmt v
| Offsetmap offsm -> pp_offsetmap typ fmt offsm
let get_pointed_bases = function
| Value v -> get_bases (get_cvalue v)
| Offsetmap offsm -> get_pointed_bases offsm
let get_pointed_markers stmt result =
let bases = get_pointed_bases result in
let vars = filter_variables bases in
let kf =
try Some (Kernel_function.find_englobing_kf stmt)
with Not_found -> None
in
let to_marker vi =
let text = Pretty_utils.to_string Printer.pp_varinfo vi in
let marker = Printer_tag.PLval (kf, Kstmt stmt, Cil.var vi) in
text, marker
in
List.map to_marker vars
(* Creates an exported [value] from an evaluation result. *)
let make_value typ stmt (result, alarms) =
let alarms =
let descr = Format.asprintf "@[<hov 2>%a@]" Alarms.pretty in
let f alarm status acc = (status, descr alarm) :: acc in
Alarmset.fold f [] alarms |> List.rev
in
let pretty_eval = Bottom.pretty (pp_result typ) in
let value = Pretty_utils.to_string pretty_eval result in
let pointed_vars =
Bottom.fold ~bottom:[] (get_pointed_markers stmt) result
in
{ value; alarms; pointed_vars }
(* --- Evaluates an expression or lvalue into an evaluation [result]. ----- *)
let lval_to_offsetmap lval state =
let cvalue_state = A.Dom.get_cvalue_or_top state in
match lval with
| Var vi, NoOffset ->
let r = extract_single_var vi cvalue_state in
`Value r, Alarmset.none
| _ ->
A.eval_lval_to_loc state lval >>=: fun loc ->
let precise_loc = get_precise_loc loc in
find_offsetmap cvalue_state precise_loc
let eval_lval lval state =
match Cil.(unrollType (typeOfLval lval)) with
| TInt _ | TEnum _ | TPtr _ | TFloat _ ->
A.copy_lvalue state lval >>=. fun value ->
value.v >>-: fun v -> Value v
| _ ->
lval_to_offsetmap lval state >>=: fun offsm -> Offsetmap offsm
let eval_expr expr state =
A.eval_expr state expr >>=: fun value -> Value value
(* --- Evaluates all steps (before/after the statement). ------------------ *)
let do_next eval state stmt callstack =
match stmt.skind with
| If (cond, _, _, _) ->
let then_state = (A.assume_cond stmt state cond true :> dstate) in
let else_state = (A.assume_cond stmt state cond false :> dstate) in
Cond (eval then_state, eval else_state)
| Instr (Set _ | Call _ | Local_init _) ->
let after_state = dstate ~after:true stmt callstack in
After (eval after_state)
| _ -> Nothing
let eval_steps typ eval stmt callstack =
let default value = { value; alarms = []; pointed_vars = []; } in
let eval = function
| `Bottom -> default "Unreachable"
| `Top -> default "No information"
| `Value state -> make_value typ stmt (eval state)
in
let before = dstate ~after:false stmt callstack in
let here = eval before in
let next =
match before with
| `Bottom | `Top -> Nothing
| `Value state -> do_next eval state stmt callstack
in
{ here; next; }
let evaluate p callstack =
match p with
| Plval (lval, stmt) ->
eval_steps (Cil.typeOfLval lval) (eval_lval lval) stmt callstack
| Pexpr (expr, stmt) ->
eval_steps (Cil.typeOf expr) (eval_expr expr) stmt callstack
| Pnone -> assert false
end
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
(* -------------------------------------------------------------------------- *)
(* --- Request getCallstacks --- *)
(* -------------------------------------------------------------------------- *)
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)
end
(* -------------------------------------------------------------------------- *)
(* --- Request getCallstackInfo --- *)
(* -------------------------------------------------------------------------- *)
let () =
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")
(module Jint)
in
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
(* -------------------------------------------------------------------------- *)
(* --- Request getProbeInfo --- *)
(* -------------------------------------------------------------------------- *)
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
(* -------------------------------------------------------------------------- *)
(* --- Request getValues --- *)
(* -------------------------------------------------------------------------- *)
module JEvaluation = struct
open Server.Data
type record
let record: record Record.signature = Record.signature ()
let value = Record.field record ~name:"value"
~descr:(Markdown.plain "Textual representation of the value")
(module Data.Jstring)
let alarms = Record.field record ~name:"alarms"
~descr:(Markdown.plain "Alarms raised by the evaluation")
(module Jlist (Jpair (Jtruth) (Jstring)))
let pointed_vars = Record.field record ~name:"pointed_vars"
~descr:(Markdown.plain "List of variables pointed by the value")
(module Jlist (Jpair (Jstring) (Jmarker)))
let data = Record.publish record ~package ~name:"evaluation"
~descr:(Markdown.plain "Evaluation of an expression or lvalue")
module R: Record.S with type r = record = (val data)
type t = evaluation
let jtype = R.jtype
let to_json t =
R.default |>
R.set value t.value |>
R.set alarms t.alarms |>
R.set pointed_vars t.pointed_vars |>
R.to_json
end
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_before = Request.result getValues ~name:"v_before"
~descr:(Md.plain "Domain values before execution")
(module JEvaluation)
and set_after = Request.result_opt getValues ~name:"v_after"
~descr:(Md.plain "Domain values after execution")
(module JEvaluation)
and set_then = Request.result_opt getValues ~name:"v_then"
~descr:(Md.plain "Domain values for true condition")
(module JEvaluation)
and set_else = Request.result_opt getValues ~name:"v_else"
~descr:(Md.plain "Domain values for false condition")
(module JEvaluation)
in
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.evaluate (probe marker) callstack in
set_before rq domain.here;
match domain.next with
| After value -> set_after rq (Some value)
| Cond (v_then, v_else) ->
set_then rq (Some v_then);
set_else rq (Some v_else)
| Nothing -> ()
end
(* -------------------------------------------------------------------------- *)