[cost] add model for iterating over collections via block

Summary:
Namely `enumerateObjectsUsingBlock` which takes a collection and a block as an arg and the iterates over the collection and applies the block to each element of the collection.

This is a common way to iterate over Objc collections, so let's add a model for it.

Reviewed By: skcho

Differential Revision: D24604590

fbshipit-source-id: 1ceeb4b40
master
Ezgi Çiçek 4 years ago committed by Facebook GitHub Bot
parent 15c1f816dc
commit a09ea5ea9c

@ -306,6 +306,11 @@ module ObjectiveC = struct
protocol_exists tenv is_protocol (Typ.Name.Objc.from_string typename)
let implements_collection =
let coll = ["NSArray"; "NSDictionary"; "NSOrderedSet"; "NSSet"] in
fun tenv typ_str -> List.exists ~f:(fun obj_class -> implements obj_class tenv typ_str) coll
let is_core_graphics_create_or_copy _ procname =
String.is_prefix ~prefix:"CG" procname
&& ( String.is_substring ~substring:"Create" procname

@ -194,6 +194,8 @@ module ObjectiveC : sig
val conforms_to : protocol:string -> Tenv.t -> string -> bool
(** Check whether class conforms to a given ObjC protocol *)
val implements_collection : Tenv.t -> string -> bool
val is_core_graphics_create_or_copy : Tenv.t -> string -> bool
val is_core_foundation_create_or_copy : Tenv.t -> string -> bool

@ -1524,17 +1524,12 @@ module NSString = struct
{exec; check= no_check}
end
let is_objc_collection =
let coll = ["NSArray"; "NSDictionary"; "NSOrderedSet"; "NSSet"] in
fun tenv typ_str ->
List.exists ~f:(fun obj_class -> PatternMatch.ObjectiveC.implements obj_class tenv typ_str) coll
let objc_malloc exp =
let can_be_zero = true in
let exec ({tenv} as model) ~ret mem =
match exp with
| Exp.Sizeof {typ} when is_objc_collection tenv (Typ.to_string typ) ->
| Exp.Sizeof {typ} when PatternMatch.ObjectiveC.implements_collection tenv (Typ.to_string typ)
->
NSCollection.new_collection.exec model ~ret mem
| Exp.Sizeof {typ} when PatternMatch.ObjectiveC.implements "NSString" tenv (Typ.to_string typ)
->
@ -1700,8 +1695,10 @@ module Call = struct
; +PatternMatch.ObjectiveC.implements "NSArray"
&:: "initWithArray:copyItems:" <>$ capt_var_exn $+ capt_exp $+ any_arg
$--> NSCollection.copy
; +is_objc_collection &:: "count" <>$ capt_exp $!--> NSCollection.size
; +is_objc_collection &:: "objectEnumerator" <>$ capt_exp $--> NSCollection.iterator
; +PatternMatch.ObjectiveC.implements_collection
&:: "count" <>$ capt_exp $!--> NSCollection.size
; +PatternMatch.ObjectiveC.implements_collection
&:: "objectEnumerator" <>$ capt_exp $--> NSCollection.iterator
; +PatternMatch.ObjectiveC.conforms_to ~protocol:"NSFastEnumeration"
&:: "objectEnumerator" <>$ capt_exp $--> NSCollection.iterator
; +PatternMatch.ObjectiveC.implements "NSArray"

@ -62,10 +62,12 @@ module InstrBasicCostWithReason = struct
&& Procdesc.is_objc_arc_on callee_pdesc )
let dispatch_operation tenv callee_pname callee_cost_opt fun_arg_list model_env ret inferbo_mem =
let dispatch_operation tenv callee_pname callee_cost_opt fun_arg_list get_summary model_env ret
inferbo_mem =
match CostModels.Call.dispatch tenv callee_pname fun_arg_list with
| Some model ->
BasicCostWithReason.of_basic_cost (model (Lazy.force model_env) ~ret inferbo_mem)
BasicCostWithReason.of_basic_cost
(model get_summary (Lazy.force model_env) ~ret inferbo_mem)
| None -> (
match callee_cost_opt with
| Some callee_cost ->
@ -181,8 +183,8 @@ module InstrBasicCostWithReason = struct
let callee_cost_opt = get_callee_cost_opt kind inferbo_mem in
match kind with
| OperationCost ->
dispatch_operation tenv callee_pname callee_cost_opt fun_arg_list model_env ret
inferbo_mem
dispatch_operation tenv callee_pname callee_cost_opt fun_arg_list
extras.get_summary model_env ret inferbo_mem
| AllocationCost ->
dispatch_allocation tenv callee_pname callee_cost_opt
| AutoreleasepoolSize ->

@ -30,7 +30,7 @@ module NSArray = struct
let length =
CostModels.BoundsOfNSCollection.linear_length
~of_function:(Procname.to_simplified_string pname)
array model_env ~ret inferbo_mem
array get_summary model_env ~ret inferbo_mem
in
BasicCost.mult_loop ~iter:length ~body:callee_cost
| None ->

@ -7,11 +7,13 @@
open! IStd
module BasicCost = CostDomain.BasicCost
module BasicCostWithReason = CostDomain.BasicCostWithReason
open BufferOverrunUtils.ModelEnv
let unit_cost_model _model_env ~ret:_ _inferbo_mem = BasicCost.one ()
let unit_cost_model _get_summary _model_env ~ret:_ _inferbo_mem = BasicCost.one ()
let cost_of_exp exp ~degree_kind ~of_function {integer_type_widths; location} ~ret:_ inferbo_mem =
let cost_of_exp exp ~degree_kind ~of_function _get_summary {integer_type_widths; location} ~ret:_
inferbo_mem =
let itv =
BufferOverrunSemantics.eval integer_type_widths exp inferbo_mem
|> BufferOverrunDomain.Val.get_itv
@ -23,7 +25,7 @@ let linear = cost_of_exp ~degree_kind:Polynomials.DegreeKind.Linear
let log = cost_of_exp ~degree_kind:Polynomials.DegreeKind.Log
let provider_get {pname; location} ~ret:(_, ret_typ) _ : BasicCost.t =
let provider_get _get_summary {pname; location} ~ret:(_, ret_typ) _ : BasicCost.t =
let callsite = CallSite.make pname location in
let path = Symb.SymbolPath.of_callsite ~ret_typ callsite in
let itv = Itv.of_modeled_path ~is_expensive:true path in
@ -32,7 +34,7 @@ let provider_get {pname; location} ~ret:(_, ret_typ) _ : BasicCost.t =
module BoundsOf (Container : CostUtils.S) = struct
let of_length exp {location} ~ret:_ mem ~of_function ~degree_kind =
let of_length exp _get_summary {location} ~ret:_ mem ~of_function ~degree_kind =
let itv = Container.length exp mem |> BufferOverrunDomain.Val.get_itv in
CostUtils.of_itv ~itv ~degree_kind ~of_function location
@ -41,14 +43,14 @@ module BoundsOf (Container : CostUtils.S) = struct
let logarithmic_length = of_length ~degree_kind:Polynomials.DegreeKind.Log
let n_log_n_length exp env ~ret mem ~of_function =
let log_n = logarithmic_length exp ~of_function env mem ~ret in
let n = linear_length exp ~of_function env mem ~ret in
let n_log_n_length exp get_summary env ~ret mem ~of_function =
let log_n = logarithmic_length exp ~of_function get_summary env mem ~ret in
let n = linear_length exp ~of_function get_summary env mem ~ret in
BasicCost.mult n log_n
end
module IntHashMap = struct
let keys {ProcnameDispatcher.Call.FuncArg.exp; typ} {location} ~ret:_ inferbo_mem =
let keys {ProcnameDispatcher.Call.FuncArg.exp; typ} _get_summary {location} ~ret:_ inferbo_mem =
let locs = BufferOverrunSemantics.eval_locs exp inferbo_mem in
match AbsLoc.PowLoc.is_singleton_or_more locs with
| Singleton this_loc -> (
@ -64,7 +66,7 @@ module IntHashMap = struct
end
module JavaString = struct
let substring_aux ~begin_idx ~end_v {integer_type_widths; location} inferbo_mem =
let substring_aux ~begin_idx ~end_v _get_summary {integer_type_widths; location} inferbo_mem =
let begin_v = BufferOverrunSemantics.eval integer_type_widths begin_idx inferbo_mem in
let begin_itv = BufferOverrunDomain.Val.get_itv begin_v in
let end_itv = BufferOverrunDomain.Val.get_itv end_v in
@ -84,28 +86,29 @@ module JavaString = struct
location
let substring_no_end exp begin_idx model_env ~ret:_ inferbo_mem =
let substring_no_end exp begin_idx get_summary model_env ~ret:_ inferbo_mem =
substring_aux ~begin_idx
~end_v:(BufferOverrunModels.JavaString.get_length model_env exp inferbo_mem)
model_env inferbo_mem
get_summary model_env inferbo_mem
let substring begin_idx end_idx ({integer_type_widths} as model_env) ~ret:_ inferbo_mem =
let substring begin_idx end_idx get_summary ({integer_type_widths} as model_env) ~ret:_
inferbo_mem =
substring_aux ~begin_idx
~end_v:(BufferOverrunSemantics.eval integer_type_widths end_idx inferbo_mem)
model_env inferbo_mem
get_summary model_env inferbo_mem
(** O(|m|) where m is the given string and |.| is the length function *)
let indexOf_char exp ({location} as model_env) ~ret:_ inferbo_mem =
let indexOf_char exp _get_summary ({location} as model_env) ~ret:_ inferbo_mem =
let itv = CostUtils.string_len_range_itv model_env exp ~from:None inferbo_mem in
CostUtils.of_itv ~itv ~degree_kind:Polynomials.DegreeKind.Linear ~of_function:"String.indexOf"
location
(** O(|m|-|n|) where m is the given string and n is the index to start searching from *)
let indexOf_char_starting_from exp start_exp ({integer_type_widths; location} as model_env) ~ret:_
inferbo_mem =
let indexOf_char_starting_from exp start_exp _get_summary
({integer_type_widths; location} as model_env) ~ret:_ inferbo_mem =
let itv =
CostUtils.string_len_range_itv model_env exp
~from:(Some (start_exp, integer_type_widths))
@ -116,7 +119,7 @@ module JavaString = struct
(** O(|m|.|n|) where m and n are the given strings *)
let indexOf_str exp index_exp ({location} as model_env) ~ret:_ inferbo_mem =
let indexOf_str exp index_exp _get_summary ({location} as model_env) ~ret:_ inferbo_mem =
let itv =
BufferOverrunModels.JavaString.get_length model_env exp inferbo_mem
|> BufferOverrunDomain.Val.get_itv
@ -142,15 +145,15 @@ module BoundsOfArray = BoundsOf (CostUtils.Array)
module BoundsOfCString = BoundsOf (CostUtils.CString)
module NSString = struct
let get_length str ~of_function ({location} as model_env) ~ret:_ mem =
let get_length str ~of_function _get_summary ({location} as model_env) ~ret:_ mem =
let itv =
BufferOverrunModels.NSString.get_length model_env str mem |> BufferOverrunDomain.Val.get_itv
in
CostUtils.of_itv ~itv ~degree_kind:Polynomials.DegreeKind.Linear ~of_function location
let op_on_two_str cost_op ~of_function str1 str2 model_env ~ret mem =
let get_length str = get_length str ~of_function model_env ~ret mem in
let op_on_two_str cost_op ~of_function str1 str2 get_summary model_env ~ret mem =
let get_length str = get_length str ~of_function get_summary model_env ~ret mem in
cost_op (get_length str1) (get_length str2)
@ -158,7 +161,7 @@ module NSString = struct
end
module NSCollection = struct
let get_length str ~of_function {location} ~ret:_ mem =
let get_length str ~of_function _get_summary {location} ~ret:_ mem =
let itv =
BufferOverrunModels.NSCollection.eval_collection_length str mem
|> BufferOverrunDomain.Val.get_itv
@ -166,9 +169,29 @@ module NSCollection = struct
CostUtils.of_itv ~itv ~degree_kind:Polynomials.DegreeKind.Linear ~of_function location
let op_on_two_coll cost_op ~of_function coll1 coll2 model_env ~ret mem =
let get_length coll = get_length coll ~of_function model_env ~ret mem in
let op_on_two_coll cost_op ~of_function coll1 coll2 get_summary model_env ~ret mem =
let get_length coll = get_length coll ~of_function get_summary model_env ~ret mem in
cost_op (get_length coll1) (get_length coll2)
let enumerate_using_block array get_summary ({pname} as model_env) ~ret inferbo_mem =
match pname with
| WithBlockParameters (_, [block_name]) -> (
match get_summary (Procname.Block block_name) with
| Some {CostDomain.post= callee_summary} ->
let {BasicCostWithReason.cost= callee_cost} =
CostDomain.get_cost_kind OperationCost callee_summary
in
let length =
BoundsOfNSCollection.linear_length
~of_function:(Procname.to_simplified_string pname)
array get_summary model_env ~ret inferbo_mem
in
BasicCost.mult_loop ~iter:length ~body:callee_cost
| None ->
BasicCost.zero )
| _ ->
BasicCost.zero
end
module ImmutableSet = struct
@ -178,7 +201,11 @@ module ImmutableSet = struct
end
module Call = struct
let dispatch : (Tenv.t, CostUtils.model, unit) ProcnameDispatcher.Call.dispatcher =
let dispatch :
( Tenv.t
, (Procname.t -> CostDomain.summary option) -> CostUtils.model
, unit )
ProcnameDispatcher.Call.dispatcher =
let open ProcnameDispatcher.Call in
let int_typ = Typ.mk (Typ.Tint Typ.IInt) in
let dispatcher =
@ -239,6 +266,9 @@ module Call = struct
; +PatternMatch.ObjectiveC.implements "NSMutableArray"
&:: "addObjectsFromArray:" <>$ any_arg $+ capt_exp
$--> BoundsOfNSCollection.linear_length ~of_function:"NSArray.addObjectsFromArray:"
; +PatternMatch.ObjectiveC.implements_collection
&:: "enumerateObjectsUsingBlock:" <>$ capt_exp $+ any_arg
$--> NSCollection.enumerate_using_block
; +PatternMatch.Java.implements_collections
&:: "sort" $ capt_exp
$+...$--> BoundsOfCollection.n_log_n_length ~of_function:"Collections.sort"

@ -107,6 +107,7 @@ let get_cost_if_expensive tenv integer_type_widths get_callee_cost_summary_and_f
Option.value_exn (BufferOverrunAnalysis.extract_pre instr_node_id inferbo_invariant_map)
in
let loc = InstrCFG.Node.loc last_node in
let get_summary pname = Option.map ~f:fst (get_callee_cost_summary_and_formals pname) in
let cost_opt =
match get_callee_cost_summary_and_formals pname with
| Some (CostDomain.{post= cost_record}, callee_formals) ->
@ -130,7 +131,7 @@ let get_cost_if_expensive tenv integer_type_widths get_callee_cost_summary_and_f
BufferOverrunUtils.ModelEnv.mk_model_env pname ~node_hash loc tenv
integer_type_widths inferbo_get_summary
in
model model_env ~ret inferbo_mem )
model get_summary model_env ~ret inferbo_mem )
in
Option.filter cost_opt ~f:CostDomain.BasicCost.is_symbolic

@ -227,3 +227,28 @@ void loop_with_my_enumerator_next_object_linear_FP(MyEnumerator* enumerator) {
while (s = [enumerator nextObject]) {
}
}
void enumerate_via_block_linear(NSArray* array) {
[array enumerateObjectsUsingBlock:^(id obj, NSUInteger index, BOOL* stop){
// do something with obj
}];
}
@interface MyBlock : NSObject
@end
@implementation MyBlock
+ (void)call_enumerate_via_block_param_linear_FN:(NSArray*)x:(int)size {
void (^b)(id, NSUInteger, BOOL*) =
^(id object, NSUInteger indexPath, BOOL* stop) {
for (int i = 0; i < size; i++) {
}
};
// Here, captured arg size is passed as the first argument to the block,
// not the array x. Hence, currently we don't recognize the mode which expects
// only two args.
[x enumerateObjectsUsingBlock:b];
}
@end

@ -1,8 +1,13 @@
${XCODE_ISYSROOT}/System/Library/Frameworks/Foundation.framework/Headers/NSArray.h, NSArray.enumerateObjectsUsingBlock:[objc_blockMyBlock.call_enumerate_via_block_param_linear_FN::_3], 0, OnUIThread:false, []
${XCODE_ISYSROOT}/System/Library/Frameworks/Foundation.framework/Headers/NSArray.h, NSArray.enumerateObjectsUsingBlock:[objc_blockenumerate_via_block_linear_2], 0, OnUIThread:false, []
${XCODE_ISYSROOT}/System/Library/Frameworks/Foundation.framework/Headers/NSArray.h, NSArray.indexOfObject:inSortedRange:options:usingComparator:[objc_blocknsarray_binary_search_log_FN_1], 0, OnUIThread:false, []
codetoanalyze/objc/performance/MyEnumerator.m, MyEnumerator.dealloc, 1, OnUIThread:false, []
codetoanalyze/objc/performance/MyEnumerator.m, MyEnumerator.nextObject, 6 + 3 ⋅ self->n.ub + 3 ⋅ (1+max(0, self->n.ub)), OnUIThread:false, [{1+max(0, self->n.ub)},Loop,{self->n.ub},Loop]
codetoanalyze/objc/performance/NSArray.m, MyBlock.call_enumerate_via_block_param_linear_FN::, 6, OnUIThread:false, []
codetoanalyze/objc/performance/NSArray.m, MyBlock.dealloc, 1, OnUIThread:false, []
codetoanalyze/objc/performance/NSArray.m, call_my_enumerator_next_object_linear, 9 + 3 ⋅ enumerator->n.ub + 3 ⋅ (1+max(0, enumerator->n.ub)), OnUIThread:false, [{1+max(0, enumerator->n.ub)},Call to MyEnumerator.nextObject,Loop,{enumerator->n.ub},Call to MyEnumerator.nextObject,Loop]
codetoanalyze/objc/performance/NSArray.m, call_nsarray_enumerator_param_linear, 6, OnUIThread:false, []
codetoanalyze/objc/performance/NSArray.m, enumerate_via_block_linear, 2 + array->elements.length.ub, OnUIThread:false, [{array->elements.length.ub},Modeled call to enumerateObjectsUsingBlock:]
codetoanalyze/objc/performance/NSArray.m, loop_with_my_enumerator_next_object_linear_FP, 2 + 3 ⋅ enumerator->n.ub × (enumerator.length + 1) + 10 ⋅ (enumerator.length + 1) + 3 ⋅ (enumerator.length + 1) × (1+max(0, enumerator->n.ub)), OnUIThread:false, [{1+max(0, enumerator->n.ub)},Call to MyEnumerator.nextObject,Loop,{enumerator.length + 1},Loop,{enumerator.length + 1},Loop,{enumerator->n.ub},Call to MyEnumerator.nextObject,Loop]
codetoanalyze/objc/performance/NSArray.m, multiple_nsarray_enumerators_param_linear, 12 + 5 ⋅ (enumerator2.length + enumerator1.length + 2), OnUIThread:false, [{enumerator2.length + enumerator1.length + 2},Loop]
codetoanalyze/objc/performance/NSArray.m, nsarray_access_constant, 50, OnUIThread:false, []
@ -33,6 +38,8 @@ codetoanalyze/objc/performance/NSArray.m, nsarray_next_object_linear, 5 + 5 ⋅
codetoanalyze/objc/performance/NSArray.m, nsarray_object_at_indexed_constant, 34, OnUIThread:false, []
codetoanalyze/objc/performance/NSArray.m, nsarray_sort_using_descriptors_constant, 39, OnUIThread:false, []
codetoanalyze/objc/performance/NSArray.m, nsarray_sort_using_descriptors_nlogn, 9 + array->elements.length.ub × log(array->elements.length.ub), OnUIThread:false, [{array->elements.length.ub},Modeled call to NSArray.sortedArrayUsingDescriptors:,{array->elements.length.ub},Modeled call to NSArray.sortedArrayUsingDescriptors:]
codetoanalyze/objc/performance/NSArray.m, objc_blockMyBlock.call_enumerate_via_block_param_linear_FN::_3, 3 + 3 ⋅ size + 2 ⋅ (1+max(0, size)), OnUIThread:false, [{1+max(0, size)},Loop,{size},Loop]
codetoanalyze/objc/performance/NSArray.m, objc_blockenumerate_via_block_linear_2, 1, OnUIThread:false, []
codetoanalyze/objc/performance/NSArray.m, objc_blocknsarray_binary_search_log_FN_1, 5, OnUIThread:false, []
codetoanalyze/objc/performance/NSDictionary.m, nsdictionary_all_keys_linear1, 3 + 3 ⋅ dict->elements.length.ub + 4 ⋅ (dict->elements.length.ub + 1), OnUIThread:false, [{dict->elements.length.ub + 1},Loop,{dict->elements.length.ub},Loop]
codetoanalyze/objc/performance/NSDictionary.m, nsdictionary_all_keys_linear2, 6 + 3 ⋅ dict->elements.length.ub + 3 ⋅ (dict->elements.length.ub + 1), OnUIThread:false, [{dict->elements.length.ub + 1},Loop,{dict->elements.length.ub},Loop]

Loading…
Cancel
Save