diff --git a/infer/src/backend/InferPrint.ml b/infer/src/backend/InferPrint.ml index ef6eb6aaf..d31635242 100644 --- a/infer/src/backend/InferPrint.ml +++ b/infer/src/backend/InferPrint.ml @@ -277,72 +277,6 @@ module JsonCostsPrinter = MakeJsonListPrinter (struct None end) -let pp_custom_of_report fmt report fields = - let pp_custom_of_issue fmt (issue : Jsonbug_t.jsonbug) = - let open Jsonbug_t in - let comma_separator index = if index > 0 then ", " else "" in - let pp_trace fmt trace comma = - let pp_trace_elem fmt {description} = F.pp_print_string fmt description in - let trace_without_empty_descs = - List.filter ~f:(fun {description} -> not (String.is_empty description)) trace - in - F.fprintf fmt "%s[%a]" comma (Pp.comma_seq pp_trace_elem) trace_without_empty_descs - in - let pp_field index field = - match field with - | `Issue_field_bug_type -> - Format.fprintf fmt "%s%s" (comma_separator index) issue.bug_type - | `Issue_field_bucket -> - let bucket = - match - String.lsplit2 issue.qualifier ~on:']' - |> Option.map ~f:fst - |> Option.bind ~f:(String.chop_prefix ~prefix:"[") - with - | Some bucket -> - bucket - | None -> - "no_bucket" - in - Format.fprintf fmt "%s%s" (comma_separator index) bucket - | `Issue_field_qualifier -> - Format.fprintf fmt "%s%s" (comma_separator index) issue.qualifier - | `Issue_field_severity -> - Format.fprintf fmt "%s%s" (comma_separator index) issue.severity - | `Issue_field_line -> - Format.fprintf fmt "%s%d" (comma_separator index) issue.line - | `Issue_field_column -> - Format.fprintf fmt "%s%d" (comma_separator index) issue.column - | `Issue_field_procedure -> - Format.fprintf fmt "%s%s" (comma_separator index) issue.procedure - | `Issue_field_procedure_start_line -> - Format.fprintf fmt "%s%d" (comma_separator index) issue.procedure_start_line - | `Issue_field_file -> - Format.fprintf fmt "%s%s" (comma_separator index) issue.file - | `Issue_field_bug_trace -> - pp_trace fmt issue.bug_trace (comma_separator index) - | `Issue_field_key -> - Format.fprintf fmt "%s%s" (comma_separator index) (Caml.Digest.to_hex issue.key) - | `Issue_field_hash -> - Format.fprintf fmt "%s%s" (comma_separator index) (Caml.Digest.to_hex issue.hash) - | `Issue_field_line_offset -> - Format.fprintf fmt "%s%d" (comma_separator index) (issue.line - issue.procedure_start_line) - | `Issue_field_qualifier_contains_potential_exception_note -> - Format.pp_print_bool fmt - (String.is_substring issue.qualifier ~substring:potential_exception_message) - in - List.iteri ~f:pp_field fields ; Format.fprintf fmt "@." - in - List.iter ~f:(pp_custom_of_issue fmt) report - - -let tests_jsonbug_compare (bug1 : Jsonbug_t.jsonbug) (bug2 : Jsonbug_t.jsonbug) = - let open Jsonbug_t in - [%compare: string * string * int * string * Caml.Digest.t] - (bug1.file, bug1.procedure, bug1.line - bug1.procedure_start_line, bug1.bug_type, bug1.hash) - (bug2.file, bug2.procedure, bug2.line - bug2.procedure_start_line, bug2.bug_type, bug2.hash) - - let error_filter filters proc_name file error_name = (Config.write_html || not (IssueType.(equal skip_function) error_name)) && filters.Inferconfig.path_filter file @@ -350,9 +284,9 @@ let error_filter filters proc_name file error_name = && filters.Inferconfig.proc_filter proc_name -type report_kind = Costs | Issues | Summary [@@deriving compare] +type report_kind = Costs | Issues [@@deriving compare] -type bug_format_kind = Json | Tests [@@deriving compare] +type bug_format_kind = Json [@@deriving compare] let get_outfile outfile = match outfile with @@ -369,8 +303,6 @@ let pp_issue_in_format (format_kind, (outfile_opt : Utils.outfile option)) error let outf = get_outfile outfile_opt in IssuesJson.pp outf.fmt {error_filter; proc_name; proc_loc_opt= Some proc_location; err_key; err_data} - | Tests -> - L.die InternalError "Printing issues as tests is not implemented" let pp_issues_in_format (format_kind, (outfile_opt : Utils.outfile option)) = @@ -378,8 +310,6 @@ let pp_issues_in_format (format_kind, (outfile_opt : Utils.outfile option)) = | Json -> let outf = get_outfile outfile_opt in IssuesJson.pp_issues_of_error_log outf.fmt - | Tests -> - L.die InternalError "Printing issues as tests is not implemented" let pp_issues_of_error_log error_filter linereader proc_loc_opt procname err_log bug_format_list = @@ -398,18 +328,11 @@ let collect_issues summary issues_acc = err_log issues_acc -let pp_summary summary = - L.result "Procedure: %a@\n%a@." Procname.pp (Summary.get_proc_name summary) Summary.pp_text - summary - - let pp_costs_in_format (format_kind, (outfile_opt : Utils.outfile option)) = match format_kind with | Json -> let outf = get_outfile outfile_opt in JsonCostsPrinter.pp outf.fmt - | Tests -> - L.die InternalError "Printing costs in tests is not implemented" let pp_costs summary costs_format_list = @@ -424,48 +347,12 @@ let pp_costs summary costs_format_list = let pp_summary_by_report_kind formats_by_report_kind summary issues_acc = let pp_summary_by_report_kind (report_kind, format_list) = - match (report_kind, format_list) with - | Costs, _ -> - pp_costs summary format_list - | Summary, _ when InferCommand.equal Config.command Report && not Config.quiet -> - pp_summary summary - | _ -> - () + match (report_kind, format_list) with Costs, _ -> pp_costs summary format_list | _ -> () in List.iter ~f:pp_summary_by_report_kind formats_by_report_kind ; collect_issues summary issues_acc -let pp_json_report_by_report_kind formats_by_report_kind fname = - match Utils.read_file fname with - | Ok report_lines -> - let pp_json_issues format_list report = - let pp_json_issue (format_kind, (outfile_opt : Utils.outfile option)) = - match format_kind with - | Tests -> - let outf = get_outfile outfile_opt in - pp_custom_of_report outf.fmt report Config.issues_tests_fields - | Json -> - L.die InternalError "Printing issues from json does not support json output" - in - List.iter ~f:pp_json_issue format_list - in - let sorted_report = - let report = Jsonbug_j.report_of_string (String.concat ~sep:"\n" report_lines) in - List.sort ~compare:tests_jsonbug_compare report - in - let pp_report_by_report_kind (report_kind, format_list) = - match (report_kind, format_list) with - | Issues, _ :: _ -> - pp_json_issues format_list sorted_report - | _ -> - () - in - List.iter ~f:pp_report_by_report_kind formats_by_report_kind - | Error error -> - L.(die UserError) "Error reading '%s': %s" fname error - - let pp_lint_issues_by_report_kind formats_by_report_kind error_filter linereader procname error_log = let pp_summary_by_report_kind (report_kind, format_list) = @@ -497,11 +384,7 @@ let mk_format format_kind fname = ~default:[] (Utils.create_outfile fname) -let init_issues_format_list report_json = - let json_format = Option.value_map ~f:(mk_format Json) ~default:[] report_json in - let tests_format = Option.value_map ~f:(mk_format Tests) ~default:[] Config.issues_tests in - json_format @ tests_format - +let init_issues_format_list report_json = mk_format Json report_json let init_files format_list_by_kind = let init_files_of_report_kind (report_kind, format_list) = @@ -513,8 +396,6 @@ let init_files format_list_by_kind = | Json, Issues -> let outfile = get_outfile outfile_opt in IssuesJson.pp_open outfile.fmt () - | Json, Summary | Tests, _ -> - () in List.iter ~f:init_files_of_format format_list in @@ -530,9 +411,7 @@ let finalize_and_close_files format_list_by_kind = JsonCostsPrinter.pp_close outfile.fmt () | Json, Issues -> let outfile = get_outfile outfile_opt in - IssuesJson.pp_close outfile.fmt () - | Json, Summary | Tests, _ -> - () ) ; + IssuesJson.pp_close outfile.fmt () ) ; match outfile_opt with Some outfile -> Utils.close_outf outfile | None -> () in List.iter ~f:close_files_of_format format_list ; @@ -568,21 +447,11 @@ let main ~report_json = let issue_formats = init_issues_format_list report_json in let formats_by_report_kind = let costs_report_format_kind = - match report_json with - | Some _ -> - let file = Config.(results_dir ^/ Config.costs_report_json) in - [(Costs, mk_format Json file)] - | None -> - [] + let file = Config.(results_dir ^/ costs_report_json) in + [(Costs, mk_format Json file)] in - costs_report_format_kind @ [(Issues, issue_formats); (Summary, [])] + costs_report_format_kind @ [(Issues, issue_formats)] in init_files formats_by_report_kind ; - ( match Config.issues_tests with - | Some _ -> - pp_json_report_by_report_kind formats_by_report_kind Config.from_json_report - | None -> - pp_summary_and_issues formats_by_report_kind issue_formats ) ; - if Config.test_determinator && Config.process_clang_ast then - TestDeterminator.merge_test_determinator_results () ; + pp_summary_and_issues formats_by_report_kind issue_formats ; () diff --git a/infer/src/backend/InferPrint.mli b/infer/src/backend/InferPrint.mli index 1b21c9c9f..0fb96851c 100644 --- a/infer/src/backend/InferPrint.mli +++ b/infer/src/backend/InferPrint.mli @@ -7,9 +7,11 @@ open! IStd +val potential_exception_message : string + val loc_trace_to_jsonbug_record : Errlog.loc_trace_elem list -> Exceptions.severity -> Jsonbug_t.json_trace_item list val censored_reason : IssueType.t -> SourceFile.t -> string option -val main : report_json:string option -> unit +val main : report_json:string -> unit diff --git a/infer/src/backend/SpecsFiles.ml b/infer/src/backend/SpecsFiles.ml index 65c632520..70f2f1c04 100644 --- a/infer/src/backend/SpecsFiles.ml +++ b/infer/src/backend/SpecsFiles.ml @@ -6,6 +6,7 @@ *) open! IStd +module F = Format module L = Logging module CLOpt = CommandLineOption @@ -71,3 +72,9 @@ let delete pname = (try Unix.unlink filename with Unix.Unix_error _ -> ()) ; Ondemand.LocalCache.remove pname ; Summary.OnDisk.remove_from_cache pname + + +let pp_from_config fmt = + iter_from_config ~f:(fun summary -> + F.fprintf fmt "Procedure: %a@\n%a@." Procname.pp (Summary.get_proc_name summary) + Summary.pp_text summary ) diff --git a/infer/src/backend/SpecsFiles.mli b/infer/src/backend/SpecsFiles.mli index 8586f075f..eae8453ca 100644 --- a/infer/src/backend/SpecsFiles.mli +++ b/infer/src/backend/SpecsFiles.mli @@ -17,3 +17,5 @@ val iter_from_config : f:(Summary.t -> unit) -> unit val delete : Procname.t -> unit (** Delete the .specs file associated with a summary and remove the summary from the caches in Summary.ml and ondemand.ml *) + +val pp_from_config : Format.formatter -> unit diff --git a/infer/src/base/Config.ml b/infer/src/base/Config.ml index 983751876..e7dcb0c19 100644 --- a/infer/src/base/Config.ml +++ b/infer/src/base/Config.ml @@ -30,24 +30,6 @@ let ml_bucket_symbols = ; ("unknown_origin", `MLeak_unknown) ] -let issues_tests_fields_symbols = - [ ("bug_type", `Issue_field_bug_type) - ; ("bucket", `Issue_field_bucket) - ; ("qualifier", `Issue_field_qualifier) - ; ("severity", `Issue_field_severity) - ; ("line", `Issue_field_line) - ; ("column", `Issue_field_column) - ; ("procedure", `Issue_field_procedure) - ; ("procedure_start_line", `Issue_field_procedure_start_line) - ; ("file", `Issue_field_file) - ; ("bug_trace", `Issue_field_bug_trace) - ; ("key", `Issue_field_key) - ; ("hash", `Issue_field_hash) - ; ("line_offset", `Issue_field_line_offset) - ; ( "qualifier_contains_potential_exception_note" - , `Issue_field_qualifier_contains_potential_exception_note ) ] - - type os_type = Unix | Win32 | Cygwin type build_system = @@ -1437,15 +1419,8 @@ and iphoneos_target_sdk_version_path_regex = and issues_tests_fields = CLOpt.mk_symbol_seq ~long:"issues-tests-fields" ~in_help:InferCommand.[(Report, manual_generic)] - ~default: - [ `Issue_field_file - ; `Issue_field_procedure - ; `Issue_field_line_offset - ; `Issue_field_bug_type - ; `Issue_field_bucket - ; `Issue_field_severity - ; `Issue_field_bug_trace ] - ~symbols:issues_tests_fields_symbols ~eq:PolyVariantEqual.( = ) + ~default:IssuesTestField.[File; Procedure; LineOffset; BugType; Bucket; Severity; BugTrace] + ~symbols:IssuesTestField.all_symbols ~eq:IssuesTestField.equal "Fields to emit with $(b,--issues-tests)" diff --git a/infer/src/base/Config.mli b/infer/src/base/Config.mli index 7d88e8a0d..6d5fa811e 100644 --- a/infer/src/base/Config.mli +++ b/infer/src/base/Config.mli @@ -387,22 +387,7 @@ val is_checker_enabled : Checker.t -> bool val issues_tests : string option -val issues_tests_fields : - [ `Issue_field_bug_type - | `Issue_field_qualifier - | `Issue_field_severity - | `Issue_field_bucket - | `Issue_field_line - | `Issue_field_column - | `Issue_field_procedure - | `Issue_field_procedure_start_line - | `Issue_field_file - | `Issue_field_bug_trace - | `Issue_field_key - | `Issue_field_hash - | `Issue_field_line_offset - | `Issue_field_qualifier_contains_potential_exception_note ] - list +val issues_tests_fields : IssuesTestField.t list val iterations : int diff --git a/infer/src/base/IssuesTestField.ml b/infer/src/base/IssuesTestField.ml new file mode 100644 index 000000000..b3ebe8b85 --- /dev/null +++ b/infer/src/base/IssuesTestField.ml @@ -0,0 +1,41 @@ +(* + * 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 + +type t = + | BugType + | Qualifier + | Severity + | Bucket + | Line + | Column + | Procedure + | ProcedureStartLine + | File + | BugTrace + | Key + | Hash + | LineOffset + | QualifierContainsPotentialExceptionNote +[@@deriving equal] + +let all_symbols = + [ ("bug_type", BugType) + ; ("bucket", Bucket) + ; ("qualifier", Qualifier) + ; ("severity", Severity) + ; ("line", Line) + ; ("column", Column) + ; ("procedure", Procedure) + ; ("procedure_start_line", ProcedureStartLine) + ; ("file", File) + ; ("bug_trace", BugTrace) + ; ("key", Key) + ; ("hash", Hash) + ; ("line_offset", LineOffset) + ; ("qualifier_contains_potential_exception_note", QualifierContainsPotentialExceptionNote) ] diff --git a/infer/src/base/IssuesTestField.mli b/infer/src/base/IssuesTestField.mli new file mode 100644 index 000000000..7c9b4f1f1 --- /dev/null +++ b/infer/src/base/IssuesTestField.mli @@ -0,0 +1,27 @@ +(* + * 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 + +type t = + | BugType + | Qualifier + | Severity + | Bucket + | Line + | Column + | Procedure + | ProcedureStartLine + | File + | BugTrace + | Key + | Hash + | LineOffset + | QualifierContainsPotentialExceptionNote +[@@deriving equal] + +val all_symbols : (string * t) list diff --git a/infer/src/infer.ml b/infer/src/infer.ml index 83d1f0f51..bd2982de1 100644 --- a/infer/src/infer.ml +++ b/infer/src/infer.ml @@ -145,8 +145,13 @@ let () = run Driver.Analyze | Capture | Compile | Run -> run (Lazy.force Driver.mode_from_command_line) - | Report -> - InferPrint.main ~report_json:None + | Report -> ( + match Config.issues_tests with + | None -> + if not Config.quiet then L.result "%t" SpecsFiles.pp_from_config + | Some out_path -> + IssuesTest.write_from_json ~json_path:Config.from_json_report ~out_path + Config.issues_tests_fields ) | ReportDiff -> (* at least one report must be passed in input to compute differential *) ( match Config.(report_current, report_previous, costs_current, costs_previous) with diff --git a/infer/src/integration/Driver.ml b/infer/src/integration/Driver.ml index 3b6aa94e5..de179488d 100644 --- a/infer/src/integration/Driver.ml +++ b/infer/src/integration/Driver.ml @@ -321,7 +321,9 @@ let execute_analyze ~changed_files = let report ?(suppress_console = false) () = let report_json = Config.(results_dir ^/ report_json) in - InferPrint.main ~report_json:(Some report_json) ; + InferPrint.main ~report_json ; + if Config.(test_determinator && process_clang_ast) then + TestDeterminator.merge_test_determinator_results () ; (* Post-process the report according to the user config. By default, calls report.py to create a human-readable report. diff --git a/infer/src/integration/IssuesTest.ml b/infer/src/integration/IssuesTest.ml new file mode 100644 index 000000000..7aafa981b --- /dev/null +++ b/infer/src/integration/IssuesTest.ml @@ -0,0 +1,81 @@ +(* + * 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 F = Format + +let pp_custom_of_report fmt report fields = + let pp_custom_of_issue fmt (issue : Jsonbug_t.jsonbug) = + let open Jsonbug_t in + let comma_separator index = if index > 0 then ", " else "" in + let pp_trace fmt trace comma = + let pp_trace_elem fmt {description} = F.pp_print_string fmt description in + let trace_without_empty_descs = + List.filter ~f:(fun {description} -> not (String.is_empty description)) trace + in + F.fprintf fmt "%s[%a]" comma (Pp.comma_seq pp_trace_elem) trace_without_empty_descs + in + let pp_field index field = + match (field : IssuesTestField.t) with + | BugType -> + F.fprintf fmt "%s%s" (comma_separator index) issue.bug_type + | Bucket -> + let bucket = + match + String.lsplit2 issue.qualifier ~on:']' + |> Option.map ~f:fst + |> Option.bind ~f:(String.chop_prefix ~prefix:"[") + with + | Some bucket -> + bucket + | None -> + "no_bucket" + in + F.fprintf fmt "%s%s" (comma_separator index) bucket + | Qualifier -> + F.fprintf fmt "%s%s" (comma_separator index) issue.qualifier + | Severity -> + F.fprintf fmt "%s%s" (comma_separator index) issue.severity + | Line -> + F.fprintf fmt "%s%d" (comma_separator index) issue.line + | Column -> + F.fprintf fmt "%s%d" (comma_separator index) issue.column + | Procedure -> + F.fprintf fmt "%s%s" (comma_separator index) issue.procedure + | ProcedureStartLine -> + F.fprintf fmt "%s%d" (comma_separator index) issue.procedure_start_line + | File -> + F.fprintf fmt "%s%s" (comma_separator index) issue.file + | BugTrace -> + pp_trace fmt issue.bug_trace (comma_separator index) + | Key -> + F.fprintf fmt "%s%s" (comma_separator index) (Caml.Digest.to_hex issue.key) + | Hash -> + F.fprintf fmt "%s%s" (comma_separator index) (Caml.Digest.to_hex issue.hash) + | LineOffset -> + F.fprintf fmt "%s%d" (comma_separator index) (issue.line - issue.procedure_start_line) + | QualifierContainsPotentialExceptionNote -> + F.pp_print_bool fmt + (String.is_substring issue.qualifier ~substring:InferPrint.potential_exception_message) + in + List.iteri ~f:pp_field fields ; F.fprintf fmt "@." + in + List.iter ~f:(pp_custom_of_issue fmt) report + + +let tests_jsonbug_compare (bug1 : Jsonbug_t.jsonbug) (bug2 : Jsonbug_t.jsonbug) = + let open Jsonbug_t in + [%compare: string * string * int * string * Caml.Digest.t] + (bug1.file, bug1.procedure, bug1.line - bug1.procedure_start_line, bug1.bug_type, bug1.hash) + (bug2.file, bug2.procedure, bug2.line - bug2.procedure_start_line, bug2.bug_type, bug2.hash) + + +let write_from_json ~json_path ~out_path issues_tests_fields = + Utils.with_file_out out_path ~f:(fun outf -> + let report = Atdgen_runtime.Util.Json.from_file Jsonbug_j.read_report json_path in + let sorted_report = List.sort ~compare:tests_jsonbug_compare report in + pp_custom_of_report (F.formatter_of_out_channel outf) sorted_report issues_tests_fields ) diff --git a/infer/src/integration/IssuesTest.mli b/infer/src/integration/IssuesTest.mli new file mode 100644 index 000000000..0b546c0c4 --- /dev/null +++ b/infer/src/integration/IssuesTest.mli @@ -0,0 +1,10 @@ +(* + * 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 + +val write_from_json : json_path:string -> out_path:string -> IssuesTestField.t list -> unit