(**************************************************************************) (* *) (* This file is part of Frama-C. *) (* *) (* Copyright (C) 2007-2018 *) (* 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 Cil_types (* vname, vaddrof *) ;; (* Formatting html with Format.formatters *) let html_tag_functions = let mark_open_tag t = Format.sprintf "<%s>" t and mark_close_tag t = try let index = String.index t ' ' in Format.sprintf "" (String.sub t 0 index) with | Not_found -> Format.sprintf "" t and print_open_tag _ = () and print_close_tag _ = () in { Format.mark_open_tag = mark_open_tag; Format.mark_close_tag = mark_close_tag; Format.print_open_tag = print_open_tag; Format.print_close_tag = print_close_tag; } ;; (* Utility function to have underlines the same length as the title. Underlines follow reStructuredText header conventions. *) let mk_hdr level ppf hdr_strg = let c = match level with | 1 -> '=' | 2 -> '-' | 3 -> '~' | _ -> assert false in let len = String.length hdr_strg in let underline = String.make len c in Format.fprintf ppf "@[%s@ %s@]" hdr_strg underline ; ;; module OptionKf = Datatype.Option_with_collections (Cil_datatype.Kf) (struct let module_name = "OptionKf" end) (** Defining base metrics and operations on those *) module BasicMetrics = struct (** Record type to compute cyclomatic complexity *) type t = { cfile_name : Datatype.Filepath.t; cfunc : OptionKf.t; cslocs: int; cifs: int; cloops: int; ccalls: int; cgotos: int; cassigns: int; cexits: int; cfuncs: int; cptrs: int; cdecision_points: int; cglob_vars: int; ccyclo: int; } ;; let empty_metrics = { cfile_name = Datatype.Filepath.dummy; cfunc = None; cslocs = 0; cifs = 0; cloops = 0; ccalls = 0; cgotos = 0; cassigns = 0; cexits = 0; cfuncs = 0; cptrs = 0; cdecision_points = 0; cglob_vars = 0; ccyclo = 0; } ;; let apply_then_set f metrics = metrics := f !metrics ;; let incr_slocs metrics = { metrics with cslocs = succ metrics.cslocs ;} ;; let incr_assigns metrics = { metrics with cassigns = succ metrics.cassigns ;} ;; let incr_calls metrics = { metrics with ccalls = succ metrics.ccalls ;} ;; let incr_exits metrics = { metrics with cexits = succ metrics.cexits ;} ;; let incr_funcs metrics = { metrics with cfuncs = succ metrics.cfuncs ;} ;; let incr_gotos metrics = { metrics with cgotos = succ metrics.cgotos ;} ;; let incr_ifs metrics = { metrics with cifs = succ metrics.cifs ;} ;; let incr_loops metrics = { metrics with cloops = succ metrics.cloops ;} ;; let incr_ptrs metrics = { metrics with cptrs = succ metrics.cptrs ;} ;; let incr_dpoints metrics = { metrics with cdecision_points = succ metrics.cdecision_points ;} ;; let incr_glob_vars metrics = { metrics with cglob_vars = succ metrics.cglob_vars ;} ;; let set_cyclo metrics cyclo = { metrics with ccyclo = cyclo ;} ;; (* Compute cyclomatic complexity of a given metrics record *) let compute_cyclo metrics = metrics.cdecision_points - metrics.cexits + 2 ;; let labels = [ "Sloc"; "Decision point"; "Global variables"; "If"; "Loop"; "Goto"; "Assignment"; "Exit point"; "Function"; "Function call"; "Pointer dereferencing"; "Cyclomatic complexity"; ] ;; let str_values metrics = List.map string_of_int [ metrics.cslocs; metrics.cdecision_points; metrics.cglob_vars; metrics.cifs; metrics.cloops; metrics.cgotos; metrics.cassigns; metrics.cexits; metrics.cfuncs; metrics.ccalls; metrics.cptrs; metrics.ccyclo; ] ;; let to_list metrics = List.map2 (fun x y -> [ x; y; ]) labels (str_values metrics) ;; let pp_func_or_none = Pretty_utils.pp_opt ~none:"" Kernel_function.pretty (* Pretty print metrics as text eg. in stdout *) let pp_base_metrics fmt metrics = let heading = if metrics.cfile_name = Datatype.Filepath.dummy && metrics.cfunc = None then (* It is a global metrics *) "Global metrics" else Format.asprintf "Stats for function <%a/%a>" Datatype.Filepath.pretty metrics.cfile_name pp_func_or_none metrics.cfunc in Format.fprintf fmt "@[%a @ %a@]" (mk_hdr 1) heading ((fun l1 ppf l2 -> List.iter2 (fun x y -> Format.fprintf ppf "%s = %s@ " x y) l1 l2) labels) (str_values metrics) ;; (* Dummy utility functions for pretty printing simple types *) let pp_int fmt n = Format.fprintf fmt "%d" n ;; type cell_type = | Classic | Entry ;; let cell_type_to_string = function | Entry -> "entry" | Classic -> "classic" ;; let pp_cell_type_html fmt cell_type = Format.fprintf fmt "class=\"%s\"" (cell_type_to_string cell_type) ;; (* Pretty print a HTML cell given a pretty printing function [pp_fun] and a value [pp_arg] *) let pp_cell cell_type pp_fun fmt pp_arg = Format.fprintf fmt "@{%a@}" pp_cell_type_html cell_type pp_fun pp_arg ;; let pp_cell_default = pp_cell Classic;; let pp_base_metrics_as_html_row fmt metrics = Format.fprintf fmt "\ @[\ @{@[@ \ @[%a@ %a@ %a@ %a@ %a@ %a@ %a@ %a@ %a@ @]@]\ @}@ @]" (pp_cell Entry pp_func_or_none) metrics.cfunc (pp_cell_default pp_int) metrics.cifs (pp_cell_default pp_int) metrics.cassigns (pp_cell_default pp_int) metrics.cloops (pp_cell_default pp_int) metrics.ccalls (pp_cell_default pp_int) metrics.cgotos (pp_cell_default pp_int) metrics.cptrs (pp_cell_default pp_int) metrics.cexits (pp_cell_default pp_int) metrics.ccyclo ;; end (* End of BasicMetrics *) (** {3 Filename utilities} *) exception No_suffix;; let get_suffix filename = try let slen = String.length filename in let last_idx = pred slen in let last_dot_idx = String.rindex_from filename last_idx '.' in if last_dot_idx < last_idx then String.sub filename (succ last_dot_idx) (slen - last_dot_idx - 1) else "" with | Not_found -> raise No_suffix ;; type output_type = | Html | Text ;; let get_file_type filename = try match get_suffix filename with | "html" | "htm" -> Html | "txt" | "text" -> Text | s -> Metrics_parameters.fatal "Unknown file extension %s. Cannot produce output.@." s with | No_suffix -> Metrics_parameters.fatal "File %s has no suffix. Cannot produce output.@." filename module VarinfoByName = struct type t = Cil_types.varinfo let compare v1 v2 = Pervasives.compare v1.vname v2.vname end (** Map and sets of varinfos sorted by name (and not by ids) *) module VInfoMap = FCMap.Make (VarinfoByName) module VInfoSet = FCSet.Make (VarinfoByName) (** Other pretty-printing and formatting utilities *) let pretty_set fmt s = Format.fprintf fmt "@["; VInfoMap.iter (fun f n -> Format.fprintf fmt "%s %s(%d call%s);@ " f.Cil_types.vname (if f.vaddrof then "(address taken) " else "") n (if n > 1 then "s" else "")) s; Format.fprintf fmt "@]" let pretty_extern_vars fmt s = Pretty_utils.pp_iter ~pre:"@[" ~suf:"@]" ~sep:";@ " VInfoSet.iter Printer.pp_varinfo fmt s let is_in_libc attrs = Cil.hasAttribute "fc_stdlib" attrs || Cil.hasAttribute "fc_stdlib_generated" attrs let is_entry_point vinfo times_called = times_called = 0 && not vinfo.vaddrof && not (is_in_libc vinfo.vattr) ;; let number_entry_points fs = VInfoMap.fold (fun fvinfo n acc -> if is_entry_point fvinfo n then succ acc else acc) fs 0 ;; let pretty_entry_points fmt fs = let print fmt = VInfoMap.iter (fun fvinfo n -> if is_entry_point fvinfo n then Format.fprintf fmt "%s;@ " fvinfo.vname) in Format.fprintf fmt "@[%a@]" print fs; ;; (* Utilities for CIL ASTs *) let file_of_vinfodef fvinfo = let kf = Globals.Functions.get fvinfo in let decl_loc1, _decl_loc2 = match kf.fundec with | Definition (_, loc) -> loc | Declaration (_, _, _, loc) -> loc in decl_loc1.Filepath.pos_path ;; let file_of_fundef (fun_dec: Cil_types.fundec) = file_of_vinfodef fun_dec.svar ;; (* Utilities for Cabs ASTs *) let extract_fundef_name sname = match sname with | _spec, (the_name, _, _, _) -> the_name ;; let kf_of_cabs_name sname = match sname with | _spec, (the_name, _, _, _) -> Globals.Functions.find_by_name the_name let get_filename fdef = match fdef with | Cabs.FUNDEF(_, _, _, (loc1, _), _loc2) -> loc1.Filepath.pos_path | _ -> assert false ;; let consider_function ~libc vinfo = not (!Db.Value.mem_builtin vinfo.vname || Ast_info.is_frama_c_builtin vinfo.vname || Cil.is_unused_builtin vinfo ) && (libc || not (is_in_libc vinfo.vattr)) let consider_variable ~libc vinfo = not (Cil.hasAttribute "FRAMA_C_MODEL" vinfo.vattr) && (libc || not (is_in_libc vinfo.vattr)) let float_to_string f = let s = Format.sprintf "%F" f in let len = String.length s in let plen = pred len in if s.[plen] = '.' then String.sub s 0 plen else Format.sprintf "%.2f" f (* Local Variables: compile-command: "make -C ../../.." End: *)