diff --git a/infer/src/absint/PatternMatch.ml b/infer/src/absint/PatternMatch.ml index 561a8e9ca..986fc2a14 100644 --- a/infer/src/absint/PatternMatch.ml +++ b/infer/src/absint/PatternMatch.ml @@ -58,6 +58,8 @@ let implements_iterator = implements "java.util.Iterator" let implements_collection = implements "java.util.Collection" +let implements_collections = implements "java.util.Collections" + let implements_list = implements "java.util.List" let implements_pseudo_collection t s = diff --git a/infer/src/absint/PatternMatch.mli b/infer/src/absint/PatternMatch.mli index eeb81f2b5..09a0ba599 100644 --- a/infer/src/absint/PatternMatch.mli +++ b/infer/src/absint/PatternMatch.mli @@ -36,6 +36,9 @@ val implements_iterator : Tenv.t -> string -> bool val implements_collection : Tenv.t -> string -> bool (** Check whether class implements a Java's Collection *) +val implements_collections : Tenv.t -> string -> bool +(** Check whether class implements a Java's Collections *) + val implements_pseudo_collection : Tenv.t -> string -> bool (** Check whether class implements a pseudo Collection with support for get() and size() methods *) diff --git a/infer/src/bufferoverrun/bufferOverrunModels.ml b/infer/src/bufferoverrun/bufferOverrunModels.ml index f7de755cd..c4848afb1 100644 --- a/infer/src/bufferoverrun/bufferOverrunModels.ml +++ b/infer/src/bufferoverrun/bufferOverrunModels.ml @@ -664,6 +664,15 @@ module Collection = struct let add coll_id = {exec= change_size_by ~size_f:Itv.incr coll_id; check= no_check} + let singleton_collection el_var = + let exec env ~ret:((id, _) as ret) mem = + let {exec= new_exec; check= _} = new_collection el_var in + let mem = new_exec env ~ret mem in + change_size_by ~size_f:Itv.incr id ~ret env mem + in + {exec; check= no_check} + + (** increase the size by [0, 1] because put replaces the value rather than add a new one when the key is found in the map *) let put coll_id = @@ -877,6 +886,16 @@ module Call = struct ; -"std" &:: "basic_string" &::.*--> no_model ; +PatternMatch.implements_collection &:: "" <>$ capt_var_exn $+ capt_exp $--> Collection.init + (* model sets as lists *) + ; +PatternMatch.implements_collections + &:: "singleton" <>$ capt_exp $--> Collection.singleton_collection + ; +PatternMatch.implements_collections + &:: "emptySet" <>$ capt_exp $--> Collection.new_collection + (* model maps as lists *) + ; +PatternMatch.implements_collections + &:: "singletonMap" <>$ capt_exp $--> Collection.singleton_collection + ; +PatternMatch.implements_collections + &:: "singletonList" <>$ capt_exp $--> Collection.singleton_collection ; +PatternMatch.implements_collection &:: "get" <>$ capt_var_exn $+ capt_exp $--> Collection.get_or_set_at_index ; +PatternMatch.implements_collection diff --git a/infer/src/checkers/costModels.ml b/infer/src/checkers/costModels.ml index 1bfdf5d81..f121239dd 100644 --- a/infer/src/checkers/costModels.ml +++ b/infer/src/checkers/costModels.ml @@ -34,9 +34,14 @@ module Collections = struct n_log_n length - let linear coll_exp {location} ~ret:_ inferbo_mem = - eval_collection_length coll_exp location ~of_function:"List.contains" inferbo_mem - |> BasicCost.of_non_negative_bound + let of_length_bound ~degree_kind coll_exp ~of_function {location} ~ret:_ inferbo_mem = + eval_collection_length coll_exp location inferbo_mem ~of_function + |> BasicCost.of_non_negative_bound ~degree_kind + + + let linear = of_length_bound ~degree_kind:Polynomials.DegreeKind.Linear + + let logarithmic = of_length_bound ~degree_kind:Polynomials.DegreeKind.Log end let provider_get {pname; location} ~ret:(_, ret_typ) _ : BasicCost.t = @@ -83,8 +88,24 @@ module Call = struct let dispatch : (Tenv.t, model) ProcnameDispatcher.Call.dispatcher = let open ProcnameDispatcher.Call in make_dispatcher - [ -"java.util.Collections" &:: "sort" $ capt_exp $--> Collections.sort - ; +PatternMatch.implements_list &:: "contains" <>$ capt_exp $+...$--> Collections.linear + [ +PatternMatch.implements_collections &:: "sort" $ capt_exp $--> Collections.sort + ; +PatternMatch.implements_list &:: "contains" <>$ capt_exp + $+...$--> Collections.linear ~of_function:"List.contains" + ; +PatternMatch.implements_collections + &:: "binarySearch" <>$ capt_exp + $+...$--> Collections.logarithmic ~of_function:"Collections.binarySearch" + ; +PatternMatch.implements_collections + &:: "copy" <>$ capt_exp + $+...$--> Collections.linear ~of_function:"Collections.copy" + ; +PatternMatch.implements_collections + &:: "fill" <>$ capt_exp + $+...$--> Collections.linear ~of_function:"Collections.fill" + ; +PatternMatch.implements_collections + &:: "reverse" <>$ capt_exp + $+...$--> Collections.linear ~of_function:"Collections.reverse" + ; +PatternMatch.implements_collections + &:: "shuffle" <>$ capt_exp + $+...$--> Collections.linear ~of_function:"Collections.shuffle" ; +PatternMatch.implements_lang "String" &:: "substring" <>$ capt_exp $+ capt_exp $--> String.substring ; +PatternMatch.implements_lang "String" diff --git a/infer/tests/codetoanalyze/java/performance/ArrayListTest.java b/infer/tests/codetoanalyze/java/performance/ArrayListTest.java index ffb9ca0e9..c4cd6026d 100644 --- a/infer/tests/codetoanalyze/java/performance/ArrayListTest.java +++ b/infer/tests/codetoanalyze/java/performance/ArrayListTest.java @@ -251,4 +251,5 @@ public class ArrayListTest { // l.length for (int i = 0; i < slist.size(); i++) {} } + } diff --git a/infer/tests/codetoanalyze/java/performance/CollectionsTest.java b/infer/tests/codetoanalyze/java/performance/CollectionsTest.java new file mode 100644 index 000000000..83ab21069 --- /dev/null +++ b/infer/tests/codetoanalyze/java/performance/CollectionsTest.java @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2019-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ +import java.util.Collections; +import java.util.List; +import java.util.Random; +import java.util.Set; + +class CollectionsTest { + + int binary_search_log(List list) { + return Collections.binarySearch(list, "x"); + } + + void shuffle_linear(List list, Random mRandom) { + Collections.shuffle(list, mRandom); + for (int i = 0; i < list.size(); i++) {} + } + + void singletonSet_constant() { + Set set = Collections.singleton("ezgi"); + for (String s : set) {} + } + + void singletonList_constant(String el) { + List list = Collections.singletonList(el); + for (String s : list) {} + } + + void fill_linear(List list, String el) { + Collections.fill(list, el); + } + + void reverse_linear(List list) { + Collections.reverse(list); + } + + void reverse_constant(String el) { + List list = Collections.singletonList(el); + Collections.reverse(list); + } + + void copy_linear(List list_from, List list_to) { + Collections.copy(list_to, list_from); + } +} diff --git a/infer/tests/codetoanalyze/java/performance/issues.exp b/infer/tests/codetoanalyze/java/performance/issues.exp index f82b3f3f9..253868279 100644 --- a/infer/tests/codetoanalyze/java/performance/issues.exp +++ b/infer/tests/codetoanalyze/java/performance/issues.exp @@ -51,6 +51,13 @@ codetoanalyze/java/performance/CollectionTest.java, CollectionTest.iterate_over_ codetoanalyze/java/performance/CollectionTest.java, CollectionTest.loop_over_call(int,CollectionTest$MyCollection):void, 1, EXPENSIVE_EXECUTION_CALL, no_bucket, ERROR, [with estimated cost 2 + 15 ⋅ size + 5 ⋅ size × list.length, degree = 2,{list.length},call to void CollectionTest.iterate_over_mycollection(CollectionTest$MyCollection),Loop at line 17,{size},Loop at line 41] codetoanalyze/java/performance/CollectionTest.java, CollectionTest.nested_iterator_qubic(int,CollectionTest$MyCollection,CollectionTest$MyCollection):void, 1, EXPENSIVE_EXECUTION_CALL, no_bucket, ERROR, [with estimated cost 4 + 13 ⋅ (list1.length - 1) + 28 ⋅ (list1.length - 1) × (list2.length - 1) + 5 ⋅ (list1.length - 1) × (list2.length - 1) × list1.length + 5 ⋅ (list1.length - 1) × (list2.length - 1) × list1.length + 3 ⋅ (list1.length - 1) × list2.length + 3 ⋅ list1.length, degree = 3,{list1.length},Loop at line 55,{list2.length},Loop at line 56,{list1.length},call to void CollectionTest.iterate_over_mycollection(CollectionTest$MyCollection),Loop at line 17,{list1.length},call to void CollectionTest.iterate_over_mycollection(CollectionTest$MyCollection),Loop at line 17,{list2.length - 1},Loop at line 56,{list1.length - 1},Loop at line 55] codetoanalyze/java/performance/CollectionTest.java, CollectionTest.sparse_array_linear(android.util.SparseArray):void, 1, EXPENSIVE_EXECUTION_CALL, no_bucket, ERROR, [with estimated cost 2 + 5 ⋅ arr.length + 3 ⋅ (arr.length + 1), degree = 1,{arr.length + 1},Loop at line 64,{arr.length},Loop at line 64] +codetoanalyze/java/performance/CollectionsTest.java, CollectionsTest.binary_search_log(java.util.List):int, 1, EXPENSIVE_EXECUTION_CALL, no_bucket, ERROR, [with estimated cost 2 + log(list.length), degree = 0 + 1⋅log,{list.length},Modeled call to Collections.binarySearch] +codetoanalyze/java/performance/CollectionsTest.java, CollectionsTest.copy_linear(java.util.List,java.util.List):void, 1, EXPENSIVE_EXECUTION_CALL, no_bucket, ERROR, [with estimated cost 3 + list_to.length, degree = 1,{list_to.length},Modeled call to Collections.copy] +codetoanalyze/java/performance/CollectionsTest.java, CollectionsTest.fill_linear(java.util.List,java.lang.String):void, 1, EXPENSIVE_EXECUTION_CALL, no_bucket, ERROR, [with estimated cost 3 + list.length, degree = 1,{list.length},Modeled call to Collections.fill] +codetoanalyze/java/performance/CollectionsTest.java, CollectionsTest.reverse_linear(java.util.List):void, 1, EXPENSIVE_EXECUTION_CALL, no_bucket, ERROR, [with estimated cost 2 + list.length, degree = 1,{list.length},Modeled call to Collections.reverse] +codetoanalyze/java/performance/CollectionsTest.java, CollectionsTest.shuffle_linear(java.util.List,java.util.Random):void, 1, EXPENSIVE_EXECUTION_CALL, no_bucket, ERROR, [with estimated cost 3 + list.length, degree = 1,{list.length},Modeled call to Collections.shuffle] +codetoanalyze/java/performance/CollectionsTest.java, CollectionsTest.singletonList_constant(java.lang.String):void, 2, CONDITION_ALWAYS_FALSE, no_bucket, WARNING, [Here] +codetoanalyze/java/performance/CollectionsTest.java, CollectionsTest.singletonSet_constant():void, 2, CONDITION_ALWAYS_FALSE, no_bucket, WARNING, [Here] codetoanalyze/java/performance/Compound_loop.java, codetoanalyze.java.performance.Compound_loop.compound_while(int):int, 3, CONDITION_ALWAYS_TRUE, no_bucket, WARNING, [Here] codetoanalyze/java/performance/Compound_loop.java, codetoanalyze.java.performance.Compound_loop.compound_while(int):int, 3, EXPENSIVE_EXECUTION_CALL, no_bucket, ERROR, [with estimated cost 4 + 5 ⋅ m + 2 ⋅ (1+max(0, m)), degree = 1,{1+max(0, m)},Loop at line 15,{m},Loop at line 15] codetoanalyze/java/performance/Compound_loop.java, codetoanalyze.java.performance.Compound_loop.nested_while_and_or(int):int, 3, CONDITION_ALWAYS_TRUE, no_bucket, WARNING, [Here]