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.
326 lines
11 KiB
326 lines
11 KiB
(*
|
|
* 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
|
|
module L = Logging
|
|
module F = Format
|
|
|
|
type node_tag =
|
|
| Condition of bool
|
|
| Exception of Typ.name
|
|
| Procedure_start of Procname.t
|
|
| Procedure_end of Procname.t
|
|
|
|
(** Element of a loc trace *)
|
|
type loc_trace_elem =
|
|
{ lt_level: int (** nesting level of procedure calls *)
|
|
; lt_loc: Location.t (** source location at the current step in the trace *)
|
|
; lt_description: string (** description of the current step in the trace *)
|
|
; lt_node_tags: node_tag list (** tags describing the node at the current location *) }
|
|
|
|
let pp_loc_trace_elem fmt {lt_level; lt_loc} = F.fprintf fmt "%d %a" lt_level Location.pp lt_loc
|
|
|
|
let pp_loc_trace fmt l = PrettyPrintable.pp_collection ~pp_item:pp_loc_trace_elem fmt l
|
|
|
|
let contains_exception loc_trace_elem =
|
|
let pred nt =
|
|
match nt with Exception _ -> true | Condition _ | Procedure_start _ | Procedure_end _ -> false
|
|
in
|
|
List.exists ~f:pred loc_trace_elem.lt_node_tags
|
|
|
|
|
|
let make_trace_element lt_level lt_loc lt_description lt_node_tags =
|
|
{lt_level; lt_loc; lt_description; lt_node_tags}
|
|
|
|
|
|
(** Trace of locations *)
|
|
type loc_trace = loc_trace_elem list
|
|
|
|
let concat_traces labelled_traces =
|
|
List.fold_right labelled_traces ~init:[] ~f:(fun labelled_trace res ->
|
|
match labelled_trace with
|
|
| _, [] ->
|
|
res
|
|
| "", trace ->
|
|
trace @ res
|
|
| label, ({lt_loc} :: _ as trace) ->
|
|
(make_trace_element 0 lt_loc label [] :: trace) @ res )
|
|
|
|
|
|
let compute_local_exception_line loc_trace =
|
|
let open Base.Continue_or_stop in
|
|
let compute_local_exception_line (last_known_step_at_level_zero_opt, line_opt) step =
|
|
let last_known_step_at_level_zero_opt' =
|
|
if Int.equal step.lt_level 0 then Some step else last_known_step_at_level_zero_opt
|
|
in
|
|
match last_known_step_at_level_zero_opt' with
|
|
| Some step_zero when contains_exception step ->
|
|
Stop (Some step_zero.lt_loc.line)
|
|
| _ ->
|
|
Continue (last_known_step_at_level_zero_opt', line_opt)
|
|
in
|
|
List.fold_until ~init:(None, None) ~f:compute_local_exception_line ~finish:snd loc_trace
|
|
|
|
|
|
type node =
|
|
| UnknownNode
|
|
| FrontendNode of {node_key: Procdesc.NodeKey.t}
|
|
| BackendNode of {node: Procdesc.Node.t}
|
|
|
|
type err_key = {severity: Exceptions.severity; err_name: IssueType.t; err_desc: Localise.error_desc}
|
|
[@@deriving compare]
|
|
|
|
let merge_err_key err_key1 err_key2 ~merge_issues ~merge_descriptions =
|
|
let max_sev sev1 sev2 = if Exceptions.compare_severity sev1 sev2 >= 0 then sev1 else sev2 in
|
|
{ severity= max_sev err_key1.severity err_key2.severity
|
|
; err_name= merge_issues err_key1.err_name err_key2.err_name
|
|
; err_desc=
|
|
Localise.verbatim_desc
|
|
(merge_descriptions err_key1.err_desc.descriptions err_key2.err_desc.descriptions) }
|
|
|
|
|
|
(** Data associated to a specific error *)
|
|
type err_data =
|
|
{ node_id: int
|
|
; node_key: Procdesc.NodeKey.t option
|
|
; session: int
|
|
; loc: Location.t
|
|
; loc_in_ml_source: L.ocaml_pos option
|
|
; loc_trace: loc_trace
|
|
; err_class: Exceptions.err_class
|
|
; visibility: Exceptions.visibility
|
|
; linters_def_file: string option
|
|
; doc_url: string option
|
|
; access: string option
|
|
; extras: Jsonbug_t.extra option (* NOTE: Please consider adding new fields as part of extras *)
|
|
}
|
|
|
|
let compare_err_data err_data1 err_data2 = Location.compare err_data1.loc err_data2.loc
|
|
|
|
let merge_err_data err_data1 err_data2 =
|
|
{ node_id= 0
|
|
; node_key= None
|
|
; session= 0
|
|
; loc= {err_data1.loc with col= -1}
|
|
; loc_in_ml_source= None
|
|
; loc_trace= concat_traces [("", err_data1.loc_trace); ("-----------", err_data2.loc_trace)]
|
|
; err_class= Exceptions.Checker
|
|
; visibility= Exceptions.Exn_user
|
|
; linters_def_file= None
|
|
; doc_url= None
|
|
; access= None
|
|
; extras= None }
|
|
|
|
|
|
module ErrDataSet = (* set err_data with no repeated loc *)
|
|
Caml.Set.Make (struct
|
|
type t = err_data
|
|
|
|
let compare = compare_err_data
|
|
end)
|
|
|
|
(** Hash table to implement error logs *)
|
|
module ErrLogHash = struct
|
|
module Key = struct
|
|
type t = err_key
|
|
|
|
(* NOTE: changing the hash function can change the order in which issues are reported. *)
|
|
let hash key = Hashtbl.hash (key.severity, key.err_name, Localise.error_desc_hash key.err_desc)
|
|
|
|
let equal key1 key2 =
|
|
[%compare.equal: Exceptions.severity * IssueType.t] (key1.severity, key1.err_name)
|
|
(key2.severity, key2.err_name)
|
|
&& Localise.error_desc_equal key1.err_desc key2.err_desc
|
|
end
|
|
|
|
include Hashtbl.Make (Key)
|
|
end
|
|
|
|
(** Type of the error log, to be reset once per function. Map severity, footprint / re - execution
|
|
flag, error name, error description, severity, to set of err_data. *)
|
|
type t = ErrDataSet.t ErrLogHash.t
|
|
|
|
(** Empty error log *)
|
|
let empty () = ErrLogHash.create 13
|
|
|
|
(** type of the function to be passed to iter *)
|
|
type iter_fun = err_key -> err_data -> unit
|
|
|
|
(** Apply f to nodes and error names *)
|
|
let iter (f : iter_fun) (err_log : t) =
|
|
ErrLogHash.iter
|
|
(fun err_key set -> ErrDataSet.iter (fun err_data -> f err_key err_data) set)
|
|
err_log
|
|
|
|
|
|
let fold (f : err_key -> err_data -> 'a -> 'a) t acc =
|
|
ErrLogHash.fold
|
|
(fun err_key set acc -> ErrDataSet.fold (fun err_data acc -> f err_key err_data acc) set acc)
|
|
t acc
|
|
|
|
|
|
(** Print errors from error log *)
|
|
let pp_errors fmt (errlog : t) =
|
|
let f key _ =
|
|
if Exceptions.equal_severity key.severity Exceptions.Error then
|
|
F.fprintf fmt "%a@ " IssueType.pp key.err_name
|
|
in
|
|
ErrLogHash.iter f errlog
|
|
|
|
|
|
(** Print warnings from error log *)
|
|
let pp_warnings fmt (errlog : t) =
|
|
let f key _ =
|
|
if Exceptions.equal_severity key.severity Exceptions.Warning then
|
|
F.fprintf fmt "%a %a@ " IssueType.pp key.err_name Localise.pp_error_desc key.err_desc
|
|
in
|
|
ErrLogHash.iter f errlog
|
|
|
|
|
|
(** Print an error log in html format *)
|
|
let pp_html source path_to_root fmt (errlog : t) =
|
|
let pp_eds fmt err_datas =
|
|
let pp_nodeid_session_loc fmt err_data =
|
|
Io_infer.Html.pp_session_link source path_to_root fmt
|
|
(err_data.node_id, err_data.session, err_data.loc.Location.line)
|
|
in
|
|
ErrDataSet.iter (pp_nodeid_session_loc fmt) err_datas
|
|
in
|
|
let pp_err_log ek key err_datas =
|
|
if Exceptions.equal_severity key.severity ek then
|
|
F.fprintf fmt "<br>%a %a %a" IssueType.pp key.err_name Localise.pp_error_desc key.err_desc
|
|
pp_eds err_datas
|
|
in
|
|
let pp severity =
|
|
F.fprintf fmt "%a%s DURING FOOTPRINT@\n" Io_infer.Html.pp_hline ()
|
|
(Exceptions.severity_string severity) ;
|
|
ErrLogHash.iter (pp_err_log severity) errlog
|
|
in
|
|
List.iter Exceptions.[Advice; Error; Info; Like; Warning] ~f:pp
|
|
|
|
|
|
(** Add an error description to the error log unless there is one already at the same node +
|
|
session; return true if added *)
|
|
let add_issue tbl err_key (err_datas : ErrDataSet.t) : bool =
|
|
try
|
|
let current_eds = ErrLogHash.find tbl err_key in
|
|
if ErrDataSet.subset err_datas current_eds then false
|
|
else (
|
|
ErrLogHash.replace tbl err_key (ErrDataSet.union err_datas current_eds) ;
|
|
true )
|
|
with Caml.Not_found | Not_found_s _ ->
|
|
ErrLogHash.add tbl err_key err_datas ;
|
|
true
|
|
|
|
|
|
(** Update an old error log with a new one *)
|
|
let update errlog_old errlog_new =
|
|
ErrLogHash.iter (fun err_key l -> ignore (add_issue errlog_old err_key l)) errlog_new
|
|
|
|
|
|
let log_issue procname ~clang_method_kind severity err_log ~loc ~node ~session ~ltr
|
|
~linters_def_file ~doc_url ~access ~extras exn =
|
|
let error = Exceptions.recognize_exception exn in
|
|
let severity = Option.value error.severity ~default:severity in
|
|
let hide_java_loc_zero =
|
|
(* hide java errors at location zero unless in -developer_mode *)
|
|
(not Config.developer_mode) && Language.curr_language_is Java && Int.equal loc.Location.line 0
|
|
in
|
|
let hide_memory_error =
|
|
match Localise.error_desc_get_bucket error.description with
|
|
| Some bucket when String.equal bucket Mleak_buckets.ml_bucket_unknown_origin ->
|
|
not Mleak_buckets.should_raise_leak_unknown_origin
|
|
| _ ->
|
|
false
|
|
in
|
|
let report_developer_exn exn =
|
|
match exn with Exceptions.Dummy_exception _ -> false | _ -> true
|
|
in
|
|
let exn_developer =
|
|
Exceptions.equal_visibility error.visibility Exceptions.Exn_developer
|
|
&& report_developer_exn exn
|
|
in
|
|
let should_report =
|
|
Exceptions.equal_visibility error.visibility Exceptions.Exn_user
|
|
|| (Config.developer_mode && exn_developer)
|
|
in
|
|
( if exn_developer then
|
|
let issue =
|
|
let lang = Procname.get_language procname in
|
|
let clang_method_kind =
|
|
match lang with
|
|
| Language.Clang ->
|
|
Option.map ~f:ClangMethodKind.to_string clang_method_kind
|
|
| _ ->
|
|
None
|
|
in
|
|
EventLogger.AnalysisIssue
|
|
{ bug_type= error.name.IssueType.unique_id
|
|
; bug_kind= Exceptions.severity_string severity
|
|
; clang_method_kind
|
|
; exception_triggered_location= error.ocaml_pos
|
|
; lang= Language.to_explicit_string lang
|
|
; procedure_name= Procname.to_string procname
|
|
; source_location= loc }
|
|
in
|
|
EventLogger.log issue ) ;
|
|
if should_report && (not hide_java_loc_zero) && not hide_memory_error then
|
|
let added =
|
|
let node_id, node_key =
|
|
match node with
|
|
| UnknownNode ->
|
|
(0, None)
|
|
| FrontendNode {node_key} ->
|
|
(0, Some node_key)
|
|
| BackendNode {node} ->
|
|
((Procdesc.Node.get_id node :> int), Some (Procdesc.Node.compute_key node))
|
|
in
|
|
let err_data =
|
|
{ node_id
|
|
; node_key
|
|
; session
|
|
; loc
|
|
; loc_in_ml_source= error.ocaml_pos
|
|
; loc_trace= ltr
|
|
; err_class= error.category
|
|
; visibility= error.visibility
|
|
; linters_def_file
|
|
; doc_url
|
|
; access
|
|
; extras }
|
|
in
|
|
let err_key = {severity; err_name= error.name; err_desc= error.description} in
|
|
add_issue err_log err_key (ErrDataSet.singleton err_data)
|
|
in
|
|
let should_print_now = match exn with Exceptions.Internal_error _ -> true | _ -> added in
|
|
let print_now () =
|
|
L.(debug Analysis Medium)
|
|
"@\n%a@\n@?"
|
|
(Exceptions.pp_err loc severity error.name error.description error.ocaml_pos)
|
|
() ;
|
|
if not (Exceptions.equal_severity severity Exceptions.Error) then (
|
|
let warn_str =
|
|
let pp fmt =
|
|
Format.fprintf fmt "%s %a" error.name.IssueType.unique_id Localise.pp_error_desc
|
|
error.description
|
|
in
|
|
F.asprintf "%t" pp
|
|
in
|
|
let d =
|
|
match severity with
|
|
| Exceptions.Error ->
|
|
L.d_error
|
|
| Exceptions.Warning ->
|
|
L.d_warning
|
|
| Exceptions.Info | Exceptions.Advice | Exceptions.Like ->
|
|
L.d_info
|
|
in
|
|
d warn_str ; L.d_ln () )
|
|
in
|
|
if should_print_now then print_now ()
|