From 6874b8e1a847443370b271a988c1c0adbbfe2b71 Mon Sep 17 00:00:00 2001 From: Andre Maroneze <andre.maroneze@cea.fr> Date: Fri, 4 Feb 2022 10:35:10 +0100 Subject: [PATCH] [Metrics] compute data for a pivot table --- Makefile | 2 +- configure.in | 1 + src/plugins/metrics/metrics_pivot.ml | 543 ++++++++++++++++++++++++++ src/plugins/metrics/metrics_pivot.mli | 23 ++ 4 files changed, 568 insertions(+), 1 deletion(-) create mode 100644 src/plugins/metrics/metrics_pivot.ml create mode 100644 src/plugins/metrics/metrics_pivot.mli diff --git a/Makefile b/Makefile index 0dd665f8262..2452b59ae85 100644 --- a/Makefile +++ b/Makefile @@ -784,7 +784,7 @@ PLUGIN_NAME:=Metrics PLUGIN_DISTRIBUTED:=yes PLUGIN_DIR:=src/plugins/metrics PLUGIN_CMO:= metrics_parameters css_html metrics_base metrics_acsl \ - metrics_cabs metrics_cilast metrics_coverage \ + metrics_cabs metrics_cilast metrics_coverage metrics_pivot \ register PLUGIN_GUI_CMO:= metrics_gui register_gui PLUGIN_DEPENDENCIES:=Eva diff --git a/configure.in b/configure.in index 54d56f2f332..faaf96d4eca 100644 --- a/configure.in +++ b/configure.in @@ -783,6 +783,7 @@ plugin_require(inout,callgraph) ######### check_plugin(metrics,src/plugins/metrics,[support for metrics analysis],yes) +plugin_require(metrics,server) plugin_require(metrics,eva) plugin_use(metrics,gui) diff --git a/src/plugins/metrics/metrics_pivot.ml b/src/plugins/metrics/metrics_pivot.ml new file mode 100644 index 00000000000..3ef5901e013 --- /dev/null +++ b/src/plugins/metrics/metrics_pivot.ml @@ -0,0 +1,543 @@ +(**************************************************************************) +(* *) +(* 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). *) +(* *) +(**************************************************************************) + +(* Data for a GUI-based pivot table *) + +open Cil_types + +module PivotSourceState = + State_builder.List_ref + (Datatype.List(Datatype.String)) + (struct + let name = "PivotSourceState" + let dependencies = [ Ast.self; Db.Value.self; Property_status.self; + Messages.self ] + end) + +type syntax_domain = + | Code + | Declaration + | Annotation + +type message_domain = Log.kind + +type domain = + | Syntax of syntax_domain + | Property + | Message of message_domain + +let string_of_string_list sep = + Format.asprintf "%a" + (Pretty_utils.pp_list ~pre:"" ~suf:"" ~sep Format.pp_print_string) + +let split_loc (loc : Cil_types.location) = + let fp = (fst loc).pos_path in + let line = string_of_int (fst loc).pos_lnum in + let fp_pretty = Filepath.Normalized.to_pretty_string fp in + let dir = Filename.dirname fp_pretty in + let name = Filename.basename fp_pretty in + let ext = Filename.extension name in + dir, name, ext, line + +let node_of_instr = function + | Set _ -> "assignment" + | Call _ -> "call" + | Local_init _ -> "local_init" + | Asm _ -> "asm" + | Skip _ -> "nop" + | Code_annot _ -> + (* should be done by the annotations visitor *) + assert false + +let opt_node_of_stmtkind = function + | Instr _ -> None + | Return (_, _) -> Some "return" + | Goto (_, _) -> Some "goto" + | Break _ -> Some "break" + | Continue _ -> Some "continue" + | If (_, _, _, _) -> Some "if" + | Switch (_, _, _, _) -> Some "switch" + | Loop (_, _, _, _, _) -> Some "loop" + | Block _ -> None + | UnspecifiedSequence _ -> Some "unspecified_sequence" + | Throw (_, _) -> Some "throw" + | TryCatch (_, _, _) -> Some "try_catch" + | TryFinally (_, _, _) -> Some "try_finally" + | TryExcept (_, _, _, _) -> Some "try_except" + +let node_of_code_annotation_node = function + | AAssert _ -> "assert" + | AStmtSpec _ -> "stmt_spec" + | AInvariant _ -> "invariant" + | AVariant _ -> "variant" + | AAssigns _ -> "assigns" + | AAllocation _ -> "allocation" + | APragma _ -> "ca_pragma" + | AExtended _ -> "extended" + +let node_of_global_annotation = function + | Dfun_or_pred _ -> "logic_fun_or_pred" + | Dvolatile _ -> "logic_volatile" + | Daxiomatic _ -> "axiomatic" + | Dtype _ -> "logic_type" + | Dlemma _ -> "logic_lemma" + | Dinvariant _ -> "invariant" + | Dtype_annot _ -> "type_annot" + | Dmodel_annot _ -> "model_annot" + | Dextended _ -> "ga_extended" + +let domain_and_node_of_global = function + | GType _ -> Declaration, "gtype" + | GCompTag _ -> Declaration, "gcomptag" + | GCompTagDecl _ -> Declaration, "gcomptagdecl" + | GEnumTag _ -> Declaration, "genumtag" + | GEnumTagDecl _ -> Declaration, "genumtagdecl" + | GVarDecl _ -> Declaration, "global_var" + | GFunDecl _ -> Declaration, "gfundecl" + | GVar _ -> Declaration, "gvar" + | GFun _ -> Declaration, "gfun" + | GAsm _ -> Code, "gasm" + | GPragma _ -> Code, "gpragma" + | GText _ -> Code, "gtext" + | GAnnot _ -> Code, "gannot" + +let names_of_global = function + | GType (_, _) -> [] + | GCompTag (_, _) -> [] + | GCompTagDecl (_, _) -> [] + | GEnumTag (_, _) -> [] + | GEnumTagDecl (_, _) -> [] + | GVarDecl (vi, _) -> [vi.vname] + | GFunDecl (_, vi, _) -> [vi.vname] + | GVar (vi, _, _) -> [vi.vname] + | GFun ({svar}, _) -> [svar.vname] + | GAsm (_, _) -> [] + | GPragma (_, _) -> [] + | GText _ -> [] + | GAnnot (_, _) -> [] + +let name_of_logic_info li = li.l_var_info.lv_name + +let name_of_global_annotation = function + | Dvolatile (_, _, _, _, _) -> None + | Dfun_or_pred (li, _) + | Dinvariant (li, _) + | Dtype_annot (li, _) -> Some (name_of_logic_info li) + | Daxiomatic (name, _, _, _) + | Dtype ({lt_name = name}, _) + | Dlemma (name, _, _, _, _, _) + | Dmodel_annot ({mi_name = name}, _) + | Dextended ({ext_name = name}, _, _) -> Some name + +let node_of_predicate_kind = function + | Assert -> "assert" + | Check -> "check" + | Admit -> "admit" + +let node_of_property = function + | Property.IPPredicate idp -> + Format.asprintf "%a" Property.pretty_predicate_kind idp.ip_kind + | IPCodeAnnot ica -> node_of_code_annotation_node ica.ica_ca.annot_content + | IPAxiomatic _ -> "axiomatic" + | IPLemma _ -> "lemma" + | IPBehavior _ -> "behavior" + | IPComplete _ -> "complete" + | IPDisjoint _ -> "disjoint" + | IPAllocation _ -> "allocates" + | IPAssigns _ -> "assigns" + | IPFrom _ -> "from" + | IPDecrease _ -> "loop variant" + | IPExtended _ -> "extended" + | IPReachable _ -> "reachable" + | IPPropertyInstance _ -> "instance" + | IPTypeInvariant _ -> "type_invariant" + | IPGlobalInvariant _ -> "global_invariant" + | IPOther _ -> "other" + +let names_of_property = function + | Property.IPPredicate idp -> idp.ip_pred.ip_content.tp_statement.pred_name + | IPCodeAnnot _ -> [] + | IPAxiomatic _ -> [] + | IPLemma _ -> [] + | IPBehavior _ -> [] + | IPComplete _ -> [] + | IPDisjoint _ -> [] + | IPAllocation _ -> [] + | IPAssigns _ -> [] + | IPFrom _ -> [] + | IPDecrease _ -> [] + | IPExtended _ -> [] + | IPReachable _ -> [] + | IPPropertyInstance _ -> [] + | IPTypeInvariant _ -> [] + | IPGlobalInvariant _ -> [] + | IPOther _ -> [] + +let plugin_of_emitters el = + match el with + | [] + | ["Call Preconditions"] + | ["Frama-C kernel"] -> "kernel" + | ["Eva"] -> "eva" + | _ -> "<unknown emitters (" ^ string_of_string_list "," el ^ ")>" + +let string_of_syntax_domain = function + | Code -> "code" + | Declaration -> "decl" + | Annotation -> "annot" + +let string_of_message_domain = function + | Log.Result -> "result" + | Feedback -> "feedback" + | Debug -> "debug" + | Warning -> "warning" + | Error -> "error" + | Failure -> "failure" + +let split_domain = function + | Syntax d -> "syntax", string_of_syntax_domain d + | Property -> "property", "" + | Message d -> "message", string_of_message_domain d + +module FunctionAtPos = struct + let tbl : + (Filepath.Normalized.t, + (Filepath.position * Filepath.position * string) Array.t) + Hashtbl.t = + Hashtbl.create 16 + + let binary_search a pos : string option = + let cmp = Cil_datatype.Position.compare in + let rec aux lo hi = + if lo > hi then None + else + let mid = (hi + lo) / 2 in + let (mstart, mend, mfunc) = a.(mid) in + if cmp pos mstart >= 0 then + if cmp pos mend <= 0 then Some mfunc + else aux (mid + 1) hi + else aux lo (mid - 1) + in + aux 0 (Array.length a - 1) + + let compute () = + let tmp = Hashtbl.create 16 in + let files = + List.fold_left (fun acc ((pos1, _, _) as triple) -> + let fp = pos1.Filepath.pos_path in + Hashtbl.add tmp fp triple; + Datatype.Filepath.Set.add fp acc + ) Datatype.Filepath.Set.empty (Cabs2cil.func_locs ()) + in + Hashtbl.clear tbl; + Datatype.Filepath.Set.iter (fun fp -> + let l = + List.sort (fun (start1, _, _) (start2, _, _) -> + Cil_datatype.Position.compare start1 start2 + ) (Hashtbl.find_all tmp fp) + in + Hashtbl.replace tbl fp (Array.of_list l) + ) files + + let find pos = + let fp = pos.Filepath.pos_path in + Option.bind (Hashtbl.find_opt tbl fp) + (fun a -> binary_search a pos) + +end + +type entry = { + loc: Cil_datatype.Location.t; + func: string option; + domain: domain; + plugin: string; + status: string; + node: string; + names: string list; + text: string; +} + +let headers = ["Directory"; "Filename"; "Extension"; "Line Number"; + "Function"; "Domain"; "Kind"; "Plugin"; "Status"; "Node"; + "Names"; "Text"] + +let add_entry_str entry = + let dir, fname, ext, line = split_loc entry.loc in + let funcname = Option.fold ~none:"" ~some:Stdlib__fun.id entry.func in + let domain, kind = split_domain entry.domain in + let names = string_of_string_list ":" entry.names in + let entry = [dir; fname; ext; line; funcname; + domain; kind; entry.plugin; entry.status; entry.node; + names; entry.text] + in + if PivotSourceState.get () = [] then + PivotSourceState.add headers; + PivotSourceState.add entry + +let new_entry ?func ?(plugin="") ?(status="") ?(node="") ?(names=[]) ?(text="") loc domain = + { loc; func; domain; plugin; status; node; names; text } + +let add_entry ?func ?plugin ?status ?node ?names ?text loc domain = + let entry = new_entry ?func ?plugin ?status ?node ?names ?text loc domain in + add_entry_str entry + +class full_visitor = object(self) + inherit Cil.nopCilVisitor + val mutable cur_func = None + method add ?func ?node ?names domain = + let loc = Cil.CurrentLoc.get () in + let func = if func <> None && func <> Some "" then func else cur_func in + add_entry ?func ?node ?names loc domain + method add_code ?func ?names node = + self#add ?func ~node ?names (Syntax Code) + method add_decl ?func ?names node = + self#add ?func ~node ?names (Syntax Declaration) + method add_annot ?names node = + self#add ~node ?names (Syntax Annotation) + + method! vvrbl (_v:varinfo) = DoChildren + method! vvdec (_v:varinfo) = DoChildren + method! vexpr (_e:exp) = DoChildren + method! vlval (_l:lval) = DoChildren + method! voffs (_o:offset) = DoChildren + method! vinitoffs (_o:offset) = DoChildren + + method! vinst (i:instr) = + let node = node_of_instr i in + self#add_code node; + DoChildren + + method! vstmt (s:stmt) = + begin + match opt_node_of_stmtkind s.skind with + | None -> () + | Some node -> self#add_code node + end; + DoChildren + + method! vfunc (f:fundec) = + cur_func <- Some f.svar.vname; + Cil.DoChildrenPost (fun fd -> + cur_func <- None; + fd + ) + + method! vglob (g:global) = + let domain, node = domain_and_node_of_global g in + let names = names_of_global g in + let func = "<global>" in + if domain = Declaration then + self#add_decl ~func ~names node + else + self#add_code ~func ~names node; + DoChildren + + method! vblock _ = DoChildren + method! vinit _ _ _ = DoChildren + method! vlocal_init _ _ = DoChildren + method! vtype _ = DoChildren + method! vcompinfo _ = DoChildren + method! venuminfo _ = DoChildren + method! vfieldinfo _ = DoChildren + method! venumitem _ = DoChildren + method! vattr _ = DoChildren + method! vattrparam _ = DoChildren + method! vmodel_info _ = DoChildren + method! vlogic_type _ = DoChildren + method! videntified_term _ = DoChildren + method! vterm _ = DoChildren + method! vterm_node _ = DoChildren + method! vterm_lval _ = DoChildren + method! vterm_lhost _ = DoChildren + method! vterm_offset _ = DoChildren + method! vlogic_label _ = DoChildren + method! vlogic_info_decl _ = DoChildren + method! vlogic_info_use _ = DoChildren + method! vlogic_type_info_decl _ = DoChildren + method! vlogic_type_info_use _ = DoChildren + method! vlogic_type_def _ = DoChildren + method! vlogic_ctor_info_decl _ = DoChildren + method! vlogic_ctor_info_use _ = DoChildren + method! vlogic_var_use _ = DoChildren + method! vlogic_var_decl _ = DoChildren + method! vquantifiers _ = DoChildren + method! videntified_predicate _ = + (* should be done by the annotations visitor *) + assert false + method! vpredicate_node _ = DoChildren + method! vpredicate _ = DoChildren + method! vbehavior _ = DoChildren + method! vassigns _ = DoChildren + method! vfrees _ = DoChildren + method! vallocates _ = DoChildren + method! vallocation _ = DoChildren + method! vloop_pragma _ = DoChildren + method! vslice_pragma _ = DoChildren + method! vimpact_pragma _ = DoChildren + method! vdeps _ = DoChildren + method! vfrom _ = DoChildren + method! vcode_annot _ = + (* should be done by the annotations visitor *) + assert false + method! vannotation ga = + let node = node_of_global_annotation ga in + let names = Option.to_list (name_of_global_annotation ga) in + self#add_annot ~names node; + DoChildren +end + +let ca_visitor_cur_func : string option ref = ref None +let ca_visitor_cur_emitter = ref "<unknown>" +class code_annot_visitor = object(self) + inherit Cil.nopCilVisitor + + method add_annot ?(names=[]) node = + let loc = Cil.CurrentLoc.get () in + let func = !ca_visitor_cur_func in + let plugin = !ca_visitor_cur_emitter in + let domain = Syntax Annotation in + add_entry ?func ~plugin ~node ~names loc domain + + method! videntified_predicate {ip_content = {tp_kind}} = + let node = node_of_predicate_kind tp_kind in + self#add_annot node; + DoChildren + + method! vcode_annot ca = + let content = ca.annot_content in + self#add_annot (node_of_code_annotation_node content); + DoChildren + + method! vannotation ga = + let node = node_of_global_annotation ga in + let names = Option.to_list (name_of_global_annotation ga) in + self#add_annot ~names node; + DoChildren +end + +let visit_annots () = + let vis = new code_annot_visitor in + Annotations.iter_all_code_annot ( + fun stmt emitter ca -> + let kf = Kernel_function.find_englobing_kf stmt in + ca_visitor_cur_func := Some (Kernel_function.get_name kf); + ca_visitor_cur_emitter := Emitter.get_name emitter; + ignore (Cil.visitCilCodeAnnotation (vis :> Cil.cilVisitor) ca) + ) + +let visit_properties () = + Property_status.iter + (fun ip -> + let loc = Property.location ip in + let func = Option.map Kernel_function.get_name (Property.get_kf ip) in + let emitters = + Property_status.fold_on_statuses (fun emitter _status acc -> + Emitter.Usable_emitter.get_name emitter.emitter :: acc + ) ip [] + in + let plugin = plugin_of_emitters emitters in + let domain = Property in + let status = + Format.asprintf "%a" + Property_status.Feedback.pretty (Property_status.Feedback.get ip) + in + let node = node_of_property ip in + let names = names_of_property ip in + add_entry ?func ~node ~status ~plugin ~names loc domain + ) + +let visit_messages () = + FunctionAtPos.compute (); + Messages.iter (fun ev -> + let plugin = ev.evt_plugin in + let loc_of_pos p = (p, p) in + let loc, func = + match ev.evt_source with + | None -> Cil_datatype.Location.unknown, "<global>" + | Some pos -> + let funcname = + match FunctionAtPos.find pos with + | None -> Metrics_parameters.warning + "FUNCTION NOT FOUND FOR NON-GLOBAL MESSAGE POS: %a" + Cil_datatype.Position.pretty_debug pos; + "<unknown>" + | Some name -> name + in + loc_of_pos pos, funcname + in + let domain = Message (ev.evt_kind) in + let text = ev.evt_message in + add_entry ~func ~plugin ~text loc domain + ) + +(* Useful mainly for debugging *) +let _pp_as_csv (data : string list list) = + let pp_list = string_of_string_list "," in + List.iter (fun ls -> Format.printf "%s@\n" (pp_list ls)) (List.rev data) + +(* Server / Ivette stuff *) + +open Server + +let package = + Package.package + ~plugin:"pivot" + ~name:"general" + ~title:"Pivot Table Services" + ~readme:"pivot.md" + () + +module TableState = struct + type t = string list list + let jtype = + Data.declare ~package + ~name:"tableStateType" + ~descr:(Markdown.plain "State of the pivot table source data.") + Package.(Jlist (Jlist Jstring)) + let to_json ll = + `List (List.rev_map (fun l -> `List (List.map (fun s -> `String s) l)) ll) +end + +let pivot_signal = + States.register_value ~package + ~name:"pivotState" + ~descr:(Markdown.plain "State of the pivot table source data.") + ~output:(module TableState) + ~get:PivotSourceState.get + () + +let compute () = + let ast = Ast.get () in + let vis = new full_visitor in + ignore (Cil.visitCilFile (vis :> Cil.cilVisitor) ast); + visit_annots (); + visit_properties (); + visit_messages (); + (*_pp_as_csv (PivotSourceState.get ());*) + (* Signals that the pivot table has been updated. *) + Server.Request.emit pivot_signal + +let _compute = + Server.Request.register ~package + ~kind:`EXEC ~name:"compute" + ~descr:(Markdown.plain "Computes the pivot table.") + ~input:(module Data.Junit) ~output:(module Data.Junit) compute diff --git a/src/plugins/metrics/metrics_pivot.mli b/src/plugins/metrics/metrics_pivot.mli new file mode 100644 index 00000000000..98ffe7ef308 --- /dev/null +++ b/src/plugins/metrics/metrics_pivot.mli @@ -0,0 +1,23 @@ +(**************************************************************************) +(* *) +(* 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). *) +(* *) +(**************************************************************************) + +(** Nothing is exported. *) -- GitLab