Newer
Older
(**************************************************************************)
(* *)
(* This file is part of Frama-C. *)
(* *)
(* 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 Md = Markdown
module Jkf = Kernel_ast.Kf
module Jstmt = Kernel_ast.Stmt
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
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
(* -------------------------------------------------------------------------- *)
(* -------------------------------------------------------------------------- *)
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 ]
| 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
(* -------------------------------------------------------------------------- *)
(* -------------------------------------------------------------------------- *)
module type Ranking_sig = sig
val stmt : stmt -> int
val sort : callstack list -> callstack list
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
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 ;
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
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
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
(* -------------------------------------------------------------------------- *)
(* --- EVA Proxy --- *)
(* -------------------------------------------------------------------------- *)
module type EvaProxy = sig
val domain : probe -> callstack option -> domain
val pointed_lvalues : probe -> callstack option -> lval list option
module Proxy(A : Analysis.S) : EvaProxy = struct
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."] }
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
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
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
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
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
| Pnone -> None
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
(* -------------------------------------------------------------------------- *)
(* -------------------------------------------------------------------------- *)
let () =
Request.register ~package
~kind:`GET ~name:"getCallstacks"
~descr:(Md.plain "Callstacks for markers")
~input:(module Jlist(Jmarker))
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 --- *)
(* -------------------------------------------------------------------------- *)
~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"
(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
(* -------------------------------------------------------------------------- *)
(* -------------------------------------------------------------------------- *)
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
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
(* -------------------------------------------------------------------------- *)
(* --- 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"
(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")
(module Jstring)
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.domain (probe marker) callstack in
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")
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")
begin fun rq () ->
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
(* -------------------------------------------------------------------------- *)