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.

325 lines
11 KiB

(*
* Copyright (c) 2015 - 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! IStd
module Hashtbl = Caml.Hashtbl
module L = Logging
module F = Format
type node_tag =
| Condition of bool
| Exception of Typ.name
| Procedure_start of Typ.Procname.t
| Procedure_end of Typ.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 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_id_key = {node_id: int; node_key: Caml.Digest.t}
type err_key =
{ err_kind: Exceptions.err_kind
; in_footprint: bool
; err_name: IssueType.t
; err_desc: Localise.error_desc
; severity: string }
[@@deriving compare]
(** Data associated to a specific error *)
type err_data =
{ node_id_key: node_id_key
; 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 }
let compare_err_data err_data1 err_data2 = Location.compare err_data1.loc err_data2.loc
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 [@@deriving compare]
(* NOTE: changing the hash function can change the order in which issues are reported. *)
let hash key =
Hashtbl.hash
(key.err_kind, key.in_footprint, key.err_name, Localise.error_desc_hash key.err_desc)
let equal key1 key2 =
[%compare.equal : Exceptions.err_kind * bool * IssueType.t]
(key1.err_kind, key1.in_footprint, key1.err_name)
(key2.err_kind, key2.in_footprint, 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 err_kind, fotprint / re - execution flag, error name,
error description, severity, to set of err_data. *)
type t = ErrDataSet.t ErrLogHash.t
let compare x y =
let bindings x = ErrLogHash.fold (fun k d l -> (k, d) :: l) x [] in
[%compare : (ErrLogHash.Key.t * ErrDataSet.t) list] (bindings x) (bindings y)
[explore] print only non-default values in the attributes Summary: Attributes have lots of fields, but the majority is usually the same from one procedure to the other. This attempts to print only non-default values, where the default is the "empty" attributes (as opposed to the "most commonly seen value" for each field), in an attempt to be predictible. This is largely something that should be done with a ppx but since there's only one use case it didn't seem worth the trouble. Output of `infer explore --procedures --procedures-attributes --procedures-name` before: ``` test: examples/hello.c test <defined> {access= <Default>; captured= []; did_preanalysis= false; err_log= [ ]; exceptions= []; formals= []; const_formals= []; by_vals= []; func_attributes= []; is_abstract= false; is_bridge_method= false; is_defined= true; is_cpp_noexcept_method= false; is_java_synchronized_method= false; is_model= false; is_specialized= false; is_synthetic_method= false; clang_method_kind= C_FUNCTION; loc= line 12, column 1; translation_unit= Some examples/hello.c; locals= [{name= s;typ= ;attributes= }]; method_annotation= <> (); objc_accessor= None; proc_flags= []; proc_name= test; ret_type= ; source_file_captured=examples/hello.c} int Hello.test(): examples/Hello.java int Hello.test() <defined> {access= <Default>; captured= []; did_preanalysis= false; err_log= [ ]; exceptions= []; formals= [(this,)]; const_formals= []; by_vals= [ ]; func_attributes= []; is_abstract= false; is_bridge_method= false; is_defined= true; is_cpp_noexcept_method= false; is_java_synchronized_method= false; is_model= false; is_specialized= false; is_synthetic_method= false; clang_method_kind= C_FUNCTION; loc= line 11; translation_unit= None; locals= [{name= $irvar0;typ= ;attributes= }; {name= s;typ= ;attributes= }]; method_annotation= <> (); objc_accessor= None; proc_flags= []; proc_name= int Hello.test(); ret_type= ; source_file_captured=examples/Hello.java} Hello.<init>(): examples/Hello.java Hello.<init>() <defined> {access= <Default>; captured= []; did_preanalysis= false; err_log= [ ]; exceptions= []; formals= [(this,)]; const_formals= []; by_vals= [ ]; func_attributes= []; is_abstract= false; is_bridge_method= false; is_defined= true; is_cpp_noexcept_method= false; is_java_synchronized_method= false; is_model= false; is_specialized= false; is_synthetic_method= false; clang_method_kind= C_FUNCTION; loc= line 10; translation_unit= None; locals= []; method_annotation= <> (); objc_accessor= None; proc_flags= []; proc_name= Hello.<init>(); ret_type= ; source_file_captured=examples/Hello.java} ``` Now: ``` test source_file: examples/hello.c proc_name: test attribute_kind: <defined> attributes: { proc_name= test ; source_file_captured= examples/hello.c ; formals= [] ; is_defined= true ; loc= line 12, column 1 ; translation_unit= <Some examples/hello.c> ; locals= [{ name= s; typ= int* }] ; ret_type= void } int Hello.test() source_file: examples/Hello.java proc_name: int Hello.test() attribute_kind: <defined> attributes: { proc_name= int Hello.test() ; source_file_captured= examples/Hello.java ; formals= [(this,Hello*)] ; is_defined= true ; loc= line 11 ; locals= [{ name= $irvar0; typ= void }; { name= s; typ= java.lang.String* }] ; ret_type= int } Hello.<init>() source_file: examples/Hello.java proc_name: Hello.<init>() attribute_kind: <defined> attributes: { proc_name= Hello.<init>() ; source_file_captured= examples/Hello.java ; formals= [(this,Hello*)] ; is_defined= true ; loc= line 10 ; ret_type= void } ``` Reviewed By: mbouaziz Differential Revision: D7757890 fbshipit-source-id: 5507ec6
7 years ago
let equal x y = [%compare.equal : t] x y
(** 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
(** Return the number of elements in the error log which satisfy [filter] *)
let size filter (err_log: t) =
let count = ref 0 in
ErrLogHash.iter
(fun key err_datas ->
if filter key.err_kind key.in_footprint then count := !count + ErrDataSet.cardinal err_datas
)
err_log ;
!count
(** Print errors from error log *)
let pp_errors fmt (errlog: t) =
let f key _ =
if Exceptions.equal_err_kind key.err_kind Exceptions.Kerror 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_err_kind key.err_kind Exceptions.Kwarning 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_key.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 do_fp ek key err_datas =
if Exceptions.equal_err_kind key.err_kind ek && Bool.equal do_fp key.in_footprint 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
F.fprintf fmt "%aERRORS DURING FOOTPRINT@\n" Io_infer.Html.pp_hline () ;
ErrLogHash.iter (pp_err_log true Exceptions.Kerror) errlog ;
F.fprintf fmt "%aERRORS DURING RE-EXECUTION@\n" Io_infer.Html.pp_hline () ;
ErrLogHash.iter (pp_err_log false Exceptions.Kerror) errlog ;
F.fprintf fmt "%aWARNINGS DURING FOOTPRINT@\n" Io_infer.Html.pp_hline () ;
ErrLogHash.iter (pp_err_log true Exceptions.Kwarning) errlog ;
F.fprintf fmt "%aWARNINGS DURING RE-EXECUTION@\n" Io_infer.Html.pp_hline () ;
ErrLogHash.iter (pp_err_log false Exceptions.Kwarning) errlog ;
F.fprintf fmt "%aINFOS DURING FOOTPRINT@\n" Io_infer.Html.pp_hline () ;
ErrLogHash.iter (pp_err_log true Exceptions.Kinfo) errlog ;
F.fprintf fmt "%aINFOS DURING RE-EXECUTION@\n" Io_infer.Html.pp_hline () ;
ErrLogHash.iter (pp_err_log false Exceptions.Kinfo) errlog
(* I use string in case we want to display a different name to the user*)
let severity_to_str severity =
match severity with
| Exceptions.High ->
"HIGH"
| Exceptions.Medium ->
"MEDIUM"
| Exceptions.Low ->
"LOW"
(** 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 ->
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 err_kind err_log loc (node_id, node_key) session ltr
?linters_def_file ?doc_url ?access exn =
let lang = Typ.Procname.get_language procname in
let error = Exceptions.recognize_exception exn in
let err_kind = match error.kind with Some err_kind -> err_kind | _ -> err_kind 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 =
EventLogger.AnalysisIssue
{ bug_type= error.name.IssueType.unique_id
; bug_kind= Exceptions.err_kind_string err_kind
; clang_method_kind= (match lang with Language.Clang -> clang_method_kind | _ -> None)
; exception_triggered_location= error.ocaml_pos
; lang= Language.to_explicit_string lang
; procedure_name= Typ.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_key = {node_id; node_key} in
let err_data =
{ node_id_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 }
in
let err_key =
{ err_kind
; in_footprint= !Config.footprint
; err_name= error.name
; err_desc= error.description
; severity= severity_to_str error.severity }
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 ~node_key loc err_kind error.name error.description error.ocaml_pos)
() ;
if err_kind <> Exceptions.Kerror 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 err_kind with
| Exceptions.Kerror ->
L.d_error
| Exceptions.Kwarning ->
L.d_warning
| Exceptions.Kinfo | Exceptions.Kadvice | Exceptions.Klike ->
L.d_info
in
d warn_str ; L.d_ln () )
in
if should_print_now then print_now ()