diff --git a/infer/src/cost/cost.ml b/infer/src/cost/cost.ml index 7f7535343..4ce1b1df6 100644 --- a/infer/src/cost/cost.ml +++ b/infer/src/cost/cost.ml @@ -52,10 +52,6 @@ module InstrBasicCostWithReason = struct List.exists allocation_functions ~f:(fun f -> Procname.equal callee_pname f) - let is_autorelease_function tenv callee_pname fun_arg_list = - CostAutoreleaseModels.Call.dispatch tenv callee_pname fun_arg_list |> Option.is_some - - (** The methods whose name start with one of the prefixes return an object that is owned by the caller. Therefore ARC will not add any objects they return into an autorelease pool. *) let return_object_owned_by_caller = @@ -74,7 +70,7 @@ module InstrBasicCostWithReason = struct let get_instr_cost_record tenv extras cfg instr_node instr = match instr with | Sil.Call (((_, ret_typ) as ret), Exp.Const (Const.Cfun callee_pname), params, location, _) - when Config.inclusive_cost -> + when Config.inclusive_cost -> ( let { inferbo_invariant_map ; integer_type_widths ; inferbo_get_summary @@ -86,63 +82,66 @@ module InstrBasicCostWithReason = struct List.map params ~f:(fun (exp, typ) -> ProcnameDispatcher.Call.FuncArg.{exp; typ; arg_payload= ()} ) in + let inferbo_mem_opt = + BufferOverrunAnalysis.extract_pre (InstrCFG.Node.id instr_node) inferbo_invariant_map + in + let model_env = + lazy + (let node_hash = InstrCFG.Node.hash instr_node in + BufferOverrunUtils.ModelEnv.mk_model_env callee_pname ~node_hash location tenv + integer_type_widths inferbo_get_summary ) + in let cost = - match - BufferOverrunAnalysis.extract_pre (InstrCFG.Node.id instr_node) inferbo_invariant_map - with + match inferbo_mem_opt with | None -> CostDomain.unit_cost_atomic_operation | Some inferbo_mem -> ( - let loc = InstrCFG.Node.loc instr_node in - match CostModels.Call.dispatch tenv callee_pname fun_arg_list with - | Some model -> - let node_hash = InstrCFG.Node.hash instr_node in - let model_env = - BufferOverrunUtils.ModelEnv.mk_model_env callee_pname ~node_hash loc tenv - integer_type_widths inferbo_get_summary + match CostModels.Call.dispatch tenv callee_pname fun_arg_list with + | Some model -> + CostDomain.of_operation_cost (model (Lazy.force model_env) ~ret inferbo_mem) + | None -> ( + match (get_summary callee_pname, get_formals callee_pname) with + | Some {CostDomain.post= callee_cost_record}, Some callee_formals -> ( + let instantiated_cost = + CostDomain.map callee_cost_record ~f:(fun callee_cost -> + instantiate_cost integer_type_widths ~inferbo_caller_mem:inferbo_mem + ~callee_pname ~callee_formals ~params ~callee_cost ~loc:location ) in - CostDomain.of_operation_cost (model model_env ~ret inferbo_mem) - | None -> ( - match (get_summary callee_pname, get_formals callee_pname) with - | Some {CostDomain.post= callee_cost_record}, Some callee_formals -> ( - let instantiated_cost = - CostDomain.map callee_cost_record ~f:(fun callee_cost -> - instantiate_cost integer_type_widths ~inferbo_caller_mem:inferbo_mem - ~callee_pname ~callee_formals ~params ~callee_cost ~loc ) - in - match CostDomain.get_operation_cost callee_cost_record with - | {cost; top_pname_opt= None} when BasicCost.is_top cost -> - CostDomain.add_top_pname_opt CostKind.OperationCost instantiated_cost - (Some callee_pname) - | _ -> - instantiated_cost ) - | _, _ -> - ScubaLogging.cost_log_message ~label:"unmodeled_function_cost_analysis" - ~message: - (F.asprintf "Unmodeled Function[Cost Analysis] : %a" Procname.pp - callee_pname) ; - CostDomain.unit_cost_atomic_operation ) ) + match CostDomain.get_operation_cost callee_cost_record with + | {cost; top_pname_opt= None} when BasicCost.is_top cost -> + CostDomain.add_top_pname_opt CostKind.OperationCost instantiated_cost + (Some callee_pname) + | _ -> + instantiated_cost ) + | _, _ -> + ScubaLogging.cost_log_message ~label:"unmodeled_function_cost_analysis" + ~message: + (F.asprintf "Unmodeled Function[Cost Analysis] : %a" Procname.pp callee_pname) ; + CostDomain.unit_cost_atomic_operation ) ) in let cost = if is_allocation_function callee_pname then CostDomain.plus CostDomain.unit_cost_allocation cost else cost in - if is_autorelease_function tenv callee_pname fun_arg_list then - let autoreleasepool_trace = - Bounds.BoundTrace.of_modeled_function (Procname.to_string callee_pname) location - in - CostDomain.plus cost (CostDomain.unit_cost_autoreleasepool_size ~autoreleasepool_trace) - else if - is_objc_call_from_no_arc_to_arc extras cfg callee_pname - && Typ.is_pointer_to_objc_class ret_typ - && not (return_object_owned_by_caller callee_pname) - then - let autoreleasepool_trace = - Bounds.BoundTrace.of_arc_from_non_arc (Procname.to_string callee_pname) location - in - CostDomain.plus cost (CostDomain.unit_cost_autoreleasepool_size ~autoreleasepool_trace) - else cost + match + (inferbo_mem_opt, CostAutoreleaseModels.Call.dispatch tenv callee_pname fun_arg_list) + with + | Some inferbo_mem, Some model -> + let autoreleasepool_size = model (Lazy.force model_env) ~ret inferbo_mem in + CostDomain.plus_autoreleasepool_size autoreleasepool_size cost + | _, _ -> + if + is_objc_call_from_no_arc_to_arc extras cfg callee_pname + && Typ.is_pointer_to_objc_class ret_typ + && not (return_object_owned_by_caller callee_pname) + then + let autoreleasepool_trace = + Bounds.BoundTrace.of_arc_from_non_arc (Procname.to_string callee_pname) location + in + CostDomain.plus cost + (CostDomain.unit_cost_autoreleasepool_size ~autoreleasepool_trace) + else cost ) | Sil.Call (_, Exp.Const (Const.Cfun _), _, _, _) -> CostDomain.zero_record | Sil.Load {id= lhs_id} when Ident.is_none lhs_id -> diff --git a/infer/src/cost/costAutoreleaseModels.ml b/infer/src/cost/costAutoreleaseModels.ml index b92ba3bb1..5becdb35f 100644 --- a/infer/src/cost/costAutoreleaseModels.ml +++ b/infer/src/cost/costAutoreleaseModels.ml @@ -6,19 +6,28 @@ *) open! IStd +module BasicCost = CostDomain.BasicCost +open BufferOverrunUtils.ModelEnv + +let unit_cost {pname; location} ~ret:_ _inferbo_mem = + let autoreleasepool_trace = + Bounds.BoundTrace.of_modeled_function (Procname.to_string pname) location + in + BasicCost.one ~autoreleasepool_trace () + module Call = struct - let dispatch : (Tenv.t, unit, unit) ProcnameDispatcher.Call.dispatcher = + let dispatch : (Tenv.t, CostUtils.model, unit) ProcnameDispatcher.Call.dispatcher = let open ProcnameDispatcher.Call in make_dispatcher - [ +PatternMatch.ObjectiveC.implements "NSObject" &:: "autorelease" &--> () - ; -"CFAutorelease" &--> () + [ +PatternMatch.ObjectiveC.implements "NSObject" &:: "autorelease" &--> unit_cost + ; -"CFAutorelease" &--> unit_cost ; +PatternMatch.ObjectiveC.implements "NSKeyedUnarchiver" - &:: "initForReadingFromData:error:" &--> () + &:: "initForReadingFromData:error:" &--> unit_cost ; +PatternMatch.ObjectiveC.implements "NSKeyedUnarchiver" - &:: "initForReadingWithData:" &--> () + &:: "initForReadingWithData:" &--> unit_cost ; +PatternMatch.ObjectiveC.implements "NSKeyedUnarchiver" - &:: "unarchivedObjectOfClass:fromData:error:" &--> () + &:: "unarchivedObjectOfClass:fromData:error:" &--> unit_cost ; +PatternMatch.ObjectiveC.implements "NSKeyedUnarchiver" - &:: "unarchivedObjectOfClasses:fromData:error:" &--> () ] + &:: "unarchivedObjectOfClasses:fromData:error:" &--> unit_cost ] end diff --git a/infer/src/cost/costAutoreleaseModels.mli b/infer/src/cost/costAutoreleaseModels.mli index ab4751a64..984656839 100644 --- a/infer/src/cost/costAutoreleaseModels.mli +++ b/infer/src/cost/costAutoreleaseModels.mli @@ -8,5 +8,5 @@ open! IStd module Call : sig - val dispatch : (Tenv.t, unit, unit) ProcnameDispatcher.Call.dispatcher + val dispatch : (Tenv.t, CostUtils.model, unit) ProcnameDispatcher.Call.dispatcher end diff --git a/infer/src/cost/costDomain.ml b/infer/src/cost/costDomain.ml index c2bad9bed..971fa7ebf 100644 --- a/infer/src/cost/costDomain.ml +++ b/infer/src/cost/costDomain.ml @@ -120,6 +120,16 @@ let plus cost_record1 cost_record2 = cost_record1 cost_record2 +let plus_autoreleasepool_size autoreleasepool_size cost = + VariantCostMap.update AutoreleasepoolSize + (function + | None -> + Some {BasicCostWithReason.cost= autoreleasepool_size; top_pname_opt= None} + | Some prev -> + Some {prev with cost= BasicCost.plus prev.cost autoreleasepool_size} ) + cost + + let unit_cost_atomic_operation = VariantCostMap.increment CostKind.OperationCost zero_record let unit_cost_allocation = VariantCostMap.increment CostKind.AllocationCost zero_record diff --git a/infer/src/cost/costDomain.mli b/infer/src/cost/costDomain.mli index 6edc9123d..dc9f1ab67 100644 --- a/infer/src/cost/costDomain.mli +++ b/infer/src/cost/costDomain.mli @@ -75,6 +75,9 @@ val mult_by : t -> nb_exec:BasicCost.t -> t val plus : t -> t -> t (** Union of two maps where common costs are added together *) +val plus_autoreleasepool_size : BasicCost.t -> t -> t +(** Add an autoreleasepool size to the cost map *) + val unit_cost_atomic_operation : t (** Map representing cost record \{OperationCost:1; AllocationCost:0; AutoreleasepoolSize:0\} *)