(* * Copyright (c) 2009-2013, Monoidics ltd. * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. *) open! IStd module Hashtbl = Caml.Hashtbl (** Printers for the analysis results *) module L = Logging module F = Format (** Module to read specific lines from files. The data from any file will stay in memory until the handle is collected by the gc. *) module LineReader = struct (** Map a file name to an array of string, one for each line in the file. *) type t = (SourceFile.t, string array) Hashtbl.t let create () = Hashtbl.create 1 let read_file fname = let cin = In_channel.create fname in let lines = ref [] in try while true do let line_raw = In_channel.input_line_exn cin in let line = let len = String.length line_raw in if len > 0 && Char.equal line_raw.[len - 1] '\r' then String.sub line_raw ~pos:0 ~len:(len - 1) else line_raw in lines := line :: !lines done ; assert false (* execution never reaches here *) with End_of_file -> In_channel.close cin ; Array.of_list_rev !lines let file_data (hash : t) fname = try Some (Hashtbl.find hash fname) with Caml.Not_found -> ( try let lines_arr = read_file (SourceFile.to_abs_path fname) in Hashtbl.add hash fname lines_arr ; Some lines_arr with exn when SymOp.exn_not_failure exn -> None ) let from_file_linenum hash fname linenum = match file_data hash fname with | Some lines_arr when linenum > 0 && linenum <= Array.length lines_arr -> Some lines_arr.(linenum - 1) | _ -> None let from_loc hash loc = from_file_linenum hash loc.Location.file loc.Location.line let iter hash fname ~f = file_data hash fname |> Option.iter ~f:(Array.iteri ~f:(fun linenum line -> f (linenum + 1) line)) end (** Current formatter for the html output *) let curr_html_formatter = ref F.std_formatter (** Return true if the node was visited during analysis *) let is_visited node = match Summary.OnDisk.get (Procdesc.Node.get_proc_name node) with | None -> false | Some summary -> let stats = summary.Summary.stats in let node_id = (Procdesc.Node.get_id node :> int) in Summary.Stats.is_visited stats node_id let pp_node_link_seq = let compare_node = let key node = (Procdesc.Node.get_wto_index node, Procdesc.Node.get_id node) in fun node1 node2 -> [%compare: int * Procdesc.Node.id] (key node1) (key node2) in fun path_to_root ~description fmt nodes -> let nodes = List.sort nodes ~compare:compare_node in let pp_one fmt node = let description = if description then Procdesc.Node.get_description (Pp.html Black) node else "" in let pname = Procdesc.Node.get_proc_name node in Io_infer.Html.pp_node_link path_to_root pname ~description ~preds:(List.map ~f:Procdesc.Node.get_id (Procdesc.Node.get_preds node) :> int list) ~succs:(List.map ~f:Procdesc.Node.get_id (Procdesc.Node.get_succs node) :> int list) ~exn:(List.map ~f:Procdesc.Node.get_id (Procdesc.Node.get_exn node) :> int list) ~isvisited:(is_visited node) fmt (Procdesc.Node.get_id node :> int) in Pp.seq pp_one fmt nodes (** Print information into html files for nodes when starting and finishing the processing of a node *) module NodesHtml : sig val start_session : pp_name:(Format.formatter -> unit) -> Procdesc.Node.t -> int -> unit val finish_session : Procdesc.Node.t -> unit end = struct let log_files = Hashtbl.create 11 let pp_node_link_seq fmt node = pp_node_link_seq [".."] ~description:false fmt node let start_session ~pp_name node session = let loc = Procdesc.Node.get_loc node in let source = loc.Location.file in let line = loc.Location.line in let proc_name = Procdesc.Node.get_proc_name node in let nodeid = (Procdesc.Node.get_id node :> int) in let node_fname = Io_infer.Html.node_filename proc_name nodeid in let needs_initialization, (fd, fmt) = let node_path = ["nodes"; node_fname] in let modified = Io_infer.Html.modified_during_analysis source node_path in if modified then (false, Io_infer.Html.open_out source node_path) else (true, Io_infer.Html.create source node_path) in curr_html_formatter := fmt ; Hashtbl.replace log_files (node_fname, source) fd ; if needs_initialization then ( F.fprintf fmt "
@\n%a@\n" ProcAttributes.pp (Procdesc.get_attributes pdesc) ; Io_infer.Html.close (fd, fmt) end module FilesHtml : sig val write_all_html_files : SourceFile.t -> unit val ensure_file_is_written : Procdesc.Node.t -> unit end = struct (* Only used in debug html mode *) let linereader = LineReader.create () (** Create a hash table mapping line numbers to the set of errors occurring on that line *) let create_table_err_per_line err_log = let err_per_line = Hashtbl.create 17 in let add_err (key : Errlog.err_key) (err_data : Errlog.err_data) = let err_str = F.asprintf "%s %a" key.err_name.IssueType.unique_id Localise.pp_error_desc key.err_desc in try let set = Hashtbl.find err_per_line err_data.loc.Location.line in Hashtbl.replace err_per_line err_data.loc.Location.line (String.Set.add set err_str) with Caml.Not_found -> Hashtbl.add err_per_line err_data.loc.Location.line (String.Set.singleton err_str) in Errlog.iter add_err err_log ; err_per_line (** Create error message for html file *) let pp_err_message fmt err_string = F.fprintf fmt "\n
%d | %s " line_number line_number line_html ; ( match Hashtbl.find table_nodes_at_linenum line_number with | nodes_at_linenum -> pp_node_link_seq [fname_encoding] ~description:true fmt nodes_at_linenum ; List.iter nodes_at_linenum ~f:(fun n -> match Procdesc.Node.get_kind n with | Procdesc.Node.Start_node -> let proc_name = Procdesc.Node.get_proc_name n in let proc_name_escaped = Escape.escape_xml (Procname.to_string proc_name) in if Summary.OnDisk.get proc_name |> Option.is_some then ( F.pp_print_char fmt ' ' ; let label = F.asprintf "summary for %s" proc_name_escaped in Io_infer.Html.pp_proc_link [fname_encoding] proc_name fmt label ) else F.fprintf fmt "no summary for %s" proc_name_escaped | _ -> () ) | exception Caml.Not_found -> () ) ; ( match Hashtbl.find table_err_per_line line_number with | errset -> String.Set.iter errset ~f:(pp_err_message fmt) | exception Caml.Not_found -> () ) ; F.fprintf fmt " |