From b802620bc8ec8e278db3f92684b0ee3bd2a75ca3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ezgi=20=C3=87i=C3=A7ek?= Date: Tue, 9 Apr 2019 08:32:06 -0700 Subject: [PATCH] [cost] Add cost models for loop invariant functions Reviewed By: jvillard Differential Revision: D14832961 fbshipit-source-id: 0d1b3353d --- infer/src/bufferoverrun/bounds.ml | 2 + infer/src/bufferoverrun/bounds.mli | 3 + .../src/bufferoverrun/bufferOverrunModels.ml | 4 +- .../bufferoverrun/bufferOverrunSemantics.ml | 12 +++ infer/src/bufferoverrun/itv.ml | 4 + infer/src/bufferoverrun/itv.mli | 2 + infer/src/bufferoverrun/symb.ml | 11 ++- infer/src/bufferoverrun/symb.mli | 4 +- infer/src/checkers/cost.ml | 43 ++++++----- infer/src/checkers/costModels.ml | 69 +++++++++++++++-- infer/src/checkers/hoisting.ml | 77 +++++++++++++------ .../java/hoistingExpensive/HoistModeled.java | 69 +++++++++++++++++ .../java/hoistingExpensive/issues.exp | 13 ++++ 13 files changed, 258 insertions(+), 55 deletions(-) create mode 100644 infer/tests/codetoanalyze/java/hoistingExpensive/HoistModeled.java diff --git a/infer/src/bufferoverrun/bounds.ml b/infer/src/bufferoverrun/bounds.ml index 42dfde127..61b70f721 100644 --- a/infer/src/bufferoverrun/bounds.ml +++ b/infer/src/bufferoverrun/bounds.ml @@ -297,6 +297,8 @@ module Bound = struct let of_length_path = of_path Symb.SymbolPath.length ~unsigned:true + let of_modeled_path = of_path Symb.SymbolPath.modeled ~unsigned:true + let is_symbolic : t -> bool = function | MInf | PInf -> false diff --git a/infer/src/bufferoverrun/bounds.mli b/infer/src/bufferoverrun/bounds.mli index 322e47269..d0d38a97c 100644 --- a/infer/src/bufferoverrun/bounds.mli +++ b/infer/src/bufferoverrun/bounds.mli @@ -47,6 +47,9 @@ module Bound : sig val of_length_path : (unsigned:bool -> Symb.SymbolPath.t -> Symb.Symbol.t) -> Symb.SymbolPath.partial -> t + val of_modeled_path : + (unsigned:bool -> Symb.SymbolPath.t -> Symb.Symbol.t) -> Symb.SymbolPath.partial -> t + val is_zero : t -> bool val is_not_infty : t -> bool diff --git a/infer/src/bufferoverrun/bufferOverrunModels.ml b/infer/src/bufferoverrun/bufferOverrunModels.ml index eab7f1bd7..9f597b096 100644 --- a/infer/src/bufferoverrun/bufferOverrunModels.ml +++ b/infer/src/bufferoverrun/bufferOverrunModels.ml @@ -160,9 +160,11 @@ let memset arr_exp size_exp = {exec; check} +let eval_string_len arr_exp mem = Dom.Mem.get_c_strlen (Sem.eval_locs arr_exp mem) mem + let strlen arr_exp = let exec _ ~ret:(id, _) mem = - let v = Dom.Mem.get_c_strlen (Sem.eval_locs arr_exp mem) mem in + let v = eval_string_len arr_exp mem in Dom.Mem.add_stack (Loc.of_id id) v mem in {exec; check= no_check} diff --git a/infer/src/bufferoverrun/bufferOverrunSemantics.ml b/infer/src/bufferoverrun/bufferOverrunSemantics.ml index 7fe10334f..6824f0cfe 100644 --- a/infer/src/bufferoverrun/bufferOverrunSemantics.ml +++ b/infer/src/bufferoverrun/bufferOverrunSemantics.ml @@ -376,6 +376,15 @@ end substituting reachabilities of proof obligations. *) type eval_mode = EvalNormal | EvalPOCond | EvalPOReachability +let eval_sympath_modeled_partial ~mode p = + match (mode, p) with + | EvalNormal, Symb.SymbolPath.Callsite _ -> + Itv.of_modeled_path p |> Val.of_itv + | _, _ -> + (* We only have modeled modeled function calls created in costModels. *) + assert false + + let rec eval_sympath_partial ~mode params p mem = match p with | Symb.SymbolPath.Pvar x -> ( @@ -421,6 +430,9 @@ and eval_locpath ~mode params p mem = let eval_sympath ~mode params sympath mem = match sympath with + | Symb.SymbolPath.Modeled p -> + let v = eval_sympath_modeled_partial ~mode p in + (Val.get_itv v, Val.get_traces v) | Symb.SymbolPath.Normal p -> let v = eval_sympath_partial ~mode params p mem in (Val.get_itv v, Val.get_traces v) diff --git a/infer/src/bufferoverrun/itv.ml b/infer/src/bufferoverrun/itv.ml index 67b07ec97..45679ac8b 100644 --- a/infer/src/bufferoverrun/itv.ml +++ b/infer/src/bufferoverrun/itv.ml @@ -436,6 +436,8 @@ module ItvPure = struct let of_offset_path = of_path Bound.of_offset_path let of_length_path = of_path Bound.of_length_path + + let of_modeled_path = of_path Bound.of_modeled_path end include AbstractDomain.BottomLifted (ItvPure) @@ -658,6 +660,8 @@ let of_offset_path path = NonBottom (ItvPure.of_offset_path path) let of_length_path path = NonBottom (ItvPure.of_length_path path) +let of_modeled_path path = NonBottom (ItvPure.of_modeled_path path) + let is_offset_path_of path x = eq (of_offset_path path) x let is_length_path_of path x = eq (of_length_path path) x diff --git a/infer/src/bufferoverrun/itv.mli b/infer/src/bufferoverrun/itv.mli index 263fa97be..a988f68a1 100644 --- a/infer/src/bufferoverrun/itv.mli +++ b/infer/src/bufferoverrun/itv.mli @@ -235,6 +235,8 @@ val of_offset_path : Symb.SymbolPath.partial -> t val of_length_path : Symb.SymbolPath.partial -> t +val of_modeled_path : Symb.SymbolPath.partial -> t + val is_offset_path_of : Symb.SymbolPath.partial -> t -> bool val is_length_path_of : Symb.SymbolPath.partial -> t -> bool diff --git a/infer/src/bufferoverrun/symb.ml b/infer/src/bufferoverrun/symb.ml index 9e3f22c9f..bb37c250b 100644 --- a/infer/src/bufferoverrun/symb.ml +++ b/infer/src/bufferoverrun/symb.ml @@ -33,7 +33,8 @@ module SymbolPath = struct | Callsite of {ret_typ: Typ.t; cs: CallSite.t} [@@deriving compare] - type t = Normal of partial | Offset of partial | Length of partial [@@deriving compare] + type t = Normal of partial | Offset of partial | Length of partial | Modeled of partial + [@@deriving compare] let equal = [%compare.equal: t] @@ -53,6 +54,8 @@ module SymbolPath = struct let length p = Length p + let modeled p = Modeled p + let is_this = function Pvar pvar -> Pvar.is_this pvar || Pvar.is_self pvar | _ -> false let rec get_pvar = function @@ -92,6 +95,8 @@ module SymbolPath = struct let pp_partial = pp_partial_paren ~paren:false let pp fmt = function + | Modeled p -> + F.fprintf fmt "%a.modeled" pp_partial p | Normal p -> pp_partial fmt p | Offset p -> @@ -146,7 +151,9 @@ module SymbolPath = struct false - let exists_str ~f = function Normal p | Offset p | Length p -> exists_str_partial ~f p + let exists_str ~f = function + | Modeled p | Normal p | Offset p | Length p -> + exists_str_partial ~f p end module Symbol = struct diff --git a/infer/src/bufferoverrun/symb.mli b/infer/src/bufferoverrun/symb.mli index bb9376355..de4d16c8c 100644 --- a/infer/src/bufferoverrun/symb.mli +++ b/infer/src/bufferoverrun/symb.mli @@ -29,7 +29,7 @@ module SymbolPath : sig | Callsite of {ret_typ: Typ.t; cs: CallSite.t} [@@deriving compare] - type t = private Normal of partial | Offset of partial | Length of partial + type t = private Normal of partial | Offset of partial | Length of partial | Modeled of partial val equal_partial : partial -> partial -> bool @@ -55,6 +55,8 @@ module SymbolPath : sig val length : partial -> t + val modeled : partial -> t + val is_this : partial -> bool val get_pvar : partial -> Pvar.t option diff --git a/infer/src/checkers/cost.ml b/infer/src/checkers/cost.ml index 680d9b51f..955a18c37 100644 --- a/infer/src/checkers/cost.ml +++ b/infer/src/checkers/cost.ml @@ -566,9 +566,9 @@ module InstrBasicCost = struct List.exists allocation_functions ~f:(fun f -> Typ.Procname.equal callee_pname f) - let get_instr_cost_record extras instr_node instr = + let get_instr_cost_record tenv extras instr_node instr = match instr with - | Sil.Call (_, Exp.Const (Const.Cfun callee_pname), params, _, _) -> + | Sil.Call (ret, Exp.Const (Const.Cfun callee_pname), params, _, _) -> let {inferbo_invariant_map; integer_type_widths; get_callee_summary_and_formals} = extras in @@ -580,9 +580,14 @@ module InstrBasicCost = struct CostDomain.unit_cost_atomic_operation | Some inferbo_mem -> ( let loc = InstrCFG.Node.loc instr_node in - match CostModels.Call.dispatch () callee_pname params with + match CostModels.Call.dispatch tenv callee_pname params with | Some model -> - CostDomain.of_operation_cost (model loc inferbo_mem) + 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 + in + CostDomain.of_operation_cost (model model_env ~ret inferbo_mem) | None -> ( match get_callee_summary_and_formals callee_pname with | Some ({CostDomain.post= callee_cost_record}, callee_formals) -> @@ -612,7 +617,7 @@ module InstrBasicCost = struct CostDomain.zero_record - let get_instr_node_cost_record extras instr_node = + let get_instr_node_cost_record tenv extras instr_node = let instrs = InstrCFG.instrs instr_node in let instr = match IContainer.singleton_or_more instrs ~fold:Instrs.fold with @@ -623,7 +628,7 @@ module InstrBasicCost = struct | More -> assert false in - get_instr_cost_record extras instr_node instr + get_instr_cost_record tenv extras instr_node instr end let compute_errlog_extras cost = @@ -663,10 +668,10 @@ module WorstCaseCost = struct (not (BasicCost.is_top cost)) && not (BasicCost.( <= ) ~lhs:cost ~rhs:threshold) - let exec_node {costs; reports} extras instr_node = + let exec_node tenv {costs; reports} extras instr_node = let {get_node_nb_exec} = extras in let node_cost = - let instr_cost_record = InstrBasicCost.get_instr_node_cost_record extras instr_node in + let instr_cost_record = InstrBasicCost.get_instr_node_cost_record tenv extras instr_node in let node_id = InstrCFG.Node.underlying_node instr_node |> Node.id in let nb_exec = get_node_nb_exec node_id in CostDomain.mult_by_scalar instr_cost_record nb_exec @@ -688,25 +693,25 @@ module WorstCaseCost = struct {costs; reports} - let rec exec_partition astate extras + let rec exec_partition tenv astate extras (partition : InstrCFG.Node.t WeakTopologicalOrder.Partition.t) = match partition with | Empty -> astate | Node {node; next} -> - let astate = exec_node astate extras node in - exec_partition astate extras next + let astate = exec_node tenv astate extras node in + exec_partition tenv astate extras next | Component {head; rest; next} -> let {costs; reports} = astate in - let {costs} = exec_partition {costs; reports= ThresholdReports.none} extras rest in + let {costs} = exec_partition tenv {costs; reports= ThresholdReports.none} extras rest in (* Execute head after the loop body to always report at loop head *) - let astate = exec_node {costs; reports} extras head in - exec_partition astate extras next + let astate = exec_node tenv {costs; reports} extras head in + exec_partition tenv astate extras next - let compute extras instr_cfg_wto = + let compute tenv extras instr_cfg_wto = let initial = {costs= CostDomain.zero_record; reports= ThresholdReports.config} in - exec_partition initial extras instr_cfg_wto + exec_partition tenv initial extras instr_cfg_wto end module Check = struct @@ -813,12 +818,12 @@ let compute_get_node_nb_exec node_cfg bound_map : get_node_nb_exec = ConstraintSolver.get_node_nb_exec equalities -let compute_worst_case_cost integer_type_widths get_callee_summary_and_formals instr_cfg_wto +let compute_worst_case_cost tenv integer_type_widths get_callee_summary_and_formals instr_cfg_wto inferbo_invariant_map get_node_nb_exec = let extras = {inferbo_invariant_map; integer_type_widths; get_node_nb_exec; get_callee_summary_and_formals} in - WorstCaseCost.compute extras instr_cfg_wto + WorstCaseCost.compute tenv extras instr_cfg_wto let get_cost_summary astate = CostDomain.{post= astate.WorstCaseCost.costs} @@ -866,7 +871,7 @@ let checker {Callbacks.tenv; proc_desc; integer_type_widths; summary} : Summary. in let instr_cfg = InstrCFG.from_pdesc proc_desc in let instr_cfg_wto = InstrCFG.wto instr_cfg in - compute_worst_case_cost integer_type_widths get_callee_summary_and_formals instr_cfg_wto + compute_worst_case_cost tenv integer_type_widths get_callee_summary_and_formals instr_cfg_wto inferbo_invariant_map get_node_nb_exec in let () = diff --git a/infer/src/checkers/costModels.ml b/infer/src/checkers/costModels.ml index f35c4da56..1bfdf5d81 100644 --- a/infer/src/checkers/costModels.ml +++ b/infer/src/checkers/costModels.ml @@ -7,11 +7,12 @@ open! IStd module BasicCost = CostDomain.BasicCost +open BufferOverrunUtils.ModelEnv -type model = Location.t -> BufferOverrunDomain.Mem.t -> BasicCost.t +type model = model_env -> ret:Ident.t * Typ.t -> BufferOverrunDomain.Mem.t -> BasicCost.t module Collections = struct - let eval_collection_length coll_exp loc inferbo_mem = + let eval_collection_length coll_exp loc inferbo_mem ~of_function = let upper_bound = let itv = BufferOverrunModels.Collection.eval_collection_length coll_exp inferbo_mem @@ -19,7 +20,7 @@ module Collections = struct in match itv with Bottom -> Bounds.Bound.pinf | NonBottom itv_pure -> Itv.ItvPure.ub itv_pure in - Bounds.NonNegativeBound.of_modeled_function "List.length" loc upper_bound + Bounds.NonNegativeBound.of_modeled_function of_function loc upper_bound let n_log_n b = @@ -28,13 +29,67 @@ module Collections = struct BasicCost.mult n log_n - let sort coll_exp loc inferbo_mem = - let length = eval_collection_length coll_exp loc inferbo_mem in + let sort coll_exp {location} ~ret:_ inferbo_mem = + let length = eval_collection_length coll_exp location ~of_function:"List.length" inferbo_mem in 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 +end + +let provider_get {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 v = + let itv = Itv.of_modeled_path path in + match itv with Bottom -> Bounds.Bound.pinf | NonBottom itv_pure -> Itv.ItvPure.ub itv_pure + in + Bounds.NonNegativeBound.of_modeled_function "Provider.get" location v + |> BasicCost.of_non_negative_bound + + +module String = struct + let substring_aux ~begin_idx ~end_v {integer_type_widths; location} inferbo_mem = + let upper_bound = + let begin_v = BufferOverrunSemantics.eval integer_type_widths begin_idx inferbo_mem in + let substring_itv = + Itv.minus (BufferOverrunDomain.Val.get_itv end_v) (BufferOverrunDomain.Val.get_itv begin_v) + in + match substring_itv with + | Bottom -> + Bounds.Bound.pinf + | NonBottom itv_pure -> + Itv.ItvPure.ub itv_pure + in + Bounds.NonNegativeBound.of_modeled_function "String.substring" location upper_bound + |> BasicCost.of_non_negative_bound + + + let substring exp begin_idx model_env ~ret:_ inferbo_mem = + substring_aux ~begin_idx + ~end_v:(BufferOverrunModels.eval_string_len exp inferbo_mem) + model_env inferbo_mem + + + let substring_no_end begin_idx end_idx ({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 end module Call = struct - let dispatch : (unit, model) ProcnameDispatcher.Call.dispatcher = + let dispatch : (Tenv.t, model) ProcnameDispatcher.Call.dispatcher = let open ProcnameDispatcher.Call in - make_dispatcher [-"java.util.Collections" &:: "sort" $ capt_exp $--> Collections.sort] + make_dispatcher + [ -"java.util.Collections" &:: "sort" $ capt_exp $--> Collections.sort + ; +PatternMatch.implements_list &:: "contains" <>$ capt_exp $+...$--> Collections.linear + ; +PatternMatch.implements_lang "String" + &:: "substring" <>$ capt_exp $+ capt_exp $--> String.substring + ; +PatternMatch.implements_lang "String" + &:: "substring" + $ any_arg_of_typ (+PatternMatch.implements_lang "String") + $+ capt_exp $+ capt_exp $--> String.substring_no_end + ; +PatternMatch.implements_inject "Provider" &:: "get" <>--> provider_get ] end diff --git a/infer/src/checkers/hoisting.ml b/infer/src/checkers/hoisting.ml index 338eb5cb5..a010bc2bc 100644 --- a/infer/src/checkers/hoisting.ml +++ b/infer/src/checkers/hoisting.ml @@ -10,7 +10,11 @@ module InstrCFG = ProcCfg.NormalOneInstrPerNode module Call = struct type t = - {loc: Location.t; pname: Typ.Procname.t; node: Procdesc.Node.t; params: (Exp.t * Typ.t) list} + { loc: Location.t + ; pname: Typ.Procname.t + ; node: Procdesc.Node.t + ; params: (Exp.t * Typ.t) list + ; ret: Ident.t * Typ.t } [@@deriving compare] let pp fmt {pname; loc} = @@ -30,12 +34,12 @@ module LoopHeadToHoistInstrs = Procdesc.NodeMap let add_if_hoistable inv_vars instr node source_nodes idom hoistable_calls = match instr with - | Sil.Call ((ret_id, _), Exp.Const (Const.Cfun pname), params, loc, _) + | Sil.Call (((ret_id, _) as ret), Exp.Const (Const.Cfun pname), params, loc, _) when (* Check condition (1); N dominates all loop sources *) List.for_all ~f:(fun source -> Dominators.dominates idom node source) source_nodes && (* Check condition (2); id should be invariant already *) LoopInvariant.InvariantVars.mem (Var.of_id ret_id) inv_vars -> - HoistCalls.add {pname; loc; node; params} hoistable_calls + HoistCalls.add {pname; loc; node; params; ret} hoistable_calls | _ -> hoistable_calls @@ -80,24 +84,32 @@ let model_satisfies ~f tenv pname = InvariantModels.ProcName.dispatch tenv pname |> Option.exists ~f -let is_call_expensive integer_type_widths get_callee_cost_summary_and_formals inferbo_invariant_map - Call.{pname; node; params} = +let is_call_expensive tenv integer_type_widths get_callee_cost_summary_and_formals + inferbo_invariant_map (Call.{pname; node; ret; params} as call) = + let last_node = InstrCFG.last_of_underlying_node node in + let instr_node_id = InstrCFG.Node.id last_node in + let inferbo_mem = + Option.value_exn (BufferOverrunAnalysis.extract_pre instr_node_id inferbo_invariant_map) + in (* only report if function call has expensive/symbolic cost *) - match get_callee_cost_summary_and_formals pname with + match get_callee_cost_summary_and_formals call inferbo_mem with | Some (CostDomain.{post= cost_record}, callee_formals) - when CostDomain.BasicCost.is_symbolic (CostDomain.get_operation_cost cost_record) -> - let last_node = InstrCFG.last_of_underlying_node node in - let instr_node_id = InstrCFG.Node.id last_node in - let inferbo_mem = - Option.value_exn (BufferOverrunAnalysis.extract_pre instr_node_id inferbo_invariant_map) - in + when CostDomain.BasicCost.is_symbolic (CostDomain.get_operation_cost cost_record) -> ( let loc = InstrCFG.Node.loc last_node in + let node_hash = InstrCFG.Node.hash last_node in + let model_env = + BufferOverrunUtils.ModelEnv.mk_model_env pname ~node_hash loc tenv integer_type_widths + in (* get the cost of the function call *) - Cost.instantiate_cost integer_type_widths ~inferbo_caller_mem:inferbo_mem ~callee_pname:pname - ~callee_formals ~params - ~callee_cost:(CostDomain.get_operation_cost cost_record) - ~loc - |> CostDomain.BasicCost.is_symbolic + match CostModels.Call.dispatch tenv pname params with + | Some model -> + model model_env ~ret inferbo_mem |> CostDomain.BasicCost.is_symbolic + | None -> + Cost.instantiate_cost integer_type_widths ~inferbo_caller_mem:inferbo_mem + ~callee_pname:pname ~callee_formals ~params + ~callee_cost:(CostDomain.get_operation_cost cost_record) + ~loc + |> CostDomain.BasicCost.is_symbolic ) | _ -> false @@ -148,15 +160,30 @@ let checker Callbacks.{tenv; summary; proc_desc; integer_type_widths} : Summary. let inferbo_invariant_map = BufferOverrunAnalysis.cached_compute_invariant_map proc_desc tenv integer_type_widths in - let get_callee_cost_summary_and_formals callee_pname = - Ondemand.analyze_proc_name ~caller_pdesc:proc_desc callee_pname - |> Option.bind ~f:(fun summary -> - summary.Summary.payloads.Payloads.cost - |> Option.map ~f:(fun cost_summary -> - (cost_summary, Summary.get_proc_desc summary |> Procdesc.get_pvar_formals) ) - ) + let get_callee_cost_summary_and_formals Call.{loc; pname; params; node; ret} inferbo_mem = + let callee_pname = pname in + match Ondemand.analyze_proc_name ~caller_pdesc:proc_desc callee_pname with + | Some summary -> + summary.Summary.payloads.Payloads.cost + |> Option.map ~f:(fun cost_summary -> + (cost_summary, Summary.get_proc_desc summary |> Procdesc.get_pvar_formals) ) + | None -> ( + match CostModels.Call.dispatch tenv callee_pname params with + | Some model -> + let last_node = InstrCFG.last_of_underlying_node node in + let node_hash = InstrCFG.Node.hash last_node in + let model_env = + BufferOverrunUtils.ModelEnv.mk_model_env pname ~node_hash loc tenv + integer_type_widths + in + let callee_cost = CostDomain.of_operation_cost (model model_env ~ret inferbo_mem) in + Some + ( CostDomain.{post= callee_cost} + , Summary.get_proc_desc summary |> Procdesc.get_pvar_formals ) + | None -> + None ) in - is_call_expensive integer_type_widths get_callee_cost_summary_and_formals + is_call_expensive tenv integer_type_widths get_callee_cost_summary_and_formals inferbo_invariant_map else fun call -> not (is_call_variant_for_hoisting tenv call) in diff --git a/infer/tests/codetoanalyze/java/hoistingExpensive/HoistModeled.java b/infer/tests/codetoanalyze/java/hoistingExpensive/HoistModeled.java new file mode 100644 index 000000000..591ee6bf0 --- /dev/null +++ b/infer/tests/codetoanalyze/java/hoistingExpensive/HoistModeled.java @@ -0,0 +1,69 @@ +/* + * 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.ArrayList; +import javax.inject.*; + +class HoistModeled { + + @Inject private Provider mProvider; + + void expensive_get_hoist(int size) { + for (int i = 0; i < size; i++) { + mProvider.get(); + } + } + + void linear_contains_hoist(ArrayList list, Integer el) { + int count = 0; + for (int i = 0; i < 10; i++) { + if (list.contains(el)) { + count++; + } + } + } + + void constant_contains_dont_hoist(Integer el) { + boolean contains = false; + ArrayList mylist = new ArrayList(); + mylist.add(1); + for (int i = 0; i < 10; i++) { + contains = mylist.contains(el); + } + } + + void constant_substring_dont_hoist(String s) { + String sub; + for (int i = 0; i < 10; i++) { + sub = s.substring(2, 10); + } + } + + void linear_substring_hoist(String s, ArrayList list, Integer el) { + String sub; + int length = s.length(); + for (int i = 0; i < 10; i++) { + sub = s.substring(2, length - 1); + } + for (int i = 0; i < 10; i++) { + sub = s.substring(1); + } + } + + void call_expensive_hoist(String s, ArrayList list, Integer el) { + for (int i = 0; i < 10; i++) { + expensive_get_hoist(10); + } + } + + void expensive_get_hoist_hoist_me(String s, ArrayList list, Integer el) { + String sub; + int length = s.length(); + for (int i = 0; i < 10; i++) { + call_expensive_hoist("ez", list, el); + } + } +} diff --git a/infer/tests/codetoanalyze/java/hoistingExpensive/issues.exp b/infer/tests/codetoanalyze/java/hoistingExpensive/issues.exp index a71ec1ad9..0a7971376 100644 --- a/infer/tests/codetoanalyze/java/hoistingExpensive/issues.exp +++ b/infer/tests/codetoanalyze/java/hoistingExpensive/issues.exp @@ -6,3 +6,16 @@ codetoanalyze/java/hoistingExpensive/HoistExpensive.java, HoistExpensive.symboli codetoanalyze/java/hoistingExpensive/HoistExpensive.java, HoistExpensive.symbolic_expensive_hoist(int):void, 2, INVARIANT_CALL, no_bucket, ERROR, [The call to void HoistExpensive.cheap_dont_hoist(int) at line 26 is loop-invariant] codetoanalyze/java/hoistingExpensive/HoistExpensive.java, HoistExpensive.symbolic_expensive_iterator_hoist(int,java.util.ArrayList):void, 0, PURE_FUNCTION, no_bucket, ERROR, [Side-effect free function void HoistExpensive.symbolic_expensive_iterator_hoist(int,ArrayList)] codetoanalyze/java/hoistingExpensive/HoistExpensive.java, HoistExpensive.symbolic_expensive_iterator_hoist(int,java.util.ArrayList):void, 2, INVARIANT_CALL, no_bucket, ERROR, [The call to void HoistExpensive.cheap_iterator_dont_hoist(ArrayList) at line 48 is loop-invariant] +codetoanalyze/java/hoistingExpensive/HoistModeled.java, HoistModeled.call_expensive_hoist(java.lang.String,java.util.ArrayList,java.lang.Integer):void, 0, PURE_FUNCTION, no_bucket, ERROR, [Side-effect free function void HoistModeled.call_expensive_hoist(String,ArrayList,Integer)] +codetoanalyze/java/hoistingExpensive/HoistModeled.java, HoistModeled.call_expensive_hoist(java.lang.String,java.util.ArrayList,java.lang.Integer):void, 2, INVARIANT_CALL, no_bucket, ERROR, [The call to void HoistModeled.expensive_get_hoist(int) at line 58 is loop-invariant] +codetoanalyze/java/hoistingExpensive/HoistModeled.java, HoistModeled.constant_contains_dont_hoist(java.lang.Integer):void, 0, PURE_FUNCTION, no_bucket, ERROR, [Side-effect free function void HoistModeled.constant_contains_dont_hoist(Integer)] +codetoanalyze/java/hoistingExpensive/HoistModeled.java, HoistModeled.constant_substring_dont_hoist(java.lang.String):void, 0, PURE_FUNCTION, no_bucket, ERROR, [Side-effect free function void HoistModeled.constant_substring_dont_hoist(String)] +codetoanalyze/java/hoistingExpensive/HoistModeled.java, HoistModeled.expensive_get_hoist(int):void, 0, PURE_FUNCTION, no_bucket, ERROR, [Side-effect free function void HoistModeled.expensive_get_hoist(int)] +codetoanalyze/java/hoistingExpensive/HoistModeled.java, HoistModeled.expensive_get_hoist(int):void, 2, LOOP_INVARIANT_CALL, no_bucket, ERROR, [The call to Object Provider.get() at line 16 is loop-invariant] +codetoanalyze/java/hoistingExpensive/HoistModeled.java, HoistModeled.expensive_get_hoist_hoist_me(java.lang.String,java.util.ArrayList,java.lang.Integer):void, 0, PURE_FUNCTION, no_bucket, ERROR, [Side-effect free function void HoistModeled.expensive_get_hoist_hoist_me(String,ArrayList,Integer)] +codetoanalyze/java/hoistingExpensive/HoistModeled.java, HoistModeled.expensive_get_hoist_hoist_me(java.lang.String,java.util.ArrayList,java.lang.Integer):void, 4, INVARIANT_CALL, no_bucket, ERROR, [The call to void HoistModeled.call_expensive_hoist(String,ArrayList,Integer) at line 66 is loop-invariant] +codetoanalyze/java/hoistingExpensive/HoistModeled.java, HoistModeled.linear_contains_hoist(java.util.ArrayList,java.lang.Integer):void, 0, PURE_FUNCTION, no_bucket, ERROR, [Side-effect free function void HoistModeled.linear_contains_hoist(ArrayList,Integer)] +codetoanalyze/java/hoistingExpensive/HoistModeled.java, HoistModeled.linear_contains_hoist(java.util.ArrayList,java.lang.Integer):void, 3, LOOP_INVARIANT_CALL, no_bucket, ERROR, [The call to boolean ArrayList.contains(Object) at line 23 is loop-invariant] +codetoanalyze/java/hoistingExpensive/HoistModeled.java, HoistModeled.linear_substring_hoist(java.lang.String,java.util.ArrayList,java.lang.Integer):void, 0, PURE_FUNCTION, no_bucket, ERROR, [Side-effect free function void HoistModeled.linear_substring_hoist(String,ArrayList,Integer)] +codetoanalyze/java/hoistingExpensive/HoistModeled.java, HoistModeled.linear_substring_hoist(java.lang.String,java.util.ArrayList,java.lang.Integer):void, 4, LOOP_INVARIANT_CALL, no_bucket, ERROR, [The call to String String.substring(int,int) at line 49 is loop-invariant] +codetoanalyze/java/hoistingExpensive/HoistModeled.java, HoistModeled.linear_substring_hoist(java.lang.String,java.util.ArrayList,java.lang.Integer):void, 7, LOOP_INVARIANT_CALL, no_bucket, ERROR, [The call to String String.substring(int) at line 52 is loop-invariant]