diff --git a/infer/src/cost/cost.ml b/infer/src/cost/cost.ml index 6dd780c8e..a24ccbc82 100644 --- a/infer/src/cost/cost.ml +++ b/infer/src/cost/cost.ml @@ -9,6 +9,7 @@ open! IStd module F = Format module L = Logging module BasicCost = CostDomain.BasicCost +module BasicCostWithReason = CostDomain.BasicCostWithReason (* CFG modules used in several other modules *) module InstrCFG = ProcCfg.NormalOneInstrPerNode @@ -29,10 +30,10 @@ let instantiate_cost integer_type_widths ~inferbo_caller_mem ~callee_pname ~call BufferOverrunSemantics.mk_eval_sym_cost integer_type_widths callee_formals params inferbo_caller_mem in - BasicCost.subst callee_pname loc callee_cost eval_sym + BasicCostWithReason.subst callee_pname loc callee_cost eval_sym -module InstrBasicCost = struct +module InstrBasicCostWithReason = struct (* Compute the cost for an instruction. For example for basic operation we set it to 1 and for function call we take it from the spec of the function. @@ -95,8 +96,8 @@ module InstrBasicCost = struct instantiate_cost integer_type_widths ~inferbo_caller_mem:inferbo_mem ~callee_pname ~callee_formals ~params ~callee_cost ~loc ) in - if BasicCost.is_top (CostDomain.get_operation_cost instantiated_cost) then - CostDomain.unit_cost_atomic_operation + if BasicCostWithReason.is_top (CostDomain.get_operation_cost instantiated_cost) + then CostDomain.unit_cost_atomic_operation else instantiated_cost | `NotFound -> ScubaLogging.log_message ~label:"unmodeled_function_cost_analysis" @@ -146,14 +147,14 @@ module InstrBasicCost = struct (Sil.pp_instr ~print_types:false Pp.text) instr in - if BasicCost.is_top operation_cost then log_msg "top" - else if BasicCost.is_unreachable operation_cost then log_msg "unreachable" ; + if BasicCostWithReason.is_top operation_cost then log_msg "top" + else if BasicCostWithReason.is_unreachable operation_cost then log_msg "unreachable" ; cost end let compute_errlog_extras cost = - { Jsonbug_t.cost_polynomial= Some (Format.asprintf "%a" BasicCost.pp_hum cost) - ; cost_degree= BasicCost.degree cost |> Option.map ~f:Polynomials.Degree.encode_to_int + { Jsonbug_t.cost_polynomial= Some (Format.asprintf "%a" BasicCostWithReason.pp_hum cost) + ; cost_degree= BasicCostWithReason.degree cost |> Option.map ~f:Polynomials.Degree.encode_to_int ; nullsafe_extra= None } @@ -164,7 +165,9 @@ module WorstCaseCost = struct we report Top cost only at the top level per function. *) let exec_node tenv extras instr_node = let {get_node_nb_exec} = extras in - let instr_cost_record = InstrBasicCost.get_instr_node_cost_record tenv extras instr_node in + let instr_cost_record = + InstrBasicCostWithReason.get_instr_node_cost_record tenv extras instr_node + in let node = InstrCFG.Node.underlying_node instr_node in let nb_exec = get_node_nb_exec node in if BasicCost.is_top nb_exec then @@ -191,11 +194,11 @@ module Check = struct let report issue suffix = let message = F.asprintf "%s of the function %a %s" name Procname.pp pname suffix in Reporting.log_issue proc_desc err_log ~loc - ~ltr:(BasicCost.polynomial_traces cost) + ~ltr:(BasicCostWithReason.polynomial_traces cost) ~extras:(compute_errlog_extras cost) Cost issue message in - if BasicCost.is_top cost then report infinite_issue "cannot be computed" - else if BasicCost.is_unreachable cost then + if BasicCostWithReason.is_top cost then report infinite_issue "cannot be computed" + else if BasicCostWithReason.is_unreachable cost then report unreachable_issue "cannot be computed since the program's exit state is never reachable" diff --git a/infer/src/cost/cost.mli b/infer/src/cost/cost.mli index b7994b02e..f4e365f52 100644 --- a/infer/src/cost/cost.mli +++ b/infer/src/cost/cost.mli @@ -20,8 +20,8 @@ val instantiate_cost : -> callee_pname:Procname.t -> callee_formals:(Pvar.t * Typ.t) list -> params:(Exp.t * Typ.t) list - -> callee_cost:CostDomain.BasicCost.t + -> callee_cost:CostDomain.BasicCostWithReason.t -> loc:Location.t - -> CostDomain.BasicCost.t + -> CostDomain.BasicCostWithReason.t val is_report_suppressed : Procname.t -> bool diff --git a/infer/src/cost/costDomain.ml b/infer/src/cost/costDomain.ml index 4aa56bbe8..6c8e9efdc 100644 --- a/infer/src/cost/costDomain.ml +++ b/infer/src/cost/costDomain.ml @@ -16,24 +16,58 @@ module BasicCost = struct let version = 7 end +module BasicCostWithReason = struct + type t = {cost: BasicCost.t; top_pname_opt: Procname.t option} + + let is_top {cost} = BasicCost.is_top cost + + let is_unreachable {cost} = BasicCost.is_unreachable cost + + let zero = {cost= BasicCost.zero; top_pname_opt= None} + + let one = {cost= BasicCost.one; top_pname_opt= None} + + let subst callee_pname location record eval_sym = + {record with cost= BasicCost.subst callee_pname location record.cost eval_sym} + + + let plus record1 record2 = + { cost= BasicCost.plus record1.cost record2.cost + ; top_pname_opt= Option.first_some record1.top_pname_opt record2.top_pname_opt } + + + let degree {cost} = BasicCost.degree cost + + let mult_unreachable cost record = {record with cost= BasicCost.mult_unreachable cost record.cost} + + let polynomial_traces {cost} = BasicCost.polynomial_traces cost + + let pp format {cost} = BasicCost.pp format cost + + let pp_hum = pp +end + (** Module to simulate a record [{OperationCost:BasicCost.t; AllocationCost: BasicCost.t; IOCost:BasicCost.t}] with a map [{OperationCost, AllocationCost, IOCost} -> BasicCost.t] *) module VariantCostMap = struct - include PrettyPrintable.PPMonoMapOfPPMap (CostIssues.CostKindMap) (BasicCost) + include PrettyPrintable.PPMonoMapOfPPMap (CostIssues.CostKindMap) (BasicCostWithReason) let[@warning "-32"] add _ = Logging.die InternalError "Don't call me" - let get kind record = find_opt kind record |> Option.value ~default:BasicCost.zero + let get kind record = find_opt kind record |> Option.value ~default:BasicCostWithReason.zero let increase_by kind cost_to_add record = update kind (function - | None -> Some cost_to_add | Some existing -> Some (BasicCost.plus cost_to_add existing) ) + | None -> + Some cost_to_add + | Some existing -> + Some (BasicCostWithReason.plus cost_to_add existing) ) record - let increment kind record = increase_by kind BasicCost.one record + let increment kind record = increase_by kind BasicCostWithReason.one record end type t = VariantCostMap.t @@ -51,11 +85,11 @@ let map ~f cost_record = VariantCostMap.map f cost_record let zero_record = VariantCostMap.empty (** If nb_exec is unreachable, we map to unreachable, not 0 *) -let mult_by cost_record ~nb_exec = map cost_record ~f:(BasicCost.mult_unreachable nb_exec) +let mult_by cost_record ~nb_exec = map cost_record ~f:(BasicCostWithReason.mult_unreachable nb_exec) let plus cost_record1 cost_record2 = VariantCostMap.union - (fun _kind cost1 cost2 -> Some (BasicCost.plus cost1 cost2)) + (fun _kind cost1 cost2 -> Some (BasicCostWithReason.plus cost1 cost2)) cost_record1 cost_record2 @@ -64,4 +98,6 @@ let unit_cost_atomic_operation = VariantCostMap.increment CostKind.OperationCost let unit_cost_allocation = VariantCostMap.increment CostKind.AllocationCost zero_record let of_operation_cost operation_cost = - VariantCostMap.increase_by CostKind.OperationCost operation_cost zero_record + VariantCostMap.increase_by CostKind.OperationCost + {cost= operation_cost; top_pname_opt= None} + zero_record diff --git a/infer/src/cost/costDomain.mli b/infer/src/cost/costDomain.mli index 996d84ab5..f0b7658b8 100644 --- a/infer/src/cost/costDomain.mli +++ b/infer/src/cost/costDomain.mli @@ -17,8 +17,35 @@ module BasicCost : sig (** version used to consistently compare at infer-reportdiff phase *) end +module BasicCostWithReason : sig + (** This is for [Call] instruction. Most top cost function is caused by calling top-costed + function. The extension aims to find the root callee with top cost. + + If the callee top cost and thus the caller has top cost, then + + 1. if for callee, [top_pname_opt] is [None], then callee is itself top cost, so for the + caller, we record [top_pname_opt = callee] ; + + 2. if for callee, [top_pname_opt] is [f], then we know that callee calls [f], which is top + cost, so for the caller, we record [top_pname_opt = f] *) + + type t = {cost: BasicCost.t; top_pname_opt: Procname.t option} + + val is_top : t -> bool + + val is_unreachable : t -> bool + + val subst : Procname.t -> Location.t -> t -> Bounds.Bound.eval_sym -> t + + val degree : t -> Polynomials.Degree.t option + + val polynomial_traces : t -> Errlog.loc_trace + + val pp_hum : Format.formatter -> t -> unit +end + module VariantCostMap : sig - type t = BasicCost.t CostIssues.CostKindMap.t + type t = BasicCostWithReason.t CostIssues.CostKindMap.t val pp : F.formatter -> t -> unit end @@ -29,11 +56,11 @@ type summary = {post: t; is_on_ui_thread: bool} val pp_summary : F.formatter -> summary -> unit -val get_cost_kind : CostKind.t -> t -> BasicCost.t +val get_cost_kind : CostKind.t -> t -> BasicCostWithReason.t -val get_operation_cost : t -> BasicCost.t +val get_operation_cost : t -> BasicCostWithReason.t -val map : f:(BasicCost.t -> BasicCost.t) -> t -> t +val map : f:(BasicCostWithReason.t -> BasicCostWithReason.t) -> t -> t val zero_record : t (** Map representing cost record \{OperationCost:0; AllocationCost:0; IOCost:0\} *) diff --git a/infer/src/cost/hoisting.ml b/infer/src/cost/hoisting.ml index 181a99848..ee9cbf8b0 100644 --- a/infer/src/cost/hoisting.ml +++ b/infer/src/cost/hoisting.ml @@ -111,10 +111,11 @@ let get_cost_if_expensive tenv integer_type_widths get_callee_cost_summary_and_f match get_callee_cost_summary_and_formals pname with | Some (CostDomain.{post= cost_record}, callee_formals) -> let callee_cost = CostDomain.get_operation_cost cost_record in - if CostDomain.BasicCost.is_symbolic callee_cost then + if CostDomain.BasicCost.is_symbolic callee_cost.cost then Some (Cost.instantiate_cost integer_type_widths ~inferbo_caller_mem:inferbo_mem ~callee_pname:pname ~callee_formals ~params ~callee_cost ~loc) + .cost else None | None -> let fun_arg_list = diff --git a/infer/src/integration/JsonReports.ml b/infer/src/integration/JsonReports.ml index 615a41752..b9bba6be1 100644 --- a/infer/src/integration/JsonReports.ml +++ b/infer/src/integration/JsonReports.ml @@ -268,7 +268,7 @@ module JsonCostsPrinter = MakeJsonListPrinter (struct ; procedure_name= Procname.get_method proc_name ; procedure_id= procedure_id_of_procname proc_name ; is_on_ui_thread - ; exec_cost= cost_info (CostDomain.get_cost_kind CostKind.OperationCost post) } + ; exec_cost= cost_info (CostDomain.get_cost_kind CostKind.OperationCost post).cost } in Some (Jsonbug_j.string_of_cost_item cost_item) | _ ->