From 965d916e57601419d7d5b2d352f101205442ee47 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ezgi=20=C3=87i=C3=A7ek?= Date: Wed, 5 May 2021 03:47:56 -0700 Subject: [PATCH] [cost][inferbo] Add models for NSAttributedString Summary: Per title. Reviewed By: skcho Differential Revision: D28183310 fbshipit-source-id: 4935e1b55 --- infer/src/absint/PatternMatch.ml | 4 ++ infer/src/absint/PatternMatch.mli | 3 ++ .../src/bufferoverrun/bufferOverrunModels.ml | 12 +++-- infer/src/cost/costModels.ml | 29 ++++++++++++ .../codetoanalyze/objc/performance/NSString.m | 45 +++++++++++++++++++ .../objc/performance/cost-issues.exp | 11 +++++ 6 files changed, 100 insertions(+), 4 deletions(-) diff --git a/infer/src/absint/PatternMatch.ml b/infer/src/absint/PatternMatch.ml index 075f3d55a..f895a82c3 100644 --- a/infer/src/absint/PatternMatch.ml +++ b/infer/src/absint/PatternMatch.ml @@ -355,6 +355,10 @@ module ObjectiveC = struct Str.string_match regex procname 0 | None -> false + + + let implements_ns_string_variants tenv procname = + implements "NSString" tenv procname || implements "NSAttributedString" tenv procname end let get_vararg_type_names tenv (call_node : Procdesc.Node.t) (ivar : Pvar.t) : string list = diff --git a/infer/src/absint/PatternMatch.mli b/infer/src/absint/PatternMatch.mli index 2c31f278f..60d233159 100644 --- a/infer/src/absint/PatternMatch.mli +++ b/infer/src/absint/PatternMatch.mli @@ -202,6 +202,9 @@ module ObjectiveC : sig val implements : string -> Tenv.t -> string -> bool (** Check whether class implements a given ObjC class *) + val implements_ns_string_variants : Tenv.t -> string -> bool + (** Check whether class implements NSString or NSAttributedString *) + val conforms_to : protocol:string -> Tenv.t -> string -> bool (** Check whether class conforms to a given ObjC protocol *) diff --git a/infer/src/bufferoverrun/bufferOverrunModels.ml b/infer/src/bufferoverrun/bufferOverrunModels.ml index d3a1a1871..59c188a24 100644 --- a/infer/src/bufferoverrun/bufferOverrunModels.ml +++ b/infer/src/bufferoverrun/bufferOverrunModels.ml @@ -1552,8 +1552,8 @@ let objc_malloc exp = | 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) - -> + | Exp.Sizeof {typ} + when PatternMatch.ObjectiveC.implements_ns_string_variants tenv (Typ.to_string typ) -> (NSString.create_with_c_string (Exp.Const (Const.Cstr ""))).exec model ~ret mem | _ -> (malloc ~can_be_zero exp).exec model ~ret mem @@ -1778,9 +1778,10 @@ module Call = struct &:: "reverseObjectEnumerator" <>$ capt_exp $--> NSCollection.iterator ; +PatternMatch.ObjectiveC.implements "NSNumber" &:: "numberWithInt:" <>$ capt_exp $--> id ; +PatternMatch.ObjectiveC.implements "NSNumber" &:: "integerValue" <>$ capt_exp $--> id + ; +PatternMatch.ObjectiveC.implements "NSAttributedString" &:: "string" <>$ capt_exp $!--> id ; +PatternMatch.ObjectiveC.implements "NSString" &:: "stringWithUTF8String:" <>$ capt_exp $!--> NSString.create_with_c_string - ; +PatternMatch.ObjectiveC.implements "NSString" + ; +PatternMatch.ObjectiveC.implements_ns_string_variants &:: "length" <>$ capt_exp $--> NSString.length ; +PatternMatch.ObjectiveC.implements "NSString" &:: "stringByAppendingString:" <>$ capt_exp $+ capt_exp $!--> NSString.concat @@ -1790,7 +1791,10 @@ module Call = struct &:: "appendString:" <>$ capt_exp $+ capt_exp $--> NSString.append_string ; +PatternMatch.ObjectiveC.implements "NSString" &:: "componentsSeparatedByString:" <>$ capt_exp $+ any_arg $--> NSString.split - ; +PatternMatch.ObjectiveC.implements "NSString" + ; +PatternMatch.ObjectiveC.implements "NSAttributedString" + &:: "initWithString:attributes:" <>$ capt_exp $+ capt_exp $+ any_arg + $--> NSString.init_with_string + ; +PatternMatch.ObjectiveC.implements_ns_string_variants &:: "initWithString:" <>$ capt_exp $+ capt_exp $--> NSString.init_with_string ; +PatternMatch.ObjectiveC.implements "NSString" &:: "initWithBytes:length:encoding:" <>$ capt_exp $+ capt_exp $+ capt_exp $+ any_arg diff --git a/infer/src/cost/costModels.ml b/infer/src/cost/costModels.ml index 6dcb476b8..93f60990c 100644 --- a/infer/src/cost/costModels.ml +++ b/infer/src/cost/costModels.ml @@ -160,6 +160,32 @@ module NSString = struct let substring_from_index = JavaString.substring_no_end end +module NSAttributedString = struct + let enumerate_using_block args ({get_summary; model_env} as cost_model_env) ~ret inferbo_mem = + let pname = model_env.pname in + match List.rev args with + | _attr :: _inRange :: _options :: _usingBlock :: {exp= str} :: _captured_args -> ( + let length = + BoundsOfCString.linear_length + ~of_function:(Procname.to_simplified_string pname) + str cost_model_env ~ret inferbo_mem + in + 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 + BasicCost.mult_loop ~iter:length ~body:callee_cost + | None -> + length ) + | _ -> + length ) + | _ -> + BasicCost.one () +end + module NSCollection = struct let get_length str ~of_function {model_env= {location}} ~ret:_ mem = let itv = @@ -284,6 +310,9 @@ module Call = struct $--> BoundsOfNSCollection.linear_length ~of_function:"NSArray.addObjectsFromArray:" ; +PatternMatch.ObjectiveC.implements_collection &:: "enumerateObjectsUsingBlock:" &::.*++> NSCollection.enumerate_using_block + ; +PatternMatch.ObjectiveC.implements "NSAttributedString" + &:: "enumerateAttribute:inRange:options:usingBlock:" + &::.*++> NSAttributedString.enumerate_using_block ; +PatternMatch.Java.implements_collections &:: "sort" $ capt_exp $+...$--> BoundsOfCollection.n_log_n_length ~of_function:"Collections.sort" diff --git a/infer/tests/codetoanalyze/objc/performance/NSString.m b/infer/tests/codetoanalyze/objc/performance/NSString.m index 63517e683..7dca833b6 100644 --- a/infer/tests/codetoanalyze/objc/performance/NSString.m +++ b/infer/tests/codetoanalyze/objc/performance/NSString.m @@ -124,6 +124,51 @@ bool string_has_prefix_linear(NSString* str, NSString* prefix) { return [str hasPrefix:prefix]; } +void attributedstring_length_linear(NSAttributedString* s) { + for (int i = 0; i < s.length; i++) { + } +} + +void call_string_length_linear(NSAttributedString* s) { + string_length_linear(s.string); +} + +void enumerateAttribute_quadratic(NSAttributedString* attributedString, + NSString* kCTFontAttributeName, + int x) { + [attributedString + enumerateAttribute:kCTFontAttributeName + inRange:NSMakeRange(0, [attributedString length]) + options: + NSAttributedStringEnumerationLongestEffectiveRangeNotRequired + usingBlock:^(id value, NSRange range, BOOL* stop) { + for (int i = 0; i <= x; i++) { + } + }]; +} + +void enumerateAttribute_linear(NSAttributedString* attributedString, + NSString* kCTFontAttributeName, + int x) { + [attributedString + enumerateAttribute:kCTFontAttributeName + inRange:NSMakeRange(0, [attributedString length]) + options: + NSAttributedStringEnumerationLongestEffectiveRangeNotRequired + usingBlock:^(id value, NSRange range, BOOL* stop) { + int p = 0; + }]; +} + +// FN because captured block variable is added as last argument which our model +// cannot recognize +void enumerateAttribute_via_block_captured_linear_FN(NSArray* array, int x) { + __block BOOL answer = NO; + [array enumerateObjectsUsingBlock:^(id obj, NSUInteger index, BOOL* stop) { + answer = YES; + }]; +} + @interface DummyClass : NSObject @end diff --git a/infer/tests/codetoanalyze/objc/performance/cost-issues.exp b/infer/tests/codetoanalyze/objc/performance/cost-issues.exp index e30b19fa9..75243fecb 100644 --- a/infer/tests/codetoanalyze/objc/performance/cost-issues.exp +++ b/infer/tests/codetoanalyze/objc/performance/cost-issues.exp @@ -1,6 +1,9 @@ ${XCODE_ISYSROOT}/System/Library/Frameworks/Foundation.framework/Headers/NSArray.h, NSArray.enumerateObjectsUsingBlock:[objc_blockMyBlock.call_enumerate_via_block_param_quadratic::_3], 0, OnUIThread:false, [] +${XCODE_ISYSROOT}/System/Library/Frameworks/Foundation.framework/Headers/NSArray.h, NSArray.enumerateObjectsUsingBlock:[objc_blockenumerateAttribute_via_block_captured_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, [] +${XCODE_ISYSROOT}/System/Library/Frameworks/Foundation.framework/Headers/NSAttributedString.h, NSAttributedString.enumerateAttribute:inRange:options:usingBlock:[objc_blockenumerateAttribute_linear_2], 0, OnUIThread:false, [] +${XCODE_ISYSROOT}/System/Library/Frameworks/Foundation.framework/Headers/NSAttributedString.h, NSAttributedString.enumerateAttribute:inRange:options:usingBlock:[objc_blockenumerateAttribute_quadratic_1], 0, OnUIThread:false, [] codetoanalyze/objc/performance/MyEnumerator.m, MyEnumerator.dealloc, 0, OnUIThread:false, [] codetoanalyze/objc/performance/MyEnumerator.m, MyEnumerator.nextObject, 5 + 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_quadratic::, 4 + 3 ⋅ size × x->elements.length.ub + 2 ⋅ x->elements.length.ub + 2 ⋅ x->elements.length.ub × (1+max(0, size)), OnUIThread:false, [{1+max(0, size)},Loop,{x->elements.length.ub},Modeled call to enumerateObjectsUsingBlock:,{x->elements.length.ub},Modeled call to enumerateObjectsUsingBlock:,{size},Loop] @@ -96,15 +99,23 @@ codetoanalyze/objc/performance/NSSet.m, nsset_iterate_linear, 5 + 8 ⋅ (set->el codetoanalyze/objc/performance/NSSet.m, nsset_next_object_linear, 4 + 5 ⋅ (set->elements.length.ub + 1), OnUIThread:false, [{set->elements.length.ub + 1},Loop] codetoanalyze/objc/performance/NSString.m, DummyClass.call_string_by_appending_string_constant, 7, OnUIThread:false, [] codetoanalyze/objc/performance/NSString.m, DummyClass.dealloc, 0, OnUIThread:false, [] +codetoanalyze/objc/performance/NSString.m, attributedstring_length_linear, 2 + 3 ⋅ s.length.ub + 3 ⋅ (s.length.ub + 1), OnUIThread:false, [{s.length.ub + 1},Loop,{s.length.ub},Loop] codetoanalyze/objc/performance/NSString.m, call_component_separated_by_char_constant, 44, OnUIThread:false, [] codetoanalyze/objc/performance/NSString.m, call_init_with_string_constant, 13, OnUIThread:false, [] +codetoanalyze/objc/performance/NSString.m, call_string_length_linear, 4 + 3 ⋅ s.length.ub + 4 ⋅ (s.length.ub + 1), OnUIThread:false, [{s.length.ub + 1},Call to string_length_linear,Loop,{s.length.ub},Call to string_length_linear,Loop] codetoanalyze/objc/performance/NSString.m, component_seperated_by_char_linear, 5 + m.length.ub + 3 ⋅ (-1+max(2, m.length.ub)) + 3 ⋅ (max(2, m.length.ub)), OnUIThread:false, [{max(2, m.length.ub)},Loop,{-1+max(2, m.length.ub)},Loop,{m.length.ub},Modeled call to NSString.componentsSeparatedByString:] codetoanalyze/objc/performance/NSString.m, component_seperated_by_string_linear, 5 + sep.length.ub × m.length.ub + 3 ⋅ (-1+max(2, m.length.ub)) + 3 ⋅ (max(2, m.length.ub)), OnUIThread:false, [{max(2, m.length.ub)},Loop,{-1+max(2, m.length.ub)},Loop,{m.length.ub},Modeled call to NSString.componentsSeparatedByString:,{sep.length.ub},Modeled call to NSString.componentsSeparatedByString:] +codetoanalyze/objc/performance/NSString.m, enumerateAttribute_linear, 5 + attributedString->strlen.ub, OnUIThread:false, [{attributedString->strlen.ub},Modeled call to enumerateAttribute:inRange:options:usingBlock:] +codetoanalyze/objc/performance/NSString.m, enumerateAttribute_quadratic, 6 + 2 ⋅ attributedString->strlen.ub + 3 ⋅ attributedString->strlen.ub × (x + 1) + 2 ⋅ attributedString->strlen.ub × (2+max(-1, x)), OnUIThread:false, [{2+max(-1, x)},Loop,{x + 1},Loop,{attributedString->strlen.ub},Modeled call to enumerateAttribute:inRange:options:usingBlock:] +codetoanalyze/objc/performance/NSString.m, enumerateAttribute_via_block_captured_linear_FN, 3, OnUIThread:false, [] codetoanalyze/objc/performance/NSString.m, has_prefix_constant, 10, OnUIThread:false, [] codetoanalyze/objc/performance/NSString.m, init_string_constant, 8, OnUIThread:false, [] codetoanalyze/objc/performance/NSString.m, init_with_bytes_linear, 8 + 3 ⋅ length + 3 ⋅ (length + 1), OnUIThread:false, [{length + 1},Loop,{length},Loop] codetoanalyze/objc/performance/NSString.m, init_with_string_constant, 38, OnUIThread:false, [] codetoanalyze/objc/performance/NSString.m, init_with_string_linear, 6 + 3 ⋅ s.length.ub + 3 ⋅ (s.length.ub + 1), OnUIThread:false, [{s.length.ub + 1},Loop,{s.length.ub},Loop] +codetoanalyze/objc/performance/NSString.m, objc_blockenumerateAttribute_linear_2, 1, OnUIThread:false, [] +codetoanalyze/objc/performance/NSString.m, objc_blockenumerateAttribute_quadratic_1, 2 + 3 ⋅ (x + 1) + 2 ⋅ (2+max(-1, x)), OnUIThread:false, [{2+max(-1, x)},Loop,{x + 1},Loop] +codetoanalyze/objc/performance/NSString.m, objc_blockenumerateAttribute_via_block_captured_linear_FN_3, 2, OnUIThread:false, [] codetoanalyze/objc/performance/NSString.m, rangeof_character_from_set_linear, 4 + m.length.ub, OnUIThread:false, [{m.length.ub},Modeled call to NSString.rangeOfString:] codetoanalyze/objc/performance/NSString.m, rangeof_string_quadratic, 4 + n.length.ub × m.length.ub, OnUIThread:false, [{m.length.ub},Modeled call to NSString.rangeOfString:,{n.length.ub},Modeled call to NSString.rangeOfString:] codetoanalyze/objc/performance/NSString.m, replace_linear_FP, ⊤, OnUIThread:false, [Unbounded loop,Loop]