diff --git a/infer/src/backend/Differential.ml b/infer/src/backend/Differential.ml index 81cd406e2..b05fb0ee5 100644 --- a/infer/src/backend/Differential.ml +++ b/infer/src/backend/Differential.ml @@ -53,7 +53,84 @@ let dedup (issues: Jsonbug_t.jsonbug list) = |> snd |> sort_by_location -type t = {introduced: Jsonbug_t.report; fixed: Jsonbug_t.report; preexisting: Jsonbug_t.report} +module CostsSummary : sig + val to_json : + current_report:Jsonbug_t.report -> previous_report:Jsonbug_t.report -> Yojson.Basic.json +end = struct + type 'a count = {top: 'a; zero: 'a; degrees: 'a Int.Map.t} + + let init = {top= 0; zero= 0; degrees= Int.Map.empty} + + type previous_current = {previous: int; current: int} + + let zero_token_str = + Format.asprintf "%a" Itv.NonNegativePolynomial.pp Itv.NonNegativePolynomial.zero + + + let top_token_str = + Format.asprintf "%a" Itv.NonNegativePolynomial.pp Itv.NonNegativePolynomial.top + + + let count report = + let count_aux t (e: Jsonbug_t.extra) = + match e with + | {cost_polynomial= Some cp} when String.equal cp zero_token_str -> + {t with zero= t.zero + 1} + | {cost_polynomial= Some cp; cost_degree= None} when String.equal cp top_token_str -> + {t with top= t.top + 1} + | {cost_degree= Some v} -> + let degrees = Int.Map.update t.degrees v ~f:(function None -> 1 | Some x -> x + 1) in + {t with degrees} + | {cost_degree= None} -> + t + in + List.fold ~init + ~f:(fun acc v -> match v.Jsonbug_t.extras with None -> acc | Some v -> count_aux acc v) + report + + + let pair_counts ~current_counts ~previous_counts = + let compute_degrees current previous = + let merge_aux ~key:_ v = + match v with + | `Both (current, previous) -> + Some {current; previous} + | `Left current -> + Some {current; previous= 0} + | `Right previous -> + Some {current= 0; previous} + in + Int.Map.merge ~f:merge_aux current previous + in + { top= {current= current_counts.top; previous= previous_counts.top} + ; zero= {current= current_counts.zero; previous= previous_counts.zero} + ; degrees= compute_degrees current_counts.degrees previous_counts.degrees } + + + let to_json ~current_report ~previous_report = + let current_counts = count current_report in + let previous_counts = count previous_report in + let paired_counts = pair_counts ~current_counts ~previous_counts in + let json_degrees = + Int.Map.to_alist ~key_order:`Increasing paired_counts.degrees + |> List.map ~f:(fun (key, {current; previous}) -> + `Assoc [("degree", `Int key); ("current", `Int current); ("previous", `Int previous)] + ) + in + let create_assoc current previous = + `Assoc [("current", `Int current); ("previous", `Int previous)] + in + `Assoc + [ ("top", create_assoc paired_counts.top.current paired_counts.top.previous) + ; ("zero", create_assoc paired_counts.zero.current paired_counts.zero.previous) + ; ("degrees", `List json_degrees) ] +end + +type t = + { introduced: Jsonbug_t.report + ; fixed: Jsonbug_t.report + ; preexisting: Jsonbug_t.report + ; costs_summary: Yojson.Basic.json } (** Set operations should keep duplicated issues with identical hashes *) let of_reports ~(current_report: Jsonbug_t.report) ~(previous_report: Jsonbug_t.report) : t = @@ -74,12 +151,15 @@ let of_reports ~(current_report: Jsonbug_t.report) ~(previous_report: Jsonbug_t. let introduced, preexisting, fixed = Map.fold2 (to_map current_report) (to_map previous_report) ~f:fold_aux ~init:([], [], []) in - {introduced= dedup introduced; fixed= dedup fixed; preexisting= dedup preexisting} + let costs_summary = CostsSummary.to_json ~current_report ~previous_report in + {introduced= dedup introduced; fixed= dedup fixed; preexisting= dedup preexisting; costs_summary} -let to_files {introduced; fixed; preexisting} destdir = +let to_files {introduced; fixed; preexisting; costs_summary} destdir = Out_channel.write_all (destdir ^/ "introduced.json") ~data:(Jsonbug_j.string_of_report introduced) ; Out_channel.write_all (destdir ^/ "fixed.json") ~data:(Jsonbug_j.string_of_report fixed) ; Out_channel.write_all (destdir ^/ "preexisting.json") - ~data:(Jsonbug_j.string_of_report preexisting) + ~data:(Jsonbug_j.string_of_report preexisting) ; + Out_channel.write_all (destdir ^/ "costs_summary.json") + ~data:(Yojson.Basic.to_string costs_summary) diff --git a/infer/src/backend/Differential.mli b/infer/src/backend/Differential.mli index f0060cb21..66d1cc480 100644 --- a/infer/src/backend/Differential.mli +++ b/infer/src/backend/Differential.mli @@ -7,7 +7,11 @@ open! IStd -type t = {introduced: Jsonbug_t.report; fixed: Jsonbug_t.report; preexisting: Jsonbug_t.report} +type t = + { introduced: Jsonbug_t.report + ; fixed: Jsonbug_t.report + ; preexisting: Jsonbug_t.report + ; costs_summary: Yojson.Basic.json } val of_reports : current_report:Jsonbug_t.report -> previous_report:Jsonbug_t.report -> t diff --git a/infer/src/backend/DifferentialFilters.ml b/infer/src/backend/DifferentialFilters.ml index 70a2fa145..b2443fb24 100644 --- a/infer/src/backend/DifferentialFilters.ml +++ b/infer/src/backend/DifferentialFilters.ml @@ -145,7 +145,7 @@ let skip_duplicated_types_on_filenames renamings (diff: Differential.t) : Differ , list_map_fst preexisting' @ diff.preexisting , list_map_fst fixed_normalized' ) in - {introduced; fixed; preexisting} + {introduced; fixed; preexisting; costs_summary= diff.costs_summary} let java_anon_class_pattern = Str.regexp "\\$[0-9]+" @@ -210,7 +210,10 @@ let skip_anonymous_class_renamings (diff: Differential.t) : Differential.t = let introduced, preexisting, fixed = relative_complements ~compare ~pred diff.introduced diff.fixed in - {introduced; fixed; preexisting= preexisting @ diff.preexisting} + { introduced + ; fixed + ; preexisting= preexisting @ diff.preexisting + ; costs_summary= diff.costs_summary } (* Strip issues whose paths are not among those we're interested in *) @@ -248,7 +251,8 @@ let do_filter (diff: Differential.t) (renamings: FileRenamings.t) ~(skip_duplica in { introduced= apply_paths_filter_if_needed `Introduced diff'.introduced ; fixed= apply_paths_filter_if_needed `Fixed diff'.fixed - ; preexisting= apply_paths_filter_if_needed `Preexisting diff'.preexisting } + ; preexisting= apply_paths_filter_if_needed `Preexisting diff'.preexisting + ; costs_summary= diff'.costs_summary } module VISIBLE_FOR_TESTING_DO_NOT_USE_DIRECTLY = struct