From 02bd49b4ba74079602968466011cb91cf99b1f62 Mon Sep 17 00:00:00 2001 From: Maxime Jacquemin <maxime2.jacquemin@gmail.com> Date: Tue, 2 Nov 2021 17:18:34 +0100 Subject: [PATCH] [ivette] Context Menu to add pointed lvalues to the Eva Component --- .../api/generated/plugins/eva/values/index.ts | 13 +- ivette/src/frama-c/plugins/eva/model.ts | 11 + ivette/src/frama-c/plugins/eva/valuetable.tsx | 14 +- src/plugins/value/api/values_request.ml | 287 ++++++++---------- 4 files changed, 153 insertions(+), 172 deletions(-) diff --git a/ivette/src/frama-c/api/generated/plugins/eva/values/index.ts b/ivette/src/frama-c/api/generated/plugins/eva/values/index.ts index 03d46e5b3fe..650b17bc090 100644 --- a/ivette/src/frama-c/api/generated/plugins/eva/values/index.ts +++ b/ivette/src/frama-c/api/generated/plugins/eva/values/index.ts @@ -178,18 +178,25 @@ export const getValues: Server.GetRequest< const getPointedLvalues_internal: Server.GetRequest< { callstack?: callstack, pointer: marker }, - { lvalues?: string[] } + { lvalues?: [ string, marker ][] } > = { kind: Server.RqKind.GET, name: 'plugins.eva.values.getPointedLvalues', input: Json.jObject({ callstack: jCallstack, pointer: jMarkerSafe,}), - output: Json.jObject({ lvalues: Json.jList(Json.jString),}), + output: Json.jObject({ + lvalues: Json.jList( + Json.jTry( + Json.jPair( + Json.jFail(Json.jString,'String expected'), + jMarkerSafe, + ))), + }), signals: [], }; /** */ export const getPointedLvalues: Server.GetRequest< { callstack?: callstack, pointer: marker }, - { lvalues?: string[] } + { lvalues?: [ string, marker ][] } >= getPointedLvalues_internal; /* ------------------------------------- */ diff --git a/ivette/src/frama-c/plugins/eva/model.ts b/ivette/src/frama-c/plugins/eva/model.ts index e2ecf1b76da..2deaedaf403 100644 --- a/ivette/src/frama-c/plugins/eva/model.ts +++ b/ivette/src/frama-c/plugins/eva/model.ts @@ -92,6 +92,17 @@ export class Model implements ModelCallbacks { return undefined; } + addProbe(location: States.Location) { + const { fct, marker } = location; + if (fct && marker) { + const probe = new Probe(this, fct, marker); + probe.setPersistent(); + probe.requestProbeInfo(); + this.probes.set(marker, probe); + this.forceLayout(); + } + } + getStacks(p: Probe | undefined): Values.callstack[] { return p ? this.stacks.getStacks(p.marker) : []; } diff --git a/ivette/src/frama-c/plugins/eva/valuetable.tsx b/ivette/src/frama-c/plugins/eva/valuetable.tsx index 01e8d8472f2..5c8bc4743a3 100644 --- a/ivette/src/frama-c/plugins/eva/valuetable.tsx +++ b/ivette/src/frama-c/plugins/eva/valuetable.tsx @@ -51,6 +51,8 @@ import { Callsite } from './stacks'; import { Stmt } from './valueinfos'; import './style.css'; +const D = new Dome.Debug('Source Code'); + // -------------------------------------------------------------------------- // --- Cell Diffs // -------------------------------------------------------------------------- @@ -186,14 +188,16 @@ function TableCell(props: TableCellProps) { .then((r) => { const lvalues = r.lvalues ?? []; const items: Dome.PopupMenuItem[] = lvalues.map((lval) => { - const label = `Display values for ${lval}`; - const onClick = () => {}; - return { label, onClick }; + const [text, lvalMarker] = lval; + const label = `Display values for ${text}`; + const location = { fct: probe.fct, marker: lvalMarker }; + const onItemClick = () => model.addProbe(location); + return { label, onClick: onItemClick }; }); if (items.length > 0) Dome.popupMenu(items); }) - .catch((err) => console.log(err)); - }; + .catch((err) => D.error(`Fail to recover pointed lvalues: ${err}`)); + } return ( <div diff --git a/src/plugins/value/api/values_request.ml b/src/plugins/value/api/values_request.ml index 402e5b4b793..29c1f4f46a2 100644 --- a/src/plugins/value/api/values_request.ml +++ b/src/plugins/value/api/values_request.ml @@ -24,6 +24,8 @@ open Server open Data open Cil_types + + module Kmap = Kernel_function.Hashtbl module Smap = Cil_datatype.Stmt.Hashtbl module CS = Value_types.Callstack @@ -63,6 +65,13 @@ let signal = Request.signal ~package ~name:"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 + + + (* -------------------------------------------------------------------------- *) (* --- Marker Utilities --- *) (* -------------------------------------------------------------------------- *) @@ -92,73 +101,71 @@ let probe marker = | PVDecl(_,Kstmt s,v) -> Plval((Var v,NoOffset),s) | _ -> Pnone + + (* -------------------------------------------------------------------------- *) (* --- Stmt Ranking --- *) (* -------------------------------------------------------------------------- *) -module Ranking : -sig +module type Ranking_sig = sig val stmt : stmt -> int val sort : callstack list -> callstack list -end = -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 - begin - Kmap.add fmark kf () ; - Queue.push kf fqueue ; - end - - 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) ; +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 -> - 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 + with Not_found -> self#newrank s - end + end let stmt = let rk = new ranker in rk#rank @@ -177,12 +184,13 @@ struct end + + (* -------------------------------------------------------------------------- *) (* --- Domain Utilities --- *) (* -------------------------------------------------------------------------- *) -module Jcallstack : S with type t = callstack = -struct +module Jcallstack : S with type t = callstack = struct module I = Data.Index (Value_types.Callstack.Map) (struct let name = "eva-callstack-id" end) @@ -192,10 +200,10 @@ struct let of_json = I.of_json end -module Jcalls : Request.Output with type t = callstack = -struct +module Jcalls : Request.Output with type t = callstack = struct type t = callstack + let jtype = Package.(Jlist (Jrecord [ "callee" , Jkf.jtype ; "caller" , Joption Jkf.jtype ; @@ -205,9 +213,7 @@ struct let rec jcallstack jcallee ki cs : json list = match ki , cs with - | Kglobal , _ | _ , [] -> [ - `Assoc [ "callee", jcallee ] - ] + | Kglobal , _ | _ , [] -> [ `Assoc [ "callee", jcallee ] ] | Kstmt stmt , (called,ki) :: cs -> let jcaller = Jkf.to_json called in let callsite = `Assoc [ @@ -215,8 +221,8 @@ struct "caller", jcaller ; "stmt", Jstmt.to_json stmt ; "rank", Jint.to_json (Ranking.stmt stmt) ; - ] in - callsite :: jcallstack jcaller ki cs + ] + in callsite :: jcallstack jcaller ki cs let to_json = function | [] -> `List [] @@ -224,13 +230,9 @@ struct end - - -module Jtruth : Data.S with type t = truth = -struct +module Jtruth : Data.S with type t = truth = struct type t = truth - let jtype = - Package.(Junion [ Jtag "True" ; Jtag "False" ; Jtag "Unknown" ]) + let jtype = Package.(Junion [ Jtag "True" ; Jtag "False" ; Jtag "Unknown" ]) let to_json = function | Abstract_interp.Unknown -> `String "Unknown" | True -> `String "True" @@ -241,19 +243,19 @@ struct | _ -> Abstract_interp.Unknown end + + (* -------------------------------------------------------------------------- *) (* --- EVA Proxy --- *) (* -------------------------------------------------------------------------- *) -module type EvaProxy = -sig +module type EvaProxy = sig val callstacks : stmt -> callstack list val domain : probe -> callstack option -> domain val pointed_lvalues : probe -> callstack option -> lval list option end -module Proxy(A : Analysis.S) : EvaProxy = -struct +module Proxy(A : Analysis.S) : EvaProxy = struct open Eval type dstate = A.Dom.state or_top_or_bottom @@ -274,6 +276,13 @@ struct | (`Top | `Bottom) as res -> res | `Value cmap -> try `Value (CSmap.find cmap cs) with Not_found -> `Bottom + let eval_lval lval state = + let value, alarms = A.copy_lvalue state lval in + let default = Eval.Flagged_Value.bottom in + (Bottom.default ~default value).v, alarms + + let eval_expr expr state = A.eval_expr state expr + let dalarms alarms = let descr = Format.asprintf "@[<hov 2>%a@]" Alarms.pretty in let f alarm status pool = (status, descr alarm) :: pool in @@ -288,16 +297,12 @@ struct | `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 handle_top_or_bottom ~top ~bottom compute = function - | `Bottom -> bottom - | `Top -> top - | `Value v -> compute v - let domain_step eval ((values, alarms) as acc) step = + let to_string = Pretty_utils.to_string (Bottom.pretty A.Val.pretty) 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, step_value) :: values, alarms + (step, to_string step_value) :: values, alarms let domain_eval eval stmt callstack = let fold = fold_steps (domain_step eval) stmt callstack in @@ -306,75 +311,29 @@ struct handle_top_or_bottom ~top:dtop ~bottom:dbottom compute_domain @@ dstate ~after:false stmt callstack - let e_expr expr state = - let value, alarms = A.eval_expr state expr in - let res = Pretty_utils.to_string (Bottom.pretty A.Val.pretty) value in - (res, alarms) - - let e_lval lval state = - let value, alarms = A.copy_lvalue state lval in - let flagged = Bottom.default value ~default:Eval.Flagged_Value.bottom in - let pp_flagged_val = Eval.Flagged_Value.pretty A.Val.pretty in - let res = Pretty_utils.to_string pp_flagged_val flagged in - (res, alarms) - let domain p callstack = match p with - | Plval (lval, stmt) -> domain_eval (e_lval lval) stmt callstack - | Pexpr (expr, stmt) -> domain_eval (e_expr expr) stmt callstack + | Plval (lval, stmt) -> domain_eval (eval_lval lval) stmt callstack + | Pexpr (expr, stmt) -> domain_eval (eval_expr expr) stmt callstack | Pnone -> dnone - let lvalues_of_zone zone = - 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 - in Locations.Zone.fold_bases var_of_base zone [] |> List.map Cil.var + 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 = - let enumerate = Precise_locs.enumerate_valid_bits Read in - let f get = fun loc -> get loc |> enumerate in - Option.(bind (A.Loc.get Main_locations.PLoc.key |> map f)) @@ fun get -> - let loc (value, typ) = A.Loc.forward_pointer typ value A.Loc.no_offset in - let lvalues data = loc data >>-: get >>-: lvalues_of_zone in - let compute state = eval_lval state >>- lvalues |> Bottom.to_option in + Option.(bind (A.Val.get Main_values.CVal.key)) @@ fun get -> + let loc state = eval_lval state |> fst >>-: get 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 lval_value_and_type lval state = - let value, _ = A.copy_lvalue state lval in - let flagged = Bottom.default value ~default:Eval.Flagged_Value.bottom in - flagged.v >>-: fun v -> (v, Cil.typeOfLval lval) - - let expr_value_and_type expr state = - fst @@ A.eval_expr state expr >>-: fun v -> (v, Cil.typeOf expr) - let pointed_lvalues p callstack = match p with - | Plval (lval, stmt) -> - compute_pointed_lvalues (lval_value_and_type lval) stmt callstack - | Pexpr (expr, stmt) -> - compute_pointed_lvalues (expr_value_and_type expr) stmt callstack - | Pnone -> None - - let compute_pointed_domains eval_lval stmt callstack = - let enumerate = Precise_locs.enumerate_valid_bits Read in - let f get = fun loc -> get loc |> enumerate in - let eval lval = (lval, domain_eval (e_lval lval) stmt callstack) in - Option.(bind (A.Loc.get Main_locations.PLoc.key |> map f)) @@ fun get -> - let loc value typ = A.Loc.forward_pointer typ value A.Loc.no_offset in - let lvalues value typ = loc value typ >>-: get >>-: lvalues_of_zone in - let domains (value, typ) = lvalues value typ >>-: List.map eval in - let compute state = eval_lval state >>- domains |> Bottom.to_option in - handle_top_or_bottom ~top:None ~bottom:None compute @@ - dstate ~after:true stmt callstack - - let pointed_domains p callstack = - match p with - | Plval (lval, stmt) -> - compute_pointed_domains (lval_value_and_type lval) stmt callstack - | Pexpr (expr, stmt) -> - compute_pointed_domains (expr_value_and_type expr) stmt callstack + | Plval (l, stmt) -> compute_pointed_lvalues (eval_lval l) stmt callstack + | Pexpr (e, stmt) -> compute_pointed_lvalues (eval_expr e) stmt callstack | Pnone -> None end @@ -382,12 +341,10 @@ 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 - begin fun a -> - current := make a ; - Request.emit signal ; - end - in current + let hook a = current := make a ; Request.emit signal in + Analysis.register_hook hook ; current + + (* -------------------------------------------------------------------------- *) (* --- Request getCallstacks --- *) @@ -410,6 +367,8 @@ let () = Request.register ~package Ranking.sort (CSet.elements cset) end + + (* -------------------------------------------------------------------------- *) (* --- Request getCallstackInfo --- *) (* -------------------------------------------------------------------------- *) @@ -422,6 +381,8 @@ let () = ~output:(module Jcalls) begin fun cs -> cs end + + (* -------------------------------------------------------------------------- *) (* --- Request getStmtInfo --- *) (* -------------------------------------------------------------------------- *) @@ -442,6 +403,8 @@ let () = set_rank rq (Ranking.stmt s) ; end + + (* -------------------------------------------------------------------------- *) (* --- Request getProbeInfo --- *) (* -------------------------------------------------------------------------- *) @@ -487,6 +450,8 @@ let () = | Pnone -> () end + + (* -------------------------------------------------------------------------- *) (* --- Request getValues --- *) (* -------------------------------------------------------------------------- *) @@ -535,24 +500,12 @@ let () = ) domain.values ; end + + (* -------------------------------------------------------------------------- *) (* --- Request getPointedLvalues --- *) (* -------------------------------------------------------------------------- *) -module Jlvalue = struct - type t = lval - let jtype = Package.Jstring - let to_json, of_json = - let tab = Hashtbl.create 10 in - let pp = Pretty_utils.to_string Printer.pp_lval in - let to_json lv = let s = pp lv in Hashtbl.add tab s lv ; `String s in - let of_json (json : Server.Data.json) = - match json with - | `String s -> (try Hashtbl.find tab s with Not_found -> assert false) - | _ -> assert false - in to_json, of_json -end - let () = let getPointedLvalues = Request.signature () in (* Inputs of the request *) @@ -566,7 +519,7 @@ let () = (* Outputs of the request *) let set_lvalues = Request.result_opt getPointedLvalues ~name:"lvalues" ~descr:(Md.plain "") - (module Jlist (Jlvalue)) + (module Jlist(Jpair(Jstring)(Jmarker))) in (* Register and request computation *) Request.register_sig ~package getPointedLvalues @@ -575,7 +528,13 @@ let () = begin fun rq () -> let module A : EvaProxy = (val !proxy) in let marker = get_tgt rq and callstack = get_cs rq in - A.pointed_lvalues (probe marker) callstack |> set_lvalues rq + 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 end (* -------------------------------------------------------------------------- *) + -- GitLab