You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1193 lines
46 KiB
1193 lines
46 KiB
(*
|
|
* Copyright (c) 2009 - 2013 Monoidics ltd.
|
|
* Copyright (c) 2013 - present Facebook, Inc.
|
|
* All rights reserved.
|
|
*
|
|
* This source code is licensed under the BSD style license found in the
|
|
* LICENSE file in the root directory of this source tree. An additional grant
|
|
* of patent rights can be found in the PATENTS file in the same directory.
|
|
*)
|
|
|
|
open! Utils
|
|
|
|
module L = Logging
|
|
module F = Format
|
|
open Jsonbug_j
|
|
|
|
(** Outfile to save the latex report *)
|
|
let latex = ref None
|
|
|
|
(** command line flag: if true, print whole seconds only *)
|
|
let whole_seconds = ref false
|
|
|
|
(** Outfile to save bugs stats in csv format *)
|
|
let bugs_csv = ref None
|
|
|
|
(** Outfile to save bugs stats in JSON format *)
|
|
let bugs_json = ref None
|
|
|
|
(** Outfile to save bugs stats in txt format *)
|
|
let bugs_txt = ref None
|
|
|
|
(** Outfile to save bugs stats in xml format *)
|
|
let bugs_xml = ref None
|
|
|
|
(** Outfile to save procedures stats in csv format *)
|
|
let procs_csv = ref None
|
|
|
|
(** Outfile to save procedures stats in xml format *)
|
|
let procs_xml = ref None
|
|
|
|
(** Outfile to save call stats in csv format *)
|
|
let calls_csv = ref None
|
|
|
|
(** Outfile to save the analysis report *)
|
|
let report = ref None
|
|
|
|
(** command line flag: if true, produce a svg file *)
|
|
let svg = ref false
|
|
|
|
(** command line flag: if true, export specs to xml files *)
|
|
let xml_specs = ref false
|
|
|
|
(** command line flag: if true, produce unit test for each spec *)
|
|
let unit_test = ref false
|
|
|
|
(** command line flag: if true, do not print the spec to standard output *)
|
|
let quiet = ref false
|
|
|
|
(** command line flag: if true, print stats about preconditions to standard output *)
|
|
let precondition_stats = ref false
|
|
|
|
(** name of the file to load analysis results from *)
|
|
let load_analysis_results = ref None
|
|
|
|
(** If true then include Infer source code locations in json reports *)
|
|
let reports_include_ml_loc = ref false
|
|
|
|
(** name of the file to load save results to *)
|
|
let save_analysis_results = ref None
|
|
|
|
(** command-line option to print the location of the copy of a source file *)
|
|
let source_file_copy = ref None
|
|
|
|
(** command line option to test the filtering based on .inferconfig *)
|
|
let test_filtering = ref false
|
|
|
|
(** Setup the analyzer in order to filter out errors for this analyzer only *)
|
|
let analyzer = ref None
|
|
|
|
let handle_source_file_copy_option () = match !source_file_copy with
|
|
| None -> ()
|
|
| Some source_file ->
|
|
let source_in_resdir = DB.source_file_in_resdir source_file in
|
|
F.fprintf F.std_formatter "%s@." (DB.filename_to_string source_in_resdir);
|
|
exit 0
|
|
|
|
let canonic_path_from_string s =
|
|
if s = Filename.dir_sep then s
|
|
else Filename.concat (Filename.dirname s) (Filename.basename s) ^ Filename.dir_sep
|
|
|
|
let arg_desc =
|
|
let base_arg =
|
|
let desc =
|
|
[
|
|
"-bugs",
|
|
Arg.String (fun s -> bugs_csv := create_outfile s),
|
|
Some "bugs.csv",
|
|
"create file bugs.csv containing a list of bugs in CSV format"
|
|
;
|
|
"-bugs_json",
|
|
Arg.String (fun s -> bugs_json := create_outfile s),
|
|
Some "bugs.json",
|
|
"create file bugs.json containing a list of bugs in JSON format"
|
|
;
|
|
"-bugs_txt",
|
|
Arg.String (fun s -> bugs_txt := create_outfile s),
|
|
Some "bugs.txt",
|
|
"create file bugs.txt containing a list of bugs in text format"
|
|
;
|
|
"-bugs_xml",
|
|
Arg.String (fun s -> bugs_xml := create_outfile s),
|
|
Some "bugs.xml",
|
|
"create file bugs.xml containing a list of bugs in XML format"
|
|
;
|
|
"-calls",
|
|
Arg.String (fun s -> calls_csv := create_outfile s),
|
|
Some "calls.csv",
|
|
"write individual calls in csv format to file.csv"
|
|
;
|
|
"-load_results",
|
|
Arg.String (fun s -> load_analysis_results := Some s),
|
|
Some "file.iar",
|
|
"load analysis results from Infer Analysis Results file file.iar"
|
|
;
|
|
"-procs",
|
|
Arg.String (fun s -> procs_csv := create_outfile s),
|
|
Some "procs.csv",
|
|
"create file procs.csv containing statistics for each procedure in CSV format"
|
|
;
|
|
"-procs_xml",
|
|
Arg.String (fun s -> procs_xml := create_outfile s),
|
|
Some "procs.xml",
|
|
"create file procs.xml containing statistics for each procedure in XML format"
|
|
;
|
|
"-results_dir",
|
|
Arg.String (fun s -> Config.results_dir := s),
|
|
Some "dir",
|
|
"read all the .specs files in the results dir"
|
|
;
|
|
"-lib",
|
|
Arg.String (fun s ->
|
|
Config.specs_library := filename_to_absolute s :: !Config.specs_library),
|
|
Some "dir",
|
|
"add dir to the list of directories to be searched for spec files"
|
|
;
|
|
"-q",
|
|
Arg.Set quiet,
|
|
None,
|
|
"quiet: do not print specs on standard output"
|
|
;
|
|
"-save_results",
|
|
Arg.String (fun s -> save_analysis_results := Some s),
|
|
Some "file.iar",
|
|
"save analysis results to Infer Analysis Results file file.iar"
|
|
;
|
|
"-unit_test",
|
|
Arg.Set unit_test,
|
|
None,
|
|
"print unit test code"
|
|
;
|
|
"-xml",
|
|
Arg.Set xml_specs,
|
|
None,
|
|
"export specs into XML files file1.xml ... filen.xml"
|
|
;
|
|
"-test_filtering",
|
|
Arg.Set test_filtering,
|
|
None,
|
|
"list all the files Infer can report on (should be call at the root of the procject, \
|
|
where .inferconfig lives)."
|
|
;
|
|
"-analyzer",
|
|
Arg.String (fun s -> analyzer := Some (Utils.analyzer_of_string s)),
|
|
Some "analyzer",
|
|
"setup the analyzer for the path filtering"
|
|
;
|
|
"-inferconfig_home",
|
|
Arg.String (fun s -> Config.inferconfig_home := Some s),
|
|
Some "dir",
|
|
"Path to the .inferconfig file"
|
|
;
|
|
"-with_infer_src_loc",
|
|
Arg.Set reports_include_ml_loc,
|
|
None,
|
|
"include the location (in the Infer source code) from where reports are generated"
|
|
;
|
|
] in
|
|
Arg.create_options_desc false "Options" desc in
|
|
let reserved_arg =
|
|
let desc =
|
|
[
|
|
"-latex",
|
|
Arg.String (fun s -> latex := create_outfile s),
|
|
Some "file.tex",
|
|
"print latex report to file.tex"
|
|
;
|
|
"-print_types",
|
|
Arg.Set Config.print_types,
|
|
None,
|
|
"print types in symbolic heaps"
|
|
;
|
|
"-precondition_stats",
|
|
Arg.Set precondition_stats,
|
|
None,
|
|
"print stats about preconditions to standard output"
|
|
;
|
|
"-report",
|
|
Arg.String (fun s -> report := create_outfile s),
|
|
Some "report_file",
|
|
"create file report_file containing a report of the analysis results"
|
|
;
|
|
"-source_file_copy",
|
|
Arg.String (fun s -> source_file_copy := Some (DB.abs_source_file_from_path s)),
|
|
Some "source_file",
|
|
"print the path of the copy of source_file in the results directory"
|
|
;
|
|
"-svg",
|
|
Arg.Set svg,
|
|
None,
|
|
"generate .dot and .svg"
|
|
;
|
|
"-whole_seconds",
|
|
Arg.Set whole_seconds,
|
|
None,
|
|
"print whole seconds only"
|
|
;
|
|
] in
|
|
Arg.create_options_desc false "Reserved Options" desc in
|
|
base_arg @ reserved_arg
|
|
|
|
let usage =
|
|
"Usage: InferPrint [options] name1.specs ... namen.specs\n\
|
|
Read, convert, and print .specs files. \
|
|
To process all the .specs in the current directory, pass . as only parameter \
|
|
To process all the .specs in the results directory, use option -results_dir \
|
|
Each spec is printed to standard output unless option -q is used."
|
|
|
|
let print_usage_exit err_s =
|
|
L.err "Load Error: %s@.@." err_s;
|
|
Arg.usage arg_desc usage;
|
|
exit(1)
|
|
|
|
(** return the list of the .specs files in the results dir and libs, if they're defined *)
|
|
let load_specfiles () =
|
|
let specs_files_in_dir dir =
|
|
let is_specs_file fname =
|
|
not (Sys.is_directory fname) && Filename.check_suffix fname Config.specs_files_suffix in
|
|
let all_filenames = Array.to_list (Sys.readdir dir) in
|
|
let all_filepaths = IList.map (fun fname -> Filename.concat dir fname) all_filenames in
|
|
IList.filter is_specs_file all_filepaths in
|
|
let specs_dirs =
|
|
let result_specs_dir = DB.filename_to_string (DB.Results_dir.specs_dir ()) in
|
|
result_specs_dir :: !Config.specs_library in
|
|
IList.flatten (IList.map specs_files_in_dir specs_dirs)
|
|
|
|
(** Create and initialize latex file *)
|
|
let begin_latex_file fmt =
|
|
let author = "Infer " ^ Version.versionString in
|
|
let title = "Report on Analysis Results" in
|
|
let table_of_contents = true in
|
|
Latex.pp_begin fmt (author, title, table_of_contents)
|
|
|
|
(** Write proc summary to latex file *)
|
|
let write_summary_latex fmt summary =
|
|
let proc_name = Specs.get_proc_name summary in
|
|
Latex.pp_section fmt ("Analysis of function "
|
|
^ (Latex.convert_string (Procname.to_string proc_name)));
|
|
F.fprintf fmt "@[<v>%a@]" (Specs.pp_summary (pe_latex Black) !whole_seconds) summary
|
|
|
|
let error_desc_to_csv_string error_desc =
|
|
let pp fmt () = F.fprintf fmt "%a" Localise.pp_error_desc error_desc in
|
|
Escape.escape_csv (pp_to_string pp ())
|
|
|
|
let error_advice_to_csv_string error_desc =
|
|
let pp fmt () = F.fprintf fmt "%a" Localise.pp_error_advice error_desc in
|
|
Escape.escape_csv (pp_to_string pp ())
|
|
|
|
let error_desc_to_plain_string error_desc =
|
|
let pp fmt () = F.fprintf fmt "%a" Localise.pp_error_desc error_desc in
|
|
pp_to_string pp ()
|
|
|
|
let error_desc_to_dotty_string error_desc =
|
|
Localise.error_desc_get_dotty error_desc
|
|
|
|
let error_desc_to_xml_string error_desc =
|
|
let pp fmt () = F.fprintf fmt "%a" Localise.pp_error_desc error_desc in
|
|
Escape.escape_xml (pp_to_string pp ())
|
|
|
|
let error_desc_to_xml_tags error_desc =
|
|
let tags = Localise.error_desc_get_tags error_desc in
|
|
let subtree label contents =
|
|
Io_infer.Xml.create_tree label [] [(Io_infer.Xml.String contents)] in
|
|
IList.map (fun (tag, value) -> subtree tag (Escape.escape_xml value)) tags
|
|
|
|
let get_bug_hash
|
|
(kind: string) (type_str: string) (procedure_id: string) (filename: string)
|
|
(node_key: int) (error_desc: Localise.error_desc) =
|
|
let qualifier_tag_call_procedure = Localise.error_desc_get_tag_call_procedure error_desc in
|
|
let qualifier_tag_value = Localise.error_desc_get_tag_value error_desc in
|
|
Hashtbl.hash
|
|
(kind, type_str, procedure_id, filename, node_key,
|
|
qualifier_tag_call_procedure, qualifier_tag_value)
|
|
|
|
let loc_trace_to_jsonbug_record trace_list ekind =
|
|
match ekind with
|
|
| Exceptions.Kinfo -> []
|
|
| _ ->
|
|
(* writes a trace as a record for atdgen conversion *)
|
|
let node_tags_to_records tags_list =
|
|
IList.map (fun tag -> { tag = fst tag; value = snd tag }) tags_list in
|
|
let trace_item_to_record trace_item =
|
|
{ level = trace_item.Errlog.lt_level;
|
|
filename = DB.source_file_to_string trace_item.Errlog.lt_loc.Location.file;
|
|
line_number = trace_item.Errlog.lt_loc.Location.line;
|
|
description = trace_item.Errlog.lt_description;
|
|
node_tags = node_tags_to_records trace_item.Errlog.lt_node_tags;
|
|
} in
|
|
let record_list = IList.rev (IList.rev_map trace_item_to_record trace_list) in
|
|
record_list
|
|
|
|
let error_desc_to_qualifier_tags_records error_desc =
|
|
let tag_value_pairs = Localise.error_desc_to_tag_value_pairs error_desc in
|
|
let tag_value_to_record (tag, value) =
|
|
{ tag = tag; value = value } in
|
|
IList.map (fun tag_value -> tag_value_to_record tag_value) tag_value_pairs
|
|
|
|
type summary_val =
|
|
{ vname : string;
|
|
vname_id : string;
|
|
vspecs : int;
|
|
vtime : string;
|
|
vto : string;
|
|
vsymop : int;
|
|
verr : int;
|
|
vfile : string;
|
|
vflags : proc_flags;
|
|
vline : int;
|
|
vloc : int;
|
|
vtop : string;
|
|
vsignature : string;
|
|
vweight : int;
|
|
vproof_coverage : string;
|
|
vrank : string;
|
|
vin_calls : int;
|
|
vout_calls : int;
|
|
vproof_trace : string;
|
|
}
|
|
|
|
(** compute values from summary data to export to csv and xml format *)
|
|
let summary_values top_proc_set summary =
|
|
let stats = summary.Specs.stats in
|
|
let attributes = summary.Specs.attributes in
|
|
let err_log = attributes.ProcAttributes.err_log in
|
|
let proc_name = Specs.get_proc_name summary in
|
|
let is_top = Procname.Set.mem proc_name top_proc_set in
|
|
let signature = Specs.get_signature summary in
|
|
let nodes_nr = IList.length summary.Specs.nodes in
|
|
let specs = Specs.get_specs_from_payload summary in
|
|
let nr_nodes_visited, lines_visited =
|
|
let visited = ref Specs.Visitedset.empty in
|
|
let do_spec spec = visited := Specs.Visitedset.union spec.Specs.visited !visited in
|
|
IList.iter do_spec specs;
|
|
let visited_lines = ref IntSet.empty in
|
|
Specs.Visitedset.iter (fun (_, ls) ->
|
|
IList.iter (fun l -> visited_lines := IntSet.add l !visited_lines) ls)
|
|
!visited;
|
|
Specs.Visitedset.cardinal !visited, IntSet.elements !visited_lines in
|
|
let proof_trace =
|
|
let pp_line fmt l = F.fprintf fmt "%d" l in
|
|
let pp fmt () = F.fprintf fmt "%a" (pp_seq pp_line) lines_visited in
|
|
pp_to_string pp () in
|
|
let node_coverage =
|
|
if nodes_nr = 0 then 0.0
|
|
else float_of_int nr_nodes_visited /. float_of_int nodes_nr in
|
|
let logscale x =
|
|
log10 (float_of_int (x + 1)) in
|
|
let in_calls, out_calls =
|
|
let calls = stats.Specs.stats_calls in
|
|
calls.Cg.in_calls, calls.Cg.out_calls in
|
|
let call_rank =
|
|
let c1 = 1 and c2 = 1 in
|
|
logscale (c1 * in_calls + c2 * out_calls) in
|
|
|
|
let pp_failure failure =
|
|
pp_to_string SymOp.pp_failure_kind failure in
|
|
|
|
|
|
{ vname = Procname.to_string proc_name;
|
|
vname_id = Procname.to_filename proc_name;
|
|
vspecs = IList.length specs;
|
|
vtime = Printf.sprintf "%.0f" stats.Specs.stats_time;
|
|
vto = Option.map_default pp_failure "NONE" stats.Specs.stats_failure;
|
|
vsymop = stats.Specs.symops;
|
|
verr = Errlog.size
|
|
(fun ekind in_footprint -> ekind = Exceptions.Kerror && in_footprint)
|
|
err_log;
|
|
vflags = attributes.ProcAttributes.proc_flags;
|
|
vfile = DB.source_file_to_string attributes.ProcAttributes.loc.Location.file;
|
|
vline = attributes.ProcAttributes.loc.Location.line;
|
|
vloc = attributes.ProcAttributes.loc.Location.nLOC;
|
|
vtop = if is_top then "Y" else "N";
|
|
vsignature = signature;
|
|
vweight = nodes_nr;
|
|
vproof_coverage = Printf.sprintf "%2.2f" node_coverage;
|
|
vrank = Printf.sprintf "%2.2f" call_rank;
|
|
vin_calls = in_calls;
|
|
vout_calls = out_calls;
|
|
vproof_trace = proof_trace;
|
|
}
|
|
|
|
|
|
module ProcsCsv = struct
|
|
(** Print the header of the procedures csv file, with column names *)
|
|
let pp_header fmt () =
|
|
Format.fprintf fmt
|
|
"%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s@\n"
|
|
Io_infer.Xml.tag_name
|
|
Io_infer.Xml.tag_name_id
|
|
Io_infer.Xml.tag_specs
|
|
Io_infer.Xml.tag_time
|
|
Io_infer.Xml.tag_to
|
|
Io_infer.Xml.tag_symop
|
|
Io_infer.Xml.tag_err
|
|
Io_infer.Xml.tag_file
|
|
Io_infer.Xml.tag_line
|
|
Io_infer.Xml.tag_loc
|
|
Io_infer.Xml.tag_top
|
|
Io_infer.Xml.tag_signature
|
|
Io_infer.Xml.tag_weight
|
|
Io_infer.Xml.tag_proof_coverage
|
|
Io_infer.Xml.tag_rank
|
|
Io_infer.Xml.tag_in_calls
|
|
Io_infer.Xml.tag_out_calls
|
|
Io_infer.Xml.tag_proof_trace
|
|
|
|
(** Write proc summary stats in csv format *)
|
|
let pp_summary top_proc_set fmt summary =
|
|
let pp x = F.fprintf fmt x in
|
|
let sv = summary_values top_proc_set summary in
|
|
pp "\"%s\"," (Escape.escape_csv sv.vname);
|
|
pp "\"%s\"," (Escape.escape_csv sv.vname_id);
|
|
pp "%d," sv.vspecs;
|
|
pp "%s," sv.vtime;
|
|
pp "%s," sv.vto;
|
|
pp "%d," sv.vsymop;
|
|
pp "%d," sv.verr;
|
|
pp "%s," sv.vfile;
|
|
pp "%d," sv.vline;
|
|
pp "%d," sv.vloc;
|
|
pp "%s," sv.vtop;
|
|
pp "\"%s\"," (Escape.escape_csv sv.vsignature);
|
|
pp "%d," sv.vweight;
|
|
pp "%s," sv.vproof_coverage;
|
|
pp "%s," sv.vrank;
|
|
pp "%d," sv.vin_calls;
|
|
pp "%d," sv.vout_calls;
|
|
pp "%s@\n" sv.vproof_trace;
|
|
end
|
|
|
|
module ProcsXml = struct
|
|
let xml_procs_id = ref 0
|
|
|
|
(** print proc in xml *)
|
|
let pp_proc top_proc_set fmt summary =
|
|
let sv = summary_values top_proc_set summary in
|
|
let subtree label contents =
|
|
Io_infer.Xml.create_tree label [] [(Io_infer.Xml.String contents)] in
|
|
let tree =
|
|
incr xml_procs_id;
|
|
let attributes = [("id", string_of_int !xml_procs_id) ] in
|
|
let forest =
|
|
[
|
|
subtree Io_infer.Xml.tag_name (Escape.escape_xml sv.vname);
|
|
subtree Io_infer.Xml.tag_name_id (Escape.escape_xml sv.vname_id);
|
|
subtree Io_infer.Xml.tag_specs (string_of_int sv.vspecs);
|
|
subtree Io_infer.Xml.tag_time sv.vtime;
|
|
subtree Io_infer.Xml.tag_to sv.vto;
|
|
subtree Io_infer.Xml.tag_symop (string_of_int sv.vsymop);
|
|
subtree Io_infer.Xml.tag_err (string_of_int sv.verr);
|
|
subtree Io_infer.Xml.tag_file sv.vfile;
|
|
subtree Io_infer.Xml.tag_line (string_of_int sv.vline);
|
|
subtree Io_infer.Xml.tag_loc (string_of_int sv.vloc);
|
|
subtree Io_infer.Xml.tag_top sv.vtop;
|
|
subtree Io_infer.Xml.tag_signature (Escape.escape_xml sv.vsignature);
|
|
subtree Io_infer.Xml.tag_weight (string_of_int sv.vweight);
|
|
subtree Io_infer.Xml.tag_proof_coverage sv.vproof_coverage;
|
|
subtree Io_infer.Xml.tag_rank sv.vrank;
|
|
subtree Io_infer.Xml.tag_in_calls (string_of_int sv.vin_calls);
|
|
subtree Io_infer.Xml.tag_out_calls (string_of_int sv.vin_calls);
|
|
subtree Io_infer.Xml.tag_proof_trace sv.vproof_trace;
|
|
subtree Io_infer.Xml.tag_flags (string_of_int (Hashtbl.length sv.vflags));
|
|
] in
|
|
Io_infer.Xml.create_tree "procedure" attributes forest in
|
|
Io_infer.Xml.pp_inner_node fmt tree
|
|
|
|
(** print the opening of the procedures xml file *)
|
|
let pp_procs_open fmt () =
|
|
Io_infer.Xml.pp_open fmt "procedures"
|
|
|
|
(** print the closing of the procedures xml file *)
|
|
let pp_procs_close fmt () =
|
|
Io_infer.Xml.pp_close fmt "procedures"
|
|
|
|
end
|
|
|
|
module BugsCsv = struct
|
|
let csv_bugs_id = ref 0
|
|
|
|
let timestamp = Unix.time ()
|
|
let pp_header fmt () =
|
|
Format.fprintf fmt "%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s@\n"
|
|
Io_infer.Xml.tag_class
|
|
Io_infer.Xml.tag_kind
|
|
Io_infer.Xml.tag_type
|
|
Io_infer.Xml.tag_qualifier
|
|
Io_infer.Xml.tag_severity
|
|
Io_infer.Xml.tag_line
|
|
Io_infer.Xml.tag_procedure
|
|
Io_infer.Xml.tag_procedure_id
|
|
Io_infer.Xml.tag_file
|
|
Io_infer.Xml.tag_trace
|
|
Io_infer.Xml.tag_key
|
|
Io_infer.Xml.tag_qualifier_tags
|
|
Io_infer.Xml.tag_hash
|
|
"bug_id"
|
|
"always_report"
|
|
"advice"
|
|
|
|
(** Write bug report in csv format *)
|
|
let pp_bugs error_filter fmt summary =
|
|
let pp x = F.fprintf fmt x in
|
|
let err_log = summary.Specs.attributes.ProcAttributes.err_log in
|
|
let pp_row (_, node_key) loc _ ekind in_footprint error_name error_desc severity ltr _ eclass =
|
|
if in_footprint && error_filter error_desc error_name then
|
|
let err_desc_string = error_desc_to_csv_string error_desc in
|
|
let err_advice_string = error_advice_to_csv_string error_desc in
|
|
let qualifier_tag_xml =
|
|
let xml_node =
|
|
Io_infer.Xml.create_tree
|
|
Io_infer.Xml.tag_qualifier_tags [] (error_desc_to_xml_tags error_desc) in
|
|
let p fmt () = F.fprintf fmt "%a" (Io_infer.Xml.pp_document false) xml_node in
|
|
let s = pp_to_string p () in
|
|
Escape.escape_csv s in
|
|
let kind = Exceptions.err_kind_string ekind in
|
|
let type_str = Localise.to_string error_name in
|
|
let procedure_id = Procname.to_filename (Specs.get_proc_name summary) in
|
|
let filename =
|
|
DB.source_file_to_string summary.Specs.attributes.ProcAttributes.loc.Location.file in
|
|
let always_report =
|
|
match Localise.error_desc_extract_tag_value error_desc "always_report" with
|
|
| "" -> "false"
|
|
| v -> v in
|
|
|
|
let trace = string_of_json_trace { trace = loc_trace_to_jsonbug_record ltr ekind } in
|
|
incr csv_bugs_id;
|
|
pp "%s," (Exceptions.err_class_string eclass);
|
|
pp "%s," kind;
|
|
pp "%s," type_str;
|
|
pp "\"%s\"," err_desc_string;
|
|
pp "%s," severity;
|
|
pp "%d," loc.Location.line;
|
|
pp "\"%s\"," (Escape.escape_csv (Procname.to_string (Specs.get_proc_name summary)));
|
|
pp "\"%s\"," (Escape.escape_csv procedure_id);
|
|
pp "%s," filename;
|
|
pp "\"%s\"," (Escape.escape_csv trace);
|
|
pp "\"%d\"," node_key;
|
|
pp "\"%s\"," qualifier_tag_xml;
|
|
pp "\"%d\"," (get_bug_hash kind type_str procedure_id filename node_key error_desc);
|
|
pp "\"%d\"," !csv_bugs_id; (** bug id *)
|
|
pp "\"%s\"," always_report;
|
|
pp "\"%s\"@\n" err_advice_string; in
|
|
Errlog.iter pp_row err_log
|
|
end
|
|
|
|
module BugsJson = struct
|
|
let is_first_item = ref true
|
|
let pp_json_open fmt () = F.fprintf fmt "[@?"
|
|
let pp_json_close fmt () = F.fprintf fmt "]\n@?"
|
|
|
|
(** Write bug report in JSON format *)
|
|
let pp_bugs error_filter fmt summary =
|
|
let pp x = F.fprintf fmt x in
|
|
let err_log = summary.Specs.attributes.ProcAttributes.err_log in
|
|
let pp_row
|
|
(_, node_key) loc ml_loc_opt ekind in_footprint error_name error_desc severity ltr _ eclass
|
|
=
|
|
if in_footprint && error_filter error_desc error_name then
|
|
let kind = Exceptions.err_kind_string ekind in
|
|
let bug_type = Localise.to_string error_name in
|
|
let procedure_id = Procname.to_filename (Specs.get_proc_name summary) in
|
|
let file =
|
|
DB.source_file_to_string summary.Specs.attributes.ProcAttributes.loc.Location.file in
|
|
let json_ml_loc = match ml_loc_opt with
|
|
| Some (file, lnum, cnum, enum) when !reports_include_ml_loc ->
|
|
Some Jsonbug_j.{ file; lnum; cnum; enum; }
|
|
| _ -> None in
|
|
let bug = {
|
|
bug_class = Exceptions.err_class_string eclass;
|
|
kind = kind;
|
|
bug_type = bug_type;
|
|
qualifier = error_desc_to_plain_string error_desc;
|
|
severity = severity;
|
|
line = loc.Location.line;
|
|
procedure = Procname.to_string (Specs.get_proc_name summary);
|
|
procedure_id = procedure_id;
|
|
file = file;
|
|
bug_trace = loc_trace_to_jsonbug_record ltr ekind;
|
|
key = node_key;
|
|
qualifier_tags = error_desc_to_qualifier_tags_records error_desc;
|
|
hash = get_bug_hash kind bug_type procedure_id file node_key error_desc;
|
|
dotty = error_desc_to_dotty_string error_desc;
|
|
infer_source_loc = json_ml_loc;
|
|
} in
|
|
if not !is_first_item then pp "," else is_first_item := false;
|
|
pp "%s@?" (string_of_jsonbug bug) in
|
|
Errlog.iter pp_row err_log
|
|
end
|
|
|
|
module BugsTxt = struct
|
|
(** Write bug report in text format *)
|
|
let pp_bugs error_filter fmt summary =
|
|
let err_log = summary.Specs.attributes.ProcAttributes.err_log in
|
|
let pp_row (node_id, node_key) loc _ ekind in_footprint error_name error_desc _ _ _ _ =
|
|
if in_footprint && error_filter error_desc error_name then
|
|
Exceptions.pp_err (node_id, node_key) loc ekind error_name error_desc None fmt () in
|
|
Errlog.iter pp_row err_log
|
|
end
|
|
|
|
module BugsXml = struct
|
|
let xml_bugs_id = ref 0
|
|
let include_precondition_tree = false
|
|
|
|
let loc_trace_to_xml linereader ltr =
|
|
let subtree label contents =
|
|
Io_infer.Xml.create_tree label [] [(Io_infer.Xml.String contents)] in
|
|
let level_to_xml level = subtree Io_infer.Xml.tag_level (string_of_int level) in
|
|
let line_to_xml line = subtree Io_infer.Xml.tag_line (string_of_int line) in
|
|
let file_to_xml file = subtree Io_infer.Xml.tag_file file in
|
|
let code_to_xml code = subtree Io_infer.Xml.tag_code code in
|
|
let description_to_xml descr = subtree Io_infer.Xml.tag_description (Escape.escape_xml descr) in
|
|
let node_tags_to_xml node_tags =
|
|
let escaped_tags = IList.map (fun (tag, value) -> (tag, Escape.escape_xml value)) node_tags in
|
|
Io_infer.Xml.create_tree Io_infer.Xml.tag_node escaped_tags [] in
|
|
let num = ref 0 in
|
|
let loc_to_xml lt =
|
|
incr num;
|
|
let loc = lt.Errlog.lt_loc in
|
|
let code = match Printer.LineReader.from_loc linereader loc with
|
|
| Some s -> Escape.escape_xml s
|
|
| None -> "" in
|
|
Io_infer.Xml.create_tree Io_infer.Xml.tag_loc [("num", string_of_int !num)]
|
|
[(level_to_xml lt.Errlog.lt_level);
|
|
(file_to_xml (DB.source_file_to_string loc.Location.file));
|
|
(line_to_xml loc.Location.line);
|
|
(code_to_xml code);
|
|
(description_to_xml lt.Errlog.lt_description);
|
|
(node_tags_to_xml lt.Errlog.lt_node_tags)] in
|
|
IList.rev (IList.rev_map loc_to_xml ltr)
|
|
|
|
(** print bugs from summary in xml *)
|
|
let pp_bugs error_filter linereader fmt summary =
|
|
let err_log = summary.Specs.attributes.ProcAttributes.err_log in
|
|
let do_row
|
|
(_, node_key) loc _ ekind in_footprint error_name error_desc severity ltr pre_opt eclass =
|
|
if in_footprint && error_filter error_desc error_name then
|
|
let err_desc_string = error_desc_to_xml_string error_desc in
|
|
let precondition_tree () = match pre_opt with
|
|
| None -> []
|
|
| Some pre ->
|
|
Dotty.reset_node_counter ();
|
|
[Dotty.prop_to_xml pre Io_infer.Xml.tag_precondition 1] in
|
|
let subtree label contents =
|
|
Io_infer.Xml.create_tree label [] [(Io_infer.Xml.String contents)] in
|
|
let kind = Exceptions.err_kind_string ekind in
|
|
let type_str = Localise.to_string error_name in
|
|
let tree =
|
|
incr xml_bugs_id;
|
|
let attributes = [("id", string_of_int !xml_bugs_id) ] in
|
|
let error_class = Exceptions.err_class_string eclass in
|
|
let error_line = string_of_int loc.Location.line in
|
|
let proc_name = Specs.get_proc_name summary in
|
|
let procedure_name = Procname.to_string proc_name in
|
|
let procedure_id = Procname.to_filename proc_name in
|
|
let filename =
|
|
DB.source_file_to_string summary.Specs.attributes.ProcAttributes.loc.Location.file in
|
|
let bug_hash = get_bug_hash kind type_str procedure_id filename node_key error_desc in
|
|
let forest =
|
|
[
|
|
subtree Io_infer.Xml.tag_class error_class;
|
|
subtree Io_infer.Xml.tag_kind kind;
|
|
subtree Io_infer.Xml.tag_type type_str;
|
|
subtree Io_infer.Xml.tag_qualifier err_desc_string;
|
|
subtree Io_infer.Xml.tag_severity severity;
|
|
subtree Io_infer.Xml.tag_line error_line;
|
|
subtree Io_infer.Xml.tag_procedure (Escape.escape_xml procedure_name);
|
|
subtree Io_infer.Xml.tag_procedure_id (Escape.escape_xml procedure_id);
|
|
subtree Io_infer.Xml.tag_file filename;
|
|
Io_infer.Xml.create_tree Io_infer.Xml.tag_trace [] (loc_trace_to_xml linereader ltr);
|
|
subtree Io_infer.Xml.tag_key (string_of_int node_key);
|
|
Io_infer.Xml.create_tree
|
|
Io_infer.Xml.tag_qualifier_tags [] (error_desc_to_xml_tags error_desc);
|
|
subtree Io_infer.Xml.tag_hash (string_of_int bug_hash)
|
|
]
|
|
@
|
|
(if include_precondition_tree then precondition_tree () else []) in
|
|
Io_infer.Xml.create_tree "bug" attributes forest in
|
|
Io_infer.Xml.pp_inner_node fmt tree in
|
|
Errlog.iter do_row err_log
|
|
|
|
(** print the opening of the bugs xml file *)
|
|
let pp_bugs_open fmt () =
|
|
Io_infer.Xml.pp_open fmt "bugs"
|
|
|
|
(** print the closing of the bugs xml file *)
|
|
let pp_bugs_close fmt () =
|
|
Io_infer.Xml.pp_close fmt "bugs"
|
|
end
|
|
|
|
module CallsCsv = struct
|
|
(** Print the header of the calls csv file, with column names *)
|
|
let pp_header fmt () =
|
|
Format.fprintf fmt "%s,%s,%s,%s,%s,%s,%s@\n"
|
|
Io_infer.Xml.tag_caller
|
|
Io_infer.Xml.tag_caller_id
|
|
Io_infer.Xml.tag_callee
|
|
Io_infer.Xml.tag_callee_id
|
|
Io_infer.Xml.tag_file
|
|
Io_infer.Xml.tag_line
|
|
Io_infer.Xml.tag_call_trace
|
|
|
|
(** Write proc summary stats in csv format *)
|
|
let pp_calls fmt summary =
|
|
let pp x = F.fprintf fmt x in
|
|
let stats = summary.Specs.stats in
|
|
let caller_name = Specs.get_proc_name summary in
|
|
let do_call (callee_name, loc) trace =
|
|
pp "\"%s\"," (Escape.escape_csv (Procname.to_string caller_name));
|
|
pp "\"%s\"," (Escape.escape_csv (Procname.to_filename caller_name));
|
|
pp "\"%s\"," (Escape.escape_csv (Procname.to_string callee_name));
|
|
pp "\"%s\"," (Escape.escape_csv (Procname.to_filename callee_name));
|
|
pp "%s," (DB.source_file_to_string summary.Specs.attributes.ProcAttributes.loc.Location.file);
|
|
pp "%d," loc.Location.line;
|
|
pp "%a@\n" Specs.CallStats.pp_trace trace in
|
|
Specs.CallStats.iter do_call stats.Specs.call_stats
|
|
end
|
|
|
|
module UnitTest = struct
|
|
(** Store the unit test functions generated, so that they can be called by main at the end *)
|
|
let procs_done = ref []
|
|
|
|
(** Print unit test for every spec in the summary *)
|
|
let print_unit_test proc_name summary =
|
|
let cnt = ref 0 in
|
|
let fmt = F.std_formatter in
|
|
let do_spec spec =
|
|
incr cnt;
|
|
let c_file = Filename.basename
|
|
(DB.source_file_to_string summary.Specs.attributes.ProcAttributes.loc.Location.file) in
|
|
let code =
|
|
Autounit.genunit c_file proc_name !cnt (Specs.get_formals summary) spec in
|
|
F.fprintf fmt "%a@." Autounit.pp_code code in
|
|
let specs = Specs.get_specs_from_payload summary in
|
|
IList.iter do_spec specs;
|
|
procs_done := (proc_name, IList.length specs) :: !procs_done
|
|
|
|
(** Print main function which calls all the unit test functions generated *)
|
|
let print_unit_test_main () =
|
|
let fmt = F.std_formatter in
|
|
let code = Autounit.genmain !procs_done in
|
|
F.fprintf fmt "%a@." Autounit.pp_code code
|
|
end
|
|
|
|
(** Module to compute the top procedures.
|
|
A procedure is top if it has specs and any procedure calling it has no specs *)
|
|
module TopProcedures : sig
|
|
type t
|
|
val create : unit -> t
|
|
val process_summary : t -> string * Specs.summary -> unit
|
|
val top_set: t -> Procname.Set.t
|
|
end = struct
|
|
type t =
|
|
{ mutable possible: Procname.Set.t;
|
|
mutable impossible: Procname.Set.t }
|
|
let create () =
|
|
{ possible = Procname.Set.empty;
|
|
impossible = Procname.Set.empty }
|
|
let mark_possible x pname =
|
|
x.possible <- Procname.Set.add pname x.possible
|
|
let mark_impossible x pname =
|
|
x.impossible <- Procname.Set.add pname x.impossible
|
|
let top_set x =
|
|
Procname.Set.diff x.possible x.impossible
|
|
let process_summary x (_, summary) =
|
|
let proc_name = Specs.get_proc_name summary in
|
|
let nspecs = IList.length (Specs.get_specs_from_payload summary) in
|
|
if nspecs > 0 then
|
|
begin
|
|
mark_possible x proc_name;
|
|
Procname.Map.iter (fun p _ -> mark_impossible x p) summary.Specs.dependency_map
|
|
end
|
|
end
|
|
|
|
module Stats = struct
|
|
type t = {
|
|
files : (DB.source_file, unit) Hashtbl.t;
|
|
mutable nchecked : int;
|
|
mutable ndefective : int;
|
|
mutable nerrors : int;
|
|
mutable ninfos : int;
|
|
mutable nLOC : int;
|
|
mutable nprocs : int;
|
|
mutable nspecs : int;
|
|
mutable ntimeouts : int;
|
|
mutable nverified : int;
|
|
mutable nwarnings : int;
|
|
mutable saved_errors : string list;
|
|
}
|
|
|
|
let create () = {
|
|
files = Hashtbl.create 3;
|
|
nchecked = 0;
|
|
ndefective = 0;
|
|
nerrors = 0;
|
|
ninfos = 0;
|
|
nLOC = 0;
|
|
nprocs = 0;
|
|
nspecs = 0;
|
|
ntimeouts = 0;
|
|
nverified = 0;
|
|
nwarnings = 0;
|
|
saved_errors = [];
|
|
}
|
|
|
|
let process_loc loc stats =
|
|
try Hashtbl.find stats.files loc.Location.file
|
|
with Not_found ->
|
|
stats.nLOC <- stats.nLOC + loc.Location.nLOC;
|
|
Hashtbl.add stats.files loc.Location.file ()
|
|
|
|
let loc_trace_to_string_list linereader indent_num ltr =
|
|
let res = ref [] in
|
|
let indent_string n =
|
|
let s = ref "" in
|
|
for _ = 1 to n do s := " " ^ !s done;
|
|
!s in
|
|
let num = ref 0 in
|
|
let loc_to_string lt =
|
|
incr num;
|
|
let loc = lt.Errlog.lt_loc in
|
|
let level = lt.Errlog.lt_level in
|
|
let description = lt.Errlog.lt_description in
|
|
let code = match Printer.LineReader.from_loc linereader loc with
|
|
| Some s -> s
|
|
| None -> "" in
|
|
let line =
|
|
let pp fmt () =
|
|
if description <> ""
|
|
then F.fprintf fmt "%s%4s // %s@\n" (indent_string (level + indent_num)) " " description;
|
|
F.fprintf fmt "%s%04d: %s" (indent_string (level + indent_num)) loc.Location.line code in
|
|
pp_to_string pp () in
|
|
res := line :: "" :: !res in
|
|
IList.iter loc_to_string ltr;
|
|
IList.rev !res
|
|
|
|
let process_err_log error_filter linereader err_log stats =
|
|
let found_errors = ref false in
|
|
let process_row _ loc _ ekind in_footprint error_name error_desc _ ltr _ _ =
|
|
let type_str = Localise.to_string error_name in
|
|
if in_footprint && error_filter error_desc error_name
|
|
then match ekind with
|
|
| Exceptions.Kerror ->
|
|
found_errors := true;
|
|
stats.nerrors <- stats.nerrors + 1;
|
|
let error_strs =
|
|
let pp1 fmt () = F.fprintf fmt "%d: %s" stats.nerrors type_str in
|
|
let pp2 fmt () = F.fprintf fmt " %s:%d"
|
|
(DB.source_file_to_string loc.Location.file) loc.Location.line in
|
|
let pp3 fmt () = F.fprintf fmt " (%a)" Localise.pp_error_desc error_desc in
|
|
[pp_to_string pp1 (); pp_to_string pp2 (); pp_to_string pp3 ()] in
|
|
let trace = loc_trace_to_string_list linereader 1 ltr in
|
|
stats.saved_errors <- IList.rev_append (error_strs @ trace @ [""]) stats.saved_errors
|
|
| Exceptions.Kwarning -> stats.nwarnings <- stats.nwarnings + 1
|
|
| Exceptions.Kinfo -> stats.ninfos <- stats.ninfos + 1 in
|
|
Errlog.iter process_row err_log;
|
|
!found_errors
|
|
|
|
let process_summary error_filter summary linereader stats =
|
|
let specs = Specs.get_specs_from_payload summary in
|
|
let found_errors = process_err_log
|
|
error_filter linereader summary.Specs.attributes.ProcAttributes.err_log stats in
|
|
let is_defective = found_errors in
|
|
let is_verified = specs <> [] && not is_defective in
|
|
let is_checked = not (is_defective || is_verified) in
|
|
let is_timeout = match Specs.(summary.stats.stats_failure) with
|
|
| None | Some (FKcrash _) -> false
|
|
| _ -> true in
|
|
stats.nprocs <- stats.nprocs + 1;
|
|
stats.nspecs <- stats.nspecs + (IList.length specs);
|
|
if is_verified then stats.nverified <- stats.nverified + 1;
|
|
if is_checked then stats.nchecked <- stats.nchecked + 1;
|
|
if is_timeout then stats.ntimeouts <- stats.ntimeouts + 1;
|
|
if is_defective then stats.ndefective <- stats.ndefective + 1;
|
|
process_loc summary.Specs.attributes.ProcAttributes.loc stats
|
|
|
|
let num_files stats =
|
|
Hashtbl.length stats.files
|
|
|
|
let pp fmt stats =
|
|
F.fprintf fmt "Files: %d@\n" (num_files stats);
|
|
F.fprintf fmt "LOC: %d@\n" stats.nLOC;
|
|
F.fprintf fmt "Specs: %d@\n" stats.nspecs;
|
|
F.fprintf fmt "Timeouts: %d@\n" stats.ntimeouts;
|
|
F.fprintf fmt "Procedures: %d@\n" stats.nprocs;
|
|
F.fprintf fmt " Verified: %d@\n" stats.nverified;
|
|
F.fprintf fmt " Checked: %d@\n" stats.nchecked;
|
|
F.fprintf fmt " Defective: %d@\n" stats.ndefective;
|
|
F.fprintf fmt "Errors: %d@\n" stats.nerrors;
|
|
F.fprintf fmt "Warnings: %d@\n" stats.nwarnings;
|
|
F.fprintf fmt "Infos: %d@\n" stats.ninfos;
|
|
F.fprintf fmt "@\n -------------------@\n";
|
|
F.fprintf fmt "@\nDetailed Errors@\n@\n";
|
|
IList.iter (fun s -> F.fprintf fmt "%s@\n" s) (IList.rev stats.saved_errors);
|
|
end
|
|
|
|
module Report = struct
|
|
let pp_header fmt () =
|
|
F.fprintf fmt "Infer Analysis Results -- generated %a@\n@\n" pp_current_time ();
|
|
F.fprintf fmt "Summary Report@\n@\n"
|
|
|
|
let pp_stats fmt stats =
|
|
Stats.pp fmt stats
|
|
end
|
|
|
|
(** Categorize the preconditions of specs and print stats *)
|
|
module PreconditionStats = struct
|
|
let nr_nopres = ref 0
|
|
let nr_empty = ref 0
|
|
let nr_onlyallocation = ref 0
|
|
let nr_dataconstraints = ref 0
|
|
|
|
let do_summary proc_name summary =
|
|
let specs = Specs.get_specs_from_payload summary in
|
|
let preconditions = IList.map (fun spec -> Specs.Jprop.to_prop spec.Specs.pre) specs in
|
|
match Prop.CategorizePreconditions.categorize preconditions with
|
|
| Prop.CategorizePreconditions.Empty ->
|
|
incr nr_empty;
|
|
L.out "Procedure: %a footprint:Empty@." Procname.pp proc_name
|
|
| Prop.CategorizePreconditions.OnlyAllocation ->
|
|
incr nr_onlyallocation;
|
|
L.out "Procedure: %a footprint:OnlyAllocation@." Procname.pp proc_name
|
|
| Prop.CategorizePreconditions.NoPres ->
|
|
incr nr_nopres;
|
|
L.out "Procedure: %a footprint:NoPres@." Procname.pp proc_name
|
|
| Prop.CategorizePreconditions.DataConstraints ->
|
|
incr nr_dataconstraints;
|
|
L.out "Procedure: %a footprint:DataConstraints@." Procname.pp proc_name
|
|
|
|
let pp_stats () =
|
|
L.out "@.Precondition stats@.";
|
|
L.out "Procedures with no preconditions: %d@." !nr_nopres;
|
|
L.out "Procedures with empty precondition: %d@." !nr_empty;
|
|
L.out "Procedures with only allocation conditions: %d@." !nr_onlyallocation;
|
|
L.out "Procedures with data constraints: %d@." !nr_dataconstraints
|
|
end
|
|
|
|
(** Process a summary *)
|
|
let process_summary filters linereader stats (top_proc_set: Procname.Set.t) (fname, summary) =
|
|
let proc_name = Specs.get_proc_name summary in
|
|
let base = DB.chop_extension (DB.filename_from_string fname) in
|
|
let pp_simple_saved = !Config.pp_simple in
|
|
Config.pp_simple := true;
|
|
if !quiet then ()
|
|
else L.out "Procedure: %a@\n%a@."
|
|
Procname.pp proc_name (Specs.pp_summary pe_text !whole_seconds) summary;
|
|
let error_filter error_desc error_name =
|
|
let always_report () =
|
|
Localise.error_desc_extract_tag_value error_desc "always_report" = "true" in
|
|
(!Config.write_html || not (Localise.equal error_name Localise.skip_function)) &&
|
|
(filters.Inferconfig.path_filter summary.Specs.attributes.ProcAttributes.loc.Location.file
|
|
|| always_report ()) &&
|
|
filters.Inferconfig.error_filter error_name &&
|
|
filters.Inferconfig.proc_filter proc_name in
|
|
do_outf procs_csv (fun outf -> ProcsCsv.pp_summary top_proc_set outf.fmt summary);
|
|
do_outf calls_csv (fun outf -> CallsCsv.pp_calls outf.fmt summary);
|
|
do_outf procs_xml (fun outf -> ProcsXml.pp_proc top_proc_set outf.fmt summary);
|
|
do_outf bugs_csv (fun outf -> BugsCsv.pp_bugs error_filter outf.fmt summary);
|
|
do_outf bugs_json (fun outf -> BugsJson.pp_bugs error_filter outf.fmt summary);
|
|
do_outf bugs_txt (fun outf -> BugsTxt.pp_bugs error_filter outf.fmt summary);
|
|
do_outf bugs_xml (fun outf -> BugsXml.pp_bugs error_filter linereader outf.fmt summary);
|
|
do_outf report (fun _ -> Stats.process_summary error_filter summary linereader stats);
|
|
if !precondition_stats then PreconditionStats.do_summary proc_name summary;
|
|
if !unit_test then UnitTest.print_unit_test proc_name summary;
|
|
Config.pp_simple := pp_simple_saved;
|
|
do_outf latex (fun outf -> write_summary_latex outf.fmt summary);
|
|
if !svg then begin
|
|
let specs = Specs.get_specs_from_payload summary in
|
|
let dot_file = DB.filename_add_suffix base ".dot" in
|
|
let svg_file = DB.filename_add_suffix base ".svg" in
|
|
if not (DB.file_exists dot_file)
|
|
|| DB.file_modified_time (DB.filename_from_string fname) > DB.file_modified_time dot_file
|
|
then
|
|
Dotty.pp_speclist_dotty_file base specs;
|
|
if not (DB.file_exists svg_file)
|
|
|| DB.file_modified_time dot_file > DB.file_modified_time svg_file
|
|
then
|
|
ignore (Sys.command ("dot -Tsvg \"" ^
|
|
(DB.filename_to_string dot_file) ^
|
|
"\" >\"" ^
|
|
(DB.filename_to_string svg_file) ^"\""))
|
|
end;
|
|
if !xml_specs then begin
|
|
let xml_file = DB.filename_add_suffix base ".xml" in
|
|
let specs = Specs.get_specs_from_payload summary in
|
|
if not (DB.file_exists xml_file)
|
|
|| DB.file_modified_time (DB.filename_from_string fname) > DB.file_modified_time xml_file
|
|
then
|
|
begin
|
|
let xml_out = ref (create_outfile (DB.filename_to_string xml_file)) in
|
|
do_outf xml_out (fun outf ->
|
|
Dotty.print_specs_xml
|
|
(Specs.get_signature summary)
|
|
specs summary.Specs.attributes.ProcAttributes.loc outf.fmt;
|
|
close_outf outf)
|
|
end
|
|
end
|
|
(* ignore (Sys.command ("open " ^ base ^ ".svg")) *)
|
|
|
|
module AnalysisResults = struct
|
|
type t = (string * Specs.summary) list
|
|
|
|
let spec_files_from_cmdline =
|
|
(* parse command-line arguments, and find spec files specified there *)
|
|
let args = ref [] in
|
|
let f arg =
|
|
if not (Filename.check_suffix arg Config.specs_files_suffix) && arg <> "."
|
|
then print_usage_exit "arguments must be .specs files"
|
|
else args := arg::!args in
|
|
Arg.parse "INFERPRINT_ARGS" arg_desc f usage;
|
|
if !test_filtering then
|
|
begin
|
|
Inferconfig.test ();
|
|
exit(0)
|
|
end;
|
|
IList.append (if !args = ["."] then begin
|
|
let arr = Sys.readdir "." in
|
|
let all_files = Array.to_list arr in
|
|
IList.filter
|
|
(fun fname -> (Filename.check_suffix fname Config.specs_files_suffix))
|
|
all_files
|
|
end
|
|
else !args) (load_specfiles ())
|
|
|
|
(** apply [f] to [arg] with the gc compaction disabled during the execution *)
|
|
let apply_without_gc f arg =
|
|
let stat = Gc.get () in
|
|
let space_oh = stat.Gc.space_overhead in
|
|
Gc.set { stat with Gc.space_overhead = 10000 };
|
|
let res = f arg in
|
|
Gc.set { stat with Gc.space_overhead = space_oh };
|
|
res
|
|
|
|
(** Load .specs files in memory and return list of summaries *)
|
|
let load_summaries_in_memory () : t =
|
|
let summaries = ref [] in
|
|
let load_file fname =
|
|
match Specs.load_summary (DB.filename_from_string fname) with
|
|
| None ->
|
|
L.err "Error: cannot open file %s@." fname;
|
|
exit 0
|
|
| Some summary ->
|
|
summaries := (fname, summary) :: !summaries in
|
|
apply_without_gc (IList.iter load_file) spec_files_from_cmdline;
|
|
let summ_cmp (_, summ1) (_, summ2) =
|
|
let n =
|
|
DB.source_file_compare
|
|
summ1.Specs.attributes.ProcAttributes.loc.Location.file
|
|
summ2.Specs.attributes.ProcAttributes.loc.Location.file in
|
|
if n <> 0 then n
|
|
else int_compare
|
|
summ1.Specs.attributes.ProcAttributes.loc.Location.line
|
|
summ2.Specs.attributes.ProcAttributes.loc.Location.line in
|
|
IList.sort summ_cmp !summaries
|
|
|
|
(** Create an iterator which loads spec files one at a time *)
|
|
let iterator_of_spec_files () =
|
|
let sorted_spec_files = IList.sort string_compare spec_files_from_cmdline in
|
|
let do_spec f fname =
|
|
match Specs.load_summary (DB.filename_from_string fname) with
|
|
| None ->
|
|
L.err "Error: cannot open file %s@." fname;
|
|
exit 0
|
|
| Some summary ->
|
|
f (fname, summary) in
|
|
let iterate f =
|
|
IList.iter (do_spec f) sorted_spec_files in
|
|
iterate
|
|
|
|
(** Serializer for analysis results *)
|
|
let analysis_results_serializer : t Serialization.serializer =
|
|
Serialization.create_serializer Serialization.analysis_results_key
|
|
|
|
(** Load analysis_results from a file *)
|
|
let load_analysis_results_from_file (filename : DB.filename) : t option =
|
|
Serialization.from_file analysis_results_serializer filename
|
|
|
|
(** Save analysis_results into a file *)
|
|
let store_analysis_results_to_file (filename : DB.filename) (analysis_results: t) =
|
|
Serialization.to_file analysis_results_serializer filename analysis_results
|
|
|
|
(** Return an iterator over all the summaries.
|
|
If options - load_results or - save_results are used,
|
|
all the summaries are loaded in memory *)
|
|
let get_summary_iterator () =
|
|
let iterator_of_summary_list r =
|
|
fun f -> IList.iter f r in
|
|
match !load_analysis_results with
|
|
| None ->
|
|
begin
|
|
match !save_analysis_results with
|
|
| None ->
|
|
iterator_of_spec_files ()
|
|
| Some s ->
|
|
let r = load_summaries_in_memory () in
|
|
store_analysis_results_to_file (DB.filename_from_string s) r;
|
|
iterator_of_summary_list r
|
|
end
|
|
| Some fname ->
|
|
begin
|
|
match load_analysis_results_from_file (DB.filename_from_string fname) with
|
|
| Some r ->
|
|
iterator_of_summary_list r
|
|
| None ->
|
|
L.err "Error: cannot open analysis results file %s@." fname;
|
|
exit 0
|
|
end
|
|
end
|
|
|
|
(* warning: computing top procedures iterates over summaries twice *)
|
|
let compute_top_procedures = ref false
|
|
|
|
let register_perf_stats_report () =
|
|
let stats_dir = Filename.concat !Config.results_dir Config.reporting_stats_dir_name in
|
|
let stats_file = Filename.concat stats_dir (Config.perf_stats_prefix ^ ".json") in
|
|
DB.create_dir !Config.results_dir ;
|
|
DB.create_dir stats_dir ;
|
|
PerfStats.register_report_at_exit stats_file
|
|
|
|
let () =
|
|
register_perf_stats_report () ;
|
|
|
|
Config.developer_mode := true;
|
|
Config.print_using_diff := true;
|
|
handle_source_file_copy_option ();
|
|
let iterate_summaries = AnalysisResults.get_summary_iterator () in
|
|
let filters =
|
|
match !analyzer with
|
|
| None -> Inferconfig.do_not_filter
|
|
| Some analyzer -> Inferconfig.create_filters analyzer in
|
|
|
|
let pdflatex fname = ignore (Sys.command ("pdflatex " ^ fname)) in
|
|
do_outf latex (fun outf -> begin_latex_file outf.fmt);
|
|
do_outf procs_csv (fun outf -> ProcsCsv.pp_header outf.fmt ());
|
|
do_outf procs_xml (fun outf -> ProcsXml.pp_procs_open outf.fmt ());
|
|
do_outf calls_csv (fun outf -> CallsCsv.pp_header outf.fmt ());
|
|
do_outf bugs_csv (fun outf -> BugsCsv.pp_header outf.fmt ());
|
|
do_outf bugs_json (fun outf -> BugsJson.pp_json_open outf.fmt ());
|
|
do_outf bugs_xml (fun outf -> BugsXml.pp_bugs_open outf.fmt ());
|
|
do_outf report (fun outf -> Report.pp_header outf.fmt ());
|
|
let top_proc = TopProcedures.create () in
|
|
if !compute_top_procedures && (!procs_csv != None || !procs_xml != None)
|
|
then iterate_summaries (TopProcedures.process_summary top_proc);
|
|
let top_proc_set = TopProcedures.top_set top_proc in
|
|
let linereader = Printer.LineReader.create () in
|
|
let stats = Stats.create () in
|
|
iterate_summaries (process_summary filters linereader stats top_proc_set);
|
|
if !unit_test then UnitTest.print_unit_test_main ();
|
|
do_outf procs_csv close_outf;
|
|
do_outf procs_xml (fun outf -> ProcsXml.pp_procs_close outf.fmt (); close_outf outf);
|
|
do_outf bugs_csv close_outf;
|
|
do_outf bugs_json (fun outf -> BugsJson.pp_json_close outf.fmt (); close_outf outf);
|
|
do_outf bugs_json close_outf;
|
|
do_outf calls_csv close_outf;
|
|
do_outf bugs_txt close_outf;
|
|
do_outf bugs_xml (fun outf -> BugsXml.pp_bugs_close outf.fmt (); close_outf outf);
|
|
do_outf latex (fun outf ->
|
|
Latex.pp_end outf.fmt ();
|
|
close_outf outf;
|
|
pdflatex outf.fname;
|
|
let pdf_name = (Filename.chop_extension outf.fname) ^ ".pdf" in
|
|
ignore (Sys.command ("open " ^ pdf_name)));
|
|
do_outf report (fun outf -> F.fprintf outf.fmt "%a@?" Report.pp_stats stats; close_outf outf);
|
|
if !precondition_stats then PreconditionStats.pp_stats ()
|